@massu/core 1.15.6 → 1.16.0

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.
Files changed (105) hide show
  1. package/CHANGELOG.md +103 -0
  2. package/agents/massu-architecture-reviewer.md +20 -0
  3. package/agents/massu-blast-radius-analyzer.md +20 -0
  4. package/agents/massu-competitive-scorer.md +20 -0
  5. package/agents/massu-help-sync.md +20 -0
  6. package/agents/massu-migration-writer.md +20 -0
  7. package/agents/massu-output-scorer.md +20 -0
  8. package/agents/massu-pattern-reviewer.md +20 -0
  9. package/agents/massu-plan-auditor.md +20 -0
  10. package/agents/massu-schema-sync-verifier.md +20 -0
  11. package/agents/massu-security-reviewer.md +20 -0
  12. package/agents/massu-ux-reviewer.md +20 -0
  13. package/commands/_shared-preamble.md +21 -0
  14. package/commands/_shared-references/auto-learning-protocol.md +2 -0
  15. package/commands/_shared-references/blast-radius-protocol.md +2 -0
  16. package/commands/_shared-references/security-pre-screen.md +2 -0
  17. package/commands/_shared-references/test-first-protocol.md +2 -0
  18. package/commands/_shared-references/verification-table.md +2 -0
  19. package/commands/_verification-laws.md +131 -0
  20. package/commands/_verification-laws.md.sha256 +1 -0
  21. package/commands/massu-data/references/common-queries.md +2 -0
  22. package/commands/massu-data/references/table-guide.md +2 -0
  23. package/commands/massu-data.md +2 -0
  24. package/commands/massu-debug/references/auto-learning.md +2 -0
  25. package/commands/massu-debug/references/codegraph-tracing.md +2 -0
  26. package/commands/massu-debug/references/common-shortcuts.md +2 -0
  27. package/commands/massu-debug/references/investigation-phases.md +2 -0
  28. package/commands/massu-debug/references/report-format.md +2 -0
  29. package/commands/massu-deploy.python-docker.md +2 -0
  30. package/commands/massu-deploy.python-fly.md +2 -0
  31. package/commands/massu-deploy.python-launchd.md +2 -0
  32. package/commands/massu-deploy.python-systemd.md +2 -0
  33. package/commands/massu-full-audit.md +2 -0
  34. package/commands/massu-golden-path/references/approval-points.md +2 -0
  35. package/commands/massu-golden-path/references/phase-0-requirements.md +2 -0
  36. package/commands/massu-golden-path/references/phase-1-plan-creation.md +2 -0
  37. package/commands/massu-golden-path/references/phase-2-implementation.md +2 -0
  38. package/commands/massu-golden-path/references/phase-2.5-gap-analyzer.md +2 -0
  39. package/commands/massu-golden-path/references/phase-3-simplify.md +2 -0
  40. package/commands/massu-golden-path/references/phase-3.5-security-audit.md +2 -0
  41. package/commands/massu-golden-path/references/phase-4-commit.md +2 -0
  42. package/commands/massu-golden-path/references/phase-5-push.md +2 -0
  43. package/commands/massu-golden-path/references/phase-5.5-production-verify.md +2 -0
  44. package/commands/massu-golden-path/references/phase-6-completion.md +2 -0
  45. package/commands/massu-golden-path/references/qa-evaluator-spec.md +2 -0
  46. package/commands/massu-golden-path/references/sprint-contract-protocol.md +2 -0
  47. package/commands/massu-golden-path/references/vr-visual-calibration.md +2 -0
  48. package/commands/massu-guide.md +2 -0
  49. package/commands/massu-hooks.md +2 -0
  50. package/commands/massu-learning-audit.md +2 -0
  51. package/commands/massu-loop/references/auto-learning.md +2 -0
  52. package/commands/massu-loop/references/checkpoint-audit.md +2 -0
  53. package/commands/massu-loop/references/guardrails.md +2 -0
  54. package/commands/massu-loop/references/iteration-structure.md +2 -0
  55. package/commands/massu-loop/references/loop-controller.md +2 -0
  56. package/commands/massu-loop/references/plan-extraction.md +2 -0
  57. package/commands/massu-loop/references/vr-plan-spec.md +2 -0
  58. package/commands/massu-parity.md +2 -0
  59. package/commands/massu-push.md +2 -0
  60. package/commands/massu-scaffold-hook.md +2 -0
  61. package/commands/massu-scaffold-page.md +2 -0
  62. package/commands/massu-scaffold-page.swift.md +2 -0
  63. package/commands/massu-scaffold-router.md +2 -0
  64. package/commands/massu-scaffold-router.python-django.md +2 -0
  65. package/commands/massu-scaffold-router.python-fastapi.md +2 -0
  66. package/commands/massu-scaffold-router.python.md +2 -0
  67. package/dist/cli.js +2012 -787
  68. package/dist/hooks/auto-learning-pipeline.js +152 -67
  69. package/dist/hooks/classify-failure.js +152 -67
  70. package/dist/hooks/cost-tracker.js +152 -67
  71. package/dist/hooks/fix-detector.js +152 -67
  72. package/dist/hooks/incident-pipeline.js +152 -67
  73. package/dist/hooks/intent-suggester.js +152 -67
  74. package/dist/hooks/memory-recall.js +152 -67
  75. package/dist/hooks/post-edit-context.js +152 -67
  76. package/dist/hooks/post-tool-use.js +152 -67
  77. package/dist/hooks/pre-compact.js +152 -67
  78. package/dist/hooks/pre-delete-check.js +154 -69
  79. package/dist/hooks/pre-tool-use-gate.js +154 -69
  80. package/dist/hooks/quality-event.js +152 -67
  81. package/dist/hooks/rule-enforcement-pipeline.js +152 -67
  82. package/dist/hooks/security-gate.js +152 -67
  83. package/dist/hooks/session-end.js +346 -80
  84. package/dist/hooks/session-start.js +155 -67
  85. package/dist/hooks/user-prompt.js +222 -111
  86. package/package.json +4 -3
  87. package/patterns/build-patterns.md +2 -0
  88. package/patterns/component-patterns.md +2 -0
  89. package/patterns/display-patterns.md +2 -0
  90. package/patterns/form-patterns.md +2 -0
  91. package/patterns/integration-testing-checklist.md +2 -0
  92. package/patterns/security-patterns.md +2 -0
  93. package/patterns/testing-patterns.md +2 -0
  94. package/patterns/tool-routing.md +2 -0
  95. package/patterns/ui-patterns.md +2 -0
  96. package/protocols/plan-implementation.md +2 -0
  97. package/protocols/recovery.md +2 -0
  98. package/protocols/verification.md +2 -0
  99. package/reference/cr-rules-reference.md +2 -0
  100. package/reference/hook-execution-order.md +2 -0
  101. package/reference/lessons-learned.md +2 -0
  102. package/reference/patterns-quickref.md +2 -0
  103. package/reference/standards.md +2 -0
  104. package/reference/subagents-reference.md +2 -0
  105. package/reference/vr-verification-reference.md +2 -0
package/dist/cli.js CHANGED
@@ -1082,6 +1082,142 @@ var init_db_backup = __esm({
1082
1082
  }
1083
1083
  });
1084
1084
 
1085
+ // src/rule-delivery.ts
1086
+ function tableExists(db, table) {
1087
+ const row = db.prepare("SELECT 1 AS ok FROM sqlite_master WHERE type='table' AND name=?").get(table);
1088
+ return row !== void 0;
1089
+ }
1090
+ function columns(db, table) {
1091
+ const rows = db.prepare(`PRAGMA table_info(${table})`).all();
1092
+ return new Set(rows.map((r2) => r2.name));
1093
+ }
1094
+ function migrateRuleDelivery(db) {
1095
+ const ADDITIONS = [
1096
+ { name: "attempts", decl: "INTEGER NOT NULL DEFAULT 0" },
1097
+ { name: "lease_token", decl: "TEXT" },
1098
+ { name: "leased_at_ms", decl: "INTEGER" },
1099
+ { name: "last_error", decl: "TEXT" }
1100
+ ];
1101
+ for (const table of ALL_OUTBOUND_TABLES) {
1102
+ if (!tableExists(db, table)) continue;
1103
+ const cols = columns(db, table);
1104
+ for (const add of ADDITIONS) {
1105
+ if (cols.has(add.name)) continue;
1106
+ db.exec(`ALTER TABLE ${table} ADD COLUMN ${add.name} ${add.decl}`);
1107
+ }
1108
+ }
1109
+ }
1110
+ function undeliveredSnapshot(db) {
1111
+ const count = (t2) => {
1112
+ if (!tableExists(db, t2)) return 0;
1113
+ const r2 = db.prepare(`SELECT COUNT(*) AS n FROM ${t2}`).get();
1114
+ return r2.n;
1115
+ };
1116
+ const promotions = count("team_promotion_outbound");
1117
+ const revocations = count("team_revocation_outbound");
1118
+ const events = count(TELEMETRY_OUTBOUND_TABLE);
1119
+ let maxAttempts = 0;
1120
+ let oldest = null;
1121
+ for (const t2 of RULE_OUTBOUND_TABLES) {
1122
+ if (!tableExists(db, t2)) continue;
1123
+ if (!columns(db, t2).has("attempts")) continue;
1124
+ const r2 = db.prepare(`SELECT MAX(attempts) AS a, MIN(created_at) AS c FROM ${t2}`).get();
1125
+ if (r2.a !== null && r2.a > maxAttempts) maxAttempts = r2.a;
1126
+ if (r2.c !== null && (oldest === null || r2.c < oldest)) oldest = r2.c;
1127
+ }
1128
+ return {
1129
+ promotions,
1130
+ revocations,
1131
+ events,
1132
+ max_rule_attempts: maxAttempts,
1133
+ oldest_rule_created_at: oldest,
1134
+ stalled: promotions + revocations > 0 && maxAttempts >= STALL_ATTEMPTS
1135
+ };
1136
+ }
1137
+ function capTelemetry(db, cap = TELEMETRY_OUTBOX_CAP) {
1138
+ if (!tableExists(db, TELEMETRY_OUTBOUND_TABLE)) return 0;
1139
+ const before = db.prepare(`SELECT COUNT(*) AS n FROM ${TELEMETRY_OUTBOUND_TABLE}`).get();
1140
+ if (before.n <= cap) return 0;
1141
+ const info = db.prepare(
1142
+ `DELETE FROM ${TELEMETRY_OUTBOUND_TABLE}
1143
+ WHERE id NOT IN (SELECT id FROM ${TELEMETRY_OUTBOUND_TABLE} ORDER BY id DESC LIMIT ?)`
1144
+ ).run(cap);
1145
+ const dropped = info.changes;
1146
+ if (dropped > 0) {
1147
+ process.stderr.write(
1148
+ `[massu] NOTE: dropped ${dropped} oldest promotion-funnel telemetry row(s) at the ${cap}-row cap. No learned rules were affected (rules are never dropped).
1149
+ `
1150
+ );
1151
+ recordAnalytics(db, EVENT_TELEMETRY_CAPPED, { dropped, cap });
1152
+ }
1153
+ return dropped;
1154
+ }
1155
+ function recordAnalytics(db, eventType, data) {
1156
+ try {
1157
+ db.prepare(
1158
+ `INSERT INTO analytics_events (event_type, event_data, created_at)
1159
+ VALUES (?, ?, datetime('now'))`
1160
+ ).run(eventType, JSON.stringify(data));
1161
+ } catch (err) {
1162
+ process.stderr.write(
1163
+ `[massu] WARNING: could not record '${eventType}' telemetry: ${err instanceof Error ? err.message : String(err)}
1164
+ `
1165
+ );
1166
+ }
1167
+ }
1168
+ var RULE_OUTBOUND_TABLES, TELEMETRY_OUTBOUND_TABLE, ALL_OUTBOUND_TABLES, TELEMETRY_OUTBOX_CAP, LEASE_TTL_MS, STALL_ATTEMPTS, EVENT_TELEMETRY_CAPPED;
1169
+ var init_rule_delivery = __esm({
1170
+ "src/rule-delivery.ts"() {
1171
+ "use strict";
1172
+ RULE_OUTBOUND_TABLES = [
1173
+ "team_promotion_outbound",
1174
+ "team_revocation_outbound"
1175
+ ];
1176
+ TELEMETRY_OUTBOUND_TABLE = "rule_promotion_events_outbound";
1177
+ ALL_OUTBOUND_TABLES = [
1178
+ ...RULE_OUTBOUND_TABLES,
1179
+ TELEMETRY_OUTBOUND_TABLE
1180
+ ];
1181
+ TELEMETRY_OUTBOX_CAP = 2e4;
1182
+ LEASE_TTL_MS = 15 * 60 * 1e3;
1183
+ STALL_ATTEMPTS = 5;
1184
+ EVENT_TELEMETRY_CAPPED = "rule_telemetry_capped";
1185
+ }
1186
+ });
1187
+
1188
+ // src/rule-candidate-store.ts
1189
+ function ensureRuleCandidatesTable(db) {
1190
+ db.exec(`
1191
+ CREATE TABLE IF NOT EXISTS rule_candidates (
1192
+ prompt_hash TEXT PRIMARY KEY,
1193
+ status TEXT NOT NULL DEFAULT 'proposed'
1194
+ CHECK (status IN ('proposed','shown','promoted','dismissed')),
1195
+ origin TEXT NOT NULL DEFAULT 'local'
1196
+ CHECK (origin IN ('local','team','pack')),
1197
+ score REAL,
1198
+ destination TEXT,
1199
+ payload_json TEXT NOT NULL DEFAULT '{}',
1200
+ created_at TEXT NOT NULL DEFAULT (datetime('now')),
1201
+ updated_at TEXT NOT NULL DEFAULT (datetime('now'))
1202
+ );
1203
+ CREATE INDEX IF NOT EXISTS idx_rule_candidates_status ON rule_candidates(status);
1204
+ CREATE INDEX IF NOT EXISTS idx_rule_candidates_created ON rule_candidates(created_at);
1205
+ `);
1206
+ }
1207
+ function setCandidateStatus(db, promptHash, status, destination) {
1208
+ ensureRuleCandidatesTable(db);
1209
+ db.prepare(`
1210
+ UPDATE rule_candidates
1211
+ SET status = ?, destination = COALESCE(?, destination), updated_at = datetime('now')
1212
+ WHERE prompt_hash = ?
1213
+ `).run(status, destination ?? null, promptHash);
1214
+ }
1215
+ var init_rule_candidate_store = __esm({
1216
+ "src/rule-candidate-store.ts"() {
1217
+ "use strict";
1218
+ }
1219
+ });
1220
+
1085
1221
  // src/hooks/lib/hook-failure-signal.ts
1086
1222
  import { appendFileSync, mkdirSync as mkdirSync3, existsSync as existsSync4 } from "fs";
1087
1223
  import { join as join2, dirname as dirname3 } from "path";
