@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,236 @@
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("test coverage scoping", () => {
57
+ test("should scope test coverage to story contextFiles", async () => {
58
+ const tempDir = await fs.mkdtemp(path.join(os.tmpdir(), "nax-test-"));
59
+
60
+ try {
61
+ // Create test directory and files
62
+ const testDir = path.join(tempDir, "test");
63
+ await fs.mkdir(testDir);
64
+
65
+ // Create multiple test files
66
+ await fs.writeFile(
67
+ path.join(testDir, "health.service.test.ts"),
68
+ 'describe("Health Service", () => { test("checks health", () => {}); });',
69
+ );
70
+ await fs.writeFile(
71
+ path.join(testDir, "auth.service.test.ts"),
72
+ 'describe("Auth Service", () => { test("authenticates", () => {}); });',
73
+ );
74
+ await fs.writeFile(
75
+ path.join(testDir, "db.connection.test.ts"),
76
+ 'describe("DB Connection", () => { test("connects", () => {}); });',
77
+ );
78
+
79
+ const prd = createTestPRD([
80
+ {
81
+ id: "US-001",
82
+ title: "Implement health service",
83
+ description: "Create health check service",
84
+ acceptanceCriteria: ["Service works"],
85
+ contextFiles: ["src/health.service.ts"], // Only health service
86
+ },
87
+ ]);
88
+
89
+ const storyContext: StoryContext = {
90
+ prd,
91
+ currentStoryId: "US-001",
92
+ workdir: tempDir,
93
+ config: {
94
+ context: {
95
+ testCoverage: {
96
+ enabled: true,
97
+ scopeToStory: true, // Enable scoping
98
+ },
99
+ },
100
+ } as any,
101
+ };
102
+
103
+ const budget: ContextBudget = {
104
+ maxTokens: 10000,
105
+ reservedForInstructions: 1000,
106
+ availableForContext: 9000,
107
+ };
108
+
109
+ const built = await buildContext(storyContext, budget);
110
+ const markdown = formatContextAsMarkdown(built);
111
+
112
+ // Should include test coverage element
113
+ expect(built.elements.some((e) => e.type === "test-coverage")).toBe(true);
114
+
115
+ // Should only mention health.service.test.ts, not auth or db tests
116
+ expect(markdown).toContain("health.service.test.ts");
117
+ expect(markdown).not.toContain("auth.service.test.ts");
118
+ expect(markdown).not.toContain("db.connection.test.ts");
119
+ } finally {
120
+ await fs.rm(tempDir, { recursive: true, force: true });
121
+ }
122
+ });
123
+
124
+ test("should scan all tests when scopeToStory=false", async () => {
125
+ const tempDir = await fs.mkdtemp(path.join(os.tmpdir(), "nax-test-"));
126
+
127
+ try {
128
+ const testDir = path.join(tempDir, "test");
129
+ await fs.mkdir(testDir);
130
+
131
+ await fs.writeFile(
132
+ path.join(testDir, "health.test.ts"),
133
+ 'describe("Health", () => { test("works", () => {}); });',
134
+ );
135
+ await fs.writeFile(path.join(testDir, "auth.test.ts"), 'describe("Auth", () => { test("works", () => {}); });');
136
+
137
+ const prd = createTestPRD([
138
+ {
139
+ id: "US-001",
140
+ title: "Story",
141
+ description: "Test",
142
+ acceptanceCriteria: ["AC1"],
143
+ contextFiles: ["src/health.ts"],
144
+ },
145
+ ]);
146
+
147
+ const storyContext: StoryContext = {
148
+ prd,
149
+ currentStoryId: "US-001",
150
+ workdir: tempDir,
151
+ config: {
152
+ context: {
153
+ testCoverage: {
154
+ enabled: true,
155
+ scopeToStory: false, // Disabled - should scan all
156
+ },
157
+ },
158
+ } as any,
159
+ };
160
+
161
+ const budget: ContextBudget = {
162
+ maxTokens: 10000,
163
+ reservedForInstructions: 1000,
164
+ availableForContext: 9000,
165
+ };
166
+
167
+ const built = await buildContext(storyContext, budget);
168
+ const markdown = formatContextAsMarkdown(built);
169
+
170
+ // Should include both test files
171
+ expect(markdown).toContain("health.test.ts");
172
+ expect(markdown).toContain("auth.test.ts");
173
+ } finally {
174
+ await fs.rm(tempDir, { recursive: true, force: true });
175
+ }
176
+ });
177
+
178
+ test("should fall back to full scan when no contextFiles provided", async () => {
179
+ const tempDir = await fs.mkdtemp(path.join(os.tmpdir(), "nax-test-"));
180
+
181
+ try {
182
+ const testDir = path.join(tempDir, "test");
183
+ await fs.mkdir(testDir);
184
+
185
+ await fs.writeFile(
186
+ path.join(testDir, "test1.test.ts"),
187
+ 'describe("Test1", () => { test("works", () => {}); });',
188
+ );
189
+ await fs.writeFile(
190
+ path.join(testDir, "test2.test.ts"),
191
+ 'describe("Test2", () => { test("works", () => {}); });',
192
+ );
193
+
194
+ const prd = createTestPRD([
195
+ {
196
+ id: "US-001",
197
+ title: "Story without contextFiles",
198
+ description: "Test",
199
+ acceptanceCriteria: ["AC1"],
200
+ // No contextFiles
201
+ },
202
+ ]);
203
+
204
+ const storyContext: StoryContext = {
205
+ prd,
206
+ currentStoryId: "US-001",
207
+ workdir: tempDir,
208
+ config: {
209
+ context: {
210
+ testCoverage: {
211
+ enabled: true,
212
+ scopeToStory: true, // true but no contextFiles
213
+ },
214
+ },
215
+ } as any,
216
+ };
217
+
218
+ const budget: ContextBudget = {
219
+ maxTokens: 10000,
220
+ reservedForInstructions: 1000,
221
+ availableForContext: 9000,
222
+ };
223
+
224
+ const built = await buildContext(storyContext, budget);
225
+ const markdown = formatContextAsMarkdown(built);
226
+
227
+ // Should fall back to scanning all files
228
+ expect(markdown).toContain("test1.test.ts");
229
+ expect(markdown).toContain("test2.test.ts");
230
+ } finally {
231
+ await fs.rm(tempDir, { recursive: true, force: true });
232
+ }
233
+ });
234
+ });
235
+
236
+ });
@@ -0,0 +1,93 @@
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("createErrorContext", () => {
57
+ test("should create error context element", () => {
58
+ const error = "TypeError: Cannot read property";
59
+ const element = createErrorContext(error, 90);
60
+
61
+ expect(element.type).toBe("error");
62
+ expect(element.content).toBe(error);
63
+ expect(element.priority).toBe(90);
64
+ expect(element.tokens).toBeGreaterThan(0);
65
+ });
66
+ });
67
+
68
+ describe("createProgressContext", () => {
69
+ test("should create progress context element", () => {
70
+ const progress = "Progress: 5/12 stories complete (4 passed, 1 failed)";
71
+ const element = createProgressContext(progress, 100);
72
+
73
+ expect(element.type).toBe("progress");
74
+ expect(element.content).toBe(progress);
75
+ expect(element.priority).toBe(100);
76
+ expect(element.tokens).toBeGreaterThan(0);
77
+ });
78
+ });
79
+
80
+ describe("createFileContext", () => {
81
+ test("should create file context element", () => {
82
+ const filePath = "src/utils/helper.ts";
83
+ const content = 'export function helper() { return "test"; }';
84
+ const element = createFileContext(filePath, content, 60);
85
+
86
+ expect(element.type).toBe("file");
87
+ expect(element.filePath).toBe(filePath);
88
+ expect(element.content).toBe(content);
89
+ expect(element.priority).toBe(60);
90
+ expect(element.tokens).toBeGreaterThan(0);
91
+ });
92
+ });
93
+ });
@@ -0,0 +1,201 @@
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("estimateTokens", () => {
57
+ test("should estimate tokens correctly", () => {
58
+ expect(estimateTokens("test")).toBe(2); // 4 chars = 2 tokens (1 token ≈ 3 chars)
59
+ expect(estimateTokens("hello world")).toBe(4); // 11 chars = 4 tokens
60
+ expect(estimateTokens("")).toBe(0);
61
+ });
62
+ });
63
+
64
+ describe("defensive checks", () => {
65
+ test("should handle story with null acceptanceCriteria", async () => {
66
+ // Create PRD directly to bypass helper defaults
67
+ const prd: PRD = {
68
+ project: "test-project",
69
+ feature: "test-feature",
70
+ branchName: "test-branch",
71
+ createdAt: new Date().toISOString(),
72
+ updatedAt: new Date().toISOString(),
73
+ userStories: [
74
+ {
75
+ id: "US-001",
76
+ title: "Malformed Story",
77
+ description: "Test",
78
+ acceptanceCriteria: null as any, // Simulate malformed data
79
+ dependencies: [],
80
+ tags: [],
81
+ status: "pending",
82
+ passes: false,
83
+ escalations: [],
84
+ attempts: 0,
85
+ },
86
+ ],
87
+ };
88
+
89
+ const context: StoryContext = {
90
+ prd,
91
+ currentStoryId: "US-001",
92
+ };
93
+
94
+ const budget: ContextBudget = {
95
+ maxTokens: 10000,
96
+ reservedForInstructions: 1000,
97
+ availableForContext: 9000,
98
+ };
99
+
100
+ const built = await buildContext(context, budget);
101
+ expect(built.elements.length).toBeGreaterThan(0);
102
+ const storyElement = built.elements.find((e) => e.type === "story");
103
+ expect(storyElement?.content).toContain("(No acceptance criteria defined)");
104
+ });
105
+
106
+ test("should handle story with undefined acceptanceCriteria", async () => {
107
+ // Create PRD directly to bypass helper defaults
108
+ const prd: PRD = {
109
+ project: "test-project",
110
+ feature: "test-feature",
111
+ branchName: "test-branch",
112
+ createdAt: new Date().toISOString(),
113
+ updatedAt: new Date().toISOString(),
114
+ userStories: [
115
+ {
116
+ id: "US-001",
117
+ title: "Malformed Story",
118
+ description: "Test",
119
+ acceptanceCriteria: undefined as any, // Simulate malformed data
120
+ dependencies: [],
121
+ tags: [],
122
+ status: "pending",
123
+ passes: false,
124
+ escalations: [],
125
+ attempts: 0,
126
+ },
127
+ ],
128
+ };
129
+
130
+ const context: StoryContext = {
131
+ prd,
132
+ currentStoryId: "US-001",
133
+ };
134
+
135
+ const budget: ContextBudget = {
136
+ maxTokens: 10000,
137
+ reservedForInstructions: 1000,
138
+ availableForContext: 9000,
139
+ };
140
+
141
+ const built = await buildContext(context, budget);
142
+ expect(built.elements.length).toBeGreaterThan(0);
143
+ const storyElement = built.elements.find((e) => e.type === "story");
144
+ expect(storyElement?.content).toContain("(No acceptance criteria defined)");
145
+ });
146
+
147
+ test("should log warning for missing dependency story", async () => {
148
+ const prd = createTestPRD([
149
+ {
150
+ id: "US-001",
151
+ title: "Story with Missing Dependency",
152
+ description: "Test",
153
+ acceptanceCriteria: ["AC1"],
154
+ dependencies: ["US-999"], // Non-existent dependency
155
+ },
156
+ ]);
157
+
158
+ const context: StoryContext = {
159
+ prd,
160
+ currentStoryId: "US-001",
161
+ };
162
+
163
+ const budget: ContextBudget = {
164
+ maxTokens: 10000,
165
+ reservedForInstructions: 1000,
166
+ availableForContext: 9000,
167
+ };
168
+
169
+ const built = await buildContext(context, budget);
170
+
171
+ // Should not include the missing dependency in the context
172
+ expect(built.elements.find((e) => e.type === "dependency")).toBeUndefined();
173
+ });
174
+
175
+ test("should handle story with non-array priorErrors", async () => {
176
+ const prd = createTestPRD([
177
+ {
178
+ id: "US-001",
179
+ title: "Story with Malformed Errors",
180
+ description: "Test",
181
+ acceptanceCriteria: ["AC1"],
182
+ priorErrors: "not an array" as any, // Malformed data
183
+ },
184
+ ]);
185
+
186
+ const context: StoryContext = {
187
+ prd,
188
+ currentStoryId: "US-001",
189
+ };
190
+
191
+ const budget: ContextBudget = {
192
+ maxTokens: 10000,
193
+ reservedForInstructions: 1000,
194
+ availableForContext: 9000,
195
+ };
196
+
197
+ const built = await buildContext(context, budget);
198
+ expect(built.elements.find((e) => e.type === "error")).toBeUndefined();
199
+ });
200
+ });
201
+ });