@wrongstack/tools 0.289.0 → 0.291.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (46) hide show
  1. package/dist/auto-proceed-loop-guard.d.ts +128 -0
  2. package/dist/auto-proceed-loop-guard.d.ts.map +1 -0
  3. package/dist/auto-proceed-loop-guard.js +46 -0
  4. package/dist/auto-proceed-loop-guard.js.map +7 -0
  5. package/dist/bash-kill-guard.d.ts +10 -1
  6. package/dist/bash-kill-guard.d.ts.map +1 -1
  7. package/dist/bash.js +140 -14
  8. package/dist/bash.js.map +2 -2
  9. package/dist/builtin.js +662 -203
  10. package/dist/builtin.js.map +4 -4
  11. package/dist/codebase-index/background-indexer.d.ts +1 -1
  12. package/dist/codebase-index/background-indexer.d.ts.map +1 -1
  13. package/dist/codebase-index/index.js +181 -104
  14. package/dist/codebase-index/index.js.map +3 -3
  15. package/dist/codebase-index/indexer.d.ts.map +1 -1
  16. package/dist/codebase-index/refs-extractor.d.ts +2 -17
  17. package/dist/codebase-index/refs-extractor.d.ts.map +1 -1
  18. package/dist/codebase-index/ts-parser.d.ts.map +1 -1
  19. package/dist/codebase-index/worker.js +164 -96
  20. package/dist/codebase-index/worker.js.map +3 -3
  21. package/dist/codebase-index/writer.d.ts +33 -0
  22. package/dist/codebase-index/writer.d.ts.map +1 -1
  23. package/dist/exec-kill-guard.d.ts +29 -0
  24. package/dist/exec-kill-guard.d.ts.map +1 -0
  25. package/dist/exec.d.ts.map +1 -1
  26. package/dist/exec.js +670 -10
  27. package/dist/exec.js.map +4 -4
  28. package/dist/index.d.ts +1 -0
  29. package/dist/index.d.ts.map +1 -1
  30. package/dist/index.js +765 -237
  31. package/dist/index.js.map +4 -4
  32. package/dist/kanban.d.ts +10 -0
  33. package/dist/kanban.d.ts.map +1 -1
  34. package/dist/kanban.js +34 -17
  35. package/dist/kanban.js.map +2 -2
  36. package/dist/pack.js +662 -203
  37. package/dist/pack.js.map +4 -4
  38. package/dist/plan.js.map +2 -2
  39. package/dist/read.d.ts.map +1 -1
  40. package/dist/read.js.map +2 -2
  41. package/dist/session-kanban.d.ts.map +1 -1
  42. package/dist/session-kanban.js +60 -3
  43. package/dist/session-kanban.js.map +2 -2
  44. package/dist/task.js.map +2 -2
  45. package/dist/todo.js.map +2 -2
  46. package/package.json +9 -5
