aiwcli 0.11.0 → 0.11.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 (25) hide show
  1. package/dist/templates/_shared/hooks-ts/session_end.ts +4 -1
  2. package/dist/templates/_shared/lib-ts/base/git-state.ts +1 -1
  3. package/dist/templates/_shared/lib-ts/base/logger.ts +15 -18
  4. package/dist/templates/_shared/lib-ts/base/subprocess-utils.ts +18 -15
  5. package/dist/templates/_shared/lib-ts/context/plan-manager.ts +26 -30
  6. package/dist/templates/_shared/lib-ts/handoff/handoff-reader.ts +12 -13
  7. package/dist/templates/_shared/scripts/resume_handoff.ts +62 -38
  8. package/dist/templates/_shared/scripts/save_handoff.ts +24 -24
  9. package/dist/templates/_shared/scripts/status_line.ts +101 -147
  10. package/dist/templates/cc-native/_cc-native/hooks/cc-native-plan-review.ts +239 -133
  11. package/dist/templates/cc-native/_cc-native/lib-ts/aggregate-agents.ts +11 -12
  12. package/dist/templates/cc-native/_cc-native/lib-ts/artifacts.ts +139 -56
  13. package/dist/templates/cc-native/_cc-native/lib-ts/cli-output-parser.ts +22 -2
  14. package/dist/templates/cc-native/_cc-native/lib-ts/corroboration.ts +115 -0
  15. package/dist/templates/cc-native/_cc-native/lib-ts/index.ts +1 -0
  16. package/dist/templates/cc-native/_cc-native/lib-ts/json-parser.ts +7 -1
  17. package/dist/templates/cc-native/_cc-native/lib-ts/orchestrator.ts +5 -4
  18. package/dist/templates/cc-native/_cc-native/lib-ts/reviewers/agent.ts +133 -13
  19. package/dist/templates/cc-native/_cc-native/lib-ts/reviewers/codex.ts +6 -6
  20. package/dist/templates/cc-native/_cc-native/lib-ts/reviewers/gemini.ts +5 -4
  21. package/dist/templates/cc-native/_cc-native/lib-ts/state.ts +30 -33
  22. package/dist/templates/cc-native/_cc-native/lib-ts/types.ts +118 -43
  23. package/dist/templates/cc-native/_cc-native/plan-review.config.json +21 -0
  24. package/oclif.manifest.json +1 -1
  25. package/package.json +2 -2
@@ -73,6 +73,7 @@ export {
73
73
 
74
74
  // Iteration state
75
75
  export {
76
+ DEFAULT_REVIEW_ITERATIONS,
76
77
  deleteState,
77
78
  getIterationState,
78
79
  getStateFilePath,
@@ -73,6 +73,12 @@ export function parseJsonMaybe(
73
73
  `Parsed JSON (${parseMethod}) missing/empty fields: ${JSON.stringify(missing)}`,
74
74
  );
75
75
  logDebug("parse", `Keys present: ${JSON.stringify(Object.keys(obj))}`);
76
+ // Heuristic extraction grabbed the wrong object — reject it.
77
+ // Strict parse still returns partial objects (caller handles defaults).
78
+ if (parseMethod === "heuristic") {
79
+ logWarn("parse", "Rejecting heuristic result due to missing required fields");
80
+ return null;
81
+ }
76
82
  }
77
83
  }
78
84
 
@@ -137,7 +143,7 @@ export function coerceToReview(
137
143
  "coerce",
138
144
  `verdict=${obj.verdict}, issues_count=${Array.isArray(obj.issues) ? (obj.issues as unknown[]).length : 0}`,
139
145
  );
140
- }
146
+ }
141
147
 
