@nathapp/nax 0.50.3 → 0.51.2

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 (353) hide show
  1. package/CHANGELOG.md +30 -0
  2. package/README.md +177 -104
  3. package/dist/nax.js +417 -213
  4. package/package.json +1 -3
  5. package/bin/nax.ts +0 -1195
  6. package/src/acceptance/fix-generator.ts +0 -322
  7. package/src/acceptance/generator.ts +0 -415
  8. package/src/acceptance/index.ts +0 -42
  9. package/src/acceptance/refinement.ts +0 -224
  10. package/src/acceptance/templates/cli.ts +0 -47
  11. package/src/acceptance/templates/component.ts +0 -78
  12. package/src/acceptance/templates/e2e.ts +0 -43
  13. package/src/acceptance/templates/index.ts +0 -21
  14. package/src/acceptance/templates/snapshot.ts +0 -50
  15. package/src/acceptance/templates/unit.ts +0 -48
  16. package/src/acceptance/types.ts +0 -138
  17. package/src/agents/acp/adapter.ts +0 -888
  18. package/src/agents/acp/cost.ts +0 -9
  19. package/src/agents/acp/index.ts +0 -7
  20. package/src/agents/acp/interaction-bridge.ts +0 -126
  21. package/src/agents/acp/parser.ts +0 -119
  22. package/src/agents/acp/spawn-client.ts +0 -373
  23. package/src/agents/acp/types.ts +0 -22
  24. package/src/agents/aider/adapter.ts +0 -135
  25. package/src/agents/claude/adapter.ts +0 -258
  26. package/src/agents/claude/complete.ts +0 -80
  27. package/src/agents/claude/cost.ts +0 -16
  28. package/src/agents/claude/execution.ts +0 -215
  29. package/src/agents/claude/index.ts +0 -3
  30. package/src/agents/claude/interactive.ts +0 -77
  31. package/src/agents/claude/plan.ts +0 -179
  32. package/src/agents/codex/adapter.ts +0 -153
  33. package/src/agents/cost/calculate.ts +0 -154
  34. package/src/agents/cost/index.ts +0 -10
  35. package/src/agents/cost/parse.ts +0 -97
  36. package/src/agents/cost/pricing.ts +0 -59
  37. package/src/agents/cost/types.ts +0 -45
  38. package/src/agents/gemini/adapter.ts +0 -177
  39. package/src/agents/index.ts +0 -18
  40. package/src/agents/opencode/adapter.ts +0 -106
  41. package/src/agents/registry.ts +0 -136
  42. package/src/agents/shared/decompose.ts +0 -154
  43. package/src/agents/shared/model-resolution.ts +0 -43
  44. package/src/agents/shared/types-extended.ts +0 -164
  45. package/src/agents/shared/validation.ts +0 -69
  46. package/src/agents/shared/version-detection.ts +0 -109
  47. package/src/agents/types.ts +0 -205
  48. package/src/analyze/classifier.ts +0 -282
  49. package/src/analyze/index.ts +0 -16
  50. package/src/analyze/scanner.ts +0 -171
  51. package/src/analyze/types.ts +0 -51
  52. package/src/cli/accept.ts +0 -108
  53. package/src/cli/agents.ts +0 -87
  54. package/src/cli/analyze-parser.ts +0 -291
  55. package/src/cli/analyze.ts +0 -352
  56. package/src/cli/config-descriptions.ts +0 -219
  57. package/src/cli/config-diff.ts +0 -103
  58. package/src/cli/config-display.ts +0 -285
  59. package/src/cli/config-get.ts +0 -55
  60. package/src/cli/config.ts +0 -14
  61. package/src/cli/constitution.ts +0 -17
  62. package/src/cli/diagnose-analysis.ts +0 -159
  63. package/src/cli/diagnose-formatter.ts +0 -87
  64. package/src/cli/diagnose.ts +0 -203
  65. package/src/cli/generate.ts +0 -250
  66. package/src/cli/index.ts +0 -42
  67. package/src/cli/init-context.ts +0 -405
  68. package/src/cli/init-detect.ts +0 -303
  69. package/src/cli/init.ts +0 -296
  70. package/src/cli/interact.ts +0 -295
  71. package/src/cli/plan.ts +0 -509
  72. package/src/cli/plugins.ts +0 -122
  73. package/src/cli/prompts-export.ts +0 -58
  74. package/src/cli/prompts-init.ts +0 -200
  75. package/src/cli/prompts-main.ts +0 -183
  76. package/src/cli/prompts-shared.ts +0 -70
  77. package/src/cli/prompts-tdd.ts +0 -88
  78. package/src/cli/prompts.ts +0 -17
  79. package/src/cli/runs.ts +0 -174
  80. package/src/cli/status-cost.ts +0 -151
  81. package/src/cli/status-features.ts +0 -405
  82. package/src/cli/status.ts +0 -13
  83. package/src/commands/common.ts +0 -171
  84. package/src/commands/diagnose.ts +0 -17
  85. package/src/commands/index.ts +0 -9
  86. package/src/commands/logs-formatter.ts +0 -201
  87. package/src/commands/logs-reader.ts +0 -171
  88. package/src/commands/logs.ts +0 -103
  89. package/src/commands/precheck.ts +0 -86
  90. package/src/commands/runs.ts +0 -220
  91. package/src/commands/unlock.ts +0 -96
  92. package/src/config/defaults.ts +0 -218
  93. package/src/config/index.ts +0 -22
  94. package/src/config/loader.ts +0 -143
  95. package/src/config/merge.ts +0 -106
  96. package/src/config/merger.ts +0 -147
  97. package/src/config/path-security.ts +0 -121
  98. package/src/config/paths.ts +0 -27
  99. package/src/config/permissions.ts +0 -63
  100. package/src/config/runtime-types.ts +0 -522
  101. package/src/config/schema-types.ts +0 -53
  102. package/src/config/schema.ts +0 -60
  103. package/src/config/schemas.ts +0 -426
  104. package/src/config/test-strategy.ts +0 -71
  105. package/src/config/types.ts +0 -57
  106. package/src/config/validate.ts +0 -103
  107. package/src/constitution/generator.ts +0 -158
  108. package/src/constitution/generators/aider.ts +0 -41
  109. package/src/constitution/generators/claude.ts +0 -35
  110. package/src/constitution/generators/cursor.ts +0 -36
  111. package/src/constitution/generators/opencode.ts +0 -38
  112. package/src/constitution/generators/types.ts +0 -33
  113. package/src/constitution/generators/windsurf.ts +0 -36
  114. package/src/constitution/index.ts +0 -11
  115. package/src/constitution/loader.ts +0 -121
  116. package/src/constitution/types.ts +0 -31
  117. package/src/context/auto-detect.ts +0 -228
  118. package/src/context/builder.ts +0 -299
  119. package/src/context/elements.ts +0 -122
  120. package/src/context/formatter.ts +0 -107
  121. package/src/context/generator.ts +0 -343
  122. package/src/context/generators/aider.ts +0 -34
  123. package/src/context/generators/claude.ts +0 -28
  124. package/src/context/generators/codex.ts +0 -28
  125. package/src/context/generators/cursor.ts +0 -28
  126. package/src/context/generators/gemini.ts +0 -28
  127. package/src/context/generators/opencode.ts +0 -30
  128. package/src/context/generators/windsurf.ts +0 -28
  129. package/src/context/greenfield.ts +0 -114
  130. package/src/context/index.ts +0 -34
  131. package/src/context/injector.ts +0 -279
  132. package/src/context/parent-context.ts +0 -39
  133. package/src/context/test-scanner.ts +0 -370
  134. package/src/context/types.ts +0 -98
  135. package/src/decompose/apply.ts +0 -50
  136. package/src/decompose/builder.ts +0 -181
  137. package/src/decompose/index.ts +0 -8
  138. package/src/decompose/sections/codebase.ts +0 -26
  139. package/src/decompose/sections/constraints.ts +0 -32
  140. package/src/decompose/sections/index.ts +0 -4
  141. package/src/decompose/sections/sibling-stories.ts +0 -25
  142. package/src/decompose/sections/target-story.ts +0 -31
  143. package/src/decompose/types.ts +0 -55
  144. package/src/decompose/validators/complexity.ts +0 -45
  145. package/src/decompose/validators/coverage.ts +0 -134
  146. package/src/decompose/validators/dependency.ts +0 -91
  147. package/src/decompose/validators/index.ts +0 -35
  148. package/src/decompose/validators/overlap.ts +0 -128
  149. package/src/errors.ts +0 -67
  150. package/src/execution/batching.ts +0 -157
  151. package/src/execution/crash-heartbeat.ts +0 -77
  152. package/src/execution/crash-recovery.ts +0 -79
  153. package/src/execution/crash-signals.ts +0 -165
  154. package/src/execution/crash-writer.ts +0 -154
  155. package/src/execution/deferred-review.ts +0 -105
  156. package/src/execution/dry-run.ts +0 -81
  157. package/src/execution/escalation/escalation.ts +0 -46
  158. package/src/execution/escalation/index.ts +0 -13
  159. package/src/execution/escalation/tier-escalation.ts +0 -346
  160. package/src/execution/escalation/tier-outcome.ts +0 -143
  161. package/src/execution/executor-types.ts +0 -73
  162. package/src/execution/helpers.ts +0 -38
  163. package/src/execution/index.ts +0 -27
  164. package/src/execution/iteration-runner.ts +0 -160
  165. package/src/execution/lifecycle/acceptance-loop.ts +0 -309
  166. package/src/execution/lifecycle/headless-formatter.ts +0 -83
  167. package/src/execution/lifecycle/index.ts +0 -11
  168. package/src/execution/lifecycle/parallel-lifecycle.ts +0 -101
  169. package/src/execution/lifecycle/precheck-runner.ts +0 -140
  170. package/src/execution/lifecycle/run-cleanup.ts +0 -81
  171. package/src/execution/lifecycle/run-completion.ts +0 -247
  172. package/src/execution/lifecycle/run-initialization.ts +0 -187
  173. package/src/execution/lifecycle/run-regression.ts +0 -305
  174. package/src/execution/lifecycle/run-setup.ts +0 -240
  175. package/src/execution/lifecycle/story-size-prompts.ts +0 -123
  176. package/src/execution/lock.ts +0 -129
  177. package/src/execution/parallel-coordinator.ts +0 -281
  178. package/src/execution/parallel-executor-rectification-pass.ts +0 -117
  179. package/src/execution/parallel-executor-rectify.ts +0 -136
  180. package/src/execution/parallel-executor.ts +0 -330
  181. package/src/execution/parallel-worker.ts +0 -149
  182. package/src/execution/parallel.ts +0 -13
  183. package/src/execution/pid-registry.ts +0 -275
  184. package/src/execution/pipeline-result-handler.ts +0 -221
  185. package/src/execution/progress.ts +0 -27
  186. package/src/execution/queue-handler.ts +0 -109
  187. package/src/execution/runner-completion.ts +0 -171
  188. package/src/execution/runner-execution.ts +0 -243
  189. package/src/execution/runner-setup.ts +0 -86
  190. package/src/execution/runner.ts +0 -265
  191. package/src/execution/sequential-executor.ts +0 -219
  192. package/src/execution/status-file.ts +0 -264
  193. package/src/execution/status-writer.ts +0 -181
  194. package/src/execution/story-context.ts +0 -266
  195. package/src/execution/story-selector.ts +0 -76
  196. package/src/execution/test-output-parser.ts +0 -14
  197. package/src/execution/timeout-handler.ts +0 -100
  198. package/src/hooks/index.ts +0 -2
  199. package/src/hooks/runner.ts +0 -280
  200. package/src/hooks/types.ts +0 -79
  201. package/src/interaction/chain.ts +0 -170
  202. package/src/interaction/index.ts +0 -61
  203. package/src/interaction/init.ts +0 -84
  204. package/src/interaction/plugins/auto.ts +0 -243
  205. package/src/interaction/plugins/cli.ts +0 -300
  206. package/src/interaction/plugins/telegram.ts +0 -384
  207. package/src/interaction/plugins/webhook.ts +0 -286
  208. package/src/interaction/state.ts +0 -171
  209. package/src/interaction/triggers.ts +0 -250
  210. package/src/interaction/types.ts +0 -170
  211. package/src/logger/formatters.ts +0 -84
  212. package/src/logger/index.ts +0 -16
  213. package/src/logger/logger.ts +0 -296
  214. package/src/logger/types.ts +0 -48
  215. package/src/logging/formatter.ts +0 -355
  216. package/src/logging/index.ts +0 -22
  217. package/src/logging/types.ts +0 -93
  218. package/src/metrics/aggregator.ts +0 -191
  219. package/src/metrics/index.ts +0 -14
  220. package/src/metrics/tracker.ts +0 -200
  221. package/src/metrics/types.ts +0 -115
  222. package/src/optimizer/index.ts +0 -63
  223. package/src/optimizer/noop.optimizer.ts +0 -24
  224. package/src/optimizer/rule-based.optimizer.ts +0 -248
  225. package/src/optimizer/types.ts +0 -53
  226. package/src/pipeline/event-bus.ts +0 -297
  227. package/src/pipeline/events.ts +0 -130
  228. package/src/pipeline/index.ts +0 -19
  229. package/src/pipeline/runner.ts +0 -149
  230. package/src/pipeline/stages/acceptance-setup.ts +0 -144
  231. package/src/pipeline/stages/acceptance.ts +0 -215
  232. package/src/pipeline/stages/autofix.ts +0 -262
  233. package/src/pipeline/stages/completion.ts +0 -110
  234. package/src/pipeline/stages/constitution.ts +0 -63
  235. package/src/pipeline/stages/context.ts +0 -122
  236. package/src/pipeline/stages/execution.ts +0 -359
  237. package/src/pipeline/stages/index.ts +0 -86
  238. package/src/pipeline/stages/optimizer.ts +0 -74
  239. package/src/pipeline/stages/prompt.ts +0 -79
  240. package/src/pipeline/stages/queue-check.ts +0 -103
  241. package/src/pipeline/stages/rectify.ts +0 -101
  242. package/src/pipeline/stages/regression.ts +0 -99
  243. package/src/pipeline/stages/review.ts +0 -94
  244. package/src/pipeline/stages/routing.ts +0 -276
  245. package/src/pipeline/stages/verify.ts +0 -286
  246. package/src/pipeline/subscribers/events-writer.ts +0 -135
  247. package/src/pipeline/subscribers/hooks.ts +0 -179
  248. package/src/pipeline/subscribers/interaction.ts +0 -103
  249. package/src/pipeline/subscribers/registry.ts +0 -73
  250. package/src/pipeline/subscribers/reporters.ts +0 -174
  251. package/src/pipeline/types.ts +0 -220
  252. package/src/plugins/extensions.ts +0 -225
  253. package/src/plugins/index.ts +0 -33
  254. package/src/plugins/loader.ts +0 -352
  255. package/src/plugins/plugin-logger.ts +0 -41
  256. package/src/plugins/registry.ts +0 -168
  257. package/src/plugins/types.ts +0 -206
  258. package/src/plugins/validator.ts +0 -352
  259. package/src/prd/index.ts +0 -220
  260. package/src/prd/schema.ts +0 -268
  261. package/src/prd/types.ts +0 -273
  262. package/src/prd/validate.ts +0 -41
  263. package/src/precheck/checks-agents.ts +0 -63
  264. package/src/precheck/checks-blockers.ts +0 -23
  265. package/src/precheck/checks-cli.ts +0 -68
  266. package/src/precheck/checks-config.ts +0 -102
  267. package/src/precheck/checks-git.ts +0 -117
  268. package/src/precheck/checks-system.ts +0 -101
  269. package/src/precheck/checks-warnings.ts +0 -221
  270. package/src/precheck/checks.ts +0 -36
  271. package/src/precheck/index.ts +0 -374
  272. package/src/precheck/story-size-gate.ts +0 -144
  273. package/src/precheck/types.ts +0 -31
  274. package/src/prompts/builder.ts +0 -166
  275. package/src/prompts/index.ts +0 -2
  276. package/src/prompts/loader.ts +0 -43
  277. package/src/prompts/sections/conventions.ts +0 -19
  278. package/src/prompts/sections/hermetic.ts +0 -41
  279. package/src/prompts/sections/index.ts +0 -12
  280. package/src/prompts/sections/isolation.ts +0 -70
  281. package/src/prompts/sections/role-task.ts +0 -182
  282. package/src/prompts/sections/story.ts +0 -55
  283. package/src/prompts/sections/verdict.ts +0 -70
  284. package/src/prompts/types.ts +0 -21
  285. package/src/queue/index.ts +0 -2
  286. package/src/queue/manager.ts +0 -254
  287. package/src/queue/types.ts +0 -54
  288. package/src/review/index.ts +0 -8
  289. package/src/review/orchestrator.ts +0 -154
  290. package/src/review/runner.ts +0 -303
  291. package/src/review/types.ts +0 -70
  292. package/src/routing/batch-route.ts +0 -35
  293. package/src/routing/builder.ts +0 -81
  294. package/src/routing/chain.ts +0 -75
  295. package/src/routing/content-hash.ts +0 -25
  296. package/src/routing/index.ts +0 -20
  297. package/src/routing/loader.ts +0 -62
  298. package/src/routing/router.ts +0 -305
  299. package/src/routing/strategies/adaptive.ts +0 -215
  300. package/src/routing/strategies/index.ts +0 -8
  301. package/src/routing/strategies/keyword.ts +0 -180
  302. package/src/routing/strategies/llm-prompts.ts +0 -224
  303. package/src/routing/strategies/llm.ts +0 -320
  304. package/src/routing/strategies/manual.ts +0 -50
  305. package/src/routing/strategy.ts +0 -102
  306. package/src/tdd/cleanup.ts +0 -120
  307. package/src/tdd/index.ts +0 -22
  308. package/src/tdd/isolation.ts +0 -117
  309. package/src/tdd/orchestrator.ts +0 -406
  310. package/src/tdd/prompts.ts +0 -40
  311. package/src/tdd/rectification-gate.ts +0 -274
  312. package/src/tdd/session-runner.ts +0 -263
  313. package/src/tdd/types.ts +0 -84
  314. package/src/tdd/verdict-reader.ts +0 -266
  315. package/src/tdd/verdict.ts +0 -152
  316. package/src/tui/App.tsx +0 -265
  317. package/src/tui/components/AgentPanel.tsx +0 -75
  318. package/src/tui/components/CostOverlay.tsx +0 -118
  319. package/src/tui/components/HelpOverlay.tsx +0 -107
  320. package/src/tui/components/StatusBar.tsx +0 -63
  321. package/src/tui/components/StoriesPanel.tsx +0 -177
  322. package/src/tui/hooks/useKeyboard.ts +0 -142
  323. package/src/tui/hooks/useLayout.ts +0 -137
  324. package/src/tui/hooks/usePipelineEvents.ts +0 -183
  325. package/src/tui/hooks/usePty.ts +0 -189
  326. package/src/tui/index.tsx +0 -38
  327. package/src/tui/types.ts +0 -76
  328. package/src/utils/errors.ts +0 -12
  329. package/src/utils/git.ts +0 -245
  330. package/src/utils/json-file.ts +0 -72
  331. package/src/utils/log-test-output.ts +0 -25
  332. package/src/utils/path-security.ts +0 -73
  333. package/src/utils/queue-writer.ts +0 -54
  334. package/src/verification/crash-detector.ts +0 -34
  335. package/src/verification/executor.ts +0 -250
  336. package/src/verification/index.ts +0 -12
  337. package/src/verification/orchestrator-types.ts +0 -154
  338. package/src/verification/orchestrator.ts +0 -76
  339. package/src/verification/parser.ts +0 -220
  340. package/src/verification/rectification-loop.ts +0 -172
  341. package/src/verification/rectification.ts +0 -108
  342. package/src/verification/runners.ts +0 -129
  343. package/src/verification/smart-runner.ts +0 -307
  344. package/src/verification/strategies/acceptance.ts +0 -136
  345. package/src/verification/strategies/regression.ts +0 -90
  346. package/src/verification/strategies/scoped.ts +0 -154
  347. package/src/verification/types.ts +0 -117
  348. package/src/version.ts +0 -40
  349. package/src/worktree/dispatcher.ts +0 -6
  350. package/src/worktree/index.ts +0 -2
  351. package/src/worktree/manager.ts +0 -193
  352. package/src/worktree/merge.ts +0 -302
  353. package/src/worktree/types.ts +0 -4
