@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,62 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pipeline Stages
|
|
3
|
+
*
|
|
4
|
+
* Composable stages for the execution pipeline.
|
|
5
|
+
* Each stage performs a specific step in story execution.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type { PipelineStage } from "../types";
|
|
9
|
+
import { acceptanceStage } from "./acceptance";
|
|
10
|
+
import { completionStage } from "./completion";
|
|
11
|
+
import { constitutionStage } from "./constitution";
|
|
12
|
+
import { contextStage } from "./context";
|
|
13
|
+
import { executionStage } from "./execution";
|
|
14
|
+
import { optimizerStage } from "./optimizer";
|
|
15
|
+
import { promptStage } from "./prompt";
|
|
16
|
+
import { queueCheckStage } from "./queue-check";
|
|
17
|
+
import { reviewStage } from "./review";
|
|
18
|
+
import { routingStage } from "./routing";
|
|
19
|
+
import { verifyStage } from "./verify";
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Default pipeline stages in execution order.
|
|
23
|
+
*
|
|
24
|
+
* This is the standard pipeline for executing a story:
|
|
25
|
+
* 1. Check for queue commands (PAUSE/ABORT/SKIP)
|
|
26
|
+
* 2. Route (classify complexity → model tier)
|
|
27
|
+
* 3. Load constitution (project coding standards)
|
|
28
|
+
* 4. Build context (gather relevant code/docs)
|
|
29
|
+
* 5. Assemble prompt (story + context + constitution)
|
|
30
|
+
* 6. Optimize prompt (reduce token usage)
|
|
31
|
+
* 7. Execute agent session (TDD or test-after)
|
|
32
|
+
* 8. Verify output (tests pass, build succeeds)
|
|
33
|
+
* 9. Review (quality checks, linting, etc.)
|
|
34
|
+
* 10. Mark complete (save PRD, fire hooks, log progress)
|
|
35
|
+
* 11. Acceptance (run acceptance tests when all stories complete)
|
|
36
|
+
*/
|
|
37
|
+
export const defaultPipeline: PipelineStage[] = [
|
|
38
|
+
queueCheckStage,
|
|
39
|
+
routingStage,
|
|
40
|
+
constitutionStage,
|
|
41
|
+
contextStage,
|
|
42
|
+
promptStage,
|
|
43
|
+
optimizerStage,
|
|
44
|
+
executionStage,
|
|
45
|
+
verifyStage,
|
|
46
|
+
reviewStage,
|
|
47
|
+
completionStage,
|
|
48
|
+
acceptanceStage,
|
|
49
|
+
];
|
|
50
|
+
|
|
51
|
+
// Re-export individual stages for custom pipeline construction
|
|
52
|
+
export { queueCheckStage } from "./queue-check";
|
|
53
|
+
export { routingStage } from "./routing";
|
|
54
|
+
export { constitutionStage } from "./constitution";
|
|
55
|
+
export { contextStage } from "./context";
|
|
56
|
+
export { promptStage } from "./prompt";
|
|
57
|
+
export { optimizerStage } from "./optimizer";
|
|
58
|
+
export { executionStage } from "./execution";
|
|
59
|
+
export { verifyStage } from "./verify";
|
|
60
|
+
export { reviewStage } from "./review";
|
|
61
|
+
export { completionStage } from "./completion";
|
|
62
|
+
export { acceptanceStage } from "./acceptance";
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Optimizer Stage
|
|
3
|
+
*
|
|
4
|
+
* Optimizes the assembled prompt to reduce token usage while preserving
|
|
5
|
+
* semantic meaning. Runs between prompt assembly and agent execution.
|
|
6
|
+
*
|
|
7
|
+
* Resolution order for optimizer selection:
|
|
8
|
+
* 1. Plugin-provided optimizer (if plugins loaded)
|
|
9
|
+
* 2. Built-in strategy from config (rule-based, noop)
|
|
10
|
+
* 3. Fallback to NoopOptimizer
|
|
11
|
+
*
|
|
12
|
+
* @returns
|
|
13
|
+
* - `continue`: Optimization complete (or skipped if disabled)
|
|
14
|
+
*
|
|
15
|
+
* @example
|
|
16
|
+
* ```ts
|
|
17
|
+
* // With rule-based optimizer enabled
|
|
18
|
+
* await optimizerStage.execute(ctx);
|
|
19
|
+
* // ctx.prompt: optimized, whitespace stripped, criteria compacted
|
|
20
|
+
* // Logs: "optimizer: rule-based: 15% savings"
|
|
21
|
+
*
|
|
22
|
+
* // With optimizer disabled
|
|
23
|
+
* await optimizerStage.execute(ctx);
|
|
24
|
+
* // ctx.prompt: unchanged (passthrough)
|
|
25
|
+
* // Logs: "optimizer: noop: 0% savings"
|
|
26
|
+
* ```
|
|
27
|
+
*/
|
|
28
|
+
|
|
29
|
+
import { getLogger } from "../../logger/index.js";
|
|
30
|
+
import { resolveOptimizer } from "../../optimizer/index.js";
|
|
31
|
+
import type { PipelineContext, PipelineStage, StageResult } from "../types.js";
|
|
32
|
+
|
|
33
|
+
export const optimizerStage: PipelineStage = {
|
|
34
|
+
name: "optimizer",
|
|
35
|
+
enabled: (ctx) => {
|
|
36
|
+
// Always enabled - NoopOptimizer is used when optimization is disabled
|
|
37
|
+
return true;
|
|
38
|
+
},
|
|
39
|
+
|
|
40
|
+
async execute(ctx: PipelineContext): Promise<StageResult> {
|
|
41
|
+
const logger = getLogger();
|
|
42
|
+
|
|
43
|
+
// Ensure prompt exists
|
|
44
|
+
if (!ctx.prompt) {
|
|
45
|
+
logger.warn("optimizer", "No prompt to optimize, skipping");
|
|
46
|
+
return { action: "continue" };
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// Resolve optimizer (checks plugins first, then config)
|
|
50
|
+
const optimizer = resolveOptimizer(ctx.config, ctx.plugins);
|
|
51
|
+
|
|
52
|
+
// Optimize the prompt
|
|
53
|
+
const result = await optimizer.optimize({
|
|
54
|
+
prompt: ctx.prompt,
|
|
55
|
+
stories: ctx.stories,
|
|
56
|
+
contextMarkdown: ctx.contextMarkdown,
|
|
57
|
+
config: ctx.config,
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
// Update context with optimized prompt
|
|
61
|
+
ctx.prompt = result.prompt;
|
|
62
|
+
|
|
63
|
+
// Log optimization results
|
|
64
|
+
const savingsPercent = Math.round(result.savings * 100);
|
|
65
|
+
logger.info("optimizer", `${optimizer.name}: ${savingsPercent}% savings`, {
|
|
66
|
+
originalTokens: result.originalTokens,
|
|
67
|
+
optimizedTokens: result.optimizedTokens,
|
|
68
|
+
tokensSaved: result.originalTokens - result.optimizedTokens,
|
|
69
|
+
appliedRules: result.appliedRules,
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
return { action: "continue" };
|
|
73
|
+
},
|
|
74
|
+
};
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Prompt Stage
|
|
3
|
+
*
|
|
4
|
+
* Assembles the final prompt for the agent from:
|
|
5
|
+
* - Story/stories (batch or single)
|
|
6
|
+
* - Context markdown
|
|
7
|
+
* - Constitution content
|
|
8
|
+
*
|
|
9
|
+
* @returns
|
|
10
|
+
* - `continue`: Prompt built successfully
|
|
11
|
+
*
|
|
12
|
+
* @example
|
|
13
|
+
* ```ts
|
|
14
|
+
* // Single story with constitution
|
|
15
|
+
* await promptStage.execute(ctx);
|
|
16
|
+
* // ctx.prompt: "# CONSTITUTION\n...\n\n# Task: Add login button\n..."
|
|
17
|
+
*
|
|
18
|
+
* // Batch of stories without constitution
|
|
19
|
+
* await promptStage.execute(ctx);
|
|
20
|
+
* // ctx.prompt: "# Batch Task: 3 Stories\n## Story 1: US-001...\n"
|
|
21
|
+
* ```
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
import { buildBatchPrompt, buildSingleSessionPrompt } from "../../execution/prompts";
|
|
25
|
+
import { getLogger } from "../../logger";
|
|
26
|
+
import type { PipelineContext, PipelineStage, StageResult } from "../types";
|
|
27
|
+
|
|
28
|
+
export const promptStage: PipelineStage = {
|
|
29
|
+
name: "prompt",
|
|
30
|
+
enabled: (ctx) =>
|
|
31
|
+
ctx.routing.testStrategy !== "three-session-tdd" && ctx.routing.testStrategy !== "three-session-tdd-lite",
|
|
32
|
+
|
|
33
|
+
async execute(ctx: PipelineContext): Promise<StageResult> {
|
|
34
|
+
const logger = getLogger();
|
|
35
|
+
const isBatch = ctx.stories.length > 1;
|
|
36
|
+
|
|
37
|
+
const prompt = isBatch
|
|
38
|
+
? buildBatchPrompt(ctx.stories, ctx.contextMarkdown, ctx.constitution)
|
|
39
|
+
: buildSingleSessionPrompt(ctx.story, ctx.contextMarkdown, ctx.constitution);
|
|
40
|
+
|
|
41
|
+
ctx.prompt = prompt;
|
|
42
|
+
|
|
43
|
+
if (isBatch) {
|
|
44
|
+
logger.info("prompt", "Batch session prepared", {
|
|
45
|
+
storyCount: ctx.stories.length,
|
|
46
|
+
testStrategy: "test-after",
|
|
47
|
+
});
|
|
48
|
+
} else {
|
|
49
|
+
logger.info("prompt", "Single session prepared", {
|
|
50
|
+
storyId: ctx.story.id,
|
|
51
|
+
testStrategy: "test-after",
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
return { action: "continue" };
|
|
56
|
+
},
|
|
57
|
+
};
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Queue Check Stage
|
|
3
|
+
*
|
|
4
|
+
* Checks for queue commands (PAUSE/ABORT/SKIP) before executing a story.
|
|
5
|
+
* Processes commands atomically and updates PRD accordingly.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { clearQueueFile, readQueueFile } from "../../execution/queue-handler";
|
|
9
|
+
import { getLogger } from "../../logger";
|
|
10
|
+
import { markStorySkipped, savePRD } from "../../prd";
|
|
11
|
+
import type { PipelineContext, PipelineStage, StageResult } from "../types";
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Queue Check Stage
|
|
15
|
+
*
|
|
16
|
+
* Checks for queue commands (PAUSE/ABORT/SKIP) before executing a story.
|
|
17
|
+
* If a command is found, processes it and returns appropriate action.
|
|
18
|
+
*
|
|
19
|
+
* @returns
|
|
20
|
+
* - `continue`: No queue commands, proceed
|
|
21
|
+
* - `pause`: PAUSE/ABORT command found, stop execution
|
|
22
|
+
* - `skip`: SKIP command removed all stories from batch
|
|
23
|
+
*
|
|
24
|
+
* @example
|
|
25
|
+
* ```ts
|
|
26
|
+
* // User writes: echo "PAUSE" > .queue.txt
|
|
27
|
+
* const result = await queueCheckStage.execute(ctx);
|
|
28
|
+
* // result: { action: "pause", reason: "User requested pause via .queue.txt" }
|
|
29
|
+
* ```
|
|
30
|
+
*/
|
|
31
|
+
export const queueCheckStage: PipelineStage = {
|
|
32
|
+
name: "queue-check",
|
|
33
|
+
enabled: () => true,
|
|
34
|
+
|
|
35
|
+
async execute(ctx: PipelineContext): Promise<StageResult> {
|
|
36
|
+
const logger = getLogger();
|
|
37
|
+
const queueCommands = await readQueueFile(ctx.workdir);
|
|
38
|
+
|
|
39
|
+
if (queueCommands.length === 0) {
|
|
40
|
+
return { action: "continue" };
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
for (const cmd of queueCommands) {
|
|
44
|
+
if (cmd.type === "PAUSE") {
|
|
45
|
+
logger.warn("queue", "Paused by user", { command: "PAUSE" });
|
|
46
|
+
await clearQueueFile(ctx.workdir);
|
|
47
|
+
return { action: "pause", reason: "User requested pause via .queue.txt" };
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
if (cmd.type === "ABORT") {
|
|
51
|
+
logger.warn("queue", "Aborting: marking remaining stories as skipped");
|
|
52
|
+
|
|
53
|
+
// Mark all pending stories as skipped
|
|
54
|
+
for (const s of ctx.prd.userStories) {
|
|
55
|
+
if (s.status === "pending") {
|
|
56
|
+
markStorySkipped(ctx.prd, s.id);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// Save PRD path from featureDir
|
|
61
|
+
const prdPath = ctx.featureDir ? `${ctx.featureDir}/prd.json` : `${ctx.workdir}/nax/features/unknown/prd.json`;
|
|
62
|
+
await savePRD(ctx.prd, prdPath);
|
|
63
|
+
await clearQueueFile(ctx.workdir);
|
|
64
|
+
|
|
65
|
+
return { action: "pause", reason: "User requested abort" };
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
if (cmd.type === "SKIP") {
|
|
69
|
+
// Check if this SKIP applies to any story in the current batch
|
|
70
|
+
const isTargeted = ctx.stories.some((s) => s.id === cmd.storyId);
|
|
71
|
+
|
|
72
|
+
if (isTargeted) {
|
|
73
|
+
logger.warn("queue", "Skipping story by user request", {
|
|
74
|
+
storyId: cmd.storyId,
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
// Mark as skipped in PRD
|
|
78
|
+
markStorySkipped(ctx.prd, cmd.storyId);
|
|
79
|
+
|
|
80
|
+
// Save PRD
|
|
81
|
+
const prdPath = ctx.featureDir
|
|
82
|
+
? `${ctx.featureDir}/prd.json`
|
|
83
|
+
: `${ctx.workdir}/nax/features/unknown/prd.json`;
|
|
84
|
+
await savePRD(ctx.prd, prdPath);
|
|
85
|
+
|
|
86
|
+
// Remove from batch
|
|
87
|
+
ctx.stories = ctx.stories.filter((s) => s.id !== cmd.storyId);
|
|
88
|
+
|
|
89
|
+
// If batch is now empty, skip this iteration
|
|
90
|
+
if (ctx.stories.length === 0) {
|
|
91
|
+
await clearQueueFile(ctx.workdir);
|
|
92
|
+
return { action: "skip", reason: "All stories in batch were skipped" };
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// Clear processed commands
|
|
99
|
+
await clearQueueFile(ctx.workdir);
|
|
100
|
+
|
|
101
|
+
return { action: "continue" };
|
|
102
|
+
},
|
|
103
|
+
};
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Review Stage
|
|
3
|
+
*
|
|
4
|
+
* Runs post-implementation review phase if enabled.
|
|
5
|
+
* Checks code quality, tests, linting, etc. via review module.
|
|
6
|
+
* After built-in checks, runs plugin reviewers if any are registered.
|
|
7
|
+
*
|
|
8
|
+
* @returns
|
|
9
|
+
* - `continue`: Review passed
|
|
10
|
+
* - `fail`: Review failed (hard failure)
|
|
11
|
+
*
|
|
12
|
+
* @example
|
|
13
|
+
* ```ts
|
|
14
|
+
* // Review enabled and passes
|
|
15
|
+
* await reviewStage.execute(ctx);
|
|
16
|
+
* // ctx.reviewResult: { success: true, totalDurationMs: 1500, ... }
|
|
17
|
+
*
|
|
18
|
+
* // Review enabled but fails
|
|
19
|
+
* await reviewStage.execute(ctx);
|
|
20
|
+
* // Returns: { action: "fail", reason: "Review failed: typecheck errors" }
|
|
21
|
+
* ```
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
import { spawn } from "bun";
|
|
25
|
+
import { getLogger } from "../../logger";
|
|
26
|
+
import { runReview } from "../../review";
|
|
27
|
+
import type { PipelineContext, PipelineStage, StageResult } from "../types";
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Get list of changed files from git.
|
|
31
|
+
* Includes both staged and unstaged changes.
|
|
32
|
+
*
|
|
33
|
+
* @param workdir - Working directory
|
|
34
|
+
* @returns Array of changed file paths
|
|
35
|
+
*/
|
|
36
|
+
async function getChangedFiles(workdir: string): Promise<string[]> {
|
|
37
|
+
try {
|
|
38
|
+
// Get both staged and unstaged changes
|
|
39
|
+
const [stagedProc, unstagedProc] = [
|
|
40
|
+
spawn({
|
|
41
|
+
cmd: ["git", "diff", "--name-only", "--cached"],
|
|
42
|
+
cwd: workdir,
|
|
43
|
+
stdout: "pipe",
|
|
44
|
+
stderr: "pipe",
|
|
45
|
+
}),
|
|
46
|
+
spawn({
|
|
47
|
+
cmd: ["git", "diff", "--name-only"],
|
|
48
|
+
cwd: workdir,
|
|
49
|
+
stdout: "pipe",
|
|
50
|
+
stderr: "pipe",
|
|
51
|
+
}),
|
|
52
|
+
];
|
|
53
|
+
|
|
54
|
+
await Promise.all([stagedProc.exited, unstagedProc.exited]);
|
|
55
|
+
|
|
56
|
+
const stagedFiles = (await new Response(stagedProc.stdout).text())
|
|
57
|
+
.trim()
|
|
58
|
+
.split("\n")
|
|
59
|
+
.filter((line) => line.length > 0);
|
|
60
|
+
|
|
61
|
+
const unstagedFiles = (await new Response(unstagedProc.stdout).text())
|
|
62
|
+
.trim()
|
|
63
|
+
.split("\n")
|
|
64
|
+
.filter((line) => line.length > 0);
|
|
65
|
+
|
|
66
|
+
// Combine and deduplicate
|
|
67
|
+
return Array.from(new Set([...stagedFiles, ...unstagedFiles]));
|
|
68
|
+
} catch {
|
|
69
|
+
return [];
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export const reviewStage: PipelineStage = {
|
|
74
|
+
name: "review",
|
|
75
|
+
enabled: (ctx) => ctx.config.review.enabled,
|
|
76
|
+
|
|
77
|
+
async execute(ctx: PipelineContext): Promise<StageResult> {
|
|
78
|
+
const logger = getLogger();
|
|
79
|
+
|
|
80
|
+
logger.info("review", "Running review phase");
|
|
81
|
+
|
|
82
|
+
// Run built-in checks (typecheck, lint, test)
|
|
83
|
+
const reviewResult = await runReview(ctx.config.review, ctx.workdir, ctx.config.execution);
|
|
84
|
+
ctx.reviewResult = reviewResult;
|
|
85
|
+
|
|
86
|
+
// HARD FAILURE: Review failure means code quality gate not met
|
|
87
|
+
if (!reviewResult.success) {
|
|
88
|
+
logger.error("review", "Review failed (built-in checks)", {
|
|
89
|
+
reason: reviewResult.failureReason,
|
|
90
|
+
storyId: ctx.story.id,
|
|
91
|
+
});
|
|
92
|
+
return { action: "fail", reason: `Review failed: ${reviewResult.failureReason}` };
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// Run plugin reviewers if any are registered
|
|
96
|
+
if (ctx.plugins) {
|
|
97
|
+
const pluginReviewers = ctx.plugins.getReviewers();
|
|
98
|
+
if (pluginReviewers.length > 0) {
|
|
99
|
+
logger.info("review", `Running ${pluginReviewers.length} plugin reviewer(s)`);
|
|
100
|
+
|
|
101
|
+
const changedFiles = await getChangedFiles(ctx.workdir);
|
|
102
|
+
const pluginReviewerResults: Array<{
|
|
103
|
+
name: string;
|
|
104
|
+
passed: boolean;
|
|
105
|
+
output: string;
|
|
106
|
+
exitCode?: number;
|
|
107
|
+
error?: string;
|
|
108
|
+
}> = [];
|
|
109
|
+
|
|
110
|
+
for (const reviewer of pluginReviewers) {
|
|
111
|
+
logger.info("review", `Running plugin reviewer: ${reviewer.name}`);
|
|
112
|
+
try {
|
|
113
|
+
const result = await reviewer.check(ctx.workdir, changedFiles);
|
|
114
|
+
|
|
115
|
+
// Capture result for debugging
|
|
116
|
+
pluginReviewerResults.push({
|
|
117
|
+
name: reviewer.name,
|
|
118
|
+
passed: result.passed,
|
|
119
|
+
output: result.output,
|
|
120
|
+
exitCode: result.exitCode,
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
if (!result.passed) {
|
|
124
|
+
logger.error("review", `Plugin reviewer failed: ${reviewer.name}`, {
|
|
125
|
+
output: result.output,
|
|
126
|
+
storyId: ctx.story.id,
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
// Store results in review result before failing
|
|
130
|
+
if (ctx.reviewResult) {
|
|
131
|
+
ctx.reviewResult.pluginReviewers = pluginReviewerResults;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
return {
|
|
135
|
+
action: "fail",
|
|
136
|
+
reason: `Review failed: plugin reviewer '${reviewer.name}' failed`,
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
logger.info("review", `Plugin reviewer passed: ${reviewer.name}`);
|
|
141
|
+
} catch (error) {
|
|
142
|
+
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
143
|
+
logger.error("review", `Plugin reviewer error: ${reviewer.name}`, {
|
|
144
|
+
error: errorMsg,
|
|
145
|
+
storyId: ctx.story.id,
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
// Capture error for debugging
|
|
149
|
+
pluginReviewerResults.push({
|
|
150
|
+
name: reviewer.name,
|
|
151
|
+
passed: false,
|
|
152
|
+
output: "",
|
|
153
|
+
error: errorMsg,
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
// Store results in review result before failing
|
|
157
|
+
if (ctx.reviewResult) {
|
|
158
|
+
ctx.reviewResult.pluginReviewers = pluginReviewerResults;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
return {
|
|
162
|
+
action: "fail",
|
|
163
|
+
reason: `Review failed: plugin reviewer '${reviewer.name}' threw error`,
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// Store successful plugin reviewer results
|
|
169
|
+
if (ctx.reviewResult) {
|
|
170
|
+
ctx.reviewResult.pluginReviewers = pluginReviewerResults;
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
logger.info("review", "Review passed", {
|
|
176
|
+
durationMs: reviewResult.totalDurationMs,
|
|
177
|
+
storyId: ctx.story.id,
|
|
178
|
+
});
|
|
179
|
+
return { action: "continue" };
|
|
180
|
+
},
|
|
181
|
+
};
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Routing Stage
|
|
3
|
+
*
|
|
4
|
+
* Classifies story complexity and determines model tier + test strategy.
|
|
5
|
+
* Uses cached complexity/testStrategy from story if available, but ALWAYS
|
|
6
|
+
* derives modelTier from current config (never cached).
|
|
7
|
+
*
|
|
8
|
+
* @returns
|
|
9
|
+
* - `continue`: Routing determined, proceed to next stage
|
|
10
|
+
*
|
|
11
|
+
* @example
|
|
12
|
+
* ```ts
|
|
13
|
+
* // Story has cached routing with complexity
|
|
14
|
+
* await routingStage.execute(ctx);
|
|
15
|
+
* // ctx.routing: { complexity: "simple", modelTier: "fast", testStrategy: "test-after", reasoning: "..." }
|
|
16
|
+
* // modelTier is derived from current config.autoMode.complexityRouting
|
|
17
|
+
* ```
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
import { isGreenfieldStory } from "../../context/greenfield";
|
|
21
|
+
import { getLogger } from "../../logger";
|
|
22
|
+
import { complexityToModelTier, routeStory } from "../../routing";
|
|
23
|
+
import { clearCache, routeBatch } from "../../routing/strategies/llm";
|
|
24
|
+
import type { PipelineContext, PipelineStage, RoutingResult, StageResult } from "../types";
|
|
25
|
+
|
|
26
|
+
export const routingStage: PipelineStage = {
|
|
27
|
+
name: "routing",
|
|
28
|
+
enabled: () => true,
|
|
29
|
+
|
|
30
|
+
async execute(ctx: PipelineContext): Promise<StageResult> {
|
|
31
|
+
const logger = getLogger();
|
|
32
|
+
|
|
33
|
+
// If story has cached routing, use it but re-derive modelTier from current config
|
|
34
|
+
// Otherwise, perform fresh classification
|
|
35
|
+
let routing: { complexity: string; testStrategy: string; modelTier: string; reasoning?: string };
|
|
36
|
+
if (ctx.story.routing) {
|
|
37
|
+
// Use cached complexity/testStrategy, but re-derive modelTier from current config
|
|
38
|
+
routing = await routeStory(ctx.story, { config: ctx.config }, ctx.workdir, ctx.plugins);
|
|
39
|
+
// Override with cached complexity if available
|
|
40
|
+
routing.complexity = ctx.story.routing.complexity;
|
|
41
|
+
routing.testStrategy = ctx.story.routing.testStrategy;
|
|
42
|
+
// Re-derive modelTier from cached complexity and current config
|
|
43
|
+
routing.modelTier = complexityToModelTier(routing.complexity as import("../../config").Complexity, ctx.config);
|
|
44
|
+
} else {
|
|
45
|
+
// Fresh classification
|
|
46
|
+
routing = await routeStory(ctx.story, { config: ctx.config }, ctx.workdir, ctx.plugins);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// BUG-010: Greenfield detection — force test-after if no test files exist
|
|
50
|
+
const greenfieldDetectionEnabled = ctx.config.tdd.greenfieldDetection ?? true;
|
|
51
|
+
if (greenfieldDetectionEnabled && routing.testStrategy.startsWith("three-session-tdd")) {
|
|
52
|
+
const isGreenfield = await isGreenfieldStory(ctx.story, ctx.workdir);
|
|
53
|
+
if (isGreenfield) {
|
|
54
|
+
logger.info("routing", "Greenfield detected — forcing test-after strategy", {
|
|
55
|
+
storyId: ctx.story.id,
|
|
56
|
+
originalStrategy: routing.testStrategy,
|
|
57
|
+
});
|
|
58
|
+
routing.testStrategy = "test-after";
|
|
59
|
+
routing.reasoning = `${routing.reasoning} [GREENFIELD OVERRIDE: No test files exist, using test-after instead of TDD]`;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Set ctx.routing after all overrides are applied
|
|
64
|
+
ctx.routing = routing as RoutingResult;
|
|
65
|
+
|
|
66
|
+
const isBatch = ctx.stories.length > 1;
|
|
67
|
+
|
|
68
|
+
logger.debug("routing", "Task classified", {
|
|
69
|
+
complexity: ctx.routing.complexity,
|
|
70
|
+
modelTier: ctx.routing.modelTier,
|
|
71
|
+
testStrategy: ctx.routing.testStrategy,
|
|
72
|
+
storyId: ctx.story.id,
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
if (!isBatch) {
|
|
76
|
+
logger.debug("routing", ctx.routing.reasoning);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
return { action: "continue" };
|
|
80
|
+
},
|
|
81
|
+
};
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Verify Stage
|
|
3
|
+
*
|
|
4
|
+
* Verifies the agent's work meets basic requirements by running tests.
|
|
5
|
+
* This is a lightweight verification before the full review stage.
|
|
6
|
+
*
|
|
7
|
+
* @returns
|
|
8
|
+
* - `continue`: Tests passed
|
|
9
|
+
* - `escalate`: Tests failed (retry with escalation)
|
|
10
|
+
*
|
|
11
|
+
* @example
|
|
12
|
+
* ```ts
|
|
13
|
+
* // Tests pass
|
|
14
|
+
* await verifyStage.execute(ctx);
|
|
15
|
+
* // Logs: "✓ Tests passed"
|
|
16
|
+
*
|
|
17
|
+
* // Tests fail
|
|
18
|
+
* await verifyStage.execute(ctx);
|
|
19
|
+
* // Returns: { action: "escalate", reason: "Tests failed (exit code 1)" }
|
|
20
|
+
* ```
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
import { getLogger } from "../../logger";
|
|
24
|
+
import { regression } from "../../verification/gate";
|
|
25
|
+
import type { PipelineContext, PipelineStage, StageResult } from "../types";
|
|
26
|
+
|
|
27
|
+
export const verifyStage: PipelineStage = {
|
|
28
|
+
name: "verify",
|
|
29
|
+
enabled: () => true,
|
|
30
|
+
|
|
31
|
+
async execute(ctx: PipelineContext): Promise<StageResult> {
|
|
32
|
+
const logger = getLogger();
|
|
33
|
+
|
|
34
|
+
// Skip verification if tests are not required
|
|
35
|
+
if (!ctx.config.quality.requireTests) {
|
|
36
|
+
logger.debug("verify", "Skipping verification (quality.requireTests = false)", { storyId: ctx.story.id });
|
|
37
|
+
return { action: "continue" };
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Skip verification if no test command is configured
|
|
41
|
+
const testCommand = ctx.config.review?.commands?.test ?? ctx.config.quality.commands.test;
|
|
42
|
+
if (!testCommand) {
|
|
43
|
+
logger.debug("verify", "Skipping verification (no test command configured)", { storyId: ctx.story.id });
|
|
44
|
+
return { action: "continue" };
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
logger.info("verify", "Running verification", { storyId: ctx.story.id });
|
|
48
|
+
|
|
49
|
+
// Use unified regression gate (includes 2s wait for agent process cleanup)
|
|
50
|
+
const result = await regression({
|
|
51
|
+
workdir: ctx.workdir,
|
|
52
|
+
command: testCommand,
|
|
53
|
+
timeoutSeconds: ctx.config.execution.verificationTimeoutSeconds,
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
// HARD FAILURE: Tests must pass for story to be marked complete
|
|
57
|
+
if (!result.success) {
|
|
58
|
+
// BUG-019: Distinguish timeout from actual test failures
|
|
59
|
+
if (result.status === "TIMEOUT") {
|
|
60
|
+
const timeout = ctx.config.execution.verificationTimeoutSeconds;
|
|
61
|
+
logger.error(
|
|
62
|
+
"verify",
|
|
63
|
+
`Test suite exceeded timeout (${timeout}s). This is NOT a test failure — consider increasing execution.verificationTimeoutSeconds or scoping tests.`,
|
|
64
|
+
{
|
|
65
|
+
exitCode: result.status,
|
|
66
|
+
storyId: ctx.story.id,
|
|
67
|
+
timeoutSeconds: timeout,
|
|
68
|
+
},
|
|
69
|
+
);
|
|
70
|
+
} else {
|
|
71
|
+
logger.error("verify", "Tests failed", {
|
|
72
|
+
exitCode: result.status,
|
|
73
|
+
storyId: ctx.story.id,
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// Log first few lines of output for context (skip for TIMEOUT — output is misleading)
|
|
78
|
+
if (result.output && result.status !== "TIMEOUT") {
|
|
79
|
+
const outputLines = result.output.split("\n").slice(0, 10);
|
|
80
|
+
if (outputLines.length > 0) {
|
|
81
|
+
logger.debug("verify", "Test output preview", {
|
|
82
|
+
storyId: ctx.story.id,
|
|
83
|
+
output: outputLines.join("\n"),
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
return {
|
|
89
|
+
action: "escalate",
|
|
90
|
+
reason:
|
|
91
|
+
result.status === "TIMEOUT"
|
|
92
|
+
? `Test suite TIMEOUT after ${ctx.config.execution.verificationTimeoutSeconds}s (not a code failure)`
|
|
93
|
+
: `Tests failed (exit code ${result.status ?? "non-zero"})`,
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
logger.info("verify", "Tests passed", { storyId: ctx.story.id });
|
|
98
|
+
return { action: "continue" };
|
|
99
|
+
},
|
|
100
|
+
};
|