@quinteroac/agents-coding-toolkit 0.1.0-preview → 0.2.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.
Files changed (71) hide show
  1. package/README.md +29 -15
  2. package/package.json +14 -4
  3. package/scaffold/.agents/flow/tmpl_it_000001_progress.example.json +20 -0
  4. package/scaffold/.agents/skills/execute-refactor-item/tmpl_SKILL.md +59 -0
  5. package/scaffold/.agents/skills/plan-refactor/tmpl_SKILL.md +89 -9
  6. package/scaffold/.agents/skills/refine-refactor-plan/tmpl_SKILL.md +30 -0
  7. package/scaffold/.agents/tmpl_state_rules.md +0 -1
  8. package/scaffold/schemas/tmpl_prototype-progress.ts +22 -0
  9. package/scaffold/schemas/tmpl_refactor-execution-progress.ts +16 -0
  10. package/scaffold/schemas/tmpl_refactor-prd.ts +14 -0
  11. package/scaffold/schemas/tmpl_state.ts +1 -0
  12. package/scaffold/schemas/tmpl_test-execution-progress.ts +17 -0
  13. package/schemas/issues.ts +19 -0
  14. package/schemas/prototype-progress.ts +22 -0
  15. package/schemas/refactor-execution-progress.ts +16 -0
  16. package/schemas/refactor-prd.ts +14 -0
  17. package/schemas/state.test.ts +58 -0
  18. package/schemas/state.ts +1 -0
  19. package/schemas/test-execution-progress.ts +17 -0
  20. package/schemas/test-plan.test.ts +1 -1
  21. package/schemas/validate-progress.ts +1 -1
  22. package/schemas/validate-state.ts +1 -1
  23. package/src/cli.test.ts +57 -0
  24. package/src/cli.ts +227 -58
  25. package/src/commands/approve-project-context.ts +13 -6
  26. package/src/commands/approve-prototype.test.ts +427 -0
  27. package/src/commands/approve-prototype.ts +185 -0
  28. package/src/commands/approve-refactor-plan.test.ts +254 -0
  29. package/src/commands/approve-refactor-plan.ts +200 -0
  30. package/src/commands/approve-requirement.test.ts +224 -0
  31. package/src/commands/approve-requirement.ts +75 -16
  32. package/src/commands/approve-test-plan.test.ts +2 -2
  33. package/src/commands/approve-test-plan.ts +21 -7
  34. package/src/commands/create-issue.test.ts +2 -2
  35. package/src/commands/create-project-context.ts +31 -25
  36. package/src/commands/create-prototype.test.ts +488 -18
  37. package/src/commands/create-prototype.ts +185 -63
  38. package/src/commands/create-test-plan.ts +8 -6
  39. package/src/commands/define-refactor-plan.test.ts +208 -0
  40. package/src/commands/define-refactor-plan.ts +96 -0
  41. package/src/commands/define-requirement.ts +15 -9
  42. package/src/commands/execute-automated-fix.test.ts +78 -33
  43. package/src/commands/execute-automated-fix.ts +34 -101
  44. package/src/commands/execute-refactor.test.ts +954 -0
  45. package/src/commands/execute-refactor.ts +332 -0
  46. package/src/commands/execute-test-plan.test.ts +24 -16
  47. package/src/commands/execute-test-plan.ts +29 -55
  48. package/src/commands/flow-config.ts +79 -0
  49. package/src/commands/flow.test.ts +755 -0
  50. package/src/commands/flow.ts +405 -0
  51. package/src/commands/refine-project-context.ts +9 -7
  52. package/src/commands/refine-refactor-plan.test.ts +210 -0
  53. package/src/commands/refine-refactor-plan.ts +95 -0
  54. package/src/commands/refine-requirement.ts +9 -6
  55. package/src/commands/refine-test-plan.test.ts +2 -2
  56. package/src/commands/refine-test-plan.ts +9 -6
  57. package/src/commands/start-iteration.test.ts +52 -0
  58. package/src/commands/start-iteration.ts +5 -0
  59. package/src/commands/write-json.ts +102 -97
  60. package/src/flow-cli.test.ts +18 -0
  61. package/src/force-flag.test.ts +144 -0
  62. package/src/guardrail.test.ts +411 -0
  63. package/src/guardrail.ts +82 -0
  64. package/src/install.test.ts +7 -5
  65. package/src/pack.test.ts +2 -1
  66. package/src/progress-utils.ts +34 -0
  67. package/src/readline.ts +23 -0
  68. package/src/write-json-artifact.ts +33 -0
  69. package/scaffold/.agents/flow/tmpl_README.md +0 -7
  70. package/scaffold/.agents/flow/tmpl_iteration_close_checklist.example.md +0 -11
  71. package/schemas/test-plan.ts +0 -20
