@nathapp/nax 0.21.0 → 0.22.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 (210) hide show
  1. package/.mcp.json +8 -0
  2. package/docs/ROADMAP.md +20 -5
  3. package/docs/adr/ADR-005-implementation-plan.md +655 -0
  4. package/docs/adr/ADR-005-pipeline-re-architecture.md +464 -0
  5. package/package.json +1 -1
  6. package/src/agents/claude.ts +44 -9
  7. package/src/config/types.ts +11 -0
  8. package/src/execution/dry-run.ts +81 -0
  9. package/src/execution/escalation/tier-outcome.ts +29 -44
  10. package/src/execution/executor-types.ts +65 -0
  11. package/src/execution/index.ts +0 -17
  12. package/src/execution/iteration-runner.ts +132 -0
  13. package/src/execution/lifecycle/index.ts +0 -1
  14. package/src/execution/lifecycle/run-regression.ts +5 -5
  15. package/src/execution/pipeline-result-handler.ts +51 -254
  16. package/src/execution/sequential-executor.ts +72 -316
  17. package/src/execution/story-selector.ts +75 -0
  18. package/src/pipeline/event-bus.ts +276 -0
  19. package/src/pipeline/runner.ts +51 -77
  20. package/src/pipeline/stages/autofix.ts +133 -0
  21. package/src/pipeline/stages/completion.ts +22 -30
  22. package/src/pipeline/stages/index.ts +30 -13
  23. package/src/pipeline/stages/rectify.ts +93 -0
  24. package/src/pipeline/stages/regression.ts +88 -0
  25. package/src/pipeline/stages/review.ts +19 -153
  26. package/src/pipeline/stages/verify.ts +18 -2
  27. package/src/pipeline/subscribers/hooks.ts +133 -0
  28. package/src/pipeline/subscribers/interaction.ts +68 -0
  29. package/src/pipeline/subscribers/reporters.ts +174 -0
  30. package/src/pipeline/types.ts +10 -1
  31. package/src/review/orchestrator.ts +105 -0
  32. package/src/tdd/prompts.ts +1 -1
  33. package/src/verification/index.ts +1 -1
  34. package/src/verification/orchestrator-types.ts +145 -0
  35. package/src/verification/orchestrator.ts +76 -0
  36. package/src/{execution/post-verify-rectification.ts → verification/rectification-loop.ts} +13 -20
  37. package/src/verification/{gate.ts → runners.ts} +17 -105
  38. package/src/verification/strategies/acceptance.ts +133 -0
  39. package/src/verification/strategies/regression.ts +90 -0
  40. package/src/verification/strategies/scoped.ts +123 -0
  41. package/test/COVERAGE-GAPS.md +333 -0
  42. package/test/{acceptance → e2e}/cm-003-default-view.test.ts +1 -0
  43. package/test/{integration/e2e.test.ts → e2e/plan-analyze-run.test.ts} +1 -0
  44. package/test/integration/{agent-validation.test.ts → cli/agent-validation.test.ts} +3 -3
  45. package/test/integration/{cli-config-default-edge-cases.test.ts → cli/cli-config-default-edge-cases.test.ts} +6 -5
  46. package/test/integration/{cli-config-default-view.test.ts → cli/cli-config-default-view.test.ts} +8 -7
  47. package/test/integration/{cli-config-diff.test.ts → cli/cli-config-diff.test.ts} +3 -2
  48. package/test/integration/{cli-config.test.ts → cli/cli-config.test.ts} +3 -2
  49. package/test/integration/{cli-diagnose.test.ts → cli/cli-diagnose.test.ts} +5 -4
  50. package/test/integration/{cli-logs.test.ts → cli/cli-logs.test.ts} +12 -3
  51. package/test/integration/{cli-plugins.test.ts → cli/cli-plugins.test.ts} +4 -3
  52. package/test/integration/{cli-precheck.test.ts → cli/cli-precheck.test.ts} +4 -3
  53. package/test/integration/{cli-run-headless.test.ts → cli/cli-run-headless.test.ts} +3 -2
  54. package/test/integration/{cli.test.ts → cli/cli.test.ts} +2 -1
  55. package/test/integration/{precheck-integration.test.ts → cli/precheck-integration.test.ts} +10 -9
  56. package/test/integration/{precheck-orchestrator.test.ts → cli/precheck-orchestrator.test.ts} +4 -3
  57. package/test/integration/{precheck.test.ts → cli/precheck.test.ts} +5 -4
  58. package/test/integration/{config-loader.test.ts → config/config-loader.test.ts} +2 -1
  59. package/test/integration/{config.test.ts → config/config.test.ts} +2 -2
  60. package/test/integration/config/merger.test.ts +1 -0
  61. package/test/integration/config/paths.test.ts +1 -0
  62. package/test/integration/{security-loader.test.ts → config/security-loader.test.ts} +2 -2
  63. package/test/integration/{context-integration.test.ts → context/context-integration.test.ts} +7 -6
  64. package/test/integration/{path-security.test.ts → context/context-path-security.test.ts} +2 -2
  65. package/test/integration/{context-provider-injection.test.ts → context/context-provider-injection.test.ts} +7 -6
  66. package/test/integration/{context-verification-integration.test.ts → context/context-verification-integration.test.ts} +5 -4
  67. package/test/integration/{s5-greenfield-fallback.test.ts → context/s5-greenfield-fallback.test.ts} +4 -3
  68. package/test/integration/{isolation.test.ts → execution/execution-isolation.test.ts} +1 -1
  69. package/test/integration/{execution.test.ts → execution/execution.test.ts} +8 -8
  70. package/test/integration/{parallel.test.ts → execution/parallel.test.ts} +2 -1
  71. package/test/integration/{prd-pause.test.ts → execution/prd-pause.test.ts} +2 -2
  72. package/test/integration/{prd-resolvers.test.ts → execution/prd-resolvers.test.ts} +3 -2
  73. package/test/integration/{progress.test.ts → execution/progress.test.ts} +1 -1
  74. package/test/integration/execution/runner-batching.test.ts +682 -0
  75. package/test/integration/{runner-config-plugins.test.ts → execution/runner-config-plugins.test.ts} +3 -2
  76. package/test/integration/execution/runner-escalation.test.ts +561 -0
  77. package/test/integration/{runner-fixes.test.ts → execution/runner-fixes.test.ts} +4 -3
  78. package/test/integration/{runner-plugin-integration.test.ts → execution/runner-plugin-integration.test.ts} +6 -5
  79. package/test/integration/execution/runner-queue-and-attempts.test.ts +476 -0
  80. package/test/integration/{status-file-integration.test.ts → execution/status-file-integration.test.ts} +9 -8
  81. package/test/integration/{status-file.test.ts → execution/status-file.test.ts} +3 -2
  82. package/test/integration/{status-writer.test.ts → execution/status-writer.test.ts} +5 -4
  83. package/test/integration/{story-id-in-events.test.ts → execution/story-id-in-events.test.ts} +9 -8
  84. package/test/integration/{interaction-chain-pipeline.test.ts → interaction/interaction-chain-pipeline.test.ts} +26 -14
  85. package/test/integration/{hooks.test.ts → pipeline/hooks.test.ts} +4 -2
  86. package/test/integration/{pipeline-acceptance.test.ts → pipeline/pipeline-acceptance.test.ts} +7 -6
  87. package/test/integration/{pipeline-events.test.ts → pipeline/pipeline-events.test.ts} +7 -6
  88. package/test/integration/{pipeline.test.ts → pipeline/pipeline.test.ts} +9 -7
  89. package/test/integration/{reporter-lifecycle.test.ts → pipeline/reporter-lifecycle.test.ts} +9 -7
  90. package/test/integration/{verify-stage.test.ts → pipeline/verify-stage.test.ts} +7 -5
  91. package/test/integration/{analyze-integration.test.ts → plan/analyze-integration.test.ts} +3 -2
  92. package/test/integration/{analyze-scanner.test.ts → plan/analyze-scanner.test.ts} +8 -7
  93. package/test/integration/{logger.test.ts → plan/logger.test.ts} +1 -1
  94. package/test/integration/{plan.test.ts → plan/plan.test.ts} +3 -3
  95. package/test/integration/plugins/config-integration.test.ts +1 -0
  96. package/test/integration/plugins/config-resolution.test.ts +1 -0
  97. package/test/integration/plugins/loader.test.ts +1 -0
  98. package/test/integration/plugins/{registry.test.ts → plugins-registry.test.ts} +1 -0
  99. package/test/integration/plugins/validator.test.ts +1 -0
  100. package/test/integration/{review-config-commands.test.ts → review/review-config-commands.test.ts} +4 -3
  101. package/test/integration/{review-config-schema.test.ts → review/review-config-schema.test.ts} +3 -2
  102. package/test/integration/{review-plugin-integration.test.ts → review/review-plugin-integration.test.ts} +5 -4
  103. package/test/integration/{review.test.ts → review/review.test.ts} +3 -2
  104. package/test/integration/routing/plugin-routing-advanced.test.ts +461 -0
  105. package/test/integration/{plugin-routing.test.ts → routing/plugin-routing-core.test.ts} +9 -403
  106. package/test/integration/{routing-stage-bug-021.test.ts → routing/routing-stage-bug-021.test.ts} +8 -7
  107. package/test/integration/{routing-stage-greenfield.test.ts → routing/routing-stage-greenfield.test.ts} +7 -6
  108. package/test/integration/{tdd-cleanup.test.ts → tdd/tdd-cleanup.test.ts} +1 -1
  109. package/test/integration/tdd/tdd-orchestrator-core.test.ts +565 -0
  110. package/test/integration/tdd/tdd-orchestrator-failureCategory.test.ts +355 -0
  111. package/test/integration/tdd/tdd-orchestrator-fallback.test.ts +311 -0
  112. package/test/integration/tdd/tdd-orchestrator-lite.test.ts +289 -0
  113. package/test/integration/tdd/tdd-orchestrator-prompts.test.ts +260 -0
  114. package/test/integration/tdd/tdd-orchestrator-verdict.test.ts +536 -0
  115. package/test/integration/tmp/headless-test/test.jsonl +30 -0
  116. package/test/integration/{test-scanner.test.ts → verification/test-scanner.test.ts} +1 -1
  117. package/test/integration/{verification-asset-check.test.ts → verification/verification-asset-check.test.ts} +3 -2
  118. package/test/unit/acceptance.test.ts +1 -0
  119. package/test/unit/agent-stderr-capture.test.ts +1 -0
  120. package/test/unit/agents/claude.test.ts +1 -0
  121. package/test/unit/analyze-classifier.test.ts +1 -0
  122. package/test/unit/auto-detect.test.ts +1 -0
  123. package/test/unit/cli-status.test.ts +1 -0
  124. package/test/unit/commands/common.test.ts +1 -0
  125. package/test/unit/commands/logs.test.ts +1 -0
  126. package/test/unit/commands/unlock.test.ts +1 -0
  127. package/test/unit/config/defaults.test.ts +1 -0
  128. package/test/unit/config/regression-gate-schema.test.ts +1 -0
  129. package/test/unit/config/smart-runner-flag.test.ts +1 -0
  130. package/test/unit/constitution-generators.test.ts +1 -0
  131. package/test/unit/constitution.test.ts +1 -0
  132. package/test/unit/context/context-autodetect.test.ts +297 -0
  133. package/test/unit/context/context-build.test.ts +575 -0
  134. package/test/unit/context/context-coverage.test.ts +236 -0
  135. package/test/unit/context/context-error.test.ts +93 -0
  136. package/test/unit/context/context-estimate-tokens.test.ts +201 -0
  137. package/test/unit/context/context-format.test.ts +302 -0
  138. package/test/unit/context/context-isolation.test.ts +267 -0
  139. package/test/unit/context/context-sort.test.ts +93 -0
  140. package/test/unit/context/context-story.test.ts +108 -0
  141. package/test/{context → unit/context}/prior-failures.test.ts +5 -4
  142. package/test/unit/context.test.ts +1 -0
  143. package/test/unit/crash-recovery.test.ts +1 -0
  144. package/test/unit/escalation.test.ts +1 -0
  145. package/test/unit/execution/lifecycle/run-completion.test.ts +1 -0
  146. package/test/unit/execution/lifecycle/run-regression.test.ts +2 -0
  147. package/test/{execution → unit/execution}/pid-registry.test.ts +2 -1
  148. package/test/{execution → unit/execution}/structured-failure.test.ts +3 -2
  149. package/test/unit/execution-logging-stderr.test.ts +1 -0
  150. package/test/unit/execution-stage.test.ts +1 -0
  151. package/test/unit/fix-generator.test.ts +1 -0
  152. package/test/unit/greenfield.test.ts +1 -0
  153. package/test/unit/interaction/human-review-trigger.test.ts +1 -0
  154. package/test/unit/interaction-network-failures.test.ts +1 -0
  155. package/test/unit/interaction-plugins.test.ts +1 -0
  156. package/test/unit/logging/formatter.test.ts +1 -0
  157. package/test/unit/merge.test.ts +1 -0
  158. package/test/unit/pipeline/event-bus.test.ts +105 -0
  159. package/test/unit/pipeline/routing-partial-override.test.ts +1 -0
  160. package/test/unit/pipeline/runner-retry.test.ts +89 -0
  161. package/test/unit/pipeline/stages/autofix.test.ts +97 -0
  162. package/test/unit/pipeline/stages/rectify.test.ts +101 -0
  163. package/test/unit/pipeline/stages/regression-stage.test.ts +69 -0
  164. package/test/unit/pipeline/stages/verify.test.ts +1 -0
  165. package/test/unit/pipeline/subscribers/hooks.test.ts +45 -0
  166. package/test/unit/pipeline/subscribers/interaction.test.ts +31 -0
  167. package/test/unit/pipeline/subscribers/reporters.test.ts +90 -0
  168. package/test/unit/pipeline/verify-smart-runner.test.ts +1 -0
  169. package/test/unit/prd-auto-default.test.ts +1 -0
  170. package/test/unit/prd-failure-category.test.ts +1 -0
  171. package/test/unit/prd-get-next-story.test.ts +1 -0
  172. package/test/unit/precheck-checks.test.ts +1 -0
  173. package/test/unit/precheck-story-size-gate.test.ts +1 -0
  174. package/test/unit/precheck-types.test.ts +1 -0
  175. package/test/unit/prompts.test.ts +1 -0
  176. package/test/unit/rectification.test.ts +2 -1
  177. package/test/unit/registry.test.ts +1 -0
  178. package/test/unit/routing/routing-stability.test.ts +1 -0
  179. package/test/unit/routing/strategies/llm.test.ts +1 -0
  180. package/test/unit/routing-advanced.test.ts +313 -0
  181. package/test/unit/routing-core.test.ts +341 -0
  182. package/test/unit/routing-strategies.test.ts +442 -0
  183. package/test/unit/storyid-events.test.ts +1 -0
  184. package/test/{ui → unit/ui}/tui-controls.test.ts +8 -7
  185. package/test/{ui → unit/ui}/tui-cost-and-pty.test.ts +4 -3
  186. package/test/{ui → unit/ui}/tui-layout.test.ts +5 -4
  187. package/test/{ui → unit/ui}/tui-stories.test.ts +5 -4
  188. package/test/unit/{isolation.test.ts → unit-isolation.test.ts} +1 -0
  189. package/test/unit/{helpers.test.ts → utils-helpers.test.ts} +1 -0
  190. package/test/unit/verdict.test.ts +1 -0
  191. package/test/unit/verification/orchestrator-types.test.ts +54 -0
  192. package/test/unit/verification/orchestrator.test.ts +66 -0
  193. package/test/unit/verification/smart-runner-config.test.ts +1 -0
  194. package/test/unit/verification/smart-runner-discovery.test.ts +8 -7
  195. package/test/unit/verification/strategies/acceptance.test.ts +33 -0
  196. package/test/unit/verification/strategies/regression.test.ts +87 -0
  197. package/test/unit/verification/strategies/scoped.test.ts +100 -0
  198. package/test/unit/worktree-manager.test.ts +1 -0
  199. package/src/execution/lifecycle/story-hooks.ts +0 -38
  200. package/src/execution/post-verify.ts +0 -193
  201. package/src/execution/rectification.ts +0 -13
  202. package/src/execution/verification.ts +0 -72
  203. package/test/integration/rectification-flow.test.ts +0 -512
  204. package/test/integration/runner.test.ts +0 -1679
  205. package/test/integration/tdd-orchestrator.test.ts +0 -1762
  206. package/test/unit/execution/post-verify-regression.test.ts +0 -362
  207. package/test/unit/execution/post-verify.test.ts +0 -236
  208. package/test/unit/routing.test.ts +0 -1039
  209. /package/test/{integration → helpers}/helpers.test.ts +0 -0
  210. /package/test/integration/worktree/{merge.test.ts → worktree-merge.test.ts} +0 -0