142
148
  if (!obj.issues) {
143
149
  logDebug("coerce", "issues array empty or missing");
@@ -3,11 +3,11 @@
3
3
  * See cc-native-plan-review-spec.md §4.8
4
4
  */
5
5
 
6
+ import { logDebug, logInfo, logWarn, logError } from "../../_shared/lib-ts/base/logger.js";
7
+ import { getInternalSubprocessEnv, findExecutable, execFileAsync } from "../../_shared/lib-ts/base/subprocess-utils.js";
6
8
  import { parseCliOutput } from "./cli-output-parser.js";
7
- import type { AgentConfig, ComplexityCategory, OrchestratorConfig, OrchestratorResult } from "./types.js";
9
+ import type { AgentConfig, OrchestratorConfig, OrchestratorResult, ComplexityCategory } from "./types.js";
8
10
  import { ORCHESTRATOR_SCHEMA } from "./types.js";
9
- import { logDebug, logError, logInfo, logWarn } from "../../_shared/lib-ts/base/logger.js";
10
- import { execFileAsync, findExecutable, getInternalSubprocessEnv } from "../../_shared/lib-ts/base/subprocess-utils.js";
11
11
 
12
12
  // ---------------------------------------------------------------------------
13
13
  // Constants
@@ -175,6 +175,7 @@ Call StructuredOutput now with: complexity, category, selectedAgents, reasoning`
175
175
  timeout: config.timeout * 1000,
176
176
  env: env as Record<string, string>,
177
177
  maxBuffer: 10 * 1024 * 1024,
178
+ shell: process.platform === "win32",
178
179
  });
179
180
 
180
181
  if (result.killed || result.signal === "SIGTERM") {
@@ -211,7 +212,7 @@ Call StructuredOutput now with: complexity, category, selectedAgents, reasoning`
211
212
  let category = (obj.category as string) ?? "code";
212
213
  if (!categories.includes(category)) category = "code";
213
214
 
214
- let {selectedAgents} = obj;
215
+ let selectedAgents = obj.selectedAgents;
215
216
  if (!Array.isArray(selectedAgents)) selectedAgents = [];
216
217
 
217
218
  const reasoning = String(obj.reasoning ?? "").trim() || "No reasoning provided";
@@ -1,23 +1,24 @@
1
1
  /**
2
- * Claude Code agent-based plan reviewer.
3
- * Uses --system-prompt with agent persona for specialized review.
2
+ * Agent-based plan reviewer with multi-provider support.
3
+ * Routes to Claude or Codex CLI based on agent.provider field.
4
4
  * See cc-native-plan-review-spec.md §4.10
5
5
  */
6
6
 
7
- import * as _path from "node:path";
8
-
9
- import { logDebug, logError, logInfo, logWarn } from "../../../_shared/lib-ts/base/logger.js";
10
- import { execFileAsync, findExecutable, getInternalSubprocessEnv } from "../../../_shared/lib-ts/base/subprocess-utils.js";
7
+ import * as fs from "node:fs";
8
+ import * as os from "node:os";
9
+ import * as path from "node:path";
10
+ import { logDebug, logInfo, logWarn, logError } from "../../../_shared/lib-ts/base/logger.js";
11
+ import { getInternalSubprocessEnv, findExecutable, execFileAsync } from "../../../_shared/lib-ts/base/subprocess-utils.js";
11
12
  import { parseCliOutput } from "../cli-output-parser.js";
13
+ import { parseJsonMaybe, coerceToReview } from "../json-parser.js";
12
14
  import { debugLog, debugRaw } from "../debug.js";
13
- import { coerceToReview } from "../json-parser.js";
14
15
  import type { AgentConfig, ReviewerResult, ReviewOptions } from "../types.js";
15
16
  import { AGENT_REVIEW_PROMPT_PREFIX } from "../types.js";
16
17
  import { makeResult } from "./types.js";
17
18
  import type { Reviewer } from "./types.js";
18
19
 
19
20
  /**
20
- * Agent reviewer — runs a Claude Code instance with a custom persona.
21
+ * Agent reviewer — runs a CLI instance with a custom persona.
21
22
  */
22
23
  export class AgentReviewer implements Reviewer {
23
24
  constructor(private agent: AgentConfig) {}
@@ -39,7 +40,8 @@ export class AgentReviewer implements Reviewer {
39
40
  }
40
41
 
41
42
  /**
42
- * Run a single Claude Code agent to review the plan.
43
+ * Run a single agent to review the plan.
44
+ * Routes to provider-specific implementation based on agent.provider.
43
45
  * Never throws — returns error ReviewerResult on failure.
44
46
  */
45
47
  export async function runAgentReview(
@@ -49,6 +51,24 @@ export async function runAgentReview(
49
51
  timeout: number,
50
52
  contextPath?: string,
51
53
  sessionName = "unknown",
54
+ ): Promise<ReviewerResult> {
55
+ if (agent.provider === "codex") {
56
+ return runAgentReviewCodex(plan, agent, schema, timeout, contextPath, sessionName);
57
+ }
58
+ // Default: Claude (existing implementation)
59
+ return runAgentReviewClaude(plan, agent, schema, timeout, contextPath, sessionName);
60
+ }
61
+
62
+ /**
63
+ * Run a single Claude Code agent to review the plan.
64
+ */
65
+ async function runAgentReviewClaude(
66
+ plan: string,
67
+ agent: AgentConfig,
68
+ schema: Record<string, unknown>,
69
+ timeout: number,
70
+ contextPath?: string,
71
+ sessionName = "unknown",
52
72
  ): Promise<ReviewerResult> {
53
73
  const claudePath = findExecutable("claude");
54
74
  if (!claudePath) {
@@ -82,7 +102,7 @@ ${plan}
82
102
  cmdArgs.push("--system-prompt", fullPrompt);
83
103
  }
84
104
 
85
- logInfo(agent.name, `Running with model: ${agent.model}, timeout: ${timeout}s`);
105
+ logInfo(agent.name, `Running Claude with model: ${agent.model}, timeout: ${timeout}s`);
86
106
 
87
107
  const env = getInternalSubprocessEnv();
88
108
 
@@ -91,10 +111,11 @@ ${plan}
91
111
  timeout: timeout * 1000,
92
112
  env: env as Record<string, string>,
93
113
  maxBuffer: 10 * 1024 * 1024,
114
+ shell: process.platform === "win32",
94
115
  });
95
116
 
96
117
  if (result.killed || result.signal === "SIGTERM") {
97
- logWarn(agent.name, `TIMEOUT after ${timeout}s`);
118
+ logWarn(agent.name, `Claude TIMEOUT after ${timeout}s`);
98
119
  return makeResult(agent.name, false, "error", {}, "", `${agent.name} timed out after ${timeout}s`);
99
120
  }
100
121
 
@@ -116,12 +137,12 @@ ${plan}
116
137
  if (err) {
117
138
  debugRaw(contextPath, sessionName, `agent:${agent.name}`, "stderr", err);
118
139
  }
119
-
120
140
  debugLog(contextPath, sessionName, `agent:${agent.name}`, "subprocess_info", {
121
141
  exit_code: result.exitCode,
122
142
  stdout_len: raw.length,
123
143
  stderr_len: err.length,
124
144
  model: agent.model,
145
+ provider: "claude",
125
146
  timeout,
126
147
  });
127
148
  }
@@ -146,10 +167,109 @@ ${plan}
146
167
  }
