@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,172 @@
1
+ /**
2
+ * End-to-end plugin config integration test
3
+ *
4
+ * Demonstrates US-007 working in a realistic scenario with:
5
+ * - A project with nax/config.json
6
+ * - Relative plugin paths
7
+ * - Plugin-specific config passed to setup()
8
+ */
9
+
10
+ import { afterEach, beforeEach, describe, expect, test } from "bun:test";
11
+ import * as fs from "node:fs/promises";
12
+ import * as os from "node:os";
13
+ import * as path from "node:path";
14
+ import { loadPlugins } from "../../../src/plugins/loader";
15
+
16
+ async function createTempDir(): Promise<string> {
17
+ const tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), "nax-integration-test-"));
18
+ return tmpDir;
19
+ }
20
+
21
+ async function cleanupTempDir(dir: string): Promise<void> {
22
+ try {
23
+ await fs.rm(dir, { recursive: true, force: true });
24
+ } catch {
25
+ // Ignore
26
+ }
27
+ }
28
+
29
+ describe("Plugin config integration (US-007)", () => {
30
+ let projectRoot: string;
31
+
32
+ beforeEach(async () => {
33
+ projectRoot = await createTempDir();
34
+ });
35
+
36
+ afterEach(async () => {
37
+ await cleanupTempDir(projectRoot);
38
+ });
39
+
40
+ test("realistic scenario: project with relative plugin paths in config", async () => {
41
+ // Setup project structure:
42
+ // project/
43
+ // nax/
44
+ // (no plugins/ directory to avoid auto-discovery)
45
+ // custom-plugins/
46
+ // external-plugin.ts
47
+ // local-plugin.ts
48
+
49
+ const naxDir = path.join(projectRoot, "nax");
50
+ const customPluginsDir = path.join(projectRoot, "custom-plugins");
51
+
52
+ await fs.mkdir(naxDir, { recursive: true });
53
+ await fs.mkdir(customPluginsDir, { recursive: true });
54
+
55
+ // Create a config file to track which plugins were initialized
56
+ const configTracker = path.join(projectRoot, "plugin-init-tracker.json");
57
+
58
+ // Create local plugin (in custom-plugins/)
59
+ const localPluginCode = `
60
+ export default {
61
+ name: "local-plugin",
62
+ version: "1.0.0",
63
+ provides: ["optimizer"],
64
+ async setup(config) {
65
+ const fs = await import("node:fs/promises");
66
+ const path = await import("node:path");
67
+ const trackerPath = "${configTracker}";
68
+
69
+ let tracker = {};
70
+ try {
71
+ tracker = JSON.parse(await fs.readFile(trackerPath, "utf-8"));
72
+ } catch {}
73
+
74
+ tracker["local-plugin"] = config;
75
+ await fs.writeFile(trackerPath, JSON.stringify(tracker, null, 2), "utf-8");
76
+ },
77
+ extensions: {
78
+ optimizer: {
79
+ name: "local",
80
+ async optimize(input) {
81
+ return {
82
+ optimizedPrompt: input.prompt,
83
+ estimatedTokens: input.estimatedTokens,
84
+ tokensSaved: 0,
85
+ appliedStrategies: []
86
+ };
87
+ }
88
+ }
89
+ }
90
+ };
91
+ `;
92
+ await fs.writeFile(path.join(customPluginsDir, "local-plugin.ts"), localPluginCode, "utf-8");
93
+
94
+ // Create external plugin (in custom-plugins/)
95
+ const externalPluginCode = `
96
+ export default {
97
+ name: "external-plugin",
98
+ version: "2.0.0",
99
+ provides: ["optimizer"],
100
+ async setup(config) {
101
+ const fs = await import("node:fs/promises");
102
+ const path = await import("node:path");
103
+ const trackerPath = "${configTracker}";
104
+
105
+ let tracker = {};
106
+ try {
107
+ tracker = JSON.parse(await fs.readFile(trackerPath, "utf-8"));
108
+ } catch {}
109
+
110
+ tracker["external-plugin"] = config;
111
+ await fs.writeFile(trackerPath, JSON.stringify(tracker, null, 2), "utf-8");
112
+ },
113
+ extensions: {
114
+ optimizer: {
115
+ name: "external",
116
+ async optimize(input) {
117
+ return {
118
+ optimizedPrompt: input.prompt,
119
+ estimatedTokens: input.estimatedTokens,
120
+ tokensSaved: 0,
121
+ appliedStrategies: []
122
+ };
123
+ }
124
+ }
125
+ }
126
+ };
127
+ `;
128
+ await fs.writeFile(path.join(customPluginsDir, "external-plugin.ts"), externalPluginCode, "utf-8");
129
+
130
+ // Simulate config.json plugins array
131
+ const configPlugins = [
132
+ {
133
+ // Relative path to custom plugins directory
134
+ module: "./custom-plugins/external-plugin.ts",
135
+ config: {
136
+ apiKey: "test-key-123",
137
+ timeout: 5000,
138
+ },
139
+ },
140
+ {
141
+ // Another relative path to custom plugins directory
142
+ module: "./custom-plugins/local-plugin.ts",
143
+ config: {
144
+ enabled: true,
145
+ level: "debug",
146
+ },
147
+ },
148
+ ];
149
+
150
+ // Load plugins as the runner would
151
+ const globalPluginsDir = path.join(projectRoot, ".nax", "plugins");
152
+ const projectPluginsDir = path.join(projectRoot, "nax", "plugins");
153
+
154
+ const registry = await loadPlugins(globalPluginsDir, projectPluginsDir, configPlugins, projectRoot);
155
+
156
+ // Verify both plugins loaded
157
+ expect(registry.plugins).toHaveLength(2);
158
+ const pluginNames = registry.plugins.map((p) => p.name).sort();
159
+ expect(pluginNames).toEqual(["external-plugin", "local-plugin"]);
160
+
161
+ // Verify configs were passed to setup()
162
+ const tracker = JSON.parse(await fs.readFile(configTracker, "utf-8"));
163
+ expect(tracker["external-plugin"]).toEqual({
164
+ apiKey: "test-key-123",
165
+ timeout: 5000,
166
+ });
167
+ expect(tracker["local-plugin"]).toEqual({
168
+ enabled: true,
169
+ level: "debug",
170
+ });
171
+ });
172
+ });
@@ -0,0 +1,522 @@
1
+ /**
2
+ * Plugin config path resolution tests
3
+ *
4
+ * Validates US-007 acceptance criteria:
5
+ * 1. plugins[] from config.json are passed to loadPlugins() as configPlugins parameter
6
+ * 2. Relative module paths in plugins[].module are resolved relative to project root
7
+ * 3. Absolute module paths and npm package names work as-is
8
+ * 4. If a plugin module cannot be found, a clear error message is logged with the path tried
9
+ * 5. Plugin-specific config (plugins[].config) is passed to the plugin's setup() function
10
+ */
11
+
12
+ import { afterEach, beforeEach, describe, expect, test } from "bun:test";
13
+ import * as fs from "node:fs/promises";
14
+ import * as os from "node:os";
15
+ import * as path from "node:path";
16
+ import { loadPlugins, _setPluginErrorSink, _resetPluginErrorSink } from "../../../src/plugins/loader";
17
+ import type { PluginConfigEntry } from "../../../src/plugins/types";
18
+ import type { NaxPlugin } from "../../../src/plugins/types";
19
+
20
+ // Test fixture helpers
21
+ async function createTempDir(): Promise<string> {
22
+ const tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), "nax-plugin-config-test-"));
23
+ return tmpDir;
24
+ }
25
+
26
+ async function cleanupTempDir(dir: string): Promise<void> {
27
+ try {
28
+ await fs.rm(dir, { recursive: true, force: true });
29
+ } catch {
30
+ // Ignore cleanup errors
31
+ }
32
+ }
33
+
34
+ async function writePluginFile(filePath: string, plugin: NaxPlugin, setupFn?: string): Promise<void> {
35
+ let extensionsCode = "";
36
+
37
+ if (plugin.extensions.optimizer) {
38
+ extensionsCode += `
39
+ optimizer: {
40
+ name: "${plugin.extensions.optimizer.name}",
41
+ async optimize(input) {
42
+ return {
43
+ optimizedPrompt: input.prompt,
44
+ estimatedTokens: input.estimatedTokens,
45
+ tokensSaved: 0,
46
+ appliedStrategies: []
47
+ };
48
+ }
49
+ },`;
50
+ }
51
+
52
+ const setupCode = setupFn || (plugin.setup ? "async setup(config) { }," : "");
53
+ const teardownCode = plugin.teardown ? "async teardown() { }," : "";
54
+
55
+ const pluginCode = `
56
+ export default {
57
+ name: "${plugin.name}",
58
+ version: "${plugin.version}",
59
+ provides: ${JSON.stringify(plugin.provides)},
60
+ ${setupCode}
61
+ ${teardownCode}
62
+ extensions: {${extensionsCode}
63
+ }
64
+ };
65
+ `;
66
+ await fs.writeFile(filePath, pluginCode, "utf-8");
67
+ }
68
+
69
+ describe("Plugin config path resolution (US-007)", () => {
70
+ let tempDir: string;
71
+
72
+ beforeEach(async () => {
73
+ tempDir = await createTempDir();
74
+ });
75
+
76
+ afterEach(async () => {
77
+ await cleanupTempDir(tempDir);
78
+ });
79
+
80
+ describe("AC1: plugins[] from config.json passed to loadPlugins", () => {
81
+ test("loads plugins from config array", async () => {
82
+ const pluginDir = path.join(tempDir, "plugins");
83
+ await fs.mkdir(pluginDir, { recursive: true });
84
+
85
+ const plugin: NaxPlugin = {
86
+ name: "config-plugin",
87
+ version: "1.0.0",
88
+ provides: ["optimizer"],
89
+ extensions: {
90
+ optimizer: {
91
+ name: "test",
92
+ async optimize(input) {
93
+ return {
94
+ optimizedPrompt: input.prompt,
95
+ estimatedTokens: input.estimatedTokens,
96
+ tokensSaved: 0,
97
+ appliedStrategies: [],
98
+ };
99
+ },
100
+ },
101
+ },
102
+ };
103
+
104
+ await writePluginFile(path.join(pluginDir, "plugin.ts"), plugin);
105
+
106
+ const configPlugins: PluginConfigEntry[] = [
107
+ {
108
+ module: path.join(pluginDir, "plugin.ts"),
109
+ config: {},
110
+ },
111
+ ];
112
+
113
+ const registry = await loadPlugins(
114
+ path.join(tempDir, "nonexistent-global"),
115
+ path.join(tempDir, "nonexistent-project"),
116
+ configPlugins,
117
+ tempDir,
118
+ );
119
+
120
+ expect(registry.plugins).toHaveLength(1);
121
+ expect(registry.plugins[0].name).toBe("config-plugin");
122
+ });
123
+
124
+ test("loads multiple plugins from config array", async () => {
125
+ const plugin1Dir = path.join(tempDir, "plugin1");
126
+ const plugin2Dir = path.join(tempDir, "plugin2");
127
+ await fs.mkdir(plugin1Dir, { recursive: true });
128
+ await fs.mkdir(plugin2Dir, { recursive: true });
129
+
130
+ const plugin1: NaxPlugin = {
131
+ name: "plugin-one",
132
+ version: "1.0.0",
133
+ provides: ["optimizer"],
134
+ extensions: {
135
+ optimizer: {
136
+ name: "test",
137
+ async optimize(input) {
138
+ return {
139
+ optimizedPrompt: input.prompt,
140
+ estimatedTokens: input.estimatedTokens,
141
+ tokensSaved: 0,
142
+ appliedStrategies: [],
143
+ };
144
+ },
145
+ },
146
+ },
147
+ };
148
+
149
+ const plugin2: NaxPlugin = {
150
+ name: "plugin-two",
151
+ version: "2.0.0",
152
+ provides: ["optimizer"],
153
+ extensions: {
154
+ optimizer: {
155
+ name: "test",
156
+ async optimize(input) {
157
+ return {
158
+ optimizedPrompt: input.prompt,
159
+ estimatedTokens: input.estimatedTokens,
160
+ tokensSaved: 0,
161
+ appliedStrategies: [],
162
+ };
163
+ },
164
+ },
165
+ },
166
+ };
167
+
168
+ await writePluginFile(path.join(plugin1Dir, "index.ts"), plugin1);
169
+ await writePluginFile(path.join(plugin2Dir, "index.ts"), plugin2);
170
+
171
+ const configPlugins: PluginConfigEntry[] = [
172
+ { module: path.join(plugin1Dir, "index.ts"), config: {} },
173
+ { module: path.join(plugin2Dir, "index.ts"), config: {} },
174
+ ];
175
+
176
+ const registry = await loadPlugins(
177
+ path.join(tempDir, "nonexistent-global"),
178
+ path.join(tempDir, "nonexistent-project"),
179
+ configPlugins,
180
+ tempDir,
181
+ );
182
+
183
+ expect(registry.plugins).toHaveLength(2);
184
+ expect(registry.plugins[0].name).toBe("plugin-one");
185
+ expect(registry.plugins[1].name).toBe("plugin-two");
186
+ });
187
+ });
188
+
189
+ describe("AC2: Relative module paths resolved relative to project root", () => {
190
+ test("resolves ./relative/path from project root", async () => {
191
+ const pluginDir = path.join(tempDir, "custom-plugins");
192
+ await fs.mkdir(pluginDir, { recursive: true });
193
+
194
+ const plugin: NaxPlugin = {
195
+ name: "relative-plugin",
196
+ version: "1.0.0",
197
+ provides: ["optimizer"],
198
+ extensions: {
199
+ optimizer: {
200
+ name: "test",
201
+ async optimize(input) {
202
+ return {
203
+ optimizedPrompt: input.prompt,
204
+ estimatedTokens: input.estimatedTokens,
205
+ tokensSaved: 0,
206
+ appliedStrategies: [],
207
+ };
208
+ },
209
+ },
210
+ },
211
+ };
212
+
213
+ await writePluginFile(path.join(pluginDir, "my-plugin.ts"), plugin);
214
+
215
+ const configPlugins: PluginConfigEntry[] = [
216
+ {
217
+ module: "./custom-plugins/my-plugin.ts",
218
+ config: {},
219
+ },
220
+ ];
221
+
222
+ const registry = await loadPlugins(
223
+ path.join(tempDir, "nonexistent-global"),
224
+ path.join(tempDir, "nonexistent-project"),
225
+ configPlugins,
226
+ tempDir,
227
+ );
228
+
229
+ expect(registry.plugins).toHaveLength(1);
230
+ expect(registry.plugins[0].name).toBe("relative-plugin");
231
+ });
232
+
233
+ test("resolves ../relative/path from project root", async () => {
234
+ const projectRoot = path.join(tempDir, "project");
235
+ const pluginDir = path.join(tempDir, "shared-plugins");
236
+ await fs.mkdir(projectRoot, { recursive: true });
237
+ await fs.mkdir(pluginDir, { recursive: true });
238
+
239
+ const plugin: NaxPlugin = {
240
+ name: "parent-relative-plugin",
241
+ version: "1.0.0",
242
+ provides: ["optimizer"],
243
+ extensions: {
244
+ optimizer: {
245
+ name: "test",
246
+ async optimize(input) {
247
+ return {
248
+ optimizedPrompt: input.prompt,
249
+ estimatedTokens: input.estimatedTokens,
250
+ tokensSaved: 0,
251
+ appliedStrategies: [],
252
+ };
253
+ },
254
+ },
255
+ },
256
+ };
257
+
258
+ await writePluginFile(path.join(pluginDir, "plugin.ts"), plugin);
259
+
260
+ const configPlugins: PluginConfigEntry[] = [
261
+ {
262
+ module: "../shared-plugins/plugin.ts",
263
+ config: {},
264
+ },
265
+ ];
266
+
267
+ const registry = await loadPlugins(
268
+ path.join(tempDir, "nonexistent-global"),
269
+ path.join(tempDir, "nonexistent-project"),
270
+ configPlugins,
271
+ projectRoot,
272
+ );
273
+
274
+ expect(registry.plugins).toHaveLength(1);
275
+ expect(registry.plugins[0].name).toBe("parent-relative-plugin");
276
+ });
277
+ });
278
+
279
+ describe("AC3: Absolute paths and npm packages work as-is", () => {
280
+ test("loads plugin with absolute path", async () => {
281
+ const pluginDir = path.join(tempDir, "absolute-test");
282
+ await fs.mkdir(pluginDir, { recursive: true });
283
+
284
+ const plugin: NaxPlugin = {
285
+ name: "absolute-plugin",
286
+ version: "1.0.0",
287
+ provides: ["optimizer"],
288
+ extensions: {
289
+ optimizer: {
290
+ name: "test",
291
+ async optimize(input) {
292
+ return {
293
+ optimizedPrompt: input.prompt,
294
+ estimatedTokens: input.estimatedTokens,
295
+ tokensSaved: 0,
296
+ appliedStrategies: [],
297
+ };
298
+ },
299
+ },
300
+ },
301
+ };
302
+
303
+ const absolutePath = path.join(pluginDir, "plugin.ts");
304
+ await writePluginFile(absolutePath, plugin);
305
+
306
+ const configPlugins: PluginConfigEntry[] = [
307
+ {
308
+ module: absolutePath,
309
+ config: {},
310
+ },
311
+ ];
312
+
313
+ const registry = await loadPlugins(
314
+ path.join(tempDir, "nonexistent-global"),
315
+ path.join(tempDir, "nonexistent-project"),
316
+ configPlugins,
317
+ tempDir,
318
+ );
319
+
320
+ expect(registry.plugins).toHaveLength(1);
321
+ expect(registry.plugins[0].name).toBe("absolute-plugin");
322
+ });
323
+
324
+ test("treats non-relative paths as npm packages (doesn't crash)", async () => {
325
+ // This test verifies that npm package names (no ./ or ../) are passed through as-is
326
+ // They will fail to load since we don't have real npm packages, but shouldn't crash
327
+ const configPlugins: PluginConfigEntry[] = [
328
+ {
329
+ module: "nonexistent-npm-package",
330
+ config: {},
331
+ },
332
+ ];
333
+
334
+ const registry = await loadPlugins(
335
+ path.join(tempDir, "nonexistent-global"),
336
+ path.join(tempDir, "nonexistent-project"),
337
+ configPlugins,
338
+ tempDir,
339
+ );
340
+
341
+ // Should return empty registry since package doesn't exist
342
+ expect(registry.plugins).toHaveLength(0);
343
+ });
344
+ });
345
+
346
+ describe("AC4: Clear error message when plugin module not found", () => {
347
+ test("logs helpful error for missing relative path", async () => {
348
+ const errorLogs: string[] = [];
349
+ _setPluginErrorSink((...args: unknown[]) => {
350
+ errorLogs.push(args.map((arg) => String(arg)).join(" "));
351
+ });
352
+
353
+ try {
354
+ const configPlugins: PluginConfigEntry[] = [
355
+ {
356
+ module: "./nonexistent/plugin.ts",
357
+ config: {},
358
+ },
359
+ ];
360
+
361
+ await loadPlugins(
362
+ path.join(tempDir, "nonexistent-global"),
363
+ path.join(tempDir, "nonexistent-project"),
364
+ configPlugins,
365
+ tempDir,
366
+ );
367
+
368
+ // Should log error with original path
369
+ const errorOutput = errorLogs.join("\n");
370
+ expect(errorOutput).toContain("Failed to load plugin module");
371
+ expect(errorOutput).toContain("./nonexistent/plugin.ts");
372
+ expect(errorOutput).toContain("Attempted path:");
373
+ expect(errorOutput).toContain(path.join(tempDir, "nonexistent/plugin.ts"));
374
+ } finally {
375
+ _resetPluginErrorSink();
376
+ }
377
+ });
378
+
379
+ test("logs helpful error for missing absolute path", async () => {
380
+ const errorLogs: string[] = [];
381
+ _setPluginErrorSink((...args: unknown[]) => {
382
+ errorLogs.push(args.map((arg) => String(arg)).join(" "));
383
+ });
384
+
385
+ try {
386
+ const absolutePath = path.join(tempDir, "missing-plugin.ts");
387
+ const configPlugins: PluginConfigEntry[] = [
388
+ {
389
+ module: absolutePath,
390
+ config: {},
391
+ },
392
+ ];
393
+
394
+ await loadPlugins(
395
+ path.join(tempDir, "nonexistent-global"),
396
+ path.join(tempDir, "nonexistent-project"),
397
+ configPlugins,
398
+ tempDir,
399
+ );
400
+
401
+ const errorOutput = errorLogs.join("\n");
402
+ expect(errorOutput).toContain("Failed to load plugin module");
403
+ expect(errorOutput).toContain(absolutePath);
404
+ } finally {
405
+ _resetPluginErrorSink();
406
+ }
407
+ });
408
+ });
409
+
410
+ describe("AC5: Plugin-specific config passed to setup()", () => {
411
+ test("passes config object to plugin setup function", async () => {
412
+ const pluginDir = path.join(tempDir, "config-test");
413
+ await fs.mkdir(pluginDir, { recursive: true });
414
+
415
+ // Create a plugin that writes received config to a file we can check
416
+ const configFile = path.join(tempDir, "received-config.json");
417
+ const plugin: NaxPlugin = {
418
+ name: "config-receiver",
419
+ version: "1.0.0",
420
+ provides: ["optimizer"],
421
+ extensions: {
422
+ optimizer: {
423
+ name: "test",
424
+ async optimize(input) {
425
+ return {
426
+ optimizedPrompt: input.prompt,
427
+ estimatedTokens: input.estimatedTokens,
428
+ tokensSaved: 0,
429
+ appliedStrategies: [],
430
+ };
431
+ },
432
+ },
433
+ },
434
+ };
435
+
436
+ const setupFn = `
437
+ async setup(config) {
438
+ const fs = await import("node:fs/promises");
439
+ await fs.writeFile("${configFile}", JSON.stringify(config), "utf-8");
440
+ },`;
441
+
442
+ await writePluginFile(path.join(pluginDir, "plugin.ts"), plugin, setupFn);
443
+
444
+ const pluginConfig = {
445
+ apiKey: "test-key-123",
446
+ enabled: true,
447
+ options: {
448
+ timeout: 5000,
449
+ },
450
+ };
451
+
452
+ const configPlugins: PluginConfigEntry[] = [
453
+ {
454
+ module: path.join(pluginDir, "plugin.ts"),
455
+ config: pluginConfig,
456
+ },
457
+ ];
458
+
459
+ await loadPlugins(
460
+ path.join(tempDir, "nonexistent-global"),
461
+ path.join(tempDir, "nonexistent-project"),
462
+ configPlugins,
463
+ tempDir,
464
+ );
465
+
466
+ // Verify config was written
467
+ const receivedConfig = JSON.parse(await fs.readFile(configFile, "utf-8"));
468
+ expect(receivedConfig).toEqual(pluginConfig);
469
+ });
470
+
471
+ test("passes empty config object when config is undefined", async () => {
472
+ const pluginDir = path.join(tempDir, "empty-config-test");
473
+ await fs.mkdir(pluginDir, { recursive: true });
474
+
475
+ const configFile = path.join(tempDir, "empty-config.json");
476
+ const plugin: NaxPlugin = {
477
+ name: "empty-config-receiver",
478
+ version: "1.0.0",
479
+ provides: ["optimizer"],
480
+ extensions: {
481
+ optimizer: {
482
+ name: "test",
483
+ async optimize(input) {
484
+ return {
485
+ optimizedPrompt: input.prompt,
486
+ estimatedTokens: input.estimatedTokens,
487
+ tokensSaved: 0,
488
+ appliedStrategies: [],
489
+ };
490
+ },
491
+ },
492
+ },
493
+ };
494
+
495
+ const setupFn = `
496
+ async setup(config) {
497
+ const fs = await import("node:fs/promises");
498
+ await fs.writeFile("${configFile}", JSON.stringify(config), "utf-8");
499
+ },`;
500
+
501
+ await writePluginFile(path.join(pluginDir, "plugin.ts"), plugin, setupFn);
502
+
503
+ const configPlugins: PluginConfigEntry[] = [
504
+ {
505
+ module: path.join(pluginDir, "plugin.ts"),
506
+ // config is undefined
507
+ },
508
+ ];
509
+
510
+ await loadPlugins(
511
+ path.join(tempDir, "nonexistent-global"),
512
+ path.join(tempDir, "nonexistent-project"),
513
+ configPlugins,
514
+ tempDir,
515
+ );
516
+
517
+ // Verify empty config was passed
518
+ const receivedConfig = JSON.parse(await fs.readFile(configFile, "utf-8"));
519
+ expect(receivedConfig).toEqual({});
520
+ });
521
+ });
522
+ });