@bastani/atomic 0.5.31 → 0.5.32-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.
@@ -8,25 +8,26 @@
8
8
  import type { ReviewResult } from "./prompts.ts";
9
9
 
10
10
  /**
11
- * Check whether the reviewer produced actionable findings.
11
+ * Check whether the loop should iterate again.
12
12
  *
13
13
  * Returns true when:
14
- * 1. The parsed ReviewResult has one or more findings, OR
15
- * 2. The review could not be parsed (null) but the raw response
16
- * text is non-empty (treat unparseable output as actionable).
14
+ * 1. The review could not be parsed (null) but the raw response text is
15
+ * non-empty treat unparseable output as actionable so the loop keeps
16
+ * iterating instead of silently exiting on a missing reviewer.
17
+ * 2. The merged review reports `overall_correctness === "patch is incorrect"`.
18
+ * {@link mergeReviewResults} sets the merged value to "patch is incorrect"
19
+ * if EITHER reviewer flagged it, so "patch is correct" here means BOTH
20
+ * reviewers signed off — the only stop condition.
17
21
  *
18
- * @param review - Parsed ReviewResult, or null if parsing failed.
22
+ * @param review - Parsed (merged) ReviewResult, or null if parsing failed.
19
23
  * @param rawText - The raw reviewer response text.
20
24
  */
21
25
  export function hasActionableFindings(
22
26
  review: ReviewResult | null,
23
27
  rawText: string,
24
28
  ): boolean {
25
- if (review !== null && review.findings.length > 0) {
26
- return true;
29
+ if (review === null) {
30
+ return rawText.trim().length > 0;
27
31
  }
28
- if (review === null && rawText.trim().length > 0) {
29
- return true;
30
- }
31
- return false;
32
+ return review.overall_correctness === "patch is incorrect";
32
33
  }
@@ -1,11 +1,16 @@
1
1
  /**
2
- * Ralph workflow for OpenCode — plan → orchestrate → review → debug loop.
2
+ * Ralph workflow for OpenCode — plan → orchestrate → review loop.
3
3
  *
4
4
  * Each sub-agent invocation spawns its own visible session in the graph,
5
5
  * so users can see each iteration's progress in real time. The loop
6
6
  * terminates when:
7
7
  * - `max_loops` iterations have completed (defaults to {@link DEFAULT_MAX_LOOPS}), OR
8
- * - Two parallel reviewer passes both return zero findings.
8
+ * - Both parallel reviewer passes return `overall_correctness === "patch is correct"`.
9
+ *
10
+ * On a failed review the merged findings are formatted into a markdown
11
+ * brief by {@link formatReviewForReplan} and fed into the next iteration's
12
+ * planner, which is responsible for validating, deduping, and clustering
13
+ * them into shared root causes before revising the RFC.
9
14
  *
10
15
  * The reviewer stages use the OpenCode SDK's structured output
11
16
  * (`format: { type: "json_schema" }`) to guarantee the review result
@@ -21,9 +26,8 @@ import {
21
26
  buildOrchestratorPrompt,
22
27
  buildInfraDiscoveryPrompts,
23
28
  buildReviewPrompt,
24
- buildDebuggerReportPrompt,
25
- extractMarkdownBlock,
26
29
  filterActionable,
30
+ formatReviewForReplan,
27
31
  mergeReviewResults,
28
32
  REVIEW_RESULT_JSON_SCHEMA,
29
33
  type ReviewResult,
@@ -71,8 +75,7 @@ function extractReview(
71
75
 
72
76
  export default defineWorkflow({
73
77
  name: "ralph",
74
- description:
75
- "Plan → orchestrate → review → debug loop with bounded iteration",
78
+ description: "Plan → orchestrate → review loop with bounded iteration",
76
79
  inputs: [
77
80
  { name: "prompt", type: "text", required: true, description: "task prompt" },
78
81
  {
@@ -87,7 +90,7 @@ export default defineWorkflow({
87
90
  .run(async (ctx) => {
88
91
  const prompt = ctx.inputs.prompt ?? "";
89
92
  const maxLoops = ctx.inputs.max_loops ?? DEFAULT_MAX_LOOPS;
90
- let debuggerReport = "";
93
+ let reviewReport = "";
91
94
 
92
95
  for (let iteration = 1; iteration <= maxLoops; iteration++) {
93
96
  // ── Plan ────────────────────────────────────────────────────────────
@@ -103,7 +106,7 @@ export default defineWorkflow({
103
106
  type: "text",
104
107
  text: buildPlannerPrompt(prompt, {
105
108
  iteration,
106
- debuggerReport: debuggerReport || undefined,
109
+ reviewReport: reviewReport || undefined,
107
110
  }),
108
111
  },
109
112
  ],
@@ -235,33 +238,8 @@ export default defineWorkflow({
235
238
  // Both reviewers agree the code is clean → done
236
239
  if (!hasActionableFindings(parsed, reviewRaw)) break;
237
240
 
238
- // ── Debug (only if another iteration is allowed) ────────────────────
239
- if (iteration < maxLoops) {
240
- const debugger_ = await ctx.stage(
241
- { name: `debugger-${iteration}` },
242
- {},
243
- { title: `debugger-${iteration}` },
244
- async (s) => {
245
- const result = await s.client.session.prompt({
246
- sessionID: s.session.id,
247
- parts: [
248
- {
249
- type: "text",
250
- text: buildDebuggerReportPrompt(parsed, reviewRaw, {
251
- iteration,
252
- changeset,
253
- }),
254
- },
255
- ],
256
- agent: "debugger",
257
- });
258
- s.save(result.data!);
259
- return extractResponseText(result.data!.parts);
260
- },
261
- );
262
-
263
- debuggerReport = extractMarkdownBlock(debugger_.result);
264
- }
241
+ // Findings exist format them for the next iteration's planner.
242
+ reviewReport = formatReviewForReplan(parsed, reviewRaw);
265
243
  }
266
244
  })
267
245
  .compile();
@@ -68,6 +68,7 @@ export {
68
68
  capturePaneVisible,
69
69
  capturePaneScrollback,
70
70
  killSession,
71
+ killSessionOnPaneExit,
71
72
  killWindow,
72
73
  sessionExists,
73
74
  listSessions,