147
168
 
148
169
  const [ok, verdict, norm] = coerceToReview(
149
- obj as null | Record<string, unknown>,
170
+ obj as Record<string, unknown> | null,
150
171
  "Retry or check agent configuration.",
151
172
  );
152
173
 
153
174
  return makeResult(agent.name, ok, verdict, norm, raw, err);
154
175
  }
155
176
 
177
+ /**
178
+ * Run a single Codex CLI agent to review the plan.
179
+ * Adapts the codex.ts reviewer pattern for agent-based review with persona.
180
+ */
181
+ async function runAgentReviewCodex(
182
+ plan: string,
183
+ agent: AgentConfig,
184
+ schema: Record<string, unknown>,
185
+ timeout: number,
186
+ contextPath?: string,
187
+ sessionName = "unknown",
188
+ ): Promise<ReviewerResult> {
189
+ const codexPath = findExecutable("codex");
190
+ if (!codexPath) {
191
+ logWarn(agent.name, "Codex CLI not found on PATH, skipping");
192
+ return makeResult(agent.name, false, "skip", {}, "", "codex CLI not found on PATH");
193
+ }
194
+
195
+ // Codex has no --system-prompt flag, so we prepend the agent persona to stdin.
196
+ const fullPrompt = [
197
+ AGENT_REVIEW_PROMPT_PREFIX,
198
+ "---",
199
+ agent.system_prompt || "",
200
+ "---",
201
+ `Return ONLY a JSON object matching this schema:\n${JSON.stringify(schema)}`,
202
+ "",
203
+ "PLAN:",
204
+ "<<<",
205
+ plan,
206
+ ">>>",
207
+ ].join("\n\n");
208
+
209
+ const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), `codex-agent-${agent.name}-`));
210
+
211
+ try {
212
+ const schemaPath = path.join(tmpDir, "schema.json");
213
+ const outPath = path.join(tmpDir, "output.json");
214
+ fs.writeFileSync(schemaPath, JSON.stringify(schema, null, 2), "utf-8");
215
+
216
+ const cmdArgs = ["exec", "--sandbox", "read-only"];
217
+ if (agent.model) cmdArgs.push("--model", agent.model);
218
+ cmdArgs.push("--output-schema", schemaPath, "-o", outPath, "-");
219
+
220
+ logInfo(agent.name, `Running Codex with model: ${agent.model}, timeout: ${timeout}s`);
221
+
222
+ const result = await execFileAsync(codexPath, cmdArgs, {
223
+ input: fullPrompt,
224
+ timeout: timeout * 1000,
225
+ maxBuffer: 10 * 1024 * 1024,
226
+ shell: process.platform === "win32",
227
+ });
228
+
229
+ if (result.killed || result.signal === "SIGTERM") {
230
+ logWarn(agent.name, `Codex TIMEOUT after ${timeout}s`);
231
+ return makeResult(agent.name, false, "error", {}, "", `${agent.name} (codex) timed out after ${timeout}s`);
232
+ }
233
+
234
+ if (!result.stdout && !result.stderr && !fs.existsSync(outPath) && result.exitCode !== 0) {
235
+ logError(agent.name, `Codex exited with code ${result.exitCode} and no output`);
236
+ return makeResult(agent.name, false, "error", {}, "", `${agent.name} (codex) failed (exit ${result.exitCode})`);
237
+ }
238
+
239
+ // Read output: prefer temp file, fallback to stdout
240
+ let raw = "";
241
+ if (fs.existsSync(outPath)) {
242
+ raw = fs.readFileSync(outPath, "utf-8");
243
+ }
244
+ const err = result.stderr.trim();
245
+
246
+ // Debug logging
247
+ if (contextPath) {
248
+ debugRaw(contextPath, sessionName, `agent:${agent.name}`, "stdout", raw || result.stdout);
249
+ if (err) debugRaw(contextPath, sessionName, `agent:${agent.name}`, "stderr", err);
250
+ debugLog(contextPath, sessionName, `agent:${agent.name}`, "subprocess_info", {
251
+ exit_code: result.exitCode,
252
+ stdout_len: (raw || result.stdout).length,
253
+ stderr_len: err.length,
254
+ model: agent.model,
255
+ provider: "codex",
256
+ timeout,
257
+ });
258
+ }
259
+
260
+ // Parse output
261
+ const obj = parseJsonMaybe(raw) ?? parseJsonMaybe(result.stdout);
262
+ const [ok, verdict, norm] = coerceToReview(
263
+ obj,
264
+ "Retry or check Codex CLI auth/config.",
265
+ );
266
+
267
+ return makeResult(agent.name, ok, verdict, norm, raw || result.stdout, err);
268
+ } finally {
269
+ try {
270
+ fs.rmSync(tmpDir, { recursive: true, force: true });
271
+ } catch (e) {
272
+ logDebug(agent.name, `Failed to cleanup temp dir ${tmpDir}: ${e}`);
273
+ }
274
+ }
275
+ }
@@ -7,10 +7,9 @@
7
7
  import * as fs from "node:fs";
