v0.2 Interactive Rule-Approval (plan-v0.2-interactive-rule-approval). Closes the silent-self-attestation bug class in Massu's auto-learning system. v0.1 (auto-learning.md:35-46) was a protocol obligation the model self-enforces at /massu-loop end, audited post-hoc by /massu-learning-audit. v0.2 adds real-time correction detection (UserPromptSubmit hook, score-based threshold ≥60), side-channel rule-candidate funnel (.massu/rule-candidates/<sha>.json), explicit slash command /massu-rule list|show|approve|dismiss|recurrence, atomic write through applyRuleCandidate() helper with audit_log UNIQUE-INDEX idempotency, three-class rubric (pattern-scanner | claude-md-cr | corrections-md) + autoLearning.customDestinations config extension point. Three-layer structural enforcement: protocol gate (slash command + hook) + idempotency lock (audit_log UNIQUE INDEX) + drift-guard vitest (rule-promotion-effectiveness.test.ts). New canonical rule CR-53 codifies "auto-learned rules must be effective". Ships as @massu/core@1.13.0 — new MCP-adjacent capability + new slash command + backwards-compatible audit_log.event_type CHECK extension (internal-process table, zero external schema consumers — minor justified per semver §7).
Added
- CR-53 / VR-INTERACTIVE-LEARNING — every
audit_logrow withevent_type='rule_promoted'older than 7 days MUST havemetadata.recurrence_count == 0. Three-layer enforcement: protocol gate (/massu-rule approveinsertsrecurrence_count: 0) + increment hook (post-tool-use.ts:incrementRecurrenceCountsForScannerFailuresvialib/recurrence-incrementer.ts) + drift-guard (rule-promotion-effectiveness.test.tsdual-channel: audit_log AND.cr53-increment-failures.jsonl). Allowlist viaMASSU_KNOWN_RULE_LIMITATIONSenv-var for documented exceptions (channel-a only; failure-log channel-b cannot be silenced). packages/core/src/rule-candidate-detector.ts—scoreCorrectionPrompt()scoring model with 9 signals + threshold ≥60. Replaces the binarycorrectionPatternsregex fromprompt-analyzer.ts:59which mis-fired on "no problem" / "no thanks". Calibration: precision=1.000, recall=0.844 on 32 labeled positives + 122 negatives..claude/commands/massu-rule.md+ pairedwebsite/content/docs/commands/massu-rule.mdx— slash command withlist | show <id> | approve <id> | dismiss <id> | recurrencesubcommands. Show-before-approve enforced via.shown-this-session.jsonl(single-keystroke read-then-act; no--forceflag — CR-46 #3).packages/core/src/rule-candidate-applier.ts— atomic-write applier with snapshot-set rollback. Single authoring surface for all 4 destinations (pattern-scanner, claude-md-cr, corrections-md, custom-destination). UNIQUE INDEXidx_audit_rule_promotedon(event_type, json_extract(metadata, '$.prompt_hash'))is the §5 idempotency lock — second approve of same prompt_hash isidempotent_noop.claude-md-crdestination splices the Canonical Rules table row AND appends body section (CR-46 #3 — no piecemeal escape hatch).packages/core/src/rule-classifier.ts— 4-rubric destination dispatch: (1) literal-grepable token → pattern-scanner, (2) process/protocol wording → claude-md-cr, (3)custom-destinationfromautoLearning.customDestinationsconfig triggerKeywords, (4) corrections-md catchall.packages/core/src/template-renderer.ts— RCE-safe template engine for custom-destination templates. Regex-substitution only, allowlist set{date, slug, score, signals_csv, prompt_preview, destination_name}.${process.exit(1)}fails identifier-shape;${process}fails allowlist. 10 explicit RCE-attempt test cases.packages/core/src/rule-candidate-renderer.ts— six-section preview renderer for/massu-rule show <id>(detected correction + reacting-to + proposed rule + destination + enforcement + conflicts).packages/core/src/rule-promotion-effectiveness.ts+tests/rule-promotion-effectiveness.test.ts— CR-53 dual-channel evaluator consumed by both the drift-guard vitest test and/massu-learning-auditSection 6.packages/core/src/lib/recurrence-incrementer.ts— extracted fromhooks/post-tool-use.ts(ARCH-04 fix; breaks test-infra ↔ esbuild-entry coupling). Tight 1-line FAIL scope per scanner violation (ARCH-05).- SQLite schema additions in
memory-db.ts:audit_logCHECK constraint extended 6 → 9 event_type values (rule_candidate_emitted,rule_promoted,rule_dismissed) via canonical 12-step recreate procedure.prompt_outcomes_signal_blacklisttable for dismissal-loop signal downweighting. UNIQUE INDEXidx_audit_rule_promoted(partial — only onrule_promotedrows). Migration helpermigrateAuditLogCheckExtension()is idempotent via CHECK-clause-shape parse with full event_type set verification (ARCH-06). AuditEntry.eventTypeunion inaudit-trail.tsextended 6 → 9 in lockstep with SQL CHECK.scripts/massu-pattern-scanner.shCheck 29 +pattern-scanner-check-29.test.tsdrift-guard — auto-generated scanner additions must carry# auto-generated by /massu-rule approve <hash> (slug=<slug>)marker followed by# Check N:within 4 lines..claude/commands/massu-learning-audit.mdSection 6 — Interactive Rule-Candidate Funnel with 6 health metrics + source-tag split + VR-INTERACTIVE-LEARNING.- 15 new vitest test files in
packages/core/src/tests/:rule-candidate-detector(49 tests),rule-candidate-detector-calibration(5),rule-candidate-renderer(9),rule-candidate-applier(10),rule-candidate-applier-extended(9),rule-candidate-applier-arch-fixes(11),rule-classifier(22),template-renderer(21),cr53-recurrence-increment(7),rule-promotion-effectiveness(10),audit-log-event-type-migration(11),pattern-scanner-check-29(3),auto-learning-source-tag(3),auto-learning-mirror-drift-guard(4). 175 new tests total. autoLearning.customDestinationsschema inpackages/core/src/config.ts+massu.config.yamldefault block. Massu framework remains generalized — project-specific destinations are config-driven, NEVER framework-baked.- v0.1 protocol amendment in
.claude/commands/massu-loop/references/auto-learning.md(+ byte-equivalent mirror atpackages/core/commands/...enforced byauto-learning-mirror-drift-guard.test.ts): before writing tocorrections.md, resolve MEMORY.md path viaencodeMemoryDirName(getProjectRoot())and check for an existingprompt_hash:line — idempotent across v0.1 + v0.2 paths. - Follow-up plan
docs/plans/2026-05-20-loop-controller-mirror-drift-closure.mdfiled to close the preexisting 78-line drift inloop-controller.mdmirror (deliberately out of scope for v0.2 per CR-46 #4).
Changed
packages/core/src/hooks/user-prompt.ts:115— wiresscoreCorrectionPrompt()into the UserPromptSubmit hook. OnemitCandidate=true, writes.massu/rule-candidates/<prompt_hash>.json(sha-keyed → idempotent on retry). Stderr nudge fires when candidate count grows since last-surfaced. Detector-swallow writes.detector-failures.jsonlfor parallel observability symmetry with the CR-53 increment hook (ARCH-07). Transcript path bound to/.claude/projects/prefix (SEC-05).packages/core/src/hooks/post-tool-use.ts— callsincrementRecurrenceCountsForScannerFailuresafterBash(massu-pattern-scanner.sh)invocations. Failure-surfaced try/catch writes to.cr53-increment-failures.jsonlso silent in-hook failures still surface in CI.DEFAULT_ABANDON_PATTERNSinprompt-analyzer.ts:22—exported so the detector can reference it (used as a comparison constant for the dedicatedCORRECTION_DISMISSAL_PATTERNin the detector). The detector itself uses a TIGHTER regex (excludes "instead" and "different" which are routine CORRECTION tokens).scripts/massu-pattern-scanner.sh— added Check 29 at the end (29 checks total, up from 28)..claude/CLAUDE.md— CR-53 row in Canonical Rules table + body section + VR-INTERACTIVE-LEARNING row in Verification Requirements table.packages/core/src/tests/codebase-introspector.test.ts— perf test now uses explicit 15s timeout + 2 retries to absorb parallel-suite measurement noise (pre-existing flake surfaced during this release's gate run; assertion stays strict at <2000ms).website/src/tests/changelog-parse.test.ts:EXPECTED_COUNTbumped 41 → 42.
Fixed
- SEC-01 path traversal —
CANDIDATE_ID_PATTERN = /^[a-f0-9]{16}$/validates candidateId at every I/O boundary inapplyRuleCandidate,readCandidate,dismissRuleCandidate. Closes the<id>like../../../tmp/fooarbitrary-file-deletion class. - SEC-02 symlink bypass —
realpathSynccontainment check oncustomDestination.pathrejects symlinks that point outsideprojectRooteven when the lexical path is contained. - SEC-03 slug shell injection —
deriveSlugis always called regardless of caller-supplied source;SLUG_ALLOWEDregex re-validates the result. Calleropts.slugis a hint, never authority. - SEC-04 candidate payload tampering —
validateCandidatePayloadruntime checksprompt_hashshape,scorefinite-range,signalsarray structure, required string fields. Planted/corrupted sidecars throwCandidatePayloadValidationErrorbefore any audit_log write. - ARCH-02 dismiss path —
dismissRuleCandidate()writes the prompt_outcomes_signal_blacklist UPSERT + audit_log row +.dismissed.jsonl+ deletes the candidate sidecar in one SQLite transaction. Closes the "documented end-to-end but the write path was unimplemented" structural drift class.