@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,461 @@
|
|
|
1
|
+
import { afterEach, beforeEach, describe, expect, test } from "bun:test";
|
|
2
|
+
import { existsSync, readFileSync, rmSync } from "node:fs";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { Logger, getLogger, initLogger, resetLogger } from "../../src/logger/logger.js";
|
|
5
|
+
|
|
6
|
+
const TEST_LOG_DIR = path.join(process.cwd(), "test-logs");
|
|
7
|
+
const TEST_LOG_FILE = path.join(TEST_LOG_DIR, "test.jsonl");
|
|
8
|
+
|
|
9
|
+
describe("Logger", () => {
|
|
10
|
+
beforeEach(() => {
|
|
11
|
+
resetLogger();
|
|
12
|
+
// Clean up test logs
|
|
13
|
+
if (existsSync(TEST_LOG_DIR)) {
|
|
14
|
+
rmSync(TEST_LOG_DIR, { recursive: true, force: true });
|
|
15
|
+
}
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
afterEach(() => {
|
|
19
|
+
resetLogger();
|
|
20
|
+
// Clean up test logs
|
|
21
|
+
if (existsSync(TEST_LOG_DIR)) {
|
|
22
|
+
rmSync(TEST_LOG_DIR, { recursive: true, force: true });
|
|
23
|
+
}
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
describe("initialization", () => {
|
|
27
|
+
test("creates singleton logger instance", () => {
|
|
28
|
+
const logger = initLogger({ level: "info" });
|
|
29
|
+
expect(logger).toBeInstanceOf(Logger);
|
|
30
|
+
expect(getLogger()).toBe(logger);
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
test("throws when initializing twice", () => {
|
|
34
|
+
initLogger({ level: "info" });
|
|
35
|
+
expect(() => initLogger({ level: "info" })).toThrow("Logger already initialized");
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
test("returns no-op logger when getting logger before init", () => {
|
|
39
|
+
const logger = getLogger();
|
|
40
|
+
expect(logger).toBeDefined();
|
|
41
|
+
// No-op logger should have all methods but not throw
|
|
42
|
+
expect(typeof logger.info).toBe("function");
|
|
43
|
+
expect(typeof logger.warn).toBe("function");
|
|
44
|
+
expect(typeof logger.error).toBe("function");
|
|
45
|
+
expect(typeof logger.debug).toBe("function");
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
test("resets singleton for testing", () => {
|
|
49
|
+
initLogger({ level: "info" });
|
|
50
|
+
resetLogger();
|
|
51
|
+
const logger = getLogger();
|
|
52
|
+
expect(logger).toBeDefined();
|
|
53
|
+
// After reset, should return no-op logger
|
|
54
|
+
expect(typeof logger.info).toBe("function");
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
test("creates log file directory if it doesn't exist", () => {
|
|
58
|
+
initLogger({ level: "info", filePath: TEST_LOG_FILE });
|
|
59
|
+
expect(existsSync(TEST_LOG_DIR)).toBe(true);
|
|
60
|
+
});
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
describe("level gating (console)", () => {
|
|
64
|
+
test("error level shows only errors", () => {
|
|
65
|
+
const logger = initLogger({ level: "error", useChalk: false });
|
|
66
|
+
|
|
67
|
+
// Capture console output
|
|
68
|
+
const originalLog = console.log;
|
|
69
|
+
const logs: string[] = [];
|
|
70
|
+
console.log = (msg: string) => logs.push(msg);
|
|
71
|
+
|
|
72
|
+
logger.error("test", "error message");
|
|
73
|
+
logger.warn("test", "warn message");
|
|
74
|
+
logger.info("test", "info message");
|
|
75
|
+
logger.debug("test", "debug message");
|
|
76
|
+
|
|
77
|
+
console.log = originalLog;
|
|
78
|
+
|
|
79
|
+
expect(logs.length).toBe(1);
|
|
80
|
+
expect(logs[0]).toContain("error message");
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
test("warn level shows errors and warnings", () => {
|
|
84
|
+
const logger = initLogger({ level: "warn", useChalk: false });
|
|
85
|
+
|
|
86
|
+
const originalLog = console.log;
|
|
87
|
+
const logs: string[] = [];
|
|
88
|
+
console.log = (msg: string) => logs.push(msg);
|
|
89
|
+
|
|
90
|
+
logger.error("test", "error message");
|
|
91
|
+
logger.warn("test", "warn message");
|
|
92
|
+
logger.info("test", "info message");
|
|
93
|
+
logger.debug("test", "debug message");
|
|
94
|
+
|
|
95
|
+
console.log = originalLog;
|
|
96
|
+
|
|
97
|
+
expect(logs.length).toBe(2);
|
|
98
|
+
expect(logs[0]).toContain("error message");
|
|
99
|
+
expect(logs[1]).toContain("warn message");
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
test("info level shows errors, warnings, and info", () => {
|
|
103
|
+
const logger = initLogger({ level: "info", useChalk: false });
|
|
104
|
+
|
|
105
|
+
const originalLog = console.log;
|
|
106
|
+
const logs: string[] = [];
|
|
107
|
+
console.log = (msg: string) => logs.push(msg);
|
|
108
|
+
|
|
109
|
+
logger.error("test", "error message");
|
|
110
|
+
logger.warn("test", "warn message");
|
|
111
|
+
logger.info("test", "info message");
|
|
112
|
+
logger.debug("test", "debug message");
|
|
113
|
+
|
|
114
|
+
console.log = originalLog;
|
|
115
|
+
|
|
116
|
+
expect(logs.length).toBe(3);
|
|
117
|
+
expect(logs[0]).toContain("error message");
|
|
118
|
+
expect(logs[1]).toContain("warn message");
|
|
119
|
+
expect(logs[2]).toContain("info message");
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
test("debug level shows all messages", () => {
|
|
123
|
+
const logger = initLogger({ level: "debug", useChalk: false });
|
|
124
|
+
|
|
125
|
+
const originalLog = console.log;
|
|
126
|
+
const logs: string[] = [];
|
|
127
|
+
console.log = (msg: string) => logs.push(msg);
|
|
128
|
+
|
|
129
|
+
logger.error("test", "error message");
|
|
130
|
+
logger.warn("test", "warn message");
|
|
131
|
+
logger.info("test", "info message");
|
|
132
|
+
logger.debug("test", "debug message");
|
|
133
|
+
|
|
134
|
+
console.log = originalLog;
|
|
135
|
+
|
|
136
|
+
expect(logs.length).toBe(4);
|
|
137
|
+
expect(logs[0]).toContain("error message");
|
|
138
|
+
expect(logs[1]).toContain("warn message");
|
|
139
|
+
expect(logs[2]).toContain("info message");
|
|
140
|
+
expect(logs[3]).toContain("debug message");
|
|
141
|
+
});
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
describe("file output", () => {
|
|
145
|
+
test("writes all log levels to file regardless of console level", () => {
|
|
146
|
+
// Console level is "error", but file should get all levels
|
|
147
|
+
const logger = initLogger({ level: "error", filePath: TEST_LOG_FILE });
|
|
148
|
+
|
|
149
|
+
logger.error("test", "error message");
|
|
150
|
+
logger.warn("test", "warn message");
|
|
151
|
+
logger.info("test", "info message");
|
|
152
|
+
logger.debug("test", "debug message");
|
|
153
|
+
|
|
154
|
+
// Read log file
|
|
155
|
+
const content = readFileSync(TEST_LOG_FILE, "utf8");
|
|
156
|
+
const lines = content
|
|
157
|
+
.trim()
|
|
158
|
+
.split("\n")
|
|
159
|
+
.filter((line) => line);
|
|
160
|
+
|
|
161
|
+
expect(lines.length).toBe(4);
|
|
162
|
+
|
|
163
|
+
// Parse each line as JSON and verify
|
|
164
|
+
const entries = lines.map((line) => JSON.parse(line));
|
|
165
|
+
expect(entries[0].level).toBe("error");
|
|
166
|
+
expect(entries[1].level).toBe("warn");
|
|
167
|
+
expect(entries[2].level).toBe("info");
|
|
168
|
+
expect(entries[3].level).toBe("debug");
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
test("JSONL lines are valid JSON with required fields", () => {
|
|
172
|
+
const logger = initLogger({ level: "info", filePath: TEST_LOG_FILE });
|
|
173
|
+
|
|
174
|
+
logger.info("routing", "Task classified", { complexity: "simple" });
|
|
175
|
+
|
|
176
|
+
const content = readFileSync(TEST_LOG_FILE, "utf8");
|
|
177
|
+
const line = content.trim();
|
|
178
|
+
|
|
179
|
+
// Verify it's valid JSON
|
|
180
|
+
const entry = JSON.parse(line);
|
|
181
|
+
|
|
182
|
+
// Verify required fields
|
|
183
|
+
expect(entry.timestamp).toBeDefined();
|
|
184
|
+
expect(typeof entry.timestamp).toBe("string");
|
|
185
|
+
expect(entry.level).toBe("info");
|
|
186
|
+
expect(entry.stage).toBe("routing");
|
|
187
|
+
expect(entry.message).toBe("Task classified");
|
|
188
|
+
expect(entry.data).toEqual({ complexity: "simple" });
|
|
189
|
+
|
|
190
|
+
// Verify timestamp is valid ISO format
|
|
191
|
+
expect(() => new Date(entry.timestamp)).not.toThrow();
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
test("handles log entries without data field", () => {
|
|
195
|
+
const logger = initLogger({ level: "info", filePath: TEST_LOG_FILE });
|
|
196
|
+
|
|
197
|
+
logger.info("test", "message without data");
|
|
198
|
+
|
|
199
|
+
const content = readFileSync(TEST_LOG_FILE, "utf8");
|
|
200
|
+
const entry = JSON.parse(content.trim());
|
|
201
|
+
|
|
202
|
+
expect(entry.data).toBeUndefined();
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
test("handles log entries without storyId", () => {
|
|
206
|
+
const logger = initLogger({ level: "info", filePath: TEST_LOG_FILE });
|
|
207
|
+
|
|
208
|
+
logger.info("test", "message without storyId");
|
|
209
|
+
|
|
210
|
+
const content = readFileSync(TEST_LOG_FILE, "utf8");
|
|
211
|
+
const entry = JSON.parse(content.trim());
|
|
212
|
+
|
|
213
|
+
expect(entry.storyId).toBeUndefined();
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
test("appends to existing log file", () => {
|
|
217
|
+
const logger = initLogger({ level: "info", filePath: TEST_LOG_FILE });
|
|
218
|
+
|
|
219
|
+
logger.info("test", "first message");
|
|
220
|
+
logger.info("test", "second message");
|
|
221
|
+
|
|
222
|
+
const content = readFileSync(TEST_LOG_FILE, "utf8");
|
|
223
|
+
const lines = content
|
|
224
|
+
.trim()
|
|
225
|
+
.split("\n")
|
|
226
|
+
.filter((line) => line);
|
|
227
|
+
|
|
228
|
+
expect(lines.length).toBe(2);
|
|
229
|
+
|
|
230
|
+
const entries = lines.map((line) => JSON.parse(line));
|
|
231
|
+
expect(entries[0].message).toBe("first message");
|
|
232
|
+
expect(entries[1].message).toBe("second message");
|
|
233
|
+
});
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
describe("withStory", () => {
|
|
237
|
+
test("returns story-scoped logger", () => {
|
|
238
|
+
const logger = initLogger({ level: "info", useChalk: false });
|
|
239
|
+
const storyLogger = logger.withStory("user-auth-001");
|
|
240
|
+
|
|
241
|
+
expect(storyLogger).toBeDefined();
|
|
242
|
+
expect(storyLogger.error).toBeInstanceOf(Function);
|
|
243
|
+
expect(storyLogger.warn).toBeInstanceOf(Function);
|
|
244
|
+
expect(storyLogger.info).toBeInstanceOf(Function);
|
|
245
|
+
expect(storyLogger.debug).toBeInstanceOf(Function);
|
|
246
|
+
});
|
|
247
|
+
|
|
248
|
+
test("auto-injects storyId into console output", () => {
|
|
249
|
+
const logger = initLogger({ level: "info", useChalk: false });
|
|
250
|
+
const storyLogger = logger.withStory("user-auth-001");
|
|
251
|
+
|
|
252
|
+
const originalLog = console.log;
|
|
253
|
+
const logs: string[] = [];
|
|
254
|
+
console.log = (msg: string) => logs.push(msg);
|
|
255
|
+
|
|
256
|
+
storyLogger.info("agent.start", "Starting agent");
|
|
257
|
+
|
|
258
|
+
console.log = originalLog;
|
|
259
|
+
|
|
260
|
+
expect(logs.length).toBe(1);
|
|
261
|
+
expect(logs[0]).toContain("[user-auth-001]");
|
|
262
|
+
expect(logs[0]).toContain("Starting agent");
|
|
263
|
+
});
|
|
264
|
+
|
|
265
|
+
test("auto-injects storyId into file output", () => {
|
|
266
|
+
const logger = initLogger({ level: "info", filePath: TEST_LOG_FILE });
|
|
267
|
+
const storyLogger = logger.withStory("user-auth-001");
|
|
268
|
+
|
|
269
|
+
storyLogger.info("agent.start", "Starting agent");
|
|
270
|
+
|
|
271
|
+
const content = readFileSync(TEST_LOG_FILE, "utf8");
|
|
272
|
+
const entry = JSON.parse(content.trim());
|
|
273
|
+
|
|
274
|
+
expect(entry.storyId).toBe("user-auth-001");
|
|
275
|
+
expect(entry.message).toBe("Starting agent");
|
|
276
|
+
});
|
|
277
|
+
|
|
278
|
+
test("story logger respects level gating", () => {
|
|
279
|
+
const logger = initLogger({ level: "warn", useChalk: false });
|
|
280
|
+
const storyLogger = logger.withStory("story-123");
|
|
281
|
+
|
|
282
|
+
const originalLog = console.log;
|
|
283
|
+
const logs: string[] = [];
|
|
284
|
+
console.log = (msg: string) => logs.push(msg);
|
|
285
|
+
|
|
286
|
+
storyLogger.debug("test", "debug message");
|
|
287
|
+
storyLogger.info("test", "info message");
|
|
288
|
+
storyLogger.warn("test", "warn message");
|
|
289
|
+
|
|
290
|
+
console.log = originalLog;
|
|
291
|
+
|
|
292
|
+
// Only warn should be visible
|
|
293
|
+
expect(logs.length).toBe(1);
|
|
294
|
+
expect(logs[0]).toContain("warn message");
|
|
295
|
+
});
|
|
296
|
+
|
|
297
|
+
test("story logger writes all levels to file", () => {
|
|
298
|
+
const logger = initLogger({ level: "error", filePath: TEST_LOG_FILE });
|
|
299
|
+
const storyLogger = logger.withStory("story-123");
|
|
300
|
+
|
|
301
|
+
storyLogger.error("test", "error");
|
|
302
|
+
storyLogger.warn("test", "warn");
|
|
303
|
+
storyLogger.info("test", "info");
|
|
304
|
+
storyLogger.debug("test", "debug");
|
|
305
|
+
|
|
306
|
+
const content = readFileSync(TEST_LOG_FILE, "utf8");
|
|
307
|
+
const lines = content
|
|
308
|
+
.trim()
|
|
309
|
+
.split("\n")
|
|
310
|
+
.filter((line) => line);
|
|
311
|
+
|
|
312
|
+
expect(lines.length).toBe(4);
|
|
313
|
+
|
|
314
|
+
const entries = lines.map((line) => JSON.parse(line));
|
|
315
|
+
entries.forEach((entry) => {
|
|
316
|
+
expect(entry.storyId).toBe("story-123");
|
|
317
|
+
});
|
|
318
|
+
});
|
|
319
|
+
});
|
|
320
|
+
|
|
321
|
+
describe("console formatting", () => {
|
|
322
|
+
test("formats console output with timestamp, stage, and message", () => {
|
|
323
|
+
const logger = initLogger({ level: "info", useChalk: false });
|
|
324
|
+
|
|
325
|
+
const originalLog = console.log;
|
|
326
|
+
const logs: string[] = [];
|
|
327
|
+
console.log = (msg: string) => logs.push(msg);
|
|
328
|
+
|
|
329
|
+
logger.info("routing", "Task classified");
|
|
330
|
+
|
|
331
|
+
console.log = originalLog;
|
|
332
|
+
|
|
333
|
+
expect(logs[0]).toMatch(/\[\d{2}:\d{2}:\d{2}\]/); // timestamp
|
|
334
|
+
expect(logs[0]).toContain("[routing]"); // stage
|
|
335
|
+
expect(logs[0]).toContain("Task classified"); // message
|
|
336
|
+
});
|
|
337
|
+
|
|
338
|
+
test("formats console output with data as pretty JSON", () => {
|
|
339
|
+
const logger = initLogger({ level: "info", useChalk: false });
|
|
340
|
+
|
|
341
|
+
const originalLog = console.log;
|
|
342
|
+
const logs: string[] = [];
|
|
343
|
+
console.log = (msg: string) => logs.push(msg);
|
|
344
|
+
|
|
345
|
+
logger.info("routing", "Task classified", { complexity: "simple" });
|
|
346
|
+
|
|
347
|
+
console.log = originalLog;
|
|
348
|
+
|
|
349
|
+
expect(logs[0]).toContain("complexity");
|
|
350
|
+
expect(logs[0]).toContain("simple");
|
|
351
|
+
});
|
|
352
|
+
|
|
353
|
+
test("supports chalk formatting when enabled", () => {
|
|
354
|
+
const logger = initLogger({ level: "info", useChalk: true });
|
|
355
|
+
|
|
356
|
+
const originalLog = console.log;
|
|
357
|
+
const logs: string[] = [];
|
|
358
|
+
console.log = (msg: string) => logs.push(msg);
|
|
359
|
+
|
|
360
|
+
logger.info("routing", "Task classified");
|
|
361
|
+
|
|
362
|
+
console.log = originalLog;
|
|
363
|
+
|
|
364
|
+
// Should contain basic formatting (timestamp, stage, message)
|
|
365
|
+
expect(logs[0]).toMatch(/\[\d{2}:\d{2}:\d{2}\]/);
|
|
366
|
+
expect(logs[0]).toContain("[routing]");
|
|
367
|
+
expect(logs[0]).toContain("Task classified");
|
|
368
|
+
});
|
|
369
|
+
|
|
370
|
+
test("disables chalk formatting when useChalk is false", () => {
|
|
371
|
+
const logger = initLogger({ level: "info", useChalk: false });
|
|
372
|
+
|
|
373
|
+
const originalLog = console.log;
|
|
374
|
+
const logs: string[] = [];
|
|
375
|
+
console.log = (msg: string) => logs.push(msg);
|
|
376
|
+
|
|
377
|
+
logger.info("routing", "Task classified");
|
|
378
|
+
|
|
379
|
+
console.log = originalLog;
|
|
380
|
+
|
|
381
|
+
// Should contain basic formatting (timestamp, stage, message)
|
|
382
|
+
expect(logs[0]).toMatch(/\[\d{2}:\d{2}:\d{2}\]/);
|
|
383
|
+
expect(logs[0]).toContain("[routing]");
|
|
384
|
+
expect(logs[0]).toContain("Task classified");
|
|
385
|
+
});
|
|
386
|
+
});
|
|
387
|
+
|
|
388
|
+
describe("data handling", () => {
|
|
389
|
+
test("logs complex data structures", () => {
|
|
390
|
+
const logger = initLogger({ level: "info", filePath: TEST_LOG_FILE });
|
|
391
|
+
|
|
392
|
+
const complexData = {
|
|
393
|
+
nested: {
|
|
394
|
+
array: [1, 2, 3],
|
|
395
|
+
object: { key: "value" },
|
|
396
|
+
},
|
|
397
|
+
null: null,
|
|
398
|
+
undefined: undefined,
|
|
399
|
+
number: 42,
|
|
400
|
+
boolean: true,
|
|
401
|
+
};
|
|
402
|
+
|
|
403
|
+
logger.info("test", "Complex data", complexData);
|
|
404
|
+
|
|
405
|
+
const content = readFileSync(TEST_LOG_FILE, "utf8");
|
|
406
|
+
const entry = JSON.parse(content.trim());
|
|
407
|
+
|
|
408
|
+
expect(entry.data.nested.array).toEqual([1, 2, 3]);
|
|
409
|
+
expect(entry.data.nested.object).toEqual({ key: "value" });
|
|
410
|
+
expect(entry.data.null).toBe(null);
|
|
411
|
+
expect(entry.data.number).toBe(42);
|
|
412
|
+
expect(entry.data.boolean).toBe(true);
|
|
413
|
+
});
|
|
414
|
+
|
|
415
|
+
test("handles empty data object", () => {
|
|
416
|
+
const logger = initLogger({ level: "info", filePath: TEST_LOG_FILE });
|
|
417
|
+
|
|
418
|
+
logger.info("test", "Empty data", {});
|
|
419
|
+
|
|
420
|
+
const content = readFileSync(TEST_LOG_FILE, "utf8");
|
|
421
|
+
const entry = JSON.parse(content.trim());
|
|
422
|
+
|
|
423
|
+
// Empty object should be included but not displayed in console
|
|
424
|
+
expect(entry.data).toEqual({});
|
|
425
|
+
});
|
|
426
|
+
});
|
|
427
|
+
|
|
428
|
+
describe("error handling", () => {
|
|
429
|
+
// Skip in CI: writing to /invalid/path/ may not trigger EACCES in all CI environments
|
|
430
|
+
// (some runners run as root and CAN write to /invalid/path). The error-handling logic
|
|
431
|
+
// is tested sufficiently by other unit tests for the Logger class itself.
|
|
432
|
+
const skipInCI = process.env.CI ? test.skip : test;
|
|
433
|
+
skipInCI("handles file write errors gracefully", () => {
|
|
434
|
+
// Create logger with invalid path
|
|
435
|
+
const originalError = console.error;
|
|
436
|
+
const errors: string[] = [];
|
|
437
|
+
console.error = (msg: string) => errors.push(msg);
|
|
438
|
+
|
|
439
|
+
const logger = initLogger({
|
|
440
|
+
level: "info",
|
|
441
|
+
filePath: "/invalid/path/test.jsonl",
|
|
442
|
+
});
|
|
443
|
+
|
|
444
|
+
logger.info("test", "message");
|
|
445
|
+
|
|
446
|
+
console.error = originalError;
|
|
447
|
+
|
|
448
|
+
// Should log error to console but not crash
|
|
449
|
+
expect(errors.some((e) => e.includes("Failed to write to log file"))).toBe(true);
|
|
450
|
+
});
|
|
451
|
+
});
|
|
452
|
+
|
|
453
|
+
describe("close", () => {
|
|
454
|
+
test("close method exists and can be called", () => {
|
|
455
|
+
const logger = initLogger({ level: "info" });
|
|
456
|
+
|
|
457
|
+
// Should not throw
|
|
458
|
+
expect(() => logger.close()).not.toThrow();
|
|
459
|
+
});
|
|
460
|
+
});
|
|
461
|
+
});
|
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Parallel Execution Tests
|
|
3
|
+
*
|
|
4
|
+
* Tests for parallel story execution with worktrees:
|
|
5
|
+
* - Dependency-based batching
|
|
6
|
+
* - Concurrent execution
|
|
7
|
+
* - Merge ordering
|
|
8
|
+
* - Cleanup logic
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { describe, expect, test } from "bun:test";
|
|
12
|
+
import type { UserStory } from "../../src/prd/types";
|
|
13
|
+
|
|
14
|
+
describe("Parallel Execution", () => {
|
|
15
|
+
describe("Story Grouping", () => {
|
|
16
|
+
test("groups independent stories into single batch", () => {
|
|
17
|
+
const stories: UserStory[] = [
|
|
18
|
+
{
|
|
19
|
+
id: "US-001",
|
|
20
|
+
title: "Story 1",
|
|
21
|
+
description: "Independent story 1",
|
|
22
|
+
acceptanceCriteria: ["AC1"],
|
|
23
|
+
tags: [],
|
|
24
|
+
dependencies: [],
|
|
25
|
+
status: "pending",
|
|
26
|
+
passes: false,
|
|
27
|
+
escalations: [],
|
|
28
|
+
attempts: 0,
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
id: "US-002",
|
|
32
|
+
title: "Story 2",
|
|
33
|
+
description: "Independent story 2",
|
|
34
|
+
acceptanceCriteria: ["AC2"],
|
|
35
|
+
tags: [],
|
|
36
|
+
dependencies: [],
|
|
37
|
+
status: "pending",
|
|
38
|
+
passes: false,
|
|
39
|
+
escalations: [],
|
|
40
|
+
attempts: 0,
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
id: "US-003",
|
|
44
|
+
title: "Story 3",
|
|
45
|
+
description: "Independent story 3",
|
|
46
|
+
acceptanceCriteria: ["AC3"],
|
|
47
|
+
tags: [],
|
|
48
|
+
dependencies: [],
|
|
49
|
+
status: "pending",
|
|
50
|
+
passes: false,
|
|
51
|
+
escalations: [],
|
|
52
|
+
attempts: 0,
|
|
53
|
+
},
|
|
54
|
+
];
|
|
55
|
+
|
|
56
|
+
// All stories are independent, should be in one batch
|
|
57
|
+
// This test validates the grouping logic conceptually
|
|
58
|
+
expect(stories.every((s) => s.dependencies.length === 0)).toBe(true);
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
test("separates dependent stories into ordered batches", () => {
|
|
62
|
+
const stories: UserStory[] = [
|
|
63
|
+
{
|
|
64
|
+
id: "US-001",
|
|
65
|
+
title: "Base story",
|
|
66
|
+
description: "No dependencies",
|
|
67
|
+
acceptanceCriteria: ["AC1"],
|
|
68
|
+
tags: [],
|
|
69
|
+
dependencies: [],
|
|
70
|
+
status: "pending",
|
|
71
|
+
passes: false,
|
|
72
|
+
escalations: [],
|
|
73
|
+
attempts: 0,
|
|
74
|
+
},
|
|
75
|
+
{
|
|
76
|
+
id: "US-002",
|
|
77
|
+
title: "Dependent story",
|
|
78
|
+
description: "Depends on US-001",
|
|
79
|
+
acceptanceCriteria: ["AC2"],
|
|
80
|
+
tags: [],
|
|
81
|
+
dependencies: ["US-001"],
|
|
82
|
+
status: "pending",
|
|
83
|
+
passes: false,
|
|
84
|
+
escalations: [],
|
|
85
|
+
attempts: 0,
|
|
86
|
+
},
|
|
87
|
+
{
|
|
88
|
+
id: "US-003",
|
|
89
|
+
title: "Double dependent",
|
|
90
|
+
description: "Depends on US-002",
|
|
91
|
+
acceptanceCriteria: ["AC3"],
|
|
92
|
+
tags: [],
|
|
93
|
+
dependencies: ["US-002"],
|
|
94
|
+
status: "pending",
|
|
95
|
+
passes: false,
|
|
96
|
+
escalations: [],
|
|
97
|
+
attempts: 0,
|
|
98
|
+
},
|
|
99
|
+
];
|
|
100
|
+
|
|
101
|
+
// US-001 has no deps (batch 1)
|
|
102
|
+
expect(stories[0].dependencies).toEqual([]);
|
|
103
|
+
// US-002 depends on US-001 (batch 2)
|
|
104
|
+
expect(stories[1].dependencies).toEqual(["US-001"]);
|
|
105
|
+
// US-003 depends on US-002 (batch 3)
|
|
106
|
+
expect(stories[2].dependencies).toEqual(["US-002"]);
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
test("handles mixed dependencies correctly", () => {
|
|
110
|
+
const stories: UserStory[] = [
|
|
111
|
+
{
|
|
112
|
+
id: "US-001",
|
|
113
|
+
title: "Independent A",
|
|
114
|
+
description: "No deps",
|
|
115
|
+
acceptanceCriteria: ["AC1"],
|
|
116
|
+
tags: [],
|
|
117
|
+
dependencies: [],
|
|
118
|
+
status: "pending",
|
|
119
|
+
passes: false,
|
|
120
|
+
escalations: [],
|
|
121
|
+
attempts: 0,
|
|
122
|
+
},
|
|
123
|
+
{
|
|
124
|
+
id: "US-002",
|
|
125
|
+
title: "Independent B",
|
|
126
|
+
description: "No deps",
|
|
127
|
+
acceptanceCriteria: ["AC2"],
|
|
128
|
+
tags: [],
|
|
129
|
+
dependencies: [],
|
|
130
|
+
status: "pending",
|
|
131
|
+
passes: false,
|
|
132
|
+
escalations: [],
|
|
133
|
+
attempts: 0,
|
|
134
|
+
},
|
|
135
|
+
{
|
|
136
|
+
id: "US-003",
|
|
137
|
+
title: "Dependent on A",
|
|
138
|
+
description: "Depends on US-001",
|
|
139
|
+
acceptanceCriteria: ["AC3"],
|
|
140
|
+
tags: [],
|
|
141
|
+
dependencies: ["US-001"],
|
|
142
|
+
status: "pending",
|
|
143
|
+
passes: false,
|
|
144
|
+
escalations: [],
|
|
145
|
+
attempts: 0,
|
|
146
|
+
},
|
|
147
|
+
{
|
|
148
|
+
id: "US-004",
|
|
149
|
+
title: "Dependent on B",
|
|
150
|
+
description: "Depends on US-002",
|
|
151
|
+
acceptanceCriteria: ["AC4"],
|
|
152
|
+
tags: [],
|
|
153
|
+
dependencies: ["US-002"],
|
|
154
|
+
status: "pending",
|
|
155
|
+
passes: false,
|
|
156
|
+
escalations: [],
|
|
157
|
+
attempts: 0,
|
|
158
|
+
},
|
|
159
|
+
];
|
|
160
|
+
|
|
161
|
+
// US-001 and US-002 are independent (batch 1)
|
|
162
|
+
expect(stories[0].dependencies).toEqual([]);
|
|
163
|
+
expect(stories[1].dependencies).toEqual([]);
|
|
164
|
+
// US-003 and US-004 depend on batch 1 stories (batch 2, can run in parallel)
|
|
165
|
+
expect(stories[2].dependencies).toEqual(["US-001"]);
|
|
166
|
+
expect(stories[3].dependencies).toEqual(["US-002"]);
|
|
167
|
+
});
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
describe("Concurrency Control", () => {
|
|
171
|
+
test("auto-detects concurrency from CPU count when parallel=0", () => {
|
|
172
|
+
const parallel = 0;
|
|
173
|
+
const cpuCount = require("os").cpus().length;
|
|
174
|
+
|
|
175
|
+
const maxConcurrency = parallel === 0 ? cpuCount : parallel;
|
|
176
|
+
expect(maxConcurrency).toBe(cpuCount);
|
|
177
|
+
expect(maxConcurrency).toBeGreaterThan(0);
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
test("uses explicit concurrency when parallel > 0", () => {
|
|
181
|
+
const parallel = 4;
|
|
182
|
+
const maxConcurrency = Math.max(1, parallel);
|
|
183
|
+
|
|
184
|
+
expect(maxConcurrency).toBe(4);
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
test("enforces minimum concurrency of 1", () => {
|
|
188
|
+
const parallel = -5;
|
|
189
|
+
const maxConcurrency = Math.max(1, parallel);
|
|
190
|
+
|
|
191
|
+
expect(maxConcurrency).toBe(1);
|
|
192
|
+
});
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
describe("Worktree Path Tracking", () => {
|
|
196
|
+
test("stores worktree path in story", () => {
|
|
197
|
+
const story: UserStory = {
|
|
198
|
+
id: "US-001",
|
|
199
|
+
title: "Test story",
|
|
200
|
+
description: "Test",
|
|
201
|
+
acceptanceCriteria: ["AC1"],
|
|
202
|
+
tags: [],
|
|
203
|
+
dependencies: [],
|
|
204
|
+
status: "pending",
|
|
205
|
+
passes: false,
|
|
206
|
+
escalations: [],
|
|
207
|
+
attempts: 0,
|
|
208
|
+
worktreePath: "/project/.nax-wt/US-001",
|
|
209
|
+
};
|
|
210
|
+
|
|
211
|
+
expect(story.worktreePath).toBe("/project/.nax-wt/US-001");
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
test("worktreePath is optional", () => {
|
|
215
|
+
const story: UserStory = {
|
|
216
|
+
id: "US-001",
|
|
217
|
+
title: "Test story",
|
|
218
|
+
description: "Test",
|
|
219
|
+
acceptanceCriteria: ["AC1"],
|
|
220
|
+
tags: [],
|
|
221
|
+
dependencies: [],
|
|
222
|
+
status: "pending",
|
|
223
|
+
passes: false,
|
|
224
|
+
escalations: [],
|
|
225
|
+
attempts: 0,
|
|
226
|
+
};
|
|
227
|
+
|
|
228
|
+
expect(story.worktreePath).toBeUndefined();
|
|
229
|
+
});
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
describe("Status File Parallel Info", () => {
|
|
233
|
+
test("includes parallel execution status", () => {
|
|
234
|
+
const parallelInfo = {
|
|
235
|
+
enabled: true,
|
|
236
|
+
maxConcurrency: 4,
|
|
237
|
+
activeStories: [
|
|
238
|
+
{ storyId: "US-001", worktreePath: "/project/.nax-wt/US-001" },
|
|
239
|
+
{ storyId: "US-002", worktreePath: "/project/.nax-wt/US-002" },
|
|
240
|
+
],
|
|
241
|
+
};
|
|
242
|
+
|
|
243
|
+
expect(parallelInfo.enabled).toBe(true);
|
|
244
|
+
expect(parallelInfo.maxConcurrency).toBe(4);
|
|
245
|
+
expect(parallelInfo.activeStories).toHaveLength(2);
|
|
246
|
+
expect(parallelInfo.activeStories[0].storyId).toBe("US-001");
|
|
247
|
+
expect(parallelInfo.activeStories[0].worktreePath).toBe("/project/.nax-wt/US-001");
|
|
248
|
+
});
|
|
249
|
+
});
|
|
250
|
+
});
|