@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.
Files changed (48) hide show
  1. package/CHANGELOG.md +16 -0
  2. package/dist/builtin/cursor/CHANGELOG.md +6 -0
  3. package/dist/builtin/cursor/package.json +2 -2
  4. package/dist/builtin/intercom/CHANGELOG.md +10 -0
  5. package/dist/builtin/intercom/README.md +1 -1
  6. package/dist/builtin/intercom/contact-supervisor-tool.ts +19 -32
  7. package/dist/builtin/intercom/index-heavy.ts +8 -51
  8. package/dist/builtin/intercom/index.ts +13 -1
  9. package/dist/builtin/intercom/intercom-tool.ts +24 -23
  10. package/dist/builtin/intercom/package.json +1 -1
  11. package/dist/builtin/intercom/reply-waiter.ts +115 -0
  12. package/dist/builtin/intercom/skills/intercom/SKILL.md +9 -2
  13. package/dist/builtin/intercom/subagent-relay.ts +11 -1
  14. package/dist/builtin/mcp/CHANGELOG.md +6 -0
  15. package/dist/builtin/mcp/package.json +1 -1
  16. package/dist/builtin/subagents/CHANGELOG.md +6 -0
  17. package/dist/builtin/subagents/package.json +1 -1
  18. package/dist/builtin/subagents/skills/subagent/SKILL.md +3 -1
  19. package/dist/builtin/subagents/src/runs/shared/model-fallback.ts +3 -2
  20. package/dist/builtin/web-access/CHANGELOG.md +6 -0
  21. package/dist/builtin/web-access/package.json +1 -1
  22. package/dist/builtin/workflows/CHANGELOG.md +20 -0
  23. package/dist/builtin/workflows/README.md +5 -5
  24. package/dist/builtin/workflows/builtin/goal-artifacts.ts +17 -4
  25. package/dist/builtin/workflows/builtin/goal-prompts.ts +32 -22
  26. package/dist/builtin/workflows/builtin/goal-reducer.ts +29 -5
  27. package/dist/builtin/workflows/builtin/goal-review.ts +6 -11
  28. package/dist/builtin/workflows/builtin/goal-runner.ts +10 -11
  29. package/dist/builtin/workflows/builtin/open-claude-design-runner.ts +2 -2
  30. package/dist/builtin/workflows/builtin/ralph-core.ts +5 -54
  31. package/dist/builtin/workflows/builtin/ralph-forked-prompts.ts +103 -0
  32. package/dist/builtin/workflows/builtin/ralph-models.ts +10 -10
  33. package/dist/builtin/workflows/builtin/ralph-review-gate.ts +27 -24
  34. package/dist/builtin/workflows/builtin/ralph-reviewer-prompt.ts +16 -9
  35. package/dist/builtin/workflows/builtin/ralph-runner.ts +47 -21
  36. package/dist/builtin/workflows/builtin/review-convergence.ts +118 -0
  37. package/dist/builtin/workflows/builtin/shared-prompts.ts +40 -0
  38. package/dist/builtin/workflows/package.json +1 -1
  39. package/dist/builtin/workflows/src/extension/workflow-prompts.ts +1 -0
  40. package/dist/builtin/workflows/src/runs/shared/model-fallback-failures.ts +4 -5
  41. package/dist/core/agent-session-retry.d.ts.map +1 -1
  42. package/dist/core/agent-session-retry.js +2 -2
  43. package/dist/core/agent-session-retry.js.map +1 -1
  44. package/docs/settings.md +1 -1
  45. package/docs/subagents.md +5 -1
  46. package/docs/workflows.md +111 -5
  47. package/npm-shrinkwrap.json +23 -23
  48. 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:max",
110
- "github-copilot/gpt-5.6-sol:max",
111
- "openai/gpt-5.6-sol:max",
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:max",
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:max",
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:max",
138
+ model: "openai-codex/gpt-5.6-sol:xhigh",
139
139
  fallbackModels: [
140
- "github-copilot/gpt-5.6-sol:max",
141
- "openai/gpt-5.6-sol:max",
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:max",
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:max",
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 { traceabilityProvenExceptFinalAction } from "./review-convergence.js";
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 and deterministic. A single reviewer
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
- * - Blocking = P0/P1/P2 (numeric priority 0, 1, or 2).
18
- * - Non-blocking = P3 (numeric priority 3) — a nice-to-have that should not keep
19
- * the loop spinning.
20
- * - A finding whose priority cannot be determined (`null`/`undefined`) is treated
21
- * as blocking, so genuine ambiguity never silently approves.
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. P0=0, P1=1, P2=2 block;
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 const MAX_BLOCKING_PRIORITY = 2;
90
+ export { MAX_BLOCKING_PRIORITY } from "./review-convergence.js";
80
91
 
81
92
  /**
82
- * True when a finding must keep the review loop iterating. P0/P1/P2 block; P3 is
83
- * a non-blocking nice-to-have. A finding without a determinable priority
84
- * (`null`/`undefined`) is treated as blocking so ambiguity never silently
85
- * approves.
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
- const alignment = finding.objective_alignment;
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, not against previous workflow reasoning or expected loop progress.",
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 a non-blocking nice-to-have that does not block approval.",
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 workflow stage summaries or prior agent reasoning.",
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. Identify the changed files or diff under review.",
131
- "2. Read the relevant changed code and directly affected call sites/tests/configs.",
132
- "3. Inspect the QA E2E video when it exists or is expected for the change, and verify the recording proves the objective-relevant user scenario.",
133
- "4. Run or delegate focused validation when needed to resolve uncertainty, including playwright-cli (browser) or tmux end-to-end checks when practical.",
134
- "5. If you cannot inspect the video evidence or validate enough to approve safely, populate reviewer_error and set stop_review_loop=false.",
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: renderResearchPromptRefinementPrompt({
70
- request: workflowPrompt,
71
- acceptanceCriteria,
72
- workflowCwdContext,
73
- latestReviewReportPath,
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: renderResearchPrompt({
84
- transformedResearchQuestion: researchPromptRefinement.text,
85
- prompt: workflowPrompt,
86
- acceptanceCriteria,
87
- workflowCwdContext,
88
- latestReviewReportPath,
89
- researchPath: workflowResearchPath,
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
- { convergence_decision: roundConvergenceDecision, reviews: reviewEntries },
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");
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bastani/workflows",
3
- "version": "0.9.6",
3
+ "version": "0.9.7-alpha.1",
4
4
  "private": true,
5
5
  "description": "Atomic extension for multi-stage workflow authoring and execution.",
6
6
  "contributors": [
@@ -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**: