@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,389 @@
1
+ /**
2
+ * Interaction Plugins Network Failure Tests (v0.15.1)
3
+ *
4
+ * Tests network error handling, exponential backoff, payload limits, and malformed input.
5
+ */
6
+
7
+ import { describe, expect, test } from "bun:test";
8
+ import type { InteractionRequest } from "../../src/interaction";
9
+ import { TelegramInteractionPlugin } from "../../src/interaction/plugins/telegram";
10
+ import { WebhookInteractionPlugin } from "../../src/interaction/plugins/webhook";
11
+
12
+ describe("TelegramInteractionPlugin - Network Failures", () => {
13
+ test("should handle network error in send()", async () => {
14
+ const plugin = new TelegramInteractionPlugin();
15
+ await plugin.init({ botToken: "test-token", chatId: "12345" });
16
+
17
+ // Mock fetch to throw network error
18
+ const originalFetch = globalThis.fetch;
19
+ globalThis.fetch = async () => {
20
+ throw new Error("ECONNREFUSED");
21
+ };
22
+
23
+ const request: InteractionRequest = {
24
+ id: "test-network-error",
25
+ type: "confirm",
26
+ featureName: "test-feature",
27
+ stage: "review",
28
+ summary: "Test network error",
29
+ fallback: "abort",
30
+ createdAt: Date.now(),
31
+ };
32
+
33
+ await expect(plugin.send(request)).rejects.toThrow("Failed to send Telegram message");
34
+
35
+ // Restore
36
+ globalThis.fetch = originalFetch;
37
+ });
38
+
39
+ test("should handle malformed API response in send()", async () => {
40
+ const plugin = new TelegramInteractionPlugin();
41
+ await plugin.init({ botToken: "test-token", chatId: "12345" });
42
+
43
+ // Mock fetch to return invalid JSON
44
+ const originalFetch = globalThis.fetch;
45
+ globalThis.fetch = async () => {
46
+ return new Response("not json", { status: 200 });
47
+ };
48
+
49
+ const request: InteractionRequest = {
50
+ id: "test-malformed-response",
51
+ type: "confirm",
52
+ featureName: "test-feature",
53
+ stage: "review",
54
+ summary: "Test malformed response",
55
+ fallback: "abort",
56
+ createdAt: Date.now(),
57
+ };
58
+
59
+ await expect(plugin.send(request)).rejects.toThrow("Failed to send Telegram message");
60
+
61
+ // Restore
62
+ globalThis.fetch = originalFetch;
63
+ });
64
+
65
+ test("should handle HTTP error status in send()", async () => {
66
+ const plugin = new TelegramInteractionPlugin();
67
+ await plugin.init({ botToken: "test-token", chatId: "12345" });
68
+
69
+ // Mock fetch to return 500 error
70
+ const originalFetch = globalThis.fetch;
71
+ globalThis.fetch = async () => {
72
+ return new Response("Internal Server Error", { status: 500 });
73
+ };
74
+
75
+ const request: InteractionRequest = {
76
+ id: "test-http-error",
77
+ type: "confirm",
78
+ featureName: "test-feature",
79
+ stage: "review",
80
+ summary: "Test HTTP error",
81
+ fallback: "abort",
82
+ createdAt: Date.now(),
83
+ };
84
+
85
+ await expect(plugin.send(request)).rejects.toThrow("Telegram API error (500)");
86
+
87
+ // Restore
88
+ globalThis.fetch = originalFetch;
89
+ });
90
+
91
+ test("should return empty updates on getUpdates() network failure", async () => {
92
+ const plugin = new TelegramInteractionPlugin();
93
+ await plugin.init({ botToken: "test-token", chatId: "12345" });
94
+
95
+ // Mock fetch to throw network error
96
+ const originalFetch = globalThis.fetch;
97
+ globalThis.fetch = async () => {
98
+ throw new Error("Network timeout");
99
+ };
100
+
101
+ // Access private method via type assertion for testing
102
+ const getUpdates = (plugin as unknown as { getUpdates: () => Promise<unknown[]> }).getUpdates;
103
+ const updates = await getUpdates.call(plugin);
104
+
105
+ expect(updates).toEqual([]);
106
+
107
+ // Restore
108
+ globalThis.fetch = originalFetch;
109
+ });
110
+
111
+ test("should apply exponential backoff on consecutive getUpdates() failures", async () => {
112
+ const plugin = new TelegramInteractionPlugin();
113
+ await plugin.init({ botToken: "test-token", chatId: "12345" });
114
+
115
+ const originalFetch = globalThis.fetch;
116
+ let fetchCallCount = 0;
117
+
118
+ globalThis.fetch = async () => {
119
+ fetchCallCount++;
120
+ throw new Error("Network error");
121
+ };
122
+
123
+ // Access private getUpdates
124
+ const getUpdates = (plugin as unknown as { getUpdates: () => Promise<unknown[]> }).getUpdates;
125
+
126
+ // Call getUpdates multiple times to trigger backoff
127
+ await getUpdates.call(plugin);
128
+ await getUpdates.call(plugin);
129
+ await getUpdates.call(plugin);
130
+
131
+ // Verify backoff is increasing (check private backoffMs property)
132
+ const backoffMs = (plugin as unknown as { backoffMs: number }).backoffMs;
133
+ expect(backoffMs).toBeGreaterThan(1000); // Should have increased from initial 1000ms
134
+
135
+ // Restore
136
+ globalThis.fetch = originalFetch;
137
+ });
138
+
139
+ test("should reset backoff on successful getUpdates()", async () => {
140
+ const plugin = new TelegramInteractionPlugin();
141
+ await plugin.init({ botToken: "test-token", chatId: "12345" });
142
+
143
+ const originalFetch = globalThis.fetch;
144
+ let callCount = 0;
145
+
146
+ globalThis.fetch = async () => {
147
+ callCount++;
148
+ if (callCount === 1) {
149
+ // First call fails
150
+ throw new Error("Network error");
151
+ }
152
+ // Second call succeeds
153
+ return new Response(JSON.stringify({ ok: true, result: [] }), { status: 200 });
154
+ };
155
+
156
+ const getUpdates = (plugin as unknown as { getUpdates: () => Promise<unknown[]> }).getUpdates;
157
+
158
+ // First call - triggers backoff
159
+ await getUpdates.call(plugin);
160
+ const backoffAfterFailure = (plugin as unknown as { backoffMs: number }).backoffMs;
161
+ expect(backoffAfterFailure).toBeGreaterThan(1000);
162
+
163
+ // Second call - should reset backoff
164
+ await getUpdates.call(plugin);
165
+ const backoffAfterSuccess = (plugin as unknown as { backoffMs: number }).backoffMs;
166
+ expect(backoffAfterSuccess).toBe(1000); // Reset to initial value
167
+
168
+ // Restore
169
+ globalThis.fetch = originalFetch;
170
+ });
171
+ });
172
+
173
+ describe("WebhookInteractionPlugin - Network Failures", () => {
174
+ test("should handle network error in send()", async () => {
175
+ const plugin = new WebhookInteractionPlugin();
176
+ await plugin.init({ url: "https://example.com/webhook" });
177
+
178
+ // Mock fetch to throw network error
179
+ const originalFetch = globalThis.fetch;
180
+ globalThis.fetch = async () => {
181
+ throw new Error("ECONNREFUSED");
182
+ };
183
+
184
+ const request: InteractionRequest = {
185
+ id: "test-network-error",
186
+ type: "confirm",
187
+ featureName: "test-feature",
188
+ stage: "review",
189
+ summary: "Test network error",
190
+ fallback: "abort",
191
+ createdAt: Date.now(),
192
+ };
193
+
194
+ await expect(plugin.send(request)).rejects.toThrow("Failed to send webhook request");
195
+
196
+ // Restore
197
+ globalThis.fetch = originalFetch;
198
+ await plugin.destroy();
199
+ });
200
+
201
+ test("should handle HTTP error in send()", async () => {
202
+ const plugin = new WebhookInteractionPlugin();
203
+ await plugin.init({ url: "https://example.com/webhook" });
204
+
205
+ // Mock fetch to return 503 error
206
+ const originalFetch = globalThis.fetch;
207
+ globalThis.fetch = async () => {
208
+ return new Response("Service Unavailable", { status: 503 });
209
+ };
210
+
211
+ const request: InteractionRequest = {
212
+ id: "test-http-error",
213
+ type: "confirm",
214
+ featureName: "test-feature",
215
+ stage: "review",
216
+ summary: "Test HTTP error",
217
+ fallback: "abort",
218
+ createdAt: Date.now(),
219
+ };
220
+
221
+ await expect(plugin.send(request)).rejects.toThrow("Webhook POST failed (503)");
222
+
223
+ // Restore
224
+ globalThis.fetch = originalFetch;
225
+ await plugin.destroy();
226
+ });
227
+
228
+ test("should apply exponential backoff in receive() polling", async () => {
229
+ const plugin = new WebhookInteractionPlugin();
230
+ await plugin.init({ url: "https://example.com/webhook" });
231
+
232
+ const startTime = Date.now();
233
+
234
+ // Call receive with short timeout
235
+ const response = await plugin.receive("test-request", 500);
236
+
237
+ const duration = Date.now() - startTime;
238
+
239
+ // Should timeout with exponential backoff
240
+ expect(response.action).toBe("skip");
241
+ expect(response.respondedBy).toBe("timeout");
242
+
243
+ // Duration should be at least timeout (500ms) but less than timeout + max backoff
244
+ expect(duration).toBeGreaterThanOrEqual(500);
245
+ expect(duration).toBeLessThan(3000); // Max backoff is 2s
246
+
247
+ await plugin.destroy();
248
+ });
249
+ });
250
+
251
+ describe("WebhookInteractionPlugin - Payload Security", () => {
252
+ test("should reject oversized payload via Content-Length header", async () => {
253
+ const plugin = new WebhookInteractionPlugin();
254
+ await plugin.init({ url: "https://example.com/webhook", maxPayloadBytes: 1000 });
255
+
256
+ // Create a mock request with large Content-Length
257
+ const req = new Request("http://localhost:8765/nax/interact/test-id", {
258
+ method: "POST",
259
+ headers: {
260
+ "Content-Type": "application/json",
261
+ "Content-Length": "10000", // 10KB - exceeds 1000 byte limit
262
+ },
263
+ body: JSON.stringify({ requestId: "test-id", action: "approve" }),
264
+ });
265
+
266
+ const handleRequest = (plugin as unknown as { handleRequest: (req: Request) => Promise<Response> }).handleRequest;
267
+ const response = await handleRequest.call(plugin, req);
268
+
269
+ expect(response.status).toBe(413); // Payload Too Large
270
+ expect(await response.text()).toBe("Payload Too Large");
271
+
272
+ await plugin.destroy();
273
+ });
274
+
275
+ test("should reject oversized payload by actual body size", async () => {
276
+ const plugin = new WebhookInteractionPlugin();
277
+ await plugin.init({ url: "https://example.com/webhook", secret: "test-secret", maxPayloadBytes: 100 });
278
+
279
+ // Create a large payload
280
+ const largePayload = "x".repeat(200);
281
+
282
+ const req = new Request("http://localhost:8765/nax/interact/test-id", {
283
+ method: "POST",
284
+ headers: {
285
+ "Content-Type": "application/json",
286
+ "X-Nax-Signature": "dummy-signature",
287
+ },
288
+ body: largePayload,
289
+ });
290
+
291
+ const handleRequest = (plugin as unknown as { handleRequest: (req: Request) => Promise<Response> }).handleRequest;
292
+ const response = await handleRequest.call(plugin, req);
293
+
294
+ expect(response.status).toBe(413); // Payload Too Large
295
+
296
+ await plugin.destroy();
297
+ });
298
+
299
+ test("should reject malformed JSON with sanitized error", async () => {
300
+ const plugin = new WebhookInteractionPlugin();
301
+ await plugin.init({ url: "https://example.com/webhook" });
302
+
303
+ const req = new Request("http://localhost:8765/nax/interact/test-id", {
304
+ method: "POST",
305
+ headers: { "Content-Type": "application/json" },
306
+ body: "not valid json{",
307
+ });
308
+
309
+ const handleRequest = (plugin as unknown as { handleRequest: (req: Request) => Promise<Response> }).handleRequest;
310
+ const response = await handleRequest.call(plugin, req);
311
+
312
+ expect(response.status).toBe(400);
313
+ const errorText = await response.text();
314
+
315
+ // Should not leak parse error details
316
+ expect(errorText).toBe("Bad Request: Invalid response format");
317
+ expect(errorText).not.toContain("JSON");
318
+ expect(errorText).not.toContain("parse");
319
+ expect(errorText).not.toContain("Unexpected");
320
+
321
+ await plugin.destroy();
322
+ });
323
+
324
+ test("should reject invalid schema with sanitized error", async () => {
325
+ const plugin = new WebhookInteractionPlugin();
326
+ await plugin.init({ url: "https://example.com/webhook" });
327
+
328
+ // Valid JSON but invalid InteractionResponse schema
329
+ const req = new Request("http://localhost:8765/nax/interact/test-id", {
330
+ method: "POST",
331
+ headers: { "Content-Type": "application/json" },
332
+ body: JSON.stringify({ malicious: "payload", action: "invalid-action" }),
333
+ });
334
+
335
+ const handleRequest = (plugin as unknown as { handleRequest: (req: Request) => Promise<Response> }).handleRequest;
336
+ const response = await handleRequest.call(plugin, req);
337
+
338
+ expect(response.status).toBe(400);
339
+ const errorText = await response.text();
340
+
341
+ // Should not leak Zod validation error details
342
+ expect(errorText).toBe("Bad Request: Invalid response format");
343
+ expect(errorText).not.toContain("Zod");
344
+ expect(errorText).not.toContain("validation");
345
+ expect(errorText).not.toContain("enum");
346
+
347
+ await plugin.destroy();
348
+ });
349
+
350
+ test("should reject request without signature when secret is configured", async () => {
351
+ const plugin = new WebhookInteractionPlugin();
352
+ await plugin.init({ url: "https://example.com/webhook", secret: "test-secret" });
353
+
354
+ const req = new Request("http://localhost:8765/nax/interact/test-id", {
355
+ method: "POST",
356
+ headers: { "Content-Type": "application/json" },
357
+ body: JSON.stringify({ requestId: "test-id", action: "approve", respondedAt: Date.now() }),
358
+ });
359
+
360
+ const handleRequest = (plugin as unknown as { handleRequest: (req: Request) => Promise<Response> }).handleRequest;
361
+ const response = await handleRequest.call(plugin, req);
362
+
363
+ expect(response.status).toBe(401); // Unauthorized
364
+ expect(await response.text()).toBe("Unauthorized");
365
+
366
+ await plugin.destroy();
367
+ });
368
+
369
+ test("should reject request with invalid signature", async () => {
370
+ const plugin = new WebhookInteractionPlugin();
371
+ await plugin.init({ url: "https://example.com/webhook", secret: "test-secret" });
372
+
373
+ const req = new Request("http://localhost:8765/nax/interact/test-id", {
374
+ method: "POST",
375
+ headers: {
376
+ "Content-Type": "application/json",
377
+ "X-Nax-Signature": "invalid-signature",
378
+ },
379
+ body: JSON.stringify({ requestId: "test-id", action: "approve", respondedAt: Date.now() }),
380
+ });
381
+
382
+ const handleRequest = (plugin as unknown as { handleRequest: (req: Request) => Promise<Response> }).handleRequest;
383
+ const response = await handleRequest.call(plugin, req);
384
+
385
+ expect(response.status).toBe(401); // Unauthorized
386
+
387
+ await plugin.destroy();
388
+ });
389
+ });
@@ -0,0 +1,164 @@
1
+ /**
2
+ * Interaction Plugins Unit Tests (v0.15.0 Phase 2)
3
+ *
4
+ * Tests for Telegram, Webhook, and Auto plugins.
5
+ */
6
+
7
+ import { describe, expect, test } from "bun:test";
8
+ import type { InteractionRequest } from "../../src/interaction";
9
+ import { AutoInteractionPlugin } from "../../src/interaction/plugins/auto";
10
+ import { TelegramInteractionPlugin } from "../../src/interaction/plugins/telegram";
11
+ import { WebhookInteractionPlugin } from "../../src/interaction/plugins/webhook";
12
+
13
+ describe("TelegramInteractionPlugin", () => {
14
+ test("should validate required config", async () => {
15
+ const plugin = new TelegramInteractionPlugin();
16
+
17
+ // Should throw without botToken and chatId
18
+ await expect(plugin.init({})).rejects.toThrow("botToken and chatId");
19
+ });
20
+
21
+ test("should initialize with config", async () => {
22
+ const plugin = new TelegramInteractionPlugin();
23
+
24
+ await plugin.init({
25
+ botToken: "test-token",
26
+ chatId: "12345",
27
+ });
28
+
29
+ expect(plugin.name).toBe("telegram");
30
+ });
31
+
32
+ test("should initialize with env vars", async () => {
33
+ const plugin = new TelegramInteractionPlugin();
34
+
35
+ // Set env vars
36
+ process.env.NAX_TELEGRAM_TOKEN = "env-token";
37
+ process.env.NAX_TELEGRAM_CHAT_ID = "env-chat";
38
+
39
+ await plugin.init({});
40
+
41
+ expect(plugin.name).toBe("telegram");
42
+
43
+ // Cleanup
44
+ process.env.NAX_TELEGRAM_TOKEN = undefined;
45
+ process.env.NAX_TELEGRAM_CHAT_ID = undefined;
46
+ });
47
+ });
48
+
49
+ describe("WebhookInteractionPlugin", () => {
50
+ test("should validate required config", async () => {
51
+ const plugin = new WebhookInteractionPlugin();
52
+
53
+ // Should throw without url
54
+ await expect(plugin.init({})).rejects.toThrow("url");
55
+ });
56
+
57
+ test("should initialize with config", async () => {
58
+ const plugin = new WebhookInteractionPlugin();
59
+
60
+ await plugin.init({
61
+ url: "https://example.com/webhook",
62
+ callbackPort: 9999,
63
+ });
64
+
65
+ expect(plugin.name).toBe("webhook");
66
+
67
+ await plugin.destroy();
68
+ });
69
+
70
+ test("should default callbackPort to 8765", async () => {
71
+ const plugin = new WebhookInteractionPlugin();
72
+
73
+ await plugin.init({
74
+ url: "https://example.com/webhook",
75
+ });
76
+
77
+ expect(plugin.name).toBe("webhook");
78
+
79
+ await plugin.destroy();
80
+ });
81
+ });
82
+
83
+ describe("AutoInteractionPlugin", () => {
84
+ test("should initialize with defaults", async () => {
85
+ const plugin = new AutoInteractionPlugin();
86
+
87
+ await plugin.init({});
88
+
89
+ expect(plugin.name).toBe("auto");
90
+ });
91
+
92
+ test("should respect config overrides", async () => {
93
+ const plugin = new AutoInteractionPlugin();
94
+
95
+ await plugin.init({
96
+ model: "balanced",
97
+ confidenceThreshold: 0.8,
98
+ maxCostPerDecision: 0.05,
99
+ });
100
+
101
+ expect(plugin.name).toBe("auto");
102
+ });
103
+
104
+ test("should reject security-review triggers", async () => {
105
+ const plugin = new AutoInteractionPlugin();
106
+
107
+ // Mock config
108
+ await plugin.init({
109
+ naxConfig: {
110
+ models: {
111
+ fast: { model: "claude-haiku-3-5" },
112
+ },
113
+ } as unknown as import("../../src/config").NaxConfig,
114
+ });
115
+
116
+ const request: InteractionRequest = {
117
+ id: "test-security-review",
118
+ type: "confirm",
119
+ featureName: "test-feature",
120
+ stage: "review",
121
+ summary: "Security review failed",
122
+ fallback: "abort",
123
+ createdAt: Date.now(),
124
+ metadata: {
125
+ trigger: "security-review",
126
+ safety: "red",
127
+ },
128
+ };
129
+
130
+ const response = await plugin.decide(request);
131
+
132
+ // Should return undefined to escalate to human
133
+ expect(response).toBeUndefined();
134
+ });
135
+
136
+ test("should escalate on low confidence", async () => {
137
+ const plugin = new AutoInteractionPlugin();
138
+
139
+ // Set high threshold
140
+ await plugin.init({
141
+ confidenceThreshold: 0.9,
142
+ naxConfig: {
143
+ models: {
144
+ fast: { model: "claude-haiku-3-5" },
145
+ },
146
+ } as unknown as import("../../src/config").NaxConfig,
147
+ });
148
+
149
+ // Mock a request that would get low confidence
150
+ const request: InteractionRequest = {
151
+ id: "test-low-confidence",
152
+ type: "confirm",
153
+ featureName: "test-feature",
154
+ stage: "custom",
155
+ summary: "Ambiguous decision",
156
+ fallback: "continue",
157
+ createdAt: Date.now(),
158
+ };
159
+
160
+ // Note: This would require mocking the LLM call in a real test
161
+ // For now, we just verify the plugin is configured correctly
162
+ expect(plugin.name).toBe("auto");
163
+ });
164
+ });
@@ -0,0 +1,134 @@
1
+ /**
2
+ * Tests for src/tdd/isolation.ts
3
+ *
4
+ * Covers: isTestFile, isSourceFile, matchesAllowedPath (via verify functions)
5
+ */
6
+
7
+ import { describe, expect, it } from "bun:test";
8
+ import { isSourceFile, isTestFile } from "../../src/tdd/isolation";
9
+
10
+ // ─────────────────────────────────────────────────────────────────────────────
11
+ // isTestFile
12
+ // ─────────────────────────────────────────────────────────────────────────────
13
+
14
+ describe("isTestFile", () => {
15
+ it("detects test/ directory files", () => {
16
+ expect(isTestFile("test/unit/foo.ts")).toBe(true);
17
+ expect(isTestFile("test/integration/bar.test.ts")).toBe(true);
18
+ expect(isTestFile("test/fixtures/data.json")).toBe(true);
19
+ });
20
+
21
+ it("detects tests/ directory files", () => {
22
+ expect(isTestFile("tests/unit/foo.ts")).toBe(true);
23
+ expect(isTestFile("tests/integration/bar.test.ts")).toBe(true);
24
+ });
25
+
26
+ it("detects __tests__/ directory files", () => {
27
+ expect(isTestFile("__tests__/unit/foo.ts")).toBe(true);
28
+ expect(isTestFile("src/__tests__/foo.test.ts")).toBe(true);
29
+ });
30
+
31
+ it("detects .spec extension", () => {
32
+ expect(isTestFile("src/foo.spec.ts")).toBe(true);
33
+ expect(isTestFile("src/utils/bar.spec.js")).toBe(true);
34
+ expect(isTestFile("lib/baz.spec.tsx")).toBe(true);
35
+ });
36
+
37
+ it("detects .test extension", () => {
38
+ expect(isTestFile("src/foo.test.ts")).toBe(true);
39
+ expect(isTestFile("src/utils/bar.test.js")).toBe(true);
40
+ expect(isTestFile("lib/baz.test.tsx")).toBe(true);
41
+ });
42
+
43
+ it("detects .e2e-spec extension (NestJS convention)", () => {
44
+ expect(isTestFile("src/app.e2e-spec.ts")).toBe(true);
45
+ expect(isTestFile("test/e2e/auth.e2e-spec.ts")).toBe(true);
46
+ });
47
+
48
+ it("returns false for source files", () => {
49
+ expect(isTestFile("src/index.ts")).toBe(false);
50
+ expect(isTestFile("lib/utils.ts")).toBe(false);
51
+ expect(isTestFile("packages/core/foo.ts")).toBe(false);
52
+ });
53
+
54
+ it("returns false for config/doc files", () => {
55
+ expect(isTestFile("package.json")).toBe(false);
56
+ expect(isTestFile("README.md")).toBe(false);
57
+ expect(isTestFile("tsconfig.json")).toBe(false);
58
+ });
59
+ });
60
+
61
+ // ─────────────────────────────────────────────────────────────────────────────
62
+ // isSourceFile
63
+ // ─────────────────────────────────────────────────────────────────────────────
64
+
65
+ describe("isSourceFile", () => {
66
+ it("detects src/ directory files", () => {
67
+ expect(isSourceFile("src/index.ts")).toBe(true);
68
+ expect(isSourceFile("src/utils/foo.ts")).toBe(true);
69
+ expect(isSourceFile("src/components/Bar.tsx")).toBe(true);
70
+ });
71
+
72
+ it("detects lib/ directory files", () => {
73
+ expect(isSourceFile("lib/index.ts")).toBe(true);
74
+ expect(isSourceFile("lib/utils/foo.ts")).toBe(true);
75
+ });
76
+
77
+ it("detects packages/ directory files (monorepo)", () => {
78
+ expect(isSourceFile("packages/core/index.ts")).toBe(true);
79
+ expect(isSourceFile("packages/utils/foo.ts")).toBe(true);
80
+ });
81
+
82
+ it("returns false for test files", () => {
83
+ expect(isSourceFile("test/unit/foo.test.ts")).toBe(false);
84
+ expect(isSourceFile("tests/integration/bar.test.ts")).toBe(false);
85
+ expect(isSourceFile("__tests__/baz.ts")).toBe(false);
86
+ });
87
+
88
+ it("returns false for config/doc files", () => {
89
+ expect(isSourceFile("package.json")).toBe(false);
90
+ expect(isSourceFile("README.md")).toBe(false);
91
+ expect(isSourceFile("tsconfig.json")).toBe(false);
92
+ });
93
+
94
+ it("returns false for root files", () => {
95
+ expect(isSourceFile("index.ts")).toBe(false);
96
+ expect(isSourceFile("utils.ts")).toBe(false);
97
+ });
98
+ });
99
+
100
+ // ─────────────────────────────────────────────────────────────────────────────
101
+ // Combined behavior tests
102
+ // ─────────────────────────────────────────────────────────────────────────────
103
+
104
+ describe("combined isTestFile + isSourceFile", () => {
105
+ it("test files in src/ are detected as both test and source", () => {
106
+ const file = "src/foo.test.ts";
107
+ expect(isTestFile(file)).toBe(true);
108
+ expect(isSourceFile(file)).toBe(true);
109
+ });
110
+
111
+ it("spec files in src/ are detected as both test and source", () => {
112
+ const file = "src/foo.spec.ts";
113
+ expect(isTestFile(file)).toBe(true);
114
+ expect(isSourceFile(file)).toBe(true);
115
+ });
116
+
117
+ it("regular src/ files are source but not test", () => {
118
+ const file = "src/foo.ts";
119
+ expect(isTestFile(file)).toBe(false);
120
+ expect(isSourceFile(file)).toBe(true);
121
+ });
122
+
123
+ it("test/ directory files are test but not source (default)", () => {
124
+ const file = "test/foo.ts";
125
+ expect(isTestFile(file)).toBe(true);
126
+ expect(isSourceFile(file)).toBe(false);
127
+ });
128
+
129
+ it("config files are neither test nor source", () => {
130
+ const file = "package.json";
131
+ expect(isTestFile(file)).toBe(false);
132
+ expect(isSourceFile(file)).toBe(false);
133
+ });
134
+ });