@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,137 @@
1
+ /**
2
+ * useLayout hook — terminal width detection and breakpoint management.
3
+ *
4
+ * Provides responsive layout breakpoints for the TUI:
5
+ * - < 80 cols: single column (stacked)
6
+ * - 80-140 cols: 2-column layout
7
+ * - > 140 cols: 2-column with wider agent panel
8
+ */
9
+
10
+ import { useEffect, useState } from "react";
11
+
12
+ /**
13
+ * Layout mode based on terminal width.
14
+ */
15
+ export type LayoutMode = "single" | "narrow" | "wide";
16
+
17
+ /**
18
+ * Layout configuration for rendering.
19
+ */
20
+ export interface LayoutConfig {
21
+ /** Current layout mode */
22
+ mode: LayoutMode;
23
+ /** Terminal width in columns */
24
+ width: number;
25
+ /** Terminal height in rows */
26
+ height: number;
27
+ /** Stories panel width (columns) */
28
+ storiesPanelWidth: number;
29
+ }
30
+
31
+ /**
32
+ * Hook for responsive terminal layout.
33
+ *
34
+ * Detects terminal size and provides breakpoint-based layout config.
35
+ * Handles SIGWINCH (terminal resize) events and cleans up listeners on unmount.
36
+ *
37
+ * Three breakpoints:
38
+ * - < 80 cols: single-column (stacked)
39
+ * - 80-140 cols: two-column (narrow)
40
+ * - > 140 cols: two-column (wide)
41
+ *
42
+ * @example
43
+ * ```tsx
44
+ * const layout = useLayout();
45
+ *
46
+ * return (
47
+ * <Box flexDirection={layout.mode === "single" ? "column" : "row"}>
48
+ * <StoriesPanel width={layout.storiesPanelWidth} compact={layout.mode === "single"} />
49
+ * <AgentPanel />
50
+ * </Box>
51
+ * );
52
+ * ```
53
+ */
54
+ export function useLayout(): LayoutConfig {
55
+ const [layout, setLayout] = useState<LayoutConfig>(() => computeLayout());
56
+
57
+ useEffect(() => {
58
+ const handleResize = () => {
59
+ setLayout(computeLayout());
60
+ };
61
+
62
+ // Listen for terminal resize (SIGWINCH)
63
+ // Node.js/Bun emits 'resize' event on process.stdout when terminal size changes
64
+ process.stdout.on("resize", handleResize);
65
+
66
+ // Clean up listener on unmount to prevent memory leaks
67
+ return () => {
68
+ process.stdout.off("resize", handleResize);
69
+ };
70
+ }, []);
71
+
72
+ return layout;
73
+ }
74
+
75
+ /**
76
+ * Minimum terminal width for usable display.
77
+ */
78
+ export const MIN_TERMINAL_WIDTH = 60;
79
+
80
+ /**
81
+ * Breakpoint: below this width, use single-column layout.
82
+ */
83
+ const BREAKPOINT_NARROW = 80;
84
+
85
+ /**
86
+ * Breakpoint: above this width, use wide two-column layout.
87
+ */
88
+ const BREAKPOINT_WIDE = 140;
89
+
90
+ /**
91
+ * Stories panel width in narrow two-column mode.
92
+ */
93
+ const PANEL_WIDTH_NARROW = 30;
94
+
95
+ /**
96
+ * Stories panel width in wide two-column mode.
97
+ */
98
+ const PANEL_WIDTH_WIDE = 35;
99
+
100
+ /**
101
+ * Maximum stories to show in compact mode (single-column).
102
+ */
103
+ export const COMPACT_MAX_VISIBLE_STORIES = 8;
104
+
105
+ /**
106
+ * Maximum stories before scrolling kicks in (normal mode).
107
+ */
108
+ export const MAX_VISIBLE_STORIES = 15;
109
+
110
+ /**
111
+ * Compute layout configuration based on current terminal size.
112
+ *
113
+ * Breakpoints:
114
+ * - < 80 cols: single-column (stacked), compact mode
115
+ * - 80-140 cols: two-column, narrow stories panel (30 cols)
116
+ * - > 140 cols: two-column, wide stories panel (35 cols)
117
+ */
118
+ function computeLayout(): LayoutConfig {
119
+ const width = process.stdout.columns ?? 80;
120
+ const height = process.stdout.rows ?? 24;
121
+
122
+ let mode: LayoutMode;
123
+ let storiesPanelWidth: number;
124
+
125
+ if (width < BREAKPOINT_NARROW) {
126
+ mode = "single";
127
+ storiesPanelWidth = width;
128
+ } else if (width < BREAKPOINT_WIDE) {
129
+ mode = "narrow";
130
+ storiesPanelWidth = PANEL_WIDTH_NARROW;
131
+ } else {
132
+ mode = "wide";
133
+ storiesPanelWidth = PANEL_WIDTH_WIDE;
134
+ }
135
+
136
+ return { mode, width, height, storiesPanelWidth };
137
+ }
@@ -0,0 +1,183 @@
1
+ /**
2
+ * usePipelineEvents hook — subscribe to pipeline events and update TUI state.
3
+ *
4
+ * Listens to pipeline lifecycle events and updates story display states,
5
+ * cost accumulator, and current stage information.
6
+ */
7
+
8
+ import { useEffect, useState } from "react";
9
+ import type { PipelineEventEmitter, RunSummary } from "../../pipeline/events";
10
+ import type { UserStory } from "../../prd/types";
11
+ import type { StoryDisplayState } from "../types";
12
+
13
+ /**
14
+ * Pipeline state managed by the hook.
15
+ */
16
+ export interface PipelineState {
17
+ /** Story display states */
18
+ stories: StoryDisplayState[];
19
+ /** Current story being executed */
20
+ currentStory?: UserStory;
21
+ /** Current pipeline stage */
22
+ currentStage?: string;
23
+ /** Total cost accumulated */
24
+ totalCost: number;
25
+ /** Elapsed time in milliseconds */
26
+ elapsedMs: number;
27
+ /** Run completion summary (if run finished) */
28
+ summary?: RunSummary;
29
+ }
30
+
31
+ /**
32
+ * Hook for subscribing to pipeline events and managing TUI state.
33
+ *
34
+ * Subscribes to pipeline lifecycle events (story:start, story:complete,
35
+ * story:escalate, stage:enter, run:complete) and updates story display
36
+ * states, cost accumulator, elapsed time, and current stage in real-time.
37
+ *
38
+ * The elapsed timer only runs while a story is active to avoid unnecessary
39
+ * re-renders during idle periods.
40
+ *
41
+ * @param events - Pipeline event emitter (from pipeline runner)
42
+ * @param initialStories - Initial story list from PRD
43
+ * @returns Pipeline state: stories (with status/cost), totalCost, elapsedMs, currentStory, currentStage, summary
44
+ *
45
+ * @example
46
+ * ```tsx
47
+ * const emitter = new PipelineEventEmitter();
48
+ * const state = usePipelineEvents(emitter, prd.userStories);
49
+ *
50
+ * // State automatically updates as pipeline emits events
51
+ * return (
52
+ * <>
53
+ * <StoriesPanel stories={state.stories} totalCost={state.totalCost} />
54
+ * <StatusBar currentStory={state.currentStory} currentStage={state.currentStage} />
55
+ * </>
56
+ * );
57
+ * ```
58
+ */
59
+ export function usePipelineEvents(events: PipelineEventEmitter, initialStories: UserStory[]): PipelineState {
60
+ const [state, setState] = useState<PipelineState>(() => ({
61
+ stories: initialStories.map((story) => ({
62
+ story,
63
+ status: story.passes ? "passed" : "pending",
64
+ routing: story.routing,
65
+ cost: 0,
66
+ })),
67
+ totalCost: 0,
68
+ elapsedMs: 0,
69
+ }));
70
+
71
+ const startTime = Date.now();
72
+
73
+ useEffect(() => {
74
+ // Elapsed timer — only runs when a story is active
75
+ let timer: ReturnType<typeof setInterval> | null = null;
76
+
77
+ const startTimer = () => {
78
+ if (!timer) {
79
+ timer = setInterval(() => {
80
+ setState((prev) => ({
81
+ ...prev,
82
+ elapsedMs: Date.now() - startTime,
83
+ }));
84
+ }, 1000);
85
+ }
86
+ };
87
+
88
+ const stopTimer = () => {
89
+ if (timer) {
90
+ clearInterval(timer);
91
+ timer = null;
92
+ }
93
+ };
94
+
95
+ // story:start — Mark story as running
96
+ const onStoryStart = (story: UserStory) => {
97
+ startTimer();
98
+ setState((prev) => ({
99
+ ...prev,
100
+ currentStory: story,
101
+ stories: prev.stories.map((s) => (s.story.id === story.id ? { ...s, status: "running" as const } : s)),
102
+ }));
103
+ };
104
+
105
+ // story:complete — Mark story as complete (passed/failed/skipped)
106
+ const onStoryComplete = (story: UserStory, result: { action: string; cost?: number }) => {
107
+ stopTimer();
108
+ setState((prev) => {
109
+ const newStories = prev.stories.map((s) => {
110
+ if (s.story.id === story.id) {
111
+ let status: StoryDisplayState["status"] = "pending";
112
+ if (result.action === "continue") {
113
+ status = "passed";
114
+ } else if (result.action === "fail") {
115
+ status = "failed";
116
+ } else if (result.action === "skip") {
117
+ status = "skipped";
118
+ } else if (result.action === "pause") {
119
+ status = "paused";
120
+ }
121
+
122
+ // Accumulate cost from result
123
+ const storyCost = (s.cost || 0) + (result.cost || 0);
124
+ return { ...s, status, cost: storyCost };
125
+ }
126
+ return s;
127
+ });
128
+
129
+ // Update total cost accumulator
130
+ const totalCost = newStories.reduce((sum, s) => sum + (s.cost || 0), 0);
131
+
132
+ return {
133
+ ...prev,
134
+ stories: newStories,
135
+ currentStory: undefined,
136
+ totalCost,
137
+ };
138
+ });
139
+ };
140
+
141
+ // story:escalate — Mark story as retrying
142
+ const onStoryEscalate = (story: UserStory) => {
143
+ setState((prev) => ({
144
+ ...prev,
145
+ stories: prev.stories.map((s) => (s.story.id === story.id ? { ...s, status: "retrying" as const } : s)),
146
+ }));
147
+ };
148
+
149
+ // stage:enter — Update current stage
150
+ const onStageEnter = (stage: string) => {
151
+ setState((prev) => ({
152
+ ...prev,
153
+ currentStage: stage,
154
+ }));
155
+ };
156
+
157
+ // run:complete — Update final summary
158
+ const onRunComplete = (summary: RunSummary) => {
159
+ setState((prev) => ({
160
+ ...prev,
161
+ totalCost: summary.totalCost,
162
+ summary,
163
+ }));
164
+ };
165
+
166
+ events.on("story:start", onStoryStart);
167
+ events.on("story:complete", onStoryComplete);
168
+ events.on("story:escalate", onStoryEscalate);
169
+ events.on("stage:enter", onStageEnter);
170
+ events.on("run:complete", onRunComplete);
171
+
172
+ return () => {
173
+ stopTimer();
174
+ events.off("story:start", onStoryStart);
175
+ events.off("story:complete", onStoryComplete);
176
+ events.off("story:escalate", onStoryEscalate);
177
+ events.off("stage:enter", onStageEnter);
178
+ events.off("run:complete", onRunComplete);
179
+ };
180
+ }, [events, startTime]);
181
+
182
+ return state;
183
+ }
@@ -0,0 +1,194 @@
1
+ /**
2
+ * usePty hook — manages node-pty lifecycle for agent PTY sessions.
3
+ *
4
+ * Spawns, buffers output, handles resize, and cleanup for PTY processes.
5
+ */
6
+
7
+ import type * as pty from "node-pty";
8
+ import { useCallback, useEffect, useState } from "react";
9
+ import type { PtyHandle } from "../../agents/types";
10
+
11
+ /**
12
+ * Options for spawning PTY process.
13
+ */
14
+ export interface PtySpawnOptions {
15
+ /** Command to execute (e.g., "claude") */
16
+ command: string;
17
+ /** Command arguments */
18
+ args?: string[];
19
+ /** Working directory */
20
+ cwd?: string;
21
+ /** Environment variables */
22
+ env?: Record<string, string>;
23
+ /** Terminal columns (default: 80) */
24
+ cols?: number;
25
+ /** Terminal rows (default: 24) */
26
+ rows?: number;
27
+ }
28
+
29
+ /**
30
+ * PTY state managed by the hook.
31
+ */
32
+ export interface PtyState {
33
+ /** Output lines buffered from PTY */
34
+ outputLines: string[];
35
+ /** Whether PTY process is running */
36
+ isRunning: boolean;
37
+ /** Exit code (if process exited) */
38
+ exitCode?: number;
39
+ }
40
+
41
+ /**
42
+ * Maximum number of output lines to buffer.
43
+ *
44
+ * Prevents memory bloat from long-running PTY sessions.
45
+ */
46
+ const MAX_PTY_BUFFER_LINES = 500;
47
+
48
+ /**
49
+ * Maximum length per line in characters.
50
+ *
51
+ * Prevents memory exhaustion from single extremely long lines.
52
+ */
53
+ const MAX_LINE_LENGTH = 10_000;
54
+
55
+ /**
56
+ * Hook for managing PTY lifecycle.
57
+ *
58
+ * Spawns a PTY process, buffers output, and provides a handle for input/resize/kill.
59
+ *
60
+ * @param options - PTY spawn options (null to skip spawning)
61
+ * @returns PTY state and handle
62
+ *
63
+ * @example
64
+ * ```tsx
65
+ * const { outputLines, isRunning, handle } = usePty({
66
+ * command: "claude",
67
+ * args: ["--model", "claude-sonnet-4.5"],
68
+ * cwd: "/project",
69
+ * });
70
+ *
71
+ * // Write input to PTY
72
+ * handle?.write("y\n");
73
+ *
74
+ * // Render output
75
+ * <AgentPanel outputLines={outputLines} />
76
+ * ```
77
+ */
78
+ export function usePty(options: PtySpawnOptions | null): PtyState & { handle: PtyHandle | null } {
79
+ const [state, setState] = useState<PtyState>(() => ({
80
+ outputLines: [],
81
+ isRunning: false,
82
+ }));
83
+
84
+ const [handle, setHandle] = useState<PtyHandle | null>(null);
85
+ const [ptyProcess, setPtyProcess] = useState<pty.IPty | null>(null);
86
+
87
+ // Spawn PTY process
88
+ useEffect(() => {
89
+ if (!options) {
90
+ return;
91
+ }
92
+
93
+ // Lazy load node-pty (only when needed)
94
+ let nodePty: typeof pty;
95
+ try {
96
+ nodePty = require("node-pty");
97
+ } catch (error) {
98
+ console.error("[usePty] node-pty not available:", error);
99
+ return;
100
+ }
101
+
102
+ const ptyProc = nodePty.spawn(options.command, options.args || [], {
103
+ name: "xterm-256color",
104
+ cols: options.cols || 80,
105
+ rows: options.rows || 24,
106
+ cwd: options.cwd || process.cwd(),
107
+ env: {
108
+ ...process.env,
109
+ ...options.env,
110
+ },
111
+ });
112
+
113
+ setPtyProcess(ptyProc);
114
+ setState((prev) => ({ ...prev, isRunning: true }));
115
+
116
+ // Buffer output line-by-line
117
+ let currentLine = "";
118
+ ptyProc.onData((data) => {
119
+ const lines = (currentLine + data).split("\n");
120
+ currentLine = lines.pop() || "";
121
+
122
+ // Truncate incomplete line if too long
123
+ if (currentLine.length > MAX_LINE_LENGTH) {
124
+ currentLine = currentLine.slice(-MAX_LINE_LENGTH);
125
+ }
126
+
127
+ if (lines.length > 0) {
128
+ // Truncate each complete line
129
+ const truncatedLines = lines.map((line) =>
130
+ line.length > MAX_LINE_LENGTH ? `${line.slice(0, MAX_LINE_LENGTH)}…` : line,
131
+ );
132
+
133
+ setState((prev) => {
134
+ const newLines = [...prev.outputLines, ...truncatedLines];
135
+ // Keep only last N lines
136
+ const trimmed = newLines.length > MAX_PTY_BUFFER_LINES ? newLines.slice(-MAX_PTY_BUFFER_LINES) : newLines;
137
+ return { ...prev, outputLines: trimmed };
138
+ });
139
+ }
140
+ });
141
+
142
+ // Handle exit
143
+ ptyProc.onExit((event) => {
144
+ setState((prev) => ({
145
+ ...prev,
146
+ isRunning: false,
147
+ exitCode: event.exitCode,
148
+ }));
149
+ });
150
+
151
+ // Create handle
152
+ const ptyHandle: PtyHandle = {
153
+ write: (data: string) => ptyProc.write(data),
154
+ resize: (cols: number, rows: number) => ptyProc.resize(cols, rows),
155
+ kill: () => ptyProc.kill(),
156
+ pid: ptyProc.pid,
157
+ };
158
+
159
+ setHandle(ptyHandle);
160
+
161
+ // Cleanup on unmount
162
+ return () => {
163
+ if (ptyProc) {
164
+ ptyProc.kill();
165
+ }
166
+ };
167
+ }, [options]);
168
+
169
+ // Handle terminal resize
170
+ const handleResize = useCallback(
171
+ (cols: number, rows: number) => {
172
+ if (ptyProcess) {
173
+ ptyProcess.resize(cols, rows);
174
+ }
175
+ },
176
+ [ptyProcess],
177
+ );
178
+
179
+ useEffect(() => {
180
+ const onResize = () => {
181
+ const cols = process.stdout.columns ?? 80;
182
+ const rows = process.stdout.rows ?? 24;
183
+ handleResize(cols, rows);
184
+ };
185
+
186
+ process.stdout.on("resize", onResize);
187
+
188
+ return () => {
189
+ process.stdout.off("resize", onResize);
190
+ };
191
+ }, [handleResize]);
192
+
193
+ return { ...state, handle };
194
+ }
@@ -0,0 +1,38 @@
1
+ /**
2
+ * TUI entry point — renders the Ink-based terminal user interface.
3
+ */
4
+
5
+ import { render } from "ink";
6
+ import { App } from "./App";
7
+ import type { TuiProps } from "./types";
8
+
9
+ /**
10
+ * Render the TUI.
11
+ *
12
+ * Initializes Ink and renders the root App component.
13
+ *
14
+ * @param props - TUI props (feature, stories, events)
15
+ * @returns Ink instance (for cleanup/unmounting)
16
+ *
17
+ * @example
18
+ * ```ts
19
+ * const emitter = new PipelineEventEmitter();
20
+ *
21
+ * const instance = renderTui({
22
+ * feature: "auth-system",
23
+ * stories: initialStories,
24
+ * totalCost: 0,
25
+ * elapsedMs: 0,
26
+ * events: emitter,
27
+ * });
28
+ *
29
+ * // Later: cleanup
30
+ * instance.unmount();
31
+ * ```
32
+ */
33
+ export function renderTui(props: TuiProps) {
34
+ return render(<App {...props} />);
35
+ }
36
+
37
+ export type { TuiProps, StoryDisplayState, PanelFocus, PtySpawnOptions } from "./types";
38
+ export { PipelineEventEmitter } from "../pipeline/events";
@@ -0,0 +1,76 @@
1
+ /**
2
+ * TUI-specific types for terminal user interface components.
3
+ */
4
+
5
+ import type { PipelineEventEmitter } from "../pipeline/events";
6
+ import type { StoryRouting, UserStory } from "../prd/types";
7
+
8
+ /**
9
+ * Story display state for the TUI.
10
+ *
11
+ * Extends UserStory with runtime state for visual rendering.
12
+ */
13
+ export interface StoryDisplayState {
14
+ /** Story data from PRD */
15
+ story: UserStory;
16
+ /** Current status for display */
17
+ status: "pending" | "running" | "passed" | "failed" | "skipped" | "retrying" | "paused";
18
+ /** Routing result (if classified) */
19
+ routing?: StoryRouting;
20
+ /** Cost incurred for this story */
21
+ cost?: number;
22
+ }
23
+
24
+ /**
25
+ * Panel focus state.
26
+ *
27
+ * Determines which panel receives keyboard input.
28
+ */
29
+ export enum PanelFocus {
30
+ /** Stories panel is focused (default) */
31
+ Stories = "stories",
32
+ /** Agent panel is focused (input routed to PTY) */
33
+ Agent = "agent",
34
+ }
35
+
36
+ /**
37
+ * PTY spawn options for agent integration.
38
+ */
39
+ export interface PtySpawnOptions {
40
+ /** Command to execute (e.g., "claude") */
41
+ command: string;
42
+ /** Command arguments */
43
+ args?: string[];
44
+ /** Working directory */
45
+ cwd?: string;
46
+ /** Environment variables */
47
+ env?: Record<string, string>;
48
+ /** Terminal columns (default: 80) */
49
+ cols?: number;
50
+ /** Terminal rows (default: 24) */
51
+ rows?: number;
52
+ }
53
+
54
+ /**
55
+ * Props for the root TUI component.
56
+ */
57
+ export interface TuiProps {
58
+ /** Feature name */
59
+ feature: string;
60
+ /** All stories to display */
61
+ stories: StoryDisplayState[];
62
+ /** Current story being executed */
63
+ currentStory?: UserStory;
64
+ /** Current pipeline stage */
65
+ currentStage?: string;
66
+ /** Total cost accumulated */
67
+ totalCost: number;
68
+ /** Elapsed time in milliseconds */
69
+ elapsedMs: number;
70
+ /** Pipeline event emitter for live updates */
71
+ events: PipelineEventEmitter;
72
+ /** Path to queue file for writing commands (optional) */
73
+ queueFilePath?: string;
74
+ /** PTY spawn options for agent session (optional) */
75
+ ptyOptions?: PtySpawnOptions | null;
76
+ }
@@ -0,0 +1,83 @@
1
+ /**
2
+ * Git utility functions
3
+ */
4
+
5
+ import { spawn } from "bun";
6
+
7
+ /**
8
+ * Capture current git HEAD ref.
9
+ *
10
+ * Returns the current HEAD commit hash, or undefined if git is not available
11
+ * or the command fails (e.g., not in a git repo).
12
+ *
13
+ * @param workdir - Working directory to run git command in
14
+ * @returns Git HEAD ref or undefined on failure
15
+ *
16
+ * @example
17
+ * ```typescript
18
+ * const ref = await captureGitRef("/path/to/repo");
19
+ * if (ref) {
20
+ * console.log(`Current HEAD: ${ref}`);
21
+ * }
22
+ * ```
23
+ */
24
+ export async function captureGitRef(workdir: string): Promise<string | undefined> {
25
+ try {
26
+ const proc = spawn({
27
+ cmd: ["git", "rev-parse", "HEAD"],
28
+ cwd: workdir,
29
+ stdout: "pipe",
30
+ stderr: "pipe",
31
+ });
32
+
33
+ const exitCode = await proc.exited;
34
+ if (exitCode !== 0) return undefined;
35
+
36
+ const stdout = await new Response(proc.stdout).text();
37
+ return stdout.trim() || undefined;
38
+ } catch {
39
+ return undefined;
40
+ }
41
+ }
42
+
43
+ /**
44
+ * Check if a story ID appears in recent git commit messages.
45
+ *
46
+ * Searches the last 20 commits for commit messages containing the story ID.
47
+ * Used for state reconciliation: if a failed story has commits in git history,
48
+ * it means the story was partially completed and should be marked as passed.
49
+ *
50
+ * @param workdir - Working directory to run git command in
51
+ * @param storyId - Story ID to search for (e.g., "US-001")
52
+ * @param maxCommits - Maximum number of commits to search (default: 20)
53
+ * @returns true if story ID found in commit messages, false otherwise
54
+ *
55
+ * @example
56
+ * ```typescript
57
+ * const hasCommits = await hasCommitsForStory("/path/to/repo", "US-001");
58
+ * if (hasCommits) {
59
+ * console.log("Story US-001 has commits in git history");
60
+ * }
61
+ * ```
62
+ */
63
+ export async function hasCommitsForStory(workdir: string, storyId: string, maxCommits = 20): Promise<boolean> {
64
+ try {
65
+ const proc = spawn({
66
+ cmd: ["git", "log", `-${maxCommits}`, "--oneline", "--grep", storyId],
67
+ cwd: workdir,
68
+ stdout: "pipe",
69
+ stderr: "pipe",
70
+ });
71
+
72
+ const exitCode = await proc.exited;
73
+ if (exitCode !== 0) return false;
74
+
75
+ const stdout = await new Response(proc.stdout).text();
76
+ const commits = stdout.trim();
77
+
78
+ // If any commits found, return true
79
+ return commits.length > 0;
80
+ } catch {
81
+ return false;
82
+ }
83
+ }