@wrongstack/tools 0.1.1 → 0.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (103) hide show
  1. package/dist/audit.d.ts +25 -0
  2. package/dist/audit.js +209 -0
  3. package/dist/audit.js.map +1 -0
  4. package/dist/bash.d.ts +16 -0
  5. package/dist/bash.js +180 -0
  6. package/dist/bash.js.map +1 -0
  7. package/dist/batch-tool-use.d.ts +26 -0
  8. package/dist/batch-tool-use.js +106 -0
  9. package/dist/batch-tool-use.js.map +1 -0
  10. package/dist/builtin.d.ts +5 -0
  11. package/dist/builtin.js +3735 -0
  12. package/dist/builtin.js.map +1 -0
  13. package/dist/diff.d.ts +20 -0
  14. package/dist/diff.js +142 -0
  15. package/dist/diff.js.map +1 -0
  16. package/dist/document.d.ts +27 -0
  17. package/dist/document.js +148 -0
  18. package/dist/document.js.map +1 -0
  19. package/dist/edit.d.ts +22 -0
  20. package/dist/edit.js +138 -0
  21. package/dist/edit.js.map +1 -0
  22. package/dist/exec.d.ts +21 -0
  23. package/dist/exec.js +159 -0
  24. package/dist/exec.js.map +1 -0
  25. package/dist/fetch.d.ts +15 -0
  26. package/dist/fetch.js +213 -0
  27. package/dist/fetch.js.map +1 -0
  28. package/dist/format.d.ts +18 -0
  29. package/dist/format.js +194 -0
  30. package/dist/format.js.map +1 -0
  31. package/dist/git.d.ts +27 -0
  32. package/dist/git.js +174 -0
  33. package/dist/git.js.map +1 -0
  34. package/dist/glob.d.ts +14 -0
  35. package/dist/glob.js +101 -0
  36. package/dist/glob.js.map +1 -0
  37. package/dist/grep.d.ts +20 -0
  38. package/dist/grep.js +264 -0
  39. package/dist/grep.js.map +1 -0
  40. package/dist/index.d.ts +34 -563
  41. package/dist/index.js +717 -442
  42. package/dist/index.js.map +1 -1
  43. package/dist/install.d.ts +19 -0
  44. package/dist/install.js +186 -0
  45. package/dist/install.js.map +1 -0
  46. package/dist/json.d.ts +20 -0
  47. package/dist/json.js +124 -0
  48. package/dist/json.js.map +1 -0
  49. package/dist/lint.d.ts +20 -0
  50. package/dist/lint.js +191 -0
  51. package/dist/lint.js.map +1 -0
  52. package/dist/logs.d.ts +27 -0
  53. package/dist/logs.js +180 -0
  54. package/dist/logs.js.map +1 -0
  55. package/dist/memory.d.ts +22 -0
  56. package/dist/memory.js +53 -0
  57. package/dist/memory.js.map +1 -0
  58. package/dist/mode.d.ts +20 -0
  59. package/dist/mode.js +81 -0
  60. package/dist/mode.js.map +1 -0
  61. package/dist/outdated.d.ts +26 -0
  62. package/dist/outdated.js +138 -0
  63. package/dist/outdated.js.map +1 -0
  64. package/dist/patch.d.ts +18 -0
  65. package/dist/patch.js +101 -0
  66. package/dist/patch.js.map +1 -0
  67. package/dist/read.d.ts +16 -0
  68. package/dist/read.js +81 -0
  69. package/dist/read.js.map +1 -0
  70. package/dist/replace.d.ts +23 -0
  71. package/dist/replace.js +196 -0
  72. package/dist/replace.js.map +1 -0
  73. package/dist/scaffold.d.ts +20 -0
  74. package/dist/scaffold.js +185 -0
  75. package/dist/scaffold.js.map +1 -0
  76. package/dist/search.d.ts +20 -0
  77. package/dist/search.js +212 -0
  78. package/dist/search.js.map +1 -0
  79. package/dist/test.d.ts +24 -0
  80. package/dist/test.js +247 -0
  81. package/dist/test.js.map +1 -0
  82. package/dist/todo.d.ts +12 -0
  83. package/dist/todo.js +53 -0
  84. package/dist/todo.js.map +1 -0
  85. package/dist/tool-help.d.ts +23 -0
  86. package/dist/tool-help.js +122 -0
  87. package/dist/tool-help.js.map +1 -0
  88. package/dist/tool-search.d.ts +22 -0
  89. package/dist/tool-search.js +70 -0
  90. package/dist/tool-search.js.map +1 -0
  91. package/dist/tool-use.d.ts +16 -0
  92. package/dist/tool-use.js +79 -0
  93. package/dist/tool-use.js.map +1 -0
  94. package/dist/tree.d.ts +21 -0
  95. package/dist/tree.js +176 -0
  96. package/dist/tree.js.map +1 -0
  97. package/dist/typecheck.d.ts +19 -0
  98. package/dist/typecheck.js +181 -0
  99. package/dist/typecheck.js.map +1 -0
  100. package/dist/write.d.ts +15 -0
  101. package/dist/write.js +77 -0
  102. package/dist/write.js.map +1 -0
  103. package/package.json +137 -4
