@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,151 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Status Cost Metrics
|
|
3
|
+
*
|
|
4
|
+
* Extracted from status.ts: cost metrics display functions for CLI output.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { getLogger } from "../logger";
|
|
8
|
+
import { calculateAggregateMetrics, getLastRun, loadRunMetrics } from "../metrics";
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Display aggregate cost metrics across all runs.
|
|
12
|
+
*
|
|
13
|
+
* @param workdir - Project root directory
|
|
14
|
+
*
|
|
15
|
+
* @example
|
|
16
|
+
* ```bash
|
|
17
|
+
* nax status --cost
|
|
18
|
+
* ```
|
|
19
|
+
*/
|
|
20
|
+
export async function displayCostMetrics(workdir: string): Promise<void> {
|
|
21
|
+
const logger = getLogger();
|
|
22
|
+
const runs = await loadRunMetrics(workdir);
|
|
23
|
+
|
|
24
|
+
if (runs.length === 0) {
|
|
25
|
+
logger.info("cli", "No metrics data available yet", { hint: "Run nax run to generate metrics" });
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const aggregate = calculateAggregateMetrics(runs);
|
|
30
|
+
|
|
31
|
+
logger.info("cli", "Cost Metrics (All Runs)", {
|
|
32
|
+
totalRuns: aggregate.totalRuns,
|
|
33
|
+
totalStories: aggregate.totalStories,
|
|
34
|
+
totalCost: aggregate.totalCost,
|
|
35
|
+
avgCostPerStory: aggregate.avgCostPerStory,
|
|
36
|
+
avgCostPerFeature: aggregate.avgCostPerFeature,
|
|
37
|
+
firstPassRate: aggregate.firstPassRate,
|
|
38
|
+
escalationRate: aggregate.escalationRate,
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Display metrics from the most recent run.
|
|
44
|
+
*
|
|
45
|
+
* @param workdir - Project root directory
|
|
46
|
+
*
|
|
47
|
+
* @example
|
|
48
|
+
* ```bash
|
|
49
|
+
* nax status --cost --last
|
|
50
|
+
* ```
|
|
51
|
+
*/
|
|
52
|
+
export async function displayLastRunMetrics(workdir: string): Promise<void> {
|
|
53
|
+
const logger = getLogger();
|
|
54
|
+
const runs = await loadRunMetrics(workdir);
|
|
55
|
+
|
|
56
|
+
if (runs.length === 0) {
|
|
57
|
+
logger.info("cli", "No metrics data available yet", { hint: "Run nax run to generate metrics" });
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const lastRun = getLastRun(runs);
|
|
62
|
+
if (!lastRun) {
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
logger.info("cli", `Last Run: ${lastRun.feature}`, {
|
|
67
|
+
runId: lastRun.runId,
|
|
68
|
+
startedAt: lastRun.startedAt,
|
|
69
|
+
completedAt: lastRun.completedAt,
|
|
70
|
+
durationMs: lastRun.totalDurationMs,
|
|
71
|
+
totalStories: lastRun.totalStories,
|
|
72
|
+
storiesCompleted: lastRun.storiesCompleted,
|
|
73
|
+
storiesFailed: lastRun.storiesFailed,
|
|
74
|
+
totalCost: lastRun.totalCost,
|
|
75
|
+
avgCostPerStory: lastRun.totalCost / lastRun.totalStories,
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
// Show top 5 most expensive stories
|
|
79
|
+
const sortedStories = [...lastRun.stories].sort((a, b) => b.cost - a.cost);
|
|
80
|
+
const topStories = sortedStories.slice(0, 5);
|
|
81
|
+
|
|
82
|
+
if (topStories.length > 0) {
|
|
83
|
+
logger.info("cli", "Top 5 Most Expensive Stories", {
|
|
84
|
+
stories: topStories.map((s) => ({
|
|
85
|
+
storyId: s.storyId,
|
|
86
|
+
cost: s.cost,
|
|
87
|
+
model: s.modelUsed,
|
|
88
|
+
attempts: s.attempts,
|
|
89
|
+
})),
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Display per-model efficiency metrics.
|
|
96
|
+
*
|
|
97
|
+
* @param workdir - Project root directory
|
|
98
|
+
*
|
|
99
|
+
* @example
|
|
100
|
+
* ```bash
|
|
101
|
+
* nax status --cost --model
|
|
102
|
+
* ```
|
|
103
|
+
*/
|
|
104
|
+
export async function displayModelEfficiency(workdir: string): Promise<void> {
|
|
105
|
+
const logger = getLogger();
|
|
106
|
+
const runs = await loadRunMetrics(workdir);
|
|
107
|
+
|
|
108
|
+
if (runs.length === 0) {
|
|
109
|
+
logger.info("cli", "No metrics data available yet", { hint: "Run nax run to generate metrics" });
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const aggregate = calculateAggregateMetrics(runs);
|
|
114
|
+
|
|
115
|
+
// Sort models by total cost (descending)
|
|
116
|
+
const sortedModels = Object.entries(aggregate.modelEfficiency).sort(([, a], [, b]) => b.totalCost - a.totalCost);
|
|
117
|
+
|
|
118
|
+
if (sortedModels.length === 0) {
|
|
119
|
+
logger.info("cli", "No model data available");
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
logger.info("cli", "Model Efficiency", {
|
|
124
|
+
models: sortedModels.map(([modelName, stats]) => ({
|
|
125
|
+
model: modelName,
|
|
126
|
+
attempts: stats.attempts,
|
|
127
|
+
passRate: stats.passRate,
|
|
128
|
+
avgCost: stats.avgCost,
|
|
129
|
+
totalCost: stats.totalCost,
|
|
130
|
+
})),
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
// Show complexity accuracy
|
|
134
|
+
const sortedComplexity = Object.entries(aggregate.complexityAccuracy).sort(
|
|
135
|
+
([, a], [, b]) => b.predicted - a.predicted,
|
|
136
|
+
);
|
|
137
|
+
|
|
138
|
+
if (sortedComplexity.length === 0) {
|
|
139
|
+
logger.info("cli", "No complexity data available");
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
logger.info("cli", "Complexity Prediction Accuracy", {
|
|
144
|
+
complexities: sortedComplexity.map(([complexity, stats]) => ({
|
|
145
|
+
complexity,
|
|
146
|
+
predicted: stats.predicted,
|
|
147
|
+
actualTierUsed: stats.actualTierUsed,
|
|
148
|
+
mismatchRate: stats.mismatchRate,
|
|
149
|
+
})),
|
|
150
|
+
});
|
|
151
|
+
}
|
|
@@ -0,0 +1,338 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Status Feature Display
|
|
3
|
+
*
|
|
4
|
+
* Extracted from status.ts: feature status display (all-features table and single-feature details).
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { existsSync, readdirSync } from "node:fs";
|
|
8
|
+
import { join } from "node:path";
|
|
9
|
+
import chalk from "chalk";
|
|
10
|
+
import { resolveProject } from "../commands/common";
|
|
11
|
+
import type { NaxStatusFile } from "../execution/status-file";
|
|
12
|
+
import { listPendingInteractions, loadPendingInteraction } from "../interaction";
|
|
13
|
+
import { countStories, loadPRD } from "../prd";
|
|
14
|
+
|
|
15
|
+
/** Options for feature status command */
|
|
16
|
+
export interface FeatureStatusOptions {
|
|
17
|
+
/** Feature name (from -f flag) */
|
|
18
|
+
feature?: string;
|
|
19
|
+
/** Explicit project directory (from -d flag) */
|
|
20
|
+
dir?: string;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/** Feature summary for the all-features table */
|
|
24
|
+
interface FeatureSummary {
|
|
25
|
+
name: string;
|
|
26
|
+
done: number;
|
|
27
|
+
failed: number;
|
|
28
|
+
pending: number;
|
|
29
|
+
total: number;
|
|
30
|
+
lastRun?: string;
|
|
31
|
+
cost?: number;
|
|
32
|
+
activeRun?: {
|
|
33
|
+
runId: string;
|
|
34
|
+
pid: number;
|
|
35
|
+
startedAt: string;
|
|
36
|
+
};
|
|
37
|
+
crashedRun?: {
|
|
38
|
+
runId: string;
|
|
39
|
+
pid: number;
|
|
40
|
+
crashedAt?: string;
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/** Check if a process is alive via PID check */
|
|
45
|
+
function isPidAlive(pid: number): boolean {
|
|
46
|
+
try {
|
|
47
|
+
const result = Bun.spawnSync(["ps", "-p", String(pid)], {
|
|
48
|
+
stdout: "ignore",
|
|
49
|
+
stderr: "ignore",
|
|
50
|
+
});
|
|
51
|
+
return result.exitCode === 0;
|
|
52
|
+
} catch {
|
|
53
|
+
return false;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/** Load status.json for a feature (if it exists) */
|
|
58
|
+
async function loadStatusFile(featureDir: string): Promise<NaxStatusFile | null> {
|
|
59
|
+
const statusPath = join(featureDir, "status.json");
|
|
60
|
+
if (!existsSync(statusPath)) {
|
|
61
|
+
return null;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
try {
|
|
65
|
+
const content = Bun.file(statusPath);
|
|
66
|
+
return (await content.json()) as NaxStatusFile;
|
|
67
|
+
} catch {
|
|
68
|
+
return null;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/** Get feature summary from prd.json and optional status.json */
|
|
73
|
+
async function getFeatureSummary(featureName: string, featureDir: string): Promise<FeatureSummary> {
|
|
74
|
+
const prdPath = join(featureDir, "prd.json");
|
|
75
|
+
|
|
76
|
+
// Load PRD for story counts
|
|
77
|
+
const prd = await loadPRD(prdPath);
|
|
78
|
+
const counts = countStories(prd);
|
|
79
|
+
|
|
80
|
+
const summary: FeatureSummary = {
|
|
81
|
+
name: featureName,
|
|
82
|
+
done: counts.passed,
|
|
83
|
+
failed: counts.failed,
|
|
84
|
+
pending: counts.pending,
|
|
85
|
+
total: counts.total,
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
// Load status.json if available
|
|
89
|
+
const status = await loadStatusFile(featureDir);
|
|
90
|
+
if (status) {
|
|
91
|
+
summary.cost = status.cost.spent;
|
|
92
|
+
|
|
93
|
+
// Check if run is active or crashed
|
|
94
|
+
const pidAlive = isPidAlive(status.run.pid);
|
|
95
|
+
|
|
96
|
+
if (status.run.status === "running" && pidAlive) {
|
|
97
|
+
summary.activeRun = {
|
|
98
|
+
runId: status.run.id,
|
|
99
|
+
pid: status.run.pid,
|
|
100
|
+
startedAt: status.run.startedAt,
|
|
101
|
+
};
|
|
102
|
+
} else if (status.run.status === "running" && !pidAlive) {
|
|
103
|
+
// Run is marked "running" but PID is dead — crashed
|
|
104
|
+
summary.crashedRun = {
|
|
105
|
+
runId: status.run.id,
|
|
106
|
+
pid: status.run.pid,
|
|
107
|
+
crashedAt: status.run.crashedAt,
|
|
108
|
+
};
|
|
109
|
+
} else if (status.run.status === "crashed") {
|
|
110
|
+
// Run explicitly marked as crashed
|
|
111
|
+
summary.crashedRun = {
|
|
112
|
+
runId: status.run.id,
|
|
113
|
+
pid: status.run.pid,
|
|
114
|
+
crashedAt: status.run.crashedAt,
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// Get last run timestamp from runs/ directory
|
|
120
|
+
const runsDir = join(featureDir, "runs");
|
|
121
|
+
if (existsSync(runsDir)) {
|
|
122
|
+
const runs = readdirSync(runsDir, { withFileTypes: true })
|
|
123
|
+
.filter((e) => e.isFile() && e.name.endsWith(".jsonl") && e.name !== "latest.jsonl")
|
|
124
|
+
.map((e) => e.name)
|
|
125
|
+
.sort()
|
|
126
|
+
.reverse();
|
|
127
|
+
|
|
128
|
+
if (runs.length > 0) {
|
|
129
|
+
// Extract timestamp from filename (YYYY-MM-DDTHH-MM-SS.jsonl)
|
|
130
|
+
const latestRun = runs[0].replace(".jsonl", "");
|
|
131
|
+
summary.lastRun = latestRun;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
return summary;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/** Display all features table */
|
|
139
|
+
async function displayAllFeatures(projectDir: string): Promise<void> {
|
|
140
|
+
const featuresDir = join(projectDir, "nax", "features");
|
|
141
|
+
|
|
142
|
+
if (!existsSync(featuresDir)) {
|
|
143
|
+
console.log(chalk.dim("No features found."));
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
const features = readdirSync(featuresDir, { withFileTypes: true })
|
|
148
|
+
.filter((e) => e.isDirectory())
|
|
149
|
+
.map((e) => e.name)
|
|
150
|
+
.sort();
|
|
151
|
+
|
|
152
|
+
if (features.length === 0) {
|
|
153
|
+
console.log(chalk.dim("No features found."));
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// Load summaries for all features
|
|
158
|
+
const summaries = await Promise.all(features.map((name) => getFeatureSummary(name, join(featuresDir, name))));
|
|
159
|
+
|
|
160
|
+
console.log(chalk.bold("\n📊 Features\n"));
|
|
161
|
+
|
|
162
|
+
// Print table header
|
|
163
|
+
const header = ` ${"Feature".padEnd(25)} ${"Done".padEnd(6)} ${"Failed".padEnd(8)} ${"Pending".padEnd(9)} ${"Last Run".padEnd(22)} ${"Cost".padEnd(10)} Status`;
|
|
164
|
+
console.log(chalk.dim(header));
|
|
165
|
+
console.log(chalk.dim(` ${"─".repeat(100)}`));
|
|
166
|
+
|
|
167
|
+
// Print each feature row
|
|
168
|
+
for (const summary of summaries) {
|
|
169
|
+
const name = summary.name.padEnd(25);
|
|
170
|
+
const done = chalk.green(String(summary.done).padEnd(6));
|
|
171
|
+
const failed =
|
|
172
|
+
summary.failed > 0 ? chalk.red(String(summary.failed).padEnd(8)) : chalk.dim(String(summary.failed).padEnd(8));
|
|
173
|
+
const pending = chalk.dim(String(summary.pending).padEnd(9));
|
|
174
|
+
const lastRun = summary.lastRun ? summary.lastRun.padEnd(22) : chalk.dim("No runs yet".padEnd(22));
|
|
175
|
+
const cost = summary.cost !== undefined ? `$${summary.cost.toFixed(4)}`.padEnd(10) : chalk.dim("—".padEnd(10));
|
|
176
|
+
|
|
177
|
+
let status = "";
|
|
178
|
+
if (summary.activeRun) {
|
|
179
|
+
status = chalk.yellow("⚡ Running");
|
|
180
|
+
} else if (summary.crashedRun) {
|
|
181
|
+
status = chalk.red("💥 Crashed");
|
|
182
|
+
} else {
|
|
183
|
+
status = chalk.dim("—");
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
console.log(` ${name} ${done} ${failed} ${pending} ${lastRun} ${cost} ${status}`);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
console.log();
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/** Display single feature details */
|
|
193
|
+
async function displayFeatureDetails(featureName: string, featureDir: string): Promise<void> {
|
|
194
|
+
const prdPath = join(featureDir, "prd.json");
|
|
195
|
+
const prd = await loadPRD(prdPath);
|
|
196
|
+
const counts = countStories(prd);
|
|
197
|
+
|
|
198
|
+
// Load status.json if available
|
|
199
|
+
const status = await loadStatusFile(featureDir);
|
|
200
|
+
|
|
201
|
+
console.log(chalk.bold(`\n📊 ${prd.feature}\n`));
|
|
202
|
+
|
|
203
|
+
// Check for pending interactions
|
|
204
|
+
const pendingIds = await listPendingInteractions(featureDir);
|
|
205
|
+
if (pendingIds.length > 0) {
|
|
206
|
+
console.log(chalk.cyan(`⏸️ Paused — Waiting for Interaction (${pendingIds.length} pending)\n`));
|
|
207
|
+
|
|
208
|
+
for (const id of pendingIds) {
|
|
209
|
+
const req = await loadPendingInteraction(id, featureDir);
|
|
210
|
+
if (req) {
|
|
211
|
+
const safety = req.metadata?.safety ?? "unknown";
|
|
212
|
+
const safetyIcon = safety === "red" ? "🔴" : safety === "yellow" ? "🟡" : "🟢";
|
|
213
|
+
const timeRemaining = req.timeout ? Math.max(0, req.createdAt + req.timeout - Date.now()) : null;
|
|
214
|
+
const timeoutSec = timeRemaining !== null ? Math.floor(timeRemaining / 1000) : null;
|
|
215
|
+
|
|
216
|
+
console.log(` ${safetyIcon} ${chalk.bold(req.id)}`);
|
|
217
|
+
console.log(chalk.dim(` Type: ${req.type}`));
|
|
218
|
+
console.log(chalk.dim(` Summary: ${req.summary}`));
|
|
219
|
+
console.log(chalk.dim(` Fallback: ${req.fallback}`));
|
|
220
|
+
if (timeoutSec !== null) {
|
|
221
|
+
if (timeoutSec > 0) {
|
|
222
|
+
console.log(chalk.dim(` Timeout: ${timeoutSec}s remaining`));
|
|
223
|
+
} else {
|
|
224
|
+
console.log(chalk.red(" Timeout: EXPIRED"));
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
console.log();
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
console.log(chalk.dim(" 💡 Respond with: nax interact respond <id> --action approve|reject|skip|abort"));
|
|
232
|
+
console.log();
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
// Display run status if active or crashed
|
|
236
|
+
if (status) {
|
|
237
|
+
const pidAlive = isPidAlive(status.run.pid);
|
|
238
|
+
|
|
239
|
+
if (status.run.status === "running" && pidAlive) {
|
|
240
|
+
console.log(chalk.yellow("⚡ Active Run:"));
|
|
241
|
+
console.log(chalk.dim(` Run ID: ${status.run.id}`));
|
|
242
|
+
console.log(chalk.dim(` PID: ${status.run.pid}`));
|
|
243
|
+
console.log(chalk.dim(` Started: ${status.run.startedAt}`));
|
|
244
|
+
console.log(chalk.dim(` Progress: ${status.progress.passed}/${status.progress.total} stories`));
|
|
245
|
+
console.log(chalk.dim(` Cost: $${status.cost.spent.toFixed(4)}`));
|
|
246
|
+
|
|
247
|
+
if (status.current) {
|
|
248
|
+
console.log(chalk.dim(` Current: ${status.current.storyId} - ${status.current.title}`));
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
console.log();
|
|
252
|
+
} else if ((status.run.status === "running" && !pidAlive) || status.run.status === "crashed") {
|
|
253
|
+
console.log(chalk.red("💥 Crashed Run Detected:\n"));
|
|
254
|
+
console.log(chalk.dim(` Run ID: ${status.run.id}`));
|
|
255
|
+
console.log(chalk.dim(` PID: ${status.run.pid} (dead)`));
|
|
256
|
+
console.log(chalk.dim(` Started: ${status.run.startedAt}`));
|
|
257
|
+
if (status.run.crashedAt) {
|
|
258
|
+
console.log(chalk.dim(` Crashed: ${status.run.crashedAt}`));
|
|
259
|
+
}
|
|
260
|
+
if (status.run.crashSignal) {
|
|
261
|
+
console.log(chalk.dim(` Signal: ${status.run.crashSignal}`));
|
|
262
|
+
}
|
|
263
|
+
console.log(chalk.dim(` Progress: ${status.progress.passed}/${status.progress.total} stories (at crash)`));
|
|
264
|
+
console.log();
|
|
265
|
+
console.log(chalk.yellow("💡 Recovery Hints:"));
|
|
266
|
+
console.log(chalk.dim(" • Check the latest run log in runs/ directory"));
|
|
267
|
+
console.log(chalk.dim(" • Review status.json for last known state"));
|
|
268
|
+
console.log(chalk.dim(` • Re-run with: nax run -f ${featureName}`));
|
|
269
|
+
console.log();
|
|
270
|
+
}
|
|
271
|
+
} else {
|
|
272
|
+
console.log(chalk.dim("No active run (status.json not found)\n"));
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
// Display story counts
|
|
276
|
+
console.log(chalk.bold("Progress:"));
|
|
277
|
+
console.log(chalk.dim(` Branch: ${prd.branchName}`));
|
|
278
|
+
console.log(chalk.dim(` Updated: ${prd.updatedAt}`));
|
|
279
|
+
console.log(chalk.dim(` Total: ${counts.total}`));
|
|
280
|
+
console.log(chalk.green(` Passed: ${counts.passed}`));
|
|
281
|
+
console.log(chalk.red(` Failed: ${counts.failed}`));
|
|
282
|
+
console.log(chalk.dim(` Pending: ${counts.pending}`));
|
|
283
|
+
if (counts.skipped > 0) {
|
|
284
|
+
console.log(chalk.yellow(` Skipped: ${counts.skipped}`));
|
|
285
|
+
}
|
|
286
|
+
console.log();
|
|
287
|
+
|
|
288
|
+
// Display story table
|
|
289
|
+
console.log(chalk.bold("Stories:\n"));
|
|
290
|
+
for (const story of prd.userStories) {
|
|
291
|
+
const icon = story.passes ? "✅" : story.status === "failed" ? "❌" : story.status === "skipped" ? "⏭️" : "⬜";
|
|
292
|
+
const routing = story.routing
|
|
293
|
+
? chalk.dim(` [${story.routing.complexity}/${story.routing.modelTier}/${story.routing.testStrategy}]`)
|
|
294
|
+
: "";
|
|
295
|
+
console.log(` ${icon} ${story.id}: ${story.title}${routing}`);
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
console.log();
|
|
299
|
+
|
|
300
|
+
// Display last run info if completed
|
|
301
|
+
if (status && status.run.status !== "running") {
|
|
302
|
+
console.log(chalk.dim(`Last run: ${status.run.id}`));
|
|
303
|
+
console.log(chalk.dim(`Cost: $${status.cost.spent.toFixed(4)}`));
|
|
304
|
+
console.log();
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
/**
|
|
309
|
+
* Display feature status (all features table or single feature details)
|
|
310
|
+
*
|
|
311
|
+
* @param options - Command options
|
|
312
|
+
*
|
|
313
|
+
* @example
|
|
314
|
+
* ```bash
|
|
315
|
+
* # Show all features
|
|
316
|
+
* nax status
|
|
317
|
+
*
|
|
318
|
+
* # Show single feature details
|
|
319
|
+
* nax status -f structured-logging
|
|
320
|
+
* ```
|
|
321
|
+
*/
|
|
322
|
+
export async function displayFeatureStatus(options: FeatureStatusOptions = {}): Promise<void> {
|
|
323
|
+
const resolved = resolveProject({
|
|
324
|
+
dir: options.dir,
|
|
325
|
+
feature: options.feature,
|
|
326
|
+
});
|
|
327
|
+
|
|
328
|
+
if (options.feature) {
|
|
329
|
+
// Single feature view
|
|
330
|
+
if (!resolved.featureDir) {
|
|
331
|
+
throw new Error("Feature directory not resolved (this should not happen)");
|
|
332
|
+
}
|
|
333
|
+
await displayFeatureDetails(options.feature, resolved.featureDir);
|
|
334
|
+
} else {
|
|
335
|
+
// All features table
|
|
336
|
+
await displayAllFeatures(resolved.projectDir);
|
|
337
|
+
}
|
|
338
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Status CLI Command
|
|
3
|
+
*
|
|
4
|
+
* Re-export barrel for backward compatibility.
|
|
5
|
+
* Cost metrics: ./status-cost
|
|
6
|
+
* Feature display: ./status-features
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
// Cost metrics
|
|
10
|
+
export { displayCostMetrics, displayLastRunMetrics, displayModelEfficiency } from "./status-cost";
|
|
11
|
+
|
|
12
|
+
// Feature display
|
|
13
|
+
export { displayFeatureStatus, type FeatureStatusOptions } from "./status-features";
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Common utilities for CLI commands
|
|
3
|
+
*
|
|
4
|
+
* Provides project resolution logic shared across status, logs, and other commands.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { existsSync, readdirSync, realpathSync } from "node:fs";
|
|
8
|
+
import { join, resolve } from "node:path";
|
|
9
|
+
import { MAX_DIRECTORY_DEPTH } from "../config/path-security";
|
|
10
|
+
import { NaxError } from "../errors";
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Options for project resolution
|
|
14
|
+
*/
|
|
15
|
+
export interface ResolveProjectOptions {
|
|
16
|
+
/** Explicit project directory (from -d flag) */
|
|
17
|
+
dir?: string;
|
|
18
|
+
/** Feature name (from -f flag) */
|
|
19
|
+
feature?: string;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Resolved project paths
|
|
24
|
+
*/
|
|
25
|
+
export interface ResolvedProject {
|
|
26
|
+
/** Absolute path to project root directory */
|
|
27
|
+
projectDir: string;
|
|
28
|
+
/** Absolute path to nax config file */
|
|
29
|
+
configPath: string;
|
|
30
|
+
/** Absolute path to feature directory (if feature specified) */
|
|
31
|
+
featureDir?: string;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Resolves project directory using the following priority:
|
|
36
|
+
* 1. Explicit -d flag path
|
|
37
|
+
* 2. Current working directory (if it contains nax/ directory)
|
|
38
|
+
* 3. Walk up directory tree to find nax/ (up to MAX_DIRECTORY_DEPTH)
|
|
39
|
+
*
|
|
40
|
+
* Validates:
|
|
41
|
+
* - nax/ directory exists
|
|
42
|
+
* - nax/config.json exists
|
|
43
|
+
* - nax/features/<name>/ exists (if feature specified)
|
|
44
|
+
*
|
|
45
|
+
* @param options - Resolution options (dir, feature)
|
|
46
|
+
* @returns Resolved project paths
|
|
47
|
+
* @throws {NaxError} If project cannot be resolved or validation fails
|
|
48
|
+
*/
|
|
49
|
+
export function resolveProject(options: ResolveProjectOptions = {}): ResolvedProject {
|
|
50
|
+
const { dir, feature } = options;
|
|
51
|
+
|
|
52
|
+
// Step 1: Determine project root and validate structure
|
|
53
|
+
let projectRoot: string;
|
|
54
|
+
let naxDir: string;
|
|
55
|
+
let configPath: string;
|
|
56
|
+
|
|
57
|
+
if (dir) {
|
|
58
|
+
// Use explicit -d flag path (resolve relative paths and symlinks)
|
|
59
|
+
projectRoot = realpathSync(resolve(dir));
|
|
60
|
+
naxDir = join(projectRoot, "nax");
|
|
61
|
+
|
|
62
|
+
// Validate nax/ directory exists
|
|
63
|
+
if (!existsSync(naxDir)) {
|
|
64
|
+
throw new NaxError(
|
|
65
|
+
`Directory does not contain a nax project: ${projectRoot}\nExpected to find: ${naxDir}`,
|
|
66
|
+
"NAX_DIR_NOT_FOUND",
|
|
67
|
+
{ projectRoot, naxDir },
|
|
68
|
+
);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// Validate nax/config.json exists
|
|
72
|
+
configPath = join(naxDir, "config.json");
|
|
73
|
+
if (!existsSync(configPath)) {
|
|
74
|
+
throw new NaxError(
|
|
75
|
+
`nax directory found but config.json is missing: ${naxDir}\nExpected to find: ${configPath}`,
|
|
76
|
+
"CONFIG_NOT_FOUND",
|
|
77
|
+
{ naxDir, configPath },
|
|
78
|
+
);
|
|
79
|
+
}
|
|
80
|
+
} else {
|
|
81
|
+
// Walk up from CWD to find nax/ directory with config.json
|
|
82
|
+
const found = findProjectRoot(process.cwd());
|
|
83
|
+
if (!found) {
|
|
84
|
+
// Check if CWD has nax/ but missing config.json (for better error message)
|
|
85
|
+
const cwdNaxDir = join(process.cwd(), "nax");
|
|
86
|
+
if (existsSync(cwdNaxDir)) {
|
|
87
|
+
const cwdConfigPath = join(cwdNaxDir, "config.json");
|
|
88
|
+
throw new NaxError(
|
|
89
|
+
`nax directory found but config.json is missing: ${cwdNaxDir}\nExpected to find: ${cwdConfigPath}`,
|
|
90
|
+
"CONFIG_NOT_FOUND",
|
|
91
|
+
{ naxDir: cwdNaxDir, configPath: cwdConfigPath },
|
|
92
|
+
);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
throw new NaxError(
|
|
96
|
+
"No nax project found. Run this command from within a nax project directory, or use -d flag to specify the project path.",
|
|
97
|
+
"PROJECT_NOT_FOUND",
|
|
98
|
+
{ cwd: process.cwd() },
|
|
99
|
+
);
|
|
100
|
+
}
|
|
101
|
+
projectRoot = found;
|
|
102
|
+
naxDir = join(projectRoot, "nax");
|
|
103
|
+
configPath = join(naxDir, "config.json");
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// Step 4: Validate feature directory (if specified)
|
|
107
|
+
let featureDir: string | undefined;
|
|
108
|
+
if (feature) {
|
|
109
|
+
const featuresDir = join(naxDir, "features");
|
|
110
|
+
featureDir = join(featuresDir, feature);
|
|
111
|
+
|
|
112
|
+
if (!existsSync(featureDir)) {
|
|
113
|
+
// List available features for helpful error message
|
|
114
|
+
const availableFeatures = existsSync(featuresDir)
|
|
115
|
+
? readdirSync(featuresDir, { withFileTypes: true })
|
|
116
|
+
.filter((entry) => entry.isDirectory())
|
|
117
|
+
.map((entry) => entry.name)
|
|
118
|
+
: [];
|
|
119
|
+
|
|
120
|
+
const availableMsg =
|
|
121
|
+
availableFeatures.length > 0
|
|
122
|
+
? `\n\nAvailable features:\n${availableFeatures.map((f) => ` - ${f}`).join("\n")}`
|
|
123
|
+
: "\n\nNo features found in this project.";
|
|
124
|
+
|
|
125
|
+
throw new NaxError(`Feature not found: ${feature}${availableMsg}`, "FEATURE_NOT_FOUND", {
|
|
126
|
+
feature,
|
|
127
|
+
featuresDir,
|
|
128
|
+
availableFeatures,
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
return {
|
|
134
|
+
projectDir: projectRoot,
|
|
135
|
+
configPath,
|
|
136
|
+
featureDir,
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Walks up directory tree to find a nax/ directory with config.json.
|
|
142
|
+
* Stops at filesystem root or MAX_DIRECTORY_DEPTH.
|
|
143
|
+
*
|
|
144
|
+
* @param startDir - Starting directory (typically CWD)
|
|
145
|
+
* @returns Absolute path to project root (with symlinks resolved), or null if not found
|
|
146
|
+
*/
|
|
147
|
+
function findProjectRoot(startDir: string): string | null {
|
|
148
|
+
let current = resolve(startDir);
|
|
149
|
+
let depth = 0;
|
|
150
|
+
|
|
151
|
+
while (depth < MAX_DIRECTORY_DEPTH) {
|
|
152
|
+
const naxDir = join(current, "nax");
|
|
153
|
+
const configPath = join(naxDir, "config.json");
|
|
154
|
+
|
|
155
|
+
if (existsSync(configPath)) {
|
|
156
|
+
// Resolve symlinks for consistent path comparison
|
|
157
|
+
return realpathSync(current);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
const parent = join(current, "..");
|
|
161
|
+
if (parent === current) {
|
|
162
|
+
// Reached filesystem root
|
|
163
|
+
break;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
current = parent;
|
|
167
|
+
depth++;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
return null;
|
|
171
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Diagnose command wrapper
|
|
3
|
+
*
|
|
4
|
+
* Thin commander.js wrapper for diagnose CLI command.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { diagnoseCommand } from "../cli/diagnose";
|
|
8
|
+
import type { DiagnoseOptions } from "../cli/diagnose";
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Execute diagnose command with commander options
|
|
12
|
+
*
|
|
13
|
+
* @param options - Command options from commander
|
|
14
|
+
*/
|
|
15
|
+
export async function diagnose(options: DiagnoseOptions): Promise<void> {
|
|
16
|
+
await diagnoseCommand(options);
|
|
17
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Common utilities for CLI commands
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
export { resolveProject, type ResolveProjectOptions, type ResolvedProject } from "./common";
|
|
6
|
+
export { logsCommand, type LogsOptions } from "./logs";
|
|
7
|
+
export { precheckCommand, type PrecheckOptions } from "./precheck";
|
|
8
|
+
export { unlockCommand, type UnlockOptions } from "./unlock";
|