@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,388 @@
|
|
|
1
|
+
/** Sequential Story Executor — main execution loop for story pipeline. */
|
|
2
|
+
|
|
3
|
+
import type { NaxConfig } from "../config";
|
|
4
|
+
import { type LoadedHooksConfig, fireHook } from "../hooks";
|
|
5
|
+
import type { InteractionChain } from "../interaction/chain";
|
|
6
|
+
import { getSafeLogger } from "../logger";
|
|
7
|
+
import type { StoryMetrics } from "../metrics";
|
|
8
|
+
import type { PipelineEventEmitter } from "../pipeline/events";
|
|
9
|
+
import { runPipeline } from "../pipeline/runner";
|
|
10
|
+
import { defaultPipeline } from "../pipeline/stages";
|
|
11
|
+
import type { PipelineContext, RoutingResult } from "../pipeline/types";
|
|
12
|
+
import type { PluginRegistry } from "../plugins";
|
|
13
|
+
import { generateHumanHaltSummary, getNextStory, isComplete, isStalled, loadPRD } from "../prd";
|
|
14
|
+
import type { PRD, UserStory } from "../prd/types";
|
|
15
|
+
import { routeTask } from "../routing";
|
|
16
|
+
import { captureGitRef } from "../utils/git";
|
|
17
|
+
import type { StoryBatch } from "./batching";
|
|
18
|
+
import { startHeartbeat, stopHeartbeat, writeExitSummary } from "./crash-recovery";
|
|
19
|
+
import { preIterationTierCheck } from "./escalation";
|
|
20
|
+
import { hookCtx } from "./helpers";
|
|
21
|
+
import {
|
|
22
|
+
applyCachedRouting,
|
|
23
|
+
handleDryRun,
|
|
24
|
+
handlePipelineFailure,
|
|
25
|
+
handlePipelineSuccess,
|
|
26
|
+
} from "./pipeline-result-handler";
|
|
27
|
+
import type { StatusWriter } from "./status-writer";
|
|
28
|
+
|
|
29
|
+
export interface SequentialExecutionContext {
|
|
30
|
+
prdPath: string;
|
|
31
|
+
workdir: string;
|
|
32
|
+
config: NaxConfig;
|
|
33
|
+
hooks: LoadedHooksConfig;
|
|
34
|
+
feature: string;
|
|
35
|
+
featureDir?: string;
|
|
36
|
+
dryRun: boolean;
|
|
37
|
+
useBatch: boolean;
|
|
38
|
+
pluginRegistry: PluginRegistry;
|
|
39
|
+
eventEmitter?: PipelineEventEmitter;
|
|
40
|
+
statusWriter: StatusWriter;
|
|
41
|
+
logFilePath?: string;
|
|
42
|
+
runId: string;
|
|
43
|
+
startTime: number;
|
|
44
|
+
batchPlan: StoryBatch[];
|
|
45
|
+
interactionChain?: InteractionChain | null;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export interface SequentialExecutionResult {
|
|
49
|
+
prd: PRD;
|
|
50
|
+
iterations: number;
|
|
51
|
+
storiesCompleted: number;
|
|
52
|
+
totalCost: number;
|
|
53
|
+
allStoryMetrics: StoryMetrics[];
|
|
54
|
+
timeoutRetryCountMap: Map<string, number>;
|
|
55
|
+
exitReason: "completed" | "cost-limit" | "max-iterations" | "stalled" | "no-stories";
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Execute stories sequentially through the pipeline
|
|
60
|
+
*/
|
|
61
|
+
export async function executeSequential(
|
|
62
|
+
ctx: SequentialExecutionContext,
|
|
63
|
+
initialPrd: PRD,
|
|
64
|
+
): Promise<SequentialExecutionResult> {
|
|
65
|
+
const logger = getSafeLogger();
|
|
66
|
+
let prd = initialPrd;
|
|
67
|
+
let prdDirty = false;
|
|
68
|
+
let iterations = 0;
|
|
69
|
+
let storiesCompleted = 0;
|
|
70
|
+
let totalCost = 0;
|
|
71
|
+
const allStoryMetrics: StoryMetrics[] = [];
|
|
72
|
+
const timeoutRetryCountMap = new Map<string, number>();
|
|
73
|
+
let currentBatchIndex = 0;
|
|
74
|
+
let lastStoryId: string | null = null;
|
|
75
|
+
|
|
76
|
+
const buildResult = (exitReason: SequentialExecutionResult["exitReason"]): SequentialExecutionResult => ({
|
|
77
|
+
prd,
|
|
78
|
+
iterations,
|
|
79
|
+
storiesCompleted,
|
|
80
|
+
totalCost,
|
|
81
|
+
allStoryMetrics,
|
|
82
|
+
timeoutRetryCountMap,
|
|
83
|
+
exitReason,
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
startHeartbeat(
|
|
87
|
+
ctx.statusWriter,
|
|
88
|
+
() => totalCost,
|
|
89
|
+
() => iterations,
|
|
90
|
+
ctx.logFilePath,
|
|
91
|
+
);
|
|
92
|
+
|
|
93
|
+
try {
|
|
94
|
+
// Main execution loop
|
|
95
|
+
while (iterations < ctx.config.execution.maxIterations) {
|
|
96
|
+
iterations++;
|
|
97
|
+
|
|
98
|
+
// MEM-1: Check memory usage (warn if > 1GB heap)
|
|
99
|
+
const memUsage = process.memoryUsage();
|
|
100
|
+
const heapUsedMB = Math.round(memUsage.heapUsed / 1024 / 1024);
|
|
101
|
+
if (heapUsedMB > 1024) {
|
|
102
|
+
logger?.warn("execution", "High memory usage detected", {
|
|
103
|
+
heapUsedMB,
|
|
104
|
+
suggestion: "Consider pausing (echo PAUSE > .queue.txt) if this continues to grow",
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// Reload PRD only if dirty (modified since last load)
|
|
109
|
+
if (prdDirty) {
|
|
110
|
+
prd = await loadPRD(ctx.prdPath);
|
|
111
|
+
prdDirty = false;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// Check completion
|
|
115
|
+
if (isComplete(prd)) {
|
|
116
|
+
logger?.info("execution", "All stories complete!", {
|
|
117
|
+
feature: ctx.feature,
|
|
118
|
+
totalCost,
|
|
119
|
+
});
|
|
120
|
+
await fireHook(
|
|
121
|
+
ctx.hooks,
|
|
122
|
+
"on-complete",
|
|
123
|
+
hookCtx(ctx.feature, { status: "complete", cost: totalCost }),
|
|
124
|
+
ctx.workdir,
|
|
125
|
+
);
|
|
126
|
+
return buildResult("completed");
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// Get next story/batch
|
|
130
|
+
let storiesToExecute: UserStory[];
|
|
131
|
+
let isBatchExecution: boolean;
|
|
132
|
+
let story: UserStory;
|
|
133
|
+
let routing: ReturnType<typeof routeTask>;
|
|
134
|
+
|
|
135
|
+
if (ctx.useBatch && currentBatchIndex < ctx.batchPlan.length) {
|
|
136
|
+
// Get next batch from precomputed plan
|
|
137
|
+
const batch = ctx.batchPlan[currentBatchIndex];
|
|
138
|
+
currentBatchIndex++;
|
|
139
|
+
|
|
140
|
+
// Filter out already-completed stories
|
|
141
|
+
storiesToExecute = batch.stories.filter(
|
|
142
|
+
(s) =>
|
|
143
|
+
!s.passes &&
|
|
144
|
+
s.status !== "passed" &&
|
|
145
|
+
s.status !== "skipped" &&
|
|
146
|
+
s.status !== "blocked" &&
|
|
147
|
+
s.status !== "failed" &&
|
|
148
|
+
s.status !== "paused",
|
|
149
|
+
);
|
|
150
|
+
isBatchExecution = batch.isBatch && storiesToExecute.length > 1;
|
|
151
|
+
|
|
152
|
+
if (storiesToExecute.length === 0) {
|
|
153
|
+
// All stories in this batch already completed, move to next batch
|
|
154
|
+
continue;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// Use first story as the primary story for routing/context
|
|
158
|
+
story = storiesToExecute[0];
|
|
159
|
+
routing = routeTask(story.title, story.description, story.acceptanceCriteria, story.tags, ctx.config);
|
|
160
|
+
routing = applyCachedRouting(routing, story, ctx.config);
|
|
161
|
+
} else {
|
|
162
|
+
// Fallback to single-story mode (when batching disabled or batch plan exhausted)
|
|
163
|
+
const nextStory = getNextStory(prd, lastStoryId, ctx.config.execution.rectification?.maxRetries ?? 2);
|
|
164
|
+
if (!nextStory) {
|
|
165
|
+
logger?.warn("execution", "No actionable stories (check dependencies)");
|
|
166
|
+
return buildResult("no-stories");
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
story = nextStory;
|
|
170
|
+
lastStoryId = story.id;
|
|
171
|
+
storiesToExecute = [story];
|
|
172
|
+
isBatchExecution = false;
|
|
173
|
+
|
|
174
|
+
routing = routeTask(story.title, story.description, story.acceptanceCriteria, story.tags, ctx.config);
|
|
175
|
+
routing = applyCachedRouting(routing, story, ctx.config);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// Pre-iteration tier escalation check
|
|
179
|
+
const tierCheckResult = await preIterationTierCheck(
|
|
180
|
+
story,
|
|
181
|
+
routing,
|
|
182
|
+
ctx.config,
|
|
183
|
+
prd,
|
|
184
|
+
ctx.prdPath,
|
|
185
|
+
ctx.featureDir,
|
|
186
|
+
ctx.hooks,
|
|
187
|
+
ctx.feature,
|
|
188
|
+
totalCost,
|
|
189
|
+
ctx.workdir,
|
|
190
|
+
);
|
|
191
|
+
|
|
192
|
+
if (tierCheckResult.shouldSkipIteration) {
|
|
193
|
+
prd = tierCheckResult.prd;
|
|
194
|
+
prdDirty = tierCheckResult.prdDirty;
|
|
195
|
+
continue;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// Check cost limit
|
|
199
|
+
if (totalCost >= ctx.config.execution.costLimit) {
|
|
200
|
+
logger?.warn("execution", "Cost limit reached, pausing", {
|
|
201
|
+
totalCost,
|
|
202
|
+
costLimit: ctx.config.execution.costLimit,
|
|
203
|
+
});
|
|
204
|
+
await fireHook(
|
|
205
|
+
ctx.hooks,
|
|
206
|
+
"on-pause",
|
|
207
|
+
hookCtx(ctx.feature, {
|
|
208
|
+
storyId: story.id,
|
|
209
|
+
reason: `Cost limit reached: $${totalCost.toFixed(2)}`,
|
|
210
|
+
cost: totalCost,
|
|
211
|
+
}),
|
|
212
|
+
ctx.workdir,
|
|
213
|
+
);
|
|
214
|
+
return buildResult("cost-limit");
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
logger?.info("iteration.start", `Starting iteration ${iterations}`, {
|
|
218
|
+
iteration: iterations,
|
|
219
|
+
storyId: story.id,
|
|
220
|
+
storyTitle: story.title,
|
|
221
|
+
isBatch: isBatchExecution,
|
|
222
|
+
batchSize: isBatchExecution ? storiesToExecute.length : 1,
|
|
223
|
+
modelTier: routing.modelTier,
|
|
224
|
+
complexity: routing.complexity,
|
|
225
|
+
...(isBatchExecution && { batchStoryIds: storiesToExecute.map((s) => s.id) }),
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
// Fire story-start hook
|
|
229
|
+
await fireHook(
|
|
230
|
+
ctx.hooks,
|
|
231
|
+
"on-story-start",
|
|
232
|
+
hookCtx(ctx.feature, {
|
|
233
|
+
storyId: story.id,
|
|
234
|
+
model: routing.modelTier,
|
|
235
|
+
agent: ctx.config.autoMode.defaultAgent,
|
|
236
|
+
iteration: iterations,
|
|
237
|
+
}),
|
|
238
|
+
ctx.workdir,
|
|
239
|
+
);
|
|
240
|
+
|
|
241
|
+
if (ctx.dryRun) {
|
|
242
|
+
const dryRunResult = await handleDryRun({
|
|
243
|
+
prd,
|
|
244
|
+
prdPath: ctx.prdPath,
|
|
245
|
+
storiesToExecute,
|
|
246
|
+
routing,
|
|
247
|
+
statusWriter: ctx.statusWriter,
|
|
248
|
+
pluginRegistry: ctx.pluginRegistry,
|
|
249
|
+
runId: ctx.runId,
|
|
250
|
+
totalCost,
|
|
251
|
+
iterations,
|
|
252
|
+
});
|
|
253
|
+
storiesCompleted += dryRunResult.storiesCompletedDelta;
|
|
254
|
+
prdDirty = dryRunResult.prdDirty;
|
|
255
|
+
continue;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
// Capture git ref for scoped verification
|
|
259
|
+
const storyGitRef = await captureGitRef(ctx.workdir);
|
|
260
|
+
|
|
261
|
+
// Build pipeline context
|
|
262
|
+
const storyStartTime = new Date().toISOString();
|
|
263
|
+
const pipelineContext: PipelineContext = {
|
|
264
|
+
config: ctx.config,
|
|
265
|
+
prd,
|
|
266
|
+
story,
|
|
267
|
+
stories: storiesToExecute,
|
|
268
|
+
routing: routing as RoutingResult,
|
|
269
|
+
workdir: ctx.workdir,
|
|
270
|
+
featureDir: ctx.featureDir,
|
|
271
|
+
hooks: ctx.hooks,
|
|
272
|
+
plugins: ctx.pluginRegistry,
|
|
273
|
+
storyStartTime,
|
|
274
|
+
interaction: ctx.interactionChain ?? undefined,
|
|
275
|
+
};
|
|
276
|
+
|
|
277
|
+
// Log agent start
|
|
278
|
+
logger?.info("agent.start", "Starting agent execution", {
|
|
279
|
+
storyId: story.id,
|
|
280
|
+
agent: ctx.config.autoMode.defaultAgent,
|
|
281
|
+
modelTier: routing.modelTier,
|
|
282
|
+
testStrategy: routing.testStrategy,
|
|
283
|
+
isBatch: isBatchExecution,
|
|
284
|
+
});
|
|
285
|
+
|
|
286
|
+
// Update status before execution
|
|
287
|
+
ctx.statusWriter.setPrd(prd);
|
|
288
|
+
ctx.statusWriter.setCurrentStory({
|
|
289
|
+
storyId: story.id,
|
|
290
|
+
title: story.title,
|
|
291
|
+
complexity: routing.complexity,
|
|
292
|
+
tddStrategy: routing.testStrategy,
|
|
293
|
+
model: routing.modelTier,
|
|
294
|
+
attempt: (story.attempts ?? 0) + 1,
|
|
295
|
+
phase: "routing",
|
|
296
|
+
});
|
|
297
|
+
await ctx.statusWriter.update(totalCost, iterations);
|
|
298
|
+
|
|
299
|
+
// Run pipeline
|
|
300
|
+
const pipelineResult = await runPipeline(defaultPipeline, pipelineContext, ctx.eventEmitter);
|
|
301
|
+
|
|
302
|
+
// Log agent complete
|
|
303
|
+
logger?.info("agent.complete", "Agent execution completed", {
|
|
304
|
+
storyId: story.id,
|
|
305
|
+
success: pipelineResult.success,
|
|
306
|
+
finalAction: pipelineResult.finalAction,
|
|
307
|
+
estimatedCost: pipelineResult.context.agentResult?.estimatedCost,
|
|
308
|
+
});
|
|
309
|
+
|
|
310
|
+
// Update PRD reference (pipeline may have modified it)
|
|
311
|
+
prd = pipelineResult.context.prd;
|
|
312
|
+
|
|
313
|
+
// Handle pipeline result
|
|
314
|
+
const handlerCtx = {
|
|
315
|
+
config: ctx.config,
|
|
316
|
+
prd,
|
|
317
|
+
prdPath: ctx.prdPath,
|
|
318
|
+
workdir: ctx.workdir,
|
|
319
|
+
featureDir: ctx.featureDir,
|
|
320
|
+
hooks: ctx.hooks,
|
|
321
|
+
feature: ctx.feature,
|
|
322
|
+
totalCost,
|
|
323
|
+
startTime: ctx.startTime,
|
|
324
|
+
runId: ctx.runId,
|
|
325
|
+
pluginRegistry: ctx.pluginRegistry,
|
|
326
|
+
story,
|
|
327
|
+
storiesToExecute,
|
|
328
|
+
routing,
|
|
329
|
+
isBatchExecution,
|
|
330
|
+
allStoryMetrics,
|
|
331
|
+
timeoutRetryCountMap,
|
|
332
|
+
storyGitRef,
|
|
333
|
+
interactionChain: ctx.interactionChain,
|
|
334
|
+
};
|
|
335
|
+
|
|
336
|
+
if (pipelineResult.success) {
|
|
337
|
+
const successResult = await handlePipelineSuccess(handlerCtx, pipelineResult);
|
|
338
|
+
totalCost += successResult.costDelta;
|
|
339
|
+
storiesCompleted += successResult.storiesCompletedDelta;
|
|
340
|
+
prd = successResult.prd;
|
|
341
|
+
prdDirty = successResult.prdDirty;
|
|
342
|
+
} else {
|
|
343
|
+
const failResult = await handlePipelineFailure(handlerCtx, pipelineResult);
|
|
344
|
+
prd = failResult.prd;
|
|
345
|
+
prdDirty = failResult.prdDirty;
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
// Update status after story complete
|
|
349
|
+
if (prdDirty) {
|
|
350
|
+
prd = await loadPRD(ctx.prdPath);
|
|
351
|
+
prdDirty = false;
|
|
352
|
+
}
|
|
353
|
+
ctx.statusWriter.setPrd(prd);
|
|
354
|
+
ctx.statusWriter.setCurrentStory(null);
|
|
355
|
+
await ctx.statusWriter.update(totalCost, iterations);
|
|
356
|
+
|
|
357
|
+
// Stall detection
|
|
358
|
+
if (isStalled(prd)) {
|
|
359
|
+
const summary = generateHumanHaltSummary(prd);
|
|
360
|
+
logger?.error("execution", "Execution stalled", {
|
|
361
|
+
reason: "All remaining stories blocked or dependent on blocked stories",
|
|
362
|
+
summary,
|
|
363
|
+
});
|
|
364
|
+
await fireHook(
|
|
365
|
+
ctx.hooks,
|
|
366
|
+
"on-pause",
|
|
367
|
+
hookCtx(ctx.feature, {
|
|
368
|
+
reason: "All remaining stories blocked or dependent on blocked stories",
|
|
369
|
+
cost: totalCost,
|
|
370
|
+
}),
|
|
371
|
+
ctx.workdir,
|
|
372
|
+
);
|
|
373
|
+
return buildResult("stalled");
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
// Delay between iterations
|
|
377
|
+
if (ctx.config.execution.iterationDelayMs > 0) {
|
|
378
|
+
await Bun.sleep(ctx.config.execution.iterationDelayMs);
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
return buildResult("max-iterations");
|
|
383
|
+
} finally {
|
|
384
|
+
// Stop heartbeat and write exit summary
|
|
385
|
+
stopHeartbeat();
|
|
386
|
+
await writeExitSummary(ctx.logFilePath, totalCost, iterations, storiesCompleted, Date.now() - ctx.startTime);
|
|
387
|
+
}
|
|
388
|
+
}
|
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Status File — Machine-readable run state for external tooling
|
|
3
|
+
*
|
|
4
|
+
* Writes a JSON status file that external tools (CI/CD, orchestrators,
|
|
5
|
+
* dashboards) can poll to monitor nax runs without parsing logs.
|
|
6
|
+
*
|
|
7
|
+
* Atomic writes: write to <path>.tmp then rename to <path>
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { rename, unlink } from "node:fs/promises";
|
|
11
|
+
import { resolve } from "node:path";
|
|
12
|
+
import type { NaxConfig } from "../config";
|
|
13
|
+
import type { PRD, StoryStatus, UserStory } from "../prd";
|
|
14
|
+
|
|
15
|
+
// ============================================================================
|
|
16
|
+
// NaxStatusFile Interface
|
|
17
|
+
// ============================================================================
|
|
18
|
+
|
|
19
|
+
/** Machine-readable status file written during nax runs */
|
|
20
|
+
export interface NaxStatusFile {
|
|
21
|
+
/** Schema version for forward compatibility */
|
|
22
|
+
version: 1;
|
|
23
|
+
|
|
24
|
+
/** Run metadata */
|
|
25
|
+
run: {
|
|
26
|
+
/** Run ID (e.g. "run-2026-02-25T10-00-00-000Z") */
|
|
27
|
+
id: string;
|
|
28
|
+
/** Feature name */
|
|
29
|
+
feature: string;
|
|
30
|
+
/** ISO 8601 start timestamp */
|
|
31
|
+
startedAt: string;
|
|
32
|
+
/** Current run status */
|
|
33
|
+
status: "running" | "completed" | "failed" | "stalled" | "crashed" | "precheck-failed";
|
|
34
|
+
/** Whether this is a dry run */
|
|
35
|
+
dryRun: boolean;
|
|
36
|
+
/** Process ID for crash detection */
|
|
37
|
+
pid: number;
|
|
38
|
+
/** ISO 8601 crash timestamp (present when status is "crashed") */
|
|
39
|
+
crashedAt?: string;
|
|
40
|
+
/** Signal or exception that caused crash (present when status is "crashed") */
|
|
41
|
+
crashSignal?: string;
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
/** Aggregate progress counts */
|
|
45
|
+
progress: {
|
|
46
|
+
/** Total stories in PRD */
|
|
47
|
+
total: number;
|
|
48
|
+
/** Stories that passed */
|
|
49
|
+
passed: number;
|
|
50
|
+
/** Stories that failed */
|
|
51
|
+
failed: number;
|
|
52
|
+
/** Stories that are paused */
|
|
53
|
+
paused: number;
|
|
54
|
+
/** Stories that are blocked */
|
|
55
|
+
blocked: number;
|
|
56
|
+
/** Stories not yet processed (total - passed - failed - paused - blocked) */
|
|
57
|
+
pending: number;
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
/** Cost tracking */
|
|
61
|
+
cost: {
|
|
62
|
+
/** Accumulated cost in USD */
|
|
63
|
+
spent: number;
|
|
64
|
+
/** Cost limit from config (null if not set) */
|
|
65
|
+
limit: number | null;
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
/** Current story being processed (null if between stories or at run boundaries) */
|
|
69
|
+
current: {
|
|
70
|
+
/** Story ID */
|
|
71
|
+
storyId: string;
|
|
72
|
+
/** Story title */
|
|
73
|
+
title: string;
|
|
74
|
+
/** Complexity level */
|
|
75
|
+
complexity: string;
|
|
76
|
+
/** TDD strategy */
|
|
77
|
+
tddStrategy: string;
|
|
78
|
+
/** Resolved model name */
|
|
79
|
+
model: string;
|
|
80
|
+
/** Current attempt number (1-based) */
|
|
81
|
+
attempt: number;
|
|
82
|
+
/** Current phase */
|
|
83
|
+
phase: string;
|
|
84
|
+
} | null;
|
|
85
|
+
|
|
86
|
+
/** Number of loop iterations completed */
|
|
87
|
+
iterations: number;
|
|
88
|
+
|
|
89
|
+
/** ISO 8601 last-updated timestamp */
|
|
90
|
+
updatedAt: string;
|
|
91
|
+
|
|
92
|
+
/** Elapsed duration in milliseconds */
|
|
93
|
+
durationMs: number;
|
|
94
|
+
|
|
95
|
+
/** ISO 8601 last heartbeat timestamp (updated every 60s during execution) */
|
|
96
|
+
lastHeartbeat?: string;
|
|
97
|
+
|
|
98
|
+
/** Parallel execution info (present when --parallel is used) */
|
|
99
|
+
parallel?: {
|
|
100
|
+
/** Whether parallel mode is enabled */
|
|
101
|
+
enabled: boolean;
|
|
102
|
+
/** Max concurrent sessions */
|
|
103
|
+
maxConcurrency: number;
|
|
104
|
+
/** Currently executing stories in parallel */
|
|
105
|
+
activeStories: Array<{
|
|
106
|
+
storyId: string;
|
|
107
|
+
worktreePath: string;
|
|
108
|
+
}>;
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// ============================================================================
|
|
113
|
+
// Progress Counting
|
|
114
|
+
// ============================================================================
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Derive progress counts from PRD story statuses.
|
|
118
|
+
*
|
|
119
|
+
* Counts each story by its current status. `pending` is computed as
|
|
120
|
+
* everything not in the four explicit terminal/waiting states.
|
|
121
|
+
*/
|
|
122
|
+
export function countProgress(prd: PRD): NaxStatusFile["progress"] {
|
|
123
|
+
const stories = prd.userStories;
|
|
124
|
+
const passed = stories.filter((s) => s.status === "passed").length;
|
|
125
|
+
const failed = stories.filter((s) => s.status === "failed").length;
|
|
126
|
+
const paused = stories.filter((s) => s.status === "paused").length;
|
|
127
|
+
const blocked = stories.filter((s) => s.status === "blocked").length;
|
|
128
|
+
const total = stories.length;
|
|
129
|
+
const pending = total - passed - failed - paused - blocked;
|
|
130
|
+
|
|
131
|
+
return { total, passed, failed, paused, blocked, pending };
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// ============================================================================
|
|
135
|
+
// Run State (for buildStatusSnapshot)
|
|
136
|
+
// ============================================================================
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Snapshot of current run state used to build NaxStatusFile.
|
|
140
|
+
*
|
|
141
|
+
* This is a value-only snapshot — callers pass in what they have at the
|
|
142
|
+
* current write point. The runner constructs this inline from local variables.
|
|
143
|
+
*/
|
|
144
|
+
export interface RunStateSnapshot {
|
|
145
|
+
/** Unique run identifier */
|
|
146
|
+
runId: string;
|
|
147
|
+
/** Feature name */
|
|
148
|
+
feature: string;
|
|
149
|
+
/** ISO 8601 start timestamp */
|
|
150
|
+
startedAt: string;
|
|
151
|
+
/** Current run status */
|
|
152
|
+
runStatus: NaxStatusFile["run"]["status"];
|
|
153
|
+
/** Whether this is a dry run */
|
|
154
|
+
dryRun: boolean;
|
|
155
|
+
/** Process ID for crash detection */
|
|
156
|
+
pid: number;
|
|
157
|
+
/** Loaded PRD (for progress counting) */
|
|
158
|
+
prd: PRD;
|
|
159
|
+
/** Accumulated cost in USD */
|
|
160
|
+
totalCost: number;
|
|
161
|
+
/** Cost limit from config (or null) */
|
|
162
|
+
costLimit: number | null;
|
|
163
|
+
/** Currently-executing story info (null between stories) */
|
|
164
|
+
currentStory: {
|
|
165
|
+
storyId: string;
|
|
166
|
+
title: string;
|
|
167
|
+
complexity: string;
|
|
168
|
+
tddStrategy: string;
|
|
169
|
+
model: string;
|
|
170
|
+
attempt: number;
|
|
171
|
+
phase: string;
|
|
172
|
+
} | null;
|
|
173
|
+
/** Number of loop iterations */
|
|
174
|
+
iterations: number;
|
|
175
|
+
/** Run start time as ms epoch (for computing durationMs) */
|
|
176
|
+
startTimeMs: number;
|
|
177
|
+
/** Parallel execution info (optional) */
|
|
178
|
+
parallel?: NaxStatusFile["parallel"];
|
|
179
|
+
/** ISO 8601 crash timestamp (present when status is "crashed") */
|
|
180
|
+
crashedAt?: string;
|
|
181
|
+
/** Signal or exception that caused crash (present when status is "crashed") */
|
|
182
|
+
crashSignal?: string;
|
|
183
|
+
/** ISO 8601 last heartbeat timestamp (updated every 60s during execution) */
|
|
184
|
+
lastHeartbeat?: string;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// ============================================================================
|
|
188
|
+
// buildStatusSnapshot
|
|
189
|
+
// ============================================================================
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* Build a NaxStatusFile object from current run state.
|
|
193
|
+
*
|
|
194
|
+
* Derives progress from PRD story statuses. Sets updatedAt and durationMs
|
|
195
|
+
* from the current time. Does not write to disk — call writeStatusFile() for that.
|
|
196
|
+
*/
|
|
197
|
+
export function buildStatusSnapshot(state: RunStateSnapshot): NaxStatusFile {
|
|
198
|
+
const now = Date.now();
|
|
199
|
+
const snapshot: NaxStatusFile = {
|
|
200
|
+
version: 1,
|
|
201
|
+
run: {
|
|
202
|
+
id: state.runId,
|
|
203
|
+
feature: state.feature,
|
|
204
|
+
startedAt: state.startedAt,
|
|
205
|
+
status: state.runStatus,
|
|
206
|
+
dryRun: state.dryRun,
|
|
207
|
+
pid: state.pid,
|
|
208
|
+
...(state.crashedAt && { crashedAt: state.crashedAt }),
|
|
209
|
+
...(state.crashSignal && { crashSignal: state.crashSignal }),
|
|
210
|
+
},
|
|
211
|
+
progress: countProgress(state.prd),
|
|
212
|
+
cost: {
|
|
213
|
+
spent: state.totalCost,
|
|
214
|
+
limit: state.costLimit,
|
|
215
|
+
},
|
|
216
|
+
current: state.currentStory,
|
|
217
|
+
iterations: state.iterations,
|
|
218
|
+
updatedAt: new Date(now).toISOString(),
|
|
219
|
+
durationMs: now - state.startTimeMs,
|
|
220
|
+
...(state.lastHeartbeat && { lastHeartbeat: state.lastHeartbeat }),
|
|
221
|
+
};
|
|
222
|
+
|
|
223
|
+
if (state.parallel) {
|
|
224
|
+
snapshot.parallel = state.parallel;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
return snapshot;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
// ============================================================================
|
|
231
|
+
// Atomic Writer
|
|
232
|
+
// ============================================================================
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* Atomically write a NaxStatusFile to disk.
|
|
236
|
+
*
|
|
237
|
+
* Writes to `<path>.tmp` first, then renames to `<path>` to prevent
|
|
238
|
+
* consumers from reading partial JSON during the write.
|
|
239
|
+
*
|
|
240
|
+
* @param filePath - Destination path for the status file
|
|
241
|
+
* @param status - Status file content to write
|
|
242
|
+
* @throws Error if path traversal is detected
|
|
243
|
+
*/
|
|
244
|
+
export async function writeStatusFile(filePath: string, status: NaxStatusFile): Promise<void> {
|
|
245
|
+
// SEC-1: Validate path to prevent path traversal attacks
|
|
246
|
+
const resolvedPath = resolve(filePath);
|
|
247
|
+
|
|
248
|
+
// Check for path traversal patterns in the original path
|
|
249
|
+
if (filePath.includes("../") || filePath.includes("..\\")) {
|
|
250
|
+
throw new Error("Invalid status file path: path traversal detected");
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
const tmpPath = `${resolvedPath}.tmp`;
|
|
254
|
+
|
|
255
|
+
// MEM-1: Clean up stale .tmp file if it exists
|
|
256
|
+
try {
|
|
257
|
+
await unlink(tmpPath);
|
|
258
|
+
} catch {
|
|
259
|
+
// Ignore error if file doesn't exist
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
await Bun.write(tmpPath, JSON.stringify(status, null, 2));
|
|
263
|
+
await rename(tmpPath, resolvedPath);
|
|
264
|
+
}
|