@bastani/atomic 0.5.17-0 → 0.5.18-0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +14 -1
- package/dist/sdk/workflows/builtin/deep-research-codebase/claude/index.d.ts +50 -54
- 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 +17 -36
- package/dist/sdk/workflows/builtin/deep-research-codebase/copilot/index.d.ts.map +1 -1
- package/dist/sdk/workflows/builtin/deep-research-codebase/helpers/heuristic.d.ts +1 -1
- package/dist/sdk/workflows/builtin/deep-research-codebase/helpers/prompts.d.ts +64 -44
- package/dist/sdk/workflows/builtin/deep-research-codebase/helpers/prompts.d.ts.map +1 -1
- package/dist/sdk/workflows/builtin/deep-research-codebase/helpers/scratch.d.ts +43 -0
- package/dist/sdk/workflows/builtin/deep-research-codebase/helpers/scratch.d.ts.map +1 -0
- package/dist/sdk/workflows/builtin/deep-research-codebase/opencode/index.d.ts +17 -39
- package/dist/sdk/workflows/builtin/deep-research-codebase/opencode/index.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/cli.ts +21 -2
- package/src/commands/cli/session.test.ts +223 -0
- package/src/commands/cli/session.ts +117 -1
- package/src/completions/bash.ts +3 -3
- package/src/completions/fish.ts +13 -7
- package/src/completions/powershell.ts +3 -0
- package/src/completions/zsh.ts +2 -1
- package/src/sdk/workflows/builtin/deep-research-codebase/claude/index.ts +260 -157
- package/src/sdk/workflows/builtin/deep-research-codebase/copilot/index.ts +224 -125
- package/src/sdk/workflows/builtin/deep-research-codebase/helpers/heuristic.ts +2 -2
- package/src/sdk/workflows/builtin/deep-research-codebase/helpers/prompts.ts +428 -469
- package/src/sdk/workflows/builtin/deep-research-codebase/helpers/scratch.ts +115 -0
- package/src/sdk/workflows/builtin/deep-research-codebase/opencode/index.ts +249 -137
|
@@ -1,67 +1,63 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* deep-research-codebase / claude
|
|
3
3
|
*
|
|
4
|
-
* A deterministically-orchestrated, distributed
|
|
5
|
-
*
|
|
6
|
-
* codebase-locator / codebase-
|
|
7
|
-
* codebase-
|
|
8
|
-
* codebase-
|
|
9
|
-
*
|
|
10
|
-
*
|
|
4
|
+
* A deterministically-orchestrated, distributed codebase researcher built on
|
|
5
|
+
* the Claude Agent SDK's native sub-agent dispatch. Specialist sub-agents
|
|
6
|
+
* (codebase-locator / codebase-pattern-finder / codebase-analyzer /
|
|
7
|
+
* codebase-online-researcher / codebase-research-locator /
|
|
8
|
+
* codebase-research-analyzer) are spawned as separate headless `ctx.stage()`
|
|
9
|
+
* calls — each binds the SDK's `agent` option to the desired specialist
|
|
10
|
+
* instead of relying on a coordinator agent that dispatches them via the
|
|
11
|
+
* `@"name (agent)"` prompt syntax.
|
|
11
12
|
*
|
|
12
|
-
*
|
|
13
|
+
* Why SDK primitives instead of in-prompt orchestration:
|
|
13
14
|
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
* ▼
|
|
19
|
-
* ┌──────────────────────────────────────────────────┐
|
|
20
|
-
* │ explorer-1 explorer-2 ... explorer-N │ (Promise.all, headless)
|
|
21
|
-
* └──────────────────────────────────────────────────┘
|
|
22
|
-
* │
|
|
23
|
-
* ▼
|
|
24
|
-
* aggregator
|
|
15
|
+
* • Each specialist runs in an ISOLATED conversation. The locator's giant
|
|
16
|
+
* file index doesn't pollute the analyzer's context window, and the
|
|
17
|
+
* online-researcher doesn't see the analyzer's reasoning at all. This is
|
|
18
|
+
* `multi-agent-patterns` swarm-style isolation, not orchestrator-style.
|
|
25
19
|
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
20
|
+
* • There is no orchestrator turn whose context grows linearly with the
|
|
21
|
+
* number of specialists. Token cost per partition is bounded by the four
|
|
22
|
+
* specialists' independent prompts — adding more partitions scales
|
|
23
|
+
* cleanly because every fan-out is a fresh session.
|
|
28
24
|
*
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
-
*
|
|
32
|
-
* partitions where N is determined by the LOC heuristic. Then makes one
|
|
33
|
-
* short LLM call to produce an architectural orientation that primes the
|
|
34
|
-
* downstream explorers. Returns structured data via `handle.result` and
|
|
35
|
-
* the agent's prose via `ctx.transcript(handle)`.
|
|
25
|
+
* • Failure of one specialist does not abort the partition mid-thought —
|
|
26
|
+
* the runtime fails the stage, but its siblings' outputs are still on
|
|
27
|
+
* disk and the aggregator can continue with whatever completed.
|
|
36
28
|
*
|
|
37
|
-
*
|
|
38
|
-
*
|
|
39
|
-
*
|
|
40
|
-
* prior decisions, completed investigations, and unresolved questions.
|
|
41
|
-
* Output is consumed via session transcript (≤400 words) and feeds into
|
|
42
|
-
* the aggregator as supplementary context.
|
|
29
|
+
* • The synthesis step that combines specialist outputs is plain TypeScript
|
|
30
|
+
* (`renderExplorerMarkdown` in helpers/scratch.ts) — no extra LLM call
|
|
31
|
+
* just to concatenate four markdown sections.
|
|
43
32
|
*
|
|
44
|
-
*
|
|
45
|
-
* Each explorer is a coordinator that dispatches specialized sub-agents
|
|
46
|
-
* over its assigned partition (single LOC-balanced slice of the codebase):
|
|
47
|
-
* - codebase-locator → finds relevant files in the partition
|
|
48
|
-
* - codebase-analyzer → documents how the most relevant files work
|
|
49
|
-
* - codebase-pattern-finder → finds existing pattern examples
|
|
50
|
-
* - codebase-online-researcher → (conditional) external library docs
|
|
51
|
-
* The explorer never reads files directly — it orchestrates specialists
|
|
52
|
-
* and writes a synthesized findings document to a known scratch path.
|
|
33
|
+
* Topology:
|
|
53
34
|
*
|
|
54
|
-
*
|
|
55
|
-
*
|
|
56
|
-
*
|
|
57
|
-
*
|
|
58
|
-
*
|
|
59
|
-
*
|
|
35
|
+
* ┌─→ codebase-scout (visible)
|
|
36
|
+
* parent ─┤
|
|
37
|
+
* └─→ history-locator → history-analyzer (headless)
|
|
38
|
+
* │
|
|
39
|
+
* ▼
|
|
40
|
+
* ┌──────────────────────────────────────────────────────────────────────┐
|
|
41
|
+
* │ Per-partition (Promise.all over partitions, all stages headless): │
|
|
42
|
+
* │ │
|
|
43
|
+
* │ locator-i ∥ pattern-finder-i (Layer 1, parallel) │
|
|
44
|
+
* │ │ │
|
|
45
|
+
* │ ▼ │
|
|
46
|
+
* │ analyzer-i ∥ online-researcher-i (Layer 2, parallel) │
|
|
47
|
+
* │ │ │
|
|
48
|
+
* │ ▼ │
|
|
49
|
+
* │ deterministic write to scratch file (TS helper, no LLM) │
|
|
50
|
+
* └──────────────────────────────────────────────────────────────────────┘
|
|
51
|
+
* │
|
|
52
|
+
* ▼
|
|
53
|
+
* aggregator (visible)
|
|
60
54
|
*
|
|
61
|
-
*
|
|
55
|
+
* Specialist stages run headless (in-process via the Agent SDK's `query()`),
|
|
56
|
+
* so they are transparent to the workflow graph. The visible nodes are just:
|
|
57
|
+
* parent → [codebase-scout] → aggregator
|
|
62
58
|
*/
|
|
63
59
|
|
|
64
|
-
import { defineWorkflow } from "../../../index.ts";
|
|
60
|
+
import { defineWorkflow, extractAssistantText } from "../../../index.ts";
|
|
65
61
|
import { mkdir } from "node:fs/promises";
|
|
66
62
|
import path from "node:path";
|
|
67
63
|
|
|
@@ -76,21 +72,31 @@ import {
|
|
|
76
72
|
} from "../helpers/heuristic.ts";
|
|
77
73
|
import {
|
|
78
74
|
buildAggregatorPrompt,
|
|
79
|
-
|
|
80
|
-
|
|
75
|
+
buildAnalyzerPrompt,
|
|
76
|
+
buildHistoryAnalyzerPrompt,
|
|
77
|
+
buildHistoryLocatorPrompt,
|
|
78
|
+
buildLocatorPrompt,
|
|
79
|
+
buildOnlineResearcherPrompt,
|
|
80
|
+
buildPatternFinderPrompt,
|
|
81
81
|
buildScoutPrompt,
|
|
82
82
|
slugifyPrompt,
|
|
83
83
|
} from "../helpers/prompts.ts";
|
|
84
|
+
import { writeExplorerScratchFile } from "../helpers/scratch.ts";
|
|
84
85
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
86
|
+
/**
|
|
87
|
+
* Shared SDK options for every sub-agent dispatch. `permissionMode` +
|
|
88
|
+
* `allowDangerouslySkipPermissions` are required so the headless sub-agents
|
|
89
|
+
* can use Read/Grep/Glob/Bash without prompting (we are running unattended).
|
|
90
|
+
*/
|
|
91
|
+
const SUBAGENT_OPTS = {
|
|
92
|
+
permissionMode: "bypassPermissions",
|
|
93
|
+
allowDangerouslySkipPermissions: true,
|
|
94
|
+
} as const;
|
|
89
95
|
|
|
90
96
|
export default defineWorkflow({
|
|
91
97
|
name: "deep-research-codebase",
|
|
92
98
|
description:
|
|
93
|
-
"Deterministic deep codebase research: scout →
|
|
99
|
+
"Deterministic deep codebase research: scout → per-partition specialist sub-agents → aggregator",
|
|
94
100
|
inputs: [
|
|
95
101
|
{
|
|
96
102
|
name: "prompt",
|
|
@@ -102,34 +108,29 @@ export default defineWorkflow({
|
|
|
102
108
|
})
|
|
103
109
|
.for<"claude">()
|
|
104
110
|
.run(async (ctx) => {
|
|
105
|
-
// Destructure once so every stage below can close over a bare
|
|
106
|
-
// `prompt` string without re-reaching into ctx.inputs.
|
|
107
111
|
const prompt = ctx.inputs.prompt ?? "";
|
|
108
112
|
const root = getCodebaseRoot();
|
|
109
113
|
const startedAt = new Date();
|
|
110
114
|
const isoDate = startedAt.toISOString().slice(0, 10);
|
|
111
115
|
const slug = slugifyPrompt(prompt);
|
|
112
116
|
|
|
113
|
-
// ──
|
|
114
|
-
//
|
|
115
|
-
//
|
|
116
|
-
//
|
|
117
|
-
//
|
|
118
|
-
//
|
|
119
|
-
//
|
|
120
|
-
|
|
121
|
-
// parent → [scout, history] → [explorer-1..N] → aggregator.
|
|
122
|
-
const [scout, history] = await Promise.all([
|
|
117
|
+
// ── Stage 1a: codebase-scout (visible) ‖ Stage 1b: research-history pipeline (headless) ──
|
|
118
|
+
//
|
|
119
|
+
// Both pipelines are independent of each other and must complete before
|
|
120
|
+
// any explorer fan-out — explorers depend on `scout.result.partitions`
|
|
121
|
+
// and the aggregator embeds the history overview as supplementary
|
|
122
|
+
// context. We wrap the history sub-pipeline (locator → analyzer) in an
|
|
123
|
+
// IIFE so Promise.all sees it as a single awaitable.
|
|
124
|
+
const [scout, historyOverview] = await Promise.all([
|
|
123
125
|
ctx.stage(
|
|
124
126
|
{
|
|
125
127
|
name: "codebase-scout",
|
|
126
128
|
description:
|
|
127
|
-
"Map codebase, count LOC, partition for parallel
|
|
129
|
+
"Map codebase, count LOC, partition for parallel specialists",
|
|
128
130
|
},
|
|
129
131
|
{},
|
|
130
132
|
{},
|
|
131
133
|
async (s) => {
|
|
132
|
-
// 1. Deterministic scouting.
|
|
133
134
|
const data = scoutCodebase(root);
|
|
134
135
|
if (data.units.length === 0) {
|
|
135
136
|
throw new Error(
|
|
@@ -138,13 +139,10 @@ export default defineWorkflow({
|
|
|
138
139
|
);
|
|
139
140
|
}
|
|
140
141
|
|
|
141
|
-
// 2. Heuristic decides explorer count (capped by available units).
|
|
142
142
|
const targetCount = calculateExplorerCount(data.totalLoc);
|
|
143
143
|
const partitions = partitionUnits(data.units, targetCount);
|
|
144
144
|
const actualCount = partitions.length;
|
|
145
145
|
|
|
146
|
-
// 3. Scratch directory for explorer outputs (timestamped to avoid
|
|
147
|
-
// collisions across runs).
|
|
148
146
|
const scratchDir = path.join(
|
|
149
147
|
root,
|
|
150
148
|
"research",
|
|
@@ -153,10 +151,6 @@ export default defineWorkflow({
|
|
|
153
151
|
);
|
|
154
152
|
await mkdir(scratchDir, { recursive: true });
|
|
155
153
|
|
|
156
|
-
// 4. Short LLM call: architectural orientation for downstream
|
|
157
|
-
// explorers. The prompt explicitly forbids the agent from
|
|
158
|
-
// answering the research question — its only job here is to
|
|
159
|
-
// orient.
|
|
160
154
|
await s.session.query(
|
|
161
155
|
buildScoutPrompt({
|
|
162
156
|
question: prompt,
|
|
@@ -181,100 +175,209 @@ export default defineWorkflow({
|
|
|
181
175
|
};
|
|
182
176
|
},
|
|
183
177
|
),
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
// Pull both scout transcripts ONCE at the workflow level so every
|
|
206
|
-
// explorer + the aggregator can embed them in their prompts. Both
|
|
207
|
-
// stages have already completed by this point (we're past Promise.all),
|
|
208
|
-
// so these reads are safe (F13).
|
|
209
|
-
const scoutOverview = (await ctx.transcript(scout)).content;
|
|
210
|
-
const historyOverview = (await ctx.transcript(history)).content;
|
|
178
|
+
// Research-history pipeline: locator → analyzer, both headless. The
|
|
179
|
+
// analyzer needs the locator's verbatim output, so this is sequential
|
|
180
|
+
// INSIDE the IIFE while remaining parallel TO the codebase scout.
|
|
181
|
+
(async (): Promise<string> => {
|
|
182
|
+
const historyLocator = await ctx.stage(
|
|
183
|
+
{
|
|
184
|
+
name: "history-locator",
|
|
185
|
+
headless: true,
|
|
186
|
+
description: "Locate prior research docs (codebase-research-locator)",
|
|
187
|
+
},
|
|
188
|
+
{},
|
|
189
|
+
{},
|
|
190
|
+
async (s) => {
|
|
191
|
+
const result = await s.session.query(
|
|
192
|
+
buildHistoryLocatorPrompt({ question: prompt, root }),
|
|
193
|
+
{ agent: "codebase-research-locator", ...SUBAGENT_OPTS },
|
|
194
|
+
);
|
|
195
|
+
s.save(s.sessionId);
|
|
196
|
+
return extractAssistantText(result, 0);
|
|
197
|
+
},
|
|
198
|
+
);
|
|
211
199
|
|
|
212
|
-
|
|
213
|
-
// Each explorer runs headless (in-process, no tmux pane) via Promise.all.
|
|
214
|
-
// They are invisible in the workflow graph but tracked by the background
|
|
215
|
-
// task counter in the statusline. Each one receives:
|
|
216
|
-
// - the original research question (top + bottom of prompt)
|
|
217
|
-
// - the scout's architectural overview
|
|
218
|
-
// - its OWN partition (never the full file list)
|
|
219
|
-
// - the absolute path to its scratch file
|
|
220
|
-
//
|
|
221
|
-
// Information flow choices:
|
|
222
|
-
// • We deliberately do not pass other explorers' work — they run in
|
|
223
|
-
// parallel and forward-only data flow is enforced by the runtime
|
|
224
|
-
// (F13). Cross-cutting happens in the aggregator.
|
|
225
|
-
// • We pass the partition via closure capture, not by parsing
|
|
226
|
-
// scout transcripts — strongly typed and lossless.
|
|
227
|
-
const explorerHandles = await Promise.all(
|
|
228
|
-
partitions.map((partition, idx) => {
|
|
229
|
-
const i = idx + 1;
|
|
230
|
-
const scratchPath = path.join(scratchDir, `explorer-${i}.md`);
|
|
231
|
-
return ctx.stage(
|
|
200
|
+
const historyAnalyzer = await ctx.stage(
|
|
232
201
|
{
|
|
233
|
-
name:
|
|
202
|
+
name: "history-analyzer",
|
|
234
203
|
headless: true,
|
|
235
|
-
description:
|
|
236
|
-
.map((u) => u.path)
|
|
237
|
-
.join(
|
|
238
|
-
", ",
|
|
239
|
-
)} (${partition.reduce((s, u) => s + u.fileCount, 0)} files)`,
|
|
204
|
+
description: "Synthesize prior research (codebase-research-analyzer)",
|
|
240
205
|
},
|
|
241
206
|
{},
|
|
242
207
|
{},
|
|
243
208
|
async (s) => {
|
|
244
|
-
await s.session.query(
|
|
245
|
-
|
|
209
|
+
const result = await s.session.query(
|
|
210
|
+
buildHistoryAnalyzerPrompt({
|
|
246
211
|
question: prompt,
|
|
247
|
-
|
|
248
|
-
total: explorerCount,
|
|
249
|
-
partition,
|
|
250
|
-
scoutOverview,
|
|
251
|
-
scratchPath,
|
|
212
|
+
locatorOutput: historyLocator.result,
|
|
252
213
|
root,
|
|
253
214
|
}),
|
|
215
|
+
{ agent: "codebase-research-analyzer", ...SUBAGENT_OPTS },
|
|
254
216
|
);
|
|
255
217
|
s.save(s.sessionId);
|
|
256
|
-
|
|
257
|
-
// Returning structured metadata lets the aggregator stage reach
|
|
258
|
-
// each explorer's scratch path without re-parsing transcripts.
|
|
259
|
-
return { index: i, scratchPath, partition };
|
|
218
|
+
return extractAssistantText(result, 0);
|
|
260
219
|
},
|
|
261
220
|
);
|
|
221
|
+
|
|
222
|
+
return historyAnalyzer.result;
|
|
223
|
+
})(),
|
|
224
|
+
]);
|
|
225
|
+
|
|
226
|
+
const { partitions, explorerCount, scratchDir, totalLoc, totalFiles } =
|
|
227
|
+
scout.result;
|
|
228
|
+
|
|
229
|
+
// Pull the scout transcript ONCE so every per-partition specialist can
|
|
230
|
+
// embed the architectural orientation in its prompt. The scout has
|
|
231
|
+
// completed by the time we get here (we're past Promise.all), so this
|
|
232
|
+
// read is safe (failure-modes F13).
|
|
233
|
+
const scoutOverview = (await ctx.transcript(scout)).content;
|
|
234
|
+
|
|
235
|
+
// ── Stage 2: per-partition specialist fan-out ─────────────────────────
|
|
236
|
+
//
|
|
237
|
+
// Per partition i:
|
|
238
|
+
// Layer 1 (parallel): locator-i ∥ pattern-finder-i
|
|
239
|
+
// Layer 2 (parallel): analyzer-i ∥ online-researcher-i ← depend on locator-i
|
|
240
|
+
// Synthesis (deterministic TS): renderExplorerMarkdown → scratch file
|
|
241
|
+
//
|
|
242
|
+
// All N partitions run as parallel branches of the outer Promise.all.
|
|
243
|
+
// Sub-agent stages are headless: invisible in the graph and bounded only
|
|
244
|
+
// by SDK concurrency. Information flow is forward-only and all context
|
|
245
|
+
// each specialist needs (research question, scope, scout overview, and —
|
|
246
|
+
// for layer 2 — locator output) is injected into the first prompt.
|
|
247
|
+
const explorerHandles = await Promise.all(
|
|
248
|
+
partitions.map(async (partition, idx) => {
|
|
249
|
+
const i = idx + 1;
|
|
250
|
+
const scratchPath = path.join(scratchDir, `explorer-${i}.md`);
|
|
251
|
+
|
|
252
|
+
// Layer 1: locator + pattern-finder run independently.
|
|
253
|
+
const [locator, patternFinder] = await Promise.all([
|
|
254
|
+
ctx.stage(
|
|
255
|
+
{
|
|
256
|
+
name: `locator-${i}`,
|
|
257
|
+
headless: true,
|
|
258
|
+
description: `codebase-locator over partition ${i}`,
|
|
259
|
+
},
|
|
260
|
+
{},
|
|
261
|
+
{},
|
|
262
|
+
async (s) => {
|
|
263
|
+
const result = await s.session.query(
|
|
264
|
+
buildLocatorPrompt({
|
|
265
|
+
question: prompt,
|
|
266
|
+
partition,
|
|
267
|
+
root,
|
|
268
|
+
scoutOverview,
|
|
269
|
+
index: i,
|
|
270
|
+
total: explorerCount,
|
|
271
|
+
}),
|
|
272
|
+
{ agent: "codebase-locator", ...SUBAGENT_OPTS },
|
|
273
|
+
);
|
|
274
|
+
s.save(s.sessionId);
|
|
275
|
+
return extractAssistantText(result, 0);
|
|
276
|
+
},
|
|
277
|
+
),
|
|
278
|
+
ctx.stage(
|
|
279
|
+
{
|
|
280
|
+
name: `pattern-finder-${i}`,
|
|
281
|
+
headless: true,
|
|
282
|
+
description: `codebase-pattern-finder over partition ${i}`,
|
|
283
|
+
},
|
|
284
|
+
{},
|
|
285
|
+
{},
|
|
286
|
+
async (s) => {
|
|
287
|
+
const result = await s.session.query(
|
|
288
|
+
buildPatternFinderPrompt({
|
|
289
|
+
question: prompt,
|
|
290
|
+
partition,
|
|
291
|
+
root,
|
|
292
|
+
scoutOverview,
|
|
293
|
+
index: i,
|
|
294
|
+
total: explorerCount,
|
|
295
|
+
}),
|
|
296
|
+
{ agent: "codebase-pattern-finder", ...SUBAGENT_OPTS },
|
|
297
|
+
);
|
|
298
|
+
s.save(s.sessionId);
|
|
299
|
+
return extractAssistantText(result, 0);
|
|
300
|
+
},
|
|
301
|
+
),
|
|
302
|
+
]);
|
|
303
|
+
|
|
304
|
+
const locatorOutput = locator.result;
|
|
305
|
+
const patternsOutput = patternFinder.result;
|
|
306
|
+
|
|
307
|
+
// Layer 2: analyzer + online-researcher both consume locator output.
|
|
308
|
+
const [analyzer, onlineResearcher] = await Promise.all([
|
|
309
|
+
ctx.stage(
|
|
310
|
+
{
|
|
311
|
+
name: `analyzer-${i}`,
|
|
312
|
+
headless: true,
|
|
313
|
+
description: `codebase-analyzer over partition ${i}`,
|
|
314
|
+
},
|
|
315
|
+
{},
|
|
316
|
+
{},
|
|
317
|
+
async (s) => {
|
|
318
|
+
const result = await s.session.query(
|
|
319
|
+
buildAnalyzerPrompt({
|
|
320
|
+
question: prompt,
|
|
321
|
+
partition,
|
|
322
|
+
locatorOutput,
|
|
323
|
+
root,
|
|
324
|
+
scoutOverview,
|
|
325
|
+
index: i,
|
|
326
|
+
total: explorerCount,
|
|
327
|
+
}),
|
|
328
|
+
{ agent: "codebase-analyzer", ...SUBAGENT_OPTS },
|
|
329
|
+
);
|
|
330
|
+
s.save(s.sessionId);
|
|
331
|
+
return extractAssistantText(result, 0);
|
|
332
|
+
},
|
|
333
|
+
),
|
|
334
|
+
ctx.stage(
|
|
335
|
+
{
|
|
336
|
+
name: `online-researcher-${i}`,
|
|
337
|
+
headless: true,
|
|
338
|
+
description: `codebase-online-researcher over partition ${i}`,
|
|
339
|
+
},
|
|
340
|
+
{},
|
|
341
|
+
{},
|
|
342
|
+
async (s) => {
|
|
343
|
+
const result = await s.session.query(
|
|
344
|
+
buildOnlineResearcherPrompt({
|
|
345
|
+
question: prompt,
|
|
346
|
+
partition,
|
|
347
|
+
locatorOutput,
|
|
348
|
+
root,
|
|
349
|
+
index: i,
|
|
350
|
+
total: explorerCount,
|
|
351
|
+
}),
|
|
352
|
+
{ agent: "codebase-online-researcher", ...SUBAGENT_OPTS },
|
|
353
|
+
);
|
|
354
|
+
s.save(s.sessionId);
|
|
355
|
+
return extractAssistantText(result, 0);
|
|
356
|
+
},
|
|
357
|
+
),
|
|
358
|
+
]);
|
|
359
|
+
|
|
360
|
+
// Deterministic synthesis — no fifth LLM call just to concatenate.
|
|
361
|
+
await writeExplorerScratchFile(scratchPath, {
|
|
362
|
+
index: i,
|
|
363
|
+
total: explorerCount,
|
|
364
|
+
partition,
|
|
365
|
+
locatorOutput,
|
|
366
|
+
patternsOutput,
|
|
367
|
+
analyzerOutput: analyzer.result,
|
|
368
|
+
onlineOutput: onlineResearcher.result,
|
|
369
|
+
});
|
|
370
|
+
|
|
371
|
+
return { index: i, scratchPath, partition };
|
|
262
372
|
}),
|
|
263
373
|
);
|
|
264
374
|
|
|
265
|
-
// ── Stage 3: aggregator
|
|
266
|
-
// Synthesizes explorer findings into the final research document at
|
|
267
|
-
// research/docs/YYYY-MM-DD-<slug>.md.
|
|
375
|
+
// ── Stage 3: aggregator (visible) ─────────────────────────────────────
|
|
268
376
|
//
|
|
269
|
-
//
|
|
270
|
-
//
|
|
271
|
-
//
|
|
272
|
-
//
|
|
273
|
-
// selectively re-read source files when explorers contradict
|
|
274
|
-
// each other.
|
|
275
|
-
// • The aggregator only sees the scout overview (short) plus a
|
|
276
|
-
// manifest of explorer scratch paths — token cost stays roughly
|
|
277
|
-
// constant in N rather than growing linearly.
|
|
377
|
+
// Reads each partition's deterministic scratch file by PATH so the
|
|
378
|
+
// aggregator's own context stays bounded by N filenames + the short
|
|
379
|
+
// scout/history overviews — not by N inlined transcripts (filesystem-
|
|
380
|
+
// context skill).
|
|
278
381
|
const finalPath = path.join(
|
|
279
382
|
root,
|
|
280
383
|
"research",
|
|
@@ -286,7 +389,7 @@ export default defineWorkflow({
|
|
|
286
389
|
{
|
|
287
390
|
name: "aggregator",
|
|
288
391
|
description:
|
|
289
|
-
"Synthesize
|
|
392
|
+
"Synthesize partition findings + history into final research doc",
|
|
290
393
|
},
|
|
291
394
|
{},
|
|
292
395
|
{},
|
|
@@ -297,7 +400,7 @@ export default defineWorkflow({
|
|
|
297
400
|
totalLoc,
|
|
298
401
|
totalFiles,
|
|
299
402
|
explorerCount,
|
|
300
|
-
explorerFiles: explorerHandles
|
|
403
|
+
explorerFiles: explorerHandles,
|
|
301
404
|
finalPath,
|
|
302
405
|
scoutOverview,
|
|
303
406
|
historyOverview,
|