@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,159 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Diagnose Analysis
|
|
3
|
+
*
|
|
4
|
+
* Extracted from diagnose.ts: failure pattern detection, symptom descriptions,
|
|
5
|
+
* fix suggestions, and recommendation generation.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type { NaxStatusFile } from "../execution/status-file";
|
|
9
|
+
import type { UserStory } from "../prd";
|
|
10
|
+
import type { PRD } from "../prd/types";
|
|
11
|
+
import type { DiagnosisReport, FailurePattern, StoryDiagnosis } from "./diagnose";
|
|
12
|
+
|
|
13
|
+
/** Detect failure pattern for a story */
|
|
14
|
+
export function detectFailurePattern(story: UserStory, prd: PRD, status: NaxStatusFile | null): FailurePattern {
|
|
15
|
+
if (
|
|
16
|
+
story.status === "passed" &&
|
|
17
|
+
story.priorErrors?.some((err) => err.toLowerCase().includes("greenfield-no-tests"))
|
|
18
|
+
) {
|
|
19
|
+
return "AUTO_RECOVERED";
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
if (story.status !== "failed" && story.status !== "blocked" && story.status !== "paused") {
|
|
23
|
+
return "UNKNOWN";
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
if (
|
|
27
|
+
story.failureCategory === "greenfield-no-tests" ||
|
|
28
|
+
story.priorErrors?.some((err) => err.toLowerCase().includes("greenfield-no-tests"))
|
|
29
|
+
) {
|
|
30
|
+
return "GREENFIELD_TDD";
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const testFailingCount = story.priorErrors?.filter((err) => err.toLowerCase().includes("tests-failing")).length || 0;
|
|
34
|
+
if (testFailingCount >= 2) return "TEST_MISMATCH";
|
|
35
|
+
|
|
36
|
+
if (
|
|
37
|
+
story.priorErrors?.some((err) => err.toLowerCase().includes("precheck-failed")) ||
|
|
38
|
+
(status?.progress.blocked ?? 0) > 0
|
|
39
|
+
) {
|
|
40
|
+
return "ENVIRONMENTAL";
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
if (story.priorErrors?.some((err) => err.toLowerCase().includes("ratelimited"))) return "RATE_LIMITED";
|
|
44
|
+
if (story.failureCategory === "isolation-violation") return "ISOLATION_VIOLATION";
|
|
45
|
+
if (status?.run.status === "stalled") return "STALLED";
|
|
46
|
+
if (story.priorErrors?.some((err) => err.toLowerCase().includes("session-failure"))) return "SESSION_CRASH";
|
|
47
|
+
if (story.attempts > 3) return "MAX_TIERS_EXHAUSTED";
|
|
48
|
+
|
|
49
|
+
return "UNKNOWN";
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/** Get symptom description for a pattern */
|
|
53
|
+
export function getPatternSymptom(pattern: FailurePattern): string {
|
|
54
|
+
const symptoms: Record<FailurePattern, string> = {
|
|
55
|
+
GREENFIELD_TDD: "Story attempted in greenfield project with no existing tests",
|
|
56
|
+
TEST_MISMATCH: "Multiple test failures across attempts",
|
|
57
|
+
ENVIRONMENTAL: "Environment prechecks failed or blockers detected",
|
|
58
|
+
RATE_LIMITED: "API rate limit exceeded",
|
|
59
|
+
ISOLATION_VIOLATION: "Story modified files outside its scope",
|
|
60
|
+
MAX_TIERS_EXHAUSTED: "Story attempted at all configured model tiers without success",
|
|
61
|
+
SESSION_CRASH: "Agent session crashed without producing commits",
|
|
62
|
+
STALLED: "All stories blocked or paused -- no forward progress possible",
|
|
63
|
+
LOCK_STALE: "Lock file present but process is dead",
|
|
64
|
+
AUTO_RECOVERED: "Greenfield issue detected but S5 auto-recovery succeeded",
|
|
65
|
+
UNKNOWN: "Unknown failure pattern",
|
|
66
|
+
};
|
|
67
|
+
return symptoms[pattern] ?? "Unknown failure pattern";
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/** Get fix suggestion for a pattern */
|
|
71
|
+
export function getPatternFixSuggestion(pattern: FailurePattern, _story: UserStory): string {
|
|
72
|
+
const fixes: Record<FailurePattern, string> = {
|
|
73
|
+
GREENFIELD_TDD: "Add --greenfield flag or bootstrap with scaffolding tests first",
|
|
74
|
+
TEST_MISMATCH: "Review acceptance criteria; tests may be too strict or story underspecified",
|
|
75
|
+
ENVIRONMENTAL: "Fix precheck issues (deps, env, build) before re-running",
|
|
76
|
+
RATE_LIMITED: "Wait for rate limit to reset or increase tier limits",
|
|
77
|
+
ISOLATION_VIOLATION: "Narrow story scope or adjust expectedFiles to allow cross-file changes",
|
|
78
|
+
MAX_TIERS_EXHAUSTED: "Simplify story or split into smaller sub-stories",
|
|
79
|
+
SESSION_CRASH: "Check agent logs for crash details; may need manual intervention",
|
|
80
|
+
STALLED: "Resolve blocked stories or skip them to unblock dependencies",
|
|
81
|
+
LOCK_STALE: "Run: rm nax.lock",
|
|
82
|
+
AUTO_RECOVERED: "No action needed -- S5 successfully handled greenfield scenario",
|
|
83
|
+
UNKNOWN: "Review logs and prior errors for clues",
|
|
84
|
+
};
|
|
85
|
+
return fixes[pattern] ?? "Review logs and prior errors for clues";
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/** Generate recommendations based on diagnosis */
|
|
89
|
+
export function generateRecommendations(report: DiagnosisReport): string[] {
|
|
90
|
+
const recommendations: string[] = [];
|
|
91
|
+
|
|
92
|
+
if (report.lockCheck.lockPresent && report.lockCheck.pidAlive === false) {
|
|
93
|
+
recommendations.push(`Remove stale lock: ${report.lockCheck.fixCommand}`);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const criticalPatterns = report.failureAnalysis.filter((f) =>
|
|
97
|
+
["ENVIRONMENTAL", "STALLED", "SESSION_CRASH"].includes(f.pattern),
|
|
98
|
+
);
|
|
99
|
+
if (criticalPatterns.length > 0) {
|
|
100
|
+
recommendations.push(
|
|
101
|
+
`Fix ${criticalPatterns.length} critical blocker(s): ${criticalPatterns.map((f) => f.storyId).join(", ")}`,
|
|
102
|
+
);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
if (report.failureAnalysis.some((f) => f.pattern === "RATE_LIMITED")) {
|
|
106
|
+
recommendations.push("Wait for rate limits to reset before re-running");
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
if (report.failureAnalysis.some((f) => f.pattern === "GREENFIELD_TDD")) {
|
|
110
|
+
recommendations.push("Consider adding --greenfield flag or bootstrap tests for greenfield stories");
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
if (report.runSummary.storiesFailed > 0 && recommendations.length === 0) {
|
|
114
|
+
recommendations.push(`Re-run with: nax run -f ${report.runSummary.feature}`);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
if (report.runSummary.storiesFailed === 0 && report.runSummary.storiesPending === 0) {
|
|
118
|
+
recommendations.push("All stories passed -- feature is complete!");
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
return recommendations;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/** Diagnose each story and build story breakdown + failure analysis lists */
|
|
125
|
+
export function diagnoseStories(
|
|
126
|
+
prd: PRD,
|
|
127
|
+
status: NaxStatusFile | null,
|
|
128
|
+
): { storyBreakdown: StoryDiagnosis[]; failureAnalysis: StoryDiagnosis[] } {
|
|
129
|
+
const storyBreakdown: StoryDiagnosis[] = [];
|
|
130
|
+
const failureAnalysis: StoryDiagnosis[] = [];
|
|
131
|
+
|
|
132
|
+
for (const story of prd.userStories) {
|
|
133
|
+
const pattern = detectFailurePattern(story, prd, status);
|
|
134
|
+
const diagnosis: StoryDiagnosis = {
|
|
135
|
+
storyId: story.id,
|
|
136
|
+
title: story.title,
|
|
137
|
+
status: story.status,
|
|
138
|
+
attempts: story.attempts,
|
|
139
|
+
tier: story.routing?.modelTier,
|
|
140
|
+
strategy: story.routing?.testStrategy,
|
|
141
|
+
pattern,
|
|
142
|
+
};
|
|
143
|
+
|
|
144
|
+
storyBreakdown.push(diagnosis);
|
|
145
|
+
|
|
146
|
+
if (
|
|
147
|
+
story.status === "failed" ||
|
|
148
|
+
story.status === "blocked" ||
|
|
149
|
+
story.status === "paused" ||
|
|
150
|
+
pattern === "AUTO_RECOVERED"
|
|
151
|
+
) {
|
|
152
|
+
diagnosis.symptom = getPatternSymptom(pattern);
|
|
153
|
+
diagnosis.fixSuggestion = getPatternFixSuggestion(pattern, story);
|
|
154
|
+
failureAnalysis.push(diagnosis);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
return { storyBreakdown, failureAnalysis };
|
|
159
|
+
}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Diagnose Report Formatter
|
|
3
|
+
*
|
|
4
|
+
* Extracted from diagnose.ts: human-readable output formatting for the diagnosis report.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import chalk from "chalk";
|
|
8
|
+
import type { DiagnosisReport } from "./diagnose";
|
|
9
|
+
|
|
10
|
+
/** Format diagnosis report as human-readable text */
|
|
11
|
+
export function formatReport(report: DiagnosisReport, verbose: boolean): string {
|
|
12
|
+
const lines: string[] = [];
|
|
13
|
+
|
|
14
|
+
lines.push(chalk.bold(`\nDiagnosis Report: ${report.runSummary.feature}\n`));
|
|
15
|
+
|
|
16
|
+
if (!report.dataSources.eventsFound) {
|
|
17
|
+
lines.push(chalk.yellow("[WARN] events.jsonl not found -- diagnosis limited to PRD + git log\n"));
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// Run Summary
|
|
21
|
+
lines.push(chalk.bold("Run Summary\n"));
|
|
22
|
+
if (report.runSummary.lastRunTime) {
|
|
23
|
+
lines.push(chalk.dim(` Last Run: ${report.runSummary.lastRunTime}`));
|
|
24
|
+
}
|
|
25
|
+
lines.push(chalk.dim(` Status: ${report.runSummary.status}`));
|
|
26
|
+
lines.push(chalk.green(` Passed: ${report.runSummary.storiesPassed}`));
|
|
27
|
+
lines.push(chalk.red(` Failed: ${report.runSummary.storiesFailed}`));
|
|
28
|
+
lines.push(chalk.dim(` Pending: ${report.runSummary.storiesPending}`));
|
|
29
|
+
if (report.runSummary.cost !== undefined) {
|
|
30
|
+
lines.push(chalk.dim(` Cost: $${report.runSummary.cost.toFixed(4)}`));
|
|
31
|
+
}
|
|
32
|
+
lines.push(chalk.dim(` Commits: ${report.runSummary.commitsProduced}`));
|
|
33
|
+
lines.push("");
|
|
34
|
+
|
|
35
|
+
// Story Breakdown (verbose only)
|
|
36
|
+
if (verbose && report.storyBreakdown.length > 0) {
|
|
37
|
+
lines.push(chalk.bold("Story Breakdown\n"));
|
|
38
|
+
for (const story of report.storyBreakdown) {
|
|
39
|
+
const icon = story.status === "passed" ? "[OK]" : story.status === "failed" ? "[FAIL]" : "[ ]";
|
|
40
|
+
const pattern = story.pattern !== "UNKNOWN" ? chalk.yellow(` [${story.pattern}]`) : "";
|
|
41
|
+
lines.push(` ${icon} ${story.storyId}: ${story.title}${pattern}`);
|
|
42
|
+
if (verbose && story.tier) {
|
|
43
|
+
lines.push(chalk.dim(` Tier: ${story.tier}, Strategy: ${story.strategy}, Attempts: ${story.attempts}`));
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
lines.push("");
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// Failure Analysis
|
|
50
|
+
if (report.failureAnalysis.length > 0) {
|
|
51
|
+
lines.push(chalk.bold("Failure Analysis\n"));
|
|
52
|
+
for (const failure of report.failureAnalysis) {
|
|
53
|
+
const level = failure.pattern === "AUTO_RECOVERED" ? chalk.green("INFO") : chalk.red("ERROR");
|
|
54
|
+
lines.push(` ${level} ${failure.storyId}: ${failure.title}`);
|
|
55
|
+
lines.push(chalk.dim(` Pattern: ${failure.pattern}`));
|
|
56
|
+
if (failure.symptom) {
|
|
57
|
+
lines.push(chalk.dim(` Symptom: ${failure.symptom}`));
|
|
58
|
+
}
|
|
59
|
+
if (failure.fixSuggestion) {
|
|
60
|
+
lines.push(chalk.yellow(` Fix: ${failure.fixSuggestion}`));
|
|
61
|
+
}
|
|
62
|
+
lines.push("");
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// Lock Check
|
|
67
|
+
lines.push(chalk.bold("Lock Check\n"));
|
|
68
|
+
if (!report.lockCheck.lockPresent) {
|
|
69
|
+
lines.push(chalk.dim(" No lock file present\n"));
|
|
70
|
+
} else if (report.lockCheck.pidAlive === false) {
|
|
71
|
+
lines.push(chalk.red(` [FAIL] Stale lock detected (PID ${report.lockCheck.pid} is dead)`));
|
|
72
|
+
lines.push(chalk.yellow(` Fix: ${report.lockCheck.fixCommand}\n`));
|
|
73
|
+
} else {
|
|
74
|
+
lines.push(chalk.green(` [OK] Active lock (PID ${report.lockCheck.pid})\n`));
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// Recommendations
|
|
78
|
+
if (report.recommendations.length > 0) {
|
|
79
|
+
lines.push(chalk.bold("Recommendations\n"));
|
|
80
|
+
for (let i = 0; i < report.recommendations.length; i++) {
|
|
81
|
+
lines.push(` ${i + 1}. ${report.recommendations[i]}`);
|
|
82
|
+
}
|
|
83
|
+
lines.push("");
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
return lines.join("\n");
|
|
87
|
+
}
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Diagnose Command
|
|
3
|
+
*
|
|
4
|
+
* Reads run artifacts and produces structured diagnosis report.
|
|
5
|
+
* Pure pattern matching -- no LLM calls, no agents.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { existsSync, readdirSync } from "node:fs";
|
|
9
|
+
import { join } from "node:path";
|
|
10
|
+
import { findProjectDir } from "../config";
|
|
11
|
+
import type { NaxStatusFile } from "../execution/status-file";
|
|
12
|
+
import { getLogger } from "../logger";
|
|
13
|
+
import { loadPRD } from "../prd";
|
|
14
|
+
import { diagnoseStories, generateRecommendations } from "./diagnose-analysis";
|
|
15
|
+
import { formatReport } from "./diagnose-formatter";
|
|
16
|
+
|
|
17
|
+
export interface DiagnoseOptions {
|
|
18
|
+
feature?: string;
|
|
19
|
+
workdir?: string;
|
|
20
|
+
json?: boolean;
|
|
21
|
+
verbose?: boolean;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export type FailurePattern =
|
|
25
|
+
| "GREENFIELD_TDD"
|
|
26
|
+
| "TEST_MISMATCH"
|
|
27
|
+
| "ENVIRONMENTAL"
|
|
28
|
+
| "RATE_LIMITED"
|
|
29
|
+
| "ISOLATION_VIOLATION"
|
|
30
|
+
| "MAX_TIERS_EXHAUSTED"
|
|
31
|
+
| "SESSION_CRASH"
|
|
32
|
+
| "STALLED"
|
|
33
|
+
| "LOCK_STALE"
|
|
34
|
+
| "AUTO_RECOVERED"
|
|
35
|
+
| "UNKNOWN";
|
|
36
|
+
|
|
37
|
+
export interface StoryDiagnosis {
|
|
38
|
+
storyId: string;
|
|
39
|
+
title: string;
|
|
40
|
+
status: string;
|
|
41
|
+
attempts: number;
|
|
42
|
+
tier?: string;
|
|
43
|
+
strategy?: string;
|
|
44
|
+
pattern: FailurePattern;
|
|
45
|
+
symptom?: string;
|
|
46
|
+
fixSuggestion?: string;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export interface LockCheck {
|
|
50
|
+
lockPresent: boolean;
|
|
51
|
+
pidAlive?: boolean;
|
|
52
|
+
pid?: number;
|
|
53
|
+
fixCommand?: string;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export interface DiagnosisReport {
|
|
57
|
+
runSummary: {
|
|
58
|
+
feature: string;
|
|
59
|
+
lastRunTime?: string;
|
|
60
|
+
status: string;
|
|
61
|
+
storiesPassed: number;
|
|
62
|
+
storiesFailed: number;
|
|
63
|
+
storiesPending: number;
|
|
64
|
+
cost?: number;
|
|
65
|
+
commitsProduced: number;
|
|
66
|
+
};
|
|
67
|
+
storyBreakdown: StoryDiagnosis[];
|
|
68
|
+
failureAnalysis: StoryDiagnosis[];
|
|
69
|
+
lockCheck: LockCheck;
|
|
70
|
+
recommendations: string[];
|
|
71
|
+
dataSources: {
|
|
72
|
+
prdFound: boolean;
|
|
73
|
+
statusFound: boolean;
|
|
74
|
+
eventsFound: boolean;
|
|
75
|
+
gitLogFound: boolean;
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function isProcessAlive(pid: number): boolean {
|
|
80
|
+
try {
|
|
81
|
+
const result = Bun.spawnSync(["ps", "-p", String(pid)], { stdout: "ignore", stderr: "ignore" });
|
|
82
|
+
return result.exitCode === 0;
|
|
83
|
+
} catch {
|
|
84
|
+
return false;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
async function loadStatusFile(workdir: string): Promise<NaxStatusFile | null> {
|
|
89
|
+
const statusPath = join(workdir, ".nax-status.json");
|
|
90
|
+
if (!existsSync(statusPath)) return null;
|
|
91
|
+
try {
|
|
92
|
+
return (await Bun.file(statusPath).json()) as NaxStatusFile;
|
|
93
|
+
} catch {
|
|
94
|
+
return null;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
async function countCommitsSince(workdir: string, since?: string): Promise<number> {
|
|
99
|
+
if (!since) return 0;
|
|
100
|
+
try {
|
|
101
|
+
const result = Bun.spawnSync(["git", "log", "--oneline", `--since=${since}`, "--all"], {
|
|
102
|
+
cwd: workdir,
|
|
103
|
+
stdout: "pipe",
|
|
104
|
+
stderr: "ignore",
|
|
105
|
+
});
|
|
106
|
+
if (result.exitCode !== 0) return 0;
|
|
107
|
+
const output = new TextDecoder().decode(result.stdout);
|
|
108
|
+
return output
|
|
109
|
+
.trim()
|
|
110
|
+
.split("\n")
|
|
111
|
+
.filter((line) => line.length > 0).length;
|
|
112
|
+
} catch {
|
|
113
|
+
return 0;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
async function checkLock(workdir: string): Promise<LockCheck> {
|
|
118
|
+
const lockFile = Bun.file(join(workdir, "nax.lock"));
|
|
119
|
+
if (!(await lockFile.exists())) return { lockPresent: false };
|
|
120
|
+
try {
|
|
121
|
+
const lockData = JSON.parse(await lockFile.text());
|
|
122
|
+
const pid = lockData.pid;
|
|
123
|
+
const pidAlive = isProcessAlive(pid);
|
|
124
|
+
if (!pidAlive) return { lockPresent: true, pidAlive: false, pid, fixCommand: "rm nax.lock" };
|
|
125
|
+
return { lockPresent: true, pidAlive: true, pid };
|
|
126
|
+
} catch {
|
|
127
|
+
return { lockPresent: true };
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/** Run diagnose command */
|
|
132
|
+
export async function diagnoseCommand(options: DiagnoseOptions = {}): Promise<void> {
|
|
133
|
+
const logger = getLogger();
|
|
134
|
+
const workdir = options.workdir ?? process.cwd();
|
|
135
|
+
|
|
136
|
+
const naxSubdir = findProjectDir(workdir);
|
|
137
|
+
let projectDir: string | null = naxSubdir ? join(naxSubdir, "..") : null;
|
|
138
|
+
if (!projectDir && existsSync(join(workdir, "nax"))) {
|
|
139
|
+
projectDir = workdir;
|
|
140
|
+
}
|
|
141
|
+
if (!projectDir) throw new Error("Not in a nax project directory");
|
|
142
|
+
|
|
143
|
+
let feature = options.feature;
|
|
144
|
+
if (!feature) {
|
|
145
|
+
const status = await loadStatusFile(projectDir);
|
|
146
|
+
if (status) {
|
|
147
|
+
feature = status.run.feature;
|
|
148
|
+
} else {
|
|
149
|
+
const featuresDir = join(projectDir, "nax", "features");
|
|
150
|
+
if (!existsSync(featuresDir)) throw new Error("No features found in project");
|
|
151
|
+
const features = readdirSync(featuresDir, { withFileTypes: true })
|
|
152
|
+
.filter((e) => e.isDirectory())
|
|
153
|
+
.map((e) => e.name);
|
|
154
|
+
if (features.length === 0) throw new Error("No features found");
|
|
155
|
+
feature = features[0];
|
|
156
|
+
logger.info("diagnose", "No feature specified, using first found", { feature });
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
const featureDir = join(projectDir, "nax", "features", feature);
|
|
161
|
+
const prdPath = join(featureDir, "prd.json");
|
|
162
|
+
if (!existsSync(prdPath)) throw new Error(`Feature not found: ${feature}`);
|
|
163
|
+
|
|
164
|
+
const prd = await loadPRD(prdPath);
|
|
165
|
+
const status = await loadStatusFile(projectDir);
|
|
166
|
+
const lockCheck = await checkLock(projectDir);
|
|
167
|
+
const commitCount = await countCommitsSince(projectDir, status?.run.startedAt);
|
|
168
|
+
|
|
169
|
+
const { storyBreakdown, failureAnalysis } = diagnoseStories(prd, status);
|
|
170
|
+
|
|
171
|
+
const report: DiagnosisReport = {
|
|
172
|
+
runSummary: {
|
|
173
|
+
feature,
|
|
174
|
+
lastRunTime: status?.run.startedAt,
|
|
175
|
+
status: status?.run.status ?? "unknown",
|
|
176
|
+
storiesPassed: prd.userStories.filter((s) => s.status === "passed").length,
|
|
177
|
+
storiesFailed: prd.userStories.filter((s) => s.status === "failed").length,
|
|
178
|
+
storiesPending: prd.userStories.filter(
|
|
179
|
+
(s) => s.status !== "passed" && s.status !== "failed" && s.status !== "skipped",
|
|
180
|
+
).length,
|
|
181
|
+
cost: status?.cost.spent,
|
|
182
|
+
commitsProduced: commitCount,
|
|
183
|
+
},
|
|
184
|
+
storyBreakdown,
|
|
185
|
+
failureAnalysis,
|
|
186
|
+
lockCheck,
|
|
187
|
+
recommendations: [],
|
|
188
|
+
dataSources: {
|
|
189
|
+
prdFound: true,
|
|
190
|
+
statusFound: status !== null,
|
|
191
|
+
eventsFound: false,
|
|
192
|
+
gitLogFound: commitCount > 0,
|
|
193
|
+
},
|
|
194
|
+
};
|
|
195
|
+
|
|
196
|
+
report.recommendations = generateRecommendations(report);
|
|
197
|
+
|
|
198
|
+
if (options.json) {
|
|
199
|
+
console.log(JSON.stringify(report, null, 2));
|
|
200
|
+
} else {
|
|
201
|
+
console.log(formatReport(report, options.verbose ?? false));
|
|
202
|
+
}
|
|
203
|
+
}
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `nax generate` CLI Command (v0.16.1)
|
|
3
|
+
*
|
|
4
|
+
* Generates agent-specific config files from nax/context.md + auto-injected project metadata.
|
|
5
|
+
* Replaces `nax constitution generate`.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { existsSync } from "node:fs";
|
|
9
|
+
import { join } from "node:path";
|
|
10
|
+
import chalk from "chalk";
|
|
11
|
+
import { loadConfig } from "../config/loader";
|
|
12
|
+
import { generateAll, generateFor } from "../context/generator";
|
|
13
|
+
import type { AgentType } from "../context/types";
|
|
14
|
+
|
|
15
|
+
/** Options for `nax generate` */
|
|
16
|
+
export interface GenerateCommandOptions {
|
|
17
|
+
/** Path to context file (default: nax/context.md) */
|
|
18
|
+
context?: string;
|
|
19
|
+
/** Output directory (default: project root) */
|
|
20
|
+
output?: string;
|
|
21
|
+
/** Specific agent to generate for */
|
|
22
|
+
agent?: string;
|
|
23
|
+
/** Dry run — preview without writing */
|
|
24
|
+
dryRun?: boolean;
|
|
25
|
+
/** Disable auto-injection of project metadata */
|
|
26
|
+
noAutoInject?: boolean;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const VALID_AGENTS: AgentType[] = ["claude", "opencode", "cursor", "windsurf", "aider"];
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* `nax generate` command handler.
|
|
33
|
+
*/
|
|
34
|
+
export async function generateCommand(options: GenerateCommandOptions): Promise<void> {
|
|
35
|
+
const workdir = process.cwd();
|
|
36
|
+
const contextPath = options.context ? join(workdir, options.context) : join(workdir, "nax/context.md");
|
|
37
|
+
const outputDir = options.output ? join(workdir, options.output) : workdir;
|
|
38
|
+
const autoInject = !options.noAutoInject;
|
|
39
|
+
const dryRun = options.dryRun ?? false;
|
|
40
|
+
|
|
41
|
+
// Validate context file
|
|
42
|
+
if (!existsSync(contextPath)) {
|
|
43
|
+
console.error(chalk.red(`✗ Context file not found: ${contextPath}`));
|
|
44
|
+
console.error(chalk.yellow(" Create nax/context.md first, or run `nax init` to scaffold it."));
|
|
45
|
+
process.exit(1);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// Validate agent if specified
|
|
49
|
+
if (options.agent && !VALID_AGENTS.includes(options.agent as AgentType)) {
|
|
50
|
+
console.error(chalk.red(`✗ Unknown agent: ${options.agent}`));
|
|
51
|
+
console.error(chalk.yellow(` Valid agents: ${VALID_AGENTS.join(", ")}`));
|
|
52
|
+
process.exit(1);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
if (dryRun) {
|
|
56
|
+
console.log(chalk.yellow("⚠ Dry run — no files will be written"));
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
console.log(chalk.blue(`→ Loading context from ${contextPath}`));
|
|
60
|
+
if (autoInject) {
|
|
61
|
+
console.log(chalk.dim(" Auto-injecting project metadata..."));
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// Load config for metadata injection (best-effort, use defaults if not found)
|
|
65
|
+
let config: Awaited<ReturnType<typeof loadConfig>>;
|
|
66
|
+
try {
|
|
67
|
+
config = await loadConfig(workdir);
|
|
68
|
+
} catch {
|
|
69
|
+
// Config not required for generate — use empty defaults
|
|
70
|
+
config = {} as Awaited<ReturnType<typeof loadConfig>>;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const genOptions = {
|
|
74
|
+
contextPath,
|
|
75
|
+
outputDir,
|
|
76
|
+
workdir,
|
|
77
|
+
dryRun,
|
|
78
|
+
autoInject,
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
try {
|
|
82
|
+
if (options.agent) {
|
|
83
|
+
const agent = options.agent as AgentType;
|
|
84
|
+
console.log(chalk.blue(`→ Generating config for ${agent}...`));
|
|
85
|
+
|
|
86
|
+
const result = await generateFor(agent, genOptions, config);
|
|
87
|
+
|
|
88
|
+
if (result.error) {
|
|
89
|
+
console.error(chalk.red(`✗ ${agent}: ${result.error}`));
|
|
90
|
+
process.exit(1);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const suffix = dryRun ? " (dry run)" : "";
|
|
94
|
+
console.log(chalk.green(`✓ ${agent} → ${result.outputFile} (${result.content.length} bytes${suffix})`));
|
|
95
|
+
} else {
|
|
96
|
+
console.log(chalk.blue("→ Generating configs for all agents..."));
|
|
97
|
+
|
|
98
|
+
const results = await generateAll(genOptions, config);
|
|
99
|
+
let errorCount = 0;
|
|
100
|
+
|
|
101
|
+
for (const result of results) {
|
|
102
|
+
if (result.error) {
|
|
103
|
+
console.error(chalk.red(`✗ ${result.agent}: ${result.error}`));
|
|
104
|
+
errorCount++;
|
|
105
|
+
} else {
|
|
106
|
+
const suffix = dryRun ? " (dry run)" : "";
|
|
107
|
+
console.log(
|
|
108
|
+
chalk.green(`✓ ${result.agent} → ${result.outputFile} (${result.content.length} bytes${suffix})`),
|
|
109
|
+
);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
if (errorCount > 0) {
|
|
114
|
+
console.error(chalk.red(`\n✗ ${errorCount} generation(s) failed`));
|
|
115
|
+
process.exit(1);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
if (!dryRun) {
|
|
120
|
+
console.log(chalk.green(`\n✓ Agent configs written to ${outputDir}`));
|
|
121
|
+
}
|
|
122
|
+
} catch (err) {
|
|
123
|
+
const error = err instanceof Error ? err.message : String(err);
|
|
124
|
+
console.error(chalk.red(`✗ Generation failed: ${error}`));
|
|
125
|
+
process.exit(1);
|
|
126
|
+
}
|
|
127
|
+
}
|
package/src/cli/index.ts
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CLI Utilities
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
export { analyzeFeature } from "./analyze";
|
|
6
|
+
export { planCommand } from "./plan";
|
|
7
|
+
export { acceptCommand, type AcceptOptions } from "./accept";
|
|
8
|
+
export {
|
|
9
|
+
displayCostMetrics,
|
|
10
|
+
displayLastRunMetrics,
|
|
11
|
+
displayModelEfficiency,
|
|
12
|
+
displayFeatureStatus,
|
|
13
|
+
type FeatureStatusOptions,
|
|
14
|
+
} from "./status";
|
|
15
|
+
export {
|
|
16
|
+
runsListCommand,
|
|
17
|
+
runsShowCommand,
|
|
18
|
+
type RunsListOptions,
|
|
19
|
+
type RunsShowOptions,
|
|
20
|
+
} from "./runs";
|
|
21
|
+
export {
|
|
22
|
+
promptsCommand,
|
|
23
|
+
type PromptsCommandOptions,
|
|
24
|
+
} from "./prompts";
|
|
25
|
+
export { initCommand, type InitOptions } from "./init";
|
|
26
|
+
export { pluginsListCommand } from "./plugins";
|
|
27
|
+
export { diagnoseCommand, type DiagnoseOptions } from "./diagnose";
|
|
28
|
+
export {
|
|
29
|
+
interactListCommand,
|
|
30
|
+
interactRespondCommand,
|
|
31
|
+
interactCancelCommand,
|
|
32
|
+
type InteractListOptions,
|
|
33
|
+
type InteractRespondOptions,
|
|
34
|
+
type InteractCancelOptions,
|
|
35
|
+
} from "./interact";
|
|
36
|
+
export { generateCommand, type GenerateCommandOptions } from "./generate";
|
|
37
|
+
export { configCommand, type ConfigCommandOptions } from "./config";
|