@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,389 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Interaction Plugins Network Failure Tests (v0.15.1)
|
|
3
|
+
*
|
|
4
|
+
* Tests network error handling, exponential backoff, payload limits, and malformed input.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { describe, expect, test } from "bun:test";
|
|
8
|
+
import type { InteractionRequest } from "../../src/interaction";
|
|
9
|
+
import { TelegramInteractionPlugin } from "../../src/interaction/plugins/telegram";
|
|
10
|
+
import { WebhookInteractionPlugin } from "../../src/interaction/plugins/webhook";
|
|
11
|
+
|
|
12
|
+
describe("TelegramInteractionPlugin - Network Failures", () => {
|
|
13
|
+
test("should handle network error in send()", async () => {
|
|
14
|
+
const plugin = new TelegramInteractionPlugin();
|
|
15
|
+
await plugin.init({ botToken: "test-token", chatId: "12345" });
|
|
16
|
+
|
|
17
|
+
// Mock fetch to throw network error
|
|
18
|
+
const originalFetch = globalThis.fetch;
|
|
19
|
+
globalThis.fetch = async () => {
|
|
20
|
+
throw new Error("ECONNREFUSED");
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
const request: InteractionRequest = {
|
|
24
|
+
id: "test-network-error",
|
|
25
|
+
type: "confirm",
|
|
26
|
+
featureName: "test-feature",
|
|
27
|
+
stage: "review",
|
|
28
|
+
summary: "Test network error",
|
|
29
|
+
fallback: "abort",
|
|
30
|
+
createdAt: Date.now(),
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
await expect(plugin.send(request)).rejects.toThrow("Failed to send Telegram message");
|
|
34
|
+
|
|
35
|
+
// Restore
|
|
36
|
+
globalThis.fetch = originalFetch;
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
test("should handle malformed API response in send()", async () => {
|
|
40
|
+
const plugin = new TelegramInteractionPlugin();
|
|
41
|
+
await plugin.init({ botToken: "test-token", chatId: "12345" });
|
|
42
|
+
|
|
43
|
+
// Mock fetch to return invalid JSON
|
|
44
|
+
const originalFetch = globalThis.fetch;
|
|
45
|
+
globalThis.fetch = async () => {
|
|
46
|
+
return new Response("not json", { status: 200 });
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
const request: InteractionRequest = {
|
|
50
|
+
id: "test-malformed-response",
|
|
51
|
+
type: "confirm",
|
|
52
|
+
featureName: "test-feature",
|
|
53
|
+
stage: "review",
|
|
54
|
+
summary: "Test malformed response",
|
|
55
|
+
fallback: "abort",
|
|
56
|
+
createdAt: Date.now(),
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
await expect(plugin.send(request)).rejects.toThrow("Failed to send Telegram message");
|
|
60
|
+
|
|
61
|
+
// Restore
|
|
62
|
+
globalThis.fetch = originalFetch;
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
test("should handle HTTP error status in send()", async () => {
|
|
66
|
+
const plugin = new TelegramInteractionPlugin();
|
|
67
|
+
await plugin.init({ botToken: "test-token", chatId: "12345" });
|
|
68
|
+
|
|
69
|
+
// Mock fetch to return 500 error
|
|
70
|
+
const originalFetch = globalThis.fetch;
|
|
71
|
+
globalThis.fetch = async () => {
|
|
72
|
+
return new Response("Internal Server Error", { status: 500 });
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
const request: InteractionRequest = {
|
|
76
|
+
id: "test-http-error",
|
|
77
|
+
type: "confirm",
|
|
78
|
+
featureName: "test-feature",
|
|
79
|
+
stage: "review",
|
|
80
|
+
summary: "Test HTTP error",
|
|
81
|
+
fallback: "abort",
|
|
82
|
+
createdAt: Date.now(),
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
await expect(plugin.send(request)).rejects.toThrow("Telegram API error (500)");
|
|
86
|
+
|
|
87
|
+
// Restore
|
|
88
|
+
globalThis.fetch = originalFetch;
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
test("should return empty updates on getUpdates() network failure", async () => {
|
|
92
|
+
const plugin = new TelegramInteractionPlugin();
|
|
93
|
+
await plugin.init({ botToken: "test-token", chatId: "12345" });
|
|
94
|
+
|
|
95
|
+
// Mock fetch to throw network error
|
|
96
|
+
const originalFetch = globalThis.fetch;
|
|
97
|
+
globalThis.fetch = async () => {
|
|
98
|
+
throw new Error("Network timeout");
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
// Access private method via type assertion for testing
|
|
102
|
+
const getUpdates = (plugin as unknown as { getUpdates: () => Promise<unknown[]> }).getUpdates;
|
|
103
|
+
const updates = await getUpdates.call(plugin);
|
|
104
|
+
|
|
105
|
+
expect(updates).toEqual([]);
|
|
106
|
+
|
|
107
|
+
// Restore
|
|
108
|
+
globalThis.fetch = originalFetch;
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
test("should apply exponential backoff on consecutive getUpdates() failures", async () => {
|
|
112
|
+
const plugin = new TelegramInteractionPlugin();
|
|
113
|
+
await plugin.init({ botToken: "test-token", chatId: "12345" });
|
|
114
|
+
|
|
115
|
+
const originalFetch = globalThis.fetch;
|
|
116
|
+
let fetchCallCount = 0;
|
|
117
|
+
|
|
118
|
+
globalThis.fetch = async () => {
|
|
119
|
+
fetchCallCount++;
|
|
120
|
+
throw new Error("Network error");
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
// Access private getUpdates
|
|
124
|
+
const getUpdates = (plugin as unknown as { getUpdates: () => Promise<unknown[]> }).getUpdates;
|
|
125
|
+
|
|
126
|
+
// Call getUpdates multiple times to trigger backoff
|
|
127
|
+
await getUpdates.call(plugin);
|
|
128
|
+
await getUpdates.call(plugin);
|
|
129
|
+
await getUpdates.call(plugin);
|
|
130
|
+
|
|
131
|
+
// Verify backoff is increasing (check private backoffMs property)
|
|
132
|
+
const backoffMs = (plugin as unknown as { backoffMs: number }).backoffMs;
|
|
133
|
+
expect(backoffMs).toBeGreaterThan(1000); // Should have increased from initial 1000ms
|
|
134
|
+
|
|
135
|
+
// Restore
|
|
136
|
+
globalThis.fetch = originalFetch;
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
test("should reset backoff on successful getUpdates()", async () => {
|
|
140
|
+
const plugin = new TelegramInteractionPlugin();
|
|
141
|
+
await plugin.init({ botToken: "test-token", chatId: "12345" });
|
|
142
|
+
|
|
143
|
+
const originalFetch = globalThis.fetch;
|
|
144
|
+
let callCount = 0;
|
|
145
|
+
|
|
146
|
+
globalThis.fetch = async () => {
|
|
147
|
+
callCount++;
|
|
148
|
+
if (callCount === 1) {
|
|
149
|
+
// First call fails
|
|
150
|
+
throw new Error("Network error");
|
|
151
|
+
}
|
|
152
|
+
// Second call succeeds
|
|
153
|
+
return new Response(JSON.stringify({ ok: true, result: [] }), { status: 200 });
|
|
154
|
+
};
|
|
155
|
+
|
|
156
|
+
const getUpdates = (plugin as unknown as { getUpdates: () => Promise<unknown[]> }).getUpdates;
|
|
157
|
+
|
|
158
|
+
// First call - triggers backoff
|
|
159
|
+
await getUpdates.call(plugin);
|
|
160
|
+
const backoffAfterFailure = (plugin as unknown as { backoffMs: number }).backoffMs;
|
|
161
|
+
expect(backoffAfterFailure).toBeGreaterThan(1000);
|
|
162
|
+
|
|
163
|
+
// Second call - should reset backoff
|
|
164
|
+
await getUpdates.call(plugin);
|
|
165
|
+
const backoffAfterSuccess = (plugin as unknown as { backoffMs: number }).backoffMs;
|
|
166
|
+
expect(backoffAfterSuccess).toBe(1000); // Reset to initial value
|
|
167
|
+
|
|
168
|
+
// Restore
|
|
169
|
+
globalThis.fetch = originalFetch;
|
|
170
|
+
});
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
describe("WebhookInteractionPlugin - Network Failures", () => {
|
|
174
|
+
test("should handle network error in send()", async () => {
|
|
175
|
+
const plugin = new WebhookInteractionPlugin();
|
|
176
|
+
await plugin.init({ url: "https://example.com/webhook" });
|
|
177
|
+
|
|
178
|
+
// Mock fetch to throw network error
|
|
179
|
+
const originalFetch = globalThis.fetch;
|
|
180
|
+
globalThis.fetch = async () => {
|
|
181
|
+
throw new Error("ECONNREFUSED");
|
|
182
|
+
};
|
|
183
|
+
|
|
184
|
+
const request: InteractionRequest = {
|
|
185
|
+
id: "test-network-error",
|
|
186
|
+
type: "confirm",
|
|
187
|
+
featureName: "test-feature",
|
|
188
|
+
stage: "review",
|
|
189
|
+
summary: "Test network error",
|
|
190
|
+
fallback: "abort",
|
|
191
|
+
createdAt: Date.now(),
|
|
192
|
+
};
|
|
193
|
+
|
|
194
|
+
await expect(plugin.send(request)).rejects.toThrow("Failed to send webhook request");
|
|
195
|
+
|
|
196
|
+
// Restore
|
|
197
|
+
globalThis.fetch = originalFetch;
|
|
198
|
+
await plugin.destroy();
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
test("should handle HTTP error in send()", async () => {
|
|
202
|
+
const plugin = new WebhookInteractionPlugin();
|
|
203
|
+
await plugin.init({ url: "https://example.com/webhook" });
|
|
204
|
+
|
|
205
|
+
// Mock fetch to return 503 error
|
|
206
|
+
const originalFetch = globalThis.fetch;
|
|
207
|
+
globalThis.fetch = async () => {
|
|
208
|
+
return new Response("Service Unavailable", { status: 503 });
|
|
209
|
+
};
|
|
210
|
+
|
|
211
|
+
const request: InteractionRequest = {
|
|
212
|
+
id: "test-http-error",
|
|
213
|
+
type: "confirm",
|
|
214
|
+
featureName: "test-feature",
|
|
215
|
+
stage: "review",
|
|
216
|
+
summary: "Test HTTP error",
|
|
217
|
+
fallback: "abort",
|
|
218
|
+
createdAt: Date.now(),
|
|
219
|
+
};
|
|
220
|
+
|
|
221
|
+
await expect(plugin.send(request)).rejects.toThrow("Webhook POST failed (503)");
|
|
222
|
+
|
|
223
|
+
// Restore
|
|
224
|
+
globalThis.fetch = originalFetch;
|
|
225
|
+
await plugin.destroy();
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
test("should apply exponential backoff in receive() polling", async () => {
|
|
229
|
+
const plugin = new WebhookInteractionPlugin();
|
|
230
|
+
await plugin.init({ url: "https://example.com/webhook" });
|
|
231
|
+
|
|
232
|
+
const startTime = Date.now();
|
|
233
|
+
|
|
234
|
+
// Call receive with short timeout
|
|
235
|
+
const response = await plugin.receive("test-request", 500);
|
|
236
|
+
|
|
237
|
+
const duration = Date.now() - startTime;
|
|
238
|
+
|
|
239
|
+
// Should timeout with exponential backoff
|
|
240
|
+
expect(response.action).toBe("skip");
|
|
241
|
+
expect(response.respondedBy).toBe("timeout");
|
|
242
|
+
|
|
243
|
+
// Duration should be at least timeout (500ms) but less than timeout + max backoff
|
|
244
|
+
expect(duration).toBeGreaterThanOrEqual(500);
|
|
245
|
+
expect(duration).toBeLessThan(3000); // Max backoff is 2s
|
|
246
|
+
|
|
247
|
+
await plugin.destroy();
|
|
248
|
+
});
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
describe("WebhookInteractionPlugin - Payload Security", () => {
|
|
252
|
+
test("should reject oversized payload via Content-Length header", async () => {
|
|
253
|
+
const plugin = new WebhookInteractionPlugin();
|
|
254
|
+
await plugin.init({ url: "https://example.com/webhook", maxPayloadBytes: 1000 });
|
|
255
|
+
|
|
256
|
+
// Create a mock request with large Content-Length
|
|
257
|
+
const req = new Request("http://localhost:8765/nax/interact/test-id", {
|
|
258
|
+
method: "POST",
|
|
259
|
+
headers: {
|
|
260
|
+
"Content-Type": "application/json",
|
|
261
|
+
"Content-Length": "10000", // 10KB - exceeds 1000 byte limit
|
|
262
|
+
},
|
|
263
|
+
body: JSON.stringify({ requestId: "test-id", action: "approve" }),
|
|
264
|
+
});
|
|
265
|
+
|
|
266
|
+
const handleRequest = (plugin as unknown as { handleRequest: (req: Request) => Promise<Response> }).handleRequest;
|
|
267
|
+
const response = await handleRequest.call(plugin, req);
|
|
268
|
+
|
|
269
|
+
expect(response.status).toBe(413); // Payload Too Large
|
|
270
|
+
expect(await response.text()).toBe("Payload Too Large");
|
|
271
|
+
|
|
272
|
+
await plugin.destroy();
|
|
273
|
+
});
|
|
274
|
+
|
|
275
|
+
test("should reject oversized payload by actual body size", async () => {
|
|
276
|
+
const plugin = new WebhookInteractionPlugin();
|
|
277
|
+
await plugin.init({ url: "https://example.com/webhook", secret: "test-secret", maxPayloadBytes: 100 });
|
|
278
|
+
|
|
279
|
+
// Create a large payload
|
|
280
|
+
const largePayload = "x".repeat(200);
|
|
281
|
+
|
|
282
|
+
const req = new Request("http://localhost:8765/nax/interact/test-id", {
|
|
283
|
+
method: "POST",
|
|
284
|
+
headers: {
|
|
285
|
+
"Content-Type": "application/json",
|
|
286
|
+
"X-Nax-Signature": "dummy-signature",
|
|
287
|
+
},
|
|
288
|
+
body: largePayload,
|
|
289
|
+
});
|
|
290
|
+
|
|
291
|
+
const handleRequest = (plugin as unknown as { handleRequest: (req: Request) => Promise<Response> }).handleRequest;
|
|
292
|
+
const response = await handleRequest.call(plugin, req);
|
|
293
|
+
|
|
294
|
+
expect(response.status).toBe(413); // Payload Too Large
|
|
295
|
+
|
|
296
|
+
await plugin.destroy();
|
|
297
|
+
});
|
|
298
|
+
|
|
299
|
+
test("should reject malformed JSON with sanitized error", async () => {
|
|
300
|
+
const plugin = new WebhookInteractionPlugin();
|
|
301
|
+
await plugin.init({ url: "https://example.com/webhook" });
|
|
302
|
+
|
|
303
|
+
const req = new Request("http://localhost:8765/nax/interact/test-id", {
|
|
304
|
+
method: "POST",
|
|
305
|
+
headers: { "Content-Type": "application/json" },
|
|
306
|
+
body: "not valid json{",
|
|
307
|
+
});
|
|
308
|
+
|
|
309
|
+
const handleRequest = (plugin as unknown as { handleRequest: (req: Request) => Promise<Response> }).handleRequest;
|
|
310
|
+
const response = await handleRequest.call(plugin, req);
|
|
311
|
+
|
|
312
|
+
expect(response.status).toBe(400);
|
|
313
|
+
const errorText = await response.text();
|
|
314
|
+
|
|
315
|
+
// Should not leak parse error details
|
|
316
|
+
expect(errorText).toBe("Bad Request: Invalid response format");
|
|
317
|
+
expect(errorText).not.toContain("JSON");
|
|
318
|
+
expect(errorText).not.toContain("parse");
|
|
319
|
+
expect(errorText).not.toContain("Unexpected");
|
|
320
|
+
|
|
321
|
+
await plugin.destroy();
|
|
322
|
+
});
|
|
323
|
+
|
|
324
|
+
test("should reject invalid schema with sanitized error", async () => {
|
|
325
|
+
const plugin = new WebhookInteractionPlugin();
|
|
326
|
+
await plugin.init({ url: "https://example.com/webhook" });
|
|
327
|
+
|
|
328
|
+
// Valid JSON but invalid InteractionResponse schema
|
|
329
|
+
const req = new Request("http://localhost:8765/nax/interact/test-id", {
|
|
330
|
+
method: "POST",
|
|
331
|
+
headers: { "Content-Type": "application/json" },
|
|
332
|
+
body: JSON.stringify({ malicious: "payload", action: "invalid-action" }),
|
|
333
|
+
});
|
|
334
|
+
|
|
335
|
+
const handleRequest = (plugin as unknown as { handleRequest: (req: Request) => Promise<Response> }).handleRequest;
|
|
336
|
+
const response = await handleRequest.call(plugin, req);
|
|
337
|
+
|
|
338
|
+
expect(response.status).toBe(400);
|
|
339
|
+
const errorText = await response.text();
|
|
340
|
+
|
|
341
|
+
// Should not leak Zod validation error details
|
|
342
|
+
expect(errorText).toBe("Bad Request: Invalid response format");
|
|
343
|
+
expect(errorText).not.toContain("Zod");
|
|
344
|
+
expect(errorText).not.toContain("validation");
|
|
345
|
+
expect(errorText).not.toContain("enum");
|
|
346
|
+
|
|
347
|
+
await plugin.destroy();
|
|
348
|
+
});
|
|
349
|
+
|
|
350
|
+
test("should reject request without signature when secret is configured", async () => {
|
|
351
|
+
const plugin = new WebhookInteractionPlugin();
|
|
352
|
+
await plugin.init({ url: "https://example.com/webhook", secret: "test-secret" });
|
|
353
|
+
|
|
354
|
+
const req = new Request("http://localhost:8765/nax/interact/test-id", {
|
|
355
|
+
method: "POST",
|
|
356
|
+
headers: { "Content-Type": "application/json" },
|
|
357
|
+
body: JSON.stringify({ requestId: "test-id", action: "approve", respondedAt: Date.now() }),
|
|
358
|
+
});
|
|
359
|
+
|
|
360
|
+
const handleRequest = (plugin as unknown as { handleRequest: (req: Request) => Promise<Response> }).handleRequest;
|
|
361
|
+
const response = await handleRequest.call(plugin, req);
|
|
362
|
+
|
|
363
|
+
expect(response.status).toBe(401); // Unauthorized
|
|
364
|
+
expect(await response.text()).toBe("Unauthorized");
|
|
365
|
+
|
|
366
|
+
await plugin.destroy();
|
|
367
|
+
});
|
|
368
|
+
|
|
369
|
+
test("should reject request with invalid signature", async () => {
|
|
370
|
+
const plugin = new WebhookInteractionPlugin();
|
|
371
|
+
await plugin.init({ url: "https://example.com/webhook", secret: "test-secret" });
|
|
372
|
+
|
|
373
|
+
const req = new Request("http://localhost:8765/nax/interact/test-id", {
|
|
374
|
+
method: "POST",
|
|
375
|
+
headers: {
|
|
376
|
+
"Content-Type": "application/json",
|
|
377
|
+
"X-Nax-Signature": "invalid-signature",
|
|
378
|
+
},
|
|
379
|
+
body: JSON.stringify({ requestId: "test-id", action: "approve", respondedAt: Date.now() }),
|
|
380
|
+
});
|
|
381
|
+
|
|
382
|
+
const handleRequest = (plugin as unknown as { handleRequest: (req: Request) => Promise<Response> }).handleRequest;
|
|
383
|
+
const response = await handleRequest.call(plugin, req);
|
|
384
|
+
|
|
385
|
+
expect(response.status).toBe(401); // Unauthorized
|
|
386
|
+
|
|
387
|
+
await plugin.destroy();
|
|
388
|
+
});
|
|
389
|
+
});
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Interaction Plugins Unit Tests (v0.15.0 Phase 2)
|
|
3
|
+
*
|
|
4
|
+
* Tests for Telegram, Webhook, and Auto plugins.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { describe, expect, test } from "bun:test";
|
|
8
|
+
import type { InteractionRequest } from "../../src/interaction";
|
|
9
|
+
import { AutoInteractionPlugin } from "../../src/interaction/plugins/auto";
|
|
10
|
+
import { TelegramInteractionPlugin } from "../../src/interaction/plugins/telegram";
|
|
11
|
+
import { WebhookInteractionPlugin } from "../../src/interaction/plugins/webhook";
|
|
12
|
+
|
|
13
|
+
describe("TelegramInteractionPlugin", () => {
|
|
14
|
+
test("should validate required config", async () => {
|
|
15
|
+
const plugin = new TelegramInteractionPlugin();
|
|
16
|
+
|
|
17
|
+
// Should throw without botToken and chatId
|
|
18
|
+
await expect(plugin.init({})).rejects.toThrow("botToken and chatId");
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
test("should initialize with config", async () => {
|
|
22
|
+
const plugin = new TelegramInteractionPlugin();
|
|
23
|
+
|
|
24
|
+
await plugin.init({
|
|
25
|
+
botToken: "test-token",
|
|
26
|
+
chatId: "12345",
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
expect(plugin.name).toBe("telegram");
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
test("should initialize with env vars", async () => {
|
|
33
|
+
const plugin = new TelegramInteractionPlugin();
|
|
34
|
+
|
|
35
|
+
// Set env vars
|
|
36
|
+
process.env.NAX_TELEGRAM_TOKEN = "env-token";
|
|
37
|
+
process.env.NAX_TELEGRAM_CHAT_ID = "env-chat";
|
|
38
|
+
|
|
39
|
+
await plugin.init({});
|
|
40
|
+
|
|
41
|
+
expect(plugin.name).toBe("telegram");
|
|
42
|
+
|
|
43
|
+
// Cleanup
|
|
44
|
+
process.env.NAX_TELEGRAM_TOKEN = undefined;
|
|
45
|
+
process.env.NAX_TELEGRAM_CHAT_ID = undefined;
|
|
46
|
+
});
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
describe("WebhookInteractionPlugin", () => {
|
|
50
|
+
test("should validate required config", async () => {
|
|
51
|
+
const plugin = new WebhookInteractionPlugin();
|
|
52
|
+
|
|
53
|
+
// Should throw without url
|
|
54
|
+
await expect(plugin.init({})).rejects.toThrow("url");
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
test("should initialize with config", async () => {
|
|
58
|
+
const plugin = new WebhookInteractionPlugin();
|
|
59
|
+
|
|
60
|
+
await plugin.init({
|
|
61
|
+
url: "https://example.com/webhook",
|
|
62
|
+
callbackPort: 9999,
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
expect(plugin.name).toBe("webhook");
|
|
66
|
+
|
|
67
|
+
await plugin.destroy();
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
test("should default callbackPort to 8765", async () => {
|
|
71
|
+
const plugin = new WebhookInteractionPlugin();
|
|
72
|
+
|
|
73
|
+
await plugin.init({
|
|
74
|
+
url: "https://example.com/webhook",
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
expect(plugin.name).toBe("webhook");
|
|
78
|
+
|
|
79
|
+
await plugin.destroy();
|
|
80
|
+
});
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
describe("AutoInteractionPlugin", () => {
|
|
84
|
+
test("should initialize with defaults", async () => {
|
|
85
|
+
const plugin = new AutoInteractionPlugin();
|
|
86
|
+
|
|
87
|
+
await plugin.init({});
|
|
88
|
+
|
|
89
|
+
expect(plugin.name).toBe("auto");
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
test("should respect config overrides", async () => {
|
|
93
|
+
const plugin = new AutoInteractionPlugin();
|
|
94
|
+
|
|
95
|
+
await plugin.init({
|
|
96
|
+
model: "balanced",
|
|
97
|
+
confidenceThreshold: 0.8,
|
|
98
|
+
maxCostPerDecision: 0.05,
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
expect(plugin.name).toBe("auto");
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
test("should reject security-review triggers", async () => {
|
|
105
|
+
const plugin = new AutoInteractionPlugin();
|
|
106
|
+
|
|
107
|
+
// Mock config
|
|
108
|
+
await plugin.init({
|
|
109
|
+
naxConfig: {
|
|
110
|
+
models: {
|
|
111
|
+
fast: { model: "claude-haiku-3-5" },
|
|
112
|
+
},
|
|
113
|
+
} as unknown as import("../../src/config").NaxConfig,
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
const request: InteractionRequest = {
|
|
117
|
+
id: "test-security-review",
|
|
118
|
+
type: "confirm",
|
|
119
|
+
featureName: "test-feature",
|
|
120
|
+
stage: "review",
|
|
121
|
+
summary: "Security review failed",
|
|
122
|
+
fallback: "abort",
|
|
123
|
+
createdAt: Date.now(),
|
|
124
|
+
metadata: {
|
|
125
|
+
trigger: "security-review",
|
|
126
|
+
safety: "red",
|
|
127
|
+
},
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
const response = await plugin.decide(request);
|
|
131
|
+
|
|
132
|
+
// Should return undefined to escalate to human
|
|
133
|
+
expect(response).toBeUndefined();
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
test("should escalate on low confidence", async () => {
|
|
137
|
+
const plugin = new AutoInteractionPlugin();
|
|
138
|
+
|
|
139
|
+
// Set high threshold
|
|
140
|
+
await plugin.init({
|
|
141
|
+
confidenceThreshold: 0.9,
|
|
142
|
+
naxConfig: {
|
|
143
|
+
models: {
|
|
144
|
+
fast: { model: "claude-haiku-3-5" },
|
|
145
|
+
},
|
|
146
|
+
} as unknown as import("../../src/config").NaxConfig,
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
// Mock a request that would get low confidence
|
|
150
|
+
const request: InteractionRequest = {
|
|
151
|
+
id: "test-low-confidence",
|
|
152
|
+
type: "confirm",
|
|
153
|
+
featureName: "test-feature",
|
|
154
|
+
stage: "custom",
|
|
155
|
+
summary: "Ambiguous decision",
|
|
156
|
+
fallback: "continue",
|
|
157
|
+
createdAt: Date.now(),
|
|
158
|
+
};
|
|
159
|
+
|
|
160
|
+
// Note: This would require mocking the LLM call in a real test
|
|
161
|
+
// For now, we just verify the plugin is configured correctly
|
|
162
|
+
expect(plugin.name).toBe("auto");
|
|
163
|
+
});
|
|
164
|
+
});
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for src/tdd/isolation.ts
|
|
3
|
+
*
|
|
4
|
+
* Covers: isTestFile, isSourceFile, matchesAllowedPath (via verify functions)
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { describe, expect, it } from "bun:test";
|
|
8
|
+
import { isSourceFile, isTestFile } from "../../src/tdd/isolation";
|
|
9
|
+
|
|
10
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
11
|
+
// isTestFile
|
|
12
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
13
|
+
|
|
14
|
+
describe("isTestFile", () => {
|
|
15
|
+
it("detects test/ directory files", () => {
|
|
16
|
+
expect(isTestFile("test/unit/foo.ts")).toBe(true);
|
|
17
|
+
expect(isTestFile("test/integration/bar.test.ts")).toBe(true);
|
|
18
|
+
expect(isTestFile("test/fixtures/data.json")).toBe(true);
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
it("detects tests/ directory files", () => {
|
|
22
|
+
expect(isTestFile("tests/unit/foo.ts")).toBe(true);
|
|
23
|
+
expect(isTestFile("tests/integration/bar.test.ts")).toBe(true);
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
it("detects __tests__/ directory files", () => {
|
|
27
|
+
expect(isTestFile("__tests__/unit/foo.ts")).toBe(true);
|
|
28
|
+
expect(isTestFile("src/__tests__/foo.test.ts")).toBe(true);
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it("detects .spec extension", () => {
|
|
32
|
+
expect(isTestFile("src/foo.spec.ts")).toBe(true);
|
|
33
|
+
expect(isTestFile("src/utils/bar.spec.js")).toBe(true);
|
|
34
|
+
expect(isTestFile("lib/baz.spec.tsx")).toBe(true);
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
it("detects .test extension", () => {
|
|
38
|
+
expect(isTestFile("src/foo.test.ts")).toBe(true);
|
|
39
|
+
expect(isTestFile("src/utils/bar.test.js")).toBe(true);
|
|
40
|
+
expect(isTestFile("lib/baz.test.tsx")).toBe(true);
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
it("detects .e2e-spec extension (NestJS convention)", () => {
|
|
44
|
+
expect(isTestFile("src/app.e2e-spec.ts")).toBe(true);
|
|
45
|
+
expect(isTestFile("test/e2e/auth.e2e-spec.ts")).toBe(true);
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it("returns false for source files", () => {
|
|
49
|
+
expect(isTestFile("src/index.ts")).toBe(false);
|
|
50
|
+
expect(isTestFile("lib/utils.ts")).toBe(false);
|
|
51
|
+
expect(isTestFile("packages/core/foo.ts")).toBe(false);
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
it("returns false for config/doc files", () => {
|
|
55
|
+
expect(isTestFile("package.json")).toBe(false);
|
|
56
|
+
expect(isTestFile("README.md")).toBe(false);
|
|
57
|
+
expect(isTestFile("tsconfig.json")).toBe(false);
|
|
58
|
+
});
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
62
|
+
// isSourceFile
|
|
63
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
64
|
+
|
|
65
|
+
describe("isSourceFile", () => {
|
|
66
|
+
it("detects src/ directory files", () => {
|
|
67
|
+
expect(isSourceFile("src/index.ts")).toBe(true);
|
|
68
|
+
expect(isSourceFile("src/utils/foo.ts")).toBe(true);
|
|
69
|
+
expect(isSourceFile("src/components/Bar.tsx")).toBe(true);
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
it("detects lib/ directory files", () => {
|
|
73
|
+
expect(isSourceFile("lib/index.ts")).toBe(true);
|
|
74
|
+
expect(isSourceFile("lib/utils/foo.ts")).toBe(true);
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
it("detects packages/ directory files (monorepo)", () => {
|
|
78
|
+
expect(isSourceFile("packages/core/index.ts")).toBe(true);
|
|
79
|
+
expect(isSourceFile("packages/utils/foo.ts")).toBe(true);
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
it("returns false for test files", () => {
|
|
83
|
+
expect(isSourceFile("test/unit/foo.test.ts")).toBe(false);
|
|
84
|
+
expect(isSourceFile("tests/integration/bar.test.ts")).toBe(false);
|
|
85
|
+
expect(isSourceFile("__tests__/baz.ts")).toBe(false);
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
it("returns false for config/doc files", () => {
|
|
89
|
+
expect(isSourceFile("package.json")).toBe(false);
|
|
90
|
+
expect(isSourceFile("README.md")).toBe(false);
|
|
91
|
+
expect(isSourceFile("tsconfig.json")).toBe(false);
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
it("returns false for root files", () => {
|
|
95
|
+
expect(isSourceFile("index.ts")).toBe(false);
|
|
96
|
+
expect(isSourceFile("utils.ts")).toBe(false);
|
|
97
|
+
});
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
101
|
+
// Combined behavior tests
|
|
102
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
103
|
+
|
|
104
|
+
describe("combined isTestFile + isSourceFile", () => {
|
|
105
|
+
it("test files in src/ are detected as both test and source", () => {
|
|
106
|
+
const file = "src/foo.test.ts";
|
|
107
|
+
expect(isTestFile(file)).toBe(true);
|
|
108
|
+
expect(isSourceFile(file)).toBe(true);
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
it("spec files in src/ are detected as both test and source", () => {
|
|
112
|
+
const file = "src/foo.spec.ts";
|
|
113
|
+
expect(isTestFile(file)).toBe(true);
|
|
114
|
+
expect(isSourceFile(file)).toBe(true);
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
it("regular src/ files are source but not test", () => {
|
|
118
|
+
const file = "src/foo.ts";
|
|
119
|
+
expect(isTestFile(file)).toBe(false);
|
|
120
|
+
expect(isSourceFile(file)).toBe(true);
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
it("test/ directory files are test but not source (default)", () => {
|
|
124
|
+
const file = "test/foo.ts";
|
|
125
|
+
expect(isTestFile(file)).toBe(true);
|
|
126
|
+
expect(isSourceFile(file)).toBe(false);
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
it("config files are neither test nor source", () => {
|
|
130
|
+
const file = "package.json";
|
|
131
|
+
expect(isTestFile(file)).toBe(false);
|
|
132
|
+
expect(isSourceFile(file)).toBe(false);
|
|
133
|
+
});
|
|
134
|
+
});
|