@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,175 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Codebase Scanner
|
|
3
|
+
*
|
|
4
|
+
* Scans the project directory to generate a summary for LLM classification.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { existsSync } from "node:fs";
|
|
8
|
+
import { join } from "node:path";
|
|
9
|
+
import type { CodebaseScan } from "./types";
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Scan codebase to generate summary for LLM classification.
|
|
13
|
+
*
|
|
14
|
+
* Generates:
|
|
15
|
+
* - File tree (src/ directory, max depth 3)
|
|
16
|
+
* - Package.json dependencies
|
|
17
|
+
* - Test pattern detection
|
|
18
|
+
*
|
|
19
|
+
* @param workdir - Project root directory
|
|
20
|
+
* @returns Codebase scan result
|
|
21
|
+
*
|
|
22
|
+
* @example
|
|
23
|
+
* ```ts
|
|
24
|
+
* const scan = await scanCodebase("/path/to/project");
|
|
25
|
+
* console.log(scan.fileTree);
|
|
26
|
+
* console.log(scan.dependencies);
|
|
27
|
+
* ```
|
|
28
|
+
*/
|
|
29
|
+
export async function scanCodebase(workdir: string): Promise<CodebaseScan> {
|
|
30
|
+
const srcPath = join(workdir, "src");
|
|
31
|
+
const packageJsonPath = join(workdir, "package.json");
|
|
32
|
+
|
|
33
|
+
// Generate file tree (src/ only, max depth 3)
|
|
34
|
+
const fileTree = existsSync(srcPath) ? await generateFileTree(srcPath, 3) : "No src/ directory";
|
|
35
|
+
|
|
36
|
+
// Extract dependencies from package.json
|
|
37
|
+
let dependencies: Record<string, string> = {};
|
|
38
|
+
let devDependencies: Record<string, string> = {};
|
|
39
|
+
|
|
40
|
+
if (existsSync(packageJsonPath)) {
|
|
41
|
+
try {
|
|
42
|
+
const pkg = await Bun.file(packageJsonPath).json();
|
|
43
|
+
dependencies = pkg.dependencies || {};
|
|
44
|
+
devDependencies = pkg.devDependencies || {};
|
|
45
|
+
} catch {
|
|
46
|
+
// Invalid package.json, use empty deps
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Detect test patterns
|
|
51
|
+
const testPatterns = detectTestPatterns(workdir, dependencies, devDependencies);
|
|
52
|
+
|
|
53
|
+
return {
|
|
54
|
+
fileTree,
|
|
55
|
+
dependencies,
|
|
56
|
+
devDependencies,
|
|
57
|
+
testPatterns,
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Generate file tree for a directory with depth limit.
|
|
63
|
+
*
|
|
64
|
+
* @param dir - Directory path
|
|
65
|
+
* @param maxDepth - Maximum depth to traverse
|
|
66
|
+
* @param currentDepth - Current depth (internal)
|
|
67
|
+
* @param prefix - Line prefix for formatting (internal)
|
|
68
|
+
* @returns Formatted file tree string
|
|
69
|
+
*/
|
|
70
|
+
async function generateFileTree(dir: string, maxDepth: number, currentDepth = 0, prefix = ""): Promise<string> {
|
|
71
|
+
if (currentDepth >= maxDepth) {
|
|
72
|
+
return "";
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const entries: string[] = [];
|
|
76
|
+
|
|
77
|
+
try {
|
|
78
|
+
const dirEntries = Array.from(
|
|
79
|
+
new Bun.Glob("*").scanSync({
|
|
80
|
+
cwd: dir,
|
|
81
|
+
onlyFiles: false,
|
|
82
|
+
}),
|
|
83
|
+
);
|
|
84
|
+
|
|
85
|
+
// Sort: directories first, then files
|
|
86
|
+
dirEntries.sort((a, b) => {
|
|
87
|
+
const aIsDir = !a.includes(".");
|
|
88
|
+
const bIsDir = !b.includes(".");
|
|
89
|
+
if (aIsDir && !bIsDir) return -1;
|
|
90
|
+
if (!aIsDir && bIsDir) return 1;
|
|
91
|
+
return a.localeCompare(b);
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
for (let i = 0; i < dirEntries.length; i++) {
|
|
95
|
+
const entry = dirEntries[i];
|
|
96
|
+
const fullPath = join(dir, entry);
|
|
97
|
+
const isLast = i === dirEntries.length - 1;
|
|
98
|
+
const connector = isLast ? "└── " : "├── ";
|
|
99
|
+
const childPrefix = isLast ? " " : "│ ";
|
|
100
|
+
|
|
101
|
+
// Check if directory
|
|
102
|
+
const stat = await Bun.file(fullPath).stat();
|
|
103
|
+
const isDir = stat.isDirectory();
|
|
104
|
+
|
|
105
|
+
entries.push(`${prefix}${connector}${entry}${isDir ? "/" : ""}`);
|
|
106
|
+
|
|
107
|
+
// Recurse into directories
|
|
108
|
+
if (isDir) {
|
|
109
|
+
const subtree = await generateFileTree(fullPath, maxDepth, currentDepth + 1, prefix + childPrefix);
|
|
110
|
+
if (subtree) {
|
|
111
|
+
entries.push(subtree);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
} catch {
|
|
116
|
+
// Directory not accessible, skip
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
return entries.join("\n");
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Detect test patterns from directory structure and dependencies.
|
|
124
|
+
*
|
|
125
|
+
* Checks for:
|
|
126
|
+
* - Test framework (vitest, jest, bun:test, mocha, etc.)
|
|
127
|
+
* - Test directory structure (test/, __tests__/)
|
|
128
|
+
* - Test file patterns (*.test.ts, *.spec.ts)
|
|
129
|
+
*
|
|
130
|
+
* @param workdir - Project root directory
|
|
131
|
+
* @param dependencies - Production dependencies
|
|
132
|
+
* @param devDependencies - Dev dependencies
|
|
133
|
+
* @returns Array of detected patterns
|
|
134
|
+
*/
|
|
135
|
+
function detectTestPatterns(
|
|
136
|
+
workdir: string,
|
|
137
|
+
dependencies: Record<string, string>,
|
|
138
|
+
devDependencies: Record<string, string>,
|
|
139
|
+
): string[] {
|
|
140
|
+
const patterns: string[] = [];
|
|
141
|
+
const allDeps = { ...dependencies, ...devDependencies };
|
|
142
|
+
|
|
143
|
+
// Detect test framework
|
|
144
|
+
if (allDeps.vitest) {
|
|
145
|
+
patterns.push("Test framework: vitest");
|
|
146
|
+
} else if (allDeps.jest || allDeps["@jest/globals"]) {
|
|
147
|
+
patterns.push("Test framework: jest");
|
|
148
|
+
} else if (allDeps.mocha) {
|
|
149
|
+
patterns.push("Test framework: mocha");
|
|
150
|
+
} else if (allDeps.ava) {
|
|
151
|
+
patterns.push("Test framework: ava");
|
|
152
|
+
} else {
|
|
153
|
+
// Check for bun:test (no package.json entry)
|
|
154
|
+
patterns.push("Test framework: likely bun:test (no framework dependency)");
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// Detect test directory
|
|
158
|
+
if (existsSync(join(workdir, "test"))) {
|
|
159
|
+
patterns.push("Test directory: test/");
|
|
160
|
+
}
|
|
161
|
+
if (existsSync(join(workdir, "__tests__"))) {
|
|
162
|
+
patterns.push("Test directory: __tests__/");
|
|
163
|
+
}
|
|
164
|
+
if (existsSync(join(workdir, "tests"))) {
|
|
165
|
+
patterns.push("Test directory: tests/");
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// Detect test file patterns
|
|
169
|
+
const hasTestFiles = existsSync(join(workdir, "test")) || existsSync(join(workdir, "src"));
|
|
170
|
+
if (hasTestFiles) {
|
|
171
|
+
patterns.push("Test files: *.test.ts, *.spec.ts");
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
return patterns;
|
|
175
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Analyze Module Types
|
|
3
|
+
*
|
|
4
|
+
* Types for codebase scanning and LLM-enhanced classification.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { Complexity } from "../config";
|
|
8
|
+
|
|
9
|
+
/** Codebase scan result */
|
|
10
|
+
export interface CodebaseScan {
|
|
11
|
+
/** File tree (src/ directory, max depth 3) */
|
|
12
|
+
fileTree: string;
|
|
13
|
+
/** Package dependencies */
|
|
14
|
+
dependencies: Record<string, string>;
|
|
15
|
+
/** Dev dependencies */
|
|
16
|
+
devDependencies: Record<string, string>;
|
|
17
|
+
/** Detected test patterns */
|
|
18
|
+
testPatterns: string[];
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/** LLM classification result for a single story */
|
|
22
|
+
export interface StoryClassification {
|
|
23
|
+
/** Story ID (e.g., "US-001") */
|
|
24
|
+
storyId: string;
|
|
25
|
+
/** Classified complexity */
|
|
26
|
+
complexity: Complexity;
|
|
27
|
+
/** Context files to inject into agent prompt before execution */
|
|
28
|
+
contextFiles: string[];
|
|
29
|
+
/** Reasoning for the classification */
|
|
30
|
+
reasoning: string;
|
|
31
|
+
/** Estimated lines of code to change */
|
|
32
|
+
estimatedLOC: number;
|
|
33
|
+
/** Potential implementation risks */
|
|
34
|
+
risks: string[];
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/** LLM classifier response (array of classifications) */
|
|
38
|
+
export type ClassifierResponse = StoryClassification[];
|
|
39
|
+
|
|
40
|
+
/** Classification method used */
|
|
41
|
+
export type ClassificationMethod = "llm" | "keyword-fallback";
|
|
42
|
+
|
|
43
|
+
/** Classification result with metadata */
|
|
44
|
+
export interface ClassificationResult {
|
|
45
|
+
/** Classification data */
|
|
46
|
+
classifications: StoryClassification[];
|
|
47
|
+
/** Method used (llm or keyword-fallback) */
|
|
48
|
+
method: ClassificationMethod;
|
|
49
|
+
/** Error message if LLM failed and fallback was used */
|
|
50
|
+
fallbackReason?: string;
|
|
51
|
+
}
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Accept Command
|
|
3
|
+
*
|
|
4
|
+
* Allows manual override of failed acceptance criteria.
|
|
5
|
+
* Stores override in prd.json with reason.
|
|
6
|
+
*
|
|
7
|
+
* Usage:
|
|
8
|
+
* nax accept --override AC-2 "intentional: using lazy expiry instead of exact timing"
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import path from "node:path";
|
|
12
|
+
import { findProjectDir, validateDirectory } from "../config";
|
|
13
|
+
import { NaxError } from "../errors";
|
|
14
|
+
import { getLogger } from "../logger";
|
|
15
|
+
import { loadPRD, savePRD } from "../prd";
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Accept command options.
|
|
19
|
+
*/
|
|
20
|
+
export interface AcceptOptions {
|
|
21
|
+
/** Feature name */
|
|
22
|
+
feature: string;
|
|
23
|
+
/** AC ID to override (e.g., "AC-2") */
|
|
24
|
+
override: string;
|
|
25
|
+
/** Reason for accepting despite test failure */
|
|
26
|
+
reason: string;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Execute the accept command.
|
|
31
|
+
*
|
|
32
|
+
* Loads the PRD, adds the AC override with reason, and saves.
|
|
33
|
+
*
|
|
34
|
+
* @param options - Command options
|
|
35
|
+
*
|
|
36
|
+
* @example
|
|
37
|
+
* ```bash
|
|
38
|
+
* nax accept --feature auth-system --override AC-2 "intentional: lazy expiry"
|
|
39
|
+
* ```
|
|
40
|
+
*/
|
|
41
|
+
export async function acceptCommand(options: AcceptOptions): Promise<void> {
|
|
42
|
+
const logger = getLogger();
|
|
43
|
+
const { feature, override, reason } = options;
|
|
44
|
+
|
|
45
|
+
// Validate AC ID format
|
|
46
|
+
if (!override.match(/^AC-\d+$/i)) {
|
|
47
|
+
logger.error("cli", "Invalid AC ID format", { override, expected: "AC-1, AC-2, etc." });
|
|
48
|
+
throw new NaxError("Invalid AC ID format", "INVALID_AC_ID", { override, expected: "AC-1, AC-2, etc." });
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Normalize AC ID to uppercase
|
|
52
|
+
const acId = override.toUpperCase();
|
|
53
|
+
|
|
54
|
+
// Find project directory
|
|
55
|
+
const projectDirResult = findProjectDir(process.cwd());
|
|
56
|
+
if (!projectDirResult) {
|
|
57
|
+
logger.error("cli", "Not in a nax project directory", { hint: "Run 'nax init' first" });
|
|
58
|
+
throw new NaxError("Not in a nax project directory", "PROJECT_NOT_FOUND", { hint: "Run 'nax init' first" });
|
|
59
|
+
}
|
|
60
|
+
const projectDir = projectDirResult;
|
|
61
|
+
|
|
62
|
+
// Validate directory
|
|
63
|
+
try {
|
|
64
|
+
validateDirectory(projectDir);
|
|
65
|
+
} catch (err) {
|
|
66
|
+
logger.error("cli", "Invalid project directory", { error: (err as Error).message });
|
|
67
|
+
throw new NaxError("Invalid project directory", "INVALID_DIRECTORY", { error: (err as Error).message });
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// Build path to feature PRD
|
|
71
|
+
const featureDir = path.join(projectDir, "nax", "features", feature);
|
|
72
|
+
const prdPath = path.join(featureDir, "prd.json");
|
|
73
|
+
|
|
74
|
+
// Check if feature exists
|
|
75
|
+
const prdFile = Bun.file(prdPath);
|
|
76
|
+
if (!(await prdFile.exists())) {
|
|
77
|
+
logger.error("cli", "Feature not found", { feature, prdPath });
|
|
78
|
+
throw new NaxError("Feature not found", "FEATURE_NOT_FOUND", { feature, prdPath });
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// Load PRD
|
|
82
|
+
const prd = await loadPRD(prdPath);
|
|
83
|
+
|
|
84
|
+
// Add override
|
|
85
|
+
if (!prd.acceptanceOverrides) {
|
|
86
|
+
prd.acceptanceOverrides = {};
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
if (prd.acceptanceOverrides[acId]) {
|
|
90
|
+
logger.warn("cli", "Override already exists", {
|
|
91
|
+
acId,
|
|
92
|
+
previous: prd.acceptanceOverrides[acId],
|
|
93
|
+
new: reason,
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
prd.acceptanceOverrides[acId] = reason;
|
|
98
|
+
|
|
99
|
+
// Save PRD
|
|
100
|
+
await savePRD(prd, prdPath);
|
|
101
|
+
|
|
102
|
+
logger.info("cli", "✓ Override added", {
|
|
103
|
+
acId,
|
|
104
|
+
reason,
|
|
105
|
+
prdPath,
|
|
106
|
+
hint: `Re-run acceptance tests: bun test ${path.join(featureDir, "acceptance.test.ts")}`,
|
|
107
|
+
});
|
|
108
|
+
}
|
|
@@ -0,0 +1,284 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Analyze Parser & Classification
|
|
3
|
+
*
|
|
4
|
+
* Extracted from analyze.ts: spec parsing, keyword classification,
|
|
5
|
+
* codebase context building, and reclassification logic.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { existsSync } from "node:fs";
|
|
9
|
+
import { join } from "node:path";
|
|
10
|
+
import { getAgent } from "../agents/registry";
|
|
11
|
+
import { scanCodebase } from "../analyze/scanner";
|
|
12
|
+
import type { CodebaseScan } from "../analyze/types";
|
|
13
|
+
import type { NaxConfig } from "../config";
|
|
14
|
+
import { resolveModel } from "../config/schema";
|
|
15
|
+
import { getLogger } from "../logger";
|
|
16
|
+
import type { PRD, UserStory } from "../prd";
|
|
17
|
+
import { loadPRD } from "../prd";
|
|
18
|
+
import { classifyComplexity, routeTask } from "../routing";
|
|
19
|
+
|
|
20
|
+
/** Parse user stories from spec.md markdown */
|
|
21
|
+
export function parseUserStoriesFromSpec(markdown: string): UserStory[] {
|
|
22
|
+
const stories: UserStory[] = [];
|
|
23
|
+
const lines = markdown.split("\n");
|
|
24
|
+
|
|
25
|
+
let currentStory: Partial<UserStory> | null = null;
|
|
26
|
+
let currentSection: "title" | "description" | "criteria" | null = null;
|
|
27
|
+
let descriptionLines: string[] = [];
|
|
28
|
+
let criteriaLines: string[] = [];
|
|
29
|
+
|
|
30
|
+
for (let i = 0; i < lines.length; i++) {
|
|
31
|
+
const line = lines[i];
|
|
32
|
+
|
|
33
|
+
const storyMatch = line.match(/^##\s+(US-\d+|Story)(?::\s*(.+))?/);
|
|
34
|
+
if (storyMatch) {
|
|
35
|
+
if (currentStory) {
|
|
36
|
+
stories.push(finalizeStory(currentStory, descriptionLines, criteriaLines));
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const id = storyMatch[1] === "Story" ? `US-${String(stories.length + 1).padStart(3, "0")}` : storyMatch[1];
|
|
40
|
+
currentStory = {
|
|
41
|
+
id,
|
|
42
|
+
title: storyMatch[2]?.trim() || "[Untitled]",
|
|
43
|
+
description: "",
|
|
44
|
+
acceptanceCriteria: [],
|
|
45
|
+
tags: [],
|
|
46
|
+
dependencies: [],
|
|
47
|
+
status: "pending",
|
|
48
|
+
passes: false,
|
|
49
|
+
escalations: [],
|
|
50
|
+
attempts: 0,
|
|
51
|
+
};
|
|
52
|
+
descriptionLines = [];
|
|
53
|
+
criteriaLines = [];
|
|
54
|
+
currentSection = "title";
|
|
55
|
+
continue;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
if (!currentStory) continue;
|
|
59
|
+
|
|
60
|
+
if (line.match(/^###\s+Description/i)) {
|
|
61
|
+
currentSection = "description";
|
|
62
|
+
continue;
|
|
63
|
+
}
|
|
64
|
+
if (line.match(/^###\s+Acceptance\s+Criteria/i)) {
|
|
65
|
+
currentSection = "criteria";
|
|
66
|
+
continue;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
if (currentSection === "description" && line.trim()) {
|
|
70
|
+
descriptionLines.push(line.trim());
|
|
71
|
+
}
|
|
72
|
+
if (currentSection === "criteria" && line.trim()) {
|
|
73
|
+
const criterionMatch = line.match(/^-\s+\[.\]\s+(.+)/);
|
|
74
|
+
if (criterionMatch) criteriaLines.push(criterionMatch[1].trim());
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const tagsMatch = line.match(/^Tags:\s*(.+)/i);
|
|
78
|
+
if (tagsMatch && currentStory) {
|
|
79
|
+
currentStory.tags = tagsMatch[1]
|
|
80
|
+
.split(",")
|
|
81
|
+
.map((t) => t.trim())
|
|
82
|
+
.filter(Boolean);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const depsMatch = line.match(/^Dependencies:\s*(.+)/i);
|
|
86
|
+
if (depsMatch && currentStory) {
|
|
87
|
+
currentStory.dependencies = depsMatch[1]
|
|
88
|
+
.split(",")
|
|
89
|
+
.map((d) => d.trim())
|
|
90
|
+
.filter(Boolean);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
if (currentStory) {
|
|
95
|
+
stories.push(finalizeStory(currentStory, descriptionLines, criteriaLines));
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
return stories;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/** Finalize a story by combining collected data */
|
|
102
|
+
function finalizeStory(story: Partial<UserStory>, descriptionLines: string[], criteriaLines: string[]): UserStory {
|
|
103
|
+
return {
|
|
104
|
+
id: story.id || "US-000",
|
|
105
|
+
title: story.title || "[Untitled]",
|
|
106
|
+
description: descriptionLines.join(" ").trim() || story.title || "",
|
|
107
|
+
acceptanceCriteria: criteriaLines.length > 0 ? criteriaLines : ["Implementation complete"],
|
|
108
|
+
tags: story.tags || [],
|
|
109
|
+
dependencies: story.dependencies || [],
|
|
110
|
+
status: "pending",
|
|
111
|
+
passes: false,
|
|
112
|
+
escalations: [],
|
|
113
|
+
attempts: 0,
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/** Build codebase context string from scan result. */
|
|
118
|
+
export function buildCodebaseContext(scan: CodebaseScan): string {
|
|
119
|
+
return `FILE TREE:
|
|
120
|
+
${scan.fileTree}
|
|
121
|
+
|
|
122
|
+
DEPENDENCIES:
|
|
123
|
+
${Object.entries(scan.dependencies)
|
|
124
|
+
.map(([name, version]) => `- ${name}: ${version}`)
|
|
125
|
+
.join("\n")}
|
|
126
|
+
|
|
127
|
+
DEV DEPENDENCIES:
|
|
128
|
+
${Object.entries(scan.devDependencies)
|
|
129
|
+
.map(([name, version]) => `- ${name}: ${version}`)
|
|
130
|
+
.join("\n")}
|
|
131
|
+
|
|
132
|
+
TEST PATTERNS:
|
|
133
|
+
${scan.testPatterns.map((p) => `- ${p}`).join("\n")}`.trim();
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/** Apply keyword-based classification to user stories. */
|
|
137
|
+
export function applyKeywordClassification(stories: UserStory[], config?: NaxConfig): UserStory[] {
|
|
138
|
+
return stories.map((story) => {
|
|
139
|
+
const complexity = classifyComplexity(story.title, story.description, story.acceptanceCriteria, story.tags);
|
|
140
|
+
const routing = config
|
|
141
|
+
? routeTask(story.title, story.description, story.acceptanceCriteria, story.tags, config)
|
|
142
|
+
: { complexity, testStrategy: "test-after" as const, reasoning: "No config provided" };
|
|
143
|
+
|
|
144
|
+
return {
|
|
145
|
+
...story,
|
|
146
|
+
routing: {
|
|
147
|
+
complexity,
|
|
148
|
+
testStrategy: routing.testStrategy,
|
|
149
|
+
reasoning: `Keyword-based classification: ${complexity}`,
|
|
150
|
+
estimatedLOC: estimateLOCFromComplexity(complexity),
|
|
151
|
+
risks: [],
|
|
152
|
+
strategy: "keyword" as const,
|
|
153
|
+
},
|
|
154
|
+
};
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/** Estimate LOC from complexity level (rough heuristic). */
|
|
159
|
+
export function estimateLOCFromComplexity(complexity: "simple" | "medium" | "complex" | "expert"): number {
|
|
160
|
+
switch (complexity) {
|
|
161
|
+
case "simple":
|
|
162
|
+
return 50;
|
|
163
|
+
case "medium":
|
|
164
|
+
return 150;
|
|
165
|
+
case "complex":
|
|
166
|
+
return 400;
|
|
167
|
+
case "expert":
|
|
168
|
+
return 800;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/** Re-classify existing prd.json without decomposing. */
|
|
173
|
+
export async function reclassifyExistingPRD(
|
|
174
|
+
featureDir: string,
|
|
175
|
+
featureName: string,
|
|
176
|
+
branchName: string,
|
|
177
|
+
workdir: string,
|
|
178
|
+
config?: NaxConfig,
|
|
179
|
+
): Promise<PRD> {
|
|
180
|
+
const prdPath = join(featureDir, "prd.json");
|
|
181
|
+
if (!existsSync(prdPath)) {
|
|
182
|
+
throw new Error(`prd.json not found at ${prdPath}. Run analyze without --reclassify first.`);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
const prd = await loadPRD(prdPath);
|
|
186
|
+
const logger = getLogger();
|
|
187
|
+
logger.info("cli", "Re-classifying existing stories");
|
|
188
|
+
|
|
189
|
+
const scan = await scanCodebase(workdir);
|
|
190
|
+
const codebaseContext = buildCodebaseContext(scan);
|
|
191
|
+
const updatedStories: UserStory[] = [];
|
|
192
|
+
|
|
193
|
+
for (const story of prd.userStories) {
|
|
194
|
+
const storySpec = `## ${story.id}: ${story.title}\n\n${story.description}\n\n### Acceptance Criteria\n${story.acceptanceCriteria.map((c) => `- ${c}`).join("\n")}`;
|
|
195
|
+
|
|
196
|
+
try {
|
|
197
|
+
if (config?.analyze.llmEnhanced) {
|
|
198
|
+
const classified = await reclassifyWithLLM(story, storySpec, workdir, codebaseContext, config);
|
|
199
|
+
updatedStories.push(classified);
|
|
200
|
+
logger.info("cli", `[OK] ${story.id} reclassified`, {
|
|
201
|
+
storyId: story.id,
|
|
202
|
+
complexity: classified.routing?.complexity,
|
|
203
|
+
});
|
|
204
|
+
} else {
|
|
205
|
+
const classified = reclassifyWithKeywords(story, config);
|
|
206
|
+
updatedStories.push(classified);
|
|
207
|
+
logger.info("cli", `[OK] ${story.id} reclassified`, {
|
|
208
|
+
storyId: story.id,
|
|
209
|
+
complexity: classified.routing?.complexity,
|
|
210
|
+
});
|
|
211
|
+
}
|
|
212
|
+
} catch (error) {
|
|
213
|
+
logger.warn("cli", `[WARN] ${story.id} kept original`, { storyId: story.id, error: (error as Error).message });
|
|
214
|
+
updatedStories.push(story);
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
return { ...prd, updatedAt: new Date().toISOString(), userStories: updatedStories };
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
/** Reclassify a single story via LLM agent. */
|
|
222
|
+
async function reclassifyWithLLM(
|
|
223
|
+
story: UserStory,
|
|
224
|
+
storySpec: string,
|
|
225
|
+
workdir: string,
|
|
226
|
+
codebaseContext: string,
|
|
227
|
+
config: NaxConfig,
|
|
228
|
+
): Promise<UserStory> {
|
|
229
|
+
const agentName = config.autoMode.defaultAgent;
|
|
230
|
+
const adapter = getAgent(agentName);
|
|
231
|
+
if (!adapter) throw new Error(`Agent "${agentName}" not found`);
|
|
232
|
+
|
|
233
|
+
const modelTier = config.analyze.model;
|
|
234
|
+
const modelDef = resolveModel(config.models[modelTier]);
|
|
235
|
+
|
|
236
|
+
const result = await adapter.decompose({ specContent: storySpec, workdir, codebaseContext, modelTier, modelDef });
|
|
237
|
+
|
|
238
|
+
if (result.stories.length === 0) return story;
|
|
239
|
+
const ds = result.stories[0];
|
|
240
|
+
|
|
241
|
+
let testStrategy: import("../config").TestStrategy;
|
|
242
|
+
let routingStrategy: "llm" | "keyword";
|
|
243
|
+
if (ds.testStrategy) {
|
|
244
|
+
testStrategy = ds.testStrategy;
|
|
245
|
+
routingStrategy = "llm";
|
|
246
|
+
} else {
|
|
247
|
+
const routing = routeTask(story.title, story.description, story.acceptanceCriteria, story.tags, config);
|
|
248
|
+
testStrategy = routing.testStrategy;
|
|
249
|
+
routingStrategy = "keyword";
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
return {
|
|
253
|
+
...story,
|
|
254
|
+
routing: {
|
|
255
|
+
complexity: ds.complexity,
|
|
256
|
+
testStrategy,
|
|
257
|
+
reasoning: ds.reasoning,
|
|
258
|
+
estimatedLOC: ds.estimatedLOC,
|
|
259
|
+
risks: ds.risks,
|
|
260
|
+
strategy: routingStrategy,
|
|
261
|
+
llmModel: routingStrategy === "llm" ? modelDef.model : undefined,
|
|
262
|
+
},
|
|
263
|
+
contextFiles: ds.contextFiles,
|
|
264
|
+
};
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
/** Reclassify a single story via keyword heuristics. */
|
|
268
|
+
function reclassifyWithKeywords(story: UserStory, config?: NaxConfig): UserStory {
|
|
269
|
+
const complexity = classifyComplexity(story.title, story.description, story.acceptanceCriteria, story.tags);
|
|
270
|
+
const routing = config
|
|
271
|
+
? routeTask(story.title, story.description, story.acceptanceCriteria, story.tags, config)
|
|
272
|
+
: { complexity, testStrategy: "test-after" as const, reasoning: "No config provided" };
|
|
273
|
+
|
|
274
|
+
return {
|
|
275
|
+
...story,
|
|
276
|
+
routing: {
|
|
277
|
+
complexity,
|
|
278
|
+
testStrategy: routing.testStrategy,
|
|
279
|
+
reasoning: `Keyword-based classification: ${complexity}`,
|
|
280
|
+
estimatedLOC: estimateLOCFromComplexity(complexity),
|
|
281
|
+
risks: [],
|
|
282
|
+
},
|
|
283
|
+
};
|
|
284
|
+
}
|