@nathapp/nax 0.20.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 (233) hide show
  1. package/.claude/settings.json +15 -0
  2. package/.mcp.json +8 -0
  3. package/docs/20260304-review-nax.md +492 -0
  4. package/docs/ROADMAP.md +65 -18
  5. package/docs/adr/ADR-005-implementation-plan.md +655 -0
  6. package/docs/adr/ADR-005-pipeline-re-architecture.md +464 -0
  7. package/docs/specs/bug-039-orphan-processes.md +131 -0
  8. package/docs/specs/bug-040-review-rectification.md +82 -0
  9. package/docs/specs/bug-041-cross-story-test-isolation.md +88 -0
  10. package/docs/specs/bug-042-verifier-failure-capture.md +117 -0
  11. package/docs/specs/feat-010-smart-runner-git-history.md +96 -0
  12. package/docs/specs/feat-011-file-context-strategy.md +73 -0
  13. package/docs/specs/feat-012-tdd-writer-tier.md +79 -0
  14. package/docs/specs/feat-013-test-after-review.md +89 -0
  15. package/docs/specs/feat-014-heartbeat-observability.md +127 -0
  16. package/memory/topic/feat-010-baseref.md +28 -0
  17. package/memory/topic/feat-013-test-after-deprecation.md +22 -0
  18. package/nax/config.json +7 -4
  19. package/nax/features/bug-039-medium/prd.json +45 -0
  20. package/package.json +2 -2
  21. package/src/agents/claude.ts +109 -15
  22. package/src/config/types.ts +11 -0
  23. package/src/context/builder.ts +9 -1
  24. package/src/execution/dry-run.ts +81 -0
  25. package/src/execution/escalation/tier-outcome.ts +29 -44
  26. package/src/execution/executor-types.ts +65 -0
  27. package/src/execution/index.ts +0 -17
  28. package/src/execution/iteration-runner.ts +132 -0
  29. package/src/execution/lifecycle/index.ts +0 -1
  30. package/src/execution/lifecycle/run-regression.ts +5 -5
  31. package/src/execution/pipeline-result-handler.ts +51 -254
  32. package/src/execution/sequential-executor.ts +72 -315
  33. package/src/execution/story-selector.ts +75 -0
  34. package/src/pipeline/event-bus.ts +276 -0
  35. package/src/pipeline/runner.ts +51 -77
  36. package/src/pipeline/stages/autofix.ts +133 -0
  37. package/src/pipeline/stages/completion.ts +22 -30
  38. package/src/pipeline/stages/index.ts +30 -13
  39. package/src/pipeline/stages/rectify.ts +93 -0
  40. package/src/pipeline/stages/regression.ts +88 -0
  41. package/src/pipeline/stages/review.ts +19 -153
  42. package/src/pipeline/stages/verify.ts +19 -3
  43. package/src/pipeline/subscribers/hooks.ts +133 -0
  44. package/src/pipeline/subscribers/interaction.ts +68 -0
  45. package/src/pipeline/subscribers/reporters.ts +174 -0
  46. package/src/pipeline/types.ts +12 -1
  47. package/src/review/orchestrator.ts +105 -0
  48. package/src/review/runner.ts +39 -4
  49. package/src/routing/router.ts +3 -3
  50. package/src/routing/strategies/keyword.ts +5 -2
  51. package/src/routing/strategies/llm.ts +27 -1
  52. package/src/tdd/prompts.ts +1 -1
  53. package/src/utils/git.ts +49 -25
  54. package/src/verification/executor.ts +8 -2
  55. package/src/verification/index.ts +1 -1
  56. package/src/verification/orchestrator-types.ts +145 -0
  57. package/src/verification/orchestrator.ts +76 -0
  58. package/src/{execution/post-verify-rectification.ts → verification/rectification-loop.ts} +13 -20
  59. package/src/verification/{gate.ts → runners.ts} +17 -105
  60. package/src/verification/smart-runner.ts +6 -10
  61. package/src/verification/strategies/acceptance.ts +133 -0
  62. package/src/verification/strategies/regression.ts +90 -0
  63. package/src/verification/strategies/scoped.ts +123 -0
  64. package/test/COVERAGE-GAPS.md +333 -0
  65. package/test/{acceptance → e2e}/cm-003-default-view.test.ts +1 -0
  66. package/test/{integration/e2e.test.ts → e2e/plan-analyze-run.test.ts} +1 -0
  67. package/test/integration/{agent-validation.test.ts → cli/agent-validation.test.ts} +3 -3
  68. package/test/integration/{cli-config-default-edge-cases.test.ts → cli/cli-config-default-edge-cases.test.ts} +6 -5
  69. package/test/integration/{cli-config-default-view.test.ts → cli/cli-config-default-view.test.ts} +8 -7
  70. package/test/integration/{cli-config-diff.test.ts → cli/cli-config-diff.test.ts} +3 -2
  71. package/test/integration/{cli-config.test.ts → cli/cli-config.test.ts} +3 -2
  72. package/test/integration/{cli-diagnose.test.ts → cli/cli-diagnose.test.ts} +5 -4
  73. package/test/integration/{cli-logs.test.ts → cli/cli-logs.test.ts} +12 -3
  74. package/test/integration/{cli-plugins.test.ts → cli/cli-plugins.test.ts} +4 -3
  75. package/test/integration/{cli-precheck.test.ts → cli/cli-precheck.test.ts} +4 -3
  76. package/test/integration/{cli-run-headless.test.ts → cli/cli-run-headless.test.ts} +3 -2
  77. package/test/integration/{cli.test.ts → cli/cli.test.ts} +2 -1
  78. package/test/integration/{precheck-integration.test.ts → cli/precheck-integration.test.ts} +10 -9
  79. package/test/integration/{precheck-orchestrator.test.ts → cli/precheck-orchestrator.test.ts} +4 -3
  80. package/test/integration/{precheck.test.ts → cli/precheck.test.ts} +5 -4
  81. package/test/integration/{config-loader.test.ts → config/config-loader.test.ts} +2 -1
  82. package/test/integration/{config.test.ts → config/config.test.ts} +2 -2
  83. package/test/integration/config/merger.test.ts +1 -0
  84. package/test/integration/config/paths.test.ts +1 -0
  85. package/test/integration/{security-loader.test.ts → config/security-loader.test.ts} +2 -2
  86. package/test/integration/{context-integration.test.ts → context/context-integration.test.ts} +7 -6
  87. package/test/integration/{path-security.test.ts → context/context-path-security.test.ts} +2 -2
  88. package/test/integration/{context-provider-injection.test.ts → context/context-provider-injection.test.ts} +7 -6
  89. package/test/integration/{context-verification-integration.test.ts → context/context-verification-integration.test.ts} +5 -4
  90. package/test/integration/{s5-greenfield-fallback.test.ts → context/s5-greenfield-fallback.test.ts} +4 -3
  91. package/test/integration/{isolation.test.ts → execution/execution-isolation.test.ts} +1 -1
  92. package/test/integration/{execution.test.ts → execution/execution.test.ts} +8 -8
  93. package/test/integration/{parallel.test.ts → execution/parallel.test.ts} +2 -1
  94. package/test/integration/{prd-pause.test.ts → execution/prd-pause.test.ts} +2 -2
  95. package/test/integration/{prd-resolvers.test.ts → execution/prd-resolvers.test.ts} +3 -2
  96. package/test/integration/{progress.test.ts → execution/progress.test.ts} +1 -1
  97. package/test/integration/execution/runner-batching.test.ts +682 -0
  98. package/test/integration/{runner-config-plugins.test.ts → execution/runner-config-plugins.test.ts} +3 -2
  99. package/test/integration/execution/runner-escalation.test.ts +561 -0
  100. package/test/integration/{runner-fixes.test.ts → execution/runner-fixes.test.ts} +4 -3
  101. package/test/integration/{runner-plugin-integration.test.ts → execution/runner-plugin-integration.test.ts} +6 -5
  102. package/test/integration/execution/runner-queue-and-attempts.test.ts +476 -0
  103. package/test/integration/{status-file-integration.test.ts → execution/status-file-integration.test.ts} +9 -8
  104. package/test/integration/{status-file.test.ts → execution/status-file.test.ts} +3 -2
  105. package/test/integration/{status-writer.test.ts → execution/status-writer.test.ts} +5 -4
  106. package/test/integration/{story-id-in-events.test.ts → execution/story-id-in-events.test.ts} +9 -8
  107. package/test/integration/{interaction-chain-pipeline.test.ts → interaction/interaction-chain-pipeline.test.ts} +26 -14
  108. package/test/integration/{hooks.test.ts → pipeline/hooks.test.ts} +4 -2
  109. package/test/integration/{pipeline-acceptance.test.ts → pipeline/pipeline-acceptance.test.ts} +7 -6
  110. package/test/integration/{pipeline-events.test.ts → pipeline/pipeline-events.test.ts} +7 -6
  111. package/test/integration/{pipeline.test.ts → pipeline/pipeline.test.ts} +9 -7
  112. package/test/integration/{reporter-lifecycle.test.ts → pipeline/reporter-lifecycle.test.ts} +9 -7
  113. package/test/integration/{verify-stage.test.ts → pipeline/verify-stage.test.ts} +7 -5
  114. package/test/integration/{analyze-integration.test.ts → plan/analyze-integration.test.ts} +3 -2
  115. package/test/integration/{analyze-scanner.test.ts → plan/analyze-scanner.test.ts} +8 -7
  116. package/test/integration/{logger.test.ts → plan/logger.test.ts} +1 -1
  117. package/test/integration/{plan.test.ts → plan/plan.test.ts} +3 -3
  118. package/test/integration/plugins/config-integration.test.ts +1 -0
  119. package/test/integration/plugins/config-resolution.test.ts +1 -0
  120. package/test/integration/plugins/loader.test.ts +1 -0
  121. package/test/integration/plugins/{registry.test.ts → plugins-registry.test.ts} +1 -0
  122. package/test/integration/plugins/validator.test.ts +1 -0
  123. package/test/integration/{review-config-commands.test.ts → review/review-config-commands.test.ts} +4 -3
  124. package/test/integration/{review-config-schema.test.ts → review/review-config-schema.test.ts} +3 -2
  125. package/test/integration/{review-plugin-integration.test.ts → review/review-plugin-integration.test.ts} +5 -4
  126. package/test/integration/{review.test.ts → review/review.test.ts} +3 -2
  127. package/test/integration/routing/plugin-routing-advanced.test.ts +461 -0
  128. package/test/integration/{plugin-routing.test.ts → routing/plugin-routing-core.test.ts} +10 -404
  129. package/test/integration/{routing-stage-bug-021.test.ts → routing/routing-stage-bug-021.test.ts} +8 -7
  130. package/test/integration/{routing-stage-greenfield.test.ts → routing/routing-stage-greenfield.test.ts} +7 -6
  131. package/test/integration/{tdd-cleanup.test.ts → tdd/tdd-cleanup.test.ts} +1 -1
  132. package/test/integration/tdd/tdd-orchestrator-core.test.ts +565 -0
  133. package/test/integration/tdd/tdd-orchestrator-failureCategory.test.ts +355 -0
  134. package/test/integration/tdd/tdd-orchestrator-fallback.test.ts +311 -0
  135. package/test/integration/tdd/tdd-orchestrator-lite.test.ts +289 -0
  136. package/test/integration/tdd/tdd-orchestrator-prompts.test.ts +260 -0
  137. package/test/integration/tdd/tdd-orchestrator-verdict.test.ts +536 -0
  138. package/test/integration/tmp/headless-test/test.jsonl +30 -0
  139. package/test/integration/{test-scanner.test.ts → verification/test-scanner.test.ts} +1 -1
  140. package/test/integration/{verification-asset-check.test.ts → verification/verification-asset-check.test.ts} +3 -2
  141. package/test/unit/acceptance.test.ts +1 -0
  142. package/test/unit/agent-stderr-capture.test.ts +1 -0
  143. package/test/unit/agents/claude.test.ts +107 -0
  144. package/test/unit/analyze-classifier.test.ts +1 -0
  145. package/test/unit/auto-detect.test.ts +1 -0
  146. package/test/unit/cli-status.test.ts +1 -0
  147. package/test/unit/commands/common.test.ts +1 -0
  148. package/test/unit/commands/logs.test.ts +1 -0
  149. package/test/unit/commands/unlock.test.ts +1 -0
  150. package/test/unit/config/defaults.test.ts +1 -0
  151. package/test/unit/config/regression-gate-schema.test.ts +1 -0
  152. package/test/unit/config/smart-runner-flag.test.ts +1 -0
  153. package/test/unit/constitution-generators.test.ts +1 -0
  154. package/test/unit/constitution.test.ts +1 -0
  155. package/test/unit/context/context-autodetect.test.ts +297 -0
  156. package/test/unit/context/context-build.test.ts +575 -0
  157. package/test/unit/context/context-coverage.test.ts +236 -0
  158. package/test/unit/context/context-error.test.ts +93 -0
  159. package/test/unit/context/context-estimate-tokens.test.ts +201 -0
  160. package/test/unit/context/context-format.test.ts +302 -0
  161. package/test/unit/context/context-isolation.test.ts +267 -0
  162. package/test/unit/context/context-sort.test.ts +93 -0
  163. package/test/unit/context/context-story.test.ts +108 -0
  164. package/test/{context → unit/context}/prior-failures.test.ts +5 -4
  165. package/test/unit/context.test.ts +7 -3
  166. package/test/unit/crash-recovery.test.ts +1 -0
  167. package/test/unit/escalation.test.ts +1 -0
  168. package/test/unit/execution/lifecycle/run-completion.test.ts +1 -0
  169. package/test/unit/execution/lifecycle/run-regression.test.ts +2 -0
  170. package/test/{execution → unit/execution}/pid-registry.test.ts +2 -1
  171. package/test/{execution → unit/execution}/structured-failure.test.ts +3 -2
  172. package/test/unit/execution-logging-stderr.test.ts +1 -0
  173. package/test/unit/execution-stage.test.ts +1 -0
  174. package/test/unit/fix-generator.test.ts +1 -0
  175. package/test/unit/greenfield.test.ts +1 -0
  176. package/test/unit/interaction/human-review-trigger.test.ts +1 -0
  177. package/test/unit/interaction-network-failures.test.ts +1 -0
  178. package/test/unit/interaction-plugins.test.ts +1 -0
  179. package/test/unit/logging/formatter.test.ts +1 -0
  180. package/test/unit/merge.test.ts +1 -0
  181. package/test/unit/pipeline/event-bus.test.ts +105 -0
  182. package/test/unit/pipeline/routing-partial-override.test.ts +1 -0
  183. package/test/unit/pipeline/runner-retry.test.ts +89 -0
  184. package/test/unit/pipeline/stages/autofix.test.ts +97 -0
  185. package/test/unit/pipeline/stages/rectify.test.ts +101 -0
  186. package/test/unit/pipeline/stages/regression-stage.test.ts +69 -0
  187. package/test/unit/pipeline/stages/verify.test.ts +1 -0
  188. package/test/unit/pipeline/subscribers/hooks.test.ts +45 -0
  189. package/test/unit/pipeline/subscribers/interaction.test.ts +31 -0
  190. package/test/unit/pipeline/subscribers/reporters.test.ts +90 -0
  191. package/test/unit/pipeline/verify-smart-runner.test.ts +2 -1
  192. package/test/unit/prd-auto-default.test.ts +3 -2
  193. package/test/unit/prd-failure-category.test.ts +1 -0
  194. package/test/unit/prd-get-next-story.test.ts +1 -0
  195. package/test/unit/precheck-checks.test.ts +1 -0
  196. package/test/unit/precheck-story-size-gate.test.ts +1 -0
  197. package/test/unit/precheck-types.test.ts +1 -0
  198. package/test/unit/prompts.test.ts +1 -0
  199. package/test/unit/rectification.test.ts +2 -1
  200. package/test/unit/registry.test.ts +1 -0
  201. package/test/unit/routing/routing-stability.test.ts +2 -1
  202. package/test/unit/routing/strategies/llm.test.ts +251 -0
  203. package/test/unit/routing-advanced.test.ts +313 -0
  204. package/test/unit/routing-core.test.ts +341 -0
  205. package/test/unit/routing-strategies.test.ts +442 -0
  206. package/test/unit/storyid-events.test.ts +1 -0
  207. package/test/{ui → unit/ui}/tui-controls.test.ts +8 -7
  208. package/test/{ui → unit/ui}/tui-cost-and-pty.test.ts +4 -3
  209. package/test/{ui → unit/ui}/tui-layout.test.ts +5 -4
  210. package/test/{ui → unit/ui}/tui-stories.test.ts +5 -4
  211. package/test/unit/{isolation.test.ts → unit-isolation.test.ts} +1 -0
  212. package/test/unit/{helpers.test.ts → utils-helpers.test.ts} +1 -0
  213. package/test/unit/verdict.test.ts +1 -0
  214. package/test/unit/verification/orchestrator-types.test.ts +54 -0
  215. package/test/unit/verification/orchestrator.test.ts +66 -0
  216. package/test/unit/verification/smart-runner-config.test.ts +1 -0
  217. package/test/unit/verification/smart-runner-discovery.test.ts +8 -7
  218. package/test/unit/verification/strategies/acceptance.test.ts +33 -0
  219. package/test/unit/verification/strategies/regression.test.ts +87 -0
  220. package/test/unit/verification/strategies/scoped.test.ts +100 -0
  221. package/test/unit/worktree-manager.test.ts +1 -0
  222. package/src/execution/lifecycle/story-hooks.ts +0 -38
  223. package/src/execution/post-verify.ts +0 -193
  224. package/src/execution/rectification.ts +0 -13
  225. package/src/execution/verification.ts +0 -72
  226. package/test/integration/rectification-flow.test.ts +0 -512
  227. package/test/integration/runner.test.ts +0 -1679
  228. package/test/integration/tdd-orchestrator.test.ts +0 -1762
  229. package/test/unit/execution/post-verify-regression.test.ts +0 -362
  230. package/test/unit/execution/post-verify.test.ts +0 -236
  231. package/test/unit/routing.test.ts +0 -1039
  232. /package/test/{integration → helpers}/helpers.test.ts +0 -0
  233. /package/test/integration/worktree/{merge.test.ts → worktree-merge.test.ts} +0 -0
