@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,295 @@
|
|
|
1
|
+
import { afterEach, beforeEach, describe, expect, test } from "bun:test";
|
|
2
|
+
import { mkdirSync, rmSync } from "node:fs";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { spawn } from "bun";
|
|
5
|
+
import { acquireLock, formatProgress, releaseLock } from "../../src/execution/helpers";
|
|
6
|
+
import type { StoryCounts } from "../../src/execution/helpers";
|
|
7
|
+
|
|
8
|
+
describe("formatProgress", () => {
|
|
9
|
+
test("formats progress with all stories pending", () => {
|
|
10
|
+
const counts: StoryCounts = {
|
|
11
|
+
total: 12,
|
|
12
|
+
passed: 0,
|
|
13
|
+
failed: 0,
|
|
14
|
+
pending: 12,
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
const progress = formatProgress(counts, 0, 5.0, 0, 12);
|
|
18
|
+
|
|
19
|
+
expect(progress).toContain("0/12 stories");
|
|
20
|
+
expect(progress).toContain("0 passed");
|
|
21
|
+
expect(progress).toContain("0 failed");
|
|
22
|
+
expect(progress).toContain("$0.00/$5.00");
|
|
23
|
+
expect(progress).toContain("calculating...");
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
test("formats progress with some stories completed", () => {
|
|
27
|
+
const counts: StoryCounts = {
|
|
28
|
+
total: 12,
|
|
29
|
+
passed: 5,
|
|
30
|
+
failed: 1,
|
|
31
|
+
pending: 6,
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
// 10 minutes elapsed (600000 ms), 6 stories completed
|
|
35
|
+
// avg = 600000 / 6 = 100000 ms per story
|
|
36
|
+
// remaining = 6 stories * 100000 = 600000 ms = 10 minutes
|
|
37
|
+
const progress = formatProgress(counts, 0.45, 5.0, 600000, 12);
|
|
38
|
+
|
|
39
|
+
expect(progress).toContain("6/12 stories");
|
|
40
|
+
expect(progress).toContain("5 passed");
|
|
41
|
+
expect(progress).toContain("1 failed");
|
|
42
|
+
expect(progress).toContain("$0.45/$5.00");
|
|
43
|
+
expect(progress).toContain("~10 min remaining");
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
test("formats progress when all stories are complete", () => {
|
|
47
|
+
const counts: StoryCounts = {
|
|
48
|
+
total: 12,
|
|
49
|
+
passed: 10,
|
|
50
|
+
failed: 2,
|
|
51
|
+
pending: 0,
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
const progress = formatProgress(counts, 1.23, 5.0, 1200000, 12);
|
|
55
|
+
|
|
56
|
+
expect(progress).toContain("12/12 stories");
|
|
57
|
+
expect(progress).toContain("10 passed");
|
|
58
|
+
expect(progress).toContain("2 failed");
|
|
59
|
+
expect(progress).toContain("$1.23/$5.00");
|
|
60
|
+
expect(progress).toContain("complete");
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
test("calculates ETA correctly for fast stories", () => {
|
|
64
|
+
const counts: StoryCounts = {
|
|
65
|
+
total: 20,
|
|
66
|
+
passed: 10,
|
|
67
|
+
failed: 0,
|
|
68
|
+
pending: 10,
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
// 2 minutes elapsed (120000 ms) for 10 stories
|
|
72
|
+
// avg = 120000 / 10 = 12000 ms per story
|
|
73
|
+
// remaining = 10 stories * 12000 = 120000 ms = 2 minutes
|
|
74
|
+
const progress = formatProgress(counts, 0.5, 10.0, 120000, 20);
|
|
75
|
+
|
|
76
|
+
expect(progress).toContain("~2 min remaining");
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
test("rounds ETA to nearest minute", () => {
|
|
80
|
+
const counts: StoryCounts = {
|
|
81
|
+
total: 10,
|
|
82
|
+
passed: 3,
|
|
83
|
+
failed: 0,
|
|
84
|
+
pending: 7,
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
// 8.5 minutes elapsed (510000 ms) for 3 stories
|
|
88
|
+
// avg = 510000 / 3 = 170000 ms per story
|
|
89
|
+
// remaining = 7 stories * 170000 = 1190000 ms ≈ 19.8 minutes → rounds to 20
|
|
90
|
+
const progress = formatProgress(counts, 0.3, 5.0, 510000, 10);
|
|
91
|
+
|
|
92
|
+
expect(progress).toContain("~20 min remaining");
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
test("includes cost information with proper formatting", () => {
|
|
96
|
+
const counts: StoryCounts = {
|
|
97
|
+
total: 5,
|
|
98
|
+
passed: 2,
|
|
99
|
+
failed: 0,
|
|
100
|
+
pending: 3,
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
const progress = formatProgress(counts, 1.2345, 10.0, 300000, 5);
|
|
104
|
+
|
|
105
|
+
// Should round cost to 2 decimal places
|
|
106
|
+
expect(progress).toContain("$1.23/$10.00");
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
test("handles zero elapsed time gracefully", () => {
|
|
110
|
+
const counts: StoryCounts = {
|
|
111
|
+
total: 10,
|
|
112
|
+
passed: 0,
|
|
113
|
+
failed: 0,
|
|
114
|
+
pending: 10,
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
const progress = formatProgress(counts, 0, 5.0, 0, 10);
|
|
118
|
+
|
|
119
|
+
expect(progress).toContain("calculating...");
|
|
120
|
+
expect(progress).not.toContain("NaN");
|
|
121
|
+
expect(progress).not.toContain("Infinity");
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
test("includes all required progress indicators", () => {
|
|
125
|
+
const counts: StoryCounts = {
|
|
126
|
+
total: 10,
|
|
127
|
+
passed: 3,
|
|
128
|
+
failed: 1,
|
|
129
|
+
pending: 6,
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
const progress = formatProgress(counts, 0.5, 5.0, 300000, 10);
|
|
133
|
+
|
|
134
|
+
expect(progress).toContain("Progress:");
|
|
135
|
+
expect(progress).toContain("passed");
|
|
136
|
+
expect(progress).toContain("failed");
|
|
137
|
+
expect(progress).toContain("$");
|
|
138
|
+
expect(progress).toContain("min remaining");
|
|
139
|
+
});
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
describe("acquireLock and releaseLock", () => {
|
|
143
|
+
const testDir = path.join(import.meta.dir, ".test-locks");
|
|
144
|
+
const lockPath = path.join(testDir, "nax.lock");
|
|
145
|
+
|
|
146
|
+
beforeEach(() => {
|
|
147
|
+
// Create clean test directory
|
|
148
|
+
rmSync(testDir, { recursive: true, force: true });
|
|
149
|
+
mkdirSync(testDir, { recursive: true });
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
afterEach(() => {
|
|
153
|
+
// Clean up test directory
|
|
154
|
+
rmSync(testDir, { recursive: true, force: true });
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
test("acquires lock when no lock file exists", async () => {
|
|
158
|
+
const acquired = await acquireLock(testDir);
|
|
159
|
+
expect(acquired).toBe(true);
|
|
160
|
+
|
|
161
|
+
// Verify lock file was created
|
|
162
|
+
const lockFile = Bun.file(lockPath);
|
|
163
|
+
expect(await lockFile.exists()).toBe(true);
|
|
164
|
+
|
|
165
|
+
// Verify lock file contains current PID
|
|
166
|
+
const lockContent = await lockFile.text();
|
|
167
|
+
const lockData = JSON.parse(lockContent);
|
|
168
|
+
expect(lockData.pid).toBe(process.pid);
|
|
169
|
+
expect(typeof lockData.timestamp).toBe("number");
|
|
170
|
+
|
|
171
|
+
await releaseLock(testDir);
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
test("fails to acquire lock when another process holds it", async () => {
|
|
175
|
+
// First process acquires lock
|
|
176
|
+
const acquired1 = await acquireLock(testDir);
|
|
177
|
+
expect(acquired1).toBe(true);
|
|
178
|
+
|
|
179
|
+
// Second process tries to acquire lock
|
|
180
|
+
const acquired2 = await acquireLock(testDir);
|
|
181
|
+
expect(acquired2).toBe(false);
|
|
182
|
+
|
|
183
|
+
await releaseLock(testDir);
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
test("releases lock successfully", async () => {
|
|
187
|
+
await acquireLock(testDir);
|
|
188
|
+
await releaseLock(testDir);
|
|
189
|
+
|
|
190
|
+
// Verify lock file was deleted
|
|
191
|
+
const lockFile = Bun.file(lockPath);
|
|
192
|
+
expect(await lockFile.exists()).toBe(false);
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
test("can re-acquire lock after release", async () => {
|
|
196
|
+
const acquired1 = await acquireLock(testDir);
|
|
197
|
+
expect(acquired1).toBe(true);
|
|
198
|
+
|
|
199
|
+
await releaseLock(testDir);
|
|
200
|
+
|
|
201
|
+
const acquired2 = await acquireLock(testDir);
|
|
202
|
+
expect(acquired2).toBe(true);
|
|
203
|
+
|
|
204
|
+
await releaseLock(testDir);
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
test("removes stale lock when process is dead", async () => {
|
|
208
|
+
// Create a lock file with a fake PID that doesn't exist
|
|
209
|
+
const stalePid = 999999; // Very unlikely to be a real process
|
|
210
|
+
const staleLock = {
|
|
211
|
+
pid: stalePid,
|
|
212
|
+
timestamp: Date.now() - 60000, // 1 minute ago
|
|
213
|
+
};
|
|
214
|
+
await Bun.write(lockPath, JSON.stringify(staleLock));
|
|
215
|
+
|
|
216
|
+
// Try to acquire lock - should detect stale lock and remove it
|
|
217
|
+
const acquired = await acquireLock(testDir);
|
|
218
|
+
expect(acquired).toBe(true);
|
|
219
|
+
|
|
220
|
+
// Verify new lock file has current PID
|
|
221
|
+
const lockFile = Bun.file(lockPath);
|
|
222
|
+
const lockContent = await lockFile.text();
|
|
223
|
+
const lockData = JSON.parse(lockContent);
|
|
224
|
+
expect(lockData.pid).toBe(process.pid);
|
|
225
|
+
|
|
226
|
+
await releaseLock(testDir);
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
test("detects stale lock from OOM-killed process", async () => {
|
|
230
|
+
// Spawn a short-lived child process
|
|
231
|
+
const proc = spawn({
|
|
232
|
+
cmd: ["sleep", "0.1"],
|
|
233
|
+
stdout: "pipe",
|
|
234
|
+
stderr: "pipe",
|
|
235
|
+
});
|
|
236
|
+
|
|
237
|
+
// Get the child PID
|
|
238
|
+
const childPid = proc.pid;
|
|
239
|
+
|
|
240
|
+
// Wait for it to exit
|
|
241
|
+
await proc.exited;
|
|
242
|
+
|
|
243
|
+
// Create a lock file with the dead child's PID
|
|
244
|
+
const staleLock = {
|
|
245
|
+
pid: childPid,
|
|
246
|
+
timestamp: Date.now() - 60000, // 1 minute ago
|
|
247
|
+
};
|
|
248
|
+
await Bun.write(lockPath, JSON.stringify(staleLock));
|
|
249
|
+
|
|
250
|
+
// Now try to acquire lock - should detect child process is dead
|
|
251
|
+
const acquired = await acquireLock(testDir);
|
|
252
|
+
expect(acquired).toBe(true);
|
|
253
|
+
|
|
254
|
+
// Verify new lock has current PID
|
|
255
|
+
const lockFile = Bun.file(lockPath);
|
|
256
|
+
const lockContent = await lockFile.text();
|
|
257
|
+
const lockData = JSON.parse(lockContent);
|
|
258
|
+
expect(lockData.pid).toBe(process.pid);
|
|
259
|
+
|
|
260
|
+
await releaseLock(testDir);
|
|
261
|
+
});
|
|
262
|
+
|
|
263
|
+
test("does not remove lock when process is still alive", async () => {
|
|
264
|
+
// Create lock with current process PID
|
|
265
|
+
const validLock = {
|
|
266
|
+
pid: process.pid,
|
|
267
|
+
timestamp: Date.now() - 60000, // 1 minute ago
|
|
268
|
+
};
|
|
269
|
+
await Bun.write(lockPath, JSON.stringify(validLock));
|
|
270
|
+
|
|
271
|
+
// Try to acquire lock - should NOT remove it since process is alive
|
|
272
|
+
const acquired = await acquireLock(testDir);
|
|
273
|
+
expect(acquired).toBe(false);
|
|
274
|
+
|
|
275
|
+
// Verify lock still exists with same PID
|
|
276
|
+
const lockFile = Bun.file(lockPath);
|
|
277
|
+
const lockContent = await lockFile.text();
|
|
278
|
+
const lockData = JSON.parse(lockContent);
|
|
279
|
+
expect(lockData.pid).toBe(process.pid);
|
|
280
|
+
});
|
|
281
|
+
|
|
282
|
+
test("handles corrupted lock file gracefully", async () => {
|
|
283
|
+
// Create invalid JSON lock file
|
|
284
|
+
await Bun.write(lockPath, "not valid json");
|
|
285
|
+
|
|
286
|
+
// Should fail to acquire but not crash
|
|
287
|
+
const acquired = await acquireLock(testDir);
|
|
288
|
+
expect(acquired).toBe(false);
|
|
289
|
+
});
|
|
290
|
+
|
|
291
|
+
test("handles release when lock file doesn't exist", async () => {
|
|
292
|
+
// Should not throw when releasing non-existent lock
|
|
293
|
+
await expect(releaseLock(testDir)).resolves.toBeUndefined();
|
|
294
|
+
});
|
|
295
|
+
});
|
|
@@ -0,0 +1,361 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Hook Runner Tests
|
|
3
|
+
*
|
|
4
|
+
* Tests for hook execution, security, and lifecycle
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { describe, expect, test } from "bun:test";
|
|
8
|
+
import { fireHook, loadHooksConfig } from "../../src/hooks/runner";
|
|
9
|
+
import type { HookContext, HooksConfig } from "../../src/hooks/types";
|
|
10
|
+
|
|
11
|
+
describe("Hook Security", () => {
|
|
12
|
+
test("executes safe commands without shell", async () => {
|
|
13
|
+
const config: HooksConfig = {
|
|
14
|
+
hooks: {
|
|
15
|
+
"on-start": {
|
|
16
|
+
command: "echo hello",
|
|
17
|
+
enabled: true,
|
|
18
|
+
},
|
|
19
|
+
},
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
const ctx: HookContext = {
|
|
23
|
+
event: "on-start",
|
|
24
|
+
feature: "test-feature",
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
// Should not throw
|
|
28
|
+
await fireHook(config, "on-start", ctx, process.cwd());
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
test("rejects command substitution with $()", async () => {
|
|
32
|
+
const config: HooksConfig = {
|
|
33
|
+
hooks: {
|
|
34
|
+
"on-start": {
|
|
35
|
+
command: "echo $(whoami)",
|
|
36
|
+
enabled: true,
|
|
37
|
+
},
|
|
38
|
+
},
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
const ctx: HookContext = {
|
|
42
|
+
event: "on-start",
|
|
43
|
+
feature: "test-feature",
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
// Should execute but validation should fail
|
|
47
|
+
await fireHook(config, "on-start", ctx, process.cwd());
|
|
48
|
+
// The hook should log a validation failure (tested via console.warn spy in integration)
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
test("rejects backtick command substitution", async () => {
|
|
52
|
+
const config: HooksConfig = {
|
|
53
|
+
hooks: {
|
|
54
|
+
"on-start": {
|
|
55
|
+
command: "echo `whoami`",
|
|
56
|
+
enabled: true,
|
|
57
|
+
},
|
|
58
|
+
},
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
const ctx: HookContext = {
|
|
62
|
+
event: "on-start",
|
|
63
|
+
feature: "test-feature",
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
await fireHook(config, "on-start", ctx, process.cwd());
|
|
67
|
+
// Should fail validation
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
test("rejects piping to bash", async () => {
|
|
71
|
+
const config: HooksConfig = {
|
|
72
|
+
hooks: {
|
|
73
|
+
"on-start": {
|
|
74
|
+
command: "echo malicious | bash",
|
|
75
|
+
enabled: true,
|
|
76
|
+
},
|
|
77
|
+
},
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
const ctx: HookContext = {
|
|
81
|
+
event: "on-start",
|
|
82
|
+
feature: "test-feature",
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
await fireHook(config, "on-start", ctx, process.cwd());
|
|
86
|
+
// Should fail validation
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
test("rejects dangerous rm -rf patterns", async () => {
|
|
90
|
+
const config: HooksConfig = {
|
|
91
|
+
hooks: {
|
|
92
|
+
"on-start": {
|
|
93
|
+
command: "echo test; rm -rf /",
|
|
94
|
+
enabled: true,
|
|
95
|
+
},
|
|
96
|
+
},
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
const ctx: HookContext = {
|
|
100
|
+
event: "on-start",
|
|
101
|
+
feature: "test-feature",
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
await fireHook(config, "on-start", ctx, process.cwd());
|
|
105
|
+
// Should fail validation
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
test("warns about shell operators", async () => {
|
|
109
|
+
const config: HooksConfig = {
|
|
110
|
+
hooks: {
|
|
111
|
+
"on-start": {
|
|
112
|
+
command: "echo hello && echo world",
|
|
113
|
+
enabled: true,
|
|
114
|
+
},
|
|
115
|
+
},
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
const ctx: HookContext = {
|
|
119
|
+
event: "on-start",
|
|
120
|
+
feature: "test-feature",
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
// Should execute but log warning about shell operators
|
|
124
|
+
await fireHook(config, "on-start", ctx, process.cwd());
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
test("escapes environment variables", async () => {
|
|
128
|
+
const config: HooksConfig = {
|
|
129
|
+
hooks: {
|
|
130
|
+
"on-story-start": {
|
|
131
|
+
command: "printenv",
|
|
132
|
+
enabled: true,
|
|
133
|
+
},
|
|
134
|
+
},
|
|
135
|
+
};
|
|
136
|
+
|
|
137
|
+
const ctx: HookContext = {
|
|
138
|
+
event: "on-story-start",
|
|
139
|
+
feature: "test-feature",
|
|
140
|
+
storyId: "story-with-\0null-byte",
|
|
141
|
+
reason: "reason\nwith\nnewlines",
|
|
142
|
+
};
|
|
143
|
+
|
|
144
|
+
// Should not throw, environment variables should be escaped
|
|
145
|
+
await fireHook(config, "on-story-start", ctx, process.cwd());
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
test("handles timeout correctly", async () => {
|
|
149
|
+
const config: HooksConfig = {
|
|
150
|
+
hooks: {
|
|
151
|
+
"on-start": {
|
|
152
|
+
command: "sleep 10",
|
|
153
|
+
enabled: true,
|
|
154
|
+
timeout: 100, // 100ms timeout
|
|
155
|
+
},
|
|
156
|
+
},
|
|
157
|
+
};
|
|
158
|
+
|
|
159
|
+
const ctx: HookContext = {
|
|
160
|
+
event: "on-start",
|
|
161
|
+
feature: "test-feature",
|
|
162
|
+
};
|
|
163
|
+
|
|
164
|
+
// Should timeout and not throw
|
|
165
|
+
await fireHook(config, "on-start", ctx, process.cwd());
|
|
166
|
+
// Should log timeout reason
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
test("skips disabled hooks", async () => {
|
|
170
|
+
const config: HooksConfig = {
|
|
171
|
+
hooks: {
|
|
172
|
+
"on-start": {
|
|
173
|
+
command: "echo should-not-run",
|
|
174
|
+
enabled: false,
|
|
175
|
+
},
|
|
176
|
+
},
|
|
177
|
+
};
|
|
178
|
+
|
|
179
|
+
const ctx: HookContext = {
|
|
180
|
+
event: "on-start",
|
|
181
|
+
feature: "test-feature",
|
|
182
|
+
};
|
|
183
|
+
|
|
184
|
+
// Should not execute
|
|
185
|
+
await fireHook(config, "on-start", ctx, process.cwd());
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
test("handles missing hooks gracefully", async () => {
|
|
189
|
+
const config: HooksConfig = {
|
|
190
|
+
hooks: {},
|
|
191
|
+
};
|
|
192
|
+
|
|
193
|
+
const ctx: HookContext = {
|
|
194
|
+
event: "on-start",
|
|
195
|
+
feature: "test-feature",
|
|
196
|
+
};
|
|
197
|
+
|
|
198
|
+
// Should not throw
|
|
199
|
+
await fireHook(config, "on-start", ctx, process.cwd());
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
test("passes context as JSON via stdin", async () => {
|
|
203
|
+
const config: HooksConfig = {
|
|
204
|
+
hooks: {
|
|
205
|
+
"on-complete": {
|
|
206
|
+
command: "cat",
|
|
207
|
+
enabled: true,
|
|
208
|
+
},
|
|
209
|
+
},
|
|
210
|
+
};
|
|
211
|
+
|
|
212
|
+
const ctx: HookContext = {
|
|
213
|
+
event: "on-complete",
|
|
214
|
+
feature: "test-feature",
|
|
215
|
+
storyId: "story-123",
|
|
216
|
+
status: "completed",
|
|
217
|
+
cost: 1.5,
|
|
218
|
+
model: "claude-sonnet-4",
|
|
219
|
+
};
|
|
220
|
+
|
|
221
|
+
// Should pass full context as JSON
|
|
222
|
+
await fireHook(config, "on-complete", ctx, process.cwd());
|
|
223
|
+
});
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
describe("Hook Configuration Loading", () => {
|
|
227
|
+
test("loads empty config when no hooks.json exists", async () => {
|
|
228
|
+
const config = await loadHooksConfig("/tmp/nonexistent-hooks-dir");
|
|
229
|
+
|
|
230
|
+
expect(config).toEqual({
|
|
231
|
+
hooks: {},
|
|
232
|
+
_global: { hooks: {} },
|
|
233
|
+
_skipGlobal: false,
|
|
234
|
+
});
|
|
235
|
+
});
|
|
236
|
+
|
|
237
|
+
test("merges global and project hooks", async () => {
|
|
238
|
+
// This test would require creating temporary hooks.json files
|
|
239
|
+
// Skipping for now, as it requires file system setup
|
|
240
|
+
});
|
|
241
|
+
});
|
|
242
|
+
|
|
243
|
+
describe("Hook Environment Variables", () => {
|
|
244
|
+
test("sets NAX_EVENT", async () => {
|
|
245
|
+
const config: HooksConfig = {
|
|
246
|
+
hooks: {
|
|
247
|
+
"on-start": {
|
|
248
|
+
command: "printenv",
|
|
249
|
+
enabled: true,
|
|
250
|
+
},
|
|
251
|
+
},
|
|
252
|
+
};
|
|
253
|
+
|
|
254
|
+
const ctx: HookContext = {
|
|
255
|
+
event: "on-start",
|
|
256
|
+
feature: "auth-feature",
|
|
257
|
+
};
|
|
258
|
+
|
|
259
|
+
await fireHook(config, "on-start", ctx, process.cwd());
|
|
260
|
+
// Environment variables should be set
|
|
261
|
+
});
|
|
262
|
+
|
|
263
|
+
test("sets NAX_FEATURE", async () => {
|
|
264
|
+
const config: HooksConfig = {
|
|
265
|
+
hooks: {
|
|
266
|
+
"on-start": {
|
|
267
|
+
command: "printenv",
|
|
268
|
+
enabled: true,
|
|
269
|
+
},
|
|
270
|
+
},
|
|
271
|
+
};
|
|
272
|
+
|
|
273
|
+
const ctx: HookContext = {
|
|
274
|
+
event: "on-start",
|
|
275
|
+
feature: "payment-system",
|
|
276
|
+
};
|
|
277
|
+
|
|
278
|
+
await fireHook(config, "on-start", ctx, process.cwd());
|
|
279
|
+
// NAX_FEATURE should be set to "payment-system"
|
|
280
|
+
});
|
|
281
|
+
|
|
282
|
+
test("sets NAX_STORY_ID when provided", async () => {
|
|
283
|
+
const config: HooksConfig = {
|
|
284
|
+
hooks: {
|
|
285
|
+
"on-story-start": {
|
|
286
|
+
command: "printenv",
|
|
287
|
+
enabled: true,
|
|
288
|
+
},
|
|
289
|
+
},
|
|
290
|
+
};
|
|
291
|
+
|
|
292
|
+
const ctx: HookContext = {
|
|
293
|
+
event: "on-story-start",
|
|
294
|
+
feature: "test-feature",
|
|
295
|
+
storyId: "user-login-001",
|
|
296
|
+
};
|
|
297
|
+
|
|
298
|
+
await fireHook(config, "on-story-start", ctx, process.cwd());
|
|
299
|
+
// NAX_STORY_ID should be set
|
|
300
|
+
});
|
|
301
|
+
|
|
302
|
+
test("sets NAX_COST when provided", async () => {
|
|
303
|
+
const config: HooksConfig = {
|
|
304
|
+
hooks: {
|
|
305
|
+
"on-complete": {
|
|
306
|
+
command: "printenv",
|
|
307
|
+
enabled: true,
|
|
308
|
+
},
|
|
309
|
+
},
|
|
310
|
+
};
|
|
311
|
+
|
|
312
|
+
const ctx: HookContext = {
|
|
313
|
+
event: "on-complete",
|
|
314
|
+
feature: "test-feature",
|
|
315
|
+
cost: 2.5678,
|
|
316
|
+
};
|
|
317
|
+
|
|
318
|
+
await fireHook(config, "on-complete", ctx, process.cwd());
|
|
319
|
+
// NAX_COST should be set to "2.5678"
|
|
320
|
+
});
|
|
321
|
+
|
|
322
|
+
test("sets NAX_MODEL when provided", async () => {
|
|
323
|
+
const config: HooksConfig = {
|
|
324
|
+
hooks: {
|
|
325
|
+
"on-complete": {
|
|
326
|
+
command: "printenv",
|
|
327
|
+
enabled: true,
|
|
328
|
+
},
|
|
329
|
+
},
|
|
330
|
+
};
|
|
331
|
+
|
|
332
|
+
const ctx: HookContext = {
|
|
333
|
+
event: "on-complete",
|
|
334
|
+
feature: "test-feature",
|
|
335
|
+
model: "claude-opus-4",
|
|
336
|
+
};
|
|
337
|
+
|
|
338
|
+
await fireHook(config, "on-complete", ctx, process.cwd());
|
|
339
|
+
// NAX_MODEL should be set
|
|
340
|
+
});
|
|
341
|
+
|
|
342
|
+
test("sets NAX_ITERATION when provided", async () => {
|
|
343
|
+
const config: HooksConfig = {
|
|
344
|
+
hooks: {
|
|
345
|
+
"on-error": {
|
|
346
|
+
command: "printenv",
|
|
347
|
+
enabled: true,
|
|
348
|
+
},
|
|
349
|
+
},
|
|
350
|
+
};
|
|
351
|
+
|
|
352
|
+
const ctx: HookContext = {
|
|
353
|
+
event: "on-error",
|
|
354
|
+
feature: "test-feature",
|
|
355
|
+
iteration: 3,
|
|
356
|
+
};
|
|
357
|
+
|
|
358
|
+
await fireHook(config, "on-error", ctx, process.cwd());
|
|
359
|
+
// NAX_ITERATION should be set to "3"
|
|
360
|
+
});
|
|
361
|
+
});
|