@massu/core 2.5.1 → 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(),
@@ -9986,21 +9993,21 @@ var require_pattern = __commonJS({
9986
9993
  exports.removeDuplicateSlashes = removeDuplicateSlashes;
9987
9994
  function partitionAbsoluteAndRelative(patterns) {
9988
9995
  const absolute = [];
9989
- const relative15 = [];
9996
+ const relative16 = [];
9990
9997
  for (const pattern of patterns) {
9991
- if (isAbsolute5(pattern)) {
9998
+ if (isAbsolute6(pattern)) {
9992
9999
  absolute.push(pattern);
9993
10000
  } else {
9994
- relative15.push(pattern);
10001
+ relative16.push(pattern);
9995
10002
  }
9996
10003
  }
9997
- return [absolute, relative15];
10004
+ return [absolute, relative16];
9998
10005
  }
9999
10006
  exports.partitionAbsoluteAndRelative = partitionAbsoluteAndRelative;
10000
- function isAbsolute5(pattern) {
10007
+ function isAbsolute6(pattern) {
10001
10008
  return path.isAbsolute(pattern);
10002
10009
  }
10003
- exports.isAbsolute = isAbsolute5;
10010
+ exports.isAbsolute = isAbsolute6;
10004
10011
  }
10005
10012
  });
10006
10013
 
@@ -11084,41 +11091,41 @@ var require_queue = __commonJS({
11084
11091
  queue.drained = drained;
11085
11092
  return queue;
11086
11093
  function push(value) {
11087
- var p19 = new Promise(function(resolve45, reject) {
11094
+ var p19 = new Promise(function(resolve47, reject) {
11088
11095
  pushCb(value, function(err, result) {
11089
11096
  if (err) {
11090
11097
  reject(err);
11091
11098
  return;
11092
11099
  }
11093
- resolve45(result);
11100
+ resolve47(result);
11094
11101
  });
11095
11102
  });
11096
11103
  p19.catch(noop);
11097
11104
  return p19;
11098
11105
  }
11099
11106
  function unshift(value) {
11100
- var p19 = new Promise(function(resolve45, reject) {
11107
+ var p19 = new Promise(function(resolve47, reject) {
11101
11108
  unshiftCb(value, function(err, result) {
11102
11109
  if (err) {
11103
11110
  reject(err);
11104
11111
  return;
11105
11112
  }
11106
- resolve45(result);
11113
+ resolve47(result);
11107
11114
  });
11108
11115
  });
11109
11116
  p19.catch(noop);
11110
11117
  return p19;
11111
11118
  }
11112
11119
  function drained() {
11113
- var p19 = new Promise(function(resolve45) {
11120
+ var p19 = new Promise(function(resolve47) {
11114
11121
  process.nextTick(function() {
11115
11122
  if (queue.idle()) {
11116
- resolve45();
11123
+ resolve47();
11117
11124
  } else {
11118
11125
  var previousDrain = queue.drain;
11119
11126
  queue.drain = function() {
11120
11127
  if (typeof previousDrain === "function") previousDrain();
11121
- resolve45();
11128
+ resolve47();
11122
11129
  queue.drain = previousDrain;
11123
11130
  };
11124
11131
  }
@@ -11604,9 +11611,9 @@ var require_stream3 = __commonJS({
11604
11611
  });
11605
11612
  }
11606
11613
  _getStat(filepath) {
11607
- return new Promise((resolve45, reject) => {
11614
+ return new Promise((resolve47, reject) => {
11608
11615
  this._stat(filepath, this._fsStatSettings, (error, stats) => {
11609
- return error === null ? resolve45(stats) : reject(error);
11616
+ return error === null ? resolve47(stats) : reject(error);
11610
11617
  });
11611
11618
  });
11612
11619
  }
@@ -11630,10 +11637,10 @@ var require_async5 = __commonJS({
11630
11637
  this._readerStream = new stream_1.default(this._settings);
11631
11638
  }
11632
11639
  dynamic(root, options) {
11633
- return new Promise((resolve45, reject) => {
11640
+ return new Promise((resolve47, reject) => {
11634
11641
  this._walkAsync(root, options, (error, entries) => {
11635
11642
  if (error === null) {
11636
- resolve45(entries);
11643
+ resolve47(entries);
11637
11644
  } else {
11638
11645
  reject(error);
11639
11646
  }
@@ -11643,10 +11650,10 @@ var require_async5 = __commonJS({
11643
11650
  async static(patterns, options) {
11644
11651
  const entries = [];
11645
11652
  const stream = this._readerStream.static(patterns, options);
11646
- return new Promise((resolve45, reject) => {
11653
+ return new Promise((resolve47, reject) => {
11647
11654
  stream.once("error", reject);
11648
11655
  stream.on("data", (entry) => entries.push(entry));
11649
- stream.once("end", () => resolve45(entries));
11656
+ stream.once("end", () => resolve47(entries));
11650
11657
  });
11651
11658
  }
11652
11659
  };
@@ -16611,15 +16618,15 @@ function resolveHooksDir() {
16611
16618
  }
16612
16619
  return "node_modules/@massu/core/dist/hooks";
16613
16620
  }
16614
- function massuRuntimeDir(version) {
16615
- return resolve9(homedir7(), ".massu", "runtime", version);
16621
+ function massuRuntimeDir(version, home = homedir7()) {
16622
+ return resolve9(home, ".massu", "runtime", version);
16616
16623
  }
16617
- function massuRuntimeCliPath(version) {
16618
- 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");
16619
16626
  }
16620
- function resolveMassuRuntimeCli(version) {
16627
+ function resolveMassuRuntimeCli(version, home = homedir7()) {
16621
16628
  if (process.env.MASSU_NO_NODE_DIRECT === "1") return null;
16622
- const cli = massuRuntimeCliPath(version);
16629
+ const cli = massuRuntimeCliPath(version, home);
16623
16630
  if (!existsSync19(cli)) return null;
16624
16631
  try {
16625
16632
  execFileSync2(process.execPath, [cli, "--version"], { stdio: "ignore", timeout: 2e4 });
@@ -16628,10 +16635,10 @@ function resolveMassuRuntimeCli(version) {
16628
16635
  return null;
16629
16636
  }
16630
16637
  }
16631
- function materializeMassuRuntime(version) {
16638
+ function materializeMassuRuntime(version, home = homedir7()) {
16632
16639
  if (process.env.MASSU_NO_NODE_DIRECT === "1") return false;
16633
- if (resolveMassuRuntimeCli(version) !== null) return true;
16634
- const dir = massuRuntimeDir(version);
16640
+ if (resolveMassuRuntimeCli(version, home) !== null) return true;
16641
+ const dir = massuRuntimeDir(version, home);
16635
16642
  try {
16636
16643
  mkdirSync10(dir, { recursive: true });
16637
16644
  execFileSync2(
@@ -16642,10 +16649,10 @@ function materializeMassuRuntime(version) {
16642
16649
  } catch {
16643
16650
  return false;
16644
16651
  }
16645
- return resolveMassuRuntimeCli(version) !== null;
16652
+ return resolveMassuRuntimeCli(version, home) !== null;
16646
16653
  }
16647
- function massuShimPath() {
16648
- return resolve9(homedir7(), ".massu", "bin", "massu-hook");
16654
+ function massuShimPath(home = homedir7()) {
16655
+ return resolve9(home, ".massu", "bin", "massu-hook");
16649
16656
  }
16650
16657
  function massuShimBody() {
16651
16658
  return `#!/bin/sh
@@ -16670,10 +16677,10 @@ fi
16670
16677
  exec npx -y "@massu/core@\${MASSU_VERSION}" "$@"
16671
16678
  `;
16672
16679
  }
16673
- function installMassuShim() {
16680
+ function installMassuShim(home = homedir7()) {
16674
16681
  if (process.env.MASSU_NO_NODE_DIRECT === "1") return false;
16675
16682
  if (process.platform === "win32") return false;
16676
- const shim = massuShimPath();
16683
+ const shim = massuShimPath(home);
16677
16684
  try {
16678
16685
  mkdirSync10(dirname12(shim), { recursive: true });
16679
16686
  writeFileSync5(shim, massuShimBody(), "utf-8");
@@ -16689,10 +16696,10 @@ function installMassuShim() {
16689
16696
  return status === 2;
16690
16697
  }
16691
16698
  }
16692
- function resolveMassuShim() {
16699
+ function resolveMassuShim(home = homedir7()) {
16693
16700
  if (process.env.MASSU_NO_NODE_DIRECT === "1") return null;
16694
16701
  if (process.platform === "win32") return null;
16695
- const shim = massuShimPath();
16702
+ const shim = massuShimPath(home);
16696
16703
  if (!existsSync19(shim)) return null;
16697
16704
  try {
16698
16705
  execFileSync2(shim, [], { stdio: "ignore", timeout: 1e4 });
@@ -16708,9 +16715,8 @@ function hookCmd(version, hookName, shim) {
16708
16715
  function getHookTimeout(name) {
16709
16716
  return HOOK_TIMEOUTS[name] ?? 5;
16710
16717
  }
16711
- function buildHooksConfig(_hooksDir) {
16718
+ function buildHooksConfig(_hooksDir, shim = resolveMassuShim()) {
16712
16719
  const version = getInstallerVersion();
16713
- const shim = resolveMassuShim();
16714
16720
  return {
16715
16721
  SessionStart: [
16716
16722
  {
@@ -18167,7 +18173,7 @@ function checkHookFiles(projectRoot) {
18167
18173
  }
18168
18174
  async function checkHookExecution(projectRoot, opts = {}) {
18169
18175
  const { spawnSync: spawnSync6 } = await import("child_process");
18170
- const { mkdtempSync: mkdtempSync2, copyFileSync: copyFileSync4, rmSync: rmSync12 } = await import("fs");
18176
+ const { mkdtempSync: mkdtempSync2, copyFileSync: copyFileSync4, rmSync: rmSync13 } = await import("fs");
18171
18177
  const { tmpdir: tmpdir2 } = await import("os");
18172
18178
  const node = opts.node ?? process.execPath;
18173
18179
  const timeoutMs = opts.timeoutMs ?? 1e4;
@@ -18221,7 +18227,7 @@ async function checkHookExecution(projectRoot, opts = {}) {
18221
18227
  };
18222
18228
  } finally {
18223
18229
  try {
18224
- rmSync12(tmpCwd, { recursive: true, force: true });
18230
+ rmSync13(tmpCwd, { recursive: true, force: true });
18225
18231
  } catch {
18226
18232
  }
18227
18233
  }
@@ -20442,8 +20448,8 @@ function renderRegion(content, lines) {
20442
20448
  ${lines.join("\n")}
20443
20449
  ` : "\n";
20444
20450
  if (state.kind === "absent") {
20445
- const sep3 = content.endsWith("\n") ? "" : "\n";
20446
- return `${content}${sep3}
20451
+ const sep4 = content.endsWith("\n") ? "" : "\n";
20452
+ return `${content}${sep4}
20447
20453
  ${BEGIN_SENTINEL}${body}${END_SENTINEL}
20448
20454
  `;
20449
20455
  }
@@ -22909,8 +22915,8 @@ function writeCustomDestination(opts, candidate, slug, targets) {
22909
22915
  const rendered = renderTemplate2(entry.template, vars);
22910
22916
  if (!existsSync38(dirname19(destPath))) mkdirSync17(dirname19(destPath), { recursive: true });
22911
22917
  const pre = existsSync38(destPath) ? readFileSync29(destPath, "utf-8") : "";
22912
- const sep3 = pre && !pre.endsWith("\n") ? "\n" : "";
22913
- 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");
22914
22920
  }
22915
22921
  function dismissRuleCandidate(db, opts) {
22916
22922
  if (!CANDIDATE_ID_PATTERN.test(opts.candidateId)) {
@@ -22963,8 +22969,8 @@ function dismissRuleCandidate(db, opts) {
22963
22969
  const dismissedLog = join24(projectRoot, ".massu", "rule-candidates", ".dismissed.jsonl");
22964
22970
  if (!existsSync38(dirname19(dismissedLog))) mkdirSync17(dirname19(dismissedLog), { recursive: true });
22965
22971
  const pre = existsSync38(dismissedLog) ? readFileSync29(dismissedLog, "utf-8") : "";
22966
- const sep3 = pre && !pre.endsWith("\n") ? "\n" : "";
22967
- writeFileSync11(dismissedLog, pre + sep3 + JSON.stringify({
22972
+ const sep4 = pre && !pre.endsWith("\n") ? "\n" : "";
22973
+ writeFileSync11(dismissedLog, pre + sep4 + JSON.stringify({
22968
22974
  id: opts.candidateId,
22969
22975
  prompt_hash: candidate.prompt_hash,
22970
22976
  score: candidate.score,
@@ -22995,8 +23001,8 @@ function appendFailureLog(projectRoot, entry) {
22995
23001
  if (!existsSync38(dir)) mkdirSync17(dir, { recursive: true });
22996
23002
  const logPath = join24(dir, ".failures.jsonl");
22997
23003
  const pre = existsSync38(logPath) ? readFileSync29(logPath, "utf-8") : "";
22998
- const sep3 = pre && !pre.endsWith("\n") ? "\n" : "";
22999
- 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");
23000
23006
  } catch {
23001
23007
  }
23002
23008
  }
@@ -25381,12 +25387,102 @@ var init_gitToplevel = __esm({
25381
25387
  }
25382
25388
  });
25383
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
+
25384
25480
  // src/watch/lockfile-detector.ts
25385
25481
  import { existsSync as existsSync45, statSync as statSync15 } from "fs";
25386
- import { resolve as resolve20 } from "path";
25482
+ import { resolve as resolve21 } from "path";
25387
25483
  function lockfileMidWrite(projectRoot, now = Date.now(), windowMs = LOCKFILE_WINDOW_MS) {
25388
25484
  for (const lf of KNOWN_LOCKFILES) {
25389
- const p19 = resolve20(projectRoot, lf);
25485
+ const p19 = resolve21(projectRoot, lf);
25390
25486
  if (!existsSync45(p19)) continue;
25391
25487
  try {
25392
25488
  const stat = statSync15(p19);
@@ -25400,7 +25496,7 @@ function lockfileMidWrite(projectRoot, now = Date.now(), windowMs = LOCKFILE_WIN
25400
25496
  function gitMidOperation(projectRoot) {
25401
25497
  const sentinels = ["MERGE_HEAD", "REBASE_HEAD", "CHERRY_PICK_HEAD", "rebase-apply", "rebase-merge"];
25402
25498
  for (const s of sentinels) {
25403
- if (existsSync45(resolve20(projectRoot, ".git", s))) return true;
25499
+ if (existsSync45(resolve21(projectRoot, ".git", s))) return true;
25404
25500
  }
25405
25501
  return false;
25406
25502
  }
@@ -25599,13 +25695,13 @@ var init_paths = __esm({
25599
25695
  });
25600
25696
 
25601
25697
  // src/watch/state.ts
25602
- 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";
25603
- 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";
25604
25700
  function watchStatePath(projectRoot) {
25605
- return resolve21(projectRoot, ".massu", "watch-state.json");
25701
+ return resolve22(projectRoot, ".massu", "watch-state.json");
25606
25702
  }
25607
25703
  function backupStatePath(projectRoot) {
25608
- return resolve21(projectRoot, ".massu", "watch-state.v0.bak.json");
25704
+ return resolve22(projectRoot, ".massu", "watch-state.v0.bak.json");
25609
25705
  }
25610
25706
  function readState(projectRoot) {
25611
25707
  const path = watchStatePath(projectRoot);
@@ -25652,14 +25748,14 @@ function readState(projectRoot) {
25652
25748
  }
25653
25749
  function archiveCorrupt(projectRoot, content) {
25654
25750
  const bak = backupStatePath(projectRoot);
25655
- mkdirSync20(dirname21(bak), { recursive: true });
25656
- writeFileSync15(bak, content, "utf-8");
25751
+ mkdirSync21(dirname21(bak), { recursive: true });
25752
+ writeFileSync16(bak, content, "utf-8");
25657
25753
  }
25658
25754
  function writeStateAtomic(projectRoot, state) {
25659
25755
  const path = watchStatePath(projectRoot);
25660
25756
  writeStateAtomicCounter = writeStateAtomicCounter + 1 >>> 0;
25661
25757
  const tmp = `${path}.${process.pid}.${writeStateAtomicCounter}.tmp`;
25662
- mkdirSync20(dirname21(path), { recursive: true });
25758
+ mkdirSync21(dirname21(path), { recursive: true });
25663
25759
  let renamed = false;
25664
25760
  try {
25665
25761
  const fd = openSync5(tmp, "w");
@@ -25675,7 +25771,7 @@ function writeStateAtomic(projectRoot, state) {
25675
25771
  } finally {
25676
25772
  if (!renamed && existsSync46(tmp)) {
25677
25773
  try {
25678
- rmSync10(tmp, { force: true });
25774
+ rmSync11(tmp, { force: true });
25679
25775
  } catch {
25680
25776
  }
25681
25777
  }
@@ -25718,6 +25814,8 @@ var init_state = __esm({
25718
25814
 
25719
25815
  // src/watch/daemon.ts
25720
25816
  import * as chokidar from "chokidar";
25817
+ import { mkdirSync as mkdirSync22 } from "fs";
25818
+ import { resolve as resolve23 } from "path";
25721
25819
  function readDaemonConfig(projectRoot) {
25722
25820
  const cfg = getConfig();
25723
25821
  const w2 = cfg.watch ?? {};
@@ -25751,6 +25849,10 @@ async function startDaemon(projectRoot, hooks) {
25751
25849
  let tickTimer = null;
25752
25850
  let stopped = false;
25753
25851
  let runningRefresh = false;
25852
+ let canary = null;
25853
+ let watchSpec = null;
25854
+ let rebuilding = false;
25855
+ let watcherReady = false;
25754
25856
  function clearDebounce() {
25755
25857
  if (ctx.debounceTimer) {
25756
25858
  clearTimeout(ctx.debounceTimer);
@@ -25857,6 +25959,110 @@ async function startDaemon(projectRoot, hooks) {
25857
25959
  await watcher.close();
25858
25960
  watcher = null;
25859
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();
25860
26066
  }
25861
26067
  function tick() {
25862
26068
  if (stopped) return;
@@ -25873,6 +26079,7 @@ async function startDaemon(projectRoot, hooks) {
25873
26079
  `);
25874
26080
  void forceReconciliation();
25875
26081
  }
26082
+ checkWatcherLiveness();
25876
26083
  }
25877
26084
  if (!hooks.noWatcher) {
25878
26085
  const cfgYaml = getConfig();
@@ -25897,23 +26104,16 @@ async function startDaemon(projectRoot, hooks) {
25897
26104
  `[massu] watch surface: ${countLabel} files (cap ${cap}, opted-in: ${optedIn}, scan ${surfaceMs}ms, scope: ${globs.effectiveScope})
25898
26105
  `
25899
26106
  );
25900
- watcher = chokidar.watch(globs.watch, {
25901
- cwd: cfg.projectRoot,
25902
- ignored: globs.ignore,
25903
- ignoreInitial: true,
25904
- persistent: true,
25905
- awaitWriteFinish: false
25906
- });
25907
- watcher.on("all", (_event, path) => pushEvent(path));
25908
- watcher.on("error", (err) => {
25909
- const msg = err instanceof Error ? err.message : String(err);
25910
- writeStderr(`[massu] chokidar error: ${msg}
25911
- `);
25912
- try {
25913
- updateState(cfg.projectRoot, { lastError: `chokidar: ${msg}` });
25914
- } catch {
25915
- }
25916
- });
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();
25917
26117
  }
25918
26118
  resetConfig();
25919
26119
  try {
@@ -25925,15 +26125,16 @@ async function startDaemon(projectRoot, hooks) {
25925
26125
  });
25926
26126
  } catch {
25927
26127
  }
25928
- tickTimer = setInterval(tick, TICK_INTERVAL_MS);
26128
+ tickTimer = setInterval(tick, hooks.tickIntervalMs ?? TICK_INTERVAL_MS);
25929
26129
  if (typeof tickTimer.unref === "function") tickTimer.unref();
25930
26130
  return { pushEvent, flushNow, stop, forceReconciliation };
25931
26131
  }
25932
- 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;
25933
26133
  var init_daemon = __esm({
25934
26134
  "src/watch/daemon.ts"() {
25935
26135
  "use strict";
25936
26136
  init_config();
26137
+ init_canary();
25937
26138
  init_lockfile_detector();
25938
26139
  init_paths();
25939
26140
  init_state();
@@ -25943,6 +26144,7 @@ var init_daemon = __esm({
25943
26144
  DEEP_STORM_WAIT_MS = 12e4;
25944
26145
  TICK_INTERVAL_MS = 1e4;
25945
26146
  TICK_GAP_THRESHOLD_MS = 3e4;
26147
+ STACK_RELEVANT_EVENTS = /* @__PURE__ */ new Set(["add", "change", "unlink"]);
25946
26148
  }
25947
26149
  });
25948
26150
 
@@ -25955,8 +26157,8 @@ __export(watch_exports, {
25955
26157
  runWatch: () => runWatch
25956
26158
  });
25957
26159
  import { spawnSync as spawnSync5 } from "child_process";
25958
- import { basename as basename8, dirname as dirname22, resolve as resolve22 } from "path";
25959
- 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";
25960
26162
  function parseFlags(args2) {
25961
26163
  const out = {
25962
26164
  foreground: false,
@@ -25981,7 +26183,7 @@ function parseFlags(args2) {
25981
26183
  }
25982
26184
  function findClaudeBg() {
25983
26185
  const home = process.env.HOME ?? "";
25984
- const fixed = home ? resolve22(home, ".claude", "bin", "claude-bg") : null;
26186
+ const fixed = home ? resolve24(home, ".claude", "bin", "claude-bg") : null;
25985
26187
  if (fixed && existsSync47(fixed)) return fixed;
25986
26188
  const which = spawnSync5("which", ["claude-bg"], { encoding: "utf-8" });
25987
26189
  if (which.status === 0 && which.stdout) {
@@ -26125,7 +26327,7 @@ async function runForeground(root) {
26125
26327
  }
26126
26328
  throw err;
26127
26329
  }
26128
- return new Promise((resolve45) => {
26330
+ return new Promise((resolve47) => {
26129
26331
  const shutdown = async () => {
26130
26332
  if (stopped) return;
26131
26333
  stopped = true;
@@ -26135,7 +26337,7 @@ async function runForeground(root) {
26135
26337
  process.chdir(priorCwd);
26136
26338
  } catch {
26137
26339
  }
26138
- resolve45({ exitCode: 0 });
26340
+ resolve47({ exitCode: 0 });
26139
26341
  };
26140
26342
  process.on("SIGINT", () => {
26141
26343
  void shutdown();
@@ -26196,12 +26398,12 @@ async function runOnQuiescent(projectRoot) {
26196
26398
  );
26197
26399
  }
26198
26400
  function refreshLogPath(projectRoot) {
26199
- return resolve22(projectRoot, ".massu", "refresh-log.jsonl");
26401
+ return resolve24(projectRoot, ".massu", "refresh-log.jsonl");
26200
26402
  }
26201
26403
  function appendRefreshLog(projectRoot, event) {
26202
26404
  const path = refreshLogPath(projectRoot);
26203
26405
  try {
26204
- mkdirSync21(dirname22(path), { recursive: true });
26406
+ mkdirSync23(dirname22(path), { recursive: true });
26205
26407
  appendFileSync4(path, JSON.stringify(event) + "\n", "utf-8");
26206
26408
  } catch {
26207
26409
  }
@@ -26287,10 +26489,10 @@ import {
26287
26489
  closeSync as closeSync6,
26288
26490
  existsSync as existsSync48,
26289
26491
  fsyncSync as fsyncSync6,
26290
- mkdirSync as mkdirSync22,
26492
+ mkdirSync as mkdirSync24,
26291
26493
  openSync as openSync6,
26292
26494
  renameSync as renameSync6,
26293
- rmSync as rmSync11,
26495
+ rmSync as rmSync12,
26294
26496
  statSync as statSync16,
26295
26497
  writeSync as writeSync4
26296
26498
  } from "node:fs";
@@ -26304,7 +26506,7 @@ function atomicWrite(path, content, opts = {}) {
26304
26506
  if (opts.ensureParentDirMode !== void 0) {
26305
26507
  mkdirOpts.mode = opts.ensureParentDirMode;
26306
26508
  }
26307
- mkdirSync22(parentDir, mkdirOpts);
26509
+ mkdirSync24(parentDir, mkdirOpts);
26308
26510
  }
26309
26511
  const buf = typeof content === "string" ? Buffer.from(content, "utf-8") : content;
26310
26512
  const openMode = opts.mode ?? 420;
@@ -26323,7 +26525,7 @@ function atomicWrite(path, content, opts = {}) {
26323
26525
  } catch (err) {
26324
26526
  if (existsSync48(tmpPath)) {
26325
26527
  try {
26326
- rmSync11(tmpPath, { force: true });
26528
+ rmSync12(tmpPath, { force: true });
26327
26529
  } catch {
26328
26530
  }
26329
26531
  }
@@ -26672,13 +26874,13 @@ var init_fetcher = __esm({
26672
26874
  // src/security/manifest-cache.ts
26673
26875
  import { existsSync as existsSync49, readFileSync as readFileSync36, statSync as statSync17 } from "node:fs";
26674
26876
  import { homedir as homedir21 } from "node:os";
26675
- import { resolve as resolve23 } from "node:path";
26877
+ import { resolve as resolve25 } from "node:path";
26676
26878
  import { z as z5 } from "zod";
26677
26879
  function defaultCachePaths() {
26678
- const dir = resolve23(homedir21(), ".massu");
26880
+ const dir = resolve25(homedir21(), ".massu");
26679
26881
  return {
26680
- cachePath: resolve23(dir, "adapter-manifest.json"),
26681
- lockPath: resolve23(dir, ".adapter-manifest.lock")
26882
+ cachePath: resolve25(dir, "adapter-manifest.json"),
26883
+ lockPath: resolve25(dir, ".adapter-manifest.lock")
26682
26884
  };
26683
26885
  }
26684
26886
  function loadCachedManifest(paths = defaultCachePaths()) {
@@ -26878,13 +27080,13 @@ var init_adapter_origin = __esm({
26878
27080
  // src/security/local-fingerprint.ts
26879
27081
  import { existsSync as existsSync50, readFileSync as readFileSync37, lstatSync as lstatSync5 } from "node:fs";
26880
27082
  import { homedir as homedir22 } from "node:os";
26881
- import { resolve as resolve24, isAbsolute as isAbsolute3 } from "node:path";
27083
+ import { resolve as resolve26, isAbsolute as isAbsolute4 } from "node:path";
26882
27084
  import { createHash as createHash14 } from "node:crypto";
26883
27085
  import { z as z6 } from "zod";
26884
27086
  function computeLocalFingerprint(localPaths, projectRoot) {
26885
27087
  const tuples = [];
26886
27088
  for (const p19 of localPaths) {
26887
- const abs = isAbsolute3(p19) ? p19 : resolve24(projectRoot, p19);
27089
+ const abs = isAbsolute4(p19) ? p19 : resolve26(projectRoot, p19);
26888
27090
  let contentTag;
26889
27091
  try {
26890
27092
  const lst = lstatSync5(abs);
@@ -26956,7 +27158,7 @@ var init_local_fingerprint = __esm({
26956
27158
  "use strict";
26957
27159
  init_atomic_write();
26958
27160
  init_manifest_schema();
26959
- FINGERPRINT_PATH = resolve24(homedir22(), ".massu", "adapters-local-fingerprint.json");
27161
+ FINGERPRINT_PATH = resolve26(homedir22(), ".massu", "adapters-local-fingerprint.json");
26960
27162
  FingerprintSentinelSchema = z6.object({
26961
27163
  fingerprint: z6.string().regex(/^[0-9a-f]{64}$/),
26962
27164
  source: z6.enum(["cli", "cli-resync"]),
@@ -26973,9 +27175,9 @@ var init_local_fingerprint = __esm({
26973
27175
 
26974
27176
  // src/security/install-tracking.ts
26975
27177
  import { readFileSync as readFileSync38, readdirSync as readdirSync17, lstatSync as lstatSync6, existsSync as existsSync51 } from "node:fs";
26976
- 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";
26977
27179
  import { homedir as homedir23 } from "node:os";
26978
- import { resolve as resolve25 } from "node:path";
27180
+ import { resolve as resolve27 } from "node:path";
26979
27181
  import { createHash as createHash15 } from "node:crypto";
26980
27182
  import { z as z7 } from "zod";
26981
27183
  function containsHiddenDirs(packageDir) {
@@ -27018,7 +27220,7 @@ function sha256OfDir(dir, opts = {}) {
27018
27220
  `sha256OfDir: file ${absPath} exceeds maxFileBytes (${lst.size} > ${maxFileBytes}); adapter packages should not ship files this large.`
27019
27221
  );
27020
27222
  }
27021
- const rel = relative7(dir, absPath).split(sep2).join("/");
27223
+ const rel = relative8(dir, absPath).split(sep3).join("/");
27022
27224
  files.push({ relativePath: rel, absPath });
27023
27225
  }
27024
27226
  }
@@ -27102,7 +27304,7 @@ var init_install_tracking = __esm({
27102
27304
  "src/security/install-tracking.ts"() {
27103
27305
  "use strict";
27104
27306
  init_atomic_write();
27105
- INSTALLED_MANIFEST_PATH = resolve25(homedir23(), ".massu", "adapter-manifest-installed.json");
27307
+ INSTALLED_MANIFEST_PATH = resolve27(homedir23(), ".massu", "adapter-manifest-installed.json");
27106
27308
  DEFAULT_MAX_FILE_BYTES = 64 * 1024 * 1024;
27107
27309
  EXCLUDED_DIR_NAMES = /* @__PURE__ */ new Set([".git", "node_modules", ".cache", ".tmp"]);
27108
27310
  InstallEntrySchema = z7.object({
@@ -27126,10 +27328,10 @@ var init_install_tracking = __esm({
27126
27328
 
27127
27329
  // src/detect/adapters/discover.ts
27128
27330
  import { existsSync as existsSync52, readdirSync as readdirSync18, readFileSync as readFileSync39, lstatSync as lstatSync7 } from "node:fs";
27129
- import { resolve as resolve26, isAbsolute as isAbsolute4 } from "node:path";
27331
+ import { resolve as resolve28, isAbsolute as isAbsolute5 } from "node:path";
27130
27332
  import { z as z8 } from "zod";
27131
27333
  function walkNodeModules(projectRoot, warnings) {
27132
- const nodeModulesDir = resolve26(projectRoot, "node_modules");
27334
+ const nodeModulesDir = resolve28(projectRoot, "node_modules");
27133
27335
  if (!existsSync52(nodeModulesDir)) {
27134
27336
  return [];
27135
27337
  }
@@ -27143,7 +27345,7 @@ function walkNodeModules(projectRoot, warnings) {
27143
27345
  }
27144
27346
  for (const entry of topLevelEntries) {
27145
27347
  if (entry.startsWith(".")) continue;
27146
- const entryPath = resolve26(nodeModulesDir, entry);
27348
+ const entryPath = resolve28(nodeModulesDir, entry);
27147
27349
  let entryStat;
27148
27350
  try {
27149
27351
  entryStat = lstatSync7(entryPath);
@@ -27165,7 +27367,7 @@ function walkNodeModules(projectRoot, warnings) {
27165
27367
  continue;
27166
27368
  }
27167
27369
  for (const sub of scopedEntries) {
27168
- const subPath = resolve26(entryPath, sub);
27370
+ const subPath = resolve28(entryPath, sub);
27169
27371
  let subStat;
27170
27372
  try {
27171
27373
  subStat = lstatSync7(subPath);
@@ -27184,7 +27386,7 @@ function walkNodeModules(projectRoot, warnings) {
27184
27386
  return candidates;
27185
27387
  }
27186
27388
  function tryReadAdapterPackage(packageDir, warnings) {
27187
- const pkgJsonPath = resolve26(packageDir, "package.json");
27389
+ const pkgJsonPath = resolve28(packageDir, "package.json");
27188
27390
  if (!existsSync52(pkgJsonPath)) return null;
27189
27391
  let raw;
27190
27392
  try {
@@ -27322,7 +27524,7 @@ function discoverAdapters(opts) {
27322
27524
  const localSet = new Set(opts.configLocalPaths);
27323
27525
  for (const localPath of opts.configLocalPaths) {
27324
27526
  if (seenIds.has(localPath)) continue;
27325
- const absPath = isAbsolute4(localPath) ? localPath : resolve26(opts.projectRoot, localPath);
27527
+ const absPath = isAbsolute5(localPath) ? localPath : resolve28(opts.projectRoot, localPath);
27326
27528
  if (!existsSync52(absPath)) {
27327
27529
  warnings.push(
27328
27530
  `local adapter file not found: ${localPath} (resolved to ${absPath}). Remove via: massu adapters remove-local ${localPath}`
@@ -27406,7 +27608,7 @@ __export(adapters_exports, {
27406
27608
  runAdaptersSearch: () => runAdaptersSearch
27407
27609
  });
27408
27610
  import { existsSync as existsSync53, readFileSync as readFileSync40 } from "node:fs";
27409
- import { resolve as resolve27 } from "node:path";
27611
+ import { resolve as resolve29 } from "node:path";
27410
27612
  import { parseDocument } from "yaml";
27411
27613
  async function handleAdaptersSubcommand(args2) {
27412
27614
  const sub = args2[0];
@@ -27673,7 +27875,7 @@ function mutateLocalArray(mutator, command) {
27673
27875
  );
27674
27876
  return { exitCode: 2 };
27675
27877
  }
27676
- const yamlPath = resolve27(projectRoot, "massu.config.yaml");
27878
+ const yamlPath = resolve29(projectRoot, "massu.config.yaml");
27677
27879
  if (!existsSync53(yamlPath)) {
27678
27880
  process.stderr.write(
27679
27881
  `${command}: massu.config.yaml not found at ${yamlPath}. Run \`massu init\` first.
@@ -27704,7 +27906,7 @@ function mutateLocalArray(mutator, command) {
27704
27906
  if (next === null) {
27705
27907
  return { exitCode: 0 };
27706
27908
  }
27707
- const lockPath = resolve27(projectRoot, ".massu", "adapters-local-mutate.lock");
27909
+ const lockPath = resolve29(projectRoot, ".massu", "adapters-local-mutate.lock");
27708
27910
  return withFileLockSync(lockPath, () => {
27709
27911
  doc.setIn(["adapters", "local"], next);
27710
27912
  const newYaml = doc.toString();
@@ -27773,7 +27975,7 @@ async function runAdaptersInstall(args2) {
27773
27975
  `);
27774
27976
  return { exitCode: 1 };
27775
27977
  }
27776
- const packageDir = resolve27(projectRoot, "node_modules", ...packageName.split("/"));
27978
+ const packageDir = resolve29(projectRoot, "node_modules", ...packageName.split("/"));
27777
27979
  if (!existsSync53(packageDir)) {
27778
27980
  process.stderr.write(
27779
27981
  `install: ${packageName} is not installed in node_modules. Run \`npm install ${packageName}\` first.
@@ -27781,7 +27983,7 @@ async function runAdaptersInstall(args2) {
27781
27983
  );
27782
27984
  return { exitCode: 1 };
27783
27985
  }
27784
- const pkgJsonPath = resolve27(packageDir, "package.json");
27986
+ const pkgJsonPath = resolve29(packageDir, "package.json");
27785
27987
  if (!existsSync53(pkgJsonPath)) {
27786
27988
  process.stderr.write(`install: ${packageName} has no package.json at ${pkgJsonPath}
27787
27989
  `);
@@ -27908,7 +28110,7 @@ async function runAdaptersResign(_args) {
27908
28110
  removeInstalledManifestEntry(name);
27909
28111
  continue;
27910
28112
  }
27911
- const packageDir = resolve27(projectRoot, "node_modules", ...name.split("/"));
28113
+ const packageDir = resolve29(projectRoot, "node_modules", ...name.split("/"));
27912
28114
  if (!existsSync53(packageDir)) {
27913
28115
  removed++;
27914
28116
  warnings.push(`${name}@${entry.version}: not present in node_modules \u2014 REMOVED from sidecar`);
@@ -28007,7 +28209,7 @@ var init_sql_table_names = __esm({
28007
28209
 
28008
28210
  // src/db.ts
28009
28211
  import { dirname as dirname24, join as join29 } from "path";
28010
- 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";
28011
28213
  function getCodeGraphDb() {
28012
28214
  const dbPath = getResolvedPaths().codegraphDbPath;
28013
28215
  assertCodegraphUsable(dbPath);
@@ -28019,7 +28221,7 @@ function getDataDb() {
28019
28221
  const dbPath = getResolvedPaths().dataDbPath;
28020
28222
  const dir = dirname24(dbPath);
28021
28223
  if (!existsSync54(dir)) {
28022
- mkdirSync23(dir, { recursive: true });
28224
+ mkdirSync25(dir, { recursive: true });
28023
28225
  }
28024
28226
  const db = openDatabase2(dbPath);
28025
28227
  db.pragma("journal_mode = WAL");
@@ -28273,12 +28475,23 @@ function initDataSchema(db) {
28273
28475
  END;
28274
28476
  `);
28275
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
+ }
28276
28489
  function isDataStale(dataDb, codegraphDb) {
28277
28490
  const lastBuild = dataDb.prepare(`SELECT value FROM ${t("meta")} WHERE key = 'last_build_time'`).get();
28278
28491
  if (!lastBuild) return true;
28279
28492
  const latestIndexed = codegraphDb.prepare("SELECT MAX(indexed_at) as latest FROM files").get();
28280
28493
  if (!latestIndexed?.latest) return true;
28281
- return latestIndexed.latest * 1e3 > new Date(lastBuild.value).getTime();
28494
+ return codegraphIndexedAtToEpochMs(latestIndexed.latest) > new Date(lastBuild.value).getTime();
28282
28495
  }
28283
28496
  function updateBuildTimestamp(dataDb) {
28284
28497
  dataDb.prepare(`INSERT OR REPLACE INTO ${t("meta")} (key, value) VALUES ('last_build_time', ?)`).run((/* @__PURE__ */ new Date()).toISOString());
@@ -28308,6 +28521,7 @@ function isPythonDataStale(dataDb, pythonRoot) {
28308
28521
  function updatePythonBuildTimestamp(dataDb) {
28309
28522
  dataDb.prepare(`INSERT OR REPLACE INTO ${t("py_meta")} (key, value) VALUES ('last_build_time', ?)`).run((/* @__PURE__ */ new Date()).toISOString());
28310
28523
  }
28524
+ var CODEGRAPH_TS_FLOOR_MS, CODEGRAPH_TS_SKEW_MS;
28311
28525
  var init_db2 = __esm({
28312
28526
  "src/db.ts"() {
28313
28527
  "use strict";
@@ -28316,14 +28530,16 @@ var init_db2 = __esm({
28316
28530
  init_config();
28317
28531
  init_sql_table_names();
28318
28532
  init_preflight();
28533
+ CODEGRAPH_TS_FLOOR_MS = 15778368e5;
28534
+ CODEGRAPH_TS_SKEW_MS = 864e5;
28319
28535
  }
28320
28536
  });
28321
28537
 
28322
28538
  // src/security-utils.ts
28323
- import { resolve as resolve28, normalize } from "path";
28539
+ import { resolve as resolve30, normalize } from "path";
28324
28540
  function ensureWithinRoot(filePath, projectRoot) {
28325
- const resolvedRoot = resolve28(projectRoot);
28326
- const resolvedPath = resolve28(resolvedRoot, filePath);
28541
+ const resolvedRoot = resolve30(projectRoot);
28542
+ const resolvedPath = resolve30(resolvedRoot, filePath);
28327
28543
  const normalizedPath = normalize(resolvedPath);
28328
28544
  const normalizedRoot = normalize(resolvedRoot);
28329
28545
  if (!normalizedPath.startsWith(normalizedRoot + "/") && normalizedPath !== normalizedRoot) {
@@ -28395,9 +28611,125 @@ var init_rules = __esm({
28395
28611
  }
28396
28612
  });
28397
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
+
28398
28730
  // src/import-resolver.ts
28399
28731
  import { readFileSync as readFileSync41, existsSync as existsSync55, statSync as statSync19 } from "fs";
28400
- 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";
28401
28733
  function parseImports(source) {
28402
28734
  const imports = [];
28403
28735
  const lines = source.split("\n");
@@ -28453,24 +28785,24 @@ function resolveImportPath(specifier, fromFile) {
28453
28785
  let basePath;
28454
28786
  if (specifier.startsWith("@/")) {
28455
28787
  const paths = getResolvedPaths();
28456
- basePath = resolve29(paths.pathAlias["@"] ?? paths.srcDir, specifier.slice(2));
28788
+ basePath = resolve31(paths.pathAlias["@"] ?? paths.srcDir, specifier.slice(2));
28457
28789
  } else {
28458
- basePath = resolve29(dirname25(fromFile), specifier);
28790
+ basePath = resolve31(dirname25(fromFile), specifier);
28459
28791
  }
28460
28792
  if (existsSync55(basePath) && !isDirectory(basePath)) {
28461
- return toRelative(basePath);
28793
+ return toRelative2(basePath);
28462
28794
  }
28463
28795
  const resolvedPaths = getResolvedPaths();
28464
28796
  for (const ext of resolvedPaths.extensions) {
28465
28797
  const withExt = basePath + ext;
28466
28798
  if (existsSync55(withExt)) {
28467
- return toRelative(withExt);
28799
+ return toRelative2(withExt);
28468
28800
  }
28469
28801
  }
28470
28802
  for (const indexFile of resolvedPaths.indexFiles) {
28471
28803
  const indexPath = join30(basePath, indexFile);
28472
28804
  if (existsSync55(indexPath)) {
28473
- return toRelative(indexPath);
28805
+ return toRelative2(indexPath);
28474
28806
  }
28475
28807
  }
28476
28808
  return null;
@@ -28482,7 +28814,7 @@ function isDirectory(path) {
28482
28814
  return false;
28483
28815
  }
28484
28816
  }
28485
- function toRelative(absPath) {
28817
+ function toRelative2(absPath) {
28486
28818
  const root = getProjectRoot();
28487
28819
  if (absPath.startsWith(root)) {
28488
28820
  return absPath.slice(root.length + 1);
@@ -28490,7 +28822,15 @@ function toRelative(absPath) {
28490
28822
  return absPath;
28491
28823
  }
28492
28824
  function buildImportIndex(dataDb, codegraphDb) {
28493
- 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
+ }
28494
28834
  dataDb.exec(`DELETE FROM ${t("imports")}`);
28495
28835
  const insertStmt = dataDb.prepare(
28496
28836
  `INSERT INTO ${t("imports")} (source_file, target_file, import_type, imported_names, line) VALUES (?, ?, ?, ?, ?)`
@@ -28505,7 +28845,7 @@ function buildImportIndex(dataDb, codegraphDb) {
28505
28845
  const batchSize = 500;
28506
28846
  let batch = [];
28507
28847
  for (const file of files) {
28508
- const absPath = ensureWithinRoot(resolve29(projectRoot, file.path), projectRoot);
28848
+ const absPath = ensureWithinRoot(resolve31(projectRoot, file.path), projectRoot);
28509
28849
  if (!existsSync55(absPath)) continue;
28510
28850
  let source;
28511
28851
  try {
@@ -28542,12 +28882,13 @@ var init_import_resolver = __esm({
28542
28882
  init_config();
28543
28883
  init_security_utils();
28544
28884
  init_sql_table_names();
28885
+ init_source_layout();
28545
28886
  }
28546
28887
  });
28547
28888
 
28548
28889
  // src/trpc-index.ts
28549
28890
  import { readFileSync as readFileSync42, existsSync as existsSync56, readdirSync as readdirSync20 } from "fs";
28550
- import { resolve as resolve30, join as join31 } from "path";
28891
+ import { resolve as resolve32, join as join31 } from "path";
28551
28892
  function parseRootRouter() {
28552
28893
  const paths = getResolvedPaths();
28553
28894
  const rootPath = paths.rootRouterPath;
@@ -28562,7 +28903,7 @@ function parseRootRouter() {
28562
28903
  while ((match = importRegex.exec(source)) !== null) {
28563
28904
  const variable = match[1];
28564
28905
  let filePath = match[2];
28565
- const fullPath = resolve30(paths.routersDir, filePath);
28906
+ const fullPath = resolve32(paths.routersDir, filePath);
28566
28907
  for (const ext of [".ts", ".tsx", ""]) {
28567
28908
  const candidate = fullPath + ext;
28568
28909
  const routersRelPath = getConfig().paths.routers ?? "src/server/api/routers";
@@ -28590,7 +28931,7 @@ function parseRootRouter() {
28590
28931
  return mappings;
28591
28932
  }
28592
28933
  function extractProcedures(routerFilePath) {
28593
- const absPath = resolve30(getProjectRoot(), routerFilePath);
28934
+ const absPath = resolve32(getProjectRoot(), routerFilePath);
28594
28935
  if (!existsSync56(absPath)) return [];
28595
28936
  const source = readFileSync42(absPath, "utf-8");
28596
28937
  const procedures = [];
@@ -28615,9 +28956,9 @@ function findUICallSites(routerKey, procedureName) {
28615
28956
  const root = getProjectRoot();
28616
28957
  const src = config.paths.source;
28617
28958
  const searchDirs = [
28618
- resolve30(root, config.paths.pages ?? src + "/app"),
28619
- resolve30(root, config.paths.components ?? src + "/components"),
28620
- 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")
28621
28962
  ];
28622
28963
  const searchPattern = `api.${routerKey}.${procedureName}`;
28623
28964
  for (const dir of searchDirs) {
@@ -28700,9 +29041,11 @@ var init_trpc_index = __esm({
28700
29041
 
28701
29042
  // src/page-deps.ts
28702
29043
  import { readFileSync as readFileSync43, existsSync as existsSync57 } from "fs";
28703
- import { resolve as resolve31 } from "path";
29044
+ import { resolve as resolve33 } from "path";
28704
29045
  function deriveRoute(pageFile) {
28705
- 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?$/, "");
28706
29049
  return route || "/";
28707
29050
  }
28708
29051
  function derivePortal(route) {
@@ -28729,7 +29072,7 @@ function traceImports(startFile, dataDb, visited, components, hooks, maxDepth =
28729
29072
  if (target.includes("/hooks/") || target.match(/use[A-Z]/)) {
28730
29073
  hooks.add(target);
28731
29074
  }
28732
- if (target.startsWith("src/")) {
29075
+ if (isUnderSourceDir(target)) {
28733
29076
  traceImports(target, dataDb, visited, components, hooks, maxDepth - 1);
28734
29077
  }
28735
29078
  }
@@ -28738,7 +29081,7 @@ function findRouterCalls(files) {
28738
29081
  const routers = /* @__PURE__ */ new Set();
28739
29082
  const projectRoot = getProjectRoot();
28740
29083
  for (const file of files) {
28741
- const absPath = ensureWithinRoot(resolve31(projectRoot, file), projectRoot);
29084
+ const absPath = ensureWithinRoot(resolve33(projectRoot, file), projectRoot);
28742
29085
  if (!existsSync57(absPath)) continue;
28743
29086
  try {
28744
29087
  const source = readFileSync43(absPath, "utf-8");
@@ -28759,7 +29102,7 @@ function findTablesFromRouters(routerNames, dataDb) {
28759
29102
  `SELECT DISTINCT router_file FROM ${t("trpc_procedures")} WHERE router_name = ? LIMIT 10000`
28760
29103
  ).all(routerName);
28761
29104
  for (const proc of procs) {
28762
- const absPath = ensureWithinRoot(resolve31(getProjectRoot(), proc.router_file), getProjectRoot());
29105
+ const absPath = ensureWithinRoot(resolve33(getProjectRoot(), proc.router_file), getProjectRoot());
28763
29106
  if (!existsSync57(absPath)) continue;
28764
29107
  try {
28765
29108
  const source = readFileSync43(absPath, "utf-8");
@@ -28777,10 +29120,12 @@ function findTablesFromRouters(routerNames, dataDb) {
28777
29120
  return [...tables];
28778
29121
  }
28779
29122
  function buildPageDeps(dataDb, codegraphDb) {
29123
+ assertSourceCandidateSet(codegraphDb, "buildPageDeps");
28780
29124
  dataDb.exec(`DELETE FROM ${t("page_deps")}`);
29125
+ const pagesPred = pagesPredicate();
28781
29126
  const pages = codegraphDb.prepare(
28782
- "SELECT path FROM files WHERE path LIKE 'src/app/%/page.tsx' OR path = 'src/app/page.tsx' LIMIT 10000"
28783
- ).all();
29127
+ `SELECT path FROM files WHERE ${pagesPred.sql} LIMIT 10000`
29128
+ ).all(...pagesPred.params);
28784
29129
  const insertStmt = dataDb.prepare(
28785
29130
  `INSERT INTO ${t("page_deps")} (page_file, route, portal, components, hooks, routers, tables_touched) VALUES (?, ?, ?, ?, ?, ?, ?)`
28786
29131
  );
@@ -28875,6 +29220,7 @@ var init_page_deps = __esm({
28875
29220
  init_config();
28876
29221
  init_security_utils();
28877
29222
  init_sql_table_names();
29223
+ init_source_layout();
28878
29224
  }
28879
29225
  });
28880
29226
 
@@ -28893,7 +29239,7 @@ function buildMiddlewareTree(dataDb) {
28893
29239
  `SELECT target_file FROM ${t("imports")} WHERE source_file = ? LIMIT 10000`
28894
29240
  ).all(current);
28895
29241
  for (const imp of imports) {
28896
- if (!visited.has(imp.target_file) && imp.target_file.startsWith("src/")) {
29242
+ if (!visited.has(imp.target_file) && isUnderSourceDir(imp.target_file)) {
28897
29243
  visited.add(imp.target_file);
28898
29244
  queue.push(imp.target_file);
28899
29245
  }
@@ -28917,6 +29263,7 @@ var init_middleware_tree = __esm({
28917
29263
  "use strict";
28918
29264
  init_config();
28919
29265
  init_sql_table_names();
29266
+ init_source_layout();
28920
29267
  }
28921
29268
  });
28922
29269
 
@@ -28974,12 +29321,11 @@ function globMatchSimple(name, pattern) {
28974
29321
  }
28975
29322
  function findCrossDomainImports(dataDb) {
28976
29323
  const domains = getDomains();
28977
- const config = getConfig();
28978
- const srcPrefix = config.paths.source;
28979
- const srcPattern = srcPrefix + "/%";
29324
+ const srcSource = sourceDirPredicate("source_file");
29325
+ const srcTarget = sourceDirPredicate("target_file");
28980
29326
  const imports = dataDb.prepare(
28981
- `SELECT source_file, target_file FROM ${t("imports")} WHERE source_file LIKE ? AND target_file LIKE ? LIMIT 10000`
28982
- ).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);
28983
29329
  const crossings = [];
28984
29330
  for (const imp of imports) {
28985
29331
  const sourceDomain = classifyFile(imp.source_file);
@@ -29004,10 +29350,9 @@ function getFilesInDomain(dataDb, codegraphDb, domainName) {
29004
29350
  const config = getConfig();
29005
29351
  const domain = domains.find((d2) => d2.name === domainName);
29006
29352
  if (!domain) return { routers: [], pages: [], components: [] };
29007
- const srcPrefix = config.paths.source;
29008
29353
  const routersPath = config.paths.routers ?? "src/server/api/routers";
29009
- const srcPattern = srcPrefix + "/%";
29010
- 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);
29011
29356
  const routers = [];
29012
29357
  const pages = [];
29013
29358
  const components = [];
@@ -29030,6 +29375,7 @@ var init_domains = __esm({
29030
29375
  init_rules();
29031
29376
  init_config();
29032
29377
  init_sql_table_names();
29378
+ init_source_layout();
29033
29379
  }
29034
29380
  });
29035
29381
 
@@ -29315,11 +29661,11 @@ var init_import_parser = __esm({
29315
29661
 
29316
29662
  // src/python/import-resolver.ts
29317
29663
  import { readFileSync as readFileSync45, existsSync as existsSync59, readdirSync as readdirSync22 } from "fs";
29318
- 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";
29319
29665
  function resolvePythonModulePath(module, fromFile, pythonRoot, level) {
29320
29666
  const projectRoot = getProjectRoot();
29321
29667
  if (level > 0) {
29322
- let baseDir = dirname26(resolve33(projectRoot, fromFile));
29668
+ let baseDir = dirname26(resolve35(projectRoot, fromFile));
29323
29669
  for (let i = 1; i < level; i++) {
29324
29670
  baseDir = dirname26(baseDir);
29325
29671
  }
@@ -29331,18 +29677,18 @@ function resolvePythonModulePath(module, fromFile, pythonRoot, level) {
29331
29677
  return tryResolvePythonPath(baseDir, projectRoot);
29332
29678
  }
29333
29679
  const parts = module.split(".");
29334
- const candidate = join33(resolve33(projectRoot, pythonRoot), ...parts);
29680
+ const candidate = join33(resolve35(projectRoot, pythonRoot), ...parts);
29335
29681
  return tryResolvePythonPath(candidate, projectRoot);
29336
29682
  }
29337
29683
  function tryResolvePythonPath(basePath, projectRoot) {
29338
29684
  if (existsSync59(basePath + ".py")) {
29339
- return relative8(projectRoot, basePath + ".py");
29685
+ return relative9(projectRoot, basePath + ".py");
29340
29686
  }
29341
29687
  if (existsSync59(join33(basePath, "__init__.py"))) {
29342
- return relative8(projectRoot, join33(basePath, "__init__.py"));
29688
+ return relative9(projectRoot, join33(basePath, "__init__.py"));
29343
29689
  }
29344
29690
  if (basePath.endsWith(".py") && existsSync59(basePath)) {
29345
- return relative8(projectRoot, basePath);
29691
+ return relative9(projectRoot, basePath);
29346
29692
  }
29347
29693
  return null;
29348
29694
  }
@@ -29364,7 +29710,7 @@ function walkPythonFiles(dir, excludeDirs) {
29364
29710
  }
29365
29711
  function buildPythonImportIndex(dataDb, pythonRoot, excludeDirs = ["__pycache__", ".venv", "venv", ".mypy_cache", ".pytest_cache"]) {
29366
29712
  const projectRoot = getProjectRoot();
29367
- const absRoot = resolve33(projectRoot, pythonRoot);
29713
+ const absRoot = resolve35(projectRoot, pythonRoot);
29368
29714
  dataDb.exec(`DELETE FROM ${t("py_imports")}`);
29369
29715
  const insertStmt = dataDb.prepare(
29370
29716
  `INSERT INTO ${t("py_imports")} (source_file, target_file, import_type, imported_names, line) VALUES (?, ?, ?, ?, ?)`
@@ -29378,7 +29724,7 @@ function buildPythonImportIndex(dataDb, pythonRoot, excludeDirs = ["__pycache__"
29378
29724
  });
29379
29725
  const batch = [];
29380
29726
  for (const absFile of files) {
29381
- const relFile = relative8(projectRoot, absFile);
29727
+ const relFile = relative9(projectRoot, absFile);
29382
29728
  let source;
29383
29729
  try {
29384
29730
  source = readFileSync45(absFile, "utf-8");
@@ -29649,7 +29995,7 @@ var init_route_parser = __esm({
29649
29995
 
29650
29996
  // src/python/route-indexer.ts
29651
29997
  import { readFileSync as readFileSync46, readdirSync as readdirSync23 } from "fs";
29652
- import { join as join34, relative as relative9 } from "path";
29998
+ import { join as join34, relative as relative10 } from "path";
29653
29999
  function walkPyFiles(dir, excludeDirs) {
29654
30000
  const files = [];
29655
30001
  try {
@@ -29678,7 +30024,7 @@ function buildPythonRouteIndex(dataDb, pythonRoot, excludeDirs = ["__pycache__",
29678
30024
  let count = 0;
29679
30025
  dataDb.transaction(() => {
29680
30026
  for (const absFile of files) {
29681
- const relFile = relative9(projectRoot, absFile);
30027
+ const relFile = relative10(projectRoot, absFile);
29682
30028
  let source;
29683
30029
  try {
29684
30030
  source = readFileSync46(absFile, "utf-8");
@@ -29894,7 +30240,7 @@ var init_model_parser = __esm({
29894
30240
 
29895
30241
  // src/python/model-indexer.ts
29896
30242
  import { readFileSync as readFileSync47, readdirSync as readdirSync24 } from "fs";
29897
- import { join as join35, relative as relative10 } from "path";
30243
+ import { join as join35, relative as relative11 } from "path";
29898
30244
  function walkPyFiles2(dir, excludeDirs) {
29899
30245
  const files = [];
29900
30246
  try {
@@ -29926,7 +30272,7 @@ function buildPythonModelIndex(dataDb, pythonRoot, excludeDirs = ["__pycache__",
29926
30272
  let count = 0;
29927
30273
  dataDb.transaction(() => {
29928
30274
  for (const absFile of files) {
29929
- const relFile = relative10(projectRoot, absFile);
30275
+ const relFile = relative11(projectRoot, absFile);
29930
30276
  let source;
29931
30277
  try {
29932
30278
  source = readFileSync47(absFile, "utf-8");
@@ -30191,7 +30537,7 @@ var init_migration_parser = __esm({
30191
30537
 
30192
30538
  // src/python/migration-indexer.ts
30193
30539
  import { readFileSync as readFileSync48, readdirSync as readdirSync25 } from "fs";
30194
- import { join as join36, relative as relative11 } from "path";
30540
+ import { join as join36, relative as relative12 } from "path";
30195
30541
  function buildPythonMigrationIndex(dataDb, alembicDir) {
30196
30542
  const projectRoot = getProjectRoot();
30197
30543
  const absDir = join36(projectRoot, alembicDir);
@@ -30227,7 +30573,7 @@ function buildPythonMigrationIndex(dataDb, alembicDir) {
30227
30573
  rows.push({
30228
30574
  revision: parsed.revision,
30229
30575
  downRevision: parsed.downRevision,
30230
- file: relative11(projectRoot, absFile),
30576
+ file: relative12(projectRoot, absFile),
30231
30577
  description: parsed.description,
30232
30578
  operations: JSON.stringify(parsed.operations)
30233
30579
  });
@@ -30252,7 +30598,7 @@ var init_migration_indexer = __esm({
30252
30598
 
30253
30599
  // src/python/coupling-detector.ts
30254
30600
  import { readFileSync as readFileSync49, readdirSync as readdirSync26 } from "fs";
30255
- import { join as join37, relative as relative12 } from "path";
30601
+ import { join as join37, relative as relative13 } from "path";
30256
30602
  function buildPythonCouplingIndex(dataDb) {
30257
30603
  const projectRoot = getProjectRoot();
30258
30604
  const config = getConfig();
@@ -30285,7 +30631,7 @@ function buildPythonCouplingIndex(dataDb) {
30285
30631
  ];
30286
30632
  dataDb.transaction(() => {
30287
30633
  for (const absFile of frontendFiles) {
30288
- const relFile = relative12(projectRoot, absFile);
30634
+ const relFile = relative13(projectRoot, absFile);
30289
30635
  let source;
30290
30636
  try {
30291
30637
  source = readFileSync49(absFile, "utf-8");
@@ -30695,7 +31041,7 @@ var init_memory_tools = __esm({
30695
31041
 
30696
31042
  // src/docs-tools.ts
30697
31043
  import { readFileSync as readFileSync50, existsSync as existsSync60 } from "fs";
30698
- import { resolve as resolve34, basename as basename9 } from "path";
31044
+ import { resolve as resolve36, basename as basename9 } from "path";
30699
31045
  function p4(baseName) {
30700
31046
  return `${getConfig().toolPrefix}_${baseName}`;
30701
31047
  }
@@ -30823,9 +31169,9 @@ function extractFrontmatter(content) {
30823
31169
  }
30824
31170
  function extractProcedureNames(routerPath) {
30825
31171
  const root = getProjectRoot();
30826
- const absPath = ensureWithinRoot(resolve34(getResolvedPaths().srcDir, "..", routerPath), root);
31172
+ const absPath = ensureWithinRoot(resolve36(getResolvedPaths().srcDir, "..", routerPath), root);
30827
31173
  if (!existsSync60(absPath)) {
30828
- 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);
30829
31175
  if (!existsSync60(altPath)) return [];
30830
31176
  return extractProcedureNamesFromContent(readFileSync50(altPath, "utf-8"));
30831
31177
  }
@@ -30869,7 +31215,7 @@ function handleDocsAudit(args2) {
30869
31215
  for (const [mappingId, triggeringFiles] of affectedMappings) {
30870
31216
  const mapping = docsMap.mappings.find((m3) => m3.id === mappingId);
30871
31217
  if (!mapping) continue;
30872
- const helpPagePath = ensureWithinRoot(resolve34(getResolvedPaths().helpSitePath, mapping.helpPage), getProjectRoot());
31218
+ const helpPagePath = ensureWithinRoot(resolve36(getResolvedPaths().helpSitePath, mapping.helpPage), getProjectRoot());
30873
31219
  if (!existsSync60(helpPagePath)) {
30874
31220
  results.push({
30875
31221
  helpPage: mapping.helpPage,
@@ -30922,7 +31268,7 @@ function handleDocsAudit(args2) {
30922
31268
  });
30923
31269
  for (const [guideName, parentId] of Object.entries(docsMap.userGuideInheritance.examples)) {
30924
31270
  if (parentId === mappingId) {
30925
- 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());
30926
31272
  if (existsSync60(guidePath)) {
30927
31273
  const guideContent = readFileSync50(guidePath, "utf-8");
30928
31274
  const guideFrontmatter = extractFrontmatter(guideContent);
@@ -30957,7 +31303,7 @@ function handleDocsCoverage(args2) {
30957
31303
  const gaps = [];
30958
31304
  const mappings = filterDomain ? docsMap.mappings.filter((m3) => m3.id === filterDomain) : docsMap.mappings;
30959
31305
  for (const mapping of mappings) {
30960
- const helpPagePath = ensureWithinRoot(resolve34(getResolvedPaths().helpSitePath, mapping.helpPage), getProjectRoot());
31306
+ const helpPagePath = ensureWithinRoot(resolve36(getResolvedPaths().helpSitePath, mapping.helpPage), getProjectRoot());
30961
31307
  const exists = existsSync60(helpPagePath);
30962
31308
  let hasContent = false;
30963
31309
  let lineCount = 0;
@@ -31306,7 +31652,7 @@ var init_observability_tools = __esm({
31306
31652
 
31307
31653
  // src/sentinel-db.ts
31308
31654
  import { existsSync as existsSync61 } from "fs";
31309
- import { resolve as resolve35 } from "path";
31655
+ import { resolve as resolve37 } from "path";
31310
31656
  function parsePortalScope(raw) {
31311
31657
  if (!raw) return [];
31312
31658
  try {
@@ -31542,22 +31888,22 @@ function validateFeatures(db, domainFilter) {
31542
31888
  const missingProcedures = [];
31543
31889
  const missingPages = [];
31544
31890
  for (const comp of components) {
31545
- const absPath = resolve35(PROJECT_ROOT, comp.component_file);
31891
+ const absPath = resolve37(PROJECT_ROOT, comp.component_file);
31546
31892
  if (!existsSync61(absPath)) {
31547
31893
  missingComponents.push(comp.component_file);
31548
31894
  }
31549
31895
  }
31550
31896
  for (const proc of procedures) {
31551
- 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`);
31552
31898
  if (!existsSync61(routerPath)) {
31553
31899
  missingProcedures.push({ router: proc.router_name, procedure: proc.procedure_name });
31554
31900
  }
31555
31901
  }
31556
31902
  for (const page of pages) {
31557
31903
  const routeToPath = page.page_route.replace(/^\/(portal-[^/]+\/)?/, "src/app/").replace(/\/$/, "") + "/page.tsx";
31558
- const absPath = resolve35(PROJECT_ROOT, routeToPath);
31904
+ const absPath = resolve37(PROJECT_ROOT, routeToPath);
31559
31905
  if (page.page_route.startsWith("/") && !existsSync61(absPath)) {
31560
- 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`);
31561
31907
  if (!existsSync61(altPath)) {
31562
31908
  missingPages.push(page.page_route);
31563
31909
  }
@@ -32085,7 +32431,7 @@ var init_sentinel_tools = __esm({
32085
32431
 
32086
32432
  // src/sentinel-scanner.ts
32087
32433
  import { readFileSync as readFileSync51, existsSync as existsSync62, readdirSync as readdirSync27, statSync as statSync20 } from "fs";
32088
- 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";
32089
32435
  function inferDomain(filePath) {
32090
32436
  const domains = getConfig().domains;
32091
32437
  const path = filePath.toLowerCase();
@@ -32216,7 +32562,7 @@ function scanComponentExports(dataDb) {
32216
32562
  const projectRoot = getProjectRoot();
32217
32563
  const componentsBase = config.paths.components ?? config.paths.source + "/components";
32218
32564
  const componentDirs = [];
32219
- const basePath = resolve36(projectRoot, componentsBase);
32565
+ const basePath = resolve38(projectRoot, componentsBase);
32220
32566
  if (existsSync62(basePath)) {
32221
32567
  try {
32222
32568
  const entries = readdirSync27(basePath, { withFileTypes: true });
@@ -32229,11 +32575,11 @@ function scanComponentExports(dataDb) {
32229
32575
  }
32230
32576
  }
32231
32577
  for (const dir of componentDirs) {
32232
- const absDir = resolve36(projectRoot, dir);
32578
+ const absDir = resolve38(projectRoot, dir);
32233
32579
  if (!existsSync62(absDir)) continue;
32234
32580
  const files = walkDir2(absDir).filter((f2) => f2.endsWith(".tsx") || f2.endsWith(".ts"));
32235
32581
  for (const file of files) {
32236
- const relPath = relative13(projectRoot, file);
32582
+ const relPath = relative14(projectRoot, file);
32237
32583
  const source = readFileSync51(file, "utf-8");
32238
32584
  const annotations = parseFeatureAnnotations(source);
32239
32585
  if (annotations.length > 0) {
@@ -33814,7 +34160,7 @@ var init_security_scorer = __esm({
33814
34160
 
33815
34161
  // src/dependency-scorer.ts
33816
34162
  import { existsSync as existsSync65, readFileSync as readFileSync54 } from "fs";
33817
- import { resolve as resolve37 } from "path";
34163
+ import { resolve as resolve39 } from "path";
33818
34164
  function p13(baseName) {
33819
34165
  return `${getConfig().toolPrefix}_${baseName}`;
33820
34166
  }
@@ -33846,7 +34192,7 @@ function calculateDepRisk(factors) {
33846
34192
  return Math.min(100, risk);
33847
34193
  }
33848
34194
  function getInstalledPackages(projectRoot) {
33849
- const pkgPath = resolve37(projectRoot, "package.json");
34195
+ const pkgPath = resolve39(projectRoot, "package.json");
33850
34196
  if (!existsSync65(pkgPath)) return /* @__PURE__ */ new Map();
33851
34197
  try {
33852
34198
  const pkg = JSON.parse(readFileSync54(pkgPath, "utf-8"));
@@ -34456,7 +34802,7 @@ var init_regression_detector = __esm({
34456
34802
  // src/knowledge-indexer.ts
34457
34803
  import { createHash as createHash16 } from "crypto";
34458
34804
  import { readFileSync as readFileSync55, readdirSync as readdirSync28, statSync as statSync21, existsSync as existsSync66 } from "fs";
34459
- 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";
34460
34806
  function getKnowledgePaths() {
34461
34807
  const resolved = getResolvedPaths();
34462
34808
  const config = getConfig();
@@ -34484,7 +34830,7 @@ function discoverMarkdownFiles(baseDir) {
34484
34830
  try {
34485
34831
  const entries = readdirSync28(dir, { withFileTypes: true });
34486
34832
  for (const entry of entries) {
34487
- const fullPath = resolve38(dir, entry.name);
34833
+ const fullPath = resolve40(dir, entry.name);
34488
34834
  if (entry.isDirectory()) {
34489
34835
  if (entry.name === "archive" && dir.includes("session-state")) continue;
34490
34836
  if (entry.name === "archive" && dir.includes("status")) continue;
@@ -34504,7 +34850,7 @@ function categorizeFile(filePath) {
34504
34850
  const paths = getKnowledgePaths();
34505
34851
  if (filePath.startsWith(paths.plansDir)) return "plan";
34506
34852
  if (filePath.startsWith(paths.docsDir)) {
34507
- const relFromDocs = relative14(paths.docsDir, filePath).replace(/\\/g, "/").toLowerCase();
34853
+ const relFromDocs = relative15(paths.docsDir, filePath).replace(/\\/g, "/").toLowerCase();
34508
34854
  if (relFromDocs.startsWith("plans/")) return "plan";
34509
34855
  if (relFromDocs.includes("architecture")) return "architecture";
34510
34856
  if (relFromDocs.includes("security")) return "security";
@@ -34520,7 +34866,7 @@ function categorizeFile(filePath) {
34520
34866
  }
34521
34867
  const claudeDirName = getConfig().conventions?.claudeDirName ?? ".claude";
34522
34868
  if (filePath.includes(`${claudeDirName}/projects/`) && filePath.includes("/memory/")) return "memory";
34523
- const rel = relative14(paths.claudeDir, filePath).replace(/\\/g, "/");
34869
+ const rel = relative15(paths.claudeDir, filePath).replace(/\\/g, "/");
34524
34870
  const firstDir = rel.split("/")[0];
34525
34871
  const knownCategories = getConfig().conventions?.knowledgeCategories ?? [
34526
34872
  "patterns",
@@ -34813,7 +35159,7 @@ function indexAllKnowledge(db) {
34813
35159
  if (!existsSync66(filePath)) continue;
34814
35160
  const content = readFileSync55(filePath, "utf-8");
34815
35161
  const hash = hashContent2(content);
34816
- 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);
34817
35163
  const category = categorizeFile(filePath);
34818
35164
  const title = extractTitle(content, filePath);
34819
35165
  const description = extractDescription2(content);
@@ -34978,8 +35324,8 @@ var init_knowledge_indexer = __esm({
34978
35324
  });
34979
35325
 
34980
35326
  // src/knowledge-tools.ts
34981
- import { readFileSync as readFileSync56, writeFileSync as writeFileSync16, appendFileSync as appendFileSync5, readdirSync as readdirSync29 } from "fs";
34982
- 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";
34983
35329
  function p16(baseName) {
34984
35330
  return `${getConfig().toolPrefix}_${baseName}`;
34985
35331
  }
@@ -34997,10 +35343,19 @@ function ensureKnowledgeIndexed(db) {
34997
35343
  try {
34998
35344
  indexIfStale(db);
34999
35345
  return true;
35000
- } catch {
35346
+ } catch (err) {
35347
+ recordHookFailure("knowledge-tools:ensure-indexed", err, {
35348
+ note: "knowledge index refresh failed; dispatch continued against the existing index"
35349
+ });
35001
35350
  return false;
35002
35351
  }
35003
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
+ }
35004
35359
  function hasData(db) {
35005
35360
  const row = db.prepare("SELECT COUNT(*) as cnt FROM knowledge_documents").get();
35006
35361
  return (row?.cnt ?? 0) > 0;
@@ -35181,8 +35536,15 @@ function isKnowledgeTool(name) {
35181
35536
  const baseName = name.startsWith(pfx) ? name.slice(pfx.length) : name;
35182
35537
  return KNOWLEDGE_BASE_NAMES.has(baseName);
35183
35538
  }
35184
- function handleKnowledgeToolCall(name, args2, db) {
35185
- 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) {
35186
35548
  if (!hasData(db)) {
35187
35549
  return text15("No knowledge indexed yet. The knowledge DB will be populated automatically on next MCP server restart.");
35188
35550
  }
@@ -35718,7 +36080,7 @@ function handleCorrect(db, args2) {
35718
36080
  if (!wrong || !correction || !rule) {
35719
36081
  return text15("Error: wrong, correction, and rule are all required.");
35720
36082
  }
35721
- const correctionsPath = resolve39(getResolvedPaths().memoryDir, "corrections.md");
36083
+ const correctionsPath = resolve41(getResolvedPaths().memoryDir, "corrections.md");
35722
36084
  const today = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
35723
36085
  const title = rule.slice(0, 60);
35724
36086
  const entry = formatCorrectionEntry({
@@ -35738,7 +36100,7 @@ function handleCorrect(db, args2) {
35738
36100
  if (archiveIdx > 0) {
35739
36101
  const before = existing.slice(0, archiveIdx);
35740
36102
  const after = existing.slice(archiveIdx);
35741
- writeFileSync16(correctionsPath, before + entry + after);
36103
+ writeFileSync17(correctionsPath, before + entry + after);
35742
36104
  } else {
35743
36105
  appendFileSync5(correctionsPath, entry);
35744
36106
  }
@@ -36052,6 +36414,7 @@ var init_knowledge_tools = __esm({
36052
36414
  init_db2();
36053
36415
  init_memory_db();
36054
36416
  init_sql_table_names();
36417
+ init_hook_failure_signal();
36055
36418
  KNOWLEDGE_BASE_NAMES = /* @__PURE__ */ new Set([
36056
36419
  "knowledge_search",
36057
36420
  "knowledge_rule",
@@ -36688,7 +37051,7 @@ var init_framework_supports = __esm({
36688
37051
 
36689
37052
  // src/tools.ts
36690
37053
  import { readFileSync as readFileSync57, existsSync as existsSync68 } from "fs";
36691
- import { resolve as resolve40, basename as basename13 } from "path";
37054
+ import { resolve as resolve42, basename as basename13 } from "path";
36692
37055
  function prefix2() {
36693
37056
  return getConfig().toolPrefix;
36694
37057
  }
@@ -36723,7 +37086,7 @@ function ensureIndexes(dataDb, codegraphDb, force = false) {
36723
37086
  if (config.python?.root) {
36724
37087
  const pythonRoot = config.python.root;
36725
37088
  const excludeDirs = config.python.exclude_dirs || ["__pycache__", ".venv", "venv", ".mypy_cache", ".pytest_cache"];
36726
- if (force || isPythonDataStale(dataDb, resolve40(getProjectRoot(), pythonRoot))) {
37089
+ if (force || isPythonDataStale(dataDb, resolve42(getProjectRoot(), pythonRoot))) {
36727
37090
  const pyImports = buildPythonImportIndex(dataDb, pythonRoot, excludeDirs);
36728
37091
  results.push(`Python imports: ${pyImports}`);
36729
37092
  const pyRoutes = buildPythonRouteIndex(dataDb, pythonRoot, excludeDirs);
@@ -36946,7 +37309,7 @@ async function handleToolCall(name, args2, dataDb, codegraphDb, memoryDbCache, k
36946
37309
  return withMemDb((memDb) => handleRegressionToolCall(name, args2, memDb));
36947
37310
  }
36948
37311
  if (isKnowledgeTool(name)) {
36949
- return withKnowledgeDb((kdb) => handleKnowledgeToolCall(name, args2, kdb));
37312
+ return withKnowledgeDb((kdb) => handleKnowledgeToolCall(name, args2, kdb, "ensure-fresh"));
36950
37313
  }
36951
37314
  if (isPythonTool(name)) {
36952
37315
  const pyDataDb = assertDataDb(dataDb, name);
@@ -37106,7 +37469,7 @@ function handleContext(file, dataDb, codegraphDb) {
37106
37469
  try {
37107
37470
  const resolvedPaths = getResolvedPaths();
37108
37471
  const root = getProjectRoot();
37109
- const absFilePath = ensureWithinRoot(resolve40(resolvedPaths.srcDir, "..", file), root);
37472
+ const absFilePath = ensureWithinRoot(resolve42(resolvedPaths.srcDir, "..", file), root);
37110
37473
  if (existsSync68(absFilePath)) {
37111
37474
  const fileContent = readFileSync57(absFilePath, "utf-8").slice(0, 3e3);
37112
37475
  const keywords = [];
@@ -37345,9 +37708,10 @@ function handleCouplingCheck(args2, dataDb, codegraphDb) {
37345
37708
  lines.push("### Uncoupled Procedures: 0 (PASS)");
37346
37709
  lines.push("");
37347
37710
  }
37711
+ const pagesPred = pagesPredicate();
37348
37712
  const allPages = codegraphDb.prepare(
37349
- "SELECT path FROM files WHERE path LIKE 'src/app/%/page.tsx' OR path = 'src/app/page.tsx' LIMIT 10000"
37350
- ).all();
37713
+ `SELECT path FROM files WHERE ${pagesPred.sql} LIMIT 10000`
37714
+ ).all(...pagesPred.params);
37351
37715
  const pageImports = /* @__PURE__ */ new Set();
37352
37716
  for (const page of allPages) {
37353
37717
  const imports = dataDb.prepare(
@@ -37360,9 +37724,10 @@ function handleCouplingCheck(args2, dataDb, codegraphDb) {
37360
37724
  let componentFiles;
37361
37725
  if (stagedFiles) {
37362
37726
  const placeholders = stagedFiles.map(() => "?").join(",");
37727
+ const componentsPred = componentsPredicate();
37363
37728
  componentFiles = codegraphDb.prepare(
37364
- `SELECT path FROM files WHERE path LIKE 'src/components/%' AND path IN (${placeholders}) LIMIT 10000`
37365
- ).all(...stagedFiles);
37729
+ `SELECT path FROM files WHERE ${componentsPred.sql} AND path IN (${placeholders}) LIMIT 10000`
37730
+ ).all(...componentsPred.params, ...stagedFiles);
37366
37731
  } else {
37367
37732
  componentFiles = [];
37368
37733
  }
@@ -37545,7 +37910,7 @@ function handleSchema(args2) {
37545
37910
  lines.push("Checking all column references against Prisma schema...");
37546
37911
  lines.push("");
37547
37912
  const projectRoot = getProjectRoot();
37548
- const absPath = ensureWithinRoot(resolve40(projectRoot, file), projectRoot);
37913
+ const absPath = ensureWithinRoot(resolve42(projectRoot, file), projectRoot);
37549
37914
  if (!existsSync68(absPath)) {
37550
37915
  return text17(`File not found: ${file}`);
37551
37916
  }
@@ -37624,6 +37989,7 @@ var init_tools = __esm({
37624
37989
  init_framework_supports();
37625
37990
  init_license();
37626
37991
  init_sql_table_names();
37992
+ init_source_layout();
37627
37993
  }
37628
37994
  });
37629
37995
 
@@ -37982,7 +38348,7 @@ var init_startup_health = __esm({
37982
38348
  // src/server.ts
37983
38349
  var server_exports = {};
37984
38350
  import { readFileSync as readFileSync58 } from "fs";
37985
- import { resolve as resolve41, dirname as dirname28 } from "path";
38351
+ import { resolve as resolve43, dirname as dirname28 } from "path";
37986
38352
  import { fileURLToPath as fileURLToPath6 } from "url";
37987
38353
  function pruneMemoryOnStartup() {
37988
38354
  try {
@@ -38030,7 +38396,7 @@ var init_server = __esm({
38030
38396
  __dirname5 = dirname28(fileURLToPath6(import.meta.url));
38031
38397
  PKG_VERSION = (() => {
38032
38398
  try {
38033
- const pkg = JSON.parse(readFileSync58(resolve41(__dirname5, "..", "package.json"), "utf-8"));
38399
+ const pkg = JSON.parse(readFileSync58(resolve43(__dirname5, "..", "package.json"), "utf-8"));
38034
38400
  return pkg.version ?? "0.0.0";
38035
38401
  } catch {
38036
38402
  return "0.0.0";
@@ -38333,12 +38699,12 @@ var config_upgrade_exports = {};
38333
38699
  __export(config_upgrade_exports, {
38334
38700
  runConfigUpgrade: () => runConfigUpgrade
38335
38701
  });
38336
- import { existsSync as existsSync69, readFileSync as readFileSync59, writeFileSync as writeFileSync17, copyFileSync as copyFileSync3, unlinkSync as unlinkSync6 } from "fs";
38337
- 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";
38338
38704
  import { parse as parseYaml6 } from "yaml";
38339
38705
  async function runConfigUpgrade(opts = {}) {
38340
38706
  const cwd = opts.cwd ?? process.cwd();
38341
- const configPath = resolve42(cwd, "massu.config.yaml");
38707
+ const configPath = resolve44(cwd, "massu.config.yaml");
38342
38708
  const bakPath = `${configPath}.bak`;
38343
38709
  const log = opts.silent ? () => {
38344
38710
  } : (s) => process.stdout.write(s);
@@ -38392,7 +38758,7 @@ async function runConfigUpgrade(opts = {}) {
38392
38758
  };
38393
38759
  try {
38394
38760
  const original = readFileSync59(configPath, "utf-8");
38395
- writeFileSync17(bakPath, original, "utf-8");
38761
+ writeFileSync18(bakPath, original, "utf-8");
38396
38762
  } catch (e2) {
38397
38763
  const message = `Failed to write backup: ${e2 instanceof Error ? e2.message : String(e2)}`;
38398
38764
  err(message + "\n");
@@ -38426,7 +38792,7 @@ __export(config_check_drift_exports, {
38426
38792
  runConfigCheckDrift: () => runConfigCheckDrift
38427
38793
  });
38428
38794
  import { existsSync as existsSync70, readFileSync as readFileSync60 } from "fs";
38429
- import { resolve as resolve43 } from "path";
38795
+ import { resolve as resolve45 } from "path";
38430
38796
  import { parse as parseYaml7 } from "yaml";
38431
38797
  function renderChanges(changes) {
38432
38798
  if (changes.length === 0) return "(none)\n";
@@ -38434,7 +38800,7 @@ function renderChanges(changes) {
38434
38800
  }
38435
38801
  async function runConfigCheckDrift(opts = {}) {
38436
38802
  const cwd = opts.cwd ?? process.cwd();
38437
- const configPath = resolve43(cwd, "massu.config.yaml");
38803
+ const configPath = resolve45(cwd, "massu.config.yaml");
38438
38804
  const log = opts.silent ? () => {
38439
38805
  } : (s) => process.stdout.write(s);
38440
38806
  const err = opts.silent ? () => {
@@ -38521,7 +38887,7 @@ var init_config_check_drift = __esm({
38521
38887
 
38522
38888
  // src/cli.ts
38523
38889
  import { readFileSync as readFileSync61 } from "fs";
38524
- import { resolve as resolve44, dirname as dirname29 } from "path";
38890
+ import { resolve as resolve46, dirname as dirname29 } from "path";
38525
38891
  import { fileURLToPath as fileURLToPath7 } from "url";
38526
38892
 
38527
38893
  // src/lib/node-bootstrap.ts
@@ -39023,7 +39389,7 @@ Examples:
39023
39389
  }
39024
39390
  function printVersion() {
39025
39391
  try {
39026
- const pkg = JSON.parse(readFileSync61(resolve44(__dirname6, "../package.json"), "utf-8"));
39392
+ const pkg = JSON.parse(readFileSync61(resolve46(__dirname6, "../package.json"), "utf-8"));
39027
39393
  console.log(`massu v${pkg.version}`);
39028
39394
  } catch {
39029
39395
  console.log("massu v0.1.0");