@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
package/src/tdd/types.ts
ADDED
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
/** TDD session role */
|
|
2
|
+
export type TddSessionRole = "test-writer" | "implementer" | "verifier";
|
|
3
|
+
|
|
4
|
+
/** Failure categories for TDD orchestrator results */
|
|
5
|
+
export type FailureCategory =
|
|
6
|
+
/** Test-writer violated file isolation or created no test files */
|
|
7
|
+
| "isolation-violation"
|
|
8
|
+
/** A session crashed, timed out, or the agent failed to produce usable output */
|
|
9
|
+
| "session-failure"
|
|
10
|
+
/** Tests were written and implemented but still fail after all sessions */
|
|
11
|
+
| "tests-failing"
|
|
12
|
+
/** Verifier explicitly rejected the implementation */
|
|
13
|
+
| "verifier-rejected"
|
|
14
|
+
/** Greenfield project with no test files — TDD not applicable (BUG-010) */
|
|
15
|
+
| "greenfield-no-tests";
|
|
16
|
+
|
|
17
|
+
/** Isolation verification result */
|
|
18
|
+
export interface IsolationCheck {
|
|
19
|
+
/** Whether isolation passed (no hard violations) */
|
|
20
|
+
passed: boolean;
|
|
21
|
+
/** Hard violation files (files that must not be modified) */
|
|
22
|
+
violations: string[];
|
|
23
|
+
/** Soft violation files (allowed-path overrides, warning only) */
|
|
24
|
+
softViolations?: string[];
|
|
25
|
+
/** Warning files (e.g., implementer touching test files slightly) */
|
|
26
|
+
warnings?: string[];
|
|
27
|
+
/** Human-readable description of what was checked */
|
|
28
|
+
description?: string;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/** Result of a single TDD session */
|
|
32
|
+
export interface TddSessionResult {
|
|
33
|
+
/** Session role */
|
|
34
|
+
role: TddSessionRole;
|
|
35
|
+
/** Whether session completed successfully */
|
|
36
|
+
success: boolean;
|
|
37
|
+
/** Isolation check results (if applicable) */
|
|
38
|
+
isolation?: IsolationCheck;
|
|
39
|
+
/** Cost of this session (USD) */
|
|
40
|
+
estimatedCost: number;
|
|
41
|
+
/** Files changed by this session (from git diff) */
|
|
42
|
+
filesChanged: string[];
|
|
43
|
+
/** Duration of this session in milliseconds */
|
|
44
|
+
durationMs: number;
|
|
45
|
+
/** Git branch created/used (optional legacy field) */
|
|
46
|
+
branch?: string;
|
|
47
|
+
/** ISO timestamp (optional legacy field) */
|
|
48
|
+
timestamp?: string;
|
|
49
|
+
/** Error message (if success=false) */
|
|
50
|
+
error?: string;
|
|
51
|
+
/** Number of tests written/passed/failed */
|
|
52
|
+
tests?: {
|
|
53
|
+
total: number;
|
|
54
|
+
passed: number;
|
|
55
|
+
failed: number;
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/** Result of a three-session TDD orchestration */
|
|
60
|
+
export interface ThreeSessionTddResult {
|
|
61
|
+
/** Overall success */
|
|
62
|
+
success: boolean;
|
|
63
|
+
/** Individual session results */
|
|
64
|
+
sessions: TddSessionResult[];
|
|
65
|
+
/** Whether human review is needed */
|
|
66
|
+
needsHumanReview: boolean;
|
|
67
|
+
/** Reason for review (if any) */
|
|
68
|
+
reviewReason?: string;
|
|
69
|
+
/** Total cost of all sessions (USD) */
|
|
70
|
+
totalCost: number;
|
|
71
|
+
/** Whether lite mode was used (skips test-writer/implementer isolation) */
|
|
72
|
+
lite: boolean;
|
|
73
|
+
/** Category of failure (if success is false) */
|
|
74
|
+
failureCategory?: FailureCategory;
|
|
75
|
+
/**
|
|
76
|
+
* Verifier verdict parsed from .nax-verifier-verdict.json (for logging/debugging).
|
|
77
|
+
* null = verdict file was missing or malformed (no verdict available)
|
|
78
|
+
* undefined = verdict was not attempted (e.g. early-exit before session 3 ran)
|
|
79
|
+
*/
|
|
80
|
+
verdict?: import("./verdict").VerifierVerdict | null;
|
|
81
|
+
}
|
|
@@ -0,0 +1,271 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Verifier Verdict — types and reader
|
|
3
|
+
*
|
|
4
|
+
* The verifier (session 3) writes a structured verdict file to
|
|
5
|
+
* `.nax-verifier-verdict.json` in the workdir. This module reads,
|
|
6
|
+
* validates, and interprets that verdict.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { unlink } from "node:fs/promises";
|
|
10
|
+
import path from "node:path";
|
|
11
|
+
import { getLogger } from "../logger";
|
|
12
|
+
import type { FailureCategory } from "./types";
|
|
13
|
+
|
|
14
|
+
/** File name written by the verifier agent */
|
|
15
|
+
export const VERDICT_FILE = ".nax-verifier-verdict.json";
|
|
16
|
+
|
|
17
|
+
/** Structured verdict written by the verifier (session 3) */
|
|
18
|
+
export interface VerifierVerdict {
|
|
19
|
+
/** Schema version */
|
|
20
|
+
version: 1;
|
|
21
|
+
|
|
22
|
+
/** Overall approval */
|
|
23
|
+
approved: boolean;
|
|
24
|
+
|
|
25
|
+
/** Test results */
|
|
26
|
+
tests: {
|
|
27
|
+
/** Did all tests pass? */
|
|
28
|
+
allPassing: boolean;
|
|
29
|
+
/** Number of passing tests */
|
|
30
|
+
passCount: number;
|
|
31
|
+
/** Number of failing tests */
|
|
32
|
+
failCount: number;
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
/** Implementer test modification review */
|
|
36
|
+
testModifications: {
|
|
37
|
+
/** Were test files modified by implementer? */
|
|
38
|
+
detected: boolean;
|
|
39
|
+
/** List of modified test files */
|
|
40
|
+
files: string[];
|
|
41
|
+
/** Are the modifications legitimate? */
|
|
42
|
+
legitimate: boolean;
|
|
43
|
+
/** Reasoning for legitimacy judgment */
|
|
44
|
+
reasoning: string;
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
/** Acceptance criteria check */
|
|
48
|
+
acceptanceCriteria: {
|
|
49
|
+
/** All criteria met? */
|
|
50
|
+
allMet: boolean;
|
|
51
|
+
/** Per-criterion status */
|
|
52
|
+
criteria: Array<{
|
|
53
|
+
criterion: string;
|
|
54
|
+
met: boolean;
|
|
55
|
+
note?: string;
|
|
56
|
+
}>;
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
/** Code quality assessment */
|
|
60
|
+
quality: {
|
|
61
|
+
/** Overall quality: good | acceptable | poor */
|
|
62
|
+
rating: "good" | "acceptable" | "poor";
|
|
63
|
+
/** Issues found */
|
|
64
|
+
issues: string[];
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
/** Fixes applied by the verifier */
|
|
68
|
+
fixes: string[];
|
|
69
|
+
|
|
70
|
+
/** Overall reasoning */
|
|
71
|
+
reasoning: string;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Validate that a parsed object has the required fields for a VerifierVerdict.
|
|
76
|
+
* Returns true if the object appears to be a valid verdict.
|
|
77
|
+
*/
|
|
78
|
+
function isValidVerdict(obj: unknown): obj is VerifierVerdict {
|
|
79
|
+
if (!obj || typeof obj !== "object") return false;
|
|
80
|
+
const v = obj as Record<string, unknown>;
|
|
81
|
+
|
|
82
|
+
// Required top-level fields
|
|
83
|
+
if (v.version !== 1) return false;
|
|
84
|
+
if (typeof v.approved !== "boolean") return false;
|
|
85
|
+
|
|
86
|
+
// tests sub-object
|
|
87
|
+
if (!v.tests || typeof v.tests !== "object") return false;
|
|
88
|
+
const tests = v.tests as Record<string, unknown>;
|
|
89
|
+
if (typeof tests.allPassing !== "boolean") return false;
|
|
90
|
+
if (typeof tests.passCount !== "number") return false;
|
|
91
|
+
if (typeof tests.failCount !== "number") return false;
|
|
92
|
+
|
|
93
|
+
// testModifications sub-object
|
|
94
|
+
if (!v.testModifications || typeof v.testModifications !== "object") return false;
|
|
95
|
+
const mods = v.testModifications as Record<string, unknown>;
|
|
96
|
+
if (typeof mods.detected !== "boolean") return false;
|
|
97
|
+
if (!Array.isArray(mods.files)) return false;
|
|
98
|
+
if (typeof mods.legitimate !== "boolean") return false;
|
|
99
|
+
if (typeof mods.reasoning !== "string") return false;
|
|
100
|
+
|
|
101
|
+
// acceptanceCriteria sub-object
|
|
102
|
+
if (!v.acceptanceCriteria || typeof v.acceptanceCriteria !== "object") return false;
|
|
103
|
+
const ac = v.acceptanceCriteria as Record<string, unknown>;
|
|
104
|
+
if (typeof ac.allMet !== "boolean") return false;
|
|
105
|
+
if (!Array.isArray(ac.criteria)) return false;
|
|
106
|
+
|
|
107
|
+
// quality sub-object
|
|
108
|
+
if (!v.quality || typeof v.quality !== "object") return false;
|
|
109
|
+
const quality = v.quality as Record<string, unknown>;
|
|
110
|
+
if (!["good", "acceptable", "poor"].includes(quality.rating as string)) return false;
|
|
111
|
+
if (!Array.isArray(quality.issues)) return false;
|
|
112
|
+
|
|
113
|
+
// fixes and reasoning
|
|
114
|
+
if (!Array.isArray(v.fixes)) return false;
|
|
115
|
+
if (typeof v.reasoning !== "string") return false;
|
|
116
|
+
|
|
117
|
+
return true;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Read the verifier verdict file from the workdir.
|
|
122
|
+
*
|
|
123
|
+
* Returns the parsed VerifierVerdict when the file exists and is valid.
|
|
124
|
+
* Returns null if:
|
|
125
|
+
* - File does not exist
|
|
126
|
+
* - File is not valid JSON
|
|
127
|
+
* - Required fields are missing or invalid
|
|
128
|
+
*
|
|
129
|
+
* Never throws.
|
|
130
|
+
*/
|
|
131
|
+
export async function readVerdict(workdir: string): Promise<VerifierVerdict | null> {
|
|
132
|
+
const logger = getLogger();
|
|
133
|
+
const verdictPath = path.join(workdir, VERDICT_FILE);
|
|
134
|
+
|
|
135
|
+
try {
|
|
136
|
+
const file = Bun.file(verdictPath);
|
|
137
|
+
const exists = await file.exists();
|
|
138
|
+
if (!exists) {
|
|
139
|
+
return null;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
let parsed: unknown;
|
|
143
|
+
try {
|
|
144
|
+
parsed = await file.json();
|
|
145
|
+
} catch (parseErr) {
|
|
146
|
+
logger.warn("tdd", "Verifier verdict file is not valid JSON — ignoring", {
|
|
147
|
+
path: verdictPath,
|
|
148
|
+
error: String(parseErr),
|
|
149
|
+
});
|
|
150
|
+
return null;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
if (!isValidVerdict(parsed)) {
|
|
154
|
+
logger.warn("tdd", "Verifier verdict file missing required fields — ignoring", {
|
|
155
|
+
path: verdictPath,
|
|
156
|
+
});
|
|
157
|
+
return null;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
return parsed;
|
|
161
|
+
} catch (err) {
|
|
162
|
+
logger.warn("tdd", "Failed to read verifier verdict file — ignoring", {
|
|
163
|
+
path: verdictPath,
|
|
164
|
+
error: String(err),
|
|
165
|
+
});
|
|
166
|
+
return null;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Delete the verifier verdict file from the workdir.
|
|
172
|
+
* Ignores all errors (file may not exist, permissions, etc.).
|
|
173
|
+
*/
|
|
174
|
+
export async function cleanupVerdict(workdir: string): Promise<void> {
|
|
175
|
+
const verdictPath = path.join(workdir, VERDICT_FILE);
|
|
176
|
+
try {
|
|
177
|
+
await unlink(verdictPath);
|
|
178
|
+
} catch {
|
|
179
|
+
// Intentionally ignored — file may not exist or already be deleted
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/** Result of categorizing a verifier verdict */
|
|
184
|
+
export interface VerdictCategorization {
|
|
185
|
+
success: boolean;
|
|
186
|
+
failureCategory?: FailureCategory;
|
|
187
|
+
reviewReason?: string;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* Categorize a verifier verdict into a success/failure outcome.
|
|
192
|
+
*
|
|
193
|
+
* @param verdict - The parsed verdict (or null if not available)
|
|
194
|
+
* @param testsPass - Fallback: whether tests pass independently (used when verdict is null)
|
|
195
|
+
* @returns Categorized outcome with optional failureCategory and reviewReason
|
|
196
|
+
*
|
|
197
|
+
* Logic:
|
|
198
|
+
* - verdict.approved = true → success
|
|
199
|
+
* - Not approved, illegitimate test mods → verifier-rejected
|
|
200
|
+
* - Not approved, tests failing → tests-failing
|
|
201
|
+
* - Not approved, criteria not met → verifier-rejected
|
|
202
|
+
* - Not approved, poor quality → verifier-rejected
|
|
203
|
+
* - Not approved, other → verifier-rejected (catch-all)
|
|
204
|
+
* - null verdict, testsPass=true → success
|
|
205
|
+
* - null verdict, testsPass=false → tests-failing
|
|
206
|
+
*/
|
|
207
|
+
export function categorizeVerdict(verdict: VerifierVerdict | null, testsPass: boolean): VerdictCategorization {
|
|
208
|
+
// No verdict — fall back to test-only check
|
|
209
|
+
if (!verdict) {
|
|
210
|
+
if (testsPass) {
|
|
211
|
+
return { success: true };
|
|
212
|
+
}
|
|
213
|
+
return {
|
|
214
|
+
success: false,
|
|
215
|
+
failureCategory: "tests-failing",
|
|
216
|
+
reviewReason: "Tests failing after all sessions (no verdict file)",
|
|
217
|
+
};
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// Approved
|
|
221
|
+
if (verdict.approved) {
|
|
222
|
+
return { success: true };
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
// Not approved — classify the reason
|
|
226
|
+
|
|
227
|
+
// 1. Illegitimate test modifications (implementer cheated)
|
|
228
|
+
if (verdict.testModifications.detected && !verdict.testModifications.legitimate) {
|
|
229
|
+
const files = verdict.testModifications.files.join(", ") || "unknown files";
|
|
230
|
+
return {
|
|
231
|
+
success: false,
|
|
232
|
+
failureCategory: "verifier-rejected",
|
|
233
|
+
reviewReason: `Verifier rejected: illegitimate test modifications in ${files}. ${verdict.testModifications.reasoning}`,
|
|
234
|
+
};
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
// 2. Tests failing
|
|
238
|
+
if (!verdict.tests.allPassing) {
|
|
239
|
+
return {
|
|
240
|
+
success: false,
|
|
241
|
+
failureCategory: "tests-failing",
|
|
242
|
+
reviewReason: `Tests failing: ${verdict.tests.failCount} failure(s). ${verdict.reasoning}`,
|
|
243
|
+
};
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
// 3. Acceptance criteria not met
|
|
247
|
+
if (!verdict.acceptanceCriteria.allMet) {
|
|
248
|
+
const unmet = verdict.acceptanceCriteria.criteria.filter((c) => !c.met).map((c) => c.criterion);
|
|
249
|
+
return {
|
|
250
|
+
success: false,
|
|
251
|
+
failureCategory: "verifier-rejected",
|
|
252
|
+
reviewReason: `Acceptance criteria not met: ${unmet.join("; ")}`,
|
|
253
|
+
};
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
// 4. Poor quality
|
|
257
|
+
if (verdict.quality.rating === "poor") {
|
|
258
|
+
return {
|
|
259
|
+
success: false,
|
|
260
|
+
failureCategory: "verifier-rejected",
|
|
261
|
+
reviewReason: `Poor code quality: ${verdict.quality.issues.join("; ")}`,
|
|
262
|
+
};
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
// Catch-all: verdict says not approved but no clear specific reason
|
|
266
|
+
return {
|
|
267
|
+
success: false,
|
|
268
|
+
failureCategory: "verifier-rejected",
|
|
269
|
+
reviewReason: verdict.reasoning || "Verifier rejected without specific reason",
|
|
270
|
+
};
|
|
271
|
+
}
|
package/src/tui/App.tsx
ADDED
|
@@ -0,0 +1,265 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* App — root TUI component.
|
|
3
|
+
*
|
|
4
|
+
* Orchestrates the layout, stories panel, agent panel, and status bar.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { Box, Text, useApp, useInput } from "ink";
|
|
8
|
+
import { useState } from "react";
|
|
9
|
+
import { writeQueueCommand } from "../utils/queue-writer";
|
|
10
|
+
import { AgentPanel } from "./components/AgentPanel";
|
|
11
|
+
import { CostOverlay } from "./components/CostOverlay";
|
|
12
|
+
import { HelpOverlay } from "./components/HelpOverlay";
|
|
13
|
+
import { StatusBar } from "./components/StatusBar";
|
|
14
|
+
import { StoriesPanel } from "./components/StoriesPanel";
|
|
15
|
+
import { type KeyboardAction, useKeyboard } from "./hooks/useKeyboard";
|
|
16
|
+
import { MIN_TERMINAL_WIDTH, useLayout } from "./hooks/useLayout";
|
|
17
|
+
import { usePipelineEvents } from "./hooks/usePipelineEvents";
|
|
18
|
+
import { usePty } from "./hooks/usePty";
|
|
19
|
+
import { PanelFocus } from "./types";
|
|
20
|
+
import type { TuiProps } from "./types";
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Root TUI application component.
|
|
24
|
+
*
|
|
25
|
+
* Renders the TUI with:
|
|
26
|
+
* - Responsive layout (single/narrow/wide)
|
|
27
|
+
* - Stories panel with status icons
|
|
28
|
+
* - Status bar showing current story/stage
|
|
29
|
+
* - Live updates via pipeline events
|
|
30
|
+
*
|
|
31
|
+
* @example
|
|
32
|
+
* ```tsx
|
|
33
|
+
* const emitter = new PipelineEventEmitter();
|
|
34
|
+
*
|
|
35
|
+
* render(
|
|
36
|
+
* <App
|
|
37
|
+
* feature="auth-system"
|
|
38
|
+
* stories={initialStories}
|
|
39
|
+
* totalCost={0}
|
|
40
|
+
* elapsedMs={0}
|
|
41
|
+
* events={emitter}
|
|
42
|
+
* />
|
|
43
|
+
* );
|
|
44
|
+
* ```
|
|
45
|
+
*/
|
|
46
|
+
export function App({ feature, stories: initialStories, events, queueFilePath, ptyOptions }: TuiProps) {
|
|
47
|
+
const layout = useLayout();
|
|
48
|
+
const state = usePipelineEvents(
|
|
49
|
+
events,
|
|
50
|
+
initialStories.map((s) => s.story),
|
|
51
|
+
);
|
|
52
|
+
const { exit } = useApp();
|
|
53
|
+
|
|
54
|
+
// Focus management (Tab toggles between Stories and Agent panels)
|
|
55
|
+
const [focus, setFocus] = useState<PanelFocus>(PanelFocus.Stories);
|
|
56
|
+
|
|
57
|
+
// Overlay state
|
|
58
|
+
const [showHelp, setShowHelp] = useState(false);
|
|
59
|
+
const [showCost, setShowCost] = useState(false);
|
|
60
|
+
const [showQuitConfirm, setShowQuitConfirm] = useState(false);
|
|
61
|
+
const [showAbortConfirm, setShowAbortConfirm] = useState(false);
|
|
62
|
+
|
|
63
|
+
// Wire PTY hook for agent session
|
|
64
|
+
const { outputLines: agentOutputLines, handle: ptyHandle } = usePty(ptyOptions ?? null);
|
|
65
|
+
|
|
66
|
+
// Handle keyboard actions
|
|
67
|
+
const handleKeyboardAction = async (action: KeyboardAction) => {
|
|
68
|
+
switch (action.type) {
|
|
69
|
+
case "TOGGLE_FOCUS":
|
|
70
|
+
setFocus((prev) => (prev === PanelFocus.Stories ? PanelFocus.Agent : PanelFocus.Stories));
|
|
71
|
+
break;
|
|
72
|
+
|
|
73
|
+
case "ESCAPE_AGENT":
|
|
74
|
+
setFocus(PanelFocus.Stories);
|
|
75
|
+
break;
|
|
76
|
+
|
|
77
|
+
case "SHOW_HELP":
|
|
78
|
+
setShowHelp(true);
|
|
79
|
+
break;
|
|
80
|
+
|
|
81
|
+
case "SHOW_COST":
|
|
82
|
+
setShowCost(true);
|
|
83
|
+
break;
|
|
84
|
+
|
|
85
|
+
case "CLOSE_OVERLAY":
|
|
86
|
+
setShowHelp(false);
|
|
87
|
+
setShowCost(false);
|
|
88
|
+
setShowQuitConfirm(false);
|
|
89
|
+
setShowAbortConfirm(false);
|
|
90
|
+
break;
|
|
91
|
+
|
|
92
|
+
case "QUIT":
|
|
93
|
+
// If a story is running, show confirmation
|
|
94
|
+
if (state.currentStory) {
|
|
95
|
+
setShowQuitConfirm(true);
|
|
96
|
+
} else {
|
|
97
|
+
exit();
|
|
98
|
+
}
|
|
99
|
+
break;
|
|
100
|
+
|
|
101
|
+
case "PAUSE":
|
|
102
|
+
if (queueFilePath) {
|
|
103
|
+
await writeQueueCommand(queueFilePath, { type: "PAUSE" });
|
|
104
|
+
}
|
|
105
|
+
break;
|
|
106
|
+
|
|
107
|
+
case "ABORT":
|
|
108
|
+
// If a story is running, show confirmation
|
|
109
|
+
if (state.currentStory) {
|
|
110
|
+
setShowAbortConfirm(true);
|
|
111
|
+
} else if (queueFilePath) {
|
|
112
|
+
await writeQueueCommand(queueFilePath, { type: "ABORT" });
|
|
113
|
+
}
|
|
114
|
+
break;
|
|
115
|
+
|
|
116
|
+
case "SKIP":
|
|
117
|
+
if (queueFilePath) {
|
|
118
|
+
await writeQueueCommand(queueFilePath, { type: "SKIP", storyId: action.storyId });
|
|
119
|
+
}
|
|
120
|
+
break;
|
|
121
|
+
|
|
122
|
+
case "RETRY":
|
|
123
|
+
// TODO: Implement retry logic for last failed story
|
|
124
|
+
// This would require tracking the last failed story and resetting its status
|
|
125
|
+
break;
|
|
126
|
+
|
|
127
|
+
default:
|
|
128
|
+
break;
|
|
129
|
+
}
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
// Custom input handler for confirmation dialogs and PTY routing
|
|
133
|
+
useInput((input, key) => {
|
|
134
|
+
// Handle confirmation dialogs
|
|
135
|
+
if (showQuitConfirm || showAbortConfirm) {
|
|
136
|
+
const inputKey = input.toLowerCase();
|
|
137
|
+
if (inputKey === "y") {
|
|
138
|
+
if (showQuitConfirm) {
|
|
139
|
+
exit();
|
|
140
|
+
} else if (showAbortConfirm && queueFilePath) {
|
|
141
|
+
writeQueueCommand(queueFilePath, { type: "ABORT" });
|
|
142
|
+
setShowAbortConfirm(false);
|
|
143
|
+
}
|
|
144
|
+
} else if (inputKey === "n" || input === "\x1b") {
|
|
145
|
+
// n or Esc cancels
|
|
146
|
+
setShowQuitConfirm(false);
|
|
147
|
+
setShowAbortConfirm(false);
|
|
148
|
+
}
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// Route input to PTY when agent panel is focused
|
|
153
|
+
if (focus === PanelFocus.Agent && ptyHandle) {
|
|
154
|
+
// Ctrl+] escapes back to TUI controls (handled by useKeyboard)
|
|
155
|
+
if (key.ctrl && input === "]") {
|
|
156
|
+
return; // Let useKeyboard handle it
|
|
157
|
+
}
|
|
158
|
+
// All other input goes to PTY
|
|
159
|
+
ptyHandle.write(input);
|
|
160
|
+
}
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
// Wire keyboard hook (disabled during confirmation dialogs)
|
|
164
|
+
useKeyboard({
|
|
165
|
+
focus,
|
|
166
|
+
currentStory: state.currentStory,
|
|
167
|
+
onAction: handleKeyboardAction,
|
|
168
|
+
disabled: showQuitConfirm || showAbortConfirm,
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
const currentRouting = state.currentStory?.routing;
|
|
172
|
+
|
|
173
|
+
// Warn if terminal is too small
|
|
174
|
+
const isTooSmall = layout.width < MIN_TERMINAL_WIDTH;
|
|
175
|
+
|
|
176
|
+
return (
|
|
177
|
+
<Box flexDirection="column" height="100%">
|
|
178
|
+
{/* Header */}
|
|
179
|
+
<Box paddingX={1} borderStyle="single" borderBottom borderColor="cyan">
|
|
180
|
+
<Text bold color="cyan">
|
|
181
|
+
nax run — {feature}
|
|
182
|
+
</Text>
|
|
183
|
+
</Box>
|
|
184
|
+
|
|
185
|
+
{/* Warning for very small terminals */}
|
|
186
|
+
{isTooSmall && (
|
|
187
|
+
<Box paddingX={1} backgroundColor="yellow">
|
|
188
|
+
<Text color="black">
|
|
189
|
+
⚠️ Terminal too narrow ({layout.width} cols). Minimum {MIN_TERMINAL_WIDTH} cols recommended.
|
|
190
|
+
</Text>
|
|
191
|
+
</Box>
|
|
192
|
+
)}
|
|
193
|
+
|
|
194
|
+
{/* Main content area */}
|
|
195
|
+
<Box flexDirection={layout.mode === "single" ? "column" : "row"} flexGrow={1}>
|
|
196
|
+
{/* Stories panel */}
|
|
197
|
+
<StoriesPanel
|
|
198
|
+
stories={state.stories}
|
|
199
|
+
totalCost={state.totalCost}
|
|
200
|
+
elapsedMs={state.elapsedMs}
|
|
201
|
+
width={layout.mode === "single" ? layout.width : layout.storiesPanelWidth}
|
|
202
|
+
compact={layout.mode === "single"}
|
|
203
|
+
maxHeight={layout.mode === "single" ? 10 : undefined}
|
|
204
|
+
/>
|
|
205
|
+
|
|
206
|
+
{/* Agent panel */}
|
|
207
|
+
<AgentPanel focused={focus === PanelFocus.Agent} outputLines={agentOutputLines} />
|
|
208
|
+
</Box>
|
|
209
|
+
|
|
210
|
+
{/* Status bar */}
|
|
211
|
+
<StatusBar
|
|
212
|
+
currentStory={state.currentStory}
|
|
213
|
+
currentStage={state.currentStage}
|
|
214
|
+
modelTier={currentRouting?.modelTier}
|
|
215
|
+
testStrategy={currentRouting?.testStrategy}
|
|
216
|
+
/>
|
|
217
|
+
|
|
218
|
+
{/* Overlays */}
|
|
219
|
+
<HelpOverlay visible={showHelp} />
|
|
220
|
+
<CostOverlay visible={showCost} stories={state.stories} totalCost={state.totalCost} />
|
|
221
|
+
|
|
222
|
+
{/* Quit confirmation */}
|
|
223
|
+
{showQuitConfirm && (
|
|
224
|
+
<Box position="absolute" width="100%" height="100%" justifyContent="center" alignItems="center">
|
|
225
|
+
<Box
|
|
226
|
+
flexDirection="column"
|
|
227
|
+
borderStyle="double"
|
|
228
|
+
borderColor="yellow"
|
|
229
|
+
paddingX={2}
|
|
230
|
+
paddingY={1}
|
|
231
|
+
backgroundColor="black"
|
|
232
|
+
>
|
|
233
|
+
<Text color="yellow">⚠️ Story is running. Quit anyway?</Text>
|
|
234
|
+
<Box paddingTop={1}>
|
|
235
|
+
<Text dimColor>
|
|
236
|
+
Press <Text color="yellow">y</Text> to confirm, <Text color="yellow">n</Text> to cancel
|
|
237
|
+
</Text>
|
|
238
|
+
</Box>
|
|
239
|
+
</Box>
|
|
240
|
+
</Box>
|
|
241
|
+
)}
|
|
242
|
+
|
|
243
|
+
{/* Abort confirmation */}
|
|
244
|
+
{showAbortConfirm && (
|
|
245
|
+
<Box position="absolute" width="100%" height="100%" justifyContent="center" alignItems="center">
|
|
246
|
+
<Box
|
|
247
|
+
flexDirection="column"
|
|
248
|
+
borderStyle="double"
|
|
249
|
+
borderColor="red"
|
|
250
|
+
paddingX={2}
|
|
251
|
+
paddingY={1}
|
|
252
|
+
backgroundColor="black"
|
|
253
|
+
>
|
|
254
|
+
<Text color="red">⚠️ Story is running. Abort anyway?</Text>
|
|
255
|
+
<Box paddingTop={1}>
|
|
256
|
+
<Text dimColor>
|
|
257
|
+
Press <Text color="yellow">y</Text> to confirm, <Text color="yellow">n</Text> to cancel
|
|
258
|
+
</Text>
|
|
259
|
+
</Box>
|
|
260
|
+
</Box>
|
|
261
|
+
</Box>
|
|
262
|
+
)}
|
|
263
|
+
</Box>
|
|
264
|
+
);
|
|
265
|
+
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AgentPanel — displays PTY output from agent session.
|
|
3
|
+
*
|
|
4
|
+
* Renders a scrollable text buffer showing live agent output.
|
|
5
|
+
* When focused, displays a border highlight.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { Box, Text } from "ink";
|
|
9
|
+
import Spinner from "ink-spinner";
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Props for AgentPanel component.
|
|
13
|
+
*/
|
|
14
|
+
export interface AgentPanelProps {
|
|
15
|
+
/** Whether the panel is focused (receives keyboard input) */
|
|
16
|
+
focused?: boolean;
|
|
17
|
+
/** PTY output lines (buffered) */
|
|
18
|
+
outputLines?: string[];
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Maximum number of output lines to buffer.
|
|
23
|
+
*
|
|
24
|
+
* Prevents memory bloat from long-running agent sessions.
|
|
25
|
+
* Last 500 lines typically contain all relevant info for debugging.
|
|
26
|
+
*/
|
|
27
|
+
const MAX_OUTPUT_LINES = 500;
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* AgentPanel component.
|
|
31
|
+
*
|
|
32
|
+
* Displays PTY output from the agent session in a scrollable text buffer.
|
|
33
|
+
* Shows a border highlight when focused to indicate keyboard input routing.
|
|
34
|
+
*
|
|
35
|
+
* @example
|
|
36
|
+
* ```tsx
|
|
37
|
+
* const [outputLines, setOutputLines] = useState<string[]>([]);
|
|
38
|
+
*
|
|
39
|
+
* <AgentPanel
|
|
40
|
+
* focused={agentFocused}
|
|
41
|
+
* outputLines={outputLines}
|
|
42
|
+
* onData={(data) => setOutputLines(prev => [...prev, data])}
|
|
43
|
+
* />
|
|
44
|
+
* ```
|
|
45
|
+
*/
|
|
46
|
+
export function AgentPanel({ focused = false, outputLines = [] }: AgentPanelProps) {
|
|
47
|
+
const borderColor = focused ? "cyan" : "gray";
|
|
48
|
+
|
|
49
|
+
// Buffer output lines (last N lines only)
|
|
50
|
+
const bufferedLines = outputLines.length > MAX_OUTPUT_LINES ? outputLines.slice(-MAX_OUTPUT_LINES) : outputLines;
|
|
51
|
+
|
|
52
|
+
const hasOutput = bufferedLines.length > 0;
|
|
53
|
+
|
|
54
|
+
return (
|
|
55
|
+
<Box flexDirection="column" flexGrow={1} borderStyle="single" borderColor={borderColor}>
|
|
56
|
+
{/* Header */}
|
|
57
|
+
<Box paddingX={1} borderStyle="single" borderBottom borderColor={borderColor}>
|
|
58
|
+
<Text bold color={focused ? "cyan" : undefined}>
|
|
59
|
+
Agent {focused && <Text dimColor>(focused)</Text>}
|
|
60
|
+
</Text>
|
|
61
|
+
</Box>
|
|
62
|
+
|
|
63
|
+
{/* Output buffer */}
|
|
64
|
+
<Box flexDirection="column" paddingX={1} paddingY={1}>
|
|
65
|
+
{hasOutput ? (
|
|
66
|
+
bufferedLines.map((line, i) => <Text key={`line-${i}-${line.slice(0, 20)}`}>{line}</Text>)
|
|
67
|
+
) : (
|
|
68
|
+
<Text dimColor>
|
|
69
|
+
<Spinner type="dots" /> Waiting for agent...
|
|
70
|
+
</Text>
|
|
71
|
+
)}
|
|
72
|
+
</Box>
|
|
73
|
+
</Box>
|
|
74
|
+
);
|
|
75
|
+
}
|