@nathapp/nax 0.18.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.gitlab-ci.yml +96 -0
- package/BRIEF.md +140 -0
- package/CHANGELOG.md +60 -0
- package/CLAUDE.md +159 -0
- package/README.md +373 -0
- package/US-007-IMPLEMENTATION.md +139 -0
- package/bin/nax.ts +930 -0
- package/biome.json +14 -0
- package/bun.lock +168 -0
- package/bunfig.toml +11 -0
- package/docs/20260216-fix-plan-context-review.md +56 -0
- package/docs/20260216-relentless-vs-ngent-comparison.md +208 -0
- package/docs/20260216-v02-plan.md +136 -0
- package/docs/20260216-v02-review.md +685 -0
- package/docs/20260217-dogfood-findings.md +56 -0
- package/docs/20260217-p2-plus-plan.md +117 -0
- package/docs/20260217-partial-fixes-plan.md +62 -0
- package/docs/20260217-plan-analyze-spec.md +117 -0
- package/docs/20260217-post-impl-review.md +1137 -0
- package/docs/20260217-quick-wins-plan.md +66 -0
- package/docs/20260217-split-runner-plan.md +75 -0
- package/docs/20260217-v03-impl-plan.md +80 -0
- package/docs/20260217-v03-post-impl-review.md +589 -0
- package/docs/20260217-v04-impl-plan.md +86 -0
- package/docs/20260217-v05-post-impl-review.md +850 -0
- package/docs/20260217-v06-post-impl-review.md +817 -0
- package/docs/20260218-adr003-port-plan.md +151 -0
- package/docs/20260218-review-adr003-verification.md +175 -0
- package/docs/20260219-fix-plan-bug16-19.md +79 -0
- package/docs/20260219-fix-plan-bug20-22.md +114 -0
- package/docs/20260219-plan-llm-routing.md +116 -0
- package/docs/20260219-review-bug20-22-fixes.md +135 -0
- package/docs/20260219-routing-baseline-keyword.md +63 -0
- package/docs/20260220-plan-structured-logging-p1.md +80 -0
- package/docs/20260220-plan-structured-logging-p2.md +37 -0
- package/docs/20260220-review-llm-routing.md +180 -0
- package/docs/20260220-review-post-fix-llm-routing.md +70 -0
- package/docs/20260221-fix-plan-relevantfiles-split.md +101 -0
- package/docs/20260221-fix-plan-routing-mode.md +125 -0
- package/docs/20260221-review-v0.9-implementation.md +379 -0
- package/docs/20260222-fix-plan-v091-routing-isolation.md +197 -0
- package/docs/20260223-fix-plan-prompt-audit.md +62 -0
- package/docs/20260224-nax-roadmap-phases.md +189 -0
- package/docs/20260225-phase2-llm-service-layer.md +401 -0
- package/docs/20260225-review-v0.10.1.md +187 -0
- package/docs/20260303-v010-implementation-plan.md +165 -0
- package/docs/CLAUDE.md.bak +191 -0
- package/docs/ROADMAP.md +165 -0
- package/docs/SPEC-rectification.md +0 -0
- package/docs/SPEC.md +324 -0
- package/docs/US-001-plugin-loading-verification.md +152 -0
- package/docs/architecture-analysis.md +1076 -0
- package/docs/bugs/BUG-21-escalation-null-attempts.md +48 -0
- package/docs/bugs-from-dogfood-run-c.md +243 -0
- package/docs/code-review-20260228.md +612 -0
- package/docs/code-review-v0.15.0.md +629 -0
- package/docs/hook-lifecycle-test-plan.md +149 -0
- package/docs/releases/v0.11.0-and-earlier.md +20 -0
- package/docs/releases/v0.12.0.md +15 -0
- package/docs/releases/v0.13.0.md +14 -0
- package/docs/releases/v0.14.0.md +20 -0
- package/docs/releases/v0.14.1.md +36 -0
- package/docs/releases/v0.14.2.md +51 -0
- package/docs/releases/v0.14.3.md +174 -0
- package/docs/releases/v0.14.4.md +94 -0
- package/docs/releases/v0.15.0.md +502 -0
- package/docs/releases/v0.15.1.md +170 -0
- package/docs/releases/v0.15.3.md +193 -0
- package/docs/specs/status-file-v0.10.1.md +812 -0
- package/docs/v0.10-global-config.md +206 -0
- package/docs/v0.10-plugin-system.md +415 -0
- package/docs/v0.10-prompt-optimizer.md +234 -0
- package/docs/v0.3-spec.md +244 -0
- package/docs/v0.4-spec.md +140 -0
- package/docs/v0.5-spec.md +237 -0
- package/docs/v0.6-spec.md +371 -0
- package/docs/v0.7-spec.md +177 -0
- package/docs/v0.8-llm-routing.md +206 -0
- package/docs/v0.8-structured-logging.md +132 -0
- package/docs/v0.9.3-prompt-audit.md +112 -0
- package/examples/plugins/console-reporter/index.test.ts +207 -0
- package/examples/plugins/console-reporter/index.ts +110 -0
- package/nax/config.json +147 -0
- package/nax/features/bugfix-v0171/prd.json +52 -0
- package/nax/features/config-management/prd.json +108 -0
- package/nax/features/config-management/progress.txt +5 -0
- package/nax/features/diagnose/acceptance.test.ts +412 -0
- package/nax/features/diagnose/prd.json +41 -0
- package/nax/features/orchestration-fixes/prd.json +89 -0
- package/nax/features/orchestration-fixes/progress.txt +1 -0
- package/nax/features/plugin-integration/US-007-VERIFICATION.md +259 -0
- package/nax/features/plugin-integration/prd.json +208 -0
- package/nax/features/plugin-integration/progress.txt +5 -0
- package/nax/features/precheck/prd.json +205 -0
- package/nax/features/precheck/progress.txt +15 -0
- package/nax/features/structured-logging/prd.json +199 -0
- package/nax/features/unlock/prd.json +36 -0
- package/package.json +47 -0
- package/src/acceptance/fix-generator.ts +348 -0
- package/src/acceptance/generator.ts +282 -0
- package/src/acceptance/index.ts +30 -0
- package/src/acceptance/types.ts +79 -0
- package/src/agents/claude-decompose.ts +169 -0
- package/src/agents/claude-plan.ts +139 -0
- package/src/agents/claude.ts +324 -0
- package/src/agents/cost.ts +268 -0
- package/src/agents/index.ts +13 -0
- package/src/agents/registry.ts +48 -0
- package/src/agents/types-extended.ts +133 -0
- package/src/agents/types.ts +113 -0
- package/src/agents/validation.ts +69 -0
- package/src/analyze/classifier.ts +305 -0
- package/src/analyze/index.ts +16 -0
- package/src/analyze/scanner.ts +175 -0
- package/src/analyze/types.ts +51 -0
- package/src/cli/accept.ts +108 -0
- package/src/cli/analyze-parser.ts +284 -0
- package/src/cli/analyze.ts +207 -0
- package/src/cli/config.ts +561 -0
- package/src/cli/constitution.ts +109 -0
- package/src/cli/diagnose-analysis.ts +159 -0
- package/src/cli/diagnose-formatter.ts +87 -0
- package/src/cli/diagnose.ts +203 -0
- package/src/cli/generate.ts +127 -0
- package/src/cli/index.ts +37 -0
- package/src/cli/init.ts +188 -0
- package/src/cli/interact.ts +295 -0
- package/src/cli/plan.ts +198 -0
- package/src/cli/plugins.ts +111 -0
- package/src/cli/prompts.ts +295 -0
- package/src/cli/runs.ts +174 -0
- package/src/cli/status-cost.ts +151 -0
- package/src/cli/status-features.ts +338 -0
- package/src/cli/status.ts +13 -0
- package/src/commands/common.ts +171 -0
- package/src/commands/diagnose.ts +17 -0
- package/src/commands/index.ts +8 -0
- package/src/commands/logs.ts +384 -0
- package/src/commands/precheck.ts +86 -0
- package/src/commands/unlock.ts +96 -0
- package/src/config/defaults.ts +160 -0
- package/src/config/index.ts +22 -0
- package/src/config/loader.ts +121 -0
- package/src/config/merger.ts +147 -0
- package/src/config/path-security.ts +121 -0
- package/src/config/paths.ts +27 -0
- package/src/config/schema.ts +56 -0
- package/src/config/schemas.ts +286 -0
- package/src/config/types.ts +423 -0
- package/src/config/validate.ts +103 -0
- package/src/constitution/generator.ts +191 -0
- package/src/constitution/generators/aider.ts +41 -0
- package/src/constitution/generators/claude.ts +35 -0
- package/src/constitution/generators/cursor.ts +36 -0
- package/src/constitution/generators/opencode.ts +38 -0
- package/src/constitution/generators/types.ts +33 -0
- package/src/constitution/generators/windsurf.ts +36 -0
- package/src/constitution/index.ts +10 -0
- package/src/constitution/loader.ts +133 -0
- package/src/constitution/types.ts +31 -0
- package/src/context/auto-detect.ts +227 -0
- package/src/context/builder.ts +246 -0
- package/src/context/elements.ts +83 -0
- package/src/context/formatter.ts +107 -0
- package/src/context/generator.ts +129 -0
- package/src/context/generators/aider.ts +34 -0
- package/src/context/generators/claude.ts +28 -0
- package/src/context/generators/cursor.ts +28 -0
- package/src/context/generators/opencode.ts +30 -0
- package/src/context/generators/windsurf.ts +28 -0
- package/src/context/greenfield.ts +114 -0
- package/src/context/index.ts +33 -0
- package/src/context/injector.ts +279 -0
- package/src/context/test-scanner.ts +370 -0
- package/src/context/types.ts +98 -0
- package/src/errors.ts +67 -0
- package/src/execution/batching.ts +157 -0
- package/src/execution/crash-recovery.ts +373 -0
- package/src/execution/escalation/escalation.ts +44 -0
- package/src/execution/escalation/index.ts +13 -0
- package/src/execution/escalation/tier-escalation.ts +295 -0
- package/src/execution/escalation/tier-outcome.ts +158 -0
- package/src/execution/helpers.ts +38 -0
- package/src/execution/index.ts +45 -0
- package/src/execution/lifecycle/acceptance-loop.ts +272 -0
- package/src/execution/lifecycle/headless-formatter.ts +85 -0
- package/src/execution/lifecycle/index.ts +12 -0
- package/src/execution/lifecycle/parallel-lifecycle.ts +101 -0
- package/src/execution/lifecycle/precheck-runner.ts +140 -0
- package/src/execution/lifecycle/run-cleanup.ts +81 -0
- package/src/execution/lifecycle/run-completion.ts +129 -0
- package/src/execution/lifecycle/run-initialization.ts +141 -0
- package/src/execution/lifecycle/run-lifecycle.ts +312 -0
- package/src/execution/lifecycle/run-setup.ts +204 -0
- package/src/execution/lifecycle/story-hooks.ts +38 -0
- package/src/execution/lifecycle/story-size-prompts.ts +123 -0
- package/src/execution/lock.ts +115 -0
- package/src/execution/parallel-executor.ts +216 -0
- package/src/execution/parallel.ts +400 -0
- package/src/execution/pid-registry.ts +280 -0
- package/src/execution/pipeline-result-handler.ts +388 -0
- package/src/execution/post-verify-rectification.ts +188 -0
- package/src/execution/post-verify.ts +274 -0
- package/src/execution/progress.ts +25 -0
- package/src/execution/prompts.ts +127 -0
- package/src/execution/queue-handler.ts +109 -0
- package/src/execution/rectification.ts +13 -0
- package/src/execution/runner.ts +377 -0
- package/src/execution/sequential-executor.ts +388 -0
- package/src/execution/status-file.ts +264 -0
- package/src/execution/status-writer.ts +139 -0
- package/src/execution/story-context.ts +229 -0
- package/src/execution/test-output-parser.ts +14 -0
- package/src/execution/verification.ts +72 -0
- package/src/hooks/index.ts +2 -0
- package/src/hooks/runner.ts +286 -0
- package/src/hooks/types.ts +67 -0
- package/src/interaction/chain.ts +154 -0
- package/src/interaction/index.ts +60 -0
- package/src/interaction/init.ts +83 -0
- package/src/interaction/plugins/auto.ts +217 -0
- package/src/interaction/plugins/cli.ts +300 -0
- package/src/interaction/plugins/telegram.ts +384 -0
- package/src/interaction/plugins/webhook.ts +258 -0
- package/src/interaction/state.ts +171 -0
- package/src/interaction/triggers.ts +229 -0
- package/src/interaction/types.ts +163 -0
- package/src/logger/formatters.ts +84 -0
- package/src/logger/index.ts +16 -0
- package/src/logger/logger.ts +298 -0
- package/src/logger/types.ts +48 -0
- package/src/logging/formatter.ts +355 -0
- package/src/logging/index.ts +22 -0
- package/src/logging/types.ts +93 -0
- package/src/metrics/aggregator.ts +190 -0
- package/src/metrics/index.ts +14 -0
- package/src/metrics/tracker.ts +200 -0
- package/src/metrics/types.ts +109 -0
- package/src/optimizer/index.ts +62 -0
- package/src/optimizer/noop.optimizer.ts +24 -0
- package/src/optimizer/rule-based.optimizer.ts +248 -0
- package/src/optimizer/types.ts +53 -0
- package/src/pipeline/events.ts +130 -0
- package/src/pipeline/index.ts +19 -0
- package/src/pipeline/runner.ts +161 -0
- package/src/pipeline/stages/acceptance.ts +197 -0
- package/src/pipeline/stages/completion.ts +99 -0
- package/src/pipeline/stages/constitution.ts +63 -0
- package/src/pipeline/stages/context.ts +117 -0
- package/src/pipeline/stages/execution.ts +194 -0
- package/src/pipeline/stages/index.ts +62 -0
- package/src/pipeline/stages/optimizer.ts +74 -0
- package/src/pipeline/stages/prompt.ts +57 -0
- package/src/pipeline/stages/queue-check.ts +103 -0
- package/src/pipeline/stages/review.ts +181 -0
- package/src/pipeline/stages/routing.ts +81 -0
- package/src/pipeline/stages/verify.ts +100 -0
- package/src/pipeline/types.ts +167 -0
- package/src/plugins/index.ts +31 -0
- package/src/plugins/loader.ts +287 -0
- package/src/plugins/registry.ts +168 -0
- package/src/plugins/types.ts +327 -0
- package/src/plugins/validator.ts +352 -0
- package/src/prd/index.ts +172 -0
- package/src/prd/types.ts +202 -0
- package/src/precheck/checks-blockers.ts +391 -0
- package/src/precheck/checks-warnings.ts +142 -0
- package/src/precheck/checks.ts +30 -0
- package/src/precheck/index.ts +247 -0
- package/src/precheck/story-size-gate.ts +144 -0
- package/src/precheck/types.ts +31 -0
- package/src/queue/index.ts +2 -0
- package/src/queue/manager.ts +254 -0
- package/src/queue/types.ts +54 -0
- package/src/review/index.ts +8 -0
- package/src/review/runner.ts +172 -0
- package/src/review/types.ts +66 -0
- package/src/routing/builder.ts +81 -0
- package/src/routing/chain.ts +74 -0
- package/src/routing/index.ts +16 -0
- package/src/routing/loader.ts +58 -0
- package/src/routing/router.ts +303 -0
- package/src/routing/strategies/adaptive.ts +215 -0
- package/src/routing/strategies/index.ts +8 -0
- package/src/routing/strategies/keyword.ts +163 -0
- package/src/routing/strategies/llm-prompts.ts +209 -0
- package/src/routing/strategies/llm.ts +235 -0
- package/src/routing/strategies/manual.ts +50 -0
- package/src/routing/strategy.ts +99 -0
- package/src/tdd/cleanup.ts +111 -0
- package/src/tdd/index.ts +23 -0
- package/src/tdd/isolation.ts +123 -0
- package/src/tdd/orchestrator.ts +383 -0
- package/src/tdd/prompts.ts +270 -0
- package/src/tdd/rectification-gate.ts +183 -0
- package/src/tdd/session-runner.ts +179 -0
- package/src/tdd/types.ts +81 -0
- package/src/tdd/verdict.ts +271 -0
- package/src/tui/App.tsx +265 -0
- package/src/tui/components/AgentPanel.tsx +75 -0
- package/src/tui/components/CostOverlay.tsx +118 -0
- package/src/tui/components/HelpOverlay.tsx +107 -0
- package/src/tui/components/StatusBar.tsx +63 -0
- package/src/tui/components/StoriesPanel.tsx +177 -0
- package/src/tui/hooks/useKeyboard.ts +142 -0
- package/src/tui/hooks/useLayout.ts +137 -0
- package/src/tui/hooks/usePipelineEvents.ts +183 -0
- package/src/tui/hooks/usePty.ts +194 -0
- package/src/tui/index.tsx +38 -0
- package/src/tui/types.ts +76 -0
- package/src/utils/git.ts +83 -0
- package/src/utils/queue-writer.ts +54 -0
- package/src/verification/executor.ts +235 -0
- package/src/verification/gate.ts +207 -0
- package/src/verification/index.ts +12 -0
- package/src/verification/parser.ts +230 -0
- package/src/verification/rectification.ts +108 -0
- package/src/verification/types.ts +113 -0
- package/src/worktree/dispatcher.ts +65 -0
- package/src/worktree/index.ts +2 -0
- package/src/worktree/manager.ts +187 -0
- package/src/worktree/merge.ts +301 -0
- package/src/worktree/types.ts +4 -0
- package/test/TEST_COVERAGE_US001.md +217 -0
- package/test/TEST_COVERAGE_US003.md +84 -0
- package/test/TEST_COVERAGE_US005.md +86 -0
- package/test/US-002-orchestrator.test.ts +246 -0
- package/test/acceptance/cm-003-default-view.test.ts +194 -0
- package/test/execution/pid-registry.test.ts +240 -0
- package/test/execution/post-verify.test.ts +224 -0
- package/test/helpers/timeout.ts +42 -0
- package/test/integration/US-002-TEST-SUMMARY.md +107 -0
- package/test/integration/US-003-TEST-SUMMARY.md +149 -0
- package/test/integration/US-004-TEST-SUMMARY.md +106 -0
- package/test/integration/US-005-TEST-SUMMARY.md +138 -0
- package/test/integration/US-007-TEST-SUMMARY.md +100 -0
- package/test/integration/agent-validation.test.ts +439 -0
- package/test/integration/analyze-integration.test.ts +261 -0
- package/test/integration/analyze-scanner.test.ts +131 -0
- package/test/integration/cli-config-default-edge-cases.test.ts +222 -0
- package/test/integration/cli-config-default-view.test.ts +229 -0
- package/test/integration/cli-config-diff.test.ts +460 -0
- package/test/integration/cli-config.test.ts +736 -0
- package/test/integration/cli-diagnose.test.ts +592 -0
- package/test/integration/cli-logs.test.ts +314 -0
- package/test/integration/cli-plugins.test.ts +678 -0
- package/test/integration/cli-precheck.test.ts +371 -0
- package/test/integration/cli-run-headless.test.ts +173 -0
- package/test/integration/cli.test.ts +75 -0
- package/test/integration/config/merger.test.ts +465 -0
- package/test/integration/config/paths.test.ts +51 -0
- package/test/integration/config-loader.test.ts +265 -0
- package/test/integration/config.test.ts +444 -0
- package/test/integration/context-integration.test.ts +702 -0
- package/test/integration/context-provider-injection.test.ts +506 -0
- package/test/integration/context-verification-integration.test.ts +295 -0
- package/test/integration/e2e.test.ts +896 -0
- package/test/integration/execution.test.ts +625 -0
- package/test/integration/helpers.test.ts +295 -0
- package/test/integration/hooks.test.ts +361 -0
- package/test/integration/interaction-chain-pipeline.test.ts +464 -0
- package/test/integration/isolation.test.ts +143 -0
- package/test/integration/logger.test.ts +461 -0
- package/test/integration/parallel.test.ts +250 -0
- package/test/integration/path-security.test.ts +173 -0
- package/test/integration/pipeline-acceptance.test.ts +302 -0
- package/test/integration/pipeline-events.test.ts +475 -0
- package/test/integration/pipeline.test.ts +658 -0
- package/test/integration/plan.test.ts +157 -0
- package/test/integration/plugin-routing.test.ts +921 -0
- package/test/integration/plugins/config-integration.test.ts +172 -0
- package/test/integration/plugins/config-resolution.test.ts +522 -0
- package/test/integration/plugins/loader.test.ts +641 -0
- package/test/integration/plugins/registry.test.ts +746 -0
- package/test/integration/plugins/validator.test.ts +563 -0
- package/test/integration/prd-pause.test.ts +205 -0
- package/test/integration/prd-resolvers.test.ts +185 -0
- package/test/integration/precheck-integration.test.ts +468 -0
- package/test/integration/precheck.test.ts +805 -0
- package/test/integration/progress.test.ts +34 -0
- package/test/integration/rectification-flow.test.ts +512 -0
- package/test/integration/reporter-lifecycle.test.ts +860 -0
- package/test/integration/review-config-commands.test.ts +319 -0
- package/test/integration/review-config-schema.test.ts +116 -0
- package/test/integration/review-plugin-integration.test.ts +722 -0
- package/test/integration/review.test.ts +149 -0
- package/test/integration/routing-stage-bug-021.test.ts +274 -0
- package/test/integration/routing-stage-greenfield.test.ts +286 -0
- package/test/integration/runner-config-plugins.test.ts +461 -0
- package/test/integration/runner-fixes.test.ts +399 -0
- package/test/integration/runner-plugin-integration.test.ts +543 -0
- package/test/integration/runner.test.ts +1679 -0
- package/test/integration/s5-greenfield-fallback.test.ts +297 -0
- package/test/integration/status-file-integration.test.ts +325 -0
- package/test/integration/status-file.test.ts +379 -0
- package/test/integration/status-writer.test.ts +345 -0
- package/test/integration/story-id-in-events.test.ts +273 -0
- package/test/integration/tdd-cleanup.test.ts +246 -0
- package/test/integration/tdd-orchestrator.test.ts +1762 -0
- package/test/integration/test-scanner.test.ts +403 -0
- package/test/integration/verification-asset-check.test.ts +142 -0
- package/test/integration/verify-stage.test.ts +275 -0
- package/test/integration/worktree/manager.test.ts +218 -0
- package/test/integration/worktree/merge.test.ts +341 -0
- package/test/manual/logging-formatter-demo.ts +158 -0
- package/test/ui/tui-agent-panel.test.tsx +99 -0
- package/test/ui/tui-controls.test.ts +334 -0
- package/test/ui/tui-cost-and-pty.test.ts +189 -0
- package/test/ui/tui-layout.test.ts +378 -0
- package/test/ui/tui-pty-integration.test.tsx +159 -0
- package/test/ui/tui-stories.test.ts +332 -0
- package/test/unit/acceptance.test.ts +186 -0
- package/test/unit/agent-stderr-capture.test.ts +146 -0
- package/test/unit/analyze-classifier.test.ts +215 -0
- package/test/unit/analyze.test.ts +224 -0
- package/test/unit/auto-detect.test.ts +249 -0
- package/test/unit/cli-status.test.ts +417 -0
- package/test/unit/commands/common.test.ts +320 -0
- package/test/unit/commands/logs.test.ts +416 -0
- package/test/unit/commands/unlock.test.ts +319 -0
- package/test/unit/constitution-generators.test.ts +160 -0
- package/test/unit/constitution.test.ts +209 -0
- package/test/unit/context.test.ts +1722 -0
- package/test/unit/cost.test.ts +231 -0
- package/test/unit/crash-recovery.test.ts +308 -0
- package/test/unit/escalation.test.ts +126 -0
- package/test/unit/execution-logging-stderr.test.ts +156 -0
- package/test/unit/execution-stage.test.ts +122 -0
- package/test/unit/fix-generator.test.ts +275 -0
- package/test/unit/formatters.test.ts +469 -0
- package/test/unit/greenfield.test.ts +179 -0
- package/test/unit/helpers.test.ts +317 -0
- package/test/unit/interaction/human-review-trigger.test.ts +164 -0
- package/test/unit/interaction-network-failures.test.ts +389 -0
- package/test/unit/interaction-plugins.test.ts +164 -0
- package/test/unit/isolation.test.ts +134 -0
- package/test/unit/logging/formatter.test.ts +455 -0
- package/test/unit/merge.test.ts +268 -0
- package/test/unit/metrics.test.ts +276 -0
- package/test/unit/optimizer/noop.optimizer.test.ts +125 -0
- package/test/unit/optimizer/rule-based.optimizer.test.ts +358 -0
- package/test/unit/prd-auto-default.test.ts +290 -0
- package/test/unit/prd-failure-category.test.ts +176 -0
- package/test/unit/prd-get-next-story.test.ts +186 -0
- package/test/unit/precheck-checks.test.ts +840 -0
- package/test/unit/precheck-story-size-gate.test.ts +287 -0
- package/test/unit/precheck-types.test.ts +142 -0
- package/test/unit/prompts.test.ts +475 -0
- package/test/unit/queue.test.ts +237 -0
- package/test/unit/rectification.test.ts +284 -0
- package/test/unit/registry.test.ts +287 -0
- package/test/unit/routing.test.ts +937 -0
- package/test/unit/run-lifecycle.test.ts +140 -0
- package/test/unit/storyid-events.test.ts +224 -0
- package/test/unit/tdd-verdict.test.ts +492 -0
- package/test/unit/test-output-parser.test.ts +377 -0
- package/test/unit/verdict.test.ts +324 -0
- package/test/unit/worktree-manager.test.ts +158 -0
- package/tsconfig.json +27 -0
package/src/cli/init.ts
ADDED
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Init Command
|
|
3
|
+
*
|
|
4
|
+
* Initializes nax configuration directories and files.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { existsSync, mkdirSync, readFileSync } from "node:fs";
|
|
8
|
+
import { join } from "node:path";
|
|
9
|
+
import { globalConfigDir, projectConfigDir } from "../config/paths";
|
|
10
|
+
import { DEFAULT_CONFIG } from "../config/schema";
|
|
11
|
+
import { getLogger } from "../logger";
|
|
12
|
+
|
|
13
|
+
/** Init command options */
|
|
14
|
+
export interface InitOptions {
|
|
15
|
+
/** Initialize global config (~/.nax) */
|
|
16
|
+
global?: boolean;
|
|
17
|
+
/** Project root (default: cwd) */
|
|
18
|
+
projectRoot?: string;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Gitignore entries added by nax init
|
|
23
|
+
*/
|
|
24
|
+
const NAX_GITIGNORE_ENTRIES = [".nax-verifier-verdict.json"];
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Add nax-specific entries to .gitignore if not already present.
|
|
28
|
+
*
|
|
29
|
+
* Appends a clearly marked nax section to the project .gitignore.
|
|
30
|
+
*/
|
|
31
|
+
async function updateGitignore(projectRoot: string): Promise<void> {
|
|
32
|
+
const logger = getLogger();
|
|
33
|
+
const gitignorePath = join(projectRoot, ".gitignore");
|
|
34
|
+
|
|
35
|
+
let existing = "";
|
|
36
|
+
if (existsSync(gitignorePath)) {
|
|
37
|
+
existing = readFileSync(gitignorePath, "utf-8");
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const missingEntries = NAX_GITIGNORE_ENTRIES.filter((entry) => !existing.includes(entry));
|
|
41
|
+
|
|
42
|
+
if (missingEntries.length === 0) {
|
|
43
|
+
logger.info("init", ".gitignore already has nax entries", { path: gitignorePath });
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const naxSection = `\n# nax — generated files\n${missingEntries.join("\n")}\n`;
|
|
48
|
+
await Bun.write(gitignorePath, existing + naxSection);
|
|
49
|
+
logger.info("init", "Updated .gitignore with nax entries", {
|
|
50
|
+
path: gitignorePath,
|
|
51
|
+
added: missingEntries,
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Template for default constitution.md
|
|
57
|
+
*/
|
|
58
|
+
const DEFAULT_CONSTITUTION = `# Project Constitution
|
|
59
|
+
|
|
60
|
+
## Goals
|
|
61
|
+
- Deliver high-quality, maintainable code
|
|
62
|
+
- Follow project conventions and best practices
|
|
63
|
+
- Maintain comprehensive test coverage
|
|
64
|
+
|
|
65
|
+
## Constraints
|
|
66
|
+
- Use Bun-native APIs only
|
|
67
|
+
- Follow functional style for pure logic
|
|
68
|
+
- Keep files focused and under 400 lines
|
|
69
|
+
|
|
70
|
+
## Preferences
|
|
71
|
+
- Prefer immutability over mutation
|
|
72
|
+
- Write tests first (TDD approach)
|
|
73
|
+
- Clear, descriptive naming
|
|
74
|
+
`;
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Template for minimal config.json (references defaults, only overrides)
|
|
78
|
+
*/
|
|
79
|
+
const MINIMAL_PROJECT_CONFIG = {
|
|
80
|
+
version: 1,
|
|
81
|
+
// Add project-specific overrides here
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
const MINIMAL_GLOBAL_CONFIG = {
|
|
85
|
+
version: 1,
|
|
86
|
+
// Add global preferences here (e.g., model tiers, execution limits)
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Initialize global nax config directory (~/.nax)
|
|
91
|
+
*/
|
|
92
|
+
async function initGlobal(): Promise<void> {
|
|
93
|
+
const logger = getLogger();
|
|
94
|
+
const globalDir = globalConfigDir();
|
|
95
|
+
|
|
96
|
+
// Create ~/.nax if it doesn't exist
|
|
97
|
+
if (!existsSync(globalDir)) {
|
|
98
|
+
mkdirSync(globalDir, { recursive: true });
|
|
99
|
+
logger.info("init", "Created global config directory", { path: globalDir });
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// Create ~/.nax/config.json if it doesn't exist
|
|
103
|
+
const configPath = join(globalDir, "config.json");
|
|
104
|
+
if (!existsSync(configPath)) {
|
|
105
|
+
await Bun.write(configPath, `${JSON.stringify(MINIMAL_GLOBAL_CONFIG, null, 2)}\n`);
|
|
106
|
+
logger.info("init", "Created global config", { path: configPath });
|
|
107
|
+
} else {
|
|
108
|
+
logger.info("init", "Global config already exists", { path: configPath });
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// Create ~/.nax/constitution.md if it doesn't exist
|
|
112
|
+
const constitutionPath = join(globalDir, "constitution.md");
|
|
113
|
+
if (!existsSync(constitutionPath)) {
|
|
114
|
+
await Bun.write(constitutionPath, DEFAULT_CONSTITUTION);
|
|
115
|
+
logger.info("init", "Created global constitution", { path: constitutionPath });
|
|
116
|
+
} else {
|
|
117
|
+
logger.info("init", "Global constitution already exists", { path: constitutionPath });
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// Create ~/.nax/hooks/ directory if it doesn't exist
|
|
121
|
+
const hooksDir = join(globalDir, "hooks");
|
|
122
|
+
if (!existsSync(hooksDir)) {
|
|
123
|
+
mkdirSync(hooksDir, { recursive: true });
|
|
124
|
+
logger.info("init", "Created global hooks directory", { path: hooksDir });
|
|
125
|
+
} else {
|
|
126
|
+
logger.info("init", "Global hooks directory already exists", { path: hooksDir });
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
logger.info("init", "Global config initialized successfully", { path: globalDir });
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Initialize project nax directory (nax/)
|
|
134
|
+
*/
|
|
135
|
+
async function initProject(projectRoot: string): Promise<void> {
|
|
136
|
+
const logger = getLogger();
|
|
137
|
+
const projectDir = projectConfigDir(projectRoot);
|
|
138
|
+
|
|
139
|
+
// Create nax/ directory if it doesn't exist
|
|
140
|
+
if (!existsSync(projectDir)) {
|
|
141
|
+
mkdirSync(projectDir, { recursive: true });
|
|
142
|
+
logger.info("init", "Created project config directory", { path: projectDir });
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// Create nax/config.json if it doesn't exist
|
|
146
|
+
const configPath = join(projectDir, "config.json");
|
|
147
|
+
if (!existsSync(configPath)) {
|
|
148
|
+
await Bun.write(configPath, `${JSON.stringify(MINIMAL_PROJECT_CONFIG, null, 2)}\n`);
|
|
149
|
+
logger.info("init", "Created project config", { path: configPath });
|
|
150
|
+
} else {
|
|
151
|
+
logger.info("init", "Project config already exists", { path: configPath });
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// Create nax/constitution.md if it doesn't exist
|
|
155
|
+
const constitutionPath = join(projectDir, "constitution.md");
|
|
156
|
+
if (!existsSync(constitutionPath)) {
|
|
157
|
+
await Bun.write(constitutionPath, DEFAULT_CONSTITUTION);
|
|
158
|
+
logger.info("init", "Created project constitution", { path: constitutionPath });
|
|
159
|
+
} else {
|
|
160
|
+
logger.info("init", "Project constitution already exists", { path: constitutionPath });
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// Create nax/hooks/ directory if it doesn't exist
|
|
164
|
+
const hooksDir = join(projectDir, "hooks");
|
|
165
|
+
if (!existsSync(hooksDir)) {
|
|
166
|
+
mkdirSync(hooksDir, { recursive: true });
|
|
167
|
+
logger.info("init", "Created project hooks directory", { path: hooksDir });
|
|
168
|
+
} else {
|
|
169
|
+
logger.info("init", "Project hooks directory already exists", { path: hooksDir });
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// Update .gitignore to include nax-specific entries
|
|
173
|
+
await updateGitignore(projectRoot);
|
|
174
|
+
|
|
175
|
+
logger.info("init", "Project config initialized successfully", { path: projectDir });
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* Run init command
|
|
180
|
+
*/
|
|
181
|
+
export async function initCommand(options: InitOptions = {}): Promise<void> {
|
|
182
|
+
if (options.global) {
|
|
183
|
+
await initGlobal();
|
|
184
|
+
} else {
|
|
185
|
+
const projectRoot = options.projectRoot ?? process.cwd();
|
|
186
|
+
await initProject(projectRoot);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
@@ -0,0 +1,295 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Interact CLI Command (v0.15.0 US-006)
|
|
3
|
+
*
|
|
4
|
+
* Manage pending interactions from CLI:
|
|
5
|
+
* - nax interact list -f <feature>
|
|
6
|
+
* - nax interact respond <id> --action approve|reject|choose|input --value <val>
|
|
7
|
+
* - nax interact cancel <id>
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { existsSync } from "node:fs";
|
|
11
|
+
import { join } from "node:path";
|
|
12
|
+
import chalk from "chalk";
|
|
13
|
+
import { resolveProject } from "../commands/common";
|
|
14
|
+
import type { InteractionRequest, InteractionResponse } from "../interaction";
|
|
15
|
+
import {
|
|
16
|
+
deletePendingInteraction,
|
|
17
|
+
listPendingInteractions,
|
|
18
|
+
loadPendingInteraction,
|
|
19
|
+
savePendingInteraction,
|
|
20
|
+
} from "../interaction";
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Options for interact list command
|
|
24
|
+
*/
|
|
25
|
+
export interface InteractListOptions {
|
|
26
|
+
/** Feature name (required) */
|
|
27
|
+
feature: string;
|
|
28
|
+
/** Explicit project directory */
|
|
29
|
+
dir?: string;
|
|
30
|
+
/** JSON output mode */
|
|
31
|
+
json?: boolean;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Options for interact respond command
|
|
36
|
+
*/
|
|
37
|
+
export interface InteractRespondOptions {
|
|
38
|
+
/** Feature name */
|
|
39
|
+
feature?: string;
|
|
40
|
+
/** Explicit project directory */
|
|
41
|
+
dir?: string;
|
|
42
|
+
/** Action to take */
|
|
43
|
+
action: "approve" | "reject" | "choose" | "input" | "skip" | "abort";
|
|
44
|
+
/** Value (for choose/input) */
|
|
45
|
+
value?: string;
|
|
46
|
+
/** JSON output mode */
|
|
47
|
+
json?: boolean;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Options for interact cancel command
|
|
52
|
+
*/
|
|
53
|
+
export interface InteractCancelOptions {
|
|
54
|
+
/** Feature name */
|
|
55
|
+
feature?: string;
|
|
56
|
+
/** Explicit project directory */
|
|
57
|
+
dir?: string;
|
|
58
|
+
/** JSON output mode */
|
|
59
|
+
json?: boolean;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* List pending interactions for a feature
|
|
64
|
+
*/
|
|
65
|
+
export async function interactListCommand(options: InteractListOptions): Promise<void> {
|
|
66
|
+
const resolved = resolveProject({
|
|
67
|
+
dir: options.dir,
|
|
68
|
+
feature: options.feature,
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
if (!resolved.featureDir) {
|
|
72
|
+
throw new Error("Feature directory not resolved");
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const interactionsDir = join(resolved.featureDir, "interactions");
|
|
76
|
+
if (!existsSync(interactionsDir)) {
|
|
77
|
+
if (options.json) {
|
|
78
|
+
console.log(JSON.stringify({ interactions: [] }));
|
|
79
|
+
} else {
|
|
80
|
+
console.log(chalk.dim("No pending interactions."));
|
|
81
|
+
}
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const ids = await listPendingInteractions(resolved.featureDir);
|
|
86
|
+
|
|
87
|
+
if (ids.length === 0) {
|
|
88
|
+
if (options.json) {
|
|
89
|
+
console.log(JSON.stringify({ interactions: [] }));
|
|
90
|
+
} else {
|
|
91
|
+
console.log(chalk.dim("No pending interactions."));
|
|
92
|
+
}
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// Load full requests
|
|
97
|
+
const requests: InteractionRequest[] = [];
|
|
98
|
+
for (const id of ids) {
|
|
99
|
+
const req = await loadPendingInteraction(id, resolved.featureDir);
|
|
100
|
+
if (req) {
|
|
101
|
+
requests.push(req);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
if (options.json) {
|
|
106
|
+
console.log(JSON.stringify({ interactions: requests }, null, 2));
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// Display table
|
|
111
|
+
console.log(chalk.bold(`\n📬 Pending Interactions (${options.feature})\n`));
|
|
112
|
+
|
|
113
|
+
for (const req of requests) {
|
|
114
|
+
const safety = req.metadata?.safety ?? "unknown";
|
|
115
|
+
const safetyIcon = safety === "red" ? "🔴" : safety === "yellow" ? "🟡" : "🟢";
|
|
116
|
+
const timeRemaining = req.timeout ? Math.max(0, req.createdAt + req.timeout - Date.now()) : null;
|
|
117
|
+
|
|
118
|
+
console.log(`${safetyIcon} ${chalk.bold(req.id)}`);
|
|
119
|
+
console.log(chalk.dim(` Type: ${req.type}`));
|
|
120
|
+
console.log(chalk.dim(` Stage: ${req.stage}`));
|
|
121
|
+
console.log(chalk.dim(` Summary: ${req.summary}`));
|
|
122
|
+
if (req.storyId) {
|
|
123
|
+
console.log(chalk.dim(` Story: ${req.storyId}`));
|
|
124
|
+
}
|
|
125
|
+
console.log(chalk.dim(` Fallback: ${req.fallback}`));
|
|
126
|
+
if (timeRemaining !== null) {
|
|
127
|
+
const timeoutSec = Math.floor(timeRemaining / 1000);
|
|
128
|
+
console.log(chalk.dim(` Timeout: ${timeoutSec}s remaining`));
|
|
129
|
+
}
|
|
130
|
+
if (req.options && req.options.length > 0) {
|
|
131
|
+
console.log(chalk.dim(" Options:"));
|
|
132
|
+
for (const opt of req.options) {
|
|
133
|
+
console.log(chalk.dim(` [${opt.key}] ${opt.label}`));
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
console.log();
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Respond to a pending interaction
|
|
142
|
+
*/
|
|
143
|
+
export async function interactRespondCommand(requestId: string, options: InteractRespondOptions): Promise<void> {
|
|
144
|
+
// Find the feature by searching all features for the request
|
|
145
|
+
let featureDir: string | null = null;
|
|
146
|
+
let request: InteractionRequest | null = null;
|
|
147
|
+
|
|
148
|
+
if (options.feature) {
|
|
149
|
+
const resolved = resolveProject({
|
|
150
|
+
dir: options.dir,
|
|
151
|
+
feature: options.feature,
|
|
152
|
+
});
|
|
153
|
+
featureDir = resolved.featureDir ?? null;
|
|
154
|
+
if (featureDir) {
|
|
155
|
+
request = await loadPendingInteraction(requestId, featureDir);
|
|
156
|
+
}
|
|
157
|
+
} else {
|
|
158
|
+
// Search all features
|
|
159
|
+
const resolved = resolveProject({ dir: options.dir });
|
|
160
|
+
const featuresDir = join(resolved.projectDir, "nax", "features");
|
|
161
|
+
if (existsSync(featuresDir)) {
|
|
162
|
+
const { readdirSync } = await import("node:fs");
|
|
163
|
+
const features = readdirSync(featuresDir, { withFileTypes: true })
|
|
164
|
+
.filter((e) => e.isDirectory())
|
|
165
|
+
.map((e) => e.name);
|
|
166
|
+
|
|
167
|
+
for (const feature of features) {
|
|
168
|
+
const dir = join(featuresDir, feature);
|
|
169
|
+
const req = await loadPendingInteraction(requestId, dir);
|
|
170
|
+
if (req) {
|
|
171
|
+
request = req;
|
|
172
|
+
featureDir = dir;
|
|
173
|
+
break;
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
if (!request || !featureDir) {
|
|
180
|
+
throw new Error(`Interaction request not found: ${requestId}`);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// Validate action matches request type
|
|
184
|
+
if (options.action === "choose" && request.type !== "choose") {
|
|
185
|
+
throw new Error(`Action "choose" only valid for type "choose" (request is ${request.type})`);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
if (options.action === "input" && request.type !== "input") {
|
|
189
|
+
throw new Error(`Action "input" only valid for type "input" (request is ${request.type})`);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
if (options.action === "choose" && !options.value) {
|
|
193
|
+
throw new Error("--value required for action choose");
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
if (options.action === "input" && !options.value) {
|
|
197
|
+
throw new Error("--value required for action input");
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// Create response
|
|
201
|
+
const response: InteractionResponse = {
|
|
202
|
+
requestId,
|
|
203
|
+
action: options.action,
|
|
204
|
+
value: options.value,
|
|
205
|
+
respondedBy: "cli",
|
|
206
|
+
respondedAt: Date.now(),
|
|
207
|
+
};
|
|
208
|
+
|
|
209
|
+
// Save response (write to responses/ directory)
|
|
210
|
+
const responsesDir = join(featureDir, "responses");
|
|
211
|
+
await Bun.write(join(responsesDir, ".gitkeep"), "");
|
|
212
|
+
const responseFile = join(responsesDir, `${requestId}.json`);
|
|
213
|
+
await Bun.write(responseFile, JSON.stringify(response, null, 2));
|
|
214
|
+
|
|
215
|
+
// Delete pending interaction
|
|
216
|
+
await deletePendingInteraction(requestId, featureDir);
|
|
217
|
+
|
|
218
|
+
if (options.json) {
|
|
219
|
+
console.log(JSON.stringify({ success: true, response }));
|
|
220
|
+
} else {
|
|
221
|
+
console.log(chalk.green(`✅ Response recorded: ${options.action}`));
|
|
222
|
+
if (options.value) {
|
|
223
|
+
console.log(chalk.dim(` Value: ${options.value}`));
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
/**
|
|
229
|
+
* Cancel a pending interaction (applies fallback)
|
|
230
|
+
*/
|
|
231
|
+
export async function interactCancelCommand(requestId: string, options: InteractCancelOptions): Promise<void> {
|
|
232
|
+
// Find the feature by searching all features for the request
|
|
233
|
+
let featureDir: string | null = null;
|
|
234
|
+
let request: InteractionRequest | null = null;
|
|
235
|
+
|
|
236
|
+
if (options.feature) {
|
|
237
|
+
const resolved = resolveProject({
|
|
238
|
+
dir: options.dir,
|
|
239
|
+
feature: options.feature,
|
|
240
|
+
});
|
|
241
|
+
featureDir = resolved.featureDir ?? null;
|
|
242
|
+
if (featureDir) {
|
|
243
|
+
request = await loadPendingInteraction(requestId, featureDir);
|
|
244
|
+
}
|
|
245
|
+
} else {
|
|
246
|
+
// Search all features
|
|
247
|
+
const resolved = resolveProject({ dir: options.dir });
|
|
248
|
+
const featuresDir = join(resolved.projectDir, "nax", "features");
|
|
249
|
+
if (existsSync(featuresDir)) {
|
|
250
|
+
const { readdirSync } = await import("node:fs");
|
|
251
|
+
const features = readdirSync(featuresDir, { withFileTypes: true })
|
|
252
|
+
.filter((e) => e.isDirectory())
|
|
253
|
+
.map((e) => e.name);
|
|
254
|
+
|
|
255
|
+
for (const feature of features) {
|
|
256
|
+
const dir = join(featuresDir, feature);
|
|
257
|
+
const req = await loadPendingInteraction(requestId, dir);
|
|
258
|
+
if (req) {
|
|
259
|
+
request = req;
|
|
260
|
+
featureDir = dir;
|
|
261
|
+
break;
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
if (!request || !featureDir) {
|
|
268
|
+
throw new Error(`Interaction request not found: ${requestId}`);
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
// Create response with fallback action
|
|
272
|
+
const fallbackAction = request.fallback === "continue" ? "approve" : request.fallback === "skip" ? "skip" : "abort";
|
|
273
|
+
|
|
274
|
+
const response: InteractionResponse = {
|
|
275
|
+
requestId,
|
|
276
|
+
action: fallbackAction,
|
|
277
|
+
respondedBy: "cli-cancel",
|
|
278
|
+
respondedAt: Date.now(),
|
|
279
|
+
};
|
|
280
|
+
|
|
281
|
+
// Save response
|
|
282
|
+
const responsesDir = join(featureDir, "responses");
|
|
283
|
+
await Bun.write(join(responsesDir, ".gitkeep"), "");
|
|
284
|
+
const responseFile = join(responsesDir, `${requestId}.json`);
|
|
285
|
+
await Bun.write(responseFile, JSON.stringify(response, null, 2));
|
|
286
|
+
|
|
287
|
+
// Delete pending interaction
|
|
288
|
+
await deletePendingInteraction(requestId, featureDir);
|
|
289
|
+
|
|
290
|
+
if (options.json) {
|
|
291
|
+
console.log(JSON.stringify({ success: true, response }));
|
|
292
|
+
} else {
|
|
293
|
+
console.log(chalk.yellow(`⏭ Interaction cancelled (fallback: ${request.fallback})`));
|
|
294
|
+
}
|
|
295
|
+
}
|
package/src/cli/plan.ts
ADDED
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Plan Command — Interactive planning via agent plan mode
|
|
3
|
+
*
|
|
4
|
+
* Spawns a coding agent in plan mode to gather requirements,
|
|
5
|
+
* ask clarifying questions, and generate a structured specification.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { existsSync } from "node:fs";
|
|
9
|
+
import { join } from "node:path";
|
|
10
|
+
import { ClaudeCodeAdapter } from "../agents/claude";
|
|
11
|
+
import type { PlanOptions } from "../agents/types";
|
|
12
|
+
import { scanCodebase } from "../analyze/scanner";
|
|
13
|
+
import type { NaxConfig } from "../config";
|
|
14
|
+
import { resolveModel } from "../config/schema";
|
|
15
|
+
import { getLogger } from "../logger";
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Template for structured specification output.
|
|
19
|
+
*
|
|
20
|
+
* This template guides the agent to produce a consistent spec format
|
|
21
|
+
* that can be parsed by the analyze command.
|
|
22
|
+
*/
|
|
23
|
+
const SPEC_TEMPLATE = `# Feature: [title]
|
|
24
|
+
|
|
25
|
+
## Problem
|
|
26
|
+
Why this is needed.
|
|
27
|
+
|
|
28
|
+
## Requirements
|
|
29
|
+
- REQ-1: ...
|
|
30
|
+
- REQ-2: ...
|
|
31
|
+
|
|
32
|
+
## Acceptance Criteria
|
|
33
|
+
- AC-1: ...
|
|
34
|
+
|
|
35
|
+
## Technical Notes
|
|
36
|
+
Architecture hints, constraints, dependencies.
|
|
37
|
+
|
|
38
|
+
## Out of Scope
|
|
39
|
+
What this does NOT include.
|
|
40
|
+
`;
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Run the plan command to generate a feature specification.
|
|
44
|
+
*
|
|
45
|
+
* @param prompt - The feature description or task
|
|
46
|
+
* @param workdir - Project root directory
|
|
47
|
+
* @param config - Ngent configuration
|
|
48
|
+
* @param options - Command options (interactive, from)
|
|
49
|
+
* @returns Path to the generated spec file
|
|
50
|
+
*/
|
|
51
|
+
export async function planCommand(
|
|
52
|
+
prompt: string,
|
|
53
|
+
workdir: string,
|
|
54
|
+
config: NaxConfig,
|
|
55
|
+
options: {
|
|
56
|
+
interactive?: boolean;
|
|
57
|
+
from?: string;
|
|
58
|
+
} = {},
|
|
59
|
+
): Promise<string> {
|
|
60
|
+
const interactive = options.interactive !== false; // Default to true
|
|
61
|
+
const ngentDir = join(workdir, "nax");
|
|
62
|
+
const outputPath = join(ngentDir, config.plan.outputPath);
|
|
63
|
+
|
|
64
|
+
// Ensure nax directory exists
|
|
65
|
+
if (!existsSync(ngentDir)) {
|
|
66
|
+
throw new Error(`nax directory not found. Run 'nax init' first in ${workdir}`);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// Scan codebase for context
|
|
70
|
+
const logger = getLogger();
|
|
71
|
+
logger.info("cli", "Scanning codebase...");
|
|
72
|
+
const scan = await scanCodebase(workdir);
|
|
73
|
+
|
|
74
|
+
// Build codebase context markdown
|
|
75
|
+
const codebaseContext = buildCodebaseContext(scan);
|
|
76
|
+
|
|
77
|
+
// Resolve model for planning
|
|
78
|
+
const modelTier = config.plan.model;
|
|
79
|
+
const modelEntry = config.models[modelTier];
|
|
80
|
+
const modelDef = resolveModel(modelEntry);
|
|
81
|
+
|
|
82
|
+
// Build full prompt with template
|
|
83
|
+
const fullPrompt = buildPlanPrompt(prompt, SPEC_TEMPLATE);
|
|
84
|
+
|
|
85
|
+
// Prepare plan options
|
|
86
|
+
const planOptions: PlanOptions = {
|
|
87
|
+
prompt: fullPrompt,
|
|
88
|
+
workdir,
|
|
89
|
+
interactive,
|
|
90
|
+
codebaseContext,
|
|
91
|
+
inputFile: options.from,
|
|
92
|
+
modelTier,
|
|
93
|
+
modelDef,
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
// Run agent in plan mode
|
|
97
|
+
const adapter = new ClaudeCodeAdapter();
|
|
98
|
+
|
|
99
|
+
logger.info("cli", interactive ? "Starting interactive planning session..." : `Reading from ${options.from}...`, {
|
|
100
|
+
interactive,
|
|
101
|
+
from: options.from,
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
const result = await adapter.plan(planOptions);
|
|
105
|
+
|
|
106
|
+
// Write spec to output file
|
|
107
|
+
if (interactive) {
|
|
108
|
+
// In interactive mode, the agent may have written directly
|
|
109
|
+
// But we also capture and write to ensure consistency
|
|
110
|
+
if (result.specContent) {
|
|
111
|
+
await Bun.write(outputPath, result.specContent);
|
|
112
|
+
} else {
|
|
113
|
+
// If agent wrote directly, verify it exists
|
|
114
|
+
if (!existsSync(outputPath)) {
|
|
115
|
+
throw new Error(`Interactive planning completed but spec not found at ${outputPath}`);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
} else {
|
|
119
|
+
// In non-interactive mode, we have the spec in result
|
|
120
|
+
if (!result.specContent) {
|
|
121
|
+
throw new Error("Agent did not produce specification content");
|
|
122
|
+
}
|
|
123
|
+
await Bun.write(outputPath, result.specContent);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
logger.info("cli", "✓ Specification written to output", { outputPath });
|
|
127
|
+
|
|
128
|
+
return outputPath;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Build codebase context markdown from scan results.
|
|
133
|
+
*
|
|
134
|
+
* @param scan - Codebase scan result
|
|
135
|
+
* @returns Formatted context string
|
|
136
|
+
*/
|
|
137
|
+
function buildCodebaseContext(scan: {
|
|
138
|
+
fileTree: string;
|
|
139
|
+
dependencies: Record<string, string>;
|
|
140
|
+
devDependencies: Record<string, string>;
|
|
141
|
+
testPatterns: string[];
|
|
142
|
+
}): string {
|
|
143
|
+
const sections: string[] = [];
|
|
144
|
+
|
|
145
|
+
// File tree
|
|
146
|
+
sections.push("## Codebase Structure\n");
|
|
147
|
+
sections.push("```");
|
|
148
|
+
sections.push(scan.fileTree);
|
|
149
|
+
sections.push("```\n");
|
|
150
|
+
|
|
151
|
+
// Dependencies
|
|
152
|
+
const allDeps = { ...scan.dependencies, ...scan.devDependencies };
|
|
153
|
+
const depList = Object.entries(allDeps)
|
|
154
|
+
.map(([name, version]) => `- ${name}@${version}`)
|
|
155
|
+
.join("\n");
|
|
156
|
+
|
|
157
|
+
if (depList) {
|
|
158
|
+
sections.push("## Dependencies\n");
|
|
159
|
+
sections.push(depList);
|
|
160
|
+
sections.push("");
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// Test patterns
|
|
164
|
+
if (scan.testPatterns.length > 0) {
|
|
165
|
+
sections.push("## Test Setup\n");
|
|
166
|
+
sections.push(scan.testPatterns.map((p) => `- ${p}`).join("\n"));
|
|
167
|
+
sections.push("");
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
return sections.join("\n");
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* Build the full planning prompt with template.
|
|
175
|
+
*
|
|
176
|
+
* @param userPrompt - User's task description
|
|
177
|
+
* @param template - Spec template
|
|
178
|
+
* @returns Full prompt with instructions
|
|
179
|
+
*/
|
|
180
|
+
function buildPlanPrompt(userPrompt: string, template: string): string {
|
|
181
|
+
return `You are helping plan a new feature for this codebase.
|
|
182
|
+
|
|
183
|
+
Task: ${userPrompt}
|
|
184
|
+
|
|
185
|
+
Please gather requirements and produce a structured specification following this template:
|
|
186
|
+
|
|
187
|
+
${template}
|
|
188
|
+
|
|
189
|
+
Ask clarifying questions as needed to ensure the spec is complete and unambiguous.
|
|
190
|
+
Focus on understanding:
|
|
191
|
+
- The problem being solved
|
|
192
|
+
- Specific requirements and constraints
|
|
193
|
+
- Acceptance criteria for success
|
|
194
|
+
- Technical approach and architecture
|
|
195
|
+
- What is explicitly out of scope
|
|
196
|
+
|
|
197
|
+
When done, output the complete specification in markdown format.`;
|
|
198
|
+
}
|