@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/audit.js +15 -15
- package/dist/audit.js.map +1 -1
- package/dist/bash.js +3 -2
- package/dist/bash.js.map +1 -1
- package/dist/builtin.js +108 -112
- 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 +3 -2
- package/dist/exec.js.map +1 -1
- package/dist/fetch.js.map +1 -1
- package/dist/format.js +4 -2
- package/dist/format.js.map +1 -1
- package/dist/git.js +3 -2
- 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 +108 -112
- package/dist/index.js.map +1 -1
- package/dist/install.js +17 -16
- package/dist/install.js.map +1 -1
- package/dist/lint.js +3 -2
- 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 +108 -112
- 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 +3 -2
- package/dist/test.js.map +1 -1
- package/dist/tree.js.map +1 -1
- package/dist/typecheck.js +3 -2
- 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":";;;;;AAcA,eAAsB,qBAAqB,GAAA,EAAsC;AAC/E,EAAA,MAAM,EAAE,IAAA,EAAK,GAAI,MAAM,OAAO,kBAAkB,CAAA;AAChD,EAAA,IAAI;AACF,IAAA,MAAM,IAAA,CAAK,CAAA,EAAG,GAAG,CAAA,eAAA,CAAiB,CAAA;AAClC,IAAA,OAAO,MAAA;AAAA,EACT,CAAA,CAAA,MAAQ;AAAA,EAER;AACA,EAAA,IAAI;AACF,IAAA,MAAM,IAAA,CAAK,CAAA,EAAG,GAAG,CAAA,UAAA,CAAY,CAAA;AAC7B,IAAA,OAAO,MAAA;AAAA,EACT,CAAA,CAAA,MAAQ;AAAA,EAER;AACA,EAAA,OAAO,KAAA;AACT;AAEO,SAAS,WAAA,CAAY,OAAe,GAAA,EAAsB;AAC/D,EAAA,OAAY,IAAA,CAAA,UAAA,CAAW,KAAK,CAAA,GAAS,IAAA,CAAA,SAAA,CAAU,KAAK,CAAA,GAAS,IAAA,CAAA,OAAA,CAAQ,GAAA,CAAI,GAAA,EAAK,KAAK,CAAA;AACrF;AAEO,SAAS,gBAAA,CAAiB,SAAiB,GAAA,EAAsB;AACtE,EAAA,MAAM,IAAA,GAAY,IAAA,CAAA,OAAA,CAAQ,GAAA,CAAI,WAAW,CAAA;AACzC,EAAA,MAAM,MAAA,GAAc,aAAQ,OAAO,CAAA;AACnC,EAAA,MAAM,GAAA,GAAW,IAAA,CAAA,QAAA,CAAS,IAAA,EAAM,MAAM,CAAA;AACtC,EAAA,IAAI,IAAI,UAAA,CAAW,IAAI,CAAA,IAAU,IAAA,CAAA,UAAA,CAAW,GAAG,CAAA,EAAG;AAChD,IAAA,MAAM,IAAI,KAAA,CAAM,CAAA,MAAA,EAAS,OAAO,CAAA,2BAAA,EAA8B,IAAI,CAAA,CAAA,CAAG,CAAA;AAAA,EACvE;AACA,EAAA,OAAO,MAAA;AACT;AAEO,SAAS,WAAA,CAAY,OAAe,GAAA,EAAsB;AAC/D,EAAA,OAAO,gBAAA,CAAiB,WAAA,CAAY,KAAA,EAAO,GAAG,GAAG,GAAG,CAAA;AACtD;;;AClBO,IAAM,YAAA,GAAoD;AAAA,EAC/D,IAAA,EAAM,UAAA;AAAA,EACN,QAAA,EAAU,oBAAA;AAAA,EACV,WAAA,EACE,wHAAA;AAAA,EACF,SAAA,EACE,qSAAA;AAAA,EAKF,UAAA,EAAY,MAAA;AAAA,EACZ,QAAA,EAAU,KAAA;AAAA,EACV,SAAA,EAAW,GAAA;AAAA,EACX,WAAA,EAAa;AAAA,IACX,IAAA,EAAM,QAAA;AAAA,IACN,UAAA,EAAY;AAAA,MACV,GAAA,EAAK,EAAE,IAAA,EAAM,QAAA,EAAU,aAAa,kCAAA,EAAmC;AAAA,MACvE,MAAA,EAAQ;AAAA,QACN,IAAA,EAAM,QAAA;AAAA,QACN,IAAA,EAAM,CAAC,MAAA,EAAQ,OAAO,CAAA;AAAA,QACtB,WAAA,EAAa;AAAA,OACf;AAAA,MACA,kBAAA,EAAoB;AAAA,QAClB,IAAA,EAAM,SAAA;AAAA,QACN,WAAA,EAAa;AAAA,OACf;AAAA,MACA,KAAA,EAAO;AAAA,QACL,IAAA,EAAM,QAAA;AAAA,QACN,WAAA,EAAa;AAAA;AACf;AACF,GACF;AAAA,EACA,MAAM,OAAA,CAAQ,KAAA,EAAO,GAAA,EAAK,IAAA,EAAM;AAC9B,IAAA,MAAM,GAAA,GAAM,MAAM,GAAA,GAAM,WAAA,CAAY,MAAM,GAAA,EAAK,GAAG,IAAI,GAAA,CAAI,GAAA;AAC1D,IAAA,MAAM,OAAA,GAAU,MAAM,oBAAA,CAAqB,GAAG,CAAA;AAE9C,IAAA,MAAM,IAAA,GAAiB,CAAC,UAAA,EAAY,QAAQ,CAAA;AAC5C,IAAA,IAAI,KAAA,CAAM,MAAA,KAAW,OAAA,EAAS,IAAA,CAAK,KAAK,SAAS,CAAA;AACjD,IAAA,IAAI,KAAA,CAAM,kBAAA,EAAoB,IAAA,CAAK,IAAA,CAAK,aAAa,YAAY,CAAA;AAEjE,IAAA,OAAO,WAAA,CAAY,OAAA,EAAS,IAAA,EAAM,GAAA,EAAK,KAAK,MAAM,CAAA;AAAA,EACpD;AACF;AAEA,SAAS,WAAA,CACP,OAAA,EACA,IAAA,EACA,GAAA,EACA,MAAA,EACyB;AACzB,EAAA,OAAO,IAAI,OAAA,CAAQ,CAACA,QAAAA,KAAY;AAC9B,IAAA,IAAI,MAAA,GAAS,EAAA;AACb,IAAA,IAAI,MAAA,GAAS,EAAA;AACb,IAAA,MAAM,GAAA,GAAM,GAAA;AAEZ,IAAA,MAAM,QAAQ,KAAA,CAAM,OAAA,EAAS,IAAA,EAAM,EAAE,KAAK,MAAA,EAAQ,GAAA,EAAK,aAAA,EAAc,EAAG,OAAO,CAAC,QAAA,EAAU,MAAA,EAAQ,MAAM,GAAG,CAAA;AAC3G,IAAA,KAAA,CAAM,MAAA,EAAQ,EAAA,CAAG,MAAA,EAAQ,CAAC,CAAA,KAAM;AAC9B,MAAA,IAAI,MAAA,CAAO,MAAA,GAAS,GAAA,EAAK,MAAA,IAAU,EAAE,QAAA,EAAS;AAAA,IAChD,CAAC,CAAA;AACD,IAAA,KAAA,CAAM,MAAA,EAAQ,EAAA,CAAG,MAAA,EAAQ,CAAC,CAAA,KAAM;AAC9B,MAAA,IAAI,MAAA,CAAO,MAAA,GAAS,GAAA,EAAK,MAAA,IAAU,EAAE,QAAA,EAAS;AAAA,IAChD,CAAC,CAAA;AACD,IAAA,KAAA,CAAM,EAAA,CAAG,OAAA,EAAS,CAAC,IAAA,KAAS;AAC1B,MAAA,MAAM,MAAA,GAAS,mBAAA,CAAoB,MAAA,EAAQ,IAAA,IAAQ,CAAC,CAAA;AACpD,MAAAA,SAAQ,MAAM,CAAA;AAAA,IAChB,CAAC,CAAA;AACD,IAAA,KAAA,CAAM,EAAA,CAAG,OAAA,EAAS,CAAC,CAAA,KAAM;AACvB,MAAAA,QAAAA,CAAQ;AAAA,QACN,SAAA,EAAW,CAAA;AAAA,QACX,UAAU,EAAC;AAAA,QACX,KAAA,EAAO,CAAA;AAAA,QACP,QAAQ,CAAA,CAAE,OAAA;AAAA,QACV,SAAA,EAAW;AAAA,OACZ,CAAA;AAAA,IACH,CAAC,CAAA;AAAA,EACH,CAAC,CAAA;AACH;AAEA,SAAS,mBAAA,CAAoB,MAAc,QAAA,EAAkC;AAC3E,EAAA,MAAM,WAA8B,EAAC;AAErC,EAAA,IAAI,CAAC,IAAA,EAAM;AACT,IAAA,OAAO;AAAA,MACL,SAAA,EAAW,QAAA;AAAA,MACX,UAAU,EAAC;AAAA,MACX,KAAA,EAAO,CAAA;AAAA,MACP,MAAA,EAAQ,QAAA,KAAa,CAAA,GAAI,yBAAA,GAA4B,mCAAA;AAAA,MACrD,SAAA,EAAW;AAAA,KACb;AAAA,EACF;AAEA,EAAA,IAAI;AACF,IAAA,MAAM,IAAA,GAAO,IAAA,CAAK,KAAA,CAAM,IAAI,CAAA;AAC5B,IAAA,KAAA,MAAW,IAAA,IAAQ,MAAA,CAAO,IAAA,CAAK,IAAI,CAAA,EAAG;AACpC,MAAA,MAAM,IAAA,GAAO,KAAK,IAAI,CAAA;AACtB,MAAA,QAAA,CAAS,IAAA,CAAK;AAAA,QACZ,IAAA;AAAA,QACA,OAAA,EAAS,KAAK,OAAA,IAAW,SAAA;AAAA,QACzB,MAAA,EAAQ,KAAK,MAAA,IAAU,SAAA;AAAA,QACvB,MAAA,EAAQ,KAAK,MAAA,IAAU,SAAA;AAAA,QACvB,IAAA,EAAM,KAAK,IAAA,IAAQ,SAAA;AAAA,QACnB,QAAA,EAAU,KAAK,QAAA,IAAY;AAAA,OAC5B,CAAA;AAAA,IACH;AAAA,EACF,CAAA,CAAA,MAAQ;AAAA,EAER;AAEA,EAAA,OAAO;AAAA,IACL,SAAA,EAAW,QAAA;AAAA,IACX,QAAA;AAAA,IACA,OAAO,QAAA,CAAS,MAAA;AAAA,IAChB,MAAA,EAAQ,IAAA;AAAA,IACR,SAAA,EAAW,KAAK,MAAA,IAAU;AAAA,GAC5B;AACF","file":"outdated.js","sourcesContent":["import * as fsp from 'node:fs/promises';\nimport * as path from 'node:path';\nimport * as Core from '@wrongstack/core';\nimport type { Context } 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 = Core.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,7 @@
|
|
|
1
1
|
import { spawn, execFileSync, spawnSync } from 'node:child_process';
|
|
2
|
-
import
|
|
2
|
+
import * as Core from '@wrongstack/core';
|
|
3
|
+
import { buildChildEnv, detectNewlineStyle, normalizeToLf, toStyle, atomicWrite, unifiedDiff, compileGlob, loadPlan, emptyPlan, clearPlan, savePlan, getPlanTemplate, addPlanItem, deriveTodosFromPlanItem, removePlanItem, setPlanItemStatus, formatPlan, resolveWstackPaths } from '@wrongstack/core';
|
|
3
4
|
import * as fs11 from 'node:fs/promises';
|
|
4
|
-
import { stat } from 'node:fs/promises';
|
|
5
5
|
import * as path from 'node:path';
|
|
6
6
|
import { resolve, sep, dirname } from 'node:path';
|
|
7
7
|
import * as os from 'node:os';
|
|
@@ -97,6 +97,20 @@ async function* spawnStream(opts) {
|
|
|
97
97
|
error
|
|
98
98
|
};
|
|
99
99
|
}
|
|
100
|
+
async function detectPackageManager(cwd) {
|
|
101
|
+
const { stat: stat10 } = await import('node:fs/promises');
|
|
102
|
+
try {
|
|
103
|
+
await stat10(`${cwd}/pnpm-lock.yaml`);
|
|
104
|
+
return "pnpm";
|
|
105
|
+
} catch {
|
|
106
|
+
}
|
|
107
|
+
try {
|
|
108
|
+
await stat10(`${cwd}/yarn.lock`);
|
|
109
|
+
return "yarn";
|
|
110
|
+
} catch {
|
|
111
|
+
}
|
|
112
|
+
return "npm";
|
|
113
|
+
}
|
|
100
114
|
function resolvePath(input, ctx) {
|
|
101
115
|
return path.isAbsolute(input) ? path.normalize(input) : path.resolve(ctx.cwd, input);
|
|
102
116
|
}
|
|
@@ -219,7 +233,7 @@ ${tail}`;
|
|
|
219
233
|
}
|
|
220
234
|
function normalizeCommandOutput(raw, opts = {}) {
|
|
221
235
|
if (!raw) return raw;
|
|
222
|
-
let text = stripAnsi(raw);
|
|
236
|
+
let text = Core.stripAnsi(raw);
|
|
223
237
|
text = collapseCarriageReturns(text);
|
|
224
238
|
text = text.replace(/[ \t]+$/gm, "");
|
|
225
239
|
text = collapseConsecutiveDuplicates(text);
|
|
@@ -259,7 +273,7 @@ var auditTool = {
|
|
|
259
273
|
},
|
|
260
274
|
async *executeStream(input, ctx, opts) {
|
|
261
275
|
const cwd = input.cwd ? safeResolve(input.cwd, ctx) : ctx.cwd;
|
|
262
|
-
const manager = await
|
|
276
|
+
const manager = await detectPackageManager(cwd);
|
|
263
277
|
yield { type: "log", text: `Auditing with ${manager}\u2026`, data: { manager } };
|
|
264
278
|
const args = ["audit", "--json"];
|
|
265
279
|
if (input.fix) args.push("--fix");
|
|
@@ -277,20 +291,6 @@ var auditTool = {
|
|
|
277
291
|
yield { type: "final", output: parseAuditOutput(result.stdout, result.exitCode) };
|
|
278
292
|
}
|
|
279
293
|
};
|
|
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
294
|
function parseAuditOutput(json, exitCode) {
|
|
295
295
|
if (!json) {
|
|
296
296
|
return {
|
|
@@ -2716,18 +2716,18 @@ async function runIndexer(_ctx, opts) {
|
|
|
2716
2716
|
for (const meta of store.getAllFileMetas()) existingMeta.set(meta.file, meta);
|
|
2717
2717
|
}
|
|
2718
2718
|
for (const file of files) {
|
|
2719
|
-
let
|
|
2719
|
+
let stat10;
|
|
2720
2720
|
try {
|
|
2721
|
-
|
|
2721
|
+
stat10 = await fs11.stat(file);
|
|
2722
2722
|
} catch {
|
|
2723
2723
|
store.deleteFile(file);
|
|
2724
2724
|
continue;
|
|
2725
2725
|
}
|
|
2726
|
-
if (!
|
|
2726
|
+
if (!stat10.isFile()) continue;
|
|
2727
2727
|
const lang = detectLang(file);
|
|
2728
2728
|
if (!lang) continue;
|
|
2729
2729
|
const meta = existingMeta.get(file);
|
|
2730
|
-
if (!force && meta && meta.mtimeMs === Math.floor(
|
|
2730
|
+
if (!force && meta && meta.mtimeMs === Math.floor(stat10.mtimeMs)) {
|
|
2731
2731
|
langStats[lang] = (langStats[lang] ?? 0) + meta.symbolCount;
|
|
2732
2732
|
symbolsIndexed += meta.symbolCount;
|
|
2733
2733
|
filesIndexed++;
|
|
@@ -2753,7 +2753,7 @@ async function runIndexer(_ctx, opts) {
|
|
|
2753
2753
|
store.upsertFile({
|
|
2754
2754
|
file,
|
|
2755
2755
|
lang,
|
|
2756
|
-
mtimeMs: Math.floor(
|
|
2756
|
+
mtimeMs: Math.floor(stat10.mtimeMs),
|
|
2757
2757
|
symbolCount: 0,
|
|
2758
2758
|
lastIndexed: Date.now()
|
|
2759
2759
|
});
|
|
@@ -2779,7 +2779,7 @@ async function runIndexer(_ctx, opts) {
|
|
|
2779
2779
|
store.upsertFile({
|
|
2780
2780
|
file,
|
|
2781
2781
|
lang,
|
|
2782
|
-
mtimeMs: Math.floor(
|
|
2782
|
+
mtimeMs: Math.floor(stat10.mtimeMs),
|
|
2783
2783
|
symbolCount: count,
|
|
2784
2784
|
lastIndexed: Date.now()
|
|
2785
2785
|
});
|
|
@@ -2810,8 +2810,9 @@ var codebaseIndexTool = {
|
|
|
2810
2810
|
category: "Project",
|
|
2811
2811
|
description: "Build or incrementally update the project-wide symbol index. This powers fast codebase search and understanding. By default it only processes files that have changed since the last indexing run.",
|
|
2812
2812
|
usageHint: "IMPORTANT FOR LARGE CODEBASES:\n\n- First run (or after major changes): consider `force: true` for a clean rebuild.\n- Normal usage: call without arguments for fast incremental updates.\n- Use `langs` to restrict to specific languages if you only care about certain parts of the project.\nThis tool is relatively expensive \u2014 do not call it on every turn. Use it when the index is stale or before heavy codebase-search sessions.",
|
|
2813
|
-
permission: "
|
|
2813
|
+
permission: "confirm",
|
|
2814
2814
|
mutating: true,
|
|
2815
|
+
capabilities: ["fs.write.outside-project"],
|
|
2815
2816
|
timeoutMs: 12e4,
|
|
2816
2817
|
inputSchema: {
|
|
2817
2818
|
type: "object",
|
|
@@ -3047,8 +3048,8 @@ var codebaseStatsTool = {
|
|
|
3047
3048
|
var diffTool = {
|
|
3048
3049
|
name: "diff",
|
|
3049
3050
|
category: "Filesystem",
|
|
3050
|
-
description: "Show
|
|
3051
|
-
usageHint: 'USE FOR CODE REVIEW AND CHANGE INSPECTION:\n\n- `files` + no `a`/`b` \u2192
|
|
3051
|
+
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.",
|
|
3052
|
+
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
3053
|
permission: "auto",
|
|
3053
3054
|
mutating: false,
|
|
3054
3055
|
capabilities: ["fs.read"],
|
|
@@ -3125,8 +3126,8 @@ function findGitDir(cwd) {
|
|
|
3125
3126
|
let dir = cwd;
|
|
3126
3127
|
for (let i = 0; i < 20; i++) {
|
|
3127
3128
|
try {
|
|
3128
|
-
const
|
|
3129
|
-
if (
|
|
3129
|
+
const stat10 = statSync(path.join(dir, ".git"));
|
|
3130
|
+
if (stat10.isDirectory()) return dir;
|
|
3130
3131
|
} catch {
|
|
3131
3132
|
}
|
|
3132
3133
|
const parent = path.dirname(dir);
|
|
@@ -3156,7 +3157,7 @@ function runGit(args, cwd, signal) {
|
|
|
3156
3157
|
});
|
|
3157
3158
|
}
|
|
3158
3159
|
async function fileDiff(input, ctx, _signal) {
|
|
3159
|
-
input.context
|
|
3160
|
+
void input.context;
|
|
3160
3161
|
const files = input.files ? (Array.isArray(input.files) ? input.files : input.files.split(",")).map((f) => f.trim()).filter(Boolean) : [];
|
|
3161
3162
|
if (files.length === 0) {
|
|
3162
3163
|
return {
|
|
@@ -3169,31 +3170,32 @@ async function fileDiff(input, ctx, _signal) {
|
|
|
3169
3170
|
const results = [];
|
|
3170
3171
|
for (const file of files) {
|
|
3171
3172
|
const absPath = safeResolve(file, ctx);
|
|
3172
|
-
const
|
|
3173
|
-
if (!
|
|
3173
|
+
const stat10 = await fs11.stat(absPath).catch(() => null);
|
|
3174
|
+
if (!stat10?.isFile()) continue;
|
|
3174
3175
|
const content = await fs11.readFile(absPath, "utf8");
|
|
3175
3176
|
const lines = content.split(/\r?\n/);
|
|
3176
|
-
results.push(
|
|
3177
|
-
+++ ${file}
|
|
3178
|
-
${formatUnified(lines)}`);
|
|
3177
|
+
results.push(formatWithLineNumbers(file, lines));
|
|
3179
3178
|
}
|
|
3180
3179
|
return {
|
|
3181
|
-
diff: results.join("\n"),
|
|
3180
|
+
diff: results.join("\n\n"),
|
|
3182
3181
|
files,
|
|
3183
3182
|
truncated: false,
|
|
3184
3183
|
mode: input.mode ?? "unified"
|
|
3185
3184
|
};
|
|
3186
3185
|
}
|
|
3187
|
-
function
|
|
3188
|
-
|
|
3186
|
+
function formatWithLineNumbers(file, lines) {
|
|
3187
|
+
const width = String(lines.length).length;
|
|
3188
|
+
const numbered = lines.map((line, i) => `${String(i + 1).padStart(width)} | ${line}`).join("\n");
|
|
3189
|
+
return `--- ${file} (line-numbered dump, not a unified diff) ---
|
|
3190
|
+
${numbered}`;
|
|
3189
3191
|
}
|
|
3190
3192
|
var documentTool = {
|
|
3191
3193
|
name: "document",
|
|
3192
3194
|
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:
|
|
3195
|
+
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.",
|
|
3196
|
+
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.",
|
|
3197
|
+
permission: "auto",
|
|
3198
|
+
mutating: false,
|
|
3197
3199
|
timeoutMs: 3e4,
|
|
3198
3200
|
inputSchema: {
|
|
3199
3201
|
type: "object",
|
|
@@ -3268,8 +3270,8 @@ async function resolveFiles(filesInput, cwd) {
|
|
|
3268
3270
|
for (const f of files) {
|
|
3269
3271
|
const absPath = f.trim().startsWith("/") ? f.trim() : `${cwd}/${f.trim()}`;
|
|
3270
3272
|
try {
|
|
3271
|
-
const
|
|
3272
|
-
if (
|
|
3273
|
+
const stat10 = await fs11.stat(absPath);
|
|
3274
|
+
if (stat10.isFile()) resolved.push(absPath);
|
|
3273
3275
|
} catch {
|
|
3274
3276
|
}
|
|
3275
3277
|
}
|
|
@@ -3360,13 +3362,13 @@ var editTool = {
|
|
|
3360
3362
|
if (input.new_string === void 0) throw new Error("edit: new_string is required");
|
|
3361
3363
|
if (input.old_string === "") throw new Error("edit: old_string cannot be empty");
|
|
3362
3364
|
const absPath = await safeResolveReal(input.path, ctx);
|
|
3363
|
-
const
|
|
3365
|
+
const stat10 = await fs11.stat(absPath).catch((err) => {
|
|
3364
3366
|
if (err.code === "ENOENT") {
|
|
3365
3367
|
throw new Error(`edit: file "${input.path}" does not exist. Use \`write\` instead.`);
|
|
3366
3368
|
}
|
|
3367
3369
|
throw err;
|
|
3368
3370
|
});
|
|
3369
|
-
if (!
|
|
3371
|
+
if (!stat10.isFile()) throw new Error(`edit: "${input.path}" is not a regular file`);
|
|
3370
3372
|
if (!ctx.hasRead(absPath)) {
|
|
3371
3373
|
throw new Error(`edit: file "${input.path}" was not read in this session. Read it first.`);
|
|
3372
3374
|
}
|
|
@@ -4062,6 +4064,7 @@ var formatTool = {
|
|
|
4062
4064
|
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
4065
|
permission: "confirm",
|
|
4064
4066
|
mutating: true,
|
|
4067
|
+
capabilities: ["fs.write", "shell.exec"],
|
|
4065
4068
|
timeoutMs: 6e4,
|
|
4066
4069
|
inputSchema: {
|
|
4067
4070
|
type: "object",
|
|
@@ -4139,13 +4142,13 @@ var formatTool = {
|
|
|
4139
4142
|
}
|
|
4140
4143
|
};
|
|
4141
4144
|
async function detectFixer(cwd) {
|
|
4142
|
-
const { stat:
|
|
4145
|
+
const { stat: stat10 } = await import('node:fs/promises');
|
|
4143
4146
|
try {
|
|
4144
|
-
await
|
|
4147
|
+
await stat10(`${cwd}/biome.json`);
|
|
4145
4148
|
return "biome";
|
|
4146
4149
|
} catch {
|
|
4147
4150
|
try {
|
|
4148
|
-
await
|
|
4151
|
+
await stat10(`${cwd}/.prettierrc`);
|
|
4149
4152
|
return "prettier";
|
|
4150
4153
|
} catch {
|
|
4151
4154
|
return "biome";
|
|
@@ -4275,8 +4278,8 @@ function findGitDir2(cwd, projectRoot) {
|
|
|
4275
4278
|
let dir = cwd;
|
|
4276
4279
|
for (let i = 0; i < 20; i++) {
|
|
4277
4280
|
try {
|
|
4278
|
-
const
|
|
4279
|
-
if (
|
|
4281
|
+
const stat10 = statSync(`${dir}/.git`);
|
|
4282
|
+
if (stat10.isDirectory() || stat10.isFile()) return dir;
|
|
4280
4283
|
} catch {
|
|
4281
4284
|
}
|
|
4282
4285
|
if (dir === root) break;
|
|
@@ -4772,8 +4775,8 @@ async function runNative(input, base, mode, limit, signal) {
|
|
|
4772
4775
|
if (globRe && !globRe.test(e.name) && !globRe.test(full)) continue;
|
|
4773
4776
|
if (globRe) globRe.lastIndex = 0;
|
|
4774
4777
|
try {
|
|
4775
|
-
const
|
|
4776
|
-
if (
|
|
4778
|
+
const stat10 = await fs11.stat(full);
|
|
4779
|
+
if (stat10.size > 1e6) continue;
|
|
4777
4780
|
const head = await fs11.readFile(full);
|
|
4778
4781
|
if (isBinaryBuffer(head)) continue;
|
|
4779
4782
|
const text = head.toString("utf8");
|
|
@@ -4918,20 +4921,6 @@ var installTool = {
|
|
|
4918
4921
|
};
|
|
4919
4922
|
}
|
|
4920
4923
|
};
|
|
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
4924
|
var jsonTool = {
|
|
4936
4925
|
name: "json",
|
|
4937
4926
|
category: "Data",
|
|
@@ -5132,11 +5121,11 @@ var lintTool = {
|
|
|
5132
5121
|
}
|
|
5133
5122
|
};
|
|
5134
5123
|
async function detectLinter(cwd) {
|
|
5135
|
-
const { stat:
|
|
5124
|
+
const { stat: stat10 } = await import('node:fs/promises');
|
|
5136
5125
|
const checks = ["biome.json", ".eslintrc.json", "tslint.json", ".eslintrc.js", "tsconfig.json"];
|
|
5137
5126
|
for (const f of checks) {
|
|
5138
5127
|
try {
|
|
5139
|
-
await
|
|
5128
|
+
await stat10(`${cwd}/${f}`);
|
|
5140
5129
|
if (f.includes("biome")) return "biome";
|
|
5141
5130
|
if (f.includes("eslint")) return "eslint";
|
|
5142
5131
|
if (f.includes("tslint")) return "tslint";
|
|
@@ -5352,7 +5341,7 @@ var outdatedTool = {
|
|
|
5352
5341
|
description: "Check for outdated dependencies in the project. Reports current, wanted (semver range), and latest versions available.",
|
|
5353
5342
|
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
5343
|
permission: "auto",
|
|
5355
|
-
mutating:
|
|
5344
|
+
mutating: false,
|
|
5356
5345
|
timeoutMs: 6e4,
|
|
5357
5346
|
inputSchema: {
|
|
5358
5347
|
type: "object",
|
|
@@ -5375,26 +5364,13 @@ var outdatedTool = {
|
|
|
5375
5364
|
},
|
|
5376
5365
|
async execute(input, ctx, opts) {
|
|
5377
5366
|
const cwd = input.cwd ? safeResolve(input.cwd, ctx) : ctx.cwd;
|
|
5378
|
-
const manager = await
|
|
5367
|
+
const manager = await detectPackageManager(cwd);
|
|
5379
5368
|
const args = ["outdated", "--json"];
|
|
5380
5369
|
if (input.format === "table") args.push("--table");
|
|
5381
5370
|
if (input.include_deprecated) args.push("--include", "deprecated");
|
|
5382
5371
|
return runOutdated(manager, args, cwd, opts.signal);
|
|
5383
5372
|
}
|
|
5384
5373
|
};
|
|
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
5374
|
function runOutdated(manager, args, cwd, signal) {
|
|
5399
5375
|
return new Promise((resolve7) => {
|
|
5400
5376
|
let stdout = "";
|
|
@@ -5571,15 +5547,26 @@ var planTool = {
|
|
|
5571
5547
|
category: "Session",
|
|
5572
5548
|
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
5549
|
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:
|
|
5550
|
+
permission: "confirm",
|
|
5551
|
+
mutating: true,
|
|
5552
|
+
capabilities: ["fs.write"],
|
|
5576
5553
|
timeoutMs: 2e3,
|
|
5577
5554
|
inputSchema: {
|
|
5578
5555
|
type: "object",
|
|
5579
5556
|
properties: {
|
|
5580
5557
|
action: {
|
|
5581
5558
|
type: "string",
|
|
5582
|
-
enum: [
|
|
5559
|
+
enum: [
|
|
5560
|
+
"show",
|
|
5561
|
+
"add",
|
|
5562
|
+
"start",
|
|
5563
|
+
"done",
|
|
5564
|
+
"remove",
|
|
5565
|
+
"promote",
|
|
5566
|
+
"derive",
|
|
5567
|
+
"template_use",
|
|
5568
|
+
"clear"
|
|
5569
|
+
],
|
|
5583
5570
|
description: "The operation to perform on the plan board."
|
|
5584
5571
|
},
|
|
5585
5572
|
title: {
|
|
@@ -5672,7 +5659,12 @@ var planTool = {
|
|
|
5672
5659
|
plan = derived.plan;
|
|
5673
5660
|
await savePlan(planPath, plan);
|
|
5674
5661
|
ctx.state.replaceTodos(derived.todos);
|
|
5675
|
-
return mkResult(
|
|
5662
|
+
return mkResult(
|
|
5663
|
+
plan,
|
|
5664
|
+
true,
|
|
5665
|
+
`${input.action} ok \u2014 ${derived.todos.length} todo(s) created.`,
|
|
5666
|
+
derived.todos
|
|
5667
|
+
);
|
|
5676
5668
|
}
|
|
5677
5669
|
case "template_use": {
|
|
5678
5670
|
const templateName = input.template?.trim();
|
|
@@ -5687,7 +5679,11 @@ var planTool = {
|
|
|
5687
5679
|
({ plan } = addPlanItem(plan, item.title, item.details));
|
|
5688
5680
|
}
|
|
5689
5681
|
await savePlan(planPath, plan);
|
|
5690
|
-
return mkResult(
|
|
5682
|
+
return mkResult(
|
|
5683
|
+
plan,
|
|
5684
|
+
true,
|
|
5685
|
+
`Applied template "${template.name}" \u2014 ${template.items.length} items added.`
|
|
5686
|
+
);
|
|
5691
5687
|
}
|
|
5692
5688
|
case "clear":
|
|
5693
5689
|
plan = clearPlan(plan);
|
|
@@ -5742,9 +5738,9 @@ var readTool = {
|
|
|
5742
5738
|
async execute(input, ctx) {
|
|
5743
5739
|
if (!input?.path) throw new Error("read: path is required");
|
|
5744
5740
|
const absPath = await safeResolveReal(input.path, ctx);
|
|
5745
|
-
let
|
|
5741
|
+
let stat10;
|
|
5746
5742
|
try {
|
|
5747
|
-
|
|
5743
|
+
stat10 = await fs11.stat(absPath);
|
|
5748
5744
|
} catch (err) {
|
|
5749
5745
|
const code = err.code;
|
|
5750
5746
|
if (code === "ENOENT") throw new Error(`read: file not found "${input.path}"`);
|
|
@@ -5752,9 +5748,9 @@ var readTool = {
|
|
|
5752
5748
|
`read: failed to stat "${input.path}": ${err instanceof Error ? err.message : String(err)}`
|
|
5753
5749
|
);
|
|
5754
5750
|
}
|
|
5755
|
-
if (!
|
|
5756
|
-
if (
|
|
5757
|
-
throw new Error(`read: file too large (${
|
|
5751
|
+
if (!stat10.isFile()) throw new Error(`read: "${input.path}" is not a regular file`);
|
|
5752
|
+
if (stat10.size > MAX_BYTES2) {
|
|
5753
|
+
throw new Error(`read: file too large (${stat10.size} bytes, limit ${MAX_BYTES2})`);
|
|
5758
5754
|
}
|
|
5759
5755
|
const buf = await fs11.readFile(absPath);
|
|
5760
5756
|
if (isBinaryBuffer(buf)) {
|
|
@@ -5766,14 +5762,14 @@ var readTool = {
|
|
|
5766
5762
|
const offset = Math.max(1, input.offset ?? 1);
|
|
5767
5763
|
const limit = Math.max(0, Math.min(input.limit ?? 2e3, 5e3));
|
|
5768
5764
|
if (limit === 0) {
|
|
5769
|
-
ctx.recordRead(absPath,
|
|
5765
|
+
ctx.recordRead(absPath, stat10.mtimeMs);
|
|
5770
5766
|
return { text: "", total_lines: total, encoding: "utf8", truncated: total > 0 };
|
|
5771
5767
|
}
|
|
5772
5768
|
const slice = allLines.slice(offset - 1, offset - 1 + limit);
|
|
5773
5769
|
const truncated = offset - 1 + slice.length < total;
|
|
5774
5770
|
const width = String(offset + slice.length - 1).length;
|
|
5775
5771
|
const numbered = slice.map((line, i) => `${String(offset + i).padStart(width, " ")}\u2192${line}`).join("\n");
|
|
5776
|
-
ctx.recordRead(absPath,
|
|
5772
|
+
ctx.recordRead(absPath, stat10.mtimeMs);
|
|
5777
5773
|
return {
|
|
5778
5774
|
text: numbered,
|
|
5779
5775
|
total_lines: total,
|
|
@@ -5842,8 +5838,8 @@ var replaceTool = {
|
|
|
5842
5838
|
}
|
|
5843
5839
|
const rel = path.relative(realRoot, realPath);
|
|
5844
5840
|
if (rel.startsWith("..") || path.isAbsolute(rel)) continue;
|
|
5845
|
-
const
|
|
5846
|
-
if (!
|
|
5841
|
+
const stat10 = await fs11.stat(realPath).catch(() => null);
|
|
5842
|
+
if (!stat10 || !stat10.isFile()) continue;
|
|
5847
5843
|
let content;
|
|
5848
5844
|
try {
|
|
5849
5845
|
const buf = await fs11.readFile(realPath);
|
|
@@ -5868,7 +5864,7 @@ var replaceTool = {
|
|
|
5868
5864
|
totalReplacements += count;
|
|
5869
5865
|
if (!dryRun) {
|
|
5870
5866
|
const newContent = toStyle(newContentLf, style);
|
|
5871
|
-
await atomicWrite(realPath, newContent, { mode:
|
|
5867
|
+
await atomicWrite(realPath, newContent, { mode: stat10.mode & 511 });
|
|
5872
5868
|
}
|
|
5873
5869
|
const diff = dryRun || matches.length > 0 ? unifiedDiff(content, toStyle(newContentLf, style), {
|
|
5874
5870
|
fromFile: absPath,
|
|
@@ -5898,8 +5894,8 @@ async function resolveFiles2(filesInput, ctx, extraGlob) {
|
|
|
5898
5894
|
const resolved = [];
|
|
5899
5895
|
for (const p of parts) {
|
|
5900
5896
|
const absPath = safeResolve(p, ctx);
|
|
5901
|
-
const
|
|
5902
|
-
if (
|
|
5897
|
+
const stat10 = await fs11.stat(absPath).catch(() => null);
|
|
5898
|
+
if (stat10?.isFile()) {
|
|
5903
5899
|
resolved.push(absPath);
|
|
5904
5900
|
}
|
|
5905
5901
|
}
|
|
@@ -5957,8 +5953,8 @@ async function globNative(pattern, base, extraGlob) {
|
|
|
5957
5953
|
if (DEFAULT_IGNORE4.includes(e.name)) continue;
|
|
5958
5954
|
const full = path.join(dir, e.name);
|
|
5959
5955
|
try {
|
|
5960
|
-
const
|
|
5961
|
-
if (
|
|
5956
|
+
const stat10 = await fs11.lstat(full);
|
|
5957
|
+
if (stat10.isSymbolicLink()) continue;
|
|
5962
5958
|
} catch {
|
|
5963
5959
|
continue;
|
|
5964
5960
|
}
|
|
@@ -6443,11 +6439,11 @@ var testTool = {
|
|
|
6443
6439
|
}
|
|
6444
6440
|
};
|
|
6445
6441
|
async function detectRunner(cwd) {
|
|
6446
|
-
const { stat:
|
|
6442
|
+
const { stat: stat10 } = await import('node:fs/promises');
|
|
6447
6443
|
const candidates = ["vitest.config.ts", "jest.config.js", ".mocharc.json"];
|
|
6448
6444
|
for (const f of candidates) {
|
|
6449
6445
|
try {
|
|
6450
|
-
await
|
|
6446
|
+
await stat10(path.join(cwd, f));
|
|
6451
6447
|
if (f.includes("vitest")) return "vitest";
|
|
6452
6448
|
if (f.includes("jest")) return "jest";
|
|
6453
6449
|
if (f.includes("mocha")) return "mocha";
|
|
@@ -7093,11 +7089,11 @@ var typecheckTool = {
|
|
|
7093
7089
|
}
|
|
7094
7090
|
};
|
|
7095
7091
|
async function findTsConfig(cwd) {
|
|
7096
|
-
const { stat:
|
|
7092
|
+
const { stat: stat10 } = await import('node:fs/promises');
|
|
7097
7093
|
const candidates = ["tsconfig.json", "tsconfig.base.json"];
|
|
7098
7094
|
for (const f of candidates) {
|
|
7099
7095
|
try {
|
|
7100
|
-
const s = await
|
|
7096
|
+
const s = await stat10(path.join(cwd, f));
|
|
7101
7097
|
if (s.isFile()) return path.join(cwd, f);
|
|
7102
7098
|
} catch {
|
|
7103
7099
|
}
|
|
@@ -7134,12 +7130,12 @@ var writeTool = {
|
|
|
7134
7130
|
let existed = false;
|
|
7135
7131
|
let prev = "";
|
|
7136
7132
|
try {
|
|
7137
|
-
const
|
|
7138
|
-
existed =
|
|
7133
|
+
const stat11 = await fs11.stat(absPath);
|
|
7134
|
+
existed = stat11.isFile();
|
|
7139
7135
|
if (existed) {
|
|
7140
7136
|
if (!ctx.hasRead(absPath)) {
|
|
7141
7137
|
prev = await fs11.readFile(absPath, "utf8");
|
|
7142
|
-
ctx.recordRead(absPath,
|
|
7138
|
+
ctx.recordRead(absPath, stat11.mtimeMs);
|
|
7143
7139
|
} else {
|
|
7144
7140
|
prev = await fs11.readFile(absPath, "utf8");
|
|
7145
7141
|
}
|
|
@@ -7152,8 +7148,8 @@ var writeTool = {
|
|
|
7152
7148
|
await atomicWrite(absPath, input.content);
|
|
7153
7149
|
const diff = existed ? unifiedDiff(prev, input.content, { fromFile: input.path, toFile: input.path }) : `+++ ${input.path}
|
|
7154
7150
|
+ (new file, ${input.content.split("\n").length} lines)`;
|
|
7155
|
-
const
|
|
7156
|
-
ctx.recordRead(absPath,
|
|
7151
|
+
const stat10 = await fs11.stat(absPath);
|
|
7152
|
+
ctx.recordRead(absPath, stat10.mtimeMs);
|
|
7157
7153
|
ctx.session.recordFileChange({
|
|
7158
7154
|
path: absPath,
|
|
7159
7155
|
action: existed ? "modified" : "created",
|