@wrongstack/tools 0.264.0 → 0.267.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (90) hide show
  1. package/dist/audit.js +154 -11
  2. package/dist/audit.js.map +1 -1
  3. package/dist/bash.js +138 -2
  4. package/dist/bash.js.map +1 -1
  5. package/dist/batch-tool-use.js +1 -0
  6. package/dist/batch-tool-use.js.map +1 -1
  7. package/dist/builtin.d.ts +20 -1
  8. package/dist/builtin.js +796 -340
  9. package/dist/builtin.js.map +1 -1
  10. package/dist/circuit-breaker.d.ts +20 -0
  11. package/dist/circuit-breaker.js +40 -2
  12. package/dist/circuit-breaker.js.map +1 -1
  13. package/dist/codebase-index/index.d.ts +16 -0
  14. package/dist/codebase-index/index.js +59 -25
  15. package/dist/codebase-index/index.js.map +1 -1
  16. package/dist/codebase-index/worker.js +56 -25
  17. package/dist/codebase-index/worker.js.map +1 -1
  18. package/dist/diff.js +14 -7
  19. package/dist/diff.js.map +1 -1
  20. package/dist/document.js +14 -8
  21. package/dist/document.js.map +1 -1
  22. package/dist/edit.d.ts +1 -0
  23. package/dist/edit.js +33 -22
  24. package/dist/edit.js.map +1 -1
  25. package/dist/exec.js +140 -3
  26. package/dist/exec.js.map +1 -1
  27. package/dist/fetch.js +1 -0
  28. package/dist/fetch.js.map +1 -1
  29. package/dist/format.js +153 -11
  30. package/dist/format.js.map +1 -1
  31. package/dist/git.d.ts +7 -0
  32. package/dist/git.js +20 -2
  33. package/dist/git.js.map +1 -1
  34. package/dist/glob.js +14 -7
  35. package/dist/glob.js.map +1 -1
  36. package/dist/grep.js +14 -7
  37. package/dist/grep.js.map +1 -1
  38. package/dist/index.d.ts +55 -3
  39. package/dist/index.js +957 -341
  40. package/dist/index.js.map +1 -1
  41. package/dist/install.js +153 -11
  42. package/dist/install.js.map +1 -1
  43. package/dist/json.js +1 -0
  44. package/dist/json.js.map +1 -1
  45. package/dist/lint.js +153 -11
  46. package/dist/lint.js.map +1 -1
  47. package/dist/logs.js +14 -7
  48. package/dist/logs.js.map +1 -1
  49. package/dist/memory.js +1 -0
  50. package/dist/memory.js.map +1 -1
  51. package/dist/mode.js +1 -0
  52. package/dist/mode.js.map +1 -1
  53. package/dist/outdated.js +21 -10
  54. package/dist/outdated.js.map +1 -1
  55. package/dist/pack.js +765 -339
  56. package/dist/pack.js.map +1 -1
  57. package/dist/patch.js +14 -7
  58. package/dist/patch.js.map +1 -1
  59. package/dist/process-registry.d.ts +56 -2
  60. package/dist/process-registry.js +138 -3
  61. package/dist/process-registry.js.map +1 -1
  62. package/dist/read.d.ts +3 -0
  63. package/dist/read.js +124 -22
  64. package/dist/read.js.map +1 -1
  65. package/dist/replace.js +14 -7
  66. package/dist/replace.js.map +1 -1
  67. package/dist/scaffold.js +14 -7
  68. package/dist/scaffold.js.map +1 -1
  69. package/dist/search.js +1 -0
  70. package/dist/search.js.map +1 -1
  71. package/dist/test.js +153 -11
  72. package/dist/test.js.map +1 -1
  73. package/dist/todo.js +1 -0
  74. package/dist/todo.js.map +1 -1
  75. package/dist/tool-help.js +1 -0
  76. package/dist/tool-help.js.map +1 -1
  77. package/dist/tool-icons.d.ts +20 -0
  78. package/dist/tool-icons.js +130 -0
  79. package/dist/tool-icons.js.map +1 -0
  80. package/dist/tool-search.js +1 -0
  81. package/dist/tool-search.js.map +1 -1
  82. package/dist/tool-use.js +1 -0
  83. package/dist/tool-use.js.map +1 -1
  84. package/dist/tree.js +14 -7
  85. package/dist/tree.js.map +1 -1
  86. package/dist/typecheck.js +153 -11
  87. package/dist/typecheck.js.map +1 -1
  88. package/dist/write.js +21 -15
  89. package/dist/write.js.map +1 -1
  90. package/package.json +6 -2
