@bastani/atomic 0.5.0-3 → 0.5.0-5

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 (42) hide show
  1. package/.atomic/workflows/hello/claude/index.ts +22 -25
  2. package/.atomic/workflows/hello/copilot/index.ts +41 -31
  3. package/.atomic/workflows/hello/opencode/index.ts +40 -40
  4. package/.atomic/workflows/hello-parallel/claude/index.ts +54 -54
  5. package/.atomic/workflows/hello-parallel/copilot/index.ts +89 -70
  6. package/.atomic/workflows/hello-parallel/opencode/index.ts +77 -77
  7. package/.atomic/workflows/ralph/claude/index.ts +128 -93
  8. package/.atomic/workflows/ralph/copilot/index.ts +212 -112
  9. package/.atomic/workflows/ralph/helpers/prompts.ts +45 -2
  10. package/.atomic/workflows/ralph/opencode/index.ts +174 -111
  11. package/README.md +138 -59
  12. package/package.json +1 -1
  13. package/src/cli.ts +0 -2
  14. package/src/commands/cli/chat/index.ts +28 -8
  15. package/src/commands/cli/init/index.ts +7 -10
  16. package/src/commands/cli/init/scm.ts +27 -10
  17. package/src/sdk/components/connectors.test.ts +45 -0
  18. package/src/sdk/components/layout.test.ts +321 -0
  19. package/src/sdk/components/layout.ts +51 -15
  20. package/src/sdk/components/orchestrator-panel-contexts.ts +13 -4
  21. package/src/sdk/components/orchestrator-panel-store.test.ts +156 -0
  22. package/src/sdk/components/orchestrator-panel-store.ts +24 -0
  23. package/src/sdk/components/orchestrator-panel.tsx +21 -0
  24. package/src/sdk/components/session-graph-panel.tsx +8 -15
  25. package/src/sdk/components/statusline.tsx +4 -6
  26. package/src/sdk/define-workflow.test.ts +71 -0
  27. package/src/sdk/define-workflow.ts +42 -39
  28. package/src/sdk/errors.ts +1 -1
  29. package/src/sdk/index.ts +4 -1
  30. package/src/sdk/providers/claude.ts +1 -1
  31. package/src/sdk/providers/copilot.ts +5 -3
  32. package/src/sdk/providers/opencode.ts +5 -3
  33. package/src/sdk/runtime/executor.ts +512 -301
  34. package/src/sdk/runtime/loader.ts +2 -2
  35. package/src/sdk/runtime/tmux.ts +31 -2
  36. package/src/sdk/types.ts +93 -20
  37. package/src/sdk/workflows.ts +7 -4
  38. package/src/services/config/definitions.ts +39 -2
  39. package/src/services/config/settings.ts +0 -6
  40. package/src/services/system/skills.ts +3 -7
  41. package/.atomic/workflows/package-lock.json +0 -31
  42. package/.atomic/workflows/package.json +0 -8
@@ -15,55 +15,79 @@ export default defineWorkflow({
15
15
  name: "hello-parallel",
16
16
  description: "Parallel OpenCode demo: describe → [summarize-a, summarize-b] → merge",
17
17
  })
