@bastani/atomic 0.5.30-0 → 0.5.31-0

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.
@@ -271,6 +271,13 @@ export declare class ClaudeSessionWrapper {
271
271
  * `claude` CLI binary, not the SDK — so they are silently ignored.
272
272
  */
273
273
  query(prompt: string, _options?: Partial<SDKOptions>): Promise<SessionMessage[]>;
274
+ /**
275
+ * Structured output is only produced by the Agent SDK's `result` message,
276
+ * which interactive stages don't consume (they drive the `claude` CLI via
277
+ * tmux, not the SDK). Always `undefined` here — pair `outputFormat` with a
278
+ * headless stage to read {@link HeadlessClaudeSessionWrapper#lastStructuredOutput}.
279
+ */
280
+ get lastStructuredOutput(): unknown;
274
281
  /** Noop — for API symmetry with CopilotSession.disconnect(). */
275
282
  disconnect(): Promise<void>;
276
283
  }
@@ -328,7 +335,17 @@ export declare class HeadlessClaudeSessionWrapper {
328
335
  * Claude stages run in parallel (each call gets its own SDK-assigned UUID).
329
336
  */
330
337
  private _lastSessionId;
338
+ /**
339
+ * Validated structured output captured from the most recent `query()`'s
340
+ * `result` message. Populated only when callers pass
341
+ * `options.outputFormat = { type: "json_schema", schema }` and the SDK
342
+ * produced a `subtype: "success"` result with `structured_output` attached.
343
+ * Remains `undefined` on plain text runs or when the SDK fails validation
344
+ * (`error_max_structured_output_retries`).
345
+ */
346
+ private _lastStructuredOutput;
331
347
  get sessionId(): string;
348
+ get lastStructuredOutput(): unknown;
332
349
  query(prompt: string | AsyncIterable<SDKUserMessage>, options?: Partial<SDKOptions>): Promise<SessionMessage[]>;
333
350
  disconnect(): Promise<void>;
334
351
  }