8
8
  import * as os from "node:os";
9
9
  import * as path from "node:path";
10
-
11
- import { logDebug, logError, logInfo as _logInfo, logWarn } from "../../../_shared/lib-ts/base/logger.js";
12
- import { execFileAsync, findExecutable } from "../../../_shared/lib-ts/base/subprocess-utils.js";
13
- import { coerceToReview, parseJsonMaybe } from "../json-parser.js";
10
+ import { logDebug, logInfo, logWarn, logError } from "../../../_shared/lib-ts/base/logger.js";
11
+ import { findExecutable, execFileAsync } from "../../../_shared/lib-ts/base/subprocess-utils.js";
12
+ import { parseJsonMaybe, coerceToReview } from "../json-parser.js";
14
13
  import { REVIEW_PROMPT_PREFIX } from "../types.js";
15
14
  import type { ReviewerResult, ReviewOptions } from "../types.js";
16
15
  import { makeResult } from "./types.js";
@@ -29,7 +28,7 @@ export class CodexReviewer implements Reviewer {
29
28
  async review(
30
29
  plan: string,
31
30
  schema: Record<string, unknown>,
32
- _options: ReviewOptions,
31
+ options: ReviewOptions,
33
32
  ): Promise<ReviewerResult> {
34
33
  return runCodexReview(plan, schema, this.settings);
35
34
  }
@@ -91,6 +90,7 @@ ${plan}
91
90
  input: prompt,
92
91
  timeout: timeout * 1000,
93
92
  maxBuffer: 10 * 1024 * 1024,
93
+ shell: process.platform === "win32",
94
94
  });