18
- .session({
19
- name: "describe",
20
- description: "Ask the agent to describe the project",
21
- run: async (ctx) => {
22
- const client = createOpencodeClient({ baseUrl: ctx.serverUrl });
23
-
24
- const session = await client.session.create({ title: "describe" });
25
- await client.tui.selectSession({ sessionID: session.data!.id });
26
-
27
- const result = await client.session.prompt({
28
- sessionID: session.data!.id,
29
- parts: [{ type: "text", text: ctx.userPrompt }],
30
- });
31
-
32
- ctx.save(result.data!);
33
- },
34
- })
35
- .session([
36
- {
37
- name: "summarize-a",
38
- description: "Summarize the description as bullet points",
39
- run: async (ctx) => {
40
- const research = await ctx.transcript("describe");
41
- const client = createOpencodeClient({ baseUrl: ctx.serverUrl });
42
-
43
- const session = await client.session.create({ title: "summarize-a" });
18
+ .run(async (ctx) => {
19
+ const describe = await ctx.session(
20
+ { name: "describe", description: "Ask the agent to describe the project" },
21
+ async (s) => {
22
+ const client = createOpencodeClient({ baseUrl: s.serverUrl });
23
+
24
+ const session = await client.session.create({ title: "describe" });
44
25
  await client.tui.selectSession({ sessionID: session.data!.id });
45
26
 
46
27
  const result = await client.session.prompt({
47
28
  sessionID: session.data!.id,
48
- parts: [
49
- {
50
- type: "text",
51
- text: `Summarize the following in 2-3 bullet points:\n\n${research.content}`,
52
- },
53
- ],
29
+ parts: [{ type: "text", text: s.userPrompt }],
54
30
  });
55
31
 
56
- ctx.save(result.data!);
32
+ s.save(result.data!);
57
33
  },
58
- },
59
- {
60
- name: "summarize-b",
61
- description: "Summarize the description as a one-liner",
62
- run: async (ctx) => {
63
- const research = await ctx.transcript("describe");
64
- const client = createOpencodeClient({ baseUrl: ctx.serverUrl });
65
-
66
- const session = await client.session.create({ title: "summarize-b" });
34
+ );
35
+
36
+ const [summarizeA, summarizeB] = await Promise.all([
37
+ ctx.session(
38
+ { name: "summarize-a", description: "Summarize the description as bullet points" },
39
+ async (s) => {
40
+ const research = await s.transcript(describe);
41
+ const client = createOpencodeClient({ baseUrl: s.serverUrl });
42
+
43
+ const session = await client.session.create({ title: "summarize-a" });
44
+ await client.tui.selectSession({ sessionID: session.data!.id });
45
+
46
+ const result = await client.session.prompt({
47
+ sessionID: session.data!.id,
48
+ parts: [
49
+ {
50
+ type: "text",
51
+ text: `Summarize the following in 2-3 bullet points:\n\n${research.content}`,
52
+ },
53
+ ],
54
+ });
55
+
56
+ s.save(result.data!);
57
+ },
58
+ ),
59
+ ctx.session(
60
+ { name: "summarize-b", description: "Summarize the description as a one-liner" },
61
+ async (s) => {
62
+ const research = await s.transcript(describe);
63
+ const client = createOpencodeClient({ baseUrl: s.serverUrl });
64
+
65
+ const session = await client.session.create({ title: "summarize-b" });
66
+ await client.tui.selectSession({ sessionID: session.data!.id });
67
+
68
+ const result = await client.session.prompt({
69
+ sessionID: session.data!.id,
70
+ parts: [
71
+ {
72
+ type: "text",
73
+ text: `Summarize the following in a single sentence:\n\n${research.content}`,
74
+ },
75
+ ],
76
+ });
77
+
78
+ s.save(result.data!);
79
+ },
80
+ ),
81
+ ]);
82
+
83
+ await ctx.session(
84
+ { name: "merge", description: "Merge both summaries into a final output" },
85
+ async (s) => {
86
+ const bullets = await s.transcript(summarizeA);
87
+ const oneliner = await s.transcript(summarizeB);
88
+ const client = createOpencodeClient({ baseUrl: s.serverUrl });
89
+
90
+ const session = await client.session.create({ title: "merge" });
67
91
  await client.tui.selectSession({ sessionID: session.data!.id });
68
92
 
69
93
  const result = await client.session.prompt({
@@ -71,45 +95,21 @@ export default defineWorkflow({
71
95
  parts: [
72
96
  {
73
97
  type: "text",
74
- text: `Summarize the following in a single sentence:\n\n${research.content}`,
98
+ text: [
99
+ "Combine the following two summaries into one concise paragraph:",
100
+ "",
101
+ "## Bullet points",
102
+ bullets.content,
103
+ "",
104
+ "## One-liner",
105
+ oneliner.content,
106
+ ].join("\n"),
75
107
  },
76
108
  ],
77
109
  });
78
110
 
79
- ctx.save(result.data!);
111
+ s.save(result.data!);
80
112
  },
81
- },
82
- ])
83
- .session({
84
- name: "merge",
85
- description: "Merge both summaries into a final output",
86
- run: async (ctx) => {
87
- const bullets = await ctx.transcript("summarize-a");
88
- const oneliner = await ctx.transcript("summarize-b");
89
- const client = createOpencodeClient({ baseUrl: ctx.serverUrl });
90
-
91
- const session = await client.session.create({ title: "merge" });
92
- await client.tui.selectSession({ sessionID: session.data!.id });
93
-
94
- const result = await client.session.prompt({
95
- sessionID: session.data!.id,
96
- parts: [
97
- {
98
- type: "text",
99
- text: [
100
- "Combine the following two summaries into one concise paragraph:",
101
- "",
102
- "## Bullet points",
103
- bullets.content,
104
- "",
105
- "## One-liner",
106
- oneliner.content,
107
- ].join("\n"),
108
- },
109
- ],
110
- });
111
-
112
- ctx.save(result.data!);
113
- },
113
+ );
114
114
  })
115
115
  .compile();
@@ -1,20 +1,12 @@
1
1
  /**
2
2
  * Ralph workflow for Claude Code — plan → orchestrate → review → debug loop.
3
3
  *
4
- * One Claude TUI runs in a tmux pane for the duration of the workflow. Each
5
- * loop iteration invokes sub-agents via @-mention syntax (planner,
6
- * orchestrator, reviewer, and — when findings remain — debugger). The loop
4
+ * Each sub-agent invocation spawns its own visible session in the graph,
5
+ * so users can see each iteration's progress in real time. The loop
7
6
  * terminates when:
8
7
  * - {@link MAX_LOOPS} iterations have completed, OR
9
8
  * - Two consecutive reviewer passes return zero findings.
10
9
  *
11
- * A loop is one cycle of plan → orchestrate → review. When a review returns
12
- * zero findings on the FIRST pass we re-run only the reviewer (still inside
13
- * the same loop iteration) to confirm; if that confirmation pass is also
14
- * clean we stop. The debugger only runs when findings remain after the
15
- * reviewer pass(es), and its markdown report is fed back to the planner on
16
- * the next iteration.
17
- *
18
10
  * Run: atomic workflow -n ralph -a claude "<your spec>"
19
11
  */
20
12
 
@@ -48,102 +40,145 @@ export default defineWorkflow({
48
40
  description:
49
41
  "Plan → orchestrate → review → debug loop with bounded iteration",
50
42
  })
51
- .session({
52
- name: "ralph-loop",
53
- description:
54
- "Drive plan/orchestrate/review/debug iterations until clean or capped",
55
- run: async (ctx) => {
56
- await createClaudeSession({ paneId: ctx.paneId });
57
-
58
- let consecutiveClean = 0;
59
- let debuggerReport = "";
60
-
61
- for (let iteration = 1; iteration <= MAX_LOOPS; iteration++) {
62
- // ── Plan ────────────────────────────────────────────────────────────
63
- await claudeQuery({
64
- paneId: ctx.paneId,
65
- prompt: asAgentCall(
66
- "planner",
67
- buildPlannerPrompt(ctx.userPrompt, {
68
- iteration,
69
- debuggerReport: debuggerReport || undefined,
70
- }),
71
- ),
72
- });
73
-
74
- // ── Orchestrate ─────────────────────────────────────────────────────
75
- await claudeQuery({
76
- paneId: ctx.paneId,
77
- prompt: asAgentCall("orchestrator", buildOrchestratorPrompt()),
78
- });
79
-
80
- // ── Review (first pass) ─────────────────────────────────────────────
81
- let gitStatus = await safeGitStatusS();
82
- let reviewQuery = await claudeQuery({
83
- paneId: ctx.paneId,
84
- prompt: asAgentCall(
85
- "reviewer",
86
- buildReviewPrompt(ctx.userPrompt, { gitStatus, iteration }),
87
- ),
88
- });
89
- let reviewRaw = reviewQuery.output;
90
- let parsed = parseReviewResult(reviewRaw);
91
-
92
- if (!hasActionableFindings(parsed, reviewRaw)) {
93
- consecutiveClean += 1;
94
- if (consecutiveClean >= CONSECUTIVE_CLEAN_THRESHOLD) {
95
- break;
96
- }
43
+ .run(async (ctx) => {
44
+ let consecutiveClean = 0;
45
+ let debuggerReport = "";
46
+ // Track the most recent session so the next stage can declare it as a
47
+ // dependency this chains planner → orchestrator → reviewer → [confirm]
48
+ // [debugger] next planner in the graph instead of showing every
49
+ // stage as an independent sibling under the root.
50
+ let prevStage: string | undefined;
51
+ const depsOn = (): string[] | undefined =>
52
+ prevStage ? [prevStage] : undefined;
97
53
 
98
- // Confirmation pass re-run reviewer only, NOT plan/orchestrate.
99
- gitStatus = await safeGitStatusS();
100
- reviewQuery = await claudeQuery({
101
- paneId: ctx.paneId,
54
+ for (let iteration = 1; iteration <= MAX_LOOPS; iteration++) {
55
+ // ── Plan ────────────────────────────────────────────────────────────
56
+ const plannerName = `planner-${iteration}`;
57
+ await ctx.session(
58
+ { name: plannerName, dependsOn: depsOn() },
59
+ async (s) => {
60
+ await createClaudeSession({ paneId: s.paneId });
61
+ await claudeQuery({
62
+ paneId: s.paneId,
102
63
  prompt: asAgentCall(
103
- "reviewer",
104
- buildReviewPrompt(ctx.userPrompt, {
105
- gitStatus,
64
+ "planner",
65
+ buildPlannerPrompt(s.userPrompt, {
106
66
  iteration,
107
- isConfirmationPass: true,
67
+ debuggerReport: debuggerReport || undefined,
108
68
  }),
109
69
  ),
110
70
  });
111
- reviewRaw = reviewQuery.output;
112
- parsed = parseReviewResult(reviewRaw);
71
+ s.save(s.sessionId);
72
+ },
73
+ );
74
+ prevStage = plannerName;
113
75
 
114
- if (!hasActionableFindings(parsed, reviewRaw)) {
115
- consecutiveClean += 1;
116
- if (consecutiveClean >= CONSECUTIVE_CLEAN_THRESHOLD) {
117
- break;
118
- }
119
- } else {
120
- consecutiveClean = 0;
121
- // fall through to debugger
122
- }
123
- } else {
124
- consecutiveClean = 0;
125
- }
76
+ // ── Orchestrate ─────────────────────────────────────────────────────
77
+ const orchName = `orchestrator-${iteration}`;
78
+ await ctx.session(
79
+ { name: orchName, dependsOn: depsOn() },
80
+ async (s) => {
81
+ await createClaudeSession({ paneId: s.paneId });
82
+ await claudeQuery({
83
+ paneId: s.paneId,
84
+ prompt: asAgentCall(
85
+ "orchestrator",
86
+ buildOrchestratorPrompt(s.userPrompt),
87
+ ),
88
+ });
89
+ s.save(s.sessionId);
90
+ },
91
+ );
92
+ prevStage = orchName;
126
93
 
127
- // ── Debug (only if findings remain AND another iteration is allowed)
128
- if (
129
- hasActionableFindings(parsed, reviewRaw) &&
130
- iteration < MAX_LOOPS
131
- ) {
132
- const debuggerQuery = await claudeQuery({
133
- paneId: ctx.paneId,
94
+ // ── Review (first pass) ─────────────────────────────────────────────
95
+ let gitStatus = await safeGitStatusS();
96
+ const reviewerName = `reviewer-${iteration}`;
97
+ const review = await ctx.session(
98
+ { name: reviewerName, dependsOn: depsOn() },
99
+ async (s) => {
100
+ await createClaudeSession({ paneId: s.paneId });
101
+ const result = await claudeQuery({
102
+ paneId: s.paneId,
134
103
  prompt: asAgentCall(
135
- "debugger",
136
- buildDebuggerReportPrompt(parsed, reviewRaw, {
137
- iteration,
138
- gitStatus,
139
- }),
104
+ "reviewer",
105
+ buildReviewPrompt(s.userPrompt, { gitStatus, iteration }),
140
106
  ),
141
107
  });
142
- debuggerReport = extractMarkdownBlock(debuggerQuery.output);
108
+ s.save(s.sessionId);
109
+ return result.output;
110
+ },
111
+ );
112
+ prevStage = reviewerName;
113
+
114
+ let reviewRaw = review.result;
115
+ let parsed = parseReviewResult(reviewRaw);
116
+
117
+ if (!hasActionableFindings(parsed, reviewRaw)) {
118
+ consecutiveClean += 1;
119
+ if (consecutiveClean >= CONSECUTIVE_CLEAN_THRESHOLD) break;
120
+
121
+ // Confirmation pass — re-run reviewer only
122
+ gitStatus = await safeGitStatusS();
123
+ const confirmName = `reviewer-${iteration}-confirm`;
124
+ const confirm = await ctx.session(
125
+ { name: confirmName, dependsOn: depsOn() },
126
+ async (s) => {
127
+ await createClaudeSession({ paneId: s.paneId });
128
+ const result = await claudeQuery({
129
+ paneId: s.paneId,
130
+ prompt: asAgentCall(
131
+ "reviewer",
132
+ buildReviewPrompt(s.userPrompt, {
133
+ gitStatus,
134
+ iteration,
135
+ isConfirmationPass: true,
136
+ }),
137
+ ),
138
+ });
139
+ s.save(s.sessionId);
140
+ return result.output;
141
+ },
142
+ );
143
+ prevStage = confirmName;
144
+
145
+ reviewRaw = confirm.result;
146
+ parsed = parseReviewResult(reviewRaw);
147
+
148
+ if (!hasActionableFindings(parsed, reviewRaw)) {
149
+ consecutiveClean += 1;
150
+ if (consecutiveClean >= CONSECUTIVE_CLEAN_THRESHOLD) break;
151
+ } else {
152
+ consecutiveClean = 0;
143
153
  }
154
+ } else {
155
+ consecutiveClean = 0;
144
156
  }
145
157
 
146
- ctx.save(ctx.sessionId);
147
- },
158
+ // ── Debug (only if findings remain AND another iteration is allowed)
159
+ if (hasActionableFindings(parsed, reviewRaw) && iteration < MAX_LOOPS) {
160
+ const debuggerName = `debugger-${iteration}`;
161
+ const debugger_ = await ctx.session(
162
+ { name: debuggerName, dependsOn: depsOn() },
163
+ async (s) => {
164
+ await createClaudeSession({ paneId: s.paneId });
165
+ const result = await claudeQuery({
166
+ paneId: s.paneId,
167
+ prompt: asAgentCall(
168
+ "debugger",
169
+ buildDebuggerReportPrompt(parsed, reviewRaw, {
170
+ iteration,
171
+ gitStatus,
172
+ }),
173
+ ),
174
+ });
175
+ s.save(s.sessionId);
176
+ return result.output;
177
+ },
178
+ );
179
+ prevStage = debuggerName;
180
+ debuggerReport = extractMarkdownBlock(debugger_.result);
181
+ }
182
+ }
148
183
  })
149
184
  .compile();