@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,81 @@
1
+ /** TDD session role */
2
+ export type TddSessionRole = "test-writer" | "implementer" | "verifier";
3
+
4
+ /** Failure categories for TDD orchestrator results */
5
+ export type FailureCategory =
6
+ /** Test-writer violated file isolation or created no test files */
7
+ | "isolation-violation"
8
+ /** A session crashed, timed out, or the agent failed to produce usable output */
9
+ | "session-failure"
10
+ /** Tests were written and implemented but still fail after all sessions */
11
+ | "tests-failing"
12
+ /** Verifier explicitly rejected the implementation */
13
+ | "verifier-rejected"
14
+ /** Greenfield project with no test files — TDD not applicable (BUG-010) */
15
+ | "greenfield-no-tests";
16
+
17
+ /** Isolation verification result */
18
+ export interface IsolationCheck {
19
+ /** Whether isolation passed (no hard violations) */
20
+ passed: boolean;
21
+ /** Hard violation files (files that must not be modified) */
22
+ violations: string[];
23
+ /** Soft violation files (allowed-path overrides, warning only) */
24
+ softViolations?: string[];
25
+ /** Warning files (e.g., implementer touching test files slightly) */
26
+ warnings?: string[];
27
+ /** Human-readable description of what was checked */
28
+ description?: string;
29
+ }
30
+
31
+ /** Result of a single TDD session */
32
+ export interface TddSessionResult {
33
+ /** Session role */
34
+ role: TddSessionRole;
35
+ /** Whether session completed successfully */
36
+ success: boolean;
37
+ /** Isolation check results (if applicable) */
38
+ isolation?: IsolationCheck;
39
+ /** Cost of this session (USD) */
40
+ estimatedCost: number;
41
+ /** Files changed by this session (from git diff) */
42
+ filesChanged: string[];
43
+ /** Duration of this session in milliseconds */
44
+ durationMs: number;
45
+ /** Git branch created/used (optional legacy field) */
46
+ branch?: string;
47
+ /** ISO timestamp (optional legacy field) */
48
+ timestamp?: string;
49
+ /** Error message (if success=false) */
50
+ error?: string;
51
+ /** Number of tests written/passed/failed */
52
+ tests?: {
53
+ total: number;
54
+ passed: number;
55
+ failed: number;
56
+ };
57
+ }
58
+
59
+ /** Result of a three-session TDD orchestration */
60
+ export interface ThreeSessionTddResult {
61
+ /** Overall success */
62
+ success: boolean;
63
+ /** Individual session results */
64
+ sessions: TddSessionResult[];
65
+ /** Whether human review is needed */
66
+ needsHumanReview: boolean;
67
+ /** Reason for review (if any) */
68
+ reviewReason?: string;
69
+ /** Total cost of all sessions (USD) */
70
+ totalCost: number;
71
+ /** Whether lite mode was used (skips test-writer/implementer isolation) */
72
+ lite: boolean;
73
+ /** Category of failure (if success is false) */
74
+ failureCategory?: FailureCategory;
75
+ /**
76
+ * Verifier verdict parsed from .nax-verifier-verdict.json (for logging/debugging).
77
+ * null = verdict file was missing or malformed (no verdict available)
78
+ * undefined = verdict was not attempted (e.g. early-exit before session 3 ran)
79
+ */
80
+ verdict?: import("./verdict").VerifierVerdict | null;
81
+ }
@@ -0,0 +1,271 @@
1
+ /**
2
+ * Verifier Verdict — types and reader
3
+ *
4
+ * The verifier (session 3) writes a structured verdict file to
5
+ * `.nax-verifier-verdict.json` in the workdir. This module reads,
6
+ * validates, and interprets that verdict.
7
+ */
8
+
9
+ import { unlink } from "node:fs/promises";
10
+ import path from "node:path";
11
+ import { getLogger } from "../logger";
12
+ import type { FailureCategory } from "./types";
13
+
14
+ /** File name written by the verifier agent */
15
+ export const VERDICT_FILE = ".nax-verifier-verdict.json";
16
+
17
+ /** Structured verdict written by the verifier (session 3) */
18
+ export interface VerifierVerdict {
19
+ /** Schema version */
20
+ version: 1;
21
+
22
+ /** Overall approval */
23
+ approved: boolean;
24
+
25
+ /** Test results */
26
+ tests: {
27
+ /** Did all tests pass? */
28
+ allPassing: boolean;
29
+ /** Number of passing tests */
30
+ passCount: number;
31
+ /** Number of failing tests */
32
+ failCount: number;
33
+ };
34
+
35
+ /** Implementer test modification review */
36
+ testModifications: {
37
+ /** Were test files modified by implementer? */
38
+ detected: boolean;
39
+ /** List of modified test files */
40
+ files: string[];
41
+ /** Are the modifications legitimate? */
42
+ legitimate: boolean;
43
+ /** Reasoning for legitimacy judgment */
44
+ reasoning: string;
45
+ };
46
+
47
+ /** Acceptance criteria check */
48
+ acceptanceCriteria: {
49
+ /** All criteria met? */
50
+ allMet: boolean;
51
+ /** Per-criterion status */
52
+ criteria: Array<{
53
+ criterion: string;
54
+ met: boolean;
55
+ note?: string;
56
+ }>;
57
+ };
58
+
59
+ /** Code quality assessment */
60
+ quality: {
61
+ /** Overall quality: good | acceptable | poor */
62
+ rating: "good" | "acceptable" | "poor";
63
+ /** Issues found */
64
+ issues: string[];
65
+ };
66
+
67
+ /** Fixes applied by the verifier */
68
+ fixes: string[];
69
+
70
+ /** Overall reasoning */
71
+ reasoning: string;
72
+ }
73
+
74
+ /**
75
+ * Validate that a parsed object has the required fields for a VerifierVerdict.
76
+ * Returns true if the object appears to be a valid verdict.
77
+ */
78
+ function isValidVerdict(obj: unknown): obj is VerifierVerdict {
79
+ if (!obj || typeof obj !== "object") return false;
80
+ const v = obj as Record<string, unknown>;
81
+
82
+ // Required top-level fields
83
+ if (v.version !== 1) return false;
84
+ if (typeof v.approved !== "boolean") return false;
85
+
86
+ // tests sub-object
87
+ if (!v.tests || typeof v.tests !== "object") return false;
88
+ const tests = v.tests as Record<string, unknown>;
89
+ if (typeof tests.allPassing !== "boolean") return false;
90
+ if (typeof tests.passCount !== "number") return false;
91
+ if (typeof tests.failCount !== "number") return false;
92
+
93
+ // testModifications sub-object
94
+ if (!v.testModifications || typeof v.testModifications !== "object") return false;
95
+ const mods = v.testModifications as Record<string, unknown>;
96
+ if (typeof mods.detected !== "boolean") return false;
97
+ if (!Array.isArray(mods.files)) return false;
98
+ if (typeof mods.legitimate !== "boolean") return false;
99
+ if (typeof mods.reasoning !== "string") return false;
100
+
101
+ // acceptanceCriteria sub-object
102
+ if (!v.acceptanceCriteria || typeof v.acceptanceCriteria !== "object") return false;
103
+ const ac = v.acceptanceCriteria as Record<string, unknown>;
104
+ if (typeof ac.allMet !== "boolean") return false;
105
+ if (!Array.isArray(ac.criteria)) return false;
106
+
107
+ // quality sub-object
108
+ if (!v.quality || typeof v.quality !== "object") return false;
109
+ const quality = v.quality as Record<string, unknown>;
110
+ if (!["good", "acceptable", "poor"].includes(quality.rating as string)) return false;
111
+ if (!Array.isArray(quality.issues)) return false;
112
+
113
+ // fixes and reasoning
114
+ if (!Array.isArray(v.fixes)) return false;
115
+ if (typeof v.reasoning !== "string") return false;
116
+
117
+ return true;
118
+ }
119
+
120
+ /**
121
+ * Read the verifier verdict file from the workdir.
122
+ *
123
+ * Returns the parsed VerifierVerdict when the file exists and is valid.
124
+ * Returns null if:
125
+ * - File does not exist
126
+ * - File is not valid JSON
127
+ * - Required fields are missing or invalid
128
+ *
129
+ * Never throws.
130
+ */
131
+ export async function readVerdict(workdir: string): Promise<VerifierVerdict | null> {
132
+ const logger = getLogger();
133
+ const verdictPath = path.join(workdir, VERDICT_FILE);
134
+
135
+ try {
136
+ const file = Bun.file(verdictPath);
137
+ const exists = await file.exists();
138
+ if (!exists) {
139
+ return null;
140
+ }
141
+
142
+ let parsed: unknown;
143
+ try {
144
+ parsed = await file.json();
145
+ } catch (parseErr) {
146
+ logger.warn("tdd", "Verifier verdict file is not valid JSON — ignoring", {
147
+ path: verdictPath,
148
+ error: String(parseErr),
149
+ });
150
+ return null;
151
+ }
152
+
153
+ if (!isValidVerdict(parsed)) {
154
+ logger.warn("tdd", "Verifier verdict file missing required fields — ignoring", {
155
+ path: verdictPath,
156
+ });
157
+ return null;
158
+ }
159
+
160
+ return parsed;
161
+ } catch (err) {
162
+ logger.warn("tdd", "Failed to read verifier verdict file — ignoring", {
163
+ path: verdictPath,
164
+ error: String(err),
165
+ });
166
+ return null;
167
+ }
168
+ }
169
+
170
+ /**
171
+ * Delete the verifier verdict file from the workdir.
172
+ * Ignores all errors (file may not exist, permissions, etc.).
173
+ */
174
+ export async function cleanupVerdict(workdir: string): Promise<void> {
175
+ const verdictPath = path.join(workdir, VERDICT_FILE);
176
+ try {
177
+ await unlink(verdictPath);
178
+ } catch {
179
+ // Intentionally ignored — file may not exist or already be deleted
180
+ }
181
+ }
182
+
183
+ /** Result of categorizing a verifier verdict */
184
+ export interface VerdictCategorization {
185
+ success: boolean;
186
+ failureCategory?: FailureCategory;
187
+ reviewReason?: string;
188
+ }
189
+
190
+ /**
191
+ * Categorize a verifier verdict into a success/failure outcome.
192
+ *
193
+ * @param verdict - The parsed verdict (or null if not available)
194
+ * @param testsPass - Fallback: whether tests pass independently (used when verdict is null)
195
+ * @returns Categorized outcome with optional failureCategory and reviewReason
196
+ *
197
+ * Logic:
198
+ * - verdict.approved = true → success
199
+ * - Not approved, illegitimate test mods → verifier-rejected
200
+ * - Not approved, tests failing → tests-failing
201
+ * - Not approved, criteria not met → verifier-rejected
202
+ * - Not approved, poor quality → verifier-rejected
203
+ * - Not approved, other → verifier-rejected (catch-all)
204
+ * - null verdict, testsPass=true → success
205
+ * - null verdict, testsPass=false → tests-failing
206
+ */
207
+ export function categorizeVerdict(verdict: VerifierVerdict | null, testsPass: boolean): VerdictCategorization {
208
+ // No verdict — fall back to test-only check
209
+ if (!verdict) {
210
+ if (testsPass) {
211
+ return { success: true };
212
+ }
213
+ return {
214
+ success: false,
215
+ failureCategory: "tests-failing",
216
+ reviewReason: "Tests failing after all sessions (no verdict file)",
217
+ };
218
+ }
219
+
220
+ // Approved
221
+ if (verdict.approved) {
222
+ return { success: true };
223
+ }
224
+
225
+ // Not approved — classify the reason
226
+
227
+ // 1. Illegitimate test modifications (implementer cheated)
228
+ if (verdict.testModifications.detected && !verdict.testModifications.legitimate) {
229
+ const files = verdict.testModifications.files.join(", ") || "unknown files";
230
+ return {
231
+ success: false,
232
+ failureCategory: "verifier-rejected",
233
+ reviewReason: `Verifier rejected: illegitimate test modifications in ${files}. ${verdict.testModifications.reasoning}`,
234
+ };
235
+ }
236
+
237
+ // 2. Tests failing
238
+ if (!verdict.tests.allPassing) {
239
+ return {
240
+ success: false,
241
+ failureCategory: "tests-failing",
242
+ reviewReason: `Tests failing: ${verdict.tests.failCount} failure(s). ${verdict.reasoning}`,
243
+ };
244
+ }
245
+
246
+ // 3. Acceptance criteria not met
247
+ if (!verdict.acceptanceCriteria.allMet) {
248
+ const unmet = verdict.acceptanceCriteria.criteria.filter((c) => !c.met).map((c) => c.criterion);
249
+ return {
250
+ success: false,
251
+ failureCategory: "verifier-rejected",
252
+ reviewReason: `Acceptance criteria not met: ${unmet.join("; ")}`,
253
+ };
254
+ }
255
+
256
+ // 4. Poor quality
257
+ if (verdict.quality.rating === "poor") {
258
+ return {
259
+ success: false,
260
+ failureCategory: "verifier-rejected",
261
+ reviewReason: `Poor code quality: ${verdict.quality.issues.join("; ")}`,
262
+ };
263
+ }
264
+
265
+ // Catch-all: verdict says not approved but no clear specific reason
266
+ return {
267
+ success: false,
268
+ failureCategory: "verifier-rejected",
269
+ reviewReason: verdict.reasoning || "Verifier rejected without specific reason",
270
+ };
271
+ }
@@ -0,0 +1,265 @@
1
+ /**
2
+ * App — root TUI component.
3
+ *
4
+ * Orchestrates the layout, stories panel, agent panel, and status bar.
5
+ */
6
+
7
+ import { Box, Text, useApp, useInput } from "ink";
8
+ import { useState } from "react";
9
+ import { writeQueueCommand } from "../utils/queue-writer";
10
+ import { AgentPanel } from "./components/AgentPanel";
11
+ import { CostOverlay } from "./components/CostOverlay";
12
+ import { HelpOverlay } from "./components/HelpOverlay";
13
+ import { StatusBar } from "./components/StatusBar";
14
+ import { StoriesPanel } from "./components/StoriesPanel";
15
+ import { type KeyboardAction, useKeyboard } from "./hooks/useKeyboard";
16
+ import { MIN_TERMINAL_WIDTH, useLayout } from "./hooks/useLayout";
17
+ import { usePipelineEvents } from "./hooks/usePipelineEvents";
18
+ import { usePty } from "./hooks/usePty";
19
+ import { PanelFocus } from "./types";
20
+ import type { TuiProps } from "./types";
21
+
22
+ /**
23
+ * Root TUI application component.
24
+ *
25
+ * Renders the TUI with:
26
+ * - Responsive layout (single/narrow/wide)
27
+ * - Stories panel with status icons
28
+ * - Status bar showing current story/stage
29
+ * - Live updates via pipeline events
30
+ *
31
+ * @example
32
+ * ```tsx
33
+ * const emitter = new PipelineEventEmitter();
34
+ *
35
+ * render(
36
+ * <App
37
+ * feature="auth-system"
38
+ * stories={initialStories}
39
+ * totalCost={0}
40
+ * elapsedMs={0}
41
+ * events={emitter}
42
+ * />
43
+ * );
44
+ * ```
45
+ */
46
+ export function App({ feature, stories: initialStories, events, queueFilePath, ptyOptions }: TuiProps) {
47
+ const layout = useLayout();
48
+ const state = usePipelineEvents(
49
+ events,
50
+ initialStories.map((s) => s.story),
51
+ );
52
+ const { exit } = useApp();
53
+
54
+ // Focus management (Tab toggles between Stories and Agent panels)
55
+ const [focus, setFocus] = useState<PanelFocus>(PanelFocus.Stories);
56
+
57
+ // Overlay state
58
+ const [showHelp, setShowHelp] = useState(false);
59
+ const [showCost, setShowCost] = useState(false);
60
+ const [showQuitConfirm, setShowQuitConfirm] = useState(false);
61
+ const [showAbortConfirm, setShowAbortConfirm] = useState(false);
62
+
63
+ // Wire PTY hook for agent session
64
+ const { outputLines: agentOutputLines, handle: ptyHandle } = usePty(ptyOptions ?? null);
65
+
66
+ // Handle keyboard actions
67
+ const handleKeyboardAction = async (action: KeyboardAction) => {
68
+ switch (action.type) {
69
+ case "TOGGLE_FOCUS":
70
+ setFocus((prev) => (prev === PanelFocus.Stories ? PanelFocus.Agent : PanelFocus.Stories));
71
+ break;
72
+
73
+ case "ESCAPE_AGENT":
74
+ setFocus(PanelFocus.Stories);
75
+ break;
76
+
77
+ case "SHOW_HELP":
78
+ setShowHelp(true);
79
+ break;
80
+
81
+ case "SHOW_COST":
82
+ setShowCost(true);
83
+ break;
84
+
85
+ case "CLOSE_OVERLAY":
86
+ setShowHelp(false);
87
+ setShowCost(false);
88
+ setShowQuitConfirm(false);
89
+ setShowAbortConfirm(false);
90
+ break;
91
+
92
+ case "QUIT":
93
+ // If a story is running, show confirmation
94
+ if (state.currentStory) {
95
+ setShowQuitConfirm(true);
96
+ } else {
97
+ exit();
98
+ }
99
+ break;
100
+
101
+ case "PAUSE":
102
+ if (queueFilePath) {
103
+ await writeQueueCommand(queueFilePath, { type: "PAUSE" });
104
+ }
105
+ break;
106
+
107
+ case "ABORT":
108
+ // If a story is running, show confirmation
109
+ if (state.currentStory) {
110
+ setShowAbortConfirm(true);
111
+ } else if (queueFilePath) {
112
+ await writeQueueCommand(queueFilePath, { type: "ABORT" });
113
+ }
114
+ break;
115
+
116
+ case "SKIP":
117
+ if (queueFilePath) {
118
+ await writeQueueCommand(queueFilePath, { type: "SKIP", storyId: action.storyId });
119
+ }
120
+ break;
121
+
122
+ case "RETRY":
123
+ // TODO: Implement retry logic for last failed story
124
+ // This would require tracking the last failed story and resetting its status
125
+ break;
126
+
127
+ default:
128
+ break;
129
+ }
130
+ };
131
+
132
+ // Custom input handler for confirmation dialogs and PTY routing
133
+ useInput((input, key) => {
134
+ // Handle confirmation dialogs
135
+ if (showQuitConfirm || showAbortConfirm) {
136
+ const inputKey = input.toLowerCase();
137
+ if (inputKey === "y") {
138
+ if (showQuitConfirm) {
139
+ exit();
140
+ } else if (showAbortConfirm && queueFilePath) {
141
+ writeQueueCommand(queueFilePath, { type: "ABORT" });
142
+ setShowAbortConfirm(false);
143
+ }
144
+ } else if (inputKey === "n" || input === "\x1b") {
145
+ // n or Esc cancels
146
+ setShowQuitConfirm(false);
147
+ setShowAbortConfirm(false);
148
+ }
149
+ return;
150
+ }
151
+
152
+ // Route input to PTY when agent panel is focused
153
+ if (focus === PanelFocus.Agent && ptyHandle) {
154
+ // Ctrl+] escapes back to TUI controls (handled by useKeyboard)
155
+ if (key.ctrl && input === "]") {
156
+ return; // Let useKeyboard handle it
157
+ }
158
+ // All other input goes to PTY
159
+ ptyHandle.write(input);
160
+ }
161
+ });
162
+
163
+ // Wire keyboard hook (disabled during confirmation dialogs)
164
+ useKeyboard({
165
+ focus,
166
+ currentStory: state.currentStory,
167
+ onAction: handleKeyboardAction,
168
+ disabled: showQuitConfirm || showAbortConfirm,
169
+ });
170
+
171
+ const currentRouting = state.currentStory?.routing;
172
+
173
+ // Warn if terminal is too small
174
+ const isTooSmall = layout.width < MIN_TERMINAL_WIDTH;
175
+
176
+ return (
177
+ <Box flexDirection="column" height="100%">
178
+ {/* Header */}
179
+ <Box paddingX={1} borderStyle="single" borderBottom borderColor="cyan">
180
+ <Text bold color="cyan">
181
+ nax run — {feature}
182
+ </Text>
183
+ </Box>
184
+
185
+ {/* Warning for very small terminals */}
186
+ {isTooSmall && (
187
+ <Box paddingX={1} backgroundColor="yellow">
188
+ <Text color="black">
189
+ ⚠️ Terminal too narrow ({layout.width} cols). Minimum {MIN_TERMINAL_WIDTH} cols recommended.
190
+ </Text>
191
+ </Box>
192
+ )}
193
+
194
+ {/* Main content area */}
195
+ <Box flexDirection={layout.mode === "single" ? "column" : "row"} flexGrow={1}>
196
+ {/* Stories panel */}
197
+ <StoriesPanel
198
+ stories={state.stories}
199
+ totalCost={state.totalCost}
200
+ elapsedMs={state.elapsedMs}
201
+ width={layout.mode === "single" ? layout.width : layout.storiesPanelWidth}
202
+ compact={layout.mode === "single"}
203
+ maxHeight={layout.mode === "single" ? 10 : undefined}
204
+ />
205
+
206
+ {/* Agent panel */}
207
+ <AgentPanel focused={focus === PanelFocus.Agent} outputLines={agentOutputLines} />
208
+ </Box>
209
+
210
+ {/* Status bar */}
211
+ <StatusBar
212
+ currentStory={state.currentStory}
213
+ currentStage={state.currentStage}
214
+ modelTier={currentRouting?.modelTier}
215
+ testStrategy={currentRouting?.testStrategy}
216
+ />
217
+
218
+ {/* Overlays */}
219
+ <HelpOverlay visible={showHelp} />
220
+ <CostOverlay visible={showCost} stories={state.stories} totalCost={state.totalCost} />
221
+
222
+ {/* Quit confirmation */}
223
+ {showQuitConfirm && (
224
+ <Box position="absolute" width="100%" height="100%" justifyContent="center" alignItems="center">
225
+ <Box
226
+ flexDirection="column"
227
+ borderStyle="double"
228
+ borderColor="yellow"
229
+ paddingX={2}
230
+ paddingY={1}
231
+ backgroundColor="black"
232
+ >
233
+ <Text color="yellow">⚠️ Story is running. Quit anyway?</Text>
234
+ <Box paddingTop={1}>
235
+ <Text dimColor>
236
+ Press <Text color="yellow">y</Text> to confirm, <Text color="yellow">n</Text> to cancel
237
+ </Text>
238
+ </Box>
239
+ </Box>
240
+ </Box>
241
+ )}
242
+
243
+ {/* Abort confirmation */}
244
+ {showAbortConfirm && (
245
+ <Box position="absolute" width="100%" height="100%" justifyContent="center" alignItems="center">
246
+ <Box
247
+ flexDirection="column"
248
+ borderStyle="double"
249
+ borderColor="red"
250
+ paddingX={2}
251
+ paddingY={1}
252
+ backgroundColor="black"
253
+ >
254
+ <Text color="red">⚠️ Story is running. Abort anyway?</Text>
255
+ <Box paddingTop={1}>
256
+ <Text dimColor>
257
+ Press <Text color="yellow">y</Text> to confirm, <Text color="yellow">n</Text> to cancel
258
+ </Text>
259
+ </Box>
260
+ </Box>
261
+ </Box>
262
+ )}
263
+ </Box>
264
+ );
265
+ }
@@ -0,0 +1,75 @@
1
+ /**
2
+ * AgentPanel — displays PTY output from agent session.
3
+ *
4
+ * Renders a scrollable text buffer showing live agent output.
5
+ * When focused, displays a border highlight.
6
+ */
7
+
8
+ import { Box, Text } from "ink";
9
+ import Spinner from "ink-spinner";
10
+
11
+ /**
12
+ * Props for AgentPanel component.
13
+ */
14
+ export interface AgentPanelProps {
15
+ /** Whether the panel is focused (receives keyboard input) */
16
+ focused?: boolean;
17
+ /** PTY output lines (buffered) */
18
+ outputLines?: string[];
19
+ }
20
+
21
+ /**
22
+ * Maximum number of output lines to buffer.
23
+ *
24
+ * Prevents memory bloat from long-running agent sessions.
25
+ * Last 500 lines typically contain all relevant info for debugging.
26
+ */
27
+ const MAX_OUTPUT_LINES = 500;
28
+
29
+ /**
30
+ * AgentPanel component.
31
+ *
32
+ * Displays PTY output from the agent session in a scrollable text buffer.
33
+ * Shows a border highlight when focused to indicate keyboard input routing.
34
+ *
35
+ * @example
36
+ * ```tsx
37
+ * const [outputLines, setOutputLines] = useState<string[]>([]);
38
+ *
39
+ * <AgentPanel
40
+ * focused={agentFocused}
41
+ * outputLines={outputLines}
42
+ * onData={(data) => setOutputLines(prev => [...prev, data])}
43
+ * />
44
+ * ```
45
+ */
46
+ export function AgentPanel({ focused = false, outputLines = [] }: AgentPanelProps) {
47
+ const borderColor = focused ? "cyan" : "gray";
48
+
49
+ // Buffer output lines (last N lines only)
50
+ const bufferedLines = outputLines.length > MAX_OUTPUT_LINES ? outputLines.slice(-MAX_OUTPUT_LINES) : outputLines;
51
+
52
+ const hasOutput = bufferedLines.length > 0;
53
+
54
+ return (
55
+ <Box flexDirection="column" flexGrow={1} borderStyle="single" borderColor={borderColor}>
56
+ {/* Header */}
57
+ <Box paddingX={1} borderStyle="single" borderBottom borderColor={borderColor}>
58
+ <Text bold color={focused ? "cyan" : undefined}>
59
+ Agent {focused && <Text dimColor>(focused)</Text>}
60
+ </Text>
61
+ </Box>
62
+
63
+ {/* Output buffer */}
64
+ <Box flexDirection="column" paddingX={1} paddingY={1}>
65
+ {hasOutput ? (
66
+ bufferedLines.map((line, i) => <Text key={`line-${i}-${line.slice(0, 20)}`}>{line}</Text>)
67
+ ) : (
68
+ <Text dimColor>
69
+ <Spinner type="dots" /> Waiting for agent...
70
+ </Text>
71
+ )}
72
+ </Box>
73
+ </Box>
74
+ );
75
+ }