@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,24 @@
|
|
|
1
|
+
import type { IPromptOptimizer, PromptOptimizerInput, PromptOptimizerResult } from "./types.js";
|
|
2
|
+
import { estimateTokens } from "./types.js";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Passthrough optimizer that returns the prompt unchanged.
|
|
6
|
+
*
|
|
7
|
+
* Used as the default when optimization is disabled or
|
|
8
|
+
* when the configured strategy is unrecognized.
|
|
9
|
+
*/
|
|
10
|
+
export class NoopOptimizer implements IPromptOptimizer {
|
|
11
|
+
public readonly name = "noop";
|
|
12
|
+
|
|
13
|
+
async optimize(input: PromptOptimizerInput): Promise<PromptOptimizerResult> {
|
|
14
|
+
const tokens = estimateTokens(input.prompt);
|
|
15
|
+
|
|
16
|
+
return {
|
|
17
|
+
prompt: input.prompt,
|
|
18
|
+
originalTokens: tokens,
|
|
19
|
+
optimizedTokens: tokens,
|
|
20
|
+
savings: 0,
|
|
21
|
+
appliedRules: [],
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
}
|
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
import type { IPromptOptimizer, PromptOptimizerInput, PromptOptimizerResult } from "./types.js";
|
|
2
|
+
import { estimateTokens } from "./types.js";
|
|
3
|
+
|
|
4
|
+
interface RuleBasedConfig {
|
|
5
|
+
stripWhitespace?: boolean;
|
|
6
|
+
compactCriteria?: boolean;
|
|
7
|
+
deduplicateContext?: boolean;
|
|
8
|
+
maxPromptTokens?: number;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const DEFAULT_CONFIG: RuleBasedConfig = {
|
|
12
|
+
stripWhitespace: true,
|
|
13
|
+
compactCriteria: true,
|
|
14
|
+
deduplicateContext: true,
|
|
15
|
+
maxPromptTokens: 8000,
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Rule-based optimizer that applies deterministic transformations
|
|
20
|
+
* to reduce token usage without external dependencies.
|
|
21
|
+
*/
|
|
22
|
+
export class RuleBasedOptimizer implements IPromptOptimizer {
|
|
23
|
+
public readonly name = "rule-based";
|
|
24
|
+
|
|
25
|
+
async optimize(input: PromptOptimizerInput): Promise<PromptOptimizerResult> {
|
|
26
|
+
const originalTokens = estimateTokens(input.prompt);
|
|
27
|
+
const appliedRules: string[] = [];
|
|
28
|
+
|
|
29
|
+
let optimized = input.prompt;
|
|
30
|
+
|
|
31
|
+
// Get config with defaults
|
|
32
|
+
const config = {
|
|
33
|
+
...DEFAULT_CONFIG,
|
|
34
|
+
...input.config.optimizer?.strategies?.["rule-based"],
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
// Rule 1: Strip whitespace
|
|
38
|
+
if (config.stripWhitespace) {
|
|
39
|
+
const before = optimized;
|
|
40
|
+
optimized = this.stripWhitespace(optimized);
|
|
41
|
+
if (optimized !== before) {
|
|
42
|
+
appliedRules.push("stripWhitespace");
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// Rule 2: Compact acceptance criteria
|
|
47
|
+
if (config.compactCriteria) {
|
|
48
|
+
const before = optimized;
|
|
49
|
+
optimized = this.compactCriteria(optimized);
|
|
50
|
+
if (optimized !== before) {
|
|
51
|
+
appliedRules.push("compactCriteria");
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// Rule 3: Deduplicate context
|
|
56
|
+
if (config.deduplicateContext && input.contextMarkdown) {
|
|
57
|
+
const before = optimized;
|
|
58
|
+
optimized = this.deduplicateContext(optimized, input.contextMarkdown);
|
|
59
|
+
if (optimized !== before) {
|
|
60
|
+
appliedRules.push("deduplicateContext");
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// Rule 4: Enforce max prompt tokens
|
|
65
|
+
if (config.maxPromptTokens) {
|
|
66
|
+
const currentTokens = estimateTokens(optimized);
|
|
67
|
+
if (currentTokens > config.maxPromptTokens) {
|
|
68
|
+
optimized = this.trimToMaxTokens(optimized, config.maxPromptTokens);
|
|
69
|
+
appliedRules.push("maxPromptTokens");
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const optimizedTokens = estimateTokens(optimized);
|
|
74
|
+
const savings = originalTokens > 0 ? (originalTokens - optimizedTokens) / originalTokens : 0;
|
|
75
|
+
|
|
76
|
+
return {
|
|
77
|
+
prompt: optimized,
|
|
78
|
+
originalTokens,
|
|
79
|
+
optimizedTokens,
|
|
80
|
+
savings,
|
|
81
|
+
appliedRules,
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Collapse multiple blank lines to single blank line and trim trailing whitespace.
|
|
87
|
+
*/
|
|
88
|
+
private stripWhitespace(prompt: string): string {
|
|
89
|
+
return (
|
|
90
|
+
prompt
|
|
91
|
+
// Trim trailing whitespace from each line
|
|
92
|
+
.split("\n")
|
|
93
|
+
.map((line) => line.trimEnd())
|
|
94
|
+
.join("\n")
|
|
95
|
+
// Collapse 3+ consecutive newlines to 2
|
|
96
|
+
.replace(/\n{3,}/g, "\n\n")
|
|
97
|
+
);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Convert verbose acceptance criteria to terse bullet format.
|
|
102
|
+
*/
|
|
103
|
+
private compactCriteria(prompt: string): string {
|
|
104
|
+
return (
|
|
105
|
+
prompt
|
|
106
|
+
// Remove verbose prefixes
|
|
107
|
+
.replace(/The user should be able to /gi, "")
|
|
108
|
+
.replace(/The system must /gi, "")
|
|
109
|
+
.replace(/The system should /gi, "")
|
|
110
|
+
.replace(/When the /gi, "")
|
|
111
|
+
.replace(/When a /gi, "")
|
|
112
|
+
// Compact common verbose patterns
|
|
113
|
+
.replace(/it should validate all fields/gi, "validate all fields")
|
|
114
|
+
.replace(/display an error message/gi, "show error")
|
|
115
|
+
.replace(/error message/gi, "error")
|
|
116
|
+
);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Remove context sections that duplicate constitution content.
|
|
121
|
+
*/
|
|
122
|
+
private deduplicateContext(prompt: string, contextMarkdown: string): string {
|
|
123
|
+
// Find context section
|
|
124
|
+
const contextSectionMatch = prompt.match(/# Context\n([\s\S]*?)(?=\n#|$)/i);
|
|
125
|
+
if (!contextSectionMatch) {
|
|
126
|
+
return prompt;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
const contextSection = contextSectionMatch[1];
|
|
130
|
+
const contextLines = contextSection.split("\n");
|
|
131
|
+
|
|
132
|
+
// Remove lines that appear in constitution
|
|
133
|
+
const dedupedLines = contextLines.filter((line) => {
|
|
134
|
+
const trimmed = line.trim();
|
|
135
|
+
if (!trimmed) return true; // Keep blank lines for now
|
|
136
|
+
// Check if this line appears in the context markdown (which may duplicate constitution)
|
|
137
|
+
return !contextMarkdown.includes(trimmed);
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
// If we removed content, replace the context section
|
|
141
|
+
if (dedupedLines.length < contextLines.length) {
|
|
142
|
+
const newContextSection = dedupedLines.join("\n");
|
|
143
|
+
return prompt.replace(contextSectionMatch[0], `# Context\n${newContextSection}`);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
return prompt;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Trim context aggressively if prompt exceeds max tokens.
|
|
151
|
+
* Preserve Task and Acceptance Criteria sections.
|
|
152
|
+
*/
|
|
153
|
+
private trimToMaxTokens(prompt: string, maxTokens: number): string {
|
|
154
|
+
const currentTokens = estimateTokens(prompt);
|
|
155
|
+
if (currentTokens <= maxTokens) {
|
|
156
|
+
return prompt;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// Extract sections
|
|
160
|
+
const sections = this.extractSections(prompt);
|
|
161
|
+
const targetChars = maxTokens * 4; // Reverse of token estimation
|
|
162
|
+
const trimmedMessage = "\n... (context trimmed)";
|
|
163
|
+
|
|
164
|
+
// Preserve task and AC, trim context
|
|
165
|
+
let result = "";
|
|
166
|
+
let remainingChars = targetChars;
|
|
167
|
+
|
|
168
|
+
// Add task section (always preserve)
|
|
169
|
+
if (sections.task) {
|
|
170
|
+
result += sections.task;
|
|
171
|
+
remainingChars -= sections.task.length;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// Add AC section (always preserve)
|
|
175
|
+
if (sections.acceptanceCriteria) {
|
|
176
|
+
result += sections.acceptanceCriteria;
|
|
177
|
+
remainingChars -= sections.acceptanceCriteria.length;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// Add as much context as fits
|
|
181
|
+
if (sections.context && remainingChars > 0) {
|
|
182
|
+
// Reserve space for the trimmed message if we're going to add it
|
|
183
|
+
const reserveForMessage = sections.context.length > remainingChars ? trimmedMessage.length : 0;
|
|
184
|
+
const maxContextChars = Math.max(0, remainingChars - reserveForMessage);
|
|
185
|
+
const trimmedContext = sections.context.substring(0, maxContextChars);
|
|
186
|
+
result += trimmedContext;
|
|
187
|
+
if (trimmedContext.length < sections.context.length) {
|
|
188
|
+
result += trimmedMessage;
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// Add other sections if there's room
|
|
193
|
+
if (sections.other && remainingChars > sections.other.length) {
|
|
194
|
+
result += sections.other;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
return result;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* Extract common prompt sections for targeted trimming.
|
|
202
|
+
*/
|
|
203
|
+
private extractSections(prompt: string): {
|
|
204
|
+
task?: string;
|
|
205
|
+
context?: string;
|
|
206
|
+
acceptanceCriteria?: string;
|
|
207
|
+
other?: string;
|
|
208
|
+
} {
|
|
209
|
+
const sections: {
|
|
210
|
+
task?: string;
|
|
211
|
+
context?: string;
|
|
212
|
+
acceptanceCriteria?: string;
|
|
213
|
+
other?: string;
|
|
214
|
+
} = {};
|
|
215
|
+
|
|
216
|
+
const taskMatch = prompt.match(/# Task\n([\s\S]*?)(?=\n#|$)/i);
|
|
217
|
+
if (taskMatch) {
|
|
218
|
+
sections.task = taskMatch[0];
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
const contextMatch = prompt.match(/# Context\n([\s\S]*?)(?=\n#|$)/i);
|
|
222
|
+
if (contextMatch) {
|
|
223
|
+
sections.context = contextMatch[0];
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
const acMatch = prompt.match(/# Acceptance Criteria\n([\s\S]*?)(?=\n#|$)/i);
|
|
227
|
+
if (acMatch) {
|
|
228
|
+
sections.acceptanceCriteria = acMatch[0];
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
// Collect everything else
|
|
232
|
+
let other = prompt;
|
|
233
|
+
if (sections.task) {
|
|
234
|
+
other = other.replace(sections.task, "");
|
|
235
|
+
}
|
|
236
|
+
if (sections.context) {
|
|
237
|
+
other = other.replace(sections.context, "");
|
|
238
|
+
}
|
|
239
|
+
if (sections.acceptanceCriteria) {
|
|
240
|
+
other = other.replace(sections.acceptanceCriteria, "");
|
|
241
|
+
}
|
|
242
|
+
if (other.trim()) {
|
|
243
|
+
sections.other = other;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
return sections;
|
|
247
|
+
}
|
|
248
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import type { NaxConfig } from "../config/schema.js";
|
|
2
|
+
import type { UserStory } from "../prd/types.js";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Interface for prompt optimizers.
|
|
6
|
+
*
|
|
7
|
+
* Optimizers transform assembled prompts to reduce token usage while
|
|
8
|
+
* preserving semantic meaning and all technical requirements.
|
|
9
|
+
*/
|
|
10
|
+
export interface IPromptOptimizer {
|
|
11
|
+
/** Unique optimizer name */
|
|
12
|
+
name: string;
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Optimize a prompt before it is sent to the coding agent.
|
|
16
|
+
*
|
|
17
|
+
* Implementations MUST preserve all technical requirements,
|
|
18
|
+
* acceptance criteria semantics, and code references.
|
|
19
|
+
*/
|
|
20
|
+
optimize(input: PromptOptimizerInput): Promise<PromptOptimizerResult>;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface PromptOptimizerInput {
|
|
24
|
+
/** Assembled prompt from promptStage */
|
|
25
|
+
prompt: string;
|
|
26
|
+
/** Stories being executed (for context) */
|
|
27
|
+
stories: UserStory[];
|
|
28
|
+
/** Raw context markdown (pre-assembly, for dedup detection) */
|
|
29
|
+
contextMarkdown?: string;
|
|
30
|
+
/** Nax configuration */
|
|
31
|
+
config: NaxConfig;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export interface PromptOptimizerResult {
|
|
35
|
+
/** Optimized prompt */
|
|
36
|
+
prompt: string;
|
|
37
|
+
/** Estimated token count before optimization */
|
|
38
|
+
originalTokens: number;
|
|
39
|
+
/** Estimated token count after optimization */
|
|
40
|
+
optimizedTokens: number;
|
|
41
|
+
/** Savings percentage (0-1) */
|
|
42
|
+
savings: number;
|
|
43
|
+
/** List of applied optimization rules/passes */
|
|
44
|
+
appliedRules: string[];
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Estimate token count using simple heuristic.
|
|
49
|
+
* ~4 chars per token for English text (rough estimate).
|
|
50
|
+
*/
|
|
51
|
+
export function estimateTokens(text: string): number {
|
|
52
|
+
return Math.ceil(text.length / 4);
|
|
53
|
+
}
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pipeline Event Emitter
|
|
3
|
+
*
|
|
4
|
+
* Typed event emitter for TUI integration. Emits lifecycle events during
|
|
5
|
+
* pipeline execution so the TUI can display real-time progress.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { EventEmitter } from "node:events";
|
|
9
|
+
import type { UserStory } from "../prd/types";
|
|
10
|
+
import type { RoutingResult, StageResult } from "./types";
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Summary of a completed run.
|
|
14
|
+
*/
|
|
15
|
+
export interface RunSummary {
|
|
16
|
+
/** Total number of stories processed */
|
|
17
|
+
storiesProcessed: number;
|
|
18
|
+
/** Number of stories completed successfully */
|
|
19
|
+
storiesCompleted: number;
|
|
20
|
+
/** Number of stories failed */
|
|
21
|
+
storiesFailed: number;
|
|
22
|
+
/** Number of stories skipped */
|
|
23
|
+
storiesSkipped: number;
|
|
24
|
+
/** Total cost in USD */
|
|
25
|
+
totalCost: number;
|
|
26
|
+
/** Total duration in milliseconds */
|
|
27
|
+
durationMs: number;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Pipeline event types matching v0.6 spec.
|
|
32
|
+
*
|
|
33
|
+
* Events:
|
|
34
|
+
* - story:start — Story execution begins
|
|
35
|
+
* - story:complete — Story execution ends (success/fail/skip)
|
|
36
|
+
* - story:escalate — Story escalated to higher tier
|
|
37
|
+
* - stage:enter — Pipeline stage starts
|
|
38
|
+
* - stage:exit — Pipeline stage finishes
|
|
39
|
+
* - run:complete — Full run completes
|
|
40
|
+
*/
|
|
41
|
+
export interface PipelineEvents {
|
|
42
|
+
"story:start": (story: UserStory, routing: RoutingResult) => void;
|
|
43
|
+
"story:complete": (story: UserStory, result: StageResult) => void;
|
|
44
|
+
"story:escalate": (story: UserStory, fromTier: string, toTier: string) => void;
|
|
45
|
+
"stage:enter": (stage: string, story: UserStory) => void;
|
|
46
|
+
"stage:exit": (stage: string, result: StageResult) => void;
|
|
47
|
+
"run:complete": (summary: RunSummary) => void;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Typed event emitter for pipeline events.
|
|
52
|
+
*
|
|
53
|
+
* Wraps Node.js EventEmitter with TypeScript types for pipeline lifecycle
|
|
54
|
+
* events. The TUI subscribes to these events to update UI state in real-time.
|
|
55
|
+
*
|
|
56
|
+
* @example
|
|
57
|
+
* ```ts
|
|
58
|
+
* const emitter = new PipelineEventEmitter();
|
|
59
|
+
*
|
|
60
|
+
* emitter.on('story:start', (story, routing) => {
|
|
61
|
+
* // Handle story start: ${story.id} with ${routing.modelTier}
|
|
62
|
+
* });
|
|
63
|
+
*
|
|
64
|
+
* emitter.on('story:complete', (story, result) => {
|
|
65
|
+
* // Handle story completion: ${story.id} - ${result.action}
|
|
66
|
+
* });
|
|
67
|
+
*
|
|
68
|
+
* // In pipeline runner:
|
|
69
|
+
* emitter.emit('story:start', story, routing);
|
|
70
|
+
* ```
|
|
71
|
+
*/
|
|
72
|
+
export class PipelineEventEmitter {
|
|
73
|
+
private emitter = new EventEmitter();
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Subscribe to a pipeline event.
|
|
77
|
+
*
|
|
78
|
+
* @param event - Event name
|
|
79
|
+
* @param listener - Event handler
|
|
80
|
+
*/
|
|
81
|
+
on<E extends keyof PipelineEvents>(event: E, listener: PipelineEvents[E]): void {
|
|
82
|
+
this.emitter.on(event, listener as (...args: unknown[]) => void);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Subscribe to an event once (auto-unsubscribe after first emission).
|
|
87
|
+
*
|
|
88
|
+
* @param event - Event name
|
|
89
|
+
* @param listener - Event handler
|
|
90
|
+
*/
|
|
91
|
+
once<E extends keyof PipelineEvents>(event: E, listener: PipelineEvents[E]): void {
|
|
92
|
+
this.emitter.once(event, listener as (...args: unknown[]) => void);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Unsubscribe from an event.
|
|
97
|
+
*
|
|
98
|
+
* @param event - Event name
|
|
99
|
+
* @param listener - Event handler to remove
|
|
100
|
+
*/
|
|
101
|
+
off<E extends keyof PipelineEvents>(event: E, listener: PipelineEvents[E]): void {
|
|
102
|
+
this.emitter.off(event, listener as (...args: unknown[]) => void);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Emit a pipeline event.
|
|
107
|
+
*
|
|
108
|
+
* Called internally by the pipeline runner. External code should only
|
|
109
|
+
* subscribe to events, not emit them.
|
|
110
|
+
*
|
|
111
|
+
* @param event - Event name
|
|
112
|
+
* @param args - Event arguments
|
|
113
|
+
*/
|
|
114
|
+
emit<E extends keyof PipelineEvents>(event: E, ...args: Parameters<PipelineEvents[E]>): void {
|
|
115
|
+
this.emitter.emit(event, ...args);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Remove all listeners for a specific event or all events.
|
|
120
|
+
*
|
|
121
|
+
* @param event - Optional event name (if not provided, removes all listeners)
|
|
122
|
+
*/
|
|
123
|
+
removeAllListeners(event?: keyof PipelineEvents): void {
|
|
124
|
+
if (event) {
|
|
125
|
+
this.emitter.removeAllListeners(event);
|
|
126
|
+
} else {
|
|
127
|
+
this.emitter.removeAllListeners();
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pipeline Module
|
|
3
|
+
*
|
|
4
|
+
* Composable stage-based execution pipeline.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
export type {
|
|
8
|
+
PipelineContext,
|
|
9
|
+
PipelineStage,
|
|
10
|
+
StageResult,
|
|
11
|
+
StageAction,
|
|
12
|
+
RoutingResult,
|
|
13
|
+
} from "./types";
|
|
14
|
+
|
|
15
|
+
export { runPipeline } from "./runner";
|
|
16
|
+
export type { PipelineRunResult } from "./runner";
|
|
17
|
+
|
|
18
|
+
export { PipelineEventEmitter } from "./events";
|
|
19
|
+
export type { PipelineEvents, RunSummary } from "./events";
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pipeline Runner
|
|
3
|
+
*
|
|
4
|
+
* Executes a sequence of pipeline stages, handling stage results and
|
|
5
|
+
* controlling the flow (continue/skip/fail/escalate/pause).
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { getLogger } from "../logger";
|
|
9
|
+
import type { PipelineEventEmitter } from "./events";
|
|
10
|
+
import type { PipelineContext, PipelineStage, StageResult } from "./types";
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Pipeline execution result.
|
|
14
|
+
*/
|
|
15
|
+
export interface PipelineRunResult {
|
|
16
|
+
/** Whether the pipeline completed successfully (reached the end) */
|
|
17
|
+
success: boolean;
|
|
18
|
+
/** Final action taken (e.g., "skip", "fail", "escalate", "pause", "complete") */
|
|
19
|
+
finalAction: "complete" | "skip" | "fail" | "escalate" | "pause";
|
|
20
|
+
/** Reason for non-complete outcomes */
|
|
21
|
+
reason?: string;
|
|
22
|
+
/** Stage where the pipeline stopped (if not completed) */
|
|
23
|
+
stoppedAtStage?: string;
|
|
24
|
+
/** Updated context after pipeline execution */
|
|
25
|
+
context: PipelineContext;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Run a pipeline of stages against a context.
|
|
30
|
+
*
|
|
31
|
+
* Iterates through each enabled stage, executing them in sequence.
|
|
32
|
+
* Stops early if a stage returns skip/fail/escalate/pause.
|
|
33
|
+
*
|
|
34
|
+
* **IMPORTANT - Context Mutation Contract:**
|
|
35
|
+
* This function mutates the input context in-place. Stages modify the context
|
|
36
|
+
* object directly (e.g., `ctx.constitution = result`, `ctx.routing = routing`).
|
|
37
|
+
* The returned `context` in PipelineRunResult is the same object reference,
|
|
38
|
+
* not a clone. If you need immutability, clone the context before calling
|
|
39
|
+
* runPipeline.
|
|
40
|
+
*
|
|
41
|
+
* @param stages - Array of pipeline stages to execute
|
|
42
|
+
* @param context - Initial pipeline context (WILL BE MUTATED IN-PLACE)
|
|
43
|
+
* @param eventEmitter - Optional event emitter for TUI integration
|
|
44
|
+
* @returns Pipeline execution result with mutated context
|
|
45
|
+
*
|
|
46
|
+
* @example
|
|
47
|
+
* ```ts
|
|
48
|
+
* const stages = [routingStage, contextStage, executionStage];
|
|
49
|
+
* const ctx = createInitialContext(); // { config, prd, story, ... }
|
|
50
|
+
*
|
|
51
|
+
* const result = await runPipeline(stages, ctx);
|
|
52
|
+
*
|
|
53
|
+
* if (result.success) {
|
|
54
|
+
* // Pipeline completed successfully
|
|
55
|
+
* // ctx and result.context are the same object
|
|
56
|
+
* // ctx.agentResult === result.context.agentResult is true
|
|
57
|
+
* } else {
|
|
58
|
+
* // Pipeline stopped: ${result.finalAction} - ${result.reason}
|
|
59
|
+
* }
|
|
60
|
+
* ```
|
|
61
|
+
*/
|
|
62
|
+
export async function runPipeline(
|
|
63
|
+
stages: PipelineStage[],
|
|
64
|
+
context: PipelineContext,
|
|
65
|
+
eventEmitter?: PipelineEventEmitter,
|
|
66
|
+
): Promise<PipelineRunResult> {
|
|
67
|
+
const logger = getLogger();
|
|
68
|
+
|
|
69
|
+
for (const stage of stages) {
|
|
70
|
+
// Skip disabled stages
|
|
71
|
+
if (!stage.enabled(context)) {
|
|
72
|
+
logger.debug("pipeline", `Stage "${stage.name}" skipped (disabled)`);
|
|
73
|
+
continue;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Emit stage:enter event
|
|
77
|
+
eventEmitter?.emit("stage:enter", stage.name, context.story);
|
|
78
|
+
|
|
79
|
+
// Execute the stage
|
|
80
|
+
let result: StageResult;
|
|
81
|
+
try {
|
|
82
|
+
result = await stage.execute(context);
|
|
83
|
+
} catch (error) {
|
|
84
|
+
// Stage execution failed with an exception
|
|
85
|
+
const failResult: StageResult = {
|
|
86
|
+
action: "fail",
|
|
87
|
+
reason: `Stage "${stage.name}" threw error: ${error instanceof Error ? error.message : String(error)}`,
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
// Emit stage:exit event
|
|
91
|
+
eventEmitter?.emit("stage:exit", stage.name, failResult);
|
|
92
|
+
|
|
93
|
+
return {
|
|
94
|
+
success: false,
|
|
95
|
+
finalAction: "fail",
|
|
96
|
+
reason: failResult.reason,
|
|
97
|
+
stoppedAtStage: stage.name,
|
|
98
|
+
context,
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// Emit stage:exit event
|
|
103
|
+
eventEmitter?.emit("stage:exit", stage.name, result);
|
|
104
|
+
|
|
105
|
+
// Handle stage result
|
|
106
|
+
switch (result.action) {
|
|
107
|
+
case "continue":
|
|
108
|
+
// Proceed to next stage
|
|
109
|
+
continue;
|
|
110
|
+
|
|
111
|
+
case "skip":
|
|
112
|
+
return {
|
|
113
|
+
success: false,
|
|
114
|
+
finalAction: "skip",
|
|
115
|
+
reason: result.reason,
|
|
116
|
+
stoppedAtStage: stage.name,
|
|
117
|
+
context,
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
case "fail":
|
|
121
|
+
return {
|
|
122
|
+
success: false,
|
|
123
|
+
finalAction: "fail",
|
|
124
|
+
reason: result.reason,
|
|
125
|
+
stoppedAtStage: stage.name,
|
|
126
|
+
context,
|
|
127
|
+
};
|
|
128
|
+
|
|
129
|
+
case "escalate":
|
|
130
|
+
return {
|
|
131
|
+
success: false,
|
|
132
|
+
finalAction: "escalate",
|
|
133
|
+
reason: "Stage requested escalation to higher tier",
|
|
134
|
+
stoppedAtStage: stage.name,
|
|
135
|
+
context,
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
case "pause":
|
|
139
|
+
return {
|
|
140
|
+
success: false,
|
|
141
|
+
finalAction: "pause",
|
|
142
|
+
reason: result.reason,
|
|
143
|
+
stoppedAtStage: stage.name,
|
|
144
|
+
context,
|
|
145
|
+
};
|
|
146
|
+
|
|
147
|
+
default: {
|
|
148
|
+
// Exhaustiveness check
|
|
149
|
+
const _exhaustive: never = result;
|
|
150
|
+
throw new Error(`Unknown stage action: ${JSON.stringify(_exhaustive)}`);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// All stages completed successfully
|
|
156
|
+
return {
|
|
157
|
+
success: true,
|
|
158
|
+
finalAction: "complete",
|
|
159
|
+
context,
|
|
160
|
+
};
|
|
161
|
+
}
|