@bastani/atomic 0.5.0 → 0.5.1-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/.atomic/workflows/hello/claude/index.ts +10 -11
- package/.atomic/workflows/hello/copilot/index.ts +11 -26
- package/.atomic/workflows/hello/opencode/index.ts +11 -17
- package/.atomic/workflows/hello-parallel/claude/index.ts +20 -23
- package/.atomic/workflows/hello-parallel/copilot/index.ts +21 -42
- package/.atomic/workflows/hello-parallel/opencode/index.ts +21 -31
- package/.atomic/workflows/ralph/claude/index.ts +42 -53
- package/.atomic/workflows/ralph/copilot/index.ts +36 -79
- package/.atomic/workflows/ralph/opencode/index.ts +36 -59
- package/README.md +391 -166
- package/package.json +1 -1
- package/src/sdk/define-workflow.ts +35 -19
- package/src/sdk/index.ts +4 -0
- package/src/sdk/providers/claude.ts +103 -10
- package/src/sdk/providers/copilot.ts +16 -23
- package/src/sdk/providers/opencode.ts +15 -22
- package/src/sdk/runtime/executor.ts +138 -55
- package/src/sdk/runtime/graph-inference.ts +50 -0
- package/src/sdk/types.ts +113 -38
- package/src/sdk/workflows.ts +14 -1
- package/src/services/system/workflows.ts +136 -1
|
@@ -11,7 +11,6 @@
|
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
13
|
import { defineWorkflow } from "@bastani/atomic/workflows";
|
|
14
|
-
import { CopilotClient, approveAll } from "@github/copilot-sdk";
|
|
15
14
|
import type { SessionEvent } from "@github/copilot-sdk";
|
|
16
15
|
|
|
17
16
|
import {
|
|
@@ -68,7 +67,7 @@ function getAssistantText(messages: SessionEvent[]): string {
|
|
|
68
67
|
.join("\n\n");
|
|
69
68
|
}
|
|
70
69
|
|
|
71
|
-
export default defineWorkflow({
|
|
70
|
+
export default defineWorkflow<"copilot">({
|
|
72
71
|
name: "ralph",
|
|
73
72
|
description:
|
|
74
73
|
"Plan → orchestrate → review → debug loop with bounded iteration",
|
|
@@ -76,28 +75,16 @@ export default defineWorkflow({
|
|
|
76
75
|
.run(async (ctx) => {
|
|
77
76
|
let consecutiveClean = 0;
|
|
78
77
|
let debuggerReport = "";
|
|
79
|
-
// Track the most recent session so the next stage can declare it as a
|
|
80
|
-
// dependency — this chains planner → orchestrator → reviewer → [confirm]
|
|
81
|
-
// → [debugger] → next planner in the graph instead of showing every
|
|
82
|
-
// stage as an independent sibling under the root.
|
|
83
|
-
let prevStage: string | undefined;
|
|
84
|
-
const depsOn = (): string[] | undefined =>
|
|
85
|
-
prevStage ? [prevStage] : undefined;
|
|
86
78
|
|
|
87
79
|
for (let iteration = 1; iteration <= MAX_LOOPS; iteration++) {
|
|
88
80
|
// ── Plan ──────────────────────────────────────────────────────────
|
|
89
81
|
const plannerName = `planner-${iteration}`;
|
|
90
|
-
const planner = await ctx.
|
|
91
|
-
{ name: plannerName
|
|
82
|
+
const planner = await ctx.stage(
|
|
83
|
+
{ name: plannerName },
|
|
84
|
+
{},
|
|
85
|
+
{ agent: "planner" },
|
|
92
86
|
async (s) => {
|
|
93
|
-
|
|
94
|
-
await client.start();
|
|
95
|
-
const session = await client.createSession({
|
|
96
|
-
agent: "planner",
|
|
97
|
-
onPermissionRequest: approveAll,
|
|
98
|
-
});
|
|
99
|
-
await client.setForegroundSessionId(session.sessionId);
|
|
100
|
-
await session.sendAndWait(
|
|
87
|
+
await s.session.sendAndWait(
|
|
101
88
|
{
|
|
102
89
|
prompt: buildPlannerPrompt(s.userPrompt, {
|
|
103
90
|
iteration,
|
|
@@ -106,28 +93,21 @@ export default defineWorkflow({
|
|
|
106
93
|
},
|
|
107
94
|
AGENT_SEND_TIMEOUT_MS,
|
|
108
95
|
);
|
|
109
|
-
const messages = await session.getMessages();
|
|
96
|
+
const messages = await s.session.getMessages();
|
|
110
97
|
s.save(messages);
|
|
111
|
-
await session.disconnect();
|
|
112
|
-
await client.stop();
|
|
113
98
|
return getAssistantText(messages);
|
|
114
99
|
},
|
|
115
100
|
);
|
|
116
|
-
|
|
101
|
+
|
|
117
102
|
|
|
118
103
|
// ── Orchestrate ───────────────────────────────────────────────────
|
|
119
104
|
const orchName = `orchestrator-${iteration}`;
|
|
120
|
-
await ctx.
|
|
121
|
-
{ name: orchName
|
|
105
|
+
await ctx.stage(
|
|
106
|
+
{ name: orchName },
|
|
107
|
+
{},
|
|
108
|
+
{ agent: "orchestrator" },
|
|
122
109
|
async (s) => {
|
|
123
|
-
|
|
124
|
-
await client.start();
|
|
125
|
-
const session = await client.createSession({
|
|
126
|
-
agent: "orchestrator",
|
|
127
|
-
onPermissionRequest: approveAll,
|
|
128
|
-
});
|
|
129
|
-
await client.setForegroundSessionId(session.sessionId);
|
|
130
|
-
await session.sendAndWait(
|
|
110
|
+
await s.session.sendAndWait(
|
|
131
111
|
{
|
|
132
112
|
prompt: buildOrchestratorPrompt(s.userPrompt, {
|
|
133
113
|
plannerNotes: planner.result,
|
|
@@ -135,27 +115,20 @@ export default defineWorkflow({
|
|
|
135
115
|
},
|
|
136
116
|
AGENT_SEND_TIMEOUT_MS,
|
|
137
117
|
);
|
|
138
|
-
s.save(await session.getMessages());
|
|
139
|
-
await session.disconnect();
|
|
140
|
-
await client.stop();
|
|
118
|
+
s.save(await s.session.getMessages());
|
|
141
119
|
},
|
|
142
120
|
);
|
|
143
|
-
|
|
121
|
+
|
|
144
122
|
|
|
145
123
|
// ── Review (first pass) ───────────────────────────────────────────
|
|
146
124
|
let gitStatus = await safeGitStatusS();
|
|
147
125
|
const reviewerName = `reviewer-${iteration}`;
|
|
148
|
-
const review = await ctx.
|
|
149
|
-
{ name: reviewerName
|
|
126
|
+
const review = await ctx.stage(
|
|
127
|
+
{ name: reviewerName },
|
|
128
|
+
{},
|
|
129
|
+
{ agent: "reviewer" },
|
|
150
130
|
async (s) => {
|
|
151
|
-
|
|
152
|
-
await client.start();
|
|
153
|
-
const session = await client.createSession({
|
|
154
|
-
agent: "reviewer",
|
|
155
|
-
onPermissionRequest: approveAll,
|
|
156
|
-
});
|
|
157
|
-
await client.setForegroundSessionId(session.sessionId);
|
|
158
|
-
await session.sendAndWait(
|
|
131
|
+
await s.session.sendAndWait(
|
|
159
132
|
{
|
|
160
133
|
prompt: buildReviewPrompt(s.userPrompt, {
|
|
161
134
|
gitStatus,
|
|
@@ -164,14 +137,12 @@ export default defineWorkflow({
|
|
|
164
137
|
},
|
|
165
138
|
AGENT_SEND_TIMEOUT_MS,
|
|
166
139
|
);
|
|
167
|
-
const messages = await session.getMessages();
|
|
140
|
+
const messages = await s.session.getMessages();
|
|
168
141
|
s.save(messages);
|
|
169
|
-
await session.disconnect();
|
|
170
|
-
await client.stop();
|
|
171
142
|
return getAssistantText(messages);
|
|
172
143
|
},
|
|
173
144
|
);
|
|
174
|
-
|
|
145
|
+
|
|
175
146
|
|
|
176
147
|
let reviewRaw = review.result;
|
|
177
148
|
let parsed = parseReviewResult(reviewRaw);
|
|
@@ -183,17 +154,12 @@ export default defineWorkflow({
|
|
|
183
154
|
// Confirmation pass — re-run reviewer only
|
|
184
155
|
gitStatus = await safeGitStatusS();
|
|
185
156
|
const confirmName = `reviewer-${iteration}-confirm`;
|
|
186
|
-
const confirm = await ctx.
|
|
187
|
-
{ name: confirmName
|
|
157
|
+
const confirm = await ctx.stage(
|
|
158
|
+
{ name: confirmName },
|
|
159
|
+
{},
|
|
160
|
+
{ agent: "reviewer" },
|
|
188
161
|
async (s) => {
|
|
189
|
-
|
|
190
|
-
await client.start();
|
|
191
|
-
const session = await client.createSession({
|
|
192
|
-
agent: "reviewer",
|
|
193
|
-
onPermissionRequest: approveAll,
|
|
194
|
-
});
|
|
195
|
-
await client.setForegroundSessionId(session.sessionId);
|
|
196
|
-
await session.sendAndWait(
|
|
162
|
+
await s.session.sendAndWait(
|
|
197
163
|
{
|
|
198
164
|
prompt: buildReviewPrompt(s.userPrompt, {
|
|
199
165
|
gitStatus,
|
|
@@ -203,14 +169,12 @@ export default defineWorkflow({
|
|
|
203
169
|
},
|
|
204
170
|
AGENT_SEND_TIMEOUT_MS,
|
|
205
171
|
);
|
|
206
|
-
const messages = await session.getMessages();
|
|
172
|
+
const messages = await s.session.getMessages();
|
|
207
173
|
s.save(messages);
|
|
208
|
-
await session.disconnect();
|
|
209
|
-
await client.stop();
|
|
210
174
|
return getAssistantText(messages);
|
|
211
175
|
},
|
|
212
176
|
);
|
|
213
|
-
|
|
177
|
+
|
|
214
178
|
|
|
215
179
|
reviewRaw = confirm.result;
|
|
216
180
|
parsed = parseReviewResult(reviewRaw);
|
|
@@ -228,17 +192,12 @@ export default defineWorkflow({
|
|
|
228
192
|
// ── Debug (only if findings remain AND another iteration is allowed) ─
|
|
229
193
|
if (hasActionableFindings(parsed, reviewRaw) && iteration < MAX_LOOPS) {
|
|
230
194
|
const debuggerName = `debugger-${iteration}`;
|
|
231
|
-
const debugger_ = await ctx.
|
|
232
|
-
{ name: debuggerName
|
|
195
|
+
const debugger_ = await ctx.stage(
|
|
196
|
+
{ name: debuggerName },
|
|
197
|
+
{},
|
|
198
|
+
{ agent: "debugger" },
|
|
233
199
|
async (s) => {
|
|
234
|
-
|
|
235
|
-
await client.start();
|
|
236
|
-
const session = await client.createSession({
|
|
237
|
-
agent: "debugger",
|
|
238
|
-
onPermissionRequest: approveAll,
|
|
239
|
-
});
|
|
240
|
-
await client.setForegroundSessionId(session.sessionId);
|
|
241
|
-
await session.sendAndWait(
|
|
200
|
+
await s.session.sendAndWait(
|
|
242
201
|
{
|
|
243
202
|
prompt: buildDebuggerReportPrompt(parsed, reviewRaw, {
|
|
244
203
|
iteration,
|
|
@@ -247,14 +206,12 @@ export default defineWorkflow({
|
|
|
247
206
|
},
|
|
248
207
|
AGENT_SEND_TIMEOUT_MS,
|
|
249
208
|
);
|
|
250
|
-
const messages = await session.getMessages();
|
|
209
|
+
const messages = await s.session.getMessages();
|
|
251
210
|
s.save(messages);
|
|
252
|
-
await session.disconnect();
|
|
253
|
-
await client.stop();
|
|
254
211
|
return getAssistantText(messages);
|
|
255
212
|
},
|
|
256
213
|
);
|
|
257
|
-
|
|
214
|
+
|
|
258
215
|
debuggerReport = extractMarkdownBlock(debugger_.result);
|
|
259
216
|
}
|
|
260
217
|
}
|
|
@@ -11,7 +11,6 @@
|
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
13
|
import { defineWorkflow } from "@bastani/atomic/workflows";
|
|
14
|
-
import { createOpencodeClient } from "@opencode-ai/sdk/v2";
|
|
15
14
|
|
|
16
15
|
import {
|
|
17
16
|
buildPlannerPrompt,
|
|
@@ -37,7 +36,7 @@ function extractResponseText(
|
|
|
37
36
|
.join("\n");
|
|
38
37
|
}
|
|
39
38
|
|
|
40
|
-
export default defineWorkflow({
|
|
39
|
+
export default defineWorkflow<"opencode">({
|
|
41
40
|
name: "ralph",
|
|
42
41
|
description:
|
|
43
42
|
"Plan → orchestrate → review → debug loop with bounded iteration",
|
|
@@ -45,27 +44,17 @@ export default defineWorkflow({
|
|
|
45
44
|
.run(async (ctx) => {
|
|
46
45
|
let consecutiveClean = 0;
|
|
47
46
|
let debuggerReport = "";
|
|
48
|
-
// Track the most recent session so the next stage can declare it as a
|
|
49
|
-
// dependency — this chains planner → orchestrator → reviewer → [confirm]
|
|
50
|
-
// → [debugger] → next planner in the graph instead of showing every
|
|
51
|
-
// stage as an independent sibling under the root.
|
|
52
|
-
let prevStage: string | undefined;
|
|
53
|
-
const depsOn = (): string[] | undefined =>
|
|
54
|
-
prevStage ? [prevStage] : undefined;
|
|
55
47
|
|
|
56
48
|
for (let iteration = 1; iteration <= MAX_LOOPS; iteration++) {
|
|
57
49
|
// ── Plan ────────────────────────────────────────────────────────────
|
|
58
50
|
const plannerName = `planner-${iteration}`;
|
|
59
|
-
const planner = await ctx.
|
|
60
|
-
{ name: plannerName
|
|
51
|
+
const planner = await ctx.stage(
|
|
52
|
+
{ name: plannerName },
|
|
53
|
+
{},
|
|
54
|
+
{ title: `planner-${iteration}` },
|
|
61
55
|
async (s) => {
|
|
62
|
-
const
|
|
63
|
-
|
|
64
|
-
title: `planner-${iteration}`,
|
|
65
|
-
});
|
|
66
|
-
await client.tui.selectSession({ sessionID: session.data!.id });
|
|
67
|
-
const result = await client.session.prompt({
|
|
68
|
-
sessionID: session.data!.id,
|
|
56
|
+
const result = await s.client.session.prompt({
|
|
57
|
+
sessionID: s.session.id,
|
|
69
58
|
parts: [
|
|
70
59
|
{
|
|
71
60
|
type: "text",
|
|
@@ -81,20 +70,17 @@ export default defineWorkflow({
|
|
|
81
70
|
return extractResponseText(result.data!.parts);
|
|
82
71
|
},
|
|
83
72
|
);
|
|
84
|
-
|
|
73
|
+
|
|
85
74
|
|
|
86
75
|
// ── Orchestrate ─────────────────────────────────────────────────────
|
|
87
76
|
const orchName = `orchestrator-${iteration}`;
|
|
88
|
-
await ctx.
|
|
89
|
-
{ name: orchName
|
|
77
|
+
await ctx.stage(
|
|
78
|
+
{ name: orchName },
|
|
79
|
+
{},
|
|
80
|
+
{ title: `orchestrator-${iteration}` },
|
|
90
81
|
async (s) => {
|
|
91
|
-
const
|
|
92
|
-
|
|
93
|
-
title: `orchestrator-${iteration}`,
|
|
94
|
-
});
|
|
95
|
-
await client.tui.selectSession({ sessionID: session.data!.id });
|
|
96
|
-
const result = await client.session.prompt({
|
|
97
|
-
sessionID: session.data!.id,
|
|
82
|
+
const result = await s.client.session.prompt({
|
|
83
|
+
sessionID: s.session.id,
|
|
98
84
|
parts: [
|
|
99
85
|
{
|
|
100
86
|
type: "text",
|
|
@@ -108,21 +94,18 @@ export default defineWorkflow({
|
|
|
108
94
|
s.save(result.data!);
|
|
109
95
|
},
|
|
110
96
|
);
|
|
111
|
-
|
|
97
|
+
|
|
112
98
|
|
|
113
99
|
// ── Review (first pass) ─────────────────────────────────────────────
|
|
114
100
|
let gitStatus = await safeGitStatusS();
|
|
115
101
|
const reviewerName = `reviewer-${iteration}`;
|
|
116
|
-
const review = await ctx.
|
|
117
|
-
{ name: reviewerName
|
|
102
|
+
const review = await ctx.stage(
|
|
103
|
+
{ name: reviewerName },
|
|
104
|
+
{},
|
|
105
|
+
{ title: `reviewer-${iteration}` },
|
|
118
106
|
async (s) => {
|
|
119
|
-
const
|
|
120
|
-
|
|
121
|
-
title: `reviewer-${iteration}`,
|
|
122
|
-
});
|
|
123
|
-
await client.tui.selectSession({ sessionID: session.data!.id });
|
|
124
|
-
const result = await client.session.prompt({
|
|
125
|
-
sessionID: session.data!.id,
|
|
107
|
+
const result = await s.client.session.prompt({
|
|
108
|
+
sessionID: s.session.id,
|
|
126
109
|
parts: [
|
|
127
110
|
{
|
|
128
111
|
type: "text",
|
|
@@ -138,7 +121,7 @@ export default defineWorkflow({
|
|
|
138
121
|
return extractResponseText(result.data!.parts);
|
|
139
122
|
},
|
|
140
123
|
);
|
|
141
|
-
|
|
124
|
+
|
|
142
125
|
|
|
143
126
|
let reviewRaw = review.result;
|
|
144
127
|
let parsed = parseReviewResult(reviewRaw);
|
|
@@ -150,16 +133,13 @@ export default defineWorkflow({
|
|
|
150
133
|
// Confirmation pass — re-run reviewer only
|
|
151
134
|
gitStatus = await safeGitStatusS();
|
|
152
135
|
const confirmName = `reviewer-${iteration}-confirm`;
|
|
153
|
-
const confirm = await ctx.
|
|
154
|
-
{ name: confirmName
|
|
136
|
+
const confirm = await ctx.stage(
|
|
137
|
+
{ name: confirmName },
|
|
138
|
+
{},
|
|
139
|
+
{ title: `reviewer-${iteration}-confirm` },
|
|
155
140
|
async (s) => {
|
|
156
|
-
const
|
|
157
|
-
|
|
158
|
-
title: `reviewer-${iteration}-confirm`,
|
|
159
|
-
});
|
|
160
|
-
await client.tui.selectSession({ sessionID: session.data!.id });
|
|
161
|
-
const result = await client.session.prompt({
|
|
162
|
-
sessionID: session.data!.id,
|
|
141
|
+
const result = await s.client.session.prompt({
|
|
142
|
+
sessionID: s.session.id,
|
|
163
143
|
parts: [
|
|
164
144
|
{
|
|
165
145
|
type: "text",
|
|
@@ -176,7 +156,7 @@ export default defineWorkflow({
|
|
|
176
156
|
return extractResponseText(result.data!.parts);
|
|
177
157
|
},
|
|
178
158
|
);
|
|
179
|
-
|
|
159
|
+
|
|
180
160
|
|
|
181
161
|
reviewRaw = confirm.result;
|
|
182
162
|
parsed = parseReviewResult(reviewRaw);
|
|
@@ -194,16 +174,13 @@ export default defineWorkflow({
|
|
|
194
174
|
// ── Debug (only if findings remain AND another iteration is allowed) ─
|
|
195
175
|
if (hasActionableFindings(parsed, reviewRaw) && iteration < MAX_LOOPS) {
|
|
196
176
|
const debuggerName = `debugger-${iteration}`;
|
|
197
|
-
const debugger_ = await ctx.
|
|
198
|
-
{ name: debuggerName
|
|
177
|
+
const debugger_ = await ctx.stage(
|
|
178
|
+
{ name: debuggerName },
|
|
179
|
+
{},
|
|
180
|
+
{ title: `debugger-${iteration}` },
|
|
199
181
|
async (s) => {
|
|
200
|
-
const
|
|
201
|
-
|
|
202
|
-
title: `debugger-${iteration}`,
|
|
203
|
-
});
|
|
204
|
-
await client.tui.selectSession({ sessionID: session.data!.id });
|
|
205
|
-
const result = await client.session.prompt({
|
|
206
|
-
sessionID: session.data!.id,
|
|
182
|
+
const result = await s.client.session.prompt({
|
|
183
|
+
sessionID: s.session.id,
|
|
207
184
|
parts: [
|
|
208
185
|
{
|
|
209
186
|
type: "text",
|
|
@@ -219,7 +196,7 @@ export default defineWorkflow({
|
|
|
219
196
|
return extractResponseText(result.data!.parts);
|
|
220
197
|
},
|
|
221
198
|
);
|
|
222
|
-
|
|
199
|
+
|
|
223
200
|
debuggerReport = extractMarkdownBlock(debugger_.result);
|
|
224
201
|
}
|
|
225
202
|
}
|