@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,282 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Acceptance Test Generator
|
|
3
|
+
*
|
|
4
|
+
* Parses spec.md acceptance criteria (AC-N lines) and generates acceptance.test.ts
|
|
5
|
+
* via LLM call to the agent adapter.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type { AgentAdapter } from "../agents/types";
|
|
9
|
+
import { getLogger } from "../logger";
|
|
10
|
+
import type { AcceptanceCriterion, AcceptanceTestResult, GenerateAcceptanceTestsOptions } from "./types";
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Parse acceptance criteria from spec.md content.
|
|
14
|
+
*
|
|
15
|
+
* Extracts lines matching "AC-N: description" or "- AC-N: description" patterns.
|
|
16
|
+
*
|
|
17
|
+
* @param specContent - Full spec.md markdown content
|
|
18
|
+
* @returns Array of extracted acceptance criteria
|
|
19
|
+
*
|
|
20
|
+
* @example
|
|
21
|
+
* ```ts
|
|
22
|
+
* const spec = `
|
|
23
|
+
* ## Acceptance Criteria
|
|
24
|
+
* - AC-1: System should handle empty input
|
|
25
|
+
* - AC-2: set(key, value, ttl) expires after ttl milliseconds
|
|
26
|
+
* `;
|
|
27
|
+
* const criteria = parseAcceptanceCriteria(spec);
|
|
28
|
+
* // Returns: [
|
|
29
|
+
* // { id: "AC-1", text: "System should handle empty input", lineNumber: 3 },
|
|
30
|
+
* // { id: "AC-2", text: "set(key, value, ttl) expires after ttl", lineNumber: 4 },
|
|
31
|
+
* // ]
|
|
32
|
+
* ```
|
|
33
|
+
*/
|
|
34
|
+
export function parseAcceptanceCriteria(specContent: string): AcceptanceCriterion[] {
|
|
35
|
+
const criteria: AcceptanceCriterion[] = [];
|
|
36
|
+
const lines = specContent.split("\n");
|
|
37
|
+
|
|
38
|
+
for (let i = 0; i < lines.length; i++) {
|
|
39
|
+
const line = lines[i];
|
|
40
|
+
const lineNumber = i + 1;
|
|
41
|
+
|
|
42
|
+
// Match patterns:
|
|
43
|
+
// - AC-1: description
|
|
44
|
+
// - [ ] AC-1: description
|
|
45
|
+
// AC-1: description
|
|
46
|
+
const acMatch = line.match(/^\s*-?\s*(?:\[.\])?\s*(AC-\d+):\s*(.+)$/i);
|
|
47
|
+
|
|
48
|
+
if (acMatch) {
|
|
49
|
+
const id = acMatch[1].toUpperCase(); // Normalize to uppercase
|
|
50
|
+
const text = acMatch[2].trim();
|
|
51
|
+
|
|
52
|
+
criteria.push({
|
|
53
|
+
id,
|
|
54
|
+
text,
|
|
55
|
+
lineNumber,
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
return criteria;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Build LLM prompt for generating acceptance tests.
|
|
65
|
+
*
|
|
66
|
+
* Combines acceptance criteria, codebase context, and test generation instructions.
|
|
67
|
+
*
|
|
68
|
+
* @param criteria - Extracted acceptance criteria
|
|
69
|
+
* @param featureName - Feature name for context
|
|
70
|
+
* @param codebaseContext - File tree, dependencies, test patterns
|
|
71
|
+
* @returns Formatted prompt string
|
|
72
|
+
*
|
|
73
|
+
* @example
|
|
74
|
+
* ```ts
|
|
75
|
+
* const prompt = buildAcceptanceTestPrompt(
|
|
76
|
+
* [{ id: "AC-1", text: "handles empty input", lineNumber: 5 }],
|
|
77
|
+
* "url-shortener",
|
|
78
|
+
* "File tree:\nsrc/\n index.ts\n"
|
|
79
|
+
* );
|
|
80
|
+
* ```
|
|
81
|
+
*/
|
|
82
|
+
export function buildAcceptanceTestPrompt(
|
|
83
|
+
criteria: AcceptanceCriterion[],
|
|
84
|
+
featureName: string,
|
|
85
|
+
codebaseContext: string,
|
|
86
|
+
): string {
|
|
87
|
+
const criteriaList = criteria.map((ac) => `${ac.id}: ${ac.text}`).join("\n");
|
|
88
|
+
|
|
89
|
+
return `You are a test engineer. Generate acceptance tests for the "${featureName}" feature based on the acceptance criteria below.
|
|
90
|
+
|
|
91
|
+
CODEBASE CONTEXT:
|
|
92
|
+
${codebaseContext}
|
|
93
|
+
|
|
94
|
+
ACCEPTANCE CRITERIA:
|
|
95
|
+
${criteriaList}
|
|
96
|
+
|
|
97
|
+
Generate a complete acceptance.test.ts file using bun:test framework. Follow these rules:
|
|
98
|
+
|
|
99
|
+
1. **One test per AC**: Each acceptance criterion maps to exactly one test
|
|
100
|
+
2. **Test observable behavior only**: No implementation details, only user-facing behavior
|
|
101
|
+
3. **Independent tests**: No shared state between tests
|
|
102
|
+
4. **Integration-level**: Tests should be runnable without mocking (use real implementations)
|
|
103
|
+
5. **Clear test names**: Use format "AC-N: <description>" for test names
|
|
104
|
+
6. **Async where needed**: Use async/await for operations that may be asynchronous
|
|
105
|
+
|
|
106
|
+
Use this structure:
|
|
107
|
+
|
|
108
|
+
\`\`\`typescript
|
|
109
|
+
import { describe, test, expect } from "bun:test";
|
|
110
|
+
|
|
111
|
+
describe("${featureName} - Acceptance Tests", () => {
|
|
112
|
+
test("AC-1: <description>", async () => {
|
|
113
|
+
// Test implementation
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
test("AC-2: <description>", async () => {
|
|
117
|
+
// Test implementation
|
|
118
|
+
});
|
|
119
|
+
});
|
|
120
|
+
\`\`\`
|
|
121
|
+
|
|
122
|
+
**Important**:
|
|
123
|
+
- Import the feature code being tested
|
|
124
|
+
- Set up any necessary test fixtures
|
|
125
|
+
- Use expect() assertions to verify behavior
|
|
126
|
+
- Clean up resources if needed (close connections, delete temp files)
|
|
127
|
+
|
|
128
|
+
Respond with ONLY the TypeScript test code (no markdown code fences, no explanation).`;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Generate acceptance tests from spec.md acceptance criteria.
|
|
133
|
+
*
|
|
134
|
+
* Parses AC lines from spec, builds LLM prompt, calls agent adapter,
|
|
135
|
+
* and returns generated test code. Falls back to skeleton tests if LLM fails.
|
|
136
|
+
*
|
|
137
|
+
* @param adapter - Agent adapter to use for test generation
|
|
138
|
+
* @param options - Generation options with spec content, context, and model
|
|
139
|
+
* @returns Generated test code and processed criteria
|
|
140
|
+
* @throws Error if AC parsing fails or agent call fails critically
|
|
141
|
+
*
|
|
142
|
+
* @example
|
|
143
|
+
* ```ts
|
|
144
|
+
* const adapter = new ClaudeCodeAdapter();
|
|
145
|
+
* const result = await generateAcceptanceTests(adapter, {
|
|
146
|
+
* specContent: await Bun.file("spec.md").text(),
|
|
147
|
+
* featureName: "url-shortener",
|
|
148
|
+
* workdir: "/project",
|
|
149
|
+
* codebaseContext: "File tree:\nsrc/\n",
|
|
150
|
+
* modelTier: "balanced",
|
|
151
|
+
* modelDef: { provider: "anthropic", model: "claude-sonnet-4-5" },
|
|
152
|
+
* });
|
|
153
|
+
*
|
|
154
|
+
* await Bun.write("acceptance.test.ts", result.testCode);
|
|
155
|
+
* ```
|
|
156
|
+
*/
|
|
157
|
+
export async function generateAcceptanceTests(
|
|
158
|
+
adapter: AgentAdapter,
|
|
159
|
+
options: GenerateAcceptanceTestsOptions,
|
|
160
|
+
): Promise<AcceptanceTestResult> {
|
|
161
|
+
// Parse acceptance criteria from spec
|
|
162
|
+
const logger = getLogger();
|
|
163
|
+
const criteria = parseAcceptanceCriteria(options.specContent);
|
|
164
|
+
|
|
165
|
+
if (criteria.length === 0) {
|
|
166
|
+
// No AC found — generate empty skeleton
|
|
167
|
+
logger.warn("acceptance", "⚠ No acceptance criteria found in spec.md");
|
|
168
|
+
return {
|
|
169
|
+
testCode: generateSkeletonTests(options.featureName, []),
|
|
170
|
+
criteria: [],
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
logger.info("acceptance", "Found acceptance criteria", { count: criteria.length });
|
|
175
|
+
|
|
176
|
+
// Build prompt
|
|
177
|
+
const prompt = buildAcceptanceTestPrompt(criteria, options.featureName, options.codebaseContext);
|
|
178
|
+
|
|
179
|
+
try {
|
|
180
|
+
// Call agent to generate tests (using decompose as pattern)
|
|
181
|
+
const cmd = [adapter.binary, "--model", options.modelDef.model, "--dangerously-skip-permissions", "-p", prompt];
|
|
182
|
+
|
|
183
|
+
const proc = Bun.spawn(cmd, {
|
|
184
|
+
cwd: options.workdir,
|
|
185
|
+
stdout: "pipe",
|
|
186
|
+
stderr: "pipe",
|
|
187
|
+
env: {
|
|
188
|
+
...process.env,
|
|
189
|
+
...(options.modelDef.env || {}),
|
|
190
|
+
},
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
const exitCode = await proc.exited;
|
|
194
|
+
const stdout = await new Response(proc.stdout).text();
|
|
195
|
+
const stderr = await new Response(proc.stderr).text();
|
|
196
|
+
|
|
197
|
+
if (exitCode !== 0) {
|
|
198
|
+
logger.warn("acceptance", "⚠ Agent test generation failed", { stderr });
|
|
199
|
+
// Fall back to skeleton
|
|
200
|
+
return {
|
|
201
|
+
testCode: generateSkeletonTests(options.featureName, criteria),
|
|
202
|
+
criteria,
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// Extract test code from output
|
|
207
|
+
const testCode = extractTestCode(stdout);
|
|
208
|
+
|
|
209
|
+
return {
|
|
210
|
+
testCode,
|
|
211
|
+
criteria,
|
|
212
|
+
};
|
|
213
|
+
} catch (error) {
|
|
214
|
+
logger.warn("acceptance", "⚠ Agent test generation error", { error: (error as Error).message });
|
|
215
|
+
// Fall back to skeleton
|
|
216
|
+
return {
|
|
217
|
+
testCode: generateSkeletonTests(options.featureName, criteria),
|
|
218
|
+
criteria,
|
|
219
|
+
};
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/**
|
|
224
|
+
* Extract TypeScript test code from agent output.
|
|
225
|
+
*
|
|
226
|
+
* Handles markdown code fences and extracts clean test code.
|
|
227
|
+
*
|
|
228
|
+
* @param output - Agent stdout
|
|
229
|
+
* @returns Extracted test code
|
|
230
|
+
*/
|
|
231
|
+
function extractTestCode(output: string): string {
|
|
232
|
+
// Try to extract from markdown code fence
|
|
233
|
+
const fenceMatch = output.match(/```(?:typescript|ts)?\s*([\s\S]*?)\s*```/);
|
|
234
|
+
if (fenceMatch) {
|
|
235
|
+
return fenceMatch[1].trim();
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
// If no fence, try to find import statement and take everything from there
|
|
239
|
+
const importMatch = output.match(/import\s+{[\s\S]+/);
|
|
240
|
+
if (importMatch) {
|
|
241
|
+
return importMatch[0].trim();
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
// Fall back to full output
|
|
245
|
+
return output.trim();
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
/**
|
|
249
|
+
* Generate skeleton acceptance tests with TODO placeholders.
|
|
250
|
+
*
|
|
251
|
+
* Used as fallback when LLM test generation fails.
|
|
252
|
+
*
|
|
253
|
+
* @param featureName - Feature name
|
|
254
|
+
* @param criteria - Acceptance criteria to generate skeletons for
|
|
255
|
+
* @returns TypeScript test code with TODO placeholders
|
|
256
|
+
*
|
|
257
|
+
* @example
|
|
258
|
+
* ```ts
|
|
259
|
+
* const skeleton = generateSkeletonTests("auth", [
|
|
260
|
+
* { id: "AC-1", text: "login succeeds", lineNumber: 5 },
|
|
261
|
+
* ]);
|
|
262
|
+
* // Generates test with TODO comment
|
|
263
|
+
* ```
|
|
264
|
+
*/
|
|
265
|
+
export function generateSkeletonTests(featureName: string, criteria: AcceptanceCriterion[]): string {
|
|
266
|
+
const tests = criteria
|
|
267
|
+
.map((ac) => {
|
|
268
|
+
return ` test("${ac.id}: ${ac.text}", async () => {
|
|
269
|
+
// TODO: Implement acceptance test for ${ac.id}
|
|
270
|
+
// ${ac.text}
|
|
271
|
+
expect(true).toBe(false); // Replace with actual test
|
|
272
|
+
});`;
|
|
273
|
+
})
|
|
274
|
+
.join("\n\n");
|
|
275
|
+
|
|
276
|
+
return `import { describe, test, expect } from "bun:test";
|
|
277
|
+
|
|
278
|
+
describe("${featureName} - Acceptance Tests", () => {
|
|
279
|
+
${tests || " // No acceptance criteria found"}
|
|
280
|
+
});
|
|
281
|
+
`;
|
|
282
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Acceptance Test Generation Module
|
|
3
|
+
*
|
|
4
|
+
* Barrel exports for acceptance test generation functionality.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
export type {
|
|
8
|
+
AcceptanceCriterion,
|
|
9
|
+
GenerateAcceptanceTestsOptions,
|
|
10
|
+
AcceptanceTestResult,
|
|
11
|
+
} from "./types";
|
|
12
|
+
|
|
13
|
+
export {
|
|
14
|
+
parseAcceptanceCriteria,
|
|
15
|
+
buildAcceptanceTestPrompt,
|
|
16
|
+
generateAcceptanceTests,
|
|
17
|
+
generateSkeletonTests,
|
|
18
|
+
} from "./generator";
|
|
19
|
+
|
|
20
|
+
export type {
|
|
21
|
+
FixStory,
|
|
22
|
+
GenerateFixStoriesOptions,
|
|
23
|
+
} from "./fix-generator";
|
|
24
|
+
|
|
25
|
+
export {
|
|
26
|
+
generateFixStories,
|
|
27
|
+
findRelatedStories,
|
|
28
|
+
parseACTextFromSpec,
|
|
29
|
+
convertFixStoryToUserStory,
|
|
30
|
+
} from "./fix-generator";
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Acceptance Test Generation Types
|
|
3
|
+
*
|
|
4
|
+
* Types for generating acceptance tests from spec.md acceptance criteria.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { ModelDef, ModelTier } from "../config/schema";
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* A single acceptance criterion extracted from spec.md.
|
|
11
|
+
*
|
|
12
|
+
* @example
|
|
13
|
+
* ```ts
|
|
14
|
+
* const ac: AcceptanceCriterion = {
|
|
15
|
+
* id: "AC-2",
|
|
16
|
+
* text: "set(key, value, ttl) expires after ttl milliseconds",
|
|
17
|
+
* lineNumber: 42,
|
|
18
|
+
* };
|
|
19
|
+
* ```
|
|
20
|
+
*/
|
|
21
|
+
export interface AcceptanceCriterion {
|
|
22
|
+
/** AC identifier (e.g., "AC-1", "AC-2") */
|
|
23
|
+
id: string;
|
|
24
|
+
/** Full criterion text */
|
|
25
|
+
text: string;
|
|
26
|
+
/** Line number in spec.md for reference */
|
|
27
|
+
lineNumber: number;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Options for generating acceptance tests.
|
|
32
|
+
*
|
|
33
|
+
* @example
|
|
34
|
+
* ```ts
|
|
35
|
+
* const options: GenerateAcceptanceTestsOptions = {
|
|
36
|
+
* specContent: "# Feature\n\n## Acceptance Criteria\n- AC-1: ...",
|
|
37
|
+
* featureName: "url-shortener",
|
|
38
|
+
* workdir: "/home/user/project",
|
|
39
|
+
* codebaseContext: "File tree:\nsrc/\n index.ts\n",
|
|
40
|
+
* modelTier: "balanced",
|
|
41
|
+
* modelDef: { provider: "anthropic", model: "claude-sonnet-4-5" },
|
|
42
|
+
* };
|
|
43
|
+
* ```
|
|
44
|
+
*/
|
|
45
|
+
export interface GenerateAcceptanceTestsOptions {
|
|
46
|
+
/** Full spec.md content */
|
|
47
|
+
specContent: string;
|
|
48
|
+
/** Feature name for context */
|
|
49
|
+
featureName: string;
|
|
50
|
+
/** Working directory for context scanning */
|
|
51
|
+
workdir: string;
|
|
52
|
+
/** Codebase context (file tree, dependencies, test patterns) */
|
|
53
|
+
codebaseContext: string;
|
|
54
|
+
/** Model tier to use for test generation */
|
|
55
|
+
modelTier: ModelTier;
|
|
56
|
+
/** Resolved model definition */
|
|
57
|
+
modelDef: ModelDef;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Result from acceptance test generation.
|
|
62
|
+
*
|
|
63
|
+
* @example
|
|
64
|
+
* ```ts
|
|
65
|
+
* const result: AcceptanceTestResult = {
|
|
66
|
+
* testCode: 'import { describe, test, expect } from "bun:test";\n\n...',
|
|
67
|
+
* criteria: [
|
|
68
|
+
* { id: "AC-1", text: "TTL expires", lineNumber: 12 },
|
|
69
|
+
* { id: "AC-2", text: "set(key, value, ttl) expires after ttl", lineNumber: 13 },
|
|
70
|
+
* ],
|
|
71
|
+
* };
|
|
72
|
+
* ```
|
|
73
|
+
*/
|
|
74
|
+
export interface AcceptanceTestResult {
|
|
75
|
+
/** Generated test code */
|
|
76
|
+
testCode: string;
|
|
77
|
+
/** Acceptance criteria that were processed */
|
|
78
|
+
criteria: AcceptanceCriterion[];
|
|
79
|
+
}
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Claude Code Decompose Logic
|
|
3
|
+
*
|
|
4
|
+
* Extracted from claude.ts: decompose(), buildDecomposePrompt(),
|
|
5
|
+
* parseDecomposeOutput(), validateComplexity()
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type { DecomposeOptions, DecomposeResult, DecomposedStory } from "./types";
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Build the decompose prompt combining spec content and codebase context.
|
|
12
|
+
*/
|
|
13
|
+
export function buildDecomposePrompt(options: DecomposeOptions): string {
|
|
14
|
+
return `You are a requirements analyst. Break down the following feature specification into user stories and classify each story's complexity.
|
|
15
|
+
|
|
16
|
+
CODEBASE CONTEXT:
|
|
17
|
+
${options.codebaseContext}
|
|
18
|
+
|
|
19
|
+
FEATURE SPECIFICATION:
|
|
20
|
+
${options.specContent}
|
|
21
|
+
|
|
22
|
+
Decompose this spec into user stories. For each story, provide:
|
|
23
|
+
1. id: Story ID (e.g., "US-001")
|
|
24
|
+
2. title: Concise story title
|
|
25
|
+
3. description: What needs to be implemented
|
|
26
|
+
4. acceptanceCriteria: Array of testable criteria
|
|
27
|
+
5. tags: Array of routing tags (e.g., ["security", "api"])
|
|
28
|
+
6. dependencies: Array of story IDs this depends on (e.g., ["US-001"])
|
|
29
|
+
7. complexity: "simple" | "medium" | "complex" | "expert"
|
|
30
|
+
8. contextFiles: Array of file paths to inject into agent prompt before execution
|
|
31
|
+
9. reasoning: Why this complexity level
|
|
32
|
+
10. estimatedLOC: Estimated lines of code to change
|
|
33
|
+
11. risks: Array of implementation risks
|
|
34
|
+
12. testStrategy: "three-session-tdd" | "test-after"
|
|
35
|
+
|
|
36
|
+
testStrategy rules:
|
|
37
|
+
- "three-session-tdd": ONLY for complex/expert tasks that are security-critical (auth, encryption, tokens, credentials) or define public API contracts consumers depend on
|
|
38
|
+
- "test-after": for all other tasks including simple/medium complexity
|
|
39
|
+
- A "simple" complexity task should almost never be "three-session-tdd"
|
|
40
|
+
|
|
41
|
+
Complexity classification rules:
|
|
42
|
+
- simple: 1-3 files, <100 LOC, straightforward implementation, existing patterns
|
|
43
|
+
- medium: 3-6 files, 100-300 LOC, moderate logic, some new patterns
|
|
44
|
+
- complex: 6+ files, 300-800 LOC, architectural changes, cross-cutting concerns
|
|
45
|
+
- expert: Security/crypto/real-time/distributed systems, >800 LOC, new infrastructure
|
|
46
|
+
|
|
47
|
+
Grouping Guidelines:
|
|
48
|
+
- Combine small, related tasks (e.g., multiple utility functions, interfaces) into a single "simple" or "medium" story.
|
|
49
|
+
- Do NOT create separate stories for every single file or function unless complex.
|
|
50
|
+
- Aim for coherent units of value (e.g., "Implement User Authentication" vs "Create User Interface", "Create Login Service").
|
|
51
|
+
- Maximum recommended stories: 10-15 per feature. Group aggressively if list grows too long.
|
|
52
|
+
|
|
53
|
+
Consider:
|
|
54
|
+
1. Does infrastructure exist? (e.g., "add caching" when no cache layer exists = complex)
|
|
55
|
+
2. How many files will be touched?
|
|
56
|
+
3. Are there cross-cutting concerns (auth, validation, error handling)?
|
|
57
|
+
4. Does it require new dependencies or architectural decisions?
|
|
58
|
+
|
|
59
|
+
Respond with ONLY a JSON array (no markdown code fences):
|
|
60
|
+
[{
|
|
61
|
+
"id": "US-001",
|
|
62
|
+
"title": "Story title",
|
|
63
|
+
"description": "Story description",
|
|
64
|
+
"acceptanceCriteria": ["Criterion 1", "Criterion 2"],
|
|
65
|
+
"tags": ["tag1"],
|
|
66
|
+
"dependencies": [],
|
|
67
|
+
"complexity": "medium",
|
|
68
|
+
"contextFiles": ["src/path/to/file.ts"],
|
|
69
|
+
"reasoning": "Why this complexity level",
|
|
70
|
+
"estimatedLOC": 150,
|
|
71
|
+
"risks": ["Risk 1"],
|
|
72
|
+
"testStrategy": "test-after"
|
|
73
|
+
}]`;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Parse decompose output from agent stdout.
|
|
78
|
+
*
|
|
79
|
+
* Extracts JSON array from output, handles markdown code fences,
|
|
80
|
+
* and validates structure.
|
|
81
|
+
*/
|
|
82
|
+
export function parseDecomposeOutput(output: string): DecomposedStory[] {
|
|
83
|
+
// Extract JSON from output (handles markdown code fences)
|
|
84
|
+
const jsonMatch = output.match(/```(?:json)?\s*(\[[\s\S]*?\])\s*```/);
|
|
85
|
+
let jsonText = jsonMatch ? jsonMatch[1] : output;
|
|
86
|
+
|
|
87
|
+
// Try to find JSON array directly if no code fence
|
|
88
|
+
if (!jsonMatch) {
|
|
89
|
+
const arrayMatch = output.match(/\[[\s\S]*\]/);
|
|
90
|
+
if (arrayMatch) {
|
|
91
|
+
jsonText = arrayMatch[0];
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// Parse JSON
|
|
96
|
+
let parsed: unknown;
|
|
97
|
+
try {
|
|
98
|
+
parsed = JSON.parse(jsonText.trim());
|
|
99
|
+
} catch (error) {
|
|
100
|
+
throw new Error(
|
|
101
|
+
`Failed to parse decompose output as JSON: ${(error as Error).message}\n\nOutput:\n${output.slice(0, 500)}`,
|
|
102
|
+
);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// Validate structure
|
|
106
|
+
if (!Array.isArray(parsed)) {
|
|
107
|
+
throw new Error("Decompose output is not an array");
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// Map to DecomposedStory[] with validation
|
|
111
|
+
const stories: DecomposedStory[] = parsed.map((item: unknown, index: number) => {
|
|
112
|
+
// Type guard: ensure item is an object
|
|
113
|
+
if (typeof item !== "object" || item === null) {
|
|
114
|
+
throw new Error(`Story at index ${index} is not an object`);
|
|
115
|
+
}
|
|
116
|
+
const record = item as Record<string, unknown>;
|
|
117
|
+
if (!record.id || typeof record.id !== "string") {
|
|
118
|
+
throw new Error(`Story at index ${index} missing valid 'id' field`);
|
|
119
|
+
}
|
|
120
|
+
if (!record.title || typeof record.title !== "string") {
|
|
121
|
+
throw new Error(`Story ${record.id} missing valid 'title' field`);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
return {
|
|
125
|
+
id: record.id,
|
|
126
|
+
title: record.title,
|
|
127
|
+
description: String(record.description || record.title),
|
|
128
|
+
acceptanceCriteria: Array.isArray(record.acceptanceCriteria)
|
|
129
|
+
? record.acceptanceCriteria
|
|
130
|
+
: ["Implementation complete"],
|
|
131
|
+
tags: Array.isArray(record.tags) ? record.tags : [],
|
|
132
|
+
dependencies: Array.isArray(record.dependencies) ? record.dependencies : [],
|
|
133
|
+
complexity: validateComplexity(record.complexity),
|
|
134
|
+
// contextFiles: prefer the new field; fall back to legacy relevantFiles from older LLM responses
|
|
135
|
+
contextFiles: Array.isArray(record.contextFiles)
|
|
136
|
+
? record.contextFiles
|
|
137
|
+
: Array.isArray(record.relevantFiles)
|
|
138
|
+
? record.relevantFiles
|
|
139
|
+
: [],
|
|
140
|
+
relevantFiles: Array.isArray(record.relevantFiles) ? record.relevantFiles : [],
|
|
141
|
+
reasoning: String(record.reasoning || "No reasoning provided"),
|
|
142
|
+
estimatedLOC: Number(record.estimatedLOC) || 0,
|
|
143
|
+
risks: Array.isArray(record.risks) ? record.risks : [],
|
|
144
|
+
testStrategy:
|
|
145
|
+
record.testStrategy === "three-session-tdd"
|
|
146
|
+
? "three-session-tdd"
|
|
147
|
+
: record.testStrategy === "test-after"
|
|
148
|
+
? "test-after"
|
|
149
|
+
: undefined,
|
|
150
|
+
};
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
if (stories.length === 0) {
|
|
154
|
+
throw new Error("Decompose returned empty story array");
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
return stories;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Validate complexity value from decompose output.
|
|
162
|
+
*/
|
|
163
|
+
export function validateComplexity(value: unknown): "simple" | "medium" | "complex" | "expert" {
|
|
164
|
+
if (value === "simple" || value === "medium" || value === "complex" || value === "expert") {
|
|
165
|
+
return value;
|
|
166
|
+
}
|
|
167
|
+
// Default to medium if invalid
|
|
168
|
+
return "medium";
|
|
169
|
+
}
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Claude Code Plan Logic
|
|
3
|
+
*
|
|
4
|
+
* Extracted from claude.ts: plan(), buildPlanCommand()
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { PidRegistry } from "../execution/pid-registry";
|
|
8
|
+
import { getLogger } from "../logger";
|
|
9
|
+
import type { AgentRunOptions } from "./types";
|
|
10
|
+
import type { PlanOptions, PlanResult } from "./types-extended";
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Build the CLI command for plan mode.
|
|
14
|
+
*/
|
|
15
|
+
export function buildPlanCommand(binary: string, options: PlanOptions): string[] {
|
|
16
|
+
const cmd = [binary, "--permission-mode", "plan"];
|
|
17
|
+
|
|
18
|
+
// Add model if specified
|
|
19
|
+
if (options.modelDef) {
|
|
20
|
+
cmd.push("--model", options.modelDef.model);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// Add dangerously-skip-permissions for automation
|
|
24
|
+
cmd.push("--dangerously-skip-permissions");
|
|
25
|
+
|
|
26
|
+
// Add prompt with codebase context and input file if available
|
|
27
|
+
let fullPrompt = options.prompt;
|
|
28
|
+
if (options.codebaseContext) {
|
|
29
|
+
fullPrompt = `${options.codebaseContext}\n\n${options.prompt}`;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// For non-interactive mode, include input file content in the prompt
|
|
33
|
+
if (options.inputFile) {
|
|
34
|
+
try {
|
|
35
|
+
const inputContent = require("node:fs").readFileSync(
|
|
36
|
+
require("node:path").resolve(options.workdir, options.inputFile),
|
|
37
|
+
"utf-8",
|
|
38
|
+
);
|
|
39
|
+
fullPrompt = `${fullPrompt}\n\n## Input Requirements\n\n${inputContent}`;
|
|
40
|
+
} catch (error) {
|
|
41
|
+
throw new Error(`Failed to read input file ${options.inputFile}: ${(error as Error).message}`);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
if (!options.interactive) {
|
|
46
|
+
cmd.push("-p", fullPrompt);
|
|
47
|
+
} else {
|
|
48
|
+
// Interactive mode: pass prompt as initial message, agent will ask follow-ups
|
|
49
|
+
cmd.push("-p", fullPrompt);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
return cmd;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Run Claude Code in plan mode to generate a feature specification.
|
|
57
|
+
*/
|
|
58
|
+
export async function runPlan(
|
|
59
|
+
binary: string,
|
|
60
|
+
options: PlanOptions,
|
|
61
|
+
pidRegistry: PidRegistry,
|
|
62
|
+
buildAllowedEnv: (options: AgentRunOptions) => Record<string, string | undefined>,
|
|
63
|
+
): Promise<PlanResult> {
|
|
64
|
+
const cmd = buildPlanCommand(binary, options);
|
|
65
|
+
|
|
66
|
+
const envOptions: AgentRunOptions = {
|
|
67
|
+
workdir: options.workdir,
|
|
68
|
+
modelDef: options.modelDef || { provider: "anthropic", model: "claude-sonnet-4-5", env: {} },
|
|
69
|
+
prompt: "",
|
|
70
|
+
modelTier: "balanced",
|
|
71
|
+
timeoutSeconds: 600,
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
if (options.interactive) {
|
|
75
|
+
// Interactive mode: inherit stdio
|
|
76
|
+
const proc = Bun.spawn(cmd, {
|
|
77
|
+
cwd: options.workdir,
|
|
78
|
+
stdin: "inherit",
|
|
79
|
+
stdout: "inherit",
|
|
80
|
+
stderr: "inherit",
|
|
81
|
+
env: buildAllowedEnv(envOptions),
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
// Register PID
|
|
85
|
+
await pidRegistry.register(proc.pid);
|
|
86
|
+
|
|
87
|
+
const exitCode = await proc.exited;
|
|
88
|
+
|
|
89
|
+
// Unregister PID after exit
|
|
90
|
+
await pidRegistry.unregister(proc.pid);
|
|
91
|
+
|
|
92
|
+
if (exitCode !== 0) {
|
|
93
|
+
throw new Error(`Plan mode failed with exit code ${exitCode}`);
|
|
94
|
+
}
|
|
95
|
+
return { specContent: "", conversationLog: "" };
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// Non-interactive: redirect stdout to temp file via Bun.file()
|
|
99
|
+
const { join } = require("node:path");
|
|
100
|
+
const { mkdtempSync, readFileSync, rmSync } = require("node:fs");
|
|
101
|
+
const { tmpdir } = require("node:os");
|
|
102
|
+
const tempDir = mkdtempSync(join(tmpdir(), "nax-plan-"));
|
|
103
|
+
const outFile = join(tempDir, "stdout.txt");
|
|
104
|
+
const errFile = join(tempDir, "stderr.txt");
|
|
105
|
+
|
|
106
|
+
try {
|
|
107
|
+
const proc = Bun.spawn(cmd, {
|
|
108
|
+
cwd: options.workdir,
|
|
109
|
+
stdin: "ignore",
|
|
110
|
+
stdout: Bun.file(outFile),
|
|
111
|
+
stderr: Bun.file(errFile),
|
|
112
|
+
env: buildAllowedEnv(envOptions),
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
// Register PID
|
|
116
|
+
await pidRegistry.register(proc.pid);
|
|
117
|
+
|
|
118
|
+
const exitCode = await proc.exited;
|
|
119
|
+
|
|
120
|
+
// Unregister PID after exit
|
|
121
|
+
await pidRegistry.unregister(proc.pid);
|
|
122
|
+
|
|
123
|
+
const specContent = readFileSync(outFile, "utf-8");
|
|
124
|
+
const conversationLog = readFileSync(errFile, "utf-8");
|
|
125
|
+
|
|
126
|
+
if (exitCode !== 0) {
|
|
127
|
+
throw new Error(`Plan mode failed with exit code ${exitCode}: ${conversationLog || "unknown error"}`);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
return { specContent, conversationLog };
|
|
131
|
+
} finally {
|
|
132
|
+
try {
|
|
133
|
+
rmSync(tempDir, { recursive: true });
|
|
134
|
+
} catch (error) {
|
|
135
|
+
const logger = getLogger();
|
|
136
|
+
logger?.debug("agent", "Failed to clean up temp directory", { error, tempDir });
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
}
|