@bastani/atomic 0.5.0-1 → 0.5.0-2
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 +44 -0
- package/.atomic/workflows/hello/copilot/index.ts +58 -0
- package/.atomic/workflows/hello/opencode/index.ts +58 -0
- package/.atomic/workflows/hello-parallel/claude/index.ts +76 -0
- package/.atomic/workflows/hello-parallel/copilot/index.ts +105 -0
- package/.atomic/workflows/hello-parallel/opencode/index.ts +115 -0
- package/.atomic/workflows/ralph/claude/index.ts +149 -0
- package/.atomic/workflows/ralph/copilot/index.ts +162 -0
- package/.atomic/workflows/ralph/helpers/git.ts +34 -0
- package/.atomic/workflows/ralph/helpers/prompts.ts +538 -0
- package/.atomic/workflows/ralph/helpers/review.ts +32 -0
- package/.atomic/workflows/ralph/opencode/index.ts +164 -0
- package/.atomic/workflows/tsconfig.json +22 -0
- package/.claude/agents/code-simplifier.md +52 -0
- package/.claude/agents/codebase-analyzer.md +166 -0
- package/.claude/agents/codebase-locator.md +122 -0
- package/.claude/agents/codebase-online-researcher.md +148 -0
- package/.claude/agents/codebase-pattern-finder.md +247 -0
- package/.claude/agents/codebase-research-analyzer.md +179 -0
- package/.claude/agents/codebase-research-locator.md +145 -0
- package/.claude/agents/debugger.md +91 -0
- package/.claude/agents/orchestrator.md +19 -0
- package/.claude/agents/planner.md +106 -0
- package/.claude/agents/reviewer.md +97 -0
- package/.claude/agents/worker.md +165 -0
- package/.github/agents/code-simplifier.md +52 -0
- package/.github/agents/codebase-analyzer.md +166 -0
- package/.github/agents/codebase-locator.md +122 -0
- package/.github/agents/codebase-online-researcher.md +146 -0
- package/.github/agents/codebase-pattern-finder.md +247 -0
- package/.github/agents/codebase-research-analyzer.md +179 -0
- package/.github/agents/codebase-research-locator.md +145 -0
- package/.github/agents/debugger.md +98 -0
- package/.github/agents/orchestrator.md +27 -0
- package/.github/agents/planner.md +131 -0
- package/.github/agents/reviewer.md +94 -0
- package/.github/agents/worker.md +237 -0
- package/.github/lsp.json +93 -0
- package/.opencode/agents/code-simplifier.md +62 -0
- package/.opencode/agents/codebase-analyzer.md +171 -0
- package/.opencode/agents/codebase-locator.md +127 -0
- package/.opencode/agents/codebase-online-researcher.md +152 -0
- package/.opencode/agents/codebase-pattern-finder.md +252 -0
- package/.opencode/agents/codebase-research-analyzer.md +183 -0
- package/.opencode/agents/codebase-research-locator.md +149 -0
- package/.opencode/agents/debugger.md +99 -0
- package/.opencode/agents/orchestrator.md +27 -0
- package/.opencode/agents/planner.md +146 -0
- package/.opencode/agents/reviewer.md +102 -0
- package/.opencode/agents/worker.md +165 -0
- package/README.md +355 -299
- package/assets/settings.schema.json +0 -5
- package/package.json +7 -2
- package/src/cli.ts +16 -8
- package/src/commands/cli/workflow.ts +209 -15
- package/src/lib/spawn.ts +106 -31
- package/src/sdk/runtime/loader.ts +1 -1
- package/src/services/config/config-path.ts +1 -1
- package/src/services/config/settings.ts +0 -9
- package/src/services/system/agents.ts +94 -0
- package/src/services/system/auto-sync.ts +131 -0
- package/src/services/system/install-ui.ts +158 -0
- package/src/services/system/skills.ts +26 -17
- package/src/services/system/workflows.ts +105 -0
- package/src/theme/colors.ts +2 -0
- package/src/commands/cli/update.ts +0 -46
- package/src/services/system/download.ts +0 -325
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Hello workflow for Claude Code — two-session example.
|
|
3
|
+
*
|
|
4
|
+
* Claude runs as a full interactive TUI in a tmux pane.
|
|
5
|
+
* We automate it via tmux send-keys using the claudeQuery() helper.
|
|
6
|
+
* Transcript is extracted via the Claude Agent SDK's getSessionMessages().
|
|
7
|
+
*
|
|
8
|
+
* Run: atomic workflow -n hello -a claude "describe this project"
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { defineWorkflow, createClaudeSession, claudeQuery } from "@bastani/atomic/workflows";
|
|
12
|
+
|
|
13
|
+
export default defineWorkflow({
|
|
14
|
+
name: "hello",
|
|
15
|
+
description: "Two-session Claude demo: describe → summarize",
|
|
16
|
+
})
|
|
17
|
+
.session({
|
|
18
|
+
name: "describe",
|
|
19
|
+
description: "Ask Claude to describe the project",
|
|
20
|
+
run: async (ctx) => {
|
|
21
|
+
await createClaudeSession({ paneId: ctx.paneId });
|
|
22
|
+
await claudeQuery({
|
|
23
|
+
paneId: ctx.paneId,
|
|
24
|
+
prompt: ctx.userPrompt,
|
|
25
|
+
});
|
|
26
|
+
// Save transcript via Claude Agent SDK (reads from ~/.claude session files)
|
|
27
|
+
ctx.save(ctx.sessionId);
|
|
28
|
+
},
|
|
29
|
+
})
|
|
30
|
+
.session({
|
|
31
|
+
name: "summarize",
|
|
32
|
+
description: "Summarize the previous session's output",
|
|
33
|
+
run: async (ctx) => {
|
|
34
|
+
await createClaudeSession({ paneId: ctx.paneId });
|
|
35
|
+
const research = await ctx.transcript("describe");
|
|
36
|
+
|
|
37
|
+
await claudeQuery({
|
|
38
|
+
paneId: ctx.paneId,
|
|
39
|
+
prompt: `Read ${research.path} and summarize it in 2-3 bullet points.`,
|
|
40
|
+
});
|
|
41
|
+
ctx.save(ctx.sessionId);
|
|
42
|
+
},
|
|
43
|
+
})
|
|
44
|
+
.compile();
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Hello workflow for Copilot — two-session example.
|
|
3
|
+
*
|
|
4
|
+
* Session 1: Ask the agent to describe the project.
|
|
5
|
+
* Session 2: Read session 1's transcript and summarize it.
|
|
6
|
+
*
|
|
7
|
+
* Run: atomic workflow -n hello -a copilot "describe this project"
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { defineWorkflow } from "@bastani/atomic/workflows";
|
|
11
|
+
import { CopilotClient, approveAll } from "@github/copilot-sdk";
|
|
12
|
+
|
|
13
|
+
export default defineWorkflow({
|
|
14
|
+
name: "hello",
|
|
15
|
+
description: "Two-session Copilot demo: describe → summarize",
|
|
16
|
+
})
|
|
17
|
+
.session({
|
|
18
|
+
name: "describe",
|
|
19
|
+
description: "Ask the agent to describe the project",
|
|
20
|
+
run: async (ctx) => {
|
|
21
|
+
const client = new CopilotClient({ cliUrl: ctx.serverUrl });
|
|
22
|
+
await client.start();
|
|
23
|
+
|
|
24
|
+
const session = await client.createSession({ onPermissionRequest: approveAll });
|
|
25
|
+
await client.setForegroundSessionId(session.sessionId);
|
|
26
|
+
|
|
27
|
+
await session.sendAndWait({ prompt: ctx.userPrompt });
|
|
28
|
+
|
|
29
|
+
// Save Copilot messages for the next session
|
|
30
|
+
ctx.save(await session.getMessages() );
|
|
31
|
+
|
|
32
|
+
await session.disconnect();
|
|
33
|
+
await client.stop();
|
|
34
|
+
},
|
|
35
|
+
})
|
|
36
|
+
.session({
|
|
37
|
+
name: "summarize",
|
|
38
|
+
description: "Summarize the previous session's output",
|
|
39
|
+
run: async (ctx) => {
|
|
40
|
+
const research = await ctx.transcript("describe");
|
|
41
|
+
|
|
42
|
+
const client = new CopilotClient({ cliUrl: ctx.serverUrl });
|
|
43
|
+
await client.start();
|
|
44
|
+
|
|
45
|
+
const session = await client.createSession({ onPermissionRequest: approveAll });
|
|
46
|
+
await client.setForegroundSessionId(session.sessionId);
|
|
47
|
+
|
|
48
|
+
await session.sendAndWait({
|
|
49
|
+
prompt: `Summarize the following in 2-3 bullet points:\n\n${research.content}`,
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
ctx.save(await session.getMessages());
|
|
53
|
+
|
|
54
|
+
await session.disconnect();
|
|
55
|
+
await client.stop();
|
|
56
|
+
},
|
|
57
|
+
})
|
|
58
|
+
.compile();
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Hello workflow for OpenCode — two-session example.
|
|
3
|
+
*
|
|
4
|
+
* Session 1: Ask the agent to describe the project.
|
|
5
|
+
* Session 2: Read session 1's transcript and summarize it.
|
|
6
|
+
*
|
|
7
|
+
* Run: atomic workflow -n hello -a opencode "describe this project"
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { defineWorkflow } from "@bastani/atomic/workflows";
|
|
11
|
+
import { createOpencodeClient } from "@opencode-ai/sdk/v2";
|
|
12
|
+
|
|
13
|
+
export default defineWorkflow({
|
|
14
|
+
name: "hello",
|
|
15
|
+
description: "Two-session OpenCode demo: describe → summarize",
|
|
16
|
+
})
|
|
17
|
+
.session({
|
|
18
|
+
name: "describe",
|
|
19
|
+
description: "Ask the agent to describe the project",
|
|
20
|
+
run: async (ctx) => {
|
|
21
|
+
const client = createOpencodeClient({ baseUrl: ctx.serverUrl });
|
|
22
|
+
|
|
23
|
+
const session = await client.session.create({ title: "describe" });
|
|
24
|
+
await client.tui.selectSession({ sessionID: session.data!.id });
|
|
25
|
+
|
|
26
|
+
const result = await client.session.prompt({
|
|
27
|
+
sessionID: session.data!.id,
|
|
28
|
+
parts: [{ type: "text", text: ctx.userPrompt }],
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
// Save OpenCode response parts for the next session
|
|
32
|
+
ctx.save(result.data!);
|
|
33
|
+
},
|
|
34
|
+
})
|
|
35
|
+
.session({
|
|
36
|
+
name: "summarize",
|
|
37
|
+
description: "Summarize the previous session's output",
|
|
38
|
+
run: async (ctx) => {
|
|
39
|
+
const research = await ctx.transcript("describe");
|
|
40
|
+
const client = createOpencodeClient({ baseUrl: ctx.serverUrl });
|
|
41
|
+
|
|
42
|
+
const session = await client.session.create({ title: "summarize" });
|
|
43
|
+
await client.tui.selectSession({ sessionID: session.data!.id });
|
|
44
|
+
|
|
45
|
+
const result = await client.session.prompt({
|
|
46
|
+
sessionID: session.data!.id,
|
|
47
|
+
parts: [
|
|
48
|
+
{
|
|
49
|
+
type: "text",
|
|
50
|
+
text: `Summarize the following in 2-3 bullet points:\n\n${research.content}`,
|
|
51
|
+
},
|
|
52
|
+
],
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
ctx.save(result.data!);
|
|
56
|
+
},
|
|
57
|
+
})
|
|
58
|
+
.compile();
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Hello-parallel workflow for Claude Code — parallel session example.
|
|
3
|
+
*
|
|
4
|
+
* Session 1 (sequential): Ask Claude to describe the project.
|
|
5
|
+
* Sessions 2+3 (parallel): Two agents summarize session 1 concurrently.
|
|
6
|
+
* Session 4 (sequential): Merge both summaries into a final output.
|
|
7
|
+
*
|
|
8
|
+
* Run: atomic workflow -n hello-parallel -a claude "describe this project"
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { defineWorkflow, createClaudeSession, claudeQuery } from "@bastani/atomic/workflows";
|
|
12
|
+
|
|
13
|
+
export default defineWorkflow({
|
|
14
|
+
name: "hello-parallel",
|
|
15
|
+
description: "Parallel Claude demo: describe → [summarize-a, summarize-b] → merge",
|
|
16
|
+
})
|
|
17
|
+
.session({
|
|
18
|
+
name: "describe",
|
|
19
|
+
description: "Ask Claude to describe the project",
|
|
20
|
+
run: async (ctx) => {
|
|
21
|
+
await createClaudeSession({ paneId: ctx.paneId });
|
|
22
|
+
await claudeQuery({ paneId: ctx.paneId, prompt: ctx.userPrompt });
|
|
23
|
+
ctx.save(ctx.sessionId);
|
|
24
|
+
},
|
|
25
|
+
})
|
|
26
|
+
.session([
|
|
27
|
+
{
|
|
28
|
+
name: "summarize-a",
|
|
29
|
+
description: "Summarize the description as bullet points",
|
|
30
|
+
run: async (ctx) => {
|
|
31
|
+
const research = await ctx.transcript("describe");
|
|
32
|
+
await createClaudeSession({ paneId: ctx.paneId });
|
|
33
|
+
await claudeQuery({
|
|
34
|
+
paneId: ctx.paneId,
|
|
35
|
+
prompt: `Read ${research.path} and summarize it in 2-3 bullet points.`,
|
|
36
|
+
});
|
|
37
|
+
ctx.save(ctx.sessionId);
|
|
38
|
+
},
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
name: "summarize-b",
|
|
42
|
+
description: "Summarize the description as a one-liner",
|
|
43
|
+
run: async (ctx) => {
|
|
44
|
+
const research = await ctx.transcript("describe");
|
|
45
|
+
await createClaudeSession({ paneId: ctx.paneId });
|
|
46
|
+
await claudeQuery({
|
|
47
|
+
paneId: ctx.paneId,
|
|
48
|
+
prompt: `Read ${research.path} and summarize it in a single sentence.`,
|
|
49
|
+
});
|
|
50
|
+
ctx.save(ctx.sessionId);
|
|
51
|
+
},
|
|
52
|
+
},
|
|
53
|
+
])
|
|
54
|
+
.session({
|
|
55
|
+
name: "merge",
|
|
56
|
+
description: "Merge both summaries into a final output",
|
|
57
|
+
run: async (ctx) => {
|
|
58
|
+
const bullets = await ctx.transcript("summarize-a");
|
|
59
|
+
const oneliner = await ctx.transcript("summarize-b");
|
|
60
|
+
await createClaudeSession({ paneId: ctx.paneId });
|
|
61
|
+
await claudeQuery({
|
|
62
|
+
paneId: ctx.paneId,
|
|
63
|
+
prompt: [
|
|
64
|
+
"Combine the following two summaries into one concise paragraph:",
|
|
65
|
+
"",
|
|
66
|
+
"## Bullet points",
|
|
67
|
+
bullets.content,
|
|
68
|
+
"",
|
|
69
|
+
"## One-liner",
|
|
70
|
+
oneliner.content,
|
|
71
|
+
].join("\n"),
|
|
72
|
+
});
|
|
73
|
+
ctx.save(ctx.sessionId);
|
|
74
|
+
},
|
|
75
|
+
})
|
|
76
|
+
.compile();
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Hello-parallel workflow for Copilot — parallel session example.
|
|
3
|
+
*
|
|
4
|
+
* Session 1 (sequential): Ask the agent to describe the project.
|
|
5
|
+
* Sessions 2+3 (parallel): Two agents summarize session 1 concurrently.
|
|
6
|
+
* Session 4 (sequential): Merge both summaries into a final output.
|
|
7
|
+
*
|
|
8
|
+
* Run: atomic workflow -n hello-parallel -a copilot "describe this project"
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { defineWorkflow } from "@bastani/atomic/workflows";
|
|
12
|
+
import { CopilotClient, approveAll } from "@github/copilot-sdk";
|
|
13
|
+
|
|
14
|
+
export default defineWorkflow({
|
|
15
|
+
name: "hello-parallel",
|
|
16
|
+
description: "Parallel Copilot demo: describe → [summarize-a, summarize-b] → merge",
|
|
17
|
+
})
|
|
18
|
+
.session({
|
|
19
|
+
name: "describe",
|
|
20
|
+
description: "Ask the agent to describe the project",
|
|
21
|
+
run: async (ctx) => {
|
|
22
|
+
const client = new CopilotClient({ cliUrl: ctx.serverUrl });
|
|
23
|
+
await client.start();
|
|
24
|
+
|
|
25
|
+
const session = await client.createSession({ onPermissionRequest: approveAll });
|
|
26
|
+
await client.setForegroundSessionId(session.sessionId);
|
|
27
|
+
await session.sendAndWait({ prompt: ctx.userPrompt });
|
|
28
|
+
|
|
29
|
+
ctx.save(await session.getMessages());
|
|
30
|
+
await session.disconnect();
|
|
31
|
+
await client.stop();
|
|
32
|
+
},
|
|
33
|
+
})
|
|
34
|
+
.session([
|
|
35
|
+
{
|
|
36
|
+
name: "summarize-a",
|
|
37
|
+
description: "Summarize the description as bullet points",
|
|
38
|
+
run: async (ctx) => {
|
|
39
|
+
const research = await ctx.transcript("describe");
|
|
40
|
+
|
|
41
|
+
const client = new CopilotClient({ cliUrl: ctx.serverUrl });
|
|
42
|
+
await client.start();
|
|
43
|
+
|
|
44
|
+
const session = await client.createSession({ onPermissionRequest: approveAll });
|
|
45
|
+
await client.setForegroundSessionId(session.sessionId);
|
|
46
|
+
await session.sendAndWait({
|
|
47
|
+
prompt: `Summarize the following in 2-3 bullet points:\n\n${research.content}`,
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
ctx.save(await session.getMessages());
|
|
51
|
+
await session.disconnect();
|
|
52
|
+
await client.stop();
|
|
53
|
+
},
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
name: "summarize-b",
|
|
57
|
+
description: "Summarize the description as a one-liner",
|
|
58
|
+
run: async (ctx) => {
|
|
59
|
+
const research = await ctx.transcript("describe");
|
|
60
|
+
|
|
61
|
+
const client = new CopilotClient({ cliUrl: ctx.serverUrl });
|
|
62
|
+
await client.start();
|
|
63
|
+
|
|
64
|
+
const session = await client.createSession({ onPermissionRequest: approveAll });
|
|
65
|
+
await client.setForegroundSessionId(session.sessionId);
|
|
66
|
+
await session.sendAndWait({
|
|
67
|
+
prompt: `Summarize the following in a single sentence:\n\n${research.content}`,
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
ctx.save(await session.getMessages());
|
|
71
|
+
await session.disconnect();
|
|
72
|
+
await client.stop();
|
|
73
|
+
},
|
|
74
|
+
},
|
|
75
|
+
])
|
|
76
|
+
.session({
|
|
77
|
+
name: "merge",
|
|
78
|
+
description: "Merge both summaries into a final output",
|
|
79
|
+
run: async (ctx) => {
|
|
80
|
+
const bullets = await ctx.transcript("summarize-a");
|
|
81
|
+
const oneliner = await ctx.transcript("summarize-b");
|
|
82
|
+
|
|
83
|
+
const client = new CopilotClient({ cliUrl: ctx.serverUrl });
|
|
84
|
+
await client.start();
|
|
85
|
+
|
|
86
|
+
const session = await client.createSession({ onPermissionRequest: approveAll });
|
|
87
|
+
await client.setForegroundSessionId(session.sessionId);
|
|
88
|
+
await session.sendAndWait({
|
|
89
|
+
prompt: [
|
|
90
|
+
"Combine the following two summaries into one concise paragraph:",
|
|
91
|
+
"",
|
|
92
|
+
"## Bullet points",
|
|
93
|
+
bullets.content,
|
|
94
|
+
"",
|
|
95
|
+
"## One-liner",
|
|
96
|
+
oneliner.content,
|
|
97
|
+
].join("\n"),
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
ctx.save(await session.getMessages());
|
|
101
|
+
await session.disconnect();
|
|
102
|
+
await client.stop();
|
|
103
|
+
},
|
|
104
|
+
})
|
|
105
|
+
.compile();
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Hello-parallel workflow for OpenCode — parallel session example.
|
|
3
|
+
*
|
|
4
|
+
* Session 1 (sequential): Ask the agent to describe the project.
|
|
5
|
+
* Sessions 2+3 (parallel): Two agents summarize session 1 concurrently.
|
|
6
|
+
* Session 4 (sequential): Merge both summaries into a final output.
|
|
7
|
+
*
|
|
8
|
+
* Run: atomic workflow -n hello-parallel -a opencode "describe this project"
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { defineWorkflow } from "@bastani/atomic/workflows";
|
|
12
|
+
import { createOpencodeClient } from "@opencode-ai/sdk/v2";
|
|
13
|
+
|
|
14
|
+
export default defineWorkflow({
|
|
15
|
+
name: "hello-parallel",
|
|
16
|
+
description: "Parallel OpenCode demo: describe → [summarize-a, summarize-b] → merge",
|
|
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" });
|
|
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
|
+
ctx.save(result.data!);
|
|
57
|
+
},
|
|
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" });
|
|
67
|
+
await client.tui.selectSession({ sessionID: session.data!.id });
|
|
68
|
+
|
|
69
|
+
const result = await client.session.prompt({
|
|
70
|
+
sessionID: session.data!.id,
|
|
71
|
+
parts: [
|
|
72
|
+
{
|
|
73
|
+
type: "text",
|
|
74
|
+
text: `Summarize the following in a single sentence:\n\n${research.content}`,
|
|
75
|
+
},
|
|
76
|
+
],
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
ctx.save(result.data!);
|
|
80
|
+
},
|
|
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
|
+
},
|
|
114
|
+
})
|
|
115
|
+
.compile();
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Ralph workflow for Claude Code — plan → orchestrate → review → debug loop.
|
|
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
|
|
7
|
+
* terminates when:
|
|
8
|
+
* - {@link MAX_LOOPS} iterations have completed, OR
|
|
9
|
+
* - Two consecutive reviewer passes return zero findings.
|
|
10
|
+
*
|
|
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
|
+
* Run: atomic workflow -n ralph -a claude "<your spec>"
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
import {
|
|
22
|
+
defineWorkflow,
|
|
23
|
+
createClaudeSession,
|
|
24
|
+
claudeQuery,
|
|
25
|
+
} from "@bastani/atomic/workflows";
|
|
26
|
+
|
|
27
|
+
import {
|
|
28
|
+
buildPlannerPrompt,
|
|
29
|
+
buildOrchestratorPrompt,
|
|
30
|
+
buildReviewPrompt,
|
|
31
|
+
buildDebuggerReportPrompt,
|
|
32
|
+
parseReviewResult,
|
|
33
|
+
extractMarkdownBlock,
|
|
34
|
+
} from "../helpers/prompts.ts";
|
|
35
|
+
import { hasActionableFindings } from "../helpers/review.ts";
|
|
36
|
+
import { safeGitStatusS } from "../helpers/git.ts";
|
|
37
|
+
|
|
38
|
+
const MAX_LOOPS = 10;
|
|
39
|
+
const CONSECUTIVE_CLEAN_THRESHOLD = 2;
|
|
40
|
+
|
|
41
|
+
/** Wrap a prompt with a Claude Code @-mention so the named sub-agent runs it. */
|
|
42
|
+
function asAgentCall(agentName: string, prompt: string): string {
|
|
43
|
+
return `@"${agentName} (agent)" ${prompt}`;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export default defineWorkflow({
|
|
47
|
+
name: "ralph",
|
|
48
|
+
description:
|
|
49
|
+
"Plan → orchestrate → review → debug loop with bounded iteration",
|
|
50
|
+
})
|
|
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
|
+
}
|
|
97
|
+
|
|
98
|
+
// Confirmation pass — re-run reviewer only, NOT plan/orchestrate.
|
|
99
|
+
gitStatus = await safeGitStatusS();
|
|
100
|
+
reviewQuery = await claudeQuery({
|
|
101
|
+
paneId: ctx.paneId,
|
|
102
|
+
prompt: asAgentCall(
|
|
103
|
+
"reviewer",
|
|
104
|
+
buildReviewPrompt(ctx.userPrompt, {
|
|
105
|
+
gitStatus,
|
|
106
|
+
iteration,
|
|
107
|
+
isConfirmationPass: true,
|
|
108
|
+
}),
|
|
109
|
+
),
|
|
110
|
+
});
|
|
111
|
+
reviewRaw = reviewQuery.output;
|
|
112
|
+
parsed = parseReviewResult(reviewRaw);
|
|
113
|
+
|
|
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
|
+
}
|
|
126
|
+
|
|
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,
|
|
134
|
+
prompt: asAgentCall(
|
|
135
|
+
"debugger",
|
|
136
|
+
buildDebuggerReportPrompt(parsed, reviewRaw, {
|
|
137
|
+
iteration,
|
|
138
|
+
gitStatus,
|
|
139
|
+
}),
|
|
140
|
+
),
|
|
141
|
+
});
|
|
142
|
+
debuggerReport = extractMarkdownBlock(debuggerQuery.output);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
ctx.save(ctx.sessionId);
|
|
147
|
+
},
|
|
148
|
+
})
|
|
149
|
+
.compile();
|