@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.
- package/dist/audit.d.ts +25 -0
- package/dist/audit.js +209 -0
- package/dist/audit.js.map +1 -0
- package/dist/bash.d.ts +16 -0
- package/dist/bash.js +180 -0
- package/dist/bash.js.map +1 -0
- package/dist/batch-tool-use.d.ts +26 -0
- package/dist/batch-tool-use.js +106 -0
- package/dist/batch-tool-use.js.map +1 -0
- package/dist/builtin.d.ts +5 -0
- package/dist/builtin.js +3735 -0
- package/dist/builtin.js.map +1 -0
- package/dist/diff.d.ts +20 -0
- package/dist/diff.js +142 -0
- package/dist/diff.js.map +1 -0
- package/dist/document.d.ts +27 -0
- package/dist/document.js +148 -0
- package/dist/document.js.map +1 -0
- package/dist/edit.d.ts +22 -0
- package/dist/edit.js +138 -0
- package/dist/edit.js.map +1 -0
- package/dist/exec.d.ts +21 -0
- package/dist/exec.js +159 -0
- package/dist/exec.js.map +1 -0
- package/dist/fetch.d.ts +15 -0
- package/dist/fetch.js +213 -0
- package/dist/fetch.js.map +1 -0
- package/dist/format.d.ts +18 -0
- package/dist/format.js +194 -0
- package/dist/format.js.map +1 -0
- package/dist/git.d.ts +27 -0
- package/dist/git.js +174 -0
- package/dist/git.js.map +1 -0
- package/dist/glob.d.ts +14 -0
- package/dist/glob.js +101 -0
- package/dist/glob.js.map +1 -0
- package/dist/grep.d.ts +20 -0
- package/dist/grep.js +264 -0
- package/dist/grep.js.map +1 -0
- package/dist/index.d.ts +34 -563
- package/dist/index.js +717 -442
- package/dist/index.js.map +1 -1
- package/dist/install.d.ts +19 -0
- package/dist/install.js +186 -0
- package/dist/install.js.map +1 -0
- package/dist/json.d.ts +20 -0
- package/dist/json.js +124 -0
- package/dist/json.js.map +1 -0
- package/dist/lint.d.ts +20 -0
- package/dist/lint.js +191 -0
- package/dist/lint.js.map +1 -0
- package/dist/logs.d.ts +27 -0
- package/dist/logs.js +180 -0
- package/dist/logs.js.map +1 -0
- package/dist/memory.d.ts +22 -0
- package/dist/memory.js +53 -0
- package/dist/memory.js.map +1 -0
- package/dist/mode.d.ts +20 -0
- package/dist/mode.js +81 -0
- package/dist/mode.js.map +1 -0
- package/dist/outdated.d.ts +26 -0
- package/dist/outdated.js +138 -0
- package/dist/outdated.js.map +1 -0
- package/dist/patch.d.ts +18 -0
- package/dist/patch.js +101 -0
- package/dist/patch.js.map +1 -0
- package/dist/read.d.ts +16 -0
- package/dist/read.js +81 -0
- package/dist/read.js.map +1 -0
- package/dist/replace.d.ts +23 -0
- package/dist/replace.js +196 -0
- package/dist/replace.js.map +1 -0
- package/dist/scaffold.d.ts +20 -0
- package/dist/scaffold.js +185 -0
- package/dist/scaffold.js.map +1 -0
- package/dist/search.d.ts +20 -0
- package/dist/search.js +212 -0
- package/dist/search.js.map +1 -0
- package/dist/test.d.ts +24 -0
- package/dist/test.js +247 -0
- package/dist/test.js.map +1 -0
- package/dist/todo.d.ts +12 -0
- package/dist/todo.js +53 -0
- package/dist/todo.js.map +1 -0
- package/dist/tool-help.d.ts +23 -0
- package/dist/tool-help.js +122 -0
- package/dist/tool-help.js.map +1 -0
- package/dist/tool-search.d.ts +22 -0
- package/dist/tool-search.js +70 -0
- package/dist/tool-search.js.map +1 -0
- package/dist/tool-use.d.ts +16 -0
- package/dist/tool-use.js +79 -0
- package/dist/tool-use.js.map +1 -0
- package/dist/tree.d.ts +21 -0
- package/dist/tree.js +176 -0
- package/dist/tree.js.map +1 -0
- package/dist/typecheck.d.ts +19 -0
- package/dist/typecheck.js +181 -0
- package/dist/typecheck.js.map +1 -0
- package/dist/write.d.ts +15 -0
- package/dist/write.js +77 -0
- package/dist/write.js.map +1 -0
- 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 };
|
package/dist/install.js
ADDED
|
@@ -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
|
package/dist/json.js.map
ADDED
|
@@ -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
|
package/dist/lint.js.map
ADDED
|
@@ -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"]}
|