@bastani/atomic 0.5.13 → 0.5.14
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/.claude/agents/planner.md +256 -67
- package/.github/agents/planner.md +262 -88
- package/.opencode/agents/planner.md +270 -107
- package/dist/sdk/components/workflow-picker-panel.d.ts.map +1 -1
- package/dist/sdk/runtime/discovery.d.ts +6 -3
- package/dist/sdk/runtime/discovery.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.map +1 -1
- package/dist/sdk/workflows/builtin/deep-research-codebase/helpers/heuristic.d.ts +5 -17
- package/dist/sdk/workflows/builtin/deep-research-codebase/helpers/heuristic.d.ts.map +1 -1
- package/dist/sdk/workflows/builtin/ralph/claude/index.d.ts.map +1 -1
- package/dist/sdk/workflows/builtin/ralph/copilot/index.d.ts.map +1 -1
- package/dist/sdk/workflows/builtin/ralph/helpers/prompts.d.ts +18 -15
- package/dist/sdk/workflows/builtin/ralph/helpers/prompts.d.ts.map +1 -1
- package/dist/services/config/definitions.d.ts.map +1 -1
- package/package.json +4 -2
- package/src/sdk/components/workflow-picker-panel.tsx +34 -43
- package/src/sdk/runtime/discovery.ts +13 -41
- package/src/sdk/runtime/executor.ts +1 -0
- package/src/sdk/workflows/builtin/deep-research-codebase/claude/index.ts +42 -24
- package/src/sdk/workflows/builtin/deep-research-codebase/helpers/heuristic.ts +9 -23
- package/src/sdk/workflows/builtin/ralph/claude/index.ts +63 -37
- package/src/sdk/workflows/builtin/ralph/copilot/index.ts +12 -4
- package/src/sdk/workflows/builtin/ralph/helpers/prompts.ts +267 -155
- package/src/services/config/definitions.ts +3 -1
|
@@ -556,42 +556,56 @@ function EmptyPreview({
|
|
|
556
556
|
|
|
557
557
|
const TEXT_FIELD_LINES = 3;
|
|
558
558
|
|
|
559
|
-
|
|
560
|
-
const NOOP_CHANGE_REF: React.RefObject<((value: string) => void) | null> = { current: null };
|
|
561
|
-
|
|
562
559
|
function TextAreaContent({
|
|
563
560
|
value,
|
|
564
561
|
placeholder,
|
|
565
562
|
focused,
|
|
566
|
-
|
|
563
|
+
onInput,
|
|
567
564
|
}: {
|
|
568
565
|
value: string;
|
|
569
566
|
placeholder: string;
|
|
570
567
|
focused: boolean;
|
|
571
|
-
|
|
568
|
+
onInput: (value: string) => void;
|
|
572
569
|
}) {
|
|
573
570
|
const theme = usePickerTheme();
|
|
574
|
-
const
|
|
575
|
-
const
|
|
571
|
+
const instanceRef = useRef<TextareaRenderable | null>(null);
|
|
572
|
+
const onInputRef = useLatest(onInput);
|
|
573
|
+
const lastTextRef = useRef(value);
|
|
576
574
|
|
|
577
|
-
|
|
575
|
+
const refCallback = useCallback((instance: TextareaRenderable | null) => {
|
|
576
|
+
instanceRef.current = instance;
|
|
577
|
+
}, []);
|
|
578
|
+
|
|
579
|
+
// Sync external value → textarea when it diverges (e.g. initial value
|
|
580
|
+
// or reset after phase transition).
|
|
578
581
|
useEffect(() => {
|
|
579
|
-
if (
|
|
580
|
-
|
|
582
|
+
if (instanceRef.current && instanceRef.current.plainText !== value) {
|
|
583
|
+
instanceRef.current.setText(value);
|
|
584
|
+
lastTextRef.current = value;
|
|
581
585
|
}
|
|
582
586
|
}, [value]);
|
|
583
587
|
|
|
584
|
-
//
|
|
585
|
-
//
|
|
586
|
-
//
|
|
587
|
-
//
|
|
588
|
-
//
|
|
589
|
-
//
|
|
590
|
-
|
|
591
|
-
|
|
588
|
+
// Detect text changes after each keypress. The native Zig edit buffer's
|
|
589
|
+
// "content-changed" event is unreliable for propagating to the JS
|
|
590
|
+
// _contentChangeListener in certain installed environments. Instead,
|
|
591
|
+
// we hook into useKeyboard (which fires before the textarea processes
|
|
592
|
+
// the key) and defer the read with queueMicrotask so the textarea has
|
|
593
|
+
// processed the keystroke by the time we read plainText.
|
|
594
|
+
useKeyboard(useCallback(() => {
|
|
595
|
+
queueMicrotask(() => {
|
|
596
|
+
const inst = instanceRef.current;
|
|
597
|
+
if (!inst) return;
|
|
598
|
+
const current = inst.plainText;
|
|
599
|
+
if (current !== lastTextRef.current) {
|
|
600
|
+
lastTextRef.current = current;
|
|
601
|
+
onInputRef.current(current);
|
|
602
|
+
}
|
|
603
|
+
});
|
|
604
|
+
}, []));
|
|
605
|
+
|
|
592
606
|
return (
|
|
593
607
|
<textarea
|
|
594
|
-
ref={
|
|
608
|
+
ref={refCallback}
|
|
595
609
|
initialValue={value}
|
|
596
610
|
placeholder={placeholder}
|
|
597
611
|
focused={focused}
|
|
@@ -602,9 +616,6 @@ function TextAreaContent({
|
|
|
602
616
|
placeholderColor={theme.textDim}
|
|
603
617
|
wrapMode="word"
|
|
604
618
|
flexGrow={1}
|
|
605
|
-
onContentChange={() => {
|
|
606
|
-
changeRef.current?.(ref.current?.plainText ?? "");
|
|
607
|
-
}}
|
|
608
619
|
/>
|
|
609
620
|
);
|
|
610
621
|
}
|
|
@@ -685,14 +696,12 @@ const Field = memo(function Field({
|
|
|
685
696
|
value,
|
|
686
697
|
focused,
|
|
687
698
|
onFieldInput,
|
|
688
|
-
onTextChangeRef,
|
|
689
699
|
}: {
|
|
690
700
|
id?: string;
|
|
691
701
|
field: WorkflowInput;
|
|
692
702
|
value: string;
|
|
693
703
|
focused: boolean;
|
|
694
704
|
onFieldInput: (fieldName: string, value: string) => void;
|
|
695
|
-
onTextChangeRef?: React.RefObject<((value: string) => void) | null>;
|
|
696
705
|
}) {
|
|
697
706
|
const theme = usePickerTheme();
|
|
698
707
|
const borderCol = focused ? theme.primary : theme.border;
|
|
@@ -730,7 +739,7 @@ const Field = memo(function Field({
|
|
|
730
739
|
value={value}
|
|
731
740
|
placeholder={field.placeholder ?? ""}
|
|
732
741
|
focused={focused}
|
|
733
|
-
|
|
742
|
+
onInput={onInput}
|
|
734
743
|
/>
|
|
735
744
|
) : field.type === "string" ? (
|
|
736
745
|
<StringContent
|
|
@@ -769,7 +778,6 @@ function InputPhase({
|
|
|
769
778
|
values,
|
|
770
779
|
focusedFieldIdx,
|
|
771
780
|
onFieldInput,
|
|
772
|
-
onTextChangeRef,
|
|
773
781
|
}: {
|
|
774
782
|
workflow: WorkflowWithMetadata;
|
|
775
783
|
agent: AgentType;
|
|
@@ -777,7 +785,6 @@ function InputPhase({
|
|
|
777
785
|
values: Record<string, string>;
|
|
778
786
|
focusedFieldIdx: number;
|
|
779
787
|
onFieldInput: (fieldName: string, value: string) => void;
|
|
780
|
-
onTextChangeRef: React.RefObject<((value: string) => void) | null>;
|
|
781
788
|
}) {
|
|
782
789
|
const theme = usePickerTheme();
|
|
783
790
|
const isStructured = workflow.inputs.length > 0;
|
|
@@ -924,11 +931,6 @@ function InputPhase({
|
|
|
924
931
|
value={values[f.name] ?? ""}
|
|
925
932
|
focused={active}
|
|
926
933
|
onFieldInput={onFieldInput}
|
|
927
|
-
onTextChangeRef={
|
|
928
|
-
f.type === "text" && active
|
|
929
|
-
? onTextChangeRef
|
|
930
|
-
: undefined
|
|
931
|
-
}
|
|
932
934
|
/>
|
|
933
935
|
);
|
|
934
936
|
})}
|
|
@@ -1371,16 +1373,6 @@ export function WorkflowPicker({
|
|
|
1371
1373
|
}, [currentFields, fieldValues]);
|
|
1372
1374
|
const isFormValid = invalidFieldIndices.length === 0;
|
|
1373
1375
|
|
|
1374
|
-
// Textarea change callback ref — useLatest keeps .current in sync
|
|
1375
|
-
// each render so the textarea effect doesn't need to re-attach.
|
|
1376
|
-
const textChangeRef = useLatest(
|
|
1377
|
-
currentField
|
|
1378
|
-
? (text: string) => {
|
|
1379
|
-
setFieldValues((prev) => ({ ...prev, [currentField.name]: text }));
|
|
1380
|
-
}
|
|
1381
|
-
: null,
|
|
1382
|
-
);
|
|
1383
|
-
|
|
1384
1376
|
// Stable callback for field input — the setter is referentially stable.
|
|
1385
1377
|
const onFieldInput = useCallback(
|
|
1386
1378
|
(name: string, v: string) => setFieldValues((prev) => ({ ...prev, [name]: v })),
|
|
@@ -1466,7 +1458,6 @@ export function WorkflowPicker({
|
|
|
1466
1458
|
values={fieldValues}
|
|
1467
1459
|
focusedFieldIdx={confirmOpen ? -1 : focusedFieldIdx}
|
|
1468
1460
|
onFieldInput={onFieldInput}
|
|
1469
|
-
onTextChangeRef={textChangeRef}
|
|
1470
1461
|
/>
|
|
1471
1462
|
) : null}
|
|
1472
1463
|
|
|
@@ -2,10 +2,13 @@
|
|
|
2
2
|
* Workflow discovery — finds workflow definitions from disk.
|
|
3
3
|
*
|
|
4
4
|
* Workflows are discovered from:
|
|
5
|
-
* 1.
|
|
6
|
-
* 2.
|
|
5
|
+
* 1. src/sdk/workflows/builtin/<name>/<agent>/index.ts (SDK-shipped builtins)
|
|
6
|
+
* 2. .atomic/workflows/<name>/<agent>/index.ts (project-local)
|
|
7
|
+
* 3. ~/.atomic/workflows/<name>/<agent>/index.ts (global)
|
|
7
8
|
*
|
|
8
|
-
*
|
|
9
|
+
* All three sources use the same scanning function (`discoverFromBaseDir`)
|
|
10
|
+
* so registration is uniform. Builtin names are reserved — project-local
|
|
11
|
+
* and global workflows with the same name are dropped during merge.
|
|
9
12
|
*/
|
|
10
13
|
|
|
11
14
|
import { join } from "node:path";
|
|
@@ -77,7 +80,7 @@ async function loadWorkflowsGitignore(workflowsDir: string): Promise<ignore.Igno
|
|
|
77
80
|
*/
|
|
78
81
|
async function discoverFromBaseDir(
|
|
79
82
|
baseDir: string,
|
|
80
|
-
source: "local" | "global",
|
|
83
|
+
source: "local" | "global" | "builtin",
|
|
81
84
|
agentFilter?: AgentType,
|
|
82
85
|
): Promise<DiscoveredWorkflow[]> {
|
|
83
86
|
const workflows: DiscoveredWorkflow[] = [];
|
|
@@ -91,7 +94,11 @@ async function discoverFromBaseDir(
|
|
|
91
94
|
return workflows;
|
|
92
95
|
}
|
|
93
96
|
|
|
94
|
-
|
|
97
|
+
// Builtin workflows live inside the SDK source tree — skip .gitignore
|
|
98
|
+
// handling to avoid writing files into node_modules or the SDK dir.
|
|
99
|
+
const ig = source === "builtin"
|
|
100
|
+
? ignore()
|
|
101
|
+
: await loadWorkflowsGitignore(baseDir);
|
|
95
102
|
|
|
96
103
|
for (const wfEntry of workflowEntries) {
|
|
97
104
|
if (!wfEntry.isDirectory()) continue;
|
|
@@ -140,41 +147,6 @@ const BUILTIN_WORKFLOWS_DIR = join(
|
|
|
140
147
|
Bun.fileURLToPath(new URL("../workflows/builtin", import.meta.url)),
|
|
141
148
|
);
|
|
142
149
|
|
|
143
|
-
/**
|
|
144
|
-
* Discover built-in workflows shipped as SDK modules.
|
|
145
|
-
*
|
|
146
|
-
* Scans `src/sdk/workflows/builtin/<name>/<agent>/index.ts` for known
|
|
147
|
-
* workflow directories. Returns entries with `source: "builtin"`.
|
|
148
|
-
*/
|
|
149
|
-
async function discoverBuiltinWorkflows(
|
|
150
|
-
agentFilter?: AgentType,
|
|
151
|
-
): Promise<DiscoveredWorkflow[]> {
|
|
152
|
-
const results: DiscoveredWorkflow[] = [];
|
|
153
|
-
const agents = agentFilter ? [agentFilter] : AGENTS;
|
|
154
|
-
|
|
155
|
-
let workflowEntries;
|
|
156
|
-
try {
|
|
157
|
-
workflowEntries = await readdir(BUILTIN_WORKFLOWS_DIR, { withFileTypes: true });
|
|
158
|
-
} catch {
|
|
159
|
-
return results;
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
const workflowNames = workflowEntries
|
|
163
|
-
.filter((d) => d.isDirectory())
|
|
164
|
-
.map((d) => d.name);
|
|
165
|
-
|
|
166
|
-
for (const name of workflowNames) {
|
|
167
|
-
for (const agent of agents) {
|
|
168
|
-
const indexPath = join(BUILTIN_WORKFLOWS_DIR, name, agent, "index.ts");
|
|
169
|
-
if (await Bun.file(indexPath).exists()) {
|
|
170
|
-
results.push({ name, agent, path: indexPath, source: "builtin" });
|
|
171
|
-
}
|
|
172
|
-
}
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
return results;
|
|
176
|
-
}
|
|
177
|
-
|
|
178
150
|
/**
|
|
179
151
|
* Discover all available workflows from built-in, global, and local sources.
|
|
180
152
|
* Optionally filter by agent.
|
|
@@ -221,7 +193,7 @@ export async function discoverWorkflows(
|
|
|
221
193
|
// reserved by a builtin `ralph` for claude, even when the discovery
|
|
222
194
|
// call was filtered to copilot.
|
|
223
195
|
const [allBuiltins, globalResults, localResults] = await Promise.all([
|
|
224
|
-
|
|
196
|
+
discoverFromBaseDir(BUILTIN_WORKFLOWS_DIR, "builtin"),
|
|
225
197
|
discoverFromBaseDir(globalDir, "global", agentFilter),
|
|
226
198
|
discoverFromBaseDir(localDir, "local", agentFilter),
|
|
227
199
|
]);
|
|
@@ -85,13 +85,18 @@ import {
|
|
|
85
85
|
// until Claude reports idle or a result (success, error_max_turns, etc.).
|
|
86
86
|
|
|
87
87
|
export default defineWorkflow({
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
88
|
+
name: "deep-research-codebase",
|
|
89
|
+
description:
|
|
90
|
+
"Deterministic deep codebase research: scout → LOC-driven parallel explorers → aggregator",
|
|
91
|
+
inputs: [
|
|
92
|
+
{
|
|
93
|
+
name: "prompt",
|
|
94
|
+
type: "text",
|
|
95
|
+
required: true,
|
|
96
|
+
description: "research question",
|
|
97
|
+
},
|
|
98
|
+
],
|
|
99
|
+
})
|
|
95
100
|
.for<"claude">()
|
|
96
101
|
.run(async (ctx) => {
|
|
97
102
|
// Destructure once so every stage below can close over a bare
|
|
@@ -115,7 +120,8 @@ export default defineWorkflow({
|
|
|
115
120
|
ctx.stage(
|
|
116
121
|
{
|
|
117
122
|
name: "codebase-scout",
|
|
118
|
-
description:
|
|
123
|
+
description:
|
|
124
|
+
"Map codebase, count LOC, partition for parallel explorers",
|
|
119
125
|
},
|
|
120
126
|
{},
|
|
121
127
|
{},
|
|
@@ -175,29 +181,28 @@ export default defineWorkflow({
|
|
|
175
181
|
ctx.stage(
|
|
176
182
|
{
|
|
177
183
|
name: "research-history",
|
|
178
|
-
description:
|
|
184
|
+
description:
|
|
185
|
+
"Surface prior research via research-locator + research-analyzer",
|
|
186
|
+
},
|
|
187
|
+
{
|
|
188
|
+
chatFlags: [
|
|
189
|
+
"--allow-dangerously-skip-permissions",
|
|
190
|
+
"--dangerously-skip-permissions",
|
|
191
|
+
],
|
|
179
192
|
},
|
|
180
|
-
{},
|
|
181
193
|
{},
|
|
182
194
|
async (s) => {
|
|
183
195
|
// Dispatches codebase-research-locator → codebase-research-analyzer
|
|
184
196
|
// over the project's research/ directory and outputs a ≤400-word
|
|
185
197
|
// synthesis as prose (no file write — consumed via transcript).
|
|
186
|
-
await s.session.query(
|
|
187
|
-
buildHistoryPrompt({ question: prompt, root }),
|
|
188
|
-
);
|
|
198
|
+
await s.session.query(buildHistoryPrompt({ question: prompt, root }));
|
|
189
199
|
s.save(s.sessionId);
|
|
190
200
|
},
|
|
191
201
|
),
|
|
192
202
|
]);
|
|
193
203
|
|
|
194
|
-
const {
|
|
195
|
-
|
|
196
|
-
explorerCount,
|
|
197
|
-
scratchDir,
|
|
198
|
-
totalLoc,
|
|
199
|
-
totalFiles,
|
|
200
|
-
} = scout.result;
|
|
204
|
+
const { partitions, explorerCount, scratchDir, totalLoc, totalFiles } =
|
|
205
|
+
scout.result;
|
|
201
206
|
|
|
202
207
|
// Pull both scout transcripts ONCE at the workflow level so every
|
|
203
208
|
// explorer + the aggregator can embed them in their prompts. Both
|
|
@@ -229,9 +234,16 @@ export default defineWorkflow({
|
|
|
229
234
|
name: `explorer-${i}`,
|
|
230
235
|
description: `Explore ${partition
|
|
231
236
|
.map((u) => u.path)
|
|
232
|
-
.join(
|
|
237
|
+
.join(
|
|
238
|
+
", ",
|
|
239
|
+
)} (${partition.reduce((s, u) => s + u.fileCount, 0)} files)`,
|
|
240
|
+
},
|
|
241
|
+
{
|
|
242
|
+
chatFlags: [
|
|
243
|
+
"--allow-dangerously-skip-permissions",
|
|
244
|
+
"--dangerously-skip-permissions",
|
|
245
|
+
],
|
|
233
246
|
},
|
|
234
|
-
{},
|
|
235
247
|
{},
|
|
236
248
|
async (s) => {
|
|
237
249
|
await s.session.query(
|
|
@@ -278,9 +290,15 @@ export default defineWorkflow({
|
|
|
278
290
|
await ctx.stage(
|
|
279
291
|
{
|
|
280
292
|
name: "aggregator",
|
|
281
|
-
description:
|
|
293
|
+
description:
|
|
294
|
+
"Synthesize explorer findings + history into final research doc",
|
|
295
|
+
},
|
|
296
|
+
{
|
|
297
|
+
chatFlags: [
|
|
298
|
+
"--allow-dangerously-skip-permissions",
|
|
299
|
+
"--dangerously-skip-permissions",
|
|
300
|
+
],
|
|
282
301
|
},
|
|
283
|
-
{},
|
|
284
302
|
{},
|
|
285
303
|
async (s) => {
|
|
286
304
|
await s.session.query(
|
|
@@ -1,33 +1,19 @@
|
|
|
1
|
+
/** Target LOC per explorer sub-agent. */
|
|
2
|
+
const LOC_PER_EXPLORER = 2_500;
|
|
3
|
+
|
|
1
4
|
/**
|
|
2
5
|
* Determine how many parallel explorer sub-agents to spawn for the
|
|
3
6
|
* deep-research-codebase workflow, based on lines of code in the codebase.
|
|
4
7
|
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
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.
|
|
8
|
+
* Scales linearly: one explorer per `LOC_PER_EXPLORER` (2.5K) lines of code,
|
|
9
|
+
* with a floor of 2 for tiny or empty codebases. The actual number of
|
|
10
|
+
* spawned explorers is still bounded by the number of partition units
|
|
11
|
+
* the scout finds (see `partitionUnits` in ./scout.ts), so we never get
|
|
12
|
+
* more explorers than the natural granularity of the codebase allows.
|
|
22
13
|
*/
|
|
23
14
|
export function calculateExplorerCount(loc: number): number {
|
|
24
15
|
if (!Number.isFinite(loc) || loc <= 0) return 2;
|
|
25
|
-
|
|
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;
|
|
16
|
+
return Math.max(2, Math.ceil(loc / LOC_PER_EXPLORER));
|
|
31
17
|
}
|
|
32
18
|
|
|
33
19
|
/** Human-readable rationale for the heuristic decision — surfaced in logs/prompts. */
|
|
@@ -66,7 +66,8 @@ async function queryWithStructuredOutput(
|
|
|
66
66
|
msg.subtype === "success" &&
|
|
67
67
|
(msg as Record<string, unknown>).structured_output
|
|
68
68
|
) {
|
|
69
|
-
structured = (msg as Record<string, unknown>)
|
|
69
|
+
structured = (msg as Record<string, unknown>)
|
|
70
|
+
.structured_output as ReviewResult;
|
|
70
71
|
}
|
|
71
72
|
}
|
|
72
73
|
}
|
|
@@ -82,7 +83,12 @@ export default defineWorkflow({
|
|
|
82
83
|
description:
|
|
83
84
|
"Plan → orchestrate → review → debug loop with bounded iteration",
|
|
84
85
|
inputs: [
|
|
85
|
-
{
|
|
86
|
+
{
|
|
87
|
+
name: "prompt",
|
|
88
|
+
type: "text",
|
|
89
|
+
required: true,
|
|
90
|
+
description: "task prompt",
|
|
91
|
+
},
|
|
86
92
|
],
|
|
87
93
|
})
|
|
88
94
|
.for<"claude">()
|
|
@@ -94,7 +100,14 @@ export default defineWorkflow({
|
|
|
94
100
|
// ── Plan ────────────────────────────────────────────────────────────
|
|
95
101
|
await ctx.stage(
|
|
96
102
|
{ name: `planner-${iteration}` },
|
|
97
|
-
{
|
|
103
|
+
{
|
|
104
|
+
chatFlags: [
|
|
105
|
+
"--agent",
|
|
106
|
+
"planner",
|
|
107
|
+
"--allow-dangerously-skip-permissions",
|
|
108
|
+
"--dangerously-skip-permissions",
|
|
109
|
+
],
|
|
110
|
+
},
|
|
98
111
|
{},
|
|
99
112
|
async (s) => {
|
|
100
113
|
await s.session.query(
|
|
@@ -110,7 +123,14 @@ export default defineWorkflow({
|
|
|
110
123
|
// ── Orchestrate ─────────────────────────────────────────────────────
|
|
111
124
|
await ctx.stage(
|
|
112
125
|
{ name: `orchestrator-${iteration}` },
|
|
113
|
-
{
|
|
126
|
+
{
|
|
127
|
+
chatFlags: [
|
|
128
|
+
"--agent",
|
|
129
|
+
"orchestrator",
|
|
130
|
+
"--allow-dangerously-skip-permissions",
|
|
131
|
+
"--dangerously-skip-permissions",
|
|
132
|
+
],
|
|
133
|
+
},
|
|
114
134
|
{},
|
|
115
135
|
async (s) => {
|
|
116
136
|
await s.session.query(buildOrchestratorPrompt(prompt));
|
|
@@ -128,10 +148,11 @@ export default defineWorkflow({
|
|
|
128
148
|
{},
|
|
129
149
|
{},
|
|
130
150
|
async (s) => {
|
|
131
|
-
const result = await s.session.query(
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
151
|
+
const result = await s.session.query(discoveryPrompts.locator, {
|
|
152
|
+
agent: "codebase-locator",
|
|
153
|
+
permissionMode: "bypassPermissions",
|
|
154
|
+
allowDangerouslySkipPermissions: true,
|
|
155
|
+
});
|
|
135
156
|
s.save(s.sessionId);
|
|
136
157
|
return extractAssistantText(result, 0);
|
|
137
158
|
},
|
|
@@ -141,10 +162,11 @@ export default defineWorkflow({
|
|
|
141
162
|
{},
|
|
142
163
|
{},
|
|
143
164
|
async (s) => {
|
|
144
|
-
const result = await s.session.query(
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
165
|
+
const result = await s.session.query(discoveryPrompts.analyzer, {
|
|
166
|
+
agent: "codebase-analyzer",
|
|
167
|
+
permissionMode: "bypassPermissions",
|
|
168
|
+
allowDangerouslySkipPermissions: true,
|
|
169
|
+
});
|
|
148
170
|
s.save(s.sessionId);
|
|
149
171
|
return extractAssistantText(result, 0);
|
|
150
172
|
},
|
|
@@ -156,7 +178,11 @@ export default defineWorkflow({
|
|
|
156
178
|
async (s) => {
|
|
157
179
|
const result = await s.session.query(
|
|
158
180
|
discoveryPrompts.patternFinder,
|
|
159
|
-
{
|
|
181
|
+
{
|
|
182
|
+
agent: "codebase-pattern-finder",
|
|
183
|
+
permissionMode: "bypassPermissions",
|
|
184
|
+
allowDangerouslySkipPermissions: true,
|
|
185
|
+
},
|
|
160
186
|
);
|
|
161
187
|
s.save(s.sessionId);
|
|
162
188
|
return extractAssistantText(result, 0);
|
|
@@ -165,9 +191,12 @@ export default defineWorkflow({
|
|
|
165
191
|
]);
|
|
166
192
|
|
|
167
193
|
const discoveryContext = [
|
|
168
|
-
"### Infrastructure Files (codebase-locator)\n\n" +
|
|
169
|
-
|
|
170
|
-
"###
|
|
194
|
+
"### Infrastructure Files (codebase-locator)\n\n" +
|
|
195
|
+
locatorResult.result,
|
|
196
|
+
"### Infrastructure Analysis (codebase-analyzer)\n\n" +
|
|
197
|
+
analyzerResult.result,
|
|
198
|
+
"### Build & Test Patterns (codebase-pattern-finder)\n\n" +
|
|
199
|
+
patternResult.result,
|
|
171
200
|
].join("\n\n---\n\n");
|
|
172
201
|
|
|
173
202
|
// ── Review (two parallel passes) ────────────────────────────────────
|
|
@@ -178,26 +207,16 @@ export default defineWorkflow({
|
|
|
178
207
|
});
|
|
179
208
|
|
|
180
209
|
const [reviewA, reviewB] = await Promise.all([
|
|
181
|
-
ctx.stage(
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
),
|
|
191
|
-
ctx.stage(
|
|
192
|
-
{ name: `reviewer-${iteration}-b` },
|
|
193
|
-
{},
|
|
194
|
-
{},
|
|
195
|
-
async (s) => {
|
|
196
|
-
const result = await queryWithStructuredOutput(reviewPrompt);
|
|
197
|
-
s.save(s.sessionId);
|
|
198
|
-
return result;
|
|
199
|
-
},
|
|
200
|
-
),
|
|
210
|
+
ctx.stage({ name: `reviewer-${iteration}-a` }, {}, {}, async (s) => {
|
|
211
|
+
const result = await queryWithStructuredOutput(reviewPrompt);
|
|
212
|
+
s.save(s.sessionId);
|
|
213
|
+
return result;
|
|
214
|
+
}),
|
|
215
|
+
ctx.stage({ name: `reviewer-${iteration}-b` }, {}, {}, async (s) => {
|
|
216
|
+
const result = await queryWithStructuredOutput(reviewPrompt);
|
|
217
|
+
s.save(s.sessionId);
|
|
218
|
+
return result;
|
|
219
|
+
}),
|
|
201
220
|
]);
|
|
202
221
|
|
|
203
222
|
const merged = mergeReviewResults(reviewA.result, reviewB.result);
|
|
@@ -211,7 +230,14 @@ export default defineWorkflow({
|
|
|
211
230
|
if (iteration < MAX_LOOPS) {
|
|
212
231
|
const debugger_ = await ctx.stage(
|
|
213
232
|
{ name: `debugger-${iteration}` },
|
|
214
|
-
{
|
|
233
|
+
{
|
|
234
|
+
chatFlags: [
|
|
235
|
+
"--agent",
|
|
236
|
+
"debugger",
|
|
237
|
+
"--allow-dangerously-skip-permissions",
|
|
238
|
+
"--dangerously-skip-permissions",
|
|
239
|
+
],
|
|
240
|
+
},
|
|
215
241
|
{},
|
|
216
242
|
async (s) => {
|
|
217
243
|
const result = await s.session.query(
|
|
@@ -79,7 +79,12 @@ export default defineWorkflow({
|
|
|
79
79
|
description:
|
|
80
80
|
"Plan → orchestrate → review → debug loop with bounded iteration",
|
|
81
81
|
inputs: [
|
|
82
|
-
{
|
|
82
|
+
{
|
|
83
|
+
name: "prompt",
|
|
84
|
+
type: "text",
|
|
85
|
+
required: true,
|
|
86
|
+
description: "task prompt",
|
|
87
|
+
},
|
|
83
88
|
],
|
|
84
89
|
})
|
|
85
90
|
.for<"copilot">()
|
|
@@ -162,9 +167,12 @@ export default defineWorkflow({
|
|
|
162
167
|
]);
|
|
163
168
|
|
|
164
169
|
const discoveryContext = [
|
|
165
|
-
"### Infrastructure Files (codebase-locator)\n\n" +
|
|
166
|
-
|
|
167
|
-
"###
|
|
170
|
+
"### Infrastructure Files (codebase-locator)\n\n" +
|
|
171
|
+
locatorResult.result,
|
|
172
|
+
"### Infrastructure Analysis (codebase-analyzer)\n\n" +
|
|
173
|
+
analyzerResult.result,
|
|
174
|
+
"### Build & Test Patterns (codebase-pattern-finder)\n\n" +
|
|
175
|
+
patternResult.result,
|
|
168
176
|
].join("\n\n---\n\n");
|
|
169
177
|
|
|
170
178
|
// ── Review (two parallel passes) ──────────────────────────────────
|