@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,215 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for LLM Classifier
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { describe, expect, test } from "bun:test";
|
|
6
|
+
import type { CodebaseScan } from "../../src/analyze";
|
|
7
|
+
import { classifyStories } from "../../src/analyze/classifier";
|
|
8
|
+
import { DEFAULT_CONFIG } from "../../src/config";
|
|
9
|
+
import type { UserStory } from "../../src/prd";
|
|
10
|
+
|
|
11
|
+
describe("classifyStories", () => {
|
|
12
|
+
const mockScan: CodebaseScan = {
|
|
13
|
+
fileTree: "src/\n├── index.ts\n└── utils/\n └── helper.ts",
|
|
14
|
+
dependencies: { zod: "^4.0.0" },
|
|
15
|
+
devDependencies: { typescript: "^5.0.0" },
|
|
16
|
+
testPatterns: ["Test framework: bun:test", "Test directory: test/"],
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
const mockStories: UserStory[] = [
|
|
20
|
+
{
|
|
21
|
+
id: "US-001",
|
|
22
|
+
title: "Add input validation",
|
|
23
|
+
description: "Validate user inputs with Zod schemas",
|
|
24
|
+
acceptanceCriteria: ["Schema defined", "Validation works"],
|
|
25
|
+
tags: [],
|
|
26
|
+
dependencies: [],
|
|
27
|
+
status: "pending",
|
|
28
|
+
passes: false,
|
|
29
|
+
escalations: [],
|
|
30
|
+
attempts: 0,
|
|
31
|
+
},
|
|
32
|
+
];
|
|
33
|
+
|
|
34
|
+
test("falls back to keyword matching when LLM disabled", async () => {
|
|
35
|
+
const config = {
|
|
36
|
+
...DEFAULT_CONFIG,
|
|
37
|
+
analyze: {
|
|
38
|
+
llmEnhanced: false,
|
|
39
|
+
classifierModel: "fast" as const,
|
|
40
|
+
fallbackToKeywords: true,
|
|
41
|
+
maxCodebaseSummaryTokens: 5000,
|
|
42
|
+
},
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
const result = await classifyStories(mockStories, mockScan, config);
|
|
46
|
+
|
|
47
|
+
expect(result.method).toBe("keyword-fallback");
|
|
48
|
+
expect(result.fallbackReason).toBe("LLM-enhanced analysis disabled in config");
|
|
49
|
+
expect(result.classifications).toHaveLength(1);
|
|
50
|
+
expect(result.classifications[0].storyId).toBe("US-001");
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
test("falls back to keyword matching when ANTHROPIC_API_KEY missing", async () => {
|
|
54
|
+
const config = {
|
|
55
|
+
...DEFAULT_CONFIG,
|
|
56
|
+
analyze: {
|
|
57
|
+
llmEnhanced: true,
|
|
58
|
+
classifierModel: "fast" as const,
|
|
59
|
+
fallbackToKeywords: true,
|
|
60
|
+
maxCodebaseSummaryTokens: 5000,
|
|
61
|
+
},
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
// Temporarily unset API key
|
|
65
|
+
const originalKey = process.env.ANTHROPIC_API_KEY;
|
|
66
|
+
delete process.env.ANTHROPIC_API_KEY;
|
|
67
|
+
|
|
68
|
+
try {
|
|
69
|
+
const result = await classifyStories(mockStories, mockScan, config);
|
|
70
|
+
|
|
71
|
+
expect(result.method).toBe("keyword-fallback");
|
|
72
|
+
expect(result.fallbackReason).toContain("ANTHROPIC_API_KEY");
|
|
73
|
+
expect(result.classifications).toHaveLength(1);
|
|
74
|
+
} finally {
|
|
75
|
+
// Restore API key
|
|
76
|
+
if (originalKey) {
|
|
77
|
+
process.env.ANTHROPIC_API_KEY = originalKey;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
test("keyword fallback classification includes all required fields", async () => {
|
|
83
|
+
const config = {
|
|
84
|
+
...DEFAULT_CONFIG,
|
|
85
|
+
analyze: {
|
|
86
|
+
llmEnhanced: false,
|
|
87
|
+
classifierModel: "fast" as const,
|
|
88
|
+
fallbackToKeywords: true,
|
|
89
|
+
maxCodebaseSummaryTokens: 5000,
|
|
90
|
+
},
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
const result = await classifyStories(mockStories, mockScan, config);
|
|
94
|
+
|
|
95
|
+
const classification = result.classifications[0];
|
|
96
|
+
expect(classification.storyId).toBe("US-001");
|
|
97
|
+
expect(classification.complexity).toMatch(/simple|medium|complex|expert/);
|
|
98
|
+
expect(classification.contextFiles).toEqual([]);
|
|
99
|
+
expect(classification.reasoning).toContain("Keyword-based classification");
|
|
100
|
+
expect(typeof classification.estimatedLOC).toBe("number");
|
|
101
|
+
expect(classification.estimatedLOC).toBeGreaterThan(0);
|
|
102
|
+
expect(classification.risks).toEqual([]);
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
test("classifies simple stories correctly in keyword mode", async () => {
|
|
106
|
+
const simpleStory: UserStory = {
|
|
107
|
+
id: "US-002",
|
|
108
|
+
title: "Update button color",
|
|
109
|
+
description: "Change primary button to blue",
|
|
110
|
+
acceptanceCriteria: ["Button is blue"],
|
|
111
|
+
tags: [],
|
|
112
|
+
dependencies: [],
|
|
113
|
+
status: "pending",
|
|
114
|
+
passes: false,
|
|
115
|
+
escalations: [],
|
|
116
|
+
attempts: 0,
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
const config = {
|
|
120
|
+
...DEFAULT_CONFIG,
|
|
121
|
+
analyze: {
|
|
122
|
+
llmEnhanced: false,
|
|
123
|
+
classifierModel: "fast" as const,
|
|
124
|
+
fallbackToKeywords: true,
|
|
125
|
+
maxCodebaseSummaryTokens: 5000,
|
|
126
|
+
},
|
|
127
|
+
};
|
|
128
|
+
|
|
129
|
+
const result = await classifyStories([simpleStory], mockScan, config);
|
|
130
|
+
|
|
131
|
+
expect(result.classifications[0].complexity).toBe("simple");
|
|
132
|
+
expect(result.classifications[0].estimatedLOC).toBe(50); // Simple = 50 LOC
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
test("classifies complex stories correctly in keyword mode", async () => {
|
|
136
|
+
const complexStory: UserStory = {
|
|
137
|
+
id: "US-003",
|
|
138
|
+
title: "Add JWT authentication",
|
|
139
|
+
description: "Implement secure JWT authentication with refresh tokens",
|
|
140
|
+
acceptanceCriteria: [
|
|
141
|
+
"Token generation",
|
|
142
|
+
"Token validation",
|
|
143
|
+
"Refresh logic",
|
|
144
|
+
"Expiry handling",
|
|
145
|
+
"Secure storage",
|
|
146
|
+
],
|
|
147
|
+
tags: ["security", "auth"],
|
|
148
|
+
dependencies: [],
|
|
149
|
+
status: "pending",
|
|
150
|
+
passes: false,
|
|
151
|
+
escalations: [],
|
|
152
|
+
attempts: 0,
|
|
153
|
+
};
|
|
154
|
+
|
|
155
|
+
const config = {
|
|
156
|
+
...DEFAULT_CONFIG,
|
|
157
|
+
analyze: {
|
|
158
|
+
llmEnhanced: false,
|
|
159
|
+
classifierModel: "fast" as const,
|
|
160
|
+
fallbackToKeywords: true,
|
|
161
|
+
maxCodebaseSummaryTokens: 5000,
|
|
162
|
+
},
|
|
163
|
+
};
|
|
164
|
+
|
|
165
|
+
const result = await classifyStories([complexStory], mockScan, config);
|
|
166
|
+
|
|
167
|
+
expect(result.classifications[0].complexity).toBe("complex");
|
|
168
|
+
expect(result.classifications[0].estimatedLOC).toBe(400); // Complex = 400 LOC
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
test("processes multiple stories", async () => {
|
|
172
|
+
const stories: UserStory[] = [
|
|
173
|
+
{
|
|
174
|
+
id: "US-001",
|
|
175
|
+
title: "Story 1",
|
|
176
|
+
description: "First story",
|
|
177
|
+
acceptanceCriteria: ["AC1"],
|
|
178
|
+
tags: [],
|
|
179
|
+
dependencies: [],
|
|
180
|
+
status: "pending",
|
|
181
|
+
passes: false,
|
|
182
|
+
escalations: [],
|
|
183
|
+
attempts: 0,
|
|
184
|
+
},
|
|
185
|
+
{
|
|
186
|
+
id: "US-002",
|
|
187
|
+
title: "Story 2",
|
|
188
|
+
description: "Second story",
|
|
189
|
+
acceptanceCriteria: ["AC2"],
|
|
190
|
+
tags: [],
|
|
191
|
+
dependencies: [],
|
|
192
|
+
status: "pending",
|
|
193
|
+
passes: false,
|
|
194
|
+
escalations: [],
|
|
195
|
+
attempts: 0,
|
|
196
|
+
},
|
|
197
|
+
];
|
|
198
|
+
|
|
199
|
+
const config = {
|
|
200
|
+
...DEFAULT_CONFIG,
|
|
201
|
+
analyze: {
|
|
202
|
+
llmEnhanced: false,
|
|
203
|
+
classifierModel: "fast" as const,
|
|
204
|
+
fallbackToKeywords: true,
|
|
205
|
+
maxCodebaseSummaryTokens: 5000,
|
|
206
|
+
},
|
|
207
|
+
};
|
|
208
|
+
|
|
209
|
+
const result = await classifyStories(stories, mockScan, config);
|
|
210
|
+
|
|
211
|
+
expect(result.classifications).toHaveLength(2);
|
|
212
|
+
expect(result.classifications[0].storyId).toBe("US-001");
|
|
213
|
+
expect(result.classifications[1].storyId).toBe("US-002");
|
|
214
|
+
});
|
|
215
|
+
});
|
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
import { describe, expect, test } from "bun:test";
|
|
2
|
+
import { analyzeFeature } from "../../src/cli/analyze";
|
|
3
|
+
import type { NaxConfig } from "../../src/config";
|
|
4
|
+
import { DEFAULT_CONFIG } from "../../src/config/schema";
|
|
5
|
+
|
|
6
|
+
describe("analyzeFeature", () => {
|
|
7
|
+
test("parses spec.md into user stories (LLM disabled, keyword fallback)", async () => {
|
|
8
|
+
const tmpDir = `/tmp/nax-analyze-${Date.now()}`;
|
|
9
|
+
await Bun.spawn(["mkdir", "-p", tmpDir], { stdout: "pipe" }).exited;
|
|
10
|
+
|
|
11
|
+
await Bun.write(
|
|
12
|
+
`${tmpDir}/spec.md`,
|
|
13
|
+
`# Feature: Auth System
|
|
14
|
+
|
|
15
|
+
## US-001: Add login endpoint
|
|
16
|
+
|
|
17
|
+
### Description
|
|
18
|
+
Create a POST /auth/login endpoint that accepts email and password.
|
|
19
|
+
|
|
20
|
+
### Acceptance Criteria
|
|
21
|
+
- [ ] Endpoint returns JWT token on success
|
|
22
|
+
- [ ] Returns 401 on invalid credentials
|
|
23
|
+
- [ ] Rate limited to 5 attempts per minute
|
|
24
|
+
|
|
25
|
+
Tags: security, auth
|
|
26
|
+
Dependencies: US-000
|
|
27
|
+
|
|
28
|
+
## US-002: Add logout endpoint
|
|
29
|
+
|
|
30
|
+
### Description
|
|
31
|
+
Create a POST /auth/logout endpoint.
|
|
32
|
+
|
|
33
|
+
### Acceptance Criteria
|
|
34
|
+
- [ ] Invalidates the current token
|
|
35
|
+
- [ ] Returns 200 on success
|
|
36
|
+
|
|
37
|
+
Dependencies: US-001
|
|
38
|
+
`,
|
|
39
|
+
);
|
|
40
|
+
|
|
41
|
+
// Disable LLM for keyword-based classification
|
|
42
|
+
const config: NaxConfig = {
|
|
43
|
+
...DEFAULT_CONFIG,
|
|
44
|
+
analyze: {
|
|
45
|
+
...DEFAULT_CONFIG.analyze,
|
|
46
|
+
llmEnhanced: false,
|
|
47
|
+
},
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
const prd = await analyzeFeature({
|
|
51
|
+
featureDir: tmpDir,
|
|
52
|
+
featureName: "auth",
|
|
53
|
+
branchName: "feat/auth",
|
|
54
|
+
config,
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
expect(prd.feature).toBe("auth");
|
|
58
|
+
expect(prd.branchName).toBe("feat/auth");
|
|
59
|
+
expect(prd.userStories).toHaveLength(2);
|
|
60
|
+
|
|
61
|
+
const s1 = prd.userStories[0];
|
|
62
|
+
expect(s1.id).toBe("US-001");
|
|
63
|
+
expect(s1.title).toBe("Add login endpoint");
|
|
64
|
+
expect(s1.acceptanceCriteria).toHaveLength(3);
|
|
65
|
+
expect(s1.tags).toContain("security");
|
|
66
|
+
expect(s1.dependencies).toContain("US-000");
|
|
67
|
+
|
|
68
|
+
const s2 = prd.userStories[1];
|
|
69
|
+
expect(s2.id).toBe("US-002");
|
|
70
|
+
expect(s2.title).toBe("Add logout endpoint");
|
|
71
|
+
expect(s2.acceptanceCriteria).toHaveLength(2);
|
|
72
|
+
expect(s2.dependencies).toContain("US-001");
|
|
73
|
+
|
|
74
|
+
await Bun.spawn(["rm", "-rf", tmpDir], { stdout: "pipe" }).exited;
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
test("throws when spec.md is missing", async () => {
|
|
78
|
+
const tmpDir = `/tmp/nax-analyze-empty-${Date.now()}`;
|
|
79
|
+
await Bun.spawn(["mkdir", "-p", tmpDir], { stdout: "pipe" }).exited;
|
|
80
|
+
|
|
81
|
+
await expect(
|
|
82
|
+
analyzeFeature({
|
|
83
|
+
featureDir: tmpDir,
|
|
84
|
+
featureName: "test",
|
|
85
|
+
branchName: "feat/test",
|
|
86
|
+
}),
|
|
87
|
+
).rejects.toThrow("spec.md not found");
|
|
88
|
+
|
|
89
|
+
await Bun.spawn(["rm", "-rf", tmpDir], { stdout: "pipe" }).exited;
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
test("warns but does not throw when story count exceeds maxStoriesPerFeature limit", async () => {
|
|
93
|
+
const tmpDir = `/tmp/nax-analyze-limit-${Date.now()}`;
|
|
94
|
+
await Bun.spawn(["mkdir", "-p", tmpDir], { stdout: "pipe" }).exited;
|
|
95
|
+
|
|
96
|
+
// Generate spec.md with 6 stories
|
|
97
|
+
const stories = Array.from(
|
|
98
|
+
{ length: 6 },
|
|
99
|
+
(_, i) => `
|
|
100
|
+
## US-${String(i + 1).padStart(3, "0")}: Story ${i + 1}
|
|
101
|
+
|
|
102
|
+
### Description
|
|
103
|
+
Description for story ${i + 1}
|
|
104
|
+
|
|
105
|
+
### Acceptance Criteria
|
|
106
|
+
- [ ] Criterion 1
|
|
107
|
+
`,
|
|
108
|
+
).join("\n");
|
|
109
|
+
|
|
110
|
+
await Bun.write(`${tmpDir}/spec.md`, `# Feature\n${stories}`);
|
|
111
|
+
|
|
112
|
+
// Create config with limit of 5 stories (LLM disabled)
|
|
113
|
+
const config: NaxConfig = {
|
|
114
|
+
...DEFAULT_CONFIG,
|
|
115
|
+
execution: {
|
|
116
|
+
...DEFAULT_CONFIG.execution,
|
|
117
|
+
maxStoriesPerFeature: 5,
|
|
118
|
+
},
|
|
119
|
+
analyze: {
|
|
120
|
+
...DEFAULT_CONFIG.analyze,
|
|
121
|
+
llmEnhanced: false,
|
|
122
|
+
},
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
// Should warn but still succeed (no longer throws)
|
|
126
|
+
const prd = await analyzeFeature({
|
|
127
|
+
featureDir: tmpDir,
|
|
128
|
+
featureName: "test",
|
|
129
|
+
branchName: "feat/test",
|
|
130
|
+
config,
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
expect(prd.userStories.length).toBe(6);
|
|
134
|
+
|
|
135
|
+
await Bun.spawn(["rm", "-rf", tmpDir], { stdout: "pipe" }).exited;
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
test("allows story count at maxStoriesPerFeature limit", async () => {
|
|
139
|
+
const tmpDir = `/tmp/nax-analyze-ok-${Date.now()}`;
|
|
140
|
+
await Bun.spawn(["mkdir", "-p", tmpDir], { stdout: "pipe" }).exited;
|
|
141
|
+
|
|
142
|
+
// Generate spec.md with exactly 5 stories
|
|
143
|
+
const stories = Array.from(
|
|
144
|
+
{ length: 5 },
|
|
145
|
+
(_, i) => `
|
|
146
|
+
## US-${String(i + 1).padStart(3, "0")}: Story ${i + 1}
|
|
147
|
+
|
|
148
|
+
### Description
|
|
149
|
+
Description for story ${i + 1}
|
|
150
|
+
|
|
151
|
+
### Acceptance Criteria
|
|
152
|
+
- [ ] Criterion 1
|
|
153
|
+
`,
|
|
154
|
+
).join("\n");
|
|
155
|
+
|
|
156
|
+
await Bun.write(`${tmpDir}/spec.md`, `# Feature\n${stories}`);
|
|
157
|
+
|
|
158
|
+
// Create config with limit of 5 stories (LLM disabled)
|
|
159
|
+
const config: NaxConfig = {
|
|
160
|
+
...DEFAULT_CONFIG,
|
|
161
|
+
execution: {
|
|
162
|
+
...DEFAULT_CONFIG.execution,
|
|
163
|
+
maxStoriesPerFeature: 5,
|
|
164
|
+
},
|
|
165
|
+
analyze: {
|
|
166
|
+
...DEFAULT_CONFIG.analyze,
|
|
167
|
+
llmEnhanced: false,
|
|
168
|
+
},
|
|
169
|
+
};
|
|
170
|
+
|
|
171
|
+
// Should NOT throw because 5 === 5
|
|
172
|
+
const prd = await analyzeFeature({
|
|
173
|
+
featureDir: tmpDir,
|
|
174
|
+
featureName: "test",
|
|
175
|
+
branchName: "feat/test",
|
|
176
|
+
config,
|
|
177
|
+
});
|
|
178
|
+
expect(prd.userStories).toHaveLength(5);
|
|
179
|
+
|
|
180
|
+
await Bun.spawn(["rm", "-rf", tmpDir], { stdout: "pipe" }).exited;
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
test("reads spec from explicit --from path", async () => {
|
|
184
|
+
const tmpDir = `/tmp/nax-analyze-from-${Date.now()}`;
|
|
185
|
+
await Bun.spawn(["mkdir", "-p", tmpDir], { stdout: "pipe" }).exited;
|
|
186
|
+
|
|
187
|
+
const customSpecPath = `${tmpDir}/custom-spec.md`;
|
|
188
|
+
await Bun.write(
|
|
189
|
+
customSpecPath,
|
|
190
|
+
`# Custom Spec
|
|
191
|
+
|
|
192
|
+
## US-001: Custom story
|
|
193
|
+
|
|
194
|
+
### Description
|
|
195
|
+
A custom story from explicit path
|
|
196
|
+
|
|
197
|
+
### Acceptance Criteria
|
|
198
|
+
- [ ] Works with --from flag
|
|
199
|
+
`,
|
|
200
|
+
);
|
|
201
|
+
|
|
202
|
+
const config: NaxConfig = {
|
|
203
|
+
...DEFAULT_CONFIG,
|
|
204
|
+
analyze: {
|
|
205
|
+
...DEFAULT_CONFIG.analyze,
|
|
206
|
+
llmEnhanced: false,
|
|
207
|
+
},
|
|
208
|
+
};
|
|
209
|
+
|
|
210
|
+
const prd = await analyzeFeature({
|
|
211
|
+
featureDir: tmpDir,
|
|
212
|
+
featureName: "custom",
|
|
213
|
+
branchName: "feat/custom",
|
|
214
|
+
config,
|
|
215
|
+
specPath: customSpecPath,
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
expect(prd.userStories).toHaveLength(1);
|
|
219
|
+
expect(prd.userStories[0].id).toBe("US-001");
|
|
220
|
+
expect(prd.userStories[0].title).toBe("Custom story");
|
|
221
|
+
|
|
222
|
+
await Bun.spawn(["rm", "-rf", tmpDir], { stdout: "pipe" }).exited;
|
|
223
|
+
});
|
|
224
|
+
});
|
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for context auto-detection (BUG-006)
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { afterEach, beforeEach, describe, expect, test } from "bun:test";
|
|
6
|
+
import fs from "node:fs/promises";
|
|
7
|
+
import os from "node:os";
|
|
8
|
+
import path from "node:path";
|
|
9
|
+
import { autoDetectContextFiles, extractKeywords } from "../../src/context/auto-detect";
|
|
10
|
+
|
|
11
|
+
describe("Context Auto-Detection", () => {
|
|
12
|
+
describe("extractKeywords", () => {
|
|
13
|
+
test("should extract keywords from simple title", () => {
|
|
14
|
+
const keywords = extractKeywords("BUG-006: Context auto-detection");
|
|
15
|
+
expect(keywords).toContain("006");
|
|
16
|
+
expect(keywords).toContain("context");
|
|
17
|
+
expect(keywords).toContain("auto");
|
|
18
|
+
expect(keywords).toContain("detection");
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
test("should remove common stop words", () => {
|
|
22
|
+
const keywords = extractKeywords("Add the new feature for user authentication");
|
|
23
|
+
expect(keywords).not.toContain("the");
|
|
24
|
+
expect(keywords).not.toContain("for");
|
|
25
|
+
expect(keywords).not.toContain("add");
|
|
26
|
+
expect(keywords).toContain("new");
|
|
27
|
+
expect(keywords).toContain("feature");
|
|
28
|
+
expect(keywords).toContain("user");
|
|
29
|
+
expect(keywords).toContain("authentication");
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
test("should handle punctuation and special chars", () => {
|
|
33
|
+
const keywords = extractKeywords("Fix: API endpoint (v2) - rate-limiting");
|
|
34
|
+
expect(keywords).toContain("api");
|
|
35
|
+
expect(keywords).toContain("endpoint");
|
|
36
|
+
expect(keywords).toContain("rate");
|
|
37
|
+
expect(keywords).toContain("limiting");
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
test("should deduplicate keywords", () => {
|
|
41
|
+
const keywords = extractKeywords("Context context CONTEXT");
|
|
42
|
+
expect(keywords.filter((k) => k === "context")).toHaveLength(1);
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
test("should filter short words (< 3 chars)", () => {
|
|
46
|
+
const keywords = extractKeywords("Fix a bug in UI");
|
|
47
|
+
expect(keywords).not.toContain("in");
|
|
48
|
+
expect(keywords).not.toContain("ui"); // length 2
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
test("should return empty array for title with only stop words", () => {
|
|
52
|
+
const keywords = extractKeywords("The and or to");
|
|
53
|
+
expect(keywords).toHaveLength(0);
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
test("should lowercase all keywords", () => {
|
|
57
|
+
const keywords = extractKeywords("UPPERCASE lowercase MixedCase");
|
|
58
|
+
expect(keywords).toContain("uppercase");
|
|
59
|
+
expect(keywords).toContain("lowercase");
|
|
60
|
+
expect(keywords).toContain("mixedcase");
|
|
61
|
+
expect(keywords).not.toContain("UPPERCASE");
|
|
62
|
+
expect(keywords).not.toContain("MixedCase");
|
|
63
|
+
});
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
describe("autoDetectContextFiles", () => {
|
|
67
|
+
let tempDir: string;
|
|
68
|
+
|
|
69
|
+
beforeEach(async () => {
|
|
70
|
+
// Create temp git repo
|
|
71
|
+
tempDir = await fs.mkdtemp(path.join(os.tmpdir(), "nax-autodetect-test-"));
|
|
72
|
+
await Bun.spawn(["git", "init"], { cwd: tempDir }).exited;
|
|
73
|
+
await Bun.spawn(["git", "config", "user.email", "test@test.com"], { cwd: tempDir }).exited;
|
|
74
|
+
await Bun.spawn(["git", "config", "user.name", "Test User"], { cwd: tempDir }).exited;
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
afterEach(async () => {
|
|
78
|
+
// Clean up
|
|
79
|
+
await fs.rm(tempDir, { recursive: true, force: true });
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
test("should detect files matching keywords", async () => {
|
|
83
|
+
// Create test files
|
|
84
|
+
await fs.mkdir(path.join(tempDir, "src"), { recursive: true });
|
|
85
|
+
await fs.writeFile(path.join(tempDir, "src/context-builder.ts"), "export function buildContext() {}");
|
|
86
|
+
await fs.writeFile(path.join(tempDir, "src/auto-detect.ts"), "export function autoDetectContextFiles() {}");
|
|
87
|
+
await fs.writeFile(path.join(tempDir, "src/unrelated.ts"), "export function foo() {}");
|
|
88
|
+
|
|
89
|
+
// Commit files so git grep can find them
|
|
90
|
+
await Bun.spawn(["git", "add", "."], { cwd: tempDir }).exited;
|
|
91
|
+
await Bun.spawn(["git", "commit", "-m", "initial"], { cwd: tempDir }).exited;
|
|
92
|
+
|
|
93
|
+
const files = await autoDetectContextFiles({
|
|
94
|
+
workdir: tempDir,
|
|
95
|
+
storyTitle: "BUG-006: Context auto-detection",
|
|
96
|
+
maxFiles: 5,
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
expect(files).toContain("src/context-builder.ts");
|
|
100
|
+
expect(files).toContain("src/auto-detect.ts");
|
|
101
|
+
expect(files).not.toContain("src/unrelated.ts");
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
test("should exclude test files", async () => {
|
|
105
|
+
await fs.mkdir(path.join(tempDir, "src"), { recursive: true });
|
|
106
|
+
await fs.mkdir(path.join(tempDir, "test"), { recursive: true });
|
|
107
|
+
await fs.writeFile(path.join(tempDir, "src/context.ts"), "export function buildContext() {}");
|
|
108
|
+
await fs.writeFile(path.join(tempDir, "test/context.test.ts"), "test('context', () => {})");
|
|
109
|
+
|
|
110
|
+
await Bun.spawn(["git", "add", "."], { cwd: tempDir }).exited;
|
|
111
|
+
await Bun.spawn(["git", "commit", "-m", "initial"], { cwd: tempDir }).exited;
|
|
112
|
+
|
|
113
|
+
const files = await autoDetectContextFiles({
|
|
114
|
+
workdir: tempDir,
|
|
115
|
+
storyTitle: "Context builder",
|
|
116
|
+
maxFiles: 5,
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
expect(files).toContain("src/context.ts");
|
|
120
|
+
expect(files).not.toContain("test/context.test.ts");
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
test("should exclude index files", async () => {
|
|
124
|
+
await fs.mkdir(path.join(tempDir, "src/context"), { recursive: true });
|
|
125
|
+
await fs.writeFile(path.join(tempDir, "src/context/index.ts"), "export * from './builder'");
|
|
126
|
+
await fs.writeFile(path.join(tempDir, "src/context/builder.ts"), "export function buildContext() {}");
|
|
127
|
+
|
|
128
|
+
await Bun.spawn(["git", "add", "."], { cwd: tempDir }).exited;
|
|
129
|
+
await Bun.spawn(["git", "commit", "-m", "initial"], { cwd: tempDir }).exited;
|
|
130
|
+
|
|
131
|
+
const files = await autoDetectContextFiles({
|
|
132
|
+
workdir: tempDir,
|
|
133
|
+
storyTitle: "Context builder",
|
|
134
|
+
maxFiles: 5,
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
expect(files).toContain("src/context/builder.ts");
|
|
138
|
+
expect(files).not.toContain("src/context/index.ts");
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
test("should respect maxFiles limit", async () => {
|
|
142
|
+
await fs.mkdir(path.join(tempDir, "src"), { recursive: true });
|
|
143
|
+
for (let i = 1; i <= 10; i++) {
|
|
144
|
+
await fs.writeFile(path.join(tempDir, `src/file${i}.ts`), `// Contains keyword: routing`);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
await Bun.spawn(["git", "add", "."], { cwd: tempDir }).exited;
|
|
148
|
+
await Bun.spawn(["git", "commit", "-m", "initial"], { cwd: tempDir }).exited;
|
|
149
|
+
|
|
150
|
+
const files = await autoDetectContextFiles({
|
|
151
|
+
workdir: tempDir,
|
|
152
|
+
storyTitle: "Routing system",
|
|
153
|
+
maxFiles: 3,
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
expect(files.length).toBeLessThanOrEqual(3);
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
test("should return empty array when no matches", async () => {
|
|
160
|
+
await fs.mkdir(path.join(tempDir, "src"), { recursive: true });
|
|
161
|
+
await fs.writeFile(path.join(tempDir, "src/unrelated.ts"), "export function foo() {}");
|
|
162
|
+
|
|
163
|
+
await Bun.spawn(["git", "add", "."], { cwd: tempDir }).exited;
|
|
164
|
+
await Bun.spawn(["git", "commit", "-m", "initial"], { cwd: tempDir }).exited;
|
|
165
|
+
|
|
166
|
+
const files = await autoDetectContextFiles({
|
|
167
|
+
workdir: tempDir,
|
|
168
|
+
storyTitle: "NonExistentKeyword XYZ123",
|
|
169
|
+
maxFiles: 5,
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
expect(files).toHaveLength(0);
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
test("should return empty array when no keywords extracted", async () => {
|
|
176
|
+
await fs.mkdir(path.join(tempDir, "src"), { recursive: true });
|
|
177
|
+
await fs.writeFile(path.join(tempDir, "src/file.ts"), "export function test() {}");
|
|
178
|
+
|
|
179
|
+
await Bun.spawn(["git", "add", "."], { cwd: tempDir }).exited;
|
|
180
|
+
await Bun.spawn(["git", "commit", "-m", "initial"], { cwd: tempDir }).exited;
|
|
181
|
+
|
|
182
|
+
const files = await autoDetectContextFiles({
|
|
183
|
+
workdir: tempDir,
|
|
184
|
+
storyTitle: "the and or", // All stop words
|
|
185
|
+
maxFiles: 5,
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
expect(files).toHaveLength(0);
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
test("should handle non-git directory gracefully", async () => {
|
|
192
|
+
// Create non-git temp dir
|
|
193
|
+
const nonGitDir = await fs.mkdtemp(path.join(os.tmpdir(), "nax-nongit-"));
|
|
194
|
+
await fs.mkdir(path.join(nonGitDir, "src"), { recursive: true });
|
|
195
|
+
await fs.writeFile(path.join(nonGitDir, "src/file.ts"), "export function test() {}");
|
|
196
|
+
|
|
197
|
+
const files = await autoDetectContextFiles({
|
|
198
|
+
workdir: nonGitDir,
|
|
199
|
+
storyTitle: "Test story",
|
|
200
|
+
maxFiles: 5,
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
expect(files).toHaveLength(0);
|
|
204
|
+
|
|
205
|
+
// Clean up
|
|
206
|
+
await fs.rm(nonGitDir, { recursive: true, force: true });
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
test("should sort files by relevance score", async () => {
|
|
210
|
+
await fs.mkdir(path.join(tempDir, "src"), { recursive: true });
|
|
211
|
+
// File with 2 keyword matches
|
|
212
|
+
await fs.writeFile(path.join(tempDir, "src/context-builder.ts"), "context builder code");
|
|
213
|
+
// File with 1 keyword match
|
|
214
|
+
await fs.writeFile(path.join(tempDir, "src/context-helper.ts"), "context helper code");
|
|
215
|
+
// File with 1 keyword match
|
|
216
|
+
await fs.writeFile(path.join(tempDir, "src/builder-utils.ts"), "builder utils code");
|
|
217
|
+
|
|
218
|
+
await Bun.spawn(["git", "add", "."], { cwd: tempDir }).exited;
|
|
219
|
+
await Bun.spawn(["git", "commit", "-m", "initial"], { cwd: tempDir }).exited;
|
|
220
|
+
|
|
221
|
+
const files = await autoDetectContextFiles({
|
|
222
|
+
workdir: tempDir,
|
|
223
|
+
storyTitle: "Context builder system",
|
|
224
|
+
maxFiles: 5,
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
// File with most keyword matches should be first
|
|
228
|
+
expect(files[0]).toBe("src/context-builder.ts");
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
test("should exclude generated files", async () => {
|
|
232
|
+
await fs.mkdir(path.join(tempDir, "src/__generated__"), { recursive: true });
|
|
233
|
+
await fs.writeFile(path.join(tempDir, "src/__generated__/schema.ts"), "// generated schema");
|
|
234
|
+
await fs.writeFile(path.join(tempDir, "src/schema.ts"), "export const schema = {}");
|
|
235
|
+
|
|
236
|
+
await Bun.spawn(["git", "add", "."], { cwd: tempDir }).exited;
|
|
237
|
+
await Bun.spawn(["git", "commit", "-m", "initial"], { cwd: tempDir }).exited;
|
|
238
|
+
|
|
239
|
+
const files = await autoDetectContextFiles({
|
|
240
|
+
workdir: tempDir,
|
|
241
|
+
storyTitle: "Schema validation",
|
|
242
|
+
maxFiles: 5,
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
expect(files).toContain("src/schema.ts");
|
|
246
|
+
expect(files).not.toContain("src/__generated__/schema.ts");
|
|
247
|
+
});
|
|
248
|
+
});
|
|
249
|
+
});
|