@wrongstack/tools 0.32.0 → 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/audit.js +15 -15
- package/dist/audit.js.map +1 -1
- package/dist/bash.js.map +1 -1
- package/dist/builtin.js +105 -110
- package/dist/builtin.js.map +1 -1
- package/dist/codebase-index/index.js +2 -1
- package/dist/codebase-index/index.js.map +1 -1
- package/dist/diff.js +10 -9
- package/dist/diff.js.map +1 -1
- package/dist/document.js +4 -4
- package/dist/document.js.map +1 -1
- package/dist/edit.js.map +1 -1
- package/dist/exec.js.map +1 -1
- package/dist/fetch.js.map +1 -1
- package/dist/format.js +1 -0
- package/dist/format.js.map +1 -1
- package/dist/git.js.map +1 -1
- package/dist/glob.js.map +1 -1
- package/dist/grep.js.map +1 -1
- package/dist/index.js +105 -110
- package/dist/index.js.map +1 -1
- package/dist/install.js +14 -14
- package/dist/install.js.map +1 -1
- package/dist/lint.js.map +1 -1
- package/dist/logs.js.map +1 -1
- package/dist/outdated.js +16 -16
- package/dist/outdated.js.map +1 -1
- package/dist/pack.js +105 -110
- package/dist/pack.js.map +1 -1
- package/dist/patch.js.map +1 -1
- package/dist/read.js.map +1 -1
- package/dist/replace.js.map +1 -1
- package/dist/scaffold.js.map +1 -1
- package/dist/test.js.map +1 -1
- package/dist/tree.js.map +1 -1
- package/dist/typecheck.js.map +1 -1
- package/dist/write.js.map +1 -1
- package/package.json +2 -2
package/dist/outdated.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/_util.ts","../src/outdated.ts"],"names":["resolve"],"mappings":"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","file":"outdated.js","sourcesContent":["import * as fsp from 'node:fs/promises';\nimport * as path from 'node:path';\nimport { type Context, stripAnsi } from '@wrongstack/core';\n\nexport function resolvePath(input: string, ctx: Context): string {\n return path.isAbsolute(input) ? path.normalize(input) : path.resolve(ctx.cwd, input);\n}\n\nexport function ensureInsideRoot(absPath: string, ctx: Context): string {\n const root = path.resolve(ctx.projectRoot);\n const target = path.resolve(absPath);\n const rel = path.relative(root, target);\n if (rel.startsWith('..') || path.isAbsolute(rel)) {\n throw new Error(`Path \"${absPath}\" is outside project root \"${root}\"`);\n }\n return target;\n}\n\nexport function safeResolve(input: string, ctx: Context): string {\n return ensureInsideRoot(resolvePath(input, ctx), ctx);\n}\n\n/**\n * Defense against in-root→out-of-root symlink escape (CWE-59). `safeResolve`\n * only does a syntactic `../` check, so a symlink that lives *inside* the\n * project root but points outside still passes it. This resolves the path\n * through `fs.realpath` and re-verifies containment against the realpath of\n * the project root (comparing like-for-like, since the root itself may be a\n * symlink — macOS `/var`→`/private/var`, Windows 8.3 short names). For a path\n * that does not exist yet (e.g. a `write` to a new file) the nearest existing\n * ancestor directory is checked instead. Throws if the real target escapes.\n *\n * Mirrors the per-file guard already used in `replace.ts`/`grep.ts`; applied\n * to single-file `read`/`edit`/`write` it throws (rather than skips) because\n * the caller named exactly one file.\n */\nexport async function assertRealInsideRoot(absPath: string, ctx: Context): Promise<void> {\n const realRoot = await fsp.realpath(ctx.projectRoot).catch(() => path.resolve(ctx.projectRoot));\n let probe = absPath;\n for (;;) {\n let real: string;\n try {\n real = await fsp.realpath(probe);\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code === 'ENOENT') {\n const parent = path.dirname(probe);\n if (parent === probe) return; // reached fs root without escaping\n probe = parent;\n continue;\n }\n throw err;\n }\n const rel = path.relative(realRoot, real);\n if (rel.startsWith('..') || path.isAbsolute(rel)) {\n throw new Error(\n `Path \"${absPath}\" resolves through a symlink outside project root \"${realRoot}\"`,\n );\n }\n return;\n }\n}\n\n/** `safeResolve` + symlink realpath containment check. Async. */\nexport async function safeResolveReal(input: string, ctx: Context): Promise<string> {\n const abs = safeResolve(input, ctx);\n await assertRealInsideRoot(abs, ctx);\n return abs;\n}\n\nexport function truncateMiddle(s: string, max: number): string {\n if (Buffer.byteLength(s, 'utf8') <= max) return s;\n const half = Math.floor(max / 2);\n return (\n s.slice(0, half) +\n `\\n…[truncated ${Buffer.byteLength(s, 'utf8') - max} bytes from middle]…\\n` +\n s.slice(-half)\n );\n}\n\nexport function isBinaryBuffer(buf: Buffer): boolean {\n const len = Math.min(buf.length, 8192);\n for (let i = 0; i < len; i++) {\n if (buf[i] === 0) return true;\n }\n return false;\n}\n\n// ─── Command-output normalization (token-saving) ────────────────────────────\n//\n// Raw process output is full of tokens the model gains nothing from: ANSI\n// escapes, carriage-return progress spam, runs of identical warning lines, and\n// huge tails of build noise. These helpers strip that noise before the output\n// reaches the LLM. They are scoped to COMMAND tools (bash/git/exec and the\n// _spawn-stream consumers) — never applied to structured/code outputs.\n\n/** Unified byte cap for all command tool output fed to the model. */\nexport const COMMAND_OUTPUT_MAX_BYTES = 32_768;\n\n/** Runs of >= this many identical consecutive lines are collapsed. */\nconst REPEAT_RUN_THRESHOLD = 3;\n\n/**\n * Collapse carriage-return overwrites the way a terminal would: `\\r\\n` becomes\n * `\\n`, and a bare `\\r` (progress redraw) keeps only the text after the LAST\n * `\\r` on its physical line. Without this, a single progress bar that redraws\n * 200 times explodes into 200 lines.\n */\nexport function collapseCarriageReturns(text: string): string {\n const lf = text.replace(/\\r\\n/g, '\\n');\n if (!lf.includes('\\r')) return lf;\n return lf\n .split('\\n')\n .map((line) => (line.includes('\\r') ? line.slice(line.lastIndexOf('\\r') + 1) : line))\n .join('\\n');\n}\n\n/**\n * Collapse a run of `minRun`+ identical consecutive lines into the line once\n * plus a marker. Consecutive-only — it never reorders or dedups non-adjacent\n * lines, so diffs/source stay intact.\n */\nexport function collapseConsecutiveDuplicates(text: string, minRun = REPEAT_RUN_THRESHOLD): string {\n const lines = text.split('\\n');\n const out: string[] = [];\n let i = 0;\n while (i < lines.length) {\n let j = i + 1;\n while (j < lines.length && lines[j] === lines[i]) j++;\n const run = j - i;\n if (run >= minRun) {\n out.push(lines[i]!, `… ⟨repeated ${run}×⟩`);\n } else {\n for (let k = i; k < j; k++) out.push(lines[k]!);\n }\n i = j;\n }\n return out.join('\\n');\n}\n\n/** Largest prefix of `s` whose UTF-8 byte length is <= `maxBytes`. */\nfunction takeHeadBytes(s: string, maxBytes: number): string {\n if (maxBytes <= 0) return '';\n if (Buffer.byteLength(s, 'utf8') <= maxBytes) return s;\n let lo = 0;\n let hi = s.length;\n while (lo < hi) {\n const mid = Math.ceil((lo + hi) / 2);\n if (Buffer.byteLength(s.slice(0, mid), 'utf8') <= maxBytes) lo = mid;\n else hi = mid - 1;\n }\n return s.slice(0, lo);\n}\n\n/** Largest suffix of `s` whose UTF-8 byte length is <= `maxBytes`. */\nfunction takeTailBytes(s: string, maxBytes: number): string {\n if (maxBytes <= 0) return '';\n if (Buffer.byteLength(s, 'utf8') <= maxBytes) return s;\n let lo = 0;\n let hi = s.length;\n while (lo < hi) {\n const mid = Math.ceil((lo + hi) / 2);\n if (Buffer.byteLength(s.slice(s.length - mid), 'utf8') <= maxBytes) lo = mid;\n else hi = mid - 1;\n }\n return s.slice(s.length - lo);\n}\n\n/**\n * Truncate to `maxBytes` keeping BOTH ends — the head (what ran / early context)\n * and the tail (errors and summaries usually land last), biased ~45/55 toward\n * the tail. The result never exceeds `maxBytes`.\n */\nexport function truncateHeadTail(s: string, maxBytes: number): string {\n const total = Buffer.byteLength(s, 'utf8');\n if (total <= maxBytes) return s;\n // Reserve a fixed allowance for the marker so the final string can't exceed\n // the cap even though the dropped-byte count's digit width varies.\n const MARKER_RESERVE = 64;\n const avail = Math.max(0, maxBytes - MARKER_RESERVE);\n const headBudget = Math.floor(avail * 0.45);\n const head = takeHeadBytes(s, headBudget);\n const tail = takeTailBytes(s, avail - Buffer.byteLength(head, 'utf8'));\n const kept = Buffer.byteLength(head, 'utf8') + Buffer.byteLength(tail, 'utf8');\n return `${head}\\n…[truncated ${total - kept} bytes]…\\n${tail}`;\n}\n\n/**\n * Full token-saving pipeline for command tool output: strip ANSI → collapse\n * carriage-return progress → trim trailing whitespace → collapse identical\n * consecutive lines → squeeze blank-line runs → head+tail truncate to the cap.\n */\nexport function normalizeCommandOutput(\n raw: string,\n opts: { maxBytes?: number } = {},\n): string {\n if (!raw) return raw;\n let text = stripAnsi(raw);\n text = collapseCarriageReturns(text);\n text = text.replace(/[ \\t]+$/gm, ''); // trailing whitespace per line\n text = collapseConsecutiveDuplicates(text);\n text = text.replace(/\\n{3,}/g, '\\n\\n'); // >=2 blank lines → 1\n return truncateHeadTail(text, opts.maxBytes ?? COMMAND_OUTPUT_MAX_BYTES);\n}\n","import { spawn } from 'node:child_process';\nimport { stat } from 'node:fs/promises';\nimport { buildChildEnv } from '@wrongstack/core';\nimport type { Tool } from '@wrongstack/core';\nimport { safeResolve } from './_util.js';\n\ninterface OutdatedInput {\n cwd?: string;\n format?: 'list' | 'table';\n include_deprecated?: boolean;\n check?: string | string[];\n}\n\ninterface OutdatedPackage {\n name: string;\n current: string;\n latest: string;\n wanted: string;\n type: string;\n location: string;\n}\n\ninterface OutdatedOutput {\n exit_code: number;\n packages: OutdatedPackage[];\n total: number;\n output: string;\n truncated: boolean;\n}\n\nexport const outdatedTool: Tool<OutdatedInput, OutdatedOutput> = {\n name: 'outdated',\n category: 'Package Management',\n description:\n 'Check for outdated dependencies in the project. Reports current, wanted (semver range), and latest versions available.',\n usageHint:\n 'MAINTENANCE & SECURITY TOOL:\\n\\n' +\n '- Run periodically or before dependency-related work.\\n' +\n '- Helps surface packages that may need updates for security or features.\\n' +\n '- Safe, read-only operation.\\n' +\n 'Use the output to decide on upgrades. Prefer this over manual shell commands for dependency hygiene.',\n permission: 'auto',\n mutating: true,\n timeoutMs: 60_000,\n inputSchema: {\n type: 'object',\n properties: {\n cwd: { type: 'string', description: 'Working directory (default: cwd)' },\n format: {\n type: 'string',\n enum: ['list', 'table'],\n description: 'Output format (default: list)',\n },\n include_deprecated: {\n type: 'boolean',\n description: 'Include deprecated packages (default: false)',\n },\n check: {\n type: 'string',\n description: 'Specific package(s) to check (comma-separated)',\n },\n },\n },\n async execute(input, ctx, opts) {\n const cwd = input.cwd ? safeResolve(input.cwd, ctx) : ctx.cwd;\n const manager = await detectManager(cwd);\n\n const args: string[] = ['outdated', '--json'];\n if (input.format === 'table') args.push('--table');\n if (input.include_deprecated) args.push('--include', 'deprecated');\n\n return runOutdated(manager, args, cwd, opts.signal);\n },\n};\n\nasync function detectManager(cwd: string): Promise<string> {\n try {\n await stat(`${cwd}/pnpm-lock.yaml`);\n return 'pnpm';\n } catch {\n /* */\n }\n try {\n await stat(`${cwd}/yarn.lock`);\n return 'yarn';\n } catch {\n /* */\n }\n return 'npm';\n}\n\nfunction runOutdated(\n manager: string,\n args: string[],\n cwd: string,\n signal: AbortSignal,\n): Promise<OutdatedOutput> {\n return new Promise((resolve) => {\n let stdout = '';\n let stderr = '';\n const MAX = 100_000;\n\n const child = spawn(manager, args, { cwd, signal, env: buildChildEnv(), stdio: ['ignore', 'pipe', 'pipe'] });\n child.stdout?.on('data', (c) => {\n if (stdout.length < MAX) stdout += c.toString();\n });\n child.stderr?.on('data', (c) => {\n if (stderr.length < MAX) stderr += c.toString();\n });\n child.on('close', (code) => {\n const result = parseOutdatedOutput(stdout, code ?? 0);\n resolve(result);\n });\n child.on('error', (e) => {\n resolve({\n exit_code: 1,\n packages: [],\n total: 0,\n output: e.message,\n truncated: false,\n });\n });\n });\n}\n\nfunction parseOutdatedOutput(json: string, exitCode: number): OutdatedOutput {\n const packages: OutdatedPackage[] = [];\n\n if (!json) {\n return {\n exit_code: exitCode,\n packages: [],\n total: 0,\n output: exitCode === 0 ? 'All packages up to date' : 'Could not check outdated packages',\n truncated: false,\n };\n }\n\n try {\n const data = JSON.parse(json);\n for (const name of Object.keys(data)) {\n const info = data[name];\n packages.push({\n name,\n current: info.current ?? 'unknown',\n latest: info.latest ?? 'unknown',\n wanted: info.wanted ?? 'unknown',\n type: info.type ?? 'unknown',\n location: info.location ?? name,\n });\n }\n } catch {\n // JSON parse failed, return raw output\n }\n\n return {\n exit_code: exitCode,\n packages,\n total: packages.length,\n output: json,\n truncated: json.length >= 100_000,\n };\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/_util.ts","../src/outdated.ts"],"names":["resolve"],"mappings":"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","file":"outdated.js","sourcesContent":["import * as fsp from 'node:fs/promises';\nimport * as path from 'node:path';\nimport { type Context, stripAnsi } from '@wrongstack/core';\n\n/** Detected package manager for a project directory. */\nexport type PackageManager = 'pnpm' | 'yarn' | 'npm';\n\n/**\n * Detect the project's package manager by inspecting lockfiles in `cwd`.\n * Order: pnpm → yarn → npm (default). Missing or unreadable directories fall\n * back to `npm` rather than throwing, so a `safeResolve`-checked cwd that\n * happens to be empty never aborts the tool.\n */\nexport async function detectPackageManager(cwd: string): Promise<PackageManager> {\n const { stat } = await import('node:fs/promises');\n try {\n await stat(`${cwd}/pnpm-lock.yaml`);\n return 'pnpm';\n } catch {\n /* not pnpm */\n }\n try {\n await stat(`${cwd}/yarn.lock`);\n return 'yarn';\n } catch {\n /* not yarn */\n }\n return 'npm';\n}\n\nexport function resolvePath(input: string, ctx: Context): string {\n return path.isAbsolute(input) ? path.normalize(input) : path.resolve(ctx.cwd, input);\n}\n\nexport function ensureInsideRoot(absPath: string, ctx: Context): string {\n const root = path.resolve(ctx.projectRoot);\n const target = path.resolve(absPath);\n const rel = path.relative(root, target);\n if (rel.startsWith('..') || path.isAbsolute(rel)) {\n throw new Error(`Path \"${absPath}\" is outside project root \"${root}\"`);\n }\n return target;\n}\n\nexport function safeResolve(input: string, ctx: Context): string {\n return ensureInsideRoot(resolvePath(input, ctx), ctx);\n}\n\n/**\n * Defense against in-root→out-of-root symlink escape (CWE-59). `safeResolve`\n * only does a syntactic `../` check, so a symlink that lives *inside* the\n * project root but points outside still passes it. This resolves the path\n * through `fs.realpath` and re-verifies containment against the realpath of\n * the project root (comparing like-for-like, since the root itself may be a\n * symlink — macOS `/var`→`/private/var`, Windows 8.3 short names). For a path\n * that does not exist yet (e.g. a `write` to a new file) the nearest existing\n * ancestor directory is checked instead. Throws if the real target escapes.\n *\n * Mirrors the per-file guard already used in `replace.ts`/`grep.ts`; applied\n * to single-file `read`/`edit`/`write` it throws (rather than skips) because\n * the caller named exactly one file.\n */\nexport async function assertRealInsideRoot(absPath: string, ctx: Context): Promise<void> {\n const realRoot = await fsp.realpath(ctx.projectRoot).catch(() => path.resolve(ctx.projectRoot));\n let probe = absPath;\n for (;;) {\n let real: string;\n try {\n real = await fsp.realpath(probe);\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code === 'ENOENT') {\n const parent = path.dirname(probe);\n if (parent === probe) return; // reached fs root without escaping\n probe = parent;\n continue;\n }\n throw err;\n }\n const rel = path.relative(realRoot, real);\n if (rel.startsWith('..') || path.isAbsolute(rel)) {\n throw new Error(\n `Path \"${absPath}\" resolves through a symlink outside project root \"${realRoot}\"`,\n );\n }\n return;\n }\n}\n\n/** `safeResolve` + symlink realpath containment check. Async. */\nexport async function safeResolveReal(input: string, ctx: Context): Promise<string> {\n const abs = safeResolve(input, ctx);\n await assertRealInsideRoot(abs, ctx);\n return abs;\n}\n\nexport function truncateMiddle(s: string, max: number): string {\n if (Buffer.byteLength(s, 'utf8') <= max) return s;\n const half = Math.floor(max / 2);\n return (\n s.slice(0, half) +\n `\\n…[truncated ${Buffer.byteLength(s, 'utf8') - max} bytes from middle]…\\n` +\n s.slice(-half)\n );\n}\n\nexport function isBinaryBuffer(buf: Buffer): boolean {\n const len = Math.min(buf.length, 8192);\n for (let i = 0; i < len; i++) {\n if (buf[i] === 0) return true;\n }\n return false;\n}\n\n// ─── Command-output normalization (token-saving) ────────────────────────────\n//\n// Raw process output is full of tokens the model gains nothing from: ANSI\n// escapes, carriage-return progress spam, runs of identical warning lines, and\n// huge tails of build noise. These helpers strip that noise before the output\n// reaches the LLM. They are scoped to COMMAND tools (bash/git/exec and the\n// _spawn-stream consumers) — never applied to structured/code outputs.\n\n/** Unified byte cap for all command tool output fed to the model. */\nexport const COMMAND_OUTPUT_MAX_BYTES = 32_768;\n\n/** Runs of >= this many identical consecutive lines are collapsed. */\nconst REPEAT_RUN_THRESHOLD = 3;\n\n/**\n * Collapse carriage-return overwrites the way a terminal would: `\\r\\n` becomes\n * `\\n`, and a bare `\\r` (progress redraw) keeps only the text after the LAST\n * `\\r` on its physical line. Without this, a single progress bar that redraws\n * 200 times explodes into 200 lines.\n */\nexport function collapseCarriageReturns(text: string): string {\n const lf = text.replace(/\\r\\n/g, '\\n');\n if (!lf.includes('\\r')) return lf;\n return lf\n .split('\\n')\n .map((line) => (line.includes('\\r') ? line.slice(line.lastIndexOf('\\r') + 1) : line))\n .join('\\n');\n}\n\n/**\n * Collapse a run of `minRun`+ identical consecutive lines into the line once\n * plus a marker. Consecutive-only — it never reorders or dedups non-adjacent\n * lines, so diffs/source stay intact.\n */\nexport function collapseConsecutiveDuplicates(text: string, minRun = REPEAT_RUN_THRESHOLD): string {\n const lines = text.split('\\n');\n const out: string[] = [];\n let i = 0;\n while (i < lines.length) {\n let j = i + 1;\n while (j < lines.length && lines[j] === lines[i]) j++;\n const run = j - i;\n if (run >= minRun) {\n out.push(lines[i]!, `… ⟨repeated ${run}×⟩`);\n } else {\n for (let k = i; k < j; k++) out.push(lines[k]!);\n }\n i = j;\n }\n return out.join('\\n');\n}\n\n/** Largest prefix of `s` whose UTF-8 byte length is <= `maxBytes`. */\nfunction takeHeadBytes(s: string, maxBytes: number): string {\n if (maxBytes <= 0) return '';\n if (Buffer.byteLength(s, 'utf8') <= maxBytes) return s;\n let lo = 0;\n let hi = s.length;\n while (lo < hi) {\n const mid = Math.ceil((lo + hi) / 2);\n if (Buffer.byteLength(s.slice(0, mid), 'utf8') <= maxBytes) lo = mid;\n else hi = mid - 1;\n }\n return s.slice(0, lo);\n}\n\n/** Largest suffix of `s` whose UTF-8 byte length is <= `maxBytes`. */\nfunction takeTailBytes(s: string, maxBytes: number): string {\n if (maxBytes <= 0) return '';\n if (Buffer.byteLength(s, 'utf8') <= maxBytes) return s;\n let lo = 0;\n let hi = s.length;\n while (lo < hi) {\n const mid = Math.ceil((lo + hi) / 2);\n if (Buffer.byteLength(s.slice(s.length - mid), 'utf8') <= maxBytes) lo = mid;\n else hi = mid - 1;\n }\n return s.slice(s.length - lo);\n}\n\n/**\n * Truncate to `maxBytes` keeping BOTH ends — the head (what ran / early context)\n * and the tail (errors and summaries usually land last), biased ~45/55 toward\n * the tail. The result never exceeds `maxBytes`.\n */\nexport function truncateHeadTail(s: string, maxBytes: number): string {\n const total = Buffer.byteLength(s, 'utf8');\n if (total <= maxBytes) return s;\n // Reserve a fixed allowance for the marker so the final string can't exceed\n // the cap even though the dropped-byte count's digit width varies.\n const MARKER_RESERVE = 64;\n const avail = Math.max(0, maxBytes - MARKER_RESERVE);\n const headBudget = Math.floor(avail * 0.45);\n const head = takeHeadBytes(s, headBudget);\n const tail = takeTailBytes(s, avail - Buffer.byteLength(head, 'utf8'));\n const kept = Buffer.byteLength(head, 'utf8') + Buffer.byteLength(tail, 'utf8');\n return `${head}\\n…[truncated ${total - kept} bytes]…\\n${tail}`;\n}\n\n/**\n * Full token-saving pipeline for command tool output: strip ANSI → collapse\n * carriage-return progress → trim trailing whitespace → collapse identical\n * consecutive lines → squeeze blank-line runs → head+tail truncate to the cap.\n */\nexport function normalizeCommandOutput(\n raw: string,\n opts: { maxBytes?: number } = {},\n): string {\n if (!raw) return raw;\n let text = stripAnsi(raw);\n text = collapseCarriageReturns(text);\n text = text.replace(/[ \\t]+$/gm, ''); // trailing whitespace per line\n text = collapseConsecutiveDuplicates(text);\n text = text.replace(/\\n{3,}/g, '\\n\\n'); // >=2 blank lines → 1\n return truncateHeadTail(text, opts.maxBytes ?? COMMAND_OUTPUT_MAX_BYTES);\n}\n","import { spawn } from 'node:child_process';\nimport { buildChildEnv } from '@wrongstack/core';\nimport type { Tool } from '@wrongstack/core';\nimport { detectPackageManager, safeResolve } from './_util.js';\n\ninterface OutdatedInput {\n cwd?: string;\n format?: 'list' | 'table';\n include_deprecated?: boolean;\n check?: string | string[];\n}\n\ninterface OutdatedPackage {\n name: string;\n current: string;\n latest: string;\n wanted: string;\n type: string;\n location: string;\n}\n\ninterface OutdatedOutput {\n exit_code: number;\n packages: OutdatedPackage[];\n total: number;\n output: string;\n truncated: boolean;\n}\n\nexport const outdatedTool: Tool<OutdatedInput, OutdatedOutput> = {\n name: 'outdated',\n category: 'Package Management',\n description:\n 'Check for outdated dependencies in the project. Reports current, wanted (semver range), and latest versions available.',\n usageHint:\n 'MAINTENANCE & SECURITY TOOL:\\n\\n' +\n '- Run periodically or before dependency-related work.\\n' +\n '- Helps surface packages that may need updates for security or features.\\n' +\n '- Safe, read-only operation.\\n' +\n 'Use the output to decide on upgrades. Prefer this over manual shell commands for dependency hygiene.',\n permission: 'auto',\n mutating: false,\n timeoutMs: 60_000,\n inputSchema: {\n type: 'object',\n properties: {\n cwd: { type: 'string', description: 'Working directory (default: cwd)' },\n format: {\n type: 'string',\n enum: ['list', 'table'],\n description: 'Output format (default: list)',\n },\n include_deprecated: {\n type: 'boolean',\n description: 'Include deprecated packages (default: false)',\n },\n check: {\n type: 'string',\n description: 'Specific package(s) to check (comma-separated)',\n },\n },\n },\n async execute(input, ctx, opts) {\n const cwd = input.cwd ? safeResolve(input.cwd, ctx) : ctx.cwd;\n const manager = await detectPackageManager(cwd);\n\n const args: string[] = ['outdated', '--json'];\n if (input.format === 'table') args.push('--table');\n if (input.include_deprecated) args.push('--include', 'deprecated');\n\n return runOutdated(manager, args, cwd, opts.signal);\n },\n};\n\nfunction runOutdated(\n manager: string,\n args: string[],\n cwd: string,\n signal: AbortSignal,\n): Promise<OutdatedOutput> {\n return new Promise((resolve) => {\n let stdout = '';\n let stderr = '';\n const MAX = 100_000;\n\n const child = spawn(manager, args, { cwd, signal, env: buildChildEnv(), stdio: ['ignore', 'pipe', 'pipe'] });\n child.stdout?.on('data', (c) => {\n if (stdout.length < MAX) stdout += c.toString();\n });\n child.stderr?.on('data', (c) => {\n if (stderr.length < MAX) stderr += c.toString();\n });\n child.on('close', (code) => {\n const result = parseOutdatedOutput(stdout, code ?? 0);\n resolve(result);\n });\n child.on('error', (e) => {\n resolve({\n exit_code: 1,\n packages: [],\n total: 0,\n output: e.message,\n truncated: false,\n });\n });\n });\n}\n\nfunction parseOutdatedOutput(json: string, exitCode: number): OutdatedOutput {\n const packages: OutdatedPackage[] = [];\n\n if (!json) {\n return {\n exit_code: exitCode,\n packages: [],\n total: 0,\n output: exitCode === 0 ? 'All packages up to date' : 'Could not check outdated packages',\n truncated: false,\n };\n }\n\n try {\n const data = JSON.parse(json);\n for (const name of Object.keys(data)) {\n const info = data[name];\n packages.push({\n name,\n current: info.current ?? 'unknown',\n latest: info.latest ?? 'unknown',\n wanted: info.wanted ?? 'unknown',\n type: info.type ?? 'unknown',\n location: info.location ?? name,\n });\n }\n } catch {\n // JSON parse failed, return raw output\n }\n\n return {\n exit_code: exitCode,\n packages,\n total: packages.length,\n output: json,\n truncated: json.length >= 100_000,\n };\n}\n"]}
|
package/dist/pack.js
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { spawn, execFileSync, spawnSync } from 'node:child_process';
|
|
2
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
|
-
import { stat } from 'node:fs/promises';
|
|
5
4
|
import * as path from 'node:path';
|
|
6
5
|
import { resolve, sep, dirname } from 'node:path';
|
|
7
6
|
import * as os from 'node:os';
|
|
@@ -97,6 +96,20 @@ async function* spawnStream(opts) {
|
|
|
97
96
|
error
|
|
98
97
|
};
|
|
99
98
|
}
|
|
99
|
+
async function detectPackageManager(cwd) {
|
|
100
|
+
const { stat: stat10 } = await import('node:fs/promises');
|
|
101
|
+
try {
|
|
102
|
+
await stat10(`${cwd}/pnpm-lock.yaml`);
|
|
103
|
+
return "pnpm";
|
|
104
|
+
} catch {
|
|
105
|
+
}
|
|
106
|
+
try {
|
|
107
|
+
await stat10(`${cwd}/yarn.lock`);
|
|
108
|
+
return "yarn";
|
|
109
|
+
} catch {
|
|
110
|
+
}
|
|
111
|
+
return "npm";
|
|
112
|
+
}
|
|
100
113
|
function resolvePath(input, ctx) {
|
|
101
114
|
return path.isAbsolute(input) ? path.normalize(input) : path.resolve(ctx.cwd, input);
|
|
102
115
|
}
|
|
@@ -259,7 +272,7 @@ var auditTool = {
|
|
|
259
272
|
},
|
|
260
273
|
async *executeStream(input, ctx, opts) {
|
|
261
274
|
const cwd = input.cwd ? safeResolve(input.cwd, ctx) : ctx.cwd;
|
|
262
|
-
const manager = await
|
|
275
|
+
const manager = await detectPackageManager(cwd);
|
|
263
276
|
yield { type: "log", text: `Auditing with ${manager}\u2026`, data: { manager } };
|
|
264
277
|
const args = ["audit", "--json"];
|
|
265
278
|
if (input.fix) args.push("--fix");
|
|
@@ -277,20 +290,6 @@ var auditTool = {
|
|
|
277
290
|
yield { type: "final", output: parseAuditOutput(result.stdout, result.exitCode) };
|
|
278
291
|
}
|
|
279
292
|
};
|
|
280
|
-
async function detectManager(cwd) {
|
|
281
|
-
const { stat: stat11 } = await import('node:fs/promises');
|
|
282
|
-
try {
|
|
283
|
-
await stat11(`${cwd}/pnpm-lock.yaml`);
|
|
284
|
-
return "pnpm";
|
|
285
|
-
} catch {
|
|
286
|
-
}
|
|
287
|
-
try {
|
|
288
|
-
await stat11(`${cwd}/yarn.lock`);
|
|
289
|
-
return "yarn";
|
|
290
|
-
} catch {
|
|
291
|
-
}
|
|
292
|
-
return "npm";
|
|
293
|
-
}
|
|
294
293
|
function parseAuditOutput(json, exitCode) {
|
|
295
294
|
if (!json) {
|
|
296
295
|
return {
|
|
@@ -2716,18 +2715,18 @@ async function runIndexer(_ctx, opts) {
|
|
|
2716
2715
|
for (const meta of store.getAllFileMetas()) existingMeta.set(meta.file, meta);
|
|
2717
2716
|
}
|
|
2718
2717
|
for (const file of files) {
|
|
2719
|
-
let
|
|
2718
|
+
let stat10;
|
|
2720
2719
|
try {
|
|
2721
|
-
|
|
2720
|
+
stat10 = await fs11.stat(file);
|
|
2722
2721
|
} catch {
|
|
2723
2722
|
store.deleteFile(file);
|
|
2724
2723
|
continue;
|
|
2725
2724
|
}
|
|
2726
|
-
if (!
|
|
2725
|
+
if (!stat10.isFile()) continue;
|
|
2727
2726
|
const lang = detectLang(file);
|
|
2728
2727
|
if (!lang) continue;
|
|
2729
2728
|
const meta = existingMeta.get(file);
|
|
2730
|
-
if (!force && meta && meta.mtimeMs === Math.floor(
|
|
2729
|
+
if (!force && meta && meta.mtimeMs === Math.floor(stat10.mtimeMs)) {
|
|
2731
2730
|
langStats[lang] = (langStats[lang] ?? 0) + meta.symbolCount;
|
|
2732
2731
|
symbolsIndexed += meta.symbolCount;
|
|
2733
2732
|
filesIndexed++;
|
|
@@ -2753,7 +2752,7 @@ async function runIndexer(_ctx, opts) {
|
|
|
2753
2752
|
store.upsertFile({
|
|
2754
2753
|
file,
|
|
2755
2754
|
lang,
|
|
2756
|
-
mtimeMs: Math.floor(
|
|
2755
|
+
mtimeMs: Math.floor(stat10.mtimeMs),
|
|
2757
2756
|
symbolCount: 0,
|
|
2758
2757
|
lastIndexed: Date.now()
|
|
2759
2758
|
});
|
|
@@ -2779,7 +2778,7 @@ async function runIndexer(_ctx, opts) {
|
|
|
2779
2778
|
store.upsertFile({
|
|
2780
2779
|
file,
|
|
2781
2780
|
lang,
|
|
2782
|
-
mtimeMs: Math.floor(
|
|
2781
|
+
mtimeMs: Math.floor(stat10.mtimeMs),
|
|
2783
2782
|
symbolCount: count,
|
|
2784
2783
|
lastIndexed: Date.now()
|
|
2785
2784
|
});
|
|
@@ -2810,8 +2809,9 @@ var codebaseIndexTool = {
|
|
|
2810
2809
|
category: "Project",
|
|
2811
2810
|
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
2811
|
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.",
|
|
2813
|
-
permission: "
|
|
2812
|
+
permission: "confirm",
|
|
2814
2813
|
mutating: true,
|
|
2814
|
+
capabilities: ["fs.write.outside-project"],
|
|
2815
2815
|
timeoutMs: 12e4,
|
|
2816
2816
|
inputSchema: {
|
|
2817
2817
|
type: "object",
|
|
@@ -3047,8 +3047,8 @@ var codebaseStatsTool = {
|
|
|
3047
3047
|
var diffTool = {
|
|
3048
3048
|
name: "diff",
|
|
3049
3049
|
category: "Filesystem",
|
|
3050
|
-
description: "Show
|
|
3051
|
-
usageHint: 'USE FOR CODE REVIEW AND CHANGE INSPECTION:\n\n- `files` + no `a`/`b` \u2192
|
|
3050
|
+
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.",
|
|
3051
|
+
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).',
|
|
3052
3052
|
permission: "auto",
|
|
3053
3053
|
mutating: false,
|
|
3054
3054
|
capabilities: ["fs.read"],
|
|
@@ -3125,8 +3125,8 @@ function findGitDir(cwd) {
|
|
|
3125
3125
|
let dir = cwd;
|
|
3126
3126
|
for (let i = 0; i < 20; i++) {
|
|
3127
3127
|
try {
|
|
3128
|
-
const
|
|
3129
|
-
if (
|
|
3128
|
+
const stat10 = statSync(path.join(dir, ".git"));
|
|
3129
|
+
if (stat10.isDirectory()) return dir;
|
|
3130
3130
|
} catch {
|
|
3131
3131
|
}
|
|
3132
3132
|
const parent = path.dirname(dir);
|
|
@@ -3156,7 +3156,7 @@ function runGit(args, cwd, signal) {
|
|
|
3156
3156
|
});
|
|
3157
3157
|
}
|
|
3158
3158
|
async function fileDiff(input, ctx, _signal) {
|
|
3159
|
-
input.context
|
|
3159
|
+
void input.context;
|
|
3160
3160
|
const files = input.files ? (Array.isArray(input.files) ? input.files : input.files.split(",")).map((f) => f.trim()).filter(Boolean) : [];
|
|
3161
3161
|
if (files.length === 0) {
|
|
3162
3162
|
return {
|
|
@@ -3169,31 +3169,32 @@ async function fileDiff(input, ctx, _signal) {
|
|
|
3169
3169
|
const results = [];
|
|
3170
3170
|
for (const file of files) {
|
|
3171
3171
|
const absPath = safeResolve(file, ctx);
|
|
3172
|
-
const
|
|
3173
|
-
if (!
|
|
3172
|
+
const stat10 = await fs11.stat(absPath).catch(() => null);
|
|
3173
|
+
if (!stat10?.isFile()) continue;
|
|
3174
3174
|
const content = await fs11.readFile(absPath, "utf8");
|
|
3175
3175
|
const lines = content.split(/\r?\n/);
|
|
3176
|
-
results.push(
|
|
3177
|
-
+++ ${file}
|
|
3178
|
-
${formatUnified(lines)}`);
|
|
3176
|
+
results.push(formatWithLineNumbers(file, lines));
|
|
3179
3177
|
}
|
|
3180
3178
|
return {
|
|
3181
|
-
diff: results.join("\n"),
|
|
3179
|
+
diff: results.join("\n\n"),
|
|
3182
3180
|
files,
|
|
3183
3181
|
truncated: false,
|
|
3184
3182
|
mode: input.mode ?? "unified"
|
|
3185
3183
|
};
|
|
3186
3184
|
}
|
|
3187
|
-
function
|
|
3188
|
-
|
|
3185
|
+
function formatWithLineNumbers(file, lines) {
|
|
3186
|
+
const width = String(lines.length).length;
|
|
3187
|
+
const numbered = lines.map((line, i) => `${String(i + 1).padStart(width)} | ${line}`).join("\n");
|
|
3188
|
+
return `--- ${file} (line-numbered dump, not a unified diff) ---
|
|
3189
|
+
${numbered}`;
|
|
3189
3190
|
}
|
|
3190
3191
|
var documentTool = {
|
|
3191
3192
|
name: "document",
|
|
3192
3193
|
category: "Project",
|
|
3193
|
-
description: "
|
|
3194
|
-
usageHint: "USE FOR IMPROVING CODE DOCUMENTATION:\n\n- Good for adding missing docs to public APIs or complex functions.\n-
|
|
3195
|
-
permission: "
|
|
3196
|
-
mutating:
|
|
3194
|
+
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.",
|
|
3195
|
+
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.",
|
|
3196
|
+
permission: "auto",
|
|
3197
|
+
mutating: false,
|
|
3197
3198
|
timeoutMs: 3e4,
|
|
3198
3199
|
inputSchema: {
|
|
3199
3200
|
type: "object",
|
|
@@ -3268,8 +3269,8 @@ async function resolveFiles(filesInput, cwd) {
|
|
|
3268
3269
|
for (const f of files) {
|
|
3269
3270
|
const absPath = f.trim().startsWith("/") ? f.trim() : `${cwd}/${f.trim()}`;
|
|
3270
3271
|
try {
|
|
3271
|
-
const
|
|
3272
|
-
if (
|
|
3272
|
+
const stat10 = await fs11.stat(absPath);
|
|
3273
|
+
if (stat10.isFile()) resolved.push(absPath);
|
|
3273
3274
|
} catch {
|
|
3274
3275
|
}
|
|
3275
3276
|
}
|
|
@@ -3360,13 +3361,13 @@ var editTool = {
|
|
|
3360
3361
|
if (input.new_string === void 0) throw new Error("edit: new_string is required");
|
|
3361
3362
|
if (input.old_string === "") throw new Error("edit: old_string cannot be empty");
|
|
3362
3363
|
const absPath = await safeResolveReal(input.path, ctx);
|
|
3363
|
-
const
|
|
3364
|
+
const stat10 = await fs11.stat(absPath).catch((err) => {
|
|
3364
3365
|
if (err.code === "ENOENT") {
|
|
3365
3366
|
throw new Error(`edit: file "${input.path}" does not exist. Use \`write\` instead.`);
|
|
3366
3367
|
}
|
|
3367
3368
|
throw err;
|
|
3368
3369
|
});
|
|
3369
|
-
if (!
|
|
3370
|
+
if (!stat10.isFile()) throw new Error(`edit: "${input.path}" is not a regular file`);
|
|
3370
3371
|
if (!ctx.hasRead(absPath)) {
|
|
3371
3372
|
throw new Error(`edit: file "${input.path}" was not read in this session. Read it first.`);
|
|
3372
3373
|
}
|
|
@@ -4062,6 +4063,7 @@ var formatTool = {
|
|
|
4062
4063
|
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.",
|
|
4063
4064
|
permission: "confirm",
|
|
4064
4065
|
mutating: true,
|
|
4066
|
+
capabilities: ["fs.write", "shell.exec"],
|
|
4065
4067
|
timeoutMs: 6e4,
|
|
4066
4068
|
inputSchema: {
|
|
4067
4069
|
type: "object",
|
|
@@ -4139,13 +4141,13 @@ var formatTool = {
|
|
|
4139
4141
|
}
|
|
4140
4142
|
};
|
|
4141
4143
|
async function detectFixer(cwd) {
|
|
4142
|
-
const { stat:
|
|
4144
|
+
const { stat: stat10 } = await import('node:fs/promises');
|
|
4143
4145
|
try {
|
|
4144
|
-
await
|
|
4146
|
+
await stat10(`${cwd}/biome.json`);
|
|
4145
4147
|
return "biome";
|
|
4146
4148
|
} catch {
|
|
4147
4149
|
try {
|
|
4148
|
-
await
|
|
4150
|
+
await stat10(`${cwd}/.prettierrc`);
|
|
4149
4151
|
return "prettier";
|
|
4150
4152
|
} catch {
|
|
4151
4153
|
return "biome";
|
|
@@ -4275,8 +4277,8 @@ function findGitDir2(cwd, projectRoot) {
|
|
|
4275
4277
|
let dir = cwd;
|
|
4276
4278
|
for (let i = 0; i < 20; i++) {
|
|
4277
4279
|
try {
|
|
4278
|
-
const
|
|
4279
|
-
if (
|
|
4280
|
+
const stat10 = statSync(`${dir}/.git`);
|
|
4281
|
+
if (stat10.isDirectory() || stat10.isFile()) return dir;
|
|
4280
4282
|
} catch {
|
|
4281
4283
|
}
|
|
4282
4284
|
if (dir === root) break;
|
|
@@ -4772,8 +4774,8 @@ async function runNative(input, base, mode, limit, signal) {
|
|
|
4772
4774
|
if (globRe && !globRe.test(e.name) && !globRe.test(full)) continue;
|
|
4773
4775
|
if (globRe) globRe.lastIndex = 0;
|
|
4774
4776
|
try {
|
|
4775
|
-
const
|
|
4776
|
-
if (
|
|
4777
|
+
const stat10 = await fs11.stat(full);
|
|
4778
|
+
if (stat10.size > 1e6) continue;
|
|
4777
4779
|
const head = await fs11.readFile(full);
|
|
4778
4780
|
if (isBinaryBuffer(head)) continue;
|
|
4779
4781
|
const text = head.toString("utf8");
|
|
@@ -4918,20 +4920,6 @@ var installTool = {
|
|
|
4918
4920
|
};
|
|
4919
4921
|
}
|
|
4920
4922
|
};
|
|
4921
|
-
async function detectPackageManager(cwd) {
|
|
4922
|
-
const { stat: stat11 } = await import('node:fs/promises');
|
|
4923
|
-
try {
|
|
4924
|
-
await stat11(`${cwd}/pnpm-lock.yaml`);
|
|
4925
|
-
return "pnpm";
|
|
4926
|
-
} catch {
|
|
4927
|
-
try {
|
|
4928
|
-
await stat11(`${cwd}/yarn.lock`);
|
|
4929
|
-
return "yarn";
|
|
4930
|
-
} catch {
|
|
4931
|
-
return "npm";
|
|
4932
|
-
}
|
|
4933
|
-
}
|
|
4934
|
-
}
|
|
4935
4923
|
var jsonTool = {
|
|
4936
4924
|
name: "json",
|
|
4937
4925
|
category: "Data",
|
|
@@ -5132,11 +5120,11 @@ var lintTool = {
|
|
|
5132
5120
|
}
|
|
5133
5121
|
};
|
|
5134
5122
|
async function detectLinter(cwd) {
|
|
5135
|
-
const { stat:
|
|
5123
|
+
const { stat: stat10 } = await import('node:fs/promises');
|
|
5136
5124
|
const checks = ["biome.json", ".eslintrc.json", "tslint.json", ".eslintrc.js", "tsconfig.json"];
|
|
5137
5125
|
for (const f of checks) {
|
|
5138
5126
|
try {
|
|
5139
|
-
await
|
|
5127
|
+
await stat10(`${cwd}/${f}`);
|
|
5140
5128
|
if (f.includes("biome")) return "biome";
|
|
5141
5129
|
if (f.includes("eslint")) return "eslint";
|
|
5142
5130
|
if (f.includes("tslint")) return "tslint";
|
|
@@ -5352,7 +5340,7 @@ var outdatedTool = {
|
|
|
5352
5340
|
description: "Check for outdated dependencies in the project. Reports current, wanted (semver range), and latest versions available.",
|
|
5353
5341
|
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.",
|
|
5354
5342
|
permission: "auto",
|
|
5355
|
-
mutating:
|
|
5343
|
+
mutating: false,
|
|
5356
5344
|
timeoutMs: 6e4,
|
|
5357
5345
|
inputSchema: {
|
|
5358
5346
|
type: "object",
|
|
@@ -5375,26 +5363,13 @@ var outdatedTool = {
|
|
|
5375
5363
|
},
|
|
5376
5364
|
async execute(input, ctx, opts) {
|
|
5377
5365
|
const cwd = input.cwd ? safeResolve(input.cwd, ctx) : ctx.cwd;
|
|
5378
|
-
const manager = await
|
|
5366
|
+
const manager = await detectPackageManager(cwd);
|
|
5379
5367
|
const args = ["outdated", "--json"];
|
|
5380
5368
|
if (input.format === "table") args.push("--table");
|
|
5381
5369
|
if (input.include_deprecated) args.push("--include", "deprecated");
|
|
5382
5370
|
return runOutdated(manager, args, cwd, opts.signal);
|
|
5383
5371
|
}
|
|
5384
5372
|
};
|
|
5385
|
-
async function detectManager2(cwd) {
|
|
5386
|
-
try {
|
|
5387
|
-
await stat(`${cwd}/pnpm-lock.yaml`);
|
|
5388
|
-
return "pnpm";
|
|
5389
|
-
} catch {
|
|
5390
|
-
}
|
|
5391
|
-
try {
|
|
5392
|
-
await stat(`${cwd}/yarn.lock`);
|
|
5393
|
-
return "yarn";
|
|
5394
|
-
} catch {
|
|
5395
|
-
}
|
|
5396
|
-
return "npm";
|
|
5397
|
-
}
|
|
5398
5373
|
function runOutdated(manager, args, cwd, signal) {
|
|
5399
5374
|
return new Promise((resolve7) => {
|
|
5400
5375
|
let stdout = "";
|
|
@@ -5571,15 +5546,26 @@ var planTool = {
|
|
|
5571
5546
|
category: "Session",
|
|
5572
5547
|
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.",
|
|
5573
5548
|
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.',
|
|
5574
|
-
permission: "
|
|
5575
|
-
mutating:
|
|
5549
|
+
permission: "confirm",
|
|
5550
|
+
mutating: true,
|
|
5551
|
+
capabilities: ["fs.write"],
|
|
5576
5552
|
timeoutMs: 2e3,
|
|
5577
5553
|
inputSchema: {
|
|
5578
5554
|
type: "object",
|
|
5579
5555
|
properties: {
|
|
5580
5556
|
action: {
|
|
5581
5557
|
type: "string",
|
|
5582
|
-
enum: [
|
|
5558
|
+
enum: [
|
|
5559
|
+
"show",
|
|
5560
|
+
"add",
|
|
5561
|
+
"start",
|
|
5562
|
+
"done",
|
|
5563
|
+
"remove",
|
|
5564
|
+
"promote",
|
|
5565
|
+
"derive",
|
|
5566
|
+
"template_use",
|
|
5567
|
+
"clear"
|
|
5568
|
+
],
|
|
5583
5569
|
description: "The operation to perform on the plan board."
|
|
5584
5570
|
},
|
|
5585
5571
|
title: {
|
|
@@ -5672,7 +5658,12 @@ var planTool = {
|
|
|
5672
5658
|
plan = derived.plan;
|
|
5673
5659
|
await savePlan(planPath, plan);
|
|
5674
5660
|
ctx.state.replaceTodos(derived.todos);
|
|
5675
|
-
return mkResult(
|
|
5661
|
+
return mkResult(
|
|
5662
|
+
plan,
|
|
5663
|
+
true,
|
|
5664
|
+
`${input.action} ok \u2014 ${derived.todos.length} todo(s) created.`,
|
|
5665
|
+
derived.todos
|
|
5666
|
+
);
|
|
5676
5667
|
}
|
|
5677
5668
|
case "template_use": {
|
|
5678
5669
|
const templateName = input.template?.trim();
|
|
@@ -5687,7 +5678,11 @@ var planTool = {
|
|
|
5687
5678
|
({ plan } = addPlanItem(plan, item.title, item.details));
|
|
5688
5679
|
}
|
|
5689
5680
|
await savePlan(planPath, plan);
|
|
5690
|
-
return mkResult(
|
|
5681
|
+
return mkResult(
|
|
5682
|
+
plan,
|
|
5683
|
+
true,
|
|
5684
|
+
`Applied template "${template.name}" \u2014 ${template.items.length} items added.`
|
|
5685
|
+
);
|
|
5691
5686
|
}
|
|
5692
5687
|
case "clear":
|
|
5693
5688
|
plan = clearPlan(plan);
|
|
@@ -5742,9 +5737,9 @@ var readTool = {
|
|
|
5742
5737
|
async execute(input, ctx) {
|
|
5743
5738
|
if (!input?.path) throw new Error("read: path is required");
|
|
5744
5739
|
const absPath = await safeResolveReal(input.path, ctx);
|
|
5745
|
-
let
|
|
5740
|
+
let stat10;
|
|
5746
5741
|
try {
|
|
5747
|
-
|
|
5742
|
+
stat10 = await fs11.stat(absPath);
|
|
5748
5743
|
} catch (err) {
|
|
5749
5744
|
const code = err.code;
|
|
5750
5745
|
if (code === "ENOENT") throw new Error(`read: file not found "${input.path}"`);
|
|
@@ -5752,9 +5747,9 @@ var readTool = {
|
|
|
5752
5747
|
`read: failed to stat "${input.path}": ${err instanceof Error ? err.message : String(err)}`
|
|
5753
5748
|
);
|
|
5754
5749
|
}
|
|
5755
|
-
if (!
|
|
5756
|
-
if (
|
|
5757
|
-
throw new Error(`read: file too large (${
|
|
5750
|
+
if (!stat10.isFile()) throw new Error(`read: "${input.path}" is not a regular file`);
|
|
5751
|
+
if (stat10.size > MAX_BYTES2) {
|
|
5752
|
+
throw new Error(`read: file too large (${stat10.size} bytes, limit ${MAX_BYTES2})`);
|
|
5758
5753
|
}
|
|
5759
5754
|
const buf = await fs11.readFile(absPath);
|
|
5760
5755
|
if (isBinaryBuffer(buf)) {
|
|
@@ -5766,14 +5761,14 @@ var readTool = {
|
|
|
5766
5761
|
const offset = Math.max(1, input.offset ?? 1);
|
|
5767
5762
|
const limit = Math.max(0, Math.min(input.limit ?? 2e3, 5e3));
|
|
5768
5763
|
if (limit === 0) {
|
|
5769
|
-
ctx.recordRead(absPath,
|
|
5764
|
+
ctx.recordRead(absPath, stat10.mtimeMs);
|
|
5770
5765
|
return { text: "", total_lines: total, encoding: "utf8", truncated: total > 0 };
|
|
5771
5766
|
}
|
|
5772
5767
|
const slice = allLines.slice(offset - 1, offset - 1 + limit);
|
|
5773
5768
|
const truncated = offset - 1 + slice.length < total;
|
|
5774
5769
|
const width = String(offset + slice.length - 1).length;
|
|
5775
5770
|
const numbered = slice.map((line, i) => `${String(offset + i).padStart(width, " ")}\u2192${line}`).join("\n");
|
|
5776
|
-
ctx.recordRead(absPath,
|
|
5771
|
+
ctx.recordRead(absPath, stat10.mtimeMs);
|
|
5777
5772
|
return {
|
|
5778
5773
|
text: numbered,
|
|
5779
5774
|
total_lines: total,
|
|
@@ -5842,8 +5837,8 @@ var replaceTool = {
|
|
|
5842
5837
|
}
|
|
5843
5838
|
const rel = path.relative(realRoot, realPath);
|
|
5844
5839
|
if (rel.startsWith("..") || path.isAbsolute(rel)) continue;
|
|
5845
|
-
const
|
|
5846
|
-
if (!
|
|
5840
|
+
const stat10 = await fs11.stat(realPath).catch(() => null);
|
|
5841
|
+
if (!stat10 || !stat10.isFile()) continue;
|
|
5847
5842
|
let content;
|
|
5848
5843
|
try {
|
|
5849
5844
|
const buf = await fs11.readFile(realPath);
|
|
@@ -5868,7 +5863,7 @@ var replaceTool = {
|
|
|
5868
5863
|
totalReplacements += count;
|
|
5869
5864
|
if (!dryRun) {
|
|
5870
5865
|
const newContent = toStyle(newContentLf, style);
|
|
5871
|
-
await atomicWrite(realPath, newContent, { mode:
|
|
5866
|
+
await atomicWrite(realPath, newContent, { mode: stat10.mode & 511 });
|
|
5872
5867
|
}
|
|
5873
5868
|
const diff = dryRun || matches.length > 0 ? unifiedDiff(content, toStyle(newContentLf, style), {
|
|
5874
5869
|
fromFile: absPath,
|
|
@@ -5898,8 +5893,8 @@ async function resolveFiles2(filesInput, ctx, extraGlob) {
|
|
|
5898
5893
|
const resolved = [];
|
|
5899
5894
|
for (const p of parts) {
|
|
5900
5895
|
const absPath = safeResolve(p, ctx);
|
|
5901
|
-
const
|
|
5902
|
-
if (
|
|
5896
|
+
const stat10 = await fs11.stat(absPath).catch(() => null);
|
|
5897
|
+
if (stat10?.isFile()) {
|
|
5903
5898
|
resolved.push(absPath);
|
|
5904
5899
|
}
|
|
5905
5900
|
}
|
|
@@ -5957,8 +5952,8 @@ async function globNative(pattern, base, extraGlob) {
|
|
|
5957
5952
|
if (DEFAULT_IGNORE4.includes(e.name)) continue;
|
|
5958
5953
|
const full = path.join(dir, e.name);
|
|
5959
5954
|
try {
|
|
5960
|
-
const
|
|
5961
|
-
if (
|
|
5955
|
+
const stat10 = await fs11.lstat(full);
|
|
5956
|
+
if (stat10.isSymbolicLink()) continue;
|
|
5962
5957
|
} catch {
|
|
5963
5958
|
continue;
|
|
5964
5959
|
}
|
|
@@ -6443,11 +6438,11 @@ var testTool = {
|
|
|
6443
6438
|
}
|
|
6444
6439
|
};
|
|
6445
6440
|
async function detectRunner(cwd) {
|
|
6446
|
-
const { stat:
|
|
6441
|
+
const { stat: stat10 } = await import('node:fs/promises');
|
|
6447
6442
|
const candidates = ["vitest.config.ts", "jest.config.js", ".mocharc.json"];
|
|
6448
6443
|
for (const f of candidates) {
|
|
6449
6444
|
try {
|
|
6450
|
-
await
|
|
6445
|
+
await stat10(path.join(cwd, f));
|
|
6451
6446
|
if (f.includes("vitest")) return "vitest";
|
|
6452
6447
|
if (f.includes("jest")) return "jest";
|
|
6453
6448
|
if (f.includes("mocha")) return "mocha";
|
|
@@ -7093,11 +7088,11 @@ var typecheckTool = {
|
|
|
7093
7088
|
}
|
|
7094
7089
|
};
|
|
7095
7090
|
async function findTsConfig(cwd) {
|
|
7096
|
-
const { stat:
|
|
7091
|
+
const { stat: stat10 } = await import('node:fs/promises');
|
|
7097
7092
|
const candidates = ["tsconfig.json", "tsconfig.base.json"];
|
|
7098
7093
|
for (const f of candidates) {
|
|
7099
7094
|
try {
|
|
7100
|
-
const s = await
|
|
7095
|
+
const s = await stat10(path.join(cwd, f));
|
|
7101
7096
|
if (s.isFile()) return path.join(cwd, f);
|
|
7102
7097
|
} catch {
|
|
7103
7098
|
}
|
|
@@ -7134,12 +7129,12 @@ var writeTool = {
|
|
|
7134
7129
|
let existed = false;
|
|
7135
7130
|
let prev = "";
|
|
7136
7131
|
try {
|
|
7137
|
-
const
|
|
7138
|
-
existed =
|
|
7132
|
+
const stat11 = await fs11.stat(absPath);
|
|
7133
|
+
existed = stat11.isFile();
|
|
7139
7134
|
if (existed) {
|
|
7140
7135
|
if (!ctx.hasRead(absPath)) {
|
|
7141
7136
|
prev = await fs11.readFile(absPath, "utf8");
|
|
7142
|
-
ctx.recordRead(absPath,
|
|
7137
|
+
ctx.recordRead(absPath, stat11.mtimeMs);
|
|
7143
7138
|
} else {
|
|
7144
7139
|
prev = await fs11.readFile(absPath, "utf8");
|
|
7145
7140
|
}
|
|
@@ -7152,8 +7147,8 @@ var writeTool = {
|
|
|
7152
7147
|
await atomicWrite(absPath, input.content);
|
|
7153
7148
|
const diff = existed ? unifiedDiff(prev, input.content, { fromFile: input.path, toFile: input.path }) : `+++ ${input.path}
|
|
7154
7149
|
+ (new file, ${input.content.split("\n").length} lines)`;
|
|
7155
|
-
const
|
|
7156
|
-
ctx.recordRead(absPath,
|
|
7150
|
+
const stat10 = await fs11.stat(absPath);
|
|
7151
|
+
ctx.recordRead(absPath, stat10.mtimeMs);
|
|
7157
7152
|
ctx.session.recordFileChange({
|
|
7158
7153
|
path: absPath,
|
|
7159
7154
|
action: existed ? "modified" : "created",
|