@massu/core 1.15.7 → 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.
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";
@@ -9159,21 +9280,21 @@ var require_pattern = __commonJS({
9159
9280
  exports.removeDuplicateSlashes = removeDuplicateSlashes;
9160
9281
  function partitionAbsoluteAndRelative(patterns) {
9161
9282
  const absolute = [];
9162
- const relative14 = [];
9283
+ const relative15 = [];
9163
9284
  for (const pattern of patterns) {
9164
- if (isAbsolute4(pattern)) {
9285
+ if (isAbsolute5(pattern)) {
9165
9286
  absolute.push(pattern);
9166
9287
  } else {
9167
- relative14.push(pattern);
9288
+ relative15.push(pattern);
9168
9289
  }
9169
9290
  }
9170
- return [absolute, relative14];
9291
+ return [absolute, relative15];
9171
9292
  }
9172
9293
  exports.partitionAbsoluteAndRelative = partitionAbsoluteAndRelative;
9173
- function isAbsolute4(pattern) {
9294
+ function isAbsolute5(pattern) {
9174
9295
  return path.isAbsolute(pattern);
9175
9296
  }
9176
- exports.isAbsolute = isAbsolute4;
9297
+ exports.isAbsolute = isAbsolute5;
9177
9298
  }
9178
9299
  });
9179
9300
 
@@ -17975,6 +18096,28 @@ function autoLearningUpgradeMessage(currentTier) {
17975
18096
  function entitledForTeamSharedPromotion(tier) {
17976
18097
  return tierLevel(tier) >= tierLevel(TEAM_SHARED_PROMOTION_MIN_TIER);
17977
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
+ }
17978
18121
  var AUTO_LEARNING_MIN_TIER, TEAM_SHARED_PROMOTION_MIN_TIER;
17979
18122
  var init_auto_learning_entitlement = __esm({
17980
18123
  "src/auto-learning-entitlement.ts"() {
@@ -19340,6 +19483,12 @@ function assertOutsideRegionUnchanged(pre, post) {
19340
19483
  );
19341
19484
  }
19342
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
+ }
19343
19492
  function memoryIndexLockPath(memoryDir) {
19344
19493
  return join16(dirname15(memoryDir), ".massu-memory-index.lock");
19345
19494
  }
@@ -19364,7 +19513,7 @@ function writeMemoryIndex(indexPath, pre, post) {
19364
19513
  assertOutsideRegionUnchanged(pre, post);
19365
19514
  atomicWriteFileSync(indexPath, post);
19366
19515
  }
19367
- 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;
19368
19517
  var init_memory_index_region = __esm({
19369
19518
  "src/memory-index-region.ts"() {
19370
19519
  "use strict";
@@ -19372,6 +19521,7 @@ var init_memory_index_region = __esm({
19372
19521
  init_safe_write();
19373
19522
  BEGIN_SENTINEL = "<!-- massu:learned:begin -->";
19374
19523
  END_SENTINEL = "<!-- massu:learned:end -->";
19524
+ DEFAULT_MAX_REGION_LINES = 50;
19375
19525
  RegionRefused = class extends Error {
19376
19526
  constructor(message, reason) {
19377
19527
  super(message);
@@ -19380,6 +19530,12 @@ var init_memory_index_region = __esm({
19380
19530
  }
19381
19531
  reason;
19382
19532
  };
19533
+ MemoryIndexMissing = class extends Error {
19534
+ constructor(message) {
19535
+ super(message);
19536
+ this.name = "MemoryIndexMissing";
19537
+ }
19538
+ };
19383
19539
  LOCK_BLOCK_MS = 2e3;
19384
19540
  LOCK_STALE_MS = 3e4;
19385
19541
  MemoryIndexLockBusy = class extends Error {
@@ -20064,6 +20220,1149 @@ var init_memory_render_cli_entry = __esm({
20064
20220
  }
20065
20221
  });
20066
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
+
20067
21366
  // src/security/promotion-pubkey.generated.ts
20068
21367
  var PROMOTION_PUBKEY_ED25519, PROMOTION_PUBKEY_FINGERPRINT_HEX, KNOWN_PROMOTION_PUBKEY_FINGERPRINTS;
20069
21368
  var init_promotion_pubkey_generated = __esm({
@@ -20097,20 +21396,8 @@ var init_promotion_envelope_verifier = __esm({
20097
21396
  }
20098
21397
  });
20099
21398
 
20100
- // src/rule-candidate-hardened.ts
20101
- var TEAM_HARDENED_SHAREABLE_DESTINATIONS;
20102
- var init_rule_candidate_hardened = __esm({
20103
- "src/rule-candidate-hardened.ts"() {
20104
- "use strict";
20105
- TEAM_HARDENED_SHAREABLE_DESTINATIONS = [
20106
- "pattern-scanner",
20107
- "custom-destination"
20108
- ];
20109
- }
20110
- });
20111
-
20112
21399
  // src/rule-pack-schema.ts
20113
- function isRuleDestination(value) {
21400
+ function isRuleDestination2(value) {
20114
21401
  return typeof value === "string" && RULE_DESTINATIONS.includes(value);
20115
21402
  }
20116
21403
  function isExecutableDestination(destination) {
@@ -20134,7 +21421,7 @@ function validateRulePackRule(rule) {
20134
21421
  if (!isNonEmptyString(r2.severity)) {
20135
21422
  errors.push("missing required field: severity");
20136
21423
  }
20137
- const destinationKnown = isRuleDestination(r2.destination);
21424
+ const destinationKnown = isRuleDestination2(r2.destination);
20138
21425
  if (!destinationKnown) {
20139
21426
  errors.push(
20140
21427
  `invalid destination: ${JSON.stringify(r2.destination)} is not a known RuleDestination (one of ${RULE_DESTINATIONS.join(", ")})`
@@ -20188,8 +21475,8 @@ var init_rule_pack_schema = __esm({
20188
21475
  });
20189
21476
 
20190
21477
  // src/rule-pack-sync.ts
20191
- import { existsSync as existsSync32, writeFileSync as writeFileSync9, mkdirSync as mkdirSync14 } from "fs";
20192
- 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";
20193
21480
  async function pullInstalledPackRules(db, opts = {}) {
20194
21481
  const config = getConfig();
20195
21482
  const cloud = config.cloud;
@@ -20251,7 +21538,7 @@ async function pullInstalledPackRules(db, opts = {}) {
20251
21538
  result.pulled += 1;
20252
21539
  const promptHash = packRulePromptHash(pack.slug, rule);
20253
21540
  const candidatePath = sidecarPath(projectRoot, promptHash);
20254
- if (existsSync32(candidatePath) || alreadyApplied(db, promptHash)) {
21541
+ if (existsSync34(candidatePath) || alreadyApplied(db, promptHash)) {
20255
21542
  result.skipped += 1;
20256
21543
  continue;
20257
21544
  }
@@ -20284,12 +21571,12 @@ function isValidInstalledPack(p19) {
20284
21571
  return typeof r2.slug === "string" && r2.slug.length > 0 && Array.isArray(r2.rules);
20285
21572
  }
20286
21573
  function packRulePromptHash(slug, rule) {
20287
- const { createHash: createHash14 } = __require("node:crypto");
21574
+ const { createHash: createHash15 } = __require("node:crypto");
20288
21575
  const body = `${slug}\0${rule.destination}\0${rule.title}\0${rule.pattern ?? rule.check ?? rule.description}`;
20289
- 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);
20290
21577
  }
20291
21578
  function sidecarPath(projectRoot, promptHash) {
20292
- return join20(projectRoot, ".massu", "rule-candidates", `${promptHash}.json`);
21579
+ return join21(projectRoot, ".massu", "rule-candidates", `${promptHash}.json`);
20293
21580
  }
20294
21581
  function alreadyApplied(db, promptHash) {
20295
21582
  try {
@@ -20338,9 +21625,9 @@ function materializeCandidate(projectRoot, candidatePath, promptHash, rule, pack
20338
21625
  destination: rule.destination,
20339
21626
  draft_text: draftText
20340
21627
  };
20341
- const dir = dirname17(candidatePath);
20342
- if (!existsSync32(dir)) mkdirSync14(dir, { recursive: true });
20343
- 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");
20344
21631
  }
20345
21632
  function emitDropTelemetry(db, eventType, data) {
20346
21633
  recordTelemetry(db, eventType, data);
@@ -20374,6 +21661,12 @@ var rule_exports = {};
20374
21661
  __export(rule_exports, {
20375
21662
  handleRuleSubcommand: () => handleRuleSubcommand
20376
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
+ }
20377
21670
  async function handleRuleSubcommand(args2) {
20378
21671
  const sub = args2[0];
20379
21672
  switch (sub) {
@@ -20422,11 +21715,179 @@ async function handleRuleSubcommand(args2) {
20422
21715
  }
20423
21716
  return { exitCode: 0 };
20424
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
+ }
20425
21886
  case "--help":
20426
21887
  case "-h":
20427
21888
  case void 0: {
20428
21889
  process.stdout.write(
20429
- "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"
20430
21891
  );
20431
21892
  return { exitCode: 0 };
20432
21893
  }
@@ -20437,14 +21898,24 @@ async function handleRuleSubcommand(args2) {
20437
21898
  }
20438
21899
  }
20439
21900
  }
20440
- var PROMPT_HASH_RE;
21901
+ var RULE_DESTINATIONS2, PROMPT_HASH_RE;
20441
21902
  var init_rule = __esm({
20442
21903
  "src/commands/rule.ts"() {
20443
21904
  "use strict";
20444
21905
  init_memory_db();
21906
+ init_config();
21907
+ init_rule_delivery();
21908
+ init_rule_candidate_applier();
21909
+ init_rule_candidate_preview();
20445
21910
  init_license();
20446
21911
  init_auto_learning_entitlement();
20447
21912
  init_rule_pack_sync();
21913
+ RULE_DESTINATIONS2 = [
21914
+ "corrections-md",
21915
+ "claude-md-cr",
21916
+ "pattern-scanner",
21917
+ "custom-destination"
21918
+ ];
20448
21919
  PROMPT_HASH_RE = /^[0-9a-f]{16}$/;
20449
21920
  }
20450
21921
  });
@@ -20938,9 +22409,9 @@ var init_memory_supersede = __esm({
20938
22409
  });
20939
22410
 
20940
22411
  // src/memory-consolidate.ts
20941
- import { createHash as createHash9 } from "crypto";
20942
- import { existsSync as existsSync33, mkdirSync as mkdirSync15, writeFileSync as writeFileSync10 } from "fs";
20943
- 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";
20944
22415
  function emptyResult() {
20945
22416
  return {
20946
22417
  deduped: 0,
@@ -21102,7 +22573,7 @@ ${r2.detail ?? ""}`.trim());
21102
22573
  }
21103
22574
  clusters.push(cluster);
21104
22575
  }
21105
- const candidateDir = join21(projectRoot, ".massu", "rule-candidates");
22576
+ const candidateDir = join23(projectRoot, ".massu", "rule-candidates");
21106
22577
  let promoted = 0;
21107
22578
  for (const cluster of clusters) {
21108
22579
  const occurrences = cluster.reduce((n, c2) => n + (c2.recurrence_count || 1), 0);
@@ -21114,15 +22585,15 @@ ${r2.detail ?? ""}`.trim());
21114
22585
  const promptText = `${representative.title}${representative.detail ? `
21115
22586
  ${representative.detail}` : ""}`;
21116
22587
  const clusterKey = cluster.map((c2) => c2.title.toLowerCase().replace(/\s+/g, " ").trim()).sort().join("|");
21117
- const promptHash = createHash9("sha256").update(clusterKey).digest("hex").slice(0, 16);
21118
- const candidatePath = join21(candidateDir, `${promptHash}.json`);
21119
- 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;
21120
22591
  if (dryRun) {
21121
22592
  promoted++;
21122
22593
  continue;
21123
22594
  }
21124
- mkdirSync15(candidateDir, { recursive: true });
21125
- writeFileSync10(
22595
+ mkdirSync16(candidateDir, { recursive: true });
22596
+ writeFileSync12(
21126
22597
  candidatePath,
21127
22598
  JSON.stringify(
21128
22599
  {
@@ -21392,7 +22863,7 @@ var show_template_exports = {};
21392
22863
  __export(show_template_exports, {
21393
22864
  runShowTemplate: () => runShowTemplate
21394
22865
  });
21395
- import { existsSync as existsSync34, readFileSync as readFileSync26 } from "fs";
22866
+ import { existsSync as existsSync36, readFileSync as readFileSync28 } from "fs";
21396
22867
  import { resolve as resolve17 } from "path";
21397
22868
  function normalizeBaseName(input) {
21398
22869
  return input.endsWith(".md") ? input.slice(0, -".md".length) : input;
@@ -21422,13 +22893,13 @@ async function runShowTemplate(args2) {
21422
22893
  }
21423
22894
  const suffix = choice.kind === "hit" ? choice.suffix : "";
21424
22895
  const file = suffix === "" ? resolve17(sourceDir, `${baseName}.md`) : resolve17(sourceDir, `${baseName}${suffix}.md`);
21425
- if (!existsSync34(file)) {
22896
+ if (!existsSync36(file)) {
21426
22897
  process.stderr.write(`massu: resolved template "${file}" no longer exists
21427
22898
  `);
21428
22899
  process.exit(1);
21429
22900
  return;
21430
22901
  }
21431
- process.stdout.write(readFileSync26(file, "utf-8"));
22902
+ process.stdout.write(readFileSync28(file, "utf-8"));
21432
22903
  }
21433
22904
  var init_show_template = __esm({
21434
22905
  "src/commands/show-template.ts"() {
@@ -21523,7 +22994,7 @@ __export(config_refresh_exports, {
21523
22994
  mergeRefresh: () => mergeRefresh,
21524
22995
  runConfigRefresh: () => runConfigRefresh
21525
22996
  });
21526
- 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";
21527
22998
  import { resolve as resolve19 } from "path";
21528
22999
  import { parse as parseYaml5 } from "yaml";
21529
23000
  function flatten(obj, prefix3 = "") {
@@ -21655,14 +23126,14 @@ async function runConfigRefresh(opts = {}) {
21655
23126
  const configPath = resolve19(cwd, "massu.config.yaml");
21656
23127
  const log = opts.silent ? () => {
21657
23128
  } : (s) => process.stdout.write(s);
21658
- if (!existsSync35(configPath)) {
23129
+ if (!existsSync37(configPath)) {
21659
23130
  const message = "massu.config.yaml not found. Run: npx massu init";
21660
23131
  if (!opts.silent) process.stderr.write(message + "\n");
21661
23132
  return { exitCode: 1, applied: false, dryRun: !!opts.dryRun, diff: [], message };
21662
23133
  }
21663
23134
  let existing;
21664
23135
  try {
21665
- const content = readFileSync27(configPath, "utf-8");
23136
+ const content = readFileSync29(configPath, "utf-8");
21666
23137
  const parsed = parseYaml5(content);
21667
23138
  if (!parsed || typeof parsed !== "object") {
21668
23139
  throw new Error("config is not a YAML object");
@@ -21736,7 +23207,7 @@ async function runConfigRefresh(opts = {}) {
21736
23207
  const stackResolved = installResult.totalInstalled > 0 || installResult.totalUpdated > 0;
21737
23208
  if (stackResolved) {
21738
23209
  const placeholderPath = resolve19(installResult.claudeDir, "commands", "_massu-needs-stack.md");
21739
- if (existsSync35(placeholderPath)) {
23210
+ if (existsSync37(placeholderPath)) {
21740
23211
  try {
21741
23212
  rmSync8(placeholderPath, { force: true });
21742
23213
  log("Removed _massu-needs-stack.md (stack now declared).\n");
@@ -21845,12 +23316,12 @@ var init_gitToplevel = __esm({
21845
23316
  });
21846
23317
 
21847
23318
  // src/watch/lockfile-detector.ts
21848
- import { existsSync as existsSync36, statSync as statSync14 } from "fs";
23319
+ import { existsSync as existsSync38, statSync as statSync14 } from "fs";
21849
23320
  import { resolve as resolve20 } from "path";
21850
23321
  function lockfileMidWrite(projectRoot, now = Date.now(), windowMs = LOCKFILE_WINDOW_MS) {
21851
23322
  for (const lf of KNOWN_LOCKFILES) {
21852
23323
  const p19 = resolve20(projectRoot, lf);
21853
- if (!existsSync36(p19)) continue;
23324
+ if (!existsSync38(p19)) continue;
21854
23325
  try {
21855
23326
  const stat = statSync14(p19);
21856
23327
  const delta = now - stat.mtimeMs;
@@ -21863,7 +23334,7 @@ function lockfileMidWrite(projectRoot, now = Date.now(), windowMs = LOCKFILE_WIN
21863
23334
  function gitMidOperation(projectRoot) {
21864
23335
  const sentinels = ["MERGE_HEAD", "REBASE_HEAD", "CHERRY_PICK_HEAD", "rebase-apply", "rebase-merge"];
21865
23336
  for (const s of sentinels) {
21866
- if (existsSync36(resolve20(projectRoot, ".git", s))) return true;
23337
+ if (existsSync38(resolve20(projectRoot, ".git", s))) return true;
21867
23338
  }
21868
23339
  return false;
21869
23340
  }
@@ -22062,8 +23533,8 @@ var init_paths = __esm({
22062
23533
  });
22063
23534
 
22064
23535
  // src/watch/state.ts
22065
- 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";
22066
- 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";
22067
23538
  function watchStatePath(projectRoot) {
22068
23539
  return resolve21(projectRoot, ".massu", "watch-state.json");
22069
23540
  }
@@ -22072,8 +23543,8 @@ function backupStatePath(projectRoot) {
22072
23543
  }
22073
23544
  function readState(projectRoot) {
22074
23545
  const path = watchStatePath(projectRoot);
22075
- if (!existsSync37(path)) return { ...DEFAULT_STATE };
22076
- const content = readFileSync28(path, "utf-8");
23546
+ if (!existsSync39(path)) return { ...DEFAULT_STATE };
23547
+ const content = readFileSync30(path, "utf-8");
22077
23548
  let raw;
22078
23549
  try {
22079
23550
  raw = JSON.parse(content);
@@ -22115,14 +23586,14 @@ function readState(projectRoot) {
22115
23586
  }
22116
23587
  function archiveCorrupt(projectRoot, content) {
22117
23588
  const bak = backupStatePath(projectRoot);
22118
- mkdirSync16(dirname18(bak), { recursive: true });
22119
- writeFileSync11(bak, content, "utf-8");
23589
+ mkdirSync17(dirname19(bak), { recursive: true });
23590
+ writeFileSync13(bak, content, "utf-8");
22120
23591
  }
22121
23592
  function writeStateAtomic(projectRoot, state) {
22122
23593
  const path = watchStatePath(projectRoot);
22123
23594
  writeStateAtomicCounter = writeStateAtomicCounter + 1 >>> 0;
22124
23595
  const tmp = `${path}.${process.pid}.${writeStateAtomicCounter}.tmp`;
22125
- mkdirSync16(dirname18(path), { recursive: true });
23596
+ mkdirSync17(dirname19(path), { recursive: true });
22126
23597
  let renamed = false;
22127
23598
  try {
22128
23599
  const fd = openSync4(tmp, "w");
@@ -22136,7 +23607,7 @@ function writeStateAtomic(projectRoot, state) {
22136
23607
  renameSync5(tmp, path);
22137
23608
  renamed = true;
22138
23609
  } finally {
22139
- if (!renamed && existsSync37(tmp)) {
23610
+ if (!renamed && existsSync39(tmp)) {
22140
23611
  try {
22141
23612
  rmSync9(tmp, { force: true });
22142
23613
  } catch {
@@ -22418,8 +23889,8 @@ __export(watch_exports, {
22418
23889
  runWatch: () => runWatch
22419
23890
  });
22420
23891
  import { spawnSync as spawnSync3 } from "child_process";
22421
- import { basename as basename8, dirname as dirname19, resolve as resolve22 } from "path";
22422
- 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";
22423
23894
  function parseFlags(args2) {
22424
23895
  const out = {
22425
23896
  foreground: false,
@@ -22445,7 +23916,7 @@ function parseFlags(args2) {
22445
23916
  function findClaudeBg() {
22446
23917
  const home = process.env.HOME ?? "";
22447
23918
  const fixed = home ? resolve22(home, ".claude", "bin", "claude-bg") : null;
22448
- if (fixed && existsSync38(fixed)) return fixed;
23919
+ if (fixed && existsSync40(fixed)) return fixed;
22449
23920
  const which = spawnSync3("which", ["claude-bg"], { encoding: "utf-8" });
22450
23921
  if (which.status === 0 && which.stdout) {
22451
23922
  const p19 = which.stdout.trim();
@@ -22506,7 +23977,7 @@ async function runWatch(args2) {
22506
23977
  }
22507
23978
  function runStatus(root) {
22508
23979
  const path = watchStatePath(root);
22509
- if (!existsSync38(path)) {
23980
+ if (!existsSync40(path)) {
22510
23981
  process.stdout.write("massu watch: not running (no state file)\n");
22511
23982
  return { exitCode: 0 };
22512
23983
  }
@@ -22664,18 +24135,18 @@ function refreshLogPath(projectRoot) {
22664
24135
  function appendRefreshLog(projectRoot, event) {
22665
24136
  const path = refreshLogPath(projectRoot);
22666
24137
  try {
22667
- mkdirSync17(dirname19(path), { recursive: true });
24138
+ mkdirSync18(dirname20(path), { recursive: true });
22668
24139
  appendFileSync3(path, JSON.stringify(event) + "\n", "utf-8");
22669
24140
  } catch {
22670
24141
  }
22671
24142
  }
22672
24143
  function readRefreshLog(projectRoot, limit = 10, opts = {}) {
22673
24144
  const path = refreshLogPath(projectRoot);
22674
- if (!existsSync38(path)) return [];
24145
+ if (!existsSync40(path)) return [];
22675
24146
  const warn = opts.warn ?? ((s) => {
22676
24147
  process.stderr.write(s);
22677
24148
  });
22678
- const lines = readFileSync29(path, "utf-8").split("\n").filter(Boolean);
24149
+ const lines = readFileSync31(path, "utf-8").split("\n").filter(Boolean);
22679
24150
  const tail = lines.slice(-limit);
22680
24151
  const out = [];
22681
24152
  let corrupt = 0;
@@ -22748,26 +24219,26 @@ var init_refresh_log = __esm({
22748
24219
  import {
22749
24220
  chmodSync as chmodSync6,
22750
24221
  closeSync as closeSync5,
22751
- existsSync as existsSync39,
24222
+ existsSync as existsSync41,
22752
24223
  fsyncSync as fsyncSync5,
22753
- mkdirSync as mkdirSync18,
24224
+ mkdirSync as mkdirSync19,
22754
24225
  openSync as openSync5,
22755
24226
  renameSync as renameSync6,
22756
24227
  rmSync as rmSync10,
22757
24228
  statSync as statSync15,
22758
24229
  writeSync as writeSync4
22759
24230
  } from "node:fs";
22760
- import { dirname as dirname20 } from "node:path";
24231
+ import { dirname as dirname21 } from "node:path";
22761
24232
  function atomicWrite(path, content, opts = {}) {
22762
24233
  const tmpPath = `${path}.tmp`;
22763
- const parentDir = dirname20(path);
24234
+ const parentDir = dirname21(path);
22764
24235
  try {
22765
- if (!existsSync39(parentDir)) {
24236
+ if (!existsSync41(parentDir)) {
22766
24237
  const mkdirOpts = { recursive: true };
22767
24238
  if (opts.ensureParentDirMode !== void 0) {
22768
24239
  mkdirOpts.mode = opts.ensureParentDirMode;
22769
24240
  }
22770
- mkdirSync18(parentDir, mkdirOpts);
24241
+ mkdirSync19(parentDir, mkdirOpts);
22771
24242
  }
22772
24243
  const buf = typeof content === "string" ? Buffer.from(content, "utf-8") : content;
22773
24244
  const openMode = opts.mode ?? 420;
@@ -22784,7 +24255,7 @@ function atomicWrite(path, content, opts = {}) {
22784
24255
  renameSync6(tmpPath, path);
22785
24256
  return { written: true };
22786
24257
  } catch (err) {
22787
- if (existsSync39(tmpPath)) {
24258
+ if (existsSync41(tmpPath)) {
22788
24259
  try {
22789
24260
  rmSync10(tmpPath, { force: true });
22790
24261
  } catch {
@@ -22864,10 +24335,10 @@ var init_registry_pubkey_generated = __esm({
22864
24335
  });
22865
24336
 
22866
24337
  // src/security/adapter-verifier.ts
22867
- import { createHash as createHash10 } from "node:crypto";
24338
+ import { createHash as createHash11 } from "node:crypto";
22868
24339
  import nacl from "tweetnacl";
22869
24340
  function sha256Hex2(bytes) {
22870
- return createHash10("sha256").update(bytes).digest("hex");
24341
+ return createHash11("sha256").update(bytes).digest("hex");
22871
24342
  }
22872
24343
  function reviver(key, value) {
22873
24344
  if (key === "__proto__" || key === "constructor" || key === "prototype") {
@@ -23133,24 +24604,24 @@ var init_fetcher = __esm({
23133
24604
  });
23134
24605
 
23135
24606
  // src/security/manifest-cache.ts
23136
- import { existsSync as existsSync40, readFileSync as readFileSync30, statSync as statSync16 } from "node:fs";
23137
- 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";
23138
24609
  import { resolve as resolve23 } from "node:path";
23139
24610
  import { z as z5 } from "zod";
23140
24611
  function defaultCachePaths() {
23141
- const dir = resolve23(homedir12(), ".massu");
24612
+ const dir = resolve23(homedir13(), ".massu");
23142
24613
  return {
23143
24614
  cachePath: resolve23(dir, "adapter-manifest.json"),
23144
24615
  lockPath: resolve23(dir, ".adapter-manifest.lock")
23145
24616
  };
23146
24617
  }
23147
24618
  function loadCachedManifest(paths = defaultCachePaths()) {
23148
- if (!existsSync40(paths.cachePath)) {
24619
+ if (!existsSync42(paths.cachePath)) {
23149
24620
  return { kind: "absent" };
23150
24621
  }
23151
24622
  let raw;
23152
24623
  try {
23153
- const content = readFileSync30(paths.cachePath, "utf-8");
24624
+ const content = readFileSync32(paths.cachePath, "utf-8");
23154
24625
  raw = JSON.parse(content);
23155
24626
  } catch (err) {
23156
24627
  return {
@@ -23339,15 +24810,15 @@ var init_adapter_origin = __esm({
23339
24810
  });
23340
24811
 
23341
24812
  // src/security/local-fingerprint.ts
23342
- import { existsSync as existsSync41, readFileSync as readFileSync31, lstatSync as lstatSync5 } from "node:fs";
23343
- import { homedir as homedir13 } from "node:os";
23344
- import { resolve as resolve24, isAbsolute as isAbsolute2 } from "node:path";
23345
- 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";
23346
24817
  import { z as z6 } from "zod";
23347
24818
  function computeLocalFingerprint(localPaths, projectRoot) {
23348
24819
  const tuples = [];
23349
24820
  for (const p19 of localPaths) {
23350
- const abs = isAbsolute2(p19) ? p19 : resolve24(projectRoot, p19);
24821
+ const abs = isAbsolute3(p19) ? p19 : resolve24(projectRoot, p19);
23351
24822
  let contentTag;
23352
24823
  try {
23353
24824
  const lst = lstatSync5(abs);
@@ -23356,7 +24827,7 @@ function computeLocalFingerprint(localPaths, projectRoot) {
23356
24827
  } else if (!lst.isFile()) {
23357
24828
  contentTag = "<not-a-file>";
23358
24829
  } else {
23359
- contentTag = createHash11("sha256").update(readFileSync31(abs)).digest("hex");
24830
+ contentTag = createHash12("sha256").update(readFileSync33(abs)).digest("hex");
23360
24831
  }
23361
24832
  } catch {
23362
24833
  contentTag = "<missing>";
@@ -23365,13 +24836,13 @@ function computeLocalFingerprint(localPaths, projectRoot) {
23365
24836
  }
23366
24837
  tuples.sort((a2, b2) => a2.path < b2.path ? -1 : a2.path > b2.path ? 1 : 0);
23367
24838
  const canonical = JSON.stringify(tuples);
23368
- return createHash11("sha256").update(canonical).digest("hex");
24839
+ return createHash12("sha256").update(canonical).digest("hex");
23369
24840
  }
23370
24841
  function readFingerprintSentinel(path = FINGERPRINT_PATH) {
23371
- if (!existsSync41(path)) return null;
24842
+ if (!existsSync43(path)) return null;
23372
24843
  let raw;
23373
24844
  try {
23374
- raw = JSON.parse(readFileSync31(path, "utf-8"));
24845
+ raw = JSON.parse(readFileSync33(path, "utf-8"));
23375
24846
  } catch {
23376
24847
  return null;
23377
24848
  }
@@ -23419,7 +24890,7 @@ var init_local_fingerprint = __esm({
23419
24890
  "use strict";
23420
24891
  init_atomic_write();
23421
24892
  init_manifest_schema();
23422
- FINGERPRINT_PATH = resolve24(homedir13(), ".massu", "adapters-local-fingerprint.json");
24893
+ FINGERPRINT_PATH = resolve24(homedir14(), ".massu", "adapters-local-fingerprint.json");
23423
24894
  FingerprintSentinelSchema = z6.object({
23424
24895
  fingerprint: z6.string().regex(/^[0-9a-f]{64}$/),
23425
24896
  source: z6.enum(["cli", "cli-resync"]),
@@ -23435,15 +24906,15 @@ var init_local_fingerprint = __esm({
23435
24906
  });
23436
24907
 
23437
24908
  // src/security/install-tracking.ts
23438
- import { readFileSync as readFileSync32, readdirSync as readdirSync16, lstatSync as lstatSync6, existsSync as existsSync42 } from "node:fs";
23439
- import { join as join22, relative as relative6, sep as sep2 } from "node:path";
23440
- 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";
23441
24912
  import { resolve as resolve25 } from "node:path";
23442
- import { createHash as createHash12 } from "node:crypto";
24913
+ import { createHash as createHash13 } from "node:crypto";
23443
24914
  import { z as z7 } from "zod";
23444
24915
  function containsHiddenDirs(packageDir) {
23445
24916
  for (const hidden of EXCLUDED_DIR_NAMES) {
23446
- if (existsSync42(`${packageDir}/${hidden}`)) {
24917
+ if (existsSync44(`${packageDir}/${hidden}`)) {
23447
24918
  return hidden;
23448
24919
  }
23449
24920
  }
@@ -23460,7 +24931,7 @@ function sha256OfDir(dir, opts = {}) {
23460
24931
  return;
23461
24932
  }
23462
24933
  for (const entry of entries.sort()) {
23463
- const absPath = join22(currentDir, entry);
24934
+ const absPath = join24(currentDir, entry);
23464
24935
  let lst;
23465
24936
  try {
23466
24937
  lst = lstatSync6(absPath);
@@ -23481,15 +24952,15 @@ function sha256OfDir(dir, opts = {}) {
23481
24952
  `sha256OfDir: file ${absPath} exceeds maxFileBytes (${lst.size} > ${maxFileBytes}); adapter packages should not ship files this large.`
23482
24953
  );
23483
24954
  }
23484
- const rel = relative6(dir, absPath).split(sep2).join("/");
24955
+ const rel = relative7(dir, absPath).split(sep2).join("/");
23485
24956
  files.push({ relativePath: rel, absPath });
23486
24957
  }
23487
24958
  }
23488
24959
  walk(dir);
23489
24960
  files.sort((a2, b2) => a2.relativePath < b2.relativePath ? -1 : a2.relativePath > b2.relativePath ? 1 : 0);
23490
- const top = createHash12("sha256");
24961
+ const top = createHash13("sha256");
23491
24962
  for (const f2 of files) {
23492
- const fileHash = createHash12("sha256").update(readFileSync32(f2.absPath)).digest("hex");
24963
+ const fileHash = createHash13("sha256").update(readFileSync34(f2.absPath)).digest("hex");
23493
24964
  top.update(f2.relativePath, "utf-8");
23494
24965
  top.update("\0", "utf-8");
23495
24966
  top.update(fileHash, "utf-8");
@@ -23498,10 +24969,10 @@ function sha256OfDir(dir, opts = {}) {
23498
24969
  return top.digest("hex");
23499
24970
  }
23500
24971
  function readInstalledManifest(path = INSTALLED_MANIFEST_PATH) {
23501
- if (!existsSync42(path)) return {};
24972
+ if (!existsSync44(path)) return {};
23502
24973
  let raw;
23503
24974
  try {
23504
- raw = JSON.parse(readFileSync32(path, "utf-8"));
24975
+ raw = JSON.parse(readFileSync34(path, "utf-8"));
23505
24976
  } catch {
23506
24977
  return {};
23507
24978
  }
@@ -23565,7 +25036,7 @@ var init_install_tracking = __esm({
23565
25036
  "src/security/install-tracking.ts"() {
23566
25037
  "use strict";
23567
25038
  init_atomic_write();
23568
- INSTALLED_MANIFEST_PATH = resolve25(homedir14(), ".massu", "adapter-manifest-installed.json");
25039
+ INSTALLED_MANIFEST_PATH = resolve25(homedir15(), ".massu", "adapter-manifest-installed.json");
23569
25040
  DEFAULT_MAX_FILE_BYTES = 64 * 1024 * 1024;
23570
25041
  EXCLUDED_DIR_NAMES = /* @__PURE__ */ new Set([".git", "node_modules", ".cache", ".tmp"]);
23571
25042
  InstallEntrySchema = z7.object({
@@ -23588,12 +25059,12 @@ var init_install_tracking = __esm({
23588
25059
  });
23589
25060
 
23590
25061
  // src/detect/adapters/discover.ts
23591
- import { existsSync as existsSync43, readdirSync as readdirSync17, readFileSync as readFileSync33, lstatSync as lstatSync7 } from "node:fs";
23592
- 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";
23593
25064
  import { z as z8 } from "zod";
23594
25065
  function walkNodeModules(projectRoot, warnings) {
23595
25066
  const nodeModulesDir = resolve26(projectRoot, "node_modules");
23596
- if (!existsSync43(nodeModulesDir)) {
25067
+ if (!existsSync45(nodeModulesDir)) {
23597
25068
  return [];
23598
25069
  }
23599
25070
  const candidates = [];
@@ -23648,10 +25119,10 @@ function walkNodeModules(projectRoot, warnings) {
23648
25119
  }
23649
25120
  function tryReadAdapterPackage(packageDir, warnings) {
23650
25121
  const pkgJsonPath = resolve26(packageDir, "package.json");
23651
- if (!existsSync43(pkgJsonPath)) return null;
25122
+ if (!existsSync45(pkgJsonPath)) return null;
23652
25123
  let raw;
23653
25124
  try {
23654
- raw = JSON.parse(readFileSync33(pkgJsonPath, "utf-8"));
25125
+ raw = JSON.parse(readFileSync35(pkgJsonPath, "utf-8"));
23655
25126
  } catch (err) {
23656
25127
  warnings.push(
23657
25128
  `skipping ${packageDir}: package.json parse failed (${err instanceof Error ? err.message : String(err)})`
@@ -23785,8 +25256,8 @@ function discoverAdapters(opts) {
23785
25256
  const localSet = new Set(opts.configLocalPaths);
23786
25257
  for (const localPath of opts.configLocalPaths) {
23787
25258
  if (seenIds.has(localPath)) continue;
23788
- const absPath = isAbsolute3(localPath) ? localPath : resolve26(opts.projectRoot, localPath);
23789
- if (!existsSync43(absPath)) {
25259
+ const absPath = isAbsolute4(localPath) ? localPath : resolve26(opts.projectRoot, localPath);
25260
+ if (!existsSync45(absPath)) {
23790
25261
  warnings.push(
23791
25262
  `local adapter file not found: ${localPath} (resolved to ${absPath}). Remove via: massu adapters remove-local ${localPath}`
23792
25263
  );
@@ -23868,7 +25339,7 @@ __export(adapters_exports, {
23868
25339
  runAdaptersResyncLocalFingerprint: () => runAdaptersResyncLocalFingerprint,
23869
25340
  runAdaptersSearch: () => runAdaptersSearch
23870
25341
  });
23871
- import { existsSync as existsSync44, readFileSync as readFileSync34 } from "node:fs";
25342
+ import { existsSync as existsSync46, readFileSync as readFileSync36 } from "node:fs";
23872
25343
  import { resolve as resolve27 } from "node:path";
23873
25344
  import { parseDocument } from "yaml";
23874
25345
  async function handleAdaptersSubcommand(args2) {
@@ -24137,7 +25608,7 @@ function mutateLocalArray(mutator, command) {
24137
25608
  return { exitCode: 2 };
24138
25609
  }
24139
25610
  const yamlPath = resolve27(projectRoot, "massu.config.yaml");
24140
- if (!existsSync44(yamlPath)) {
25611
+ if (!existsSync46(yamlPath)) {
24141
25612
  process.stderr.write(
24142
25613
  `${command}: massu.config.yaml not found at ${yamlPath}. Run \`massu init\` first.
24143
25614
  `
@@ -24146,7 +25617,7 @@ function mutateLocalArray(mutator, command) {
24146
25617
  }
24147
25618
  let yamlText;
24148
25619
  try {
24149
- yamlText = readFileSync34(yamlPath, "utf-8");
25620
+ yamlText = readFileSync36(yamlPath, "utf-8");
24150
25621
  } catch (err) {
24151
25622
  process.stderr.write(`${command}: failed to read ${yamlPath}: ${err instanceof Error ? err.message : String(err)}
24152
25623
  `);
@@ -24237,7 +25708,7 @@ async function runAdaptersInstall(args2) {
24237
25708
  return { exitCode: 1 };
24238
25709
  }
24239
25710
  const packageDir = resolve27(projectRoot, "node_modules", ...packageName.split("/"));
24240
- if (!existsSync44(packageDir)) {
25711
+ if (!existsSync46(packageDir)) {
24241
25712
  process.stderr.write(
24242
25713
  `install: ${packageName} is not installed in node_modules. Run \`npm install ${packageName}\` first.
24243
25714
  `
@@ -24245,14 +25716,14 @@ async function runAdaptersInstall(args2) {
24245
25716
  return { exitCode: 1 };
24246
25717
  }
24247
25718
  const pkgJsonPath = resolve27(packageDir, "package.json");
24248
- if (!existsSync44(pkgJsonPath)) {
25719
+ if (!existsSync46(pkgJsonPath)) {
24249
25720
  process.stderr.write(`install: ${packageName} has no package.json at ${pkgJsonPath}
24250
25721
  `);
24251
25722
  return { exitCode: 1 };
24252
25723
  }
24253
25724
  let pkgJson;
24254
25725
  try {
24255
- pkgJson = JSON.parse(readFileSync34(pkgJsonPath, "utf-8"));
25726
+ pkgJson = JSON.parse(readFileSync36(pkgJsonPath, "utf-8"));
24256
25727
  } catch (err) {
24257
25728
  process.stderr.write(`install: ${packageName} has malformed package.json: ${err instanceof Error ? err.message : String(err)}
24258
25729
  `);
@@ -24372,7 +25843,7 @@ async function runAdaptersResign(_args) {
24372
25843
  continue;
24373
25844
  }
24374
25845
  const packageDir = resolve27(projectRoot, "node_modules", ...name.split("/"));
24375
- if (!existsSync44(packageDir)) {
25846
+ if (!existsSync46(packageDir)) {
24376
25847
  removed++;
24377
25848
  warnings.push(`${name}@${entry.version}: not present in node_modules \u2014 REMOVED from sidecar`);
24378
25849
  removeInstalledManifestEntry(name);
@@ -24459,10 +25930,10 @@ var init_adapters2 = __esm({
24459
25930
 
24460
25931
  // src/preflight.ts
24461
25932
  import Database4 from "better-sqlite3";
24462
- import { existsSync as existsSync45 } from "fs";
25933
+ import { existsSync as existsSync47 } from "fs";
24463
25934
  function assertCodegraphUsable(dbPath) {
24464
25935
  const path = dbPath ?? getResolvedPaths().codegraphDbPath;
24465
- if (!existsSync45(path)) {
25936
+ if (!existsSync47(path)) {
24466
25937
  throw new CodegraphDbUnusableError(path, "missing");
24467
25938
  }
24468
25939
  let db = null;
@@ -24551,8 +26022,8 @@ var init_sql_table_names = __esm({
24551
26022
 
24552
26023
  // src/db.ts
24553
26024
  import Database5 from "better-sqlite3";
24554
- import { dirname as dirname21, join as join23 } from "path";
24555
- 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";
24556
26027
  function getCodeGraphDb() {
24557
26028
  const dbPath = getResolvedPaths().codegraphDbPath;
24558
26029
  assertCodegraphUsable(dbPath);
@@ -24562,9 +26033,9 @@ function getCodeGraphDb() {
24562
26033
  }
24563
26034
  function getDataDb() {
24564
26035
  const dbPath = getResolvedPaths().dataDbPath;
24565
- const dir = dirname21(dbPath);
24566
- if (!existsSync46(dir)) {
24567
- mkdirSync19(dir, { recursive: true });
26036
+ const dir = dirname22(dbPath);
26037
+ if (!existsSync48(dir)) {
26038
+ mkdirSync20(dir, { recursive: true });
24568
26039
  }
24569
26040
  const db = new Database5(dbPath);
24570
26041
  db.pragma("journal_mode = WAL");
@@ -24836,7 +26307,7 @@ function isPythonDataStale(dataDb, pythonRoot) {
24836
26307
  try {
24837
26308
  const entries = readdirSync18(dir, { withFileTypes: true });
24838
26309
  for (const entry of entries) {
24839
- const fullPath = join23(dir, entry.name);
26310
+ const fullPath = join25(dir, entry.name);
24840
26311
  if (entry.isDirectory()) {
24841
26312
  if (["__pycache__", ".venv", "venv", "node_modules", ".mypy_cache", ".pytest_cache"].includes(entry.name)) continue;
24842
26313
  if (checkDir(fullPath)) return true;
@@ -24940,8 +26411,8 @@ var init_rules = __esm({
24940
26411
  });
24941
26412
 
24942
26413
  // src/import-resolver.ts
24943
- import { readFileSync as readFileSync35, existsSync as existsSync47, statSync as statSync18 } from "fs";
24944
- 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";
24945
26416
  function parseImports(source) {
24946
26417
  const imports = [];
24947
26418
  const lines = source.split("\n");
@@ -24999,21 +26470,21 @@ function resolveImportPath(specifier, fromFile) {
24999
26470
  const paths = getResolvedPaths();
25000
26471
  basePath = resolve29(paths.pathAlias["@"] ?? paths.srcDir, specifier.slice(2));
25001
26472
  } else {
25002
- basePath = resolve29(dirname22(fromFile), specifier);
26473
+ basePath = resolve29(dirname23(fromFile), specifier);
25003
26474
  }
25004
- if (existsSync47(basePath) && !isDirectory(basePath)) {
26475
+ if (existsSync49(basePath) && !isDirectory(basePath)) {
25005
26476
  return toRelative(basePath);
25006
26477
  }
25007
26478
  const resolvedPaths = getResolvedPaths();
25008
26479
  for (const ext of resolvedPaths.extensions) {
25009
26480
  const withExt = basePath + ext;
25010
- if (existsSync47(withExt)) {
26481
+ if (existsSync49(withExt)) {
25011
26482
  return toRelative(withExt);
25012
26483
  }
25013
26484
  }
25014
26485
  for (const indexFile of resolvedPaths.indexFiles) {
25015
- const indexPath = join24(basePath, indexFile);
25016
- if (existsSync47(indexPath)) {
26486
+ const indexPath = join26(basePath, indexFile);
26487
+ if (existsSync49(indexPath)) {
25017
26488
  return toRelative(indexPath);
25018
26489
  }
25019
26490
  }
@@ -25050,10 +26521,10 @@ function buildImportIndex(dataDb, codegraphDb) {
25050
26521
  let batch = [];
25051
26522
  for (const file of files) {
25052
26523
  const absPath = ensureWithinRoot(resolve29(projectRoot, file.path), projectRoot);
25053
- if (!existsSync47(absPath)) continue;
26524
+ if (!existsSync49(absPath)) continue;
25054
26525
  let source;
25055
26526
  try {
25056
- source = readFileSync35(absPath, "utf-8");
26527
+ source = readFileSync37(absPath, "utf-8");
25057
26528
  } catch {
25058
26529
  continue;
25059
26530
  }
@@ -25090,15 +26561,15 @@ var init_import_resolver = __esm({
25090
26561
  });
25091
26562
 
25092
26563
  // src/trpc-index.ts
25093
- import { readFileSync as readFileSync36, existsSync as existsSync48, readdirSync as readdirSync19 } from "fs";
25094
- 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";
25095
26566
  function parseRootRouter() {
25096
26567
  const paths = getResolvedPaths();
25097
26568
  const rootPath = paths.rootRouterPath;
25098
- if (!existsSync48(rootPath)) {
26569
+ if (!existsSync50(rootPath)) {
25099
26570
  throw new Error(`Root router not found at ${rootPath}`);
25100
26571
  }
25101
- const source = readFileSync36(rootPath, "utf-8");
26572
+ const source = readFileSync38(rootPath, "utf-8");
25102
26573
  const mappings = [];
25103
26574
  const importMap = /* @__PURE__ */ new Map();
25104
26575
  const importRegex = /import\s+\{[^}]*?(\w+Router)[^}]*\}\s+from\s+['"]\.\/routers\/([^'"]+)['"]/g;
@@ -25110,12 +26581,12 @@ function parseRootRouter() {
25110
26581
  for (const ext of [".ts", ".tsx", ""]) {
25111
26582
  const candidate = fullPath + ext;
25112
26583
  const routersRelPath = getConfig().paths.routers ?? "src/server/api/routers";
25113
- if (existsSync48(candidate)) {
26584
+ if (existsSync50(candidate)) {
25114
26585
  filePath = routersRelPath + "/" + filePath + ext;
25115
26586
  break;
25116
26587
  }
25117
- const indexCandidate = join25(fullPath, "index.ts");
25118
- if (existsSync48(indexCandidate)) {
26588
+ const indexCandidate = join27(fullPath, "index.ts");
26589
+ if (existsSync50(indexCandidate)) {
25119
26590
  filePath = routersRelPath + "/" + filePath + "/index.ts";
25120
26591
  break;
25121
26592
  }
@@ -25135,8 +26606,8 @@ function parseRootRouter() {
25135
26606
  }
25136
26607
  function extractProcedures(routerFilePath) {
25137
26608
  const absPath = resolve30(getProjectRoot(), routerFilePath);
25138
- if (!existsSync48(absPath)) return [];
25139
- const source = readFileSync36(absPath, "utf-8");
26609
+ if (!existsSync50(absPath)) return [];
26610
+ const source = readFileSync38(absPath, "utf-8");
25140
26611
  const procedures = [];
25141
26612
  const seen = /* @__PURE__ */ new Set();
25142
26613
  const procRegex = /(\w+)\s*:\s*(protected|public)Procedure/g;
@@ -25165,7 +26636,7 @@ function findUICallSites(routerKey, procedureName) {
25165
26636
  ];
25166
26637
  const searchPattern = `api.${routerKey}.${procedureName}`;
25167
26638
  for (const dir of searchDirs) {
25168
- if (!existsSync48(dir)) continue;
26639
+ if (!existsSync50(dir)) continue;
25169
26640
  searchDirectory(dir, searchPattern, callSites);
25170
26641
  }
25171
26642
  return callSites;
@@ -25173,13 +26644,13 @@ function findUICallSites(routerKey, procedureName) {
25173
26644
  function searchDirectory(dir, pattern, results) {
25174
26645
  const entries = readdirSync19(dir, { withFileTypes: true });
25175
26646
  for (const entry of entries) {
25176
- const fullPath = join25(dir, entry.name);
26647
+ const fullPath = join27(dir, entry.name);
25177
26648
  if (entry.isDirectory()) {
25178
26649
  if (entry.name === "node_modules" || entry.name === ".next") continue;
25179
26650
  searchDirectory(fullPath, pattern, results);
25180
26651
  } else if (entry.name.endsWith(".ts") || entry.name.endsWith(".tsx")) {
25181
26652
  try {
25182
- const source = readFileSync36(fullPath, "utf-8");
26653
+ const source = readFileSync38(fullPath, "utf-8");
25183
26654
  const lines = source.split("\n");
25184
26655
  for (let i = 0; i < lines.length; i++) {
25185
26656
  if (lines[i].includes(pattern)) {
@@ -25243,7 +26714,7 @@ var init_trpc_index = __esm({
25243
26714
  });
25244
26715
 
25245
26716
  // src/page-deps.ts
25246
- import { readFileSync as readFileSync37, existsSync as existsSync49 } from "fs";
26717
+ import { readFileSync as readFileSync39, existsSync as existsSync51 } from "fs";
25247
26718
  import { resolve as resolve31 } from "path";
25248
26719
  function deriveRoute(pageFile) {
25249
26720
  let route = pageFile.replace(/^src\/app/, "").replace(/\/page\.tsx?$/, "").replace(/\/page\.jsx?$/, "");
@@ -25283,9 +26754,9 @@ function findRouterCalls(files) {
25283
26754
  const projectRoot = getProjectRoot();
25284
26755
  for (const file of files) {
25285
26756
  const absPath = ensureWithinRoot(resolve31(projectRoot, file), projectRoot);
25286
- if (!existsSync49(absPath)) continue;
26757
+ if (!existsSync51(absPath)) continue;
25287
26758
  try {
25288
- const source = readFileSync37(absPath, "utf-8");
26759
+ const source = readFileSync39(absPath, "utf-8");
25289
26760
  const apiCallRegex = /api\.(\w+)\.\w+/g;
25290
26761
  let match;
25291
26762
  while ((match = apiCallRegex.exec(source)) !== null) {
@@ -25304,9 +26775,9 @@ function findTablesFromRouters(routerNames, dataDb) {
25304
26775
  ).all(routerName);
25305
26776
  for (const proc of procs) {
25306
26777
  const absPath = ensureWithinRoot(resolve31(getProjectRoot(), proc.router_file), getProjectRoot());
25307
- if (!existsSync49(absPath)) continue;
26778
+ if (!existsSync51(absPath)) continue;
25308
26779
  try {
25309
- const source = readFileSync37(absPath, "utf-8");
26780
+ const source = readFileSync39(absPath, "utf-8");
25310
26781
  const dbPattern = getConfig().dbAccessPattern ?? "ctx.db.{table}";
25311
26782
  const regexStr = dbPattern.replace(/[.*+?^${}()|[\]\\]/g, "\\$&").replace("\\{table\\}", "(\\w+)");
25312
26783
  const tableRegex = new RegExp(regexStr + "\\.", "g");
@@ -25578,14 +27049,14 @@ var init_domains = __esm({
25578
27049
  });
25579
27050
 
25580
27051
  // src/schema-mapper.ts
25581
- import { readFileSync as readFileSync38, existsSync as existsSync50, readdirSync as readdirSync20 } from "fs";
25582
- 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";
25583
27054
  function parsePrismaSchema() {
25584
27055
  const schemaPath = getResolvedPaths().prismaSchemaPath;
25585
- if (!existsSync50(schemaPath)) {
27056
+ if (!existsSync52(schemaPath)) {
25586
27057
  throw new Error(`Prisma schema not found at ${schemaPath}`);
25587
27058
  }
25588
- const source = readFileSync38(schemaPath, "utf-8");
27059
+ const source = readFileSync40(schemaPath, "utf-8");
25589
27060
  const models = [];
25590
27061
  const sourceLines = source.split("\n");
25591
27062
  let i = 0;
@@ -25643,14 +27114,14 @@ function toSnakeCase(str) {
25643
27114
  function findColumnUsageInRouters(tableName) {
25644
27115
  const usage2 = /* @__PURE__ */ new Map();
25645
27116
  const routersDir = getResolvedPaths().routersDir;
25646
- if (!existsSync50(routersDir)) return usage2;
27117
+ if (!existsSync52(routersDir)) return usage2;
25647
27118
  scanDirectory(routersDir, tableName, usage2);
25648
27119
  return usage2;
25649
27120
  }
25650
27121
  function scanDirectory(dir, tableName, usage2) {
25651
27122
  const entries = readdirSync20(dir, { withFileTypes: true });
25652
27123
  for (const entry of entries) {
25653
- const fullPath = join26(dir, entry.name);
27124
+ const fullPath = join28(dir, entry.name);
25654
27125
  if (entry.isDirectory()) {
25655
27126
  scanDirectory(fullPath, tableName, usage2);
25656
27127
  } else if (entry.name.endsWith(".ts")) {
@@ -25660,7 +27131,7 @@ function scanDirectory(dir, tableName, usage2) {
25660
27131
  }
25661
27132
  function scanFile(absPath, tableName, usage2) {
25662
27133
  try {
25663
- const source = readFileSync38(absPath, "utf-8");
27134
+ const source = readFileSync40(absPath, "utf-8");
25664
27135
  if (!source.includes(tableName)) return;
25665
27136
  const relPath = absPath.slice(getProjectRoot().length + 1);
25666
27137
  const lines = source.split("\n");
@@ -25705,15 +27176,15 @@ function detectMismatches(models) {
25705
27176
  }
25706
27177
  function findFilesUsingColumn(dir, column, tableName) {
25707
27178
  const result = [];
25708
- if (!existsSync50(dir)) return result;
27179
+ if (!existsSync52(dir)) return result;
25709
27180
  const entries = readdirSync20(dir, { withFileTypes: true });
25710
27181
  for (const entry of entries) {
25711
- const fullPath = join26(dir, entry.name);
27182
+ const fullPath = join28(dir, entry.name);
25712
27183
  if (entry.isDirectory()) {
25713
27184
  result.push(...findFilesUsingColumn(fullPath, column, tableName));
25714
27185
  } else if (entry.name.endsWith(".ts")) {
25715
27186
  try {
25716
- const source = readFileSync38(fullPath, "utf-8");
27187
+ const source = readFileSync40(fullPath, "utf-8");
25717
27188
  if (source.includes(tableName) && source.includes(column)) {
25718
27189
  result.push(fullPath.slice(getProjectRoot().length + 1));
25719
27190
  }
@@ -25858,35 +27329,35 @@ var init_import_parser = __esm({
25858
27329
  });
25859
27330
 
25860
27331
  // src/python/import-resolver.ts
25861
- import { readFileSync as readFileSync39, existsSync as existsSync51, readdirSync as readdirSync21 } from "fs";
25862
- 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";
25863
27334
  function resolvePythonModulePath(module, fromFile, pythonRoot, level) {
25864
27335
  const projectRoot = getProjectRoot();
25865
27336
  if (level > 0) {
25866
- let baseDir = dirname23(resolve33(projectRoot, fromFile));
27337
+ let baseDir = dirname24(resolve33(projectRoot, fromFile));
25867
27338
  for (let i = 1; i < level; i++) {
25868
- baseDir = dirname23(baseDir);
27339
+ baseDir = dirname24(baseDir);
25869
27340
  }
25870
27341
  const modulePart = module.replace(/^\.+/, "");
25871
27342
  if (modulePart) {
25872
27343
  const parts2 = modulePart.split(".");
25873
- return tryResolvePythonPath(join27(baseDir, ...parts2), projectRoot);
27344
+ return tryResolvePythonPath(join29(baseDir, ...parts2), projectRoot);
25874
27345
  }
25875
27346
  return tryResolvePythonPath(baseDir, projectRoot);
25876
27347
  }
25877
27348
  const parts = module.split(".");
25878
- const candidate = join27(resolve33(projectRoot, pythonRoot), ...parts);
27349
+ const candidate = join29(resolve33(projectRoot, pythonRoot), ...parts);
25879
27350
  return tryResolvePythonPath(candidate, projectRoot);
25880
27351
  }
25881
27352
  function tryResolvePythonPath(basePath, projectRoot) {
25882
- if (existsSync51(basePath + ".py")) {
25883
- return relative7(projectRoot, basePath + ".py");
27353
+ if (existsSync53(basePath + ".py")) {
27354
+ return relative8(projectRoot, basePath + ".py");
25884
27355
  }
25885
- if (existsSync51(join27(basePath, "__init__.py"))) {
25886
- return relative7(projectRoot, join27(basePath, "__init__.py"));
27356
+ if (existsSync53(join29(basePath, "__init__.py"))) {
27357
+ return relative8(projectRoot, join29(basePath, "__init__.py"));
25887
27358
  }
25888
- if (basePath.endsWith(".py") && existsSync51(basePath)) {
25889
- return relative7(projectRoot, basePath);
27359
+ if (basePath.endsWith(".py") && existsSync53(basePath)) {
27360
+ return relative8(projectRoot, basePath);
25890
27361
  }
25891
27362
  return null;
25892
27363
  }
@@ -25897,9 +27368,9 @@ function walkPythonFiles(dir, excludeDirs) {
25897
27368
  for (const entry of entries) {
25898
27369
  if (entry.isDirectory()) {
25899
27370
  if (excludeDirs.includes(entry.name)) continue;
25900
- files.push(...walkPythonFiles(join27(dir, entry.name), excludeDirs));
27371
+ files.push(...walkPythonFiles(join29(dir, entry.name), excludeDirs));
25901
27372
  } else if (entry.name.endsWith(".py")) {
25902
- files.push(join27(dir, entry.name));
27373
+ files.push(join29(dir, entry.name));
25903
27374
  }
25904
27375
  }
25905
27376
  } catch {
@@ -25922,10 +27393,10 @@ function buildPythonImportIndex(dataDb, pythonRoot, excludeDirs = ["__pycache__"
25922
27393
  });
25923
27394
  const batch = [];
25924
27395
  for (const absFile of files) {
25925
- const relFile = relative7(projectRoot, absFile);
27396
+ const relFile = relative8(projectRoot, absFile);
25926
27397
  let source;
25927
27398
  try {
25928
- source = readFileSync39(absFile, "utf-8");
27399
+ source = readFileSync41(absFile, "utf-8");
25929
27400
  } catch {
25930
27401
  continue;
25931
27402
  }
@@ -26192,8 +27663,8 @@ var init_route_parser = __esm({
26192
27663
  });
26193
27664
 
26194
27665
  // src/python/route-indexer.ts
26195
- import { readFileSync as readFileSync40, readdirSync as readdirSync22 } from "fs";
26196
- 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";
26197
27668
  function walkPyFiles(dir, excludeDirs) {
26198
27669
  const files = [];
26199
27670
  try {
@@ -26201,9 +27672,9 @@ function walkPyFiles(dir, excludeDirs) {
26201
27672
  for (const entry of entries) {
26202
27673
  if (entry.isDirectory()) {
26203
27674
  if (excludeDirs.includes(entry.name)) continue;
26204
- files.push(...walkPyFiles(join28(dir, entry.name), excludeDirs));
27675
+ files.push(...walkPyFiles(join30(dir, entry.name), excludeDirs));
26205
27676
  } else if (entry.name.endsWith(".py")) {
26206
- files.push(join28(dir, entry.name));
27677
+ files.push(join30(dir, entry.name));
26207
27678
  }
26208
27679
  }
26209
27680
  } catch {
@@ -26212,7 +27683,7 @@ function walkPyFiles(dir, excludeDirs) {
26212
27683
  }
26213
27684
  function buildPythonRouteIndex(dataDb, pythonRoot, excludeDirs = ["__pycache__", ".venv", "venv", ".mypy_cache", ".pytest_cache"]) {
26214
27685
  const projectRoot = getProjectRoot();
26215
- const absRoot = join28(projectRoot, pythonRoot);
27686
+ const absRoot = join30(projectRoot, pythonRoot);
26216
27687
  dataDb.exec(`DELETE FROM ${t("py_routes")}`);
26217
27688
  dataDb.exec(`DELETE FROM ${t("py_route_callers")}`);
26218
27689
  const insertStmt = dataDb.prepare(
@@ -26222,10 +27693,10 @@ function buildPythonRouteIndex(dataDb, pythonRoot, excludeDirs = ["__pycache__",
26222
27693
  let count = 0;
26223
27694
  dataDb.transaction(() => {
26224
27695
  for (const absFile of files) {
26225
- const relFile = relative8(projectRoot, absFile);
27696
+ const relFile = relative9(projectRoot, absFile);
26226
27697
  let source;
26227
27698
  try {
26228
- source = readFileSync40(absFile, "utf-8");
27699
+ source = readFileSync42(absFile, "utf-8");
26229
27700
  } catch {
26230
27701
  continue;
26231
27702
  }
@@ -26332,7 +27803,7 @@ function parsePythonModels(source) {
26332
27803
  const bases = classMatch[2];
26333
27804
  const classLine = i + 1;
26334
27805
  let isModel = isModelClass(bases);
26335
- const columns = [];
27806
+ const columns2 = [];
26336
27807
  const relationships = [];
26337
27808
  const foreignKeys = [];
26338
27809
  let tableName = null;
@@ -26375,7 +27846,7 @@ function parsePythonModels(source) {
26375
27846
  const colType = extractColumnType(argsStr);
26376
27847
  const nullable = extractNullable(argsStr);
26377
27848
  const primaryKey = extractPrimaryKey(argsStr);
26378
- columns.push({ name: colName, type: colType, nullable, primaryKey });
27849
+ columns2.push({ name: colName, type: colType, nullable, primaryKey });
26379
27850
  foreignKeys.push(...extractForeignKeys(colName, argsStr));
26380
27851
  }
26381
27852
  i++;
@@ -26389,7 +27860,7 @@ function parsePythonModels(source) {
26389
27860
  const mappedType = extractMappedType(mappedAnnotation);
26390
27861
  const nullable = mappedType.startsWith("Optional") || extractNullable(argsStr || "");
26391
27862
  const primaryKey = extractPrimaryKey(argsStr || "");
26392
- columns.push({ name: colName, type: mappedType, nullable, primaryKey });
27863
+ columns2.push({ name: colName, type: mappedType, nullable, primaryKey });
26393
27864
  if (argsStr) {
26394
27865
  foreignKeys.push(...extractForeignKeys(colName, argsStr));
26395
27866
  }
@@ -26419,7 +27890,7 @@ function parsePythonModels(source) {
26419
27890
  models.push({
26420
27891
  className,
26421
27892
  tableName,
26422
- columns,
27893
+ columns: columns2,
26423
27894
  relationships,
26424
27895
  foreignKeys,
26425
27896
  line: classLine
@@ -26437,8 +27908,8 @@ var init_model_parser = __esm({
26437
27908
  });
26438
27909
 
26439
27910
  // src/python/model-indexer.ts
26440
- import { readFileSync as readFileSync41, readdirSync as readdirSync23 } from "fs";
26441
- 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";
26442
27913
  function walkPyFiles2(dir, excludeDirs) {
26443
27914
  const files = [];
26444
27915
  try {
@@ -26446,9 +27917,9 @@ function walkPyFiles2(dir, excludeDirs) {
26446
27917
  for (const entry of entries) {
26447
27918
  if (entry.isDirectory()) {
26448
27919
  if (excludeDirs.includes(entry.name)) continue;
26449
- files.push(...walkPyFiles2(join29(dir, entry.name), excludeDirs));
27920
+ files.push(...walkPyFiles2(join31(dir, entry.name), excludeDirs));
26450
27921
  } else if (entry.name.endsWith(".py")) {
26451
- files.push(join29(dir, entry.name));
27922
+ files.push(join31(dir, entry.name));
26452
27923
  }
26453
27924
  }
26454
27925
  } catch {
@@ -26457,7 +27928,7 @@ function walkPyFiles2(dir, excludeDirs) {
26457
27928
  }
26458
27929
  function buildPythonModelIndex(dataDb, pythonRoot, excludeDirs = ["__pycache__", ".venv", "venv", ".mypy_cache", ".pytest_cache"]) {
26459
27930
  const projectRoot = getProjectRoot();
26460
- const absRoot = join29(projectRoot, pythonRoot);
27931
+ const absRoot = join31(projectRoot, pythonRoot);
26461
27932
  dataDb.exec(`DELETE FROM ${t("py_models")}`);
26462
27933
  dataDb.exec(`DELETE FROM ${t("py_fk_edges")}`);
26463
27934
  const insertModel = dataDb.prepare(
@@ -26470,10 +27941,10 @@ function buildPythonModelIndex(dataDb, pythonRoot, excludeDirs = ["__pycache__",
26470
27941
  let count = 0;
26471
27942
  dataDb.transaction(() => {
26472
27943
  for (const absFile of files) {
26473
- const relFile = relative9(projectRoot, absFile);
27944
+ const relFile = relative10(projectRoot, absFile);
26474
27945
  let source;
26475
27946
  try {
26476
- source = readFileSync41(absFile, "utf-8");
27947
+ source = readFileSync43(absFile, "utf-8");
26477
27948
  } catch {
26478
27949
  continue;
26479
27950
  }
@@ -26734,19 +28205,19 @@ var init_migration_parser = __esm({
26734
28205
  });
26735
28206
 
26736
28207
  // src/python/migration-indexer.ts
26737
- import { readFileSync as readFileSync42, readdirSync as readdirSync24 } from "fs";
26738
- 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";
26739
28210
  function buildPythonMigrationIndex(dataDb, alembicDir) {
26740
28211
  const projectRoot = getProjectRoot();
26741
- const absDir = join30(projectRoot, alembicDir);
28212
+ const absDir = join32(projectRoot, alembicDir);
26742
28213
  dataDb.exec(`DELETE FROM ${t("py_migrations")}`);
26743
- const versionsDir = join30(absDir, "versions");
28214
+ const versionsDir = join32(absDir, "versions");
26744
28215
  let files = [];
26745
28216
  try {
26746
- 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));
26747
28218
  } catch {
26748
28219
  try {
26749
- 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));
26750
28221
  } catch {
26751
28222
  }
26752
28223
  }
@@ -26760,7 +28231,7 @@ function buildPythonMigrationIndex(dataDb, alembicDir) {
26760
28231
  for (const absFile of files) {
26761
28232
  let source;
26762
28233
  try {
26763
- source = readFileSync42(absFile, "utf-8");
28234
+ source = readFileSync44(absFile, "utf-8");
26764
28235
  } catch {
26765
28236
  continue;
26766
28237
  }
@@ -26771,7 +28242,7 @@ function buildPythonMigrationIndex(dataDb, alembicDir) {
26771
28242
  rows.push({
26772
28243
  revision: parsed.revision,
26773
28244
  downRevision: parsed.downRevision,
26774
- file: relative10(projectRoot, absFile),
28245
+ file: relative11(projectRoot, absFile),
26775
28246
  description: parsed.description,
26776
28247
  operations: JSON.stringify(parsed.operations)
26777
28248
  });
@@ -26795,12 +28266,12 @@ var init_migration_indexer = __esm({
26795
28266
  });
26796
28267
 
26797
28268
  // src/python/coupling-detector.ts
26798
- import { readFileSync as readFileSync43, readdirSync as readdirSync25 } from "fs";
26799
- 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";
26800
28271
  function buildPythonCouplingIndex(dataDb) {
26801
28272
  const projectRoot = getProjectRoot();
26802
28273
  const config = getConfig();
26803
- const srcDir = join31(projectRoot, config.paths.source);
28274
+ const srcDir = join33(projectRoot, config.paths.source);
26804
28275
  const routes = dataDb.prepare(`SELECT id, method, path FROM ${t("py_routes")}`).all();
26805
28276
  if (routes.length === 0) return 0;
26806
28277
  dataDb.exec(`DELETE FROM ${t("py_route_callers")}`);
@@ -26829,10 +28300,10 @@ function buildPythonCouplingIndex(dataDb) {
26829
28300
  ];
26830
28301
  dataDb.transaction(() => {
26831
28302
  for (const absFile of frontendFiles) {
26832
- const relFile = relative11(projectRoot, absFile);
28303
+ const relFile = relative12(projectRoot, absFile);
26833
28304
  let source;
26834
28305
  try {
26835
- source = readFileSync43(absFile, "utf-8");
28306
+ source = readFileSync45(absFile, "utf-8");
26836
28307
  } catch {
26837
28308
  continue;
26838
28309
  }
@@ -26864,9 +28335,9 @@ function walkFrontendFiles(dir) {
26864
28335
  for (const entry of entries) {
26865
28336
  if (entry.isDirectory()) {
26866
28337
  if (exclude.includes(entry.name)) continue;
26867
- files.push(...walkFrontendFiles(join31(dir, entry.name)));
28338
+ files.push(...walkFrontendFiles(join33(dir, entry.name)));
26868
28339
  } else if (/\.(tsx?|jsx?)$/.test(entry.name)) {
26869
- files.push(join31(dir, entry.name));
28340
+ files.push(join33(dir, entry.name));
26870
28341
  }
26871
28342
  }
26872
28343
  } catch {
@@ -26896,14 +28367,14 @@ var init_coupling_detector = __esm({
26896
28367
  });
26897
28368
 
26898
28369
  // src/memory-tools.ts
26899
- function p2(baseName) {
28370
+ function p3(baseName) {
26900
28371
  return `${getConfig().toolPrefix}_${baseName}`;
26901
28372
  }
26902
28373
  function getMemoryToolDefinitions() {
26903
28374
  return [
26904
28375
  // P4-001: memory_search
26905
28376
  {
26906
- name: p2("memory_search"),
28377
+ name: p3("memory_search"),
26907
28378
  description: "Search past session observations and decisions using full-text search. Returns compact index of matching observations.",
26908
28379
  inputSchema: {
26909
28380
  type: "object",
@@ -26919,7 +28390,7 @@ function getMemoryToolDefinitions() {
26919
28390
  },
26920
28391
  // P4-002: memory_timeline
26921
28392
  {
26922
- name: p2("memory_timeline"),
28393
+ name: p3("memory_timeline"),
26923
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.",
26924
28395
  inputSchema: {
26925
28396
  type: "object",
@@ -26933,7 +28404,7 @@ function getMemoryToolDefinitions() {
26933
28404
  },
26934
28405
  // P4-003: memory_detail
26935
28406
  {
26936
- name: p2("memory_detail"),
28407
+ name: p3("memory_detail"),
26937
28408
  description: "Get full observation details by IDs (batch). Includes evidence, files, plan items.",
26938
28409
  inputSchema: {
26939
28410
  type: "object",
@@ -26949,7 +28420,7 @@ function getMemoryToolDefinitions() {
26949
28420
  },
26950
28421
  // P4-004: memory_sessions
26951
28422
  {
26952
- name: p2("memory_sessions"),
28423
+ name: p3("memory_sessions"),
26953
28424
  description: "List recent sessions with summaries.",
26954
28425
  inputSchema: {
26955
28426
  type: "object",
@@ -26962,7 +28433,7 @@ function getMemoryToolDefinitions() {
26962
28433
  },
26963
28434
  // P4-005: memory_failures
26964
28435
  {
26965
- name: p2("memory_failures"),
28436
+ name: p3("memory_failures"),
26966
28437
  description: "Get all failed attempts (DON'T RETRY warnings). Check before attempting a fix to see if it was already tried.",
26967
28438
  inputSchema: {
26968
28439
  type: "object",
@@ -26975,7 +28446,7 @@ function getMemoryToolDefinitions() {
26975
28446
  },
26976
28447
  // P4-007: memory_backfill
26977
28448
  {
26978
- name: p2("memory_backfill"),
28449
+ name: p3("memory_backfill"),
26979
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.",
26980
28451
  inputSchema: {
26981
28452
  type: "object",
@@ -26985,7 +28456,7 @@ function getMemoryToolDefinitions() {
26985
28456
  },
26986
28457
  // P4-006: memory_ingest
26987
28458
  {
26988
- name: p2("memory_ingest"),
28459
+ name: p3("memory_ingest"),
26989
28460
  description: "Manually record an observation that hooks cannot auto-detect. Use for significant decisions, discoveries, or failed attempts mid-session.",
26990
28461
  inputSchema: {
26991
28462
  type: "object",
@@ -27030,15 +28501,15 @@ function handleMemoryToolCall(name, args2, memoryDb) {
27030
28501
  case "memory_backfill":
27031
28502
  return handleBackfill(memoryDb);
27032
28503
  default:
27033
- return text(`Unknown memory tool: ${name}`);
28504
+ return text2(`Unknown memory tool: ${name}`);
27034
28505
  }
27035
28506
  } catch (error) {
27036
- 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)}`);
27037
28508
  }
27038
28509
  }
27039
28510
  function handleSearch(args2, db) {
27040
28511
  const query = args2.query;
27041
- if (!query) return text("Error: query is required");
28512
+ if (!query) return text2("Error: query is required");
27042
28513
  const results = searchObservations(db, query, {
27043
28514
  type: args2.type,
27044
28515
  crRule: args2.cr_rule,
@@ -27046,7 +28517,7 @@ function handleSearch(args2, db) {
27046
28517
  limit: args2.limit
27047
28518
  });
27048
28519
  if (results.length === 0) {
27049
- return text(`No observations found for "${query}".`);
28520
+ return text2(`No observations found for "${query}".`);
27050
28521
  }
27051
28522
  const lines = [`## Search Results for "${query}" (${results.length} matches)`, ""];
27052
28523
  lines.push("| ID | Type | Title | Date | Importance |");
@@ -27055,17 +28526,17 @@ function handleSearch(args2, db) {
27055
28526
  lines.push(`| ${r2.id} | ${r2.type} | ${r2.title.slice(0, 80)} | ${r2.created_at.split("T")[0]} | ${r2.importance} |`);
27056
28527
  }
27057
28528
  lines.push("");
27058
- lines.push(`Use ${p2("memory_detail")} with IDs for full details.`);
27059
- lines.push(`Use ${p2("memory_timeline")} with an ID for chronological context.`);
27060
- 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"));
27061
28532
  }
27062
28533
  function handleTimeline(args2, db) {
27063
28534
  const observationId = args2.observation_id;
27064
- if (!observationId) return text("Error: observation_id is required");
28535
+ if (!observationId) return text2("Error: observation_id is required");
27065
28536
  const depthBefore = args2.depth_before ?? 5;
27066
28537
  const depthAfter = args2.depth_after ?? 5;
27067
28538
  const anchor = db.prepare("SELECT * FROM observations WHERE id = ?").get(observationId);
27068
- if (!anchor) return text(`Observation ${observationId} not found.`);
28539
+ if (!anchor) return text2(`Observation ${observationId} not found.`);
27069
28540
  const before = db.prepare(
27070
28541
  "SELECT id, type, title, created_at, importance FROM observations WHERE session_id = ? AND created_at_epoch < ? ORDER BY created_at_epoch DESC LIMIT ?"
27071
28542
  ).all(anchor.session_id, anchor.created_at_epoch, depthBefore);
@@ -27080,17 +28551,17 @@ function handleTimeline(args2, db) {
27080
28551
  for (const o of after) {
27081
28552
  lines.push(` ${o.id} | ${o.type} | ${o.title.slice(0, 60)} | ${o.created_at.split("T")[0]}`);
27082
28553
  }
27083
- return text(lines.join("\n"));
28554
+ return text2(lines.join("\n"));
27084
28555
  }
27085
28556
  function handleDetail(args2, db) {
27086
28557
  const ids = args2.ids;
27087
- 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");
27088
28559
  const placeholders = ids.map(() => "?").join(",");
27089
28560
  const observations = db.prepare(
27090
28561
  `SELECT * FROM observations WHERE id IN (${placeholders}) ORDER BY created_at_epoch ASC LIMIT 10000`
27091
28562
  ).all(...ids);
27092
28563
  if (observations.length === 0) {
27093
- return text("No observations found for the given IDs.");
28564
+ return text2("No observations found for the given IDs.");
27094
28565
  }
27095
28566
  const lines = [];
27096
28567
  for (const o of observations) {
@@ -27110,7 +28581,7 @@ function handleDetail(args2, db) {
27110
28581
  if (o.recurrence_count > 1) lines.push(`**Recurrence**: ${o.recurrence_count}x`);
27111
28582
  lines.push("");
27112
28583
  }
27113
- return text(lines.join("\n"));
28584
+ return text2(lines.join("\n"));
27114
28585
  }
27115
28586
  function handleSessions(args2, db) {
27116
28587
  const limit = args2.limit ?? 10;
@@ -27125,7 +28596,7 @@ function handleSessions(args2, db) {
27125
28596
  params.push(limit);
27126
28597
  const sessions = db.prepare(sql).all(...params);
27127
28598
  if (sessions.length === 0) {
27128
- return text("No sessions found.");
28599
+ return text2("No sessions found.");
27129
28600
  }
27130
28601
  const lines = ["## Recent Sessions", ""];
27131
28602
  lines.push("| Session ID | Status | Branch | Started | Plan |");
@@ -27145,14 +28616,14 @@ function handleSessions(args2, db) {
27145
28616
  if (summary.completed) lines.push(`**Completed**: ${summary.completed.slice(0, 200)}`);
27146
28617
  }
27147
28618
  }
27148
- return text(lines.join("\n"));
28619
+ return text2(lines.join("\n"));
27149
28620
  }
27150
28621
  function handleFailures(args2, db) {
27151
28622
  const query = args2.query;
27152
28623
  const limit = args2.limit ?? 20;
27153
28624
  const failures = getFailedAttempts(db, query, limit);
27154
28625
  if (failures.length === 0) {
27155
- 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.");
27156
28627
  }
27157
28628
  const lines = ["## Failed Attempts (DO NOT RETRY)", ""];
27158
28629
  for (const f2 of failures) {
@@ -27162,12 +28633,12 @@ function handleFailures(args2, db) {
27162
28633
  lines.push(`Session: ${f2.session_id.slice(0, 8)}... | Date: ${f2.created_at.split("T")[0]}`);
27163
28634
  lines.push("");
27164
28635
  }
27165
- return text(lines.join("\n"));
28636
+ return text2(lines.join("\n"));
27166
28637
  }
27167
28638
  function handleIngest(args2, db) {
27168
28639
  const type = args2.type;
27169
28640
  const title = args2.title;
27170
- if (!type || !title) return text("Error: type and title are required");
28641
+ if (!type || !title) return text2("Error: type and title are required");
27171
28642
  const validTypes = [
27172
28643
  "decision",
27173
28644
  "bugfix",
@@ -27182,13 +28653,13 @@ function handleIngest(args2, db) {
27182
28653
  "incident_near_miss"
27183
28654
  ];
27184
28655
  if (!validTypes.includes(type)) {
27185
- return text(`Error: invalid type "${type}". Valid types: ${validTypes.join(", ")}`);
28656
+ return text2(`Error: invalid type "${type}". Valid types: ${validTypes.join(", ")}`);
27186
28657
  }
27187
28658
  const activeSession = db.prepare(
27188
28659
  "SELECT session_id FROM sessions WHERE status = 'active' ORDER BY started_at_epoch DESC LIMIT 1"
27189
28660
  ).get();
27190
28661
  if (!activeSession) {
27191
- return text("Error: no active session found. Start a session first.");
28662
+ return text2("Error: no active session found. Start a session first.");
27192
28663
  }
27193
28664
  const importance = args2.importance ?? assignImportance(type);
27194
28665
  const id = addObservation(db, activeSession.session_id, type, title, args2.detail ?? null, {
@@ -27197,7 +28668,7 @@ function handleIngest(args2, db) {
27197
28668
  planItem: args2.plan_item,
27198
28669
  filesInvolved: args2.files
27199
28670
  });
27200
- return text(`Observation #${id} recorded successfully.
28671
+ return text2(`Observation #${id} recorded successfully.
27201
28672
  Type: ${type}
27202
28673
  Title: ${title}
27203
28674
  Importance: ${importance}
@@ -27216,9 +28687,9 @@ function handleBackfill(db) {
27216
28687
  "",
27217
28688
  stats.total === 0 ? "No memory files found in memory directory." : `Successfully processed ${stats.inserted + stats.updated} of ${stats.total} memory files.`
27218
28689
  ];
27219
- return text(lines.join("\n"));
28690
+ return text2(lines.join("\n"));
27220
28691
  }
27221
- function text(content) {
28692
+ function text2(content) {
27222
28693
  return { content: [{ type: "text", text: content }] };
27223
28694
  }
27224
28695
  function safeParseJson(json, fallback) {
@@ -27238,15 +28709,15 @@ var init_memory_tools = __esm({
27238
28709
  });
27239
28710
 
27240
28711
  // src/docs-tools.ts
27241
- import { readFileSync as readFileSync44, existsSync as existsSync52 } from "fs";
28712
+ import { readFileSync as readFileSync46, existsSync as existsSync54 } from "fs";
27242
28713
  import { resolve as resolve34, basename as basename9 } from "path";
27243
- function p3(baseName) {
28714
+ function p4(baseName) {
27244
28715
  return `${getConfig().toolPrefix}_${baseName}`;
27245
28716
  }
27246
28717
  function getDocsToolDefinitions() {
27247
28718
  return [
27248
28719
  {
27249
- name: p3("docs_audit"),
28720
+ name: p4("docs_audit"),
27250
28721
  description: "Audit which help site pages need updating based on changed files. Maps code changes to affected documentation pages using docs-map.json.",
27251
28722
  inputSchema: {
27252
28723
  type: "object",
@@ -27265,7 +28736,7 @@ function getDocsToolDefinitions() {
27265
28736
  }
27266
28737
  },
27267
28738
  {
27268
- name: p3("docs_coverage"),
28739
+ name: p4("docs_coverage"),
27269
28740
  description: "Report docs coverage: which help pages exist, have content, and are up-to-date. Identifies documentation gaps.",
27270
28741
  inputSchema: {
27271
28742
  type: "object",
@@ -27289,15 +28760,15 @@ function handleDocsToolCall(name, args2) {
27289
28760
  case "docs_coverage":
27290
28761
  return handleDocsCoverage(args2);
27291
28762
  default:
27292
- return text2(`Unknown docs tool: ${name}`);
28763
+ return text3(`Unknown docs tool: ${name}`);
27293
28764
  }
27294
28765
  }
27295
28766
  function loadDocsMap() {
27296
28767
  const mapPath = getResolvedPaths().docsMapPath;
27297
- if (!existsSync52(mapPath)) {
28768
+ if (!existsSync54(mapPath)) {
27298
28769
  throw new Error(`docs-map.json not found at ${mapPath}`);
27299
28770
  }
27300
- return JSON.parse(readFileSync44(mapPath, "utf-8"));
28771
+ return JSON.parse(readFileSync46(mapPath, "utf-8"));
27301
28772
  }
27302
28773
  function matchesPattern(filePath, pattern) {
27303
28774
  const regexStr = pattern.replace(/\./g, "\\.").replace(/\*\*/g, "{{GLOBSTAR}}").replace(/\*/g, "[^/]*").replace(/\{\{GLOBSTAR\}\}/g, ".*");
@@ -27368,12 +28839,12 @@ function extractFrontmatter(content) {
27368
28839
  function extractProcedureNames(routerPath) {
27369
28840
  const root = getProjectRoot();
27370
28841
  const absPath = ensureWithinRoot(resolve34(getResolvedPaths().srcDir, "..", routerPath), root);
27371
- if (!existsSync52(absPath)) {
28842
+ if (!existsSync54(absPath)) {
27372
28843
  const altPath = ensureWithinRoot(resolve34(getResolvedPaths().srcDir, "../server/api/routers", basename9(routerPath)), root);
27373
- if (!existsSync52(altPath)) return [];
27374
- return extractProcedureNamesFromContent(readFileSync44(altPath, "utf-8"));
28844
+ if (!existsSync54(altPath)) return [];
28845
+ return extractProcedureNamesFromContent(readFileSync46(altPath, "utf-8"));
27375
28846
  }
27376
- return extractProcedureNamesFromContent(readFileSync44(absPath, "utf-8"));
28847
+ return extractProcedureNamesFromContent(readFileSync46(absPath, "utf-8"));
27377
28848
  }
27378
28849
  function extractProcedureNamesFromContent(content) {
27379
28850
  const procRegex = /\.(?:query|mutation)\s*\(/g;
@@ -27399,12 +28870,12 @@ function handleDocsAudit(args2) {
27399
28870
  const changedFiles = args2.changed_files;
27400
28871
  const commitMessage = args2.commit_message || "";
27401
28872
  if (!changedFiles || changedFiles.length === 0) {
27402
- return text2(JSON.stringify({ affectedPages: [], summary: "No changed files provided." }));
28873
+ return text3(JSON.stringify({ affectedPages: [], summary: "No changed files provided." }));
27403
28874
  }
27404
28875
  const docsMap = loadDocsMap();
27405
28876
  const affectedMappings = findAffectedMappings(docsMap, changedFiles);
27406
28877
  if (affectedMappings.size === 0) {
27407
- return text2(JSON.stringify({
28878
+ return text3(JSON.stringify({
27408
28879
  affectedPages: [],
27409
28880
  summary: `0 help pages affected by ${changedFiles.length} changed files. No docs update needed.`
27410
28881
  }));
@@ -27414,7 +28885,7 @@ function handleDocsAudit(args2) {
27414
28885
  const mapping = docsMap.mappings.find((m3) => m3.id === mappingId);
27415
28886
  if (!mapping) continue;
27416
28887
  const helpPagePath = ensureWithinRoot(resolve34(getResolvedPaths().helpSitePath, mapping.helpPage), getProjectRoot());
27417
- if (!existsSync52(helpPagePath)) {
28888
+ if (!existsSync54(helpPagePath)) {
27418
28889
  results.push({
27419
28890
  helpPage: mapping.helpPage,
27420
28891
  mappingId,
@@ -27426,7 +28897,7 @@ function handleDocsAudit(args2) {
27426
28897
  });
27427
28898
  continue;
27428
28899
  }
27429
- const content = readFileSync44(helpPagePath, "utf-8");
28900
+ const content = readFileSync46(helpPagePath, "utf-8");
27430
28901
  const sections = extractSections(content);
27431
28902
  const frontmatter = extractFrontmatter(content);
27432
28903
  const staleReasons = [];
@@ -27467,8 +28938,8 @@ function handleDocsAudit(args2) {
27467
28938
  for (const [guideName, parentId] of Object.entries(docsMap.userGuideInheritance.examples)) {
27468
28939
  if (parentId === mappingId) {
27469
28940
  const guidePath = ensureWithinRoot(resolve34(getResolvedPaths().helpSitePath, `pages/user-guides/${guideName}/index.mdx`), getProjectRoot());
27470
- if (existsSync52(guidePath)) {
27471
- const guideContent = readFileSync44(guidePath, "utf-8");
28941
+ if (existsSync54(guidePath)) {
28942
+ const guideContent = readFileSync46(guidePath, "utf-8");
27472
28943
  const guideFrontmatter = extractFrontmatter(guideContent);
27473
28944
  if (!guideFrontmatter?.lastVerified || status === "STALE") {
27474
28945
  results.push({
@@ -27492,7 +28963,7 @@ function handleDocsAudit(args2) {
27492
28963
  affectedPages: results,
27493
28964
  summary: `${results.length} pages checked: ${staleCount} STALE, ${newCount} NEW, ${okCount} OK. ${staleCount + newCount > 0 ? `${staleCount + newCount} pages need updates.` : "All docs are current."}`
27494
28965
  };
27495
- return text2(JSON.stringify(report, null, 2));
28966
+ return text3(JSON.stringify(report, null, 2));
27496
28967
  }
27497
28968
  function handleDocsCoverage(args2) {
27498
28969
  const filterDomain = args2.domain;
@@ -27502,13 +28973,13 @@ function handleDocsCoverage(args2) {
27502
28973
  const mappings = filterDomain ? docsMap.mappings.filter((m3) => m3.id === filterDomain) : docsMap.mappings;
27503
28974
  for (const mapping of mappings) {
27504
28975
  const helpPagePath = ensureWithinRoot(resolve34(getResolvedPaths().helpSitePath, mapping.helpPage), getProjectRoot());
27505
- const exists = existsSync52(helpPagePath);
28976
+ const exists = existsSync54(helpPagePath);
27506
28977
  let hasContent = false;
27507
28978
  let lineCount = 0;
27508
28979
  let lastVerified = null;
27509
28980
  let status = null;
27510
28981
  if (exists) {
27511
- const content = readFileSync44(helpPagePath, "utf-8");
28982
+ const content = readFileSync46(helpPagePath, "utf-8");
27512
28983
  lineCount = content.split("\n").length;
27513
28984
  hasContent = lineCount > 10;
27514
28985
  const frontmatter = extractFrontmatter(content);
@@ -27559,9 +29030,9 @@ function handleDocsCoverage(args2) {
27559
29030
  const icon = entry.hasContent ? "OK" : entry.exists ? "THIN" : "MISSING";
27560
29031
  lines.push(`- [${icon}] ${entry.id}: ${entry.helpPage}${verified}${pageStatus} (${entry.lineCount} lines)`);
27561
29032
  }
27562
- return text2(lines.join("\n"));
29033
+ return text3(lines.join("\n"));
27563
29034
  }
27564
- function text2(content) {
29035
+ function text3(content) {
27565
29036
  return { content: [{ type: "text", text: content }] };
27566
29037
  }
27567
29038
  var init_docs_tools = __esm({
@@ -27573,7 +29044,7 @@ var init_docs_tools = __esm({
27573
29044
  });
27574
29045
 
27575
29046
  // src/observability-tools.ts
27576
- function p4(baseName) {
29047
+ function p5(baseName) {
27577
29048
  return `${getConfig().toolPrefix}_${baseName}`;
27578
29049
  }
27579
29050
  function isObservabilityTool(name) {
@@ -27585,7 +29056,7 @@ function getObservabilityToolDefinitions() {
27585
29056
  return [
27586
29057
  // P3-001: session_replay
27587
29058
  {
27588
- name: p4("session_replay"),
29059
+ name: p5("session_replay"),
27589
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.",
27590
29061
  inputSchema: {
27591
29062
  type: "object",
@@ -27600,7 +29071,7 @@ function getObservabilityToolDefinitions() {
27600
29071
  },
27601
29072
  // P3-002: prompt_analysis
27602
29073
  {
27603
- name: p4("prompt_analysis"),
29074
+ name: p5("prompt_analysis"),
27604
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).",
27605
29076
  inputSchema: {
27606
29077
  type: "object",
@@ -27617,7 +29088,7 @@ function getObservabilityToolDefinitions() {
27617
29088
  },
27618
29089
  // P3-003: tool_patterns
27619
29090
  {
27620
- name: p4("tool_patterns"),
29091
+ name: p5("tool_patterns"),
27621
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.",
27622
29093
  inputSchema: {
27623
29094
  type: "object",
@@ -27632,7 +29103,7 @@ function getObservabilityToolDefinitions() {
27632
29103
  },
27633
29104
  // P3-004: session_stats
27634
29105
  {
27635
- name: p4("session_stats"),
29106
+ name: p5("session_stats"),
27636
29107
  description: "Get per-session statistics: turn count, tool call breakdown, token usage, duration. Includes database size monitoring for observability data.",
27637
29108
  inputSchema: {
27638
29109
  type: "object",
@@ -27659,15 +29130,15 @@ function handleObservabilityToolCall(name, args2, memoryDb) {
27659
29130
  case "session_stats":
27660
29131
  return handleSessionStats(args2, memoryDb);
27661
29132
  default:
27662
- return text3(`Unknown observability tool: ${name}`);
29133
+ return text4(`Unknown observability tool: ${name}`);
27663
29134
  }
27664
29135
  } catch (error) {
27665
- 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)}`);
27666
29137
  }
27667
29138
  }
27668
29139
  function handleSessionReplay(args2, db) {
27669
29140
  const sessionId = args2.session_id;
27670
- if (!sessionId) return text3("Error: session_id is required");
29141
+ if (!sessionId) return text4("Error: session_id is required");
27671
29142
  const includeToolCalls = args2.include_tool_calls ?? false;
27672
29143
  const turns = getConversationTurns(db, sessionId, {
27673
29144
  turnFrom: args2.turn_from,
@@ -27675,7 +29146,7 @@ function handleSessionReplay(args2, db) {
27675
29146
  includeToolCalls
27676
29147
  });
27677
29148
  if (turns.length === 0) {
27678
- 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)}...
27679
29150
  Note: Conversation data is captured by the session-end hook. Only sessions that have ended will have replay data.`);
27680
29151
  }
27681
29152
  const lines = [`## Session Replay: ${sessionId.slice(0, 12)}...`, `Turns: ${turns.length}`, ""];
@@ -27707,7 +29178,7 @@ Note: Conversation data is captured by the session-end hook. Only sessions that
27707
29178
  lines.push("---");
27708
29179
  lines.push("");
27709
29180
  }
27710
- return text3(lines.join("\n"));
29181
+ return text4(lines.join("\n"));
27711
29182
  }
27712
29183
  function handlePromptAnalysis(args2, db) {
27713
29184
  const query = args2.query;
@@ -27716,7 +29187,7 @@ function handlePromptAnalysis(args2, db) {
27716
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 ?"
27717
29188
  ).all(args2.limit ?? 20);
27718
29189
  if (recentTurns.length === 0) {
27719
- return text3("No conversation turns recorded yet.");
29190
+ return text4("No conversation turns recorded yet.");
27720
29191
  }
27721
29192
  const lines2 = ["## Recent Prompts", ""];
27722
29193
  lines2.push("| Session | Turn | Prompt (truncated) | Tools | Response Tokens | Date |");
@@ -27724,7 +29195,7 @@ function handlePromptAnalysis(args2, db) {
27724
29195
  for (const t2 of recentTurns) {
27725
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]} |`);
27726
29197
  }
27727
- return text3(lines2.join("\n"));
29198
+ return text4(lines2.join("\n"));
27728
29199
  }
27729
29200
  const results = searchConversationTurns(db, query, {
27730
29201
  sessionId: args2.session_id,
@@ -27734,7 +29205,7 @@ function handlePromptAnalysis(args2, db) {
27734
29205
  limit: args2.limit
27735
29206
  });
27736
29207
  if (results.length === 0) {
27737
- return text3(`No prompts found matching "${query}".`);
29208
+ return text4(`No prompts found matching "${query}".`);
27738
29209
  }
27739
29210
  const lines = [`## Prompt Search: "${query}" (${results.length} results)`, ""];
27740
29211
  lines.push("| Session | Turn | Prompt (truncated) | Tools | Response Tokens | Date |");
@@ -27742,7 +29213,7 @@ function handlePromptAnalysis(args2, db) {
27742
29213
  for (const r2 of results) {
27743
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]} |`);
27744
29215
  }
27745
- return text3(lines.join("\n"));
29216
+ return text4(lines.join("\n"));
27746
29217
  }
27747
29218
  function handleToolPatterns(args2, db) {
27748
29219
  const groupBy = args2.group_by ?? "tool";
@@ -27753,7 +29224,7 @@ function handleToolPatterns(args2, db) {
27753
29224
  groupBy
27754
29225
  });
27755
29226
  if (patterns.length === 0) {
27756
- return text3("No tool usage data recorded yet.");
29227
+ return text4("No tool usage data recorded yet.");
27757
29228
  }
27758
29229
  const lines = [`## Tool Usage Patterns (grouped by ${groupBy})`, ""];
27759
29230
  switch (groupBy) {
@@ -27783,7 +29254,7 @@ function handleToolPatterns(args2, db) {
27783
29254
  }
27784
29255
  break;
27785
29256
  }
27786
- return text3(lines.join("\n"));
29257
+ return text4(lines.join("\n"));
27787
29258
  }
27788
29259
  function handleSessionStats(args2, db) {
27789
29260
  const stats = getSessionStats(db, {
@@ -27791,7 +29262,7 @@ function handleSessionStats(args2, db) {
27791
29262
  limit: args2.limit
27792
29263
  });
27793
29264
  if (stats.length === 0) {
27794
- return text3("No session stats available.");
29265
+ return text4("No session stats available.");
27795
29266
  }
27796
29267
  const lines = ["## Session Statistics", ""];
27797
29268
  if (args2.session_id) {
@@ -27828,9 +29299,9 @@ function handleSessionStats(args2, db) {
27828
29299
  lines.push(`- Tool call details: ${dbSize.tool_call_details_count}`);
27829
29300
  lines.push(`- Observations: ${dbSize.observations_count}`);
27830
29301
  lines.push(`- Database size: ~${dbSize.estimated_size_mb} MB`);
27831
- return text3(lines.join("\n"));
29302
+ return text4(lines.join("\n"));
27832
29303
  }
27833
- function text3(content) {
29304
+ function text4(content) {
27834
29305
  return { content: [{ type: "text", text: content }] };
27835
29306
  }
27836
29307
  var OBSERVABILITY_BASE_NAMES;
@@ -27849,7 +29320,7 @@ var init_observability_tools = __esm({
27849
29320
  });
27850
29321
 
27851
29322
  // src/sentinel-db.ts
27852
- import { existsSync as existsSync53 } from "fs";
29323
+ import { existsSync as existsSync55 } from "fs";
27853
29324
  import { resolve as resolve35 } from "path";
27854
29325
  function parsePortalScope(raw) {
27855
29326
  if (!raw) return [];
@@ -28087,22 +29558,22 @@ function validateFeatures(db, domainFilter) {
28087
29558
  const missingPages = [];
28088
29559
  for (const comp of components) {
28089
29560
  const absPath = resolve35(PROJECT_ROOT, comp.component_file);
28090
- if (!existsSync53(absPath)) {
29561
+ if (!existsSync55(absPath)) {
28091
29562
  missingComponents.push(comp.component_file);
28092
29563
  }
28093
29564
  }
28094
29565
  for (const proc of procedures) {
28095
29566
  const routerPath = resolve35(PROJECT_ROOT, `src/server/api/routers/${proc.router_name}.ts`);
28096
- if (!existsSync53(routerPath)) {
29567
+ if (!existsSync55(routerPath)) {
28097
29568
  missingProcedures.push({ router: proc.router_name, procedure: proc.procedure_name });
28098
29569
  }
28099
29570
  }
28100
29571
  for (const page of pages) {
28101
29572
  const routeToPath = page.page_route.replace(/^\/(portal-[^/]+\/)?/, "src/app/").replace(/\/$/, "") + "/page.tsx";
28102
29573
  const absPath = resolve35(PROJECT_ROOT, routeToPath);
28103
- if (page.page_route.startsWith("/") && !existsSync53(absPath)) {
29574
+ if (page.page_route.startsWith("/") && !existsSync55(absPath)) {
28104
29575
  const altPath = resolve35(PROJECT_ROOT, `src/app${page.page_route}/page.tsx`);
28105
- if (!existsSync53(altPath)) {
29576
+ if (!existsSync55(altPath)) {
28106
29577
  missingPages.push(page.page_route);
28107
29578
  }
28108
29579
  }
@@ -28198,17 +29669,17 @@ var init_sentinel_db = __esm({
28198
29669
  });
28199
29670
 
28200
29671
  // src/sentinel-tools.ts
28201
- function p5(baseName) {
29672
+ function p6(baseName) {
28202
29673
  return `${getConfig().toolPrefix}_${baseName}`;
28203
29674
  }
28204
- function text4(content) {
29675
+ function text5(content) {
28205
29676
  return { content: [{ type: "text", text: content }] };
28206
29677
  }
28207
29678
  function getSentinelToolDefinitions() {
28208
29679
  return [
28209
29680
  // P2-001: sentinel_search
28210
29681
  {
28211
- name: p5("sentinel_search"),
29682
+ name: p6("sentinel_search"),
28212
29683
  description: "Search and list features in the Sentinel feature registry. Supports FTS5 full-text search and domain/status/portal filters.",
28213
29684
  inputSchema: {
28214
29685
  type: "object",
@@ -28225,7 +29696,7 @@ function getSentinelToolDefinitions() {
28225
29696
  },
28226
29697
  // P2-002: sentinel_detail
28227
29698
  {
28228
- name: p5("sentinel_detail"),
29699
+ name: p6("sentinel_detail"),
28229
29700
  description: "Get full feature details including all linked components, procedures, pages, dependencies, and changelog.",
28230
29701
  inputSchema: {
28231
29702
  type: "object",
@@ -28238,7 +29709,7 @@ function getSentinelToolDefinitions() {
28238
29709
  },
28239
29710
  // P2-003: sentinel_impact
28240
29711
  {
28241
- name: p5("sentinel_impact"),
29712
+ name: p6("sentinel_impact"),
28242
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.",
28243
29714
  inputSchema: {
28244
29715
  type: "object",
@@ -28254,7 +29725,7 @@ function getSentinelToolDefinitions() {
28254
29725
  },
28255
29726
  // P2-004: sentinel_validate
28256
29727
  {
28257
- name: p5("sentinel_validate"),
29728
+ name: p6("sentinel_validate"),
28258
29729
  description: "Validate all active features have living implementation files. Reports alive, orphaned, and degraded features.",
28259
29730
  inputSchema: {
28260
29731
  type: "object",
@@ -28267,7 +29738,7 @@ function getSentinelToolDefinitions() {
28267
29738
  },
28268
29739
  // P2-005: sentinel_register
28269
29740
  {
28270
- name: p5("sentinel_register"),
29741
+ name: p6("sentinel_register"),
28271
29742
  description: "Register or update a feature in the sentinel registry. Links components, procedures, and pages.",
28272
29743
  inputSchema: {
28273
29744
  type: "object",
@@ -28324,7 +29795,7 @@ function getSentinelToolDefinitions() {
28324
29795
  },
28325
29796
  // P2-006: sentinel_parity
28326
29797
  {
28327
- name: p5("sentinel_parity"),
29798
+ name: p6("sentinel_parity"),
28328
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).",
28329
29800
  inputSchema: {
28330
29801
  type: "object",
@@ -28362,7 +29833,7 @@ function handleSentinelToolCall(name, args2, dataDb) {
28362
29833
  case "sentinel_parity":
28363
29834
  return handleParityCheck(args2, dataDb);
28364
29835
  default:
28365
- return text4(`Unknown sentinel tool: ${name}`);
29836
+ return text5(`Unknown sentinel tool: ${name}`);
28366
29837
  }
28367
29838
  }
28368
29839
  function handleSearch2(args2, db) {
@@ -28385,7 +29856,7 @@ function handleSearch2(args2, db) {
28385
29856
  lines.push("");
28386
29857
  if (results.length === 0) {
28387
29858
  lines.push("No features found matching criteria.");
28388
- return text4(lines.join("\n"));
29859
+ return text5(lines.join("\n"));
28389
29860
  }
28390
29861
  let currentDomain = "";
28391
29862
  for (const f2 of results) {
@@ -28397,17 +29868,17 @@ function handleSearch2(args2, db) {
28397
29868
  const priority = f2.priority === "critical" ? " [CRITICAL]" : "";
28398
29869
  lines.push(`- **${f2.feature_key}** ${sub}${priority}: ${f2.title} (${f2.component_count}C/${f2.procedure_count}P/${f2.page_count}R) [${f2.status}]`);
28399
29870
  }
28400
- return text4(lines.join("\n"));
29871
+ return text5(lines.join("\n"));
28401
29872
  }
28402
29873
  function handleDetail2(args2, db) {
28403
29874
  const key = args2.feature_key;
28404
29875
  const id = args2.feature_id;
28405
29876
  if (!key && !id) {
28406
- return text4("Error: Provide either feature_key or feature_id");
29877
+ return text5("Error: Provide either feature_key or feature_id");
28407
29878
  }
28408
29879
  const detail = getFeatureDetail(db, id || key);
28409
29880
  if (!detail) {
28410
- return text4(`Feature not found: ${key || id}`);
29881
+ return text5(`Feature not found: ${key || id}`);
28411
29882
  }
28412
29883
  const lines = [];
28413
29884
  lines.push(`## Feature: ${detail.feature_key}`);
@@ -28453,12 +29924,12 @@ function handleDetail2(args2, db) {
28453
29924
  lines.push(`- [${c2.created_at}] ${c2.change_type}: ${c2.change_detail || "(no detail)"}${c2.commit_hash ? " @" + c2.commit_hash.substring(0, 8) : ""}`);
28454
29925
  }
28455
29926
  }
28456
- return text4(lines.join("\n"));
29927
+ return text5(lines.join("\n"));
28457
29928
  }
28458
29929
  function handleImpact(args2, db) {
28459
29930
  const files = args2.files;
28460
29931
  if (!files || files.length === 0) {
28461
- return text4("Error: Provide files array");
29932
+ return text5("Error: Provide files array");
28462
29933
  }
28463
29934
  const report = getFeatureImpact(db, files);
28464
29935
  const lines = [];
@@ -28494,7 +29965,7 @@ function handleImpact(args2, db) {
28494
29965
  }
28495
29966
  lines.push("");
28496
29967
  }
28497
- return text4(lines.join("\n"));
29968
+ return text5(lines.join("\n"));
28498
29969
  }
28499
29970
  function handleValidate(args2, db) {
28500
29971
  const domain = args2.domain;
@@ -28539,14 +30010,14 @@ function handleValidate(args2, db) {
28539
30010
  }
28540
30011
  const result = report.orphaned === 0 ? "PASS" : "FAIL";
28541
30012
  lines.push(`### RESULT: ${result}`);
28542
- return text4(lines.join("\n"));
30013
+ return text5(lines.join("\n"));
28543
30014
  }
28544
30015
  function handleRegister(args2, db) {
28545
30016
  const featureKey = args2.feature_key;
28546
30017
  const domain = args2.domain;
28547
30018
  const title = args2.title;
28548
30019
  if (!featureKey || !domain || !title) {
28549
- return text4("Error: feature_key, domain, and title are required");
30020
+ return text5("Error: feature_key, domain, and title are required");
28550
30021
  }
28551
30022
  const featureId = upsertFeature(db, {
28552
30023
  feature_key: featureKey,
@@ -28576,15 +30047,15 @@ function handleRegister(args2, db) {
28576
30047
  linkPage(db, featureId, p19.route, p19.portal);
28577
30048
  }
28578
30049
  }
28579
- logChange(db, featureId, "created", `Registered via ${p5("sentinel_register")}`);
28580
- 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})
28581
30052
  Components: ${components?.length || 0}, Procedures: ${procedures?.length || 0}, Pages: ${pages?.length || 0}`);
28582
30053
  }
28583
30054
  function handleParityCheck(args2, db) {
28584
30055
  const oldFiles = args2.old_files;
28585
30056
  const newFiles = args2.new_files;
28586
30057
  if (!oldFiles || !newFiles) {
28587
- return text4("Error: Provide both old_files and new_files arrays");
30058
+ return text5("Error: Provide both old_files and new_files arrays");
28588
30059
  }
28589
30060
  const report = checkParity(db, oldFiles, newFiles);
28590
30061
  const lines = [];
@@ -28616,7 +30087,7 @@ function handleParityCheck(args2, db) {
28616
30087
  }
28617
30088
  const result = report.gaps.length === 0 ? "PASS" : `FAIL (${report.gaps.length} gaps)`;
28618
30089
  lines.push(`### RESULT: ${result}`);
28619
- return text4(lines.join("\n"));
30090
+ return text5(lines.join("\n"));
28620
30091
  }
28621
30092
  var init_sentinel_tools = __esm({
28622
30093
  "src/sentinel-tools.ts"() {
@@ -28628,8 +30099,8 @@ var init_sentinel_tools = __esm({
28628
30099
  });
28629
30100
 
28630
30101
  // src/sentinel-scanner.ts
28631
- import { readFileSync as readFileSync45, existsSync as existsSync54, readdirSync as readdirSync26, statSync as statSync19 } from "fs";
28632
- 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";
28633
30104
  function inferDomain(filePath) {
28634
30105
  const domains = getConfig().domains;
28635
30106
  const path = filePath.toLowerCase();
@@ -28761,7 +30232,7 @@ function scanComponentExports(dataDb) {
28761
30232
  const componentsBase = config.paths.components ?? config.paths.source + "/components";
28762
30233
  const componentDirs = [];
28763
30234
  const basePath = resolve36(projectRoot, componentsBase);
28764
- if (existsSync54(basePath)) {
30235
+ if (existsSync56(basePath)) {
28765
30236
  try {
28766
30237
  const entries = readdirSync26(basePath, { withFileTypes: true });
28767
30238
  for (const entry of entries) {
@@ -28774,11 +30245,11 @@ function scanComponentExports(dataDb) {
28774
30245
  }
28775
30246
  for (const dir of componentDirs) {
28776
30247
  const absDir = resolve36(projectRoot, dir);
28777
- if (!existsSync54(absDir)) continue;
30248
+ if (!existsSync56(absDir)) continue;
28778
30249
  const files = walkDir2(absDir).filter((f2) => f2.endsWith(".tsx") || f2.endsWith(".ts"));
28779
30250
  for (const file of files) {
28780
- const relPath = relative12(projectRoot, file);
28781
- const source = readFileSync45(file, "utf-8");
30251
+ const relPath = relative13(projectRoot, file);
30252
+ const source = readFileSync47(file, "utf-8");
28782
30253
  const annotations = parseFeatureAnnotations(source);
28783
30254
  if (annotations.length > 0) {
28784
30255
  for (const ann of annotations) {
@@ -28804,7 +30275,7 @@ function scanComponentExports(dataDb) {
28804
30275
  if (hasHandlers && exportMatch) {
28805
30276
  const componentName = exportMatch[1];
28806
30277
  const domain = inferDomain(relPath);
28807
- const subdomain = basename10(dirname24(relPath));
30278
+ const subdomain = basename10(dirname25(relPath));
28808
30279
  const featureKey = `component.${subdomain}.${componentName.replace(/([A-Z])/g, "-$1").toLowerCase().replace(/^-/, "")}`;
28809
30280
  if (!annotations.some((a2) => a2.featureKey === featureKey)) {
28810
30281
  features.push({
@@ -28829,7 +30300,7 @@ function walkDir2(dir) {
28829
30300
  try {
28830
30301
  const entries = readdirSync26(dir);
28831
30302
  for (const entry of entries) {
28832
- const fullPath = join32(dir, entry);
30303
+ const fullPath = join34(dir, entry);
28833
30304
  try {
28834
30305
  const stat = statSync19(fullPath);
28835
30306
  if (stat.isDirectory()) {
@@ -28900,7 +30371,7 @@ var init_sentinel_scanner = __esm({
28900
30371
  });
28901
30372
 
28902
30373
  // src/analytics.ts
28903
- function p6(baseName) {
30374
+ function p7(baseName) {
28904
30375
  return `${getConfig().toolPrefix}_${baseName}`;
28905
30376
  }
28906
30377
  function getWeights() {
@@ -28968,7 +30439,7 @@ function backfillQualityScores(db) {
28968
30439
  function getAnalyticsToolDefinitions() {
28969
30440
  return [
28970
30441
  {
28971
- name: p6("quality_score"),
30442
+ name: p7("quality_score"),
28972
30443
  description: "Calculate and store quality score for a session based on observations. Shows breakdown by category.",
28973
30444
  inputSchema: {
28974
30445
  type: "object",
@@ -28979,7 +30450,7 @@ function getAnalyticsToolDefinitions() {
28979
30450
  }
28980
30451
  },
28981
30452
  {
28982
- name: p6("quality_trend"),
30453
+ name: p7("quality_trend"),
28983
30454
  description: "Quality trend over recent sessions. Shows score progression and identifies improving/declining areas.",
28984
30455
  inputSchema: {
28985
30456
  type: "object",
@@ -28990,7 +30461,7 @@ function getAnalyticsToolDefinitions() {
28990
30461
  }
28991
30462
  },
28992
30463
  {
28993
- name: p6("quality_report"),
30464
+ name: p7("quality_report"),
28994
30465
  description: "Comprehensive quality report with averages, trends, and recommendations.",
28995
30466
  inputSchema: {
28996
30467
  type: "object",
@@ -29019,17 +30490,17 @@ function handleAnalyticsToolCall(name, args2, memoryDb) {
29019
30490
  case "quality_report":
29020
30491
  return handleQualityReport(args2, memoryDb);
29021
30492
  default:
29022
- return text5(`Unknown analytics tool: ${name}`);
30493
+ return text6(`Unknown analytics tool: ${name}`);
29023
30494
  }
29024
30495
  } catch (error) {
29025
- 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)}
29026
30497
 
29027
- Usage: ${p6("quality_score")} { session_id: "..." }, ${p6("quality_trend")} { days: 30 }`);
30498
+ Usage: ${p7("quality_score")} { session_id: "..." }, ${p7("quality_trend")} { days: 30 }`);
29028
30499
  }
29029
30500
  }
29030
30501
  function handleQualityScore(args2, db) {
29031
30502
  const sessionId = args2.session_id;
29032
- 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.`);
29033
30504
  const { score, breakdown } = calculateQualityScore(db, sessionId);
29034
30505
  storeQualityScore(db, sessionId, score, breakdown);
29035
30506
  const categories = getCategories();
@@ -29045,7 +30516,7 @@ function handleQualityScore(args2, db) {
29045
30516
  const indicator = impact > 0 ? "+" : impact < 0 ? "" : " ";
29046
30517
  lines.push(`| ${category} | ${indicator}${impact} |`);
29047
30518
  }
29048
- return text5(lines.join("\n"));
30519
+ return text6(lines.join("\n"));
29049
30520
  }
29050
30521
  function handleQualityTrend(args2, db, retried = false) {
29051
30522
  const days = args2.days ?? 30;
@@ -29063,7 +30534,7 @@ function handleQualityTrend(args2, db, retried = false) {
29063
30534
  return handleQualityTrend(args2, db, true);
29064
30535
  }
29065
30536
  }
29066
- 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 }.`);
29067
30538
  }
29068
30539
  const avg = scores.reduce((sum, s) => sum + s.score, 0) / scores.length;
29069
30540
  const recent = scores.slice(-5);
@@ -29082,7 +30553,7 @@ function handleQualityTrend(args2, db, retried = false) {
29082
30553
  for (const s of scores.slice(-10)) {
29083
30554
  lines.push(`| ${s.session_id.slice(0, 8)}... | ${s.score} | ${s.created_at} |`);
29084
30555
  }
29085
- return text5(lines.join("\n"));
30556
+ return text6(lines.join("\n"));
29086
30557
  }
29087
30558
  function handleQualityReport(args2, db, retried = false) {
29088
30559
  const days = args2.days ?? 30;
@@ -29099,7 +30570,7 @@ function handleQualityReport(args2, db, retried = false) {
29099
30570
  return handleQualityReport(args2, db, true);
29100
30571
  }
29101
30572
  }
29102
- 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 }.`);
29103
30574
  }
29104
30575
  const avg = scores.reduce((sum, s) => sum + s.score, 0) / scores.length;
29105
30576
  const max = Math.max(...scores.map((s) => s.score));
@@ -29156,9 +30627,9 @@ function handleQualityReport(args2, db, retried = false) {
29156
30627
  if (avg >= 70) {
29157
30628
  lines.push("- Quality is good. Maintain current practices and focus on consistency.");
29158
30629
  }
29159
- return text5(lines.join("\n"));
30630
+ return text6(lines.join("\n"));
29160
30631
  }
29161
- function text5(content) {
30632
+ function text6(content) {
29162
30633
  return { content: [{ type: "text", text: content }] };
29163
30634
  }
29164
30635
  var DEFAULT_WEIGHTS, DEFAULT_CATEGORIES, ANALYTICS_BASE_NAMES;
@@ -29181,7 +30652,7 @@ var init_analytics = __esm({
29181
30652
  });
29182
30653
 
29183
30654
  // src/cost-tracker.ts
29184
- function p7(baseName) {
30655
+ function p8(baseName) {
29185
30656
  return `${getConfig().toolPrefix}_${baseName}`;
29186
30657
  }
29187
30658
  function getModelPricing() {
@@ -29190,7 +30661,7 @@ function getModelPricing() {
29190
30661
  function getCostToolDefinitions() {
29191
30662
  return [
29192
30663
  {
29193
- name: p7("cost_session"),
30664
+ name: p8("cost_session"),
29194
30665
  description: "Show cost breakdown for a session. Includes token counts, model pricing, and cost by category.",
29195
30666
  inputSchema: {
29196
30667
  type: "object",
@@ -29201,7 +30672,7 @@ function getCostToolDefinitions() {
29201
30672
  }
29202
30673
  },
29203
30674
  {
29204
- name: p7("cost_trend"),
30675
+ name: p8("cost_trend"),
29205
30676
  description: "Cost trend over time. Shows daily/weekly spending and identifies cost drivers.",
29206
30677
  inputSchema: {
29207
30678
  type: "object",
@@ -29213,7 +30684,7 @@ function getCostToolDefinitions() {
29213
30684
  }
29214
30685
  },
29215
30686
  {
29216
- name: p7("cost_feature"),
30687
+ name: p8("cost_feature"),
29217
30688
  description: "Cost attribution by feature. Shows which features consume the most tokens.",
29218
30689
  inputSchema: {
29219
30690
  type: "object",
@@ -29242,22 +30713,22 @@ function handleCostToolCall(name, args2, memoryDb) {
29242
30713
  case "cost_feature":
29243
30714
  return handleCostFeature(args2, memoryDb);
29244
30715
  default:
29245
- return text6(`Unknown cost tool: ${name}`);
30716
+ return text7(`Unknown cost tool: ${name}`);
29246
30717
  }
29247
30718
  } catch (error) {
29248
- 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)}
29249
30720
 
29250
- Usage: ${p7("cost_session")} { session_id: "..." }, ${p7("cost_trend")} { days: 30 }`);
30721
+ Usage: ${p8("cost_session")} { session_id: "..." }, ${p8("cost_trend")} { days: 30 }`);
29251
30722
  }
29252
30723
  }
29253
30724
  function handleCostSession(args2, db) {
29254
30725
  const sessionId = args2.session_id;
29255
- 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.`);
29256
30727
  const cost = db.prepare(
29257
30728
  "SELECT * FROM session_costs WHERE session_id = ? ORDER BY created_at DESC LIMIT 1"
29258
30729
  ).get(sessionId);
29259
30730
  if (!cost) {
29260
- 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.`);
29261
30732
  }
29262
30733
  const modelPricing = getModelPricing();
29263
30734
  const mp = modelPricing[cost.model] ?? modelPricing["claude-sonnet-4-5"] ?? { input_per_million: 3, output_per_million: 15 };
@@ -29278,13 +30749,13 @@ function handleCostSession(args2, db) {
29278
30749
  `| Cache Write | ${cost.cache_write_tokens.toLocaleString()} | $${cacheWriteCost.toFixed(4)} |`,
29279
30750
  `| **Total** | ${cost.total_tokens.toLocaleString()} | **$${cost.estimated_cost_usd.toFixed(4)}** |`
29280
30751
  ];
29281
- return text6(lines.join("\n"));
30752
+ return text7(lines.join("\n"));
29282
30753
  }
29283
30754
  function handleCostTrend(args2, db) {
29284
30755
  const days = args2.days ?? 30;
29285
30756
  const groupBy = args2.group_by ?? "day";
29286
30757
  if (!["day", "week"].includes(groupBy)) {
29287
- 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" }`);
29288
30759
  }
29289
30760
  const sql = groupBy === "week" ? `SELECT strftime('%Y-W%W', created_at) as period,
29290
30761
  COUNT(*) as sessions,
@@ -29305,7 +30776,7 @@ function handleCostTrend(args2, db) {
29305
30776
  ORDER BY period ASC`;
29306
30777
  const rows = db.prepare(sql).all(`-${days} days`);
29307
30778
  if (rows.length === 0) {
29308
- 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.`);
29309
30780
  }
29310
30781
  const totalCost = rows.reduce((sum, r2) => sum + r2.total_cost, 0);
29311
30782
  const totalSessions = rows.reduce((sum, r2) => sum + r2.sessions, 0);
@@ -29323,7 +30794,7 @@ function handleCostTrend(args2, db) {
29323
30794
  `| ${row.period} | ${row.sessions} | $${row.total_cost.toFixed(2)} | $${row.avg_cost.toFixed(4)} |`
29324
30795
  );
29325
30796
  }
29326
- return text6(lines.join("\n"));
30797
+ return text7(lines.join("\n"));
29327
30798
  }
29328
30799
  function handleCostFeature(args2, db) {
29329
30800
  const days = args2.days ?? 30;
@@ -29335,7 +30806,7 @@ function handleCostFeature(args2, db) {
29335
30806
  ORDER BY total_cost DESC
29336
30807
  `).all(`-${days} days`);
29337
30808
  if (rows.length === 0) {
29338
- 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.`);
29339
30810
  }
29340
30811
  const totalCost = rows.reduce((sum, r2) => sum + r2.total_cost, 0);
29341
30812
  const lines = [
@@ -29351,9 +30822,9 @@ function handleCostFeature(args2, db) {
29351
30822
  `| ${row.feature_key} | $${row.total_cost.toFixed(2)} | ${row.total_tokens.toLocaleString()} | ${row.entries} | ${pct}% |`
29352
30823
  );
29353
30824
  }
29354
- return text6(lines.join("\n"));
30825
+ return text7(lines.join("\n"));
29355
30826
  }
29356
- function text6(content) {
30827
+ function text7(content) {
29357
30828
  return { content: [{ type: "text", text: content }] };
29358
30829
  }
29359
30830
  var DEFAULT_MODEL_PRICING, COST_BASE_NAMES;
@@ -29373,7 +30844,7 @@ var init_cost_tracker = __esm({
29373
30844
  });
29374
30845
 
29375
30846
  // src/prompt-analyzer.ts
29376
- function p8(baseName) {
30847
+ function p9(baseName) {
29377
30848
  return `${getConfig().toolPrefix}_${baseName}`;
29378
30849
  }
29379
30850
  function categorizePrompt(promptText) {
@@ -29388,7 +30859,7 @@ function categorizePrompt(promptText) {
29388
30859
  function getPromptToolDefinitions() {
29389
30860
  return [
29390
30861
  {
29391
- name: p8("prompt_effectiveness"),
30862
+ name: p9("prompt_effectiveness"),
29392
30863
  description: "Prompt effectiveness statistics by category. Shows success rates, average corrections needed, and best-performing prompt patterns.",
29393
30864
  inputSchema: {
29394
30865
  type: "object",
@@ -29403,7 +30874,7 @@ function getPromptToolDefinitions() {
29403
30874
  }
29404
30875
  },
29405
30876
  {
29406
- name: p8("prompt_suggestions"),
30877
+ name: p9("prompt_suggestions"),
29407
30878
  description: "Suggest improvements for a prompt based on past outcomes. Finds similar prompts ranked by success rate.",
29408
30879
  inputSchema: {
29409
30880
  type: "object",
@@ -29430,12 +30901,12 @@ function handlePromptToolCall(name, args2, memoryDb) {
29430
30901
  case "prompt_suggestions":
29431
30902
  return handleSuggestions(args2, memoryDb);
29432
30903
  default:
29433
- return text7(`Unknown prompt tool: ${name}`);
30904
+ return text8(`Unknown prompt tool: ${name}`);
29434
30905
  }
29435
30906
  } catch (error) {
29436
- 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)}
29437
30908
 
29438
- Usage: ${p8("prompt_effectiveness")} { days: 30 }, ${p8("prompt_suggestions")} { prompt: "..." }`);
30909
+ Usage: ${p9("prompt_effectiveness")} { days: 30 }, ${p9("prompt_suggestions")} { prompt: "..." }`);
29439
30910
  }
29440
30911
  }
29441
30912
  function handleEffectiveness(args2, db) {
@@ -29461,7 +30932,7 @@ function handleEffectiveness(args2, db) {
29461
30932
  sql += " GROUP BY prompt_category ORDER BY total DESC";
29462
30933
  const rows = db.prepare(sql).all(...params);
29463
30934
  if (rows.length === 0) {
29464
- 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.`);
29465
30936
  }
29466
30937
  const lines = [
29467
30938
  `## Prompt Effectiveness (${days} days)`,
@@ -29476,11 +30947,11 @@ function handleEffectiveness(args2, db) {
29476
30947
  `| ${row.prompt_category} | ${total} | ${successRate}% | ${row.partials} | ${row.failures} | ${row.abandoned} | ${row.avg_corrections.toFixed(1)} | ${Math.round(row.avg_word_count)} |`
29477
30948
  );
29478
30949
  }
29479
- return text7(lines.join("\n"));
30950
+ return text8(lines.join("\n"));
29480
30951
  }
29481
30952
  function handleSuggestions(args2, db) {
29482
30953
  const prompt = args2.prompt;
29483
- 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.`);
29484
30955
  const category = categorizePrompt(prompt);
29485
30956
  const wordCount = prompt.split(/\s+/).length;
29486
30957
  const similar = db.prepare(`
@@ -29523,9 +30994,9 @@ function handleSuggestions(args2, db) {
29523
30994
  lines.push(`- Success rate: ${Math.round(stats.successes / stats.total * 100)}%`);
29524
30995
  lines.push(`- Avg corrections needed: ${stats.avg_corrections.toFixed(1)}`);
29525
30996
  }
29526
- return text7(lines.join("\n"));
30997
+ return text8(lines.join("\n"));
29527
30998
  }
29528
- function text7(content) {
30999
+ function text8(content) {
29529
31000
  return { content: [{ type: "text", text: content }] };
29530
31001
  }
29531
31002
  var PROMPT_BASE_NAMES;
@@ -29538,290 +31009,8 @@ var init_prompt_analyzer = __esm({
29538
31009
  }
29539
31010
  });
29540
31011
 
29541
- // src/audit-trail.ts
29542
- function p9(baseName) {
29543
- return `${getConfig().toolPrefix}_${baseName}`;
29544
- }
29545
- function getAuditFormats() {
29546
- return getConfig().governance?.audit?.formats ?? DEFAULT_FORMATS;
29547
- }
29548
- function queryAuditLog(db, options) {
29549
- let sql = "SELECT * FROM audit_log WHERE 1=1";
29550
- const params = [];
29551
- if (options.eventType) {
29552
- sql += " AND event_type = ?";
29553
- params.push(options.eventType);
29554
- }
29555
- if (options.actor) {
29556
- sql += " AND actor = ?";
29557
- params.push(options.actor);
29558
- }
29559
- if (options.days) {
29560
- sql += " AND timestamp >= datetime('now', ?)";
29561
- params.push(`-${options.days} days`);
29562
- }
29563
- if (options.filePath) {
29564
- sql += " AND file_path = ?";
29565
- params.push(options.filePath);
29566
- }
29567
- if (options.changeType) {
29568
- sql += " AND change_type = ?";
29569
- params.push(options.changeType);
29570
- }
29571
- sql += " ORDER BY timestamp DESC";
29572
- if (options.limit) {
29573
- sql += " LIMIT ?";
29574
- params.push(options.limit);
29575
- }
29576
- return db.prepare(sql).all(...params);
29577
- }
29578
- function getFileChain(db, filePath) {
29579
- return db.prepare(
29580
- "SELECT * FROM audit_log WHERE file_path = ? ORDER BY timestamp ASC LIMIT 10000"
29581
- ).all(filePath);
29582
- }
29583
- function getAuditToolDefinitions() {
29584
- return [
29585
- {
29586
- name: p9("audit_log"),
29587
- description: "Query the audit log. Filter by event type, actor, file, or time range.",
29588
- inputSchema: {
29589
- type: "object",
29590
- properties: {
29591
- event_type: { type: "string", description: "Filter by event type: code_change, rule_enforced, approval, review, commit, compaction" },
29592
- actor: { type: "string", description: "Filter by actor: ai, human, hook, agent" },
29593
- file_path: { type: "string", description: "Filter by file path" },
29594
- change_type: { type: "string", description: "Filter by change type: create, edit, delete" },
29595
- days: { type: "number", description: "Days to look back (default: 30)" },
29596
- limit: { type: "number", description: "Max results (default: 50)" }
29597
- },
29598
- required: []
29599
- }
29600
- },
29601
- {
29602
- name: p9("audit_report"),
29603
- description: "Generate an audit report in a specified format (summary, detailed, soc2).",
29604
- inputSchema: {
29605
- type: "object",
29606
- properties: {
29607
- format: { type: "string", description: "Report format: summary, detailed, soc2 (default: summary)" },
29608
- days: { type: "number", description: "Days to cover (default: 30)" }
29609
- },
29610
- required: []
29611
- }
29612
- },
29613
- {
29614
- name: p9("audit_chain"),
29615
- description: "Get the complete audit trail for a specific file. Shows all changes, reviews, and decisions chronologically.",
29616
- inputSchema: {
29617
- type: "object",
29618
- properties: {
29619
- file: { type: "string", description: "File path to trace" }
29620
- },
29621
- required: ["file"]
29622
- }
29623
- }
29624
- ];
29625
- }
29626
- function isAuditTool(name) {
29627
- const pfx = getConfig().toolPrefix + "_";
29628
- const baseName = name.startsWith(pfx) ? name.slice(pfx.length) : name;
29629
- return AUDIT_BASE_NAMES.has(baseName);
29630
- }
29631
- function handleAuditToolCall(name, args2, memoryDb) {
29632
- try {
29633
- const pfx = getConfig().toolPrefix + "_";
29634
- const baseName = name.startsWith(pfx) ? name.slice(pfx.length) : name;
29635
- switch (baseName) {
29636
- case "audit_log":
29637
- return handleAuditLog(args2, memoryDb);
29638
- case "audit_report":
29639
- return handleAuditReport(args2, memoryDb);
29640
- case "audit_chain":
29641
- return handleAuditChain(args2, memoryDb);
29642
- default:
29643
- return text8(`Unknown audit tool: ${name}`);
29644
- }
29645
- } catch (error) {
29646
- return text8(`Error in ${name}: ${error instanceof Error ? error.message : String(error)}
29647
-
29648
- Usage: ${p9("audit_log")} { severity: "critical" }, ${p9("audit_report")} { format: "soc2" }`);
29649
- }
29650
- }
29651
- function handleAuditLog(args2, db) {
29652
- const entries = queryAuditLog(db, {
29653
- eventType: args2.event_type,
29654
- actor: args2.actor,
29655
- filePath: args2.file_path,
29656
- changeType: args2.change_type,
29657
- days: args2.days ?? 30,
29658
- limit: args2.limit ?? 50
29659
- });
29660
- if (entries.length === 0) {
29661
- 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.`);
29662
- }
29663
- const lines = [
29664
- `## Audit Log (${entries.length} entries)`,
29665
- "",
29666
- "| Timestamp | Event | Actor | Change | File | Evidence |",
29667
- "|-----------|-------|-------|--------|------|----------|"
29668
- ];
29669
- for (const entry of entries) {
29670
- const file = entry.file_path ? entry.file_path.split("/").pop() : "-";
29671
- const evidence = entry.evidence ? entry.evidence.slice(0, 50) : "-";
29672
- lines.push(
29673
- `| ${entry.timestamp.slice(0, 16)} | ${entry.event_type} | ${entry.actor} | ${entry.change_type ?? "-"} | ${file} | ${evidence} |`
29674
- );
29675
- }
29676
- return text8(lines.join("\n"));
29677
- }
29678
- function handleAuditReport(args2, db) {
29679
- const format = args2.format ?? "summary";
29680
- const days = args2.days ?? 30;
29681
- const availableFormats = getAuditFormats();
29682
- if (!availableFormats.includes(format)) {
29683
- 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.`);
29684
- }
29685
- const entries = queryAuditLog(db, { days });
29686
- if (entries.length === 0) {
29687
- 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.`);
29688
- }
29689
- const byEventType = {};
29690
- const byActor = {};
29691
- for (const e2 of entries) {
29692
- const et = e2.event_type;
29693
- byEventType[et] = (byEventType[et] ?? 0) + 1;
29694
- const actor = e2.actor;
29695
- byActor[actor] = (byActor[actor] ?? 0) + 1;
29696
- }
29697
- if (format === "soc2") {
29698
- return generateSoc2Report(entries, byEventType, byActor, days);
29699
- }
29700
- if (format === "detailed") {
29701
- return generateDetailedReport(entries, byEventType, byActor, days);
29702
- }
29703
- const lines = [
29704
- `## Audit Summary Report (${days} days)`,
29705
- `Generated: ${(/* @__PURE__ */ new Date()).toISOString().slice(0, 16)}`,
29706
- "",
29707
- "### By Event Type",
29708
- `| Event Type | Count |`,
29709
- `|------------|-------|`
29710
- ];
29711
- for (const [et, count] of Object.entries(byEventType).sort((a2, b2) => b2[1] - a2[1])) {
29712
- lines.push(`| ${et} | ${count} |`);
29713
- }
29714
- lines.push(`| **Total** | **${entries.length}** |`);
29715
- lines.push("");
29716
- lines.push("### By Actor");
29717
- lines.push(`| Actor | Count |`);
29718
- lines.push(`|-------|-------|`);
29719
- for (const [actor, count] of Object.entries(byActor).sort((a2, b2) => b2[1] - a2[1])) {
29720
- lines.push(`| ${actor} | ${count} |`);
29721
- }
29722
- return text8(lines.join("\n"));
29723
- }
29724
- function generateSoc2Report(entries, byEventType, byActor, days) {
29725
- const lines = [
29726
- `# SOC 2 Compliance Report`,
29727
- `Period: Last ${days} days`,
29728
- `Generated: ${(/* @__PURE__ */ new Date()).toISOString()}`,
29729
- "",
29730
- "## 1. Control Environment",
29731
- `Total audit events: ${entries.length}`,
29732
- `Human-initiated: ${byActor["human"] ?? 0}`,
29733
- `AI-initiated: ${byActor["ai"] ?? 0}`,
29734
- `Hook-triggered: ${byActor["hook"] ?? 0}`,
29735
- "",
29736
- "## 2. Change Management",
29737
- `Code changes logged: ${byEventType["code_change"] ?? 0}`,
29738
- `Rule enforcements: ${byEventType["rule_enforced"] ?? 0}`,
29739
- `Approvals recorded: ${byEventType["approval"] ?? 0}`,
29740
- `Reviews: ${byEventType["review"] ?? 0}`,
29741
- `Commits: ${byEventType["commit"] ?? 0}`,
29742
- "",
29743
- "## 3. Approval Status"
29744
- ];
29745
- const pendingApprovals = entries.filter((e2) => e2.approval_status === "pending");
29746
- const deniedApprovals = entries.filter((e2) => e2.approval_status === "denied");
29747
- lines.push(`Pending approvals: ${pendingApprovals.length}`);
29748
- lines.push(`Denied approvals: ${deniedApprovals.length}`);
29749
- lines.push("");
29750
- lines.push("## 4. Events Requiring Review");
29751
- const reviewEntries = entries.filter((e2) => e2.approval_status === "denied" || e2.event_type === "rule_enforced");
29752
- if (reviewEntries.length > 0) {
29753
- for (const e2 of reviewEntries.slice(0, 20)) {
29754
- lines.push(`- [${e2.timestamp.slice(0, 16)}] ${e2.event_type}: ${(e2.evidence ?? "").slice(0, 100)}`);
29755
- }
29756
- } else {
29757
- lines.push("No events requiring review in this period.");
29758
- }
29759
- return text8(lines.join("\n"));
29760
- }
29761
- function generateDetailedReport(entries, byEventType, byActor, days) {
29762
- const lines = [
29763
- `## Detailed Audit Report (${days} days)`,
29764
- `Total events: ${entries.length}`,
29765
- "",
29766
- "### Event Type Distribution"
29767
- ];
29768
- for (const [et, count] of Object.entries(byEventType).sort((a2, b2) => b2[1] - a2[1])) {
29769
- lines.push(`- ${et}: ${count}`);
29770
- }
29771
- lines.push("");
29772
- lines.push("### Actor Distribution");
29773
- for (const [actor, count] of Object.entries(byActor).sort((a2, b2) => b2[1] - a2[1])) {
29774
- lines.push(`- ${actor}: ${count}`);
29775
- }
29776
- lines.push("");
29777
- lines.push("### All Events");
29778
- for (const e2 of entries.slice(0, 100)) {
29779
- lines.push(`#### ${e2.timestamp.slice(0, 16)} [${e2.event_type}]`);
29780
- lines.push(`Actor: ${e2.actor} | Change: ${e2.change_type ?? "-"} | Approval: ${e2.approval_status ?? "-"}`);
29781
- if (e2.file_path) lines.push(`File: ${e2.file_path}`);
29782
- if (e2.evidence) lines.push(`Evidence: ${e2.evidence}`);
29783
- lines.push("");
29784
- }
29785
- if (entries.length > 100) {
29786
- lines.push(`... and ${entries.length - 100} more entries`);
29787
- }
29788
- return text8(lines.join("\n"));
29789
- }
29790
- function handleAuditChain(args2, db) {
29791
- const filePath = args2.file;
29792
- if (!filePath) return text8(`Usage: ${p9("audit_chain")} { file: "src/path/to/file.ts" } - Get complete audit history for a file.`);
29793
- const chain = getFileChain(db, filePath);
29794
- if (chain.length === 0) {
29795
- 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.`);
29796
- }
29797
- const lines = [
29798
- `## Audit Chain: ${filePath}`,
29799
- `Total events: ${chain.length}`,
29800
- ""
29801
- ];
29802
- for (const entry of chain) {
29803
- lines.push(`### ${entry.timestamp.slice(0, 16)} [${entry.event_type}]`);
29804
- lines.push(`**${entry.actor}** (${entry.change_type ?? "unknown"})`);
29805
- if (entry.evidence) lines.push(entry.evidence);
29806
- lines.push("");
29807
- }
29808
- return text8(lines.join("\n"));
29809
- }
29810
- function text8(content) {
29811
- return { content: [{ type: "text", text: content }] };
29812
- }
29813
- var DEFAULT_FORMATS, AUDIT_BASE_NAMES;
29814
- var init_audit_trail = __esm({
29815
- "src/audit-trail.ts"() {
29816
- "use strict";
29817
- init_config();
29818
- DEFAULT_FORMATS = ["summary", "detailed", "soc2"];
29819
- AUDIT_BASE_NAMES = /* @__PURE__ */ new Set(["audit_log", "audit_report", "audit_chain"]);
29820
- }
29821
- });
29822
-
29823
31012
  // src/validation-engine.ts
29824
- import { existsSync as existsSync55, readFileSync as readFileSync46 } from "fs";
31013
+ import { existsSync as existsSync57, readFileSync as readFileSync48 } from "fs";
29825
31014
  function p10(baseName) {
29826
31015
  return `${getConfig().toolPrefix}_${baseName}`;
29827
31016
  }
@@ -29852,7 +31041,7 @@ function validateFile(filePath, projectRoot) {
29852
31041
  });
29853
31042
  return checks;
29854
31043
  }
29855
- if (!existsSync55(absPath)) {
31044
+ if (!existsSync57(absPath)) {
29856
31045
  checks.push({
29857
31046
  name: "file_exists",
29858
31047
  severity: "error",
@@ -29861,7 +31050,7 @@ function validateFile(filePath, projectRoot) {
29861
31050
  });
29862
31051
  return checks;
29863
31052
  }
29864
- const source = readFileSync46(absPath, "utf-8");
31053
+ const source = readFileSync48(absPath, "utf-8");
29865
31054
  const lines = source.split("\n");
29866
31055
  if (activeChecks.rule_compliance !== false) {
29867
31056
  for (const ruleSet of config.rules) {
@@ -30320,7 +31509,7 @@ var init_adr_generator = __esm({
30320
31509
  });
30321
31510
 
30322
31511
  // src/security-scorer.ts
30323
- import { existsSync as existsSync56, readFileSync as readFileSync47 } from "fs";
31512
+ import { existsSync as existsSync58, readFileSync as readFileSync49 } from "fs";
30324
31513
  function p12(baseName) {
30325
31514
  return `${getConfig().toolPrefix}_${baseName}`;
30326
31515
  }
@@ -30344,12 +31533,12 @@ function scoreFileSecurity(filePath, projectRoot) {
30344
31533
  }]
30345
31534
  };
30346
31535
  }
30347
- if (!existsSync56(absPath)) {
31536
+ if (!existsSync58(absPath)) {
30348
31537
  return { riskScore: 0, findings: [] };
30349
31538
  }
30350
31539
  let source;
30351
31540
  try {
30352
- source = readFileSync47(absPath, "utf-8");
31541
+ source = readFileSync49(absPath, "utf-8");
30353
31542
  } catch {
30354
31543
  return { riskScore: 0, findings: [] };
30355
31544
  }
@@ -30639,7 +31828,7 @@ var init_security_scorer = __esm({
30639
31828
  });
30640
31829
 
30641
31830
  // src/dependency-scorer.ts
30642
- import { existsSync as existsSync57, readFileSync as readFileSync48 } from "fs";
31831
+ import { existsSync as existsSync59, readFileSync as readFileSync50 } from "fs";
30643
31832
  import { resolve as resolve37 } from "path";
30644
31833
  function p13(baseName) {
30645
31834
  return `${getConfig().toolPrefix}_${baseName}`;
@@ -30673,9 +31862,9 @@ function calculateDepRisk(factors) {
30673
31862
  }
30674
31863
  function getInstalledPackages(projectRoot) {
30675
31864
  const pkgPath = resolve37(projectRoot, "package.json");
30676
- if (!existsSync57(pkgPath)) return /* @__PURE__ */ new Map();
31865
+ if (!existsSync59(pkgPath)) return /* @__PURE__ */ new Map();
30677
31866
  try {
30678
- const pkg = JSON.parse(readFileSync48(pkgPath, "utf-8"));
31867
+ const pkg = JSON.parse(readFileSync50(pkgPath, "utf-8"));
30679
31868
  const packages = /* @__PURE__ */ new Map();
30680
31869
  for (const [name, version] of Object.entries(pkg.dependencies ?? {})) {
30681
31870
  packages.set(name, version);
@@ -31279,30 +32468,10 @@ var init_regression_detector = __esm({
31279
32468
  }
31280
32469
  });
31281
32470
 
31282
- // src/lib/corrections-md.ts
31283
- function formatCorrectionEntry(e2) {
31284
- const one = (s) => s.replace(/\s*\n+\s*/g, " ").trim();
31285
- const lines = [`### ${e2.date} - ${one(e2.title)}`];
31286
- if (e2.wrong) lines.push(`- **Wrong**: ${one(e2.wrong)}`);
31287
- if (e2.correction) lines.push(`- **Correction**: ${one(e2.correction)}`);
31288
- lines.push(`- **Rule**: ${one(e2.rule)}`);
31289
- for (const [k3, v3] of Object.entries(e2.extra ?? {})) {
31290
- lines.push(`- ${k3}: ${one(String(v3))}`);
31291
- }
31292
- return `
31293
- ${lines.join("\n")}
31294
- `;
31295
- }
31296
- var init_corrections_md = __esm({
31297
- "src/lib/corrections-md.ts"() {
31298
- "use strict";
31299
- }
31300
- });
31301
-
31302
32471
  // src/knowledge-indexer.ts
31303
- import { createHash as createHash13 } from "crypto";
31304
- import { readFileSync as readFileSync49, readdirSync as readdirSync27, statSync as statSync20, existsSync as existsSync58 } from "fs";
31305
- 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";
31306
32475
  function getKnowledgePaths() {
31307
32476
  const resolved = getResolvedPaths();
31308
32477
  const config = getConfig();
@@ -31350,7 +32519,7 @@ function categorizeFile(filePath) {
31350
32519
  const paths = getKnowledgePaths();
31351
32520
  if (filePath.startsWith(paths.plansDir)) return "plan";
31352
32521
  if (filePath.startsWith(paths.docsDir)) {
31353
- const relFromDocs = relative13(paths.docsDir, filePath).replace(/\\/g, "/").toLowerCase();
32522
+ const relFromDocs = relative14(paths.docsDir, filePath).replace(/\\/g, "/").toLowerCase();
31354
32523
  if (relFromDocs.startsWith("plans/")) return "plan";
31355
32524
  if (relFromDocs.includes("architecture")) return "architecture";
31356
32525
  if (relFromDocs.includes("security")) return "security";
@@ -31366,7 +32535,7 @@ function categorizeFile(filePath) {
31366
32535
  }
31367
32536
  const claudeDirName = getConfig().conventions?.claudeDirName ?? ".claude";
31368
32537
  if (filePath.includes(`${claudeDirName}/projects/`) && filePath.includes("/memory/")) return "memory";
31369
- const rel = relative13(paths.claudeDir, filePath).replace(/\\/g, "/");
32538
+ const rel = relative14(paths.claudeDir, filePath).replace(/\\/g, "/");
31370
32539
  const firstDir = rel.split("/")[0];
31371
32540
  const knownCategories = getConfig().conventions?.knowledgeCategories ?? [
31372
32541
  "patterns",
@@ -31388,7 +32557,7 @@ function categorizeFile(filePath) {
31388
32557
  return "root";
31389
32558
  }
31390
32559
  function hashContent2(content) {
31391
- return createHash13("sha256").update(content).digest("hex");
32560
+ return createHash14("sha256").update(content).digest("hex");
31392
32561
  }
31393
32562
  function parseCRTable(content) {
31394
32563
  const rules = [];
@@ -31609,11 +32778,11 @@ function indexAllKnowledge(db) {
31609
32778
  files.push(...memFiles);
31610
32779
  } catch {
31611
32780
  }
31612
- if (existsSync58(paths.plansDir)) {
32781
+ if (existsSync60(paths.plansDir)) {
31613
32782
  const planFiles = discoverMarkdownFiles(paths.plansDir);
31614
32783
  files.push(...planFiles);
31615
32784
  }
31616
- if (existsSync58(paths.docsDir)) {
32785
+ if (existsSync60(paths.docsDir)) {
31617
32786
  const excludePatterns = getConfig().conventions?.excludePatterns ?? ["/ARCHIVE/", "/SESSION-HISTORY/"];
31618
32787
  const docsFiles = discoverMarkdownFiles(paths.docsDir).filter((f2) => !f2.includes("/plans/") && !excludePatterns.some((p19) => f2.includes(p19)));
31619
32788
  files.push(...docsFiles);
@@ -31656,10 +32825,10 @@ function indexAllKnowledge(db) {
31656
32825
  } catch {
31657
32826
  }
31658
32827
  for (const filePath of files) {
31659
- if (!existsSync58(filePath)) continue;
31660
- const content = readFileSync49(filePath, "utf-8");
32828
+ if (!existsSync60(filePath)) continue;
32829
+ const content = readFileSync51(filePath, "utf-8");
31661
32830
  const hash = hashContent2(content);
31662
- 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);
31663
32832
  const category = categorizeFile(filePath);
31664
32833
  const title = extractTitle(content, filePath);
31665
32834
  const description = extractDescription2(content);
@@ -31792,10 +32961,10 @@ function isKnowledgeStale(db) {
31792
32961
  files.push(...discoverMarkdownFiles(paths.memoryDir));
31793
32962
  } catch {
31794
32963
  }
31795
- if (existsSync58(paths.plansDir)) {
32964
+ if (existsSync60(paths.plansDir)) {
31796
32965
  files.push(...discoverMarkdownFiles(paths.plansDir));
31797
32966
  }
31798
- if (existsSync58(paths.docsDir)) {
32967
+ if (existsSync60(paths.docsDir)) {
31799
32968
  const excludePatterns = getConfig().conventions?.excludePatterns ?? ["/ARCHIVE/", "/SESSION-HISTORY/"];
31800
32969
  const docsFiles = discoverMarkdownFiles(paths.docsDir).filter((f2) => !f2.includes("/plans/") && !excludePatterns.some((p19) => f2.includes(p19)));
31801
32970
  files.push(...docsFiles);
@@ -31824,7 +32993,7 @@ var init_knowledge_indexer = __esm({
31824
32993
  });
31825
32994
 
31826
32995
  // src/knowledge-tools.ts
31827
- 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";
31828
32997
  import { resolve as resolve39, basename as basename12 } from "path";
31829
32998
  function p16(baseName) {
31830
32999
  return `${getConfig().toolPrefix}_${baseName}`;
@@ -32577,14 +33746,14 @@ function handleCorrect(db, args2) {
32577
33746
  });
32578
33747
  let existing = "";
32579
33748
  try {
32580
- existing = readFileSync50(correctionsPath, "utf-8");
33749
+ existing = readFileSync52(correctionsPath, "utf-8");
32581
33750
  } catch {
32582
33751
  }
32583
33752
  const archiveIdx = existing.indexOf("## Archived");
32584
33753
  if (archiveIdx > 0) {
32585
33754
  const before = existing.slice(0, archiveIdx);
32586
33755
  const after = existing.slice(archiveIdx);
32587
- writeFileSync12(correctionsPath, before + entry + after);
33756
+ writeFileSync14(correctionsPath, before + entry + after);
32588
33757
  } else {
32589
33758
  appendFileSync4(correctionsPath, entry);
32590
33759
  }
@@ -33533,7 +34702,7 @@ var init_framework_supports = __esm({
33533
34702
  });
33534
34703
 
33535
34704
  // src/tools.ts
33536
- import { readFileSync as readFileSync51, existsSync as existsSync60 } from "fs";
34705
+ import { readFileSync as readFileSync53, existsSync as existsSync62 } from "fs";
33537
34706
  import { resolve as resolve40, basename as basename13 } from "path";
33538
34707
  function prefix2() {
33539
34708
  return getConfig().toolPrefix;
@@ -33953,8 +35122,8 @@ function handleContext(file, dataDb, codegraphDb) {
33953
35122
  const resolvedPaths = getResolvedPaths();
33954
35123
  const root = getProjectRoot();
33955
35124
  const absFilePath = ensureWithinRoot(resolve40(resolvedPaths.srcDir, "..", file), root);
33956
- if (existsSync60(absFilePath)) {
33957
- const fileContent = readFileSync51(absFilePath, "utf-8").slice(0, 3e3);
35125
+ if (existsSync62(absFilePath)) {
35126
+ const fileContent = readFileSync53(absFilePath, "utf-8").slice(0, 3e3);
33958
35127
  const keywords = [];
33959
35128
  if (fileContent.includes("ctx.db")) keywords.push("database", "schema");
33960
35129
  if (fileContent.includes("BigInt") || fileContent.includes("Decimal")) keywords.push("BigInt", "serialization");
@@ -34392,10 +35561,10 @@ function handleSchema(args2) {
34392
35561
  lines.push("");
34393
35562
  const projectRoot = getProjectRoot();
34394
35563
  const absPath = ensureWithinRoot(resolve40(projectRoot, file), projectRoot);
34395
- if (!existsSync60(absPath)) {
35564
+ if (!existsSync62(absPath)) {
34396
35565
  return text17(`File not found: ${file}`);
34397
35566
  }
34398
- const source = readFileSync51(absPath, "utf-8");
35567
+ const source = readFileSync53(absPath, "utf-8");
34399
35568
  const config = getConfig();
34400
35569
  const dbPattern = config.dbAccessPattern ?? "ctx.db.{table}";
34401
35570
  const regexStr = dbPattern.replace(/[.*+?^${}()|[\]\\]/g, "\\$&").replace("\\{table\\}", "(\\w+)");
@@ -34800,8 +35969,8 @@ var init_server_dispatch = __esm({
34800
35969
 
34801
35970
  // src/server.ts
34802
35971
  var server_exports = {};
34803
- import { readFileSync as readFileSync52 } from "fs";
34804
- 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";
34805
35974
  import { fileURLToPath as fileURLToPath6 } from "url";
34806
35975
  function pruneMemoryOnStartup() {
34807
35976
  try {
@@ -34843,10 +36012,10 @@ var init_server = __esm({
34843
36012
  init_consolidation_config();
34844
36013
  init_license();
34845
36014
  init_server_dispatch();
34846
- __dirname5 = dirname25(fileURLToPath6(import.meta.url));
36015
+ __dirname5 = dirname26(fileURLToPath6(import.meta.url));
34847
36016
  PKG_VERSION = (() => {
34848
36017
  try {
34849
- const pkg = JSON.parse(readFileSync52(resolve41(__dirname5, "..", "package.json"), "utf-8"));
36018
+ const pkg = JSON.parse(readFileSync54(resolve41(__dirname5, "..", "package.json"), "utf-8"));
34850
36019
  return pkg.version ?? "0.0.0";
34851
36020
  } catch {
34852
36021
  return "0.0.0";
@@ -35132,7 +36301,7 @@ var config_upgrade_exports = {};
35132
36301
  __export(config_upgrade_exports, {
35133
36302
  runConfigUpgrade: () => runConfigUpgrade
35134
36303
  });
35135
- 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";
35136
36305
  import { resolve as resolve42 } from "path";
35137
36306
  import { parse as parseYaml6 } from "yaml";
35138
36307
  async function runConfigUpgrade(opts = {}) {
@@ -35144,14 +36313,14 @@ async function runConfigUpgrade(opts = {}) {
35144
36313
  const err = opts.silent ? () => {
35145
36314
  } : (s) => process.stderr.write(s);
35146
36315
  if (opts.rollback) {
35147
- if (!existsSync61(bakPath)) {
36316
+ if (!existsSync63(bakPath)) {
35148
36317
  const message = `No backup found at ${bakPath}`;
35149
36318
  err(message + "\n");
35150
36319
  return { exitCode: 1, action: "none", message };
35151
36320
  }
35152
36321
  try {
35153
36322
  copyFileSync3(bakPath, configPath);
35154
- unlinkSync5(bakPath);
36323
+ unlinkSync6(bakPath);
35155
36324
  log("Config restored from backup.\n");
35156
36325
  return { exitCode: 0, action: "rolled-back" };
35157
36326
  } catch (e2) {
@@ -35160,14 +36329,14 @@ async function runConfigUpgrade(opts = {}) {
35160
36329
  return { exitCode: 2, action: "none", message };
35161
36330
  }
35162
36331
  }
35163
- if (!existsSync61(configPath)) {
36332
+ if (!existsSync63(configPath)) {
35164
36333
  const message = "massu.config.yaml not found. Run: npx massu init";
35165
36334
  err(message + "\n");
35166
36335
  return { exitCode: 1, action: "none", message };
35167
36336
  }
35168
36337
  let existing;
35169
36338
  try {
35170
- const content = readFileSync53(configPath, "utf-8");
36339
+ const content = readFileSync55(configPath, "utf-8");
35171
36340
  const parsed = parseYaml6(content);
35172
36341
  if (!parsed || typeof parsed !== "object") {
35173
36342
  throw new Error("config is not a YAML object");
@@ -35190,8 +36359,8 @@ async function runConfigUpgrade(opts = {}) {
35190
36359
  fingerprint: computeFingerprint(detection)
35191
36360
  };
35192
36361
  try {
35193
- const original = readFileSync53(configPath, "utf-8");
35194
- writeFileSync13(bakPath, original, "utf-8");
36362
+ const original = readFileSync55(configPath, "utf-8");
36363
+ writeFileSync15(bakPath, original, "utf-8");
35195
36364
  } catch (e2) {
35196
36365
  const message = `Failed to write backup: ${e2 instanceof Error ? e2.message : String(e2)}`;
35197
36366
  err(message + "\n");
@@ -35224,7 +36393,7 @@ var config_check_drift_exports = {};
35224
36393
  __export(config_check_drift_exports, {
35225
36394
  runConfigCheckDrift: () => runConfigCheckDrift
35226
36395
  });
35227
- import { existsSync as existsSync62, readFileSync as readFileSync54 } from "fs";
36396
+ import { existsSync as existsSync64, readFileSync as readFileSync56 } from "fs";
35228
36397
  import { resolve as resolve43 } from "path";
35229
36398
  import { parse as parseYaml7 } from "yaml";
35230
36399
  function renderChanges(changes) {
@@ -35238,7 +36407,7 @@ async function runConfigCheckDrift(opts = {}) {
35238
36407
  } : (s) => process.stdout.write(s);
35239
36408
  const err = opts.silent ? () => {
35240
36409
  } : (s) => process.stderr.write(s);
35241
- if (!existsSync62(configPath)) {
36410
+ if (!existsSync64(configPath)) {
35242
36411
  const message = "massu.config.yaml not found. Run: npx massu init";
35243
36412
  err(message + "\n");
35244
36413
  return {
@@ -35252,7 +36421,7 @@ async function runConfigCheckDrift(opts = {}) {
35252
36421
  }
35253
36422
  let config;
35254
36423
  try {
35255
- const content = readFileSync54(configPath, "utf-8");
36424
+ const content = readFileSync56(configPath, "utf-8");
35256
36425
  const parsed = parseYaml7(content);
35257
36426
  if (!parsed || typeof parsed !== "object") {
35258
36427
  throw new Error("config is not a YAML object");
@@ -35319,11 +36488,11 @@ var init_config_check_drift = __esm({
35319
36488
  });
35320
36489
 
35321
36490
  // src/cli.ts
35322
- import { readFileSync as readFileSync55 } from "fs";
35323
- 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";
35324
36493
  import { fileURLToPath as fileURLToPath7 } from "url";
35325
36494
  var __filename5 = fileURLToPath7(import.meta.url);
35326
- var __dirname6 = dirname26(__filename5);
36495
+ var __dirname6 = dirname27(__filename5);
35327
36496
  var args = process.argv.slice(2);
35328
36497
  var subcommand = args[0];
35329
36498
  async function main() {
@@ -35601,7 +36770,7 @@ Examples:
35601
36770
  }
35602
36771
  function printVersion() {
35603
36772
  try {
35604
- const pkg = JSON.parse(readFileSync55(resolve44(__dirname6, "../package.json"), "utf-8"));
36773
+ const pkg = JSON.parse(readFileSync57(resolve44(__dirname6, "../package.json"), "utf-8"));
35605
36774
  console.log(`massu v${pkg.version}`);
35606
36775
  } catch {
35607
36776
  console.log("massu v0.1.0");