@bastani/atomic 0.9.4-alpha.9 → 0.9.5-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 +51 -0
- package/dist/builtin/cursor/CHANGELOG.md +6 -0
- package/dist/builtin/cursor/package.json +2 -2
- package/dist/builtin/intercom/CHANGELOG.md +6 -0
- package/dist/builtin/intercom/package.json +1 -1
- package/dist/builtin/mcp/CHANGELOG.md +6 -0
- package/dist/builtin/mcp/package.json +1 -1
- package/dist/builtin/subagents/CHANGELOG.md +27 -0
- package/dist/builtin/subagents/package.json +1 -1
- package/dist/builtin/subagents/src/slash/slash-commands.ts +2 -2
- package/dist/builtin/subagents/src/tui/render-result-compact.ts +3 -2
- package/dist/builtin/subagents/src/tui/render-result.ts +3 -3
- package/dist/builtin/subagents/src/tui/render-widget-graph.ts +4 -3
- package/dist/builtin/subagents/src/tui/render-widget.ts +10 -4
- package/dist/builtin/web-access/CHANGELOG.md +6 -0
- package/dist/builtin/web-access/package.json +1 -1
- package/dist/builtin/workflows/CHANGELOG.md +34 -0
- package/dist/builtin/workflows/README.md +13 -11
- package/dist/builtin/workflows/builtin/goal-ledger.ts +3 -0
- package/dist/builtin/workflows/builtin/goal-prompts.ts +15 -4
- package/dist/builtin/workflows/builtin/goal-reports.ts +23 -0
- package/dist/builtin/workflows/builtin/goal-review.ts +29 -4
- package/dist/builtin/workflows/builtin/goal-runner.ts +3 -1
- package/dist/builtin/workflows/builtin/goal-schemas.ts +22 -0
- package/dist/builtin/workflows/builtin/goal-types.ts +17 -0
- package/dist/builtin/workflows/builtin/goal.d.ts +3 -0
- package/dist/builtin/workflows/builtin/goal.ts +4 -2
- package/dist/builtin/workflows/builtin/index.d.ts +6 -0
- package/dist/builtin/workflows/builtin/ralph-core.ts +39 -2
- package/dist/builtin/workflows/builtin/ralph-review-gate.ts +29 -4
- package/dist/builtin/workflows/builtin/ralph-runner.ts +12 -5
- package/dist/builtin/workflows/builtin/ralph.d.ts +3 -0
- package/dist/builtin/workflows/builtin/ralph.ts +4 -1
- package/dist/builtin/workflows/builtin/shared-prompts.ts +15 -1
- package/dist/builtin/workflows/package.json +1 -1
- package/dist/builtin/workflows/src/tui/keybindings-adapter.ts +4 -0
- package/dist/builtin/workflows/src/tui/overlay-adapter.ts +4 -0
- package/dist/builtin/workflows/src/tui/stage-chat-view-input.ts +12 -1
- package/dist/builtin/workflows/src/tui/stage-chat-view-state.ts +2 -0
- package/dist/builtin/workflows/src/tui/stage-chat-view-types.ts +6 -0
- package/dist/builtin/workflows/src/tui/stage-chat-view.ts +4 -0
- package/dist/builtin/workflows/src/tui/workflow-attach-pane-types.ts +4 -0
- package/dist/builtin/workflows/src/tui/workflow-attach-pane.ts +8 -9
- package/dist/core/agent-session-events.d.ts.map +1 -1
- package/dist/core/agent-session-events.js +2 -0
- package/dist/core/agent-session-events.js.map +1 -1
- package/dist/core/agent-session-methods.d.ts +4 -1
- package/dist/core/agent-session-methods.d.ts.map +1 -1
- package/dist/core/agent-session-methods.js.map +1 -1
- package/dist/core/agent-session-tool-registry.d.ts.map +1 -1
- package/dist/core/agent-session-tool-registry.js +4 -0
- package/dist/core/agent-session-tool-registry.js.map +1 -1
- package/dist/core/agent-session.d.ts +4 -1
- package/dist/core/agent-session.d.ts.map +1 -1
- package/dist/core/agent-session.js +4 -0
- package/dist/core/agent-session.js.map +1 -1
- package/dist/core/async/format.d.ts +3 -0
- package/dist/core/async/format.d.ts.map +1 -0
- package/dist/core/async/format.js +50 -0
- package/dist/core/async/format.js.map +1 -0
- package/dist/core/async/job-manager.d.ts +39 -0
- package/dist/core/async/job-manager.d.ts.map +1 -0
- package/dist/core/async/job-manager.js +254 -0
- package/dist/core/async/job-manager.js.map +1 -0
- package/dist/core/async/session-manager.d.ts +18 -0
- package/dist/core/async/session-manager.d.ts.map +1 -0
- package/dist/core/async/session-manager.js +61 -0
- package/dist/core/async/session-manager.js.map +1 -0
- package/dist/core/async/types.d.ts +23 -0
- package/dist/core/async/types.d.ts.map +1 -0
- package/dist/core/async/types.js +2 -0
- package/dist/core/async/types.js.map +1 -0
- package/dist/core/extensions/loader-virtual-modules.d.ts +11 -0
- package/dist/core/extensions/loader-virtual-modules.d.ts.map +1 -1
- package/dist/core/extensions/loader-virtual-modules.js +20 -5
- package/dist/core/extensions/loader-virtual-modules.js.map +1 -1
- package/dist/core/tools/bash-async-execution.d.ts +25 -0
- package/dist/core/tools/bash-async-execution.d.ts.map +1 -0
- package/dist/core/tools/bash-async-execution.js +71 -0
- package/dist/core/tools/bash-async-execution.js.map +1 -0
- package/dist/core/tools/bash-async-jobs.d.ts +10 -0
- package/dist/core/tools/bash-async-jobs.d.ts.map +1 -1
- package/dist/core/tools/bash-async-jobs.js +18 -2
- package/dist/core/tools/bash-async-jobs.js.map +1 -1
- package/dist/core/tools/bash-async-output.d.ts +3 -1
- package/dist/core/tools/bash-async-output.d.ts.map +1 -1
- package/dist/core/tools/bash-async-output.js +4 -1
- package/dist/core/tools/bash-async-output.js.map +1 -1
- package/dist/core/tools/bash.d.ts +10 -4
- package/dist/core/tools/bash.d.ts.map +1 -1
- package/dist/core/tools/bash.js +20 -38
- package/dist/core/tools/bash.js.map +1 -1
- package/docs/tools.md +4 -2
- package/docs/workflows.md +9 -4
- package/npm-shrinkwrap.json +23 -23
- package/package.json +2 -2
|
@@ -9,6 +9,23 @@ export function formatReviewReport(reviews: readonly ReviewRecord[]): string {
|
|
|
9
9
|
`Decision: ${review.decision}`,
|
|
10
10
|
`Artifact: ${review.artifact_path}`,
|
|
11
11
|
`Verification remaining: ${review.verification_remaining}`,
|
|
12
|
+
"Finding alignment warning: beyond_objective and contradicts_objective findings are non-blocking and must not be folded into follow-up objectives without checking them against the acceptance criteria.",
|
|
13
|
+
review.findings.length === 0
|
|
14
|
+
? "Findings: none"
|
|
15
|
+
: [
|
|
16
|
+
"Findings:",
|
|
17
|
+
...review.findings.map((finding) =>
|
|
18
|
+
`- ${finding.objective_alignment}: ${finding.title}`
|
|
19
|
+
),
|
|
20
|
+
].join("\n"),
|
|
21
|
+
review.requirements_traceability.length === 0
|
|
22
|
+
? "Requirements traceability: none"
|
|
23
|
+
: [
|
|
24
|
+
"Requirements traceability:",
|
|
25
|
+
...review.requirements_traceability.map((entry) =>
|
|
26
|
+
`- ${entry.status}: ${entry.requirement} — ${entry.evidence}`
|
|
27
|
+
),
|
|
28
|
+
].join("\n"),
|
|
12
29
|
].join("\n"))
|
|
13
30
|
.join("\n\n---\n\n");
|
|
14
31
|
}
|
|
@@ -35,6 +52,9 @@ export function renderFinalReport(
|
|
|
35
52
|
"## Objective",
|
|
36
53
|
ledger.objective,
|
|
37
54
|
"",
|
|
55
|
+
"## Acceptance criteria",
|
|
56
|
+
ledger.acceptance_criteria,
|
|
57
|
+
"",
|
|
38
58
|
"## Final status",
|
|
39
59
|
ledger.status,
|
|
40
60
|
"",
|
|
@@ -47,6 +67,9 @@ export function renderFinalReport(
|
|
|
47
67
|
"## Final decision",
|
|
48
68
|
lastDecision?.reason ?? "No reducer decision was recorded.",
|
|
49
69
|
"",
|
|
70
|
+
"## Objective-alignment warning",
|
|
71
|
+
"Review findings classified beyond_objective or contradicts_objective are non-blocking and must not be promoted into follow-up objectives without checking them against the acceptance criteria.",
|
|
72
|
+
"",
|
|
50
73
|
"## Remaining work if incomplete",
|
|
51
74
|
ledger.status === "complete" ? "none" : remainingWork,
|
|
52
75
|
].join("\n");
|
|
@@ -5,14 +5,32 @@ export function reviewDecisionFromResult(result: WorkflowTaskResult): ReviewDeci
|
|
|
5
5
|
return result.structured as ReviewDecision | undefined;
|
|
6
6
|
}
|
|
7
7
|
|
|
8
|
+
const NON_BLOCKING_ALIGNMENTS = new Set([
|
|
9
|
+
"beyond_objective",
|
|
10
|
+
"contradicts_objective",
|
|
11
|
+
]);
|
|
12
|
+
|
|
13
|
+
function findingBlocksApproval(finding: ReviewDecision["findings"][number]): boolean {
|
|
14
|
+
const alignment = finding.objective_alignment;
|
|
15
|
+
if (NON_BLOCKING_ALIGNMENTS.has(alignment)) return false;
|
|
16
|
+
if (alignment !== "required_by_objective" && alignment !== "consistent_with_objective") {
|
|
17
|
+
return true;
|
|
18
|
+
}
|
|
19
|
+
return finding.priority !== 3;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function traceabilityApproves(decision: ReviewDecision): boolean {
|
|
23
|
+
return decision.requirements_traceability.length > 0 &&
|
|
24
|
+
decision.requirements_traceability.every((entry) => entry.status === "proven");
|
|
25
|
+
}
|
|
26
|
+
|
|
8
27
|
export function reviewApproved(decision: ReviewDecision): boolean {
|
|
9
|
-
const hasBlockingFindings = decision.findings.some(
|
|
10
|
-
(finding) => finding.priority !== 3,
|
|
11
|
-
);
|
|
28
|
+
const hasBlockingFindings = decision.findings.some(findingBlocksApproval);
|
|
12
29
|
return (
|
|
13
30
|
decision.stop_review_loop === true &&
|
|
14
31
|
decision.overall_correctness === "patch is correct" &&
|
|
15
32
|
decision.goal_oracle_satisfied === true &&
|
|
33
|
+
traceabilityApproves(decision) &&
|
|
16
34
|
!hasBlockingFindings &&
|
|
17
35
|
decision.reviewer_error == null
|
|
18
36
|
);
|
|
@@ -26,6 +44,7 @@ export function reviewerErrorDecision(message: string): ReviewDecision {
|
|
|
26
44
|
"Reviewer execution failed, so the review gate cannot safely approve the current repository state.",
|
|
27
45
|
overall_confidence_score: 0,
|
|
28
46
|
goal_oracle_satisfied: false,
|
|
47
|
+
requirements_traceability: [],
|
|
29
48
|
receipt_assessment:
|
|
30
49
|
"No reviewer receipt could be produced because reviewer execution failed.",
|
|
31
50
|
verification_remaining: "Recover reviewer execution and re-run oracle validation.",
|
|
@@ -61,8 +80,14 @@ export function reviewDecisionToRecord(args: {
|
|
|
61
80
|
const blocker = blockerFromReviewDecision(args.decision);
|
|
62
81
|
const approved = reviewApproved(args.decision);
|
|
63
82
|
const verificationGap = args.decision.verification_remaining.trim();
|
|
83
|
+
const traceabilityGaps = args.decision.requirements_traceability
|
|
84
|
+
.filter((entry) => entry.status !== "proven")
|
|
85
|
+
.map((entry) => `${entry.status}: ${entry.requirement} — ${entry.evidence}`);
|
|
64
86
|
const gaps = [
|
|
65
|
-
...args.decision.findings.map((finding) =>
|
|
87
|
+
...args.decision.findings.map((finding) =>
|
|
88
|
+
`[${finding.objective_alignment}] ${finding.title}: ${finding.body}`
|
|
89
|
+
),
|
|
90
|
+
...traceabilityGaps,
|
|
66
91
|
...(approved || verificationGap.length === 0 ? [] : [verificationGap]),
|
|
67
92
|
...(args.decision.reviewer_error == null
|
|
68
93
|
? []
|
|
@@ -85,12 +85,13 @@ export async function runGoalWorkflow(ctx: GoalRunnerContext, options: GoalWorkf
|
|
|
85
85
|
throw new Error("goal requires an objective input.");
|
|
86
86
|
}
|
|
87
87
|
const objective = rawObjective;
|
|
88
|
+
const acceptanceCriteria = inputs.acceptance_criteria?.trim() || objective;
|
|
88
89
|
|
|
89
90
|
const maxTurns = positiveInteger(inputs.max_turns, DEFAULT_MAX_TURNS);
|
|
90
91
|
const reviewQuorum = DEFAULT_REVIEW_QUORUM;
|
|
91
92
|
const blockerThreshold = Math.min(DEFAULT_BLOCKER_THRESHOLD, maxTurns);
|
|
92
93
|
const comparisonBaseBranch = normalizeBranchInput(inputs.base_branch, "origin/main");
|
|
93
|
-
const { ledger, ledgerPath, artifactDir } = await createGoalLedger(objective);
|
|
94
|
+
const { ledger, ledgerPath, artifactDir } = await createGoalLedger(objective, acceptanceCriteria);
|
|
94
95
|
|
|
95
96
|
// Chains curated from Atomic's agentic-coding benchmark (see
|
|
96
97
|
// ralph-models.ts for the frontier data and drop rationale).
|
|
@@ -415,6 +416,7 @@ export async function runGoalWorkflow(ctx: GoalRunnerContext, options: GoalWorkf
|
|
|
415
416
|
approved: ledger.status === "complete",
|
|
416
417
|
goal_id: ledger.goal_id,
|
|
417
418
|
objective: ledger.objective,
|
|
419
|
+
acceptance_criteria: ledger.acceptance_criteria,
|
|
418
420
|
ledger_path: ledgerPath,
|
|
419
421
|
turns_completed: ledger.turns,
|
|
420
422
|
iterations_completed: ledger.turns,
|
|
@@ -5,6 +5,12 @@ const reviewFindingSchema = Type.Object(
|
|
|
5
5
|
title: Type.String(),
|
|
6
6
|
body: Type.String(),
|
|
7
7
|
confidence_score: Type.Number({ minimum: 0, maximum: 1 }),
|
|
8
|
+
objective_alignment: Type.Union([
|
|
9
|
+
Type.Literal("required_by_objective"),
|
|
10
|
+
Type.Literal("consistent_with_objective"),
|
|
11
|
+
Type.Literal("beyond_objective"),
|
|
12
|
+
Type.Literal("contradicts_objective"),
|
|
13
|
+
]),
|
|
8
14
|
priority: Type.Optional(
|
|
9
15
|
Type.Union([Type.Integer({ minimum: 0, maximum: 3 }), Type.Null()]),
|
|
10
16
|
),
|
|
@@ -25,6 +31,21 @@ const reviewFindingSchema = Type.Object(
|
|
|
25
31
|
{ additionalProperties: false },
|
|
26
32
|
);
|
|
27
33
|
|
|
34
|
+
|
|
35
|
+
const requirementsTraceabilitySchema = Type.Object(
|
|
36
|
+
{
|
|
37
|
+
requirement: Type.String(),
|
|
38
|
+
status: Type.Union([
|
|
39
|
+
Type.Literal("proven"),
|
|
40
|
+
Type.Literal("contradicted"),
|
|
41
|
+
Type.Literal("missing"),
|
|
42
|
+
Type.Literal("unverified"),
|
|
43
|
+
]),
|
|
44
|
+
evidence: Type.String(),
|
|
45
|
+
},
|
|
46
|
+
{ additionalProperties: false },
|
|
47
|
+
);
|
|
48
|
+
|
|
28
49
|
const reviewerErrorSchema = Type.Object(
|
|
29
50
|
{
|
|
30
51
|
kind: Type.Union([
|
|
@@ -49,6 +70,7 @@ export const reviewDecisionSchema = Type.Object(
|
|
|
49
70
|
overall_explanation: Type.String(),
|
|
50
71
|
overall_confidence_score: Type.Number({ minimum: 0, maximum: 1 }),
|
|
51
72
|
goal_oracle_satisfied: Type.Boolean(),
|
|
73
|
+
requirements_traceability: Type.Array(requirementsTraceabilitySchema),
|
|
52
74
|
receipt_assessment: Type.String(),
|
|
53
75
|
verification_remaining: Type.String(),
|
|
54
76
|
stop_review_loop: Type.Boolean(),
|
|
@@ -14,10 +14,23 @@ export type WorkReceipt = {
|
|
|
14
14
|
readonly summary: string;
|
|
15
15
|
};
|
|
16
16
|
|
|
17
|
+
export type ObjectiveAlignment =
|
|
18
|
+
| "required_by_objective"
|
|
19
|
+
| "consistent_with_objective"
|
|
20
|
+
| "beyond_objective"
|
|
21
|
+
| "contradicts_objective";
|
|
22
|
+
|
|
23
|
+
export type RequirementTraceability = {
|
|
24
|
+
readonly requirement: string;
|
|
25
|
+
readonly status: "proven" | "contradicted" | "missing" | "unverified";
|
|
26
|
+
readonly evidence: string;
|
|
27
|
+
};
|
|
28
|
+
|
|
17
29
|
export type ReviewFinding = {
|
|
18
30
|
readonly title: string;
|
|
19
31
|
readonly body: string;
|
|
20
32
|
readonly confidence_score: number;
|
|
33
|
+
readonly objective_alignment: ObjectiveAlignment;
|
|
21
34
|
readonly priority?: number | null;
|
|
22
35
|
readonly code_location: {
|
|
23
36
|
readonly absolute_file_path: string;
|
|
@@ -44,6 +57,7 @@ export type ReviewDecision = {
|
|
|
44
57
|
readonly overall_explanation: string;
|
|
45
58
|
readonly overall_confidence_score: number;
|
|
46
59
|
readonly goal_oracle_satisfied: boolean;
|
|
60
|
+
readonly requirements_traceability: readonly RequirementTraceability[];
|
|
47
61
|
readonly receipt_assessment: string;
|
|
48
62
|
readonly verification_remaining: string;
|
|
49
63
|
readonly stop_review_loop: boolean;
|
|
@@ -93,6 +107,7 @@ export type GoalLifecycleEvent = {
|
|
|
93
107
|
export type GoalLedger = {
|
|
94
108
|
readonly goal_id: string;
|
|
95
109
|
readonly objective: string;
|
|
110
|
+
readonly acceptance_criteria: string;
|
|
96
111
|
status: GoalStatus;
|
|
97
112
|
turns: number;
|
|
98
113
|
readonly created_at: string;
|
|
@@ -112,6 +127,7 @@ export type ReducerOutcome = {
|
|
|
112
127
|
|
|
113
128
|
export type GoalWorkflowInputs = {
|
|
114
129
|
readonly objective: string;
|
|
130
|
+
readonly acceptance_criteria?: string;
|
|
115
131
|
readonly max_turns: number;
|
|
116
132
|
readonly base_branch: string;
|
|
117
133
|
readonly create_pr: boolean;
|
|
@@ -123,6 +139,7 @@ export type GoalWorkflowOutputs = {
|
|
|
123
139
|
readonly approved?: boolean;
|
|
124
140
|
readonly goal_id?: string;
|
|
125
141
|
readonly objective?: string;
|
|
142
|
+
readonly acceptance_criteria?: string;
|
|
126
143
|
readonly ledger_path?: string;
|
|
127
144
|
readonly turns_completed?: number;
|
|
128
145
|
readonly iterations_completed?: number;
|
|
@@ -11,6 +11,7 @@ export type GoalWorkflowReceipt = {
|
|
|
11
11
|
|
|
12
12
|
export type GoalWorkflowInputs = WorkflowInputValues & {
|
|
13
13
|
readonly objective: string;
|
|
14
|
+
readonly acceptance_criteria?: string;
|
|
14
15
|
readonly max_turns: number;
|
|
15
16
|
readonly base_branch: string;
|
|
16
17
|
readonly create_pr: boolean;
|
|
@@ -18,6 +19,7 @@ export type GoalWorkflowInputs = WorkflowInputValues & {
|
|
|
18
19
|
|
|
19
20
|
export type GoalWorkflowRunInputs = WorkflowInputValues & {
|
|
20
21
|
readonly objective: string;
|
|
22
|
+
readonly acceptance_criteria?: string;
|
|
21
23
|
readonly max_turns?: number;
|
|
22
24
|
readonly base_branch?: string;
|
|
23
25
|
readonly create_pr?: boolean;
|
|
@@ -29,6 +31,7 @@ export type GoalWorkflowOutputs = WorkflowOutputValues & {
|
|
|
29
31
|
readonly approved?: boolean;
|
|
30
32
|
readonly goal_id?: string;
|
|
31
33
|
readonly objective?: string;
|
|
34
|
+
readonly acceptance_criteria?: string;
|
|
32
35
|
readonly ledger_path?: string;
|
|
33
36
|
readonly turns_completed?: number;
|
|
34
37
|
readonly iterations_completed?: number;
|
|
@@ -13,9 +13,10 @@ import { DEFAULT_MAX_TURNS } from "./goal-types.js";
|
|
|
13
13
|
|
|
14
14
|
export default workflow({
|
|
15
15
|
name: "goal",
|
|
16
|
-
description: "Goal Runner workflow with bounded LM turns, ledger artifacts, parallel reviewers, and reducer-gated completion.",
|
|
16
|
+
description: "Goal Runner workflow with bounded LM turns, immutable acceptance criteria, ledger artifacts, parallel reviewers, and reducer-gated completion. When launching follow-up goal runs from review findings, pass the ORIGINAL task text as acceptance_criteria so deltas cannot drift from the literal contract.",
|
|
17
17
|
inputs: {
|
|
18
|
-
objective: Type.String({ description: "The objective for
|
|
18
|
+
objective: Type.String({ description: "The objective or delta for this Goal Runner workflow run." }),
|
|
19
|
+
acceptance_criteria: Type.Optional(Type.String({ description: "Original immutable task contract this run must remain consistent with. Defaults to objective. Orchestrators launching follow-up runs from reviewer findings should pass the ORIGINAL task text here." })),
|
|
19
20
|
max_turns: Type.Number({
|
|
20
21
|
default: DEFAULT_MAX_TURNS,
|
|
21
22
|
description: "Maximum worker/review turns before Goal Runner stops as needs_human.",
|
|
@@ -39,6 +40,7 @@ export default workflow({
|
|
|
39
40
|
approved: Type.Optional(Type.Boolean({ description: "Whether the reducer reached complete." })),
|
|
40
41
|
goal_id: Type.Optional(Type.String({ description: "Per-run goal identifier stored in the ledger." })),
|
|
41
42
|
objective: Type.Optional(Type.String({ description: "Raw goal objective used by the run." })),
|
|
43
|
+
acceptance_criteria: Type.Optional(Type.String({ description: "Immutable acceptance criteria used by the run." })),
|
|
42
44
|
ledger_path: Type.Optional(Type.String({ description: "OS-temp path to goal-ledger.json with receipts, reviewer decisions, blockers, and lifecycle events." })),
|
|
43
45
|
turns_completed: Type.Optional(Type.Number({ description: "Worker/review turns completed." })),
|
|
44
46
|
iterations_completed: Type.Optional(Type.Number({ description: "Worker/review turns completed, retained for status summaries." })),
|
|
@@ -37,12 +37,14 @@ export type GoalWorkflowReceipt = {
|
|
|
37
37
|
};
|
|
38
38
|
export type GoalWorkflowInputs = WorkflowInputValues & {
|
|
39
39
|
readonly objective: string;
|
|
40
|
+
readonly acceptance_criteria?: string;
|
|
40
41
|
readonly max_turns: number;
|
|
41
42
|
readonly base_branch: string;
|
|
42
43
|
readonly create_pr: boolean;
|
|
43
44
|
};
|
|
44
45
|
export type GoalWorkflowRunInputs = WorkflowInputValues & {
|
|
45
46
|
readonly objective: string;
|
|
47
|
+
readonly acceptance_criteria?: string;
|
|
46
48
|
readonly max_turns?: number;
|
|
47
49
|
readonly base_branch?: string;
|
|
48
50
|
readonly create_pr?: boolean;
|
|
@@ -53,6 +55,7 @@ export type GoalWorkflowOutputs = WorkflowOutputValues & {
|
|
|
53
55
|
readonly approved?: boolean;
|
|
54
56
|
readonly goal_id?: string;
|
|
55
57
|
readonly objective?: string;
|
|
58
|
+
readonly acceptance_criteria?: string;
|
|
56
59
|
readonly ledger_path?: string;
|
|
57
60
|
readonly turns_completed?: number;
|
|
58
61
|
readonly iterations_completed?: number;
|
|
@@ -70,6 +73,7 @@ export type GoalWorkflowDefinition = WorkflowDefinition<
|
|
|
70
73
|
|
|
71
74
|
export type RalphWorkflowInputs = WorkflowInputValues & {
|
|
72
75
|
readonly prompt: string;
|
|
76
|
+
readonly acceptance_criteria?: string;
|
|
73
77
|
readonly max_loops: number;
|
|
74
78
|
readonly base_branch: string;
|
|
75
79
|
readonly git_worktree_dir: string;
|
|
@@ -77,6 +81,7 @@ export type RalphWorkflowInputs = WorkflowInputValues & {
|
|
|
77
81
|
};
|
|
78
82
|
export type RalphWorkflowRunInputs = WorkflowInputValues & {
|
|
79
83
|
readonly prompt: string;
|
|
84
|
+
readonly acceptance_criteria?: string;
|
|
80
85
|
readonly max_loops?: number;
|
|
81
86
|
readonly base_branch?: string;
|
|
82
87
|
readonly git_worktree_dir?: string;
|
|
@@ -89,6 +94,7 @@ export type RalphWorkflowOutputs = WorkflowOutputValues & {
|
|
|
89
94
|
readonly research?: string;
|
|
90
95
|
readonly research_path?: string;
|
|
91
96
|
readonly implementation_notes_path?: string;
|
|
97
|
+
readonly qa_video_path?: string;
|
|
92
98
|
readonly pr_report?: string;
|
|
93
99
|
readonly approved?: boolean;
|
|
94
100
|
readonly iterations_completed?: number;
|
|
@@ -3,7 +3,7 @@ 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 { E2E_VERIFICATION_GUIDANCE } from "./shared-prompts.js";
|
|
6
|
+
import { E2E_VERIFICATION_GUIDANCE, LITERAL_OBJECTIVE_CONTRACT } from "./shared-prompts.js";
|
|
7
7
|
import type { ReviewDecision } from "./ralph-review-gate.js";
|
|
8
8
|
|
|
9
9
|
export const DEFAULT_MAX_LOOPS = 10;
|
|
@@ -26,6 +26,12 @@ const reviewFindingSchema = Type.Object(
|
|
|
26
26
|
title: Type.String(),
|
|
27
27
|
body: Type.String(),
|
|
28
28
|
confidence_score: Type.Number({ minimum: 0, maximum: 1 }),
|
|
29
|
+
objective_alignment: Type.Union([
|
|
30
|
+
Type.Literal("required_by_objective"),
|
|
31
|
+
Type.Literal("consistent_with_objective"),
|
|
32
|
+
Type.Literal("beyond_objective"),
|
|
33
|
+
Type.Literal("contradicts_objective"),
|
|
34
|
+
]),
|
|
29
35
|
priority: Type.Optional(
|
|
30
36
|
Type.Union([Type.Integer({ minimum: 0, maximum: 3 }), Type.Null()]),
|
|
31
37
|
),
|
|
@@ -46,6 +52,20 @@ const reviewFindingSchema = Type.Object(
|
|
|
46
52
|
{ additionalProperties: false },
|
|
47
53
|
);
|
|
48
54
|
|
|
55
|
+
const requirementsTraceabilitySchema = Type.Object(
|
|
56
|
+
{
|
|
57
|
+
requirement: Type.String(),
|
|
58
|
+
status: Type.Union([
|
|
59
|
+
Type.Literal("proven"),
|
|
60
|
+
Type.Literal("contradicted"),
|
|
61
|
+
Type.Literal("missing"),
|
|
62
|
+
Type.Literal("unverified"),
|
|
63
|
+
]),
|
|
64
|
+
evidence: Type.String(),
|
|
65
|
+
},
|
|
66
|
+
{ additionalProperties: false },
|
|
67
|
+
);
|
|
68
|
+
|
|
49
69
|
const reviewerErrorSchema = Type.Object(
|
|
50
70
|
{
|
|
51
71
|
kind: Type.Union([
|
|
@@ -69,6 +89,7 @@ export const reviewDecisionSchema = Type.Object(
|
|
|
69
89
|
]),
|
|
70
90
|
overall_explanation: Type.String(),
|
|
71
91
|
overall_confidence_score: Type.Number({ minimum: 0, maximum: 1 }),
|
|
92
|
+
requirements_traceability: Type.Array(requirementsTraceabilitySchema),
|
|
72
93
|
stop_review_loop: Type.Boolean(),
|
|
73
94
|
reviewer_error: Type.Optional(
|
|
74
95
|
Type.Union([Type.Null(), reviewerErrorSchema]),
|
|
@@ -170,6 +191,7 @@ export function renderQaE2eVideoGuidance(qaVideoPath: string): string {
|
|
|
170
191
|
`Record that QA E2E pass as a reviewable video so the user can watch the feature working. After \`playwright-cli open\`, start recording with \`playwright-cli video-start ${qaVideoPath}\`, annotate the scenario with \`playwright-cli video-chapter\` / \`playwright-cli video-show-actions\`, exercise the full user scenario, then \`playwright-cli video-stop\`. Write the video to exactly this path and overwrite any prior recording so it always reflects the latest implemented state: ${qaVideoPath}`,
|
|
171
192
|
`After recording, add the video to the implementation notes as a reference: include a \`## QA E2E Video\` entry with the absolute path ${qaVideoPath} and a one-line description of the proven scenario, so the user can review the proof when this stage finishes.`,
|
|
172
193
|
"If the change has no user-visible UI scenario (pure refactor, docs, infra, or non-UI library code), do not fabricate a video; record in the implementation notes that no QA E2E video applies and why.",
|
|
194
|
+
"Assume credentials, auth, and browser environment access exist until a concrete attempt proves otherwise. Before declaring the QA E2E video impractical, check credential/auth state with non-destructive commands, attempt to launch the app/flow, and record the exact command(s) plus observed failure output.",
|
|
173
195
|
"If `playwright-cli` or a browser runtime is unavailable, install it once per the skill (`npm install -g @playwright/cli@latest`, then `npx playwright install chromium` for a missing browser executable). If it still cannot run, record the smallest validation actually performed and note that the QA E2E video could not be produced — never claim a video exists when it does not.",
|
|
174
196
|
].join("\n");
|
|
175
197
|
}
|
|
@@ -186,6 +208,7 @@ export function reviewerErrorDecision(error: string): ReviewDecision {
|
|
|
186
208
|
"Reviewer execution failed, so the review gate cannot safely approve the current repository state.",
|
|
187
209
|
overall_confidence_score: 0,
|
|
188
210
|
stop_review_loop: false,
|
|
211
|
+
requirements_traceability: [],
|
|
189
212
|
reviewer_error: {
|
|
190
213
|
kind: "reviewer_failure",
|
|
191
214
|
message: error,
|
|
@@ -258,6 +281,7 @@ export function forkContinuationOptions(
|
|
|
258
281
|
|
|
259
282
|
export function renderResearchPromptRefinementPrompt(args: {
|
|
260
283
|
readonly request: string;
|
|
284
|
+
readonly acceptanceCriteria: string;
|
|
261
285
|
readonly workflowCwdContext: PromptSection;
|
|
262
286
|
readonly latestReviewReportPath: string | undefined;
|
|
263
287
|
}): string {
|
|
@@ -265,6 +289,9 @@ export function renderResearchPromptRefinementPrompt(args: {
|
|
|
265
289
|
return [
|
|
266
290
|
basePrompt,
|
|
267
291
|
taggedPrompt([
|
|
292
|
+
["objective", `Research the full requested task: ${args.request}`],
|
|
293
|
+
["acceptance_criteria", args.acceptanceCriteria],
|
|
294
|
+
["literal_contract", LITERAL_OBJECTIVE_CONTRACT],
|
|
268
295
|
args.workflowCwdContext,
|
|
269
296
|
[
|
|
270
297
|
"review_findings",
|
|
@@ -272,7 +299,7 @@ export function renderResearchPromptRefinementPrompt(args: {
|
|
|
272
299
|
? "No prior review artifact is available."
|
|
273
300
|
: [
|
|
274
301
|
`Latest review round artifact: ${args.latestReviewReportPath}`,
|
|
275
|
-
"Read this JSON artifact and include unresolved reviewer findings in the transformed research question
|
|
302
|
+
"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.",
|
|
276
303
|
].join("\n"),
|
|
277
304
|
],
|
|
278
305
|
[
|
|
@@ -285,6 +312,8 @@ export function renderResearchPromptRefinementPrompt(args: {
|
|
|
285
312
|
|
|
286
313
|
export function renderResearchPrompt(args: {
|
|
287
314
|
readonly transformedResearchQuestion: string;
|
|
315
|
+
readonly prompt: string;
|
|
316
|
+
readonly acceptanceCriteria: string;
|
|
288
317
|
readonly workflowCwdContext: PromptSection;
|
|
289
318
|
readonly latestReviewReportPath: string | undefined;
|
|
290
319
|
readonly researchPath: string;
|
|
@@ -293,6 +322,9 @@ export function renderResearchPrompt(args: {
|
|
|
293
322
|
return [
|
|
294
323
|
basePrompt,
|
|
295
324
|
taggedPrompt([
|
|
325
|
+
["objective", `Research implementation requirements for: ${args.prompt}`],
|
|
326
|
+
["acceptance_criteria", args.acceptanceCriteria],
|
|
327
|
+
["literal_contract", LITERAL_OBJECTIVE_CONTRACT],
|
|
296
328
|
args.workflowCwdContext,
|
|
297
329
|
[
|
|
298
330
|
"review_findings",
|
|
@@ -318,6 +350,7 @@ export function renderResearchPrompt(args: {
|
|
|
318
350
|
|
|
319
351
|
export function renderForkedOrchestratorPrompt(args: {
|
|
320
352
|
readonly prompt: string;
|
|
353
|
+
readonly acceptanceCriteria: string;
|
|
321
354
|
readonly workflowCwdContext: PromptSection;
|
|
322
355
|
readonly researchPath: string;
|
|
323
356
|
readonly implementationNotesPath: string;
|
|
@@ -331,6 +364,8 @@ export function renderForkedOrchestratorPrompt(args: {
|
|
|
331
364
|
].join("\n"),
|
|
332
365
|
],
|
|
333
366
|
["objective", `Implement the full requested task: ${args.prompt}`],
|
|
367
|
+
["acceptance_criteria", args.acceptanceCriteria],
|
|
368
|
+
["literal_contract", LITERAL_OBJECTIVE_CONTRACT],
|
|
334
369
|
args.workflowCwdContext,
|
|
335
370
|
[
|
|
336
371
|
"research",
|
|
@@ -367,6 +402,7 @@ export function renderForkedOrchestratorPrompt(args: {
|
|
|
367
402
|
|
|
368
403
|
export type RalphInputs = {
|
|
369
404
|
readonly prompt?: string;
|
|
405
|
+
readonly acceptance_criteria?: string;
|
|
370
406
|
readonly max_loops?: number;
|
|
371
407
|
readonly base_branch?: string;
|
|
372
408
|
readonly git_worktree_dir?: string;
|
|
@@ -375,6 +411,7 @@ export type RalphInputs = {
|
|
|
375
411
|
|
|
376
412
|
export type RalphWorkflowOptions = {
|
|
377
413
|
readonly prompt: string;
|
|
414
|
+
readonly acceptanceCriteria: string;
|
|
378
415
|
readonly maxLoops: number;
|
|
379
416
|
readonly comparisonBaseBranch: string;
|
|
380
417
|
readonly workflowStartCwd: string;
|
|
@@ -23,10 +23,17 @@
|
|
|
23
23
|
* depend on the model correctly deriving that flag.
|
|
24
24
|
*/
|
|
25
25
|
|
|
26
|
+
export type ObjectiveAlignment =
|
|
27
|
+
| "required_by_objective"
|
|
28
|
+
| "consistent_with_objective"
|
|
29
|
+
| "beyond_objective"
|
|
30
|
+
| "contradicts_objective";
|
|
31
|
+
|
|
26
32
|
export type ReviewFinding = {
|
|
27
33
|
readonly title: string;
|
|
28
34
|
readonly body: string;
|
|
29
35
|
readonly confidence_score: number;
|
|
36
|
+
readonly objective_alignment: ObjectiveAlignment;
|
|
30
37
|
readonly priority?: number | null;
|
|
31
38
|
readonly code_location: {
|
|
32
39
|
readonly absolute_file_path: string;
|
|
@@ -46,12 +53,19 @@ export type ReviewerError = {
|
|
|
46
53
|
readonly message: string;
|
|
47
54
|
readonly attempted_recovery: string;
|
|
48
55
|
};
|
|
56
|
+
export type RequirementTraceability = {
|
|
57
|
+
readonly requirement: string;
|
|
58
|
+
readonly status: "proven" | "contradicted" | "missing" | "unverified";
|
|
59
|
+
readonly evidence: string;
|
|
60
|
+
};
|
|
61
|
+
|
|
49
62
|
|
|
50
63
|
export type ReviewDecision = {
|
|
51
64
|
readonly findings: readonly ReviewFinding[];
|
|
52
65
|
readonly overall_correctness: "patch is correct" | "patch is incorrect";
|
|
53
66
|
readonly overall_explanation: string;
|
|
54
67
|
readonly overall_confidence_score: number;
|
|
68
|
+
readonly requirements_traceability: readonly RequirementTraceability[];
|
|
55
69
|
readonly stop_review_loop: boolean;
|
|
56
70
|
readonly reviewer_error?: ReviewerError | null;
|
|
57
71
|
};
|
|
@@ -69,6 +83,13 @@ export const MAX_BLOCKING_PRIORITY = 2;
|
|
|
69
83
|
* approves.
|
|
70
84
|
*/
|
|
71
85
|
export function isBlockingFinding(finding: ReviewFinding): boolean {
|
|
86
|
+
const alignment = finding.objective_alignment;
|
|
87
|
+
if (alignment === "beyond_objective" || alignment === "contradicts_objective") {
|
|
88
|
+
return false;
|
|
89
|
+
}
|
|
90
|
+
if (alignment !== "required_by_objective" && alignment !== "consistent_with_objective") {
|
|
91
|
+
return true;
|
|
92
|
+
}
|
|
72
93
|
const priority = finding.priority;
|
|
73
94
|
if (priority === undefined || priority === null) return true;
|
|
74
95
|
return priority <= MAX_BLOCKING_PRIORITY;
|
|
@@ -76,14 +97,18 @@ export function isBlockingFinding(finding: ReviewFinding): boolean {
|
|
|
76
97
|
|
|
77
98
|
/**
|
|
78
99
|
* A single reviewer approves (would stop the loop) when it judged the patch
|
|
79
|
-
* correct, surfaced no reviewer execution error,
|
|
80
|
-
*
|
|
81
|
-
*
|
|
100
|
+
* correct, surfaced no reviewer execution error, filed no blocking (P0/P1/P2)
|
|
101
|
+
* finding, and supplied a non-empty requirement traceability map where every
|
|
102
|
+
* explicit requirement is proven. P3 nice-to-haves and placeholder/dummy
|
|
103
|
+
* findings do not block approval.
|
|
82
104
|
*/
|
|
83
105
|
export function reviewDecisionApproved(decision: ReviewDecision): boolean {
|
|
106
|
+
const traceability = decision.requirements_traceability;
|
|
84
107
|
return (
|
|
85
108
|
decision.overall_correctness === "patch is correct" &&
|
|
86
109
|
decision.reviewer_error == null &&
|
|
87
|
-
!decision.findings.some(isBlockingFinding)
|
|
110
|
+
!decision.findings.some(isBlockingFinding) &&
|
|
111
|
+
traceability.length > 0 &&
|
|
112
|
+
traceability.every((entry) => entry.status === "proven")
|
|
88
113
|
);
|
|
89
114
|
}
|
|
@@ -5,6 +5,7 @@ import { join, resolve } from "node:path";
|
|
|
5
5
|
import type { WorkflowRunContext, WorkflowTaskResult } from "../src/shared/types.js";
|
|
6
6
|
import {
|
|
7
7
|
E2E_VERIFICATION_GUIDANCE,
|
|
8
|
+
LITERAL_OBJECTIVE_CONTRACT,
|
|
8
9
|
WORKER_PREFLIGHT_CONTRACT,
|
|
9
10
|
renderE2eQaVideoReviewGuidance,
|
|
10
11
|
} from "./shared-prompts.js";
|
|
@@ -43,7 +44,7 @@ export async function runRalphWorkflow(
|
|
|
43
44
|
ctx: WorkflowRunContext<RalphInputs>,
|
|
44
45
|
options: RalphWorkflowOptions,
|
|
45
46
|
): Promise<RalphWorkflowResult> {
|
|
46
|
-
const { prompt, maxLoops, comparisonBaseBranch, workflowStartCwd, createPr } = options;
|
|
47
|
+
const { prompt, acceptanceCriteria, maxLoops, comparisonBaseBranch, workflowStartCwd, createPr } = options;
|
|
47
48
|
let latestReviewReportPath: string | undefined;
|
|
48
49
|
let finalPlan = "";
|
|
49
50
|
let finalPlanPath = "";
|
|
@@ -68,6 +69,7 @@ export async function runRalphWorkflow(
|
|
|
68
69
|
const researchPromptRefinement = await ctx.task(`research-prompt-refinement-${iteration}`, {
|
|
69
70
|
prompt: renderResearchPromptRefinementPrompt({
|
|
70
71
|
request: workflowPrompt,
|
|
72
|
+
acceptanceCriteria,
|
|
71
73
|
workflowCwdContext,
|
|
72
74
|
latestReviewReportPath,
|
|
73
75
|
}),
|
|
@@ -81,6 +83,8 @@ export async function runRalphWorkflow(
|
|
|
81
83
|
const research = await ctx.task(`research-${iteration}`, {
|
|
82
84
|
prompt: renderResearchPrompt({
|
|
83
85
|
transformedResearchQuestion: researchPromptRefinement.text,
|
|
86
|
+
prompt: workflowPrompt,
|
|
87
|
+
acceptanceCriteria,
|
|
84
88
|
workflowCwdContext,
|
|
85
89
|
latestReviewReportPath,
|
|
86
90
|
researchPath: workflowResearchPath,
|
|
@@ -108,6 +112,8 @@ export async function runRalphWorkflow(
|
|
|
108
112
|
"objective",
|
|
109
113
|
`Implement the full requested task: ${workflowPrompt}`,
|
|
110
114
|
],
|
|
115
|
+
["acceptance_criteria", acceptanceCriteria],
|
|
116
|
+
["literal_contract", LITERAL_OBJECTIVE_CONTRACT],
|
|
111
117
|
workflowCwdContext,
|
|
112
118
|
[
|
|
113
119
|
"research",
|
|
@@ -195,6 +201,7 @@ export async function runRalphWorkflow(
|
|
|
195
201
|
])
|
|
196
202
|
: renderForkedOrchestratorPrompt({
|
|
197
203
|
prompt: workflowPrompt,
|
|
204
|
+
acceptanceCriteria,
|
|
198
205
|
workflowCwdContext,
|
|
199
206
|
researchPath,
|
|
200
207
|
implementationNotesPath,
|
|
@@ -220,6 +227,8 @@ export async function runRalphWorkflow(
|
|
|
220
227
|
].join("\n"),
|
|
221
228
|
],
|
|
222
229
|
["objective", `Review the current code delta for the task: ${workflowPrompt}`],
|
|
230
|
+
["acceptance_criteria", acceptanceCriteria],
|
|
231
|
+
["literal_contract", LITERAL_OBJECTIVE_CONTRACT],
|
|
223
232
|
workflowCwdContext,
|
|
224
233
|
[
|
|
225
234
|
"comparison_baseline",
|
|
@@ -279,6 +288,7 @@ export async function runRalphWorkflow(
|
|
|
279
288
|
[
|
|
280
289
|
"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.",
|
|
281
290
|
"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.",
|
|
291
|
+
"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.",
|
|
282
292
|
"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.",
|
|
283
293
|
"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`.",
|
|
284
294
|
"Keep code_location ranges as short as possible, ideally one line and never longer than 5-10 lines unless unavoidable.",
|
|
@@ -322,10 +332,7 @@ export async function runRalphWorkflow(
|
|
|
322
332
|
],
|
|
323
333
|
[
|
|
324
334
|
"decision_rules",
|
|
325
|
-
[
|
|
326
|
-
"Set stop_review_loop=true when the patch is correct, reviewer_error is null/omitted, and there are no blocking (P0/P1/P2) findings; remaining P3 nice-to-haves do not block approval. The loop gate is computed from finding priorities, so an unresolved P0/P1/P2 keeps the loop going regardless of this flag.",
|
|
327
|
-
"If you hit a reviewer/tool/validation error, set stop_review_loop=false and populate reviewer_error instead of pretending the patch is approved.",
|
|
328
|
-
].join("\n"),
|
|
335
|
+
["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 entry is proven, and no objective-relevant verification 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 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"),
|
|
329
336
|
],
|
|
330
337
|
]);
|
|
331
338
|
let reviews: WorkflowTaskResult[];
|
|
@@ -2,6 +2,7 @@ import type { WorkflowDefinition, WorkflowInputValues, WorkflowOutputValues } fr
|
|
|
2
2
|
|
|
3
3
|
export type RalphWorkflowInputs = WorkflowInputValues & {
|
|
4
4
|
readonly prompt: string;
|
|
5
|
+
readonly acceptance_criteria?: string;
|
|
5
6
|
readonly max_loops: number;
|
|
6
7
|
readonly base_branch: string;
|
|
7
8
|
readonly git_worktree_dir: string;
|
|
@@ -10,6 +11,7 @@ export type RalphWorkflowInputs = WorkflowInputValues & {
|
|
|
10
11
|
|
|
11
12
|
export type RalphWorkflowRunInputs = WorkflowInputValues & {
|
|
12
13
|
readonly prompt: string;
|
|
14
|
+
readonly acceptance_criteria?: string;
|
|
13
15
|
readonly max_loops?: number;
|
|
14
16
|
readonly base_branch?: string;
|
|
15
17
|
readonly git_worktree_dir?: string;
|
|
@@ -23,6 +25,7 @@ export type RalphWorkflowOutputs = WorkflowOutputValues & {
|
|
|
23
25
|
readonly research?: string;
|
|
24
26
|
readonly research_path?: string;
|
|
25
27
|
readonly implementation_notes_path?: string;
|
|
28
|
+
readonly qa_video_path?: string;
|
|
26
29
|
readonly pr_report?: string;
|
|
27
30
|
readonly approved?: boolean;
|
|
28
31
|
readonly iterations_completed?: number;
|
|
@@ -11,9 +11,10 @@ import { runRalphWorkflow } from "./ralph-runner.js";
|
|
|
11
11
|
|
|
12
12
|
export default workflow({
|
|
13
13
|
name: "ralph",
|
|
14
|
-
description: "Raw prompt → research-prompt-refinement → research → orchestrate → multi-model parallel review loop with bounded iteration.",
|
|
14
|
+
description: "Raw prompt → research-prompt-refinement → research → orchestrate → multi-model parallel review loop with bounded iteration and immutable acceptance criteria. When launching follow-up ralph runs from review findings, pass the ORIGINAL task text as acceptance_criteria so deltas cannot drift from the literal contract.",
|
|
15
15
|
inputs: {
|
|
16
16
|
prompt: Type.String({ description: "The task or goal to research, execute, and refine." }),
|
|
17
|
+
acceptance_criteria: Type.Optional(Type.String({ description: "Original immutable task contract this run must remain consistent with. Defaults to prompt. Orchestrators launching follow-up runs from reviewer findings should pass the ORIGINAL task text here." })),
|
|
17
18
|
max_loops: Type.Number({
|
|
18
19
|
default: DEFAULT_MAX_LOOPS,
|
|
19
20
|
description: `Maximum research/orchestrate/review iterations (default ${DEFAULT_MAX_LOOPS}).`,
|
|
@@ -56,6 +57,7 @@ export default workflow({
|
|
|
56
57
|
const workflowStartCwd = workflowCtx.cwd ?? process.cwd();
|
|
57
58
|
const inputs = workflowCtx.inputs;
|
|
58
59
|
const prompt = inputs.prompt;
|
|
60
|
+
const acceptanceCriteria = inputs.acceptance_criteria?.trim() || prompt;
|
|
59
61
|
const maxLoops = positiveInteger(inputs.max_loops, DEFAULT_MAX_LOOPS);
|
|
60
62
|
const comparisonBaseBranch = normalizeBranchInput(
|
|
61
63
|
inputs.base_branch,
|
|
@@ -64,6 +66,7 @@ export default workflow({
|
|
|
64
66
|
const createPr = inputs.create_pr === true;
|
|
65
67
|
return await runRalphWorkflow(workflowCtx, {
|
|
66
68
|
prompt,
|
|
69
|
+
acceptanceCriteria,
|
|
67
70
|
maxLoops,
|
|
68
71
|
comparisonBaseBranch,
|
|
69
72
|
workflowStartCwd,
|