@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.
Files changed (25) hide show
  1. package/.claude/agents/planner.md +256 -67
  2. package/.github/agents/planner.md +262 -88
  3. package/.opencode/agents/planner.md +270 -107
  4. package/dist/sdk/components/workflow-picker-panel.d.ts.map +1 -1
  5. package/dist/sdk/runtime/discovery.d.ts +6 -3
  6. package/dist/sdk/runtime/discovery.d.ts.map +1 -1
  7. package/dist/sdk/runtime/executor.d.ts.map +1 -1
  8. package/dist/sdk/workflows/builtin/deep-research-codebase/claude/index.d.ts.map +1 -1
  9. package/dist/sdk/workflows/builtin/deep-research-codebase/helpers/heuristic.d.ts +5 -17
  10. package/dist/sdk/workflows/builtin/deep-research-codebase/helpers/heuristic.d.ts.map +1 -1
  11. package/dist/sdk/workflows/builtin/ralph/claude/index.d.ts.map +1 -1
  12. package/dist/sdk/workflows/builtin/ralph/copilot/index.d.ts.map +1 -1
  13. package/dist/sdk/workflows/builtin/ralph/helpers/prompts.d.ts +18 -15
  14. package/dist/sdk/workflows/builtin/ralph/helpers/prompts.d.ts.map +1 -1
  15. package/dist/services/config/definitions.d.ts.map +1 -1
  16. package/package.json +4 -2
  17. package/src/sdk/components/workflow-picker-panel.tsx +34 -43
  18. package/src/sdk/runtime/discovery.ts +13 -41
  19. package/src/sdk/runtime/executor.ts +1 -0
  20. package/src/sdk/workflows/builtin/deep-research-codebase/claude/index.ts +42 -24
  21. package/src/sdk/workflows/builtin/deep-research-codebase/helpers/heuristic.ts +9 -23
  22. package/src/sdk/workflows/builtin/ralph/claude/index.ts +63 -37
  23. package/src/sdk/workflows/builtin/ralph/copilot/index.ts +12 -4
  24. package/src/sdk/workflows/builtin/ralph/helpers/prompts.ts +267 -155
  25. 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
- onChangeRef,
563
+ onInput,
567
564
  }: {
568
565
  value: string;
569
566
  placeholder: string;
570
567
  focused: boolean;
571
- onChangeRef?: React.RefObject<((value: string) => void) | null>;
568
+ onInput: (value: string) => void;
572
569
  }) {
573
570
  const theme = usePickerTheme();
574
- const ref = useRef<TextareaRenderable>(null);
575
- const changeRef = onChangeRef ?? NOOP_CHANGE_REF;
571
+ const instanceRef = useRef<TextareaRenderable | null>(null);
572
+ const onInputRef = useLatest(onInput);
573
+ const lastTextRef = useRef(value);
576
574
 
577
- // Sync external value textarea when it diverges (e.g. initial value).
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 (ref.current && ref.current.plainText !== value) {
580
- ref.current.setText(value);
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
- // Wire onContentChange as a prop so the reconciler sets it during the
585
- // commit phase (via the constructor and setProperty's default branch),
586
- // guaranteeing it is active before the textarea can receive key events.
587
- // The previous useEffect approach deferred setup as a passive effect
588
- // (ConcurrentRoot schedules these via setTimeout) creating a window
589
- // where keystrokes reached the focused textarea before the listener
590
- // existed, silently dropping content-change notifications and leaving
591
- // fieldValues stale.
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={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
- onChangeRef={onTextChangeRef}
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. .atomic/workflows/<name>/<agent>/index.ts (project-local)
6
- * 2. ~/.atomic/workflows/<name>/<agent>/index.ts (global)
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
- * Project-local workflows take precedence over global ones with the same name.
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
- const ig = await loadWorkflowsGitignore(baseDir);
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
- discoverBuiltinWorkflows(),
196
+ discoverFromBaseDir(BUILTIN_WORKFLOWS_DIR, "builtin"),
225
197
  discoverFromBaseDir(globalDir, "global", agentFilter),
226
198
  discoverFromBaseDir(localDir, "local", agentFilter),
227
199
  ]);
@@ -85,6 +85,7 @@ const AGENT_CLI: Record<
85
85
  // which the idle detection in claude.ts watches for to know when the
86
86
  // agent has finished processing a prompt.
87
87
  CLAUDE_CODE_EMIT_SESSION_STATE_EVENTS: "1",
88
+ CLAUDE_CODE_NO_FLICKER: "1",
88
89
  },
89
90
  },
90
91
  };
@@ -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
- name: "deep-research-codebase",
89
- description:
90
- "Deterministic deep codebase research: scout → LOC-driven parallel explorers → aggregator",
91
- inputs: [
92
- { name: "prompt", type: "text", required: true, description: "research question" },
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: "Map codebase, count LOC, partition for parallel explorers",
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: "Surface prior research via research-locator + research-analyzer",
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
- partitions,
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(", ")} (${partition.reduce((s, u) => s + u.fileCount, 0)} files)`,
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: "Synthesize explorer findings + history into final research doc",
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
- * 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.
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
- 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;
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>).structured_output as ReviewResult;
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
- { name: "prompt", type: "text", required: true, description: "task prompt" },
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
- { chatFlags: ["--agent", "planner", "--allow-dangerously-skip-permissions", "--dangerously-skip-permissions"] },
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
- { chatFlags: ["--agent", "orchestrator", "--allow-dangerously-skip-permissions", "--dangerously-skip-permissions"] },
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
- discoveryPrompts.locator,
133
- { agent: "codebase-locator", permissionMode: "bypassPermissions", allowDangerouslySkipPermissions: true },
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
- discoveryPrompts.analyzer,
146
- { agent: "codebase-analyzer", permissionMode: "bypassPermissions", allowDangerouslySkipPermissions: true },
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
- { agent: "codebase-pattern-finder", permissionMode: "bypassPermissions", allowDangerouslySkipPermissions: true },
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" + locatorResult.result,
169
- "### Infrastructure Analysis (codebase-analyzer)\n\n" + analyzerResult.result,
170
- "### Build & Test Patterns (codebase-pattern-finder)\n\n" + patternResult.result,
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
- { name: `reviewer-${iteration}-a` },
183
- {},
184
- {},
185
- async (s) => {
186
- const result = await queryWithStructuredOutput(reviewPrompt);
187
- s.save(s.sessionId);
188
- return result;
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
- { chatFlags: ["--agent", "debugger", "--allow-dangerously-skip-permissions", "--dangerously-skip-permissions"] },
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
- { name: "prompt", type: "text", required: true, description: "task prompt" },
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" + locatorResult.result,
166
- "### Infrastructure Analysis (codebase-analyzer)\n\n" + analyzerResult.result,
167
- "### Build & Test Patterns (codebase-pattern-finder)\n\n" + patternResult.result,
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) ──────────────────────────────────