@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,464 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Integration Tests: Interaction Chain → Pipeline (BUG-025)
|
|
3
|
+
*
|
|
4
|
+
* Verifies the three acceptance criteria:
|
|
5
|
+
* AC1: interactionChain is accessible in PipelineContext
|
|
6
|
+
* AC2: Story reaching max retries triggers a 'human-review' interaction request
|
|
7
|
+
* AC3: CLI interaction plugin participates in non-headless human-review
|
|
8
|
+
*
|
|
9
|
+
* These tests FAIL until BUG-025 is implemented.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { describe, expect, mock, test } from "bun:test";
|
|
13
|
+
import type { NaxConfig } from "../../src/config";
|
|
14
|
+
import { InteractionChain } from "../../src/interaction/chain";
|
|
15
|
+
import { CLIInteractionPlugin } from "../../src/interaction/plugins/cli";
|
|
16
|
+
import type { InteractionPlugin, InteractionRequest, InteractionResponse, TriggerName } from "../../src/interaction/types";
|
|
17
|
+
import { TRIGGER_METADATA } from "../../src/interaction/types";
|
|
18
|
+
import type { PipelineContext } from "../../src/pipeline/types";
|
|
19
|
+
import type { SequentialExecutionContext } from "../../src/execution/sequential-executor";
|
|
20
|
+
import type { PRD, UserStory } from "../../src/prd/types";
|
|
21
|
+
|
|
22
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
23
|
+
// Fixtures
|
|
24
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
25
|
+
|
|
26
|
+
const baseConfig: Partial<NaxConfig> = {
|
|
27
|
+
execution: {
|
|
28
|
+
maxIterations: 10,
|
|
29
|
+
costLimit: 100,
|
|
30
|
+
iterationDelayMs: 0,
|
|
31
|
+
sessionTimeoutSeconds: 60,
|
|
32
|
+
maxStoriesPerFeature: 50,
|
|
33
|
+
rectification: { enabled: true, maxRetries: 2, fullSuiteTimeoutSeconds: 30, maxFailureSummaryChars: 500 },
|
|
34
|
+
verificationTimeoutSeconds: 60,
|
|
35
|
+
},
|
|
36
|
+
interaction: {
|
|
37
|
+
triggers: {
|
|
38
|
+
"human-review": { enabled: true },
|
|
39
|
+
},
|
|
40
|
+
defaults: {
|
|
41
|
+
timeout: 5000,
|
|
42
|
+
fallback: "skip" as const,
|
|
43
|
+
},
|
|
44
|
+
},
|
|
45
|
+
} as Partial<NaxConfig>;
|
|
46
|
+
|
|
47
|
+
const baseStory: UserStory = {
|
|
48
|
+
id: "US-001",
|
|
49
|
+
title: "Test story",
|
|
50
|
+
description: "A story that fails repeatedly",
|
|
51
|
+
acceptanceCriteria: [],
|
|
52
|
+
status: "pending",
|
|
53
|
+
attempts: 0,
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
const basePrd: PRD = {
|
|
57
|
+
feature: "test-feature",
|
|
58
|
+
version: "1",
|
|
59
|
+
userStories: [baseStory],
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
/** Build a mock InteractionPlugin that records sent requests */
|
|
63
|
+
function buildCapturingPlugin(): { plugin: InteractionPlugin; sentRequests: InteractionRequest[] } {
|
|
64
|
+
const sentRequests: InteractionRequest[] = [];
|
|
65
|
+
const plugin: InteractionPlugin = {
|
|
66
|
+
name: "capture",
|
|
67
|
+
send: mock(async (req: InteractionRequest) => {
|
|
68
|
+
sentRequests.push(req);
|
|
69
|
+
}),
|
|
70
|
+
receive: mock(async (requestId: string): Promise<InteractionResponse> => ({
|
|
71
|
+
requestId,
|
|
72
|
+
action: "skip",
|
|
73
|
+
respondedBy: "user",
|
|
74
|
+
respondedAt: Date.now(),
|
|
75
|
+
})),
|
|
76
|
+
};
|
|
77
|
+
return { plugin, sentRequests };
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function buildInteractionChain(): InteractionChain {
|
|
81
|
+
return new InteractionChain({ defaultTimeout: 5000, defaultFallback: "skip" });
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
85
|
+
// AC1: interactionChain is accessible in PipelineContext
|
|
86
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
87
|
+
|
|
88
|
+
describe("AC1: interactionChain accessible in PipelineContext", () => {
|
|
89
|
+
test("PipelineContext type includes 'interaction' field", () => {
|
|
90
|
+
// FAILS until BUG-025 adds 'interaction?: InteractionChain' to PipelineContext interface
|
|
91
|
+
// We create a minimal context and verify the interaction field is accepted by the type system at runtime
|
|
92
|
+
const chain = buildInteractionChain();
|
|
93
|
+
|
|
94
|
+
const ctx: PipelineContext = {
|
|
95
|
+
config: baseConfig as NaxConfig,
|
|
96
|
+
prd: basePrd,
|
|
97
|
+
story: baseStory,
|
|
98
|
+
stories: [baseStory],
|
|
99
|
+
routing: { complexity: "simple", modelTier: "fast", testStrategy: "test-after", reasoning: "test" },
|
|
100
|
+
workdir: "/tmp",
|
|
101
|
+
hooks: { hooks: {} },
|
|
102
|
+
// @ts-expect-error — will fail until PipelineContext adds 'interaction' field
|
|
103
|
+
interaction: chain,
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
// The interaction field should be accessible
|
|
107
|
+
expect((ctx as Record<string, unknown>).interaction).toBe(chain);
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
test("PipelineContext 'interaction' field is optional (not required)", () => {
|
|
111
|
+
// FAILS until BUG-025 adds the field (or if it becomes required incorrectly)
|
|
112
|
+
// A context without 'interaction' should still be valid
|
|
113
|
+
const ctx: PipelineContext = {
|
|
114
|
+
config: baseConfig as NaxConfig,
|
|
115
|
+
prd: basePrd,
|
|
116
|
+
story: baseStory,
|
|
117
|
+
stories: [baseStory],
|
|
118
|
+
routing: { complexity: "simple", modelTier: "fast", testStrategy: "test-after", reasoning: "test" },
|
|
119
|
+
workdir: "/tmp",
|
|
120
|
+
hooks: { hooks: {} },
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
// Without the field, it should be undefined (not cause errors)
|
|
124
|
+
expect((ctx as Record<string, unknown>).interaction).toBeUndefined();
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
test("interactionChain stored in PipelineContext survives pipeline execution (via runPipeline)", async () => {
|
|
128
|
+
// BUG-025: PipelineContext now accepts 'interaction?: InteractionChain' field.
|
|
129
|
+
// Verifies the field is set and preserved correctly.
|
|
130
|
+
const chain = buildInteractionChain();
|
|
131
|
+
const { plugin } = buildCapturingPlugin();
|
|
132
|
+
chain.register(plugin, 10);
|
|
133
|
+
|
|
134
|
+
const ctx: PipelineContext = {
|
|
135
|
+
config: baseConfig as NaxConfig,
|
|
136
|
+
prd: basePrd,
|
|
137
|
+
story: baseStory,
|
|
138
|
+
stories: [baseStory],
|
|
139
|
+
routing: { complexity: "simple", modelTier: "fast", testStrategy: "test-after", reasoning: "test" },
|
|
140
|
+
workdir: "/tmp",
|
|
141
|
+
hooks: { hooks: {} },
|
|
142
|
+
interaction: chain, // BUG-025: 'interaction' field now accepted by PipelineContext
|
|
143
|
+
};
|
|
144
|
+
|
|
145
|
+
// The interaction chain is preserved in the context
|
|
146
|
+
expect((ctx as Record<string, unknown>).interaction).toBeInstanceOf(InteractionChain);
|
|
147
|
+
expect((ctx as Record<string, unknown>).interaction).toBe(chain);
|
|
148
|
+
});
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
152
|
+
// SequentialExecutionContext — accepts interactionChain field
|
|
153
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
154
|
+
|
|
155
|
+
describe("SequentialExecutionContext accepts interactionChain", () => {
|
|
156
|
+
test("SequentialExecutionContext type has interactionChain field", () => {
|
|
157
|
+
// FAILS until BUG-025 adds 'interactionChain?: InteractionChain | null' to SequentialExecutionContext
|
|
158
|
+
const chain = buildInteractionChain();
|
|
159
|
+
|
|
160
|
+
const ctx: SequentialExecutionContext = {
|
|
161
|
+
prdPath: "/tmp/prd.json",
|
|
162
|
+
workdir: "/tmp",
|
|
163
|
+
config: baseConfig as NaxConfig,
|
|
164
|
+
hooks: { hooks: {} } as any,
|
|
165
|
+
feature: "test-feature",
|
|
166
|
+
dryRun: true,
|
|
167
|
+
useBatch: false,
|
|
168
|
+
pluginRegistry: { plugins: [], getReporters: () => [], teardownAll: async () => {} } as any,
|
|
169
|
+
statusWriter: { setPrd: () => {}, setCurrentStory: () => {}, setRunStatus: () => {}, update: async () => {} } as any,
|
|
170
|
+
logFilePath: undefined,
|
|
171
|
+
runId: "run-test-001",
|
|
172
|
+
startTime: Date.now(),
|
|
173
|
+
batchPlan: [],
|
|
174
|
+
// @ts-expect-error — will fail until SequentialExecutionContext adds 'interactionChain' field
|
|
175
|
+
interactionChain: chain,
|
|
176
|
+
};
|
|
177
|
+
|
|
178
|
+
expect((ctx as Record<string, unknown>).interactionChain).toBe(chain);
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
test("runner.ts passes interactionChain from setupRun result to executeSequential", () => {
|
|
182
|
+
// BUG-025: SequentialExecutionContext now has 'interactionChain?: InteractionChain | null'.
|
|
183
|
+
// Verifies the data flow: runner.ts -> executeSequential ctx -> pipeline handler.
|
|
184
|
+
const chain = buildInteractionChain();
|
|
185
|
+
|
|
186
|
+
const ctx: SequentialExecutionContext = {
|
|
187
|
+
prdPath: "/tmp/prd.json",
|
|
188
|
+
workdir: "/tmp",
|
|
189
|
+
config: baseConfig as NaxConfig,
|
|
190
|
+
hooks: { hooks: {} } as any,
|
|
191
|
+
feature: "test-feature",
|
|
192
|
+
dryRun: true,
|
|
193
|
+
useBatch: false,
|
|
194
|
+
pluginRegistry: { plugins: [], getReporters: () => [], teardownAll: async () => {} } as any,
|
|
195
|
+
statusWriter: { setPrd: () => {}, setCurrentStory: () => {}, setRunStatus: () => {}, update: async () => {} } as any,
|
|
196
|
+
logFilePath: undefined,
|
|
197
|
+
runId: "run-test-002",
|
|
198
|
+
startTime: Date.now(),
|
|
199
|
+
batchPlan: [],
|
|
200
|
+
interactionChain: chain, // BUG-025: field now exists in SequentialExecutionContext
|
|
201
|
+
};
|
|
202
|
+
|
|
203
|
+
// interactionChain is accessible and preserved in the context
|
|
204
|
+
expect((ctx as Record<string, unknown>).interactionChain).toBe(chain);
|
|
205
|
+
expect((ctx as Record<string, unknown>).interactionChain).toBeInstanceOf(InteractionChain);
|
|
206
|
+
});
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
210
|
+
// AC2: Story reaching max retries triggers 'human-review'
|
|
211
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
212
|
+
|
|
213
|
+
describe("AC2: max retries triggers human-review interaction", () => {
|
|
214
|
+
test("human-review trigger is called when story.attempts >= maxRetries", async () => {
|
|
215
|
+
// FAILS until BUG-025 wires executeTrigger('human-review', ...) into the failure/max-retries path
|
|
216
|
+
const chain = buildInteractionChain();
|
|
217
|
+
const { plugin, sentRequests } = buildCapturingPlugin();
|
|
218
|
+
chain.register(plugin, 10);
|
|
219
|
+
|
|
220
|
+
// Story has already hit max retries (attempts >= maxRetries = 2)
|
|
221
|
+
const exhaustedStory: UserStory = {
|
|
222
|
+
...baseStory,
|
|
223
|
+
id: "US-002",
|
|
224
|
+
attempts: 2,
|
|
225
|
+
status: "pending",
|
|
226
|
+
};
|
|
227
|
+
|
|
228
|
+
const prd: PRD = { ...basePrd, userStories: [exhaustedStory] };
|
|
229
|
+
|
|
230
|
+
// Import and invoke the failure handler or sequential executor path
|
|
231
|
+
// that should fire 'human-review' when a story has exceeded max retries
|
|
232
|
+
const { handlePipelineFailure } = await import("../../src/execution/pipeline-result-handler");
|
|
233
|
+
await handlePipelineFailure(
|
|
234
|
+
{
|
|
235
|
+
config: baseConfig as NaxConfig,
|
|
236
|
+
prd,
|
|
237
|
+
prdPath: "/tmp/prd.json",
|
|
238
|
+
workdir: "/tmp",
|
|
239
|
+
featureDir: undefined,
|
|
240
|
+
hooks: { hooks: {} } as any,
|
|
241
|
+
feature: "test-feature",
|
|
242
|
+
totalCost: 0,
|
|
243
|
+
startTime: Date.now(),
|
|
244
|
+
runId: "run-test-001",
|
|
245
|
+
pluginRegistry: { plugins: [], getReporters: () => [], teardownAll: async () => {} } as any,
|
|
246
|
+
story: exhaustedStory,
|
|
247
|
+
storiesToExecute: [exhaustedStory],
|
|
248
|
+
routing: { complexity: "simple", modelTier: "fast", testStrategy: "test-after", reasoning: "test" },
|
|
249
|
+
isBatchExecution: false,
|
|
250
|
+
allStoryMetrics: [],
|
|
251
|
+
timeoutRetryCountMap: new Map(),
|
|
252
|
+
storyGitRef: null,
|
|
253
|
+
// @ts-expect-error — interactionChain not in PipelineHandlerContext yet
|
|
254
|
+
interactionChain: chain,
|
|
255
|
+
},
|
|
256
|
+
{
|
|
257
|
+
success: false,
|
|
258
|
+
finalAction: "fail",
|
|
259
|
+
reason: "Max retries exceeded",
|
|
260
|
+
context: {
|
|
261
|
+
config: baseConfig as NaxConfig,
|
|
262
|
+
prd,
|
|
263
|
+
story: exhaustedStory,
|
|
264
|
+
stories: [exhaustedStory],
|
|
265
|
+
routing: { complexity: "simple", modelTier: "fast", testStrategy: "test-after", reasoning: "test" },
|
|
266
|
+
workdir: "/tmp",
|
|
267
|
+
hooks: { hooks: {} } as any,
|
|
268
|
+
},
|
|
269
|
+
} as any,
|
|
270
|
+
);
|
|
271
|
+
|
|
272
|
+
// FAILS: human-review trigger is not currently called in handlePipelineFailure
|
|
273
|
+
const humanReviewRequests = sentRequests.filter(
|
|
274
|
+
(r) => (r.metadata?.trigger as string) === "human-review",
|
|
275
|
+
);
|
|
276
|
+
expect(humanReviewRequests.length).toBeGreaterThan(0);
|
|
277
|
+
});
|
|
278
|
+
|
|
279
|
+
test("human-review request has storyId set to the failing story", async () => {
|
|
280
|
+
// BUG-025: handlePipelineFailure fires 'human-review' with the correct storyId
|
|
281
|
+
const chain = buildInteractionChain();
|
|
282
|
+
const { plugin, sentRequests } = buildCapturingPlugin();
|
|
283
|
+
chain.register(plugin, 10);
|
|
284
|
+
|
|
285
|
+
const failingStory: UserStory = {
|
|
286
|
+
...baseStory,
|
|
287
|
+
id: "US-FAILING",
|
|
288
|
+
attempts: 3, // exceeds maxRetries=2
|
|
289
|
+
status: "pending",
|
|
290
|
+
};
|
|
291
|
+
|
|
292
|
+
const prd: PRD = { ...basePrd, userStories: [failingStory] };
|
|
293
|
+
|
|
294
|
+
// Verify the trigger is enabled
|
|
295
|
+
const { isTriggerEnabled } = await import("../../src/interaction/triggers");
|
|
296
|
+
const enabled = isTriggerEnabled("human-review" as TriggerName, baseConfig as NaxConfig);
|
|
297
|
+
expect(enabled).toBe(true);
|
|
298
|
+
|
|
299
|
+
// Call handlePipelineFailure to trigger the human-review request
|
|
300
|
+
const { handlePipelineFailure } = await import("../../src/execution/pipeline-result-handler");
|
|
301
|
+
await handlePipelineFailure(
|
|
302
|
+
{
|
|
303
|
+
config: baseConfig as NaxConfig,
|
|
304
|
+
prd,
|
|
305
|
+
prdPath: "/tmp/prd.json",
|
|
306
|
+
workdir: "/tmp",
|
|
307
|
+
featureDir: undefined,
|
|
308
|
+
hooks: { hooks: {} } as any,
|
|
309
|
+
feature: "test-feature",
|
|
310
|
+
totalCost: 0,
|
|
311
|
+
startTime: Date.now(),
|
|
312
|
+
runId: "run-test-003",
|
|
313
|
+
pluginRegistry: { plugins: [], getReporters: () => [], teardownAll: async () => {} } as any,
|
|
314
|
+
story: failingStory,
|
|
315
|
+
storiesToExecute: [failingStory],
|
|
316
|
+
routing: { complexity: "simple", modelTier: "fast", testStrategy: "test-after", reasoning: "test" },
|
|
317
|
+
isBatchExecution: false,
|
|
318
|
+
allStoryMetrics: [],
|
|
319
|
+
timeoutRetryCountMap: new Map(),
|
|
320
|
+
storyGitRef: null,
|
|
321
|
+
interactionChain: chain,
|
|
322
|
+
},
|
|
323
|
+
{
|
|
324
|
+
success: false,
|
|
325
|
+
finalAction: "fail",
|
|
326
|
+
reason: "Max retries exceeded",
|
|
327
|
+
context: {
|
|
328
|
+
config: baseConfig as NaxConfig,
|
|
329
|
+
prd,
|
|
330
|
+
story: failingStory,
|
|
331
|
+
stories: [failingStory],
|
|
332
|
+
routing: { complexity: "simple", modelTier: "fast", testStrategy: "test-after", reasoning: "test" },
|
|
333
|
+
workdir: "/tmp",
|
|
334
|
+
hooks: { hooks: {} } as any,
|
|
335
|
+
},
|
|
336
|
+
} as any,
|
|
337
|
+
);
|
|
338
|
+
|
|
339
|
+
// human-review request should have the correct storyId
|
|
340
|
+
const humanReviewRequests = sentRequests.filter(
|
|
341
|
+
(r) => (r.metadata?.trigger as string) === "human-review",
|
|
342
|
+
);
|
|
343
|
+
expect(humanReviewRequests.length).toBeGreaterThan(0);
|
|
344
|
+
expect(humanReviewRequests[0].storyId).toBe("US-FAILING");
|
|
345
|
+
});
|
|
346
|
+
|
|
347
|
+
test("human-review response 'skip' causes story to be skipped", async () => {
|
|
348
|
+
// FAILS until BUG-025 implements the full human-review trigger + response handling
|
|
349
|
+
const chain = buildInteractionChain();
|
|
350
|
+
const plugin: InteractionPlugin = {
|
|
351
|
+
name: "skip-responder",
|
|
352
|
+
send: mock(async () => {}),
|
|
353
|
+
receive: mock(async (requestId: string): Promise<InteractionResponse> => ({
|
|
354
|
+
requestId,
|
|
355
|
+
action: "skip",
|
|
356
|
+
respondedBy: "user",
|
|
357
|
+
respondedAt: Date.now(),
|
|
358
|
+
})),
|
|
359
|
+
};
|
|
360
|
+
chain.register(plugin, 10);
|
|
361
|
+
|
|
362
|
+
// When human-review returns 'skip', the story outcome should be 'skipped'
|
|
363
|
+
const { executeSequential } = await import("../../src/execution/sequential-executor");
|
|
364
|
+
|
|
365
|
+
const exhaustedStory: UserStory = {
|
|
366
|
+
...baseStory,
|
|
367
|
+
id: "US-EXHAUST",
|
|
368
|
+
attempts: 3, // exceeds maxRetries=2
|
|
369
|
+
status: "pending",
|
|
370
|
+
};
|
|
371
|
+
|
|
372
|
+
// We cannot run full executeSequential (spawns agents, prechecks, etc.)
|
|
373
|
+
// Instead, verify the interactionChain is passed correctly and used
|
|
374
|
+
// This test serves as a sentinel: after BUG-025, executeSequential must accept interactionChain
|
|
375
|
+
const seqCtx = {
|
|
376
|
+
prdPath: "/tmp/prd.json",
|
|
377
|
+
workdir: "/tmp",
|
|
378
|
+
config: baseConfig as NaxConfig,
|
|
379
|
+
hooks: { hooks: {} },
|
|
380
|
+
feature: "test-feature",
|
|
381
|
+
dryRun: false,
|
|
382
|
+
useBatch: false,
|
|
383
|
+
pluginRegistry: { plugins: [], getReporters: () => [] },
|
|
384
|
+
statusWriter: { setPrd: () => {}, setCurrentStory: () => {}, update: async () => {} },
|
|
385
|
+
runId: "run-001",
|
|
386
|
+
startTime: Date.now(),
|
|
387
|
+
batchPlan: [],
|
|
388
|
+
interactionChain: chain, // <- not accepted yet
|
|
389
|
+
};
|
|
390
|
+
|
|
391
|
+
// The interactionChain field is not recognized by SequentialExecutionContext — FAILS
|
|
392
|
+
expect((seqCtx as Record<string, unknown>).interactionChain).toBeInstanceOf(InteractionChain);
|
|
393
|
+
// AND it should be passed through to pipeline execution
|
|
394
|
+
// (behavioral verification would require full integration, guarded by AC1 tests above)
|
|
395
|
+
});
|
|
396
|
+
});
|
|
397
|
+
|
|
398
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
399
|
+
// AC3: CLI interaction plugin participates in non-headless human-review
|
|
400
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
401
|
+
|
|
402
|
+
describe("AC3: CLI interaction plugin for non-headless human-review", () => {
|
|
403
|
+
test("CLIInteractionPlugin can be instantiated and registered in the chain", async () => {
|
|
404
|
+
// FAILS if CLIInteractionPlugin doesn't exist or cannot be imported
|
|
405
|
+
const plugin = new CLIInteractionPlugin();
|
|
406
|
+
expect(plugin.name).toBe("cli");
|
|
407
|
+
|
|
408
|
+
const chain = buildInteractionChain();
|
|
409
|
+
chain.register(plugin, 10);
|
|
410
|
+
|
|
411
|
+
// Primary plugin should be CLI
|
|
412
|
+
expect(chain.getPrimary()).toBe(plugin);
|
|
413
|
+
});
|
|
414
|
+
|
|
415
|
+
test("initInteractionChain registers CLI plugin for non-headless mode", async () => {
|
|
416
|
+
// FAILS if initInteractionChain doesn't register CLI plugin when headless=false
|
|
417
|
+
const { initInteractionChain } = await import("../../src/interaction");
|
|
418
|
+
|
|
419
|
+
const config = {
|
|
420
|
+
...baseConfig,
|
|
421
|
+
interaction: {
|
|
422
|
+
...baseConfig.interaction,
|
|
423
|
+
enabled: true,
|
|
424
|
+
plugin: "cli",
|
|
425
|
+
},
|
|
426
|
+
} as unknown as NaxConfig;
|
|
427
|
+
|
|
428
|
+
const chain = await initInteractionChain(config, false /* headless = false */);
|
|
429
|
+
|
|
430
|
+
// After BUG-025, the chain should have a CLI plugin registered for non-headless mode
|
|
431
|
+
// Currently may return null or a chain without a CLI plugin
|
|
432
|
+
expect(chain).not.toBeNull();
|
|
433
|
+
expect(chain?.getPrimary()).not.toBeNull();
|
|
434
|
+
expect(chain?.getPrimary()?.name).toBe("cli");
|
|
435
|
+
});
|
|
436
|
+
|
|
437
|
+
test("human-review request sent through CLI plugin contains all required fields", async () => {
|
|
438
|
+
// FAILS until BUG-025 implements human-review trigger in TRIGGER_METADATA
|
|
439
|
+
const { createTriggerRequest } = await import("../../src/interaction/triggers");
|
|
440
|
+
|
|
441
|
+
const request = createTriggerRequest(
|
|
442
|
+
"human-review" as TriggerName,
|
|
443
|
+
{
|
|
444
|
+
featureName: "my-feature",
|
|
445
|
+
storyId: "US-003",
|
|
446
|
+
iteration: 5,
|
|
447
|
+
reason: "Story has failed 5 times",
|
|
448
|
+
},
|
|
449
|
+
baseConfig as NaxConfig,
|
|
450
|
+
);
|
|
451
|
+
|
|
452
|
+
// Verify request is well-formed for CLI presentation
|
|
453
|
+
expect(request.type).toBe("confirm");
|
|
454
|
+
expect(request.stage).toBe("custom");
|
|
455
|
+
expect(request.featureName).toBe("my-feature");
|
|
456
|
+
expect(request.storyId).toBe("US-003");
|
|
457
|
+
expect(request.summary).toBeDefined();
|
|
458
|
+
expect(request.summary.length).toBeGreaterThan(0);
|
|
459
|
+
expect(request.fallback).toBe("skip");
|
|
460
|
+
expect(request.metadata?.trigger).toBe("human-review");
|
|
461
|
+
expect(request.metadata?.safety).toBe("yellow");
|
|
462
|
+
expect(request.createdAt).toBeGreaterThan(0);
|
|
463
|
+
});
|
|
464
|
+
});
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
import { afterEach, beforeEach, describe, expect, test } from "bun:test";
|
|
2
|
+
import { execSync } from "node:child_process";
|
|
3
|
+
import { mkdtempSync, rmSync } from "node:fs";
|
|
4
|
+
import { tmpdir } from "node:os";
|
|
5
|
+
import { join } from "node:path";
|
|
6
|
+
import { isSourceFile, isTestFile, verifyTestWriterIsolation } from "../../src/tdd";
|
|
7
|
+
|
|
8
|
+
describe("isTestFile", () => {
|
|
9
|
+
test("matches test/ directory", () => {
|
|
10
|
+
expect(isTestFile("test/auth.e2e-spec.ts")).toBe(true);
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
test("matches .spec.ts files", () => {
|
|
14
|
+
expect(isTestFile("src/auth/auth.spec.ts")).toBe(true);
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
test("matches .test.ts files", () => {
|
|
18
|
+
expect(isTestFile("src/utils.test.ts")).toBe(true);
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
test("does not match source files", () => {
|
|
22
|
+
expect(isTestFile("src/auth/auth.service.ts")).toBe(false);
|
|
23
|
+
});
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
describe("isSourceFile", () => {
|
|
27
|
+
test("matches src/ directory", () => {
|
|
28
|
+
expect(isSourceFile("src/auth/auth.service.ts")).toBe(true);
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
test("matches lib/ directory", () => {
|
|
32
|
+
expect(isSourceFile("lib/utils.ts")).toBe(true);
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
test("does not match test files in test/", () => {
|
|
36
|
+
expect(isSourceFile("test/auth.spec.ts")).toBe(false);
|
|
37
|
+
});
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
describe("verifyTestWriterIsolation", () => {
|
|
41
|
+
let testDir: string;
|
|
42
|
+
|
|
43
|
+
beforeEach(() => {
|
|
44
|
+
// Create a temporary git repository for testing
|
|
45
|
+
testDir = mkdtempSync(join(tmpdir(), "nax-isolation-test-"));
|
|
46
|
+
execSync("git init", { cwd: testDir, stdio: "ignore" });
|
|
47
|
+
execSync("git config user.email 'test@test.com'", { cwd: testDir, stdio: "ignore" });
|
|
48
|
+
execSync("git config user.name 'Test'", { cwd: testDir, stdio: "ignore" });
|
|
49
|
+
// Create initial commit
|
|
50
|
+
execSync("touch README.md", { cwd: testDir, stdio: "ignore" });
|
|
51
|
+
execSync("git add .", { cwd: testDir, stdio: "ignore" });
|
|
52
|
+
execSync("git commit -m 'Initial commit'", { cwd: testDir, stdio: "ignore" });
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
afterEach(() => {
|
|
56
|
+
// Clean up
|
|
57
|
+
rmSync(testDir, { recursive: true, force: true });
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
test("passes when only test files are modified", async () => {
|
|
61
|
+
// Create test file
|
|
62
|
+
execSync("mkdir -p test", { cwd: testDir, stdio: "ignore" });
|
|
63
|
+
execSync("echo 'test code' > test/example.test.ts", { cwd: testDir, stdio: "ignore" });
|
|
64
|
+
execSync("git add .", { cwd: testDir, stdio: "ignore" });
|
|
65
|
+
|
|
66
|
+
const result = await verifyTestWriterIsolation(testDir, "HEAD");
|
|
67
|
+
expect(result.passed).toBe(true);
|
|
68
|
+
expect(result.violations).toHaveLength(0);
|
|
69
|
+
expect(result.softViolations ?? []).toHaveLength(0);
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
test("fails when source files are modified (hard violation)", async () => {
|
|
73
|
+
// Create source file
|
|
74
|
+
execSync("mkdir -p src/auth", { cwd: testDir, stdio: "ignore" });
|
|
75
|
+
execSync("echo 'source code' > src/auth/service.ts", { cwd: testDir, stdio: "ignore" });
|
|
76
|
+
execSync("git add .", { cwd: testDir, stdio: "ignore" });
|
|
77
|
+
|
|
78
|
+
const result = await verifyTestWriterIsolation(testDir, "HEAD");
|
|
79
|
+
expect(result.passed).toBe(false);
|
|
80
|
+
expect(result.violations).toContain("src/auth/service.ts");
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
test("passes with soft violation when barrel export is modified (default allowed paths)", async () => {
|
|
84
|
+
// Create src/index.ts (barrel export)
|
|
85
|
+
execSync("mkdir -p src", { cwd: testDir, stdio: "ignore" });
|
|
86
|
+
execSync("echo 'export * from \"./module\"' > src/index.ts", { cwd: testDir, stdio: "ignore" });
|
|
87
|
+
execSync("git add .", { cwd: testDir, stdio: "ignore" });
|
|
88
|
+
|
|
89
|
+
const result = await verifyTestWriterIsolation(testDir, "HEAD");
|
|
90
|
+
expect(result.passed).toBe(true);
|
|
91
|
+
expect(result.violations).toHaveLength(0);
|
|
92
|
+
expect(result.softViolations).toContain("src/index.ts");
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
test("passes with soft violation when nested barrel export is modified", async () => {
|
|
96
|
+
// Create src/module/index.ts (nested barrel export)
|
|
97
|
+
execSync("mkdir -p src/module", { cwd: testDir, stdio: "ignore" });
|
|
98
|
+
execSync("echo 'export * from \"./service\"' > src/module/index.ts", { cwd: testDir, stdio: "ignore" });
|
|
99
|
+
execSync("git add .", { cwd: testDir, stdio: "ignore" });
|
|
100
|
+
|
|
101
|
+
const result = await verifyTestWriterIsolation(testDir, "HEAD");
|
|
102
|
+
expect(result.passed).toBe(true);
|
|
103
|
+
expect(result.violations).toHaveLength(0);
|
|
104
|
+
expect(result.softViolations).toContain("src/module/index.ts");
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
test("respects custom allowed paths", async () => {
|
|
108
|
+
// Create custom allowed file
|
|
109
|
+
execSync("mkdir -p src/config", { cwd: testDir, stdio: "ignore" });
|
|
110
|
+
execSync("echo 'config' > src/config/config.ts", { cwd: testDir, stdio: "ignore" });
|
|
111
|
+
execSync("git add .", { cwd: testDir, stdio: "ignore" });
|
|
112
|
+
|
|
113
|
+
const result = await verifyTestWriterIsolation(testDir, "HEAD", ["src/config/config.ts"]);
|
|
114
|
+
expect(result.passed).toBe(true);
|
|
115
|
+
expect(result.violations).toHaveLength(0);
|
|
116
|
+
expect(result.softViolations).toContain("src/config/config.ts");
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
test("combines hard and soft violations correctly", async () => {
|
|
120
|
+
// Create both barrel export (soft) and source file (hard)
|
|
121
|
+
execSync("mkdir -p src/auth", { cwd: testDir, stdio: "ignore" });
|
|
122
|
+
execSync("echo 'export * from \"./module\"' > src/index.ts", { cwd: testDir, stdio: "ignore" });
|
|
123
|
+
execSync("echo 'source code' > src/auth/service.ts", { cwd: testDir, stdio: "ignore" });
|
|
124
|
+
execSync("git add .", { cwd: testDir, stdio: "ignore" });
|
|
125
|
+
|
|
126
|
+
const result = await verifyTestWriterIsolation(testDir, "HEAD");
|
|
127
|
+
expect(result.passed).toBe(false);
|
|
128
|
+
expect(result.violations).toContain("src/auth/service.ts");
|
|
129
|
+
expect(result.softViolations).toContain("src/index.ts");
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
test("empty allowed paths array means no soft violations", async () => {
|
|
133
|
+
// Create src/index.ts
|
|
134
|
+
execSync("mkdir -p src", { cwd: testDir, stdio: "ignore" });
|
|
135
|
+
execSync("echo 'export * from \"./module\"' > src/index.ts", { cwd: testDir, stdio: "ignore" });
|
|
136
|
+
execSync("git add .", { cwd: testDir, stdio: "ignore" });
|
|
137
|
+
|
|
138
|
+
const result = await verifyTestWriterIsolation(testDir, "HEAD", []);
|
|
139
|
+
expect(result.passed).toBe(false);
|
|
140
|
+
expect(result.violations).toContain("src/index.ts");
|
|
141
|
+
expect(result.softViolations ?? []).toHaveLength(0);
|
|
142
|
+
});
|
|
143
|
+
});
|