@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.
- package/.atomic/workflows/hello/claude/index.ts +22 -25
- package/.atomic/workflows/hello/copilot/index.ts +41 -31
- package/.atomic/workflows/hello/opencode/index.ts +40 -40
- package/.atomic/workflows/hello-parallel/claude/index.ts +54 -54
- package/.atomic/workflows/hello-parallel/copilot/index.ts +89 -70
- package/.atomic/workflows/hello-parallel/opencode/index.ts +77 -77
- package/.atomic/workflows/ralph/claude/index.ts +128 -93
- package/.atomic/workflows/ralph/copilot/index.ts +212 -112
- package/.atomic/workflows/ralph/helpers/prompts.ts +45 -2
- package/.atomic/workflows/ralph/opencode/index.ts +174 -111
- package/README.md +138 -59
- package/package.json +1 -1
- package/src/cli.ts +0 -2
- package/src/commands/cli/chat/index.ts +28 -8
- package/src/commands/cli/init/index.ts +7 -10
- package/src/commands/cli/init/scm.ts +27 -10
- package/src/sdk/components/connectors.test.ts +45 -0
- package/src/sdk/components/layout.test.ts +321 -0
- package/src/sdk/components/layout.ts +51 -15
- package/src/sdk/components/orchestrator-panel-contexts.ts +13 -4
- package/src/sdk/components/orchestrator-panel-store.test.ts +156 -0
- package/src/sdk/components/orchestrator-panel-store.ts +24 -0
- package/src/sdk/components/orchestrator-panel.tsx +21 -0
- package/src/sdk/components/session-graph-panel.tsx +8 -15
- package/src/sdk/components/statusline.tsx +4 -6
- package/src/sdk/define-workflow.test.ts +71 -0
- package/src/sdk/define-workflow.ts +42 -39
- package/src/sdk/errors.ts +1 -1
- package/src/sdk/index.ts +4 -1
- package/src/sdk/providers/claude.ts +1 -1
- package/src/sdk/providers/copilot.ts +5 -3
- package/src/sdk/providers/opencode.ts +5 -3
- package/src/sdk/runtime/executor.ts +512 -301
- package/src/sdk/runtime/loader.ts +2 -2
- package/src/sdk/runtime/tmux.ts +31 -2
- package/src/sdk/types.ts +93 -20
- package/src/sdk/workflows.ts +7 -4
- package/src/services/config/definitions.ts +39 -2
- package/src/services/config/settings.ts +0 -6
- package/src/services/system/skills.ts +3 -7
- package/.atomic/workflows/package-lock.json +0 -31
- 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
|
-
.
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
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
|
-
|
|
32
|
+
s.save(result.data!);
|
|
57
33
|
},
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
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:
|
|
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
|
-
|
|
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
|
-
*
|
|
5
|
-
*
|
|
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
|
-
.
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
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
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
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
|
-
"
|
|
104
|
-
|
|
105
|
-
gitStatus,
|
|
64
|
+
"planner",
|
|
65
|
+
buildPlannerPrompt(s.userPrompt, {
|
|
106
66
|
iteration,
|
|
107
|
-
|
|
67
|
+
debuggerReport: debuggerReport || undefined,
|
|
108
68
|
}),
|
|
109
69
|
),
|
|
110
70
|
});
|
|
111
|
-
|
|
112
|
-
|
|
71
|
+
s.save(s.sessionId);
|
|
72
|
+
},
|
|
73
|
+
);
|
|
74
|
+
prevStage = plannerName;
|
|
113
75
|
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
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
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
)
|
|
132
|
-
|
|
133
|
-
|
|
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
|
-
"
|
|
136
|
-
|
|
137
|
-
iteration,
|
|
138
|
-
gitStatus,
|
|
139
|
-
}),
|
|
104
|
+
"reviewer",
|
|
105
|
+
buildReviewPrompt(s.userPrompt, { gitStatus, iteration }),
|
|
140
106
|
),
|
|
141
107
|
});
|
|
142
|
-
|
|
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
|
-
|
|
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();
|