@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,137 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* useLayout hook — terminal width detection and breakpoint management.
|
|
3
|
+
*
|
|
4
|
+
* Provides responsive layout breakpoints for the TUI:
|
|
5
|
+
* - < 80 cols: single column (stacked)
|
|
6
|
+
* - 80-140 cols: 2-column layout
|
|
7
|
+
* - > 140 cols: 2-column with wider agent panel
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { useEffect, useState } from "react";
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Layout mode based on terminal width.
|
|
14
|
+
*/
|
|
15
|
+
export type LayoutMode = "single" | "narrow" | "wide";
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Layout configuration for rendering.
|
|
19
|
+
*/
|
|
20
|
+
export interface LayoutConfig {
|
|
21
|
+
/** Current layout mode */
|
|
22
|
+
mode: LayoutMode;
|
|
23
|
+
/** Terminal width in columns */
|
|
24
|
+
width: number;
|
|
25
|
+
/** Terminal height in rows */
|
|
26
|
+
height: number;
|
|
27
|
+
/** Stories panel width (columns) */
|
|
28
|
+
storiesPanelWidth: number;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Hook for responsive terminal layout.
|
|
33
|
+
*
|
|
34
|
+
* Detects terminal size and provides breakpoint-based layout config.
|
|
35
|
+
* Handles SIGWINCH (terminal resize) events and cleans up listeners on unmount.
|
|
36
|
+
*
|
|
37
|
+
* Three breakpoints:
|
|
38
|
+
* - < 80 cols: single-column (stacked)
|
|
39
|
+
* - 80-140 cols: two-column (narrow)
|
|
40
|
+
* - > 140 cols: two-column (wide)
|
|
41
|
+
*
|
|
42
|
+
* @example
|
|
43
|
+
* ```tsx
|
|
44
|
+
* const layout = useLayout();
|
|
45
|
+
*
|
|
46
|
+
* return (
|
|
47
|
+
* <Box flexDirection={layout.mode === "single" ? "column" : "row"}>
|
|
48
|
+
* <StoriesPanel width={layout.storiesPanelWidth} compact={layout.mode === "single"} />
|
|
49
|
+
* <AgentPanel />
|
|
50
|
+
* </Box>
|
|
51
|
+
* );
|
|
52
|
+
* ```
|
|
53
|
+
*/
|
|
54
|
+
export function useLayout(): LayoutConfig {
|
|
55
|
+
const [layout, setLayout] = useState<LayoutConfig>(() => computeLayout());
|
|
56
|
+
|
|
57
|
+
useEffect(() => {
|
|
58
|
+
const handleResize = () => {
|
|
59
|
+
setLayout(computeLayout());
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
// Listen for terminal resize (SIGWINCH)
|
|
63
|
+
// Node.js/Bun emits 'resize' event on process.stdout when terminal size changes
|
|
64
|
+
process.stdout.on("resize", handleResize);
|
|
65
|
+
|
|
66
|
+
// Clean up listener on unmount to prevent memory leaks
|
|
67
|
+
return () => {
|
|
68
|
+
process.stdout.off("resize", handleResize);
|
|
69
|
+
};
|
|
70
|
+
}, []);
|
|
71
|
+
|
|
72
|
+
return layout;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Minimum terminal width for usable display.
|
|
77
|
+
*/
|
|
78
|
+
export const MIN_TERMINAL_WIDTH = 60;
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Breakpoint: below this width, use single-column layout.
|
|
82
|
+
*/
|
|
83
|
+
const BREAKPOINT_NARROW = 80;
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Breakpoint: above this width, use wide two-column layout.
|
|
87
|
+
*/
|
|
88
|
+
const BREAKPOINT_WIDE = 140;
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Stories panel width in narrow two-column mode.
|
|
92
|
+
*/
|
|
93
|
+
const PANEL_WIDTH_NARROW = 30;
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Stories panel width in wide two-column mode.
|
|
97
|
+
*/
|
|
98
|
+
const PANEL_WIDTH_WIDE = 35;
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Maximum stories to show in compact mode (single-column).
|
|
102
|
+
*/
|
|
103
|
+
export const COMPACT_MAX_VISIBLE_STORIES = 8;
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Maximum stories before scrolling kicks in (normal mode).
|
|
107
|
+
*/
|
|
108
|
+
export const MAX_VISIBLE_STORIES = 15;
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Compute layout configuration based on current terminal size.
|
|
112
|
+
*
|
|
113
|
+
* Breakpoints:
|
|
114
|
+
* - < 80 cols: single-column (stacked), compact mode
|
|
115
|
+
* - 80-140 cols: two-column, narrow stories panel (30 cols)
|
|
116
|
+
* - > 140 cols: two-column, wide stories panel (35 cols)
|
|
117
|
+
*/
|
|
118
|
+
function computeLayout(): LayoutConfig {
|
|
119
|
+
const width = process.stdout.columns ?? 80;
|
|
120
|
+
const height = process.stdout.rows ?? 24;
|
|
121
|
+
|
|
122
|
+
let mode: LayoutMode;
|
|
123
|
+
let storiesPanelWidth: number;
|
|
124
|
+
|
|
125
|
+
if (width < BREAKPOINT_NARROW) {
|
|
126
|
+
mode = "single";
|
|
127
|
+
storiesPanelWidth = width;
|
|
128
|
+
} else if (width < BREAKPOINT_WIDE) {
|
|
129
|
+
mode = "narrow";
|
|
130
|
+
storiesPanelWidth = PANEL_WIDTH_NARROW;
|
|
131
|
+
} else {
|
|
132
|
+
mode = "wide";
|
|
133
|
+
storiesPanelWidth = PANEL_WIDTH_WIDE;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
return { mode, width, height, storiesPanelWidth };
|
|
137
|
+
}
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* usePipelineEvents hook — subscribe to pipeline events and update TUI state.
|
|
3
|
+
*
|
|
4
|
+
* Listens to pipeline lifecycle events and updates story display states,
|
|
5
|
+
* cost accumulator, and current stage information.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { useEffect, useState } from "react";
|
|
9
|
+
import type { PipelineEventEmitter, RunSummary } from "../../pipeline/events";
|
|
10
|
+
import type { UserStory } from "../../prd/types";
|
|
11
|
+
import type { StoryDisplayState } from "../types";
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Pipeline state managed by the hook.
|
|
15
|
+
*/
|
|
16
|
+
export interface PipelineState {
|
|
17
|
+
/** Story display states */
|
|
18
|
+
stories: StoryDisplayState[];
|
|
19
|
+
/** Current story being executed */
|
|
20
|
+
currentStory?: UserStory;
|
|
21
|
+
/** Current pipeline stage */
|
|
22
|
+
currentStage?: string;
|
|
23
|
+
/** Total cost accumulated */
|
|
24
|
+
totalCost: number;
|
|
25
|
+
/** Elapsed time in milliseconds */
|
|
26
|
+
elapsedMs: number;
|
|
27
|
+
/** Run completion summary (if run finished) */
|
|
28
|
+
summary?: RunSummary;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Hook for subscribing to pipeline events and managing TUI state.
|
|
33
|
+
*
|
|
34
|
+
* Subscribes to pipeline lifecycle events (story:start, story:complete,
|
|
35
|
+
* story:escalate, stage:enter, run:complete) and updates story display
|
|
36
|
+
* states, cost accumulator, elapsed time, and current stage in real-time.
|
|
37
|
+
*
|
|
38
|
+
* The elapsed timer only runs while a story is active to avoid unnecessary
|
|
39
|
+
* re-renders during idle periods.
|
|
40
|
+
*
|
|
41
|
+
* @param events - Pipeline event emitter (from pipeline runner)
|
|
42
|
+
* @param initialStories - Initial story list from PRD
|
|
43
|
+
* @returns Pipeline state: stories (with status/cost), totalCost, elapsedMs, currentStory, currentStage, summary
|
|
44
|
+
*
|
|
45
|
+
* @example
|
|
46
|
+
* ```tsx
|
|
47
|
+
* const emitter = new PipelineEventEmitter();
|
|
48
|
+
* const state = usePipelineEvents(emitter, prd.userStories);
|
|
49
|
+
*
|
|
50
|
+
* // State automatically updates as pipeline emits events
|
|
51
|
+
* return (
|
|
52
|
+
* <>
|
|
53
|
+
* <StoriesPanel stories={state.stories} totalCost={state.totalCost} />
|
|
54
|
+
* <StatusBar currentStory={state.currentStory} currentStage={state.currentStage} />
|
|
55
|
+
* </>
|
|
56
|
+
* );
|
|
57
|
+
* ```
|
|
58
|
+
*/
|
|
59
|
+
export function usePipelineEvents(events: PipelineEventEmitter, initialStories: UserStory[]): PipelineState {
|
|
60
|
+
const [state, setState] = useState<PipelineState>(() => ({
|
|
61
|
+
stories: initialStories.map((story) => ({
|
|
62
|
+
story,
|
|
63
|
+
status: story.passes ? "passed" : "pending",
|
|
64
|
+
routing: story.routing,
|
|
65
|
+
cost: 0,
|
|
66
|
+
})),
|
|
67
|
+
totalCost: 0,
|
|
68
|
+
elapsedMs: 0,
|
|
69
|
+
}));
|
|
70
|
+
|
|
71
|
+
const startTime = Date.now();
|
|
72
|
+
|
|
73
|
+
useEffect(() => {
|
|
74
|
+
// Elapsed timer — only runs when a story is active
|
|
75
|
+
let timer: ReturnType<typeof setInterval> | null = null;
|
|
76
|
+
|
|
77
|
+
const startTimer = () => {
|
|
78
|
+
if (!timer) {
|
|
79
|
+
timer = setInterval(() => {
|
|
80
|
+
setState((prev) => ({
|
|
81
|
+
...prev,
|
|
82
|
+
elapsedMs: Date.now() - startTime,
|
|
83
|
+
}));
|
|
84
|
+
}, 1000);
|
|
85
|
+
}
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
const stopTimer = () => {
|
|
89
|
+
if (timer) {
|
|
90
|
+
clearInterval(timer);
|
|
91
|
+
timer = null;
|
|
92
|
+
}
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
// story:start — Mark story as running
|
|
96
|
+
const onStoryStart = (story: UserStory) => {
|
|
97
|
+
startTimer();
|
|
98
|
+
setState((prev) => ({
|
|
99
|
+
...prev,
|
|
100
|
+
currentStory: story,
|
|
101
|
+
stories: prev.stories.map((s) => (s.story.id === story.id ? { ...s, status: "running" as const } : s)),
|
|
102
|
+
}));
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
// story:complete — Mark story as complete (passed/failed/skipped)
|
|
106
|
+
const onStoryComplete = (story: UserStory, result: { action: string; cost?: number }) => {
|
|
107
|
+
stopTimer();
|
|
108
|
+
setState((prev) => {
|
|
109
|
+
const newStories = prev.stories.map((s) => {
|
|
110
|
+
if (s.story.id === story.id) {
|
|
111
|
+
let status: StoryDisplayState["status"] = "pending";
|
|
112
|
+
if (result.action === "continue") {
|
|
113
|
+
status = "passed";
|
|
114
|
+
} else if (result.action === "fail") {
|
|
115
|
+
status = "failed";
|
|
116
|
+
} else if (result.action === "skip") {
|
|
117
|
+
status = "skipped";
|
|
118
|
+
} else if (result.action === "pause") {
|
|
119
|
+
status = "paused";
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// Accumulate cost from result
|
|
123
|
+
const storyCost = (s.cost || 0) + (result.cost || 0);
|
|
124
|
+
return { ...s, status, cost: storyCost };
|
|
125
|
+
}
|
|
126
|
+
return s;
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
// Update total cost accumulator
|
|
130
|
+
const totalCost = newStories.reduce((sum, s) => sum + (s.cost || 0), 0);
|
|
131
|
+
|
|
132
|
+
return {
|
|
133
|
+
...prev,
|
|
134
|
+
stories: newStories,
|
|
135
|
+
currentStory: undefined,
|
|
136
|
+
totalCost,
|
|
137
|
+
};
|
|
138
|
+
});
|
|
139
|
+
};
|
|
140
|
+
|
|
141
|
+
// story:escalate — Mark story as retrying
|
|
142
|
+
const onStoryEscalate = (story: UserStory) => {
|
|
143
|
+
setState((prev) => ({
|
|
144
|
+
...prev,
|
|
145
|
+
stories: prev.stories.map((s) => (s.story.id === story.id ? { ...s, status: "retrying" as const } : s)),
|
|
146
|
+
}));
|
|
147
|
+
};
|
|
148
|
+
|
|
149
|
+
// stage:enter — Update current stage
|
|
150
|
+
const onStageEnter = (stage: string) => {
|
|
151
|
+
setState((prev) => ({
|
|
152
|
+
...prev,
|
|
153
|
+
currentStage: stage,
|
|
154
|
+
}));
|
|
155
|
+
};
|
|
156
|
+
|
|
157
|
+
// run:complete — Update final summary
|
|
158
|
+
const onRunComplete = (summary: RunSummary) => {
|
|
159
|
+
setState((prev) => ({
|
|
160
|
+
...prev,
|
|
161
|
+
totalCost: summary.totalCost,
|
|
162
|
+
summary,
|
|
163
|
+
}));
|
|
164
|
+
};
|
|
165
|
+
|
|
166
|
+
events.on("story:start", onStoryStart);
|
|
167
|
+
events.on("story:complete", onStoryComplete);
|
|
168
|
+
events.on("story:escalate", onStoryEscalate);
|
|
169
|
+
events.on("stage:enter", onStageEnter);
|
|
170
|
+
events.on("run:complete", onRunComplete);
|
|
171
|
+
|
|
172
|
+
return () => {
|
|
173
|
+
stopTimer();
|
|
174
|
+
events.off("story:start", onStoryStart);
|
|
175
|
+
events.off("story:complete", onStoryComplete);
|
|
176
|
+
events.off("story:escalate", onStoryEscalate);
|
|
177
|
+
events.off("stage:enter", onStageEnter);
|
|
178
|
+
events.off("run:complete", onRunComplete);
|
|
179
|
+
};
|
|
180
|
+
}, [events, startTime]);
|
|
181
|
+
|
|
182
|
+
return state;
|
|
183
|
+
}
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* usePty hook — manages node-pty lifecycle for agent PTY sessions.
|
|
3
|
+
*
|
|
4
|
+
* Spawns, buffers output, handles resize, and cleanup for PTY processes.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type * as pty from "node-pty";
|
|
8
|
+
import { useCallback, useEffect, useState } from "react";
|
|
9
|
+
import type { PtyHandle } from "../../agents/types";
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Options for spawning PTY process.
|
|
13
|
+
*/
|
|
14
|
+
export interface PtySpawnOptions {
|
|
15
|
+
/** Command to execute (e.g., "claude") */
|
|
16
|
+
command: string;
|
|
17
|
+
/** Command arguments */
|
|
18
|
+
args?: string[];
|
|
19
|
+
/** Working directory */
|
|
20
|
+
cwd?: string;
|
|
21
|
+
/** Environment variables */
|
|
22
|
+
env?: Record<string, string>;
|
|
23
|
+
/** Terminal columns (default: 80) */
|
|
24
|
+
cols?: number;
|
|
25
|
+
/** Terminal rows (default: 24) */
|
|
26
|
+
rows?: number;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* PTY state managed by the hook.
|
|
31
|
+
*/
|
|
32
|
+
export interface PtyState {
|
|
33
|
+
/** Output lines buffered from PTY */
|
|
34
|
+
outputLines: string[];
|
|
35
|
+
/** Whether PTY process is running */
|
|
36
|
+
isRunning: boolean;
|
|
37
|
+
/** Exit code (if process exited) */
|
|
38
|
+
exitCode?: number;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Maximum number of output lines to buffer.
|
|
43
|
+
*
|
|
44
|
+
* Prevents memory bloat from long-running PTY sessions.
|
|
45
|
+
*/
|
|
46
|
+
const MAX_PTY_BUFFER_LINES = 500;
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Maximum length per line in characters.
|
|
50
|
+
*
|
|
51
|
+
* Prevents memory exhaustion from single extremely long lines.
|
|
52
|
+
*/
|
|
53
|
+
const MAX_LINE_LENGTH = 10_000;
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Hook for managing PTY lifecycle.
|
|
57
|
+
*
|
|
58
|
+
* Spawns a PTY process, buffers output, and provides a handle for input/resize/kill.
|
|
59
|
+
*
|
|
60
|
+
* @param options - PTY spawn options (null to skip spawning)
|
|
61
|
+
* @returns PTY state and handle
|
|
62
|
+
*
|
|
63
|
+
* @example
|
|
64
|
+
* ```tsx
|
|
65
|
+
* const { outputLines, isRunning, handle } = usePty({
|
|
66
|
+
* command: "claude",
|
|
67
|
+
* args: ["--model", "claude-sonnet-4.5"],
|
|
68
|
+
* cwd: "/project",
|
|
69
|
+
* });
|
|
70
|
+
*
|
|
71
|
+
* // Write input to PTY
|
|
72
|
+
* handle?.write("y\n");
|
|
73
|
+
*
|
|
74
|
+
* // Render output
|
|
75
|
+
* <AgentPanel outputLines={outputLines} />
|
|
76
|
+
* ```
|
|
77
|
+
*/
|
|
78
|
+
export function usePty(options: PtySpawnOptions | null): PtyState & { handle: PtyHandle | null } {
|
|
79
|
+
const [state, setState] = useState<PtyState>(() => ({
|
|
80
|
+
outputLines: [],
|
|
81
|
+
isRunning: false,
|
|
82
|
+
}));
|
|
83
|
+
|
|
84
|
+
const [handle, setHandle] = useState<PtyHandle | null>(null);
|
|
85
|
+
const [ptyProcess, setPtyProcess] = useState<pty.IPty | null>(null);
|
|
86
|
+
|
|
87
|
+
// Spawn PTY process
|
|
88
|
+
useEffect(() => {
|
|
89
|
+
if (!options) {
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// Lazy load node-pty (only when needed)
|
|
94
|
+
let nodePty: typeof pty;
|
|
95
|
+
try {
|
|
96
|
+
nodePty = require("node-pty");
|
|
97
|
+
} catch (error) {
|
|
98
|
+
console.error("[usePty] node-pty not available:", error);
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const ptyProc = nodePty.spawn(options.command, options.args || [], {
|
|
103
|
+
name: "xterm-256color",
|
|
104
|
+
cols: options.cols || 80,
|
|
105
|
+
rows: options.rows || 24,
|
|
106
|
+
cwd: options.cwd || process.cwd(),
|
|
107
|
+
env: {
|
|
108
|
+
...process.env,
|
|
109
|
+
...options.env,
|
|
110
|
+
},
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
setPtyProcess(ptyProc);
|
|
114
|
+
setState((prev) => ({ ...prev, isRunning: true }));
|
|
115
|
+
|
|
116
|
+
// Buffer output line-by-line
|
|
117
|
+
let currentLine = "";
|
|
118
|
+
ptyProc.onData((data) => {
|
|
119
|
+
const lines = (currentLine + data).split("\n");
|
|
120
|
+
currentLine = lines.pop() || "";
|
|
121
|
+
|
|
122
|
+
// Truncate incomplete line if too long
|
|
123
|
+
if (currentLine.length > MAX_LINE_LENGTH) {
|
|
124
|
+
currentLine = currentLine.slice(-MAX_LINE_LENGTH);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
if (lines.length > 0) {
|
|
128
|
+
// Truncate each complete line
|
|
129
|
+
const truncatedLines = lines.map((line) =>
|
|
130
|
+
line.length > MAX_LINE_LENGTH ? `${line.slice(0, MAX_LINE_LENGTH)}…` : line,
|
|
131
|
+
);
|
|
132
|
+
|
|
133
|
+
setState((prev) => {
|
|
134
|
+
const newLines = [...prev.outputLines, ...truncatedLines];
|
|
135
|
+
// Keep only last N lines
|
|
136
|
+
const trimmed = newLines.length > MAX_PTY_BUFFER_LINES ? newLines.slice(-MAX_PTY_BUFFER_LINES) : newLines;
|
|
137
|
+
return { ...prev, outputLines: trimmed };
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
// Handle exit
|
|
143
|
+
ptyProc.onExit((event) => {
|
|
144
|
+
setState((prev) => ({
|
|
145
|
+
...prev,
|
|
146
|
+
isRunning: false,
|
|
147
|
+
exitCode: event.exitCode,
|
|
148
|
+
}));
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
// Create handle
|
|
152
|
+
const ptyHandle: PtyHandle = {
|
|
153
|
+
write: (data: string) => ptyProc.write(data),
|
|
154
|
+
resize: (cols: number, rows: number) => ptyProc.resize(cols, rows),
|
|
155
|
+
kill: () => ptyProc.kill(),
|
|
156
|
+
pid: ptyProc.pid,
|
|
157
|
+
};
|
|
158
|
+
|
|
159
|
+
setHandle(ptyHandle);
|
|
160
|
+
|
|
161
|
+
// Cleanup on unmount
|
|
162
|
+
return () => {
|
|
163
|
+
if (ptyProc) {
|
|
164
|
+
ptyProc.kill();
|
|
165
|
+
}
|
|
166
|
+
};
|
|
167
|
+
}, [options]);
|
|
168
|
+
|
|
169
|
+
// Handle terminal resize
|
|
170
|
+
const handleResize = useCallback(
|
|
171
|
+
(cols: number, rows: number) => {
|
|
172
|
+
if (ptyProcess) {
|
|
173
|
+
ptyProcess.resize(cols, rows);
|
|
174
|
+
}
|
|
175
|
+
},
|
|
176
|
+
[ptyProcess],
|
|
177
|
+
);
|
|
178
|
+
|
|
179
|
+
useEffect(() => {
|
|
180
|
+
const onResize = () => {
|
|
181
|
+
const cols = process.stdout.columns ?? 80;
|
|
182
|
+
const rows = process.stdout.rows ?? 24;
|
|
183
|
+
handleResize(cols, rows);
|
|
184
|
+
};
|
|
185
|
+
|
|
186
|
+
process.stdout.on("resize", onResize);
|
|
187
|
+
|
|
188
|
+
return () => {
|
|
189
|
+
process.stdout.off("resize", onResize);
|
|
190
|
+
};
|
|
191
|
+
}, [handleResize]);
|
|
192
|
+
|
|
193
|
+
return { ...state, handle };
|
|
194
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TUI entry point — renders the Ink-based terminal user interface.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { render } from "ink";
|
|
6
|
+
import { App } from "./App";
|
|
7
|
+
import type { TuiProps } from "./types";
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Render the TUI.
|
|
11
|
+
*
|
|
12
|
+
* Initializes Ink and renders the root App component.
|
|
13
|
+
*
|
|
14
|
+
* @param props - TUI props (feature, stories, events)
|
|
15
|
+
* @returns Ink instance (for cleanup/unmounting)
|
|
16
|
+
*
|
|
17
|
+
* @example
|
|
18
|
+
* ```ts
|
|
19
|
+
* const emitter = new PipelineEventEmitter();
|
|
20
|
+
*
|
|
21
|
+
* const instance = renderTui({
|
|
22
|
+
* feature: "auth-system",
|
|
23
|
+
* stories: initialStories,
|
|
24
|
+
* totalCost: 0,
|
|
25
|
+
* elapsedMs: 0,
|
|
26
|
+
* events: emitter,
|
|
27
|
+
* });
|
|
28
|
+
*
|
|
29
|
+
* // Later: cleanup
|
|
30
|
+
* instance.unmount();
|
|
31
|
+
* ```
|
|
32
|
+
*/
|
|
33
|
+
export function renderTui(props: TuiProps) {
|
|
34
|
+
return render(<App {...props} />);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export type { TuiProps, StoryDisplayState, PanelFocus, PtySpawnOptions } from "./types";
|
|
38
|
+
export { PipelineEventEmitter } from "../pipeline/events";
|
package/src/tui/types.ts
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TUI-specific types for terminal user interface components.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import type { PipelineEventEmitter } from "../pipeline/events";
|
|
6
|
+
import type { StoryRouting, UserStory } from "../prd/types";
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Story display state for the TUI.
|
|
10
|
+
*
|
|
11
|
+
* Extends UserStory with runtime state for visual rendering.
|
|
12
|
+
*/
|
|
13
|
+
export interface StoryDisplayState {
|
|
14
|
+
/** Story data from PRD */
|
|
15
|
+
story: UserStory;
|
|
16
|
+
/** Current status for display */
|
|
17
|
+
status: "pending" | "running" | "passed" | "failed" | "skipped" | "retrying" | "paused";
|
|
18
|
+
/** Routing result (if classified) */
|
|
19
|
+
routing?: StoryRouting;
|
|
20
|
+
/** Cost incurred for this story */
|
|
21
|
+
cost?: number;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Panel focus state.
|
|
26
|
+
*
|
|
27
|
+
* Determines which panel receives keyboard input.
|
|
28
|
+
*/
|
|
29
|
+
export enum PanelFocus {
|
|
30
|
+
/** Stories panel is focused (default) */
|
|
31
|
+
Stories = "stories",
|
|
32
|
+
/** Agent panel is focused (input routed to PTY) */
|
|
33
|
+
Agent = "agent",
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* PTY spawn options for agent integration.
|
|
38
|
+
*/
|
|
39
|
+
export interface PtySpawnOptions {
|
|
40
|
+
/** Command to execute (e.g., "claude") */
|
|
41
|
+
command: string;
|
|
42
|
+
/** Command arguments */
|
|
43
|
+
args?: string[];
|
|
44
|
+
/** Working directory */
|
|
45
|
+
cwd?: string;
|
|
46
|
+
/** Environment variables */
|
|
47
|
+
env?: Record<string, string>;
|
|
48
|
+
/** Terminal columns (default: 80) */
|
|
49
|
+
cols?: number;
|
|
50
|
+
/** Terminal rows (default: 24) */
|
|
51
|
+
rows?: number;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Props for the root TUI component.
|
|
56
|
+
*/
|
|
57
|
+
export interface TuiProps {
|
|
58
|
+
/** Feature name */
|
|
59
|
+
feature: string;
|
|
60
|
+
/** All stories to display */
|
|
61
|
+
stories: StoryDisplayState[];
|
|
62
|
+
/** Current story being executed */
|
|
63
|
+
currentStory?: UserStory;
|
|
64
|
+
/** Current pipeline stage */
|
|
65
|
+
currentStage?: string;
|
|
66
|
+
/** Total cost accumulated */
|
|
67
|
+
totalCost: number;
|
|
68
|
+
/** Elapsed time in milliseconds */
|
|
69
|
+
elapsedMs: number;
|
|
70
|
+
/** Pipeline event emitter for live updates */
|
|
71
|
+
events: PipelineEventEmitter;
|
|
72
|
+
/** Path to queue file for writing commands (optional) */
|
|
73
|
+
queueFilePath?: string;
|
|
74
|
+
/** PTY spawn options for agent session (optional) */
|
|
75
|
+
ptyOptions?: PtySpawnOptions | null;
|
|
76
|
+
}
|
package/src/utils/git.ts
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Git utility functions
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { spawn } from "bun";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Capture current git HEAD ref.
|
|
9
|
+
*
|
|
10
|
+
* Returns the current HEAD commit hash, or undefined if git is not available
|
|
11
|
+
* or the command fails (e.g., not in a git repo).
|
|
12
|
+
*
|
|
13
|
+
* @param workdir - Working directory to run git command in
|
|
14
|
+
* @returns Git HEAD ref or undefined on failure
|
|
15
|
+
*
|
|
16
|
+
* @example
|
|
17
|
+
* ```typescript
|
|
18
|
+
* const ref = await captureGitRef("/path/to/repo");
|
|
19
|
+
* if (ref) {
|
|
20
|
+
* console.log(`Current HEAD: ${ref}`);
|
|
21
|
+
* }
|
|
22
|
+
* ```
|
|
23
|
+
*/
|
|
24
|
+
export async function captureGitRef(workdir: string): Promise<string | undefined> {
|
|
25
|
+
try {
|
|
26
|
+
const proc = spawn({
|
|
27
|
+
cmd: ["git", "rev-parse", "HEAD"],
|
|
28
|
+
cwd: workdir,
|
|
29
|
+
stdout: "pipe",
|
|
30
|
+
stderr: "pipe",
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
const exitCode = await proc.exited;
|
|
34
|
+
if (exitCode !== 0) return undefined;
|
|
35
|
+
|
|
36
|
+
const stdout = await new Response(proc.stdout).text();
|
|
37
|
+
return stdout.trim() || undefined;
|
|
38
|
+
} catch {
|
|
39
|
+
return undefined;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Check if a story ID appears in recent git commit messages.
|
|
45
|
+
*
|
|
46
|
+
* Searches the last 20 commits for commit messages containing the story ID.
|
|
47
|
+
* Used for state reconciliation: if a failed story has commits in git history,
|
|
48
|
+
* it means the story was partially completed and should be marked as passed.
|
|
49
|
+
*
|
|
50
|
+
* @param workdir - Working directory to run git command in
|
|
51
|
+
* @param storyId - Story ID to search for (e.g., "US-001")
|
|
52
|
+
* @param maxCommits - Maximum number of commits to search (default: 20)
|
|
53
|
+
* @returns true if story ID found in commit messages, false otherwise
|
|
54
|
+
*
|
|
55
|
+
* @example
|
|
56
|
+
* ```typescript
|
|
57
|
+
* const hasCommits = await hasCommitsForStory("/path/to/repo", "US-001");
|
|
58
|
+
* if (hasCommits) {
|
|
59
|
+
* console.log("Story US-001 has commits in git history");
|
|
60
|
+
* }
|
|
61
|
+
* ```
|
|
62
|
+
*/
|
|
63
|
+
export async function hasCommitsForStory(workdir: string, storyId: string, maxCommits = 20): Promise<boolean> {
|
|
64
|
+
try {
|
|
65
|
+
const proc = spawn({
|
|
66
|
+
cmd: ["git", "log", `-${maxCommits}`, "--oneline", "--grep", storyId],
|
|
67
|
+
cwd: workdir,
|
|
68
|
+
stdout: "pipe",
|
|
69
|
+
stderr: "pipe",
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
const exitCode = await proc.exited;
|
|
73
|
+
if (exitCode !== 0) return false;
|
|
74
|
+
|
|
75
|
+
const stdout = await new Response(proc.stdout).text();
|
|
76
|
+
const commits = stdout.trim();
|
|
77
|
+
|
|
78
|
+
// If any commits found, return true
|
|
79
|
+
return commits.length > 0;
|
|
80
|
+
} catch {
|
|
81
|
+
return false;
|
|
82
|
+
}
|
|
83
|
+
}
|