package/dist/index.js CHANGED
@@ -709,8 +709,8 @@ var init_process_registry = __esm({
709
709
  if (p.killed) return true;
710
710
  if (p.protected) return false;
711
711
  const { force = false, graceMs = DEFAULT_GRACE_MS } = opts;
712
- const isWin4 = os.platform() === "win32";
713
- if (isWin4) {
712
+ const isWin5 = os.platform() === "win32";
713
+ if (isWin5) {
714
714
  const liveRealChild = p.child.exitCode === null && typeof p.child.pid === "number";
715
715
  const directFallback = () => {
716
716
  if (p.child.exitCode === null) {
@@ -2608,8 +2608,8 @@ async function scanDirectory(directory, depth, profiles, limits, state, extraIgn
2608
2608
  collectFileEvidence(directory, fullPath, entry.name, profiles, state);
2609
2609
  }
2610
2610
  }
2611
- function collectFileEvidence(directory, fullPath, basename10, profiles, state) {
2612
- const lower = basename10.toLowerCase();
2611
+ function collectFileEvidence(directory, fullPath, basename11, profiles, state) {
2612
+ const lower = basename11.toLowerCase();
2613
2613
  const extension = path4.extname(lower);
2614
2614
  for (const profile of profiles) {
2615
2615
  const detector = profile.detectors.find(
@@ -2620,7 +2620,7 @@ function collectFileEvidence(directory, fullPath, basename10, profiles, state) {
2620
2620
  candidate.evidence.push({
2621
2621
  kind: detector.kind,
2622
2622
  path: fullPath,
2623
- value: basename10,
2623
+ value: basename11,
2624
2624
  weight: detector.weight
2625
2625
  });
2626
2626
  if (detector.kind === "manifest" || detector.kind === "config") {
@@ -3937,15 +3937,15 @@ async function changedPaths(before, after, beforeSizes, afterSizes) {
3937
3937
  const beforeSet = new Set(before);
3938
3938
  const afterSet = new Set(after);
3939
3939
  const changed = /* @__PURE__ */ new Set();
3940
- for (const path33 of after) {
3941
- if (!beforeSet.has(path33)) changed.add(path33);
3940
+ for (const path34 of after) {
3941
+ if (!beforeSet.has(path34)) changed.add(path34);
3942
3942
  }
3943
- for (const path33 of before) {
3944
- if (!afterSet.has(path33)) changed.add(path33);
3943
+ for (const path34 of before) {
3944
+ if (!afterSet.has(path34)) changed.add(path34);
3945
3945
  }
3946
3946
  if (beforeSizes && afterSizes) {
3947
- for (const path33 of after) {
3948
- if (beforeSizes.get(path33) !== afterSizes.get(path33)) changed.add(path33);
3947
+ for (const path34 of after) {
3948
+ if (beforeSizes.get(path34) !== afterSizes.get(path34)) changed.add(path34);
3949
3949
  }
3950
3950
  }
3951
3951
  return [...changed].sort();
@@ -5103,8 +5103,8 @@ function normalizeShell(value) {
5103
5103
  if (v === "pwsh" || v === "pwsh.exe") return "pwsh";
5104
5104
  return void 0;
5105
5105
  }
5106
- function resolveSessionShell(platform5, env, deps = {}) {
5107
- if (platform5 !== "win32") return void 0;
5106
+ function resolveSessionShell(platform6, env, deps = {}) {
5107
+ if (platform6 !== "win32") return void 0;
5108
5108
  const override = normalizeShell(env.get("WRONGSTACK_SHELL"));
5109
5109
  if (override) return override;
5110
5110
  const hasBinary = deps.hasBinary ?? ((bin) => resolveWin32Command(bin) !== bin);
@@ -5114,11 +5114,11 @@ function resolveSessionShell(platform5, env, deps = {}) {
5114
5114
  }
5115
5115
  function ensureSessionShell(opts = {}) {
5116
5116
  const env = opts.env ?? process.env;
5117
- const platform5 = opts.platform ?? process.platform;
5118
- if (platform5 !== "win32") return void 0;
5117
+ const platform6 = opts.platform ?? process.platform;
5118
+ if (platform6 !== "win32") return void 0;
5119
5119
  const existing = normalizeShell(env["WRONGSTACK_SHELL"]);
5120
5120
  if (existing) return existing;
5121
- const chosen = resolveSessionShell(platform5, { get: (k) => env[k] }, { hasBinary: opts.hasBinary }) ?? "cmd";
5121
+ const chosen = resolveSessionShell(platform6, { get: (k) => env[k] }, { hasBinary: opts.hasBinary }) ?? "cmd";
5122
5122
  env["WRONGSTACK_SHELL"] = chosen;
5123
5123
  return chosen;
5124
5124
  }
@@ -5693,17 +5693,18 @@ function resetPersistentProcessRegistry() {
5693
5693
 
5694
5694
  // src/bash-kill-guard.ts
5695
5695
  var isWin2 = os3.platform() === "win32";
5696
+ var SCRIPT_KILL_RE = /^(?:\.\\|\.\/)?(?:kill|terminate|stop)\S*\.(?:ps1|bat|cmd|sh)(?:\s|$)/i;
5697
+ var SCRIPT_KILL_RE_POSIX = /^(?:\.\/)?(?:kill|terminate|stop)\S*\.sh(?:\s|$)/i;
5698
+ var SCRIPT_KILL_FALLBACK_RE = /^\S*(?:kill|terminate|stop)\S*\.(?:ps1|bat|cmd|sh)\b/i;
5696
5699
  function extractKillCommand(command) {
5697
5700
  const normalized = command.replace(/\s+/g, " ").trim();
5698
- const shellCMatch = normalized.match(
5699
- /^(?:\S+(?:\s+\S+)?)?\s+-c\s+['"](.+?)['"]$/
5700
- );
5701
- if (shellCMatch?.[1]) {
5702
- const inner = shellCMatch[1].trim();
5701
+ const shellCMatch = normalized.match(/^.+?\s+-c\s+(['"])([\s\S]+)\1$/);
5702
+ if (shellCMatch?.[2]) {
5703
+ const inner = shellCMatch[2].trim();
5703
5704
  return isKillRelatedCommand(inner) ? inner : null;
5704
5705
  }
5705
5706
  const shellCUnquoted = normalized.match(
5706
- /^(?:\S+(?:\s+\S+)?)?\s+-c\s+(kill(?:\s+-[a-zA-Z]+)?(?:\s+\d+)+)$/
5707
+ /^.+?\s+-c\s+(kill(?:\s+-s\s+[a-zA-Z0-9]+|\s+-[a-zA-Z0-9]+)?\s+\d+)$/
5707
5708
  );
5708
5709
  if (shellCUnquoted?.[1]) {
5709
5710
  return shellCUnquoted[1];
@@ -5715,22 +5716,47 @@ function isKillRelatedCommand(cmd) {
5715
5716
  if (isWin2) {
5716
5717
  if (/^taskkill\s/i.test(normalized)) return true;
5717
5718
  if (/^tskill\s/i.test(normalized)) return true;
5719
+ if (/^(stop-process|kill|stop)\s/i.test(normalized)) return true;
5720
+ if (/^wmic\s+process\s/i.test(normalized) && /\bdelete\b/i.test(normalized)) return true;
5721
+ if (SCRIPT_KILL_RE.test(normalized)) return true;
5718
5722
  return false;
5719
5723
  }
5720
5724
  if (/^kill(\s|$)/.test(normalized)) return true;
5721
5725
  if (/^(pkill|killall|pgrep|skill)\s/.test(normalized)) return true;
5722
5726
  if (/^\/proc\/\d+\/(?:kill|fd)/.test(normalized)) return true;
5727
+ if (SCRIPT_KILL_RE_POSIX.test(normalized)) return true;
5723
5728
  return false;
5724
5729
  }
5725
5730
  function parseKillCommand(command) {
5726
5731
  const normalized = command.replace(/\s+/g, " ").trim();
5727
5732
  if (isWin2) {
5728
- const taskkillMatch = normalized.match(/^taskkill\s+(?:\/[a-zA-Z]+\s+)*\/PID\s+(\d+)/i);
5729
- if (taskkillMatch?.[1]) {
5730
- const pidStr = taskkillMatch[1];
5733
+ const hasTaskkillForce = /(?:^|\s)\/F(?=\s|$)/i.test(normalized);
5734
+ const isSimpleTaskkill = /^taskkill\s+/i.test(normalized) && !/[|&<>]/.test(normalized);
5735
+ const taskkillPidMatch = isSimpleTaskkill ? normalized.match(/(?:^|\s)\/PID\s+(\d+)(?=\s|$)/i) : null;
5736
+ if (taskkillPidMatch?.[1]) {
5737
+ return {
5738
+ pid: parseInt(taskkillPidMatch[1], 10),
5739
+ signal: hasTaskkillForce ? "FORCE" : "TERM",
5740
+ isGroupKill: false,
5741
+ isAllKill: false,
5742
+ originalCommand: command
5743
+ };
5744
+ }
5745
+ const taskkillImMatch = isSimpleTaskkill ? normalized.match(/(?:^|\s)\/IM\s+([^\s/]+)(?=\s|$)/i) : null;
5746
+ if (taskkillImMatch?.[1]) {
5731
5747
  return {
5732
- pid: parseInt(pidStr, 10),
5733
- signal: normalized.includes("/F") ? "FORCE" : "TERM",
5748
+ name: taskkillImMatch[1],
5749
+ signal: hasTaskkillForce ? "FORCE" : "TERM",
5750
+ isGroupKill: false,
5751
+ isAllKill: false,
5752
+ originalCommand: command
5753
+ };
5754
+ }
5755
+ const taskkillFiMatch = isSimpleTaskkill ? normalized.match(/(?:^|\s)\/FI\s+"IMAGENAME\s+eq\s+([^"]+)"(?=\s|$)/i) : null;
5756
+ if (taskkillFiMatch?.[1]) {
5757
+ return {
5758
+ name: taskkillFiMatch[1],
5759
+ signal: hasTaskkillForce ? "FORCE" : "TERM",
5734
5760
  isGroupKill: false,
5735
5761
  isAllKill: false,
5736
5762
  originalCommand: command
@@ -5738,18 +5764,109 @@ function parseKillCommand(command) {
5738
5764
  }
5739
5765
  const tskillMatch = normalized.match(/^tskill\s+(\d+)/i);
5740
5766
  if (tskillMatch?.[1]) {
5741
- const pidStr = tskillMatch[1];
5742
5767
  return {
5743
- pid: parseInt(pidStr, 10),
5768
+ pid: parseInt(tskillMatch[1], 10),
5744
5769
  signal: "TERM",
5745
5770
  isGroupKill: false,
5746
5771
  isAllKill: false,
5747
5772
  originalCommand: command
5748
5773
  };
5749
5774
  }
5775
+ const isStopProcIdCommand = /^(?:stop-process|kill)(?:\s+-(?:id|pid)\s+\d+|\s+-[a-zA-Z]+)+$/i.test(normalized);
5776
+ const stopProcIdMatch = normalized.match(/(?:^|\s)-(?:id|pid)\s+(\d+)(?=\s|$)/i);
5777
+ if (isStopProcIdCommand && stopProcIdMatch?.[1]) {
5778
+ return {
5779
+ pid: parseInt(stopProcIdMatch[1], 10),
5780
+ signal: "FORCE",
5781
+ isGroupKill: false,
5782
+ isAllKill: false,
5783
+ originalCommand: command
5784
+ };
5785
+ }
5786
+ const killSignalOptionMatch = normalized.match(/^kill\s+-s\s+([a-zA-Z0-9]+)\s+(\d+)$/i);
5787
+ if (killSignalOptionMatch?.[1] && killSignalOptionMatch[2]) {
5788
+ return {
5789
+ pid: parseInt(killSignalOptionMatch[2], 10),
5790
+ signal: killSignalOptionMatch[1].toUpperCase(),
5791
+ isGroupKill: false,
5792
+ isAllKill: false,
5793
+ originalCommand: command
5794
+ };
5795
+ }
5796
+ const killPosixMatch = normalized.match(/^kill\s+(?:(-[a-zA-Z0-9]+)\s+)?(\d+)$/);
5797
+ if (killPosixMatch?.[2]) {
5798
+ const sig = killPosixMatch[1] ? killPosixMatch[1].slice(1).toUpperCase() : "TERM";
5799
+ return {
5800
+ pid: parseInt(killPosixMatch[2], 10),
5801
+ signal: sig,
5802
+ isGroupKill: false,
5803
+ isAllKill: false,
5804
+ originalCommand: command
5805
+ };
5806
+ }
5807
+ const stopProcNameMatch = normalized.match(
5808
+ /^(?:stop-process|kill)\s+-(?:name|n)\s+(?:['"]([a-zA-Z0-9_.-]+)['"]|([a-zA-Z0-9_.-]+))(?:\s|$)/i
5809
+ );
5810
+ const stopProcName = stopProcNameMatch?.[1] ?? stopProcNameMatch?.[2];
5811
+ if (stopProcName) {
5812
+ return {
5813
+ name: stopProcName,
5814
+ signal: "FORCE",
5815
+ isGroupKill: false,
5816
+ isAllKill: false,
5817
+ originalCommand: command
5818
+ };
5819
+ }
5820
+ const stopProcStandalone = normalized.match(
5821
+ /^(?:stop-process|kill)\s+['"]?([a-zA-Z][a-zA-Z0-9_.-]+)['"]?$/i
5822
+ );
5823
+ if (stopProcStandalone?.[1]) {
5824
+ return {
5825
+ name: stopProcStandalone[1],
5826
+ signal: "FORCE",
5827
+ isGroupKill: false,
5828
+ isAllKill: false,
5829
+ originalCommand: command
5830
+ };
5831
+ }
5832
+ const wmicMatch = normalized.match(
5833
+ /^wmic\s+process\s+where\s+['"]?(?:name\s*=\s*['"]?)([a-zA-Z0-9_.-]+)/i
5834
+ );
5835
+ if (wmicMatch?.[1]) {
5836
+ return {
5837
+ name: wmicMatch[1],
5838
+ signal: "FORCE",
5839
+ isGroupKill: false,
5840
+ isAllKill: false,
5841
+ originalCommand: command
5842
+ };
5843
+ }
5844
+ const killScriptMatch = normalized.match(SCRIPT_KILL_RE);
5845
+ if (killScriptMatch) {
5846
+ return {
5847
+ name: "kill-script",
5848
+ // sentinel — isKillProtected always blocks "kill-script"
5849
+ signal: "FORCE",
5850
+ isGroupKill: false,
5851
+ isAllKill: false,
5852
+ originalCommand: command
5853
+ };
5854
+ }
5750
5855
  return null;
5751
5856
  }
5752
- const simpleMatch = normalized.match(/^kill\s+(?:(-[a-zA-Z]+)\s+)?(\d+|-?\d+)$/);
5857
+ const signalOptionMatch = normalized.match(/^kill\s+-s\s+([a-zA-Z0-9]+)\s+(\d+|-?\d+)$/i);
5858
+ if (signalOptionMatch?.[1] && signalOptionMatch[2]) {
5859
+ const pidOrGroup = signalOptionMatch[2];
5860
+ const isGroupKill = pidOrGroup.startsWith("-");
5861
+ return {
5862
+ pid: parseInt(isGroupKill ? pidOrGroup.slice(1) : pidOrGroup, 10),
5863
+ signal: signalOptionMatch[1].toUpperCase(),
5864
+ isGroupKill,
5865
+ isAllKill: false,
5866
+ originalCommand: command
5867
+ };
5868
+ }
5869
+ const simpleMatch = normalized.match(/^kill\s+(?:(-[a-zA-Z0-9]+)\s+)?(\d+|-?\d+)$/);
5753
5870
  if (simpleMatch) {
5754
5871
  const signal = simpleMatch[1] ?? "-TERM";
5755
5872
  const pidOrGroup = simpleMatch[2];
@@ -5809,6 +5926,9 @@ async function getProtectedEntries() {
5809
5926
  }
5810
5927
  async function isKillProtected(kill) {
5811
5928
  const registry = getPersistentProcessRegistry();
5929
+ if (kill.name === "kill-script") {
5930
+ return true;
5931
+ }
5812
5932
  if (kill.name) {
5813
5933
  const entries = await getProtectedEntries();
5814
5934
  const killNameLower = kill.name.toLowerCase();
@@ -5848,6 +5968,12 @@ async function checkAndBlockKillCommand(command) {
5848
5968
  reason: `Blocked: complex kill pipeline detected \u2014 "${killCmd.slice(0, 50)}..."`
5849
5969
  };
5850
5970
  }
5971
+ if (SCRIPT_KILL_FALLBACK_RE.test(killCmd)) {
5972
+ return {
5973
+ blocked: true,
5974
+ reason: `Blocked: script-based kill detected \u2014 "${killCmd.slice(0, 80)}" may target protected WrongStack processes (cannot inspect script body).`
5975
+ };
5976
+ }
5851
5977
  return { blocked: false };
5852
5978
  }
5853
5979
  if (await isKillProtected(parsed)) {
@@ -5871,8 +5997,8 @@ async function checkAndBlockKillCommand(command) {
5871
5997
 
5872
5998
  // src/_shell-pick.ts
5873
5999
  var POSIX_DEFAULT = "cmd";
5874
- function pickShell(platform5, command, env) {
5875
- if (platform5 !== "win32") return POSIX_DEFAULT;
6000
+ function pickShell(platform6, command, env) {
6001
+ if (platform6 !== "win32") return POSIX_DEFAULT;
5876
6002
  const override = env.get("WRONGSTACK_SHELL")?.trim().toLowerCase();
5877
6003
  if (override === "cmd" || override === "cmd.exe") return "cmd";
5878
6004
  if (override === "powershell" || override === "powershell.exe") return "powershell";
@@ -6076,10 +6202,10 @@ var bashTool = {
6076
6202
  }));
6077
6203
  }
6078
6204
  const timeoutMs = Math.max(1, Math.min(input.timeout_ms ?? DEFAULT_TIMEOUT_MS2, 6e5));
6079
- const isWin4 = os4.platform() === "win32";
6205
+ const isWin5 = os4.platform() === "win32";
6080
6206
  let plan;
6081
6207
  let winShellKind;
6082
- if (isWin4) {
6208
+ if (isWin5) {
6083
6209
  const shell2 = pickShell("win32", input.command, {
6084
6210
  get: (k) => process.env[k]
6085
6211
  });
@@ -6108,7 +6234,7 @@ var bashTool = {
6108
6234
  const shell = plan.bin;
6109
6235
  const args = plan.useStdin ? [...plan.argv] : [...plan.argv, input.command];
6110
6236
  const env = buildChildEnv2(ctx.session?.id);
6111
- const detached = !isWin4;
6237
+ const detached = !isWin5;
6112
6238
  const startedAt = Date.now();
6113
6239
  if (input.background) {
6114
6240
  let buf2 = "";
@@ -6126,7 +6252,7 @@ var bashTool = {
6126
6252
  // apply: the child gets a hidden console that grandchildren inherit.
6127
6253
  // Windows children survive parent exit either way. POSIX keeps
6128
6254
  // detached for the process-group kill semantics.
6129
- detached: !isWin4,
6255
+ detached: !isWin5,
6130
6256
  windowsHide: true
6131
6257
  });
6132
6258
  if (plan.useStdin) {
@@ -6237,7 +6363,7 @@ var bashTool = {
6237
6363
  stdio: [plan.useStdin ? "pipe" : "ignore", "pipe", "pipe"],
6238
6364
  detached,
6239
6365
  windowsHide: true,
6240
- ...isWin4 ? {} : { signal: opts.signal }
6366
+ ...isWin5 ? {} : { signal: opts.signal }
6241
6367
  });
6242
6368
  if (plan.useStdin) {
6243
6369
  try {
@@ -6290,7 +6416,7 @@ var bashTool = {
6290
6416
  const timers = [];
6291
6417
  const spool = createOutputSpool({ tool: "bash", thresholdBytes: MAX_OUTPUT });
6292
6418
  function killWithTimeout(child2, timeoutMs2) {
6293
- if (isWin4) {
6419
+ if (isWin5) {
6294
6420
  if (typeof child2.pid === "number" && child2.exitCode === null) {
6295
6421
  const attempted = registry.kill(child2.pid, { force: true, graceMs: timeoutMs2 });
6296
6422
  if (!attempted) {
@@ -6331,7 +6457,7 @@ var bashTool = {
6331
6457
  timers.push(timer);
6332
6458
  timer.unref?.();
6333
6459
  const onAbort = () => killWithTimeout(child, 2e3);
6334
- if (isWin4) {
6460
+ if (isWin5) {
6335
6461
  if (opts.signal.aborted) onAbort();
6336
6462
  else opts.signal.addEventListener("abort", onAbort, { once: true });
6337
6463
  }
@@ -6445,7 +6571,7 @@ ${hint}` : ""),
6445
6571
  } finally {
6446
6572
  for (const t of timers) clearTimeout(t);
6447
6573
  spool.finalize();
6448
- if (isWin4) opts.signal.removeEventListener("abort", onAbort);
6574
+ if (isWin5) opts.signal.removeEventListener("abort", onAbort);
6449
6575
  child.stdout?.off("data", onStdoutData);
6450
6576
  child.stderr?.off("data", onStderrData);
6451
6577
  child.stdout?.destroy();
@@ -8099,6 +8225,53 @@ function codebaseIndexDirOverride(ctx) {
8099
8225
  const v = ctx.meta?.["codebaseIndexDir"];
8100
8226
  return typeof v === "string" ? v : void 0;
8101
8227
  }
8228
+ var StorePool = class {
8229
+ stores = /* @__PURE__ */ new Map();
8230
+ key(projectRoot, indexDir) {
8231
+ return `${projectRoot}\0${indexDir ?? ""}`;
8232
+ }
8233
+ /** Borrow a store. Creates it on first access for this key. */
8234
+ acquire(projectRoot, opts) {
8235
+ const k = this.key(projectRoot, opts?.indexDir);
8236
+ let store = this.stores.get(k);
8237
+ if (!store) {
8238
+ store = new IndexStore(projectRoot, { indexDir: opts?.indexDir });
8239
+ this.stores.set(k, store);
8240
+ }
8241
+ return store;
8242
+ }
8243
+ /** Return the store to the pool. The connection stays warm for subsequent
8244
+ * operations on the same (projectRoot, indexDir). */
8245
+ release(_store) {
8246
+ }
8247
+ /** Close every pooled connection and drain the pool. Call on shutdown. */
8248
+ closeAll() {
8249
+ for (const store of this.stores.values()) {
8250
+ try {
8251
+ store.close();
8252
+ } catch {
8253
+ }
8254
+ }
8255
+ this.stores.clear();
8256
+ }
8257
+ /** Remove one store from the pool. Used by tests that need isolation. */
8258
+ evict(projectRoot, indexDir) {
8259
+ const k = this.key(projectRoot, indexDir);
8260
+ const store = this.stores.get(k);
8261
+ if (store) {
8262
+ try {
8263
+ store.close();
8264
+ } catch {
8265
+ }
8266
+ this.stores.delete(k);
8267
+ }
8268
+ }
8269
+ /** True when the pool holds a connection for the given key. */
8270
+ has(projectRoot, indexDir) {
8271
+ return this.stores.has(this.key(projectRoot, indexDir));
8272
+ }
8273
+ };
8274
+ var indexStorePool = new StorePool();
8102
8275
  var warningSilenced = false;
8103
8276
  function silenceSqliteExperimentalWarning() {
8104
8277
  if (warningSilenced) return;
@@ -8316,7 +8489,7 @@ var IndexStore = class _IndexStore {
8316
8489
  return this.runWithRetry(() => {
8317
8490
  this.db.exec("BEGIN IMMEDIATE");
8318
8491
  try {
8319
- const maxRows = this.db.prepare("SELECT MAX(id) AS m FROM symbols").all();
8492
+ const maxRows = this.stmt("SELECT MAX(id) AS m FROM symbols").all();
8320
8493
  let nextId = (maxRows[0]?.m ?? 0) + 1;
8321
8494
  const stmt = this.db.prepare(
8322
8495
  `INSERT INTO symbols(id, lang, kind, name, file, line, col, signature, doc_comment, scope, text, file_fk)
@@ -8588,7 +8761,7 @@ var IndexStore = class _IndexStore {
8588
8761
  return { results, total: candidates.length };
8589
8762
  }
8590
8763
  getAllIndexable() {
8591
- return this.db.prepare("SELECT id, text FROM symbols").all().map(({ id, text }) => ({ id, text }));
8764
+ return this.stmt("SELECT id, text FROM symbols").all().map(({ id, text }) => ({ id, text }));
8592
8765
  }
8593
8766
  /**
8594
8767
  * Largest symbol id currently in the table (0 when empty). New ids must be
@@ -8598,7 +8771,7 @@ var IndexStore = class _IndexStore {
8598
8771
  * `symbols.id`). Ids may have gaps — that is fine.
8599
8772
  */
8600
8773
  getMaxSymbolId() {
8601
- const rows = this.db.prepare("SELECT MAX(id) AS m FROM symbols").all();
8774
+ const rows = this.stmt("SELECT MAX(id) AS m FROM symbols").all();
8602
8775
  return rows[0]?.m ?? 0;
8603
8776
  }
8604
8777
  // ─── Stats ───────────────────────────────────────────────────────────────────
@@ -8606,14 +8779,14 @@ var IndexStore = class _IndexStore {
8606
8779
  const sizeBytes = this.sizeBytes();
8607
8780
  const lastRows = this.db.prepare("SELECT value FROM metadata WHERE key = 'last_indexed'").all();
8608
8781
  const lastIndexed = lastRows.length ? Number(lastRows[0]?.value) : null;
8609
- const totalRows = this.db.prepare("SELECT COUNT(*) FROM symbols").all();
8782
+ const totalRows = this.stmt("SELECT COUNT(*) FROM symbols").all();
8610
8783
  const totalSymbols = totalRows[0] ? Number(totalRows[0]["COUNT(*)"]) : 0;
8611
- const fileRows = this.db.prepare("SELECT COUNT(*) FROM files").all();
8784
+ const fileRows = this.stmt("SELECT COUNT(*) FROM files").all();
8612
8785
  const totalFiles = fileRows[0] ? Number(fileRows[0]["COUNT(*)"]) : 0;
8613
- const langRows = this.db.prepare("SELECT lang, COUNT(*) FROM symbols GROUP BY lang").all();
8786
+ const langRows = this.stmt("SELECT lang, COUNT(*) FROM symbols GROUP BY lang").all();
8614
8787
  const byLang = {};
8615
8788
  for (const row of langRows) byLang[row.lang] = Number(row["COUNT(*)"]);
8616
- const kindRows = this.db.prepare("SELECT kind, COUNT(*) FROM symbols GROUP BY kind").all();
8789
+ const kindRows = this.stmt("SELECT kind, COUNT(*) FROM symbols GROUP BY kind").all();
8617
8790
  const byKind = {};
8618
8791
  for (const row of kindRows) byKind[row.kind] = Number(row["COUNT(*)"]);
8619
8792
  return {
@@ -8645,11 +8818,14 @@ var IndexStore = class _IndexStore {
8645
8818
  this.runWithRetry(() => {
8646
8819
  this.db.exec("BEGIN IMMEDIATE");
8647
8820
  try {
8648
- this.db.exec("DELETE FROM refs");
8649
- this.db.exec("DELETE FROM symbols");
8650
- this.db.exec("DELETE FROM files");
8651
- if (this.ftsAvailable) this.db.exec("DELETE FROM symbols_fts");
8821
+ this.db.exec("DROP TABLE IF EXISTS refs");
8822
+ this.db.exec("DROP TABLE IF EXISTS symbols");
8823
+ this.db.exec("DROP TABLE IF EXISTS files");
8824
+ this.db.exec("DROP TABLE IF EXISTS metadata");
8825
+ if (this.ftsAvailable) this.db.exec("DROP TABLE IF EXISTS symbols_fts");
8652
8826
  this.db.exec("COMMIT");
8827
+ this.stmtCache.clear();
8828
+ this.initSchema();
8653
8829
  } catch (err) {
8654
8830
  this.db.exec("ROLLBACK");
8655
8831
  throw err;
@@ -8734,7 +8910,7 @@ var IndexStore = class _IndexStore {
8734
8910
  ).run(...options.deleteForFiles);
8735
8911
  this.db.prepare(`DELETE FROM symbols WHERE file IN (${placeholders})`).run(...options.deleteForFiles);
8736
8912
  }
8737
- const maxRows = this.db.prepare("SELECT MAX(id) AS m FROM symbols").all();
8913
+ const maxRows = this.stmt("SELECT MAX(id) AS m FROM symbols").all();
8738
8914
  let nextId = (maxRows[0]?.m ?? 0) + 1;
8739
8915
  const symStmt = this.db.prepare(
8740
8916
  `INSERT INTO symbols(id, lang, kind, name, file, line, col, signature, doc_comment, scope, text, file_fk)
@@ -8918,7 +9094,7 @@ var IndexStore = class _IndexStore {
8918
9094
  * symbol resolved in package B). Node metadata includes symbol/file counts.
8919
9095
  */
8920
9096
  getPackageGraph() {
8921
- const symbols = this.db.prepare("SELECT file, id, name, kind, lang, line FROM symbols ORDER BY id").all();
9097
+ const symbols = this.db.prepare("SELECT file, id FROM symbols ORDER BY id").all();
8922
9098
  const pkgNodes = /* @__PURE__ */ new Map();
8923
9099
  const fileToPkg = /* @__PURE__ */ new Map();
8924
9100
  const symbolToPkg = /* @__PURE__ */ new Map();
@@ -9009,15 +9185,18 @@ var IndexStore = class _IndexStore {
9009
9185
  * derived from cross-file symbol references within the package.
9010
9186
  */
9011
9187
  getFileGraph(packageFilter) {
9012
- const allSymbols = this.db.prepare("SELECT file, id, name, kind, lang, line FROM symbols ORDER BY id").all();
9013
- const pkgSyms = allSymbols.filter(
9014
- (s) => (_IndexStore.derivePackage(s.file) ?? "(root)") === packageFilter
9015
- );
9016
- if (pkgSyms.length === 0) return { nodes: [], edges: [] };
9188
+ const allFiles = this.db.prepare("SELECT DISTINCT file FROM symbols").all();
9189
+ const pkgFilePaths = allFiles.filter((f) => (_IndexStore.derivePackage(f.file) ?? "(root)") === packageFilter).map((f) => f.file);
9190
+ const localFiles = new Set(pkgFilePaths);
9191
+ if (localFiles.size === 0) return { nodes: [], edges: [] };
9192
+ const filePlaceholders = [...localFiles].map(() => "?").join(",");
9193
+ const pkgSyms = this.db.prepare(
9194
+ `SELECT file, id, name, kind, lang, line FROM symbols WHERE file IN (${filePlaceholders}) ORDER BY id`
9195
+ ).all(...pkgFilePaths);
9017
9196
  const fileNodes = /* @__PURE__ */ new Map();
9018
9197
  const symToFile = /* @__PURE__ */ new Map();
9019
9198
  const fileStats = /* @__PURE__ */ new Map();
9020
- for (const s of allSymbols) {
9199
+ for (const s of pkgSyms) {
9021
9200
  symToFile.set(s.id, s.file);
9022
9201
  const current = fileStats.get(s.file);
9023
9202
  fileStats.set(s.file, {
@@ -9025,8 +9204,6 @@ var IndexStore = class _IndexStore {
9025
9204
  lang: current?.lang ?? s.lang
9026
9205
  });
9027
9206
  }
9028
- const localFiles = new Set(pkgSyms.map((s) => s.file));
9029
- const indexedFiles = new Set(allSymbols.map((s) => s.file));
9030
9207
  const ensureFileNode = (file) => {
9031
9208
  if (fileNodes.has(file)) return;
9032
9209
  const stats = fileStats.get(file);
@@ -9044,11 +9221,32 @@ var IndexStore = class _IndexStore {
9044
9221
  for (const file of localFiles) {
9045
9222
  ensureFileNode(file);
9046
9223
  }
9224
+ const indexedFiles = new Set(allFiles.map((f) => f.file));
9047
9225
  const refRows = this.db.prepare(
9048
9226
  `SELECT r.from_id, r.to_id, r.call_type
9049
9227
  FROM refs r
9050
- WHERE r.to_id IS NOT NULL`
9051
- ).all();
9228
+ WHERE (r.from_id IN (SELECT id FROM symbols WHERE file IN (${filePlaceholders}))
9229
+ OR r.to_id IN (SELECT id FROM symbols WHERE file IN (${filePlaceholders})))
9230
+ AND r.to_id IS NOT NULL`
9231
+ ).all(...pkgFilePaths, ...pkgFilePaths);
9232
+ const knownSymIds = new Set(pkgSyms.map((s) => s.id));
9233
+ const crossRefIds = /* @__PURE__ */ new Set();
9234
+ for (const r of refRows) {
9235
+ if (!knownSymIds.has(r.from_id)) crossRefIds.add(r.from_id);
9236
+ if (!knownSymIds.has(r.to_id)) crossRefIds.add(r.to_id);
9237
+ }
9238
+ if (crossRefIds.size > 0) {
9239
+ const crossPlaceholders = [...crossRefIds].map(() => "?").join(",");
9240
+ const extras = this.db.prepare(
9241
+ `SELECT id, file FROM symbols WHERE id IN (${crossPlaceholders})`
9242
+ ).all(...crossRefIds);
9243
+ for (const x of extras) {
9244
+ symToFile.set(x.id, x.file);
9245
+ if (!fileStats.has(x.file)) {
9246
+ fileStats.set(x.file, { count: 0, lang: "ts" });
9247
+ }
9248
+ }
9249
+ }
9052
9250
  const edgeMap = /* @__PURE__ */ new Map();
9053
9251
  for (const r of refRows) {
9054
9252
  if (r.call_type === "import") continue;
@@ -9070,8 +9268,9 @@ var IndexStore = class _IndexStore {
9070
9268
  const importRows = this.db.prepare(
9071
9269
  `SELECT r.from_id, r.to_name
9072
9270
  FROM refs r
9073
- WHERE r.call_type = 'import'`
9074
- ).all();
9271
+ WHERE r.call_type = 'import'
9272
+ AND r.from_id IN (SELECT id FROM symbols WHERE file IN (${filePlaceholders}))`
9273
+ ).all(...pkgFilePaths);
9075
9274
  for (const r of importRows) {
9076
9275
  const fromFile = symToFile.get(r.from_id);
9077
9276
  if (!fromFile || !localFiles.has(fromFile)) continue;
@@ -9113,12 +9312,11 @@ var IndexStore = class _IndexStore {
9113
9312
  * derived from intra-file and cross-file symbol references (who calls whom).
9114
9313
  */
9115
9314
  getSymbolGraph(fileFilter) {
9116
- const allSymbols = this.db.prepare(
9117
- "SELECT id, name, kind, lang, file, line, signature, scope FROM symbols ORDER BY file, line, id"
9118
- ).all();
9119
- const syms = allSymbols.filter((symbol) => symbol.file === fileFilter);
9315
+ const syms = this.db.prepare(
9316
+ "SELECT id, name, kind, lang, file, line, signature, scope FROM symbols WHERE file = ? ORDER BY line, id"
9317
+ ).all(fileFilter);
9120
9318
  if (syms.length === 0) return { nodes: [], edges: [] };
9121
- const symById = new Map(allSymbols.map((symbol) => [symbol.id, symbol]));
9319
+ const symById = new Map(syms.map((symbol) => [symbol.id, symbol]));
9122
9320
  const relatedIds = new Set(syms.map((symbol) => symbol.id));
9123
9321
  const toGraphNode = (s) => ({
9124
9322
  id: `sym:${s.id}`,
@@ -9174,6 +9372,15 @@ var IndexStore = class _IndexStore {
9174
9372
  refType: bestType
9175
9373
  });
9176
9374
  }
9375
+ const loadedIds = new Set(syms.map((s) => s.id));
9376
+ const missingIds = [...relatedIds].filter((id) => !loadedIds.has(id));
9377
+ if (missingIds.length > 0) {
9378
+ const placeholders = missingIds.map(() => "?").join(",");
9379
+ const extras = this.db.prepare(
9380
+ `SELECT id, name, kind, lang, file, line, signature, scope FROM symbols WHERE id IN (${placeholders})`
9381
+ ).all(...missingIds);
9382
+ for (const s of extras) symById.set(s.id, s);
9383
+ }
9177
9384
  const nodes = [...relatedIds].map((id) => symById.get(id)).filter((symbol) => symbol !== void 0).sort((a, b) => {
9178
9385
  const aExternal = a.file === fileFilter ? 0 : 1;
9179
9386
  const bExternal = b.file === fileFilter ? 0 : 1;
@@ -9199,6 +9406,7 @@ import { Worker } from "node:worker_threads";
9199
9406
  import { expectDefined as expectDefined6 } from "@wrongstack/core";
9200
9407
  import * as fs14 from "node:fs/promises";
9201
9408
  import * as path19 from "node:path";
9409
+ import { availableParallelism } from "node:os";
9202
9410
  import { compileGlob as compileGlob2 } from "@wrongstack/core";
9203
9411
 
9204
9412
  // src/codebase-index/ts-parser.ts
@@ -9255,8 +9463,7 @@ function extToLang(ext) {
9255
9463
  return null;
9256
9464
  }
9257
9465
  }
9258
- function getSignature(node, sourceFile) {
9259
- const printer = ts.createPrinter({});
9466
+ function getSignature(printer, node, sourceFile) {
9260
9467
  const raw = printer.printNode(ts.EmitHint.Unspecified, node, sourceFile);
9261
9468
  return raw.replace(/\s+/g, " ").slice(0, 500);
9262
9469
  }
@@ -9275,28 +9482,14 @@ function getJsDoc(node, sourceFile) {
9275
9482
  }
9276
9483
  return "";
9277
9484
  }
9278
- function hasFunctionLikeAncestor(node) {
9279
- let current = node.parent;
9280
- while (current) {
9281
- if (ts.isFunctionLike(current)) return true;
9282
- current = current.parent;
9283
- }
9284
- return false;
9285
- }
9286
- function buildScope(node) {
9287
- const parts = [];
9288
- let current = node.parent;
9289
- while (current) {
9290
- if (ts.isClassDeclaration(current) || ts.isInterfaceDeclaration(current) || ts.isEnumDeclaration(current) || ts.isTypeAliasDeclaration(current)) {
9291
- parts.unshift(current.name?.text ?? "Anon");
9292
- } else if (ts.isMethodDeclaration(current) || ts.isGetAccessor(current) || ts.isSetAccessor(current) || ts.isPropertyDeclaration(current) || ts.isFunctionDeclaration(current)) {
9293
- if (current.name && ts.isIdentifier(current.name)) {
9294
- parts.unshift(current.name.text);
9295
- }
9485
+ function pushScopeName(node, parts) {
9486
+ if (ts.isClassDeclaration(node) || ts.isInterfaceDeclaration(node) || ts.isEnumDeclaration(node) || ts.isTypeAliasDeclaration(node)) {
9487
+ parts.push(node.name?.text ?? "Anon");
9488
+ } else if (ts.isMethodDeclaration(node) || ts.isGetAccessor(node) || ts.isSetAccessor(node) || ts.isPropertyDeclaration(node) || ts.isFunctionDeclaration(node)) {
9489
+ if (node.name && ts.isIdentifier(node.name)) {
9490
+ parts.push(node.name.text);
9296
9491
  }
9297
- current = current.parent;
9298
9492
  }
9299
- return parts.join(".");
9300
9493
  }
9301
9494
  function parseSymbols(opts) {
9302
9495
  const { file, content, lang } = opts;
@@ -9307,45 +9500,39 @@ function parseSymbols(opts) {
9307
9500
  return { file, lang, symbols: [], mtimeMs: Date.now() };
9308
9501
  }
9309
9502
  const symbols = [];
9310
- function visit(node) {
9503
+ const refs = [];
9504
+ const printer = ts.createPrinter({});
9505
+ function visit(node, funcDepth, scopeParts) {
9311
9506
  const kind = kindOf(node);
9312
9507
  if (kind) {
9313
- if ((kind === "const" || kind === "let" || kind === "var" || kind === "parameter") && hasFunctionLikeAncestor(node)) {
9314
- ts.forEachChild(node, visit);
9315
- return;
9508
+ if ((kind === "const" || kind === "let" || kind === "var" || kind === "parameter") && funcDepth > 0) {
9509
+ } else {
9510
+ const nameNode = node.name;
9511
+ if (!nameNode || !ts.isIdentifier(nameNode)) {
9512
+ return;
9513
+ }
9514
+ const name = nameNode.text;
9515
+ const pos2 = nameNode.getStart(sourceFile);
9516
+ const { line: line2, character } = sourceFile.getLineAndCharacterOfPosition(pos2);
9517
+ const scope = scopeParts.join(".");
9518
+ const signature = getSignature(printer, node, sourceFile);
9519
+ const docComment = getJsDoc(node, sourceFile);
9520
+ const text = [name, signature, docComment].filter(Boolean).join(" | ");
9521
+ symbols.push({
9522
+ id: 0,
9523
+ lang,
9524
+ kind,
9525
+ name,
9526
+ file,
9527
+ line: line2 + 1,
9528
+ col: character,
9529
+ signature,
9530
+ docComment,
9531
+ scope,
9532
+ text
9533
+ });
9316
9534
  }
9317
- const nameNode = node.name;
9318
- if (!nameNode || !ts.isIdentifier(nameNode)) return;
9319
- const name = nameNode.text;
9320
- const pos = nameNode.getStart(sourceFile);
9321
- const { line, character } = sourceFile.getLineAndCharacterOfPosition(pos);
9322
- const scope = buildScope(node);
9323
- const signature = getSignature(node, sourceFile);
9324
- const docComment = getJsDoc(node, sourceFile);
9325
- const text = [name, signature, docComment].filter(Boolean).join(" | ");
9326
- symbols.push({
9327
- id: 0,
9328
- lang,
9329
- kind,
9330
- name,
9331
- file,
9332
- line: line + 1,
9333
- col: character,
9334
- signature,
9335
- docComment,
9336
- scope,
9337
- text
9338
- });
9339
9535
  }
9340
- ts.forEachChild(node, visit);
9341
- }
9342
- visit(sourceFile);
9343
- const refs = extractRefs(sourceFile);
9344
- return { file, lang, symbols, refs, mtimeMs: Date.now() };
9345
- }
9346
- function extractRefs(sourceFile) {
9347
- const refs = [];
9348
- function visit(node) {
9349
9536
  const pos = node.getStart(sourceFile);
9350
9537
  const { line } = sourceFile.getLineAndCharacterOfPosition(pos);
9351
9538
  const lineNum = line + 1;
@@ -9370,10 +9557,14 @@ function extractRefs(sourceFile) {
9370
9557
  const moduleName = getModuleName(node);
9371
9558
  if (moduleName) refs.push({ fromId: 0, toName: moduleName, callType: "import", line: lineNum });
9372
9559
  }
9373
- ts.forEachChild(node, visit);
9560
+ const scopeIdx = scopeParts.length;
9561
+ pushScopeName(node, scopeParts);
9562
+ const childFuncDepth = ts.isFunctionLike(node) ? funcDepth + 1 : funcDepth;
9563
+ ts.forEachChild(node, (child) => visit(child, childFuncDepth, scopeParts));
9564
+ scopeParts.length = scopeIdx;
9374
9565
  }
9375
- visit(sourceFile);
9376
- return deduplicateRefs(refs);
9566
+ visit(sourceFile, 0, []);
9567
+ return { file, lang, symbols, refs: deduplicateRefs(refs), mtimeMs: Date.now() };
9377
9568
  }
9378
9569
  function getTypeName(name) {
9379
9570
  if (ts.isIdentifier(name)) return name.text;
@@ -10258,9 +10449,9 @@ function parseSymbols5(opts) {
10258
10449
  function regexParse2(opts) {
10259
10450
  const { file, content, lang } = opts;
10260
10451
  const symbols = [];
10261
- const basename10 = path17.basename(file).toLowerCase();
10262
- const isPackageJson = basename10 === "package.json";
10263
- const isTsconfig = basename10 === "tsconfig.json" || basename10 === "tsconfig.build.json";
10452
+ const basename11 = path17.basename(file).toLowerCase();
10453
+ const isPackageJson = basename11 === "package.json";
10454
+ const isTsconfig = basename11 === "tsconfig.json" || basename11 === "tsconfig.build.json";
10264
10455
  const isJsonSchema = content.includes("$schema") || content.includes("$id") || content.includes("$ref");
10265
10456
  const isOpenApi = content.includes("openapi") || content.includes("swagger");
10266
10457
  const lines = content.split("\n");
@@ -10655,7 +10846,7 @@ async function loadGitignoreMatcher(projectRoot) {
10655
10846
 
10656
10847
  // src/codebase-index/indexer.ts
10657
10848
  var YIELD_EVERY_N = 50;
10658
- var PARALLEL_BATCH = 20;
10849
+ var PARALLEL_BATCH = Math.min(availableParallelism() * 4, 40);
10659
10850
  function yieldEventLoop() {
10660
10851
  return new Promise((resolve15) => setImmediate(resolve15));
10661
10852
  }
@@ -10833,11 +11024,14 @@ async function runIndexerWithStore(store, opts) {
10833
11024
  if (!force) {
10834
11025
  for (const meta of store.getAllFileMetas()) existingMeta.set(meta.file, meta);
10835
11026
  }
11027
+ let filesSinceLastYield = 0;
10836
11028
  for (let batchStart = 0; batchStart < files.length; batchStart += PARALLEL_BATCH) {
10837
11029
  const batchEnd = Math.min(batchStart + PARALLEL_BATCH, files.length);
10838
11030
  const batchFiles = files.slice(batchStart, batchEnd);
10839
11031
  opts.onProgress?.(batchEnd, files.length);
10840
- if (batchStart > 0 && batchStart % YIELD_EVERY_N === 0) {
11032
+ filesSinceLastYield += batchFiles.length;
11033
+ if (filesSinceLastYield >= YIELD_EVERY_N) {
11034
+ filesSinceLastYield = 0;
10841
11035
  await yieldEventLoop();
10842
11036
  throwIfAborted(signal);
10843
11037
  }
@@ -11048,7 +11242,7 @@ async function indexService(args, hooks = {}) {
11048
11242
  });
11049
11243
  }
11050
11244
  function searchService(args) {
11051
- const store = new IndexStore(args.projectRoot, { indexDir: args.indexDir });
11245
+ const store = indexStorePool.acquire(args.projectRoot, { indexDir: args.indexDir });
11052
11246
  try {
11053
11247
  return store.searchRanked(
11054
11248
  args.query,
@@ -11061,39 +11255,39 @@ function searchService(args) {
11061
11255
  args.limit
11062
11256
  );
11063
11257
  } finally {
11064
- store.close();
11258
+ indexStorePool.release(store);
11065
11259
  }
11066
11260
  }
11067
11261
  function statsService(args) {
11068
- const store = new IndexStore(args.projectRoot, { indexDir: args.indexDir });
11262
+ const store = indexStorePool.acquire(args.projectRoot, { indexDir: args.indexDir });
11069
11263
  try {
11070
11264
  return store.getStats();
11071
11265
  } finally {
11072
- store.close();
11266
+ indexStorePool.release(store);
11073
11267
  }
11074
11268
  }
11075
11269
  function packageGraphService(args) {
11076
- const store = new IndexStore(args.projectRoot, { indexDir: args.indexDir });
11270
+ const store = indexStorePool.acquire(args.projectRoot, { indexDir: args.indexDir });
11077
11271
  try {
11078
11272
  return store.getPackageGraph();
11079
11273
  } finally {
11080
- store.close();
11274
+ indexStorePool.release(store);
11081
11275
  }
11082
11276
  }
11083
11277
  function fileGraphService(args) {
11084
- const store = new IndexStore(args.projectRoot, { indexDir: args.indexDir });
11278
+ const store = indexStorePool.acquire(args.projectRoot, { indexDir: args.indexDir });
11085
11279
  try {
11086
11280
  return store.getFileGraph(args.packageFilter);
11087
11281
  } finally {
11088
- store.close();
11282
+ indexStorePool.release(store);
11089
11283
  }
11090
11284
  }
11091
11285
  function symbolGraphService(args) {
11092
- const store = new IndexStore(args.projectRoot, { indexDir: args.indexDir });
11286
+ const store = indexStorePool.acquire(args.projectRoot, { indexDir: args.indexDir });
11093
11287
  try {
11094
11288
  return store.getSymbolGraph(args.fileFilter);
11095
11289
  } finally {
11096
- store.close();
11290
+ indexStorePool.release(store);
11097
11291
  }
11098
11292
  }
11099
11293
 
@@ -11210,10 +11404,19 @@ function terminateWorker(reason) {
11210
11404
  if (w) void w.terminate().catch(() => {
11211
11405
  });
11212
11406
  }
11213
- function shutdownCodebaseIndexHost() {
11407
+ async function shutdownCodebaseIndexHost() {
11214
11408
  cancelPendingReindexes();
11215
- terminateWorker(new Error("codebase-index host shut down"));
11409
+ indexStorePool.closeAll();
11410
+ const w = worker;
11411
+ worker = null;
11412
+ failAllPending(new Error("codebase-index host shut down"));
11216
11413
  workerUnavailable = false;
11414
+ if (w) {
11415
+ try {
11416
+ await w.terminate();
11417
+ } catch {
11418
+ }
11419
+ }
11217
11420
  }
11218
11421
  function callIndexOp(op, args, opts) {
11219
11422
  const w = ensureWorker();
@@ -13212,7 +13415,244 @@ init_output_spool();
13212
13415
  init_util();
13213
13416
  init_process_registry();
13214
13417
  init_win32_resolve();
13215
- var isWin3 = process.platform === "win32";
13418
+
13419
+ // src/exec-kill-guard.ts
13420
+ import * as os7 from "node:os";
13421
+ import * as path24 from "node:path";
13422
+ var isWin3 = os7.platform() === "win32";
13423
+ async function checkExecKillCommand(cmd, args) {
13424
+ if (!cmd) return { blocked: false };
13425
+ const cmdLower = cmd.toLowerCase().trim();
13426
+ const fullCommand = [cmdLower, ...args].join(" ").replace(/\s+/g, " ").trim();
13427
+ if (isWin3) {
13428
+ if (cmdLower === "taskkill" || cmdLower === "taskkill.exe") {
13429
+ const hasForce = args.some((a) => a.toUpperCase() === "/F" || a.toUpperCase() === "-F");
13430
+ const signal = hasForce ? "FORCE" : "TERM";
13431
+ for (let i = 0; i < args.length; i++) {
13432
+ const a = args[i];
13433
+ if (a.toUpperCase() === "/IM" || a.toUpperCase() === "-IM") {
13434
+ const nameArg = args[i + 1];
13435
+ if (nameArg) {
13436
+ const result = await checkKillTarget({ name: nameArg, signal, cmd: fullCommand });
13437
+ if (result.blocked) return result;
13438
+ }
13439
+ }
13440
+ }
13441
+ for (let i = 0; i < args.length; i++) {
13442
+ const a = args[i];
13443
+ if (a.toUpperCase() === "/PID" || a.toUpperCase() === "-PID") {
13444
+ const pidArg = args[i + 1];
13445
+ if (pidArg && /^\d+$/.test(pidArg)) {
13446
+ const result = await checkKillTarget({
13447
+ pid: parseInt(pidArg, 10),
13448
+ signal,
13449
+ cmd: fullCommand
13450
+ });
13451
+ if (result.blocked) return result;
13452
+ }
13453
+ }
13454
+ }
13455
+ for (let i = 0; i < args.length; i++) {
13456
+ const a = args[i];
13457
+ if (a.toUpperCase() === "/FI" || a.toUpperCase() === "-FI") {
13458
+ const filterArg = args[i + 1];
13459
+ if (filterArg) {
13460
+ const nameMatch = filterArg.match(/IMAGENAME\s+eq\s+"?([^"\s]+)/i);
13461
+ if (nameMatch?.[1]) {
13462
+ const result = await checkKillTarget({ name: nameMatch[1], signal, cmd: fullCommand });
13463
+ if (result.blocked) return result;
13464
+ }
13465
+ }
13466
+ }
13467
+ }
13468
+ return { blocked: false };
13469
+ }
13470
+ if (cmdLower === "powershell" || cmdLower === "powershell.exe" || cmdLower === "pwsh" || cmdLower === "pwsh.exe" || cmdLower === "cmd" || cmdLower === "cmd.exe") {
13471
+ const shellFlagIndex = args.findIndex((arg) => {
13472
+ const lower = arg.toLowerCase();
13473
+ return lower === "-c" || lower === "-command" || lower === "/c";
13474
+ });
13475
+ if (shellFlagIndex >= 0) {
13476
+ const innerTokens = tokenizeShellCommand(args.slice(shellFlagIndex + 1).join(" "));
13477
+ const innerCommand = innerTokens[0];
13478
+ if (innerCommand) {
13479
+ const result = await checkExecKillCommand(innerCommand, innerTokens.slice(1));
13480
+ if (result.blocked) return result;
13481
+ }
13482
+ }
13483
+ }
13484
+ if (cmdLower === "stop-process" || cmdLower === "kill") {
13485
+ for (let i = 0; i < args.length; i++) {
13486
+ const a = args[i];
13487
+ if (a === "-Name" || a === "-n") {
13488
+ const nameArg = args[i + 1]?.replace(/^['"]|['"]$/g, "");
13489
+ if (nameArg) {
13490
+ const result = await checkKillTarget({
13491
+ name: nameArg,
13492
+ signal: "FORCE",
13493
+ cmd: fullCommand
13494
+ });
13495
+ if (result.blocked) return result;
13496
+ }
13497
+ }
13498
+ if (a === "-Id" || a === "-PID" || a === "-pid") {
13499
+ const pidArg = args[i + 1];
13500
+ if (pidArg && /^\d+$/.test(pidArg)) {
13501
+ const result = await checkKillTarget({
13502
+ pid: parseInt(pidArg, 10),
13503
+ signal: "FORCE",
13504
+ cmd: fullCommand
13505
+ });
13506
+ if (result.blocked) return result;
13507
+ }
13508
+ }
13509
+ }
13510
+ const firstNonFlag = args.find((a) => !a.startsWith("-"));
13511
+ if (firstNonFlag) {
13512
+ const name = firstNonFlag.replace(/^['"]|['"]$/g, "");
13513
+ const result = await checkKillTarget({ name, signal: "TERM", cmd: fullCommand });
13514
+ if (result.blocked) return result;
13515
+ }
13516
+ }
13517
+ if (cmdLower === "wmic" || cmdLower === "wmic.exe") {
13518
+ const joined = args.join(" ").toLowerCase();
13519
+ if (/\bprocess\b/.test(joined) && /\bdelete\b/.test(joined)) {
13520
+ const nameMatch = joined.match(/name\s*=\s*['"]?([^'"]+)/);
13521
+ if (nameMatch?.[1]) {
13522
+ const result = await checkKillTarget({
13523
+ name: nameMatch[1].trim(),
13524
+ signal: "FORCE",
13525
+ cmd: fullCommand
13526
+ });
13527
+ if (result.blocked) return result;
13528
+ }
13529
+ return {
13530
+ blocked: true,
13531
+ reason: "Blocked: wmic process delete targets all matched processes \u2014 would include protected WrongStack processes."
13532
+ };
13533
+ }
13534
+ }
13535
+ if (cmdLower === "node" || cmdLower === "node.exe") {
13536
+ if (args.includes("-e") || args.includes("--eval")) {
13537
+ const evalIdx = args.indexOf("-e") !== -1 ? args.indexOf("-e") : args.indexOf("--eval");
13538
+ const evalCode = args[evalIdx + 1] ?? "";
13539
+ if (/\bprocess\.kill\s*\(/.test(evalCode)) {
13540
+ const pidMatch = evalCode.match(/process\.kill\s*\(\s*(\d+)/);
13541
+ if (pidMatch?.[1]) {
13542
+ const pid = parseInt(pidMatch[1], 10);
13543
+ const result = await checkKillTarget({ pid, signal: "SIGTERM", cmd: fullCommand });
13544
+ if (result.blocked) return result;
13545
+ }
13546
+ return {
13547
+ blocked: true,
13548
+ reason: "Blocked: node -e with process.kill() \u2014 would target protected WrongStack process(es)."
13549
+ };
13550
+ }
13551
+ }
13552
+ }
13553
+ } else {
13554
+ if (cmdLower === "kill") {
13555
+ for (const a of args) {
13556
+ const num = a.replace(/^-/, "");
13557
+ if (/^\d+$/.test(num)) {
13558
+ const pid = parseInt(num, 10);
13559
+ const result = await checkKillTarget({ pid, signal: "SIGTERM", cmd: fullCommand });
13560
+ if (result.blocked) return result;
13561
+ }
13562
+ }
13563
+ }
13564
+ if (cmdLower === "pkill" || cmdLower === "killall") {
13565
+ const firstNonFlag = args.find((a) => !a.startsWith("-"));
13566
+ if (firstNonFlag) {
13567
+ const result = await checkKillTarget({
13568
+ name: firstNonFlag,
13569
+ signal: "SIGTERM",
13570
+ cmd: fullCommand
13571
+ });
13572
+ if (result.blocked) return result;
13573
+ }
13574
+ }
13575
+ }
13576
+ return { blocked: false };
13577
+ }
13578
+ function tokenizeShellCommand(command) {
13579
+ const tokens = [];
13580
+ let current = "";
13581
+ let quote = null;
13582
+ for (const char of command.trim()) {
13583
+ if (quote) {
13584
+ if (char === quote) quote = null;
13585
+ else current += char;
13586
+ continue;
13587
+ }
13588
+ if (char === '"' || char === "'") {
13589
+ quote = char;
13590
+ } else if (/\s/.test(char)) {
13591
+ if (current) {
13592
+ tokens.push(current);
13593
+ current = "";
13594
+ }
13595
+ } else {
13596
+ current += char;
13597
+ }
13598
+ }
13599
+ if (current) tokens.push(current);
13600
+ return tokens;
13601
+ }
13602
+ async function checkKillTarget(target) {
13603
+ const registry = getPersistentProcessRegistry();
13604
+ if (target.pid !== void 0) {
13605
+ const blocked = await registry.shouldBlockKill(target.pid);
13606
+ if (blocked) {
13607
+ return {
13608
+ blocked: true,
13609
+ reason: `Blocked: kill ${target.signal} PID ${target.pid} targets a protected WrongStack process (${target.cmd.slice(0, 80)}).`
13610
+ };
13611
+ }
13612
+ if (target.pid === process.pid) {
13613
+ return {
13614
+ blocked: true,
13615
+ reason: "Blocked: cannot kill the current WrongStack process."
13616
+ };
13617
+ }
13618
+ if (target.pid === process.ppid) {
13619
+ return {
13620
+ blocked: true,
13621
+ reason: "Blocked: cannot kill the parent terminal hosting WrongStack."
13622
+ };
13623
+ }
13624
+ return { blocked: false };
13625
+ }
13626
+ if (target.name) {
13627
+ const nameLower = target.name.toLowerCase().replace(/\.exe$/, "");
13628
+ if (nameLower.includes("wrongstack")) {
13629
+ return {
13630
+ blocked: true,
13631
+ reason: `Blocked: kill ${target.signal} '${target.name}' targets a WrongStack process name.`
13632
+ };
13633
+ }
13634
+ const currentImage = path24.basename(process.execPath).toLowerCase().replace(/\.exe$/, "");
13635
+ const targetsNodeRuntime = nameLower === "node" || nameLower.startsWith("node");
13636
+ if (targetsNodeRuntime && currentImage === "node") {
13637
+ return {
13638
+ blocked: true,
13639
+ reason: `Blocked: kill ${target.signal} '${target.name}' would kill the active WrongStack node.exe runtime.`
13640
+ };
13641
+ }
13642
+ const protectedPids = await registry.getAllProtectedPids();
13643
+ if (protectedPids.length > 0 && targetsNodeRuntime) {
13644
+ return {
13645
+ blocked: true,
13646
+ reason: `Blocked: kill ${target.signal} '${target.name}' would kill all node.exe processes including active WrongStack instance(s).`
13647
+ };
13648
+ }
13649
+ return { blocked: false };
13650
+ }
13651
+ return { blocked: false };
13652
+ }
13653
+
13654
+ // src/exec.ts
13655
+ var isWin4 = process.platform === "win32";
13216
13656
  var DEFAULT_ALLOWED_COMMANDS = /* @__PURE__ */ new Set([
13217
13657
  // JS / TS toolchain
13218
13658
  "node",
@@ -14017,6 +14457,19 @@ var execTool = {
14017
14457
  const args = (input.args ?? []).slice(0, MAX_ARGS);
14018
14458
  const timeout = Math.max(1, Math.min(input.timeout ?? DEFAULT_TIMEOUT_MS3, DEFAULT_TIMEOUT_MS3));
14019
14459
  const danger = detectDanger(cmd, args, dangerBypass);
14460
+ const killCheck = await checkExecKillCommand(cmd, args);
14461
+ if (killCheck.blocked) {
14462
+ return {
14463
+ command: cmd,
14464
+ args,
14465
+ stdout: "",
14466
+ stderr: killCheck.reason ?? "Kill command blocked: targets a protected WrongStack process.",
14467
+ exitCode: 1,
14468
+ truncated: false,
14469
+ allowed: false,
14470
+ danger
14471
+ };
14472
+ }
14020
14473
  const argError = validateArgs(cmd, args);
14021
14474
  if (argError) {
14022
14475
  return {
@@ -14067,7 +14520,7 @@ function runCommand(cmd, args, cwd, timeout, signal, sessionId, danger) {
14067
14520
  let timedOut = false;
14068
14521
  const spool = createOutputSpool({ tool: `exec-${cmd}`, thresholdBytes: MAX_OUTPUT2 });
14069
14522
  const resolved = resolveWin32Command(cmd);
14070
- const needsShell = isWin3 && (resolved.endsWith(".cmd") || resolved.endsWith(".bat"));
14523
+ const needsShell = isWin4 && (resolved.endsWith(".cmd") || resolved.endsWith(".bat"));
14071
14524
  const shim = needsShell ? buildWin32CmdShimInvocation(resolved, args) : null;
14072
14525
  const spawnCmd = shim?.command ?? resolved;
14073
14526
  const spawnArgs = shim?.args ?? args;
@@ -14092,7 +14545,7 @@ function runCommand(cmd, args, cwd, timeout, signal, sessionId, danger) {
14092
14545
  env: buildChildEnv2(sessionId),
14093
14546
  stdio: ["ignore", "pipe", "pipe"],
14094
14547
  windowsHide: true,
14095
- ...isWin3 ? {} : { signal },
14548
+ ...isWin4 ? {} : { signal },
14096
14549
  ...shim ? { windowsVerbatimArguments: shim.windowsVerbatimArguments } : {}
14097
14550
  });
14098
14551
  } catch (err) {
@@ -14131,7 +14584,7 @@ function runCommand(cmd, args, cwd, timeout, signal, sessionId, danger) {
14131
14584
  const isAbort = err && err.code === "ABORT_ERR";
14132
14585
  const stderrText = isAbort ? `Aborted: ${err.message}` : err.message;
14133
14586
  clearTimeout(timer);
14134
- if (isWin3) signal.removeEventListener("abort", onAbort);
14587
+ if (isWin4) signal.removeEventListener("abort", onAbort);
14135
14588
  if (typeof pid === "number") registry.unregister(pid);
14136
14589
  registry.afterCall(Date.now() - startedAt, true);
14137
14590
  spool.finalize();
@@ -14164,7 +14617,7 @@ function runCommand(cmd, args, cwd, timeout, signal, sessionId, danger) {
14164
14617
  if (typeof pid === "number") registry.kill(pid, { force: true });
14165
14618
  else child.kill("SIGTERM");
14166
14619
  };
14167
- if (isWin3) {
14620
+ if (isWin4) {
14168
14621
  if (signal.aborted) onAbort();
14169
14622
  else signal.addEventListener("abort", onAbort, { once: true });
14170
14623
  }
@@ -14184,7 +14637,7 @@ function runCommand(cmd, args, cwd, timeout, signal, sessionId, danger) {
14184
14637
  });
14185
14638
  child.on("close", (code) => {
14186
14639
  clearTimeout(timer);
14187
- if (isWin3) signal.removeEventListener("abort", onAbort);
14640
+ if (isWin4) signal.removeEventListener("abort", onAbort);
14188
14641
  if (typeof pid === "number") registry.unregister(pid);
14189
14642
  const durationMs = Date.now() - startedAt;
14190
14643
  const exitCode = killed ? 124 : code ?? 1;
@@ -14995,7 +15448,7 @@ function runGit2(args, cwd, signal) {
14995
15448
 
14996
15449
  // src/glob.ts
14997
15450
  import * as fs20 from "node:fs/promises";
14998
- import * as path24 from "node:path";
15451
+ import * as path25 from "node:path";
14999
15452
  import { compileGlob as compileGlob3 } from "@wrongstack/core";
15000
15453
 
15001
15454
  // src/_concurrency.ts
@@ -15099,7 +15552,7 @@ var globTool = {
15099
15552
  if (DEFAULT_IGNORE2.includes(name)) continue;
15100
15553
  if (ignored.includes(name)) continue;
15101
15554
  const rel = relPrefix ? `${relPrefix}/${name}` : name;
15102
- const full = path24.join(dir, name);
15555
+ const full = path25.join(dir, name);
15103
15556
  if (e.isDirectory()) {
15104
15557
  subdirs.push({ full, rel });
15105
15558
  } else if (e.isFile()) {
@@ -15143,7 +15596,7 @@ var globTool = {
15143
15596
  };
15144
15597
  async function readGitignore(dir) {
15145
15598
  try {
15146
- const raw = await fs20.readFile(path24.join(dir, ".gitignore"), "utf8");
15599
+ const raw = await fs20.readFile(path25.join(dir, ".gitignore"), "utf8");
15147
15600
  return raw.split("\n").map((l) => l.trim()).filter((l) => l && !l.startsWith("#"));
15148
15601
  } catch {
15149
15602
  return [];
@@ -15154,7 +15607,7 @@ async function readGitignore(dir) {
15154
15607
  import { expectDefined as expectDefined7 } from "@wrongstack/core";
15155
15608
  import { spawn as spawn10 } from "node:child_process";
15156
15609
  import * as fs21 from "node:fs/promises";
15157
- import * as path25 from "node:path";
15610
+ import * as path26 from "node:path";
15158
15611
  import { buildChildEnv as buildChildEnv5, compileGlob as compileGlob4, ToolValidationError as ToolValidationError4 } from "@wrongstack/core";
15159
15612
 
15160
15613
  // src/_regex.ts
@@ -15547,7 +16000,7 @@ async function runNative(input, base, mode, limit, signal) {
15547
16000
  if (stopped) return;
15548
16001
  if (DEFAULT_IGNORE3.includes(e.name)) continue;
15549
16002
  if (e.isSymbolicLink()) continue;
15550
- const full = path25.join(dir, e.name);
16003
+ const full = path26.join(dir, e.name);
15551
16004
  if (e.isDirectory()) {
15552
16005
  subdirs.push(full);
15553
16006
  } else if (e.isFile()) {
@@ -16135,56 +16588,56 @@ function jmespathSearch(data, query) {
16135
16588
  }
16136
16589
  function validateJsonSchema(data, schema) {
16137
16590
  const errors = [];
16138
- function check(value, s, path33) {
16591
+ function check(value, s, path34) {
16139
16592
  if (s["type"]) {
16140
16593
  const expectedType = s["type"];
16141
16594
  const actualType = Array.isArray(value) ? "array" : value === null ? "null" : typeof value;
16142
16595
  if (expectedType === "integer") {
16143
- if (!Number.isInteger(value)) errors.push(`${path33}: expected integer, got ${actualType}`);
16596
+ if (!Number.isInteger(value)) errors.push(`${path34}: expected integer, got ${actualType}`);
16144
16597
  } else if (expectedType !== actualType) {
16145
- errors.push(`${path33}: expected ${expectedType}, got ${actualType}`);
16598
+ errors.push(`${path34}: expected ${expectedType}, got ${actualType}`);
16146
16599
  }
16147
16600
  }
16148
16601
  if (typeof value === "string" && s["format"] === "uri" && value) {
16149
16602
  try {
16150
16603
  new URL(value);
16151
16604
  } catch {
16152
- errors.push(`${path33}: not a valid URI`);
16605
+ errors.push(`${path34}: not a valid URI`);
16153
16606
  }
16154
16607
  }
16155
16608
  if (typeof value === "string" && s["pattern"]) {
16156
16609
  const re = new RegExp(s["pattern"]);
16157
- if (!re.test(value)) errors.push(`${path33}: does not match pattern ${s["pattern"]}`);
16610
+ if (!re.test(value)) errors.push(`${path34}: does not match pattern ${s["pattern"]}`);
16158
16611
  }
16159
16612
  if (typeof value === "string" && s["minLength"] !== void 0 && value.length < s["minLength"]) {
16160
- errors.push(`${path33}: string too short (min ${s["minLength"]})`);
16613
+ errors.push(`${path34}: string too short (min ${s["minLength"]})`);
16161
16614
  }
16162
16615
  if (typeof value === "string" && s["maxLength"] !== void 0 && value.length > s["maxLength"]) {
16163
- errors.push(`${path33}: string too long (max ${s["maxLength"]})`);
16616
+ errors.push(`${path34}: string too long (max ${s["maxLength"]})`);
16164
16617
  }
16165
16618
  if (typeof value === "number" && s["minimum"] !== void 0 && value < s["minimum"]) {
16166
- errors.push(`${path33}: below minimum ${s["minimum"]}`);
16619
+ errors.push(`${path34}: below minimum ${s["minimum"]}`);
16167
16620
  }
16168
16621
  if (typeof value === "number" && s["maximum"] !== void 0 && value > s["maximum"]) {
16169
- errors.push(`${path33}: above maximum ${s["maximum"]}`);
16622
+ errors.push(`${path34}: above maximum ${s["maximum"]}`);
16170
16623
  }
16171
16624
  if (Array.isArray(value) && s["items"] && Array.isArray(s["items"])) {
16172
16625
  for (let i = 0; i < value.length; i++) {
16173
- check(value[i], s["items"], `${path33}[${i}]`);
16626
+ check(value[i], s["items"], `${path34}[${i}]`);
16174
16627
  }
16175
16628
  }
16176
16629
  if (typeof value === "object" && value !== null && !Array.isArray(value) && s["properties"]) {
16177
16630
  const props = s["properties"];
16178
16631
  for (const [k, propSchema] of Object.entries(props)) {
16179
- check(value[k], propSchema, `${path33}.${k}`);
16632
+ check(value[k], propSchema, `${path34}.${k}`);
16180
16633
  }
16181
16634
  }
16182
16635
  }
16183
16636
  check(data, schema, "$");
16184
16637
  return { valid: errors.length === 0, errors };
16185
16638
  }
16186
- function simpleQuery(data, path33) {
16187
- const parts = path33.replace(/\[(\d+)\]/g, ".$1").split(".").filter(Boolean);
16639
+ function simpleQuery(data, path34) {
16640
+ const parts = path34.replace(/\[(\d+)\]/g, ".$1").split(".").filter(Boolean);
16188
16641
  let current = data;
16189
16642
  for (const part of parts) {
16190
16643
  if (current === null || current === void 0) return void 0;
@@ -16252,7 +16705,7 @@ import {
16252
16705
  duplicateBoard,
16253
16706
  exportBoardAsMarkdown,
16254
16707
  exportBoardToTaskGraph,
16255
- generateBoardFromDescription,
16708
+ createBoardFromText,
16256
16709
  getBoard as getBoard2,
16257
16710
  getKanbanOrchestrationSnapshot,
16258
16711
  getKanbanQueueHealth,
@@ -16287,7 +16740,7 @@ import {
16287
16740
 
16288
16741
  // src/session-kanban.ts
16289
16742
  import { watch } from "node:fs";
16290
- import { basename as basename9, dirname as dirname13 } from "node:path";
16743
+ import { basename as basename10, dirname as dirname13 } from "node:path";
16291
16744
  import {
16292
16745
  deserializeTaskGraph,
16293
16746
  GlobalMailbox,
@@ -16317,12 +16770,17 @@ var SESSION_KANBAN_COLUMNS = [
16317
16770
  ];
16318
16771
  var boardQueue = /* @__PURE__ */ new Map();
16319
16772
  var boardEnsures = /* @__PURE__ */ new Map();
16773
+ var pendingMirrors = /* @__PURE__ */ new Map();
16774
+ var activeMirrors = /* @__PURE__ */ new Set();
16320
16775
  var bindings = /* @__PURE__ */ new WeakMap();
16321
16776
  var suppressedTodoMirrors = /* @__PURE__ */ new WeakSet();
16322
16777
  var activeSessionBoards = /* @__PURE__ */ new Map();
16323
16778
  function boardKey(projectRoot, sessionId) {
16324
16779
  return `${projectRoot}\0${sessionId}`;
16325
16780
  }
16781
+ function mirrorKey(projectRoot, sessionId, sourceSystem) {
16782
+ return `${boardKey(projectRoot, sessionId)}\0${sourceSystem}`;
16783
+ }
16326
16784
  function sessionTag(sessionId) {
16327
16785
  return `session:${sessionId}`;
16328
16786
  }
@@ -16458,6 +16916,43 @@ async function projectGraph(projectRoot, sessionId, graph, sourceSystem) {
16458
16916
  return result?.board ?? null;
16459
16917
  });
16460
16918
  }
16919
+ function queueLatestMirror(projectRoot, sessionId, graph, sourceSystem) {
16920
+ if (!projectRoot || !sessionId || process.env[MIRROR_DISABLED_ENV] === "0") return;
16921
+ const key = mirrorKey(projectRoot, sessionId, sourceSystem);
16922
+ pendingMirrors.set(key, { projectRoot, sessionId, graph, sourceSystem });
16923
+ if (activeMirrors.has(key)) return;
16924
+ activeMirrors.add(key);
16925
+ void (async () => {
16926
+ try {
16927
+ for (; ; ) {
16928
+ const pending2 = pendingMirrors.get(key);
16929
+ if (!pending2) break;
16930
+ pendingMirrors.delete(key);
16931
+ try {
16932
+ await projectGraph(
16933
+ pending2.projectRoot,
16934
+ pending2.sessionId,
16935
+ pending2.graph,
16936
+ pending2.sourceSystem
16937
+ );
16938
+ } catch {
16939
+ }
16940
+ }
16941
+ } finally {
16942
+ activeMirrors.delete(key);
16943
+ const pending2 = pendingMirrors.get(key);
16944
+ if (pending2) {
16945
+ pendingMirrors.delete(key);
16946
+ queueLatestMirror(
16947
+ pending2.projectRoot,
16948
+ pending2.sessionId,
16949
+ pending2.graph,
16950
+ pending2.sourceSystem
16951
+ );
16952
+ }
16953
+ }
16954
+ })();
16955
+ }
16461
16956
  function todoListToSerializedGraph(todos, sessionId) {
16462
16957
  const nodes = todos.map((todo, index) => ({
16463
16958
  id: todo.id,
@@ -16608,13 +17103,28 @@ Reassess your current plan before continuing; do not rely on the initial todo sn
16608
17103
  }
16609
17104
  }
16610
17105
  function mirrorSessionTodosToKanban(projectRoot, todos, sessionId) {
16611
- fireAndForget(projectSessionTodosToKanban(projectRoot, todos, sessionId));
17106
+ queueLatestMirror(
17107
+ projectRoot,
17108
+ sessionId,
17109
+ todoListToSerializedGraph(todos, sessionId),
17110
+ "session-todo"
17111
+ );
16612
17112
  }
16613
17113
  function mirrorSessionTasksToKanban(projectRoot, tasks, sessionId) {
16614
- fireAndForget(projectSessionTasksToKanban(projectRoot, tasks, sessionId));
17114
+ queueLatestMirror(
17115
+ projectRoot,
17116
+ sessionId,
17117
+ taskFileToSerializedGraph(tasks, sessionId),
17118
+ "session-task"
17119
+ );
16615
17120
  }
16616
17121
  function mirrorSessionPlanToKanban(projectRoot, items, sessionId) {
16617
- fireAndForget(projectSessionPlanToKanban(projectRoot, items, sessionId));
17122
+ queueLatestMirror(
17123
+ projectRoot,
17124
+ sessionId,
17125
+ planFileToSerializedGraph(items, sessionId),
17126
+ "session-plan"
17127
+ );
16618
17128
  }
16619
17129
  function attachSessionKanbanMirror(context) {
16620
17130
  const existing = bindings.get(context);
@@ -16712,8 +17222,8 @@ function attachSessionKanbanMirror(context) {
16712
17222
  const name = filename?.toString();
16713
17223
  const currentPlanPath = context.meta["plan.path"];
16714
17224
  const currentTaskPath = context.meta["task.path"];
16715
- const planName = typeof currentPlanPath === "string" ? basename9(currentPlanPath) : "";
16716
- const taskName = typeof currentTaskPath === "string" ? basename9(currentTaskPath) : "";
17225
+ const planName = typeof currentPlanPath === "string" ? basename10(currentPlanPath) : "";
17226
+ const taskName = typeof currentTaskPath === "string" ? basename10(currentTaskPath) : "";
16717
17227
  if (name && name !== planName && name !== taskName) return;
16718
17228
  if (timer) clearTimeout(timer);
16719
17229
  timer = setTimeout(() => fireAndForget(refreshFiles()), 60);
@@ -17158,7 +17668,7 @@ var kanbanTool = {
17158
17668
  }
17159
17669
  case "generate_board": {
17160
17670
  if (!input.description) return fail("generate_board requires description.");
17161
- const boardInput = generateBoardFromDescription({
17671
+ const boardInput = createBoardFromText({
17162
17672
  description: input.description,
17163
17673
  ...input.title !== void 0 ? { title: input.title } : {},
17164
17674
  ...input.context !== void 0 ? { context: input.context } : {},
@@ -17543,20 +18053,31 @@ var kanbanTool = {
17543
18053
  if (!input.boardId || !input.taskId)
17544
18054
  return fail("mark_assignment requires boardId and taskId.");
17545
18055
  const assignmentStatus = input.assignmentStatus ?? (input.status === "completed" ? "completed" : input.error ? "failed" : void 0);
17546
- const board = await updateTaskAssignment(projectRoot, input.boardId, input.taskId, {
17547
- ...assignmentStatus !== void 0 ? { status: assignmentStatus } : {},
17548
- ...input.subagentId !== void 0 ? { subagentId: input.subagentId } : {},
17549
- ...input.runTaskId !== void 0 ? { runTaskId: input.runTaskId } : {},
17550
- ...input.lastResult !== void 0 ? { lastResult: input.lastResult } : {},
17551
- ...input.error !== void 0 ? { error: input.error } : {},
17552
- ...input.agentId !== void 0 ? { agentId: input.agentId } : {},
17553
- ...input.leaseId !== void 0 ? { leaseId: input.leaseId } : {},
17554
- ...input.claimedAt !== void 0 ? { claimedAt: input.claimedAt } : {},
17555
- ...input.heartbeatAt !== void 0 ? { heartbeatAt: input.heartbeatAt } : {},
17556
- ...input.leaseExpiresAt !== void 0 ? { leaseExpiresAt: input.leaseExpiresAt } : {},
17557
- ...input.attempt !== void 0 ? { attempt: input.attempt } : {},
17558
- ...input.maxAttempts !== void 0 ? { maxAttempts: input.maxAttempts } : {}
17559
- });
18056
+ const board = await updateTaskAssignment(
18057
+ projectRoot,
18058
+ input.boardId,
18059
+ input.taskId,
18060
+ {
18061
+ ...assignmentStatus !== void 0 ? { status: assignmentStatus } : {},
18062
+ ...input.subagentId !== void 0 ? { subagentId: input.subagentId } : {},
18063
+ ...input.runTaskId !== void 0 ? { runTaskId: input.runTaskId } : {},
18064
+ ...input.lastResult !== void 0 ? { lastResult: input.lastResult } : {},
18065
+ ...input.error !== void 0 ? { error: input.error } : {},
18066
+ ...input.agentId !== void 0 ? { agentId: input.agentId } : {},
18067
+ ...input.leaseId !== void 0 ? { leaseId: input.leaseId } : {},
18068
+ ...input.claimedAt !== void 0 ? { claimedAt: input.claimedAt } : {},
18069
+ ...input.heartbeatAt !== void 0 ? { heartbeatAt: input.heartbeatAt } : {},
18070
+ ...input.leaseExpiresAt !== void 0 ? { leaseExpiresAt: input.leaseExpiresAt } : {},
18071
+ ...input.attempt !== void 0 ? { attempt: input.attempt } : {},
18072
+ ...input.maxAttempts !== void 0 ? { maxAttempts: input.maxAttempts } : {}
18073
+ },
18074
+ // Ownership fence: when expectedLeaseId is supplied, the write is
18075
+ // applied only if the current assignment still holds this lease.
18076
+ // This prevents a recovered+reassigned stale worker's terminal
18077
+ // mark_assignment from overwriting the successor's state. The check
18078
+ // is atomic inside updateTaskAssignment's mutateBoard lock.
18079
+ input.expectedLeaseId !== void 0 ? { expectedLeaseId: input.expectedLeaseId } : {}
18080
+ );
17560
18081
  return board ? okBoard(board, "Assignment updated.") : fail("Task not found.");
17561
18082
  }
17562
18083
  case "heartbeat_assignment": {
@@ -17565,7 +18086,13 @@ var kanbanTool = {
17565
18086
  }
17566
18087
  const board = await heartbeatTaskAssignment(projectRoot, input.boardId, input.taskId, {
17567
18088
  ...input.heartbeatAt !== void 0 ? { heartbeatAt: input.heartbeatAt } : {},
17568
- ...input.leaseExpiresAt !== void 0 ? { leaseExpiresAt: input.leaseExpiresAt } : {}
18089
+ ...input.leaseExpiresAt !== void 0 ? { leaseExpiresAt: input.leaseExpiresAt } : {},
18090
+ // Ownership fence: when expectedLeaseId is supplied, the renewal
18091
+ // is applied only if the current assignment still holds this lease.
18092
+ // This prevents a recovered+reassigned stale worker's heartbeat
18093
+ // from renewing the successor's lease. The check is atomic inside
18094
+ // heartbeatTaskAssignment's mutateBoard lock.
18095
+ ...input.expectedLeaseId !== void 0 ? { expectedLeaseId: input.expectedLeaseId } : {}
17569
18096
  });
17570
18097
  return board ? okBoard(board, "Assignment heartbeat updated.") : fail("Task assignment not found.");
17571
18098
  }
@@ -18156,7 +18683,7 @@ async function dockerLogs(service, lines, filterRe, cwd, signal, since) {
18156
18683
  }
18157
18684
  var DOCKER_LOGS_TIMEOUT_MS = 3e3;
18158
18685
  var MAX_TAIL_LINES = 1e5;
18159
- async function fileLogs(path33, lines, filterRe, stream) {
18686
+ async function fileLogs(path34, lines, filterRe, stream) {
18160
18687
  const { createInterface } = await import("node:readline");
18161
18688
  const { createReadStream: createReadStream2 } = await import("node:fs");
18162
18689
  const entries = [];
@@ -18165,7 +18692,7 @@ async function fileLogs(path33, lines, filterRe, stream) {
18165
18692
  let writeIdx = 0;
18166
18693
  let totalLines = 0;
18167
18694
  const rl = createInterface({
18168
- input: createReadStream2(path33),
18695
+ input: createReadStream2(path34),
18169
18696
  crlfDelay: Number.POSITIVE_INFINITY
18170
18697
  });
18171
18698
  for await (const line of rl) {
@@ -18186,7 +18713,7 @@ async function fileLogs(path33, lines, filterRe, stream) {
18186
18713
  if (parsed) entries.push(parsed);
18187
18714
  }
18188
18715
  return {
18189
- source: path33,
18716
+ source: path34,
18190
18717
  entries,
18191
18718
  total: entries.length,
18192
18719
  truncated: totalLines > effLines,
@@ -18417,8 +18944,8 @@ function parseOutdatedOutput(json2, exitCode) {
18417
18944
  init_util();
18418
18945
  import { spawn as spawn13 } from "node:child_process";
18419
18946
  import * as fs23 from "node:fs/promises";
18420
- import * as os7 from "node:os";
18421
- import * as path26 from "node:path";
18947
+ import * as os8 from "node:os";
18948
+ import * as path27 from "node:path";
18422
18949
  import { buildChildEnv as buildChildEnv8 } from "@wrongstack/core";
18423
18950
  var patchTool = {
18424
18951
  name: "patch",
@@ -18454,9 +18981,9 @@ var patchTool = {
18454
18981
  for (const t of targets) {
18455
18982
  const stripped = stripPathComponents(t, strip);
18456
18983
  if (!stripped) continue;
18457
- const candidate = path26.resolve(dir, stripped);
18458
- const rel = path26.relative(ctx.projectRoot, candidate);
18459
- if (rel.startsWith("..") || path26.isAbsolute(rel)) {
18984
+ const candidate = path27.resolve(dir, stripped);
18985
+ const rel = path27.relative(ctx.projectRoot, candidate);
18986
+ if (rel.startsWith("..") || path27.isAbsolute(rel)) {
18460
18987
  return {
18461
18988
  applied: 0,
18462
18989
  rejected: 1,
@@ -18473,11 +19000,11 @@ var patchTool = {
18473
19000
  beforeContents.set(target, await readTextForTracking(target));
18474
19001
  }
18475
19002
  }
18476
- const tmpDir = await fs23.mkdtemp(path26.join(os7.tmpdir(), ".wstack_patch_"));
19003
+ const tmpDir = await fs23.mkdtemp(path27.join(os8.tmpdir(), ".wstack_patch_"));
18477
19004
  try {
18478
19005
  await fs23.chmod(tmpDir, 448).catch(() => {
18479
19006
  });
18480
- const patchFile = path26.join(tmpDir, "in.diff");
19007
+ const patchFile = path27.join(tmpDir, "in.diff");
18481
19008
  await fs23.writeFile(patchFile, input.patch, { mode: 384 });
18482
19009
  const args = [`-p${strip}`, "--merge", ...dryRun ? ["--dry-run"] : [], "-i", patchFile];
18483
19010
  const result = await runPatch(args, dir, opts.signal);
@@ -19063,7 +19590,7 @@ ${interesting.join("\n")}` : "symbols/imports: (none detected)"
19063
19590
  import { expectDefined as expectDefined8 } from "@wrongstack/core";
19064
19591
  import { spawn as spawn14 } from "node:child_process";
19065
19592
  import * as fs25 from "node:fs/promises";
19066
- import * as path27 from "node:path";
19593
+ import * as path28 from "node:path";
19067
19594
  import {
19068
19595
  atomicWrite as atomicWrite3,
19069
19596
  buildChildEnv as buildChildEnv9,
@@ -19152,8 +19679,8 @@ var replaceTool = {
19152
19679
  } catch {
19153
19680
  continue;
19154
19681
  }
19155
- const rel = path27.relative(realRoot, realPath);
19156
- if (rel.startsWith("..") || path27.isAbsolute(rel)) continue;
19682
+ const rel = path28.relative(realRoot, realPath);
19683
+ if (rel.startsWith("..") || path28.isAbsolute(rel)) continue;
19157
19684
  const stat18 = await fs25.stat(realPath).catch(() => null);
19158
19685
  if (!stat18?.isFile()) continue;
19159
19686
  let content;
@@ -19282,7 +19809,7 @@ async function globNative(pattern, base, extraGlob) {
19282
19809
  }
19283
19810
  for (const e of entries) {
19284
19811
  if (DEFAULT_IGNORE4.includes(e.name)) continue;
19285
- const full = path27.join(dir, e.name);
19812
+ const full = path28.join(dir, e.name);
19286
19813
  try {
19287
19814
  const stat18 = await fs25.lstat(full);
19288
19815
  if (stat18.isSymbolicLink()) continue;
@@ -19309,7 +19836,7 @@ async function globNative(pattern, base, extraGlob) {
19309
19836
  // src/scaffold.ts
19310
19837
  init_util();
19311
19838
  import * as fs26 from "node:fs/promises";
19312
- import * as path28 from "node:path";
19839
+ import * as path29 from "node:path";
19313
19840
  import { atomicWrite as atomicWrite4 } from "@wrongstack/core";
19314
19841
  var BUILT_IN_TEMPLATES = {
19315
19842
  "npm-package": {
@@ -19459,16 +19986,16 @@ async function handleBuiltIn(name, templateFiles, cwd, ctx, dryRun, vars) {
19459
19986
  let filesCreated = 0;
19460
19987
  for (const [filePath, content] of Object.entries(templateFiles)) {
19461
19988
  const resolvedPath = substituteVars(filePath, name, vars);
19462
- const joinedPath = path28.join(cwd, resolvedPath);
19463
- const root = path28.resolve(ctx.projectRoot);
19464
- const target = path28.resolve(joinedPath);
19465
- const rel = path28.relative(root, target);
19466
- if (rel.startsWith("..") || path28.isAbsolute(rel)) {
19989
+ const joinedPath = path29.join(cwd, resolvedPath);
19990
+ const root = path29.resolve(ctx.projectRoot);
19991
+ const target = path29.resolve(joinedPath);
19992
+ const rel = path29.relative(root, target);
19993
+ if (rel.startsWith("..") || path29.isAbsolute(rel)) {
19467
19994
  throw new Error(`scaffold: generated path "${resolvedPath}" would escape project root`);
19468
19995
  }
19469
19996
  const fullPath = target;
19470
19997
  if (!dryRun) {
19471
- await fs26.mkdir(path28.dirname(fullPath), { recursive: true });
19998
+ await fs26.mkdir(path29.dirname(fullPath), { recursive: true });
19472
19999
  await atomicWrite4(fullPath, substituteVars(content, name, vars));
19473
20000
  }
19474
20001
  files.push(resolvedPath);
@@ -20325,7 +20852,7 @@ ${formatTaskList2(file.tasks)}` : file.tasks.length > 0 ? formatTaskList2(file.t
20325
20852
  init_spawn_stream();
20326
20853
  init_util();
20327
20854
  init_legacy_bridge();
20328
- import * as path29 from "node:path";
20855
+ import * as path30 from "node:path";
20329
20856
  var testTool = {
20330
20857
  name: "test",
20331
20858
  category: "Code Quality",
@@ -20432,7 +20959,7 @@ async function detectRunner(cwd) {
20432
20959
  const candidates = ["vitest.config.ts", "jest.config.js", ".mocharc.json"];
20433
20960
  for (const f of candidates) {
20434
20961
  try {
20435
- await stat18(path29.join(cwd, f));
20962
+ await stat18(path30.join(cwd, f));
20436
20963
  if (f.includes("vitest")) return "vitest";
20437
20964
  if (f.includes("jest")) return "jest";
20438
20965
  if (f.includes("mocha")) return "mocha";
@@ -20903,7 +21430,7 @@ var toolUseTool = {
20903
21430
  init_util();
20904
21431
  import { expectDefined as expectDefined10 } from "@wrongstack/core";
20905
21432
  import * as fs28 from "node:fs/promises";
20906
- import * as path30 from "node:path";
21433
+ import * as path31 from "node:path";
20907
21434
  var DEFAULT_IGNORE5 = [
20908
21435
  "node_modules",
20909
21436
  ".git",
@@ -21074,7 +21601,7 @@ async function walkDir(dir, depth, opts) {
21074
21601
  opts.lines.push(opts.prefix + branch + displayName);
21075
21602
  if (entry.isDirectory() && (opts.maxDepth === 0 || depth < opts.maxDepth)) {
21076
21603
  const childPrefix = opts.prefix + connector;
21077
- await walkDir(path30.join(dir, entry.name), depth + 1, {
21604
+ await walkDir(path31.join(dir, entry.name), depth + 1, {
21078
21605
  ...opts,
21079
21606
  prefix: childPrefix,
21080
21607
  isLast
@@ -21087,7 +21614,7 @@ async function walkDir(dir, depth, opts) {
21087
21614
  init_spawn_stream();
21088
21615
  init_util();
21089
21616
  init_legacy_bridge();
21090
- import * as path31 from "node:path";
21617
+ import * as path32 from "node:path";
21091
21618
  var typecheckTool = {
21092
21619
  name: "typecheck",
21093
21620
  category: "Code Quality",
@@ -21190,8 +21717,8 @@ async function findTsConfig(cwd) {
21190
21717
  const candidates = ["tsconfig.json", "tsconfig.base.json"];
21191
21718
  for (const f of candidates) {
21192
21719
  try {
21193
- const s = await stat18(path31.join(cwd, f));
21194
- if (s.isFile()) return path31.join(cwd, f);
21720
+ const s = await stat18(path32.join(cwd, f));
21721
+ if (s.isFile()) return path32.join(cwd, f);
21195
21722
  } catch {
21196
21723
  }
21197
21724
  }
@@ -21724,7 +22251,7 @@ var builtinToolsPack = {
21724
22251
  };
21725
22252
 
21726
22253
  // src/process-guardian.ts
21727
- import * as os8 from "node:os";
22254
+ import * as os9 from "node:os";
21728
22255
  var ProcessGuardian = class {
21729
22256
  registry;
21730
22257
  config;
@@ -21763,7 +22290,7 @@ var ProcessGuardian = class {
21763
22290
  event: "process_guardian.started",
21764
22291
  instanceId: this.instanceId,
21765
22292
  mainPid: process.pid,
21766
- hostname: os8.hostname(),
22293
+ hostname: os9.hostname(),
21767
22294
  platform: process.platform
21768
22295
  }));
21769
22296
  }
@@ -21952,8 +22479,8 @@ var ProcessGuardian = class {
21952
22479
  instanceId: this.instanceId,
21953
22480
  mainPid: process.pid,
21954
22481
  protectedCount: this.protectedProcesses.size,
21955
- platform: os8.platform(),
21956
- hostname: os8.hostname(),
22482
+ platform: os9.platform(),
22483
+ hostname: os9.hostname(),
21957
22484
  uptime: process.uptime()
21958
22485
  };
21959
22486
  }
@@ -21982,7 +22509,7 @@ function stopProcessGuardian() {
21982
22509
  init_process_registry();
21983
22510
 
21984
22511
  // src/ps-slash.ts
21985
- import * as os9 from "node:os";
22512
+ import * as os10 from "node:os";
21986
22513
  var IDLE_THRESHOLD_MS = 2 * 6e4;
21987
22514
  var STALE_THRESHOLD_MS2 = 5 * 6e4;
21988
22515
  function now() {
@@ -22023,7 +22550,7 @@ async function listInstances(options = {}) {
22023
22550
  const mainProc = processes.find((p) => p.spawnMode === "main");
22024
22551
  const firstProc = processes.at(0);
22025
22552
  const mainPid = mainProc?.pid ?? firstProc?.pid ?? 0;
22026
- const hostname_ = firstProc?.hostname ?? os9.hostname();
22553
+ const hostname_ = firstProc?.hostname ?? os10.hostname();
22027
22554
  const startedAt = Math.min(...processes.map((p) => p.startedAt));
22028
22555
  const lastActivity = Math.max(...processes.map((p) => p.lastHeartbeat));
22029
22556
  const age = timestamp - lastActivity;
@@ -22111,7 +22638,7 @@ async function getGlobalProcessStatus() {
22111
22638
  mainPid: process.pid,
22112
22639
  protectedCount: 0,
22113
22640
  platform: process.platform,
22114
- hostname: os9.hostname(),
22641
+ hostname: os10.hostname(),
22115
22642
  uptime: 0
22116
22643
  },
22117
22644
  allInstances: instances.map((inst) => ({
@@ -22296,7 +22823,7 @@ function createGlobalPsSlashCommand() {
22296
22823
 
22297
22824
  // src/skill.ts
22298
22825
  import * as fs30 from "node:fs/promises";
22299
- import * as path32 from "node:path";
22826
+ import * as path33 from "node:path";
22300
22827
  import {
22301
22828
  SKILL_LIMITS,
22302
22829
  stripFrontmatter,
@@ -22343,7 +22870,7 @@ function makeSkillTool(skillLoader) {
22343
22870
  field: "name"
22344
22871
  });
22345
22872
  }
22346
- const dir = path32.dirname(manifest.path);
22873
+ const dir = path33.dirname(manifest.path);
22347
22874
  let loadedResource;
22348
22875
  if (input.resource?.trim()) {
22349
22876
  loadedResource = await loadResource(dir, input.resource.trim());
@@ -22393,15 +22920,15 @@ ${listing}`;
22393
22920
  }
22394
22921
  async function loadResource(skillDir, rel) {
22395
22922
  const norm = rel.replace(/\\/g, "/");
22396
- if (path32.isAbsolute(rel) || norm.split("/").some((seg) => seg === "..")) {
22923
+ if (path33.isAbsolute(rel) || norm.split("/").some((seg) => seg === "..")) {
22397
22924
  throw new ToolValidationError10({
22398
22925
  message: `skill: invalid resource path "${rel}"`,
22399
22926
  field: "resource"
22400
22927
  });
22401
22928
  }
22402
- const absPath = path32.resolve(skillDir, rel);
22403
- const root = path32.resolve(skillDir);
22404
- if (absPath !== root && !absPath.startsWith(root + path32.sep)) {
22929
+ const absPath = path33.resolve(skillDir, rel);
22930
+ const root = path33.resolve(skillDir);
22931
+ if (absPath !== root && !absPath.startsWith(root + path33.sep)) {
22405
22932
  throw new ToolValidationError10({
22406
22933
  message: `skill: resource "${rel}" escapes the skill directory`,
22407
22934
  field: "resource"
@@ -22442,7 +22969,7 @@ async function walk(root, dir, out) {
22442
22969
  }
22443
22970
  for (const e of entries) {
22444
22971
  if (out.length >= MAX_LISTED_RESOURCES) return;
22445
- const fullPath = path32.join(dir, e.name);
22972
+ const fullPath = path33.join(dir, e.name);
22446
22973
  let isDir = e.isDirectory();
22447
22974
  if (e.isSymbolicLink()) {
22448
22975
  try {
@@ -22458,7 +22985,7 @@ async function walk(root, dir, out) {
22458
22985
  if (e.name === "SKILL.md" || e.name === "SKILL.save.md") continue;
22459
22986
  try {
22460
22987
  const stat18 = await fs30.stat(fullPath);
22461
- const rel = path32.relative(root, fullPath).split(path32.sep).join("/");
22988
+ const rel = path33.relative(root, fullPath).split(path33.sep).join("/");
22462
22989
  out.push({ path: rel, bytes: stat18.size });
22463
22990
  } catch {
22464
22991
  }
@@ -22681,6 +23208,7 @@ export {
22681
23208
  builtinTools,
22682
23209
  builtinToolsPack,
22683
23210
  cancelPendingReindexes,
23211
+ checkExecKillCommand,
22684
23212
  codebaseIndexStats,
22685
23213
  codebaseIndexTool,
22686
23214
  codebaseSearchTool,