@quinteroac/agents-coding-toolkit 0.1.0-preview

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 (85) hide show
  1. package/AGENTS.md +7 -0
  2. package/README.md +127 -0
  3. package/package.json +34 -0
  4. package/scaffold/.agents/flow/archived/tmpl_.gitkeep +0 -0
  5. package/scaffold/.agents/flow/tmpl_README.md +7 -0
  6. package/scaffold/.agents/flow/tmpl_iteration_close_checklist.example.md +11 -0
  7. package/scaffold/.agents/skills/automated-fix/tmpl_SKILL.md +67 -0
  8. package/scaffold/.agents/skills/create-issue/tmpl_SKILL.md +68 -0
  9. package/scaffold/.agents/skills/create-pr-document/tmpl_SKILL.md +125 -0
  10. package/scaffold/.agents/skills/create-project-context/tmpl_SKILL.md +168 -0
  11. package/scaffold/.agents/skills/create-test-plan/tmpl_SKILL.md +86 -0
  12. package/scaffold/.agents/skills/debug/tmpl_SKILL.md +19 -0
  13. package/scaffold/.agents/skills/evaluate/tmpl_SKILL.md +19 -0
  14. package/scaffold/.agents/skills/execute-test-batch/tmpl_SKILL.md +49 -0
  15. package/scaffold/.agents/skills/execute-test-case/tmpl_SKILL.md +47 -0
  16. package/scaffold/.agents/skills/implement-user-story/tmpl_SKILL.md +68 -0
  17. package/scaffold/.agents/skills/plan-refactor/tmpl_SKILL.md +19 -0
  18. package/scaffold/.agents/skills/refactor-prd/tmpl_SKILL.md +19 -0
  19. package/scaffold/.agents/skills/refine-pr-document/tmpl_SKILL.md +108 -0
  20. package/scaffold/.agents/skills/refine-project-context/tmpl_SKILL.md +157 -0
  21. package/scaffold/.agents/skills/refine-test-plan/tmpl_SKILL.md +76 -0
  22. package/scaffold/.agents/tmpl_PROJECT_CONTEXT.md +3 -0
  23. package/scaffold/.agents/tmpl_state.example.json +26 -0
  24. package/scaffold/.agents/tmpl_state_rules.md +29 -0
  25. package/scaffold/docs/nvst-flow/templates/tmpl_CHANGELOG.md +18 -0
  26. package/scaffold/docs/nvst-flow/templates/tmpl_TECHNICAL_DEBT.md +11 -0
  27. package/scaffold/docs/nvst-flow/templates/tmpl_it_000001_evaluation-report.md +19 -0
  28. package/scaffold/docs/nvst-flow/templates/tmpl_it_000001_product-requirement-document.md +19 -0
  29. package/scaffold/docs/nvst-flow/templates/tmpl_it_000001_refactor_plan.md +19 -0
  30. package/scaffold/docs/nvst-flow/templates/tmpl_it_000001_test-plan.md +19 -0
  31. package/scaffold/docs/nvst-flow/tmpl_COMMANDS.md +0 -0
  32. package/scaffold/docs/nvst-flow/tmpl_QUICK_USE.md +0 -0
  33. package/scaffold/docs/tmpl_PLACEHOLDER.md +0 -0
  34. package/scaffold/schemas/node-shims.d.ts +15 -0
  35. package/scaffold/schemas/tmpl_issues.ts +19 -0
  36. package/scaffold/schemas/tmpl_prd.ts +26 -0
  37. package/scaffold/schemas/tmpl_progress.ts +39 -0
  38. package/scaffold/schemas/tmpl_state.ts +81 -0
  39. package/scaffold/schemas/tmpl_test-plan.ts +20 -0
  40. package/scaffold/schemas/tmpl_validate-progress.ts +13 -0
  41. package/scaffold/schemas/tmpl_validate-state.ts +13 -0
  42. package/scaffold/tmpl_AGENTS.md +7 -0
  43. package/schemas/prd.ts +26 -0
  44. package/schemas/progress.ts +39 -0
  45. package/schemas/state.ts +81 -0
  46. package/schemas/test-plan.test.ts +53 -0
  47. package/schemas/test-plan.ts +20 -0
  48. package/schemas/validate-progress.ts +13 -0
  49. package/schemas/validate-state.ts +13 -0
  50. package/src/agent.test.ts +37 -0
  51. package/src/agent.ts +225 -0
  52. package/src/cli-path.ts +4 -0
  53. package/src/cli.ts +578 -0
  54. package/src/commands/approve-project-context.ts +37 -0
  55. package/src/commands/approve-requirement.ts +217 -0
  56. package/src/commands/approve-test-plan.test.ts +193 -0
  57. package/src/commands/approve-test-plan.ts +202 -0
  58. package/src/commands/create-issue.test.ts +484 -0
  59. package/src/commands/create-issue.ts +371 -0
  60. package/src/commands/create-project-context.ts +96 -0
  61. package/src/commands/create-prototype.test.ts +153 -0
  62. package/src/commands/create-prototype.ts +425 -0
  63. package/src/commands/create-test-plan.test.ts +381 -0
  64. package/src/commands/create-test-plan.ts +248 -0
  65. package/src/commands/define-requirement.ts +47 -0
  66. package/src/commands/destroy.ts +113 -0
  67. package/src/commands/execute-automated-fix.test.ts +580 -0
  68. package/src/commands/execute-automated-fix.ts +363 -0
  69. package/src/commands/execute-manual-fix.test.ts +343 -0
  70. package/src/commands/execute-manual-fix.ts +203 -0
  71. package/src/commands/execute-test-plan.test.ts +1891 -0
  72. package/src/commands/execute-test-plan.ts +722 -0
  73. package/src/commands/init.ts +85 -0
  74. package/src/commands/refine-project-context.ts +74 -0
  75. package/src/commands/refine-requirement.ts +60 -0
  76. package/src/commands/refine-test-plan.test.ts +200 -0
  77. package/src/commands/refine-test-plan.ts +93 -0
  78. package/src/commands/start-iteration.test.ts +144 -0
  79. package/src/commands/start-iteration.ts +101 -0
  80. package/src/commands/write-json.ts +136 -0
  81. package/src/install.test.ts +124 -0
  82. package/src/pack.test.ts +103 -0
  83. package/src/state.test.ts +66 -0
  84. package/src/state.ts +52 -0
  85. package/tsconfig.json +15 -0