package/dist/logs.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/_regex.ts","../src/_util.ts","../src/logs.ts"],"names":["resolve","path"],"mappings":"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","file":"logs.js","sourcesContent":["/**\n * Compile a user-supplied regex with conservative bounds against ReDoS.\n *\n * Node's regex engine (V8) is backtracking-based and cannot interrupt a\n * synchronous match — a pattern like `(a+)+$` against a sufficiently long\n * line will pin a worker for seconds. The executor's outer `timeoutMs` only\n * fires between async boundaries, so a long regex eval inside a sync loop\n * is uninterruptible.\n *\n * We can't fully prevent ReDoS without an alternative engine (re2-wasm), but\n * we can sharply limit the blast radius:\n *\n * 1. Cap pattern length — practically all legitimate user patterns are\n * under 256 characters. A 4 KB pattern is almost certainly malicious\n * or a copy-paste accident.\n * 2. Reject patterns containing the most obvious super-linear structures.\n * This is a coarse filter (false-positives are likely; we accept that\n * for hostile-input contexts).\n *\n * Callers should additionally bound the *subject* length (e.g. by capping\n * line size before matching).\n */\n\nconst MAX_PATTERN_LEN = 256;\n\n// Heuristics for catastrophic-backtracking constructs. Not exhaustive; bias\n// toward false-positives in tools that accept LLM-generated input.\nconst DANGEROUS_PATTERNS: ReadonlyArray<RegExp> = [\n // (a+)+, (.*)+, etc — nested quantifier on a group with internal quantifier\n /(\\([^)]*[+*][^)]*\\))[+*]/,\n /(\\(\\?:[^)]*[+*][^)]*\\))[+*]/,\n // Adjacent quantifiers: a++ a*+\n /[+*]{2,}/,\n // Quantifier on alternation with length 2+\n /\\([^|)]+\\|[^)]+\\)[+*][+*]/,\n // Greedy quantifier inside lookahead/lookbehind — (?!.*a+)\n /[([][^)\\]]*[+*][^)\\]]*[)\\]][^)]*\\?\\??/,\n];\n\nexport interface CompileResult {\n ok: true;\n regex: RegExp;\n}\n\nexport interface CompileFail {\n ok: false;\n reason: string;\n}\n\nexport function compileUserRegex(pattern: string, flags: string): CompileResult | CompileFail {\n if (typeof pattern !== 'string') {\n return { ok: false, reason: 'pattern must be a string' };\n }\n if (pattern.length === 0) {\n return { ok: false, reason: 'pattern is empty' };\n }\n if (pattern.length > MAX_PATTERN_LEN) {\n return { ok: false, reason: `pattern exceeds ${MAX_PATTERN_LEN} characters` };\n }\n for (const rx of DANGEROUS_PATTERNS) {\n if (rx.test(pattern)) {\n return {\n ok: false,\n reason:\n 'pattern looks vulnerable to catastrophic backtracking — rewrite without nested quantifiers',\n };\n }\n }\n try {\n return { ok: true, regex: new RegExp(pattern, flags) };\n } catch (err) {\n return {\n ok: false,\n reason: err instanceof Error ? err.message : 'invalid regex',\n };\n }\n}\n\n/**\n * Truncate a subject line to a safe length for synchronous regex eval.\n * The cap is conservative; tools that need exact-line matching against very\n * long lines should use ripgrep externally rather than the native walker.\n */\nexport const MAX_SUBJECT_LEN = 64 * 1024;\n\nexport function capSubject(line: string): string {\n return line.length > MAX_SUBJECT_LEN ? line.slice(0, MAX_SUBJECT_LEN) : line;\n}\n","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/** 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.workingDir ?? ctx.cwd, input);\n}\n\nexport function ensureInsideRoot(absPath: string, ctx: Context): string {\n // If allowOutsideProjectRoot is true, skip the project-root restriction.\n if (ctx.allowOutsideProjectRoot) return path.resolve(absPath);\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 // If allowOutsideProjectRoot is true, skip the symlink-escape check.\n if (ctx.allowOutsideProjectRoot) return;\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 /* v8 ignore next -- only caller (truncateHeadTail) passes a budget smaller than s; defensive. */\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 /* v8 ignore next -- only caller (truncateHeadTail) passes a budget smaller than s; defensive. */\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 | undefined } = {},\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';\r\nimport { buildChildEnv } from '@wrongstack/core';\r\nimport type { Tool } from '@wrongstack/core';\r\nimport { compileUserRegex } from './_regex.js';\r\nimport { safeResolve } from './_util.js';\r\n\r\ninterface LogsInput {\r\n service?: string | undefined;\r\n path?: string | undefined;\r\n lines?: number | undefined;\r\n stream?: boolean | undefined;\r\n filter?: string | undefined;\r\n since?: '1h' | '6h' | '24h' | 'all' | undefined;\r\n cwd?: string | undefined;\r\n}\r\n\r\ninterface LogEntry {\r\n timestamp: string;\r\n level: string;\r\n message: string;\r\n source?: string | undefined;\r\n}\r\n\r\ninterface LogsOutput {\r\n source: string;\r\n entries: LogEntry[];\r\n total: number;\r\n truncated: boolean;\r\n stream_mode: boolean;\r\n}\r\n\r\nexport const logsTool: Tool<LogsInput, LogsOutput> = {\r\n name: 'logs',\r\n category: 'Logs',\r\n description:\r\n 'Read or stream logs from files, Docker containers, or systemd services. Useful for debugging running applications.',\r\n usageHint:\r\n 'DEBUGGING TOOL — USE CAREFULLY IN AUTONOMOUS MODE:\\n\\n' +\r\n '- Prefer `path` for local files or `service` for containers/systemd.\\n' +\r\n '- `stream: true` = live tail (can be expensive).\\n' +\r\n '- Always use `filter` (regex) when possible to reduce noise and token usage.\\n' +\r\n '- Long-running streams should be avoided unless the user explicitly wants live logs.',\r\n permission: 'confirm',\r\n mutating: false,\r\n timeoutMs: 30_000,\r\n capabilities: ['shell.restricted'],\r\n inputSchema: {\r\n type: 'object',\r\n properties: {\r\n service: {\r\n type: 'string',\r\n description: 'Service name for Docker or systemd journal',\r\n },\r\n path: {\r\n type: 'string',\r\n description: 'Path to log file (alternative to service)',\r\n },\r\n lines: {\r\n type: 'integer',\r\n description: 'Number of log lines to fetch (default: 100, 0 for all)',\r\n minimum: 0,\r\n maximum: 10000,\r\n },\r\n stream: {\r\n type: 'boolean',\r\n description: 'Stream logs continuously (like tail -f) (default: false)',\r\n },\r\n filter: {\r\n type: 'string',\r\n description: 'Regex pattern to filter log lines',\r\n },\r\n since: {\r\n type: 'string',\r\n enum: ['1h', '6h', '24h', 'all'],\r\n description: 'Only show logs since duration',\r\n },\r\n cwd: { type: 'string', description: 'Working directory (default: cwd)' },\r\n },\r\n },\r\n async execute(input, ctx, opts) {\r\n const cwd = input.cwd ? safeResolve(input.cwd, ctx) : ctx.cwd;\r\n const lines = input.lines ?? 100;\r\n let filterRe: RegExp | null = null;\r\n if (input.filter) {\r\n const compiled = compileUserRegex(input.filter, 'i');\r\n if (!compiled.ok) {\r\n throw new Error(`logs: ${compiled.reason}`);\r\n }\r\n filterRe = compiled.regex;\r\n }\r\n\r\n if (input.service) {\r\n return await dockerLogs(input.service, lines, filterRe, cwd, opts.signal);\r\n }\r\n\r\n if (input.path) {\r\n return await fileLogs(safeResolve(input.path, ctx), lines, filterRe, input.stream ?? false);\r\n }\r\n\r\n return {\r\n source: 'none',\r\n entries: [],\r\n total: 0,\r\n truncated: false,\r\n stream_mode: false,\r\n };\r\n },\r\n};\r\n\r\nasync function dockerLogs(\r\n service: string,\r\n lines: number,\r\n filterRe: RegExp | null,\r\n cwd: string,\r\n signal: AbortSignal,\r\n since?: string | undefined,\r\n): Promise<LogsOutput> {\r\n const args = ['logs'];\r\n if (lines > 0) args.push('--tail', String(lines));\r\n /* v8 ignore start -- execute() does not forward `since` to dockerLogs yet; this block is currently unreachable. */\r\n if (since) {\r\n const sinceMap: Record<string, string> = { '1h': '1h', '6h': '6h', '24h': '24h' };\r\n args.push('--since', sinceMap[since] ?? '1h');\r\n }\r\n /* v8 ignore stop */\r\n // Validate service name to prevent container name injection.\r\n // Docker container names are limited to [a-zA-Z0-9][a-zA-Z0-9._-]+.\r\n if (!/^[a-zA-Z0-9][a-zA-Z0-9._:-]+$/.test(service)) {\r\n return {\r\n source: `docker:${service}`,\r\n entries: [],\r\n total: 0,\r\n truncated: false,\r\n stream_mode: false,\r\n };\r\n }\r\n args.push('--timestamps', service);\r\n\r\n return new Promise((resolve) => {\r\n let stdout = '';\r\n let stderr = '';\r\n const MAX = 200_000;\r\n let settled = false;\r\n\r\n const empty = (): LogsOutput => ({\r\n source: `docker:${service}`,\r\n entries: [],\r\n total: 0,\r\n truncated: false,\r\n stream_mode: false,\r\n });\r\n const finish = (result: LogsOutput) => {\r\n if (settled) return;\r\n settled = true;\r\n clearTimeout(timer);\r\n resolve(result);\r\n };\r\n\r\n const child = spawn('docker', args, { cwd, signal, env: buildChildEnv(), stdio: ['ignore', 'pipe', 'pipe'], windowsHide: true });\r\n\r\n // `docker logs --tail N` reads recent lines and exits — fast when the\r\n // daemon is up. But if the daemon is unreachable (common on CI runners\r\n // with no running Docker), the CLI can hang on the socket connection and\r\n // emit neither `close` nor `error`. Kill it and return empty so the tool\r\n // (and its tests) never hang.\r\n const timer = setTimeout(() => {\r\n child.kill('SIGTERM');\r\n finish(empty());\r\n }, DOCKER_LOGS_TIMEOUT_MS);\r\n\r\n child.stdout?.on('data', (c) => {\r\n if (stdout.length < MAX) stdout += c.toString();\r\n });\r\n child.stderr?.on('data', (c) => {\r\n if (stderr.length < MAX) stderr += c.toString();\r\n });\r\n // When the child is SIGTERM-killed on timeout (or aborted via `signal`),\r\n // its pipes can emit `error` (e.g. EPIPE on Windows). Without a listener\r\n // that surfaces as an unhandled error and can fail the host/test — swallow\r\n // it at debug level; `child.on('error')` and the timeout already drive the result.\r\n child.stdout?.on('error', (e) => {\r\n console.log(JSON.stringify({ level: 'debug', event: 'pipe_error', stream: 'stdout', error: e.message }));\r\n });\r\n child.stderr?.on('error', (e) => {\r\n console.log(JSON.stringify({ level: 'debug', event: 'pipe_error', stream: 'stderr', error: e.message }));\r\n });\r\n child.on('close', () => {\r\n const output = stdout + stderr;\r\n const entries = parseLogLines(output, filterRe);\r\n finish({\r\n source: `docker:${service}`,\r\n entries,\r\n total: entries.length,\r\n truncated: output.length >= MAX,\r\n stream_mode: false,\r\n });\r\n });\r\n child.on('error', () => finish(empty()));\r\n });\r\n}\r\n\r\n/**\r\n * Hard ceiling for a `docker logs` read. The daemon may be unreachable on CI\r\n * (no Docker running), where the CLI hangs on the socket without ever exiting.\r\n */\r\nconst DOCKER_LOGS_TIMEOUT_MS = 3_000;\r\n\r\n// Hard cap on tail-window size — `lines: 0` historically meant \"all\" and\r\n// happily buffered an entire multi-GB log into memory. Cap at 100k lines;\r\n// callers that need more should narrow with `filter`.\r\nconst MAX_TAIL_LINES = 100_000;\r\n\r\nasync function fileLogs(\r\n path: string,\r\n lines: number,\r\n filterRe: RegExp | null,\r\n stream: boolean,\r\n): Promise<LogsOutput> {\r\n const { createInterface } = await import('node:readline');\r\n const { createReadStream } = await import('node:fs');\r\n const entries: LogEntry[] = [];\r\n\r\n // Effective tail window: clamp to MAX_TAIL_LINES; treat 0 / negative as\r\n // \"max window\" rather than \"unlimited\" so a malicious /proc/kcore path\r\n // cannot OOM the worker.\r\n const effLines = lines > 0 ? Math.min(lines, MAX_TAIL_LINES) : MAX_TAIL_LINES;\r\n // Rolling window backed by a fixed-size circular buffer — at most\r\n // `effLines` strings live in memory regardless of file size.\r\n const window: string[] = new Array(effLines);\r\n let writeIdx = 0;\r\n let totalLines = 0;\r\n\r\n const rl = createInterface({\r\n input: createReadStream(path),\r\n crlfDelay: Number.POSITIVE_INFINITY,\r\n });\r\n\r\n for await (const line of rl) {\r\n if (filterRe && !filterRe.test(line)) continue;\r\n window[writeIdx] = line;\r\n writeIdx = (writeIdx + 1) % effLines;\r\n totalLines++;\r\n }\r\n\r\n // Read the window back in arrival order.\r\n const ordered: string[] = [];\r\n const start = totalLines >= effLines ? writeIdx : 0;\r\n const count = Math.min(totalLines, effLines);\r\n for (let i = 0; i < count; i++) {\r\n const v = window[(start + i) % effLines];\r\n if (v !== undefined) ordered.push(v);\r\n }\r\n\r\n for (const line of ordered) {\r\n const parsed = parseLine(line);\r\n if (parsed) entries.push(parsed);\r\n }\r\n\r\n return {\r\n source: path,\r\n entries,\r\n total: entries.length,\r\n truncated: totalLines > effLines,\r\n stream_mode: stream,\r\n };\r\n}\r\n\r\nfunction parseLogLines(output: string, filterRe: RegExp | null): LogEntry[] {\r\n const lines = output.split('\\n').filter(Boolean);\r\n const entries: LogEntry[] = [];\r\n\r\n for (const line of lines) {\r\n if (filterRe && !filterRe.test(line)) continue;\r\n const parsed = parseLine(line);\r\n if (parsed) entries.push(parsed);\r\n }\r\n\r\n return entries;\r\n}\r\n\r\nfunction parseLine(line: string): LogEntry | null {\r\n const tsRe = /^(\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}(?:\\.\\d+)?Z?)\\s+(?:\\[?(\\w+)\\]?)\\s*(.*)/;\r\n const match = tsRe.exec(line);\r\n\r\n if (match) {\r\n return {\r\n timestamp: match[1] ?? '',\r\n level: match[2]?.toLowerCase() ?? 'info',\r\n message: match[3] ?? '',\r\n };\r\n }\r\n\r\n const levelRe = /(ERROR|WARN|INFO|DEBUG|TRACE)\\s+(.*)/i;\r\n const levelMatch = levelRe.exec(line);\r\n\r\n if (levelMatch) {\r\n return {\r\n timestamp: '',\r\n level: levelMatch[1]?.toLowerCase() ?? 'info',\r\n message: levelMatch[2] ?? line,\r\n };\r\n }\r\n\r\n return {\r\n timestamp: '',\r\n level: 'info',\r\n message: line,\r\n };\r\n}\r\n"]}
1
+ {"version":3,"sources":["../src/_regex.ts","../src/_util.ts","../src/logs.ts"],"names":["resolve","path"],"mappings":";;;;;;;;AAuBA,IAAM,eAAA,GAAkB,GAAA;AAIxB,IAAM,kBAAA,GAA4C;AAAA;AAAA,EAEhD,0BAAA;AAAA,EACA,6BAAA;AAAA;AAAA,EAEA,UAAA;AAAA;AAAA,EAEA,2BAAA;AAAA;AAAA,EAEA;AACF,CAAA;AAYO,SAAS,gBAAA,CAAiB,SAAiB,KAAA,EAA4C;AAC5F,EAAA,IAAI,OAAO,YAAY,QAAA,EAAU;AAC/B,IAAA,OAAO,EAAE,EAAA,EAAI,KAAA,EAAO,MAAA,EAAQ,0BAAA,EAA2B;AAAA,EACzD;AACA,EAAA,IAAI,OAAA,CAAQ,WAAW,CAAA,EAAG;AACxB,IAAA,OAAO,EAAE,EAAA,EAAI,KAAA,EAAO,MAAA,EAAQ,kBAAA,EAAmB;AAAA,EACjD;AACA,EAAA,IAAI,OAAA,CAAQ,SAAS,eAAA,EAAiB;AACpC,IAAA,OAAO,EAAE,EAAA,EAAI,KAAA,EAAO,MAAA,EAAQ,CAAA,gBAAA,EAAmB,eAAe,CAAA,WAAA,CAAA,EAAc;AAAA,EAC9E;AACA,EAAA,KAAA,MAAW,MAAM,kBAAA,EAAoB;AACnC,IAAA,IAAI,EAAA,CAAG,IAAA,CAAK,OAAO,CAAA,EAAG;AACpB,MAAA,OAAO;AAAA,QACL,EAAA,EAAI,KAAA;AAAA,QACJ,MAAA,EACE;AAAA,OACJ;AAAA,IACF;AAAA,EACF;AACA,EAAA,IAAI;AACF,IAAA,OAAO,EAAE,IAAI,IAAA,EAAM,KAAA,EAAO,IAAI,MAAA,CAAO,OAAA,EAAS,KAAK,CAAA,EAAE;AAAA,EACvD,SAAS,GAAA,EAAK;AACZ,IAAA,OAAO;AAAA,MACL,EAAA,EAAI,KAAA;AAAA,MACJ,MAAA,EAAQ,GAAA,YAAe,KAAA,GAAQ,GAAA,CAAI,OAAA,GAAU;AAAA,KAC/C;AAAA,EACF;AACF;AC9CO,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,UAAA,IAAc,GAAA,CAAI,GAAA,EAAK,KAAK,CAAA;AACvG;AAOA,SAAS,aAAa,GAAA,EAAwB;AAC5C,EAAA,OAAO,CAAM,aAAQ,GAAA,CAAI,WAAW,GAAQ,IAAA,CAAA,OAAA,CAAa,IAAA,CAAA,gBAAA,EAAkB,CAAC,CAAA;AAC9E;AAGA,SAAS,WAAA,CAAY,QAAgB,KAAA,EAA0B;AAC7D,EAAA,OAAO,KAAA,CAAM,IAAA,CAAK,CAAC,IAAA,KAAS;AAC1B,IAAA,MAAM,GAAA,GAAW,IAAA,CAAA,QAAA,CAAS,IAAA,EAAM,MAAM,CAAA;AACtC,IAAA,OAAO,GAAA,KAAQ,MAAO,CAAC,GAAA,CAAI,WAAW,IAAI,CAAA,IAAK,CAAM,IAAA,CAAA,UAAA,CAAW,GAAG,CAAA;AAAA,EACrE,CAAC,CAAA;AACH;AAEO,SAAS,gBAAA,CAAiB,SAAiB,GAAA,EAAsB;AACtE,EAAA,MAAM,MAAA,GAAc,aAAQ,OAAO,CAAA;AAEnC,EAAA,IAAI,GAAA,CAAI,yBAAyB,OAAO,MAAA;AACxC,EAAA,IAAI,YAAY,MAAA,EAAQ,YAAA,CAAa,GAAG,CAAC,GAAG,OAAO,MAAA;AACnD,EAAA,MAAM,IAAI,MAAM,CAAA,MAAA,EAAS,OAAO,8BAAmC,IAAA,CAAA,OAAA,CAAQ,GAAA,CAAI,WAAW,CAAC,CAAA,CAAA,CAAG,CAAA;AAChG;AAEO,SAAS,WAAA,CAAY,OAAe,GAAA,EAAsB;AAC/D,EAAA,OAAO,gBAAA,CAAiB,WAAA,CAAY,KAAA,EAAO,GAAG,GAAG,GAAG,CAAA;AACtD;;;AC9BO,IAAM,QAAA,GAAwC;AAAA,EACnD,IAAA,EAAM,MAAA;AAAA,EACN,QAAA,EAAU,MAAA;AAAA,EACV,WAAA,EACE,oHAAA;AAAA,EACF,SAAA,EACE,uVAAA;AAAA,EAKF,UAAA,EAAY,SAAA;AAAA,EACZ,QAAA,EAAU,KAAA;AAAA,EACV,SAAA,EAAW,GAAA;AAAA,EACX,YAAA,EAAc,CAAC,kBAAkB,CAAA;AAAA,EACjC,IAAA,EAAM,MAAA;AAAA,EACN,WAAA,EAAa;AAAA,IACX,IAAA,EAAM,QAAA;AAAA,IACN,UAAA,EAAY;AAAA,MACV,OAAA,EAAS;AAAA,QACP,IAAA,EAAM,QAAA;AAAA,QACN,WAAA,EAAa;AAAA,OACf;AAAA,MACA,IAAA,EAAM;AAAA,QACJ,IAAA,EAAM,QAAA;AAAA,QACN,WAAA,EAAa;AAAA,OACf;AAAA,MACA,KAAA,EAAO;AAAA,QACL,IAAA,EAAM,SAAA;AAAA,QACN,WAAA,EAAa,wDAAA;AAAA,QACb,OAAA,EAAS,CAAA;AAAA,QACT,OAAA,EAAS;AAAA,OACX;AAAA,MACA,MAAA,EAAQ;AAAA,QACN,IAAA,EAAM,SAAA;AAAA,QACN,WAAA,EAAa;AAAA,OACf;AAAA,MACA,MAAA,EAAQ;AAAA,QACN,IAAA,EAAM,QAAA;AAAA,QACN,WAAA,EAAa;AAAA,OACf;AAAA,MACA,KAAA,EAAO;AAAA,QACL,IAAA,EAAM,QAAA;AAAA,QACN,IAAA,EAAM,CAAC,IAAA,EAAM,IAAA,EAAM,OAAO,KAAK,CAAA;AAAA,QAC/B,WAAA,EAAa;AAAA,OACf;AAAA,MACA,GAAA,EAAK,EAAE,IAAA,EAAM,QAAA,EAAU,aAAa,kCAAA;AAAmC;AACzE,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,KAAA,GAAQ,MAAM,KAAA,IAAS,GAAA;AAC7B,IAAA,IAAI,QAAA,GAA0B,IAAA;AAC9B,IAAA,IAAI,MAAM,MAAA,EAAQ;AAChB,MAAA,MAAM,QAAA,GAAW,gBAAA,CAAiB,KAAA,CAAM,MAAA,EAAQ,GAAG,CAAA;AACnD,MAAA,IAAI,CAAC,SAAS,EAAA,EAAI;AAChB,QAAA,MAAM,IAAI,KAAA,CAAM,CAAA,MAAA,EAAS,QAAA,CAAS,MAAM,CAAA,CAAE,CAAA;AAAA,MAC5C;AACA,MAAA,QAAA,GAAW,QAAA,CAAS,KAAA;AAAA,IACtB;AAEA,IAAA,IAAI,MAAM,OAAA,EAAS;AACjB,MAAA,OAAO,MAAM,WAAW,KAAA,CAAM,OAAA,EAAS,OAAO,QAAA,EAAU,GAAA,EAAK,KAAK,MAAM,CAAA;AAAA,IAC1E;AAEA,IAAA,IAAI,MAAM,IAAA,EAAM;AACd,MAAA,OAAO,MAAM,QAAA,CAAS,WAAA,CAAY,KAAA,CAAM,IAAA,EAAM,GAAG,CAAA,EAAG,KAAA,EAAO,QAAA,EAAU,KAAA,CAAM,MAAA,IAAU,KAAK,CAAA;AAAA,IAC5F;AAEA,IAAA,OAAO;AAAA,MACL,MAAA,EAAQ,MAAA;AAAA,MACR,SAAS,EAAC;AAAA,MACV,KAAA,EAAO,CAAA;AAAA,MACP,SAAA,EAAW,KAAA;AAAA,MACX,WAAA,EAAa;AAAA,KACf;AAAA,EACF;AACF;AAEA,eAAe,WACb,OAAA,EACA,KAAA,EACA,QAAA,EACA,GAAA,EACA,QACA,KAAA,EACqB;AACrB,EAAA,MAAM,IAAA,GAAO,CAAC,MAAM,CAAA;AACpB,EAAA,IAAI,QAAQ,CAAA,EAAG,IAAA,CAAK,KAAK,QAAA,EAAU,MAAA,CAAO,KAAK,CAAC,CAAA;AAShD,EAAA,IAAI,CAAC,+BAAA,CAAgC,IAAA,CAAK,OAAO,CAAA,EAAG;AAClD,IAAA,OAAO;AAAA,MACL,MAAA,EAAQ,UAAU,OAAO,CAAA,CAAA;AAAA,MACzB,SAAS,EAAC;AAAA,MACV,KAAA,EAAO,CAAA;AAAA,MACP,SAAA,EAAW,KAAA;AAAA,MACX,WAAA,EAAa;AAAA,KACf;AAAA,EACF;AACA,EAAA,IAAA,CAAK,IAAA,CAAK,gBAAgB,OAAO,CAAA;AAEjC,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;AACZ,IAAA,IAAI,OAAA,GAAU,KAAA;AAEd,IAAA,MAAM,QAAQ,OAAmB;AAAA,MAC/B,MAAA,EAAQ,UAAU,OAAO,CAAA,CAAA;AAAA,MACzB,SAAS,EAAC;AAAA,MACV,KAAA,EAAO,CAAA;AAAA,MACP,SAAA,EAAW,KAAA;AAAA,MACX,WAAA,EAAa;AAAA,KACf,CAAA;AACA,IAAA,MAAM,MAAA,GAAS,CAAC,MAAA,KAAuB;AACrC,MAAA,IAAI,OAAA,EAAS;AACb,MAAA,OAAA,GAAU,IAAA;AACV,MAAA,YAAA,CAAa,KAAK,CAAA;AAClB,MAAAA,SAAQ,MAAM,CAAA;AAAA,IAChB,CAAA;AAEA,IAAA,MAAM,QAAQ,KAAA,CAAM,QAAA,EAAU,MAAM,EAAE,GAAA,EAAK,QAAQ,GAAA,EAAK,aAAA,EAAc,EAAG,KAAA,EAAO,CAAC,QAAA,EAAU,MAAA,EAAQ,MAAM,CAAA,EAAG,WAAA,EAAa,MAAM,CAAA;AAO/H,IAAA,MAAM,KAAA,GAAQ,WAAW,MAAM;AAC7B,MAAA,KAAA,CAAM,KAAK,SAAS,CAAA;AACpB,MAAA,MAAA,CAAO,OAAO,CAAA;AAAA,IAChB,GAAG,sBAAsB,CAAA;AAEzB,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;AAKD,IAAA,KAAA,CAAM,MAAA,EAAQ,EAAA,CAAG,OAAA,EAAS,CAAC,CAAA,KAAM;AAC/B,MAAA,OAAA,CAAQ,GAAA,CAAI,IAAA,CAAK,SAAA,CAAU,EAAE,OAAO,OAAA,EAAS,KAAA,EAAO,YAAA,EAAc,MAAA,EAAQ,QAAA,EAAU,KAAA,EAAO,CAAA,CAAE,OAAA,EAAS,CAAC,CAAA;AAAA,IACzG,CAAC,CAAA;AACD,IAAA,KAAA,CAAM,MAAA,EAAQ,EAAA,CAAG,OAAA,EAAS,CAAC,CAAA,KAAM;AAC/B,MAAA,OAAA,CAAQ,GAAA,CAAI,IAAA,CAAK,SAAA,CAAU,EAAE,OAAO,OAAA,EAAS,KAAA,EAAO,YAAA,EAAc,MAAA,EAAQ,QAAA,EAAU,KAAA,EAAO,CAAA,CAAE,OAAA,EAAS,CAAC,CAAA;AAAA,IACzG,CAAC,CAAA;AACD,IAAA,KAAA,CAAM,EAAA,CAAG,SAAS,MAAM;AACtB,MAAA,MAAM,SAAS,MAAA,GAAS,MAAA;AACxB,MAAA,MAAM,OAAA,GAAU,aAAA,CAAc,MAAA,EAAQ,QAAQ,CAAA;AAC9C,MAAA,MAAA,CAAO;AAAA,QACL,MAAA,EAAQ,UAAU,OAAO,CAAA,CAAA;AAAA,QACzB,OAAA;AAAA,QACA,OAAO,OAAA,CAAQ,MAAA;AAAA,QACf,SAAA,EAAW,OAAO,MAAA,IAAU,GAAA;AAAA,QAC5B,WAAA,EAAa;AAAA,OACd,CAAA;AAAA,IACH,CAAC,CAAA;AACD,IAAA,KAAA,CAAM,GAAG,OAAA,EAAS,MAAM,MAAA,CAAO,KAAA,EAAO,CAAC,CAAA;AAAA,EACzC,CAAC,CAAA;AACH;AAMA,IAAM,sBAAA,GAAyB,GAAA;AAK/B,IAAM,cAAA,GAAiB,GAAA;AAEvB,eAAe,QAAA,CACbC,KAAAA,EACA,KAAA,EACA,QAAA,EACA,MAAA,EACqB;AACrB,EAAA,MAAM,EAAE,eAAA,EAAgB,GAAI,MAAM,OAAO,eAAe,CAAA;AACxD,EAAA,MAAM,EAAE,gBAAA,EAAiB,GAAI,MAAM,OAAO,SAAS,CAAA;AACnD,EAAA,MAAM,UAAsB,EAAC;AAK7B,EAAA,MAAM,WAAW,KAAA,GAAQ,CAAA,GAAI,KAAK,GAAA,CAAI,KAAA,EAAO,cAAc,CAAA,GAAI,cAAA;AAG/D,EAAA,MAAM,MAAA,GAAmB,IAAI,KAAA,CAAM,QAAQ,CAAA;AAC3C,EAAA,IAAI,QAAA,GAAW,CAAA;AACf,EAAA,IAAI,UAAA,GAAa,CAAA;AAEjB,EAAA,MAAM,KAAK,eAAA,CAAgB;AAAA,IACzB,KAAA,EAAO,iBAAiBA,KAAI,CAAA;AAAA,IAC5B,WAAW,MAAA,CAAO;AAAA,GACnB,CAAA;AAED,EAAA,WAAA,MAAiB,QAAQ,EAAA,EAAI;AAC3B,IAAA,IAAI,QAAA,IAAY,CAAC,QAAA,CAAS,IAAA,CAAK,IAAI,CAAA,EAAG;AACtC,IAAA,MAAA,CAAO,QAAQ,CAAA,GAAI,IAAA;AACnB,IAAA,QAAA,GAAA,CAAY,WAAW,CAAA,IAAK,QAAA;AAC5B,IAAA,UAAA,EAAA;AAAA,EACF;AAGA,EAAA,MAAM,UAAoB,EAAC;AAC3B,EAAA,MAAM,KAAA,GAAQ,UAAA,IAAc,QAAA,GAAW,QAAA,GAAW,CAAA;AAClD,EAAA,MAAM,KAAA,GAAQ,IAAA,CAAK,GAAA,CAAI,UAAA,EAAY,QAAQ,CAAA;AAC3C,EAAA,KAAA,IAAS,CAAA,GAAI,CAAA,EAAG,CAAA,GAAI,KAAA,EAAO,CAAA,EAAA,EAAK;AAC9B,IAAA,MAAM,CAAA,GAAI,MAAA,CAAA,CAAQ,KAAA,GAAQ,CAAA,IAAK,QAAQ,CAAA;AACvC,IAAA,IAAI,CAAA,KAAM,MAAA,EAAW,OAAA,CAAQ,IAAA,CAAK,CAAC,CAAA;AAAA,EACrC;AAEA,EAAA,KAAA,MAAW,QAAQ,OAAA,EAAS;AAC1B,IAAA,MAAM,MAAA,GAAS,UAAU,IAAI,CAAA;AAC7B,IAAA,IAAI,MAAA,EAAQ,OAAA,CAAQ,IAAA,CAAK,MAAM,CAAA;AAAA,EACjC;AAEA,EAAA,OAAO;AAAA,IACL,MAAA,EAAQA,KAAAA;AAAA,IACR,OAAA;AAAA,IACA,OAAO,OAAA,CAAQ,MAAA;AAAA,IACf,WAAW,UAAA,GAAa,QAAA;AAAA,IACxB,WAAA,EAAa;AAAA,GACf;AACF;AAEA,SAAS,aAAA,CAAc,QAAgB,QAAA,EAAqC;AAC1E,EAAA,MAAM,QAAQ,MAAA,CAAO,KAAA,CAAM,IAAI,CAAA,CAAE,OAAO,OAAO,CAAA;AAC/C,EAAA,MAAM,UAAsB,EAAC;AAE7B,EAAA,KAAA,MAAW,QAAQ,KAAA,EAAO;AACxB,IAAA,IAAI,QAAA,IAAY,CAAC,QAAA,CAAS,IAAA,CAAK,IAAI,CAAA,EAAG;AACtC,IAAA,MAAM,MAAA,GAAS,UAAU,IAAI,CAAA;AAC7B,IAAA,IAAI,MAAA,EAAQ,OAAA,CAAQ,IAAA,CAAK,MAAM,CAAA;AAAA,EACjC;AAEA,EAAA,OAAO,OAAA;AACT;AAEA,SAAS,UAAU,IAAA,EAA+B;AAChD,EAAA,MAAM,IAAA,GAAO,6EAAA;AACb,EAAA,MAAM,KAAA,GAAQ,IAAA,CAAK,IAAA,CAAK,IAAI,CAAA;AAE5B,EAAA,IAAI,KAAA,EAAO;AACT,IAAA,OAAO;AAAA,MACL,SAAA,EAAW,KAAA,CAAM,CAAC,CAAA,IAAK,EAAA;AAAA,MACvB,KAAA,EAAO,KAAA,CAAM,CAAC,CAAA,EAAG,aAAY,IAAK,MAAA;AAAA,MAClC,OAAA,EAAS,KAAA,CAAM,CAAC,CAAA,IAAK;AAAA,KACvB;AAAA,EACF;AAEA,EAAA,MAAM,OAAA,GAAU,uCAAA;AAChB,EAAA,MAAM,UAAA,GAAa,OAAA,CAAQ,IAAA,CAAK,IAAI,CAAA;AAEpC,EAAA,IAAI,UAAA,EAAY;AACd,IAAA,OAAO;AAAA,MACL,SAAA,EAAW,EAAA;AAAA,MACX,KAAA,EAAO,UAAA,CAAW,CAAC,CAAA,EAAG,aAAY,IAAK,MAAA;AAAA,MACvC,OAAA,EAAS,UAAA,CAAW,CAAC,CAAA,IAAK;AAAA,KAC5B;AAAA,EACF;AAEA,EAAA,OAAO;AAAA,IACL,SAAA,EAAW,EAAA;AAAA,IACX,KAAA,EAAO,MAAA;AAAA,IACP,OAAA,EAAS;AAAA,GACX;AACF","file":"logs.js","sourcesContent":["/**\n * Compile a user-supplied regex with conservative bounds against ReDoS.\n *\n * Node's regex engine (V8) is backtracking-based and cannot interrupt a\n * synchronous match — a pattern like `(a+)+$` against a sufficiently long\n * line will pin a worker for seconds. The executor's outer `timeoutMs` only\n * fires between async boundaries, so a long regex eval inside a sync loop\n * is uninterruptible.\n *\n * We can't fully prevent ReDoS without an alternative engine (re2-wasm), but\n * we can sharply limit the blast radius:\n *\n * 1. Cap pattern length — practically all legitimate user patterns are\n * under 256 characters. A 4 KB pattern is almost certainly malicious\n * or a copy-paste accident.\n * 2. Reject patterns containing the most obvious super-linear structures.\n * This is a coarse filter (false-positives are likely; we accept that\n * for hostile-input contexts).\n *\n * Callers should additionally bound the *subject* length (e.g. by capping\n * line size before matching).\n */\n\nconst MAX_PATTERN_LEN = 256;\n\n// Heuristics for catastrophic-backtracking constructs. Not exhaustive; bias\n// toward false-positives in tools that accept LLM-generated input.\nconst DANGEROUS_PATTERNS: ReadonlyArray<RegExp> = [\n // (a+)+, (.*)+, etc — nested quantifier on a group with internal quantifier\n /(\\([^)]*[+*][^)]*\\))[+*]/,\n /(\\(\\?:[^)]*[+*][^)]*\\))[+*]/,\n // Adjacent quantifiers: a++ a*+\n /[+*]{2,}/,\n // Quantifier on alternation with length 2+\n /\\([^|)]+\\|[^)]+\\)[+*][+*]/,\n // Greedy quantifier inside lookahead/lookbehind — (?!.*a+)\n /[([][^)\\]]*[+*][^)\\]]*[)\\]][^)]*\\?\\??/,\n];\n\nexport interface CompileResult {\n ok: true;\n regex: RegExp;\n}\n\nexport interface CompileFail {\n ok: false;\n reason: string;\n}\n\nexport function compileUserRegex(pattern: string, flags: string): CompileResult | CompileFail {\n if (typeof pattern !== 'string') {\n return { ok: false, reason: 'pattern must be a string' };\n }\n if (pattern.length === 0) {\n return { ok: false, reason: 'pattern is empty' };\n }\n if (pattern.length > MAX_PATTERN_LEN) {\n return { ok: false, reason: `pattern exceeds ${MAX_PATTERN_LEN} characters` };\n }\n for (const rx of DANGEROUS_PATTERNS) {\n if (rx.test(pattern)) {\n return {\n ok: false,\n reason:\n 'pattern looks vulnerable to catastrophic backtracking — rewrite without nested quantifiers',\n };\n }\n }\n try {\n return { ok: true, regex: new RegExp(pattern, flags) };\n } catch (err) {\n return {\n ok: false,\n reason: err instanceof Error ? err.message : 'invalid regex',\n };\n }\n}\n\n/**\n * Truncate a subject line to a safe length for synchronous regex eval.\n * The cap is conservative; tools that need exact-line matching against very\n * long lines should use ripgrep externally rather than the native walker.\n */\nexport const MAX_SUBJECT_LEN = 64 * 1024;\n\nexport function capSubject(line: string): string {\n return line.length > MAX_SUBJECT_LEN ? line.slice(0, MAX_SUBJECT_LEN) : line;\n}\n","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/** 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.workingDir ?? ctx.cwd, input);\n}\n\n/**\n * Roots every file tool may always reach, even in restricted mode: the\n * project root and the user-global `~/.wrongstack` directory (config, memory,\n * sessions, skills). `~/.wrongstack` honors the `WRONGSTACK_HOME` override.\n */\nfunction allowedRoots(ctx: Context): string[] {\n return [path.resolve(ctx.projectRoot), path.resolve(Core.wstackGlobalRoot())];\n}\n\n/** True if `target` is `root` itself or nested inside any of `roots`. */\nfunction isInsideAny(target: string, roots: string[]): boolean {\n return roots.some((root) => {\n const rel = path.relative(root, target);\n return rel === '' || (!rel.startsWith('..') && !path.isAbsolute(rel));\n });\n}\n\nexport function ensureInsideRoot(absPath: string, ctx: Context): string {\n const target = path.resolve(absPath);\n // Unrestricted filesystem access: skip the project-root containment check.\n if (ctx.allowOutsideProjectRoot) return target;\n if (isInsideAny(target, allowedRoots(ctx))) return target;\n throw new Error(`Path \"${absPath}\" is outside project root \"${path.resolve(ctx.projectRoot)}\"`);\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 // Unrestricted filesystem access: no symlink-escape check to perform.\n if (ctx.allowOutsideProjectRoot) return;\n // Compare like-for-like against the realpath of each always-allowed root\n // (project root + ~/.wrongstack), since a root may itself be a symlink.\n const realRoots = await Promise.all(\n allowedRoots(ctx).map((r) => fsp.realpath(r).catch(() => path.resolve(r))),\n );\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 if (isInsideAny(real, realRoots)) return;\n throw new Error(\n `Path \"${absPath}\" resolves through a symlink outside project root \"${realRoots[0]}\"`,\n );\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 /* v8 ignore next -- only caller (truncateHeadTail) passes a budget smaller than s; defensive. */\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 /* v8 ignore next -- only caller (truncateHeadTail) passes a budget smaller than s; defensive. */\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 | undefined } = {},\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';\r\nimport { buildChildEnv } from '@wrongstack/core';\r\nimport type { Tool } from '@wrongstack/core';\r\nimport { compileUserRegex } from './_regex.js';\r\nimport { safeResolve } from './_util.js';\r\n\r\ninterface LogsInput {\r\n service?: string | undefined;\r\n path?: string | undefined;\r\n lines?: number | undefined;\r\n stream?: boolean | undefined;\r\n filter?: string | undefined;\r\n since?: '1h' | '6h' | '24h' | 'all' | undefined;\r\n cwd?: string | undefined;\r\n}\r\n\r\ninterface LogEntry {\r\n timestamp: string;\r\n level: string;\r\n message: string;\r\n source?: string | undefined;\r\n}\r\n\r\ninterface LogsOutput {\r\n source: string;\r\n entries: LogEntry[];\r\n total: number;\r\n truncated: boolean;\r\n stream_mode: boolean;\r\n}\r\n\r\nexport const logsTool: Tool<LogsInput, LogsOutput> = {\r\n name: 'logs',\r\n category: 'Logs',\r\n description:\r\n 'Read or stream logs from files, Docker containers, or systemd services. Useful for debugging running applications.',\r\n usageHint:\r\n 'DEBUGGING TOOL — USE CAREFULLY IN AUTONOMOUS MODE:\\n\\n' +\r\n '- Prefer `path` for local files or `service` for containers/systemd.\\n' +\r\n '- `stream: true` = live tail (can be expensive).\\n' +\r\n '- Always use `filter` (regex) when possible to reduce noise and token usage.\\n' +\r\n '- Long-running streams should be avoided unless the user explicitly wants live logs.',\r\n permission: 'confirm',\r\n mutating: false,\r\n timeoutMs: 30_000,\r\n capabilities: ['shell.restricted'],\r\n icon: 'logs',\r\n inputSchema: {\r\n type: 'object',\r\n properties: {\r\n service: {\r\n type: 'string',\r\n description: 'Service name for Docker or systemd journal',\r\n },\r\n path: {\r\n type: 'string',\r\n description: 'Path to log file (alternative to service)',\r\n },\r\n lines: {\r\n type: 'integer',\r\n description: 'Number of log lines to fetch (default: 100, 0 for all)',\r\n minimum: 0,\r\n maximum: 10000,\r\n },\r\n stream: {\r\n type: 'boolean',\r\n description: 'Stream logs continuously (like tail -f) (default: false)',\r\n },\r\n filter: {\r\n type: 'string',\r\n description: 'Regex pattern to filter log lines',\r\n },\r\n since: {\r\n type: 'string',\r\n enum: ['1h', '6h', '24h', 'all'],\r\n description: 'Only show logs since duration',\r\n },\r\n cwd: { type: 'string', description: 'Working directory (default: cwd)' },\r\n },\r\n },\r\n async execute(input, ctx, opts) {\r\n const cwd = input.cwd ? safeResolve(input.cwd, ctx) : ctx.cwd;\r\n const lines = input.lines ?? 100;\r\n let filterRe: RegExp | null = null;\r\n if (input.filter) {\r\n const compiled = compileUserRegex(input.filter, 'i');\r\n if (!compiled.ok) {\r\n throw new Error(`logs: ${compiled.reason}`);\r\n }\r\n filterRe = compiled.regex;\r\n }\r\n\r\n if (input.service) {\r\n return await dockerLogs(input.service, lines, filterRe, cwd, opts.signal);\r\n }\r\n\r\n if (input.path) {\r\n return await fileLogs(safeResolve(input.path, ctx), lines, filterRe, input.stream ?? false);\r\n }\r\n\r\n return {\r\n source: 'none',\r\n entries: [],\r\n total: 0,\r\n truncated: false,\r\n stream_mode: false,\r\n };\r\n },\r\n};\r\n\r\nasync function dockerLogs(\r\n service: string,\r\n lines: number,\r\n filterRe: RegExp | null,\r\n cwd: string,\r\n signal: AbortSignal,\r\n since?: string | undefined,\r\n): Promise<LogsOutput> {\r\n const args = ['logs'];\r\n if (lines > 0) args.push('--tail', String(lines));\r\n /* v8 ignore start -- execute() does not forward `since` to dockerLogs yet; this block is currently unreachable. */\r\n if (since) {\r\n const sinceMap: Record<string, string> = { '1h': '1h', '6h': '6h', '24h': '24h' };\r\n args.push('--since', sinceMap[since] ?? '1h');\r\n }\r\n /* v8 ignore stop */\r\n // Validate service name to prevent container name injection.\r\n // Docker container names are limited to [a-zA-Z0-9][a-zA-Z0-9._-]+.\r\n if (!/^[a-zA-Z0-9][a-zA-Z0-9._:-]+$/.test(service)) {\r\n return {\r\n source: `docker:${service}`,\r\n entries: [],\r\n total: 0,\r\n truncated: false,\r\n stream_mode: false,\r\n };\r\n }\r\n args.push('--timestamps', service);\r\n\r\n return new Promise((resolve) => {\r\n let stdout = '';\r\n let stderr = '';\r\n const MAX = 200_000;\r\n let settled = false;\r\n\r\n const empty = (): LogsOutput => ({\r\n source: `docker:${service}`,\r\n entries: [],\r\n total: 0,\r\n truncated: false,\r\n stream_mode: false,\r\n });\r\n const finish = (result: LogsOutput) => {\r\n if (settled) return;\r\n settled = true;\r\n clearTimeout(timer);\r\n resolve(result);\r\n };\r\n\r\n const child = spawn('docker', args, { cwd, signal, env: buildChildEnv(), stdio: ['ignore', 'pipe', 'pipe'], windowsHide: true });\r\n\r\n // `docker logs --tail N` reads recent lines and exits — fast when the\r\n // daemon is up. But if the daemon is unreachable (common on CI runners\r\n // with no running Docker), the CLI can hang on the socket connection and\r\n // emit neither `close` nor `error`. Kill it and return empty so the tool\r\n // (and its tests) never hang.\r\n const timer = setTimeout(() => {\r\n child.kill('SIGTERM');\r\n finish(empty());\r\n }, DOCKER_LOGS_TIMEOUT_MS);\r\n\r\n child.stdout?.on('data', (c) => {\r\n if (stdout.length < MAX) stdout += c.toString();\r\n });\r\n child.stderr?.on('data', (c) => {\r\n if (stderr.length < MAX) stderr += c.toString();\r\n });\r\n // When the child is SIGTERM-killed on timeout (or aborted via `signal`),\r\n // its pipes can emit `error` (e.g. EPIPE on Windows). Without a listener\r\n // that surfaces as an unhandled error and can fail the host/test — swallow\r\n // it at debug level; `child.on('error')` and the timeout already drive the result.\r\n child.stdout?.on('error', (e) => {\r\n console.log(JSON.stringify({ level: 'debug', event: 'pipe_error', stream: 'stdout', error: e.message }));\r\n });\r\n child.stderr?.on('error', (e) => {\r\n console.log(JSON.stringify({ level: 'debug', event: 'pipe_error', stream: 'stderr', error: e.message }));\r\n });\r\n child.on('close', () => {\r\n const output = stdout + stderr;\r\n const entries = parseLogLines(output, filterRe);\r\n finish({\r\n source: `docker:${service}`,\r\n entries,\r\n total: entries.length,\r\n truncated: output.length >= MAX,\r\n stream_mode: false,\r\n });\r\n });\r\n child.on('error', () => finish(empty()));\r\n });\r\n}\r\n\r\n/**\r\n * Hard ceiling for a `docker logs` read. The daemon may be unreachable on CI\r\n * (no Docker running), where the CLI hangs on the socket without ever exiting.\r\n */\r\nconst DOCKER_LOGS_TIMEOUT_MS = 3_000;\r\n\r\n// Hard cap on tail-window size — `lines: 0` historically meant \"all\" and\r\n// happily buffered an entire multi-GB log into memory. Cap at 100k lines;\r\n// callers that need more should narrow with `filter`.\r\nconst MAX_TAIL_LINES = 100_000;\r\n\r\nasync function fileLogs(\r\n path: string,\r\n lines: number,\r\n filterRe: RegExp | null,\r\n stream: boolean,\r\n): Promise<LogsOutput> {\r\n const { createInterface } = await import('node:readline');\r\n const { createReadStream } = await import('node:fs');\r\n const entries: LogEntry[] = [];\r\n\r\n // Effective tail window: clamp to MAX_TAIL_LINES; treat 0 / negative as\r\n // \"max window\" rather than \"unlimited\" so a malicious /proc/kcore path\r\n // cannot OOM the worker.\r\n const effLines = lines > 0 ? Math.min(lines, MAX_TAIL_LINES) : MAX_TAIL_LINES;\r\n // Rolling window backed by a fixed-size circular buffer — at most\r\n // `effLines` strings live in memory regardless of file size.\r\n const window: string[] = new Array(effLines);\r\n let writeIdx = 0;\r\n let totalLines = 0;\r\n\r\n const rl = createInterface({\r\n input: createReadStream(path),\r\n crlfDelay: Number.POSITIVE_INFINITY,\r\n });\r\n\r\n for await (const line of rl) {\r\n if (filterRe && !filterRe.test(line)) continue;\r\n window[writeIdx] = line;\r\n writeIdx = (writeIdx + 1) % effLines;\r\n totalLines++;\r\n }\r\n\r\n // Read the window back in arrival order.\r\n const ordered: string[] = [];\r\n const start = totalLines >= effLines ? writeIdx : 0;\r\n const count = Math.min(totalLines, effLines);\r\n for (let i = 0; i < count; i++) {\r\n const v = window[(start + i) % effLines];\r\n if (v !== undefined) ordered.push(v);\r\n }\r\n\r\n for (const line of ordered) {\r\n const parsed = parseLine(line);\r\n if (parsed) entries.push(parsed);\r\n }\r\n\r\n return {\r\n source: path,\r\n entries,\r\n total: entries.length,\r\n truncated: totalLines > effLines,\r\n stream_mode: stream,\r\n };\r\n}\r\n\r\nfunction parseLogLines(output: string, filterRe: RegExp | null): LogEntry[] {\r\n const lines = output.split('\\n').filter(Boolean);\r\n const entries: LogEntry[] = [];\r\n\r\n for (const line of lines) {\r\n if (filterRe && !filterRe.test(line)) continue;\r\n const parsed = parseLine(line);\r\n if (parsed) entries.push(parsed);\r\n }\r\n\r\n return entries;\r\n}\r\n\r\nfunction parseLine(line: string): LogEntry | null {\r\n const tsRe = /^(\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}(?:\\.\\d+)?Z?)\\s+(?:\\[?(\\w+)\\]?)\\s*(.*)/;\r\n const match = tsRe.exec(line);\r\n\r\n if (match) {\r\n return {\r\n timestamp: match[1] ?? '',\r\n level: match[2]?.toLowerCase() ?? 'info',\r\n message: match[3] ?? '',\r\n };\r\n }\r\n\r\n const levelRe = /(ERROR|WARN|INFO|DEBUG|TRACE)\\s+(.*)/i;\r\n const levelMatch = levelRe.exec(line);\r\n\r\n if (levelMatch) {\r\n return {\r\n timestamp: '',\r\n level: levelMatch[1]?.toLowerCase() ?? 'info',\r\n message: levelMatch[2] ?? line,\r\n };\r\n }\r\n\r\n return {\r\n timestamp: '',\r\n level: 'info',\r\n message: line,\r\n };\r\n}\r\n"]}
package/dist/memory.js CHANGED
@@ -9,6 +9,7 @@ function rememberTool(memory) {
9
9
  mutating: true,
10
10
  timeoutMs: 2e3,
11
11
  capabilities: ["memory.write"],
12
+ icon: "settings",
12
13
  inputSchema: {
13
14
  type: "object",
14
15
  properties: {
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/memory.ts"],"names":[],"mappings":"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","file":"memory.js","sourcesContent":["import type { MemoryScope, MemoryStore, Tool } from '@wrongstack/core';\nimport type { MemoryEntry } from '@wrongstack/core';\n\ninterface RememberInput {\n text: string;\n scope?: MemoryScope | undefined;\n /** Memory type for categorization. */\n type?: 'fact' | 'decision' | 'convention' | 'preference' | 'reference' | 'anti_pattern' | undefined;\n /** Hashtag-style tags for grouping and search. */\n tags?: string[] | undefined;\n /** Priority level — critical entries always injected into context. */\n priority?: 'critical' | 'high' | 'medium' | 'low' | undefined;\n}\n\ninterface RememberOutput {\n ok: true;\n scope: MemoryScope;\n}\n\ninterface ForgetInput {\n query: string;\n scope?: MemoryScope | undefined;\n}\n\ninterface ForgetOutput {\n removed: number;\n scope: MemoryScope;\n}\n\nexport function rememberTool(memory: MemoryStore): Tool<RememberInput, RememberOutput> {\n return {\n name: 'remember',\n category: 'Session',\n description:\n 'Persist facts, conventions, decisions, and preferences into long-term memory. Memories survive restarts and are scored for relevance in future sessions.',\n usageHint:\n 'Persist facts, conventions, decisions, and preferences into long-term memory.\\n\\n' +\n 'WHEN TO USE:\\n' +\n '- Project conventions discovered during a task (build tool, lint rules, code style)\\n' +\n '- Architecture decisions made (chose X over Y, decided to use pattern Z)\\n' +\n '- User preferences expressed (prefers short names, always uses pnpm)\\n' +\n '- Anti-patterns identified (never do X, avoid pattern Y)\\n' +\n '- File/location references useful across sessions\\n\\n' +\n 'WHEN NOT TO USE:\\n' +\n '- Temporary task state or progress → use `todo`\\n' +\n '- One-off debugging notes\\n' +\n '- Information already obvious from the codebase\\n\\n' +\n 'Always include `type` and `priority`. Use 1-3 `tags` for grouping.\\n' +\n 'Better to remember a fact now than rediscover it next session.',\n permission: 'auto',\n mutating: true,\n timeoutMs: 2_000,\n capabilities: ['memory.write'],\n inputSchema: {\n type: 'object',\n properties: {\n text: {\n type: 'string',\n description: 'The fact or note to remember. Keep it concise and factual.',\n },\n scope: {\n type: 'string',\n enum: ['project-agents', 'project-memory', 'user-memory'],\n description: 'Where to store it: project-memory (shared), user-memory (personal), or project-agents.',\n },\n type: {\n type: 'string',\n enum: ['fact', 'decision', 'convention', 'preference', 'reference', 'anti_pattern'],\n description: 'Category for filtering and relevance scoring.',\n },\n tags: {\n type: 'array',\n items: { type: 'string' },\n description: 'Hashtag-style tags for grouping and search.',\n },\n priority: {\n type: 'string',\n enum: ['critical', 'high', 'medium', 'low'],\n description: 'Priority level. Critical = always injected into context.',\n },\n },\n required: ['text'],\n },\n async execute(input) {\n if (!input?.text) throw new Error('remember: text is required');\n const scope = input.scope ?? 'project-memory';\n await memory.remember(input.text, scope, {\n type: input.type,\n tags: input.tags,\n priority: input.priority,\n });\n return { ok: true, scope };\n },\n };\n}\n\nexport function forgetTool(memory: MemoryStore): Tool<ForgetInput, ForgetOutput> {\n return {\n name: 'forget',\n category: 'Session',\n description: 'Remove memory entries that contain the given substring (case-insensitive). Use with caution.',\n usageHint:\n 'This permanently deletes matching memories in the chosen scope.\\n' +\n '- Provide a reasonably specific `query` to avoid deleting unrelated memories.\\n' +\n '- Always double-check before calling with broad queries.\\n' +\n '- Use `remember` + `forget` together to maintain clean long-term memory.',\n permission: 'confirm',\n mutating: true,\n timeoutMs: 2_000,\n capabilities: ['memory.delete'],\n inputSchema: {\n type: 'object',\n properties: {\n query: { type: 'string' },\n scope: { type: 'string', enum: ['project-agents', 'project-memory', 'user-memory'] },\n },\n required: ['query'],\n },\n async execute(input) {\n if (!input?.query) throw new Error('forget: query is required');\n const scope = input.scope ?? 'project-memory';\n const removed = await memory.forget(input.query, scope);\n return { removed, scope };\n },\n };\n}\n\n// ── Enhanced memory query tools — use backend capabilities ───────────\n\ninterface SearchMemoryInput {\n query: string;\n scope?: MemoryScope | undefined;\n limit?: number | undefined;\n}\n\ninterface SearchMemoryOutput {\n results: Array<{\n text: string;\n ts: string;\n scope: MemoryScope;\n type?: string | undefined;\n tags?: string[] | undefined;\n priority?: string | undefined;\n }>;\n}\n\nexport function searchMemoryTool(memory: MemoryStore): Tool<SearchMemoryInput, SearchMemoryOutput> {\n return {\n name: 'search_memory',\n category: 'Session',\n description:\n 'Search memory entries by content. With the default backend this does substring matching; semantic/graph backends use embedding similarity or graph traversal.',\n usageHint:\n 'Search long-term memory for relevant facts, conventions, or decisions.\\n' +\n '- Returns results ordered by relevance (newest-first for default, similarity for semantic).\\n' +\n '- Use before starting a task to recall project conventions and past decisions.\\n' +\n '- `limit` caps results (default 5, max 20).',\n permission: 'auto',\n mutating: false,\n timeoutMs: 2_000,\n capabilities: ['memory.read'],\n inputSchema: {\n type: 'object',\n properties: {\n query: {\n type: 'string',\n description: 'Search query — words or phrase to find in memory.',\n },\n scope: {\n type: 'string',\n enum: ['project-agents', 'project-memory', 'user-memory'],\n description: 'Which scope to search. Defaults to project-memory.',\n },\n limit: {\n type: 'number',\n description: 'Maximum results to return (default 5, max 20).',\n },\n },\n required: ['query'],\n },\n async execute(input) {\n if (!input?.query) throw new Error('search_memory: query is required');\n const scope = input.scope ?? 'project-memory';\n const limit = Math.min(input.limit ?? 5, 20);\n const entries = await memory.search(input.query, scope, limit);\n return {\n results: entries.map((e: MemoryEntry) => ({\n text: e.text,\n ts: e.ts,\n scope: e.scope,\n type: e.type,\n tags: e.tags,\n priority: e.priority,\n })),\n };\n },\n };\n}\n\ninterface RelatedMemoryInput {\n text: string;\n scope?: MemoryScope | undefined;\n limit?: number | undefined;\n}\n\nexport function relatedMemoryTool(memory: MemoryStore): Tool<RelatedMemoryInput, SearchMemoryOutput> {\n return {\n name: 'find_related_memories',\n category: 'Session',\n description:\n 'Find memories related to the given text via graph traversal. Only available with graph backends; falls back to content search with file backends.',\n usageHint:\n 'Discover memories connected to a topic through co-occurrence or similarity edges.\\n' +\n '- Useful for exploring what else the project knows about a given concept.\\n' +\n '- Falls back to content search when no graph backend is configured.\\n' +\n '- `limit` caps results (default 5, max 20).',\n permission: 'auto',\n mutating: false,\n timeoutMs: 2_000,\n capabilities: ['memory.read'],\n inputSchema: {\n type: 'object',\n properties: {\n text: {\n type: 'string',\n description: 'Text to find related memories for.',\n },\n scope: {\n type: 'string',\n enum: ['project-agents', 'project-memory', 'user-memory'],\n description: 'Which scope to search. Defaults to project-memory.',\n },\n limit: {\n type: 'number',\n description: 'Maximum results to return (default 5, max 20).',\n },\n },\n required: ['text'],\n },\n async execute(input) {\n if (!input?.text) throw new Error('find_related_memories: text is required');\n const scope = input.scope ?? 'project-memory';\n const limit = Math.min(input.limit ?? 5, 20);\n const entries = memory.findRelated\n ? await memory.findRelated(input.text, scope, limit)\n : await memory.search(input.text, scope, limit);\n return {\n results: entries.map((e: MemoryEntry) => ({\n text: e.text,\n ts: e.ts,\n scope: e.scope,\n type: e.type,\n tags: e.tags,\n priority: e.priority,\n })),\n };\n },\n };\n}\n"]}
1
+ {"version":3,"sources":["../src/memory.ts"],"names":[],"mappings":"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","file":"memory.js","sourcesContent":["import type { MemoryScope, MemoryStore, Tool } from '@wrongstack/core';\nimport type { MemoryEntry } from '@wrongstack/core';\n\ninterface RememberInput {\n text: string;\n scope?: MemoryScope | undefined;\n /** Memory type for categorization. */\n type?: 'fact' | 'decision' | 'convention' | 'preference' | 'reference' | 'anti_pattern' | undefined;\n /** Hashtag-style tags for grouping and search. */\n tags?: string[] | undefined;\n /** Priority level — critical entries always injected into context. */\n priority?: 'critical' | 'high' | 'medium' | 'low' | undefined;\n}\n\ninterface RememberOutput {\n ok: true;\n scope: MemoryScope;\n}\n\ninterface ForgetInput {\n query: string;\n scope?: MemoryScope | undefined;\n}\n\ninterface ForgetOutput {\n removed: number;\n scope: MemoryScope;\n}\n\nexport function rememberTool(memory: MemoryStore): Tool<RememberInput, RememberOutput> {\n return {\n name: 'remember',\n category: 'Session',\n description:\n 'Persist facts, conventions, decisions, and preferences into long-term memory. Memories survive restarts and are scored for relevance in future sessions.',\n usageHint:\n 'Persist facts, conventions, decisions, and preferences into long-term memory.\\n\\n' +\n 'WHEN TO USE:\\n' +\n '- Project conventions discovered during a task (build tool, lint rules, code style)\\n' +\n '- Architecture decisions made (chose X over Y, decided to use pattern Z)\\n' +\n '- User preferences expressed (prefers short names, always uses pnpm)\\n' +\n '- Anti-patterns identified (never do X, avoid pattern Y)\\n' +\n '- File/location references useful across sessions\\n\\n' +\n 'WHEN NOT TO USE:\\n' +\n '- Temporary task state or progress → use `todo`\\n' +\n '- One-off debugging notes\\n' +\n '- Information already obvious from the codebase\\n\\n' +\n 'Always include `type` and `priority`. Use 1-3 `tags` for grouping.\\n' +\n 'Better to remember a fact now than rediscover it next session.',\n permission: 'auto',\n mutating: true,\n timeoutMs: 2_000,\n capabilities: ['memory.write'],\n icon: 'settings',\n inputSchema: {\n type: 'object',\n properties: {\n text: {\n type: 'string',\n description: 'The fact or note to remember. Keep it concise and factual.',\n },\n scope: {\n type: 'string',\n enum: ['project-agents', 'project-memory', 'user-memory'],\n description: 'Where to store it: project-memory (shared), user-memory (personal), or project-agents.',\n },\n type: {\n type: 'string',\n enum: ['fact', 'decision', 'convention', 'preference', 'reference', 'anti_pattern'],\n description: 'Category for filtering and relevance scoring.',\n },\n tags: {\n type: 'array',\n items: { type: 'string' },\n description: 'Hashtag-style tags for grouping and search.',\n },\n priority: {\n type: 'string',\n enum: ['critical', 'high', 'medium', 'low'],\n description: 'Priority level. Critical = always injected into context.',\n },\n },\n required: ['text'],\n },\n async execute(input) {\n if (!input?.text) throw new Error('remember: text is required');\n const scope = input.scope ?? 'project-memory';\n await memory.remember(input.text, scope, {\n type: input.type,\n tags: input.tags,\n priority: input.priority,\n });\n return { ok: true, scope };\n },\n };\n}\n\nexport function forgetTool(memory: MemoryStore): Tool<ForgetInput, ForgetOutput> {\n return {\n name: 'forget',\n category: 'Session',\n description: 'Remove memory entries that contain the given substring (case-insensitive). Use with caution.',\n usageHint:\n 'This permanently deletes matching memories in the chosen scope.\\n' +\n '- Provide a reasonably specific `query` to avoid deleting unrelated memories.\\n' +\n '- Always double-check before calling with broad queries.\\n' +\n '- Use `remember` + `forget` together to maintain clean long-term memory.',\n permission: 'confirm',\n mutating: true,\n timeoutMs: 2_000,\n capabilities: ['memory.delete'],\n inputSchema: {\n type: 'object',\n properties: {\n query: { type: 'string' },\n scope: { type: 'string', enum: ['project-agents', 'project-memory', 'user-memory'] },\n },\n required: ['query'],\n },\n async execute(input) {\n if (!input?.query) throw new Error('forget: query is required');\n const scope = input.scope ?? 'project-memory';\n const removed = await memory.forget(input.query, scope);\n return { removed, scope };\n },\n };\n}\n\n// ── Enhanced memory query tools — use backend capabilities ───────────\n\ninterface SearchMemoryInput {\n query: string;\n scope?: MemoryScope | undefined;\n limit?: number | undefined;\n}\n\ninterface SearchMemoryOutput {\n results: Array<{\n text: string;\n ts: string;\n scope: MemoryScope;\n type?: string | undefined;\n tags?: string[] | undefined;\n priority?: string | undefined;\n }>;\n}\n\nexport function searchMemoryTool(memory: MemoryStore): Tool<SearchMemoryInput, SearchMemoryOutput> {\n return {\n name: 'search_memory',\n category: 'Session',\n description:\n 'Search memory entries by content. With the default backend this does substring matching; semantic/graph backends use embedding similarity or graph traversal.',\n usageHint:\n 'Search long-term memory for relevant facts, conventions, or decisions.\\n' +\n '- Returns results ordered by relevance (newest-first for default, similarity for semantic).\\n' +\n '- Use before starting a task to recall project conventions and past decisions.\\n' +\n '- `limit` caps results (default 5, max 20).',\n permission: 'auto',\n mutating: false,\n timeoutMs: 2_000,\n capabilities: ['memory.read'],\n inputSchema: {\n type: 'object',\n properties: {\n query: {\n type: 'string',\n description: 'Search query — words or phrase to find in memory.',\n },\n scope: {\n type: 'string',\n enum: ['project-agents', 'project-memory', 'user-memory'],\n description: 'Which scope to search. Defaults to project-memory.',\n },\n limit: {\n type: 'number',\n description: 'Maximum results to return (default 5, max 20).',\n },\n },\n required: ['query'],\n },\n async execute(input) {\n if (!input?.query) throw new Error('search_memory: query is required');\n const scope = input.scope ?? 'project-memory';\n const limit = Math.min(input.limit ?? 5, 20);\n const entries = await memory.search(input.query, scope, limit);\n return {\n results: entries.map((e: MemoryEntry) => ({\n text: e.text,\n ts: e.ts,\n scope: e.scope,\n type: e.type,\n tags: e.tags,\n priority: e.priority,\n })),\n };\n },\n };\n}\n\ninterface RelatedMemoryInput {\n text: string;\n scope?: MemoryScope | undefined;\n limit?: number | undefined;\n}\n\nexport function relatedMemoryTool(memory: MemoryStore): Tool<RelatedMemoryInput, SearchMemoryOutput> {\n return {\n name: 'find_related_memories',\n category: 'Session',\n description:\n 'Find memories related to the given text via graph traversal. Only available with graph backends; falls back to content search with file backends.',\n usageHint:\n 'Discover memories connected to a topic through co-occurrence or similarity edges.\\n' +\n '- Useful for exploring what else the project knows about a given concept.\\n' +\n '- Falls back to content search when no graph backend is configured.\\n' +\n '- `limit` caps results (default 5, max 20).',\n permission: 'auto',\n mutating: false,\n timeoutMs: 2_000,\n capabilities: ['memory.read'],\n inputSchema: {\n type: 'object',\n properties: {\n text: {\n type: 'string',\n description: 'Text to find related memories for.',\n },\n scope: {\n type: 'string',\n enum: ['project-agents', 'project-memory', 'user-memory'],\n description: 'Which scope to search. Defaults to project-memory.',\n },\n limit: {\n type: 'number',\n description: 'Maximum results to return (default 5, max 20).',\n },\n },\n required: ['text'],\n },\n async execute(input) {\n if (!input?.text) throw new Error('find_related_memories: text is required');\n const scope = input.scope ?? 'project-memory';\n const limit = Math.min(input.limit ?? 5, 20);\n const entries = memory.findRelated\n ? await memory.findRelated(input.text, scope, limit)\n : await memory.search(input.text, scope, limit);\n return {\n results: entries.map((e: MemoryEntry) => ({\n text: e.text,\n ts: e.ts,\n scope: e.scope,\n type: e.type,\n tags: e.tags,\n priority: e.priority,\n })),\n };\n },\n };\n}\n"]}
package/dist/mode.js CHANGED
@@ -9,6 +9,7 @@ function createModeTool(modeStore) {
9
9
  mutating: true,
10
10
  timeoutMs: 5e3,
11
11
  capabilities: ["session.mode"],
12
+ icon: "settings",
12
13
  inputSchema: {
13
14
  type: "object",
14
15
  properties: {
package/dist/mode.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/mode.ts"],"names":[],"mappings":";AAeO,SAAS,eAAe,SAAA,EAAmD;AAChF,EAAA,OAAO;AAAA,IACL,IAAA,EAAM,MAAA;AAAA,IACN,QAAA,EAAU,SAAA;AAAA,IACV,WAAA,EACE,mKAAA;AAAA,IACF,SAAA,EACE,yWAAA;AAAA,IAMF,UAAA,EAAY,SAAA;AAAA,IACZ,QAAA,EAAU,IAAA;AAAA,IACV,SAAA,EAAW,GAAA;AAAA,IACX,YAAA,EAAc,CAAC,cAAc,CAAA;AAAA,IAC7B,WAAA,EAAa;AAAA,MACX,IAAA,EAAM,QAAA;AAAA,MACN,UAAA,EAAY;AAAA,QACV,MAAA,EAAQ;AAAA,UACN,IAAA,EAAM,QAAA;AAAA,UACN,IAAA,EAAM,CAAC,KAAA,EAAO,MAAA,EAAQ,OAAO,OAAO,CAAA;AAAA,UACpC,WAAA,EAAa;AAAA,SACf;AAAA,QACA,IAAA,EAAM;AAAA,UACJ,IAAA,EAAM,QAAA;AAAA,UACN,WAAA,EAAa;AAAA;AACf,OACF;AAAA,MACA,QAAA,EAAU,CAAC,QAAQ;AAAA,KACrB;AAAA,IACA,MAAM,QAAQ,KAAA,EAAO;AACnB,MAAA,QAAQ,MAAM,MAAA;AAAQ,QACpB,KAAK,KAAA,EAAO;AACV,UAAA,MAAM,IAAA,GAAO,MAAM,SAAA,CAAU,aAAA,EAAc;AAC3C,UAAA,OAAO;AAAA,YACL,MAAA,EAAQ,KAAA;AAAA,YACR,aAAa,IAAA,EAAM,EAAA;AAAA,YACnB,OAAA,EAAS,IAAA;AAAA,YACT,OAAA,EAAS,OACL,CAAA,cAAA,EAAiB,IAAA,CAAK,IAAI,CAAA,QAAA,EAAM,IAAA,CAAK,WAAW,CAAA,CAAA,GAChD;AAAA,WACN;AAAA,QACF;AAAA,QACA,KAAK,MAAA,EAAQ;AACX,UAAA,MAAM,KAAA,GAAQ,MAAM,SAAA,CAAU,SAAA,EAAU;AACxC,UAAA,MAAM,KAAA,GAAQ,MACX,GAAA,CAAI,CAAC,MAAM,CAAA,EAAA,EAAK,CAAA,CAAE,GAAG,MAAA,CAAO,EAAE,CAAC,CAAA,CAAA,EAAI,CAAA,CAAE,IAAI,CAAA,QAAA,EAAM,CAAA,CAAE,WAAW,CAAA,CAAE,CAAA,CAC9D,KAAK,IAAI,CAAA;AACZ,UAAA,OAAO;AAAA,YACL,MAAA,EAAQ,MAAA;AAAA,YACR,KAAA,EAAO,KAAA,CAAM,GAAA,CAAI,CAAC,OAAO,EAAE,EAAA,EAAI,CAAA,CAAE,EAAA,EAAI,MAAM,CAAA,CAAE,IAAA,EAAM,WAAA,EAAa,CAAA,CAAE,aAAY,CAAE,CAAA;AAAA,YAChF,OAAA,EAAS,IAAA;AAAA,YACT,OAAA,EAAS;AAAA,WACX;AAAA,QACF;AAAA,QACA,KAAK,KAAA,EAAO;AACV,UAAA,IAAI,CAAC,MAAM,IAAA,EAAM;AACf,YAAA,OAAO,EAAE,MAAA,EAAQ,KAAA,EAAO,OAAA,EAAS,KAAA,EAAO,SAAS,iCAAA,EAAkC;AAAA,UACrF;AACA,UAAA,MAAM,IAAA,GAAO,MAAM,SAAA,CAAU,OAAA,CAAQ,MAAM,IAAI,CAAA;AAC/C,UAAA,IAAI,CAAC,IAAA,EAAM;AACT,YAAA,OAAO,EAAE,QAAQ,KAAA,EAAO,OAAA,EAAS,OAAO,OAAA,EAAS,CAAA,MAAA,EAAS,KAAA,CAAM,IAAI,CAAA,WAAA,CAAA,EAAc;AAAA,UACpF;AACA,UAAA,MAAM,SAAA,CAAU,aAAA,CAAc,KAAA,CAAM,IAAI,CAAA;AACxC,UAAA,OAAO;AAAA,YACL,MAAA,EAAQ,KAAA;AAAA,YACR,aAAa,IAAA,CAAK,EAAA;AAAA,YAClB,OAAA,EAAS,IAAA;AAAA,YACT,OAAA,EAAS,CAAA,kBAAA,EAAqB,IAAA,CAAK,IAAI;;AAAA,EAAO,KAAK,WAAW,CAAA;AAAA,WAChE;AAAA,QACF;AAAA,QACA,KAAK,OAAA,EAAS;AACZ,UAAA,MAAM,SAAA,CAAU,cAAc,IAAI,CAAA;AAClC,UAAA,OAAO;AAAA,YACL,MAAA,EAAQ,OAAA;AAAA,YACR,OAAA,EAAS,IAAA;AAAA,YACT,OAAA,EAAS;AAAA,WACX;AAAA,QACF;AAAA,QACA;AACE,UAAA,OAAO;AAAA,YACL,QAAQ,KAAA,CAAM,MAAA;AAAA,YACd,OAAA,EAAS,KAAA;AAAA,YACT,OAAA,EAAS,CAAA,gBAAA,EAAmB,KAAA,CAAM,MAAM,CAAA,CAAA;AAAA,WAC1C;AAAA;AACJ,IACF;AAAA,GACF;AACF","file":"mode.js","sourcesContent":["import type { ModeStore, Tool } from '@wrongstack/core';\n\ninterface ModeInput {\n action: 'get' | 'list' | 'set' | 'clear';\n mode?: string | undefined;\n}\n\ninterface ModeOutput {\n action: string;\n currentMode?: string | undefined;\n modes?: { id: string | undefined; name: string; description: string }[];\n success: boolean;\n message: string;\n}\n\nexport function createModeTool(modeStore: ModeStore): Tool<ModeInput, ModeOutput> {\n return {\n name: 'mode',\n category: 'Session',\n description:\n 'Manage agent operating modes. Modes change the agent\\'s behavior, personality, and system prompt for different workflows (e.g. coding, security review, planning).',\n usageHint:\n 'POWERFUL BEHAVIOR CONTROL TOOL:\\n\\n' +\n '- Use `list` to see available modes.\\n' +\n '- Use `set <modeId>` to switch the agent into a specific role/mode.\\n' +\n '- Use `get` to check current mode.\\n' +\n '- Use `clear` to return to default behavior.\\n' +\n 'Switching modes is very effective for specialized tasks. The mode change affects how the agent reasons and which guidelines it follows.',\n permission: 'confirm',\n mutating: true,\n timeoutMs: 5_000,\n capabilities: ['session.mode'],\n inputSchema: {\n type: 'object',\n properties: {\n action: {\n type: 'string',\n enum: ['get', 'list', 'set', 'clear'],\n description: 'The mode operation to perform.',\n },\n mode: {\n type: 'string',\n description: 'The mode identifier to activate (only required when action=set).',\n },\n },\n required: ['action'],\n },\n async execute(input) {\n switch (input.action) {\n case 'get': {\n const mode = await modeStore.getActiveMode();\n return {\n action: 'get',\n currentMode: mode?.id,\n success: true,\n message: mode\n ? `Current mode: ${mode.name} — ${mode.description}`\n : 'No mode set (using default)',\n };\n }\n case 'list': {\n const modes = await modeStore.listModes();\n const lines = modes\n .map((m) => ` ${m.id.padEnd(20)} ${m.name} — ${m.description}`)\n .join('\\n');\n return {\n action: 'list',\n modes: modes.map((m) => ({ id: m.id, name: m.name, description: m.description })),\n success: true,\n message: lines,\n };\n }\n case 'set': {\n if (!input.mode) {\n return { action: 'set', success: false, message: 'mode is required for action=set' };\n }\n const mode = await modeStore.getMode(input.mode);\n if (!mode) {\n return { action: 'set', success: false, message: `Mode \"${input.mode}\" not found` };\n }\n await modeStore.setActiveMode(input.mode);\n return {\n action: 'set',\n currentMode: mode.id,\n success: true,\n message: `Switched to mode: ${mode.name}\\n\\n${mode.description}`,\n };\n }\n case 'clear': {\n await modeStore.setActiveMode(null);\n return {\n action: 'clear',\n success: true,\n message: 'Mode cleared — using default mode',\n };\n }\n default:\n return {\n action: input.action,\n success: false,\n message: `Unknown action \"${input.action}\"`,\n };\n }\n },\n };\n}\n"]}
1
+ {"version":3,"sources":["../src/mode.ts"],"names":[],"mappings":"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","file":"mode.js","sourcesContent":["import type { ModeStore, Tool } from '@wrongstack/core';\n\ninterface ModeInput {\n action: 'get' | 'list' | 'set' | 'clear';\n mode?: string | undefined;\n}\n\ninterface ModeOutput {\n action: string;\n currentMode?: string | undefined;\n modes?: { id: string | undefined; name: string; description: string }[];\n success: boolean;\n message: string;\n}\n\nexport function createModeTool(modeStore: ModeStore): Tool<ModeInput, ModeOutput> {\n return {\n name: 'mode',\n category: 'Session',\n description:\n 'Manage agent operating modes. Modes change the agent\\'s behavior, personality, and system prompt for different workflows (e.g. coding, security review, planning).',\n usageHint:\n 'POWERFUL BEHAVIOR CONTROL TOOL:\\n\\n' +\n '- Use `list` to see available modes.\\n' +\n '- Use `set <modeId>` to switch the agent into a specific role/mode.\\n' +\n '- Use `get` to check current mode.\\n' +\n '- Use `clear` to return to default behavior.\\n' +\n 'Switching modes is very effective for specialized tasks. The mode change affects how the agent reasons and which guidelines it follows.',\n permission: 'confirm',\n mutating: true,\n timeoutMs: 5_000,\n capabilities: ['session.mode'],\n icon: 'settings',\n inputSchema: {\n type: 'object',\n properties: {\n action: {\n type: 'string',\n enum: ['get', 'list', 'set', 'clear'],\n description: 'The mode operation to perform.',\n },\n mode: {\n type: 'string',\n description: 'The mode identifier to activate (only required when action=set).',\n },\n },\n required: ['action'],\n },\n async execute(input) {\n switch (input.action) {\n case 'get': {\n const mode = await modeStore.getActiveMode();\n return {\n action: 'get',\n currentMode: mode?.id,\n success: true,\n message: mode\n ? `Current mode: ${mode.name} — ${mode.description}`\n : 'No mode set (using default)',\n };\n }\n case 'list': {\n const modes = await modeStore.listModes();\n const lines = modes\n .map((m) => ` ${m.id.padEnd(20)} ${m.name} — ${m.description}`)\n .join('\\n');\n return {\n action: 'list',\n modes: modes.map((m) => ({ id: m.id, name: m.name, description: m.description })),\n success: true,\n message: lines,\n };\n }\n case 'set': {\n if (!input.mode) {\n return { action: 'set', success: false, message: 'mode is required for action=set' };\n }\n const mode = await modeStore.getMode(input.mode);\n if (!mode) {\n return { action: 'set', success: false, message: `Mode \"${input.mode}\" not found` };\n }\n await modeStore.setActiveMode(input.mode);\n return {\n action: 'set',\n currentMode: mode.id,\n success: true,\n message: `Switched to mode: ${mode.name}\\n\\n${mode.description}`,\n };\n }\n case 'clear': {\n await modeStore.setActiveMode(null);\n return {\n action: 'clear',\n success: true,\n message: 'Mode cleared — using default mode',\n };\n }\n default:\n return {\n action: input.action,\n success: false,\n message: `Unknown action \"${input.action}\"`,\n };\n }\n },\n };\n}\n"]}
package/dist/outdated.js CHANGED
@@ -1,4 +1,5 @@
1
1
  import { spawn } from 'node:child_process';
2
+ import * as Core from '@wrongstack/core';
2
3
  import { buildChildEnv } from '@wrongstack/core';
3
4
  import * as path from 'node:path';
4
5
  import * as fs from 'node:fs';
@@ -21,15 +22,20 @@ async function detectPackageManager(cwd) {
21
22
  function resolvePath(input, ctx) {
22
23
  return path.isAbsolute(input) ? path.normalize(input) : path.resolve(ctx.workingDir ?? ctx.cwd, input);
23
24
  }
25
+ function allowedRoots(ctx) {
26
+ return [path.resolve(ctx.projectRoot), path.resolve(Core.wstackGlobalRoot())];
27
+ }
28
+ function isInsideAny(target, roots) {
29
+ return roots.some((root) => {
30
+ const rel = path.relative(root, target);
31
+ return rel === "" || !rel.startsWith("..") && !path.isAbsolute(rel);
32
+ });
33
+ }
24
34
  function ensureInsideRoot(absPath, ctx) {
25
- if (ctx.allowOutsideProjectRoot) return path.resolve(absPath);
26
- const root = path.resolve(ctx.projectRoot);
27
35
  const target = path.resolve(absPath);
28
- const rel = path.relative(root, target);
29
- if (rel.startsWith("..") || path.isAbsolute(rel)) {
30
- throw new Error(`Path "${absPath}" is outside project root "${root}"`);
31
- }
32
- return target;
36
+ if (ctx.allowOutsideProjectRoot) return target;
37
+ if (isInsideAny(target, allowedRoots(ctx))) return target;
38
+ throw new Error(`Path "${absPath}" is outside project root "${path.resolve(ctx.projectRoot)}"`);
33
39
  }
34
40
  function safeResolve(input, ctx) {
35
41
  return ensureInsideRoot(resolvePath(input, ctx), ctx);
@@ -62,6 +68,7 @@ var outdatedTool = {
62
68
  description: "Check for outdated dependencies in the project. Reports current, wanted (semver range), and latest versions available.",
63
69
  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- Hits the package registry over HTTP, so it is NOT purely local \u2014 flagged as mutating for the confirmation gate.\nUse the output to decide on upgrades. Prefer this over manual shell commands for dependency hygiene.",
64
70
  permission: "confirm",
71
+ icon: "package",
65
72
  // Network side-effecting (registry HTTP). Pairs with `mutating: true`
66
73
  // so the H7 invariant test (`no auto-permission tool declares
67
74
  // mutating: true`) passes — a tool claiming `'auto'` must be purely
@@ -71,12 +78,15 @@ var outdatedTool = {
71
78
  // fixed four sibling tools (mcp_control, shellcheck, shellcheck_scan,
72
79
  // web_search) but missed this one; applying the same contract here.
73
80
  mutating: true,
74
- // Capability is just "network" — the tool only hits the package
81
+ // Capability is outbound network — the tool only hits the package
75
82
  // registry over HTTP, never touches the filesystem or runs shell.
83
+ // Use the canonical `net.outbound` capability (not the non-existent
84
+ // `network` string) so the subagent allowlist recognises it and
85
+ // permits read-only registry lookups under a director.
76
86
  // The H7 invariant test requires this array to be non-empty for
77
87
  // any mutating:true tool (meta-tools whitelisted). See
78
88
  // tests/permission-mutating-invariant.test.ts:92.
79
- capabilities: ["network"],
89
+ capabilities: ["net.outbound"],
80
90
  timeoutMs: 6e4,
81
91
  inputSchema: {
82
92
  type: "object",
@@ -113,7 +123,8 @@ function runOutdated(manager, args, cwd, signal) {
113
123
  const MAX = 1e5;
114
124
  const resolved = resolveWin32Command(manager);
115
125
  const needsShell = process.platform === "win32" && (resolved.endsWith(".cmd") || resolved.endsWith(".bat"));
116
- const child = spawn(resolved, args, { cwd, signal, env: buildChildEnv(), stdio: ["ignore", "pipe", "pipe"], windowsHide: true, ...needsShell ? { shell: true, windowsVerbatimArguments: true } : {} });
126
+ const spawnCmd = needsShell ? manager : resolved;
127
+ const child = spawn(spawnCmd, args, { cwd, signal, env: buildChildEnv(), stdio: ["ignore", "pipe", "pipe"], windowsHide: true, ...needsShell ? { shell: true, windowsVerbatimArguments: true } : {} });
117
128
  child.stdout?.on("data", (c) => {
118
129
  if (stdout.length < MAX) stdout += c.toString();
119
130
  });
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/_util.ts","../src/_win32-resolve.ts","../src/outdated.ts"],"names":["path2","resolve"],"mappings":"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","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/** 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.workingDir ?? ctx.cwd, input);\n}\n\nexport function ensureInsideRoot(absPath: string, ctx: Context): string {\n // If allowOutsideProjectRoot is true, skip the project-root restriction.\n if (ctx.allowOutsideProjectRoot) return path.resolve(absPath);\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 // If allowOutsideProjectRoot is true, skip the symlink-escape check.\n if (ctx.allowOutsideProjectRoot) return;\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 /* v8 ignore next -- only caller (truncateHeadTail) passes a budget smaller than s; defensive. */\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 /* v8 ignore next -- only caller (truncateHeadTail) passes a budget smaller than s; defensive. */\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 | undefined } = {},\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 * as fs from 'node:fs';\nimport * as path from 'node:path';\n\n/**\n * On Windows, Node.js `spawn()` without a shell does NOT resolve .cmd/.bat\n * extensions through PATHEXT — it only auto-resolves .exe. Most Node.js CLI\n * tools (npx, pnpm, biome, tsc, vitest, etc.) ship as .cmd wrappers on\n * Windows. This function resolves the command name to its full path so spawn\n * can find it without relying on shell-mode argument concatenation.\n *\n * On non-Windows, returns the command unchanged.\n */\nexport function resolveWin32Command(cmd: string): string {\n if (process.platform !== 'win32') return cmd;\n\n // Already has a path or extension — use as-is\n // Normalize forward slashes so path.extname correctly detects extensions\n // even when a Unix-style path is passed on Windows.\n if (cmd.includes('/') || cmd.includes('\\\\') || path.extname(cmd.replace(/\\//g, '\\\\'))) {\n return cmd;\n }\n\n const pathext = (process.env['PATHEXT'] ?? '.COM;.EXE;.BAT;.CMD;.VBS;.JS;.WS;.MSC')\n .toLowerCase()\n .split(';');\n\n const pathDirs = (process.env['PATH'] ?? '').split(path.delimiter);\n\n for (const dir of pathDirs) {\n const base = path.join(dir, cmd);\n // Check extensions in PATHEXT order. .EXE should win first because\n // it's typically listed first, and .exe doesn't need shell: true.\n for (const ext of pathext) {\n const full = `${base}${ext}`;\n try {\n fs.accessSync(full, fs.constants.X_OK);\n return full;\n } catch {\n // Not found with this extension — try next\n }\n }\n }\n\n // Not found — return original; let spawn report ENOENT with the\n // expected error message so tools can surface it properly.\n return cmd;\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';\nimport { resolveWin32Command } from './_win32-resolve.js';\n\ninterface OutdatedInput {\n cwd?: string | undefined;\n format?: 'list' | 'table' | undefined;\n include_deprecated?: boolean | undefined;\n check?: string | string[] | undefined;\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 '- Hits the package registry over HTTP, so it is NOT purely local — flagged as mutating for the confirmation gate.\\n' +\n 'Use the output to decide on upgrades. Prefer this over manual shell commands for dependency hygiene.',\n permission: 'confirm',\n // Network side-effecting (registry HTTP). Pairs with `mutating: true`\n // so the H7 invariant test (`no auto-permission tool declares\n // mutating: true`) passes — a tool claiming `'auto'` must be purely\n // read-only, but `outdated` makes outbound HTTP calls to the\n // registry. The 'confirm' permission routes the call through the\n // tool.confirm_needed flow on every invocation. M-1 originally\n // fixed four sibling tools (mcp_control, shellcheck, shellcheck_scan,\n // web_search) but missed this one; applying the same contract here.\n mutating: true,\n // Capability is just \"network\" — the tool only hits the package\n // registry over HTTP, never touches the filesystem or runs shell.\n // The H7 invariant test requires this array to be non-empty for\n // any mutating:true tool (meta-tools whitelisted). See\n // tests/permission-mutating-invariant.test.ts:92.\n capabilities: ['network'],\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 resolved = resolveWin32Command(manager);\n const needsShell = process.platform === 'win32' && (resolved.endsWith('.cmd') || resolved.endsWith('.bat'));\n const child = spawn(resolved, args, { cwd, signal, env: buildChildEnv(), stdio: ['ignore', 'pipe', 'pipe'], windowsHide: true, ...(needsShell ? { shell: true, windowsVerbatimArguments: true } : {}) });\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/_win32-resolve.ts","../src/outdated.ts"],"names":["path2","resolve"],"mappings":"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","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/** 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.workingDir ?? ctx.cwd, input);\n}\n\n/**\n * Roots every file tool may always reach, even in restricted mode: the\n * project root and the user-global `~/.wrongstack` directory (config, memory,\n * sessions, skills). `~/.wrongstack` honors the `WRONGSTACK_HOME` override.\n */\nfunction allowedRoots(ctx: Context): string[] {\n return [path.resolve(ctx.projectRoot), path.resolve(Core.wstackGlobalRoot())];\n}\n\n/** True if `target` is `root` itself or nested inside any of `roots`. */\nfunction isInsideAny(target: string, roots: string[]): boolean {\n return roots.some((root) => {\n const rel = path.relative(root, target);\n return rel === '' || (!rel.startsWith('..') && !path.isAbsolute(rel));\n });\n}\n\nexport function ensureInsideRoot(absPath: string, ctx: Context): string {\n const target = path.resolve(absPath);\n // Unrestricted filesystem access: skip the project-root containment check.\n if (ctx.allowOutsideProjectRoot) return target;\n if (isInsideAny(target, allowedRoots(ctx))) return target;\n throw new Error(`Path \"${absPath}\" is outside project root \"${path.resolve(ctx.projectRoot)}\"`);\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 // Unrestricted filesystem access: no symlink-escape check to perform.\n if (ctx.allowOutsideProjectRoot) return;\n // Compare like-for-like against the realpath of each always-allowed root\n // (project root + ~/.wrongstack), since a root may itself be a symlink.\n const realRoots = await Promise.all(\n allowedRoots(ctx).map((r) => fsp.realpath(r).catch(() => path.resolve(r))),\n );\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 if (isInsideAny(real, realRoots)) return;\n throw new Error(\n `Path \"${absPath}\" resolves through a symlink outside project root \"${realRoots[0]}\"`,\n );\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 /* v8 ignore next -- only caller (truncateHeadTail) passes a budget smaller than s; defensive. */\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 /* v8 ignore next -- only caller (truncateHeadTail) passes a budget smaller than s; defensive. */\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 | undefined } = {},\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 * as fs from 'node:fs';\nimport * as path from 'node:path';\n\n/**\n * On Windows, Node.js `spawn()` without a shell does NOT resolve .cmd/.bat\n * extensions through PATHEXT — it only auto-resolves .exe. Most Node.js CLI\n * tools (npx, pnpm, biome, tsc, vitest, etc.) ship as .cmd wrappers on\n * Windows. This function resolves the command name to its full path so spawn\n * can find it without relying on shell-mode argument concatenation.\n *\n * On non-Windows, returns the command unchanged.\n */\nexport function resolveWin32Command(cmd: string): string {\n if (process.platform !== 'win32') return cmd;\n\n // Already has a path or extension — use as-is\n // Normalize forward slashes so path.extname correctly detects extensions\n // even when a Unix-style path is passed on Windows.\n if (cmd.includes('/') || cmd.includes('\\\\') || path.extname(cmd.replace(/\\//g, '\\\\'))) {\n return cmd;\n }\n\n const pathext = (process.env['PATHEXT'] ?? '.COM;.EXE;.BAT;.CMD;.VBS;.JS;.WS;.MSC')\n .toLowerCase()\n .split(';');\n\n const pathDirs = (process.env['PATH'] ?? '').split(path.delimiter);\n\n for (const dir of pathDirs) {\n const base = path.join(dir, cmd);\n // Check extensions in PATHEXT order. .EXE should win first because\n // it's typically listed first, and .exe doesn't need shell: true.\n for (const ext of pathext) {\n const full = `${base}${ext}`;\n try {\n fs.accessSync(full, fs.constants.X_OK);\n return full;\n } catch {\n // Not found with this extension — try next\n }\n }\n }\n\n // Not found — return original; let spawn report ENOENT with the\n // expected error message so tools can surface it properly.\n return cmd;\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';\nimport { resolveWin32Command } from './_win32-resolve.js';\n\ninterface OutdatedInput {\n cwd?: string | undefined;\n format?: 'list' | 'table' | undefined;\n include_deprecated?: boolean | undefined;\n check?: string | string[] | undefined;\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 '- Hits the package registry over HTTP, so it is NOT purely local — flagged as mutating for the confirmation gate.\\n' +\n 'Use the output to decide on upgrades. Prefer this over manual shell commands for dependency hygiene.',\n permission: 'confirm',\n icon: 'package',\n // Network side-effecting (registry HTTP). Pairs with `mutating: true`\n // so the H7 invariant test (`no auto-permission tool declares\n // mutating: true`) passes — a tool claiming `'auto'` must be purely\n // read-only, but `outdated` makes outbound HTTP calls to the\n // registry. The 'confirm' permission routes the call through the\n // tool.confirm_needed flow on every invocation. M-1 originally\n // fixed four sibling tools (mcp_control, shellcheck, shellcheck_scan,\n // web_search) but missed this one; applying the same contract here.\n mutating: true,\n // Capability is outbound network — the tool only hits the package\n // registry over HTTP, never touches the filesystem or runs shell.\n // Use the canonical `net.outbound` capability (not the non-existent\n // `network` string) so the subagent allowlist recognises it and\n // permits read-only registry lookups under a director.\n // The H7 invariant test requires this array to be non-empty for\n // any mutating:true tool (meta-tools whitelisted). See\n // tests/permission-mutating-invariant.test.ts:92.\n capabilities: ['net.outbound'],\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 resolved = resolveWin32Command(manager);\n const needsShell = process.platform === 'win32' && (resolved.endsWith('.cmd') || resolved.endsWith('.bat'));\n // When using shell: true, the shell resolves through PATH — passing\n // the full resolved path (which may contain spaces) breaks cmd.exe.\n const spawnCmd = needsShell ? manager : resolved;\n const child = spawn(spawnCmd, args, { cwd, signal, env: buildChildEnv(), stdio: ['ignore', 'pipe', 'pipe'], windowsHide: true, ...(needsShell ? { shell: true, windowsVerbatimArguments: true } : {}) });\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"]}