@wrongstack/tools 0.31.1 → 0.41.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.
package/dist/index.js CHANGED
@@ -1,5 +1,4 @@
1
1
  import * as fs4 from 'node:fs/promises';
2
- import { stat } from 'node:fs/promises';
3
2
  import * as path from 'node:path';
4
3
  import { resolve, sep, dirname } from 'node:path';
5
4
  import { atomicWrite, unifiedDiff, detectNewlineStyle, normalizeToLf, toStyle, compileGlob, buildChildEnv, loadPlan, emptyPlan, clearPlan, savePlan, getPlanTemplate, addPlanItem, deriveTodosFromPlanItem, removePlanItem, setPlanItemStatus, formatPlan, stripAnsi, resolveWstackPaths } from '@wrongstack/core';
@@ -19,6 +18,20 @@ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require
19
18
  if (typeof require !== "undefined") return require.apply(this, arguments);
20
19
  throw Error('Dynamic require of "' + x + '" is not supported');
21
20
  });
21
+ async function detectPackageManager(cwd) {
22
+ const { stat: stat10 } = await import('node:fs/promises');
23
+ try {
24
+ await stat10(`${cwd}/pnpm-lock.yaml`);
25
+ return "pnpm";
26
+ } catch {
27
+ }
28
+ try {
29
+ await stat10(`${cwd}/yarn.lock`);
30
+ return "yarn";
31
+ } catch {
32
+ }
33
+ return "npm";
34
+ }
22
35
  function resolvePath(input, ctx) {
23
36
  return path.isAbsolute(input) ? path.normalize(input) : path.resolve(ctx.cwd, input);
24
37
  }