@@ -1 +1 @@
1
- {"version":3,"file":"claude.d.ts","sourceRoot":"","sources":["../../../src/sdk/providers/claude.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"claude.d.ts","sourceRoot":"","sources":["../../../src/sdk/providers/claude.ts"],"names":[],"mappings":"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"}
@@ -7,13 +7,13 @@
7
7
  * - `max_loops` iterations have completed (defaults to {@link DEFAULT_MAX_LOOPS}), OR
8
8
  * - Two parallel reviewer passes both return zero findings.
9
9
  *
10
- * The reviewer stages run the `reviewer` sub-agent in a visible TUI via the
11
- * `--agent reviewer` chatFlag, then parse the JSON review out of the
12
- * assistant text with {@link parseReviewResult}. The prompt enumerates the
13
- * {@link ReviewResultSchema} fields so the model emits matching JSON. We
14
- * deliberately avoid invoking the Claude Agent SDK's `query()` from inside a
15
- * non-headless stage that would spawn a TUI pane that goes unused while
16
- * the SDK runs in-process (see workflow-creator skill, failure-modes F17).
10
+ * The reviewer stages run **headless** via the Claude Agent SDK with
11
+ * `outputFormat: { type: "json_schema", schema: REVIEW_RESULT_JSON_SCHEMA }`,
12
+ * so the SDK validates {@link ReviewResultSchema} before returning. The
13
+ * validated object is read from `s.session.lastStructuredOutput` no text
14
+ * parsing required. Running the reviewers headless (no tmux pane) keeps the
15
+ * graph focused on stages the user cares about and lets the SDK enforce the
16
+ * schema without TUI round-trips.
17
17
  *
18
18
  * Run: atomic workflow -n ralph -a claude "<your spec>"
19
19
  */
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../../src/sdk/workflows/builtin/ralph/claude/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;;;;;;;;;;;;AAmCH,wBAyMa"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../../src/sdk/workflows/builtin/ralph/claude/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;;;;;;;;;;;;AA+CH,wBAuMa"}
@@ -45,7 +45,15 @@ export declare const ReviewResultSchema: z.ZodObject<{
45
45
  overall_explanation: z.ZodString;
46
46
  overall_confidence_score: z.ZodOptional<z.ZodNumber>;
47
47
  }, z.core.$strip>;
48
- /** JSON Schema derived from the Zod schema — used by Claude and OpenCode SDKs. */
48
+ /**
49
+ * JSON Schema derived from the Zod schema — used by Claude and OpenCode SDKs.
50
+ *
51
+ * `target: "openapi-3.0"` drops the `$schema` draft URL that Zod stamps
52
+ * by default. The Claude Agent SDK's validator silently drops
53
+ * `structured_output` when that metadata field is present, so we emit
54
+ * the OpenAPI-flavoured variant which matches the hand-written shape in
55
+ * the SDK's structured-output guide.
56
+ */
49
57
  export declare const REVIEW_RESULT_JSON_SCHEMA: z.core.ZodStandardJSONSchemaPayload<z.ZodObject<{
50
58
  findings: z.ZodArray<z.ZodObject<{
51
59
  title: z.ZodString;
@@ -74,10 +82,12 @@ export interface StructuredReviewResult {
74
82
  /**
75
83
  * Merge two parallel reviewer results into one.
76
84
  *
77
- * Two independent reviewers run the same prompt simultaneously. This function
78
- * unions their findings and picks the more conservative overall_correctness.
79
- * When either reviewer's structured output is unavailable, it falls back to
80
- * text parsing ({@link parseReviewResult}) before merging.
85
+ * Each SDK enforces {@link ReviewResultSchema} at the provider level (Claude
86
+ * `outputFormat`, OpenCode `format: json_schema`, Copilot `defineTool`), so a
87
+ * non-null `structured` is already a validated {@link ReviewResult}. When
88
+ * either reviewer failed to produce validated output we propagate `null` —
89
+ * {@link hasActionableFindings} then treats the raw response as actionable so
90
+ * the loop keeps iterating instead of silently exiting on a missing reviewer.
81
91
  */
82
92
  export declare function mergeReviewResults(a: StructuredReviewResult, b: StructuredReviewResult): StructuredReviewResult;
83
93
  export interface PlannerContext {
@@ -215,16 +225,6 @@ export interface DebuggerContext {
215
225
  * planner consumes.
216
226
  */
217
227
  export declare function buildDebuggerReportPrompt(review: ReviewResult | null, rawReview: string, context: DebuggerContext): string;
218
- /**
219
- * Parse the reviewer's JSON output. Tries, in order:
220
- * 1. Direct JSON.parse on the entire content.
221
- * 2. The LAST fenced ```json (or unlabelled) code block.
222
- * 3. The LAST balanced object containing a "findings" key in surrounding prose.
223
- *
224
- * Filters out P3 (minor/style) findings — only P0/P1/P2 count as actionable.
225
- * Returns null when no parse strategy succeeds.
226
- */
227
- export declare function parseReviewResult(content: string): ReviewResult | null;
228
228
  export declare function filterActionable(parsed: {
229
229
  findings: ReviewFinding[];
230
230
  overall_correctness: string;
@@ -1 +1 @@
1
- {"version":3,"file":"prompts.d.ts","sourceRoot":"","sources":["../../../../../../src/sdk/workflows/builtin/ralph/helpers/prompts.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"prompts.d.ts","sourceRoot":"","sources":["../../../../../../src/sdk/workflows/builtin/ralph/helpers/prompts.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../../src/sdk/workflows/builtin/ralph/opencode/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;;;;;;;;;;;;AAqDH,wBAmMa"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../../src/sdk/workflows/builtin/ralph/opencode/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;;;;;;;;;;;;AAyDH,wBAmMa"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bastani/atomic",
3
- "version": "0.5.30-0",
3
+ "version": "0.5.31-0",
4
4
  "description": "Configuration management CLI and SDK for coding agents",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -61,7 +61,7 @@
61
61
  "typecheck": "bunx tsc --noEmit && bunx tsc -p tests --noEmit",
62
62
  "lint": "oxlint --config=oxlint.json src",
63
63
  "lint:fix": "oxlint --config=oxlint.json --fix src",
64
- "prepare": "prek install -t pre-commit -t pre-push || true"
64
+ "prepare": "prek install || true"
65
65
  },
66
66
  "devDependencies": {
67
67
  "@j178/prek": "^0.3.10",
@@ -1064,6 +1064,16 @@ export class ClaudeSessionWrapper {
1064
1064
  });
1065
1065
  }
1066
1066
 
1067
+ /**
1068
+ * Structured output is only produced by the Agent SDK's `result` message,
1069
+ * which interactive stages don't consume (they drive the `claude` CLI via
1070
+ * tmux, not the SDK). Always `undefined` here — pair `outputFormat` with a
1071
+ * headless stage to read {@link HeadlessClaudeSessionWrapper#lastStructuredOutput}.
1072
+ */
1073
+ get lastStructuredOutput(): unknown {
1074
+ return undefined;
1075
+ }
1076
+
1067
1077
  /** Noop — for API symmetry with CopilotSession.disconnect(). */
1068
1078
  async disconnect(): Promise<void> {}
1069
1079
  }
@@ -1143,11 +1153,24 @@ export class HeadlessClaudeSessionWrapper {
1143
1153
  * Claude stages run in parallel (each call gets its own SDK-assigned UUID).
1144
1154
  */
1145
1155
  private _lastSessionId: string = "";
1156
+ /**
1157
+ * Validated structured output captured from the most recent `query()`'s
1158
+ * `result` message. Populated only when callers pass
1159
+ * `options.outputFormat = { type: "json_schema", schema }` and the SDK
1160
+ * produced a `subtype: "success"` result with `structured_output` attached.
1161
+ * Remains `undefined` on plain text runs or when the SDK fails validation
1162
+ * (`error_max_structured_output_retries`).
1163
+ */
1164
+ private _lastStructuredOutput: unknown = undefined;
1146
1165
 
1147
1166
  get sessionId(): string {
1148
1167
  return this._lastSessionId;
1149
1168
  }
1150
1169
 
1170
+ get lastStructuredOutput(): unknown {
1171
+ return this._lastStructuredOutput;
1172
+ }
1173
+
1151
1174
  async query(
1152
1175
  prompt: string | AsyncIterable<SDKUserMessage>,
1153
1176
  options?: Partial<SDKOptions>,
@@ -1166,12 +1189,15 @@ export class HeadlessClaudeSessionWrapper {
1166
1189
  };
1167
1190
 
1168
1191
  let sdkSessionId = "";
1192
+ let structuredOutput: unknown = undefined;
1169
1193
  try {
1170
1194
  for await (const msg of sdkQuery({ prompt, options: headlessSdkOpts })) {
1171
1195
  if (msg.type === "result") {
1172
- sdkSessionId = String(
1173
- (msg as Record<string, unknown>).session_id ?? "",
1174
- );
1196
+ const record = msg as Record<string, unknown>;
1197
+ sdkSessionId = String(record.session_id ?? "");
1198
+ if (record.subtype === "success" && "structured_output" in record) {
1199
+ structuredOutput = record.structured_output;
1200
+ }
1175
1201
  }
1176
1202
  }
1177
1203
  } catch (err) {
@@ -1187,6 +1213,7 @@ export class HeadlessClaudeSessionWrapper {
1187
1213
  );
1188
1214
  }
1189
1215
  this._lastSessionId = sdkSessionId;
1216
+ this._lastStructuredOutput = structuredOutput;
1190
1217
  return getSessionMessages(sdkSessionId, { dir: process.cwd() });
1191
1218
  }
1192
1219
 
@@ -7,13 +7,13 @@
7
7
  * - `max_loops` iterations have completed (defaults to {@link DEFAULT_MAX_LOOPS}), OR
8
8
  * - Two parallel reviewer passes both return zero findings.
9
9
  *
10
- * The reviewer stages run the `reviewer` sub-agent in a visible TUI via the
11
- * `--agent reviewer` chatFlag, then parse the JSON review out of the
12
- * assistant text with {@link parseReviewResult}. The prompt enumerates the
13
- * {@link ReviewResultSchema} fields so the model emits matching JSON. We
14
- * deliberately avoid invoking the Claude Agent SDK's `query()` from inside a
15
- * non-headless stage that would spawn a TUI pane that goes unused while
16
- * the SDK runs in-process (see workflow-creator skill, failure-modes F17).
10
+ * The reviewer stages run **headless** via the Claude Agent SDK with
11
+ * `outputFormat: { type: "json_schema", schema: REVIEW_RESULT_JSON_SCHEMA }`,
12
+ * so the SDK validates {@link ReviewResultSchema} before returning. The
13
+ * validated object is read from `s.session.lastStructuredOutput` no text
14
+ * parsing required. Running the reviewers headless (no tmux pane) keeps the
15
+ * graph focused on stages the user cares about and lets the SDK enforce the
16
+ * schema without TUI round-trips.
17
17
  *
18
18
  * Run: atomic workflow -n ralph -a claude "<your spec>"
19
19
  */
@@ -27,8 +27,10 @@ import {
27
27
  buildReviewPrompt,
28
28
  buildDebuggerReportPrompt,
29
29
  extractMarkdownBlock,
30
- parseReviewResult,
30
+ filterActionable,
31
31
  mergeReviewResults,
32
+ REVIEW_RESULT_JSON_SCHEMA,
33
+ type ReviewResult,
32
34
  type StructuredReviewResult,
33
35
  } from "../helpers/prompts.ts";
34
36
  import { hasActionableFindings } from "../helpers/review.ts";
@@ -42,13 +44,23 @@ const DEFAULT_MAX_LOOPS = 10;
42
44
  // timeout is needed.
43
45
 
44
46
  /**
45
- * Extract a {@link StructuredReviewResult} from the reviewer TUI's assistant
46
- * text. {@link parseReviewResult} tolerates surrounding prose and fenced
47
- * code blocks; the prompt instructs the model to emit JSON matching
48
- * {@link ReviewResultSchema}.
47
+ * Turn the SDK's validated structured_output (plus raw transcript text) into a
48
+ * {@link StructuredReviewResult}. When the SDK failed to validate the schema
49
+ * (`error_max_structured_output_retries`) `structured_output` is absent and
50
+ * we propagate `null` so {@link mergeReviewResults} treats the pass as
51
+ * unknown/actionable.
49
52
  */
50
- function extractReview(rawText: string): StructuredReviewResult {
51
- return { structured: parseReviewResult(rawText), raw: rawText };
53
+ function extractReview(
54
+ structuredOutput: unknown,
55
+ rawText: string,
56
+ ): StructuredReviewResult {
57
+ if (structuredOutput && typeof structuredOutput === "object") {
58
+ return {
59
+ structured: filterActionable(structuredOutput as ReviewResult),
60
+ raw: rawText,
61
+ };
62
+ }
63
+ return { structured: null, raw: rawText };
52
64
  }
53
65
 
54
66
  export default defineWorkflow({
@@ -179,41 +191,39 @@ export default defineWorkflow({
179
191
  patternResult.result,
180
192
  ].join("\n\n---\n\n");
181
193
 
182
- // ── Review (two parallel passes) ────────────────────────────────────
194
+ // ── Review (two parallel headless passes with schema enforcement) ──
183
195
  const reviewPrompt = buildReviewPrompt(prompt, {
184
196
  changeset,
185
197
  iteration,
186
198
  discoveryContext,
187
199
  });
188
200
 
189
- const reviewerChatFlags = [
190
- "--agent",
191
- "reviewer",
192
- "--allow-dangerously-skip-permissions",
193
- "--dangerously-skip-permissions",
194
- ];
195
-
196
- const [reviewA, reviewB] = await Promise.all([
201
+ const runReviewer = (name: string) =>
197
202
  ctx.stage(
198
- { name: `reviewer-${iteration}-a` },
199
- { chatFlags: reviewerChatFlags },
203
+ { name, headless: true },
200
204
  {},
201
- async (s) => {
202
- const result = await s.session.query(reviewPrompt);
203
- s.save(s.sessionId);
204
- return extractReview(extractAssistantText(result, 0));
205
- },
206
- ),
207
- ctx.stage(
208
- { name: `reviewer-${iteration}-b` },
209
- { chatFlags: reviewerChatFlags },
210
205
  {},
211
206
  async (s) => {
212
- const result = await s.session.query(reviewPrompt);
207
+ const result = await s.session.query(reviewPrompt, {
208
+ agent: "reviewer",
209
+ permissionMode: "bypassPermissions",
210
+ allowDangerouslySkipPermissions: true,
211
+ outputFormat: {
212
+ type: "json_schema",
213
+ schema: REVIEW_RESULT_JSON_SCHEMA,
214
+ },
215
+ });
213
216
  s.save(s.sessionId);
214
- return extractReview(extractAssistantText(result, 0));
217
+ return extractReview(
218
+ s.session.lastStructuredOutput,
219
+ extractAssistantText(result, 0),
220
+ );
215
221
  },
216
- ),
222
+ );
223
+
224
+ const [reviewA, reviewB] = await Promise.all([
225
+ runReviewer(`reviewer-${iteration}-a`),
226
+ runReviewer(`reviewer-${iteration}-b`),
217
227
  ]);
218
228
 
219
229
  const merged = mergeReviewResults(reviewA.result, reviewB.result);
@@ -78,8 +78,18 @@ export const ReviewResultSchema = z.object({
78
78
  .describe("Overall confidence in the review (0.0–1.0)"),
79
79
  });
80
80
 
81
- /** JSON Schema derived from the Zod schema — used by Claude and OpenCode SDKs. */
82
- export const REVIEW_RESULT_JSON_SCHEMA = z.toJSONSchema(ReviewResultSchema);
81
+ /**
82
+ * JSON Schema derived from the Zod schema — used by Claude and OpenCode SDKs.
83
+ *
84
+ * `target: "openapi-3.0"` drops the `$schema` draft URL that Zod stamps
85
+ * by default. The Claude Agent SDK's validator silently drops
86
+ * `structured_output` when that metadata field is present, so we emit
87
+ * the OpenAPI-flavoured variant which matches the hand-written shape in
88
+ * the SDK's structured-output guide.
89
+ */
90
+ export const REVIEW_RESULT_JSON_SCHEMA = z.toJSONSchema(ReviewResultSchema, {
91
+ target: "openapi-3.0",
92
+ });
83
93
 
84
94
  /** Result from a reviewer stage with structured output support. */
85
95
  export interface StructuredReviewResult {
@@ -92,10 +102,12 @@ export interface StructuredReviewResult {
92
102
  /**
93
103
  * Merge two parallel reviewer results into one.
94
104
  *
95
- * Two independent reviewers run the same prompt simultaneously. This function
96
- * unions their findings and picks the more conservative overall_correctness.
97
- * When either reviewer's structured output is unavailable, it falls back to
98
- * text parsing ({@link parseReviewResult}) before merging.
105
+ * Each SDK enforces {@link ReviewResultSchema} at the provider level (Claude
106
+ * `outputFormat`, OpenCode `format: json_schema`, Copilot `defineTool`), so a
107
+ * non-null `structured` is already a validated {@link ReviewResult}. When
108
+ * either reviewer failed to produce validated output we propagate `null` —
109
+ * {@link hasActionableFindings} then treats the raw response as actionable so
110
+ * the loop keeps iterating instead of silently exiting on a missing reviewer.
99
111
  */
100
112
  export function mergeReviewResults(
101
113
  a: StructuredReviewResult,
@@ -103,38 +115,30 @@ export function mergeReviewResults(
103
115
  ): StructuredReviewResult {
104
116
  const rawCombined = [a.raw, b.raw].filter(Boolean).join("\n\n---\n\n");
105
117
 
106
- // Resolve: prefer structured output, fall back to text parsing
107
- const parsedA =
108
- a.structured ?? (a.raw.trim() ? parseReviewResult(a.raw) : null);
109
- const parsedB =
110
- b.structured ?? (b.raw.trim() ? parseReviewResult(b.raw) : null);
111
-
112
- if (!parsedA && !parsedB) {
118
+ // Conservative: any missing structured output propagate null. Fabricating
119
+ // a "patch is correct" default here is how the loop previously exited after
120
+ // a single iteration when one reviewer's output failed SDK validation.
121
+ if (!a.structured || !b.structured) {
113
122
  return { structured: null, raw: rawCombined };
114
123
  }
115
124
 
116
- const findingsA = parsedA?.findings ?? [];
117
- const findingsB = parsedB?.findings ?? [];
118
-
119
- const correctnessA = parsedA?.overall_correctness ?? "patch is correct";
120
- const correctnessB = parsedB?.overall_correctness ?? "patch is correct";
121
125
  const isIncorrect =
122
- correctnessA === "patch is incorrect" ||
123
- correctnessB === "patch is incorrect";
126
+ a.structured.overall_correctness === "patch is incorrect" ||
127
+ b.structured.overall_correctness === "patch is incorrect";
124
128
 
125
129
  const explanations = [
126
- parsedA?.overall_explanation,
127
- parsedB?.overall_explanation,
128
- ].filter(Boolean) as string[];
130
+ a.structured.overall_explanation,
131
+ b.structured.overall_explanation,
132
+ ].filter((e): e is string => typeof e === "string" && e.length > 0);
129
133
 
130
134
  const confidences = [
131
- parsedA?.overall_confidence_score,
132
- parsedB?.overall_confidence_score,
135
+ a.structured.overall_confidence_score,
136
+ b.structured.overall_confidence_score,
133
137
  ].filter((c): c is number => c !== undefined);
134
138
 
135
139
  return {
136
140
  structured: {
137
- findings: [...findingsA, ...findingsB],
141
+ findings: [...a.structured.findings, ...b.structured.findings],
138
142
  overall_correctness: isIncorrect
139
143
  ? "patch is incorrect"
140
144
  : "patch is correct",
@@ -981,65 +985,6 @@ the "Pitfalls" section entirely if there are none. Begin now.`;
981
985
  // PARSING HELPERS
982
986
  // ============================================================================
983
987
 
984
- /**
985
- * Parse the reviewer's JSON output. Tries, in order:
986
- * 1. Direct JSON.parse on the entire content.
987
- * 2. The LAST fenced ```json (or unlabelled) code block.
988
- * 3. The LAST balanced object containing a "findings" key in surrounding prose.
989
- *
990
- * Filters out P3 (minor/style) findings — only P0/P1/P2 count as actionable.
991
- * Returns null when no parse strategy succeeds.
992
- */
993
- export function parseReviewResult(content: string): ReviewResult | null {
994
- // Strategy 1: direct JSON
995
- try {
996
- const parsed = JSON.parse(content);
997
- if (parsed && parsed.findings && parsed.overall_correctness) {
998
- return filterActionable(parsed);
999
- }
1000
- } catch {
1001
- /* fall through */
1002
- }
1003
-
1004
- // Strategy 2: last fenced code block
1005
- const blockRe = /```(?:json)?\s*\n([\s\S]*?)\n```/g;
1006
- let lastBlock: string | null = null;
1007
- let blockMatch: RegExpExecArray | null;
1008
- while ((blockMatch = blockRe.exec(content)) !== null) {
1009
- if (blockMatch[1]) lastBlock = blockMatch[1];
1010
- }
1011
- if (lastBlock !== null) {
1012
- try {
1013
- const parsed = JSON.parse(lastBlock);
1014
- if (parsed && parsed.findings && parsed.overall_correctness) {
1015
- return filterActionable(parsed);
1016
- }
1017
- } catch {
1018
- /* fall through */
1019
- }
1020
- }
1021
-
1022
- // Strategy 3: last "{...findings...}" object in surrounding prose
1023
- const objRe = /\{[\s\S]*?"findings"[\s\S]*?\}/g;
1024
- let lastObj: string | null = null;
1025
- let objMatch: RegExpExecArray | null;
1026
- while ((objMatch = objRe.exec(content)) !== null) {
1027
- lastObj = objMatch[0];
1028
- }
1029
- if (lastObj !== null) {
1030
- try {
1031
- const parsed = JSON.parse(lastObj);
1032
- if (parsed && parsed.findings && parsed.overall_correctness) {
1033
- return filterActionable(parsed);
1034
- }
1035
- } catch {
1036
- /* nothing more to try */
1037
- }
1038
- }
1039
-
1040
- return null;
1041
- }
1042
-
1043
988
  export function filterActionable(parsed: {
1044
989
  findings: ReviewFinding[];
1045
990
  overall_correctness: string;
@@ -46,15 +46,19 @@ function extractResponseText(
46
46
 
47
47
  /**
48
48
  * Extract a {@link StructuredReviewResult} from an OpenCode prompt response.
49
- * Prefers the SDK's structured_output field; falls back to text extraction.
49
+ *
50
+ * The OpenCode SDK places the SDK-validated structured output on the
51
+ * AssistantMessage as `structured` (see `@opencode-ai/sdk` v2 types.gen.d.ts
52
+ * — AssistantMessage.structured). Returns `structured: null` whenever the
53
+ * SDK did not produce a validated object so {@link mergeReviewResults}
54
+ * treats the pass as unknown/actionable.
50
55
  */
51
56
  function extractReview(
52
57
  data: { info?: Record<string, unknown>; parts: Array<{ type: string; [key: string]: unknown }> },
53
58
  ): StructuredReviewResult {
54
59
  const raw = extractResponseText(data.parts);
55
60
 
56
- // The SDK places validated structured output at data.info.structured_output
57
- const structuredOutput = data.info?.structured_output;
61
+ const structuredOutput = data.info?.structured;
58
62
  if (structuredOutput && typeof structuredOutput === "object") {
59
63
  return {
60
64
  structured: filterActionable(structuredOutput as ReviewResult),