@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,341 @@
|
|
|
1
|
+
import { afterEach, beforeEach, describe, expect, test } from "bun:test";
|
|
2
|
+
import { execSync } from "node:child_process";
|
|
3
|
+
import { existsSync, mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
|
|
4
|
+
import { tmpdir } from "node:os";
|
|
5
|
+
import { join } from "node:path";
|
|
6
|
+
import { WorktreeManager } from "../../../src/worktree/manager";
|
|
7
|
+
import { MergeEngine } from "../../../src/worktree/merge";
|
|
8
|
+
|
|
9
|
+
describe("MergeEngine", () => {
|
|
10
|
+
let testDir: string;
|
|
11
|
+
let projectRoot: string;
|
|
12
|
+
let manager: WorktreeManager;
|
|
13
|
+
let engine: MergeEngine;
|
|
14
|
+
|
|
15
|
+
beforeEach(() => {
|
|
16
|
+
// Create a temporary directory for each test
|
|
17
|
+
testDir = mkdtempSync(join(tmpdir(), "merge-test-"));
|
|
18
|
+
projectRoot = join(testDir, "test-project");
|
|
19
|
+
mkdirSync(projectRoot, { recursive: true });
|
|
20
|
+
|
|
21
|
+
// Initialize a git repository
|
|
22
|
+
execSync("git init", { cwd: projectRoot, stdio: "pipe" });
|
|
23
|
+
execSync('git config user.email "test@example.com"', {
|
|
24
|
+
cwd: projectRoot,
|
|
25
|
+
stdio: "pipe",
|
|
26
|
+
});
|
|
27
|
+
execSync('git config user.name "Test User"', {
|
|
28
|
+
cwd: projectRoot,
|
|
29
|
+
stdio: "pipe",
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
// Create an initial commit (required for worktree creation)
|
|
33
|
+
writeFileSync(join(projectRoot, "README.md"), "# Test Project");
|
|
34
|
+
execSync("git add README.md", { cwd: projectRoot, stdio: "pipe" });
|
|
35
|
+
execSync('git commit -m "Initial commit"', {
|
|
36
|
+
cwd: projectRoot,
|
|
37
|
+
stdio: "pipe",
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
manager = new WorktreeManager();
|
|
41
|
+
engine = new MergeEngine(manager);
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
afterEach(() => {
|
|
45
|
+
// Clean up test directory
|
|
46
|
+
if (existsSync(testDir)) {
|
|
47
|
+
rmSync(testDir, { recursive: true, force: true });
|
|
48
|
+
}
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
describe("merge", () => {
|
|
52
|
+
test("performs git merge --no-ff of story branch", async () => {
|
|
53
|
+
const storyId = "story-merge-1";
|
|
54
|
+
|
|
55
|
+
// Create worktree and make a change
|
|
56
|
+
await manager.create(projectRoot, storyId);
|
|
57
|
+
const worktreePath = join(projectRoot, ".nax-wt", storyId);
|
|
58
|
+
writeFileSync(join(worktreePath, "feature.txt"), "feature content");
|
|
59
|
+
execSync("git add feature.txt", {
|
|
60
|
+
cwd: worktreePath,
|
|
61
|
+
stdio: "pipe",
|
|
62
|
+
});
|
|
63
|
+
execSync('git commit -m "Add feature"', {
|
|
64
|
+
cwd: worktreePath,
|
|
65
|
+
stdio: "pipe",
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
// Merge the branch
|
|
69
|
+
const result = await engine.merge(projectRoot, storyId);
|
|
70
|
+
|
|
71
|
+
expect(result.success).toBe(true);
|
|
72
|
+
expect(result.conflictFiles).toBeUndefined();
|
|
73
|
+
|
|
74
|
+
// Verify merge commit exists
|
|
75
|
+
const log = execSync("git log --oneline --graph", {
|
|
76
|
+
cwd: projectRoot,
|
|
77
|
+
encoding: "utf-8",
|
|
78
|
+
});
|
|
79
|
+
expect(log).toContain(`Merge branch 'nax/${storyId}'`);
|
|
80
|
+
|
|
81
|
+
// Verify feature file exists in main branch
|
|
82
|
+
expect(existsSync(join(projectRoot, "feature.txt"))).toBe(true);
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
test("returns { success: true } on clean merge", async () => {
|
|
86
|
+
const storyId = "story-clean";
|
|
87
|
+
|
|
88
|
+
// Create worktree and make a non-conflicting change
|
|
89
|
+
await manager.create(projectRoot, storyId);
|
|
90
|
+
const worktreePath = join(projectRoot, ".nax-wt", storyId);
|
|
91
|
+
writeFileSync(join(worktreePath, "new-file.txt"), "new content");
|
|
92
|
+
execSync("git add new-file.txt", {
|
|
93
|
+
cwd: worktreePath,
|
|
94
|
+
stdio: "pipe",
|
|
95
|
+
});
|
|
96
|
+
execSync('git commit -m "Add new file"', {
|
|
97
|
+
cwd: worktreePath,
|
|
98
|
+
stdio: "pipe",
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
const result = await engine.merge(projectRoot, storyId);
|
|
102
|
+
|
|
103
|
+
expect(result.success).toBe(true);
|
|
104
|
+
expect(result.conflictFiles).toBeUndefined();
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
test("returns { success: false, conflictFiles: [...] } on conflict", async () => {
|
|
108
|
+
const storyId = "story-conflict";
|
|
109
|
+
|
|
110
|
+
// Create worktree
|
|
111
|
+
await manager.create(projectRoot, storyId);
|
|
112
|
+
const worktreePath = join(projectRoot, ".nax-wt", storyId);
|
|
113
|
+
|
|
114
|
+
// Make conflicting changes in both branches
|
|
115
|
+
// Change in main branch
|
|
116
|
+
writeFileSync(join(projectRoot, "conflict.txt"), "main content");
|
|
117
|
+
execSync("git add conflict.txt", {
|
|
118
|
+
cwd: projectRoot,
|
|
119
|
+
stdio: "pipe",
|
|
120
|
+
});
|
|
121
|
+
execSync('git commit -m "Add conflict file in main"', {
|
|
122
|
+
cwd: projectRoot,
|
|
123
|
+
stdio: "pipe",
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
// Change in story branch (same file, different content)
|
|
127
|
+
writeFileSync(join(worktreePath, "conflict.txt"), "story content");
|
|
128
|
+
execSync("git add conflict.txt", {
|
|
129
|
+
cwd: worktreePath,
|
|
130
|
+
stdio: "pipe",
|
|
131
|
+
});
|
|
132
|
+
execSync('git commit -m "Add conflict file in story"', {
|
|
133
|
+
cwd: worktreePath,
|
|
134
|
+
stdio: "pipe",
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
const result = await engine.merge(projectRoot, storyId);
|
|
138
|
+
|
|
139
|
+
expect(result.success).toBe(false);
|
|
140
|
+
expect(result.conflictFiles).toBeDefined();
|
|
141
|
+
expect(result.conflictFiles?.length).toBeGreaterThan(0);
|
|
142
|
+
expect(result.conflictFiles).toContain("conflict.txt");
|
|
143
|
+
|
|
144
|
+
// Verify merge was aborted (working tree should be clean except for .nax-wt)
|
|
145
|
+
const status = execSync("git status --short", {
|
|
146
|
+
cwd: projectRoot,
|
|
147
|
+
encoding: "utf-8",
|
|
148
|
+
});
|
|
149
|
+
const nonWorktreeStatus = status
|
|
150
|
+
.split("\n")
|
|
151
|
+
.filter((line) => !line.includes(".nax-wt"))
|
|
152
|
+
.join("\n")
|
|
153
|
+
.trim();
|
|
154
|
+
expect(nonWorktreeStatus).toBe(""); // Clean working tree after abort
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
test("cleans up worktree after successful merge", async () => {
|
|
158
|
+
const storyId = "story-cleanup";
|
|
159
|
+
|
|
160
|
+
// Create worktree and make a change
|
|
161
|
+
await manager.create(projectRoot, storyId);
|
|
162
|
+
const worktreePath = join(projectRoot, ".nax-wt", storyId);
|
|
163
|
+
writeFileSync(join(worktreePath, "cleanup.txt"), "cleanup test");
|
|
164
|
+
execSync("git add cleanup.txt", {
|
|
165
|
+
cwd: worktreePath,
|
|
166
|
+
stdio: "pipe",
|
|
167
|
+
});
|
|
168
|
+
execSync('git commit -m "Add cleanup test"', {
|
|
169
|
+
cwd: worktreePath,
|
|
170
|
+
stdio: "pipe",
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
// Merge and verify cleanup
|
|
174
|
+
await engine.merge(projectRoot, storyId);
|
|
175
|
+
|
|
176
|
+
// Worktree should be removed
|
|
177
|
+
expect(existsSync(worktreePath)).toBe(false);
|
|
178
|
+
|
|
179
|
+
// Branch should be deleted
|
|
180
|
+
const branches = execSync("git branch --list", {
|
|
181
|
+
cwd: projectRoot,
|
|
182
|
+
encoding: "utf-8",
|
|
183
|
+
});
|
|
184
|
+
expect(branches).not.toContain(`nax/${storyId}`);
|
|
185
|
+
});
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
describe("mergeAll", () => {
|
|
189
|
+
test("processes stories in topological order", async () => {
|
|
190
|
+
// Create three stories with dependencies: story-1 <- story-2 <- story-3
|
|
191
|
+
const storyIds = ["story-1", "story-2", "story-3"];
|
|
192
|
+
const dependencies = {
|
|
193
|
+
"story-1": [],
|
|
194
|
+
"story-2": ["story-1"],
|
|
195
|
+
"story-3": ["story-2"],
|
|
196
|
+
};
|
|
197
|
+
|
|
198
|
+
// Create worktrees and commits for each story
|
|
199
|
+
for (const storyId of storyIds) {
|
|
200
|
+
await manager.create(projectRoot, storyId);
|
|
201
|
+
const worktreePath = join(projectRoot, ".nax-wt", storyId);
|
|
202
|
+
writeFileSync(join(worktreePath, `${storyId}.txt`), `${storyId} content`);
|
|
203
|
+
execSync(`git add ${storyId}.txt`, {
|
|
204
|
+
cwd: worktreePath,
|
|
205
|
+
stdio: "pipe",
|
|
206
|
+
});
|
|
207
|
+
execSync(`git commit -m "Add ${storyId}"`, {
|
|
208
|
+
cwd: worktreePath,
|
|
209
|
+
stdio: "pipe",
|
|
210
|
+
});
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
const results = await engine.mergeAll(projectRoot, storyIds, dependencies);
|
|
214
|
+
|
|
215
|
+
// All should succeed
|
|
216
|
+
expect(results.every((r) => r.success)).toBe(true);
|
|
217
|
+
expect(results.length).toBe(3);
|
|
218
|
+
|
|
219
|
+
// Verify all files exist
|
|
220
|
+
for (const storyId of storyIds) {
|
|
221
|
+
expect(existsSync(join(projectRoot, `${storyId}.txt`))).toBe(true);
|
|
222
|
+
}
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
test("retries once on conflict after rebasing worktree", async () => {
|
|
226
|
+
const storyIds = ["story-base", "story-conflict"];
|
|
227
|
+
const dependencies = {
|
|
228
|
+
"story-base": [],
|
|
229
|
+
"story-conflict": [],
|
|
230
|
+
};
|
|
231
|
+
|
|
232
|
+
// Create base story
|
|
233
|
+
await manager.create(projectRoot, "story-base");
|
|
234
|
+
const basePath = join(projectRoot, ".nax-wt", "story-base");
|
|
235
|
+
writeFileSync(join(basePath, "shared.txt"), "base content");
|
|
236
|
+
execSync("git add shared.txt", { cwd: basePath, stdio: "pipe" });
|
|
237
|
+
execSync('git commit -m "Add shared file"', {
|
|
238
|
+
cwd: basePath,
|
|
239
|
+
stdio: "pipe",
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
// Create conflicting story
|
|
243
|
+
await manager.create(projectRoot, "story-conflict");
|
|
244
|
+
const conflictPath = join(projectRoot, ".nax-wt", "story-conflict");
|
|
245
|
+
writeFileSync(join(conflictPath, "shared.txt"), "conflict content");
|
|
246
|
+
execSync("git add shared.txt", { cwd: conflictPath, stdio: "pipe" });
|
|
247
|
+
execSync('git commit -m "Add conflicting shared file"', {
|
|
248
|
+
cwd: conflictPath,
|
|
249
|
+
stdio: "pipe",
|
|
250
|
+
});
|
|
251
|
+
|
|
252
|
+
// This should handle the conflict scenario
|
|
253
|
+
const results = await engine.mergeAll(projectRoot, storyIds, dependencies);
|
|
254
|
+
|
|
255
|
+
expect(results.length).toBe(2);
|
|
256
|
+
// First story should succeed
|
|
257
|
+
expect(results[0].success).toBe(true);
|
|
258
|
+
expect(results[0].storyId).toBe("story-base");
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
test("marks story as failed on second conflict", async () => {
|
|
262
|
+
const storyIds = ["story-1", "story-2"];
|
|
263
|
+
const dependencies = {
|
|
264
|
+
"story-1": [],
|
|
265
|
+
"story-2": [],
|
|
266
|
+
};
|
|
267
|
+
|
|
268
|
+
// Create first story
|
|
269
|
+
await manager.create(projectRoot, "story-1");
|
|
270
|
+
const path1 = join(projectRoot, ".nax-wt", "story-1");
|
|
271
|
+
writeFileSync(join(path1, "conflict.txt"), "content 1");
|
|
272
|
+
execSync("git add conflict.txt", { cwd: path1, stdio: "pipe" });
|
|
273
|
+
execSync('git commit -m "Story 1"', { cwd: path1, stdio: "pipe" });
|
|
274
|
+
|
|
275
|
+
// Create second story with conflict
|
|
276
|
+
await manager.create(projectRoot, "story-2");
|
|
277
|
+
const path2 = join(projectRoot, ".nax-wt", "story-2");
|
|
278
|
+
writeFileSync(join(path2, "conflict.txt"), "content 2");
|
|
279
|
+
execSync("git add conflict.txt", { cwd: path2, stdio: "pipe" });
|
|
280
|
+
execSync('git commit -m "Story 2"', { cwd: path2, stdio: "pipe" });
|
|
281
|
+
|
|
282
|
+
const results = await engine.mergeAll(projectRoot, storyIds, dependencies);
|
|
283
|
+
|
|
284
|
+
// First should succeed, second should fail
|
|
285
|
+
expect(results.length).toBe(2);
|
|
286
|
+
expect(results[0].success).toBe(true);
|
|
287
|
+
expect(results[1].success).toBe(false);
|
|
288
|
+
expect(results[1].conflictFiles).toBeDefined();
|
|
289
|
+
});
|
|
290
|
+
|
|
291
|
+
test("continues with remaining stories after failure", async () => {
|
|
292
|
+
const storyIds = ["story-base", "story-conflict", "story-3"];
|
|
293
|
+
const dependencies = {
|
|
294
|
+
"story-base": [],
|
|
295
|
+
"story-conflict": [],
|
|
296
|
+
"story-3": [],
|
|
297
|
+
};
|
|
298
|
+
|
|
299
|
+
// Create base story with a file
|
|
300
|
+
await manager.create(projectRoot, "story-base");
|
|
301
|
+
const pathBase = join(projectRoot, ".nax-wt", "story-base");
|
|
302
|
+
writeFileSync(join(pathBase, "shared.txt"), "base content");
|
|
303
|
+
execSync("git add shared.txt", { cwd: pathBase, stdio: "pipe" });
|
|
304
|
+
execSync('git commit -m "Add shared file"', {
|
|
305
|
+
cwd: pathBase,
|
|
306
|
+
stdio: "pipe",
|
|
307
|
+
});
|
|
308
|
+
|
|
309
|
+
// Create conflicting story (modifies same file)
|
|
310
|
+
await manager.create(projectRoot, "story-conflict");
|
|
311
|
+
const pathConflict = join(projectRoot, ".nax-wt", "story-conflict");
|
|
312
|
+
writeFileSync(join(pathConflict, "shared.txt"), "conflict content");
|
|
313
|
+
execSync("git add shared.txt", {
|
|
314
|
+
cwd: pathConflict,
|
|
315
|
+
stdio: "pipe",
|
|
316
|
+
});
|
|
317
|
+
execSync('git commit -m "Modify shared file in story-conflict"', {
|
|
318
|
+
cwd: pathConflict,
|
|
319
|
+
stdio: "pipe",
|
|
320
|
+
});
|
|
321
|
+
|
|
322
|
+
// Create third story (no conflict)
|
|
323
|
+
await manager.create(projectRoot, "story-3");
|
|
324
|
+
const path3 = join(projectRoot, ".nax-wt", "story-3");
|
|
325
|
+
writeFileSync(join(path3, "file3.txt"), "content 3");
|
|
326
|
+
execSync("git add file3.txt", { cwd: path3, stdio: "pipe" });
|
|
327
|
+
execSync('git commit -m "Story 3"', { cwd: path3, stdio: "pipe" });
|
|
328
|
+
|
|
329
|
+
const results = await engine.mergeAll(projectRoot, storyIds, dependencies);
|
|
330
|
+
|
|
331
|
+
// Base should succeed, conflict should fail, story-3 should succeed
|
|
332
|
+
expect(results.length).toBe(3);
|
|
333
|
+
expect(results[0].success).toBe(true);
|
|
334
|
+
expect(results[0].storyId).toBe("story-base");
|
|
335
|
+
expect(results[1].success).toBe(false);
|
|
336
|
+
expect(results[1].storyId).toBe("story-conflict");
|
|
337
|
+
expect(results[2].success).toBe(true);
|
|
338
|
+
expect(results[2].storyId).toBe("story-3");
|
|
339
|
+
});
|
|
340
|
+
});
|
|
341
|
+
});
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Manual demo of the logging formatter
|
|
3
|
+
*
|
|
4
|
+
* Run with: bun run test/manual/logging-formatter-demo.ts
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { LogEntry } from "../../src/logger/types.js";
|
|
8
|
+
import { formatLogEntry, formatRunSummary } from "../../src/logging/formatter.js";
|
|
9
|
+
import type { RunSummary } from "../../src/logging/types.js";
|
|
10
|
+
|
|
11
|
+
console.log("\n=== LOGGING FORMATTER DEMO ===\n");
|
|
12
|
+
|
|
13
|
+
// Run start
|
|
14
|
+
const runStart: LogEntry = {
|
|
15
|
+
timestamp: new Date().toISOString(),
|
|
16
|
+
level: "info",
|
|
17
|
+
stage: "run.start",
|
|
18
|
+
message: "Starting feature",
|
|
19
|
+
data: {
|
|
20
|
+
runId: "run-2026-02-27T14-00-00-000Z",
|
|
21
|
+
feature: "authentication",
|
|
22
|
+
workdir: "/Users/test/project",
|
|
23
|
+
totalStories: 5,
|
|
24
|
+
},
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
console.log(formatLogEntry(runStart, { mode: "normal", useColor: true }).output);
|
|
28
|
+
|
|
29
|
+
// Story start
|
|
30
|
+
const storyStart: LogEntry = {
|
|
31
|
+
timestamp: new Date().toISOString(),
|
|
32
|
+
level: "info",
|
|
33
|
+
stage: "story.start",
|
|
34
|
+
storyId: "US-001",
|
|
35
|
+
message: "Starting story",
|
|
36
|
+
data: {
|
|
37
|
+
storyId: "US-001",
|
|
38
|
+
storyTitle: "Add user authentication with JWT",
|
|
39
|
+
complexity: "medium",
|
|
40
|
+
modelTier: "balanced",
|
|
41
|
+
},
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
console.log(formatLogEntry(storyStart, { mode: "normal", useColor: true }).output);
|
|
45
|
+
|
|
46
|
+
// TDD session
|
|
47
|
+
const tddSession: LogEntry = {
|
|
48
|
+
timestamp: new Date().toISOString(),
|
|
49
|
+
level: "info",
|
|
50
|
+
stage: "tdd",
|
|
51
|
+
message: "→ Session: test-writer",
|
|
52
|
+
data: {
|
|
53
|
+
role: "test-writer",
|
|
54
|
+
storyId: "US-001",
|
|
55
|
+
},
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
console.log(formatLogEntry(tddSession, { mode: "normal", useColor: true }).output);
|
|
59
|
+
|
|
60
|
+
// Routing (verbose only)
|
|
61
|
+
const routing: LogEntry = {
|
|
62
|
+
timestamp: new Date().toISOString(),
|
|
63
|
+
level: "debug",
|
|
64
|
+
stage: "routing",
|
|
65
|
+
message: "Task classified",
|
|
66
|
+
data: {
|
|
67
|
+
complexity: "medium",
|
|
68
|
+
modelTier: "balanced",
|
|
69
|
+
tokens: 1500,
|
|
70
|
+
contextFiles: ["src/auth.ts", "src/middleware.ts"],
|
|
71
|
+
},
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
console.log("\n--- Normal mode (should hide debug) ---");
|
|
75
|
+
console.log(formatLogEntry(routing, { mode: "normal", useColor: true }).output);
|
|
76
|
+
|
|
77
|
+
console.log("\n--- Verbose mode (should show debug with data) ---");
|
|
78
|
+
console.log(formatLogEntry(routing, { mode: "verbose", useColor: true }).output);
|
|
79
|
+
|
|
80
|
+
// Story complete - success
|
|
81
|
+
const storySuccess: LogEntry = {
|
|
82
|
+
timestamp: new Date().toISOString(),
|
|
83
|
+
level: "info",
|
|
84
|
+
stage: "story.complete",
|
|
85
|
+
storyId: "US-001",
|
|
86
|
+
message: "Story completed",
|
|
87
|
+
data: {
|
|
88
|
+
storyId: "US-001",
|
|
89
|
+
success: true,
|
|
90
|
+
cost: 0.2567,
|
|
91
|
+
durationMs: 45000,
|
|
92
|
+
},
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
console.log("\n");
|
|
96
|
+
console.log(formatLogEntry(storySuccess, { mode: "normal", useColor: true }).output);
|
|
97
|
+
|
|
98
|
+
// Story complete - escalation
|
|
99
|
+
const storyEscalate: LogEntry = {
|
|
100
|
+
timestamp: new Date().toISOString(),
|
|
101
|
+
level: "warn",
|
|
102
|
+
stage: "agent.complete",
|
|
103
|
+
storyId: "US-002",
|
|
104
|
+
message: "Escalated",
|
|
105
|
+
data: {
|
|
106
|
+
storyId: "US-002",
|
|
107
|
+
success: false,
|
|
108
|
+
finalAction: "escalate",
|
|
109
|
+
cost: 0.12,
|
|
110
|
+
durationMs: 30000,
|
|
111
|
+
},
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
console.log(formatLogEntry(storyEscalate, { mode: "normal", useColor: true }).output);
|
|
115
|
+
|
|
116
|
+
// Story complete - failure
|
|
117
|
+
const storyFail: LogEntry = {
|
|
118
|
+
timestamp: new Date().toISOString(),
|
|
119
|
+
level: "error",
|
|
120
|
+
stage: "story.complete",
|
|
121
|
+
storyId: "US-003",
|
|
122
|
+
message: "Failed",
|
|
123
|
+
data: {
|
|
124
|
+
storyId: "US-003",
|
|
125
|
+
success: false,
|
|
126
|
+
finalAction: "fail",
|
|
127
|
+
reason: "Test suite failed after 3 attempts",
|
|
128
|
+
cost: 0.35,
|
|
129
|
+
durationMs: 120000,
|
|
130
|
+
},
|
|
131
|
+
};
|
|
132
|
+
|
|
133
|
+
console.log(formatLogEntry(storyFail, { mode: "verbose", useColor: true }).output);
|
|
134
|
+
|
|
135
|
+
// Run summary
|
|
136
|
+
const summary: RunSummary = {
|
|
137
|
+
total: 10,
|
|
138
|
+
passed: 8,
|
|
139
|
+
failed: 1,
|
|
140
|
+
skipped: 1,
|
|
141
|
+
durationMs: 300000,
|
|
142
|
+
totalCost: 1.2345,
|
|
143
|
+
startedAt: "2026-02-27T14:00:00Z",
|
|
144
|
+
completedAt: "2026-02-27T14:05:00Z",
|
|
145
|
+
};
|
|
146
|
+
|
|
147
|
+
console.log(formatRunSummary(summary, { mode: "normal", useColor: true }));
|
|
148
|
+
|
|
149
|
+
console.log("\n=== JSON MODE ===\n");
|
|
150
|
+
console.log(formatLogEntry(storyStart, { mode: "json" }).output);
|
|
151
|
+
console.log(formatRunSummary(summary, { mode: "json" }));
|
|
152
|
+
|
|
153
|
+
console.log("\n=== QUIET MODE (only shows critical events) ===\n");
|
|
154
|
+
console.log("Run start:", formatLogEntry(runStart, { mode: "quiet" }).shouldDisplay);
|
|
155
|
+
console.log("Story start:", formatLogEntry(storyStart, { mode: "quiet" }).shouldDisplay);
|
|
156
|
+
console.log("TDD session:", formatLogEntry(tddSession, { mode: "quiet" }).shouldDisplay);
|
|
157
|
+
console.log("Routing debug:", formatLogEntry(routing, { mode: "quiet" }).shouldDisplay);
|
|
158
|
+
console.log("Story complete:", formatLogEntry(storySuccess, { mode: "quiet" }).shouldDisplay);
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for TUI Agent Panel and PTY integration (Phase 3).
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { describe, expect, test } from "bun:test";
|
|
6
|
+
import { render } from "ink-testing-library";
|
|
7
|
+
import { ClaudeCodeAdapter } from "../../src/agents/claude";
|
|
8
|
+
import type { PtyHandle } from "../../src/agents/types";
|
|
9
|
+
import { AgentPanel } from "../../src/tui/components/AgentPanel";
|
|
10
|
+
|
|
11
|
+
describe("AgentPanel", () => {
|
|
12
|
+
test("renders placeholder when no output", () => {
|
|
13
|
+
const { lastFrame } = render(<AgentPanel outputLines={[]} />);
|
|
14
|
+
expect(lastFrame()).toContain("Waiting for agent...");
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
test("renders output lines", () => {
|
|
18
|
+
const lines = ["Line 1", "Line 2", "Line 3"];
|
|
19
|
+
const { lastFrame } = render(<AgentPanel outputLines={lines} />);
|
|
20
|
+
|
|
21
|
+
for (const line of lines) {
|
|
22
|
+
expect(lastFrame()).toContain(line);
|
|
23
|
+
}
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
test("shows focus indicator when focused", () => {
|
|
27
|
+
const { lastFrame } = render(<AgentPanel focused outputLines={[]} />);
|
|
28
|
+
expect(lastFrame()).toContain("(focused)");
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
test("does not show focus indicator when not focused", () => {
|
|
32
|
+
const { lastFrame } = render(<AgentPanel focused={false} outputLines={[]} />);
|
|
33
|
+
expect(lastFrame()).not.toContain("(focused)");
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
test("buffers only last N lines", () => {
|
|
37
|
+
// Generate 600 lines (exceeds MAX_OUTPUT_LINES = 500)
|
|
38
|
+
const lines = Array.from({ length: 600 }, (_, i) => `Output ${i + 1}`);
|
|
39
|
+
const { lastFrame } = render(<AgentPanel outputLines={lines} />);
|
|
40
|
+
const frame = lastFrame();
|
|
41
|
+
|
|
42
|
+
// Should contain last 500 lines
|
|
43
|
+
expect(frame).toContain("Output 600");
|
|
44
|
+
expect(frame).toContain("Output 101");
|
|
45
|
+
|
|
46
|
+
// Should NOT contain first 100 lines (trimmed)
|
|
47
|
+
// Use exact line boundaries to avoid substring matches
|
|
48
|
+
expect(frame).not.toContain("Output 1\n");
|
|
49
|
+
expect(frame).not.toContain("Output 100\n");
|
|
50
|
+
});
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
describe("PtyHandle interface", () => {
|
|
54
|
+
test("ClaudeCodeAdapter has PtyHandle interface", () => {
|
|
55
|
+
const adapter = new ClaudeCodeAdapter();
|
|
56
|
+
|
|
57
|
+
// Check that runInteractive method exists
|
|
58
|
+
expect(adapter.runInteractive).toBeDefined();
|
|
59
|
+
expect(typeof adapter.runInteractive).toBe("function");
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
test("PtyHandle has required methods", () => {
|
|
63
|
+
// Mock PtyHandle to verify interface contract
|
|
64
|
+
const mockHandle: PtyHandle = {
|
|
65
|
+
write: (data: string) => {
|
|
66
|
+
expect(typeof data).toBe("string");
|
|
67
|
+
},
|
|
68
|
+
resize: (cols: number, rows: number) => {
|
|
69
|
+
expect(typeof cols).toBe("number");
|
|
70
|
+
expect(typeof rows).toBe("number");
|
|
71
|
+
},
|
|
72
|
+
kill: () => {
|
|
73
|
+
// noop
|
|
74
|
+
},
|
|
75
|
+
pid: 12345,
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
// Verify all methods exist
|
|
79
|
+
expect(mockHandle.write).toBeDefined();
|
|
80
|
+
expect(mockHandle.resize).toBeDefined();
|
|
81
|
+
expect(mockHandle.kill).toBeDefined();
|
|
82
|
+
expect(mockHandle.pid).toBe(12345);
|
|
83
|
+
|
|
84
|
+
// Test methods
|
|
85
|
+
mockHandle.write("test");
|
|
86
|
+
mockHandle.resize(80, 24);
|
|
87
|
+
mockHandle.kill();
|
|
88
|
+
});
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
describe("usePty hook", () => {
|
|
92
|
+
test("returns null handle when options is null", () => {
|
|
93
|
+
// This test would require rendering a component that uses usePty
|
|
94
|
+
// For now, we just document the expected behavior
|
|
95
|
+
// const { handle } = usePty(null);
|
|
96
|
+
// expect(handle).toBeNull();
|
|
97
|
+
expect(true).toBe(true); // Placeholder
|
|
98
|
+
});
|
|
99
|
+
});
|