@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,246 @@
1
+ /**
2
+ * US-002: Precheck orchestrator with formatted output
3
+ *
4
+ * Acceptance criteria verification tests
5
+ */
6
+
7
+ import { afterEach, beforeEach, describe, expect, test } from "bun:test";
8
+
9
+ // Skip in CI: AC2, AC5, AC6 call runPrecheck() which includes checkClaudeCLI as a
10
+ // Tier 1 blocker. Without the claude binary installed, blockers.length > 0 always,
11
+ // breaking assertions like expect(blockers.length).toBe(0). These ACs test correct
12
+ // orchestration behaviour and pass reliably on Mac01/VPS where claude is installed.
13
+ const skipInCI = process.env.CI ? test.skip : test;
14
+ import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
15
+ import { tmpdir } from "node:os";
16
+ import { join } from "node:path";
17
+ import type { NaxConfig } from "../src/config";
18
+ import type { PRD } from "../src/prd/types";
19
+ import { EXIT_CODES, runPrecheck } from "../src/precheck";
20
+
21
+ // Helper to create a minimal valid git environment
22
+ async function setupGitRepo(dir: string): Promise<void> {
23
+ mkdirSync(join(dir, ".git"));
24
+ await Bun.spawn(["git", "init"], { cwd: dir, stdout: "ignore", stderr: "ignore" }).exited;
25
+ await Bun.spawn(["git", "config", "user.name", "Test"], { cwd: dir, stdout: "ignore", stderr: "ignore" }).exited;
26
+ await Bun.spawn(["git", "config", "user.email", "test@test.com"], { cwd: dir, stdout: "ignore", stderr: "ignore" })
27
+ .exited;
28
+ writeFileSync(join(dir, "README.md"), "# Test");
29
+ await Bun.spawn(["git", "add", "."], { cwd: dir, stdout: "ignore", stderr: "ignore" }).exited;
30
+ await Bun.spawn(["git", "commit", "-m", "init"], { cwd: dir, stdout: "ignore", stderr: "ignore" }).exited;
31
+ }
32
+
33
+ const createConfig = (workdir: string): NaxConfig =>
34
+ ({
35
+ execution: {
36
+ maxIterations: 10,
37
+ iterationDelayMs: 1000,
38
+ maxCostUSD: 10,
39
+ testCommand: "echo test",
40
+ lintCommand: "echo lint",
41
+ typecheckCommand: "echo typecheck",
42
+ contextProviderTokenBudget: 2000,
43
+ requireExplicitContextFiles: false,
44
+ preflightExpectedFilesEnabled: false,
45
+ cwd: workdir,
46
+ },
47
+ autoMode: {
48
+ enabled: false,
49
+ defaultAgent: "test",
50
+ fallbackOrder: [],
51
+ complexityRouting: {},
52
+ escalation: { enabled: false, tierOrder: [] },
53
+ },
54
+ quality: { minTestCoverage: 80, requireTypecheck: true, requireLint: true },
55
+ tdd: { strategy: "auto", skipGeneratedVerificationTests: false },
56
+ models: {},
57
+ rectification: {
58
+ enabled: true,
59
+ maxRetries: 2,
60
+ fullSuiteTimeoutSeconds: 120,
61
+ maxFailureSummaryChars: 2000,
62
+ abortOnIncreasingFailures: true,
63
+ },
64
+ }) as NaxConfig;
65
+
66
+ const createPRD = (): PRD => ({
67
+ project: "test",
68
+ feature: "test-feature",
69
+ branchName: "test",
70
+ createdAt: new Date().toISOString(),
71
+ updatedAt: new Date().toISOString(),
72
+ userStories: [
73
+ {
74
+ id: "US-001",
75
+ title: "Test",
76
+ description: "Test description",
77
+ acceptanceCriteria: [],
78
+ tags: [],
79
+ dependencies: [],
80
+ status: "pending",
81
+ passes: false,
82
+ escalations: [],
83
+ attempts: 0,
84
+ },
85
+ ],
86
+ });
87
+
88
+ describe("US-002: Precheck orchestrator acceptance criteria", () => {
89
+ let testDir: string;
90
+
91
+ beforeEach(() => {
92
+ testDir = mkdtempSync(join(tmpdir(), "nax-test-us002-"));
93
+ });
94
+
95
+ afterEach(() => {
96
+ rmSync(testDir, { recursive: true, force: true });
97
+ });
98
+
99
+ test("AC1: Runs Tier 1 checks first, stops on first failure", async () => {
100
+ // No .git directory - should fail on first check
101
+ const config = createConfig(testDir);
102
+ const prd = createPRD();
103
+
104
+ const { result } = await runPrecheck(config, prd, { workdir: testDir, format: "json" });
105
+
106
+ // Should have exactly 1 blocker (fail-fast)
107
+ expect(result.blockers.length).toBe(1);
108
+ expect(result.blockers[0].name).toBe("git-repo-exists");
109
+ });
110
+
111
+ skipInCI("AC2: Runs all Tier 2 checks even if some warn", async () => {
112
+ // Create valid Tier 1 environment
113
+ await setupGitRepo(testDir);
114
+ mkdirSync(join(testDir, "node_modules"));
115
+
116
+ const config = createConfig(testDir);
117
+ const prd = createPRD();
118
+
119
+ const { result } = await runPrecheck(config, prd, { workdir: testDir, format: "json" });
120
+
121
+ // No blockers
122
+ expect(result.blockers.length).toBe(0);
123
+
124
+ // All Tier 2 checks should run (5 total)
125
+ // At least 2 will fail (CLAUDE.md, .gitignore)
126
+ expect(result.warnings.length).toBeGreaterThanOrEqual(2);
127
+ });
128
+
129
+ test("AC3: Human output shows emoji per check result", async () => {
130
+ await setupGitRepo(testDir);
131
+ mkdirSync(join(testDir, "node_modules"));
132
+
133
+ const config = createConfig(testDir);
134
+ const prd = createPRD();
135
+
136
+ // Capture console output
137
+ const originalLog = console.log;
138
+ const logs: string[] = [];
139
+ console.log = (msg: string) => logs.push(msg);
140
+
141
+ try {
142
+ await runPrecheck(config, prd, { workdir: testDir, format: "human" });
143
+
144
+ // Should have emoji indicators
145
+ const hasCheckmark = logs.some((l) => l.includes("✓"));
146
+ const hasWarning = logs.some((l) => l.includes("⚠"));
147
+
148
+ expect(hasCheckmark || hasWarning).toBe(true);
149
+ } finally {
150
+ console.log = originalLog;
151
+ }
152
+ });
153
+
154
+ test("AC4: JSON output matches spec schema", async () => {
155
+ await setupGitRepo(testDir);
156
+ mkdirSync(join(testDir, "node_modules"));
157
+
158
+ const config = createConfig(testDir);
159
+ const prd = createPRD();
160
+
161
+ const originalLog = console.log;
162
+ let jsonOutput = "";
163
+ console.log = (msg: string) => {
164
+ jsonOutput += msg;
165
+ };
166
+
167
+ try {
168
+ await runPrecheck(config, prd, { workdir: testDir, format: "json" });
169
+
170
+ const output = JSON.parse(jsonOutput);
171
+
172
+ // Verify schema fields
173
+ expect(typeof output.passed).toBe("boolean");
174
+ expect(Array.isArray(output.blockers)).toBe(true);
175
+ expect(Array.isArray(output.warnings)).toBe(true);
176
+ expect(output.summary).toBeDefined();
177
+ expect(typeof output.summary.total).toBe("number");
178
+ expect(typeof output.summary.passed).toBe("number");
179
+ expect(typeof output.summary.failed).toBe("number");
180
+ expect(typeof output.summary.warnings).toBe("number");
181
+ expect(output.feature).toBe("test-feature");
182
+ } finally {
183
+ console.log = originalLog;
184
+ }
185
+ });
186
+
187
+ skipInCI("AC5: Exit code 0 for pass, 1 for blocker, 2 for invalid PRD", async () => {
188
+ // Test exit code 0 (pass)
189
+ await setupGitRepo(testDir);
190
+ mkdirSync(join(testDir, "node_modules"));
191
+ writeFileSync(join(testDir, "CLAUDE.md"), "# Test");
192
+ writeFileSync(join(testDir, ".gitignore"), "nax.lock\nruns/\ntest/tmp/");
193
+ await Bun.spawn(["git", "add", "."], { cwd: testDir, stdout: "ignore" }).exited;
194
+ await Bun.spawn(["git", "commit", "-m", "add"], { cwd: testDir, stdout: "ignore" }).exited;
195
+
196
+ let config = createConfig(testDir);
197
+ const prd = createPRD();
198
+ let result = await runPrecheck(config, prd, { workdir: testDir, format: "json" });
199
+ expect(result.exitCode).toBe(EXIT_CODES.SUCCESS);
200
+
201
+ // Test exit code 1 (blocker)
202
+ const testDir2 = mkdtempSync(join(tmpdir(), "nax-test-blocker-"));
203
+ config = createConfig(testDir2);
204
+ result = await runPrecheck(config, prd, { workdir: testDir2, format: "json" });
205
+ expect(result.exitCode).toBe(EXIT_CODES.BLOCKER);
206
+ rmSync(testDir2, { recursive: true, force: true });
207
+
208
+ // Test exit code 2 (invalid PRD)
209
+ const testDir3 = mkdtempSync(join(tmpdir(), "nax-test-invalid-prd-"));
210
+ await setupGitRepo(testDir3);
211
+ mkdirSync(join(testDir3, "node_modules"));
212
+ config = createConfig(testDir3);
213
+ const invalidPRD: PRD = {
214
+ ...prd,
215
+ userStories: [{ ...prd.userStories[0], id: "", title: "", description: "" }],
216
+ };
217
+ result = await runPrecheck(config, invalidPRD, { workdir: testDir3, format: "json" });
218
+ expect(result.exitCode).toBe(EXIT_CODES.INVALID_PRD);
219
+ rmSync(testDir3, { recursive: true, force: true });
220
+ });
221
+
222
+ test("AC6: Summary line shows total checks/passed/failed/warnings", async () => {
223
+ await setupGitRepo(testDir);
224
+ mkdirSync(join(testDir, "node_modules"));
225
+
226
+ const config = createConfig(testDir);
227
+ const prd = createPRD();
228
+
229
+ const originalLog = console.log;
230
+ const logs: string[] = [];
231
+ console.log = (msg: string) => logs.push(msg);
232
+
233
+ try {
234
+ await runPrecheck(config, prd, { workdir: testDir, format: "human" });
235
+
236
+ // Should have summary with counts
237
+ const hasSummary = logs.some(
238
+ (l) => l.includes("total") && l.includes("passed") && (l.includes("failed") || l.includes("warnings")),
239
+ );
240
+
241
+ expect(hasSummary).toBe(true);
242
+ } finally {
243
+ console.log = originalLog;
244
+ }
245
+ });
246
+ });
@@ -0,0 +1,194 @@
1
+ /**
2
+ * Acceptance Tests for CM-003: nax config (default view)
3
+ *
4
+ * Tests the acceptance criteria for running `nax config` without flags.
5
+ *
6
+ * ACCEPTANCE CRITERIA:
7
+ * 1. Running `nax config` prints the effective merged config as formatted JSON
8
+ * 2. Header shows paths of config files found (global, project)
9
+ * 3. Missing config files noted in header
10
+ */
11
+
12
+ import { describe, test, expect, beforeEach, afterEach } from "bun:test";
13
+ import { mkdtempSync, rmSync, writeFileSync, mkdirSync } from "node:fs";
14
+ import { tmpdir } from "node:os";
15
+ import { join } from "node:path";
16
+
17
+ describe("CM-003: nax config (default view)", () => {
18
+ let tempDir: string;
19
+ let originalCwd: string;
20
+
21
+ beforeEach(() => {
22
+ tempDir = mkdtempSync(join(tmpdir(), "cm-003-test-"));
23
+ originalCwd = process.cwd();
24
+ });
25
+
26
+ afterEach(() => {
27
+ process.chdir(originalCwd);
28
+ rmSync(tempDir, { recursive: true, force: true });
29
+ });
30
+
31
+ // AC1: Running `nax config` prints the effective merged config as formatted JSON
32
+ test("AC1: prints effective merged config as formatted JSON", async () => {
33
+ process.chdir(tempDir);
34
+
35
+ const proc = Bun.spawn(["bun", join(import.meta.dir, "../../bin/nax.ts"), "config"], {
36
+ cwd: tempDir,
37
+ stdout: "pipe",
38
+ stderr: "pipe",
39
+ });
40
+
41
+ const output = await new Response(proc.stdout).text();
42
+ const exitCode = await proc.exited;
43
+
44
+ expect(exitCode).toBe(0);
45
+
46
+ // Should output valid JSON
47
+ const lines = output.split("\n");
48
+ const jsonStartIndex = lines.findIndex((line) => line.startsWith("{"));
49
+ expect(jsonStartIndex).toBeGreaterThanOrEqual(0);
50
+
51
+ const jsonOutput = lines.slice(jsonStartIndex).join("\n");
52
+ expect(() => JSON.parse(jsonOutput)).not.toThrow();
53
+
54
+ // Verify it's the merged config (contains version, models, etc.)
55
+ const parsed = JSON.parse(jsonOutput);
56
+ expect(parsed.version).toBe(1);
57
+ expect(parsed.models).toBeDefined();
58
+ expect(parsed.autoMode).toBeDefined();
59
+ expect(parsed.execution).toBeDefined();
60
+
61
+ // Verify it's formatted (pretty-printed with indentation)
62
+ expect(jsonOutput).toContain(" "); // Should have 2-space indentation
63
+ });
64
+
65
+ // AC2: Header shows paths of config files found (global, project)
66
+ test("AC2: header shows global config path", async () => {
67
+ process.chdir(tempDir);
68
+
69
+ const proc = Bun.spawn(["bun", join(import.meta.dir, "../../bin/nax.ts"), "config"], {
70
+ cwd: tempDir,
71
+ stdout: "pipe",
72
+ stderr: "pipe",
73
+ });
74
+
75
+ const output = await new Response(proc.stdout).text();
76
+ const exitCode = await proc.exited;
77
+
78
+ expect(exitCode).toBe(0);
79
+
80
+ // Should show global config line
81
+ expect(output).toContain("// Global config:");
82
+ });
83
+
84
+ test("AC2: header shows project config path when present", async () => {
85
+ // Create project config
86
+ const naxDir = join(tempDir, "nax");
87
+ mkdirSync(naxDir, { recursive: true });
88
+ writeFileSync(
89
+ join(naxDir, "config.json"),
90
+ JSON.stringify({
91
+ execution: {
92
+ maxIterations: 42,
93
+ },
94
+ }),
95
+ );
96
+
97
+ process.chdir(tempDir);
98
+
99
+ const proc = Bun.spawn(["bun", join(import.meta.dir, "../../bin/nax.ts"), "config"], {
100
+ cwd: tempDir,
101
+ stdout: "pipe",
102
+ stderr: "pipe",
103
+ });
104
+
105
+ const output = await new Response(proc.stdout).text();
106
+ const exitCode = await proc.exited;
107
+
108
+ expect(exitCode).toBe(0);
109
+
110
+ // Should show project config path
111
+ expect(output).toContain("// Project config:");
112
+ expect(output).toContain("config.json");
113
+
114
+ // Verify the merged config reflects project override
115
+ const lines = output.split("\n");
116
+ const jsonStartIndex = lines.findIndex((line) => line.startsWith("{"));
117
+ const jsonOutput = lines.slice(jsonStartIndex).join("\n");
118
+ const parsed = JSON.parse(jsonOutput);
119
+ expect(parsed.execution.maxIterations).toBe(42);
120
+ });
121
+
122
+ // AC3: Missing config files noted in header
123
+ test("AC3: notes missing project config in header", async () => {
124
+ // Use a directory without nax/config.json
125
+ const isolatedDir = join(tempDir, "isolated");
126
+ mkdirSync(isolatedDir, { recursive: true });
127
+ process.chdir(isolatedDir);
128
+
129
+ const proc = Bun.spawn(["bun", join(import.meta.dir, "../../bin/nax.ts"), "config"], {
130
+ cwd: isolatedDir,
131
+ stdout: "pipe",
132
+ stderr: "pipe",
133
+ });
134
+
135
+ const output = await new Response(proc.stdout).text();
136
+ const exitCode = await proc.exited;
137
+
138
+ expect(exitCode).toBe(0);
139
+
140
+ // Should indicate project config is not found
141
+ expect(output).toContain("// Project config:");
142
+ expect(output).toContain("(not found)");
143
+ });
144
+
145
+ // Additional verification: header includes resolution order info
146
+ test("header includes resolution order information", async () => {
147
+ process.chdir(tempDir);
148
+
149
+ const proc = Bun.spawn(["bun", join(import.meta.dir, "../../bin/nax.ts"), "config"], {
150
+ cwd: tempDir,
151
+ stdout: "pipe",
152
+ stderr: "pipe",
153
+ });
154
+
155
+ const output = await new Response(proc.stdout).text();
156
+ const exitCode = await proc.exited;
157
+
158
+ expect(exitCode).toBe(0);
159
+
160
+ // Should show resolution order
161
+ expect(output).toContain("// Resolution order:");
162
+ expect(output).toContain("defaults");
163
+ expect(output).toContain("global");
164
+ expect(output).toContain("project");
165
+ });
166
+
167
+ // Additional verification: header precedes JSON
168
+ test("header appears before JSON output with blank line separator", async () => {
169
+ process.chdir(tempDir);
170
+
171
+ const proc = Bun.spawn(["bun", join(import.meta.dir, "../../bin/nax.ts"), "config"], {
172
+ cwd: tempDir,
173
+ stdout: "pipe",
174
+ stderr: "pipe",
175
+ });
176
+
177
+ const output = await new Response(proc.stdout).text();
178
+ const exitCode = await proc.exited;
179
+
180
+ expect(exitCode).toBe(0);
181
+
182
+ const lines = output.split("\n");
183
+
184
+ // Header should come before JSON
185
+ const headerIndex = lines.findIndex((line) => line.includes("// nax Configuration"));
186
+ const jsonIndex = lines.findIndex((line) => line.startsWith("{"));
187
+
188
+ expect(headerIndex).toBeGreaterThanOrEqual(0);
189
+ expect(jsonIndex).toBeGreaterThan(headerIndex);
190
+
191
+ // Should have blank line between header and JSON
192
+ expect(lines[jsonIndex - 1]).toBe("");
193
+ });
194
+ });
@@ -0,0 +1,240 @@
1
+ /**
2
+ * PID Registry Tests
3
+ */
4
+
5
+ import { afterEach, beforeEach, describe, expect, test } from "bun:test";
6
+ import { existsSync, mkdirSync, rmSync } from "node:fs";
7
+ import { PidRegistry } from "../../src/execution/pid-registry";
8
+
9
+ const TEST_WORKDIR = "/tmp/nax-pid-registry-test";
10
+ const PID_FILE = `${TEST_WORKDIR}/.nax-pids`;
11
+
12
+ describe("PidRegistry", () => {
13
+ beforeEach(() => {
14
+ // Create test workdir
15
+ if (!existsSync(TEST_WORKDIR)) {
16
+ mkdirSync(TEST_WORKDIR, { recursive: true });
17
+ }
18
+
19
+ // Clean up any existing .nax-pids file
20
+ if (existsSync(PID_FILE)) {
21
+ rmSync(PID_FILE);
22
+ }
23
+ });
24
+
25
+ afterEach(() => {
26
+ // Cleanup test workdir
27
+ if (existsSync(TEST_WORKDIR)) {
28
+ rmSync(TEST_WORKDIR, { recursive: true });
29
+ }
30
+ });
31
+
32
+ test("register() adds PID to in-memory set and writes to file", async () => {
33
+ const registry = new PidRegistry(TEST_WORKDIR);
34
+
35
+ await registry.register(12345);
36
+
37
+ // Check in-memory state
38
+ expect(registry.getPids()).toEqual([12345]);
39
+
40
+ // Check file content
41
+ const content = await Bun.file(PID_FILE).text();
42
+ const lines = content.split("\n").filter((line) => line.trim());
43
+ expect(lines.length).toBe(1);
44
+
45
+ const entry = JSON.parse(lines[0]);
46
+ expect(entry.pid).toBe(12345);
47
+ expect(entry.workdir).toBe(TEST_WORKDIR);
48
+ expect(entry.spawnedAt).toBeDefined();
49
+ });
50
+
51
+ test("register() appends multiple PIDs to file", async () => {
52
+ const registry = new PidRegistry(TEST_WORKDIR);
53
+
54
+ await registry.register(12345);
55
+ await registry.register(67890);
56
+
57
+ // Check in-memory state
58
+ expect(registry.getPids()).toEqual([12345, 67890]);
59
+
60
+ // Check file content
61
+ const content = await Bun.file(PID_FILE).text();
62
+ const lines = content.split("\n").filter((line) => line.trim());
63
+ expect(lines.length).toBe(2);
64
+
65
+ const entry1 = JSON.parse(lines[0]);
66
+ expect(entry1.pid).toBe(12345);
67
+
68
+ const entry2 = JSON.parse(lines[1]);
69
+ expect(entry2.pid).toBe(67890);
70
+ });
71
+
72
+ test("unregister() removes PID from in-memory set and rewrites file", async () => {
73
+ const registry = new PidRegistry(TEST_WORKDIR);
74
+
75
+ await registry.register(12345);
76
+ await registry.register(67890);
77
+ await registry.unregister(12345);
78
+
79
+ // Check in-memory state
80
+ expect(registry.getPids()).toEqual([67890]);
81
+
82
+ // Check file content
83
+ const content = await Bun.file(PID_FILE).text();
84
+ const lines = content.split("\n").filter((line) => line.trim());
85
+ expect(lines.length).toBe(1);
86
+
87
+ const entry = JSON.parse(lines[0]);
88
+ expect(entry.pid).toBe(67890);
89
+ });
90
+
91
+ test("unregister() clears file when last PID is removed", async () => {
92
+ const registry = new PidRegistry(TEST_WORKDIR);
93
+
94
+ await registry.register(12345);
95
+ await registry.unregister(12345);
96
+
97
+ // Check in-memory state
98
+ expect(registry.getPids()).toEqual([]);
99
+
100
+ // Check file is empty
101
+ const content = await Bun.file(PID_FILE).text();
102
+ expect(content.trim()).toBe("");
103
+ });
104
+
105
+ test("killAll() clears in-memory PIDs and registry file", async () => {
106
+ const registry = new PidRegistry(TEST_WORKDIR);
107
+
108
+ // Register non-existent PIDs (will fail to kill but should clear registry)
109
+ await registry.register(99999);
110
+ await registry.register(88888);
111
+
112
+ await registry.killAll();
113
+
114
+ // Check in-memory state
115
+ expect(registry.getPids()).toEqual([]);
116
+
117
+ // Check file is empty
118
+ const content = await Bun.file(PID_FILE).text();
119
+ expect(content.trim()).toBe("");
120
+ });
121
+
122
+ test("killAll() handles empty registry gracefully", async () => {
123
+ const registry = new PidRegistry(TEST_WORKDIR);
124
+
125
+ // Should not throw
126
+ await registry.killAll();
127
+
128
+ expect(registry.getPids()).toEqual([]);
129
+ });
130
+
131
+ test("cleanupStale() reads and kills PIDs from previous run", async () => {
132
+ // Simulate a previous run that left PIDs in the file
133
+ const entry1 = JSON.stringify({
134
+ pid: 99999, // Non-existent PID
135
+ spawnedAt: new Date().toISOString(),
136
+ workdir: TEST_WORKDIR,
137
+ });
138
+ const entry2 = JSON.stringify({
139
+ pid: 88888, // Non-existent PID
140
+ spawnedAt: new Date().toISOString(),
141
+ workdir: TEST_WORKDIR,
142
+ });
143
+ await Bun.write(PID_FILE, `${entry1}\n${entry2}\n`);
144
+
145
+ // Create new registry and cleanup stale PIDs
146
+ const registry = new PidRegistry(TEST_WORKDIR);
147
+ await registry.cleanupStale();
148
+
149
+ // Check file is cleared
150
+ const content = await Bun.file(PID_FILE).text();
151
+ expect(content.trim()).toBe("");
152
+ });
153
+
154
+ test("cleanupStale() handles missing .nax-pids file", async () => {
155
+ const registry = new PidRegistry(TEST_WORKDIR);
156
+
157
+ // Should not throw
158
+ await registry.cleanupStale();
159
+
160
+ // File should not exist
161
+ expect(existsSync(PID_FILE)).toBe(false);
162
+ });
163
+
164
+ test("cleanupStale() handles empty .nax-pids file", async () => {
165
+ // Create empty file
166
+ await Bun.write(PID_FILE, "");
167
+
168
+ const registry = new PidRegistry(TEST_WORKDIR);
169
+ await registry.cleanupStale();
170
+
171
+ // File should be empty
172
+ const content = await Bun.file(PID_FILE).text();
173
+ expect(content.trim()).toBe("");
174
+ });
175
+
176
+ test("cleanupStale() handles malformed JSON lines gracefully", async () => {
177
+ // Write malformed JSON
178
+ await Bun.write(PID_FILE, 'not valid json\n{"pid":12345}\n');
179
+
180
+ const registry = new PidRegistry(TEST_WORKDIR);
181
+
182
+ // Should not throw
183
+ await registry.cleanupStale();
184
+
185
+ // File should be cleared
186
+ const content = await Bun.file(PID_FILE).text();
187
+ expect(content.trim()).toBe("");
188
+ });
189
+
190
+ test("platform-specific kill command: Linux uses process groups", async () => {
191
+ const registry = new PidRegistry(TEST_WORKDIR, "linux");
192
+
193
+ // Register a non-existent PID
194
+ await registry.register(99999);
195
+
196
+ // Should not throw (process doesn't exist, but kill command should be correct)
197
+ await registry.killAll();
198
+
199
+ expect(registry.getPids()).toEqual([]);
200
+ });
201
+
202
+ test("platform-specific kill command: macOS uses direct PID", async () => {
203
+ const registry = new PidRegistry(TEST_WORKDIR, "darwin");
204
+
205
+ // Register a non-existent PID
206
+ await registry.register(99999);
207
+
208
+ // Should not throw (process doesn't exist, but kill command should be correct)
209
+ await registry.killAll();
210
+
211
+ expect(registry.getPids()).toEqual([]);
212
+ });
213
+
214
+ test("multiple registries can coexist with different workdirs", async () => {
215
+ const workdir1 = `${TEST_WORKDIR}/workspace1`;
216
+ const workdir2 = `${TEST_WORKDIR}/workspace2`;
217
+
218
+ mkdirSync(workdir1, { recursive: true });
219
+ mkdirSync(workdir2, { recursive: true });
220
+
221
+ const registry1 = new PidRegistry(workdir1);
222
+ const registry2 = new PidRegistry(workdir2);
223
+
224
+ await registry1.register(11111);
225
+ await registry2.register(22222);
226
+
227
+ expect(registry1.getPids()).toEqual([11111]);
228
+ expect(registry2.getPids()).toEqual([22222]);
229
+
230
+ // Check separate files
231
+ const content1 = await Bun.file(`${workdir1}/.nax-pids`).text();
232
+ const content2 = await Bun.file(`${workdir2}/.nax-pids`).text();
233
+
234
+ const entry1 = JSON.parse(content1.trim());
235
+ const entry2 = JSON.parse(content2.trim());
236
+
237
+ expect(entry1.pid).toBe(11111);
238
+ expect(entry2.pid).toBe(22222);
239
+ });
240
+ });