@@ -0,0 +1,476 @@
1
+ // RE-ARCH: keep
2
+ /**
3
+ * Runner Tests — Story Batching + TDD Escalation
4
+ *
5
+ * Tests for grouping consecutive simple stories into batches,
6
+ * and TDD escalation handling (retryAsLite, failure category outcomes).
7
+ */
8
+
9
+ import { describe, expect, test } from "bun:test";
10
+ import { groupStoriesIntoBatches, precomputeBatchPlan } from "../../../src/execution/batching";
11
+ import type { StoryBatch } from "../../../src/execution/batching";
12
+ import { escalateTier } from "../../../src/execution/escalation";
13
+ import { buildBatchPrompt } from "../../../src/execution/prompts";
14
+ import { resolveMaxAttemptsOutcome } from "../../../src/execution/runner";
15
+ import type { UserStory } from "../../../src/prd";
16
+ import type { FailureCategory } from "../../../src/tdd/types";
17
+
18
+
19
+ describe("Queue Commands Before Batch Execution", () => {
20
+ test("SKIP command should filter story from batch before execution", () => {
21
+ // Simulate a batch of 3 simple stories: [US-001, US-002, US-003]
22
+ // User issues SKIP US-002 in .queue.txt
23
+ // Expected: Batch should only contain [US-001, US-003]
24
+ const batchStories: UserStory[] = [
25
+ {
26
+ id: "US-001",
27
+ title: "Simple 1",
28
+ description: "First story in batch",
29
+ acceptanceCriteria: ["AC1"],
30
+ tags: [],
31
+ dependencies: [],
32
+ status: "pending",
33
+ passes: false,
34
+ escalations: [],
35
+ attempts: 0,
36
+ routing: { complexity: "simple", modelTier: "fast", testStrategy: "test-after", reasoning: "simple" },
37
+ },
38
+ {
39
+ id: "US-002",
40
+ title: "Simple 2",
41
+ description: "Second story in batch (to be skipped)",
42
+ acceptanceCriteria: ["AC2"],
43
+ tags: [],
44
+ dependencies: [],
45
+ status: "pending",
46
+ passes: false,
47
+ escalations: [],
48
+ attempts: 0,
49
+ routing: { complexity: "simple", modelTier: "fast", testStrategy: "test-after", reasoning: "simple" },
50
+ },
51
+ {
52
+ id: "US-003",
53
+ title: "Simple 3",
54
+ description: "Third story in batch",
55
+ acceptanceCriteria: ["AC3"],
56
+ tags: [],
57
+ dependencies: [],
58
+ status: "pending",
59
+ passes: false,
60
+ escalations: [],
61
+ attempts: 0,
62
+ routing: { complexity: "simple", modelTier: "fast", testStrategy: "test-after", reasoning: "simple" },
63
+ },
64
+ ];
65
+
66
+ // Simulate SKIP command processing
67
+ const skipCommand = { type: "SKIP" as const, storyId: "US-002" };
68
+ const storyIndex = batchStories.findIndex((s) => s.id === skipCommand.storyId);
69
+
70
+ expect(storyIndex).toBe(1);
71
+
72
+ // Remove from batch
73
+ const filteredBatch = batchStories.filter((s) => s.id !== skipCommand.storyId);
74
+
75
+ expect(filteredBatch).toHaveLength(2);
76
+ expect(filteredBatch.map((s) => s.id)).toEqual(["US-001", "US-003"]);
77
+ expect(filteredBatch.every((s) => s.status === "pending")).toBe(true);
78
+ });
79
+
80
+ test("SKIP all stories in batch should result in empty batch and continue to next iteration", () => {
81
+ // Simulate a batch of 2 simple stories: [US-001, US-002]
82
+ // User issues SKIP US-001 and SKIP US-002
83
+ // Expected: Batch becomes empty, runner should continue to next iteration
84
+ const batchStories: UserStory[] = [
85
+ {
86
+ id: "US-001",
87
+ title: "Simple 1",
88
+ description: "First story",
89
+ acceptanceCriteria: ["AC1"],
90
+ tags: [],
91
+ dependencies: [],
92
+ status: "pending",
93
+ passes: false,
94
+ escalations: [],
95
+ attempts: 0,
96
+ routing: { complexity: "simple", modelTier: "fast", testStrategy: "test-after", reasoning: "simple" },
97
+ },
98
+ {
99
+ id: "US-002",
100
+ title: "Simple 2",
101
+ description: "Second story",
102
+ acceptanceCriteria: ["AC2"],
103
+ tags: [],
104
+ dependencies: [],
105
+ status: "pending",
106
+ passes: false,
107
+ escalations: [],
108
+ attempts: 0,
109
+ routing: { complexity: "simple", modelTier: "fast", testStrategy: "test-after", reasoning: "simple" },
110
+ },
111
+ ];
112
+
113
+ // Simulate SKIP commands
114
+ const skipCommands = [
115
+ { type: "SKIP" as const, storyId: "US-001" },
116
+ { type: "SKIP" as const, storyId: "US-002" },
117
+ ];
118
+
119
+ let filteredBatch = [...batchStories];
120
+ for (const cmd of skipCommands) {
121
+ filteredBatch = filteredBatch.filter((s) => s.id !== cmd.storyId);
122
+ }
123
+
124
+ expect(filteredBatch).toHaveLength(0);
125
+ // When batch is empty, runner should continue to next iteration
126
+ });
127
+
128
+ test("PAUSE command should halt execution before batch starts", () => {
129
+ // When PAUSE is issued before batch execution
130
+ // Expected: Execution stops, no stories are processed
131
+ const pauseCommand = "PAUSE" as const;
132
+ const batchStories: UserStory[] = [
133
+ {
134
+ id: "US-001",
135
+ title: "Simple 1",
136
+ description: "First story",
137
+ acceptanceCriteria: ["AC1"],
138
+ tags: [],
139
+ dependencies: [],
140
+ status: "pending",
141
+ passes: false,
142
+ escalations: [],
143
+ attempts: 0,
144
+ routing: { complexity: "simple", modelTier: "fast", testStrategy: "test-after", reasoning: "simple" },
145
+ },
146
+ ];
147
+
148
+ // Simulate PAUSE processing
149
+ let shouldHalt = false;
150
+ if (pauseCommand === "PAUSE") {
151
+ shouldHalt = true;
152
+ }
153
+
154
+ expect(shouldHalt).toBe(true);
155
+ // When halted, no stories should be executed
156
+ expect(batchStories.every((s) => s.status === "pending")).toBe(true);
157
+ });
158
+
159
+ test("SKIP command for story not in batch should still mark it as skipped", () => {
160
+ // Simulate batch [US-001, US-002] but user issues SKIP US-003
161
+ // Expected: US-003 is marked skipped even though not in current batch
162
+ const batchStories: UserStory[] = [
163
+ {
164
+ id: "US-001",
165
+ title: "Simple 1",
166
+ description: "First story",
167
+ acceptanceCriteria: ["AC1"],
168
+ tags: [],
169
+ dependencies: [],
170
+ status: "pending",
171
+ passes: false,
172
+ escalations: [],
173
+ attempts: 0,
174
+ routing: { complexity: "simple", modelTier: "fast", testStrategy: "test-after", reasoning: "simple" },
175
+ },
176
+ {
177
+ id: "US-002",
178
+ title: "Simple 2",
179
+ description: "Second story",
180
+ acceptanceCriteria: ["AC2"],
181
+ tags: [],
182
+ dependencies: [],
183
+ status: "pending",
184
+ passes: false,
185
+ escalations: [],
186
+ attempts: 0,
187
+ routing: { complexity: "simple", modelTier: "fast", testStrategy: "test-after", reasoning: "simple" },
188
+ },
189
+ ];
190
+
191
+ const skipCommand = { type: "SKIP" as const, storyId: "US-003" };
192
+ const storyIndex = batchStories.findIndex((s) => s.id === skipCommand.storyId);
193
+
194
+ // Story not found in batch
195
+ expect(storyIndex).toBe(-1);
196
+
197
+ // But should still be processed (in actual runner, PRD would be checked)
198
+ // This test validates the logic path exists
199
+ });
200
+
201
+ test("batch size reduction from 4 to 1 should disable batch execution flag", () => {
202
+ // Simulate batch [US-001, US-002, US-003, US-004]
203
+ // User issues SKIP US-002, SKIP US-003, SKIP US-004
204
+ // Expected: Only US-001 remains, isBatchExecution should be false
205
+ const batchStories: UserStory[] = [
206
+ {
207
+ id: "US-001",
208
+ title: "Simple 1",
209
+ description: "First story",
210
+ acceptanceCriteria: ["AC1"],
211
+ tags: [],
212
+ dependencies: [],
213
+ status: "pending",
214
+ passes: false,
215
+ escalations: [],
216
+ attempts: 0,
217
+ routing: { complexity: "simple", modelTier: "fast", testStrategy: "test-after", reasoning: "simple" },
218
+ },
219
+ {
220
+ id: "US-002",
221
+ title: "Simple 2",
222
+ description: "Second story",
223
+ acceptanceCriteria: ["AC2"],
224
+ tags: [],
225
+ dependencies: [],
226
+ status: "pending",
227
+ passes: false,
228
+ escalations: [],
229
+ attempts: 0,
230
+ routing: { complexity: "simple", modelTier: "fast", testStrategy: "test-after", reasoning: "simple" },
231
+ },
232
+ {
233
+ id: "US-003",
234
+ title: "Simple 3",
235
+ description: "Third story",
236
+ acceptanceCriteria: ["AC3"],
237
+ tags: [],
238
+ dependencies: [],
239
+ status: "pending",
240
+ passes: false,
241
+ escalations: [],
242
+ attempts: 0,
243
+ routing: { complexity: "simple", modelTier: "fast", testStrategy: "test-after", reasoning: "simple" },
244
+ },
245
+ {
246
+ id: "US-004",
247
+ title: "Simple 4",
248
+ description: "Fourth story",
249
+ acceptanceCriteria: ["AC4"],
250
+ tags: [],
251
+ dependencies: [],
252
+ status: "pending",
253
+ passes: false,
254
+ escalations: [],
255
+ attempts: 0,
256
+ routing: { complexity: "simple", modelTier: "fast", testStrategy: "test-after", reasoning: "simple" },
257
+ },
258
+ ];
259
+
260
+ let isBatchExecution = true; // Initially true for 4 stories
261
+ const skipCommands = [
262
+ { type: "SKIP" as const, storyId: "US-002" },
263
+ { type: "SKIP" as const, storyId: "US-003" },
264
+ { type: "SKIP" as const, storyId: "US-004" },
265
+ ];
266
+
267
+ let filteredBatch = [...batchStories];
268
+ for (const cmd of skipCommands) {
269
+ filteredBatch = filteredBatch.filter((s) => s.id !== cmd.storyId);
270
+ }
271
+
272
+ // Re-check batch flag
273
+ if (isBatchExecution && filteredBatch.length === 1) {
274
+ isBatchExecution = false;
275
+ }
276
+
277
+ expect(filteredBatch).toHaveLength(1);
278
+ expect(filteredBatch[0].id).toBe("US-001");
279
+ expect(isBatchExecution).toBe(false);
280
+ });
281
+
282
+ test("ABORT command should mark all pending stories as skipped", () => {
283
+ // When ABORT is issued
284
+ // Expected: All pending stories should be marked as skipped
285
+ const abortCommand = "ABORT" as const;
286
+ const allStories: UserStory[] = [
287
+ {
288
+ id: "US-001",
289
+ title: "Passed",
290
+ description: "Already done",
291
+ acceptanceCriteria: ["AC1"],
292
+ tags: [],
293
+ dependencies: [],
294
+ status: "passed",
295
+ passes: true,
296
+ escalations: [],
297
+ attempts: 1,
298
+ },
299
+ {
300
+ id: "US-002",
301
+ title: "Pending 1",
302
+ description: "Not started",
303
+ acceptanceCriteria: ["AC2"],
304
+ tags: [],
305
+ dependencies: [],
306
+ status: "pending",
307
+ passes: false,
308
+ escalations: [],
309
+ attempts: 0,
310
+ },
311
+ {
312
+ id: "US-003",
313
+ title: "Pending 2",
314
+ description: "Not started",
315
+ acceptanceCriteria: ["AC3"],
316
+ tags: [],
317
+ dependencies: [],
318
+ status: "pending",
319
+ passes: false,
320
+ escalations: [],
321
+ attempts: 0,
322
+ },
323
+ ];
324
+
325
+ // Simulate ABORT processing
326
+ let shouldAbort = false;
327
+ if (abortCommand === "ABORT") {
328
+ shouldAbort = true;
329
+ }
330
+
331
+ expect(shouldAbort).toBe(true);
332
+
333
+ // Filter pending stories that should be skipped
334
+ const pendingStories = allStories.filter((s) => s.status === "pending");
335
+ expect(pendingStories).toHaveLength(2);
336
+ expect(pendingStories.map((s) => s.id)).toEqual(["US-002", "US-003"]);
337
+
338
+ // In actual runner, these would be marked as skipped via markStorySkipped()
339
+ });
340
+ });
341
+
342
+
343
+ describe("TDD escalation attempts counting", () => {
344
+ const defaultTiers = [
345
+ { tier: "fast", attempts: 5 },
346
+ { tier: "balanced", attempts: 3 },
347
+ { tier: "powerful", attempts: 2 },
348
+ ];
349
+
350
+ test("attempts increment on each TDD escalation", () => {
351
+ // Simulate a TDD story escalating: fast(attempt 1) → balanced(attempt 2) → ...
352
+ let story: UserStory = {
353
+ id: "US-001",
354
+ title: "TDD Story",
355
+ description: "Complex TDD story",
356
+ acceptanceCriteria: ["All tests pass"],
357
+ tags: [],
358
+ dependencies: [],
359
+ status: "pending",
360
+ passes: false,
361
+ escalations: [],
362
+ attempts: 0,
363
+ routing: { complexity: "complex", modelTier: "fast", testStrategy: "three-session-tdd", reasoning: "complex" },
364
+ };
365
+
366
+ // Simulate escalation (what runner does)
367
+ story = {
368
+ ...story,
369
+ attempts: story.attempts + 1,
370
+ routing: story.routing ? { ...story.routing, modelTier: "balanced" } : undefined,
371
+ };
372
+
373
+ expect(story.attempts).toBe(1);
374
+ expect(story.routing?.modelTier).toBe("balanced");
375
+
376
+ // Second escalation
377
+ story = {
378
+ ...story,
379
+ attempts: story.attempts + 1,
380
+ routing: story.routing ? { ...story.routing, modelTier: "powerful" } : undefined,
381
+ };
382
+
383
+ expect(story.attempts).toBe(2);
384
+ expect(story.routing?.modelTier).toBe("powerful");
385
+ });
386
+
387
+ test("TDD story with retryAsLite gets lite strategy on first isolation-violation escalation", () => {
388
+ let story: UserStory = {
389
+ id: "US-001",
390
+ title: "TDD Story",
391
+ description: "Complex TDD story",
392
+ acceptanceCriteria: ["All tests pass"],
393
+ tags: [],
394
+ dependencies: [],
395
+ status: "pending",
396
+ passes: false,
397
+ escalations: [],
398
+ attempts: 0,
399
+ routing: { complexity: "complex", modelTier: "fast", testStrategy: "three-session-tdd", reasoning: "complex" },
400
+ };
401
+
402
+ // First escalation: isolation-violation → retryAsLite=true
403
+ const retryAsLite = true;
404
+ const nextTier = "balanced" as const;
405
+
406
+ story = {
407
+ ...story,
408
+ attempts: story.attempts + 1,
409
+ routing: story.routing
410
+ ? {
411
+ ...story.routing,
412
+ modelTier: nextTier,
413
+ ...(retryAsLite ? { testStrategy: "three-session-tdd-lite" as const } : {}),
414
+ }
415
+ : undefined,
416
+ };
417
+
418
+ expect(story.attempts).toBe(1);
419
+ expect(story.routing?.modelTier).toBe("balanced");
420
+ expect(story.routing?.testStrategy).toBe("three-session-tdd-lite");
421
+ });
422
+
423
+ test("second escalation after retryAsLite does NOT change strategy again", () => {
424
+ // Story is now in lite mode after first escalation
425
+ let story: UserStory = {
426
+ id: "US-001",
427
+ title: "TDD Story",
428
+ description: "Complex TDD story",
429
+ acceptanceCriteria: ["All tests pass"],
430
+ tags: [],
431
+ dependencies: [],
432
+ status: "pending",
433
+ passes: false,
434
+ escalations: [],
435
+ attempts: 1,
436
+ routing: {
437
+ complexity: "complex",
438
+ modelTier: "balanced",
439
+ testStrategy: "three-session-tdd-lite", // Already downgraded
440
+ reasoning: "complex",
441
+ },
442
+ };
443
+
444
+ // Second escalation: lite mode failure → retryAsLite is NOT set (only fires once)
445
+ const retryAsLite = false; // Not set on subsequent escalations
446
+ const nextTier = "powerful" as const;
447
+
448
+ story = {
449
+ ...story,
450
+ attempts: story.attempts + 1,
451
+ routing: story.routing
452
+ ? {
453
+ ...story.routing,
454
+ modelTier: nextTier,
455
+ ...(retryAsLite ? { testStrategy: "three-session-tdd-lite" as const } : {}),
456
+ }
457
+ : undefined,
458
+ };
459
+
460
+ expect(story.attempts).toBe(2);
461
+ expect(story.routing?.modelTier).toBe("powerful");
462
+ // Strategy remains lite (not reset) — retryAsLite only fires once
463
+ expect(story.routing?.testStrategy).toBe("three-session-tdd-lite");
464
+ });
465
+
466
+ test("max attempts check works correctly for TDD stories using total across tiers", () => {
467
+ const { calculateMaxIterations } = require("../../../src/execution/escalation");
468
+ const maxAttempts = calculateMaxIterations(defaultTiers);
469
+
470
+ // A TDD story at attempt 9 (one below max) should still be escalatable
471
+ expect(9 < maxAttempts).toBe(true);
472
+
473
+ // A TDD story at attempt 10 (= max) should NOT be escalatable
474
+ expect(10 < maxAttempts).toBe(false);
475
+ });
476
+ });
@@ -1,3 +1,4 @@
1
+ // RE-ARCH: keep
1
2
  /**
2
3
  * Integration Tests: Status File — runner + CLI (T2)
3
4
  *
@@ -15,7 +16,7 @@ import * as nodeFs from "node:fs";
15
16
  import * as fs from "node:fs/promises";
16
17
  import * as os from "node:os";
17
18
  import * as path from "node:path";
18
- import { ALL_AGENTS } from "../../src/agents/registry";
19
+ import { ALL_AGENTS } from "../../../src/agents/registry";
19
20
  import type {
20
21
  AgentAdapter,
21
22
  AgentCapabilities,
@@ -25,13 +26,13 @@ import type {
25
26
  DecomposeResult,
26
27
  PlanOptions,
27
28
  PlanResult,
28
- } from "../../src/agents/types";
29
- import { DEFAULT_CONFIG } from "../../src/config";
30
- import type { NaxConfig } from "../../src/config";
31
- import { run } from "../../src/execution/runner";
32
- import type { RunOptions } from "../../src/execution/runner";
33
- import type { NaxStatusFile } from "../../src/execution/status-file";
34
- import type { PRD } from "../../src/prd/types";
29
+ } from "../../../src/agents/types";
30
+ import { DEFAULT_CONFIG } from "../../../src/config";
31
+ import type { NaxConfig } from "../../../src/config";
32
+ import { run } from "../../../src/execution/runner";
33
+ import type { RunOptions } from "../../../src/execution/runner";
34
+ import type { NaxStatusFile } from "../../../src/execution/status-file";
35
+ import type { PRD } from "../../../src/prd/types";
35
36
 
36
37
  // ============================================================================
37
38
  // Mock agent (satisfies agent installation check in runner)
@@ -1,3 +1,4 @@
1
+ // RE-ARCH: keep
1
2
  /**
2
3
  * Status File Tests
3
4
  *
@@ -20,8 +21,8 @@ import {
20
21
  buildStatusSnapshot,
21
22
  countProgress,
22
23
  writeStatusFile,
23
- } from "../../src/execution/status-file";
24
- import type { PRD, UserStory } from "../../src/prd";
24
+ } from "../../../src/execution/status-file";
25
+ import type { PRD, UserStory } from "../../../src/prd";
25
26
 
26
27
  // ============================================================================
27
28
  // Helpers
@@ -1,3 +1,4 @@
1
+ // RE-ARCH: keep
1
2
  /**
2
3
  * StatusWriter Tests
3
4
  *
@@ -13,10 +14,10 @@ import { existsSync, readFileSync } from "node:fs";
13
14
  import { mkdtemp, rm } from "node:fs/promises";
14
15
  import { tmpdir } from "node:os";
15
16
  import { join } from "node:path";
16
- import type { NaxConfig } from "../../src/config";
17
- import type { NaxStatusFile } from "../../src/execution/status-file";
18
- import { StatusWriter, type StatusWriterContext } from "../../src/execution/status-writer";
19
- import type { PRD, UserStory } from "../../src/prd";
17
+ import type { NaxConfig } from "../../../src/config";
18
+ import type { NaxStatusFile } from "../../../src/execution/status-file";
19
+ import { StatusWriter, type StatusWriterContext } from "../../../src/execution/status-writer";
20
+ import type { PRD, UserStory } from "../../../src/prd";
20
21
 
21
22
  // ============================================================================
22
23
  // Helpers
@@ -1,3 +1,4 @@
1
+ // RE-ARCH: keep
1
2
  /**
2
3
  * Test: Verify storyId presence in JSONL events (BUG-020)
3
4
  *
@@ -9,14 +10,14 @@ import { afterEach, beforeEach, describe, expect, test } from "bun:test";
9
10
  import { mkdtempSync, rmSync } from "node:fs";
10
11
  import { tmpdir } from "node:os";
11
12
  import { join } from "node:path";
12
- import type { NaxConfig } from "../../src/config/schema";
13
- import { initLogger, resetLogger } from "../../src/logger";
14
- import { getLogger } from "../../src/logger";
15
- import type { LogEntry } from "../../src/logger/types";
16
- import { executionStage } from "../../src/pipeline/stages/execution";
17
- import { verifyStage } from "../../src/pipeline/stages/verify";
18
- import type { PipelineContext } from "../../src/pipeline/types";
19
- import type { PRD, UserStory } from "../../src/prd/types";
13
+ import type { NaxConfig } from "../../../src/config/schema";
14
+ import { initLogger, resetLogger } from "../../../src/logger";
15
+ import { getLogger } from "../../../src/logger";
16
+ import type { LogEntry } from "../../../src/logger/types";
17
+ import { executionStage } from "../../../src/pipeline/stages/execution";
18
+ import { verifyStage } from "../../../src/pipeline/stages/verify";
19
+ import type { PipelineContext } from "../../../src/pipeline/types";
20
+ import type { PRD, UserStory } from "../../../src/prd/types";
20
21
 
21
22
  /** Captured log entries */
