@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,469 @@
|
|
|
1
|
+
import { describe, expect, test } from "bun:test";
|
|
2
|
+
import { formatConsole, formatJsonl } from "../../src/logger/formatters.js";
|
|
3
|
+
import type { LogEntry } from "../../src/logger/types.js";
|
|
4
|
+
|
|
5
|
+
describe("formatConsole", () => {
|
|
6
|
+
test("formats basic log entry with timestamp, stage, and message", () => {
|
|
7
|
+
const entry: LogEntry = {
|
|
8
|
+
timestamp: "2026-02-20T10:30:00.123Z",
|
|
9
|
+
level: "info",
|
|
10
|
+
stage: "routing",
|
|
11
|
+
message: "Task classified",
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
const output = formatConsole(entry);
|
|
15
|
+
|
|
16
|
+
// Should contain timestamp in HH:MM:SS format
|
|
17
|
+
expect(output).toMatch(/\[\d{2}:\d{2}:\d{2}\]/);
|
|
18
|
+
expect(output).toContain("[routing]");
|
|
19
|
+
expect(output).toContain("Task classified");
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
test("includes storyId when present", () => {
|
|
23
|
+
const entry: LogEntry = {
|
|
24
|
+
timestamp: "2026-02-20T10:30:00.123Z",
|
|
25
|
+
level: "info",
|
|
26
|
+
stage: "agent.start",
|
|
27
|
+
storyId: "user-auth-001",
|
|
28
|
+
message: "Starting agent session",
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
const output = formatConsole(entry);
|
|
32
|
+
|
|
33
|
+
expect(output).toContain("[user-auth-001]");
|
|
34
|
+
expect(output).toContain("Starting agent session");
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
test("omits storyId when not present", () => {
|
|
38
|
+
const entry: LogEntry = {
|
|
39
|
+
timestamp: "2026-02-20T10:30:00.123Z",
|
|
40
|
+
level: "info",
|
|
41
|
+
stage: "routing",
|
|
42
|
+
message: "Task classified",
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
const output = formatConsole(entry);
|
|
46
|
+
|
|
47
|
+
// Should not contain brackets around storyId
|
|
48
|
+
const bracketCount = (output.match(/\[/g) || []).length;
|
|
49
|
+
expect(bracketCount).toBe(2); // Only timestamp and stage
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
test("formats data as pretty JSON on new line", () => {
|
|
53
|
+
const entry: LogEntry = {
|
|
54
|
+
timestamp: "2026-02-20T10:30:00.123Z",
|
|
55
|
+
level: "info",
|
|
56
|
+
stage: "routing",
|
|
57
|
+
message: "Task classified",
|
|
58
|
+
data: {
|
|
59
|
+
complexity: "simple",
|
|
60
|
+
model: "claude-sonnet-4-5",
|
|
61
|
+
},
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
const output = formatConsole(entry);
|
|
65
|
+
|
|
66
|
+
expect(output).toContain("complexity");
|
|
67
|
+
expect(output).toContain("simple");
|
|
68
|
+
expect(output).toContain("model");
|
|
69
|
+
expect(output).toContain("claude-sonnet-4-5");
|
|
70
|
+
// Data should be on separate line
|
|
71
|
+
expect(output).toContain("\n");
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
test("omits data section when data is undefined", () => {
|
|
75
|
+
const entry: LogEntry = {
|
|
76
|
+
timestamp: "2026-02-20T10:30:00.123Z",
|
|
77
|
+
level: "info",
|
|
78
|
+
stage: "routing",
|
|
79
|
+
message: "Task classified",
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
const output = formatConsole(entry);
|
|
83
|
+
|
|
84
|
+
expect(output).not.toContain("complexity");
|
|
85
|
+
expect(output.split("\n").length).toBe(1); // Single line
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
test("omits data section when data is empty object", () => {
|
|
89
|
+
const entry: LogEntry = {
|
|
90
|
+
timestamp: "2026-02-20T10:30:00.123Z",
|
|
91
|
+
level: "info",
|
|
92
|
+
stage: "routing",
|
|
93
|
+
message: "Task classified",
|
|
94
|
+
data: {},
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
const output = formatConsole(entry);
|
|
98
|
+
|
|
99
|
+
expect(output.split("\n").length).toBe(1); // Single line
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
test("applies formatting for error level", () => {
|
|
103
|
+
const entry: LogEntry = {
|
|
104
|
+
timestamp: "2026-02-20T10:30:00.123Z",
|
|
105
|
+
level: "error",
|
|
106
|
+
stage: "agent.error",
|
|
107
|
+
message: "Agent failed",
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
const output = formatConsole(entry);
|
|
111
|
+
|
|
112
|
+
// Should contain all required components
|
|
113
|
+
expect(output).toContain("[agent.error]");
|
|
114
|
+
expect(output).toContain("Agent failed");
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
test("applies formatting for warn level", () => {
|
|
118
|
+
const entry: LogEntry = {
|
|
119
|
+
timestamp: "2026-02-20T10:30:00.123Z",
|
|
120
|
+
level: "warn",
|
|
121
|
+
stage: "verification",
|
|
122
|
+
message: "Tests failed",
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
const output = formatConsole(entry);
|
|
126
|
+
|
|
127
|
+
// Should contain all required components
|
|
128
|
+
expect(output).toContain("[verification]");
|
|
129
|
+
expect(output).toContain("Tests failed");
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
test("applies formatting for info level", () => {
|
|
133
|
+
const entry: LogEntry = {
|
|
134
|
+
timestamp: "2026-02-20T10:30:00.123Z",
|
|
135
|
+
level: "info",
|
|
136
|
+
stage: "routing",
|
|
137
|
+
message: "Task classified",
|
|
138
|
+
};
|
|
139
|
+
|
|
140
|
+
const output = formatConsole(entry);
|
|
141
|
+
|
|
142
|
+
// Should contain all required components
|
|
143
|
+
expect(output).toContain("[routing]");
|
|
144
|
+
expect(output).toContain("Task classified");
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
test("applies formatting for debug level", () => {
|
|
148
|
+
const entry: LogEntry = {
|
|
149
|
+
timestamp: "2026-02-20T10:30:00.123Z",
|
|
150
|
+
level: "debug",
|
|
151
|
+
stage: "context",
|
|
152
|
+
message: "Building context",
|
|
153
|
+
};
|
|
154
|
+
|
|
155
|
+
const output = formatConsole(entry);
|
|
156
|
+
|
|
157
|
+
// Should contain all required components
|
|
158
|
+
expect(output).toContain("[context]");
|
|
159
|
+
expect(output).toContain("Building context");
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
test("formats complex nested data structures", () => {
|
|
163
|
+
const entry: LogEntry = {
|
|
164
|
+
timestamp: "2026-02-20T10:30:00.123Z",
|
|
165
|
+
level: "info",
|
|
166
|
+
stage: "routing",
|
|
167
|
+
message: "Complex data",
|
|
168
|
+
data: {
|
|
169
|
+
nested: {
|
|
170
|
+
array: [1, 2, 3],
|
|
171
|
+
object: { key: "value" },
|
|
172
|
+
},
|
|
173
|
+
number: 42,
|
|
174
|
+
boolean: true,
|
|
175
|
+
null: null,
|
|
176
|
+
},
|
|
177
|
+
};
|
|
178
|
+
|
|
179
|
+
const output = formatConsole(entry);
|
|
180
|
+
|
|
181
|
+
expect(output).toContain("nested");
|
|
182
|
+
expect(output).toContain("array");
|
|
183
|
+
expect(output).toContain("object");
|
|
184
|
+
expect(output).toContain("number");
|
|
185
|
+
expect(output).toContain("42");
|
|
186
|
+
expect(output).toContain("true");
|
|
187
|
+
expect(output).toContain("null");
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
test("handles timestamps in different formats", () => {
|
|
191
|
+
const entry: LogEntry = {
|
|
192
|
+
timestamp: "2026-02-20T23:59:59.999Z",
|
|
193
|
+
level: "info",
|
|
194
|
+
stage: "test",
|
|
195
|
+
message: "Late night message",
|
|
196
|
+
};
|
|
197
|
+
|
|
198
|
+
const output = formatConsole(entry);
|
|
199
|
+
|
|
200
|
+
// Should format timestamp correctly (depends on local timezone)
|
|
201
|
+
expect(output).toMatch(/\[\d{2}:\d{2}:\d{2}\]/);
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
test("formats complete log entry with all fields", () => {
|
|
205
|
+
const entry: LogEntry = {
|
|
206
|
+
timestamp: "2026-02-20T10:30:00.123Z",
|
|
207
|
+
level: "info",
|
|
208
|
+
stage: "agent.complete",
|
|
209
|
+
storyId: "user-auth-001",
|
|
210
|
+
message: "Agent completed successfully",
|
|
211
|
+
data: {
|
|
212
|
+
duration: 45.2,
|
|
213
|
+
cost: 0.12,
|
|
214
|
+
model: "claude-sonnet-4-5",
|
|
215
|
+
},
|
|
216
|
+
};
|
|
217
|
+
|
|
218
|
+
const output = formatConsole(entry);
|
|
219
|
+
|
|
220
|
+
expect(output).toMatch(/\[\d{2}:\d{2}:\d{2}\]/);
|
|
221
|
+
expect(output).toContain("[agent.complete]");
|
|
222
|
+
expect(output).toContain("[user-auth-001]");
|
|
223
|
+
expect(output).toContain("Agent completed successfully");
|
|
224
|
+
expect(output).toContain("duration");
|
|
225
|
+
expect(output).toContain("45.2");
|
|
226
|
+
expect(output).toContain("cost");
|
|
227
|
+
expect(output).toContain("0.12");
|
|
228
|
+
expect(output).toContain("claude-sonnet-4-5");
|
|
229
|
+
});
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
describe("formatJsonl", () => {
|
|
233
|
+
test("formats basic log entry as single-line JSON", () => {
|
|
234
|
+
const entry: LogEntry = {
|
|
235
|
+
timestamp: "2026-02-20T10:30:00.123Z",
|
|
236
|
+
level: "info",
|
|
237
|
+
stage: "routing",
|
|
238
|
+
message: "Task classified",
|
|
239
|
+
};
|
|
240
|
+
|
|
241
|
+
const output = formatJsonl(entry);
|
|
242
|
+
|
|
243
|
+
// Should be single line
|
|
244
|
+
expect(output).not.toContain("\n");
|
|
245
|
+
|
|
246
|
+
// Should be valid JSON
|
|
247
|
+
const parsed = JSON.parse(output);
|
|
248
|
+
expect(parsed).toEqual(entry);
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
test("includes all fields when present", () => {
|
|
252
|
+
const entry: LogEntry = {
|
|
253
|
+
timestamp: "2026-02-20T10:30:00.123Z",
|
|
254
|
+
level: "info",
|
|
255
|
+
stage: "agent.start",
|
|
256
|
+
storyId: "user-auth-001",
|
|
257
|
+
message: "Starting agent",
|
|
258
|
+
data: { model: "claude-sonnet-4-5" },
|
|
259
|
+
};
|
|
260
|
+
|
|
261
|
+
const output = formatJsonl(entry);
|
|
262
|
+
|
|
263
|
+
const parsed = JSON.parse(output);
|
|
264
|
+
expect(parsed.timestamp).toBe("2026-02-20T10:30:00.123Z");
|
|
265
|
+
expect(parsed.level).toBe("info");
|
|
266
|
+
expect(parsed.stage).toBe("agent.start");
|
|
267
|
+
expect(parsed.storyId).toBe("user-auth-001");
|
|
268
|
+
expect(parsed.message).toBe("Starting agent");
|
|
269
|
+
expect(parsed.data).toEqual({ model: "claude-sonnet-4-5" });
|
|
270
|
+
});
|
|
271
|
+
|
|
272
|
+
test("omits optional fields when not present", () => {
|
|
273
|
+
const entry: LogEntry = {
|
|
274
|
+
timestamp: "2026-02-20T10:30:00.123Z",
|
|
275
|
+
level: "info",
|
|
276
|
+
stage: "routing",
|
|
277
|
+
message: "Task classified",
|
|
278
|
+
};
|
|
279
|
+
|
|
280
|
+
const output = formatJsonl(entry);
|
|
281
|
+
|
|
282
|
+
const parsed = JSON.parse(output);
|
|
283
|
+
expect(parsed.timestamp).toBe("2026-02-20T10:30:00.123Z");
|
|
284
|
+
expect(parsed.level).toBe("info");
|
|
285
|
+
expect(parsed.stage).toBe("routing");
|
|
286
|
+
expect(parsed.message).toBe("Task classified");
|
|
287
|
+
expect(parsed.storyId).toBeUndefined();
|
|
288
|
+
expect(parsed.data).toBeUndefined();
|
|
289
|
+
});
|
|
290
|
+
|
|
291
|
+
test("preserves complex data structures", () => {
|
|
292
|
+
const entry: LogEntry = {
|
|
293
|
+
timestamp: "2026-02-20T10:30:00.123Z",
|
|
294
|
+
level: "info",
|
|
295
|
+
stage: "test",
|
|
296
|
+
message: "Complex data",
|
|
297
|
+
data: {
|
|
298
|
+
nested: {
|
|
299
|
+
array: [1, 2, 3],
|
|
300
|
+
object: { key: "value" },
|
|
301
|
+
},
|
|
302
|
+
number: 42,
|
|
303
|
+
boolean: true,
|
|
304
|
+
null: null,
|
|
305
|
+
},
|
|
306
|
+
};
|
|
307
|
+
|
|
308
|
+
const output = formatJsonl(entry);
|
|
309
|
+
|
|
310
|
+
const parsed = JSON.parse(output);
|
|
311
|
+
expect(parsed.data).toEqual(entry.data);
|
|
312
|
+
expect(parsed.data.nested.array).toEqual([1, 2, 3]);
|
|
313
|
+
expect(parsed.data.nested.object).toEqual({ key: "value" });
|
|
314
|
+
expect(parsed.data.number).toBe(42);
|
|
315
|
+
expect(parsed.data.boolean).toBe(true);
|
|
316
|
+
expect(parsed.data.null).toBe(null);
|
|
317
|
+
});
|
|
318
|
+
|
|
319
|
+
test("handles all log levels", () => {
|
|
320
|
+
const levels: Array<"error" | "warn" | "info" | "debug"> = ["error", "warn", "info", "debug"];
|
|
321
|
+
|
|
322
|
+
for (const level of levels) {
|
|
323
|
+
const entry: LogEntry = {
|
|
324
|
+
timestamp: "2026-02-20T10:30:00.123Z",
|
|
325
|
+
level,
|
|
326
|
+
stage: "test",
|
|
327
|
+
message: `${level} message`,
|
|
328
|
+
};
|
|
329
|
+
|
|
330
|
+
const output = formatJsonl(entry);
|
|
331
|
+
const parsed = JSON.parse(output);
|
|
332
|
+
|
|
333
|
+
expect(parsed.level).toBe(level);
|
|
334
|
+
}
|
|
335
|
+
});
|
|
336
|
+
|
|
337
|
+
test("escapes special characters in strings", () => {
|
|
338
|
+
const entry: LogEntry = {
|
|
339
|
+
timestamp: "2026-02-20T10:30:00.123Z",
|
|
340
|
+
level: "info",
|
|
341
|
+
stage: "test",
|
|
342
|
+
message: 'Message with "quotes" and \n newlines',
|
|
343
|
+
data: {
|
|
344
|
+
text: "Special chars: \t\r\n\"'\\",
|
|
345
|
+
},
|
|
346
|
+
};
|
|
347
|
+
|
|
348
|
+
const output = formatJsonl(entry);
|
|
349
|
+
|
|
350
|
+
// Should be valid JSON despite special characters
|
|
351
|
+
const parsed = JSON.parse(output);
|
|
352
|
+
expect(parsed.message).toBe('Message with "quotes" and \n newlines');
|
|
353
|
+
expect(parsed.data.text).toBe("Special chars: \t\r\n\"'\\");
|
|
354
|
+
});
|
|
355
|
+
|
|
356
|
+
test("handles empty data object", () => {
|
|
357
|
+
const entry: LogEntry = {
|
|
358
|
+
timestamp: "2026-02-20T10:30:00.123Z",
|
|
359
|
+
level: "info",
|
|
360
|
+
stage: "test",
|
|
361
|
+
message: "Empty data",
|
|
362
|
+
data: {},
|
|
363
|
+
};
|
|
364
|
+
|
|
365
|
+
const output = formatJsonl(entry);
|
|
366
|
+
|
|
367
|
+
const parsed = JSON.parse(output);
|
|
368
|
+
expect(parsed.data).toEqual({});
|
|
369
|
+
});
|
|
370
|
+
|
|
371
|
+
test("produces consistent output for same input", () => {
|
|
372
|
+
const entry: LogEntry = {
|
|
373
|
+
timestamp: "2026-02-20T10:30:00.123Z",
|
|
374
|
+
level: "info",
|
|
375
|
+
stage: "routing",
|
|
376
|
+
message: "Task classified",
|
|
377
|
+
data: { complexity: "simple" },
|
|
378
|
+
};
|
|
379
|
+
|
|
380
|
+
const output1 = formatJsonl(entry);
|
|
381
|
+
const output2 = formatJsonl(entry);
|
|
382
|
+
|
|
383
|
+
expect(output1).toBe(output2);
|
|
384
|
+
});
|
|
385
|
+
|
|
386
|
+
test("formats complete log entry with all fields", () => {
|
|
387
|
+
const entry: LogEntry = {
|
|
388
|
+
timestamp: "2026-02-20T10:30:00.123Z",
|
|
389
|
+
level: "debug",
|
|
390
|
+
stage: "context.built",
|
|
391
|
+
storyId: "user-auth-001",
|
|
392
|
+
message: "Context built successfully",
|
|
393
|
+
data: {
|
|
394
|
+
fileCount: 12,
|
|
395
|
+
totalLines: 1234,
|
|
396
|
+
relevantModules: ["auth", "user", "session"],
|
|
397
|
+
},
|
|
398
|
+
};
|
|
399
|
+
|
|
400
|
+
const output = formatJsonl(entry);
|
|
401
|
+
|
|
402
|
+
// Verify it's valid JSON
|
|
403
|
+
expect(() => JSON.parse(output)).not.toThrow();
|
|
404
|
+
|
|
405
|
+
// Verify all fields preserved
|
|
406
|
+
const parsed = JSON.parse(output);
|
|
407
|
+
expect(parsed).toEqual(entry);
|
|
408
|
+
});
|
|
409
|
+
|
|
410
|
+
test("handles unicode characters correctly", () => {
|
|
411
|
+
const entry: LogEntry = {
|
|
412
|
+
timestamp: "2026-02-20T10:30:00.123Z",
|
|
413
|
+
level: "info",
|
|
414
|
+
stage: "test",
|
|
415
|
+
message: "Unicode: 你好 🚀 émojis",
|
|
416
|
+
data: {
|
|
417
|
+
emoji: "✅",
|
|
418
|
+
chinese: "测试",
|
|
419
|
+
accent: "café",
|
|
420
|
+
},
|
|
421
|
+
};
|
|
422
|
+
|
|
423
|
+
const output = formatJsonl(entry);
|
|
424
|
+
|
|
425
|
+
const parsed = JSON.parse(output);
|
|
426
|
+
expect(parsed.message).toBe("Unicode: 你好 🚀 émojis");
|
|
427
|
+
expect(parsed.data.emoji).toBe("✅");
|
|
428
|
+
expect(parsed.data.chinese).toBe("测试");
|
|
429
|
+
expect(parsed.data.accent).toBe("café");
|
|
430
|
+
});
|
|
431
|
+
|
|
432
|
+
test("multiple JSONL lines are independently parseable", () => {
|
|
433
|
+
const entries: LogEntry[] = [
|
|
434
|
+
{
|
|
435
|
+
timestamp: "2026-02-20T10:30:00.123Z",
|
|
436
|
+
level: "info",
|
|
437
|
+
stage: "routing",
|
|
438
|
+
message: "First entry",
|
|
439
|
+
},
|
|
440
|
+
{
|
|
441
|
+
timestamp: "2026-02-20T10:30:01.456Z",
|
|
442
|
+
level: "debug",
|
|
443
|
+
stage: "context",
|
|
444
|
+
message: "Second entry",
|
|
445
|
+
data: { test: true },
|
|
446
|
+
},
|
|
447
|
+
{
|
|
448
|
+
timestamp: "2026-02-20T10:30:02.789Z",
|
|
449
|
+
level: "error",
|
|
450
|
+
stage: "agent.error",
|
|
451
|
+
storyId: "story-123",
|
|
452
|
+
message: "Third entry",
|
|
453
|
+
},
|
|
454
|
+
];
|
|
455
|
+
|
|
456
|
+
const lines = entries.map((entry) => formatJsonl(entry));
|
|
457
|
+
|
|
458
|
+
// Each line should be independently parseable
|
|
459
|
+
lines.forEach((line, index) => {
|
|
460
|
+
const parsed = JSON.parse(line);
|
|
461
|
+
expect(parsed).toEqual(entries[index]);
|
|
462
|
+
});
|
|
463
|
+
|
|
464
|
+
// Concatenated lines should be parseable as JSONL
|
|
465
|
+
const jsonlContent = lines.join("\n");
|
|
466
|
+
const parsedLines = jsonlContent.split("\n").map((line) => JSON.parse(line));
|
|
467
|
+
expect(parsedLines).toEqual(entries);
|
|
468
|
+
});
|
|
469
|
+
});
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for src/context/greenfield.ts
|
|
3
|
+
*
|
|
4
|
+
* Covers: isGreenfieldStory
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { afterEach, beforeEach, describe, expect, it } from "bun:test";
|
|
8
|
+
import { mkdtemp, rm } from "node:fs/promises";
|
|
9
|
+
import { tmpdir } from "node:os";
|
|
10
|
+
import { join } from "node:path";
|
|
11
|
+
import { isGreenfieldStory } from "../../src/context/greenfield";
|
|
12
|
+
import type { UserStory } from "../../src/prd/types";
|
|
13
|
+
|
|
14
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
15
|
+
// Test Helpers
|
|
16
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
17
|
+
|
|
18
|
+
const createMockStory = (id = "US-001"): UserStory => ({
|
|
19
|
+
id,
|
|
20
|
+
title: "Test story",
|
|
21
|
+
description: "Test description",
|
|
22
|
+
acceptanceCriteria: [],
|
|
23
|
+
tags: [],
|
|
24
|
+
dependencies: [],
|
|
25
|
+
status: "pending",
|
|
26
|
+
passes: false,
|
|
27
|
+
escalations: [],
|
|
28
|
+
attempts: 0,
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
async function createTestFile(workdir: string, filepath: string, content = ""): Promise<void> {
|
|
32
|
+
const fullPath = join(workdir, filepath);
|
|
33
|
+
const dir = fullPath.substring(0, fullPath.lastIndexOf("/"));
|
|
34
|
+
await Bun.write(fullPath, content);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
38
|
+
// isGreenfieldStory
|
|
39
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
40
|
+
|
|
41
|
+
describe("isGreenfieldStory", () => {
|
|
42
|
+
let workdir: string;
|
|
43
|
+
|
|
44
|
+
beforeEach(async () => {
|
|
45
|
+
workdir = await mkdtemp(join(tmpdir(), "nax-greenfield-test-"));
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
afterEach(async () => {
|
|
49
|
+
await rm(workdir, { recursive: true, force: true });
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
it("returns true when no test files exist", async () => {
|
|
53
|
+
const story = createMockStory();
|
|
54
|
+
const result = await isGreenfieldStory(story, workdir);
|
|
55
|
+
expect(result).toBe(true);
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
it("returns false when .test.ts files exist", async () => {
|
|
59
|
+
await createTestFile(workdir, "src/foo.test.ts", "test('foo', () => {})");
|
|
60
|
+
const story = createMockStory();
|
|
61
|
+
const result = await isGreenfieldStory(story, workdir);
|
|
62
|
+
expect(result).toBe(false);
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
it("returns false when .spec.ts files exist", async () => {
|
|
66
|
+
await createTestFile(workdir, "src/foo.spec.ts", "describe('foo', () => {})");
|
|
67
|
+
const story = createMockStory();
|
|
68
|
+
const result = await isGreenfieldStory(story, workdir);
|
|
69
|
+
expect(result).toBe(false);
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
it("returns false when .test.js files exist", async () => {
|
|
73
|
+
await createTestFile(workdir, "src/foo.test.js", "test('foo', () => {})");
|
|
74
|
+
const story = createMockStory();
|
|
75
|
+
const result = await isGreenfieldStory(story, workdir);
|
|
76
|
+
expect(result).toBe(false);
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
it("returns false when .test.tsx files exist", async () => {
|
|
80
|
+
await createTestFile(workdir, "src/Component.test.tsx", "test('renders', () => {})");
|
|
81
|
+
const story = createMockStory();
|
|
82
|
+
const result = await isGreenfieldStory(story, workdir);
|
|
83
|
+
expect(result).toBe(false);
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
it("returns false when test files exist in test/ directory", async () => {
|
|
87
|
+
await createTestFile(workdir, "test/unit/foo.test.ts", "test('foo', () => {})");
|
|
88
|
+
const story = createMockStory();
|
|
89
|
+
const result = await isGreenfieldStory(story, workdir);
|
|
90
|
+
expect(result).toBe(false);
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
it("ignores test files in node_modules", async () => {
|
|
94
|
+
await createTestFile(workdir, "node_modules/lib/foo.test.ts", "test('foo', () => {})");
|
|
95
|
+
const story = createMockStory();
|
|
96
|
+
const result = await isGreenfieldStory(story, workdir);
|
|
97
|
+
expect(result).toBe(true);
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
it("ignores test files in dist", async () => {
|
|
101
|
+
await createTestFile(workdir, "dist/foo.test.ts", "test('foo', () => {})");
|
|
102
|
+
const story = createMockStory();
|
|
103
|
+
const result = await isGreenfieldStory(story, workdir);
|
|
104
|
+
expect(result).toBe(true);
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
it("ignores test files in build", async () => {
|
|
108
|
+
await createTestFile(workdir, "build/foo.test.ts", "test('foo', () => {})");
|
|
109
|
+
const story = createMockStory();
|
|
110
|
+
const result = await isGreenfieldStory(story, workdir);
|
|
111
|
+
expect(result).toBe(true);
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
it("returns true when only source files exist", async () => {
|
|
115
|
+
await createTestFile(workdir, "src/index.ts", "export const foo = 42;");
|
|
116
|
+
await createTestFile(workdir, "src/utils.ts", "export const bar = 'baz';");
|
|
117
|
+
const story = createMockStory();
|
|
118
|
+
const result = await isGreenfieldStory(story, workdir);
|
|
119
|
+
expect(result).toBe(true);
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
it("accepts custom test pattern", async () => {
|
|
123
|
+
await createTestFile(workdir, "src/foo.custom.ts", "test('foo', () => {})");
|
|
124
|
+
const story = createMockStory();
|
|
125
|
+
|
|
126
|
+
// Default pattern should not match
|
|
127
|
+
const resultDefault = await isGreenfieldStory(story, workdir);
|
|
128
|
+
expect(resultDefault).toBe(true);
|
|
129
|
+
|
|
130
|
+
// Custom pattern should match
|
|
131
|
+
const resultCustom = await isGreenfieldStory(story, workdir, "**/*.custom.ts");
|
|
132
|
+
expect(resultCustom).toBe(false);
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
it("returns false on scan error (safe fallback - don't skip TDD)", async () => {
|
|
136
|
+
const story = createMockStory();
|
|
137
|
+
// Use an invalid workdir to trigger scan error
|
|
138
|
+
const result = await isGreenfieldStory(story, "/nonexistent/path/that/does/not/exist");
|
|
139
|
+
// Should return false (not greenfield) to be safe - don't skip TDD when unsure
|
|
140
|
+
expect(result).toBe(false);
|
|
141
|
+
});
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
145
|
+
// BUG-012 Regression: pre-existing tests should not be treated as greenfield
|
|
146
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
147
|
+
|
|
148
|
+
describe("BUG-012: pre-existing test files prevent false greenfield detection", () => {
|
|
149
|
+
let workdir: string;
|
|
150
|
+
|
|
151
|
+
beforeEach(async () => {
|
|
152
|
+
workdir = await mkdtemp(join(tmpdir(), "nax-greenfield-bug012-"));
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
afterEach(async () => {
|
|
156
|
+
await rm(workdir, { recursive: true, force: true });
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
it("returns false (not greenfield) when test file was committed before test-writer ran", async () => {
|
|
160
|
+
// Simulate: developer pre-wrote tests and committed them (dogfood scenario)
|
|
161
|
+
await createTestFile(workdir, "test/unit/commands/unlock.test.ts", "import { describe, it, expect } from 'bun:test'; describe('unlock', () => { it('works', () => { expect(true).toBe(true); }); });");
|
|
162
|
+
|
|
163
|
+
const story = createMockStory("US-001");
|
|
164
|
+
const result = await isGreenfieldStory(story, workdir);
|
|
165
|
+
|
|
166
|
+
// Should NOT be greenfield — pre-existing tests exist
|
|
167
|
+
expect(result).toBe(false);
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
it("returns true (greenfield) only when absolutely no test files exist", async () => {
|
|
171
|
+
// Only source files, no tests
|
|
172
|
+
await createTestFile(workdir, "src/commands/unlock.ts", "export function unlockCommand() {}");
|
|
173
|
+
|
|
174
|
+
const story = createMockStory("US-001");
|
|
175
|
+
const result = await isGreenfieldStory(story, workdir);
|
|
176
|
+
|
|
177
|
+
expect(result).toBe(true);
|
|
178
|
+
});
|
|
179
|
+
});
|