@@ -1650,9 +1786,6 @@ __export(memory_db_exports, {
1650
1786
  createSession: () => createSession,
1651
1787
  deduplicateFailedAttempt: () => deduplicateFailedAttempt,
1652
1788
  dequeuePendingSync: () => dequeuePendingSync,
1653
- drainRulePromotionEvents: () => drainRulePromotionEvents,
1654
- drainTeamPromotions: () => drainTeamPromotions,
1655
- drainTeamRevocations: () => drainTeamRevocations,
1656
1789
  embedMissingObservations: () => embedMissingObservations,
1657
1790
  endSession: () => endSession,
1658
1791
  enqueueRulePromotionEvent: () => enqueueRulePromotionEvent,
@@ -2559,6 +2692,8 @@ function initMemorySchema(db) {
2559
2692
  `);
2560
2693
  migrateObservationEmbeddingChunks(db);
2561
2694
  migrateMemoryFilesFor4B(db);
2695
+ ensureRuleCandidatesTable(db);
2696
+ migrateRuleDelivery(db);
2562
2697
  }
2563
2698
  function enqueueSyncPayload(db, payload) {
2564
2699
  db.prepare("INSERT INTO pending_sync (payload) VALUES (?)").run(payload);
@@ -2633,38 +2768,12 @@ function enqueueTeamPromotion(db, promo) {
2633
2768
  promo.review_attestation !== void 0 ? JSON.stringify(promo.review_attestation) : null
2634
2769
  );
2635
2770
  }
2636
- function drainTeamPromotions(db) {
2637
- const rows = db.prepare(`
2638
- SELECT prompt_hash, destination, draft_text, score, signals_json, content_hash, hardened, review_attestation_json
2639
- FROM team_promotion_outbound ORDER BY created_at ASC LIMIT 1000
2640
- `).all();
2641
- if (rows.length === 0) return [];
2642
- db.prepare("DELETE FROM team_promotion_outbound").run();
2643
- return rows.map((r2) => ({
2644
- prompt_hash: r2.prompt_hash,
2645
- destination: r2.destination,
2646
- draft_text: r2.draft_text,
2647
- score: r2.score ?? void 0,
2648
- signals: safeJsonArray(r2.signals_json),
2649
- content_hash: r2.content_hash,
2650
- hardened: r2.hardened === 1,
2651
- review_attestation: r2.review_attestation_json ? safeJsonParse(r2.review_attestation_json) : void 0
2652
- }));
2653
- }
2654
2771
  function enqueueTeamRevocation(db, promptHash) {
2655
2772
  db.prepare(`
2656
2773
  INSERT OR REPLACE INTO team_revocation_outbound (prompt_hash, created_at)
2657
2774
  VALUES (?, datetime('now'))
2658
2775
  `).run(promptHash);
2659
2776
  }
2660
- function drainTeamRevocations(db) {
2661
- const rows = db.prepare(
2662
- `SELECT prompt_hash FROM team_revocation_outbound ORDER BY created_at ASC LIMIT 1000`
2663
- ).all();
2664
- if (rows.length === 0) return [];
2665
- db.prepare("DELETE FROM team_revocation_outbound").run();
2666
- return rows.map((r2) => r2.prompt_hash);
2667
- }
2668
2777
  function getRecurrenceCountForPromptHash(db, promptHash) {
2669
2778
  try {
2670
2779
  const row = db.prepare(`
@@ -2692,30 +2801,14 @@ function enqueueRulePromotionEvent(db, ev) {
2692
2801
  JSON.stringify(ev.metadata ?? {}),
2693
2802
  ev.created_at
2694
2803
  );
2695
- db.prepare(`
2696
- DELETE FROM rule_promotion_events_outbound
2697
- WHERE id NOT IN (
2698
- SELECT id FROM rule_promotion_events_outbound ORDER BY id DESC LIMIT ?
2699
- )
2700
- `).run(FUNNEL_EVENT_OUTBOX_CAP);
2701
- } catch {
2804
+ capTelemetry(db, FUNNEL_EVENT_OUTBOX_CAP);
2805
+ } catch (err) {
2806
+ process.stderr.write(
2807
+ `[massu] WARNING: failed to record promotion-funnel event (${ev.event_type} / ${ev.prompt_hash}): ${err instanceof Error ? err.message : String(err)}
2808
+ `
2809
+ );
2702
2810
  }
2703
2811
  }
2704
- function drainRulePromotionEvents(db) {
2705
- const rows = db.prepare(`
2706
- SELECT id, prompt_hash, event_type, metadata_json, created_at
2707
- FROM rule_promotion_events_outbound ORDER BY id ASC LIMIT ?
2708
- `).all(FUNNEL_EVENT_DRAIN_LIMIT);
2709
- if (rows.length === 0) return [];
2710
- const maxId = rows[rows.length - 1].id;
2711
- db.prepare("DELETE FROM rule_promotion_events_outbound WHERE id <= ?").run(maxId);
2712
- return rows.map((r2) => ({
2713
- prompt_hash: r2.prompt_hash,
2714
- event_type: r2.event_type,
2715
- created_at: r2.created_at,
2716
- metadata: safeJsonParse(r2.metadata_json) ?? {}
2717
- }));
2718
- }
2719
2812
  function recordTelemetry(db, eventType, data) {
2720
2813
  try {
2721
2814
  db.prepare(`
@@ -2725,28 +2818,55 @@ function recordTelemetry(db, eventType, data) {
2725
2818
  } catch {
2726
2819
  }
2727
2820
  }
2728
- function safeJsonArray(json) {
2729
- try {
2730
- const v3 = JSON.parse(json);
2731
- return Array.isArray(v3) ? v3 : [];
2732
- } catch {
2733
- return [];
2734
- }
2735
- }
2736
- function safeJsonParse(json) {
2737
- try {
2738
- return JSON.parse(json);
2739
- } catch {
2740
- return void 0;
2821
+ function rescueLearningFromStalePayloads(db, stale) {
2822
+ let rescued = 0;
2823
+ for (const item of stale) {
2824
+ let payload;
2825
+ try {
2826
+ payload = JSON.parse(item.payload);
2827
+ } catch {
2828
+ continue;
2829
+ }
2830
+ try {
2831
+ const promotions = Array.isArray(payload.rule_promotions) ? payload.rule_promotions : [];
2832
+ for (const p19 of promotions) {
2833
+ enqueueTeamPromotion(db, p19);
2834
+ rescued++;
2835
+ }
2836
+ const revocations = Array.isArray(payload.rule_revocations) ? payload.rule_revocations : [];
2837
+ for (const r2 of revocations) {
2838
+ if (typeof r2?.prompt_hash === "string") {
2839
+ enqueueTeamRevocation(db, r2.prompt_hash);
2840
+ rescued++;
2841
+ }
2842
+ }
2843
+ const events = Array.isArray(payload.rule_promotion_events) ? payload.rule_promotion_events : [];
2844
+ for (const e2 of events) {
2845
+ enqueueRulePromotionEvent(db, e2);
2846
+ }
2847
+ } catch (err) {
2848
+ process.stderr.write(
2849
+ `[massu] WARNING: failed to rescue learned rules from give-up payload ${item.id}: ${err instanceof Error ? err.message : String(err)}
2850
+ `
2851
+ );
2852
+ }
2741
2853
  }
2854
+ return rescued;
2742
2855
  }
2743
2856
  function dequeuePendingSync(db, limit = 10) {
2744
2857
  const stale = db.prepare(
2745
- "SELECT id, retry_count, last_error FROM pending_sync WHERE retry_count >= 10 LIMIT 10000"
2858
+ "SELECT id, retry_count, last_error, payload FROM pending_sync WHERE retry_count >= 10 LIMIT 10000"
2746
2859
  ).all();
2747
2860
  if (stale.length > 0) {
2861
+ const rescued = rescueLearningFromStalePayloads(db, stale);
2748
2862
  const ids = stale.map((s) => s.id);
2749
2863
  db.prepare(`DELETE FROM pending_sync WHERE id IN (${ids.map(() => "?").join(",")})`).run(...ids);
2864
+ if (rescued > 0) {
2865
+ process.stderr.write(
2866
+ `[massu] RESCUED ${rescued} learned rule(s) from ${stale.length} give-up payload(s) \u2014 they were re-queued for delivery, NOT discarded.
2867
+ `
2868
+ );
2869
+ }
2750
2870
  const lastErrors = [...new Set(stale.map((s) => s.last_error).filter(Boolean))];
2751
2871
  process.stderr.write(
2752
2872
  `[massu] WARNING: ${stale.length} cloud-sync queue item(s) discarded after 10+ retries. Likely cause: invalid API key or unreachable endpoint. Recent errors: ${lastErrors.slice(0, 3).join("; ") || "(none recorded)"}
@@ -3438,19 +3558,20 @@ function scoreFailureClasses(db, matchText, filePath, promptContext, weights) {
3438
3558
  }
3439
3559
  return bestMatch;
3440
3560
  }
3441
- var MEMORY_SCHEMA_VERSION, TOOL_COST_EVENTS_RETENTION_DAYS, MAX_DRAFT_TEXT_LEN, FUNNEL_EVENT_DRAIN_LIMIT, FUNNEL_EVENT_OUTBOX_CAP, _temporalColCache, CONSOLIDATION_LESSON_EVIDENCE, MEMORY_FILE_TITLE_PREFIX, MEMORY_FILE_TITLE_LIKE, USAGE_COUNTER_ARMED_KEY;
3561
+ var MEMORY_SCHEMA_VERSION, TOOL_COST_EVENTS_RETENTION_DAYS, MAX_DRAFT_TEXT_LEN, FUNNEL_EVENT_OUTBOX_CAP, _temporalColCache, CONSOLIDATION_LESSON_EVIDENCE, MEMORY_FILE_TITLE_PREFIX, MEMORY_FILE_TITLE_LIKE, USAGE_COUNTER_ARMED_KEY;
3442
3562
  var init_memory_db = __esm({
3443
3563
  "src/memory-db.ts"() {
3444
3564
  "use strict";
3445
3565
  init_config();
3446
3566
  init_memory_vector();
3447
3567
  init_db_backup();
3568
+ init_rule_delivery();
3569
+ init_rule_candidate_store();
3448
3570
  init_hook_failure_signal();
3449
3571
  init_memory_embed_sweep();
3450
3572
  MEMORY_SCHEMA_VERSION = 1;
3451
3573
  TOOL_COST_EVENTS_RETENTION_DAYS = 90;
3452
3574
  MAX_DRAFT_TEXT_LEN = 16384;
3453
- FUNNEL_EVENT_DRAIN_LIMIT = 5e3;
3454
3575
  FUNNEL_EVENT_OUTBOX_CAP = 2e4;
3455
3576
  _temporalColCache = /* @__PURE__ */ new WeakMap();
3456
3577
  CONSOLIDATION_LESSON_EVIDENCE = "consolidation:session-summary";
@@ -4132,11 +4253,15 @@ var init_permissions = __esm({
4132
4253
  // src/commands/install-commands.ts
4133
4254
  var install_commands_exports = {};
4134
4255
  __export(install_commands_exports, {
4256
+ MASSU_OWNED_PATHS: () => MASSU_OWNED_PATHS,
4135
4257
  buildTemplateVars: () => buildTemplateVars,
4258
+ canonicalizeOwned: () => canonicalizeOwned,
4136
4259
  hashContent: () => hashContent,
4137
4260
  installAll: () => installAll,
4138
4261
  installCommands: () => installCommands,
4262
+ isMassuOwned: () => isMassuOwned,
4139
4263
  loadManifest: () => loadManifest,
4264
+ massuOwnedSourceIsValid: () => massuOwnedSourceIsValid,
4140
4265
  pickVariant: () => pickVariant,
4141
4266
  resolveAssetDir: () => resolveAssetDir,
4142
4267
  resolveCommandsDir: () => resolveCommandsDir,
@@ -4155,25 +4280,51 @@ import {
4155
4280
  import { resolve as resolve6, dirname as dirname7, join as join6 } from "path";
4156
4281
  import { fileURLToPath as fileURLToPath2 } from "url";
4157
4282
  import { createHash as createHash3 } from "crypto";
4283
+ function dirHoldsAssets(dir) {
4284
+ if (!existsSync9(dir)) return false;
4285
+ try {
4286
+ return readdirSync3(dir).some(
4287
+ (e2) => e2.endsWith(".md") || statSync2(resolve6(dir, e2)).isDirectory()
4288
+ );
4289
+ } catch {
4290
+ return false;
4291
+ }
4292
+ }
4158
4293
  function resolveAssetDir(assetName) {
4159
4294
  const cwd = process.cwd();
4160
- const nodeModulesPath = resolve6(cwd, "node_modules/@massu/core", assetName);
4161
- if (existsSync9(nodeModulesPath)) {
4162
- return nodeModulesPath;
4163
- }
4164
- const distRelPath = resolve6(__dirname, "..", assetName);
4165
- if (existsSync9(distRelPath)) {
4166
- return distRelPath;
4167
- }
4168
- const srcRelPath = resolve6(__dirname, "../..", assetName);
4169
- if (existsSync9(srcRelPath)) {
4170
- return srcRelPath;
4295
+ const candidates = [
4296
+ // 1. npm-installed: node_modules/@massu/core/{assetName}
4297
+ resolve6(cwd, "node_modules/@massu/core", assetName),
4298
+ // 2. Relative to compiled dist/cli.js → ../{assetName}
4299
+ resolve6(__dirname, "..", assetName),
4300
+ // 3. Relative to source src/commands/ → ../../{assetName}
4301
+ resolve6(__dirname, "../..", assetName)
4302
+ ];
4303
+ for (const c2 of candidates) {
4304
+ if (dirHoldsAssets(c2)) return c2;
4171
4305
  }
4172
4306
  return null;
4173
4307
  }
4174
4308
  function resolveCommandsDir() {
4175
4309
  return resolveAssetDir("commands");
4176
4310
  }
4311
+ function isMassuOwned(manifestKey) {
4312
+ return MASSU_OWNED_PATHS.includes(manifestKey);
4313
+ }
4314
+ function canonicalizeOwned(content) {
4315
+ return content.replace(/\r\n/g, "\n").split("\n").map((l2) => l2.replace(/\s+$/, "")).join("\n").trim();
4316
+ }
4317
+ function massuOwnedSourceIsValid(resolvedSourcePath, content) {
4318
+ const sidecar = `${resolvedSourcePath}.sha256`;
4319
+ if (!existsSync9(sidecar)) return false;
4320
+ let expected;
4321
+ try {
4322
+ expected = readFileSync6(sidecar, "utf-8").trim();
4323
+ } catch {
4324
+ return false;
4325
+ }
4326
+ return hashContent(canonicalizeOwned(content)) === expected;
4327
+ }
4177
4328
  function hashContent(content) {
4178
4329
  return createHash3("sha256").update(content, "utf-8").digest("hex");
4179
4330
  }
@@ -4349,13 +4500,38 @@ function syncDirectory(sourceDir, targetDir, framework, manifest, manifestKeyPre
4349
4500
  const manifestKey = manifestKeyPrefix === "" ? targetFilename : `${manifestKeyPrefix}/${targetFilename}`;
4350
4501
  const lastInstalledHash = manifest.entries[manifestKey];
4351
4502
  if (existsSync9(targetPath)) {
4352
- const existingContent = readFileSync6(targetPath, "utf-8");
4503
+ let existingContent;
4504
+ try {
4505
+ if (!statSync2(targetPath).isFile()) throw new Error("not a regular file");
4506
+ existingContent = readFileSync6(targetPath, "utf-8");
4507
+ } catch (err) {
4508
+ process.stderr.write(
4509
+ `massu: skipping ${manifestKey}: target is not a readable file (${err instanceof Error ? err.message : String(err)}). Remove it and re-install.
4510
+ `
4511
+ );
4512
+ stats.skipped++;
4513
+ continue;
4514
+ }
4353
4515
  const existingHash = hashContent(existingContent);
4354
4516
  if (existingHash === sourceHash) {
4355
4517
  manifest.entries[manifestKey] = sourceHash;
4356
4518
  stats.skipped++;
4357
4519
  continue;
4358
4520
  }
4521
+ if (isMassuOwned(manifestKey)) {
4522
+ if (!massuOwnedSourceIsValid(resolvedSourcePath, sourceContent)) {
4523
+ process.stderr.write(
4524
+ `massu: REFUSING to overwrite massu-owned ${manifestKey} \u2014 the source failed its integrity hash (${resolvedSourcePath}.sha256 missing or mismatched). Your installed @massu/core may be stale or tampered; run \`npm i @massu/core@latest\` and re-install. Kept the existing file.
4525
+ `
4526
+ );
4527
+ stats.kept++;
4528
+ continue;
4529
+ }
4530
+ atomicWriteFile(targetPath, sourceContent);
4531
+ manifest.entries[manifestKey] = sourceHash;
4532
+ stats.updated++;
4533
+ continue;
4534
+ }
4359
4535
  if (lastInstalledHash === void 0) {
4360
4536
  process.stderr.write(
4361
4537
  `First-install: keeping existing ${targetPath} (differs from upstream, and massu has no record of writing it).
@@ -4531,7 +4707,7 @@ async function runInstallCommands() {
4531
4707
  console.log(" Restart your Claude Code session to use them.");
4532
4708
  console.log("");
4533
4709
  }
4534
- var __filename, __dirname, ASSET_TYPES, MANIFEST_VERSION, MANIFEST_RELPATH, PASSTHROUGH_LANG_KEYS;
4710
+ var __filename, __dirname, ASSET_TYPES, MANIFEST_VERSION, MANIFEST_RELPATH, MASSU_OWNED_PATHS, PASSTHROUGH_LANG_KEYS;
4535
4711
  var init_install_commands = __esm({
4536
4712
  "src/commands/install-commands.ts"() {
4537
4713
  "use strict";
@@ -4550,6 +4726,7 @@ var init_install_commands = __esm({
4550
4726
  ];
4551
4727
  MANIFEST_VERSION = 1;
4552
4728
  MANIFEST_RELPATH = join6(".massu", "install-manifest.json");
4729
+ MASSU_OWNED_PATHS = ["commands/_verification-laws.md"];
4553
4730
  PASSTHROUGH_LANG_KEYS = [
4554
4731
  "typescript",
4555
4732
  "javascript",
@@ -9103,21 +9280,21 @@ var require_pattern = __commonJS({
9103
9280
  exports.removeDuplicateSlashes = removeDuplicateSlashes;
9104
9281
  function partitionAbsoluteAndRelative(patterns) {
9105
9282
  const absolute = [];
9106
- const relative14 = [];
9283
+ const relative15 = [];
9107
9284
  for (const pattern of patterns) {
9108
- if (isAbsolute4(pattern)) {
9285
+ if (isAbsolute5(pattern)) {
9109
9286
  absolute.push(pattern);
9110
9287
  } else {
9111
- relative14.push(pattern);
9288
+ relative15.push(pattern);
9112
9289
  }
9113
9290
  }
9114
- return [absolute, relative14];
9291
+ return [absolute, relative15];
9115
9292
  }
9116
9293
  exports.partitionAbsoluteAndRelative = partitionAbsoluteAndRelative;
9117
- function isAbsolute4(pattern) {
9294
+ function isAbsolute5(pattern) {
9118
9295
  return path.isAbsolute(pattern);
9119
9296
  }
9120
- exports.isAbsolute = isAbsolute4;
9297
+ exports.isAbsolute = isAbsolute5;
9121
9298
  }
9122
9299
  });
9123
9300
 
@@ -17919,6 +18096,28 @@ function autoLearningUpgradeMessage(currentTier) {
17919
18096
  function entitledForTeamSharedPromotion(tier) {
17920
18097
  return tierLevel(tier) >= tierLevel(TEAM_SHARED_PROMOTION_MIN_TIER);
17921
18098
  }
18099
+ function teamSharedPromotionUpgradeMessage(currentTier) {
18100
+ return `Team-shared rule promotion (your team learns as one \u2014 a promoted rule propagates to your org as a reviewable proposal) is a Team feature. Your tier: ${currentTier.toUpperCase()}. Upgrade at https://massu.ai/pricing`;
18101
+ }
18102
+ async function assertAutoLearningEntitled() {
18103
+ try {
18104
+ const currentTier = await getCurrentTier();
18105
+ if (entitledForAutoLearning(currentTier)) {
18106
+ return { entitled: true, currentTier };
18107
+ }
18108
+ return {
18109
+ entitled: false,
18110
+ currentTier,
18111
+ message: autoLearningUpgradeMessage(currentTier)
18112
+ };
18113
+ } catch {
18114
+ return {
18115
+ entitled: false,
18116
+ currentTier: "free",
18117
+ message: autoLearningUpgradeMessage("free")
18118
+ };
18119
+ }
18120
+ }
17922
18121
  var AUTO_LEARNING_MIN_TIER, TEAM_SHARED_PROMOTION_MIN_TIER;
17923
18122
  var init_auto_learning_entitlement = __esm({
17924
18123
  "src/auto-learning-entitlement.ts"() {
@@ -19284,6 +19483,12 @@ function assertOutsideRegionUnchanged(pre, post) {
19284
19483
  );
19285
19484
  }
19286
19485
  }
19486
+ function readRegionLines(content) {
19487
+ const state = parseRegion(content);
19488
+ if (state.kind !== "valid") return [];
19489
+ const inner = content.slice(state.beginIdx + BEGIN_SENTINEL.length, state.endIdx);
19490
+ return inner.split("\n").filter((l2) => l2.trim().length > 0);
19491
+ }
19287
19492
  function memoryIndexLockPath(memoryDir) {
19288
19493
  return join16(dirname15(memoryDir), ".massu-memory-index.lock");
19289
19494
  }
@@ -19308,7 +19513,7 @@ function writeMemoryIndex(indexPath, pre, post) {
19308
19513
  assertOutsideRegionUnchanged(pre, post);
19309
19514
  atomicWriteFileSync(indexPath, post);
19310
19515
  }
19311
- var BEGIN_SENTINEL, END_SENTINEL, RegionRefused, LOCK_BLOCK_MS, LOCK_STALE_MS, MemoryIndexLockBusy;
19516
+ var BEGIN_SENTINEL, END_SENTINEL, DEFAULT_MAX_REGION_LINES, RegionRefused, MemoryIndexMissing, LOCK_BLOCK_MS, LOCK_STALE_MS, MemoryIndexLockBusy;
19312
19517
  var init_memory_index_region = __esm({
19313
19518
  "src/memory-index-region.ts"() {
19314
19519
  "use strict";
@@ -19316,6 +19521,7 @@ var init_memory_index_region = __esm({
19316
19521
  init_safe_write();
19317
19522
  BEGIN_SENTINEL = "<!-- massu:learned:begin -->";
19318
19523
  END_SENTINEL = "<!-- massu:learned:end -->";
19524
+ DEFAULT_MAX_REGION_LINES = 50;
19319
19525
  RegionRefused = class extends Error {
19320
19526
  constructor(message, reason) {
19321
19527
  super(message);
@@ -19324,6 +19530,12 @@ var init_memory_index_region = __esm({
19324
19530
  }
19325
19531
  reason;
19326
19532
  };
19533
+ MemoryIndexMissing = class extends Error {
19534
+ constructor(message) {
19535
+ super(message);
19536
+ this.name = "MemoryIndexMissing";
19537
+ }
19538
+ };
19327
19539
  LOCK_BLOCK_MS = 2e3;
19328
19540
  LOCK_STALE_MS = 3e4;
19329
19541
  MemoryIndexLockBusy = class extends Error {
@@ -20008,6 +20220,1149 @@ var init_memory_render_cli_entry = __esm({
20008
20220
  }
20009
20221
  });
20010
20222
 
20223
+ // src/lib/corrections-md.ts
20224
+ function formatCorrectionEntry(e2) {
20225
+ const one = (s) => s.replace(/\s*\n+\s*/g, " ").trim();
20226
+ const lines = [`### ${e2.date} - ${one(e2.title)}`];
20227
+ if (e2.wrong) lines.push(`- **Wrong**: ${one(e2.wrong)}`);
20228
+ if (e2.correction) lines.push(`- **Correction**: ${one(e2.correction)}`);
20229
+ lines.push(`- **Rule**: ${one(e2.rule)}`);
20230
+ for (const [k3, v3] of Object.entries(e2.extra ?? {})) {
20231
+ lines.push(`- ${k3}: ${one(String(v3))}`);
20232
+ }
20233
+ return `
20234
+ ${lines.join("\n")}
20235
+ `;
20236
+ }
20237
+ var init_corrections_md = __esm({
20238
+ "src/lib/corrections-md.ts"() {
20239
+ "use strict";
20240
+ }
20241
+ });
20242
+
20243
+ // src/audit-trail.ts
20244
+ function p2(baseName) {
20245
+ return `${getConfig().toolPrefix}_${baseName}`;
20246
+ }
20247
+ function getAuditFormats() {
20248
+ return getConfig().governance?.audit?.formats ?? DEFAULT_FORMATS;
20249
+ }
20250
+ function logAuditEntry(db, entry) {
20251
+ db.prepare(`
20252
+ INSERT INTO audit_log (session_id, event_type, actor, model_id, file_path, change_type, rules_in_effect, approval_status, evidence, metadata)
20253
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
20254
+ `).run(
20255
+ entry.sessionId ?? null,
20256
+ entry.eventType,
20257
+ entry.actor,
20258
+ entry.modelId ?? null,
20259
+ entry.filePath ?? null,
20260
+ entry.changeType ?? null,
20261
+ entry.rulesInEffect ?? null,
20262
+ entry.approvalStatus ?? null,
20263
+ entry.evidence ?? null,
20264
+ entry.metadata ? JSON.stringify(entry.metadata) : null
20265
+ );
20266
+ }
20267
+ function queryAuditLog(db, options) {
20268
+ let sql = "SELECT * FROM audit_log WHERE 1=1";
20269
+ const params = [];
20270
+ if (options.eventType) {
20271
+ sql += " AND event_type = ?";
20272
+ params.push(options.eventType);
20273
+ }
20274
+ if (options.actor) {
20275
+ sql += " AND actor = ?";
20276
+ params.push(options.actor);
20277
+ }
20278
+ if (options.days) {
20279
+ sql += " AND timestamp >= datetime('now', ?)";
20280
+ params.push(`-${options.days} days`);
20281
+ }
20282
+ if (options.filePath) {
20283
+ sql += " AND file_path = ?";
20284
+ params.push(options.filePath);
20285
+ }
20286
+ if (options.changeType) {
20287
+ sql += " AND change_type = ?";
20288
+ params.push(options.changeType);
20289
+ }
20290
+ sql += " ORDER BY timestamp DESC";
20291
+ if (options.limit) {
20292
+ sql += " LIMIT ?";
20293
+ params.push(options.limit);
20294
+ }
20295
+ return db.prepare(sql).all(...params);
20296
+ }
20297
+ function getFileChain(db, filePath) {
20298
+ return db.prepare(
20299
+ "SELECT * FROM audit_log WHERE file_path = ? ORDER BY timestamp ASC LIMIT 10000"
20300
+ ).all(filePath);
20301
+ }
20302
+ function getAuditToolDefinitions() {
20303
+ return [
20304
+ {
20305
+ name: p2("audit_log"),
20306
+ description: "Query the audit log. Filter by event type, actor, file, or time range.",
20307
+ inputSchema: {
20308
+ type: "object",
20309
+ properties: {
20310
+ event_type: { type: "string", description: "Filter by event type: code_change, rule_enforced, approval, review, commit, compaction" },
20311
+ actor: { type: "string", description: "Filter by actor: ai, human, hook, agent" },
20312
+ file_path: { type: "string", description: "Filter by file path" },
20313
+ change_type: { type: "string", description: "Filter by change type: create, edit, delete" },
20314
+ days: { type: "number", description: "Days to look back (default: 30)" },
20315
+ limit: { type: "number", description: "Max results (default: 50)" }
20316
+ },
20317
+ required: []
20318
+ }
20319
+ },
20320
+ {
20321
+ name: p2("audit_report"),
20322
+ description: "Generate an audit report in a specified format (summary, detailed, soc2).",
20323
+ inputSchema: {
20324
+ type: "object",
20325
+ properties: {
20326
+ format: { type: "string", description: "Report format: summary, detailed, soc2 (default: summary)" },
20327
+ days: { type: "number", description: "Days to cover (default: 30)" }
20328
+ },
20329
+ required: []
20330
+ }
20331
+ },
20332
+ {
20333
+ name: p2("audit_chain"),
20334
+ description: "Get the complete audit trail for a specific file. Shows all changes, reviews, and decisions chronologically.",
20335
+ inputSchema: {
20336
+ type: "object",
20337
+ properties: {
20338
+ file: { type: "string", description: "File path to trace" }
20339
+ },
20340
+ required: ["file"]
20341
+ }
20342
+ }
20343
+ ];
20344
+ }
20345
+ function isAuditTool(name) {
20346
+ const pfx = getConfig().toolPrefix + "_";
20347
+ const baseName = name.startsWith(pfx) ? name.slice(pfx.length) : name;
20348
+ return AUDIT_BASE_NAMES.has(baseName);
20349
+ }
20350
+ function handleAuditToolCall(name, args2, memoryDb) {
20351
+ try {
20352
+ const pfx = getConfig().toolPrefix + "_";
20353
+ const baseName = name.startsWith(pfx) ? name.slice(pfx.length) : name;
20354
+ switch (baseName) {
20355
+ case "audit_log":
20356
+ return handleAuditLog(args2, memoryDb);
20357
+ case "audit_report":
20358
+ return handleAuditReport(args2, memoryDb);
20359
+ case "audit_chain":
20360
+ return handleAuditChain(args2, memoryDb);
20361
+ default:
20362
+ return text(`Unknown audit tool: ${name}`);
20363
+ }
20364
+ } catch (error) {
20365
+ return text(`Error in ${name}: ${error instanceof Error ? error.message : String(error)}
20366
+
20367
+ Usage: ${p2("audit_log")} { severity: "critical" }, ${p2("audit_report")} { format: "soc2" }`);
20368
+ }
20369
+ }
20370
+ function handleAuditLog(args2, db) {
20371
+ const entries = queryAuditLog(db, {
20372
+ eventType: args2.event_type,
20373
+ actor: args2.actor,
20374
+ filePath: args2.file_path,
20375
+ changeType: args2.change_type,
20376
+ days: args2.days ?? 30,
20377
+ limit: args2.limit ?? 50
20378
+ });
20379
+ if (entries.length === 0) {
20380
+ return text(`No audit log entries found matching the filters. Audit entries are recorded automatically during code changes, rule checks, and commits. Try broadening your search: ${p2("audit_log")} { days: 90 } for a longer time range, or ${p2("audit_log")} {} with no filters.`);
20381
+ }
20382
+ const lines = [
20383
+ `## Audit Log (${entries.length} entries)`,
20384
+ "",
20385
+ "| Timestamp | Event | Actor | Change | File | Evidence |",
20386
+ "|-----------|-------|-------|--------|------|----------|"
20387
+ ];
20388
+ for (const entry of entries) {
20389
+ const file = entry.file_path ? entry.file_path.split("/").pop() : "-";
20390
+ const evidence = entry.evidence ? entry.evidence.slice(0, 50) : "-";
20391
+ lines.push(
20392
+ `| ${entry.timestamp.slice(0, 16)} | ${entry.event_type} | ${entry.actor} | ${entry.change_type ?? "-"} | ${file} | ${evidence} |`
20393
+ );
20394
+ }
20395
+ return text(lines.join("\n"));
20396
+ }
20397
+ function handleAuditReport(args2, db) {
20398
+ const format = args2.format ?? "summary";
20399
+ const days = args2.days ?? 30;
20400
+ const availableFormats = getAuditFormats();
20401
+ if (!availableFormats.includes(format)) {
20402
+ return text(`Unknown report format: "${format}". Available formats: ${availableFormats.join(", ")}. Example: ${p2("audit_report")} { format: "soc2", days: 30 }. Configure additional formats via the governance.audit.formats config key.`);
20403
+ }
20404
+ const entries = queryAuditLog(db, { days });
20405
+ if (entries.length === 0) {
20406
+ return text(`No audit entries found in the last ${days} days. Audit entries are recorded automatically during code changes, reviews, and commits. Try: ${p2("audit_log")} { days: 90 } for a longer time range.`);
20407
+ }
20408
+ const byEventType = {};
20409
+ const byActor = {};
20410
+ for (const e2 of entries) {
20411
+ const et = e2.event_type;
20412
+ byEventType[et] = (byEventType[et] ?? 0) + 1;
20413
+ const actor = e2.actor;
20414
+ byActor[actor] = (byActor[actor] ?? 0) + 1;
20415
+ }
20416
+ if (format === "soc2") {
20417
+ return generateSoc2Report(entries, byEventType, byActor, days);
20418
+ }
20419
+ if (format === "detailed") {
20420
+ return generateDetailedReport(entries, byEventType, byActor, days);
20421
+ }
20422
+ const lines = [
20423
+ `## Audit Summary Report (${days} days)`,
20424
+ `Generated: ${(/* @__PURE__ */ new Date()).toISOString().slice(0, 16)}`,
20425
+ "",
20426
+ "### By Event Type",
20427
+ `| Event Type | Count |`,
20428
+ `|------------|-------|`
20429
+ ];
20430
+ for (const [et, count] of Object.entries(byEventType).sort((a2, b2) => b2[1] - a2[1])) {
20431
+ lines.push(`| ${et} | ${count} |`);
20432
+ }
20433
+ lines.push(`| **Total** | **${entries.length}** |`);
20434
+ lines.push("");
20435
+ lines.push("### By Actor");
20436
+ lines.push(`| Actor | Count |`);
20437
+ lines.push(`|-------|-------|`);
20438
+ for (const [actor, count] of Object.entries(byActor).sort((a2, b2) => b2[1] - a2[1])) {
20439
+ lines.push(`| ${actor} | ${count} |`);
20440
+ }
20441
+ return text(lines.join("\n"));
20442
+ }
20443
+ function generateSoc2Report(entries, byEventType, byActor, days) {
20444
+ const lines = [
20445
+ `# SOC 2 Compliance Report`,
20446
+ `Period: Last ${days} days`,
20447
+ `Generated: ${(/* @__PURE__ */ new Date()).toISOString()}`,
20448
+ "",
20449
+ "## 1. Control Environment",
20450
+ `Total audit events: ${entries.length}`,
20451
+ `Human-initiated: ${byActor["human"] ?? 0}`,
20452
+ `AI-initiated: ${byActor["ai"] ?? 0}`,
20453
+ `Hook-triggered: ${byActor["hook"] ?? 0}`,
20454
+ "",
20455
+ "## 2. Change Management",
20456
+ `Code changes logged: ${byEventType["code_change"] ?? 0}`,
20457
+ `Rule enforcements: ${byEventType["rule_enforced"] ?? 0}`,
20458
+ `Approvals recorded: ${byEventType["approval"] ?? 0}`,
20459
+ `Reviews: ${byEventType["review"] ?? 0}`,
20460
+ `Commits: ${byEventType["commit"] ?? 0}`,
20461
+ "",
20462
+ "## 3. Approval Status"
20463
+ ];
20464
+ const pendingApprovals = entries.filter((e2) => e2.approval_status === "pending");
20465
+ const deniedApprovals = entries.filter((e2) => e2.approval_status === "denied");
20466
+ lines.push(`Pending approvals: ${pendingApprovals.length}`);
20467
+ lines.push(`Denied approvals: ${deniedApprovals.length}`);
20468
+ lines.push("");
20469
+ lines.push("## 4. Events Requiring Review");
20470
+ const reviewEntries = entries.filter((e2) => e2.approval_status === "denied" || e2.event_type === "rule_enforced");
20471
+ if (reviewEntries.length > 0) {
20472
+ for (const e2 of reviewEntries.slice(0, 20)) {
20473
+ lines.push(`- [${e2.timestamp.slice(0, 16)}] ${e2.event_type}: ${(e2.evidence ?? "").slice(0, 100)}`);
20474
+ }
20475
+ } else {
20476
+ lines.push("No events requiring review in this period.");
20477
+ }
20478
+ return text(lines.join("\n"));
20479
+ }
20480
+ function generateDetailedReport(entries, byEventType, byActor, days) {
20481
+ const lines = [
20482
+ `## Detailed Audit Report (${days} days)`,
20483
+ `Total events: ${entries.length}`,
20484
+ "",
20485
+ "### Event Type Distribution"
20486
+ ];
20487
+ for (const [et, count] of Object.entries(byEventType).sort((a2, b2) => b2[1] - a2[1])) {
20488
+ lines.push(`- ${et}: ${count}`);
20489
+ }
20490
+ lines.push("");
20491
+ lines.push("### Actor Distribution");
20492
+ for (const [actor, count] of Object.entries(byActor).sort((a2, b2) => b2[1] - a2[1])) {
20493
+ lines.push(`- ${actor}: ${count}`);
20494
+ }
20495
+ lines.push("");
20496
+ lines.push("### All Events");
20497
+ for (const e2 of entries.slice(0, 100)) {
20498
+ lines.push(`#### ${e2.timestamp.slice(0, 16)} [${e2.event_type}]`);
20499
+ lines.push(`Actor: ${e2.actor} | Change: ${e2.change_type ?? "-"} | Approval: ${e2.approval_status ?? "-"}`);
20500
+ if (e2.file_path) lines.push(`File: ${e2.file_path}`);
20501
+ if (e2.evidence) lines.push(`Evidence: ${e2.evidence}`);
20502
+ lines.push("");
20503
+ }
20504
+ if (entries.length > 100) {
20505
+ lines.push(`... and ${entries.length - 100} more entries`);
20506
+ }
20507
+ return text(lines.join("\n"));
20508
+ }
20509
+ function handleAuditChain(args2, db) {
20510
+ const filePath = args2.file;
20511
+ if (!filePath) return text(`Usage: ${p2("audit_chain")} { file: "src/path/to/file.ts" } - Get complete audit history for a file.`);
20512
+ const chain = getFileChain(db, filePath);
20513
+ if (chain.length === 0) {
20514
+ return text(`No audit entries found for "${filePath}". Audit entries are recorded automatically when files are modified, reviewed, or validated. Try: ${p2("audit_log")} {} to see all recent audit entries across all files.`);
20515
+ }
20516
+ const lines = [
20517
+ `## Audit Chain: ${filePath}`,
20518
+ `Total events: ${chain.length}`,
20519
+ ""
20520
+ ];
20521
+ for (const entry of chain) {
20522
+ lines.push(`### ${entry.timestamp.slice(0, 16)} [${entry.event_type}]`);
20523
+ lines.push(`**${entry.actor}** (${entry.change_type ?? "unknown"})`);
20524
+ if (entry.evidence) lines.push(entry.evidence);
20525
+ lines.push("");
20526
+ }
20527
+ return text(lines.join("\n"));
20528
+ }
20529
+ function text(content) {
20530
+ return { content: [{ type: "text", text: content }] };
20531
+ }
20532
+ var DEFAULT_FORMATS, AUDIT_BASE_NAMES;
20533
+ var init_audit_trail = __esm({
20534
+ "src/audit-trail.ts"() {
20535
+ "use strict";
20536
+ init_config();
20537
+ DEFAULT_FORMATS = ["summary", "detailed", "soc2"];
20538
+ AUDIT_BASE_NAMES = /* @__PURE__ */ new Set(["audit_log", "audit_report", "audit_chain"]);
20539
+ }
20540
+ });
20541
+
20542
+ // src/template-renderer.ts
20543
+ function renderTemplate2(template, vars) {
20544
+ return template.replace(INTERPOLATION_TOKEN, (_2, raw) => {
20545
+ const key = raw.trim();
20546
+ if (!IDENTIFIER_SHAPE.test(key)) {
20547
+ throw new TemplateAllowlistError(key, [...ALLOWED_VARS]);
20548
+ }
20549
+ if (!ALLOWED_VARS.has(key)) {
20550
+ throw new TemplateAllowlistError(key, [...ALLOWED_VARS]);
20551
+ }
20552
+ const v3 = vars[key];
20553
+ if (v3 === void 0) {
20554
+ throw new TemplateMissingValueError(key);
20555
+ }
20556
+ return String(v3);
20557
+ });
20558
+ }
20559
+ var ALLOWED_VARS, TemplateAllowlistError, TemplateMissingValueError, IDENTIFIER_SHAPE, INTERPOLATION_TOKEN;
20560
+ var init_template_renderer = __esm({
20561
+ "src/template-renderer.ts"() {
20562
+ "use strict";
20563
+ ALLOWED_VARS = /* @__PURE__ */ new Set([
20564
+ "date",
20565
+ "slug",
20566
+ "score",
20567
+ "signals_csv",
20568
+ "prompt_preview",
20569
+ "destination_name"
20570
+ ]);
20571
+ TemplateAllowlistError = class extends Error {
20572
+ constructor(key, allowedVars) {
20573
+ super(`template variable "${key}" is not in the allowlist [${allowedVars.join(", ")}]`);
20574
+ this.key = key;
20575
+ this.allowedVars = allowedVars;
20576
+ this.name = "TemplateAllowlistError";
20577
+ }
20578
+ key;
20579
+ allowedVars;
20580
+ };
20581
+ TemplateMissingValueError = class extends Error {
20582
+ constructor(key) {
20583
+ super(`template variable "${key}" is allowed but no value was supplied`);
20584
+ this.key = key;
20585
+ this.name = "TemplateMissingValueError";
20586
+ }
20587
+ key;
20588
+ };
20589
+ IDENTIFIER_SHAPE = /^[a-zA-Z_][a-zA-Z0-9_]*$/;
20590
+ INTERPOLATION_TOKEN = /\$\{([^}]*)\}/g;
20591
+ }
20592
+ });
20593
+
20594
+ // src/rule-candidate-funnel.ts
20595
+ function recordApprovalFunnelEvent(db, promptHash, destination, score, entitledTeam) {
20596
+ if (!entitledTeam) return;
20597
+ enqueueRulePromotionEvent(db, {
20598
+ prompt_hash: promptHash,
20599
+ event_type: "approved",
20600
+ created_at: (/* @__PURE__ */ new Date()).toISOString(),
20601
+ metadata: { destination, score }
20602
+ });
20603
+ }
20604
+ function recordDismissalFunnelEvent(db, promptHash, score) {
20605
+ try {
20606
+ if (entitledForTeamSharedPromotion(getCachedTierReadOnly(db))) {
20607
+ enqueueRulePromotionEvent(db, {
20608
+ prompt_hash: promptHash,
20609
+ event_type: "dismissed",
20610
+ created_at: (/* @__PURE__ */ new Date()).toISOString(),
20611
+ metadata: { score }
20612
+ });
20613
+ }
20614
+ } catch {
20615
+ }
20616
+ }
20617
+ var init_rule_candidate_funnel = __esm({
20618
+ "src/rule-candidate-funnel.ts"() {
20619
+ "use strict";
20620
+ init_memory_db();
20621
+ init_license();
20622
+ init_auto_learning_entitlement();
20623
+ }
20624
+ });
20625
+
20626
+ // src/rule-candidate-hardened.ts
20627
+ function isHardenedShareableDestination(destination) {
20628
+ return TEAM_HARDENED_SHAREABLE_DESTINATIONS.includes(destination);
20629
+ }
20630
+ function roleRank(role) {
20631
+ switch (role) {
20632
+ case "owner":
20633
+ return 4;
20634
+ case "admin":
20635
+ return 3;
20636
+ case "developer":
20637
+ return 2;
20638
+ case "auditor":
20639
+ return 1;
20640
+ default:
20641
+ return 0;
20642
+ }
20643
+ }
20644
+ function validateGovernanceGate(policy, approvals, ctx) {
20645
+ if (policy.min_promoter_role != null && ctx?.promoterRole !== void 0) {
20646
+ if (roleRank(ctx.promoterRole) < roleRank(policy.min_promoter_role)) {
20647
+ return `promoter role '${ctx.promoterRole}' is below the org minimum '${policy.min_promoter_role}' \u2014 refusing to apply`;
20648
+ }
20649
+ }
20650
+ if (ctx?.destination !== void 0 && !policy.allowed_destinations.includes(ctx.destination)) {
20651
+ return `destination '${ctx.destination}' is not in the org's allowed destinations \u2014 refusing to apply`;
20652
+ }
20653
+ if (policy.require_hardened_review === true && (ctx?.destination === "pattern-scanner" || ctx?.destination === "custom-destination")) {
20654
+ if (!(ctx.hardened === true && ctx.hasAttestation === true)) {
20655
+ return "org policy requires hardened review for executable destinations \u2014 refusing to apply";
20656
+ }
20657
+ }
20658
+ const distinct = new Set(
20659
+ approvals.approver_user_ids.filter((id) => typeof id === "string" && id.length > 0 && id !== approvals.promoted_by)
20660
+ );
20661
+ if (distinct.size < policy.approvals_required) {
20662
+ return `needs ${policy.approvals_required} approval(s); ${distinct.size} recorded \u2014 refusing to apply`;
20663
+ }
20664
+ return null;
20665
+ }
20666
+ function validateHardenedApplyGate(prov) {
20667
+ if (prov.hardened !== true) {
20668
+ return "team-origin hardened-destination candidate missing hardened provenance flag \u2014 refusing to apply";
20669
+ }
20670
+ const att = prov.review_attestation;
20671
+ if (!att || typeof att !== "object") {
20672
+ return "team-origin hardened candidate missing review_attestation \u2014 refusing to apply";
20673
+ }
20674
+ if (typeof att.second_operator_id !== "string" || att.second_operator_id.length === 0) {
20675
+ return "team-origin hardened candidate review_attestation missing second_operator_id \u2014 refusing to apply";
20676
+ }
20677
+ const govErr = validateGovernanceGate(
20678
+ { approvals_required: 1, allowed_destinations: [] },
20679
+ { approver_user_ids: [att.second_operator_id], promoted_by: prov.promoted_by }
20680
+ );
20681
+ if (govErr) {
20682
+ return "team-origin hardened candidate second_operator_id equals the promoter \u2014 two-operator review not satisfied";
20683
+ }
20684
+ const ack = att.dry_run_ack;
20685
+ if (!ack || typeof ack !== "object" || ack.ack !== true || typeof ack.ran_at !== "string") {
20686
+ return "team-origin hardened candidate missing render-only dry_run_ack \u2014 refusing to apply";
20687
+ }
20688
+ return null;
20689
+ }
20690
+ var TEAM_HARDENED_SHAREABLE_DESTINATIONS;
20691
+ var init_rule_candidate_hardened = __esm({
20692
+ "src/rule-candidate-hardened.ts"() {
20693
+ "use strict";
20694
+ TEAM_HARDENED_SHAREABLE_DESTINATIONS = [
20695
+ "pattern-scanner",
20696
+ "custom-destination"
20697
+ ];
20698
+ }
20699
+ });
20700
+
20701
+ // src/rule-candidate-applier.ts
20702
+ import { readFileSync as readFileSync26, writeFileSync as writeFileSync9, existsSync as existsSync32, unlinkSync as unlinkSync5, mkdirSync as mkdirSync14 } from "fs";
20703
+ import { join as join20, dirname as dirname17, isAbsolute as isAbsolute2 } from "path";
20704
+ import { homedir as homedir12 } from "os";
20705
+ import { createHash as createHash9 } from "crypto";
20706
+ function isRuleDestination(value) {
20707
+ return typeof value === "string" && ALL_RULE_DESTINATIONS.includes(value);
20708
+ }
20709
+ function isTeamShareableDestination(destination) {
20710
+ return TEAM_SHAREABLE_DESTINATIONS.includes(destination);
20711
+ }
20712
+ function resolveMemoryDir(projectRoot = getProjectRoot(), home = homedir12()) {
20713
+ return join20(home, ".claude", "projects", encodeMemoryDirName(projectRoot), "memory");
20714
+ }
20715
+ function resolveMemoryIndexPath(projectRoot = getProjectRoot(), home = homedir12()) {
20716
+ return join20(resolveMemoryDir(projectRoot, home), "MEMORY.md");
20717
+ }
20718
+ function deriveSlug2(prompt) {
20719
+ return deriveSlug(prompt);
20720
+ }
20721
+ function writeIndexLineInRegion(indexPath, line) {
20722
+ const pre = readMemoryIndex(indexPath);
20723
+ if (pre === void 0) {
20724
+ throw new MemoryIndexMissing("MEMORY.md does not exist \u2014 index line skipped (file still written)");
20725
+ }
20726
+ const existing = readRegionLines(pre);
20727
+ if (existing.includes(line)) return;
20728
+ const next = [...existing, line].slice(-DEFAULT_MAX_REGION_LINES);
20729
+ const post = renderRegion(pre, next);
20730
+ if (post !== pre) writeMemoryIndex(indexPath, pre, post);
20731
+ }
20732
+ function correctionsMdTargets(slug, projectRoot, home) {
20733
+ const dir = resolveMemoryDir(projectRoot, home);
20734
+ return {
20735
+ correctionsMd: join20(dir, "corrections.md"),
20736
+ feedbackMd: join20(dir, `feedback_${slug}.md`),
20737
+ memoryIndex: join20(dir, "MEMORY.md")
20738
+ };
20739
+ }
20740
+ function readCandidate(candidateId, projectRoot = getProjectRoot()) {
20741
+ if (!CANDIDATE_ID_PATTERN.test(candidateId)) {
20742
+ throw new InvalidCandidateIdError(candidateId);
20743
+ }
20744
+ const path = join20(projectRoot, ".massu", "rule-candidates", `${candidateId}.json`);
20745
+ if (!existsSync32(path)) throw new CandidateNotFoundError(path);
20746
+ const raw = readFileSync26(path, "utf-8");
20747
+ let parsed;
20748
+ try {
20749
+ parsed = JSON.parse(raw);
20750
+ } catch (err) {
20751
+ throw new CandidatePayloadValidationError(`JSON parse: ${err instanceof Error ? err.message : String(err)}`);
20752
+ }
20753
+ return validateCandidatePayload(parsed);
20754
+ }
20755
+ function validateCandidatePayload(raw) {
20756
+ if (!raw || typeof raw !== "object") {
20757
+ throw new CandidatePayloadValidationError("payload is not an object");
20758
+ }
20759
+ const r2 = raw;
20760
+ if (typeof r2.prompt !== "string" || r2.prompt.length === 0) {
20761
+ throw new CandidatePayloadValidationError("prompt must be a non-empty string");
20762
+ }
20763
+ if (typeof r2.prompt_hash !== "string" || !/^[a-f0-9]{16}$/.test(r2.prompt_hash)) {
20764
+ throw new CandidatePayloadValidationError("prompt_hash must be 16 hex chars");
20765
+ }
20766
+ if (typeof r2.score !== "number" || r2.score < -200 || r2.score > 200 || !Number.isFinite(r2.score)) {
20767
+ throw new CandidatePayloadValidationError("score must be a finite number in [-200, 200]");
20768
+ }
20769
+ if (!Array.isArray(r2.signals)) {
20770
+ throw new CandidatePayloadValidationError("signals must be an array");
20771
+ }
20772
+ for (const s of r2.signals) {
20773
+ if (!s || typeof s !== "object") throw new CandidatePayloadValidationError("signal entry not an object");
20774
+ const sig = s;
20775
+ if (typeof sig.name !== "string") throw new CandidatePayloadValidationError("signal.name must be a string");
20776
+ if (typeof sig.baseWeight !== "number") throw new CandidatePayloadValidationError("signal.baseWeight must be a number");
20777
+ if (typeof sig.applied !== "number") throw new CandidatePayloadValidationError("signal.applied must be a number");
20778
+ }
20779
+ if (!Array.isArray(r2.prior_turn_files)) {
20780
+ throw new CandidatePayloadValidationError("prior_turn_files must be an array");
20781
+ }
20782
+ if (typeof r2.timestamp !== "string") {
20783
+ throw new CandidatePayloadValidationError("timestamp must be a string");
20784
+ }
20785
+ if (typeof r2.session_id !== "string") {
20786
+ throw new CandidatePayloadValidationError("session_id must be a string");
20787
+ }
20788
+ if (r2.provenance !== void 0) {
20789
+ if (!r2.provenance || typeof r2.provenance !== "object") {
20790
+ throw new CandidatePayloadValidationError("provenance must be an object when present");
20791
+ }
20792
+ const p19 = r2.provenance;
20793
+ if (p19.origin !== "team" && p19.origin !== "pack") {
20794
+ throw new CandidatePayloadValidationError("provenance.origin must be 'team' or 'pack'");
20795
+ }
20796
+ if (typeof p19.org_id !== "string" || p19.org_id.length === 0) {
20797
+ throw new CandidatePayloadValidationError("provenance.org_id must be a non-empty string");
20798
+ }
20799
+ if (typeof p19.promoted_by !== "string" || p19.promoted_by.length === 0) {
20800
+ throw new CandidatePayloadValidationError("provenance.promoted_by must be a non-empty string");
20801
+ }
20802
+ if (typeof p19.promoted_at !== "string") {
20803
+ throw new CandidatePayloadValidationError("provenance.promoted_at must be a string");
20804
+ }
20805
+ if (typeof p19.signature_verified !== "boolean") {
20806
+ throw new CandidatePayloadValidationError("provenance.signature_verified must be a boolean");
20807
+ }
20808
+ if (!isRuleDestination(r2.destination)) {
20809
+ throw new CandidatePayloadValidationError("provenance-bearing candidate must declare a valid destination");
20810
+ }
20811
+ if (r2.draft_text !== void 0 && typeof r2.draft_text !== "string") {
20812
+ throw new CandidatePayloadValidationError("draft_text must be a string when present");
20813
+ }
20814
+ } else if (r2.destination !== void 0 && !isRuleDestination(r2.destination)) {
20815
+ throw new CandidatePayloadValidationError("destination, when present, must be a valid RuleDestination");
20816
+ }
20817
+ return r2;
20818
+ }
20819
+ async function applyRuleCandidate(db, opts) {
20820
+ const ent = await assertAutoLearningEntitled();
20821
+ if (!ent.entitled) {
20822
+ return { ok: false, error: ent.message, tier_refused: true };
20823
+ }
20824
+ if (!CANDIDATE_ID_PATTERN.test(opts.candidateId)) {
20825
+ throw new InvalidCandidateIdError(opts.candidateId);
20826
+ }
20827
+ const projectRoot = opts.projectRoot ?? getProjectRoot();
20828
+ const home = opts.home ?? homedir12();
20829
+ const candidate = readCandidate(opts.candidateId, projectRoot);
20830
+ if (candidate.provenance?.origin === "team" || candidate.provenance?.origin === "pack") {
20831
+ const provOrigin = candidate.provenance.origin;
20832
+ if (candidate.destination !== void 0 && opts.destination !== candidate.destination) {
20833
+ return {
20834
+ ok: false,
20835
+ error: "destination mismatch: provenance-bearing candidate must apply to its authored destination"
20836
+ };
20837
+ }
20838
+ if (!entitledForTeamSharedPromotion(ent.currentTier)) {
20839
+ return { ok: false, error: teamSharedPromotionUpgradeMessage(ent.currentTier), tier_refused: true };
20840
+ }
20841
+ if (candidate.provenance.signature_verified !== true) {
20842
+ return { ok: false, error: `${provOrigin}-origin rule candidate has unverified provenance \u2014 refusing to apply` };
20843
+ }
20844
+ if (isTeamShareableDestination(opts.destination)) {
20845
+ } else if (isHardenedShareableDestination(opts.destination)) {
20846
+ const gateErr = validateHardenedApplyGate(candidate.provenance);
20847
+ if (gateErr) return { ok: false, error: gateErr };
20848
+ } else {
20849
+ return { ok: false, error: `${provOrigin}-origin candidate has a non-shareable destination \u2014 refusing to apply` };
20850
+ }
20851
+ }
20852
+ const slug = opts.slug ? deriveSlug2(opts.slug) : deriveSlug2(candidate.prompt);
20853
+ if (!SLUG_ALLOWED2.test(slug)) {
20854
+ throw new InvalidSlugError(slug);
20855
+ }
20856
+ const memoryIndex = resolveMemoryIndexPath(projectRoot, home);
20857
+ if (!existsSync32(memoryIndex)) {
20858
+ throw new MemoryIndexMissingError(memoryIndex);
20859
+ }
20860
+ let destTargets;
20861
+ try {
20862
+ destTargets = computeDestinationTargets(opts, candidate, slug, projectRoot, home);
20863
+ } catch (err) {
20864
+ return { ok: false, error: err instanceof Error ? err.message : String(err) };
20865
+ }
20866
+ const candidatePath = join20(projectRoot, ".massu", "rule-candidates", `${opts.candidateId}.json`);
20867
+ const snapshot = takeSnapshots([
20868
+ ...destTargets.paths,
20869
+ memoryIndex
20870
+ ]);
20871
+ db.exec("BEGIN");
20872
+ let auditRowId;
20873
+ let indexLineSkipped;
20874
+ try {
20875
+ const auditEntry = {
20876
+ eventType: "rule_promoted",
20877
+ actor: "human",
20878
+ sessionId: candidate.session_id,
20879
+ filePath: destTargets.primary,
20880
+ metadata: {
20881
+ candidate_id: opts.candidateId,
20882
+ prompt_hash: candidate.prompt_hash,
20883
+ score: candidate.score,
20884
+ classification: opts.destination,
20885
+ cr_assigned: opts.claudeMdCrNumber ?? null,
20886
+ recurrence_count: 0
20887
+ }
20888
+ };
20889
+ try {
20890
+ logAuditEntry(db, auditEntry);
20891
+ auditRowId = db.prepare("SELECT last_insert_rowid() AS id").get().id;
20892
+ } catch (err) {
20893
+ const msg = err instanceof Error ? err.message : String(err);
20894
+ if (/UNIQUE constraint failed/i.test(msg)) {
20895
+ db.exec("ROLLBACK");
20896
+ return { ok: true, idempotent_noop: true };
20897
+ }
20898
+ throw err;
20899
+ }
20900
+ writeDestination(opts, candidate, slug, destTargets, auditRowId);
20901
+ const indexLine = assertSingleLine(
20902
+ `- [${slug}](feedback_${slug}.md) \u2014 ${candidate.prompt.replace(/\n+/g, " ").slice(0, 100)}`
20903
+ );
20904
+ try {
20905
+ writeIndexLineInRegion(memoryIndex, indexLine);
20906
+ } catch (err) {
20907
+ if (err instanceof RegionRefused || err instanceof MemoryIndexMissing) {
20908
+ indexLineSkipped = err.message;
20909
+ } else {
20910
+ throw err;
20911
+ }
20912
+ }
20913
+ if (existsSync32(candidatePath)) unlinkSync5(candidatePath);
20914
+ let teamShared = false;
20915
+ const localOrigin = candidate.provenance?.origin !== "team" && candidate.provenance?.origin !== "pack";
20916
+ const entitledTeam = entitledForTeamSharedPromotion(ent.currentTier);
20917
+ const nonHardenedShare = isTeamShareableDestination(opts.destination);
20918
+ const hardenedShare = isHardenedShareableDestination(opts.destination) && opts.reviewAttestation != null;
20919
+ if (localOrigin && entitledTeam && (nonHardenedShare || hardenedShare)) {
20920
+ enqueueTeamPromotion(db, {
20921
+ prompt_hash: candidate.prompt_hash,
20922
+ destination: opts.destination,
20923
+ draft_text: opts.draftText,
20924
+ score: candidate.score,
20925
+ signals: candidate.signals,
20926
+ content_hash: createHash9("sha256").update(`${opts.destination}
20927
+ ${opts.draftText}`).digest("hex"),
20928
+ ...hardenedShare ? { hardened: true, review_attestation: opts.reviewAttestation } : {}
20929
+ });
20930
+ teamShared = true;
20931
+ }
20932
+ setCandidateStatus(db, candidate.prompt_hash, "promoted", opts.destination);
20933
+ db.exec("COMMIT");
20934
+ if (localOrigin) recordApprovalFunnelEvent(db, candidate.prompt_hash, opts.destination, candidate.score, entitledTeam);
20935
+ return { ok: true, audit_log_id: auditRowId, team_shared: teamShared };
20936
+ } catch (err) {
20937
+ db.exec("ROLLBACK");
20938
+ restoreSnapshots(snapshot);
20939
+ const msg = err instanceof Error ? err.message : String(err);
20940
+ appendFailureLog(projectRoot, {
20941
+ candidate_id: opts.candidateId,
20942
+ destination: opts.destination,
20943
+ error: msg,
20944
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
20945
+ });
20946
+ return { ok: false, error: msg };
20947
+ }
20948
+ }
20949
+ function computeDestinationTargets(opts, candidate, slug, projectRoot, home) {
20950
+ if (opts.destination === "corrections-md") {
20951
+ const t2 = correctionsMdTargets(slug, projectRoot, home);
20952
+ return { primary: t2.feedbackMd, paths: [t2.correctionsMd, t2.feedbackMd] };
20953
+ }
20954
+ if (opts.destination === "pattern-scanner") {
20955
+ if (opts.patternScannerCheckNumber == null) {
20956
+ throw new DestinationConfigError("pattern-scanner destination requires patternScannerCheckNumber");
20957
+ }
20958
+ const scannerPath = join20(projectRoot, "scripts", "massu-pattern-scanner.sh");
20959
+ const driftGuardPath = join20(projectRoot, "packages", "core", "src", "__tests__", `${slug}-drift-guard.test.ts`);
20960
+ return { primary: scannerPath, paths: [scannerPath, driftGuardPath] };
20961
+ }
20962
+ if (opts.destination === "claude-md-cr") {
20963
+ if (opts.claudeMdCrNumber == null) {
20964
+ throw new DestinationConfigError("claude-md-cr destination requires claudeMdCrNumber");
20965
+ }
20966
+ const claudePath = join20(projectRoot, ".claude", "CLAUDE.md");
20967
+ return { primary: claudePath, paths: [claudePath] };
20968
+ }
20969
+ if (opts.destination === "custom-destination") {
20970
+ if (!opts.customDestination) {
20971
+ throw new DestinationConfigError("custom-destination requires customDestination config entry");
20972
+ }
20973
+ const raw = opts.customDestination.path;
20974
+ const absolute = isAbsolute2(raw) ? raw : join20(projectRoot, raw);
20975
+ try {
20976
+ assertContainedIn(projectRoot, absolute, { allowNested: true });
20977
+ } catch (err) {
20978
+ throw new DestinationConfigError(
20979
+ `custom destination path escapes project root: ${raw} (${err.message})`
20980
+ );
20981
+ }
20982
+ return { primary: absolute, paths: [absolute] };
20983
+ }
20984
+ throw new DestinationConfigError(`unknown destination "${opts.destination}"`);
20985
+ }
20986
+ function writeDestination(opts, candidate, slug, targets, auditRowId) {
20987
+ if (opts.destination === "corrections-md") {
20988
+ return writeCorrectionsMd(opts, candidate, slug, targets, auditRowId);
20989
+ }
20990
+ if (opts.destination === "pattern-scanner") {
20991
+ return writePatternScanner(opts, candidate, slug, targets);
20992
+ }
20993
+ if (opts.destination === "claude-md-cr") {
20994
+ return writeClaudeMdCr(opts, candidate, slug, targets);
20995
+ }
20996
+ if (opts.destination === "custom-destination") {
20997
+ return writeCustomDestination(opts, candidate, slug, targets);
20998
+ }
20999
+ throw new DestinationConfigError(`unknown destination "${opts.destination}"`);
21000
+ }
21001
+ function writeCorrectionsMd(opts, candidate, slug, targets, auditRowId) {
21002
+ const [correctionsMd, feedbackMd] = targets.paths;
21003
+ const dateIso = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
21004
+ const correctionsEntry = formatCorrectionEntry({
21005
+ date: dateIso,
21006
+ title: slug,
21007
+ rule: opts.draftText,
21008
+ extra: {
21009
+ prompt_hash: candidate.prompt_hash,
21010
+ score: candidate.score,
21011
+ audit_log_id: auditRowId
21012
+ }
21013
+ });
21014
+ if (!existsSync32(dirname17(correctionsMd))) mkdirSync14(dirname17(correctionsMd), { recursive: true });
21015
+ const correctionsPre = existsSync32(correctionsMd) ? readFileSync26(correctionsMd, "utf-8") : "# Corrections\n";
21016
+ writeFileSync9(correctionsMd, correctionsPre + correctionsEntry, "utf-8");
21017
+ const body = `${opts.draftText}
21018
+ `;
21019
+ const mac = mintAuthorship(body);
21020
+ const feedbackBody = [
21021
+ "---",
21022
+ `name: ${slug}`,
21023
+ `description: ${candidate.prompt.replace(/\n+/g, " ").slice(0, 200)}`,
21024
+ "type: feedback",
21025
+ "metadata:",
21026
+ ` source: interactive`,
21027
+ ` prompt_hash: ${candidate.prompt_hash}`,
21028
+ ` audit_log_id: ${auditRowId}`,
21029
+ ` score: ${candidate.score}`,
21030
+ // Display-only; never read for trust (drift-guarded). The MAC is the credential.
21031
+ ...mac ? ["massu_authored: true", `${RENDER_MAC_KEY}: ${mac}`] : [],
21032
+ "---",
21033
+ "",
21034
+ body
21035
+ ].join("\n");
21036
+ atomicWriteFileSync(feedbackMd, feedbackBody);
21037
+ }
21038
+ function writePatternScanner(opts, candidate, slug, targets) {
21039
+ const [scannerPath, driftGuardPath] = targets.paths;
21040
+ const checkNumber = opts.patternScannerCheckNumber;
21041
+ const scannerPre = existsSync32(scannerPath) ? readFileSync26(scannerPath, "utf-8") : "";
21042
+ if (scannerPre.includes(`${SCANNER_AUTOGEN_MARKER_PREFIX} ${candidate.prompt_hash}`)) {
21043
+ throw new DestinationConfigError(
21044
+ `scanner already contains a check auto-generated for prompt_hash ${candidate.prompt_hash}`
21045
+ );
21046
+ }
21047
+ const checkBlock = [
21048
+ "",
21049
+ `${SCANNER_AUTOGEN_MARKER_PREFIX} ${candidate.prompt_hash} (slug=${slug})`,
21050
+ `# Check ${checkNumber}: ${slug}`,
21051
+ 'echo ""',
21052
+ `echo "Check ${checkNumber}: ${slug}"`,
21053
+ opts.draftText.trim(),
21054
+ ""
21055
+ ].join("\n");
21056
+ writeFileSync9(scannerPath, scannerPre + checkBlock, "utf-8");
21057
+ const driftGuard = [
21058
+ "// Copyright (c) 2026 Massu. All rights reserved.",
21059
+ "// Licensed under BSL 1.1 - see LICENSE file for details.",
21060
+ "",
21061
+ `// Drift-guard mirror for pattern-scanner Check ${checkNumber} (${slug}).`,
21062
+ `// Auto-generated by /massu-rule approve for prompt_hash ${candidate.prompt_hash}.`,
21063
+ "",
21064
+ "import { describe, it, expect } from 'vitest';",
21065
+ "import { readFileSync } from 'fs';",
21066
+ "import { join } from 'path';",
21067
+ "",
21068
+ `describe('${slug} drift-guard', () => {`,
21069
+ ` it('pattern-scanner Check ${checkNumber} is present', () => {`,
21070
+ ` const scanner = readFileSync(join(process.cwd(), 'scripts', 'massu-pattern-scanner.sh'), 'utf-8');`,
21071
+ ` expect(scanner).toContain('Check ${checkNumber}: ${slug}');`,
21072
+ ` expect(scanner).toContain('${candidate.prompt_hash}');`,
21073
+ ` });`,
21074
+ `});`,
21075
+ ""
21076
+ ].join("\n");
21077
+ if (!existsSync32(dirname17(driftGuardPath))) mkdirSync14(dirname17(driftGuardPath), { recursive: true });
21078
+ writeFileSync9(driftGuardPath, driftGuard, "utf-8");
21079
+ }
21080
+ function writeClaudeMdCr(opts, candidate, slug, targets) {
21081
+ const [claudePath] = targets.paths;
21082
+ const crNumber = opts.claudeMdCrNumber;
21083
+ const pre = existsSync32(claudePath) ? readFileSync26(claudePath, "utf-8") : "";
21084
+ if (new RegExp(`\\bCR-${crNumber}\\b`).test(pre)) {
21085
+ throw new DestinationConfigError(`CLAUDE.md already contains a CR-${crNumber} entry`);
21086
+ }
21087
+ const tableHeaderRe = /(\| ID \| Rule \| Verification Type \|\s*\n\|[-| ]+\|\s*\n)((?:\| CR-\d+ \|[^\n]*\n)+)/;
21088
+ const tableMatch = pre.match(tableHeaderRe);
21089
+ if (!tableMatch) {
21090
+ throw new DestinationConfigError(
21091
+ "CLAUDE.md does not contain a recognizable Canonical Rules table \u2014 refusing to write CR row"
21092
+ );
21093
+ }
21094
+ const tableHeader = tableMatch[1];
21095
+ const existingRows = tableMatch[2];
21096
+ const newRow = `| CR-${crNumber} | ${opts.draftText.replace(/\n+/g, " ").trim().slice(0, 400)} | VR-NEW-${slug.toUpperCase()} |
21097
+ `;
21098
+ const updatedTable = tableHeader + existingRows + newRow;
21099
+ const afterTable = pre.replace(tableHeaderRe, updatedTable);
21100
+ const block = [
21101
+ "",
21102
+ "---",
21103
+ "",
21104
+ `### CR-${crNumber}: ${slug}`,
21105
+ "",
21106
+ `Auto-promoted from rule-candidate ${candidate.prompt_hash} (score=${candidate.score}).`,
21107
+ "",
21108
+ opts.draftText,
21109
+ ""
21110
+ ].join("\n");
21111
+ writeFileSync9(claudePath, afterTable + block, "utf-8");
21112
+ }
21113
+ function writeCustomDestination(opts, candidate, slug, targets) {
21114
+ const [destPath] = targets.paths;
21115
+ const entry = opts.customDestination;
21116
+ const vars = {
21117
+ date: (/* @__PURE__ */ new Date()).toISOString(),
21118
+ slug,
21119
+ score: candidate.score,
21120
+ signals_csv: candidate.signals.map((s) => s.name).join(","),
21121
+ prompt_preview: candidate.prompt.replace(/\n+/g, " ").slice(0, 120),
21122
+ destination_name: entry.name
21123
+ };
21124
+ const rendered = renderTemplate2(entry.template, vars);
21125
+ if (!existsSync32(dirname17(destPath))) mkdirSync14(dirname17(destPath), { recursive: true });
21126
+ const pre = existsSync32(destPath) ? readFileSync26(destPath, "utf-8") : "";
21127
+ const sep3 = pre && !pre.endsWith("\n") ? "\n" : "";
21128
+ writeFileSync9(destPath, pre + sep3 + rendered + (rendered.endsWith("\n") ? "" : "\n"), "utf-8");
21129
+ }
21130
+ function dismissRuleCandidate(db, opts) {
21131
+ if (!CANDIDATE_ID_PATTERN.test(opts.candidateId)) {
21132
+ throw new InvalidCandidateIdError(opts.candidateId);
21133
+ }
21134
+ const projectRoot = opts.projectRoot ?? getProjectRoot();
21135
+ const candidate = readCandidate(opts.candidateId, projectRoot);
21136
+ const candidatePath = join20(projectRoot, ".massu", "rule-candidates", `${opts.candidateId}.json`);
21137
+ db.exec("BEGIN");
21138
+ let auditRowId;
21139
+ let blacklistUpdates = 0;
21140
+ try {
21141
+ const upsert = db.prepare(`
21142
+ INSERT INTO prompt_outcomes_signal_blacklist (signal, dismissal_count, last_dismissed_at)
21143
+ VALUES (?, 1, datetime('now'))
21144
+ ON CONFLICT(signal) DO UPDATE SET
21145
+ dismissal_count = dismissal_count + 1,
21146
+ last_dismissed_at = datetime('now')
21147
+ `);
21148
+ for (const sig of candidate.signals) {
21149
+ if (sig.baseWeight <= 0) continue;
21150
+ const r2 = upsert.run(sig.name);
21151
+ blacklistUpdates += r2.changes;
21152
+ }
21153
+ const entry = {
21154
+ eventType: "rule_dismissed",
21155
+ actor: "human",
21156
+ sessionId: candidate.session_id,
21157
+ metadata: {
21158
+ candidate_id: opts.candidateId,
21159
+ prompt_hash: candidate.prompt_hash,
21160
+ score: candidate.score,
21161
+ reason: opts.reason,
21162
+ signals_fired: candidate.signals.map((s) => s.name)
21163
+ }
21164
+ };
21165
+ logAuditEntry(db, entry);
21166
+ auditRowId = db.prepare("SELECT last_insert_rowid() AS id").get().id;
21167
+ setCandidateStatus(db, candidate.prompt_hash, "dismissed");
21168
+ db.exec("COMMIT");
21169
+ } catch (err) {
21170
+ db.exec("ROLLBACK");
21171
+ return {
21172
+ ok: false,
21173
+ signals_blacklisted_or_incremented: 0,
21174
+ error: err instanceof Error ? err.message : String(err)
21175
+ };
21176
+ }
21177
+ try {
21178
+ const dismissedLog = join20(projectRoot, ".massu", "rule-candidates", ".dismissed.jsonl");
21179
+ if (!existsSync32(dirname17(dismissedLog))) mkdirSync14(dirname17(dismissedLog), { recursive: true });
21180
+ const pre = existsSync32(dismissedLog) ? readFileSync26(dismissedLog, "utf-8") : "";
21181
+ const sep3 = pre && !pre.endsWith("\n") ? "\n" : "";
21182
+ writeFileSync9(dismissedLog, pre + sep3 + JSON.stringify({
21183
+ id: opts.candidateId,
21184
+ prompt_hash: candidate.prompt_hash,
21185
+ score: candidate.score,
21186
+ signals_fired: candidate.signals.map((s) => s.name),
21187
+ reason: opts.reason,
21188
+ audit_log_id: auditRowId,
21189
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
21190
+ }) + "\n", "utf-8");
21191
+ if (existsSync32(candidatePath)) unlinkSync5(candidatePath);
21192
+ } catch (err) {
21193
+ appendFailureLog(projectRoot, {
21194
+ candidate_id: opts.candidateId,
21195
+ destination: "dismiss-postcommit",
21196
+ error: err instanceof Error ? err.message : String(err),
21197
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
21198
+ });
21199
+ }
21200
+ recordDismissalFunnelEvent(db, candidate.prompt_hash, candidate.score);
21201
+ return {
21202
+ ok: true,
21203
+ audit_log_id: auditRowId,
21204
+ signals_blacklisted_or_incremented: blacklistUpdates
21205
+ };
21206
+ }
21207
+ function appendFailureLog(projectRoot, entry) {
21208
+ try {
21209
+ const dir = join20(projectRoot, ".massu", "rule-candidates");
21210
+ if (!existsSync32(dir)) mkdirSync14(dir, { recursive: true });
21211
+ const logPath = join20(dir, ".failures.jsonl");
21212
+ const pre = existsSync32(logPath) ? readFileSync26(logPath, "utf-8") : "";
21213
+ const sep3 = pre && !pre.endsWith("\n") ? "\n" : "";
21214
+ writeFileSync9(logPath, pre + sep3 + JSON.stringify(entry) + "\n", "utf-8");
21215
+ } catch {
21216
+ }
21217
+ }
21218
+ var CANDIDATE_ID_PATTERN, SLUG_ALLOWED2, SCANNER_AUTOGEN_MARKER_PREFIX, InvalidCandidateIdError, InvalidSlugError, CandidatePayloadValidationError, ALL_RULE_DESTINATIONS, TEAM_SHAREABLE_DESTINATIONS, MemoryIndexMissingError, CandidateNotFoundError, DestinationConfigError;
21219
+ var init_rule_candidate_applier = __esm({
21220
+ "src/rule-candidate-applier.ts"() {
21221
+ "use strict";
21222
+ init_safe_write();
21223
+ init_corrections_md();
21224
+ init_memory_authorship();
21225
+ init_memory_index_region();
21226
+ init_config();
21227
+ init_memory_path();
21228
+ init_audit_trail();
21229
+ init_template_renderer();
21230
+ init_auto_learning_entitlement();
21231
+ init_memory_db();
21232
+ init_rule_candidate_funnel();
21233
+ init_rule_candidate_store();
21234
+ init_rule_candidate_hardened();
21235
+ init_rule_candidate_hardened();
21236
+ init_rule_candidate_snapshot();
21237
+ CANDIDATE_ID_PATTERN = /^[a-f0-9]{16}$/;
21238
+ SLUG_ALLOWED2 = /^[a-z0-9_]+$/;
21239
+ SCANNER_AUTOGEN_MARKER_PREFIX = "# auto-generated by /massu-rule approve";
21240
+ InvalidCandidateIdError = class extends Error {
21241
+ constructor(id) {
21242
+ super(`candidate id "${id}" is not a 16-hex-char sha-keyed identifier`);
21243
+ this.id = id;
21244
+ this.name = "InvalidCandidateIdError";
21245
+ }
21246
+ id;
21247
+ };
21248
+ InvalidSlugError = class extends Error {
21249
+ constructor(slug) {
21250
+ super(`slug "${slug}" contains characters outside [a-z0-9_]`);
21251
+ this.slug = slug;
21252
+ this.name = "InvalidSlugError";
21253
+ }
21254
+ slug;
21255
+ };
21256
+ CandidatePayloadValidationError = class extends Error {
21257
+ constructor(issue) {
21258
+ super(`candidate payload validation failed: ${issue}`);
21259
+ this.issue = issue;
21260
+ this.name = "CandidatePayloadValidationError";
21261
+ }
21262
+ issue;
21263
+ };
21264
+ ALL_RULE_DESTINATIONS = ["corrections-md", "pattern-scanner", "claude-md-cr", "custom-destination"];
21265
+ TEAM_SHAREABLE_DESTINATIONS = [
21266
+ "corrections-md",
21267
+ "claude-md-cr"
21268
+ ];
21269
+ MemoryIndexMissingError = class extends Error {
21270
+ constructor(path) {
21271
+ super(`MEMORY.md missing at ${path} \u2014 v0.2 requires the canonical memory index. Create it via \`mkdir -p ${dirname17(path)} && echo '# Memory Index' > ${path}\` or run \`/massu-bearings\``);
21272
+ this.path = path;
21273
+ this.name = "MemoryIndexMissingError";
21274
+ }
21275
+ path;
21276
+ };
21277
+ CandidateNotFoundError = class extends Error {
21278
+ constructor(path) {
21279
+ super(`candidate sidecar not found: ${path}`);
21280
+ this.path = path;
21281
+ this.name = "CandidateNotFoundError";
21282
+ }
21283
+ path;
21284
+ };
21285
+ DestinationConfigError = class extends Error {
21286
+ constructor(msg) {
21287
+ super(msg);
21288
+ this.name = "DestinationConfigError";
21289
+ }
21290
+ };
21291
+ }
21292
+ });
21293
+
21294
+ // src/rule-candidate-preview.ts
21295
+ import { readFileSync as readFileSync27, writeFileSync as writeFileSync10, existsSync as existsSync33 } from "node:fs";
21296
+ function renderHardenedPreview(destination, body) {
21297
+ const rendered = String(body ?? "");
21298
+ const riskFindings = RISK_PATTERNS.filter((p19) => p19.re.test(rendered)).map((p19) => p19.label);
21299
+ return { destination, rendered, riskFindings };
21300
+ }
21301
+ function validateReviewAttestation(att) {
21302
+ if (!att || typeof att !== "object") {
21303
+ throw new InvalidReviewAttestationError("must be an object");
21304
+ }
21305
+ const a2 = att;
21306
+ if (typeof a2.second_operator_id !== "string" || a2.second_operator_id.length === 0) {
21307
+ throw new InvalidReviewAttestationError("second_operator_id must be a non-empty string");
21308
+ }
21309
+ const ack = a2.dry_run_ack;
21310
+ if (!ack || typeof ack !== "object" || ack.ack !== true || typeof ack.ran_at !== "string") {
21311
+ throw new InvalidReviewAttestationError("dry_run_ack must be { ran_at: string, ack: true }");
21312
+ }
21313
+ return {
21314
+ second_operator_id: a2.second_operator_id,
21315
+ dry_run_ack: {
21316
+ ran_at: ack.ran_at,
21317
+ ...typeof ack.exit_code === "number" ? { exit_code: ack.exit_code } : {},
21318
+ ack: true
21319
+ }
21320
+ };
21321
+ }
21322
+ function recordHardenedReviewAttestation(sidecarPath2, attestation) {
21323
+ if (!existsSync33(sidecarPath2)) {
21324
+ throw new Error(`candidate sidecar not found: ${sidecarPath2}`);
21325
+ }
21326
+ const validated = validateReviewAttestation(attestation);
21327
+ const raw = readFileSync27(sidecarPath2, "utf-8");
21328
+ const sidecar = JSON.parse(raw);
21329
+ const prov = sidecar.provenance;
21330
+ if (!prov || prov.origin !== "team" || prov.hardened !== true) {
21331
+ throw new Error("sidecar is not a hardened team-origin candidate \u2014 refusing to attach an attestation");
21332
+ }
21333
+ if (validated.second_operator_id === prov.promoted_by) {
21334
+ throw new InvalidReviewAttestationError(
21335
+ "second_operator_id equals the promoter \u2014 two-operator review requires a distinct operator"
21336
+ );
21337
+ }
21338
+ prov.review_attestation = validated;
21339
+ writeFileSync10(sidecarPath2, JSON.stringify(sidecar, null, 2), "utf-8");
21340
+ }
21341
+ var RISK_PATTERNS, InvalidReviewAttestationError;
21342
+ var init_rule_candidate_preview = __esm({
21343
+ "src/rule-candidate-preview.ts"() {
21344
+ "use strict";
21345
+ RISK_PATTERNS = [
21346
+ { re: /\brm\s+-[a-z]*r[a-z]*f?\b|\brm\s+-[a-z]*f[a-z]*r\b/i, label: "rm -rf (recursive/forced delete)" },
21347
+ { re: /\|\s*(sh|bash|zsh|dash)\b/i, label: "pipe-to-shell (| sh)" },
21348
+ { re: /\b(curl|wget|nc|ncat|telnet|ssh|scp)\b/i, label: "network tool (curl/wget/nc/ssh/...)" },
21349
+ { re: /\beval\b/i, label: "eval" },
21350
+ { re: /\$\([^)]*\)|`[^`]*`/, label: "command substitution $(...) / backticks" },
21351
+ { re: />\s*(\/|~|\$HOME|\.\.)/i, label: "redirect to absolute / home / parent path" },
21352
+ { re: /\bsudo\b/i, label: "sudo (privilege escalation)" },
21353
+ { re: /\b(chmod|chown)\b/i, label: "permission change (chmod/chown)" },
21354
+ { re: /\bdd\b\s|\bmkfs\b|\b:\(\)\s*\{/i, label: "destructive/forkbomb pattern" },
21355
+ { re: /\b(base64|xxd)\b.*\|/i, label: "encoded payload piped" }
21356
+ ];
21357
+ InvalidReviewAttestationError = class extends Error {
21358
+ constructor(issue) {
21359
+ super(`invalid review attestation: ${issue}`);
21360
+ this.name = "InvalidReviewAttestationError";
21361
+ }
21362
+ };
21363
+ }
21364
+ });
21365
+
20011
21366
  // src/security/promotion-pubkey.generated.ts
20012
21367
  var PROMOTION_PUBKEY_ED25519, PROMOTION_PUBKEY_FINGERPRINT_HEX, KNOWN_PROMOTION_PUBKEY_FINGERPRINTS;
20013
21368
  var init_promotion_pubkey_generated = __esm({
@@ -20041,20 +21396,8 @@ var init_promotion_envelope_verifier = __esm({
20041
21396
  }
20042
21397
  });
20043
21398
 
20044
- // src/rule-candidate-hardened.ts
20045
- var TEAM_HARDENED_SHAREABLE_DESTINATIONS;
20046
- var init_rule_candidate_hardened = __esm({
20047
- "src/rule-candidate-hardened.ts"() {
20048
- "use strict";
20049
- TEAM_HARDENED_SHAREABLE_DESTINATIONS = [
20050
- "pattern-scanner",
20051
- "custom-destination"
20052
- ];
20053
- }
20054
- });
20055
-
20056
21399
  // src/rule-pack-schema.ts
20057
- function isRuleDestination(value) {
21400
+ function isRuleDestination2(value) {
20058
21401
  return typeof value === "string" && RULE_DESTINATIONS.includes(value);
20059
21402
  }
20060
21403
  function isExecutableDestination(destination) {
@@ -20078,7 +21421,7 @@ function validateRulePackRule(rule) {
20078
21421
  if (!isNonEmptyString(r2.severity)) {
20079
21422
  errors.push("missing required field: severity");
20080
21423
  }
20081
- const destinationKnown = isRuleDestination(r2.destination);
21424
+ const destinationKnown = isRuleDestination2(r2.destination);
20082
21425
  if (!destinationKnown) {
20083
21426
  errors.push(
20084
21427
  `invalid destination: ${JSON.stringify(r2.destination)} is not a known RuleDestination (one of ${RULE_DESTINATIONS.join(", ")})`
@@ -20132,8 +21475,8 @@ var init_rule_pack_schema = __esm({
20132
21475
  });
20133
21476
 
20134
21477
  // src/rule-pack-sync.ts
20135
- import { existsSync as existsSync32, writeFileSync as writeFileSync9, mkdirSync as mkdirSync14 } from "fs";
20136
- import { join as join20, dirname as dirname17 } from "path";
21478
+ import { existsSync as existsSync34, writeFileSync as writeFileSync11, mkdirSync as mkdirSync15 } from "fs";
21479
+ import { join as join21, dirname as dirname18 } from "path";
20137
21480
  async function pullInstalledPackRules(db, opts = {}) {
20138
21481
  const config = getConfig();
20139
21482
  const cloud = config.cloud;
@@ -20195,7 +21538,7 @@ async function pullInstalledPackRules(db, opts = {}) {
20195
21538
  result.pulled += 1;
20196
21539
  const promptHash = packRulePromptHash(pack.slug, rule);
20197
21540
  const candidatePath = sidecarPath(projectRoot, promptHash);
20198
- if (existsSync32(candidatePath) || alreadyApplied(db, promptHash)) {
21541
+ if (existsSync34(candidatePath) || alreadyApplied(db, promptHash)) {
20199
21542
  result.skipped += 1;
20200
21543
  continue;
20201
21544
  }
@@ -20228,12 +21571,12 @@ function isValidInstalledPack(p19) {
20228
21571
  return typeof r2.slug === "string" && r2.slug.length > 0 && Array.isArray(r2.rules);
20229
21572
  }
20230
21573
  function packRulePromptHash(slug, rule) {
20231
- const { createHash: createHash14 } = __require("node:crypto");
21574
+ const { createHash: createHash15 } = __require("node:crypto");
20232
21575
  const body = `${slug}\0${rule.destination}\0${rule.title}\0${rule.pattern ?? rule.check ?? rule.description}`;
20233
- return createHash14("sha256").update(body, "utf-8").digest("hex").slice(0, 16);
21576
+ return createHash15("sha256").update(body, "utf-8").digest("hex").slice(0, 16);
20234
21577
  }
20235
21578
  function sidecarPath(projectRoot, promptHash) {
20236
- return join20(projectRoot, ".massu", "rule-candidates", `${promptHash}.json`);
21579
+ return join21(projectRoot, ".massu", "rule-candidates", `${promptHash}.json`);
20237
21580
  }
20238
21581
  function alreadyApplied(db, promptHash) {
20239
21582
  try {
@@ -20282,9 +21625,9 @@ function materializeCandidate(projectRoot, candidatePath, promptHash, rule, pack
20282
21625
  destination: rule.destination,
20283
21626
  draft_text: draftText
20284
21627
  };
20285
- const dir = dirname17(candidatePath);
20286
- if (!existsSync32(dir)) mkdirSync14(dir, { recursive: true });
20287
- writeFileSync9(candidatePath, JSON.stringify(sidecar, null, 2), "utf-8");
21628
+ const dir = dirname18(candidatePath);
21629
+ if (!existsSync34(dir)) mkdirSync15(dir, { recursive: true });
21630
+ writeFileSync11(candidatePath, JSON.stringify(sidecar, null, 2), "utf-8");
20288
21631
  }
20289
21632
  function emitDropTelemetry(db, eventType, data) {
20290
21633
  recordTelemetry(db, eventType, data);
@@ -20318,6 +21661,12 @@ var rule_exports = {};
20318
21661
  __export(rule_exports, {
20319
21662
  handleRuleSubcommand: () => handleRuleSubcommand
20320
21663
  });
21664
+ import { join as join22 } from "node:path";
21665
+ function flagValue2(args2, flag) {
21666
+ const i = args2.indexOf(flag);
21667
+ if (i === -1 || i === args2.length - 1) return void 0;
21668
+ return args2[i + 1];
21669
+ }
20321
21670
  async function handleRuleSubcommand(args2) {
20322
21671
  const sub = args2[0];
20323
21672
  switch (sub) {
@@ -20366,11 +21715,179 @@ async function handleRuleSubcommand(args2) {
20366
21715
  }
20367
21716
  return { exitCode: 0 };
20368
21717
  }
21718
+ case "review": {
21719
+ const candidateId = args2[1];
21720
+ if (!candidateId) {
21721
+ process.stderr.write(
21722
+ "Usage: massu rule review <candidate_id> --destination <dest> --draft <text>\n [--attest-operator <id> --attest-note <text>]\n"
21723
+ );
21724
+ return { exitCode: 2 };
21725
+ }
21726
+ const destination = flagValue2(args2, "--destination");
21727
+ const draft = flagValue2(args2, "--draft");
21728
+ if (!destination || !isRuleDestination(destination) || draft === void 0) {
21729
+ process.stderr.write("massu: review requires --destination <dest> and --draft <text>\n");
21730
+ return { exitCode: 2 };
21731
+ }
21732
+ const preview = renderHardenedPreview(destination, draft);
21733
+ process.stdout.write(
21734
+ `Hardened promotion review \u2014 ${preview.destination}
21735
+
21736
+ This is EXACTLY what would be applied (it is displayed, never run):
21737
+ ${"-".repeat(60)}
21738
+ ${preview.rendered}
21739
+ ${"-".repeat(60)}
21740
+
21741
+ `
21742
+ );
21743
+ if (preview.riskFindings.length > 0) {
21744
+ process.stdout.write(
21745
+ `RISK FINDINGS \u2014 ${preview.riskFindings.length} pattern(s) a second operator must weigh:
21746
+ ` + preview.riskFindings.map((f2) => ` - ${f2}
21747
+ `).join("") + "\nThese are advisory, not auto-rejections. A human decides.\n\n"
21748
+ );
21749
+ } else {
21750
+ process.stdout.write("No static risk patterns matched.\n\n");
21751
+ }
21752
+ const operator = flagValue2(args2, "--attest-operator");
21753
+ if (!operator) {
21754
+ process.stdout.write(
21755
+ `To attest (required before a hardened rule can be shared with your team):
21756
+ massu rule review ${candidateId} --destination ${destination} --draft '<text>' \\
21757
+ --attest-operator <your-id> --attest-note '<why this is safe>'
21758
+ `
21759
+ );
21760
+ return { exitCode: 0 };
21761
+ }
21762
+ try {
21763
+ const attestation = validateReviewAttestation({
21764
+ second_operator_id: operator,
21765
+ reviewed_at: (/* @__PURE__ */ new Date()).toISOString(),
21766
+ note: flagValue2(args2, "--attest-note") ?? ""
21767
+ });
21768
+ const sidecar = join22(getProjectRoot(), ".massu", "rule-candidates", `${candidateId}.json`);
21769
+ recordHardenedReviewAttestation(sidecar, attestation);
21770
+ process.stdout.write(`Attestation recorded by '${operator}'. This candidate may now be approved.
21771
+ `);
21772
+ return { exitCode: 0 };
21773
+ } catch (err) {
21774
+ process.stderr.write(
21775
+ `massu: attestation refused: ${err instanceof Error ? err.message : String(err)}
21776
+ `
21777
+ );
21778
+ return { exitCode: 1 };
21779
+ }
21780
+ }
21781
+ case "approve": {
21782
+ const candidateId = args2[1];
21783
+ if (!candidateId) {
21784
+ process.stderr.write(
21785
+ "Usage: massu rule approve <candidate_id> --destination <dest> --draft <text> [--slug <s>]\n"
21786
+ );
21787
+ return { exitCode: 2 };
21788
+ }
21789
+ const destination = flagValue2(args2, "--destination");
21790
+ const draftText = flagValue2(args2, "--draft");
21791
+ if (!destination || !isRuleDestination(destination)) {
21792
+ process.stderr.write(
21793
+ `massu: --destination must be one of: ${RULE_DESTINATIONS2.join(", ")}
21794
+ `
21795
+ );
21796
+ return { exitCode: 2 };
21797
+ }
21798
+ if (draftText === void 0) {
21799
+ process.stderr.write("massu: approve requires --draft <text>\n");
21800
+ return { exitCode: 2 };
21801
+ }
21802
+ const db = getMemoryDb();
21803
+ try {
21804
+ const result = await applyRuleCandidate(db, {
21805
+ candidateId,
21806
+ destination,
21807
+ draftText,
21808
+ ...flagValue2(args2, "--slug") ? { slug: flagValue2(args2, "--slug") } : {}
21809
+ });
21810
+ if (!result.ok) {
21811
+ process.stderr.write(`massu: promotion refused: ${result.error}
21812
+ `);
21813
+ return { exitCode: result.tier_refused ? 3 : 1 };
21814
+ }
21815
+ process.stdout.write(
21816
+ `Rule promoted to ${destination}.
21817
+ ` + (result.team_shared ? " Queued to share with your team. It will be delivered on the next sync \u2014 and it will NOT be deleted until your team's server confirms receipt.\n" : " Local only (team sharing needs a Team seat and a shareable destination).\n")
21818
+ );
21819
+ return { exitCode: 0 };
21820
+ } finally {
21821
+ db.close();
21822
+ }
21823
+ }
21824
+ case "dismiss": {
21825
+ const candidateId = args2[1];
21826
+ if (!candidateId) {
21827
+ process.stderr.write('Usage: massu rule dismiss <candidate_id> [--reason "..."]\n');
21828
+ return { exitCode: 2 };
21829
+ }
21830
+ const db = getMemoryDb();
21831
+ try {
21832
+ const result = dismissRuleCandidate(db, {
21833
+ candidateId,
21834
+ reason: flagValue2(args2, "--reason") ?? ""
21835
+ });
21836
+ if (!result.ok) {
21837
+ process.stderr.write(`massu: dismiss failed: ${result.error}
21838
+ `);
21839
+ return { exitCode: 1 };
21840
+ }
21841
+ process.stdout.write("Candidate dismissed.\n");
21842
+ return { exitCode: 0 };
21843
+ } finally {
21844
+ db.close();
21845
+ }
21846
+ }
21847
+ case "delivery-status": {
21848
+ const db = getMemoryDb();
21849
+ try {
21850
+ const snap = undeliveredSnapshot(db);
21851
+ const rules = snap.promotions + snap.revocations;
21852
+ const giveups = db.prepare(
21853
+ `SELECT COUNT(*) AS n, MAX(created_at) AS last
21854
+ FROM analytics_events WHERE event_type = 'cloud_sync_giveup'`
21855
+ ).get();
21856
+ const lastErr = db.prepare(
21857
+ `SELECT last_error FROM team_promotion_outbound
21858
+ WHERE last_error IS NOT NULL ORDER BY attempts DESC LIMIT 1`
21859
+ ).get();
21860
+ if (rules === 0 && snap.events === 0) {
21861
+ process.stdout.write("Rule delivery: everything has reached your team. Nothing queued.\n");
21862
+ } else {
21863
+ process.stdout.write(
21864
+ `Rule delivery: ${rules} learned rule(s) and ${snap.events} funnel event(s) have NOT yet reached your team.
21865
+ They are SAFE \u2014 nothing is ever deleted until the server confirms it.
21866
+ ` + (snap.oldest_rule_created_at ? ` Oldest waiting since: ${snap.oldest_rule_created_at}
21867
+ ` : "") + ` Delivery attempts so far: ${snap.max_rule_attempts}
21868
+ ` + (lastErr?.last_error ? ` Last error: ${lastErr.last_error}
21869
+ ` : "") + (snap.stalled ? ` STALLED \u2014 this is not retrying its way out. Check your API key and endpoint (\`massu doctor\`).
21870
+ ` : "")
21871
+ );
21872
+ }
21873
+ if (giveups.n > 0) {
21874
+ process.stdout.write(
21875
+ `
21876
+ Cloud sync has given up ${giveups.n} time(s) (most recent: ${giveups.last}).
21877
+ Session/observation data from those attempts was dropped; learned rules were not.
21878
+ `
21879
+ );
21880
+ }
21881
+ return { exitCode: snap.stalled ? 1 : 0 };
21882
+ } finally {
21883
+ db.close();
21884
+ }
21885
+ }
20369
21886
  case "--help":
20370
21887
  case "-h":
20371
21888
  case void 0: {
20372
21889
  process.stdout.write(
20373
- "massu rule <subcommand>\n\nSubcommands:\n record-shown <prompt_hash> Record a `shown` promotion-funnel event (Team-gated, best-effort).\n packs Pull installed rule-pack rules as reviewable candidates (Team-gated).\n"
21890
+ "massu rule <subcommand>\n\nSubcommands:\n approve <id> --destination <d> --draft <text>\n Promote a candidate to a rule (Pro+). Shares to your team at Team+.\n dismiss <id> [--reason ...] Dismiss a candidate and downweight its signals.\n review <id> --destination <d> --draft <text>\n Preview a HARDENED (executable) promotion + its risk findings;\n add --attest-operator/--attest-note to record a 2nd-operator review.\n record-shown <prompt_hash> Record a `shown` promotion-funnel event (Team-gated, best-effort).\n packs Pull installed rule-pack rules as reviewable candidates (Team-gated).\n delivery-status Show which learned rules have not yet reached your team, and why.\n"
20374
21891
  );
20375
21892
  return { exitCode: 0 };
20376
21893
  }
@@ -20381,14 +21898,24 @@ async function handleRuleSubcommand(args2) {
20381
21898
  }
20382
21899
  }
20383
21900
  }
20384
- var PROMPT_HASH_RE;
21901
+ var RULE_DESTINATIONS2, PROMPT_HASH_RE;
20385
21902
  var init_rule = __esm({
20386
21903
  "src/commands/rule.ts"() {
20387
21904
  "use strict";
20388
21905
  init_memory_db();
21906
+ init_config();
21907
+ init_rule_delivery();
21908
+ init_rule_candidate_applier();
21909
+ init_rule_candidate_preview();
20389
21910
  init_license();
20390
21911
  init_auto_learning_entitlement();
20391
21912
  init_rule_pack_sync();
21913
+ RULE_DESTINATIONS2 = [
21914
+ "corrections-md",
21915
+ "claude-md-cr",
21916
+ "pattern-scanner",
21917
+ "custom-destination"
21918
+ ];
20392
21919
  PROMPT_HASH_RE = /^[0-9a-f]{16}$/;
20393
21920
  }
20394
21921
  });
@@ -20882,9 +22409,9 @@ var init_memory_supersede = __esm({
20882
22409
  });
20883
22410
 
20884
22411
  // src/memory-consolidate.ts
20885
- import { createHash as createHash9 } from "crypto";
20886
- import { existsSync as existsSync33, mkdirSync as mkdirSync15, writeFileSync as writeFileSync10 } from "fs";
20887
- import { join as join21 } from "path";
22412
+ import { createHash as createHash10 } from "crypto";
22413
+ import { existsSync as existsSync35, mkdirSync as mkdirSync16, writeFileSync as writeFileSync12 } from "fs";
22414
+ import { join as join23 } from "path";
20888
22415
  function emptyResult() {
20889
22416
  return {
20890
22417
  deduped: 0,
@@ -21046,7 +22573,7 @@ ${r2.detail ?? ""}`.trim());
21046
22573
  }
21047
22574
  clusters.push(cluster);
21048
22575
  }
21049
- const candidateDir = join21(projectRoot, ".massu", "rule-candidates");
22576
+ const candidateDir = join23(projectRoot, ".massu", "rule-candidates");
21050
22577
  let promoted = 0;
21051
22578
  for (const cluster of clusters) {
21052
22579
  const occurrences = cluster.reduce((n, c2) => n + (c2.recurrence_count || 1), 0);
@@ -21058,15 +22585,15 @@ ${r2.detail ?? ""}`.trim());
21058
22585
  const promptText = `${representative.title}${representative.detail ? `
21059
22586
  ${representative.detail}` : ""}`;
21060
22587
  const clusterKey = cluster.map((c2) => c2.title.toLowerCase().replace(/\s+/g, " ").trim()).sort().join("|");
21061
- const promptHash = createHash9("sha256").update(clusterKey).digest("hex").slice(0, 16);
21062
- const candidatePath = join21(candidateDir, `${promptHash}.json`);
21063
- if (existsSync33(candidatePath)) continue;
22588
+ const promptHash = createHash10("sha256").update(clusterKey).digest("hex").slice(0, 16);
22589
+ const candidatePath = join23(candidateDir, `${promptHash}.json`);
22590
+ if (existsSync35(candidatePath)) continue;
21064
22591
  if (dryRun) {
21065
22592
  promoted++;
21066
22593
  continue;
21067
22594
  }
21068
- mkdirSync15(candidateDir, { recursive: true });
21069
- writeFileSync10(
22595
+ mkdirSync16(candidateDir, { recursive: true });
22596
+ writeFileSync12(
21070
22597
  candidatePath,
21071
22598
  JSON.stringify(
21072
22599
  {
@@ -21336,7 +22863,7 @@ var show_template_exports = {};
21336
22863
  __export(show_template_exports, {
21337
22864
  runShowTemplate: () => runShowTemplate
21338
22865
  });
21339
- import { existsSync as existsSync34, readFileSync as readFileSync26 } from "fs";
22866
+ import { existsSync as existsSync36, readFileSync as readFileSync28 } from "fs";
21340
22867
  import { resolve as resolve17 } from "path";
21341
22868
  function normalizeBaseName(input) {
21342
22869
  return input.endsWith(".md") ? input.slice(0, -".md".length) : input;
@@ -21366,13 +22893,13 @@ async function runShowTemplate(args2) {
21366
22893
  }
21367
22894
  const suffix = choice.kind === "hit" ? choice.suffix : "";
21368
22895
  const file = suffix === "" ? resolve17(sourceDir, `${baseName}.md`) : resolve17(sourceDir, `${baseName}${suffix}.md`);
21369
- if (!existsSync34(file)) {
22896
+ if (!existsSync36(file)) {
21370
22897
  process.stderr.write(`massu: resolved template "${file}" no longer exists
21371
22898
  `);
21372
22899
  process.exit(1);
21373
22900
  return;
21374
22901
  }
21375
- process.stdout.write(readFileSync26(file, "utf-8"));
22902
+ process.stdout.write(readFileSync28(file, "utf-8"));
21376
22903
  }
21377
22904
  var init_show_template = __esm({
21378
22905
  "src/commands/show-template.ts"() {
@@ -21467,7 +22994,7 @@ __export(config_refresh_exports, {
21467
22994
  mergeRefresh: () => mergeRefresh,
21468
22995
  runConfigRefresh: () => runConfigRefresh
21469
22996
  });
21470
- import { existsSync as existsSync35, readFileSync as readFileSync27, rmSync as rmSync8 } from "fs";
22997
+ import { existsSync as existsSync37, readFileSync as readFileSync29, rmSync as rmSync8 } from "fs";
21471
22998
  import { resolve as resolve19 } from "path";
21472
22999
  import { parse as parseYaml5 } from "yaml";
21473
23000
  function flatten(obj, prefix3 = "") {
@@ -21599,14 +23126,14 @@ async function runConfigRefresh(opts = {}) {
21599
23126
  const configPath = resolve19(cwd, "massu.config.yaml");
21600
23127
  const log = opts.silent ? () => {
21601
23128
  } : (s) => process.stdout.write(s);
21602
- if (!existsSync35(configPath)) {
23129
+ if (!existsSync37(configPath)) {
21603
23130
  const message = "massu.config.yaml not found. Run: npx massu init";
21604
23131
  if (!opts.silent) process.stderr.write(message + "\n");
21605
23132
  return { exitCode: 1, applied: false, dryRun: !!opts.dryRun, diff: [], message };
21606
23133
  }
21607
23134
  let existing;
21608
23135
  try {
21609
- const content = readFileSync27(configPath, "utf-8");
23136
+ const content = readFileSync29(configPath, "utf-8");
21610
23137
  const parsed = parseYaml5(content);
21611
23138
  if (!parsed || typeof parsed !== "object") {
21612
23139
  throw new Error("config is not a YAML object");
@@ -21680,7 +23207,7 @@ async function runConfigRefresh(opts = {}) {
21680
23207
  const stackResolved = installResult.totalInstalled > 0 || installResult.totalUpdated > 0;
21681
23208
  if (stackResolved) {
21682
23209
  const placeholderPath = resolve19(installResult.claudeDir, "commands", "_massu-needs-stack.md");
21683
- if (existsSync35(placeholderPath)) {
23210
+ if (existsSync37(placeholderPath)) {
21684
23211
  try {
21685
23212
  rmSync8(placeholderPath, { force: true });
21686
23213
  log("Removed _massu-needs-stack.md (stack now declared).\n");
@@ -21789,12 +23316,12 @@ var init_gitToplevel = __esm({
21789
23316
  });
21790
23317
 
21791
23318
  // src/watch/lockfile-detector.ts
21792
- import { existsSync as existsSync36, statSync as statSync14 } from "fs";
23319
+ import { existsSync as existsSync38, statSync as statSync14 } from "fs";
21793
23320
  import { resolve as resolve20 } from "path";
21794
23321
  function lockfileMidWrite(projectRoot, now = Date.now(), windowMs = LOCKFILE_WINDOW_MS) {
21795
23322
  for (const lf of KNOWN_LOCKFILES) {
21796
23323
  const p19 = resolve20(projectRoot, lf);
21797
- if (!existsSync36(p19)) continue;
23324
+ if (!existsSync38(p19)) continue;
21798
23325
  try {
21799
23326
  const stat = statSync14(p19);
21800
23327
  const delta = now - stat.mtimeMs;
@@ -21807,7 +23334,7 @@ function lockfileMidWrite(projectRoot, now = Date.now(), windowMs = LOCKFILE_WIN
21807
23334
  function gitMidOperation(projectRoot) {
21808
23335
  const sentinels = ["MERGE_HEAD", "REBASE_HEAD", "CHERRY_PICK_HEAD", "rebase-apply", "rebase-merge"];
21809
23336
  for (const s of sentinels) {
21810
- if (existsSync36(resolve20(projectRoot, ".git", s))) return true;
23337
+ if (existsSync38(resolve20(projectRoot, ".git", s))) return true;
21811
23338
  }
21812
23339
  return false;
21813
23340
  }
@@ -22006,8 +23533,8 @@ var init_paths = __esm({
22006
23533
  });
22007
23534
 
22008
23535
  // src/watch/state.ts
22009
- import { closeSync as closeSync4, existsSync as existsSync37, fsyncSync as fsyncSync4, mkdirSync as mkdirSync16, openSync as openSync4, readFileSync as readFileSync28, renameSync as renameSync5, rmSync as rmSync9, writeFileSync as writeFileSync11, writeSync as writeSync3 } from "fs";
22010
- import { dirname as dirname18, resolve as resolve21 } from "path";
23536
+ import { closeSync as closeSync4, existsSync as existsSync39, fsyncSync as fsyncSync4, mkdirSync as mkdirSync17, openSync as openSync4, readFileSync as readFileSync30, renameSync as renameSync5, rmSync as rmSync9, writeFileSync as writeFileSync13, writeSync as writeSync3 } from "fs";
23537
+ import { dirname as dirname19, resolve as resolve21 } from "path";
22011
23538
  function watchStatePath(projectRoot) {
22012
23539
  return resolve21(projectRoot, ".massu", "watch-state.json");
22013
23540
  }
@@ -22016,8 +23543,8 @@ function backupStatePath(projectRoot) {
22016
23543
  }
22017
23544
  function readState(projectRoot) {
22018
23545
  const path = watchStatePath(projectRoot);
22019
- if (!existsSync37(path)) return { ...DEFAULT_STATE };
22020
- const content = readFileSync28(path, "utf-8");
23546
+ if (!existsSync39(path)) return { ...DEFAULT_STATE };
23547
+ const content = readFileSync30(path, "utf-8");
22021
23548
  let raw;
22022
23549
  try {
22023
23550
  raw = JSON.parse(content);
@@ -22059,14 +23586,14 @@ function readState(projectRoot) {
22059
23586
  }
22060
23587
  function archiveCorrupt(projectRoot, content) {
22061
23588
  const bak = backupStatePath(projectRoot);
22062
- mkdirSync16(dirname18(bak), { recursive: true });
22063
- writeFileSync11(bak, content, "utf-8");
23589
+ mkdirSync17(dirname19(bak), { recursive: true });
23590
+ writeFileSync13(bak, content, "utf-8");
22064
23591
  }
22065
23592
  function writeStateAtomic(projectRoot, state) {
22066
23593
  const path = watchStatePath(projectRoot);
22067
23594
  writeStateAtomicCounter = writeStateAtomicCounter + 1 >>> 0;
22068
23595
  const tmp = `${path}.${process.pid}.${writeStateAtomicCounter}.tmp`;
22069
- mkdirSync16(dirname18(path), { recursive: true });
23596
+ mkdirSync17(dirname19(path), { recursive: true });
22070
23597
  let renamed = false;
22071
23598
  try {
22072
23599
  const fd = openSync4(tmp, "w");
@@ -22080,7 +23607,7 @@ function writeStateAtomic(projectRoot, state) {
22080
23607
  renameSync5(tmp, path);
22081
23608
  renamed = true;
22082
23609
  } finally {
22083
- if (!renamed && existsSync37(tmp)) {
23610
+ if (!renamed && existsSync39(tmp)) {
22084
23611
  try {
22085
23612
  rmSync9(tmp, { force: true });
22086
23613
  } catch {
@@ -22362,8 +23889,8 @@ __export(watch_exports, {
22362
23889
  runWatch: () => runWatch
22363
23890
  });
22364
23891
  import { spawnSync as spawnSync3 } from "child_process";
22365
- import { basename as basename8, dirname as dirname19, resolve as resolve22 } from "path";
22366
- import { appendFileSync as appendFileSync3, existsSync as existsSync38, mkdirSync as mkdirSync17, readFileSync as readFileSync29 } from "fs";
23892
+ import { basename as basename8, dirname as dirname20, resolve as resolve22 } from "path";
23893
+ import { appendFileSync as appendFileSync3, existsSync as existsSync40, mkdirSync as mkdirSync18, readFileSync as readFileSync31 } from "fs";
22367
23894
  function parseFlags(args2) {
22368
23895
  const out = {
22369
23896
  foreground: false,
@@ -22389,7 +23916,7 @@ function parseFlags(args2) {
22389
23916
  function findClaudeBg() {
22390
23917
  const home = process.env.HOME ?? "";
22391
23918
  const fixed = home ? resolve22(home, ".claude", "bin", "claude-bg") : null;
22392
- if (fixed && existsSync38(fixed)) return fixed;
23919
+ if (fixed && existsSync40(fixed)) return fixed;
22393
23920
  const which = spawnSync3("which", ["claude-bg"], { encoding: "utf-8" });
22394
23921
  if (which.status === 0 && which.stdout) {
22395
23922
  const p19 = which.stdout.trim();
@@ -22450,7 +23977,7 @@ async function runWatch(args2) {
22450
23977
  }
22451
23978
  function runStatus(root) {
22452
23979
  const path = watchStatePath(root);
22453
- if (!existsSync38(path)) {
23980
+ if (!existsSync40(path)) {
22454
23981
  process.stdout.write("massu watch: not running (no state file)\n");
22455
23982
  return { exitCode: 0 };
22456
23983
  }
@@ -22608,18 +24135,18 @@ function refreshLogPath(projectRoot) {
22608
24135
  function appendRefreshLog(projectRoot, event) {
22609
24136
  const path = refreshLogPath(projectRoot);
22610
24137
  try {
22611
- mkdirSync17(dirname19(path), { recursive: true });
24138
+ mkdirSync18(dirname20(path), { recursive: true });
22612
24139
  appendFileSync3(path, JSON.stringify(event) + "\n", "utf-8");
22613
24140
  } catch {
22614
24141
  }
22615
24142
  }
22616
24143
  function readRefreshLog(projectRoot, limit = 10, opts = {}) {
22617
24144
  const path = refreshLogPath(projectRoot);
22618
- if (!existsSync38(path)) return [];
24145
+ if (!existsSync40(path)) return [];
22619
24146
  const warn = opts.warn ?? ((s) => {
22620
24147
  process.stderr.write(s);
22621
24148
  });
22622
- const lines = readFileSync29(path, "utf-8").split("\n").filter(Boolean);
24149
+ const lines = readFileSync31(path, "utf-8").split("\n").filter(Boolean);
22623
24150
  const tail = lines.slice(-limit);
22624
24151
  const out = [];
22625
24152
  let corrupt = 0;
@@ -22692,26 +24219,26 @@ var init_refresh_log = __esm({
22692
24219
  import {
22693
24220
  chmodSync as chmodSync6,
22694
24221
  closeSync as closeSync5,
22695
- existsSync as existsSync39,
24222
+ existsSync as existsSync41,
22696
24223
  fsyncSync as fsyncSync5,
22697
- mkdirSync as mkdirSync18,
24224
+ mkdirSync as mkdirSync19,
22698
24225
  openSync as openSync5,
22699
24226
  renameSync as renameSync6,
22700
24227
  rmSync as rmSync10,
22701
24228
  statSync as statSync15,
22702
24229
  writeSync as writeSync4
22703
24230
  } from "node:fs";
22704
- import { dirname as dirname20 } from "node:path";
24231
+ import { dirname as dirname21 } from "node:path";
22705
24232
  function atomicWrite(path, content, opts = {}) {
22706
24233
  const tmpPath = `${path}.tmp`;
22707
- const parentDir = dirname20(path);
24234
+ const parentDir = dirname21(path);
22708
24235
  try {
22709
- if (!existsSync39(parentDir)) {
24236
+ if (!existsSync41(parentDir)) {
22710
24237
  const mkdirOpts = { recursive: true };
22711
24238
  if (opts.ensureParentDirMode !== void 0) {
22712
24239
  mkdirOpts.mode = opts.ensureParentDirMode;
22713
24240
  }
22714
- mkdirSync18(parentDir, mkdirOpts);
24241
+ mkdirSync19(parentDir, mkdirOpts);
22715
24242
  }
22716
24243
  const buf = typeof content === "string" ? Buffer.from(content, "utf-8") : content;
22717
24244
  const openMode = opts.mode ?? 420;
@@ -22728,7 +24255,7 @@ function atomicWrite(path, content, opts = {}) {
22728
24255
  renameSync6(tmpPath, path);
22729
24256
  return { written: true };
22730
24257
  } catch (err) {
22731
- if (existsSync39(tmpPath)) {
24258
+ if (existsSync41(tmpPath)) {
22732
24259
  try {
22733
24260
  rmSync10(tmpPath, { force: true });
22734
24261
  } catch {
@@ -22808,10 +24335,10 @@ var init_registry_pubkey_generated = __esm({
22808
24335
  });
22809
24336
 
22810
24337
  // src/security/adapter-verifier.ts
22811
- import { createHash as createHash10 } from "node:crypto";
24338
+ import { createHash as createHash11 } from "node:crypto";
22812
24339
  import nacl from "tweetnacl";
22813
24340
  function sha256Hex2(bytes) {
22814
- return createHash10("sha256").update(bytes).digest("hex");
24341
+ return createHash11("sha256").update(bytes).digest("hex");
22815
24342
  }
22816
24343
  function reviver(key, value) {
22817
24344
  if (key === "__proto__" || key === "constructor" || key === "prototype") {
@@ -23077,24 +24604,24 @@ var init_fetcher = __esm({
23077
24604
  });
23078
24605
 
23079
24606
  // src/security/manifest-cache.ts
23080
- import { existsSync as existsSync40, readFileSync as readFileSync30, statSync as statSync16 } from "node:fs";
23081
- import { homedir as homedir12 } from "node:os";
24607
+ import { existsSync as existsSync42, readFileSync as readFileSync32, statSync as statSync16 } from "node:fs";
24608
+ import { homedir as homedir13 } from "node:os";
23082
24609
  import { resolve as resolve23 } from "node:path";
23083
24610
  import { z as z5 } from "zod";
23084
24611
  function defaultCachePaths() {
23085
- const dir = resolve23(homedir12(), ".massu");
24612
+ const dir = resolve23(homedir13(), ".massu");
23086
24613
  return {
23087
24614
  cachePath: resolve23(dir, "adapter-manifest.json"),
23088
24615
  lockPath: resolve23(dir, ".adapter-manifest.lock")
23089
24616
  };
23090
24617
  }
23091
24618
  function loadCachedManifest(paths = defaultCachePaths()) {
23092
- if (!existsSync40(paths.cachePath)) {
24619
+ if (!existsSync42(paths.cachePath)) {
23093
24620
  return { kind: "absent" };
23094
24621
  }
23095
24622
  let raw;
23096
24623
  try {
23097
- const content = readFileSync30(paths.cachePath, "utf-8");
24624
+ const content = readFileSync32(paths.cachePath, "utf-8");
23098
24625
  raw = JSON.parse(content);
23099
24626
  } catch (err) {
23100
24627
  return {
@@ -23283,15 +24810,15 @@ var init_adapter_origin = __esm({
23283
24810
  });
23284
24811
 
23285
24812
  // src/security/local-fingerprint.ts
23286
- import { existsSync as existsSync41, readFileSync as readFileSync31, lstatSync as lstatSync5 } from "node:fs";
23287
- import { homedir as homedir13 } from "node:os";
23288
- import { resolve as resolve24, isAbsolute as isAbsolute2 } from "node:path";
23289
- import { createHash as createHash11 } from "node:crypto";
24813
+ import { existsSync as existsSync43, readFileSync as readFileSync33, lstatSync as lstatSync5 } from "node:fs";
24814
+ import { homedir as homedir14 } from "node:os";
24815
+ import { resolve as resolve24, isAbsolute as isAbsolute3 } from "node:path";
24816
+ import { createHash as createHash12 } from "node:crypto";
23290
24817
  import { z as z6 } from "zod";
23291
24818
  function computeLocalFingerprint(localPaths, projectRoot) {
23292
24819
  const tuples = [];
23293
24820
  for (const p19 of localPaths) {
23294
- const abs = isAbsolute2(p19) ? p19 : resolve24(projectRoot, p19);
24821
+ const abs = isAbsolute3(p19) ? p19 : resolve24(projectRoot, p19);
23295
24822
  let contentTag;
23296
24823
  try {
23297
24824
  const lst = lstatSync5(abs);
@@ -23300,7 +24827,7 @@ function computeLocalFingerprint(localPaths, projectRoot) {
23300
24827
  } else if (!lst.isFile()) {
23301
24828
  contentTag = "<not-a-file>";
23302
24829
  } else {
23303
- contentTag = createHash11("sha256").update(readFileSync31(abs)).digest("hex");
24830
+ contentTag = createHash12("sha256").update(readFileSync33(abs)).digest("hex");
23304
24831
  }
23305
24832
  } catch {
23306
24833
  contentTag = "<missing>";
@@ -23309,13 +24836,13 @@ function computeLocalFingerprint(localPaths, projectRoot) {
23309
24836
  }
23310
24837
  tuples.sort((a2, b2) => a2.path < b2.path ? -1 : a2.path > b2.path ? 1 : 0);
23311
24838
  const canonical = JSON.stringify(tuples);
23312
- return createHash11("sha256").update(canonical).digest("hex");
24839
+ return createHash12("sha256").update(canonical).digest("hex");
23313
24840
  }
23314
24841
  function readFingerprintSentinel(path = FINGERPRINT_PATH) {
23315
- if (!existsSync41(path)) return null;
24842
+ if (!existsSync43(path)) return null;
23316
24843
  let raw;
23317
24844
  try {
23318
- raw = JSON.parse(readFileSync31(path, "utf-8"));
24845
+ raw = JSON.parse(readFileSync33(path, "utf-8"));
23319
24846
  } catch {
23320
24847
  return null;
23321
24848
  }
@@ -23363,7 +24890,7 @@ var init_local_fingerprint = __esm({
23363
24890
  "use strict";
23364
24891
  init_atomic_write();
23365
24892
  init_manifest_schema();
23366
- FINGERPRINT_PATH = resolve24(homedir13(), ".massu", "adapters-local-fingerprint.json");
24893
+ FINGERPRINT_PATH = resolve24(homedir14(), ".massu", "adapters-local-fingerprint.json");
23367
24894
  FingerprintSentinelSchema = z6.object({
23368
24895
  fingerprint: z6.string().regex(/^[0-9a-f]{64}$/),
23369
24896
  source: z6.enum(["cli", "cli-resync"]),
@@ -23379,15 +24906,15 @@ var init_local_fingerprint = __esm({
23379
24906
  });
23380
24907
 
23381
24908
  // src/security/install-tracking.ts
23382
- import { readFileSync as readFileSync32, readdirSync as readdirSync16, lstatSync as lstatSync6, existsSync as existsSync42 } from "node:fs";
23383
- import { join as join22, relative as relative6, sep as sep2 } from "node:path";
23384
- import { homedir as homedir14 } from "node:os";
24909
+ import { readFileSync as readFileSync34, readdirSync as readdirSync16, lstatSync as lstatSync6, existsSync as existsSync44 } from "node:fs";
24910
+ import { join as join24, relative as relative7, sep as sep2 } from "node:path";
24911
+ import { homedir as homedir15 } from "node:os";
23385
24912
  import { resolve as resolve25 } from "node:path";
23386
- import { createHash as createHash12 } from "node:crypto";
24913
+ import { createHash as createHash13 } from "node:crypto";
23387
24914
  import { z as z7 } from "zod";
23388
24915
  function containsHiddenDirs(packageDir) {
23389
24916
  for (const hidden of EXCLUDED_DIR_NAMES) {
23390
- if (existsSync42(`${packageDir}/${hidden}`)) {
24917
+ if (existsSync44(`${packageDir}/${hidden}`)) {
23391
24918
  return hidden;
23392
24919
  }
23393
24920
  }
@@ -23404,7 +24931,7 @@ function sha256OfDir(dir, opts = {}) {
23404
24931
  return;
23405
24932
  }
23406
24933
  for (const entry of entries.sort()) {
23407
- const absPath = join22(currentDir, entry);
24934
+ const absPath = join24(currentDir, entry);
23408
24935
  let lst;
23409
24936
  try {
23410
24937
  lst = lstatSync6(absPath);
@@ -23425,15 +24952,15 @@ function sha256OfDir(dir, opts = {}) {
23425
24952
  `sha256OfDir: file ${absPath} exceeds maxFileBytes (${lst.size} > ${maxFileBytes}); adapter packages should not ship files this large.`
23426
24953
  );
23427
24954
  }
23428
- const rel = relative6(dir, absPath).split(sep2).join("/");
24955
+ const rel = relative7(dir, absPath).split(sep2).join("/");
23429
24956
  files.push({ relativePath: rel, absPath });
23430
24957
  }
23431
24958
  }
23432
24959
  walk(dir);
23433
24960
  files.sort((a2, b2) => a2.relativePath < b2.relativePath ? -1 : a2.relativePath > b2.relativePath ? 1 : 0);
23434
- const top = createHash12("sha256");
24961
+ const top = createHash13("sha256");
23435
24962
  for (const f2 of files) {
23436
- const fileHash = createHash12("sha256").update(readFileSync32(f2.absPath)).digest("hex");
24963
+ const fileHash = createHash13("sha256").update(readFileSync34(f2.absPath)).digest("hex");
23437
24964
  top.update(f2.relativePath, "utf-8");
23438
24965
  top.update("\0", "utf-8");
23439
24966
  top.update(fileHash, "utf-8");
@@ -23442,10 +24969,10 @@ function sha256OfDir(dir, opts = {}) {
23442
24969
  return top.digest("hex");
23443
24970
  }
23444
24971
  function readInstalledManifest(path = INSTALLED_MANIFEST_PATH) {
23445
- if (!existsSync42(path)) return {};
24972
+ if (!existsSync44(path)) return {};
23446
24973
  let raw;
23447
24974
  try {
23448
- raw = JSON.parse(readFileSync32(path, "utf-8"));
24975
+ raw = JSON.parse(readFileSync34(path, "utf-8"));
23449
24976
  } catch {
23450
24977
  return {};
23451
24978
  }
@@ -23509,7 +25036,7 @@ var init_install_tracking = __esm({
23509
25036
  "src/security/install-tracking.ts"() {
23510
25037
  "use strict";
23511
25038
  init_atomic_write();
23512
- INSTALLED_MANIFEST_PATH = resolve25(homedir14(), ".massu", "adapter-manifest-installed.json");
25039
+ INSTALLED_MANIFEST_PATH = resolve25(homedir15(), ".massu", "adapter-manifest-installed.json");
23513
25040
  DEFAULT_MAX_FILE_BYTES = 64 * 1024 * 1024;
23514
25041
  EXCLUDED_DIR_NAMES = /* @__PURE__ */ new Set([".git", "node_modules", ".cache", ".tmp"]);
23515
25042
  InstallEntrySchema = z7.object({
@@ -23532,12 +25059,12 @@ var init_install_tracking = __esm({
23532
25059
  });
23533
25060
 
23534
25061
  // src/detect/adapters/discover.ts
23535
- import { existsSync as existsSync43, readdirSync as readdirSync17, readFileSync as readFileSync33, lstatSync as lstatSync7 } from "node:fs";
23536
- import { resolve as resolve26, isAbsolute as isAbsolute3 } from "node:path";
25062
+ import { existsSync as existsSync45, readdirSync as readdirSync17, readFileSync as readFileSync35, lstatSync as lstatSync7 } from "node:fs";
25063
+ import { resolve as resolve26, isAbsolute as isAbsolute4 } from "node:path";
23537
25064
  import { z as z8 } from "zod";
23538
25065
  function walkNodeModules(projectRoot, warnings) {
23539
25066
  const nodeModulesDir = resolve26(projectRoot, "node_modules");
23540
- if (!existsSync43(nodeModulesDir)) {
25067
+ if (!existsSync45(nodeModulesDir)) {
23541
25068
  return [];
23542
25069
  }
23543
25070
  const candidates = [];
@@ -23592,10 +25119,10 @@ function walkNodeModules(projectRoot, warnings) {
23592
25119
  }
23593
25120
  function tryReadAdapterPackage(packageDir, warnings) {
23594
25121
  const pkgJsonPath = resolve26(packageDir, "package.json");
23595
- if (!existsSync43(pkgJsonPath)) return null;
25122
+ if (!existsSync45(pkgJsonPath)) return null;
23596
25123
  let raw;
23597
25124
  try {
23598
- raw = JSON.parse(readFileSync33(pkgJsonPath, "utf-8"));
25125
+ raw = JSON.parse(readFileSync35(pkgJsonPath, "utf-8"));
23599
25126
  } catch (err) {
23600
25127
  warnings.push(
23601
25128
  `skipping ${packageDir}: package.json parse failed (${err instanceof Error ? err.message : String(err)})`
@@ -23729,8 +25256,8 @@ function discoverAdapters(opts) {
23729
25256
  const localSet = new Set(opts.configLocalPaths);
23730
25257
  for (const localPath of opts.configLocalPaths) {
23731
25258
  if (seenIds.has(localPath)) continue;
23732
- const absPath = isAbsolute3(localPath) ? localPath : resolve26(opts.projectRoot, localPath);
23733
- if (!existsSync43(absPath)) {
25259
+ const absPath = isAbsolute4(localPath) ? localPath : resolve26(opts.projectRoot, localPath);
25260
+ if (!existsSync45(absPath)) {
23734
25261
  warnings.push(
23735
25262
  `local adapter file not found: ${localPath} (resolved to ${absPath}). Remove via: massu adapters remove-local ${localPath}`
23736
25263
  );
@@ -23812,7 +25339,7 @@ __export(adapters_exports, {
23812
25339
  runAdaptersResyncLocalFingerprint: () => runAdaptersResyncLocalFingerprint,
23813
25340
  runAdaptersSearch: () => runAdaptersSearch
23814
25341
  });
23815
- import { existsSync as existsSync44, readFileSync as readFileSync34 } from "node:fs";
25342
+ import { existsSync as existsSync46, readFileSync as readFileSync36 } from "node:fs";
23816
25343
  import { resolve as resolve27 } from "node:path";
23817
25344
  import { parseDocument } from "yaml";
23818
25345
  async function handleAdaptersSubcommand(args2) {
@@ -24081,7 +25608,7 @@ function mutateLocalArray(mutator, command) {
24081
25608
  return { exitCode: 2 };
24082
25609
  }
24083
25610
  const yamlPath = resolve27(projectRoot, "massu.config.yaml");
24084
- if (!existsSync44(yamlPath)) {
25611
+ if (!existsSync46(yamlPath)) {
24085
25612
  process.stderr.write(
24086
25613
  `${command}: massu.config.yaml not found at ${yamlPath}. Run \`massu init\` first.
24087
25614
  `
@@ -24090,7 +25617,7 @@ function mutateLocalArray(mutator, command) {
24090
25617
  }
24091
25618
  let yamlText;
24092
25619
  try {
24093
- yamlText = readFileSync34(yamlPath, "utf-8");
25620
+ yamlText = readFileSync36(yamlPath, "utf-8");
24094
25621
  } catch (err) {
24095
25622
  process.stderr.write(`${command}: failed to read ${yamlPath}: ${err instanceof Error ? err.message : String(err)}
24096
25623
  `);
@@ -24181,7 +25708,7 @@ async function runAdaptersInstall(args2) {
24181
25708
  return { exitCode: 1 };
24182
25709
  }
24183
25710
  const packageDir = resolve27(projectRoot, "node_modules", ...packageName.split("/"));
24184
- if (!existsSync44(packageDir)) {
25711
+ if (!existsSync46(packageDir)) {
24185
25712
  process.stderr.write(
24186
25713
  `install: ${packageName} is not installed in node_modules. Run \`npm install ${packageName}\` first.
24187
25714
  `
@@ -24189,14 +25716,14 @@ async function runAdaptersInstall(args2) {
24189
25716
  return { exitCode: 1 };
24190
25717
  }
24191
25718
  const pkgJsonPath = resolve27(packageDir, "package.json");
24192
- if (!existsSync44(pkgJsonPath)) {
25719
+ if (!existsSync46(pkgJsonPath)) {
24193
25720
  process.stderr.write(`install: ${packageName} has no package.json at ${pkgJsonPath}
24194
25721
  `);
24195
25722
  return { exitCode: 1 };
24196
25723
  }
24197
25724
  let pkgJson;
24198
25725
  try {
24199
- pkgJson = JSON.parse(readFileSync34(pkgJsonPath, "utf-8"));
25726
+ pkgJson = JSON.parse(readFileSync36(pkgJsonPath, "utf-8"));
24200
25727
  } catch (err) {
24201
25728
  process.stderr.write(`install: ${packageName} has malformed package.json: ${err instanceof Error ? err.message : String(err)}
24202
25729
  `);
@@ -24316,7 +25843,7 @@ async function runAdaptersResign(_args) {
24316
25843
  continue;
24317
25844
  }
24318
25845
  const packageDir = resolve27(projectRoot, "node_modules", ...name.split("/"));
24319
- if (!existsSync44(packageDir)) {
25846
+ if (!existsSync46(packageDir)) {
24320
25847
  removed++;
24321
25848
  warnings.push(`${name}@${entry.version}: not present in node_modules \u2014 REMOVED from sidecar`);
24322
25849
  removeInstalledManifestEntry(name);
@@ -24403,10 +25930,10 @@ var init_adapters2 = __esm({
24403
25930
 
24404
25931
  // src/preflight.ts
24405
25932
  import Database4 from "better-sqlite3";
24406
- import { existsSync as existsSync45 } from "fs";
25933
+ import { existsSync as existsSync47 } from "fs";
24407
25934
  function assertCodegraphUsable(dbPath) {
24408
25935
  const path = dbPath ?? getResolvedPaths().codegraphDbPath;
24409
- if (!existsSync45(path)) {
25936
+ if (!existsSync47(path)) {
24410
25937
  throw new CodegraphDbUnusableError(path, "missing");
24411
25938
  }
24412
25939
  let db = null;
@@ -24495,8 +26022,8 @@ var init_sql_table_names = __esm({
24495
26022
 
24496
26023
  // src/db.ts
24497
26024
  import Database5 from "better-sqlite3";
24498
- import { dirname as dirname21, join as join23 } from "path";
24499
- import { existsSync as existsSync46, mkdirSync as mkdirSync19, readdirSync as readdirSync18, statSync as statSync17 } from "fs";
26025
+ import { dirname as dirname22, join as join25 } from "path";
26026
+ import { existsSync as existsSync48, mkdirSync as mkdirSync20, readdirSync as readdirSync18, statSync as statSync17 } from "fs";
24500
26027
  function getCodeGraphDb() {
24501
26028
  const dbPath = getResolvedPaths().codegraphDbPath;
24502
26029
  assertCodegraphUsable(dbPath);
@@ -24506,9 +26033,9 @@ function getCodeGraphDb() {
24506
26033
  }
24507
26034
  function getDataDb() {
24508
26035
  const dbPath = getResolvedPaths().dataDbPath;
24509
- const dir = dirname21(dbPath);
24510
- if (!existsSync46(dir)) {
24511
- mkdirSync19(dir, { recursive: true });
26036
+ const dir = dirname22(dbPath);
26037
+ if (!existsSync48(dir)) {
26038
+ mkdirSync20(dir, { recursive: true });
24512
26039
  }
24513
26040
  const db = new Database5(dbPath);
24514
26041
  db.pragma("journal_mode = WAL");
@@ -24780,7 +26307,7 @@ function isPythonDataStale(dataDb, pythonRoot) {
24780
26307
  try {
24781
26308
  const entries = readdirSync18(dir, { withFileTypes: true });
24782
26309
  for (const entry of entries) {
24783
- const fullPath = join23(dir, entry.name);
26310
+ const fullPath = join25(dir, entry.name);
24784
26311
  if (entry.isDirectory()) {
24785
26312
  if (["__pycache__", ".venv", "venv", "node_modules", ".mypy_cache", ".pytest_cache"].includes(entry.name)) continue;
24786
26313
  if (checkDir(fullPath)) return true;
@@ -24884,8 +26411,8 @@ var init_rules = __esm({
24884
26411
  });
24885
26412
 
24886
26413
  // src/import-resolver.ts
24887
- import { readFileSync as readFileSync35, existsSync as existsSync47, statSync as statSync18 } from "fs";
24888
- import { resolve as resolve29, dirname as dirname22, join as join24 } from "path";
26414
+ import { readFileSync as readFileSync37, existsSync as existsSync49, statSync as statSync18 } from "fs";
26415
+ import { resolve as resolve29, dirname as dirname23, join as join26 } from "path";
24889
26416
  function parseImports(source) {
24890
26417
  const imports = [];
24891
26418
  const lines = source.split("\n");
@@ -24943,21 +26470,21 @@ function resolveImportPath(specifier, fromFile) {
24943
26470
  const paths = getResolvedPaths();
24944
26471
  basePath = resolve29(paths.pathAlias["@"] ?? paths.srcDir, specifier.slice(2));
24945
26472
  } else {
24946
- basePath = resolve29(dirname22(fromFile), specifier);
26473
+ basePath = resolve29(dirname23(fromFile), specifier);
24947
26474
  }
24948
- if (existsSync47(basePath) && !isDirectory(basePath)) {
26475
+ if (existsSync49(basePath) && !isDirectory(basePath)) {
24949
26476
  return toRelative(basePath);
24950
26477
  }
24951
26478
  const resolvedPaths = getResolvedPaths();
24952
26479
  for (const ext of resolvedPaths.extensions) {
24953
26480
  const withExt = basePath + ext;
24954
- if (existsSync47(withExt)) {
26481
+ if (existsSync49(withExt)) {
24955
26482
  return toRelative(withExt);
24956
26483
  }
24957
26484
  }
24958
26485
  for (const indexFile of resolvedPaths.indexFiles) {
24959
- const indexPath = join24(basePath, indexFile);
24960
- if (existsSync47(indexPath)) {
26486
+ const indexPath = join26(basePath, indexFile);
26487
+ if (existsSync49(indexPath)) {
24961
26488
  return toRelative(indexPath);
24962
26489
  }
24963
26490
  }
@@ -24994,10 +26521,10 @@ function buildImportIndex(dataDb, codegraphDb) {
24994
26521
  let batch = [];
24995
26522
  for (const file of files) {
24996
26523
  const absPath = ensureWithinRoot(resolve29(projectRoot, file.path), projectRoot);
24997
- if (!existsSync47(absPath)) continue;
26524
+ if (!existsSync49(absPath)) continue;
24998
26525
  let source;
24999
26526
  try {
25000
- source = readFileSync35(absPath, "utf-8");
26527
+ source = readFileSync37(absPath, "utf-8");
25001
26528
  } catch {
25002
26529
  continue;
25003
26530
  }
@@ -25034,15 +26561,15 @@ var init_import_resolver = __esm({
25034
26561
  });
25035
26562
 
25036
26563
  // src/trpc-index.ts
25037
- import { readFileSync as readFileSync36, existsSync as existsSync48, readdirSync as readdirSync19 } from "fs";
25038
- import { resolve as resolve30, join as join25 } from "path";
26564
+ import { readFileSync as readFileSync38, existsSync as existsSync50, readdirSync as readdirSync19 } from "fs";
26565
+ import { resolve as resolve30, join as join27 } from "path";
25039
26566
  function parseRootRouter() {
25040
26567
  const paths = getResolvedPaths();
25041
26568
  const rootPath = paths.rootRouterPath;
25042
- if (!existsSync48(rootPath)) {
26569
+ if (!existsSync50(rootPath)) {
25043
26570
  throw new Error(`Root router not found at ${rootPath}`);
25044
26571
  }
25045
- const source = readFileSync36(rootPath, "utf-8");
26572
+ const source = readFileSync38(rootPath, "utf-8");
25046
26573
  const mappings = [];
25047
26574
  const importMap = /* @__PURE__ */ new Map();
25048
26575
  const importRegex = /import\s+\{[^}]*?(\w+Router)[^}]*\}\s+from\s+['"]\.\/routers\/([^'"]+)['"]/g;
@@ -25054,12 +26581,12 @@ function parseRootRouter() {
25054
26581
  for (const ext of [".ts", ".tsx", ""]) {
25055
26582
  const candidate = fullPath + ext;
25056
26583
  const routersRelPath = getConfig().paths.routers ?? "src/server/api/routers";
25057
- if (existsSync48(candidate)) {
26584
+ if (existsSync50(candidate)) {
25058
26585
  filePath = routersRelPath + "/" + filePath + ext;
25059
26586
  break;
25060
26587
  }
25061
- const indexCandidate = join25(fullPath, "index.ts");
25062
- if (existsSync48(indexCandidate)) {
26588
+ const indexCandidate = join27(fullPath, "index.ts");
26589
+ if (existsSync50(indexCandidate)) {
25063
26590
  filePath = routersRelPath + "/" + filePath + "/index.ts";
25064
26591
  break;
25065
26592
  }
@@ -25079,8 +26606,8 @@ function parseRootRouter() {
25079
26606
  }
25080
26607
  function extractProcedures(routerFilePath) {
25081
26608
  const absPath = resolve30(getProjectRoot(), routerFilePath);
25082
- if (!existsSync48(absPath)) return [];
25083
- const source = readFileSync36(absPath, "utf-8");
26609
+ if (!existsSync50(absPath)) return [];
26610
+ const source = readFileSync38(absPath, "utf-8");
25084
26611
  const procedures = [];
25085
26612
  const seen = /* @__PURE__ */ new Set();
25086
26613
  const procRegex = /(\w+)\s*:\s*(protected|public)Procedure/g;
@@ -25109,7 +26636,7 @@ function findUICallSites(routerKey, procedureName) {
25109
26636
  ];
25110
26637
  const searchPattern = `api.${routerKey}.${procedureName}`;
25111
26638
  for (const dir of searchDirs) {
25112
- if (!existsSync48(dir)) continue;
26639
+ if (!existsSync50(dir)) continue;
25113
26640
  searchDirectory(dir, searchPattern, callSites);
25114
26641
  }
25115
26642
  return callSites;
@@ -25117,13 +26644,13 @@ function findUICallSites(routerKey, procedureName) {
25117
26644
  function searchDirectory(dir, pattern, results) {
25118
26645
  const entries = readdirSync19(dir, { withFileTypes: true });
25119
26646
  for (const entry of entries) {
25120
- const fullPath = join25(dir, entry.name);
26647
+ const fullPath = join27(dir, entry.name);
25121
26648
  if (entry.isDirectory()) {
25122
26649
  if (entry.name === "node_modules" || entry.name === ".next") continue;
25123
26650
  searchDirectory(fullPath, pattern, results);
25124
26651
  } else if (entry.name.endsWith(".ts") || entry.name.endsWith(".tsx")) {
25125
26652
  try {
25126
- const source = readFileSync36(fullPath, "utf-8");
26653
+ const source = readFileSync38(fullPath, "utf-8");
25127
26654
  const lines = source.split("\n");
25128
26655
  for (let i = 0; i < lines.length; i++) {
25129
26656
  if (lines[i].includes(pattern)) {
@@ -25187,7 +26714,7 @@ var init_trpc_index = __esm({
25187
26714
  });
25188
26715
 
25189
26716
  // src/page-deps.ts
25190
- import { readFileSync as readFileSync37, existsSync as existsSync49 } from "fs";
26717
+ import { readFileSync as readFileSync39, existsSync as existsSync51 } from "fs";
25191
26718
  import { resolve as resolve31 } from "path";
25192
26719
  function deriveRoute(pageFile) {
25193
26720
  let route = pageFile.replace(/^src\/app/, "").replace(/\/page\.tsx?$/, "").replace(/\/page\.jsx?$/, "");
@@ -25227,9 +26754,9 @@ function findRouterCalls(files) {
25227
26754
  const projectRoot = getProjectRoot();
25228
26755
  for (const file of files) {
25229
26756
  const absPath = ensureWithinRoot(resolve31(projectRoot, file), projectRoot);
25230
- if (!existsSync49(absPath)) continue;
26757
+ if (!existsSync51(absPath)) continue;
25231
26758
  try {
25232
- const source = readFileSync37(absPath, "utf-8");
26759
+ const source = readFileSync39(absPath, "utf-8");
25233
26760
  const apiCallRegex = /api\.(\w+)\.\w+/g;
25234
26761
  let match;
25235
26762
  while ((match = apiCallRegex.exec(source)) !== null) {
@@ -25248,9 +26775,9 @@ function findTablesFromRouters(routerNames, dataDb) {
25248
26775
  ).all(routerName);
25249
26776
  for (const proc of procs) {
25250
26777
  const absPath = ensureWithinRoot(resolve31(getProjectRoot(), proc.router_file), getProjectRoot());
25251
- if (!existsSync49(absPath)) continue;
26778
+ if (!existsSync51(absPath)) continue;
25252
26779
  try {
25253
- const source = readFileSync37(absPath, "utf-8");
26780
+ const source = readFileSync39(absPath, "utf-8");
25254
26781
  const dbPattern = getConfig().dbAccessPattern ?? "ctx.db.{table}";
25255
26782
  const regexStr = dbPattern.replace(/[.*+?^${}()|[\]\\]/g, "\\$&").replace("\\{table\\}", "(\\w+)");
25256
26783
  const tableRegex = new RegExp(regexStr + "\\.", "g");
@@ -25522,14 +27049,14 @@ var init_domains = __esm({
25522
27049
  });
25523
27050
 
25524
27051
  // src/schema-mapper.ts
25525
- import { readFileSync as readFileSync38, existsSync as existsSync50, readdirSync as readdirSync20 } from "fs";
25526
- import { join as join26 } from "path";
27052
+ import { readFileSync as readFileSync40, existsSync as existsSync52, readdirSync as readdirSync20 } from "fs";
27053
+ import { join as join28 } from "path";
25527
27054
  function parsePrismaSchema() {
25528
27055
  const schemaPath = getResolvedPaths().prismaSchemaPath;
25529
- if (!existsSync50(schemaPath)) {
27056
+ if (!existsSync52(schemaPath)) {
25530
27057
  throw new Error(`Prisma schema not found at ${schemaPath}`);
25531
27058
  }
25532
- const source = readFileSync38(schemaPath, "utf-8");
27059
+ const source = readFileSync40(schemaPath, "utf-8");
25533
27060
  const models = [];
25534
27061
  const sourceLines = source.split("\n");
25535
27062
  let i = 0;
@@ -25587,14 +27114,14 @@ function toSnakeCase(str) {
25587
27114
  function findColumnUsageInRouters(tableName) {
25588
27115
  const usage2 = /* @__PURE__ */ new Map();
25589
27116
  const routersDir = getResolvedPaths().routersDir;
25590
- if (!existsSync50(routersDir)) return usage2;
27117
+ if (!existsSync52(routersDir)) return usage2;
25591
27118
  scanDirectory(routersDir, tableName, usage2);
25592
27119
  return usage2;
25593
27120
  }
25594
27121
  function scanDirectory(dir, tableName, usage2) {
25595
27122
  const entries = readdirSync20(dir, { withFileTypes: true });
25596
27123
  for (const entry of entries) {
25597
- const fullPath = join26(dir, entry.name);
27124
+ const fullPath = join28(dir, entry.name);
25598
27125
  if (entry.isDirectory()) {
25599
27126
  scanDirectory(fullPath, tableName, usage2);
25600
27127
  } else if (entry.name.endsWith(".ts")) {
@@ -25604,7 +27131,7 @@ function scanDirectory(dir, tableName, usage2) {
25604
27131
  }
25605
27132
  function scanFile(absPath, tableName, usage2) {
25606
27133
  try {
25607
- const source = readFileSync38(absPath, "utf-8");
27134
+ const source = readFileSync40(absPath, "utf-8");
25608
27135
  if (!source.includes(tableName)) return;
25609
27136
  const relPath = absPath.slice(getProjectRoot().length + 1);
25610
27137
  const lines = source.split("\n");
@@ -25649,15 +27176,15 @@ function detectMismatches(models) {
25649
27176
  }
25650
27177
  function findFilesUsingColumn(dir, column, tableName) {
25651
27178
  const result = [];
25652
- if (!existsSync50(dir)) return result;
27179
+ if (!existsSync52(dir)) return result;
25653
27180
  const entries = readdirSync20(dir, { withFileTypes: true });
25654
27181
  for (const entry of entries) {
25655
- const fullPath = join26(dir, entry.name);
27182
+ const fullPath = join28(dir, entry.name);
25656
27183
  if (entry.isDirectory()) {
25657
27184
  result.push(...findFilesUsingColumn(fullPath, column, tableName));
25658
27185
  } else if (entry.name.endsWith(".ts")) {
25659
27186
  try {
25660
- const source = readFileSync38(fullPath, "utf-8");
27187
+ const source = readFileSync40(fullPath, "utf-8");
25661
27188
  if (source.includes(tableName) && source.includes(column)) {
25662
27189
  result.push(fullPath.slice(getProjectRoot().length + 1));
25663
27190
  }
@@ -25802,35 +27329,35 @@ var init_import_parser = __esm({
25802
27329
  });
25803
27330
 
25804
27331
  // src/python/import-resolver.ts
25805
- import { readFileSync as readFileSync39, existsSync as existsSync51, readdirSync as readdirSync21 } from "fs";
25806
- import { resolve as resolve33, join as join27, relative as relative7, dirname as dirname23 } from "path";
27332
+ import { readFileSync as readFileSync41, existsSync as existsSync53, readdirSync as readdirSync21 } from "fs";
27333
+ import { resolve as resolve33, join as join29, relative as relative8, dirname as dirname24 } from "path";
25807
27334
  function resolvePythonModulePath(module, fromFile, pythonRoot, level) {
25808
27335
  const projectRoot = getProjectRoot();
25809
27336
  if (level > 0) {
25810
- let baseDir = dirname23(resolve33(projectRoot, fromFile));
27337
+ let baseDir = dirname24(resolve33(projectRoot, fromFile));
25811
27338
  for (let i = 1; i < level; i++) {
25812
- baseDir = dirname23(baseDir);
27339
+ baseDir = dirname24(baseDir);
25813
27340
  }
25814
27341
  const modulePart = module.replace(/^\.+/, "");
25815
27342
  if (modulePart) {
25816
27343
  const parts2 = modulePart.split(".");
25817
- return tryResolvePythonPath(join27(baseDir, ...parts2), projectRoot);
27344
+ return tryResolvePythonPath(join29(baseDir, ...parts2), projectRoot);
25818
27345
  }
25819
27346
  return tryResolvePythonPath(baseDir, projectRoot);
25820
27347
  }
25821
27348
  const parts = module.split(".");
25822
- const candidate = join27(resolve33(projectRoot, pythonRoot), ...parts);
27349
+ const candidate = join29(resolve33(projectRoot, pythonRoot), ...parts);
25823
27350
  return tryResolvePythonPath(candidate, projectRoot);
25824
27351
  }
25825
27352
  function tryResolvePythonPath(basePath, projectRoot) {
25826
- if (existsSync51(basePath + ".py")) {
25827
- return relative7(projectRoot, basePath + ".py");
27353
+ if (existsSync53(basePath + ".py")) {
27354
+ return relative8(projectRoot, basePath + ".py");
25828
27355
  }
25829
- if (existsSync51(join27(basePath, "__init__.py"))) {
25830
- return relative7(projectRoot, join27(basePath, "__init__.py"));
27356
+ if (existsSync53(join29(basePath, "__init__.py"))) {
27357
+ return relative8(projectRoot, join29(basePath, "__init__.py"));
25831
27358
  }
25832
- if (basePath.endsWith(".py") && existsSync51(basePath)) {
25833
- return relative7(projectRoot, basePath);
27359
+ if (basePath.endsWith(".py") && existsSync53(basePath)) {
27360
+ return relative8(projectRoot, basePath);
25834
27361
  }
25835
27362
  return null;
25836
27363
  }
@@ -25841,9 +27368,9 @@ function walkPythonFiles(dir, excludeDirs) {
25841
27368
  for (const entry of entries) {
25842
27369
  if (entry.isDirectory()) {
25843
27370
  if (excludeDirs.includes(entry.name)) continue;
25844
- files.push(...walkPythonFiles(join27(dir, entry.name), excludeDirs));
27371
+ files.push(...walkPythonFiles(join29(dir, entry.name), excludeDirs));
25845
27372
  } else if (entry.name.endsWith(".py")) {
25846
- files.push(join27(dir, entry.name));
27373
+ files.push(join29(dir, entry.name));
25847
27374
  }
25848
27375
  }
25849
27376
  } catch {
@@ -25866,10 +27393,10 @@ function buildPythonImportIndex(dataDb, pythonRoot, excludeDirs = ["__pycache__"
25866
27393
  });
25867
27394
  const batch = [];
25868
27395
  for (const absFile of files) {
25869
- const relFile = relative7(projectRoot, absFile);
27396
+ const relFile = relative8(projectRoot, absFile);
25870
27397
  let source;
25871
27398
  try {
25872
- source = readFileSync39(absFile, "utf-8");
27399
+ source = readFileSync41(absFile, "utf-8");
25873
27400
  } catch {
25874
27401
  continue;
25875
27402
  }
@@ -26136,8 +27663,8 @@ var init_route_parser = __esm({
26136
27663
  });
26137
27664
 
26138
27665
  // src/python/route-indexer.ts
26139
- import { readFileSync as readFileSync40, readdirSync as readdirSync22 } from "fs";
26140
- import { join as join28, relative as relative8 } from "path";
27666
+ import { readFileSync as readFileSync42, readdirSync as readdirSync22 } from "fs";
27667
+ import { join as join30, relative as relative9 } from "path";
26141
27668
  function walkPyFiles(dir, excludeDirs) {
26142
27669
  const files = [];
26143
27670
  try {
@@ -26145,9 +27672,9 @@ function walkPyFiles(dir, excludeDirs) {
26145
27672
  for (const entry of entries) {
26146
27673
  if (entry.isDirectory()) {
26147
27674
  if (excludeDirs.includes(entry.name)) continue;
26148
- files.push(...walkPyFiles(join28(dir, entry.name), excludeDirs));
27675
+ files.push(...walkPyFiles(join30(dir, entry.name), excludeDirs));
26149
27676
  } else if (entry.name.endsWith(".py")) {
26150
- files.push(join28(dir, entry.name));
27677
+ files.push(join30(dir, entry.name));
26151
27678
  }
26152
27679
  }
26153
27680
  } catch {
@@ -26156,7 +27683,7 @@ function walkPyFiles(dir, excludeDirs) {
26156
27683
  }
26157
27684
  function buildPythonRouteIndex(dataDb, pythonRoot, excludeDirs = ["__pycache__", ".venv", "venv", ".mypy_cache", ".pytest_cache"]) {
26158
27685
  const projectRoot = getProjectRoot();
26159
- const absRoot = join28(projectRoot, pythonRoot);
27686
+ const absRoot = join30(projectRoot, pythonRoot);
26160
27687
  dataDb.exec(`DELETE FROM ${t("py_routes")}`);
26161
27688
  dataDb.exec(`DELETE FROM ${t("py_route_callers")}`);
26162
27689
  const insertStmt = dataDb.prepare(
@@ -26166,10 +27693,10 @@ function buildPythonRouteIndex(dataDb, pythonRoot, excludeDirs = ["__pycache__",
26166
27693
  let count = 0;
26167
27694
  dataDb.transaction(() => {
26168
27695
  for (const absFile of files) {
26169
- const relFile = relative8(projectRoot, absFile);
27696
+ const relFile = relative9(projectRoot, absFile);
26170
27697
  let source;
26171
27698
  try {
26172
- source = readFileSync40(absFile, "utf-8");
27699
+ source = readFileSync42(absFile, "utf-8");
26173
27700
  } catch {
26174
27701
  continue;
26175
27702
  }
@@ -26276,7 +27803,7 @@ function parsePythonModels(source) {
26276
27803
  const bases = classMatch[2];
26277
27804
  const classLine = i + 1;
26278
27805
  let isModel = isModelClass(bases);
26279
- const columns = [];
27806
+ const columns2 = [];
26280
27807
  const relationships = [];
26281
27808
  const foreignKeys = [];
26282
27809
  let tableName = null;
@@ -26319,7 +27846,7 @@ function parsePythonModels(source) {
26319
27846
  const colType = extractColumnType(argsStr);
26320
27847
  const nullable = extractNullable(argsStr);
26321
27848
  const primaryKey = extractPrimaryKey(argsStr);
26322
- columns.push({ name: colName, type: colType, nullable, primaryKey });
27849
+ columns2.push({ name: colName, type: colType, nullable, primaryKey });
26323
27850
  foreignKeys.push(...extractForeignKeys(colName, argsStr));
26324
27851
  }
26325
27852
  i++;
@@ -26333,7 +27860,7 @@ function parsePythonModels(source) {
26333
27860
  const mappedType = extractMappedType(mappedAnnotation);
26334
27861
  const nullable = mappedType.startsWith("Optional") || extractNullable(argsStr || "");
26335
27862
  const primaryKey = extractPrimaryKey(argsStr || "");
26336
- columns.push({ name: colName, type: mappedType, nullable, primaryKey });
27863
+ columns2.push({ name: colName, type: mappedType, nullable, primaryKey });
26337
27864
  if (argsStr) {
26338
27865
  foreignKeys.push(...extractForeignKeys(colName, argsStr));
26339
27866
  }
@@ -26363,7 +27890,7 @@ function parsePythonModels(source) {
26363
27890
  models.push({
26364
27891
  className,
26365
27892
  tableName,
26366
- columns,
27893
+ columns: columns2,
26367
27894
  relationships,
26368
27895
  foreignKeys,
26369
27896
  line: classLine
@@ -26381,8 +27908,8 @@ var init_model_parser = __esm({
26381
27908
  });
26382
27909
 
26383
27910
  // src/python/model-indexer.ts
26384
- import { readFileSync as readFileSync41, readdirSync as readdirSync23 } from "fs";
26385
- import { join as join29, relative as relative9 } from "path";
27911
+ import { readFileSync as readFileSync43, readdirSync as readdirSync23 } from "fs";
27912
+ import { join as join31, relative as relative10 } from "path";
26386
27913
  function walkPyFiles2(dir, excludeDirs) {
26387
27914
  const files = [];
26388
27915
  try {
@@ -26390,9 +27917,9 @@ function walkPyFiles2(dir, excludeDirs) {
26390
27917
  for (const entry of entries) {
26391
27918
  if (entry.isDirectory()) {
26392
27919
  if (excludeDirs.includes(entry.name)) continue;
26393
- files.push(...walkPyFiles2(join29(dir, entry.name), excludeDirs));
27920
+ files.push(...walkPyFiles2(join31(dir, entry.name), excludeDirs));
26394
27921
  } else if (entry.name.endsWith(".py")) {
26395
- files.push(join29(dir, entry.name));
27922
+ files.push(join31(dir, entry.name));
26396
27923
  }
26397
27924
  }
26398
27925
  } catch {
@@ -26401,7 +27928,7 @@ function walkPyFiles2(dir, excludeDirs) {
26401
27928
  }
26402
27929
  function buildPythonModelIndex(dataDb, pythonRoot, excludeDirs = ["__pycache__", ".venv", "venv", ".mypy_cache", ".pytest_cache"]) {
26403
27930
  const projectRoot = getProjectRoot();
26404
- const absRoot = join29(projectRoot, pythonRoot);
27931
+ const absRoot = join31(projectRoot, pythonRoot);
26405
27932
  dataDb.exec(`DELETE FROM ${t("py_models")}`);
26406
27933
  dataDb.exec(`DELETE FROM ${t("py_fk_edges")}`);
26407
27934
  const insertModel = dataDb.prepare(
@@ -26414,10 +27941,10 @@ function buildPythonModelIndex(dataDb, pythonRoot, excludeDirs = ["__pycache__",
26414
27941
  let count = 0;
26415
27942
  dataDb.transaction(() => {
26416
27943
  for (const absFile of files) {
26417
- const relFile = relative9(projectRoot, absFile);
27944
+ const relFile = relative10(projectRoot, absFile);
26418
27945
  let source;
26419
27946
  try {
26420
- source = readFileSync41(absFile, "utf-8");
27947
+ source = readFileSync43(absFile, "utf-8");
26421
27948
  } catch {
26422
27949
  continue;
26423
27950
  }
@@ -26678,19 +28205,19 @@ var init_migration_parser = __esm({
26678
28205
  });
26679
28206
 
26680
28207
  // src/python/migration-indexer.ts
26681
- import { readFileSync as readFileSync42, readdirSync as readdirSync24 } from "fs";
26682
- import { join as join30, relative as relative10 } from "path";
28208
+ import { readFileSync as readFileSync44, readdirSync as readdirSync24 } from "fs";
28209
+ import { join as join32, relative as relative11 } from "path";
26683
28210
  function buildPythonMigrationIndex(dataDb, alembicDir) {
26684
28211
  const projectRoot = getProjectRoot();
26685
- const absDir = join30(projectRoot, alembicDir);
28212
+ const absDir = join32(projectRoot, alembicDir);
26686
28213
  dataDb.exec(`DELETE FROM ${t("py_migrations")}`);
26687
- const versionsDir = join30(absDir, "versions");
28214
+ const versionsDir = join32(absDir, "versions");
26688
28215
  let files = [];
26689
28216
  try {
26690
- files = readdirSync24(versionsDir).filter((f2) => f2.endsWith(".py")).map((f2) => join30(versionsDir, f2));
28217
+ files = readdirSync24(versionsDir).filter((f2) => f2.endsWith(".py")).map((f2) => join32(versionsDir, f2));
26691
28218
  } catch {
26692
28219
  try {
26693
- files = readdirSync24(absDir).filter((f2) => f2.endsWith(".py") && f2 !== "env.py").map((f2) => join30(absDir, f2));
28220
+ files = readdirSync24(absDir).filter((f2) => f2.endsWith(".py") && f2 !== "env.py").map((f2) => join32(absDir, f2));
26694
28221
  } catch {
26695
28222
  }
26696
28223
  }
@@ -26704,7 +28231,7 @@ function buildPythonMigrationIndex(dataDb, alembicDir) {
26704
28231
  for (const absFile of files) {
26705
28232
  let source;
26706
28233
  try {
26707
- source = readFileSync42(absFile, "utf-8");
28234
+ source = readFileSync44(absFile, "utf-8");
26708
28235
  } catch {
26709
28236
  continue;
26710
28237
  }
@@ -26715,7 +28242,7 @@ function buildPythonMigrationIndex(dataDb, alembicDir) {
26715
28242
  rows.push({
26716
28243
  revision: parsed.revision,
26717
28244
  downRevision: parsed.downRevision,
26718
- file: relative10(projectRoot, absFile),
28245
+ file: relative11(projectRoot, absFile),
26719
28246
  description: parsed.description,
26720
28247
  operations: JSON.stringify(parsed.operations)
26721
28248
  });
@@ -26739,12 +28266,12 @@ var init_migration_indexer = __esm({
26739
28266
  });
26740
28267
 
26741
28268
  // src/python/coupling-detector.ts
26742
- import { readFileSync as readFileSync43, readdirSync as readdirSync25 } from "fs";
26743
- import { join as join31, relative as relative11 } from "path";
28269
+ import { readFileSync as readFileSync45, readdirSync as readdirSync25 } from "fs";
28270
+ import { join as join33, relative as relative12 } from "path";
26744
28271
  function buildPythonCouplingIndex(dataDb) {
26745
28272
  const projectRoot = getProjectRoot();
26746
28273
  const config = getConfig();
26747
- const srcDir = join31(projectRoot, config.paths.source);
28274
+ const srcDir = join33(projectRoot, config.paths.source);
26748
28275
  const routes = dataDb.prepare(`SELECT id, method, path FROM ${t("py_routes")}`).all();
26749
28276
  if (routes.length === 0) return 0;
26750
28277
  dataDb.exec(`DELETE FROM ${t("py_route_callers")}`);
@@ -26773,10 +28300,10 @@ function buildPythonCouplingIndex(dataDb) {
26773
28300
  ];
26774
28301
  dataDb.transaction(() => {
26775
28302
  for (const absFile of frontendFiles) {
26776
- const relFile = relative11(projectRoot, absFile);
28303
+ const relFile = relative12(projectRoot, absFile);
26777
28304
  let source;
26778
28305
  try {
26779
- source = readFileSync43(absFile, "utf-8");
28306
+ source = readFileSync45(absFile, "utf-8");
26780
28307
  } catch {
26781
28308
  continue;
26782
28309
  }
@@ -26808,9 +28335,9 @@ function walkFrontendFiles(dir) {
26808
28335
  for (const entry of entries) {
26809
28336
  if (entry.isDirectory()) {
26810
28337
  if (exclude.includes(entry.name)) continue;
26811
- files.push(...walkFrontendFiles(join31(dir, entry.name)));
28338
+ files.push(...walkFrontendFiles(join33(dir, entry.name)));
26812
28339
  } else if (/\.(tsx?|jsx?)$/.test(entry.name)) {
26813
- files.push(join31(dir, entry.name));
28340
+ files.push(join33(dir, entry.name));
26814
28341
  }
26815
28342
  }
26816
28343
  } catch {
@@ -26840,14 +28367,14 @@ var init_coupling_detector = __esm({
26840
28367
  });
26841
28368
 
26842
28369
  // src/memory-tools.ts
26843
- function p2(baseName) {
28370
+ function p3(baseName) {
26844
28371
  return `${getConfig().toolPrefix}_${baseName}`;
26845
28372
  }
26846
28373
  function getMemoryToolDefinitions() {
26847
28374
  return [
26848
28375
  // P4-001: memory_search
26849
28376
  {
26850
- name: p2("memory_search"),
28377
+ name: p3("memory_search"),
26851
28378
  description: "Search past session observations and decisions using full-text search. Returns compact index of matching observations.",
26852
28379
  inputSchema: {
26853
28380
  type: "object",
@@ -26863,7 +28390,7 @@ function getMemoryToolDefinitions() {
26863
28390
  },
26864
28391
  // P4-002: memory_timeline
26865
28392
  {
26866
- name: p2("memory_timeline"),
28393
+ name: p3("memory_timeline"),
26867
28394
  description: "Retrieve episodic memory - chronological context around a specific event. Shows observations before and after the anchor point to reconstruct what happened and why.",
26868
28395
  inputSchema: {
26869
28396
  type: "object",
@@ -26877,7 +28404,7 @@ function getMemoryToolDefinitions() {
26877
28404
  },
26878
28405
  // P4-003: memory_detail
26879
28406
  {
26880
- name: p2("memory_detail"),
28407
+ name: p3("memory_detail"),
26881
28408
  description: "Get full observation details by IDs (batch). Includes evidence, files, plan items.",
26882
28409
  inputSchema: {
26883
28410
  type: "object",
@@ -26893,7 +28420,7 @@ function getMemoryToolDefinitions() {
26893
28420
  },
26894
28421
  // P4-004: memory_sessions
26895
28422
  {
26896
- name: p2("memory_sessions"),
28423
+ name: p3("memory_sessions"),
26897
28424
  description: "List recent sessions with summaries.",
26898
28425
  inputSchema: {
26899
28426
  type: "object",
@@ -26906,7 +28433,7 @@ function getMemoryToolDefinitions() {
26906
28433
  },
26907
28434
  // P4-005: memory_failures
26908
28435
  {
26909
- name: p2("memory_failures"),
28436
+ name: p3("memory_failures"),
26910
28437
  description: "Get all failed attempts (DON'T RETRY warnings). Check before attempting a fix to see if it was already tried.",
26911
28438
  inputSchema: {
26912
28439
  type: "object",
@@ -26919,7 +28446,7 @@ function getMemoryToolDefinitions() {
26919
28446
  },
26920
28447
  // P4-007: memory_backfill
26921
28448
  {
26922
- name: p2("memory_backfill"),
28449
+ name: p3("memory_backfill"),
26923
28450
  description: "Scan all memory/*.md files and ingest into database. Run after massu init or to recover from DB loss. Parses YAML frontmatter and deduplicates by title.",
26924
28451
  inputSchema: {
26925
28452
  type: "object",
@@ -26929,7 +28456,7 @@ function getMemoryToolDefinitions() {
26929
28456
  },
26930
28457
  // P4-006: memory_ingest
26931
28458
  {
26932
- name: p2("memory_ingest"),
28459
+ name: p3("memory_ingest"),
26933
28460
  description: "Manually record an observation that hooks cannot auto-detect. Use for significant decisions, discoveries, or failed attempts mid-session.",
26934
28461
  inputSchema: {
26935
28462
  type: "object",
@@ -26974,15 +28501,15 @@ function handleMemoryToolCall(name, args2, memoryDb) {
26974
28501
  case "memory_backfill":
26975
28502
  return handleBackfill(memoryDb);
26976
28503
  default:
26977
- return text(`Unknown memory tool: ${name}`);
28504
+ return text2(`Unknown memory tool: ${name}`);
26978
28505
  }
26979
28506
  } catch (error) {
26980
- return text(`Error in ${name}: ${error instanceof Error ? error.message : String(error)}`);
28507
+ return text2(`Error in ${name}: ${error instanceof Error ? error.message : String(error)}`);
26981
28508
  }
26982
28509
  }
26983
28510
  function handleSearch(args2, db) {
26984
28511
  const query = args2.query;
26985
- if (!query) return text("Error: query is required");
28512
+ if (!query) return text2("Error: query is required");
26986
28513
  const results = searchObservations(db, query, {
26987
28514
  type: args2.type,
26988
28515
  crRule: args2.cr_rule,
@@ -26990,7 +28517,7 @@ function handleSearch(args2, db) {
26990
28517
  limit: args2.limit
26991
28518
  });
26992
28519
  if (results.length === 0) {
26993
- return text(`No observations found for "${query}".`);
28520
+ return text2(`No observations found for "${query}".`);
26994
28521
  }
26995
28522
  const lines = [`## Search Results for "${query}" (${results.length} matches)`, ""];
26996
28523
  lines.push("| ID | Type | Title | Date | Importance |");
@@ -26999,17 +28526,17 @@ function handleSearch(args2, db) {
26999
28526
  lines.push(`| ${r2.id} | ${r2.type} | ${r2.title.slice(0, 80)} | ${r2.created_at.split("T")[0]} | ${r2.importance} |`);
27000
28527
  }
27001
28528
  lines.push("");
27002
- lines.push(`Use ${p2("memory_detail")} with IDs for full details.`);
27003
- lines.push(`Use ${p2("memory_timeline")} with an ID for chronological context.`);
27004
- return text(lines.join("\n"));
28529
+ lines.push(`Use ${p3("memory_detail")} with IDs for full details.`);
28530
+ lines.push(`Use ${p3("memory_timeline")} with an ID for chronological context.`);
28531
+ return text2(lines.join("\n"));
27005
28532
  }
27006
28533
  function handleTimeline(args2, db) {
27007
28534
  const observationId = args2.observation_id;
27008
- if (!observationId) return text("Error: observation_id is required");
28535
+ if (!observationId) return text2("Error: observation_id is required");
27009
28536
  const depthBefore = args2.depth_before ?? 5;
27010
28537
  const depthAfter = args2.depth_after ?? 5;
27011
28538
  const anchor = db.prepare("SELECT * FROM observations WHERE id = ?").get(observationId);
27012
- if (!anchor) return text(`Observation ${observationId} not found.`);
28539
+ if (!anchor) return text2(`Observation ${observationId} not found.`);
27013
28540
  const before = db.prepare(
27014
28541
  "SELECT id, type, title, created_at, importance FROM observations WHERE session_id = ? AND created_at_epoch < ? ORDER BY created_at_epoch DESC LIMIT ?"
27015
28542
  ).all(anchor.session_id, anchor.created_at_epoch, depthBefore);
@@ -27024,17 +28551,17 @@ function handleTimeline(args2, db) {
27024
28551
  for (const o of after) {
27025
28552
  lines.push(` ${o.id} | ${o.type} | ${o.title.slice(0, 60)} | ${o.created_at.split("T")[0]}`);
27026
28553
  }
27027
- return text(lines.join("\n"));
28554
+ return text2(lines.join("\n"));
27028
28555
  }
27029
28556
  function handleDetail(args2, db) {
27030
28557
  const ids = args2.ids;
27031
- if (!ids || ids.length === 0) return text("Error: ids array is required");
28558
+ if (!ids || ids.length === 0) return text2("Error: ids array is required");
27032
28559
  const placeholders = ids.map(() => "?").join(",");
27033
28560
  const observations = db.prepare(
27034
28561
  `SELECT * FROM observations WHERE id IN (${placeholders}) ORDER BY created_at_epoch ASC LIMIT 10000`
27035
28562
  ).all(...ids);
27036
28563
  if (observations.length === 0) {
27037
- return text("No observations found for the given IDs.");
28564
+ return text2("No observations found for the given IDs.");
27038
28565
  }
27039
28566
  const lines = [];
27040
28567
  for (const o of observations) {
@@ -27054,7 +28581,7 @@ function handleDetail(args2, db) {
27054
28581
  if (o.recurrence_count > 1) lines.push(`**Recurrence**: ${o.recurrence_count}x`);
27055
28582
  lines.push("");
27056
28583
  }
27057
- return text(lines.join("\n"));
28584
+ return text2(lines.join("\n"));
27058
28585
  }
27059
28586
  function handleSessions(args2, db) {
27060
28587
  const limit = args2.limit ?? 10;
@@ -27069,7 +28596,7 @@ function handleSessions(args2, db) {
27069
28596
  params.push(limit);
27070
28597
  const sessions = db.prepare(sql).all(...params);
27071
28598
  if (sessions.length === 0) {
27072
- return text("No sessions found.");
28599
+ return text2("No sessions found.");
27073
28600
  }
27074
28601
  const lines = ["## Recent Sessions", ""];
27075
28602
  lines.push("| Session ID | Status | Branch | Started | Plan |");
@@ -27089,14 +28616,14 @@ function handleSessions(args2, db) {
27089
28616
  if (summary.completed) lines.push(`**Completed**: ${summary.completed.slice(0, 200)}`);
27090
28617
  }
27091
28618
  }
27092
- return text(lines.join("\n"));
28619
+ return text2(lines.join("\n"));
27093
28620
  }
27094
28621
  function handleFailures(args2, db) {
27095
28622
  const query = args2.query;
27096
28623
  const limit = args2.limit ?? 20;
27097
28624
  const failures = getFailedAttempts(db, query, limit);
27098
28625
  if (failures.length === 0) {
27099
- return text(query ? `No failed attempts found for "${query}".` : "No failed attempts recorded.");
28626
+ return text2(query ? `No failed attempts found for "${query}".` : "No failed attempts recorded.");
27100
28627
  }
27101
28628
  const lines = ["## Failed Attempts (DO NOT RETRY)", ""];
27102
28629
  for (const f2 of failures) {
@@ -27106,12 +28633,12 @@ function handleFailures(args2, db) {
27106
28633
  lines.push(`Session: ${f2.session_id.slice(0, 8)}... | Date: ${f2.created_at.split("T")[0]}`);
27107
28634
  lines.push("");
27108
28635
  }
27109
- return text(lines.join("\n"));
28636
+ return text2(lines.join("\n"));
27110
28637
  }
27111
28638
  function handleIngest(args2, db) {
27112
28639
  const type = args2.type;
27113
28640
  const title = args2.title;
27114
- if (!type || !title) return text("Error: type and title are required");
28641
+ if (!type || !title) return text2("Error: type and title are required");
27115
28642
  const validTypes = [
27116
28643
  "decision",
27117
28644
  "bugfix",
@@ -27126,13 +28653,13 @@ function handleIngest(args2, db) {
27126
28653
  "incident_near_miss"
27127
28654
  ];
27128
28655
  if (!validTypes.includes(type)) {
27129
- return text(`Error: invalid type "${type}". Valid types: ${validTypes.join(", ")}`);
28656
+ return text2(`Error: invalid type "${type}". Valid types: ${validTypes.join(", ")}`);
27130
28657
  }
27131
28658
  const activeSession = db.prepare(
27132
28659
  "SELECT session_id FROM sessions WHERE status = 'active' ORDER BY started_at_epoch DESC LIMIT 1"
27133
28660
  ).get();
27134
28661
  if (!activeSession) {
27135
- return text("Error: no active session found. Start a session first.");
28662
+ return text2("Error: no active session found. Start a session first.");
27136
28663
  }
27137
28664
  const importance = args2.importance ?? assignImportance(type);
27138
28665
  const id = addObservation(db, activeSession.session_id, type, title, args2.detail ?? null, {
@@ -27141,7 +28668,7 @@ function handleIngest(args2, db) {
27141
28668
  planItem: args2.plan_item,
27142
28669
  filesInvolved: args2.files
27143
28670
  });
27144
- return text(`Observation #${id} recorded successfully.
28671
+ return text2(`Observation #${id} recorded successfully.
27145
28672
  Type: ${type}
27146
28673
  Title: ${title}
27147
28674
  Importance: ${importance}
@@ -27160,9 +28687,9 @@ function handleBackfill(db) {
27160
28687
  "",
27161
28688
  stats.total === 0 ? "No memory files found in memory directory." : `Successfully processed ${stats.inserted + stats.updated} of ${stats.total} memory files.`
27162
28689
  ];
27163
- return text(lines.join("\n"));
28690
+ return text2(lines.join("\n"));
27164
28691
  }
27165
- function text(content) {
28692
+ function text2(content) {
27166
28693
  return { content: [{ type: "text", text: content }] };
27167
28694
  }
27168
28695
  function safeParseJson(json, fallback) {
@@ -27182,15 +28709,15 @@ var init_memory_tools = __esm({
27182
28709
  });
27183
28710
 
27184
28711
  // src/docs-tools.ts
27185
- import { readFileSync as readFileSync44, existsSync as existsSync52 } from "fs";
28712
+ import { readFileSync as readFileSync46, existsSync as existsSync54 } from "fs";
27186
28713
  import { resolve as resolve34, basename as basename9 } from "path";
27187
- function p3(baseName) {
28714
+ function p4(baseName) {
27188
28715
  return `${getConfig().toolPrefix}_${baseName}`;
27189
28716
  }
27190
28717
  function getDocsToolDefinitions() {
27191
28718
  return [
27192
28719
  {
27193
- name: p3("docs_audit"),
28720
+ name: p4("docs_audit"),
27194
28721
  description: "Audit which help site pages need updating based on changed files. Maps code changes to affected documentation pages using docs-map.json.",
27195
28722
  inputSchema: {
27196
28723
  type: "object",
@@ -27209,7 +28736,7 @@ function getDocsToolDefinitions() {
27209
28736
  }
27210
28737
  },
27211
28738
  {
27212
- name: p3("docs_coverage"),
28739
+ name: p4("docs_coverage"),
27213
28740
  description: "Report docs coverage: which help pages exist, have content, and are up-to-date. Identifies documentation gaps.",
27214
28741
  inputSchema: {
27215
28742
  type: "object",
@@ -27233,15 +28760,15 @@ function handleDocsToolCall(name, args2) {
27233
28760
  case "docs_coverage":
27234
28761
  return handleDocsCoverage(args2);
27235
28762
  default:
27236
- return text2(`Unknown docs tool: ${name}`);
28763
+ return text3(`Unknown docs tool: ${name}`);
27237
28764
  }
27238
28765
  }
27239
28766
  function loadDocsMap() {
27240
28767
  const mapPath = getResolvedPaths().docsMapPath;
27241
- if (!existsSync52(mapPath)) {
28768
+ if (!existsSync54(mapPath)) {
27242
28769
  throw new Error(`docs-map.json not found at ${mapPath}`);
27243
28770
  }
27244
- return JSON.parse(readFileSync44(mapPath, "utf-8"));
28771
+ return JSON.parse(readFileSync46(mapPath, "utf-8"));
27245
28772
  }
27246
28773
  function matchesPattern(filePath, pattern) {
27247
28774
  const regexStr = pattern.replace(/\./g, "\\.").replace(/\*\*/g, "{{GLOBSTAR}}").replace(/\*/g, "[^/]*").replace(/\{\{GLOBSTAR\}\}/g, ".*");
@@ -27312,12 +28839,12 @@ function extractFrontmatter(content) {
27312
28839
  function extractProcedureNames(routerPath) {
27313
28840
  const root = getProjectRoot();
27314
28841
  const absPath = ensureWithinRoot(resolve34(getResolvedPaths().srcDir, "..", routerPath), root);
27315
- if (!existsSync52(absPath)) {
28842
+ if (!existsSync54(absPath)) {
27316
28843
  const altPath = ensureWithinRoot(resolve34(getResolvedPaths().srcDir, "../server/api/routers", basename9(routerPath)), root);
27317
- if (!existsSync52(altPath)) return [];
27318
- return extractProcedureNamesFromContent(readFileSync44(altPath, "utf-8"));
28844
+ if (!existsSync54(altPath)) return [];
28845
+ return extractProcedureNamesFromContent(readFileSync46(altPath, "utf-8"));
27319
28846
  }
27320
- return extractProcedureNamesFromContent(readFileSync44(absPath, "utf-8"));
28847
+ return extractProcedureNamesFromContent(readFileSync46(absPath, "utf-8"));
27321
28848
  }
27322
28849
  function extractProcedureNamesFromContent(content) {
27323
28850
  const procRegex = /\.(?:query|mutation)\s*\(/g;
@@ -27343,12 +28870,12 @@ function handleDocsAudit(args2) {
27343
28870
  const changedFiles = args2.changed_files;
27344
28871
  const commitMessage = args2.commit_message || "";
27345
28872
  if (!changedFiles || changedFiles.length === 0) {
27346
- return text2(JSON.stringify({ affectedPages: [], summary: "No changed files provided." }));
28873
+ return text3(JSON.stringify({ affectedPages: [], summary: "No changed files provided." }));
27347
28874
  }
27348
28875
  const docsMap = loadDocsMap();
27349
28876
  const affectedMappings = findAffectedMappings(docsMap, changedFiles);
27350
28877
  if (affectedMappings.size === 0) {
27351
- return text2(JSON.stringify({
28878
+ return text3(JSON.stringify({
27352
28879
  affectedPages: [],
27353
28880
  summary: `0 help pages affected by ${changedFiles.length} changed files. No docs update needed.`
27354
28881
  }));
@@ -27358,7 +28885,7 @@ function handleDocsAudit(args2) {
27358
28885
  const mapping = docsMap.mappings.find((m3) => m3.id === mappingId);
27359
28886
  if (!mapping) continue;
27360
28887
  const helpPagePath = ensureWithinRoot(resolve34(getResolvedPaths().helpSitePath, mapping.helpPage), getProjectRoot());
27361
- if (!existsSync52(helpPagePath)) {
28888
+ if (!existsSync54(helpPagePath)) {
27362
28889
  results.push({
27363
28890
  helpPage: mapping.helpPage,
27364
28891
  mappingId,
@@ -27370,7 +28897,7 @@ function handleDocsAudit(args2) {
27370
28897
  });
27371
28898
  continue;
27372
28899
  }
27373
- const content = readFileSync44(helpPagePath, "utf-8");
28900
+ const content = readFileSync46(helpPagePath, "utf-8");
27374
28901
  const sections = extractSections(content);
27375
28902
  const frontmatter = extractFrontmatter(content);
27376
28903
  const staleReasons = [];
@@ -27411,8 +28938,8 @@ function handleDocsAudit(args2) {
27411
28938
  for (const [guideName, parentId] of Object.entries(docsMap.userGuideInheritance.examples)) {
27412
28939
  if (parentId === mappingId) {
27413
28940
  const guidePath = ensureWithinRoot(resolve34(getResolvedPaths().helpSitePath, `pages/user-guides/${guideName}/index.mdx`), getProjectRoot());
27414
- if (existsSync52(guidePath)) {
27415
- const guideContent = readFileSync44(guidePath, "utf-8");
28941
+ if (existsSync54(guidePath)) {
28942
+ const guideContent = readFileSync46(guidePath, "utf-8");
27416
28943
  const guideFrontmatter = extractFrontmatter(guideContent);
27417
28944
  if (!guideFrontmatter?.lastVerified || status === "STALE") {
27418
28945
  results.push({
@@ -27436,7 +28963,7 @@ function handleDocsAudit(args2) {
27436
28963
  affectedPages: results,
27437
28964
  summary: `${results.length} pages checked: ${staleCount} STALE, ${newCount} NEW, ${okCount} OK. ${staleCount + newCount > 0 ? `${staleCount + newCount} pages need updates.` : "All docs are current."}`
27438
28965
  };
27439
- return text2(JSON.stringify(report, null, 2));
28966
+ return text3(JSON.stringify(report, null, 2));
27440
28967
  }
27441
28968
  function handleDocsCoverage(args2) {
27442
28969
  const filterDomain = args2.domain;
@@ -27446,13 +28973,13 @@ function handleDocsCoverage(args2) {
27446
28973
  const mappings = filterDomain ? docsMap.mappings.filter((m3) => m3.id === filterDomain) : docsMap.mappings;
27447
28974
  for (const mapping of mappings) {
27448
28975
  const helpPagePath = ensureWithinRoot(resolve34(getResolvedPaths().helpSitePath, mapping.helpPage), getProjectRoot());
27449
- const exists = existsSync52(helpPagePath);
28976
+ const exists = existsSync54(helpPagePath);
27450
28977
  let hasContent = false;
27451
28978
  let lineCount = 0;
27452
28979
  let lastVerified = null;
27453
28980
  let status = null;
27454
28981
  if (exists) {
27455
- const content = readFileSync44(helpPagePath, "utf-8");
28982
+ const content = readFileSync46(helpPagePath, "utf-8");
27456
28983
  lineCount = content.split("\n").length;
27457
28984
  hasContent = lineCount > 10;
27458
28985
  const frontmatter = extractFrontmatter(content);
@@ -27503,9 +29030,9 @@ function handleDocsCoverage(args2) {
27503
29030
  const icon = entry.hasContent ? "OK" : entry.exists ? "THIN" : "MISSING";
27504
29031
  lines.push(`- [${icon}] ${entry.id}: ${entry.helpPage}${verified}${pageStatus} (${entry.lineCount} lines)`);
27505
29032
  }
27506
- return text2(lines.join("\n"));
29033
+ return text3(lines.join("\n"));
27507
29034
  }
27508
- function text2(content) {
29035
+ function text3(content) {
27509
29036
  return { content: [{ type: "text", text: content }] };
27510
29037
  }
27511
29038
  var init_docs_tools = __esm({
@@ -27517,7 +29044,7 @@ var init_docs_tools = __esm({
27517
29044
  });
27518
29045
 
27519
29046
  // src/observability-tools.ts
27520
- function p4(baseName) {
29047
+ function p5(baseName) {
27521
29048
  return `${getConfig().toolPrefix}_${baseName}`;
27522
29049
  }
27523
29050
  function isObservabilityTool(name) {
@@ -27529,7 +29056,7 @@ function getObservabilityToolDefinitions() {
27529
29056
  return [
27530
29057
  // P3-001: session_replay
27531
29058
  {
27532
- name: p4("session_replay"),
29059
+ name: p5("session_replay"),
27533
29060
  description: "Replay a past session as a chronological conversation with user prompts, assistant responses, and optional tool call details. Requires conversation data captured by the session-end hook.",
27534
29061
  inputSchema: {
27535
29062
  type: "object",
@@ -27544,7 +29071,7 @@ function getObservabilityToolDefinitions() {
27544
29071
  },
27545
29072
  // P3-002: prompt_analysis
27546
29073
  {
27547
- name: p4("prompt_analysis"),
29074
+ name: p5("prompt_analysis"),
27548
29075
  description: "Search and analyze prompts across sessions using FTS5 full-text search on conversation_turns_fts. Find prompts by keyword, filter by date range or complexity (tool call count).",
27549
29076
  inputSchema: {
27550
29077
  type: "object",
@@ -27561,7 +29088,7 @@ function getObservabilityToolDefinitions() {
27561
29088
  },
27562
29089
  // P3-003: tool_patterns
27563
29090
  {
27564
- name: p4("tool_patterns"),
29091
+ name: p5("tool_patterns"),
27565
29092
  description: "Analyze tool usage patterns across sessions. Shows counts, success rates, average I/O sizes per tool. Can group by tool name, session, or day.",
27566
29093
  inputSchema: {
27567
29094
  type: "object",
@@ -27576,7 +29103,7 @@ function getObservabilityToolDefinitions() {
27576
29103
  },
27577
29104
  // P3-004: session_stats
27578
29105
  {
27579
- name: p4("session_stats"),
29106
+ name: p5("session_stats"),
27580
29107
  description: "Get per-session statistics: turn count, tool call breakdown, token usage, duration. Includes database size monitoring for observability data.",
27581
29108
  inputSchema: {
27582
29109
  type: "object",
@@ -27603,15 +29130,15 @@ function handleObservabilityToolCall(name, args2, memoryDb) {
27603
29130
  case "session_stats":
27604
29131
  return handleSessionStats(args2, memoryDb);
27605
29132
  default:
27606
- return text3(`Unknown observability tool: ${name}`);
29133
+ return text4(`Unknown observability tool: ${name}`);
27607
29134
  }
27608
29135
  } catch (error) {
27609
- return text3(`Error in ${name}: ${error instanceof Error ? error.message : String(error)}`);
29136
+ return text4(`Error in ${name}: ${error instanceof Error ? error.message : String(error)}`);
27610
29137
  }
27611
29138
  }
27612
29139
  function handleSessionReplay(args2, db) {
27613
29140
  const sessionId = args2.session_id;
27614
- if (!sessionId) return text3("Error: session_id is required");
29141
+ if (!sessionId) return text4("Error: session_id is required");
27615
29142
  const includeToolCalls = args2.include_tool_calls ?? false;
27616
29143
  const turns = getConversationTurns(db, sessionId, {
27617
29144
  turnFrom: args2.turn_from,
@@ -27619,7 +29146,7 @@ function handleSessionReplay(args2, db) {
27619
29146
  includeToolCalls
27620
29147
  });
27621
29148
  if (turns.length === 0) {
27622
- return text3(`No conversation turns found for session ${sessionId.slice(0, 8)}...
29149
+ return text4(`No conversation turns found for session ${sessionId.slice(0, 8)}...
27623
29150
  Note: Conversation data is captured by the session-end hook. Only sessions that have ended will have replay data.`);
27624
29151
  }
27625
29152
  const lines = [`## Session Replay: ${sessionId.slice(0, 12)}...`, `Turns: ${turns.length}`, ""];
@@ -27651,7 +29178,7 @@ Note: Conversation data is captured by the session-end hook. Only sessions that
27651
29178
  lines.push("---");
27652
29179
  lines.push("");
27653
29180
  }
27654
- return text3(lines.join("\n"));
29181
+ return text4(lines.join("\n"));
27655
29182
  }
27656
29183
  function handlePromptAnalysis(args2, db) {
27657
29184
  const query = args2.query;
@@ -27660,7 +29187,7 @@ function handlePromptAnalysis(args2, db) {
27660
29187
  "SELECT ct.session_id, ct.turn_number, ct.user_prompt, ct.tool_call_count, ct.response_tokens, ct.created_at FROM conversation_turns ct ORDER BY ct.created_at_epoch DESC LIMIT ?"
27661
29188
  ).all(args2.limit ?? 20);
27662
29189
  if (recentTurns.length === 0) {
27663
- return text3("No conversation turns recorded yet.");
29190
+ return text4("No conversation turns recorded yet.");
27664
29191
  }
27665
29192
  const lines2 = ["## Recent Prompts", ""];
27666
29193
  lines2.push("| Session | Turn | Prompt (truncated) | Tools | Response Tokens | Date |");
@@ -27668,7 +29195,7 @@ function handlePromptAnalysis(args2, db) {
27668
29195
  for (const t2 of recentTurns) {
27669
29196
  lines2.push(`| ${t2.session_id.slice(0, 8)}... | ${t2.turn_number} | ${t2.user_prompt.slice(0, 60).replace(/\|/g, "\\|")} | ${t2.tool_call_count} | ${t2.response_tokens ?? "-"} | ${t2.created_at.split("T")[0]} |`);
27670
29197
  }
27671
- return text3(lines2.join("\n"));
29198
+ return text4(lines2.join("\n"));
27672
29199
  }
27673
29200
  const results = searchConversationTurns(db, query, {
27674
29201
  sessionId: args2.session_id,
@@ -27678,7 +29205,7 @@ function handlePromptAnalysis(args2, db) {
27678
29205
  limit: args2.limit
27679
29206
  });
27680
29207
  if (results.length === 0) {
27681
- return text3(`No prompts found matching "${query}".`);
29208
+ return text4(`No prompts found matching "${query}".`);
27682
29209
  }
27683
29210
  const lines = [`## Prompt Search: "${query}" (${results.length} results)`, ""];
27684
29211
  lines.push("| Session | Turn | Prompt (truncated) | Tools | Response Tokens | Date |");
@@ -27686,7 +29213,7 @@ function handlePromptAnalysis(args2, db) {
27686
29213
  for (const r2 of results) {
27687
29214
  lines.push(`| ${r2.session_id.slice(0, 8)}... | ${r2.turn_number} | ${r2.user_prompt.slice(0, 60).replace(/\|/g, "\\|")} | ${r2.tool_call_count} | ${r2.response_tokens ?? "-"} | ${r2.created_at.split("T")[0]} |`);
27688
29215
  }
27689
- return text3(lines.join("\n"));
29216
+ return text4(lines.join("\n"));
27690
29217
  }
27691
29218
  function handleToolPatterns(args2, db) {
27692
29219
  const groupBy = args2.group_by ?? "tool";
@@ -27697,7 +29224,7 @@ function handleToolPatterns(args2, db) {
27697
29224
  groupBy
27698
29225
  });
27699
29226
  if (patterns.length === 0) {
27700
- return text3("No tool usage data recorded yet.");
29227
+ return text4("No tool usage data recorded yet.");
27701
29228
  }
27702
29229
  const lines = [`## Tool Usage Patterns (grouped by ${groupBy})`, ""];
27703
29230
  switch (groupBy) {
@@ -27727,7 +29254,7 @@ function handleToolPatterns(args2, db) {
27727
29254
  }
27728
29255
  break;
27729
29256
  }
27730
- return text3(lines.join("\n"));
29257
+ return text4(lines.join("\n"));
27731
29258
  }
27732
29259
  function handleSessionStats(args2, db) {
27733
29260
  const stats = getSessionStats(db, {
@@ -27735,7 +29262,7 @@ function handleSessionStats(args2, db) {
27735
29262
  limit: args2.limit
27736
29263
  });
27737
29264
  if (stats.length === 0) {
27738
- return text3("No session stats available.");
29265
+ return text4("No session stats available.");
27739
29266
  }
27740
29267
  const lines = ["## Session Statistics", ""];
27741
29268
  if (args2.session_id) {
@@ -27772,9 +29299,9 @@ function handleSessionStats(args2, db) {
27772
29299
  lines.push(`- Tool call details: ${dbSize.tool_call_details_count}`);
27773
29300
  lines.push(`- Observations: ${dbSize.observations_count}`);
27774
29301
  lines.push(`- Database size: ~${dbSize.estimated_size_mb} MB`);
27775
- return text3(lines.join("\n"));
29302
+ return text4(lines.join("\n"));
27776
29303
  }
27777
- function text3(content) {
29304
+ function text4(content) {
27778
29305
  return { content: [{ type: "text", text: content }] };
27779
29306
  }
27780
29307
  var OBSERVABILITY_BASE_NAMES;
@@ -27793,7 +29320,7 @@ var init_observability_tools = __esm({
27793
29320
  });
27794
29321
 
27795
29322
  // src/sentinel-db.ts
27796
- import { existsSync as existsSync53 } from "fs";
29323
+ import { existsSync as existsSync55 } from "fs";
27797
29324
  import { resolve as resolve35 } from "path";
27798
29325
  function parsePortalScope(raw) {
27799
29326
  if (!raw) return [];
@@ -28031,22 +29558,22 @@ function validateFeatures(db, domainFilter) {
28031
29558
  const missingPages = [];
28032
29559
  for (const comp of components) {
28033
29560
  const absPath = resolve35(PROJECT_ROOT, comp.component_file);
28034
- if (!existsSync53(absPath)) {
29561
+ if (!existsSync55(absPath)) {
28035
29562
  missingComponents.push(comp.component_file);
28036
29563
  }
28037
29564
  }
28038
29565
  for (const proc of procedures) {
28039
29566
  const routerPath = resolve35(PROJECT_ROOT, `src/server/api/routers/${proc.router_name}.ts`);
28040
- if (!existsSync53(routerPath)) {
29567
+ if (!existsSync55(routerPath)) {
28041
29568
  missingProcedures.push({ router: proc.router_name, procedure: proc.procedure_name });
28042
29569
  }
28043
29570
  }
28044
29571
  for (const page of pages) {
28045
29572
  const routeToPath = page.page_route.replace(/^\/(portal-[^/]+\/)?/, "src/app/").replace(/\/$/, "") + "/page.tsx";
28046
29573
  const absPath = resolve35(PROJECT_ROOT, routeToPath);
28047
- if (page.page_route.startsWith("/") && !existsSync53(absPath)) {
29574
+ if (page.page_route.startsWith("/") && !existsSync55(absPath)) {
28048
29575
  const altPath = resolve35(PROJECT_ROOT, `src/app${page.page_route}/page.tsx`);
28049
- if (!existsSync53(altPath)) {
29576
+ if (!existsSync55(altPath)) {
28050
29577
  missingPages.push(page.page_route);
28051
29578
  }
28052
29579
  }
@@ -28142,17 +29669,17 @@ var init_sentinel_db = __esm({
28142
29669
  });
28143
29670
 
28144
29671
  // src/sentinel-tools.ts
28145
- function p5(baseName) {
29672
+ function p6(baseName) {
28146
29673
  return `${getConfig().toolPrefix}_${baseName}`;
28147
29674
  }
28148
- function text4(content) {
29675
+ function text5(content) {
28149
29676
  return { content: [{ type: "text", text: content }] };
28150
29677
  }
28151
29678
  function getSentinelToolDefinitions() {
28152
29679
  return [
28153
29680
  // P2-001: sentinel_search
28154
29681
  {
28155
- name: p5("sentinel_search"),
29682
+ name: p6("sentinel_search"),
28156
29683
  description: "Search and list features in the Sentinel feature registry. Supports FTS5 full-text search and domain/status/portal filters.",
28157
29684
  inputSchema: {
28158
29685
  type: "object",
@@ -28169,7 +29696,7 @@ function getSentinelToolDefinitions() {
28169
29696
  },
28170
29697
  // P2-002: sentinel_detail
28171
29698
  {
28172
- name: p5("sentinel_detail"),
29699
+ name: p6("sentinel_detail"),
28173
29700
  description: "Get full feature details including all linked components, procedures, pages, dependencies, and changelog.",
28174
29701
  inputSchema: {
28175
29702
  type: "object",
@@ -28182,7 +29709,7 @@ function getSentinelToolDefinitions() {
28182
29709
  },
28183
29710
  // P2-003: sentinel_impact
28184
29711
  {
28185
- name: p5("sentinel_impact"),
29712
+ name: p6("sentinel_impact"),
28186
29713
  description: "Pre-deletion impact analysis. Shows which features would be orphaned, degraded, or unaffected if specified files are deleted. BLOCKS if critical features would be orphaned.",
28187
29714
  inputSchema: {
28188
29715
  type: "object",
@@ -28198,7 +29725,7 @@ function getSentinelToolDefinitions() {
28198
29725
  },
28199
29726
  // P2-004: sentinel_validate
28200
29727
  {
28201
- name: p5("sentinel_validate"),
29728
+ name: p6("sentinel_validate"),
28202
29729
  description: "Validate all active features have living implementation files. Reports alive, orphaned, and degraded features.",
28203
29730
  inputSchema: {
28204
29731
  type: "object",
@@ -28211,7 +29738,7 @@ function getSentinelToolDefinitions() {
28211
29738
  },
28212
29739
  // P2-005: sentinel_register
28213
29740
  {
28214
- name: p5("sentinel_register"),
29741
+ name: p6("sentinel_register"),
28215
29742
  description: "Register or update a feature in the sentinel registry. Links components, procedures, and pages.",
28216
29743
  inputSchema: {
28217
29744
  type: "object",
@@ -28268,7 +29795,7 @@ function getSentinelToolDefinitions() {
28268
29795
  },
28269
29796
  // P2-006: sentinel_parity
28270
29797
  {
28271
- name: p5("sentinel_parity"),
29798
+ name: p6("sentinel_parity"),
28272
29799
  description: "Compare two sets of files for feature parity. Shows features in old but not new (GAPs), features in both (DONE), and features only in new (NEW).",
28273
29800
  inputSchema: {
28274
29801
  type: "object",
@@ -28306,7 +29833,7 @@ function handleSentinelToolCall(name, args2, dataDb) {
28306
29833
  case "sentinel_parity":
28307
29834
  return handleParityCheck(args2, dataDb);
28308
29835
  default:
28309
- return text4(`Unknown sentinel tool: ${name}`);
29836
+ return text5(`Unknown sentinel tool: ${name}`);
28310
29837
  }
28311
29838
  }
28312
29839
  function handleSearch2(args2, db) {
@@ -28329,7 +29856,7 @@ function handleSearch2(args2, db) {
28329
29856
  lines.push("");
28330
29857
  if (results.length === 0) {
28331
29858
  lines.push("No features found matching criteria.");
28332
- return text4(lines.join("\n"));
29859
+ return text5(lines.join("\n"));
28333
29860
  }
28334
29861
  let currentDomain = "";
28335
29862
  for (const f2 of results) {
@@ -28341,17 +29868,17 @@ function handleSearch2(args2, db) {
28341
29868
  const priority = f2.priority === "critical" ? " [CRITICAL]" : "";
28342
29869
  lines.push(`- **${f2.feature_key}** ${sub}${priority}: ${f2.title} (${f2.component_count}C/${f2.procedure_count}P/${f2.page_count}R) [${f2.status}]`);
28343
29870
  }
28344
- return text4(lines.join("\n"));
29871
+ return text5(lines.join("\n"));
28345
29872
  }
28346
29873
  function handleDetail2(args2, db) {
28347
29874
  const key = args2.feature_key;
28348
29875
  const id = args2.feature_id;
28349
29876
  if (!key && !id) {
28350
- return text4("Error: Provide either feature_key or feature_id");
29877
+ return text5("Error: Provide either feature_key or feature_id");
28351
29878
  }
28352
29879
  const detail = getFeatureDetail(db, id || key);
28353
29880
  if (!detail) {
28354
- return text4(`Feature not found: ${key || id}`);
29881
+ return text5(`Feature not found: ${key || id}`);
28355
29882
  }
28356
29883
  const lines = [];
28357
29884
  lines.push(`## Feature: ${detail.feature_key}`);
@@ -28397,12 +29924,12 @@ function handleDetail2(args2, db) {
28397
29924
  lines.push(`- [${c2.created_at}] ${c2.change_type}: ${c2.change_detail || "(no detail)"}${c2.commit_hash ? " @" + c2.commit_hash.substring(0, 8) : ""}`);
28398
29925
  }
28399
29926
  }
28400
- return text4(lines.join("\n"));
29927
+ return text5(lines.join("\n"));
28401
29928
  }
28402
29929
  function handleImpact(args2, db) {
28403
29930
  const files = args2.files;
28404
29931
  if (!files || files.length === 0) {
28405
- return text4("Error: Provide files array");
29932
+ return text5("Error: Provide files array");
28406
29933
  }
28407
29934
  const report = getFeatureImpact(db, files);
28408
29935
  const lines = [];
@@ -28438,7 +29965,7 @@ function handleImpact(args2, db) {
28438
29965
  }
28439
29966
  lines.push("");
28440
29967
  }
28441
- return text4(lines.join("\n"));
29968
+ return text5(lines.join("\n"));
28442
29969
  }
28443
29970
  function handleValidate(args2, db) {
28444
29971
  const domain = args2.domain;
@@ -28483,14 +30010,14 @@ function handleValidate(args2, db) {
28483
30010
  }
28484
30011
  const result = report.orphaned === 0 ? "PASS" : "FAIL";
28485
30012
  lines.push(`### RESULT: ${result}`);
28486
- return text4(lines.join("\n"));
30013
+ return text5(lines.join("\n"));
28487
30014
  }
28488
30015
  function handleRegister(args2, db) {
28489
30016
  const featureKey = args2.feature_key;
28490
30017
  const domain = args2.domain;
28491
30018
  const title = args2.title;
28492
30019
  if (!featureKey || !domain || !title) {
28493
- return text4("Error: feature_key, domain, and title are required");
30020
+ return text5("Error: feature_key, domain, and title are required");
28494
30021
  }
28495
30022
  const featureId = upsertFeature(db, {
28496
30023
  feature_key: featureKey,
@@ -28520,15 +30047,15 @@ function handleRegister(args2, db) {
28520
30047
  linkPage(db, featureId, p19.route, p19.portal);
28521
30048
  }
28522
30049
  }
28523
- logChange(db, featureId, "created", `Registered via ${p5("sentinel_register")}`);
28524
- return text4(`Feature registered: ${featureKey} (ID: ${featureId})
30050
+ logChange(db, featureId, "created", `Registered via ${p6("sentinel_register")}`);
30051
+ return text5(`Feature registered: ${featureKey} (ID: ${featureId})
28525
30052
  Components: ${components?.length || 0}, Procedures: ${procedures?.length || 0}, Pages: ${pages?.length || 0}`);
28526
30053
  }
28527
30054
  function handleParityCheck(args2, db) {
28528
30055
  const oldFiles = args2.old_files;
28529
30056
  const newFiles = args2.new_files;
28530
30057
  if (!oldFiles || !newFiles) {
28531
- return text4("Error: Provide both old_files and new_files arrays");
30058
+ return text5("Error: Provide both old_files and new_files arrays");
28532
30059
  }
28533
30060
  const report = checkParity(db, oldFiles, newFiles);
28534
30061
  const lines = [];
@@ -28560,7 +30087,7 @@ function handleParityCheck(args2, db) {
28560
30087
  }
28561
30088
  const result = report.gaps.length === 0 ? "PASS" : `FAIL (${report.gaps.length} gaps)`;
28562
30089
  lines.push(`### RESULT: ${result}`);
28563
- return text4(lines.join("\n"));
30090
+ return text5(lines.join("\n"));
28564
30091
  }
28565
30092
  var init_sentinel_tools = __esm({
28566
30093
  "src/sentinel-tools.ts"() {
@@ -28572,8 +30099,8 @@ var init_sentinel_tools = __esm({
28572
30099
  });
28573
30100
 
28574
30101
  // src/sentinel-scanner.ts
28575
- import { readFileSync as readFileSync45, existsSync as existsSync54, readdirSync as readdirSync26, statSync as statSync19 } from "fs";
28576
- import { resolve as resolve36, join as join32, basename as basename10, dirname as dirname24, relative as relative12 } from "path";
30102
+ import { readFileSync as readFileSync47, existsSync as existsSync56, readdirSync as readdirSync26, statSync as statSync19 } from "fs";
30103
+ import { resolve as resolve36, join as join34, basename as basename10, dirname as dirname25, relative as relative13 } from "path";
28577
30104
  function inferDomain(filePath) {
28578
30105
  const domains = getConfig().domains;
28579
30106
  const path = filePath.toLowerCase();
@@ -28705,7 +30232,7 @@ function scanComponentExports(dataDb) {
28705
30232
  const componentsBase = config.paths.components ?? config.paths.source + "/components";
28706
30233
  const componentDirs = [];
28707
30234
  const basePath = resolve36(projectRoot, componentsBase);
28708
- if (existsSync54(basePath)) {
30235
+ if (existsSync56(basePath)) {
28709
30236
  try {
28710
30237
  const entries = readdirSync26(basePath, { withFileTypes: true });
28711
30238
  for (const entry of entries) {
@@ -28718,11 +30245,11 @@ function scanComponentExports(dataDb) {
28718
30245
  }
28719
30246
  for (const dir of componentDirs) {
28720
30247
  const absDir = resolve36(projectRoot, dir);
28721
- if (!existsSync54(absDir)) continue;
30248
+ if (!existsSync56(absDir)) continue;
28722
30249
  const files = walkDir2(absDir).filter((f2) => f2.endsWith(".tsx") || f2.endsWith(".ts"));
28723
30250
  for (const file of files) {
28724
- const relPath = relative12(projectRoot, file);
28725
- const source = readFileSync45(file, "utf-8");
30251
+ const relPath = relative13(projectRoot, file);
30252
+ const source = readFileSync47(file, "utf-8");
28726
30253
  const annotations = parseFeatureAnnotations(source);
28727
30254
  if (annotations.length > 0) {
28728
30255
  for (const ann of annotations) {
@@ -28748,7 +30275,7 @@ function scanComponentExports(dataDb) {
28748
30275
  if (hasHandlers && exportMatch) {
28749
30276
  const componentName = exportMatch[1];
28750
30277
  const domain = inferDomain(relPath);
28751
- const subdomain = basename10(dirname24(relPath));
30278
+ const subdomain = basename10(dirname25(relPath));
28752
30279
  const featureKey = `component.${subdomain}.${componentName.replace(/([A-Z])/g, "-$1").toLowerCase().replace(/^-/, "")}`;
28753
30280
  if (!annotations.some((a2) => a2.featureKey === featureKey)) {
28754
30281
  features.push({
@@ -28773,7 +30300,7 @@ function walkDir2(dir) {
28773
30300
  try {
28774
30301
  const entries = readdirSync26(dir);
28775
30302
  for (const entry of entries) {
28776
- const fullPath = join32(dir, entry);
30303
+ const fullPath = join34(dir, entry);
28777
30304
  try {
28778
30305
  const stat = statSync19(fullPath);
28779
30306
  if (stat.isDirectory()) {
@@ -28844,7 +30371,7 @@ var init_sentinel_scanner = __esm({
28844
30371
  });
28845
30372
 
28846
30373
  // src/analytics.ts
28847
- function p6(baseName) {
30374
+ function p7(baseName) {
28848
30375
  return `${getConfig().toolPrefix}_${baseName}`;
28849
30376
  }
28850
30377
  function getWeights() {
@@ -28912,7 +30439,7 @@ function backfillQualityScores(db) {
28912
30439
  function getAnalyticsToolDefinitions() {
28913
30440
  return [
28914
30441
  {
28915
- name: p6("quality_score"),
30442
+ name: p7("quality_score"),
28916
30443
  description: "Calculate and store quality score for a session based on observations. Shows breakdown by category.",
28917
30444
  inputSchema: {
28918
30445
  type: "object",
@@ -28923,7 +30450,7 @@ function getAnalyticsToolDefinitions() {
28923
30450
  }
28924
30451
  },
28925
30452
  {
28926
- name: p6("quality_trend"),
30453
+ name: p7("quality_trend"),
28927
30454
  description: "Quality trend over recent sessions. Shows score progression and identifies improving/declining areas.",
28928
30455
  inputSchema: {
28929
30456
  type: "object",
@@ -28934,7 +30461,7 @@ function getAnalyticsToolDefinitions() {
28934
30461
  }
28935
30462
  },
28936
30463
  {
28937
- name: p6("quality_report"),
30464
+ name: p7("quality_report"),
28938
30465
  description: "Comprehensive quality report with averages, trends, and recommendations.",
28939
30466
  inputSchema: {
28940
30467
  type: "object",
@@ -28963,17 +30490,17 @@ function handleAnalyticsToolCall(name, args2, memoryDb) {
28963
30490
  case "quality_report":
28964
30491
  return handleQualityReport(args2, memoryDb);
28965
30492
  default:
28966
- return text5(`Unknown analytics tool: ${name}`);
30493
+ return text6(`Unknown analytics tool: ${name}`);
28967
30494
  }
28968
30495
  } catch (error) {
28969
- return text5(`Error in ${name}: ${error instanceof Error ? error.message : String(error)}
30496
+ return text6(`Error in ${name}: ${error instanceof Error ? error.message : String(error)}
28970
30497
 
28971
- Usage: ${p6("quality_score")} { session_id: "..." }, ${p6("quality_trend")} { days: 30 }`);
30498
+ Usage: ${p7("quality_score")} { session_id: "..." }, ${p7("quality_trend")} { days: 30 }`);
28972
30499
  }
28973
30500
  }
28974
30501
  function handleQualityScore(args2, db) {
28975
30502
  const sessionId = args2.session_id;
28976
- if (!sessionId) return text5(`Usage: ${p6("quality_score")} { session_id: "abc123" } - Calculate quality score for a specific session.`);
30503
+ if (!sessionId) return text6(`Usage: ${p7("quality_score")} { session_id: "abc123" } - Calculate quality score for a specific session.`);
28977
30504
  const { score, breakdown } = calculateQualityScore(db, sessionId);
28978
30505
  storeQualityScore(db, sessionId, score, breakdown);
28979
30506
  const categories = getCategories();
@@ -28989,7 +30516,7 @@ function handleQualityScore(args2, db) {
28989
30516
  const indicator = impact > 0 ? "+" : impact < 0 ? "" : " ";
28990
30517
  lines.push(`| ${category} | ${indicator}${impact} |`);
28991
30518
  }
28992
- return text5(lines.join("\n"));
30519
+ return text6(lines.join("\n"));
28993
30520
  }
28994
30521
  function handleQualityTrend(args2, db, retried = false) {
28995
30522
  const days = args2.days ?? 30;
@@ -29007,7 +30534,7 @@ function handleQualityTrend(args2, db, retried = false) {
29007
30534
  return handleQualityTrend(args2, db, true);
29008
30535
  }
29009
30536
  }
29010
- return text5(`No quality scores found in the last ${days} days. Quality scores are calculated automatically from session observations (bugfixes, VR checks, incidents). Try: ${p6("quality_score")} { session_id: "..." } to score a specific session, or try a longer time range with { days: 90 }.`);
30537
+ return text6(`No quality scores found in the last ${days} days. Quality scores are calculated automatically from session observations (bugfixes, VR checks, incidents). Try: ${p7("quality_score")} { session_id: "..." } to score a specific session, or try a longer time range with { days: 90 }.`);
29011
30538
  }
29012
30539
  const avg = scores.reduce((sum, s) => sum + s.score, 0) / scores.length;
29013
30540
  const recent = scores.slice(-5);
@@ -29026,7 +30553,7 @@ function handleQualityTrend(args2, db, retried = false) {
29026
30553
  for (const s of scores.slice(-10)) {
29027
30554
  lines.push(`| ${s.session_id.slice(0, 8)}... | ${s.score} | ${s.created_at} |`);
29028
30555
  }
29029
- return text5(lines.join("\n"));
30556
+ return text6(lines.join("\n"));
29030
30557
  }
29031
30558
  function handleQualityReport(args2, db, retried = false) {
29032
30559
  const days = args2.days ?? 30;
@@ -29043,7 +30570,7 @@ function handleQualityReport(args2, db, retried = false) {
29043
30570
  return handleQualityReport(args2, db, true);
29044
30571
  }
29045
30572
  }
29046
- return text5(`No quality data available for the last ${days} days. Quality scores are calculated from session observations (bugfixes, VR checks, incidents). Try: ${p6("quality_score")} { session_id: "..." } to score individual sessions first, or try a longer time range with { days: 90 }.`);
30573
+ return text6(`No quality data available for the last ${days} days. Quality scores are calculated from session observations (bugfixes, VR checks, incidents). Try: ${p7("quality_score")} { session_id: "..." } to score individual sessions first, or try a longer time range with { days: 90 }.`);
29047
30574
  }
29048
30575
  const avg = scores.reduce((sum, s) => sum + s.score, 0) / scores.length;
29049
30576
  const max = Math.max(...scores.map((s) => s.score));
@@ -29100,9 +30627,9 @@ function handleQualityReport(args2, db, retried = false) {
29100
30627
  if (avg >= 70) {
29101
30628
  lines.push("- Quality is good. Maintain current practices and focus on consistency.");
29102
30629
  }
29103
- return text5(lines.join("\n"));
30630
+ return text6(lines.join("\n"));
29104
30631
  }
29105
- function text5(content) {
30632
+ function text6(content) {
29106
30633
  return { content: [{ type: "text", text: content }] };
29107
30634
  }
29108
30635
  var DEFAULT_WEIGHTS, DEFAULT_CATEGORIES, ANALYTICS_BASE_NAMES;
@@ -29125,7 +30652,7 @@ var init_analytics = __esm({
29125
30652
  });
29126
30653
 
29127
30654
  // src/cost-tracker.ts
29128
- function p7(baseName) {
30655
+ function p8(baseName) {
29129
30656
  return `${getConfig().toolPrefix}_${baseName}`;
29130
30657
  }
29131
30658
  function getModelPricing() {
@@ -29134,7 +30661,7 @@ function getModelPricing() {
29134
30661
  function getCostToolDefinitions() {
29135
30662
  return [
29136
30663
  {
29137
- name: p7("cost_session"),
30664
+ name: p8("cost_session"),
29138
30665
  description: "Show cost breakdown for a session. Includes token counts, model pricing, and cost by category.",
29139
30666
  inputSchema: {
29140
30667
  type: "object",
@@ -29145,7 +30672,7 @@ function getCostToolDefinitions() {
29145
30672
  }
29146
30673
  },
29147
30674
  {
29148
- name: p7("cost_trend"),
30675
+ name: p8("cost_trend"),
29149
30676
  description: "Cost trend over time. Shows daily/weekly spending and identifies cost drivers.",
29150
30677
  inputSchema: {
29151
30678
  type: "object",
@@ -29157,7 +30684,7 @@ function getCostToolDefinitions() {
29157
30684
  }
29158
30685
  },
29159
30686
  {
29160
- name: p7("cost_feature"),
30687
+ name: p8("cost_feature"),
29161
30688
  description: "Cost attribution by feature. Shows which features consume the most tokens.",
29162
30689
  inputSchema: {
29163
30690
  type: "object",
@@ -29186,22 +30713,22 @@ function handleCostToolCall(name, args2, memoryDb) {
29186
30713
  case "cost_feature":
29187
30714
  return handleCostFeature(args2, memoryDb);
29188
30715
  default:
29189
- return text6(`Unknown cost tool: ${name}`);
30716
+ return text7(`Unknown cost tool: ${name}`);
29190
30717
  }
29191
30718
  } catch (error) {
29192
- return text6(`Error in ${name}: ${error instanceof Error ? error.message : String(error)}
30719
+ return text7(`Error in ${name}: ${error instanceof Error ? error.message : String(error)}
29193
30720
 
29194
- Usage: ${p7("cost_session")} { session_id: "..." }, ${p7("cost_trend")} { days: 30 }`);
30721
+ Usage: ${p8("cost_session")} { session_id: "..." }, ${p8("cost_trend")} { days: 30 }`);
29195
30722
  }
29196
30723
  }
29197
30724
  function handleCostSession(args2, db) {
29198
30725
  const sessionId = args2.session_id;
29199
- if (!sessionId) return text6(`Usage: ${p7("cost_session")} { session_id: "abc123" } - Show cost breakdown for a specific session.`);
30726
+ if (!sessionId) return text7(`Usage: ${p8("cost_session")} { session_id: "abc123" } - Show cost breakdown for a specific session.`);
29200
30727
  const cost = db.prepare(
29201
30728
  "SELECT * FROM session_costs WHERE session_id = ? ORDER BY created_at DESC LIMIT 1"
29202
30729
  ).get(sessionId);
29203
30730
  if (!cost) {
29204
- return text6(`No cost data found for session ${sessionId}. Cost data is recorded automatically when sessions end via the session-end hook. If the session is still active, data will appear after it completes. Try: ${p7("cost_trend")} { days: 30 } to see aggregate cost data instead.`);
30731
+ return text7(`No cost data found for session ${sessionId}. Cost data is recorded automatically when sessions end via the session-end hook. If the session is still active, data will appear after it completes. Try: ${p8("cost_trend")} { days: 30 } to see aggregate cost data instead.`);
29205
30732
  }
29206
30733
  const modelPricing = getModelPricing();
29207
30734
  const mp = modelPricing[cost.model] ?? modelPricing["claude-sonnet-4-5"] ?? { input_per_million: 3, output_per_million: 15 };
@@ -29222,13 +30749,13 @@ function handleCostSession(args2, db) {
29222
30749
  `| Cache Write | ${cost.cache_write_tokens.toLocaleString()} | $${cacheWriteCost.toFixed(4)} |`,
29223
30750
  `| **Total** | ${cost.total_tokens.toLocaleString()} | **$${cost.estimated_cost_usd.toFixed(4)}** |`
29224
30751
  ];
29225
- return text6(lines.join("\n"));
30752
+ return text7(lines.join("\n"));
29226
30753
  }
29227
30754
  function handleCostTrend(args2, db) {
29228
30755
  const days = args2.days ?? 30;
29229
30756
  const groupBy = args2.group_by ?? "day";
29230
30757
  if (!["day", "week"].includes(groupBy)) {
29231
- return text6(`Invalid group_by value: "${groupBy}". Use "day" or "week". Example: ${p7("cost_trend")} { days: 30, group_by: "week" }`);
30758
+ return text7(`Invalid group_by value: "${groupBy}". Use "day" or "week". Example: ${p8("cost_trend")} { days: 30, group_by: "week" }`);
29232
30759
  }
29233
30760
  const sql = groupBy === "week" ? `SELECT strftime('%Y-W%W', created_at) as period,
29234
30761
  COUNT(*) as sessions,
@@ -29249,7 +30776,7 @@ function handleCostTrend(args2, db) {
29249
30776
  ORDER BY period ASC`;
29250
30777
  const rows = db.prepare(sql).all(`-${days} days`);
29251
30778
  if (rows.length === 0) {
29252
- return text6(`No cost data found in the last ${days} days. Cost tracking records token usage automatically at session end via hooks. Ensure session-end hooks are configured in your settings. Try: ${p7("cost_session")} { session_id: "..." } to check a specific session.`);
30779
+ return text7(`No cost data found in the last ${days} days. Cost tracking records token usage automatically at session end via hooks. Ensure session-end hooks are configured in your settings. Try: ${p8("cost_session")} { session_id: "..." } to check a specific session.`);
29253
30780
  }
29254
30781
  const totalCost = rows.reduce((sum, r2) => sum + r2.total_cost, 0);
29255
30782
  const totalSessions = rows.reduce((sum, r2) => sum + r2.sessions, 0);
@@ -29267,7 +30794,7 @@ function handleCostTrend(args2, db) {
29267
30794
  `| ${row.period} | ${row.sessions} | $${row.total_cost.toFixed(2)} | $${row.avg_cost.toFixed(4)} |`
29268
30795
  );
29269
30796
  }
29270
- return text6(lines.join("\n"));
30797
+ return text7(lines.join("\n"));
29271
30798
  }
29272
30799
  function handleCostFeature(args2, db) {
29273
30800
  const days = args2.days ?? 30;
@@ -29279,7 +30806,7 @@ function handleCostFeature(args2, db) {
29279
30806
  ORDER BY total_cost DESC
29280
30807
  `).all(`-${days} days`);
29281
30808
  if (rows.length === 0) {
29282
- return text6(`No feature cost data found in the last ${days} days. Feature costs are attributed automatically when sessions work on registered features. Ensure features are registered, then costs are tracked per-session. Try: ${p7("cost_trend")} { days: 30 } for session-level cost trends instead.`);
30809
+ return text7(`No feature cost data found in the last ${days} days. Feature costs are attributed automatically when sessions work on registered features. Ensure features are registered, then costs are tracked per-session. Try: ${p8("cost_trend")} { days: 30 } for session-level cost trends instead.`);
29283
30810
  }
29284
30811
  const totalCost = rows.reduce((sum, r2) => sum + r2.total_cost, 0);
29285
30812
  const lines = [
@@ -29295,9 +30822,9 @@ function handleCostFeature(args2, db) {
29295
30822
  `| ${row.feature_key} | $${row.total_cost.toFixed(2)} | ${row.total_tokens.toLocaleString()} | ${row.entries} | ${pct}% |`
29296
30823
  );
29297
30824
  }
29298
- return text6(lines.join("\n"));
30825
+ return text7(lines.join("\n"));
29299
30826
  }
29300
- function text6(content) {
30827
+ function text7(content) {
29301
30828
  return { content: [{ type: "text", text: content }] };
29302
30829
  }
29303
30830
  var DEFAULT_MODEL_PRICING, COST_BASE_NAMES;
@@ -29317,7 +30844,7 @@ var init_cost_tracker = __esm({
29317
30844
  });
29318
30845
 
29319
30846
  // src/prompt-analyzer.ts
29320
- function p8(baseName) {
30847
+ function p9(baseName) {
29321
30848
  return `${getConfig().toolPrefix}_${baseName}`;
29322
30849
  }
29323
30850
  function categorizePrompt(promptText) {
@@ -29332,7 +30859,7 @@ function categorizePrompt(promptText) {
29332
30859
  function getPromptToolDefinitions() {
29333
30860
  return [
29334
30861
  {
29335
- name: p8("prompt_effectiveness"),
30862
+ name: p9("prompt_effectiveness"),
29336
30863
  description: "Prompt effectiveness statistics by category. Shows success rates, average corrections needed, and best-performing prompt patterns.",
29337
30864
  inputSchema: {
29338
30865
  type: "object",
@@ -29347,7 +30874,7 @@ function getPromptToolDefinitions() {
29347
30874
  }
29348
30875
  },
29349
30876
  {
29350
- name: p8("prompt_suggestions"),
30877
+ name: p9("prompt_suggestions"),
29351
30878
  description: "Suggest improvements for a prompt based on past outcomes. Finds similar prompts ranked by success rate.",
29352
30879
  inputSchema: {
29353
30880
  type: "object",
@@ -29374,12 +30901,12 @@ function handlePromptToolCall(name, args2, memoryDb) {
29374
30901
  case "prompt_suggestions":
29375
30902
  return handleSuggestions(args2, memoryDb);
29376
30903
  default:
29377
- return text7(`Unknown prompt tool: ${name}`);
30904
+ return text8(`Unknown prompt tool: ${name}`);
29378
30905
  }
29379
30906
  } catch (error) {
29380
- return text7(`Error in ${name}: ${error instanceof Error ? error.message : String(error)}
30907
+ return text8(`Error in ${name}: ${error instanceof Error ? error.message : String(error)}
29381
30908
 
29382
- Usage: ${p8("prompt_effectiveness")} { days: 30 }, ${p8("prompt_suggestions")} { prompt: "..." }`);
30909
+ Usage: ${p9("prompt_effectiveness")} { days: 30 }, ${p9("prompt_suggestions")} { prompt: "..." }`);
29383
30910
  }
29384
30911
  }
29385
30912
  function handleEffectiveness(args2, db) {
@@ -29405,7 +30932,7 @@ function handleEffectiveness(args2, db) {
29405
30932
  sql += " GROUP BY prompt_category ORDER BY total DESC";
29406
30933
  const rows = db.prepare(sql).all(...params);
29407
30934
  if (rows.length === 0) {
29408
- return text7(`No prompt outcomes found in the last ${days} days. Prompt analysis runs automatically at session end. Try a longer time range: ${p8("prompt_effectiveness")} { days: 90 }, or use ${p8("prompt_suggestions")} { prompt: "your text" } to analyze a prompt directly.`);
30935
+ return text8(`No prompt outcomes found in the last ${days} days. Prompt analysis runs automatically at session end. Try a longer time range: ${p9("prompt_effectiveness")} { days: 90 }, or use ${p9("prompt_suggestions")} { prompt: "your text" } to analyze a prompt directly.`);
29409
30936
  }
29410
30937
  const lines = [
29411
30938
  `## Prompt Effectiveness (${days} days)`,
@@ -29420,11 +30947,11 @@ function handleEffectiveness(args2, db) {
29420
30947
  `| ${row.prompt_category} | ${total} | ${successRate}% | ${row.partials} | ${row.failures} | ${row.abandoned} | ${row.avg_corrections.toFixed(1)} | ${Math.round(row.avg_word_count)} |`
29421
30948
  );
29422
30949
  }
29423
- return text7(lines.join("\n"));
30950
+ return text8(lines.join("\n"));
29424
30951
  }
29425
30952
  function handleSuggestions(args2, db) {
29426
30953
  const prompt = args2.prompt;
29427
- if (!prompt) return text7(`Usage: ${p8("prompt_suggestions")} { prompt: "your prompt text here" } - Analyzes a prompt and suggests improvements based on past outcomes.`);
30954
+ if (!prompt) return text8(`Usage: ${p9("prompt_suggestions")} { prompt: "your prompt text here" } - Analyzes a prompt and suggests improvements based on past outcomes.`);
29428
30955
  const category = categorizePrompt(prompt);
29429
30956
  const wordCount = prompt.split(/\s+/).length;
29430
30957
  const similar = db.prepare(`
@@ -29467,9 +30994,9 @@ function handleSuggestions(args2, db) {
29467
30994
  lines.push(`- Success rate: ${Math.round(stats.successes / stats.total * 100)}%`);
29468
30995
  lines.push(`- Avg corrections needed: ${stats.avg_corrections.toFixed(1)}`);
29469
30996
  }
29470
- return text7(lines.join("\n"));
30997
+ return text8(lines.join("\n"));
29471
30998
  }
29472
- function text7(content) {
30999
+ function text8(content) {
29473
31000
  return { content: [{ type: "text", text: content }] };
29474
31001
  }
29475
31002
  var PROMPT_BASE_NAMES;
@@ -29482,290 +31009,8 @@ var init_prompt_analyzer = __esm({
29482
31009
  }
29483
31010
  });
29484
31011
 
29485
- // src/audit-trail.ts
29486
- function p9(baseName) {
29487
- return `${getConfig().toolPrefix}_${baseName}`;
29488
- }
29489
- function getAuditFormats() {
29490
- return getConfig().governance?.audit?.formats ?? DEFAULT_FORMATS;
29491
- }
29492
- function queryAuditLog(db, options) {
29493
- let sql = "SELECT * FROM audit_log WHERE 1=1";
29494
- const params = [];
29495
- if (options.eventType) {
29496
- sql += " AND event_type = ?";
29497
- params.push(options.eventType);
29498
- }
29499
- if (options.actor) {
29500
- sql += " AND actor = ?";
29501
- params.push(options.actor);
29502
- }
29503
- if (options.days) {
29504
- sql += " AND timestamp >= datetime('now', ?)";
29505
- params.push(`-${options.days} days`);
29506
- }
29507
- if (options.filePath) {
29508
- sql += " AND file_path = ?";
29509
- params.push(options.filePath);
29510
- }
29511
- if (options.changeType) {
29512
- sql += " AND change_type = ?";
29513
- params.push(options.changeType);
29514
- }
29515
- sql += " ORDER BY timestamp DESC";
29516
- if (options.limit) {
29517
- sql += " LIMIT ?";
29518
- params.push(options.limit);
29519
- }
29520
- return db.prepare(sql).all(...params);
29521
- }
29522
- function getFileChain(db, filePath) {
29523
- return db.prepare(
29524
- "SELECT * FROM audit_log WHERE file_path = ? ORDER BY timestamp ASC LIMIT 10000"
29525
- ).all(filePath);
29526
- }
29527
- function getAuditToolDefinitions() {
29528
- return [
29529
- {
29530
- name: p9("audit_log"),
29531
- description: "Query the audit log. Filter by event type, actor, file, or time range.",
29532
- inputSchema: {
29533
- type: "object",
29534
- properties: {
29535
- event_type: { type: "string", description: "Filter by event type: code_change, rule_enforced, approval, review, commit, compaction" },
29536
- actor: { type: "string", description: "Filter by actor: ai, human, hook, agent" },
29537
- file_path: { type: "string", description: "Filter by file path" },
29538
- change_type: { type: "string", description: "Filter by change type: create, edit, delete" },
29539
- days: { type: "number", description: "Days to look back (default: 30)" },
29540
- limit: { type: "number", description: "Max results (default: 50)" }
29541
- },
29542
- required: []
29543
- }
29544
- },
29545
- {
29546
- name: p9("audit_report"),
29547
- description: "Generate an audit report in a specified format (summary, detailed, soc2).",
29548
- inputSchema: {
29549
- type: "object",
29550
- properties: {
29551
- format: { type: "string", description: "Report format: summary, detailed, soc2 (default: summary)" },
29552
- days: { type: "number", description: "Days to cover (default: 30)" }
29553
- },
29554
- required: []
29555
- }
29556
- },
29557
- {
29558
- name: p9("audit_chain"),
29559
- description: "Get the complete audit trail for a specific file. Shows all changes, reviews, and decisions chronologically.",
29560
- inputSchema: {
29561
- type: "object",
29562
- properties: {
29563
- file: { type: "string", description: "File path to trace" }
29564
- },
29565
- required: ["file"]
29566
- }
29567
- }
29568
- ];
29569
- }
29570
- function isAuditTool(name) {
29571
- const pfx = getConfig().toolPrefix + "_";
29572
- const baseName = name.startsWith(pfx) ? name.slice(pfx.length) : name;
29573
- return AUDIT_BASE_NAMES.has(baseName);
29574
- }
29575
- function handleAuditToolCall(name, args2, memoryDb) {
29576
- try {
29577
- const pfx = getConfig().toolPrefix + "_";
29578
- const baseName = name.startsWith(pfx) ? name.slice(pfx.length) : name;
29579
- switch (baseName) {
29580
- case "audit_log":
29581
- return handleAuditLog(args2, memoryDb);
29582
- case "audit_report":
29583
- return handleAuditReport(args2, memoryDb);
29584
- case "audit_chain":
29585
- return handleAuditChain(args2, memoryDb);
29586
- default:
29587
- return text8(`Unknown audit tool: ${name}`);
29588
- }
29589
- } catch (error) {
29590
- return text8(`Error in ${name}: ${error instanceof Error ? error.message : String(error)}
29591
-
29592
- Usage: ${p9("audit_log")} { severity: "critical" }, ${p9("audit_report")} { format: "soc2" }`);
29593
- }
29594
- }
29595
- function handleAuditLog(args2, db) {
29596
- const entries = queryAuditLog(db, {
29597
- eventType: args2.event_type,
29598
- actor: args2.actor,
29599
- filePath: args2.file_path,
29600
- changeType: args2.change_type,
29601
- days: args2.days ?? 30,
29602
- limit: args2.limit ?? 50
29603
- });
29604
- if (entries.length === 0) {
29605
- return text8(`No audit log entries found matching the filters. Audit entries are recorded automatically during code changes, rule checks, and commits. Try broadening your search: ${p9("audit_log")} { days: 90 } for a longer time range, or ${p9("audit_log")} {} with no filters.`);
29606
- }
29607
- const lines = [
29608
- `## Audit Log (${entries.length} entries)`,
29609
- "",
29610
- "| Timestamp | Event | Actor | Change | File | Evidence |",
29611
- "|-----------|-------|-------|--------|------|----------|"
29612
- ];
29613
- for (const entry of entries) {
29614
- const file = entry.file_path ? entry.file_path.split("/").pop() : "-";
29615
- const evidence = entry.evidence ? entry.evidence.slice(0, 50) : "-";
29616
- lines.push(
29617
- `| ${entry.timestamp.slice(0, 16)} | ${entry.event_type} | ${entry.actor} | ${entry.change_type ?? "-"} | ${file} | ${evidence} |`
29618
- );
29619
- }
29620
- return text8(lines.join("\n"));
29621
- }
29622
- function handleAuditReport(args2, db) {
29623
- const format = args2.format ?? "summary";
29624
- const days = args2.days ?? 30;
29625
- const availableFormats = getAuditFormats();
29626
- if (!availableFormats.includes(format)) {
29627
- return text8(`Unknown report format: "${format}". Available formats: ${availableFormats.join(", ")}. Example: ${p9("audit_report")} { format: "soc2", days: 30 }. Configure additional formats via the governance.audit.formats config key.`);
29628
- }
29629
- const entries = queryAuditLog(db, { days });
29630
- if (entries.length === 0) {
29631
- return text8(`No audit entries found in the last ${days} days. Audit entries are recorded automatically during code changes, reviews, and commits. Try: ${p9("audit_log")} { days: 90 } for a longer time range.`);
29632
- }
29633
- const byEventType = {};
29634
- const byActor = {};
29635
- for (const e2 of entries) {
29636
- const et = e2.event_type;
29637
- byEventType[et] = (byEventType[et] ?? 0) + 1;
29638
- const actor = e2.actor;
29639
- byActor[actor] = (byActor[actor] ?? 0) + 1;
29640
- }
29641
- if (format === "soc2") {
29642
- return generateSoc2Report(entries, byEventType, byActor, days);
29643
- }
29644
- if (format === "detailed") {
29645
- return generateDetailedReport(entries, byEventType, byActor, days);
29646
- }
29647
- const lines = [
29648
- `## Audit Summary Report (${days} days)`,
29649
- `Generated: ${(/* @__PURE__ */ new Date()).toISOString().slice(0, 16)}`,
29650
- "",
29651
- "### By Event Type",
29652
- `| Event Type | Count |`,
29653
- `|------------|-------|`
29654
- ];
29655
- for (const [et, count] of Object.entries(byEventType).sort((a2, b2) => b2[1] - a2[1])) {
29656
- lines.push(`| ${et} | ${count} |`);
29657
- }
29658
- lines.push(`| **Total** | **${entries.length}** |`);
29659
- lines.push("");
29660
- lines.push("### By Actor");
29661
- lines.push(`| Actor | Count |`);
29662
- lines.push(`|-------|-------|`);
29663
- for (const [actor, count] of Object.entries(byActor).sort((a2, b2) => b2[1] - a2[1])) {
29664
- lines.push(`| ${actor} | ${count} |`);
29665
- }
29666
- return text8(lines.join("\n"));
29667
- }
29668
- function generateSoc2Report(entries, byEventType, byActor, days) {
29669
- const lines = [
29670
- `# SOC 2 Compliance Report`,
29671
- `Period: Last ${days} days`,
29672
- `Generated: ${(/* @__PURE__ */ new Date()).toISOString()}`,
29673
- "",
29674
- "## 1. Control Environment",
29675
- `Total audit events: ${entries.length}`,
29676
- `Human-initiated: ${byActor["human"] ?? 0}`,
29677
- `AI-initiated: ${byActor["ai"] ?? 0}`,
29678
- `Hook-triggered: ${byActor["hook"] ?? 0}`,
29679
- "",
29680
- "## 2. Change Management",
29681
- `Code changes logged: ${byEventType["code_change"] ?? 0}`,
29682
- `Rule enforcements: ${byEventType["rule_enforced"] ?? 0}`,
29683
- `Approvals recorded: ${byEventType["approval"] ?? 0}`,
29684
- `Reviews: ${byEventType["review"] ?? 0}`,
29685
- `Commits: ${byEventType["commit"] ?? 0}`,
29686
- "",
29687
- "## 3. Approval Status"
29688
- ];
29689
- const pendingApprovals = entries.filter((e2) => e2.approval_status === "pending");
29690
- const deniedApprovals = entries.filter((e2) => e2.approval_status === "denied");
29691
- lines.push(`Pending approvals: ${pendingApprovals.length}`);
29692
- lines.push(`Denied approvals: ${deniedApprovals.length}`);
29693
- lines.push("");
29694
- lines.push("## 4. Events Requiring Review");
29695
- const reviewEntries = entries.filter((e2) => e2.approval_status === "denied" || e2.event_type === "rule_enforced");
29696
- if (reviewEntries.length > 0) {
29697
- for (const e2 of reviewEntries.slice(0, 20)) {
29698
- lines.push(`- [${e2.timestamp.slice(0, 16)}] ${e2.event_type}: ${(e2.evidence ?? "").slice(0, 100)}`);
29699
- }
29700
- } else {
29701
- lines.push("No events requiring review in this period.");
29702
- }
29703
- return text8(lines.join("\n"));
29704
- }
29705
- function generateDetailedReport(entries, byEventType, byActor, days) {
29706
- const lines = [
29707
- `## Detailed Audit Report (${days} days)`,
29708
- `Total events: ${entries.length}`,
29709
- "",
29710
- "### Event Type Distribution"
29711
- ];
29712
- for (const [et, count] of Object.entries(byEventType).sort((a2, b2) => b2[1] - a2[1])) {
29713
- lines.push(`- ${et}: ${count}`);
29714
- }
29715
- lines.push("");
29716
- lines.push("### Actor Distribution");
29717
- for (const [actor, count] of Object.entries(byActor).sort((a2, b2) => b2[1] - a2[1])) {
29718
- lines.push(`- ${actor}: ${count}`);
29719
- }
29720
- lines.push("");
29721
- lines.push("### All Events");
29722
- for (const e2 of entries.slice(0, 100)) {
29723
- lines.push(`#### ${e2.timestamp.slice(0, 16)} [${e2.event_type}]`);
29724
- lines.push(`Actor: ${e2.actor} | Change: ${e2.change_type ?? "-"} | Approval: ${e2.approval_status ?? "-"}`);
29725
- if (e2.file_path) lines.push(`File: ${e2.file_path}`);
29726
- if (e2.evidence) lines.push(`Evidence: ${e2.evidence}`);
29727
- lines.push("");
29728
- }
29729
- if (entries.length > 100) {
29730
- lines.push(`... and ${entries.length - 100} more entries`);
29731
- }
29732
- return text8(lines.join("\n"));
29733
- }
29734
- function handleAuditChain(args2, db) {
29735
- const filePath = args2.file;
29736
- if (!filePath) return text8(`Usage: ${p9("audit_chain")} { file: "src/path/to/file.ts" } - Get complete audit history for a file.`);
29737
- const chain = getFileChain(db, filePath);
29738
- if (chain.length === 0) {
29739
- return text8(`No audit entries found for "${filePath}". Audit entries are recorded automatically when files are modified, reviewed, or validated. Try: ${p9("audit_log")} {} to see all recent audit entries across all files.`);
29740
- }
29741
- const lines = [
29742
- `## Audit Chain: ${filePath}`,
29743
- `Total events: ${chain.length}`,
29744
- ""
29745
- ];
29746
- for (const entry of chain) {
29747
- lines.push(`### ${entry.timestamp.slice(0, 16)} [${entry.event_type}]`);
29748
- lines.push(`**${entry.actor}** (${entry.change_type ?? "unknown"})`);
29749
- if (entry.evidence) lines.push(entry.evidence);
29750
- lines.push("");
29751
- }
29752
- return text8(lines.join("\n"));
29753
- }
29754
- function text8(content) {
29755
- return { content: [{ type: "text", text: content }] };
29756
- }
29757
- var DEFAULT_FORMATS, AUDIT_BASE_NAMES;
29758
- var init_audit_trail = __esm({
29759
- "src/audit-trail.ts"() {
29760
- "use strict";
29761
- init_config();
29762
- DEFAULT_FORMATS = ["summary", "detailed", "soc2"];
29763
- AUDIT_BASE_NAMES = /* @__PURE__ */ new Set(["audit_log", "audit_report", "audit_chain"]);
29764
- }
29765
- });
29766
-
29767
31012
  // src/validation-engine.ts
29768
- import { existsSync as existsSync55, readFileSync as readFileSync46 } from "fs";
31013
+ import { existsSync as existsSync57, readFileSync as readFileSync48 } from "fs";
29769
31014
  function p10(baseName) {
29770
31015
  return `${getConfig().toolPrefix}_${baseName}`;
29771
31016
  }
@@ -29796,7 +31041,7 @@ function validateFile(filePath, projectRoot) {
29796
31041
  });
29797
31042
  return checks;
29798
31043
  }
29799
- if (!existsSync55(absPath)) {
31044
+ if (!existsSync57(absPath)) {
29800
31045
  checks.push({
29801
31046
  name: "file_exists",
29802
31047
  severity: "error",
@@ -29805,7 +31050,7 @@ function validateFile(filePath, projectRoot) {
29805
31050
  });
29806
31051
  return checks;
29807
31052
  }
29808
- const source = readFileSync46(absPath, "utf-8");
31053
+ const source = readFileSync48(absPath, "utf-8");
29809
31054
  const lines = source.split("\n");
29810
31055
  if (activeChecks.rule_compliance !== false) {
29811
31056
  for (const ruleSet of config.rules) {
@@ -30264,7 +31509,7 @@ var init_adr_generator = __esm({
30264
31509
  });
30265
31510
 
30266
31511
  // src/security-scorer.ts
30267
- import { existsSync as existsSync56, readFileSync as readFileSync47 } from "fs";
31512
+ import { existsSync as existsSync58, readFileSync as readFileSync49 } from "fs";
30268
31513
  function p12(baseName) {
30269
31514
  return `${getConfig().toolPrefix}_${baseName}`;
30270
31515
  }
@@ -30288,12 +31533,12 @@ function scoreFileSecurity(filePath, projectRoot) {
30288
31533
  }]
30289
31534
  };
30290
31535
  }
30291
- if (!existsSync56(absPath)) {
31536
+ if (!existsSync58(absPath)) {
30292
31537
  return { riskScore: 0, findings: [] };
30293
31538
  }
30294
31539
  let source;
30295
31540
  try {
30296
- source = readFileSync47(absPath, "utf-8");
31541
+ source = readFileSync49(absPath, "utf-8");
30297
31542
  } catch {
30298
31543
  return { riskScore: 0, findings: [] };
30299
31544
  }
@@ -30583,7 +31828,7 @@ var init_security_scorer = __esm({
30583
31828
  });
30584
31829
 
30585
31830
  // src/dependency-scorer.ts
30586
- import { existsSync as existsSync57, readFileSync as readFileSync48 } from "fs";
31831
+ import { existsSync as existsSync59, readFileSync as readFileSync50 } from "fs";
30587
31832
  import { resolve as resolve37 } from "path";
30588
31833
  function p13(baseName) {
30589
31834
  return `${getConfig().toolPrefix}_${baseName}`;
@@ -30617,9 +31862,9 @@ function calculateDepRisk(factors) {
30617
31862
  }
30618
31863
  function getInstalledPackages(projectRoot) {
30619
31864
  const pkgPath = resolve37(projectRoot, "package.json");
30620
- if (!existsSync57(pkgPath)) return /* @__PURE__ */ new Map();
31865
+ if (!existsSync59(pkgPath)) return /* @__PURE__ */ new Map();
30621
31866
  try {
30622
- const pkg = JSON.parse(readFileSync48(pkgPath, "utf-8"));
31867
+ const pkg = JSON.parse(readFileSync50(pkgPath, "utf-8"));
30623
31868
  const packages = /* @__PURE__ */ new Map();
30624
31869
  for (const [name, version] of Object.entries(pkg.dependencies ?? {})) {
30625
31870
  packages.set(name, version);
@@ -31223,30 +32468,10 @@ var init_regression_detector = __esm({
31223
32468
  }
31224
32469
  });
31225
32470
 
31226
- // src/lib/corrections-md.ts
31227
- function formatCorrectionEntry(e2) {
31228
- const one = (s) => s.replace(/\s*\n+\s*/g, " ").trim();
31229
- const lines = [`### ${e2.date} - ${one(e2.title)}`];
31230
- if (e2.wrong) lines.push(`- **Wrong**: ${one(e2.wrong)}`);
31231
- if (e2.correction) lines.push(`- **Correction**: ${one(e2.correction)}`);
31232
- lines.push(`- **Rule**: ${one(e2.rule)}`);
31233
- for (const [k3, v3] of Object.entries(e2.extra ?? {})) {
31234
- lines.push(`- ${k3}: ${one(String(v3))}`);
31235
- }
31236
- return `
31237
- ${lines.join("\n")}
31238
- `;
31239
- }
31240
- var init_corrections_md = __esm({
31241
- "src/lib/corrections-md.ts"() {
31242
- "use strict";
31243
- }
31244
- });
31245
-
31246
32471
  // src/knowledge-indexer.ts
31247
- import { createHash as createHash13 } from "crypto";
31248
- import { readFileSync as readFileSync49, readdirSync as readdirSync27, statSync as statSync20, existsSync as existsSync58 } from "fs";
31249
- import { resolve as resolve38, relative as relative13, basename as basename11, extname as extname2 } from "path";
32472
+ import { createHash as createHash14 } from "crypto";
32473
+ import { readFileSync as readFileSync51, readdirSync as readdirSync27, statSync as statSync20, existsSync as existsSync60 } from "fs";
32474
+ import { resolve as resolve38, relative as relative14, basename as basename11, extname as extname2 } from "path";
31250
32475
  function getKnowledgePaths() {
31251
32476
  const resolved = getResolvedPaths();
31252
32477
  const config = getConfig();
@@ -31294,7 +32519,7 @@ function categorizeFile(filePath) {
31294
32519
  const paths = getKnowledgePaths();
31295
32520
  if (filePath.startsWith(paths.plansDir)) return "plan";
31296
32521
  if (filePath.startsWith(paths.docsDir)) {
31297
- const relFromDocs = relative13(paths.docsDir, filePath).replace(/\\/g, "/").toLowerCase();
32522
+ const relFromDocs = relative14(paths.docsDir, filePath).replace(/\\/g, "/").toLowerCase();
31298
32523
  if (relFromDocs.startsWith("plans/")) return "plan";
31299
32524
  if (relFromDocs.includes("architecture")) return "architecture";
31300
32525
  if (relFromDocs.includes("security")) return "security";
@@ -31310,7 +32535,7 @@ function categorizeFile(filePath) {
31310
32535
  }
31311
32536
  const claudeDirName = getConfig().conventions?.claudeDirName ?? ".claude";
31312
32537
  if (filePath.includes(`${claudeDirName}/projects/`) && filePath.includes("/memory/")) return "memory";
31313
- const rel = relative13(paths.claudeDir, filePath).replace(/\\/g, "/");
32538
+ const rel = relative14(paths.claudeDir, filePath).replace(/\\/g, "/");
31314
32539
  const firstDir = rel.split("/")[0];
31315
32540
  const knownCategories = getConfig().conventions?.knowledgeCategories ?? [
31316
32541
  "patterns",
@@ -31332,7 +32557,7 @@ function categorizeFile(filePath) {
31332
32557
  return "root";
31333
32558
  }
31334
32559
  function hashContent2(content) {
31335
- return createHash13("sha256").update(content).digest("hex");
32560
+ return createHash14("sha256").update(content).digest("hex");
31336
32561
  }
31337
32562
  function parseCRTable(content) {
31338
32563
  const rules = [];
@@ -31553,11 +32778,11 @@ function indexAllKnowledge(db) {
31553
32778
  files.push(...memFiles);
31554
32779
  } catch {
31555
32780
  }
31556
- if (existsSync58(paths.plansDir)) {
32781
+ if (existsSync60(paths.plansDir)) {
31557
32782
  const planFiles = discoverMarkdownFiles(paths.plansDir);
31558
32783
  files.push(...planFiles);
31559
32784
  }
31560
- if (existsSync58(paths.docsDir)) {
32785
+ if (existsSync60(paths.docsDir)) {
31561
32786
  const excludePatterns = getConfig().conventions?.excludePatterns ?? ["/ARCHIVE/", "/SESSION-HISTORY/"];
31562
32787
  const docsFiles = discoverMarkdownFiles(paths.docsDir).filter((f2) => !f2.includes("/plans/") && !excludePatterns.some((p19) => f2.includes(p19)));
31563
32788
  files.push(...docsFiles);
@@ -31600,10 +32825,10 @@ function indexAllKnowledge(db) {
31600
32825
  } catch {
31601
32826
  }
31602
32827
  for (const filePath of files) {
31603
- if (!existsSync58(filePath)) continue;
31604
- const content = readFileSync49(filePath, "utf-8");
32828
+ if (!existsSync60(filePath)) continue;
32829
+ const content = readFileSync51(filePath, "utf-8");
31605
32830
  const hash = hashContent2(content);
31606
- const relPath = filePath.startsWith(paths.claudeDir) ? relative13(paths.claudeDir, filePath) : filePath.startsWith(paths.plansDir) ? "plans/" + relative13(paths.plansDir, filePath) : filePath.startsWith(paths.docsDir) ? "docs/" + relative13(paths.docsDir, filePath) : filePath.startsWith(paths.memoryDir) ? `memory/${relative13(paths.memoryDir, filePath)}` : basename11(filePath);
32831
+ const relPath = filePath.startsWith(paths.claudeDir) ? relative14(paths.claudeDir, filePath) : filePath.startsWith(paths.plansDir) ? "plans/" + relative14(paths.plansDir, filePath) : filePath.startsWith(paths.docsDir) ? "docs/" + relative14(paths.docsDir, filePath) : filePath.startsWith(paths.memoryDir) ? `memory/${relative14(paths.memoryDir, filePath)}` : basename11(filePath);
31607
32832
  const category = categorizeFile(filePath);
31608
32833
  const title = extractTitle(content, filePath);
31609
32834
  const description = extractDescription2(content);
@@ -31736,10 +32961,10 @@ function isKnowledgeStale(db) {
31736
32961
  files.push(...discoverMarkdownFiles(paths.memoryDir));
31737
32962
  } catch {
31738
32963
  }
31739
- if (existsSync58(paths.plansDir)) {
32964
+ if (existsSync60(paths.plansDir)) {
31740
32965
  files.push(...discoverMarkdownFiles(paths.plansDir));
31741
32966
  }
31742
- if (existsSync58(paths.docsDir)) {
32967
+ if (existsSync60(paths.docsDir)) {
31743
32968
  const excludePatterns = getConfig().conventions?.excludePatterns ?? ["/ARCHIVE/", "/SESSION-HISTORY/"];
31744
32969
  const docsFiles = discoverMarkdownFiles(paths.docsDir).filter((f2) => !f2.includes("/plans/") && !excludePatterns.some((p19) => f2.includes(p19)));
31745
32970
  files.push(...docsFiles);
@@ -31768,7 +32993,7 @@ var init_knowledge_indexer = __esm({
31768
32993
  });
31769
32994
 
31770
32995
  // src/knowledge-tools.ts
31771
- import { readFileSync as readFileSync50, writeFileSync as writeFileSync12, appendFileSync as appendFileSync4, readdirSync as readdirSync28 } from "fs";
32996
+ import { readFileSync as readFileSync52, writeFileSync as writeFileSync14, appendFileSync as appendFileSync4, readdirSync as readdirSync28 } from "fs";
31772
32997
  import { resolve as resolve39, basename as basename12 } from "path";
31773
32998
  function p16(baseName) {
31774
32999
  return `${getConfig().toolPrefix}_${baseName}`;
@@ -32521,14 +33746,14 @@ function handleCorrect(db, args2) {
32521
33746
  });
32522
33747
  let existing = "";
32523
33748
  try {
32524
- existing = readFileSync50(correctionsPath, "utf-8");
33749
+ existing = readFileSync52(correctionsPath, "utf-8");
32525
33750
  } catch {
32526
33751
  }
32527
33752
  const archiveIdx = existing.indexOf("## Archived");
32528
33753
  if (archiveIdx > 0) {
32529
33754
  const before = existing.slice(0, archiveIdx);
32530
33755
  const after = existing.slice(archiveIdx);
32531
- writeFileSync12(correctionsPath, before + entry + after);
33756
+ writeFileSync14(correctionsPath, before + entry + after);
32532
33757
  } else {
32533
33758
  appendFileSync4(correctionsPath, entry);
32534
33759
  }
@@ -33477,7 +34702,7 @@ var init_framework_supports = __esm({
33477
34702
  });
33478
34703
 
33479
34704
  // src/tools.ts
33480
- import { readFileSync as readFileSync51, existsSync as existsSync60 } from "fs";
34705
+ import { readFileSync as readFileSync53, existsSync as existsSync62 } from "fs";
33481
34706
  import { resolve as resolve40, basename as basename13 } from "path";
33482
34707
  function prefix2() {
33483
34708
  return getConfig().toolPrefix;
@@ -33897,8 +35122,8 @@ function handleContext(file, dataDb, codegraphDb) {
33897
35122
  const resolvedPaths = getResolvedPaths();
33898
35123
  const root = getProjectRoot();
33899
35124
  const absFilePath = ensureWithinRoot(resolve40(resolvedPaths.srcDir, "..", file), root);
33900
- if (existsSync60(absFilePath)) {
33901
- const fileContent = readFileSync51(absFilePath, "utf-8").slice(0, 3e3);
35125
+ if (existsSync62(absFilePath)) {
35126
+ const fileContent = readFileSync53(absFilePath, "utf-8").slice(0, 3e3);
33902
35127
  const keywords = [];
33903
35128
  if (fileContent.includes("ctx.db")) keywords.push("database", "schema");
33904
35129
  if (fileContent.includes("BigInt") || fileContent.includes("Decimal")) keywords.push("BigInt", "serialization");
@@ -34336,10 +35561,10 @@ function handleSchema(args2) {
34336
35561
  lines.push("");
34337
35562
  const projectRoot = getProjectRoot();
34338
35563
  const absPath = ensureWithinRoot(resolve40(projectRoot, file), projectRoot);
34339
- if (!existsSync60(absPath)) {
35564
+ if (!existsSync62(absPath)) {
34340
35565
  return text17(`File not found: ${file}`);
34341
35566
  }
34342
- const source = readFileSync51(absPath, "utf-8");
35567
+ const source = readFileSync53(absPath, "utf-8");
34343
35568
  const config = getConfig();
34344
35569
  const dbPattern = config.dbAccessPattern ?? "ctx.db.{table}";
34345
35570
  const regexStr = dbPattern.replace(/[.*+?^${}()|[\]\\]/g, "\\$&").replace("\\{table\\}", "(\\w+)");
@@ -34744,8 +35969,8 @@ var init_server_dispatch = __esm({
34744
35969
 
34745
35970
  // src/server.ts
34746
35971
  var server_exports = {};
34747
- import { readFileSync as readFileSync52 } from "fs";
34748
- import { resolve as resolve41, dirname as dirname25 } from "path";
35972
+ import { readFileSync as readFileSync54 } from "fs";
35973
+ import { resolve as resolve41, dirname as dirname26 } from "path";
34749
35974
  import { fileURLToPath as fileURLToPath6 } from "url";
34750
35975
  function pruneMemoryOnStartup() {
34751
35976
  try {
@@ -34787,10 +36012,10 @@ var init_server = __esm({
34787
36012
  init_consolidation_config();
34788
36013
  init_license();
34789
36014
  init_server_dispatch();
34790
- __dirname5 = dirname25(fileURLToPath6(import.meta.url));
36015
+ __dirname5 = dirname26(fileURLToPath6(import.meta.url));
34791
36016
  PKG_VERSION = (() => {
34792
36017
  try {
34793
- const pkg = JSON.parse(readFileSync52(resolve41(__dirname5, "..", "package.json"), "utf-8"));
36018
+ const pkg = JSON.parse(readFileSync54(resolve41(__dirname5, "..", "package.json"), "utf-8"));
34794
36019
  return pkg.version ?? "0.0.0";
34795
36020
  } catch {
34796
36021
  return "0.0.0";
@@ -35076,7 +36301,7 @@ var config_upgrade_exports = {};
35076
36301
  __export(config_upgrade_exports, {
35077
36302
  runConfigUpgrade: () => runConfigUpgrade
35078
36303
  });
35079
- import { existsSync as existsSync61, readFileSync as readFileSync53, writeFileSync as writeFileSync13, copyFileSync as copyFileSync3, unlinkSync as unlinkSync5 } from "fs";
36304
+ import { existsSync as existsSync63, readFileSync as readFileSync55, writeFileSync as writeFileSync15, copyFileSync as copyFileSync3, unlinkSync as unlinkSync6 } from "fs";
35080
36305
  import { resolve as resolve42 } from "path";
35081
36306
  import { parse as parseYaml6 } from "yaml";
35082
36307
  async function runConfigUpgrade(opts = {}) {
@@ -35088,14 +36313,14 @@ async function runConfigUpgrade(opts = {}) {
35088
36313
  const err = opts.silent ? () => {
35089
36314
  } : (s) => process.stderr.write(s);
35090
36315
  if (opts.rollback) {
35091
- if (!existsSync61(bakPath)) {
36316
+ if (!existsSync63(bakPath)) {
35092
36317
  const message = `No backup found at ${bakPath}`;
35093
36318
  err(message + "\n");
35094
36319
  return { exitCode: 1, action: "none", message };
35095
36320
  }
35096
36321
  try {
35097
36322
  copyFileSync3(bakPath, configPath);
35098
- unlinkSync5(bakPath);
36323
+ unlinkSync6(bakPath);
35099
36324
  log("Config restored from backup.\n");
35100
36325
  return { exitCode: 0, action: "rolled-back" };
35101
36326
  } catch (e2) {
@@ -35104,14 +36329,14 @@ async function runConfigUpgrade(opts = {}) {
35104
36329
  return { exitCode: 2, action: "none", message };
35105
36330
  }
35106
36331
  }
35107
- if (!existsSync61(configPath)) {
36332
+ if (!existsSync63(configPath)) {
35108
36333
  const message = "massu.config.yaml not found. Run: npx massu init";
35109
36334
  err(message + "\n");
35110
36335
  return { exitCode: 1, action: "none", message };
35111
36336
  }
35112
36337
  let existing;
35113
36338
  try {
35114
- const content = readFileSync53(configPath, "utf-8");
36339
+ const content = readFileSync55(configPath, "utf-8");
35115
36340
  const parsed = parseYaml6(content);
35116
36341
  if (!parsed || typeof parsed !== "object") {
35117
36342
  throw new Error("config is not a YAML object");
@@ -35134,8 +36359,8 @@ async function runConfigUpgrade(opts = {}) {
35134
36359
  fingerprint: computeFingerprint(detection)
35135
36360
  };
35136
36361
  try {
35137
- const original = readFileSync53(configPath, "utf-8");
35138
- writeFileSync13(bakPath, original, "utf-8");
36362
+ const original = readFileSync55(configPath, "utf-8");
36363
+ writeFileSync15(bakPath, original, "utf-8");
35139
36364
  } catch (e2) {
35140
36365
  const message = `Failed to write backup: ${e2 instanceof Error ? e2.message : String(e2)}`;
35141
36366
  err(message + "\n");
@@ -35168,7 +36393,7 @@ var config_check_drift_exports = {};
35168
36393
  __export(config_check_drift_exports, {
35169
36394
  runConfigCheckDrift: () => runConfigCheckDrift
35170
36395
  });
35171
- import { existsSync as existsSync62, readFileSync as readFileSync54 } from "fs";
36396
+ import { existsSync as existsSync64, readFileSync as readFileSync56 } from "fs";
35172
36397
  import { resolve as resolve43 } from "path";
35173
36398
  import { parse as parseYaml7 } from "yaml";
35174
36399
  function renderChanges(changes) {
@@ -35182,7 +36407,7 @@ async function runConfigCheckDrift(opts = {}) {
35182
36407
  } : (s) => process.stdout.write(s);
35183
36408
  const err = opts.silent ? () => {
35184
36409
  } : (s) => process.stderr.write(s);
35185
- if (!existsSync62(configPath)) {
36410
+ if (!existsSync64(configPath)) {
35186
36411
  const message = "massu.config.yaml not found. Run: npx massu init";
35187
36412
  err(message + "\n");
35188
36413
  return {
@@ -35196,7 +36421,7 @@ async function runConfigCheckDrift(opts = {}) {
35196
36421
  }
35197
36422
  let config;
35198
36423
  try {
35199
- const content = readFileSync54(configPath, "utf-8");
36424
+ const content = readFileSync56(configPath, "utf-8");
35200
36425
  const parsed = parseYaml7(content);
35201
36426
  if (!parsed || typeof parsed !== "object") {
35202
36427
  throw new Error("config is not a YAML object");
@@ -35263,11 +36488,11 @@ var init_config_check_drift = __esm({
35263
36488
  });
35264
36489
 
35265
36490
  // src/cli.ts
35266
- import { readFileSync as readFileSync55 } from "fs";
35267
- import { resolve as resolve44, dirname as dirname26 } from "path";
36491
+ import { readFileSync as readFileSync57 } from "fs";
36492
+ import { resolve as resolve44, dirname as dirname27 } from "path";
35268
36493
  import { fileURLToPath as fileURLToPath7 } from "url";
35269
36494
  var __filename5 = fileURLToPath7(import.meta.url);
35270
- var __dirname6 = dirname26(__filename5);
36495
+ var __dirname6 = dirname27(__filename5);
35271
36496
  var args = process.argv.slice(2);
35272
36497
  var subcommand = args[0];
35273
36498
  async function main() {
@@ -35545,7 +36770,7 @@ Examples:
35545
36770
  }
35546
36771
  function printVersion() {
35547
36772
  try {
35548
- const pkg = JSON.parse(readFileSync55(resolve44(__dirname6, "../package.json"), "utf-8"));
36773
+ const pkg = JSON.parse(readFileSync57(resolve44(__dirname6, "../package.json"), "utf-8"));
35549
36774
  console.log(`massu v${pkg.version}`);
35550
36775
  } catch {
35551
36776
  console.log("massu v0.1.0");