@@ -182,9 +195,9 @@ var readTool = {
182
195
  async execute(input, ctx) {
183
196
  if (!input?.path) throw new Error("read: path is required");
184
197
  const absPath = await safeResolveReal(input.path, ctx);
185
- let stat11;
198
+ let stat10;
186
199
  try {
187
- stat11 = await fs4.stat(absPath);
200
+ stat10 = await fs4.stat(absPath);
188
201
  } catch (err) {
189
202
  const code = err.code;
190
203
  if (code === "ENOENT") throw new Error(`read: file not found "${input.path}"`);
@@ -192,9 +205,9 @@ var readTool = {
192
205
  `read: failed to stat "${input.path}": ${err instanceof Error ? err.message : String(err)}`
193
206
  );
194
207
  }
195
- if (!stat11.isFile()) throw new Error(`read: "${input.path}" is not a regular file`);
196
- if (stat11.size > MAX_BYTES) {
197
- throw new Error(`read: file too large (${stat11.size} bytes, limit ${MAX_BYTES})`);
208
+ if (!stat10.isFile()) throw new Error(`read: "${input.path}" is not a regular file`);
209
+ if (stat10.size > MAX_BYTES) {
210
+ throw new Error(`read: file too large (${stat10.size} bytes, limit ${MAX_BYTES})`);
198
211
  }
199
212
  const buf = await fs4.readFile(absPath);
200
213
  if (isBinaryBuffer(buf)) {
@@ -206,14 +219,14 @@ var readTool = {
206
219
  const offset = Math.max(1, input.offset ?? 1);
207
220
  const limit = Math.max(0, Math.min(input.limit ?? 2e3, 5e3));
208
221
  if (limit === 0) {
209
- ctx.recordRead(absPath, stat11.mtimeMs);
222
+ ctx.recordRead(absPath, stat10.mtimeMs);
210
223
  return { text: "", total_lines: total, encoding: "utf8", truncated: total > 0 };
211
224
  }
212
225
  const slice = allLines.slice(offset - 1, offset - 1 + limit);
213
226
  const truncated = offset - 1 + slice.length < total;
214
227
  const width = String(offset + slice.length - 1).length;
215
228
  const numbered = slice.map((line, i) => `${String(offset + i).padStart(width, " ")}\u2192${line}`).join("\n");
216
- ctx.recordRead(absPath, stat11.mtimeMs);
229
+ ctx.recordRead(absPath, stat10.mtimeMs);
217
230
  return {
218
231
  text: numbered,
219
232
  total_lines: total,
@@ -252,12 +265,12 @@ var writeTool = {
252
265
  let existed = false;
253
266
  let prev = "";
254
267
  try {
255
- const stat12 = await fs4.stat(absPath);
256
- existed = stat12.isFile();
268
+ const stat11 = await fs4.stat(absPath);
269
+ existed = stat11.isFile();
257
270
  if (existed) {
258
271
  if (!ctx.hasRead(absPath)) {
259
272
  prev = await fs4.readFile(absPath, "utf8");
260
- ctx.recordRead(absPath, stat12.mtimeMs);
273
+ ctx.recordRead(absPath, stat11.mtimeMs);
261
274
  } else {
262
275
  prev = await fs4.readFile(absPath, "utf8");
263
276
  }
@@ -270,8 +283,8 @@ var writeTool = {
270
283
  await atomicWrite(absPath, input.content);
271
284
  const diff = existed ? unifiedDiff(prev, input.content, { fromFile: input.path, toFile: input.path }) : `+++ ${input.path}
272
285
  + (new file, ${input.content.split("\n").length} lines)`;
273
- const stat11 = await fs4.stat(absPath);
274
- ctx.recordRead(absPath, stat11.mtimeMs);
286
+ const stat10 = await fs4.stat(absPath);
287
+ ctx.recordRead(absPath, stat10.mtimeMs);
275
288
  ctx.session.recordFileChange({
276
289
  path: absPath,
277
290
  action: existed ? "modified" : "created",
@@ -311,13 +324,13 @@ var editTool = {
311
324
  if (input.new_string === void 0) throw new Error("edit: new_string is required");
312
325
  if (input.old_string === "") throw new Error("edit: old_string cannot be empty");
313
326
  const absPath = await safeResolveReal(input.path, ctx);
314
- const stat11 = await fs4.stat(absPath).catch((err) => {
327
+ const stat10 = await fs4.stat(absPath).catch((err) => {
315
328
  if (err.code === "ENOENT") {
316
329
  throw new Error(`edit: file "${input.path}" does not exist. Use \`write\` instead.`);
317
330
  }
318
331
  throw err;
319
332
  });
320
- if (!stat11.isFile()) throw new Error(`edit: "${input.path}" is not a regular file`);
333
+ if (!stat10.isFile()) throw new Error(`edit: "${input.path}" is not a regular file`);
321
334
  if (!ctx.hasRead(absPath)) {
322
335
  throw new Error(`edit: file "${input.path}" was not read in this session. Read it first.`);
323
336
  }
@@ -511,8 +524,8 @@ var replaceTool = {
511
524
  }
512
525
  const rel = path.relative(realRoot, realPath);
513
526
  if (rel.startsWith("..") || path.isAbsolute(rel)) continue;
514
- const stat11 = await fs4.stat(realPath).catch(() => null);
515
- if (!stat11 || !stat11.isFile()) continue;
527
+ const stat10 = await fs4.stat(realPath).catch(() => null);
528
+ if (!stat10 || !stat10.isFile()) continue;
516
529
  let content;
517
530
  try {
518
531
  const buf = await fs4.readFile(realPath);
@@ -537,7 +550,7 @@ var replaceTool = {
537
550
  totalReplacements += count;
538
551
  if (!dryRun) {
539
552
  const newContent = toStyle(newContentLf, style);
540
- await atomicWrite(realPath, newContent, { mode: stat11.mode & 511 });
553
+ await atomicWrite(realPath, newContent, { mode: stat10.mode & 511 });
541
554
  }
542
555
  const diff = dryRun || matches.length > 0 ? unifiedDiff(content, toStyle(newContentLf, style), {
543
556
  fromFile: absPath,
@@ -567,8 +580,8 @@ async function resolveFiles(filesInput, ctx, extraGlob) {
567
580
  const resolved = [];
568
581
  for (const p of parts) {
569
582
  const absPath = safeResolve(p, ctx);
570
- const stat11 = await fs4.stat(absPath).catch(() => null);
571
- if (stat11?.isFile()) {
583
+ const stat10 = await fs4.stat(absPath).catch(() => null);
584
+ if (stat10?.isFile()) {
572
585
  resolved.push(absPath);
573
586
  }
574
587
  }
@@ -626,8 +639,8 @@ async function globNative(pattern, base, extraGlob) {
626
639
  if (DEFAULT_IGNORE.includes(e.name)) continue;
627
640
  const full = path.join(dir, e.name);
628
641
  try {
629
- const stat11 = await fs4.lstat(full);
630
- if (stat11.isSymbolicLink()) continue;
642
+ const stat10 = await fs4.lstat(full);
643
+ if (stat10.isSymbolicLink()) continue;
631
644
  } catch {
632
645
  continue;
633
646
  }
@@ -976,8 +989,8 @@ async function runNative(input, base, mode, limit, signal) {
976
989
  if (globRe && !globRe.test(e.name) && !globRe.test(full)) continue;
977
990
  if (globRe) globRe.lastIndex = 0;
978
991
  try {
979
- const stat11 = await fs4.stat(full);
980
- if (stat11.size > 1e6) continue;
992
+ const stat10 = await fs4.stat(full);
993
+ if (stat10.size > 1e6) continue;
981
994
  const head = await fs4.readFile(full);
982
995
  if (isBinaryBuffer(head)) continue;
983
996
  const text = head.toString("utf8");
@@ -2510,15 +2523,26 @@ var planTool = {
2510
2523
  category: "Session",
2511
2524
  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.",
2512
2525
  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.',
2513
- permission: "auto",
2514
- mutating: false,
2526
+ permission: "confirm",
2527
+ mutating: true,
2528
+ capabilities: ["fs.write"],
2515
2529
  timeoutMs: 2e3,
2516
2530
  inputSchema: {
2517
2531
  type: "object",
2518
2532
  properties: {
2519
2533
  action: {
2520
2534
  type: "string",
2521
- enum: ["show", "add", "start", "done", "remove", "promote", "derive", "template_use", "clear"],
2535
+ enum: [
2536
+ "show",
2537
+ "add",
2538
+ "start",
2539
+ "done",
2540
+ "remove",
2541
+ "promote",
2542
+ "derive",
2543
+ "template_use",
2544
+ "clear"
2545
+ ],
2522
2546
  description: "The operation to perform on the plan board."
2523
2547
  },
2524
2548
  title: {
@@ -2611,7 +2635,12 @@ var planTool = {
2611
2635
  plan = derived.plan;
2612
2636
  await savePlan(planPath, plan);
2613
2637
  ctx.state.replaceTodos(derived.todos);
2614
- return mkResult(plan, true, `${input.action} ok \u2014 ${derived.todos.length} todo(s) created.`, derived.todos);
2638
+ return mkResult(
2639
+ plan,
2640
+ true,
2641
+ `${input.action} ok \u2014 ${derived.todos.length} todo(s) created.`,
2642
+ derived.todos
2643
+ );
2615
2644
  }
2616
2645
  case "template_use": {
2617
2646
  const templateName = input.template?.trim();
@@ -2626,7 +2655,11 @@ var planTool = {
2626
2655
  ({ plan } = addPlanItem(plan, item.title, item.details));
2627
2656
  }
2628
2657
  await savePlan(planPath, plan);
2629
- return mkResult(plan, true, `Applied template "${template.name}" \u2014 ${template.items.length} items added.`);
2658
+ return mkResult(
2659
+ plan,
2660
+ true,
2661
+ `Applied template "${template.name}" \u2014 ${template.items.length} items added.`
2662
+ );
2630
2663
  }
2631
2664
  case "clear":
2632
2665
  plan = clearPlan(plan);
@@ -2772,8 +2805,8 @@ function findGitDir(cwd, projectRoot) {
2772
2805
  let dir = cwd;
2773
2806
  for (let i = 0; i < 20; i++) {
2774
2807
  try {
2775
- const stat11 = statSync(`${dir}/.git`);
2776
- if (stat11.isDirectory() || stat11.isFile()) return dir;
2808
+ const stat10 = statSync(`${dir}/.git`);
2809
+ if (stat10.isDirectory() || stat10.isFile()) return dir;
2777
2810
  } catch {
2778
2811
  }
2779
2812
  if (dir === root) break;
@@ -3127,8 +3160,8 @@ function toYaml(data, indent = 0) {
3127
3160
  var diffTool = {
3128
3161
  name: "diff",
3129
3162
  category: "Filesystem",
3130
- description: "Show code differences between files, commits, branches, or staged changes. A safer and more structured alternative to raw `git diff` via shell.",
3131
- 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).',
3163
+ description: "Show file content with line numbers, staged/working-tree diffs via git, or commit/branch diffs. A safer and more structured alternative to raw `git diff` via shell.",
3164
+ usageHint: 'USE FOR CODE REVIEW AND CHANGE INSPECTION:\n\n- `files` + no `a`/`b` \u2192 show file content with line numbers (NOT a unified diff; no +/- prefixes).\n- `a` and/or `b` \u2192 git-style commit/branch diff (unified format, real +/- prefixes).\n- `staged: true` \u2192 only show staged changes.\n- `mode` can be "unified", "stat", or "side-by-side" (only affects the git-diff path).\n\nNOTE: For a true file-vs-file unified diff, supply `a` and `b` so the tool delegates to `git diff`. The `files`-only path is a line-numbered dump, not a diff.\n\nThis tool has important safety guards against flag injection (see previous security findings).',
3132
3165
  permission: "auto",
3133
3166
  mutating: false,
3134
3167
  capabilities: ["fs.read"],
@@ -3205,8 +3238,8 @@ function findGitDir2(cwd) {
3205
3238
  let dir = cwd;
3206
3239
  for (let i = 0; i < 20; i++) {
3207
3240
  try {
3208
- const stat11 = statSync(path.join(dir, ".git"));
3209
- if (stat11.isDirectory()) return dir;
3241
+ const stat10 = statSync(path.join(dir, ".git"));
3242
+ if (stat10.isDirectory()) return dir;
3210
3243
  } catch {
3211
3244
  }
3212
3245
  const parent = path.dirname(dir);
@@ -3236,7 +3269,7 @@ function runGit2(args, cwd, signal) {
3236
3269
  });
3237
3270
  }
3238
3271
  async function fileDiff(input, ctx, _signal) {
3239
- input.context ?? 3;
3272
+ void input.context;
3240
3273
  const files = input.files ? (Array.isArray(input.files) ? input.files : input.files.split(",")).map((f) => f.trim()).filter(Boolean) : [];
3241
3274
  if (files.length === 0) {
3242
3275
  return {
@@ -3249,23 +3282,24 @@ async function fileDiff(input, ctx, _signal) {
3249
3282
  const results = [];
3250
3283
  for (const file of files) {
3251
3284
  const absPath = safeResolve(file, ctx);
3252
- const stat11 = await fs4.stat(absPath).catch(() => null);
3253
- if (!stat11?.isFile()) continue;
3285
+ const stat10 = await fs4.stat(absPath).catch(() => null);
3286
+ if (!stat10?.isFile()) continue;
3254
3287
  const content = await fs4.readFile(absPath, "utf8");
3255
3288
  const lines = content.split(/\r?\n/);
3256
- results.push(`--- ${file}
3257
- +++ ${file}
3258
- ${formatUnified(lines)}`);
3289
+ results.push(formatWithLineNumbers(file, lines));
3259
3290
  }
3260
3291
  return {
3261
- diff: results.join("\n"),
3292
+ diff: results.join("\n\n"),
3262
3293
  files,
3263
3294
  truncated: false,
3264
3295
  mode: input.mode ?? "unified"
3265
3296
  };
3266
3297
  }
3267
- function formatUnified(lines, _context) {
3268
- return lines.map((line, _i) => ` ${line}`).join("\n");
3298
+ function formatWithLineNumbers(file, lines) {
3299
+ const width = String(lines.length).length;
3300
+ const numbered = lines.map((line, i) => `${String(i + 1).padStart(width)} | ${line}`).join("\n");
3301
+ return `--- ${file} (line-numbered dump, not a unified diff) ---
3302
+ ${numbered}`;
3269
3303
  }
3270
3304
  var DEFAULT_IGNORE4 = [
3271
3305
  "node_modules",
@@ -3599,11 +3633,11 @@ var lintTool = {
3599
3633
  }
3600
3634
  };
3601
3635
  async function detectLinter(cwd) {
3602
- const { stat: stat11 } = await import('node:fs/promises');
3636
+ const { stat: stat10 } = await import('node:fs/promises');
3603
3637
  const checks = ["biome.json", ".eslintrc.json", "tslint.json", ".eslintrc.js", "tsconfig.json"];
3604
3638
  for (const f of checks) {
3605
3639
  try {
3606
- await stat11(`${cwd}/${f}`);
3640
+ await stat10(`${cwd}/${f}`);
3607
3641
  if (f.includes("biome")) return "biome";
3608
3642
  if (f.includes("eslint")) return "eslint";
3609
3643
  if (f.includes("tslint")) return "tslint";
@@ -3621,6 +3655,7 @@ var formatTool = {
3621
3655
  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.",
3622
3656
  permission: "confirm",
3623
3657
  mutating: true,
3658
+ capabilities: ["fs.write", "shell.exec"],
3624
3659
  timeoutMs: 6e4,
3625
3660
  inputSchema: {
3626
3661
  type: "object",
@@ -3698,13 +3733,13 @@ var formatTool = {
3698
3733
  }
3699
3734
  };
3700
3735
  async function detectFixer(cwd) {
3701
- const { stat: stat11 } = await import('node:fs/promises');
3736
+ const { stat: stat10 } = await import('node:fs/promises');
3702
3737
  try {
3703
- await stat11(`${cwd}/biome.json`);
3738
+ await stat10(`${cwd}/biome.json`);
3704
3739
  return "biome";
3705
3740
  } catch {
3706
3741
  try {
3707
- await stat11(`${cwd}/.prettierrc`);
3742
+ await stat10(`${cwd}/.prettierrc`);
3708
3743
  return "prettier";
3709
3744
  } catch {
3710
3745
  return "biome";
@@ -3780,11 +3815,11 @@ var typecheckTool = {
3780
3815
  }
3781
3816
  };
3782
3817
  async function findTsConfig(cwd) {
3783
- const { stat: stat11 } = await import('node:fs/promises');
3818
+ const { stat: stat10 } = await import('node:fs/promises');
3784
3819
  const candidates = ["tsconfig.json", "tsconfig.base.json"];
3785
3820
  for (const f of candidates) {
3786
3821
  try {
3787
- const s = await stat11(path.join(cwd, f));
3822
+ const s = await stat10(path.join(cwd, f));
3788
3823
  if (s.isFile()) return path.join(cwd, f);
3789
3824
  } catch {
3790
3825
  }
@@ -3861,11 +3896,11 @@ var testTool = {
3861
3896
  }
3862
3897
  };
3863
3898
  async function detectRunner(cwd) {
3864
- const { stat: stat11 } = await import('node:fs/promises');
3899
+ const { stat: stat10 } = await import('node:fs/promises');
3865
3900
  const candidates = ["vitest.config.ts", "jest.config.js", ".mocharc.json"];
3866
3901
  for (const f of candidates) {
3867
3902
  try {
3868
- await stat11(path.join(cwd, f));
3903
+ await stat10(path.join(cwd, f));
3869
3904
  if (f.includes("vitest")) return "vitest";
3870
3905
  if (f.includes("jest")) return "jest";
3871
3906
  if (f.includes("mocha")) return "mocha";
@@ -4042,20 +4077,6 @@ var installTool = {
4042
4077
  };
4043
4078
  }
4044
4079
  };
4045
- async function detectPackageManager(cwd) {
4046
- const { stat: stat11 } = await import('node:fs/promises');
4047
- try {
4048
- await stat11(`${cwd}/pnpm-lock.yaml`);
4049
- return "pnpm";
4050
- } catch {
4051
- try {
4052
- await stat11(`${cwd}/yarn.lock`);
4053
- return "yarn";
4054
- } catch {
4055
- return "npm";
4056
- }
4057
- }
4058
- }
4059
4080
 
4060
4081
  // src/audit.ts
4061
4082
  var auditTool = {
@@ -4089,7 +4110,7 @@ var auditTool = {
4089
4110
  },
4090
4111
  async *executeStream(input, ctx, opts) {
4091
4112
  const cwd = input.cwd ? safeResolve(input.cwd, ctx) : ctx.cwd;
4092
- const manager = await detectManager(cwd);
4113
+ const manager = await detectPackageManager(cwd);
4093
4114
  yield { type: "log", text: `Auditing with ${manager}\u2026`, data: { manager } };
4094
4115
  const args = ["audit", "--json"];
4095
4116
  if (input.fix) args.push("--fix");
@@ -4107,20 +4128,6 @@ var auditTool = {
4107
4128
  yield { type: "final", output: parseAuditOutput(result.stdout, result.exitCode) };
4108
4129
  }
4109
4130
  };
4110
- async function detectManager(cwd) {
4111
- const { stat: stat11 } = await import('node:fs/promises');
4112
- try {
4113
- await stat11(`${cwd}/pnpm-lock.yaml`);
4114
- return "pnpm";
4115
- } catch {
4116
- }
4117
- try {
4118
- await stat11(`${cwd}/yarn.lock`);
4119
- return "yarn";
4120
- } catch {
4121
- }
4122
- return "npm";
4123
- }
4124
4131
  function parseAuditOutput(json, exitCode) {
4125
4132
  if (!json) {
4126
4133
  return {
@@ -4172,7 +4179,7 @@ var outdatedTool = {
4172
4179
  description: "Check for outdated dependencies in the project. Reports current, wanted (semver range), and latest versions available.",
4173
4180
  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.",
4174
4181
  permission: "auto",
4175
- mutating: true,
4182
+ mutating: false,
4176
4183
  timeoutMs: 6e4,
4177
4184
  inputSchema: {
4178
4185
  type: "object",
@@ -4195,26 +4202,13 @@ var outdatedTool = {
4195
4202
  },
4196
4203
  async execute(input, ctx, opts) {
4197
4204
  const cwd = input.cwd ? safeResolve(input.cwd, ctx) : ctx.cwd;
4198
- const manager = await detectManager2(cwd);
4205
+ const manager = await detectPackageManager(cwd);
4199
4206
  const args = ["outdated", "--json"];
4200
4207
  if (input.format === "table") args.push("--table");
4201
4208
  if (input.include_deprecated) args.push("--include", "deprecated");
4202
4209
  return runOutdated(manager, args, cwd, opts.signal);
4203
4210
  }
4204
4211
  };
4205
- async function detectManager2(cwd) {
4206
- try {
4207
- await stat(`${cwd}/pnpm-lock.yaml`);
4208
- return "pnpm";
4209
- } catch {
4210
- }
4211
- try {
4212
- await stat(`${cwd}/yarn.lock`);
4213
- return "yarn";
4214
- } catch {
4215
- }
4216
- return "npm";
4217
- }
4218
4212
  function runOutdated(manager, args, cwd, signal) {
4219
4213
  return new Promise((resolve7) => {
4220
4214
  let stdout = "";
@@ -4480,10 +4474,10 @@ function parseLine(line) {
4480
4474
  var documentTool = {
4481
4475
  name: "document",
4482
4476
  category: "Project",
4483
- description: "Automatically generate or update documentation comments (JSDoc/TSDoc style) for code. Can target specific symbols or entire files/directories.",
4484
- 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.",
4485
- permission: "confirm",
4486
- mutating: true,
4477
+ description: "Preview documentation comments (JSDoc/TSDoc style) that would be generated for code symbols. Returns a list of candidates with status `skipped` \u2014 the tool is currently a read-only preview and does NOT write to files.",
4478
+ usageHint: "USE FOR IMPROVING CODE DOCUMENTATION:\n\n- Good for adding missing docs to public APIs or complex functions.\n- Currently this is a PREVIEW-ONLY tool: it does not modify files.\n- Use the output to decide which symbols to document manually, or pass the candidates to `edit` / `patch`.\n- `overwrite`, `style`, and `target` parameters are accepted for future expansion but are ignored today.\nAlways review the proposed documentation before applying it \u2014 the model can hallucinate details.",
4479
+ permission: "auto",
4480
+ mutating: false,
4487
4481
  timeoutMs: 3e4,
4488
4482
  inputSchema: {
4489
4483
  type: "object",
@@ -4558,8 +4552,8 @@ async function resolveFiles2(filesInput, cwd) {
4558
4552
  for (const f of files) {
4559
4553
  const absPath = f.trim().startsWith("/") ? f.trim() : `${cwd}/${f.trim()}`;
4560
4554
  try {
4561
- const stat11 = await fs4.stat(absPath);
4562
- if (stat11.isFile()) resolved.push(absPath);
4555
+ const stat10 = await fs4.stat(absPath);
4556
+ if (stat10.isFile()) resolved.push(absPath);
4563
4557
  } catch {
4564
4558
  }
4565
4559
  }
@@ -6987,18 +6981,18 @@ async function runIndexer(_ctx, opts) {
6987
6981
  for (const meta of store.getAllFileMetas()) existingMeta.set(meta.file, meta);
6988
6982
  }
6989
6983
  for (const file of files) {
6990
- let stat11;
6984
+ let stat10;
6991
6985
  try {
6992
- stat11 = await fs4.stat(file);
6986
+ stat10 = await fs4.stat(file);
6993
6987
  } catch {
6994
6988
  store.deleteFile(file);
6995
6989
  continue;
6996
6990
  }
6997
- if (!stat11.isFile()) continue;
6991
+ if (!stat10.isFile()) continue;
6998
6992
  const lang = detectLang(file);
6999
6993
  if (!lang) continue;
7000
6994
  const meta = existingMeta.get(file);
7001
- if (!force && meta && meta.mtimeMs === Math.floor(stat11.mtimeMs)) {
6995
+ if (!force && meta && meta.mtimeMs === Math.floor(stat10.mtimeMs)) {
7002
6996
  langStats[lang] = (langStats[lang] ?? 0) + meta.symbolCount;
7003
6997
  symbolsIndexed += meta.symbolCount;
7004
6998
  filesIndexed++;
@@ -7024,7 +7018,7 @@ async function runIndexer(_ctx, opts) {
7024
7018
  store.upsertFile({
7025
7019
  file,
7026
7020
  lang,
7027
- mtimeMs: Math.floor(stat11.mtimeMs),
7021
+ mtimeMs: Math.floor(stat10.mtimeMs),
7028
7022
  symbolCount: 0,
7029
7023
  lastIndexed: Date.now()
7030
7024
  });
@@ -7050,7 +7044,7 @@ async function runIndexer(_ctx, opts) {
7050
7044
  store.upsertFile({
7051
7045
  file,
7052
7046
  lang,
7053
- mtimeMs: Math.floor(stat11.mtimeMs),
7047
+ mtimeMs: Math.floor(stat10.mtimeMs),
7054
7048
  symbolCount: count,
7055
7049
  lastIndexed: Date.now()
7056
7050
  });
@@ -7081,8 +7075,9 @@ var codebaseIndexTool = {
7081
7075
  category: "Project",
7082
7076
  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.",
7083
7077
  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.",
7084
- permission: "auto",
7078
+ permission: "confirm",
7085
7079
  mutating: true,
7080
+ capabilities: ["fs.write.outside-project"],
7086
7081
  timeoutMs: 12e4,
7087
7082
  inputSchema: {
7088
7083
  type: "object",