@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
package/bin/nax.ts
ADDED
|
@@ -0,0 +1,930 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
/**
|
|
3
|
+
* nax — AI Coding Agent Orchestrator
|
|
4
|
+
*
|
|
5
|
+
* CLI entry point for the nax orchestration system.
|
|
6
|
+
*
|
|
7
|
+
* Features:
|
|
8
|
+
* - `init`: Initialize nax in a project directory
|
|
9
|
+
* - `run`: Execute the orchestration loop for a feature
|
|
10
|
+
* - `features create/list`: Manage feature definitions
|
|
11
|
+
* - `analyze`: Parse spec.md + tasks.md into prd.json
|
|
12
|
+
* - `agents`: Check available coding agent installations
|
|
13
|
+
* - `status`: Show current feature progress
|
|
14
|
+
*
|
|
15
|
+
* Architecture:
|
|
16
|
+
* - Complexity-based routing to model tiers (fast/balanced/powerful)
|
|
17
|
+
* - Three-session TDD for security-critical and complex stories
|
|
18
|
+
* - Story batching for simple stories to reduce overhead
|
|
19
|
+
* - Lifecycle hooks for custom automation (on-start, on-complete, etc.)
|
|
20
|
+
*
|
|
21
|
+
* @example
|
|
22
|
+
* ```bash
|
|
23
|
+
* # Initialize in project
|
|
24
|
+
* nax init
|
|
25
|
+
*
|
|
26
|
+
* # Create feature
|
|
27
|
+
* nax features create auth-system
|
|
28
|
+
*
|
|
29
|
+
* # Analyze spec/tasks into PRD
|
|
30
|
+
* nax analyze --feature auth-system
|
|
31
|
+
*
|
|
32
|
+
* # Run orchestration
|
|
33
|
+
* nax run --feature auth-system
|
|
34
|
+
*
|
|
35
|
+
* # Check status
|
|
36
|
+
* nax status --feature auth-system
|
|
37
|
+
* ```
|
|
38
|
+
*/
|
|
39
|
+
|
|
40
|
+
import { existsSync, mkdirSync } from "node:fs";
|
|
41
|
+
import { homedir } from "node:os";
|
|
42
|
+
import { join, resolve } from "node:path";
|
|
43
|
+
import chalk from "chalk";
|
|
44
|
+
import { Command } from "commander";
|
|
45
|
+
|
|
46
|
+
import { checkAgentHealth, getAllAgentNames } from "../src/agents";
|
|
47
|
+
import {
|
|
48
|
+
acceptCommand,
|
|
49
|
+
analyzeFeature,
|
|
50
|
+
displayCostMetrics,
|
|
51
|
+
displayFeatureStatus,
|
|
52
|
+
displayLastRunMetrics,
|
|
53
|
+
displayModelEfficiency,
|
|
54
|
+
planCommand,
|
|
55
|
+
pluginsListCommand,
|
|
56
|
+
promptsCommand,
|
|
57
|
+
runsListCommand,
|
|
58
|
+
runsShowCommand,
|
|
59
|
+
} from "../src/cli";
|
|
60
|
+
import { configCommand } from "../src/cli/config";
|
|
61
|
+
import { generateCommand } from "../src/cli/generate";
|
|
62
|
+
import { diagnose } from "../src/commands/diagnose";
|
|
63
|
+
import { logsCommand } from "../src/commands/logs";
|
|
64
|
+
import { precheckCommand } from "../src/commands/precheck";
|
|
65
|
+
import { unlockCommand } from "../src/commands/unlock";
|
|
66
|
+
import { DEFAULT_CONFIG, findProjectDir, loadConfig, validateDirectory } from "../src/config";
|
|
67
|
+
import { run } from "../src/execution";
|
|
68
|
+
import { loadHooksConfig } from "../src/hooks";
|
|
69
|
+
import { type LogLevel, initLogger } from "../src/logger";
|
|
70
|
+
import { countStories, loadPRD } from "../src/prd";
|
|
71
|
+
import { PipelineEventEmitter, type StoryDisplayState, renderTui } from "../src/tui";
|
|
72
|
+
|
|
73
|
+
const pkg = await Bun.file(join(import.meta.dir, "..", "package.json")).json();
|
|
74
|
+
|
|
75
|
+
const program = new Command();
|
|
76
|
+
|
|
77
|
+
program.name("nax").description("AI Coding Agent Orchestrator — loops until done").version(pkg.version);
|
|
78
|
+
|
|
79
|
+
// ── init ─────────────────────────────────────────────
|
|
80
|
+
program
|
|
81
|
+
.command("init")
|
|
82
|
+
.description("Initialize nax in the current project")
|
|
83
|
+
.option("-d, --dir <path>", "Project directory", process.cwd())
|
|
84
|
+
.option("-f, --force", "Force overwrite existing files", false)
|
|
85
|
+
.action(async (options) => {
|
|
86
|
+
// Validate directory path
|
|
87
|
+
let workdir: string;
|
|
88
|
+
try {
|
|
89
|
+
workdir = validateDirectory(options.dir);
|
|
90
|
+
} catch (err) {
|
|
91
|
+
console.error(chalk.red(`Invalid directory: ${(err as Error).message}`));
|
|
92
|
+
process.exit(1);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const naxDir = join(workdir, "nax");
|
|
96
|
+
|
|
97
|
+
if (existsSync(naxDir) && !options.force) {
|
|
98
|
+
console.log(chalk.yellow("nax already initialized. Use --force to overwrite."));
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// Create directory structure
|
|
103
|
+
mkdirSync(join(naxDir, "features"), { recursive: true });
|
|
104
|
+
mkdirSync(join(naxDir, "hooks"), { recursive: true });
|
|
105
|
+
|
|
106
|
+
// Write default config
|
|
107
|
+
await Bun.write(join(naxDir, "config.json"), JSON.stringify(DEFAULT_CONFIG, null, 2));
|
|
108
|
+
|
|
109
|
+
// Write default hooks.json
|
|
110
|
+
await Bun.write(
|
|
111
|
+
join(naxDir, "hooks.json"),
|
|
112
|
+
JSON.stringify(
|
|
113
|
+
{
|
|
114
|
+
hooks: {
|
|
115
|
+
"on-start": { command: 'echo "nax started: $NAX_FEATURE"', enabled: false },
|
|
116
|
+
"on-complete": { command: 'echo "nax complete: $NAX_FEATURE"', enabled: false },
|
|
117
|
+
"on-pause": { command: 'echo "nax paused: $NAX_REASON"', enabled: false },
|
|
118
|
+
"on-error": { command: 'echo "nax error: $NAX_REASON"', enabled: false },
|
|
119
|
+
},
|
|
120
|
+
},
|
|
121
|
+
null,
|
|
122
|
+
2,
|
|
123
|
+
),
|
|
124
|
+
);
|
|
125
|
+
|
|
126
|
+
// Write .gitignore
|
|
127
|
+
await Bun.write(join(naxDir, ".gitignore"), "# nax temp files\n*.tmp\n.paused.json\n.nax-verifier-verdict.json\n");
|
|
128
|
+
|
|
129
|
+
// Write starter context.md
|
|
130
|
+
await Bun.write(
|
|
131
|
+
join(naxDir, "context.md"),
|
|
132
|
+
`# Project Context
|
|
133
|
+
|
|
134
|
+
This document defines coding standards, architectural decisions, and forbidden patterns for this project.
|
|
135
|
+
Run \`nax generate\` to regenerate agent config files (CLAUDE.md, AGENTS.md, .cursorrules, etc.) from this file.
|
|
136
|
+
|
|
137
|
+
> Project metadata (dependencies, commands) is auto-injected by \`nax generate\`.
|
|
138
|
+
|
|
139
|
+
## Coding Standards
|
|
140
|
+
|
|
141
|
+
- Follow the project's existing code style and conventions
|
|
142
|
+
- Write clear, self-documenting code with meaningful names
|
|
143
|
+
- Keep functions small and focused (single responsibility)
|
|
144
|
+
- Prefer immutability over mutation
|
|
145
|
+
- Use consistent formatting throughout the codebase
|
|
146
|
+
|
|
147
|
+
## Testing Requirements
|
|
148
|
+
|
|
149
|
+
- All new code must include tests
|
|
150
|
+
- Tests should cover happy paths, edge cases, and error conditions
|
|
151
|
+
- Aim for high test coverage (80%+ recommended)
|
|
152
|
+
- Tests must pass before marking a story as complete
|
|
153
|
+
- Before writing tests, read existing test files to understand what is already covered
|
|
154
|
+
- Do not duplicate test coverage that prior stories already wrote
|
|
155
|
+
- Focus on testing NEW behavior introduced by this story
|
|
156
|
+
|
|
157
|
+
## Architecture Rules
|
|
158
|
+
|
|
159
|
+
- Follow the project's existing architecture patterns
|
|
160
|
+
- Each module should have a clear, single purpose
|
|
161
|
+
- Avoid tight coupling between modules
|
|
162
|
+
- Use dependency injection where appropriate
|
|
163
|
+
- Document architectural decisions in comments or docs
|
|
164
|
+
|
|
165
|
+
## Forbidden Patterns
|
|
166
|
+
|
|
167
|
+
- No hardcoded secrets, API keys, or credentials
|
|
168
|
+
- No console.log in production code (use proper logging)
|
|
169
|
+
- No \`any\` types in TypeScript (use proper typing)
|
|
170
|
+
- No commented-out code (use version control instead)
|
|
171
|
+
- No large files (split into smaller, focused modules)
|
|
172
|
+
|
|
173
|
+
## Commit Standards
|
|
174
|
+
|
|
175
|
+
- Write clear, descriptive commit messages
|
|
176
|
+
- Follow conventional commits format (feat:, fix:, refactor:, etc.)
|
|
177
|
+
- Commit early and often with atomic changes
|
|
178
|
+
- Reference story IDs in commit messages
|
|
179
|
+
|
|
180
|
+
## Documentation
|
|
181
|
+
|
|
182
|
+
- Add JSDoc comments for public APIs
|
|
183
|
+
- Update README when adding new features
|
|
184
|
+
- Document complex algorithms or business logic
|
|
185
|
+
- Keep documentation up-to-date with code changes
|
|
186
|
+
|
|
187
|
+
---
|
|
188
|
+
|
|
189
|
+
**Note:** Customize this file to match your project's specific needs.
|
|
190
|
+
`,
|
|
191
|
+
);
|
|
192
|
+
|
|
193
|
+
console.log(chalk.green("✅ Initialized nax"));
|
|
194
|
+
console.log(chalk.dim(` ${naxDir}/`));
|
|
195
|
+
console.log(chalk.dim(" ├── config.json"));
|
|
196
|
+
console.log(chalk.dim(" ├── context.md"));
|
|
197
|
+
console.log(chalk.dim(" ├── hooks.json"));
|
|
198
|
+
console.log(chalk.dim(" ├── features/"));
|
|
199
|
+
console.log(chalk.dim(" └── hooks/"));
|
|
200
|
+
console.log(chalk.dim("\nNext: nax features create <name>"));
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
// ── run ──────────────────────────────────────────────
|
|
204
|
+
program
|
|
205
|
+
.command("run")
|
|
206
|
+
.description("Run the orchestration loop for a feature")
|
|
207
|
+
.requiredOption("-f, --feature <name>", "Feature name")
|
|
208
|
+
.option("-a, --agent <name>", "Force a specific agent")
|
|
209
|
+
.option("-m, --max-iterations <n>", "Max iterations", "20")
|
|
210
|
+
.option("--dry-run", "Show plan without executing", false)
|
|
211
|
+
.option("--no-context", "Disable context builder (skip file context in prompts)")
|
|
212
|
+
.option("--no-batch", "Disable story batching (execute all stories individually)")
|
|
213
|
+
.option("--parallel <n>", "Max parallel sessions (0=auto, omit=sequential)")
|
|
214
|
+
.option("--headless", "Force headless mode (disable TUI, use pipe mode)", false)
|
|
215
|
+
.option("--verbose", "Enable verbose logging (debug level)", false)
|
|
216
|
+
.option("--quiet", "Quiet mode (warnings and errors only)", false)
|
|
217
|
+
.option("--silent", "Silent mode (errors only)", false)
|
|
218
|
+
.option("--json", "JSON mode (raw JSONL output to stdout)", false)
|
|
219
|
+
.option("-d, --dir <path>", "Working directory", process.cwd())
|
|
220
|
+
.option("--status-file <path>", "Write machine-readable JSON status file (updated during run)")
|
|
221
|
+
.option("--skip-precheck", "Skip precheck validations (advanced users only)", false)
|
|
222
|
+
.action(async (options) => {
|
|
223
|
+
// Validate directory path
|
|
224
|
+
let workdir: string;
|
|
225
|
+
try {
|
|
226
|
+
workdir = validateDirectory(options.dir);
|
|
227
|
+
} catch (err) {
|
|
228
|
+
console.error(chalk.red(`Invalid directory: ${(err as Error).message}`));
|
|
229
|
+
process.exit(1);
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
// Determine log level from flags or env var (env var takes precedence)
|
|
233
|
+
let logLevel: LogLevel = "info"; // default
|
|
234
|
+
const envLevel = process.env.NAX_LOG_LEVEL?.toLowerCase();
|
|
235
|
+
if (envLevel && ["error", "warn", "info", "debug"].includes(envLevel)) {
|
|
236
|
+
logLevel = envLevel as LogLevel;
|
|
237
|
+
} else if (options.verbose) {
|
|
238
|
+
logLevel = "debug";
|
|
239
|
+
} else if (options.quiet) {
|
|
240
|
+
logLevel = "warn";
|
|
241
|
+
} else if (options.silent) {
|
|
242
|
+
logLevel = "error";
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
// Determine formatter mode from flags
|
|
246
|
+
let formatterMode: "quiet" | "normal" | "verbose" | "json" = "normal"; // default
|
|
247
|
+
if (options.json) {
|
|
248
|
+
formatterMode = "json";
|
|
249
|
+
} else if (options.verbose) {
|
|
250
|
+
formatterMode = "verbose";
|
|
251
|
+
} else if (options.quiet || options.silent) {
|
|
252
|
+
formatterMode = "quiet";
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
const config = await loadConfig();
|
|
256
|
+
const naxDir = findProjectDir(workdir);
|
|
257
|
+
|
|
258
|
+
if (!naxDir) {
|
|
259
|
+
console.error(chalk.red("nax not initialized. Run: nax init"));
|
|
260
|
+
process.exit(1);
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
const featureDir = join(naxDir, "features", options.feature);
|
|
264
|
+
const prdPath = join(featureDir, "prd.json");
|
|
265
|
+
|
|
266
|
+
if (!existsSync(prdPath)) {
|
|
267
|
+
console.error(chalk.red(`Feature "${options.feature}" not found or missing prd.json`));
|
|
268
|
+
process.exit(1);
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
// Create run directory and JSONL log file path
|
|
272
|
+
const runsDir = join(featureDir, "runs");
|
|
273
|
+
mkdirSync(runsDir, { recursive: true });
|
|
274
|
+
|
|
275
|
+
// Generate run ID from ISO timestamp
|
|
276
|
+
const runId = new Date().toISOString().replace(/:/g, "-").replace(/\..+/, "");
|
|
277
|
+
const logFilePath = join(runsDir, `${runId}.jsonl`);
|
|
278
|
+
|
|
279
|
+
// Determine TUI vs headless mode
|
|
280
|
+
// TUI activates when:
|
|
281
|
+
// 1. stdout is a TTY, AND
|
|
282
|
+
// 2. --headless flag is NOT passed, AND
|
|
283
|
+
// 3. NAX_HEADLESS env var is NOT set
|
|
284
|
+
const isTTY = process.stdout.isTTY ?? false;
|
|
285
|
+
const headlessFlag = options.headless ?? false;
|
|
286
|
+
const headlessEnv = process.env.NAX_HEADLESS === "1";
|
|
287
|
+
const useHeadless = !isTTY || headlessFlag || headlessEnv;
|
|
288
|
+
|
|
289
|
+
// Initialize logger with selected level, file path, and formatter mode
|
|
290
|
+
initLogger({
|
|
291
|
+
level: logLevel,
|
|
292
|
+
filePath: logFilePath,
|
|
293
|
+
useChalk: true,
|
|
294
|
+
formatterMode: useHeadless ? formatterMode : undefined,
|
|
295
|
+
headless: useHeadless,
|
|
296
|
+
});
|
|
297
|
+
|
|
298
|
+
// Override config from CLI
|
|
299
|
+
if (options.agent) {
|
|
300
|
+
config.autoMode.defaultAgent = options.agent;
|
|
301
|
+
}
|
|
302
|
+
config.execution.maxIterations = Number.parseInt(options.maxIterations, 10);
|
|
303
|
+
|
|
304
|
+
const globalNaxDir = join(homedir(), ".nax");
|
|
305
|
+
const hooks = await loadHooksConfig(naxDir, globalNaxDir);
|
|
306
|
+
|
|
307
|
+
// Create event emitter for TUI integration
|
|
308
|
+
const eventEmitter = new PipelineEventEmitter();
|
|
309
|
+
|
|
310
|
+
// Render TUI if not in headless mode
|
|
311
|
+
let tuiInstance: ReturnType<typeof renderTui> | undefined;
|
|
312
|
+
if (!useHeadless) {
|
|
313
|
+
// Load PRD to get initial story states
|
|
314
|
+
const prd = await loadPRD(prdPath);
|
|
315
|
+
const initialStories: StoryDisplayState[] = prd.userStories.map((story) => ({
|
|
316
|
+
story,
|
|
317
|
+
status: story.passes ? "passed" : "pending",
|
|
318
|
+
routing: story.routing,
|
|
319
|
+
cost: 0,
|
|
320
|
+
}));
|
|
321
|
+
|
|
322
|
+
tuiInstance = renderTui({
|
|
323
|
+
feature: options.feature,
|
|
324
|
+
stories: initialStories,
|
|
325
|
+
totalCost: 0,
|
|
326
|
+
elapsedMs: 0,
|
|
327
|
+
events: eventEmitter,
|
|
328
|
+
ptyOptions: null, // TODO: Pass actual PTY spawn options when runner supports it
|
|
329
|
+
});
|
|
330
|
+
} else {
|
|
331
|
+
console.log(chalk.dim(" [Headless mode — pipe output]"));
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
// Resolve --status-file relative to cwd (absolute paths unchanged)
|
|
335
|
+
const statusFilePath = options.statusFile ? resolve(process.cwd(), options.statusFile) : undefined;
|
|
336
|
+
|
|
337
|
+
// Parse --parallel option
|
|
338
|
+
let parallel: number | undefined;
|
|
339
|
+
if (options.parallel !== undefined) {
|
|
340
|
+
parallel = Number.parseInt(options.parallel, 10);
|
|
341
|
+
if (Number.isNaN(parallel) || parallel < 0) {
|
|
342
|
+
console.error(chalk.red("--parallel must be a non-negative integer"));
|
|
343
|
+
process.exit(1);
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
const result = await run({
|
|
348
|
+
prdPath,
|
|
349
|
+
workdir,
|
|
350
|
+
config,
|
|
351
|
+
hooks,
|
|
352
|
+
feature: options.feature,
|
|
353
|
+
featureDir,
|
|
354
|
+
dryRun: options.dryRun,
|
|
355
|
+
useBatch: options.batch ?? true,
|
|
356
|
+
parallel,
|
|
357
|
+
eventEmitter,
|
|
358
|
+
statusFile: statusFilePath,
|
|
359
|
+
logFilePath,
|
|
360
|
+
formatterMode: useHeadless ? formatterMode : undefined,
|
|
361
|
+
headless: useHeadless,
|
|
362
|
+
skipPrecheck: options.skipPrecheck ?? false,
|
|
363
|
+
});
|
|
364
|
+
|
|
365
|
+
// Create/update latest.jsonl symlink
|
|
366
|
+
const latestSymlink = join(runsDir, "latest.jsonl");
|
|
367
|
+
try {
|
|
368
|
+
// Remove existing symlink if present
|
|
369
|
+
if (existsSync(latestSymlink)) {
|
|
370
|
+
Bun.spawnSync(["rm", latestSymlink]);
|
|
371
|
+
}
|
|
372
|
+
// Create new symlink pointing to current run log
|
|
373
|
+
Bun.spawnSync(["ln", "-s", `${runId}.jsonl`, latestSymlink], {
|
|
374
|
+
cwd: runsDir,
|
|
375
|
+
});
|
|
376
|
+
} catch (error) {
|
|
377
|
+
console.error(chalk.yellow(`Warning: Failed to create latest.jsonl symlink: ${error}`));
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
// Cleanup TUI if it was rendered
|
|
381
|
+
if (tuiInstance) {
|
|
382
|
+
tuiInstance.unmount();
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
// Summary (only in headless mode; TUI shows summary itself)
|
|
386
|
+
if (useHeadless) {
|
|
387
|
+
console.log(chalk.dim("\n── Summary ──────────────────────────────────"));
|
|
388
|
+
console.log(chalk.dim(` Iterations: ${result.iterations}`));
|
|
389
|
+
console.log(chalk.dim(` Completed: ${result.storiesCompleted}`));
|
|
390
|
+
console.log(chalk.dim(` Cost: $${result.totalCost.toFixed(4)}`));
|
|
391
|
+
console.log(chalk.dim(` Duration: ${(result.durationMs / 1000 / 60).toFixed(1)} min`));
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
process.exit(result.success ? 0 : 1);
|
|
395
|
+
});
|
|
396
|
+
|
|
397
|
+
// ── features ─────────────────────────────────────────
|
|
398
|
+
const features = program.command("features").description("Manage features");
|
|
399
|
+
|
|
400
|
+
features
|
|
401
|
+
.command("create <name>")
|
|
402
|
+
.description("Create a new feature")
|
|
403
|
+
.option("-d, --dir <path>", "Project directory", process.cwd())
|
|
404
|
+
.action(async (name, options) => {
|
|
405
|
+
// Validate directory path
|
|
406
|
+
let workdir: string;
|
|
407
|
+
try {
|
|
408
|
+
workdir = validateDirectory(options.dir);
|
|
409
|
+
} catch (err) {
|
|
410
|
+
console.error(chalk.red(`Invalid directory: ${(err as Error).message}`));
|
|
411
|
+
process.exit(1);
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
const naxDir = findProjectDir(workdir);
|
|
415
|
+
if (!naxDir) {
|
|
416
|
+
console.error(chalk.red("nax not initialized. Run: nax init"));
|
|
417
|
+
process.exit(1);
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
const featureDir = join(naxDir, "features", name);
|
|
421
|
+
mkdirSync(featureDir, { recursive: true });
|
|
422
|
+
|
|
423
|
+
// Create empty templates
|
|
424
|
+
await Bun.write(
|
|
425
|
+
join(featureDir, "spec.md"),
|
|
426
|
+
`# Feature: ${name}\n\n## Overview\n\n## Requirements\n\n## Acceptance Criteria\n`,
|
|
427
|
+
);
|
|
428
|
+
await Bun.write(
|
|
429
|
+
join(featureDir, "plan.md"),
|
|
430
|
+
`# Plan: ${name}\n\n## Architecture\n\n## Phases\n\n## Dependencies\n`,
|
|
431
|
+
);
|
|
432
|
+
await Bun.write(
|
|
433
|
+
join(featureDir, "tasks.md"),
|
|
434
|
+
`# Tasks: ${name}\n\n## US-001: [Title]\n\n### Description\n\n### Acceptance Criteria\n- [ ] Criterion 1\n`,
|
|
435
|
+
);
|
|
436
|
+
await Bun.write(
|
|
437
|
+
join(featureDir, "progress.txt"),
|
|
438
|
+
`# Progress: ${name}\n\nCreated: ${new Date().toISOString()}\n\n---\n`,
|
|
439
|
+
);
|
|
440
|
+
|
|
441
|
+
console.log(chalk.green(`✅ Created feature: ${name}`));
|
|
442
|
+
console.log(chalk.dim(` ${featureDir}/`));
|
|
443
|
+
console.log(chalk.dim(" ├── spec.md"));
|
|
444
|
+
console.log(chalk.dim(" ├── plan.md"));
|
|
445
|
+
console.log(chalk.dim(" ├── tasks.md"));
|
|
446
|
+
console.log(chalk.dim(" └── progress.txt"));
|
|
447
|
+
console.log(chalk.dim(`\nNext: Edit spec.md and tasks.md, then: nax analyze --feature ${name}`));
|
|
448
|
+
});
|
|
449
|
+
|
|
450
|
+
features
|
|
451
|
+
.command("list")
|
|
452
|
+
.description("List all features")
|
|
453
|
+
.option("-d, --dir <path>", "Project directory", process.cwd())
|
|
454
|
+
.action(async (options) => {
|
|
455
|
+
// Validate directory path
|
|
456
|
+
let workdir: string;
|
|
457
|
+
try {
|
|
458
|
+
workdir = validateDirectory(options.dir);
|
|
459
|
+
} catch (err) {
|
|
460
|
+
console.error(chalk.red(`Invalid directory: ${(err as Error).message}`));
|
|
461
|
+
process.exit(1);
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
const naxDir = findProjectDir(workdir);
|
|
465
|
+
if (!naxDir) {
|
|
466
|
+
console.error(chalk.red("nax not initialized."));
|
|
467
|
+
process.exit(1);
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
const featuresDir = join(naxDir, "features");
|
|
471
|
+
if (!existsSync(featuresDir)) {
|
|
472
|
+
console.log(chalk.dim("No features yet."));
|
|
473
|
+
return;
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
const { readdirSync } = await import("node:fs");
|
|
477
|
+
const entries = readdirSync(featuresDir, { withFileTypes: true })
|
|
478
|
+
.filter((e) => e.isDirectory())
|
|
479
|
+
.map((e) => e.name);
|
|
480
|
+
|
|
481
|
+
if (entries.length === 0) {
|
|
482
|
+
console.log(chalk.dim("No features yet."));
|
|
483
|
+
return;
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
console.log(chalk.bold("\nFeatures:\n"));
|
|
487
|
+
for (const name of entries) {
|
|
488
|
+
const prdPath = join(featuresDir, name, "prd.json");
|
|
489
|
+
if (existsSync(prdPath)) {
|
|
490
|
+
const prd = await loadPRD(prdPath);
|
|
491
|
+
const c = countStories(prd);
|
|
492
|
+
console.log(` ${name} — ${c.passed}/${c.total} stories done`);
|
|
493
|
+
} else {
|
|
494
|
+
console.log(` ${name} (no prd.json yet)`);
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
console.log();
|
|
498
|
+
});
|
|
499
|
+
|
|
500
|
+
// ── plan ─────────────────────────────────────────────
|
|
501
|
+
program
|
|
502
|
+
.command("plan <description>")
|
|
503
|
+
.description("Interactive planning via agent plan mode")
|
|
504
|
+
.option("--from <file>", "Non-interactive mode: read from input file")
|
|
505
|
+
.option("-d, --dir <path>", "Project directory", process.cwd())
|
|
506
|
+
.action(async (description: string, options) => {
|
|
507
|
+
// Validate directory path
|
|
508
|
+
let workdir: string;
|
|
509
|
+
try {
|
|
510
|
+
workdir = validateDirectory(options.dir);
|
|
511
|
+
} catch (err) {
|
|
512
|
+
console.error(chalk.red(`Invalid directory: ${(err as Error).message}`));
|
|
513
|
+
process.exit(1);
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
const naxDir = findProjectDir(workdir);
|
|
517
|
+
if (!naxDir) {
|
|
518
|
+
console.error(chalk.red("nax not initialized. Run: nax init"));
|
|
519
|
+
process.exit(1);
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
// Load config
|
|
523
|
+
const config = await loadConfig(workdir);
|
|
524
|
+
|
|
525
|
+
try {
|
|
526
|
+
const specPath = await planCommand(description, workdir, config, {
|
|
527
|
+
interactive: !options.from,
|
|
528
|
+
from: options.from,
|
|
529
|
+
});
|
|
530
|
+
|
|
531
|
+
console.log(chalk.green("\n✅ Planning complete"));
|
|
532
|
+
console.log(chalk.dim(` Spec: ${specPath}`));
|
|
533
|
+
console.log(chalk.dim("\nNext: nax analyze -f <feature-name>"));
|
|
534
|
+
} catch (err) {
|
|
535
|
+
console.error(chalk.red(`Error: ${(err as Error).message}`));
|
|
536
|
+
process.exit(1);
|
|
537
|
+
}
|
|
538
|
+
});
|
|
539
|
+
|
|
540
|
+
// ── analyze ──────────────────────────────────────────
|
|
541
|
+
program
|
|
542
|
+
.command("analyze")
|
|
543
|
+
.description("Parse spec.md into prd.json via agent decompose")
|
|
544
|
+
.requiredOption("-f, --feature <name>", "Feature name")
|
|
545
|
+
.option("-b, --branch <name>", "Branch name", "feat/<feature>")
|
|
546
|
+
.option("--from <path>", "Explicit spec path (overrides default spec.md)")
|
|
547
|
+
.option("--reclassify", "Re-classify existing prd.json without decompose", false)
|
|
548
|
+
.option("-d, --dir <path>", "Project directory", process.cwd())
|
|
549
|
+
.action(async (options) => {
|
|
550
|
+
// Validate directory path
|
|
551
|
+
let workdir: string;
|
|
552
|
+
try {
|
|
553
|
+
workdir = validateDirectory(options.dir);
|
|
554
|
+
} catch (err) {
|
|
555
|
+
console.error(chalk.red(`Invalid directory: ${(err as Error).message}`));
|
|
556
|
+
process.exit(1);
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
const naxDir = findProjectDir(workdir);
|
|
560
|
+
if (!naxDir) {
|
|
561
|
+
console.error(chalk.red("nax not initialized. Run: nax init"));
|
|
562
|
+
process.exit(1);
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
const featureDir = join(naxDir, "features", options.feature);
|
|
566
|
+
if (!existsSync(featureDir)) {
|
|
567
|
+
console.error(chalk.red(`Feature "${options.feature}" not found.`));
|
|
568
|
+
process.exit(1);
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
const branchName = options.branch.replace("<feature>", options.feature);
|
|
572
|
+
|
|
573
|
+
// Load config for validation
|
|
574
|
+
const config = await loadConfig(workdir);
|
|
575
|
+
|
|
576
|
+
try {
|
|
577
|
+
const prd = await analyzeFeature({
|
|
578
|
+
featureDir,
|
|
579
|
+
featureName: options.feature,
|
|
580
|
+
branchName,
|
|
581
|
+
config,
|
|
582
|
+
specPath: options.from,
|
|
583
|
+
reclassify: options.reclassify,
|
|
584
|
+
});
|
|
585
|
+
|
|
586
|
+
const prdPath = join(featureDir, "prd.json");
|
|
587
|
+
await Bun.write(prdPath, JSON.stringify(prd, null, 2));
|
|
588
|
+
|
|
589
|
+
const c = countStories(prd);
|
|
590
|
+
console.log(chalk.green(`\n✅ Generated prd.json for ${options.feature}`));
|
|
591
|
+
console.log(chalk.dim(` Stories: ${c.total}`));
|
|
592
|
+
console.log(chalk.dim(` Path: ${prdPath}`));
|
|
593
|
+
|
|
594
|
+
for (const story of prd.userStories) {
|
|
595
|
+
const routing = story.routing ? chalk.dim(` [${story.routing.complexity}]`) : "";
|
|
596
|
+
console.log(chalk.dim(` ${story.id}: ${story.title}${routing}`));
|
|
597
|
+
}
|
|
598
|
+
console.log();
|
|
599
|
+
} catch (err) {
|
|
600
|
+
console.error(chalk.red(`Error: ${(err as Error).message}`));
|
|
601
|
+
process.exit(1);
|
|
602
|
+
}
|
|
603
|
+
});
|
|
604
|
+
|
|
605
|
+
// ── agents ───────────────────────────────────────────
|
|
606
|
+
program
|
|
607
|
+
.command("agents")
|
|
608
|
+
.description("Check available coding agents")
|
|
609
|
+
.action(async () => {
|
|
610
|
+
const health = await checkAgentHealth();
|
|
611
|
+
|
|
612
|
+
console.log(chalk.bold("\nCoding Agents:\n"));
|
|
613
|
+
for (const agent of health) {
|
|
614
|
+
const status = agent.installed ? chalk.green("✅ installed") : chalk.red("❌ not found");
|
|
615
|
+
console.log(` ${agent.displayName.padEnd(15)} ${status}`);
|
|
616
|
+
}
|
|
617
|
+
console.log();
|
|
618
|
+
});
|
|
619
|
+
|
|
620
|
+
// ── config ───────────────────────────────────────────
|
|
621
|
+
program
|
|
622
|
+
.command("config")
|
|
623
|
+
.description("Display effective merged configuration")
|
|
624
|
+
.option("--explain", "Show detailed field descriptions", false)
|
|
625
|
+
.option("--diff", "Show only fields where project overrides global", false)
|
|
626
|
+
.action(async (options) => {
|
|
627
|
+
try {
|
|
628
|
+
const config = await loadConfig();
|
|
629
|
+
await configCommand(config, { explain: options.explain, diff: options.diff });
|
|
630
|
+
} catch (err) {
|
|
631
|
+
console.error(chalk.red(`Error: ${(err as Error).message}`));
|
|
632
|
+
process.exit(1);
|
|
633
|
+
}
|
|
634
|
+
});
|
|
635
|
+
|
|
636
|
+
// ── status ───────────────────────────────────────────
|
|
637
|
+
program
|
|
638
|
+
.command("status")
|
|
639
|
+
.description("Show current run status")
|
|
640
|
+
.option("-f, --feature <name>", "Feature name")
|
|
641
|
+
.option("-d, --dir <path>", "Project directory", process.cwd())
|
|
642
|
+
.option("--cost", "Show cost metrics across all runs", false)
|
|
643
|
+
.option("--last", "Show last run metrics (requires --cost)", false)
|
|
644
|
+
.option("--model", "Show per-model efficiency (requires --cost)", false)
|
|
645
|
+
.action(async (options) => {
|
|
646
|
+
// Validate directory path
|
|
647
|
+
let workdir: string;
|
|
648
|
+
try {
|
|
649
|
+
workdir = validateDirectory(options.dir);
|
|
650
|
+
} catch (err) {
|
|
651
|
+
console.error(chalk.red(`Invalid directory: ${(err as Error).message}`));
|
|
652
|
+
process.exit(1);
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
const naxDir = findProjectDir(workdir);
|
|
656
|
+
if (!naxDir) {
|
|
657
|
+
console.error(chalk.red("nax not initialized."));
|
|
658
|
+
process.exit(1);
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
// Handle cost metrics flags
|
|
662
|
+
if (options.cost) {
|
|
663
|
+
if (options.last) {
|
|
664
|
+
await displayLastRunMetrics(workdir);
|
|
665
|
+
} else if (options.model) {
|
|
666
|
+
await displayModelEfficiency(workdir);
|
|
667
|
+
} else {
|
|
668
|
+
await displayCostMetrics(workdir);
|
|
669
|
+
}
|
|
670
|
+
return;
|
|
671
|
+
}
|
|
672
|
+
|
|
673
|
+
// Default status: show feature progress (new implementation with active run detection)
|
|
674
|
+
await displayFeatureStatus({
|
|
675
|
+
feature: options.feature,
|
|
676
|
+
dir: options.dir,
|
|
677
|
+
});
|
|
678
|
+
});
|
|
679
|
+
|
|
680
|
+
// ── logs ─────────────────────────────────────────────
|
|
681
|
+
program
|
|
682
|
+
.command("logs")
|
|
683
|
+
.description("Display run logs with filtering and follow mode")
|
|
684
|
+
.option("-d, --dir <path>", "Project directory", process.cwd())
|
|
685
|
+
.option("-f, --follow", "Follow mode - stream new entries real-time", false)
|
|
686
|
+
.option("-s, --story <id>", "Filter to specific story")
|
|
687
|
+
.option("--level <level>", "Filter by log level (debug|info|warn|error)")
|
|
688
|
+
.option("-l, --list", "List all runs in table format", false)
|
|
689
|
+
.option("-r, --run <timestamp>", "Select specific run by timestamp")
|
|
690
|
+
.option("-j, --json", "Output raw JSONL", false)
|
|
691
|
+
.action(async (options) => {
|
|
692
|
+
let workdir: string;
|
|
693
|
+
try {
|
|
694
|
+
workdir = validateDirectory(options.dir);
|
|
695
|
+
} catch (err) {
|
|
696
|
+
console.error(chalk.red(`Invalid directory: ${(err as Error).message}`));
|
|
697
|
+
process.exit(1);
|
|
698
|
+
}
|
|
699
|
+
|
|
700
|
+
try {
|
|
701
|
+
await logsCommand({
|
|
702
|
+
dir: workdir,
|
|
703
|
+
follow: options.follow,
|
|
704
|
+
story: options.story,
|
|
705
|
+
level: options.level,
|
|
706
|
+
list: options.list,
|
|
707
|
+
run: options.run,
|
|
708
|
+
json: options.json,
|
|
709
|
+
});
|
|
710
|
+
} catch (err) {
|
|
711
|
+
console.error(chalk.red(`Error: ${(err as Error).message}`));
|
|
712
|
+
process.exit(1);
|
|
713
|
+
}
|
|
714
|
+
});
|
|
715
|
+
|
|
716
|
+
// ── diagnose ─────────────────────────────────────────
|
|
717
|
+
program
|
|
718
|
+
.command("diagnose")
|
|
719
|
+
.description("Diagnose run failures and generate recommendations")
|
|
720
|
+
.option("-f, --feature <name>", "Feature name (defaults to current feature)")
|
|
721
|
+
.option("-d, --dir <path>", "Working directory", process.cwd())
|
|
722
|
+
.option("--json", "Output machine-readable JSON", false)
|
|
723
|
+
.option("--verbose", "Verbose output with story breakdown", false)
|
|
724
|
+
.action(async (options) => {
|
|
725
|
+
try {
|
|
726
|
+
await diagnose({
|
|
727
|
+
feature: options.feature,
|
|
728
|
+
workdir: options.dir,
|
|
729
|
+
json: options.json,
|
|
730
|
+
verbose: options.verbose,
|
|
731
|
+
});
|
|
732
|
+
} catch (err) {
|
|
733
|
+
console.error(chalk.red(`Error: ${(err as Error).message}`));
|
|
734
|
+
process.exit(1);
|
|
735
|
+
}
|
|
736
|
+
});
|
|
737
|
+
|
|
738
|
+
// ── precheck ─────────────────────────────────────────
|
|
739
|
+
program
|
|
740
|
+
.command("precheck")
|
|
741
|
+
.description("Validate feature readiness before execution")
|
|
742
|
+
.option("-f, --feature <name>", "Feature name")
|
|
743
|
+
.option("-d, --dir <path>", "Project directory", process.cwd())
|
|
744
|
+
.option("--json", "Output machine-readable JSON", false)
|
|
745
|
+
.action(async (options) => {
|
|
746
|
+
try {
|
|
747
|
+
await precheckCommand({
|
|
748
|
+
feature: options.feature,
|
|
749
|
+
dir: options.dir,
|
|
750
|
+
json: options.json,
|
|
751
|
+
});
|
|
752
|
+
} catch (err) {
|
|
753
|
+
console.error(chalk.red(`Error: ${(err as Error).message}`));
|
|
754
|
+
process.exit(1);
|
|
755
|
+
}
|
|
756
|
+
});
|
|
757
|
+
|
|
758
|
+
// ── unlock ───────────────────────────────────────────
|
|
759
|
+
program
|
|
760
|
+
.command("unlock")
|
|
761
|
+
.description("Release stale lock from crashed nax process")
|
|
762
|
+
.option("-d, --dir <path>", "Project directory", process.cwd())
|
|
763
|
+
.option("--force", "Skip liveness check and remove unconditionally", false)
|
|
764
|
+
.action(async (options) => {
|
|
765
|
+
try {
|
|
766
|
+
await unlockCommand({
|
|
767
|
+
dir: options.dir,
|
|
768
|
+
force: options.force,
|
|
769
|
+
});
|
|
770
|
+
} catch (err) {
|
|
771
|
+
console.error(chalk.red(`Error: ${(err as Error).message}`));
|
|
772
|
+
process.exit(1);
|
|
773
|
+
}
|
|
774
|
+
});
|
|
775
|
+
|
|
776
|
+
// ── runs ─────────────────────────────────────────────
|
|
777
|
+
const runs = program.command("runs").description("Manage and view run history");
|
|
778
|
+
|
|
779
|
+
runs
|
|
780
|
+
.command("list")
|
|
781
|
+
.description("List all runs for a feature")
|
|
782
|
+
.requiredOption("-f, --feature <name>", "Feature name")
|
|
783
|
+
.option("-d, --dir <path>", "Project directory", process.cwd())
|
|
784
|
+
.action(async (options) => {
|
|
785
|
+
let workdir: string;
|
|
786
|
+
try {
|
|
787
|
+
workdir = validateDirectory(options.dir);
|
|
788
|
+
} catch (err) {
|
|
789
|
+
console.error(chalk.red(`Invalid directory: ${(err as Error).message}`));
|
|
790
|
+
process.exit(1);
|
|
791
|
+
}
|
|
792
|
+
|
|
793
|
+
await runsListCommand({ feature: options.feature, workdir });
|
|
794
|
+
});
|
|
795
|
+
|
|
796
|
+
runs
|
|
797
|
+
.command("show <run-id>")
|
|
798
|
+
.description("Show detailed information for a specific run")
|
|
799
|
+
.requiredOption("-f, --feature <name>", "Feature name")
|
|
800
|
+
.option("-d, --dir <path>", "Project directory", process.cwd())
|
|
801
|
+
.action(async (runId, options) => {
|
|
802
|
+
let workdir: string;
|
|
803
|
+
try {
|
|
804
|
+
workdir = validateDirectory(options.dir);
|
|
805
|
+
} catch (err) {
|
|
806
|
+
console.error(chalk.red(`Invalid directory: ${(err as Error).message}`));
|
|
807
|
+
process.exit(1);
|
|
808
|
+
}
|
|
809
|
+
|
|
810
|
+
await runsShowCommand({ runId, feature: options.feature, workdir });
|
|
811
|
+
});
|
|
812
|
+
|
|
813
|
+
// ── accept ───────────────────────────────────────────
|
|
814
|
+
program
|
|
815
|
+
.command("accept")
|
|
816
|
+
.description("Override failed acceptance criteria")
|
|
817
|
+
.requiredOption("-f, --feature <name>", "Feature name")
|
|
818
|
+
.requiredOption("--override <ac-id>", "AC ID to override (e.g., AC-2)")
|
|
819
|
+
.requiredOption("-r, --reason <reason>", "Reason for accepting despite test failure")
|
|
820
|
+
.action(async (options) => {
|
|
821
|
+
try {
|
|
822
|
+
await acceptCommand({
|
|
823
|
+
feature: options.feature,
|
|
824
|
+
override: options.override,
|
|
825
|
+
reason: options.reason,
|
|
826
|
+
});
|
|
827
|
+
} catch (err) {
|
|
828
|
+
console.error(chalk.red(`Error: ${(err as Error).message}`));
|
|
829
|
+
process.exit(1);
|
|
830
|
+
}
|
|
831
|
+
});
|
|
832
|
+
|
|
833
|
+
// ── prompts ──────────────────────────────────────────
|
|
834
|
+
program
|
|
835
|
+
.command("prompts")
|
|
836
|
+
.description("Assemble prompts for stories without executing agents")
|
|
837
|
+
.requiredOption("-f, --feature <name>", "Feature name")
|
|
838
|
+
.option("--story <id>", "Filter to a single story ID (e.g., US-003)")
|
|
839
|
+
.option("--out <dir>", "Output directory for prompt files (default: stdout)")
|
|
840
|
+
.option("-d, --dir <path>", "Project directory", process.cwd())
|
|
841
|
+
.action(async (options) => {
|
|
842
|
+
// Validate directory path
|
|
843
|
+
let workdir: string;
|
|
844
|
+
try {
|
|
845
|
+
workdir = validateDirectory(options.dir);
|
|
846
|
+
} catch (err) {
|
|
847
|
+
console.error(chalk.red(`Invalid directory: ${(err as Error).message}`));
|
|
848
|
+
process.exit(1);
|
|
849
|
+
}
|
|
850
|
+
|
|
851
|
+
// Load config
|
|
852
|
+
const config = await loadConfig(workdir);
|
|
853
|
+
|
|
854
|
+
try {
|
|
855
|
+
const processedStories = await promptsCommand({
|
|
856
|
+
feature: options.feature,
|
|
857
|
+
workdir,
|
|
858
|
+
config,
|
|
859
|
+
storyId: options.story,
|
|
860
|
+
outputDir: options.out,
|
|
861
|
+
});
|
|
862
|
+
|
|
863
|
+
if (options.out) {
|
|
864
|
+
console.log(chalk.green(`\n✅ Prompts written to ${options.out}`));
|
|
865
|
+
console.log(chalk.dim(` Processed ${processedStories.length} stories`));
|
|
866
|
+
}
|
|
867
|
+
} catch (err) {
|
|
868
|
+
console.error(chalk.red(`Error: ${(err as Error).message}`));
|
|
869
|
+
process.exit(1);
|
|
870
|
+
}
|
|
871
|
+
});
|
|
872
|
+
|
|
873
|
+
// ── generate ──────────────────────────────────────────
|
|
874
|
+
program
|
|
875
|
+
.command("generate")
|
|
876
|
+
.description("Generate agent config files (CLAUDE.md, AGENTS.md, etc.) from nax/context.md")
|
|
877
|
+
.option("-c, --context <path>", "Context file path (default: nax/context.md)")
|
|
878
|
+
.option("-o, --output <dir>", "Output directory (default: project root)")
|
|
879
|
+
.option("-a, --agent <name>", "Specific agent (claude|opencode|cursor|windsurf|aider)")
|
|
880
|
+
.option("--dry-run", "Preview without writing files", false)
|
|
881
|
+
.option("--no-auto-inject", "Disable auto-injection of project metadata")
|
|
882
|
+
.action(async (options) => {
|
|
883
|
+
try {
|
|
884
|
+
await generateCommand({
|
|
885
|
+
context: options.context,
|
|
886
|
+
output: options.output,
|
|
887
|
+
agent: options.agent,
|
|
888
|
+
dryRun: options.dryRun,
|
|
889
|
+
noAutoInject: !options.autoInject,
|
|
890
|
+
});
|
|
891
|
+
} catch (err) {
|
|
892
|
+
console.error(chalk.red(`Error: ${(err as Error).message}`));
|
|
893
|
+
process.exit(1);
|
|
894
|
+
}
|
|
895
|
+
});
|
|
896
|
+
|
|
897
|
+
// ── plugins ──────────────────────────────────────────
|
|
898
|
+
const plugins = program.command("plugins").description("Manage plugins");
|
|
899
|
+
|
|
900
|
+
plugins
|
|
901
|
+
.command("list")
|
|
902
|
+
.description("List all installed plugins")
|
|
903
|
+
.option("-d, --dir <path>", "Project directory", process.cwd())
|
|
904
|
+
.action(async (options) => {
|
|
905
|
+
// Validate directory path
|
|
906
|
+
let workdir: string;
|
|
907
|
+
try {
|
|
908
|
+
workdir = validateDirectory(options.dir);
|
|
909
|
+
} catch (err) {
|
|
910
|
+
console.error(chalk.red(`Invalid directory: ${(err as Error).message}`));
|
|
911
|
+
process.exit(1);
|
|
912
|
+
}
|
|
913
|
+
|
|
914
|
+
// Load config (or use default if outside project)
|
|
915
|
+
let config = DEFAULT_CONFIG;
|
|
916
|
+
try {
|
|
917
|
+
config = await loadConfig(workdir);
|
|
918
|
+
} catch {
|
|
919
|
+
// Outside project directory, use default config (global plugins only)
|
|
920
|
+
}
|
|
921
|
+
|
|
922
|
+
try {
|
|
923
|
+
await pluginsListCommand(config, workdir);
|
|
924
|
+
} catch (err) {
|
|
925
|
+
console.error(chalk.red(`Error: ${(err as Error).message}`));
|
|
926
|
+
process.exit(1);
|
|
927
|
+
}
|
|
928
|
+
});
|
|
929
|
+
|
|
930
|
+
program.parse();
|