@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
@@ -0,0 +1,108 @@
1
+ // RE-ARCH: keep
2
+ /**
3
+ * Tests for context builder module
4
+ */
5
+
6
+ // RE-ARCH: keep
7
+ /**
8
+ * Tests for context builder module
9
+ */
10
+
11
+ import { describe, expect, test } from "bun:test";
12
+ import fs from "node:fs/promises";
13
+ import os from "node:os";
14
+ import path from "node:path";
15
+ import {
16
+ buildContext,
17
+ createDependencyContext,
18
+ createErrorContext,
19
+ createFileContext,
20
+ createProgressContext,
21
+ createStoryContext,
22
+ estimateTokens,
23
+ formatContextAsMarkdown,
24
+ sortContextElements,
25
+ } from "../../../src/context/builder";
26
+ import type { ContextBudget, ContextElement, StoryContext } from "../../../src/context/types";
27
+ import type { PRD, UserStory } from "../../../src/prd";
28
+
29
+ // Helper to create test PRD
30
+ const createTestPRD = (stories: Partial<UserStory>[]): PRD => ({
31
+ project: "test-project",
32
+ feature: "test-feature",
33
+ branchName: "test-branch",
34
+ createdAt: new Date().toISOString(),
35
+ updatedAt: new Date().toISOString(),
36
+ userStories: stories.map((s, i) => ({
37
+ id: s.id || `US-${String(i + 1).padStart(3, "0")}`,
38
+ title: s.title || "Test Story",
39
+ description: s.description || "Test description",
40
+ acceptanceCriteria: s.acceptanceCriteria || ["AC1"],
41
+ dependencies: s.dependencies || [],
42
+ tags: s.tags || [],
43
+ status: s.status || "pending",
44
+ passes: s.passes ?? false,
45
+ escalations: s.escalations || [],
46
+ attempts: s.attempts || 0,
47
+ routing: s.routing,
48
+ priorErrors: s.priorErrors,
49
+ relevantFiles: s.relevantFiles,
50
+ contextFiles: s.contextFiles,
51
+ expectedFiles: s.expectedFiles,
52
+ })),
53
+ });
54
+
55
+ describe("Context Builder", () => {
56
+ describe("createStoryContext", () => {
57
+ test("should create story context element", () => {
58
+ const story: UserStory = {
59
+ id: "US-001",
60
+ title: "Test Story",
61
+ description: "Test description",
62
+ acceptanceCriteria: ["AC1", "AC2"],
63
+ dependencies: [],
64
+ tags: ["feature"],
65
+ status: "pending",
66
+ passes: false,
67
+ escalations: [],
68
+ attempts: 0,
69
+ };
70
+
71
+ const element = createStoryContext(story, 80);
72
+
73
+ expect(element.type).toBe("story");
74
+ expect(element.storyId).toBe("US-001");
75
+ expect(element.priority).toBe(80);
76
+ expect(element.content).toContain("US-001: Test Story");
77
+ expect(element.content).toContain("Test description");
78
+ expect(element.content).toContain("AC1");
79
+ expect(element.content).toContain("AC2");
80
+ expect(element.tokens).toBeGreaterThan(0);
81
+ });
82
+ });
83
+
84
+ describe("createDependencyContext", () => {
85
+ test("should create dependency context element", () => {
86
+ const story: UserStory = {
87
+ id: "US-002",
88
+ title: "Dependency Story",
89
+ description: "Dependency description",
90
+ acceptanceCriteria: ["AC1"],
91
+ dependencies: [],
92
+ tags: [],
93
+ status: "passed",
94
+ passes: true,
95
+ escalations: [],
96
+ attempts: 0,
97
+ };
98
+
99
+ const element = createDependencyContext(story, 50);
100
+
101
+ expect(element.type).toBe("dependency");
102
+ expect(element.storyId).toBe("US-002");
103
+ expect(element.priority).toBe(50);
104
+ expect(element.content).toContain("US-002: Dependency Story");
105
+ expect(element.tokens).toBeGreaterThan(0);
106
+ });
107
+ });
108
+ });
@@ -1,3 +1,4 @@
1
+ // RE-ARCH: keep
1
2
  /**
2
3
  * Unit tests for priorFailures context formatting
3
4
  *
@@ -5,10 +6,10 @@
5
6
  */
6
7
 
7
8
  import { describe, expect, test } from "bun:test";
