@nathapp/nax 0.18.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 (459) hide show
  1. package/.gitlab-ci.yml +96 -0
  2. package/BRIEF.md +140 -0
  3. package/CHANGELOG.md +60 -0
  4. package/CLAUDE.md +159 -0
  5. package/README.md +373 -0
  6. package/US-007-IMPLEMENTATION.md +139 -0
  7. package/bin/nax.ts +930 -0
  8. package/biome.json +14 -0
  9. package/bun.lock +168 -0
  10. package/bunfig.toml +11 -0
  11. package/docs/20260216-fix-plan-context-review.md +56 -0
  12. package/docs/20260216-relentless-vs-ngent-comparison.md +208 -0
  13. package/docs/20260216-v02-plan.md +136 -0
  14. package/docs/20260216-v02-review.md +685 -0
  15. package/docs/20260217-dogfood-findings.md +56 -0
  16. package/docs/20260217-p2-plus-plan.md +117 -0
  17. package/docs/20260217-partial-fixes-plan.md +62 -0
  18. package/docs/20260217-plan-analyze-spec.md +117 -0
  19. package/docs/20260217-post-impl-review.md +1137 -0
  20. package/docs/20260217-quick-wins-plan.md +66 -0
  21. package/docs/20260217-split-runner-plan.md +75 -0
  22. package/docs/20260217-v03-impl-plan.md +80 -0
  23. package/docs/20260217-v03-post-impl-review.md +589 -0
  24. package/docs/20260217-v04-impl-plan.md +86 -0
  25. package/docs/20260217-v05-post-impl-review.md +850 -0
  26. package/docs/20260217-v06-post-impl-review.md +817 -0
  27. package/docs/20260218-adr003-port-plan.md +151 -0
  28. package/docs/20260218-review-adr003-verification.md +175 -0
  29. package/docs/20260219-fix-plan-bug16-19.md +79 -0
  30. package/docs/20260219-fix-plan-bug20-22.md +114 -0
  31. package/docs/20260219-plan-llm-routing.md +116 -0
  32. package/docs/20260219-review-bug20-22-fixes.md +135 -0
  33. package/docs/20260219-routing-baseline-keyword.md +63 -0
  34. package/docs/20260220-plan-structured-logging-p1.md +80 -0
  35. package/docs/20260220-plan-structured-logging-p2.md +37 -0
  36. package/docs/20260220-review-llm-routing.md +180 -0
  37. package/docs/20260220-review-post-fix-llm-routing.md +70 -0
  38. package/docs/20260221-fix-plan-relevantfiles-split.md +101 -0
  39. package/docs/20260221-fix-plan-routing-mode.md +125 -0
  40. package/docs/20260221-review-v0.9-implementation.md +379 -0
  41. package/docs/20260222-fix-plan-v091-routing-isolation.md +197 -0
  42. package/docs/20260223-fix-plan-prompt-audit.md +62 -0
  43. package/docs/20260224-nax-roadmap-phases.md +189 -0
  44. package/docs/20260225-phase2-llm-service-layer.md +401 -0
  45. package/docs/20260225-review-v0.10.1.md +187 -0
  46. package/docs/20260303-v010-implementation-plan.md +165 -0
  47. package/docs/CLAUDE.md.bak +191 -0
  48. package/docs/ROADMAP.md +165 -0
  49. package/docs/SPEC-rectification.md +0 -0
  50. package/docs/SPEC.md +324 -0
  51. package/docs/US-001-plugin-loading-verification.md +152 -0
  52. package/docs/architecture-analysis.md +1076 -0
  53. package/docs/bugs/BUG-21-escalation-null-attempts.md +48 -0
  54. package/docs/bugs-from-dogfood-run-c.md +243 -0
  55. package/docs/code-review-20260228.md +612 -0
  56. package/docs/code-review-v0.15.0.md +629 -0
  57. package/docs/hook-lifecycle-test-plan.md +149 -0
  58. package/docs/releases/v0.11.0-and-earlier.md +20 -0
  59. package/docs/releases/v0.12.0.md +15 -0
  60. package/docs/releases/v0.13.0.md +14 -0
  61. package/docs/releases/v0.14.0.md +20 -0
  62. package/docs/releases/v0.14.1.md +36 -0
  63. package/docs/releases/v0.14.2.md +51 -0
  64. package/docs/releases/v0.14.3.md +174 -0
  65. package/docs/releases/v0.14.4.md +94 -0
  66. package/docs/releases/v0.15.0.md +502 -0
  67. package/docs/releases/v0.15.1.md +170 -0
  68. package/docs/releases/v0.15.3.md +193 -0
  69. package/docs/specs/status-file-v0.10.1.md +812 -0
  70. package/docs/v0.10-global-config.md +206 -0
  71. package/docs/v0.10-plugin-system.md +415 -0
  72. package/docs/v0.10-prompt-optimizer.md +234 -0
  73. package/docs/v0.3-spec.md +244 -0
  74. package/docs/v0.4-spec.md +140 -0
  75. package/docs/v0.5-spec.md +237 -0
  76. package/docs/v0.6-spec.md +371 -0
  77. package/docs/v0.7-spec.md +177 -0
  78. package/docs/v0.8-llm-routing.md +206 -0
  79. package/docs/v0.8-structured-logging.md +132 -0
  80. package/docs/v0.9.3-prompt-audit.md +112 -0
  81. package/examples/plugins/console-reporter/index.test.ts +207 -0
  82. package/examples/plugins/console-reporter/index.ts +110 -0
  83. package/nax/config.json +147 -0
  84. package/nax/features/bugfix-v0171/prd.json +52 -0
  85. package/nax/features/config-management/prd.json +108 -0
  86. package/nax/features/config-management/progress.txt +5 -0
  87. package/nax/features/diagnose/acceptance.test.ts +412 -0
  88. package/nax/features/diagnose/prd.json +41 -0
  89. package/nax/features/orchestration-fixes/prd.json +89 -0
  90. package/nax/features/orchestration-fixes/progress.txt +1 -0
  91. package/nax/features/plugin-integration/US-007-VERIFICATION.md +259 -0
  92. package/nax/features/plugin-integration/prd.json +208 -0
  93. package/nax/features/plugin-integration/progress.txt +5 -0
  94. package/nax/features/precheck/prd.json +205 -0
  95. package/nax/features/precheck/progress.txt +15 -0
  96. package/nax/features/structured-logging/prd.json +199 -0
  97. package/nax/features/unlock/prd.json +36 -0
  98. package/package.json +47 -0
  99. package/src/acceptance/fix-generator.ts +348 -0
  100. package/src/acceptance/generator.ts +282 -0
  101. package/src/acceptance/index.ts +30 -0
  102. package/src/acceptance/types.ts +79 -0
  103. package/src/agents/claude-decompose.ts +169 -0
  104. package/src/agents/claude-plan.ts +139 -0
  105. package/src/agents/claude.ts +324 -0
  106. package/src/agents/cost.ts +268 -0
  107. package/src/agents/index.ts +13 -0
  108. package/src/agents/registry.ts +48 -0
  109. package/src/agents/types-extended.ts +133 -0
  110. package/src/agents/types.ts +113 -0
  111. package/src/agents/validation.ts +69 -0
  112. package/src/analyze/classifier.ts +305 -0
  113. package/src/analyze/index.ts +16 -0
  114. package/src/analyze/scanner.ts +175 -0
  115. package/src/analyze/types.ts +51 -0
  116. package/src/cli/accept.ts +108 -0
  117. package/src/cli/analyze-parser.ts +284 -0
  118. package/src/cli/analyze.ts +207 -0
  119. package/src/cli/config.ts +561 -0
  120. package/src/cli/constitution.ts +109 -0
  121. package/src/cli/diagnose-analysis.ts +159 -0
  122. package/src/cli/diagnose-formatter.ts +87 -0
  123. package/src/cli/diagnose.ts +203 -0
  124. package/src/cli/generate.ts +127 -0
  125. package/src/cli/index.ts +37 -0
  126. package/src/cli/init.ts +188 -0
  127. package/src/cli/interact.ts +295 -0
  128. package/src/cli/plan.ts +198 -0
  129. package/src/cli/plugins.ts +111 -0
  130. package/src/cli/prompts.ts +295 -0
  131. package/src/cli/runs.ts +174 -0
  132. package/src/cli/status-cost.ts +151 -0
  133. package/src/cli/status-features.ts +338 -0
  134. package/src/cli/status.ts +13 -0
  135. package/src/commands/common.ts +171 -0
  136. package/src/commands/diagnose.ts +17 -0
  137. package/src/commands/index.ts +8 -0
  138. package/src/commands/logs.ts +384 -0
  139. package/src/commands/precheck.ts +86 -0
  140. package/src/commands/unlock.ts +96 -0
  141. package/src/config/defaults.ts +160 -0
  142. package/src/config/index.ts +22 -0
  143. package/src/config/loader.ts +121 -0
  144. package/src/config/merger.ts +147 -0
  145. package/src/config/path-security.ts +121 -0
  146. package/src/config/paths.ts +27 -0
  147. package/src/config/schema.ts +56 -0
  148. package/src/config/schemas.ts +286 -0
  149. package/src/config/types.ts +423 -0
  150. package/src/config/validate.ts +103 -0
  151. package/src/constitution/generator.ts +191 -0
  152. package/src/constitution/generators/aider.ts +41 -0
  153. package/src/constitution/generators/claude.ts +35 -0
  154. package/src/constitution/generators/cursor.ts +36 -0
  155. package/src/constitution/generators/opencode.ts +38 -0
  156. package/src/constitution/generators/types.ts +33 -0
  157. package/src/constitution/generators/windsurf.ts +36 -0
  158. package/src/constitution/index.ts +10 -0
  159. package/src/constitution/loader.ts +133 -0
  160. package/src/constitution/types.ts +31 -0
  161. package/src/context/auto-detect.ts +227 -0
  162. package/src/context/builder.ts +246 -0
  163. package/src/context/elements.ts +83 -0
  164. package/src/context/formatter.ts +107 -0
  165. package/src/context/generator.ts +129 -0
  166. package/src/context/generators/aider.ts +34 -0
  167. package/src/context/generators/claude.ts +28 -0
  168. package/src/context/generators/cursor.ts +28 -0
  169. package/src/context/generators/opencode.ts +30 -0
  170. package/src/context/generators/windsurf.ts +28 -0
  171. package/src/context/greenfield.ts +114 -0
  172. package/src/context/index.ts +33 -0
  173. package/src/context/injector.ts +279 -0
  174. package/src/context/test-scanner.ts +370 -0
  175. package/src/context/types.ts +98 -0
  176. package/src/errors.ts +67 -0
  177. package/src/execution/batching.ts +157 -0
  178. package/src/execution/crash-recovery.ts +373 -0
  179. package/src/execution/escalation/escalation.ts +44 -0
  180. package/src/execution/escalation/index.ts +13 -0
  181. package/src/execution/escalation/tier-escalation.ts +295 -0
  182. package/src/execution/escalation/tier-outcome.ts +158 -0
  183. package/src/execution/helpers.ts +38 -0
  184. package/src/execution/index.ts +45 -0
  185. package/src/execution/lifecycle/acceptance-loop.ts +272 -0
  186. package/src/execution/lifecycle/headless-formatter.ts +85 -0
  187. package/src/execution/lifecycle/index.ts +12 -0
  188. package/src/execution/lifecycle/parallel-lifecycle.ts +101 -0
  189. package/src/execution/lifecycle/precheck-runner.ts +140 -0
  190. package/src/execution/lifecycle/run-cleanup.ts +81 -0
  191. package/src/execution/lifecycle/run-completion.ts +129 -0
  192. package/src/execution/lifecycle/run-initialization.ts +141 -0
  193. package/src/execution/lifecycle/run-lifecycle.ts +312 -0
  194. package/src/execution/lifecycle/run-setup.ts +204 -0
  195. package/src/execution/lifecycle/story-hooks.ts +38 -0
  196. package/src/execution/lifecycle/story-size-prompts.ts +123 -0
  197. package/src/execution/lock.ts +115 -0
  198. package/src/execution/parallel-executor.ts +216 -0
  199. package/src/execution/parallel.ts +400 -0
  200. package/src/execution/pid-registry.ts +280 -0
  201. package/src/execution/pipeline-result-handler.ts +388 -0
  202. package/src/execution/post-verify-rectification.ts +188 -0
  203. package/src/execution/post-verify.ts +274 -0
  204. package/src/execution/progress.ts +25 -0
  205. package/src/execution/prompts.ts +127 -0
  206. package/src/execution/queue-handler.ts +109 -0
  207. package/src/execution/rectification.ts +13 -0
  208. package/src/execution/runner.ts +377 -0
  209. package/src/execution/sequential-executor.ts +388 -0
  210. package/src/execution/status-file.ts +264 -0
  211. package/src/execution/status-writer.ts +139 -0
  212. package/src/execution/story-context.ts +229 -0
  213. package/src/execution/test-output-parser.ts +14 -0
  214. package/src/execution/verification.ts +72 -0
  215. package/src/hooks/index.ts +2 -0
  216. package/src/hooks/runner.ts +286 -0
  217. package/src/hooks/types.ts +67 -0
  218. package/src/interaction/chain.ts +154 -0
  219. package/src/interaction/index.ts +60 -0
  220. package/src/interaction/init.ts +83 -0
  221. package/src/interaction/plugins/auto.ts +217 -0
  222. package/src/interaction/plugins/cli.ts +300 -0
  223. package/src/interaction/plugins/telegram.ts +384 -0
  224. package/src/interaction/plugins/webhook.ts +258 -0
  225. package/src/interaction/state.ts +171 -0
  226. package/src/interaction/triggers.ts +229 -0
  227. package/src/interaction/types.ts +163 -0
  228. package/src/logger/formatters.ts +84 -0
  229. package/src/logger/index.ts +16 -0
  230. package/src/logger/logger.ts +298 -0
  231. package/src/logger/types.ts +48 -0
  232. package/src/logging/formatter.ts +355 -0
  233. package/src/logging/index.ts +22 -0
  234. package/src/logging/types.ts +93 -0
  235. package/src/metrics/aggregator.ts +190 -0
  236. package/src/metrics/index.ts +14 -0
  237. package/src/metrics/tracker.ts +200 -0
  238. package/src/metrics/types.ts +109 -0
  239. package/src/optimizer/index.ts +62 -0
  240. package/src/optimizer/noop.optimizer.ts +24 -0
  241. package/src/optimizer/rule-based.optimizer.ts +248 -0
  242. package/src/optimizer/types.ts +53 -0
  243. package/src/pipeline/events.ts +130 -0
  244. package/src/pipeline/index.ts +19 -0
  245. package/src/pipeline/runner.ts +161 -0
  246. package/src/pipeline/stages/acceptance.ts +197 -0
  247. package/src/pipeline/stages/completion.ts +99 -0
  248. package/src/pipeline/stages/constitution.ts +63 -0
  249. package/src/pipeline/stages/context.ts +117 -0
  250. package/src/pipeline/stages/execution.ts +194 -0
  251. package/src/pipeline/stages/index.ts +62 -0
  252. package/src/pipeline/stages/optimizer.ts +74 -0
  253. package/src/pipeline/stages/prompt.ts +57 -0
  254. package/src/pipeline/stages/queue-check.ts +103 -0
  255. package/src/pipeline/stages/review.ts +181 -0
  256. package/src/pipeline/stages/routing.ts +81 -0
  257. package/src/pipeline/stages/verify.ts +100 -0
  258. package/src/pipeline/types.ts +167 -0
  259. package/src/plugins/index.ts +31 -0
  260. package/src/plugins/loader.ts +287 -0
  261. package/src/plugins/registry.ts +168 -0
  262. package/src/plugins/types.ts +327 -0
  263. package/src/plugins/validator.ts +352 -0
  264. package/src/prd/index.ts +172 -0
  265. package/src/prd/types.ts +202 -0
  266. package/src/precheck/checks-blockers.ts +391 -0
  267. package/src/precheck/checks-warnings.ts +142 -0
  268. package/src/precheck/checks.ts +30 -0
  269. package/src/precheck/index.ts +247 -0
  270. package/src/precheck/story-size-gate.ts +144 -0
  271. package/src/precheck/types.ts +31 -0
  272. package/src/queue/index.ts +2 -0
  273. package/src/queue/manager.ts +254 -0
  274. package/src/queue/types.ts +54 -0
  275. package/src/review/index.ts +8 -0
  276. package/src/review/runner.ts +172 -0
  277. package/src/review/types.ts +66 -0
  278. package/src/routing/builder.ts +81 -0
  279. package/src/routing/chain.ts +74 -0
  280. package/src/routing/index.ts +16 -0
  281. package/src/routing/loader.ts +58 -0
  282. package/src/routing/router.ts +303 -0
  283. package/src/routing/strategies/adaptive.ts +215 -0
  284. package/src/routing/strategies/index.ts +8 -0
  285. package/src/routing/strategies/keyword.ts +163 -0
  286. package/src/routing/strategies/llm-prompts.ts +209 -0
  287. package/src/routing/strategies/llm.ts +235 -0
  288. package/src/routing/strategies/manual.ts +50 -0
  289. package/src/routing/strategy.ts +99 -0
  290. package/src/tdd/cleanup.ts +111 -0
  291. package/src/tdd/index.ts +23 -0
  292. package/src/tdd/isolation.ts +123 -0
  293. package/src/tdd/orchestrator.ts +383 -0
  294. package/src/tdd/prompts.ts +270 -0
  295. package/src/tdd/rectification-gate.ts +183 -0
  296. package/src/tdd/session-runner.ts +179 -0
  297. package/src/tdd/types.ts +81 -0
  298. package/src/tdd/verdict.ts +271 -0
  299. package/src/tui/App.tsx +265 -0
  300. package/src/tui/components/AgentPanel.tsx +75 -0
  301. package/src/tui/components/CostOverlay.tsx +118 -0
  302. package/src/tui/components/HelpOverlay.tsx +107 -0
  303. package/src/tui/components/StatusBar.tsx +63 -0
  304. package/src/tui/components/StoriesPanel.tsx +177 -0
  305. package/src/tui/hooks/useKeyboard.ts +142 -0
  306. package/src/tui/hooks/useLayout.ts +137 -0
  307. package/src/tui/hooks/usePipelineEvents.ts +183 -0
  308. package/src/tui/hooks/usePty.ts +194 -0
  309. package/src/tui/index.tsx +38 -0
  310. package/src/tui/types.ts +76 -0
  311. package/src/utils/git.ts +83 -0
  312. package/src/utils/queue-writer.ts +54 -0
  313. package/src/verification/executor.ts +235 -0
  314. package/src/verification/gate.ts +207 -0
  315. package/src/verification/index.ts +12 -0
  316. package/src/verification/parser.ts +230 -0
  317. package/src/verification/rectification.ts +108 -0
  318. package/src/verification/types.ts +113 -0
  319. package/src/worktree/dispatcher.ts +65 -0
  320. package/src/worktree/index.ts +2 -0
  321. package/src/worktree/manager.ts +187 -0
  322. package/src/worktree/merge.ts +301 -0
  323. package/src/worktree/types.ts +4 -0
  324. package/test/TEST_COVERAGE_US001.md +217 -0
  325. package/test/TEST_COVERAGE_US003.md +84 -0
  326. package/test/TEST_COVERAGE_US005.md +86 -0
  327. package/test/US-002-orchestrator.test.ts +246 -0
  328. package/test/acceptance/cm-003-default-view.test.ts +194 -0
  329. package/test/execution/pid-registry.test.ts +240 -0
  330. package/test/execution/post-verify.test.ts +224 -0
  331. package/test/helpers/timeout.ts +42 -0
  332. package/test/integration/US-002-TEST-SUMMARY.md +107 -0
  333. package/test/integration/US-003-TEST-SUMMARY.md +149 -0
  334. package/test/integration/US-004-TEST-SUMMARY.md +106 -0
  335. package/test/integration/US-005-TEST-SUMMARY.md +138 -0
  336. package/test/integration/US-007-TEST-SUMMARY.md +100 -0
  337. package/test/integration/agent-validation.test.ts +439 -0
  338. package/test/integration/analyze-integration.test.ts +261 -0
  339. package/test/integration/analyze-scanner.test.ts +131 -0
  340. package/test/integration/cli-config-default-edge-cases.test.ts +222 -0
  341. package/test/integration/cli-config-default-view.test.ts +229 -0
  342. package/test/integration/cli-config-diff.test.ts +460 -0
  343. package/test/integration/cli-config.test.ts +736 -0
  344. package/test/integration/cli-diagnose.test.ts +592 -0
  345. package/test/integration/cli-logs.test.ts +314 -0
  346. package/test/integration/cli-plugins.test.ts +678 -0
  347. package/test/integration/cli-precheck.test.ts +371 -0
  348. package/test/integration/cli-run-headless.test.ts +173 -0
  349. package/test/integration/cli.test.ts +75 -0
  350. package/test/integration/config/merger.test.ts +465 -0
  351. package/test/integration/config/paths.test.ts +51 -0
  352. package/test/integration/config-loader.test.ts +265 -0
  353. package/test/integration/config.test.ts +444 -0
  354. package/test/integration/context-integration.test.ts +702 -0
  355. package/test/integration/context-provider-injection.test.ts +506 -0
  356. package/test/integration/context-verification-integration.test.ts +295 -0
  357. package/test/integration/e2e.test.ts +896 -0
  358. package/test/integration/execution.test.ts +625 -0
  359. package/test/integration/helpers.test.ts +295 -0
  360. package/test/integration/hooks.test.ts +361 -0
  361. package/test/integration/interaction-chain-pipeline.test.ts +464 -0
  362. package/test/integration/isolation.test.ts +143 -0
  363. package/test/integration/logger.test.ts +461 -0
  364. package/test/integration/parallel.test.ts +250 -0
  365. package/test/integration/path-security.test.ts +173 -0
  366. package/test/integration/pipeline-acceptance.test.ts +302 -0
  367. package/test/integration/pipeline-events.test.ts +475 -0
  368. package/test/integration/pipeline.test.ts +658 -0
  369. package/test/integration/plan.test.ts +157 -0
  370. package/test/integration/plugin-routing.test.ts +921 -0
  371. package/test/integration/plugins/config-integration.test.ts +172 -0
  372. package/test/integration/plugins/config-resolution.test.ts +522 -0
  373. package/test/integration/plugins/loader.test.ts +641 -0
  374. package/test/integration/plugins/registry.test.ts +746 -0
  375. package/test/integration/plugins/validator.test.ts +563 -0
  376. package/test/integration/prd-pause.test.ts +205 -0
  377. package/test/integration/prd-resolvers.test.ts +185 -0
  378. package/test/integration/precheck-integration.test.ts +468 -0
  379. package/test/integration/precheck.test.ts +805 -0
  380. package/test/integration/progress.test.ts +34 -0
  381. package/test/integration/rectification-flow.test.ts +512 -0
  382. package/test/integration/reporter-lifecycle.test.ts +860 -0
  383. package/test/integration/review-config-commands.test.ts +319 -0
  384. package/test/integration/review-config-schema.test.ts +116 -0
  385. package/test/integration/review-plugin-integration.test.ts +722 -0
  386. package/test/integration/review.test.ts +149 -0
  387. package/test/integration/routing-stage-bug-021.test.ts +274 -0
  388. package/test/integration/routing-stage-greenfield.test.ts +286 -0
  389. package/test/integration/runner-config-plugins.test.ts +461 -0
  390. package/test/integration/runner-fixes.test.ts +399 -0
  391. package/test/integration/runner-plugin-integration.test.ts +543 -0
  392. package/test/integration/runner.test.ts +1679 -0
  393. package/test/integration/s5-greenfield-fallback.test.ts +297 -0
  394. package/test/integration/status-file-integration.test.ts +325 -0
  395. package/test/integration/status-file.test.ts +379 -0
  396. package/test/integration/status-writer.test.ts +345 -0
  397. package/test/integration/story-id-in-events.test.ts +273 -0
  398. package/test/integration/tdd-cleanup.test.ts +246 -0
  399. package/test/integration/tdd-orchestrator.test.ts +1762 -0
  400. package/test/integration/test-scanner.test.ts +403 -0
  401. package/test/integration/verification-asset-check.test.ts +142 -0
  402. package/test/integration/verify-stage.test.ts +275 -0
  403. package/test/integration/worktree/manager.test.ts +218 -0
  404. package/test/integration/worktree/merge.test.ts +341 -0
  405. package/test/manual/logging-formatter-demo.ts +158 -0
  406. package/test/ui/tui-agent-panel.test.tsx +99 -0
  407. package/test/ui/tui-controls.test.ts +334 -0
  408. package/test/ui/tui-cost-and-pty.test.ts +189 -0
  409. package/test/ui/tui-layout.test.ts +378 -0
  410. package/test/ui/tui-pty-integration.test.tsx +159 -0
  411. package/test/ui/tui-stories.test.ts +332 -0
  412. package/test/unit/acceptance.test.ts +186 -0
  413. package/test/unit/agent-stderr-capture.test.ts +146 -0
  414. package/test/unit/analyze-classifier.test.ts +215 -0
  415. package/test/unit/analyze.test.ts +224 -0
  416. package/test/unit/auto-detect.test.ts +249 -0
  417. package/test/unit/cli-status.test.ts +417 -0
  418. package/test/unit/commands/common.test.ts +320 -0
  419. package/test/unit/commands/logs.test.ts +416 -0
  420. package/test/unit/commands/unlock.test.ts +319 -0
  421. package/test/unit/constitution-generators.test.ts +160 -0
  422. package/test/unit/constitution.test.ts +209 -0
  423. package/test/unit/context.test.ts +1722 -0
  424. package/test/unit/cost.test.ts +231 -0
  425. package/test/unit/crash-recovery.test.ts +308 -0
  426. package/test/unit/escalation.test.ts +126 -0
  427. package/test/unit/execution-logging-stderr.test.ts +156 -0
  428. package/test/unit/execution-stage.test.ts +122 -0
  429. package/test/unit/fix-generator.test.ts +275 -0
  430. package/test/unit/formatters.test.ts +469 -0
  431. package/test/unit/greenfield.test.ts +179 -0
  432. package/test/unit/helpers.test.ts +317 -0
  433. package/test/unit/interaction/human-review-trigger.test.ts +164 -0
  434. package/test/unit/interaction-network-failures.test.ts +389 -0
  435. package/test/unit/interaction-plugins.test.ts +164 -0
  436. package/test/unit/isolation.test.ts +134 -0
  437. package/test/unit/logging/formatter.test.ts +455 -0
  438. package/test/unit/merge.test.ts +268 -0
  439. package/test/unit/metrics.test.ts +276 -0
  440. package/test/unit/optimizer/noop.optimizer.test.ts +125 -0
  441. package/test/unit/optimizer/rule-based.optimizer.test.ts +358 -0
  442. package/test/unit/prd-auto-default.test.ts +290 -0
  443. package/test/unit/prd-failure-category.test.ts +176 -0
  444. package/test/unit/prd-get-next-story.test.ts +186 -0
  445. package/test/unit/precheck-checks.test.ts +840 -0
  446. package/test/unit/precheck-story-size-gate.test.ts +287 -0
  447. package/test/unit/precheck-types.test.ts +142 -0
  448. package/test/unit/prompts.test.ts +475 -0
  449. package/test/unit/queue.test.ts +237 -0
  450. package/test/unit/rectification.test.ts +284 -0
  451. package/test/unit/registry.test.ts +287 -0
  452. package/test/unit/routing.test.ts +937 -0
  453. package/test/unit/run-lifecycle.test.ts +140 -0
  454. package/test/unit/storyid-events.test.ts +224 -0
  455. package/test/unit/tdd-verdict.test.ts +492 -0
  456. package/test/unit/test-output-parser.test.ts +377 -0
  457. package/test/unit/verdict.test.ts +324 -0
  458. package/test/unit/worktree-manager.test.ts +158 -0
  459. package/tsconfig.json +27 -0