@@ -0,0 +1,484 @@
1
+ import { mkdir, writeFile, rm, readFile } from "node:fs/promises";
2
+ import { join } from "node:path";
3
+ import { describe, expect, test } from "bun:test";
4
+
5
+ import { IssuesSchema } from "../../scaffold/schemas/tmpl_issues";
6
+ import { readState, writeState } from "../state";
7
+ import {
8
+ extractJson,
9
+ buildIssuesFromTestResults,
10
+ isActionableStatus,
11
+ runCreateIssueFromTestReport,
12
+ type TestExecutionResults,
13
+ } from "./create-issue";
14
+
15
+ // ---------------------------------------------------------------------------
16
+ // AC03 / FR-6: ISSUES schema validation
17
+ // ---------------------------------------------------------------------------
18
+
19
+ describe("IssuesSchema validation", () => {
20
+ test("accepts a valid issues array", () => {
21
+ const data = [
22
+ { id: "ISSUE-000008-001", title: "Bug A", description: "Details A", status: "open" },
23
+ { id: "ISSUE-000008-002", title: "Bug B", description: "Details B", status: "fixed" },
24
+ ];
25
+ const result = IssuesSchema.safeParse(data);
26
+ expect(result.success).toBe(true);
27
+ });
28
+
29
+ test("accepts an empty array", () => {
30
+ const result = IssuesSchema.safeParse([]);
31
+ expect(result.success).toBe(true);
32
+ });
33
+
34
+ test("rejects duplicate IDs", () => {
35
+ const data = [
36
+ { id: "ISSUE-001", title: "Bug A", description: "Details A", status: "open" },
37
+ { id: "ISSUE-001", title: "Bug B", description: "Details B", status: "open" },
38
+ ];
39
+ const result = IssuesSchema.safeParse(data);
40
+ expect(result.success).toBe(false);
41
+ });
42
+
43
+ test("rejects invalid status value", () => {
44
+ const data = [
45
+ { id: "ISSUE-001", title: "Bug A", description: "Details A", status: "closed" },
46
+ ];
47
+ const result = IssuesSchema.safeParse(data);
48
+ expect(result.success).toBe(false);
49
+ });
50
+
51
+ test("rejects missing required fields", () => {
52
+ const data = [{ id: "ISSUE-001", title: "Bug A" }];
53
+ const result = IssuesSchema.safeParse(data);
54
+ expect(result.success).toBe(false);
55
+ });
56
+
57
+ test("rejects non-array input", () => {
58
+ const result = IssuesSchema.safeParse({ id: "ISSUE-001", title: "Bug" });
59
+ expect(result.success).toBe(false);
60
+ });
61
+ });
62
+
63
+ // ---------------------------------------------------------------------------
64
+ // extractJson helper
65
+ // ---------------------------------------------------------------------------
66
+
67
+ describe("extractJson", () => {
68
+ test("extracts JSON from markdown code fences", () => {
69
+ const input = 'Some text\n```json\n[{"title":"Bug"}]\n```\nMore text';
70
+ expect(extractJson(input)).toBe('[{"title":"Bug"}]');
71
+ });
72
+
73
+ test("extracts JSON from plain code fences", () => {
74
+ const input = '```\n[{"title":"Bug"}]\n```';
75
+ expect(extractJson(input)).toBe('[{"title":"Bug"}]');
76
+ });
77
+
78
+ test("extracts JSON array directly from text", () => {
79
+ const input = 'Here is the output: [{"title":"Bug","description":"Desc"}]';
80
+ expect(extractJson(input)).toBe('[{"title":"Bug","description":"Desc"}]');
81
+ });
82
+
83
+ test("returns raw text when no JSON array found", () => {
84
+ const input = "no json here";
85
+ expect(extractJson(input)).toBe("no json here");
86
+ });
87
+
88
+ test("handles clean JSON array input", () => {
89
+ const input = '[{"title":"Bug","description":"Details"}]';
90
+ expect(extractJson(input)).toBe(input);
91
+ });
92
+
93
+ test("extracts JSON from ```json block when multiple markdown blocks follow (e.g. ```bash)", () => {
94
+ const input = `Intro text.
95
+
96
+ \`\`\`json
97
+ [{"testCaseId":"TC-01","status":"passed","evidence":"ok","notes":""}]
98
+ \`\`\`
99
+
100
+ Para obtener resultados, ejecuta:
101
+
102
+ \`\`\`bash
103
+ bun run test.ts
104
+ \`\`\``;
105
+ const extracted = extractJson(input);
106
+ expect(JSON.parse(extracted)).toEqual([
107
+ { testCaseId: "TC-01", status: "passed", evidence: "ok", notes: "" },
108
+ ]);
109
+ });
110
+ });
111
+
112
+ // ---------------------------------------------------------------------------
113
+ // AC01: CLI routing — create issue requires --agent
114
+ // ---------------------------------------------------------------------------
115
+
116
+ describe("CLI routing for create issue", () => {
117
+ test("exits with code 1 when --agent is missing", async () => {
118
+ const proc = Bun.spawn(
119
+ ["bun", "run", "src/cli.ts", "create", "issue"],
120
+ { cwd: process.cwd(), stdout: "pipe", stderr: "pipe" },
121
+ );
122
+ const exitCode = await proc.exited;
123
+ const stderr = await new Response(proc.stderr).text();
124
+ expect(exitCode).toBe(1);
125
+ expect(stderr).toContain("--agent");
126
+ });
127
+
128
+ test("exits with code 1 for unknown provider", async () => {
129
+ const proc = Bun.spawn(
130
+ ["bun", "run", "src/cli.ts", "create", "issue", "--agent", "invalid-provider"],
131
+ { cwd: process.cwd(), stdout: "pipe", stderr: "pipe" },
132
+ );
133
+ const exitCode = await proc.exited;
134
+ const stderr = await new Response(proc.stderr).text();
135
+ expect(exitCode).toBe(1);
136
+ expect(stderr).toContain("Unknown agent provider");
137
+ });
138
+
139
+ test("exits with code 1 for unknown options", async () => {
140
+ const proc = Bun.spawn(
141
+ ["bun", "run", "src/cli.ts", "create", "issue", "--agent", "claude", "--unknown-flag"],
142
+ { cwd: process.cwd(), stdout: "pipe", stderr: "pipe" },
143
+ );
144
+ const exitCode = await proc.exited;
145
+ const stderr = await new Response(proc.stderr).text();
146
+ expect(exitCode).toBe(1);
147
+ expect(stderr).toContain("Unknown option");
148
+ });
149
+ });
150
+
151
+ // ---------------------------------------------------------------------------
152
+ // AC02: Auto-generated id and default status
153
+ // ---------------------------------------------------------------------------
154
+
155
+ describe("issue ID generation and status defaults", () => {
156
+ test("generated issues have unique sequential IDs and status open", () => {
157
+ const agentOutput = [
158
+ { title: "Bug 1", description: "First bug" },
159
+ { title: "Bug 2", description: "Second bug" },
160
+ ];
161
+ const iteration = "000008";
162
+ const issues = agentOutput.map((item, index) => ({
163
+ id: `ISSUE-${iteration}-${String(index + 1).padStart(3, "0")}`,
164
+ title: item.title,
165
+ description: item.description,
166
+ status: "open" as const,
167
+ }));
168
+
169
+ expect(issues[0].id).toBe("ISSUE-000008-001");
170
+ expect(issues[1].id).toBe("ISSUE-000008-002");
171
+ expect(issues[0].status).toBe("open");
172
+ expect(issues[1].status).toBe("open");
173
+
174
+ // Validate against schema
175
+ const result = IssuesSchema.safeParse(issues);
176
+ expect(result.success).toBe(true);
177
+ });
178
+ });
179
+
180
+ // ---------------------------------------------------------------------------
181
+ // AC04: Output path convention
182
+ // ---------------------------------------------------------------------------
183
+
184
+ describe("output file path", () => {
185
+ test("follows it_{iteration}_ISSUES.json naming convention", () => {
186
+ const iteration = "000008";
187
+ const expectedFileName = `it_${iteration}_ISSUES.json`;
188
+ expect(expectedFileName).toBe("it_000008_ISSUES.json");
189
+
190
+ const expectedRelPath = `.agents/flow/${expectedFileName}`;
191
+ expect(expectedRelPath).toBe(".agents/flow/it_000008_ISSUES.json");
192
+ });
193
+ });
194
+
195
+ // ---------------------------------------------------------------------------
196
+ // AC04: write-json schema registration (issues schema available)
197
+ // ---------------------------------------------------------------------------
198
+
199
+ describe("write-json issues schema registration", () => {
200
+ test("write-json accepts issues schema name", async () => {
201
+ const validData = JSON.stringify([
202
+ { id: "ISSUE-001", title: "Test", description: "Desc", status: "open" },
203
+ ]);
204
+ const proc = Bun.spawn(
205
+ [
206
+ "bun",
207
+ "run",
208
+ "src/cli.ts",
209
+ "write-json",
210
+ "--schema",
211
+ "issues",
212
+ "--out",
213
+ "/tmp/nvst-test-issues.json",
214
+ "--data",
215
+ validData,
216
+ ],
217
+ { cwd: process.cwd(), stdout: "pipe", stderr: "pipe" },
218
+ );
219
+ const exitCode = await proc.exited;
220
+ expect(exitCode).toBe(0);
221
+
222
+ // Clean up
223
+ try {
224
+ const { unlink } = await import("node:fs/promises");
225
+ await unlink("/tmp/nvst-test-issues.json");
226
+ } catch {
227
+ // ignore cleanup errors
228
+ }
229
+ });
230
+
231
+ test("write-json rejects invalid issues data", async () => {
232
+ const invalidData = JSON.stringify([
233
+ { id: "ISSUE-001", title: "Test" }, // missing description and status
234
+ ]);
235
+ const proc = Bun.spawn(
236
+ [
237
+ "bun",
238
+ "run",
239
+ "src/cli.ts",
240
+ "write-json",
241
+ "--schema",
242
+ "issues",
243
+ "--out",
244
+ "/tmp/nvst-test-issues-invalid.json",
245
+ "--data",
246
+ invalidData,
247
+ ],
248
+ { cwd: process.cwd(), stdout: "pipe", stderr: "pipe" },
249
+ );
250
+ const exitCode = await proc.exited;
251
+ expect(exitCode).toBe(1);
252
+ });
253
+ });
254
+
255
+ // ===========================================================================
256
+ // US-002: Create issues from test execution results
257
+ // ===========================================================================
258
+
259
+ // ---------------------------------------------------------------------------
260
+ // US-002-AC02: isActionableStatus helper
261
+ // ---------------------------------------------------------------------------
262
+
263
+ describe("isActionableStatus", () => {
264
+ test("returns true for failed", () => {
265
+ expect(isActionableStatus("failed")).toBe(true);
266
+ });
267
+
268
+ test("returns true for skipped", () => {
269
+ expect(isActionableStatus("skipped")).toBe(true);
270
+ });
271
+
272
+ test("returns true for invocation_failed", () => {
273
+ expect(isActionableStatus("invocation_failed")).toBe(true);
274
+ });
275
+
276
+ test("returns false for passed", () => {
277
+ expect(isActionableStatus("passed")).toBe(false);
278
+ });
279
+
280
+ test("returns false for arbitrary string", () => {
281
+ expect(isActionableStatus("unknown")).toBe(false);
282
+ });
283
+ });
284
+
285
+ // ---------------------------------------------------------------------------
286
+ // US-002-AC02: buildIssuesFromTestResults
287
+ // ---------------------------------------------------------------------------
288
+
289
+ describe("buildIssuesFromTestResults", () => {
290
+ const makeResults = (
291
+ entries: Array<{ id: string; desc: string; status: string; notes?: string; evidence?: string }>,
292
+ ): TestExecutionResults => ({
293
+ iteration: "000008",
294
+ results: entries.map((e) => ({
295
+ testCaseId: e.id,
296
+ description: e.desc,
297
+ correlatedRequirements: ["US-001"],
298
+ payload: {
299
+ status: e.status,
300
+ notes: e.notes,
301
+ evidence: e.evidence,
302
+ },
303
+ })),
304
+ });
305
+
306
+ test("converts failed/skipped/invocation_failed tests to issues with status open", () => {
307
+ const results = makeResults([
308
+ { id: "TC-001", desc: "Test A fails", status: "failed", notes: "assertion error" },
309
+ { id: "TC-002", desc: "Test B skipped", status: "skipped" },
310
+ { id: "TC-003", desc: "Test C invocation failed", status: "invocation_failed", notes: "timeout" },
311
+ ]);
312
+
313
+ const issues = buildIssuesFromTestResults(results, "000008");
314
+
315
+ expect(issues).toHaveLength(3);
316
+ expect(issues[0].id).toBe("ISSUE-000008-001");
317
+ expect(issues[0].status).toBe("open");
318
+ expect(issues[0].title).toContain("[failed]");
319
+ expect(issues[0].title).toContain("TC-001");
320
+
321
+ expect(issues[1].id).toBe("ISSUE-000008-002");
322
+ expect(issues[1].status).toBe("open");
323
+ expect(issues[1].title).toContain("[skipped]");
324
+
325
+ expect(issues[2].id).toBe("ISSUE-000008-003");
326
+ expect(issues[2].status).toBe("open");
327
+ expect(issues[2].title).toContain("[invocation_failed]");
328
+ });
329
+
330
+ test("includes notes and evidence in description", () => {
331
+ const results = makeResults([
332
+ { id: "TC-001", desc: "Test A", status: "failed", notes: "some note", evidence: "log output" },
333
+ ]);
334
+
335
+ const issues = buildIssuesFromTestResults(results, "000008");
336
+ expect(issues[0].description).toContain("some note");
337
+ expect(issues[0].description).toContain("log output");
338
+ expect(issues[0].description).toContain("US-001");
339
+ });
340
+
341
+ test("filters out passing tests", () => {
342
+ const results = makeResults([
343
+ { id: "TC-001", desc: "Passing test", status: "passed" },
344
+ { id: "TC-002", desc: "Failing test", status: "failed" },
345
+ ]);
346
+
347
+ const issues = buildIssuesFromTestResults(results, "000008");
348
+ expect(issues).toHaveLength(1);
349
+ expect(issues[0].title).toContain("TC-002");
350
+ });
351
+
352
+ test("returns empty array when all tests pass (AC04)", () => {
353
+ const results = makeResults([
354
+ { id: "TC-001", desc: "Pass A", status: "passed" },
355
+ { id: "TC-002", desc: "Pass B", status: "passed" },
356
+ ]);
357
+
358
+ const issues = buildIssuesFromTestResults(results, "000008");
359
+ expect(issues).toHaveLength(0);
360
+
361
+ // Validate empty array against schema
362
+ const validation = IssuesSchema.safeParse(issues);
363
+ expect(validation.success).toBe(true);
364
+ });
365
+
366
+ test("generated issues pass ISSUES schema validation (AC06)", () => {
367
+ const results = makeResults([
368
+ { id: "TC-001", desc: "Fail A", status: "failed", notes: "err" },
369
+ { id: "TC-002", desc: "Skip B", status: "skipped" },
370
+ ]);
371
+
372
+ const issues = buildIssuesFromTestResults(results, "000008");
373
+ const validation = IssuesSchema.safeParse(issues);
374
+ expect(validation.success).toBe(true);
375
+ });
376
+
377
+ test("issue IDs are unique and sequential", () => {
378
+ const results = makeResults([
379
+ { id: "TC-001", desc: "A", status: "failed" },
380
+ { id: "TC-002", desc: "B", status: "skipped" },
381
+ { id: "TC-003", desc: "C", status: "invocation_failed" },
382
+ ]);
383
+
384
+ const issues = buildIssuesFromTestResults(results, "000010");
385
+ const ids = issues.map((i) => i.id);
386
+ expect(ids).toEqual(["ISSUE-000010-001", "ISSUE-000010-002", "ISSUE-000010-003"]);
387
+ expect(new Set(ids).size).toBe(ids.length);
388
+ });
389
+ });
390
+
391
+ // ---------------------------------------------------------------------------
392
+ // US-002-AC01/AC03/AC04/AC05: CLI integration tests for --test-execution-report
393
+ // ---------------------------------------------------------------------------
394
+
395
+ describe("create issue --test-execution-report CLI integration", () => {
396
+ const tmpDir = join(process.cwd(), ".agents", "flow", "__test_tmp__");
397
+ const cwd = process.cwd();
398
+
399
+ // We cannot easily test the full CLI flow without mocking state.json,
400
+ // so we test the CLI routing layer separately.
401
+
402
+ test("exits with code 1 for unknown options combined with --test-execution-report", async () => {
403
+ const proc = Bun.spawn(
404
+ ["bun", "run", "src/cli.ts", "create", "issue", "--test-execution-report", "--extra-flag"],
405
+ { cwd, stdout: "pipe", stderr: "pipe" },
406
+ );
407
+ const exitCode = await proc.exited;
408
+ const stderr = await new Response(proc.stderr).text();
409
+ expect(exitCode).toBe(1);
410
+ expect(stderr).toContain("Unknown option");
411
+ });
412
+
413
+ test("--test-execution-report is accepted as a valid flag (does not show --agent error)", async () => {
414
+ // TC-US002-14: --test-execution-report does not require --agent.
415
+ // May succeed (if results file exists) or fail with file-not-found; must NOT show "Missing --agent"
416
+ const proc = Bun.spawn(
417
+ ["bun", "run", "src/cli.ts", "create", "issue", "--test-execution-report"],
418
+ { cwd, stdout: "pipe", stderr: "pipe" },
419
+ );
420
+ const exitCode = await proc.exited;
421
+ const stderr = await new Response(proc.stderr).text();
422
+ expect(stderr).not.toContain("Missing --agent");
423
+ if (exitCode !== 0) {
424
+ expect(stderr).toContain("test-execution-results");
425
+ }
426
+ });
427
+
428
+ // TC-US002-10: Command falls back to archived path from state.json history when flow file absent
429
+ test("falls back to archived path when flow file absent (TC-US002-10)", async () => {
430
+ const projectRoot = cwd;
431
+ const testIteration = "999999";
432
+ const archivedRelPath = join(".agents", "flow", "archived", testIteration);
433
+ const archivedDir = join(projectRoot, archivedRelPath);
434
+ const resultsFileName = `it_${testIteration}_test-execution-results.json`;
435
+ const flowResultsPath = join(projectRoot, ".agents", "flow", resultsFileName);
436
+ const archivedResultsPath = join(archivedDir, resultsFileName);
437
+ const issuesPath = join(projectRoot, ".agents", "flow", `it_${testIteration}_ISSUES.json`);
438
+
439
+ const validTestResults = {
440
+ iteration: testIteration,
441
+ results: [
442
+ {
443
+ testCaseId: "TC-001",
444
+ description: "A failing test",
445
+ correlatedRequirements: ["FR-2"],
446
+ payload: { status: "failed", notes: "fallback test", evidence: "archived path used" },
447
+ },
448
+ ],
449
+ };
450
+
451
+ const originalState = await readState(projectRoot);
452
+ try {
453
+ await mkdir(archivedDir, { recursive: true });
454
+ await writeFile(archivedResultsPath, JSON.stringify(validTestResults, null, 2), "utf8");
455
+
456
+ await writeState(projectRoot, {
457
+ ...originalState,
458
+ current_iteration: testIteration,
459
+ history: [
460
+ ...(originalState.history ?? []),
461
+ {
462
+ iteration: testIteration,
463
+ archived_at: "2026-02-22T00:00:00.000Z",
464
+ archived_path: archivedRelPath,
465
+ },
466
+ ],
467
+ });
468
+
469
+ await runCreateIssueFromTestReport();
470
+
471
+ const issuesContent = await readFile(issuesPath, "utf8");
472
+ const issues = JSON.parse(issuesContent);
473
+ expect(Array.isArray(issues)).toBe(true);
474
+ expect(issues.length).toBe(1);
475
+ expect(issues[0].id).toBe(`ISSUE-${testIteration}-001`);
476
+ expect(issues[0].title).toContain("[failed]");
477
+ expect(issues[0].title).toContain("TC-001");
478
+ } finally {
479
+ await rm(archivedDir, { recursive: true, force: true }).catch(() => {});
480
+ await rm(issuesPath, { force: true }).catch(() => {});
481
+ await writeState(projectRoot, originalState);
482
+ }
483
+ });
484
+ });