@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,229 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CLI Integration Tests for `nax config` Default View
|
|
3
|
+
*
|
|
4
|
+
* Tests the full end-to-end flow of running `nax config` without flags
|
|
5
|
+
* via the CLI entry point (bin/nax.ts).
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { describe, test, expect, beforeEach, afterEach } from "bun:test";
|
|
9
|
+
import { mkdtempSync, rmSync, writeFileSync, mkdirSync } from "node:fs";
|
|
10
|
+
import { tmpdir } from "node:os";
|
|
11
|
+
import { join } from "node:path";
|
|
12
|
+
|
|
13
|
+
describe("nax config (default view) - CLI integration", () => {
|
|
14
|
+
let tempDir: string;
|
|
15
|
+
let originalCwd: string;
|
|
16
|
+
|
|
17
|
+
beforeEach(() => {
|
|
18
|
+
// Create temp directory
|
|
19
|
+
tempDir = mkdtempSync(join(tmpdir(), "nax-config-cli-test-"));
|
|
20
|
+
originalCwd = process.cwd();
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
afterEach(() => {
|
|
24
|
+
// Cleanup
|
|
25
|
+
process.chdir(originalCwd);
|
|
26
|
+
rmSync(tempDir, { recursive: true, force: true });
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
test("prints effective merged config as JSON with header", async () => {
|
|
30
|
+
process.chdir(tempDir);
|
|
31
|
+
|
|
32
|
+
// Run `nax config` command
|
|
33
|
+
const proc = Bun.spawn(["bun", join(import.meta.dir, "../../bin/nax.ts"), "config"], {
|
|
34
|
+
cwd: tempDir,
|
|
35
|
+
stdout: "pipe",
|
|
36
|
+
stderr: "pipe",
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
const output = await new Response(proc.stdout).text();
|
|
40
|
+
const exitCode = await proc.exited;
|
|
41
|
+
|
|
42
|
+
expect(exitCode).toBe(0);
|
|
43
|
+
|
|
44
|
+
// Should have header
|
|
45
|
+
expect(output).toContain("// nax Configuration");
|
|
46
|
+
expect(output).toContain("// Resolution order: defaults → global → project → CLI overrides");
|
|
47
|
+
expect(output).toContain("// Global config:");
|
|
48
|
+
expect(output).toContain("// Project config:");
|
|
49
|
+
|
|
50
|
+
// Should have valid JSON after header
|
|
51
|
+
const lines = output.split("\n");
|
|
52
|
+
const jsonStartIndex = lines.findIndex((line) => line.startsWith("{"));
|
|
53
|
+
expect(jsonStartIndex).toBeGreaterThan(0);
|
|
54
|
+
|
|
55
|
+
const jsonOutput = lines.slice(jsonStartIndex).join("\n");
|
|
56
|
+
expect(() => JSON.parse(jsonOutput)).not.toThrow();
|
|
57
|
+
|
|
58
|
+
const parsed = JSON.parse(jsonOutput);
|
|
59
|
+
expect(parsed.version).toBe(1);
|
|
60
|
+
expect(parsed.models).toBeDefined();
|
|
61
|
+
expect(parsed.autoMode).toBeDefined();
|
|
62
|
+
expect(parsed.execution).toBeDefined();
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
test("shows global config path in header", async () => {
|
|
66
|
+
process.chdir(tempDir);
|
|
67
|
+
|
|
68
|
+
const proc = Bun.spawn(["bun", join(import.meta.dir, "../../bin/nax.ts"), "config"], {
|
|
69
|
+
cwd: tempDir,
|
|
70
|
+
stdout: "pipe",
|
|
71
|
+
stderr: "pipe",
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
const output = await new Response(proc.stdout).text();
|
|
75
|
+
const exitCode = await proc.exited;
|
|
76
|
+
|
|
77
|
+
expect(exitCode).toBe(0);
|
|
78
|
+
|
|
79
|
+
// Should show global config path (may be "not found" or actual path)
|
|
80
|
+
expect(output).toContain("// Global config:");
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
test("shows (not found) for missing project config", async () => {
|
|
84
|
+
// Use a directory without nax/config.json
|
|
85
|
+
const isolatedDir = join(tempDir, "isolated");
|
|
86
|
+
mkdirSync(isolatedDir, { recursive: true });
|
|
87
|
+
process.chdir(isolatedDir);
|
|
88
|
+
|
|
89
|
+
const proc = Bun.spawn(["bun", join(import.meta.dir, "../../bin/nax.ts"), "config"], {
|
|
90
|
+
cwd: isolatedDir,
|
|
91
|
+
stdout: "pipe",
|
|
92
|
+
stderr: "pipe",
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
const output = await new Response(proc.stdout).text();
|
|
96
|
+
const exitCode = await proc.exited;
|
|
97
|
+
|
|
98
|
+
expect(exitCode).toBe(0);
|
|
99
|
+
|
|
100
|
+
// Should show project config as not found
|
|
101
|
+
expect(output).toContain("// Project config: (not found)");
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
test("shows project config path when present", async () => {
|
|
105
|
+
// Create project config
|
|
106
|
+
const naxDir = join(tempDir, "nax");
|
|
107
|
+
mkdirSync(naxDir, { recursive: true });
|
|
108
|
+
writeFileSync(
|
|
109
|
+
join(naxDir, "config.json"),
|
|
110
|
+
JSON.stringify({
|
|
111
|
+
execution: {
|
|
112
|
+
maxIterations: 20,
|
|
113
|
+
},
|
|
114
|
+
}),
|
|
115
|
+
);
|
|
116
|
+
|
|
117
|
+
process.chdir(tempDir);
|
|
118
|
+
|
|
119
|
+
const proc = Bun.spawn(["bun", join(import.meta.dir, "../../bin/nax.ts"), "config"], {
|
|
120
|
+
cwd: tempDir,
|
|
121
|
+
stdout: "pipe",
|
|
122
|
+
stderr: "pipe",
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
const output = await new Response(proc.stdout).text();
|
|
126
|
+
const exitCode = await proc.exited;
|
|
127
|
+
|
|
128
|
+
expect(exitCode).toBe(0);
|
|
129
|
+
|
|
130
|
+
// Should show project config path
|
|
131
|
+
expect(output).toContain("// Project config:");
|
|
132
|
+
expect(output).toContain("config.json");
|
|
133
|
+
|
|
134
|
+
// Should reflect merged config
|
|
135
|
+
const lines = output.split("\n");
|
|
136
|
+
const jsonStartIndex = lines.findIndex((line) => line.startsWith("{"));
|
|
137
|
+
const jsonOutput = lines.slice(jsonStartIndex).join("\n");
|
|
138
|
+
const parsed = JSON.parse(jsonOutput);
|
|
139
|
+
expect(parsed.execution.maxIterations).toBe(20);
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
test("header precedes JSON output with blank line", async () => {
|
|
143
|
+
process.chdir(tempDir);
|
|
144
|
+
|
|
145
|
+
const proc = Bun.spawn(["bun", join(import.meta.dir, "../../bin/nax.ts"), "config"], {
|
|
146
|
+
cwd: tempDir,
|
|
147
|
+
stdout: "pipe",
|
|
148
|
+
stderr: "pipe",
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
const output = await new Response(proc.stdout).text();
|
|
152
|
+
const exitCode = await proc.exited;
|
|
153
|
+
|
|
154
|
+
expect(exitCode).toBe(0);
|
|
155
|
+
|
|
156
|
+
const lines = output.split("\n");
|
|
157
|
+
|
|
158
|
+
// Find header and JSON start
|
|
159
|
+
const headerLineIndex = lines.findIndex((line) => line.includes("// nax Configuration"));
|
|
160
|
+
const jsonLineIndex = lines.findIndex((line) => line.startsWith("{"));
|
|
161
|
+
|
|
162
|
+
expect(headerLineIndex).toBeGreaterThanOrEqual(0);
|
|
163
|
+
expect(jsonLineIndex).toBeGreaterThan(headerLineIndex);
|
|
164
|
+
|
|
165
|
+
// Should have blank line between header and JSON
|
|
166
|
+
expect(lines[jsonLineIndex - 1]).toBe("");
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
test("JSON output is pretty-printed (indented)", async () => {
|
|
170
|
+
process.chdir(tempDir);
|
|
171
|
+
|
|
172
|
+
const proc = Bun.spawn(["bun", join(import.meta.dir, "../../bin/nax.ts"), "config"], {
|
|
173
|
+
cwd: tempDir,
|
|
174
|
+
stdout: "pipe",
|
|
175
|
+
stderr: "pipe",
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
const output = await new Response(proc.stdout).text();
|
|
179
|
+
const exitCode = await proc.exited;
|
|
180
|
+
|
|
181
|
+
expect(exitCode).toBe(0);
|
|
182
|
+
|
|
183
|
+
// JSON should be pretty-printed with 2-space indentation
|
|
184
|
+
expect(output).toMatch(/"version": 1/);
|
|
185
|
+
expect(output).toMatch(/"models": \{/);
|
|
186
|
+
expect(output).toContain(" "); // Should have indentation
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
test("works when run from project subdirectory", async () => {
|
|
190
|
+
// Create project config
|
|
191
|
+
const naxDir = join(tempDir, "nax");
|
|
192
|
+
mkdirSync(naxDir, { recursive: true });
|
|
193
|
+
writeFileSync(
|
|
194
|
+
join(naxDir, "config.json"),
|
|
195
|
+
JSON.stringify({
|
|
196
|
+
execution: {
|
|
197
|
+
maxIterations: 30,
|
|
198
|
+
},
|
|
199
|
+
}),
|
|
200
|
+
);
|
|
201
|
+
|
|
202
|
+
// Create subdirectory and run from there
|
|
203
|
+
const subdir = join(tempDir, "src", "components");
|
|
204
|
+
mkdirSync(subdir, { recursive: true });
|
|
205
|
+
process.chdir(subdir);
|
|
206
|
+
|
|
207
|
+
const proc = Bun.spawn(["bun", join(import.meta.dir, "../../bin/nax.ts"), "config"], {
|
|
208
|
+
cwd: subdir,
|
|
209
|
+
stdout: "pipe",
|
|
210
|
+
stderr: "pipe",
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
const output = await new Response(proc.stdout).text();
|
|
214
|
+
const exitCode = await proc.exited;
|
|
215
|
+
|
|
216
|
+
expect(exitCode).toBe(0);
|
|
217
|
+
|
|
218
|
+
// Should find project config by walking up
|
|
219
|
+
expect(output).toContain("// Project config:");
|
|
220
|
+
expect(output).toContain("config.json");
|
|
221
|
+
|
|
222
|
+
// Should reflect merged config
|
|
223
|
+
const lines = output.split("\n");
|
|
224
|
+
const jsonStartIndex = lines.findIndex((line) => line.startsWith("{"));
|
|
225
|
+
const jsonOutput = lines.slice(jsonStartIndex).join("\n");
|
|
226
|
+
const parsed = JSON.parse(jsonOutput);
|
|
227
|
+
expect(parsed.execution.maxIterations).toBe(30);
|
|
228
|
+
});
|
|
229
|
+
});
|
|
@@ -0,0 +1,460 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Config Command --diff Flag Integration Tests
|
|
3
|
+
*
|
|
4
|
+
* Tests for `nax config --diff` command that shows only fields where
|
|
5
|
+
* project config overrides the global config.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { describe, test, expect, beforeEach, afterEach } from "bun:test";
|
|
9
|
+
import { mkdtempSync, rmSync, writeFileSync, mkdirSync } from "node:fs";
|
|
10
|
+
import { tmpdir } from "node:os";
|
|
11
|
+
import { join } from "node:path";
|
|
12
|
+
import { configCommand } from "../../src/cli/config";
|
|
13
|
+
import { loadConfig } from "../../src/config/loader";
|
|
14
|
+
|
|
15
|
+
describe("Config Command --diff", () => {
|
|
16
|
+
let tempDir: string;
|
|
17
|
+
let originalCwd: string;
|
|
18
|
+
let consoleOutput: string[];
|
|
19
|
+
let originalConsoleLog: typeof console.log;
|
|
20
|
+
let originalProcessExit: typeof process.exit;
|
|
21
|
+
let exitCode: number | undefined;
|
|
22
|
+
|
|
23
|
+
beforeEach(() => {
|
|
24
|
+
// Create temp directory
|
|
25
|
+
tempDir = mkdtempSync(join(tmpdir(), "nax-config-diff-test-"));
|
|
26
|
+
originalCwd = process.cwd();
|
|
27
|
+
|
|
28
|
+
// Capture console output
|
|
29
|
+
consoleOutput = [];
|
|
30
|
+
originalConsoleLog = console.log;
|
|
31
|
+
console.log = (...args: unknown[]) => {
|
|
32
|
+
consoleOutput.push(args.map((a) => String(a)).join(" "));
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
// Mock process.exit to capture exit code
|
|
36
|
+
exitCode = undefined;
|
|
37
|
+
originalProcessExit = process.exit;
|
|
38
|
+
process.exit = ((code?: number) => {
|
|
39
|
+
exitCode = code ?? 0;
|
|
40
|
+
throw new Error(`process.exit(${code ?? 0})`);
|
|
41
|
+
}) as typeof process.exit;
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
afterEach(() => {
|
|
45
|
+
// Restore console and process.exit
|
|
46
|
+
console.log = originalConsoleLog;
|
|
47
|
+
process.exit = originalProcessExit;
|
|
48
|
+
|
|
49
|
+
// Cleanup
|
|
50
|
+
process.chdir(originalCwd);
|
|
51
|
+
rmSync(tempDir, { recursive: true, force: true });
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
describe("No project config", () => {
|
|
55
|
+
test("shows 'No project config found' when no nax/config.json exists", async () => {
|
|
56
|
+
// Set up: no project config, just load defaults
|
|
57
|
+
process.chdir(tempDir);
|
|
58
|
+
const config = await loadConfig(tempDir);
|
|
59
|
+
|
|
60
|
+
// Run with --diff
|
|
61
|
+
await configCommand(config, { diff: true });
|
|
62
|
+
|
|
63
|
+
const output = consoleOutput.join("\n");
|
|
64
|
+
expect(output).toContain("No project config found — using global defaults");
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
test("shows 'No project config found' when nax/ dir exists but config.json doesn't", async () => {
|
|
68
|
+
// Create nax/ dir but no config.json
|
|
69
|
+
const naxDir = join(tempDir, "nax");
|
|
70
|
+
mkdirSync(naxDir, { recursive: true });
|
|
71
|
+
|
|
72
|
+
process.chdir(tempDir);
|
|
73
|
+
const config = await loadConfig(tempDir);
|
|
74
|
+
|
|
75
|
+
await configCommand(config, { diff: true });
|
|
76
|
+
|
|
77
|
+
const output = consoleOutput.join("\n");
|
|
78
|
+
expect(output).toContain("No project config found — using global defaults");
|
|
79
|
+
});
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
describe("Project config exists but identical to global", () => {
|
|
83
|
+
test("shows 'No differences' when project config matches global", async () => {
|
|
84
|
+
// Create empty project config (should merge to same as global)
|
|
85
|
+
const naxDir = join(tempDir, "nax");
|
|
86
|
+
mkdirSync(naxDir, { recursive: true });
|
|
87
|
+
writeFileSync(join(naxDir, "config.json"), JSON.stringify({}));
|
|
88
|
+
|
|
89
|
+
process.chdir(tempDir);
|
|
90
|
+
const config = await loadConfig(tempDir);
|
|
91
|
+
|
|
92
|
+
await configCommand(config, { diff: true });
|
|
93
|
+
|
|
94
|
+
const output = consoleOutput.join("\n");
|
|
95
|
+
expect(output).toContain("No differences between project and global config");
|
|
96
|
+
});
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
describe("Project config overrides global", () => {
|
|
100
|
+
test("shows table with field path, project value, and global value", async () => {
|
|
101
|
+
// Create project config with a simple override
|
|
102
|
+
const naxDir = join(tempDir, "nax");
|
|
103
|
+
mkdirSync(naxDir, { recursive: true });
|
|
104
|
+
const projectConfig = {
|
|
105
|
+
execution: {
|
|
106
|
+
maxIterations: 25, // Different from any global value
|
|
107
|
+
},
|
|
108
|
+
};
|
|
109
|
+
writeFileSync(join(naxDir, "config.json"), JSON.stringify(projectConfig, null, 2));
|
|
110
|
+
|
|
111
|
+
process.chdir(tempDir);
|
|
112
|
+
const config = await loadConfig(tempDir);
|
|
113
|
+
|
|
114
|
+
await configCommand(config, { diff: true });
|
|
115
|
+
|
|
116
|
+
const output = consoleOutput.join("\n");
|
|
117
|
+
|
|
118
|
+
// Should show header
|
|
119
|
+
expect(output).toContain("# Config Differences (Project overrides Global)");
|
|
120
|
+
|
|
121
|
+
// Should show table separator
|
|
122
|
+
expect(output).toContain("─");
|
|
123
|
+
|
|
124
|
+
// Should show column headers
|
|
125
|
+
expect(output).toContain("Field");
|
|
126
|
+
expect(output).toContain("Project Value");
|
|
127
|
+
expect(output).toContain("Global Value");
|
|
128
|
+
|
|
129
|
+
// Should show the specific field
|
|
130
|
+
expect(output).toContain("execution.maxIterations");
|
|
131
|
+
|
|
132
|
+
// Should show project value (25)
|
|
133
|
+
expect(output).toContain("25");
|
|
134
|
+
|
|
135
|
+
// Global value will vary based on ~/.nax/config.json, just verify it's present
|
|
136
|
+
// The key assertion is that we show both values in a diff
|
|
137
|
+
const lines = output.split("\n");
|
|
138
|
+
const maxIterLine = lines.find((line) => line.includes("execution.maxIterations"));
|
|
139
|
+
expect(maxIterLine).toBeDefined();
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
test("shows multiple differences when multiple fields override", async () => {
|
|
143
|
+
const naxDir = join(tempDir, "nax");
|
|
144
|
+
mkdirSync(naxDir, { recursive: true });
|
|
145
|
+
const projectConfig = {
|
|
146
|
+
execution: {
|
|
147
|
+
maxIterations: 25,
|
|
148
|
+
costLimit: 10.0,
|
|
149
|
+
},
|
|
150
|
+
tdd: {
|
|
151
|
+
maxRetries: 5,
|
|
152
|
+
},
|
|
153
|
+
};
|
|
154
|
+
writeFileSync(join(naxDir, "config.json"), JSON.stringify(projectConfig, null, 2));
|
|
155
|
+
|
|
156
|
+
process.chdir(tempDir);
|
|
157
|
+
const config = await loadConfig(tempDir);
|
|
158
|
+
|
|
159
|
+
await configCommand(config, { diff: true });
|
|
160
|
+
|
|
161
|
+
const output = consoleOutput.join("\n");
|
|
162
|
+
|
|
163
|
+
// Should show all three differences
|
|
164
|
+
expect(output).toContain("execution.maxIterations");
|
|
165
|
+
expect(output).toContain("execution.costLimit");
|
|
166
|
+
expect(output).toContain("tdd.maxRetries");
|
|
167
|
+
|
|
168
|
+
// Check project values are present
|
|
169
|
+
expect(output).toContain("25"); // maxIterations project
|
|
170
|
+
expect(output).toContain("10"); // costLimit project
|
|
171
|
+
expect(output).toContain("5"); // tdd.maxRetries project
|
|
172
|
+
|
|
173
|
+
// Verify table structure exists
|
|
174
|
+
const lines = output.split("\n");
|
|
175
|
+
expect(lines.some((line) => line.includes("execution.maxIterations"))).toBe(true);
|
|
176
|
+
expect(lines.some((line) => line.includes("execution.costLimit"))).toBe(true);
|
|
177
|
+
expect(lines.some((line) => line.includes("tdd.maxRetries"))).toBe(true);
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
test("shows nested field paths correctly", async () => {
|
|
181
|
+
const naxDir = join(tempDir, "nax");
|
|
182
|
+
mkdirSync(naxDir, { recursive: true });
|
|
183
|
+
const projectConfig = {
|
|
184
|
+
routing: {
|
|
185
|
+
llm: {
|
|
186
|
+
timeoutMs: 30000, // Different from default (15000)
|
|
187
|
+
},
|
|
188
|
+
},
|
|
189
|
+
};
|
|
190
|
+
writeFileSync(join(naxDir, "config.json"), JSON.stringify(projectConfig, null, 2));
|
|
191
|
+
|
|
192
|
+
process.chdir(tempDir);
|
|
193
|
+
const config = await loadConfig(tempDir);
|
|
194
|
+
|
|
195
|
+
await configCommand(config, { diff: true });
|
|
196
|
+
|
|
197
|
+
const output = consoleOutput.join("\n");
|
|
198
|
+
|
|
199
|
+
// Should show nested path
|
|
200
|
+
expect(output).toContain("routing.llm.timeoutMs");
|
|
201
|
+
expect(output).toContain("30000");
|
|
202
|
+
expect(output).toContain("15000");
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
test("shows field descriptions when available", async () => {
|
|
206
|
+
const naxDir = join(tempDir, "nax");
|
|
207
|
+
mkdirSync(naxDir, { recursive: true });
|
|
208
|
+
const projectConfig = {
|
|
209
|
+
execution: {
|
|
210
|
+
maxIterations: 25,
|
|
211
|
+
},
|
|
212
|
+
};
|
|
213
|
+
writeFileSync(join(naxDir, "config.json"), JSON.stringify(projectConfig, null, 2));
|
|
214
|
+
|
|
215
|
+
process.chdir(tempDir);
|
|
216
|
+
const config = await loadConfig(tempDir);
|
|
217
|
+
|
|
218
|
+
await configCommand(config, { diff: true });
|
|
219
|
+
|
|
220
|
+
const output = consoleOutput.join("\n");
|
|
221
|
+
|
|
222
|
+
// Should show description for execution.maxIterations
|
|
223
|
+
// Description: "Max iterations per feature run (auto-calculated if not set)"
|
|
224
|
+
expect(output).toContain("Max iterations per feature run");
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
test("handles array differences correctly", async () => {
|
|
228
|
+
const naxDir = join(tempDir, "nax");
|
|
229
|
+
mkdirSync(naxDir, { recursive: true });
|
|
230
|
+
const projectConfig = {
|
|
231
|
+
quality: {
|
|
232
|
+
stripEnvVars: ["CUSTOM_VAR", "ANOTHER_VAR"], // Different from default
|
|
233
|
+
},
|
|
234
|
+
};
|
|
235
|
+
writeFileSync(join(naxDir, "config.json"), JSON.stringify(projectConfig, null, 2));
|
|
236
|
+
|
|
237
|
+
process.chdir(tempDir);
|
|
238
|
+
const config = await loadConfig(tempDir);
|
|
239
|
+
|
|
240
|
+
await configCommand(config, { diff: true });
|
|
241
|
+
|
|
242
|
+
const output = consoleOutput.join("\n");
|
|
243
|
+
|
|
244
|
+
// Should show the field
|
|
245
|
+
expect(output).toContain("quality.stripEnvVars");
|
|
246
|
+
|
|
247
|
+
// Arrays should be formatted compactly for table
|
|
248
|
+
expect(output).toContain("[..."); // Compact array notation
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
test("handles boolean differences correctly", async () => {
|
|
252
|
+
const naxDir = join(tempDir, "nax");
|
|
253
|
+
mkdirSync(naxDir, { recursive: true });
|
|
254
|
+
const projectConfig = {
|
|
255
|
+
quality: {
|
|
256
|
+
requireTests: false, // Different from default/global (true)
|
|
257
|
+
},
|
|
258
|
+
};
|
|
259
|
+
writeFileSync(join(naxDir, "config.json"), JSON.stringify(projectConfig, null, 2));
|
|
260
|
+
|
|
261
|
+
process.chdir(tempDir);
|
|
262
|
+
const config = await loadConfig(tempDir);
|
|
263
|
+
|
|
264
|
+
await configCommand(config, { diff: true });
|
|
265
|
+
|
|
266
|
+
const output = consoleOutput.join("\n");
|
|
267
|
+
|
|
268
|
+
expect(output).toContain("quality.requireTests");
|
|
269
|
+
expect(output).toContain("false");
|
|
270
|
+
expect(output).toContain("true");
|
|
271
|
+
});
|
|
272
|
+
|
|
273
|
+
test("handles string value differences correctly", async () => {
|
|
274
|
+
const naxDir = join(tempDir, "nax");
|
|
275
|
+
mkdirSync(naxDir, { recursive: true });
|
|
276
|
+
const projectConfig = {
|
|
277
|
+
routing: {
|
|
278
|
+
strategy: "manual", // Use "manual" to ensure it's different from any global
|
|
279
|
+
},
|
|
280
|
+
};
|
|
281
|
+
writeFileSync(join(naxDir, "config.json"), JSON.stringify(projectConfig, null, 2));
|
|
282
|
+
|
|
283
|
+
process.chdir(tempDir);
|
|
284
|
+
const config = await loadConfig(tempDir);
|
|
285
|
+
|
|
286
|
+
await configCommand(config, { diff: true });
|
|
287
|
+
|
|
288
|
+
const output = consoleOutput.join("\n");
|
|
289
|
+
|
|
290
|
+
expect(output).toContain("routing.strategy");
|
|
291
|
+
expect(output).toContain('"manual"'); // Project value
|
|
292
|
+
// Global value will vary, just verify the field is shown
|
|
293
|
+
});
|
|
294
|
+
|
|
295
|
+
test("truncates long string values in table", async () => {
|
|
296
|
+
const naxDir = join(tempDir, "nax");
|
|
297
|
+
mkdirSync(naxDir, { recursive: true });
|
|
298
|
+
const projectConfig = {
|
|
299
|
+
constitution: {
|
|
300
|
+
path: "very-long-constitution-filename-that-should-be-truncated.md",
|
|
301
|
+
},
|
|
302
|
+
};
|
|
303
|
+
writeFileSync(join(naxDir, "config.json"), JSON.stringify(projectConfig, null, 2));
|
|
304
|
+
|
|
305
|
+
process.chdir(tempDir);
|
|
306
|
+
const config = await loadConfig(tempDir);
|
|
307
|
+
|
|
308
|
+
await configCommand(config, { diff: true });
|
|
309
|
+
|
|
310
|
+
const output = consoleOutput.join("\n");
|
|
311
|
+
|
|
312
|
+
expect(output).toContain("constitution.path");
|
|
313
|
+
// Long strings should be truncated with "..."
|
|
314
|
+
expect(output).toMatch(/\.\.\./);
|
|
315
|
+
});
|
|
316
|
+
|
|
317
|
+
test("handles object differences correctly", async () => {
|
|
318
|
+
const naxDir = join(tempDir, "nax");
|
|
319
|
+
mkdirSync(naxDir, { recursive: true });
|
|
320
|
+
const projectConfig = {
|
|
321
|
+
models: {
|
|
322
|
+
fast: {
|
|
323
|
+
provider: "openai",
|
|
324
|
+
model: "gpt-4o-mini",
|
|
325
|
+
},
|
|
326
|
+
},
|
|
327
|
+
};
|
|
328
|
+
writeFileSync(join(naxDir, "config.json"), JSON.stringify(projectConfig, null, 2));
|
|
329
|
+
|
|
330
|
+
process.chdir(tempDir);
|
|
331
|
+
const config = await loadConfig(tempDir);
|
|
332
|
+
|
|
333
|
+
await configCommand(config, { diff: true });
|
|
334
|
+
|
|
335
|
+
const output = consoleOutput.join("\n");
|
|
336
|
+
|
|
337
|
+
// Should show nested differences (provider and model fields)
|
|
338
|
+
expect(output).toContain("models.fast.provider");
|
|
339
|
+
expect(output).toContain("models.fast.model");
|
|
340
|
+
});
|
|
341
|
+
});
|
|
342
|
+
|
|
343
|
+
describe("Mutual exclusivity with --explain", () => {
|
|
344
|
+
test("rejects when both --diff and --explain are provided", async () => {
|
|
345
|
+
process.chdir(tempDir);
|
|
346
|
+
const config = await loadConfig(tempDir);
|
|
347
|
+
|
|
348
|
+
// Attempt to use both flags
|
|
349
|
+
let didThrow = false;
|
|
350
|
+
try {
|
|
351
|
+
await configCommand(config, { diff: true, explain: true });
|
|
352
|
+
} catch (err) {
|
|
353
|
+
didThrow = true;
|
|
354
|
+
expect(String(err)).toContain("process.exit(1)");
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
expect(didThrow).toBe(true);
|
|
358
|
+
expect(exitCode).toBe(1);
|
|
359
|
+
});
|
|
360
|
+
|
|
361
|
+
test("shows error message when both flags provided", async () => {
|
|
362
|
+
// Capture console.error as well
|
|
363
|
+
const errorOutput: string[] = [];
|
|
364
|
+
const originalConsoleError = console.error;
|
|
365
|
+
console.error = (...args: unknown[]) => {
|
|
366
|
+
errorOutput.push(args.map((a) => String(a)).join(" "));
|
|
367
|
+
};
|
|
368
|
+
|
|
369
|
+
process.chdir(tempDir);
|
|
370
|
+
const config = await loadConfig(tempDir);
|
|
371
|
+
|
|
372
|
+
try {
|
|
373
|
+
await configCommand(config, { diff: true, explain: true });
|
|
374
|
+
} catch {
|
|
375
|
+
// Expected to throw
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
console.error = originalConsoleError;
|
|
379
|
+
|
|
380
|
+
const errorMsg = errorOutput.join("\n");
|
|
381
|
+
expect(errorMsg).toContain("--explain and --diff are mutually exclusive");
|
|
382
|
+
});
|
|
383
|
+
});
|
|
384
|
+
|
|
385
|
+
describe("Edge cases", () => {
|
|
386
|
+
test("handles null values in diff correctly", async () => {
|
|
387
|
+
const naxDir = join(tempDir, "nax");
|
|
388
|
+
mkdirSync(naxDir, { recursive: true });
|
|
389
|
+
const projectConfig = {
|
|
390
|
+
execution: {
|
|
391
|
+
lintCommand: null, // Explicitly disable
|
|
392
|
+
},
|
|
393
|
+
};
|
|
394
|
+
writeFileSync(join(naxDir, "config.json"), JSON.stringify(projectConfig, null, 2));
|
|
395
|
+
|
|
396
|
+
process.chdir(tempDir);
|
|
397
|
+
const config = await loadConfig(tempDir);
|
|
398
|
+
|
|
399
|
+
await configCommand(config, { diff: true });
|
|
400
|
+
|
|
401
|
+
const output = consoleOutput.join("\n");
|
|
402
|
+
|
|
403
|
+
// Should show the difference if global has undefined and project has null
|
|
404
|
+
// (depends on how deepDiffConfigs handles null vs undefined)
|
|
405
|
+
// This test verifies the function doesn't crash
|
|
406
|
+
expect(output).toBeDefined();
|
|
407
|
+
});
|
|
408
|
+
|
|
409
|
+
test("skips fields that are only in global (not overridden)", async () => {
|
|
410
|
+
const naxDir = join(tempDir, "nax");
|
|
411
|
+
mkdirSync(naxDir, { recursive: true });
|
|
412
|
+
const projectConfig = {
|
|
413
|
+
execution: {
|
|
414
|
+
maxIterations: 25, // Only override this one field
|
|
415
|
+
},
|
|
416
|
+
};
|
|
417
|
+
writeFileSync(join(naxDir, "config.json"), JSON.stringify(projectConfig, null, 2));
|
|
418
|
+
|
|
419
|
+
process.chdir(tempDir);
|
|
420
|
+
const config = await loadConfig(tempDir);
|
|
421
|
+
|
|
422
|
+
await configCommand(config, { diff: true });
|
|
423
|
+
|
|
424
|
+
const output = consoleOutput.join("\n");
|
|
425
|
+
|
|
426
|
+
// Should only show execution.maxIterations
|
|
427
|
+
expect(output).toContain("execution.maxIterations");
|
|
428
|
+
|
|
429
|
+
// Should NOT show other execution fields that weren't overridden
|
|
430
|
+
expect(output).not.toContain("execution.costLimit");
|
|
431
|
+
expect(output).not.toContain("execution.sessionTimeoutSeconds");
|
|
432
|
+
});
|
|
433
|
+
|
|
434
|
+
test("handles deeply nested config overrides", async () => {
|
|
435
|
+
const naxDir = join(tempDir, "nax");
|
|
436
|
+
mkdirSync(naxDir, { recursive: true });
|
|
437
|
+
const projectConfig = {
|
|
438
|
+
autoMode: {
|
|
439
|
+
escalation: {
|
|
440
|
+
tierOrder: [
|
|
441
|
+
{ tier: "fast", attempts: 3 },
|
|
442
|
+
{ tier: "balanced", attempts: 2 },
|
|
443
|
+
],
|
|
444
|
+
},
|
|
445
|
+
},
|
|
446
|
+
};
|
|
447
|
+
writeFileSync(join(naxDir, "config.json"), JSON.stringify(projectConfig, null, 2));
|
|
448
|
+
|
|
449
|
+
process.chdir(tempDir);
|
|
450
|
+
const config = await loadConfig(tempDir);
|
|
451
|
+
|
|
452
|
+
await configCommand(config, { diff: true });
|
|
453
|
+
|
|
454
|
+
const output = consoleOutput.join("\n");
|
|
455
|
+
|
|
456
|
+
// Should show the nested field
|
|
457
|
+
expect(output).toContain("autoMode.escalation.tierOrder");
|
|
458
|
+
});
|
|
459
|
+
});
|
|
460
|
+
});
|