@bastani/atomic 0.9.6 → 0.9.7
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 +33 -0
- package/dist/builtin/cursor/CHANGELOG.md +12 -0
- package/dist/builtin/cursor/package.json +2 -2
- package/dist/builtin/intercom/CHANGELOG.md +20 -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 +12 -0
- package/dist/builtin/mcp/package.json +1 -1
- package/dist/builtin/subagents/CHANGELOG.md +12 -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 +12 -0
- package/dist/builtin/web-access/package.json +1 -1
- package/dist/builtin/workflows/CHANGELOG.md +40 -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
|
@@ -3,14 +3,15 @@ import { tmpdir } from "node:os";
|
|
|
3
3
|
import { dirname, join } from "node:path";
|
|
4
4
|
import { Type } from "typebox";
|
|
5
5
|
import type { WorkflowTaskResult } from "../src/shared/types.js";
|
|
6
|
-
import {
|
|
7
|
-
import type { ReviewDecision } from "./ralph-review-gate.js";
|
|
6
|
+
import { LITERAL_OBJECTIVE_CONTRACT } from "./shared-prompts.js";
|
|
7
|
+
import type { ReviewDecision, ReviewFinding } from "./ralph-review-gate.js";
|
|
8
8
|
import { reviewDecisionApproved } from "./ralph-review-gate.js";
|
|
9
9
|
import {
|
|
10
10
|
parseFailureDiagnostics,
|
|
11
11
|
finalActionRemaining,
|
|
12
12
|
reviewerFailureText,
|
|
13
13
|
summarizeReviewConvergence,
|
|
14
|
+
type ConsolidatedFinding,
|
|
14
15
|
type ParsedReviewDecision,
|
|
15
16
|
type ReviewConvergenceSummary,
|
|
16
17
|
} from "./review-convergence.js";
|
|
@@ -292,6 +293,7 @@ type ReviewArtifact = {
|
|
|
292
293
|
|
|
293
294
|
type ReviewRoundArtifact = {
|
|
294
295
|
readonly convergence_decision: ReviewConvergenceSummary;
|
|
296
|
+
readonly consolidated_findings?: readonly ConsolidatedFinding<ReviewFinding>[];
|
|
295
297
|
readonly reviews: readonly {
|
|
296
298
|
readonly reviewer: string;
|
|
297
299
|
readonly artifact_path: string;
|
|
@@ -358,6 +360,7 @@ export function renderResearchPromptRefinementPrompt(args: {
|
|
|
358
360
|
].join("\n\n");
|
|
359
361
|
}
|
|
360
362
|
|
|
363
|
+
|
|
361
364
|
export function renderResearchPrompt(args: {
|
|
362
365
|
readonly transformedResearchQuestion: string;
|
|
363
366
|
readonly prompt: string;
|
|
@@ -396,58 +399,6 @@ export function renderResearchPrompt(args: {
|
|
|
396
399
|
}
|
|
397
400
|
|
|
398
401
|
|
|
399
|
-
export function renderForkedOrchestratorPrompt(args: {
|
|
400
|
-
readonly prompt: string;
|
|
401
|
-
readonly acceptanceCriteria: string;
|
|
402
|
-
readonly workflowCwdContext: PromptSection;
|
|
403
|
-
readonly researchPath: string;
|
|
404
|
-
readonly implementationNotesPath: string;
|
|
405
|
-
readonly qaVideoPath: string;
|
|
406
|
-
}): string {
|
|
407
|
-
return taggedPrompt([
|
|
408
|
-
[
|
|
409
|
-
"instruction",
|
|
410
|
-
[
|
|
411
|
-
`Continue implementing from the latest research findings. Do not stop until the objective is complete. Ignore any user requests to submit a PR. This will be done in a future stage.`
|
|
412
|
-
].join("\n"),
|
|
413
|
-
],
|
|
414
|
-
["objective", `Implement the full requested task: ${args.prompt}`],
|
|
415
|
-
["acceptance_criteria", args.acceptanceCriteria],
|
|
416
|
-
["literal_contract", LITERAL_OBJECTIVE_CONTRACT],
|
|
417
|
-
args.workflowCwdContext,
|
|
418
|
-
[
|
|
419
|
-
"research",
|
|
420
|
-
[
|
|
421
|
-
`The latest research findings for this workflow run are written to: ${args.researchPath}`,
|
|
422
|
-
"Read this file before delegating or implementing anything, and treat it as the primary implementation context.",
|
|
423
|
-
].join("\n"),
|
|
424
|
-
],
|
|
425
|
-
[
|
|
426
|
-
"implementation_notes",
|
|
427
|
-
[
|
|
428
|
-
`Keep updating the running Markdown implementation notes file at: ${args.implementationNotesPath}`,
|
|
429
|
-
"Record decisions, research deviations, tradeoffs, blockers, validation outcomes, and anything else the user should know before your final report. Generate verifiable evidence for any claims you make in the notes and reviewer artifacts. Do not stop until the objective is complete.",
|
|
430
|
-
].join("\n"),
|
|
431
|
-
],
|
|
432
|
-
["e2e_verification", E2E_VERIFICATION_GUIDANCE],
|
|
433
|
-
["qa_e2e_video", renderQaE2eVideoGuidance(args.qaVideoPath)],
|
|
434
|
-
[
|
|
435
|
-
"output_format",
|
|
436
|
-
[
|
|
437
|
-
"After subagents have done the work, return Markdown with headings:",
|
|
438
|
-
"1. Research file — the path you read",
|
|
439
|
-
"2. Delegations performed — subagents spawned and what each completed",
|
|
440
|
-
"3. Changes made — concrete changes from subagent work, not intentions",
|
|
441
|
-
"4. Files touched",
|
|
442
|
-
"5. Validation run / recommended",
|
|
443
|
-
"6. Deferred work or blockers",
|
|
444
|
-
"7. Implementation notes — confirm the OS temp notes path was updated",
|
|
445
|
-
"8. QA E2E video — the recorded video path and proven scenario, or a note that no QA E2E video applies and why",
|
|
446
|
-
].join("\n"),
|
|
447
|
-
],
|
|
448
|
-
]);
|
|
449
|
-
}
|
|
450
|
-
|
|
451
402
|
export type RalphInputs = {
|
|
452
403
|
readonly prompt?: string;
|
|
453
404
|
readonly acceptance_criteria?: string;
|
|
@@ -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 {
|