@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
package/dist/grep.js ADDED
@@ -0,0 +1,264 @@
1
+ import * as fs from 'fs/promises';
2
+ import * as path from 'path';
3
+ import { spawn } from 'child_process';
4
+ import { compileGlob } from '@wrongstack/core';
5
+
6
+ // src/grep.ts
7
+ function resolvePath(input, ctx) {
8
+ return path.isAbsolute(input) ? path.normalize(input) : path.resolve(ctx.cwd, input);
9
+ }
10
+ function ensureInsideRoot(absPath, ctx) {
11
+ const root = path.resolve(ctx.projectRoot);
12
+ const target = path.resolve(absPath);
13
+ const rel = path.relative(root, target);
14
+ if (rel.startsWith("..") || path.isAbsolute(rel)) {
15
+ throw new Error(`Path "${absPath}" is outside project root "${root}"`);
16
+ }
17
+ return target;
18
+ }
19
+ function safeResolve(input, ctx) {
20
+ return ensureInsideRoot(resolvePath(input, ctx), ctx);
21
+ }
22
+ function isBinaryBuffer(buf) {
23
+ const len = Math.min(buf.length, 8192);
24
+ for (let i = 0; i < len; i++) {
25
+ if (buf[i] === 0) return true;
26
+ }
27
+ return false;
28
+ }
29
+
30
+ // src/grep.ts
31
+ var DEFAULT_IGNORE = ["node_modules", ".git", "dist", "build", ".next", "coverage"];
32
+ var grepTool = {
33
+ name: "grep",
34
+ description: "Search file contents with a regex. Uses ripgrep when available.",
35
+ usageHint: 'Pattern is regex. Use `output_mode: "content"` for matched lines, `"files_with_matches"` for paths, `"count"` for tallies. `glob` filters files (e.g. `*.ts`).',
36
+ permission: "auto",
37
+ mutating: false,
38
+ maxOutputBytes: 131072,
39
+ timeoutMs: 1e4,
40
+ inputSchema: {
41
+ type: "object",
42
+ properties: {
43
+ pattern: { type: "string" },
44
+ path: { type: "string" },
45
+ glob: { type: "string" },
46
+ output_mode: { type: "string", enum: ["content", "files_with_matches", "count"] },
47
+ context_lines: { type: "integer" },
48
+ case_insensitive: { type: "boolean" },
49
+ limit: { type: "integer" }
50
+ },
51
+ required: ["pattern"]
52
+ },
53
+ async execute(input, ctx, opts) {
54
+ let final;
55
+ for await (const ev of grepTool.executeStream(input, ctx, opts)) {
56
+ if (ev.type === "final") final = ev.output;
57
+ }
58
+ if (!final) throw new Error("grep: stream ended without final event");
59
+ return final;
60
+ },
61
+ async *executeStream(input, ctx, opts) {
62
+ if (!input?.pattern) throw new Error("grep: pattern is required");
63
+ const base = input.path ? safeResolve(input.path, ctx) : ctx.cwd;
64
+ const mode = input.output_mode ?? "content";
65
+ const limit = Math.max(1, Math.min(input.limit ?? 200, 2e3));
66
+ const rgAvailable = await detectRg(opts.signal);
67
+ if (rgAvailable) {
68
+ try {
69
+ yield* runRgStream(input, base, mode, limit, opts.signal);
70
+ return;
71
+ } catch {
72
+ }
73
+ }
74
+ yield { type: "log", text: "Falling back to native grep\u2026" };
75
+ const out = await runNative(input, base, mode, limit, opts.signal);
76
+ yield { type: "final", output: out };
77
+ }
78
+ };
79
+ async function detectRg(signal) {
80
+ return new Promise((resolve2) => {
81
+ try {
82
+ const p = spawn("rg", ["--version"], { stdio: "ignore", signal });
83
+ p.on("error", () => resolve2(false));
84
+ p.on("close", (code) => resolve2(code === 0));
85
+ } catch {
86
+ resolve2(false);
87
+ }
88
+ });
89
+ }
90
+ async function* runRgStream(input, base, mode, limit, signal) {
91
+ const args = ["--no-heading"];
92
+ if (input.case_insensitive) args.push("-i");
93
+ if (mode === "files_with_matches") args.push("-l");
94
+ if (mode === "count") args.push("-c");
95
+ if (mode === "content") {
96
+ args.push("-n");
97
+ if (input.context_lines) args.push("-C", String(input.context_lines));
98
+ }
99
+ if (input.glob) args.push("--glob", input.glob);
100
+ args.push("--", input.pattern, base);
101
+ const matches = [];
102
+ let buf = "";
103
+ let totalLines = 0;
104
+ let batchSinceFlush = 0;
105
+ const FLUSH_AT = 16;
106
+ const child = spawn("rg", args, { signal, stdio: ["ignore", "pipe", "pipe"] });
107
+ const queue = [];
108
+ let waiter;
109
+ const wake = () => {
110
+ if (waiter) {
111
+ const w = waiter;
112
+ waiter = void 0;
113
+ w();
114
+ }
115
+ };
116
+ child.stdout?.on("data", (c) => {
117
+ queue.push({ kind: "out", data: c.toString() });
118
+ wake();
119
+ });
120
+ child.on("error", (e) => {
121
+ queue.push({ kind: "error", data: e.message });
122
+ wake();
123
+ });
124
+ child.on("close", () => {
125
+ queue.push({ kind: "close", data: "" });
126
+ wake();
127
+ });
128
+ let pendingBatch = [];
129
+ let errored = false;
130
+ for (; ; ) {
131
+ while (queue.length === 0) {
132
+ await new Promise((r) => {
133
+ waiter = r;
134
+ });
135
+ }
136
+ const c = queue.shift();
137
+ if (c.kind === "error") {
138
+ errored = true;
139
+ continue;
140
+ }
141
+ if (c.kind === "close") break;
142
+ buf += c.data;
143
+ const idx = buf.lastIndexOf("\n");
144
+ if (idx === -1) continue;
145
+ const ready = buf.slice(0, idx);
146
+ buf = buf.slice(idx + 1);
147
+ for (const line of ready.split("\n")) {
148
+ if (!line) continue;
149
+ totalLines++;
150
+ if (matches.length < limit) {
151
+ matches.push(line);
152
+ pendingBatch.push(line);
153
+ batchSinceFlush++;
154
+ }
155
+ }
156
+ if (batchSinceFlush >= FLUSH_AT) {
157
+ yield {
158
+ type: "partial_output",
159
+ text: pendingBatch.join("\n"),
160
+ data: { matches_so_far: matches.length }
161
+ };
162
+ pendingBatch = [];
163
+ batchSinceFlush = 0;
164
+ }
165
+ }
166
+ if (buf.trim()) {
167
+ for (const line of buf.split("\n")) {
168
+ if (!line) continue;
169
+ totalLines++;
170
+ if (matches.length < limit) {
171
+ matches.push(line);
172
+ pendingBatch.push(line);
173
+ }
174
+ }
175
+ }
176
+ if (pendingBatch.length > 0) {
177
+ yield {
178
+ type: "partial_output",
179
+ text: pendingBatch.join("\n"),
180
+ data: { matches_so_far: matches.length }
181
+ };
182
+ }
183
+ if (errored) throw new Error("rg: spawn error");
184
+ yield {
185
+ type: "final",
186
+ output: {
187
+ matches,
188
+ count: totalLines,
189
+ truncated: totalLines > limit,
190
+ used: "rg"
191
+ }
192
+ };
193
+ }
194
+ async function runNative(input, base, mode, limit, signal) {
195
+ const flags = input.case_insensitive ? "i" : "";
196
+ const re = new RegExp(input.pattern, flags);
197
+ const globRe = input.glob ? compileGlob(input.glob) : null;
198
+ const matches = [];
199
+ const fileMatches = /* @__PURE__ */ new Map();
200
+ let total = 0;
201
+ let stopped = false;
202
+ const walk = async (dir) => {
203
+ if (stopped || signal.aborted) return;
204
+ let entries;
205
+ try {
206
+ entries = await fs.readdir(dir, { withFileTypes: true });
207
+ } catch {
208
+ return;
209
+ }
210
+ for (const e of entries) {
211
+ if (stopped) return;
212
+ if (DEFAULT_IGNORE.includes(e.name)) continue;
213
+ const full = path.join(dir, e.name);
214
+ if (e.isDirectory()) {
215
+ await walk(full);
216
+ } else if (e.isFile()) {
217
+ if (globRe && !globRe.test(e.name) && !globRe.test(full)) continue;
218
+ if (globRe) globRe.lastIndex = 0;
219
+ try {
220
+ const stat2 = await fs.stat(full);
221
+ if (stat2.size > 1e6) continue;
222
+ const head = await fs.readFile(full);
223
+ if (isBinaryBuffer(head)) continue;
224
+ const text = head.toString("utf8");
225
+ const lines = text.split(/\r?\n/);
226
+ let fileHits = 0;
227
+ for (let i = 0; i < lines.length; i++) {
228
+ const ln = lines[i] ?? "";
229
+ re.lastIndex = 0;
230
+ if (re.test(ln)) {
231
+ fileHits++;
232
+ total++;
233
+ if (mode === "content" && matches.length < limit) {
234
+ matches.push(`${full}:${i + 1}:${ln}`);
235
+ }
236
+ }
237
+ }
238
+ if (fileHits > 0) {
239
+ fileMatches.set(full, fileHits);
240
+ if (mode === "files_with_matches" && matches.length < limit) {
241
+ matches.push(full);
242
+ }
243
+ if (mode === "count" && matches.length < limit) {
244
+ matches.push(`${full}:${fileHits}`);
245
+ }
246
+ }
247
+ if (matches.length >= limit) stopped = true;
248
+ } catch {
249
+ }
250
+ }
251
+ }
252
+ };
253
+ await walk(base);
254
+ return {
255
+ matches,
256
+ count: total,
257
+ truncated: stopped,
258
+ used: "native"
259
+ };
260
+ }
261
+
262
+ export { grepTool };
263
+ //# sourceMappingURL=grep.js.map
264
+ //# sourceMappingURL=grep.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/_util.ts","../src/grep.ts"],"names":["resolve","spawn","path2","stat"],"mappings":"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","file":"grep.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 * as fs from 'node:fs/promises';\r\nimport * as path from 'node:path';\r\nimport { spawn } from 'node:child_process';\r\nimport type { Tool, ToolStreamEvent } from '@wrongstack/core';\r\nimport { compileGlob } from '@wrongstack/core';\r\nimport { isBinaryBuffer, safeResolve } from './_util.js';\r\n\r\ninterface GrepInput {\r\n pattern: string;\r\n path?: string;\r\n glob?: string;\r\n output_mode?: 'content' | 'files_with_matches' | 'count';\r\n context_lines?: number;\r\n case_insensitive?: boolean;\r\n limit?: number;\r\n}\r\n\r\ninterface GrepOutput {\r\n matches: string[];\r\n count: number;\r\n truncated: boolean;\r\n used: 'rg' | 'native';\r\n}\r\n\r\nconst DEFAULT_IGNORE = ['node_modules', '.git', 'dist', 'build', '.next', 'coverage'];\r\n\r\nexport const grepTool: Tool<GrepInput, GrepOutput> = {\r\n name: 'grep',\r\n description: 'Search file contents with a regex. Uses ripgrep when available.',\r\n usageHint:\r\n 'Pattern is regex. Use `output_mode: \"content\"` for matched lines, `\"files_with_matches\"` for paths, `\"count\"` for tallies. `glob` filters files (e.g. `*.ts`).',\r\n permission: 'auto',\r\n mutating: false,\r\n maxOutputBytes: 131_072,\r\n timeoutMs: 10_000,\r\n inputSchema: {\r\n type: 'object',\r\n properties: {\r\n pattern: { type: 'string' },\r\n path: { type: 'string' },\r\n glob: { type: 'string' },\r\n output_mode: { type: 'string', enum: ['content', 'files_with_matches', 'count'] },\r\n context_lines: { type: 'integer' },\r\n case_insensitive: { type: 'boolean' },\r\n limit: { type: 'integer' },\r\n },\r\n required: ['pattern'],\r\n },\r\n async execute(input, ctx, opts) {\r\n let final: GrepOutput | undefined;\r\n for await (const ev of grepTool.executeStream!(input, ctx, opts)) {\r\n if (ev.type === 'final') final = ev.output;\r\n }\r\n if (!final) throw new Error('grep: stream ended without final event');\r\n return final;\r\n },\r\n async *executeStream(input, ctx, opts): AsyncGenerator<ToolStreamEvent<GrepOutput>> {\r\n if (!input?.pattern) throw new Error('grep: pattern is required');\r\n const base = input.path ? safeResolve(input.path, ctx) : ctx.cwd;\r\n const mode = input.output_mode ?? 'content';\r\n const limit = Math.max(1, Math.min(input.limit ?? 200, 2000));\r\n\r\n const rgAvailable = await detectRg(opts.signal);\r\n if (rgAvailable) {\r\n try {\r\n yield* runRgStream(input, base, mode, limit, opts.signal);\r\n return;\r\n } catch {\r\n // fall through to native\r\n }\r\n }\r\n yield { type: 'log', text: 'Falling back to native grep…' };\r\n const out = await runNative(input, base, mode, limit, opts.signal);\r\n yield { type: 'final', output: out };\r\n },\r\n};\r\n\r\nasync function detectRg(signal: AbortSignal): Promise<boolean> {\r\n return new Promise((resolve) => {\r\n try {\r\n const p = spawn('rg', ['--version'], { stdio: 'ignore', signal });\r\n p.on('error', () => resolve(false));\r\n p.on('close', (code) => resolve(code === 0));\r\n } catch {\r\n resolve(false);\r\n }\r\n });\r\n}\r\n\r\nasync function* runRgStream(\r\n input: GrepInput,\r\n base: string,\r\n mode: 'content' | 'files_with_matches' | 'count',\r\n limit: number,\r\n signal: AbortSignal,\r\n): AsyncGenerator<ToolStreamEvent<GrepOutput>> {\r\n const args: string[] = ['--no-heading'];\r\n if (input.case_insensitive) args.push('-i');\r\n if (mode === 'files_with_matches') args.push('-l');\r\n if (mode === 'count') args.push('-c');\r\n if (mode === 'content') {\r\n args.push('-n');\r\n if (input.context_lines) args.push('-C', String(input.context_lines));\r\n }\r\n if (input.glob) args.push('--glob', input.glob);\r\n args.push('--', input.pattern, base);\r\n\r\n const matches: string[] = [];\r\n let buf = '';\r\n let totalLines = 0;\r\n let batchSinceFlush = 0;\r\n const FLUSH_AT = 16; // yield a partial_output every 16 matches\r\n\r\n const child = spawn('rg', args, { signal, stdio: ['ignore', 'pipe', 'pipe'] });\r\n\r\n type Chunk = { kind: 'out' | 'close' | 'error'; data: string };\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 child.stdout?.on('data', (c) => {\r\n queue.push({ kind: 'out', data: c.toString() });\r\n wake();\r\n });\r\n child.on('error', (e) => {\r\n queue.push({ kind: 'error', data: e.message });\r\n wake();\r\n });\r\n child.on('close', () => {\r\n queue.push({ kind: 'close', data: '' });\r\n wake();\r\n });\r\n\r\n let pendingBatch: string[] = [];\r\n let errored = false;\r\n for (;;) {\r\n while (queue.length === 0) {\r\n await new Promise<void>((r) => {\r\n waiter = r;\r\n });\r\n }\r\n const c = queue.shift()!;\r\n if (c.kind === 'error') {\r\n errored = true;\r\n continue;\r\n }\r\n if (c.kind === 'close') break;\r\n buf += c.data;\r\n const idx = buf.lastIndexOf('\\n');\r\n if (idx === -1) continue;\r\n const ready = buf.slice(0, idx);\r\n buf = buf.slice(idx + 1);\r\n for (const line of ready.split('\\n')) {\r\n if (!line) continue;\r\n totalLines++;\r\n if (matches.length < limit) {\r\n matches.push(line);\r\n pendingBatch.push(line);\r\n batchSinceFlush++;\r\n }\r\n }\r\n if (batchSinceFlush >= FLUSH_AT) {\r\n yield {\r\n type: 'partial_output',\r\n text: pendingBatch.join('\\n'),\r\n data: { matches_so_far: matches.length },\r\n };\r\n pendingBatch = [];\r\n batchSinceFlush = 0;\r\n }\r\n }\r\n\r\n if (buf.trim()) {\r\n for (const line of buf.split('\\n')) {\r\n if (!line) continue;\r\n totalLines++;\r\n if (matches.length < limit) {\r\n matches.push(line);\r\n pendingBatch.push(line);\r\n }\r\n }\r\n }\r\n if (pendingBatch.length > 0) {\r\n yield {\r\n type: 'partial_output',\r\n text: pendingBatch.join('\\n'),\r\n data: { matches_so_far: matches.length },\r\n };\r\n }\r\n if (errored) throw new Error('rg: spawn error');\r\n\r\n yield {\r\n type: 'final',\r\n output: {\r\n matches,\r\n count: totalLines,\r\n truncated: totalLines > limit,\r\n used: 'rg',\r\n },\r\n };\r\n}\r\n\r\nasync function runNative(\r\n input: GrepInput,\r\n base: string,\r\n mode: 'content' | 'files_with_matches' | 'count',\r\n limit: number,\r\n signal: AbortSignal,\r\n): Promise<GrepOutput> {\r\n const flags = input.case_insensitive ? 'i' : '';\r\n const re = new RegExp(input.pattern, flags);\r\n const globRe = input.glob ? compileGlob(input.glob) : null;\r\n const matches: string[] = [];\r\n const fileMatches = new Map<string, number>();\r\n let total = 0;\r\n let stopped = false;\r\n\r\n const walk = async (dir: string): Promise<void> => {\r\n if (stopped || signal.aborted) return;\r\n let entries: import('node:fs').Dirent[];\r\n try {\r\n entries = await fs.readdir(dir, { withFileTypes: true });\r\n } catch {\r\n return;\r\n }\r\n for (const e of entries) {\r\n if (stopped) return;\r\n if (DEFAULT_IGNORE.includes(e.name)) continue;\r\n const full = path.join(dir, e.name);\r\n if (e.isDirectory()) {\r\n await walk(full);\r\n } else if (e.isFile()) {\r\n if (globRe && !globRe.test(e.name) && !globRe.test(full)) continue;\r\n if (globRe) globRe.lastIndex = 0;\r\n try {\r\n const stat = await fs.stat(full);\r\n if (stat.size > 1_000_000) continue;\r\n const head = await fs.readFile(full);\r\n if (isBinaryBuffer(head)) continue;\r\n const text = head.toString('utf8');\r\n const lines = text.split(/\\r?\\n/);\r\n let fileHits = 0;\r\n for (let i = 0; i < lines.length; i++) {\r\n const ln = lines[i] ?? '';\r\n re.lastIndex = 0;\r\n if (re.test(ln)) {\r\n fileHits++;\r\n total++;\r\n if (mode === 'content' && matches.length < limit) {\r\n matches.push(`${full}:${i + 1}:${ln}`);\r\n }\r\n }\r\n }\r\n if (fileHits > 0) {\r\n fileMatches.set(full, fileHits);\r\n if (mode === 'files_with_matches' && matches.length < limit) {\r\n matches.push(full);\r\n }\r\n if (mode === 'count' && matches.length < limit) {\r\n matches.push(`${full}:${fileHits}`);\r\n }\r\n }\r\n if (matches.length >= limit) stopped = true;\r\n } catch {\r\n // skip read errors\r\n }\r\n }\r\n }\r\n };\r\n await walk(base);\r\n\r\n return {\r\n matches,\r\n count: total,\r\n truncated: stopped,\r\n used: 'native',\r\n };\r\n}\r\n"]}