@@ -0,0 +1,96 @@
1
+ import {
2
+ buildPrompt,
3
+ invokeAgent,
4
+ loadSkill,
5
+ type AgentInvokeOptions,
6
+ type AgentProvider,
7
+ type AgentResult,
8
+ } from "../agent";
9
+ import { assertGuardrail } from "../guardrail";
10
+ import { readState, writeState } from "../state";
11
+
12
+ export interface DefineRefactorPlanOptions {
13
+ provider: AgentProvider;
14
+ force?: boolean;
15
+ }
16
+
17
+ interface DefineRefactorPlanDeps {
18
+ invokeAgentFn: (options: AgentInvokeOptions) => Promise<AgentResult>;
19
+ loadSkillFn: (projectRoot: string, skillName: string) => Promise<string>;
20
+ nowFn: () => Date;
21
+ }
22
+
23
+ const defaultDeps: DefineRefactorPlanDeps = {
24
+ invokeAgentFn: invokeAgent,
25
+ loadSkillFn: loadSkill,
26
+ nowFn: () => new Date(),
27
+ };
28
+
29
+ export async function runDefineRefactorPlan(
30
+ opts: DefineRefactorPlanOptions,
31
+ deps: Partial<DefineRefactorPlanDeps> = {},
32
+ ): Promise<void> {
33
+ const { provider, force = false } = opts;
34
+ const projectRoot = process.cwd();
35
+ const state = await readState(projectRoot);
36
+ const mergedDeps: DefineRefactorPlanDeps = { ...defaultDeps, ...deps };
37
+
38
+ await assertGuardrail(
39
+ state,
40
+ !state.phases.prototype.prototype_approved,
41
+ "Cannot define refactor plan: phases.prototype.prototype_approved must be true. Complete prototype (all tests passing) first.",
42
+ { force },
43
+ );
44
+
45
+ // Intentional auto-transition: if the user runs this command directly from
46
+ // the prototype phase (prototype_approved === true), we advance to "refactor"
47
+ // so they don't have to manually update the phase. US-001-AC01 says the phase
48
+ // must be "refactor", but accepting "prototype" here is a UX convenience that
49
+ // is safe because prototype_approved is already enforced above.
50
+ if (state.current_phase === "prototype") {
51
+ state.current_phase = "refactor";
52
+ } else if (state.current_phase !== "refactor") {
53
+ await assertGuardrail(
54
+ state,
55
+ true,
56
+ `Cannot define refactor plan: current_phase must be 'prototype' or 'refactor'. Current: '${state.current_phase}'.`,
57
+ { force },
58
+ );
59
+ }
60
+
61
+ const refactorPlan = state.phases.refactor.refactor_plan;
62
+ await assertGuardrail(
63
+ state,
64
+ refactorPlan.status !== "pending",
65
+ `Cannot define refactor plan from status '${refactorPlan.status}'. Expected pending.`,
66
+ { force },
67
+ );
68
+
69
+ const skillBody = await mergedDeps.loadSkillFn(projectRoot, "plan-refactor");
70
+ const prompt = buildPrompt(skillBody, {
71
+ current_iteration: state.current_iteration,
72
+ });
73
+ const result = await mergedDeps.invokeAgentFn({
74
+ provider,
75
+ prompt,
76
+ cwd: projectRoot,
77
+ interactive: true,
78
+ });
79
+
80
+ if (result.exitCode !== 0) {
81
+ throw new Error(`Agent invocation failed with exit code ${result.exitCode}.`);
82
+ }
83
+
84
+ state.phases.refactor.evaluation_report.status = "created";
85
+ state.phases.refactor.evaluation_report.file = `it_${state.current_iteration}_evaluation-report.md`;
86
+ refactorPlan.status = "pending_approval";
87
+ refactorPlan.file = `it_${state.current_iteration}_refactor-plan.md`;
88
+ state.last_updated = mergedDeps.nowFn().toISOString();
89
+ state.updated_by = "nvst:define-refactor-plan";
90
+
91
+ await writeState(projectRoot, state);
92
+
93
+ console.log(
94
+ "Evaluation report and refactor plan created. Refactor plan is pending approval.",
95
+ );
96
+ }
@@ -1,25 +1,31 @@
1
1
  import { buildPrompt, invokeAgent, loadSkill, type AgentProvider } from "../agent";
