@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,561 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Config Command
|
|
3
|
+
*
|
|
4
|
+
* Displays effective merged configuration with inline explanations.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { existsSync } from "node:fs";
|
|
8
|
+
import { join } from "node:path";
|
|
9
|
+
import { DEFAULT_CONFIG } from "../config/defaults";
|
|
10
|
+
import { findProjectDir, globalConfigPath } from "../config/loader";
|
|
11
|
+
import { deepMergeConfig } from "../config/merger";
|
|
12
|
+
import type { NaxConfig } from "../config/schema";
|
|
13
|
+
|
|
14
|
+
/** Field descriptions for human-readable output */
|
|
15
|
+
const FIELD_DESCRIPTIONS: Record<string, string> = {
|
|
16
|
+
// Top-level
|
|
17
|
+
version: "Configuration schema version",
|
|
18
|
+
|
|
19
|
+
// Models
|
|
20
|
+
models: "Model tier definitions (fast/balanced/powerful)",
|
|
21
|
+
"models.fast": "Fast model for lightweight tasks (e.g., haiku)",
|
|
22
|
+
"models.balanced": "Balanced model for general coding (e.g., sonnet)",
|
|
23
|
+
"models.powerful": "Powerful model for complex tasks (e.g., opus)",
|
|
24
|
+
|
|
25
|
+
// Auto mode
|
|
26
|
+
autoMode: "Auto mode configuration for agent orchestration",
|
|
27
|
+
"autoMode.enabled": "Enable automatic agent selection and escalation",
|
|
28
|
+
"autoMode.defaultAgent": "Default agent to use (e.g., claude, codex)",
|
|
29
|
+
"autoMode.fallbackOrder": "Fallback order when agent is rate-limited",
|
|
30
|
+
"autoMode.complexityRouting": "Model tier per complexity level",
|
|
31
|
+
"autoMode.complexityRouting.simple": "Model tier for simple tasks",
|
|
32
|
+
"autoMode.complexityRouting.medium": "Model tier for medium tasks",
|
|
33
|
+
"autoMode.complexityRouting.complex": "Model tier for complex tasks",
|
|
34
|
+
"autoMode.complexityRouting.expert": "Model tier for expert tasks",
|
|
35
|
+
"autoMode.escalation": "Escalation settings for failed stories",
|
|
36
|
+
"autoMode.escalation.enabled": "Enable tier escalation on failure",
|
|
37
|
+
"autoMode.escalation.tierOrder": "Ordered tier escalation with per-tier attempt budgets",
|
|
38
|
+
"autoMode.escalation.escalateEntireBatch": "Escalate all stories in batch when one fails",
|
|
39
|
+
|
|
40
|
+
// Routing
|
|
41
|
+
routing: "Model routing strategy configuration",
|
|
42
|
+
"routing.strategy": "Routing strategy: keyword | llm | manual | adaptive | custom",
|
|
43
|
+
"routing.customStrategyPath": "Path to custom routing strategy (if strategy=custom)",
|
|
44
|
+
"routing.adaptive": "Adaptive routing settings",
|
|
45
|
+
"routing.adaptive.minSamples": "Minimum samples before adaptive routing activates",
|
|
46
|
+
"routing.adaptive.costThreshold": "Cost threshold for strategy switching (0-1)",
|
|
47
|
+
"routing.adaptive.fallbackStrategy": "Fallback strategy if adaptive fails",
|
|
48
|
+
"routing.llm": "LLM-based routing settings",
|
|
49
|
+
"routing.llm.model": "Model tier for routing decisions",
|
|
50
|
+
"routing.llm.fallbackToKeywords": "Fall back to keyword routing on LLM failure",
|
|
51
|
+
"routing.llm.cacheDecisions": "Cache routing decisions per story ID",
|
|
52
|
+
"routing.llm.mode": "Routing mode: one-shot | per-story | hybrid",
|
|
53
|
+
"routing.llm.timeoutMs": "Timeout for LLM routing call in milliseconds",
|
|
54
|
+
|
|
55
|
+
// Execution
|
|
56
|
+
execution: "Execution limits and timeouts",
|
|
57
|
+
"execution.maxIterations": "Max iterations per feature run (auto-calculated if not set)",
|
|
58
|
+
"execution.iterationDelayMs": "Delay between iterations in milliseconds",
|
|
59
|
+
"execution.costLimit": "Max cost in USD before pausing execution",
|
|
60
|
+
"execution.sessionTimeoutSeconds": "Timeout per agent coding session in seconds",
|
|
61
|
+
"execution.verificationTimeoutSeconds": "Verification subprocess timeout in seconds",
|
|
62
|
+
"execution.maxStoriesPerFeature": "Max stories per feature (prevents memory exhaustion)",
|
|
63
|
+
"execution.contextProviderTokenBudget": "Token budget for plugin context providers",
|
|
64
|
+
"execution.lintCommand": "Lint command override (null=disabled, undefined=auto-detect)",
|
|
65
|
+
"execution.typecheckCommand": "Typecheck command override (null=disabled, undefined=auto-detect)",
|
|
66
|
+
"execution.dangerouslySkipPermissions": "Skip permissions for agent (use with caution)",
|
|
67
|
+
"execution.rectification": "Rectification loop settings (retry failed tests)",
|
|
68
|
+
"execution.rectification.enabled": "Enable rectification loop",
|
|
69
|
+
"execution.rectification.maxRetries": "Max retry attempts per story",
|
|
70
|
+
"execution.rectification.fullSuiteTimeoutSeconds": "Timeout for full test suite run in seconds",
|
|
71
|
+
"execution.rectification.maxFailureSummaryChars": "Max characters in failure summary",
|
|
72
|
+
"execution.rectification.abortOnIncreasingFailures": "Abort if failure count increases",
|
|
73
|
+
"execution.regressionGate": "Regression gate settings (full suite after scoped tests)",
|
|
74
|
+
"execution.regressionGate.enabled": "Enable full-suite regression gate",
|
|
75
|
+
"execution.regressionGate.timeoutSeconds": "Timeout for regression run in seconds",
|
|
76
|
+
|
|
77
|
+
// Quality
|
|
78
|
+
quality: "Quality gate configuration",
|
|
79
|
+
"quality.requireTypecheck": "Require typecheck to pass",
|
|
80
|
+
"quality.requireLint": "Require lint to pass",
|
|
81
|
+
"quality.requireTests": "Require tests to pass",
|
|
82
|
+
"quality.commands": "Custom quality commands",
|
|
83
|
+
"quality.commands.typecheck": "Custom typecheck command",
|
|
84
|
+
"quality.commands.lint": "Custom lint command",
|
|
85
|
+
"quality.commands.test": "Custom test command",
|
|
86
|
+
"quality.forceExit": "Append --forceExit to test command (prevents hangs)",
|
|
87
|
+
"quality.detectOpenHandles": "Append --detectOpenHandles on timeout",
|
|
88
|
+
"quality.detectOpenHandlesRetries": "Max retries with --detectOpenHandles",
|
|
89
|
+
"quality.gracePeriodMs": "Grace period in ms after SIGTERM before SIGKILL",
|
|
90
|
+
"quality.drainTimeoutMs": "Deadline in ms to drain stdout/stderr after kill",
|
|
91
|
+
"quality.shell": "Shell to use for verification commands",
|
|
92
|
+
"quality.stripEnvVars": "Environment variables to strip during verification",
|
|
93
|
+
"quality.environmentalEscalationDivisor": "Divisor for environmental failure early escalation",
|
|
94
|
+
|
|
95
|
+
// TDD
|
|
96
|
+
tdd: "Test-driven development configuration",
|
|
97
|
+
"tdd.maxRetries": "Max retries per TDD session before escalating",
|
|
98
|
+
"tdd.autoVerifyIsolation": "Auto-verify test isolation between sessions",
|
|
99
|
+
"tdd.strategy": "TDD strategy: auto | strict | lite | off",
|
|
100
|
+
"tdd.autoApproveVerifier": "Auto-approve legitimate fixes in verifier session",
|
|
101
|
+
"tdd.sessionTiers": "Per-session model tier overrides",
|
|
102
|
+
"tdd.sessionTiers.testWriter": "Model tier for test-writer session",
|
|
103
|
+
"tdd.sessionTiers.implementer": "Model tier for implementer session",
|
|
104
|
+
"tdd.sessionTiers.verifier": "Model tier for verifier session",
|
|
105
|
+
"tdd.testWriterAllowedPaths": "Glob patterns for files test-writer can modify",
|
|
106
|
+
"tdd.rollbackOnFailure": "Rollback git changes when TDD fails",
|
|
107
|
+
"tdd.greenfieldDetection": "Force test-after on projects with no test files",
|
|
108
|
+
|
|
109
|
+
// Constitution
|
|
110
|
+
constitution: "Constitution settings (core rules and constraints)",
|
|
111
|
+
"constitution.enabled": "Enable constitution loading and injection",
|
|
112
|
+
"constitution.path": "Path to constitution file (relative to nax/ directory)",
|
|
113
|
+
"constitution.maxTokens": "Maximum tokens allowed for constitution content",
|
|
114
|
+
"constitution.skipGlobal": "Skip loading global constitution",
|
|
115
|
+
|
|
116
|
+
// Analyze
|
|
117
|
+
analyze: "Feature analysis settings",
|
|
118
|
+
"analyze.llmEnhanced": "Enable LLM-enhanced analysis",
|
|
119
|
+
"analyze.model": "Model tier for decompose and classify",
|
|
120
|
+
"analyze.fallbackToKeywords": "Fall back to keyword matching on LLM failure",
|
|
121
|
+
"analyze.maxCodebaseSummaryTokens": "Max tokens for codebase summary",
|
|
122
|
+
|
|
123
|
+
// Review
|
|
124
|
+
review: "Review phase configuration",
|
|
125
|
+
"review.enabled": "Enable review phase",
|
|
126
|
+
"review.checks": "List of checks to run (typecheck, lint, test)",
|
|
127
|
+
"review.commands": "Custom commands per check",
|
|
128
|
+
"review.commands.typecheck": "Custom typecheck command for review",
|
|
129
|
+
"review.commands.lint": "Custom lint command for review",
|
|
130
|
+
"review.commands.test": "Custom test command for review",
|
|
131
|
+
|
|
132
|
+
// Plan
|
|
133
|
+
plan: "Planning phase configuration",
|
|
134
|
+
"plan.model": "Model tier for planning",
|
|
135
|
+
"plan.outputPath": "Output path for generated spec (relative to nax/)",
|
|
136
|
+
|
|
137
|
+
// Acceptance
|
|
138
|
+
acceptance: "Acceptance test configuration",
|
|
139
|
+
"acceptance.enabled": "Enable acceptance test generation and validation",
|
|
140
|
+
"acceptance.maxRetries": "Max retry loops for fix stories",
|
|
141
|
+
"acceptance.generateTests": "Generate acceptance tests during analyze",
|
|
142
|
+
"acceptance.testPath": "Path to acceptance test file (relative to feature dir)",
|
|
143
|
+
|
|
144
|
+
// Context
|
|
145
|
+
context: "Context injection configuration",
|
|
146
|
+
"context.testCoverage": "Test coverage context settings",
|
|
147
|
+
"context.testCoverage.enabled": "Enable test coverage context injection",
|
|
148
|
+
"context.testCoverage.detail": "Detail level: names-only | names-and-counts | describe-blocks",
|
|
149
|
+
"context.testCoverage.maxTokens": "Max tokens for test summary",
|
|
150
|
+
"context.testCoverage.testDir": "Test directory relative to workdir",
|
|
151
|
+
"context.testCoverage.testPattern": "Glob pattern for test files",
|
|
152
|
+
"context.testCoverage.scopeToStory": "Scope test coverage to story-relevant files only",
|
|
153
|
+
"context.autoDetect": "Auto-detect relevant files settings",
|
|
154
|
+
"context.autoDetect.enabled": "Enable auto-detection of relevant files",
|
|
155
|
+
"context.autoDetect.maxFiles": "Max files to auto-detect",
|
|
156
|
+
"context.autoDetect.traceImports": "Trace imports to find related files",
|
|
157
|
+
|
|
158
|
+
// Optimizer
|
|
159
|
+
optimizer: "Prompt optimizer configuration",
|
|
160
|
+
"optimizer.enabled": "Enable prompt optimizer",
|
|
161
|
+
"optimizer.strategy": "Optimization strategy: rule-based | llm | noop",
|
|
162
|
+
|
|
163
|
+
// Plugins
|
|
164
|
+
plugins: "Plugin configurations",
|
|
165
|
+
|
|
166
|
+
// Hooks
|
|
167
|
+
hooks: "Hooks configuration",
|
|
168
|
+
"hooks.skipGlobal": "Skip loading global hooks",
|
|
169
|
+
|
|
170
|
+
// Interaction
|
|
171
|
+
interaction: "Interaction plugin configuration",
|
|
172
|
+
"interaction.plugin": "Plugin to use for interactions (default: cli)",
|
|
173
|
+
"interaction.config": "Plugin-specific configuration",
|
|
174
|
+
"interaction.defaults": "Default interaction settings",
|
|
175
|
+
"interaction.defaults.timeout": "Default timeout in milliseconds",
|
|
176
|
+
"interaction.defaults.fallback": "Default fallback behavior: continue | skip | escalate | abort",
|
|
177
|
+
"interaction.triggers": "Enable/disable built-in triggers",
|
|
178
|
+
|
|
179
|
+
// Precheck
|
|
180
|
+
precheck: "Precheck configuration (run before analysis)",
|
|
181
|
+
"precheck.storySizeGate": "Story size gate settings",
|
|
182
|
+
"precheck.storySizeGate.enabled": "Enable story size gate",
|
|
183
|
+
"precheck.storySizeGate.maxAcCount": "Max acceptance criteria count before flagging",
|
|
184
|
+
"precheck.storySizeGate.maxDescriptionLength": "Max description character length before flagging",
|
|
185
|
+
"precheck.storySizeGate.maxBulletPoints": "Max bullet point count before flagging",
|
|
186
|
+
};
|
|
187
|
+
|
|
188
|
+
/** Options for config command */
|
|
189
|
+
export interface ConfigCommandOptions {
|
|
190
|
+
/** Show field explanations */
|
|
191
|
+
explain?: boolean;
|
|
192
|
+
/** Show only fields where project overrides global */
|
|
193
|
+
diff?: boolean;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* Load and parse a JSON config file.
|
|
198
|
+
*
|
|
199
|
+
* @param path - Path to config file
|
|
200
|
+
* @returns Parsed config object or null if file doesn't exist
|
|
201
|
+
*/
|
|
202
|
+
async function loadConfigFile(path: string): Promise<Record<string, unknown> | null> {
|
|
203
|
+
if (!existsSync(path)) return null;
|
|
204
|
+
try {
|
|
205
|
+
return await Bun.file(path).json();
|
|
206
|
+
} catch {
|
|
207
|
+
return null;
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
/**
|
|
212
|
+
* Load global config merged with defaults.
|
|
213
|
+
*
|
|
214
|
+
* @returns Global config object (defaults + global overrides)
|
|
215
|
+
*/
|
|
216
|
+
async function loadGlobalConfig(): Promise<Record<string, unknown>> {
|
|
217
|
+
const globalPath = globalConfigPath();
|
|
218
|
+
const globalConf = await loadConfigFile(globalPath);
|
|
219
|
+
|
|
220
|
+
if (!globalConf) {
|
|
221
|
+
return structuredClone(DEFAULT_CONFIG as unknown as Record<string, unknown>);
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
return deepMergeConfig(structuredClone(DEFAULT_CONFIG as unknown as Record<string, unknown>), globalConf);
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
/**
|
|
228
|
+
* Load project config (raw, without defaults or global).
|
|
229
|
+
*
|
|
230
|
+
* @returns Project config object or null if not found
|
|
231
|
+
*/
|
|
232
|
+
async function loadProjectConfig(): Promise<Record<string, unknown> | null> {
|
|
233
|
+
const projectDir = findProjectDir();
|
|
234
|
+
if (!projectDir) return null;
|
|
235
|
+
|
|
236
|
+
const projectPath = join(projectDir, "config.json");
|
|
237
|
+
return await loadConfigFile(projectPath);
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
/**
|
|
241
|
+
* Represents a single config field difference.
|
|
242
|
+
*/
|
|
243
|
+
interface ConfigDiff {
|
|
244
|
+
/** Dot-separated field path (e.g., "execution.maxIterations") */
|
|
245
|
+
path: string;
|
|
246
|
+
/** Value from global config */
|
|
247
|
+
globalValue: unknown;
|
|
248
|
+
/** Value from project config */
|
|
249
|
+
projectValue: unknown;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
/**
|
|
253
|
+
* Deep diff two config objects, returning only fields that differ.
|
|
254
|
+
*
|
|
255
|
+
* @param global - Global config (defaults + global overrides)
|
|
256
|
+
* @param project - Project config (raw overrides only)
|
|
257
|
+
* @param currentPath - Current path in object tree (for recursion)
|
|
258
|
+
* @returns Array of differences
|
|
259
|
+
*/
|
|
260
|
+
function deepDiffConfigs(
|
|
261
|
+
global: Record<string, unknown>,
|
|
262
|
+
project: Record<string, unknown>,
|
|
263
|
+
currentPath: string[] = [],
|
|
264
|
+
): ConfigDiff[] {
|
|
265
|
+
const diffs: ConfigDiff[] = [];
|
|
266
|
+
|
|
267
|
+
// Iterate over project config keys (we only care about what project overrides)
|
|
268
|
+
for (const key of Object.keys(project)) {
|
|
269
|
+
const projectValue = project[key];
|
|
270
|
+
const globalValue = global[key];
|
|
271
|
+
const path = [...currentPath, key];
|
|
272
|
+
const pathStr = path.join(".");
|
|
273
|
+
|
|
274
|
+
// Handle nested objects
|
|
275
|
+
if (
|
|
276
|
+
projectValue !== null &&
|
|
277
|
+
typeof projectValue === "object" &&
|
|
278
|
+
!Array.isArray(projectValue) &&
|
|
279
|
+
globalValue !== null &&
|
|
280
|
+
typeof globalValue === "object" &&
|
|
281
|
+
!Array.isArray(globalValue)
|
|
282
|
+
) {
|
|
283
|
+
// Recurse into nested object
|
|
284
|
+
const nestedDiffs = deepDiffConfigs(
|
|
285
|
+
globalValue as Record<string, unknown>,
|
|
286
|
+
projectValue as Record<string, unknown>,
|
|
287
|
+
path,
|
|
288
|
+
);
|
|
289
|
+
diffs.push(...nestedDiffs);
|
|
290
|
+
} else {
|
|
291
|
+
// Compare primitive values or arrays
|
|
292
|
+
if (!deepEqual(projectValue, globalValue)) {
|
|
293
|
+
diffs.push({
|
|
294
|
+
path: pathStr,
|
|
295
|
+
globalValue,
|
|
296
|
+
projectValue,
|
|
297
|
+
});
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
return diffs;
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
/**
|
|
306
|
+
* Deep equality check for two values.
|
|
307
|
+
*
|
|
308
|
+
* @param a - First value
|
|
309
|
+
* @param b - Second value
|
|
310
|
+
* @returns True if values are deeply equal
|
|
311
|
+
*/
|
|
312
|
+
function deepEqual(a: unknown, b: unknown): boolean {
|
|
313
|
+
if (a === b) return true;
|
|
314
|
+
if (a === null || b === null) return false;
|
|
315
|
+
if (a === undefined || b === undefined) return false;
|
|
316
|
+
|
|
317
|
+
// Handle arrays
|
|
318
|
+
if (Array.isArray(a) && Array.isArray(b)) {
|
|
319
|
+
if (a.length !== b.length) return false;
|
|
320
|
+
return a.every((val, idx) => deepEqual(val, b[idx]));
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
// Handle objects
|
|
324
|
+
if (typeof a === "object" && typeof b === "object") {
|
|
325
|
+
const aObj = a as Record<string, unknown>;
|
|
326
|
+
const bObj = b as Record<string, unknown>;
|
|
327
|
+
const aKeys = Object.keys(aObj);
|
|
328
|
+
const bKeys = Object.keys(bObj);
|
|
329
|
+
|
|
330
|
+
if (aKeys.length !== bKeys.length) return false;
|
|
331
|
+
|
|
332
|
+
return aKeys.every((key) => deepEqual(aObj[key], bObj[key]));
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
return false;
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
/**
|
|
339
|
+
* Display effective configuration with optional explanations.
|
|
340
|
+
*
|
|
341
|
+
* @param config - Loaded configuration
|
|
342
|
+
* @param options - Command options
|
|
343
|
+
*/
|
|
344
|
+
export async function configCommand(config: NaxConfig, options: ConfigCommandOptions = {}): Promise<void> {
|
|
345
|
+
const { explain = false, diff = false } = options;
|
|
346
|
+
|
|
347
|
+
// Validate mutually exclusive flags
|
|
348
|
+
if (explain && diff) {
|
|
349
|
+
console.error("Error: --explain and --diff are mutually exclusive");
|
|
350
|
+
process.exit(1);
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
// Determine sources
|
|
354
|
+
const sources = determineConfigSources();
|
|
355
|
+
|
|
356
|
+
if (diff) {
|
|
357
|
+
// Diff mode: show only fields where project overrides global
|
|
358
|
+
const projectConf = await loadProjectConfig();
|
|
359
|
+
|
|
360
|
+
if (!projectConf) {
|
|
361
|
+
console.log("No project config found — using global defaults");
|
|
362
|
+
return;
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
const globalConf = await loadGlobalConfig();
|
|
366
|
+
const diffs = deepDiffConfigs(globalConf, projectConf);
|
|
367
|
+
|
|
368
|
+
if (diffs.length === 0) {
|
|
369
|
+
console.log("No differences between project and global config");
|
|
370
|
+
return;
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
console.log("# Config Differences (Project overrides Global)");
|
|
374
|
+
console.log();
|
|
375
|
+
console.log("─".repeat(80));
|
|
376
|
+
console.log(`${"Field".padEnd(40)}${"Project Value".padEnd(20)}Global Value`);
|
|
377
|
+
console.log("─".repeat(80));
|
|
378
|
+
|
|
379
|
+
for (const diff of diffs) {
|
|
380
|
+
const path = diff.path.padEnd(40);
|
|
381
|
+
const projectVal = formatValueForTable(diff.projectValue);
|
|
382
|
+
const globalVal = formatValueForTable(diff.globalValue);
|
|
383
|
+
|
|
384
|
+
console.log(`${path}${projectVal.padEnd(20)}${globalVal}`);
|
|
385
|
+
|
|
386
|
+
// Show description if available
|
|
387
|
+
const description = FIELD_DESCRIPTIONS[diff.path];
|
|
388
|
+
if (description) {
|
|
389
|
+
console.log(`${"".padEnd(40)}↳ ${description}`);
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
console.log("─".repeat(80));
|
|
394
|
+
} else if (explain) {
|
|
395
|
+
console.log("# nax Configuration");
|
|
396
|
+
console.log("#");
|
|
397
|
+
console.log("# Resolution order: defaults → global → project → CLI overrides");
|
|
398
|
+
console.log(`# Global config: ${sources.global ? sources.global : "(not found)"}`);
|
|
399
|
+
console.log(`# Project config: ${sources.project ? sources.project : "(not found)"}`);
|
|
400
|
+
console.log();
|
|
401
|
+
|
|
402
|
+
// Recursively display config with descriptions
|
|
403
|
+
displayConfigWithDescriptions(config, [], sources);
|
|
404
|
+
} else {
|
|
405
|
+
// Default view: JSON with header showing config sources
|
|
406
|
+
console.log("// nax Configuration");
|
|
407
|
+
console.log("// Resolution order: defaults → global → project → CLI overrides");
|
|
408
|
+
console.log(`// Global config: ${sources.global ? sources.global : "(not found)"}`);
|
|
409
|
+
console.log(`// Project config: ${sources.project ? sources.project : "(not found)"}`);
|
|
410
|
+
console.log();
|
|
411
|
+
console.log(JSON.stringify(config, null, 2));
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
/**
|
|
416
|
+
* Determine which config files are present.
|
|
417
|
+
*
|
|
418
|
+
* @returns Paths to global and project config files (null if not found)
|
|
419
|
+
*/
|
|
420
|
+
function determineConfigSources(): { global: string | null; project: string | null } {
|
|
421
|
+
const globalPath = globalConfigPath();
|
|
422
|
+
const projectDir = findProjectDir();
|
|
423
|
+
const projectPath = projectDir ? join(projectDir, "config.json") : null;
|
|
424
|
+
|
|
425
|
+
return {
|
|
426
|
+
global: fileExists(globalPath) ? globalPath : null,
|
|
427
|
+
project: projectPath && fileExists(projectPath) ? projectPath : null,
|
|
428
|
+
};
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
/**
|
|
432
|
+
* Check if a file exists.
|
|
433
|
+
*
|
|
434
|
+
* @param path - File path to check
|
|
435
|
+
* @returns True if file exists, false otherwise
|
|
436
|
+
*/
|
|
437
|
+
function fileExists(path: string): boolean {
|
|
438
|
+
return existsSync(path);
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
/**
|
|
442
|
+
* Display configuration with descriptions and source annotations.
|
|
443
|
+
*
|
|
444
|
+
* @param obj - Configuration object or value
|
|
445
|
+
* @param path - Current path in config tree
|
|
446
|
+
* @param sources - Config source paths
|
|
447
|
+
* @param indent - Current indentation level
|
|
448
|
+
*/
|
|
449
|
+
function displayConfigWithDescriptions(
|
|
450
|
+
obj: unknown,
|
|
451
|
+
path: string[],
|
|
452
|
+
sources: { global: string | null; project: string | null },
|
|
453
|
+
indent = 0,
|
|
454
|
+
): void {
|
|
455
|
+
const indentStr = " ".repeat(indent);
|
|
456
|
+
const pathStr = path.join(".");
|
|
457
|
+
|
|
458
|
+
// Handle primitives and arrays
|
|
459
|
+
if (obj === null || obj === undefined || typeof obj !== "object" || Array.isArray(obj)) {
|
|
460
|
+
const description = FIELD_DESCRIPTIONS[pathStr];
|
|
461
|
+
const value = formatValue(obj);
|
|
462
|
+
|
|
463
|
+
if (description) {
|
|
464
|
+
console.log(`${indentStr}# ${description}`);
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
const key = path[path.length - 1] || "";
|
|
468
|
+
console.log(`${indentStr}${key}: ${value}`);
|
|
469
|
+
console.log();
|
|
470
|
+
return;
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
// Handle objects
|
|
474
|
+
const entries = Object.entries(obj as Record<string, unknown>);
|
|
475
|
+
|
|
476
|
+
for (let i = 0; i < entries.length; i++) {
|
|
477
|
+
const [key, value] = entries[i];
|
|
478
|
+
const currentPath = [...path, key];
|
|
479
|
+
const currentPathStr = currentPath.join(".");
|
|
480
|
+
const description = FIELD_DESCRIPTIONS[currentPathStr];
|
|
481
|
+
|
|
482
|
+
// Display description comment if available
|
|
483
|
+
if (description) {
|
|
484
|
+
console.log(`${indentStr}# ${description}`);
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
// Handle nested objects
|
|
488
|
+
if (value !== null && typeof value === "object" && !Array.isArray(value)) {
|
|
489
|
+
console.log(`${indentStr}${key}:`);
|
|
490
|
+
displayConfigWithDescriptions(value, currentPath, sources, indent + 1);
|
|
491
|
+
} else {
|
|
492
|
+
// Display value
|
|
493
|
+
const formattedValue = formatValue(value);
|
|
494
|
+
console.log(`${indentStr}${key}: ${formattedValue}`);
|
|
495
|
+
|
|
496
|
+
// Add blank line after each top-level section
|
|
497
|
+
if (indent === 0 && i < entries.length - 1) {
|
|
498
|
+
console.log();
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
/**
|
|
505
|
+
* Format a config value for display.
|
|
506
|
+
*
|
|
507
|
+
* @param value - Value to format
|
|
508
|
+
* @returns Formatted string
|
|
509
|
+
*/
|
|
510
|
+
function formatValue(value: unknown): string {
|
|
511
|
+
if (value === null) return "null";
|
|
512
|
+
if (value === undefined) return "undefined";
|
|
513
|
+
if (typeof value === "string") return `"${value}"`;
|
|
514
|
+
if (typeof value === "boolean") return String(value);
|
|
515
|
+
if (typeof value === "number") return String(value);
|
|
516
|
+
if (Array.isArray(value)) {
|
|
517
|
+
if (value.length === 0) return "[]";
|
|
518
|
+
if (value.length <= 3) {
|
|
519
|
+
return `[${value.map((v) => formatValue(v)).join(", ")}]`;
|
|
520
|
+
}
|
|
521
|
+
return `[${value
|
|
522
|
+
.slice(0, 3)
|
|
523
|
+
.map((v) => formatValue(v))
|
|
524
|
+
.join(", ")}, ... (${value.length} items)]`;
|
|
525
|
+
}
|
|
526
|
+
if (typeof value === "object") {
|
|
527
|
+
return JSON.stringify(value);
|
|
528
|
+
}
|
|
529
|
+
return String(value);
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
/**
|
|
533
|
+
* Format a config value for table display (shorter format).
|
|
534
|
+
*
|
|
535
|
+
* @param value - Value to format
|
|
536
|
+
* @returns Formatted string (max ~18 chars)
|
|
537
|
+
*/
|
|
538
|
+
function formatValueForTable(value: unknown): string {
|
|
539
|
+
if (value === null) return "null";
|
|
540
|
+
if (value === undefined) return "undefined";
|
|
541
|
+
if (typeof value === "string") {
|
|
542
|
+
if (value.length > 15) {
|
|
543
|
+
return `"${value.slice(0, 12)}..."`;
|
|
544
|
+
}
|
|
545
|
+
return `"${value}"`;
|
|
546
|
+
}
|
|
547
|
+
if (typeof value === "boolean") return String(value);
|
|
548
|
+
if (typeof value === "number") return String(value);
|
|
549
|
+
if (Array.isArray(value)) {
|
|
550
|
+
if (value.length === 0) return "[]";
|
|
551
|
+
return `[...${value.length}]`;
|
|
552
|
+
}
|
|
553
|
+
if (typeof value === "object") {
|
|
554
|
+
const str = JSON.stringify(value);
|
|
555
|
+
if (str.length > 15) {
|
|
556
|
+
return "{...}";
|
|
557
|
+
}
|
|
558
|
+
return str;
|
|
559
|
+
}
|
|
560
|
+
return String(value);
|
|
561
|
+
}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Constitution CLI Command
|
|
3
|
+
*
|
|
4
|
+
* Generates agent-specific config files from nax/constitution.md.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { existsSync } from "node:fs";
|
|
8
|
+
import { join } from "node:path";
|
|
9
|
+
import chalk from "chalk";
|
|
10
|
+
import { generateAll, generateFor } from "../constitution/generator";
|
|
11
|
+
import type { AgentType } from "../constitution/generators/types";
|
|
12
|
+
|
|
13
|
+
/** Constitution generate options */
|
|
14
|
+
export interface ConstitutionGenerateOptions {
|
|
15
|
+
/** Path to constitution file (default: nax/constitution.md) */
|
|
16
|
+
constitution?: string;
|
|
17
|
+
/** Output directory (default: project root) */
|
|
18
|
+
output?: string;
|
|
19
|
+
/** Specific agent to generate for (default: all) */
|
|
20
|
+
agent?: string;
|
|
21
|
+
/** Dry run mode (don't write files) */
|
|
22
|
+
dryRun?: boolean;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Constitution generate command
|
|
27
|
+
*/
|
|
28
|
+
export async function constitutionGenerateCommand(options: ConstitutionGenerateOptions): Promise<void> {
|
|
29
|
+
const workdir = process.cwd();
|
|
30
|
+
const constitutionPath = options.constitution
|
|
31
|
+
? join(workdir, options.constitution)
|
|
32
|
+
: join(workdir, "nax/constitution.md");
|
|
33
|
+
const outputDir = options.output ? join(workdir, options.output) : workdir;
|
|
34
|
+
|
|
35
|
+
// Validate constitution file exists
|
|
36
|
+
if (!existsSync(constitutionPath)) {
|
|
37
|
+
console.error(chalk.red(`[FAIL] Constitution file not found: ${constitutionPath}`));
|
|
38
|
+
console.error(chalk.yellow(`Create ${constitutionPath} first or use --constitution to specify a different path.`));
|
|
39
|
+
process.exit(1);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
console.log(chalk.blue(`[OK] Loading constitution from ${constitutionPath}`));
|
|
43
|
+
|
|
44
|
+
// Validate agent type if specified
|
|
45
|
+
const validAgents: AgentType[] = ["claude", "opencode", "cursor", "windsurf", "aider"];
|
|
46
|
+
if (options.agent && !validAgents.includes(options.agent as AgentType)) {
|
|
47
|
+
console.error(chalk.red(`[FAIL] Unknown agent type: ${options.agent}`));
|
|
48
|
+
console.error(chalk.yellow(`Valid agents: ${validAgents.join(", ")}`));
|
|
49
|
+
process.exit(1);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const dryRun = options.dryRun ?? false;
|
|
53
|
+
|
|
54
|
+
if (dryRun) {
|
|
55
|
+
console.log(chalk.yellow("[DRY RUN] No files will be written"));
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
try {
|
|
59
|
+
// Generate for specific agent or all agents
|
|
60
|
+
if (options.agent) {
|
|
61
|
+
const agent = options.agent as AgentType;
|
|
62
|
+
console.log(chalk.blue(`-> Generating config for ${agent}...`));
|
|
63
|
+
|
|
64
|
+
const result = await generateFor(agent, constitutionPath, outputDir, dryRun);
|
|
65
|
+
|
|
66
|
+
if (result.error) {
|
|
67
|
+
console.error(chalk.red(`[FAIL] ${agent}: ${result.error}`));
|
|
68
|
+
process.exit(1);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
if (dryRun) {
|
|
72
|
+
console.log(chalk.green(`[OK] ${agent} -> ${result.outputFile} (${result.content.length} bytes, dry run)`));
|
|
73
|
+
} else {
|
|
74
|
+
console.log(chalk.green(`[OK] ${agent} -> ${result.outputFile} (${result.content.length} bytes)`));
|
|
75
|
+
}
|
|
76
|
+
} else {
|
|
77
|
+
console.log(chalk.blue("-> Generating configs for all agents..."));
|
|
78
|
+
|
|
79
|
+
const results = await generateAll(constitutionPath, outputDir, dryRun);
|
|
80
|
+
|
|
81
|
+
let errorCount = 0;
|
|
82
|
+
for (const result of results) {
|
|
83
|
+
if (result.error) {
|
|
84
|
+
console.error(chalk.red(`[FAIL] ${result.agent}: ${result.error}`));
|
|
85
|
+
errorCount++;
|
|
86
|
+
} else if (dryRun) {
|
|
87
|
+
console.log(
|
|
88
|
+
chalk.green(`[OK] ${result.agent} -> ${result.outputFile} (${result.content.length} bytes, dry run)`),
|
|
89
|
+
);
|
|
90
|
+
} else {
|
|
91
|
+
console.log(chalk.green(`[OK] ${result.agent} -> ${result.outputFile} (${result.content.length} bytes)`));
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
if (errorCount > 0) {
|
|
96
|
+
console.error(chalk.red(`[FAIL] ${errorCount} generation(s) failed`));
|
|
97
|
+
process.exit(1);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
if (!dryRun) {
|
|
102
|
+
console.log(chalk.green(`\n[OK] Constitution config(s) generated in ${outputDir}`));
|
|
103
|
+
}
|
|
104
|
+
} catch (err) {
|
|
105
|
+
const error = err instanceof Error ? err.message : String(err);
|
|
106
|
+
console.error(chalk.red(`[FAIL] Generation failed: ${error}`));
|
|
107
|
+
process.exit(1);
|
|
108
|
+
}
|
|
109
|
+
}
|