@bastani/atomic 0.5.3-1 → 0.5.4-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 +110 -11
- package/dist/{chunk-mn870nrv.js → chunk-xkxndz5g.js} +213 -154
- package/dist/sdk/components/workflow-picker-panel.d.ts +120 -0
- package/dist/sdk/define-workflow.d.ts +1 -1
- package/dist/sdk/index.js +1 -1
- package/dist/sdk/runtime/discovery.d.ts +57 -3
- package/dist/sdk/runtime/executor.d.ts +15 -2
- package/dist/sdk/runtime/tmux.d.ts +9 -0
- package/dist/sdk/types.d.ts +63 -4
- package/dist/sdk/workflows/builtin/deep-research-codebase/claude/index.d.ts +61 -0
- package/dist/sdk/workflows/builtin/deep-research-codebase/copilot/index.d.ts +48 -0
- package/dist/sdk/workflows/builtin/deep-research-codebase/helpers/heuristic.d.ts +25 -0
- package/dist/sdk/workflows/builtin/deep-research-codebase/helpers/prompts.d.ts +91 -0
- package/dist/sdk/workflows/builtin/deep-research-codebase/helpers/scout.d.ts +56 -0
- package/dist/sdk/workflows/builtin/deep-research-codebase/opencode/index.d.ts +48 -0
- package/dist/sdk/workflows/builtin/ralph/claude/index.js +6 -5
- package/dist/sdk/workflows/builtin/ralph/copilot/index.js +6 -5
- package/dist/sdk/workflows/builtin/ralph/opencode/index.js +6 -5
- package/dist/sdk/workflows/index.d.ts +4 -4
- package/dist/sdk/workflows/index.js +7 -1
- package/package.json +1 -1
- package/src/cli.ts +25 -3
- package/src/commands/cli/chat/index.ts +5 -5
- package/src/commands/cli/init/index.ts +79 -77
- package/src/commands/cli/workflow-command.test.ts +757 -0
- package/src/commands/cli/workflow.test.ts +310 -0
- package/src/commands/cli/workflow.ts +445 -105
- package/src/sdk/components/workflow-picker-panel.tsx +1462 -0
- package/src/sdk/define-workflow.test.ts +101 -0
- package/src/sdk/define-workflow.ts +62 -2
- package/src/sdk/runtime/discovery.ts +111 -8
- package/src/sdk/runtime/executor.ts +89 -32
- package/src/sdk/runtime/tmux.conf +55 -0
- package/src/sdk/runtime/tmux.ts +34 -10
- package/src/sdk/types.ts +67 -4
- package/src/sdk/workflows/builtin/deep-research-codebase/claude/index.ts +294 -0
- package/src/sdk/workflows/builtin/deep-research-codebase/copilot/index.ts +276 -0
- package/src/sdk/workflows/builtin/deep-research-codebase/helpers/heuristic.ts +38 -0
- package/src/sdk/workflows/builtin/deep-research-codebase/helpers/prompts.ts +816 -0
- package/src/sdk/workflows/builtin/deep-research-codebase/helpers/scout.ts +334 -0
- package/src/sdk/workflows/builtin/deep-research-codebase/opencode/index.ts +284 -0
- package/src/sdk/workflows/builtin/ralph/claude/index.ts +8 -4
- package/src/sdk/workflows/builtin/ralph/copilot/index.ts +10 -4
- package/src/sdk/workflows/builtin/ralph/opencode/index.ts +8 -4
- package/src/sdk/workflows/index.ts +9 -1
- package/src/services/system/auto-sync.ts +1 -1
- package/src/services/system/install-ui.ts +109 -39
- package/src/theme/colors.ts +65 -1
|
@@ -0,0 +1,276 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* deep-research-codebase / copilot
|
|
3
|
+
*
|
|
4
|
+
* Copilot replica of the Claude deep-research-codebase workflow. The Claude
|
|
5
|
+
* version dispatches specialist sub-agents (codebase-locator, codebase-
|
|
6
|
+
* analyzer, etc.) inside a single explorer session via `@"name (agent)"`
|
|
7
|
+
* syntax — a Claude-specific feature. Copilot sessions are bound to a single
|
|
8
|
+
* agent for their entire lifetime, so we keep the SAME graph topology
|
|
9
|
+
* (scout ∥ history → explorer-1..N → aggregator) but drive each explorer
|
|
10
|
+
* through the locate → analyze → patterns → synthesize sequence inline using
|
|
11
|
+
* the default agent's built-in file tools.
|
|
12
|
+
*
|
|
13
|
+
* Topology (identical to Claude version):
|
|
14
|
+
*
|
|
15
|
+
* ┌─→ codebase-scout
|
|
16
|
+
* parent ─┤
|
|
17
|
+
* └─→ research-history
|
|
18
|
+
* │
|
|
19
|
+
* ▼
|
|
20
|
+
* ┌──────────────────────────────────────────────────┐
|
|
21
|
+
* │ explorer-1 explorer-2 ... explorer-N │ (Promise.all)
|
|
22
|
+
* └──────────────────────────────────────────────────┘
|
|
23
|
+
* │
|
|
24
|
+
* ▼
|
|
25
|
+
* aggregator
|
|
26
|
+
*
|
|
27
|
+
* Copilot-specific concerns baked in:
|
|
28
|
+
*
|
|
29
|
+
* • F10 — every `sendAndWait` passes an explicit 30-minute timeout. The SDK
|
|
30
|
+
* default is 60 seconds; a timeout THROWS and aborts the entire stage.
|
|
31
|
+
* Explorers can easily exceed 60s on large partitions.
|
|
32
|
+
*
|
|
33
|
+
* • F5 — every `ctx.stage()` call is a FRESH session with no memory of prior
|
|
34
|
+
* stages. We forward the scout overview, history overview, and partition
|
|
35
|
+
* assignment explicitly into each explorer's first prompt. The aggregator
|
|
36
|
+
* gets the same plus the explorer scratch file paths.
|
|
37
|
+
*
|
|
38
|
+
* • F9 — `s.save()` receives `SessionEvent[]` via `s.session.getMessages()`
|
|
39
|
+
* (Copilot's correct shape). Passing anything else breaks downstream
|
|
40
|
+
* `transcript()` reads.
|
|
41
|
+
*
|
|
42
|
+
* • F6 — every prompt explicitly requires trailing prose AFTER any tool
|
|
43
|
+
* call, so `transcript()` is never empty. A Copilot turn whose final
|
|
44
|
+
* message is a tool call produces an empty assistant.message terminator
|
|
45
|
+
* (F1); trailing prose is our insurance.
|
|
46
|
+
*/
|
|
47
|
+
|
|
48
|
+
import { defineWorkflow } from "../../../index.ts";
|
|
49
|
+
import { mkdir } from "node:fs/promises";
|
|
50
|
+
import path from "node:path";
|
|
51
|
+
|
|
52
|
+
import {
|
|
53
|
+
getCodebaseRoot,
|
|
54
|
+
partitionUnits,
|
|
55
|
+
scoutCodebase,
|
|
56
|
+
} from "../helpers/scout.ts";
|
|
57
|
+
import {
|
|
58
|
+
calculateExplorerCount,
|
|
59
|
+
explainHeuristic,
|
|
60
|
+
} from "../helpers/heuristic.ts";
|
|
61
|
+
import {
|
|
62
|
+
buildAggregatorPrompt,
|
|
63
|
+
buildExplorerPromptGeneric,
|
|
64
|
+
buildHistoryPromptGeneric,
|
|
65
|
+
buildScoutPrompt,
|
|
66
|
+
slugifyPrompt,
|
|
67
|
+
} from "../helpers/prompts.ts";
|
|
68
|
+
|
|
69
|
+
// ── Timeouts ────────────────────────────────────────────────────────────────
|
|
70
|
+
// Every sendAndWait call passes one of these explicitly — never relying on
|
|
71
|
+
// the 60-second default (F10). Pick generously; a hung session still surfaces
|
|
72
|
+
// as a clear error rather than silently breaking downstream stages.
|
|
73
|
+
const SCOUT_TIMEOUT_MS = 15 * 60 * 1000; // 15 min — short orientation call
|
|
74
|
+
const HISTORY_TIMEOUT_MS = 20 * 60 * 1000; // 20 min — reads research/ docs
|
|
75
|
+
const EXPLORER_TIMEOUT_MS = 45 * 60 * 1000; // 45 min — multi-step locate/analyze
|
|
76
|
+
const AGGREGATOR_TIMEOUT_MS = 45 * 60 * 1000; // 45 min — reads N explorer reports
|
|
77
|
+
|
|
78
|
+
export default defineWorkflow<"copilot">({
|
|
79
|
+
name: "deep-research-codebase",
|
|
80
|
+
description:
|
|
81
|
+
"Deterministic deep codebase research: scout → LOC-driven parallel explorers → aggregator",
|
|
82
|
+
})
|
|
83
|
+
.run(async (ctx) => {
|
|
84
|
+
// Free-form workflows receive their positional prompt under
|
|
85
|
+
// `inputs.prompt`; destructure once so every stage below can close
|
|
86
|
+
// over a bare `prompt` string without re-reaching into ctx.inputs.
|
|
87
|
+
const prompt = ctx.inputs.prompt ?? "";
|
|
88
|
+
const root = getCodebaseRoot();
|
|
89
|
+
const startedAt = new Date();
|
|
90
|
+
const isoDate = startedAt.toISOString().slice(0, 10);
|
|
91
|
+
const slug = slugifyPrompt(prompt);
|
|
92
|
+
|
|
93
|
+
// ── Stages 1a + 1b: codebase-scout ∥ research-history ──────────────────
|
|
94
|
+
const [scout, history] = await Promise.all([
|
|
95
|
+
ctx.stage(
|
|
96
|
+
{
|
|
97
|
+
name: "codebase-scout",
|
|
98
|
+
description: "Map codebase, count LOC, partition for parallel explorers",
|
|
99
|
+
},
|
|
100
|
+
{},
|
|
101
|
+
{},
|
|
102
|
+
async (s) => {
|
|
103
|
+
// 1. Deterministic scouting (pure TypeScript — no LLM).
|
|
104
|
+
const data = scoutCodebase(root);
|
|
105
|
+
if (data.units.length === 0) {
|
|
106
|
+
throw new Error(
|
|
107
|
+
`deep-research-codebase: scout found no source files under ${root}. ` +
|
|
108
|
+
`Run from inside a code repository or check the CODE_EXTENSIONS list.`,
|
|
109
|
+
);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// 2. Heuristic decides explorer count (capped by available units).
|
|
113
|
+
const targetCount = calculateExplorerCount(data.totalLoc);
|
|
114
|
+
const partitions = partitionUnits(data.units, targetCount);
|
|
115
|
+
const actualCount = partitions.length;
|
|
116
|
+
|
|
117
|
+
// 3. Scratch directory for explorer outputs (timestamped to avoid
|
|
118
|
+
// collisions across runs).
|
|
119
|
+
const scratchDir = path.join(
|
|
120
|
+
root,
|
|
121
|
+
"research",
|
|
122
|
+
"docs",
|
|
123
|
+
`.deep-research-${startedAt.getTime()}`,
|
|
124
|
+
);
|
|
125
|
+
await mkdir(scratchDir, { recursive: true });
|
|
126
|
+
|
|
127
|
+
// 4. Short LLM call: architectural orientation for downstream
|
|
128
|
+
// explorers. The prompt forbids the agent from answering the
|
|
129
|
+
// research question — its only job here is to orient.
|
|
130
|
+
await s.session.sendAndWait(
|
|
131
|
+
{
|
|
132
|
+
prompt: buildScoutPrompt({
|
|
133
|
+
question: prompt,
|
|
134
|
+
tree: data.tree,
|
|
135
|
+
totalLoc: data.totalLoc,
|
|
136
|
+
totalFiles: data.totalFiles,
|
|
137
|
+
explorerCount: actualCount,
|
|
138
|
+
partitionPreview: partitions,
|
|
139
|
+
}),
|
|
140
|
+
},
|
|
141
|
+
SCOUT_TIMEOUT_MS,
|
|
142
|
+
);
|
|
143
|
+
// F9: Copilot takes SessionEvent[], not a session ID.
|
|
144
|
+
s.save(await s.session.getMessages());
|
|
145
|
+
|
|
146
|
+
return {
|
|
147
|
+
root,
|
|
148
|
+
totalLoc: data.totalLoc,
|
|
149
|
+
totalFiles: data.totalFiles,
|
|
150
|
+
tree: data.tree,
|
|
151
|
+
partitions,
|
|
152
|
+
explorerCount: actualCount,
|
|
153
|
+
scratchDir,
|
|
154
|
+
heuristicNote: explainHeuristic(data.totalLoc, actualCount),
|
|
155
|
+
};
|
|
156
|
+
},
|
|
157
|
+
),
|
|
158
|
+
ctx.stage(
|
|
159
|
+
{
|
|
160
|
+
name: "research-history",
|
|
161
|
+
description: "Surface prior research from research/ directory",
|
|
162
|
+
},
|
|
163
|
+
{},
|
|
164
|
+
{},
|
|
165
|
+
async (s) => {
|
|
166
|
+
// The generic history prompt drives a single default-agent session
|
|
167
|
+
// through locate → analyze → synthesize inline, instead of Claude's
|
|
168
|
+
// sub-agent dispatch.
|
|
169
|
+
await s.session.sendAndWait(
|
|
170
|
+
{ prompt: buildHistoryPromptGeneric({ question: prompt, root }) },
|
|
171
|
+
HISTORY_TIMEOUT_MS,
|
|
172
|
+
);
|
|
173
|
+
s.save(await s.session.getMessages());
|
|
174
|
+
},
|
|
175
|
+
),
|
|
176
|
+
]);
|
|
177
|
+
|
|
178
|
+
const {
|
|
179
|
+
partitions,
|
|
180
|
+
explorerCount,
|
|
181
|
+
scratchDir,
|
|
182
|
+
totalLoc,
|
|
183
|
+
totalFiles,
|
|
184
|
+
} = scout.result;
|
|
185
|
+
|
|
186
|
+
// Pull both scout transcripts ONCE at the workflow level so every
|
|
187
|
+
// explorer + the aggregator can embed them in their prompts (F5). Both
|
|
188
|
+
// stages have completed here (we're past Promise.all), so these reads
|
|
189
|
+
// are safe (F13).
|
|
190
|
+
const scoutOverview = (await ctx.transcript(scout)).content;
|
|
191
|
+
const historyOverview = (await ctx.transcript(history)).content;
|
|
192
|
+
|
|
193
|
+
// ── Stage 2: parallel explorers ────────────────────────────────────────
|
|
194
|
+
// Each explorer is a separate Copilot session, running concurrently via
|
|
195
|
+
// Promise.all. Because the session is fresh (F5), every piece of context
|
|
196
|
+
// it needs — question, architectural orientation, historical context,
|
|
197
|
+
// partition assignment, scratch path — is injected into the first prompt
|
|
198
|
+
// via buildExplorerPromptGeneric.
|
|
199
|
+
const explorerHandles = await Promise.all(
|
|
200
|
+
partitions.map((partition, idx) => {
|
|
201
|
+
const i = idx + 1;
|
|
202
|
+
const scratchPath = path.join(scratchDir, `explorer-${i}.md`);
|
|
203
|
+
return ctx.stage(
|
|
204
|
+
{
|
|
205
|
+
name: `explorer-${i}`,
|
|
206
|
+
description: `Explore ${partition
|
|
207
|
+
.map((u) => u.path)
|
|
208
|
+
.join(", ")} (${partition.reduce((s, u) => s + u.fileCount, 0)} files)`,
|
|
209
|
+
},
|
|
210
|
+
{},
|
|
211
|
+
{},
|
|
212
|
+
async (s) => {
|
|
213
|
+
await s.session.sendAndWait(
|
|
214
|
+
{
|
|
215
|
+
prompt: buildExplorerPromptGeneric({
|
|
216
|
+
question: prompt,
|
|
217
|
+
index: i,
|
|
218
|
+
total: explorerCount,
|
|
219
|
+
partition,
|
|
220
|
+
scoutOverview,
|
|
221
|
+
historyOverview,
|
|
222
|
+
scratchPath,
|
|
223
|
+
root,
|
|
224
|
+
}),
|
|
225
|
+
},
|
|
226
|
+
EXPLORER_TIMEOUT_MS,
|
|
227
|
+
);
|
|
228
|
+
s.save(await s.session.getMessages());
|
|
229
|
+
|
|
230
|
+
// Returning structured metadata lets the aggregator stage reach
|
|
231
|
+
// each explorer's scratch path without re-parsing transcripts.
|
|
232
|
+
return { index: i, scratchPath, partition };
|
|
233
|
+
},
|
|
234
|
+
);
|
|
235
|
+
}),
|
|
236
|
+
);
|
|
237
|
+
|
|
238
|
+
// ── Stage 3: aggregator ────────────────────────────────────────────────
|
|
239
|
+
// Reads explorer findings via FILE PATHS (filesystem-context skill) to
|
|
240
|
+
// keep the aggregator's own context lean — we deliberately do NOT inline
|
|
241
|
+
// N transcripts into the prompt. Token cost stays roughly constant in N.
|
|
242
|
+
const finalPath = path.join(
|
|
243
|
+
root,
|
|
244
|
+
"research",
|
|
245
|
+
"docs",
|
|
246
|
+
`${isoDate}-${slug}.md`,
|
|
247
|
+
);
|
|
248
|
+
|
|
249
|
+
await ctx.stage(
|
|
250
|
+
{
|
|
251
|
+
name: "aggregator",
|
|
252
|
+
description: "Synthesize explorer findings + history into final research doc",
|
|
253
|
+
},
|
|
254
|
+
{},
|
|
255
|
+
{},
|
|
256
|
+
async (s) => {
|
|
257
|
+
await s.session.sendAndWait(
|
|
258
|
+
{
|
|
259
|
+
prompt: buildAggregatorPrompt({
|
|
260
|
+
question: prompt,
|
|
261
|
+
totalLoc,
|
|
262
|
+
totalFiles,
|
|
263
|
+
explorerCount,
|
|
264
|
+
explorerFiles: explorerHandles.map((h) => h.result),
|
|
265
|
+
finalPath,
|
|
266
|
+
scoutOverview,
|
|
267
|
+
historyOverview,
|
|
268
|
+
}),
|
|
269
|
+
},
|
|
270
|
+
AGGREGATOR_TIMEOUT_MS,
|
|
271
|
+
);
|
|
272
|
+
s.save(await s.session.getMessages());
|
|
273
|
+
},
|
|
274
|
+
);
|
|
275
|
+
})
|
|
276
|
+
.compile();
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Determine how many parallel explorer sub-agents to spawn for the
|
|
3
|
+
* deep-research-codebase workflow, based on lines of code in the codebase.
|
|
4
|
+
*
|
|
5
|
+
* The heuristic balances coverage against coordination overhead:
|
|
6
|
+
* - Too few explorers leave parts of the codebase under-investigated.
|
|
7
|
+
* - Too many explorers flood the aggregator with redundant findings,
|
|
8
|
+
* burn tokens on coordination, and exhaust tmux/process budgets.
|
|
9
|
+
*
|
|
10
|
+
* Tier choices were anchored to the rough sizes of common project shapes:
|
|
11
|
+
*
|
|
12
|
+
* < 5,000 LOC → 2 explorers scripts, single-purpose tools
|
|
13
|
+
* < 25,000 LOC → 3 explorers small libraries, CLI utilities
|
|
14
|
+
* < 100,000 LOC → 5 explorers medium applications
|
|
15
|
+
* < 500,000 LOC → 7 explorers large applications, small monorepos
|
|
16
|
+
* <2,000,000 LOC → 9 explorers large monorepos
|
|
17
|
+
* ≥2,000,000 LOC → 12 explorers massive monorepos (hard cap)
|
|
18
|
+
*
|
|
19
|
+
* The hard cap of 12 prevents runaway parallelism: each explorer is a
|
|
20
|
+
* Claude tmux pane plus an LLM session, so the cost grows linearly in
|
|
21
|
+
* tokens, processes, and walltime as well as in aggregator context.
|
|
22
|
+
*/
|
|
23
|
+
export function calculateExplorerCount(loc: number): number {
|
|
24
|
+
if (!Number.isFinite(loc) || loc <= 0) return 2;
|
|
25
|
+
if (loc < 5_000) return 2;
|
|
26
|
+
if (loc < 25_000) return 3;
|
|
27
|
+
if (loc < 100_000) return 5;
|
|
28
|
+
if (loc < 500_000) return 7;
|
|
29
|
+
if (loc < 2_000_000) return 9;
|
|
30
|
+
return 12;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/** Human-readable rationale for the heuristic decision — surfaced in logs/prompts. */
|
|
34
|
+
export function explainHeuristic(loc: number, count: number): string {
|
|
35
|
+
return `Codebase: ${loc.toLocaleString()} LOC → spawning ${count} parallel explorer${
|
|
36
|
+
count === 1 ? "" : "s"
|
|
37
|
+
}.`;
|
|
38
|
+
}
|