@nathapp/nax 0.21.0 → 0.22.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (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
@@ -7,6 +7,7 @@
7
7
 
8
8
  import type { PipelineStage } from "../types";
9
9
  import { acceptanceStage } from "./acceptance";
10
+ import { autofixStage } from "./autofix";
10
11
  import { completionStage } from "./completion";
11
12
  import { constitutionStage } from "./constitution";
12
13
  import { contextStage } from "./context";
@@ -14,6 +15,8 @@ import { executionStage } from "./execution";
14
15
  import { optimizerStage } from "./optimizer";
15
16
  import { promptStage } from "./prompt";
16
17
  import { queueCheckStage } from "./queue-check";
18
+ import { rectifyStage } from "./rectify";
19
+ import { regressionStage } from "./regression";
17
20
  import { reviewStage } from "./review";
18
21
  import { routingStage } from "./routing";
19
22
  import { verifyStage } from "./verify";
@@ -21,18 +24,21 @@ import { verifyStage } from "./verify";
21
24
  /**
22
25
  * Default pipeline stages in execution order.
23
26
  *
24
- * This is the standard pipeline for executing a story:
25
- * 1. Check for queue commands (PAUSE/ABORT/SKIP)
26
- * 2. Route (classify complexity → model tier)
27
- * 3. Load constitution (project coding standards)
28
- * 4. Build context (gather relevant code/docs)
29
- * 5. Assemble prompt (story + context + constitution)
30
- * 6. Optimize prompt (reduce token usage)
31
- * 7. Execute agent session (TDD or test-after)
32
- * 8. Verify output (tests pass, build succeeds)
33
- * 9. Review (quality checks, linting, etc.)
34
- * 10. Mark complete (save PRD, fire hooks, log progress)
35
- * 11. Acceptance (run acceptance tests when all stories complete)
27
+ * New stage order (ADR-005 Phase 2):
28
+ * 1. Check for queue commands (PAUSE/ABORT/SKIP)
29
+ * 2. Route (classify complexity → model tier)
30
+ * 3. Load constitution (project coding standards)
31
+ * 4. Build context (gather relevant code/docs)
32
+ * 5. Assemble prompt (story + context + constitution)
33
+ * 6. Optimize prompt (reduce token usage)
34
+ * 7. Execute agent session (TDD or test-after)
35
+ * 8. Verify output (tests pass scoped via smart-runner)
36
+ * 9. Rectify (fix test failures before escalating)
37
+ * 10. Review (quality checks: lint, typecheck, format)
38
+ * 11. Autofix (auto-fix lint/format before escalating)
39
+ * 12. Regression (full-suite gate, inline mode only)
40
+ * 13. Mark complete (save PRD, fire hooks, log progress)
41
+ * 14. Acceptance (run AC tests when all stories complete)
36
42
  */
37
43
  export const defaultPipeline: PipelineStage[] = [
38
44
  queueCheckStage,
@@ -43,11 +49,19 @@ export const defaultPipeline: PipelineStage[] = [
43
49
  optimizerStage,
44
50
  executionStage,
45
51
  verifyStage,
52
+ rectifyStage,
46
53
  reviewStage,
54
+ autofixStage,
55
+ regressionStage,
47
56
  completionStage,
48
- acceptanceStage,
49
57
  ];
50
58
 
59
+ /**
60
+ * Post-run pipeline stages — run once after all per-story iterations complete.
61
+ * Handles deferred regression and acceptance tests.
62
+ */
63
+ export const postRunPipeline: PipelineStage[] = [acceptanceStage];
64
+
51
65
  // Re-export individual stages for custom pipeline construction
52
66
  export { queueCheckStage } from "./queue-check";
53
67
  export { routingStage } from "./routing";
@@ -57,6 +71,9 @@ export { promptStage } from "./prompt";
57
71
  export { optimizerStage } from "./optimizer";
58
72
  export { executionStage } from "./execution";
59
73
  export { verifyStage } from "./verify";
74
+ export { rectifyStage } from "./rectify";
60
75
  export { reviewStage } from "./review";
76
+ export { autofixStage } from "./autofix";
77
+ export { regressionStage } from "./regression";
61
78
  export { completionStage } from "./completion";
62
79
  export { acceptanceStage } from "./acceptance";
@@ -0,0 +1,93 @@
1
+ // RE-ARCH: keep
2
+ /**
3
+ * Rectify Stage (ADR-005, Phase 2)
4
+ *
5
+ * Runs after a failed verify stage. Attempts to fix test failures by
6
+ * running a rectification loop (agent + re-verify cycle).
7
+ *
8
+ * Enabled only when ctx.verifyResult?.success === false.
9
+ *
10
+ * Returns:
11
+ * - `retry` fromStage:"verify" — rectification fixed the failures
12
+ * - `escalate` — max retries exhausted
13
+ */
14
+
15
+ import { getLogger } from "../../logger";
16
+ import { pipelineEventBus } from "../event-bus";
17
+ import type { PipelineContext, PipelineStage, StageResult } from "../types";
18
+
19
+ export const rectifyStage: PipelineStage = {
20
+ name: "rectify",
21
+
22
+ enabled(ctx: PipelineContext): boolean {
23
+ // Only run when verify failed
24
+ if (!ctx.verifyResult) return false;
25
+ if (ctx.verifyResult.success) return false;
26
+ // Only run when rectification is enabled in config
27
+ return ctx.config.execution.rectification?.enabled ?? false;
28
+ },
29
+
30
+ async execute(ctx: PipelineContext): Promise<StageResult> {
31
+ const logger = getLogger();
32
+ const { verifyResult } = ctx;
33
+
34
+ if (!verifyResult || verifyResult.success) {
35
+ return { action: "continue" };
36
+ }
37
+
38
+ const testOutput = verifyResult.rawOutput ?? "";
39
+ const maxRetries = ctx.config.execution.rectification?.maxRetries ?? 3;
40
+
41
+ logger.info("rectify", "Starting rectification loop", {
42
+ storyId: ctx.story.id,
43
+ failCount: verifyResult.failCount,
44
+ maxRetries,
45
+ });
46
+
47
+ ctx.rectifyAttempt = (ctx.rectifyAttempt ?? 0) + 1;
48
+ const rectifyAttempt = ctx.rectifyAttempt;
49
+
50
+ pipelineEventBus.emit({
51
+ type: "rectify:started",
52
+ storyId: ctx.story.id,
53
+ attempt: rectifyAttempt,
54
+ testOutput,
55
+ });
56
+
57
+ const testCommand = ctx.config.review?.commands?.test ?? ctx.config.quality.commands.test ?? "bun test";
58
+ const fixed = await _rectifyDeps.runRectificationLoop({
59
+ config: ctx.config,
60
+ workdir: ctx.workdir,
61
+ story: ctx.story,
62
+ testCommand,
63
+ timeoutSeconds: ctx.config.execution.verificationTimeoutSeconds,
64
+ testOutput,
65
+ });
66
+
67
+ pipelineEventBus.emit({
68
+ type: "rectify:completed",
69
+ storyId: ctx.story.id,
70
+ attempt: rectifyAttempt,
71
+ fixed,
72
+ });
73
+
74
+ if (fixed) {
75
+ logger.info("rectify", "Rectification succeeded — retrying verify", { storyId: ctx.story.id });
76
+ // Clear verifyResult so verify stage re-runs fresh
77
+ ctx.verifyResult = undefined;
78
+ return { action: "retry", fromStage: "verify" };
79
+ }
80
+
81
+ logger.warn("rectify", "Rectification exhausted — escalating", { storyId: ctx.story.id });
82
+ return {
83
+ action: "escalate",
84
+ reason: `Rectification exhausted after ${maxRetries} attempts (${verifyResult.failCount} test failures)`,
85
+ };
86
+ },
87
+ };
88
+
89
+ /**
90
+ * Injectable deps for testing.
91
+ */
92
+ import { runRectificationLoop } from "../../verification/rectification-loop";
93
+ export const _rectifyDeps = { runRectificationLoop };
@@ -0,0 +1,88 @@
1
+ // RE-ARCH: keep
2
+ /**
3
+ * Regression Stage (ADR-005, Phase 2)
4
+ *
5
+ * Runs a full-suite regression gate as part of the per-story pipeline,
6
+ * when regressionGate.mode === "per-story" AND verify passed.
7
+ *
8
+ * This replaces the per-story regression gate previously handled in
9
+ * src/execution/post-verify.ts (which will be deleted in Phase 4).
10
+ *
11
+ * Returns:
12
+ * - `continue` — full suite passed
13
+ * - `escalate` — full suite failed after optional rectification
14
+ */
15
+
16
+ import { getLogger } from "../../logger";
17
+ import { verificationOrchestrator } from "../../verification/orchestrator";
18
+ import type { VerifyContext } from "../../verification/orchestrator-types";
19
+ import { pipelineEventBus } from "../event-bus";
20
+ import type { PipelineContext, PipelineStage, StageResult } from "../types";
21
+
22
+ export const regressionStage: PipelineStage = {
23
+ name: "regression",
24
+
25
+ enabled(ctx: PipelineContext): boolean {
26
+ const mode = ctx.config.execution.regressionGate?.mode ?? "deferred";
27
+ if (mode !== "per-story") return false;
28
+ // Only run when verify passed (or was skipped/not set)
29
+ // Only run when verify passed (or was skipped/not set)
30
+ if (ctx.verifyResult && !ctx.verifyResult.success) return false;
31
+ const gateEnabled = ctx.config.execution.regressionGate?.enabled ?? true;
32
+ return gateEnabled;
33
+ },
34
+
35
+ async execute(ctx: PipelineContext): Promise<StageResult> {
36
+ const logger = getLogger();
37
+ const testCommand = ctx.config.review?.commands?.test ?? ctx.config.quality.commands.test ?? "bun test";
38
+ const timeoutSeconds = ctx.config.execution.regressionGate?.timeoutSeconds ?? 120;
39
+
40
+ logger.info("regression", "Running full-suite regression gate", { storyId: ctx.story.id });
41
+
42
+ const verifyCtx: VerifyContext = {
43
+ workdir: ctx.workdir,
44
+ testCommand,
45
+ timeoutSeconds,
46
+ storyId: ctx.story.id,
47
+ acceptOnTimeout: ctx.config.execution.regressionGate?.acceptOnTimeout ?? true,
48
+ config: ctx.config,
49
+ };
50
+
51
+ const result = await _regressionStageDeps.verifyRegression(verifyCtx);
52
+
53
+ pipelineEventBus.emit({ type: "verify:completed", storyId: ctx.story.id, result });
54
+
55
+ if (result.success) {
56
+ logger.info("regression", "Full-suite regression gate passed", { storyId: ctx.story.id });
57
+ return { action: "continue" };
58
+ }
59
+
60
+ if (result.status === "TIMEOUT") {
61
+ logger.warn("regression", "Regression gate timed out (accepted as pass)", { storyId: ctx.story.id });
62
+ return { action: "continue" };
63
+ }
64
+
65
+ logger.warn("regression", "Full-suite regression detected", {
66
+ storyId: ctx.story.id,
67
+ failCount: result.failCount,
68
+ });
69
+
70
+ pipelineEventBus.emit({
71
+ type: "regression:detected",
72
+ storyId: ctx.story.id,
73
+ failedTests: result.failCount,
74
+ });
75
+
76
+ return {
77
+ action: "escalate",
78
+ reason: `Full-suite regression: ${result.failCount} test(s) failing`,
79
+ };
80
+ },
81
+ };
82
+
83
+ /**
84
+ * Injectable deps for testing.
85
+ */
86
+ export const _regressionStageDeps = {
87
+ verifyRegression: (ctx: VerifyContext) => verificationOrchestrator.verifyRegression(ctx),
88
+ };
@@ -1,75 +1,19 @@
1
1
  /**
2
- * Review Stage
2
+ * Review Stage (ADR-005, Phase 2)
3
3
  *
4
- * Runs post-implementation review phase if enabled.
5
- * Checks code quality, tests, linting, etc. via review module.
6
- * After built-in checks, runs plugin reviewers if any are registered.
4
+ * Delegates to ReviewOrchestrator for built-in checks + plugin reviewers.
7
5
  *
8
6
  * @returns
9
7
  * - `continue`: Review passed
10
- * - `fail`: Review failed (hard failure)
11
- *
12
- * @example
13
- * ```ts
14
- * // Review enabled and passes
15
- * await reviewStage.execute(ctx);
16
- * // ctx.reviewResult: { success: true, totalDurationMs: 1500, ... }
17
- *
18
- * // Review enabled but fails
19
- * await reviewStage.execute(ctx);
20
- * // Returns: { action: "fail", reason: "Review failed: typecheck errors" }
21
- * ```
8
+ * - `escalate`: Built-in check failed (lint/typecheck) — autofix stage handles retry
9
+ * - `fail`: Plugin reviewer hard-failed
22
10
  */
23
11
 
24
- import { spawn } from "bun";
12
+ // RE-ARCH: rewrite
25
13
  import { getLogger } from "../../logger";
26
- import { runReview } from "../../review";
14
+ import { reviewOrchestrator } from "../../review/orchestrator";
27
15
  import type { PipelineContext, PipelineStage, StageResult } from "../types";
28
16
 
29
- /**
30
- * Get list of changed files from git.
31
- * Includes both staged and unstaged changes.
32
- *
33
- * @param workdir - Working directory
34
- * @returns Array of changed file paths
35
- */
36
- async function getChangedFiles(workdir: string): Promise<string[]> {
37
- try {
38
- // Get both staged and unstaged changes
39
- const [stagedProc, unstagedProc] = [
40
- spawn({
41
- cmd: ["git", "diff", "--name-only", "--cached"],
42
- cwd: workdir,
43
- stdout: "pipe",
44
- stderr: "pipe",
45
- }),
46
- spawn({
47
- cmd: ["git", "diff", "--name-only"],
48
- cwd: workdir,
49
- stdout: "pipe",
50
- stderr: "pipe",
51
- }),
52
- ];
53
-
54
- await Promise.all([stagedProc.exited, unstagedProc.exited]);
55
-
56
- const stagedFiles = (await new Response(stagedProc.stdout).text())
57
- .trim()
58
- .split("\n")
59
- .filter((line) => line.length > 0);
60
-
61
- const unstagedFiles = (await new Response(unstagedProc.stdout).text())
62
- .trim()
63
- .split("\n")
64
- .filter((line) => line.length > 0);
65
-
66
- // Combine and deduplicate
67
- return Array.from(new Set([...stagedFiles, ...unstagedFiles]));
68
- } catch {
69
- return [];
70
- }
71
- }
72
-
73
17
  export const reviewStage: PipelineStage = {
74
18
  name: "review",
75
19
  enabled: (ctx) => ctx.config.review.enabled,
@@ -77,105 +21,27 @@ export const reviewStage: PipelineStage = {
77
21
  async execute(ctx: PipelineContext): Promise<StageResult> {
78
22
  const logger = getLogger();
79
23
 
80
- logger.info("review", "Running review phase");
24
+ logger.info("review", "Running review phase", { storyId: ctx.story.id });
25
+
26
+ const result = await reviewOrchestrator.review(ctx.config.review, ctx.workdir, ctx.config.execution, ctx.plugins);
81
27
 
82
- // Run built-in checks (typecheck, lint, test)
83
- const reviewResult = await runReview(ctx.config.review, ctx.workdir, ctx.config.execution);
84
- ctx.reviewResult = reviewResult;
28
+ ctx.reviewResult = result.builtIn;
29
+
30
+ if (!result.success) {
31
+ if (result.pluginFailed) {
32
+ logger.error("review", `Plugin reviewer failed: ${result.failureReason}`, { storyId: ctx.story.id });
33
+ return { action: "fail", reason: `Review failed: ${result.failureReason}` };
34
+ }
85
35
 
86
- // BUG-030: Review failure (lint/typecheck) should escalate, not hard-fail.
87
- // Lint/typecheck errors are auto-fixable — give the agent a retry with error context.
88
- // Only plugin reviewer rejections are hard failures.
89
- if (!reviewResult.success) {
90
36
  logger.warn("review", "Review failed (built-in checks) — escalating for retry", {
91
- reason: reviewResult.failureReason,
37
+ reason: result.failureReason,
92
38
  storyId: ctx.story.id,
93
39
  });
94
- return { action: "escalate", reason: `Review failed: ${reviewResult.failureReason}` };
95
- }
96
-
97
- // Run plugin reviewers if any are registered
98
- if (ctx.plugins) {
99
- const pluginReviewers = ctx.plugins.getReviewers();
100
- if (pluginReviewers.length > 0) {
101
- logger.info("review", `Running ${pluginReviewers.length} plugin reviewer(s)`);
102
-
103
- const changedFiles = await getChangedFiles(ctx.workdir);
104
- const pluginReviewerResults: Array<{
105
- name: string;
106
- passed: boolean;
107
- output: string;
108
- exitCode?: number;
109
- error?: string;
110
- }> = [];
111
-
112
- for (const reviewer of pluginReviewers) {
113
- logger.info("review", `Running plugin reviewer: ${reviewer.name}`);
114
- try {
115
- const result = await reviewer.check(ctx.workdir, changedFiles);
116
-
117
- // Capture result for debugging
118
- pluginReviewerResults.push({
119
- name: reviewer.name,
120
- passed: result.passed,
121
- output: result.output,
122
- exitCode: result.exitCode,
123
- });
124
-
125
- if (!result.passed) {
126
- logger.error("review", `Plugin reviewer failed: ${reviewer.name}`, {
127
- output: result.output,
128
- storyId: ctx.story.id,
129
- });
130
-
131
- // Store results in review result before failing
132
- if (ctx.reviewResult) {
133
- ctx.reviewResult.pluginReviewers = pluginReviewerResults;
134
- }
135
-
136
- return {
137
- action: "fail",
138
- reason: `Review failed: plugin reviewer '${reviewer.name}' failed`,
139
- };
140
- }
141
-
142
- logger.info("review", `Plugin reviewer passed: ${reviewer.name}`);
143
- } catch (error) {
144
- const errorMsg = error instanceof Error ? error.message : String(error);
145
- logger.error("review", `Plugin reviewer error: ${reviewer.name}`, {
146
- error: errorMsg,
147
- storyId: ctx.story.id,
148
- });
149
-
150
- // Capture error for debugging
151
- pluginReviewerResults.push({
152
- name: reviewer.name,
153
- passed: false,
154
- output: "",
155
- error: errorMsg,
156
- });
157
-
158
- // Store results in review result before failing
159
- if (ctx.reviewResult) {
160
- ctx.reviewResult.pluginReviewers = pluginReviewerResults;
161
- }
162
-
163
- return {
164
- action: "fail",
165
- reason: `Review failed: plugin reviewer '${reviewer.name}' threw error`,
166
- };
167
- }
168
- }
169
-
170
- // Store successful plugin reviewer results
171
- if (ctx.reviewResult) {
172
- ctx.reviewResult.pluginReviewers = pluginReviewerResults;
173
- }
174
- }
40
+ return { action: "escalate", reason: `Review failed: ${result.failureReason}` };
175
41
  }
176
42
 
177
43
  logger.info("review", "Review passed", {
178
- durationMs: reviewResult.totalDurationMs,
44
+ durationMs: result.builtIn.totalDurationMs,
179
45
  storyId: ctx.story.id,
180
46
  });
181
47
  return { action: "continue" };
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * Verify Stage
3
3
  *
4
- * Verifies the agent\'s work meets basic requirements by running tests.
4
+ * Verifies the agent's work meets basic requirements by running tests.
5
5
  * This is a lightweight verification before the full review stage.
6
6
  *
7
7
  * @returns
@@ -11,7 +11,8 @@
11
11
 
12
12
  import type { SmartTestRunnerConfig } from "../../config/types";
13
13
  import { getLogger } from "../../logger";
14
- import { regression } from "../../verification/gate";
14
+ import type { VerifyStatus } from "../../verification/orchestrator-types";
15
+ import { regression } from "../../verification/runners";
15
16
  import { _smartRunnerDeps } from "../../verification/smart-runner";
16
17
  import type { PipelineContext, PipelineStage, StageResult } from "../types";
17
18
 
@@ -109,6 +110,21 @@ export const verifyStage: PipelineStage = {
109
110
  acceptOnTimeout: ctx.config.execution.regressionGate?.acceptOnTimeout ?? true,
110
111
  });
111
112
 
113
+ // Store result on context for rectify stage
114
+ ctx.verifyResult = {
115
+ success: result.success,
116
+ status: (result.status === "TIMEOUT" ? "TIMEOUT" : result.success ? "PASS" : "TEST_FAILURE") as VerifyStatus,
117
+ storyId: ctx.story.id,
118
+ strategy: "scoped",
119
+ passCount: result.passCount ?? 0,
120
+ failCount: result.failCount ?? 0,
121
+ totalCount: (result.passCount ?? 0) + (result.failCount ?? 0),
122
+ failures: [],
123
+ rawOutput: result.output,
124
+ durationMs: 0,
125
+ countsTowardEscalation: result.countsTowardEscalation,
126
+ };
127
+
112
128
  // HARD FAILURE: Tests must pass for story to be marked complete
113
129
  if (!result.success) {
114
130
  // BUG-019: Distinguish timeout from actual test failures
@@ -0,0 +1,133 @@
1
+ // RE-ARCH: keep
2
+ /**
3
+ * Hooks Subscriber (ADR-005, Phase 3 US-P3-001)
4
+ *
5
+ * Maps pipeline events to nax lifecycle hooks (on-start, on-story-start,
6
+ * on-story-complete, on-story-fail, on-pause, on-complete).
7
+ *
8
+ * Design:
9
+ * - All hook calls are fire-and-forget (matching prior fireHook behavior)
10
+ * - Errors in hooks are logged, never rethrown
11
+ * - Returns unsubscribe function for cleanup
12
+ */
13
+
14
+ import { hookCtx } from "../../execution/story-context";
15
+ import { type LoadedHooksConfig, fireHook } from "../../hooks";
16
+ import { getSafeLogger } from "../../logger";
17
+ import type { PipelineEventBus } from "../event-bus";
18
+
19
+ export type UnsubscribeFn = () => void;
20
+
21
+ /**
22
+ * Wire pipeline lifecycle hooks to the event bus.
23
+ *
24
+ * @param bus - The pipeline event bus
25
+ * @param hooks - Loaded hooks config (from nax.config)
26
+ * @param workdir - Working directory for hook script execution
27
+ * @param feature - Feature name (for hook context payload)
28
+ * @returns Unsubscribe function (call to remove all subscriptions)
29
+ */
30
+ export function wireHooks(
31
+ bus: PipelineEventBus,
32
+ hooks: LoadedHooksConfig,
33
+ workdir: string,
34
+ feature: string,
35
+ ): UnsubscribeFn {
36
+ const logger = getSafeLogger();
37
+
38
+ const safe = (name: string, fn: () => Promise<void>) => {
39
+ fn().catch((err) => logger?.warn("hooks-subscriber", `Hook "${name}" failed`, { error: String(err) }));
40
+ };
41
+
42
+ const unsubs: UnsubscribeFn[] = [];
43
+
44
+ // run:started → on-start
45
+ unsubs.push(
46
+ bus.on("run:started", (ev) => {
47
+ safe("on-start", () => fireHook(hooks, "on-start", hookCtx(feature, { status: "running" }), workdir));
48
+ }),
49
+ );
50
+
51
+ // story:started → on-story-start
52
+ unsubs.push(
53
+ bus.on("story:started", (ev) => {
54
+ safe("on-story-start", () =>
55
+ fireHook(
56
+ hooks,
57
+ "on-story-start",
58
+ hookCtx(feature, { storyId: ev.storyId, model: ev.modelTier, agent: ev.agent }),
59
+ workdir,
60
+ ),
61
+ );
62
+ }),
63
+ );
64
+
65
+ // story:completed → on-story-complete
66
+ unsubs.push(
67
+ bus.on("story:completed", (ev) => {
68
+ safe("on-story-complete", () =>
69
+ fireHook(
70
+ hooks,
71
+ "on-story-complete",
72
+ hookCtx(feature, { storyId: ev.storyId, status: "passed", cost: ev.cost }),
73
+ workdir,
74
+ ),
75
+ );
76
+ }),
77
+ );
78
+
79
+ // story:failed → on-story-fail
80
+ unsubs.push(
81
+ bus.on("story:failed", (ev) => {
82
+ safe("on-story-fail", () =>
83
+ fireHook(
84
+ hooks,
85
+ "on-story-fail",
86
+ hookCtx(feature, { storyId: ev.storyId, status: "failed", reason: ev.reason }),
87
+ workdir,
88
+ ),
89
+ );
90
+ }),
91
+ );
92
+
93
+ // story:paused → on-pause
94
+ unsubs.push(
95
+ bus.on("story:paused", (ev) => {
96
+ safe("on-pause (story)", () =>
97
+ fireHook(
98
+ hooks,
99
+ "on-pause",
100
+ hookCtx(feature, { storyId: ev.storyId, reason: ev.reason, cost: ev.cost }),
101
+ workdir,
102
+ ),
103
+ );
104
+ }),
105
+ );
106
+
107
+ // run:paused → on-pause
108
+ unsubs.push(
109
+ bus.on("run:paused", (ev) => {
110
+ safe("on-pause (run)", () =>
111
+ fireHook(
112
+ hooks,
113
+ "on-pause",
114
+ hookCtx(feature, { storyId: ev.storyId, reason: ev.reason, cost: ev.cost }),
115
+ workdir,
116
+ ),
117
+ );
118
+ }),
119
+ );
120
+
121
+ // run:completed → on-complete
122
+ unsubs.push(
123
+ bus.on("run:completed", (ev) => {
124
+ safe("on-complete", () =>
125
+ fireHook(hooks, "on-complete", hookCtx(feature, { status: "complete", cost: ev.totalCost ?? 0 }), workdir),
126
+ );
127
+ }),
128
+ );
129
+
130
+ return () => {
131
+ for (const u of unsubs) u();
132
+ };
133
+ }