@wrongstack/tools 0.9.20 → 0.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (74) hide show
  1. package/dist/audit.js +2 -2
  2. package/dist/audit.js.map +1 -1
  3. package/dist/bash.js +86 -16
  4. package/dist/bash.js.map +1 -1
  5. package/dist/batch-tool-use.js +2 -2
  6. package/dist/batch-tool-use.js.map +1 -1
  7. package/dist/builtin.js +376 -159
  8. package/dist/builtin.js.map +1 -1
  9. package/dist/codebase-index/index.d.ts +24 -4
  10. package/dist/codebase-index/index.js +32 -23
  11. package/dist/codebase-index/index.js.map +1 -1
  12. package/dist/diff.js +25 -9
  13. package/dist/diff.js.map +1 -1
  14. package/dist/document.js +3 -2
  15. package/dist/document.js.map +1 -1
  16. package/dist/edit.js +3 -2
  17. package/dist/edit.js.map +1 -1
  18. package/dist/exec.js +96 -12
  19. package/dist/exec.js.map +1 -1
  20. package/dist/fetch.js +13 -6
  21. package/dist/fetch.js.map +1 -1
  22. package/dist/format.js +74 -4
  23. package/dist/format.js.map +1 -1
  24. package/dist/git.js +81 -8
  25. package/dist/git.js.map +1 -1
  26. package/dist/glob.js +15 -5
  27. package/dist/glob.js.map +1 -1
  28. package/dist/grep.js +32 -9
  29. package/dist/grep.js.map +1 -1
  30. package/dist/index.js +394 -170
  31. package/dist/index.js.map +1 -1
  32. package/dist/install.js +85 -8
  33. package/dist/install.js.map +1 -1
  34. package/dist/json.js +2 -2
  35. package/dist/json.js.map +1 -1
  36. package/dist/lint.js +74 -4
  37. package/dist/lint.js.map +1 -1
  38. package/dist/logs.js +2 -2
  39. package/dist/logs.js.map +1 -1
  40. package/dist/memory.js +13 -6
  41. package/dist/memory.js.map +1 -1
  42. package/dist/mode.js +4 -4
  43. package/dist/mode.js.map +1 -1
  44. package/dist/outdated.js +2 -2
  45. package/dist/outdated.js.map +1 -1
  46. package/dist/pack.js +377 -160
  47. package/dist/pack.js.map +1 -1
  48. package/dist/patch.js +3 -2
  49. package/dist/patch.js.map +1 -1
  50. package/dist/read.js +16 -5
  51. package/dist/read.js.map +1 -1
  52. package/dist/replace.js +3 -2
  53. package/dist/replace.js.map +1 -1
  54. package/dist/scaffold.js +3 -2
  55. package/dist/scaffold.js.map +1 -1
  56. package/dist/search.js +3 -2
  57. package/dist/search.js.map +1 -1
  58. package/dist/test.js +74 -4
  59. package/dist/test.js.map +1 -1
  60. package/dist/todo.js +21 -7
  61. package/dist/todo.js.map +1 -1
  62. package/dist/tool-help.js +5 -5
  63. package/dist/tool-help.js.map +1 -1
  64. package/dist/tool-search.js +2 -2
  65. package/dist/tool-search.js.map +1 -1
  66. package/dist/tool-use.js +4 -4
  67. package/dist/tool-use.js.map +1 -1
  68. package/dist/tree.js +16 -8
  69. package/dist/tree.js.map +1 -1
  70. package/dist/typecheck.js +74 -4
  71. package/dist/typecheck.js.map +1 -1
  72. package/dist/write.js +11 -4
  73. package/dist/write.js.map +1 -1
  74. package/package.json +2 -2
package/dist/pack.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { spawn, execFileSync, spawnSync } from 'node:child_process';
2
- import { buildChildEnv, stripAnsi, detectNewlineStyle, normalizeToLf, toStyle, atomicWrite, unifiedDiff, compileGlob, loadPlan, emptyPlan, clearPlan, savePlan, getPlanTemplate, addPlanItem, deriveTodosFromPlanItem, removePlanItem, setPlanItemStatus, formatPlan } from '@wrongstack/core';
2
+ import { buildChildEnv, detectNewlineStyle, normalizeToLf, toStyle, atomicWrite, unifiedDiff, compileGlob, loadPlan, emptyPlan, clearPlan, savePlan, getPlanTemplate, addPlanItem, deriveTodosFromPlanItem, removePlanItem, setPlanItemStatus, formatPlan, stripAnsi, resolveWstackPaths } from '@wrongstack/core';
3
3
  import * as fs11 from 'node:fs/promises';
4
4
  import { stat } from 'node:fs/promises';
5
5
  import * as path from 'node:path';
@@ -156,13 +156,83 @@ function isBinaryBuffer(buf) {
156
156
  }
157
157
  return false;
158
158
  }
159
+ var COMMAND_OUTPUT_MAX_BYTES = 32768;
160
+ var REPEAT_RUN_THRESHOLD = 3;
161
+ function collapseCarriageReturns(text) {
162
+ const lf = text.replace(/\r\n/g, "\n");
163
+ if (!lf.includes("\r")) return lf;
164
+ return lf.split("\n").map((line) => line.includes("\r") ? line.slice(line.lastIndexOf("\r") + 1) : line).join("\n");
165
+ }
166
+ function collapseConsecutiveDuplicates(text, minRun = REPEAT_RUN_THRESHOLD) {
167
+ const lines = text.split("\n");
168
+ const out = [];
169
+ let i = 0;
170
+ while (i < lines.length) {
171
+ let j = i + 1;
172
+ while (j < lines.length && lines[j] === lines[i]) j++;
173
+ const run = j - i;
174
+ if (run >= minRun) {
175
+ out.push(lines[i], `\u2026 \u27E8repeated ${run}\xD7\u27E9`);
176
+ } else {
177
+ for (let k = i; k < j; k++) out.push(lines[k]);
178
+ }
179
+ i = j;
180
+ }
181
+ return out.join("\n");
182
+ }
183
+ function takeHeadBytes(s, maxBytes) {
184
+ if (maxBytes <= 0) return "";
185
+ if (Buffer.byteLength(s, "utf8") <= maxBytes) return s;
186
+ let lo = 0;
187
+ let hi = s.length;
188
+ while (lo < hi) {
189
+ const mid = Math.ceil((lo + hi) / 2);
190
+ if (Buffer.byteLength(s.slice(0, mid), "utf8") <= maxBytes) lo = mid;
191
+ else hi = mid - 1;
192
+ }
193
+ return s.slice(0, lo);
194
+ }
195
+ function takeTailBytes(s, maxBytes) {
196
+ if (maxBytes <= 0) return "";
197
+ if (Buffer.byteLength(s, "utf8") <= maxBytes) return s;
198
+ let lo = 0;
199
+ let hi = s.length;
200
+ while (lo < hi) {
201
+ const mid = Math.ceil((lo + hi) / 2);
202
+ if (Buffer.byteLength(s.slice(s.length - mid), "utf8") <= maxBytes) lo = mid;
203
+ else hi = mid - 1;
204
+ }
205
+ return s.slice(s.length - lo);
206
+ }
207
+ function truncateHeadTail(s, maxBytes) {
208
+ const total = Buffer.byteLength(s, "utf8");
209
+ if (total <= maxBytes) return s;
210
+ const MARKER_RESERVE = 64;
211
+ const avail = Math.max(0, maxBytes - MARKER_RESERVE);
212
+ const headBudget = Math.floor(avail * 0.45);
213
+ const head = takeHeadBytes(s, headBudget);
214
+ const tail = takeTailBytes(s, avail - Buffer.byteLength(head, "utf8"));
215
+ const kept = Buffer.byteLength(head, "utf8") + Buffer.byteLength(tail, "utf8");
216
+ return `${head}
217
+ \u2026[truncated ${total - kept} bytes]\u2026
218
+ ${tail}`;
219
+ }
220
+ function normalizeCommandOutput(raw, opts = {}) {
221
+ if (!raw) return raw;
222
+ let text = stripAnsi(raw);
223
+ text = collapseCarriageReturns(text);
224
+ text = text.replace(/[ \t]+$/gm, "");
225
+ text = collapseConsecutiveDuplicates(text);
226
+ text = text.replace(/\n{3,}/g, "\n\n");
227
+ return truncateHeadTail(text, opts.maxBytes ?? COMMAND_OUTPUT_MAX_BYTES);
228
+ }
159
229
 
160
230
  // src/audit.ts