2
+ import { assertGuardrail } from "../guardrail";
2
3
  import { readState, writeState } from "../state";
3
4
 
4
5
  export interface DefineRequirementOptions {
5
6
  provider: AgentProvider;
7
+ force?: boolean;
6
8
  }
7
9
 
8
10
  export async function runDefineRequirement(opts: DefineRequirementOptions): Promise<void> {
9
- const { provider } = opts;
11
+ const { provider, force = false } = opts;
10
12
  const projectRoot = process.cwd();
11
13
  const state = await readState(projectRoot);
12
14
 
13
- if (state.current_phase !== "define") {
14
- throw new Error("Cannot define requirement: current_phase must be 'define'.");
15
- }
15
+ await assertGuardrail(
16
+ state,
17
+ state.current_phase !== "define",
18
+ "Cannot define requirement: current_phase must be 'define'.",
19
+ { force },
20
+ );
16
21
 
17
22
  const requirementDefinition = state.phases.define.requirement_definition;
18
- if (requirementDefinition.status !== "pending") {
19
- throw new Error(
20
- `Cannot define requirement from status '${requirementDefinition.status}'. Expected pending.`,
21
- );
22
- }
23
+ await assertGuardrail(
24
+ state,
25
+ requirementDefinition.status !== "pending",
26
+ `Cannot define requirement from status '${requirementDefinition.status}'. Expected pending.`,
27
+ { force },
28
+ );
23
29
 
24
30
  const skillBody = await loadSkill(projectRoot, "create-pr-document");
25
31
  const prompt = buildPrompt(skillBody, {
@@ -193,6 +193,66 @@ describe("execute automated-fix", () => {
193
193
  expect(providersUsed).toEqual(["cursor"]);
194
194
  });
195
195
 
196
+ // RI-005: execute-automated-fix is a phase-independent exception to the guardrail system.
197
+ // It must process issues regardless of current_phase because issues can arise and need
198
+ // fixing at any point in the workflow (prototype OR refactor phases).
199
+ test("RI-005: processes open issues regardless of current_phase (phase-independent guardrail exception)", async () => {
200
+ const projectRoot = await createProjectRoot();
201
+ createdRoots.push(projectRoot);
202
+
203
+ // Seed state with current_phase = "refactor", not the typical "prototype"
204
+ await mkdir(join(projectRoot, ".agents", "flow"), { recursive: true });
205
+ const { writeState } = await import("../state");
206
+ await writeState(projectRoot, {
207
+ current_iteration: "000009",
208
+ current_phase: "refactor",
209
+ phases: {
210
+ define: {
211
+ requirement_definition: { status: "approved", file: "it_000009_product-requirement-document.md" },
212
+ prd_generation: { status: "completed", file: "it_000009_PRD.json" },
213
+ },
214
+ prototype: {
215
+ project_context: { status: "created", file: ".agents/PROJECT_CONTEXT.md" },
216
+ test_plan: { status: "created", file: "it_000009_test-plan.md" },
217
+ tp_generation: { status: "created", file: "it_000009_test-plan.json" },
218
+ prototype_build: { status: "created", file: null },
219
+ test_execution: { status: "completed", file: null },
220
+ prototype_approved: true,
221
+ },
222
+ refactor: {
223
+ evaluation_report: { status: "created", file: null },
224
+ refactor_plan: { status: "approved", file: null },
225
+ refactor_execution: { status: "in_progress", file: null },
226
+ changelog: { status: "pending", file: null },
227
+ },
228
+ },
229
+ last_updated: "2026-02-22T00:00:00.000Z",
230
+ updated_by: "seed",
231
+ history: [],
232
+ });
233
+ await writeIssues(projectRoot, "000009", [
234
+ { id: "ISSUE-000009-001", title: "Refactor-phase issue", description: "fix me", status: "open" },
235
+ ]);
236
+
237
+ let invokeCount = 0;
238
+
239
+ await withCwd(projectRoot, async () => {
240
+ await runExecuteAutomatedFix(
241
+ { provider: "codex" },
242
+ {
243
+ loadSkillFn: async () => "debug workflow",
244
+ invokeAgentFn: async () => {
245
+ invokeCount += 1;
246
+ return { exitCode: 0, stdout: "ok", stderr: "" };
247
+ },
248
+ runCommitFn: async () => 0,
249
+ },
250
+ );
251
+ });
252
+
253
+ expect(invokeCount).toBe(1);
254
+ });
255
+
196
256
  test("logs informative message and exits without changes when zero open issues exist", async () => {
197
257
  const projectRoot = await createProjectRoot();
198
258
  createdRoots.push(projectRoot);
@@ -225,7 +285,8 @@ describe("execute automated-fix", () => {
225
285
  expect(logs).toContain("No open issues to process. Exiting without changes.");
226
286
  });
227
287
 
228
- test("defaults --iterations to 1 and leaves remaining open issues untouched", async () => {
288
+ // US-001-AC01: When --iterations is not provided, all open issues are processed
289
+ test("US-001-AC01: processes all open issues when --iterations is not provided", async () => {
229
290
  const projectRoot = await createProjectRoot();
230
291
  createdRoots.push(projectRoot);
231
292
 
@@ -237,6 +298,7 @@ describe("execute automated-fix", () => {
237
298
  ]);
238
299
 
239
300
  let invokeCount = 0;
301
+ const logs: string[] = [];
240
302
 
241
303
  await withCwd(projectRoot, async () => {
242
304
  await runExecuteAutomatedFix(
@@ -248,6 +310,8 @@ describe("execute automated-fix", () => {
248
310
  return { exitCode: 0, stdout: "", stderr: "" };
249
311
  },
250
312
  runCommitFn: async () => 0,
313
+ logFn: (message) => logs.push(message),
314
+ nowFn: () => new Date("2026-02-22T12:00:00.000Z"),
251
315
  },
252
316
  );
253
317
  });
@@ -255,10 +319,14 @@ describe("execute automated-fix", () => {
255
319
  const issuesRaw = await readFile(join(projectRoot, ".agents", "flow", "it_000009_ISSUES.json"), "utf8");
256
320
  const issues = JSON.parse(issuesRaw) as Array<{ id: string; status: string }>;
257
321
 
258
- expect(invokeCount).toBe(1);
322
+ // All 3 open issues should be processed
323
+ expect(invokeCount).toBe(3);
259
324
  expect(issues.find((issue) => issue.id === "ISSUE-000009-001")?.status).toBe("fixed");
260
- expect(issues.find((issue) => issue.id === "ISSUE-000009-002")?.status).toBe("open");
261
- expect(issues.find((issue) => issue.id === "ISSUE-000009-003")?.status).toBe("open");
325
+ expect(issues.find((issue) => issue.id === "ISSUE-000009-002")?.status).toBe("fixed");
326
+ expect(issues.find((issue) => issue.id === "ISSUE-000009-003")?.status).toBe("fixed");
327
+ // AC04: summary reflects all processed issues
328
+ expect(logs).toContain("Summary: Fixed=3 Failed=0");
329
+ expect(logs).toContain("Processed 3 open issue(s) at 2026-02-22T12:00:00.000Z");
262
330
  });
263
331
 
264
332
  test("processes only the first N open issues when --iterations is provided", async () => {
@@ -297,7 +365,7 @@ describe("execute automated-fix", () => {
297
365
  expect(issues.find((issue) => issue.id === "ISSUE-000009-003")?.status).toBe("open");
298
366
  });
299
367
 
300
- test("skips issues with missing required fields and continues processing remaining open issues", async () => {
368
+ test("throws deterministic validation error when issues file contains entries with missing required fields", async () => {
301
369
  const projectRoot = await createProjectRoot();
302
370
  createdRoots.push(projectRoot);
303
371
 
@@ -308,34 +376,11 @@ describe("execute automated-fix", () => {
308
376
  { id: "ISSUE-000009-003", title: "Fixed", description: "skip", status: "fixed" },
309
377
  ]);
310
378
 
311
- const logs: string[] = [];
312
- const prompts: string[] = [];
313
-
314
379
  await withCwd(projectRoot, async () => {
315
- await runExecuteAutomatedFix(
316
- { provider: "codex", iterations: 3 },
317
- {
318
- loadSkillFn: async () => "debug workflow",
319
- invokeAgentFn: async (options) => {
320
- prompts.push(options.prompt);
321
- return { exitCode: 0, stdout: "", stderr: "" };
322
- },
323
- runCommitFn: async () => 0,
324
- logFn: (message) => logs.push(message),
325
- },
380
+ await expect(runExecuteAutomatedFix({ provider: "codex" })).rejects.toThrow(
381
+ "Deterministic validation error: issues schema mismatch in .agents/flow/it_000009_ISSUES.json.",
326
382
  );
327
383
  });
328
-
329
- expect(prompts).toHaveLength(1);
330
- expect(prompts[0]).toContain('"id": "ISSUE-000009-001"');
331
- expect(logs.some((line) => line.includes("Warning: Skipping issue at index 1"))).toBe(true);
332
-
333
- const issuesRaw = await readFile(join(projectRoot, ".agents", "flow", "it_000009_ISSUES.json"), "utf8");
334
- const issues = JSON.parse(issuesRaw) as Array<{ id: string; status: string }>;
335
-
336
- expect(issues.find((issue) => issue.id === "ISSUE-000009-001")?.status).toBe("fixed");
337
- expect(issues.find((issue) => issue.id === "ISSUE-000009-003")?.status).toBe("fixed");
338
- expect(issues.some((issue) => issue.id === "ISSUE-000009-002")).toBe(false);
339
384
  });
340
385
 
341
386
  test("marks issue as retry when hypothesis is not confirmed and retries remain", async () => {
@@ -363,9 +408,9 @@ describe("execute automated-fix", () => {
363
408
  return { exitCode: 0, stdout: "", stderr: "" };
364
409
  },
365
410
  runCommitFn: async () => 0,
366
- writeFileFn: async (path, data, options) => {
367
- writtenSnapshots.push(String(data));
368
- return writeFile(path, data, options);
411
+ writeJsonArtifactFn: async (path, _schema, data) => {
412
+ writtenSnapshots.push(JSON.stringify(data, null, 2));
413
+ await writeFile(path, `${JSON.stringify(data, null, 2)}\n`, "utf8");
369
414
  },
370
415
  },
371
416
  );
@@ -1,4 +1,4 @@
1
- import { readFile, writeFile } from "node:fs/promises";
1
+ import { readFile } from "node:fs/promises";
2
2
  import { join } from "node:path";
3
3
  import { $ as dollar } from "bun";
4
4
 
@@ -11,7 +11,8 @@ import {
11
11
  type AgentResult,
12
12
  } from "../agent";
13
13
  import { exists, FLOW_REL_DIR, readState } from "../state";
14
- import { type Issue } from "../../scaffold/schemas/tmpl_issues";
14
+ import { type Issue, IssuesSchema } from "../../scaffold/schemas/tmpl_issues";
15
+ import { writeJsonArtifact, type WriteJsonArtifactFn } from "../write-json-artifact";
15
16
 
16
17
  export interface ExecuteAutomatedFixOptions {
17
18
  provider: AgentProvider;
@@ -27,7 +28,7 @@ interface ExecuteAutomatedFixDeps {
27
28
  nowFn: () => Date;
28
29
  readFileFn: typeof readFile;
29
30
  runCommitFn: (projectRoot: string, message: string) => Promise<number>;
30
- writeFileFn: typeof writeFile;
31
+ writeJsonArtifactFn: WriteJsonArtifactFn;
31
32
  }
32
33
 
33
34
  const defaultDeps: ExecuteAutomatedFixDeps = {
@@ -44,7 +45,7 @@ const defaultDeps: ExecuteAutomatedFixDeps = {
44
45
  .quiet();
45
46
  return result.exitCode;
46
47
  },
47
- writeFileFn: writeFile,
48
+ writeJsonArtifactFn: writeJsonArtifact,
48
49
  };
49
50
 
50
51
  function isNetworkErrorText(text: string): boolean {
@@ -72,106 +73,12 @@ function sortIssuesById(issues: Issue[]): Issue[] {
72
73
  return [...issues].sort((left, right) => left.id.localeCompare(right.id));
73
74
  }
74
75
 
75
- const ALLOWED_ISSUE_STATUSES: Set<Issue["status"]> = new Set([
76
- "open",
77
- "fixed",
78
- "retry",
79
- "manual-fix",
80
- ]);
81
-
82
- function asRecord(value: unknown): Record<string, unknown> | null {
83
- if (typeof value !== "object" || value === null || Array.isArray(value)) {
84
- return null;
85
- }
86
- return value as Record<string, unknown>;
87
- }
88
-
89
- function parseIssuesForProcessing(
90
- raw: unknown,
91
- flowRelativePath: string,
92
- logFn: (message: string) => void,
93
- ): Issue[] {
94
- if (!Array.isArray(raw)) {
95
- throw new Error(
96
- `Deterministic validation error: issues schema mismatch in ${flowRelativePath}.`,
97
- );
98
- }
99
-
100
- const parsedIssues: Issue[] = [];
101
- const seenIds = new Set<string>();
102
-
103
- for (const [index, item] of raw.entries()) {
104
- const issue = asRecord(item);
105
- if (!issue) {
106
- logFn(
107
- `Warning: Skipping invalid issue at index ${index} in ${flowRelativePath}: expected an object.`,
108
- );
109
- continue;
110
- }
111
-
112
- const id = issue.id;
113
- const title = issue.title;
114
- const description = issue.description;
115
- const status = issue.status;
116
-
117
- const missingFields: string[] = [];
118
- if (typeof id !== "string") {
119
- missingFields.push("id");
120
- }
121
- if (typeof title !== "string") {
122
- missingFields.push("title");
123
- }
124
- if (typeof description !== "string") {
125
- missingFields.push("description");
126
- }
127
- if (typeof status !== "string") {
128
- missingFields.push("status");
129
- }
130
-
131
- if (missingFields.length > 0) {
132
- logFn(
133
- `Warning: Skipping issue at index ${index} in ${flowRelativePath}: missing required field(s): ${missingFields.join(", ")}.`,
134
- );
135
- continue;
136
- }
137
-
138
- const validId = id as string;
139
- const validTitle = title as string;
140
- const validDescription = description as string;
141
- const validStatus = status as Issue["status"];
142
-
143
- if (!ALLOWED_ISSUE_STATUSES.has(validStatus)) {
144
- logFn(
145
- `Warning: Skipping issue ${validId} in ${flowRelativePath}: invalid status '${status}'.`,
146
- );
147
- continue;
148
- }
149
-
150
- if (seenIds.has(validId)) {
151
- logFn(
152
- `Warning: Skipping duplicate issue id '${validId}' in ${flowRelativePath}.`,
153
- );
154
- continue;
155
- }
156
-
157
- seenIds.add(validId);
158
- parsedIssues.push({
159
- id: validId,
160
- title: validTitle,
161
- description: validDescription,
162
- status: validStatus,
163
- });
164
- }
165
-
166
- return parsedIssues;
167
- }
168
-
169
76
  async function writeIssuesFile(
170
77
  issuesPath: string,
171
78
  issues: Issue[],
172
79
  deps: ExecuteAutomatedFixDeps,
173
80
  ): Promise<void> {
174
- await deps.writeFileFn(issuesPath, `${JSON.stringify(issues, null, 2)}\n`, "utf8");
81
+ await deps.writeJsonArtifactFn(issuesPath, IssuesSchema, issues);
175
82
  }
176
83
 
177
84
  async function commitIssueUpdate(
@@ -185,6 +92,25 @@ async function commitIssueUpdate(
185
92
  return exitCode === 0;
186
93
  }
187
94
 
95
+ /**
96
+ * Guardrail policy: `execute-automated-fix` is an explicit exception to the
97
+ * phase-based guardrail system used by `execute-test-plan` and
98
+ * `execute-refactor`. Those commands assert `current_phase` and prerequisite
99
+ * status fields via `assertGuardrail` before running, because they depend on
100
+ * phase-specific state transitions being in place.
101
+ *
102
+ * `execute-automated-fix` is deliberately phase-independent: issues can exist
103
+ * and require automated remediation at any point in the workflow (prototype or
104
+ * refactor phases, or during reruns after partial fixes). Its sole
105
+ * prerequisite is the existence of a valid issues file for the current
106
+ * iteration, which is already enforced by a hard error below. Adding a
107
+ * phase-based guardrail here would prevent legitimate use cases (e.g. fixing
108
+ * issues discovered late in a refactor pass) without adding safety value.
109
+ *
110
+ * `--force` is therefore not applicable to this command and is not accepted as
111
+ * a flag (any unrecognised option, including `--force`, is rejected by the CLI
112
+ * router before reaching this function).
113
+ */
188
114
  export async function runExecuteAutomatedFix(
189
115
  opts: ExecuteAutomatedFixOptions,
190
116
  deps: Partial<ExecuteAutomatedFixDeps> = {},
@@ -224,7 +150,14 @@ export async function runExecuteAutomatedFix(
224
150
  );
225
151
  }
226
152
 
227
- const issues = sortIssuesById(parseIssuesForProcessing(parsedIssuesRaw, flowRelativePath, mergedDeps.logFn));
153
+ const issuesValidation = IssuesSchema.safeParse(parsedIssuesRaw);
154
+ if (!issuesValidation.success) {
155
+ throw new Error(
156
+ `Deterministic validation error: issues schema mismatch in ${flowRelativePath}.`,
157
+ );
158
+ }
159
+
160
+ const issues = sortIssuesById(issuesValidation.data);
228
161
  const openIssues = issues.filter((issue) => issue.status === "open");
229
162
 
230
163
  if (openIssues.length === 0) {
@@ -233,7 +166,7 @@ export async function runExecuteAutomatedFix(
233
166
  }
234
167
 
235
168
  const skillTemplate = await mergedDeps.loadSkillFn(projectRoot, "automated-fix");
236
- const maxIssuesToProcess = opts.iterations ?? 1;
169
+ const maxIssuesToProcess = opts.iterations ?? openIssues.length;
237
170
  const issuesToProcess = openIssues.slice(0, maxIssuesToProcess);
238
171
  const maxRetries = opts.retryOnFail ?? 0;
239
172