@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,129 @@
1
+ /**
2
+ * Run Completion — Final Metrics and Status Updates
3
+ *
4
+ * Handles the final steps after sequential execution completes:
5
+ * - Save run metrics
6
+ * - Log completion summary with per-story metrics
7
+ * - Update final status
8
+ */
9
+
10
+ import { getSafeLogger } from "../../logger";
11
+ import type { StoryMetrics } from "../../metrics";
12
+ import { saveRunMetrics } from "../../metrics";
13
+ import { countStories, isComplete, isStalled } from "../../prd";
14
+ import type { PRD } from "../../prd";
15
+ import type { StatusWriter } from "../status-writer";
16
+
17
+ export interface RunCompletionOptions {
18
+ runId: string;
19
+ feature: string;
20
+ startedAt: string;
21
+ prd: PRD;
22
+ allStoryMetrics: StoryMetrics[];
23
+ totalCost: number;
24
+ storiesCompleted: number;
25
+ iterations: number;
26
+ startTime: number;
27
+ workdir: string;
28
+ statusWriter: StatusWriter;
29
+ }
30
+
31
+ export interface RunCompletionResult {
32
+ durationMs: number;
33
+ runCompletedAt: string;
34
+ finalCounts: {
35
+ total: number;
36
+ passed: number;
37
+ failed: number;
38
+ skipped: number;
39
+ pending: number;
40
+ };
41
+ }
42
+
43
+ /**
44
+ * Handle final run completion: save metrics, log summary, update status
45
+ */
46
+ export async function handleRunCompletion(options: RunCompletionOptions): Promise<RunCompletionResult> {
47
+ const logger = getSafeLogger();
48
+ const {
49
+ runId,
50
+ feature,
51
+ startedAt,
52
+ prd,
53
+ allStoryMetrics,
54
+ totalCost,
55
+ storiesCompleted,
56
+ iterations,
57
+ startTime,
58
+ workdir,
59
+ statusWriter,
60
+ } = options;
61
+
62
+ const durationMs = Date.now() - startTime;
63
+ const runCompletedAt = new Date().toISOString();
64
+
65
+ // Save run metrics
66
+ const runMetrics = {
67
+ runId,
68
+ feature,
69
+ startedAt,
70
+ completedAt: runCompletedAt,
71
+ totalCost,
72
+ totalStories: allStoryMetrics.length,
73
+ storiesCompleted,
74
+ storiesFailed: countStories(prd).failed,
75
+ totalDurationMs: durationMs,
76
+ stories: allStoryMetrics,
77
+ };
78
+
79
+ await saveRunMetrics(workdir, runMetrics);
80
+
81
+ // Log run completion
82
+ const finalCounts = countStories(prd);
83
+
84
+ // Prepare per-story metrics summary
85
+ const storyMetricsSummary = allStoryMetrics.map((sm) => ({
86
+ storyId: sm.storyId,
87
+ complexity: sm.complexity,
88
+ modelTier: sm.modelTier,
89
+ modelUsed: sm.modelUsed,
90
+ attempts: sm.attempts,
91
+ finalTier: sm.finalTier,
92
+ success: sm.success,
93
+ cost: sm.cost,
94
+ durationMs: sm.durationMs,
95
+ firstPassSuccess: sm.firstPassSuccess,
96
+ }));
97
+
98
+ logger?.info("run.complete", "Feature execution completed", {
99
+ runId,
100
+ feature,
101
+ success: isComplete(prd),
102
+ iterations,
103
+ totalStories: finalCounts.total,
104
+ storiesCompleted,
105
+ storiesFailed: finalCounts.failed,
106
+ storiesPending: finalCounts.pending,
107
+ totalCost,
108
+ durationMs,
109
+ storyMetrics: storyMetricsSummary,
110
+ });
111
+
112
+ // Update final status
113
+ statusWriter.setPrd(prd);
114
+ statusWriter.setCurrentStory(null);
115
+ statusWriter.setRunStatus(isComplete(prd) ? "completed" : isStalled(prd) ? "stalled" : "running");
116
+ await statusWriter.update(totalCost, iterations);
117
+
118
+ return {
119
+ durationMs,
120
+ runCompletedAt,
121
+ finalCounts: {
122
+ total: finalCounts.total,
123
+ passed: finalCounts.passed,
124
+ failed: finalCounts.failed,
125
+ skipped: finalCounts.skipped,
126
+ pending: finalCounts.pending,
127
+ },
128
+ };
129
+ }
@@ -0,0 +1,141 @@
1
+ /**
2
+ * Run Initialization
3
+ *
4
+ * Handles initialization tasks before the main execution loop starts:
5
+ * 1. State reconciliation (failed stories with commits)
6
+ * 2. Agent installation check
7
+ * 3. Story count validation
8
+ * 4. Initial PRD analysis
9
+ */
10
+
11
+ import chalk from "chalk";
12
+ import { getAgent } from "../../agents";
13
+ import type { NaxConfig } from "../../config";
14
+ import { AgentNotFoundError, AgentNotInstalledError, StoryLimitExceededError } from "../../errors";
15
+ import { getSafeLogger } from "../../logger";
16
+ import { countStories, loadPRD, markStoryPassed, savePRD } from "../../prd";
17
+ import type { PRD } from "../../prd/types";
18
+ import { hasCommitsForStory } from "../../utils/git";
19
+
20
+ export interface InitializationContext {
21
+ config: NaxConfig;
22
+ prdPath: string;
23
+ workdir: string;
24
+ dryRun: boolean;
25
+ }
26
+
27
+ export interface InitializationResult {
28
+ prd: PRD;
29
+ storyCounts: {
30
+ total: number;
31
+ pending: number;
32
+ passed: number;
33
+ failed: number;
34
+ skipped: number;
35
+ paused: number;
36
+ blocked: number;
37
+ };
38
+ }
39
+
40
+ /**
41
+ * Reconcile PRD state with git history
42
+ *
43
+ * Checks if failed stories have commits in git history and marks them as passed.
44
+ * This handles the case where TDD failed but agent already committed code.
45
+ */
46
+ async function reconcileState(prd: PRD, prdPath: string, workdir: string): Promise<PRD> {
47
+ const logger = getSafeLogger();
48
+ let reconciledCount = 0;
49
+ let modified = false;
50
+
51
+ for (const story of prd.userStories) {
52
+ if (story.status === "failed") {
53
+ const hasCommits = await hasCommitsForStory(workdir, story.id);
54
+ if (hasCommits) {
55
+ logger?.warn("reconciliation", "Failed story has commits in git history, marking as passed", {
56
+ storyId: story.id,
57
+ title: story.title,
58
+ });
59
+ markStoryPassed(prd, story.id);
60
+ reconciledCount++;
61
+ modified = true;
62
+ }
63
+ }
64
+ }
65
+
66
+ if (reconciledCount > 0) {
67
+ logger?.info("reconciliation", `Reconciled ${reconciledCount} failed stories from git history`);
68
+ await savePRD(prd, prdPath);
69
+ }
70
+
71
+ return prd;
72
+ }
73
+
74
+ /**
75
+ * Validate agent installation
76
+ */
77
+ async function checkAgentInstalled(config: NaxConfig, dryRun: boolean): Promise<void> {
78
+ if (dryRun) return;
79
+
80
+ const logger = getSafeLogger();
81
+ const agent = getAgent(config.autoMode.defaultAgent);
82
+
83
+ if (!agent) {
84
+ logger?.error("execution", "Agent not found", {
85
+ agent: config.autoMode.defaultAgent,
86
+ });
87
+ throw new AgentNotFoundError(config.autoMode.defaultAgent);
88
+ }
89
+
90
+ const installed = await agent.isInstalled();
91
+ if (!installed) {
92
+ logger?.error("execution", "Agent is not installed or not in PATH", {
93
+ agent: config.autoMode.defaultAgent,
94
+ binary: agent.binary,
95
+ });
96
+ logger?.error("execution", "Please install the agent and try again");
97
+ throw new AgentNotInstalledError(config.autoMode.defaultAgent, agent.binary);
98
+ }
99
+ }
100
+
101
+ /**
102
+ * Validate story count doesn't exceed limit
103
+ */
104
+ function validateStoryCount(counts: ReturnType<typeof countStories>, config: NaxConfig): void {
105
+ const logger = getSafeLogger();
106
+
107
+ if (counts.total > config.execution.maxStoriesPerFeature) {
108
+ logger?.error("execution", "Feature exceeds story limit", {
109
+ totalStories: counts.total,
110
+ limit: config.execution.maxStoriesPerFeature,
111
+ });
112
+ logger?.error("execution", "Split this feature into smaller features or increase maxStoriesPerFeature in config");
113
+ throw new StoryLimitExceededError(counts.total, config.execution.maxStoriesPerFeature);
114
+ }
115
+ }
116
+
117
+ /**
118
+ * Initialize execution: validate agent, reconcile state, check limits
119
+ */
120
+ export async function initializeRun(ctx: InitializationContext): Promise<InitializationResult> {
121
+ const logger = getSafeLogger();
122
+
123
+ // Check agent installation
124
+ await checkAgentInstalled(ctx.config, ctx.dryRun);
125
+
126
+ // Load and reconcile PRD
127
+ let prd = await loadPRD(ctx.prdPath);
128
+ prd = await reconcileState(prd, ctx.prdPath, ctx.workdir);
129
+
130
+ // Validate story counts
131
+ const counts = countStories(prd);
132
+ validateStoryCount(counts, ctx.config);
133
+
134
+ logger?.info("execution", "Run initialization complete", {
135
+ totalStories: counts.total,
136
+ doneStories: counts.passed,
137
+ pendingStories: counts.pending,
138
+ });
139
+
140
+ return { prd, storyCounts: counts };
141
+ }
@@ -0,0 +1,312 @@
1
+ /**
2
+ * Run Lifecycle — Setup & Teardown Logic
3
+ *
4
+ * Encapsulates the bookend operations for a nax execution run:
5
+ * - Setup: lock acquisition, PRD loading, plugin initialization, reporter setup
6
+ * - Teardown: metrics computation, final status write, lock release, plugin cleanup
7
+ */
8
+
9
+ import * as os from "node:os";
10
+ import path from "node:path";
11
+ import { getAgent } from "../../agents";
12
+ import type { NaxConfig } from "../../config";
13
+ import {
14
+ AgentNotFoundError,
15
+ AgentNotInstalledError,
16
+ LockAcquisitionError,
17
+ StoryLimitExceededError,
18
+ } from "../../errors";
19
+ import { type LoadedHooksConfig, fireHook } from "../../hooks";
20
+ import { getSafeLogger } from "../../logger";
21
+ import { type StoryMetrics, saveRunMetrics } from "../../metrics";
22
+ import { loadPlugins } from "../../plugins/loader";
23
+ import type { PluginRegistry } from "../../plugins/registry";
24
+ import type { PRD } from "../../prd";
25
+ import { countStories, isComplete, isStalled, loadPRD } from "../../prd";
26
+ import { clearCache as clearLlmCache, routeBatch as llmRouteBatch } from "../../routing/strategies/llm";
27
+ import { type StoryBatch, precomputeBatchPlan } from "../batching";
28
+ import { acquireLock, getAllReadyStories, hookCtx, releaseLock } from "../helpers";
29
+ import type { StatusWriter } from "../status-writer";
30
+
31
+ /** Setup result containing initialized state */
32
+ export interface SetupResult {
33
+ prd: PRD;
34
+ pluginRegistry: PluginRegistry;
35
+ batchPlan: StoryBatch[];
36
+ }
37
+
38
+ /** Teardown options */
39
+ export interface TeardownOptions {
40
+ runId: string;
41
+ feature: string;
42
+ startedAt: string;
43
+ prd: PRD;
44
+ allStoryMetrics: StoryMetrics[];
45
+ totalCost: number;
46
+ storiesCompleted: number;
47
+ startTime: number;
48
+ workdir: string;
49
+ pluginRegistry: PluginRegistry;
50
+ statusWriter: StatusWriter;
51
+ iterations: number;
52
+ }
53
+
54
+ /**
55
+ * Run lifecycle manager — handles setup and teardown for nax execution
56
+ */
57
+ export class RunLifecycle {
58
+ constructor(
59
+ private readonly prdPath: string,
60
+ private readonly workdir: string,
61
+ private readonly config: NaxConfig,
62
+ private readonly hooks: LoadedHooksConfig,
63
+ private readonly feature: string,
64
+ private readonly dryRun: boolean,
65
+ private readonly useBatch: boolean,
66
+ private readonly statusWriter: StatusWriter,
67
+ private readonly runId: string,
68
+ private readonly startedAt: string,
69
+ ) {}
70
+
71
+ /**
72
+ * Setup: Acquire lock, load PRD, initialize plugins, setup reporters
73
+ */
74
+ async setup(): Promise<SetupResult> {
75
+ const logger = getSafeLogger();
76
+
77
+ // Acquire lock to prevent concurrent execution
78
+ const lockAcquired = await acquireLock(this.workdir);
79
+ if (!lockAcquired) {
80
+ logger?.error("execution", "Another nax process is already running in this directory");
81
+ logger?.error("execution", "If you believe this is an error, remove nax.lock manually");
82
+ throw new LockAcquisitionError(this.workdir);
83
+ }
84
+
85
+ // Load plugins
86
+ const globalPluginsDir = path.join(os.homedir(), ".nax", "plugins");
87
+ const projectPluginsDir = path.join(this.workdir, "nax", "plugins");
88
+ const configPlugins = this.config.plugins || [];
89
+ const pluginRegistry = await loadPlugins(globalPluginsDir, projectPluginsDir, configPlugins, this.workdir);
90
+ const reporters = pluginRegistry.getReporters();
91
+
92
+ logger?.info("plugins", `Loaded ${pluginRegistry.plugins.length} plugins`, {
93
+ plugins: pluginRegistry.plugins.map((p) => ({ name: p.name, version: p.version, provides: p.provides })),
94
+ });
95
+
96
+ // Log run start
97
+ const routingMode = this.config.routing.llm?.mode ?? "hybrid";
98
+ logger?.info("run.start", `Starting feature: ${this.feature}`, {
99
+ runId: this.runId,
100
+ feature: this.feature,
101
+ workdir: this.workdir,
102
+ dryRun: this.dryRun,
103
+ useBatch: this.useBatch,
104
+ routingMode,
105
+ });
106
+
107
+ // Fire on-start hook
108
+ await fireHook(this.hooks, "on-start", hookCtx(this.feature), this.workdir);
109
+
110
+ // Check agent installation before starting
111
+ const agent = getAgent(this.config.autoMode.defaultAgent);
112
+ if (!agent) {
113
+ logger?.error("execution", "Agent not found", {
114
+ agent: this.config.autoMode.defaultAgent,
115
+ });
116
+ throw new AgentNotFoundError(this.config.autoMode.defaultAgent);
117
+ }
118
+
119
+ const installed = await agent.isInstalled();
120
+ if (!installed) {
121
+ logger?.error("execution", "Agent is not installed or not in PATH", {
122
+ agent: this.config.autoMode.defaultAgent,
123
+ binary: agent.binary,
124
+ });
125
+ logger?.error("execution", "Please install the agent and try again");
126
+ throw new AgentNotInstalledError(this.config.autoMode.defaultAgent, agent.binary);
127
+ }
128
+
129
+ // Load PRD
130
+ const prd = await loadPRD(this.prdPath);
131
+ const counts = countStories(prd);
132
+
133
+ // Status write point: run started
134
+ this.statusWriter.setPrd(prd);
135
+ this.statusWriter.setRunStatus("running");
136
+ this.statusWriter.setCurrentStory(null);
137
+ await this.statusWriter.update(0, 0);
138
+
139
+ // Update reporters with correct totalStories count
140
+ for (const reporter of reporters) {
141
+ if (reporter.onRunStart) {
142
+ try {
143
+ await reporter.onRunStart({
144
+ runId: this.runId,
145
+ feature: this.feature,
146
+ totalStories: counts.total,
147
+ startTime: this.startedAt,
148
+ });
149
+ } catch (error) {
150
+ logger?.warn("plugins", `Reporter '${reporter.name}' onRunStart failed`, { error });
151
+ }
152
+ }
153
+ }
154
+
155
+ // MEM-1: Validate story count doesn't exceed limit
156
+ if (counts.total > this.config.execution.maxStoriesPerFeature) {
157
+ logger?.error("execution", "Feature exceeds story limit", {
158
+ totalStories: counts.total,
159
+ limit: this.config.execution.maxStoriesPerFeature,
160
+ });
161
+ logger?.error("execution", "Split this feature into smaller features or increase maxStoriesPerFeature in config");
162
+ throw new StoryLimitExceededError(counts.total, this.config.execution.maxStoriesPerFeature);
163
+ }
164
+
165
+ logger?.info("execution", `Starting ${this.feature}`, {
166
+ totalStories: counts.total,
167
+ doneStories: counts.passed,
168
+ pendingStories: counts.pending,
169
+ batchingEnabled: this.useBatch,
170
+ });
171
+
172
+ // Clear LLM routing cache at start of new run
173
+ clearLlmCache();
174
+
175
+ // PERF-1: Precompute batch plan once from ready stories
176
+ let batchPlan: StoryBatch[] = [];
177
+ if (this.useBatch) {
178
+ const readyStories = getAllReadyStories(prd);
179
+ batchPlan = precomputeBatchPlan(readyStories, 4);
180
+
181
+ // Initial batch routing
182
+ const mode = this.config.routing.llm?.mode ?? "hybrid";
183
+ if (this.config.routing.strategy === "llm" && mode !== "per-story" && readyStories.length > 0) {
184
+ try {
185
+ logger?.debug("routing", "LLM batch routing: routing", { storyCount: readyStories.length, mode });
186
+ await llmRouteBatch(readyStories, { config: this.config });
187
+ logger?.debug("routing", "LLM batch routing complete", { label: "routing" });
188
+ } catch (err) {
189
+ logger?.warn("routing", "LLM batch routing failed, falling back to individual routing", {
190
+ error: (err as Error).message,
191
+ label: "routing",
192
+ });
193
+ }
194
+ }
195
+ }
196
+
197
+ return {
198
+ prd,
199
+ pluginRegistry,
200
+ batchPlan,
201
+ };
202
+ }
203
+
204
+ /**
205
+ * Teardown: Compute final metrics, write final status, release lock, cleanup plugins
206
+ */
207
+ async teardown(options: TeardownOptions): Promise<void> {
208
+ const logger = getSafeLogger();
209
+ const {
210
+ runId,
211
+ feature,
212
+ startedAt,
213
+ prd,
214
+ allStoryMetrics,
215
+ totalCost,
216
+ storiesCompleted,
217
+ startTime,
218
+ workdir,
219
+ pluginRegistry,
220
+ statusWriter,
221
+ iterations,
222
+ } = options;
223
+
224
+ const durationMs = Date.now() - startTime;
225
+
226
+ // Save run metrics
227
+ const runCompletedAt = new Date().toISOString();
228
+ const runMetrics = {
229
+ runId,
230
+ feature,
231
+ startedAt,
232
+ completedAt: runCompletedAt,
233
+ totalCost,
234
+ totalStories: allStoryMetrics.length,
235
+ storiesCompleted,
236
+ storiesFailed: countStories(prd).failed,
237
+ totalDurationMs: durationMs,
238
+ stories: allStoryMetrics,
239
+ };
240
+
241
+ await saveRunMetrics(workdir, runMetrics);
242
+
243
+ // Log run completion
244
+ const finalCounts = countStories(prd);
245
+
246
+ // Prepare per-story metrics summary
247
+ const storyMetricsSummary = allStoryMetrics.map((sm) => ({
248
+ storyId: sm.storyId,
249
+ complexity: sm.complexity,
250
+ modelTier: sm.modelTier,
251
+ modelUsed: sm.modelUsed,
252
+ attempts: sm.attempts,
253
+ finalTier: sm.finalTier,
254
+ success: sm.success,
255
+ cost: sm.cost,
256
+ durationMs: sm.durationMs,
257
+ firstPassSuccess: sm.firstPassSuccess,
258
+ }));
259
+
260
+ logger?.info("run.complete", "Feature execution completed", {
261
+ runId,
262
+ feature,
263
+ success: isComplete(prd),
264
+ iterations,
265
+ totalStories: finalCounts.total,
266
+ storiesCompleted,
267
+ storiesFailed: finalCounts.failed,
268
+ storiesPending: finalCounts.pending,
269
+ totalCost,
270
+ durationMs,
271
+ storyMetrics: storyMetricsSummary,
272
+ });
273
+
274
+ // Emit onRunEnd to reporters
275
+ const reporters = pluginRegistry.getReporters();
276
+ for (const reporter of reporters) {
277
+ if (reporter.onRunEnd) {
278
+ try {
279
+ await reporter.onRunEnd({
280
+ runId,
281
+ totalDurationMs: durationMs,
282
+ totalCost,
283
+ storySummary: {
284
+ completed: storiesCompleted,
285
+ failed: finalCounts.failed,
286
+ skipped: finalCounts.skipped,
287
+ paused: finalCounts.paused,
288
+ },
289
+ });
290
+ } catch (error) {
291
+ logger?.warn("plugins", `Reporter '${reporter.name}' onRunEnd failed`, { error });
292
+ }
293
+ }
294
+ }
295
+
296
+ // Status write point: run end
297
+ statusWriter.setPrd(prd);
298
+ statusWriter.setCurrentStory(null);
299
+ statusWriter.setRunStatus(isComplete(prd) ? "completed" : isStalled(prd) ? "stalled" : "running");
300
+ await statusWriter.update(totalCost, iterations);
301
+
302
+ // Teardown plugins
303
+ try {
304
+ await pluginRegistry.teardownAll();
305
+ } catch (error) {
306
+ logger?.warn("plugins", "Plugin teardown failed", { error });
307
+ }
308
+
309
+ // Always release lock, even if execution fails
310
+ await releaseLock(workdir);
311
+ }
312
+ }