22
23
  let capturedLogs: LogEntry[] = [];
@@ -1,3 +1,5 @@
1
+ // RE-ARCH: rewrite
2
+
1
3
  /**
2
4
  * Integration Tests: Interaction Chain → Pipeline (BUG-025)
3
5
  *
@@ -10,14 +12,16 @@
10
12
  */
11
13
 
12
14
  import { describe, expect, mock, test } from "bun:test";
13
- import type { NaxConfig } from "../../src/config";
14
- import { InteractionChain } from "../../src/interaction/chain";
15
- import { CLIInteractionPlugin } from "../../src/interaction/plugins/cli";
16
- import type { InteractionPlugin, InteractionRequest, InteractionResponse, TriggerName } from "../../src/interaction/types";
17
- import { TRIGGER_METADATA } from "../../src/interaction/types";
18
- import type { PipelineContext } from "../../src/pipeline/types";
19
- import type { SequentialExecutionContext } from "../../src/execution/sequential-executor";
20
- import type { PRD, UserStory } from "../../src/prd/types";
15
+ import type { NaxConfig } from "../../../src/config";
16
+ import { InteractionChain } from "../../../src/interaction/chain";
17
+ import { pipelineEventBus } from "../../../src/pipeline/event-bus";
18
+ import { wireInteraction } from "../../../src/pipeline/subscribers/interaction";
19
+ import { CLIInteractionPlugin } from "../../../src/interaction/plugins/cli";
20
+ import type { InteractionPlugin, InteractionRequest, InteractionResponse, TriggerName } from "../../../src/interaction/types";
21
+ import { TRIGGER_METADATA } from "../../../src/interaction/types";
22
+ import type { PipelineContext } from "../../../src/pipeline/types";
23
+ import type { SequentialExecutionContext } from "../../../src/execution/sequential-executor";
24
+ import type { PRD, UserStory } from "../../../src/prd/types";
21
25
 
