@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,286 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Hook Runner
|
|
3
|
+
*
|
|
4
|
+
* Loads hooks.json and executes hooks at lifecycle events.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { existsSync } from "node:fs";
|
|
8
|
+
import { join } from "node:path";
|
|
9
|
+
import { getLogger } from "../logger";
|
|
10
|
+
import type { HookContext, HookDef, HookEvent, HooksConfig } from "./types";
|
|
11
|
+
|
|
12
|
+
const DEFAULT_TIMEOUT = 5000;
|
|
13
|
+
|
|
14
|
+
/** Extended hooks config that tracks global vs project hooks */
|
|
15
|
+
export interface LoadedHooksConfig extends HooksConfig {
|
|
16
|
+
/** Global hooks (loaded from ~/.nax/hooks.json) */
|
|
17
|
+
_global?: HooksConfig;
|
|
18
|
+
/** Whether global hooks were skipped */
|
|
19
|
+
_skipGlobal?: boolean;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Load hooks config from global and project paths.
|
|
24
|
+
*
|
|
25
|
+
* Both global and project hooks are preserved independently (not merged).
|
|
26
|
+
* The skipGlobal flag in project config disables global hook loading.
|
|
27
|
+
*
|
|
28
|
+
* @param projectDir - Project nax directory path
|
|
29
|
+
* @param globalDir - Global nax directory path (optional)
|
|
30
|
+
* @returns Merged hooks config with both global and project hooks
|
|
31
|
+
*/
|
|
32
|
+
export async function loadHooksConfig(projectDir: string, globalDir?: string): Promise<LoadedHooksConfig> {
|
|
33
|
+
let globalHooks: HooksConfig = { hooks: {} };
|
|
34
|
+
let projectHooks: HooksConfig = { hooks: {} };
|
|
35
|
+
let skipGlobal = false;
|
|
36
|
+
|
|
37
|
+
// Load project hooks first to check skipGlobal flag
|
|
38
|
+
const projectPath = join(projectDir, "hooks.json");
|
|
39
|
+
if (existsSync(projectPath)) {
|
|
40
|
+
try {
|
|
41
|
+
const projectData = await Bun.file(projectPath).json();
|
|
42
|
+
projectHooks = projectData as HooksConfig;
|
|
43
|
+
// Check if project config has skipGlobal flag
|
|
44
|
+
skipGlobal = (projectData as { skipGlobal?: boolean }).skipGlobal ?? false;
|
|
45
|
+
} catch (err) {
|
|
46
|
+
const logger = getLogger();
|
|
47
|
+
logger.warn("hooks", "Failed to parse project hooks.json", { path: projectPath, error: String(err) });
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Load global hooks only if not skipped
|
|
52
|
+
if (!skipGlobal && globalDir) {
|
|
53
|
+
const globalPath = join(globalDir, "hooks.json");
|
|
54
|
+
if (existsSync(globalPath)) {
|
|
55
|
+
try {
|
|
56
|
+
const globalData = await Bun.file(globalPath).json();
|
|
57
|
+
globalHooks = globalData as HooksConfig;
|
|
58
|
+
} catch (err) {
|
|
59
|
+
const logger = getLogger();
|
|
60
|
+
logger.warn("hooks", "Failed to parse global hooks.json", { path: globalPath, error: String(err) });
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// Return project hooks as the main config, with global hooks stored separately
|
|
66
|
+
return {
|
|
67
|
+
...projectHooks,
|
|
68
|
+
_global: skipGlobal ? undefined : globalHooks,
|
|
69
|
+
_skipGlobal: skipGlobal,
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Escape environment variable values to prevent injection
|
|
75
|
+
* @param value - Raw value to escape
|
|
76
|
+
* @returns Escaped value safe for subprocess environment
|
|
77
|
+
*/
|
|
78
|
+
function escapeEnvValue(value: string): string {
|
|
79
|
+
// Remove null bytes and newlines that could cause issues
|
|
80
|
+
return value.replace(/\0/g, "").replace(/\n/g, " ").replace(/\r/g, "");
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Build environment variables from hook context
|
|
85
|
+
* All values are escaped to prevent injection attacks
|
|
86
|
+
*/
|
|
87
|
+
function buildEnv(ctx: HookContext): Record<string, string> {
|
|
88
|
+
const env: Record<string, string> = {
|
|
89
|
+
NAX_EVENT: escapeEnvValue(ctx.event),
|
|
90
|
+
NAX_FEATURE: escapeEnvValue(ctx.feature),
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
if (ctx.storyId) env.NAX_STORY_ID = escapeEnvValue(ctx.storyId);
|
|
94
|
+
if (ctx.status) env.NAX_STATUS = escapeEnvValue(ctx.status);
|
|
95
|
+
if (ctx.reason) env.NAX_REASON = escapeEnvValue(ctx.reason);
|
|
96
|
+
if (ctx.cost !== undefined) env.NAX_COST = ctx.cost.toFixed(4);
|
|
97
|
+
if (ctx.model) env.NAX_MODEL = escapeEnvValue(ctx.model);
|
|
98
|
+
if (ctx.agent) env.NAX_AGENT = escapeEnvValue(ctx.agent);
|
|
99
|
+
if (ctx.iteration !== undefined) env.NAX_ITERATION = String(ctx.iteration);
|
|
100
|
+
|
|
101
|
+
return env;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Detect shell operators that indicate shell interpolation
|
|
106
|
+
* @param command - Command string to check
|
|
107
|
+
* @returns true if shell operators detected
|
|
108
|
+
*/
|
|
109
|
+
function hasShellOperators(command: string): boolean {
|
|
110
|
+
// Check for common shell operators that require shell interpretation
|
|
111
|
+
const shellOperators = /[|&;$`<>(){}]/;
|
|
112
|
+
return shellOperators.test(command);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Validate hook command for injection patterns
|
|
117
|
+
* @param command - Command string to validate
|
|
118
|
+
* @throws Error if obvious injection pattern detected
|
|
119
|
+
*/
|
|
120
|
+
function validateHookCommand(command: string): void {
|
|
121
|
+
// Reject commands with obvious injection patterns
|
|
122
|
+
const dangerousPatterns = [
|
|
123
|
+
/\$\(.*\)/, // Command substitution $(...)
|
|
124
|
+
/`.*`/, // Backtick command substitution
|
|
125
|
+
/\|\s*bash/, // Piping to bash
|
|
126
|
+
/\|\s*sh/, // Piping to sh
|
|
127
|
+
/;\s*rm\s+-rf/, // Dangerous deletion
|
|
128
|
+
/&&\s*rm\s+-rf/, // Dangerous deletion after success
|
|
129
|
+
/\beval\s+/, // SEC-3 fix: eval command
|
|
130
|
+
/curl\s+[^|]*\|\s*/, // SEC-3 fix: curl piping
|
|
131
|
+
/python\s+-c/, // SEC-3 fix: python -c execution
|
|
132
|
+
];
|
|
133
|
+
|
|
134
|
+
for (const pattern of dangerousPatterns) {
|
|
135
|
+
if (pattern.test(command)) {
|
|
136
|
+
throw new Error(`Hook command contains dangerous pattern: ${pattern.source}`);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Parse command string into argv array
|
|
143
|
+
* Simple space-based splitting (does not handle complex quoting)
|
|
144
|
+
* @param command - Command string
|
|
145
|
+
* @returns Array of command and arguments
|
|
146
|
+
*/
|
|
147
|
+
function parseCommandToArgv(command: string): string[] {
|
|
148
|
+
return command.trim().split(/\s+/);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Execute a single hook
|
|
153
|
+
*
|
|
154
|
+
* SECURITY WARNING: Hook commands are executed as subprocesses.
|
|
155
|
+
* - Commands are parsed into argv arrays to avoid shell injection
|
|
156
|
+
* - Shell operators (|, &&, ;, $, etc.) trigger a security warning
|
|
157
|
+
* - Obvious injection patterns are rejected
|
|
158
|
+
* - Environment variables are escaped
|
|
159
|
+
* - Only configure hooks from trusted sources
|
|
160
|
+
*
|
|
161
|
+
* @param hookDef - Hook definition from config
|
|
162
|
+
* @param ctx - Hook context with environment variables
|
|
163
|
+
* @param workdir - Working directory for command execution
|
|
164
|
+
* @returns Promise with success status and output
|
|
165
|
+
*/
|
|
166
|
+
async function executeHook(
|
|
167
|
+
hookDef: HookDef,
|
|
168
|
+
ctx: HookContext,
|
|
169
|
+
workdir: string,
|
|
170
|
+
): Promise<{ success: boolean; output: string }> {
|
|
171
|
+
if (hookDef.enabled === false) {
|
|
172
|
+
return { success: true, output: "(disabled)" };
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// Validate command for injection patterns
|
|
176
|
+
try {
|
|
177
|
+
validateHookCommand(hookDef.command);
|
|
178
|
+
} catch (err) {
|
|
179
|
+
return {
|
|
180
|
+
success: false,
|
|
181
|
+
output: `Security validation failed: ${err}`,
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// Warn if shell operators detected
|
|
186
|
+
const logger = getLogger();
|
|
187
|
+
if (hasShellOperators(hookDef.command)) {
|
|
188
|
+
logger.warn("hooks", "[SECURITY] Hook command contains shell operators", {
|
|
189
|
+
command: hookDef.command,
|
|
190
|
+
warning: "Shell operators may enable injection attacks. Consider using simple commands only.",
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
const timeout = hookDef.timeout ?? DEFAULT_TIMEOUT;
|
|
195
|
+
const env = buildEnv(ctx);
|
|
196
|
+
|
|
197
|
+
// Pass full context as JSON via stdin
|
|
198
|
+
const contextJson = JSON.stringify(ctx);
|
|
199
|
+
|
|
200
|
+
// Parse command to argv array (no shell interpolation)
|
|
201
|
+
const argv = parseCommandToArgv(hookDef.command);
|
|
202
|
+
if (argv.length === 0) {
|
|
203
|
+
return { success: false, output: "Empty command" };
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
const proc = Bun.spawn(argv, {
|
|
207
|
+
cwd: workdir,
|
|
208
|
+
stdin: new Response(contextJson),
|
|
209
|
+
stdout: "pipe",
|
|
210
|
+
stderr: "pipe",
|
|
211
|
+
env: { ...process.env, ...env },
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
// Timeout handling
|
|
215
|
+
const timeoutId = setTimeout(() => {
|
|
216
|
+
proc.kill("SIGTERM");
|
|
217
|
+
}, timeout);
|
|
218
|
+
|
|
219
|
+
const exitCode = await proc.exited;
|
|
220
|
+
clearTimeout(timeoutId);
|
|
221
|
+
|
|
222
|
+
const stdout = await new Response(proc.stdout).text();
|
|
223
|
+
const stderr = await new Response(proc.stderr).text();
|
|
224
|
+
|
|
225
|
+
const output = (stdout + stderr).trim();
|
|
226
|
+
|
|
227
|
+
// Check if process was killed by timeout
|
|
228
|
+
if (exitCode !== 0 && output === "") {
|
|
229
|
+
return {
|
|
230
|
+
success: false,
|
|
231
|
+
output: `Hook timed out after ${timeout}ms`,
|
|
232
|
+
};
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
return {
|
|
236
|
+
success: exitCode === 0,
|
|
237
|
+
output,
|
|
238
|
+
};
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
/**
|
|
242
|
+
* Fire a hook event for both global and project hooks.
|
|
243
|
+
*
|
|
244
|
+
* Both hooks fire independently - global failure doesn't block project hook.
|
|
245
|
+
*
|
|
246
|
+
* @param config - Loaded hooks config (contains both global and project hooks)
|
|
247
|
+
* @param event - Hook event name
|
|
248
|
+
* @param ctx - Hook context
|
|
249
|
+
* @param workdir - Working directory
|
|
250
|
+
*/
|
|
251
|
+
export async function fireHook(
|
|
252
|
+
config: LoadedHooksConfig,
|
|
253
|
+
event: HookEvent,
|
|
254
|
+
ctx: HookContext,
|
|
255
|
+
workdir: string,
|
|
256
|
+
): Promise<void> {
|
|
257
|
+
const logger = getLogger();
|
|
258
|
+
|
|
259
|
+
// Fire global hook first (if present and not skipped)
|
|
260
|
+
if (config._global && !config._skipGlobal) {
|
|
261
|
+
const globalHookDef = config._global.hooks[event];
|
|
262
|
+
if (globalHookDef && globalHookDef.enabled !== false) {
|
|
263
|
+
try {
|
|
264
|
+
const result = await executeHook(globalHookDef, { ...ctx, event }, workdir);
|
|
265
|
+
if (!result.success) {
|
|
266
|
+
logger.warn("hooks", `Global hook ${event} failed`, { event, output: result.output });
|
|
267
|
+
}
|
|
268
|
+
} catch (err) {
|
|
269
|
+
logger.warn("hooks", `Global hook ${event} error`, { event, error: String(err) });
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
// Fire project hook (independent of global hook result)
|
|
275
|
+
const projectHookDef = config.hooks[event];
|
|
276
|
+
if (projectHookDef && projectHookDef.enabled !== false) {
|
|
277
|
+
try {
|
|
278
|
+
const result = await executeHook(projectHookDef, { ...ctx, event }, workdir);
|
|
279
|
+
if (!result.success) {
|
|
280
|
+
logger.warn("hooks", `Project hook ${event} failed`, { event, output: result.output });
|
|
281
|
+
}
|
|
282
|
+
} catch (err) {
|
|
283
|
+
logger.warn("hooks", `Project hook ${event} error`, { event, error: String(err) });
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Hook System Types
|
|
3
|
+
*
|
|
4
|
+
* Script-based lifecycle hooks configured via hooks.json.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
/** All supported hook events */
|
|
8
|
+
export type HookEvent =
|
|
9
|
+
| "on-start"
|
|
10
|
+
| "on-story-start"
|
|
11
|
+
| "on-story-complete"
|
|
12
|
+
| "on-story-fail"
|
|
13
|
+
| "on-pause"
|
|
14
|
+
| "on-resume"
|
|
15
|
+
| "on-session-end"
|
|
16
|
+
| "on-complete"
|
|
17
|
+
| "on-error";
|
|
18
|
+
|
|
19
|
+
/** Single hook definition */
|
|
20
|
+
export interface HookDef {
|
|
21
|
+
/** Command to execute */
|
|
22
|
+
command: string;
|
|
23
|
+
/** Timeout in milliseconds (default: 5000) */
|
|
24
|
+
timeout?: number;
|
|
25
|
+
/** Whether this hook is enabled (default: true) */
|
|
26
|
+
enabled?: boolean;
|
|
27
|
+
/** Interaction prompt (v0.15.0) */
|
|
28
|
+
interaction?: {
|
|
29
|
+
/** Interaction type */
|
|
30
|
+
type: "confirm" | "choose" | "input" | "review" | "notify";
|
|
31
|
+
/** Summary template (supports {{variable}} syntax) */
|
|
32
|
+
summary: string;
|
|
33
|
+
/** Detail template (optional) */
|
|
34
|
+
detail?: string;
|
|
35
|
+
/** Fallback behavior on timeout */
|
|
36
|
+
fallback: "continue" | "skip" | "escalate" | "abort";
|
|
37
|
+
/** Timeout in milliseconds (optional) */
|
|
38
|
+
timeout?: number;
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/** hooks.json schema */
|
|
43
|
+
export interface HooksConfig {
|
|
44
|
+
hooks: Partial<Record<HookEvent, HookDef>>;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/** Context passed to hooks via environment variables */
|
|
48
|
+
export interface HookContext {
|
|
49
|
+
/** Event name */
|
|
50
|
+
event: HookEvent;
|
|
51
|
+
/** Feature name */
|
|
52
|
+
feature: string;
|
|
53
|
+
/** Current story ID */
|
|
54
|
+
storyId?: string;
|
|
55
|
+
/** Status (pass/fail/paused/error) */
|
|
56
|
+
status?: string;
|
|
57
|
+
/** Reason for pause/error */
|
|
58
|
+
reason?: string;
|
|
59
|
+
/** Accumulated cost (USD) */
|
|
60
|
+
cost?: number;
|
|
61
|
+
/** Current model */
|
|
62
|
+
model?: string;
|
|
63
|
+
/** Current agent */
|
|
64
|
+
agent?: string;
|
|
65
|
+
/** Current iteration number */
|
|
66
|
+
iteration?: number;
|
|
67
|
+
}
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Interaction Plugin Chain (v0.15.0)
|
|
3
|
+
*
|
|
4
|
+
* Manages plugin priority, fallback cascade, and request routing.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { InteractionFallback, InteractionPlugin, InteractionRequest, InteractionResponse } from "./types";
|
|
8
|
+
|
|
9
|
+
/** Plugin chain entry */
|
|
10
|
+
interface ChainEntry {
|
|
11
|
+
plugin: InteractionPlugin;
|
|
12
|
+
priority: number;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/** Plugin chain configuration */
|
|
16
|
+
export interface ChainConfig {
|
|
17
|
+
/** Default timeout for all requests (ms) */
|
|
18
|
+
defaultTimeout: number;
|
|
19
|
+
/** Default fallback behavior */
|
|
20
|
+
defaultFallback: InteractionFallback;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Interaction plugin chain with priority and fallback cascade
|
|
25
|
+
*/
|
|
26
|
+
export class InteractionChain {
|
|
27
|
+
private plugins: ChainEntry[] = [];
|
|
28
|
+
private config: ChainConfig;
|
|
29
|
+
|
|
30
|
+
constructor(config: ChainConfig) {
|
|
31
|
+
this.config = config;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Register a plugin with priority (higher = earlier in chain)
|
|
36
|
+
*/
|
|
37
|
+
register(plugin: InteractionPlugin, priority = 0): void {
|
|
38
|
+
this.plugins.push({ plugin, priority });
|
|
39
|
+
this.plugins.sort((a, b) => b.priority - a.priority); // descending
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Get primary plugin (highest priority)
|
|
44
|
+
*/
|
|
45
|
+
getPrimary(): InteractionPlugin | null {
|
|
46
|
+
return this.plugins[0]?.plugin ?? null;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Send interaction request through the chain
|
|
51
|
+
*/
|
|
52
|
+
async send(request: InteractionRequest): Promise<void> {
|
|
53
|
+
const plugin = this.getPrimary();
|
|
54
|
+
if (!plugin) {
|
|
55
|
+
throw new Error("No interaction plugin registered");
|
|
56
|
+
}
|
|
57
|
+
await plugin.send(request);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Receive interaction response with timeout and fallback cascade
|
|
62
|
+
*/
|
|
63
|
+
async receive(requestId: string, timeout?: number): Promise<InteractionResponse> {
|
|
64
|
+
if (this.plugins.length === 0) {
|
|
65
|
+
throw new Error("No interaction plugin registered");
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const timeoutMs = timeout ?? this.config.defaultTimeout;
|
|
69
|
+
const errors: Error[] = [];
|
|
70
|
+
|
|
71
|
+
// Try each plugin in priority order (fallback cascade)
|
|
72
|
+
for (const entry of this.plugins) {
|
|
73
|
+
try {
|
|
74
|
+
const response = await entry.plugin.receive(requestId, timeoutMs);
|
|
75
|
+
return response;
|
|
76
|
+
} catch (err) {
|
|
77
|
+
const error = err instanceof Error ? err : new Error(String(err));
|
|
78
|
+
errors.push(error);
|
|
79
|
+
// Continue to next plugin
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// All plugins failed - throw with all error messages
|
|
84
|
+
const errorMessages = errors.map((e) => e.message).join("; ");
|
|
85
|
+
throw new Error(`All interaction plugins failed: ${errorMessages}`);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Send and receive in one call (convenience method)
|
|
90
|
+
*/
|
|
91
|
+
async prompt(request: InteractionRequest): Promise<InteractionResponse> {
|
|
92
|
+
await this.send(request);
|
|
93
|
+
const response = await this.receive(request.id, request.timeout);
|
|
94
|
+
return response;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Cancel a pending interaction
|
|
99
|
+
*/
|
|
100
|
+
async cancel(requestId: string): Promise<void> {
|
|
101
|
+
const plugin = this.getPrimary();
|
|
102
|
+
if (plugin?.cancel) {
|
|
103
|
+
await plugin.cancel(requestId);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Initialize all plugins
|
|
109
|
+
*/
|
|
110
|
+
async init(pluginConfigs: Record<string, Record<string, unknown>>): Promise<void> {
|
|
111
|
+
for (const entry of this.plugins) {
|
|
112
|
+
if (entry.plugin.init) {
|
|
113
|
+
const config = pluginConfigs[entry.plugin.name] ?? {};
|
|
114
|
+
await entry.plugin.init(config);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Destroy all plugins
|
|
121
|
+
*/
|
|
122
|
+
async destroy(): Promise<void> {
|
|
123
|
+
for (const entry of this.plugins) {
|
|
124
|
+
if (entry.plugin.destroy) {
|
|
125
|
+
await entry.plugin.destroy();
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Apply fallback behavior to get final action
|
|
132
|
+
*/
|
|
133
|
+
applyFallback(response: InteractionResponse, fallback: InteractionFallback): InteractionAction {
|
|
134
|
+
// If user responded explicitly, use their action
|
|
135
|
+
if (response.respondedBy !== "timeout" && response.respondedBy !== "system") {
|
|
136
|
+
return response.action;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// Otherwise apply fallback
|
|
140
|
+
switch (fallback) {
|
|
141
|
+
case "continue":
|
|
142
|
+
return "approve";
|
|
143
|
+
case "skip":
|
|
144
|
+
return "skip";
|
|
145
|
+
case "escalate":
|
|
146
|
+
return "approve"; // proceed but escalate tier
|
|
147
|
+
case "abort":
|
|
148
|
+
return "abort";
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/** Convenience type for action */
|
|
154
|
+
type InteractionAction = InteractionResponse["action"];
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Interaction System — Barrel Exports (v0.15.0)
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
// Types
|
|
6
|
+
export type {
|
|
7
|
+
InteractionType,
|
|
8
|
+
InteractionStage,
|
|
9
|
+
InteractionFallback,
|
|
10
|
+
InteractionRequest,
|
|
11
|
+
InteractionAction,
|
|
12
|
+
InteractionResponse,
|
|
13
|
+
InteractionPlugin,
|
|
14
|
+
TriggerName,
|
|
15
|
+
TriggerConfig,
|
|
16
|
+
TriggerSafety,
|
|
17
|
+
TriggerMetadata,
|
|
18
|
+
} from "./types";
|
|
19
|
+
export { TRIGGER_METADATA } from "./types";
|
|
20
|
+
|
|
21
|
+
// Chain
|
|
22
|
+
export { InteractionChain } from "./chain";
|
|
23
|
+
export type { ChainConfig } from "./chain";
|
|
24
|
+
|
|
25
|
+
// State persistence
|
|
26
|
+
export {
|
|
27
|
+
serializeRunState,
|
|
28
|
+
deserializeRunState,
|
|
29
|
+
clearRunState,
|
|
30
|
+
savePendingInteraction,
|
|
31
|
+
loadPendingInteraction,
|
|
32
|
+
deletePendingInteraction,
|
|
33
|
+
listPendingInteractions,
|
|
34
|
+
} from "./state";
|
|
35
|
+
export type { RunState } from "./state";
|
|
36
|
+
|
|
37
|
+
// Plugins
|
|
38
|
+
export { CLIInteractionPlugin } from "./plugins/cli";
|
|
39
|
+
export { TelegramInteractionPlugin } from "./plugins/telegram";
|
|
40
|
+
export { WebhookInteractionPlugin } from "./plugins/webhook";
|
|
41
|
+
export { AutoInteractionPlugin } from "./plugins/auto";
|
|
42
|
+
|
|
43
|
+
// Triggers
|
|
44
|
+
export {
|
|
45
|
+
isTriggerEnabled,
|
|
46
|
+
createTriggerRequest,
|
|
47
|
+
executeTrigger,
|
|
48
|
+
checkSecurityReview,
|
|
49
|
+
checkCostExceeded,
|
|
50
|
+
checkMergeConflict,
|
|
51
|
+
checkCostWarning,
|
|
52
|
+
checkMaxRetries,
|
|
53
|
+
checkPreMerge,
|
|
54
|
+
checkStoryAmbiguity,
|
|
55
|
+
checkReviewGate,
|
|
56
|
+
} from "./triggers";
|
|
57
|
+
export type { TriggerContext } from "./triggers";
|
|
58
|
+
|
|
59
|
+
// Initialization
|
|
60
|
+
export { initInteractionChain } from "./init";
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Interaction Chain Initialization Helper
|
|
3
|
+
*
|
|
4
|
+
* Creates and initializes interaction chain from config.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { NaxConfig } from "../config";
|
|
8
|
+
import { getSafeLogger } from "../logger";
|
|
9
|
+
import { InteractionChain } from "./chain";
|
|
10
|
+
import { AutoInteractionPlugin } from "./plugins/auto";
|
|
11
|
+
import { CLIInteractionPlugin } from "./plugins/cli";
|
|
12
|
+
import { TelegramInteractionPlugin } from "./plugins/telegram";
|
|
13
|
+
import { WebhookInteractionPlugin } from "./plugins/webhook";
|
|
14
|
+
import type { InteractionPlugin } from "./types";
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Create interaction plugin based on config
|
|
18
|
+
*/
|
|
19
|
+
function createInteractionPlugin(pluginName: string): InteractionPlugin {
|
|
20
|
+
switch (pluginName) {
|
|
21
|
+
case "cli":
|
|
22
|
+
return new CLIInteractionPlugin();
|
|
23
|
+
case "telegram":
|
|
24
|
+
return new TelegramInteractionPlugin();
|
|
25
|
+
case "webhook":
|
|
26
|
+
return new WebhookInteractionPlugin();
|
|
27
|
+
case "auto":
|
|
28
|
+
return new AutoInteractionPlugin();
|
|
29
|
+
default:
|
|
30
|
+
throw new Error(`Unknown interaction plugin: ${pluginName}`);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Initialize interaction chain from config
|
|
36
|
+
*
|
|
37
|
+
* @param config - Nax configuration
|
|
38
|
+
* @param headless - Whether running in headless mode (skip interactions)
|
|
39
|
+
* @returns Initialized interaction chain or null if disabled/headless
|
|
40
|
+
*/
|
|
41
|
+
export async function initInteractionChain(config: NaxConfig, headless: boolean): Promise<InteractionChain | null> {
|
|
42
|
+
const logger = getSafeLogger();
|
|
43
|
+
|
|
44
|
+
// If headless mode, skip interaction system
|
|
45
|
+
if (headless) {
|
|
46
|
+
logger?.debug("interaction", "Headless mode - skipping interaction system");
|
|
47
|
+
return null;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// If no interaction config, skip
|
|
51
|
+
if (!config.interaction) {
|
|
52
|
+
logger?.debug("interaction", "No interaction config - skipping interaction system");
|
|
53
|
+
return null;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Create chain
|
|
57
|
+
const chain = new InteractionChain({
|
|
58
|
+
defaultTimeout: config.interaction.defaults.timeout,
|
|
59
|
+
defaultFallback: config.interaction.defaults.fallback,
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
// Create and register plugin
|
|
63
|
+
const pluginName = config.interaction.plugin;
|
|
64
|
+
try {
|
|
65
|
+
const plugin = createInteractionPlugin(pluginName);
|
|
66
|
+
chain.register(plugin, 100);
|
|
67
|
+
|
|
68
|
+
// Initialize plugin
|
|
69
|
+
const pluginConfig = config.interaction.config ?? {};
|
|
70
|
+
await chain.init({ [pluginName]: pluginConfig });
|
|
71
|
+
|
|
72
|
+
logger?.info("interaction", `Initialized ${pluginName} interaction plugin`, {
|
|
73
|
+
timeout: config.interaction.defaults.timeout,
|
|
74
|
+
fallback: config.interaction.defaults.fallback,
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
return chain;
|
|
78
|
+
} catch (err) {
|
|
79
|
+
const error = err instanceof Error ? err.message : String(err);
|
|
80
|
+
logger?.error("interaction", `Failed to initialize interaction plugin: ${error}`);
|
|
81
|
+
throw err;
|
|
82
|
+
}
|
|
83
|
+
}
|