@massu/core 1.12.2 → 1.13.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +44 -0
- package/commands/massu-loop/references/auto-learning.md +1 -1
- package/dist/cli.js +94 -20
- package/dist/hooks/auto-learning-pipeline.js +11 -1
- package/dist/hooks/classify-failure.js +89 -2
- package/dist/hooks/cost-tracker.js +89 -2
- package/dist/hooks/fix-detector.js +11 -1
- package/dist/hooks/incident-pipeline.js +89 -2
- package/dist/hooks/post-edit-context.js +11 -1
- package/dist/hooks/post-tool-use.js +173 -3
- package/dist/hooks/pre-compact.js +89 -2
- package/dist/hooks/pre-delete-check.js +11 -1
- package/dist/hooks/pre-tool-use-gate.js +11 -1
- package/dist/hooks/quality-event.js +89 -2
- package/dist/hooks/rule-enforcement-pipeline.js +11 -1
- package/dist/hooks/session-end.js +89 -2
- package/dist/hooks/session-start.js +89 -2
- package/dist/hooks/user-prompt.js +346 -5
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,50 @@ All notable changes to this project will be documented in this file.
|
|
|
4
4
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
|
|
6
6
|
|
|
7
|
+
## [1.13.0] - 2026-05-20
|
|
8
|
+
|
|
9
|
+
**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).
|
|
10
|
+
|
|
11
|
+
### Added
|
|
12
|
+
|
|
13
|
+
- **CR-53 / VR-INTERACTIVE-LEARNING** — every `audit_log` row with `event_type='rule_promoted'` older than 7 days MUST have `metadata.recurrence_count == 0`. Three-layer enforcement: protocol gate (`/massu-rule approve` inserts `recurrence_count: 0`) + increment hook (`post-tool-use.ts:incrementRecurrenceCountsForScannerFailures` via `lib/recurrence-incrementer.ts`) + drift-guard (`rule-promotion-effectiveness.test.ts` dual-channel: audit_log AND `.cr53-increment-failures.jsonl`). Allowlist via `MASSU_KNOWN_RULE_LIMITATIONS` env-var for documented exceptions (channel-a only; failure-log channel-b cannot be silenced).
|
|
14
|
+
- **`packages/core/src/rule-candidate-detector.ts`** — `scoreCorrectionPrompt()` scoring model with 9 signals + threshold ≥60. Replaces the binary `correctionPatterns` regex from `prompt-analyzer.ts:59` which mis-fired on "no problem" / "no thanks". Calibration: precision=1.000, recall=0.844 on 32 labeled positives + 122 negatives.
|
|
15
|
+
- **`.claude/commands/massu-rule.md`** + paired `website/content/docs/commands/massu-rule.mdx` — slash command with `list | show <id> | approve <id> | dismiss <id> | recurrence` subcommands. Show-before-approve enforced via `.shown-this-session.jsonl` (single-keystroke read-then-act; no `--force` flag — CR-46 #3).
|
|
16
|
+
- **`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 INDEX `idx_audit_rule_promoted` on `(event_type, json_extract(metadata, '$.prompt_hash'))` is the §5 idempotency lock — second approve of same prompt_hash is `idempotent_noop`. `claude-md-cr` destination splices the Canonical Rules table row AND appends body section (CR-46 #3 — no piecemeal escape hatch).
|
|
17
|
+
- **`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-destination` from `autoLearning.customDestinations` config triggerKeywords, (4) corrections-md catchall.
|
|
18
|
+
- **`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.
|
|
19
|
+
- **`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).
|
|
20
|
+
- **`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-audit` Section 6.
|
|
21
|
+
- **`packages/core/src/lib/recurrence-incrementer.ts`** — extracted from `hooks/post-tool-use.ts` (ARCH-04 fix; breaks test-infra ↔ esbuild-entry coupling). Tight 1-line FAIL scope per scanner violation (ARCH-05).
|
|
22
|
+
- **SQLite schema additions** in `memory-db.ts`: `audit_log` CHECK constraint extended 6 → 9 event_type values (`rule_candidate_emitted`, `rule_promoted`, `rule_dismissed`) via canonical 12-step recreate procedure. `prompt_outcomes_signal_blacklist` table for dismissal-loop signal downweighting. UNIQUE INDEX `idx_audit_rule_promoted` (partial — only on `rule_promoted` rows). Migration helper `migrateAuditLogCheckExtension()` is idempotent via CHECK-clause-shape parse with full event_type set verification (ARCH-06).
|
|
23
|
+
- **`AuditEntry.eventType` union** in `audit-trail.ts` extended 6 → 9 in lockstep with SQL CHECK.
|
|
24
|
+
- **`scripts/massu-pattern-scanner.sh` Check 29** + `pattern-scanner-check-29.test.ts` drift-guard — auto-generated scanner additions must carry `# auto-generated by /massu-rule approve <hash> (slug=<slug>)` marker followed by `# Check N:` within 4 lines.
|
|
25
|
+
- **`.claude/commands/massu-learning-audit.md` Section 6** — Interactive Rule-Candidate Funnel with 6 health metrics + source-tag split + VR-INTERACTIVE-LEARNING.
|
|
26
|
+
- **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.
|
|
27
|
+
- **`autoLearning.customDestinations`** schema in `packages/core/src/config.ts` + `massu.config.yaml` default block. Massu framework remains generalized — project-specific destinations are config-driven, NEVER framework-baked.
|
|
28
|
+
- **v0.1 protocol amendment** in `.claude/commands/massu-loop/references/auto-learning.md` (+ byte-equivalent mirror at `packages/core/commands/...` enforced by `auto-learning-mirror-drift-guard.test.ts`): before writing to `corrections.md`, resolve MEMORY.md path via `encodeMemoryDirName(getProjectRoot())` and check for an existing `prompt_hash:` line — idempotent across v0.1 + v0.2 paths.
|
|
29
|
+
- **Follow-up plan** `docs/plans/2026-05-20-loop-controller-mirror-drift-closure.md` filed to close the preexisting 78-line drift in `loop-controller.md` mirror (deliberately out of scope for v0.2 per CR-46 #4).
|
|
30
|
+
|
|
31
|
+
### Changed
|
|
32
|
+
|
|
33
|
+
- **`packages/core/src/hooks/user-prompt.ts:115`** — wires `scoreCorrectionPrompt()` into the UserPromptSubmit hook. On `emitCandidate=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.jsonl` for parallel observability symmetry with the CR-53 increment hook (ARCH-07). Transcript path bound to `/.claude/projects/` prefix (SEC-05).
|
|
34
|
+
- **`packages/core/src/hooks/post-tool-use.ts`** — calls `incrementRecurrenceCountsForScannerFailures` after `Bash(massu-pattern-scanner.sh)` invocations. Failure-surfaced try/catch writes to `.cr53-increment-failures.jsonl` so silent in-hook failures still surface in CI.
|
|
35
|
+
- **`DEFAULT_ABANDON_PATTERNS`** in `prompt-analyzer.ts:22` — `export`ed so the detector can reference it (used as a comparison constant for the dedicated `CORRECTION_DISMISSAL_PATTERN` in the detector). The detector itself uses a TIGHTER regex (excludes "instead" and "different" which are routine CORRECTION tokens).
|
|
36
|
+
- **`scripts/massu-pattern-scanner.sh`** — added Check 29 at the end (29 checks total, up from 28).
|
|
37
|
+
- **`.claude/CLAUDE.md`** — CR-53 row in Canonical Rules table + body section + VR-INTERACTIVE-LEARNING row in Verification Requirements table.
|
|
38
|
+
- **`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).
|
|
39
|
+
- **`website/src/__tests__/changelog-parse.test.ts:EXPECTED_COUNT`** bumped 41 → 42.
|
|
40
|
+
|
|
41
|
+
### Fixed
|
|
42
|
+
|
|
43
|
+
Phase 1.5 multi-perspective review surfaced 5 security + 7 architecture findings; all fixed within the same plan (CR-45 zero-tolerance).
|
|
44
|
+
|
|
45
|
+
- **SEC-01 path traversal** — `CANDIDATE_ID_PATTERN = /^[a-f0-9]{16}$/` validates candidateId at every I/O boundary in `applyRuleCandidate`, `readCandidate`, `dismissRuleCandidate`. Closes the `<id>` like `../../../tmp/foo` arbitrary-file-deletion class.
|
|
46
|
+
- **SEC-02 symlink bypass** — `realpathSync` containment check on `customDestination.path` rejects symlinks that point outside `projectRoot` even when the lexical path is contained.
|
|
47
|
+
- **SEC-03 slug shell injection** — `deriveSlug` is always called regardless of caller-supplied source; `SLUG_ALLOWED` regex re-validates the result. Caller `opts.slug` is a hint, never authority.
|
|
48
|
+
- **SEC-04 candidate payload tampering** — `validateCandidatePayload` runtime checks `prompt_hash` shape, `score` finite-range, `signals` array structure, required string fields. Planted/corrupted sidecars throw `CandidatePayloadValidationError` before any audit_log write.
|
|
49
|
+
- **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.
|
|
50
|
+
|
|
7
51
|
## [1.12.2] - 2026-05-18
|
|
8
52
|
|
|
9
53
|
**Security MEDIUM sweep (`plan-2026-05-18-security-medium-sweep`)**. Closes the 5 MEDIUM findings from the post-1.12.0 `massu-security-reviewer` audit-loop with structural drift-guards for each. M-1 IP_HASH_PEPPER consistency (silent-fallback vs throw class), M-2 webhook POST schema symmetry, M-3 Lemon Squeezy webhook idempotency parity with Stripe, M-4 v1 audit UUID echo redaction, M-5 SSO IdP host allowlist (precondition for SSO re-enable). New CR-51 + VR-PEPPER-GUARD + pattern-scanner Check 27.
|
|
@@ -43,7 +43,7 @@ After Loop Completes (Zero Gaps):
|
|
|
43
43
|
- **Update pattern scanner**: Add new grep-able bad patterns to `scripts/pattern-scanner.sh`
|
|
44
44
|
- **Codebase-wide search**: Verify no other instances of same bad pattern (CR-9)
|
|
45
45
|
- **Consider new CR rule**: If a class of bug was found (not one-off), propose for CLAUDE.md
|
|
46
|
-
- **Record user corrections**: If the user corrected any behavior during this loop, add structured entry to `memory/corrections.md` with date, wrong behavior, correction, and prevention rule
|
|
46
|
+
- **Record user corrections**: If the user corrected any behavior during this loop, add structured entry to `memory/corrections.md` with date, wrong behavior, correction, and prevention rule. Before writing, resolve the MEMORY.md path via `encodeMemoryDirName(getProjectRoot())` (see `packages/core/src/lib/memory-path.ts:37`) and check that file for an existing `prompt_hash:` line matching this correction; if present, the entry was captured interactively (v0.2 — plan `plan-v0.2-interactive-rule-approval`) and this step is a no-op (CR-53 effectiveness invariant — see `.claude/CLAUDE.md`).
|
|
47
47
|
|
|
48
48
|
**A loop that fixes 5 bugs but records 0 learnings is 80% wasted. The fixes are temporary; the learnings are permanent.**
|
|
49
49
|
**A reflection that isn't persisted to memory is a learning that will be lost. Text output is not persistence.**
|
package/dist/cli.js
CHANGED
|
@@ -377,7 +377,17 @@ var init_config = __esm({
|
|
|
377
377
|
requireIncidentReport: z.boolean().default(true),
|
|
378
378
|
requirePreventionRule: z.boolean().default(true),
|
|
379
379
|
requireEnforcement: z.boolean().default(true)
|
|
380
|
-
}).default({})
|
|
380
|
+
}).default({}),
|
|
381
|
+
// plan-v0.2-interactive-rule-approval P-D-008 / P-D-009: project-configured
|
|
382
|
+
// custom destinations for the rule-candidate funnel. The classifier matches
|
|
383
|
+
// a candidate to one of these entries when none of the framework
|
|
384
|
+
// destinations (pattern-scanner / claude-md-cr / corrections-md) apply.
|
|
385
|
+
customDestinations: z.array(z.object({
|
|
386
|
+
name: z.string(),
|
|
387
|
+
path: z.string(),
|
|
388
|
+
triggerKeywords: z.array(z.string()).default([]),
|
|
389
|
+
template: z.string()
|
|
390
|
+
})).default([])
|
|
381
391
|
}).optional();
|
|
382
392
|
CloudConfigSchema = z.object({
|
|
383
393
|
enabled: z.boolean().default(false),
|
|
@@ -631,6 +641,7 @@ __export(memory_db_exports, {
|
|
|
631
641
|
incrementRetryCount: () => incrementRetryCount,
|
|
632
642
|
initMemorySchema: () => initMemorySchema,
|
|
633
643
|
linkSessionToTask: () => linkSessionToTask,
|
|
644
|
+
migrateAuditLogCheckExtension: () => migrateAuditLogCheckExtension,
|
|
634
645
|
pruneOldConversationTurns: () => pruneOldConversationTurns,
|
|
635
646
|
pruneOldObservations: () => pruneOldObservations,
|
|
636
647
|
pruneToolCostEvents: () => pruneToolCostEvents,
|
|
@@ -668,6 +679,66 @@ function pruneToolCostEvents(db) {
|
|
|
668
679
|
).run(TOOL_COST_EVENTS_RETENTION_DAYS);
|
|
669
680
|
return result.changes;
|
|
670
681
|
}
|
|
682
|
+
function migrateAuditLogCheckExtension(db) {
|
|
683
|
+
const row = db.prepare("SELECT sql FROM sqlite_master WHERE type='table' AND name='audit_log'").get();
|
|
684
|
+
if (!row) return;
|
|
685
|
+
const expected = [
|
|
686
|
+
"code_change",
|
|
687
|
+
"rule_enforced",
|
|
688
|
+
"approval",
|
|
689
|
+
"review",
|
|
690
|
+
"commit",
|
|
691
|
+
"compaction",
|
|
692
|
+
"rule_candidate_emitted",
|
|
693
|
+
"rule_promoted",
|
|
694
|
+
"rule_dismissed"
|
|
695
|
+
];
|
|
696
|
+
const checkClauseMatch = row.sql.match(/event_type\s+TEXT\s+NOT\s+NULL\s+CHECK\s*\(\s*event_type\s+IN\s*\(([\s\S]*?)\)\s*\)/i);
|
|
697
|
+
if (checkClauseMatch) {
|
|
698
|
+
const values = (checkClauseMatch[1].match(/'([^']+)'/g) ?? []).map((s) => s.slice(1, -1));
|
|
699
|
+
if (expected.every((v3) => values.includes(v3))) return;
|
|
700
|
+
}
|
|
701
|
+
db.pragma("foreign_keys = OFF");
|
|
702
|
+
try {
|
|
703
|
+
db.exec("BEGIN TRANSACTION");
|
|
704
|
+
db.exec(`
|
|
705
|
+
CREATE TABLE audit_log_new (
|
|
706
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
707
|
+
session_id TEXT NOT NULL,
|
|
708
|
+
timestamp TEXT DEFAULT (datetime('now')),
|
|
709
|
+
event_type TEXT NOT NULL CHECK(event_type IN (
|
|
710
|
+
'code_change', 'rule_enforced', 'approval', 'review', 'commit', 'compaction',
|
|
711
|
+
'rule_candidate_emitted', 'rule_promoted', 'rule_dismissed'
|
|
712
|
+
)),
|
|
713
|
+
actor TEXT NOT NULL DEFAULT 'ai' CHECK(actor IN ('ai', 'human', 'hook', 'agent')),
|
|
714
|
+
model_id TEXT,
|
|
715
|
+
file_path TEXT,
|
|
716
|
+
change_type TEXT CHECK(change_type IN ('create', 'edit', 'delete')),
|
|
717
|
+
rules_in_effect TEXT,
|
|
718
|
+
approval_status TEXT CHECK(approval_status IN ('auto_approved', 'human_approved', 'pending', 'denied')),
|
|
719
|
+
evidence TEXT,
|
|
720
|
+
metadata TEXT,
|
|
721
|
+
FOREIGN KEY (session_id) REFERENCES sessions(session_id) ON DELETE CASCADE
|
|
722
|
+
);
|
|
723
|
+
INSERT INTO audit_log_new SELECT * FROM audit_log;
|
|
724
|
+
DROP TABLE audit_log;
|
|
725
|
+
ALTER TABLE audit_log_new RENAME TO audit_log;
|
|
726
|
+
CREATE INDEX IF NOT EXISTS idx_al_session ON audit_log(session_id);
|
|
727
|
+
CREATE INDEX IF NOT EXISTS idx_al_file ON audit_log(file_path);
|
|
728
|
+
CREATE INDEX IF NOT EXISTS idx_al_event ON audit_log(event_type);
|
|
729
|
+
CREATE INDEX IF NOT EXISTS idx_al_timestamp ON audit_log(timestamp DESC);
|
|
730
|
+
CREATE UNIQUE INDEX IF NOT EXISTS idx_audit_rule_promoted
|
|
731
|
+
ON audit_log (event_type, json_extract(metadata, '$.prompt_hash'))
|
|
732
|
+
WHERE event_type = 'rule_promoted';
|
|
733
|
+
`);
|
|
734
|
+
db.exec("COMMIT");
|
|
735
|
+
} catch (err) {
|
|
736
|
+
db.exec("ROLLBACK");
|
|
737
|
+
throw err;
|
|
738
|
+
} finally {
|
|
739
|
+
db.pragma("foreign_keys = ON");
|
|
740
|
+
}
|
|
741
|
+
}
|
|
671
742
|
function initMemorySchema(db) {
|
|
672
743
|
db.exec(`
|
|
673
744
|
-- Sessions table (linked to Claude Code session IDs)
|
|
@@ -957,7 +1028,10 @@ function initMemorySchema(db) {
|
|
|
957
1028
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
958
1029
|
session_id TEXT NOT NULL,
|
|
959
1030
|
timestamp TEXT DEFAULT (datetime('now')),
|
|
960
|
-
event_type TEXT NOT NULL CHECK(event_type IN (
|
|
1031
|
+
event_type TEXT NOT NULL CHECK(event_type IN (
|
|
1032
|
+
'code_change', 'rule_enforced', 'approval', 'review', 'commit', 'compaction',
|
|
1033
|
+
'rule_candidate_emitted', 'rule_promoted', 'rule_dismissed'
|
|
1034
|
+
)),
|
|
961
1035
|
actor TEXT NOT NULL DEFAULT 'ai' CHECK(actor IN ('ai', 'human', 'hook', 'agent')),
|
|
962
1036
|
model_id TEXT,
|
|
963
1037
|
file_path TEXT,
|
|
@@ -972,7 +1046,21 @@ function initMemorySchema(db) {
|
|
|
972
1046
|
CREATE INDEX IF NOT EXISTS idx_al_file ON audit_log(file_path);
|
|
973
1047
|
CREATE INDEX IF NOT EXISTS idx_al_event ON audit_log(event_type);
|
|
974
1048
|
CREATE INDEX IF NOT EXISTS idx_al_timestamp ON audit_log(timestamp DESC);
|
|
1049
|
+
CREATE UNIQUE INDEX IF NOT EXISTS idx_audit_rule_promoted
|
|
1050
|
+
ON audit_log (event_type, json_extract(metadata, '$.prompt_hash'))
|
|
1051
|
+
WHERE event_type = 'rule_promoted';
|
|
1052
|
+
`);
|
|
1053
|
+
db.exec(`
|
|
1054
|
+
CREATE TABLE IF NOT EXISTS prompt_outcomes_signal_blacklist (
|
|
1055
|
+
signal TEXT PRIMARY KEY,
|
|
1056
|
+
dismissal_count INTEGER NOT NULL DEFAULT 0,
|
|
1057
|
+
first_dismissed_at TEXT DEFAULT (datetime('now')),
|
|
1058
|
+
last_dismissed_at TEXT DEFAULT (datetime('now'))
|
|
1059
|
+
);
|
|
1060
|
+
CREATE INDEX IF NOT EXISTS idx_psb_count
|
|
1061
|
+
ON prompt_outcomes_signal_blacklist(dismissal_count DESC);
|
|
975
1062
|
`);
|
|
1063
|
+
migrateAuditLogCheckExtension(db);
|
|
976
1064
|
db.exec(`
|
|
977
1065
|
CREATE TABLE IF NOT EXISTS validation_results (
|
|
978
1066
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
@@ -15219,26 +15307,12 @@ var __filename4, __dirname4, HOOK_NAME_TO_FILE;
|
|
|
15219
15307
|
var init_hook_runner = __esm({
|
|
15220
15308
|
"src/commands/hook-runner.ts"() {
|
|
15221
15309
|
"use strict";
|
|
15310
|
+
init_hook_registry();
|
|
15222
15311
|
__filename4 = fileURLToPath4(import.meta.url);
|
|
15223
15312
|
__dirname4 = dirname8(__filename4);
|
|
15224
|
-
HOOK_NAME_TO_FILE =
|
|
15225
|
-
|
|
15226
|
-
|
|
15227
|
-
"security-gate": "security-gate.js",
|
|
15228
|
-
"pre-delete-check": "pre-delete-check.js",
|
|
15229
|
-
"post-tool-use": "post-tool-use.js",
|
|
15230
|
-
"post-edit-context": "post-edit-context.js",
|
|
15231
|
-
"quality-event": "quality-event.js",
|
|
15232
|
-
"cost-tracker": "cost-tracker.js",
|
|
15233
|
-
"fix-detector": "fix-detector.js",
|
|
15234
|
-
"classify-failure": "classify-failure.js",
|
|
15235
|
-
"incident-pipeline": "incident-pipeline.js",
|
|
15236
|
-
"rule-enforcement-pipeline": "rule-enforcement-pipeline.js",
|
|
15237
|
-
"auto-learning-pipeline": "auto-learning-pipeline.js",
|
|
15238
|
-
"pre-compact": "pre-compact.js",
|
|
15239
|
-
"user-prompt": "user-prompt.js",
|
|
15240
|
-
"intent-suggester": "intent-suggester.js"
|
|
15241
|
-
};
|
|
15313
|
+
HOOK_NAME_TO_FILE = Object.freeze(
|
|
15314
|
+
Object.fromEntries(REGISTERED_HOOKS.map((name) => [name, `${name}.js`]))
|
|
15315
|
+
);
|
|
15242
15316
|
}
|
|
15243
15317
|
});
|
|
15244
15318
|
|
|
@@ -168,7 +168,17 @@ var AutoLearningConfigSchema = z.object({
|
|
|
168
168
|
requireIncidentReport: z.boolean().default(true),
|
|
169
169
|
requirePreventionRule: z.boolean().default(true),
|
|
170
170
|
requireEnforcement: z.boolean().default(true)
|
|
171
|
-
}).default({})
|
|
171
|
+
}).default({}),
|
|
172
|
+
// plan-v0.2-interactive-rule-approval P-D-008 / P-D-009: project-configured
|
|
173
|
+
// custom destinations for the rule-candidate funnel. The classifier matches
|
|
174
|
+
// a candidate to one of these entries when none of the framework
|
|
175
|
+
// destinations (pattern-scanner / claude-md-cr / corrections-md) apply.
|
|
176
|
+
customDestinations: z.array(z.object({
|
|
177
|
+
name: z.string(),
|
|
178
|
+
path: z.string(),
|
|
179
|
+
triggerKeywords: z.array(z.string()).default([]),
|
|
180
|
+
template: z.string()
|
|
181
|
+
})).default([])
|
|
172
182
|
}).optional();
|
|
173
183
|
var CloudConfigSchema = z.object({
|
|
174
184
|
enabled: z.boolean().default(false),
|
|
@@ -175,7 +175,17 @@ var AutoLearningConfigSchema = z.object({
|
|
|
175
175
|
requireIncidentReport: z.boolean().default(true),
|
|
176
176
|
requirePreventionRule: z.boolean().default(true),
|
|
177
177
|
requireEnforcement: z.boolean().default(true)
|
|
178
|
-
}).default({})
|
|
178
|
+
}).default({}),
|
|
179
|
+
// plan-v0.2-interactive-rule-approval P-D-008 / P-D-009: project-configured
|
|
180
|
+
// custom destinations for the rule-candidate funnel. The classifier matches
|
|
181
|
+
// a candidate to one of these entries when none of the framework
|
|
182
|
+
// destinations (pattern-scanner / claude-md-cr / corrections-md) apply.
|
|
183
|
+
customDestinations: z.array(z.object({
|
|
184
|
+
name: z.string(),
|
|
185
|
+
path: z.string(),
|
|
186
|
+
triggerKeywords: z.array(z.string()).default([]),
|
|
187
|
+
template: z.string()
|
|
188
|
+
})).default([])
|
|
179
189
|
}).optional();
|
|
180
190
|
var CloudConfigSchema = z.object({
|
|
181
191
|
enabled: z.boolean().default(false),
|
|
@@ -565,6 +575,66 @@ function getMemoryDb() {
|
|
|
565
575
|
initMemorySchema(db);
|
|
566
576
|
return db;
|
|
567
577
|
}
|
|
578
|
+
function migrateAuditLogCheckExtension(db) {
|
|
579
|
+
const row = db.prepare("SELECT sql FROM sqlite_master WHERE type='table' AND name='audit_log'").get();
|
|
580
|
+
if (!row) return;
|
|
581
|
+
const expected = [
|
|
582
|
+
"code_change",
|
|
583
|
+
"rule_enforced",
|
|
584
|
+
"approval",
|
|
585
|
+
"review",
|
|
586
|
+
"commit",
|
|
587
|
+
"compaction",
|
|
588
|
+
"rule_candidate_emitted",
|
|
589
|
+
"rule_promoted",
|
|
590
|
+
"rule_dismissed"
|
|
591
|
+
];
|
|
592
|
+
const checkClauseMatch = row.sql.match(/event_type\s+TEXT\s+NOT\s+NULL\s+CHECK\s*\(\s*event_type\s+IN\s*\(([\s\S]*?)\)\s*\)/i);
|
|
593
|
+
if (checkClauseMatch) {
|
|
594
|
+
const values = (checkClauseMatch[1].match(/'([^']+)'/g) ?? []).map((s) => s.slice(1, -1));
|
|
595
|
+
if (expected.every((v) => values.includes(v))) return;
|
|
596
|
+
}
|
|
597
|
+
db.pragma("foreign_keys = OFF");
|
|
598
|
+
try {
|
|
599
|
+
db.exec("BEGIN TRANSACTION");
|
|
600
|
+
db.exec(`
|
|
601
|
+
CREATE TABLE audit_log_new (
|
|
602
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
603
|
+
session_id TEXT NOT NULL,
|
|
604
|
+
timestamp TEXT DEFAULT (datetime('now')),
|
|
605
|
+
event_type TEXT NOT NULL CHECK(event_type IN (
|
|
606
|
+
'code_change', 'rule_enforced', 'approval', 'review', 'commit', 'compaction',
|
|
607
|
+
'rule_candidate_emitted', 'rule_promoted', 'rule_dismissed'
|
|
608
|
+
)),
|
|
609
|
+
actor TEXT NOT NULL DEFAULT 'ai' CHECK(actor IN ('ai', 'human', 'hook', 'agent')),
|
|
610
|
+
model_id TEXT,
|
|
611
|
+
file_path TEXT,
|
|
612
|
+
change_type TEXT CHECK(change_type IN ('create', 'edit', 'delete')),
|
|
613
|
+
rules_in_effect TEXT,
|
|
614
|
+
approval_status TEXT CHECK(approval_status IN ('auto_approved', 'human_approved', 'pending', 'denied')),
|
|
615
|
+
evidence TEXT,
|
|
616
|
+
metadata TEXT,
|
|
617
|
+
FOREIGN KEY (session_id) REFERENCES sessions(session_id) ON DELETE CASCADE
|
|
618
|
+
);
|
|
619
|
+
INSERT INTO audit_log_new SELECT * FROM audit_log;
|
|
620
|
+
DROP TABLE audit_log;
|
|
621
|
+
ALTER TABLE audit_log_new RENAME TO audit_log;
|
|
622
|
+
CREATE INDEX IF NOT EXISTS idx_al_session ON audit_log(session_id);
|
|
623
|
+
CREATE INDEX IF NOT EXISTS idx_al_file ON audit_log(file_path);
|
|
624
|
+
CREATE INDEX IF NOT EXISTS idx_al_event ON audit_log(event_type);
|
|
625
|
+
CREATE INDEX IF NOT EXISTS idx_al_timestamp ON audit_log(timestamp DESC);
|
|
626
|
+
CREATE UNIQUE INDEX IF NOT EXISTS idx_audit_rule_promoted
|
|
627
|
+
ON audit_log (event_type, json_extract(metadata, '$.prompt_hash'))
|
|
628
|
+
WHERE event_type = 'rule_promoted';
|
|
629
|
+
`);
|
|
630
|
+
db.exec("COMMIT");
|
|
631
|
+
} catch (err) {
|
|
632
|
+
db.exec("ROLLBACK");
|
|
633
|
+
throw err;
|
|
634
|
+
} finally {
|
|
635
|
+
db.pragma("foreign_keys = ON");
|
|
636
|
+
}
|
|
637
|
+
}
|
|
568
638
|
function initMemorySchema(db) {
|
|
569
639
|
db.exec(`
|
|
570
640
|
-- Sessions table (linked to Claude Code session IDs)
|
|
@@ -854,7 +924,10 @@ function initMemorySchema(db) {
|
|
|
854
924
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
855
925
|
session_id TEXT NOT NULL,
|
|
856
926
|
timestamp TEXT DEFAULT (datetime('now')),
|
|
857
|
-
event_type TEXT NOT NULL CHECK(event_type IN (
|
|
927
|
+
event_type TEXT NOT NULL CHECK(event_type IN (
|
|
928
|
+
'code_change', 'rule_enforced', 'approval', 'review', 'commit', 'compaction',
|
|
929
|
+
'rule_candidate_emitted', 'rule_promoted', 'rule_dismissed'
|
|
930
|
+
)),
|
|
858
931
|
actor TEXT NOT NULL DEFAULT 'ai' CHECK(actor IN ('ai', 'human', 'hook', 'agent')),
|
|
859
932
|
model_id TEXT,
|
|
860
933
|
file_path TEXT,
|
|
@@ -869,7 +942,21 @@ function initMemorySchema(db) {
|
|
|
869
942
|
CREATE INDEX IF NOT EXISTS idx_al_file ON audit_log(file_path);
|
|
870
943
|
CREATE INDEX IF NOT EXISTS idx_al_event ON audit_log(event_type);
|
|
871
944
|
CREATE INDEX IF NOT EXISTS idx_al_timestamp ON audit_log(timestamp DESC);
|
|
945
|
+
CREATE UNIQUE INDEX IF NOT EXISTS idx_audit_rule_promoted
|
|
946
|
+
ON audit_log (event_type, json_extract(metadata, '$.prompt_hash'))
|
|
947
|
+
WHERE event_type = 'rule_promoted';
|
|
948
|
+
`);
|
|
949
|
+
db.exec(`
|
|
950
|
+
CREATE TABLE IF NOT EXISTS prompt_outcomes_signal_blacklist (
|
|
951
|
+
signal TEXT PRIMARY KEY,
|
|
952
|
+
dismissal_count INTEGER NOT NULL DEFAULT 0,
|
|
953
|
+
first_dismissed_at TEXT DEFAULT (datetime('now')),
|
|
954
|
+
last_dismissed_at TEXT DEFAULT (datetime('now'))
|
|
955
|
+
);
|
|
956
|
+
CREATE INDEX IF NOT EXISTS idx_psb_count
|
|
957
|
+
ON prompt_outcomes_signal_blacklist(dismissal_count DESC);
|
|
872
958
|
`);
|
|
959
|
+
migrateAuditLogCheckExtension(db);
|
|
873
960
|
db.exec(`
|
|
874
961
|
CREATE TABLE IF NOT EXISTS validation_results (
|
|
875
962
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
@@ -175,7 +175,17 @@ var AutoLearningConfigSchema = z.object({
|
|
|
175
175
|
requireIncidentReport: z.boolean().default(true),
|
|
176
176
|
requirePreventionRule: z.boolean().default(true),
|
|
177
177
|
requireEnforcement: z.boolean().default(true)
|
|
178
|
-
}).default({})
|
|
178
|
+
}).default({}),
|
|
179
|
+
// plan-v0.2-interactive-rule-approval P-D-008 / P-D-009: project-configured
|
|
180
|
+
// custom destinations for the rule-candidate funnel. The classifier matches
|
|
181
|
+
// a candidate to one of these entries when none of the framework
|
|
182
|
+
// destinations (pattern-scanner / claude-md-cr / corrections-md) apply.
|
|
183
|
+
customDestinations: z.array(z.object({
|
|
184
|
+
name: z.string(),
|
|
185
|
+
path: z.string(),
|
|
186
|
+
triggerKeywords: z.array(z.string()).default([]),
|
|
187
|
+
template: z.string()
|
|
188
|
+
})).default([])
|
|
179
189
|
}).optional();
|
|
180
190
|
var CloudConfigSchema = z.object({
|
|
181
191
|
enabled: z.boolean().default(false),
|
|
@@ -562,6 +572,66 @@ function getMemoryDb() {
|
|
|
562
572
|
initMemorySchema(db);
|
|
563
573
|
return db;
|
|
564
574
|
}
|
|
575
|
+
function migrateAuditLogCheckExtension(db) {
|
|
576
|
+
const row = db.prepare("SELECT sql FROM sqlite_master WHERE type='table' AND name='audit_log'").get();
|
|
577
|
+
if (!row) return;
|
|
578
|
+
const expected = [
|
|
579
|
+
"code_change",
|
|
580
|
+
"rule_enforced",
|
|
581
|
+
"approval",
|
|
582
|
+
"review",
|
|
583
|
+
"commit",
|
|
584
|
+
"compaction",
|
|
585
|
+
"rule_candidate_emitted",
|
|
586
|
+
"rule_promoted",
|
|
587
|
+
"rule_dismissed"
|
|
588
|
+
];
|
|
589
|
+
const checkClauseMatch = row.sql.match(/event_type\s+TEXT\s+NOT\s+NULL\s+CHECK\s*\(\s*event_type\s+IN\s*\(([\s\S]*?)\)\s*\)/i);
|
|
590
|
+
if (checkClauseMatch) {
|
|
591
|
+
const values = (checkClauseMatch[1].match(/'([^']+)'/g) ?? []).map((s) => s.slice(1, -1));
|
|
592
|
+
if (expected.every((v) => values.includes(v))) return;
|
|
593
|
+
}
|
|
594
|
+
db.pragma("foreign_keys = OFF");
|
|
595
|
+
try {
|
|
596
|
+
db.exec("BEGIN TRANSACTION");
|
|
597
|
+
db.exec(`
|
|
598
|
+
CREATE TABLE audit_log_new (
|
|
599
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
600
|
+
session_id TEXT NOT NULL,
|
|
601
|
+
timestamp TEXT DEFAULT (datetime('now')),
|
|
602
|
+
event_type TEXT NOT NULL CHECK(event_type IN (
|
|
603
|
+
'code_change', 'rule_enforced', 'approval', 'review', 'commit', 'compaction',
|
|
604
|
+
'rule_candidate_emitted', 'rule_promoted', 'rule_dismissed'
|
|
605
|
+
)),
|
|
606
|
+
actor TEXT NOT NULL DEFAULT 'ai' CHECK(actor IN ('ai', 'human', 'hook', 'agent')),
|
|
607
|
+
model_id TEXT,
|
|
608
|
+
file_path TEXT,
|
|
609
|
+
change_type TEXT CHECK(change_type IN ('create', 'edit', 'delete')),
|
|
610
|
+
rules_in_effect TEXT,
|
|
611
|
+
approval_status TEXT CHECK(approval_status IN ('auto_approved', 'human_approved', 'pending', 'denied')),
|
|
612
|
+
evidence TEXT,
|
|
613
|
+
metadata TEXT,
|
|
614
|
+
FOREIGN KEY (session_id) REFERENCES sessions(session_id) ON DELETE CASCADE
|
|
615
|
+
);
|
|
616
|
+
INSERT INTO audit_log_new SELECT * FROM audit_log;
|
|
617
|
+
DROP TABLE audit_log;
|
|
618
|
+
ALTER TABLE audit_log_new RENAME TO audit_log;
|
|
619
|
+
CREATE INDEX IF NOT EXISTS idx_al_session ON audit_log(session_id);
|
|
620
|
+
CREATE INDEX IF NOT EXISTS idx_al_file ON audit_log(file_path);
|
|
621
|
+
CREATE INDEX IF NOT EXISTS idx_al_event ON audit_log(event_type);
|
|
622
|
+
CREATE INDEX IF NOT EXISTS idx_al_timestamp ON audit_log(timestamp DESC);
|
|
623
|
+
CREATE UNIQUE INDEX IF NOT EXISTS idx_audit_rule_promoted
|
|
624
|
+
ON audit_log (event_type, json_extract(metadata, '$.prompt_hash'))
|
|
625
|
+
WHERE event_type = 'rule_promoted';
|
|
626
|
+
`);
|
|
627
|
+
db.exec("COMMIT");
|
|
628
|
+
} catch (err) {
|
|
629
|
+
db.exec("ROLLBACK");
|
|
630
|
+
throw err;
|
|
631
|
+
} finally {
|
|
632
|
+
db.pragma("foreign_keys = ON");
|
|
633
|
+
}
|
|
634
|
+
}
|
|
565
635
|
function initMemorySchema(db) {
|
|
566
636
|
db.exec(`
|
|
567
637
|
-- Sessions table (linked to Claude Code session IDs)
|
|
@@ -851,7 +921,10 @@ function initMemorySchema(db) {
|
|
|
851
921
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
852
922
|
session_id TEXT NOT NULL,
|
|
853
923
|
timestamp TEXT DEFAULT (datetime('now')),
|
|
854
|
-
event_type TEXT NOT NULL CHECK(event_type IN (
|
|
924
|
+
event_type TEXT NOT NULL CHECK(event_type IN (
|
|
925
|
+
'code_change', 'rule_enforced', 'approval', 'review', 'commit', 'compaction',
|
|
926
|
+
'rule_candidate_emitted', 'rule_promoted', 'rule_dismissed'
|
|
927
|
+
)),
|
|
855
928
|
actor TEXT NOT NULL DEFAULT 'ai' CHECK(actor IN ('ai', 'human', 'hook', 'agent')),
|
|
856
929
|
model_id TEXT,
|
|
857
930
|
file_path TEXT,
|
|
@@ -866,7 +939,21 @@ function initMemorySchema(db) {
|
|
|
866
939
|
CREATE INDEX IF NOT EXISTS idx_al_file ON audit_log(file_path);
|
|
867
940
|
CREATE INDEX IF NOT EXISTS idx_al_event ON audit_log(event_type);
|
|
868
941
|
CREATE INDEX IF NOT EXISTS idx_al_timestamp ON audit_log(timestamp DESC);
|
|
942
|
+
CREATE UNIQUE INDEX IF NOT EXISTS idx_audit_rule_promoted
|
|
943
|
+
ON audit_log (event_type, json_extract(metadata, '$.prompt_hash'))
|
|
944
|
+
WHERE event_type = 'rule_promoted';
|
|
945
|
+
`);
|
|
946
|
+
db.exec(`
|
|
947
|
+
CREATE TABLE IF NOT EXISTS prompt_outcomes_signal_blacklist (
|
|
948
|
+
signal TEXT PRIMARY KEY,
|
|
949
|
+
dismissal_count INTEGER NOT NULL DEFAULT 0,
|
|
950
|
+
first_dismissed_at TEXT DEFAULT (datetime('now')),
|
|
951
|
+
last_dismissed_at TEXT DEFAULT (datetime('now'))
|
|
952
|
+
);
|
|
953
|
+
CREATE INDEX IF NOT EXISTS idx_psb_count
|
|
954
|
+
ON prompt_outcomes_signal_blacklist(dismissal_count DESC);
|
|
869
955
|
`);
|
|
956
|
+
migrateAuditLogCheckExtension(db);
|
|
870
957
|
db.exec(`
|
|
871
958
|
CREATE TABLE IF NOT EXISTS validation_results (
|
|
872
959
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
@@ -174,7 +174,17 @@ var AutoLearningConfigSchema = z.object({
|
|
|
174
174
|
requireIncidentReport: z.boolean().default(true),
|
|
175
175
|
requirePreventionRule: z.boolean().default(true),
|
|
176
176
|
requireEnforcement: z.boolean().default(true)
|
|
177
|
-
}).default({})
|
|
177
|
+
}).default({}),
|
|
178
|
+
// plan-v0.2-interactive-rule-approval P-D-008 / P-D-009: project-configured
|
|
179
|
+
// custom destinations for the rule-candidate funnel. The classifier matches
|
|
180
|
+
// a candidate to one of these entries when none of the framework
|
|
181
|
+
// destinations (pattern-scanner / claude-md-cr / corrections-md) apply.
|
|
182
|
+
customDestinations: z.array(z.object({
|
|
183
|
+
name: z.string(),
|
|
184
|
+
path: z.string(),
|
|
185
|
+
triggerKeywords: z.array(z.string()).default([]),
|
|
186
|
+
template: z.string()
|
|
187
|
+
})).default([])
|
|
178
188
|
}).optional();
|
|
179
189
|
var CloudConfigSchema = z.object({
|
|
180
190
|
enabled: z.boolean().default(false),
|
|
@@ -174,7 +174,17 @@ var AutoLearningConfigSchema = z.object({
|
|
|
174
174
|
requireIncidentReport: z.boolean().default(true),
|
|
175
175
|
requirePreventionRule: z.boolean().default(true),
|
|
176
176
|
requireEnforcement: z.boolean().default(true)
|
|
177
|
-
}).default({})
|
|
177
|
+
}).default({}),
|
|
178
|
+
// plan-v0.2-interactive-rule-approval P-D-008 / P-D-009: project-configured
|
|
179
|
+
// custom destinations for the rule-candidate funnel. The classifier matches
|
|
180
|
+
// a candidate to one of these entries when none of the framework
|
|
181
|
+
// destinations (pattern-scanner / claude-md-cr / corrections-md) apply.
|
|
182
|
+
customDestinations: z.array(z.object({
|
|
183
|
+
name: z.string(),
|
|
184
|
+
path: z.string(),
|
|
185
|
+
triggerKeywords: z.array(z.string()).default([]),
|
|
186
|
+
template: z.string()
|
|
187
|
+
})).default([])
|
|
178
188
|
}).optional();
|
|
179
189
|
var CloudConfigSchema = z.object({
|
|
180
190
|
enabled: z.boolean().default(false),
|
|
@@ -564,6 +574,66 @@ function getMemoryDb() {
|
|
|
564
574
|
initMemorySchema(db);
|
|
565
575
|
return db;
|
|
566
576
|
}
|
|
577
|
+
function migrateAuditLogCheckExtension(db) {
|
|
578
|
+
const row = db.prepare("SELECT sql FROM sqlite_master WHERE type='table' AND name='audit_log'").get();
|
|
579
|
+
if (!row) return;
|
|
580
|
+
const expected = [
|
|
581
|
+
"code_change",
|
|
582
|
+
"rule_enforced",
|
|
583
|
+
"approval",
|
|
584
|
+
"review",
|
|
585
|
+
"commit",
|
|
586
|
+
"compaction",
|
|
587
|
+
"rule_candidate_emitted",
|
|
588
|
+
"rule_promoted",
|
|
589
|
+
"rule_dismissed"
|
|
590
|
+
];
|
|
591
|
+
const checkClauseMatch = row.sql.match(/event_type\s+TEXT\s+NOT\s+NULL\s+CHECK\s*\(\s*event_type\s+IN\s*\(([\s\S]*?)\)\s*\)/i);
|
|
592
|
+
if (checkClauseMatch) {
|
|
593
|
+
const values = (checkClauseMatch[1].match(/'([^']+)'/g) ?? []).map((s) => s.slice(1, -1));
|
|
594
|
+
if (expected.every((v) => values.includes(v))) return;
|
|
595
|
+
}
|
|
596
|
+
db.pragma("foreign_keys = OFF");
|
|
597
|
+
try {
|
|
598
|
+
db.exec("BEGIN TRANSACTION");
|
|
599
|
+
db.exec(`
|
|
600
|
+
CREATE TABLE audit_log_new (
|
|
601
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
602
|
+
session_id TEXT NOT NULL,
|
|
603
|
+
timestamp TEXT DEFAULT (datetime('now')),
|
|
604
|
+
event_type TEXT NOT NULL CHECK(event_type IN (
|
|
605
|
+
'code_change', 'rule_enforced', 'approval', 'review', 'commit', 'compaction',
|
|
606
|
+
'rule_candidate_emitted', 'rule_promoted', 'rule_dismissed'
|
|
607
|
+
)),
|
|
608
|
+
actor TEXT NOT NULL DEFAULT 'ai' CHECK(actor IN ('ai', 'human', 'hook', 'agent')),
|
|
609
|
+
model_id TEXT,
|
|
610
|
+
file_path TEXT,
|
|
611
|
+
change_type TEXT CHECK(change_type IN ('create', 'edit', 'delete')),
|
|
612
|
+
rules_in_effect TEXT,
|
|
613
|
+
approval_status TEXT CHECK(approval_status IN ('auto_approved', 'human_approved', 'pending', 'denied')),
|
|
614
|
+
evidence TEXT,
|
|
615
|
+
metadata TEXT,
|
|
616
|
+
FOREIGN KEY (session_id) REFERENCES sessions(session_id) ON DELETE CASCADE
|
|
617
|
+
);
|
|
618
|
+
INSERT INTO audit_log_new SELECT * FROM audit_log;
|
|
619
|
+
DROP TABLE audit_log;
|
|
620
|
+
ALTER TABLE audit_log_new RENAME TO audit_log;
|
|
621
|
+
CREATE INDEX IF NOT EXISTS idx_al_session ON audit_log(session_id);
|
|
622
|
+
CREATE INDEX IF NOT EXISTS idx_al_file ON audit_log(file_path);
|
|
623
|
+
CREATE INDEX IF NOT EXISTS idx_al_event ON audit_log(event_type);
|
|
624
|
+
CREATE INDEX IF NOT EXISTS idx_al_timestamp ON audit_log(timestamp DESC);
|
|
625
|
+
CREATE UNIQUE INDEX IF NOT EXISTS idx_audit_rule_promoted
|
|
626
|
+
ON audit_log (event_type, json_extract(metadata, '$.prompt_hash'))
|
|
627
|
+
WHERE event_type = 'rule_promoted';
|
|
628
|
+
`);
|
|
629
|
+
db.exec("COMMIT");
|
|
630
|
+
} catch (err) {
|
|
631
|
+
db.exec("ROLLBACK");
|
|
632
|
+
throw err;
|
|
633
|
+
} finally {
|
|
634
|
+
db.pragma("foreign_keys = ON");
|
|
635
|
+
}
|
|
636
|
+
}
|
|
567
637
|
function initMemorySchema(db) {
|
|
568
638
|
db.exec(`
|
|
569
639
|
-- Sessions table (linked to Claude Code session IDs)
|
|
@@ -853,7 +923,10 @@ function initMemorySchema(db) {
|
|
|
853
923
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
854
924
|
session_id TEXT NOT NULL,
|
|
855
925
|
timestamp TEXT DEFAULT (datetime('now')),
|
|
856
|
-
event_type TEXT NOT NULL CHECK(event_type IN (
|
|
926
|
+
event_type TEXT NOT NULL CHECK(event_type IN (
|
|
927
|
+
'code_change', 'rule_enforced', 'approval', 'review', 'commit', 'compaction',
|
|
928
|
+
'rule_candidate_emitted', 'rule_promoted', 'rule_dismissed'
|
|
929
|
+
)),
|
|
857
930
|
actor TEXT NOT NULL DEFAULT 'ai' CHECK(actor IN ('ai', 'human', 'hook', 'agent')),
|
|
858
931
|
model_id TEXT,
|
|
859
932
|
file_path TEXT,
|
|
@@ -868,7 +941,21 @@ function initMemorySchema(db) {
|
|
|
868
941
|
CREATE INDEX IF NOT EXISTS idx_al_file ON audit_log(file_path);
|
|
869
942
|
CREATE INDEX IF NOT EXISTS idx_al_event ON audit_log(event_type);
|
|
870
943
|
CREATE INDEX IF NOT EXISTS idx_al_timestamp ON audit_log(timestamp DESC);
|
|
944
|
+
CREATE UNIQUE INDEX IF NOT EXISTS idx_audit_rule_promoted
|
|
945
|
+
ON audit_log (event_type, json_extract(metadata, '$.prompt_hash'))
|
|
946
|
+
WHERE event_type = 'rule_promoted';
|
|
947
|
+
`);
|
|
948
|
+
db.exec(`
|
|
949
|
+
CREATE TABLE IF NOT EXISTS prompt_outcomes_signal_blacklist (
|
|
950
|
+
signal TEXT PRIMARY KEY,
|
|
951
|
+
dismissal_count INTEGER NOT NULL DEFAULT 0,
|
|
952
|
+
first_dismissed_at TEXT DEFAULT (datetime('now')),
|
|
953
|
+
last_dismissed_at TEXT DEFAULT (datetime('now'))
|
|
954
|
+
);
|
|
955
|
+
CREATE INDEX IF NOT EXISTS idx_psb_count
|
|
956
|
+
ON prompt_outcomes_signal_blacklist(dismissal_count DESC);
|
|
871
957
|
`);
|
|
958
|
+
migrateAuditLogCheckExtension(db);
|
|
872
959
|
db.exec(`
|
|
873
960
|
CREATE TABLE IF NOT EXISTS validation_results (
|
|
874
961
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
@@ -173,7 +173,17 @@ var AutoLearningConfigSchema = z.object({
|
|
|
173
173
|
requireIncidentReport: z.boolean().default(true),
|
|
174
174
|
requirePreventionRule: z.boolean().default(true),
|
|
175
175
|
requireEnforcement: z.boolean().default(true)
|
|
176
|
-
}).default({})
|
|
176
|
+
}).default({}),
|
|
177
|
+
// plan-v0.2-interactive-rule-approval P-D-008 / P-D-009: project-configured
|
|
178
|
+
// custom destinations for the rule-candidate funnel. The classifier matches
|
|
179
|
+
// a candidate to one of these entries when none of the framework
|
|
180
|
+
// destinations (pattern-scanner / claude-md-cr / corrections-md) apply.
|
|
181
|
+
customDestinations: z.array(z.object({
|
|
182
|
+
name: z.string(),
|
|
183
|
+
path: z.string(),
|
|
184
|
+
triggerKeywords: z.array(z.string()).default([]),
|
|
185
|
+
template: z.string()
|
|
186
|
+
})).default([])
|
|
177
187
|
}).optional();
|
|
178
188
|
var CloudConfigSchema = z.object({
|
|
179
189
|
enabled: z.boolean().default(false),
|