@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,303 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Task Router
|
|
3
|
+
*
|
|
4
|
+
* Routes stories using pluggable strategy system.
|
|
5
|
+
* Falls back to keyword-based classification for backward compatibility.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type { Complexity, ModelTier, NaxConfig, TddStrategy, TestStrategy } from "../config";
|
|
9
|
+
import type { UserStory } from "../prd/types";
|
|
10
|
+
import { buildStrategyChain } from "./builder";
|
|
11
|
+
import type { RoutingContext } from "./strategy";
|
|
12
|
+
|
|
13
|
+
/** Routing decision for a story */
|
|
14
|
+
export interface RoutingDecision {
|
|
15
|
+
/** Classified complexity */
|
|
16
|
+
complexity: Complexity;
|
|
17
|
+
/** Model tier to use */
|
|
18
|
+
modelTier: ModelTier;
|
|
19
|
+
/** Test strategy to apply */
|
|
20
|
+
testStrategy: TestStrategy;
|
|
21
|
+
/** Reasoning for the classification */
|
|
22
|
+
reasoning: string;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/** Keywords that indicate higher complexity */
|
|
26
|
+
const COMPLEX_KEYWORDS = [
|
|
27
|
+
"refactor",
|
|
28
|
+
"redesign",
|
|
29
|
+
"architecture",
|
|
30
|
+
"migration",
|
|
31
|
+
"breaking change",
|
|
32
|
+
"public api",
|
|
33
|
+
"security",
|
|
34
|
+
"auth",
|
|
35
|
+
"encryption",
|
|
36
|
+
"permission",
|
|
37
|
+
"rbac",
|
|
38
|
+
"casl",
|
|
39
|
+
"jwt",
|
|
40
|
+
"grpc",
|
|
41
|
+
"microservice",
|
|
42
|
+
"event-driven",
|
|
43
|
+
"saga",
|
|
44
|
+
];
|
|
45
|
+
|
|
46
|
+
const EXPERT_KEYWORDS = [
|
|
47
|
+
"cryptograph",
|
|
48
|
+
"zero-knowledge",
|
|
49
|
+
"distributed consensus",
|
|
50
|
+
"real-time",
|
|
51
|
+
"websocket",
|
|
52
|
+
"streaming",
|
|
53
|
+
"performance critical",
|
|
54
|
+
];
|
|
55
|
+
|
|
56
|
+
const SECURITY_KEYWORDS = [
|
|
57
|
+
"auth",
|
|
58
|
+
"security",
|
|
59
|
+
"permission",
|
|
60
|
+
"jwt",
|
|
61
|
+
"oauth",
|
|
62
|
+
"token",
|
|
63
|
+
"encryption",
|
|
64
|
+
"secret",
|
|
65
|
+
"credential",
|
|
66
|
+
"password",
|
|
67
|
+
"rbac",
|
|
68
|
+
"casl",
|
|
69
|
+
];
|
|
70
|
+
|
|
71
|
+
const PUBLIC_API_KEYWORDS = [
|
|
72
|
+
"public api",
|
|
73
|
+
"breaking change",
|
|
74
|
+
"external",
|
|
75
|
+
"consumer",
|
|
76
|
+
"sdk",
|
|
77
|
+
"npm publish",
|
|
78
|
+
"release",
|
|
79
|
+
"endpoint",
|
|
80
|
+
];
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Classify a story's complexity based on keywords and acceptance criteria count.
|
|
84
|
+
*
|
|
85
|
+
* Classification rules:
|
|
86
|
+
* - expert: matches expert keywords (cryptography, distributed consensus, real-time)
|
|
87
|
+
* - complex: matches complex keywords or >8 acceptance criteria
|
|
88
|
+
* - medium: >4 acceptance criteria
|
|
89
|
+
* - simple: default
|
|
90
|
+
*
|
|
91
|
+
* @param title - Story title
|
|
92
|
+
* @param description - Story description
|
|
93
|
+
* @param acceptanceCriteria - Array of acceptance criteria
|
|
94
|
+
* @param tags - Optional story tags
|
|
95
|
+
* @returns Classified complexity level
|
|
96
|
+
*
|
|
97
|
+
* @example
|
|
98
|
+
* ```ts
|
|
99
|
+
* classifyComplexity(
|
|
100
|
+
* "Add JWT authentication",
|
|
101
|
+
* "Implement JWT auth with refresh tokens",
|
|
102
|
+
* ["Secure token storage", "Token refresh", "Expiry handling"],
|
|
103
|
+
* ["security", "auth"]
|
|
104
|
+
* );
|
|
105
|
+
* // "complex" (matches security keywords)
|
|
106
|
+
*
|
|
107
|
+
* classifyComplexity(
|
|
108
|
+
* "Fix typo in README",
|
|
109
|
+
* "Correct spelling mistake",
|
|
110
|
+
* ["Update README.md"],
|
|
111
|
+
* []
|
|
112
|
+
* );
|
|
113
|
+
* // "simple"
|
|
114
|
+
* ```
|
|
115
|
+
*/
|
|
116
|
+
export function classifyComplexity(
|
|
117
|
+
title: string,
|
|
118
|
+
description: string,
|
|
119
|
+
acceptanceCriteria: string[],
|
|
120
|
+
tags: string[] = [],
|
|
121
|
+
): Complexity {
|
|
122
|
+
const text = [title, description, ...(acceptanceCriteria ?? []), ...(tags ?? [])].join(" ").toLowerCase();
|
|
123
|
+
|
|
124
|
+
// Expert: matches expert keywords
|
|
125
|
+
if (EXPERT_KEYWORDS.some((kw) => text.includes(kw))) {
|
|
126
|
+
return "expert";
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// Complex: matches complex keywords or has many criteria
|
|
130
|
+
if (COMPLEX_KEYWORDS.some((kw) => text.includes(kw)) || acceptanceCriteria.length > 8) {
|
|
131
|
+
return "complex";
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// Medium: moderate criteria or some structural keywords
|
|
135
|
+
if (acceptanceCriteria.length > 4) {
|
|
136
|
+
return "medium";
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
return "simple";
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/** Tags that indicate a lite-mode story (UI, layout, CLI, integration, polyglot) */
|
|
143
|
+
const LITE_TAGS = ["ui", "layout", "cli", "integration", "polyglot"];
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Determine test strategy using decision tree logic.
|
|
147
|
+
*
|
|
148
|
+
* When tddStrategy is provided:
|
|
149
|
+
* - 'strict' → always three-session-tdd
|
|
150
|
+
* - 'lite' → always three-session-tdd-lite
|
|
151
|
+
* - 'off' → always test-after
|
|
152
|
+
* - 'auto' → existing heuristic logic, plus:
|
|
153
|
+
* if tags include ui/layout/cli/integration/polyglot → three-session-tdd-lite
|
|
154
|
+
* if security/public-api/complex/expert → three-session-tdd
|
|
155
|
+
* otherwise → test-after
|
|
156
|
+
*
|
|
157
|
+
* @param complexity - Pre-classified complexity level
|
|
158
|
+
* @param title - Story title
|
|
159
|
+
* @param description - Story description
|
|
160
|
+
* @param tags - Optional story tags
|
|
161
|
+
* @param tddStrategy - TDD strategy override from config (default: 'auto')
|
|
162
|
+
* @returns Test strategy
|
|
163
|
+
*
|
|
164
|
+
* @example
|
|
165
|
+
* ```ts
|
|
166
|
+
* determineTestStrategy("complex", "Add OAuth", "Implement OAuth 2.0", ["security", "auth"], "strict");
|
|
167
|
+
* // "three-session-tdd"
|
|
168
|
+
*
|
|
169
|
+
* determineTestStrategy("simple", "Update button", "Change primary button", ["ui"], "auto");
|
|
170
|
+
* // "three-session-tdd-lite"
|
|
171
|
+
* ```
|
|
172
|
+
*/
|
|
173
|
+
|
|
174
|
+
export function determineTestStrategy(
|
|
175
|
+
complexity: Complexity,
|
|
176
|
+
title: string,
|
|
177
|
+
description: string,
|
|
178
|
+
tags: string[] = [],
|
|
179
|
+
tddStrategy: TddStrategy = "auto",
|
|
180
|
+
): TestStrategy {
|
|
181
|
+
// Explicit overrides — ignore all heuristics
|
|
182
|
+
if (tddStrategy === "strict") return "three-session-tdd";
|
|
183
|
+
if (tddStrategy === "lite") return "three-session-tdd-lite";
|
|
184
|
+
if (tddStrategy === "off") return "test-after";
|
|
185
|
+
|
|
186
|
+
// auto mode: apply heuristics
|
|
187
|
+
const text = [title, description, ...(tags ?? [])].join(" ").toLowerCase();
|
|
188
|
+
|
|
189
|
+
// Public API or security → three-session-tdd (strict)
|
|
190
|
+
const isSecurityCritical = SECURITY_KEYWORDS.some((kw) => text.includes(kw));
|
|
191
|
+
const isPublicApi = PUBLIC_API_KEYWORDS.some((kw) => text.includes(kw));
|
|
192
|
+
|
|
193
|
+
if (isSecurityCritical || isPublicApi) {
|
|
194
|
+
return "three-session-tdd";
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// Complex/expert heuristic
|
|
198
|
+
if (complexity === "complex" || complexity === "expert") {
|
|
199
|
+
const normalizedTags = (tags ?? []).map((t) => t.toLowerCase());
|
|
200
|
+
const hasLiteTag = LITE_TAGS.some((tag) => normalizedTags.includes(tag));
|
|
201
|
+
return hasLiteTag ? "three-session-tdd-lite" : "three-session-tdd";
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// Simple/medium → test-after (default)
|
|
205
|
+
return "test-after";
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/** Map complexity to model tier */
|
|
209
|
+
export function complexityToModelTier(complexity: Complexity, config: NaxConfig): ModelTier {
|
|
210
|
+
const mapping = config.autoMode.complexityRouting;
|
|
211
|
+
return (mapping[complexity] ?? "balanced") as ModelTier;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* Route a story using the pluggable strategy system.
|
|
216
|
+
*
|
|
217
|
+
* This is the new main entry point for the routing system. It:
|
|
218
|
+
* 1. Builds the strategy chain based on config
|
|
219
|
+
* 2. Routes the story through the chain
|
|
220
|
+
* 3. Returns the first non-null decision
|
|
221
|
+
*
|
|
222
|
+
* @param story - User story to route
|
|
223
|
+
* @param context - Routing context (config, codebase, metrics)
|
|
224
|
+
* @param workdir - Working directory for resolving custom strategy paths
|
|
225
|
+
* @param plugins - Optional plugin registry for plugin-provided routers
|
|
226
|
+
* @returns Routing decision from the strategy chain
|
|
227
|
+
*
|
|
228
|
+
* @example
|
|
229
|
+
* ```ts
|
|
230
|
+
* const decision = await routeStory(story, { config }, "/path/to/project", plugins);
|
|
231
|
+
* // {
|
|
232
|
+
* // complexity: "complex",
|
|
233
|
+
* // modelTier: "balanced",
|
|
234
|
+
* // testStrategy: "three-session-tdd",
|
|
235
|
+
* // reasoning: "three-session-tdd: security-critical, complexity:complex"
|
|
236
|
+
* // }
|
|
237
|
+
* ```
|
|
238
|
+
*/
|
|
239
|
+
export async function routeStory(
|
|
240
|
+
story: UserStory,
|
|
241
|
+
context: RoutingContext,
|
|
242
|
+
workdir: string,
|
|
243
|
+
plugins?: import("../plugins/registry").PluginRegistry,
|
|
244
|
+
): Promise<RoutingDecision> {
|
|
245
|
+
const chain = await buildStrategyChain(context.config, workdir, plugins);
|
|
246
|
+
return await chain.route(story, context);
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
/**
|
|
250
|
+
* Route a task through complexity classification, model tier selection, and test strategy.
|
|
251
|
+
*
|
|
252
|
+
* DEPRECATED: Use routeStory() instead. This function is kept for backward compatibility
|
|
253
|
+
* and uses only the keyword strategy.
|
|
254
|
+
*
|
|
255
|
+
* @param title - Story title
|
|
256
|
+
* @param description - Story description
|
|
257
|
+
* @param acceptanceCriteria - Array of acceptance criteria
|
|
258
|
+
* @param tags - Story tags
|
|
259
|
+
* @param config - nax configuration with complexity routing mappings
|
|
260
|
+
* @returns Routing decision with complexity, model tier, test strategy, and reasoning
|
|
261
|
+
*
|
|
262
|
+
* @deprecated Use routeStory() with a UserStory object instead
|
|
263
|
+
*/
|
|
264
|
+
export function routeTask(
|
|
265
|
+
title: string,
|
|
266
|
+
description: string,
|
|
267
|
+
acceptanceCriteria: string[],
|
|
268
|
+
tags: string[],
|
|
269
|
+
config: NaxConfig,
|
|
270
|
+
): RoutingDecision {
|
|
271
|
+
const complexity = classifyComplexity(title, description, acceptanceCriteria, tags);
|
|
272
|
+
const modelTier = complexityToModelTier(complexity, config);
|
|
273
|
+
const tddStrategy: TddStrategy = config.tdd?.strategy ?? "auto";
|
|
274
|
+
const testStrategy = determineTestStrategy(complexity, title, description, tags, tddStrategy);
|
|
275
|
+
|
|
276
|
+
const reasons: string[] = [];
|
|
277
|
+
const text = [title, description, ...(tags ?? [])].join(" ").toLowerCase();
|
|
278
|
+
const normalizedTags = (tags ?? []).map((t) => t.toLowerCase());
|
|
279
|
+
const hasLiteTag = LITE_TAGS.some((tag) => normalizedTags.includes(tag));
|
|
280
|
+
|
|
281
|
+
if (SECURITY_KEYWORDS.some((kw) => text.includes(kw))) reasons.push("security-critical");
|
|
282
|
+
if (PUBLIC_API_KEYWORDS.some((kw) => text.includes(kw))) reasons.push("public-api");
|
|
283
|
+
|
|
284
|
+
// Only add complexity reason if it's the primary reason for strict/lite TDD
|
|
285
|
+
if (complexity === "complex" || complexity === "expert") {
|
|
286
|
+
if (reasons.length === 0) {
|
|
287
|
+
reasons.push(`complexity:${complexity}`);
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
if (tddStrategy !== "auto") reasons.push(`strategy:${tddStrategy}`);
|
|
292
|
+
if (hasLiteTag && (complexity === "complex" || complexity === "expert")) {
|
|
293
|
+
reasons.push("lite-tags");
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
const prefix = testStrategy;
|
|
297
|
+
return {
|
|
298
|
+
complexity,
|
|
299
|
+
modelTier,
|
|
300
|
+
testStrategy,
|
|
301
|
+
reasoning: reasons.length > 0 ? `${prefix}: ${reasons.join(", ")}` : `test-after: simple task (${complexity})`,
|
|
302
|
+
};
|
|
303
|
+
}
|
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Adaptive Routing Strategy
|
|
3
|
+
*
|
|
4
|
+
* Uses historical metrics to optimize model tier selection based on cost-effectiveness.
|
|
5
|
+
* Routes to the cheapest tier that maintains acceptable success rates, accounting for
|
|
6
|
+
* escalation costs.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import type { Complexity, ModelTier } from "../../config";
|
|
10
|
+
import type { AggregateMetrics } from "../../metrics/types";
|
|
11
|
+
import type { UserStory } from "../../prd/types";
|
|
12
|
+
import type { RoutingContext, RoutingDecision, RoutingStrategy } from "../strategy";
|
|
13
|
+
import { keywordStrategy } from "./keyword";
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Estimated costs per model tier (USD per story, approximate).
|
|
17
|
+
* These are rough estimates based on typical story complexity.
|
|
18
|
+
* Actual costs vary based on input/output tokens.
|
|
19
|
+
*/
|
|
20
|
+
const ESTIMATED_TIER_COSTS: Record<ModelTier, number> = {
|
|
21
|
+
fast: 0.005, // ~$0.005 per simple story
|
|
22
|
+
balanced: 0.02, // ~$0.02 per medium story
|
|
23
|
+
powerful: 0.08, // ~$0.08 per complex story
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Calculate effective cost for a model tier given historical metrics.
|
|
28
|
+
*
|
|
29
|
+
* effectiveCost = baseCost + (failRate × escalationCost)
|
|
30
|
+
*
|
|
31
|
+
* Where:
|
|
32
|
+
* - baseCost = cost of using this tier
|
|
33
|
+
* - failRate = probability of failure (requiring escalation)
|
|
34
|
+
* - escalationCost = cost of escalating to next tier
|
|
35
|
+
*
|
|
36
|
+
* @param tier - Model tier to evaluate
|
|
37
|
+
* @param complexity - Story complexity level
|
|
38
|
+
* @param metrics - Historical aggregate metrics
|
|
39
|
+
* @param tierOrder - Escalation tier order
|
|
40
|
+
* @returns Effective cost (USD)
|
|
41
|
+
*/
|
|
42
|
+
function calculateEffectiveCost(
|
|
43
|
+
tier: ModelTier,
|
|
44
|
+
complexity: Complexity,
|
|
45
|
+
metrics: AggregateMetrics,
|
|
46
|
+
tierOrder: ModelTier[],
|
|
47
|
+
): number {
|
|
48
|
+
const baseCost = ESTIMATED_TIER_COSTS[tier];
|
|
49
|
+
|
|
50
|
+
// Get historical pass rate for this tier on this complexity level
|
|
51
|
+
const complexityStats = metrics.complexityAccuracy[complexity];
|
|
52
|
+
if (!complexityStats || complexityStats.predicted < 1) {
|
|
53
|
+
// No data for this complexity level — assume base cost (no escalation)
|
|
54
|
+
return baseCost;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Calculate fail rate (stories that needed escalation)
|
|
58
|
+
// mismatchRate = percentage of stories where initial tier != final tier
|
|
59
|
+
const failRate = complexityStats.mismatchRate;
|
|
60
|
+
|
|
61
|
+
// Find next tier in escalation chain
|
|
62
|
+
const currentIndex = tierOrder.indexOf(tier);
|
|
63
|
+
const nextTier = currentIndex < tierOrder.length - 1 ? tierOrder[currentIndex + 1] : null;
|
|
64
|
+
|
|
65
|
+
if (!nextTier) {
|
|
66
|
+
// Already at highest tier — no escalation possible
|
|
67
|
+
return baseCost;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// Escalation cost = cost of trying this tier + cost of next tier
|
|
71
|
+
const escalationCost = ESTIMATED_TIER_COSTS[nextTier];
|
|
72
|
+
|
|
73
|
+
return baseCost + failRate * escalationCost;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Find the most cost-effective tier for a given complexity level.
|
|
78
|
+
*
|
|
79
|
+
* Evaluates all tiers in the escalation chain and selects the one with
|
|
80
|
+
* the lowest effective cost (including escalation probability).
|
|
81
|
+
*
|
|
82
|
+
* @param complexity - Story complexity
|
|
83
|
+
* @param metrics - Historical metrics
|
|
84
|
+
* @param tierOrder - Escalation tier order
|
|
85
|
+
* @param costThreshold - Switch threshold (0-1)
|
|
86
|
+
* @returns Best tier and reasoning
|
|
87
|
+
*/
|
|
88
|
+
function selectOptimalTier(
|
|
89
|
+
complexity: Complexity,
|
|
90
|
+
metrics: AggregateMetrics,
|
|
91
|
+
tierOrder: ModelTier[],
|
|
92
|
+
costThreshold: number,
|
|
93
|
+
): { tier: ModelTier; reasoning: string } {
|
|
94
|
+
// Calculate effective cost for each tier
|
|
95
|
+
const costs = tierOrder.map((tier) => ({
|
|
96
|
+
tier,
|
|
97
|
+
effectiveCost: calculateEffectiveCost(tier, complexity, metrics, tierOrder),
|
|
98
|
+
}));
|
|
99
|
+
|
|
100
|
+
// Sort by effective cost (lowest first)
|
|
101
|
+
costs.sort((a, b) => a.effectiveCost - b.effectiveCost);
|
|
102
|
+
|
|
103
|
+
const optimal = costs[0];
|
|
104
|
+
const complexityStats = metrics.complexityAccuracy[complexity];
|
|
105
|
+
|
|
106
|
+
// If the cheapest tier's effective cost is within threshold of next tier, use it
|
|
107
|
+
const reasoning = complexityStats
|
|
108
|
+
? `adaptive: ${complexity} → ${optimal.tier} (cost: $${optimal.effectiveCost.toFixed(4)}, ` +
|
|
109
|
+
`samples: ${complexityStats.predicted}, mismatch: ${(complexityStats.mismatchRate * 100).toFixed(1)}%)`
|
|
110
|
+
: `adaptive: ${complexity} → ${optimal.tier} (insufficient data, using base cost: $${optimal.effectiveCost.toFixed(4)})`;
|
|
111
|
+
|
|
112
|
+
return { tier: optimal.tier, reasoning };
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Check if there's sufficient data for adaptive routing.
|
|
117
|
+
*
|
|
118
|
+
* @param complexity - Story complexity
|
|
119
|
+
* @param metrics - Historical metrics
|
|
120
|
+
* @param minSamples - Minimum samples required
|
|
121
|
+
* @returns True if sufficient data exists
|
|
122
|
+
*/
|
|
123
|
+
function hasSufficientData(complexity: Complexity, metrics: AggregateMetrics, minSamples: number): boolean {
|
|
124
|
+
const complexityStats = metrics.complexityAccuracy[complexity];
|
|
125
|
+
return Boolean(complexityStats && complexityStats.predicted >= minSamples);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Adaptive routing strategy.
|
|
130
|
+
*
|
|
131
|
+
* Uses historical metrics to select the most cost-effective model tier.
|
|
132
|
+
* Falls back to configured strategy when insufficient data is available.
|
|
133
|
+
*
|
|
134
|
+
* Algorithm:
|
|
135
|
+
* 1. Check if sufficient historical data exists (>= minSamples)
|
|
136
|
+
* 2. If yes: Calculate effective cost for each tier (base + fail × escalation)
|
|
137
|
+
* 3. Select tier with lowest effective cost
|
|
138
|
+
* 4. If no: Delegate to fallback strategy
|
|
139
|
+
*
|
|
140
|
+
* @example
|
|
141
|
+
* ```ts
|
|
142
|
+
* const decision = adaptiveStrategy.route(story, context);
|
|
143
|
+
* // With sufficient data:
|
|
144
|
+
* // {
|
|
145
|
+
* // complexity: "medium",
|
|
146
|
+
* // modelTier: "fast",
|
|
147
|
+
* // reasoning: "adaptive: medium → fast (cost: $0.0078, samples: 23, mismatch: 12.5%)"
|
|
148
|
+
* // }
|
|
149
|
+
* //
|
|
150
|
+
* // Without sufficient data:
|
|
151
|
+
* // {
|
|
152
|
+
* // complexity: "medium",
|
|
153
|
+
* // modelTier: "balanced",
|
|
154
|
+
* // reasoning: "adaptive: insufficient data (7/10 samples) → fallback to llm"
|
|
155
|
+
* // }
|
|
156
|
+
* ```
|
|
157
|
+
*/
|
|
158
|
+
export const adaptiveStrategy: RoutingStrategy = {
|
|
159
|
+
name: "adaptive",
|
|
160
|
+
|
|
161
|
+
async route(story: UserStory, context: RoutingContext): Promise<RoutingDecision | null> {
|
|
162
|
+
const { config, metrics } = context;
|
|
163
|
+
|
|
164
|
+
// Require metrics to be present - use keyword as ultimate fallback
|
|
165
|
+
if (!metrics) {
|
|
166
|
+
const fallbackStrategy = config.routing.adaptive?.fallbackStrategy || "llm";
|
|
167
|
+
const decision = await keywordStrategy.route(story, context); // keyword never returns null
|
|
168
|
+
if (!decision) return null;
|
|
169
|
+
|
|
170
|
+
return {
|
|
171
|
+
...decision,
|
|
172
|
+
reasoning: `adaptive: no metrics available → fallback to ${fallbackStrategy}`,
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// Get adaptive config
|
|
177
|
+
const adaptiveConfig = config.routing.adaptive || {
|
|
178
|
+
minSamples: 10,
|
|
179
|
+
costThreshold: 0.8,
|
|
180
|
+
fallbackStrategy: "llm" as const,
|
|
181
|
+
};
|
|
182
|
+
|
|
183
|
+
// First, classify complexity using fallback strategy
|
|
184
|
+
// (We need to know complexity before checking metrics)
|
|
185
|
+
// Always use keyword as the classification source since it never returns null
|
|
186
|
+
const fallbackDecision = await keywordStrategy.route(story, context);
|
|
187
|
+
if (!fallbackDecision) return null;
|
|
188
|
+
|
|
189
|
+
const complexity = fallbackDecision.complexity;
|
|
190
|
+
|
|
191
|
+
// Check if we have sufficient historical data for this complexity
|
|
192
|
+
if (!hasSufficientData(complexity, metrics, adaptiveConfig.minSamples)) {
|
|
193
|
+
const complexityStats = metrics.complexityAccuracy[complexity];
|
|
194
|
+
const sampleCount = complexityStats?.predicted || 0;
|
|
195
|
+
|
|
196
|
+
return {
|
|
197
|
+
...fallbackDecision,
|
|
198
|
+
reasoning:
|
|
199
|
+
`adaptive: insufficient data (${sampleCount}/${adaptiveConfig.minSamples} samples) ` +
|
|
200
|
+
`→ fallback to ${adaptiveConfig.fallbackStrategy}`,
|
|
201
|
+
};
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// We have sufficient data — calculate optimal tier
|
|
205
|
+
const tierOrder = config.autoMode.escalation.tierOrder.map((t) => t.tier);
|
|
206
|
+
const { tier, reasoning } = selectOptimalTier(complexity, metrics, tierOrder, adaptiveConfig.costThreshold);
|
|
207
|
+
|
|
208
|
+
return {
|
|
209
|
+
complexity,
|
|
210
|
+
modelTier: tier,
|
|
211
|
+
testStrategy: fallbackDecision.testStrategy, // Use fallback's test strategy decision
|
|
212
|
+
reasoning,
|
|
213
|
+
};
|
|
214
|
+
},
|
|
215
|
+
};
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Keyword-Based Routing Strategy
|
|
3
|
+
*
|
|
4
|
+
* Routes stories based on keyword matching and acceptance criteria count.
|
|
5
|
+
* This is the default fallback strategy — always returns a decision (never null).
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type { Complexity, ModelTier, TestStrategy } from "../../config";
|
|
9
|
+
import type { UserStory } from "../../prd/types";
|
|
10
|
+
import type { RoutingContext, RoutingDecision, RoutingStrategy } from "../strategy";
|
|
11
|
+
|
|
12
|
+
/** Keywords that indicate higher complexity */
|
|
13
|
+
const COMPLEX_KEYWORDS = [
|
|
14
|
+
"refactor",
|
|
15
|
+
"redesign",
|
|
16
|
+
"architecture",
|
|
17
|
+
"migration",
|
|
18
|
+
"breaking change",
|
|
19
|
+
"public api",
|
|
20
|
+
"security",
|
|
21
|
+
"auth",
|
|
22
|
+
"encryption",
|
|
23
|
+
"permission",
|
|
24
|
+
"rbac",
|
|
25
|
+
"casl",
|
|
26
|
+
"jwt",
|
|
27
|
+
"grpc",
|
|
28
|
+
"microservice",
|
|
29
|
+
"event-driven",
|
|
30
|
+
"saga",
|
|
31
|
+
];
|
|
32
|
+
|
|
33
|
+
const EXPERT_KEYWORDS = [
|
|
34
|
+
"cryptograph",
|
|
35
|
+
"zero-knowledge",
|
|
36
|
+
"distributed consensus",
|
|
37
|
+
"real-time",
|
|
38
|
+
"websocket",
|
|
39
|
+
"streaming",
|
|
40
|
+
"performance critical",
|
|
41
|
+
];
|
|
42
|
+
|
|
43
|
+
const SECURITY_KEYWORDS = [
|
|
44
|
+
"auth",
|
|
45
|
+
"security",
|
|
46
|
+
"permission",
|
|
47
|
+
"jwt",
|
|
48
|
+
"oauth",
|
|
49
|
+
"token",
|
|
50
|
+
"encryption",
|
|
51
|
+
"secret",
|
|
52
|
+
"credential",
|
|
53
|
+
"password",
|
|
54
|
+
"rbac",
|
|
55
|
+
"casl",
|
|
56
|
+
];
|
|
57
|
+
|
|
58
|
+
const PUBLIC_API_KEYWORDS = [
|
|
59
|
+
"public api",
|
|
60
|
+
"breaking change",
|
|
61
|
+
"external",
|
|
62
|
+
"consumer",
|
|
63
|
+
"sdk",
|
|
64
|
+
"npm publish",
|
|
65
|
+
"release",
|
|
66
|
+
"endpoint",
|
|
67
|
+
];
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Classify complexity based on keywords and criteria count.
|
|
71
|
+
*/
|
|
72
|
+
function classifyComplexity(
|
|
73
|
+
title: string,
|
|
74
|
+
description: string,
|
|
75
|
+
acceptanceCriteria: string[],
|
|
76
|
+
tags: string[] = [],
|
|
77
|
+
): Complexity {
|
|
78
|
+
const text = [title, description, ...acceptanceCriteria, ...tags].join(" ").toLowerCase();
|
|
79
|
+
|
|
80
|
+
if (EXPERT_KEYWORDS.some((kw) => text.includes(kw))) {
|
|
81
|
+
return "expert";
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
if (COMPLEX_KEYWORDS.some((kw) => text.includes(kw)) || acceptanceCriteria.length > 8) {
|
|
85
|
+
return "complex";
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
if (acceptanceCriteria.length > 4) {
|
|
89
|
+
return "medium";
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
return "simple";
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Determine test strategy using decision tree.
|
|
97
|
+
*/
|
|
98
|
+
function determineTestStrategy(
|
|
99
|
+
complexity: Complexity,
|
|
100
|
+
title: string,
|
|
101
|
+
description: string,
|
|
102
|
+
tags: string[] = [],
|
|
103
|
+
): TestStrategy {
|
|
104
|
+
const text = [title, description, ...tags].join(" ").toLowerCase();
|
|
105
|
+
|
|
106
|
+
const isSecurityCritical = SECURITY_KEYWORDS.some((kw) => text.includes(kw));
|
|
107
|
+
const isPublicApi = PUBLIC_API_KEYWORDS.some((kw) => text.includes(kw));
|
|
108
|
+
|
|
109
|
+
if (isSecurityCritical || isPublicApi) {
|
|
110
|
+
return "three-session-tdd";
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
if (complexity === "complex" || complexity === "expert") {
|
|
114
|
+
return "three-session-tdd";
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
return "test-after";
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/** Map complexity to model tier */
|
|
121
|
+
function complexityToModelTier(complexity: Complexity, context: RoutingContext): ModelTier {
|
|
122
|
+
const mapping = context.config.autoMode.complexityRouting;
|
|
123
|
+
return (mapping[complexity] ?? "balanced") as ModelTier;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Keyword-based routing strategy.
|
|
128
|
+
*
|
|
129
|
+
* This strategy:
|
|
130
|
+
* - Classifies complexity based on keywords and criteria count
|
|
131
|
+
* - Maps complexity to model tier via config
|
|
132
|
+
* - Applies test strategy decision tree
|
|
133
|
+
* - ALWAYS returns a decision (never null)
|
|
134
|
+
*
|
|
135
|
+
* Use as the final fallback strategy in a chain.
|
|
136
|
+
*/
|
|
137
|
+
export const keywordStrategy: RoutingStrategy = {
|
|
138
|
+
name: "keyword",
|
|
139
|
+
|
|
140
|
+
route(story: UserStory, context: RoutingContext): RoutingDecision {
|
|
141
|
+
const { title, description, acceptanceCriteria, tags } = story;
|
|
142
|
+
|
|
143
|
+
const complexity = classifyComplexity(title, description, acceptanceCriteria, tags);
|
|
144
|
+
const modelTier = complexityToModelTier(complexity, context);
|
|
145
|
+
const testStrategy = determineTestStrategy(complexity, title, description, tags);
|
|
146
|
+
|
|
147
|
+
const reasons: string[] = [];
|
|
148
|
+
if (testStrategy === "three-session-tdd") {
|
|
149
|
+
const text = [title, description, ...tags].join(" ").toLowerCase();
|
|
150
|
+
if (SECURITY_KEYWORDS.some((kw) => text.includes(kw))) reasons.push("security-critical");
|
|
151
|
+
if (PUBLIC_API_KEYWORDS.some((kw) => text.includes(kw))) reasons.push("public-api");
|
|
152
|
+
if (complexity === "complex" || complexity === "expert") reasons.push(`complexity:${complexity}`);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
return {
|
|
156
|
+
complexity,
|
|
157
|
+
modelTier,
|
|
158
|
+
testStrategy,
|
|
159
|
+
reasoning:
|
|
160
|
+
reasons.length > 0 ? `three-session-tdd: ${reasons.join(", ")}` : `test-after: simple task (${complexity})`,
|
|
161
|
+
};
|
|
162
|
+
},
|
|
163
|
+
};
|