161
231
  var auditTool = {
162
232
  name: "audit",
163
233
  category: "Package Management",
164
- description: "Run npm/pnpm security audit. Returns vulnerabilities sorted by severity.",
165
- usageHint: "Set `level` to filter minimum severity. `fix` attempts auto-fix. `packages` checks specific packages.",
234
+ description: "Run a security audit against project dependencies (using pnpm/npm audit). Reports known vulnerabilities with severity.",
235
+ usageHint: "CRITICAL SECURITY TOOL:\n\n- Run regularly and especially before any release.\n- Use `level` to focus on high/critical issues.\n- `fix` can attempt automatic remediation for some vulnerabilities.\nThis is one of the most important tools for supply chain security.",
166
236
  permission: "confirm",
167
237
  mutating: false,
168
238
  timeoutMs: 6e4,
@@ -618,23 +688,33 @@ var STREAM_FLUSH_BYTES = 4 * 1024;
618
688
  var bashTool = {
619
689
  name: "bash",
620
690
  category: "Shell",
621
- description: "Run a shell command. stdout and stderr are merged.",
622
- usageHint: "Runs via `bash -c` (or `cmd /c` on Windows). Cwd is the project root. Default timeout 30s. Output truncated from the middle if oversized. Use for git, npm, builds, tests.",
691
+ description: "Execute an arbitrary command in the user's default shell (bash/zsh/pwsh/cmd). stdout and stderr are merged into one stream. This is the most powerful and dangerous tool \u2014 it gives the model full access to the developer's machine. Prefer specialized tools whenever possible.",
692
+ usageHint: "SECURITY WARNING: This tool runs with the full privileges of the current user.\n\nBest practices for the model:\n- Strongly prefer `exec` for known safe commands (node, npm, pnpm, tsc, git, etc.).\n- Use bash only when you genuinely need shell features (pipes, redirection, complex one-liners).\n- Prefer single focused commands over huge `&&` chains.\n- Use `background: true` only for long-running processes (dev servers, watchers).\n- The working directory is the project root.\n- Output may be truncated in the middle for very large results.",
623
693
  permission: "confirm",
624
694
  mutating: true,
625
695
  // Trust rules match on the literal `command` string. Without subjectKey
626
696
  // the policy heuristic would have done the same here, but declaring it
627
697
  // explicitly removes the implicit cross-tool aliasing.
628
698
  subjectKey: "command",
699
+ capabilities: ["shell.arbitrary"],
629
700
  timeoutMs: 3e4,
630
701
  maxOutputBytes: MAX_OUTPUT,
631
702
  estimatedDurationMs: 3e3,
632
703
  inputSchema: {
633
704
  type: "object",
634
705
  properties: {
635
- command: { type: "string" },
636
- timeout_ms: { type: "integer" },
637
- background: { type: "boolean" }
706
+ command: {
707
+ type: "string",
708
+ description: "The exact shell command to run. Prefer simple, focused commands."
709
+ },
710
+ timeout_ms: {
711
+ type: "integer",
712
+ description: "Optional timeout for this specific command in milliseconds."
713
+ },
714
+ background: {
715
+ type: "boolean",
716
+ description: "If true, launch the process in the background and return the PID immediately."
717
+ }
638
718
  },
639
719
  required: ["command"]
640
720
  },
@@ -716,7 +796,7 @@ var bashTool = {
716
796
  yield {
717
797
  type: "final",
718
798
  output: {
719
- output: truncated ? buf2.slice(0, MAX_OUTPUT) + "\u2026[truncated]" : buf2,
799
+ output: normalizeCommandOutput(buf2),
720
800
  exit_code: null,
721
801
  timed_out: false,
722
802
  pid: pid2
@@ -843,11 +923,10 @@ var bashTool = {
843
923
  if (remainder !== null) {
844
924
  yield { type: "partial_output", text: remainder };
845
925
  }
846
- const cleaned = stripAnsi(buf).replace(/\r\n?/g, "\n");
847
926
  yield {
848
927
  type: "final",
849
928
  output: {
850
- output: truncateMiddle(cleaned, MAX_OUTPUT),
929
+ output: normalizeCommandOutput(buf),
851
930
  exit_code: c.code,
852
931
  timed_out: timedOut
853
932
  }
@@ -870,8 +949,8 @@ var bashTool = {
870
949
  var batchToolUseTool = {
871
950
  name: "batch_tool_use",
872
951
  category: "Meta",
873
- description: "Execute multiple tool calls in sequence or parallel. Returns all results.",
874
- usageHint: "Set `calls` array with tool names and inputs. `stop_on_error` halts on first failure. `parallel` runs concurrently (default: true).",
952
+ description: "Execute a batch of tool calls either sequentially or in parallel. Returns structured results for every call.",
953
+ usageHint: "ADVANCED / POWER USER TOOL:\n\n- Useful when you have a clear list of independent operations to perform.\n- `parallel: true` (default) runs them concurrently for speed.\n- `stop_on_error: true` makes it fail fast on the first error.\nUse with care \u2014 batching many mutating operations can be risky. Prefer explicit sequential steps for important work.",
875
954
  permission: "confirm",
876
955
  mutating: true,
877
956
  timeoutMs: 12e4,
@@ -1007,8 +1086,14 @@ function lspKindToInternalKind(k) {
1007
1086
  }
1008
1087
 
1009
1088
  // src/codebase-index/writer.ts
1010
- var INDEX_DIR = ".codebase-index";
1011
1089
  var DB_FILE = "index.db";
1090
+ function resolveIndexDir(projectRoot, override) {
1091
+ return override ?? resolveWstackPaths({ projectRoot }).projectCodebaseIndex;
1092
+ }
1093
+ function codebaseIndexDirOverride(ctx) {
1094
+ const v = ctx.meta?.["codebaseIndexDir"];
1095
+ return typeof v === "string" ? v : void 0;
1096
+ }
1012
1097
  var warningSilenced = false;
1013
1098
  function silenceSqliteExperimentalWarning() {
1014
1099
  if (warningSilenced) return;
@@ -1036,16 +1121,16 @@ function loadDatabaseSync() {
1036
1121
  return DatabaseSyncCtor;
1037
1122
  }
1038
1123
  var IndexStore = class {
1039
- constructor(projectRoot) {
1040
- this.projectRoot = projectRoot;
1041
- const dir = path.join(projectRoot, INDEX_DIR);
1042
- fs.mkdirSync(dir, { recursive: true });
1124
+ db;
1125
+ /** Absolute path to this project's index directory. */
1126
+ indexDir;
1127
+ constructor(projectRoot, opts = {}) {
1128
+ this.indexDir = resolveIndexDir(projectRoot, opts.indexDir);
1129
+ fs.mkdirSync(this.indexDir, { recursive: true });
1043
1130
  const Database = loadDatabaseSync();
1044
- this.db = new Database(path.join(dir, DB_FILE));
1131
+ this.db = new Database(path.join(this.indexDir, DB_FILE));
1045
1132
  this.initSchema();
1046
1133
  }
1047
- projectRoot;
1048
- db;
1049
1134
  initSchema() {
1050
1135
  this.db.exec(`
1051
1136
  CREATE TABLE IF NOT EXISTS metadata (
@@ -1236,7 +1321,7 @@ var IndexStore = class {
1236
1321
  totalFiles,
1237
1322
  byLang,
1238
1323
  byKind,
1239
- indexPath: path.join(this.projectRoot, INDEX_DIR),
1324
+ indexPath: this.indexDir,
1240
1325
  lastIndexed,
1241
1326
  sizeBytes,
1242
1327
  version: SCHEMA_VERSION
@@ -1329,7 +1414,7 @@ var IndexStore = class {
1329
1414
  }));
1330
1415
  }
1331
1416
  sizeBytes() {
1332
- const dbPath = path.join(this.projectRoot, INDEX_DIR, DB_FILE);
1417
+ const dbPath = path.join(this.indexDir, DB_FILE);
1333
1418
  try {
1334
1419
  return fs.statSync(dbPath).size;
1335
1420
  } catch {
@@ -2605,8 +2690,8 @@ async function parseFile(file, content, lang) {
2605
2690
  }
2606
2691
  }
2607
2692
  async function runIndexer(_ctx, opts) {
2608
- const { projectRoot, force = false, langs, ignore = [] } = opts;
2609
- const store = new IndexStore(projectRoot);
2693
+ const { projectRoot, force = false, langs, ignore = [], indexDir } = opts;
2694
+ const store = new IndexStore(projectRoot, { indexDir });
2610
2695
  const startMs = Date.now();
2611
2696
  const errors = [];
2612
2697
  const langStats = {};
@@ -2723,8 +2808,8 @@ async function runIndexer(_ctx, opts) {
2723
2808
  var codebaseIndexTool = {
2724
2809
  name: "codebase-index",
2725
2810
  category: "Project",
2726
- description: "Build or update the symbol index for the project. Runs incrementally by default \u2014 only re-indexes files that changed since the last run.",
2727
- usageHint: "Call with `force: true` to wipe and rebuild the index from scratch. Use `langs` to limit to specific languages. First call without arguments to do an incremental index.",
2811
+ description: "Build or incrementally update the project-wide symbol index. This powers fast codebase search and understanding. By default it only processes files that have changed since the last indexing run.",
2812
+ usageHint: "IMPORTANT FOR LARGE CODEBASES:\n\n- First run (or after major changes): consider `force: true` for a clean rebuild.\n- Normal usage: call without arguments for fast incremental updates.\n- Use `langs` to restrict to specific languages if you only care about certain parts of the project.\nThis tool is relatively expensive \u2014 do not call it on every turn. Use it when the index is stale or before heavy codebase-search sessions.",
2728
2813
  permission: "auto",
2729
2814
  mutating: true,
2730
2815
  timeoutMs: 12e4,
@@ -2746,7 +2831,8 @@ var codebaseIndexTool = {
2746
2831
  const result = await runIndexer(ctx, {
2747
2832
  projectRoot: ctx.projectRoot,
2748
2833
  force: input.force ?? false,
2749
- langs: input.langs
2834
+ langs: input.langs,
2835
+ indexDir: codebaseIndexDirOverride(ctx)
2750
2836
  });
2751
2837
  return result;
2752
2838
  }
@@ -2844,10 +2930,11 @@ var Bm25Index = class {
2844
2930
  var codebaseSearchTool = {
2845
2931
  name: "codebase-search",
2846
2932
  category: "Project",
2847
- description: "Search indexed code symbols by name, signature, or doc comment. Uses BM25 ranking for relevance.",
2848
- usageHint: "Pass `query` for keyword search. Filter with `kind` (class/function/interface/etc), `lang` (ts/js/etc), `file` (substring). `limit` caps results (default 20).",
2933
+ description: "Semantic/keyword search over the indexed codebase symbols (functions, classes, interfaces, etc.). Uses BM25 ranking. Much more powerful and structured than raw `grep` for finding code by name or concept.",
2934
+ usageHint: "PREFERRED FOR CODE UNDERSTANDING:\n\n- Use when you need to find where something is defined or used by name.\n- `kind` filter is very useful (e.g. only functions or only interfaces).\n- Combine with `file` filter to scope to a specific directory or module.\nThis is generally better than `grep` when you are looking for symbols rather than arbitrary text patterns.",
2849
2935
  permission: "auto",
2850
2936
  mutating: false,
2937
+ capabilities: ["fs.read"],
2851
2938
  timeoutMs: 1e4,
2852
2939
  inputSchema: {
2853
2940
  type: "object",
@@ -2882,7 +2969,7 @@ var codebaseSearchTool = {
2882
2969
  required: ["query"]
2883
2970
  },
2884
2971
  async execute(input, ctx) {
2885
- const store = new IndexStore(ctx.projectRoot);
2972
+ const store = new IndexStore(ctx.projectRoot, { indexDir: codebaseIndexDirOverride(ctx) });
2886
2973
  try {
2887
2974
  const limit = Math.min(input.limit ?? 20, 100);
2888
2975
  const candidates = store.search(input.query, {
@@ -2927,10 +3014,11 @@ var codebaseSearchTool = {
2927
3014
  var codebaseStatsTool = {
2928
3015
  name: "codebase-stats",
2929
3016
  category: "Project",
2930
- description: "Return statistics about the symbol index: total symbols, files, breakdown by language and kind, index size, and last update time.",
2931
- usageHint: "No arguments needed. Use to check if the index is stale or healthy before running a search.",
3017
+ description: "Return health and statistics about the current symbol index (total symbols, files, language/kind breakdown, size, last update). Useful to decide whether to re-index.",
3018
+ usageHint: "CALL BEFORE HEAVY CODEBASE-SEARCH WORK:\n\n- Use to see if the index is up-to-date or needs a refresh.\n- No arguments required.\n- Helps avoid wasting tokens on searches against a stale index.\nLightweight and safe to call frequently.",
2932
3019
  permission: "auto",
2933
3020
  mutating: false,
3021
+ capabilities: ["fs.read"],
2934
3022
  timeoutMs: 5e3,
2935
3023
  inputSchema: {
2936
3024
  type: "object",
@@ -2938,7 +3026,7 @@ var codebaseStatsTool = {
2938
3026
  additionalProperties: false
2939
3027
  },
2940
3028
  async execute(_input, ctx) {
2941
- const store = new IndexStore(ctx.projectRoot);
3029
+ const store = new IndexStore(ctx.projectRoot, { indexDir: codebaseIndexDirOverride(ctx) });
2942
3030
  try {
2943
3031
  const stats = store.getStats();
2944
3032
  return {
@@ -2959,28 +3047,44 @@ var codebaseStatsTool = {
2959
3047
  var diffTool = {
2960
3048
  name: "diff",
2961
3049
  category: "Filesystem",
2962
- description: "Show differences between files, commits, or branches. Supports staged vs working tree.",
2963
- usageHint: "Use `files` for file paths, `a`/`b` for commit refs, `staged` for git index. `mode`: unified (default), stat, side-by-side.",
3050
+ description: "Show code differences between files, commits, branches, or staged changes. A safer and more structured alternative to raw `git diff` via shell.",
3051
+ usageHint: 'USE FOR CODE REVIEW AND CHANGE INSPECTION:\n\n- `files` + no `a`/`b` \u2192 diff working tree vs HEAD for those files.\n- `a` and/or `b` \u2192 git-style commit/branch diff.\n- `staged: true` \u2192 only show staged changes.\n- `mode` can be "unified", "stat", or "side-by-side".\nThis tool has important safety guards against flag injection (see previous security findings).',
2964
3052
  permission: "auto",
2965
3053
  mutating: false,
3054
+ capabilities: ["fs.read"],
2966
3055
  timeoutMs: 1e4,
2967
3056
  inputSchema: {
2968
3057
  type: "object",
2969
3058
  properties: {
2970
- path: { type: "string", description: "Working directory for diff" },
3059
+ path: {
3060
+ type: "string",
3061
+ description: "Working directory for the diff operation (defaults to project root)."
3062
+ },
2971
3063
  files: {
2972
3064
  type: "string",
2973
- description: 'File(s) to diff: single path, comma-separated, or "**/*.ts" glob'
3065
+ description: 'Files or globs to diff (e.g. "src/**/*.ts" or comma-separated list).'
3066
+ },
3067
+ a: {
3068
+ type: "string",
3069
+ description: "First ref/commit/branch for git diff (e.g. HEAD, main, a commit hash)."
3070
+ },
3071
+ b: {
3072
+ type: "string",
3073
+ description: "Second ref/commit/branch for git diff."
3074
+ },
3075
+ staged: {
3076
+ type: "boolean",
3077
+ description: "If true, only show changes that are staged in git."
2974
3078
  },
2975
- a: { type: "string", description: "First commit/branch/ref (for git diff)" },
2976
- b: { type: "string", description: "Second commit/branch/ref (for git diff)" },
2977
- staged: { type: "boolean", description: "Diff staged changes only" },
2978
3079
  mode: {
2979
3080
  type: "string",
2980
3081
  enum: ["unified", "side-by-side", "stat"],
2981
- description: "Output mode (default: unified)"
3082
+ description: 'Output format. "unified" is default, "stat" shows summary only.'
2982
3083
  },
2983
- context: { type: "integer", description: "Context lines for unified diff (default: 3)" }
3084
+ context: {
3085
+ type: "integer",
3086
+ description: "Number of context lines for unified diffs (default: 3)."
3087
+ }
2984
3088
  }
2985
3089
  },
2986
3090
  async execute(input, ctx, opts) {
@@ -3086,8 +3190,8 @@ function formatUnified(lines, _context) {
3086
3190
  var documentTool = {
3087
3191
  name: "document",
3088
3192
  category: "Project",
3089
- description: "Generate or update documentation comments for functions, classes, and types. Supports JSDoc, TSDoc, and block comments.",
3090
- usageHint: "Set `target` for what to document. `files` for paths. `style` for comment format. `overwrite` replaces existing docs.",
3193
+ description: "Automatically generate or update documentation comments (JSDoc/TSDoc style) for code. Can target specific symbols or entire files/directories.",
3194
+ usageHint: "USE FOR IMPROVING CODE DOCUMENTATION:\n\n- Good for adding missing docs to public APIs or complex functions.\n- `overwrite: true` will replace existing documentation (use carefully).\n- You can target specific symbols via `target` or whole files/directories via `files`.\nAlways review the generated documentation before committing \u2014 the model can hallucinate details.",
3091
3195
  permission: "confirm",
3092
3196
  mutating: true,
3093
3197
  timeoutMs: 3e4,
@@ -3234,10 +3338,11 @@ function processFile(content, absPath, _style, _overwrite, target) {
3234
3338
  var editTool = {
3235
3339
  name: "edit",
3236
3340
  category: "Filesystem",
3237
- description: "Make a surgical edit by replacing exact text. Fails if `old_string` is not unique unless `replace_all` is true.",
3238
- usageHint: "Always `read` the file first. `old_string` must be an EXACT match (whitespace included). If multiple matches exist, either narrow `old_string` with more context or set `replace_all: true`.",
3341
+ description: "Perform a precise, surgical text replacement in a file. This is the preferred tool for modifying existing code. It requires that you have previously called `read` on the file in the current session. Fails safely if the `old_string` appears more than once unless `replace_all` is set.",
3342
+ usageHint: "MANDATORY WORKFLOW:\n1. Call `read` on the target file first (in the same conversation).\n2. Use a sufficiently unique `old_string` (include surrounding lines/context if needed).\n3. If the string appears multiple times and you want to change all of them, set `replace_all: true`.\n4. `new_string` must be the exact replacement text.\n\nThis tool is much safer than `write` for existing files because it works against the last-read version.",
3239
3343
  permission: "confirm",
3240
3344
  mutating: true,
3345
+ capabilities: ["fs.write"],
3241
3346
  timeoutMs: 5e3,
3242
3347
  inputSchema: {
3243
3348
  type: "object",
@@ -3451,18 +3556,32 @@ function validateArgs(cmd, args) {
3451
3556
  var execTool = {
3452
3557
  name: "exec",
3453
3558
  category: "Shell",
3454
- description: "Restricted shell that only runs pre-approved commands with constrained arguments. Safer alternative to `bash`.",
3455
- usageHint: "Set `command` (must be in allowlist). `args` passed through. For arbitrary shell access use the `bash` tool instead.",
3559
+ description: "Execute a **whitelisted, restricted set of commands** with strict argument validation. This is the **preferred and safer** alternative to the `bash` tool for running development tools (node, npm, pnpm, tsc, git, tests, linters, etc.). It prevents arbitrary command injection and limits what the model can do.",
3560
+ usageHint: "PREFERRED SHELL TOOL for most cases.\n\nUse this instead of `bash` whenever possible.\n- `command` must be one of the allowed commands (node, npm, pnpm, git, tsc, eslint, vitest, etc.).\n- Arguments are passed as a clean array (no shell interpretation).\n- `cwd` is validated to stay inside the project.\n- For anything that requires real shell features (pipes, complex redirection, arbitrary commands), fall back to `bash` (with strong justification).\nThis tool significantly reduces the risk compared to full shell access.",
3456
3561
  permission: "confirm",
3457
3562
  mutating: true,
3458
3563
  timeoutMs: TIMEOUT_MS,
3564
+ capabilities: ["shell.restricted"],
3459
3565
  inputSchema: {
3460
3566
  type: "object",
3461
3567
  properties: {
3462
- command: { type: "string", description: "Command to run (must be in allowlist)" },
3463
- args: { type: "array", items: { type: "string" }, description: "Arguments" },
3464
- cwd: { type: "string", description: "Working directory (must resolve inside project root)" },
3465
- timeout: { type: "integer", description: "Timeout in ms (default: 30000)" }
3568
+ command: {
3569
+ type: "string",
3570
+ description: 'The base command to run. Must be in the internal allowlist (e.g. "node", "pnpm", "git", "tsc").'
3571
+ },
3572
+ args: {
3573
+ type: "array",
3574
+ items: { type: "string" },
3575
+ description: "Arguments passed to the command. Passed as an array (no shell parsing)."
3576
+ },
3577
+ cwd: {
3578
+ type: "string",
3579
+ description: "Optional working directory. Must resolve inside the project root."
3580
+ },
3581
+ timeout: {
3582
+ type: "integer",
3583
+ description: "Per-command timeout in milliseconds."
3584
+ }
3466
3585
  },
3467
3586
  required: ["command"]
3468
3587
  },
@@ -3571,10 +3690,10 @@ function runCommand(cmd, args, cwd, timeout, signal, sessionId) {
3571
3690
  resolve7({
3572
3691
  command: cmd,
3573
3692
  args,
3574
- stdout: stdout.slice(0, MAX_OUTPUT2),
3575
- stderr: stderr.slice(0, MAX_OUTPUT2),
3693
+ stdout: normalizeCommandOutput(stdout),
3694
+ stderr: normalizeCommandOutput(stderr),
3576
3695
  exitCode,
3577
- truncated: stdout.length >= MAX_OUTPUT2 || stderr.length >= MAX_OUTPUT2,
3696
+ truncated: Buffer.byteLength(stdout, "utf8") > COMMAND_OUTPUT_MAX_BYTES || Buffer.byteLength(stderr, "utf8") > COMMAND_OUTPUT_MAX_BYTES,
3578
3697
  allowed: true
3579
3698
  });
3580
3699
  });
@@ -3585,10 +3704,10 @@ function runCommand(cmd, args, cwd, timeout, signal, sessionId) {
3585
3704
  resolve7({
3586
3705
  command: cmd,
3587
3706
  args,
3588
- stdout: stdout.slice(0, MAX_OUTPUT2),
3707
+ stdout: normalizeCommandOutput(stdout),
3589
3708
  stderr: err.message,
3590
3709
  exitCode: 1,
3591
- truncated: false,
3710
+ truncated: Buffer.byteLength(stdout, "utf8") > COMMAND_OUTPUT_MAX_BYTES,
3592
3711
  allowed: true
3593
3712
  });
3594
3713
  });
@@ -3678,10 +3797,11 @@ async function guardedFetch(url, maxRedirects, signal, headers = {
3678
3797
  var fetchTool = {
3679
3798
  name: "fetch",
3680
3799
  category: "Network",
3681
- description: "Fetch the contents of a URL. HTML is converted to markdown by default.",
3682
- usageHint: "HTTPS only by default. Localhost and RFC1918 ranges blocked unless WRONGSTACK_FETCH_ALLOW_PRIVATE=1. Max 5 redirects, 20s timeout, 128KB cap.",
3800
+ description: "Fetch a URL and return its content. HTML pages are automatically converted to clean markdown. This tool has strong SSRF protections (private IPs, localhost, and cloud metadata endpoints are blocked by default).",
3801
+ usageHint: "Use this when you need external information (documentation, API responses, web pages, etc.).\n\nSecurity notes:\n- Only HTTPS is allowed by default.\n- Internal/private networks are blocked unless explicitly enabled via environment variable.\n- Redirects are followed but re-validated at each hop.\n- Output is capped (128KB by default) to avoid flooding context.\nPrefer this over raw `bash curl` or `bash wget`.",
3683
3802
  permission: "confirm",
3684
3803
  mutating: false,
3804
+ capabilities: ["net.outbound"],
3685
3805
  // Trust rules for fetch match on the literal URL — declare it explicitly
3686
3806
  // so a user can trust `https://api.example.com/*` without accidentally
3687
3807
  // matching that pattern on any other tool that happens to have a `url`
@@ -3692,8 +3812,15 @@ var fetchTool = {
3692
3812
  inputSchema: {
3693
3813
  type: "object",
3694
3814
  properties: {
3695
- url: { type: "string" },
3696
- format: { type: "string", enum: ["markdown", "text", "raw"] }
3815
+ url: {
3816
+ type: "string",
3817
+ description: "The target URL (must use https://)."
3818
+ },
3819
+ format: {
3820
+ type: "string",
3821
+ enum: ["markdown", "text", "raw"],
3822
+ description: 'Output format. "markdown" is recommended for HTML pages.'
3823
+ }
3697
3824
  },
3698
3825
  required: ["url"]
3699
3826
  },
@@ -3930,8 +4057,8 @@ function stripTags(s) {
3930
4057
  var formatTool = {
3931
4058
  name: "format",
3932
4059
  category: "Code Quality",
3933
- description: "Format files with biome or prettier. Use `check` to verify without modifying.",
3934
- usageHint: "Set `files` (glob or comma-separated). `check` only validates. `fixer` forces tool.",
4060
+ description: "Format source files according to project style (Biome). Can also run in check-only mode.",
4061
+ usageHint: "RUN REGULARLY:\n\n- Use on changed files before committing.\n- `check: true` verifies formatting without making changes (useful in CI-like flows).\nThis project has very consistent formatting expectations. Always ensure your changes are formatted.",
3935
4062
  permission: "confirm",
3936
4063
  mutating: true,
3937
4064
  timeoutMs: 6e4,
@@ -4004,7 +4131,7 @@ var formatTool = {
4004
4131
  fixer: detected,
4005
4132
  files_checked: 0,
4006
4133
  files_changed: changed,
4007
- output: result.stdout || result.stderr || result.error || "",
4134
+ output: normalizeCommandOutput(result.stdout || result.stderr || result.error || ""),
4008
4135
  truncated: result.truncated
4009
4136
  }
4010
4137
  };
@@ -4029,13 +4156,14 @@ var MAX_OUTPUT3 = 1e5;
4029
4156
  var gitTool = {
4030
4157
  name: "git",
4031
4158
  category: "Git",
4032
- description: "Run git commands. Wraps common operations: status, log, diff, commit, branch, checkout, stash, push, pull, fetch, reset, worktree.",
4033
- usageHint: "Prefer built-in subcommands over raw args. `command` is required. `message` for commits. `branch` for checkout/branch. `files` for status/diff. `format` for log.",
4159
+ description: "Safe wrapper around common git operations. Supports status, log, diff, commit, branch, checkout, stash, push, pull, fetch, reset, worktree, etc. This is the preferred way to interact with git instead of using the raw `bash` or `exec` tools.",
4160
+ usageHint: "ALWAYS prefer this tool over raw shell git commands.\n\nKey fields:\n- `command`: one of the supported subcommands (status, log, diff, commit, etc.)\n- Use `message` only for commit operations.\n- Use `files` array for operations that take paths (status, diff, add, etc.).\n- Non-mutating commands (status, log, diff, branch, fetch) are still permission:confirm for safety.\nNever pass raw git flags through `args` for dangerous operations \u2014 use the structured fields.",
4034
4161
  permission: "confirm",
4035
4162
  // Conservative: any of these may mutate. The non-mutating commands
4036
4163
  // (status/log/diff/branch/fetch) are still gated on `permission: 'confirm'`
4037
4164
  // and `MUTATING_SUBCOMMANDS` is consulted at runtime for per-call checks.
4038
4165
  mutating: true,
4166
+ capabilities: ["fs.write", "shell.restricted"],
4039
4167
  timeoutMs: TIMEOUT_MS3,
4040
4168
  inputSchema: {
4041
4169
  type: "object",
@@ -4250,19 +4378,19 @@ function runGit2(args, cwd, signal) {
4250
4378
  child.on("error", (err) => {
4251
4379
  resolve7({
4252
4380
  command: args[0],
4253
- stdout,
4381
+ stdout: normalizeCommandOutput(stdout),
4254
4382
  stderr: err.message,
4255
4383
  exitCode: 1,
4256
- truncated: stdout.length >= MAX_OUTPUT3
4384
+ truncated: Buffer.byteLength(stdout, "utf8") > COMMAND_OUTPUT_MAX_BYTES
4257
4385
  });
4258
4386
  });
4259
4387
  child.on("close", (code) => {
4260
4388
  resolve7({
4261
4389
  command: args[0],
4262
- stdout: stdout.slice(0, MAX_OUTPUT3),
4263
- stderr: stderr.slice(0, MAX_OUTPUT3),
4390
+ stdout: normalizeCommandOutput(stdout),
4391
+ stderr: normalizeCommandOutput(stderr),
4264
4392
  exitCode: code ?? 1,
4265
- truncated: stdout.length >= MAX_OUTPUT3 || stderr.length >= MAX_OUTPUT3
4393
+ truncated: Buffer.byteLength(stdout, "utf8") > COMMAND_OUTPUT_MAX_BYTES || Buffer.byteLength(stderr, "utf8") > COMMAND_OUTPUT_MAX_BYTES
4266
4394
  });
4267
4395
  });
4268
4396
  });
@@ -4271,18 +4399,28 @@ var DEFAULT_IGNORE2 = ["node_modules", ".git", "dist", "build", ".next", "covera
4271
4399
  var globTool = {
4272
4400
  name: "glob",
4273
4401
  category: "Filesystem",
4274
- description: "Find files matching a glob pattern. Returns paths sorted by mtime (newest first).",
4275
- usageHint: "Examples: `**/*.ts`, `src/**/*.test.ts`, `*.json`. Common dirs (node_modules, .git, dist) are ignored by default. Returns up to 1000 paths.",
4402
+ description: "Find files matching a glob pattern. Fast way to discover relevant files before reading, grepping, or editing them.",
4403
+ usageHint: "RECOMMENDED FOR SCOPING SEARCHES:\n\n- Use early to get a list of files you actually care about.\n- Combine with `path` and `limit`.\n- Default ignores common build/dependency directories.\nMuch more efficient than shell `find` for most use cases inside the agent.",
4276
4404
  permission: "auto",
4277
4405
  mutating: false,
4406
+ capabilities: ["fs.read"],
4278
4407
  maxOutputBytes: 65536,
4279
4408
  timeoutMs: 5e3,
4280
4409
  inputSchema: {
4281
4410
  type: "object",
4282
4411
  properties: {
4283
- pattern: { type: "string" },
4284
- path: { type: "string", description: "Base directory (defaults to cwd)" },
4285
- limit: { type: "integer" }
4412
+ pattern: {
4413
+ type: "string",
4414
+ description: 'Glob pattern to match (e.g. "**/*.ts", "src/**").'
4415
+ },
4416
+ path: {
4417
+ type: "string",
4418
+ description: "Base directory to search from (defaults to project root)."
4419
+ },
4420
+ limit: {
4421
+ type: "integer",
4422
+ description: "Maximum number of results to return (default 1000, max 5000)."
4423
+ }
4286
4424
  },
4287
4425
  required: ["pattern"]
4288
4426
  },
@@ -4393,22 +4531,45 @@ var DEFAULT_IGNORE3 = ["node_modules", ".git", "dist", "build", ".next", "covera
4393
4531
  var grepTool = {
4394
4532
  name: "grep",
4395
4533
  category: "Search",
4396
- description: "Search file contents with a regex. Uses ripgrep when available.",
4397
- usageHint: 'Pattern is regex. Use `output_mode: "content"` for matched lines, `"files_with_matches"` for paths, `"count"` for tallies. `glob` filters files (e.g. `*.ts`).',
4534
+ description: "Search across files using a regular expression. This is one of the primary code search tools. Prefers ripgrep for speed and features when available.",
4535
+ usageHint: 'POWERFUL CODE SEARCH TOOL:\n\n- `pattern` is a regular expression.\n- Use `output_mode: "content"` (default) to get matching lines with context.\n- Use `"files_with_matches"` when you only need the list of files.\n- Use `"count"` for quick statistics.\n- `glob` and `path` let you narrow the search scope significantly.\n- Always prefer this over `bash grep` when searching code.',
4398
4536
  permission: "auto",
4399
4537
  mutating: false,
4538
+ capabilities: ["fs.read"],
4400
4539
  maxOutputBytes: 131072,
4401
4540
  timeoutMs: 1e4,
4402
4541
  inputSchema: {
4403
4542
  type: "object",
4404
4543
  properties: {
4405
- pattern: { type: "string" },
4406
- path: { type: "string" },
4407
- glob: { type: "string" },
4408
- output_mode: { type: "string", enum: ["content", "files_with_matches", "count"] },
4409
- context_lines: { type: "integer" },
4410
- case_insensitive: { type: "boolean" },
4411
- limit: { type: "integer" }
4544
+ pattern: {
4545
+ type: "string",
4546
+ description: "Regular expression pattern to search for in file contents."
4547
+ },
4548
+ path: {
4549
+ type: "string",
4550
+ description: "Limit search to this directory or file (relative to project root)."
4551
+ },
4552
+ glob: {
4553
+ type: "string",
4554
+ description: 'Glob filter for which files to include (e.g. "**/*.ts", "src/**").'
4555
+ },
4556
+ output_mode: {
4557
+ type: "string",
4558
+ enum: ["content", "files_with_matches", "count"],
4559
+ description: "Return style: detailed matches, just file list, or count only."
4560
+ },
4561
+ context_lines: {
4562
+ type: "integer",
4563
+ description: "How many lines of surrounding context to include with each match."
4564
+ },
4565
+ case_insensitive: {
4566
+ type: "boolean",
4567
+ description: "Ignore case when matching."
4568
+ },
4569
+ limit: {
4570
+ type: "integer",
4571
+ description: "Maximum number of matches to return."
4572
+ }
4412
4573
  },
4413
4574
  required: ["pattern"]
4414
4575
  },
@@ -4656,11 +4817,12 @@ async function runNative(input, base, mode, limit, signal) {
4656
4817
  var installTool = {
4657
4818
  name: "install",
4658
4819
  category: "Package Management",
4659
- description: "Install npm packages. Detects pnpm/npm/yarn and uses the right package manager.",
4660
- usageHint: "Set `packages` to install. `save` as dependency type. `global` for global install. `dry_run` to preview.",
4820
+ description: "Install, update or manage packages using the detected package manager (pnpm/npm/yarn). Strongly preferred over raw shell commands for dependency management because it is structured and safer.",
4821
+ usageHint: "ALWAYS USE THIS INSTEAD OF BASH FOR PACKAGE WORK:\n\n- Empty `packages` \u2192 normal `install` (respects lockfile).\n- Provide names \u2192 adds/updates specific packages.\n- `dry_run: true` for safe preview.\n- Set `save` appropriately.\nThis tool has proper capability declaration and is heavily recommended in the security posture of the project.",
4661
4822
  permission: "confirm",
4662
4823
  mutating: true,
4663
4824
  timeoutMs: 12e4,
4825
+ capabilities: ["package.install", "shell.restricted"],
4664
4826
  inputSchema: {
4665
4827
  type: "object",
4666
4828
  properties: {
@@ -4671,14 +4833,20 @@ var installTool = {
4671
4833
  save: {
4672
4834
  type: "string",
4673
4835
  enum: ["dependency", "dev", "optional"],
4674
- description: "Save as regular, dev, or optional dependency"
4836
+ description: 'Where to save the package(s): "dependency", "devDependencies", or "optionalDependencies".'
4837
+ },
4838
+ cwd: {
4839
+ type: "string",
4840
+ description: "Working directory for the install command (must stay inside project)."
4675
4841
  },
4676
- cwd: { type: "string", description: "Working directory (default: cwd)" },
4677
4842
  dry_run: {
4678
4843
  type: "boolean",
4679
- description: "Preview install without modifying (default: false)"
4844
+ description: "If true, show what would be installed without actually modifying package.json or node_modules."
4680
4845
  },
4681
- global: { type: "boolean", description: "Install globally (default: false)" }
4846
+ global: {
4847
+ type: "boolean",
4848
+ description: "Whether to perform a global install (use with caution)."
4849
+ }
4682
4850
  }
4683
4851
  },
4684
4852
  async execute(input, ctx, opts) {
@@ -4742,7 +4910,7 @@ var installTool = {
4742
4910
  output: {
4743
4911
  packages: pkgList,
4744
4912
  exit_code: result.exitCode,
4745
- output: result.stdout || result.stderr || result.error || "",
4913
+ output: normalizeCommandOutput(result.stdout || result.stderr || result.error || ""),
4746
4914
  dry_run: args.includes("--dry-run"),
4747
4915
  truncated: result.truncated
4748
4916
  }
@@ -4766,8 +4934,8 @@ async function detectPackageManager(cwd) {
4766
4934
  var jsonTool = {
4767
4935
  name: "json",
4768
4936
  category: "Data",
4769
- description: "Parse, query, and validate JSON/JSON5/YAML. Use `query` with JMESPath-like paths to extract values.",
4770
- usageHint: 'Provide `file` path or `data` string. `query` supports dot notation (e.g. "results[0].name"). `format` outputs in specified format.',
4937
+ description: "Parse, pretty-print, query, and convert between JSON, JSON5, and YAML. Supports simple path-based queries.",
4938
+ usageHint: "VERY USEFUL FOR DATA INSPECTION:\n\n- Use on package.json, tsconfig, config files, or any structured data.\n- `query` lets you extract specific values without reading the whole file.\n- Great for validating that a file has the expected structure.\nPrefer this over raw `read` + manual parsing when dealing with configuration or data files.",
4771
4939
  permission: "auto",
4772
4940
  mutating: false,
4773
4941
  timeoutMs: 5e3,
@@ -4889,8 +5057,8 @@ function toYaml(data, indent = 0) {
4889
5057
  var lintTool = {
4890
5058
  name: "lint",
4891
5059
  category: "Code Quality",
4892
- description: "Run a linter on files. Auto-detects biome, eslint, or tslint. Use `fix` to auto-fix issues.",
4893
- usageHint: "Set `files` (glob or comma-separated). `fix` applies corrections. `linter` forces specific tool.",
5060
+ description: "Run the project linter (primarily Biome in this repo). Detects style violations, potential bugs, and formatting issues.",
5061
+ usageHint: "RUN OFTEN DURING DEVELOPMENT:\n\n- `fix: true` will automatically correct what it can.\n- Target specific files or globs when you only want to check part of the project.\nThis is a fast and important quality gate. Use it before typecheck in most workflows.",
4894
5062
  permission: "confirm",
4895
5063
  mutating: false,
4896
5064
  timeoutMs: 6e4,
@@ -4955,7 +5123,7 @@ var lintTool = {
4955
5123
  files_checked: input.files ? Array.isArray(input.files) ? input.files.length : input.files.split(",").length : 0,
4956
5124
  errors,
4957
5125
  warnings,
4958
- output: result.stdout,
5126
+ output: normalizeCommandOutput(result.stdout),
4959
5127
  fix_applied: input.fix ?? false,
4960
5128
  truncated: result.truncated
4961
5129
  }
@@ -4979,8 +5147,8 @@ async function detectLinter(cwd) {
4979
5147
  var logsTool = {
4980
5148
  name: "logs",
4981
5149
  category: "Logs",
4982
- description: "Stream or fetch logs from a service or file. Supports Docker, systemd, or plain log files.",
4983
- usageHint: "Set `service` for Docker/systemd, `path` for file. `lines` limits output. `stream` for tail -f behavior. `filter` regex filters lines.",
5150
+ description: "Read or stream logs from files, Docker containers, or systemd services. Useful for debugging running applications.",
5151
+ usageHint: "DEBUGGING TOOL \u2014 USE CAREFULLY IN AUTONOMOUS MODE:\n\n- Prefer `path` for local files or `service` for containers/systemd.\n- `stream: true` = live tail (can be expensive).\n- Always use `filter` (regex) when possible to reduce noise and token usage.\n- Long-running streams should be avoided unless the user explicitly wants live logs.",
4984
5152
  permission: "confirm",
4985
5153
  mutating: false,
4986
5154
  timeoutMs: 3e4,
@@ -5180,8 +5348,8 @@ function parseLine(line) {
5180
5348
  var outdatedTool = {
5181
5349
  name: "outdated",
5182
5350
  category: "Package Management",
5183
- description: "Check for outdated npm packages. Shows current, wanted, and latest versions.",
5184
- usageHint: "Set `check` to filter specific packages. `format` as list or table. `include_deprecated` shows deprecated packages.",
5351
+ description: "Check for outdated dependencies in the project. Reports current, wanted (semver range), and latest versions available.",
5352
+ usageHint: "MAINTENANCE & SECURITY TOOL:\n\n- Run periodically or before dependency-related work.\n- Helps surface packages that may need updates for security or features.\n- Safe, read-only operation.\nUse the output to decide on upgrades. Prefer this over manual shell commands for dependency hygiene.",
5185
5353
  permission: "auto",
5186
5354
  mutating: true,
5187
5355
  timeoutMs: 6e4,
@@ -5290,10 +5458,11 @@ function parseOutdatedOutput(json, exitCode) {
5290
5458
  var patchTool = {
5291
5459
  name: "patch",
5292
5460
  category: "Filesystem",
5293
- description: "Apply a unified diff patch to files. Writes .orig and .rej files on failure.",
5294
- usageHint: "Set `patch` (the diff text). `directory` defaults to cwd. `strip` removes leading path components. `dry_run` previews.",
5461
+ description: "Apply a unified diff (patch) to the project. This is the correct tool when you have a diff that needs to be applied precisely, including handling of rejects.",
5462
+ usageHint: "Best used when you already have a diff (from generation, external source, or previous step).\n- Use `dry_run: true` to see what would happen without modifying files.\n- On failure it creates .rej and .orig files for manual review.\nOften cleaner than many small `edit` operations for larger changes.",
5295
5463
  permission: "confirm",
5296
5464
  mutating: true,
5465
+ capabilities: ["fs.write"],
5297
5466
  timeoutMs: 3e4,
5298
5467
  inputSchema: {
5299
5468
  type: "object",
@@ -5399,8 +5568,8 @@ function extractPatchedFiles(output) {
5399
5568
  var planTool = {
5400
5569
  name: "plan",
5401
5570
  category: "Session",
5402
- description: "Inspect or edit the strategic plan board for this session. Plans persist across resume (unlike todos). Use this to lay out the multi-step approach before diving in, then mark steps in_progress/done as the work proceeds. Promote a plan item to todos to start working on it. Apply templates for common workflows.",
5403
- usageHint: 'Set action to one of: show | add | start | done | remove | promote | derive | template_use | clear. Pass `title` for add. Pass `target` (item id, 1-based index, or title substring) for start/done/remove/promote/derive. Pass `subtasks` for promote/derive to break the plan item into multiple todos. Pass `template` (e.g. "new-feature", "bug-fix", "refactor", "release") for template_use. Always returns the formatted plan plus open/total counts.',
5571
+ description: "Manage a persistent strategic plan for the current session. Unlike todos, plans are meant for higher-level, multi-phase approaches and survive across conversation resumptions. Use this to outline big-picture work, then promote concrete items into the todo list when ready to execute.",
5572
+ usageHint: 'RECOMMENDED FOR COMPLEX, MULTI-PHASE WORK:\n\n- Start by creating a high-level plan with `action: "add"` or using templates (`template_use`).\n- Use `promote` to turn a plan item into actionable todos.\n- Keep plans at the "why and what" level, and todos at the "how and next step" level.\n- Common templates: "new-feature", "bug-fix", "refactor", "release", "security-audit".\n\nThis tool is excellent for maintaining long-term direction across many turns or even multiple sessions.',
5404
5573
  permission: "auto",
5405
5574
  mutating: false,
5406
5575
  timeoutMs: 2e3,
@@ -5409,22 +5578,29 @@ var planTool = {
5409
5578
  properties: {
5410
5579
  action: {
5411
5580
  type: "string",
5412
- enum: ["show", "add", "start", "done", "remove", "promote", "derive", "template_use", "clear"]
5581
+ enum: ["show", "add", "start", "done", "remove", "promote", "derive", "template_use", "clear"],
5582
+ description: "The operation to perform on the plan board."
5583
+ },
5584
+ title: {
5585
+ type: "string",
5586
+ description: "Title of the plan item. Required for action=add."
5587
+ },
5588
+ details: {
5589
+ type: "string",
5590
+ description: "Additional details or description for a new plan item (action=add)."
5413
5591
  },
5414
- title: { type: "string", description: "Required when action = add." },
5415
- details: { type: "string", description: "Optional extra context for add." },
5416
5592
  target: {
5417
5593
  type: "string",
5418
- description: "Plan item id, 1-based index, or title substring. Required for start/done/remove/promote/derive."
5594
+ description: "Identifier for the target plan item (id, 1-based index, or partial title). Required for most actions except add/show/clear."
5419
5595
  },
5420
5596
  subtasks: {
5421
5597
  type: "array",
5422
5598
  items: { type: "string" },
5423
- description: "Optional subtasks for promote/derive. If omitted, a single todo is created from the plan item title."
5599
+ description: "List of subtask titles. Used with promote or derive to break a plan item into multiple todos."
5424
5600
  },
5425
5601
  template: {
5426
5602
  type: "string",
5427
- description: "Template name for template_use action. Available: new-feature, bug-fix, refactor, release, security-audit, onboarding."
5603
+ description: "Template identifier when using action=template_use. Common values: new-feature, bug-fix, refactor, release, security-audit."
5428
5604
  }
5429
5605
  },
5430
5606
  required: ["action"]
@@ -5537,18 +5713,28 @@ var MAX_BYTES2 = 5 * 1024 * 1024;
5537
5713
  var readTool = {
5538
5714
  name: "read",
5539
5715
  category: "Filesystem",
5540
- description: "Read the contents of a file. Lines are 1-indexed and prefixed with line numbers.",
5541
- usageHint: "Read a file before editing it. Returns lines numbered like ` 1\u2192content`. Use `offset` and `limit` for large files (default reads up to 2000 lines).",
5716
+ description: "Read the contents of a file with line numbers. This is the primary way to inspect source code, configuration, or any text file before making changes. Lines are returned 1-indexed with a ` N| ` prefix for easy reference in edits.",
5717
+ usageHint: "FOUNDATIONAL TOOL \u2014 call this before almost any edit operation.\n\nBest practices:\n- Always read a file before using `edit`, `replace`, or `write` on it (the system often requires it for safety).\n- Use `offset` + `limit` for very large files instead of reading everything at once.\n- Default limit is generous (2000 lines) but can be increased.\n- The output format is designed to be directly usable as context for `edit` operations.",
5542
5718
  permission: "auto",
5543
5719
  mutating: false,
5720
+ capabilities: ["fs.read"],
5544
5721
  maxOutputBytes: 262144,
5545
5722
  timeoutMs: 5e3,
5546
5723
  inputSchema: {
5547
5724
  type: "object",
5548
5725
  properties: {
5549
- path: { type: "string", description: "File path (absolute or relative to cwd)" },
5550
- offset: { type: "integer", description: "1-based line number to start from" },
5551
- limit: { type: "integer", description: "Max lines to read (default 2000)" }
5726
+ path: {
5727
+ type: "string",
5728
+ description: "Path to the file (relative to project root or absolute within project)."
5729
+ },
5730
+ offset: {
5731
+ type: "integer",
5732
+ description: "1-based starting line number. Use together with `limit` for large files."
5733
+ },
5734
+ limit: {
5735
+ type: "integer",
5736
+ description: "Maximum number of lines to return (default is 2000)."
5737
+ }
5552
5738
  },
5553
5739
  required: ["path"]
5554
5740
  },
@@ -5599,10 +5785,11 @@ var DEFAULT_IGNORE4 = ["node_modules", ".git", "dist", "build", ".next", "covera
5599
5785
  var replaceTool = {
5600
5786
  name: "replace",
5601
5787
  category: "Transform",
5602
- description: "Batch replace a pattern across multiple files matched by glob. Returns diff for each modified file.",
5603
- usageHint: 'Use `glob` for broad patterns (e.g. "**/*.ts"). Set `dry_run: true` to preview without modifying. `files` can be a single path, comma-separated list, or glob pattern.',
5788
+ description: "Perform a search-and-replace across multiple files using a regex pattern. This is a powerful bulk transformation tool. Always use `dry_run: true` first on anything non-trivial.",
5789
+ usageHint: "DANGEROUS IF USED CARELESSLY \u2014 review the diff output carefully.\n\nRecommended workflow:\n1. Start with `dry_run: true` to see exactly what would change.\n2. Use a specific enough `pattern` (and `glob` / `files`) to avoid accidental broad changes.\n3. `replace_all` controls whether only the first match per file or all matches are replaced.\nThis tool is excellent for large-scale refactors (renaming, import updates, etc.) but must be used with caution.",
5604
5790
  permission: "confirm",
5605
5791
  mutating: true,
5792
+ capabilities: ["fs.write"],
5606
5793
  timeoutMs: 3e4,
5607
5794
  inputSchema: {
5608
5795
  type: "object",
@@ -5884,10 +6071,11 @@ describe('{{Name}}', () => {
5884
6071
  var scaffoldTool = {
5885
6072
  name: "scaffold",
5886
6073
  category: "Project",
5887
- description: "Generate boilerplate code from built-in templates or paths. Creates package.json, source files, tests.",
5888
- usageHint: "Set `template` (npm-package, cli-tool, react-component) and `name`. `vars` for template variables. `dry_run` preview.",
6074
+ description: "Generate new files and folder structures from built-in templates or custom definitions. This is the recommended way to bootstrap new packages, components, or modules instead of creating files one by one with `write`.",
6075
+ usageHint: "PREFERRED FOR SCAFFOLDING:\n\n- Use built-in templates when they match your needs (e.g. react-component, npm-package).\n- Supports `dry_run` so you can preview exactly what will be created.\n- Has the powerful `fs.write.outside-project` capability \u2014 review paths carefully.\nMuch cleaner and safer than manually writing multiple files.",
5889
6076
  permission: "confirm",
5890
6077
  mutating: true,
6078
+ capabilities: ["fs.write.outside-project", "fs.write"],
5891
6079
  timeoutMs: 3e4,
5892
6080
  inputSchema: {
5893
6081
  type: "object",
@@ -5980,10 +6168,11 @@ var TIMEOUT_MS4 = 15e3;
5980
6168
  var searchTool = {
5981
6169
  name: "search",
5982
6170
  category: "Search",
5983
- description: "Search the web for information. Returns title, URL, and snippet for each result.",
5984
- usageHint: "Set `num_results` (1-50, default 10). Use `source` to pick engine: duckduckgo (default), google, bing.",
6171
+ description: "Perform a web search and return results with title, URL, and snippet. Use this when you need up-to-date external information that is not in the local codebase.",
6172
+ usageHint: "Good for: API documentation, error messages, library usage examples, current best practices.\n\n- Prefer specific queries over very broad ones.\n- Results go through the guarded fetch system (same protections as the `fetch` tool).\n- This is often better than the model trying to recall outdated knowledge.",
5985
6173
  permission: "confirm",
5986
6174
  mutating: false,
6175
+ capabilities: ["net.outbound"],
5987
6176
  timeoutMs: TIMEOUT_MS4,
5988
6177
  inputSchema: {
5989
6178
  type: "object",
@@ -6186,8 +6375,8 @@ function stripTags2(html) {
6186
6375
  var testTool = {
6187
6376
  name: "test",
6188
6377
  category: "Code Quality",
6189
- description: "Run tests with vitest, jest, or mocha. Returns pass/fail counts and output.",
6190
- usageHint: "Set `files` for specific tests. `watch` enables watch mode. `coverage` generates coverage report. `grep` filters by name.",
6378
+ description: "Execute the project's test suite. This is one of the most critical tools for validating that your changes are correct.",
6379
+ usageHint: "ESSENTIAL BEFORE CONSIDERING WORK DONE:\n\n- Use `files` or `grep` to run only relevant tests during development.\n- `coverage: true` is useful when working on critical paths.\nRun tests frequently. A clean test run is usually required before the task can be considered complete.",
6191
6380
  permission: "confirm",
6192
6381
  mutating: false,
6193
6382
  timeoutMs: 12e4,
@@ -6325,7 +6514,7 @@ function parseResult(runner, result, duration) {
6325
6514
  passed,
6326
6515
  failed,
6327
6516
  duration_ms: duration,
6328
- output: result.stdout || result.error || "",
6517
+ output: normalizeCommandOutput(result.stdout || result.error || ""),
6329
6518
  truncated: result.truncated
6330
6519
  };
6331
6520
  }
@@ -6334,8 +6523,8 @@ function parseResult(runner, result, duration) {
6334
6523
  var todoTool = {
6335
6524
  name: "todo",
6336
6525
  category: "Session",
6337
- description: "Replace the current todo list with a new set of items.",
6338
- usageHint: "Use for multi-step tasks. Replace the full list on each call. At most ONE task may be in_progress at a time. Items have id, content, status (pending|in_progress|completed), and optional activeForm.",
6526
+ description: "Manage the session-level todo list. This is the primary mechanism for tracking multi-step work. The list is fully replaced on every call (not appended).",
6527
+ usageHint: "BEST PRACTICE for complex tasks:\n- At the beginning of a non-trivial task, create a clear todo list with specific, actionable items.\n- Only **one** item should be `in_progress` at any time.\n- Update the list frequently as work progresses (mark items done, add new ones, change status).\n- The system and user can see this list, so keep it honest and up-to-date.\nThis tool is extremely valuable for maintaining focus and giving the user visibility into your plan.",
6339
6528
  permission: "auto",
6340
6529
  mutating: false,
6341
6530
  timeoutMs: 1e3,
@@ -6347,13 +6536,27 @@ var todoTool = {
6347
6536
  items: {
6348
6537
  type: "object",
6349
6538
  properties: {
6350
- id: { type: "string" },
6351
- content: { type: "string" },
6352
- status: { type: "string", enum: ["pending", "in_progress", "completed"] },
6353
- activeForm: { type: "string" }
6539
+ id: {
6540
+ type: "string",
6541
+ description: 'Unique identifier for the todo item (e.g. "1", "auth-flow").'
6542
+ },
6543
+ content: {
6544
+ type: "string",
6545
+ description: "Clear, actionable description of the task."
6546
+ },
6547
+ status: {
6548
+ type: "string",
6549
+ enum: ["pending", "in_progress", "completed"],
6550
+ description: 'Current status. Only one item should be "in_progress" at a time.'
6551
+ },
6552
+ activeForm: {
6553
+ type: "string",
6554
+ description: 'Optional present-tense form shown while the task is active (e.g. "Fixing auth bug").'
6555
+ }
6354
6556
  },
6355
6557
  required: ["id", "content", "status"]
6356
- }
6558
+ },
6559
+ description: "The complete new list of todos. This replaces the previous list entirely."
6357
6560
  }
6358
6561
  },
6359
6562
  required: ["todos"]
@@ -6385,8 +6588,8 @@ var todoTool = {
6385
6588
  var toolHelpTool = {
6386
6589
  name: "tool_help",
6387
6590
  category: "Meta",
6388
- description: "Get help and usage information for a specific tool or list all available tools.",
6389
- usageHint: "Set `tool` for specific help. Omit to list all tools. `format`: short (one-liner), full (schema), markdown (formatted).",
6591
+ description: "Get detailed help for one or more tools, including their full schema and usage guidance. This is the best way to understand exactly how to call a specific tool.",
6592
+ usageHint: "USE WHEN YOU NEED PRECISE TOOL INFORMATION:\n\n- Call with a specific `tool` name when you want the full schema and current usageHint.\n- Omit `tool` (or use a broad query) to get an overview of available tools.\n- Different `format` options give you different levels of detail.\nThis tool is extremely valuable for self-correction when you are unsure about a tool's interface.",
6390
6593
  permission: "auto",
6391
6594
  mutating: false,
6392
6595
  timeoutMs: 5e3,
@@ -6395,16 +6598,16 @@ var toolHelpTool = {
6395
6598
  properties: {
6396
6599
  tool: {
6397
6600
  type: "string",
6398
- description: "Tool name to get help for (omit for all tools)"
6601
+ description: "Specific tool name to get detailed help for. Omit to get a list of all tools."
6399
6602
  },
6400
6603
  format: {
6401
6604
  type: "string",
6402
6605
  enum: ["short", "full", "markdown"],
6403
- description: "Output format (default: short)"
6606
+ description: 'Level of detail: "short" (summary), "full" (with full schema), "markdown" (human readable).'
6404
6607
  },
6405
6608
  include_examples: {
6406
6609
  type: "boolean",
6407
- description: "Include usage examples in output (default: false)"
6610
+ description: "Whether to include example usage in the response."
6408
6611
  }
6409
6612
  }
6410
6613
  },
@@ -6507,8 +6710,8 @@ function formatAllToolsMarkdown(tools) {
6507
6710
  var toolSearchTool = {
6508
6711
  name: "tool_search",
6509
6712
  category: "Meta",
6510
- description: "Search available tools by name, description, tags, permission level, or mutating flag.",
6511
- usageHint: "Set `query` for keyword search. `tags` to filter by category. `permission` to filter by required permission. `mutating` to filter by mutating flag.",
6713
+ description: "Search the catalog of available tools. Very useful when you are unsure which tool to use for a task.",
6714
+ usageHint: "SELF-DISCOVERY TOOL:\n\n- Use when you need to find the right tool for a job.\n- `query` searches names and descriptions.\n- You can filter by `tags` (category), `permission`, or `mutating`.\nCall this before guessing tool names. It helps you discover the best tool for the current situation.",
6512
6715
  permission: "auto",
6513
6716
  mutating: false,
6514
6717
  timeoutMs: 1e3,
@@ -6581,8 +6784,8 @@ var toolSearchTool = {
6581
6784
  var toolUseTool = {
6582
6785
  name: "tool_use",
6583
6786
  category: "Meta",
6584
- description: "Execute a specific tool by name with given input. Useful when the agent knows exactly which tool to call.",
6585
- usageHint: "Set `tool` with exact tool name and `input` with the tool parameters. Returns result or error.",
6787
+ description: "Directly execute any registered tool by its exact name, bypassing normal discovery. This is a powerful meta-tool intended for cases where the agent has a clear plan and knows precisely which tool to invoke.",
6788
+ usageHint: "ADVANCED META TOOL \u2014 USE WITH CARE:\n\n- Only use when you are certain of the exact tool name and its expected input shape.\n- Prefer using the normal tool calling mechanism when possible.\n- Very useful in batch-tool-use or when orchestrating complex workflows programmatically.\n- The call still goes through full permission checks and capability validation.",
6586
6789
  permission: "confirm",
6587
6790
  mutating: true,
6588
6791
  timeoutMs: 6e4,
@@ -6591,11 +6794,11 @@ var toolUseTool = {
6591
6794
  properties: {
6592
6795
  tool: {
6593
6796
  type: "string",
6594
- description: "Exact name of the tool to execute"
6797
+ description: 'The exact registered name of the tool to invoke (e.g. "bash", "read", "codebase-search").'
6595
6798
  },
6596
6799
  input: {
6597
6800
  type: "object",
6598
- description: "Input parameters for the tool"
6801
+ description: "The input object matching the target tool's inputSchema."
6599
6802
  }
6600
6803
  },
6601
6804
  required: ["tool"]
@@ -6661,34 +6864,41 @@ var DEFAULT_IGNORE5 = [
6661
6864
  var treeTool = {
6662
6865
  name: "tree",
6663
6866
  category: "Filesystem",
6664
- description: "Display directory structure as an ASCII tree. Shows files and folders with indentation.",
6665
- usageHint: "Set `path` (default: cwd). `depth` limits nesting (default: 3). `glob` filters files. `exclude` ignores dirs. `show_files` toggles file listing (default: true).",
6867
+ description: "Display a directory tree of the project (or a subpath). This is the recommended way to explore the high-level structure of a codebase before reading specific files.",
6868
+ usageHint: "BEST PRACTICE FOR INITIAL EXPLORATION:\n\n- Call early when working with an unfamiliar project or module.\n- Tune `depth` (default 3) and use `glob`/`exclude` to focus the view.\n- Prefer this over raw `bash find` or `glob` + manual reading when you need a quick structural overview.\nOutput is truncated for very large trees.",
6666
6869
  permission: "auto",
6667
6870
  mutating: false,
6871
+ capabilities: ["fs.read"],
6668
6872
  timeoutMs: 15e3,
6669
6873
  inputSchema: {
6670
6874
  type: "object",
6671
6875
  properties: {
6672
- path: { type: "string", description: "Root directory (default: cwd)" },
6876
+ path: {
6877
+ type: "string",
6878
+ description: "Root directory to display the tree from (defaults to project root)."
6879
+ },
6673
6880
  depth: {
6674
6881
  type: "integer",
6675
- description: "Max nesting depth (default: 3, 0 for unlimited)",
6882
+ description: "Maximum directory depth to traverse (default 3, use 0 for unlimited).",
6676
6883
  minimum: 0,
6677
6884
  maximum: 20
6678
6885
  },
6679
- glob: { type: "string", description: 'Filter files matching glob (e.g. "*.ts")' },
6886
+ glob: {
6887
+ type: "string",
6888
+ description: "Only include files matching this glob pattern."
6889
+ },
6680
6890
  exclude: {
6681
6891
  type: "array",
6682
6892
  items: { type: "string" },
6683
- description: "Directory names to exclude"
6893
+ description: "List of directory names to completely ignore."
6684
6894
  },
6685
6895
  show_files: {
6686
6896
  type: "boolean",
6687
- description: "Show files (default: true, false shows dirs only)"
6897
+ description: "Whether to show individual files (default true)."
6688
6898
  },
6689
6899
  show_dirs: {
6690
6900
  type: "boolean",
6691
- description: "Show directories (default: true)"
6901
+ description: "Whether to show directories (default true)."
6692
6902
  },
6693
6903
  show_hidden: {
6694
6904
  type: "boolean",
@@ -6816,8 +7026,8 @@ async function walkDir(dir, depth, opts) {
6816
7026
  var typecheckTool = {
6817
7027
  name: "typecheck",
6818
7028
  category: "Code Quality",
6819
- description: "Run TypeScript type checking with `tsc --noEmit`. Checks for type errors without compiling.",
6820
- usageHint: "Set `project` for tsconfig path (default: nearest). `strict` enables strictest flags. `all` checks all projects in workspace.",
7029
+ description: "Run the project's TypeScript type checker (`tsc --noEmit` or equivalent). Essential for verifying type safety before making changes or committing.",
7030
+ usageHint: "ALWAYS RUN BEFORE CONSIDERING WORK COMPLETE:\n\n- Use this to catch type errors early.\n- In monorepos, `all: true` will check every package.\n- This is one of the most important quality gates in this project.\nNever claim a task is done without a clean typecheck (unless the user explicitly says otherwise).",
6821
7031
  permission: "confirm",
6822
7032
  mutating: false,
6823
7033
  timeoutMs: 12e4,
@@ -6875,7 +7085,7 @@ var typecheckTool = {
6875
7085
  exit_code: result.exitCode,
6876
7086
  errors,
6877
7087
  warnings,
6878
- output: result.stdout || result.stderr || result.error || "",
7088
+ output: normalizeCommandOutput(result.stdout || result.stderr || result.error || ""),
6879
7089
  truncated: result.truncated
6880
7090
  }
6881
7091
  };
@@ -6896,16 +7106,23 @@ async function findTsConfig(cwd) {
6896
7106
  var writeTool = {
6897
7107
  name: "write",
6898
7108
  category: "Filesystem",
6899
- description: "Write or overwrite a file. For existing files, prefer `edit` over `write`.",
6900
- usageHint: "Use `write` for new files or full replacements. For partial edits use `edit`. Existing files must have been `read` first in this session.",
7109
+ description: "Write or completely overwrite a file on disk. This is a high-privilege operation. For modifying existing files, you should almost always prefer the `edit` tool instead, because `edit` is safer and works on the last-read version of the file.",
7110
+ usageHint: "RULES FOR CORRECT USAGE:\n- Use `write` primarily for **new files** or when you want to replace the entire content.\n- For any existing file, strongly prefer `edit` (it requires a prior `read` in the same session and is more precise).\n- You MUST have called `read` on the file earlier in the conversation before using `write` on an existing path (the system enforces this for safety).\n- The path is resolved relative to the project root and protected against escaping the workspace.",
6901
7111
  permission: "confirm",
6902
7112
  mutating: true,
6903
7113
  timeoutMs: 5e3,
7114
+ capabilities: ["fs.write"],
6904
7115
  inputSchema: {
6905
7116
  type: "object",
6906
7117
  properties: {
6907
- path: { type: "string" },
6908
- content: { type: "string" }
7118
+ path: {
7119
+ type: "string",
7120
+ description: "Relative path from project root. Must not escape the project."
7121
+ },
7122
+ content: {
7123
+ type: "string",
7124
+ description: "The complete new content of the file."
7125
+ }
6909
7126
  },
6910
7127
  required: ["path", "content"]
6911
7128
  },
@@ -6992,7 +7209,7 @@ var builtinTools = [
6992
7209
  // src/pack.ts
6993
7210
  var builtinToolsPack = {
6994
7211
  name: "builtin-tools",
6995
- description: "WrongStack built-in filesystem, execution, network, lifecycle, and agent-control tools.",
7212
+ description: "The complete set of built-in tools that ship with WrongStack. Covers filesystem (read/write/edit/replace/glob/grep/tree), execution (bash/exec/git/install), networking (fetch/search), code quality (lint/test/typecheck/format), planning (todo/plan/memory), and meta tools (tool-search/tool-help/batch-tool-use/codebase-*).",
6996
7213
  tools: builtinTools
6997
7214
  };
6998
7215