@bastani/atomic 0.6.3 → 0.6.4
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/.agents/skills/ast-grep/SKILL.md +323 -0
- package/.agents/skills/ast-grep/references/rule_reference.md +297 -0
- package/.agents/skills/ripgrep/SKILL.md +382 -0
- package/.mcp.json +5 -6
- package/dist/commands/cli/claude-inflight-hook.d.ts +100 -0
- package/dist/commands/cli/claude-inflight-hook.d.ts.map +1 -0
- package/dist/commands/cli/claude-stop-hook.d.ts +2 -0
- package/dist/commands/cli/claude-stop-hook.d.ts.map +1 -1
- package/dist/lib/spawn.d.ts +1 -1
- package/dist/lib/spawn.d.ts.map +1 -1
- package/dist/sdk/providers/claude.d.ts +36 -0
- package/dist/sdk/providers/claude.d.ts.map +1 -1
- package/dist/sdk/providers/copilot.d.ts +17 -1
- package/dist/sdk/providers/copilot.d.ts.map +1 -1
- package/dist/sdk/runtime/executor.d.ts.map +1 -1
- package/dist/sdk/workflows/builtin/deep-research-codebase/claude/index.d.ts +49 -34
- package/dist/sdk/workflows/builtin/deep-research-codebase/claude/index.d.ts.map +1 -1
- package/dist/sdk/workflows/builtin/deep-research-codebase/copilot/index.d.ts +18 -16
- package/dist/sdk/workflows/builtin/deep-research-codebase/copilot/index.d.ts.map +1 -1
- package/dist/sdk/workflows/builtin/deep-research-codebase/helpers/batching.d.ts +43 -0
- package/dist/sdk/workflows/builtin/deep-research-codebase/helpers/batching.d.ts.map +1 -0
- package/dist/sdk/workflows/builtin/deep-research-codebase/helpers/prompts.d.ts +30 -0
- package/dist/sdk/workflows/builtin/deep-research-codebase/helpers/prompts.d.ts.map +1 -1
- package/dist/sdk/workflows/builtin/deep-research-codebase/helpers/scout.d.ts +2 -1
- package/dist/sdk/workflows/builtin/deep-research-codebase/helpers/scout.d.ts.map +1 -1
- package/dist/sdk/workflows/builtin/deep-research-codebase/opencode/index.d.ts +18 -16
- package/dist/sdk/workflows/builtin/deep-research-codebase/opencode/index.d.ts.map +1 -1
- package/dist/services/config/additional-instructions.d.ts +67 -0
- package/dist/services/config/additional-instructions.d.ts.map +1 -0
- package/package.json +3 -1
- package/src/cli.ts +18 -1
- package/src/commands/cli/chat/index.ts +52 -2
- package/src/commands/cli/claude-inflight-hook.test.ts +598 -0
- package/src/commands/cli/claude-inflight-hook.ts +359 -0
- package/src/commands/cli/claude-stop-hook.ts +40 -4
- package/src/commands/cli/init/index.ts +9 -0
- package/src/lib/spawn.ts +6 -2
- package/src/sdk/providers/claude.ts +131 -0
- package/src/sdk/providers/copilot.ts +30 -1
- package/src/sdk/runtime/executor.ts +43 -2
- package/src/sdk/workflows/builtin/deep-research-codebase/claude/index.ts +318 -158
- package/src/sdk/workflows/builtin/deep-research-codebase/copilot/index.ts +253 -129
- package/src/sdk/workflows/builtin/deep-research-codebase/helpers/batching.ts +65 -0
- package/src/sdk/workflows/builtin/deep-research-codebase/helpers/ignore-by-default.d.ts +8 -0
- package/src/sdk/workflows/builtin/deep-research-codebase/helpers/prompts.ts +203 -12
- package/src/sdk/workflows/builtin/deep-research-codebase/helpers/scout.ts +248 -78
- package/src/sdk/workflows/builtin/deep-research-codebase/opencode/index.ts +258 -146
- package/src/services/config/additional-instructions.ts +273 -0
- package/src/services/system/auto-sync.ts +10 -1
|
@@ -1,34 +1,36 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* deep-research-codebase / copilot
|
|
3
3
|
*
|
|
4
|
-
* Copilot replica of the Claude deep-research-codebase workflow
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
4
|
+
* Copilot replica of the Claude deep-research-codebase workflow with the
|
|
5
|
+
* same **batched** Task-tool fan-out. Specialist sub-agents run inside batch
|
|
6
|
+
* sessions: each batch is a single `ctx.stage()` whose orchestrator turn
|
|
7
|
+
* dispatches up to MAX_TASKS_PER_BATCH (≈10) specialists in parallel via
|
|
8
|
+
* Copilot's `agent` tool (alias `Task`, see Copilot subagents docs).
|
|
9
|
+
* Research-history specialists remain as their own sequential sub-pipeline.
|
|
8
10
|
*
|
|
9
|
-
*
|
|
11
|
+
* See claude/index.ts for the full design rationale and topology diagram.
|
|
10
12
|
*
|
|
11
|
-
*
|
|
12
|
-
* everything it needs (research question, scope, scout overview, and —
|
|
13
|
-
* for layer-2 specialists — verbatim locator output) in its first prompt.
|
|
13
|
+
* Copilot-specific concerns baked in (see references/failure-modes.md):
|
|
14
14
|
*
|
|
15
15
|
* • F1 — Copilot's last assistant turn is often empty when the agent ends
|
|
16
|
-
* on a tool call.
|
|
17
|
-
*
|
|
18
|
-
*
|
|
16
|
+
* on a tool call. Batch sessions don't read `getAssistantText` for the
|
|
17
|
+
* orchestrator output (sub-agents write to disk; the orchestrator's text
|
|
18
|
+
* reply is just a short tally), so this is no longer load-bearing for
|
|
19
|
+
* Stage 2 — but the history pipeline still depends on it.
|
|
19
20
|
*
|
|
20
|
-
* •
|
|
21
|
-
*
|
|
22
|
-
*
|
|
21
|
+
* • F5 — every `ctx.stage()` is a FRESH session. Batch session prompts
|
|
22
|
+
* embed everything the orchestrator needs (per-task subagent_type,
|
|
23
|
+
* output path, and verbatim specialist prompt) in the first turn.
|
|
23
24
|
*
|
|
24
|
-
* •
|
|
25
|
+
* • F6 — orchestrator prompt requires a single-line tally as the trailing
|
|
26
|
+
* turn so transcripts are never empty.
|
|
25
27
|
*
|
|
26
|
-
*
|
|
28
|
+
* • F9 — `s.save()` receives `SessionEvent[]` from `s.session.getMessages()`.
|
|
27
29
|
*/
|
|
28
30
|
|
|
29
31
|
import { defineWorkflow } from "../../../index.ts";
|
|
30
32
|
import type { SessionEvent } from "@github/copilot-sdk";
|
|
31
|
-
import { mkdir } from "node:fs/promises";
|
|
33
|
+
import { mkdir, readFile } from "node:fs/promises";
|
|
32
34
|
import path from "node:path";
|
|
33
35
|
|
|
34
36
|
import {
|
|
@@ -43,6 +45,7 @@ import {
|
|
|
43
45
|
import {
|
|
44
46
|
buildAggregatorPrompt,
|
|
45
47
|
buildAnalyzerPrompt,
|
|
48
|
+
buildBatchOrchestratorPrompt,
|
|
46
49
|
buildHistoryAnalyzerPrompt,
|
|
47
50
|
buildHistoryLocatorPrompt,
|
|
48
51
|
buildLocatorPrompt,
|
|
@@ -50,13 +53,23 @@ import {
|
|
|
50
53
|
buildPatternFinderPrompt,
|
|
51
54
|
buildScoutPrompt,
|
|
52
55
|
slugifyPrompt,
|
|
56
|
+
wrapPromptForTaskDispatch,
|
|
53
57
|
} from "../helpers/prompts.ts";
|
|
54
58
|
import { writeExplorerScratchFile } from "../helpers/scratch.ts";
|
|
59
|
+
import {
|
|
60
|
+
chunkBatches,
|
|
61
|
+
MAX_TASKS_PER_BATCH,
|
|
62
|
+
SUBAGENT_TYPE,
|
|
63
|
+
type Layer1Task,
|
|
64
|
+
type Layer2Task,
|
|
65
|
+
} from "../helpers/batching.ts";
|
|
55
66
|
|
|
56
67
|
/**
|
|
57
68
|
* Concatenate every top-level assistant turn's non-empty content. The final
|
|
58
69
|
* `assistant.message` of a Copilot turn is often empty when the agent ends
|
|
59
70
|
* on a tool call (F1), and sub-agent traffic is signalled by `parentToolCallId`.
|
|
71
|
+
* Used for the history pipeline only — batch sessions don't need this since
|
|
72
|
+
* sub-agents write to disk.
|
|
60
73
|
*/
|
|
61
74
|
function getAssistantText(messages: SessionEvent[]): string {
|
|
62
75
|
return messages
|
|
@@ -69,6 +82,31 @@ function getAssistantText(messages: SessionEvent[]): string {
|
|
|
69
82
|
.join("\n\n");
|
|
70
83
|
}
|
|
71
84
|
|
|
85
|
+
/** Read a file as UTF-8, returning empty string if missing or unreadable. */
|
|
86
|
+
async function safeReadFile(absPath: string): Promise<string> {
|
|
87
|
+
try {
|
|
88
|
+
return await readFile(absPath, "utf8");
|
|
89
|
+
} catch {
|
|
90
|
+
return "";
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Log Promise.allSettled rejection reasons to stderr so an all-failed wave
|
|
96
|
+
* leaves a debugging trail instead of silently producing an empty report.
|
|
97
|
+
*/
|
|
98
|
+
function logBatchRejections(
|
|
99
|
+
label: string,
|
|
100
|
+
results: PromiseSettledResult<unknown>[],
|
|
101
|
+
): void {
|
|
102
|
+
for (let i = 0; i < results.length; i++) {
|
|
103
|
+
const r = results[i];
|
|
104
|
+
if (r?.status === "rejected") {
|
|
105
|
+
console.error(`[deep-research-codebase] ${label} batch ${i + 1} failed:`, r.reason);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
72
110
|
export default defineWorkflow({
|
|
73
111
|
name: "deep-research-codebase",
|
|
74
112
|
description:
|
|
@@ -105,7 +143,9 @@ export default defineWorkflow({
|
|
|
105
143
|
if (data.units.length === 0) {
|
|
106
144
|
throw new Error(
|
|
107
145
|
`deep-research-codebase: scout found no source files under ${root}. ` +
|
|
108
|
-
`Run from inside a code repository or
|
|
146
|
+
`Run from inside a code repository, or verify your files use a ` +
|
|
147
|
+
`recognized programming-language extension (sourced from GitHub ` +
|
|
148
|
+
`Linguist + sql/graphql/proto).`,
|
|
109
149
|
);
|
|
110
150
|
}
|
|
111
151
|
|
|
@@ -196,127 +236,211 @@ export default defineWorkflow({
|
|
|
196
236
|
|
|
197
237
|
const scoutOverview = (await ctx.transcript(scout)).content;
|
|
198
238
|
|
|
199
|
-
// ── Stage 2:
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
239
|
+
// ── Stage 2: batched specialist fan-out ───────────────────────────────
|
|
240
|
+
//
|
|
241
|
+
// Same two-wave batched design as claude/index.ts. Each batch session
|
|
242
|
+
// pins the dispatcher to the `orchestrator` agent (.github/agents/
|
|
243
|
+
// orchestrator.md) — its system prompt is purpose-built to delegate
|
|
244
|
+
// everything via the `agent`/Task tool, so the dispatcher cannot wander
|
|
245
|
+
// off and start doing the specialists' work itself.
|
|
246
|
+
|
|
247
|
+
// Per-partition output paths, computed once and reused across both wave
|
|
248
|
+
// task-list construction and synthesis.
|
|
249
|
+
const partitionPaths = partitions.map((_, idx) => {
|
|
250
|
+
const i = idx + 1;
|
|
251
|
+
return {
|
|
252
|
+
locator: path.join(scratchDir, `locator-${i}.md`),
|
|
253
|
+
patternFinder: path.join(scratchDir, `pattern-finder-${i}.md`),
|
|
254
|
+
analyzer: path.join(scratchDir, `analyzer-${i}.md`),
|
|
255
|
+
online: path.join(scratchDir, `online-${i}.md`),
|
|
256
|
+
explorer: path.join(scratchDir, `explorer-${i}.md`),
|
|
257
|
+
};
|
|
258
|
+
});
|
|
259
|
+
|
|
260
|
+
const wave1Tasks: Layer1Task[] = partitions.flatMap((partition, idx) => {
|
|
261
|
+
const i = idx + 1;
|
|
262
|
+
const paths = partitionPaths[idx]!;
|
|
263
|
+
return [
|
|
264
|
+
{
|
|
265
|
+
kind: "locator" as const,
|
|
266
|
+
partitionIndex: i,
|
|
267
|
+
partition,
|
|
268
|
+
outputPath: paths.locator,
|
|
269
|
+
},
|
|
270
|
+
{
|
|
271
|
+
kind: "pattern-finder" as const,
|
|
272
|
+
partitionIndex: i,
|
|
273
|
+
partition,
|
|
274
|
+
outputPath: paths.patternFinder,
|
|
275
|
+
},
|
|
276
|
+
];
|
|
277
|
+
});
|
|
278
|
+
|
|
279
|
+
const wave1Batches = chunkBatches(wave1Tasks, MAX_TASKS_PER_BATCH);
|
|
280
|
+
|
|
281
|
+
const wave1Results = await Promise.allSettled(
|
|
282
|
+
wave1Batches.map((batch, batchIdx) => {
|
|
283
|
+
const batchNumber = batchIdx + 1;
|
|
284
|
+
return ctx.stage(
|
|
285
|
+
{
|
|
286
|
+
name: `wave1-batch-${batchNumber}`,
|
|
287
|
+
headless: true,
|
|
288
|
+
description: `Layer 1 dispatch (${batch.length} tasks)`,
|
|
289
|
+
},
|
|
290
|
+
{},
|
|
291
|
+
{ agent: "orchestrator" },
|
|
292
|
+
async (s) => {
|
|
293
|
+
const taskSpecs = batch.map((t) => {
|
|
294
|
+
const builder =
|
|
295
|
+
t.kind === "locator" ? buildLocatorPrompt : buildPatternFinderPrompt;
|
|
296
|
+
const specialistPrompt = builder({
|
|
297
|
+
question: prompt,
|
|
298
|
+
partition: t.partition,
|
|
299
|
+
scoutOverview,
|
|
300
|
+
index: t.partitionIndex,
|
|
301
|
+
total: explorerCount,
|
|
247
302
|
});
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
const locatorOutput = locator.result;
|
|
256
|
-
const patternsOutput = patternFinder.result;
|
|
257
|
-
|
|
258
|
-
// Layer 2: analyzer + online-researcher consume locator output.
|
|
259
|
-
const [analyzer, onlineResearcher] = await Promise.all([
|
|
260
|
-
ctx.stage(
|
|
261
|
-
{
|
|
262
|
-
name: `analyzer-${i}`,
|
|
263
|
-
headless: true,
|
|
264
|
-
description: `codebase-analyzer over partition ${i}`,
|
|
265
|
-
},
|
|
266
|
-
{},
|
|
267
|
-
{ agent: "codebase-analyzer" },
|
|
268
|
-
async (s) => {
|
|
269
|
-
await s.session.send({
|
|
270
|
-
prompt: buildAnalyzerPrompt({
|
|
271
|
-
question: prompt,
|
|
272
|
-
partition,
|
|
273
|
-
locatorOutput,
|
|
274
|
-
scoutOverview,
|
|
275
|
-
index: i,
|
|
276
|
-
total: explorerCount,
|
|
303
|
+
return {
|
|
304
|
+
subagentType: SUBAGENT_TYPE[t.kind],
|
|
305
|
+
outputPath: t.outputPath,
|
|
306
|
+
prompt: wrapPromptForTaskDispatch({
|
|
307
|
+
specialistPrompt,
|
|
308
|
+
outputPath: t.outputPath,
|
|
309
|
+
agentLabel: t.kind.toUpperCase().replaceAll("-", "_"),
|
|
277
310
|
}),
|
|
278
|
-
}
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
311
|
+
};
|
|
312
|
+
});
|
|
313
|
+
|
|
314
|
+
await s.session.send({
|
|
315
|
+
prompt: buildBatchOrchestratorPrompt({
|
|
316
|
+
wave: 1,
|
|
317
|
+
batchIndex: batchNumber,
|
|
318
|
+
totalBatches: wave1Batches.length,
|
|
319
|
+
tasks: taskSpecs,
|
|
320
|
+
}),
|
|
321
|
+
});
|
|
322
|
+
s.save(await s.session.getMessages());
|
|
323
|
+
},
|
|
324
|
+
);
|
|
325
|
+
}),
|
|
326
|
+
);
|
|
327
|
+
logBatchRejections("wave1", wave1Results);
|
|
328
|
+
|
|
329
|
+
const locatorOutputs: Map<number, string> = new Map();
|
|
330
|
+
await Promise.all(
|
|
331
|
+
partitions.map(async (_p, idx) => {
|
|
332
|
+
const i = idx + 1;
|
|
333
|
+
locatorOutputs.set(i, await safeReadFile(partitionPaths[idx]!.locator));
|
|
334
|
+
}),
|
|
335
|
+
);
|
|
336
|
+
|
|
337
|
+
const wave2Tasks: Layer2Task[] = partitions.flatMap((partition, idx) => {
|
|
338
|
+
const i = idx + 1;
|
|
339
|
+
const paths = partitionPaths[idx]!;
|
|
340
|
+
const locatorOutput = locatorOutputs.get(i) ?? "";
|
|
341
|
+
return [
|
|
342
|
+
{
|
|
343
|
+
kind: "analyzer" as const,
|
|
344
|
+
partitionIndex: i,
|
|
345
|
+
partition,
|
|
346
|
+
outputPath: paths.analyzer,
|
|
347
|
+
locatorOutput,
|
|
348
|
+
},
|
|
349
|
+
{
|
|
350
|
+
kind: "online-researcher" as const,
|
|
351
|
+
partitionIndex: i,
|
|
352
|
+
partition,
|
|
353
|
+
outputPath: paths.online,
|
|
354
|
+
locatorOutput,
|
|
355
|
+
},
|
|
356
|
+
];
|
|
357
|
+
});
|
|
358
|
+
|
|
359
|
+
const wave2Batches = chunkBatches(wave2Tasks, MAX_TASKS_PER_BATCH);
|
|
360
|
+
|
|
361
|
+
const wave2Results = await Promise.allSettled(
|
|
362
|
+
wave2Batches.map((batch, batchIdx) => {
|
|
363
|
+
const batchNumber = batchIdx + 1;
|
|
364
|
+
return ctx.stage(
|
|
365
|
+
{
|
|
366
|
+
name: `wave2-batch-${batchNumber}`,
|
|
367
|
+
headless: true,
|
|
368
|
+
description: `Layer 2 dispatch (${batch.length} tasks)`,
|
|
369
|
+
},
|
|
370
|
+
{},
|
|
371
|
+
{ agent: "orchestrator" },
|
|
372
|
+
async (s) => {
|
|
373
|
+
const taskSpecs = batch.map((t) => {
|
|
374
|
+
const specialistPrompt =
|
|
375
|
+
t.kind === "analyzer"
|
|
376
|
+
? buildAnalyzerPrompt({
|
|
377
|
+
question: prompt,
|
|
378
|
+
partition: t.partition,
|
|
379
|
+
locatorOutput: t.locatorOutput,
|
|
380
|
+
scoutOverview,
|
|
381
|
+
index: t.partitionIndex,
|
|
382
|
+
total: explorerCount,
|
|
383
|
+
})
|
|
384
|
+
: buildOnlineResearcherPrompt({
|
|
385
|
+
question: prompt,
|
|
386
|
+
partition: t.partition,
|
|
387
|
+
locatorOutput: t.locatorOutput,
|
|
388
|
+
index: t.partitionIndex,
|
|
389
|
+
total: explorerCount,
|
|
390
|
+
});
|
|
391
|
+
return {
|
|
392
|
+
subagentType: SUBAGENT_TYPE[t.kind],
|
|
393
|
+
outputPath: t.outputPath,
|
|
394
|
+
prompt: wrapPromptForTaskDispatch({
|
|
395
|
+
specialistPrompt,
|
|
396
|
+
outputPath: t.outputPath,
|
|
397
|
+
agentLabel: t.kind.toUpperCase().replaceAll("-", "_"),
|
|
300
398
|
}),
|
|
301
|
-
}
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
399
|
+
};
|
|
400
|
+
});
|
|
401
|
+
|
|
402
|
+
await s.session.send({
|
|
403
|
+
prompt: buildBatchOrchestratorPrompt({
|
|
404
|
+
wave: 2,
|
|
405
|
+
batchIndex: batchNumber,
|
|
406
|
+
totalBatches: wave2Batches.length,
|
|
407
|
+
tasks: taskSpecs,
|
|
408
|
+
}),
|
|
409
|
+
});
|
|
410
|
+
s.save(await s.session.getMessages());
|
|
411
|
+
},
|
|
412
|
+
);
|
|
413
|
+
}),
|
|
414
|
+
);
|
|
415
|
+
logBatchRejections("wave2", wave2Results);
|
|
416
|
+
|
|
417
|
+
// Synthesis: read all four specialist files per partition and write the
|
|
418
|
+
// consolidated explorer scratch file. Missing files fall back to "" so
|
|
419
|
+
// partial batch failures degrade gracefully.
|
|
420
|
+
const explorerHandles = await Promise.all(
|
|
421
|
+
partitions.map(async (partition, idx) => {
|
|
422
|
+
const i = idx + 1;
|
|
423
|
+
const paths = partitionPaths[idx]!;
|
|
424
|
+
|
|
425
|
+
const [locatorOutput, patternsOutput, analyzerOutput, onlineOutput] =
|
|
426
|
+
await Promise.all([
|
|
427
|
+
Promise.resolve(locatorOutputs.get(i) ?? ""),
|
|
428
|
+
safeReadFile(paths.patternFinder),
|
|
429
|
+
safeReadFile(paths.analyzer),
|
|
430
|
+
safeReadFile(paths.online),
|
|
431
|
+
]);
|
|
432
|
+
|
|
433
|
+
await writeExplorerScratchFile(paths.explorer, {
|
|
310
434
|
index: i,
|
|
311
435
|
total: explorerCount,
|
|
312
436
|
partition,
|
|
313
437
|
locatorOutput,
|
|
314
438
|
patternsOutput,
|
|
315
|
-
analyzerOutput
|
|
316
|
-
onlineOutput
|
|
439
|
+
analyzerOutput,
|
|
440
|
+
onlineOutput,
|
|
317
441
|
});
|
|
318
442
|
|
|
319
|
-
return { index: i, scratchPath, partition };
|
|
443
|
+
return { index: i, scratchPath: paths.explorer, partition };
|
|
320
444
|
}),
|
|
321
445
|
);
|
|
322
446
|
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Batching primitives for the deep-research-codebase workflow.
|
|
3
|
+
*
|
|
4
|
+
* The workflow caps SDK-level fan-out by grouping specialist invocations into
|
|
5
|
+
* "batch sessions" — one `ctx.stage()` per batch. Inside each batch session,
|
|
6
|
+
* the default Claude Code agent dispatches up to MAX_TASKS_PER_BATCH
|
|
7
|
+
* sub-agents in parallel via the Task tool. This keeps the parallel SDK
|
|
8
|
+
* subprocess count proportional to (specialists / 10) rather than to
|
|
9
|
+
* specialists itself, which scales linearly with codebase size.
|
|
10
|
+
*
|
|
11
|
+
* The per-message Task fan-out cap is empirical: there's no documented hard
|
|
12
|
+
* limit, but ~10 parallel sub-agents per single message is the reliable
|
|
13
|
+
* ceiling before rate limits, context contention, and degraded coordination
|
|
14
|
+
* kick in. Lower this if you see batch sessions stalling or returning
|
|
15
|
+
* partial completions; raise it only after measuring.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
import type { PartitionUnit } from "./scout.ts";
|
|
19
|
+
|
|
20
|
+
/** Maximum Task-tool sub-agent dispatches per single batch session. */
|
|
21
|
+
export const MAX_TASKS_PER_BATCH = 10;
|
|
22
|
+
|
|
23
|
+
/** Specialist kinds that share Layer 1 (no inter-task dependencies). */
|
|
24
|
+
export type Layer1Kind = "locator" | "pattern-finder";
|
|
25
|
+
|
|
26
|
+
/** Specialist kinds that share Layer 2 (depend on Layer 1 locator output). */
|
|
27
|
+
export type Layer2Kind = "analyzer" | "online-researcher";
|
|
28
|
+
|
|
29
|
+
/** Maps a specialist kind to the Claude agent name it dispatches as. */
|
|
30
|
+
export const SUBAGENT_TYPE: Record<Layer1Kind | Layer2Kind, string> = {
|
|
31
|
+
locator: "codebase-locator",
|
|
32
|
+
"pattern-finder": "codebase-pattern-finder",
|
|
33
|
+
analyzer: "codebase-analyzer",
|
|
34
|
+
"online-researcher": "codebase-online-researcher",
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
export type Layer1Task = {
|
|
38
|
+
kind: Layer1Kind;
|
|
39
|
+
partitionIndex: number;
|
|
40
|
+
partition: PartitionUnit[];
|
|
41
|
+
/** Absolute path the sub-agent must write its verbatim findings to. */
|
|
42
|
+
outputPath: string;
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
export type Layer2Task = {
|
|
46
|
+
kind: Layer2Kind;
|
|
47
|
+
partitionIndex: number;
|
|
48
|
+
partition: PartitionUnit[];
|
|
49
|
+
outputPath: string;
|
|
50
|
+
/** Verbatim locator text for this partition, embedded into the prompt. */
|
|
51
|
+
locatorOutput: string;
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
/** Split a flat task list into fixed-size chunks (last chunk may be smaller). */
|
|
55
|
+
export function chunkBatches<T>(
|
|
56
|
+
items: T[],
|
|
57
|
+
size: number = MAX_TASKS_PER_BATCH,
|
|
58
|
+
): T[][] {
|
|
59
|
+
if (size <= 0) throw new Error("chunkBatches: size must be > 0");
|
|
60
|
+
const out: T[][] = [];
|
|
61
|
+
for (let i = 0; i < items.length; i += size) {
|
|
62
|
+
out.push(items.slice(i, i + size));
|
|
63
|
+
}
|
|
64
|
+
return out;
|
|
65
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
// `ignore-by-default` ships no .d.ts and `@types/ignore-by-default` is an
|
|
2
|
+
// empty placeholder package. Declare the surface we use here so the import
|
|
3
|
+
// in scout.ts type-checks. The runtime API is a single CJS export:
|
|
4
|
+
// `module.exports.directories(): string[]`.
|
|
5
|
+
declare module "ignore-by-default" {
|
|
6
|
+
const ibd: { directories(): string[] };
|
|
7
|
+
export default ibd;
|
|
8
|
+
}
|