@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,327 @@
1
+ /**
2
+ * Plugin System Types
3
+ *
4
+ * Defines the plugin interface and extension point types for the nax plugin system.
5
+ * Plugins export a NaxPlugin object with extension implementations.
6
+ */
7
+
8
+ import type { AgentAdapter } from "../agents/types";
9
+ import type { IPromptOptimizer } from "../optimizer/types";
10
+ import type { UserStory } from "../prd/types";
11
+ import type { RoutingStrategy } from "../routing/strategy";
12
+
13
+ /**
14
+ * Extension point types that plugins can provide.
15
+ */
16
+ export type PluginType = "optimizer" | "router" | "agent" | "reviewer" | "context-provider" | "reporter";
17
+
18
+ /**
19
+ * A nax plugin module.
20
+ *
21
+ * Plugins export a single NaxPlugin object (default or named export).
22
+ * Each plugin declares which extension points it provides and supplies
23
+ * the corresponding implementations.
24
+ *
25
+ * @example
26
+ * ```ts
27
+ * const myPlugin: NaxPlugin = {
28
+ * name: "my-security-scanner",
29
+ * version: "1.0.0",
30
+ * provides: ["reviewer"],
31
+ * async setup(config) {
32
+ * // Initialize plugin
33
+ * },
34
+ * async teardown() {
35
+ * // Cleanup
36
+ * },
37
+ * extensions: {
38
+ * reviewer: {
39
+ * name: "security-scan",
40
+ * description: "Scans for security vulnerabilities",
41
+ * async check(workdir, changedFiles) {
42
+ * // Perform check
43
+ * }
44
+ * }
45
+ * }
46
+ * };
47
+ * ```
48
+ */
49
+ export interface NaxPlugin {
50
+ /** Unique plugin name (e.g., "jira-context", "llmlingua-optimizer") */
51
+ name: string;
52
+
53
+ /** Plugin version (semver) */
54
+ version: string;
55
+
56
+ /** Which extension points this plugin provides */
57
+ provides: PluginType[];
58
+
59
+ /**
60
+ * Called once when plugin is loaded. Use for initialization,
61
+ * validating config, establishing connections, etc.
62
+ *
63
+ * @param config - Plugin-specific config from nax config.json
64
+ */
65
+ setup?(config: Record<string, unknown>): Promise<void>;
66
+
67
+ /**
68
+ * Called when the nax run ends (success or failure).
69
+ * Use for cleanup, closing connections, flushing buffers.
70
+ */
71
+ teardown?(): Promise<void>;
72
+
73
+ /**
74
+ * Extension implementations. Only the types listed in `provides`
75
+ * are required; others are ignored.
76
+ */
77
+ extensions: PluginExtensions;
78
+ }
79
+
80
+ /**
81
+ * Extension implementations provided by a plugin.
82
+ * Only extensions matching the plugin's `provides` array are required.
83
+ */
84
+ export interface PluginExtensions {
85
+ /** Custom prompt optimizer */
86
+ optimizer?: IPromptOptimizer;
87
+
88
+ /** Custom routing strategy (inserted into the strategy chain) */
89
+ router?: RoutingStrategy;
90
+
91
+ /** Custom agent adapter (e.g., Codex, Gemini, Aider) */
92
+ agent?: AgentAdapter;
93
+
94
+ /** Custom review check (runs alongside built-in typecheck/lint/test) */
95
+ reviewer?: IReviewPlugin;
96
+
97
+ /** Custom context provider (injects external context into prompts) */
98
+ contextProvider?: IContextProvider;
99
+
100
+ /** Custom reporter (receives run events for dashboards, CI, etc.) */
101
+ reporter?: IReporter;
102
+ }
103
+
104
+ // ============================================================================
105
+ // Optimizer Extension
106
+ // ============================================================================
107
+
108
+ /**
109
+ * Re-export optimizer types from the optimizer module.
110
+ * Plugin optimizers use the same interface as built-in optimizers.
111
+ */
112
+ export type {
113
+ PromptOptimizerInput,
114
+ PromptOptimizerResult,
115
+ } from "../optimizer/types";
116
+ export type { IPromptOptimizer } from "../optimizer/types";
117
+
118
+ // ============================================================================
119
+ // Review Extension
120
+ // ============================================================================
121
+
122
+ /**
123
+ * Result from a review check.
124
+ */
125
+ export interface ReviewCheckResult {
126
+ /** Whether the review check passed */
127
+ passed: boolean;
128
+ /** Human-readable output or error messages */
129
+ output: string;
130
+ /** Exit code from the check process (if applicable) */
131
+ exitCode?: number;
132
+ }
133
+
134
+ /**
135
+ * Review plugin interface.
136
+ *
137
+ * Review plugins run custom checks after agent execution (e.g., security scans,
138
+ * license checks, performance tests). Failures trigger retry/escalation.
139
+ *
140
+ * @example
141
+ * ```ts
142
+ * const reviewer: IReviewPlugin = {
143
+ * name: "security-scan",
144
+ * description: "Scans for security vulnerabilities",
145
+ * async check(workdir, changedFiles) {
146
+ * const result = await securityScanner.scan(workdir, changedFiles);
147
+ * return {
148
+ * passed: result.vulnerabilities.length === 0,
149
+ * output: result.report
150
+ * };
151
+ * }
152
+ * };
153
+ * ```
154
+ */
155
+ export interface IReviewPlugin {
156
+ /** Check name (e.g., "security-scan", "license-check") */
157
+ name: string;
158
+
159
+ /** Human-readable description */
160
+ description: string;
161
+
162
+ /**
163
+ * Run the review check against the working directory.
164
+ *
165
+ * @param workdir - Project root directory
166
+ * @param changedFiles - Files modified by the agent in this story
167
+ * @returns Review check result
168
+ */
169
+ check(workdir: string, changedFiles: string[]): Promise<ReviewCheckResult>;
170
+ }
171
+
172
+ // ============================================================================
173
+ // Context Provider Extension
174
+ // ============================================================================
175
+
176
+ /**
177
+ * Result from a context provider.
178
+ */
179
+ export interface ContextProviderResult {
180
+ /** Markdown content to inject */
181
+ content: string;
182
+ /** Token estimate for budget tracking */
183
+ estimatedTokens: number;
184
+ /** Section label in the prompt (e.g., "Jira Context") */
185
+ label: string;
186
+ }
187
+
188
+ /**
189
+ * Context provider interface.
190
+ *
191
+ * Context providers fetch external data (Jira tickets, Confluence docs,
192
+ * Linear issues, etc.) and inject it into agent prompts.
193
+ *
194
+ * @example
195
+ * ```ts
196
+ * const provider: IContextProvider = {
197
+ * name: "jira",
198
+ * async getContext(story) {
199
+ * const ticket = await jiraApi.getTicket(story.tags[0]);
200
+ * return {
201
+ * content: `## ${ticket.key}\n\n${ticket.description}`,
202
+ * estimatedTokens: estimateTokens(ticket.description),
203
+ * label: "Jira Context"
204
+ * };
205
+ * }
206
+ * };
207
+ * ```
208
+ */
209
+ export interface IContextProvider {
210
+ /** Provider name (e.g., "jira", "linear", "confluence") */
211
+ name: string;
212
+
213
+ /**
214
+ * Fetch external context relevant to a story.
215
+ *
216
+ * @param story - The user story being executed
217
+ * @returns Markdown content to inject into the agent prompt
218
+ */
219
+ getContext(story: UserStory): Promise<ContextProviderResult>;
220
+ }
221
+
222
+ // ============================================================================
223
+ // Reporter Extension
224
+ // ============================================================================
225
+
226
+ /**
227
+ * Event emitted when a run starts.
228
+ */
229
+ export interface RunStartEvent {
230
+ runId: string;
231
+ feature: string;
232
+ totalStories: number;
233
+ startTime: string;
234
+ }
235
+
236
+ /**
237
+ * Event emitted when a story completes.
238
+ */
239
+ export interface StoryCompleteEvent {
240
+ runId: string;
241
+ storyId: string;
242
+ status: "completed" | "failed" | "skipped" | "paused";
243
+ durationMs: number;
244
+ cost: number;
245
+ tier: string;
246
+ testStrategy: string;
247
+ }
248
+
249
+ /**
250
+ * Event emitted when a run ends.
251
+ */
252
+ export interface RunEndEvent {
253
+ runId: string;
254
+ totalDurationMs: number;
255
+ totalCost: number;
256
+ storySummary: {
257
+ completed: number;
258
+ failed: number;
259
+ skipped: number;
260
+ paused: number;
261
+ };
262
+ }
263
+
264
+ /**
265
+ * Reporter interface.
266
+ *
267
+ * Reporters receive run lifecycle events and can emit them to external
268
+ * systems (dashboards, Slack, CI, databases, etc.).
269
+ *
270
+ * All reporter methods are fire-and-forget — failures are logged but
271
+ * never block the pipeline.
272
+ *
273
+ * @example
274
+ * ```ts
275
+ * const reporter: IReporter = {
276
+ * name: "telegram",
277
+ * async onRunStart(event) {
278
+ * await telegram.send(`Started ${event.feature}`);
279
+ * },
280
+ * async onStoryComplete(event) {
281
+ * await telegram.send(`${event.storyId} ${event.status}`);
282
+ * },
283
+ * async onRunEnd(event) {
284
+ * await telegram.send(`Completed ${event.storySummary.completed} stories`);
285
+ * }
286
+ * };
287
+ * ```
288
+ */
289
+ export interface IReporter {
290
+ /** Reporter name */
291
+ name: string;
292
+
293
+ /** Called when a run starts */
294
+ onRunStart?(event: RunStartEvent): Promise<void>;
295
+
296
+ /** Called when a story completes (success or failure) */
297
+ onStoryComplete?(event: StoryCompleteEvent): Promise<void>;
298
+
299
+ /** Called when a run ends */
300
+ onRunEnd?(event: RunEndEvent): Promise<void>;
301
+ }
302
+
303
+ // ============================================================================
304
+ // Plugin Config
305
+ // ============================================================================
306
+
307
+ /**
308
+ * Plugin configuration entry from nax config.json.
309
+ *
310
+ * @example
311
+ * ```json
312
+ * {
313
+ * "plugins": [
314
+ * {
315
+ * "module": "./nax/plugins/my-plugin",
316
+ * "config": { "apiKey": "secret" }
317
+ * }
318
+ * ]
319
+ * }
320
+ * ```
321
+ */
322
+ export interface PluginConfigEntry {
323
+ /** Module path or npm package name */
324
+ module: string;
325
+ /** Plugin-specific configuration */
326
+ config?: Record<string, unknown>;
327
+ }
@@ -0,0 +1,352 @@
1
+ /**
2
+ * Plugin Validator
3
+ *
4
+ * Runtime type checking for plugin modules.
5
+ * Validates plugin shape and ensures all required extensions are present.
6
+ */
7
+
8
+ import { getLogger } from "../logger";
9
+ import type { NaxPlugin, PluginType } from "./types";
10
+
11
+ /**
12
+ * Safely get logger instance, returns null if not initialized
13
+ */
14
+ function getSafeLogger() {
15
+ try {
16
+ return getLogger();
17
+ } catch {
18
+ return null;
19
+ }
20
+ }
21
+
22
+ const VALID_PLUGIN_TYPES: readonly PluginType[] = [
23
+ "optimizer",
24
+ "router",
25
+ "agent",
26
+ "reviewer",
27
+ "context-provider",
28
+ "reporter",
29
+ ] as const;
30
+
31
+ /**
32
+ * Validate a plugin module at runtime.
33
+ *
34
+ * Returns the plugin if valid, null if invalid (with warning logged).
35
+ *
36
+ * @param module - The module to validate (can be any type)
37
+ * @returns The validated plugin or null
38
+ */
39
+ export function validatePlugin(module: unknown): NaxPlugin | null {
40
+ // Must be an object
41
+ if (typeof module !== "object" || module === null) {
42
+ getSafeLogger()?.warn("plugins", "Plugin validation failed: module is not an object");
43
+ return null;
44
+ }
45
+
46
+ const plugin = module as Record<string, unknown>;
47
+
48
+ // Validate name
49
+ if (typeof plugin.name !== "string") {
50
+ getSafeLogger()?.warn("plugins", "Plugin validation failed: missing or invalid 'name' (must be string)");
51
+ return null;
52
+ }
53
+
54
+ // Validate version
55
+ if (typeof plugin.version !== "string") {
56
+ getSafeLogger()?.warn(
57
+ "plugins",
58
+ `Plugin '${plugin.name}' validation failed: missing or invalid 'version' (must be string)`,
59
+ );
60
+ return null;
61
+ }
62
+
63
+ // Validate provides
64
+ if (!Array.isArray(plugin.provides)) {
65
+ getSafeLogger()?.warn("plugins", `Plugin '${plugin.name}' validation failed: 'provides' must be an array`);
66
+ return null;
67
+ }
68
+
69
+ if (plugin.provides.length === 0) {
70
+ getSafeLogger()?.warn("plugins", `Plugin '${plugin.name}' validation failed: 'provides' must not be empty`);
71
+ return null;
72
+ }
73
+
74
+ for (const type of plugin.provides) {
75
+ if (!VALID_PLUGIN_TYPES.includes(type as PluginType)) {
76
+ getSafeLogger()?.warn(
77
+ "plugins",
78
+ `Plugin '${plugin.name}' validation failed: invalid plugin type '${type}' in 'provides'`,
79
+ );
80
+ return null;
81
+ }
82
+ }
83
+
84
+ // Validate setup (optional)
85
+ if ("setup" in plugin && typeof plugin.setup !== "function") {
86
+ getSafeLogger()?.warn("plugins", `Plugin '${plugin.name}' validation failed: 'setup' must be a function`);
87
+ return null;
88
+ }
89
+
90
+ // Validate teardown (optional)
91
+ if ("teardown" in plugin && typeof plugin.teardown !== "function") {
92
+ getSafeLogger()?.warn("plugins", `Plugin '${plugin.name}' validation failed: 'teardown' must be a function`);
93
+ return null;
94
+ }
95
+
96
+ // Validate extensions
97
+ if (typeof plugin.extensions !== "object" || plugin.extensions === null) {
98
+ getSafeLogger()?.warn("plugins", `Plugin '${plugin.name}' validation failed: 'extensions' must be an object`);
99
+ return null;
100
+ }
101
+
102
+ const extensions = plugin.extensions as Record<string, unknown>;
103
+
104
+ // Validate each extension type in provides
105
+ for (const type of plugin.provides) {
106
+ const isValid = validateExtension(plugin.name as string, type as PluginType, extensions);
107
+ if (!isValid) {
108
+ return null;
109
+ }
110
+ }
111
+
112
+ return plugin as unknown as NaxPlugin;
113
+ }
114
+
115
+ /**
116
+ * Validate a specific extension type.
117
+ *
118
+ * @param pluginName - Plugin name (for error messages)
119
+ * @param type - Extension type to validate
120
+ * @param extensions - Extensions object
121
+ * @returns Whether the extension is valid
122
+ */
123
+ function validateExtension(pluginName: string, type: PluginType, extensions: Record<string, unknown>): boolean {
124
+ switch (type) {
125
+ case "optimizer":
126
+ return validateOptimizer(pluginName, extensions.optimizer);
127
+ case "router":
128
+ return validateRouter(pluginName, extensions.router);
129
+ case "agent":
130
+ return validateAgent(pluginName, extensions.agent);
131
+ case "reviewer":
132
+ return validateReviewer(pluginName, extensions.reviewer);
133
+ case "context-provider":
134
+ return validateContextProvider(pluginName, extensions.contextProvider);
135
+ case "reporter":
136
+ return validateReporter(pluginName, extensions.reporter);
137
+ default:
138
+ getSafeLogger()?.warn("plugins", `Plugin '${pluginName}' validation failed: unknown extension type '${type}'`);
139
+ return false;
140
+ }
141
+ }
142
+
143
+ /**
144
+ * Validate optimizer extension.
145
+ */
146
+ function validateOptimizer(pluginName: string, optimizer: unknown): boolean {
147
+ if (typeof optimizer !== "object" || optimizer === null) {
148
+ getSafeLogger()?.warn("plugins", `Plugin '${pluginName}' validation failed: optimizer extension must be an object`);
149
+ return false;
150
+ }
151
+
152
+ const opt = optimizer as Record<string, unknown>;
153
+
154
+ if (typeof opt.name !== "string") {
155
+ getSafeLogger()?.warn("plugins", `Plugin '${pluginName}' validation failed: optimizer.name must be a string`);
156
+ return false;
157
+ }
158
+
159
+ if (typeof opt.optimize !== "function") {
160
+ getSafeLogger()?.warn("plugins", `Plugin '${pluginName}' validation failed: optimizer.optimize must be a function`);
161
+ return false;
162
+ }
163
+
164
+ return true;
165
+ }
166
+
167
+ /**
168
+ * Validate router extension.
169
+ */
170
+ function validateRouter(pluginName: string, router: unknown): boolean {
171
+ if (typeof router !== "object" || router === null) {
172
+ getSafeLogger()?.warn("plugins", `Plugin '${pluginName}' validation failed: router extension must be an object`);
173
+ return false;
174
+ }
175
+
176
+ const rtr = router as Record<string, unknown>;
177
+
178
+ if (typeof rtr.name !== "string") {
179
+ getSafeLogger()?.warn("plugins", `Plugin '${pluginName}' validation failed: router.name must be a string`);
180
+ return false;
181
+ }
182
+
183
+ if (typeof rtr.route !== "function") {
184
+ getSafeLogger()?.warn("plugins", `Plugin '${pluginName}' validation failed: router.route must be a function`);
185
+ return false;
186
+ }
187
+
188
+ return true;
189
+ }
190
+
191
+ /**
192
+ * Validate agent extension.
193
+ */
194
+ function validateAgent(pluginName: string, agent: unknown): boolean {
195
+ if (typeof agent !== "object" || agent === null) {
196
+ getSafeLogger()?.warn("plugins", `Plugin '${pluginName}' validation failed: agent extension must be an object`);
197
+ return false;
198
+ }
199
+
200
+ const agt = agent as Record<string, unknown>;
201
+
202
+ const requiredFields = [
203
+ { name: "name", type: "string" },
204
+ { name: "displayName", type: "string" },
205
+ { name: "binary", type: "string" },
206
+ { name: "capabilities", type: "object" },
207
+ { name: "isInstalled", type: "function" },
208
+ { name: "run", type: "function" },
209
+ { name: "buildCommand", type: "function" },
210
+ { name: "plan", type: "function" },
211
+ { name: "decompose", type: "function" },
212
+ ];
213
+
214
+ for (const field of requiredFields) {
215
+ if (field.type === "object") {
216
+ if (typeof agt[field.name] !== "object" || agt[field.name] === null) {
217
+ getSafeLogger()?.warn(
218
+ "plugins",
219
+ `Plugin '${pluginName}' validation failed: agent.${field.name} must be an object`,
220
+ );
221
+ return false;
222
+ }
223
+ } else {
224
+ // Validate field.type is a valid typeof result before comparison
225
+ const expectedType = field.type as string;
226
+
227
+ // Use explicit type checks instead of dynamic typeof comparison
228
+ let isValid = false;
229
+ if (expectedType === "string") {
230
+ isValid = typeof agt[field.name] === "string";
231
+ } else if (expectedType === "number") {
232
+ isValid = typeof agt[field.name] === "number";
233
+ } else if (expectedType === "boolean") {
234
+ isValid = typeof agt[field.name] === "boolean";
235
+ } else if (expectedType === "symbol") {
236
+ isValid = typeof agt[field.name] === "symbol";
237
+ } else if (expectedType === "undefined") {
238
+ isValid = typeof agt[field.name] === "undefined";
239
+ } else if (expectedType === "function") {
240
+ isValid = typeof agt[field.name] === "function";
241
+ } else if (expectedType === "bigint") {
242
+ isValid = typeof agt[field.name] === "bigint";
243
+ } else {
244
+ getSafeLogger()?.warn(
245
+ "plugins",
246
+ `Plugin '${pluginName}' validation failed: invalid type constraint '${expectedType}'`,
247
+ );
248
+ return false;
249
+ }
250
+
251
+ if (!isValid) {
252
+ getSafeLogger()?.warn(
253
+ "plugins",
254
+ `Plugin '${pluginName}' validation failed: agent.${field.name} must be a ${expectedType}`,
255
+ );
256
+ return false;
257
+ }
258
+ }
259
+ }
260
+
261
+ return true;
262
+ }
263
+
264
+ /**
265
+ * Validate reviewer extension.
266
+ */
267
+ function validateReviewer(pluginName: string, reviewer: unknown): boolean {
268
+ if (typeof reviewer !== "object" || reviewer === null) {
269
+ getSafeLogger()?.warn("plugins", `Plugin '${pluginName}' validation failed: reviewer extension must be an object`);
270
+ return false;
271
+ }
272
+
273
+ const rev = reviewer as Record<string, unknown>;
274
+
275
+ if (typeof rev.name !== "string") {
276
+ getSafeLogger()?.warn("plugins", `Plugin '${pluginName}' validation failed: reviewer.name must be a string`);
277
+ return false;
278
+ }
279
+
280
+ if (typeof rev.description !== "string") {
281
+ getSafeLogger()?.warn("plugins", `Plugin '${pluginName}' validation failed: reviewer.description must be a string`);
282
+ return false;
283
+ }
284
+
285
+ if (typeof rev.check !== "function") {
286
+ getSafeLogger()?.warn("plugins", `Plugin '${pluginName}' validation failed: reviewer.check must be a function`);
287
+ return false;
288
+ }
289
+
290
+ return true;
291
+ }
292
+
293
+ /**
294
+ * Validate context-provider extension.
295
+ */
296
+ function validateContextProvider(pluginName: string, provider: unknown): boolean {
297
+ if (typeof provider !== "object" || provider === null) {
298
+ getSafeLogger()?.warn(
299
+ "plugins",
300
+ `Plugin '${pluginName}' validation failed: contextProvider extension must be an object`,
301
+ );
302
+ return false;
303
+ }
304
+
305
+ const prov = provider as Record<string, unknown>;
306
+
307
+ if (typeof prov.name !== "string") {
308
+ getSafeLogger()?.warn("plugins", `Plugin '${pluginName}' validation failed: contextProvider.name must be a string`);
309
+ return false;
310
+ }
311
+
312
+ if (typeof prov.getContext !== "function") {
313
+ getSafeLogger()?.warn(
314
+ "plugins",
315
+ `Plugin '${pluginName}' validation failed: contextProvider.getContext must be a function`,
316
+ );
317
+ return false;
318
+ }
319
+
320
+ return true;
321
+ }
322
+
323
+ /**
324
+ * Validate reporter extension.
325
+ */
326
+ function validateReporter(pluginName: string, reporter: unknown): boolean {
327
+ if (typeof reporter !== "object" || reporter === null) {
328
+ getSafeLogger()?.warn("plugins", `Plugin '${pluginName}' validation failed: reporter extension must be an object`);
329
+ return false;
330
+ }
331
+
332
+ const rep = reporter as Record<string, unknown>;
333
+
334
+ if (typeof rep.name !== "string") {
335
+ getSafeLogger()?.warn("plugins", `Plugin '${pluginName}' validation failed: reporter.name must be a string`);
336
+ return false;
337
+ }
338
+
339
+ // At least one event handler is optional, but all must be functions if present
340
+ const eventHandlers = ["onRunStart", "onStoryComplete", "onRunEnd"];
341
+ for (const handler of eventHandlers) {
342
+ if (handler in rep && typeof rep[handler] !== "function") {
343
+ getSafeLogger()?.warn(
344
+ "plugins",
345
+ `Plugin '${pluginName}' validation failed: reporter.${handler} must be a function`,
346
+ );
347
+ return false;
348
+ }
349
+ }
350
+
351
+ return true;
352
+ }