@wrongstack/tools 0.32.0 → 0.51.3

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