95
95
 
96
96
  if (result.killed || result.signal === "SIGTERM") {
@@ -107,7 +107,7 @@ ${plan}
107
107
 
108
108
  let raw = "";
109
109
  if (fs.existsSync(outPath)) {
110
- raw = fs.readFileSync(outPath, "utf8");
110
+ raw = fs.readFileSync(outPath, "utf-8");
111
111
  }
112
112
 
113
113
  const obj = parseJsonMaybe(raw) ?? parseJsonMaybe(result.stdout);
@@ -4,9 +4,9 @@
4
4
  * See cc-native-plan-review-spec.md §4.12
5
5
  */
6
6
 
7
- import { logDebug, logError, logInfo as _logInfo, logWarn } from "../../../_shared/lib-ts/base/logger.js";
8
- import { execFileAsync, findExecutable } from "../../../_shared/lib-ts/base/subprocess-utils.js";
9
- import { coerceToReview, parseJsonMaybe } from "../json-parser.js";
7
+ import { logDebug, logInfo, logWarn, logError } from "../../../_shared/lib-ts/base/logger.js";
8
+ import { findExecutable, execFileAsync } from "../../../_shared/lib-ts/base/subprocess-utils.js";
9
+ import { parseJsonMaybe, coerceToReview } from "../json-parser.js";
10
10
  import type { ReviewerResult, ReviewOptions } from "../types.js";
11
11
  import { makeResult } from "./types.js";
12
12
  import type { Reviewer } from "./types.js";
@@ -24,7 +24,7 @@ export class GeminiReviewer implements Reviewer {
24
24
  async review(
25
25
  plan: string,
26
26
  schema: Record<string, unknown>,
27
- _options: ReviewOptions,
27
+ options: ReviewOptions,
28
28
  ): Promise<ReviewerResult> {
29
29
  return runGeminiReview(plan, schema, this.settings);
30
30
  }
@@ -78,6 +78,7 @@ ${JSON.stringify(schema)}
78
78
  input: plan,
79
79
  timeout: timeout * 1000,
80
80
  maxBuffer: 10 * 1024 * 1024,
81
+ shell: process.platform === "win32",
81
82
  });
82
83
 
