@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.
- package/.gitlab-ci.yml +96 -0
- package/BRIEF.md +140 -0
- package/CHANGELOG.md +60 -0
- package/CLAUDE.md +159 -0
- package/README.md +373 -0
- package/US-007-IMPLEMENTATION.md +139 -0
- package/bin/nax.ts +930 -0
- package/biome.json +14 -0
- package/bun.lock +168 -0
- package/bunfig.toml +11 -0
- package/docs/20260216-fix-plan-context-review.md +56 -0
- package/docs/20260216-relentless-vs-ngent-comparison.md +208 -0
- package/docs/20260216-v02-plan.md +136 -0
- package/docs/20260216-v02-review.md +685 -0
- package/docs/20260217-dogfood-findings.md +56 -0
- package/docs/20260217-p2-plus-plan.md +117 -0
- package/docs/20260217-partial-fixes-plan.md +62 -0
- package/docs/20260217-plan-analyze-spec.md +117 -0
- package/docs/20260217-post-impl-review.md +1137 -0
- package/docs/20260217-quick-wins-plan.md +66 -0
- package/docs/20260217-split-runner-plan.md +75 -0
- package/docs/20260217-v03-impl-plan.md +80 -0
- package/docs/20260217-v03-post-impl-review.md +589 -0
- package/docs/20260217-v04-impl-plan.md +86 -0
- package/docs/20260217-v05-post-impl-review.md +850 -0
- package/docs/20260217-v06-post-impl-review.md +817 -0
- package/docs/20260218-adr003-port-plan.md +151 -0
- package/docs/20260218-review-adr003-verification.md +175 -0
- package/docs/20260219-fix-plan-bug16-19.md +79 -0
- package/docs/20260219-fix-plan-bug20-22.md +114 -0
- package/docs/20260219-plan-llm-routing.md +116 -0
- package/docs/20260219-review-bug20-22-fixes.md +135 -0
- package/docs/20260219-routing-baseline-keyword.md +63 -0
- package/docs/20260220-plan-structured-logging-p1.md +80 -0
- package/docs/20260220-plan-structured-logging-p2.md +37 -0
- package/docs/20260220-review-llm-routing.md +180 -0
- package/docs/20260220-review-post-fix-llm-routing.md +70 -0
- package/docs/20260221-fix-plan-relevantfiles-split.md +101 -0
- package/docs/20260221-fix-plan-routing-mode.md +125 -0
- package/docs/20260221-review-v0.9-implementation.md +379 -0
- package/docs/20260222-fix-plan-v091-routing-isolation.md +197 -0
- package/docs/20260223-fix-plan-prompt-audit.md +62 -0
- package/docs/20260224-nax-roadmap-phases.md +189 -0
- package/docs/20260225-phase2-llm-service-layer.md +401 -0
- package/docs/20260225-review-v0.10.1.md +187 -0
- package/docs/20260303-v010-implementation-plan.md +165 -0
- package/docs/CLAUDE.md.bak +191 -0
- package/docs/ROADMAP.md +165 -0
- package/docs/SPEC-rectification.md +0 -0
- package/docs/SPEC.md +324 -0
- package/docs/US-001-plugin-loading-verification.md +152 -0
- package/docs/architecture-analysis.md +1076 -0
- package/docs/bugs/BUG-21-escalation-null-attempts.md +48 -0
- package/docs/bugs-from-dogfood-run-c.md +243 -0
- package/docs/code-review-20260228.md +612 -0
- package/docs/code-review-v0.15.0.md +629 -0
- package/docs/hook-lifecycle-test-plan.md +149 -0
- package/docs/releases/v0.11.0-and-earlier.md +20 -0
- package/docs/releases/v0.12.0.md +15 -0
- package/docs/releases/v0.13.0.md +14 -0
- package/docs/releases/v0.14.0.md +20 -0
- package/docs/releases/v0.14.1.md +36 -0
- package/docs/releases/v0.14.2.md +51 -0
- package/docs/releases/v0.14.3.md +174 -0
- package/docs/releases/v0.14.4.md +94 -0
- package/docs/releases/v0.15.0.md +502 -0
- package/docs/releases/v0.15.1.md +170 -0
- package/docs/releases/v0.15.3.md +193 -0
- package/docs/specs/status-file-v0.10.1.md +812 -0
- package/docs/v0.10-global-config.md +206 -0
- package/docs/v0.10-plugin-system.md +415 -0
- package/docs/v0.10-prompt-optimizer.md +234 -0
- package/docs/v0.3-spec.md +244 -0
- package/docs/v0.4-spec.md +140 -0
- package/docs/v0.5-spec.md +237 -0
- package/docs/v0.6-spec.md +371 -0
- package/docs/v0.7-spec.md +177 -0
- package/docs/v0.8-llm-routing.md +206 -0
- package/docs/v0.8-structured-logging.md +132 -0
- package/docs/v0.9.3-prompt-audit.md +112 -0
- package/examples/plugins/console-reporter/index.test.ts +207 -0
- package/examples/plugins/console-reporter/index.ts +110 -0
- package/nax/config.json +147 -0
- package/nax/features/bugfix-v0171/prd.json +52 -0
- package/nax/features/config-management/prd.json +108 -0
- package/nax/features/config-management/progress.txt +5 -0
- package/nax/features/diagnose/acceptance.test.ts +412 -0
- package/nax/features/diagnose/prd.json +41 -0
- package/nax/features/orchestration-fixes/prd.json +89 -0
- package/nax/features/orchestration-fixes/progress.txt +1 -0
- package/nax/features/plugin-integration/US-007-VERIFICATION.md +259 -0
- package/nax/features/plugin-integration/prd.json +208 -0
- package/nax/features/plugin-integration/progress.txt +5 -0
- package/nax/features/precheck/prd.json +205 -0
- package/nax/features/precheck/progress.txt +15 -0
- package/nax/features/structured-logging/prd.json +199 -0
- package/nax/features/unlock/prd.json +36 -0
- package/package.json +47 -0
- package/src/acceptance/fix-generator.ts +348 -0
- package/src/acceptance/generator.ts +282 -0
- package/src/acceptance/index.ts +30 -0
- package/src/acceptance/types.ts +79 -0
- package/src/agents/claude-decompose.ts +169 -0
- package/src/agents/claude-plan.ts +139 -0
- package/src/agents/claude.ts +324 -0
- package/src/agents/cost.ts +268 -0
- package/src/agents/index.ts +13 -0
- package/src/agents/registry.ts +48 -0
- package/src/agents/types-extended.ts +133 -0
- package/src/agents/types.ts +113 -0
- package/src/agents/validation.ts +69 -0
- package/src/analyze/classifier.ts +305 -0
- package/src/analyze/index.ts +16 -0
- package/src/analyze/scanner.ts +175 -0
- package/src/analyze/types.ts +51 -0
- package/src/cli/accept.ts +108 -0
- package/src/cli/analyze-parser.ts +284 -0
- package/src/cli/analyze.ts +207 -0
- package/src/cli/config.ts +561 -0
- package/src/cli/constitution.ts +109 -0
- package/src/cli/diagnose-analysis.ts +159 -0
- package/src/cli/diagnose-formatter.ts +87 -0
- package/src/cli/diagnose.ts +203 -0
- package/src/cli/generate.ts +127 -0
- package/src/cli/index.ts +37 -0
- package/src/cli/init.ts +188 -0
- package/src/cli/interact.ts +295 -0
- package/src/cli/plan.ts +198 -0
- package/src/cli/plugins.ts +111 -0
- package/src/cli/prompts.ts +295 -0
- package/src/cli/runs.ts +174 -0
- package/src/cli/status-cost.ts +151 -0
- package/src/cli/status-features.ts +338 -0
- package/src/cli/status.ts +13 -0
- package/src/commands/common.ts +171 -0
- package/src/commands/diagnose.ts +17 -0
- package/src/commands/index.ts +8 -0
- package/src/commands/logs.ts +384 -0
- package/src/commands/precheck.ts +86 -0
- package/src/commands/unlock.ts +96 -0
- package/src/config/defaults.ts +160 -0
- package/src/config/index.ts +22 -0
- package/src/config/loader.ts +121 -0
- package/src/config/merger.ts +147 -0
- package/src/config/path-security.ts +121 -0
- package/src/config/paths.ts +27 -0
- package/src/config/schema.ts +56 -0
- package/src/config/schemas.ts +286 -0
- package/src/config/types.ts +423 -0
- package/src/config/validate.ts +103 -0
- package/src/constitution/generator.ts +191 -0
- package/src/constitution/generators/aider.ts +41 -0
- package/src/constitution/generators/claude.ts +35 -0
- package/src/constitution/generators/cursor.ts +36 -0
- package/src/constitution/generators/opencode.ts +38 -0
- package/src/constitution/generators/types.ts +33 -0
- package/src/constitution/generators/windsurf.ts +36 -0
- package/src/constitution/index.ts +10 -0
- package/src/constitution/loader.ts +133 -0
- package/src/constitution/types.ts +31 -0
- package/src/context/auto-detect.ts +227 -0
- package/src/context/builder.ts +246 -0
- package/src/context/elements.ts +83 -0
- package/src/context/formatter.ts +107 -0
- package/src/context/generator.ts +129 -0
- package/src/context/generators/aider.ts +34 -0
- package/src/context/generators/claude.ts +28 -0
- package/src/context/generators/cursor.ts +28 -0
- package/src/context/generators/opencode.ts +30 -0
- package/src/context/generators/windsurf.ts +28 -0
- package/src/context/greenfield.ts +114 -0
- package/src/context/index.ts +33 -0
- package/src/context/injector.ts +279 -0
- package/src/context/test-scanner.ts +370 -0
- package/src/context/types.ts +98 -0
- package/src/errors.ts +67 -0
- package/src/execution/batching.ts +157 -0
- package/src/execution/crash-recovery.ts +373 -0
- package/src/execution/escalation/escalation.ts +44 -0
- package/src/execution/escalation/index.ts +13 -0
- package/src/execution/escalation/tier-escalation.ts +295 -0
- package/src/execution/escalation/tier-outcome.ts +158 -0
- package/src/execution/helpers.ts +38 -0
- package/src/execution/index.ts +45 -0
- package/src/execution/lifecycle/acceptance-loop.ts +272 -0
- package/src/execution/lifecycle/headless-formatter.ts +85 -0
- package/src/execution/lifecycle/index.ts +12 -0
- package/src/execution/lifecycle/parallel-lifecycle.ts +101 -0
- package/src/execution/lifecycle/precheck-runner.ts +140 -0
- package/src/execution/lifecycle/run-cleanup.ts +81 -0
- package/src/execution/lifecycle/run-completion.ts +129 -0
- package/src/execution/lifecycle/run-initialization.ts +141 -0
- package/src/execution/lifecycle/run-lifecycle.ts +312 -0
- package/src/execution/lifecycle/run-setup.ts +204 -0
- package/src/execution/lifecycle/story-hooks.ts +38 -0
- package/src/execution/lifecycle/story-size-prompts.ts +123 -0
- package/src/execution/lock.ts +115 -0
- package/src/execution/parallel-executor.ts +216 -0
- package/src/execution/parallel.ts +400 -0
- package/src/execution/pid-registry.ts +280 -0
- package/src/execution/pipeline-result-handler.ts +388 -0
- package/src/execution/post-verify-rectification.ts +188 -0
- package/src/execution/post-verify.ts +274 -0
- package/src/execution/progress.ts +25 -0
- package/src/execution/prompts.ts +127 -0
- package/src/execution/queue-handler.ts +109 -0
- package/src/execution/rectification.ts +13 -0
- package/src/execution/runner.ts +377 -0
- package/src/execution/sequential-executor.ts +388 -0
- package/src/execution/status-file.ts +264 -0
- package/src/execution/status-writer.ts +139 -0
- package/src/execution/story-context.ts +229 -0
- package/src/execution/test-output-parser.ts +14 -0
- package/src/execution/verification.ts +72 -0
- package/src/hooks/index.ts +2 -0
- package/src/hooks/runner.ts +286 -0
- package/src/hooks/types.ts +67 -0
- package/src/interaction/chain.ts +154 -0
- package/src/interaction/index.ts +60 -0
- package/src/interaction/init.ts +83 -0
- package/src/interaction/plugins/auto.ts +217 -0
- package/src/interaction/plugins/cli.ts +300 -0
- package/src/interaction/plugins/telegram.ts +384 -0
- package/src/interaction/plugins/webhook.ts +258 -0
- package/src/interaction/state.ts +171 -0
- package/src/interaction/triggers.ts +229 -0
- package/src/interaction/types.ts +163 -0
- package/src/logger/formatters.ts +84 -0
- package/src/logger/index.ts +16 -0
- package/src/logger/logger.ts +298 -0
- package/src/logger/types.ts +48 -0
- package/src/logging/formatter.ts +355 -0
- package/src/logging/index.ts +22 -0
- package/src/logging/types.ts +93 -0
- package/src/metrics/aggregator.ts +190 -0
- package/src/metrics/index.ts +14 -0
- package/src/metrics/tracker.ts +200 -0
- package/src/metrics/types.ts +109 -0
- package/src/optimizer/index.ts +62 -0
- package/src/optimizer/noop.optimizer.ts +24 -0
- package/src/optimizer/rule-based.optimizer.ts +248 -0
- package/src/optimizer/types.ts +53 -0
- package/src/pipeline/events.ts +130 -0
- package/src/pipeline/index.ts +19 -0
- package/src/pipeline/runner.ts +161 -0
- package/src/pipeline/stages/acceptance.ts +197 -0
- package/src/pipeline/stages/completion.ts +99 -0
- package/src/pipeline/stages/constitution.ts +63 -0
- package/src/pipeline/stages/context.ts +117 -0
- package/src/pipeline/stages/execution.ts +194 -0
- package/src/pipeline/stages/index.ts +62 -0
- package/src/pipeline/stages/optimizer.ts +74 -0
- package/src/pipeline/stages/prompt.ts +57 -0
- package/src/pipeline/stages/queue-check.ts +103 -0
- package/src/pipeline/stages/review.ts +181 -0
- package/src/pipeline/stages/routing.ts +81 -0
- package/src/pipeline/stages/verify.ts +100 -0
- package/src/pipeline/types.ts +167 -0
- package/src/plugins/index.ts +31 -0
- package/src/plugins/loader.ts +287 -0
- package/src/plugins/registry.ts +168 -0
- package/src/plugins/types.ts +327 -0
- package/src/plugins/validator.ts +352 -0
- package/src/prd/index.ts +172 -0
- package/src/prd/types.ts +202 -0
- package/src/precheck/checks-blockers.ts +391 -0
- package/src/precheck/checks-warnings.ts +142 -0
- package/src/precheck/checks.ts +30 -0
- package/src/precheck/index.ts +247 -0
- package/src/precheck/story-size-gate.ts +144 -0
- package/src/precheck/types.ts +31 -0
- package/src/queue/index.ts +2 -0
- package/src/queue/manager.ts +254 -0
- package/src/queue/types.ts +54 -0
- package/src/review/index.ts +8 -0
- package/src/review/runner.ts +172 -0
- package/src/review/types.ts +66 -0
- package/src/routing/builder.ts +81 -0
- package/src/routing/chain.ts +74 -0
- package/src/routing/index.ts +16 -0
- package/src/routing/loader.ts +58 -0
- package/src/routing/router.ts +303 -0
- package/src/routing/strategies/adaptive.ts +215 -0
- package/src/routing/strategies/index.ts +8 -0
- package/src/routing/strategies/keyword.ts +163 -0
- package/src/routing/strategies/llm-prompts.ts +209 -0
- package/src/routing/strategies/llm.ts +235 -0
- package/src/routing/strategies/manual.ts +50 -0
- package/src/routing/strategy.ts +99 -0
- package/src/tdd/cleanup.ts +111 -0
- package/src/tdd/index.ts +23 -0
- package/src/tdd/isolation.ts +123 -0
- package/src/tdd/orchestrator.ts +383 -0
- package/src/tdd/prompts.ts +270 -0
- package/src/tdd/rectification-gate.ts +183 -0
- package/src/tdd/session-runner.ts +179 -0
- package/src/tdd/types.ts +81 -0
- package/src/tdd/verdict.ts +271 -0
- package/src/tui/App.tsx +265 -0
- package/src/tui/components/AgentPanel.tsx +75 -0
- package/src/tui/components/CostOverlay.tsx +118 -0
- package/src/tui/components/HelpOverlay.tsx +107 -0
- package/src/tui/components/StatusBar.tsx +63 -0
- package/src/tui/components/StoriesPanel.tsx +177 -0
- package/src/tui/hooks/useKeyboard.ts +142 -0
- package/src/tui/hooks/useLayout.ts +137 -0
- package/src/tui/hooks/usePipelineEvents.ts +183 -0
- package/src/tui/hooks/usePty.ts +194 -0
- package/src/tui/index.tsx +38 -0
- package/src/tui/types.ts +76 -0
- package/src/utils/git.ts +83 -0
- package/src/utils/queue-writer.ts +54 -0
- package/src/verification/executor.ts +235 -0
- package/src/verification/gate.ts +207 -0
- package/src/verification/index.ts +12 -0
- package/src/verification/parser.ts +230 -0
- package/src/verification/rectification.ts +108 -0
- package/src/verification/types.ts +113 -0
- package/src/worktree/dispatcher.ts +65 -0
- package/src/worktree/index.ts +2 -0
- package/src/worktree/manager.ts +187 -0
- package/src/worktree/merge.ts +301 -0
- package/src/worktree/types.ts +4 -0
- package/test/TEST_COVERAGE_US001.md +217 -0
- package/test/TEST_COVERAGE_US003.md +84 -0
- package/test/TEST_COVERAGE_US005.md +86 -0
- package/test/US-002-orchestrator.test.ts +246 -0
- package/test/acceptance/cm-003-default-view.test.ts +194 -0
- package/test/execution/pid-registry.test.ts +240 -0
- package/test/execution/post-verify.test.ts +224 -0
- package/test/helpers/timeout.ts +42 -0
- package/test/integration/US-002-TEST-SUMMARY.md +107 -0
- package/test/integration/US-003-TEST-SUMMARY.md +149 -0
- package/test/integration/US-004-TEST-SUMMARY.md +106 -0
- package/test/integration/US-005-TEST-SUMMARY.md +138 -0
- package/test/integration/US-007-TEST-SUMMARY.md +100 -0
- package/test/integration/agent-validation.test.ts +439 -0
- package/test/integration/analyze-integration.test.ts +261 -0
- package/test/integration/analyze-scanner.test.ts +131 -0
- package/test/integration/cli-config-default-edge-cases.test.ts +222 -0
- package/test/integration/cli-config-default-view.test.ts +229 -0
- package/test/integration/cli-config-diff.test.ts +460 -0
- package/test/integration/cli-config.test.ts +736 -0
- package/test/integration/cli-diagnose.test.ts +592 -0
- package/test/integration/cli-logs.test.ts +314 -0
- package/test/integration/cli-plugins.test.ts +678 -0
- package/test/integration/cli-precheck.test.ts +371 -0
- package/test/integration/cli-run-headless.test.ts +173 -0
- package/test/integration/cli.test.ts +75 -0
- package/test/integration/config/merger.test.ts +465 -0
- package/test/integration/config/paths.test.ts +51 -0
- package/test/integration/config-loader.test.ts +265 -0
- package/test/integration/config.test.ts +444 -0
- package/test/integration/context-integration.test.ts +702 -0
- package/test/integration/context-provider-injection.test.ts +506 -0
- package/test/integration/context-verification-integration.test.ts +295 -0
- package/test/integration/e2e.test.ts +896 -0
- package/test/integration/execution.test.ts +625 -0
- package/test/integration/helpers.test.ts +295 -0
- package/test/integration/hooks.test.ts +361 -0
- package/test/integration/interaction-chain-pipeline.test.ts +464 -0
- package/test/integration/isolation.test.ts +143 -0
- package/test/integration/logger.test.ts +461 -0
- package/test/integration/parallel.test.ts +250 -0
- package/test/integration/path-security.test.ts +173 -0
- package/test/integration/pipeline-acceptance.test.ts +302 -0
- package/test/integration/pipeline-events.test.ts +475 -0
- package/test/integration/pipeline.test.ts +658 -0
- package/test/integration/plan.test.ts +157 -0
- package/test/integration/plugin-routing.test.ts +921 -0
- package/test/integration/plugins/config-integration.test.ts +172 -0
- package/test/integration/plugins/config-resolution.test.ts +522 -0
- package/test/integration/plugins/loader.test.ts +641 -0
- package/test/integration/plugins/registry.test.ts +746 -0
- package/test/integration/plugins/validator.test.ts +563 -0
- package/test/integration/prd-pause.test.ts +205 -0
- package/test/integration/prd-resolvers.test.ts +185 -0
- package/test/integration/precheck-integration.test.ts +468 -0
- package/test/integration/precheck.test.ts +805 -0
- package/test/integration/progress.test.ts +34 -0
- package/test/integration/rectification-flow.test.ts +512 -0
- package/test/integration/reporter-lifecycle.test.ts +860 -0
- package/test/integration/review-config-commands.test.ts +319 -0
- package/test/integration/review-config-schema.test.ts +116 -0
- package/test/integration/review-plugin-integration.test.ts +722 -0
- package/test/integration/review.test.ts +149 -0
- package/test/integration/routing-stage-bug-021.test.ts +274 -0
- package/test/integration/routing-stage-greenfield.test.ts +286 -0
- package/test/integration/runner-config-plugins.test.ts +461 -0
- package/test/integration/runner-fixes.test.ts +399 -0
- package/test/integration/runner-plugin-integration.test.ts +543 -0
- package/test/integration/runner.test.ts +1679 -0
- package/test/integration/s5-greenfield-fallback.test.ts +297 -0
- package/test/integration/status-file-integration.test.ts +325 -0
- package/test/integration/status-file.test.ts +379 -0
- package/test/integration/status-writer.test.ts +345 -0
- package/test/integration/story-id-in-events.test.ts +273 -0
- package/test/integration/tdd-cleanup.test.ts +246 -0
- package/test/integration/tdd-orchestrator.test.ts +1762 -0
- package/test/integration/test-scanner.test.ts +403 -0
- package/test/integration/verification-asset-check.test.ts +142 -0
- package/test/integration/verify-stage.test.ts +275 -0
- package/test/integration/worktree/manager.test.ts +218 -0
- package/test/integration/worktree/merge.test.ts +341 -0
- package/test/manual/logging-formatter-demo.ts +158 -0
- package/test/ui/tui-agent-panel.test.tsx +99 -0
- package/test/ui/tui-controls.test.ts +334 -0
- package/test/ui/tui-cost-and-pty.test.ts +189 -0
- package/test/ui/tui-layout.test.ts +378 -0
- package/test/ui/tui-pty-integration.test.tsx +159 -0
- package/test/ui/tui-stories.test.ts +332 -0
- package/test/unit/acceptance.test.ts +186 -0
- package/test/unit/agent-stderr-capture.test.ts +146 -0
- package/test/unit/analyze-classifier.test.ts +215 -0
- package/test/unit/analyze.test.ts +224 -0
- package/test/unit/auto-detect.test.ts +249 -0
- package/test/unit/cli-status.test.ts +417 -0
- package/test/unit/commands/common.test.ts +320 -0
- package/test/unit/commands/logs.test.ts +416 -0
- package/test/unit/commands/unlock.test.ts +319 -0
- package/test/unit/constitution-generators.test.ts +160 -0
- package/test/unit/constitution.test.ts +209 -0
- package/test/unit/context.test.ts +1722 -0
- package/test/unit/cost.test.ts +231 -0
- package/test/unit/crash-recovery.test.ts +308 -0
- package/test/unit/escalation.test.ts +126 -0
- package/test/unit/execution-logging-stderr.test.ts +156 -0
- package/test/unit/execution-stage.test.ts +122 -0
- package/test/unit/fix-generator.test.ts +275 -0
- package/test/unit/formatters.test.ts +469 -0
- package/test/unit/greenfield.test.ts +179 -0
- package/test/unit/helpers.test.ts +317 -0
- package/test/unit/interaction/human-review-trigger.test.ts +164 -0
- package/test/unit/interaction-network-failures.test.ts +389 -0
- package/test/unit/interaction-plugins.test.ts +164 -0
- package/test/unit/isolation.test.ts +134 -0
- package/test/unit/logging/formatter.test.ts +455 -0
- package/test/unit/merge.test.ts +268 -0
- package/test/unit/metrics.test.ts +276 -0
- package/test/unit/optimizer/noop.optimizer.test.ts +125 -0
- package/test/unit/optimizer/rule-based.optimizer.test.ts +358 -0
- package/test/unit/prd-auto-default.test.ts +290 -0
- package/test/unit/prd-failure-category.test.ts +176 -0
- package/test/unit/prd-get-next-story.test.ts +186 -0
- package/test/unit/precheck-checks.test.ts +840 -0
- package/test/unit/precheck-story-size-gate.test.ts +287 -0
- package/test/unit/precheck-types.test.ts +142 -0
- package/test/unit/prompts.test.ts +475 -0
- package/test/unit/queue.test.ts +237 -0
- package/test/unit/rectification.test.ts +284 -0
- package/test/unit/registry.test.ts +287 -0
- package/test/unit/routing.test.ts +937 -0
- package/test/unit/run-lifecycle.test.ts +140 -0
- package/test/unit/storyid-events.test.ts +224 -0
- package/test/unit/tdd-verdict.test.ts +492 -0
- package/test/unit/test-output-parser.test.ts +377 -0
- package/test/unit/verdict.test.ts +324 -0
- package/test/unit/worktree-manager.test.ts +158 -0
- package/tsconfig.json +27 -0
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pipeline Framework Types
|
|
3
|
+
*
|
|
4
|
+
* Composable stage-based execution pipeline for refactoring the monolithic runner.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { AgentResult } from "../agents/types";
|
|
8
|
+
import type { NaxConfig } from "../config/schema";
|
|
9
|
+
import type { ConstitutionResult } from "../constitution/types";
|
|
10
|
+
import type { BuiltContext } from "../context/types";
|
|
11
|
+
import type { HooksConfig } from "../hooks/types";
|
|
12
|
+
import type { InteractionChain } from "../interaction/chain";
|
|
13
|
+
import type { StoryMetrics } from "../metrics/types";
|
|
14
|
+
import type { PluginRegistry } from "../plugins/registry";
|
|
15
|
+
import type { PRD, UserStory } from "../prd/types";
|
|
16
|
+
import type { ReviewResult } from "../review/types";
|
|
17
|
+
import type { FailureCategory } from "../tdd/types";
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Routing result from complexity classification
|
|
21
|
+
*/
|
|
22
|
+
export interface RoutingResult {
|
|
23
|
+
/** Classified complexity */
|
|
24
|
+
complexity: "simple" | "medium" | "complex" | "expert";
|
|
25
|
+
/** Selected model tier */
|
|
26
|
+
modelTier: "fast" | "balanced" | "powerful";
|
|
27
|
+
/** Test strategy */
|
|
28
|
+
testStrategy: "test-after" | "three-session-tdd" | "three-session-tdd-lite";
|
|
29
|
+
/** Reasoning for the classification */
|
|
30
|
+
reasoning: string;
|
|
31
|
+
/** Estimated cost for this story */
|
|
32
|
+
estimatedCost?: number;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Pipeline context — shared state passed through all stages.
|
|
37
|
+
*
|
|
38
|
+
* Stages read from and write to this context. It accumulates data
|
|
39
|
+
* as the pipeline progresses through each stage.
|
|
40
|
+
*
|
|
41
|
+
* @example
|
|
42
|
+
* ```ts
|
|
43
|
+
* const ctx: PipelineContext = {
|
|
44
|
+
* config: loadedConfig,
|
|
45
|
+
* prd: loadedPRD,
|
|
46
|
+
* story: currentStory,
|
|
47
|
+
* stories: [currentStory],
|
|
48
|
+
* routing: { complexity: "simple", modelTier: "fast", ... },
|
|
49
|
+
* workdir: "/home/user/project",
|
|
50
|
+
* hooks: loadedHooks,
|
|
51
|
+
* };
|
|
52
|
+
* ```
|
|
53
|
+
*/
|
|
54
|
+
export interface PipelineContext {
|
|
55
|
+
/** Ngent configuration */
|
|
56
|
+
config: NaxConfig;
|
|
57
|
+
/** Full PRD document */
|
|
58
|
+
prd: PRD;
|
|
59
|
+
/** Current story (or batch leader) */
|
|
60
|
+
story: UserStory;
|
|
61
|
+
/** Batch of stories (length 1 for single-story execution) */
|
|
62
|
+
stories: UserStory[];
|
|
63
|
+
/** Routing result from complexity classification */
|
|
64
|
+
routing: RoutingResult;
|
|
65
|
+
/** Working directory (project root) */
|
|
66
|
+
workdir: string;
|
|
67
|
+
/** Feature directory (optional, e.g., nax/features/my-feature/) */
|
|
68
|
+
featureDir?: string;
|
|
69
|
+
/** Hooks configuration */
|
|
70
|
+
hooks: HooksConfig;
|
|
71
|
+
/** Plugin registry (optional, for plugin-provided extensions) */
|
|
72
|
+
plugins?: PluginRegistry;
|
|
73
|
+
/** Interaction chain (optional, for human-in-the-loop triggers) */
|
|
74
|
+
interaction?: InteractionChain;
|
|
75
|
+
/** Constitution result (set by constitutionStage) */
|
|
76
|
+
constitution?: ConstitutionResult;
|
|
77
|
+
/** Context markdown for the agent (set by contextStage) */
|
|
78
|
+
contextMarkdown?: string;
|
|
79
|
+
/** Built context with element-level token tracking (set by contextStage) */
|
|
80
|
+
builtContext?: BuiltContext;
|
|
81
|
+
/** Final prompt sent to agent (set by promptStage) */
|
|
82
|
+
prompt?: string;
|
|
83
|
+
/** Agent execution result (set by executionStage) */
|
|
84
|
+
agentResult?: AgentResult;
|
|
85
|
+
/** Review result (set by reviewStage) */
|
|
86
|
+
reviewResult?: ReviewResult;
|
|
87
|
+
/** Acceptance test failures (set by acceptanceStage) */
|
|
88
|
+
acceptanceFailures?: {
|
|
89
|
+
failedACs: string[];
|
|
90
|
+
testOutput: string;
|
|
91
|
+
};
|
|
92
|
+
/** Story start timestamp (ISO string, set by runner before pipeline) */
|
|
93
|
+
storyStartTime?: string;
|
|
94
|
+
/** Collected story metrics (set by completionStage) */
|
|
95
|
+
storyMetrics?: StoryMetrics[];
|
|
96
|
+
/** Whether to retry the story in lite mode after a failure */
|
|
97
|
+
retryAsLite?: boolean;
|
|
98
|
+
/** Failure category from TDD orchestrator (set by executionStage on TDD failure) */
|
|
99
|
+
tddFailureCategory?: FailureCategory;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Stage action — determines how the pipeline proceeds after a stage executes.
|
|
104
|
+
*/
|
|
105
|
+
export type StageAction =
|
|
106
|
+
/** Continue to the next stage */
|
|
107
|
+
| { action: "continue"; cost?: number }
|
|
108
|
+
/** Skip this story (mark as skipped, don't run further stages) */
|
|
109
|
+
| { action: "skip"; reason: string; cost?: number }
|
|
110
|
+
/** Mark story as failed (don't run further stages) */
|
|
111
|
+
| { action: "fail"; reason: string; cost?: number }
|
|
112
|
+
/** Escalate to a higher tier and retry the pipeline */
|
|
113
|
+
| { action: "escalate"; reason?: string; cost?: number }
|
|
114
|
+
/** Pause execution (user intervention required via queue command) */
|
|
115
|
+
| { action: "pause"; reason: string; cost?: number };
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Result returned by a pipeline stage after execution.
|
|
119
|
+
*/
|
|
120
|
+
export type StageResult = StageAction;
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* A single pipeline stage.
|
|
124
|
+
*
|
|
125
|
+
* Stages are composable units of work that execute sequentially.
|
|
126
|
+
* Each stage can read from and modify the pipeline context, then
|
|
127
|
+
* return an action that determines whether to continue, skip, fail,
|
|
128
|
+
* escalate, or pause.
|
|
129
|
+
*
|
|
130
|
+
* @example
|
|
131
|
+
* ```ts
|
|
132
|
+
* const routingStage: PipelineStage = {
|
|
133
|
+
* name: "routing",
|
|
134
|
+
* enabled: (ctx) => true,
|
|
135
|
+
* execute: async (ctx) => {
|
|
136
|
+
* const result = await classifyComplexity(ctx.story);
|
|
137
|
+
* ctx.routing = result;
|
|
138
|
+
* return { action: "continue" };
|
|
139
|
+
* },
|
|
140
|
+
* };
|
|
141
|
+
* ```
|
|
142
|
+
*/
|
|
143
|
+
export interface PipelineStage {
|
|
144
|
+
/** Unique stage identifier (e.g., "routing", "execution", "review") */
|
|
145
|
+
name: string;
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Determines if this stage should run.
|
|
149
|
+
*
|
|
150
|
+
* If false, the stage is skipped and the pipeline continues to the next stage.
|
|
151
|
+
*
|
|
152
|
+
* @param ctx - Current pipeline context
|
|
153
|
+
* @returns true if the stage should execute, false to skip
|
|
154
|
+
*/
|
|
155
|
+
enabled: (ctx: PipelineContext) => boolean;
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Execute the stage logic.
|
|
159
|
+
*
|
|
160
|
+
* Can read from and modify the pipeline context, then returns a result
|
|
161
|
+
* that determines how the pipeline should proceed.
|
|
162
|
+
*
|
|
163
|
+
* @param ctx - Current pipeline context
|
|
164
|
+
* @returns Stage result indicating next action
|
|
165
|
+
*/
|
|
166
|
+
execute: (ctx: PipelineContext) => Promise<StageResult>;
|
|
167
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Plugin System — Public API
|
|
3
|
+
*
|
|
4
|
+
* Exports all plugin types, interfaces, and loading utilities.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
export type {
|
|
8
|
+
NaxPlugin,
|
|
9
|
+
PluginType,
|
|
10
|
+
PluginExtensions,
|
|
11
|
+
PluginConfigEntry,
|
|
12
|
+
IReviewPlugin,
|
|
13
|
+
ReviewCheckResult,
|
|
14
|
+
IContextProvider,
|
|
15
|
+
ContextProviderResult,
|
|
16
|
+
IReporter,
|
|
17
|
+
RunStartEvent,
|
|
18
|
+
StoryCompleteEvent,
|
|
19
|
+
RunEndEvent,
|
|
20
|
+
} from "./types";
|
|
21
|
+
|
|
22
|
+
// Re-export optimizer types from optimizer module (via types.ts)
|
|
23
|
+
export type {
|
|
24
|
+
IPromptOptimizer,
|
|
25
|
+
PromptOptimizerInput,
|
|
26
|
+
PromptOptimizerResult,
|
|
27
|
+
} from "./types";
|
|
28
|
+
|
|
29
|
+
export { validatePlugin } from "./validator";
|
|
30
|
+
export { loadPlugins } from "./loader";
|
|
31
|
+
export { PluginRegistry } from "./registry";
|
|
@@ -0,0 +1,287 @@
|
|
|
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 { PluginRegistry } from "./registry";
|
|
14
|
+
import type { NaxPlugin, PluginConfigEntry } from "./types";
|
|
15
|
+
import { validatePlugin } from "./validator";
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Swappable error sink — defaults to console.error.
|
|
19
|
+
* Tests can replace this to capture plugin error output.
|
|
20
|
+
* @internal
|
|
21
|
+
*/
|
|
22
|
+
export let _pluginErrorSink: (...args: unknown[]) => void = (...args) => console.error(...args);
|
|
23
|
+
|
|
24
|
+
/** @internal — for testing only */
|
|
25
|
+
export function _setPluginErrorSink(fn: (...args: unknown[]) => void): void {
|
|
26
|
+
_pluginErrorSink = fn;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/** @internal — reset to default */
|
|
30
|
+
export function _resetPluginErrorSink(): void {
|
|
31
|
+
_pluginErrorSink = (...args) => console.error(...args);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Safely get logger instance, returns null if not initialized.
|
|
36
|
+
* Delegates to the module's getSafeLogger which correctly returns null for noopLogger.
|
|
37
|
+
*/
|
|
38
|
+
function getSafeLogger() {
|
|
39
|
+
return _getSafeLoggerFromModule();
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Plugin source metadata.
|
|
44
|
+
*/
|
|
45
|
+
export interface PluginSource {
|
|
46
|
+
type: "global" | "project" | "config";
|
|
47
|
+
path: string;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Plugin with source information.
|
|
52
|
+
*/
|
|
53
|
+
export interface LoadedPlugin {
|
|
54
|
+
plugin: NaxPlugin;
|
|
55
|
+
source: PluginSource;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Load and validate all plugins from global + project + config sources.
|
|
60
|
+
*
|
|
61
|
+
* Load order:
|
|
62
|
+
* 1. Scan ~/.nax/plugins/ (if exists)
|
|
63
|
+
* 2. Scan <project>/nax/plugins/ (if exists)
|
|
64
|
+
* 3. Load explicit modules from config.plugins[]
|
|
65
|
+
*
|
|
66
|
+
* Each plugin is validated, then setup() is called with its config.
|
|
67
|
+
*
|
|
68
|
+
* @param globalDir - Global plugins directory (e.g., ~/.nax/plugins)
|
|
69
|
+
* @param projectDir - Project plugins directory (e.g., <project>/nax/plugins)
|
|
70
|
+
* @param configPlugins - Explicit plugin entries from config
|
|
71
|
+
* @param projectRoot - Project root directory for resolving relative paths in config
|
|
72
|
+
* @returns PluginRegistry with all loaded plugins and their sources
|
|
73
|
+
*/
|
|
74
|
+
export async function loadPlugins(
|
|
75
|
+
globalDir: string,
|
|
76
|
+
projectDir: string,
|
|
77
|
+
configPlugins: PluginConfigEntry[],
|
|
78
|
+
projectRoot?: string,
|
|
79
|
+
): Promise<PluginRegistry> {
|
|
80
|
+
const loadedPlugins: LoadedPlugin[] = [];
|
|
81
|
+
const pluginNames = new Set<string>();
|
|
82
|
+
|
|
83
|
+
// 1. Load plugins from global directory
|
|
84
|
+
const globalPlugins = await discoverPlugins(globalDir);
|
|
85
|
+
for (const plugin of globalPlugins) {
|
|
86
|
+
const validated = await loadAndValidatePlugin(plugin.path, {});
|
|
87
|
+
if (validated) {
|
|
88
|
+
if (pluginNames.has(validated.name)) {
|
|
89
|
+
const logger = getSafeLogger();
|
|
90
|
+
logger?.warn("plugins", `Plugin name collision: '${validated.name}' (global directory)`);
|
|
91
|
+
}
|
|
92
|
+
loadedPlugins.push({
|
|
93
|
+
plugin: validated,
|
|
94
|
+
source: { type: "global", path: plugin.path },
|
|
95
|
+
});
|
|
96
|
+
pluginNames.add(validated.name);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// 2. Load plugins from project directory
|
|
101
|
+
const projectPlugins = await discoverPlugins(projectDir);
|
|
102
|
+
for (const plugin of projectPlugins) {
|
|
103
|
+
const validated = await loadAndValidatePlugin(plugin.path, {});
|
|
104
|
+
if (validated) {
|
|
105
|
+
if (pluginNames.has(validated.name)) {
|
|
106
|
+
const logger = getSafeLogger();
|
|
107
|
+
logger?.warn("plugins", `Plugin name collision: '${validated.name}' (project directory overrides global)`);
|
|
108
|
+
}
|
|
109
|
+
loadedPlugins.push({
|
|
110
|
+
plugin: validated,
|
|
111
|
+
source: { type: "project", path: plugin.path },
|
|
112
|
+
});
|
|
113
|
+
pluginNames.add(validated.name);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// 3. Load plugins from config entries
|
|
118
|
+
for (const entry of configPlugins) {
|
|
119
|
+
// Resolve module path relative to project root for relative paths
|
|
120
|
+
const resolvedModule = resolveModulePath(entry.module, projectRoot);
|
|
121
|
+
const validated = await loadAndValidatePlugin(resolvedModule, entry.config ?? {}, entry.module);
|
|
122
|
+
if (validated) {
|
|
123
|
+
if (pluginNames.has(validated.name)) {
|
|
124
|
+
const logger = getSafeLogger();
|
|
125
|
+
logger?.warn("plugins", `Plugin name collision: '${validated.name}' (config entry overrides previous)`);
|
|
126
|
+
}
|
|
127
|
+
loadedPlugins.push({
|
|
128
|
+
plugin: validated,
|
|
129
|
+
source: { type: "config", path: entry.module },
|
|
130
|
+
});
|
|
131
|
+
pluginNames.add(validated.name);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
return new PluginRegistry(loadedPlugins);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Discover plugin files in a directory.
|
|
140
|
+
*
|
|
141
|
+
* Scans for:
|
|
142
|
+
* - Single-file plugins (*.ts, *.js, *.mjs)
|
|
143
|
+
* - Directory plugins with index.ts/index.js/index.mjs
|
|
144
|
+
*
|
|
145
|
+
* @param dir - Directory to scan
|
|
146
|
+
* @returns Array of discovered plugin paths
|
|
147
|
+
*/
|
|
148
|
+
async function discoverPlugins(dir: string): Promise<Array<{ path: string }>> {
|
|
149
|
+
const discovered: Array<{ path: string }> = [];
|
|
150
|
+
|
|
151
|
+
try {
|
|
152
|
+
const entries = await fs.readdir(dir, { withFileTypes: true });
|
|
153
|
+
|
|
154
|
+
for (const entry of entries) {
|
|
155
|
+
const fullPath = path.join(dir, entry.name);
|
|
156
|
+
|
|
157
|
+
if (entry.isFile()) {
|
|
158
|
+
// Single-file plugin
|
|
159
|
+
if (isPluginFile(entry.name)) {
|
|
160
|
+
discovered.push({ path: fullPath });
|
|
161
|
+
}
|
|
162
|
+
} else if (entry.isDirectory()) {
|
|
163
|
+
// Directory plugin — check for index file
|
|
164
|
+
const indexPaths = ["index.ts", "index.js", "index.mjs"];
|
|
165
|
+
for (const indexFile of indexPaths) {
|
|
166
|
+
const indexPath = path.join(fullPath, indexFile);
|
|
167
|
+
try {
|
|
168
|
+
await fs.access(indexPath);
|
|
169
|
+
discovered.push({ path: indexPath });
|
|
170
|
+
break;
|
|
171
|
+
} catch {
|
|
172
|
+
// Index file doesn't exist, try next
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
} catch (error) {
|
|
178
|
+
// ERR-1 fix: Only catch ENOENT, re-throw other errors
|
|
179
|
+
if ((error as NodeJS.ErrnoException).code === "ENOENT") {
|
|
180
|
+
// Directory doesn't exist — not an error, just no plugins
|
|
181
|
+
return [];
|
|
182
|
+
}
|
|
183
|
+
// Re-throw permission errors, disk failures, etc.
|
|
184
|
+
throw error;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
return discovered;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* Check if a filename is a valid plugin file.
|
|
192
|
+
*
|
|
193
|
+
* @param filename - Filename to check
|
|
194
|
+
* @returns Whether the file could be a plugin
|
|
195
|
+
*/
|
|
196
|
+
function isPluginFile(filename: string): boolean {
|
|
197
|
+
return /\.(ts|js|mjs)$/.test(filename) && !filename.endsWith(".test.ts") && !filename.endsWith(".spec.ts");
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* Resolve a module path, handling relative paths, absolute paths, and npm packages.
|
|
202
|
+
*
|
|
203
|
+
* @param modulePath - Module path from config (can be relative, absolute, or npm package)
|
|
204
|
+
* @param projectRoot - Project root directory for resolving relative paths
|
|
205
|
+
* @returns Resolved absolute path or npm package name
|
|
206
|
+
*/
|
|
207
|
+
function resolveModulePath(modulePath: string, projectRoot?: string): string {
|
|
208
|
+
// Absolute paths and npm packages (no leading ./ or ../) work as-is
|
|
209
|
+
if (path.isAbsolute(modulePath) || (!modulePath.startsWith("./") && !modulePath.startsWith("../"))) {
|
|
210
|
+
return modulePath;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// Relative paths need to be resolved relative to project root
|
|
214
|
+
if (projectRoot) {
|
|
215
|
+
return path.resolve(projectRoot, modulePath);
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
// Fallback: resolve relative to cwd (shouldn't happen in normal usage)
|
|
219
|
+
return path.resolve(modulePath);
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
/**
|
|
223
|
+
* Load and validate a plugin from a module path.
|
|
224
|
+
*
|
|
225
|
+
* @param modulePath - Path to plugin module (should be resolved)
|
|
226
|
+
* @param config - Plugin-specific config
|
|
227
|
+
* @param originalPath - Original path from config (for error messages)
|
|
228
|
+
* @returns Validated plugin or null if invalid
|
|
229
|
+
*/
|
|
230
|
+
async function loadAndValidatePlugin(
|
|
231
|
+
modulePath: string,
|
|
232
|
+
config: Record<string, unknown>,
|
|
233
|
+
originalPath?: string,
|
|
234
|
+
): Promise<NaxPlugin | null> {
|
|
235
|
+
try {
|
|
236
|
+
// Import the module
|
|
237
|
+
const imported = await import(modulePath);
|
|
238
|
+
|
|
239
|
+
// Try default export first, then named exports
|
|
240
|
+
const module = imported.default || imported;
|
|
241
|
+
|
|
242
|
+
// Validate plugin shape
|
|
243
|
+
const validated = validatePlugin(module);
|
|
244
|
+
if (!validated) {
|
|
245
|
+
return null;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
// Call setup() if defined
|
|
249
|
+
if (validated.setup) {
|
|
250
|
+
try {
|
|
251
|
+
await validated.setup(config);
|
|
252
|
+
} catch (error) {
|
|
253
|
+
const logger = getSafeLogger();
|
|
254
|
+
logger?.error("plugins", `Plugin '${validated.name}' setup failed`, { error });
|
|
255
|
+
return null;
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
return validated;
|
|
260
|
+
} catch (error) {
|
|
261
|
+
const displayPath = originalPath || modulePath;
|
|
262
|
+
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
263
|
+
const logger = getSafeLogger();
|
|
264
|
+
|
|
265
|
+
// Provide helpful error message with attempted paths
|
|
266
|
+
if (errorMsg.includes("Cannot find module") || errorMsg.includes("ENOENT")) {
|
|
267
|
+
const msg = `Failed to load plugin module '${displayPath}'`;
|
|
268
|
+
logger?.error("plugins", msg);
|
|
269
|
+
logger?.error("plugins", `Attempted path: ${modulePath}`);
|
|
270
|
+
logger?.error(
|
|
271
|
+
"plugins",
|
|
272
|
+
"Ensure the module exists and the path is correct (relative paths are resolved from project root)",
|
|
273
|
+
);
|
|
274
|
+
// Always emit to sink so tests (and headless mode without logger) can capture output
|
|
275
|
+
_pluginErrorSink(`[plugins] ${msg}`);
|
|
276
|
+
_pluginErrorSink(`[plugins] Attempted path: ${modulePath}`);
|
|
277
|
+
_pluginErrorSink(
|
|
278
|
+
"[plugins] Ensure the module exists and the path is correct (relative paths are resolved from project root)",
|
|
279
|
+
);
|
|
280
|
+
} else {
|
|
281
|
+
logger?.warn("plugins", `Failed to load plugin from '${displayPath}'`, { error: errorMsg });
|
|
282
|
+
// Always emit to sink
|
|
283
|
+
_pluginErrorSink(`[plugins] Failed to load plugin from '${displayPath}': ${errorMsg}`);
|
|
284
|
+
}
|
|
285
|
+
return null;
|
|
286
|
+
}
|
|
287
|
+
}
|
|
@@ -0,0 +1,168 @@
|
|
|
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
|
+
}
|