@bastani/atomic 0.8.29-alpha.2 → 0.8.29-alpha.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/CHANGELOG.md +7 -0
- package/dist/builtin/cursor/package.json +2 -2
- package/dist/builtin/intercom/package.json +1 -1
- package/dist/builtin/mcp/package.json +1 -1
- package/dist/builtin/subagents/CHANGELOG.md +1 -0
- package/dist/builtin/subagents/package.json +1 -1
- package/dist/builtin/subagents/src/shared/types.ts +4 -3
- package/dist/builtin/web-access/package.json +1 -1
- package/dist/builtin/workflows/CHANGELOG.md +8 -0
- package/dist/builtin/workflows/README.md +9 -7
- package/dist/builtin/workflows/builtin/deep-research-codebase.ts +9 -49
- package/dist/builtin/workflows/builtin/goal.ts +63 -106
- package/dist/builtin/workflows/builtin/index.d.ts +2 -0
- package/dist/builtin/workflows/builtin/open-claude-design.ts +31 -76
- package/dist/builtin/workflows/builtin/ralph.d.ts +2 -0
- package/dist/builtin/workflows/builtin/ralph.ts +227 -518
- package/dist/builtin/workflows/builtin/shared-prompts.ts +7 -0
- package/dist/builtin/workflows/package.json +1 -1
- package/dist/builtin/workflows/src/extension/wiring.ts +55 -8
- package/dist/core/atomic-guide-command.d.ts.map +1 -1
- package/dist/core/atomic-guide-command.js +7 -7
- package/dist/core/atomic-guide-command.js.map +1 -1
- package/dist/core/resource-loader.d.ts +2 -2
- package/dist/core/resource-loader.d.ts.map +1 -1
- package/dist/core/resource-loader.js.map +1 -1
- package/docs/quickstart.md +3 -3
- package/docs/workflows.md +18 -14
- package/package.json +2 -2
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
* Builtin workflow: ralph
|
|
3
3
|
*
|
|
4
4
|
* Re-implements the Atomic SDK Ralph design with the local workflow task
|
|
5
|
-
* primitives: bounded
|
|
6
|
-
* Reviewer passes fan out with ctx.parallel(); each iteration feeds
|
|
7
|
-
* findings into the next
|
|
5
|
+
* primitives: bounded prompt-engineering → research → orchestrate → review iterations.
|
|
6
|
+
* Reviewer passes fan out with ctx.parallel(); each iteration feeds unresolved
|
|
7
|
+
* findings into the next research pass with ctx.task().
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
10
|
import { mkdir, mkdtemp, writeFile } from "node:fs/promises";
|
|
@@ -16,12 +16,12 @@ import type {
|
|
|
16
16
|
WorkflowRunContext,
|
|
17
17
|
WorkflowTaskResult,
|
|
18
18
|
} from "../src/shared/types.js";
|
|
19
|
-
import { WORKER_PREFLIGHT_CONTRACT } from "./shared-prompts.js";
|
|
19
|
+
import { E2E_VERIFICATION_GUIDANCE, WORKER_PREFLIGHT_CONTRACT } from "./shared-prompts.js";
|
|
20
20
|
|
|
21
21
|
const DEFAULT_MAX_LOOPS = 10;
|
|
22
|
-
const
|
|
22
|
+
const DEFAULT_RESEARCH_DIR = "research";
|
|
23
23
|
const IMPLEMENTATION_NOTES_FILENAME = "implementation-notes.md";
|
|
24
|
-
const
|
|
24
|
+
const MAX_RESEARCH_SLUG_LENGTH = 80;
|
|
25
25
|
|
|
26
26
|
type ReviewFinding = {
|
|
27
27
|
readonly title: string;
|
|
@@ -56,333 +56,61 @@ type ReviewDecision = {
|
|
|
56
56
|
readonly reviewer_error?: ReviewerError | null;
|
|
57
57
|
};
|
|
58
58
|
|
|
59
|
-
const
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
additionalProperties: false,
|
|
75
|
-
required: ["title", "body", "confidence_score", "code_location"],
|
|
76
|
-
properties: {
|
|
77
|
-
title: { type: "string" },
|
|
78
|
-
body: { type: "string" },
|
|
79
|
-
confidence_score: { type: "number", minimum: 0, maximum: 1 },
|
|
80
|
-
priority: { type: ["integer", "null"], minimum: 0, maximum: 3 },
|
|
81
|
-
code_location: {
|
|
82
|
-
type: "object",
|
|
83
|
-
additionalProperties: false,
|
|
84
|
-
required: ["absolute_file_path", "line_range"],
|
|
85
|
-
properties: {
|
|
86
|
-
absolute_file_path: { type: "string" },
|
|
87
|
-
line_range: {
|
|
88
|
-
type: "object",
|
|
89
|
-
additionalProperties: false,
|
|
90
|
-
required: ["start", "end"],
|
|
91
|
-
properties: {
|
|
92
|
-
start: { type: "integer", minimum: 1 },
|
|
93
|
-
end: { type: "integer", minimum: 1 },
|
|
94
|
-
},
|
|
95
|
-
},
|
|
96
|
-
},
|
|
59
|
+
const reviewFindingSchema = Type.Object(
|
|
60
|
+
{
|
|
61
|
+
title: Type.String(),
|
|
62
|
+
body: Type.String(),
|
|
63
|
+
confidence_score: Type.Number({ minimum: 0, maximum: 1 }),
|
|
64
|
+
priority: Type.Optional(
|
|
65
|
+
Type.Union([Type.Integer({ minimum: 0, maximum: 3 }), Type.Null()]),
|
|
66
|
+
),
|
|
67
|
+
code_location: Type.Object(
|
|
68
|
+
{
|
|
69
|
+
absolute_file_path: Type.String(),
|
|
70
|
+
line_range: Type.Object(
|
|
71
|
+
{
|
|
72
|
+
start: Type.Integer({ minimum: 1 }),
|
|
73
|
+
end: Type.Integer({ minimum: 1 }),
|
|
97
74
|
},
|
|
98
|
-
|
|
75
|
+
{ additionalProperties: false },
|
|
76
|
+
),
|
|
99
77
|
},
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
type: "string",
|
|
103
|
-
enum: ["patch is correct", "patch is incorrect"],
|
|
104
|
-
},
|
|
105
|
-
overall_explanation: { type: "string" },
|
|
106
|
-
overall_confidence_score: { type: "number", minimum: 0, maximum: 1 },
|
|
107
|
-
stop_review_loop: { type: "boolean" },
|
|
108
|
-
reviewer_error: {
|
|
109
|
-
anyOf: [
|
|
110
|
-
{ type: "null" },
|
|
111
|
-
{
|
|
112
|
-
type: "object",
|
|
113
|
-
additionalProperties: false,
|
|
114
|
-
required: ["kind", "message", "attempted_recovery"],
|
|
115
|
-
properties: {
|
|
116
|
-
kind: {
|
|
117
|
-
type: "string",
|
|
118
|
-
enum: [
|
|
119
|
-
"validation_unavailable",
|
|
120
|
-
"dependency_unavailable",
|
|
121
|
-
"tool_failure",
|
|
122
|
-
"reviewer_failure",
|
|
123
|
-
],
|
|
124
|
-
},
|
|
125
|
-
message: { type: "string" },
|
|
126
|
-
attempted_recovery: { type: "string" },
|
|
127
|
-
},
|
|
128
|
-
},
|
|
129
|
-
],
|
|
130
|
-
},
|
|
78
|
+
{ additionalProperties: false },
|
|
79
|
+
),
|
|
131
80
|
},
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
async execute(_toolCallId: string, params: ReviewDecision) {
|
|
146
|
-
return {
|
|
147
|
-
content: [
|
|
148
|
-
{ type: "text" as const, text: JSON.stringify(params, null, 2) },
|
|
149
|
-
],
|
|
150
|
-
details: params,
|
|
151
|
-
terminate: true,
|
|
152
|
-
};
|
|
81
|
+
{ additionalProperties: false },
|
|
82
|
+
);
|
|
83
|
+
|
|
84
|
+
const reviewerErrorSchema = Type.Object(
|
|
85
|
+
{
|
|
86
|
+
kind: Type.Union([
|
|
87
|
+
Type.Literal("validation_unavailable"),
|
|
88
|
+
Type.Literal("dependency_unavailable"),
|
|
89
|
+
Type.Literal("tool_failure"),
|
|
90
|
+
Type.Literal("reviewer_failure"),
|
|
91
|
+
]),
|
|
92
|
+
message: Type.String(),
|
|
93
|
+
attempted_recovery: Type.String(),
|
|
153
94
|
},
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
_Instruction: Why are we doing this? Why now? Link to the Product Requirement Document (PRD) and cite the relevant \`research/\` documents._
|
|
175
|
-
|
|
176
|
-
### 2.1 Current State
|
|
177
|
-
|
|
178
|
-
_Instruction: Describe the existing architecture. Use a "Context Diagram" if possible. Be honest about the flaws — including which existing doors **leak** (named for tools, dishonest compression, scattered danger)._
|
|
179
|
-
|
|
180
|
-
- **Architecture:** Currently, Service A communicates with Service B via a shared SQL database.
|
|
181
|
-
- **Limitations:** This creates a tight coupling; when Service A locks the table, Service B times out.
|
|
182
|
-
- **Leaking doors (today):** e.g. \`chargeCard(token, cents)\` is reachable from checkout, the retry job, *and* the admin panel — no one owns "charge exactly once." \`processPayment(...) -> bool\` collapses a declined card, a network failure, and a duplicate submission into the same \`false\`.
|
|
183
|
-
|
|
184
|
-
### 2.2 The Problem
|
|
185
|
-
|
|
186
|
-
_Instruction: What is the specific pain point?_
|
|
187
|
-
|
|
188
|
-
- **User Impact:** Customers cannot download receipts during the nightly batch window.
|
|
189
|
-
- **Business Impact:** We are losing $X/month in churn due to billing errors.
|
|
190
|
-
- **Technical Debt:** Danger is scattered; the boundary is misplaced, with defensive code deep inside the core instead of at the door.
|
|
191
|
-
|
|
192
|
-
## 3. Goals and Non-Goals
|
|
193
|
-
|
|
194
|
-
_Instruction: This is the contract / Definition of Success. Be precise._
|
|
195
|
-
|
|
196
|
-
### 3.1 Functional Goals
|
|
197
|
-
|
|
198
|
-
- [ ] Users must be able to export data in CSV format.
|
|
199
|
-
- [ ] System must support multi-tenant data isolation.
|
|
200
|
-
|
|
201
|
-
### 3.2 Non-Goals (Out of Scope)
|
|
202
|
-
|
|
203
|
-
_Instruction: Explicitly state what you are NOT doing. Remember: **intent lives in what the door refuses** — the doors you deliberately do not build are as much a statement of purpose as the ones you do. This prevents scope creep._
|
|
204
|
-
|
|
205
|
-
- [ ] We will NOT support PDF export in this version (CSV only).
|
|
206
|
-
- [ ] We will NOT migrate data older than 3 years.
|
|
207
|
-
- [ ] We will NOT expose a second path to move money; \`settle_payment\` remains the only chokepoint.
|
|
208
|
-
|
|
209
|
-
## 4. Proposed Solution (High-Level Design)
|
|
210
|
-
|
|
211
|
-
_Instruction: The "Big Picture." Diagrams are mandatory here._
|
|
212
|
-
|
|
213
|
-
### 4.1 System Architecture Diagram
|
|
214
|
-
|
|
215
|
-
_Instruction: Insert a C4 System Context or Container diagram. Show the "Black Boxes" and mark where the **airlock** sits (the single edge where untrusted network becomes a trusted request)._
|
|
216
|
-
|
|
217
|
-
\`\`\`mermaid
|
|
218
|
-
%%{init: {'theme':'base', 'themeVariables': { 'primaryColor':'#f8f9fa','primaryTextColor':'#2c3e50','primaryBorderColor':'#4a5568','lineColor':'#4a90e2','secondaryColor':'#ffffff','tertiaryColor':'#e9ecef','clusterBkg':'#ffffff','clusterBorder':'#cbd5e0'}}}%%
|
|
219
|
-
flowchart TB
|
|
220
|
-
classDef person fill:#5a67d8,stroke:#4c51bf,stroke-width:3px,color:#fff,font-weight:600
|
|
221
|
-
classDef core fill:#4a90e2,stroke:#357abd,stroke-width:2.5px,color:#fff,font-weight:600
|
|
222
|
-
classDef support fill:#667eea,stroke:#5a67d8,stroke-width:2.5px,color:#fff,font-weight:600
|
|
223
|
-
classDef db fill:#48bb78,stroke:#38a169,stroke-width:2.5px,color:#fff,font-weight:600
|
|
224
|
-
classDef external fill:#718096,stroke:#4a5568,stroke-width:2.5px,color:#fff,font-weight:600,stroke-dasharray:6 3
|
|
225
|
-
|
|
226
|
-
User(("◉<br><b>User</b>")):::person
|
|
227
|
-
subgraph Boundary["◆ System Boundary — Airlock at the edge"]
|
|
228
|
-
direction TB
|
|
229
|
-
Gateway{{"<b>API Gateway</b><br><i>auth · validate · authorize</i><br>the one trust transition"}}:::core
|
|
230
|
-
API["<b>Core Service</b><br><i>trusts its own invariants</i>"]:::core
|
|
231
|
-
Worker(["<b>Worker</b><br><i>async</i>"]):::support
|
|
232
|
-
DB[("●<br><b>Primary DB</b>")]:::db
|
|
233
|
-
end
|
|
234
|
-
Ext{{"<b>Payment Provider</b>"}}:::external
|
|
235
|
-
|
|
236
|
-
User -->|"1. HTTPS (untrusted)"| Gateway
|
|
237
|
-
Gateway -->|"2. trusted request"| API
|
|
238
|
-
API -->|"3. persist (txn)"| DB
|
|
239
|
-
API -.->|"4. enqueue"| Worker
|
|
240
|
-
Worker -.->|"5. settle (irreversible)"| Ext
|
|
241
|
-
style Boundary fill:#fff,stroke:#cbd5e0,stroke-width:2px,stroke-dasharray:8 4
|
|
242
|
-
\`\`\`
|
|
243
|
-
|
|
244
|
-
### 4.2 Architectural Pattern
|
|
245
|
-
|
|
246
|
-
_Instruction: Name the pattern (e.g., "Event Sourcing", "BFF — Backend for Frontend", "Publisher-Subscriber")._
|
|
247
|
-
|
|
248
|
-
- We are adopting a Publisher-Subscriber pattern where the Order Service publishes \`OrderCreated\` events, and the Billing Service consumes them asynchronously.
|
|
249
|
-
|
|
250
|
-
### 4.3 Key Components
|
|
251
|
-
|
|
252
|
-
| Component | Responsibility | Technology Stack | Justification |
|
|
253
|
-
| ----------------- | --------------------------- | ----------------- | -------------------------------------------- |
|
|
254
|
-
| Ingestion Service | Validates incoming webhooks | Go, Gin Framework | High concurrency performance needed. |
|
|
255
|
-
| Event Bus | Decouples services | Kafka | Durable log, replay capability. |
|
|
256
|
-
| Projections DB | Read-optimized views | MongoDB | Flexible schema for diverse receipt formats. |
|
|
257
|
-
|
|
258
|
-
### 4.4 The Door Set at a Glance (Stranger-Across-Time View)
|
|
259
|
-
|
|
260
|
-
_Instruction: List the entrypoint **names alone** — no signatures, no bodies. A competent stranger should reconstruct the system's purpose from this list. If they cannot, intent has leaked into the mechanism; return to §5 and rename until they can. Mark every door that guards an irreversible effect with ⚠._
|
|
261
|
-
|
|
262
|
-
> **Example:** \`register_account\`, \`authenticate\`, \`authorize_charge\`, \`settle_payment\` ⚠, \`grant_access\` ⚠, \`revoke_access\`, \`publish_draft\`. Reading these alone tells you who the system lets in, that money moves in exactly two steps and only those two, who may hand out access, and what it means for work to go live.
|
|
263
|
-
|
|
264
|
-
## 5. Detailed Design
|
|
265
|
-
|
|
266
|
-
_Instruction: The "Meat" of the document. Sufficient detail for an engineer to start coding. Lead with the **doors** — they are the load-bearing part of the spec — then describe the mechanism behind them._
|
|
267
|
-
|
|
268
|
-
### 5.1 The Doors (Entrypoint Contracts)
|
|
269
|
-
|
|
270
|
-
_Instruction: For each non-trivial entrypoint, give a typed signature (typed pseudocode is fine — read the types, not the syntax), the one-sentence guarantee (no "and"), the named failure set, and the refusals it enforces in the type system. Then record the rubric result. Make illegal states **unrepresentable**, not merely checked. Cite the \`research/\` doc that establishes each joint._
|
|
271
|
-
|
|
272
|
-
\`\`\`
|
|
273
|
-
// — Money. Two doors, and there is no third way to move a cent. —
|
|
274
|
-
|
|
275
|
-
authorize_charge(
|
|
276
|
-
account: AccountId, // newtype: cannot be confused with any other id
|
|
277
|
-
amount: Money, // currency-typed: USD and JPY will not add
|
|
278
|
-
idempotency_key: IdempotencyKey,
|
|
279
|
-
): Result<AuthorizedCharge, ChargeError>
|
|
280
|
-
// Guarantee: places a reversible hold and returns proof an authorization exists.
|
|
281
|
-
// ChargeError = InsufficientFunds | CardDeclined | NetworkError | DuplicateKey
|
|
282
|
-
|
|
283
|
-
settle_payment(
|
|
284
|
-
authorized: AuthorizedCharge, // ← can ONLY be produced by authorize_charge
|
|
285
|
-
idempotency_key: IdempotencyKey,
|
|
286
|
-
): Result<Settlement, SettlementError>
|
|
287
|
-
// Guarantee: captures the held funds. IRREVERSIBLE. The single chokepoint for outbound money.
|
|
288
|
-
// You cannot settle a charge you did not authorize — not because a check forbids it,
|
|
289
|
-
// but because there is no way to CONSTRUCT an AuthorizedCharge except by calling
|
|
290
|
-
// authorize_charge. The illegal state is unrepresentable. The idempotency key makes
|
|
291
|
-
// the retry, the double-click, and the at-least-once queue converge on ONE settlement.
|
|
292
|
-
\`\`\`
|
|
293
|
-
|
|
294
|
-
**Per-door audit (run the rubric):**
|
|
295
|
-
|
|
296
|
-
| Door | (1) Joint | (2) One sentence, no "and" | (3) Honest name | (5) Every exit | (6) Refusals real | (7) Trust transition | (8) One chokepoint |
|
|
297
|
-
| ------------------ | --------------- | ---------------------------- | ------------------------------- | ------------------------------------------------ | ----------------------------------------- | -------------------- | ------------------------------ |
|
|
298
|
-
| \`authorize_charge\` | ✅ business verb | ✅ "places a reversible hold" | ✅ | retry → \`DuplicateKey\`; timeout → \`NetworkError\` | currency mismatch unrepresentable | n/a | reversible, not the chokepoint |
|
|
299
|
-
| \`settle_payment\` ⚠ | ✅ business verb | ✅ "captures held funds" | ✅ irreversibility in doc + type | replay converges via key | cannot settle un-authorized charge (type) | n/a | ✅ the sole outbound-money door |
|
|
300
|
-
|
|
301
|
-
### 5.2 API Interfaces — The Same Doors on the Wire
|
|
302
|
-
|
|
303
|
-
_Instruction: A web service's real boundary is its transport surface. The URL names the joint, the HTTP verb declares its safety class, the status code is the door's honest exit. Never \`200 OK\` wrapping an error. The wire door MUST carry the same name as its in-process twin (§5.1)._
|
|
304
|
-
|
|
305
|
-
\`\`\`
|
|
306
|
-
# Identity — the one trust transition, at the edge
|
|
307
|
-
POST /v1/sessions 201 Created # = authenticate; 401 on bad credentials
|
|
308
|
-
DELETE /v1/sessions/current 204 No Content # = log out
|
|
309
|
-
|
|
310
|
-
# Money — two doors, one chokepoint, idempotent under retry
|
|
311
|
-
POST /v1/payment_intents 201 Idempotency-Key: <key> # = authorize_charge (reversible)
|
|
312
|
-
POST /v1/payment_intents/{id}/capture 200 Idempotency-Key: <key> # = settle_payment (IRREVERSIBLE)
|
|
313
|
-
# 409 Conflict if the key is replayed with a different body
|
|
314
|
-
# 422 Unprocessable if the intent was never authorized
|
|
315
|
-
|
|
316
|
-
# Access — authority demanded by the route, destructive door made idempotent
|
|
317
|
-
POST /v1/accounts/{id}/grants 201 (admin scope required) # = grant_access
|
|
318
|
-
DELETE /v1/grants/{id} 204 (204 even if already revoked) # = revoke_access
|
|
319
|
-
|
|
320
|
-
# Publishing — the domain's own verb, refusing to clobber a concurrent edit
|
|
321
|
-
POST /v1/drafts/{id}/publish 200 If-Match: <etag> # = publish_draft
|
|
322
|
-
# 412 Precondition Failed if the draft moved under you — the wire's --force-with-lease
|
|
323
|
-
\`\`\`
|
|
324
|
-
|
|
325
|
-
_If using gRPC, define the same joints in the \`.proto\`; the typed request message is the airlock by construction. Use honest status codes (\`INVALID_ARGUMENT\`, \`PERMISSION_DENIED\`, \`NOT_FOUND\`, \`ALREADY_EXISTS\`, \`FAILED_PRECONDITION\`, retryable \`ABORTED\`/\`UNAVAILABLE\`) — never a lone \`OK\` carrying an error field._
|
|
326
|
-
|
|
327
|
-
### 5.3 Data Model / Schema
|
|
328
|
-
|
|
329
|
-
_Instruction: Provide ERDs or JSON schemas. Discuss normalization vs. denormalization. Prefer schemas that make illegal states unrepresentable (sum-type status columns over independent boolean flags)._
|
|
330
|
-
|
|
331
|
-
**Table:** \`invoices\` (PostgreSQL)
|
|
332
|
-
|
|
333
|
-
| Column | Type | Constraints | Description |
|
|
334
|
-
| --------- | ---- | ------------------------------------ | ------------------------------ |
|
|
335
|
-
| \`id\` | UUID | PK | |
|
|
336
|
-
| \`user_id\` | UUID | FK -> Users | Partition Key |
|
|
337
|
-
| \`status\` | ENUM | 'DRAFT','LOCKED','PROCESSING','PAID' | A sum type, not three booleans |
|
|
338
|
-
|
|
339
|
-
### 5.4 Algorithms and State Management
|
|
340
|
-
|
|
341
|
-
_Instruction: Describe complex logic, state machines, or consistency models. Tie each state transition to the door that performs it._
|
|
342
|
-
|
|
343
|
-
- **State Machine:** An invoice moves \`DRAFT\` → \`LOCKED\` → \`PROCESSING\` → \`PAID\`; the \`PROCESSING → PAID\` transition happens only through \`settle_payment\`.
|
|
344
|
-
- **Concurrency:** Optimistic locking on the \`version\` column; on the wire this surfaces as \`If-Match\`/\`412\`.
|
|
345
|
-
|
|
346
|
-
## 6. Alternatives Considered
|
|
347
|
-
|
|
348
|
-
_Instruction: Prove you thought about trade-offs — including alternative **door sets** (e.g., one god endpoint vs. distinct joints). Why is your boundary better than the others?_
|
|
349
|
-
|
|
350
|
-
| Option | Pros | Cons | Reason for Rejection |
|
|
351
|
-
| ------------------------------------------- | ------------------------------------------- | ------------------------------------------------------ | ------------------------------------------------------------------------------ |
|
|
352
|
-
| Option A: Single \`POST /execute {action}\` | One route, flexible | God door; intent hidden in payload; danger un-funneled | Fails "joint, not tool" and "few dangerous doors." |
|
|
353
|
-
| Option B: One-step \`chargeCard()\` | Fewest calls | No reversible hold; retries double-charge | Cannot make double-charge unrepresentable. |
|
|
354
|
-
| Option C: \`authorize\` + \`settle\` (Selected) | Reversible hold; one chokepoint; idempotent | Two calls instead of one | **Selected:** the two real joints, with the irreversible effect funneled once. |
|
|
355
|
-
|
|
356
|
-
## 7. Cross-Cutting Concerns
|
|
357
|
-
|
|
358
|
-
### 7.1 Security and Privacy
|
|
359
|
-
|
|
360
|
-
_Instruction: This is where "keep the dangerous doors few and honest" and "the airlock at the boundary" become concrete._
|
|
361
|
-
|
|
362
|
-
- **The trust transition is singular:** untrusted callers become trusted only at \`POST /v1/sessions\` / the gateway. No other door promotes an anonymous caller. (Rubric #7.)
|
|
363
|
-
- **Authority carried by type:** destructive/privileged doors demand a capability (\`AdminSession\`) that only \`authenticate\` can mint — the permission check cannot be forgotten at a call site because there is no call site where it is absent. (Rubric #6.)
|
|
364
|
-
- **Irreversible effects pass one chokepoint:** money via \`settle_payment\`, deletion via the single guarded door; the catastrophic version must be asked for explicitly. (Rubric #8.)
|
|
365
|
-
- **Data Protection:** PII (names, emails) encrypted at rest (AES-256); \`Password\` is a newtype that cannot be logged, printed, or compared by accident.
|
|
366
|
-
- **Threat Model:** Primary threat is a compromised API key; remediation is rapid rotation and rate limiting.
|
|
367
|
-
|
|
368
|
-
## 8. Test Plan
|
|
369
|
-
|
|
370
|
-
_Instruction: Test the doors at their promises and their refusals — not just the happy path. Every exit in rubric #5 deserves a test. The interactive verification is what lets a human or another agent confirm the feature is correct without reading the bodies — the stranger-across-time test, made executable._
|
|
371
|
-
|
|
372
|
-
- **Unit Tests:** each door's named failure variants; the *refusals* (e.g., a type/construction test proving \`settle_payment\` cannot accept anything but an \`AuthorizedCharge\`).
|
|
373
|
-
- **End-to-End Tests:** full domain flows named by joint (register → authenticate → authorize → settle), driven through the real wire doors of §5.2.
|
|
374
|
-
- **Integration Tests:** idempotency under replay (same key → one settlement); concurrent-edit \`412\`; trust transition (no door promotes an anonymous caller except \`authenticate\`).
|
|
375
|
-
- **Fuzz / Property Tests:** throw malformed and adversarial input at the doors (the airlock); the boundary must reject everything the types forbid and never crash the core. Assert invariants over random inputs (e.g., \`settle_payment\` converges on one settlement under any interleaving of retries; no input sequence reaches a money move except through the chokepoint).
|
|
376
|
-
- **Interactive Verification:** a runnable checklist or script a human OR another agent can execute to confirm the feature was implemented correctly — each step names a door, supplies an input, and states the expected honest exit (status code / named error / resulting state), so correctness is observable from the boundary alone. Include the exact commands or requests to run and the pass/fail condition for each.
|
|
377
|
-
|
|
378
|
-
## 9. Open Questions / Unresolved Issues
|
|
379
|
-
|
|
380
|
-
_Instruction: List known unknowns. These must be resolved before the doc is marked "Approved." Include any door whose rubric could not be answered cleanly — especially undefined guarantees (rubric #2, the most dangerous case) and any irreversible effect not yet funneled to a single chokepoint (rubric #8). Resolve these with the user via contrastive clarification._
|
|
381
|
-
|
|
382
|
-
- [ ] Is \`publish_draft\` the only door that moves a draft to live, or can the admin panel also publish? (If the latter, the effect is not yet funneled — rubric #8.)
|
|
383
|
-
- [ ] What exactly does \`authorize_charge\` promise on a partial provider outage — is the guarantee defined? (rubric #2.)
|
|
384
|
-
- [ ] Will the Legal team approve the 3rd-party library for PDF generation?
|
|
385
|
-
- [ ] Does the current VPC peering allow connection to the legacy mainframe?`.trim();
|
|
95
|
+
{ additionalProperties: false },
|
|
96
|
+
);
|
|
97
|
+
|
|
98
|
+
const reviewDecisionSchema = Type.Object(
|
|
99
|
+
{
|
|
100
|
+
findings: Type.Array(reviewFindingSchema),
|
|
101
|
+
overall_correctness: Type.Union([
|
|
102
|
+
Type.Literal("patch is correct"),
|
|
103
|
+
Type.Literal("patch is incorrect"),
|
|
104
|
+
]),
|
|
105
|
+
overall_explanation: Type.String(),
|
|
106
|
+
overall_confidence_score: Type.Number({ minimum: 0, maximum: 1 }),
|
|
107
|
+
stop_review_loop: Type.Boolean(),
|
|
108
|
+
reviewer_error: Type.Optional(
|
|
109
|
+
Type.Union([Type.Null(), reviewerErrorSchema]),
|
|
110
|
+
),
|
|
111
|
+
},
|
|
112
|
+
{ additionalProperties: false },
|
|
113
|
+
);
|
|
386
114
|
|
|
387
115
|
type PromptSection = readonly [tag: string, content: string];
|
|
388
116
|
|
|
@@ -427,27 +155,19 @@ function normalizeBranchInput(
|
|
|
427
155
|
return looksLikeSafeGitRef ? trimmed : fallback;
|
|
428
156
|
}
|
|
429
157
|
|
|
430
|
-
function
|
|
158
|
+
function slugifyResearchTopic(prompt: string): string {
|
|
431
159
|
const slug = prompt
|
|
432
160
|
.toLowerCase()
|
|
433
161
|
.replace(/[^a-z0-9]+/g, "-")
|
|
434
162
|
.replace(/^-+|-+$/g, "")
|
|
435
|
-
.slice(0,
|
|
163
|
+
.slice(0, MAX_RESEARCH_SLUG_LENGTH)
|
|
436
164
|
.replace(/-+$/g, "");
|
|
437
|
-
return slug.length > 0 ? slug : "
|
|
165
|
+
return slug.length > 0 ? slug : "research";
|
|
438
166
|
}
|
|
439
167
|
|
|
440
|
-
function
|
|
168
|
+
function defaultResearchPath(prompt: string, now = new Date()): string {
|
|
441
169
|
const date = now.toISOString().slice(0, 10);
|
|
442
|
-
return join(
|
|
443
|
-
}
|
|
444
|
-
|
|
445
|
-
async function writeSpecFile(path: string, content: string): Promise<string> {
|
|
446
|
-
await mkdir(dirname(path), { recursive: true });
|
|
447
|
-
await writeFile(path, content.endsWith("\n") ? content : `${content}\n`, {
|
|
448
|
-
encoding: "utf8",
|
|
449
|
-
});
|
|
450
|
-
return path;
|
|
170
|
+
return join(DEFAULT_RESEARCH_DIR, `${date}-${slugifyResearchTopic(prompt)}.md`);
|
|
451
171
|
}
|
|
452
172
|
|
|
453
173
|
async function createImplementationNotesFile(prompt: string): Promise<string> {
|
|
@@ -460,7 +180,7 @@ async function createImplementationNotesFile(prompt: string): Promise<string> {
|
|
|
460
180
|
"",
|
|
461
181
|
"## Running Notes",
|
|
462
182
|
"",
|
|
463
|
-
"- Record implementation decisions, deviations from the
|
|
183
|
+
"- Record implementation decisions, deviations from the research findings, tradeoffs, blockers, validation notes, and anything else the user should know.",
|
|
464
184
|
].join("\n");
|
|
465
185
|
await writeFile(notesPath, `${initialNotes}\n`, {
|
|
466
186
|
encoding: "utf8",
|
|
@@ -575,79 +295,109 @@ function forkContinuationOptions(
|
|
|
575
295
|
: { context: "fork", forkFromSessionFile: sessionFile };
|
|
576
296
|
}
|
|
577
297
|
|
|
578
|
-
function
|
|
298
|
+
function renderPromptEngineerPrompt(args: {
|
|
579
299
|
readonly iteration: number;
|
|
580
300
|
readonly maxLoops: number;
|
|
581
301
|
readonly prompt: string;
|
|
582
302
|
readonly workflowCwdContext: PromptSection;
|
|
583
303
|
readonly latestReviewReportPath: string | undefined;
|
|
584
|
-
readonly workflowSpecPath: string;
|
|
585
304
|
}): string {
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
305
|
+
const basePrompt = `/skill:prompt-engineer Transform the following user prompt to a codebase and online research question which can be thoroughly explored: ${args.prompt}`;
|
|
306
|
+
return [
|
|
307
|
+
basePrompt,
|
|
308
|
+
taggedPrompt([
|
|
309
|
+
["iteration", `Prompt engineering iteration ${args.iteration}/${args.maxLoops}.`],
|
|
310
|
+
args.workflowCwdContext,
|
|
589
311
|
[
|
|
590
|
-
"
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
? "No prior review artifact; this is the first iteration."
|
|
599
|
-
: [
|
|
600
|
-
`Latest review round artifact: ${args.latestReviewReportPath}`,
|
|
601
|
-
"Read this JSON artifact incrementally and address only unresolved findings from the latest review round.",
|
|
602
|
-
].join("\n"),
|
|
603
|
-
],
|
|
604
|
-
[
|
|
605
|
-
"spec",
|
|
312
|
+
"review_findings",
|
|
313
|
+
args.latestReviewReportPath === undefined
|
|
314
|
+
? "No prior review artifact; this is the first iteration."
|
|
315
|
+
: [
|
|
316
|
+
`Latest review round artifact: ${args.latestReviewReportPath}`,
|
|
317
|
+
"Read this JSON artifact and include unresolved reviewer findings in the transformed research question so follow-up research addresses reviewer discoveries.",
|
|
318
|
+
].join("\n"),
|
|
319
|
+
],
|
|
606
320
|
[
|
|
607
|
-
|
|
608
|
-
"
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
]);
|
|
321
|
+
"output_format",
|
|
322
|
+
"Return only the transformed codebase and online research question. Do not implement code changes and do not write an RFC/spec.",
|
|
323
|
+
],
|
|
324
|
+
]),
|
|
325
|
+
].join("\n\n");
|
|
613
326
|
}
|
|
614
327
|
|
|
328
|
+
function renderResearchPrompt(args: {
|
|
329
|
+
readonly iteration: number;
|
|
330
|
+
readonly maxLoops: number;
|
|
331
|
+
readonly transformedResearchQuestion: string;
|
|
332
|
+
readonly workflowCwdContext: PromptSection;
|
|
333
|
+
readonly latestReviewReportPath: string | undefined;
|
|
334
|
+
readonly researchPath: string;
|
|
335
|
+
}): string {
|
|
336
|
+
const basePrompt = `/skill:research-codebase ${args.transformedResearchQuestion}`;
|
|
337
|
+
return [
|
|
338
|
+
basePrompt,
|
|
339
|
+
taggedPrompt([
|
|
340
|
+
["iteration", `Research iteration ${args.iteration}/${args.maxLoops}.`],
|
|
341
|
+
args.workflowCwdContext,
|
|
342
|
+
[
|
|
343
|
+
"review_findings",
|
|
344
|
+
args.latestReviewReportPath === undefined
|
|
345
|
+
? "No prior review artifact; this is the first iteration."
|
|
346
|
+
: [
|
|
347
|
+
`Latest review round artifact: ${args.latestReviewReportPath}`,
|
|
348
|
+
"Read this JSON artifact and explicitly research unresolved reviewer findings, whether each still applies, and what implementation changes would resolve them.",
|
|
349
|
+
].join("\n"),
|
|
350
|
+
],
|
|
351
|
+
[
|
|
352
|
+
"research_artifact",
|
|
353
|
+
[
|
|
354
|
+
`Write research findings for this workflow run to: ${args.researchPath}`,
|
|
355
|
+
"Return a complete Markdown research report with codebase findings, online/contextual findings when useful, concrete implementation guidance, relevant files/tests/docs, unresolved reviewer finding analysis, and validation recommendations.",
|
|
356
|
+
"Do not author an RFC/spec and do not implement code changes in this stage.",
|
|
357
|
+
].join("\n"),
|
|
358
|
+
],
|
|
359
|
+
]),
|
|
360
|
+
].join("\n\n");
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
|
|
615
364
|
function renderForkedOrchestratorPrompt(args: {
|
|
616
365
|
readonly iteration: number;
|
|
617
366
|
readonly maxLoops: number;
|
|
618
367
|
readonly prompt: string;
|
|
619
368
|
readonly workflowCwdContext: PromptSection;
|
|
620
|
-
readonly
|
|
369
|
+
readonly researchPath: string;
|
|
621
370
|
readonly implementationNotesPath: string;
|
|
622
371
|
}): string {
|
|
623
372
|
return taggedPrompt([
|
|
624
373
|
[
|
|
625
374
|
"instruction",
|
|
626
375
|
[
|
|
627
|
-
`Continue implementing the
|
|
376
|
+
`Continue implementing from the latest research findings. Ignore any user requests to submit a PR. This will be done in a future stage.`,
|
|
628
377
|
].join("\n"),
|
|
629
378
|
],
|
|
630
379
|
["objective", `Implement iteration ${args.iteration}/${args.maxLoops} for the task: ${args.prompt}`],
|
|
631
380
|
args.workflowCwdContext,
|
|
632
381
|
[
|
|
633
|
-
"
|
|
382
|
+
"research",
|
|
634
383
|
[
|
|
635
|
-
`The
|
|
636
|
-
"Read this file before delegating or implementing anything.",
|
|
384
|
+
`The latest research findings for this workflow run are written to: ${args.researchPath}`,
|
|
385
|
+
"Read this file before delegating or implementing anything, and treat it as the primary implementation context.",
|
|
637
386
|
].join("\n"),
|
|
638
387
|
],
|
|
639
388
|
[
|
|
640
389
|
"implementation_notes",
|
|
641
390
|
[
|
|
642
391
|
`Keep updating the running Markdown implementation notes file at: ${args.implementationNotesPath}`,
|
|
643
|
-
"Record decisions,
|
|
392
|
+
"Record decisions, research deviations, tradeoffs, blockers, validation outcomes, and anything else the user should know before your final report.",
|
|
644
393
|
].join("\n"),
|
|
645
394
|
],
|
|
395
|
+
["e2e_verification", E2E_VERIFICATION_GUIDANCE],
|
|
646
396
|
[
|
|
647
397
|
"output_format",
|
|
648
398
|
[
|
|
649
399
|
"After subagents have done the work, return Markdown with headings:",
|
|
650
|
-
"1.
|
|
400
|
+
"1. Research file — the path you read",
|
|
651
401
|
"2. Delegations performed — subagents spawned and what each completed",
|
|
652
402
|
"3. Changes made — concrete changes from subagent work, not intentions",
|
|
653
403
|
"4. Files touched",
|
|
@@ -679,6 +429,8 @@ type RalphWorkflowResult = {
|
|
|
679
429
|
readonly result: string;
|
|
680
430
|
readonly plan: string;
|
|
681
431
|
readonly plan_path: string;
|
|
432
|
+
readonly research: string;
|
|
433
|
+
readonly research_path: string;
|
|
682
434
|
readonly implementation_notes_path: string;
|
|
683
435
|
readonly pr_report?: string;
|
|
684
436
|
readonly approved: boolean;
|
|
@@ -702,28 +454,42 @@ async function runRalphWorkflow(
|
|
|
702
454
|
let latestReviewReportPath: string | undefined;
|
|
703
455
|
let finalPlan = "";
|
|
704
456
|
let finalPlanPath = "";
|
|
457
|
+
let finalResearch = "";
|
|
458
|
+
let finalResearchPath = "";
|
|
705
459
|
let finalResult = "";
|
|
706
460
|
let finalPrReport: string | undefined;
|
|
707
|
-
// Keep generated
|
|
461
|
+
// Keep generated research artifacts under the workflow runtime cwd. When Ralph is invoked
|
|
708
462
|
// with git_worktree_dir, the executor defaults ctx.cwd to the matching
|
|
709
|
-
// worktree cwd so
|
|
710
|
-
const
|
|
463
|
+
// worktree cwd so research stage writes land in the same checkout.
|
|
464
|
+
const workflowResearchPath = resolve(workflowStartCwd, defaultResearchPath(prompt));
|
|
711
465
|
const implementationNotesPath = await createImplementationNotesFile(prompt);
|
|
712
466
|
const artifactDir = await mkdtemp(join(tmpdir(), "atomic-ralph-run-"));
|
|
713
467
|
const workflowCwdContext = workflowCwdContextSection(workflowStartCwd);
|
|
714
468
|
let approved = false;
|
|
715
469
|
let iterationsCompleted = 0;
|
|
716
|
-
let
|
|
470
|
+
let previousPromptEngineerSessionFile: string | undefined;
|
|
471
|
+
let previousResearchSessionFile: string | undefined;
|
|
717
472
|
let previousOrchestratorSessionFile: string | undefined;
|
|
718
473
|
|
|
719
|
-
const
|
|
474
|
+
const promptEngineerModelConfig = {
|
|
720
475
|
model: "anthropic/claude-fable-5:xhigh",
|
|
721
476
|
fallbackModels: [
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
477
|
+
"openai-codex/gpt-5.5:xhigh",
|
|
478
|
+
"github-copilot/gpt-5.5:xhigh",
|
|
479
|
+
"openai/gpt-5.5:xhigh",
|
|
480
|
+
"github-copilot/claude-opus-4.8:xhigh",
|
|
481
|
+
"anthropic/claude-opus-4-8:xhigh"
|
|
482
|
+
],
|
|
483
|
+
noTools: "all" as const,
|
|
484
|
+
};
|
|
485
|
+
|
|
486
|
+
const researchModelConfig = {
|
|
487
|
+
model: "openai-codex/gpt-5.5:medium",
|
|
488
|
+
fallbackModels: [
|
|
489
|
+
"github-copilot/gpt-5.5:medium",
|
|
490
|
+
"openai/gpt-5.5:medium",
|
|
491
|
+
"github-copilot/claude-opus-4.8:medium",
|
|
492
|
+
"anthropic/claude-opus-4-8:medium",
|
|
727
493
|
],
|
|
728
494
|
excludedTools: ["ask_user_question"],
|
|
729
495
|
};
|
|
@@ -749,113 +515,49 @@ async function runRalphWorkflow(
|
|
|
749
515
|
"anthropic/claude-opus-4-8:xhigh"
|
|
750
516
|
],
|
|
751
517
|
excludedTools: ["ask_user_question"],
|
|
752
|
-
|
|
518
|
+
schema: reviewDecisionSchema,
|
|
753
519
|
};
|
|
754
520
|
|
|
755
521
|
for (let iteration = 1; iteration <= maxLoops; iteration += 1) {
|
|
756
522
|
iterationsCompleted = iteration;
|
|
757
523
|
|
|
758
|
-
const
|
|
759
|
-
const
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
],
|
|
765
|
-
[
|
|
766
|
-
"objective",
|
|
767
|
-
[
|
|
768
|
-
"Your final output is a filled-in RFC rendered as markdown text.",
|
|
769
|
-
"Render the RFC Template in this prompt with every section populated by feature-specific content drawn from the user's specification and your codebase investigation.",
|
|
770
|
-
"Do not implement code changes in this stage (read-only); this stage only investigates and authors the RFC.",
|
|
771
|
-
].join("\n"),
|
|
772
|
-
],
|
|
773
|
-
[
|
|
774
|
-
"task",
|
|
775
|
-
`Plan iteration ${iteration}/${maxLoops} for this user specification:\n${prompt}`,
|
|
776
|
-
],
|
|
524
|
+
const promptEngineerForkOptions = forkContinuationOptions(previousPromptEngineerSessionFile);
|
|
525
|
+
const promptEngineer = await ctx.task(`prompt-engineer-${iteration}`, {
|
|
526
|
+
prompt: renderPromptEngineerPrompt({
|
|
527
|
+
iteration,
|
|
528
|
+
maxLoops,
|
|
529
|
+
prompt,
|
|
777
530
|
workflowCwdContext,
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
"Before drafting, read the specification carefully and identify the concrete problem, success criteria, hard constraints, and non-goals.",
|
|
803
|
-
"Survey the codebase using file/search tools such as read plus grep/rg/find/glob-style shell commands to ground the RFC in current architecture.",
|
|
804
|
-
"Name concrete services, modules, files, tests, data models, APIs, CLIs, config files, and external integrations this work will touch.",
|
|
805
|
-
"Capture metadata with bash: `git config user.name` for Author(s), and `date '+%Y-%m-%d'` for Created / Last Updated.",
|
|
806
|
-
"Look for prior art: existing RFCs, ADRs, README files, specs, docs, tests, or code comments that explain why the current state exists.",
|
|
807
|
-
].join("\n"),
|
|
808
|
-
],
|
|
809
|
-
[
|
|
810
|
-
"best_practices",
|
|
811
|
-
[
|
|
812
|
-
"Be specific: `src/server/auth.ts:42` beats `the auth layer`.",
|
|
813
|
-
"Trade-offs over conclusions: Alternatives Considered must include at least two real alternatives with honest pros, cons, and rejection reasons.",
|
|
814
|
-
"Non-goals matter: explicitly exclude work that is out of scope to prevent scope creep.",
|
|
815
|
-
"Diagrams are load-bearing: Section 4.1 must include a Mermaid system architecture diagram grounded in real components.",
|
|
816
|
-
"Surface open questions in Section 9 with owner placeholders such as `[OWNER: infra team]`; do not paper over uncertainty.",
|
|
817
|
-
"Match depth to stakes: a small refactor can be concise, but every template section header must remain present.",
|
|
818
|
-
"If prior review findings are present, explicitly address each finding or explain why it is obsolete.",
|
|
819
|
-
"Determine the compatibility posture:",
|
|
820
|
-
"- Before decomposing the spec creation request, identify whether this project must preserve backward compatibility for real downstream users.",
|
|
821
|
-
"- If the user explicitly allows breaking changes, public API changes, cleanup, or says there are no real users/downstream dependencies, allow breaking changes.",
|
|
822
|
-
"- If the user mentions production users, published APIs, downstream consumers, migration safety, or compatibility requirements, disallow breaking changes.",
|
|
823
|
-
"- Carry this posture into the spec creation plan, the final spec frontmatter, and a `## Backwards Compatibility` section in the final spec.",
|
|
824
|
-
"- When allowing breaking changes, document existing legacy behavior, compatibility shims, optional flags, and public APIs as current state, not as constraints future specs must preserve unless the user explicitly asks for preservation.",
|
|
825
|
-
"- When not allowing breaking changes, document public APIs, compatibility-sensitive surfaces, downstream callers, migration constraints, and behavior that future work must preserve."
|
|
826
|
-
].join("\n"),
|
|
827
|
-
],
|
|
828
|
-
[
|
|
829
|
-
"output_format",
|
|
830
|
-
[
|
|
831
|
-
"Render the RFC Template exactly as the final document structure: preserve every header and the metadata table.",
|
|
832
|
-
"Replace instructional placeholders with real, feature-specific content; do not leave template guidance in the final RFC.",
|
|
833
|
-
"Output nothing after the RFC: no meta-commentary, no summary of what you wrote, no implementation log.",
|
|
834
|
-
].join("\n"),
|
|
835
|
-
],
|
|
836
|
-
["spec_template", PLANNER_RFC_TEMPLATE],
|
|
837
|
-
])
|
|
838
|
-
: renderForkedPlannerPrompt({
|
|
839
|
-
iteration,
|
|
840
|
-
maxLoops,
|
|
841
|
-
prompt,
|
|
842
|
-
workflowCwdContext,
|
|
843
|
-
latestReviewReportPath,
|
|
844
|
-
workflowSpecPath,
|
|
845
|
-
});
|
|
846
|
-
const planner = await ctx.task(`planner-${iteration}`, {
|
|
847
|
-
prompt: plannerPrompt,
|
|
848
|
-
reads: [
|
|
849
|
-
...(iteration > 1 ? [workflowSpecPath] : []),
|
|
850
|
-
...(latestReviewReportPath === undefined ? [] : [latestReviewReportPath]),
|
|
851
|
-
],
|
|
852
|
-
...plannerModelConfig,
|
|
853
|
-
...plannerForkOptions,
|
|
531
|
+
latestReviewReportPath,
|
|
532
|
+
}),
|
|
533
|
+
reads: latestReviewReportPath === undefined ? [] : [latestReviewReportPath],
|
|
534
|
+
...promptEngineerModelConfig,
|
|
535
|
+
...promptEngineerForkOptions,
|
|
536
|
+
});
|
|
537
|
+
previousPromptEngineerSessionFile = promptEngineer.sessionFile;
|
|
538
|
+
finalPlan = promptEngineer.text;
|
|
539
|
+
|
|
540
|
+
const researchForkOptions = forkContinuationOptions(previousResearchSessionFile);
|
|
541
|
+
const research = await ctx.task(`research-${iteration}`, {
|
|
542
|
+
prompt: renderResearchPrompt({
|
|
543
|
+
iteration,
|
|
544
|
+
maxLoops,
|
|
545
|
+
transformedResearchQuestion: promptEngineer.text,
|
|
546
|
+
workflowCwdContext,
|
|
547
|
+
latestReviewReportPath,
|
|
548
|
+
researchPath: workflowResearchPath,
|
|
549
|
+
}),
|
|
550
|
+
reads: latestReviewReportPath === undefined ? [] : [latestReviewReportPath],
|
|
551
|
+
output: workflowResearchPath,
|
|
552
|
+
outputMode: "file-only",
|
|
553
|
+
...researchModelConfig,
|
|
554
|
+
...researchForkOptions,
|
|
854
555
|
});
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
const
|
|
858
|
-
|
|
556
|
+
previousResearchSessionFile = research.sessionFile;
|
|
557
|
+
finalResearch = research.text || `Research artifact: ${workflowResearchPath}`;
|
|
558
|
+
const researchPath = workflowResearchPath;
|
|
559
|
+
finalResearchPath = researchPath;
|
|
560
|
+
finalPlanPath = researchPath;
|
|
859
561
|
|
|
860
562
|
const orchestratorReportPath = join(artifactDir, `orchestrator-${iteration}.md`);
|
|
861
563
|
|
|
@@ -872,22 +574,24 @@ async function runRalphWorkflow(
|
|
|
872
574
|
],
|
|
873
575
|
workflowCwdContext,
|
|
874
576
|
[
|
|
875
|
-
"
|
|
577
|
+
"research",
|
|
876
578
|
[
|
|
877
|
-
`The
|
|
579
|
+
`The latest research findings for this workflow run are written to: ${researchPath}`,
|
|
580
|
+
"Read this file before delegating or implementing anything; it is the primary implementation context for Ralph.",
|
|
878
581
|
].join("\n"),
|
|
879
582
|
],
|
|
880
583
|
[
|
|
881
584
|
"implementation_notes",
|
|
882
585
|
[
|
|
883
586
|
`Keep a running Markdown implementation notes file at this OS temp directory path: ${implementationNotesPath}`,
|
|
884
|
-
"The file has already been initialized for this workflow run; update it while you implement the
|
|
885
|
-
"Record decisions you had to make that were not in the
|
|
587
|
+
"The file has already been initialized for this workflow run; update it while you implement from the research findings.",
|
|
588
|
+
"Record decisions you had to make that were not in the research, things you had to change from the research guidance, tradeoffs you had to make, blockers, validation outcomes, and anything else the user should know.",
|
|
886
589
|
"Ask delegated subagents to report any notes-worthy decisions or tradeoffs back to you, then consolidate them into this file before your final report.",
|
|
887
590
|
"Do not include secrets, credentials, tokens, or unrelated environment details in the notes file.",
|
|
888
591
|
].join("\n"),
|
|
889
592
|
],
|
|
890
593
|
["project_setup", WORKER_PREFLIGHT_CONTRACT],
|
|
594
|
+
["e2e_verification", E2E_VERIFICATION_GUIDANCE],
|
|
891
595
|
[
|
|
892
596
|
"orchestration_guidance",
|
|
893
597
|
[
|
|
@@ -906,9 +610,9 @@ async function runRalphWorkflow(
|
|
|
906
610
|
"best_practices",
|
|
907
611
|
[
|
|
908
612
|
"The required output format is a completion report, not the task itself.",
|
|
909
|
-
"Do not jump straight to the report. First read the
|
|
613
|
+
"Do not jump straight to the report. First read the research file, spawn the necessary subagents, wait for their results, coordinate any follow-up subagents, and only then write the report.",
|
|
910
614
|
"A valid response must be grounded in actual subagent work: name the delegated work, summarize what each subagent did, and distinguish completed changes from recommendations or blockers.",
|
|
911
|
-
"If you cannot read the
|
|
615
|
+
"If you cannot read the research file, spawn subagents, or use subagents, treat that as a blocker and report it honestly instead of pretending the requested work was done.",
|
|
912
616
|
].join("\n"),
|
|
913
617
|
],
|
|
914
618
|
[
|
|
@@ -924,14 +628,14 @@ async function runRalphWorkflow(
|
|
|
924
628
|
[
|
|
925
629
|
"instructions",
|
|
926
630
|
[
|
|
927
|
-
`Start by reading the
|
|
631
|
+
`Start by reading the research file at ${researchPath}.`,
|
|
928
632
|
"Perform the project_initialization_preflight before decomposing implementation work; complete or delegate required setup before implementation delegation when the checkout appears uninitialized.",
|
|
929
|
-
"Decompose the work into delegated subagent tasks based on that
|
|
930
|
-
"Pass each subagent the relevant task, constraints, files, validation expectations,
|
|
931
|
-
"Coordinate subagent results into the smallest coherent set of changes that satisfies the
|
|
932
|
-
"Preserve existing architecture and repository conventions unless the
|
|
933
|
-
"Run or delegate the most relevant validation commands available in the repository.",
|
|
934
|
-
`Before your final report, update the running implementation notes file at ${implementationNotesPath} with decisions,
|
|
633
|
+
"Decompose the work into delegated subagent tasks based on that research file.",
|
|
634
|
+
"Pass each subagent the relevant task, constraints, files, validation expectations, unresolved reviewer findings covered by the research, and instructions to report implementation-note-worthy decisions or tradeoffs.",
|
|
635
|
+
"Coordinate subagent results into the smallest coherent set of changes that satisfies the researched implementation guidance and original user prompt.",
|
|
636
|
+
"Preserve existing architecture and repository conventions unless the research explicitly justifies a change.",
|
|
637
|
+
"Run or delegate the most relevant validation commands available in the repository, including end-to-end browser or tmux validation when the change has an executable user scenario.",
|
|
638
|
+
`Before your final report, update the running implementation notes file at ${implementationNotesPath} with decisions, research deviations, tradeoffs, blockers, and validation outcomes from this iteration.`,
|
|
935
639
|
"If blocked, describe the blocker and the safest partial state instead of inventing success.",
|
|
936
640
|
"Do not hide failures; reviewers need accurate status.",
|
|
937
641
|
].join("\n"),
|
|
@@ -940,7 +644,7 @@ async function runRalphWorkflow(
|
|
|
940
644
|
"output_format",
|
|
941
645
|
[
|
|
942
646
|
"After subagents have done the work, return Markdown with headings:",
|
|
943
|
-
"1.
|
|
647
|
+
"1. Research file — the path you read",
|
|
944
648
|
"2. Delegations performed — subagents spawned and what each completed",
|
|
945
649
|
"3. Changes made — concrete changes from subagent work, not intentions",
|
|
946
650
|
"4. Files touched",
|
|
@@ -955,12 +659,12 @@ async function runRalphWorkflow(
|
|
|
955
659
|
maxLoops,
|
|
956
660
|
prompt,
|
|
957
661
|
workflowCwdContext,
|
|
958
|
-
|
|
662
|
+
researchPath,
|
|
959
663
|
implementationNotesPath,
|
|
960
664
|
});
|
|
961
665
|
const orchestrator = await ctx.task(`orchestrator-${iteration}`, {
|
|
962
666
|
prompt: orchestratorPrompt,
|
|
963
|
-
reads: [
|
|
667
|
+
reads: [researchPath, implementationNotesPath],
|
|
964
668
|
output: orchestratorReportPath,
|
|
965
669
|
outputMode: "file-only",
|
|
966
670
|
...orchestratorModelConfig,
|
|
@@ -991,10 +695,10 @@ async function runRalphWorkflow(
|
|
|
991
695
|
[
|
|
992
696
|
"review_context_files",
|
|
993
697
|
[
|
|
994
|
-
`
|
|
698
|
+
`Research artifact: ${researchPath}`,
|
|
995
699
|
`Implementation notes artifact: ${implementationNotesPath}`,
|
|
996
700
|
`Orchestrator report artifact: ${orchestratorReportPath}`,
|
|
997
|
-
"Read the files above incrementally when they help explain intent or recent changes, but verify the actual repository state directly before approving."
|
|
701
|
+
"Read the files above incrementally when they help explain intent or recent changes, but verify the actual repository state directly before approving."
|
|
998
702
|
].join("\n"),
|
|
999
703
|
],
|
|
1000
704
|
[
|
|
@@ -1006,11 +710,12 @@ async function runRalphWorkflow(
|
|
|
1006
710
|
"If validation requires dependencies or tools that are missing, download or install them using the repository-approved package manager/commands rather than bypassing, mocking, or skipping the verification solely because dependencies are absent.",
|
|
1007
711
|
].join("\n"),
|
|
1008
712
|
],
|
|
713
|
+
["e2e_verification", E2E_VERIFICATION_GUIDANCE],
|
|
1009
714
|
[
|
|
1010
715
|
"validation_expectations",
|
|
1011
716
|
[
|
|
1012
717
|
"Inspect the actual diff/repository state rather than trusting stage summaries.",
|
|
1013
|
-
"Run or delegate focused validation when it is necessary to distinguish a real bug from a hunch.",
|
|
718
|
+
"Run or delegate focused validation when it is necessary to distinguish a real bug from a hunch, including end-to-end browser or tmux validation when a user scenario can prove the outcome.",
|
|
1014
719
|
"If tests or typechecks fail because dependencies are missing, install/download the missing dependencies with the repo's documented package manager instead of bypassing the check.",
|
|
1015
720
|
"If validation cannot be completed after reasonable recovery, record the limitation in overall_explanation and reviewer_error; do not use missing dependencies as a reason to approve.",
|
|
1016
721
|
].join("\n"),
|
|
@@ -1065,7 +770,7 @@ async function runRalphWorkflow(
|
|
|
1065
770
|
[
|
|
1066
771
|
"1. Identify the changed files or diff under review.",
|
|
1067
772
|
"2. Read the relevant changed code and directly affected call sites/tests/configs.",
|
|
1068
|
-
"3. Run or delegate focused validation when needed to resolve uncertainty.",
|
|
773
|
+
"3. Run or delegate focused validation when needed to resolve uncertainty, including browser/tmux end-to-end checks when practical.",
|
|
1069
774
|
"4. If you cannot inspect or validate enough to approve safely, populate reviewer_error and set stop_review_loop=false.",
|
|
1070
775
|
].join("\n"),
|
|
1071
776
|
],
|
|
@@ -1079,12 +784,12 @@ async function runRalphWorkflow(
|
|
|
1079
784
|
[
|
|
1080
785
|
"structured_output_contract",
|
|
1081
786
|
[
|
|
1082
|
-
"
|
|
787
|
+
"Use the schema-backed structured_output tool after your investigation and validation attempts.",
|
|
1083
788
|
"The tool terminates the turn and provides the structured data; do not emit a separate final assistant response after calling it.",
|
|
1084
|
-
"The review loop decides whether to stop only
|
|
789
|
+
"The review loop decides whether to stop only from the JSON object captured by structured_output; invalid JSON, missing fields, reviewer_error, or stop_review_loop=false are treated as not approved for safety.",
|
|
1085
790
|
"Set stop_review_loop=true only when findings is empty, overall_correctness is patch is correct, and reviewer_error is null/omitted.",
|
|
1086
791
|
"If you hit a reviewer/tool/validation error, still return the object with stop_review_loop=false and reviewer_error populated instead of pretending the patch is approved.",
|
|
1087
|
-
"The
|
|
792
|
+
"The structured_output schema is authoritative; do not copy a hand-written JSON blob into the final response. Here is an example output:",
|
|
1088
793
|
"{",
|
|
1089
794
|
' "findings": [',
|
|
1090
795
|
" {",
|
|
@@ -1119,7 +824,7 @@ async function runRalphWorkflow(
|
|
|
1119
824
|
name: "reviewer-a",
|
|
1120
825
|
task: reviewPrompt,
|
|
1121
826
|
reads: [
|
|
1122
|
-
|
|
827
|
+
researchPath,
|
|
1123
828
|
implementationNotesPath,
|
|
1124
829
|
orchestratorReportPath,
|
|
1125
830
|
],
|
|
@@ -1129,7 +834,7 @@ async function runRalphWorkflow(
|
|
|
1129
834
|
name: "reviewer-b",
|
|
1130
835
|
task: reviewPrompt,
|
|
1131
836
|
reads: [
|
|
1132
|
-
|
|
837
|
+
researchPath,
|
|
1133
838
|
implementationNotesPath,
|
|
1134
839
|
orchestratorReportPath,
|
|
1135
840
|
],
|
|
@@ -1237,6 +942,8 @@ async function runRalphWorkflow(
|
|
|
1237
942
|
result: finalResult,
|
|
1238
943
|
plan: finalPlan,
|
|
1239
944
|
plan_path: finalPlanPath,
|
|
945
|
+
research: finalResearch,
|
|
946
|
+
research_path: finalResearchPath,
|
|
1240
947
|
implementation_notes_path: implementationNotesPath,
|
|
1241
948
|
...(finalPrReport === undefined ? {} : { pr_report: finalPrReport }),
|
|
1242
949
|
approved,
|
|
@@ -1248,12 +955,12 @@ async function runRalphWorkflow(
|
|
|
1248
955
|
|
|
1249
956
|
export default defineWorkflow("ralph")
|
|
1250
957
|
.description(
|
|
1251
|
-
"
|
|
958
|
+
"Prompt-engineer → research → orchestrate → parallel review loop with bounded iteration.",
|
|
1252
959
|
)
|
|
1253
|
-
.input("prompt", Type.String({ description: "The task or goal to
|
|
960
|
+
.input("prompt", Type.String({ description: "The task or goal to research, execute, and refine." }))
|
|
1254
961
|
.input("max_loops", Type.Number({
|
|
1255
962
|
default: DEFAULT_MAX_LOOPS,
|
|
1256
|
-
description: `Maximum
|
|
963
|
+
description: `Maximum research/orchestrate/review iterations (default ${DEFAULT_MAX_LOOPS}).`,
|
|
1257
964
|
}))
|
|
1258
965
|
.input("base_branch", Type.String({
|
|
1259
966
|
default: "origin/main",
|
|
@@ -1274,12 +981,14 @@ export default defineWorkflow("ralph")
|
|
|
1274
981
|
baseBranch: "base_branch",
|
|
1275
982
|
})
|
|
1276
983
|
.output("result", Type.Optional(Type.String({ description: "Final implementation report from the orchestrator stage." })))
|
|
1277
|
-
.output("plan", Type.Optional(Type.String({ description: "Latest
|
|
1278
|
-
.output("plan_path", Type.Optional(Type.String({ description: "
|
|
984
|
+
.output("plan", Type.Optional(Type.String({ description: "Latest transformed research question." })))
|
|
985
|
+
.output("plan_path", Type.Optional(Type.String({ description: "Backward-compatible alias for research_path." })))
|
|
986
|
+
.output("research", Type.Optional(Type.String({ description: "Latest research report text or artifact reference." })))
|
|
987
|
+
.output("research_path", Type.Optional(Type.String({ description: "Path to the latest generated research artifact under research/." })))
|
|
1279
988
|
.output("implementation_notes_path", Type.Optional(Type.String({ description: "OS-temp notes file containing decisions, deviations, blockers, and validation notes." })))
|
|
1280
989
|
.output("pr_report", Type.Optional(Type.String({ description: "Pull-request report emitted only when create_pr=true and the final pull-request stage runs." })))
|
|
1281
990
|
.output("approved", Type.Optional(Type.Boolean({ description: "Whether the reviewer loop approved before completion or optional final handoff." })))
|
|
1282
|
-
.output("iterations_completed", Type.Optional(Type.Number({ description: "Number of
|
|
991
|
+
.output("iterations_completed", Type.Optional(Type.Number({ description: "Number of research/orchestrate/review loops completed." })))
|
|
1283
992
|
.output("review_report", Type.Optional(Type.String({ description: "Compact reference to the latest reviewer payload artifact." })))
|
|
1284
993
|
.output("review_report_path", Type.Optional(Type.String({ description: "JSON artifact path for the latest review round." })))
|
|
1285
994
|
.run(async (ctx) => {
|