83
84
  if (result.killed || result.signal === "SIGTERM") {
@@ -5,13 +5,12 @@
5
5
  */
6
6
 
7
7
  import * as fs from "node:fs";
8
- import * as path from "node:path";
9
-
10
- import { validatePlanPath } from "./constants.js";
11
- import type { IterationEntry, IterationState } from "./types.js";
8
+ import * as path from "node:path";
12
9
  import { atomicWrite } from "../../_shared/lib-ts/base/atomic-write.js";
13
- import { logError, logInfo, logWarn } from "../../_shared/lib-ts/base/logger.js";
10
+ import { logInfo, logWarn, logError } from "../../_shared/lib-ts/base/logger.js";
14
11
  import { nowIso } from "../../_shared/lib-ts/base/utils.js";
12
+ import { validatePlanPath } from "./constants.js";
13
+ import type { IterationState, IterationEntry } from "./types.js";
15
14
 
16
15
  // ---------------------------------------------------------------------------
17
16
  // Constants
@@ -19,10 +18,10 @@ import { nowIso } from "../../_shared/lib-ts/base/utils.js";
19
18
 
20
19
  const STATE_SCHEMA_VERSION = "1.0.0";
21
20
 
22
- const DEFAULT_REVIEW_ITERATIONS: Record<string, number> = {
21
+ export const DEFAULT_REVIEW_ITERATIONS: Record<string, number> = {
23
22
  simple: 1,
24
- medium: 1,
25
- high: 2,
23
+ medium: 3,
24
+ high: 5,
26
25
  };
27
26
 
28
27
  // ---------------------------------------------------------------------------
@@ -44,7 +43,7 @@ export function getStateFilePath(planPath: string): string {
44
43
  /**
45
44
  * Load state file with schema validation and migration.
46
45
  */
47
- export function loadState(planPath: string): null | Record<string, unknown> {
46
+ export function loadState(planPath: string): Record<string, unknown> | null {
48
47
  try {
49
48
  const stateFile = getStateFilePath(planPath);
50
49
 
@@ -53,7 +52,7 @@ export function loadState(planPath: string): null | Record<string, unknown> {
53
52
  }
54
53
 
55
54
  const state = JSON.parse(
56
- fs.readFileSync(stateFile, "utf8"),
55
+ fs.readFileSync(stateFile, "utf-8"),
57
56
  ) as Record<string, unknown>;
58
57
 
59
58
  // Handle schema version (backward compatible)
@@ -73,13 +72,12 @@ export function loadState(planPath: string): null | Record<string, unknown> {
73
72
  }
74
73
 
75
74
  return state;
76
- } catch (error: unknown) {
77
- if (error instanceof Error && error.message.includes("Invalid plan path")) {
78
- logError("state", `SECURITY: Invalid plan path: ${error}`);
75
+ } catch (e: unknown) {
76
+ if (e instanceof Error && e.message.includes("Invalid plan path")) {
77
+ logError("state", `SECURITY: Invalid plan path: ${e}`);
79
78
  } else {
80
- logError("state", `Failed to load state: ${error}`);
81
- }
82
-
79
+ logError("state", `Failed to load state: ${e}`);
80
+ }
83
81
  return null;
84
82
  }
85
83
  }
@@ -111,13 +109,12 @@ export function saveStateToPlan(
111
109
  }
112
110
 
113
111
  return true;
114
- } catch (error: unknown) {
115
- if (error instanceof Error && error.message.includes("Invalid plan path")) {
116
- logError("state", `SECURITY: Invalid plan path: ${error}`);
112
+ } catch (e: unknown) {
113
+ if (e instanceof Error && e.message.includes("Invalid plan path")) {
114
+ logError("state", `SECURITY: Invalid plan path: ${e}`);
117
115
  } else {
118
- logError("state", String(error));
119
- }
120
-
116
+ logError("state", String(e));
117
+ }
121
118
  return false;
122
119
  }
123
120
  }
@@ -132,16 +129,14 @@ export function deleteState(planPath: string): boolean {
132
129
  if (fs.existsSync(stateFile)) {
133
130
  fs.unlinkSync(stateFile);
134
131
  logInfo("state", `Deleted state file: ${stateFile}`);
135
- }
136
-
132
+ }
137
133
  return true;
138
- } catch (error: unknown) {
139
- if (error instanceof Error && error.message.includes("Invalid plan path")) {
140
- logError("state", `SECURITY: Invalid plan path in delete: ${error}`);
134
+ } catch (e: unknown) {
135
+ if (e instanceof Error && e.message.includes("Invalid plan path")) {
136
+ logError("state", `SECURITY: Invalid plan path in delete: ${e}`);
141
137
  return false;
142
- }
143
-
144
- logWarn("state", `Failed to delete state file: ${error}`);
138
+ }
139
+ logWarn("state", `Failed to delete state file: ${e}`);
145
140
  return false;
146
141
  }
147
142
  }
@@ -178,6 +173,9 @@ export function getIterationState(
178
173
  max: reviewIterations[complexity] ?? 1,
179
174
  complexity,
180
175
  history: [],
176
+ graduated: [],
177
+ passStreaks: {},
178
+ lastPlanHash: "",
181
179
  };
182
180
  }
183
181
 
@@ -209,7 +207,7 @@ export function shouldContinueIterating(
209
207
  verdict: string,
210
208
  config?: Record<string, unknown>,
211
209
  ): boolean {
212
- const {current} = iteration;
210
+ const current = iteration.current;
213
211
  const maxIter = iteration.max;
214
212
 
215
213
  // At or past max iterations
@@ -225,8 +223,7 @@ export function shouldContinueIterating(
225
223
  let earlyExit = true;
226
224
  if (config) {
227
225
  earlyExit = (config.earlyExitOnAllPass as boolean) ?? true;
228
- }
229
-
226
+ }
230
227
  if (earlyExit && verdict === "pass") {
231
228
  logInfo(
232
229
  "state",