@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,896 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* End-to-End Integration Tests
|
|
3
|
+
*
|
|
4
|
+
* Tests the full nax workflow: plan → analyze → run
|
|
5
|
+
* Uses a MockAgentAdapter to avoid requiring real Claude Code installation
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, test } from "bun:test";
|
|
9
|
+
import { existsSync, mkdirSync, rmSync } from "node:fs";
|
|
10
|
+
import { join } from "node:path";
|
|
11
|
+
import { ALL_AGENTS } from "../../src/agents/registry";
|
|
12
|
+
import type {
|
|
13
|
+
AgentAdapter,
|
|
14
|
+
AgentCapabilities,
|
|
15
|
+
AgentResult,
|
|
16
|
+
AgentRunOptions,
|
|
17
|
+
DecomposeOptions,
|
|
18
|
+
DecomposeResult,
|
|
19
|
+
PlanOptions,
|
|
20
|
+
PlanResult,
|
|
21
|
+
} from "../../src/agents/types";
|
|
22
|
+
import { analyzeFeature } from "../../src/cli/analyze";
|
|
23
|
+
import { planCommand } from "../../src/cli/plan";
|
|
24
|
+
import { DEFAULT_CONFIG } from "../../src/config";
|
|
25
|
+
import type { NaxConfig } from "../../src/config";
|
|
26
|
+
import { run } from "../../src/execution/runner";
|
|
27
|
+
import { initLogger, resetLogger } from "../../src/logger";
|
|
28
|
+
import { loadPRD } from "../../src/prd";
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Mock Agent Adapter for testing
|
|
32
|
+
*
|
|
33
|
+
* Implements the AgentAdapter interface but doesn't spawn real processes.
|
|
34
|
+
* Returns realistic, controllable results for testing scenarios.
|
|
35
|
+
*/
|
|
36
|
+
class MockAgentAdapter implements AgentAdapter {
|
|
37
|
+
readonly name = "mock";
|
|
38
|
+
readonly displayName = "Mock Agent";
|
|
39
|
+
readonly binary = "mock-agent";
|
|
40
|
+
|
|
41
|
+
readonly capabilities: AgentCapabilities = {
|
|
42
|
+
supportedTiers: ["fast", "balanced", "powerful"],
|
|
43
|
+
maxContextTokens: 200_000,
|
|
44
|
+
features: new Set(["tdd", "review", "refactor", "batch"]),
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
// Control behavior via flags
|
|
48
|
+
public shouldFailRun = false;
|
|
49
|
+
public shouldRateLimit = false;
|
|
50
|
+
public shouldFailReview = false;
|
|
51
|
+
public callCount = 0;
|
|
52
|
+
public runCalls: AgentRunOptions[] = [];
|
|
53
|
+
public planCalls: PlanOptions[] = [];
|
|
54
|
+
public decomposeCalls: DecomposeOptions[] = [];
|
|
55
|
+
|
|
56
|
+
// Hard iteration cap to prevent infinite retry loops in tests
|
|
57
|
+
// Set to 5 to allow for story batching and escalation scenarios
|
|
58
|
+
public maxAttempts = 5;
|
|
59
|
+
private attemptCountMap = new Map<string, number>();
|
|
60
|
+
|
|
61
|
+
async isInstalled(): Promise<boolean> {
|
|
62
|
+
return true;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
buildCommand(options: AgentRunOptions): string[] {
|
|
66
|
+
return [this.binary, "--prompt", options.prompt];
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
async run(_options: AgentRunOptions): Promise<AgentResult> {
|
|
70
|
+
this.callCount++;
|
|
71
|
+
this.runCalls.push(_options);
|
|
72
|
+
|
|
73
|
+
// Track attempts per unique prompt to prevent infinite loops
|
|
74
|
+
const promptKey = _options.prompt.slice(0, 100);
|
|
75
|
+
const currentAttempts = (this.attemptCountMap.get(promptKey) || 0) + 1;
|
|
76
|
+
this.attemptCountMap.set(promptKey, currentAttempts);
|
|
77
|
+
|
|
78
|
+
// Hard cap: fail after maxAttempts to prevent infinite retry loops
|
|
79
|
+
if (currentAttempts > this.maxAttempts) {
|
|
80
|
+
return {
|
|
81
|
+
success: false,
|
|
82
|
+
exitCode: 1,
|
|
83
|
+
output: `Mock agent: max attempts (${this.maxAttempts}) exceeded for this prompt`,
|
|
84
|
+
rateLimited: false,
|
|
85
|
+
durationMs: 100,
|
|
86
|
+
estimatedCost: 0.01,
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// Simulate execution time
|
|
91
|
+
await Bun.sleep(10);
|
|
92
|
+
|
|
93
|
+
// Rate limit scenario
|
|
94
|
+
if (this.shouldRateLimit && this.callCount === 1) {
|
|
95
|
+
return {
|
|
96
|
+
success: false,
|
|
97
|
+
exitCode: 1,
|
|
98
|
+
output: "Rate limit exceeded. Too many requests.",
|
|
99
|
+
rateLimited: true,
|
|
100
|
+
durationMs: 100,
|
|
101
|
+
estimatedCost: 0.0,
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// Failure scenario
|
|
106
|
+
if (this.shouldFailRun) {
|
|
107
|
+
return {
|
|
108
|
+
success: false,
|
|
109
|
+
exitCode: 1,
|
|
110
|
+
output: "Agent execution failed: mock error",
|
|
111
|
+
rateLimited: false,
|
|
112
|
+
durationMs: 500,
|
|
113
|
+
estimatedCost: 0.01,
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// Success scenario
|
|
118
|
+
return {
|
|
119
|
+
success: true,
|
|
120
|
+
exitCode: 0,
|
|
121
|
+
output: `Mock agent completed task: ${_options.prompt.slice(0, 50)}...\n\nToken usage: 1500 input, 800 output`,
|
|
122
|
+
rateLimited: false,
|
|
123
|
+
durationMs: 2000,
|
|
124
|
+
estimatedCost: 0.015,
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
async plan(_options: PlanOptions): Promise<PlanResult> {
|
|
129
|
+
this.planCalls.push(_options);
|
|
130
|
+
|
|
131
|
+
// Simulate planning time
|
|
132
|
+
await Bun.sleep(10);
|
|
133
|
+
|
|
134
|
+
const specContent = `# Feature: URL Shortener
|
|
135
|
+
|
|
136
|
+
## Problem
|
|
137
|
+
We need a URL shortening service to make long URLs more shareable.
|
|
138
|
+
|
|
139
|
+
## Requirements
|
|
140
|
+
- REQ-1: Accept long URLs and generate short codes
|
|
141
|
+
- REQ-2: Redirect short codes to original URLs
|
|
142
|
+
- REQ-3: Track click analytics
|
|
143
|
+
- REQ-4: Support custom short codes (optional)
|
|
144
|
+
|
|
145
|
+
## Acceptance Criteria
|
|
146
|
+
- AC-1: Short codes are unique and collision-free
|
|
147
|
+
- AC-2: Redirects work with 301 status
|
|
148
|
+
- AC-3: Click counts are tracked accurately
|
|
149
|
+
- AC-4: API returns JSON responses
|
|
150
|
+
|
|
151
|
+
## Technical Notes
|
|
152
|
+
- Use base62 encoding for short codes
|
|
153
|
+
- Store mappings in database (consider Redis for caching)
|
|
154
|
+
- Log all redirects for analytics
|
|
155
|
+
- Validate URLs before shortening
|
|
156
|
+
|
|
157
|
+
## Out of Scope
|
|
158
|
+
- User accounts and authentication (MVP only)
|
|
159
|
+
- Custom domains
|
|
160
|
+
- Link expiration
|
|
161
|
+
`;
|
|
162
|
+
|
|
163
|
+
return {
|
|
164
|
+
specContent,
|
|
165
|
+
conversationLog: "Mock planning session",
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
async decompose(_options: DecomposeOptions): Promise<DecomposeResult> {
|
|
170
|
+
this.decomposeCalls.push(_options);
|
|
171
|
+
|
|
172
|
+
// Simulate decompose time
|
|
173
|
+
await Bun.sleep(10);
|
|
174
|
+
|
|
175
|
+
// Parse the spec content to determine what stories to generate
|
|
176
|
+
// For URL shortener spec, return realistic stories
|
|
177
|
+
const stories = [
|
|
178
|
+
{
|
|
179
|
+
id: "US-001",
|
|
180
|
+
title: "Implement short code generation",
|
|
181
|
+
description: "Create algorithm to generate unique base62 short codes from URLs",
|
|
182
|
+
acceptanceCriteria: [
|
|
183
|
+
"Short codes are 6-8 characters",
|
|
184
|
+
"Codes use base62 charset (a-zA-Z0-9)",
|
|
185
|
+
"Collision detection works",
|
|
186
|
+
"Codes are URL-safe",
|
|
187
|
+
],
|
|
188
|
+
tags: ["core", "algorithm"],
|
|
189
|
+
dependencies: [],
|
|
190
|
+
complexity: "medium" as const,
|
|
191
|
+
relevantFiles: ["src/shortener/generator.ts", "src/utils/base62.ts"],
|
|
192
|
+
reasoning: "Requires algorithmic implementation with collision handling. 2-3 files, ~150 LOC.",
|
|
193
|
+
estimatedLOC: 150,
|
|
194
|
+
risks: ["Collision probability under high load"],
|
|
195
|
+
},
|
|
196
|
+
{
|
|
197
|
+
id: "US-002",
|
|
198
|
+
title: "Add database storage for URL mappings",
|
|
199
|
+
description: "Store short code → long URL mappings in database with timestamps",
|
|
200
|
+
acceptanceCriteria: [
|
|
201
|
+
"Database schema defined",
|
|
202
|
+
"CRUD operations work",
|
|
203
|
+
"Queries are indexed",
|
|
204
|
+
"Timestamps recorded",
|
|
205
|
+
],
|
|
206
|
+
tags: ["database", "storage"],
|
|
207
|
+
dependencies: [],
|
|
208
|
+
complexity: "medium" as const,
|
|
209
|
+
relevantFiles: ["src/db/schema.ts", "src/db/repository.ts"],
|
|
210
|
+
reasoning: "Standard CRUD with indexing. 2 files, ~120 LOC.",
|
|
211
|
+
estimatedLOC: 120,
|
|
212
|
+
risks: ["Database performance at scale"],
|
|
213
|
+
},
|
|
214
|
+
{
|
|
215
|
+
id: "US-003",
|
|
216
|
+
title: "Create redirect handler",
|
|
217
|
+
description: "Route that redirects /:code to the original URL with 301 status",
|
|
218
|
+
acceptanceCriteria: [
|
|
219
|
+
"GET /:code returns 301 redirect",
|
|
220
|
+
"404 for invalid codes",
|
|
221
|
+
"Click count incremented",
|
|
222
|
+
"Response headers correct",
|
|
223
|
+
],
|
|
224
|
+
tags: ["api", "core"],
|
|
225
|
+
dependencies: ["US-001", "US-002"],
|
|
226
|
+
complexity: "simple" as const,
|
|
227
|
+
relevantFiles: ["src/api/redirect.ts"],
|
|
228
|
+
reasoning: "Simple handler with lookup and redirect. 1 file, ~50 LOC.",
|
|
229
|
+
estimatedLOC: 50,
|
|
230
|
+
risks: [],
|
|
231
|
+
},
|
|
232
|
+
{
|
|
233
|
+
id: "US-004",
|
|
234
|
+
title: "Build URL shortening handler",
|
|
235
|
+
description: "POST /api/shorten route that accepts URL and returns short code",
|
|
236
|
+
acceptanceCriteria: [
|
|
237
|
+
"POST /api/shorten accepts JSON",
|
|
238
|
+
"URL validation works",
|
|
239
|
+
"Returns short code in response",
|
|
240
|
+
"Error handling for invalid URLs",
|
|
241
|
+
],
|
|
242
|
+
tags: ["api", "core"],
|
|
243
|
+
dependencies: ["US-001", "US-002"],
|
|
244
|
+
complexity: "simple" as const,
|
|
245
|
+
relevantFiles: ["src/api/shorten.ts"],
|
|
246
|
+
reasoning: "Standard POST handler. 1 file, ~60 LOC.",
|
|
247
|
+
estimatedLOC: 60,
|
|
248
|
+
risks: [],
|
|
249
|
+
},
|
|
250
|
+
{
|
|
251
|
+
id: "US-005",
|
|
252
|
+
title: "Implement click analytics tracking",
|
|
253
|
+
description: "Track clicks on each short URL with timestamps and IP addresses",
|
|
254
|
+
acceptanceCriteria: [
|
|
255
|
+
"Clicks logged with timestamp",
|
|
256
|
+
"IP address recorded (anonymized)",
|
|
257
|
+
"Analytics queryable by code",
|
|
258
|
+
"Performance doesn't block redirects",
|
|
259
|
+
],
|
|
260
|
+
tags: ["analytics", "database"],
|
|
261
|
+
dependencies: ["US-003"],
|
|
262
|
+
complexity: "medium" as const,
|
|
263
|
+
relevantFiles: ["src/analytics/tracker.ts", "src/db/analytics-schema.ts"],
|
|
264
|
+
reasoning: "Async logging with privacy concerns. 2 files, ~100 LOC.",
|
|
265
|
+
estimatedLOC: 100,
|
|
266
|
+
risks: ["Privacy compliance (GDPR)", "Performance under high traffic"],
|
|
267
|
+
},
|
|
268
|
+
];
|
|
269
|
+
|
|
270
|
+
return { stories };
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
reset() {
|
|
274
|
+
this.shouldFailRun = false;
|
|
275
|
+
this.shouldRateLimit = false;
|
|
276
|
+
this.shouldFailReview = false;
|
|
277
|
+
this.callCount = 0;
|
|
278
|
+
this.runCalls = [];
|
|
279
|
+
this.planCalls = [];
|
|
280
|
+
this.decomposeCalls = [];
|
|
281
|
+
this.attemptCountMap.clear();
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
/**
|
|
286
|
+
* Register mock agent in the registry for testing
|
|
287
|
+
*
|
|
288
|
+
* Modifies the ALL_AGENTS array to include the mock agent
|
|
289
|
+
*/
|
|
290
|
+
function registerMockAgent(adapter: MockAgentAdapter): () => void {
|
|
291
|
+
// Add mock agent to registry
|
|
292
|
+
ALL_AGENTS.push(adapter);
|
|
293
|
+
|
|
294
|
+
// Return cleanup function that removes it
|
|
295
|
+
return () => {
|
|
296
|
+
const index = ALL_AGENTS.findIndex((a) => a.name === "mock");
|
|
297
|
+
if (index >= 0) {
|
|
298
|
+
ALL_AGENTS.splice(index, 1);
|
|
299
|
+
}
|
|
300
|
+
};
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
describe("E2E: plan → analyze → run workflow", () => {
|
|
304
|
+
let testDir: string;
|
|
305
|
+
let mockAgent: MockAgentAdapter;
|
|
306
|
+
let cleanup: () => void;
|
|
307
|
+
|
|
308
|
+
beforeAll(() => {
|
|
309
|
+
// Create mock agent and register once for the whole suite
|
|
310
|
+
mockAgent = new MockAgentAdapter();
|
|
311
|
+
cleanup = registerMockAgent(mockAgent);
|
|
312
|
+
});
|
|
313
|
+
|
|
314
|
+
afterAll(() => {
|
|
315
|
+
// Unregister mock agent once after all tests complete
|
|
316
|
+
cleanup();
|
|
317
|
+
});
|
|
318
|
+
|
|
319
|
+
beforeEach(() => {
|
|
320
|
+
// Initialize logger
|
|
321
|
+
initLogger({ level: "error", useChalk: false });
|
|
322
|
+
|
|
323
|
+
// Reset mock agent state between tests
|
|
324
|
+
mockAgent.reset();
|
|
325
|
+
|
|
326
|
+
// Create temp directory
|
|
327
|
+
testDir = `/tmp/nax-e2e-test-${Date.now()}`;
|
|
328
|
+
mkdirSync(testDir, { recursive: true });
|
|
329
|
+
|
|
330
|
+
// Set up minimal project structure
|
|
331
|
+
setupTestProject(testDir);
|
|
332
|
+
});
|
|
333
|
+
|
|
334
|
+
afterEach(() => {
|
|
335
|
+
// Clean up temp directory
|
|
336
|
+
if (existsSync(testDir)) {
|
|
337
|
+
rmSync(testDir, { recursive: true, force: true });
|
|
338
|
+
}
|
|
339
|
+
// Reset logger
|
|
340
|
+
resetLogger();
|
|
341
|
+
});
|
|
342
|
+
|
|
343
|
+
test("full workflow: init → plan → analyze → run", { timeout: 120000 }, async () => {
|
|
344
|
+
const ngentDir = join(testDir, "nax");
|
|
345
|
+
const featureDir = join(ngentDir, "features/url-shortener");
|
|
346
|
+
mkdirSync(featureDir, { recursive: true });
|
|
347
|
+
|
|
348
|
+
// Step 1: Initialize (create config, constitution, hooks)
|
|
349
|
+
await initializeNgent(ngentDir);
|
|
350
|
+
expect(existsSync(join(ngentDir, "config.json"))).toBe(true);
|
|
351
|
+
expect(existsSync(join(ngentDir, "constitution.md"))).toBe(true);
|
|
352
|
+
expect(existsSync(join(ngentDir, "hooks.json"))).toBe(true);
|
|
353
|
+
|
|
354
|
+
// Step 2: Plan (manually create spec.md since mock agent doesn't spawn real process)
|
|
355
|
+
const config = createTestConfig();
|
|
356
|
+
const specPath = join(featureDir, "spec.md");
|
|
357
|
+
const spec = await mockAgent.plan({
|
|
358
|
+
prompt: "Build a URL shortener with analytics",
|
|
359
|
+
workdir: testDir,
|
|
360
|
+
interactive: false,
|
|
361
|
+
});
|
|
362
|
+
await Bun.write(specPath, spec.specContent);
|
|
363
|
+
|
|
364
|
+
expect(existsSync(specPath)).toBe(true);
|
|
365
|
+
const specContent = await Bun.file(specPath).text();
|
|
366
|
+
expect(specContent).toContain("# Feature: URL Shortener");
|
|
367
|
+
expect(specContent).toContain("## Requirements");
|
|
368
|
+
expect(mockAgent.planCalls).toHaveLength(1);
|
|
369
|
+
|
|
370
|
+
// Step 3: Analyze (decompose spec into prd.json)
|
|
371
|
+
const prd = await analyzeFeature({
|
|
372
|
+
featureDir,
|
|
373
|
+
featureName: "url-shortener",
|
|
374
|
+
branchName: "feat/url-shortener",
|
|
375
|
+
config,
|
|
376
|
+
});
|
|
377
|
+
|
|
378
|
+
expect(prd.userStories).toHaveLength(5);
|
|
379
|
+
expect(prd.userStories[0].id).toBe("US-001");
|
|
380
|
+
expect(prd.userStories[0].routing?.complexity).toBe("medium");
|
|
381
|
+
expect(prd.userStories[2].dependencies).toContain("US-001");
|
|
382
|
+
expect(mockAgent.decomposeCalls).toHaveLength(1);
|
|
383
|
+
|
|
384
|
+
// Save PRD
|
|
385
|
+
const prdPath = join(featureDir, "prd.json");
|
|
386
|
+
await Bun.write(prdPath, JSON.stringify(prd, null, 2));
|
|
387
|
+
|
|
388
|
+
// Step 4: Run (execute stories via pipeline)
|
|
389
|
+
const runResult = await run({
|
|
390
|
+
prdPath,
|
|
391
|
+
workdir: testDir,
|
|
392
|
+
config: {
|
|
393
|
+
...config,
|
|
394
|
+
execution: {
|
|
395
|
+
...config.execution,
|
|
396
|
+
maxIterations: 10, // Enough for 5 stories
|
|
397
|
+
},
|
|
398
|
+
},
|
|
399
|
+
hooks: { hooks: {} },
|
|
400
|
+
feature: "url-shortener",
|
|
401
|
+
featureDir,
|
|
402
|
+
dryRun: false,
|
|
403
|
+
useBatch: true, // Enable batching
|
|
404
|
+
skipPrecheck: true, // Skip precheck for E2E test (no git repo in temp dir)
|
|
405
|
+
});
|
|
406
|
+
|
|
407
|
+
expect(runResult.success).toBe(true);
|
|
408
|
+
expect(runResult.storiesCompleted).toBe(5);
|
|
409
|
+
expect(mockAgent.runCalls.length).toBeGreaterThan(0);
|
|
410
|
+
|
|
411
|
+
// Verify PRD was updated
|
|
412
|
+
const finalPRD = await loadPRD(prdPath);
|
|
413
|
+
expect(finalPRD.userStories.every((s) => s.status === "passed")).toBe(true);
|
|
414
|
+
});
|
|
415
|
+
|
|
416
|
+
test("pipeline stages execute in correct order", { timeout: 15000 }, async () => {
|
|
417
|
+
const ngentDir = join(testDir, "nax");
|
|
418
|
+
const featureDir = join(ngentDir, "features/simple-task");
|
|
419
|
+
mkdirSync(featureDir, { recursive: true });
|
|
420
|
+
|
|
421
|
+
await initializeNgent(ngentDir);
|
|
422
|
+
|
|
423
|
+
// Create minimal PRD with one simple story
|
|
424
|
+
const prd = {
|
|
425
|
+
project: "test",
|
|
426
|
+
feature: "simple-task",
|
|
427
|
+
branchName: "feat/simple-task",
|
|
428
|
+
createdAt: new Date().toISOString(),
|
|
429
|
+
updatedAt: new Date().toISOString(),
|
|
430
|
+
userStories: [
|
|
431
|
+
{
|
|
432
|
+
id: "US-001",
|
|
433
|
+
title: "Add console log",
|
|
434
|
+
description: "Add a console.log statement to index.ts",
|
|
435
|
+
acceptanceCriteria: ["Log statement added"],
|
|
436
|
+
tags: [],
|
|
437
|
+
dependencies: [],
|
|
438
|
+
status: "pending" as const,
|
|
439
|
+
passes: false,
|
|
440
|
+
escalations: [],
|
|
441
|
+
attempts: 0,
|
|
442
|
+
routing: {
|
|
443
|
+
complexity: "simple" as const,
|
|
444
|
+
modelTier: "fast" as const,
|
|
445
|
+
testStrategy: "test-after" as const,
|
|
446
|
+
reasoning: "Trivial change",
|
|
447
|
+
estimatedLOC: 1,
|
|
448
|
+
risks: [],
|
|
449
|
+
},
|
|
450
|
+
},
|
|
451
|
+
],
|
|
452
|
+
};
|
|
453
|
+
|
|
454
|
+
const prdPath = join(featureDir, "prd.json");
|
|
455
|
+
await Bun.write(prdPath, JSON.stringify(prd, null, 2));
|
|
456
|
+
|
|
457
|
+
const config = createTestConfig();
|
|
458
|
+
|
|
459
|
+
// Run execution
|
|
460
|
+
await run({
|
|
461
|
+
prdPath,
|
|
462
|
+
workdir: testDir,
|
|
463
|
+
config,
|
|
464
|
+
hooks: {
|
|
465
|
+
hooks: {
|
|
466
|
+
"on-story-start": {
|
|
467
|
+
command: "echo story-start",
|
|
468
|
+
enabled: true,
|
|
469
|
+
timeout: 60000,
|
|
470
|
+
},
|
|
471
|
+
"on-story-complete": {
|
|
472
|
+
command: "echo story-complete",
|
|
473
|
+
enabled: true,
|
|
474
|
+
timeout: 60000,
|
|
475
|
+
},
|
|
476
|
+
},
|
|
477
|
+
},
|
|
478
|
+
feature: "simple-task",
|
|
479
|
+
featureDir,
|
|
480
|
+
dryRun: false,
|
|
481
|
+
skipPrecheck: true, // Skip precheck for E2E test (no git repo in temp dir)
|
|
482
|
+
});
|
|
483
|
+
|
|
484
|
+
// Verify agent was called (execution stage ran)
|
|
485
|
+
expect(mockAgent.runCalls.length).toBeGreaterThan(0);
|
|
486
|
+
|
|
487
|
+
// Verify story completed (completion stage ran)
|
|
488
|
+
const finalPRD = await loadPRD(prdPath);
|
|
489
|
+
expect(finalPRD.userStories[0].status).toBe("passed");
|
|
490
|
+
});
|
|
491
|
+
|
|
492
|
+
test("agent failure triggers escalation", { timeout: 60000 }, async () => {
|
|
493
|
+
const ngentDir = join(testDir, "nax");
|
|
494
|
+
const featureDir = join(ngentDir, "features/fail-task");
|
|
495
|
+
mkdirSync(featureDir, { recursive: true });
|
|
496
|
+
|
|
497
|
+
await initializeNgent(ngentDir);
|
|
498
|
+
|
|
499
|
+
const prd = {
|
|
500
|
+
project: "test",
|
|
501
|
+
feature: "fail-task",
|
|
502
|
+
branchName: "feat/fail-task",
|
|
503
|
+
createdAt: new Date().toISOString(),
|
|
504
|
+
updatedAt: new Date().toISOString(),
|
|
505
|
+
userStories: [
|
|
506
|
+
{
|
|
507
|
+
id: "US-001",
|
|
508
|
+
title: "Task that will fail",
|
|
509
|
+
description: "This task will fail on first attempt",
|
|
510
|
+
acceptanceCriteria: ["Task complete"],
|
|
511
|
+
tags: [],
|
|
512
|
+
dependencies: [],
|
|
513
|
+
status: "pending" as const,
|
|
514
|
+
passes: false,
|
|
515
|
+
escalations: [],
|
|
516
|
+
attempts: 0,
|
|
517
|
+
routing: {
|
|
518
|
+
complexity: "simple" as const,
|
|
519
|
+
modelTier: "fast" as const,
|
|
520
|
+
testStrategy: "test-after" as const,
|
|
521
|
+
reasoning: "Simple task",
|
|
522
|
+
estimatedLOC: 10,
|
|
523
|
+
risks: [],
|
|
524
|
+
},
|
|
525
|
+
},
|
|
526
|
+
],
|
|
527
|
+
};
|
|
528
|
+
|
|
529
|
+
const prdPath = join(featureDir, "prd.json");
|
|
530
|
+
await Bun.write(prdPath, JSON.stringify(prd, null, 2));
|
|
531
|
+
|
|
532
|
+
// Make first call fail, subsequent calls succeed
|
|
533
|
+
let failCount = 0;
|
|
534
|
+
const originalRun = mockAgent.run.bind(mockAgent);
|
|
535
|
+
mockAgent.run = async (opts: AgentRunOptions): Promise<AgentResult> => {
|
|
536
|
+
failCount++;
|
|
537
|
+
if (failCount === 1) {
|
|
538
|
+
// First call fails
|
|
539
|
+
return {
|
|
540
|
+
success: false,
|
|
541
|
+
exitCode: 1,
|
|
542
|
+
output: "Tests failed",
|
|
543
|
+
rateLimited: false,
|
|
544
|
+
durationMs: 100,
|
|
545
|
+
estimatedCost: 0.01,
|
|
546
|
+
};
|
|
547
|
+
}
|
|
548
|
+
// Subsequent calls succeed
|
|
549
|
+
return originalRun(opts);
|
|
550
|
+
};
|
|
551
|
+
|
|
552
|
+
const config = createTestConfig();
|
|
553
|
+
|
|
554
|
+
await run({
|
|
555
|
+
prdPath,
|
|
556
|
+
workdir: testDir,
|
|
557
|
+
config,
|
|
558
|
+
hooks: { hooks: {} },
|
|
559
|
+
feature: "fail-task",
|
|
560
|
+
featureDir,
|
|
561
|
+
dryRun: false,
|
|
562
|
+
skipPrecheck: true, // Skip precheck for E2E test (no git repo in temp dir)
|
|
563
|
+
});
|
|
564
|
+
|
|
565
|
+
// Verify story completed (escalation auto-handled by system)
|
|
566
|
+
const finalPRD = await loadPRD(prdPath);
|
|
567
|
+
|
|
568
|
+
// The story should complete after escalation kicks in
|
|
569
|
+
expect(finalPRD.userStories[0].status).toBe("passed");
|
|
570
|
+
});
|
|
571
|
+
|
|
572
|
+
test("rate limit triggers retry with backoff", { timeout: 60000 }, async () => {
|
|
573
|
+
const ngentDir = join(testDir, "nax");
|
|
574
|
+
const featureDir = join(ngentDir, "features/rate-limit-task");
|
|
575
|
+
mkdirSync(featureDir, { recursive: true });
|
|
576
|
+
|
|
577
|
+
await initializeNgent(ngentDir);
|
|
578
|
+
|
|
579
|
+
const prd = {
|
|
580
|
+
project: "test",
|
|
581
|
+
feature: "rate-limit-task",
|
|
582
|
+
branchName: "feat/rate-limit",
|
|
583
|
+
createdAt: new Date().toISOString(),
|
|
584
|
+
updatedAt: new Date().toISOString(),
|
|
585
|
+
userStories: [
|
|
586
|
+
{
|
|
587
|
+
id: "US-001",
|
|
588
|
+
title: "Task with rate limit",
|
|
589
|
+
description: "This task will hit rate limit once",
|
|
590
|
+
acceptanceCriteria: ["Task complete"],
|
|
591
|
+
tags: [],
|
|
592
|
+
dependencies: [],
|
|
593
|
+
status: "pending" as const,
|
|
594
|
+
passes: false,
|
|
595
|
+
escalations: [],
|
|
596
|
+
attempts: 0,
|
|
597
|
+
routing: {
|
|
598
|
+
complexity: "simple" as const,
|
|
599
|
+
modelTier: "fast" as const,
|
|
600
|
+
testStrategy: "test-after" as const,
|
|
601
|
+
reasoning: "Simple task",
|
|
602
|
+
estimatedLOC: 10,
|
|
603
|
+
risks: [],
|
|
604
|
+
},
|
|
605
|
+
},
|
|
606
|
+
],
|
|
607
|
+
};
|
|
608
|
+
|
|
609
|
+
const prdPath = join(featureDir, "prd.json");
|
|
610
|
+
await Bun.write(prdPath, JSON.stringify(prd, null, 2));
|
|
611
|
+
|
|
612
|
+
// Set rate limit on first call
|
|
613
|
+
mockAgent.shouldRateLimit = true;
|
|
614
|
+
|
|
615
|
+
const config = createTestConfig();
|
|
616
|
+
|
|
617
|
+
const runResult = await run({
|
|
618
|
+
prdPath,
|
|
619
|
+
workdir: testDir,
|
|
620
|
+
config,
|
|
621
|
+
hooks: { hooks: {} },
|
|
622
|
+
feature: "rate-limit-task",
|
|
623
|
+
featureDir,
|
|
624
|
+
dryRun: false,
|
|
625
|
+
skipPrecheck: true, // Skip precheck for E2E test (no git repo in temp dir)
|
|
626
|
+
});
|
|
627
|
+
|
|
628
|
+
expect(runResult.success).toBe(true);
|
|
629
|
+
|
|
630
|
+
const finalPRD = await loadPRD(prdPath);
|
|
631
|
+
expect(finalPRD.userStories[0].status).toBe("passed");
|
|
632
|
+
});
|
|
633
|
+
|
|
634
|
+
test.skip("review phase failure marks story as failed (skipped - review disabled in tests)", async () => {
|
|
635
|
+
// This test is skipped because review is disabled in test config to avoid mocking
|
|
636
|
+
// typecheck/lint/test commands. In a real scenario with review enabled, this would
|
|
637
|
+
// test that review failures are properly handled.
|
|
638
|
+
});
|
|
639
|
+
|
|
640
|
+
test("story batching groups simple stories", { timeout: 15000 }, async () => {
|
|
641
|
+
const ngentDir = join(testDir, "nax");
|
|
642
|
+
const featureDir = join(ngentDir, "features/batch-test");
|
|
643
|
+
mkdirSync(featureDir, { recursive: true });
|
|
644
|
+
|
|
645
|
+
await initializeNgent(ngentDir);
|
|
646
|
+
|
|
647
|
+
const prd = {
|
|
648
|
+
project: "test",
|
|
649
|
+
feature: "batch-test",
|
|
650
|
+
branchName: "feat/batch",
|
|
651
|
+
createdAt: new Date().toISOString(),
|
|
652
|
+
updatedAt: new Date().toISOString(),
|
|
653
|
+
userStories: [
|
|
654
|
+
{
|
|
655
|
+
id: "US-001",
|
|
656
|
+
title: "Add log statement 1",
|
|
657
|
+
description: "Add console.log to file1.ts",
|
|
658
|
+
acceptanceCriteria: ["Log added"],
|
|
659
|
+
tags: [],
|
|
660
|
+
dependencies: [],
|
|
661
|
+
status: "pending" as const,
|
|
662
|
+
passes: false,
|
|
663
|
+
escalations: [],
|
|
664
|
+
attempts: 0,
|
|
665
|
+
routing: {
|
|
666
|
+
complexity: "simple" as const,
|
|
667
|
+
modelTier: "fast" as const,
|
|
668
|
+
testStrategy: "test-after" as const,
|
|
669
|
+
reasoning: "Trivial",
|
|
670
|
+
estimatedLOC: 1,
|
|
671
|
+
risks: [],
|
|
672
|
+
},
|
|
673
|
+
},
|
|
674
|
+
{
|
|
675
|
+
id: "US-002",
|
|
676
|
+
title: "Add log statement 2",
|
|
677
|
+
description: "Add console.log to file2.ts",
|
|
678
|
+
acceptanceCriteria: ["Log added"],
|
|
679
|
+
tags: [],
|
|
680
|
+
dependencies: [],
|
|
681
|
+
status: "pending" as const,
|
|
682
|
+
passes: false,
|
|
683
|
+
escalations: [],
|
|
684
|
+
attempts: 0,
|
|
685
|
+
routing: {
|
|
686
|
+
complexity: "simple" as const,
|
|
687
|
+
modelTier: "fast" as const,
|
|
688
|
+
testStrategy: "test-after" as const,
|
|
689
|
+
reasoning: "Trivial",
|
|
690
|
+
estimatedLOC: 1,
|
|
691
|
+
risks: [],
|
|
692
|
+
},
|
|
693
|
+
},
|
|
694
|
+
{
|
|
695
|
+
id: "US-003",
|
|
696
|
+
title: "Add log statement 3",
|
|
697
|
+
description: "Add console.log to file3.ts",
|
|
698
|
+
acceptanceCriteria: ["Log added"],
|
|
699
|
+
tags: [],
|
|
700
|
+
dependencies: [],
|
|
701
|
+
status: "pending" as const,
|
|
702
|
+
passes: false,
|
|
703
|
+
escalations: [],
|
|
704
|
+
attempts: 0,
|
|
705
|
+
routing: {
|
|
706
|
+
complexity: "simple" as const,
|
|
707
|
+
modelTier: "fast" as const,
|
|
708
|
+
testStrategy: "test-after" as const,
|
|
709
|
+
reasoning: "Trivial",
|
|
710
|
+
estimatedLOC: 1,
|
|
711
|
+
risks: [],
|
|
712
|
+
},
|
|
713
|
+
},
|
|
714
|
+
],
|
|
715
|
+
};
|
|
716
|
+
|
|
717
|
+
const prdPath = join(featureDir, "prd.json");
|
|
718
|
+
await Bun.write(prdPath, JSON.stringify(prd, null, 2));
|
|
719
|
+
|
|
720
|
+
const config = createTestConfig();
|
|
721
|
+
|
|
722
|
+
const runResult = await run({
|
|
723
|
+
prdPath,
|
|
724
|
+
workdir: testDir,
|
|
725
|
+
config,
|
|
726
|
+
hooks: { hooks: {} },
|
|
727
|
+
feature: "batch-test",
|
|
728
|
+
featureDir,
|
|
729
|
+
dryRun: false,
|
|
730
|
+
useBatch: true,
|
|
731
|
+
skipPrecheck: true, // Skip precheck for E2E test (no git repo in temp dir)
|
|
732
|
+
});
|
|
733
|
+
|
|
734
|
+
expect(runResult.success).toBe(true);
|
|
735
|
+
expect(runResult.storiesCompleted).toBe(3);
|
|
736
|
+
|
|
737
|
+
// With batching, should have fewer agent calls than stories
|
|
738
|
+
// (3 simple stories should be batched into 1 call)
|
|
739
|
+
expect(mockAgent.runCalls.length).toBeLessThan(3);
|
|
740
|
+
|
|
741
|
+
// Verify all stories completed
|
|
742
|
+
const finalPRD = await loadPRD(prdPath);
|
|
743
|
+
expect(finalPRD.userStories.every((s) => s.status === "passed")).toBe(true);
|
|
744
|
+
});
|
|
745
|
+
});
|
|
746
|
+
|
|
747
|
+
// ── Helper Functions ──────────────────────────────────
|
|
748
|
+
|
|
749
|
+
function setupTestProject(dir: string) {
|
|
750
|
+
// Create src/ directory
|
|
751
|
+
mkdirSync(join(dir, "src"), { recursive: true });
|
|
752
|
+
Bun.write(join(dir, "src/index.ts"), "export const greet = () => 'Hello';\n");
|
|
753
|
+
|
|
754
|
+
// Create test/ directory
|
|
755
|
+
mkdirSync(join(dir, "test"), { recursive: true });
|
|
756
|
+
Bun.write(
|
|
757
|
+
join(dir, "test/index.test.ts"),
|
|
758
|
+
"import { expect, test } from 'bun:test';\nimport { greet } from '../../src/index';\n\ntest('greet', () => {\n expect(greet()).toBe('Hello');\n});\n",
|
|
759
|
+
);
|
|
760
|
+
|
|
761
|
+
// Create package.json
|
|
762
|
+
Bun.write(
|
|
763
|
+
join(dir, "package.json"),
|
|
764
|
+
JSON.stringify(
|
|
765
|
+
{
|
|
766
|
+
name: "e2e-test-project",
|
|
767
|
+
version: "1.0.0",
|
|
768
|
+
dependencies: {
|
|
769
|
+
zod: "^4.0.0",
|
|
770
|
+
},
|
|
771
|
+
devDependencies: {
|
|
772
|
+
typescript: "^5.0.0",
|
|
773
|
+
"@types/bun": "^1.0.0",
|
|
774
|
+
},
|
|
775
|
+
},
|
|
776
|
+
null,
|
|
777
|
+
2,
|
|
778
|
+
),
|
|
779
|
+
);
|
|
780
|
+
|
|
781
|
+
// Create tsconfig.json
|
|
782
|
+
Bun.write(
|
|
783
|
+
join(dir, "tsconfig.json"),
|
|
784
|
+
JSON.stringify(
|
|
785
|
+
{
|
|
786
|
+
compilerOptions: {
|
|
787
|
+
target: "ES2022",
|
|
788
|
+
module: "ESNext",
|
|
789
|
+
moduleResolution: "bundler",
|
|
790
|
+
strict: true,
|
|
791
|
+
esModuleInterop: true,
|
|
792
|
+
skipLibCheck: true,
|
|
793
|
+
forceConsistentCasingInFileNames: true,
|
|
794
|
+
outDir: "./dist",
|
|
795
|
+
},
|
|
796
|
+
include: ["src/**/*"],
|
|
797
|
+
exclude: ["node_modules", "dist"],
|
|
798
|
+
},
|
|
799
|
+
null,
|
|
800
|
+
2,
|
|
801
|
+
),
|
|
802
|
+
);
|
|
803
|
+
}
|
|
804
|
+
|
|
805
|
+
async function initializeNgent(ngentDir: string) {
|
|
806
|
+
// Create directory structure
|
|
807
|
+
mkdirSync(join(ngentDir, "features"), { recursive: true });
|
|
808
|
+
mkdirSync(join(ngentDir, "hooks"), { recursive: true });
|
|
809
|
+
|
|
810
|
+
// Write config.json
|
|
811
|
+
await Bun.write(join(ngentDir, "config.json"), JSON.stringify(DEFAULT_CONFIG, null, 2));
|
|
812
|
+
|
|
813
|
+
// Write hooks.json
|
|
814
|
+
await Bun.write(
|
|
815
|
+
join(ngentDir, "hooks.json"),
|
|
816
|
+
JSON.stringify(
|
|
817
|
+
{
|
|
818
|
+
hooks: {
|
|
819
|
+
"on-start": { command: "echo nax started", enabled: false },
|
|
820
|
+
"on-complete": { command: "echo nax complete", enabled: false },
|
|
821
|
+
},
|
|
822
|
+
},
|
|
823
|
+
null,
|
|
824
|
+
2,
|
|
825
|
+
),
|
|
826
|
+
);
|
|
827
|
+
|
|
828
|
+
// Write constitution.md
|
|
829
|
+
await Bun.write(
|
|
830
|
+
join(ngentDir, "constitution.md"),
|
|
831
|
+
`# Project Constitution
|
|
832
|
+
|
|
833
|
+
## Coding Standards
|
|
834
|
+
- Write clear, maintainable code
|
|
835
|
+
- Follow project conventions
|
|
836
|
+
|
|
837
|
+
## Testing Requirements
|
|
838
|
+
- All code must have tests
|
|
839
|
+
- Aim for 80%+ coverage
|
|
840
|
+
|
|
841
|
+
## Architecture Rules
|
|
842
|
+
- Keep functions small and focused
|
|
843
|
+
- Avoid tight coupling
|
|
844
|
+
`,
|
|
845
|
+
);
|
|
846
|
+
}
|
|
847
|
+
|
|
848
|
+
function createTestConfig(): NaxConfig {
|
|
849
|
+
return {
|
|
850
|
+
...DEFAULT_CONFIG,
|
|
851
|
+
autoMode: {
|
|
852
|
+
...DEFAULT_CONFIG.autoMode,
|
|
853
|
+
defaultAgent: "mock", // Use our mock agent
|
|
854
|
+
escalation: {
|
|
855
|
+
...DEFAULT_CONFIG.autoMode.escalation,
|
|
856
|
+
enabled: true,
|
|
857
|
+
tierOrder: [
|
|
858
|
+
{ tier: "fast", attempts: 1 },
|
|
859
|
+
{ tier: "balanced", attempts: 1 },
|
|
860
|
+
],
|
|
861
|
+
},
|
|
862
|
+
},
|
|
863
|
+
analyze: {
|
|
864
|
+
...DEFAULT_CONFIG.analyze,
|
|
865
|
+
llmEnhanced: true, // Enable LLM decompose
|
|
866
|
+
},
|
|
867
|
+
execution: {
|
|
868
|
+
...DEFAULT_CONFIG.execution,
|
|
869
|
+
maxIterations: 15, // Reduced from 20 to fail faster in tests
|
|
870
|
+
maxStoriesPerFeature: 500,
|
|
871
|
+
regressionGate: {
|
|
872
|
+
...DEFAULT_CONFIG.execution.regressionGate,
|
|
873
|
+
enabled: false, // Disable regression gate for E2E tests
|
|
874
|
+
},
|
|
875
|
+
rectification: {
|
|
876
|
+
...DEFAULT_CONFIG.execution.rectification,
|
|
877
|
+
enabled: false, // Disable rectification for E2E tests
|
|
878
|
+
},
|
|
879
|
+
},
|
|
880
|
+
quality: {
|
|
881
|
+
...DEFAULT_CONFIG.quality,
|
|
882
|
+
requireTypecheck: false,
|
|
883
|
+
requireLint: false,
|
|
884
|
+
requireTests: false,
|
|
885
|
+
commands: {}, // No quality commands for E2E tests
|
|
886
|
+
},
|
|
887
|
+
review: {
|
|
888
|
+
...DEFAULT_CONFIG.review,
|
|
889
|
+
enabled: false, // Disable review for tests (would require mocking typecheck/lint/test)
|
|
890
|
+
},
|
|
891
|
+
acceptance: {
|
|
892
|
+
...DEFAULT_CONFIG.acceptance,
|
|
893
|
+
enabled: false, // Disable acceptance for E2E tests (no real acceptance tests)
|
|
894
|
+
},
|
|
895
|
+
};
|
|
896
|
+
}
|