@@ -1,3 +1,4 @@
1
+ // RE-ARCH: keep
1
2
  /**
2
3
  * Smart Test Runner — 3-Pass Discovery Tests
3
4
  *
@@ -115,7 +116,7 @@ describe("Pass 2: importGrepFallback", () => {
115
116
  test("matches test file that imports the source by basename path", async () => {
116
117
  mockGlob(["test/unit/routing.test.ts"]);
117
118
  mockFileContent({
118
- "/repo/test/unit/routing.test.ts": `import { route } from "../../src/routing/strategies/llm";`,
119
+ "/repo/test/unit/routing.test.ts": `import { route } from "../../../src/routing/strategies/llm";`,
119
120
  });
120
121
 
121
122
  const result = await importGrepFallback(
@@ -145,7 +146,7 @@ describe("Pass 2: importGrepFallback", () => {
145
146
  test("does not match test file with no import reference", async () => {
146
147
  mockGlob(["test/unit/other.test.ts"]);
147
148
  mockFileContent({
148
- "/repo/test/unit/other.test.ts": `import { something } from "../../src/other/module";`,
149
+ "/repo/test/unit/other.test.ts": `import { something } from "../../../src/other/module";`,
149
150
  });
150
151
 
151
152
  const result = await importGrepFallback(
@@ -160,8 +161,8 @@ describe("Pass 2: importGrepFallback", () => {
160
161
  test("returns multiple matching test files", async () => {
161
162
  mockGlob(["test/unit/a.test.ts", "test/unit/b.test.ts"]);
162
163
  mockFileContent({
163
- "/repo/test/unit/a.test.ts": `import { fn } from "../src/utils/helper";`,
164
- "/repo/test/unit/b.test.ts": `import { fn } from "../../src/utils/helper";`,
164
+ "/repo/test/unit/a.test.ts": `import { fn } from "../../../src/utils/helper";`,
165
+ "/repo/test/unit/b.test.ts": `import { fn } from "../../../src/utils/helper";`,
165
166
  });
166
167
 
167
168
  const result = await importGrepFallback(
@@ -182,7 +183,7 @@ describe("Pass 2: importGrepFallback", () => {
182
183
  exists: () => Promise.resolve(true),
183
184
  text: () => {
184
185
  if (path.includes("broken")) throw new Error("read error");
185
- return Promise.resolve(`import { fn } from "../src/utils/helper";`);
186
+ return Promise.resolve(`import { fn } from "../../../src/utils/helper";`);
186
187
  },
187
188
  });
188
189
 
@@ -200,7 +201,7 @@ describe("Pass 2: importGrepFallback", () => {
200
201
  mockGlob(["test/unit/routing.test.ts"]);
201
202
  // Content contains both "/llm" and "routing/strategies/llm"
202
203
  mockFileContent({
203
- "/repo/test/unit/routing.test.ts": `import { classify } from "../../src/routing/strategies/llm";`,
204
+ "/repo/test/unit/routing.test.ts": `import { classify } from "../../../src/routing/strategies/llm";`,
204
205
  });
205
206
 
206
207
  const result = await importGrepFallback(
@@ -273,7 +274,7 @@ describe("Pass 3: full-suite fallback (empty return from both passes)", () => {
273
274
  };
274
275
  // biome-ignore lint/suspicious/noExplicitAny: mocking
275
276
  (Bun as any).file = (_path: string) => ({
276
- text: () => Promise.resolve(`import { x } from "../src/completely/different";`),
277
+ text: () => Promise.resolve(`import { x } from "../../../src/completely/different";`),
277
278
  });
278
279
 
279
280
  const result = await importGrepFallback(
@@ -0,0 +1,33 @@
1
+ // RE-ARCH: keep
2
+ import { describe, expect, test } from "bun:test";
3
+ import { AcceptanceStrategy } from "../../../../src/verification/strategies/acceptance";
4
+ import type { VerifyContext } from "../../../../src/verification/orchestrator-types";
5
+
6
+ function makeCtx(overrides: Partial<VerifyContext> = {}): VerifyContext {
7
+ return {
8
+ workdir: "/tmp/test-repo",
9
+ testCommand: "bun test",
10
+ timeoutSeconds: 60,
11
+ storyId: "US-001",
12
+ ...overrides,
13
+ };
14
+ }
15
+
16
+ describe("AcceptanceStrategy", () => {
17
+ test("name is acceptance", () => {
18
+ expect(new AcceptanceStrategy().name).toBe("acceptance");
19
+ });
20
+
21
+ test("returns SKIPPED when no acceptanceTestPath", async () => {
22
+ const result = await new AcceptanceStrategy().execute(makeCtx());
23
+ expect(result.status).toBe("SKIPPED");
24
+ expect(result.success).toBe(true);
25
+ });
26
+
27
+ test("returns SKIPPED when test file does not exist", async () => {
28
+ const result = await new AcceptanceStrategy().execute(
29
+ makeCtx({ acceptanceTestPath: "/nonexistent/path/acceptance.test.ts" }),
30
+ );
31
+ expect(result.status).toBe("SKIPPED");
32
+ });
33
+ });
@@ -0,0 +1,87 @@
1
+ // RE-ARCH: keep
2
+ import { describe, expect, test } from "bun:test";
3
+ import { RegressionStrategy, _regressionStrategyDeps } from "../../../../src/verification/strategies/regression";
4
+ import type { VerifyContext } from "../../../../src/verification/orchestrator-types";
5
+ import { DEFAULT_CONFIG } from "../../../../src/config";
6
+
7
+ function makeCtx(overrides: Partial<VerifyContext> = {}): VerifyContext {
8
+ return {
9
+ workdir: "/tmp/test-repo",
10
+ testCommand: "bun test",
11
+ timeoutSeconds: 120,
12
+ storyId: "US-001",
13
+ acceptOnTimeout: true,
14
+ config: DEFAULT_CONFIG,
15
+ ...overrides,
16
+ };
17
+ }
18
+
19
+ describe("RegressionStrategy", () => {
20
+ test("name is regression", () => {
21
+ expect(new RegressionStrategy().name).toBe("regression");
22
+ });
23
+
24
+ test("returns SKIPPED when gate disabled", async () => {
25
+ const config = {
26
+ ...DEFAULT_CONFIG,
27
+ execution: {
28
+ ...DEFAULT_CONFIG.execution,
29
+ regressionGate: { enabled: false, timeoutSeconds: 120, mode: "inline" as const },
30
+ },
31
+ };
32
+ const result = await new RegressionStrategy().execute(makeCtx({ config }));
33
+ expect(result.status).toBe("SKIPPED");
34
+ expect(result.success).toBe(true);
35
+ });
36
+
37
+ test("returns PASS when tests pass", async () => {
38
+ const saved = { ...(_regressionStrategyDeps as any) };
39
+ _regressionStrategyDeps.runVerification = async () => ({
40
+ success: true,
41
+ status: "SUCCESS" as const,
42
+ countsTowardEscalation: false,
43
+ output: "10 pass",
44
+ });
45
+
46
+ const result = await new RegressionStrategy().execute(makeCtx());
47
+
48
+ Object.assign(_regressionStrategyDeps, saved);
49
+
50
+ expect(result.success).toBe(true);
51
+ expect(result.status).toBe("PASS");
52
+ expect(result.strategy).toBe("regression");
53
+ });
54
+
55
+ test("accepts TIMEOUT as pass when acceptOnTimeout=true", async () => {
56
+ const saved = { ...(_regressionStrategyDeps as any) };
57
+ _regressionStrategyDeps.runVerification = async () => ({
58
+ success: false,
59
+ status: "TIMEOUT" as const,
60
+ countsTowardEscalation: false,
61
+ });
62
+
63
+ const result = await new RegressionStrategy().execute(makeCtx({ acceptOnTimeout: true }));
64
+
65
+ Object.assign(_regressionStrategyDeps, saved);
66
+
67
+ expect(result.success).toBe(true);
68
+ expect(result.status).toBe("PASS");
69
+ });
70
+
71
+ test("returns TEST_FAILURE when tests fail", async () => {
72
+ const saved = { ...(_regressionStrategyDeps as any) };
73
+ _regressionStrategyDeps.runVerification = async () => ({
74
+ success: false,
75
+ status: "TEST_FAILURE" as const,
76
+ countsTowardEscalation: true,
77
+ output: "(fail) x > y\n2 fail",
78
+ });
79
+
80
+ const result = await new RegressionStrategy().execute(makeCtx({ acceptOnTimeout: false }));
81
+
82
+ Object.assign(_regressionStrategyDeps, saved);
83
+
84
+ expect(result.success).toBe(false);
85
+ expect(result.status).toBe("TEST_FAILURE");
86
+ });
87
+ });
@@ -0,0 +1,100 @@
1
+ // RE-ARCH: keep
2
+ import { describe, expect, test } from "bun:test";
3
+ import { ScopedStrategy, _scopedDeps } from "../../../../src/verification/strategies/scoped";
4
+ import type { VerifyContext } from "../../../../src/verification/orchestrator-types";
5
+
6
+ function makeCtx(overrides: Partial<VerifyContext> = {}): VerifyContext {
7
+ return {
8
+ workdir: "/tmp/test-repo",
9
+ testCommand: "bun test",
10
+ timeoutSeconds: 60,
11
+ storyId: "US-001",
12
+ storyGitRef: "abc123",
13
+ regressionMode: "deferred",
14
+ acceptOnTimeout: true,
15
+ ...overrides,
16
+ };
17
+ }
18
+
19
+ describe("ScopedStrategy", () => {
20
+ test("name is scoped", () => {
21
+ expect(new ScopedStrategy().name).toBe("scoped");
22
+ });
23
+
24
+ test("returns SKIPPED when deferred mode and no mapped tests", async () => {
25
+ const saved = { ..._scopedDeps };
26
+ _scopedDeps.getChangedSourceFiles = async () => [];
27
+ _scopedDeps.mapSourceToTests = async () => [];
28
+ _scopedDeps.importGrepFallback = async () => [];
29
+
30
+ const result = await new ScopedStrategy().execute(makeCtx({ regressionMode: "deferred" }));
31
+
32
+ Object.assign(_scopedDeps, saved);
33
+
34
+ expect(result.status).toBe("SKIPPED");
35
+ expect(result.success).toBe(true);
36
+ expect(result.countsTowardEscalation).toBe(false);
37
+ });
38
+
39
+ test("runs full suite when inline mode and no mapped tests", async () => {
40
+ const saved = { ..._scopedDeps };
41
+ _scopedDeps.getChangedSourceFiles = async () => [];
42
+ _scopedDeps.mapSourceToTests = async () => [];
43
+ _scopedDeps.importGrepFallback = async () => [];
44
+ _scopedDeps.regression = async () => ({
45
+ success: true,
46
+ status: "SUCCESS" as const,
47
+ countsTowardEscalation: false,
48
+ output: "1 pass",
49
+ });
50
+
51
+ const result = await new ScopedStrategy().execute(makeCtx({ regressionMode: "inline" }));
52
+
53
+ Object.assign(_scopedDeps, saved);
54
+
55
+ expect(result.success).toBe(true);
56
+ expect(result.status).toBe("PASS");
57
+ });
58
+
59
+ test("returns PASS when tests pass", async () => {
60
+ const saved = { ..._scopedDeps };
61
+ _scopedDeps.getChangedSourceFiles = async () => ["src/foo.ts"];
62
+ _scopedDeps.mapSourceToTests = async () => ["test/unit/foo.test.ts"];
63
+ _scopedDeps.buildSmartTestCommand = (_files: string[], cmd: string) => `${cmd} test/unit/foo.test.ts`;
64
+ _scopedDeps.regression = async () => ({
65
+ success: true,
66
+ status: "SUCCESS" as const,
67
+ countsTowardEscalation: false,
68
+ output: "5 pass\n0 fail",
69
+ });
70
+
71
+ const result = await new ScopedStrategy().execute(makeCtx());
72
+
73
+ Object.assign(_scopedDeps, saved);
74
+
75
+ expect(result.success).toBe(true);
76
+ expect(result.status).toBe("PASS");
77
+ expect(result.strategy).toBe("scoped");
78
+ });
79
+
80
+ test("returns TEST_FAILURE when tests fail", async () => {
81
+ const saved = { ..._scopedDeps };
82
+ _scopedDeps.getChangedSourceFiles = async () => ["src/foo.ts"];
83
+ _scopedDeps.mapSourceToTests = async () => ["test/unit/foo.test.ts"];
84
+ _scopedDeps.buildSmartTestCommand = (_files: string[], cmd: string) => cmd;
85
+ _scopedDeps.regression = async () => ({
86
+ success: false,
87
+ status: "TEST_FAILURE" as const,
88
+ countsTowardEscalation: true,
89
+ output: "(fail) foo > bar\n1 fail",
90
+ });
91
+
92
+ const result = await new ScopedStrategy().execute(makeCtx());
93
+
94
+ Object.assign(_scopedDeps, saved);
95
+
96
+ expect(result.success).toBe(false);
97
+ expect(result.status).toBe("TEST_FAILURE");
98
+ expect(result.countsTowardEscalation).toBe(true);
99
+ });
100
+ });
@@ -1,3 +1,4 @@
1
+ // RE-ARCH: keep
1
2
  /**
2
3
  * Tests for src/worktree/manager.ts
3
4
  *
@@ -1,38 +0,0 @@
1
- /**
2
- * Story Lifecycle Hooks
3
- *
4
- * Centralizes reporter notification boilerplate for story lifecycle events:
5
- * - onStoryComplete (completed/paused/skipped/failed)
6
- */
7
-
8
- import { getSafeLogger } from "../../logger";
9
- import type { IReporter } from "../../plugins/types";
10
-
11
- export interface StoryCompleteEvent {
12
- runId: string;
13
- storyId: string;
14
- status: "completed" | "paused" | "skipped" | "failed";
15
- durationMs: number;
16
- cost: number;
17
- tier: string;
18
- testStrategy: string;
19
- }
20
-
21
- /**
22
- * Emit onStoryComplete event to all reporters
23
- *
24
- * Handles error recovery for individual reporter failures.
25
- */
26
- export async function emitStoryComplete(reporters: IReporter[], event: StoryCompleteEvent): Promise<void> {
27
- const logger = getSafeLogger();
28
-
29
- for (const reporter of reporters) {
30
- if (reporter.onStoryComplete) {
31
- try {
32
- await reporter.onStoryComplete(event);
33
- } catch (error) {
34
- logger?.warn("plugins", `Reporter '${reporter.name}' onStoryComplete failed`, { error });
35
- }
36
- }
37
- }
38
- }
@@ -1,193 +0,0 @@
1
- /**
2
- * Post-Agent Verification (ADR-003)
3
- *
4
- * Runs verification after the agent completes, reverts story state on failure.
5
- */
6
-
7
- import type { NaxConfig } from "../config";
8
- import { getSafeLogger } from "../logger";
9
- import type { StoryMetrics } from "../metrics";
10
- import type { PRD, StructuredFailure, UserStory, VerificationStage } from "../prd";
11
- import { getExpectedFiles, savePRD } from "../prd";
12
- import type { VerificationResult } from "../verification";
13
- import { parseBunTestOutput } from "../verification";
14
- import { revertStoriesOnFailure, runRectificationLoop } from "./post-verify-rectification";
15
- import { runVerification } from "./verification";
16
-
17
- /** Build a StructuredFailure from verification result and test output. */
18
- function buildStructuredFailure(
19
- story: UserStory,
20
- stage: VerificationStage,
21
- verificationResult: VerificationResult,
22
- summary: string,
23
- ): StructuredFailure {
24
- const testFailures =
25
- verificationResult.status === "TEST_FAILURE" && verificationResult.output
26
- ? _postVerifyDeps.parseBunTestOutput(verificationResult.output).failures.map((f) => ({
27
- file: f.file,
28
- testName: f.testName,
29
- error: f.error,
30
- stackTrace: f.stackTrace,
31
- }))
32
- : undefined;
33
-
34
- return {
35
- attempt: (story.attempts ?? 0) + 1,
36
- modelTier: story.routing?.modelTier ?? "unknown",
37
- stage,
38
- summary,
39
- testFailures: testFailures && testFailures.length > 0 ? testFailures : undefined,
40
- timestamp: new Date().toISOString(),
41
- };
42
- }
43
-
44
- export interface PostVerifyOptions {
45
- config: NaxConfig;
46
- prd: PRD;
47
- prdPath: string;
48
- workdir: string;
49
- featureDir?: string;
50
- story: UserStory;
51
- storiesToExecute: UserStory[];
52
- allStoryMetrics: StoryMetrics[];
53
- timeoutRetryCountMap: Map<string, number>;
54
- }
55
-
56
- export interface PostVerifyResult {
57
- passed: boolean;
58
- prd: PRD;
59
- }
60
-
61
- /**
62
- * Run post-agent verification and handle failure state.
63
- *
64
- * @design Shell command in config.quality.commands.test is operator-controlled,
65
- * not user/PRD input. No shell injection risk from untrusted sources.
66
- */
67
- export async function runPostAgentVerification(opts: PostVerifyOptions): Promise<PostVerifyResult> {
68
- const { config, prd, prdPath, workdir, featureDir, story, storiesToExecute, allStoryMetrics } = opts;
69
-
70
- if (!config.quality.commands.test) return { passed: true, prd };
71
-
72
- const rectificationEnabled = config.execution.rectification?.enabled ?? false;
73
- const regressionMode = config.execution.regressionGate?.mode;
74
-
75
- // Skip per-story regression gate only when explicitly set to deferred
76
- if (regressionMode === "deferred") {
77
- return { passed: true, prd };
78
- }
79
-
80
- // Run full-suite regression gate (per-story mode)
81
- const regressionGateResult = await runRegressionGate(config, workdir, story, rectificationEnabled);
82
-
83
- if (regressionGateResult.status === "passed" || regressionGateResult.status === "skipped") {
84
- return { passed: true, prd };
85
- }
86
-
87
- // Regression failed -- build StructuredFailure and revert stories
88
- // verificationResult is always set when status === "failed" (see RegressionGateResult)
89
- const regressionVerificationResult = regressionGateResult.verificationResult ?? {
90
- status: "TEST_FAILURE" as const,
91
- success: false,
92
- countsTowardEscalation: true,
93
- };
94
- const regressionFailure = buildStructuredFailure(
95
- story,
96
- "regression",
97
- regressionVerificationResult,
98
- "Full-suite regression detected",
99
- );
100
- const updatedPrd = await _postVerifyDeps.revertStoriesOnFailure({
101
- prd,
102
- prdPath,
103
- story,
104
- storiesToExecute,
105
- allStoryMetrics,
106
- featureDir,
107
- diagnosticContext: "REGRESSION: full-suite regression detected",
108
- countsTowardEscalation: true,
109
- priorFailure: regressionFailure,
110
- });
111
- return { passed: false, prd: updatedPrd };
112
- }
113
-
114
- interface RegressionGateResult {
115
- status: "passed" | "skipped" | "failed";
116
- verificationResult?: VerificationResult;
117
- }
118
-
119
- /** Run full-suite regression gate. */
120
- async function runRegressionGate(
121
- config: NaxConfig,
122
- workdir: string,
123
- story: UserStory,
124
- rectificationEnabled: boolean,
125
- ): Promise<RegressionGateResult> {
126
- const logger = getSafeLogger();
127
- const regressionGateEnabled = config.execution.regressionGate?.enabled ?? true;
128
-
129
- if (!regressionGateEnabled) {
130
- return { status: "skipped" };
131
- }
132
-
133
- logger?.info("regression-gate", "Running full-suite regression gate");
134
- const fullSuiteCommand = config.quality.commands.test ?? "bun test";
135
- const regressionResult = await _postVerifyDeps.runVerification({
136
- workingDirectory: workdir,
137
- expectedFiles: _postVerifyDeps.getExpectedFiles(story),
138
- command: fullSuiteCommand,
139
- timeoutSeconds: config.execution.regressionGate.timeoutSeconds,
140
- forceExit: config.quality.forceExit,
141
- detectOpenHandles: config.quality.detectOpenHandles,
142
- detectOpenHandlesRetries: config.quality.detectOpenHandlesRetries,
143
- timeoutRetryCount: 0,
144
- gracePeriodMs: config.quality.gracePeriodMs,
145
- drainTimeoutMs: config.quality.drainTimeoutMs,
146
- shell: config.quality.shell,
147
- stripEnvVars: config.quality.stripEnvVars,
148
- });
149
-
150
- if (regressionResult.success) {
151
- logger?.info("regression-gate", "Full-suite regression gate passed");
152
- return { status: "passed" };
153
- }
154
-
155
- // Handle timeout: accept as pass if configured (BUG-026)
156
- const acceptOnTimeout = config.execution.regressionGate?.acceptOnTimeout ?? true;
157
- if (regressionResult.status === "TIMEOUT" && acceptOnTimeout) {
158
- logger?.warn("regression-gate", "[BUG-026] Full-suite regression gate timed out (accepted as pass)");
159
- return { status: "passed" };
160
- }
161
-
162
- logger?.warn("regression-gate", "Full-suite regression detected", { status: regressionResult.status });
163
-
164
- // Attempt rectification on regression failures
165
- const isTestFailure = regressionResult.status === "TEST_FAILURE" && regressionResult.output;
166
- if (rectificationEnabled && isTestFailure && regressionResult.output) {
167
- const fixed = await _postVerifyDeps.runRectificationLoop({
168
- config,
169
- workdir,
170
- story,
171
- testCommand: fullSuiteCommand,
172
- timeoutSeconds: config.execution.regressionGate.timeoutSeconds,
173
- testOutput: regressionResult.output,
174
- promptPrefix:
175
- "# REGRESSION: Full-Suite Test Failures\n\nYour changes broke tests in the full suite. Fix these regressions.",
176
- });
177
- if (fixed) return { status: "passed" };
178
- }
179
-
180
- return { status: "failed", verificationResult: regressionResult };
181
- }
182
-
183
- /**
184
- * Swappable dependencies for testing (avoids mock.module() which leaks in Bun 1.x).
185
- */
186
- export const _postVerifyDeps = {
187
- parseBunTestOutput,
188
- runVerification,
189
- getExpectedFiles,
190
- savePRD,
191
- revertStoriesOnFailure,
192
- runRectificationLoop,
193
- };
@@ -1,13 +0,0 @@
1
- /**
2
- * Rectification Core Logic (v0.11)
3
- *
4
- * DEPRECATED: Use src/verification/rectification.ts instead.
5
- * This file is kept for backward compatibility only.
6
- */
7
-
8
- // Re-export from unified verification layer
9
- export {
10
- type RectificationState,
11
- shouldRetryRectification,
12
- createRectificationPrompt,
13
- } from "../verification/rectification";
@@ -1,72 +0,0 @@
1
- /**
2
- * ADR-003: Robust Orchestration Feedback Loop - Verification Module
3
- *
4
- * DEPRECATED: Use src/verification/ unified layer instead.
5
- * This file is kept for backward compatibility only.
6
- *
7
- * Implements Decisions 3-6:
8
- * - Pre-Flight Asset Verification
9
- * - Execution Guard (Verification Timeout)
10
- * - Smart Exit-Code Analysis
11
- * - Environment Normalization
12
- */
13
-
14
- // Re-export from unified verification layer
15
- export {
16
- type AssetVerificationResult,
17
- type TestExecutionResult as TimeoutExecutionResult,
18
- type TestOutputAnalysis,
19
- type VerificationResult,
20
- type VerificationStatus,
21
- verifyAssets,
22
- executeWithTimeout,
23
- parseTestOutput,
24
- getEnvironmentalEscalationThreshold,
25
- normalizeEnvironment,
26
- appendOpenHandlesFlag,
27
- appendForceExitFlag,
28
- buildTestCommand,
29
- } from "../verification";
30
-
31
- // Adapter function for backward compatibility
32
- import { fullSuite } from "../verification";
33
- import type { VerificationGateOptions } from "../verification/types";
34
-
35
- /**
36
- * Run complete verification flow with all ADR-003 safety checks.
37
- *
38
- * @deprecated Use fullSuite() from src/verification/gate.ts instead
39
- */
40
- export async function runVerification(options: {
41
- workingDirectory: string;
42
- expectedFiles?: string[];
43
- command: string;
44
- timeoutSeconds: number;
45
- forceExit?: boolean;
46
- detectOpenHandles?: boolean;
47
- detectOpenHandlesRetries?: number;
48
- timeoutRetryCount?: number;
49
- gracePeriodMs?: number;
50
- drainTimeoutMs?: number;
51
- shell?: string;
52
- stripEnvVars?: string[];
53
- cwd?: string;
54
- }) {
55
- // Map old options to new VerificationGateOptions
56
- const gateOptions: VerificationGateOptions = {
57
- workdir: options.workingDirectory,
58
- expectedFiles: options.expectedFiles,
59
- command: options.command,
60
- timeoutSeconds: options.timeoutSeconds,
61
- forceExit: options.forceExit,
62
- detectOpenHandles: options.detectOpenHandles,
63
- detectOpenHandlesRetries: options.detectOpenHandlesRetries,
64
- timeoutRetryCount: options.timeoutRetryCount,
65
- gracePeriodMs: options.gracePeriodMs,
66
- drainTimeoutMs: options.drainTimeoutMs,
67
- shell: options.shell,
68
- stripEnvVars: options.stripEnvVars,
69
- };
70
-
71
- return fullSuite(gateOptions);
72
- }