@massu/core 2.5.0 → 2.5.2

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
@@ -1355,6 +1355,13 @@ var init_config = __esm({
1355
1355
  });
1356
1356
  LanguageFrameworkEntrySchema = z2.object({
1357
1357
  framework: z2.string().optional(),
1358
+ // `source_dirs` is emitted by `init` (commands/init.ts:417) and existence-checked
1359
+ // by config validation (init.ts:868-876), but until 2026-08-14 it survived only via
1360
+ // `.passthrough()` — declared in the file, untyped in the schema. `lib/source-layout.ts`
1361
+ // derives the index builders' candidate set from it, so it is typed here rather than
1362
+ // read as an unknown: a consumer that has to shape-check its own SoT is one branch away
1363
+ // from silently contributing `undefined` to a path predicate.
1364
+ source_dirs: z2.array(z2.string()).optional(),
1358
1365
  test_framework: z2.string().optional(),
1359
1366
  test: z2.string().optional(),
1360
1367
  runtime: z2.string().optional(),
@@ -1548,6 +1555,16 @@ var init_memory_vector = __esm({
1548
1555
  }
1549
1556
  });
1550
1557
 
1558
+ // src/lib/timestamps.ts
1559
+ function nowIso() {
1560
+ return (/* @__PURE__ */ new Date()).toISOString();
1561
+ }
1562
+ var init_timestamps = __esm({
1563
+ "src/lib/timestamps.ts"() {
1564
+ "use strict";
1565
+ }
1566
+ });
1567
+
1551
1568
  // src/db-backup.ts
1552
1569
  import { existsSync as existsSync6, mkdirSync as mkdirSync4, readdirSync as readdirSync2, statSync as statSync2, unlinkSync, copyFileSync, rmSync as rmSync4 } from "fs";
1553
1570
  import { resolve as resolve3, join as join3, basename, dirname as dirname4 } from "path";
@@ -3972,8 +3989,8 @@ function linkSessionToTask(db, sessionId, taskId) {
3972
3989
  }
3973
3990
  function addConversationTurn(db, sessionId, turnNumber, userPrompt, assistantResponse, toolCallsJson, toolCallCount, promptTokens, responseTokens) {
3974
3991
  const result = db.prepare(`
3975
- INSERT INTO conversation_turns (session_id, turn_number, user_prompt, assistant_response, tool_calls_json, tool_call_count, prompt_tokens, response_tokens)
3976
- VALUES (?, ?, ?, ?, ?, ?, ?, ?)
3992
+ INSERT INTO conversation_turns (session_id, turn_number, user_prompt, assistant_response, tool_calls_json, tool_call_count, prompt_tokens, response_tokens, created_at)
3993
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
3977
3994
  `).run(
3978
3995
  sessionId,
3979
3996
  turnNumber,
@@ -3982,14 +3999,15 @@ function addConversationTurn(db, sessionId, turnNumber, userPrompt, assistantRes
3982
3999
  toolCallsJson,
3983
4000
  toolCallCount,
3984
4001
  promptTokens,
3985
- responseTokens
4002
+ responseTokens,
4003
+ nowIso()
3986
4004
  );
3987
4005
  return Number(result.lastInsertRowid);
3988
4006
  }
3989
4007
  function addToolCallDetail(db, sessionId, turnNumber, toolName, inputSummary, inputSize, outputSize, success, filesInvolved) {
3990
4008
  db.prepare(`
3991
- INSERT INTO tool_call_details (session_id, turn_number, tool_name, tool_input_summary, tool_input_size, tool_output_size, tool_success, files_involved)
3992
- VALUES (?, ?, ?, ?, ?, ?, ?, ?)
4009
+ INSERT INTO tool_call_details (session_id, turn_number, tool_name, tool_input_summary, tool_input_size, tool_output_size, tool_success, files_involved, created_at)
4010
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
3993
4011
  `).run(
3994
4012
  sessionId,
3995
4013
  turnNumber,
@@ -3998,7 +4016,8 @@ function addToolCallDetail(db, sessionId, turnNumber, toolName, inputSummary, in
3998
4016
  inputSize,
3999
4017
  outputSize,
4000
4018
  success ? 1 : 0,
4001
- filesInvolved ? JSON.stringify(filesInvolved) : null
4019
+ filesInvolved ? JSON.stringify(filesInvolved) : null,
4020
+ nowIso()
4002
4021
  );
4003
4022
  }
4004
4023
  function getLastProcessedLine(db, sessionId) {
@@ -4257,6 +4276,7 @@ var init_memory_db = __esm({
4257
4276
  init_db_driver();
4258
4277
  init_config();
4259
4278
  init_memory_vector();
4279
+ init_timestamps();
4260
4280
  init_db_backup();
4261
4281
  init_rule_delivery();
4262
4282
  init_rule_candidate_store();
@@ -9973,21 +9993,21 @@ var require_pattern = __commonJS({
9973
9993
  exports.removeDuplicateSlashes = removeDuplicateSlashes;
9974
9994
  function partitionAbsoluteAndRelative(patterns) {
9975
9995
  const absolute = [];
9976
- const relative15 = [];
9996
+ const relative16 = [];
9977
9997
  for (const pattern of patterns) {
9978
- if (isAbsolute5(pattern)) {
9998
+ if (isAbsolute6(pattern)) {
9979
9999
  absolute.push(pattern);
9980
10000
  } else {
9981
- relative15.push(pattern);
10001
+ relative16.push(pattern);
9982
10002
  }
9983
10003
  }
9984
- return [absolute, relative15];
10004
+ return [absolute, relative16];
9985
10005
  }
9986
10006
  exports.partitionAbsoluteAndRelative = partitionAbsoluteAndRelative;
9987
- function isAbsolute5(pattern) {
10007
+ function isAbsolute6(pattern) {
9988
10008
  return path.isAbsolute(pattern);
9989
10009
  }
9990
- exports.isAbsolute = isAbsolute5;
10010
+ exports.isAbsolute = isAbsolute6;
9991
10011
  }
9992
10012
  });
9993
10013
 
@@ -11071,41 +11091,41 @@ var require_queue = __commonJS({
11071
11091
  queue.drained = drained;
11072
11092
  return queue;
11073
11093
  function push(value) {
11074
- var p19 = new Promise(function(resolve45, reject) {
11094
+ var p19 = new Promise(function(resolve47, reject) {
11075
11095
  pushCb(value, function(err, result) {
11076
11096
  if (err) {
11077
11097
  reject(err);
11078
11098
  return;
11079
11099
  }
11080
- resolve45(result);
11100
+ resolve47(result);
11081
11101
  });
11082
11102
  });
11083
11103
  p19.catch(noop);
11084
11104
  return p19;
11085
11105
  }
11086
11106
  function unshift(value) {
11087
- var p19 = new Promise(function(resolve45, reject) {
11107
+ var p19 = new Promise(function(resolve47, reject) {
11088
11108
  unshiftCb(value, function(err, result) {
11089
11109
  if (err) {
11090
11110
  reject(err);
11091
11111
  return;
11092
11112
  }
11093
- resolve45(result);
11113
+ resolve47(result);
11094
11114
  });
11095
11115
  });
11096
11116
  p19.catch(noop);
11097
11117
  return p19;
11098
11118
  }
11099
11119
  function drained() {
11100
- var p19 = new Promise(function(resolve45) {
11120
+ var p19 = new Promise(function(resolve47) {
11101
11121
  process.nextTick(function() {
11102
11122
  if (queue.idle()) {
11103
- resolve45();
11123
+ resolve47();
11104
11124
  } else {
11105
11125
  var previousDrain = queue.drain;
11106
11126
  queue.drain = function() {
11107
11127
  if (typeof previousDrain === "function") previousDrain();
11108
- resolve45();
11128
+ resolve47();
11109
11129
  queue.drain = previousDrain;
11110
11130
  };
11111
11131
  }
@@ -11591,9 +11611,9 @@ var require_stream3 = __commonJS({
11591
11611
  });
11592
11612
  }
11593
11613
  _getStat(filepath) {
11594
- return new Promise((resolve45, reject) => {
11614
+ return new Promise((resolve47, reject) => {
11595
11615
  this._stat(filepath, this._fsStatSettings, (error, stats) => {
11596
- return error === null ? resolve45(stats) : reject(error);
11616
+ return error === null ? resolve47(stats) : reject(error);
11597
11617
  });
11598
11618
  });
11599
11619
  }
@@ -11617,10 +11637,10 @@ var require_async5 = __commonJS({
11617
11637
  this._readerStream = new stream_1.default(this._settings);
11618
11638
  }
11619
11639
  dynamic(root, options) {
11620
- return new Promise((resolve45, reject) => {
11640
+ return new Promise((resolve47, reject) => {
11621
11641
  this._walkAsync(root, options, (error, entries) => {
11622
11642
  if (error === null) {
11623
- resolve45(entries);
11643
+ resolve47(entries);
11624
11644
  } else {
11625
11645
  reject(error);
11626
11646
  }
@@ -11630,10 +11650,10 @@ var require_async5 = __commonJS({
11630
11650
  async static(patterns, options) {
11631
11651
  const entries = [];
11632
11652
  const stream = this._readerStream.static(patterns, options);
11633
- return new Promise((resolve45, reject) => {
11653
+ return new Promise((resolve47, reject) => {
11634
11654
  stream.once("error", reject);
11635
11655
  stream.on("data", (entry) => entries.push(entry));
11636
- stream.once("end", () => resolve45(entries));
11656
+ stream.once("end", () => resolve47(entries));
11637
11657
  });
11638
11658
  }
11639
11659
  };
@@ -16598,15 +16618,15 @@ function resolveHooksDir() {
16598
16618
  }
16599
16619
  return "node_modules/@massu/core/dist/hooks";
16600
16620
  }
16601
- function massuRuntimeDir(version) {
16602
- return resolve9(homedir7(), ".massu", "runtime", version);
16621
+ function massuRuntimeDir(version, home = homedir7()) {
16622
+ return resolve9(home, ".massu", "runtime", version);
16603
16623
  }
16604
- function massuRuntimeCliPath(version) {
16605
- return resolve9(massuRuntimeDir(version), "node_modules", "@massu", "core", "dist", "cli.js");
16624
+ function massuRuntimeCliPath(version, home = homedir7()) {
16625
+ return resolve9(massuRuntimeDir(version, home), "node_modules", "@massu", "core", "dist", "cli.js");
16606
16626
  }
16607
- function resolveMassuRuntimeCli(version) {
16627
+ function resolveMassuRuntimeCli(version, home = homedir7()) {
16608
16628
  if (process.env.MASSU_NO_NODE_DIRECT === "1") return null;
16609
- const cli = massuRuntimeCliPath(version);
16629
+ const cli = massuRuntimeCliPath(version, home);
16610
16630
  if (!existsSync19(cli)) return null;
16611
16631
  try {
16612
16632
  execFileSync2(process.execPath, [cli, "--version"], { stdio: "ignore", timeout: 2e4 });
@@ -16615,10 +16635,10 @@ function resolveMassuRuntimeCli(version) {
16615
16635
  return null;
16616
16636
  }
16617
16637
  }
16618
- function materializeMassuRuntime(version) {
16638
+ function materializeMassuRuntime(version, home = homedir7()) {
16619
16639
  if (process.env.MASSU_NO_NODE_DIRECT === "1") return false;
16620
- if (resolveMassuRuntimeCli(version) !== null) return true;
16621
- const dir = massuRuntimeDir(version);
16640
+ if (resolveMassuRuntimeCli(version, home) !== null) return true;
16641
+ const dir = massuRuntimeDir(version, home);
16622
16642
  try {
16623
16643
  mkdirSync10(dir, { recursive: true });
16624
16644
  execFileSync2(
@@ -16629,10 +16649,10 @@ function materializeMassuRuntime(version) {
16629
16649
  } catch {
16630
16650
  return false;
16631
16651
  }
16632
- return resolveMassuRuntimeCli(version) !== null;
16652
+ return resolveMassuRuntimeCli(version, home) !== null;
16633
16653
  }
16634
- function massuShimPath() {
16635
- return resolve9(homedir7(), ".massu", "bin", "massu-hook");
16654
+ function massuShimPath(home = homedir7()) {
16655
+ return resolve9(home, ".massu", "bin", "massu-hook");
16636
16656
  }
16637
16657
  function massuShimBody() {
16638
16658
  return `#!/bin/sh
@@ -16657,10 +16677,10 @@ fi
16657
16677
  exec npx -y "@massu/core@\${MASSU_VERSION}" "$@"
16658
16678
  `;
16659
16679
  }
16660
- function installMassuShim() {
16680
+ function installMassuShim(home = homedir7()) {
16661
16681
  if (process.env.MASSU_NO_NODE_DIRECT === "1") return false;
16662
16682
  if (process.platform === "win32") return false;
16663
- const shim = massuShimPath();
16683
+ const shim = massuShimPath(home);
16664
16684
  try {
16665
16685
  mkdirSync10(dirname12(shim), { recursive: true });
16666
16686
  writeFileSync5(shim, massuShimBody(), "utf-8");
@@ -16676,10 +16696,10 @@ function installMassuShim() {
16676
16696
  return status === 2;
16677
16697
  }
16678
16698
  }
16679
- function resolveMassuShim() {
16699
+ function resolveMassuShim(home = homedir7()) {
16680
16700
  if (process.env.MASSU_NO_NODE_DIRECT === "1") return null;
16681
16701
  if (process.platform === "win32") return null;
16682
- const shim = massuShimPath();
16702
+ const shim = massuShimPath(home);
16683
16703
  if (!existsSync19(shim)) return null;
16684
16704
  try {
16685
16705
  execFileSync2(shim, [], { stdio: "ignore", timeout: 1e4 });
@@ -16695,9 +16715,8 @@ function hookCmd(version, hookName, shim) {
16695
16715
  function getHookTimeout(name) {
16696
16716
  return HOOK_TIMEOUTS[name] ?? 5;
16697
16717
  }
16698
- function buildHooksConfig(_hooksDir) {
16718
+ function buildHooksConfig(_hooksDir, shim = resolveMassuShim()) {
16699
16719
  const version = getInstallerVersion();
16700
- const shim = resolveMassuShim();
16701
16720
  return {
16702
16721
  SessionStart: [
16703
16722
  {
@@ -18154,7 +18173,7 @@ function checkHookFiles(projectRoot) {
18154
18173
  }
18155
18174
  async function checkHookExecution(projectRoot, opts = {}) {
18156
18175
  const { spawnSync: spawnSync6 } = await import("child_process");
18157
- const { mkdtempSync: mkdtempSync2, copyFileSync: copyFileSync4, rmSync: rmSync12 } = await import("fs");
18176
+ const { mkdtempSync: mkdtempSync2, copyFileSync: copyFileSync4, rmSync: rmSync13 } = await import("fs");
18158
18177
  const { tmpdir: tmpdir2 } = await import("os");
18159
18178
  const node = opts.node ?? process.execPath;
18160
18179
  const timeoutMs = opts.timeoutMs ?? 1e4;
@@ -18208,7 +18227,7 @@ async function checkHookExecution(projectRoot, opts = {}) {
18208
18227
  };
18209
18228
  } finally {
18210
18229
  try {
18211
- rmSync12(tmpCwd, { recursive: true, force: true });
18230
+ rmSync13(tmpCwd, { recursive: true, force: true });
18212
18231
  } catch {
18213
18232
  }
18214
18233
  }
@@ -20429,8 +20448,8 @@ function renderRegion(content, lines) {
20429
20448
  ${lines.join("\n")}
20430
20449
  ` : "\n";
20431
20450
  if (state.kind === "absent") {
20432
- const sep3 = content.endsWith("\n") ? "" : "\n";
20433
- return `${content}${sep3}
20451
+ const sep4 = content.endsWith("\n") ? "" : "\n";
20452
+ return `${content}${sep4}
20434
20453
  ${BEGIN_SENTINEL}${body}${END_SENTINEL}
20435
20454
  `;
20436
20455
  }
@@ -20573,7 +20592,7 @@ import { createHash as createHash8 } from "crypto";
20573
20592
  function renderMemoryFiles(db, candidates, opts) {
20574
20593
  const config = opts.config ?? resolveMemoryFilesConfig();
20575
20594
  if (!config.renderEnabled) {
20576
- return EMPTY("render_disabled");
20595
+ return EMPTY("render_disabled", candidates.length);
20577
20596
  }
20578
20597
  const dryRun = opts.dryRun === true;
20579
20598
  const now = opts.now ?? Date.now();
@@ -20582,7 +20601,7 @@ function renderMemoryFiles(db, candidates, opts) {
20582
20601
  });
20583
20602
  const { memoryDir } = opts;
20584
20603
  if (!existsSync33(memoryDir)) {
20585
- return EMPTY("no_memory_dir");
20604
+ return EMPTY("no_memory_dir", candidates.length);
20586
20605
  }
20587
20606
  try {
20588
20607
  return withMemoryIndexLock(
@@ -20591,10 +20610,10 @@ function renderMemoryFiles(db, candidates, opts) {
20591
20610
  );
20592
20611
  } catch (err) {
20593
20612
  if (err instanceof MemoryIndexLockBusy) {
20594
- return EMPTY("lock_busy");
20613
+ return EMPTY("lock_busy", candidates.length);
20595
20614
  }
20596
20615
  audit("memory_file_render_refused", { reason: "unexpected_error", error: String(err) });
20597
- return EMPTY("error");
20616
+ return EMPTY("error", candidates.length);
20598
20617
  }
20599
20618
  }
20600
20619
  function renderLocked(db, candidates, o) {
@@ -20604,11 +20623,14 @@ function renderLocked(db, candidates, o) {
20604
20623
  try {
20605
20624
  tombstones = readTombstones(memoryDir);
20606
20625
  } catch {
20607
- return EMPTY("tombstone_ledger_unreadable");
20626
+ return EMPTY("tombstone_ledger_unreadable", candidates.length);
20608
20627
  }
20609
20628
  const ownerOf = relPathOwnerLookup(db);
20610
20629
  const planned = [];
20611
- for (const c2 of candidates) {
20630
+ let unchanged = 0;
20631
+ let capped = 0;
20632
+ for (let i = 0; i < candidates.length; i++) {
20633
+ const c2 = candidates[i];
20612
20634
  if (!isLocalOrigin(c2.origin)) {
20613
20635
  refusals.push({ name: c2.name, reason: "non_local_origin", detail: c2.origin || "unknown" });
20614
20636
  audit("memory_file_render_refused", { name: c2.name, reason: "non_local_origin" });
@@ -20664,7 +20686,10 @@ function renderLocked(db, candidates, o) {
20664
20686
  continue;
20665
20687
  }
20666
20688
  const next = composeFile(c2, home);
20667
- if (next !== null && next === existing) continue;
20689
+ if (next !== null && next === existing) {
20690
+ unchanged++;
20691
+ continue;
20692
+ }
20668
20693
  }
20669
20694
  const content = composeFile(c2, home);
20670
20695
  if (content === null) {
@@ -20672,9 +20697,24 @@ function renderLocked(db, candidates, o) {
20672
20697
  continue;
20673
20698
  }
20674
20699
  planned.push({ c: c2, relPath, absPath, content });
20675
- if (planned.length >= config.renderMaxFilesPerSession) break;
20700
+ if (planned.length >= config.renderMaxFilesPerSession) {
20701
+ capped = candidates.length - (i + 1);
20702
+ break;
20703
+ }
20704
+ }
20705
+ const accounted = planned.length + refusals.length + unchanged + capped;
20706
+ if (accounted !== candidates.length) {
20707
+ audit("memory_file_render_refused", {
20708
+ reason: "accounting_leak",
20709
+ considered: candidates.length,
20710
+ accounted
20711
+ });
20712
+ throw new RenderAccountingLeak(
20713
+ `render did not account for every candidate: considered=${candidates.length} but accounted=${accounted} (planned=${planned.length} refused=${refusals.length} unchanged=${unchanged} capped=${capped}). A candidate was dropped without a bucket.`
20714
+ );
20676
20715
  }
20677
20716
  const indexLines = buildIndexLines(db, memoryDir, planned, config);
20717
+ const books = { considered: candidates.length, unchanged, capped };
20678
20718
  if (dryRun) {
20679
20719
  return {
20680
20720
  enabled: true,
@@ -20682,11 +20722,20 @@ function renderLocked(db, candidates, o) {
20682
20722
  written: planned.map((p19) => p19.relPath),
20683
20723
  refusals,
20684
20724
  indexLines,
20685
- bytesWritten: 0
20725
+ bytesWritten: 0,
20726
+ ...books
20686
20727
  };
20687
20728
  }
20688
20729
  if (planned.length === 0 && indexLines.length === 0) {
20689
- return { enabled: true, dryRun: false, written: [], refusals, indexLines: [], bytesWritten: 0 };
20730
+ return {
20731
+ enabled: true,
20732
+ dryRun: false,
20733
+ written: [],
20734
+ refusals,
20735
+ indexLines: [],
20736
+ bytesWritten: 0,
20737
+ ...books
20738
+ };
20690
20739
  }
20691
20740
  if (!hasFreshBackup(memoryDir, home)) {
20692
20741
  try {
@@ -20696,7 +20745,7 @@ function renderLocked(db, candidates, o) {
20696
20745
  reason: "backup_failed",
20697
20746
  error: err instanceof BackupError ? err.message : String(err)
20698
20747
  });
20699
- return EMPTY("backup_failed");
20748
+ return EMPTY("backup_failed", candidates.length);
20700
20749
  }
20701
20750
  }
20702
20751
  const indexPath = o.indexPath ?? join19(memoryDir, "MEMORY.md");
@@ -20764,7 +20813,10 @@ function renderLocked(db, candidates, o) {
20764
20813
  errors: restored.errors
20765
20814
  });
20766
20815
  return {
20767
- ...EMPTY("rollback_incomplete"),
20816
+ ...EMPTY("rollback_incomplete", candidates.length),
20817
+ // The books were balanced before the write began; keep them rather than zeroing.
20818
+ unchanged,
20819
+ capped,
20768
20820
  enabled: true,
20769
20821
  refusals: [
20770
20822
  ...refusals,
@@ -20773,9 +20825,9 @@ function renderLocked(db, candidates, o) {
20773
20825
  };
20774
20826
  }
20775
20827
  audit("memory_file_render_refused", { reason: "write_failed", error: String(err) });
20776
- return { ...EMPTY("write_failed"), enabled: true, refusals };
20828
+ return { ...EMPTY("write_failed", candidates.length), enabled: true, refusals, unchanged, capped };
20777
20829
  }
20778
- return { enabled: true, dryRun: false, written, refusals, indexLines, bytesWritten };
20830
+ return { enabled: true, dryRun: false, written, refusals, indexLines, bytesWritten, ...books };
20779
20831
  }
20780
20832
  function sha2562(s) {
20781
20833
  return createHash8("sha256").update(s, "utf8").digest("hex");
@@ -20844,7 +20896,7 @@ function buildIndexLines(db, memoryDir, planned, config) {
20844
20896
  function oneLine(s) {
20845
20897
  return s.replace(/[\r\n]+/g, " ").slice(0, 120);
20846
20898
  }
20847
- var EMPTY;
20899
+ var RenderAccountingLeak, EMPTY;
20848
20900
  var init_memory_renderer = __esm({
20849
20901
  "src/memory-renderer.ts"() {
20850
20902
  "use strict";
@@ -20859,14 +20911,22 @@ var init_memory_renderer = __esm({
20859
20911
  init_memory_index_region();
20860
20912
  init_memory_files_config();
20861
20913
  init_memory_file_ingest();
20862
- EMPTY = (reason) => ({
20914
+ RenderAccountingLeak = class extends Error {
20915
+ };
20916
+ EMPTY = (reason, considered = 0) => ({
20863
20917
  enabled: false,
20864
20918
  dryRun: false,
20865
20919
  written: [],
20866
20920
  refusals: [],
20867
20921
  indexLines: [],
20868
20922
  bytesWritten: 0,
20869
- skippedReason: reason
20923
+ skippedReason: reason,
20924
+ // An early gate refused, so every candidate was dropped — by that ONE named reason.
20925
+ // Reporting `considered` here is what distinguishes "nothing to do" from "walked away
20926
+ // from 59 rows because the lock was busy".
20927
+ considered,
20928
+ unchanged: 0,
20929
+ capped: 0
20870
20930
  });
20871
20931
  }
20872
20932
  });
@@ -21057,25 +21117,46 @@ var init_memory_render_cli = __esm({
21057
21117
  function loadRenderCandidates(db) {
21058
21118
  const cfg = resolveMemoryFilesConfig();
21059
21119
  const typePlaceholders = RENDERABLE_MEMORY_TYPES.map(() => "?").join(", ");
21120
+ const WHERE = `WHERE importance >= ?
21121
+ AND COALESCE(expired_at_epoch, 0) = 0
21122
+ AND type IN (${typePlaceholders})`;
21123
+ const { population } = db.prepare(`SELECT COUNT(*) AS population FROM observations ${WHERE}`).get(cfg.renderMinImportance, ...RENDERABLE_MEMORY_TYPES);
21060
21124
  const rows = db.prepare(
21061
21125
  `SELECT id, title, detail, importance, COALESCE(origin, 'local') AS origin
21062
21126
  FROM observations
21063
- WHERE importance >= ?
21064
- AND COALESCE(expired_at_epoch, 0) = 0
21065
- AND type IN (${typePlaceholders})
21127
+ ${WHERE}
21066
21128
  ORDER BY importance DESC, created_at_epoch DESC
21067
- LIMIT 50`
21129
+ LIMIT ${CANDIDATE_WINDOW}`
21068
21130
  ).all(cfg.renderMinImportance, ...RENDERABLE_MEMORY_TYPES);
21069
- return rows.filter((r2) => !r2.title.startsWith("[memory-file]")).map((r2) => ({
21070
- observationId: r2.id,
21071
- name: r2.title,
21072
- title: r2.title,
21073
- body: r2.detail ?? "",
21074
- importance: r2.importance,
21075
- origin: r2.origin
21076
- }));
21131
+ const reingest = rows.filter((r2) => r2.title.startsWith("[memory-file]"));
21132
+ const kept = rows.filter((r2) => !r2.title.startsWith("[memory-file]"));
21133
+ const excluded = reingest.length ? [{ reason: EXCLUSION_MEMORY_FILE_REINGEST, count: reingest.length }] : [];
21134
+ const ledger = {
21135
+ population,
21136
+ windowed: rows.length,
21137
+ truncatedByWindow: Math.max(0, population - rows.length),
21138
+ excluded,
21139
+ returned: kept.length
21140
+ };
21141
+ const accounted = ledger.returned + ledger.truncatedByWindow + excluded.reduce((n, e2) => n + e2.count, 0);
21142
+ if (accounted !== population) {
21143
+ throw new CandidateAccountingLeak(
21144
+ `candidate ledger does not balance: population=${population} but accounted=${accounted} (returned=${ledger.returned} truncated=${ledger.truncatedByWindow} excluded=${JSON.stringify(excluded)}). A row was dropped without a bucket.`
21145
+ );
21146
+ }
21147
+ return {
21148
+ ledger,
21149
+ candidates: kept.map((r2) => ({
21150
+ observationId: r2.id,
21151
+ name: r2.title,
21152
+ title: r2.title,
21153
+ body: r2.detail ?? "",
21154
+ importance: r2.importance,
21155
+ origin: r2.origin
21156
+ }))
21157
+ };
21077
21158
  }
21078
- var RENDERABLE_MEMORY_TYPES;
21159
+ var RENDERABLE_MEMORY_TYPES, CANDIDATE_WINDOW, EXCLUSION_MEMORY_FILE_REINGEST, CandidateAccountingLeak;
21079
21160
  var init_memory_render_candidates = __esm({
21080
21161
  "src/memory-render-candidates.ts"() {
21081
21162
  "use strict";
@@ -21086,6 +21167,10 @@ var init_memory_render_candidates = __esm({
21086
21167
  "incident_near_miss",
21087
21168
  "cr_violation"
21088
21169
  ];
21170
+ CANDIDATE_WINDOW = 50;
21171
+ EXCLUSION_MEMORY_FILE_REINGEST = "memory_file_reingest";
21172
+ CandidateAccountingLeak = class extends Error {
21173
+ };
21089
21174
  }
21090
21175
  });
21091
21176
 
@@ -21165,13 +21250,13 @@ function pruneNoiseObservations(db, opts = {}) {
21165
21250
  const counts = countNoise(db);
21166
21251
  if (dryRun || counts.total === 0) return { counts, expired: 0, dryRun };
21167
21252
  const nowSec = opts.now ?? Math.floor(Date.now() / 1e3);
21168
- const nowIso = new Date(nowSec * 1e3).toISOString();
21253
+ const nowIso2 = new Date(nowSec * 1e3).toISOString();
21169
21254
  const res = db.prepare(
21170
21255
  `UPDATE observations
21171
21256
  SET expired_at = ?, expired_at_epoch = ?, valid_to = ?, valid_to_epoch = ?
21172
21257
  WHERE COALESCE(expired_at_epoch, 0) = 0
21173
21258
  AND ((${DA_PREDICATE}) OR (${FT_DECISION_PREDICATE}) OR (${DB_PREDICATE}))`
21174
- ).run(nowIso, nowSec, nowIso, nowSec);
21259
+ ).run(nowIso2, nowSec, nowIso2, nowSec);
21175
21260
  return { counts, expired: res.changes, dryRun: false };
21176
21261
  }
21177
21262
  var DA_PREDICATE, FT_DECISION_PREDICATE, DB_PREDICATE;
@@ -21216,7 +21301,7 @@ async function runMemoryRenderCli(sub, args2) {
21216
21301
  };
21217
21302
  }
21218
21303
  const cfg = resolveMemoryFilesConfig();
21219
- const candidates = loadRenderCandidates(db);
21304
+ const { candidates, ledger } = loadRenderCandidates(db);
21220
21305
  const result = renderMemoryFiles(db, candidates, {
21221
21306
  memoryDir,
21222
21307
  dryRun: true,
@@ -21231,6 +21316,19 @@ async function runMemoryRenderCli(sub, args2) {
21231
21316
  }
21232
21317
  lines.push(`Massu would write ${result.written.length} file(s):`);
21233
21318
  for (const f2 of result.written) lines.push(` ${f2}`);
21319
+ lines.push("", "Where every row went:");
21320
+ lines.push(` candidate population (type+importance+expiry) : ${ledger.population}`);
21321
+ if (ledger.truncatedByWindow > 0) {
21322
+ lines.push(` truncated by the row window : ${ledger.truncatedByWindow}`);
21323
+ }
21324
+ for (const e2 of ledger.excluded) {
21325
+ lines.push(` excluded: ${e2.reason.padEnd(30)} : ${e2.count}`);
21326
+ }
21327
+ lines.push(` -> reached the renderer : ${ledger.returned}`);
21328
+ lines.push(
21329
+ ` renderer: considered ${result.considered} = written ${result.written.length} + refused ${result.refusals.length} + unchanged ${result.unchanged} + capped ${result.capped}`
21330
+ );
21331
+ lines.push(` skipped: ${result.skippedReason ?? "(none)"}`);
21234
21332
  if (result.indexLines.length > 0) {
21235
21333
  lines.push("", "MEMORY.md \u2014 inside the massu:learned region only:");
21236
21334
  for (const l2 of result.indexLines) lines.push(` ${l2}`);
@@ -22817,8 +22915,8 @@ function writeCustomDestination(opts, candidate, slug, targets) {
22817
22915
  const rendered = renderTemplate2(entry.template, vars);
22818
22916
  if (!existsSync38(dirname19(destPath))) mkdirSync17(dirname19(destPath), { recursive: true });
22819
22917
  const pre = existsSync38(destPath) ? readFileSync29(destPath, "utf-8") : "";
22820
- const sep3 = pre && !pre.endsWith("\n") ? "\n" : "";
22821
- writeFileSync11(destPath, pre + sep3 + rendered + (rendered.endsWith("\n") ? "" : "\n"), "utf-8");
22918
+ const sep4 = pre && !pre.endsWith("\n") ? "\n" : "";
22919
+ writeFileSync11(destPath, pre + sep4 + rendered + (rendered.endsWith("\n") ? "" : "\n"), "utf-8");
22822
22920
  }
22823
22921
  function dismissRuleCandidate(db, opts) {
22824
22922
  if (!CANDIDATE_ID_PATTERN.test(opts.candidateId)) {
@@ -22871,8 +22969,8 @@ function dismissRuleCandidate(db, opts) {
22871
22969
  const dismissedLog = join24(projectRoot, ".massu", "rule-candidates", ".dismissed.jsonl");
22872
22970
  if (!existsSync38(dirname19(dismissedLog))) mkdirSync17(dirname19(dismissedLog), { recursive: true });
22873
22971
  const pre = existsSync38(dismissedLog) ? readFileSync29(dismissedLog, "utf-8") : "";
22874
- const sep3 = pre && !pre.endsWith("\n") ? "\n" : "";
22875
- writeFileSync11(dismissedLog, pre + sep3 + JSON.stringify({
22972
+ const sep4 = pre && !pre.endsWith("\n") ? "\n" : "";
22973
+ writeFileSync11(dismissedLog, pre + sep4 + JSON.stringify({
22876
22974
  id: opts.candidateId,
22877
22975
  prompt_hash: candidate.prompt_hash,
22878
22976
  score: candidate.score,
@@ -22903,8 +23001,8 @@ function appendFailureLog(projectRoot, entry) {
22903
23001
  if (!existsSync38(dir)) mkdirSync17(dir, { recursive: true });
22904
23002
  const logPath = join24(dir, ".failures.jsonl");
22905
23003
  const pre = existsSync38(logPath) ? readFileSync29(logPath, "utf-8") : "";
22906
- const sep3 = pre && !pre.endsWith("\n") ? "\n" : "";
22907
- writeFileSync11(logPath, pre + sep3 + JSON.stringify(entry) + "\n", "utf-8");
23004
+ const sep4 = pre && !pre.endsWith("\n") ? "\n" : "";
23005
+ writeFileSync11(logPath, pre + sep4 + JSON.stringify(entry) + "\n", "utf-8");
22908
23006
  } catch {
22909
23007
  }
22910
23008
  }
@@ -23389,7 +23487,7 @@ async function pullInstalledPackRules(db, opts = {}) {
23389
23487
  }
23390
23488
  const packs = parsePacks(envelope.packs_json);
23391
23489
  const result = { ...ZERO };
23392
- const nowIso = (/* @__PURE__ */ new Date()).toISOString();
23490
+ const nowIso2 = (/* @__PURE__ */ new Date()).toISOString();
23393
23491
  for (const pack of packs) {
23394
23492
  if (!isValidInstalledPack(pack)) continue;
23395
23493
  for (const rawRule of pack.rules) {
@@ -23406,7 +23504,7 @@ async function pullInstalledPackRules(db, opts = {}) {
23406
23504
  result.skipped += 1;
23407
23505
  continue;
23408
23506
  }
23409
- materializeCandidate(projectRoot, candidatePath, promptHash, rule, pack, signedOrgId, nowIso, opts.home ?? homedir20());
23507
+ materializeCandidate(projectRoot, candidatePath, promptHash, rule, pack, signedOrgId, nowIso2, opts.home ?? homedir20());
23410
23508
  result.materialized += 1;
23411
23509
  }
23412
23510
  }
@@ -23453,7 +23551,7 @@ function alreadyApplied(db, promptHash) {
23453
23551
  return false;
23454
23552
  }
23455
23553
  }
23456
- function materializeCandidate(projectRoot, candidatePath, promptHash, rule, pack, orgId, nowIso, home) {
23554
+ function materializeCandidate(projectRoot, candidatePath, promptHash, rule, pack, orgId, nowIso2, home) {
23457
23555
  const promptText = `${rule.title}: ${rule.description}`.replace(/\n+/g, " ").slice(0, 200) || `pack rule ${promptHash}`;
23458
23556
  const draftText = rule.pattern ?? rule.check ?? rule.description;
23459
23557
  const hardened = isExecutableDestination(rule.destination);
@@ -23469,7 +23567,7 @@ function materializeCandidate(projectRoot, candidatePath, promptHash, rule, pack
23469
23567
  score: 0,
23470
23568
  signals: [],
23471
23569
  prior_turn_files: [],
23472
- timestamp: nowIso,
23570
+ timestamp: nowIso2,
23473
23571
  session_id: `pack:${pack.slug}`,
23474
23572
  // Provenance (P2-002/P2-003): the applier's team-origin / hardened apply-gate
23475
23573
  // keys on this. `origin:'pack'` rides the SAME gate as a team origin
@@ -23481,7 +23579,7 @@ function materializeCandidate(projectRoot, candidatePath, promptHash, rule, pack
23481
23579
  origin: "pack",
23482
23580
  org_id: orgId,
23483
23581
  promoted_by: `pack:${pack.slug}`,
23484
- promoted_at: nowIso,
23582
+ promoted_at: nowIso2,
23485
23583
  signature_verified: true,
23486
23584
  ...applyMac ? { apply_mac: applyMac } : {},
23487
23585
  pack_slug: pack.slug,
@@ -25289,12 +25387,102 @@ var init_gitToplevel = __esm({
25289
25387
  }
25290
25388
  });
25291
25389
 
25390
+ // src/watch/canary.ts
25391
+ import { mkdirSync as mkdirSync20, rmSync as rmSync10, writeFileSync as writeFileSync15 } from "fs";
25392
+ import { isAbsolute as isAbsolute3, relative as relative7, resolve as resolve20, sep as sep2 } from "path";
25393
+ function toRelative(projectRoot, p19) {
25394
+ const raw = String(p19);
25395
+ const rel = isAbsolute3(raw) ? relative7(projectRoot, raw) : raw;
25396
+ return rel.split(sep2).join("/");
25397
+ }
25398
+ function isCanaryPath(projectRoot, p19) {
25399
+ const rel = toRelative(projectRoot, p19);
25400
+ return rel === CANARY_DIR_REL || rel.startsWith(`${CANARY_DIR_REL}/`);
25401
+ }
25402
+ function canaryAwareIgnore(ignore, projectRoot) {
25403
+ const withoutMassu = ignore.filter((g2) => g2 !== "**/.massu/**");
25404
+ const massuButNotCanary = (p19) => {
25405
+ if (isCanaryPath(projectRoot, p19)) return false;
25406
+ const rel = toRelative(projectRoot, p19);
25407
+ return rel === ".massu" || rel.startsWith(".massu/") || rel.includes("/.massu/");
25408
+ };
25409
+ return [...withoutMassu, massuButNotCanary];
25410
+ }
25411
+ function createWatcherCanary(opts) {
25412
+ const projectRoot = resolve20(opts.projectRoot);
25413
+ const now = opts.now ?? Date.now;
25414
+ const filePath = resolve20(projectRoot, CANARY_FILE_REL);
25415
+ let outstanding = false;
25416
+ let echoed = false;
25417
+ let consecutiveDead = 0;
25418
+ let armedAt = 0;
25419
+ return {
25420
+ dirRel: CANARY_DIR_REL,
25421
+ filePath,
25422
+ get consecutiveDead() {
25423
+ return consecutiveDead;
25424
+ },
25425
+ arm() {
25426
+ if (outstanding) return "armed";
25427
+ try {
25428
+ mkdirSync20(resolve20(projectRoot, CANARY_DIR_REL), { recursive: true });
25429
+ writeFileSync15(filePath, `${now()}
25430
+ `, "utf-8");
25431
+ } catch {
25432
+ outstanding = false;
25433
+ echoed = false;
25434
+ return "unwritable";
25435
+ }
25436
+ outstanding = true;
25437
+ echoed = false;
25438
+ armedAt = now();
25439
+ return "armed";
25440
+ },
25441
+ observe(path) {
25442
+ if (!isCanaryPath(projectRoot, path)) return false;
25443
+ if (outstanding) echoed = true;
25444
+ return true;
25445
+ },
25446
+ evaluate() {
25447
+ if (!outstanding) return "idle";
25448
+ if (now() - armedAt < CANARY_MIN_GRACE_MS) return "idle";
25449
+ outstanding = false;
25450
+ if (echoed) {
25451
+ consecutiveDead = 0;
25452
+ return "alive";
25453
+ }
25454
+ consecutiveDead += 1;
25455
+ return "dead";
25456
+ },
25457
+ cleanup() {
25458
+ try {
25459
+ rmSync10(filePath, { force: true });
25460
+ } catch {
25461
+ }
25462
+ }
25463
+ };
25464
+ }
25465
+ var CANARY_DIR_REL, CANARY_FILE_REL, CANARY_DEAD_STRIKES_BEFORE_REBUILD, CANARY_MIN_GRACE_MS, CANARY_DEAD_LOG, CANARY_REBUILD_LOG, CANARY_UNWRITABLE_LOG, CANARY_RECOVERED_LOG;
25466
+ var init_canary = __esm({
25467
+ "src/watch/canary.ts"() {
25468
+ "use strict";
25469
+ CANARY_DIR_REL = ".massu/watch-canary";
25470
+ CANARY_FILE_REL = `${CANARY_DIR_REL}/liveness`;
25471
+ CANARY_DEAD_STRIKES_BEFORE_REBUILD = 2;
25472
+ CANARY_MIN_GRACE_MS = 2500;
25473
+ CANARY_DEAD_LOG = "[massu] watcher liveness canary NOT echoed";
25474
+ CANARY_REBUILD_LOG = "[massu] rebuilding file watcher";
25475
+ CANARY_UNWRITABLE_LOG = "[massu] watcher liveness canary could not be written";
25476
+ CANARY_RECOVERED_LOG = "[massu] file watcher recovered";
25477
+ }
25478
+ });
25479
+
25292
25480
  // src/watch/lockfile-detector.ts
25293
25481
  import { existsSync as existsSync45, statSync as statSync15 } from "fs";
25294
- import { resolve as resolve20 } from "path";
25482
+ import { resolve as resolve21 } from "path";
25295
25483
  function lockfileMidWrite(projectRoot, now = Date.now(), windowMs = LOCKFILE_WINDOW_MS) {
25296
25484
  for (const lf of KNOWN_LOCKFILES) {
25297
- const p19 = resolve20(projectRoot, lf);
25485
+ const p19 = resolve21(projectRoot, lf);
25298
25486
  if (!existsSync45(p19)) continue;
25299
25487
  try {
25300
25488
  const stat = statSync15(p19);
@@ -25308,7 +25496,7 @@ function lockfileMidWrite(projectRoot, now = Date.now(), windowMs = LOCKFILE_WIN
25308
25496
  function gitMidOperation(projectRoot) {
25309
25497
  const sentinels = ["MERGE_HEAD", "REBASE_HEAD", "CHERRY_PICK_HEAD", "rebase-apply", "rebase-merge"];
25310
25498
  for (const s of sentinels) {
25311
- if (existsSync45(resolve20(projectRoot, ".git", s))) return true;
25499
+ if (existsSync45(resolve21(projectRoot, ".git", s))) return true;
25312
25500
  }
25313
25501
  return false;
25314
25502
  }
@@ -25507,13 +25695,13 @@ var init_paths = __esm({
25507
25695
  });
25508
25696
 
25509
25697
  // src/watch/state.ts
25510
- import { closeSync as closeSync5, existsSync as existsSync46, fsyncSync as fsyncSync5, mkdirSync as mkdirSync20, openSync as openSync5, readFileSync as readFileSync34, renameSync as renameSync5, rmSync as rmSync10, writeFileSync as writeFileSync15, writeSync as writeSync3 } from "fs";
25511
- import { dirname as dirname21, resolve as resolve21 } from "path";
25698
+ import { closeSync as closeSync5, existsSync as existsSync46, fsyncSync as fsyncSync5, mkdirSync as mkdirSync21, openSync as openSync5, readFileSync as readFileSync34, renameSync as renameSync5, rmSync as rmSync11, writeFileSync as writeFileSync16, writeSync as writeSync3 } from "fs";
25699
+ import { dirname as dirname21, resolve as resolve22 } from "path";
25512
25700
  function watchStatePath(projectRoot) {
25513
- return resolve21(projectRoot, ".massu", "watch-state.json");
25701
+ return resolve22(projectRoot, ".massu", "watch-state.json");
25514
25702
  }
25515
25703
  function backupStatePath(projectRoot) {
25516
- return resolve21(projectRoot, ".massu", "watch-state.v0.bak.json");
25704
+ return resolve22(projectRoot, ".massu", "watch-state.v0.bak.json");
25517
25705
  }
25518
25706
  function readState(projectRoot) {
25519
25707
  const path = watchStatePath(projectRoot);
@@ -25560,14 +25748,14 @@ function readState(projectRoot) {
25560
25748
  }
25561
25749
  function archiveCorrupt(projectRoot, content) {
25562
25750
  const bak = backupStatePath(projectRoot);
25563
- mkdirSync20(dirname21(bak), { recursive: true });
25564
- writeFileSync15(bak, content, "utf-8");
25751
+ mkdirSync21(dirname21(bak), { recursive: true });
25752
+ writeFileSync16(bak, content, "utf-8");
25565
25753
  }
25566
25754
  function writeStateAtomic(projectRoot, state) {
25567
25755
  const path = watchStatePath(projectRoot);
25568
25756
  writeStateAtomicCounter = writeStateAtomicCounter + 1 >>> 0;
25569
25757
  const tmp = `${path}.${process.pid}.${writeStateAtomicCounter}.tmp`;
25570
- mkdirSync20(dirname21(path), { recursive: true });
25758
+ mkdirSync21(dirname21(path), { recursive: true });
25571
25759
  let renamed = false;
25572
25760
  try {
25573
25761
  const fd = openSync5(tmp, "w");
@@ -25583,7 +25771,7 @@ function writeStateAtomic(projectRoot, state) {
25583
25771
  } finally {
25584
25772
  if (!renamed && existsSync46(tmp)) {
25585
25773
  try {
25586
- rmSync10(tmp, { force: true });
25774
+ rmSync11(tmp, { force: true });
25587
25775
  } catch {
25588
25776
  }
25589
25777
  }
@@ -25626,6 +25814,8 @@ var init_state = __esm({
25626
25814
 
25627
25815
  // src/watch/daemon.ts
25628
25816
  import * as chokidar from "chokidar";
25817
+ import { mkdirSync as mkdirSync22 } from "fs";
25818
+ import { resolve as resolve23 } from "path";
25629
25819
  function readDaemonConfig(projectRoot) {
25630
25820
  const cfg = getConfig();
25631
25821
  const w2 = cfg.watch ?? {};
@@ -25659,6 +25849,10 @@ async function startDaemon(projectRoot, hooks) {
25659
25849
  let tickTimer = null;
25660
25850
  let stopped = false;
25661
25851
  let runningRefresh = false;
25852
+ let canary = null;
25853
+ let watchSpec = null;
25854
+ let rebuilding = false;
25855
+ let watcherReady = false;
25662
25856
  function clearDebounce() {
25663
25857
  if (ctx.debounceTimer) {
25664
25858
  clearTimeout(ctx.debounceTimer);
@@ -25765,6 +25959,110 @@ async function startDaemon(projectRoot, hooks) {
25765
25959
  await watcher.close();
25766
25960
  watcher = null;
25767
25961
  }
25962
+ if (canary) {
25963
+ canary.cleanup();
25964
+ canary = null;
25965
+ }
25966
+ }
25967
+ function createWatcherInstance() {
25968
+ const spec = watchSpec;
25969
+ if (!spec) throw new Error("watch spec not derived before createWatcherInstance()");
25970
+ const w2 = chokidar.watch(spec.watch, {
25971
+ cwd: cfg.projectRoot,
25972
+ ignored: spec.ignored,
25973
+ ignoreInitial: true,
25974
+ persistent: true,
25975
+ awaitWriteFinish: false
25976
+ });
25977
+ w2.on("ready", () => {
25978
+ watcherReady = true;
25979
+ });
25980
+ w2.on("all", (event, path) => {
25981
+ if (canary?.observe(path)) return;
25982
+ if (!STACK_RELEVANT_EVENTS.has(event)) return;
25983
+ pushEvent(path);
25984
+ });
25985
+ w2.on("error", (err) => {
25986
+ const msg = err instanceof Error ? err.message : String(err);
25987
+ writeStderr(`[massu] chokidar error: ${msg}
25988
+ `);
25989
+ try {
25990
+ updateState(cfg.projectRoot, { lastError: `chokidar: ${msg}` });
25991
+ } catch {
25992
+ }
25993
+ });
25994
+ return w2;
25995
+ }
25996
+ function armCanary() {
25997
+ if (!canary || stopped) return;
25998
+ if (canary.arm() === "unwritable") {
25999
+ writeStderr(
26000
+ `${CANARY_UNWRITABLE_LOG} (${canary.filePath}); watcher liveness is UNKNOWN for this interval
26001
+ `
26002
+ );
26003
+ }
26004
+ }
26005
+ function isPowerOfTwo(n) {
26006
+ return n > 0 && (n & n - 1) === 0;
26007
+ }
26008
+ async function rebuildWatcher() {
26009
+ if (rebuilding || stopped || !watchSpec) return;
26010
+ rebuilding = true;
26011
+ try {
26012
+ const old = watcher;
26013
+ watcher = null;
26014
+ if (old) {
26015
+ try {
26016
+ await old.close();
26017
+ } catch {
26018
+ }
26019
+ }
26020
+ if (stopped) return;
26021
+ watcherReady = false;
26022
+ watcher = createWatcherInstance();
26023
+ } catch (err) {
26024
+ const msg = err instanceof Error ? err.message : String(err);
26025
+ writeStderr(`[massu] watcher rebuild FAILED: ${msg}
26026
+ `);
26027
+ } finally {
26028
+ rebuilding = false;
26029
+ }
26030
+ }
26031
+ function checkWatcherLiveness() {
26032
+ if (!canary || !watcher || rebuilding || !watcherReady) return;
26033
+ const priorDead = canary.consecutiveDead;
26034
+ const verdict = canary.evaluate();
26035
+ if (verdict === "dead") {
26036
+ const streak = canary.consecutiveDead;
26037
+ if (!isPowerOfTwo(streak)) return armCanary();
26038
+ writeStderr(
26039
+ `${CANARY_DEAD_LOG} after ${streak} consecutive interval(s): the watcher reported ready and emitted no error, but did not deliver its own canary write (${canary.filePath}). Treating it as DEAD and reconciling so changes are not silently missed.
26040
+ `
26041
+ );
26042
+ try {
26043
+ updateState(cfg.projectRoot, {
26044
+ lastError: `watcher liveness canary not echoed for ${streak} consecutive interval(s)`
26045
+ });
26046
+ } catch {
26047
+ }
26048
+ void forceReconciliation();
26049
+ if (streak >= CANARY_DEAD_STRIKES_BEFORE_REBUILD) {
26050
+ writeStderr(`${CANARY_REBUILD_LOG} after ${streak} dead interval(s) (close + re-watch)
26051
+ `);
26052
+ void rebuildWatcher();
26053
+ return;
26054
+ }
26055
+ return armCanary();
26056
+ }
26057
+ if (verdict === "alive" && priorDead > 0) {
26058
+ writeStderr(`${CANARY_RECOVERED_LOG} after ${priorDead} dead interval(s)
26059
+ `);
26060
+ try {
26061
+ updateState(cfg.projectRoot, { lastError: null });
26062
+ } catch {
26063
+ }
26064
+ }
26065
+ armCanary();
25768
26066
  }
25769
26067
  function tick() {
25770
26068
  if (stopped) return;
@@ -25781,6 +26079,7 @@ async function startDaemon(projectRoot, hooks) {
25781
26079
  `);
25782
26080
  void forceReconciliation();
25783
26081
  }
26082
+ checkWatcherLiveness();
25784
26083
  }
25785
26084
  if (!hooks.noWatcher) {
25786
26085
  const cfgYaml = getConfig();
@@ -25805,23 +26104,16 @@ async function startDaemon(projectRoot, hooks) {
25805
26104
  `[massu] watch surface: ${countLabel} files (cap ${cap}, opted-in: ${optedIn}, scan ${surfaceMs}ms, scope: ${globs.effectiveScope})
25806
26105
  `
25807
26106
  );
25808
- watcher = chokidar.watch(globs.watch, {
25809
- cwd: cfg.projectRoot,
25810
- ignored: globs.ignore,
25811
- ignoreInitial: true,
25812
- persistent: true,
25813
- awaitWriteFinish: false
25814
- });
25815
- watcher.on("all", (_event, path) => pushEvent(path));
25816
- watcher.on("error", (err) => {
25817
- const msg = err instanceof Error ? err.message : String(err);
25818
- writeStderr(`[massu] chokidar error: ${msg}
25819
- `);
25820
- try {
25821
- updateState(cfg.projectRoot, { lastError: `chokidar: ${msg}` });
25822
- } catch {
25823
- }
25824
- });
26107
+ canary = createWatcherCanary({ projectRoot: cfg.projectRoot, now });
26108
+ try {
26109
+ mkdirSync22(resolve23(cfg.projectRoot, canary.dirRel), { recursive: true });
26110
+ } catch {
26111
+ }
26112
+ watchSpec = {
26113
+ watch: [...globs.watch, canary.dirRel],
26114
+ ignored: canaryAwareIgnore(globs.ignore, cfg.projectRoot)
26115
+ };
26116
+ watcher = createWatcherInstance();
25825
26117
  }
25826
26118
  resetConfig();
25827
26119
  try {
@@ -25833,15 +26125,16 @@ async function startDaemon(projectRoot, hooks) {
25833
26125
  });
25834
26126
  } catch {
25835
26127
  }
25836
- tickTimer = setInterval(tick, TICK_INTERVAL_MS);
26128
+ tickTimer = setInterval(tick, hooks.tickIntervalMs ?? TICK_INTERVAL_MS);
25837
26129
  if (typeof tickTimer.unref === "function") tickTimer.unref();
25838
26130
  return { pushEvent, flushNow, stop, forceReconciliation };
25839
26131
  }
25840
- var STORM_WINDOW_MS, DEEP_STORM_WINDOW_MS, STORM_WAIT_MS, DEEP_STORM_WAIT_MS, TICK_INTERVAL_MS, TICK_GAP_THRESHOLD_MS;
26132
+ var STORM_WINDOW_MS, DEEP_STORM_WINDOW_MS, STORM_WAIT_MS, DEEP_STORM_WAIT_MS, TICK_INTERVAL_MS, TICK_GAP_THRESHOLD_MS, STACK_RELEVANT_EVENTS;
25841
26133
  var init_daemon = __esm({
25842
26134
  "src/watch/daemon.ts"() {
25843
26135
  "use strict";
25844
26136
  init_config();
26137
+ init_canary();
25845
26138
  init_lockfile_detector();
25846
26139
  init_paths();
25847
26140
  init_state();
@@ -25851,6 +26144,7 @@ var init_daemon = __esm({
25851
26144
  DEEP_STORM_WAIT_MS = 12e4;
25852
26145
  TICK_INTERVAL_MS = 1e4;
25853
26146
  TICK_GAP_THRESHOLD_MS = 3e4;
26147
+ STACK_RELEVANT_EVENTS = /* @__PURE__ */ new Set(["add", "change", "unlink"]);
25854
26148
  }
25855
26149
  });
25856
26150
 
@@ -25863,8 +26157,8 @@ __export(watch_exports, {
25863
26157
  runWatch: () => runWatch
25864
26158
  });
25865
26159
  import { spawnSync as spawnSync5 } from "child_process";
25866
- import { basename as basename8, dirname as dirname22, resolve as resolve22 } from "path";
25867
- import { appendFileSync as appendFileSync4, existsSync as existsSync47, mkdirSync as mkdirSync21, readFileSync as readFileSync35 } from "fs";
26160
+ import { basename as basename8, dirname as dirname22, resolve as resolve24 } from "path";
26161
+ import { appendFileSync as appendFileSync4, existsSync as existsSync47, mkdirSync as mkdirSync23, readFileSync as readFileSync35 } from "fs";
25868
26162
  function parseFlags(args2) {
25869
26163
  const out = {
25870
26164
  foreground: false,
@@ -25889,7 +26183,7 @@ function parseFlags(args2) {
25889
26183
  }
25890
26184
  function findClaudeBg() {
25891
26185
  const home = process.env.HOME ?? "";
25892
- const fixed = home ? resolve22(home, ".claude", "bin", "claude-bg") : null;
26186
+ const fixed = home ? resolve24(home, ".claude", "bin", "claude-bg") : null;
25893
26187
  if (fixed && existsSync47(fixed)) return fixed;
25894
26188
  const which = spawnSync5("which", ["claude-bg"], { encoding: "utf-8" });
25895
26189
  if (which.status === 0 && which.stdout) {
@@ -26033,7 +26327,7 @@ async function runForeground(root) {
26033
26327
  }
26034
26328
  throw err;
26035
26329
  }
26036
- return new Promise((resolve45) => {
26330
+ return new Promise((resolve47) => {
26037
26331
  const shutdown = async () => {
26038
26332
  if (stopped) return;
26039
26333
  stopped = true;
@@ -26043,7 +26337,7 @@ async function runForeground(root) {
26043
26337
  process.chdir(priorCwd);
26044
26338
  } catch {
26045
26339
  }
26046
- resolve45({ exitCode: 0 });
26340
+ resolve47({ exitCode: 0 });
26047
26341
  };
26048
26342
  process.on("SIGINT", () => {
26049
26343
  void shutdown();
@@ -26104,12 +26398,12 @@ async function runOnQuiescent(projectRoot) {
26104
26398
  );
26105
26399
  }
26106
26400
  function refreshLogPath(projectRoot) {
26107
- return resolve22(projectRoot, ".massu", "refresh-log.jsonl");
26401
+ return resolve24(projectRoot, ".massu", "refresh-log.jsonl");
26108
26402
  }
26109
26403
  function appendRefreshLog(projectRoot, event) {
26110
26404
  const path = refreshLogPath(projectRoot);
26111
26405
  try {
26112
- mkdirSync21(dirname22(path), { recursive: true });
26406
+ mkdirSync23(dirname22(path), { recursive: true });
26113
26407
  appendFileSync4(path, JSON.stringify(event) + "\n", "utf-8");
26114
26408
  } catch {
26115
26409
  }
@@ -26195,10 +26489,10 @@ import {
26195
26489
  closeSync as closeSync6,
26196
26490
  existsSync as existsSync48,
26197
26491
  fsyncSync as fsyncSync6,
26198
- mkdirSync as mkdirSync22,
26492
+ mkdirSync as mkdirSync24,
26199
26493
  openSync as openSync6,
26200
26494
  renameSync as renameSync6,
26201
- rmSync as rmSync11,
26495
+ rmSync as rmSync12,
26202
26496
  statSync as statSync16,
26203
26497
  writeSync as writeSync4
26204
26498
  } from "node:fs";
@@ -26212,7 +26506,7 @@ function atomicWrite(path, content, opts = {}) {
26212
26506
  if (opts.ensureParentDirMode !== void 0) {
26213
26507
  mkdirOpts.mode = opts.ensureParentDirMode;
26214
26508
  }
26215
- mkdirSync22(parentDir, mkdirOpts);
26509
+ mkdirSync24(parentDir, mkdirOpts);
26216
26510
  }
26217
26511
  const buf = typeof content === "string" ? Buffer.from(content, "utf-8") : content;
26218
26512
  const openMode = opts.mode ?? 420;
@@ -26231,7 +26525,7 @@ function atomicWrite(path, content, opts = {}) {
26231
26525
  } catch (err) {
26232
26526
  if (existsSync48(tmpPath)) {
26233
26527
  try {
26234
- rmSync11(tmpPath, { force: true });
26528
+ rmSync12(tmpPath, { force: true });
26235
26529
  } catch {
26236
26530
  }
26237
26531
  }
@@ -26580,13 +26874,13 @@ var init_fetcher = __esm({
26580
26874
  // src/security/manifest-cache.ts
26581
26875
  import { existsSync as existsSync49, readFileSync as readFileSync36, statSync as statSync17 } from "node:fs";
26582
26876
  import { homedir as homedir21 } from "node:os";
26583
- import { resolve as resolve23 } from "node:path";
26877
+ import { resolve as resolve25 } from "node:path";
26584
26878
  import { z as z5 } from "zod";
26585
26879
  function defaultCachePaths() {
26586
- const dir = resolve23(homedir21(), ".massu");
26880
+ const dir = resolve25(homedir21(), ".massu");
26587
26881
  return {
26588
- cachePath: resolve23(dir, "adapter-manifest.json"),
26589
- lockPath: resolve23(dir, ".adapter-manifest.lock")
26882
+ cachePath: resolve25(dir, "adapter-manifest.json"),
26883
+ lockPath: resolve25(dir, ".adapter-manifest.lock")
26590
26884
  };
26591
26885
  }
26592
26886
  function loadCachedManifest(paths = defaultCachePaths()) {
@@ -26786,13 +27080,13 @@ var init_adapter_origin = __esm({
26786
27080
  // src/security/local-fingerprint.ts
26787
27081
  import { existsSync as existsSync50, readFileSync as readFileSync37, lstatSync as lstatSync5 } from "node:fs";
26788
27082
  import { homedir as homedir22 } from "node:os";
26789
- import { resolve as resolve24, isAbsolute as isAbsolute3 } from "node:path";
27083
+ import { resolve as resolve26, isAbsolute as isAbsolute4 } from "node:path";
26790
27084
  import { createHash as createHash14 } from "node:crypto";
26791
27085
  import { z as z6 } from "zod";
26792
27086
  function computeLocalFingerprint(localPaths, projectRoot) {
26793
27087
  const tuples = [];
26794
27088
  for (const p19 of localPaths) {
26795
- const abs = isAbsolute3(p19) ? p19 : resolve24(projectRoot, p19);
27089
+ const abs = isAbsolute4(p19) ? p19 : resolve26(projectRoot, p19);
26796
27090
  let contentTag;
26797
27091
  try {
26798
27092
  const lst = lstatSync5(abs);
@@ -26864,7 +27158,7 @@ var init_local_fingerprint = __esm({
26864
27158
  "use strict";
26865
27159
  init_atomic_write();
26866
27160
  init_manifest_schema();
26867
- FINGERPRINT_PATH = resolve24(homedir22(), ".massu", "adapters-local-fingerprint.json");
27161
+ FINGERPRINT_PATH = resolve26(homedir22(), ".massu", "adapters-local-fingerprint.json");
26868
27162
  FingerprintSentinelSchema = z6.object({
26869
27163
  fingerprint: z6.string().regex(/^[0-9a-f]{64}$/),
26870
27164
  source: z6.enum(["cli", "cli-resync"]),
@@ -26881,9 +27175,9 @@ var init_local_fingerprint = __esm({
26881
27175
 
26882
27176
  // src/security/install-tracking.ts
26883
27177
  import { readFileSync as readFileSync38, readdirSync as readdirSync17, lstatSync as lstatSync6, existsSync as existsSync51 } from "node:fs";
26884
- import { join as join28, relative as relative7, sep as sep2 } from "node:path";
27178
+ import { join as join28, relative as relative8, sep as sep3 } from "node:path";
26885
27179
  import { homedir as homedir23 } from "node:os";
26886
- import { resolve as resolve25 } from "node:path";
27180
+ import { resolve as resolve27 } from "node:path";
26887
27181
  import { createHash as createHash15 } from "node:crypto";
26888
27182
  import { z as z7 } from "zod";
26889
27183
  function containsHiddenDirs(packageDir) {
@@ -26926,7 +27220,7 @@ function sha256OfDir(dir, opts = {}) {
26926
27220
  `sha256OfDir: file ${absPath} exceeds maxFileBytes (${lst.size} > ${maxFileBytes}); adapter packages should not ship files this large.`
26927
27221
  );
26928
27222
  }
26929
- const rel = relative7(dir, absPath).split(sep2).join("/");
27223
+ const rel = relative8(dir, absPath).split(sep3).join("/");
26930
27224
  files.push({ relativePath: rel, absPath });
26931
27225
  }
26932
27226
  }
@@ -27010,7 +27304,7 @@ var init_install_tracking = __esm({
27010
27304
  "src/security/install-tracking.ts"() {
27011
27305
  "use strict";
27012
27306
  init_atomic_write();
27013
- INSTALLED_MANIFEST_PATH = resolve25(homedir23(), ".massu", "adapter-manifest-installed.json");
27307
+ INSTALLED_MANIFEST_PATH = resolve27(homedir23(), ".massu", "adapter-manifest-installed.json");
27014
27308
  DEFAULT_MAX_FILE_BYTES = 64 * 1024 * 1024;
27015
27309
  EXCLUDED_DIR_NAMES = /* @__PURE__ */ new Set([".git", "node_modules", ".cache", ".tmp"]);
27016
27310
  InstallEntrySchema = z7.object({
@@ -27034,10 +27328,10 @@ var init_install_tracking = __esm({
27034
27328
 
27035
27329
  // src/detect/adapters/discover.ts
27036
27330
  import { existsSync as existsSync52, readdirSync as readdirSync18, readFileSync as readFileSync39, lstatSync as lstatSync7 } from "node:fs";
27037
- import { resolve as resolve26, isAbsolute as isAbsolute4 } from "node:path";
27331
+ import { resolve as resolve28, isAbsolute as isAbsolute5 } from "node:path";
27038
27332
  import { z as z8 } from "zod";
27039
27333
  function walkNodeModules(projectRoot, warnings) {
27040
- const nodeModulesDir = resolve26(projectRoot, "node_modules");
27334
+ const nodeModulesDir = resolve28(projectRoot, "node_modules");
27041
27335
  if (!existsSync52(nodeModulesDir)) {
27042
27336
  return [];
27043
27337
  }
@@ -27051,7 +27345,7 @@ function walkNodeModules(projectRoot, warnings) {
27051
27345
  }
27052
27346
  for (const entry of topLevelEntries) {
27053
27347
  if (entry.startsWith(".")) continue;
27054
- const entryPath = resolve26(nodeModulesDir, entry);
27348
+ const entryPath = resolve28(nodeModulesDir, entry);
27055
27349
  let entryStat;
27056
27350
  try {
27057
27351
  entryStat = lstatSync7(entryPath);
@@ -27073,7 +27367,7 @@ function walkNodeModules(projectRoot, warnings) {
27073
27367
  continue;
27074
27368
  }
27075
27369
  for (const sub of scopedEntries) {
27076
- const subPath = resolve26(entryPath, sub);
27370
+ const subPath = resolve28(entryPath, sub);
27077
27371
  let subStat;
27078
27372
  try {
27079
27373
  subStat = lstatSync7(subPath);
@@ -27092,7 +27386,7 @@ function walkNodeModules(projectRoot, warnings) {
27092
27386
  return candidates;
27093
27387
  }
27094
27388
  function tryReadAdapterPackage(packageDir, warnings) {
27095
- const pkgJsonPath = resolve26(packageDir, "package.json");
27389
+ const pkgJsonPath = resolve28(packageDir, "package.json");
27096
27390
  if (!existsSync52(pkgJsonPath)) return null;
27097
27391
  let raw;
27098
27392
  try {
@@ -27230,7 +27524,7 @@ function discoverAdapters(opts) {
27230
27524
  const localSet = new Set(opts.configLocalPaths);
27231
27525
  for (const localPath of opts.configLocalPaths) {
27232
27526
  if (seenIds.has(localPath)) continue;
27233
- const absPath = isAbsolute4(localPath) ? localPath : resolve26(opts.projectRoot, localPath);
27527
+ const absPath = isAbsolute5(localPath) ? localPath : resolve28(opts.projectRoot, localPath);
27234
27528
  if (!existsSync52(absPath)) {
27235
27529
  warnings.push(
27236
27530
  `local adapter file not found: ${localPath} (resolved to ${absPath}). Remove via: massu adapters remove-local ${localPath}`
@@ -27314,7 +27608,7 @@ __export(adapters_exports, {
27314
27608
  runAdaptersSearch: () => runAdaptersSearch
27315
27609
  });
27316
27610
  import { existsSync as existsSync53, readFileSync as readFileSync40 } from "node:fs";
27317
- import { resolve as resolve27 } from "node:path";
27611
+ import { resolve as resolve29 } from "node:path";
27318
27612
  import { parseDocument } from "yaml";
27319
27613
  async function handleAdaptersSubcommand(args2) {
27320
27614
  const sub = args2[0];
@@ -27581,7 +27875,7 @@ function mutateLocalArray(mutator, command) {
27581
27875
  );
27582
27876
  return { exitCode: 2 };
27583
27877
  }
27584
- const yamlPath = resolve27(projectRoot, "massu.config.yaml");
27878
+ const yamlPath = resolve29(projectRoot, "massu.config.yaml");
27585
27879
  if (!existsSync53(yamlPath)) {
27586
27880
  process.stderr.write(
27587
27881
  `${command}: massu.config.yaml not found at ${yamlPath}. Run \`massu init\` first.
@@ -27612,7 +27906,7 @@ function mutateLocalArray(mutator, command) {
27612
27906
  if (next === null) {
27613
27907
  return { exitCode: 0 };
27614
27908
  }
27615
- const lockPath = resolve27(projectRoot, ".massu", "adapters-local-mutate.lock");
27909
+ const lockPath = resolve29(projectRoot, ".massu", "adapters-local-mutate.lock");
27616
27910
  return withFileLockSync(lockPath, () => {
27617
27911
  doc.setIn(["adapters", "local"], next);
27618
27912
  const newYaml = doc.toString();
@@ -27681,7 +27975,7 @@ async function runAdaptersInstall(args2) {
27681
27975
  `);
27682
27976
  return { exitCode: 1 };
27683
27977
  }
27684
- const packageDir = resolve27(projectRoot, "node_modules", ...packageName.split("/"));
27978
+ const packageDir = resolve29(projectRoot, "node_modules", ...packageName.split("/"));
27685
27979
  if (!existsSync53(packageDir)) {
27686
27980
  process.stderr.write(
27687
27981
  `install: ${packageName} is not installed in node_modules. Run \`npm install ${packageName}\` first.
@@ -27689,7 +27983,7 @@ async function runAdaptersInstall(args2) {
27689
27983
  );
27690
27984
  return { exitCode: 1 };
27691
27985
  }
27692
- const pkgJsonPath = resolve27(packageDir, "package.json");
27986
+ const pkgJsonPath = resolve29(packageDir, "package.json");
27693
27987
  if (!existsSync53(pkgJsonPath)) {
27694
27988
  process.stderr.write(`install: ${packageName} has no package.json at ${pkgJsonPath}
27695
27989
  `);
@@ -27816,7 +28110,7 @@ async function runAdaptersResign(_args) {
27816
28110
  removeInstalledManifestEntry(name);
27817
28111
  continue;
27818
28112
  }
27819
- const packageDir = resolve27(projectRoot, "node_modules", ...name.split("/"));
28113
+ const packageDir = resolve29(projectRoot, "node_modules", ...name.split("/"));
27820
28114
  if (!existsSync53(packageDir)) {
27821
28115
  removed++;
27822
28116
  warnings.push(`${name}@${entry.version}: not present in node_modules \u2014 REMOVED from sidecar`);
@@ -27915,7 +28209,7 @@ var init_sql_table_names = __esm({
27915
28209
 
27916
28210
  // src/db.ts
27917
28211
  import { dirname as dirname24, join as join29 } from "path";
27918
- import { existsSync as existsSync54, mkdirSync as mkdirSync23, readdirSync as readdirSync19, statSync as statSync18 } from "fs";
28212
+ import { existsSync as existsSync54, mkdirSync as mkdirSync25, readdirSync as readdirSync19, statSync as statSync18 } from "fs";
27919
28213
  function getCodeGraphDb() {
27920
28214
  const dbPath = getResolvedPaths().codegraphDbPath;
27921
28215
  assertCodegraphUsable(dbPath);
@@ -27927,7 +28221,7 @@ function getDataDb() {
27927
28221
  const dbPath = getResolvedPaths().dataDbPath;
27928
28222
  const dir = dirname24(dbPath);
27929
28223
  if (!existsSync54(dir)) {
27930
- mkdirSync23(dir, { recursive: true });
28224
+ mkdirSync25(dir, { recursive: true });
27931
28225
  }
27932
28226
  const db = openDatabase2(dbPath);
27933
28227
  db.pragma("journal_mode = WAL");
@@ -28181,12 +28475,23 @@ function initDataSchema(db) {
28181
28475
  END;
28182
28476
  `);
28183
28477
  }
28478
+ function codegraphIndexedAtToEpochMs(raw) {
28479
+ const ceiling = Date.now() + CODEGRAPH_TS_SKEW_MS;
28480
+ const asMs = raw;
28481
+ const asSeconds = raw * 1e3;
28482
+ if (asMs >= CODEGRAPH_TS_FLOOR_MS && asMs <= ceiling) return asMs;
28483
+ if (asSeconds >= CODEGRAPH_TS_FLOOR_MS && asSeconds <= ceiling) return asSeconds;
28484
+ const describe = (ms) => Number.isFinite(ms) && Math.abs(ms) <= 864e13 ? new Date(ms).toISOString() : "out of representable range";
28485
+ throw new Error(
28486
+ `CodeGraph files.indexed_at=${raw} is implausible as both milliseconds (${describe(asMs)}) and seconds (${describe(asSeconds)}). Expected a timestamp between ${new Date(CODEGRAPH_TS_FLOOR_MS).toISOString()} and now. The CodeGraph engine's timestamp format may have changed \u2014 do not guess the unit.`
28487
+ );
28488
+ }
28184
28489
  function isDataStale(dataDb, codegraphDb) {
28185
28490
  const lastBuild = dataDb.prepare(`SELECT value FROM ${t("meta")} WHERE key = 'last_build_time'`).get();
28186
28491
  if (!lastBuild) return true;
28187
28492
  const latestIndexed = codegraphDb.prepare("SELECT MAX(indexed_at) as latest FROM files").get();
28188
28493
  if (!latestIndexed?.latest) return true;
28189
- return latestIndexed.latest * 1e3 > new Date(lastBuild.value).getTime();
28494
+ return codegraphIndexedAtToEpochMs(latestIndexed.latest) > new Date(lastBuild.value).getTime();
28190
28495
  }
28191
28496
  function updateBuildTimestamp(dataDb) {
28192
28497
  dataDb.prepare(`INSERT OR REPLACE INTO ${t("meta")} (key, value) VALUES ('last_build_time', ?)`).run((/* @__PURE__ */ new Date()).toISOString());
@@ -28216,6 +28521,7 @@ function isPythonDataStale(dataDb, pythonRoot) {
28216
28521
  function updatePythonBuildTimestamp(dataDb) {
28217
28522
  dataDb.prepare(`INSERT OR REPLACE INTO ${t("py_meta")} (key, value) VALUES ('last_build_time', ?)`).run((/* @__PURE__ */ new Date()).toISOString());
28218
28523
  }
28524
+ var CODEGRAPH_TS_FLOOR_MS, CODEGRAPH_TS_SKEW_MS;
28219
28525
  var init_db2 = __esm({
28220
28526
  "src/db.ts"() {
28221
28527
  "use strict";
@@ -28224,14 +28530,16 @@ var init_db2 = __esm({
28224
28530
  init_config();
28225
28531
  init_sql_table_names();
28226
28532
  init_preflight();
28533
+ CODEGRAPH_TS_FLOOR_MS = 15778368e5;
28534
+ CODEGRAPH_TS_SKEW_MS = 864e5;
28227
28535
  }
28228
28536
  });
28229
28537
 
28230
28538
  // src/security-utils.ts
28231
- import { resolve as resolve28, normalize } from "path";
28539
+ import { resolve as resolve30, normalize } from "path";
28232
28540
  function ensureWithinRoot(filePath, projectRoot) {
28233
- const resolvedRoot = resolve28(projectRoot);
28234
- const resolvedPath = resolve28(resolvedRoot, filePath);
28541
+ const resolvedRoot = resolve30(projectRoot);
28542
+ const resolvedPath = resolve30(resolvedRoot, filePath);
28235
28543
  const normalizedPath = normalize(resolvedPath);
28236
28544
  const normalizedRoot = normalize(resolvedRoot);
28237
28545
  if (!normalizedPath.startsWith(normalizedRoot + "/") && normalizedPath !== normalizedRoot) {
@@ -28303,9 +28611,125 @@ var init_rules = __esm({
28303
28611
  }
28304
28612
  });
28305
28613
 
28614
+ // src/lib/source-layout.ts
28615
+ function normalizeDir(raw) {
28616
+ if (typeof raw !== "string") return null;
28617
+ const posix = raw.replace(/\\/g, "/");
28618
+ if (posix.startsWith("/") || /^[A-Za-z]:\//.test(posix)) return null;
28619
+ const parts = posix.split("/").filter((p19) => p19 !== "" && p19 !== ".");
28620
+ if (parts.some((p19) => p19 === "..")) return null;
28621
+ if (parts.length === 0) return ".";
28622
+ return parts.join("/");
28623
+ }
28624
+ function declaredLanguageSourceDirs() {
28625
+ const languages = getConfig().framework.languages;
28626
+ if (!languages) return [];
28627
+ const out = [];
28628
+ for (const entry of Object.values(languages)) {
28629
+ if (Array.isArray(entry.source_dirs)) out.push(...entry.source_dirs);
28630
+ }
28631
+ return out;
28632
+ }
28633
+ function getSourceLayout() {
28634
+ const config = getConfig();
28635
+ const paths = config.paths;
28636
+ const declared = [
28637
+ paths.source,
28638
+ ...paths.monorepo_roots ?? [],
28639
+ ...declaredLanguageSourceDirs()
28640
+ ];
28641
+ const normalized = declared.map(normalizeDir).filter((d2) => d2 !== null);
28642
+ const includesRoot = normalized.includes(".");
28643
+ const sourceDirs = includesRoot ? [] : [...new Set(normalized)].sort();
28644
+ const source = normalizeDir(paths.source) ?? ".";
28645
+ const under = (child) => source === "." ? child : `${source}/${child}`;
28646
+ return {
28647
+ sourceDirs,
28648
+ includesRoot,
28649
+ extensions: getResolvedPaths().extensions,
28650
+ pagesDir: normalizeDir(paths.pages ?? under("app")) ?? under("app"),
28651
+ componentsDir: normalizeDir(paths.components ?? under("components")) ?? under("components")
28652
+ };
28653
+ }
28654
+ function escapeLike(literal) {
28655
+ return literal.replace(/[\\%_]/g, (m3) => `\\${m3}`);
28656
+ }
28657
+ function assertIdentifier(column) {
28658
+ if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(column)) {
28659
+ throw new Error(`source-layout: refusing to interpolate '${column}' as a SQL column name`);
28660
+ }
28661
+ return column;
28662
+ }
28663
+ function likeAny(column, patterns) {
28664
+ if (patterns.length === 0) return { sql: "1=0", params: [] };
28665
+ const col = assertIdentifier(column);
28666
+ const sql = patterns.map(() => `${col} LIKE ? ESCAPE '\\'`).join(" OR ");
28667
+ return { sql: `(${sql})`, params: patterns };
28668
+ }
28669
+ function sourceDirPredicate(column = "path", layout = getSourceLayout()) {
28670
+ if (layout.includesRoot) return { sql: `(${assertIdentifier(column)} IS NOT NULL)`, params: [] };
28671
+ return likeAny(column, layout.sourceDirs.map((d2) => `${escapeLike(d2)}/%`));
28672
+ }
28673
+ function sourceFilePredicate(column = "path", layout = getSourceLayout()) {
28674
+ const dirs = sourceDirPredicate(column, layout);
28675
+ const exts = likeAny(column, layout.extensions.map((e2) => `%${escapeLike(e2)}`));
28676
+ return {
28677
+ sql: `${dirs.sql} AND ${exts.sql}`,
28678
+ params: [...dirs.params, ...exts.params]
28679
+ };
28680
+ }
28681
+ function pagesPredicate(layout = getSourceLayout()) {
28682
+ const dir = escapeLike(layout.pagesDir);
28683
+ return {
28684
+ sql: `(path LIKE ? ESCAPE '\\' OR path = ?)`,
28685
+ params: [`${dir}/%/page.tsx`, `${layout.pagesDir}/page.tsx`]
28686
+ };
28687
+ }
28688
+ function componentsPredicate(layout = getSourceLayout()) {
28689
+ return {
28690
+ sql: `path LIKE ? ESCAPE '\\'`,
28691
+ params: [`${escapeLike(layout.componentsDir)}/%`]
28692
+ };
28693
+ }
28694
+ function isUnderSourceDir(path, layout = getSourceLayout()) {
28695
+ if (normalizeDir(path) === null) return false;
28696
+ if (layout.includesRoot) return true;
28697
+ return layout.sourceDirs.some((d2) => path.startsWith(`${d2}/`));
28698
+ }
28699
+ function assertSourceCandidateSet(codegraphDb, builder, layout = getSourceLayout()) {
28700
+ const present = codegraphDb.prepare("SELECT COUNT(*) AS n FROM files").get().n;
28701
+ if (present === 0) return;
28702
+ const pred = sourceDirPredicate("path", layout);
28703
+ const matched = codegraphDb.prepare(`SELECT COUNT(*) AS n FROM files WHERE ${pred.sql}`).get(...pred.params).n;
28704
+ if (matched > 0) return;
28705
+ const prefixes = codegraphDb.prepare(
28706
+ `SELECT CASE WHEN instr(path, '/') > 0 THEN substr(path, 1, instr(path, '/') - 1) ELSE '<root>' END AS prefix,
28707
+ COUNT(*) AS n
28708
+ FROM files GROUP BY prefix ORDER BY n DESC LIMIT 20`
28709
+ ).all();
28710
+ const declared = layout.includesRoot ? "<project root>" : layout.sourceDirs.join(", ") || "<none>";
28711
+ const found = prefixes.map((p19) => `${p19.prefix} (${p19.n})`).join(", ") || "<none>";
28712
+ throw new CandidateSetContractError(
28713
+ `${builder}: declared source dirs match 0 of ${present} indexed files. Declared: ${declared}. Present in CodeGraph: ${found}. Declare the real layout in massu.config.yaml (paths.source, paths.monorepo_roots, or framework.languages.<lang>.source_dirs) \u2014 an index built from an empty candidate set is indistinguishable from a project with no imports.`
28714
+ );
28715
+ }
28716
+ var CandidateSetContractError;
28717
+ var init_source_layout = __esm({
28718
+ "src/lib/source-layout.ts"() {
28719
+ "use strict";
28720
+ init_config();
28721
+ CandidateSetContractError = class extends Error {
28722
+ constructor(message) {
28723
+ super(message);
28724
+ this.name = "CandidateSetContractError";
28725
+ }
28726
+ };
28727
+ }
28728
+ });
28729
+
28306
28730
  // src/import-resolver.ts
28307
28731
  import { readFileSync as readFileSync41, existsSync as existsSync55, statSync as statSync19 } from "fs";
28308
- import { resolve as resolve29, dirname as dirname25, join as join30 } from "path";
28732
+ import { resolve as resolve31, dirname as dirname25, join as join30 } from "path";
28309
28733
  function parseImports(source) {
28310
28734
  const imports = [];
28311
28735
  const lines = source.split("\n");
@@ -28361,24 +28785,24 @@ function resolveImportPath(specifier, fromFile) {
28361
28785
  let basePath;
28362
28786
  if (specifier.startsWith("@/")) {
28363
28787
  const paths = getResolvedPaths();
28364
- basePath = resolve29(paths.pathAlias["@"] ?? paths.srcDir, specifier.slice(2));
28788
+ basePath = resolve31(paths.pathAlias["@"] ?? paths.srcDir, specifier.slice(2));
28365
28789
  } else {
28366
- basePath = resolve29(dirname25(fromFile), specifier);
28790
+ basePath = resolve31(dirname25(fromFile), specifier);
28367
28791
  }
28368
28792
  if (existsSync55(basePath) && !isDirectory(basePath)) {
28369
- return toRelative(basePath);
28793
+ return toRelative2(basePath);
28370
28794
  }
28371
28795
  const resolvedPaths = getResolvedPaths();
28372
28796
  for (const ext of resolvedPaths.extensions) {
28373
28797
  const withExt = basePath + ext;
28374
28798
  if (existsSync55(withExt)) {
28375
- return toRelative(withExt);
28799
+ return toRelative2(withExt);
28376
28800
  }
28377
28801
  }
28378
28802
  for (const indexFile of resolvedPaths.indexFiles) {
28379
28803
  const indexPath = join30(basePath, indexFile);
28380
28804
  if (existsSync55(indexPath)) {
28381
- return toRelative(indexPath);
28805
+ return toRelative2(indexPath);
28382
28806
  }
28383
28807
  }
28384
28808
  return null;
@@ -28390,7 +28814,7 @@ function isDirectory(path) {
28390
28814
  return false;
28391
28815
  }
28392
28816
  }
28393
- function toRelative(absPath) {
28817
+ function toRelative2(absPath) {
28394
28818
  const root = getProjectRoot();
28395
28819
  if (absPath.startsWith(root)) {
28396
28820
  return absPath.slice(root.length + 1);
@@ -28398,7 +28822,15 @@ function toRelative(absPath) {
28398
28822
  return absPath;
28399
28823
  }
28400
28824
  function buildImportIndex(dataDb, codegraphDb) {
28401
- const files = codegraphDb.prepare("SELECT path FROM files WHERE path LIKE 'src/%'").all();
28825
+ assertSourceCandidateSet(codegraphDb, "buildImportIndex");
28826
+ const filesPredicate = sourceFilePredicate();
28827
+ const FILE_SCAN_LIMIT = 2e5;
28828
+ const files = codegraphDb.prepare(`SELECT path FROM files WHERE ${filesPredicate.sql} LIMIT ${FILE_SCAN_LIMIT}`).all(...filesPredicate.params);
28829
+ if (files.length === FILE_SCAN_LIMIT) {
28830
+ throw new Error(
28831
+ `buildImportIndex: candidate set hit the ${FILE_SCAN_LIMIT}-row scan limit, so the import index would be built from a truncated file list. Narrow the declared source dirs in massu.config.yaml, or raise FILE_SCAN_LIMIT deliberately.`
28832
+ );
28833
+ }
28402
28834
  dataDb.exec(`DELETE FROM ${t("imports")}`);
28403
28835
  const insertStmt = dataDb.prepare(
28404
28836
  `INSERT INTO ${t("imports")} (source_file, target_file, import_type, imported_names, line) VALUES (?, ?, ?, ?, ?)`
@@ -28413,7 +28845,7 @@ function buildImportIndex(dataDb, codegraphDb) {
28413
28845
  const batchSize = 500;
28414
28846
  let batch = [];
28415
28847
  for (const file of files) {
28416
- const absPath = ensureWithinRoot(resolve29(projectRoot, file.path), projectRoot);
28848
+ const absPath = ensureWithinRoot(resolve31(projectRoot, file.path), projectRoot);
28417
28849
  if (!existsSync55(absPath)) continue;
28418
28850
  let source;
28419
28851
  try {
@@ -28450,12 +28882,13 @@ var init_import_resolver = __esm({
28450
28882
  init_config();
28451
28883
  init_security_utils();
28452
28884
  init_sql_table_names();
28885
+ init_source_layout();
28453
28886
  }
28454
28887
  });
28455
28888
 
28456
28889
  // src/trpc-index.ts
28457
28890
  import { readFileSync as readFileSync42, existsSync as existsSync56, readdirSync as readdirSync20 } from "fs";
28458
- import { resolve as resolve30, join as join31 } from "path";
28891
+ import { resolve as resolve32, join as join31 } from "path";
28459
28892
  function parseRootRouter() {
28460
28893
  const paths = getResolvedPaths();
28461
28894
  const rootPath = paths.rootRouterPath;
@@ -28470,7 +28903,7 @@ function parseRootRouter() {
28470
28903
  while ((match = importRegex.exec(source)) !== null) {
28471
28904
  const variable = match[1];
28472
28905
  let filePath = match[2];
28473
- const fullPath = resolve30(paths.routersDir, filePath);
28906
+ const fullPath = resolve32(paths.routersDir, filePath);
28474
28907
  for (const ext of [".ts", ".tsx", ""]) {
28475
28908
  const candidate = fullPath + ext;
28476
28909
  const routersRelPath = getConfig().paths.routers ?? "src/server/api/routers";
@@ -28498,7 +28931,7 @@ function parseRootRouter() {
28498
28931
  return mappings;
28499
28932
  }
28500
28933
  function extractProcedures(routerFilePath) {
28501
- const absPath = resolve30(getProjectRoot(), routerFilePath);
28934
+ const absPath = resolve32(getProjectRoot(), routerFilePath);
28502
28935
  if (!existsSync56(absPath)) return [];
28503
28936
  const source = readFileSync42(absPath, "utf-8");
28504
28937
  const procedures = [];
@@ -28523,9 +28956,9 @@ function findUICallSites(routerKey, procedureName) {
28523
28956
  const root = getProjectRoot();
28524
28957
  const src = config.paths.source;
28525
28958
  const searchDirs = [
28526
- resolve30(root, config.paths.pages ?? src + "/app"),
28527
- resolve30(root, config.paths.components ?? src + "/components"),
28528
- resolve30(root, config.paths.hooks ?? src + "/hooks")
28959
+ resolve32(root, config.paths.pages ?? src + "/app"),
28960
+ resolve32(root, config.paths.components ?? src + "/components"),
28961
+ resolve32(root, config.paths.hooks ?? src + "/hooks")
28529
28962
  ];
28530
28963
  const searchPattern = `api.${routerKey}.${procedureName}`;
28531
28964
  for (const dir of searchDirs) {
@@ -28608,9 +29041,11 @@ var init_trpc_index = __esm({
28608
29041
 
28609
29042
  // src/page-deps.ts
28610
29043
  import { readFileSync as readFileSync43, existsSync as existsSync57 } from "fs";
28611
- import { resolve as resolve31 } from "path";
29044
+ import { resolve as resolve33 } from "path";
28612
29045
  function deriveRoute(pageFile) {
28613
- let route = pageFile.replace(/^src\/app/, "").replace(/\/page\.tsx?$/, "").replace(/\/page\.jsx?$/, "");
29046
+ const pagesDir = getSourceLayout().pagesDir;
29047
+ const prefix3 = pagesDir.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
29048
+ let route = pageFile.replace(new RegExp(`^${prefix3}`), "").replace(/\/page\.tsx?$/, "").replace(/\/page\.jsx?$/, "");
28614
29049
  return route || "/";
28615
29050
  }
28616
29051
  function derivePortal(route) {
@@ -28637,7 +29072,7 @@ function traceImports(startFile, dataDb, visited, components, hooks, maxDepth =
28637
29072
  if (target.includes("/hooks/") || target.match(/use[A-Z]/)) {
28638
29073
  hooks.add(target);
28639
29074
  }
28640
- if (target.startsWith("src/")) {
29075
+ if (isUnderSourceDir(target)) {
28641
29076
  traceImports(target, dataDb, visited, components, hooks, maxDepth - 1);
28642
29077
  }
28643
29078
  }
@@ -28646,7 +29081,7 @@ function findRouterCalls(files) {
28646
29081
  const routers = /* @__PURE__ */ new Set();
28647
29082
  const projectRoot = getProjectRoot();
28648
29083
  for (const file of files) {
28649
- const absPath = ensureWithinRoot(resolve31(projectRoot, file), projectRoot);
29084
+ const absPath = ensureWithinRoot(resolve33(projectRoot, file), projectRoot);
28650
29085
  if (!existsSync57(absPath)) continue;
28651
29086
  try {
28652
29087
  const source = readFileSync43(absPath, "utf-8");
@@ -28667,7 +29102,7 @@ function findTablesFromRouters(routerNames, dataDb) {
28667
29102
  `SELECT DISTINCT router_file FROM ${t("trpc_procedures")} WHERE router_name = ? LIMIT 10000`
28668
29103
  ).all(routerName);
28669
29104
  for (const proc of procs) {
28670
- const absPath = ensureWithinRoot(resolve31(getProjectRoot(), proc.router_file), getProjectRoot());
29105
+ const absPath = ensureWithinRoot(resolve33(getProjectRoot(), proc.router_file), getProjectRoot());
28671
29106
  if (!existsSync57(absPath)) continue;
28672
29107
  try {
28673
29108
  const source = readFileSync43(absPath, "utf-8");
@@ -28685,10 +29120,12 @@ function findTablesFromRouters(routerNames, dataDb) {
28685
29120
  return [...tables];
28686
29121
  }
28687
29122
  function buildPageDeps(dataDb, codegraphDb) {
29123
+ assertSourceCandidateSet(codegraphDb, "buildPageDeps");
28688
29124
  dataDb.exec(`DELETE FROM ${t("page_deps")}`);
29125
+ const pagesPred = pagesPredicate();
28689
29126
  const pages = codegraphDb.prepare(
28690
- "SELECT path FROM files WHERE path LIKE 'src/app/%/page.tsx' OR path = 'src/app/page.tsx' LIMIT 10000"
28691
- ).all();
29127
+ `SELECT path FROM files WHERE ${pagesPred.sql} LIMIT 10000`
29128
+ ).all(...pagesPred.params);
28692
29129
  const insertStmt = dataDb.prepare(
28693
29130
  `INSERT INTO ${t("page_deps")} (page_file, route, portal, components, hooks, routers, tables_touched) VALUES (?, ?, ?, ?, ?, ?, ?)`
28694
29131
  );
@@ -28783,6 +29220,7 @@ var init_page_deps = __esm({
28783
29220
  init_config();
28784
29221
  init_security_utils();
28785
29222
  init_sql_table_names();
29223
+ init_source_layout();
28786
29224
  }
28787
29225
  });
28788
29226
 
@@ -28801,7 +29239,7 @@ function buildMiddlewareTree(dataDb) {
28801
29239
  `SELECT target_file FROM ${t("imports")} WHERE source_file = ? LIMIT 10000`
28802
29240
  ).all(current);
28803
29241
  for (const imp of imports) {
28804
- if (!visited.has(imp.target_file) && imp.target_file.startsWith("src/")) {
29242
+ if (!visited.has(imp.target_file) && isUnderSourceDir(imp.target_file)) {
28805
29243
  visited.add(imp.target_file);
28806
29244
  queue.push(imp.target_file);
28807
29245
  }
@@ -28825,6 +29263,7 @@ var init_middleware_tree = __esm({
28825
29263
  "use strict";
28826
29264
  init_config();
28827
29265
  init_sql_table_names();
29266
+ init_source_layout();
28828
29267
  }
28829
29268
  });
28830
29269
 
@@ -28882,12 +29321,11 @@ function globMatchSimple(name, pattern) {
28882
29321
  }
28883
29322
  function findCrossDomainImports(dataDb) {
28884
29323
  const domains = getDomains();
28885
- const config = getConfig();
28886
- const srcPrefix = config.paths.source;
28887
- const srcPattern = srcPrefix + "/%";
29324
+ const srcSource = sourceDirPredicate("source_file");
29325
+ const srcTarget = sourceDirPredicate("target_file");
28888
29326
  const imports = dataDb.prepare(
28889
- `SELECT source_file, target_file FROM ${t("imports")} WHERE source_file LIKE ? AND target_file LIKE ? LIMIT 10000`
28890
- ).all(srcPattern, srcPattern);
29327
+ `SELECT source_file, target_file FROM ${t("imports")} WHERE ${srcSource.sql} AND ${srcTarget.sql} LIMIT 10000`
29328
+ ).all(...srcSource.params, ...srcTarget.params);
28891
29329
  const crossings = [];
28892
29330
  for (const imp of imports) {
28893
29331
  const sourceDomain = classifyFile(imp.source_file);
@@ -28912,10 +29350,9 @@ function getFilesInDomain(dataDb, codegraphDb, domainName) {
28912
29350
  const config = getConfig();
28913
29351
  const domain = domains.find((d2) => d2.name === domainName);
28914
29352
  if (!domain) return { routers: [], pages: [], components: [] };
28915
- const srcPrefix = config.paths.source;
28916
29353
  const routersPath = config.paths.routers ?? "src/server/api/routers";
28917
- const srcPattern = srcPrefix + "/%";
28918
- const allFiles = codegraphDb.prepare("SELECT path FROM files WHERE path LIKE ?").all(srcPattern);
29354
+ const srcPred = sourceDirPredicate();
29355
+ const allFiles = codegraphDb.prepare(`SELECT path FROM files WHERE ${srcPred.sql} LIMIT 200000`).all(...srcPred.params);
28919
29356
  const routers = [];
28920
29357
  const pages = [];
28921
29358
  const components = [];
@@ -28938,6 +29375,7 @@ var init_domains = __esm({
28938
29375
  init_rules();
28939
29376
  init_config();
28940
29377
  init_sql_table_names();
29378
+ init_source_layout();
28941
29379
  }
28942
29380
  });
28943
29381
 
@@ -29223,11 +29661,11 @@ var init_import_parser = __esm({
29223
29661
 
29224
29662
  // src/python/import-resolver.ts
29225
29663
  import { readFileSync as readFileSync45, existsSync as existsSync59, readdirSync as readdirSync22 } from "fs";
29226
- import { resolve as resolve33, join as join33, relative as relative8, dirname as dirname26 } from "path";
29664
+ import { resolve as resolve35, join as join33, relative as relative9, dirname as dirname26 } from "path";
29227
29665
  function resolvePythonModulePath(module, fromFile, pythonRoot, level) {
29228
29666
  const projectRoot = getProjectRoot();
29229
29667
  if (level > 0) {
29230
- let baseDir = dirname26(resolve33(projectRoot, fromFile));
29668
+ let baseDir = dirname26(resolve35(projectRoot, fromFile));
29231
29669
  for (let i = 1; i < level; i++) {
29232
29670
  baseDir = dirname26(baseDir);
29233
29671
  }
@@ -29239,18 +29677,18 @@ function resolvePythonModulePath(module, fromFile, pythonRoot, level) {
29239
29677
  return tryResolvePythonPath(baseDir, projectRoot);
29240
29678
  }
29241
29679
  const parts = module.split(".");
29242
- const candidate = join33(resolve33(projectRoot, pythonRoot), ...parts);
29680
+ const candidate = join33(resolve35(projectRoot, pythonRoot), ...parts);
29243
29681
  return tryResolvePythonPath(candidate, projectRoot);
29244
29682
  }
29245
29683
  function tryResolvePythonPath(basePath, projectRoot) {
29246
29684
  if (existsSync59(basePath + ".py")) {
29247
- return relative8(projectRoot, basePath + ".py");
29685
+ return relative9(projectRoot, basePath + ".py");
29248
29686
  }
29249
29687
  if (existsSync59(join33(basePath, "__init__.py"))) {
29250
- return relative8(projectRoot, join33(basePath, "__init__.py"));
29688
+ return relative9(projectRoot, join33(basePath, "__init__.py"));
29251
29689
  }
29252
29690
  if (basePath.endsWith(".py") && existsSync59(basePath)) {
29253
- return relative8(projectRoot, basePath);
29691
+ return relative9(projectRoot, basePath);
29254
29692
  }
29255
29693
  return null;
29256
29694
  }
@@ -29272,7 +29710,7 @@ function walkPythonFiles(dir, excludeDirs) {
29272
29710
  }
29273
29711
  function buildPythonImportIndex(dataDb, pythonRoot, excludeDirs = ["__pycache__", ".venv", "venv", ".mypy_cache", ".pytest_cache"]) {
29274
29712
  const projectRoot = getProjectRoot();
29275
- const absRoot = resolve33(projectRoot, pythonRoot);
29713
+ const absRoot = resolve35(projectRoot, pythonRoot);
29276
29714
  dataDb.exec(`DELETE FROM ${t("py_imports")}`);
29277
29715
  const insertStmt = dataDb.prepare(
29278
29716
  `INSERT INTO ${t("py_imports")} (source_file, target_file, import_type, imported_names, line) VALUES (?, ?, ?, ?, ?)`
@@ -29286,7 +29724,7 @@ function buildPythonImportIndex(dataDb, pythonRoot, excludeDirs = ["__pycache__"
29286
29724
  });
29287
29725
  const batch = [];
29288
29726
  for (const absFile of files) {
29289
- const relFile = relative8(projectRoot, absFile);
29727
+ const relFile = relative9(projectRoot, absFile);
29290
29728
  let source;
29291
29729
  try {
29292
29730
  source = readFileSync45(absFile, "utf-8");
@@ -29557,7 +29995,7 @@ var init_route_parser = __esm({
29557
29995
 
29558
29996
  // src/python/route-indexer.ts
29559
29997
  import { readFileSync as readFileSync46, readdirSync as readdirSync23 } from "fs";
29560
- import { join as join34, relative as relative9 } from "path";
29998
+ import { join as join34, relative as relative10 } from "path";
29561
29999
  function walkPyFiles(dir, excludeDirs) {
29562
30000
  const files = [];
29563
30001
  try {
@@ -29586,7 +30024,7 @@ function buildPythonRouteIndex(dataDb, pythonRoot, excludeDirs = ["__pycache__",
29586
30024
  let count = 0;
29587
30025
  dataDb.transaction(() => {
29588
30026
  for (const absFile of files) {
29589
- const relFile = relative9(projectRoot, absFile);
30027
+ const relFile = relative10(projectRoot, absFile);
29590
30028
  let source;
29591
30029
  try {
29592
30030
  source = readFileSync46(absFile, "utf-8");
@@ -29802,7 +30240,7 @@ var init_model_parser = __esm({
29802
30240
 
29803
30241
  // src/python/model-indexer.ts
29804
30242
  import { readFileSync as readFileSync47, readdirSync as readdirSync24 } from "fs";
29805
- import { join as join35, relative as relative10 } from "path";
30243
+ import { join as join35, relative as relative11 } from "path";
29806
30244
  function walkPyFiles2(dir, excludeDirs) {
29807
30245
  const files = [];
29808
30246
  try {
@@ -29834,7 +30272,7 @@ function buildPythonModelIndex(dataDb, pythonRoot, excludeDirs = ["__pycache__",
29834
30272
  let count = 0;
29835
30273
  dataDb.transaction(() => {
29836
30274
  for (const absFile of files) {
29837
- const relFile = relative10(projectRoot, absFile);
30275
+ const relFile = relative11(projectRoot, absFile);
29838
30276
  let source;
29839
30277
  try {
29840
30278
  source = readFileSync47(absFile, "utf-8");
@@ -30099,7 +30537,7 @@ var init_migration_parser = __esm({
30099
30537
 
30100
30538
  // src/python/migration-indexer.ts
30101
30539
  import { readFileSync as readFileSync48, readdirSync as readdirSync25 } from "fs";
30102
- import { join as join36, relative as relative11 } from "path";
30540
+ import { join as join36, relative as relative12 } from "path";
30103
30541
  function buildPythonMigrationIndex(dataDb, alembicDir) {
30104
30542
  const projectRoot = getProjectRoot();
30105
30543
  const absDir = join36(projectRoot, alembicDir);
@@ -30135,7 +30573,7 @@ function buildPythonMigrationIndex(dataDb, alembicDir) {
30135
30573
  rows.push({
30136
30574
  revision: parsed.revision,
30137
30575
  downRevision: parsed.downRevision,
30138
- file: relative11(projectRoot, absFile),
30576
+ file: relative12(projectRoot, absFile),
30139
30577
  description: parsed.description,
30140
30578
  operations: JSON.stringify(parsed.operations)
30141
30579
  });
@@ -30160,7 +30598,7 @@ var init_migration_indexer = __esm({
30160
30598
 
30161
30599
  // src/python/coupling-detector.ts
30162
30600
  import { readFileSync as readFileSync49, readdirSync as readdirSync26 } from "fs";
30163
- import { join as join37, relative as relative12 } from "path";
30601
+ import { join as join37, relative as relative13 } from "path";
30164
30602
  function buildPythonCouplingIndex(dataDb) {
30165
30603
  const projectRoot = getProjectRoot();
30166
30604
  const config = getConfig();
@@ -30193,7 +30631,7 @@ function buildPythonCouplingIndex(dataDb) {
30193
30631
  ];
30194
30632
  dataDb.transaction(() => {
30195
30633
  for (const absFile of frontendFiles) {
30196
- const relFile = relative12(projectRoot, absFile);
30634
+ const relFile = relative13(projectRoot, absFile);
30197
30635
  let source;
30198
30636
  try {
30199
30637
  source = readFileSync49(absFile, "utf-8");
@@ -30603,7 +31041,7 @@ var init_memory_tools = __esm({
30603
31041
 
30604
31042
  // src/docs-tools.ts
30605
31043
  import { readFileSync as readFileSync50, existsSync as existsSync60 } from "fs";
30606
- import { resolve as resolve34, basename as basename9 } from "path";
31044
+ import { resolve as resolve36, basename as basename9 } from "path";
30607
31045
  function p4(baseName) {
30608
31046
  return `${getConfig().toolPrefix}_${baseName}`;
30609
31047
  }
@@ -30731,9 +31169,9 @@ function extractFrontmatter(content) {
30731
31169
  }
30732
31170
  function extractProcedureNames(routerPath) {
30733
31171
  const root = getProjectRoot();
30734
- const absPath = ensureWithinRoot(resolve34(getResolvedPaths().srcDir, "..", routerPath), root);
31172
+ const absPath = ensureWithinRoot(resolve36(getResolvedPaths().srcDir, "..", routerPath), root);
30735
31173
  if (!existsSync60(absPath)) {
30736
- const altPath = ensureWithinRoot(resolve34(getResolvedPaths().srcDir, "../server/api/routers", basename9(routerPath)), root);
31174
+ const altPath = ensureWithinRoot(resolve36(getResolvedPaths().srcDir, "../server/api/routers", basename9(routerPath)), root);
30737
31175
  if (!existsSync60(altPath)) return [];
30738
31176
  return extractProcedureNamesFromContent(readFileSync50(altPath, "utf-8"));
30739
31177
  }
@@ -30777,7 +31215,7 @@ function handleDocsAudit(args2) {
30777
31215
  for (const [mappingId, triggeringFiles] of affectedMappings) {
30778
31216
  const mapping = docsMap.mappings.find((m3) => m3.id === mappingId);
30779
31217
  if (!mapping) continue;
30780
- const helpPagePath = ensureWithinRoot(resolve34(getResolvedPaths().helpSitePath, mapping.helpPage), getProjectRoot());
31218
+ const helpPagePath = ensureWithinRoot(resolve36(getResolvedPaths().helpSitePath, mapping.helpPage), getProjectRoot());
30781
31219
  if (!existsSync60(helpPagePath)) {
30782
31220
  results.push({
30783
31221
  helpPage: mapping.helpPage,
@@ -30830,7 +31268,7 @@ function handleDocsAudit(args2) {
30830
31268
  });
30831
31269
  for (const [guideName, parentId] of Object.entries(docsMap.userGuideInheritance.examples)) {
30832
31270
  if (parentId === mappingId) {
30833
- const guidePath = ensureWithinRoot(resolve34(getResolvedPaths().helpSitePath, `pages/user-guides/${guideName}/index.mdx`), getProjectRoot());
31271
+ const guidePath = ensureWithinRoot(resolve36(getResolvedPaths().helpSitePath, `pages/user-guides/${guideName}/index.mdx`), getProjectRoot());
30834
31272
  if (existsSync60(guidePath)) {
30835
31273
  const guideContent = readFileSync50(guidePath, "utf-8");
30836
31274
  const guideFrontmatter = extractFrontmatter(guideContent);
@@ -30865,7 +31303,7 @@ function handleDocsCoverage(args2) {
30865
31303
  const gaps = [];
30866
31304
  const mappings = filterDomain ? docsMap.mappings.filter((m3) => m3.id === filterDomain) : docsMap.mappings;
30867
31305
  for (const mapping of mappings) {
30868
- const helpPagePath = ensureWithinRoot(resolve34(getResolvedPaths().helpSitePath, mapping.helpPage), getProjectRoot());
31306
+ const helpPagePath = ensureWithinRoot(resolve36(getResolvedPaths().helpSitePath, mapping.helpPage), getProjectRoot());
30869
31307
  const exists = existsSync60(helpPagePath);
30870
31308
  let hasContent = false;
30871
31309
  let lineCount = 0;
@@ -31214,7 +31652,7 @@ var init_observability_tools = __esm({
31214
31652
 
31215
31653
  // src/sentinel-db.ts
31216
31654
  import { existsSync as existsSync61 } from "fs";
31217
- import { resolve as resolve35 } from "path";
31655
+ import { resolve as resolve37 } from "path";
31218
31656
  function parsePortalScope(raw) {
31219
31657
  if (!raw) return [];
31220
31658
  try {
@@ -31450,22 +31888,22 @@ function validateFeatures(db, domainFilter) {
31450
31888
  const missingProcedures = [];
31451
31889
  const missingPages = [];
31452
31890
  for (const comp of components) {
31453
- const absPath = resolve35(PROJECT_ROOT, comp.component_file);
31891
+ const absPath = resolve37(PROJECT_ROOT, comp.component_file);
31454
31892
  if (!existsSync61(absPath)) {
31455
31893
  missingComponents.push(comp.component_file);
31456
31894
  }
31457
31895
  }
31458
31896
  for (const proc of procedures) {
31459
- const routerPath = resolve35(PROJECT_ROOT, `src/server/api/routers/${proc.router_name}.ts`);
31897
+ const routerPath = resolve37(PROJECT_ROOT, `src/server/api/routers/${proc.router_name}.ts`);
31460
31898
  if (!existsSync61(routerPath)) {
31461
31899
  missingProcedures.push({ router: proc.router_name, procedure: proc.procedure_name });
31462
31900
  }
31463
31901
  }
31464
31902
  for (const page of pages) {
31465
31903
  const routeToPath = page.page_route.replace(/^\/(portal-[^/]+\/)?/, "src/app/").replace(/\/$/, "") + "/page.tsx";
31466
- const absPath = resolve35(PROJECT_ROOT, routeToPath);
31904
+ const absPath = resolve37(PROJECT_ROOT, routeToPath);
31467
31905
  if (page.page_route.startsWith("/") && !existsSync61(absPath)) {
31468
- const altPath = resolve35(PROJECT_ROOT, `src/app${page.page_route}/page.tsx`);
31906
+ const altPath = resolve37(PROJECT_ROOT, `src/app${page.page_route}/page.tsx`);
31469
31907
  if (!existsSync61(altPath)) {
31470
31908
  missingPages.push(page.page_route);
31471
31909
  }
@@ -31993,7 +32431,7 @@ var init_sentinel_tools = __esm({
31993
32431
 
31994
32432
  // src/sentinel-scanner.ts
31995
32433
  import { readFileSync as readFileSync51, existsSync as existsSync62, readdirSync as readdirSync27, statSync as statSync20 } from "fs";
31996
- import { resolve as resolve36, join as join38, basename as basename10, dirname as dirname27, relative as relative13 } from "path";
32434
+ import { resolve as resolve38, join as join38, basename as basename10, dirname as dirname27, relative as relative14 } from "path";
31997
32435
  function inferDomain(filePath) {
31998
32436
  const domains = getConfig().domains;
31999
32437
  const path = filePath.toLowerCase();
@@ -32124,7 +32562,7 @@ function scanComponentExports(dataDb) {
32124
32562
  const projectRoot = getProjectRoot();
32125
32563
  const componentsBase = config.paths.components ?? config.paths.source + "/components";
32126
32564
  const componentDirs = [];
32127
- const basePath = resolve36(projectRoot, componentsBase);
32565
+ const basePath = resolve38(projectRoot, componentsBase);
32128
32566
  if (existsSync62(basePath)) {
32129
32567
  try {
32130
32568
  const entries = readdirSync27(basePath, { withFileTypes: true });
@@ -32137,11 +32575,11 @@ function scanComponentExports(dataDb) {
32137
32575
  }
32138
32576
  }
32139
32577
  for (const dir of componentDirs) {
32140
- const absDir = resolve36(projectRoot, dir);
32578
+ const absDir = resolve38(projectRoot, dir);
32141
32579
  if (!existsSync62(absDir)) continue;
32142
32580
  const files = walkDir2(absDir).filter((f2) => f2.endsWith(".tsx") || f2.endsWith(".ts"));
32143
32581
  for (const file of files) {
32144
- const relPath = relative13(projectRoot, file);
32582
+ const relPath = relative14(projectRoot, file);
32145
32583
  const source = readFileSync51(file, "utf-8");
32146
32584
  const annotations = parseFeatureAnnotations(source);
32147
32585
  if (annotations.length > 0) {
@@ -33722,7 +34160,7 @@ var init_security_scorer = __esm({
33722
34160
 
33723
34161
  // src/dependency-scorer.ts
33724
34162
  import { existsSync as existsSync65, readFileSync as readFileSync54 } from "fs";
33725
- import { resolve as resolve37 } from "path";
34163
+ import { resolve as resolve39 } from "path";
33726
34164
  function p13(baseName) {
33727
34165
  return `${getConfig().toolPrefix}_${baseName}`;
33728
34166
  }
@@ -33754,7 +34192,7 @@ function calculateDepRisk(factors) {
33754
34192
  return Math.min(100, risk);
33755
34193
  }
33756
34194
  function getInstalledPackages(projectRoot) {
33757
- const pkgPath = resolve37(projectRoot, "package.json");
34195
+ const pkgPath = resolve39(projectRoot, "package.json");
33758
34196
  if (!existsSync65(pkgPath)) return /* @__PURE__ */ new Map();
33759
34197
  try {
33760
34198
  const pkg = JSON.parse(readFileSync54(pkgPath, "utf-8"));
@@ -34364,7 +34802,7 @@ var init_regression_detector = __esm({
34364
34802
  // src/knowledge-indexer.ts
34365
34803
  import { createHash as createHash16 } from "crypto";
34366
34804
  import { readFileSync as readFileSync55, readdirSync as readdirSync28, statSync as statSync21, existsSync as existsSync66 } from "fs";
34367
- import { resolve as resolve38, relative as relative14, basename as basename11, extname as extname2 } from "path";
34805
+ import { resolve as resolve40, relative as relative15, basename as basename11, extname as extname2 } from "path";
34368
34806
  function getKnowledgePaths() {
34369
34807
  const resolved = getResolvedPaths();
34370
34808
  const config = getConfig();
@@ -34392,7 +34830,7 @@ function discoverMarkdownFiles(baseDir) {
34392
34830
  try {
34393
34831
  const entries = readdirSync28(dir, { withFileTypes: true });
34394
34832
  for (const entry of entries) {
34395
- const fullPath = resolve38(dir, entry.name);
34833
+ const fullPath = resolve40(dir, entry.name);
34396
34834
  if (entry.isDirectory()) {
34397
34835
  if (entry.name === "archive" && dir.includes("session-state")) continue;
34398
34836
  if (entry.name === "archive" && dir.includes("status")) continue;
@@ -34412,7 +34850,7 @@ function categorizeFile(filePath) {
34412
34850
  const paths = getKnowledgePaths();
34413
34851
  if (filePath.startsWith(paths.plansDir)) return "plan";
34414
34852
  if (filePath.startsWith(paths.docsDir)) {
34415
- const relFromDocs = relative14(paths.docsDir, filePath).replace(/\\/g, "/").toLowerCase();
34853
+ const relFromDocs = relative15(paths.docsDir, filePath).replace(/\\/g, "/").toLowerCase();
34416
34854
  if (relFromDocs.startsWith("plans/")) return "plan";
34417
34855
  if (relFromDocs.includes("architecture")) return "architecture";
34418
34856
  if (relFromDocs.includes("security")) return "security";
@@ -34428,7 +34866,7 @@ function categorizeFile(filePath) {
34428
34866
  }
34429
34867
  const claudeDirName = getConfig().conventions?.claudeDirName ?? ".claude";
34430
34868
  if (filePath.includes(`${claudeDirName}/projects/`) && filePath.includes("/memory/")) return "memory";
34431
- const rel = relative14(paths.claudeDir, filePath).replace(/\\/g, "/");
34869
+ const rel = relative15(paths.claudeDir, filePath).replace(/\\/g, "/");
34432
34870
  const firstDir = rel.split("/")[0];
34433
34871
  const knownCategories = getConfig().conventions?.knowledgeCategories ?? [
34434
34872
  "patterns",
@@ -34681,7 +35119,7 @@ function indexAllKnowledge(db) {
34681
35119
  files.push(...docsFiles);
34682
35120
  }
34683
35121
  const now = /* @__PURE__ */ new Date();
34684
- const nowIso = now.toISOString();
35122
+ const nowIso2 = now.toISOString();
34685
35123
  const nowEpoch = now.getTime();
34686
35124
  const transaction = db.transaction(() => {
34687
35125
  db.exec("DROP TRIGGER IF EXISTS kc_fts_delete");
@@ -34721,11 +35159,11 @@ function indexAllKnowledge(db) {
34721
35159
  if (!existsSync66(filePath)) continue;
34722
35160
  const content = readFileSync55(filePath, "utf-8");
34723
35161
  const hash = hashContent2(content);
34724
- 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);
35162
+ const relPath = filePath.startsWith(paths.claudeDir) ? relative15(paths.claudeDir, filePath) : filePath.startsWith(paths.plansDir) ? "plans/" + relative15(paths.plansDir, filePath) : filePath.startsWith(paths.docsDir) ? "docs/" + relative15(paths.docsDir, filePath) : filePath.startsWith(paths.memoryDir) ? `memory/${relative15(paths.memoryDir, filePath)}` : basename11(filePath);
34725
35163
  const category = categorizeFile(filePath);
34726
35164
  const title = extractTitle(content, filePath);
34727
35165
  const description = extractDescription2(content);
34728
- const result = insertDoc.run(relPath, category, title, description, hash, nowIso, nowEpoch);
35166
+ const result = insertDoc.run(relPath, category, title, description, hash, nowIso2, nowEpoch);
34729
35167
  const docId = result.lastInsertRowid;
34730
35168
  stats.filesIndexed++;
34731
35169
  const sections = parseSections(content, filePath);
@@ -34886,8 +35324,8 @@ var init_knowledge_indexer = __esm({
34886
35324
  });
34887
35325
 
34888
35326
  // src/knowledge-tools.ts
34889
- import { readFileSync as readFileSync56, writeFileSync as writeFileSync16, appendFileSync as appendFileSync5, readdirSync as readdirSync29 } from "fs";
34890
- import { resolve as resolve39, basename as basename12 } from "path";
35327
+ import { readFileSync as readFileSync56, writeFileSync as writeFileSync17, appendFileSync as appendFileSync5, readdirSync as readdirSync29 } from "fs";
35328
+ import { resolve as resolve41, basename as basename12 } from "path";
34891
35329
  function p16(baseName) {
34892
35330
  return `${getConfig().toolPrefix}_${baseName}`;
34893
35331
  }
@@ -34905,10 +35343,19 @@ function ensureKnowledgeIndexed(db) {
34905
35343
  try {
34906
35344
  indexIfStale(db);
34907
35345
  return true;
34908
- } catch {
35346
+ } catch (err) {
35347
+ recordHookFailure("knowledge-tools:ensure-indexed", err, {
35348
+ note: "knowledge index refresh failed; dispatch continued against the existing index"
35349
+ });
34909
35350
  return false;
34910
35351
  }
34911
35352
  }
35353
+ function withStaleIndexWarning(result) {
35354
+ const banner = "\u26A0\uFE0F KNOWLEDGE INDEX NOT REFRESHED \u2014 the re-index for this call FAILED, so the results below come from the last successfully built index and may be out of date. The error was recorded to the hook-failure log under `knowledge-tools:ensure-indexed`.";
35355
+ return {
35356
+ content: [{ type: "text", text: banner }, ...result.content]
35357
+ };
35358
+ }
34912
35359
  function hasData(db) {
34913
35360
  const row = db.prepare("SELECT COUNT(*) as cnt FROM knowledge_documents").get();
34914
35361
  return (row?.cnt ?? 0) > 0;
@@ -35089,8 +35536,15 @@ function isKnowledgeTool(name) {
35089
35536
  const baseName = name.startsWith(pfx) ? name.slice(pfx.length) : name;
35090
35537
  return KNOWLEDGE_BASE_NAMES.has(baseName);
35091
35538
  }
35092
- function handleKnowledgeToolCall(name, args2, db) {
35093
- ensureKnowledgeIndexed(db);
35539
+ function handleKnowledgeToolCall(name, args2, db, freshness) {
35540
+ if (freshness === "use-existing") {
35541
+ return dispatchKnowledgeTool(name, args2, db);
35542
+ }
35543
+ const indexed = ensureKnowledgeIndexed(db);
35544
+ const result = dispatchKnowledgeTool(name, args2, db);
35545
+ return indexed ? result : withStaleIndexWarning(result);
35546
+ }
35547
+ function dispatchKnowledgeTool(name, args2, db) {
35094
35548
  if (!hasData(db)) {
35095
35549
  return text15("No knowledge indexed yet. The knowledge DB will be populated automatically on next MCP server restart.");
35096
35550
  }
@@ -35626,7 +36080,7 @@ function handleCorrect(db, args2) {
35626
36080
  if (!wrong || !correction || !rule) {
35627
36081
  return text15("Error: wrong, correction, and rule are all required.");
35628
36082
  }
35629
- const correctionsPath = resolve39(getResolvedPaths().memoryDir, "corrections.md");
36083
+ const correctionsPath = resolve41(getResolvedPaths().memoryDir, "corrections.md");
35630
36084
  const today = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
35631
36085
  const title = rule.slice(0, 60);
35632
36086
  const entry = formatCorrectionEntry({
@@ -35646,7 +36100,7 @@ function handleCorrect(db, args2) {
35646
36100
  if (archiveIdx > 0) {
35647
36101
  const before = existing.slice(0, archiveIdx);
35648
36102
  const after = existing.slice(archiveIdx);
35649
- writeFileSync16(correctionsPath, before + entry + after);
36103
+ writeFileSync17(correctionsPath, before + entry + after);
35650
36104
  } else {
35651
36105
  appendFileSync5(correctionsPath, entry);
35652
36106
  }
@@ -35960,6 +36414,7 @@ var init_knowledge_tools = __esm({
35960
36414
  init_db2();
35961
36415
  init_memory_db();
35962
36416
  init_sql_table_names();
36417
+ init_hook_failure_signal();
35963
36418
  KNOWLEDGE_BASE_NAMES = /* @__PURE__ */ new Set([
35964
36419
  "knowledge_search",
35965
36420
  "knowledge_rule",
@@ -36596,7 +37051,7 @@ var init_framework_supports = __esm({
36596
37051
 
36597
37052
  // src/tools.ts
36598
37053
  import { readFileSync as readFileSync57, existsSync as existsSync68 } from "fs";
36599
- import { resolve as resolve40, basename as basename13 } from "path";
37054
+ import { resolve as resolve42, basename as basename13 } from "path";
36600
37055
  function prefix2() {
36601
37056
  return getConfig().toolPrefix;
36602
37057
  }
@@ -36631,7 +37086,7 @@ function ensureIndexes(dataDb, codegraphDb, force = false) {
36631
37086
  if (config.python?.root) {
36632
37087
  const pythonRoot = config.python.root;
36633
37088
  const excludeDirs = config.python.exclude_dirs || ["__pycache__", ".venv", "venv", ".mypy_cache", ".pytest_cache"];
36634
- if (force || isPythonDataStale(dataDb, resolve40(getProjectRoot(), pythonRoot))) {
37089
+ if (force || isPythonDataStale(dataDb, resolve42(getProjectRoot(), pythonRoot))) {
36635
37090
  const pyImports = buildPythonImportIndex(dataDb, pythonRoot, excludeDirs);
36636
37091
  results.push(`Python imports: ${pyImports}`);
36637
37092
  const pyRoutes = buildPythonRouteIndex(dataDb, pythonRoot, excludeDirs);
@@ -36854,7 +37309,7 @@ async function handleToolCall(name, args2, dataDb, codegraphDb, memoryDbCache, k
36854
37309
  return withMemDb((memDb) => handleRegressionToolCall(name, args2, memDb));
36855
37310
  }
36856
37311
  if (isKnowledgeTool(name)) {
36857
- return withKnowledgeDb((kdb) => handleKnowledgeToolCall(name, args2, kdb));
37312
+ return withKnowledgeDb((kdb) => handleKnowledgeToolCall(name, args2, kdb, "ensure-fresh"));
36858
37313
  }
36859
37314
  if (isPythonTool(name)) {
36860
37315
  const pyDataDb = assertDataDb(dataDb, name);
@@ -37014,7 +37469,7 @@ function handleContext(file, dataDb, codegraphDb) {
37014
37469
  try {
37015
37470
  const resolvedPaths = getResolvedPaths();
37016
37471
  const root = getProjectRoot();
37017
- const absFilePath = ensureWithinRoot(resolve40(resolvedPaths.srcDir, "..", file), root);
37472
+ const absFilePath = ensureWithinRoot(resolve42(resolvedPaths.srcDir, "..", file), root);
37018
37473
  if (existsSync68(absFilePath)) {
37019
37474
  const fileContent = readFileSync57(absFilePath, "utf-8").slice(0, 3e3);
37020
37475
  const keywords = [];
@@ -37253,9 +37708,10 @@ function handleCouplingCheck(args2, dataDb, codegraphDb) {
37253
37708
  lines.push("### Uncoupled Procedures: 0 (PASS)");
37254
37709
  lines.push("");
37255
37710
  }
37711
+ const pagesPred = pagesPredicate();
37256
37712
  const allPages = codegraphDb.prepare(
37257
- "SELECT path FROM files WHERE path LIKE 'src/app/%/page.tsx' OR path = 'src/app/page.tsx' LIMIT 10000"
37258
- ).all();
37713
+ `SELECT path FROM files WHERE ${pagesPred.sql} LIMIT 10000`
37714
+ ).all(...pagesPred.params);
37259
37715
  const pageImports = /* @__PURE__ */ new Set();
37260
37716
  for (const page of allPages) {
37261
37717
  const imports = dataDb.prepare(
@@ -37268,9 +37724,10 @@ function handleCouplingCheck(args2, dataDb, codegraphDb) {
37268
37724
  let componentFiles;
37269
37725
  if (stagedFiles) {
37270
37726
  const placeholders = stagedFiles.map(() => "?").join(",");
37727
+ const componentsPred = componentsPredicate();
37271
37728
  componentFiles = codegraphDb.prepare(
37272
- `SELECT path FROM files WHERE path LIKE 'src/components/%' AND path IN (${placeholders}) LIMIT 10000`
37273
- ).all(...stagedFiles);
37729
+ `SELECT path FROM files WHERE ${componentsPred.sql} AND path IN (${placeholders}) LIMIT 10000`
37730
+ ).all(...componentsPred.params, ...stagedFiles);
37274
37731
  } else {
37275
37732
  componentFiles = [];
37276
37733
  }
@@ -37453,7 +37910,7 @@ function handleSchema(args2) {
37453
37910
  lines.push("Checking all column references against Prisma schema...");
37454
37911
  lines.push("");
37455
37912
  const projectRoot = getProjectRoot();
37456
- const absPath = ensureWithinRoot(resolve40(projectRoot, file), projectRoot);
37913
+ const absPath = ensureWithinRoot(resolve42(projectRoot, file), projectRoot);
37457
37914
  if (!existsSync68(absPath)) {
37458
37915
  return text17(`File not found: ${file}`);
37459
37916
  }
@@ -37532,6 +37989,7 @@ var init_tools = __esm({
37532
37989
  init_framework_supports();
37533
37990
  init_license();
37534
37991
  init_sql_table_names();
37992
+ init_source_layout();
37535
37993
  }
37536
37994
  });
37537
37995
 
@@ -37890,7 +38348,7 @@ var init_startup_health = __esm({
37890
38348
  // src/server.ts
37891
38349
  var server_exports = {};
37892
38350
  import { readFileSync as readFileSync58 } from "fs";
37893
- import { resolve as resolve41, dirname as dirname28 } from "path";
38351
+ import { resolve as resolve43, dirname as dirname28 } from "path";
37894
38352
  import { fileURLToPath as fileURLToPath6 } from "url";
37895
38353
  function pruneMemoryOnStartup() {
37896
38354
  try {
@@ -37938,7 +38396,7 @@ var init_server = __esm({
37938
38396
  __dirname5 = dirname28(fileURLToPath6(import.meta.url));
37939
38397
  PKG_VERSION = (() => {
37940
38398
  try {
37941
- const pkg = JSON.parse(readFileSync58(resolve41(__dirname5, "..", "package.json"), "utf-8"));
38399
+ const pkg = JSON.parse(readFileSync58(resolve43(__dirname5, "..", "package.json"), "utf-8"));
37942
38400
  return pkg.version ?? "0.0.0";
37943
38401
  } catch {
37944
38402
  return "0.0.0";
@@ -38241,12 +38699,12 @@ var config_upgrade_exports = {};
38241
38699
  __export(config_upgrade_exports, {
38242
38700
  runConfigUpgrade: () => runConfigUpgrade
38243
38701
  });
38244
- import { existsSync as existsSync69, readFileSync as readFileSync59, writeFileSync as writeFileSync17, copyFileSync as copyFileSync3, unlinkSync as unlinkSync6 } from "fs";
38245
- import { resolve as resolve42 } from "path";
38702
+ import { existsSync as existsSync69, readFileSync as readFileSync59, writeFileSync as writeFileSync18, copyFileSync as copyFileSync3, unlinkSync as unlinkSync6 } from "fs";
38703
+ import { resolve as resolve44 } from "path";
38246
38704
  import { parse as parseYaml6 } from "yaml";
38247
38705
  async function runConfigUpgrade(opts = {}) {
38248
38706
  const cwd = opts.cwd ?? process.cwd();
38249
- const configPath = resolve42(cwd, "massu.config.yaml");
38707
+ const configPath = resolve44(cwd, "massu.config.yaml");
38250
38708
  const bakPath = `${configPath}.bak`;
38251
38709
  const log = opts.silent ? () => {
38252
38710
  } : (s) => process.stdout.write(s);
@@ -38300,7 +38758,7 @@ async function runConfigUpgrade(opts = {}) {
38300
38758
  };
38301
38759
  try {
38302
38760
  const original = readFileSync59(configPath, "utf-8");
38303
- writeFileSync17(bakPath, original, "utf-8");
38761
+ writeFileSync18(bakPath, original, "utf-8");
38304
38762
  } catch (e2) {
38305
38763
  const message = `Failed to write backup: ${e2 instanceof Error ? e2.message : String(e2)}`;
38306
38764
  err(message + "\n");
@@ -38334,7 +38792,7 @@ __export(config_check_drift_exports, {
38334
38792
  runConfigCheckDrift: () => runConfigCheckDrift
38335
38793
  });
38336
38794
  import { existsSync as existsSync70, readFileSync as readFileSync60 } from "fs";
38337
- import { resolve as resolve43 } from "path";
38795
+ import { resolve as resolve45 } from "path";
38338
38796
  import { parse as parseYaml7 } from "yaml";
38339
38797
  function renderChanges(changes) {
38340
38798
  if (changes.length === 0) return "(none)\n";
@@ -38342,7 +38800,7 @@ function renderChanges(changes) {
38342
38800
  }
38343
38801
  async function runConfigCheckDrift(opts = {}) {
38344
38802
  const cwd = opts.cwd ?? process.cwd();
38345
- const configPath = resolve43(cwd, "massu.config.yaml");
38803
+ const configPath = resolve45(cwd, "massu.config.yaml");
38346
38804
  const log = opts.silent ? () => {
38347
38805
  } : (s) => process.stdout.write(s);
38348
38806
  const err = opts.silent ? () => {
@@ -38429,7 +38887,7 @@ var init_config_check_drift = __esm({
38429
38887
 
38430
38888
  // src/cli.ts
38431
38889
  import { readFileSync as readFileSync61 } from "fs";
38432
- import { resolve as resolve44, dirname as dirname29 } from "path";
38890
+ import { resolve as resolve46, dirname as dirname29 } from "path";
38433
38891
  import { fileURLToPath as fileURLToPath7 } from "url";
38434
38892
 
38435
38893
  // src/lib/node-bootstrap.ts
@@ -38931,7 +39389,7 @@ Examples:
38931
39389
  }
38932
39390
  function printVersion() {
38933
39391
  try {
38934
- const pkg = JSON.parse(readFileSync61(resolve44(__dirname6, "../package.json"), "utf-8"));
39392
+ const pkg = JSON.parse(readFileSync61(resolve46(__dirname6, "../package.json"), "utf-8"));
38935
39393
  console.log(`massu v${pkg.version}`);
38936
39394
  } catch {
38937
39395
  console.log("massu v0.1.0");