@wrongstack/tools 1.0.8 → 1.0.9

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 (135) hide show
  1. package/dist/_win32-resolve.d.ts +5 -0
  2. package/dist/audit.js +5 -5
  3. package/dist/bash.js +2 -2
  4. package/dist/browser/index.js +1 -1
  5. package/dist/builtin.js +41 -41
  6. package/dist/{chunk-N3OTTQNC.js → chunk-2OCAAFAT.js} +44 -25
  7. package/dist/{chunk-5MP76C72.js → chunk-2UVAIS5O.js} +24 -29
  8. package/dist/{chunk-SIIOHBNY.js → chunk-3RRK33YM.js} +2 -2
  9. package/dist/{chunk-M2W36JT7.js → chunk-4CE3RKEP.js} +6 -3
  10. package/dist/{chunk-QJARSK2G.js → chunk-4SUGLJLO.js} +11 -3
  11. package/dist/{chunk-AH7J5HWL.js → chunk-5RBUYFRV.js} +15 -7
  12. package/dist/{chunk-WKLUOXR4.js → chunk-7GOE2ZTZ.js} +2 -2
  13. package/dist/{chunk-O6AGFJBO.js → chunk-7V33PHT6.js} +49 -77
  14. package/dist/{chunk-CAF4L6KN.js → chunk-C3WP6ZOT.js} +36 -48
  15. package/dist/{chunk-KMXPP32M.js → chunk-C6ZOXNXV.js} +13 -3
  16. package/dist/{chunk-EGT5QLCB.js → chunk-D6RQZMZS.js} +123 -255
  17. package/dist/{chunk-S6CGSFLX.js → chunk-DBG7UQGK.js} +10 -3
  18. package/dist/{chunk-FI6SOIVE.js → chunk-DLZCOEVG.js} +21 -3
  19. package/dist/{chunk-NL45OWEO.js → chunk-DOAHONOS.js} +7 -12
  20. package/dist/{chunk-3LQJXJDM.js → chunk-E7A4SLKR.js} +2 -2
  21. package/dist/{chunk-JB5JLY7V.js → chunk-ENE22I5W.js} +387 -394
  22. package/dist/{chunk-SEONDNLN.js → chunk-G7CYW5EP.js} +2 -2
  23. package/dist/{chunk-MF6ZUBI7.js → chunk-HV7RWDCL.js} +3 -3
  24. package/dist/{chunk-TU6UVATU.js → chunk-HYHE4VNA.js} +3 -3
  25. package/dist/{chunk-HIRVCXLP.js → chunk-IE4KDAOB.js} +3 -3
  26. package/dist/{chunk-YA2ZYCJY.js → chunk-JPK2HHBK.js} +1 -1
  27. package/dist/{chunk-7E55DAFW.js → chunk-KHUCKLOR.js} +12 -7
  28. package/dist/{chunk-R3HIZVFS.js → chunk-KPDCKSN5.js} +23 -3
  29. package/dist/{chunk-B3MGJBFI.js → chunk-KS2OEZWV.js} +23 -47
  30. package/dist/{chunk-UI3ZJIG3.js → chunk-LKLIQUCW.js} +14 -8
  31. package/dist/{chunk-62OAECEQ.js → chunk-LLSFOUZF.js} +8 -8
  32. package/dist/{chunk-YZQSUL5G.js → chunk-M4ZHHCCW.js} +14 -6
  33. package/dist/{chunk-IXIS7RSG.js → chunk-MRXWOOOR.js} +29 -47
  34. package/dist/{chunk-C4DSQBTT.js → chunk-NEYUPZL6.js} +3 -3
  35. package/dist/{chunk-6XYFYLN7.js → chunk-O2MHULBO.js} +13 -8
  36. package/dist/{chunk-B62LKMWQ.js → chunk-OKZJJDOG.js} +86 -188
  37. package/dist/{chunk-HOFVYIPI.js → chunk-ON6ET5ER.js} +16 -1
  38. package/dist/{chunk-KF7GMVNU.js → chunk-OUERRFEM.js} +32 -11
  39. package/dist/{chunk-GNLHBDFO.js → chunk-OXO773CW.js} +127 -196
  40. package/dist/{chunk-GF4JHLCO.js → chunk-PWZUHZP7.js} +2 -2
  41. package/dist/{chunk-5KRKL3WD.js → chunk-QA37PFYL.js} +28 -67
  42. package/dist/{chunk-OGQMPX3J.js → chunk-QAWI3RSK.js} +37 -81
  43. package/dist/{chunk-ZMCV3XOV.js → chunk-SPNXDTCJ.js} +29 -63
  44. package/dist/{chunk-H2VB7GCR.js → chunk-ULI2J6TX.js} +27 -14
  45. package/dist/{chunk-EMA2XENR.js → chunk-UVYRQKPZ.js} +4 -3
  46. package/dist/{chunk-2NTHMZV3.js → chunk-VMJY6FYJ.js} +693 -276
  47. package/dist/{chunk-YUK3WLHJ.js → chunk-WEUEU53R.js} +2 -2
  48. package/dist/{chunk-ZOUG3KKS.js → chunk-XPVYZ2S6.js} +2 -2
  49. package/dist/{chunk-5U5ULQX2.js → chunk-XZLVJD56.js} +5 -1
  50. package/dist/{chunk-HCPSVK7J.js → chunk-YKSOSI2N.js} +47 -31
  51. package/dist/codebase-index/codebase-ast-replace-tool.d.ts +5 -6
  52. package/dist/codebase-index/codebase-context-tool.d.ts +4 -6
  53. package/dist/codebase-index/codebase-impact-analysis-tool.d.ts +1 -2
  54. package/dist/codebase-index/codebase-incoming-calls-tool.d.ts +0 -2
  55. package/dist/codebase-index/codebase-index-tool.d.ts +0 -2
  56. package/dist/codebase-index/codebase-invariant-check-tool.d.ts +1 -2
  57. package/dist/codebase-index/codebase-outgoing-calls-tool.d.ts +0 -2
  58. package/dist/codebase-index/codebase-repo-map-tool.d.ts +1 -2
  59. package/dist/codebase-index/codebase-search-tool.d.ts +1 -1
  60. package/dist/codebase-index/codebase-targeted-test-tool.d.ts +1 -2
  61. package/dist/codebase-index/index.js +8 -8
  62. package/dist/codebase-index/parser-worker-script.js +3 -3
  63. package/dist/codebase-index/project-server.js +5 -5
  64. package/dist/codebase-index/worker.js +4 -4
  65. package/dist/design.js +1 -1
  66. package/dist/diff.js +1 -1
  67. package/dist/edit.js +7 -7
  68. package/dist/exec.d.ts +7 -7
  69. package/dist/exec.js +2 -2
  70. package/dist/format.js +5 -5
  71. package/dist/git.js +1 -1
  72. package/dist/glob.js +1 -1
  73. package/dist/{go-parser-VDPRQEGM.js → go-parser-Z4H6SR3R.js} +6 -3
  74. package/dist/grep.js +1 -1
  75. package/dist/index.js +45 -45
  76. package/dist/install.js +5 -5
  77. package/dist/json.d.ts +0 -1
  78. package/dist/json.js +1 -1
  79. package/dist/kanban-presence.d.ts +6 -0
  80. package/dist/kanban-serializer.d.ts +9 -1
  81. package/dist/kanban-split-task-handler.d.ts +1 -1
  82. package/dist/kanban-tool-results.d.ts +57 -2
  83. package/dist/kanban-tool-types.d.ts +16 -5
  84. package/dist/kanban.d.ts +14 -0
  85. package/dist/kanban.js +9 -1
  86. package/dist/languages/index.js +3 -3
  87. package/dist/{legacy-bridge-4T4OTSQP.js → legacy-bridge-BPT2S2F4.js} +5 -5
  88. package/dist/lint.js +5 -5
  89. package/dist/logs.js +1 -1
  90. package/dist/mode.d.ts +3 -2
  91. package/dist/mode.js +1 -1
  92. package/dist/outdated.js +2 -2
  93. package/dist/pack.js +42 -42
  94. package/dist/{parser-dispatch-4E2SDQUO.js → parser-dispatch-WG4DNBHH.js} +4 -4
  95. package/dist/patch.js +7 -7
  96. package/dist/plan.d.ts +2 -1
  97. package/dist/plan.js +3 -3
  98. package/dist/{py-parser-MAW6DHFE.js → py-parser-4PFZYAJ7.js} +3 -3
  99. package/dist/read.js +7 -7
  100. package/dist/replace.js +7 -7
  101. package/dist/search.d.ts +0 -6
  102. package/dist/search.js +1 -1
  103. package/dist/security-ast-scan-tool.d.ts +1 -2
  104. package/dist/session-shell.js +2 -2
  105. package/dist/task.d.ts +2 -1
  106. package/dist/task.js +3 -3
  107. package/dist/test.d.ts +8 -1
  108. package/dist/test.js +5 -5
  109. package/dist/todo.js +2 -2
  110. package/dist/tool-search.d.ts +2 -0
  111. package/dist/tool-search.js +1 -1
  112. package/dist/tool-tier.js +43 -43
  113. package/dist/tool-use.d.ts +2 -2
  114. package/dist/tool-use.js +1 -1
  115. package/dist/tree.js +1 -1
  116. package/dist/typecheck.js +5 -5
  117. package/dist/wasm/bash/tree-sitter-bash.wasm +0 -0
  118. package/dist/wasm/c/tree-sitter-c.wasm +0 -0
  119. package/dist/wasm/c_sharp/tree-sitter-c_sharp.wasm +0 -0
  120. package/dist/wasm/checksums.json +16 -0
  121. package/dist/wasm/cpp/tree-sitter-cpp.wasm +0 -0
  122. package/dist/wasm/elixir/tree-sitter-elixir.wasm +0 -0
  123. package/dist/wasm/go/tree-sitter-go.wasm +0 -0
  124. package/dist/wasm/java/tree-sitter-java.wasm +0 -0
  125. package/dist/wasm/kotlin/tree-sitter-kotlin.wasm +0 -0
  126. package/dist/wasm/php/tree-sitter-php.wasm +0 -0
  127. package/dist/wasm/python/tree-sitter-python.wasm +0 -0
  128. package/dist/wasm/ruby/tree-sitter-ruby.wasm +0 -0
  129. package/dist/wasm/rust/tree-sitter-rust.wasm +0 -0
  130. package/dist/wasm/swift/tree-sitter-swift.wasm +0 -0
  131. package/dist/wasm/tree-sitter-runtime.wasm +0 -0
  132. package/dist/win32.d.ts +1 -1
  133. package/dist/win32.js +3 -1
  134. package/dist/write.js +7 -7
  135. package/package.json +10 -10