8
- import { createPriorFailuresContext, formatPriorFailures } from "../../src/context/elements";
9
- import { buildContext, sortContextElements } from "../../src/context/builder";
10
- import type { StructuredFailure, UserStory } from "../../src/prd";
11
- import type { StoryContext } from "../../src/context/types";
9
+ import { createPriorFailuresContext, formatPriorFailures } from "../../../src/context/elements";
10
+ import { buildContext, sortContextElements } from "../../../src/context/builder";
11
+ import type { StructuredFailure, UserStory } from "../../../src/prd";
12
+ import type { StoryContext } from "../../../src/context/types";
12
13
 
13
14
  describe("formatPriorFailures", () => {
14
15
  test("should format a single prior failure correctly", () => {
@@ -1,3 +1,4 @@
1
+ // RE-ARCH: keep
1
2
  /**
2
3
  * Tests for context builder module
3
4
  */
@@ -1,3 +1,4 @@
1
+ // RE-ARCH: keep
1
2
  /**
2
3
  * Unit tests for crash recovery module (US-007)
3
4
  */
@@ -1,3 +1,4 @@
1
+ // RE-ARCH: keep
1
2
  /**
2
3
  * Tests for src/execution/escalation.ts
3
4
  *
@@ -1,3 +1,4 @@
1
+ // RE-ARCH: keep
1
2
  /**
2
3
  * run-completion.ts — Deferred regression gate invocation (US-003)
3
4
  *
@@ -1,3 +1,5 @@
1
+ // RE-ARCH: delete-with-source
2
+
1
3
  /**
2
4
  * Smart Runner Reverse Mapping Tests + Deferred Regression Gate Tests
3
5
  *
@@ -1,10 +1,11 @@
1
+ // RE-ARCH: keep
1
2
  /**
2
3
  * PID Registry Tests
3
4
  */
4
5
 
5
6
  import { afterEach, beforeEach, describe, expect, test } from "bun:test";
6
7
  import { existsSync, mkdirSync, rmSync } from "node:fs";
7
- import { PidRegistry } from "../../src/execution/pid-registry";
8
+ import { PidRegistry } from "../../../src/execution/pid-registry";
8
9
 
9
10
  const TEST_WORKDIR = "/tmp/nax-pid-registry-test";
10
11
  const PID_FILE = `${TEST_WORKDIR}/.nax-pids`;
@@ -1,3 +1,4 @@
1
+ // RE-ARCH: keep
1
2
  /**
2
3
  * Unit tests for StructuredFailure and priorFailures tracking
3
4
  *
@@ -5,8 +6,8 @@
5
6
  */
6
7
 
7
8
  import { describe, expect, test } from "bun:test";
8
- import { loadPRD } from "../../src/prd";
9
- import type { StructuredFailure, TestFailureContext, UserStory } from "../../src/prd";
9
+ import { loadPRD } from "../../../src/prd";
10
+ import type { StructuredFailure, TestFailureContext, UserStory } from "../../../src/prd";
10
11
 
11
12
  describe("StructuredFailure Type", () => {
12
13
  test("should have all required fields", () => {
@@ -1,3 +1,4 @@
1
+ // RE-ARCH: keep
1
2
  /**
2
3
  * Tests for exitCode and stderr in agent error scenarios
3
4
  *
@@ -1,3 +1,4 @@
1
+ // RE-ARCH: keep
1
2
  /**
2
3
  * Tests for src/pipeline/stages/execution.ts
3
4
  *
@@ -1,3 +1,4 @@
1
+ // RE-ARCH: keep
1
2
  /**
2
3
  * Fix Generator Tests
3
4
  */
@@ -1,3 +1,4 @@
1
+ // RE-ARCH: keep
1
2
  /**
2
3
  * Tests for src/context/greenfield.ts
3
4
  *
@@ -1,3 +1,4 @@
1
+ // RE-ARCH: keep
1
2
  /**
2
3
  * Unit Tests: human-review trigger (BUG-025)
3
4
  *
@@ -1,3 +1,4 @@
1
+ // RE-ARCH: keep
1
2
  /**
2
3
  * Interaction Plugins Network Failure Tests (v0.15.1)
3
4
  *
@@ -1,3 +1,4 @@
1
+ // RE-ARCH: keep
1
2
  /**
2
3
  * Interaction Plugins Unit Tests (v0.15.0 Phase 2)
3
4
  *
@@ -1,3 +1,4 @@
1
+ // RE-ARCH: keep
1
2
  /**
2
3
  * Tests for human-friendly logging formatter
3
4
  */
@@ -1,3 +1,4 @@
1
+ // RE-ARCH: keep
1
2
  /**
2
3
  * Tests for src/worktree/merge.ts
3
4
  *
@@ -0,0 +1,105 @@
1
+ // RE-ARCH: keep
2
+ import { describe, expect, test } from "bun:test";
3
+ import { PipelineEventBus } from "../../../src/pipeline/event-bus";
4
+ import type { PipelineEvent } from "../../../src/pipeline/event-bus";
5
+
6
+ function makeStoryCompletedEvent(): PipelineEvent {
7
+ return {
8
+ type: "story:completed",
9
+ storyId: "US-001",
10
+ story: { id: "US-001", title: "Test story", status: "passed", acceptanceCriteria: [] } as any,
11
+ passed: true,
12
+ durationMs: 1000,
13
+ };
14
+ }
15
+
16
+ describe("PipelineEventBus", () => {
17
+ test("subscribes and receives event", () => {
18
+ const bus = new PipelineEventBus();
19
+ const received: PipelineEvent[] = [];
20
+ bus.on("story:completed", (e) => received.push(e));
21
+
22
+ const evt = makeStoryCompletedEvent();
23
+ bus.emit(evt);
24
+
25
+ expect(received).toHaveLength(1);
26
+ expect(received[0]).toBe(evt);
27
+ });
28
+
29
+ test("onAll receives all event types", () => {
30
+ const bus = new PipelineEventBus();
31
+ const received: string[] = [];
32
+ bus.onAll((e) => received.push(e.type));
33
+
34
+ bus.emit(makeStoryCompletedEvent());
35
+ bus.emit({ type: "run:completed", totalStories: 1, passedStories: 1, failedStories: 0, durationMs: 5000 });
36
+
37
+ expect(received).toEqual(["story:completed", "run:completed"]);
38
+ });
39
+
40
+ test("unsubscribe stops receiving events", () => {
41
+ const bus = new PipelineEventBus();
42
+ const received: PipelineEvent[] = [];
43
+ const unsub = bus.on("story:completed", (e) => received.push(e));
44
+
45
+ bus.emit(makeStoryCompletedEvent());
46
+ unsub();
47
+ bus.emit(makeStoryCompletedEvent());
48
+
49
+ expect(received).toHaveLength(1);
50
+ });
51
+
52
+ test("subscriber error does not prevent other subscribers from running", () => {
53
+ const bus = new PipelineEventBus();
54
+ const results: string[] = [];
55
+
56
+ bus.on("story:completed", () => { throw new Error("boom"); });
57
+ bus.on("story:completed", () => results.push("second"));
58
+
59
+ bus.emit(makeStoryCompletedEvent());
60
+
61
+ expect(results).toEqual(["second"]);
62
+ });
63
+
64
+ test("subscriberCount returns correct count", () => {
65
+ const bus = new PipelineEventBus();
66
+ expect(bus.subscriberCount("story:completed")).toBe(0);
67
+
68
+ bus.on("story:completed", () => {});
69
+ bus.on("story:completed", () => {});
70
+ expect(bus.subscriberCount("story:completed")).toBe(2);
71
+ });
72
+
73
+ test("clear removes all subscribers", () => {
74
+ const bus = new PipelineEventBus();
75
+ const received: PipelineEvent[] = [];
76
+ bus.on("story:completed", (e) => received.push(e));
77
+ bus.clear();
78
+ bus.emit(makeStoryCompletedEvent());
79
+
80
+ expect(received).toHaveLength(0);
81
+ });
82
+
83
+ test("emitAsync awaits all async subscribers", async () => {
84
+ const bus = new PipelineEventBus();
85
+ let resolved = false;
86
+
87
+ bus.on("story:completed", async () => {
88
+ await new Promise((r) => setTimeout(r, 10));
89
+ resolved = true;
90
+ });
91
+
92
+ await bus.emitAsync(makeStoryCompletedEvent());
93
+ expect(resolved).toBe(true);
94
+ });
95
+
96
+ test("does not deliver typed events to wrong subscriber", () => {
97
+ const bus = new PipelineEventBus();
98
+ const received: PipelineEvent[] = [];
99
+
100
+ bus.on("run:completed", (e) => received.push(e));
101
+ bus.emit(makeStoryCompletedEvent());
102
+
103
+ expect(received).toHaveLength(0);
104
+ });
105
+ });
@@ -1,3 +1,4 @@
1
+ // RE-ARCH: keep
1
2
  /**
2
3
  * Tests for partial routing override in routing stage (FIX-001)
3
4
  *
@@ -0,0 +1,89 @@
1
+ // RE-ARCH: keep
2
+ import { describe, expect, test } from "bun:test";
3
+ import { MAX_STAGE_RETRIES, runPipeline } from "../../../src/pipeline/runner";
4
+ import type { PipelineContext, PipelineStage } from "../../../src/pipeline/types";
5
+ import { DEFAULT_CONFIG } from "../../../src/config";
6
+
7
+ function makeCtx(): PipelineContext {
8
+ return {
9
+ config: DEFAULT_CONFIG,
10
+ prd: { stories: [], acceptanceOverrides: {} } as any,
11
+ story: { id: "US-001", title: "t", status: "pending", acceptanceCriteria: [] } as any,
12
+ stories: [],
13
+ routing: { complexity: "simple", modelTier: "fast", testStrategy: "test-after", reasoning: "" },
14
+ workdir: "/tmp",
15
+ hooks: {},
16
+ };
17
+ }
18
+
19
+ function stage(name: string, action: () => import("../../../src/pipeline/types").StageResult | Promise<import("../../../src/pipeline/types").StageResult>): PipelineStage {
20
+ return { name, enabled: () => true, execute: async () => action() };
21
+ }
22
+
23
+ describe("runPipeline retry action", () => {
24
+ test("retry jumps back to named stage", async () => {
25
+ const order: string[] = [];
26
+ let attempt = 0;
27
+
28
+ const stages = [
29
+ stage("a", () => { order.push("a"); return { action: "continue" }; }),
30
+ stage("b", () => { order.push("b"); return { action: "continue" }; }),
31
+ stage("c", () => {
32
+ order.push("c");
33
+ attempt++;
34
+ if (attempt < 2) return { action: "retry", fromStage: "b" };
35
+ return { action: "continue" };
36
+ }),
37
+ stage("d", () => { order.push("d"); return { action: "continue" }; }),
38
+ ];
39
+
40
+ const result = await runPipeline(stages, makeCtx());
41
+
42
+ expect(result.finalAction).toBe("complete");
43
+ expect(order).toEqual(["a", "b", "c", "b", "c", "d"]);
44
+ });
45
+
46
+ test("retry fails after MAX_STAGE_RETRIES exceeded", async () => {
47
+ let calls = 0;
48
+ const stages = [
49
+ stage("verify", () => { return { action: "continue" }; }),
50
+ stage("rectify", () => { calls++; return { action: "retry", fromStage: "verify" }; }),
51
+ ];
52
+
53
+ const result = await runPipeline(stages, makeCtx());
54
+
55
+ expect(result.finalAction).toBe("fail");
56
+ expect(calls).toBe(MAX_STAGE_RETRIES + 1);
57
+ expect(result.reason).toContain("exceeded max retries");
58
+ });
59
+
60
+ test("retry to unknown stage escalates", async () => {
61
+ const stages = [
62
+ stage("a", () => ({ action: "retry", fromStage: "nonexistent" })),
63
+ ];
64
+
65
+ const result = await runPipeline(stages, makeCtx());
66
+ expect(result.finalAction).toBe("escalate");
67
+ expect(result.reason).toContain("not found");
68
+ });
69
+
70
+ test("disabled stages are skipped during retry", async () => {
71
+ const order: string[] = [];
72
+ let attempt = 0;
73
+
74
+ const stages = [
75
+ stage("verify", () => { order.push("verify"); return { action: "continue" }; }),
76
+ { name: "disabled", enabled: () => false, execute: async () => { order.push("disabled"); return { action: "continue" as const }; } },
77
+ stage("rectify", () => {
78
+ order.push("rectify");
79
+ attempt++;
80
+ if (attempt < 2) return { action: "retry", fromStage: "verify" };
81
+ return { action: "continue" };
82
+ }),
83
+ ];
84
+
85
+ await runPipeline(stages, makeCtx());
86
+ expect(order).not.toContain("disabled");
87
+ expect(order).toEqual(["verify", "rectify", "verify", "rectify"]);
88
+ });
89
+ });
@@ -0,0 +1,97 @@
1
+ // RE-ARCH: keep
2
+ import { describe, expect, test } from "bun:test";
3
+ import { autofixStage, _autofixDeps } from "../../../../src/pipeline/stages/autofix";
4
+ import type { PipelineContext } from "../../../../src/pipeline/types";
5
+ import { DEFAULT_CONFIG } from "../../../../src/config";
6
+
7
+ function makeReviewResult(success: boolean) {
8
+ return { success, checks: [], summary: "" } as any;
9
+ }
10
+
11
+ function makeCtx(overrides: Partial<PipelineContext> = {}): PipelineContext {
12
+ return {
13
+ config: {
14
+ ...DEFAULT_CONFIG,
15
+ quality: {
16
+ ...DEFAULT_CONFIG.quality,
17
+ commands: {
18
+ ...DEFAULT_CONFIG.quality.commands,
19
+ lintFix: "biome check --fix",
20
+ formatFix: "biome format --write",
21
+ },
22
+ autofix: { enabled: true, maxAttempts: 2 },
23
+ },
24
+ } as any,
25
+ prd: { stories: [] } as any,
26
+ story: { id: "US-001", title: "t", status: "in-progress", acceptanceCriteria: [] } as any,
27
+ stories: [],
28
+ routing: { complexity: "simple", modelTier: "fast", testStrategy: "test-after", reasoning: "" },
29
+ workdir: "/tmp",
30
+ hooks: {},
31
+ ...overrides,
32
+ };
33
+ }
34
+
35
+ describe("autofixStage", () => {
36
+ test("disabled when reviewResult is undefined", () => {
37
+ expect(autofixStage.enabled(makeCtx())).toBe(false);
38
+ });
39
+
40
+ test("disabled when review passed", () => {
41
+ expect(autofixStage.enabled(makeCtx({ reviewResult: makeReviewResult(true) }))).toBe(false);
42
+ });
43
+
44
+ test("disabled when autofix.enabled = false", () => {
45
+ const ctx = makeCtx({
46
+ reviewResult: makeReviewResult(false),
47
+ config: {
48
+ ...DEFAULT_CONFIG,
49
+ quality: { ...DEFAULT_CONFIG.quality, autofix: { enabled: false } },
50
+ } as any,
51
+ });
52
+ expect(autofixStage.enabled(ctx)).toBe(false);
53
+ });
54
+
55
+ test("escalates when no fix commands configured", async () => {
56
+ const ctx = makeCtx({
57
+ reviewResult: makeReviewResult(false),
58
+ config: {
59
+ ...DEFAULT_CONFIG,
60
+ quality: {
61
+ ...DEFAULT_CONFIG.quality,
62
+ commands: { test: "bun test" },
63
+ autofix: { enabled: true },
64
+ },
65
+ } as any,
66
+ });
67
+ const result = await autofixStage.execute(ctx);
68
+ expect(result.action).toBe("escalate");
69
+ });
70
+
71
+ test("returns retry when recheck passes", async () => {
72
+ const saved = { ..._autofixDeps };
73
+ _autofixDeps.runCommand = async () => ({ exitCode: 0, output: "" });
74
+ _autofixDeps.recheckReview = async () => true;
75
+
76
+ const ctx = makeCtx({ reviewResult: makeReviewResult(false) });
77
+ const result = await autofixStage.execute(ctx);
78
+
79
+ Object.assign(_autofixDeps, saved);
80
+
81
+ expect(result.action).toBe("retry");
82
+ if (result.action === "retry") expect(result.fromStage).toBe("review");
83
+ });
84
+
85
+ test("escalates when recheck still fails after max attempts", async () => {
86
+ const saved = { ..._autofixDeps };
87
+ _autofixDeps.runCommand = async () => ({ exitCode: 1, output: "lint error" });
88
+ _autofixDeps.recheckReview = async () => false;
89
+
90
+ const ctx = makeCtx({ reviewResult: makeReviewResult(false) });
91
+ const result = await autofixStage.execute(ctx);
92
+
93
+ Object.assign(_autofixDeps, saved);
94
+
95
+ expect(result.action).toBe("escalate");
96
+ });
97
+ });
@@ -0,0 +1,101 @@
1
+ // RE-ARCH: keep
2
+ import { describe, expect, test } from "bun:test";
3
+ import { rectifyStage, _rectifyDeps } from "../../../../src/pipeline/stages/rectify";
4
+ import type { PipelineContext } from "../../../../src/pipeline/types";
5
+ import { DEFAULT_CONFIG } from "../../../../src/config";
6
+
7
+ function makeCtx(overrides: Partial<PipelineContext> = {}): PipelineContext {
8
+ return {
9
+ config: {
10
+ ...DEFAULT_CONFIG,
11
+ execution: {
12
+ ...DEFAULT_CONFIG.execution,
13
+ rectification: { enabled: true, maxRetries: 3, abortOnIncreasingFailures: true, maxFailureSummaryChars: 2000 },
14
+ },
15
+ quality: {
16
+ ...DEFAULT_CONFIG.quality,
17
+ commands: { ...DEFAULT_CONFIG.quality.commands, test: "bun test" },
18
+ },
19
+ },
20
+ prd: { stories: [] } as any,
21
+ story: { id: "US-001", title: "t", status: "in-progress", acceptanceCriteria: [] } as any,
22
+ stories: [],
23
+ routing: { complexity: "simple", modelTier: "fast", testStrategy: "test-after", reasoning: "" },
24
+ workdir: "/tmp",
25
+ hooks: {},
26
+ ...overrides,
27
+ };
28
+ }
29
+
30
+ function makeVerifyResult(success: boolean) {
31
+ return {
32
+ success,
33
+ status: success ? ("PASS" as const) : ("TEST_FAILURE" as const),
34
+ storyId: "US-001",
35
+ strategy: "scoped" as const,
36
+ passCount: success ? 10 : 8,
37
+ failCount: success ? 0 : 2,
38
+ totalCount: 10,
39
+ failures: [],
40
+ rawOutput: "(fail) foo > bar",
41
+ durationMs: 100,
42
+ countsTowardEscalation: !success,
43
+ };
44
+ }
45
+
46
+ describe("rectifyStage", () => {
47
+ test("disabled when verifyResult is undefined", () => {
48
+ expect(rectifyStage.enabled(makeCtx())).toBe(false);
49
+ });
50
+
51
+ test("disabled when verify passed", () => {
52
+ const ctx = makeCtx({ verifyResult: makeVerifyResult(true) });
53
+ expect(rectifyStage.enabled(ctx)).toBe(false);
54
+ });
55
+
56
+ test("disabled when rectification config disabled", () => {
57
+ const ctx = makeCtx({
58
+ verifyResult: makeVerifyResult(false),
59
+ config: {
60
+ ...DEFAULT_CONFIG,
61
+ execution: {
62
+ ...DEFAULT_CONFIG.execution,
63
+ rectification: { enabled: false, maxRetries: 3, abortOnIncreasingFailures: true, maxFailureSummaryChars: 2000 },
64
+ },
65
+ } as any,
66
+ });
67
+ expect(rectifyStage.enabled(ctx)).toBe(false);
68
+ });
69
+
70
+ test("enabled when verify failed and rectification enabled", () => {
71
+ const ctx = makeCtx({ verifyResult: makeVerifyResult(false) });
72
+ expect(rectifyStage.enabled(ctx)).toBe(true);
73
+ });
74
+
75
+ test("returns retry when rectification succeeds", async () => {
76
+ const saved = { ..._rectifyDeps };
77
+ _rectifyDeps.runRectificationLoop = async () => true;
78
+
79
+ const ctx = makeCtx({ verifyResult: makeVerifyResult(false) });
80
+ const result = await rectifyStage.execute(ctx);
81
+
82
+ Object.assign(_rectifyDeps, saved);
83
+
84
+ expect(result.action).toBe("retry");
85
+ if (result.action === "retry") expect(result.fromStage).toBe("verify");
86
+ // verifyResult should be cleared so verify re-runs fresh
87
+ expect(ctx.verifyResult).toBeUndefined();
88
+ });
89
+
90
+ test("returns escalate when rectification exhausted", async () => {
91
+ const saved = { ..._rectifyDeps };
92
+ _rectifyDeps.runRectificationLoop = async () => false;
93
+
94
+ const ctx = makeCtx({ verifyResult: makeVerifyResult(false) });
95
+ const result = await rectifyStage.execute(ctx);
96
+
97
+ Object.assign(_rectifyDeps, saved);
98
+
99
+ expect(result.action).toBe("escalate");
100
+ });
101
+ });