22
26
  // ─────────────────────────────────────────────────────────────────────────────
23
27
  // Fixtures
@@ -227,9 +231,13 @@ describe("AC2: max retries triggers human-review interaction", () => {
227
231
 
228
232
  const prd: PRD = { ...basePrd, userStories: [exhaustedStory] };
229
233
 
234
+ // Wire interaction subscriber on the singleton bus (Phase 3: replaces direct executeTrigger call)
235
+ pipelineEventBus.clear();
236
+ wireInteraction(pipelineEventBus, chain, baseConfig as any);
237
+
230
238
  // Import and invoke the failure handler or sequential executor path
231
239
  // that should fire 'human-review' when a story has exceeded max retries
232
- const { handlePipelineFailure } = await import("../../src/execution/pipeline-result-handler");
240
+ const { handlePipelineFailure } = await import("../../../src/execution/pipeline-result-handler");
233
241
  await handlePipelineFailure(
234
242
  {
235
243
  config: baseConfig as NaxConfig,
@@ -282,6 +290,10 @@ describe("AC2: max retries triggers human-review interaction", () => {
282
290
  const { plugin, sentRequests } = buildCapturingPlugin();
283
291
  chain.register(plugin, 10);
284
292
 
293
+ // Wire interaction subscriber on the singleton bus (Phase 3)
294
+ pipelineEventBus.clear();
295
+ wireInteraction(pipelineEventBus, chain, baseConfig as any);
296
+
285
297
  const failingStory: UserStory = {
286
298
  ...baseStory,
287
299
  id: "US-FAILING",
@@ -292,12 +304,12 @@ describe("AC2: max retries triggers human-review interaction", () => {
292
304
  const prd: PRD = { ...basePrd, userStories: [failingStory] };
293
305
 
294
306
  // Verify the trigger is enabled
295
- const { isTriggerEnabled } = await import("../../src/interaction/triggers");
307
+ const { isTriggerEnabled } = await import("../../../src/interaction/triggers");
296
308
  const enabled = isTriggerEnabled("human-review" as TriggerName, baseConfig as NaxConfig);
297
309
  expect(enabled).toBe(true);
298
310
 
299
311
  // Call handlePipelineFailure to trigger the human-review request
300
- const { handlePipelineFailure } = await import("../../src/execution/pipeline-result-handler");
312
+ const { handlePipelineFailure } = await import("../../../src/execution/pipeline-result-handler");
301
313
  await handlePipelineFailure(
302
314
  {
303
315
  config: baseConfig as NaxConfig,
@@ -360,7 +372,7 @@ describe("AC2: max retries triggers human-review interaction", () => {
360
372
  chain.register(plugin, 10);
361
373
 
362
374
  // When human-review returns 'skip', the story outcome should be 'skipped'
363
- const { executeSequential } = await import("../../src/execution/sequential-executor");
375
+ const { executeSequential } = await import("../../../src/execution/sequential-executor");
364
376
 
365
377
  const exhaustedStory: UserStory = {
366
378
  ...baseStory,
@@ -414,7 +426,7 @@ describe("AC3: CLI interaction plugin for non-headless human-review", () => {
414
426
 
415
427
  test("initInteractionChain registers CLI plugin for non-headless mode", async () => {
416
428
  // FAILS if initInteractionChain doesn't register CLI plugin when headless=false
417
- const { initInteractionChain } = await import("../../src/interaction");
429
+ const { initInteractionChain } = await import("../../../src/interaction");
418
430
 
419
431
  const config = {
420
432
  ...baseConfig,
@@ -436,7 +448,7 @@ describe("AC3: CLI interaction plugin for non-headless human-review", () => {
436
448
 
437
449
  test("human-review request sent through CLI plugin contains all required fields", async () => {
438
450
  // FAILS until BUG-025 implements human-review trigger in TRIGGER_METADATA
439
- const { createTriggerRequest } = await import("../../src/interaction/triggers");
451
+ const { createTriggerRequest } = await import("../../../src/interaction/triggers");
440
452
 
441
453
  const request = createTriggerRequest(
442
454
  "human-review" as TriggerName,