@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,230 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Test Output Parsing
|
|
3
|
+
*
|
|
4
|
+
* Unified test output parsing logic for Bun test framework.
|
|
5
|
+
* Extracted from execution/test-output-parser.ts and execution/verification.ts.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type { TestFailure, TestOutputAnalysis, TestSummary } from "./types";
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Parse Bun test output into structured failure objects.
|
|
12
|
+
*
|
|
13
|
+
* Example output format:
|
|
14
|
+
* ```
|
|
15
|
+
* bun test v1.0.0
|
|
16
|
+
*
|
|
17
|
+
* test/example.test.ts:
|
|
18
|
+
* ✓ passing test [0.5ms]
|
|
19
|
+
* ✗ failing test [1.2ms]
|
|
20
|
+
*
|
|
21
|
+
* (fail) describe block > nested block > test name [1.2ms]
|
|
22
|
+
* Error: Expected 1 to equal 2
|
|
23
|
+
* at /path/to/file.ts:10:15
|
|
24
|
+
* at Object.test (/path/to/file.ts:8:3)
|
|
25
|
+
* ```
|
|
26
|
+
*/
|
|
27
|
+
export function parseBunTestOutput(output: string): TestSummary {
|
|
28
|
+
const lines = output.split("\n");
|
|
29
|
+
const failures: TestFailure[] = [];
|
|
30
|
+
let passed = 0;
|
|
31
|
+
let failed = 0;
|
|
32
|
+
let currentFile = "";
|
|
33
|
+
let i = 0;
|
|
34
|
+
|
|
35
|
+
while (i < lines.length) {
|
|
36
|
+
const line = lines[i];
|
|
37
|
+
|
|
38
|
+
// Extract file path from headers like "test/example.test.ts:"
|
|
39
|
+
if (line.trim().endsWith(".test.ts:") || line.trim().endsWith(".test.js:")) {
|
|
40
|
+
currentFile = line.trim().replace(/:$/, "");
|
|
41
|
+
i++;
|
|
42
|
+
continue;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Count passed tests (✓ or ✔)
|
|
46
|
+
if (line.includes("✓") || line.includes("✔")) {
|
|
47
|
+
passed++;
|
|
48
|
+
i++;
|
|
49
|
+
continue;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// Count failed tests (✗ or ✘)
|
|
53
|
+
if (line.includes("✗") || line.includes("✘")) {
|
|
54
|
+
failed++;
|
|
55
|
+
i++;
|
|
56
|
+
continue;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Parse failure line: "(fail) TestName > nested > name [duration]"
|
|
60
|
+
const failMatch = line.match(/^\(fail\)\s+(.+?)\s+\[[\d.]+m?s\]/);
|
|
61
|
+
if (failMatch) {
|
|
62
|
+
const testName = failMatch[1].trim();
|
|
63
|
+
i++;
|
|
64
|
+
|
|
65
|
+
// Extract error message and stack trace
|
|
66
|
+
let error = "";
|
|
67
|
+
const stackTrace: string[] = [];
|
|
68
|
+
let stackLineCount = 0;
|
|
69
|
+
|
|
70
|
+
// Read lines until we hit a blank line or another test result
|
|
71
|
+
while (i < lines.length && stackLineCount < 5) {
|
|
72
|
+
const nextLine = lines[i];
|
|
73
|
+
|
|
74
|
+
// Stop at blank line or next test result
|
|
75
|
+
if (!nextLine.trim() || nextLine.includes("(fail)") || nextLine.includes("✓") || nextLine.includes("✗")) {
|
|
76
|
+
break;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// First non-blank line is typically the error message
|
|
80
|
+
if (!error && nextLine.trim()) {
|
|
81
|
+
error = nextLine.trim();
|
|
82
|
+
i++;
|
|
83
|
+
continue;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// Subsequent lines starting with "at" are stack trace
|
|
87
|
+
if (nextLine.trim().startsWith("at ")) {
|
|
88
|
+
stackTrace.push(nextLine.trim());
|
|
89
|
+
stackLineCount++;
|
|
90
|
+
}
|
|
91
|
+
i++;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
failures.push({
|
|
95
|
+
file: currentFile || "unknown",
|
|
96
|
+
testName,
|
|
97
|
+
error: error || "Unknown error",
|
|
98
|
+
stackTrace,
|
|
99
|
+
});
|
|
100
|
+
continue;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
i++;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
return { passed, failed, failures };
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Format failure summary for agent feedback.
|
|
111
|
+
*
|
|
112
|
+
* Format:
|
|
113
|
+
* ```
|
|
114
|
+
* 1. file.test.ts > TestName > nested
|
|
115
|
+
* Error: message
|
|
116
|
+
* at file.ts:10:15
|
|
117
|
+
*
|
|
118
|
+
* 2. another.test.ts > OtherTest
|
|
119
|
+
* Error: another error
|
|
120
|
+
* at other.ts:20:10
|
|
121
|
+
* ```
|
|
122
|
+
*/
|
|
123
|
+
export function formatFailureSummary(failures: TestFailure[], maxChars = 2000): string {
|
|
124
|
+
if (failures.length === 0) {
|
|
125
|
+
return "No test failures";
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
const lines: string[] = [];
|
|
129
|
+
let totalChars = 0;
|
|
130
|
+
|
|
131
|
+
for (let i = 0; i < failures.length; i++) {
|
|
132
|
+
const failure = failures[i];
|
|
133
|
+
const num = i + 1;
|
|
134
|
+
|
|
135
|
+
// Format: "1. file.test.ts > TestName"
|
|
136
|
+
const header = `${num}. ${failure.file} > ${failure.testName}`;
|
|
137
|
+
const errorLine = ` Error: ${failure.error}`;
|
|
138
|
+
|
|
139
|
+
// Add first stack trace line if available
|
|
140
|
+
const stackLine = failure.stackTrace.length > 0 ? ` ${failure.stackTrace[0]}` : "";
|
|
141
|
+
|
|
142
|
+
const blockLines = [header, errorLine];
|
|
143
|
+
if (stackLine) {
|
|
144
|
+
blockLines.push(stackLine);
|
|
145
|
+
}
|
|
146
|
+
blockLines.push(""); // blank line separator
|
|
147
|
+
|
|
148
|
+
const block = blockLines.join("\n");
|
|
149
|
+
const blockLength = block.length;
|
|
150
|
+
|
|
151
|
+
// Check if adding this block would exceed maxChars
|
|
152
|
+
if (totalChars + blockLength > maxChars && lines.length > 0) {
|
|
153
|
+
const remaining = failures.length - i;
|
|
154
|
+
lines.push(`\n... and ${remaining} more failure(s) (truncated)`);
|
|
155
|
+
break;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
lines.push(...blockLines);
|
|
159
|
+
totalChars += blockLength;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
return lines.join("\n").trim();
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Parse test output to detect environmental failures.
|
|
167
|
+
*
|
|
168
|
+
* When exit code != 0 but all tests pass, classifies as ENVIRONMENTAL_FAILURE
|
|
169
|
+
* instead of TEST_FAILURE.
|
|
170
|
+
*/
|
|
171
|
+
export function parseTestOutput(output: string, exitCode: number): TestOutputAnalysis {
|
|
172
|
+
// Regex patterns for different test frameworks
|
|
173
|
+
const patterns = [
|
|
174
|
+
/(\d+)\s+pass(?:ed)?(?:,\s+|\s+)(\d+)\s+fail/i, // "5 pass, 0 fail" or "5 passed 0 fail"
|
|
175
|
+
/Tests:\s+(\d+)\s+passed,\s+(\d+)\s+failed/i, // Jest format
|
|
176
|
+
/(\d+)\s+pass/i, // Bun format (just pass count)
|
|
177
|
+
];
|
|
178
|
+
|
|
179
|
+
let passCount = 0;
|
|
180
|
+
let failCount = 0;
|
|
181
|
+
|
|
182
|
+
for (const pattern of patterns) {
|
|
183
|
+
// Match ALL occurrences — use the LAST one (final summary line)
|
|
184
|
+
const matches = Array.from(output.matchAll(new RegExp(pattern, "gi")));
|
|
185
|
+
if (matches.length > 0) {
|
|
186
|
+
const lastMatch = matches[matches.length - 1];
|
|
187
|
+
passCount = Number.parseInt(lastMatch[1], 10);
|
|
188
|
+
// Some formats only show pass count
|
|
189
|
+
failCount = lastMatch[2] ? Number.parseInt(lastMatch[2], 10) : 0;
|
|
190
|
+
break;
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
// Check for explicit fail count if not found
|
|
195
|
+
if (failCount === 0) {
|
|
196
|
+
const failMatches = Array.from(output.matchAll(/(\d+)\s+fail/gi));
|
|
197
|
+
if (failMatches.length > 0) {
|
|
198
|
+
failCount = Number.parseInt(failMatches[failMatches.length - 1][1], 10);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
const allTestsPassed = passCount > 0 && failCount === 0;
|
|
203
|
+
const isEnvironmentalFailure = allTestsPassed && exitCode !== 0;
|
|
204
|
+
|
|
205
|
+
const result: TestOutputAnalysis = {
|
|
206
|
+
allTestsPassed,
|
|
207
|
+
passCount,
|
|
208
|
+
failCount,
|
|
209
|
+
isEnvironmentalFailure,
|
|
210
|
+
};
|
|
211
|
+
|
|
212
|
+
if (isEnvironmentalFailure) {
|
|
213
|
+
result.error = `ENVIRONMENTAL_FAILURE: All ${passCount} tests passed but exit code was ${exitCode}. Check linter/typecheck/missing files.`;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
return result;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* Calculate early escalation threshold for environmental failures.
|
|
221
|
+
*
|
|
222
|
+
* Environmental failures should escalate faster: after ceil(tier.attempts / 2)
|
|
223
|
+
* instead of the full tier budget.
|
|
224
|
+
*/
|
|
225
|
+
export function getEnvironmentalEscalationThreshold(tierAttempts: number, divisor = 2): number {
|
|
226
|
+
return Math.ceil(tierAttempts / divisor);
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
// Re-export types for consumers that import from this module
|
|
230
|
+
export type { TestFailure, TestSummary } from "./types";
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Rectification Logic
|
|
3
|
+
*
|
|
4
|
+
* Retry logic with backoff, max attempt tracking, and failure categorization.
|
|
5
|
+
* Extracted from execution/rectification.ts to eliminate duplication.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type { RectificationConfig } from "../config";
|
|
9
|
+
import type { UserStory } from "../prd";
|
|
10
|
+
import { formatFailureSummary } from "./parser";
|
|
11
|
+
import type { RectificationState, TestFailure } from "./types";
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Determine if rectification should retry based on state and config.
|
|
15
|
+
*
|
|
16
|
+
* Returns true if:
|
|
17
|
+
* - Current attempt < maxRetries
|
|
18
|
+
* - AND currentFailures > 0 (still have failures to fix)
|
|
19
|
+
* - AND NOT regressing (if abortOnIncreasingFailures is true)
|
|
20
|
+
*
|
|
21
|
+
* Returns false if:
|
|
22
|
+
* - Max retries reached
|
|
23
|
+
* - OR all tests passing (currentFailures = 0)
|
|
24
|
+
* - OR failures increased (regression spiral) AND abortOnIncreasingFailures = true
|
|
25
|
+
*/
|
|
26
|
+
export function shouldRetryRectification(state: RectificationState, config: RectificationConfig): boolean {
|
|
27
|
+
// Stop if max retries reached
|
|
28
|
+
if (state.attempt >= config.maxRetries) {
|
|
29
|
+
return false;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// Stop if all tests passing
|
|
33
|
+
if (state.currentFailures === 0) {
|
|
34
|
+
return false;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Abort if failures increased (regression spiral check)
|
|
38
|
+
if (config.abortOnIncreasingFailures && state.currentFailures > state.initialFailures) {
|
|
39
|
+
return false;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// Continue retrying
|
|
43
|
+
return true;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Create a rectification prompt with failure context.
|
|
48
|
+
*
|
|
49
|
+
* Includes:
|
|
50
|
+
* - Clear instructions about test regressions
|
|
51
|
+
* - Formatted failure summary
|
|
52
|
+
* - Specific test commands for failing files
|
|
53
|
+
*/
|
|
54
|
+
export function createRectificationPrompt(
|
|
55
|
+
failures: TestFailure[],
|
|
56
|
+
story: UserStory,
|
|
57
|
+
config?: RectificationConfig,
|
|
58
|
+
): string {
|
|
59
|
+
const maxChars = config?.maxFailureSummaryChars ?? 2000;
|
|
60
|
+
const failureSummary = formatFailureSummary(failures, maxChars);
|
|
61
|
+
|
|
62
|
+
// Extract unique failing test files
|
|
63
|
+
const failingFiles = Array.from(new Set(failures.map((f) => f.file)));
|
|
64
|
+
const testCommands = failingFiles.map((file) => ` bun test ${file}`).join("\n");
|
|
65
|
+
|
|
66
|
+
return `# Rectification Required
|
|
67
|
+
|
|
68
|
+
Your changes caused test regressions. Fix these without breaking existing logic.
|
|
69
|
+
|
|
70
|
+
## Story Context
|
|
71
|
+
|
|
72
|
+
**Title:** ${story.title}
|
|
73
|
+
|
|
74
|
+
**Description:**
|
|
75
|
+
${story.description}
|
|
76
|
+
|
|
77
|
+
**Acceptance Criteria:**
|
|
78
|
+
${story.acceptanceCriteria.map((c, i) => `${i + 1}. ${c}`).join("\n")}
|
|
79
|
+
|
|
80
|
+
---
|
|
81
|
+
|
|
82
|
+
## Test Failures
|
|
83
|
+
|
|
84
|
+
${failureSummary}
|
|
85
|
+
|
|
86
|
+
---
|
|
87
|
+
|
|
88
|
+
## Instructions
|
|
89
|
+
|
|
90
|
+
1. Review the failures above carefully.
|
|
91
|
+
2. Identify the root cause of each failure.
|
|
92
|
+
3. Fix the implementation WITHOUT loosening test assertions.
|
|
93
|
+
4. Run the failing tests to verify your fixes:
|
|
94
|
+
|
|
95
|
+
${testCommands}
|
|
96
|
+
|
|
97
|
+
5. Ensure ALL tests pass before completing.
|
|
98
|
+
|
|
99
|
+
**IMPORTANT:**
|
|
100
|
+
- Do NOT modify test files unless there is a legitimate bug in the test itself.
|
|
101
|
+
- Do NOT loosen assertions to mask implementation bugs.
|
|
102
|
+
- Focus on fixing the source code to meet the test requirements.
|
|
103
|
+
- When running tests, run ONLY the failing test files shown above — NEVER run \`bun test\` without a file filter.
|
|
104
|
+
`;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// Re-export types for consumers that import from this module
|
|
108
|
+
export type { RectificationState } from "./types";
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Unified Verification Types
|
|
3
|
+
*
|
|
4
|
+
* Shared type definitions for test execution, parsing, and verification.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
/** Verification scope: what tests to run */
|
|
8
|
+
export type VerificationScope = "scoped" | "full" | "regression";
|
|
9
|
+
|
|
10
|
+
/** Verification status outcomes */
|
|
11
|
+
export type VerificationStatus =
|
|
12
|
+
| "SUCCESS"
|
|
13
|
+
| "TEST_FAILURE"
|
|
14
|
+
| "ENVIRONMENTAL_FAILURE"
|
|
15
|
+
| "ASSET_CHECK_FAILED"
|
|
16
|
+
| "TIMEOUT";
|
|
17
|
+
|
|
18
|
+
/** Test execution result (raw) */
|
|
19
|
+
export interface TestExecutionResult {
|
|
20
|
+
success: boolean;
|
|
21
|
+
timeout: boolean;
|
|
22
|
+
exitCode?: number;
|
|
23
|
+
output?: string;
|
|
24
|
+
error?: string;
|
|
25
|
+
killed?: boolean;
|
|
26
|
+
childProcessesKilled?: boolean;
|
|
27
|
+
countsTowardEscalation: boolean;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/** Test output analysis (parsed) */
|
|
31
|
+
export interface TestOutputAnalysis {
|
|
32
|
+
allTestsPassed: boolean;
|
|
33
|
+
passCount: number;
|
|
34
|
+
failCount: number;
|
|
35
|
+
isEnvironmentalFailure: boolean;
|
|
36
|
+
error?: string;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/** Asset verification result */
|
|
40
|
+
export interface AssetVerificationResult {
|
|
41
|
+
success: boolean;
|
|
42
|
+
missingFiles: string[];
|
|
43
|
+
error?: string;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/** Structured test failure information */
|
|
47
|
+
export interface TestFailure {
|
|
48
|
+
/** File path where the test failed */
|
|
49
|
+
file: string;
|
|
50
|
+
/** Full test name (including nested describe blocks) */
|
|
51
|
+
testName: string;
|
|
52
|
+
/** Error message */
|
|
53
|
+
error: string;
|
|
54
|
+
/** Stack trace lines (truncated to first 5 lines) */
|
|
55
|
+
stackTrace: string[];
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/** Test run summary */
|
|
59
|
+
export interface TestSummary {
|
|
60
|
+
/** Number of tests that passed */
|
|
61
|
+
passed: number;
|
|
62
|
+
/** Number of tests that failed */
|
|
63
|
+
failed: number;
|
|
64
|
+
/** Structured failure details */
|
|
65
|
+
failures: TestFailure[];
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/** Complete verification result */
|
|
69
|
+
export interface VerificationResult {
|
|
70
|
+
status: VerificationStatus;
|
|
71
|
+
success: boolean;
|
|
72
|
+
countsTowardEscalation: boolean;
|
|
73
|
+
output?: string;
|
|
74
|
+
error?: string;
|
|
75
|
+
missingFiles?: string[];
|
|
76
|
+
passCount?: number;
|
|
77
|
+
failCount?: number;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/** Rectification state tracking per story execution */
|
|
81
|
+
export interface RectificationState {
|
|
82
|
+
/** Current attempt number (0 = initial run, 1+ = retries) */
|
|
83
|
+
attempt: number;
|
|
84
|
+
/** Number of test failures on initial run */
|
|
85
|
+
initialFailures: number;
|
|
86
|
+
/** Number of test failures on current run */
|
|
87
|
+
currentFailures: number;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/** Verification gate options */
|
|
91
|
+
export interface VerificationGateOptions {
|
|
92
|
+
/** Working directory */
|
|
93
|
+
workdir: string;
|
|
94
|
+
/** Test command to execute */
|
|
95
|
+
command: string;
|
|
96
|
+
/** Timeout in seconds */
|
|
97
|
+
timeoutSeconds: number;
|
|
98
|
+
/** Expected files (for asset verification) */
|
|
99
|
+
expectedFiles?: string[];
|
|
100
|
+
/** Quality config for open handle / force exit behavior */
|
|
101
|
+
forceExit?: boolean;
|
|
102
|
+
detectOpenHandles?: boolean;
|
|
103
|
+
detectOpenHandlesRetries?: number;
|
|
104
|
+
/** How many times this story has timed out (tracks across retries) */
|
|
105
|
+
timeoutRetryCount?: number;
|
|
106
|
+
/** Process management config */
|
|
107
|
+
gracePeriodMs?: number;
|
|
108
|
+
drainTimeoutMs?: number;
|
|
109
|
+
shell?: string;
|
|
110
|
+
stripEnvVars?: string[];
|
|
111
|
+
/** Scoped test paths (for scoped verification) */
|
|
112
|
+
scopedTestPaths?: string[];
|
|
113
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import type { UserStory } from "../prd/types";
|
|
2
|
+
import type { WorktreeManager } from "./manager";
|
|
3
|
+
|
|
4
|
+
export interface DispatchResult {
|
|
5
|
+
storyId: string;
|
|
6
|
+
success: boolean;
|
|
7
|
+
worktreePath: string;
|
|
8
|
+
error?: string;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export class ParallelDispatcher {
|
|
12
|
+
constructor(
|
|
13
|
+
private worktreeManager: WorktreeManager,
|
|
14
|
+
private runPipeline: (args: { workdir: string; story: UserStory }) => Promise<boolean>,
|
|
15
|
+
) {}
|
|
16
|
+
|
|
17
|
+
async dispatch(projectRoot: string, stories: UserStory[], maxConcurrency: number): Promise<DispatchResult[]> {
|
|
18
|
+
const results: DispatchResult[] = [];
|
|
19
|
+
const independentBatches = this.getBatches(stories);
|
|
20
|
+
|
|
21
|
+
for (const batch of independentBatches) {
|
|
22
|
+
const batchPromises = batch.map(async (story) => {
|
|
23
|
+
const worktreePath = `${projectRoot}/.nax-wt/${story.id}`;
|
|
24
|
+
try {
|
|
25
|
+
await this.worktreeManager.create(projectRoot, story.id);
|
|
26
|
+
const success = await this.runPipeline({ workdir: worktreePath, story });
|
|
27
|
+
return { storyId: story.id, success, worktreePath };
|
|
28
|
+
} catch (err) {
|
|
29
|
+
return {
|
|
30
|
+
storyId: story.id,
|
|
31
|
+
success: false,
|
|
32
|
+
worktreePath,
|
|
33
|
+
error: err instanceof Error ? err.message : String(err),
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
const batchResults = await pLimit(maxConcurrency, batchPromises);
|
|
39
|
+
results.push(...batchResults);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
return results;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
private getBatches(stories: UserStory[]): UserStory[][] {
|
|
46
|
+
// TODO: Implement dependency-aware batching
|
|
47
|
+
return [stories];
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Helper for concurrency limiting (Simplified p-limit)
|
|
52
|
+
async function pLimit<T>(concurrency: number, promises: Promise<T>[]): Promise<T[]> {
|
|
53
|
+
const results: T[] = [];
|
|
54
|
+
const executing: Promise<void>[] = [];
|
|
55
|
+
for (const p of promises) {
|
|
56
|
+
const e = p.then((r) => {
|
|
57
|
+
results.push(r);
|
|
58
|
+
executing.splice(executing.indexOf(e), 1);
|
|
59
|
+
});
|
|
60
|
+
executing.push(e);
|
|
61
|
+
if (executing.length >= concurrency) await Promise.race(executing);
|
|
62
|
+
}
|
|
63
|
+
await Promise.all(executing);
|
|
64
|
+
return results;
|
|
65
|
+
}
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
import { existsSync, symlinkSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { getSafeLogger } from "../logger";
|
|
4
|
+
import type { WorktreeInfo } from "./types";
|
|
5
|
+
|
|
6
|
+
export class WorktreeManager {
|
|
7
|
+
/**
|
|
8
|
+
* Creates a git worktree at .nax-wt/<storyId>/ with branch nax/<storyId>
|
|
9
|
+
* and symlinks node_modules and .env from project root
|
|
10
|
+
*/
|
|
11
|
+
async create(projectRoot: string, storyId: string): Promise<void> {
|
|
12
|
+
const worktreePath = join(projectRoot, ".nax-wt", storyId);
|
|
13
|
+
const branchName = `nax/${storyId}`;
|
|
14
|
+
|
|
15
|
+
try {
|
|
16
|
+
// Create worktree with new branch
|
|
17
|
+
const proc = Bun.spawn(["git", "worktree", "add", worktreePath, "-b", branchName], {
|
|
18
|
+
cwd: projectRoot,
|
|
19
|
+
stdout: "pipe",
|
|
20
|
+
stderr: "pipe",
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
const exitCode = await proc.exited;
|
|
24
|
+
if (exitCode !== 0) {
|
|
25
|
+
const stderr = await new Response(proc.stderr).text();
|
|
26
|
+
throw new Error(`Failed to create worktree: ${stderr || "unknown error"}`);
|
|
27
|
+
}
|
|
28
|
+
} catch (error) {
|
|
29
|
+
if (error instanceof Error) {
|
|
30
|
+
// Enhance error messages for common scenarios
|
|
31
|
+
if (error.message.includes("not a git repository")) {
|
|
32
|
+
throw new Error(`Not a git repository: ${projectRoot}`);
|
|
33
|
+
}
|
|
34
|
+
if (error.message.includes("already exists")) {
|
|
35
|
+
throw new Error(`Worktree for story ${storyId} already exists at ${worktreePath}`);
|
|
36
|
+
}
|
|
37
|
+
throw error;
|
|
38
|
+
}
|
|
39
|
+
throw new Error(`Failed to create worktree: ${String(error)}`);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// Symlink node_modules if it exists
|
|
43
|
+
const nodeModulesSource = join(projectRoot, "node_modules");
|
|
44
|
+
if (existsSync(nodeModulesSource)) {
|
|
45
|
+
const nodeModulesTarget = join(worktreePath, "node_modules");
|
|
46
|
+
try {
|
|
47
|
+
symlinkSync(nodeModulesSource, nodeModulesTarget, "dir");
|
|
48
|
+
} catch (error) {
|
|
49
|
+
// Clean up worktree if symlinking fails
|
|
50
|
+
await this.remove(projectRoot, storyId);
|
|
51
|
+
throw new Error(`Failed to symlink node_modules: ${error instanceof Error ? error.message : String(error)}`);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// Symlink .env if it exists
|
|
56
|
+
const envSource = join(projectRoot, ".env");
|
|
57
|
+
if (existsSync(envSource)) {
|
|
58
|
+
const envTarget = join(worktreePath, ".env");
|
|
59
|
+
try {
|
|
60
|
+
symlinkSync(envSource, envTarget, "file");
|
|
61
|
+
} catch (error) {
|
|
62
|
+
// Clean up worktree if symlinking fails
|
|
63
|
+
await this.remove(projectRoot, storyId);
|
|
64
|
+
throw new Error(`Failed to symlink .env: ${error instanceof Error ? error.message : String(error)}`);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Removes worktree and deletes branch
|
|
71
|
+
*/
|
|
72
|
+
async remove(projectRoot: string, storyId: string): Promise<void> {
|
|
73
|
+
const worktreePath = join(projectRoot, ".nax-wt", storyId);
|
|
74
|
+
const branchName = `nax/${storyId}`;
|
|
75
|
+
|
|
76
|
+
// Remove worktree
|
|
77
|
+
try {
|
|
78
|
+
const proc = Bun.spawn(["git", "worktree", "remove", worktreePath, "--force"], {
|
|
79
|
+
cwd: projectRoot,
|
|
80
|
+
stdout: "pipe",
|
|
81
|
+
stderr: "pipe",
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
const exitCode = await proc.exited;
|
|
85
|
+
if (exitCode !== 0) {
|
|
86
|
+
const stderr = await new Response(proc.stderr).text();
|
|
87
|
+
if (
|
|
88
|
+
stderr.includes("not found") ||
|
|
89
|
+
stderr.includes("does not exist") ||
|
|
90
|
+
stderr.includes("no such worktree") ||
|
|
91
|
+
stderr.includes("is not a working tree")
|
|
92
|
+
) {
|
|
93
|
+
throw new Error(`Worktree not found: ${worktreePath}`);
|
|
94
|
+
}
|
|
95
|
+
throw new Error(`Failed to remove worktree: ${stderr || "unknown error"}`);
|
|
96
|
+
}
|
|
97
|
+
} catch (error) {
|
|
98
|
+
if (error instanceof Error) {
|
|
99
|
+
throw error;
|
|
100
|
+
}
|
|
101
|
+
throw new Error(`Failed to remove worktree: ${String(error)}`);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// Delete branch
|
|
105
|
+
try {
|
|
106
|
+
const proc = Bun.spawn(["git", "branch", "-D", branchName], {
|
|
107
|
+
cwd: projectRoot,
|
|
108
|
+
stdout: "pipe",
|
|
109
|
+
stderr: "pipe",
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
const exitCode = await proc.exited;
|
|
113
|
+
if (exitCode !== 0) {
|
|
114
|
+
const stderr = await new Response(proc.stderr).text();
|
|
115
|
+
// Don't fail if branch doesn't exist
|
|
116
|
+
if (!stderr.includes("not found")) {
|
|
117
|
+
const logger = getSafeLogger();
|
|
118
|
+
logger?.warn("worktree", `Failed to delete branch ${branchName}`, { stderr });
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
} catch (error) {
|
|
122
|
+
// Log warning but don't fail - worktree is already removed
|
|
123
|
+
const logger = getSafeLogger();
|
|
124
|
+
logger?.warn("worktree", `Failed to delete branch ${branchName}`, {
|
|
125
|
+
error: error instanceof Error ? error.message : String(error),
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Returns active worktrees
|
|
132
|
+
*/
|
|
133
|
+
async list(projectRoot: string): Promise<WorktreeInfo[]> {
|
|
134
|
+
try {
|
|
135
|
+
const proc = Bun.spawn(["git", "worktree", "list", "--porcelain"], {
|
|
136
|
+
cwd: projectRoot,
|
|
137
|
+
stdout: "pipe",
|
|
138
|
+
stderr: "pipe",
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
const exitCode = await proc.exited;
|
|
142
|
+
if (exitCode !== 0) {
|
|
143
|
+
const stderr = await new Response(proc.stderr).text();
|
|
144
|
+
throw new Error(`Failed to list worktrees: ${stderr || "unknown error"}`);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
const stdout = await new Response(proc.stdout).text();
|
|
148
|
+
return this.parseWorktreeList(stdout);
|
|
149
|
+
} catch (error) {
|
|
150
|
+
if (error instanceof Error) {
|
|
151
|
+
throw error;
|
|
152
|
+
}
|
|
153
|
+
throw new Error(`Failed to list worktrees: ${String(error)}`);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Parses git worktree list --porcelain output
|
|
159
|
+
*/
|
|
160
|
+
private parseWorktreeList(output: string): WorktreeInfo[] {
|
|
161
|
+
const worktrees: WorktreeInfo[] = [];
|
|
162
|
+
const lines = output.trim().split("\n");
|
|
163
|
+
|
|
164
|
+
let currentWorktree: Partial<WorktreeInfo> = {};
|
|
165
|
+
|
|
166
|
+
for (const line of lines) {
|
|
167
|
+
if (line.startsWith("worktree ")) {
|
|
168
|
+
currentWorktree.path = line.substring("worktree ".length);
|
|
169
|
+
} else if (line.startsWith("branch ")) {
|
|
170
|
+
currentWorktree.branch = line.substring("branch ".length).replace("refs/heads/", "");
|
|
171
|
+
} else if (line === "") {
|
|
172
|
+
// Empty line indicates end of worktree entry
|
|
173
|
+
if (currentWorktree.path && currentWorktree.branch) {
|
|
174
|
+
worktrees.push(currentWorktree as WorktreeInfo);
|
|
175
|
+
}
|
|
176
|
+
currentWorktree = {};
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// Handle last entry if no trailing newline
|
|
181
|
+
if (currentWorktree.path && currentWorktree.branch) {
|
|
182
|
+
worktrees.push(currentWorktree as WorktreeInfo);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
return worktrees;
|
|
186
|
+
}
|
|
187
|
+
}
|