@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,298 @@
1
+ import { appendFileSync } from "node:fs";
2
+ import { type FormatterOptions, type VerbosityMode, formatLogEntry } from "../logging/index.js";
3
+ import { formatConsole, formatJsonl } from "./formatters.js";
4
+ import type { LogEntry, LogLevel, LoggerOptions, StoryLogger } from "./types.js";
5
+
6
+ /**
7
+ * Severity ordering for log levels (lower number = more severe)
8
+ */
9
+ const LOG_LEVEL_PRIORITY: Record<LogLevel, number> = {
10
+ error: 0,
11
+ warn: 1,
12
+ info: 2,
13
+ debug: 3,
14
+ };
15
+
16
+ /**
17
+ * Singleton logger instance
18
+ */
19
+ let instance: Logger | null = null;
20
+
21
+ /**
22
+ * Structured logger with level gating and dual output (console + JSONL file)
23
+ *
24
+ * @example
25
+ * ```typescript
26
+ * // Initialize logger (usually in CLI entry point)
27
+ * initLogger({ level: "info", filePath: "nax/features/auth/runs/run-123.jsonl" });
28
+ *
29
+ * // Use logger throughout application
30
+ * const logger = getLogger();
31
+ * logger.info("routing", "Task classified", { complexity: "simple" });
32
+ *
33
+ * // Story-scoped logger
34
+ * const storyLogger = logger.withStory("user-auth-001");
35
+ * storyLogger.info("agent.start", "Starting agent session");
36
+ * ```
37
+ */
38
+ export class Logger {
39
+ private readonly level: LogLevel;
40
+ private readonly filePath?: string;
41
+ private readonly useChalk: boolean;
42
+ private readonly formatterMode?: VerbosityMode;
43
+ private readonly headless: boolean;
44
+
45
+ constructor(options: LoggerOptions) {
46
+ this.level = options.level;
47
+ this.filePath = options.filePath;
48
+ this.useChalk = options.useChalk ?? true;
49
+ this.formatterMode = options.formatterMode;
50
+ this.headless = options.headless ?? false;
51
+
52
+ // Ensure parent directory exists if file path provided
53
+ if (this.filePath) {
54
+ this.initFileDirectory();
55
+ }
56
+ }
57
+
58
+ /**
59
+ * Create parent directory for log file if it doesn't exist
60
+ */
61
+ private initFileDirectory(): void {
62
+ if (!this.filePath) return;
63
+
64
+ try {
65
+ const dir = this.filePath.substring(0, this.filePath.lastIndexOf("/"));
66
+ if (dir) {
67
+ Bun.spawnSync(["mkdir", "-p", dir]);
68
+ }
69
+ } catch (error) {
70
+ console.error(`[logger] Failed to create log directory: ${error}`);
71
+ }
72
+ }
73
+
74
+ /**
75
+ * Check if a log level should be displayed on console
76
+ */
77
+ private shouldLog(level: LogLevel): boolean {
78
+ return LOG_LEVEL_PRIORITY[level] <= LOG_LEVEL_PRIORITY[this.level];
79
+ }
80
+
81
+ /**
82
+ * Internal log method — writes to console (if level permits) and file (always)
83
+ */
84
+ private log(level: LogLevel, stage: string, message: string, data?: Record<string, unknown>, storyId?: string): void {
85
+ const entry: LogEntry = {
86
+ timestamp: new Date().toISOString(),
87
+ level,
88
+ stage,
89
+ message,
90
+ ...(storyId && { storyId }),
91
+ ...(data && { data }),
92
+ };
93
+
94
+ // Console output (level-gated)
95
+ if (this.shouldLog(level)) {
96
+ let consoleOutput: string | null = null;
97
+
98
+ // Use formatter in headless mode if mode is specified
99
+ if (this.headless && this.formatterMode) {
100
+ const formatterOptions: FormatterOptions = {
101
+ mode: this.formatterMode,
102
+ useColor: this.useChalk,
103
+ };
104
+ const formatted = formatLogEntry(entry, formatterOptions);
105
+ if (formatted.shouldDisplay) {
106
+ consoleOutput = formatted.output;
107
+ }
108
+ // If formatter says not to display, consoleOutput stays null
109
+ } else {
110
+ // Default console formatting (existing behavior)
111
+ consoleOutput = this.useChalk ? formatConsole(entry) : this.formatPlainConsole(entry);
112
+ }
113
+
114
+ // Only log if we have output to display
115
+ if (consoleOutput !== null) {
116
+ console.log(consoleOutput);
117
+ }
118
+ }
119
+
120
+ // File output (always write all levels)
121
+ if (this.filePath) {
122
+ this.writeToFile(entry);
123
+ }
124
+ }
125
+
126
+ /**
127
+ * Plain console format (no chalk) — used when useChalk is false
128
+ */
129
+ private formatPlainConsole(entry: LogEntry): string {
130
+ const timestamp = new Date(entry.timestamp).toLocaleTimeString("en-US", {
131
+ hour12: false,
132
+ });
133
+ const parts = [`[${timestamp}]`, `[${entry.stage}]`];
134
+ if (entry.storyId) {
135
+ parts.push(`[${entry.storyId}]`);
136
+ }
137
+ parts.push(entry.message);
138
+ let output = parts.join(" ");
139
+ if (entry.data && Object.keys(entry.data).length > 0) {
140
+ output += `\n${JSON.stringify(entry.data, null, 2)}`;
141
+ }
142
+ return output;
143
+ }
144
+
145
+ /**
146
+ * Write JSONL line to file (synchronous append)
147
+ */
148
+ private writeToFile(entry: LogEntry): void {
149
+ if (!this.filePath) return;
150
+
151
+ try {
152
+ const line = `${formatJsonl(entry)}\n`;
153
+ // Use Node.js fs for simple synchronous append
154
+ appendFileSync(this.filePath, line, "utf8");
155
+ } catch (error) {
156
+ console.error(`[logger] Failed to write to log file: ${error}`);
157
+ }
158
+ }
159
+
160
+ /**
161
+ * Log an error message
162
+ */
163
+ error(stage: string, message: string, data?: Record<string, unknown>): void {
164
+ this.log("error", stage, message, data);
165
+ }
166
+
167
+ /**
168
+ * Log a warning message
169
+ */
170
+ warn(stage: string, message: string, data?: Record<string, unknown>): void {
171
+ this.log("warn", stage, message, data);
172
+ }
173
+
174
+ /**
175
+ * Log an info message
176
+ */
177
+ info(stage: string, message: string, data?: Record<string, unknown>): void {
178
+ this.log("info", stage, message, data);
179
+ }
180
+
181
+ /**
182
+ * Log a debug message
183
+ */
184
+ debug(stage: string, message: string, data?: Record<string, unknown>): void {
185
+ this.log("debug", stage, message, data);
186
+ }
187
+
188
+ /**
189
+ * Create a story-scoped logger that auto-injects storyId
190
+ *
191
+ * @param storyId - Story identifier to inject into all log calls
192
+ * @returns StoryLogger instance
193
+ *
194
+ * @example
195
+ * ```typescript
196
+ * const logger = getLogger();
197
+ * const storyLogger = logger.withStory("user-auth-001");
198
+ * storyLogger.info("agent.start", "Starting agent"); // storyId auto-added
199
+ * ```
200
+ */
201
+ withStory(storyId: string): StoryLogger {
202
+ return {
203
+ error: (stage: string, message: string, data?: Record<string, unknown>) =>
204
+ this.log("error", stage, message, data, storyId),
205
+ warn: (stage: string, message: string, data?: Record<string, unknown>) =>
206
+ this.log("warn", stage, message, data, storyId),
207
+ info: (stage: string, message: string, data?: Record<string, unknown>) =>
208
+ this.log("info", stage, message, data, storyId),
209
+ debug: (stage: string, message: string, data?: Record<string, unknown>) =>
210
+ this.log("debug", stage, message, data, storyId),
211
+ };
212
+ }
213
+
214
+ /**
215
+ * Close logger (cleanup method for shutdown)
216
+ * Note: Bun.write handles file operations automatically, no manual cleanup needed
217
+ */
218
+ close(): void {
219
+ // No-op: Bun handles file operations internally
220
+ }
221
+ }
222
+
223
+ /**
224
+ * Initialize the singleton logger instance
225
+ *
226
+ * @param options - Logger configuration options
227
+ * @throws Error if logger is already initialized
228
+ *
229
+ * @example
230
+ * ```typescript
231
+ * initLogger({
232
+ * level: "info",
233
+ * filePath: "nax/features/auth/runs/2026-02-20T10-30-00Z.jsonl"
234
+ * });
235
+ * ```
236
+ */
237
+ export function initLogger(options: LoggerOptions): Logger {
238
+ if (instance) {
239
+ throw new Error("Logger already initialized. Call getLogger() to access existing instance.");
240
+ }
241
+ instance = new Logger(options);
242
+ return instance;
243
+ }
244
+
245
+ /**
246
+ * Get the singleton logger instance
247
+ *
248
+ * @throws Error if logger has not been initialized
249
+ * @returns Logger instance
250
+ *
251
+ * @example
252
+ * ```typescript
253
+ * const logger = getLogger();
254
+ * logger.info("routing", "Task classified");
255
+ * ```
256
+ */
257
+ /**
258
+ * No-op logger for tests/environments where logger isn't initialized
259
+ */
260
+ const noopLogger: Logger = new Logger({ level: "error", useChalk: false, headless: false });
261
+
262
+ export function getLogger(): Logger {
263
+ if (!instance) {
264
+ return noopLogger;
265
+ }
266
+ return instance;
267
+ }
268
+
269
+ /**
270
+ * Safely get logger instance, returns null if not initialized
271
+ *
272
+ * @returns Logger instance or null if not initialized
273
+ *
274
+ * @example
275
+ * ```typescript
276
+ * const logger = getSafeLogger();
277
+ * logger?.info("routing", "Task classified");
278
+ * ```
279
+ */
280
+ export function getSafeLogger(): Logger | null {
281
+ try {
282
+ const logger = getLogger();
283
+ return logger === noopLogger ? null : logger;
284
+ } catch {
285
+ return null;
286
+ }
287
+ }
288
+
289
+ /**
290
+ * Reset logger singleton (for testing only)
291
+ * @internal
292
+ */
293
+ export function resetLogger(): void {
294
+ if (instance) {
295
+ instance.close();
296
+ }
297
+ instance = null;
298
+ }
@@ -0,0 +1,48 @@
1
+ /**
2
+ * Log level type — ordered from most to least severe
3
+ */
4
+ export type LogLevel = "error" | "warn" | "info" | "debug";
5
+
6
+ /**
7
+ * Structured log entry format
8
+ */
9
+ export interface LogEntry {
10
+ /** ISO timestamp when log was created */
11
+ timestamp: string;
12
+ /** Severity level */
13
+ level: LogLevel;
14
+ /** Pipeline stage or module name */
15
+ stage: string;
16
+ /** Optional story identifier for context */
17
+ storyId?: string;
18
+ /** Human-readable message */
19
+ message: string;
20
+ /** Optional structured metadata */
21
+ data?: Record<string, unknown>;
22
+ }
23
+
24
+ /**
25
+ * Logger initialization options
26
+ */
27
+ export interface LoggerOptions {
28
+ /** Minimum log level for console output (file gets all levels) */
29
+ level: LogLevel;
30
+ /** Optional path to JSONL log file */
31
+ filePath?: string;
32
+ /** Whether to use chalk for console formatting (default: true) */
33
+ useChalk?: boolean;
34
+ /** Formatter verbosity mode for console output (default: uses formatConsole) */
35
+ formatterMode?: "quiet" | "normal" | "verbose" | "json";
36
+ /** Whether running in headless mode (enables formatter) */
37
+ headless?: boolean;
38
+ }
39
+
40
+ /**
41
+ * Story-scoped logger that auto-injects storyId into all log calls
42
+ */
43
+ export interface StoryLogger {
44
+ error(stage: string, message: string, data?: Record<string, unknown>): void;
45
+ warn(stage: string, message: string, data?: Record<string, unknown>): void;
46
+ info(stage: string, message: string, data?: Record<string, unknown>): void;
47
+ debug(stage: string, message: string, data?: Record<string, unknown>): void;
48
+ }
@@ -0,0 +1,355 @@
1
+ /**
2
+ * Human-friendly logging formatter with verbosity levels
3
+ *
4
+ * Transforms JSONL log entries into readable output with emoji indicators
5
+ * and supports multiple verbosity modes: quiet, normal, verbose, json
6
+ */
7
+
8
+ import chalk from "chalk";
9
+ import type { LogEntry } from "../logger/types.js";
10
+ import { EMOJI, type FormatterOptions, type RunSummary } from "./types.js";
11
+
12
+ /**
13
+ * Formatted output entry
14
+ */
15
+ export interface FormattedEntry {
16
+ /** Formatted string ready for console output */
17
+ output: string;
18
+ /** Whether this entry should be shown in the current verbosity mode */
19
+ shouldDisplay: boolean;
20
+ }
21
+
22
+ /**
23
+ * Format a timestamp to local timezone HH:MM:SS
24
+ */
25
+ export function formatTimestamp(isoTimestamp: string): string {
26
+ const date = new Date(isoTimestamp);
27
+ return date.toLocaleTimeString("en-US", {
28
+ hour12: false,
29
+ hour: "2-digit",
30
+ minute: "2-digit",
31
+ second: "2-digit",
32
+ });
33
+ }
34
+
35
+ /**
36
+ * Format duration in milliseconds to human-readable format
37
+ */
38
+ export function formatDuration(durationMs: number): string {
39
+ if (durationMs < 1000) {
40
+ return `${durationMs}ms`;
41
+ }
42
+ if (durationMs < 60000) {
43
+ return `${(durationMs / 1000).toFixed(1)}s`;
44
+ }
45
+ const minutes = Math.floor(durationMs / 60000);
46
+ const seconds = Math.floor((durationMs % 60000) / 1000);
47
+ return `${minutes}m ${seconds}s`;
48
+ }
49
+
50
+ /**
51
+ * Format cost in dollars
52
+ */
53
+ export function formatCost(cost: number): string {
54
+ return `$${cost.toFixed(4)}`;
55
+ }
56
+
57
+ /**
58
+ * Get emoji for stage name
59
+ */
60
+ function getStageEmoji(stage: string): string {
61
+ if (stage.includes("routing")) return EMOJI.routing;
62
+ if (stage.includes("execution") || stage.includes("agent")) return EMOJI.execution;
63
+ if (stage.includes("review")) return EMOJI.review;
64
+ if (stage.includes("tdd")) return EMOJI.tdd;
65
+ return EMOJI.info;
66
+ }
67
+
68
+ /**
69
+ * Check if entry should be displayed based on verbosity mode
70
+ */
71
+ function shouldDisplay(entry: LogEntry, mode: string): boolean {
72
+ if (mode === "json") return true;
73
+ if (mode === "quiet") {
74
+ // Only show critical events: run start/end, story pass/fail
75
+ return (
76
+ entry.stage === "run.start" ||
77
+ entry.stage === "run.end" ||
78
+ entry.stage === "story.complete" ||
79
+ entry.level === "error"
80
+ );
81
+ }
82
+ if (mode === "verbose") return true;
83
+
84
+ // Normal mode: filter out debug logs
85
+ return entry.level !== "debug";
86
+ }
87
+
88
+ /**
89
+ * Format a log entry for human-readable output
90
+ *
91
+ * Supports different verbosity modes and styling options
92
+ */
93
+ export function formatLogEntry(entry: LogEntry, options: FormatterOptions): FormattedEntry {
94
+ const { mode, useColor = true } = options;
95
+
96
+ // JSON mode: pass through raw JSONL
97
+ if (mode === "json") {
98
+ return {
99
+ output: JSON.stringify(entry),
100
+ shouldDisplay: true,
101
+ };
102
+ }
103
+
104
+ // Check if should display based on mode
105
+ if (!shouldDisplay(entry, mode)) {
106
+ return {
107
+ output: "",
108
+ shouldDisplay: false,
109
+ };
110
+ }
111
+
112
+ const timestamp = formatTimestamp(entry.timestamp);
113
+ const colorize = useColor ? chalk : createNoopChalk();
114
+
115
+ // Handle special stages with custom formatting
116
+ if (entry.stage === "run.start") {
117
+ return formatRunStart(entry, colorize, timestamp, mode);
118
+ }
119
+
120
+ if (entry.stage === "story.start" || entry.stage === "iteration.start") {
121
+ return formatStoryStart(entry, colorize, timestamp, mode);
122
+ }
123
+
124
+ if (entry.stage === "story.complete" || entry.stage === "agent.complete") {
125
+ return formatStoryComplete(entry, colorize, timestamp, mode);
126
+ }
127
+
128
+ if (entry.stage.includes("tdd") && entry.message.startsWith("→ Session:")) {
129
+ return formatTDDSession(entry, colorize, timestamp, mode);
130
+ }
131
+
132
+ // Default formatting for other entries
133
+ return formatDefault(entry, colorize, timestamp, mode);
134
+ }
135
+
136
+ /**
137
+ * Format run start event
138
+ */
139
+ function formatRunStart(entry: LogEntry, c: ChalkLike, timestamp: string, mode: string): FormattedEntry {
140
+ const data = entry.data as Record<string, unknown>;
141
+ const lines: string[] = [];
142
+
143
+ lines.push("");
144
+ lines.push(c.bold(c.blue("═".repeat(60))));
145
+ lines.push(c.bold(c.blue(` ${EMOJI.storyStart} NAX RUN STARTED`)));
146
+ lines.push(c.blue("═".repeat(60)));
147
+ lines.push(` ${c.gray("Time:")} ${timestamp}`);
148
+ lines.push(` ${c.gray("Feature:")} ${c.cyan(String(data.feature || "unknown"))}`);
149
+ lines.push(` ${c.gray("Run ID:")} ${c.dim(String(data.runId || "unknown"))}`);
150
+ lines.push(` ${c.gray("Workdir:")} ${c.dim(String(data.workdir || "."))}`);
151
+ lines.push(c.blue("═".repeat(60)));
152
+ lines.push("");
153
+
154
+ return {
155
+ output: lines.join("\n"),
156
+ shouldDisplay: true,
157
+ };
158
+ }
159
+
160
+ /**
161
+ * Format story start event
162
+ */
163
+ function formatStoryStart(entry: LogEntry, c: ChalkLike, timestamp: string, mode: string): FormattedEntry {
164
+ const data = entry.data as Record<string, unknown>;
165
+ const storyId = String(data.storyId || entry.storyId || "unknown");
166
+ const title = String(data.storyTitle || data.title || "Untitled story");
167
+ const complexity = typeof data.complexity === "string" ? data.complexity : "unknown";
168
+ const tier = typeof data.modelTier === "string" ? data.modelTier : "unknown";
169
+ const attempt = typeof data.attempt === "number" ? data.attempt : 1;
170
+
171
+ const lines: string[] = [];
172
+ lines.push("");
173
+ lines.push(c.bold(`${EMOJI.storyStart} ${c.cyan(storyId)}: ${title}`));
174
+
175
+ if (mode === "verbose") {
176
+ lines.push(` ${c.gray("├─")} Complexity: ${c.yellow(complexity)}`);
177
+ lines.push(` ${c.gray("├─")} Tier: ${c.magenta(tier)}`);
178
+ if (attempt > 1) {
179
+ lines.push(` ${c.gray("└─")} Attempt: ${c.yellow(`#${attempt}`)} ${EMOJI.retry}`);
180
+ } else {
181
+ lines.push(` ${c.gray("└─")} Status: ${c.green("starting")}`);
182
+ }
183
+ } else {
184
+ const metadata = [complexity, tier];
185
+ if (attempt > 1) metadata.push(`attempt #${attempt} ${EMOJI.retry}`);
186
+ lines.push(` ${c.gray(metadata.join(" • "))}`);
187
+ }
188
+
189
+ return {
190
+ output: lines.join("\n"),
191
+ shouldDisplay: true,
192
+ };
193
+ }
194
+
195
+ /**
196
+ * Format story completion event
197
+ */
198
+ function formatStoryComplete(entry: LogEntry, c: ChalkLike, timestamp: string, mode: string): FormattedEntry {
199
+ const data = entry.data as Record<string, unknown>;
200
+ const storyId = String(data.storyId || entry.storyId || "unknown");
201
+ const success = data.success ?? true;
202
+ const cost =
203
+ typeof data.cost === "number" ? data.cost : typeof data.estimatedCost === "number" ? data.estimatedCost : 0;
204
+ const duration = typeof data.durationMs === "number" ? data.durationMs : 0;
205
+ const action = data.finalAction || data.action;
206
+
207
+ const emoji = success ? EMOJI.success : action === "escalate" ? EMOJI.retry : EMOJI.failure;
208
+ const statusColor = success ? c.green : action === "escalate" ? c.yellow : c.red;
209
+ const status = success ? "PASSED" : action === "escalate" ? "ESCALATED" : "FAILED";
210
+
211
+ const lines: string[] = [];
212
+ lines.push(statusColor(` ${emoji} ${c.bold(storyId)}: ${status}`));
213
+
214
+ if (mode === "verbose" || mode === "normal") {
215
+ const metadata: string[] = [];
216
+ if (cost > 0) metadata.push(`${EMOJI.cost} ${formatCost(cost)}`);
217
+ if (duration > 0) metadata.push(`${EMOJI.duration} ${formatDuration(duration)}`);
218
+ if (metadata.length > 0) {
219
+ lines.push(` ${c.gray(metadata.join(" "))}`);
220
+ }
221
+ }
222
+
223
+ if (mode === "verbose" && data.reason) {
224
+ lines.push(` ${c.gray(`Reason: ${data.reason}`)}`);
225
+ }
226
+
227
+ lines.push("");
228
+
229
+ return {
230
+ output: lines.join("\n"),
231
+ shouldDisplay: true,
232
+ };
233
+ }
234
+
235
+ /**
236
+ * Format TDD session start
237
+ */
238
+ function formatTDDSession(entry: LogEntry, c: ChalkLike, timestamp: string, mode: string): FormattedEntry {
239
+ if (mode === "quiet") {
240
+ return { output: "", shouldDisplay: false };
241
+ }
242
+
243
+ const data = entry.data as Record<string, unknown>;
244
+ const role = typeof data.role === "string" ? data.role : "unknown";
245
+ const roleLabel = role.replace(/-/g, " ").replace(/\b\w/g, (l: string) => l.toUpperCase());
246
+
247
+ return {
248
+ output: ` ${c.gray("│")} ${EMOJI.tdd} ${c.cyan(roleLabel)}`,
249
+ shouldDisplay: true,
250
+ };
251
+ }
252
+
253
+ /**
254
+ * Format default log entry
255
+ */
256
+ function formatDefault(entry: LogEntry, c: ChalkLike, timestamp: string, mode: string): FormattedEntry {
257
+ const levelEmoji = entry.level === "error" ? EMOJI.failure : entry.level === "warn" ? EMOJI.warning : EMOJI.info;
258
+ const levelColor = entry.level === "error" ? c.red : entry.level === "warn" ? c.yellow : c.gray;
259
+ const stageEmoji = getStageEmoji(entry.stage);
260
+
261
+ const parts = [c.gray(`[${timestamp}]`), levelColor(`${levelEmoji} ${entry.stage}`)];
262
+
263
+ if (entry.storyId) {
264
+ parts.push(c.dim(`[${entry.storyId}]`));
265
+ }
266
+
267
+ parts.push(entry.message);
268
+
269
+ let output = parts.join(" ");
270
+
271
+ // Include data in verbose mode
272
+ if (mode === "verbose" && entry.data && Object.keys(entry.data).length > 0) {
273
+ output += `\n${c.gray(JSON.stringify(entry.data, null, 2))}`;
274
+ }
275
+
276
+ return {
277
+ output,
278
+ shouldDisplay: true,
279
+ };
280
+ }
281
+
282
+ /**
283
+ * Format run summary footer
284
+ */
285
+ export function formatRunSummary(summary: RunSummary, options: FormatterOptions): string {
286
+ const { mode, useColor = true } = options;
287
+
288
+ if (mode === "json") {
289
+ return JSON.stringify(summary);
290
+ }
291
+
292
+ const c = useColor ? chalk : createNoopChalk();
293
+ const lines: string[] = [];
294
+
295
+ lines.push("");
296
+ lines.push(c.blue("═".repeat(60)));
297
+ lines.push(c.bold(c.blue(` ${EMOJI.storyComplete} RUN SUMMARY`)));
298
+ lines.push(c.blue("═".repeat(60)));
299
+
300
+ const successRate = summary.total > 0 ? ((summary.passed / summary.total) * 100).toFixed(1) : "0.0";
301
+ const statusColor = summary.failed === 0 ? c.green : summary.passed > summary.failed ? c.yellow : c.red;
302
+
303
+ lines.push(` ${c.gray("Total:")} ${c.bold(summary.total.toString())}`);
304
+ lines.push(` ${c.green(`${EMOJI.success} Passed:`)} ${c.bold(summary.passed.toString())}`);
305
+
306
+ if (summary.failed > 0) {
307
+ lines.push(` ${c.red(`${EMOJI.failure} Failed:`)} ${c.bold(summary.failed.toString())}`);
308
+ }
309
+
310
+ if (summary.skipped > 0) {
311
+ lines.push(` ${c.yellow(`${EMOJI.skip} Skipped:`)} ${c.bold(summary.skipped.toString())}`);
312
+ }
313
+
314
+ lines.push(` ${c.gray("Success:")} ${statusColor(c.bold(`${successRate}%`))}`);
315
+ lines.push(c.blue("─".repeat(60)));
316
+ lines.push(` ${EMOJI.duration} Duration: ${c.bold(formatDuration(summary.durationMs))}`);
317
+ lines.push(` ${EMOJI.cost} Cost: ${c.bold(formatCost(summary.totalCost))}`);
318
+ lines.push(c.blue("═".repeat(60)));
319
+ lines.push("");
320
+
321
+ return lines.join("\n");
322
+ }
323
+
324
+ /**
325
+ * Chalk-like interface for no-op mode (no colors)
326
+ */
327
+ interface ChalkLike {
328
+ bold: (s: string) => string;
329
+ dim: (s: string) => string;
330
+ gray: (s: string) => string;
331
+ red: (s: string) => string;
332
+ green: (s: string) => string;
333
+ yellow: (s: string) => string;
334
+ blue: (s: string) => string;
335
+ magenta: (s: string) => string;
336
+ cyan: (s: string) => string;
337
+ }
338
+
339
+ /**
340
+ * Create a no-op chalk instance (returns strings unchanged)
341
+ */
342
+ function createNoopChalk(): ChalkLike {
343
+ const noop = (s: string) => s;
344
+ return {
345
+ bold: noop,
346
+ dim: noop,
347
+ gray: noop,
348
+ red: noop,
349
+ green: noop,
350
+ yellow: noop,
351
+ blue: noop,
352
+ magenta: noop,
353
+ cyan: noop,
354
+ };
355
+ }