super-ralph 0.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/LICENSE +21 -0
- package/README.md +126 -0
- package/package.json +50 -0
- package/src/components/SuperRalph.tsx +412 -0
- package/src/components/index.ts +2 -0
- package/src/hooks/index.ts +2 -0
- package/src/hooks/useSuperRalph.ts +40 -0
- package/src/index.ts +66 -0
- package/src/prompts/BuildVerify.mdx +28 -0
- package/src/prompts/CategoryReview.mdx +52 -0
- package/src/prompts/CodeReview.mdx +28 -0
- package/src/prompts/Discover.mdx +41 -0
- package/src/prompts/Implement.mdx +67 -0
- package/src/prompts/IntegrationTest.mdx +82 -0
- package/src/prompts/Plan.mdx +50 -0
- package/src/prompts/Report.mdx +32 -0
- package/src/prompts/Research.mdx +31 -0
- package/src/prompts/ReviewFix.mdx +49 -0
- package/src/prompts/SpecReview.mdx +43 -0
- package/src/prompts/Test.mdx +22 -0
- package/src/prompts/UpdateProgress.mdx +30 -0
- package/src/schemas.ts +128 -0
- package/src/selectors.ts +134 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 William Cory
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
# super-ralph
|
|
2
|
+
|
|
3
|
+
> Reusable Ralph workflow - ticket-driven development with multi-agent review loops
|
|
4
|
+
|
|
5
|
+
An opinionated [Smithers](https://smithers.sh) workflow. You just provide the specs, this workflow does the rest.
|
|
6
|
+
|
|
7
|
+
Supports subscriptions.
|
|
8
|
+
|
|
9
|
+
## Installation
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
bun add super-ralph smithers-orchestrator
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## Usage
|
|
16
|
+
|
|
17
|
+
```tsx
|
|
18
|
+
import {
|
|
19
|
+
SuperRalph,
|
|
20
|
+
ralphOutputSchemas,
|
|
21
|
+
} from "super-ralph";
|
|
22
|
+
import {
|
|
23
|
+
createSmithers,
|
|
24
|
+
ClaudeCodeAgent,
|
|
25
|
+
CodexAgent,
|
|
26
|
+
GeminiAgent,
|
|
27
|
+
} from "smithers-orchestrator";
|
|
28
|
+
import PRD from "./specs/PRD.mdx";
|
|
29
|
+
import EngineeringSpec from "./specs/Engineering.mdx";
|
|
30
|
+
|
|
31
|
+
const { smithers, outputs } = createSmithers(ralphOutputSchemas, {
|
|
32
|
+
dbPath: "./workflow.db",
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
export default smithers((ctx) => (
|
|
36
|
+
<SuperRalph
|
|
37
|
+
ctx={ctx}
|
|
38
|
+
outputs={outputs}
|
|
39
|
+
focuses={[
|
|
40
|
+
{ id: "auth", name: "Authentication" },
|
|
41
|
+
{ id: "api", name: "API Server" },
|
|
42
|
+
]}
|
|
43
|
+
projectId="my-project"
|
|
44
|
+
projectName="My Project"
|
|
45
|
+
specsPath="docs/specs/"
|
|
46
|
+
referenceFiles={["docs/reference/"]}
|
|
47
|
+
buildCmds={{ go: "go build ./...", rust: "cargo build" }}
|
|
48
|
+
testCmds={{ go: "go test ./...", rust: "cargo test" }}
|
|
49
|
+
codeStyle="Go: snake_case, Rust: snake_case"
|
|
50
|
+
reviewChecklist={["Spec compliance", "Test coverage", "Security"]}
|
|
51
|
+
maxConcurrency={12}
|
|
52
|
+
agents={{
|
|
53
|
+
planning: new CodexAgent({ model: "gpt-5.3-codex", cwd: process.cwd(), yolo: true }),
|
|
54
|
+
implementation: new ClaudeCodeAgent({ model: "claude-sonnet-4-6", cwd: process.cwd() }),
|
|
55
|
+
testing: new ClaudeCodeAgent({ model: "claude-sonnet-4-6", cwd: process.cwd() }),
|
|
56
|
+
reviewing: new CodexAgent({ model: "gpt-5.3-codex", cwd: process.cwd(), yolo: true }),
|
|
57
|
+
reporting: new GeminiAgent({ model: "gemini-2.5-pro", cwd: process.cwd(), yolo: true }),
|
|
58
|
+
}}
|
|
59
|
+
>
|
|
60
|
+
<PRD />
|
|
61
|
+
<EngineeringSpec />
|
|
62
|
+
</SuperRalph>
|
|
63
|
+
));
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
That's it! 30 lines of configuration for a complete workflow.
|
|
67
|
+
|
|
68
|
+
## The Pattern
|
|
69
|
+
|
|
70
|
+
Under the hood this opinionated workflow is the following steps all in parallel in a pipeline
|
|
71
|
+
|
|
72
|
+
```
|
|
73
|
+
Ralph (infinite loop)
|
|
74
|
+
├─ UpdateProgress → PROGRESS.md
|
|
75
|
+
├─ CodebaseReview → per-focus reviews → tickets
|
|
76
|
+
├─ Discover → new feature tickets
|
|
77
|
+
├─ IntegrationTest → per-focus test runs
|
|
78
|
+
└─ TicketPipeline × N (parallel, in worktrees)
|
|
79
|
+
├─ Research → gather context
|
|
80
|
+
├─ Plan → TDD plan
|
|
81
|
+
├─ ValidationLoop (loops until approved)
|
|
82
|
+
│ ├─ Implement → write tests + code
|
|
83
|
+
│ ├─ Test → run all tests
|
|
84
|
+
│ ├─ BuildVerify → check compilation
|
|
85
|
+
│ ├─ SpecReview + CodeReview (parallel)
|
|
86
|
+
│ └─ ReviewFix → fix issues
|
|
87
|
+
└─ Report → completion summary
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
This opinionated workflow is optimized in following ways:
|
|
91
|
+
|
|
92
|
+
- Observability: multiple reporting steps and lots of data stored in sqlite
|
|
93
|
+
- Quality: via CI checks, review loops, and context-engineered research-plan-implement steps
|
|
94
|
+
- Planning: Optimizes ralph by in real time generating tickets rather than hardcoding them up front
|
|
95
|
+
- Parallelization: All tickets implemented in a JJ Workspace in parallel and then merged back into the main branch as stacked changes
|
|
96
|
+
|
|
97
|
+
## Advanced: Custom Components
|
|
98
|
+
|
|
99
|
+
Override any step with a custom component:
|
|
100
|
+
|
|
101
|
+
```tsx
|
|
102
|
+
<SuperRalph
|
|
103
|
+
{...props}
|
|
104
|
+
discover={<MyCustomDiscover agent={...} />}
|
|
105
|
+
/>
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
Or run additional logic in parallel:
|
|
109
|
+
|
|
110
|
+
```tsx
|
|
111
|
+
<SuperRalph
|
|
112
|
+
{...props}
|
|
113
|
+
discover={
|
|
114
|
+
<Parallel>
|
|
115
|
+
<SuperRalph.Discover agent={...} specsPath="..." referenceFiles={[...]} />
|
|
116
|
+
<MyAdditionalDiscovery agent={...} />
|
|
117
|
+
</Parallel>
|
|
118
|
+
}
|
|
119
|
+
/>
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
These steps default to <SuperRalph.Component when not provided.
|
|
123
|
+
|
|
124
|
+
## License
|
|
125
|
+
|
|
126
|
+
MIT
|
package/package.json
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "super-ralph",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Reusable Ralph workflow pattern - ticket-driven development with multi-agent review loops",
|
|
5
|
+
"author": "William Cory",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"type": "module",
|
|
8
|
+
"module": "src/index.ts",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": "./src/index.ts",
|
|
11
|
+
"./selectors": "./src/selectors.ts",
|
|
12
|
+
"./components": "./src/components/index.ts"
|
|
13
|
+
},
|
|
14
|
+
"files": [
|
|
15
|
+
"src/",
|
|
16
|
+
"README.md",
|
|
17
|
+
"LICENSE"
|
|
18
|
+
],
|
|
19
|
+
"engines": {
|
|
20
|
+
"bun": ">=1.3.0"
|
|
21
|
+
},
|
|
22
|
+
"repository": {
|
|
23
|
+
"type": "git",
|
|
24
|
+
"url": "https://github.com/evmts/super-ralph.git"
|
|
25
|
+
},
|
|
26
|
+
"bugs": {
|
|
27
|
+
"url": "https://github.com/evmts/super-ralph/issues"
|
|
28
|
+
},
|
|
29
|
+
"homepage": "https://github.com/evmts/super-ralph#readme",
|
|
30
|
+
"keywords": [
|
|
31
|
+
"ai",
|
|
32
|
+
"workflow",
|
|
33
|
+
"orchestration",
|
|
34
|
+
"ralph",
|
|
35
|
+
"multi-agent",
|
|
36
|
+
"tdd",
|
|
37
|
+
"code-review"
|
|
38
|
+
],
|
|
39
|
+
"peerDependencies": {
|
|
40
|
+
"smithers-orchestrator": "*"
|
|
41
|
+
},
|
|
42
|
+
"devDependencies": {
|
|
43
|
+
"@types/react": "^18.2.0",
|
|
44
|
+
"typescript": "^5.6.0"
|
|
45
|
+
},
|
|
46
|
+
"publishConfig": {
|
|
47
|
+
"access": "public",
|
|
48
|
+
"registry": "https://registry.npmjs.org/"
|
|
49
|
+
}
|
|
50
|
+
}
|
|
@@ -0,0 +1,412 @@
|
|
|
1
|
+
import { Ralph, Parallel, Sequence, Worktree, Task } from "smithers-orchestrator";
|
|
2
|
+
import type { SmithersCtx } from "smithers-orchestrator";
|
|
3
|
+
import { selectAllTickets, selectReviewTickets, selectProgressSummary, selectImplement, selectTestResults, selectSpecReview, selectCodeReviews } from "../selectors";
|
|
4
|
+
import React, { type ReactElement, type ReactNode } from "react";
|
|
5
|
+
import UpdateProgressPrompt from "../prompts/UpdateProgress.mdx";
|
|
6
|
+
import DiscoverPrompt from "../prompts/Discover.mdx";
|
|
7
|
+
import IntegrationTestPrompt from "../prompts/IntegrationTest.mdx";
|
|
8
|
+
import ResearchPrompt from "../prompts/Research.mdx";
|
|
9
|
+
import PlanPrompt from "../prompts/Plan.mdx";
|
|
10
|
+
import ImplementPrompt from "../prompts/Implement.mdx";
|
|
11
|
+
import TestPrompt from "../prompts/Test.mdx";
|
|
12
|
+
import BuildVerifyPrompt from "../prompts/BuildVerify.mdx";
|
|
13
|
+
import SpecReviewPrompt from "../prompts/SpecReview.mdx";
|
|
14
|
+
import CodeReviewPrompt from "../prompts/CodeReview.mdx";
|
|
15
|
+
import ReviewFixPrompt from "../prompts/ReviewFix.mdx";
|
|
16
|
+
import ReportPrompt from "../prompts/Report.mdx";
|
|
17
|
+
import CategoryReviewPrompt from "../prompts/CategoryReview.mdx";
|
|
18
|
+
|
|
19
|
+
// Main component props (simple API)
|
|
20
|
+
export type SuperRalphProps = {
|
|
21
|
+
ctx: SmithersCtx<any>;
|
|
22
|
+
focuses: ReadonlyArray<{ readonly id: string; readonly name: string }>;
|
|
23
|
+
outputs: any;
|
|
24
|
+
|
|
25
|
+
// Project config (flattened)
|
|
26
|
+
projectId: string;
|
|
27
|
+
projectName: string;
|
|
28
|
+
specsPath: string;
|
|
29
|
+
referenceFiles: string[];
|
|
30
|
+
buildCmds: Record<string, string>;
|
|
31
|
+
testCmds: Record<string, string>;
|
|
32
|
+
codeStyle: string;
|
|
33
|
+
reviewChecklist: string[];
|
|
34
|
+
|
|
35
|
+
maxConcurrency: number;
|
|
36
|
+
taskRetries?: number;
|
|
37
|
+
|
|
38
|
+
// Agents (grouped)
|
|
39
|
+
agents: {
|
|
40
|
+
planning: any;
|
|
41
|
+
implementation: any;
|
|
42
|
+
testing: any;
|
|
43
|
+
reviewing: any;
|
|
44
|
+
reporting: any;
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
// Configuration
|
|
48
|
+
progressFile?: string;
|
|
49
|
+
findingsFile?: string;
|
|
50
|
+
commitConfig?: {
|
|
51
|
+
prefix?: string;
|
|
52
|
+
mainBranch?: string;
|
|
53
|
+
emojiPrefixes?: string;
|
|
54
|
+
};
|
|
55
|
+
testSuites?: Array<{
|
|
56
|
+
name: string;
|
|
57
|
+
command: string;
|
|
58
|
+
description: string;
|
|
59
|
+
}>;
|
|
60
|
+
focusTestSuites?: Record<string, { suites: string[]; setupHints: string[]; testDirs: string[] }>;
|
|
61
|
+
focusDirs?: Record<string, string[]>;
|
|
62
|
+
skipPhases?: Set<string>;
|
|
63
|
+
|
|
64
|
+
// Advanced: Override any step with custom component
|
|
65
|
+
updateProgress?: ReactElement;
|
|
66
|
+
discover?: ReactElement;
|
|
67
|
+
integrationTest?: ReactElement;
|
|
68
|
+
categoryReview?: ReactElement;
|
|
69
|
+
research?: ReactElement;
|
|
70
|
+
plan?: ReactElement;
|
|
71
|
+
implement?: ReactElement;
|
|
72
|
+
test?: ReactElement;
|
|
73
|
+
buildVerify?: ReactElement;
|
|
74
|
+
specReview?: ReactElement;
|
|
75
|
+
codeReview?: ReactElement;
|
|
76
|
+
reviewFix?: ReactElement;
|
|
77
|
+
report?: ReactElement;
|
|
78
|
+
|
|
79
|
+
// Specs as children
|
|
80
|
+
children?: ReactNode;
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
export function SuperRalph({
|
|
84
|
+
ctx,
|
|
85
|
+
focuses,
|
|
86
|
+
outputs,
|
|
87
|
+
projectId,
|
|
88
|
+
projectName,
|
|
89
|
+
specsPath,
|
|
90
|
+
referenceFiles,
|
|
91
|
+
buildCmds,
|
|
92
|
+
testCmds,
|
|
93
|
+
codeStyle,
|
|
94
|
+
reviewChecklist,
|
|
95
|
+
maxConcurrency,
|
|
96
|
+
taskRetries = 3,
|
|
97
|
+
agents,
|
|
98
|
+
progressFile = "PROGRESS.md",
|
|
99
|
+
findingsFile = "docs/test-suite-findings.md",
|
|
100
|
+
commitConfig = {},
|
|
101
|
+
testSuites = [],
|
|
102
|
+
focusTestSuites = {},
|
|
103
|
+
focusDirs = {},
|
|
104
|
+
skipPhases = new Set(),
|
|
105
|
+
|
|
106
|
+
// Advanced overrides
|
|
107
|
+
updateProgress: customUpdateProgress,
|
|
108
|
+
discover: customDiscover,
|
|
109
|
+
integrationTest: customIntegrationTest,
|
|
110
|
+
categoryReview: customCategoryReview,
|
|
111
|
+
research: customResearch,
|
|
112
|
+
plan: customPlan,
|
|
113
|
+
implement: customImplement,
|
|
114
|
+
test: customTest,
|
|
115
|
+
buildVerify: customBuildVerify,
|
|
116
|
+
specReview: customSpecReview,
|
|
117
|
+
codeReview: customCodeReview,
|
|
118
|
+
reviewFix: customReviewFix,
|
|
119
|
+
report: customReport,
|
|
120
|
+
|
|
121
|
+
children,
|
|
122
|
+
}: SuperRalphProps) {
|
|
123
|
+
const { findings: reviewFindings } = selectReviewTickets(ctx, focuses, outputs);
|
|
124
|
+
const { completed: completedTicketIds, unfinished: unfinishedTickets } = selectAllTickets(ctx, focuses, outputs);
|
|
125
|
+
const progressSummary = selectProgressSummary(ctx, outputs);
|
|
126
|
+
|
|
127
|
+
const { prefix = "📝", mainBranch = "main", emojiPrefixes = "✨ feat, 🐛 fix, ♻️ refactor, 📝 docs, 🧪 test" } = commitConfig;
|
|
128
|
+
|
|
129
|
+
return (
|
|
130
|
+
<Ralph until={false} maxIterations={Infinity} onMaxReached="return-last">
|
|
131
|
+
<Parallel maxConcurrency={maxConcurrency}>
|
|
132
|
+
{!skipPhases.has("PROGRESS") && (customUpdateProgress || (
|
|
133
|
+
<Task id="update-progress" output={outputs.progress} agent={agents.reporting} retries={taskRetries}>
|
|
134
|
+
<UpdateProgressPrompt
|
|
135
|
+
projectName={projectName}
|
|
136
|
+
progressFile={progressFile}
|
|
137
|
+
commitMessage={`${prefix} docs: update progress`}
|
|
138
|
+
completedTickets={completedTicketIds}
|
|
139
|
+
/>
|
|
140
|
+
</Task>
|
|
141
|
+
))}
|
|
142
|
+
|
|
143
|
+
{!skipPhases.has("CODEBASE_REVIEW") && (customCategoryReview ? (
|
|
144
|
+
customCategoryReview
|
|
145
|
+
) : (
|
|
146
|
+
<Parallel maxConcurrency={maxConcurrency}>
|
|
147
|
+
{focuses.map(({ id, name }) => (
|
|
148
|
+
<Task key={id} id={`codebase-review:${id}`} output={outputs.category_review} agent={agents.reviewing} retries={taskRetries}>
|
|
149
|
+
<CategoryReviewPrompt categoryId={id} categoryName={name} relevantDirs={focusDirs[id] ?? null} />
|
|
150
|
+
</Task>
|
|
151
|
+
))}
|
|
152
|
+
</Parallel>
|
|
153
|
+
))}
|
|
154
|
+
|
|
155
|
+
{!skipPhases.has("DISCOVER") && (customDiscover || (
|
|
156
|
+
<Task id="discover" output={outputs.discover} agent={agents.planning} retries={taskRetries}>
|
|
157
|
+
<DiscoverPrompt
|
|
158
|
+
projectName={projectName}
|
|
159
|
+
specsPath={specsPath}
|
|
160
|
+
referenceFiles={referenceFiles}
|
|
161
|
+
categories={focuses}
|
|
162
|
+
completedTicketIds={completedTicketIds}
|
|
163
|
+
previousProgress={progressSummary}
|
|
164
|
+
reviewFindings={reviewFindings}
|
|
165
|
+
/>
|
|
166
|
+
</Task>
|
|
167
|
+
))}
|
|
168
|
+
|
|
169
|
+
{!skipPhases.has("INTEGRATION_TEST") && (customIntegrationTest || (
|
|
170
|
+
<Parallel maxConcurrency={maxConcurrency}>
|
|
171
|
+
{focuses.map(({ id, name }) => {
|
|
172
|
+
const suiteInfo = focusTestSuites[id] ?? { suites: [], setupHints: [], testDirs: [] };
|
|
173
|
+
return (
|
|
174
|
+
<Task key={id} id={`integration-test:${id}`} output={outputs.integration_test} agent={agents.testing} retries={taskRetries}>
|
|
175
|
+
<IntegrationTestPrompt
|
|
176
|
+
categoryId={id}
|
|
177
|
+
categoryName={name}
|
|
178
|
+
suites={suiteInfo.suites}
|
|
179
|
+
setupHints={suiteInfo.setupHints}
|
|
180
|
+
testDirs={suiteInfo.testDirs}
|
|
181
|
+
findingsFile={findingsFile}
|
|
182
|
+
/>
|
|
183
|
+
</Task>
|
|
184
|
+
);
|
|
185
|
+
})}
|
|
186
|
+
</Parallel>
|
|
187
|
+
))}
|
|
188
|
+
|
|
189
|
+
{unfinishedTickets.map((ticket: any) => {
|
|
190
|
+
const researchData = ctx.outputMaybe(outputs.research, { nodeId: `${ticket.id}:research` });
|
|
191
|
+
const planData = ctx.outputMaybe(outputs.plan, { nodeId: `${ticket.id}:plan` });
|
|
192
|
+
const contextFilePath = researchData?.contextFilePath ?? `docs/context/${ticket.id}.md`;
|
|
193
|
+
const planFilePath = planData?.planFilePath ?? `docs/plans/${ticket.id}.md`;
|
|
194
|
+
|
|
195
|
+
return (
|
|
196
|
+
<Worktree key={ticket.id} id={`wt-${ticket.id}`} path={`/tmp/workflow-wt-${ticket.id}`}>
|
|
197
|
+
<Sequence skipIf={ctx.outputMaybe(outputs.report, { nodeId: `${ticket.id}:report` })?.status === "complete"}>
|
|
198
|
+
{customResearch || (
|
|
199
|
+
<Task id={`${ticket.id}:research`} output={outputs.research} agent={agents.planning} retries={taskRetries}>
|
|
200
|
+
<ResearchPrompt
|
|
201
|
+
ticketId={ticket.id}
|
|
202
|
+
ticketTitle={ticket.title}
|
|
203
|
+
ticketDescription={ticket.description}
|
|
204
|
+
ticketCategory={ticket.category}
|
|
205
|
+
referenceFiles={ticket.referenceFiles}
|
|
206
|
+
relevantFiles={ticket.relevantFiles}
|
|
207
|
+
contextFilePath={contextFilePath}
|
|
208
|
+
referencePaths={[specsPath, ...referenceFiles]}
|
|
209
|
+
/>
|
|
210
|
+
</Task>
|
|
211
|
+
)}
|
|
212
|
+
|
|
213
|
+
{customPlan || (
|
|
214
|
+
<Task id={`${ticket.id}:plan`} output={outputs.plan} agent={agents.planning} retries={taskRetries}>
|
|
215
|
+
<PlanPrompt
|
|
216
|
+
ticketId={ticket.id}
|
|
217
|
+
ticketTitle={ticket.title}
|
|
218
|
+
ticketDescription={ticket.description}
|
|
219
|
+
ticketCategory={ticket.category}
|
|
220
|
+
acceptanceCriteria={ticket.acceptanceCriteria ?? []}
|
|
221
|
+
contextFilePath={contextFilePath}
|
|
222
|
+
researchSummary={researchData?.summary ?? null}
|
|
223
|
+
planFilePath={planFilePath}
|
|
224
|
+
tddPatterns={["Write tests FIRST, then implementation"]}
|
|
225
|
+
commitPrefix={prefix}
|
|
226
|
+
mainBranch={mainBranch}
|
|
227
|
+
/>
|
|
228
|
+
</Task>
|
|
229
|
+
)}
|
|
230
|
+
|
|
231
|
+
{/* ValidationLoop */}
|
|
232
|
+
<Sequence>
|
|
233
|
+
{(() => {
|
|
234
|
+
const latestImplement = selectImplement(ctx, ticket.id);
|
|
235
|
+
const latestTest = selectTestResults(ctx, ticket.id);
|
|
236
|
+
const latestSpecReview = selectSpecReview(ctx, ticket.id);
|
|
237
|
+
const { worstSeverity: worstCodeSeverity, mergedIssues: mergedCodeIssues, mergedFeedback: mergedCodeFeedback } = selectCodeReviews(ctx, ticket.id, outputs);
|
|
238
|
+
|
|
239
|
+
const specApproved = latestSpecReview?.severity === "none";
|
|
240
|
+
const codeApproved = worstCodeSeverity === "none";
|
|
241
|
+
const noReviewIssues = specApproved && codeApproved;
|
|
242
|
+
|
|
243
|
+
const toArray = (v: unknown): string[] => Array.isArray(v) ? v : typeof v === "string" ? [v] : [];
|
|
244
|
+
const reviewFeedback = (() => {
|
|
245
|
+
const parts: string[] = [];
|
|
246
|
+
if (latestSpecReview && !specApproved) {
|
|
247
|
+
parts.push(`SPEC REVIEW (${latestSpecReview.severity}): ${latestSpecReview.feedback}`);
|
|
248
|
+
if (latestSpecReview.issues) parts.push(`Issues: ${toArray(latestSpecReview.issues).join("; ")}`);
|
|
249
|
+
}
|
|
250
|
+
if (!codeApproved && mergedCodeFeedback) {
|
|
251
|
+
parts.push(`CODE REVIEW (${worstCodeSeverity}): ${mergedCodeFeedback}`);
|
|
252
|
+
if (mergedCodeIssues.length > 0) parts.push(`Issues: ${mergedCodeIssues.join("; ")}`);
|
|
253
|
+
}
|
|
254
|
+
return parts.length > 0 ? parts.join("\n\n") : null;
|
|
255
|
+
})();
|
|
256
|
+
|
|
257
|
+
return (
|
|
258
|
+
<>
|
|
259
|
+
{customImplement || (
|
|
260
|
+
<Task id={`${ticket.id}:implement`} output={outputs.implement} agent={agents.implementation} retries={taskRetries}>
|
|
261
|
+
<ImplementPrompt
|
|
262
|
+
ticketId={ticket.id}
|
|
263
|
+
ticketTitle={ticket.title}
|
|
264
|
+
ticketCategory={ticket.category}
|
|
265
|
+
planFilePath={planFilePath}
|
|
266
|
+
contextFilePath={contextFilePath}
|
|
267
|
+
implementationSteps={planData?.implementationSteps ?? null}
|
|
268
|
+
previousImplementation={latestImplement ?? null}
|
|
269
|
+
reviewFeedback={reviewFeedback}
|
|
270
|
+
failingTests={latestTest?.failingSummary ?? null}
|
|
271
|
+
testWritingGuidance={["Write unit tests AND integration tests"]}
|
|
272
|
+
implementationGuidance={["Follow architecture patterns from specs"]}
|
|
273
|
+
formatterCommands={Object.entries(buildCmds).map(([lang, cmd]) => `Format ${lang}`)}
|
|
274
|
+
verifyCommands={Object.values(buildCmds)}
|
|
275
|
+
architectureRules={[`Read ${specsPath} for patterns`]}
|
|
276
|
+
commitPrefix={prefix}
|
|
277
|
+
mainBranch={mainBranch}
|
|
278
|
+
emojiPrefixes={emojiPrefixes}
|
|
279
|
+
/>
|
|
280
|
+
</Task>
|
|
281
|
+
)}
|
|
282
|
+
|
|
283
|
+
{customTest || (
|
|
284
|
+
<Task id={`${ticket.id}:test`} output={outputs.test_results} agent={agents.testing} retries={taskRetries}>
|
|
285
|
+
<TestPrompt
|
|
286
|
+
ticketId={ticket.id}
|
|
287
|
+
ticketTitle={ticket.title}
|
|
288
|
+
ticketCategory={ticket.category}
|
|
289
|
+
testSuites={testSuites.length > 0 ? testSuites : Object.entries(testCmds).map(([name, command]) => ({
|
|
290
|
+
name: `${name} tests`,
|
|
291
|
+
command,
|
|
292
|
+
description: `Run ${name} tests`,
|
|
293
|
+
}))}
|
|
294
|
+
fixCommitPrefix={`🐛 fix`}
|
|
295
|
+
mainBranch={mainBranch}
|
|
296
|
+
/>
|
|
297
|
+
</Task>
|
|
298
|
+
)}
|
|
299
|
+
|
|
300
|
+
{customBuildVerify || (
|
|
301
|
+
<Task id={`${ticket.id}:build-verify`} output={outputs.build_verify} agent={agents.testing} retries={taskRetries}>
|
|
302
|
+
<BuildVerifyPrompt
|
|
303
|
+
ticketId={ticket.id}
|
|
304
|
+
ticketTitle={ticket.title}
|
|
305
|
+
ticketCategory={ticket.category}
|
|
306
|
+
filesCreated={latestImplement?.filesCreated ?? null}
|
|
307
|
+
filesModified={latestImplement?.filesModified ?? null}
|
|
308
|
+
whatWasDone={latestImplement?.whatWasDone ?? null}
|
|
309
|
+
/>
|
|
310
|
+
</Task>
|
|
311
|
+
)}
|
|
312
|
+
|
|
313
|
+
<Parallel maxConcurrency={maxConcurrency}>
|
|
314
|
+
{customSpecReview || (
|
|
315
|
+
<Task id={`${ticket.id}:spec-review`} output={outputs.spec_review} agent={agents.reviewing} retries={taskRetries}>
|
|
316
|
+
<SpecReviewPrompt
|
|
317
|
+
ticketId={ticket.id}
|
|
318
|
+
ticketTitle={ticket.title}
|
|
319
|
+
ticketCategory={ticket.category}
|
|
320
|
+
filesCreated={latestImplement?.filesCreated ?? null}
|
|
321
|
+
filesModified={latestImplement?.filesModified ?? null}
|
|
322
|
+
testResults={[
|
|
323
|
+
{ name: "Tests", status: latestTest?.goTestsPassed ? "PASS" : "FAIL" },
|
|
324
|
+
]}
|
|
325
|
+
failingSummary={latestTest?.failingSummary ?? null}
|
|
326
|
+
specChecks={[
|
|
327
|
+
{ name: "Code Style", items: [codeStyle] },
|
|
328
|
+
{ name: "Review Checklist", items: reviewChecklist },
|
|
329
|
+
]}
|
|
330
|
+
/>
|
|
331
|
+
</Task>
|
|
332
|
+
)}
|
|
333
|
+
|
|
334
|
+
{customCodeReview || (
|
|
335
|
+
<Task id={`${ticket.id}:code-review`} output={outputs.code_review} agent={agents.reviewing} retries={taskRetries}>
|
|
336
|
+
<CodeReviewPrompt
|
|
337
|
+
ticketId={ticket.id}
|
|
338
|
+
ticketTitle={ticket.title}
|
|
339
|
+
ticketCategory={ticket.category}
|
|
340
|
+
filesCreated={latestImplement?.filesCreated ?? null}
|
|
341
|
+
filesModified={latestImplement?.filesModified ?? null}
|
|
342
|
+
reviewChecklist={reviewChecklist}
|
|
343
|
+
/>
|
|
344
|
+
</Task>
|
|
345
|
+
)}
|
|
346
|
+
</Parallel>
|
|
347
|
+
|
|
348
|
+
{customReviewFix || (!noReviewIssues && (
|
|
349
|
+
<Task id={`${ticket.id}:review-fix`} output={outputs.review_fix} agent={agents.implementation} retries={taskRetries}>
|
|
350
|
+
<ReviewFixPrompt
|
|
351
|
+
ticketId={ticket.id}
|
|
352
|
+
ticketTitle={ticket.title}
|
|
353
|
+
ticketCategory={ticket.category}
|
|
354
|
+
specSeverity={latestSpecReview?.severity ?? "none"}
|
|
355
|
+
specFeedback={latestSpecReview?.feedback ?? ""}
|
|
356
|
+
specIssues={latestSpecReview?.issues ?? null}
|
|
357
|
+
codeSeverity={worstCodeSeverity}
|
|
358
|
+
codeFeedback={mergedCodeFeedback}
|
|
359
|
+
codeIssues={mergedCodeIssues.length > 0 ? mergedCodeIssues : null}
|
|
360
|
+
validationCommands={Object.values(testCmds)}
|
|
361
|
+
commitPrefix={`🐛 fix`}
|
|
362
|
+
mainBranch={mainBranch}
|
|
363
|
+
emojiPrefixes={emojiPrefixes}
|
|
364
|
+
/>
|
|
365
|
+
</Task>
|
|
366
|
+
))}
|
|
367
|
+
</>
|
|
368
|
+
);
|
|
369
|
+
})()}
|
|
370
|
+
</Sequence>
|
|
371
|
+
|
|
372
|
+
{customReport || (
|
|
373
|
+
<Task id={`${ticket.id}:report`} output={outputs.report} agent={agents.reporting} retries={taskRetries}>
|
|
374
|
+
<ReportPrompt
|
|
375
|
+
ticketId={ticket.id}
|
|
376
|
+
ticketTitle={ticket.title}
|
|
377
|
+
ticketCategory={ticket.category}
|
|
378
|
+
acceptanceCriteria={ticket.acceptanceCriteria ?? []}
|
|
379
|
+
specSeverity={ctx.outputMaybe(outputs.spec_review, { nodeId: `${ticket.id}:spec-review` })?.severity ?? "none"}
|
|
380
|
+
codeSeverity={selectCodeReviews(ctx, ticket.id, outputs).worstSeverity}
|
|
381
|
+
allIssuesResolved={ctx.outputMaybe(outputs.review_fix, { nodeId: `${ticket.id}:review-fix` })?.allIssuesResolved ?? true}
|
|
382
|
+
reviewRounds={1}
|
|
383
|
+
goTests={selectTestResults(ctx, ticket.id)?.goTestsPassed ? "PASS" : "FAIL"}
|
|
384
|
+
rustTests={selectTestResults(ctx, ticket.id)?.rustTestsPassed ? "PASS" : "FAIL"}
|
|
385
|
+
e2eTests={selectTestResults(ctx, ticket.id)?.e2eTestsPassed ? "PASS" : "FAIL"}
|
|
386
|
+
sqlcGen={selectTestResults(ctx, ticket.id)?.sqlcGenPassed ? "PASS" : "FAIL"}
|
|
387
|
+
/>
|
|
388
|
+
</Task>
|
|
389
|
+
)}
|
|
390
|
+
</Sequence>
|
|
391
|
+
</Worktree>
|
|
392
|
+
);
|
|
393
|
+
})}
|
|
394
|
+
</Parallel>
|
|
395
|
+
</Ralph>
|
|
396
|
+
);
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
// Compound components for advanced customization
|
|
400
|
+
SuperRalph.UpdateProgress = function UpdateProgress(_props: any) { return null; };
|
|
401
|
+
SuperRalph.Discover = function Discover(_props: any) { return null; };
|
|
402
|
+
SuperRalph.IntegrationTest = function IntegrationTest(_props: any) { return null; };
|
|
403
|
+
SuperRalph.CategoryReview = function CategoryReview(_props: any) { return null; };
|
|
404
|
+
SuperRalph.Research = function Research(_props: any) { return null; };
|
|
405
|
+
SuperRalph.Plan = function Plan(_props: any) { return null; };
|
|
406
|
+
SuperRalph.Implement = function Implement(_props: any) { return null; };
|
|
407
|
+
SuperRalph.Test = function Test(_props: any) { return null; };
|
|
408
|
+
SuperRalph.BuildVerify = function BuildVerify(_props: any) { return null; };
|
|
409
|
+
SuperRalph.SpecReview = function SpecReview(_props: any) { return null; };
|
|
410
|
+
SuperRalph.CodeReview = function CodeReview(_props: any) { return null; };
|
|
411
|
+
SuperRalph.ReviewFix = function ReviewFix(_props: any) { return null; };
|
|
412
|
+
SuperRalph.Report = function Report(_props: any) { return null; };
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import type { SmithersCtx } from "smithers-orchestrator";
|
|
2
|
+
import { selectAllTickets, selectReviewTickets, selectProgressSummary } from "../selectors";
|
|
3
|
+
|
|
4
|
+
export type SuperRalphContext = {
|
|
5
|
+
ctx: SmithersCtx<any>;
|
|
6
|
+
completedTicketIds: string[];
|
|
7
|
+
unfinishedTickets: any[];
|
|
8
|
+
reviewFindings: string | null;
|
|
9
|
+
progressSummary: string | null;
|
|
10
|
+
focuses: ReadonlyArray<{ readonly id: string; readonly name: string }>;
|
|
11
|
+
outputs: any;
|
|
12
|
+
target: any;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
export type UseSuperRalphConfig = {
|
|
16
|
+
focuses: ReadonlyArray<{ readonly id: string; readonly name: string }>;
|
|
17
|
+
outputs: any;
|
|
18
|
+
target: any;
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Hook to extract SuperRalph state from SmithersCtx.
|
|
23
|
+
* Use this for controlled component pattern or to access workflow state.
|
|
24
|
+
*/
|
|
25
|
+
export function useSuperRalph(ctx: SmithersCtx<any>, config: UseSuperRalphConfig): SuperRalphContext {
|
|
26
|
+
const { findings: reviewFindings } = selectReviewTickets(ctx, config.focuses, config.outputs);
|
|
27
|
+
const { completed: completedTicketIds, unfinished: unfinishedTickets } = selectAllTickets(ctx, config.focuses, config.outputs);
|
|
28
|
+
const progressSummary = selectProgressSummary(ctx, config.outputs);
|
|
29
|
+
|
|
30
|
+
return {
|
|
31
|
+
ctx,
|
|
32
|
+
completedTicketIds,
|
|
33
|
+
unfinishedTickets,
|
|
34
|
+
reviewFindings,
|
|
35
|
+
progressSummary,
|
|
36
|
+
focuses: config.focuses,
|
|
37
|
+
outputs: config.outputs,
|
|
38
|
+
target: config.target,
|
|
39
|
+
};
|
|
40
|
+
}
|