@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,149 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Review Phase Tests
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { describe, expect, test } from "bun:test";
|
|
6
|
+
import { mkdtempSync } from "node:fs";
|
|
7
|
+
import { tmpdir } from "node:os";
|
|
8
|
+
import { join } from "node:path";
|
|
9
|
+
import { runReview } from "../../src/review";
|
|
10
|
+
import type { ReviewConfig } from "../../src/review";
|
|
11
|
+
|
|
12
|
+
describe("Review Phase", () => {
|
|
13
|
+
test("runReview - all checks pass", async () => {
|
|
14
|
+
const tempDir = mkdtempSync(join(tmpdir(), "nax-review-test-"));
|
|
15
|
+
|
|
16
|
+
const config: ReviewConfig = {
|
|
17
|
+
enabled: true,
|
|
18
|
+
checks: ["test"],
|
|
19
|
+
commands: {
|
|
20
|
+
test: "echo 'Tests passed'",
|
|
21
|
+
},
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
const result = await runReview(config, tempDir);
|
|
25
|
+
|
|
26
|
+
expect(result.success).toBe(true);
|
|
27
|
+
expect(result.checks).toHaveLength(1);
|
|
28
|
+
expect(result.checks[0].check).toBe("test");
|
|
29
|
+
expect(result.checks[0].success).toBe(true);
|
|
30
|
+
expect(result.checks[0].exitCode).toBe(0);
|
|
31
|
+
expect(result.failureReason).toBeUndefined();
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
test("runReview - check fails", async () => {
|
|
35
|
+
const tempDir = mkdtempSync(join(tmpdir(), "nax-review-test-"));
|
|
36
|
+
|
|
37
|
+
const config: ReviewConfig = {
|
|
38
|
+
enabled: true,
|
|
39
|
+
checks: ["typecheck"],
|
|
40
|
+
commands: {
|
|
41
|
+
typecheck: "sh -c 'exit 1'",
|
|
42
|
+
},
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
const result = await runReview(config, tempDir);
|
|
46
|
+
|
|
47
|
+
expect(result.success).toBe(false);
|
|
48
|
+
expect(result.checks).toHaveLength(1);
|
|
49
|
+
expect(result.checks[0].check).toBe("typecheck");
|
|
50
|
+
expect(result.checks[0].success).toBe(false);
|
|
51
|
+
expect(result.checks[0].exitCode).not.toBe(0);
|
|
52
|
+
expect(result.failureReason).toContain("typecheck failed");
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
test("runReview - multiple checks, stop on first failure", async () => {
|
|
56
|
+
const tempDir = mkdtempSync(join(tmpdir(), "nax-review-test-"));
|
|
57
|
+
|
|
58
|
+
const config: ReviewConfig = {
|
|
59
|
+
enabled: true,
|
|
60
|
+
checks: ["typecheck", "lint", "test"],
|
|
61
|
+
commands: {
|
|
62
|
+
typecheck: "echo 'typecheck ok'",
|
|
63
|
+
lint: "sh -c 'exit 1'",
|
|
64
|
+
test: "echo 'test ok'",
|
|
65
|
+
},
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
const result = await runReview(config, tempDir);
|
|
69
|
+
|
|
70
|
+
expect(result.success).toBe(false);
|
|
71
|
+
// Should only run typecheck and lint, not test (fail-fast)
|
|
72
|
+
expect(result.checks).toHaveLength(2);
|
|
73
|
+
expect(result.checks[0].check).toBe("typecheck");
|
|
74
|
+
expect(result.checks[0].success).toBe(true);
|
|
75
|
+
expect(result.checks[1].check).toBe("lint");
|
|
76
|
+
expect(result.checks[1].success).toBe(false);
|
|
77
|
+
expect(result.failureReason).toContain("lint failed");
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
test("runReview - uses review config commands when specified", async () => {
|
|
81
|
+
const tempDir = mkdtempSync(join(tmpdir(), "nax-review-test-"));
|
|
82
|
+
|
|
83
|
+
const config: ReviewConfig = {
|
|
84
|
+
enabled: true,
|
|
85
|
+
checks: ["test"],
|
|
86
|
+
commands: {
|
|
87
|
+
test: "echo 'custom test command'",
|
|
88
|
+
},
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
const result = await runReview(config, tempDir);
|
|
92
|
+
|
|
93
|
+
// Custom command from config.review.commands
|
|
94
|
+
expect(result.checks[0].command).toBe("echo 'custom test command'");
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
test("runReview - empty checks array", async () => {
|
|
98
|
+
const tempDir = mkdtempSync(join(tmpdir(), "nax-review-test-"));
|
|
99
|
+
|
|
100
|
+
const config: ReviewConfig = {
|
|
101
|
+
enabled: true,
|
|
102
|
+
checks: [],
|
|
103
|
+
commands: {},
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
const result = await runReview(config, tempDir);
|
|
107
|
+
|
|
108
|
+
expect(result.success).toBe(true);
|
|
109
|
+
expect(result.checks).toHaveLength(0);
|
|
110
|
+
expect(result.failureReason).toBeUndefined();
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
test("runReview - captures command output", async () => {
|
|
114
|
+
const tempDir = mkdtempSync(join(tmpdir(), "nax-review-test-"));
|
|
115
|
+
|
|
116
|
+
const config: ReviewConfig = {
|
|
117
|
+
enabled: true,
|
|
118
|
+
checks: ["test"],
|
|
119
|
+
commands: {
|
|
120
|
+
test: "echo 'Test output line 1' && echo 'Test output line 2'",
|
|
121
|
+
},
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
const result = await runReview(config, tempDir);
|
|
125
|
+
|
|
126
|
+
expect(result.success).toBe(true);
|
|
127
|
+
expect(result.checks[0].output).toContain("Test output line 1");
|
|
128
|
+
expect(result.checks[0].output).toContain("Test output line 2");
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
test("runReview - records duration", async () => {
|
|
132
|
+
const tempDir = mkdtempSync(join(tmpdir(), "nax-review-test-"));
|
|
133
|
+
|
|
134
|
+
const config: ReviewConfig = {
|
|
135
|
+
enabled: true,
|
|
136
|
+
checks: ["test"],
|
|
137
|
+
commands: {
|
|
138
|
+
test: "echo 'done'",
|
|
139
|
+
},
|
|
140
|
+
};
|
|
141
|
+
|
|
142
|
+
const result = await runReview(config, tempDir);
|
|
143
|
+
|
|
144
|
+
expect(result.success).toBe(true);
|
|
145
|
+
expect(result.checks[0].durationMs).toBeGreaterThan(0);
|
|
146
|
+
expect(result.totalDurationMs).toBeGreaterThan(0);
|
|
147
|
+
expect(result.totalDurationMs).toBeGreaterThanOrEqual(result.checks[0].durationMs);
|
|
148
|
+
});
|
|
149
|
+
});
|
|
@@ -0,0 +1,274 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Routing Stage Bug-021 Fix: Ensure "Task classified" log shows final routing state
|
|
3
|
+
*
|
|
4
|
+
* Tests that the "Task classified" log reflects the final routing state after all
|
|
5
|
+
* overrides (config cache overrides, greenfield detection, etc.) are applied.
|
|
6
|
+
*
|
|
7
|
+
* BUG-021: The log was showing raw LLM output even after overrides were applied.
|
|
8
|
+
* Fix: Ensure ctx.routing is set AFTER all overrides, and the log uses ctx.routing.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { afterEach, beforeEach, describe, expect, mock, test } from "bun:test";
|
|
12
|
+
import { mkdtemp, rm } from "node:fs/promises";
|
|
13
|
+
import { tmpdir } from "node:os";
|
|
14
|
+
import { join } from "node:path";
|
|
15
|
+
import type { NaxConfig } from "../../src/config/schema";
|
|
16
|
+
import { initLogger, resetLogger } from "../../src/logger";
|
|
17
|
+
import type { Logger } from "../../src/logger";
|
|
18
|
+
import { routingStage } from "../../src/pipeline/stages/routing";
|
|
19
|
+
import type { PipelineContext } from "../../src/pipeline/types";
|
|
20
|
+
import { PluginRegistry } from "../../src/plugins/registry";
|
|
21
|
+
import type { PRD, UserStory } from "../../src/prd/types";
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Helper: Create minimal test context
|
|
25
|
+
*/
|
|
26
|
+
function createTestContext(
|
|
27
|
+
workdir: string,
|
|
28
|
+
overrides?: Partial<PipelineContext>,
|
|
29
|
+
): PipelineContext {
|
|
30
|
+
const story: UserStory = {
|
|
31
|
+
id: "BUG-021-test",
|
|
32
|
+
title: "Add user authentication",
|
|
33
|
+
description: "Implement JWT-based authentication",
|
|
34
|
+
acceptanceCriteria: ["Secure token storage", "Token refresh", "Password hashing", "Session management"],
|
|
35
|
+
tags: ["security", "auth"],
|
|
36
|
+
dependencies: [],
|
|
37
|
+
status: "pending",
|
|
38
|
+
passes: false,
|
|
39
|
+
escalations: [],
|
|
40
|
+
attempts: 0,
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
const prd: PRD = {
|
|
44
|
+
project: "test-project",
|
|
45
|
+
feature: "test-feature",
|
|
46
|
+
branchName: "test-branch",
|
|
47
|
+
createdAt: new Date().toISOString(),
|
|
48
|
+
updatedAt: new Date().toISOString(),
|
|
49
|
+
userStories: [story],
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
const config: NaxConfig = {
|
|
53
|
+
version: 1,
|
|
54
|
+
models: {
|
|
55
|
+
fast: "claude-haiku-4-5",
|
|
56
|
+
balanced: "claude-sonnet-4-5",
|
|
57
|
+
powerful: "claude-opus-4-6",
|
|
58
|
+
},
|
|
59
|
+
autoMode: {
|
|
60
|
+
enabled: true,
|
|
61
|
+
defaultAgent: "nax-agent-claude",
|
|
62
|
+
fallbackOrder: ["nax-agent-claude"],
|
|
63
|
+
complexityRouting: {
|
|
64
|
+
simple: "fast",
|
|
65
|
+
medium: "balanced",
|
|
66
|
+
complex: "powerful",
|
|
67
|
+
expert: "powerful",
|
|
68
|
+
},
|
|
69
|
+
escalation: {
|
|
70
|
+
enabled: true,
|
|
71
|
+
tierOrder: [
|
|
72
|
+
{ tier: "fast", attempts: 2 },
|
|
73
|
+
{ tier: "balanced", attempts: 2 },
|
|
74
|
+
{ tier: "powerful", attempts: 1 },
|
|
75
|
+
],
|
|
76
|
+
escalateEntireBatch: true,
|
|
77
|
+
},
|
|
78
|
+
},
|
|
79
|
+
routing: {
|
|
80
|
+
strategy: "keyword",
|
|
81
|
+
},
|
|
82
|
+
execution: {
|
|
83
|
+
maxIterations: 100,
|
|
84
|
+
iterationDelayMs: 1000,
|
|
85
|
+
costLimit: 50,
|
|
86
|
+
sessionTimeoutSeconds: 600,
|
|
87
|
+
verificationTimeoutSeconds: 300,
|
|
88
|
+
maxStoriesPerFeature: 50,
|
|
89
|
+
rectification: {
|
|
90
|
+
enabled: true,
|
|
91
|
+
maxRetries: 2,
|
|
92
|
+
fullSuiteTimeoutSeconds: 120,
|
|
93
|
+
maxFailureSummaryChars: 2000,
|
|
94
|
+
abortOnIncreasingFailures: true,
|
|
95
|
+
},
|
|
96
|
+
contextProviderTokenBudget: 2000,
|
|
97
|
+
},
|
|
98
|
+
quality: {
|
|
99
|
+
requireTypecheck: false,
|
|
100
|
+
requireLint: false,
|
|
101
|
+
requireTests: true,
|
|
102
|
+
commands: {},
|
|
103
|
+
forceExit: false,
|
|
104
|
+
detectOpenHandles: true,
|
|
105
|
+
detectOpenHandlesRetries: 1,
|
|
106
|
+
gracePeriodMs: 5000,
|
|
107
|
+
drainTimeoutMs: 2000,
|
|
108
|
+
shell: "/bin/sh",
|
|
109
|
+
stripEnvVars: [],
|
|
110
|
+
environmentalEscalationDivisor: 2,
|
|
111
|
+
},
|
|
112
|
+
tdd: {
|
|
113
|
+
maxRetries: 3,
|
|
114
|
+
autoVerifyIsolation: true,
|
|
115
|
+
autoApproveVerifier: true,
|
|
116
|
+
strategy: "auto",
|
|
117
|
+
greenfieldDetection: true,
|
|
118
|
+
rollbackOnFailure: true,
|
|
119
|
+
},
|
|
120
|
+
constitution: {
|
|
121
|
+
enabled: false,
|
|
122
|
+
path: "constitution.md",
|
|
123
|
+
maxTokens: 2000,
|
|
124
|
+
},
|
|
125
|
+
analyze: {
|
|
126
|
+
llmEnhanced: false,
|
|
127
|
+
model: "balanced",
|
|
128
|
+
fallbackToKeywords: true,
|
|
129
|
+
maxCodebaseSummaryTokens: 4000,
|
|
130
|
+
},
|
|
131
|
+
review: {
|
|
132
|
+
enabled: true,
|
|
133
|
+
checks: ["test"],
|
|
134
|
+
commands: {},
|
|
135
|
+
},
|
|
136
|
+
plan: {
|
|
137
|
+
model: "balanced",
|
|
138
|
+
outputPath: "features",
|
|
139
|
+
},
|
|
140
|
+
acceptance: {
|
|
141
|
+
enabled: true,
|
|
142
|
+
maxRetries: 2,
|
|
143
|
+
generateTests: true,
|
|
144
|
+
testPath: "acceptance.test.ts",
|
|
145
|
+
},
|
|
146
|
+
context: {
|
|
147
|
+
testCoverage: {
|
|
148
|
+
enabled: true,
|
|
149
|
+
detail: "names-and-counts",
|
|
150
|
+
maxTokens: 500,
|
|
151
|
+
testPattern: "**/*.test.{ts,js,tsx,jsx}",
|
|
152
|
+
scopeToStory: true,
|
|
153
|
+
},
|
|
154
|
+
},
|
|
155
|
+
};
|
|
156
|
+
|
|
157
|
+
return {
|
|
158
|
+
workdir,
|
|
159
|
+
story,
|
|
160
|
+
stories: [story],
|
|
161
|
+
prd,
|
|
162
|
+
config,
|
|
163
|
+
plugins: new PluginRegistry([]),
|
|
164
|
+
...overrides,
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
describe("Routing Stage - BUG-021: Task classified log shows final routing state", () => {
|
|
169
|
+
let workdir: string;
|
|
170
|
+
let capturedLogs: Array<{
|
|
171
|
+
level: string;
|
|
172
|
+
stage: string;
|
|
173
|
+
message: string;
|
|
174
|
+
data?: Record<string, unknown>;
|
|
175
|
+
}>;
|
|
176
|
+
|
|
177
|
+
beforeEach(async () => {
|
|
178
|
+
workdir = await mkdtemp(join(tmpdir(), "nax-bug-021-test-"));
|
|
179
|
+
capturedLogs = [];
|
|
180
|
+
|
|
181
|
+
// Initialize logger with debug level to capture all logs
|
|
182
|
+
initLogger({ level: "debug" });
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
afterEach(async () => {
|
|
186
|
+
await rm(workdir, { recursive: true, force: true });
|
|
187
|
+
resetLogger();
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
test("logs final routing state (not raw LLM output) when greenfield override is applied", async () => {
|
|
191
|
+
// Create source files but no test files (triggers greenfield detection)
|
|
192
|
+
await Bun.write(join(workdir, "src/index.ts"), "export const foo = 42;");
|
|
193
|
+
|
|
194
|
+
const ctx = createTestContext(workdir);
|
|
195
|
+
const result = await routingStage.execute(ctx);
|
|
196
|
+
|
|
197
|
+
expect(result.action).toBe("continue");
|
|
198
|
+
|
|
199
|
+
// Verify ctx.routing has the final state (greenfield override applied)
|
|
200
|
+
expect(ctx.routing).toBeDefined();
|
|
201
|
+
expect(ctx.routing?.testStrategy).toBe("test-after");
|
|
202
|
+
expect(ctx.routing?.complexity).toBe("complex");
|
|
203
|
+
expect(ctx.routing?.modelTier).toBe("powerful");
|
|
204
|
+
|
|
205
|
+
// Verify reasoning mentions the greenfield override
|
|
206
|
+
expect(ctx.routing?.reasoning).toContain("GREENFIELD OVERRIDE");
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
test("logs final routing state when using cached routing with greenfield override", async () => {
|
|
210
|
+
// Create source files but no test files
|
|
211
|
+
await Bun.write(join(workdir, "src/index.ts"), "export const foo = 42;");
|
|
212
|
+
|
|
213
|
+
const ctx = createTestContext(workdir);
|
|
214
|
+
// Simulate cached routing that would normally use TDD
|
|
215
|
+
ctx.story.routing = {
|
|
216
|
+
complexity: "medium",
|
|
217
|
+
testStrategy: "three-session-tdd",
|
|
218
|
+
reasoning: "Cached from previous run",
|
|
219
|
+
};
|
|
220
|
+
|
|
221
|
+
const result = await routingStage.execute(ctx);
|
|
222
|
+
|
|
223
|
+
expect(result.action).toBe("continue");
|
|
224
|
+
|
|
225
|
+
// Verify final state has greenfield override applied to cached TDD strategy
|
|
226
|
+
expect(ctx.routing).toBeDefined();
|
|
227
|
+
expect(ctx.routing?.testStrategy).toBe("test-after");
|
|
228
|
+
expect(ctx.routing?.complexity).toBe("medium");
|
|
229
|
+
expect(ctx.routing?.modelTier).toBe("balanced");
|
|
230
|
+
expect(ctx.routing?.reasoning).toContain("GREENFIELD OVERRIDE");
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
test("logs final routing state when no overrides are needed", async () => {
|
|
234
|
+
// Create test files so greenfield detection doesn't trigger
|
|
235
|
+
await Bun.write(join(workdir, "src/index.test.ts"), "test('foo', () => {})");
|
|
236
|
+
|
|
237
|
+
const ctx = createTestContext(workdir);
|
|
238
|
+
const result = await routingStage.execute(ctx);
|
|
239
|
+
|
|
240
|
+
expect(result.action).toBe("continue");
|
|
241
|
+
|
|
242
|
+
// Verify ctx.routing shows final state (no greenfield override)
|
|
243
|
+
expect(ctx.routing).toBeDefined();
|
|
244
|
+
expect(ctx.routing?.testStrategy).toMatch(/three-session-tdd/);
|
|
245
|
+
expect(ctx.routing?.complexity).toBe("complex");
|
|
246
|
+
expect(ctx.routing?.modelTier).toBe("powerful");
|
|
247
|
+
expect(ctx.routing?.reasoning).not.toContain("GREENFIELD OVERRIDE");
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
test("ctx.routing is set after all overrides are applied", async () => {
|
|
251
|
+
// Create source files but no test files (triggers greenfield detection)
|
|
252
|
+
await Bun.write(join(workdir, "src/auth.ts"), "export function authenticate() {}");
|
|
253
|
+
|
|
254
|
+
const ctx = createTestContext(workdir);
|
|
255
|
+
// Set initial cached routing
|
|
256
|
+
ctx.story.routing = {
|
|
257
|
+
complexity: "simple",
|
|
258
|
+
testStrategy: "three-session-tdd-lite",
|
|
259
|
+
reasoning: "Cached simple routing",
|
|
260
|
+
};
|
|
261
|
+
|
|
262
|
+
const result = await routingStage.execute(ctx);
|
|
263
|
+
|
|
264
|
+
expect(result.action).toBe("continue");
|
|
265
|
+
|
|
266
|
+
// After routing stage completes, ctx.routing should reflect:
|
|
267
|
+
// 1. Cached complexity: "simple"
|
|
268
|
+
// 2. Fresh modelTier: derived from config
|
|
269
|
+
// 3. Greenfield override: test-after instead of TDD
|
|
270
|
+
expect(ctx.routing?.complexity).toBe("simple");
|
|
271
|
+
expect(ctx.routing?.testStrategy).toBe("test-after");
|
|
272
|
+
expect(ctx.routing?.modelTier).toBe("fast"); // simple -> fast
|
|
273
|
+
});
|
|
274
|
+
});
|
|
@@ -0,0 +1,286 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Routing Stage Greenfield Detection Tests
|
|
3
|
+
*
|
|
4
|
+
* Tests BUG-010 fix: greenfield detection forces test-after strategy
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { afterEach, beforeEach, describe, expect, test } from "bun:test";
|
|
8
|
+
import { mkdtemp, rm } from "node:fs/promises";
|
|
9
|
+
import { tmpdir } from "node:os";
|
|
10
|
+
import { join } from "node:path";
|
|
11
|
+
import type { NaxConfig } from "../../src/config/schema";
|
|
12
|
+
import { initLogger, resetLogger } from "../../src/logger";
|
|
13
|
+
import { routingStage } from "../../src/pipeline/stages/routing";
|
|
14
|
+
import type { PipelineContext } from "../../src/pipeline/types";
|
|
15
|
+
import { PluginRegistry } from "../../src/plugins/registry";
|
|
16
|
+
import type { PRD, UserStory } from "../../src/prd/types";
|
|
17
|
+
|
|
18
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
19
|
+
// Test Helpers
|
|
20
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
21
|
+
|
|
22
|
+
async function createTestFile(workdir: string, filepath: string, content = ""): Promise<void> {
|
|
23
|
+
const fullPath = join(workdir, filepath);
|
|
24
|
+
await Bun.write(fullPath, content);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/** Helper: Create minimal test context */
|
|
28
|
+
function createTestContext(
|
|
29
|
+
workdir: string,
|
|
30
|
+
greenfieldDetectionEnabled = true,
|
|
31
|
+
overrides?: Partial<PipelineContext>,
|
|
32
|
+
): PipelineContext {
|
|
33
|
+
const story: UserStory = {
|
|
34
|
+
id: "US-001",
|
|
35
|
+
title: "Add user authentication",
|
|
36
|
+
description: "Implement JWT-based authentication",
|
|
37
|
+
acceptanceCriteria: ["Secure token storage", "Token refresh", "Password hashing", "Session management"],
|
|
38
|
+
tags: ["security", "auth"],
|
|
39
|
+
dependencies: [],
|
|
40
|
+
status: "pending",
|
|
41
|
+
passes: false,
|
|
42
|
+
escalations: [],
|
|
43
|
+
attempts: 0,
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
const prd: PRD = {
|
|
47
|
+
project: "test-project",
|
|
48
|
+
feature: "test-feature",
|
|
49
|
+
branchName: "test-branch",
|
|
50
|
+
createdAt: new Date().toISOString(),
|
|
51
|
+
updatedAt: new Date().toISOString(),
|
|
52
|
+
userStories: [story],
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
const config: NaxConfig = {
|
|
56
|
+
version: 1,
|
|
57
|
+
models: {
|
|
58
|
+
fast: "claude-haiku-4-5",
|
|
59
|
+
balanced: "claude-sonnet-4-5",
|
|
60
|
+
powerful: "claude-opus-4-6",
|
|
61
|
+
},
|
|
62
|
+
autoMode: {
|
|
63
|
+
enabled: true,
|
|
64
|
+
defaultAgent: "nax-agent-claude",
|
|
65
|
+
fallbackOrder: ["nax-agent-claude"],
|
|
66
|
+
complexityRouting: {
|
|
67
|
+
simple: "fast",
|
|
68
|
+
medium: "balanced",
|
|
69
|
+
complex: "powerful",
|
|
70
|
+
expert: "powerful",
|
|
71
|
+
},
|
|
72
|
+
escalation: {
|
|
73
|
+
enabled: true,
|
|
74
|
+
tierOrder: [
|
|
75
|
+
{ tier: "fast", attempts: 2 },
|
|
76
|
+
{ tier: "balanced", attempts: 2 },
|
|
77
|
+
{ tier: "powerful", attempts: 1 },
|
|
78
|
+
],
|
|
79
|
+
escalateEntireBatch: true,
|
|
80
|
+
},
|
|
81
|
+
},
|
|
82
|
+
routing: {
|
|
83
|
+
strategy: "keyword",
|
|
84
|
+
},
|
|
85
|
+
execution: {
|
|
86
|
+
maxIterations: 100,
|
|
87
|
+
iterationDelayMs: 1000,
|
|
88
|
+
costLimit: 50,
|
|
89
|
+
sessionTimeoutSeconds: 600,
|
|
90
|
+
verificationTimeoutSeconds: 300,
|
|
91
|
+
maxStoriesPerFeature: 50,
|
|
92
|
+
rectification: {
|
|
93
|
+
enabled: true,
|
|
94
|
+
maxRetries: 2,
|
|
95
|
+
fullSuiteTimeoutSeconds: 120,
|
|
96
|
+
maxFailureSummaryChars: 2000,
|
|
97
|
+
abortOnIncreasingFailures: true,
|
|
98
|
+
},
|
|
99
|
+
contextProviderTokenBudget: 2000,
|
|
100
|
+
},
|
|
101
|
+
quality: {
|
|
102
|
+
requireTypecheck: false,
|
|
103
|
+
requireLint: false,
|
|
104
|
+
requireTests: true,
|
|
105
|
+
commands: {},
|
|
106
|
+
forceExit: false,
|
|
107
|
+
detectOpenHandles: true,
|
|
108
|
+
detectOpenHandlesRetries: 1,
|
|
109
|
+
gracePeriodMs: 5000,
|
|
110
|
+
drainTimeoutMs: 2000,
|
|
111
|
+
shell: "/bin/sh",
|
|
112
|
+
stripEnvVars: [],
|
|
113
|
+
environmentalEscalationDivisor: 2,
|
|
114
|
+
},
|
|
115
|
+
tdd: {
|
|
116
|
+
maxRetries: 3,
|
|
117
|
+
autoVerifyIsolation: true,
|
|
118
|
+
autoApproveVerifier: true,
|
|
119
|
+
strategy: "auto",
|
|
120
|
+
greenfieldDetection: greenfieldDetectionEnabled,
|
|
121
|
+
rollbackOnFailure: true,
|
|
122
|
+
},
|
|
123
|
+
constitution: {
|
|
124
|
+
enabled: false,
|
|
125
|
+
path: "constitution.md",
|
|
126
|
+
maxTokens: 2000,
|
|
127
|
+
},
|
|
128
|
+
analyze: {
|
|
129
|
+
llmEnhanced: false,
|
|
130
|
+
model: "balanced",
|
|
131
|
+
fallbackToKeywords: true,
|
|
132
|
+
maxCodebaseSummaryTokens: 4000,
|
|
133
|
+
},
|
|
134
|
+
review: {
|
|
135
|
+
enabled: true,
|
|
136
|
+
checks: ["test"],
|
|
137
|
+
commands: {},
|
|
138
|
+
},
|
|
139
|
+
plan: {
|
|
140
|
+
model: "balanced",
|
|
141
|
+
outputPath: "features",
|
|
142
|
+
},
|
|
143
|
+
acceptance: {
|
|
144
|
+
enabled: true,
|
|
145
|
+
maxRetries: 2,
|
|
146
|
+
generateTests: true,
|
|
147
|
+
testPath: "acceptance.test.ts",
|
|
148
|
+
},
|
|
149
|
+
context: {
|
|
150
|
+
testCoverage: {
|
|
151
|
+
enabled: true,
|
|
152
|
+
detail: "names-and-counts",
|
|
153
|
+
maxTokens: 500,
|
|
154
|
+
testPattern: "**/*.test.{ts,js,tsx,jsx}",
|
|
155
|
+
scopeToStory: true,
|
|
156
|
+
},
|
|
157
|
+
},
|
|
158
|
+
};
|
|
159
|
+
|
|
160
|
+
return {
|
|
161
|
+
workdir,
|
|
162
|
+
story,
|
|
163
|
+
stories: [story],
|
|
164
|
+
prd,
|
|
165
|
+
config,
|
|
166
|
+
plugins: new PluginRegistry([]),
|
|
167
|
+
...overrides,
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
172
|
+
// Tests
|
|
173
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
174
|
+
|
|
175
|
+
describe("Routing Stage - Greenfield Detection (BUG-010)", () => {
|
|
176
|
+
let workdir: string;
|
|
177
|
+
|
|
178
|
+
beforeEach(async () => {
|
|
179
|
+
workdir = await mkdtemp(join(tmpdir(), "nax-routing-greenfield-test-"));
|
|
180
|
+
await initLogger({ level: "silent" });
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
afterEach(async () => {
|
|
184
|
+
await rm(workdir, { recursive: true, force: true });
|
|
185
|
+
resetLogger();
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
test("forces test-after when no test files exist (greenfield)", async () => {
|
|
189
|
+
// Create source files but no test files
|
|
190
|
+
await createTestFile(workdir, "src/index.ts", "export const foo = 42;");
|
|
191
|
+
|
|
192
|
+
const ctx = createTestContext(workdir, true);
|
|
193
|
+
const result = await routingStage.execute(ctx);
|
|
194
|
+
|
|
195
|
+
expect(result.action).toBe("continue");
|
|
196
|
+
expect(ctx.routing).toBeDefined();
|
|
197
|
+
expect(ctx.routing?.testStrategy).toBe("test-after");
|
|
198
|
+
expect(ctx.routing?.reasoning).toContain("GREENFIELD OVERRIDE");
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
test("preserves TDD when test files exist", async () => {
|
|
202
|
+
// Create test files
|
|
203
|
+
await createTestFile(workdir, "src/index.test.ts", "test('foo', () => {})");
|
|
204
|
+
|
|
205
|
+
const ctx = createTestContext(workdir, true);
|
|
206
|
+
const result = await routingStage.execute(ctx);
|
|
207
|
+
|
|
208
|
+
expect(result.action).toBe("continue");
|
|
209
|
+
expect(ctx.routing).toBeDefined();
|
|
210
|
+
// Should use TDD for complex stories with existing tests
|
|
211
|
+
expect(ctx.routing?.testStrategy).toMatch(/three-session-tdd/);
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
test("respects greenfieldDetection config disabled", async () => {
|
|
215
|
+
// No test files, but greenfield detection disabled
|
|
216
|
+
await createTestFile(workdir, "src/index.ts", "export const foo = 42;");
|
|
217
|
+
|
|
218
|
+
const ctx = createTestContext(workdir, false); // greenfieldDetection = false
|
|
219
|
+
const result = await routingStage.execute(ctx);
|
|
220
|
+
|
|
221
|
+
expect(result.action).toBe("continue");
|
|
222
|
+
expect(ctx.routing).toBeDefined();
|
|
223
|
+
// Should use TDD even though greenfield, because detection is disabled
|
|
224
|
+
expect(ctx.routing?.testStrategy).toMatch(/three-session-tdd/);
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
test("only overrides TDD strategies, not test-after", async () => {
|
|
228
|
+
// Create a simple story that would normally get test-after
|
|
229
|
+
const ctx = createTestContext(workdir, true);
|
|
230
|
+
ctx.story.title = "Fix typo in README";
|
|
231
|
+
ctx.story.description = "Update README.md";
|
|
232
|
+
ctx.story.acceptanceCriteria = ["Typo fixed"];
|
|
233
|
+
|
|
234
|
+
const result = await routingStage.execute(ctx);
|
|
235
|
+
|
|
236
|
+
expect(result.action).toBe("continue");
|
|
237
|
+
expect(ctx.routing).toBeDefined();
|
|
238
|
+
// test-after strategy should remain unchanged
|
|
239
|
+
expect(ctx.routing?.testStrategy).toBe("test-after");
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
test("handles both TDD and TDD-lite strategies", async () => {
|
|
243
|
+
// Test that greenfield detection works for both TDD variants
|
|
244
|
+
await createTestFile(workdir, "src/index.ts", "export const foo = 42;");
|
|
245
|
+
|
|
246
|
+
const ctx = createTestContext(workdir, true);
|
|
247
|
+
ctx.story.routing = {
|
|
248
|
+
complexity: "medium",
|
|
249
|
+
testStrategy: "three-session-tdd-lite",
|
|
250
|
+
reasoning: "Pre-cached routing",
|
|
251
|
+
};
|
|
252
|
+
|
|
253
|
+
const result = await routingStage.execute(ctx);
|
|
254
|
+
|
|
255
|
+
expect(result.action).toBe("continue");
|
|
256
|
+
expect(ctx.routing).toBeDefined();
|
|
257
|
+
expect(ctx.routing?.testStrategy).toBe("test-after");
|
|
258
|
+
expect(ctx.routing?.reasoning).toContain("GREENFIELD OVERRIDE");
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
test("ignores test files in node_modules", async () => {
|
|
262
|
+
// Create test file in node_modules (should be ignored)
|
|
263
|
+
await createTestFile(workdir, "node_modules/lib/foo.test.ts", "test('foo', () => {})");
|
|
264
|
+
|
|
265
|
+
const ctx = createTestContext(workdir, true);
|
|
266
|
+
const result = await routingStage.execute(ctx);
|
|
267
|
+
|
|
268
|
+
expect(result.action).toBe("continue");
|
|
269
|
+
expect(ctx.routing).toBeDefined();
|
|
270
|
+
// Should treat as greenfield since node_modules is ignored
|
|
271
|
+
expect(ctx.routing?.testStrategy).toBe("test-after");
|
|
272
|
+
});
|
|
273
|
+
|
|
274
|
+
test("detects various test file patterns", async () => {
|
|
275
|
+
// Test .spec.ts pattern
|
|
276
|
+
await createTestFile(workdir, "src/foo.spec.ts", "describe('foo', () => {})");
|
|
277
|
+
|
|
278
|
+
const ctx = createTestContext(workdir, true);
|
|
279
|
+
const result = await routingStage.execute(ctx);
|
|
280
|
+
|
|
281
|
+
expect(result.action).toBe("continue");
|
|
282
|
+
expect(ctx.routing).toBeDefined();
|
|
283
|
+
// Should preserve TDD because .spec.ts files exist
|
|
284
|
+
expect(ctx.routing?.testStrategy).toMatch(/three-session-tdd/);
|
|
285
|
+
});
|
|
286
|
+
});
|