@bastani/atomic 0.9.6 → 0.9.7-alpha.1
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 +16 -0
- package/dist/builtin/cursor/CHANGELOG.md +6 -0
- package/dist/builtin/cursor/package.json +2 -2
- package/dist/builtin/intercom/CHANGELOG.md +10 -0
- package/dist/builtin/intercom/README.md +1 -1
- package/dist/builtin/intercom/contact-supervisor-tool.ts +19 -32
- package/dist/builtin/intercom/index-heavy.ts +8 -51
- package/dist/builtin/intercom/index.ts +13 -1
- package/dist/builtin/intercom/intercom-tool.ts +24 -23
- package/dist/builtin/intercom/package.json +1 -1
- package/dist/builtin/intercom/reply-waiter.ts +115 -0
- package/dist/builtin/intercom/skills/intercom/SKILL.md +9 -2
- package/dist/builtin/intercom/subagent-relay.ts +11 -1
- package/dist/builtin/mcp/CHANGELOG.md +6 -0
- package/dist/builtin/mcp/package.json +1 -1
- package/dist/builtin/subagents/CHANGELOG.md +6 -0
- package/dist/builtin/subagents/package.json +1 -1
- package/dist/builtin/subagents/skills/subagent/SKILL.md +3 -1
- package/dist/builtin/subagents/src/runs/shared/model-fallback.ts +3 -2
- package/dist/builtin/web-access/CHANGELOG.md +6 -0
- package/dist/builtin/web-access/package.json +1 -1
- package/dist/builtin/workflows/CHANGELOG.md +20 -0
- package/dist/builtin/workflows/README.md +5 -5
- package/dist/builtin/workflows/builtin/goal-artifacts.ts +17 -4
- package/dist/builtin/workflows/builtin/goal-prompts.ts +32 -22
- package/dist/builtin/workflows/builtin/goal-reducer.ts +29 -5
- package/dist/builtin/workflows/builtin/goal-review.ts +6 -11
- package/dist/builtin/workflows/builtin/goal-runner.ts +10 -11
- package/dist/builtin/workflows/builtin/open-claude-design-runner.ts +2 -2
- package/dist/builtin/workflows/builtin/ralph-core.ts +5 -54
- package/dist/builtin/workflows/builtin/ralph-forked-prompts.ts +103 -0
- package/dist/builtin/workflows/builtin/ralph-models.ts +10 -10
- package/dist/builtin/workflows/builtin/ralph-review-gate.ts +27 -24
- package/dist/builtin/workflows/builtin/ralph-reviewer-prompt.ts +16 -9
- package/dist/builtin/workflows/builtin/ralph-runner.ts +47 -21
- package/dist/builtin/workflows/builtin/review-convergence.ts +118 -0
- package/dist/builtin/workflows/builtin/shared-prompts.ts +40 -0
- package/dist/builtin/workflows/package.json +1 -1
- package/dist/builtin/workflows/src/extension/workflow-prompts.ts +1 -0
- package/dist/builtin/workflows/src/runs/shared/model-fallback-failures.ts +4 -5
- package/dist/core/agent-session-retry.d.ts.map +1 -1
- package/dist/core/agent-session-retry.js +2 -2
- package/dist/core/agent-session-retry.js.map +1 -1
- package/docs/settings.md +1 -1
- package/docs/subagents.md +5 -1
- package/docs/workflows.md +111 -5
- package/npm-shrinkwrap.json +23 -23
- package/package.json +2 -2
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
// Forked-continuation prompt renderers for the builtin Ralph workflow.
|
|
2
|
+
//
|
|
3
|
+
// A forked stage session already carries the role, contracts, guidance, and
|
|
4
|
+
// output format from its own earlier prompts, so these renderers send only the
|
|
5
|
+
// per-iteration delta plus a one-line pointer back to the guidance already
|
|
6
|
+
// established in the forked history. Keep the full canonical contracts in the
|
|
7
|
+
// first-iteration prompts (see ralph-core.ts / ralph-runner.ts) and never
|
|
8
|
+
// duplicate them here.
|
|
9
|
+
import { taggedPrompt } from "./ralph-core.js";
|
|
10
|
+
|
|
11
|
+
// Forked continuation of the previous refinement session: the fork already
|
|
12
|
+
// carries the skill instructions, request, acceptance criteria, contracts, and
|
|
13
|
+
// working directory.
|
|
14
|
+
export function renderForkedResearchPromptRefinementPrompt(args: {
|
|
15
|
+
readonly latestReviewReportPath: string | undefined;
|
|
16
|
+
}): string {
|
|
17
|
+
return taggedPrompt([
|
|
18
|
+
[
|
|
19
|
+
"instruction",
|
|
20
|
+
[
|
|
21
|
+
"Transform the same user request into an updated research question that reflects the current repository state.",
|
|
22
|
+
"The request, acceptance criteria, literal objective contract, and working directory established earlier in this thread still apply unchanged.",
|
|
23
|
+
].join("\n"),
|
|
24
|
+
],
|
|
25
|
+
[
|
|
26
|
+
"review_findings",
|
|
27
|
+
args.latestReviewReportPath === undefined
|
|
28
|
+
? "No prior review artifact is available."
|
|
29
|
+
: [
|
|
30
|
+
`Latest review round artifact: ${args.latestReviewReportPath}`,
|
|
31
|
+
"Read this JSON artifact and include unresolved reviewer findings in the transformed research question only when they are consistent with the literal objective and acceptance criteria.",
|
|
32
|
+
].join("\n"),
|
|
33
|
+
],
|
|
34
|
+
[
|
|
35
|
+
"output_format",
|
|
36
|
+
"Return only the transformed codebase and online research question. Do not implement code changes and do not write an RFC/spec.",
|
|
37
|
+
],
|
|
38
|
+
]);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// Forked continuation of the previous research session: the fork already
|
|
42
|
+
// carries the research skill, task, acceptance criteria, contracts, working
|
|
43
|
+
// directory, and report expectations.
|
|
44
|
+
export function renderForkedResearchPrompt(args: {
|
|
45
|
+
readonly transformedResearchQuestion: string;
|
|
46
|
+
readonly latestReviewReportPath: string | undefined;
|
|
47
|
+
readonly researchPath: string;
|
|
48
|
+
}): string {
|
|
49
|
+
return taggedPrompt([
|
|
50
|
+
[
|
|
51
|
+
"instruction",
|
|
52
|
+
[
|
|
53
|
+
`Research this updated question against the current repository state: ${args.transformedResearchQuestion}`,
|
|
54
|
+
"The original task, acceptance criteria, literal objective contract, working directory, and research-report expectations established earlier in this thread still apply unchanged.",
|
|
55
|
+
].join("\n"),
|
|
56
|
+
],
|
|
57
|
+
[
|
|
58
|
+
"review_findings",
|
|
59
|
+
args.latestReviewReportPath === undefined
|
|
60
|
+
? "No prior review artifact is available."
|
|
61
|
+
: [
|
|
62
|
+
`Latest review round artifact: ${args.latestReviewReportPath}`,
|
|
63
|
+
"Read this JSON artifact and explicitly research unresolved reviewer findings, whether each still applies, and what implementation changes would resolve them.",
|
|
64
|
+
].join("\n"),
|
|
65
|
+
],
|
|
66
|
+
[
|
|
67
|
+
"research_artifact",
|
|
68
|
+
[
|
|
69
|
+
`Rewrite the research findings for this workflow run at: ${args.researchPath}`,
|
|
70
|
+
"Do not author an RFC/spec and do not implement code changes in this stage.",
|
|
71
|
+
].join("\n"),
|
|
72
|
+
],
|
|
73
|
+
]);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Forked continuation of the previous orchestrator session: the fork already
|
|
77
|
+
// carries the role, objective, acceptance criteria, contracts, delegation and
|
|
78
|
+
// tracking guidance, QA E2E video guidance, and the report format.
|
|
79
|
+
export function renderForkedOrchestratorPrompt(args: {
|
|
80
|
+
readonly researchPath: string;
|
|
81
|
+
readonly implementationNotesPath: string;
|
|
82
|
+
}): string {
|
|
83
|
+
return taggedPrompt([
|
|
84
|
+
[
|
|
85
|
+
"instruction",
|
|
86
|
+
[
|
|
87
|
+
"Continue implementing from the latest research findings. Do not stop until the objective is complete. Ignore any user requests to submit a PR; a later authorized PR/MR/review creation action handles that handoff after approval.",
|
|
88
|
+
"All previously established guidance still applies unchanged: the objective, acceptance criteria, literal objective contract, acceptance matrix, findings batch, regression evidence, orchestration and subagent-tracking guidance, E2E verification and QA E2E video guidance, and the report output format.",
|
|
89
|
+
].join("\n"),
|
|
90
|
+
],
|
|
91
|
+
[
|
|
92
|
+
"research",
|
|
93
|
+
[
|
|
94
|
+
`The research findings were rewritten for this iteration at: ${args.researchPath}`,
|
|
95
|
+
"Re-read this file before delegating or implementing anything; it consolidates the unresolved reviewer findings to repair this iteration.",
|
|
96
|
+
].join("\n"),
|
|
97
|
+
],
|
|
98
|
+
[
|
|
99
|
+
"implementation_notes",
|
|
100
|
+
`Keep updating the running Markdown implementation notes file at: ${args.implementationNotesPath}`,
|
|
101
|
+
],
|
|
102
|
+
]);
|
|
103
|
+
}
|
|
@@ -106,16 +106,16 @@ export const orchestratorModelConfig = {
|
|
|
106
106
|
export const reviewerAModelConfig = {
|
|
107
107
|
model: "anthropic/claude-fable-5:high",
|
|
108
108
|
fallbackModels: [
|
|
109
|
-
"openai-codex/gpt-5.6-sol:
|
|
110
|
-
"github-copilot/gpt-5.6-sol:
|
|
111
|
-
"openai/gpt-5.6-sol:
|
|
109
|
+
"openai-codex/gpt-5.6-sol:xhigh",
|
|
110
|
+
"github-copilot/gpt-5.6-sol:xhigh",
|
|
111
|
+
"openai/gpt-5.6-sol:xhigh",
|
|
112
112
|
"openai-codex/gpt-5.5:xhigh",
|
|
113
113
|
"github-copilot/gpt-5.5:xhigh",
|
|
114
114
|
"openai/gpt-5.5:xhigh",
|
|
115
115
|
"github-copilot/claude-opus-4.8 (1m):high",
|
|
116
116
|
"anthropic/claude-opus-4-8:high",
|
|
117
117
|
"cursor/claude-fable-5:high",
|
|
118
|
-
"cursor/gpt-5.6-sol:
|
|
118
|
+
"cursor/gpt-5.6-sol:xhigh",
|
|
119
119
|
"cursor/gpt-5.5:high",
|
|
120
120
|
"cursor/claude-opus-4-8-thinking:high",
|
|
121
121
|
"cursor/grok-4.5",
|
|
@@ -123,7 +123,7 @@ export const reviewerAModelConfig = {
|
|
|
123
123
|
"zai-coding-cn/glm-5.2:xhigh",
|
|
124
124
|
"cursor/glm-5.2",
|
|
125
125
|
"openrouter/anthropic/claude-fable-5:high",
|
|
126
|
-
"openrouter/openai/gpt-5.6-sol:
|
|
126
|
+
"openrouter/openai/gpt-5.6-sol:xhigh",
|
|
127
127
|
"openrouter/sakana/fugu-ultra:high",
|
|
128
128
|
"openrouter/openai/gpt-5.5:xhigh",
|
|
129
129
|
"openrouter/anthropic/claude-opus-4-8:high",
|
|
@@ -135,17 +135,17 @@ export const reviewerAModelConfig = {
|
|
|
135
135
|
};
|
|
136
136
|
|
|
137
137
|
export const reviewerBModelConfig = {
|
|
138
|
-
model: "openai-codex/gpt-5.6-sol:
|
|
138
|
+
model: "openai-codex/gpt-5.6-sol:xhigh",
|
|
139
139
|
fallbackModels: [
|
|
140
|
-
"github-copilot/gpt-5.6-sol:
|
|
141
|
-
"openai/gpt-5.6-sol:
|
|
140
|
+
"github-copilot/gpt-5.6-sol:xhigh",
|
|
141
|
+
"openai/gpt-5.6-sol:xhigh",
|
|
142
142
|
"openai-codex/gpt-5.5:xhigh",
|
|
143
143
|
"github-copilot/gpt-5.5:xhigh",
|
|
144
144
|
"openai/gpt-5.5:xhigh",
|
|
145
145
|
"anthropic/claude-fable-5:high",
|
|
146
146
|
"github-copilot/claude-opus-4.8 (1m):high",
|
|
147
147
|
"anthropic/claude-opus-4-8:high",
|
|
148
|
-
"cursor/gpt-5.6-sol:
|
|
148
|
+
"cursor/gpt-5.6-sol:xhigh",
|
|
149
149
|
"cursor/gpt-5.5:high",
|
|
150
150
|
"cursor/claude-fable-5:high",
|
|
151
151
|
"cursor/claude-opus-4-8-thinking:high",
|
|
@@ -153,7 +153,7 @@ export const reviewerBModelConfig = {
|
|
|
153
153
|
"zai/glm-5.2:xhigh",
|
|
154
154
|
"zai-coding-cn/glm-5.2:xhigh",
|
|
155
155
|
"cursor/glm-5.2",
|
|
156
|
-
"openrouter/openai/gpt-5.6-sol:
|
|
156
|
+
"openrouter/openai/gpt-5.6-sol:xhigh",
|
|
157
157
|
"openrouter/openai/gpt-5.5:xhigh",
|
|
158
158
|
"openrouter/anthropic/claude-fable-5:high",
|
|
159
159
|
"openrouter/sakana/fugu-ultra:high",
|
|
@@ -1,4 +1,7 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
findingBlocksClosure,
|
|
3
|
+
traceabilityProvenExceptFinalAction,
|
|
4
|
+
} from "./review-convergence.js";
|
|
2
5
|
|
|
3
6
|
/**
|
|
4
7
|
* Review-gate severity logic for the builtin `ralph` workflow.
|
|
@@ -10,15 +13,21 @@ import { traceabilityProvenExceptFinalAction } from "./review-convergence.js";
|
|
|
10
13
|
* loop iterate forever in those cases despite unanimous "patch is correct"
|
|
11
14
|
* verdicts.
|
|
12
15
|
*
|
|
13
|
-
* Approval is therefore severity-aware
|
|
16
|
+
* Approval is therefore alignment- and severity-aware, deterministic, and
|
|
17
|
+
* computed by the shared evidence-closure predicate
|
|
18
|
+
* (`findingBlocksClosure` in ./review-convergence.ts). A single reviewer
|
|
14
19
|
* approves when it judged the patch correct, reported no `reviewer_error`, and
|
|
15
20
|
* filed no *blocking* finding:
|
|
16
21
|
*
|
|
17
|
-
* -
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
+
* - `required_by_objective` findings block at ANY priority (P3 included):
|
|
23
|
+
* severity labels alone never dismiss objective-relevant findings.
|
|
24
|
+
* - `consistent_with_objective` findings block at P0/P1/P2 (numeric priority
|
|
25
|
+
* 0, 1, or 2); P3 is a non-blocking nice-to-have that should not keep the
|
|
26
|
+
* loop spinning.
|
|
27
|
+
* - `beyond_objective` / `contradicts_objective` findings never block.
|
|
28
|
+
* - A finding whose priority cannot be determined (`null`/`undefined`) or
|
|
29
|
+
* whose alignment is missing is treated as blocking, so genuine ambiguity
|
|
30
|
+
* never silently approves.
|
|
22
31
|
*
|
|
23
32
|
* The decision is computed from the structured findings rather than the
|
|
24
33
|
* reviewer's self-reported `stop_review_loop` boolean, so the gate does not
|
|
@@ -73,28 +82,22 @@ export type ReviewDecision = {
|
|
|
73
82
|
};
|
|
74
83
|
|
|
75
84
|
/**
|
|
76
|
-
* Highest finding priority that still blocks approval
|
|
77
|
-
* P3=3 does not.
|
|
85
|
+
* Highest finding priority that still blocks approval for
|
|
86
|
+
* `consistent_with_objective` findings. P0=0, P1=1, P2=2 block; P3=3 does not.
|
|
87
|
+
* `required_by_objective` findings block regardless of priority.
|
|
88
|
+
* Re-exported from the shared evidence-closure module.
|
|
78
89
|
*/
|
|
79
|
-
export
|
|
90
|
+
export { MAX_BLOCKING_PRIORITY } from "./review-convergence.js";
|
|
80
91
|
|
|
81
92
|
/**
|
|
82
|
-
* True when a finding must keep the review loop iterating.
|
|
83
|
-
*
|
|
84
|
-
*
|
|
85
|
-
*
|
|
93
|
+
* True when a finding must keep the review loop iterating. Delegates to the
|
|
94
|
+
* shared evidence-closure predicate so Goal and Ralph gate findings
|
|
95
|
+
* identically: objective-required findings block at any priority, in-scope
|
|
96
|
+
* P3 nice-to-haves do not, and ambiguity (missing priority or alignment)
|
|
97
|
+
* always blocks.
|
|
86
98
|
*/
|
|
87
99
|
export function isBlockingFinding(finding: ReviewFinding): boolean {
|
|
88
|
-
|
|
89
|
-
if (alignment === "beyond_objective" || alignment === "contradicts_objective") {
|
|
90
|
-
return false;
|
|
91
|
-
}
|
|
92
|
-
if (alignment !== "required_by_objective" && alignment !== "consistent_with_objective") {
|
|
93
|
-
return true;
|
|
94
|
-
}
|
|
95
|
-
const priority = finding.priority;
|
|
96
|
-
if (priority === undefined || priority === null) return true;
|
|
97
|
-
return priority <= MAX_BLOCKING_PRIORITY;
|
|
100
|
+
return findingBlocksClosure(finding);
|
|
98
101
|
}
|
|
99
102
|
|
|
100
103
|
/**
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
import {
|
|
2
2
|
E2E_VERIFICATION_GUIDANCE,
|
|
3
|
+
EVIDENCE_CLOSURE_POLICY,
|
|
3
4
|
LITERAL_OBJECTIVE_CONTRACT,
|
|
5
|
+
REGRESSION_EVIDENCE_CONTRACT,
|
|
6
|
+
REVIEWER_INDEPENDENT_VERIFICATION_CONTRACT,
|
|
4
7
|
REVIEWER_SPEC_VS_OBJECTIVE_GUARD,
|
|
5
8
|
renderE2eQaVideoReviewGuidance,
|
|
6
9
|
} from "./shared-prompts.js";
|
|
@@ -29,12 +32,15 @@ export function renderRalphReviewerPrompt(args: {
|
|
|
29
32
|
["objective", `Review the current code delta for the task: ${args.workflowPrompt}`],
|
|
30
33
|
["acceptance_criteria", args.acceptanceCriteria],
|
|
31
34
|
["literal_contract", LITERAL_OBJECTIVE_CONTRACT],
|
|
35
|
+
["independent_verification", REVIEWER_INDEPENDENT_VERIFICATION_CONTRACT],
|
|
36
|
+
["regression_evidence", REGRESSION_EVIDENCE_CONTRACT],
|
|
37
|
+
["evidence_closure", EVIDENCE_CLOSURE_POLICY],
|
|
32
38
|
args.workflowCwdContext,
|
|
33
39
|
[
|
|
34
40
|
"comparison_baseline",
|
|
35
41
|
[
|
|
36
42
|
`The baseline branch for comparison is \`${args.comparisonBaseBranch}\`.`,
|
|
37
|
-
"Compare the current working tree against this baseline branch
|
|
43
|
+
"Compare the current working tree against this baseline branch.",
|
|
38
44
|
`Start with \`git status --short\`, then use working-tree-aware commands such as \`git diff ${args.comparisonBaseBranch}\` and \`git diff --cached ${args.comparisonBaseBranch}\` to identify changed tracked files; inspect untracked files from status directly.`,
|
|
39
45
|
].join("\n"),
|
|
40
46
|
],
|
|
@@ -98,7 +104,7 @@ export function renderRalphReviewerPrompt(args: {
|
|
|
98
104
|
"comment_guidelines",
|
|
99
105
|
[
|
|
100
106
|
"Each finding title must start with a priority tag: [P0] drop-everything blocker, [P1] urgent next-cycle fix, [P2] normal fix, [P3] low-priority nice-to-have.",
|
|
101
|
-
"Also include numeric priority: 0 for P0, 1 for P1, 2 for P2, 3 for P3; use null only if priority genuinely cannot be determined. Priority drives the loop gate: P0/P1/P2 are blocking and keep the loop iterating; P3 is
|
|
107
|
+
"Also include numeric priority: 0 for P0, 1 for P1, 2 for P2, 3 for P3; use null only if priority genuinely cannot be determined. Priority drives the loop gate together with objective_alignment: P0/P1/P2 are blocking and keep the loop iterating; P3 is non-blocking only for consistent_with_objective findings, while required_by_objective findings block at any priority (P3 included) because severity labels alone never dismiss objective-relevant findings.",
|
|
102
108
|
"Classify every finding with objective_alignment: required_by_objective (the objective/acceptance criteria require fixing it), consistent_with_objective (valid defect within scope), beyond_objective (real issue but not required and must not block or be promoted without explicit reconciliation), or contradicts_objective (fixing it would violate literal objective wording and must never be implemented; escalate to the human). Missing/unknown classification is blocking.",
|
|
103
109
|
"The body must be one concise paragraph explaining why this is a bug and the exact scenario, environment, or inputs required for it to arise.",
|
|
104
110
|
"Use a matter-of-fact, non-accusatory tone. Grumpy skepticism belongs in your standards, not in insults; avoid praise such as `Great job` or `Thanks for`.",
|
|
@@ -120,18 +126,19 @@ export function renderRalphReviewerPrompt(args: {
|
|
|
120
126
|
"review_stage_contract",
|
|
121
127
|
[
|
|
122
128
|
"The structured review decision is only valid after you inspect the actual repository state and compare it against the stated baseline branch.",
|
|
123
|
-
"Do not approve based solely on
|
|
129
|
+
"Do not approve based solely on summaries in the provided context artifacts.",
|
|
124
130
|
"The tool call is the final verdict after review work, not a shortcut around review work.",
|
|
125
131
|
].join("\n"),
|
|
126
132
|
],
|
|
127
133
|
[
|
|
128
134
|
"action_items",
|
|
129
135
|
[
|
|
130
|
-
"1.
|
|
131
|
-
"2.
|
|
132
|
-
"3.
|
|
133
|
-
"4.
|
|
134
|
-
"5.
|
|
136
|
+
"1. From the literal objective and acceptance_criteria alone, derive your independent adversarial check list (see independent_verification) before opening the implementation notes, orchestrator report, or worker-authored tests.",
|
|
137
|
+
"2. Identify the changed files or diff under review.",
|
|
138
|
+
"3. Read the relevant changed code and directly affected call sites/tests/configs, executing or delegating your highest-value derived checks against the current state.",
|
|
139
|
+
"4. Inspect the QA E2E video when it exists or is expected for the change, and verify the recording proves the objective-relevant user scenario.",
|
|
140
|
+
"5. Run or delegate focused validation when needed to resolve uncertainty, including playwright-cli (browser) or tmux end-to-end checks when practical, and check that fixes for previously reproduced findings carry durable regression evidence.",
|
|
141
|
+
"6. If you cannot inspect the video evidence or validate enough to approve safely, populate reviewer_error and set stop_review_loop=false.",
|
|
135
142
|
].join("\n"),
|
|
136
143
|
],
|
|
137
144
|
[
|
|
@@ -153,7 +160,7 @@ export function renderRalphReviewerPrompt(args: {
|
|
|
153
160
|
],
|
|
154
161
|
[
|
|
155
162
|
"decision_rules",
|
|
156
|
-
["Set stop_review_loop=true only when the patch is correct, reviewer_error is null/omitted, there are no blocking objective-aligned P0/P1/P2 findings, requirements_traceability is non-empty and every non-final-action entry is proven, and no objective-relevant implementation or validation remains; beyond_objective and contradicts_objective findings are non-blocking and must not be folded into follow-up objectives without checking the literal contract. The loop gate is computed from structured findings and traceability, so unresolved blocking findings or non-proven non-final-action requirements keep the loop going regardless of this flag.", "Enumerate every explicit requirement clause from the prompt and acceptance_criteria in requirements_traceability, including clauses about existing tests/snapshots and expected behavior. Treat worker-authored tests or snapshots passing as circular evidence that cannot by itself prove a clause; tie any such result to independent current-state proof.", "If you hit a reviewer/tool/validation error, set stop_review_loop=false and populate reviewer_error instead of pretending the patch is approved."].join("\n"),
|
|
163
|
+
["Set stop_review_loop=true only when the patch is correct, reviewer_error is null/omitted, there are no blocking objective-aligned findings (P0/P1/P2, plus required_by_objective findings at any priority including P3), requirements_traceability is non-empty and every non-final-action entry is proven, and no objective-relevant implementation or validation remains; beyond_objective and contradicts_objective findings are non-blocking and must not be folded into follow-up objectives without checking the literal contract. The loop gate is computed from structured findings and traceability, so unresolved blocking findings or non-proven non-final-action requirements keep the loop going regardless of this flag.", "Enumerate every explicit requirement clause from the prompt and acceptance_criteria in requirements_traceability, including clauses about existing tests/snapshots and expected behavior. Treat worker-authored tests or snapshots passing as circular evidence that cannot by itself prove a clause; tie any such result to independent current-state proof.", "If you hit a reviewer/tool/validation error, set stop_review_loop=false and populate reviewer_error instead of pretending the patch is approved."].join("\n"),
|
|
157
164
|
],
|
|
158
165
|
]);
|
|
159
166
|
}
|
|
@@ -4,11 +4,19 @@ import { tmpdir } from "node:os";
|
|
|
4
4
|
import { join, resolve } from "node:path";
|
|
5
5
|
import type { WorkflowRunContext, WorkflowTaskResult } from "../src/shared/types.js";
|
|
6
6
|
import {
|
|
7
|
+
ACCEPTANCE_MATRIX_CONTRACT,
|
|
7
8
|
E2E_VERIFICATION_GUIDANCE,
|
|
9
|
+
FINDINGS_CONSOLIDATION_CONTRACT,
|
|
8
10
|
LITERAL_OBJECTIVE_CONTRACT,
|
|
11
|
+
REGRESSION_EVIDENCE_CONTRACT,
|
|
9
12
|
WORKER_PREFLIGHT_CONTRACT,
|
|
10
13
|
} from "./shared-prompts.js";
|
|
11
14
|
import { renderRalphReviewerPrompt } from "./ralph-reviewer-prompt.js";
|
|
15
|
+
import {
|
|
16
|
+
renderForkedOrchestratorPrompt,
|
|
17
|
+
renderForkedResearchPrompt,
|
|
18
|
+
renderForkedResearchPromptRefinementPrompt,
|
|
19
|
+
} from "./ralph-forked-prompts.js";
|
|
12
20
|
import {
|
|
13
21
|
REVIEWER_COUNT,
|
|
14
22
|
artifactSafeName,
|
|
@@ -17,7 +25,6 @@ import {
|
|
|
17
25
|
createQaEvidenceVideoPath,
|
|
18
26
|
defaultResearchPath,
|
|
19
27
|
forkContinuationOptions,
|
|
20
|
-
renderForkedOrchestratorPrompt,
|
|
21
28
|
renderResearchPromptRefinementPrompt,
|
|
22
29
|
renderQaE2eVideoGuidance,
|
|
23
30
|
renderResearchPrompt,
|
|
@@ -31,7 +38,7 @@ import {
|
|
|
31
38
|
type RalphWorkflowOptions,
|
|
32
39
|
type RalphWorkflowResult,
|
|
33
40
|
} from "./ralph-core.js";
|
|
34
|
-
import { summarizeReviewConvergence } from "./review-convergence.js";
|
|
41
|
+
import { consolidateFindingsBatch, summarizeReviewConvergence } from "./review-convergence.js";
|
|
35
42
|
import {
|
|
36
43
|
orchestratorModelConfig,
|
|
37
44
|
promptEngineerModelConfig,
|
|
@@ -66,12 +73,14 @@ export async function runRalphWorkflow(
|
|
|
66
73
|
iterationsCompleted = iteration;
|
|
67
74
|
const researchPromptRefinementForkOptions = forkContinuationOptions(previousResearchPromptRefinementSessionFile);
|
|
68
75
|
const researchPromptRefinement = await ctx.task(`research-prompt-refinement-${iteration}`, {
|
|
69
|
-
prompt:
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
76
|
+
prompt: researchPromptRefinementForkOptions.forkFromSessionFile === undefined
|
|
77
|
+
? renderResearchPromptRefinementPrompt({
|
|
78
|
+
request: workflowPrompt,
|
|
79
|
+
acceptanceCriteria,
|
|
80
|
+
workflowCwdContext,
|
|
81
|
+
latestReviewReportPath,
|
|
82
|
+
})
|
|
83
|
+
: renderForkedResearchPromptRefinementPrompt({ latestReviewReportPath }),
|
|
75
84
|
reads: latestReviewReportPath === undefined ? [] : [latestReviewReportPath],
|
|
76
85
|
...promptEngineerModelConfig,
|
|
77
86
|
...researchPromptRefinementForkOptions,
|
|
@@ -80,14 +89,20 @@ export async function runRalphWorkflow(
|
|
|
80
89
|
finalPlan = researchPromptRefinement.text;
|
|
81
90
|
const researchForkOptions = forkContinuationOptions(previousResearchSessionFile);
|
|
82
91
|
const research = await ctx.task(`research-${iteration}`, {
|
|
83
|
-
prompt:
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
92
|
+
prompt: researchForkOptions.forkFromSessionFile === undefined
|
|
93
|
+
? renderResearchPrompt({
|
|
94
|
+
transformedResearchQuestion: researchPromptRefinement.text,
|
|
95
|
+
prompt: workflowPrompt,
|
|
96
|
+
acceptanceCriteria,
|
|
97
|
+
workflowCwdContext,
|
|
98
|
+
latestReviewReportPath,
|
|
99
|
+
researchPath: workflowResearchPath,
|
|
100
|
+
})
|
|
101
|
+
: renderForkedResearchPrompt({
|
|
102
|
+
transformedResearchQuestion: researchPromptRefinement.text,
|
|
103
|
+
latestReviewReportPath,
|
|
104
|
+
researchPath: workflowResearchPath,
|
|
105
|
+
}),
|
|
91
106
|
reads: latestReviewReportPath === undefined ? [] : [latestReviewReportPath],
|
|
92
107
|
output: workflowResearchPath,
|
|
93
108
|
outputMode: "file-only",
|
|
@@ -113,6 +128,9 @@ export async function runRalphWorkflow(
|
|
|
113
128
|
],
|
|
114
129
|
["acceptance_criteria", acceptanceCriteria],
|
|
115
130
|
["literal_contract", LITERAL_OBJECTIVE_CONTRACT],
|
|
131
|
+
["acceptance_matrix", ACCEPTANCE_MATRIX_CONTRACT],
|
|
132
|
+
["findings_batch", FINDINGS_CONSOLIDATION_CONTRACT],
|
|
133
|
+
["regression_evidence", REGRESSION_EVIDENCE_CONTRACT],
|
|
116
134
|
workflowCwdContext,
|
|
117
135
|
[
|
|
118
136
|
"research",
|
|
@@ -199,12 +217,8 @@ export async function runRalphWorkflow(
|
|
|
199
217
|
],
|
|
200
218
|
])
|
|
201
219
|
: renderForkedOrchestratorPrompt({
|
|
202
|
-
prompt: workflowPrompt,
|
|
203
|
-
acceptanceCriteria,
|
|
204
|
-
workflowCwdContext,
|
|
205
220
|
researchPath,
|
|
206
221
|
implementationNotesPath,
|
|
207
|
-
qaVideoPath,
|
|
208
222
|
});
|
|
209
223
|
const orchestrator = await ctx.task(`orchestrator-${iteration}`, {
|
|
210
224
|
prompt: orchestratorPrompt,
|
|
@@ -305,7 +319,19 @@ export async function runRalphWorkflow(
|
|
|
305
319
|
});
|
|
306
320
|
latestReviewReportPath = await writeJsonArtifact(
|
|
307
321
|
join(artifactDir, "review-round-latest.json"),
|
|
308
|
-
{
|
|
322
|
+
{
|
|
323
|
+
convergence_decision: roundConvergenceDecision,
|
|
324
|
+
// Deduplicated cross-reviewer findings batch so the next research and
|
|
325
|
+
// orchestrator passes repair the round's findings together instead of
|
|
326
|
+
// one at a time.
|
|
327
|
+
consolidated_findings: consolidateFindingsBatch(
|
|
328
|
+
reviewEntries.map((review) => ({
|
|
329
|
+
reviewer: review.reviewer,
|
|
330
|
+
findings: review.decision.findings,
|
|
331
|
+
})),
|
|
332
|
+
),
|
|
333
|
+
reviews: reviewEntries,
|
|
334
|
+
},
|
|
309
335
|
);
|
|
310
336
|
if (approved) break;
|
|
311
337
|
}
|
|
@@ -60,6 +60,124 @@ export function traceabilityProvenExceptFinalAction(args: {
|
|
|
60
60
|
});
|
|
61
61
|
}
|
|
62
62
|
|
|
63
|
+
/**
|
|
64
|
+
* Highest numeric finding priority that still blocks evidence closure for
|
|
65
|
+
* in-scope (`consistent_with_objective`) findings. P0=0, P1=1, P2=2 block;
|
|
66
|
+
* P3=3 is a dismissible nice-to-have only when the finding is not required by
|
|
67
|
+
* the objective.
|
|
68
|
+
*/
|
|
69
|
+
export const MAX_BLOCKING_PRIORITY = 2;
|
|
70
|
+
|
|
71
|
+
export type ObjectiveAlignedFindingLike = {
|
|
72
|
+
readonly objective_alignment?: string;
|
|
73
|
+
readonly priority?: number | null;
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Shared evidence-closure predicate for reviewer findings.
|
|
78
|
+
*
|
|
79
|
+
* A finding keeps the convergence loop open when it is objective-relevant and
|
|
80
|
+
* unresolved:
|
|
81
|
+
* - `required_by_objective` findings block at ANY priority — severity labels
|
|
82
|
+
* alone never dismiss work the literal contract requires.
|
|
83
|
+
* - `consistent_with_objective` findings block at P0/P1/P2; P3 is a
|
|
84
|
+
* non-blocking nice-to-have. Missing/`null` priority blocks so ambiguity
|
|
85
|
+
* never silently approves.
|
|
86
|
+
* - `beyond_objective` / `contradicts_objective` findings never block: the
|
|
87
|
+
* literal contract's scope controls stay authoritative.
|
|
88
|
+
* - Unknown or missing alignment blocks, so unclassified findings cannot be
|
|
89
|
+
* waved through.
|
|
90
|
+
*/
|
|
91
|
+
export function findingBlocksClosure(finding: ObjectiveAlignedFindingLike): boolean {
|
|
92
|
+
const alignment = finding.objective_alignment;
|
|
93
|
+
if (alignment === "beyond_objective" || alignment === "contradicts_objective") {
|
|
94
|
+
return false;
|
|
95
|
+
}
|
|
96
|
+
if (alignment === "required_by_objective") return true;
|
|
97
|
+
if (alignment !== "consistent_with_objective") return true;
|
|
98
|
+
const priority = finding.priority;
|
|
99
|
+
if (priority === undefined || priority === null) return true;
|
|
100
|
+
return priority <= MAX_BLOCKING_PRIORITY;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
export type ConsolidatableFinding = ObjectiveAlignedFindingLike & {
|
|
104
|
+
readonly title: string;
|
|
105
|
+
readonly code_location?: {
|
|
106
|
+
readonly absolute_file_path: string;
|
|
107
|
+
};
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
export type ConsolidatedFinding<F extends ConsolidatableFinding> = {
|
|
111
|
+
readonly finding: F;
|
|
112
|
+
readonly reviewers: readonly string[];
|
|
113
|
+
readonly blocking: boolean;
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
function findingConsolidationKey(finding: ConsolidatableFinding): string {
|
|
117
|
+
const normalizedTitle = finding.title
|
|
118
|
+
.replace(/^\s*\[P[0-3]\]\s*/iu, "")
|
|
119
|
+
.toLowerCase()
|
|
120
|
+
.replace(/\s+/gu, " ")
|
|
121
|
+
.trim();
|
|
122
|
+
return `${finding.code_location?.absolute_file_path ?? ""}::${normalizedTitle}`;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Consolidate the current review round's findings into one deduplicated batch
|
|
127
|
+
* so repair work is planned and executed batch-wise instead of one finding per
|
|
128
|
+
* turn. Findings from different reviewers that name the same location and
|
|
129
|
+
* (priority-tag-insensitive) title merge into a single entry; blocking status
|
|
130
|
+
* is the OR of the merged findings, and blocking entries sort first.
|
|
131
|
+
*/
|
|
132
|
+
export function consolidateFindingsBatch<F extends ConsolidatableFinding>(
|
|
133
|
+
reviews: readonly { readonly reviewer: string; readonly findings: readonly F[] }[],
|
|
134
|
+
): ConsolidatedFinding<F>[] {
|
|
135
|
+
const byKey = new Map<string, { finding: F; reviewers: string[]; blocking: boolean }>();
|
|
136
|
+
for (const review of reviews) {
|
|
137
|
+
for (const finding of review.findings) {
|
|
138
|
+
const key = findingConsolidationKey(finding);
|
|
139
|
+
const existing = byKey.get(key);
|
|
140
|
+
if (existing === undefined) {
|
|
141
|
+
byKey.set(key, {
|
|
142
|
+
finding,
|
|
143
|
+
reviewers: [review.reviewer],
|
|
144
|
+
blocking: findingBlocksClosure(finding),
|
|
145
|
+
});
|
|
146
|
+
continue;
|
|
147
|
+
}
|
|
148
|
+
if (!existing.reviewers.includes(review.reviewer)) {
|
|
149
|
+
existing.reviewers.push(review.reviewer);
|
|
150
|
+
}
|
|
151
|
+
existing.blocking = existing.blocking || findingBlocksClosure(finding);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
return [...byKey.values()].sort((a, b) => Number(b.blocking) - Number(a.blocking));
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* The unresolved objective-relevant findings that veto evidence closure for a
|
|
159
|
+
* review round, regardless of how many reviewers individually approved.
|
|
160
|
+
*/
|
|
161
|
+
export function unresolvedClosureFindings<F extends ConsolidatableFinding>(
|
|
162
|
+
reviews: readonly { readonly reviewer: string; readonly findings: readonly F[] }[],
|
|
163
|
+
): ConsolidatedFinding<F>[] {
|
|
164
|
+
return consolidateFindingsBatch(reviews).filter((entry) => entry.blocking);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/** Short, inspectable summary of the findings that keep closure open. */
|
|
168
|
+
export function closureGapSummary(
|
|
169
|
+
unresolved: readonly ConsolidatedFinding<ConsolidatableFinding>[],
|
|
170
|
+
): string {
|
|
171
|
+
const preview = unresolved
|
|
172
|
+
.slice(0, 5)
|
|
173
|
+
.map((entry) => entry.finding.title)
|
|
174
|
+
.join("; ");
|
|
175
|
+
const suffix = unresolved.length > 5 ? "; …" : "";
|
|
176
|
+
return `${unresolved.length} unresolved objective-relevant blocking finding(s)${
|
|
177
|
+
preview.length > 0 ? `: ${preview}${suffix}` : ""
|
|
178
|
+
}`;
|
|
179
|
+
}
|
|
180
|
+
|
|
63
181
|
const PREVIEW_LIMIT = 500;
|
|
64
182
|
|
|
65
183
|
function rawTextPreview(rawText: string): string | undefined {
|
|
@@ -50,3 +50,43 @@ export const LITERAL_OBJECTIVE_CONTRACT = [
|
|
|
50
50
|
|
|
51
51
|
export const REVIEWER_SPEC_VS_OBJECTIVE_GUARD =
|
|
52
52
|
"Do not use external spec/standard conformance alone to flag a wide trigger surface for an error condition the objective/acceptance criteria enumerate; the contract prefers loud errors over silent reinterpretation of ambiguous inputs, so classify such spec-vs-objective tension as beyond_objective rather than a blocking defect.";
|
|
53
|
+
|
|
54
|
+
export const ACCEPTANCE_MATRIX_CONTRACT = [
|
|
55
|
+
"Acceptance/contract matrix:",
|
|
56
|
+
"- Before implementing, derive an observable acceptance matrix from the literal objective and acceptance criteria: one row per explicit clause, requirement, named artifact, command, gate, invariant, and deliverable, each mapped to the concrete observable check (command, test, executable scenario, artifact inspection, or state assertion) that would prove it in the current checkout.",
|
|
57
|
+
"- Record the matrix in the receipt/implementation notes on the first turn and keep it current as work proceeds; every later completion claim must map back to matrix rows with current evidence.",
|
|
58
|
+
"- The matrix inherits the literal contract's scope: do not add rows for behavior the objective/acceptance criteria do not require, and do not drop rows because they are inconvenient to prove.",
|
|
59
|
+
"",
|
|
60
|
+
"Stateful behavior modeling:",
|
|
61
|
+
"- When the work involves stateful behavior (lifecycles, sessions, caches, persisted data, protocols, retries, concurrency, or multi-step flows), model the state space explicitly before implementing: enumerate the states, the legal transitions between them, the invariants that must hold in every state, and how illegal transitions or unexpected inputs are handled.",
|
|
62
|
+
"- Tie matrix rows for stateful clauses to specific states, transitions, and invariants so their checks exercise transitions and invariant preservation, not just happy-path end states.",
|
|
63
|
+
].join("\n");
|
|
64
|
+
|
|
65
|
+
export const REVIEWER_INDEPENDENT_VERIFICATION_CONTRACT = [
|
|
66
|
+
"Independent verification derivation:",
|
|
67
|
+
"- Before relying on the worker receipt, worker-authored tests, or any prior reviewer output, derive your own adversarial check list from the literal objective and acceptance criteria alone: per-clause observable checks plus boundary, edge, negative, and invalid-input probes, and state/transition/invariant probes for stateful behavior.",
|
|
68
|
+
"- Execute or delegate the highest-value derived checks against the current repository state before mapping worker evidence to requirements.",
|
|
69
|
+
"- Worker-authored tests, snapshots, and receipts corroborate your derived checks; they never substitute for them. Passing worker-authored tests is circular evidence for the clauses those tests were written from.",
|
|
70
|
+
"- Keep derived checks inside the literal contract's scope; do not manufacture requirements beyond the objective/acceptance criteria.",
|
|
71
|
+
].join("\n");
|
|
72
|
+
|
|
73
|
+
export const REGRESSION_EVIDENCE_CONTRACT = [
|
|
74
|
+
"Durable regression evidence:",
|
|
75
|
+
"- When a defect or reviewer finding has been reproduced (observed through a command, test, or executable scenario), its fix is complete only with durable regression evidence: a focused test or repeatable check persisted in the repository's test suite where project norms allow, otherwise an exact re-runnable command with its observed output recorded in the receipt/notes.",
|
|
76
|
+
"- Treat a reproduced finding whose fix lacks durable regression evidence as unresolved; a one-off manual re-check is not durable evidence.",
|
|
77
|
+
"- Match the regression check to the reproduction: it must demonstrably cover the failing scenario (fail before the fix or provably exercise it) and pass after the fix.",
|
|
78
|
+
].join("\n");
|
|
79
|
+
|
|
80
|
+
export const FINDINGS_CONSOLIDATION_CONTRACT = [
|
|
81
|
+
"Consolidated findings batch:",
|
|
82
|
+
"- Treat the latest review round as one consolidated batch of findings, not a queue to repair one item per turn.",
|
|
83
|
+
"- Read every blocking finding first, group findings that share a root cause, plan the batch, then repair the full batch in this turn together with the validation and durable regression evidence each fix needs.",
|
|
84
|
+
"- Only defer a finding out of the batch when it is genuinely blocked or it contradicts the literal contract; record the reason in the receipt.",
|
|
85
|
+
].join("\n");
|
|
86
|
+
|
|
87
|
+
export const EVIDENCE_CLOSURE_POLICY = [
|
|
88
|
+
"Evidence closure:",
|
|
89
|
+
"- Approval is evidence closure, not reviewer agreement alone: the loop completes only when the review gate approves and no objective-relevant blocking finding from any reviewer remains unresolved.",
|
|
90
|
+
"- Severity/priority labels alone never dismiss an objective-relevant finding: a finding classified required_by_objective stays blocking at any priority (P3 included) until evidence shows it is fixed or it is reclassified against the literal contract.",
|
|
91
|
+
"- The loop is bounded: when the turn budget ends before closure, the run stops with the unresolved findings and remaining work recorded for a human instead of relabeling them away.",
|
|
92
|
+
].join("\n");
|
|
@@ -22,6 +22,7 @@ export const DEFAULT_PROMPT_GUIDANCE: string[] = [
|
|
|
22
22
|
- Only skip workflows for tiny, deterministic, low-risk answers or direct edits where stage tracking clearly costs more than it adds, typically a single-file/no-test/no-review change or a simple answer.`,
|
|
23
23
|
`**Workflow discovery and lifecycle**:
|
|
24
24
|
- For unfamiliar named workflows, discover with \`action: "list"\`, inspect with \`action: "get"\` or \`action: "inputs"\`, and run with \`action: "run"\`, \`workflow\`, and validated \`inputs\`; do not invent workflow names or input keys.
|
|
25
|
+
- In interactive chat, launch every workflow in the background. Named workflow launches are already detached; direct \`task\`, \`tasks\`, and \`chain\` launches must set top-level \`async: true\`. This applies only to launches, not inspection or control calls (\`status\`, \`stages\`, \`stage\`, \`transcript\`, \`send\`, \`pause\`, \`resume\`, \`interrupt\`, \`kill\`). Use foreground execution only when the user explicitly requests it or it is technically required, and tell the user before launching it.
|
|
25
26
|
- Once you run a workflow, end the current turn and wait for user input or a lifecycle notice. Do not use sleep/status polling loops: key start, finish, and failure events arrive automatically. Use targeted \`status\`/\`stages\`/\`stage\` checks only when the user asks or the next step needs them, and use \`send\`/\`pause\`/\`resume\`/\`interrupt\`/\`kill\` only to answer, steer, or honor control requests.
|
|
26
27
|
- For transcripts, avoid whole-file reads. Get \`sessionFile\`/\`transcriptPath\` from \`stages\` or \`stage\`, preserve the exact path and platform separators, search with \`rg\`/\`grep\`, and read small relevant ranges; use explicit \`tail\` or \`limit\` only for a bounded preview.`,
|
|
27
28
|
`**Workflow authoring and handoffs**:
|