@@ -1,352 +0,0 @@
1
- /**
2
- * Plugin Loader
3
- *
4
- * Discovers, imports, validates, and initializes plugins from:
5
- * 1. Global directory (~/.nax/plugins/)
6
- * 2. Project directory (<project>/nax/plugins/)
7
- * 3. Config entries (explicit module paths)
8
- */
9
-
10
- import * as fs from "node:fs/promises";
11
- import * as path from "node:path";
12
- import { getSafeLogger as _getSafeLoggerFromModule } from "../logger";
13
- import { errorMessage } from "../utils/errors";
14
- import { validateModulePath } from "../utils/path-security";
15
- import { createPluginLogger } from "./plugin-logger";
16
- import { PluginRegistry } from "./registry";
17
- import type { NaxPlugin, PluginConfigEntry } from "./types";
18
- import { validatePlugin } from "./validator";
19
-
20
- /**
21
- * Swappable error sink — defaults to console.error.
22
- * Tests can replace this to capture plugin error output.
23
- * @internal
24
- */
25
- export let _pluginErrorSink: (...args: unknown[]) => void = (...args) => console.error(...args);
26
-
27
- /** @internal — for testing only */
28
- export function _setPluginErrorSink(fn: (...args: unknown[]) => void): void {
29
- _pluginErrorSink = fn;
30
- }
31
-
32
- /** @internal — reset to default */
33
- export function _resetPluginErrorSink(): void {
34
- _pluginErrorSink = (...args) => console.error(...args);
35
- }
36
-
37
- /**
38
- * Safely get logger instance, returns null if not initialized.
39
- * Delegates to the module's getSafeLogger which correctly returns null for noopLogger.
40
- */
41
- function getSafeLogger() {
42
- return _getSafeLoggerFromModule();
43
- }
44
-
45
- /**
46
- * Plugin source metadata.
47
- */
48
- export interface PluginSource {
49
- type: "global" | "project" | "config";
50
- path: string;
51
- }
52
-
53
- /**
54
- * Extract plugin name from file path.
55
- * For index files (e.g., /path/to/plugin/index.ts), returns the parent directory name.
56
- * For single files (e.g., /path/to/plugin.ts), returns the filename without extension.
57
- *
58
- * @param pluginPath - Path to plugin file
59
- * @returns Plugin name
60
- */
61
- function extractPluginName(pluginPath: string): string {
62
- const basename = path.basename(pluginPath);
63
- if (basename === "index.ts" || basename === "index.js" || basename === "index.mjs") {
64
- // For index files, use the parent directory name
65
- return path.basename(path.dirname(pluginPath));
66
- }
67
- // For single files, use filename without extension
68
- return basename.replace(/\.(ts|js|mjs)$/, "");
69
- }
70
-
71
- /**
72
- * Plugin with source information.
73
- */
74
- export interface LoadedPlugin {
75
- plugin: NaxPlugin;
76
- source: PluginSource;
77
- }
78
-
79
- /**
80
- * Load and validate all plugins from global + project + config sources.
81
- *
82
- * Load order:
83
- * 1. Scan ~/.nax/plugins/ (if exists)
84
- * 2. Scan <project>/nax/plugins/ (if exists)
85
- * 3. Load explicit modules from config.plugins[]
86
- *
87
- * Each plugin is validated, then setup() is called with its config.
88
- * Plugins can be disabled via config.plugins[].enabled or config.disabledPlugins[].
89
- *
90
- * @param globalDir - Global plugins directory (e.g., ~/.nax/plugins)
91
- * @param projectDir - Project plugins directory (e.g., <project>/nax/plugins)
92
- * @param configPlugins - Explicit plugin entries from config
93
- * @param projectRoot - Project root directory for resolving relative paths in config
94
- * @param disabledPlugins - List of plugin names to disable (auto-discovered plugins only)
95
- * @returns PluginRegistry with all loaded plugins and their sources
96
- */
97
- export async function loadPlugins(
98
- globalDir: string,
99
- projectDir: string,
100
- configPlugins: PluginConfigEntry[],
101
- projectRoot?: string,
102
- disabledPlugins?: string[],
103
- ): Promise<PluginRegistry> {
104
- const loadedPlugins: LoadedPlugin[] = [];
105
- const effectiveProjectRoot = projectRoot || projectDir;
106
- const pluginNames = new Set<string>();
107
- const disabledSet = new Set(disabledPlugins ?? []);
108
- const logger = getSafeLogger();
109
-
110
- // 1. Load plugins from global directory
111
- const globalPlugins = await discoverPlugins(globalDir);
112
- for (const plugin of globalPlugins) {
113
- const pluginName = extractPluginName(plugin.path);
114
- if (disabledSet.has(pluginName)) {
115
- logger?.info("plugins", `Skipping disabled plugin: '${pluginName}' (global directory)`);
116
- continue;
117
- }
118
- const validated = await loadAndValidatePlugin(plugin.path, {}, [globalDir]);
119
- if (validated) {
120
- if (pluginNames.has(validated.name)) {
121
- logger?.warn("plugins", `Plugin name collision: '${validated.name}' (global directory)`);
122
- }
123
- loadedPlugins.push({
124
- plugin: validated,
125
- source: { type: "global", path: plugin.path },
126
- });
127
- pluginNames.add(validated.name);
128
- }
129
- }
130
-
131
- // 2. Load plugins from project directory
132
- const projectPlugins = await discoverPlugins(projectDir);
133
- for (const plugin of projectPlugins) {
134
- const pluginName = extractPluginName(plugin.path);
135
- if (disabledSet.has(pluginName)) {
136
- logger?.info("plugins", `Skipping disabled plugin: '${pluginName}' (project directory)`);
137
- continue;
138
- }
139
- const validated = await loadAndValidatePlugin(plugin.path, {}, [projectDir]);
140
- if (validated) {
141
- if (pluginNames.has(validated.name)) {
142
- logger?.warn("plugins", `Plugin name collision: '${validated.name}' (project directory overrides global)`);
143
- }
144
- loadedPlugins.push({
145
- plugin: validated,
146
- source: { type: "project", path: plugin.path },
147
- });
148
- pluginNames.add(validated.name);
149
- }
150
- }
151
-
152
- // 3. Load plugins from config entries
153
- for (const entry of configPlugins) {
154
- // Check if plugin is explicitly disabled in config
155
- if (entry.enabled === false) {
156
- logger?.info("plugins", `Skipping disabled plugin: '${entry.module}'`);
157
- continue;
158
- }
159
- // Resolve module path relative to effective project root for relative paths
160
- const resolvedModule = resolveModulePath(entry.module, effectiveProjectRoot);
161
- const validated = await loadAndValidatePlugin(
162
- resolvedModule,
163
- entry.config ?? {},
164
- [globalDir, projectDir, effectiveProjectRoot].filter(Boolean),
165
- entry.module,
166
- );
167
- if (validated) {
168
- if (pluginNames.has(validated.name)) {
169
- logger?.warn("plugins", `Plugin name collision: '${validated.name}' (config entry overrides previous)`);
170
- }
171
- loadedPlugins.push({
172
- plugin: validated,
173
- source: { type: "config", path: entry.module },
174
- });
175
- pluginNames.add(validated.name);
176
- }
177
- }
178
-
179
- return new PluginRegistry(loadedPlugins);
180
- }
181
-
182
- /**
183
- * Discover plugin files in a directory.
184
- *
185
- * Scans for:
186
- * - Single-file plugins (*.ts, *.js, *.mjs)
187
- * - Directory plugins with index.ts/index.js/index.mjs
188
- *
189
- * @param dir - Directory to scan
190
- * @returns Array of discovered plugin paths
191
- */
192
- async function discoverPlugins(dir: string): Promise<Array<{ path: string }>> {
193
- const discovered: Array<{ path: string }> = [];
194
-
195
- try {
196
- const entries = await fs.readdir(dir, { withFileTypes: true });
197
-
198
- for (const entry of entries) {
199
- const fullPath = path.join(dir, entry.name);
200
-
201
- if (entry.isFile()) {
202
- // Single-file plugin
203
- if (isPluginFile(entry.name)) {
204
- discovered.push({ path: fullPath });
205
- }
206
- } else if (entry.isDirectory()) {
207
- // Directory plugin — check for index file
208
- const indexPaths = ["index.ts", "index.js", "index.mjs"];
209
- for (const indexFile of indexPaths) {
210
- const indexPath = path.join(fullPath, indexFile);
211
- try {
212
- await fs.access(indexPath);
213
- discovered.push({ path: indexPath });
214
- break;
215
- } catch {
216
- // Index file doesn't exist, try next
217
- }
218
- }
219
- }
220
- }
221
- } catch (error) {
222
- // ERR-1 fix: Only catch ENOENT, re-throw other errors
223
- if ((error as NodeJS.ErrnoException).code === "ENOENT") {
224
- // Directory doesn't exist — not an error, just no plugins
225
- return [];
226
- }
227
- // Re-throw permission errors, disk failures, etc.
228
- throw error;
229
- }
230
-
231
- return discovered;
232
- }
233
-
234
- /**
235
- * Check if a filename is a valid plugin file.
236
- *
237
- * @param filename - Filename to check
238
- * @returns Whether the file could be a plugin
239
- */
240
- function isPluginFile(filename: string): boolean {
241
- return /\.(ts|js|mjs)$/.test(filename) && !filename.endsWith(".test.ts") && !filename.endsWith(".spec.ts");
242
- }
243
-
244
- /**
245
- * Resolve a module path, handling relative paths, absolute paths, and npm packages.
246
- *
247
- * @param modulePath - Module path from config (can be relative, absolute, or npm package)
248
- * @param projectRoot - Project root directory for resolving relative paths
249
- * @returns Resolved absolute path or npm package name
250
- */
251
- function resolveModulePath(modulePath: string, projectRoot?: string): string {
252
- // Absolute paths and npm packages (no leading ./ or ../) work as-is
253
- if (path.isAbsolute(modulePath) || (!modulePath.startsWith("./") && !modulePath.startsWith("../"))) {
254
- return modulePath;
255
- }
256
-
257
- // Relative paths need to be resolved relative to project root
258
- if (projectRoot) {
259
- return path.resolve(projectRoot, modulePath);
260
- }
261
-
262
- // Fallback: resolve relative to cwd (shouldn't happen in normal usage)
263
- return path.resolve(modulePath);
264
- }
265
-
266
- /**
267
- * Load and validate a plugin from a module path.
268
- *
269
- * @param modulePath - Path to plugin module (should be resolved)
270
- * @param config - Plugin-specific config
271
- * @param originalPath - Original path from config (for error messages)
272
- * @returns Validated plugin or null if invalid
273
- */
274
- async function loadAndValidatePlugin(
275
- initialModulePath: string,
276
- config: Record<string, unknown>,
277
- allowedRoots: string[] = [],
278
- originalPath?: string,
279
- ): Promise<NaxPlugin | null> {
280
- let attemptedPath = initialModulePath;
281
- try {
282
- // SEC-1: Validate module path if it's a file path (not an npm package)
283
- let modulePath = initialModulePath;
284
- const isFilePath = modulePath.startsWith("/") || modulePath.startsWith("./") || modulePath.startsWith("../");
285
-
286
- if (isFilePath && allowedRoots.length > 0) {
287
- const validation = validateModulePath(modulePath, allowedRoots);
288
- if (!validation.valid) {
289
- const logger = getSafeLogger();
290
- logger?.error("plugins", `Security: ${validation.error}`);
291
- _pluginErrorSink(`[plugins] Security: ${validation.error}`);
292
- return null;
293
- }
294
- // Use the normalized absolute path from the validator
295
- const validatedPath = validation.absolutePath as string;
296
- modulePath = validatedPath;
297
- }
298
-
299
- // Import the module
300
- attemptedPath = modulePath;
301
- const imported = await import(modulePath);
302
-
303
- // Try default export first, then named exports
304
- const module = imported.default || imported;
305
-
306
- // Validate plugin shape
307
- const validated = validatePlugin(module);
308
- if (!validated) {
309
- return null;
310
- }
311
-
312
- // Call setup() if defined — pass plugin-scoped logger
313
- if (validated.setup) {
314
- try {
315
- const pluginLogger = createPluginLogger(validated.name);
316
- await validated.setup(config, pluginLogger);
317
- } catch (error) {
318
- const logger = getSafeLogger();
319
- logger?.error("plugins", `Plugin '${validated.name}' setup failed`, { error });
320
- return null;
321
- }
322
- }
323
-
324
- return validated;
325
- } catch (error) {
326
- const displayPath = originalPath || initialModulePath;
327
- const errorMsg = errorMessage(error);
328
- const logger = getSafeLogger();
329
-
330
- // Provide helpful error message with attempted paths
331
- if (errorMsg.includes("Cannot find module") || errorMsg.includes("ENOENT")) {
332
- const msg = `Failed to load plugin module '${displayPath}'`;
333
- logger?.error("plugins", msg);
334
- logger?.error("plugins", `Attempted path: ${attemptedPath}`);
335
- logger?.error(
336
- "plugins",
337
- "Ensure the module exists and the path is correct (relative paths are resolved from project root)",
338
- );
339
- // Always emit to sink so tests (and headless mode without logger) can capture output
340
- _pluginErrorSink(`[plugins] ${msg}`);
341
- _pluginErrorSink(`[plugins] Attempted path: ${attemptedPath}`);
342
- _pluginErrorSink(
343
- "[plugins] Ensure the module exists and the path is correct (relative paths are resolved from project root)",
344
- );
345
- } else {
346
- logger?.warn("plugins", `Failed to load plugin from '${displayPath}'`, { error: errorMsg });
347
- // Always emit to sink
348
- _pluginErrorSink(`[plugins] Failed to load plugin from '${displayPath}': ${errorMsg}`);
349
- }
350
- return null;
351
- }
352
- }
@@ -1,41 +0,0 @@
1
- /**
2
- * Plugin Logger Factory
3
- *
4
- * Creates write-only, stage-prefixed loggers for plugins.
5
- * Each logger auto-tags entries with `plugin:<name>` so plugin
6
- * output is filterable and cannot impersonate core stages.
7
- *
8
- * @module plugins/plugin-logger
9
- */
10
-
11
- import { getSafeLogger } from "../logger";
12
- import type { PluginLogger } from "./types";
13
-
14
- /**
15
- * Create a PluginLogger scoped to a plugin name.
16
- *
17
- * The returned logger delegates to the global nax Logger with
18
- * `plugin:<pluginName>` as the stage. If the global logger is
19
- * not initialized (e.g., during tests), calls are silently dropped.
20
- *
21
- * @param pluginName - Plugin name used as stage prefix
22
- * @returns PluginLogger instance
23
- */
24
- export function createPluginLogger(pluginName: string): PluginLogger {
25
- const stage = `plugin:${pluginName}`;
26
-
27
- return {
28
- error(message: string, data?: Record<string, unknown>): void {
29
- getSafeLogger()?.error(stage, message, data);
30
- },
31
- warn(message: string, data?: Record<string, unknown>): void {
32
- getSafeLogger()?.warn(stage, message, data);
33
- },
34
- info(message: string, data?: Record<string, unknown>): void {
35
- getSafeLogger()?.info(stage, message, data);
36
- },
37
- debug(message: string, data?: Record<string, unknown>): void {
38
- getSafeLogger()?.debug(stage, message, data);
39
- },
40
- };
41
- }
@@ -1,168 +0,0 @@
1
- /**
2
- * Plugin Registry
3
- *
4
- * Central registry for all loaded plugins with typed getters.
5
- */
6
-
7
- import type { AgentAdapter } from "../agents/types";
8
- import { getSafeLogger } from "../logger";
9
- import type { RoutingStrategy } from "../routing/strategy";
10
- import type { LoadedPlugin, PluginSource } from "./loader";
11
- import type { IContextProvider, IPromptOptimizer, IReporter, IReviewPlugin, NaxPlugin } from "./types";
12
-
13
- /**
14
- * Plugin registry with typed getters for each extension type.
15
- *
16
- * Created once at run start and passed through the pipeline context.
17
- * Provides efficient access to plugins by extension type.
18
- */
19
- export class PluginRegistry {
20
- /** All loaded plugins (readonly) */
21
- readonly plugins: ReadonlyArray<NaxPlugin>;
22
-
23
- /** Plugin source information (maps plugin name to source) */
24
- private readonly sources: Map<string, PluginSource>;
25
-
26
- constructor(loadedPlugins: LoadedPlugin[] | NaxPlugin[]) {
27
- // Support both LoadedPlugin[] and NaxPlugin[] for backward compatibility
28
- if (loadedPlugins.length > 0 && "plugin" in loadedPlugins[0]) {
29
- // New format: LoadedPlugin[]
30
- const typed = loadedPlugins as LoadedPlugin[];
31
- this.plugins = typed.map((lp) => lp.plugin);
32
- this.sources = new Map(typed.map((lp) => [lp.plugin.name, lp.source]));
33
- } else {
34
- // Legacy format: NaxPlugin[]
35
- const typed = loadedPlugins as NaxPlugin[];
36
- this.plugins = typed;
37
- this.sources = new Map();
38
- }
39
- }
40
-
41
- /**
42
- * Get the source information for a plugin.
43
- *
44
- * @param pluginName - Name of the plugin
45
- * @returns Plugin source or undefined if not found
46
- */
47
- getSource(pluginName: string): PluginSource | undefined {
48
- return this.sources.get(pluginName);
49
- }
50
-
51
- /**
52
- * Get all prompt optimizers.
53
- *
54
- * @returns Array of optimizer implementations
55
- */
56
- getOptimizers(): IPromptOptimizer[] {
57
- return this.plugins
58
- .filter((p) => p.provides.includes("optimizer"))
59
- .map((p) => p.extensions.optimizer)
60
- .filter((opt): opt is IPromptOptimizer => opt !== undefined);
61
- }
62
-
63
- /**
64
- * Get all routing strategies.
65
- *
66
- * Plugin routers are returned in load order and should be inserted
67
- * before built-in strategies in the routing chain.
68
- *
69
- * @returns Array of routing strategy implementations
70
- */
71
- getRouters(): RoutingStrategy[] {
72
- return this.plugins
73
- .filter((p) => p.provides.includes("router"))
74
- .map((p) => p.extensions.router)
75
- .filter((router): router is RoutingStrategy => router !== undefined);
76
- }
77
-
78
- /**
79
- * Get agent adapter by name.
80
- *
81
- * If multiple plugins provide the same agent name, the last loaded wins.
82
- *
83
- * @param name - Agent name to lookup
84
- * @returns Agent adapter or undefined if not found
85
- */
86
- getAgent(name: string): AgentAdapter | undefined {
87
- const agents = this.plugins
88
- .filter((p) => p.provides.includes("agent"))
89
- .map((p) => p.extensions.agent)
90
- .filter((agent): agent is AgentAdapter => agent !== undefined);
91
-
92
- // Last loaded wins on name collision
93
- for (let i = agents.length - 1; i >= 0; i--) {
94
- if (agents[i].name === name) {
95
- return agents[i];
96
- }
97
- }
98
-
99
- return undefined;
100
- }
101
-
102
- /**
103
- * Get all review plugins.
104
- *
105
- * Review plugins run after built-in checks (typecheck, lint, test).
106
- * All plugin checks are additive.
107
- *
108
- * @returns Array of review plugin implementations
109
- */
110
- getReviewers(): IReviewPlugin[] {
111
- return this.plugins
112
- .filter((p) => p.provides.includes("reviewer"))
113
- .map((p) => p.extensions.reviewer)
114
- .filter((reviewer): reviewer is IReviewPlugin => reviewer !== undefined);
115
- }
116
-
117
- /**
118
- * Get all context providers.
119
- *
120
- * Context providers fetch external data (Jira, Linear, etc.) and
121
- * inject it into agent prompts. All providers are additive, subject
122
- * to token budget.
123
- *
124
- * @returns Array of context provider implementations
125
- */
126
- getContextProviders(): IContextProvider[] {
127
- return this.plugins
128
- .filter((p) => p.provides.includes("context-provider"))
129
- .map((p) => p.extensions.contextProvider)
130
- .filter((provider): provider is IContextProvider => provider !== undefined);
131
- }
132
-
133
- /**
134
- * Get all reporters.
135
- *
136
- * Reporters receive run lifecycle events for dashboards, CI, etc.
137
- * All reporters are additive and fire-and-forget.
138
- *
139
- * @returns Array of reporter implementations
140
- */
141
- getReporters(): IReporter[] {
142
- return this.plugins
143
- .filter((p) => p.provides.includes("reporter"))
144
- .map((p) => p.extensions.reporter)
145
- .filter((reporter): reporter is IReporter => reporter !== undefined);
146
- }
147
-
148
- /**
149
- * Teardown all plugins.
150
- *
151
- * Calls teardown() on each plugin (if defined) in order.
152
- * Logs errors but continues teardown for all plugins.
153
- *
154
- * Called when the nax run ends (success or failure).
155
- */
156
- async teardownAll(): Promise<void> {
157
- const logger = getSafeLogger();
158
- for (const plugin of this.plugins) {
159
- if (plugin.teardown) {
160
- try {
161
- await plugin.teardown();
162
- } catch (error) {
163
- logger?.error("plugins", `Plugin '${plugin.name}' teardown failed`, { error });
164
- }
165
- }
166
- }
167
- }
168
- }