@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,860 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Test reporter lifecycle events
|
|
3
|
+
*
|
|
4
|
+
* Verifies that reporter plugins receive lifecycle events at the appropriate
|
|
5
|
+
* points in the runner loop (US-004).
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, test } from "bun:test";
|
|
9
|
+
import * as fs from "node:fs/promises";
|
|
10
|
+
import * as os from "node:os";
|
|
11
|
+
import * as path from "node:path";
|
|
12
|
+
import { ALL_AGENTS } from "../../src/agents/registry";
|
|
13
|
+
import type {
|
|
14
|
+
AgentAdapter,
|
|
15
|
+
AgentCapabilities,
|
|
16
|
+
AgentResult,
|
|
17
|
+
AgentRunOptions,
|
|
18
|
+
DecomposeOptions,
|
|
19
|
+
DecomposeResult,
|
|
20
|
+
PlanOptions,
|
|
21
|
+
PlanResult,
|
|
22
|
+
} from "../../src/agents/types";
|
|
23
|
+
import type { NaxConfig } from "../../src/config";
|
|
24
|
+
import { run } from "../../src/execution/runner";
|
|
25
|
+
import { loadHooksConfig } from "../../src/hooks";
|
|
26
|
+
import type { IReporter, NaxPlugin, RunEndEvent, RunStartEvent, StoryCompleteEvent } from "../../src/plugins/types";
|
|
27
|
+
import { loadPRD, savePRD } from "../../src/prd";
|
|
28
|
+
|
|
29
|
+
// ============================================================================
|
|
30
|
+
// Mock agent (satisfies agent installation check in runner)
|
|
31
|
+
// ============================================================================
|
|
32
|
+
class MockAgentAdapter implements AgentAdapter {
|
|
33
|
+
readonly name = "mock";
|
|
34
|
+
readonly displayName = "Mock Agent";
|
|
35
|
+
readonly binary = "mock-agent";
|
|
36
|
+
readonly capabilities: AgentCapabilities = {
|
|
37
|
+
supportedTiers: ["fast", "balanced", "powerful"],
|
|
38
|
+
maxContextTokens: 200_000,
|
|
39
|
+
features: new Set(["tdd", "review", "refactor", "batch"]),
|
|
40
|
+
};
|
|
41
|
+
async isInstalled(): Promise<boolean> {
|
|
42
|
+
return true;
|
|
43
|
+
}
|
|
44
|
+
buildCommand(_o: AgentRunOptions): string[] {
|
|
45
|
+
return [this.binary];
|
|
46
|
+
}
|
|
47
|
+
async run(_o: AgentRunOptions): Promise<AgentResult> {
|
|
48
|
+
return { success: true, exitCode: 0, output: "", durationMs: 10, estimatedCost: 0 };
|
|
49
|
+
}
|
|
50
|
+
async plan(_o: PlanOptions): Promise<PlanResult> {
|
|
51
|
+
return { specContent: "# Feature\n", success: true };
|
|
52
|
+
}
|
|
53
|
+
async decompose(_o: DecomposeOptions): Promise<DecomposeResult> {
|
|
54
|
+
return { stories: [], success: true };
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
describe("Reporter Lifecycle Events (US-004)", () => {
|
|
59
|
+
let tmpDir: string;
|
|
60
|
+
let workdir: string;
|
|
61
|
+
let prdPath: string;
|
|
62
|
+
let pluginDir: string;
|
|
63
|
+
let config: NaxConfig;
|
|
64
|
+
|
|
65
|
+
// Track reporter calls
|
|
66
|
+
let onRunStartCalls: RunStartEvent[] = [];
|
|
67
|
+
let onStoryCompleteCalls: StoryCompleteEvent[] = [];
|
|
68
|
+
let onRunEndCalls: RunEndEvent[] = [];
|
|
69
|
+
|
|
70
|
+
beforeAll(() => {
|
|
71
|
+
// Register mock agent
|
|
72
|
+
ALL_AGENTS.push(new MockAgentAdapter());
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
afterAll(() => {
|
|
76
|
+
// Cleanup mock agent
|
|
77
|
+
const mockIndex = ALL_AGENTS.findIndex((a) => a.name === "mock");
|
|
78
|
+
if (mockIndex !== -1) {
|
|
79
|
+
ALL_AGENTS.splice(mockIndex, 1);
|
|
80
|
+
}
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
beforeEach(async () => {
|
|
84
|
+
// Create temp directory
|
|
85
|
+
tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), "nax-reporter-test-"));
|
|
86
|
+
workdir = tmpDir;
|
|
87
|
+
prdPath = path.join(workdir, "nax", "features", "test-feature", "prd.json");
|
|
88
|
+
pluginDir = path.join(workdir, "nax", "plugins");
|
|
89
|
+
|
|
90
|
+
// Ensure directories exist
|
|
91
|
+
await fs.mkdir(path.dirname(prdPath), { recursive: true });
|
|
92
|
+
await fs.mkdir(pluginDir, { recursive: true });
|
|
93
|
+
|
|
94
|
+
// Initialize git repo
|
|
95
|
+
await Bun.spawn(["git", "init"], { cwd: workdir, stdout: "ignore" }).exited;
|
|
96
|
+
await Bun.spawn(["git", "config", "user.email", "test@example.com"], { cwd: workdir, stdout: "ignore" }).exited;
|
|
97
|
+
await Bun.spawn(["git", "config", "user.name", "Test User"], { cwd: workdir, stdout: "ignore" }).exited;
|
|
98
|
+
await fs.writeFile(path.join(workdir, "README.md"), "# Test");
|
|
99
|
+
await Bun.spawn(["git", "add", "."], { cwd: workdir, stdout: "ignore" }).exited;
|
|
100
|
+
await Bun.spawn(["git", "commit", "-m", "Initial commit"], { cwd: workdir, stdout: "ignore" }).exited;
|
|
101
|
+
|
|
102
|
+
// Reset tracking arrays
|
|
103
|
+
onRunStartCalls = [];
|
|
104
|
+
onStoryCompleteCalls = [];
|
|
105
|
+
onRunEndCalls = [];
|
|
106
|
+
|
|
107
|
+
// Create mock reporter plugin
|
|
108
|
+
const mockReporter: IReporter = {
|
|
109
|
+
name: "test-reporter",
|
|
110
|
+
async onRunStart(event: RunStartEvent) {
|
|
111
|
+
onRunStartCalls.push(event);
|
|
112
|
+
},
|
|
113
|
+
async onStoryComplete(event: StoryCompleteEvent) {
|
|
114
|
+
onStoryCompleteCalls.push(event);
|
|
115
|
+
},
|
|
116
|
+
async onRunEnd(event: RunEndEvent) {
|
|
117
|
+
onRunEndCalls.push(event);
|
|
118
|
+
},
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
const plugin: NaxPlugin = {
|
|
122
|
+
name: "test-reporter-plugin",
|
|
123
|
+
version: "1.0.0",
|
|
124
|
+
provides: ["reporter"],
|
|
125
|
+
extensions: {
|
|
126
|
+
reporter: mockReporter,
|
|
127
|
+
},
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
// Write plugin to disk
|
|
131
|
+
const pluginCode = `
|
|
132
|
+
const mockReporter = {
|
|
133
|
+
name: "test-reporter",
|
|
134
|
+
async onRunStart(event) {
|
|
135
|
+
const fs = require("node:fs/promises");
|
|
136
|
+
const path = require("node:path");
|
|
137
|
+
const file = path.join("${tmpDir}", "run-start.json");
|
|
138
|
+
await fs.writeFile(file, JSON.stringify(event, null, 2));
|
|
139
|
+
},
|
|
140
|
+
async onStoryComplete(event) {
|
|
141
|
+
const fs = require("node:fs/promises");
|
|
142
|
+
const path = require("node:path");
|
|
143
|
+
const file = path.join("${tmpDir}", "story-complete-" + event.storyId + ".json");
|
|
144
|
+
await fs.writeFile(file, JSON.stringify(event, null, 2));
|
|
145
|
+
},
|
|
146
|
+
async onRunEnd(event) {
|
|
147
|
+
const fs = require("node:fs/promises");
|
|
148
|
+
const path = require("node:path");
|
|
149
|
+
const file = path.join("${tmpDir}", "run-end.json");
|
|
150
|
+
await fs.writeFile(file, JSON.stringify(event, null, 2));
|
|
151
|
+
},
|
|
152
|
+
};
|
|
153
|
+
|
|
154
|
+
export default {
|
|
155
|
+
name: "test-reporter-plugin",
|
|
156
|
+
version: "1.0.0",
|
|
157
|
+
provides: ["reporter"],
|
|
158
|
+
extensions: {
|
|
159
|
+
reporter: mockReporter,
|
|
160
|
+
},
|
|
161
|
+
};
|
|
162
|
+
`;
|
|
163
|
+
|
|
164
|
+
await fs.writeFile(path.join(pluginDir, "test-reporter.ts"), pluginCode);
|
|
165
|
+
|
|
166
|
+
// Create minimal config
|
|
167
|
+
config = {
|
|
168
|
+
agents: {
|
|
169
|
+
mock: { enabled: true },
|
|
170
|
+
},
|
|
171
|
+
routing: {
|
|
172
|
+
strategy: "complexity",
|
|
173
|
+
defaultTier: "fast",
|
|
174
|
+
defaultTestStrategy: "unit",
|
|
175
|
+
},
|
|
176
|
+
autoMode: {
|
|
177
|
+
defaultAgent: "mock",
|
|
178
|
+
complexityRouting: {
|
|
179
|
+
simple: "fast",
|
|
180
|
+
moderate: "balanced",
|
|
181
|
+
complex: "advanced",
|
|
182
|
+
},
|
|
183
|
+
escalation: {
|
|
184
|
+
enabled: false,
|
|
185
|
+
tierOrder: [],
|
|
186
|
+
},
|
|
187
|
+
},
|
|
188
|
+
execution: {
|
|
189
|
+
maxIterations: 20,
|
|
190
|
+
timeout: 1800000,
|
|
191
|
+
costLimit: 100,
|
|
192
|
+
iterationDelayMs: 0,
|
|
193
|
+
maxStoriesPerFeature: 100,
|
|
194
|
+
},
|
|
195
|
+
analyze: {
|
|
196
|
+
model: "balanced",
|
|
197
|
+
},
|
|
198
|
+
models: {
|
|
199
|
+
fast: {
|
|
200
|
+
model: "claude-3-5-haiku-20241022",
|
|
201
|
+
apiKeyEnvVar: "ANTHROPIC_API_KEY",
|
|
202
|
+
},
|
|
203
|
+
balanced: {
|
|
204
|
+
model: "claude-3-5-sonnet-20241022",
|
|
205
|
+
apiKeyEnvVar: "ANTHROPIC_API_KEY",
|
|
206
|
+
},
|
|
207
|
+
advanced: {
|
|
208
|
+
model: "claude-3-opus-20240229",
|
|
209
|
+
apiKeyEnvVar: "ANTHROPIC_API_KEY",
|
|
210
|
+
},
|
|
211
|
+
},
|
|
212
|
+
quality: {
|
|
213
|
+
commands: {},
|
|
214
|
+
},
|
|
215
|
+
acceptance: {
|
|
216
|
+
enabled: false,
|
|
217
|
+
maxRetries: 3,
|
|
218
|
+
},
|
|
219
|
+
plugins: [
|
|
220
|
+
{
|
|
221
|
+
module: path.join(pluginDir, "test-reporter.ts"),
|
|
222
|
+
},
|
|
223
|
+
],
|
|
224
|
+
} as NaxConfig;
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
afterEach(async () => {
|
|
228
|
+
// Cleanup
|
|
229
|
+
try {
|
|
230
|
+
await fs.rm(tmpDir, { recursive: true, force: true });
|
|
231
|
+
} catch (error) {
|
|
232
|
+
// Ignore cleanup errors
|
|
233
|
+
}
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
test("AC1: onRunStart fires once at run start with runId, feature, totalStories, startTime", async () => {
|
|
237
|
+
// Create minimal PRD with 2 stories
|
|
238
|
+
const prd = {
|
|
239
|
+
feature: "test-feature",
|
|
240
|
+
userStories: [
|
|
241
|
+
{
|
|
242
|
+
id: "US-001",
|
|
243
|
+
title: "Story 1",
|
|
244
|
+
description: "Test story 1",
|
|
245
|
+
acceptanceCriteria: ["AC1: Should work"],
|
|
246
|
+
status: "pending" as const,
|
|
247
|
+
dependencies: [],
|
|
248
|
+
tags: [],
|
|
249
|
+
},
|
|
250
|
+
{
|
|
251
|
+
id: "US-002",
|
|
252
|
+
title: "Story 2",
|
|
253
|
+
description: "Test story 2",
|
|
254
|
+
acceptanceCriteria: ["AC1: Should work"],
|
|
255
|
+
status: "pending" as const,
|
|
256
|
+
dependencies: [],
|
|
257
|
+
tags: [],
|
|
258
|
+
},
|
|
259
|
+
],
|
|
260
|
+
};
|
|
261
|
+
|
|
262
|
+
await savePRD(prd, prdPath);
|
|
263
|
+
|
|
264
|
+
const hooks = await loadHooksConfig(workdir);
|
|
265
|
+
|
|
266
|
+
// Run in dry-run mode
|
|
267
|
+
await run({
|
|
268
|
+
prdPath,
|
|
269
|
+
workdir,
|
|
270
|
+
config,
|
|
271
|
+
hooks,
|
|
272
|
+
feature: "test-feature",
|
|
273
|
+
dryRun: true,
|
|
274
|
+
useBatch: false,
|
|
275
|
+
skipPrecheck: true,
|
|
276
|
+
});
|
|
277
|
+
|
|
278
|
+
// Verify onRunStart was called
|
|
279
|
+
const runStartFile = path.join(tmpDir, "run-start.json");
|
|
280
|
+
const runStartExists = await Bun.file(runStartFile).exists();
|
|
281
|
+
expect(runStartExists).toBe(true);
|
|
282
|
+
|
|
283
|
+
const runStartData = JSON.parse(await Bun.file(runStartFile).text());
|
|
284
|
+
|
|
285
|
+
// Verify event structure
|
|
286
|
+
expect(runStartData).toHaveProperty("runId");
|
|
287
|
+
expect(runStartData.runId).toContain("run-");
|
|
288
|
+
expect(runStartData.feature).toBe("test-feature");
|
|
289
|
+
expect(runStartData.totalStories).toBe(2);
|
|
290
|
+
expect(runStartData).toHaveProperty("startTime");
|
|
291
|
+
expect(new Date(runStartData.startTime).toString()).not.toBe("Invalid Date");
|
|
292
|
+
});
|
|
293
|
+
|
|
294
|
+
test("AC2: onStoryComplete fires after each story with storyId, status, durationMs, cost, tier, testStrategy", async () => {
|
|
295
|
+
// Create minimal PRD with 1 story
|
|
296
|
+
const prd = {
|
|
297
|
+
feature: "test-feature",
|
|
298
|
+
userStories: [
|
|
299
|
+
{
|
|
300
|
+
id: "US-001",
|
|
301
|
+
title: "Story 1",
|
|
302
|
+
description: "Test story 1",
|
|
303
|
+
acceptanceCriteria: ["AC1: Should work"],
|
|
304
|
+
status: "pending" as const,
|
|
305
|
+
dependencies: [],
|
|
306
|
+
tags: [],
|
|
307
|
+
},
|
|
308
|
+
],
|
|
309
|
+
};
|
|
310
|
+
|
|
311
|
+
await savePRD(prd, prdPath);
|
|
312
|
+
|
|
313
|
+
const hooks = await loadHooksConfig(workdir);
|
|
314
|
+
|
|
315
|
+
// Run in dry-run mode
|
|
316
|
+
await run({
|
|
317
|
+
prdPath,
|
|
318
|
+
workdir,
|
|
319
|
+
config,
|
|
320
|
+
hooks,
|
|
321
|
+
feature: "test-feature",
|
|
322
|
+
dryRun: true,
|
|
323
|
+
useBatch: false,
|
|
324
|
+
skipPrecheck: true,
|
|
325
|
+
});
|
|
326
|
+
|
|
327
|
+
// Verify onStoryComplete was called
|
|
328
|
+
const storyCompleteFile = path.join(tmpDir, "story-complete-US-001.json");
|
|
329
|
+
const storyCompleteExists = await Bun.file(storyCompleteFile).exists();
|
|
330
|
+
expect(storyCompleteExists).toBe(true);
|
|
331
|
+
|
|
332
|
+
const storyCompleteData = JSON.parse(await Bun.file(storyCompleteFile).text());
|
|
333
|
+
|
|
334
|
+
// Verify event structure
|
|
335
|
+
expect(storyCompleteData).toHaveProperty("runId");
|
|
336
|
+
expect(storyCompleteData.storyId).toBe("US-001");
|
|
337
|
+
expect(storyCompleteData.status).toBe("completed");
|
|
338
|
+
expect(typeof storyCompleteData.durationMs).toBe("number");
|
|
339
|
+
expect(storyCompleteData.durationMs).toBeGreaterThanOrEqual(0);
|
|
340
|
+
expect(typeof storyCompleteData.cost).toBe("number");
|
|
341
|
+
expect(storyCompleteData).toHaveProperty("tier");
|
|
342
|
+
expect(storyCompleteData).toHaveProperty("testStrategy");
|
|
343
|
+
});
|
|
344
|
+
|
|
345
|
+
test("AC3: onRunEnd fires once at run end with runId, totalDurationMs, totalCost, storySummary counts", async () => {
|
|
346
|
+
// Create minimal PRD with 2 stories
|
|
347
|
+
const prd = {
|
|
348
|
+
feature: "test-feature",
|
|
349
|
+
userStories: [
|
|
350
|
+
{
|
|
351
|
+
id: "US-001",
|
|
352
|
+
title: "Story 1",
|
|
353
|
+
description: "Test story 1",
|
|
354
|
+
acceptanceCriteria: ["AC1: Should work"],
|
|
355
|
+
status: "pending" as const,
|
|
356
|
+
dependencies: [],
|
|
357
|
+
tags: [],
|
|
358
|
+
},
|
|
359
|
+
{
|
|
360
|
+
id: "US-002",
|
|
361
|
+
title: "Story 2",
|
|
362
|
+
description: "Test story 2",
|
|
363
|
+
acceptanceCriteria: ["AC1: Should work"],
|
|
364
|
+
status: "pending" as const,
|
|
365
|
+
dependencies: [],
|
|
366
|
+
tags: [],
|
|
367
|
+
},
|
|
368
|
+
],
|
|
369
|
+
};
|
|
370
|
+
|
|
371
|
+
await savePRD(prd, prdPath);
|
|
372
|
+
|
|
373
|
+
const hooks = await loadHooksConfig(workdir);
|
|
374
|
+
|
|
375
|
+
// Run in dry-run mode
|
|
376
|
+
await run({
|
|
377
|
+
prdPath,
|
|
378
|
+
workdir,
|
|
379
|
+
config,
|
|
380
|
+
hooks,
|
|
381
|
+
feature: "test-feature",
|
|
382
|
+
dryRun: true,
|
|
383
|
+
useBatch: false,
|
|
384
|
+
skipPrecheck: true,
|
|
385
|
+
});
|
|
386
|
+
|
|
387
|
+
// Verify onRunEnd was called
|
|
388
|
+
const runEndFile = path.join(tmpDir, "run-end.json");
|
|
389
|
+
const runEndExists = await Bun.file(runEndFile).exists();
|
|
390
|
+
expect(runEndExists).toBe(true);
|
|
391
|
+
|
|
392
|
+
const runEndData = JSON.parse(await Bun.file(runEndFile).text());
|
|
393
|
+
|
|
394
|
+
// Verify event structure
|
|
395
|
+
expect(runEndData).toHaveProperty("runId");
|
|
396
|
+
expect(typeof runEndData.totalDurationMs).toBe("number");
|
|
397
|
+
expect(runEndData.totalDurationMs).toBeGreaterThanOrEqual(0);
|
|
398
|
+
expect(typeof runEndData.totalCost).toBe("number");
|
|
399
|
+
expect(runEndData).toHaveProperty("storySummary");
|
|
400
|
+
expect(runEndData.storySummary).toHaveProperty("completed");
|
|
401
|
+
expect(runEndData.storySummary).toHaveProperty("failed");
|
|
402
|
+
expect(runEndData.storySummary).toHaveProperty("skipped");
|
|
403
|
+
expect(runEndData.storySummary).toHaveProperty("paused");
|
|
404
|
+
expect(runEndData.storySummary.completed).toBe(2); // Both stories completed in dry-run
|
|
405
|
+
});
|
|
406
|
+
|
|
407
|
+
test("AC4: Reporter errors are caught and logged but never block execution", async () => {
|
|
408
|
+
// Create a failing reporter
|
|
409
|
+
const failingPluginCode = `
|
|
410
|
+
const failingReporter = {
|
|
411
|
+
name: "failing-reporter",
|
|
412
|
+
async onRunStart(event) {
|
|
413
|
+
throw new Error("onRunStart intentional failure");
|
|
414
|
+
},
|
|
415
|
+
async onStoryComplete(event) {
|
|
416
|
+
throw new Error("onStoryComplete intentional failure");
|
|
417
|
+
},
|
|
418
|
+
async onRunEnd(event) {
|
|
419
|
+
throw new Error("onRunEnd intentional failure");
|
|
420
|
+
},
|
|
421
|
+
};
|
|
422
|
+
|
|
423
|
+
export default {
|
|
424
|
+
name: "failing-reporter-plugin",
|
|
425
|
+
version: "1.0.0",
|
|
426
|
+
provides: ["reporter"],
|
|
427
|
+
extensions: {
|
|
428
|
+
reporter: failingReporter,
|
|
429
|
+
},
|
|
430
|
+
};
|
|
431
|
+
`;
|
|
432
|
+
|
|
433
|
+
await fs.writeFile(path.join(pluginDir, "failing-reporter.ts"), failingPluginCode);
|
|
434
|
+
|
|
435
|
+
// Update config to use failing reporter
|
|
436
|
+
config.plugins = [
|
|
437
|
+
{
|
|
438
|
+
module: path.join(pluginDir, "failing-reporter.ts"),
|
|
439
|
+
},
|
|
440
|
+
];
|
|
441
|
+
|
|
442
|
+
// Create minimal PRD
|
|
443
|
+
const prd = {
|
|
444
|
+
feature: "test-feature",
|
|
445
|
+
userStories: [
|
|
446
|
+
{
|
|
447
|
+
id: "US-001",
|
|
448
|
+
title: "Story 1",
|
|
449
|
+
description: "Test story 1",
|
|
450
|
+
acceptanceCriteria: ["AC1: Should work"],
|
|
451
|
+
status: "pending" as const,
|
|
452
|
+
dependencies: [],
|
|
453
|
+
tags: [],
|
|
454
|
+
},
|
|
455
|
+
],
|
|
456
|
+
};
|
|
457
|
+
|
|
458
|
+
await savePRD(prd, prdPath);
|
|
459
|
+
|
|
460
|
+
const hooks = await loadHooksConfig(workdir);
|
|
461
|
+
|
|
462
|
+
// Run should not throw even though reporter fails
|
|
463
|
+
const result = await run({
|
|
464
|
+
prdPath,
|
|
465
|
+
workdir,
|
|
466
|
+
config,
|
|
467
|
+
hooks,
|
|
468
|
+
feature: "test-feature",
|
|
469
|
+
dryRun: true,
|
|
470
|
+
useBatch: false,
|
|
471
|
+
skipPrecheck: true,
|
|
472
|
+
});
|
|
473
|
+
|
|
474
|
+
// Verify run completed successfully despite reporter errors
|
|
475
|
+
expect(result.success).toBe(true);
|
|
476
|
+
expect(result.storiesCompleted).toBe(1);
|
|
477
|
+
});
|
|
478
|
+
|
|
479
|
+
test("AC5: Multiple reporters all receive events (not short-circuited on error)", async () => {
|
|
480
|
+
// Create two reporters
|
|
481
|
+
const reporter1Code = `
|
|
482
|
+
const reporter1 = {
|
|
483
|
+
name: "reporter-1",
|
|
484
|
+
async onRunStart(event) {
|
|
485
|
+
const fs = require("node:fs/promises");
|
|
486
|
+
const path = require("node:path");
|
|
487
|
+
const file = path.join("${tmpDir}", "reporter-1-run-start.json");
|
|
488
|
+
await fs.writeFile(file, JSON.stringify(event, null, 2));
|
|
489
|
+
},
|
|
490
|
+
async onStoryComplete(event) {
|
|
491
|
+
const fs = require("node:fs/promises");
|
|
492
|
+
const path = require("node:path");
|
|
493
|
+
const file = path.join("${tmpDir}", "reporter-1-story-" + event.storyId + ".json");
|
|
494
|
+
await fs.writeFile(file, JSON.stringify(event, null, 2));
|
|
495
|
+
},
|
|
496
|
+
async onRunEnd(event) {
|
|
497
|
+
const fs = require("node:fs/promises");
|
|
498
|
+
const path = require("node:path");
|
|
499
|
+
const file = path.join("${tmpDir}", "reporter-1-run-end.json");
|
|
500
|
+
await fs.writeFile(file, JSON.stringify(event, null, 2));
|
|
501
|
+
},
|
|
502
|
+
};
|
|
503
|
+
|
|
504
|
+
export default {
|
|
505
|
+
name: "reporter-1-plugin",
|
|
506
|
+
version: "1.0.0",
|
|
507
|
+
provides: ["reporter"],
|
|
508
|
+
extensions: {
|
|
509
|
+
reporter: reporter1,
|
|
510
|
+
},
|
|
511
|
+
};
|
|
512
|
+
`;
|
|
513
|
+
|
|
514
|
+
const reporter2Code = `
|
|
515
|
+
const reporter2 = {
|
|
516
|
+
name: "reporter-2",
|
|
517
|
+
async onRunStart(event) {
|
|
518
|
+
const fs = require("node:fs/promises");
|
|
519
|
+
const path = require("node:path");
|
|
520
|
+
const file = path.join("${tmpDir}", "reporter-2-run-start.json");
|
|
521
|
+
await fs.writeFile(file, JSON.stringify(event, null, 2));
|
|
522
|
+
},
|
|
523
|
+
async onStoryComplete(event) {
|
|
524
|
+
const fs = require("node:fs/promises");
|
|
525
|
+
const path = require("node:path");
|
|
526
|
+
const file = path.join("${tmpDir}", "reporter-2-story-" + event.storyId + ".json");
|
|
527
|
+
await fs.writeFile(file, JSON.stringify(event, null, 2));
|
|
528
|
+
},
|
|
529
|
+
async onRunEnd(event) {
|
|
530
|
+
const fs = require("node:fs/promises");
|
|
531
|
+
const path = require("node:path");
|
|
532
|
+
const file = path.join("${tmpDir}", "reporter-2-run-end.json");
|
|
533
|
+
await fs.writeFile(file, JSON.stringify(event, null, 2));
|
|
534
|
+
},
|
|
535
|
+
};
|
|
536
|
+
|
|
537
|
+
export default {
|
|
538
|
+
name: "reporter-2-plugin",
|
|
539
|
+
version: "1.0.0",
|
|
540
|
+
provides: ["reporter"],
|
|
541
|
+
extensions: {
|
|
542
|
+
reporter: reporter2,
|
|
543
|
+
},
|
|
544
|
+
};
|
|
545
|
+
`;
|
|
546
|
+
|
|
547
|
+
await fs.writeFile(path.join(pluginDir, "reporter-1.ts"), reporter1Code);
|
|
548
|
+
await fs.writeFile(path.join(pluginDir, "reporter-2.ts"), reporter2Code);
|
|
549
|
+
|
|
550
|
+
// Update config to use both reporters
|
|
551
|
+
config.plugins = [
|
|
552
|
+
{
|
|
553
|
+
module: path.join(pluginDir, "reporter-1.ts"),
|
|
554
|
+
},
|
|
555
|
+
{
|
|
556
|
+
module: path.join(pluginDir, "reporter-2.ts"),
|
|
557
|
+
},
|
|
558
|
+
];
|
|
559
|
+
|
|
560
|
+
// Create minimal PRD
|
|
561
|
+
const prd = {
|
|
562
|
+
feature: "test-feature",
|
|
563
|
+
userStories: [
|
|
564
|
+
{
|
|
565
|
+
id: "US-001",
|
|
566
|
+
title: "Story 1",
|
|
567
|
+
description: "Test story 1",
|
|
568
|
+
acceptanceCriteria: ["AC1: Should work"],
|
|
569
|
+
status: "pending" as const,
|
|
570
|
+
dependencies: [],
|
|
571
|
+
tags: [],
|
|
572
|
+
},
|
|
573
|
+
],
|
|
574
|
+
};
|
|
575
|
+
|
|
576
|
+
await savePRD(prd, prdPath);
|
|
577
|
+
|
|
578
|
+
const hooks = await loadHooksConfig(workdir);
|
|
579
|
+
|
|
580
|
+
await run({
|
|
581
|
+
prdPath,
|
|
582
|
+
workdir,
|
|
583
|
+
config,
|
|
584
|
+
hooks,
|
|
585
|
+
feature: "test-feature",
|
|
586
|
+
dryRun: true,
|
|
587
|
+
useBatch: false,
|
|
588
|
+
skipPrecheck: true,
|
|
589
|
+
});
|
|
590
|
+
|
|
591
|
+
// Verify both reporters received events
|
|
592
|
+
const reporter1RunStart = path.join(tmpDir, "reporter-1-run-start.json");
|
|
593
|
+
const reporter2RunStart = path.join(tmpDir, "reporter-2-run-start.json");
|
|
594
|
+
const reporter1Story = path.join(tmpDir, "reporter-1-story-US-001.json");
|
|
595
|
+
const reporter2Story = path.join(tmpDir, "reporter-2-story-US-001.json");
|
|
596
|
+
const reporter1RunEnd = path.join(tmpDir, "reporter-1-run-end.json");
|
|
597
|
+
const reporter2RunEnd = path.join(tmpDir, "reporter-2-run-end.json");
|
|
598
|
+
|
|
599
|
+
expect(await Bun.file(reporter1RunStart).exists()).toBe(true);
|
|
600
|
+
expect(await Bun.file(reporter2RunStart).exists()).toBe(true);
|
|
601
|
+
expect(await Bun.file(reporter1Story).exists()).toBe(true);
|
|
602
|
+
expect(await Bun.file(reporter2Story).exists()).toBe(true);
|
|
603
|
+
expect(await Bun.file(reporter1RunEnd).exists()).toBe(true);
|
|
604
|
+
expect(await Bun.file(reporter2RunEnd).exists()).toBe(true);
|
|
605
|
+
});
|
|
606
|
+
|
|
607
|
+
test("AC5 (edge case): Second reporter receives events even if first reporter fails", async () => {
|
|
608
|
+
// Create a failing reporter and a working reporter
|
|
609
|
+
const failingReporterCode = `
|
|
610
|
+
const failingReporter = {
|
|
611
|
+
name: "failing-reporter",
|
|
612
|
+
async onRunStart(event) {
|
|
613
|
+
throw new Error("onRunStart failure");
|
|
614
|
+
},
|
|
615
|
+
async onStoryComplete(event) {
|
|
616
|
+
throw new Error("onStoryComplete failure");
|
|
617
|
+
},
|
|
618
|
+
async onRunEnd(event) {
|
|
619
|
+
throw new Error("onRunEnd failure");
|
|
620
|
+
},
|
|
621
|
+
};
|
|
622
|
+
|
|
623
|
+
export default {
|
|
624
|
+
name: "failing-reporter-plugin",
|
|
625
|
+
version: "1.0.0",
|
|
626
|
+
provides: ["reporter"],
|
|
627
|
+
extensions: {
|
|
628
|
+
reporter: failingReporter,
|
|
629
|
+
},
|
|
630
|
+
};
|
|
631
|
+
`;
|
|
632
|
+
|
|
633
|
+
const workingReporterCode = `
|
|
634
|
+
const workingReporter = {
|
|
635
|
+
name: "working-reporter",
|
|
636
|
+
async onRunStart(event) {
|
|
637
|
+
const fs = require("node:fs/promises");
|
|
638
|
+
const path = require("node:path");
|
|
639
|
+
const file = path.join("${tmpDir}", "working-run-start.json");
|
|
640
|
+
await fs.writeFile(file, JSON.stringify(event, null, 2));
|
|
641
|
+
},
|
|
642
|
+
async onStoryComplete(event) {
|
|
643
|
+
const fs = require("node:fs/promises");
|
|
644
|
+
const path = require("node:path");
|
|
645
|
+
const file = path.join("${tmpDir}", "working-story-" + event.storyId + ".json");
|
|
646
|
+
await fs.writeFile(file, JSON.stringify(event, null, 2));
|
|
647
|
+
},
|
|
648
|
+
async onRunEnd(event) {
|
|
649
|
+
const fs = require("node:fs/promises");
|
|
650
|
+
const path = require("node:path");
|
|
651
|
+
const file = path.join("${tmpDir}", "working-run-end.json");
|
|
652
|
+
await fs.writeFile(file, JSON.stringify(event, null, 2));
|
|
653
|
+
},
|
|
654
|
+
};
|
|
655
|
+
|
|
656
|
+
export default {
|
|
657
|
+
name: "working-reporter-plugin",
|
|
658
|
+
version: "1.0.0",
|
|
659
|
+
provides: ["reporter"],
|
|
660
|
+
extensions: {
|
|
661
|
+
reporter: workingReporter,
|
|
662
|
+
},
|
|
663
|
+
};
|
|
664
|
+
`;
|
|
665
|
+
|
|
666
|
+
await fs.writeFile(path.join(pluginDir, "failing-reporter.ts"), failingReporterCode);
|
|
667
|
+
await fs.writeFile(path.join(pluginDir, "working-reporter.ts"), workingReporterCode);
|
|
668
|
+
|
|
669
|
+
// Update config to use both reporters (failing first)
|
|
670
|
+
config.plugins = [
|
|
671
|
+
{
|
|
672
|
+
module: path.join(pluginDir, "failing-reporter.ts"),
|
|
673
|
+
},
|
|
674
|
+
{
|
|
675
|
+
module: path.join(pluginDir, "working-reporter.ts"),
|
|
676
|
+
},
|
|
677
|
+
];
|
|
678
|
+
|
|
679
|
+
// Create minimal PRD
|
|
680
|
+
const prd = {
|
|
681
|
+
feature: "test-feature",
|
|
682
|
+
userStories: [
|
|
683
|
+
{
|
|
684
|
+
id: "US-001",
|
|
685
|
+
title: "Story 1",
|
|
686
|
+
description: "Test story 1",
|
|
687
|
+
acceptanceCriteria: ["AC1: Should work"],
|
|
688
|
+
status: "pending" as const,
|
|
689
|
+
dependencies: [],
|
|
690
|
+
tags: [],
|
|
691
|
+
},
|
|
692
|
+
],
|
|
693
|
+
};
|
|
694
|
+
|
|
695
|
+
await savePRD(prd, prdPath);
|
|
696
|
+
|
|
697
|
+
const hooks = await loadHooksConfig(workdir);
|
|
698
|
+
|
|
699
|
+
await run({
|
|
700
|
+
prdPath,
|
|
701
|
+
workdir,
|
|
702
|
+
config,
|
|
703
|
+
hooks,
|
|
704
|
+
feature: "test-feature",
|
|
705
|
+
dryRun: true,
|
|
706
|
+
useBatch: false,
|
|
707
|
+
skipPrecheck: true,
|
|
708
|
+
});
|
|
709
|
+
|
|
710
|
+
// Verify working reporter still received events despite first reporter failing
|
|
711
|
+
const workingRunStart = path.join(tmpDir, "working-run-start.json");
|
|
712
|
+
const workingStory = path.join(tmpDir, "working-story-US-001.json");
|
|
713
|
+
const workingRunEnd = path.join(tmpDir, "working-run-end.json");
|
|
714
|
+
|
|
715
|
+
expect(await Bun.file(workingRunStart).exists()).toBe(true);
|
|
716
|
+
expect(await Bun.file(workingStory).exists()).toBe(true);
|
|
717
|
+
expect(await Bun.file(workingRunEnd).exists()).toBe(true);
|
|
718
|
+
});
|
|
719
|
+
|
|
720
|
+
test("AC6: Events fire even when the run fails or is aborted (onRunEnd still fires)", async () => {
|
|
721
|
+
// Create PRD with a story that will be marked as failed
|
|
722
|
+
const prd = {
|
|
723
|
+
feature: "test-feature",
|
|
724
|
+
userStories: [
|
|
725
|
+
{
|
|
726
|
+
id: "US-001",
|
|
727
|
+
title: "Story 1",
|
|
728
|
+
description: "Test story 1",
|
|
729
|
+
acceptanceCriteria: ["AC1: Should work"],
|
|
730
|
+
status: "failed" as const,
|
|
731
|
+
dependencies: [],
|
|
732
|
+
},
|
|
733
|
+
],
|
|
734
|
+
};
|
|
735
|
+
|
|
736
|
+
await savePRD(prd, prdPath);
|
|
737
|
+
|
|
738
|
+
const hooks = await loadHooksConfig(workdir);
|
|
739
|
+
|
|
740
|
+
// Run should complete even though story is already failed
|
|
741
|
+
await run({
|
|
742
|
+
prdPath,
|
|
743
|
+
workdir,
|
|
744
|
+
config,
|
|
745
|
+
hooks,
|
|
746
|
+
feature: "test-feature",
|
|
747
|
+
dryRun: false,
|
|
748
|
+
useBatch: false,
|
|
749
|
+
skipPrecheck: true,
|
|
750
|
+
});
|
|
751
|
+
|
|
752
|
+
// Verify onRunStart and onRunEnd were still called
|
|
753
|
+
const runStartFile = path.join(tmpDir, "run-start.json");
|
|
754
|
+
const runEndFile = path.join(tmpDir, "run-end.json");
|
|
755
|
+
|
|
756
|
+
expect(await Bun.file(runStartFile).exists()).toBe(true);
|
|
757
|
+
expect(await Bun.file(runEndFile).exists()).toBe(true);
|
|
758
|
+
|
|
759
|
+
const runEndData = JSON.parse(await Bun.file(runEndFile).text());
|
|
760
|
+
expect(runEndData.storySummary.failed).toBeGreaterThan(0);
|
|
761
|
+
});
|
|
762
|
+
|
|
763
|
+
test("onStoryComplete receives correct status for paused stories", async () => {
|
|
764
|
+
// Create PRD with a paused story
|
|
765
|
+
const prd = {
|
|
766
|
+
feature: "test-feature",
|
|
767
|
+
userStories: [
|
|
768
|
+
{
|
|
769
|
+
id: "US-001",
|
|
770
|
+
title: "Story 1",
|
|
771
|
+
description: "Test story 1",
|
|
772
|
+
acceptanceCriteria: ["AC1: Should work"],
|
|
773
|
+
status: "paused" as const,
|
|
774
|
+
dependencies: [],
|
|
775
|
+
},
|
|
776
|
+
],
|
|
777
|
+
};
|
|
778
|
+
|
|
779
|
+
await savePRD(prd, prdPath);
|
|
780
|
+
|
|
781
|
+
const hooks = await loadHooksConfig(workdir);
|
|
782
|
+
|
|
783
|
+
// Run with paused story
|
|
784
|
+
await run({
|
|
785
|
+
prdPath,
|
|
786
|
+
workdir,
|
|
787
|
+
config,
|
|
788
|
+
hooks,
|
|
789
|
+
feature: "test-feature",
|
|
790
|
+
dryRun: false,
|
|
791
|
+
useBatch: false,
|
|
792
|
+
skipPrecheck: true,
|
|
793
|
+
});
|
|
794
|
+
|
|
795
|
+
// Note: paused stories are not picked up by getNextStory, so no onStoryComplete event fires
|
|
796
|
+
// This is expected behavior - paused stories don't get executed
|
|
797
|
+
});
|
|
798
|
+
|
|
799
|
+
test("onStoryComplete receives all required fields for different story outcomes", async () => {
|
|
800
|
+
// Create PRD with multiple stories
|
|
801
|
+
const prd = {
|
|
802
|
+
feature: "test-feature",
|
|
803
|
+
userStories: [
|
|
804
|
+
{
|
|
805
|
+
id: "US-001",
|
|
806
|
+
title: "Story 1",
|
|
807
|
+
description: "Test story 1",
|
|
808
|
+
acceptanceCriteria: ["AC1: Should work"],
|
|
809
|
+
status: "pending" as const,
|
|
810
|
+
dependencies: [],
|
|
811
|
+
tags: [],
|
|
812
|
+
},
|
|
813
|
+
{
|
|
814
|
+
id: "US-002",
|
|
815
|
+
title: "Story 2",
|
|
816
|
+
description: "Test story 2",
|
|
817
|
+
acceptanceCriteria: ["AC1: Should work"],
|
|
818
|
+
status: "pending" as const,
|
|
819
|
+
dependencies: [],
|
|
820
|
+
tags: [],
|
|
821
|
+
},
|
|
822
|
+
],
|
|
823
|
+
};
|
|
824
|
+
|
|
825
|
+
await savePRD(prd, prdPath);
|
|
826
|
+
|
|
827
|
+
const hooks = await loadHooksConfig(workdir);
|
|
828
|
+
|
|
829
|
+
// Run in dry-run mode
|
|
830
|
+
await run({
|
|
831
|
+
prdPath,
|
|
832
|
+
workdir,
|
|
833
|
+
config,
|
|
834
|
+
hooks,
|
|
835
|
+
feature: "test-feature",
|
|
836
|
+
dryRun: true,
|
|
837
|
+
useBatch: false,
|
|
838
|
+
skipPrecheck: true,
|
|
839
|
+
});
|
|
840
|
+
|
|
841
|
+
// Verify both stories received onStoryComplete events
|
|
842
|
+
const story1File = path.join(tmpDir, "story-complete-US-001.json");
|
|
843
|
+
const story2File = path.join(tmpDir, "story-complete-US-002.json");
|
|
844
|
+
|
|
845
|
+
expect(await Bun.file(story1File).exists()).toBe(true);
|
|
846
|
+
expect(await Bun.file(story2File).exists()).toBe(true);
|
|
847
|
+
|
|
848
|
+
const story1Data = JSON.parse(await Bun.file(story1File).text());
|
|
849
|
+
const story2Data = JSON.parse(await Bun.file(story2File).text());
|
|
850
|
+
|
|
851
|
+
// Verify both events have the same runId
|
|
852
|
+
expect(story1Data.runId).toBe(story2Data.runId);
|
|
853
|
+
|
|
854
|
+
// Verify both events have required fields
|
|
855
|
+
expect(story1Data.storyId).toBe("US-001");
|
|
856
|
+
expect(story2Data.storyId).toBe("US-002");
|
|
857
|
+
expect(story1Data.status).toBe("completed");
|
|
858
|
+
expect(story2Data.status).toBe("completed");
|
|
859
|
+
});
|
|
860
|
+
});
|