@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,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Queue Types
|
|
3
|
+
*
|
|
4
|
+
* Priority queue for scheduling user stories across agents.
|
|
5
|
+
* Enables future parallel agent execution.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
/** Queue item status */
|
|
9
|
+
export type QueueItemStatus = "pending" | "in-progress" | "completed" | "failed" | "skipped";
|
|
10
|
+
|
|
11
|
+
/** Queue item representing a user story to be executed */
|
|
12
|
+
export interface QueueItem {
|
|
13
|
+
/** Unique story ID */
|
|
14
|
+
storyId: string;
|
|
15
|
+
/** Display title */
|
|
16
|
+
title: string;
|
|
17
|
+
/** Priority score (higher = more urgent) */
|
|
18
|
+
priority: number;
|
|
19
|
+
/** Current status */
|
|
20
|
+
status: QueueItemStatus;
|
|
21
|
+
/** Assigned agent (if any) */
|
|
22
|
+
assignedAgent?: string;
|
|
23
|
+
/** Number of retry attempts */
|
|
24
|
+
attempts: number;
|
|
25
|
+
/** Timestamp when added to queue */
|
|
26
|
+
addedAt: Date;
|
|
27
|
+
/** Timestamp when started (if in-progress) */
|
|
28
|
+
startedAt?: Date;
|
|
29
|
+
/** Timestamp when completed/failed */
|
|
30
|
+
completedAt?: Date;
|
|
31
|
+
/** Error message (if failed) */
|
|
32
|
+
error?: string;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/** Queue statistics */
|
|
36
|
+
export interface QueueStats {
|
|
37
|
+
total: number;
|
|
38
|
+
pending: number;
|
|
39
|
+
inProgress: number;
|
|
40
|
+
completed: number;
|
|
41
|
+
failed: number;
|
|
42
|
+
skipped: number;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/** Queue command for mid-run control */
|
|
46
|
+
export type QueueCommand = { type: "PAUSE" } | { type: "ABORT" } | { type: "SKIP"; storyId: string };
|
|
47
|
+
|
|
48
|
+
/** Result of parsing a queue file */
|
|
49
|
+
export interface QueueFileResult {
|
|
50
|
+
/** Parsed commands from the queue file */
|
|
51
|
+
commands: QueueCommand[];
|
|
52
|
+
/** Non-command lines (existing guidance behavior) */
|
|
53
|
+
guidance: string[];
|
|
54
|
+
}
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Review Runner
|
|
3
|
+
*
|
|
4
|
+
* Runs configurable quality checks after story implementation
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { spawn } from "bun";
|
|
8
|
+
import type { ExecutionConfig } from "../config/schema";
|
|
9
|
+
import type { ReviewCheckName, ReviewCheckResult, ReviewConfig, ReviewResult } from "./types";
|
|
10
|
+
|
|
11
|
+
/** Default commands for each check type */
|
|
12
|
+
const DEFAULT_COMMANDS: Record<ReviewCheckName, string> = {
|
|
13
|
+
typecheck: "bun run typecheck",
|
|
14
|
+
lint: "bun run lint",
|
|
15
|
+
test: "bun test",
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Load package.json from workdir
|
|
20
|
+
*/
|
|
21
|
+
async function loadPackageJson(workdir: string): Promise<Record<string, unknown> | null> {
|
|
22
|
+
try {
|
|
23
|
+
const file = Bun.file(`${workdir}/package.json`);
|
|
24
|
+
const content = await file.text();
|
|
25
|
+
return JSON.parse(content);
|
|
26
|
+
} catch {
|
|
27
|
+
return null;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Check if package.json has a script
|
|
33
|
+
*/
|
|
34
|
+
function hasScript(packageJson: Record<string, unknown> | null, scriptName: string): boolean {
|
|
35
|
+
if (!packageJson) return false;
|
|
36
|
+
const scripts = packageJson.scripts;
|
|
37
|
+
if (typeof scripts !== "object" || scripts === null) return false;
|
|
38
|
+
return scriptName in scripts;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Resolve command for a check
|
|
43
|
+
* Resolution order:
|
|
44
|
+
* 1. Explicit executionConfig field (lintCommand/typecheckCommand) - null = disabled
|
|
45
|
+
* 2. package.json has script -> use 'bun run <script>'
|
|
46
|
+
* 3. Not found -> return null (skip)
|
|
47
|
+
*/
|
|
48
|
+
async function resolveCommand(
|
|
49
|
+
check: ReviewCheckName,
|
|
50
|
+
config: ReviewConfig,
|
|
51
|
+
executionConfig: ExecutionConfig | undefined,
|
|
52
|
+
workdir: string,
|
|
53
|
+
): Promise<string | null> {
|
|
54
|
+
// 1. Check explicit config.execution commands (v0.13 story)
|
|
55
|
+
if (executionConfig) {
|
|
56
|
+
if (check === "lint" && executionConfig.lintCommand !== undefined) {
|
|
57
|
+
return executionConfig.lintCommand; // null = disabled
|
|
58
|
+
}
|
|
59
|
+
if (check === "typecheck" && executionConfig.typecheckCommand !== undefined) {
|
|
60
|
+
return executionConfig.typecheckCommand; // null = disabled
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// 2. Check config.review.commands (legacy, backwards compat)
|
|
65
|
+
if (config.commands[check]) {
|
|
66
|
+
return config.commands[check] ?? null;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// 3. Check package.json
|
|
70
|
+
const packageJson = await loadPackageJson(workdir);
|
|
71
|
+
if (hasScript(packageJson, check)) {
|
|
72
|
+
return `bun run ${check}`;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// 4. Not found - return null to skip
|
|
76
|
+
return null;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Run a single review check
|
|
81
|
+
*/
|
|
82
|
+
async function runCheck(check: ReviewCheckName, command: string, workdir: string): Promise<ReviewCheckResult> {
|
|
83
|
+
const startTime = Date.now();
|
|
84
|
+
|
|
85
|
+
try {
|
|
86
|
+
// Parse command into executable and args
|
|
87
|
+
const parts = command.split(/\s+/);
|
|
88
|
+
const executable = parts[0];
|
|
89
|
+
const args = parts.slice(1);
|
|
90
|
+
|
|
91
|
+
// Spawn the process
|
|
92
|
+
const proc = spawn({
|
|
93
|
+
cmd: [executable, ...args],
|
|
94
|
+
cwd: workdir,
|
|
95
|
+
stdout: "pipe",
|
|
96
|
+
stderr: "pipe",
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
// Wait for completion
|
|
100
|
+
const result = await proc.exited;
|
|
101
|
+
|
|
102
|
+
// Collect output
|
|
103
|
+
const stdout = await new Response(proc.stdout).text();
|
|
104
|
+
const stderr = await new Response(proc.stderr).text();
|
|
105
|
+
const output = [stdout, stderr].filter(Boolean).join("\n");
|
|
106
|
+
|
|
107
|
+
return {
|
|
108
|
+
check,
|
|
109
|
+
command,
|
|
110
|
+
success: result === 0,
|
|
111
|
+
exitCode: result,
|
|
112
|
+
output,
|
|
113
|
+
durationMs: Date.now() - startTime,
|
|
114
|
+
};
|
|
115
|
+
} catch (error) {
|
|
116
|
+
return {
|
|
117
|
+
check,
|
|
118
|
+
command,
|
|
119
|
+
success: false,
|
|
120
|
+
exitCode: -1,
|
|
121
|
+
output: error instanceof Error ? error.message : String(error),
|
|
122
|
+
durationMs: Date.now() - startTime,
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Run all configured review checks
|
|
129
|
+
*/
|
|
130
|
+
export async function runReview(
|
|
131
|
+
config: ReviewConfig,
|
|
132
|
+
workdir: string,
|
|
133
|
+
executionConfig?: ExecutionConfig,
|
|
134
|
+
): Promise<ReviewResult> {
|
|
135
|
+
const startTime = Date.now();
|
|
136
|
+
const checks: ReviewCheckResult[] = [];
|
|
137
|
+
let firstFailure: string | undefined;
|
|
138
|
+
|
|
139
|
+
for (const checkName of config.checks) {
|
|
140
|
+
// Resolve command using resolution strategy
|
|
141
|
+
const command = await resolveCommand(checkName, config, executionConfig, workdir);
|
|
142
|
+
|
|
143
|
+
// Skip if explicitly disabled or not found
|
|
144
|
+
if (command === null) {
|
|
145
|
+
console.warn(`[nax] Skipping ${checkName} check (command not configured or disabled)`);
|
|
146
|
+
continue;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// Run the check
|
|
150
|
+
const result = await runCheck(checkName, command, workdir);
|
|
151
|
+
checks.push(result);
|
|
152
|
+
|
|
153
|
+
// Track first failure
|
|
154
|
+
if (!result.success && !firstFailure) {
|
|
155
|
+
firstFailure = `${checkName} failed (exit code ${result.exitCode})`;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// Stop on first failure (fail-fast)
|
|
159
|
+
if (!result.success) {
|
|
160
|
+
break;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
const allPassed = checks.every((c) => c.success);
|
|
165
|
+
|
|
166
|
+
return {
|
|
167
|
+
success: allPassed,
|
|
168
|
+
checks,
|
|
169
|
+
totalDurationMs: Date.now() - startTime,
|
|
170
|
+
failureReason: firstFailure,
|
|
171
|
+
};
|
|
172
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Review Phase Types
|
|
3
|
+
*
|
|
4
|
+
* Post-implementation quality verification
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
/** Review check name */
|
|
8
|
+
export type ReviewCheckName = "typecheck" | "lint" | "test";
|
|
9
|
+
|
|
10
|
+
/** Review check result */
|
|
11
|
+
export interface ReviewCheckResult {
|
|
12
|
+
/** Check name */
|
|
13
|
+
check: ReviewCheckName;
|
|
14
|
+
/** Pass or fail */
|
|
15
|
+
success: boolean;
|
|
16
|
+
/** Command that was run */
|
|
17
|
+
command: string;
|
|
18
|
+
/** Exit code */
|
|
19
|
+
exitCode: number;
|
|
20
|
+
/** Output from the command */
|
|
21
|
+
output: string;
|
|
22
|
+
/** Duration in milliseconds */
|
|
23
|
+
durationMs: number;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/** Plugin reviewer result */
|
|
27
|
+
export interface PluginReviewerResult {
|
|
28
|
+
/** Plugin reviewer name */
|
|
29
|
+
name: string;
|
|
30
|
+
/** Pass or fail */
|
|
31
|
+
passed: boolean;
|
|
32
|
+
/** Output from the reviewer */
|
|
33
|
+
output: string;
|
|
34
|
+
/** Exit code (if applicable) */
|
|
35
|
+
exitCode?: number;
|
|
36
|
+
/** Error message if reviewer threw an exception */
|
|
37
|
+
error?: string;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/** Review phase result */
|
|
41
|
+
export interface ReviewResult {
|
|
42
|
+
/** All checks passed */
|
|
43
|
+
success: boolean;
|
|
44
|
+
/** Individual check results */
|
|
45
|
+
checks: ReviewCheckResult[];
|
|
46
|
+
/** Total duration */
|
|
47
|
+
totalDurationMs: number;
|
|
48
|
+
/** First failure reason (if any) */
|
|
49
|
+
failureReason?: string;
|
|
50
|
+
/** Plugin reviewer results (if any) */
|
|
51
|
+
pluginReviewers?: PluginReviewerResult[];
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/** Review configuration */
|
|
55
|
+
export interface ReviewConfig {
|
|
56
|
+
/** Enable review phase */
|
|
57
|
+
enabled: boolean;
|
|
58
|
+
/** List of checks to run */
|
|
59
|
+
checks: ReviewCheckName[];
|
|
60
|
+
/** Custom commands per check */
|
|
61
|
+
commands: {
|
|
62
|
+
typecheck?: string;
|
|
63
|
+
lint?: string;
|
|
64
|
+
test?: string;
|
|
65
|
+
};
|
|
66
|
+
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Routing Strategy Chain Builder
|
|
3
|
+
*
|
|
4
|
+
* Builds the strategy chain based on configuration.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { NaxConfig } from "../config";
|
|
8
|
+
import type { PluginRegistry } from "../plugins/registry";
|
|
9
|
+
import { StrategyChain } from "./chain";
|
|
10
|
+
import { loadCustomStrategy } from "./loader";
|
|
11
|
+
import { adaptiveStrategy, keywordStrategy, llmStrategy, manualStrategy } from "./strategies";
|
|
12
|
+
import type { RoutingStrategy } from "./strategy";
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Build the routing strategy chain based on configuration.
|
|
16
|
+
*
|
|
17
|
+
* Chain order (plugin routers first, then config-based, keyword always last):
|
|
18
|
+
* - plugin routers (from plugin registry, in load order)
|
|
19
|
+
* - manual (if strategy = "manual")
|
|
20
|
+
* - custom (if strategy = "custom")
|
|
21
|
+
* - llm (if strategy = "llm")
|
|
22
|
+
* - adaptive (if strategy = "adaptive") [v0.5 Phase 3]
|
|
23
|
+
* - keyword (always last — never returns null)
|
|
24
|
+
*
|
|
25
|
+
* @param config - nax configuration
|
|
26
|
+
* @param workdir - Working directory for resolving custom strategy paths
|
|
27
|
+
* @param plugins - Optional plugin registry for plugin-provided routers
|
|
28
|
+
* @returns Strategy chain instance
|
|
29
|
+
*
|
|
30
|
+
* @example
|
|
31
|
+
* ```ts
|
|
32
|
+
* const chain = await buildStrategyChain(config, "/path/to/project", plugins);
|
|
33
|
+
* const decision = chain.route(story, context);
|
|
34
|
+
* ```
|
|
35
|
+
*/
|
|
36
|
+
export async function buildStrategyChain(
|
|
37
|
+
config: NaxConfig,
|
|
38
|
+
workdir: string,
|
|
39
|
+
plugins?: PluginRegistry,
|
|
40
|
+
): Promise<StrategyChain> {
|
|
41
|
+
const strategies: RoutingStrategy[] = [];
|
|
42
|
+
|
|
43
|
+
// Prepend plugin routers before built-in strategies
|
|
44
|
+
if (plugins) {
|
|
45
|
+
const pluginRouters = plugins.getRouters();
|
|
46
|
+
strategies.push(...pluginRouters);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// Add strategies based on config
|
|
50
|
+
switch (config.routing.strategy) {
|
|
51
|
+
case "manual":
|
|
52
|
+
strategies.push(manualStrategy);
|
|
53
|
+
break;
|
|
54
|
+
|
|
55
|
+
case "llm":
|
|
56
|
+
strategies.push(llmStrategy);
|
|
57
|
+
break;
|
|
58
|
+
|
|
59
|
+
case "adaptive":
|
|
60
|
+
strategies.push(adaptiveStrategy);
|
|
61
|
+
break;
|
|
62
|
+
|
|
63
|
+
case "custom": {
|
|
64
|
+
if (!config.routing.customStrategyPath) {
|
|
65
|
+
throw new Error("routing.customStrategyPath is required when strategy is 'custom'");
|
|
66
|
+
}
|
|
67
|
+
const customStrategy = await loadCustomStrategy(config.routing.customStrategyPath, workdir);
|
|
68
|
+
strategies.push(customStrategy);
|
|
69
|
+
break;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
case "keyword":
|
|
73
|
+
// Keyword will be added at the end anyway
|
|
74
|
+
break;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// Always add keyword strategy as final fallback
|
|
78
|
+
strategies.push(keywordStrategy);
|
|
79
|
+
|
|
80
|
+
return new StrategyChain(strategies);
|
|
81
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Strategy Chain
|
|
3
|
+
*
|
|
4
|
+
* Executes routing strategies in order, falling through on null returns.
|
|
5
|
+
* First strategy to return a non-null decision wins.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { getSafeLogger } from "../logger";
|
|
9
|
+
import type { UserStory } from "../prd/types";
|
|
10
|
+
import type { RoutingContext, RoutingDecision, RoutingStrategy } from "./strategy";
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Strategy chain that tries strategies in order until one returns a decision.
|
|
14
|
+
*
|
|
15
|
+
* @example
|
|
16
|
+
* ```ts
|
|
17
|
+
* const chain = new StrategyChain([customStrategy, adaptiveStrategy, keywordStrategy]);
|
|
18
|
+
* const decision = chain.route(story, context);
|
|
19
|
+
* // Tries custom first, then adaptive, then keyword
|
|
20
|
+
* // Returns first non-null decision
|
|
21
|
+
* ```
|
|
22
|
+
*/
|
|
23
|
+
export class StrategyChain {
|
|
24
|
+
constructor(private readonly strategies: RoutingStrategy[]) {}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Route a story through the strategy chain.
|
|
28
|
+
*
|
|
29
|
+
* Tries each strategy in order:
|
|
30
|
+
* - If strategy returns a decision → use it
|
|
31
|
+
* - If strategy returns null → try next strategy
|
|
32
|
+
* - If strategy throws an error → log it and try next strategy
|
|
33
|
+
* - If all strategies return null → throw error
|
|
34
|
+
*
|
|
35
|
+
* @param story - User story to route
|
|
36
|
+
* @param context - Routing context
|
|
37
|
+
* @returns Routing decision from first strategy that handles it
|
|
38
|
+
* @throws Error if no strategy returns a decision
|
|
39
|
+
*/
|
|
40
|
+
async route(story: UserStory, context: RoutingContext): Promise<RoutingDecision> {
|
|
41
|
+
const logger = getSafeLogger();
|
|
42
|
+
|
|
43
|
+
for (const strategy of this.strategies) {
|
|
44
|
+
try {
|
|
45
|
+
const decision = await strategy.route(story, context);
|
|
46
|
+
if (decision !== null) {
|
|
47
|
+
return decision;
|
|
48
|
+
}
|
|
49
|
+
} catch (error) {
|
|
50
|
+
// Log the error and continue to next strategy
|
|
51
|
+
logger?.error("routing", `Plugin router "${strategy.name}" failed`, {
|
|
52
|
+
strategyName: strategy.name,
|
|
53
|
+
storyId: story.id,
|
|
54
|
+
error: error instanceof Error ? error.message : String(error),
|
|
55
|
+
});
|
|
56
|
+
// Continue to next strategy
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// This should never happen if keyword strategy is last (it never returns null)
|
|
61
|
+
throw new Error(
|
|
62
|
+
`No routing strategy returned a decision for story ${story.id}. Ensure at least one fallback strategy (e.g., keyword) is in the chain.`,
|
|
63
|
+
);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Get the list of strategy names in this chain.
|
|
68
|
+
*
|
|
69
|
+
* @returns Array of strategy names
|
|
70
|
+
*/
|
|
71
|
+
getStrategyNames(): string[] {
|
|
72
|
+
return this.strategies.map((s) => s.name);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
// Core types and interfaces
|
|
2
|
+
export type { RoutingDecision } from "./router";
|
|
3
|
+
export type { RoutingStrategy, RoutingContext, AggregateMetrics } from "./strategy";
|
|
4
|
+
|
|
5
|
+
// Main routing functions
|
|
6
|
+
export { routeStory, routeTask, classifyComplexity, determineTestStrategy, complexityToModelTier } from "./router";
|
|
7
|
+
|
|
8
|
+
// Strategy chain
|
|
9
|
+
export { StrategyChain } from "./chain";
|
|
10
|
+
export { buildStrategyChain } from "./builder";
|
|
11
|
+
|
|
12
|
+
// Built-in strategies
|
|
13
|
+
export { keywordStrategy, llmStrategy, manualStrategy } from "./strategies";
|
|
14
|
+
|
|
15
|
+
// Custom strategy loader
|
|
16
|
+
export { loadCustomStrategy } from "./loader";
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Custom Strategy Loader
|
|
3
|
+
*
|
|
4
|
+
* Dynamically imports custom routing strategies from user-provided paths.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { resolve } from "node:path";
|
|
8
|
+
import type { RoutingStrategy } from "./strategy";
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Load a custom routing strategy from a file path.
|
|
12
|
+
*
|
|
13
|
+
* The custom strategy file must export a default object that satisfies
|
|
14
|
+
* the RoutingStrategy interface.
|
|
15
|
+
*
|
|
16
|
+
* @param strategyPath - Path to the custom strategy file (relative to project root or absolute)
|
|
17
|
+
* @param workdir - Working directory (project root)
|
|
18
|
+
* @returns Loaded routing strategy
|
|
19
|
+
* @throws Error if the strategy cannot be loaded or is invalid
|
|
20
|
+
*
|
|
21
|
+
* @example
|
|
22
|
+
* ```ts
|
|
23
|
+
* const strategy = await loadCustomStrategy("./my-router.ts", process.cwd());
|
|
24
|
+
* const decision = strategy.route(story, context);
|
|
25
|
+
* ```
|
|
26
|
+
*/
|
|
27
|
+
export async function loadCustomStrategy(strategyPath: string, workdir: string): Promise<RoutingStrategy> {
|
|
28
|
+
const absolutePath = resolve(workdir, strategyPath);
|
|
29
|
+
|
|
30
|
+
try {
|
|
31
|
+
// Dynamic import (works with both .ts and .js files in Bun)
|
|
32
|
+
const module = await import(absolutePath);
|
|
33
|
+
|
|
34
|
+
// Expect default export
|
|
35
|
+
const strategy = module.default;
|
|
36
|
+
|
|
37
|
+
if (!strategy) {
|
|
38
|
+
throw new Error(`Custom strategy at ${absolutePath} does not have a default export`);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// Validate strategy interface
|
|
42
|
+
if (typeof strategy.name !== "string") {
|
|
43
|
+
throw new Error(`Custom strategy at ${absolutePath} is missing 'name' property`);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
if (typeof strategy.route !== "function") {
|
|
47
|
+
throw new Error(`Custom strategy at ${absolutePath} is missing 'route' method`);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
return strategy as RoutingStrategy;
|
|
51
|
+
} catch (error) {
|
|
52
|
+
throw new Error(
|
|
53
|
+
`Failed to load custom routing strategy from ${absolutePath}: ${
|
|
54
|
+
error instanceof Error ? error.message : String(error)
|
|
55
|
+
}`,
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
}
|