@@ -0,0 +1,227 @@
1
+ /**
2
+ * Auto-detection for contextFiles when PRD omits them (BUG-006)
3
+ *
4
+ * Extracts keywords from story title, runs git grep to find matching source files,
5
+ * excludes test/index/generated files, caps at maxFiles.
6
+ */
7
+
8
+ import { getLogger } from "../logger";
9
+
10
+ export interface AutoDetectOptions {
11
+ /** Working directory for git grep */
12
+ workdir: string;
13
+ /** Story title to extract keywords from */
14
+ storyTitle: string;
15
+ /** Maximum files to return (default: 5) */
16
+ maxFiles?: number;
17
+ /** Enable import tracing (default: false, reserved for future use) */
18
+ traceImports?: boolean;
19
+ }
20
+
21
+ /**
22
+ * Extract keywords from story title for git grep search.
23
+ *
24
+ * Heuristics:
25
+ * - Remove common words (the, a, an, and, or, to, from, for, with, etc.)
26
+ * - Split on spaces/punctuation
27
+ * - Keep words >= 3 chars
28
+ * - Lowercase for case-insensitive matching
29
+ *
30
+ * @example
31
+ * extractKeywords("BUG-006: Context auto-detection (contextFiles)")
32
+ * // => ["bug", "006", "context", "auto", "detection", "contextfiles"]
33
+ */
34
+ export function extractKeywords(title: string): string[] {
35
+ const stopWords = new Set([
36
+ "the",
37
+ "a",
38
+ "an",
39
+ "and",
40
+ "or",
41
+ "to",
42
+ "from",
43
+ "for",
44
+ "with",
45
+ "in",
46
+ "on",
47
+ "at",
48
+ "by",
49
+ "is",
50
+ "are",
51
+ "was",
52
+ "were",
53
+ "be",
54
+ "been",
55
+ "being",
56
+ "have",
57
+ "has",
58
+ "had",
59
+ "do",
60
+ "does",
61
+ "did",
62
+ "will",
63
+ "would",
64
+ "should",
65
+ "could",
66
+ "may",
67
+ "might",
68
+ "can",
69
+ "must",
70
+ "of",
71
+ "as",
72
+ "if",
73
+ "when",
74
+ "bug",
75
+ "fix",
76
+ "add",
77
+ "update",
78
+ "remove",
79
+ "implement",
80
+ ]);
81
+
82
+ // Split on non-alphanumeric, filter, lowercase
83
+ const words = title
84
+ .toLowerCase()
85
+ .split(/[^a-z0-9]+/)
86
+ .filter((w) => w.length >= 3 && !stopWords.has(w));
87
+
88
+ // Deduplicate
89
+ return Array.from(new Set(words));
90
+ }
91
+
92
+ /**
93
+ * Auto-detect context files via git grep when PRD omits contextFiles.
94
+ *
95
+ * Algorithm:
96
+ * 1. Extract keywords from story title
97
+ * 2. Run git grep for each keyword in src/ directories
98
+ * 3. Deduplicate files
99
+ * 4. Exclude test/index/generated files
100
+ * 5. Cap at maxFiles (default: 5)
101
+ *
102
+ * Returns empty array if:
103
+ * - Not a git repository
104
+ * - No keywords extracted
105
+ * - git grep fails
106
+ * - No matching files found
107
+ *
108
+ * @param options - Auto-detect configuration
109
+ * @returns Array of relative file paths (sorted by relevance score)
110
+ */
111
+ export async function autoDetectContextFiles(options: AutoDetectOptions): Promise<string[]> {
112
+ const { workdir, storyTitle, maxFiles = 5 } = options;
113
+ const logger = getLogger();
114
+
115
+ // Extract keywords
116
+ const keywords = extractKeywords(storyTitle);
117
+ if (keywords.length === 0) {
118
+ logger.debug("auto-detect", "No keywords extracted from story title", { storyTitle });
119
+ return [];
120
+ }
121
+
122
+ logger.debug("auto-detect", "Extracted keywords", { keywords, storyTitle });
123
+
124
+ // Build git grep command
125
+ // Use -i for case-insensitive, -l for filename-only, -I to skip binary files
126
+ // Search in src/ directories only (exclude test, node_modules, etc.)
127
+ const grepPattern = keywords.join("|"); // OR pattern
128
+ const grepCommand = [
129
+ "git",
130
+ "grep",
131
+ "-i", // case-insensitive
132
+ "-l", // files-with-matches
133
+ "-I", // skip binary files
134
+ "-E", // extended regex
135
+ "-e",
136
+ grepPattern,
137
+ "--", // separator
138
+ "src/", // limit to src directory
139
+ ];
140
+
141
+ try {
142
+ const proc = Bun.spawn(grepCommand, {
143
+ cwd: workdir,
144
+ stdout: "pipe",
145
+ stderr: "pipe",
146
+ });
147
+
148
+ const stdout = await new Response(proc.stdout).text();
149
+ const exitCode = await proc.exited;
150
+
151
+ // git grep returns 1 when no matches found (not an error)
152
+ if (exitCode !== 0 && exitCode !== 1) {
153
+ const stderr = await new Response(proc.stderr).text();
154
+ logger.warn("auto-detect", "git grep failed", { exitCode, stderr: stderr.trim() });
155
+ return [];
156
+ }
157
+
158
+ if (!stdout.trim()) {
159
+ logger.debug("auto-detect", "No files matched keywords", { keywords });
160
+ return [];
161
+ }
162
+
163
+ // Parse file list
164
+ const allFiles = stdout
165
+ .trim()
166
+ .split("\n")
167
+ .map((line) => line.trim())
168
+ .filter((line) => line.length > 0);
169
+
170
+ // Filter out test files, index files, generated files
171
+ const filtered = allFiles.filter((filePath) => {
172
+ const lower = filePath.toLowerCase();
173
+ // Exclude test files
174
+ if (lower.includes(".test.") || lower.includes(".spec.") || lower.includes("/test/")) {
175
+ return false;
176
+ }
177
+ // Exclude index files (barrel exports, low signal)
178
+ if (lower.endsWith("/index.ts") || lower.endsWith("/index.js")) {
179
+ return false;
180
+ }
181
+ // Exclude generated files
182
+ if (lower.includes(".generated.") || lower.includes("/__generated__/")) {
183
+ return false;
184
+ }
185
+ return true;
186
+ });
187
+
188
+ // Score and sort by relevance
189
+ // Simple heuristic: count keyword matches in file path
190
+ interface ScoredFile {
191
+ path: string;
192
+ score: number;
193
+ }
194
+
195
+ const scored: ScoredFile[] = filtered.map((filePath) => {
196
+ const lowerPath = filePath.toLowerCase();
197
+ const score = keywords.filter((kw) => lowerPath.includes(kw)).length;
198
+ return { path: filePath, score };
199
+ });
200
+
201
+ // Sort by score descending, then alphabetically
202
+ scored.sort((a, b) => {
203
+ if (a.score !== b.score) {
204
+ return b.score - a.score; // Higher score first
205
+ }
206
+ return a.path.localeCompare(b.path); // Alphabetical tiebreaker
207
+ });
208
+
209
+ // Cap at maxFiles
210
+ const selected = scored.slice(0, maxFiles).map((s) => s.path);
211
+
212
+ logger.info("auto-detect", "Auto-detected context files", {
213
+ keywords,
214
+ totalMatches: allFiles.length,
215
+ afterFilter: filtered.length,
216
+ selected: selected.length,
217
+ files: selected,
218
+ });
219
+
220
+ return selected;
221
+ } catch (error) {
222
+ logger.warn("auto-detect", "Auto-detection failed", {
223
+ error: error instanceof Error ? error.message : String(error),
224
+ });
225
+ return [];
226
+ }
227
+ }
@@ -0,0 +1,246 @@
1
+ /**
2
+ * Context builder for story-scoped prompt optimization
3
+ *
4
+ * Extracts current story + dependency stories from PRD and builds context within token budget.
5
+ */
6
+
7
+ import path from "node:path";
8
+ import type { NaxConfig } from "../config";
9
+ import { getLogger } from "../logger";
10
+ import type { UserStory } from "../prd";
11
+ import { countStories, getContextFiles } from "../prd";
12
+ import { autoDetectContextFiles } from "./auto-detect";
13
+ import {
14
+ createDependencyContext,
15
+ createErrorContext,
16
+ createFileContext,
17
+ createProgressContext,
18
+ createStoryContext,
19
+ createTestCoverageContext,
20
+ estimateTokens,
21
+ } from "./elements";
22
+ import { generateTestCoverageSummary } from "./test-scanner";
23
+ import type { BuiltContext, ContextBudget, ContextElement, StoryContext } from "./types";
24
+
25
+ // Re-export for backward compatibility
26
+ export {
27
+ estimateTokens,
28
+ createStoryContext,
29
+ createDependencyContext,
30
+ createErrorContext,
31
+ createProgressContext,
32
+ createFileContext,
33
+ createTestCoverageContext,
34
+ } from "./elements";
35
+ export { formatContextAsMarkdown } from "./formatter";
36
+
37
+ /** Sort context elements by priority (descending) and token count (ascending for same priority) */
38
+ export function sortContextElements(elements: ContextElement[]): ContextElement[] {
39
+ return [...elements].sort((a, b) => {
40
+ if (a.priority !== b.priority) return b.priority - a.priority;
41
+ return a.tokens - b.tokens;
42
+ });
43
+ }
44
+
45
+ /** Generate progress summary */
46
+ function generateProgressSummary(prd: StoryContext["prd"]): string {
47
+ const counts = countStories(prd);
48
+ const total = counts.total;
49
+ const complete = counts.passed + counts.failed;
50
+ if (counts.failed > 0) {
51
+ return `Progress: ${complete}/${total} stories complete (${counts.passed} passed, ${counts.failed} failed)`;
52
+ }
53
+ return `Progress: ${complete}/${total} stories complete (${counts.passed} passed)`;
54
+ }
55
+
56
+ /** Generate human-readable summary of built context */
57
+ function generateSummary(elements: ContextElement[], totalTokens: number, truncated: boolean): string {
58
+ const counts: Record<string, number> = {
59
+ story: 0,
60
+ dependency: 0,
61
+ error: 0,
62
+ progress: 0,
63
+ file: 0,
64
+ "test-coverage": 0,
65
+ };
66
+ for (const element of elements) {
67
+ counts[element.type]++;
68
+ }
69
+ const parts: string[] = [];
70
+ if (counts.progress > 0) parts.push(`${counts.progress} progress`);
71
+ if (counts.story > 0) parts.push(`${counts.story} story`);
72
+ if (counts.dependency > 0) parts.push(`${counts.dependency} dependencies`);
73
+ if (counts.error > 0) parts.push(`${counts.error} errors`);
74
+ if (counts.file > 0) parts.push(`${counts.file} files`);
75
+ if (counts["test-coverage"] > 0) parts.push("test coverage");
76
+
77
+ const summary = `Context: ${parts.join(", ")} (${totalTokens} tokens)`;
78
+ return truncated ? `${summary} [TRUNCATED]` : summary;
79
+ }
80
+
81
+ /** Build context from PRD + current story within token budget. */
82
+ export async function buildContext(storyContext: StoryContext, budget: ContextBudget): Promise<BuiltContext> {
83
+ const { prd, currentStoryId } = storyContext;
84
+ const elements: ContextElement[] = [];
85
+
86
+ const currentStory = prd.userStories.find((s) => s.id === currentStoryId);
87
+ if (!currentStory) throw new Error(`Story ${currentStoryId} not found in PRD`);
88
+
89
+ // Add progress summary (highest priority)
90
+ elements.push(createProgressContext(generateProgressSummary(prd), 100));
91
+
92
+ // Add prior errors (high priority)
93
+ if (currentStory.priorErrors && Array.isArray(currentStory.priorErrors) && currentStory.priorErrors.length > 0) {
94
+ for (const error of currentStory.priorErrors) {
95
+ elements.push(createErrorContext(error, 90));
96
+ }
97
+ }
98
+
99
+ // Add current story (high priority)
100
+ elements.push(createStoryContext(currentStory, 80));
101
+
102
+ // Add dependency stories (medium priority)
103
+ addDependencyElements(elements, currentStory, prd);
104
+
105
+ // Add test coverage summary (priority 85)
106
+ await addTestCoverageElement(elements, storyContext, currentStory);
107
+
108
+ // Add relevant source files (priority 60)
109
+ await addFileElements(elements, storyContext, currentStory);
110
+
111
+ // Select elements within budget
112
+ const sorted = sortContextElements(elements);
113
+ const selected: ContextElement[] = [];
114
+ let totalTokens = 0;
115
+ let truncated = false;
116
+
117
+ for (const element of sorted) {
118
+ if (totalTokens + element.tokens <= budget.availableForContext) {
119
+ selected.push(element);
120
+ totalTokens += element.tokens;
121
+ } else {
122
+ truncated = true;
123
+ }
124
+ }
125
+
126
+ return { elements: selected, totalTokens, truncated, summary: generateSummary(selected, totalTokens, truncated) };
127
+ }
128
+
129
+ /** Add dependency story elements to the context. */
130
+ function addDependencyElements(elements: ContextElement[], story: UserStory, prd: StoryContext["prd"]): void {
131
+ if (!story.dependencies || story.dependencies.length === 0) return;
132
+ for (const depId of story.dependencies) {
133
+ const depStory = prd.userStories.find((s) => s.id === depId);
134
+ if (depStory) {
135
+ elements.push(createDependencyContext(depStory, 50));
136
+ } else {
137
+ const logger = getLogger();
138
+ logger.warn("context", "Dependency story not found in PRD", { dependencyId: depId, referencedBy: story.id });
139
+ }
140
+ }
141
+ }
142
+
143
+ /** Add test coverage summary element. */
144
+ async function addTestCoverageElement(
145
+ elements: ContextElement[],
146
+ storyContext: StoryContext,
147
+ story: UserStory,
148
+ ): Promise<void> {
149
+ if (storyContext.config?.context?.testCoverage?.enabled === false || !storyContext.workdir) return;
150
+ try {
151
+ const tcConfig = storyContext.config?.context?.testCoverage;
152
+ const contextFiles = getContextFiles(story);
153
+ const scanResult = await generateTestCoverageSummary({
154
+ workdir: storyContext.workdir,
155
+ testDir: tcConfig?.testDir,
156
+ testPattern: tcConfig?.testPattern,
157
+ maxTokens: tcConfig?.maxTokens ?? 500,
158
+ detail: tcConfig?.detail ?? "names-and-counts",
159
+ contextFiles: contextFiles.length > 0 ? contextFiles : undefined,
160
+ scopeToStory: tcConfig?.scopeToStory ?? true,
161
+ });
162
+ if (scanResult.summary) {
163
+ elements.push(createTestCoverageContext(scanResult.summary, scanResult.tokens, 85));
164
+ }
165
+ } catch (error) {
166
+ const logger = getLogger();
167
+ logger.warn("context", "Test coverage scan failed", { error: (error as Error).message });
168
+ }
169
+ }
170
+
171
+ /** Add relevant source file elements (auto-detected or from story config). */
172
+ async function addFileElements(
173
+ elements: ContextElement[],
174
+ storyContext: StoryContext,
175
+ story: UserStory,
176
+ ): Promise<void> {
177
+ const MAX_FILE_SIZE_BYTES = 10 * 1024;
178
+ const MAX_FILES = 5;
179
+
180
+ let contextFiles = getContextFiles(story);
181
+
182
+ // Auto-detect contextFiles if empty and enabled (BUG-006)
183
+ if (
184
+ contextFiles.length === 0 &&
185
+ storyContext.config?.context?.autoDetect?.enabled !== false &&
186
+ storyContext.workdir
187
+ ) {
188
+ const autoDetectConfig = storyContext.config?.context?.autoDetect;
189
+ try {
190
+ const detected = await autoDetectContextFiles({
191
+ workdir: storyContext.workdir,
192
+ storyTitle: story.title,
193
+ maxFiles: autoDetectConfig?.maxFiles ?? 5,
194
+ traceImports: autoDetectConfig?.traceImports ?? false,
195
+ });
196
+ if (detected.length > 0) {
197
+ contextFiles = detected;
198
+ const logger = getLogger();
199
+ logger.info("context", "Auto-detected context files", { storyId: story.id, files: detected });
200
+ }
201
+ } catch (error) {
202
+ const logger = getLogger();
203
+ logger.warn("context", "Context auto-detection failed", {
204
+ storyId: story.id,
205
+ error: error instanceof Error ? error.message : String(error),
206
+ });
207
+ }
208
+ }
209
+
210
+ if (contextFiles.length === 0) return;
211
+ const filesToLoad = contextFiles.slice(0, MAX_FILES);
212
+ const workdir = storyContext.workdir || process.cwd();
213
+
214
+ for (const relativeFilePath of filesToLoad) {
215
+ try {
216
+ const absolutePath = path.resolve(workdir, relativeFilePath);
217
+ const file = Bun.file(absolutePath);
218
+ if (!(await file.exists())) {
219
+ const logger = getLogger();
220
+ logger.warn("context", "Relevant file not found", { filePath: relativeFilePath, storyId: story.id });
221
+ continue;
222
+ }
223
+ if (file.size > MAX_FILE_SIZE_BYTES) {
224
+ const logger = getLogger();
225
+ logger.warn("context", "File too large", {
226
+ filePath: relativeFilePath,
227
+ sizeKB: Math.round(file.size / 1024),
228
+ maxKB: 10,
229
+ storyId: story.id,
230
+ });
231
+ continue;
232
+ }
233
+ const content = await file.text();
234
+ const ext = path.extname(relativeFilePath).slice(1) || "txt";
235
+ elements.push(
236
+ createFileContext(relativeFilePath, `\`\`\`${ext}\n// File: ${relativeFilePath}\n${content}\n\`\`\``, 60),
237
+ );
238
+ } catch (error) {
239
+ const logger = getLogger();
240
+ logger.warn("context", "Error loading file", {
241
+ filePath: relativeFilePath,
242
+ error: error instanceof Error ? error.message : String(error),
243
+ });
244
+ }
245
+ }
246
+ }
@@ -0,0 +1,83 @@
1
+ /**
2
+ * Context Element Factories
3
+ *
4
+ * Extracted from builder.ts: token estimation and context element creation.
5
+ */
6
+
7
+ import { getLogger } from "../logger";
8
+ import type { UserStory } from "../prd";
9
+ import type { ContextElement } from "./types";
10
+
11
+ /**
12
+ * Approximate character-to-token ratio for token estimation.
13
+ * Value of 3 is a middle ground optimized for mixed content (prose + code + markdown).
14
+ * Slightly overestimates tokens, preventing budget overflow.
15
+ */
16
+ const CHARS_PER_TOKEN = 3;
17
+
18
+ /** Estimate token count for text using character-to-token ratio. */
19
+ export function estimateTokens(text: string): number {
20
+ return Math.ceil(text.length / CHARS_PER_TOKEN);
21
+ }
22
+
23
+ /** Create context element from current story */
24
+ export function createStoryContext(story: UserStory, priority: number): ContextElement {
25
+ const content = formatStoryAsText(story);
26
+ return { type: "story", storyId: story.id, content, priority, tokens: estimateTokens(content) };
27
+ }
28
+
29
+ /** Create context element from dependency story */
30
+ export function createDependencyContext(story: UserStory, priority: number): ContextElement {
31
+ const content = formatStoryAsText(story);
32
+ return { type: "dependency", storyId: story.id, content, priority, tokens: estimateTokens(content) };
33
+ }
34
+
35
+ /** Create context element from error */
36
+ export function createErrorContext(errorMessage: string, priority: number): ContextElement {
37
+ return { type: "error", content: errorMessage, priority, tokens: estimateTokens(errorMessage) };
38
+ }
39
+
40
+ /** Create context element from progress summary */
41
+ export function createProgressContext(progressText: string, priority: number): ContextElement {
42
+ return { type: "progress", content: progressText, priority, tokens: estimateTokens(progressText) };
43
+ }
44
+
45
+ /** Create context element from file content */
46
+ export function createFileContext(filePath: string, content: string, priority: number): ContextElement {
47
+ return { type: "file", filePath, content, priority, tokens: estimateTokens(content) };
48
+ }
49
+
50
+ /** Create context element from test coverage summary */
51
+ export function createTestCoverageContext(content: string, tokens: number, priority: number): ContextElement {
52
+ return { type: "test-coverage", content, priority, tokens };
53
+ }
54
+
55
+ /** Format story as text for context (defensive checks for malformed PRD data) */
56
+ export function formatStoryAsText(story: UserStory): string {
57
+ const parts: string[] = [];
58
+ parts.push(`## ${story.id}: ${story.title}`);
59
+ parts.push("");
60
+ parts.push(`**Description:** ${story.description}`);
61
+ parts.push("");
62
+ parts.push("**Acceptance Criteria:**");
63
+
64
+ if (story.acceptanceCriteria && Array.isArray(story.acceptanceCriteria)) {
65
+ for (const ac of story.acceptanceCriteria) {
66
+ parts.push(`- ${ac}`);
67
+ }
68
+ } else {
69
+ const logger = getLogger();
70
+ logger.warn("context", "Story has invalid acceptanceCriteria", {
71
+ storyId: story.id,
72
+ type: typeof story.acceptanceCriteria,
73
+ });
74
+ parts.push("- (No acceptance criteria defined)");
75
+ }
76
+
77
+ if (story.tags && story.tags.length > 0) {
78
+ parts.push("");
79
+ parts.push(`**Tags:** ${story.tags.join(", ")}`);
80
+ }
81
+
82
+ return parts.join("\n");
83
+ }
@@ -0,0 +1,107 @@
1
+ /**
2
+ * Context Markdown Formatter
3
+ *
4
+ * Extracted from builder.ts: formats built context as markdown for agent consumption.
5
+ */
6
+
7
+ import type { BuiltContext, ContextElement } from "./types";
8
+
9
+ /**
10
+ * Format built context as markdown for agent consumption.
11
+ *
12
+ * Generates markdown with sections for progress, prior errors,
13
+ * test coverage, current story, dependency stories, and relevant files.
14
+ */
15
+ export function formatContextAsMarkdown(built: BuiltContext): string {
16
+ const sections: string[] = [];
17
+
18
+ sections.push("# Story Context\n");
19
+ sections.push(`${built.summary}\n`);
20
+
21
+ // Group by type
22
+ const byType = new Map<string, ContextElement[]>();
23
+ for (const element of built.elements) {
24
+ const existing = byType.get(element.type) || [];
25
+ existing.push(element);
26
+ byType.set(element.type, existing);
27
+ }
28
+
29
+ renderSection(sections, byType, "progress", "## Progress\n", renderSimple);
30
+ renderErrorSection(sections, byType);
31
+ renderSection(sections, byType, "test-coverage", "", renderSimple);
32
+ renderSection(sections, byType, "story", "## Current Story\n", renderSimple);
33
+ renderSection(sections, byType, "dependency", "## Dependency Stories\n", renderSimple);
34
+ renderSection(sections, byType, "file", "## Relevant Source Files\n", renderSimple);
35
+
36
+ return sections.join("\n");
37
+ }
38
+
39
+ /** Render a simple section with elements as-is. */
40
+ function renderSimple(sections: string[], elements: ContextElement[]): void {
41
+ for (const element of elements) {
42
+ sections.push(element.content);
43
+ sections.push("\n");
44
+ }
45
+ }
46
+
47
+ /** Render a typed section if elements exist. */
48
+ function renderSection(
49
+ sections: string[],
50
+ byType: Map<string, ContextElement[]>,
51
+ type: string,
52
+ header: string,
53
+ renderer: (sections: string[], elements: ContextElement[]) => void,
54
+ ): void {
55
+ const elements = byType.get(type);
56
+ if (!elements || elements.length === 0) return;
57
+ if (header) sections.push(header);
58
+ renderer(sections, elements);
59
+ }
60
+
61
+ /** Render error section with special handling for ASSET_CHECK errors. */
62
+ function renderErrorSection(sections: string[], byType: Map<string, ContextElement[]>): void {
63
+ const errorElements = byType.get("error");
64
+ if (!errorElements || errorElements.length === 0) return;
65
+
66
+ const assetCheckErrors: ContextElement[] = [];
67
+ const otherErrors: ContextElement[] = [];
68
+
69
+ for (const element of errorElements) {
70
+ if (element.content.startsWith("ASSET_CHECK_FAILED:")) {
71
+ assetCheckErrors.push(element);
72
+ } else {
73
+ otherErrors.push(element);
74
+ }
75
+ }
76
+
77
+ if (assetCheckErrors.length > 0) {
78
+ sections.push("## ⚠️ MANDATORY: Missing Files from Previous Attempts\n");
79
+ sections.push("**CRITICAL:** Previous attempts failed because these files were not created.\n");
80
+ sections.push("You MUST create these exact files. Do NOT use alternative filenames.\n\n");
81
+
82
+ for (const element of assetCheckErrors) {
83
+ const match = element.content.match(/Missing files: \[([^\]]+)\]/);
84
+ if (match) {
85
+ const fileList = match[1].split(",").map((f) => f.trim());
86
+ sections.push("**Required files:**\n");
87
+ for (const file of fileList) {
88
+ sections.push(`- \`${file}\``);
89
+ }
90
+ sections.push("\n");
91
+ } else {
92
+ sections.push("```");
93
+ sections.push(element.content);
94
+ sections.push("```\n");
95
+ }
96
+ }
97
+ }
98
+
99
+ if (otherErrors.length > 0) {
100
+ sections.push("## Prior Errors\n");
101
+ for (const element of otherErrors) {
102
+ sections.push("```");
103
+ sections.push(element.content);
104
+ sections.push("```\n");
105
+ }
106
+ }
107
+ }