@@ -0,0 +1,19 @@
1
+ import { Tool } from '@wrongstack/core';
2
+
3
+ interface InstallInput {
4
+ packages?: string | string[];
5
+ save?: 'dependency' | 'dev' | 'optional';
6
+ cwd?: string;
7
+ dry_run?: boolean;
8
+ global?: boolean;
9
+ }
10
+ interface InstallOutput {
11
+ packages: string[];
12
+ exit_code: number;
13
+ output: string;
14
+ dry_run: boolean;
15
+ truncated: boolean;
16
+ }
17
+ declare const installTool: Tool<InstallInput, InstallOutput>;
18
+
19
+ export { installTool };
@@ -0,0 +1,186 @@
1
+ import * as path from 'path';
2
+ import { spawn } from 'child_process';
3
+
4
+ // src/_util.ts
5
+ function resolvePath(input, ctx) {
6
+ return path.isAbsolute(input) ? path.normalize(input) : path.resolve(ctx.cwd, input);
7
+ }
8
+ function ensureInsideRoot(absPath, ctx) {
9
+ const root = path.resolve(ctx.projectRoot);
10
+ const target = path.resolve(absPath);
11
+ const rel = path.relative(root, target);
12
+ if (rel.startsWith("..") || path.isAbsolute(rel)) {
13
+ throw new Error(`Path "${absPath}" is outside project root "${root}"`);
14
+ }
15
+ return target;
16
+ }
17
+ function safeResolve(input, ctx) {
18
+ return ensureInsideRoot(resolvePath(input, ctx), ctx);
19
+ }
20
+ async function* spawnStream(opts) {
21
+ const max = opts.maxBytes;
22
+ const flushAt = opts.flushBytes ?? 4 * 1024;
23
+ let stdout = "";
24
+ let stderr = "";
25
+ let pending = "";
26
+ let error;
27
+ const child = spawn(opts.cmd, opts.args, {
28
+ cwd: opts.cwd,
29
+ signal: opts.signal,
30
+ stdio: ["ignore", "pipe", "pipe"]
31
+ });
32
+ const queue = [];
33
+ let waiter;
34
+ const wake = () => {
35
+ if (waiter) {
36
+ const w = waiter;
37
+ waiter = void 0;
38
+ w();
39
+ }
40
+ };
41
+ child.stdout?.on("data", (c) => {
42
+ const s = c.toString();
43
+ if (stdout.length < max) stdout += s;
44
+ queue.push({ kind: "out", data: s });
45
+ wake();
46
+ });
47
+ child.stderr?.on("data", (c) => {
48
+ const s = c.toString();
49
+ if (stderr.length < max) stderr += s;
50
+ queue.push({ kind: "err", data: s });
51
+ wake();
52
+ });
53
+ child.on("error", (e) => {
54
+ error = e.message;
55
+ queue.push({ kind: "error", data: e.message });
56
+ wake();
57
+ });
58
+ child.on("close", (code) => {
59
+ queue.push({ kind: "close", data: "", code: code ?? 0 });
60
+ wake();
61
+ });
62
+ let exitCode = 0;
63
+ let spawnFailed = false;
64
+ for (; ; ) {
65
+ while (queue.length === 0) {
66
+ await new Promise((resolve2) => {
67
+ waiter = resolve2;
68
+ });
69
+ }
70
+ const chunk = queue.shift();
71
+ if (chunk.kind === "close") {
72
+ if (!spawnFailed) exitCode = chunk.code ?? 0;
73
+ break;
74
+ }
75
+ if (chunk.kind === "error") {
76
+ spawnFailed = true;
77
+ exitCode = 1;
78
+ continue;
79
+ }
80
+ pending += chunk.data;
81
+ if (pending.length >= flushAt) {
82
+ yield { type: "partial_output", text: pending };
83
+ pending = "";
84
+ }
85
+ }
86
+ if (pending.length > 0) {
87
+ yield { type: "partial_output", text: pending };
88
+ }
89
+ return {
90
+ stdout,
91
+ stderr,
92
+ exitCode,
93
+ truncated: stdout.length >= max || stderr.length >= max,
94
+ error
95
+ };
96
+ }
97
+
98
+ // src/install.ts
99
+ var installTool = {
100
+ name: "install",
101
+ description: "Install npm packages. Detects pnpm/npm/yarn and uses the right package manager.",
102
+ usageHint: "Set `packages` to install. `save` as dependency type. `global` for global install. `dry_run` to preview.",
103
+ permission: "confirm",
104
+ mutating: true,
105
+ timeoutMs: 12e4,
106
+ inputSchema: {
107
+ type: "object",
108
+ properties: {
109
+ packages: {
110
+ type: "string",
111
+ description: "Package(s) to install: single name, comma-separated list, or empty for all deps"
112
+ },
113
+ save: {
114
+ type: "string",
115
+ enum: ["dependency", "dev", "optional"],
116
+ description: "Save as regular, dev, or optional dependency"
117
+ },
118
+ cwd: { type: "string", description: "Working directory (default: cwd)" },
119
+ dry_run: { type: "boolean", description: "Preview install without modifying (default: false)" },
120
+ global: { type: "boolean", description: "Install globally (default: false)" }
121
+ }
122
+ },
123
+ async execute(input, ctx, opts) {
124
+ let final;
125
+ for await (const ev of installTool.executeStream(input, ctx, opts)) {
126
+ if (ev.type === "final") final = ev.output;
127
+ }
128
+ if (!final) throw new Error("install: stream ended without final event");
129
+ return final;
130
+ },
131
+ async *executeStream(input, ctx, opts) {
132
+ const cwd = input.cwd ? safeResolve(input.cwd, ctx) : ctx.cwd;
133
+ const pkgManager = await detectPackageManager(cwd);
134
+ yield { type: "log", text: `Resolving with ${pkgManager}\u2026`, data: { phase: "resolve" } };
135
+ const save = input.save === "dev" ? "-D" : input.save === "optional" ? "-O" : "";
136
+ const globalFlag = input.global ? ["-g"] : [];
137
+ const args = [];
138
+ if (input.dry_run) args.push("--dry-run");
139
+ if (pkgManager === "pnpm") {
140
+ if (save) args.push(save);
141
+ args.push("add", ...globalFlag);
142
+ } else if (pkgManager === "yarn") {
143
+ args.push("add", ...globalFlag);
144
+ } else {
145
+ args.push("install", ...globalFlag);
146
+ }
147
+ const pkgList = input.packages ? (Array.isArray(input.packages) ? input.packages : input.packages.split(",")).map((p) => p.trim()) : [];
148
+ if (pkgList.length > 0) args.push(...pkgList);
149
+ yield { type: "log", text: `Fetching ${pkgList.length || "all"} packages\u2026`, data: { phase: "fetch" } };
150
+ const result = yield* spawnStream({
151
+ cmd: pkgManager,
152
+ args,
153
+ cwd,
154
+ signal: opts.signal,
155
+ maxBytes: 1e5
156
+ });
157
+ yield {
158
+ type: "final",
159
+ output: {
160
+ packages: pkgList,
161
+ exit_code: result.exitCode,
162
+ output: result.stdout || result.stderr || result.error || "",
163
+ dry_run: args.includes("--dry-run"),
164
+ truncated: result.truncated
165
+ }
166
+ };
167
+ }
168
+ };
169
+ async function detectPackageManager(cwd) {
170
+ const { stat } = await import('fs/promises');
171
+ try {
172
+ await stat(`${cwd}/pnpm-lock.yaml`);
173
+ return "pnpm";
174
+ } catch {
175
+ try {
176
+ await stat(`${cwd}/yarn.lock`);
177
+ return "yarn";
178
+ } catch {
179
+ return "npm";
180
+ }
181
+ }
182
+ }
183
+
184
+ export { installTool };
185
+ //# sourceMappingURL=install.js.map
186
+ //# sourceMappingURL=install.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/_util.ts","../src/install.ts"],"names":["resolve"],"mappings":"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","file":"install.js","sourcesContent":["import * as path from 'node:path';\r\nimport { spawn } from 'node:child_process';\r\nimport type { Context, ToolProgressEvent } from '@wrongstack/core';\r\n\r\nexport function resolvePath(input: string, ctx: Context): string {\r\n return path.isAbsolute(input) ? path.normalize(input) : path.resolve(ctx.cwd, input);\r\n}\r\n\r\nexport function ensureInsideRoot(absPath: string, ctx: Context): string {\r\n const root = path.resolve(ctx.projectRoot);\r\n const target = path.resolve(absPath);\r\n const rel = path.relative(root, target);\r\n if (rel.startsWith('..') || path.isAbsolute(rel)) {\r\n throw new Error(`Path \"${absPath}\" is outside project root \"${root}\"`);\r\n }\r\n return target;\r\n}\r\n\r\nexport function safeResolve(input: string, ctx: Context): string {\r\n return ensureInsideRoot(resolvePath(input, ctx), ctx);\r\n}\r\n\r\nexport function truncateMiddle(s: string, max: number): string {\r\n if (Buffer.byteLength(s, 'utf8') <= max) return s;\r\n const half = Math.floor(max / 2);\r\n return (\r\n s.slice(0, half) +\r\n `\\n…[truncated ${Buffer.byteLength(s, 'utf8') - max} bytes from middle]…\\n` +\r\n s.slice(-half)\r\n );\r\n}\r\n\r\nexport function isBinaryBuffer(buf: Buffer): boolean {\r\n const len = Math.min(buf.length, 8192);\r\n for (let i = 0; i < len; i++) {\r\n if (buf[i] === 0) return true;\r\n }\r\n return false;\r\n}\r\n\r\nexport interface SpawnStreamResult {\r\n stdout: string;\r\n stderr: string;\r\n exitCode: number;\r\n truncated: boolean;\r\n error?: string;\r\n}\r\n\r\nexport interface SpawnStreamOptions {\r\n cmd: string;\r\n args: string[];\r\n cwd: string;\r\n signal: AbortSignal;\r\n maxBytes?: number;\r\n /** Bytes of new stdout/stderr to accumulate before yielding a `partial_output` event. */\r\n flushBytes?: number;\r\n}\r\n\r\n/**\r\n * Spawn a child process and yield `partial_output` progress events as\r\n * stdout/stderr arrive (batched by byte threshold), then return the full\r\n * buffered result. Shared between install/lint/format/typecheck/test/audit\r\n * so the TUI live tail sees consistent progress regardless of which tool\r\n * is running.\r\n */\r\nexport async function* spawnStream(\r\n opts: SpawnStreamOptions,\r\n): AsyncGenerator<ToolProgressEvent, SpawnStreamResult> {\r\n const max = opts.maxBytes ?? 200_000;\r\n const flushAt = opts.flushBytes ?? 4 * 1024;\r\n let stdout = '';\r\n let stderr = '';\r\n let pending = '';\r\n let error: string | undefined;\r\n\r\n const child = spawn(opts.cmd, opts.args, {\r\n cwd: opts.cwd,\r\n signal: opts.signal,\r\n stdio: ['ignore', 'pipe', 'pipe'],\r\n });\r\n\r\n type Chunk = { kind: 'out' | 'err' | 'close' | 'error'; data: string; code?: number };\r\n const queue: Chunk[] = [];\r\n let waiter: (() => void) | undefined;\r\n const wake = () => {\r\n if (waiter) {\r\n const w = waiter;\r\n waiter = undefined;\r\n w();\r\n }\r\n };\r\n\r\n child.stdout?.on('data', (c) => {\r\n const s = c.toString();\r\n if (stdout.length < max) stdout += s;\r\n queue.push({ kind: 'out', data: s });\r\n wake();\r\n });\r\n child.stderr?.on('data', (c) => {\r\n const s = c.toString();\r\n if (stderr.length < max) stderr += s;\r\n queue.push({ kind: 'err', data: s });\r\n wake();\r\n });\r\n child.on('error', (e) => {\r\n error = e.message;\r\n queue.push({ kind: 'error', data: e.message });\r\n wake();\r\n });\r\n child.on('close', (code) => {\r\n queue.push({ kind: 'close', data: '', code: code ?? 0 });\r\n wake();\r\n });\r\n\r\n let exitCode = 0;\r\n let spawnFailed = false;\r\n for (;;) {\r\n while (queue.length === 0) {\r\n await new Promise<void>((resolve) => {\r\n waiter = resolve;\r\n });\r\n }\r\n const chunk = queue.shift()!;\r\n if (chunk.kind === 'close') {\r\n // If we already saw a spawn error (ENOENT etc.), keep exitCode=1\r\n // rather than the negative platform code Node fabricates.\r\n if (!spawnFailed) exitCode = chunk.code ?? 0;\r\n break;\r\n }\r\n if (chunk.kind === 'error') {\r\n spawnFailed = true;\r\n exitCode = 1;\r\n // close usually follows\r\n continue;\r\n }\r\n pending += chunk.data;\r\n if (pending.length >= flushAt) {\r\n yield { type: 'partial_output', text: pending };\r\n pending = '';\r\n }\r\n }\r\n if (pending.length > 0) {\r\n yield { type: 'partial_output', text: pending };\r\n }\r\n\r\n return {\r\n stdout,\r\n stderr,\r\n exitCode,\r\n truncated: stdout.length >= max || stderr.length >= max,\r\n error,\r\n };\r\n}\r\n","import type { Tool, ToolStreamEvent } from '@wrongstack/core';\r\nimport { safeResolve, spawnStream } from './_util.js';\r\n\r\ninterface InstallInput {\r\n packages?: string | string[];\r\n save?: 'dependency' | 'dev' | 'optional';\r\n cwd?: string;\r\n dry_run?: boolean;\r\n global?: boolean;\r\n}\r\n\r\ninterface InstallOutput {\r\n packages: string[];\r\n exit_code: number;\r\n output: string;\r\n dry_run: boolean;\r\n truncated: boolean;\r\n}\r\n\r\nexport const installTool: Tool<InstallInput, InstallOutput> = {\r\n name: 'install',\r\n description:\r\n 'Install npm packages. Detects pnpm/npm/yarn and uses the right package manager.',\r\n usageHint:\r\n 'Set `packages` to install. `save` as dependency type. `global` for global install. `dry_run` to preview.',\r\n permission: 'confirm',\r\n mutating: true,\r\n timeoutMs: 120_000,\r\n inputSchema: {\r\n type: 'object',\r\n properties: {\r\n packages: {\r\n type: 'string',\r\n description: 'Package(s) to install: single name, comma-separated list, or empty for all deps',\r\n },\r\n save: {\r\n type: 'string',\r\n enum: ['dependency', 'dev', 'optional'],\r\n description: 'Save as regular, dev, or optional dependency',\r\n },\r\n cwd: { type: 'string', description: 'Working directory (default: cwd)' },\r\n dry_run: { type: 'boolean', description: 'Preview install without modifying (default: false)' },\r\n global: { type: 'boolean', description: 'Install globally (default: false)' },\r\n },\r\n },\r\n async execute(input, ctx, opts) {\r\n let final: InstallOutput | undefined;\r\n for await (const ev of installTool.executeStream!(input, ctx, opts)) {\r\n if (ev.type === 'final') final = ev.output;\r\n }\r\n if (!final) throw new Error('install: stream ended without final event');\r\n return final;\r\n },\r\n async *executeStream(input, ctx, opts): AsyncGenerator<ToolStreamEvent<InstallOutput>> {\r\n const cwd = input.cwd ? safeResolve(input.cwd, ctx) : ctx.cwd;\r\n const pkgManager = await detectPackageManager(cwd);\r\n yield { type: 'log', text: `Resolving with ${pkgManager}…`, data: { phase: 'resolve' } };\r\n\r\n const save = input.save === 'dev' ? '-D' : input.save === 'optional' ? '-O' : '';\r\n const globalFlag = input.global ? ['-g'] : [];\r\n\r\n const args: string[] = [];\r\n if (input.dry_run) args.push('--dry-run');\r\n if (pkgManager === 'pnpm') {\r\n if (save) args.push(save);\r\n args.push('add', ...globalFlag);\r\n } else if (pkgManager === 'yarn') {\r\n args.push('add', ...globalFlag);\r\n } else {\r\n args.push('install', ...globalFlag);\r\n }\r\n\r\n const pkgList = input.packages\r\n ? (Array.isArray(input.packages) ? input.packages : input.packages.split(',')).map((p) => p.trim())\r\n : [];\r\n if (pkgList.length > 0) args.push(...pkgList);\r\n\r\n yield { type: 'log', text: `Fetching ${pkgList.length || 'all'} packages…`, data: { phase: 'fetch' } };\r\n\r\n const result = yield* spawnStream({\r\n cmd: pkgManager,\r\n args,\r\n cwd,\r\n signal: opts.signal,\r\n maxBytes: 100_000,\r\n });\r\n\r\n yield {\r\n type: 'final',\r\n output: {\r\n packages: pkgList,\r\n exit_code: result.exitCode,\r\n output: result.stdout || result.stderr || result.error || '',\r\n dry_run: args.includes('--dry-run'),\r\n truncated: result.truncated,\r\n },\r\n };\r\n },\r\n};\r\n\r\nasync function detectPackageManager(cwd: string): Promise<string> {\r\n const { stat } = await import('node:fs/promises');\r\n try {\r\n await stat(`${cwd}/pnpm-lock.yaml`);\r\n return 'pnpm';\r\n } catch {\r\n try {\r\n await stat(`${cwd}/yarn.lock`);\r\n return 'yarn';\r\n } catch {\r\n return 'npm';\r\n }\r\n }\r\n}\r\n"]}
package/dist/json.d.ts ADDED
@@ -0,0 +1,20 @@
1
+ import { Tool } from '@wrongstack/core';
2
+
3
+ interface JsonInput {
4
+ file?: string;
5
+ data?: string;
6
+ query?: string;
7
+ format?: 'json' | 'json5' | 'yaml';
8
+ validate?: boolean;
9
+ }
10
+ interface JsonOutput {
11
+ data: unknown;
12
+ formatted: string;
13
+ type: string;
14
+ keys?: string[];
15
+ query_result?: unknown;
16
+ error?: string;
17
+ }
18
+ declare const jsonTool: Tool<JsonInput, JsonOutput>;
19
+
20
+ export { jsonTool };
package/dist/json.js ADDED
@@ -0,0 +1,124 @@
1
+ import * as fs from 'fs/promises';
2
+
3
+ // src/json.ts
4
+ var jsonTool = {
5
+ name: "json",
6
+ description: "Parse, query, and validate JSON/JSON5/YAML. Use `query` with JMESPath-like paths to extract values.",
7
+ usageHint: 'Provide `file` path or `data` string. `query` supports dot notation (e.g. "results[0].name"). `format` outputs in specified format.',
8
+ permission: "auto",
9
+ mutating: false,
10
+ timeoutMs: 5e3,
11
+ inputSchema: {
12
+ type: "object",
13
+ properties: {
14
+ file: { type: "string", description: "Path to JSON/JSON5/YAML file" },
15
+ data: { type: "string", description: "JSON/JSON5/YAML string (alternative to file)" },
16
+ query: { type: "string", description: 'JMESPath-like query (e.g. "a.b[0].c" or "a[*].name")' },
17
+ format: {
18
+ type: "string",
19
+ enum: ["json", "json5", "yaml"],
20
+ description: "Output format (default: json)"
21
+ },
22
+ validate: {
23
+ type: "boolean",
24
+ description: "Validate syntax only, no output (default: false)"
25
+ }
26
+ }
27
+ },
28
+ async execute(input) {
29
+ const format = input.format ?? "json";
30
+ let parsed;
31
+ let raw;
32
+ if (input.file) {
33
+ try {
34
+ raw = await fs.readFile(input.file, "utf8");
35
+ } catch {
36
+ return { data: null, formatted: "", type: "unknown", error: `Could not read file` };
37
+ }
38
+ } else if (input.data) {
39
+ raw = input.data;
40
+ } else {
41
+ return { data: null, formatted: "", type: "unknown", error: "Provide file or data" };
42
+ }
43
+ try {
44
+ parsed = JSON.parse(raw);
45
+ } catch (e) {
46
+ return {
47
+ data: null,
48
+ formatted: "",
49
+ type: "unknown",
50
+ error: `Parse failed: ${e instanceof Error ? e.message : String(e)}`
51
+ };
52
+ }
53
+ if (input.validate) {
54
+ return {
55
+ data: parsed,
56
+ formatted: "valid",
57
+ type: Array.isArray(parsed) ? "array" : typeof parsed,
58
+ keys: typeof parsed === "object" && parsed !== null ? Object.keys(parsed) : void 0
59
+ };
60
+ }
61
+ const queryResult = input.query ? query(parsed, input.query) : void 0;
62
+ const formatted = formatOutput(queryResult ?? parsed, format);
63
+ return {
64
+ data: parsed,
65
+ formatted,
66
+ type: Array.isArray(parsed) ? "array" : typeof parsed,
67
+ keys: typeof parsed === "object" && parsed !== null ? Object.keys(parsed) : void 0,
68
+ query_result: queryResult
69
+ };
70
+ }
71
+ };
72
+ function query(data, path) {
73
+ const parts = path.replace(/\[(\d+)\]/g, ".$1").split(".").filter(Boolean);
74
+ let current = data;
75
+ for (const part of parts) {
76
+ if (current === null || current === void 0) return void 0;
77
+ const idx = Number(part);
78
+ if (!Number.isNaN(idx) && Array.isArray(current)) {
79
+ current = current[idx];
80
+ } else if (typeof current === "object" && current !== null) {
81
+ current = current[part];
82
+ } else {
83
+ return void 0;
84
+ }
85
+ }
86
+ return current;
87
+ }
88
+ function formatOutput(data, format) {
89
+ if (format === "json5") {
90
+ return JSON.stringify(data, null, 2).replace(/,\s*}/g, "}").replace(/,\s*\]/g, "]");
91
+ }
92
+ if (format === "yaml") {
93
+ return toYaml(data);
94
+ }
95
+ return JSON.stringify(data, null, 2);
96
+ }
97
+ function toYaml(data, indent = 0) {
98
+ if (data === null) return "null\n";
99
+ if (data === void 0) return "";
100
+ if (typeof data === "boolean") return String(data) + "\n";
101
+ if (typeof data === "number") return String(data) + "\n";
102
+ if (typeof data === "string") {
103
+ if (data.includes("\n") || data.includes(":") || data.includes("#")) {
104
+ return `"${data.replace(/\\/g, "\\\\").replace(/"/g, '\\"')}"
105
+ `;
106
+ }
107
+ return data + "\n";
108
+ }
109
+ if (Array.isArray(data)) {
110
+ if (data.length === 0) return "[]\n";
111
+ const prefix = " ".repeat(indent);
112
+ return data.map((item) => `${prefix}- ${toYaml(item, indent + 1).trimStart()}`).join("");
113
+ }
114
+ if (typeof data === "object") {
115
+ const prefix = " ".repeat(indent);
116
+ const entries = Object.entries(data);
117
+ return entries.map(([k, v]) => `${prefix}${k}: ${toYaml(v, indent + 1)}`).join("");
118
+ }
119
+ return String(data) + "\n";
120
+ }
121
+
122
+ export { jsonTool };
123
+ //# sourceMappingURL=json.js.map
124
+ //# sourceMappingURL=json.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/json.ts"],"names":[],"mappings":"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","file":"json.js","sourcesContent":["import * as fs from 'node:fs/promises';\nimport type { Tool } from '@wrongstack/core';\nimport { safeResolve } from './_util.js';\n\ninterface JsonInput {\n file?: string;\n data?: string;\n query?: string;\n format?: 'json' | 'json5' | 'yaml';\n validate?: boolean;\n}\n\ninterface JsonOutput {\n data: unknown;\n formatted: string;\n type: string;\n keys?: string[];\n query_result?: unknown;\n error?: string;\n}\n\nexport const jsonTool: Tool<JsonInput, JsonOutput> = {\n name: 'json',\n description:\n 'Parse, query, and validate JSON/JSON5/YAML. Use `query` with JMESPath-like paths to extract values.',\n usageHint:\n 'Provide `file` path or `data` string. `query` supports dot notation (e.g. \"results[0].name\"). `format` outputs in specified format.',\n permission: 'auto',\n mutating: false,\n timeoutMs: 5_000,\n inputSchema: {\n type: 'object',\n properties: {\n file: { type: 'string', description: 'Path to JSON/JSON5/YAML file' },\n data: { type: 'string', description: 'JSON/JSON5/YAML string (alternative to file)' },\n query: { type: 'string', description: 'JMESPath-like query (e.g. \"a.b[0].c\" or \"a[*].name\")' },\n format: {\n type: 'string',\n enum: ['json', 'json5', 'yaml'],\n description: 'Output format (default: json)',\n },\n validate: {\n type: 'boolean',\n description: 'Validate syntax only, no output (default: false)',\n },\n },\n },\n async execute(input) {\n const format = input.format ?? 'json';\n\n let parsed: unknown;\n let raw: string;\n\n if (input.file) {\n try {\n raw = await fs.readFile(input.file, 'utf8');\n } catch {\n return { data: null, formatted: '', type: 'unknown', error: `Could not read file` };\n }\n } else if (input.data) {\n raw = input.data;\n } else {\n return { data: null, formatted: '', type: 'unknown', error: 'Provide file or data' };\n }\n\n try {\n parsed = JSON.parse(raw);\n } catch (e) {\n return {\n data: null,\n formatted: '',\n type: 'unknown',\n error: `Parse failed: ${e instanceof Error ? e.message : String(e)}`,\n };\n }\n\n if (input.validate) {\n return {\n data: parsed,\n formatted: 'valid',\n type: Array.isArray(parsed) ? 'array' : typeof parsed,\n keys: typeof parsed === 'object' && parsed !== null ? Object.keys(parsed as object) : undefined,\n };\n }\n\n const queryResult = input.query ? query(parsed, input.query) : undefined;\n const formatted = formatOutput(queryResult ?? parsed, format);\n\n return {\n data: parsed,\n formatted,\n type: Array.isArray(parsed) ? 'array' : typeof parsed,\n keys: typeof parsed === 'object' && parsed !== null ? Object.keys(parsed as object) : undefined,\n query_result: queryResult,\n };\n },\n};\n\nfunction query(data: unknown, path: string): unknown {\n const parts = path.replace(/\\[(\\d+)\\]/g, '.$1').split('.').filter(Boolean);\n let current: unknown = data;\n\n for (const part of parts) {\n if (current === null || current === undefined) return undefined;\n\n const idx = Number(part);\n if (!Number.isNaN(idx) && Array.isArray(current)) {\n current = current[idx];\n } else if (typeof current === 'object' && current !== null) {\n current = (current as Record<string, unknown>)[part];\n } else {\n return undefined;\n }\n }\n\n return current;\n}\n\nfunction formatOutput(data: unknown, format: string): string {\n if (format === 'json5') {\n return JSON.stringify(data, null, 2)\n .replace(/,\\s*}/g, '}')\n .replace(/,\\s*\\]/g, ']');\n }\n if (format === 'yaml') {\n return toYaml(data);\n }\n return JSON.stringify(data, null, 2);\n}\n\nfunction toYaml(data: unknown, indent = 0): string {\n if (data === null) return 'null\\n';\n if (data === undefined) return '';\n if (typeof data === 'boolean') return String(data) + '\\n';\n if (typeof data === 'number') return String(data) + '\\n';\n if (typeof data === 'string') {\n if (data.includes('\\n') || data.includes(':') || data.includes('#')) {\n return `\"${data.replace(/\\\\/g, '\\\\\\\\').replace(/\"/g, '\\\\\"')}\"\\n`;\n }\n return data + '\\n';\n }\n if (Array.isArray(data)) {\n if (data.length === 0) return '[]\\n';\n const prefix = ' '.repeat(indent);\n return data.map((item) => `${prefix}- ${toYaml(item, indent + 1).trimStart()}`).join('');\n }\n if (typeof data === 'object') {\n const prefix = ' '.repeat(indent);\n const entries = Object.entries(data as Record<string, unknown>);\n return entries.map(([k, v]) => `${prefix}${k}: ${toYaml(v, indent + 1)}`).join('');\n }\n return String(data) + '\\n';\n}"]}
package/dist/lint.d.ts ADDED
@@ -0,0 +1,20 @@
1
+ import { Tool } from '@wrongstack/core';
2
+
3
+ interface LintInput {
4
+ files?: string | string[];
5
+ fix?: boolean;
6
+ linter?: 'biome' | 'eslint' | 'tslint' | 'auto';
7
+ cwd?: string;
8
+ }
9
+ interface LintOutput {
10
+ linter: string;
11
+ files_checked: number;
12
+ errors: number;
13
+ warnings: number;
14
+ output: string;
15
+ fix_applied: boolean;
16
+ truncated: boolean;
17
+ }
18
+ declare const lintTool: Tool<LintInput, LintOutput>;
19
+
20
+ export { lintTool };
package/dist/lint.js ADDED
@@ -0,0 +1,191 @@
1
+ import * as path from 'path';
2
+ import { spawn } from 'child_process';
3
+
4
+ // src/_util.ts
5
+ function resolvePath(input, ctx) {
6
+ return path.isAbsolute(input) ? path.normalize(input) : path.resolve(ctx.cwd, input);
7
+ }
8
+ function ensureInsideRoot(absPath, ctx) {
9
+ const root = path.resolve(ctx.projectRoot);
10
+ const target = path.resolve(absPath);
11
+ const rel = path.relative(root, target);
12
+ if (rel.startsWith("..") || path.isAbsolute(rel)) {
13
+ throw new Error(`Path "${absPath}" is outside project root "${root}"`);
14
+ }
15
+ return target;
16
+ }
17
+ function safeResolve(input, ctx) {
18
+ return ensureInsideRoot(resolvePath(input, ctx), ctx);
19
+ }
20
+ async function* spawnStream(opts) {
21
+ const max = opts.maxBytes;
22
+ const flushAt = opts.flushBytes ?? 4 * 1024;
23
+ let stdout = "";
24
+ let stderr = "";
25
+ let pending = "";
26
+ let error;
27
+ const child = spawn(opts.cmd, opts.args, {
28
+ cwd: opts.cwd,
29
+ signal: opts.signal,
30
+ stdio: ["ignore", "pipe", "pipe"]
31
+ });
32
+ const queue = [];
33
+ let waiter;
34
+ const wake = () => {
35
+ if (waiter) {
36
+ const w = waiter;
37
+ waiter = void 0;
38
+ w();
39
+ }
40
+ };
41
+ child.stdout?.on("data", (c) => {
42
+ const s = c.toString();
43
+ if (stdout.length < max) stdout += s;
44
+ queue.push({ kind: "out", data: s });
45
+ wake();
46
+ });
47
+ child.stderr?.on("data", (c) => {
48
+ const s = c.toString();
49
+ if (stderr.length < max) stderr += s;
50
+ queue.push({ kind: "err", data: s });
51
+ wake();
52
+ });
53
+ child.on("error", (e) => {
54
+ error = e.message;
55
+ queue.push({ kind: "error", data: e.message });
56
+ wake();
57
+ });
58
+ child.on("close", (code) => {
59
+ queue.push({ kind: "close", data: "", code: code ?? 0 });
60
+ wake();
61
+ });
62
+ let exitCode = 0;
63
+ let spawnFailed = false;
64
+ for (; ; ) {
65
+ while (queue.length === 0) {
66
+ await new Promise((resolve2) => {
67
+ waiter = resolve2;
68
+ });
69
+ }
70
+ const chunk = queue.shift();
71
+ if (chunk.kind === "close") {
72
+ if (!spawnFailed) exitCode = chunk.code ?? 0;
73
+ break;
74
+ }
75
+ if (chunk.kind === "error") {
76
+ spawnFailed = true;
77
+ exitCode = 1;
78
+ continue;
79
+ }
80
+ pending += chunk.data;
81
+ if (pending.length >= flushAt) {
82
+ yield { type: "partial_output", text: pending };
83
+ pending = "";
84
+ }
85
+ }
86
+ if (pending.length > 0) {
87
+ yield { type: "partial_output", text: pending };
88
+ }
89
+ return {
90
+ stdout,
91
+ stderr,
92
+ exitCode,
93
+ truncated: stdout.length >= max || stderr.length >= max,
94
+ error
95
+ };
96
+ }
97
+
98
+ // src/lint.ts
99
+ var lintTool = {
100
+ name: "lint",
101
+ description: "Run a linter on files. Auto-detects biome, eslint, or tslint. Use `fix` to auto-fix issues.",
102
+ usageHint: "Set `files` (glob or comma-separated). `fix` applies corrections. `linter` forces specific tool.",
103
+ permission: "confirm",
104
+ mutating: false,
105
+ timeoutMs: 6e4,
106
+ inputSchema: {
107
+ type: "object",
108
+ properties: {
109
+ files: {
110
+ type: "string",
111
+ description: 'Files/patterns: single path, comma-separated list, or glob (e.g. "src/**/*.ts")'
112
+ },
113
+ fix: { type: "boolean", description: "Auto-fix fixable issues (default: false)" },
114
+ linter: {
115
+ type: "string",
116
+ enum: ["biome", "eslint", "tslint", "auto"],
117
+ description: "Linter to use (default: auto-detect)"
118
+ },
119
+ cwd: { type: "string", description: "Working directory (default: cwd)" }
120
+ }
121
+ },
122
+ async execute(input, ctx, opts) {
123
+ let final;
124
+ for await (const ev of lintTool.executeStream(input, ctx, opts)) {
125
+ if (ev.type === "final") final = ev.output;
126
+ }
127
+ if (!final) throw new Error("lint: stream ended without final event");
128
+ return final;
129
+ },
130
+ async *executeStream(input, ctx, opts) {
131
+ const cwd = input.cwd ? safeResolve(input.cwd, ctx) : ctx.cwd;
132
+ const linter = input.linter ?? "auto";
133
+ const detected = linter === "auto" ? await detectLinter(cwd) : linter;
134
+ if (!detected) {
135
+ yield {
136
+ type: "final",
137
+ output: {
138
+ linter: "none",
139
+ files_checked: 0,
140
+ errors: 0,
141
+ warnings: 0,
142
+ output: "No linter found (biome.json, .eslintrc, tslint.json)",
143
+ fix_applied: false,
144
+ truncated: false
145
+ }
146
+ };
147
+ return;
148
+ }
149
+ yield { type: "log", text: `Running ${detected}\u2026`, data: { linter: detected } };
150
+ const args = ["lint"];
151
+ if (input.fix) args.push("--write");
152
+ if (input.files) {
153
+ const files = Array.isArray(input.files) ? input.files : input.files.split(",");
154
+ args.push("--", ...files.map((f) => f.trim()));
155
+ }
156
+ const cmd = detected === "biome" ? "biome" : detected;
157
+ const result = yield* spawnStream({ cmd, args, cwd, signal: opts.signal, maxBytes: 1e5 });
158
+ const errors = (result.stdout.match(/error/g) || []).length;
159
+ const warnings = (result.stdout.match(/warning/g) || []).length;
160
+ yield {
161
+ type: "final",
162
+ output: {
163
+ linter: detected,
164
+ files_checked: input.files ? Array.isArray(input.files) ? input.files.length : input.files.split(",").length : 0,
165
+ errors,
166
+ warnings,
167
+ output: result.stdout,
168
+ fix_applied: input.fix ?? false,
169
+ truncated: result.truncated
170
+ }
171
+ };
172
+ }
173
+ };
174
+ async function detectLinter(cwd) {
175
+ const { stat } = await import('fs/promises');
176
+ const checks = ["biome.json", ".eslintrc.json", "tslint.json", ".eslintrc.js", "tsconfig.json"];
177
+ for (const f of checks) {
178
+ try {
179
+ await stat(`${cwd}/${f}`);
180
+ if (f.includes("biome")) return "biome";
181
+ if (f.includes("eslint")) return "eslint";
182
+ if (f.includes("tslint")) return "tslint";
183
+ } catch {
184
+ }
185
+ }
186
+ return "biome";
187
+ }
188
+
189
+ export { lintTool };
190
+ //# sourceMappingURL=lint.js.map
191
+ //# sourceMappingURL=lint.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/_util.ts","../src/lint.ts"],"names":["resolve"],"mappings":"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","file":"lint.js","sourcesContent":["import * as path from 'node:path';\r\nimport { spawn } from 'node:child_process';\r\nimport type { Context, ToolProgressEvent } from '@wrongstack/core';\r\n\r\nexport function resolvePath(input: string, ctx: Context): string {\r\n return path.isAbsolute(input) ? path.normalize(input) : path.resolve(ctx.cwd, input);\r\n}\r\n\r\nexport function ensureInsideRoot(absPath: string, ctx: Context): string {\r\n const root = path.resolve(ctx.projectRoot);\r\n const target = path.resolve(absPath);\r\n const rel = path.relative(root, target);\r\n if (rel.startsWith('..') || path.isAbsolute(rel)) {\r\n throw new Error(`Path \"${absPath}\" is outside project root \"${root}\"`);\r\n }\r\n return target;\r\n}\r\n\r\nexport function safeResolve(input: string, ctx: Context): string {\r\n return ensureInsideRoot(resolvePath(input, ctx), ctx);\r\n}\r\n\r\nexport function truncateMiddle(s: string, max: number): string {\r\n if (Buffer.byteLength(s, 'utf8') <= max) return s;\r\n const half = Math.floor(max / 2);\r\n return (\r\n s.slice(0, half) +\r\n `\\n…[truncated ${Buffer.byteLength(s, 'utf8') - max} bytes from middle]…\\n` +\r\n s.slice(-half)\r\n );\r\n}\r\n\r\nexport function isBinaryBuffer(buf: Buffer): boolean {\r\n const len = Math.min(buf.length, 8192);\r\n for (let i = 0; i < len; i++) {\r\n if (buf[i] === 0) return true;\r\n }\r\n return false;\r\n}\r\n\r\nexport interface SpawnStreamResult {\r\n stdout: string;\r\n stderr: string;\r\n exitCode: number;\r\n truncated: boolean;\r\n error?: string;\r\n}\r\n\r\nexport interface SpawnStreamOptions {\r\n cmd: string;\r\n args: string[];\r\n cwd: string;\r\n signal: AbortSignal;\r\n maxBytes?: number;\r\n /** Bytes of new stdout/stderr to accumulate before yielding a `partial_output` event. */\r\n flushBytes?: number;\r\n}\r\n\r\n/**\r\n * Spawn a child process and yield `partial_output` progress events as\r\n * stdout/stderr arrive (batched by byte threshold), then return the full\r\n * buffered result. Shared between install/lint/format/typecheck/test/audit\r\n * so the TUI live tail sees consistent progress regardless of which tool\r\n * is running.\r\n */\r\nexport async function* spawnStream(\r\n opts: SpawnStreamOptions,\r\n): AsyncGenerator<ToolProgressEvent, SpawnStreamResult> {\r\n const max = opts.maxBytes ?? 200_000;\r\n const flushAt = opts.flushBytes ?? 4 * 1024;\r\n let stdout = '';\r\n let stderr = '';\r\n let pending = '';\r\n let error: string | undefined;\r\n\r\n const child = spawn(opts.cmd, opts.args, {\r\n cwd: opts.cwd,\r\n signal: opts.signal,\r\n stdio: ['ignore', 'pipe', 'pipe'],\r\n });\r\n\r\n type Chunk = { kind: 'out' | 'err' | 'close' | 'error'; data: string; code?: number };\r\n const queue: Chunk[] = [];\r\n let waiter: (() => void) | undefined;\r\n const wake = () => {\r\n if (waiter) {\r\n const w = waiter;\r\n waiter = undefined;\r\n w();\r\n }\r\n };\r\n\r\n child.stdout?.on('data', (c) => {\r\n const s = c.toString();\r\n if (stdout.length < max) stdout += s;\r\n queue.push({ kind: 'out', data: s });\r\n wake();\r\n });\r\n child.stderr?.on('data', (c) => {\r\n const s = c.toString();\r\n if (stderr.length < max) stderr += s;\r\n queue.push({ kind: 'err', data: s });\r\n wake();\r\n });\r\n child.on('error', (e) => {\r\n error = e.message;\r\n queue.push({ kind: 'error', data: e.message });\r\n wake();\r\n });\r\n child.on('close', (code) => {\r\n queue.push({ kind: 'close', data: '', code: code ?? 0 });\r\n wake();\r\n });\r\n\r\n let exitCode = 0;\r\n let spawnFailed = false;\r\n for (;;) {\r\n while (queue.length === 0) {\r\n await new Promise<void>((resolve) => {\r\n waiter = resolve;\r\n });\r\n }\r\n const chunk = queue.shift()!;\r\n if (chunk.kind === 'close') {\r\n // If we already saw a spawn error (ENOENT etc.), keep exitCode=1\r\n // rather than the negative platform code Node fabricates.\r\n if (!spawnFailed) exitCode = chunk.code ?? 0;\r\n break;\r\n }\r\n if (chunk.kind === 'error') {\r\n spawnFailed = true;\r\n exitCode = 1;\r\n // close usually follows\r\n continue;\r\n }\r\n pending += chunk.data;\r\n if (pending.length >= flushAt) {\r\n yield { type: 'partial_output', text: pending };\r\n pending = '';\r\n }\r\n }\r\n if (pending.length > 0) {\r\n yield { type: 'partial_output', text: pending };\r\n }\r\n\r\n return {\r\n stdout,\r\n stderr,\r\n exitCode,\r\n truncated: stdout.length >= max || stderr.length >= max,\r\n error,\r\n };\r\n}\r\n","import type { Tool, ToolStreamEvent } from '@wrongstack/core';\r\nimport { safeResolve, spawnStream } from './_util.js';\r\n\r\ninterface LintInput {\r\n files?: string | string[];\r\n fix?: boolean;\r\n linter?: 'biome' | 'eslint' | 'tslint' | 'auto';\r\n cwd?: string;\r\n}\r\n\r\ninterface LintOutput {\r\n linter: string;\r\n files_checked: number;\r\n errors: number;\r\n warnings: number;\r\n output: string;\r\n fix_applied: boolean;\r\n truncated: boolean;\r\n}\r\n\r\nexport const lintTool: Tool<LintInput, LintOutput> = {\r\n name: 'lint',\r\n description:\r\n 'Run a linter on files. Auto-detects biome, eslint, or tslint. Use `fix` to auto-fix issues.',\r\n usageHint:\r\n 'Set `files` (glob or comma-separated). `fix` applies corrections. `linter` forces specific tool.',\r\n permission: 'confirm',\r\n mutating: false,\r\n timeoutMs: 60_000,\r\n inputSchema: {\r\n type: 'object',\r\n properties: {\r\n files: {\r\n type: 'string',\r\n description: 'Files/patterns: single path, comma-separated list, or glob (e.g. \"src/**/*.ts\")',\r\n },\r\n fix: { type: 'boolean', description: 'Auto-fix fixable issues (default: false)' },\r\n linter: {\r\n type: 'string',\r\n enum: ['biome', 'eslint', 'tslint', 'auto'],\r\n description: 'Linter to use (default: auto-detect)',\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 let final: LintOutput | undefined;\r\n for await (const ev of lintTool.executeStream!(input, ctx, opts)) {\r\n if (ev.type === 'final') final = ev.output;\r\n }\r\n if (!final) throw new Error('lint: stream ended without final event');\r\n return final;\r\n },\r\n async *executeStream(input, ctx, opts): AsyncGenerator<ToolStreamEvent<LintOutput>> {\r\n const cwd = input.cwd ? safeResolve(input.cwd, ctx) : ctx.cwd;\r\n const linter = input.linter ?? 'auto';\r\n\r\n const detected = linter === 'auto' ? await detectLinter(cwd) : linter;\r\n if (!detected) {\r\n yield {\r\n type: 'final',\r\n output: {\r\n linter: 'none',\r\n files_checked: 0,\r\n errors: 0,\r\n warnings: 0,\r\n output: 'No linter found (biome.json, .eslintrc, tslint.json)',\r\n fix_applied: false,\r\n truncated: false,\r\n },\r\n };\r\n return;\r\n }\r\n\r\n yield { type: 'log', text: `Running ${detected}…`, data: { linter: detected } };\r\n\r\n const args: string[] = ['lint'];\r\n if (input.fix) args.push('--write');\r\n if (input.files) {\r\n const files = Array.isArray(input.files) ? input.files : input.files.split(',');\r\n args.push('--', ...files.map((f) => f.trim()));\r\n }\r\n\r\n const cmd = detected === 'biome' ? 'biome' : detected;\r\n const result = yield* spawnStream({ cmd, args, cwd, signal: opts.signal, maxBytes: 100_000 });\r\n\r\n const errors = (result.stdout.match(/error/g) || []).length;\r\n const warnings = (result.stdout.match(/warning/g) || []).length;\r\n\r\n yield {\r\n type: 'final',\r\n output: {\r\n linter: detected,\r\n files_checked: input.files\r\n ? (Array.isArray(input.files) ? input.files.length : input.files.split(',').length)\r\n : 0,\r\n errors,\r\n warnings,\r\n output: result.stdout,\r\n fix_applied: input.fix ?? false,\r\n truncated: result.truncated,\r\n },\r\n };\r\n },\r\n};\r\n\r\nasync function detectLinter(cwd: string): Promise<string | null> {\r\n const { stat } = await import('node:fs/promises');\r\n const checks = ['biome.json', '.eslintrc.json', 'tslint.json', '.eslintrc.js', 'tsconfig.json'];\r\n for (const f of checks) {\r\n try {\r\n await stat(`${cwd}/${f}`);\r\n if (f.includes('biome')) return 'biome';\r\n if (f.includes('eslint')) return 'eslint';\r\n if (f.includes('tslint')) return 'tslint';\r\n } catch {\r\n // continue\r\n }\r\n }\r\n return 'biome';\r\n}\r\n"]}