@bastani/atomic 0.5.0-1 → 0.5.0-3
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/package-lock.json +31 -0
- package/.atomic/workflows/package.json +8 -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 +9 -3
- 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/tsconfig.json +34 -0
- package/src/commands/cli/update.ts +0 -46
- package/src/services/system/download.ts +0 -325
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Ralph workflow for Copilot — plan → orchestrate → review → debug loop.
|
|
3
|
+
*
|
|
4
|
+
* One CopilotClient backs every iteration; each loop step creates a fresh
|
|
5
|
+
* sub-session bound to the appropriate sub-agent (planner, orchestrator,
|
|
6
|
+
* reviewer, debugger). The loop terminates when:
|
|
7
|
+
* - {@link MAX_LOOPS} iterations have completed, OR
|
|
8
|
+
* - Two consecutive reviewer passes return zero findings.
|
|
9
|
+
*
|
|
10
|
+
* A loop is one cycle of plan → orchestrate → review. When a review returns
|
|
11
|
+
* zero findings on the FIRST pass we re-run only the reviewer (still inside
|
|
12
|
+
* the same loop iteration) to confirm; if that confirmation pass is also
|
|
13
|
+
* clean we stop. The debugger only runs when findings remain, and its
|
|
14
|
+
* markdown report is fed back into the next iteration's planner.
|
|
15
|
+
*
|
|
16
|
+
* Run: atomic workflow -n ralph -a copilot "<your spec>"
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
import { defineWorkflow } from "@bastani/atomic/workflows";
|
|
20
|
+
import { CopilotClient, approveAll } from "@github/copilot-sdk";
|
|
21
|
+
import type { SessionEvent } from "@github/copilot-sdk";
|
|
22
|
+
|
|
23
|
+
import {
|
|
24
|
+
buildPlannerPrompt,
|
|
25
|
+
buildOrchestratorPrompt,
|
|
26
|
+
buildReviewPrompt,
|
|
27
|
+
buildDebuggerReportPrompt,
|
|
28
|
+
parseReviewResult,
|
|
29
|
+
extractMarkdownBlock,
|
|
30
|
+
} from "../helpers/prompts.ts";
|
|
31
|
+
import { hasActionableFindings } from "../helpers/review.ts";
|
|
32
|
+
import { safeGitStatusS } from "../helpers/git.ts";
|
|
33
|
+
|
|
34
|
+
const MAX_LOOPS = 10;
|
|
35
|
+
const CONSECUTIVE_CLEAN_THRESHOLD = 2;
|
|
36
|
+
|
|
37
|
+
/** Concatenate the text content of every assistant message in an event stream. */
|
|
38
|
+
function getLastAssistantText(messages: SessionEvent[]): string {
|
|
39
|
+
const assistantMessages = messages.filter(
|
|
40
|
+
(m): m is Extract<SessionEvent, { type: "assistant.message" }> =>
|
|
41
|
+
m.type === "assistant.message",
|
|
42
|
+
);
|
|
43
|
+
const last = assistantMessages.at(-1);
|
|
44
|
+
if (!last) return "";
|
|
45
|
+
return last.data.content;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export default defineWorkflow({
|
|
49
|
+
name: "ralph",
|
|
50
|
+
description:
|
|
51
|
+
"Plan → orchestrate → review → debug loop with bounded iteration",
|
|
52
|
+
})
|
|
53
|
+
.session({
|
|
54
|
+
name: "ralph-loop",
|
|
55
|
+
description:
|
|
56
|
+
"Drive plan/orchestrate/review/debug iterations until clean or capped",
|
|
57
|
+
run: async (ctx) => {
|
|
58
|
+
const client = new CopilotClient({ cliUrl: ctx.serverUrl });
|
|
59
|
+
await client.start();
|
|
60
|
+
|
|
61
|
+
let lastMessages: SessionEvent[] = [];
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Spin up a fresh sub-session bound to the named agent, send the
|
|
65
|
+
* prompt, await the response, then disconnect. Returns the text of the
|
|
66
|
+
* last assistant message so the caller can parse it.
|
|
67
|
+
*/
|
|
68
|
+
async function runAgent(agent: string, prompt: string): Promise<string> {
|
|
69
|
+
const session = await client.createSession({
|
|
70
|
+
agent,
|
|
71
|
+
onPermissionRequest: approveAll,
|
|
72
|
+
});
|
|
73
|
+
await client.setForegroundSessionId(session.sessionId);
|
|
74
|
+
|
|
75
|
+
await session.sendAndWait({ prompt });
|
|
76
|
+
|
|
77
|
+
const messages = await session.getMessages();
|
|
78
|
+
lastMessages = messages;
|
|
79
|
+
|
|
80
|
+
await session.disconnect();
|
|
81
|
+
return getLastAssistantText(messages);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
try {
|
|
85
|
+
let consecutiveClean = 0;
|
|
86
|
+
let debuggerReport = "";
|
|
87
|
+
|
|
88
|
+
for (let iteration = 1; iteration <= MAX_LOOPS; iteration++) {
|
|
89
|
+
// ── Plan ──────────────────────────────────────────────────────────
|
|
90
|
+
await runAgent(
|
|
91
|
+
"planner",
|
|
92
|
+
buildPlannerPrompt(ctx.userPrompt, {
|
|
93
|
+
iteration,
|
|
94
|
+
debuggerReport: debuggerReport || undefined,
|
|
95
|
+
}),
|
|
96
|
+
);
|
|
97
|
+
|
|
98
|
+
// ── Orchestrate ───────────────────────────────────────────────────
|
|
99
|
+
await runAgent("orchestrator", buildOrchestratorPrompt());
|
|
100
|
+
|
|
101
|
+
// ── Review (first pass) ───────────────────────────────────────────
|
|
102
|
+
let gitStatus = await safeGitStatusS();
|
|
103
|
+
let reviewRaw = await runAgent(
|
|
104
|
+
"reviewer",
|
|
105
|
+
buildReviewPrompt(ctx.userPrompt, { gitStatus, iteration }),
|
|
106
|
+
);
|
|
107
|
+
let parsed = parseReviewResult(reviewRaw);
|
|
108
|
+
|
|
109
|
+
if (!hasActionableFindings(parsed, reviewRaw)) {
|
|
110
|
+
consecutiveClean += 1;
|
|
111
|
+
if (consecutiveClean >= CONSECUTIVE_CLEAN_THRESHOLD) {
|
|
112
|
+
break;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// Confirmation pass — re-run reviewer only, NOT plan/orchestrate.
|
|
116
|
+
gitStatus = await safeGitStatusS();
|
|
117
|
+
reviewRaw = await runAgent(
|
|
118
|
+
"reviewer",
|
|
119
|
+
buildReviewPrompt(ctx.userPrompt, {
|
|
120
|
+
gitStatus,
|
|
121
|
+
iteration,
|
|
122
|
+
isConfirmationPass: true,
|
|
123
|
+
}),
|
|
124
|
+
);
|
|
125
|
+
parsed = parseReviewResult(reviewRaw);
|
|
126
|
+
|
|
127
|
+
if (!hasActionableFindings(parsed, reviewRaw)) {
|
|
128
|
+
consecutiveClean += 1;
|
|
129
|
+
if (consecutiveClean >= CONSECUTIVE_CLEAN_THRESHOLD) {
|
|
130
|
+
break;
|
|
131
|
+
}
|
|
132
|
+
} else {
|
|
133
|
+
consecutiveClean = 0;
|
|
134
|
+
// fall through to debugger
|
|
135
|
+
}
|
|
136
|
+
} else {
|
|
137
|
+
consecutiveClean = 0;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// ── Debug (only if findings remain AND another iteration is allowed) ─
|
|
141
|
+
if (
|
|
142
|
+
hasActionableFindings(parsed, reviewRaw) &&
|
|
143
|
+
iteration < MAX_LOOPS
|
|
144
|
+
) {
|
|
145
|
+
const debuggerRaw = await runAgent(
|
|
146
|
+
"debugger",
|
|
147
|
+
buildDebuggerReportPrompt(parsed, reviewRaw, {
|
|
148
|
+
iteration,
|
|
149
|
+
gitStatus,
|
|
150
|
+
}),
|
|
151
|
+
);
|
|
152
|
+
debuggerReport = extractMarkdownBlock(debuggerRaw);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
ctx.save(lastMessages);
|
|
157
|
+
} finally {
|
|
158
|
+
await client.stop();
|
|
159
|
+
}
|
|
160
|
+
},
|
|
161
|
+
})
|
|
162
|
+
.compile();
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Deterministic working-tree probes used by the Ralph loop.
|
|
3
|
+
*
|
|
4
|
+
* The reviewer and debugger sub-agents both benefit from knowing exactly which
|
|
5
|
+
* files were touched in the current iteration. Asking an LLM to figure that
|
|
6
|
+
* out via tool calls is expensive and lossy, so we capture `git status -s`
|
|
7
|
+
* directly from the workflow runner and inject it into prompts.
|
|
8
|
+
*
|
|
9
|
+
* Failures (no git binary, not a repo, command non-zero) collapse to "" so
|
|
10
|
+
* the prompts can fall back to the "working tree clean" branch.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Run `git status -s` from the given cwd. Returns trimmed stdout, or "" on
|
|
15
|
+
* any error. Never throws.
|
|
16
|
+
*/
|
|
17
|
+
export async function safeGitStatusS(
|
|
18
|
+
cwd: string = process.cwd(),
|
|
19
|
+
): Promise<string> {
|
|
20
|
+
try {
|
|
21
|
+
const proc = Bun.spawn({
|
|
22
|
+
cmd: ["git", "status", "-s"],
|
|
23
|
+
cwd,
|
|
24
|
+
stdout: "pipe",
|
|
25
|
+
stderr: "ignore",
|
|
26
|
+
});
|
|
27
|
+
const stdout = await new Response(proc.stdout).text();
|
|
28
|
+
const code = await proc.exited;
|
|
29
|
+
if (code !== 0) return "";
|
|
30
|
+
return stdout;
|
|
31
|
+
} catch {
|
|
32
|
+
return "";
|
|
33
|
+
}
|
|
34
|
+
}
|