@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,160 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Default Configuration
|
|
3
|
+
*
|
|
4
|
+
* The default NaxConfig used as a base for all projects.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { NaxConfig } from "./types";
|
|
8
|
+
|
|
9
|
+
/** Default configuration */
|
|
10
|
+
export const DEFAULT_CONFIG: NaxConfig = {
|
|
11
|
+
version: 1,
|
|
12
|
+
models: {
|
|
13
|
+
fast: { provider: "anthropic", model: "haiku" },
|
|
14
|
+
balanced: { provider: "anthropic", model: "sonnet" },
|
|
15
|
+
powerful: { provider: "anthropic", model: "opus" },
|
|
16
|
+
},
|
|
17
|
+
autoMode: {
|
|
18
|
+
enabled: true,
|
|
19
|
+
defaultAgent: "claude",
|
|
20
|
+
fallbackOrder: ["claude", "codex", "opencode", "gemini"],
|
|
21
|
+
complexityRouting: {
|
|
22
|
+
simple: "fast",
|
|
23
|
+
medium: "balanced",
|
|
24
|
+
complex: "powerful",
|
|
25
|
+
expert: "powerful",
|
|
26
|
+
},
|
|
27
|
+
escalation: {
|
|
28
|
+
enabled: true,
|
|
29
|
+
tierOrder: [
|
|
30
|
+
{ tier: "fast", attempts: 5 },
|
|
31
|
+
{ tier: "balanced", attempts: 3 },
|
|
32
|
+
{ tier: "powerful", attempts: 2 },
|
|
33
|
+
],
|
|
34
|
+
escalateEntireBatch: true,
|
|
35
|
+
},
|
|
36
|
+
},
|
|
37
|
+
routing: {
|
|
38
|
+
strategy: "keyword",
|
|
39
|
+
adaptive: {
|
|
40
|
+
minSamples: 10,
|
|
41
|
+
costThreshold: 0.8,
|
|
42
|
+
fallbackStrategy: "llm",
|
|
43
|
+
},
|
|
44
|
+
llm: {
|
|
45
|
+
model: "fast",
|
|
46
|
+
fallbackToKeywords: true,
|
|
47
|
+
cacheDecisions: true,
|
|
48
|
+
mode: "hybrid",
|
|
49
|
+
timeoutMs: 15000,
|
|
50
|
+
},
|
|
51
|
+
},
|
|
52
|
+
execution: {
|
|
53
|
+
maxIterations: 10, // auto-calculated: sum of tier attempts (5+3+2=10)
|
|
54
|
+
iterationDelayMs: 2000,
|
|
55
|
+
costLimit: 5.0,
|
|
56
|
+
sessionTimeoutSeconds: 600, // 10 minutes
|
|
57
|
+
verificationTimeoutSeconds: 300, // 5 minutes
|
|
58
|
+
maxStoriesPerFeature: 500,
|
|
59
|
+
rectification: {
|
|
60
|
+
enabled: true,
|
|
61
|
+
maxRetries: 2,
|
|
62
|
+
fullSuiteTimeoutSeconds: 120,
|
|
63
|
+
maxFailureSummaryChars: 2000,
|
|
64
|
+
abortOnIncreasingFailures: true,
|
|
65
|
+
},
|
|
66
|
+
regressionGate: {
|
|
67
|
+
enabled: true,
|
|
68
|
+
timeoutSeconds: 120,
|
|
69
|
+
},
|
|
70
|
+
contextProviderTokenBudget: 2000,
|
|
71
|
+
},
|
|
72
|
+
quality: {
|
|
73
|
+
requireTypecheck: true,
|
|
74
|
+
requireLint: true,
|
|
75
|
+
requireTests: true,
|
|
76
|
+
commands: {},
|
|
77
|
+
forceExit: false,
|
|
78
|
+
detectOpenHandles: true,
|
|
79
|
+
detectOpenHandlesRetries: 1,
|
|
80
|
+
gracePeriodMs: 5000,
|
|
81
|
+
drainTimeoutMs: 2000,
|
|
82
|
+
shell: "/bin/sh",
|
|
83
|
+
stripEnvVars: ["CLAUDECODE", "REPL_ID", "AGENT"],
|
|
84
|
+
environmentalEscalationDivisor: 2,
|
|
85
|
+
},
|
|
86
|
+
tdd: {
|
|
87
|
+
maxRetries: 2,
|
|
88
|
+
autoVerifyIsolation: true,
|
|
89
|
+
autoApproveVerifier: true,
|
|
90
|
+
strategy: "auto",
|
|
91
|
+
sessionTiers: {
|
|
92
|
+
testWriter: "balanced",
|
|
93
|
+
// implementer: undefined = uses story's routed tier
|
|
94
|
+
verifier: "fast",
|
|
95
|
+
},
|
|
96
|
+
testWriterAllowedPaths: ["src/index.ts", "src/**/index.ts"],
|
|
97
|
+
rollbackOnFailure: true,
|
|
98
|
+
greenfieldDetection: true,
|
|
99
|
+
},
|
|
100
|
+
constitution: {
|
|
101
|
+
enabled: true,
|
|
102
|
+
path: "constitution.md",
|
|
103
|
+
maxTokens: 2000,
|
|
104
|
+
},
|
|
105
|
+
analyze: {
|
|
106
|
+
llmEnhanced: true,
|
|
107
|
+
model: "balanced",
|
|
108
|
+
fallbackToKeywords: true,
|
|
109
|
+
maxCodebaseSummaryTokens: 5000,
|
|
110
|
+
},
|
|
111
|
+
review: {
|
|
112
|
+
enabled: true,
|
|
113
|
+
checks: ["typecheck", "lint", "test"],
|
|
114
|
+
commands: {},
|
|
115
|
+
},
|
|
116
|
+
plan: {
|
|
117
|
+
model: "balanced",
|
|
118
|
+
outputPath: "spec.md",
|
|
119
|
+
},
|
|
120
|
+
acceptance: {
|
|
121
|
+
enabled: true,
|
|
122
|
+
maxRetries: 2,
|
|
123
|
+
generateTests: true,
|
|
124
|
+
testPath: "acceptance.test.ts",
|
|
125
|
+
},
|
|
126
|
+
context: {
|
|
127
|
+
testCoverage: {
|
|
128
|
+
enabled: true,
|
|
129
|
+
detail: "names-and-counts",
|
|
130
|
+
maxTokens: 500,
|
|
131
|
+
testPattern: "**/*.test.{ts,js,tsx,jsx}",
|
|
132
|
+
scopeToStory: true,
|
|
133
|
+
},
|
|
134
|
+
autoDetect: {
|
|
135
|
+
enabled: true,
|
|
136
|
+
maxFiles: 5,
|
|
137
|
+
traceImports: false,
|
|
138
|
+
},
|
|
139
|
+
},
|
|
140
|
+
interaction: {
|
|
141
|
+
plugin: "cli",
|
|
142
|
+
config: {},
|
|
143
|
+
defaults: {
|
|
144
|
+
timeout: 600000, // 10 minutes
|
|
145
|
+
fallback: "escalate",
|
|
146
|
+
},
|
|
147
|
+
triggers: {
|
|
148
|
+
"security-review": true,
|
|
149
|
+
"cost-warning": true,
|
|
150
|
+
},
|
|
151
|
+
},
|
|
152
|
+
precheck: {
|
|
153
|
+
storySizeGate: {
|
|
154
|
+
enabled: true,
|
|
155
|
+
maxAcCount: 6,
|
|
156
|
+
maxDescriptionLength: 2000,
|
|
157
|
+
maxBulletPoints: 8,
|
|
158
|
+
},
|
|
159
|
+
},
|
|
160
|
+
};
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
export type {
|
|
2
|
+
NaxConfig,
|
|
3
|
+
Complexity,
|
|
4
|
+
TestStrategy,
|
|
5
|
+
TddStrategy,
|
|
6
|
+
ModelTier,
|
|
7
|
+
ModelDef,
|
|
8
|
+
ModelEntry,
|
|
9
|
+
ModelMap,
|
|
10
|
+
AutoModeConfig,
|
|
11
|
+
ExecutionConfig,
|
|
12
|
+
QualityConfig,
|
|
13
|
+
TddConfig,
|
|
14
|
+
TierConfig,
|
|
15
|
+
RectificationConfig,
|
|
16
|
+
} from "./schema";
|
|
17
|
+
export { DEFAULT_CONFIG, resolveModel, NaxConfigSchema } from "./schema";
|
|
18
|
+
export { loadConfig, findProjectDir, globalConfigPath } from "./loader";
|
|
19
|
+
export { validateConfig, type ValidationResult } from "./validate"; // @deprecated: Use NaxConfigSchema.safeParse() instead
|
|
20
|
+
export { validateDirectory, validateFilePath, isWithinDirectory, MAX_DIRECTORY_DEPTH } from "./path-security";
|
|
21
|
+
export { globalConfigDir, projectConfigDir } from "./paths";
|
|
22
|
+
export { deepMergeConfig } from "./merger";
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Configuration Loader
|
|
3
|
+
*
|
|
4
|
+
* Merges global + project config with defaults.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { existsSync } from "node:fs";
|
|
8
|
+
import { join, resolve } from "node:path";
|
|
9
|
+
import { getLogger } from "../logger";
|
|
10
|
+
import { deepMergeConfig } from "./merger";
|
|
11
|
+
import { MAX_DIRECTORY_DEPTH } from "./path-security";
|
|
12
|
+
import { globalConfigDir, projectConfigDir } from "./paths";
|
|
13
|
+
import { DEFAULT_CONFIG, type NaxConfig, NaxConfigSchema } from "./schema";
|
|
14
|
+
|
|
15
|
+
/** Global config path */
|
|
16
|
+
export function globalConfigPath(): string {
|
|
17
|
+
return join(globalConfigDir(), "config.json");
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/** Find project nax directory (walks up from cwd) */
|
|
21
|
+
export function findProjectDir(startDir: string = process.cwd()): string | null {
|
|
22
|
+
let dir = resolve(startDir);
|
|
23
|
+
let depth = 0;
|
|
24
|
+
|
|
25
|
+
while (depth < MAX_DIRECTORY_DEPTH) {
|
|
26
|
+
const candidate = join(dir, "nax");
|
|
27
|
+
if (existsSync(join(candidate, "config.json"))) {
|
|
28
|
+
return candidate;
|
|
29
|
+
}
|
|
30
|
+
const parent = join(dir, "..");
|
|
31
|
+
if (parent === dir) break; // Root reached
|
|
32
|
+
dir = parent;
|
|
33
|
+
depth++;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
return null;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/** Load and parse a JSON config file */
|
|
40
|
+
async function loadJsonFile<T>(path: string): Promise<T | null> {
|
|
41
|
+
if (!existsSync(path)) return null;
|
|
42
|
+
try {
|
|
43
|
+
return await Bun.file(path).json();
|
|
44
|
+
} catch (err) {
|
|
45
|
+
const logger = getLogger();
|
|
46
|
+
logger.warn("config", "Failed to parse config file", { path, error: String(err) });
|
|
47
|
+
return null;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/** @internal Backward compat: map deprecated routing.llm.batchMode to routing.llm.mode.
|
|
52
|
+
* Returns a new object (immutable -- does not mutate the input). */
|
|
53
|
+
function applyBatchModeCompat(conf: Record<string, unknown>): Record<string, unknown> {
|
|
54
|
+
const routing = conf.routing as Record<string, unknown> | undefined;
|
|
55
|
+
const llm = routing?.llm as Record<string, unknown> | undefined;
|
|
56
|
+
if (llm && "batchMode" in llm && !("mode" in llm)) {
|
|
57
|
+
const batchMode = llm.batchMode;
|
|
58
|
+
if (typeof batchMode === "boolean") {
|
|
59
|
+
const mappedMode = batchMode ? "one-shot" : "per-story";
|
|
60
|
+
try {
|
|
61
|
+
getLogger().warn(
|
|
62
|
+
"config",
|
|
63
|
+
`routing.llm.batchMode is deprecated and will be removed in v1.0. Mapped to mode="${mappedMode}". Update your config to use routing.llm.mode instead.`,
|
|
64
|
+
);
|
|
65
|
+
} catch {
|
|
66
|
+
/* logger may not be init yet */
|
|
67
|
+
}
|
|
68
|
+
return {
|
|
69
|
+
...conf,
|
|
70
|
+
routing: {
|
|
71
|
+
...routing,
|
|
72
|
+
llm: { ...llm, mode: mappedMode },
|
|
73
|
+
},
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
return conf;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/** Load merged configuration (defaults < global < project < CLI overrides) */
|
|
81
|
+
export async function loadConfig(projectDir?: string, cliOverrides?: Record<string, unknown>): Promise<NaxConfig> {
|
|
82
|
+
// Start with defaults as a plain object
|
|
83
|
+
let rawConfig: Record<string, unknown> = structuredClone(DEFAULT_CONFIG as unknown as Record<string, unknown>);
|
|
84
|
+
|
|
85
|
+
// Layer 1: Global config (~/.nax/config.json)
|
|
86
|
+
const globalConfRaw = await loadJsonFile<Record<string, unknown>>(globalConfigPath());
|
|
87
|
+
if (globalConfRaw) {
|
|
88
|
+
// Backward compatibility: apply batchMode->mode shim before merge so defaults don't shadow it
|
|
89
|
+
const globalConf = applyBatchModeCompat(globalConfRaw);
|
|
90
|
+
rawConfig = deepMergeConfig(rawConfig, globalConf);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// Layer 2: Project config (nax/config.json)
|
|
94
|
+
const projDir = projectDir ?? findProjectDir();
|
|
95
|
+
if (projDir) {
|
|
96
|
+
const projConf = await loadJsonFile<Record<string, unknown>>(join(projDir, "config.json"));
|
|
97
|
+
if (projConf) {
|
|
98
|
+
// Backward compatibility: map deprecated batchMode -> mode on raw user config
|
|
99
|
+
// MUST run before deepMergeConfig so defaults don't shadow the check.
|
|
100
|
+
const resolvedProjConf = applyBatchModeCompat(projConf);
|
|
101
|
+
rawConfig = deepMergeConfig(rawConfig, resolvedProjConf);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// Layer 3: CLI overrides (highest priority)
|
|
106
|
+
if (cliOverrides) {
|
|
107
|
+
rawConfig = deepMergeConfig(rawConfig, cliOverrides);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// Parse and validate with Zod
|
|
111
|
+
const result = NaxConfigSchema.safeParse(rawConfig);
|
|
112
|
+
if (!result.success) {
|
|
113
|
+
const errors = result.error.issues.map((err) => {
|
|
114
|
+
const path = String(err.path.join("."));
|
|
115
|
+
return path ? `${path}: ${err.message}` : err.message;
|
|
116
|
+
});
|
|
117
|
+
throw new Error(`Invalid configuration:\n${errors.join("\n")}`);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
return result.data as NaxConfig;
|
|
121
|
+
}
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Configuration Merger Utility
|
|
3
|
+
*
|
|
4
|
+
* Deep merge utility for NaxConfig with special handling:
|
|
5
|
+
* - Arrays: replace (not merge)
|
|
6
|
+
* - Null values: remove keys
|
|
7
|
+
* - Hooks: concatenate from both configs
|
|
8
|
+
* - Constitution content: concatenate with newline separator
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import type { NaxConfig } from "./schema";
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Deep merge two configuration objects.
|
|
15
|
+
*
|
|
16
|
+
* Rules:
|
|
17
|
+
* - Objects are merged recursively
|
|
18
|
+
* - Arrays replace (override completely replaces base)
|
|
19
|
+
* - Null values in override remove the key from result
|
|
20
|
+
* - Undefined values in override are skipped
|
|
21
|
+
* - Hooks are concatenated (both base and override hooks preserved)
|
|
22
|
+
* - Constitution content is concatenated with newline separator
|
|
23
|
+
*
|
|
24
|
+
* @param base - Base configuration object
|
|
25
|
+
* @param override - Override configuration object
|
|
26
|
+
* @returns New merged configuration (immutable - does not mutate inputs)
|
|
27
|
+
*/
|
|
28
|
+
export function deepMergeConfig<T = NaxConfig>(base: Record<string, unknown>, override: Record<string, unknown>): T {
|
|
29
|
+
// Start with a clone of base to ensure immutability
|
|
30
|
+
const result: Record<string, unknown> = { ...base };
|
|
31
|
+
|
|
32
|
+
for (const key of Object.keys(override)) {
|
|
33
|
+
const overrideValue = override[key];
|
|
34
|
+
|
|
35
|
+
// Skip undefined values
|
|
36
|
+
if (overrideValue === undefined) {
|
|
37
|
+
continue;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Handle null values - remove key from result
|
|
41
|
+
if (overrideValue === null) {
|
|
42
|
+
delete result[key];
|
|
43
|
+
continue;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const baseValue = result[key];
|
|
47
|
+
|
|
48
|
+
// Special case: hooks concatenation
|
|
49
|
+
if (key === "hooks" && isPlainObject(baseValue) && isPlainObject(overrideValue)) {
|
|
50
|
+
const baseHooks = baseValue as Record<string, unknown>;
|
|
51
|
+
const overrideHooks = overrideValue as Record<string, unknown>;
|
|
52
|
+
const merged: Record<string, unknown> = { ...baseHooks };
|
|
53
|
+
|
|
54
|
+
// Merge the nested hooks object
|
|
55
|
+
if (isPlainObject(baseHooks.hooks) && isPlainObject(overrideHooks.hooks)) {
|
|
56
|
+
const baseHookDefs = baseHooks.hooks as Record<string, unknown>;
|
|
57
|
+
const overrideHookDefs = overrideHooks.hooks as Record<string, unknown>;
|
|
58
|
+
const mergedHookDefs: Record<string, unknown> = {};
|
|
59
|
+
|
|
60
|
+
// Collect all hook event names
|
|
61
|
+
const allHookNames = new Set([...Object.keys(baseHookDefs), ...Object.keys(overrideHookDefs)]);
|
|
62
|
+
|
|
63
|
+
// For each hook event, concatenate hooks into an array
|
|
64
|
+
for (const hookName of allHookNames) {
|
|
65
|
+
const baseHook = baseHookDefs[hookName];
|
|
66
|
+
const overrideHook = overrideHookDefs[hookName];
|
|
67
|
+
|
|
68
|
+
if (baseHook && overrideHook) {
|
|
69
|
+
// Both exist - create array with both
|
|
70
|
+
mergedHookDefs[hookName] = [baseHook, overrideHook];
|
|
71
|
+
} else if (overrideHook) {
|
|
72
|
+
// Only override exists
|
|
73
|
+
mergedHookDefs[hookName] = overrideHook;
|
|
74
|
+
} else {
|
|
75
|
+
// Only base exists
|
|
76
|
+
mergedHookDefs[hookName] = baseHook;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
merged.hooks = mergedHookDefs;
|
|
81
|
+
} else if (overrideHooks.hooks) {
|
|
82
|
+
merged.hooks = overrideHooks.hooks;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// Handle other hook config fields (e.g., skipGlobal)
|
|
86
|
+
for (const hookKey of Object.keys(overrideHooks)) {
|
|
87
|
+
if (hookKey !== "hooks") {
|
|
88
|
+
merged[hookKey] = overrideHooks[hookKey];
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
result[key] = merged;
|
|
93
|
+
continue;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// Special case: constitution content concatenation
|
|
97
|
+
if (key === "constitution" && isPlainObject(baseValue) && isPlainObject(overrideValue)) {
|
|
98
|
+
const baseConst = baseValue as Record<string, unknown>;
|
|
99
|
+
const overrideConst = overrideValue as Record<string, unknown>;
|
|
100
|
+
|
|
101
|
+
const baseContent = typeof baseConst.content === "string" ? baseConst.content : "";
|
|
102
|
+
const overrideContent = typeof overrideConst.content === "string" ? overrideConst.content : "";
|
|
103
|
+
|
|
104
|
+
// Merge constitution object, but concatenate content field
|
|
105
|
+
const mergedConstitution = deepMergeConfig(baseConst, overrideConst);
|
|
106
|
+
|
|
107
|
+
// Concatenate content if both exist
|
|
108
|
+
if (baseContent && overrideContent) {
|
|
109
|
+
(mergedConstitution as unknown as Record<string, unknown>).content = `${baseContent}\n\n${overrideContent}`;
|
|
110
|
+
} else if (overrideContent) {
|
|
111
|
+
(mergedConstitution as unknown as Record<string, unknown>).content = overrideContent;
|
|
112
|
+
} else if (baseContent) {
|
|
113
|
+
(mergedConstitution as unknown as Record<string, unknown>).content = baseContent;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
result[key] = mergedConstitution;
|
|
117
|
+
continue;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// Arrays replace completely (no merging)
|
|
121
|
+
if (Array.isArray(overrideValue)) {
|
|
122
|
+
result[key] = [...overrideValue];
|
|
123
|
+
continue;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// Recursive merge for plain objects
|
|
127
|
+
if (isPlainObject(overrideValue) && isPlainObject(baseValue)) {
|
|
128
|
+
result[key] = deepMergeConfig(baseValue as Record<string, unknown>, overrideValue as Record<string, unknown>);
|
|
129
|
+
continue;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// Default: override replaces base
|
|
133
|
+
result[key] = overrideValue;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
return result as T;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Check if value is a plain object (not null, not array, not class instance).
|
|
141
|
+
*
|
|
142
|
+
* @param value - Value to check
|
|
143
|
+
* @returns True if value is a plain object
|
|
144
|
+
*/
|
|
145
|
+
function isPlainObject(value: unknown): value is Record<string, unknown> {
|
|
146
|
+
return typeof value === "object" && value !== null && !Array.isArray(value) && value.constructor === Object;
|
|
147
|
+
}
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Path Security Utilities
|
|
3
|
+
*
|
|
4
|
+
* Prevents path traversal attacks by validating and resolving paths.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { existsSync, lstatSync, realpathSync } from "node:fs";
|
|
8
|
+
import { isAbsolute, normalize, resolve } from "node:path";
|
|
9
|
+
|
|
10
|
+
/** Maximum directory depth to prevent infinite loops */
|
|
11
|
+
export const MAX_DIRECTORY_DEPTH = 10;
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Validate and resolve a directory path safely
|
|
15
|
+
* @param dirPath - The directory path to validate
|
|
16
|
+
* @param baseDir - Optional base directory to check bounds (if provided, dirPath must be within baseDir)
|
|
17
|
+
* @returns Resolved absolute path
|
|
18
|
+
* @throws Error if path is invalid, not a directory, or outside bounds
|
|
19
|
+
*/
|
|
20
|
+
export function validateDirectory(dirPath: string, baseDir?: string): string {
|
|
21
|
+
// Resolve to absolute path
|
|
22
|
+
const resolved = resolve(dirPath);
|
|
23
|
+
|
|
24
|
+
// Check if path exists
|
|
25
|
+
if (!existsSync(resolved)) {
|
|
26
|
+
throw new Error(`Directory does not exist: ${dirPath}`);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// Get real path (resolves symlinks)
|
|
30
|
+
let realPath: string;
|
|
31
|
+
try {
|
|
32
|
+
realPath = realpathSync(resolved);
|
|
33
|
+
} catch (error) {
|
|
34
|
+
throw new Error(`Failed to resolve path: ${dirPath} (${(error as Error).message})`);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Check if it's a directory
|
|
38
|
+
try {
|
|
39
|
+
const stats = lstatSync(realPath);
|
|
40
|
+
if (!stats.isDirectory()) {
|
|
41
|
+
throw new Error(`Not a directory: ${dirPath}`);
|
|
42
|
+
}
|
|
43
|
+
} catch (error) {
|
|
44
|
+
throw new Error(`Failed to stat path: ${dirPath} (${(error as Error).message})`);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// If baseDir provided, ensure realPath is within baseDir
|
|
48
|
+
if (baseDir) {
|
|
49
|
+
const resolvedBase = resolve(baseDir);
|
|
50
|
+
const realBase = existsSync(resolvedBase) ? realpathSync(resolvedBase) : resolvedBase;
|
|
51
|
+
|
|
52
|
+
if (!isWithinDirectory(realPath, realBase)) {
|
|
53
|
+
throw new Error(`Path is outside allowed directory: ${dirPath} (resolved to ${realPath}, base: ${realBase})`);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
return realPath;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Check if a path is within a base directory (prevents path traversal)
|
|
62
|
+
* @param targetPath - The path to check (must be absolute)
|
|
63
|
+
* @param basePath - The base directory (must be absolute)
|
|
64
|
+
* @returns true if targetPath is within basePath
|
|
65
|
+
*/
|
|
66
|
+
export function isWithinDirectory(targetPath: string, basePath: string): boolean {
|
|
67
|
+
const normalizedTarget = normalize(targetPath);
|
|
68
|
+
const normalizedBase = normalize(basePath);
|
|
69
|
+
|
|
70
|
+
// Ensure both are absolute
|
|
71
|
+
if (!isAbsolute(normalizedTarget) || !isAbsolute(normalizedBase)) {
|
|
72
|
+
return false;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// Add trailing slash to base to prevent partial matches
|
|
76
|
+
const baseWithSlash = normalizedBase.endsWith("/") ? normalizedBase : `${normalizedBase}/`;
|
|
77
|
+
const targetWithSlash = normalizedTarget.endsWith("/") ? normalizedTarget : `${normalizedTarget}/`;
|
|
78
|
+
|
|
79
|
+
// Check if target starts with base
|
|
80
|
+
return targetWithSlash.startsWith(baseWithSlash) || normalizedTarget === normalizedBase;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Validate file path and ensure it's within a base directory
|
|
85
|
+
* @param filePath - The file path to validate
|
|
86
|
+
* @param baseDir - Base directory (filePath must be within this directory)
|
|
87
|
+
* @returns Resolved absolute path
|
|
88
|
+
* @throws Error if path is invalid or outside bounds
|
|
89
|
+
*/
|
|
90
|
+
export function validateFilePath(filePath: string, baseDir: string): string {
|
|
91
|
+
const resolved = resolve(filePath);
|
|
92
|
+
|
|
93
|
+
// Get real path (resolves symlinks)
|
|
94
|
+
let realPath: string;
|
|
95
|
+
try {
|
|
96
|
+
// For non-existent files, use parent directory's real path
|
|
97
|
+
if (!existsSync(resolved)) {
|
|
98
|
+
const parent = resolve(resolved, "..");
|
|
99
|
+
if (existsSync(parent)) {
|
|
100
|
+
const realParent = realpathSync(parent);
|
|
101
|
+
realPath = resolve(realParent, filePath.split("/").pop() || "");
|
|
102
|
+
} else {
|
|
103
|
+
realPath = resolved;
|
|
104
|
+
}
|
|
105
|
+
} else {
|
|
106
|
+
realPath = realpathSync(resolved);
|
|
107
|
+
}
|
|
108
|
+
} catch (error) {
|
|
109
|
+
throw new Error(`Failed to resolve path: ${filePath} (${(error as Error).message})`);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// Ensure realPath is within baseDir
|
|
113
|
+
const resolvedBase = resolve(baseDir);
|
|
114
|
+
const realBase = existsSync(resolvedBase) ? realpathSync(resolvedBase) : resolvedBase;
|
|
115
|
+
|
|
116
|
+
if (!isWithinDirectory(realPath, realBase)) {
|
|
117
|
+
throw new Error(`Path is outside allowed directory: ${filePath} (resolved to ${realPath}, base: ${realBase})`);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
return realPath;
|
|
121
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Configuration Path Utilities
|
|
3
|
+
*
|
|
4
|
+
* Provides path resolution for global and project-level config directories.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { homedir } from "node:os";
|
|
8
|
+
import { join, resolve } from "node:path";
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Returns the global config directory path (~/.nax).
|
|
12
|
+
*
|
|
13
|
+
* @returns Absolute path to global config directory
|
|
14
|
+
*/
|
|
15
|
+
export function globalConfigDir(): string {
|
|
16
|
+
return join(homedir(), ".nax");
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Returns the project config directory path (projectRoot/nax).
|
|
21
|
+
*
|
|
22
|
+
* @param projectRoot - Absolute or relative path to project root
|
|
23
|
+
* @returns Absolute path to project config directory
|
|
24
|
+
*/
|
|
25
|
+
export function projectConfigDir(projectRoot: string): string {
|
|
26
|
+
return join(resolve(projectRoot), "nax");
|
|
27
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Configuration Schema — Re-export Barrel
|
|
3
|
+
*
|
|
4
|
+
* Backward-compatible re-exports from split modules:
|
|
5
|
+
* - types.ts: All TypeScript interfaces, type aliases, resolveModel
|
|
6
|
+
* - schemas.ts: Zod validation schemas
|
|
7
|
+
* - defaults.ts: DEFAULT_CONFIG constant
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
// Types and resolveModel
|
|
11
|
+
export type {
|
|
12
|
+
Complexity,
|
|
13
|
+
TestStrategy,
|
|
14
|
+
TddStrategy,
|
|
15
|
+
EscalationEntry,
|
|
16
|
+
ModelTier,
|
|
17
|
+
TokenPricing,
|
|
18
|
+
ModelDef,
|
|
19
|
+
ModelEntry,
|
|
20
|
+
ModelMap,
|
|
21
|
+
TierConfig,
|
|
22
|
+
AutoModeConfig,
|
|
23
|
+
RectificationConfig,
|
|
24
|
+
RegressionGateConfig,
|
|
25
|
+
ExecutionConfig,
|
|
26
|
+
QualityConfig,
|
|
27
|
+
TddConfig,
|
|
28
|
+
ConstitutionConfig,
|
|
29
|
+
AnalyzeConfig,
|
|
30
|
+
ReviewConfig,
|
|
31
|
+
PlanConfig,
|
|
32
|
+
AcceptanceConfig,
|
|
33
|
+
OptimizerConfig,
|
|
34
|
+
PluginConfigEntry,
|
|
35
|
+
HooksConfig,
|
|
36
|
+
InteractionConfig,
|
|
37
|
+
TestCoverageConfig,
|
|
38
|
+
ContextAutoDetectConfig,
|
|
39
|
+
ContextConfig,
|
|
40
|
+
RoutingStrategyName,
|
|
41
|
+
AdaptiveRoutingConfig,
|
|
42
|
+
LlmRoutingMode,
|
|
43
|
+
LlmRoutingConfig,
|
|
44
|
+
RoutingConfig,
|
|
45
|
+
StorySizeGateConfig,
|
|
46
|
+
PrecheckConfig,
|
|
47
|
+
NaxConfig,
|
|
48
|
+
} from "./types";
|
|
49
|
+
|
|
50
|
+
export { resolveModel } from "./types";
|
|
51
|
+
|
|
52
|
+
// Zod schemas
|
|
53
|
+
export { NaxConfigSchema } from "./schemas";
|
|
54
|
+
|
|
55
|
+
// Default config
|
|
56
|
+
export { DEFAULT_CONFIG } from "./defaults";
|