@@ -3,7 +3,7 @@ import {
3
3
  } from "./chunk-XRNN44QY.js";
4
4
  import {
5
5
  spawnStream
6
- } from "./chunk-EMA2XENR.js";
6
+ } from "./chunk-UVYRQKPZ.js";
7
7
  import {
8
8
  codebaseContext,
9
9
  codebaseIndexStats,
@@ -15,7 +15,7 @@ import {
15
15
  outgoingCallsService,
16
16
  runStartupIndex,
17
17
  searchCodebaseIndex
18
- } from "./chunk-TU6UVATU.js";
18
+ } from "./chunk-HYHE4VNA.js";
19
19
  import {
20
20
  IndexTimeoutError,
21
21
  SCHEMA_VERSION,
@@ -26,7 +26,7 @@ import {
26
26
  posixIndexPath,
27
27
  resolveIndexDir,
28
28
  xxhash64String
29
- } from "./chunk-C4DSQBTT.js";
29
+ } from "./chunk-NEYUPZL6.js";
30
30
  import {
31
31
  EXT_TO_LANG,
32
32
  detectLang,
@@ -477,6 +477,16 @@ async function loadTs2() {
477
477
  }
478
478
  return tsModule2;
479
479
  }
480
+ function indentAt(content, pos) {
481
+ const lineStart = content.lastIndexOf("\n", pos - 1) + 1;
482
+ return /^[ \t]*/.exec(content.slice(lineStart, pos))?.[0] ?? "";
483
+ }
484
+ function reindent(text, indent) {
485
+ const lines = text.replace(/^\s*\n/, "").trimEnd().split("\n");
486
+ const widths = lines.filter((l) => l.trim()).map((l) => /^[ \t]*/.exec(l)?.[0].length ?? 0);
487
+ const common = widths.length > 0 ? Math.min(...widths) : 0;
488
+ return lines.map((l) => l.trim() ? `${indent}${l.slice(common)}` : "").join("\n");
489
+ }
480
490
  async function mutateTsSymbol(content, lang, symbolName, newBody, target = "body") {
481
491
  const ts = await loadTs2();
482
492
  const scriptKind = lang === "tsx" ? ts.ScriptKind.TSX : lang === "jsx" ? ts.ScriptKind.JSX : lang === "js" ? ts.ScriptKind.JS : ts.ScriptKind.TS;
@@ -487,49 +497,88 @@ async function mutateTsSymbol(content, lang, symbolName, newBody, target = "body
487
497
  true,
488
498
  scriptKind
489
499
  );
490
- let targetNode = null;
491
- let bodyNode = null;
500
+ const nameOf = (node) => {
501
+ const n = node.name;
502
+ if (!n) return void 0;
503
+ return ts.isIdentifier(n) || ts.isPrivateIdentifier(n) || ts.isStringLiteral(n) ? n.text : void 0;
504
+ };
505
+ const matches = [];
506
+ const scope = [];
492
507
  function visit(node) {
493
- if (targetNode) return;
494
- if (ts.isFunctionDeclaration(node) || ts.isMethodDeclaration(node) || ts.isConstructorDeclaration(node) || ts.isGetAccessor(node) || ts.isSetAccessor(node) || ts.isClassDeclaration(node) || ts.isInterfaceDeclaration(node)) {
495
- let name;
496
- if ("name" in node && node.name && ts.isIdentifier(node.name)) {
497
- name = node.name.text;
498
- }
499
- if (name === symbolName) {
500
- targetNode = node;
501
- if ("body" in node && node.body) {
502
- bodyNode = node.body;
503
- }
504
- return;
505
- }
506
- } else if (ts.isVariableStatement(node)) {
507
- for (const decl of node.declarationList.declarations) {
508
- if (ts.isIdentifier(decl.name) && decl.name.text === symbolName) {
509
- targetNode = decl;
510
- if (decl.initializer && (ts.isArrowFunction(decl.initializer) || ts.isFunctionExpression(decl.initializer))) {
511
- bodyNode = decl.initializer.body;
512
- }
513
- return;
514
- }
515
- }
508
+ let declared;
509
+ let body = null;
510
+ if (ts.isFunctionDeclaration(node) || ts.isMethodDeclaration(node) || ts.isGetAccessor(node) || ts.isSetAccessor(node)) {
511
+ declared = nameOf(node);
512
+ body = node.body ?? null;
513
+ } else if (ts.isConstructorDeclaration(node)) {
514
+ declared = "constructor";
515
+ body = node.body ?? null;
516
+ } else if (ts.isClassDeclaration(node) || ts.isInterfaceDeclaration(node) || ts.isEnumDeclaration(node) || ts.isModuleDeclaration(node)) {
517
+ declared = nameOf(node);
518
+ } else if (ts.isTypeAliasDeclaration(node)) {
519
+ declared = nameOf(node);
520
+ body = node.type;
521
+ } else if (ts.isVariableDeclaration(node) || ts.isPropertyDeclaration(node)) {
522
+ declared = nameOf(node);
523
+ const init = node.initializer;
524
+ if (init && (ts.isArrowFunction(init) || ts.isFunctionExpression(init))) {
525
+ body = init.body;
526
+ }
527
+ }
528
+ if (declared === void 0) {
529
+ ts.forEachChild(node, visit);
530
+ return;
516
531
  }
532
+ matches.push({
533
+ node,
534
+ body,
535
+ qualified: [...scope, declared].join("."),
536
+ line: sourceFile.getLineAndCharacterOfPosition(node.getStart(sourceFile)).line + 1
537
+ });
538
+ scope.push(declared);
517
539
  ts.forEachChild(node, visit);
540
+ scope.pop();
518
541
  }
519
542
  ts.forEachChild(sourceFile, visit);
520
- if (!targetNode) return null;
521
- const replaceTarget = target === "full" || !bodyNode ? targetNode : bodyNode;
543
+ let candidates = matches.filter(
544
+ (m) => m.qualified === symbolName || m.qualified.endsWith(`.${symbolName}`)
545
+ );
546
+ if (candidates.length === 0) return null;
547
+ if (candidates.length > 1) {
548
+ const first = candidates[0]?.qualified;
549
+ const overloadable = candidates.every(
550
+ (m) => m.qualified === first && (ts.isFunctionDeclaration(m.node) || ts.isMethodDeclaration(m.node) || ts.isConstructorDeclaration(m.node))
551
+ );
552
+ const implemented = candidates.filter((m) => m.body !== null);
553
+ if (overloadable && implemented.length === 1) candidates = implemented;
554
+ }
555
+ if (candidates.length > 1) {
556
+ const listed = candidates.map((m) => `${m.qualified} (L${m.line})`).join(", ");
557
+ const example = candidates.find((m) => m.qualified.includes("."))?.qualified;
558
+ throw new Error(
559
+ `Symbol '${symbolName}' is ambiguous: ${candidates.length} declarations match \u2014 ${listed}. ` + (example ? `Pass a qualified name, e.g. symbol: "${example}".` : "Rename-free disambiguation is impossible here; use the edit tool.")
560
+ );
561
+ }
562
+ const match = candidates[0];
563
+ let fullNode = match.node;
564
+ if (ts.isVariableDeclaration(match.node)) {
565
+ const list = match.node.parent;
566
+ const stmt = list?.parent;
567
+ if (list && ts.isVariableDeclarationList(list) && list.declarations.length === 1 && stmt && ts.isVariableStatement(stmt)) {
568
+ fullNode = stmt;
569
+ }
570
+ }
571
+ const replaceTarget = target === "full" || !match.body ? fullNode : match.body;
522
572
  const startPos = replaceTarget.getStart(sourceFile);
523
573
  const endPos = replaceTarget.getEnd();
524
574
  const startLine = sourceFile.getLineAndCharacterOfPosition(startPos).line + 1;
525
575
  const endLine = sourceFile.getLineAndCharacterOfPosition(endPos).line + 1;
526
576
  let formattedReplacement = newBody.trim();
527
- if (target === "body" && bodyNode && ts.isBlock(bodyNode)) {
528
- if (!formattedReplacement.startsWith("{")) {
529
- formattedReplacement = `{
530
- ${formattedReplacement.split("\n").join("\n ")}
531
- }`;
532
- }
577
+ if (target === "body" && match.body && ts.isBlock(match.body) && !formattedReplacement.startsWith("{")) {
578
+ const baseIndent = indentAt(content, fullNode.getStart(sourceFile));
579
+ formattedReplacement = `{
580
+ ${reindent(newBody, `${baseIndent} `)}
581
+ ${baseIndent}}`;
533
582
  }
534
583
  const updatedContent = content.slice(0, startPos) + formattedReplacement + content.slice(endPos);
535
584
  const newEndLine = startLine + formattedReplacement.split("\n").length - 1;
@@ -599,6 +648,21 @@ async function mutateTreeSitterSymbol(content, lang, symbolName, newBody, target
599
648
  parser.delete();
600
649
  }
601
650
  }
651
+ var TEST_CALLEES = String.raw`(?:it|test|describe|suite|bench|context|specify)`;
652
+ function notFoundMessage(symbol, relPath, content) {
653
+ const base = `Symbol '${symbol}' could not be located in AST of '${relPath}'`;
654
+ const escaped = symbol.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
655
+ const testCall = new RegExp(
656
+ String.raw`\b${TEST_CALLEES}(?:\.\w+)*\s*\(\s*(['"\x60])${escaped}\1`
657
+ );
658
+ if (testCall.test(content)) {
659
+ return `${base}: that is a test-case title, not a declaration. codebase-ast-replace only resolves named declarations (function, method, class, interface, type, enum, variable); use the edit tool to change a test case.`;
660
+ }
661
+ if (!/^[\p{L}_$#][\p{L}\p{N}_$]*(?:\.[\p{L}_$#][\p{L}\p{N}_$]*)*$/u.test(symbol)) {
662
+ return `${base}: '${symbol}' is not a declaration name. Pass an identifier such as "calculateTotal", or a qualified "ClassName.method" for a nested member.`;
663
+ }
664
+ return `${base}. Check the name with codebase-search, or qualify a nested member as "Outer.inner".`;
665
+ }
602
666
  async function replaceSymbolInFile(opts, projectRoot) {
603
667
  const resolved = path.isAbsolute(opts.file) ? opts.file : path.resolve(projectRoot, opts.file);
604
668
  const content = await fs.readFile(resolved, "utf8");
@@ -610,9 +674,7 @@ async function replaceSymbolInFile(opts, projectRoot) {
610
674
  result = await mutateTreeSitterSymbol(content, lang, opts.symbol, opts.newBody, opts.target);
611
675
  }
612
676
  if (!result) {
613
- throw new Error(
614
- `Symbol '${opts.symbol}' could not be located in AST of '${path.relative(projectRoot, resolved)}'`
615
- );
677
+ throw new Error(notFoundMessage(opts.symbol, path.relative(projectRoot, resolved), content));
616
678
  }
617
679
  result.file = resolved;
618
680
  if (opts.checkInvariants !== false) {
@@ -642,7 +704,6 @@ Pass allowBreakingChanges: true if this breaking change is intended.`
642
704
 
643
705
  // src/codebase-index/codebase-ast-replace-tool.ts
644
706
  import * as path2 from "node:path";
645
- import { toErrorMessage } from "@wrongstack/core/utils";
646
707
  var codebaseAstReplaceTool = {
647
708
  name: "codebase-ast-replace",
648
709
  category: "Edit",
@@ -651,7 +712,7 @@ var codebaseAstReplaceTool = {
651
712
  subjectKey: "file",
652
713
  mutating: true,
653
714
  capabilities: ["fs.write", "fs.read"],
654
- description: "Surgically replace the implementation body or full definition of a function, method, class, or type alias using AST parsing. Guarantees zero string-matching errors, regex escape bugs, or whitespace indentation mismatches. Automatically verifies AST backward-compatibility invariants (no mandatory param addition, no export deletion, no interface breaking).",
715
+ description: "Surgically replace the implementation body or full definition of a named declaration (function, method, class, interface, type alias, enum, variable) using AST parsing. Test cases (`it(...)`/`test(...)` titles) are call arguments, not declarations \u2014 use the edit tool for those. Guarantees zero string-matching errors, regex escape bugs, or whitespace indentation mismatches. Automatically verifies AST backward-compatibility invariants (no mandatory param addition, no export deletion, no interface breaking).",
655
716
  usageHint: 'SURGICAL AST-BASED CODE EDITING:\n\n- Use when updating an existing function, method, or class without worrying about context lines.\n- Pass `symbol: "calculateTotal"` and `newBody: "return items.reduce(...);"`.\n- Default `target: "body"` replaces only the inner `{ ... }` block while preserving the signature and JSDoc.\n- Use `target: "full"` if you also need to modify the function parameters or return type.\n- Setting `allowBreakingChanges: true` bypasses the invariant engine if breaking changes are deliberate.',
656
717
  inputSchema: {
657
718
  type: "object",
@@ -662,7 +723,7 @@ var codebaseAstReplaceTool = {
662
723
  },
663
724
  symbol: {
664
725
  type: "string",
665
- description: "Name of the function, method, class, interface, or variable to replace."
726
+ description: 'Declaration name (function, method, class, interface, type, enum, variable). Qualify nested members as "ClassName.method" when the bare name is ambiguous. Not a test-case title.'
666
727
  },
667
728
  newBody: {
668
729
  type: "string",
@@ -685,33 +746,27 @@ var codebaseAstReplaceTool = {
685
746
  required: ["file", "symbol", "newBody"],
686
747
  additionalProperties: false
687
748
  },
749
+ // Failures THROW. A returned `{ status: 'error' }` is a successful tool call
750
+ // to the executor (is_error:false) — the UI showed "ok", the audit log and
751
+ // spans recorded success, and only a model reading the payload noticed.
688
752
  async execute(input, ctx) {
689
- try {
690
- const projectRoot = ctx.projectRoot ?? ctx.cwd ?? process.cwd();
691
- const file = await safeResolveProjectPath(input.file, ctx);
692
- const result = await replaceSymbolInFile({ ...input, file }, projectRoot);
693
- return {
694
- status: "ok",
695
- file: path2.relative(projectRoot, result.file).replace(/\\/g, "/"),
696
- symbol: result.symbol,
697
- originalRange: result.originalRange,
698
- newRange: result.newRange,
699
- violations: result.violations?.map((v) => `[${v.ruleId}] ${v.message}`)
700
- };
701
- } catch (err) {
702
- return {
703
- status: "error",
704
- file: input.file,
705
- symbol: input.symbol,
706
- error: toErrorMessage(err)
707
- };
708
- }
753
+ const projectRoot = ctx.projectRoot ?? ctx.cwd ?? process.cwd();
754
+ const file = await safeResolveProjectPath(input.file, ctx);
755
+ const result = await replaceSymbolInFile({ ...input, file }, projectRoot);
756
+ return {
757
+ status: "ok",
758
+ file: path2.relative(projectRoot, result.file).replace(/\\/g, "/"),
759
+ symbol: result.symbol,
760
+ originalRange: result.originalRange,
761
+ newRange: result.newRange,
762
+ violations: result.violations?.map((v) => `[${v.ruleId}] ${v.message}`)
763
+ };
709
764
  }
710
765
  };
711
766
 
712
767
  // src/codebase-index/codebase-impact-analysis-tool.ts
713
768
  import * as path3 from "node:path";
714
- import { toErrorMessage as toErrorMessage2 } from "@wrongstack/core/utils";
769
+ import { toErrorMessage } from "@wrongstack/core/utils";
715
770
  var TEST_FILE_REGEX = /(?:test|spec|mock|fixture|bench)[s]?[/\\._]/i;
716
771
  var codebaseImpactAnalysisTool = {
717
772
  name: "codebase-impact-analysis",
@@ -742,141 +797,114 @@ var codebaseImpactAnalysisTool = {
742
797
  additionalProperties: false
743
798
  },
744
799
  async execute(input, ctx) {
800
+ const projectRoot = ctx.projectRoot ?? ctx.cwd ?? process.cwd();
801
+ const indexDir = codebaseIndexDirOverride(ctx);
802
+ const limit = 200;
803
+ const transitive = input.transitive ?? true;
804
+ let rawSites = [];
805
+ let symbolFound = true;
806
+ let indexUnavailable = false;
807
+ let indexError;
745
808
  try {
746
- const projectRoot = ctx.projectRoot ?? ctx.cwd ?? process.cwd();
747
- const indexDir = codebaseIndexDirOverride(ctx);
748
- const limit = 200;
749
- const transitive = input.transitive ?? true;
750
- let rawSites = [];
751
- let symbolFound = true;
752
- let indexUnavailable = false;
753
- let indexError;
754
- try {
755
- const serviced = await incomingCallsService({
756
- projectRoot,
757
- indexDir,
758
- symbol: input.symbol,
759
- file: input.file,
760
- limit,
761
- transitive
762
- });
763
- rawSites = serviced.calls;
764
- symbolFound = serviced.symbolFound;
765
- } catch (err) {
766
- symbolFound = false;
767
- indexUnavailable = true;
768
- indexError = toErrorMessage2(err);
769
- }
770
- if (indexUnavailable) {
771
- const detail = indexError ? ` (${indexError})` : "";
772
- const reason = `Index query failed for '${input.symbol}'. Run codebase-index, then retry.${detail}`;
773
- return {
774
- status: "error",
775
- symbol: input.symbol,
776
- riskLevel: "low",
777
- summary: reason,
778
- totalCallSites: 0,
779
- affectedProductionFiles: [],
780
- affectedTestFiles: [],
781
- callSites: [],
782
- recommendedActionPlan: [reason],
783
- symbolFound: false,
784
- error: reason
785
- };
786
- }
787
- if (!symbolFound) {
788
- const reason = `Symbol '${input.symbol}' was not found in the index` + (input.file ? ` for file filter '${input.file}'` : "") + ". Use codebase-search to verify the name.";
789
- return {
790
- status: "ok",
791
- symbol: input.symbol,
792
- riskLevel: "low",
793
- summary: reason,
794
- totalCallSites: 0,
795
- affectedProductionFiles: [],
796
- affectedTestFiles: [],
797
- callSites: [],
798
- recommendedActionPlan: [reason],
799
- symbolFound: false
800
- };
801
- }
802
- const prodFilesSet = /* @__PURE__ */ new Set();
803
- const testFilesSet = /* @__PURE__ */ new Set();
804
- const callSites = [];
805
- for (const site of rawSites) {
806
- const relPath = path3.relative(projectRoot, site.symbol.file).replace(/\\/g, "/");
807
- const isTest = TEST_FILE_REGEX.test(relPath);
808
- if (isTest) {
809
- testFilesSet.add(relPath);
810
- } else {
811
- prodFilesSet.add(relPath);
812
- }
813
- callSites.push({
814
- file: relPath,
815
- line: site.line,
816
- callerName: site.symbol.name,
817
- callerKind: site.symbol.kind,
818
- callType: site.callType,
819
- isTest
820
- });
821
- }
822
- const prodFiles = [...prodFilesSet];
823
- const testFiles = [...testFilesSet];
824
- const totalCallSites = callSites.length;
825
- let riskLevel = "low";
826
- if (prodFiles.length > 5 || totalCallSites > 10) {
827
- riskLevel = "high";
828
- } else if (prodFiles.length > 1 || totalCallSites > 3) {
829
- riskLevel = "medium";
830
- }
831
- const recommendedActionPlan = [];
832
- if (prodFiles.length > 0) {
833
- recommendedActionPlan.push(
834
- `Update ${totalCallSites} call site(s) across ${prodFiles.length} production file(s): ${prodFiles.slice(0, 3).join(", ")}${prodFiles.length > 3 ? ` +${prodFiles.length - 3} more` : ""}`
835
- );
836
- }
837
- if (testFiles.length > 0) {
838
- recommendedActionPlan.push(
839
- `Run targeted tests in ${testFiles.length} test suite(s): ${testFiles.join(", ")}`
840
- );
841
- } else {
842
- recommendedActionPlan.push(
843
- `No existing test files directly cover '${input.symbol}'. Consider adding a test suite.`
844
- );
845
- }
846
- const summary = `Blast Radius for '${input.symbol}': ${riskLevel.toUpperCase()} RISK (${totalCallSites} call sites in ${prodFiles.length} prod files, ${testFiles.length} test suites).`;
847
- return {
848
- status: "ok",
809
+ const serviced = await incomingCallsService({
810
+ projectRoot,
811
+ indexDir,
849
812
  symbol: input.symbol,
850
- riskLevel,
851
- summary,
852
- totalCallSites,
853
- affectedProductionFiles: prodFiles,
854
- affectedTestFiles: testFiles,
855
- callSites,
856
- recommendedActionPlan,
857
- symbolFound: true
858
- };
813
+ file: input.file,
814
+ limit,
815
+ transitive
816
+ });
817
+ rawSites = serviced.calls;
818
+ symbolFound = serviced.symbolFound;
859
819
  } catch (err) {
820
+ symbolFound = false;
821
+ indexUnavailable = true;
822
+ indexError = toErrorMessage(err);
823
+ }
824
+ if (indexUnavailable) {
825
+ const detail = indexError ? ` (${indexError})` : "";
826
+ throw new Error(
827
+ `Index query failed for '${input.symbol}'. Run codebase-index, then retry.${detail}`
828
+ );
829
+ }
830
+ if (!symbolFound) {
831
+ const reason = `Symbol '${input.symbol}' was not found in the index` + (input.file ? ` for file filter '${input.file}'` : "") + ". Use codebase-search to verify the name.";
860
832
  return {
861
- status: "error",
833
+ status: "ok",
862
834
  symbol: input.symbol,
863
835
  riskLevel: "low",
864
- summary: "",
836
+ summary: reason,
865
837
  totalCallSites: 0,
866
838
  affectedProductionFiles: [],
867
839
  affectedTestFiles: [],
868
840
  callSites: [],
869
- recommendedActionPlan: [],
870
- symbolFound: false,
871
- error: toErrorMessage2(err)
841
+ recommendedActionPlan: [reason],
842
+ symbolFound: false
872
843
  };
873
844
  }
845
+ const prodFilesSet = /* @__PURE__ */ new Set();
846
+ const testFilesSet = /* @__PURE__ */ new Set();
847
+ const callSites = [];
848
+ for (const site of rawSites) {
849
+ const relPath = path3.relative(projectRoot, site.symbol.file).replace(/\\/g, "/");
850
+ const isTest = TEST_FILE_REGEX.test(relPath);
851
+ if (isTest) {
852
+ testFilesSet.add(relPath);
853
+ } else {
854
+ prodFilesSet.add(relPath);
855
+ }
856
+ callSites.push({
857
+ file: relPath,
858
+ line: site.line,
859
+ callerName: site.symbol.name,
860
+ callerKind: site.symbol.kind,
861
+ callType: site.callType,
862
+ isTest
863
+ });
864
+ }
865
+ const prodFiles = [...prodFilesSet];
866
+ const testFiles = [...testFilesSet];
867
+ const totalCallSites = callSites.length;
868
+ let riskLevel = "low";
869
+ if (prodFiles.length > 5 || totalCallSites > 10) {
870
+ riskLevel = "high";
871
+ } else if (prodFiles.length > 1 || totalCallSites > 3) {
872
+ riskLevel = "medium";
873
+ }
874
+ const recommendedActionPlan = [];
875
+ if (prodFiles.length > 0) {
876
+ recommendedActionPlan.push(
877
+ `Update ${totalCallSites} call site(s) across ${prodFiles.length} production file(s): ${prodFiles.slice(0, 3).join(", ")}${prodFiles.length > 3 ? ` +${prodFiles.length - 3} more` : ""}`
878
+ );
879
+ }
880
+ if (testFiles.length > 0) {
881
+ recommendedActionPlan.push(
882
+ `Run targeted tests in ${testFiles.length} test suite(s): ${testFiles.join(", ")}`
883
+ );
884
+ } else {
885
+ recommendedActionPlan.push(
886
+ `No existing test files directly cover '${input.symbol}'. Consider adding a test suite.`
887
+ );
888
+ }
889
+ const summary = `Blast Radius for '${input.symbol}': ${riskLevel.toUpperCase()} RISK (${totalCallSites} call sites in ${prodFiles.length} prod files, ${testFiles.length} test suites).`;
890
+ return {
891
+ status: "ok",
892
+ symbol: input.symbol,
893
+ riskLevel,
894
+ summary,
895
+ totalCallSites,
896
+ affectedProductionFiles: prodFiles,
897
+ affectedTestFiles: testFiles,
898
+ callSites,
899
+ recommendedActionPlan,
900
+ symbolFound: true
901
+ };
874
902
  }
875
903
  };
876
904
 
877
905
  // src/codebase-index/codebase-incoming-calls-tool.ts
878
906
  import { ToolValidationError } from "@wrongstack/core/types";
879
- import { toErrorMessage as toErrorMessage3 } from "@wrongstack/core/utils";
907
+ import { toErrorMessage as toErrorMessage2 } from "@wrongstack/core/utils";
880
908
  var codebaseIncomingCallsTool = {
881
909
  name: "codebase-incoming-calls",
882
910
  category: "Project",
@@ -923,12 +951,7 @@ var codebaseIncomingCallsTool = {
923
951
  if (state.lastError) {
924
952
  const circuit = state.circuit;
925
953
  const retryHint = circuit.state === "open" ? `Indexing is paused (circuit open, retry in ${Math.ceil(circuit.cooldownRemainingMs / 1e3)}s).` : "Try /codebase-reindex.";
926
- return {
927
- symbol: input.symbol,
928
- calls: [],
929
- total: 0,
930
- indexStatus: `Index build failed: ${state.lastError}. ${retryHint}`
931
- };
954
+ throw new Error(`Index build failed: ${state.lastError}. ${retryHint}`);
932
955
  }
933
956
  const limit = Math.max(1, Math.min(Math.trunc(input.limit ?? 50), 200));
934
957
  const transitive = input.transitive === true;
@@ -945,19 +968,15 @@ var codebaseIncomingCallsTool = {
945
968
  });
946
969
  } catch (err) {
947
970
  if (err.name === "IndexRefreshInProgressError") {
948
- return {
949
- symbol: input.symbol,
950
- calls: [],
951
- total: 0,
952
- indexStatus: `Index refresh in progress (${state.currentFile}/${state.totalFiles} files); this symbol has no cached answer yet \u2014 retry after the completed generation is published.`
953
- };
971
+ throw new Error(
972
+ `Index refresh in progress (${state.currentFile}/${state.totalFiles} files); this symbol has no cached answer yet \u2014 retry after the completed generation is published.`,
973
+ { cause: err }
974
+ );
954
975
  }
955
- return {
956
- symbol: input.symbol,
957
- calls: [],
958
- total: 0,
959
- indexStatus: `Index query failed: ${toErrorMessage3(err)}. Fall back to grep for this lookup.`
960
- };
976
+ throw new Error(
977
+ `Index query failed: ${toErrorMessage2(err)}. Fall back to grep for this lookup.`,
978
+ { cause: err }
979
+ );
961
980
  }
962
981
  const { calls, symbolFound, ambiguous, totalMatches, stale } = serviced;
963
982
  if (!symbolFound) {
@@ -969,16 +988,17 @@ var codebaseIncomingCallsTool = {
969
988
  indexDir: codebaseIndexDirOverride(ctx)
970
989
  });
971
990
  hasPersistedIndex = stats.totalFiles > 0 || stats.lastIndexed !== null;
972
- } catch {
991
+ } catch (err) {
992
+ throw new Error(
993
+ `Symbol "${input.symbol}" was not found and the persisted index could not be verified: ${toErrorMessage2(err)}. Try /codebase-reindex or fall back to grep.`,
994
+ { cause: err }
995
+ );
973
996
  }
974
997
  }
975
998
  if (!hasPersistedIndex) {
976
- return {
977
- symbol: input.symbol,
978
- calls: [],
979
- total: 0,
980
- indexStatus: "No persisted index data found. Run codebase-index to build it."
981
- };
999
+ throw new Error(
1000
+ "No persisted index data found. Run codebase-index to build it, then retry."
1001
+ );
982
1002
  }
983
1003
  return {
984
1004
  symbol: input.symbol,
@@ -1018,7 +1038,7 @@ import { ToolValidationError as ToolValidationError3 } from "@wrongstack/core/ty
1018
1038
 
1019
1039
  // src/codebase-index/codebase-search-tool.ts
1020
1040
  import { ToolValidationError as ToolValidationError2 } from "@wrongstack/core/types";
1021
- import { toErrorMessage as toErrorMessage4 } from "@wrongstack/core/utils";
1041
+ import { toErrorMessage as toErrorMessage3 } from "@wrongstack/core/utils";
1022
1042
  var INDEXABLE_LANG_IDS = [
1023
1043
  ...new Set(Object.values(EXT_TO_LANG))
1024
1044
  ].sort();
@@ -1112,12 +1132,7 @@ var codebaseSearchTool = {
1112
1132
  if (state.lastError) {
1113
1133
  const circuit = state.circuit;
1114
1134
  const retryHint = circuit.state === "open" ? `Indexing is paused (circuit open, retry in ${Math.ceil(circuit.cooldownRemainingMs / 1e3)}s); the user can run /codebase-reindex to retry now.` : "Try /codebase-reindex.";
1115
- return {
1116
- results: [],
1117
- total: 0,
1118
- query: input.query,
1119
- indexStatus: `Index build failed: ${state.lastError}. ${retryHint}`
1120
- };
1135
+ throw new Error(`Index build failed: ${state.lastError}. ${retryHint}`);
1121
1136
  }
1122
1137
  const limit = Math.max(1, Math.min(Math.trunc(input.limit ?? 20), 100));
1123
1138
  let searched;
@@ -1140,19 +1155,15 @@ var codebaseSearchTool = {
1140
1155
  signal.throwIfAborted();
1141
1156
  }
1142
1157
  if (err.name === "IndexRefreshInProgressError") {
1143
- return {
1144
- results: [],
1145
- total: 0,
1146
- query: input.query,
1147
- indexStatus: `Index refresh in progress (${state.currentFile}/${state.totalFiles} files); this query has no cached answer yet \u2014 retry after the completed generation is published.`
1148
- };
1158
+ throw new Error(
1159
+ `Index refresh in progress (${state.currentFile}/${state.totalFiles} files); this query has no cached answer yet \u2014 retry after the completed generation is published.`,
1160
+ { cause: err }
1161
+ );
1149
1162
  }
1150
- return {
1151
- results: [],
1152
- total: 0,
1153
- query: input.query,
1154
- indexStatus: `Index query failed: ${toErrorMessage4(err)}. Fall back to grep/glob for this lookup.`
1155
- };
1163
+ throw new Error(
1164
+ `Index query failed: ${toErrorMessage3(err)}. Fall back to grep/glob for this lookup.`,
1165
+ { cause: err }
1166
+ );
1156
1167
  }
1157
1168
  const { results, total, stale, indexSummary } = searched;
1158
1169
  let hasPersistedIndex = state.ready || total > 0;
@@ -1166,9 +1177,19 @@ var codebaseSearchTool = {
1166
1177
  { signal }
1167
1178
  );
1168
1179
  hasPersistedIndex = stats.totalFiles > 0 || stats.lastIndexed !== null;
1169
- } catch {
1180
+ } catch (err) {
1181
+ if (signal?.aborted) signal.throwIfAborted();
1182
+ throw new Error(
1183
+ `codebase-search found no results and could not verify the persisted index: ${toErrorMessage3(err)}. Try /codebase-reindex or fall back to grep/glob.`,
1184
+ { cause: err }
1185
+ );
1170
1186
  }
1171
1187
  }
1188
+ if (!hasPersistedIndex) {
1189
+ throw new Error(
1190
+ "No persisted index data found. Run codebase-index to build it, then retry codebase-search."
1191
+ );
1192
+ }
1172
1193
  return {
1173
1194
  results,
1174
1195
  total,
@@ -1176,8 +1197,7 @@ var codebaseSearchTool = {
1176
1197
  ...stale ? {
1177
1198
  stale: true,
1178
1199
  indexStatus: `Index refresh in progress (${state.currentFile}/${state.totalFiles} files); results served from the previous generation \u2014 symbols from files being indexed may lag.`
1179
- } : {},
1180
- ...hasPersistedIndex ? {} : { indexStatus: "No persisted index data found. Run codebase-index to build it." }
1200
+ } : {}
1181
1201
  };
1182
1202
  }
1183
1203
  };
@@ -1235,28 +1255,18 @@ var codebaseIndexTool = {
1235
1255
  }
1236
1256
  }
1237
1257
  if (isIndexing()) {
1238
- return {
1239
- filesIndexed: 0,
1240
- symbolsIndexed: 0,
1241
- langStats: {},
1242
- durationMs: 0,
1243
- errors: [],
1244
- note: "A full index is already in progress. Retry codebase-index after it completes (check codebase-stats)."
1245
- };
1258
+ throw new Error(
1259
+ "A full index is already in progress. Retry codebase-index after it completes (check codebase-stats)."
1260
+ );
1246
1261
  }
1247
1262
  if (input.force) {
1248
1263
  indexCircuitBreaker.reset();
1249
1264
  } else {
1250
1265
  const circuit = indexCircuitBreaker.snapshot();
1251
1266
  if (circuit.state === "open" && circuit.cooldownRemainingMs > 0) {
1252
- return {
1253
- filesIndexed: 0,
1254
- symbolsIndexed: 0,
1255
- langStats: {},
1256
- durationMs: 0,
1257
- errors: [],
1258
- note: `Codebase indexing is paused after repeated failures (last: ${circuit.lastFailure ?? "unknown"}). Auto-retry possible in ${Math.max(1, Math.ceil(circuit.cooldownRemainingMs / 1e3))}s; use force: true or run /codebase-reindex to retry immediately.`
1259
- };
1267
+ throw new Error(
1268
+ `Codebase indexing is paused after repeated failures (last: ${circuit.lastFailure ?? "unknown"}). Auto-retry possible in ${Math.max(1, Math.ceil(circuit.cooldownRemainingMs / 1e3))}s; use force: true or run /codebase-reindex to retry immediately.`
1269
+ );
1260
1270
  }
1261
1271
  }
1262
1272
  const result = await runStartupIndex({
@@ -1279,7 +1289,7 @@ var codebaseIndexTool = {
1279
1289
 
1280
1290
  // src/codebase-index/codebase-invariant-check-tool.ts
1281
1291
  import * as fs2 from "node:fs/promises";
1282
- import { toErrorMessage as toErrorMessage5 } from "@wrongstack/core/utils";
1292
+ import { ToolValidationError as ToolValidationError4 } from "@wrongstack/core/types";
1283
1293
  var codebaseInvariantCheckTool = {
1284
1294
  name: "codebase-invariant-check",
1285
1295
  category: "Inspect",
@@ -1313,52 +1323,44 @@ var codebaseInvariantCheckTool = {
1313
1323
  required: ["modifiedCode"],
1314
1324
  additionalProperties: false
1315
1325
  },
1326
+ // Failures THROW. The old catch returned `valid: false` — a successful call
1327
+ // (is_error:false) whose payload was indistinguishable from "this change
1328
+ // breaks compatibility", so an unreadable file read as a real violation.
1316
1329
  async execute(input, ctx) {
1317
- try {
1318
- let originalCode = input.originalCode;
1319
- let lang = input.lang;
1320
- if (!originalCode && input.file) {
1321
- const resolved = await safeResolveProjectPath(input.file, ctx);
1322
- originalCode = await fs2.readFile(resolved, "utf8");
1323
- if (!lang) {
1324
- lang = detectLang(resolved) ?? "ts";
1325
- }
1326
- }
1327
- if (originalCode === void 0) {
1328
- return {
1329
- valid: false,
1330
- violations: [],
1331
- summary: "Neither originalCode nor file was provided for invariant comparison.",
1332
- error: "Missing originalCode or file path."
1333
- };
1330
+ let originalCode = input.originalCode;
1331
+ let lang = input.lang;
1332
+ if (!originalCode && input.file) {
1333
+ const resolved = await safeResolveProjectPath(input.file, ctx);
1334
+ originalCode = await fs2.readFile(resolved, "utf8");
1335
+ if (!lang) {
1336
+ lang = detectLang(resolved) ?? "ts";
1334
1337
  }
1335
- const res = await polyglotInvariantEngine.evaluate({
1336
- originalCode,
1337
- modifiedCode: input.modifiedCode,
1338
- lang,
1339
- filePath: input.file
1338
+ }
1339
+ if (originalCode === void 0) {
1340
+ throw new ToolValidationError4({
1341
+ message: "Neither originalCode nor file was provided for invariant comparison.",
1342
+ field: "originalCode"
1340
1343
  });
1341
- const summary = res.valid ? "AST Invariants PASSED. Mutation is 100% backward compatible." : `AST Invariants FAILED with ${res.violations.length} violation(s):
1342
- ` + res.violations.map((v) => ` - [${v.ruleId}] ${v.symbolName}: ${v.message}`).join("\n");
1343
- return {
1344
- valid: res.valid,
1345
- violations: res.violations,
1346
- summary
1347
- };
1348
- } catch (err) {
1349
- return {
1350
- valid: false,
1351
- violations: [],
1352
- summary: `Error during invariant evaluation: ${toErrorMessage5(err)}`,
1353
- error: toErrorMessage5(err)
1354
- };
1355
1344
  }
1345
+ const res = await polyglotInvariantEngine.evaluate({
1346
+ originalCode,
1347
+ modifiedCode: input.modifiedCode,
1348
+ lang,
1349
+ filePath: input.file
1350
+ });
1351
+ const summary = res.valid ? "AST Invariants PASSED. Mutation is 100% backward compatible." : `AST Invariants FAILED with ${res.violations.length} violation(s):
1352
+ ` + res.violations.map((v) => ` - [${v.ruleId}] ${v.symbolName}: ${v.message}`).join("\n");
1353
+ return {
1354
+ valid: res.valid,
1355
+ violations: res.violations,
1356
+ summary
1357
+ };
1356
1358
  }
1357
1359
  };
1358
1360
 
1359
1361
  // src/codebase-index/codebase-outgoing-calls-tool.ts
1360
- import { ToolValidationError as ToolValidationError4 } from "@wrongstack/core/types";
1361
- import { toErrorMessage as toErrorMessage6 } from "@wrongstack/core/utils";
1362
+ import { ToolValidationError as ToolValidationError5 } from "@wrongstack/core/types";
1363
+ import { toErrorMessage as toErrorMessage4 } from "@wrongstack/core/utils";
1362
1364
  var codebaseOutgoingCallsTool = {
1363
1365
  name: "codebase-outgoing-calls",
1364
1366
  category: "Project",
@@ -1396,7 +1398,7 @@ var codebaseOutgoingCallsTool = {
1396
1398
  },
1397
1399
  async execute(input, ctx) {
1398
1400
  if (!input?.symbol || typeof input.symbol !== "string" || !input.symbol.trim()) {
1399
- throw new ToolValidationError4({
1401
+ throw new ToolValidationError5({
1400
1402
  message: "codebase-outgoing-calls: symbol is required and cannot be empty",
1401
1403
  field: "symbol"
1402
1404
  });
@@ -1405,12 +1407,7 @@ var codebaseOutgoingCallsTool = {
1405
1407
  if (state.lastError) {
1406
1408
  const circuit = state.circuit;
1407
1409
  const retryHint = circuit.state === "open" ? `Indexing is paused (circuit open, retry in ${Math.ceil(circuit.cooldownRemainingMs / 1e3)}s).` : "Try /codebase-reindex.";
1408
- return {
1409
- symbol: input.symbol,
1410
- calls: [],
1411
- total: 0,
1412
- indexStatus: `Index build failed: ${state.lastError}. ${retryHint}`
1413
- };
1410
+ throw new Error(`Index build failed: ${state.lastError}. ${retryHint}`);
1414
1411
  }
1415
1412
  const limit = Math.max(1, Math.min(Math.trunc(input.limit ?? 50), 200));
1416
1413
  const transitive = input.transitive === true;
@@ -1427,19 +1424,15 @@ var codebaseOutgoingCallsTool = {
1427
1424
  });
1428
1425
  } catch (err) {
1429
1426
  if (err.name === "IndexRefreshInProgressError") {
1430
- return {
1431
- symbol: input.symbol,
1432
- calls: [],
1433
- total: 0,
1434
- indexStatus: `Index refresh in progress (${state.currentFile}/${state.totalFiles} files); this symbol has no cached answer yet \u2014 retry after the completed generation is published.`
1435
- };
1427
+ throw new Error(
1428
+ `Index refresh in progress (${state.currentFile}/${state.totalFiles} files); this symbol has no cached answer yet \u2014 retry after the completed generation is published.`,
1429
+ { cause: err }
1430
+ );
1436
1431
  }
1437
- return {
1438
- symbol: input.symbol,
1439
- calls: [],
1440
- total: 0,
1441
- indexStatus: `Index query failed: ${toErrorMessage6(err)}. Fall back to grep for this lookup.`
1442
- };
1432
+ throw new Error(
1433
+ `Index query failed: ${toErrorMessage4(err)}. Fall back to grep for this lookup.`,
1434
+ { cause: err }
1435
+ );
1443
1436
  }
1444
1437
  const { calls, symbolFound, unresolvedCount, totalMatches, stale } = serviced;
1445
1438
  if (!symbolFound) {
@@ -1451,16 +1444,17 @@ var codebaseOutgoingCallsTool = {
1451
1444
  indexDir: codebaseIndexDirOverride(ctx)
1452
1445
  });
1453
1446
  hasPersistedIndex = stats.totalFiles > 0 || stats.lastIndexed !== null;
1454
- } catch {
1447
+ } catch (err) {
1448
+ throw new Error(
1449
+ `Symbol "${input.symbol}" was not found and the persisted index could not be verified: ${toErrorMessage4(err)}. Try /codebase-reindex or fall back to grep.`,
1450
+ { cause: err }
1451
+ );
1455
1452
  }
1456
1453
  }
1457
1454
  if (!hasPersistedIndex) {
1458
- return {
1459
- symbol: input.symbol,
1460
- calls: [],
1461
- total: 0,
1462
- indexStatus: "No persisted index data found. Run codebase-index to build it."
1463
- };
1455
+ throw new Error(
1456
+ "No persisted index data found. Run codebase-index to build it, then retry."
1457
+ );
1464
1458
  }
1465
1459
  return {
1466
1460
  symbol: input.symbol,
@@ -1495,9 +1489,6 @@ var codebaseOutgoingCallsTool = {
1495
1489
  }
1496
1490
  };
1497
1491
 
1498
- // src/codebase-index/codebase-repo-map-tool.ts
1499
- import { toErrorMessage as toErrorMessage7 } from "@wrongstack/core/utils";
1500
-
1501
1492
  // src/codebase-index/repo-map.ts
1502
1493
  import { existsSync } from "node:fs";
1503
1494
  import * as path6 from "node:path";
@@ -2171,38 +2162,28 @@ var codebaseRepoMapTool = {
2171
2162
  },
2172
2163
  additionalProperties: false
2173
2164
  },
2165
+ // Failures THROW so the executor marks the call is_error (a returned
2166
+ // `status: 'error'` payload was recorded as a successful call).
2174
2167
  async execute(input, ctx) {
2175
- try {
2176
- const projectRoot = ctx.projectRoot ?? ctx.cwd ?? process.cwd();
2177
- const result = await generateRepoMap({
2178
- projectRoot,
2179
- maxTokens: input.maxTokens,
2180
- focusFiles: input.focusFiles,
2181
- // Honour a caller-supplied index location so the map reads the same
2182
- // index the other codebase-* tools do.
2183
- indexDir: codebaseIndexDirOverride(ctx)
2184
- });
2185
- return {
2186
- status: "ok",
2187
- ...result
2188
- };
2189
- } catch (err) {
2190
- return {
2191
- status: "error",
2192
- map: "",
2193
- filesCount: 0,
2194
- totalFilesScanned: 0,
2195
- estimatedTokens: 0,
2196
- rankedFiles: [],
2197
- error: toErrorMessage7(err)
2198
- };
2199
- }
2168
+ const projectRoot = ctx.projectRoot ?? ctx.cwd ?? process.cwd();
2169
+ const result = await generateRepoMap({
2170
+ projectRoot,
2171
+ maxTokens: input.maxTokens,
2172
+ focusFiles: input.focusFiles,
2173
+ // Honour a caller-supplied index location so the map reads the same
2174
+ // index the other codebase-* tools do.
2175
+ indexDir: codebaseIndexDirOverride(ctx)
2176
+ });
2177
+ return {
2178
+ status: "ok",
2179
+ ...result
2180
+ };
2200
2181
  }
2201
2182
  };
2202
2183
 
2203
2184
  // src/codebase-index/codebase-skeleton-tool.ts
2204
2185
  import * as fs6 from "node:fs/promises";
2205
- import { toErrorMessage as toErrorMessage8 } from "@wrongstack/core/utils";
2186
+ import { toErrorMessage as toErrorMessage5 } from "@wrongstack/core/utils";
2206
2187
  var codebaseSkeletonTool = {
2207
2188
  name: "codebase-skeleton",
2208
2189
  category: "Project",
@@ -2249,7 +2230,7 @@ var codebaseSkeletonTool = {
2249
2230
  stat3 = await fs6.stat(targetPath);
2250
2231
  } catch (err) {
2251
2232
  throw new Error(
2252
- `codebase-skeleton: file or directory not found at "${input.path}": ${toErrorMessage8(err)}`
2233
+ `codebase-skeleton: file or directory not found at "${input.path}": ${toErrorMessage5(err)}`
2253
2234
  );
2254
2235
  }
2255
2236
  const options = {
@@ -2298,7 +2279,7 @@ var codebaseSkeletonTool = {
2298
2279
  };
2299
2280
 
2300
2281
  // src/codebase-index/codebase-stats-tool.ts
2301
- import { toErrorMessage as toErrorMessage9 } from "@wrongstack/core/utils";
2282
+ import { toErrorMessage as toErrorMessage6 } from "@wrongstack/core/utils";
2302
2283
  var codebaseStatsTool = {
2303
2284
  name: "codebase-stats",
2304
2285
  category: "Project",
@@ -2350,18 +2331,10 @@ var codebaseStatsTool = {
2350
2331
  if (signal?.aborted) {
2351
2332
  signal.throwIfAborted();
2352
2333
  }
2353
- return {
2354
- totalSymbols: 0,
2355
- totalFiles: 0,
2356
- byLang: {},
2357
- byKind: {},
2358
- lastIndexed: null,
2359
- sizeBytes: 0,
2360
- indexPath,
2361
- version: SCHEMA_VERSION,
2362
- statsAvailable: false,
2363
- indexStatus: err instanceof IndexTimeoutError || err?.name === "IndexTimeoutError" ? "Index statistics timed out. Do not repeatedly retry stats; try codebase-search directly or use the appropriate grep/glob/tree fallback." : `Index statistics query failed: ${toErrorMessage9(err)}. The index may be corrupted or inaccessible; try /codebase-reindex.`
2364
- };
2334
+ throw new Error(
2335
+ err instanceof IndexTimeoutError || err?.name === "IndexTimeoutError" ? "Index statistics timed out. Do not repeatedly retry stats; try codebase-search directly or use the appropriate grep/glob/tree fallback." : `Index statistics query failed: ${toErrorMessage6(err)}. The index may be corrupted or inaccessible; try /codebase-reindex.`,
2336
+ { cause: err }
2337
+ );
2365
2338
  }
2366
2339
  const circuit = idxState.circuit;
2367
2340
  const hasPersistedIndex = stats.totalFiles > 0 || stats.lastIndexed !== null;
@@ -2390,7 +2363,7 @@ var codebaseStatsTool = {
2390
2363
  // src/codebase-index/codebase-targeted-test-tool.ts
2391
2364
  import * as fs7 from "node:fs/promises";
2392
2365
  import * as path7 from "node:path";
2393
- import { toErrorMessage as toErrorMessage10 } from "@wrongstack/core/utils";
2366
+ import { toErrorMessage as toErrorMessage7 } from "@wrongstack/core/utils";
2394
2367
  var TEST_FILE_REGEX2 = /(?:test|spec|mock|fixture|bench)[s]?[/\\._]/i;
2395
2368
  async function findConventionTestFiles(projectRoot, sourceRelPath) {
2396
2369
  const parsed = path7.parse(sourceRelPath);
@@ -2535,16 +2508,10 @@ ${streamRes.stderr}`.trim(),
2535
2508
  output: combinedOutput
2536
2509
  };
2537
2510
  } catch (err) {
2538
- return {
2539
- status: "error",
2540
- discoveredSuites: [...suitesSet],
2541
- testsRun: 0,
2542
- passed: 0,
2543
- failed: 0,
2544
- durationMs: 0,
2545
- output: "",
2546
- error: toErrorMessage10(err)
2547
- };
2511
+ const suites = suitesSet.size > 0 ? ` (discovered suites: ${[...suitesSet].join(", ")})` : "";
2512
+ throw new Error(`Targeted test run failed to execute: ${toErrorMessage7(err)}${suites}`, {
2513
+ cause: err
2514
+ });
2548
2515
  }
2549
2516
  }
2550
2517
  };
@@ -2552,6 +2519,7 @@ ${streamRes.stderr}`.trim(),
2552
2519
  // src/codebase-index/dead-code-scan.ts
2553
2520
  import * as fs8 from "node:fs";
2554
2521
  import * as path8 from "node:path";
2522
+ import { ToolValidationError as ToolValidationError6 } from "@wrongstack/core/types";
2555
2523
  var deadCodeScanTool = {
2556
2524
  name: "dead-code-scan",
2557
2525
  category: "Project",
@@ -2582,12 +2550,36 @@ var deadCodeScanTool = {
2582
2550
  },
2583
2551
  async execute(input, ctx, _execOpts) {
2584
2552
  const startMs = Date.now();
2585
- const projectRoot = input.projectRoot ?? ctx.projectRoot ?? ctx.cwd ?? process.cwd();
2553
+ const projectRoot = input.projectRoot !== void 0 ? await safeResolveProjectPath(input.projectRoot, ctx) : ctx.projectRoot ?? ctx.cwd ?? process.cwd();
2586
2554
  const indexDir = input.indexDir ?? codebaseIndexDirOverride(ctx) ?? void 0;
2587
- const result = runDeadCodeScan(projectRoot, {
2588
- indexDir,
2589
- userEntryPoints: input.entryPoints
2590
- });
2555
+ let userEntryPoints;
2556
+ if (input.entryPoints) {
2557
+ userEntryPoints = [];
2558
+ for (const ep of input.entryPoints) {
2559
+ const resolved = await safeResolveProjectPath(
2560
+ path8.isAbsolute(ep) ? ep : path8.resolve(projectRoot, ep),
2561
+ ctx
2562
+ );
2563
+ if (!fs8.existsSync(resolved)) {
2564
+ throw new ToolValidationError6({
2565
+ message: `dead-code-scan: entry point not found: "${ep}"`,
2566
+ field: "entryPoints"
2567
+ });
2568
+ }
2569
+ userEntryPoints.push(resolved);
2570
+ }
2571
+ }
2572
+ const result = runDeadCodeScan(projectRoot, { indexDir, userEntryPoints });
2573
+ if (result.stats.totalSymbols === 0) {
2574
+ throw new Error(
2575
+ `dead-code-scan: the codebase index for "${projectRoot}" has no symbols. Run codebase-index, then retry.`
2576
+ );
2577
+ }
2578
+ if (result.stats.alive === 0) {
2579
+ throw new Error(
2580
+ `dead-code-scan: no indexed entry point was found under "${projectRoot}" (package.json main/bin/exports/types or src/index.ts), so reachability cannot be computed. Pass \`entryPoints\`.`
2581
+ );
2582
+ }
2591
2583
  return {
2592
2584
  ...result,
2593
2585
  stats: { ...result.stats, durationMs: Date.now() - startMs }
@@ -3568,8 +3560,8 @@ async function buildProjectAtlasBrief(projectRoot, opts = {}) {
3568
3560
  }
3569
3561
 
3570
3562
  // src/codebase-index/codebase-context-tool.ts
3571
- import { ToolValidationError as ToolValidationError5 } from "@wrongstack/core/types";
3572
- import { toErrorMessage as toErrorMessage11 } from "@wrongstack/core/utils";
3563
+ import { ToolValidationError as ToolValidationError7 } from "@wrongstack/core/types";
3564
+ import { toErrorMessage as toErrorMessage8 } from "@wrongstack/core/utils";
3573
3565
  var TOOL_TIMEOUT_MS = 35e3;
3574
3566
  var VECTOR_HIT_LIMIT = 12;
3575
3567
  var VECTOR_MIN_SCORE = 0.25;
@@ -3604,7 +3596,7 @@ var codebaseContextTool = {
3604
3596
  capabilities: ["fs.read"],
3605
3597
  timeoutMs: TOOL_TIMEOUT_MS,
3606
3598
  description: "Find the files and declarations a task touches, from a plain-language description. Combines ranked symbol search with a personalised walk over the reference graph, so results include not just what matched by name but what those matches are structurally attached to \u2014 the interface behind a handler, the store behind a route, the module every match imports. Returns ranked files with the relevant declarations, their signatures and line numbers.",
3607
- usageHint: 'START HERE FOR ANY TASK THAT SPANS MORE THAN ONE FILE:\n\n- Describe the task, not a symbol name: `codebase-context({ query: "retry a provider request after a 429" })`.\n- Prefer this over `codebase-search` when you do not already know the symbol you want; prefer `codebase-search` when you do.\n- Use the returned `file` + `line` to `read` only the parts you need, instead of reading whole files.\n- `matched: true` means the file matched the query directly; the rest were reached through the graph.\n- `relevance` is relative to this answer only (top result is always 1.0) \u2014 never compare it across queries.\n- Narrow with `pathPrefix: "packages/core"` when you already know the area.\n- `indexStatus: "no-index"` means run `/codebase-reindex` first.',
3599
+ usageHint: 'START HERE FOR ANY TASK THAT SPANS MORE THAN ONE FILE:\n\n- Describe the task, not a symbol name: `codebase-context({ query: "retry a provider request after a 429" })`.\n- Prefer this over `codebase-search` when you do not already know the symbol you want; prefer `codebase-search` when you do.\n- Use the returned `file` + `line` to `read` only the parts you need, instead of reading whole files.\n- `matched: true` means the file matched the query directly; the rest were reached through the graph.\n- `relevance` is relative to this answer only (top result is always 1.0) \u2014 never compare it across queries.\n- Narrow with `pathPrefix: "packages/core"` when you already know the area.\n- If the call fails because no index exists, run `/codebase-reindex` (or `codebase-index`) first.',
3608
3600
  inputSchema: {
3609
3601
  type: "object",
3610
3602
  properties: {
@@ -3631,19 +3623,20 @@ var codebaseContextTool = {
3631
3623
  async execute(input, ctx, execOpts) {
3632
3624
  const query = typeof input.query === "string" ? input.query.trim() : "";
3633
3625
  if (query.length === 0) {
3634
- throw new ToolValidationError5({
3626
+ throw new ToolValidationError7({
3635
3627
  message: "codebase-context requires a non-empty `query`.",
3636
3628
  field: "query"
3637
3629
  });
3638
3630
  }
3639
3631
  const signal = execOpts?.signal ?? ctx.signal;
3640
3632
  const projectRoot = ctx.projectRoot ?? ctx.cwd ?? process.cwd();
3633
+ signal?.throwIfAborted();
3634
+ const indexDir = codebaseIndexDirOverride(ctx);
3635
+ let result;
3641
3636
  try {
3642
- signal?.throwIfAborted();
3643
- const indexDir = codebaseIndexDirOverride(ctx);
3644
3637
  const vectorFiles = await semanticSeeds(projectRoot, indexDir, query);
3645
3638
  signal?.throwIfAborted();
3646
- const result = await codebaseContext({
3639
+ result = await codebaseContext({
3647
3640
  projectRoot,
3648
3641
  indexDir,
3649
3642
  query,
@@ -3652,28 +3645,28 @@ var codebaseContextTool = {
3652
3645
  symbolsPerFile: input.symbolsPerFile,
3653
3646
  pathPrefix: input.pathPrefix
3654
3647
  });
3655
- return {
3656
- query: result.query,
3657
- entries: result.entries,
3658
- seedCount: result.seedCount,
3659
- semanticSeedCount: result.semanticSeedCount,
3660
- totalCandidates: result.totalCandidates,
3661
- indexStatus: result.indexStatus,
3662
- ..."stale" in result && result.stale === true ? { stale: true } : {}
3663
- };
3664
3648
  } catch (err) {
3665
3649
  if (signal?.aborted) throw err;
3666
3650
  const refreshing = err instanceof Error && err.name === "IndexRefreshInProgressError";
3667
- return {
3668
- query,
3669
- entries: [],
3670
- seedCount: 0,
3671
- semanticSeedCount: 0,
3672
- totalCandidates: 0,
3673
- indexStatus: "error",
3674
- error: refreshing ? "Codebase index is refreshing; retry once the current generation is published." : toErrorMessage11(err)
3675
- };
3651
+ throw new Error(
3652
+ refreshing ? "Codebase index is refreshing; retry once the current generation is published." : `codebase-context lookup failed: ${toErrorMessage8(err)}`,
3653
+ { cause: err }
3654
+ );
3655
+ }
3656
+ if (result.indexStatus === "no-index") {
3657
+ throw new Error(
3658
+ "No codebase index data found. Run /codebase-reindex (or codebase-index), then retry codebase-context."
3659
+ );
3676
3660
  }
3661
+ return {
3662
+ query: result.query,
3663
+ entries: result.entries,
3664
+ seedCount: result.seedCount,
3665
+ semanticSeedCount: result.semanticSeedCount,
3666
+ totalCandidates: result.totalCandidates,
3667
+ indexStatus: result.indexStatus,
3668
+ ..."stale" in result && result.stale === true ? { stale: true } : {}
3669
+ };
3677
3670
  }
3678
3671
  };
3679
3672
 
@@ -4047,4 +4040,4 @@ export {
4047
4040
  embedFiles,
4048
4041
  embedProjectFiles
4049
4042
  };
4050
- //# sourceMappingURL=chunk-JB5JLY7V.js.map
4043
+ //# sourceMappingURL=chunk-ENE22I5W.js.map