@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,327 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Plugin System Types
|
|
3
|
+
*
|
|
4
|
+
* Defines the plugin interface and extension point types for the nax plugin system.
|
|
5
|
+
* Plugins export a NaxPlugin object with extension implementations.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type { AgentAdapter } from "../agents/types";
|
|
9
|
+
import type { IPromptOptimizer } from "../optimizer/types";
|
|
10
|
+
import type { UserStory } from "../prd/types";
|
|
11
|
+
import type { RoutingStrategy } from "../routing/strategy";
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Extension point types that plugins can provide.
|
|
15
|
+
*/
|
|
16
|
+
export type PluginType = "optimizer" | "router" | "agent" | "reviewer" | "context-provider" | "reporter";
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* A nax plugin module.
|
|
20
|
+
*
|
|
21
|
+
* Plugins export a single NaxPlugin object (default or named export).
|
|
22
|
+
* Each plugin declares which extension points it provides and supplies
|
|
23
|
+
* the corresponding implementations.
|
|
24
|
+
*
|
|
25
|
+
* @example
|
|
26
|
+
* ```ts
|
|
27
|
+
* const myPlugin: NaxPlugin = {
|
|
28
|
+
* name: "my-security-scanner",
|
|
29
|
+
* version: "1.0.0",
|
|
30
|
+
* provides: ["reviewer"],
|
|
31
|
+
* async setup(config) {
|
|
32
|
+
* // Initialize plugin
|
|
33
|
+
* },
|
|
34
|
+
* async teardown() {
|
|
35
|
+
* // Cleanup
|
|
36
|
+
* },
|
|
37
|
+
* extensions: {
|
|
38
|
+
* reviewer: {
|
|
39
|
+
* name: "security-scan",
|
|
40
|
+
* description: "Scans for security vulnerabilities",
|
|
41
|
+
* async check(workdir, changedFiles) {
|
|
42
|
+
* // Perform check
|
|
43
|
+
* }
|
|
44
|
+
* }
|
|
45
|
+
* }
|
|
46
|
+
* };
|
|
47
|
+
* ```
|
|
48
|
+
*/
|
|
49
|
+
export interface NaxPlugin {
|
|
50
|
+
/** Unique plugin name (e.g., "jira-context", "llmlingua-optimizer") */
|
|
51
|
+
name: string;
|
|
52
|
+
|
|
53
|
+
/** Plugin version (semver) */
|
|
54
|
+
version: string;
|
|
55
|
+
|
|
56
|
+
/** Which extension points this plugin provides */
|
|
57
|
+
provides: PluginType[];
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Called once when plugin is loaded. Use for initialization,
|
|
61
|
+
* validating config, establishing connections, etc.
|
|
62
|
+
*
|
|
63
|
+
* @param config - Plugin-specific config from nax config.json
|
|
64
|
+
*/
|
|
65
|
+
setup?(config: Record<string, unknown>): Promise<void>;
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Called when the nax run ends (success or failure).
|
|
69
|
+
* Use for cleanup, closing connections, flushing buffers.
|
|
70
|
+
*/
|
|
71
|
+
teardown?(): Promise<void>;
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Extension implementations. Only the types listed in `provides`
|
|
75
|
+
* are required; others are ignored.
|
|
76
|
+
*/
|
|
77
|
+
extensions: PluginExtensions;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Extension implementations provided by a plugin.
|
|
82
|
+
* Only extensions matching the plugin's `provides` array are required.
|
|
83
|
+
*/
|
|
84
|
+
export interface PluginExtensions {
|
|
85
|
+
/** Custom prompt optimizer */
|
|
86
|
+
optimizer?: IPromptOptimizer;
|
|
87
|
+
|
|
88
|
+
/** Custom routing strategy (inserted into the strategy chain) */
|
|
89
|
+
router?: RoutingStrategy;
|
|
90
|
+
|
|
91
|
+
/** Custom agent adapter (e.g., Codex, Gemini, Aider) */
|
|
92
|
+
agent?: AgentAdapter;
|
|
93
|
+
|
|
94
|
+
/** Custom review check (runs alongside built-in typecheck/lint/test) */
|
|
95
|
+
reviewer?: IReviewPlugin;
|
|
96
|
+
|
|
97
|
+
/** Custom context provider (injects external context into prompts) */
|
|
98
|
+
contextProvider?: IContextProvider;
|
|
99
|
+
|
|
100
|
+
/** Custom reporter (receives run events for dashboards, CI, etc.) */
|
|
101
|
+
reporter?: IReporter;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// ============================================================================
|
|
105
|
+
// Optimizer Extension
|
|
106
|
+
// ============================================================================
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Re-export optimizer types from the optimizer module.
|
|
110
|
+
* Plugin optimizers use the same interface as built-in optimizers.
|
|
111
|
+
*/
|
|
112
|
+
export type {
|
|
113
|
+
PromptOptimizerInput,
|
|
114
|
+
PromptOptimizerResult,
|
|
115
|
+
} from "../optimizer/types";
|
|
116
|
+
export type { IPromptOptimizer } from "../optimizer/types";
|
|
117
|
+
|
|
118
|
+
// ============================================================================
|
|
119
|
+
// Review Extension
|
|
120
|
+
// ============================================================================
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Result from a review check.
|
|
124
|
+
*/
|
|
125
|
+
export interface ReviewCheckResult {
|
|
126
|
+
/** Whether the review check passed */
|
|
127
|
+
passed: boolean;
|
|
128
|
+
/** Human-readable output or error messages */
|
|
129
|
+
output: string;
|
|
130
|
+
/** Exit code from the check process (if applicable) */
|
|
131
|
+
exitCode?: number;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Review plugin interface.
|
|
136
|
+
*
|
|
137
|
+
* Review plugins run custom checks after agent execution (e.g., security scans,
|
|
138
|
+
* license checks, performance tests). Failures trigger retry/escalation.
|
|
139
|
+
*
|
|
140
|
+
* @example
|
|
141
|
+
* ```ts
|
|
142
|
+
* const reviewer: IReviewPlugin = {
|
|
143
|
+
* name: "security-scan",
|
|
144
|
+
* description: "Scans for security vulnerabilities",
|
|
145
|
+
* async check(workdir, changedFiles) {
|
|
146
|
+
* const result = await securityScanner.scan(workdir, changedFiles);
|
|
147
|
+
* return {
|
|
148
|
+
* passed: result.vulnerabilities.length === 0,
|
|
149
|
+
* output: result.report
|
|
150
|
+
* };
|
|
151
|
+
* }
|
|
152
|
+
* };
|
|
153
|
+
* ```
|
|
154
|
+
*/
|
|
155
|
+
export interface IReviewPlugin {
|
|
156
|
+
/** Check name (e.g., "security-scan", "license-check") */
|
|
157
|
+
name: string;
|
|
158
|
+
|
|
159
|
+
/** Human-readable description */
|
|
160
|
+
description: string;
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Run the review check against the working directory.
|
|
164
|
+
*
|
|
165
|
+
* @param workdir - Project root directory
|
|
166
|
+
* @param changedFiles - Files modified by the agent in this story
|
|
167
|
+
* @returns Review check result
|
|
168
|
+
*/
|
|
169
|
+
check(workdir: string, changedFiles: string[]): Promise<ReviewCheckResult>;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// ============================================================================
|
|
173
|
+
// Context Provider Extension
|
|
174
|
+
// ============================================================================
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* Result from a context provider.
|
|
178
|
+
*/
|
|
179
|
+
export interface ContextProviderResult {
|
|
180
|
+
/** Markdown content to inject */
|
|
181
|
+
content: string;
|
|
182
|
+
/** Token estimate for budget tracking */
|
|
183
|
+
estimatedTokens: number;
|
|
184
|
+
/** Section label in the prompt (e.g., "Jira Context") */
|
|
185
|
+
label: string;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* Context provider interface.
|
|
190
|
+
*
|
|
191
|
+
* Context providers fetch external data (Jira tickets, Confluence docs,
|
|
192
|
+
* Linear issues, etc.) and inject it into agent prompts.
|
|
193
|
+
*
|
|
194
|
+
* @example
|
|
195
|
+
* ```ts
|
|
196
|
+
* const provider: IContextProvider = {
|
|
197
|
+
* name: "jira",
|
|
198
|
+
* async getContext(story) {
|
|
199
|
+
* const ticket = await jiraApi.getTicket(story.tags[0]);
|
|
200
|
+
* return {
|
|
201
|
+
* content: `## ${ticket.key}\n\n${ticket.description}`,
|
|
202
|
+
* estimatedTokens: estimateTokens(ticket.description),
|
|
203
|
+
* label: "Jira Context"
|
|
204
|
+
* };
|
|
205
|
+
* }
|
|
206
|
+
* };
|
|
207
|
+
* ```
|
|
208
|
+
*/
|
|
209
|
+
export interface IContextProvider {
|
|
210
|
+
/** Provider name (e.g., "jira", "linear", "confluence") */
|
|
211
|
+
name: string;
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* Fetch external context relevant to a story.
|
|
215
|
+
*
|
|
216
|
+
* @param story - The user story being executed
|
|
217
|
+
* @returns Markdown content to inject into the agent prompt
|
|
218
|
+
*/
|
|
219
|
+
getContext(story: UserStory): Promise<ContextProviderResult>;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
// ============================================================================
|
|
223
|
+
// Reporter Extension
|
|
224
|
+
// ============================================================================
|
|
225
|
+
|
|
226
|
+
/**
|
|
227
|
+
* Event emitted when a run starts.
|
|
228
|
+
*/
|
|
229
|
+
export interface RunStartEvent {
|
|
230
|
+
runId: string;
|
|
231
|
+
feature: string;
|
|
232
|
+
totalStories: number;
|
|
233
|
+
startTime: string;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
/**
|
|
237
|
+
* Event emitted when a story completes.
|
|
238
|
+
*/
|
|
239
|
+
export interface StoryCompleteEvent {
|
|
240
|
+
runId: string;
|
|
241
|
+
storyId: string;
|
|
242
|
+
status: "completed" | "failed" | "skipped" | "paused";
|
|
243
|
+
durationMs: number;
|
|
244
|
+
cost: number;
|
|
245
|
+
tier: string;
|
|
246
|
+
testStrategy: string;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
/**
|
|
250
|
+
* Event emitted when a run ends.
|
|
251
|
+
*/
|
|
252
|
+
export interface RunEndEvent {
|
|
253
|
+
runId: string;
|
|
254
|
+
totalDurationMs: number;
|
|
255
|
+
totalCost: number;
|
|
256
|
+
storySummary: {
|
|
257
|
+
completed: number;
|
|
258
|
+
failed: number;
|
|
259
|
+
skipped: number;
|
|
260
|
+
paused: number;
|
|
261
|
+
};
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
/**
|
|
265
|
+
* Reporter interface.
|
|
266
|
+
*
|
|
267
|
+
* Reporters receive run lifecycle events and can emit them to external
|
|
268
|
+
* systems (dashboards, Slack, CI, databases, etc.).
|
|
269
|
+
*
|
|
270
|
+
* All reporter methods are fire-and-forget — failures are logged but
|
|
271
|
+
* never block the pipeline.
|
|
272
|
+
*
|
|
273
|
+
* @example
|
|
274
|
+
* ```ts
|
|
275
|
+
* const reporter: IReporter = {
|
|
276
|
+
* name: "telegram",
|
|
277
|
+
* async onRunStart(event) {
|
|
278
|
+
* await telegram.send(`Started ${event.feature}`);
|
|
279
|
+
* },
|
|
280
|
+
* async onStoryComplete(event) {
|
|
281
|
+
* await telegram.send(`${event.storyId} ${event.status}`);
|
|
282
|
+
* },
|
|
283
|
+
* async onRunEnd(event) {
|
|
284
|
+
* await telegram.send(`Completed ${event.storySummary.completed} stories`);
|
|
285
|
+
* }
|
|
286
|
+
* };
|
|
287
|
+
* ```
|
|
288
|
+
*/
|
|
289
|
+
export interface IReporter {
|
|
290
|
+
/** Reporter name */
|
|
291
|
+
name: string;
|
|
292
|
+
|
|
293
|
+
/** Called when a run starts */
|
|
294
|
+
onRunStart?(event: RunStartEvent): Promise<void>;
|
|
295
|
+
|
|
296
|
+
/** Called when a story completes (success or failure) */
|
|
297
|
+
onStoryComplete?(event: StoryCompleteEvent): Promise<void>;
|
|
298
|
+
|
|
299
|
+
/** Called when a run ends */
|
|
300
|
+
onRunEnd?(event: RunEndEvent): Promise<void>;
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
// ============================================================================
|
|
304
|
+
// Plugin Config
|
|
305
|
+
// ============================================================================
|
|
306
|
+
|
|
307
|
+
/**
|
|
308
|
+
* Plugin configuration entry from nax config.json.
|
|
309
|
+
*
|
|
310
|
+
* @example
|
|
311
|
+
* ```json
|
|
312
|
+
* {
|
|
313
|
+
* "plugins": [
|
|
314
|
+
* {
|
|
315
|
+
* "module": "./nax/plugins/my-plugin",
|
|
316
|
+
* "config": { "apiKey": "secret" }
|
|
317
|
+
* }
|
|
318
|
+
* ]
|
|
319
|
+
* }
|
|
320
|
+
* ```
|
|
321
|
+
*/
|
|
322
|
+
export interface PluginConfigEntry {
|
|
323
|
+
/** Module path or npm package name */
|
|
324
|
+
module: string;
|
|
325
|
+
/** Plugin-specific configuration */
|
|
326
|
+
config?: Record<string, unknown>;
|
|
327
|
+
}
|
|
@@ -0,0 +1,352 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Plugin Validator
|
|
3
|
+
*
|
|
4
|
+
* Runtime type checking for plugin modules.
|
|
5
|
+
* Validates plugin shape and ensures all required extensions are present.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { getLogger } from "../logger";
|
|
9
|
+
import type { NaxPlugin, PluginType } from "./types";
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Safely get logger instance, returns null if not initialized
|
|
13
|
+
*/
|
|
14
|
+
function getSafeLogger() {
|
|
15
|
+
try {
|
|
16
|
+
return getLogger();
|
|
17
|
+
} catch {
|
|
18
|
+
return null;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const VALID_PLUGIN_TYPES: readonly PluginType[] = [
|
|
23
|
+
"optimizer",
|
|
24
|
+
"router",
|
|
25
|
+
"agent",
|
|
26
|
+
"reviewer",
|
|
27
|
+
"context-provider",
|
|
28
|
+
"reporter",
|
|
29
|
+
] as const;
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Validate a plugin module at runtime.
|
|
33
|
+
*
|
|
34
|
+
* Returns the plugin if valid, null if invalid (with warning logged).
|
|
35
|
+
*
|
|
36
|
+
* @param module - The module to validate (can be any type)
|
|
37
|
+
* @returns The validated plugin or null
|
|
38
|
+
*/
|
|
39
|
+
export function validatePlugin(module: unknown): NaxPlugin | null {
|
|
40
|
+
// Must be an object
|
|
41
|
+
if (typeof module !== "object" || module === null) {
|
|
42
|
+
getSafeLogger()?.warn("plugins", "Plugin validation failed: module is not an object");
|
|
43
|
+
return null;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const plugin = module as Record<string, unknown>;
|
|
47
|
+
|
|
48
|
+
// Validate name
|
|
49
|
+
if (typeof plugin.name !== "string") {
|
|
50
|
+
getSafeLogger()?.warn("plugins", "Plugin validation failed: missing or invalid 'name' (must be string)");
|
|
51
|
+
return null;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// Validate version
|
|
55
|
+
if (typeof plugin.version !== "string") {
|
|
56
|
+
getSafeLogger()?.warn(
|
|
57
|
+
"plugins",
|
|
58
|
+
`Plugin '${plugin.name}' validation failed: missing or invalid 'version' (must be string)`,
|
|
59
|
+
);
|
|
60
|
+
return null;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Validate provides
|
|
64
|
+
if (!Array.isArray(plugin.provides)) {
|
|
65
|
+
getSafeLogger()?.warn("plugins", `Plugin '${plugin.name}' validation failed: 'provides' must be an array`);
|
|
66
|
+
return null;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
if (plugin.provides.length === 0) {
|
|
70
|
+
getSafeLogger()?.warn("plugins", `Plugin '${plugin.name}' validation failed: 'provides' must not be empty`);
|
|
71
|
+
return null;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
for (const type of plugin.provides) {
|
|
75
|
+
if (!VALID_PLUGIN_TYPES.includes(type as PluginType)) {
|
|
76
|
+
getSafeLogger()?.warn(
|
|
77
|
+
"plugins",
|
|
78
|
+
`Plugin '${plugin.name}' validation failed: invalid plugin type '${type}' in 'provides'`,
|
|
79
|
+
);
|
|
80
|
+
return null;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// Validate setup (optional)
|
|
85
|
+
if ("setup" in plugin && typeof plugin.setup !== "function") {
|
|
86
|
+
getSafeLogger()?.warn("plugins", `Plugin '${plugin.name}' validation failed: 'setup' must be a function`);
|
|
87
|
+
return null;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// Validate teardown (optional)
|
|
91
|
+
if ("teardown" in plugin && typeof plugin.teardown !== "function") {
|
|
92
|
+
getSafeLogger()?.warn("plugins", `Plugin '${plugin.name}' validation failed: 'teardown' must be a function`);
|
|
93
|
+
return null;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// Validate extensions
|
|
97
|
+
if (typeof plugin.extensions !== "object" || plugin.extensions === null) {
|
|
98
|
+
getSafeLogger()?.warn("plugins", `Plugin '${plugin.name}' validation failed: 'extensions' must be an object`);
|
|
99
|
+
return null;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const extensions = plugin.extensions as Record<string, unknown>;
|
|
103
|
+
|
|
104
|
+
// Validate each extension type in provides
|
|
105
|
+
for (const type of plugin.provides) {
|
|
106
|
+
const isValid = validateExtension(plugin.name as string, type as PluginType, extensions);
|
|
107
|
+
if (!isValid) {
|
|
108
|
+
return null;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
return plugin as unknown as NaxPlugin;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Validate a specific extension type.
|
|
117
|
+
*
|
|
118
|
+
* @param pluginName - Plugin name (for error messages)
|
|
119
|
+
* @param type - Extension type to validate
|
|
120
|
+
* @param extensions - Extensions object
|
|
121
|
+
* @returns Whether the extension is valid
|
|
122
|
+
*/
|
|
123
|
+
function validateExtension(pluginName: string, type: PluginType, extensions: Record<string, unknown>): boolean {
|
|
124
|
+
switch (type) {
|
|
125
|
+
case "optimizer":
|
|
126
|
+
return validateOptimizer(pluginName, extensions.optimizer);
|
|
127
|
+
case "router":
|
|
128
|
+
return validateRouter(pluginName, extensions.router);
|
|
129
|
+
case "agent":
|
|
130
|
+
return validateAgent(pluginName, extensions.agent);
|
|
131
|
+
case "reviewer":
|
|
132
|
+
return validateReviewer(pluginName, extensions.reviewer);
|
|
133
|
+
case "context-provider":
|
|
134
|
+
return validateContextProvider(pluginName, extensions.contextProvider);
|
|
135
|
+
case "reporter":
|
|
136
|
+
return validateReporter(pluginName, extensions.reporter);
|
|
137
|
+
default:
|
|
138
|
+
getSafeLogger()?.warn("plugins", `Plugin '${pluginName}' validation failed: unknown extension type '${type}'`);
|
|
139
|
+
return false;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Validate optimizer extension.
|
|
145
|
+
*/
|
|
146
|
+
function validateOptimizer(pluginName: string, optimizer: unknown): boolean {
|
|
147
|
+
if (typeof optimizer !== "object" || optimizer === null) {
|
|
148
|
+
getSafeLogger()?.warn("plugins", `Plugin '${pluginName}' validation failed: optimizer extension must be an object`);
|
|
149
|
+
return false;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
const opt = optimizer as Record<string, unknown>;
|
|
153
|
+
|
|
154
|
+
if (typeof opt.name !== "string") {
|
|
155
|
+
getSafeLogger()?.warn("plugins", `Plugin '${pluginName}' validation failed: optimizer.name must be a string`);
|
|
156
|
+
return false;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
if (typeof opt.optimize !== "function") {
|
|
160
|
+
getSafeLogger()?.warn("plugins", `Plugin '${pluginName}' validation failed: optimizer.optimize must be a function`);
|
|
161
|
+
return false;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
return true;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Validate router extension.
|
|
169
|
+
*/
|
|
170
|
+
function validateRouter(pluginName: string, router: unknown): boolean {
|
|
171
|
+
if (typeof router !== "object" || router === null) {
|
|
172
|
+
getSafeLogger()?.warn("plugins", `Plugin '${pluginName}' validation failed: router extension must be an object`);
|
|
173
|
+
return false;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
const rtr = router as Record<string, unknown>;
|
|
177
|
+
|
|
178
|
+
if (typeof rtr.name !== "string") {
|
|
179
|
+
getSafeLogger()?.warn("plugins", `Plugin '${pluginName}' validation failed: router.name must be a string`);
|
|
180
|
+
return false;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
if (typeof rtr.route !== "function") {
|
|
184
|
+
getSafeLogger()?.warn("plugins", `Plugin '${pluginName}' validation failed: router.route must be a function`);
|
|
185
|
+
return false;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
return true;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* Validate agent extension.
|
|
193
|
+
*/
|
|
194
|
+
function validateAgent(pluginName: string, agent: unknown): boolean {
|
|
195
|
+
if (typeof agent !== "object" || agent === null) {
|
|
196
|
+
getSafeLogger()?.warn("plugins", `Plugin '${pluginName}' validation failed: agent extension must be an object`);
|
|
197
|
+
return false;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
const agt = agent as Record<string, unknown>;
|
|
201
|
+
|
|
202
|
+
const requiredFields = [
|
|
203
|
+
{ name: "name", type: "string" },
|
|
204
|
+
{ name: "displayName", type: "string" },
|
|
205
|
+
{ name: "binary", type: "string" },
|
|
206
|
+
{ name: "capabilities", type: "object" },
|
|
207
|
+
{ name: "isInstalled", type: "function" },
|
|
208
|
+
{ name: "run", type: "function" },
|
|
209
|
+
{ name: "buildCommand", type: "function" },
|
|
210
|
+
{ name: "plan", type: "function" },
|
|
211
|
+
{ name: "decompose", type: "function" },
|
|
212
|
+
];
|
|
213
|
+
|
|
214
|
+
for (const field of requiredFields) {
|
|
215
|
+
if (field.type === "object") {
|
|
216
|
+
if (typeof agt[field.name] !== "object" || agt[field.name] === null) {
|
|
217
|
+
getSafeLogger()?.warn(
|
|
218
|
+
"plugins",
|
|
219
|
+
`Plugin '${pluginName}' validation failed: agent.${field.name} must be an object`,
|
|
220
|
+
);
|
|
221
|
+
return false;
|
|
222
|
+
}
|
|
223
|
+
} else {
|
|
224
|
+
// Validate field.type is a valid typeof result before comparison
|
|
225
|
+
const expectedType = field.type as string;
|
|
226
|
+
|
|
227
|
+
// Use explicit type checks instead of dynamic typeof comparison
|
|
228
|
+
let isValid = false;
|
|
229
|
+
if (expectedType === "string") {
|
|
230
|
+
isValid = typeof agt[field.name] === "string";
|
|
231
|
+
} else if (expectedType === "number") {
|
|
232
|
+
isValid = typeof agt[field.name] === "number";
|
|
233
|
+
} else if (expectedType === "boolean") {
|
|
234
|
+
isValid = typeof agt[field.name] === "boolean";
|
|
235
|
+
} else if (expectedType === "symbol") {
|
|
236
|
+
isValid = typeof agt[field.name] === "symbol";
|
|
237
|
+
} else if (expectedType === "undefined") {
|
|
238
|
+
isValid = typeof agt[field.name] === "undefined";
|
|
239
|
+
} else if (expectedType === "function") {
|
|
240
|
+
isValid = typeof agt[field.name] === "function";
|
|
241
|
+
} else if (expectedType === "bigint") {
|
|
242
|
+
isValid = typeof agt[field.name] === "bigint";
|
|
243
|
+
} else {
|
|
244
|
+
getSafeLogger()?.warn(
|
|
245
|
+
"plugins",
|
|
246
|
+
`Plugin '${pluginName}' validation failed: invalid type constraint '${expectedType}'`,
|
|
247
|
+
);
|
|
248
|
+
return false;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
if (!isValid) {
|
|
252
|
+
getSafeLogger()?.warn(
|
|
253
|
+
"plugins",
|
|
254
|
+
`Plugin '${pluginName}' validation failed: agent.${field.name} must be a ${expectedType}`,
|
|
255
|
+
);
|
|
256
|
+
return false;
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
return true;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
/**
|
|
265
|
+
* Validate reviewer extension.
|
|
266
|
+
*/
|
|
267
|
+
function validateReviewer(pluginName: string, reviewer: unknown): boolean {
|
|
268
|
+
if (typeof reviewer !== "object" || reviewer === null) {
|
|
269
|
+
getSafeLogger()?.warn("plugins", `Plugin '${pluginName}' validation failed: reviewer extension must be an object`);
|
|
270
|
+
return false;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
const rev = reviewer as Record<string, unknown>;
|
|
274
|
+
|
|
275
|
+
if (typeof rev.name !== "string") {
|
|
276
|
+
getSafeLogger()?.warn("plugins", `Plugin '${pluginName}' validation failed: reviewer.name must be a string`);
|
|
277
|
+
return false;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
if (typeof rev.description !== "string") {
|
|
281
|
+
getSafeLogger()?.warn("plugins", `Plugin '${pluginName}' validation failed: reviewer.description must be a string`);
|
|
282
|
+
return false;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
if (typeof rev.check !== "function") {
|
|
286
|
+
getSafeLogger()?.warn("plugins", `Plugin '${pluginName}' validation failed: reviewer.check must be a function`);
|
|
287
|
+
return false;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
return true;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
/**
|
|
294
|
+
* Validate context-provider extension.
|
|
295
|
+
*/
|
|
296
|
+
function validateContextProvider(pluginName: string, provider: unknown): boolean {
|
|
297
|
+
if (typeof provider !== "object" || provider === null) {
|
|
298
|
+
getSafeLogger()?.warn(
|
|
299
|
+
"plugins",
|
|
300
|
+
`Plugin '${pluginName}' validation failed: contextProvider extension must be an object`,
|
|
301
|
+
);
|
|
302
|
+
return false;
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
const prov = provider as Record<string, unknown>;
|
|
306
|
+
|
|
307
|
+
if (typeof prov.name !== "string") {
|
|
308
|
+
getSafeLogger()?.warn("plugins", `Plugin '${pluginName}' validation failed: contextProvider.name must be a string`);
|
|
309
|
+
return false;
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
if (typeof prov.getContext !== "function") {
|
|
313
|
+
getSafeLogger()?.warn(
|
|
314
|
+
"plugins",
|
|
315
|
+
`Plugin '${pluginName}' validation failed: contextProvider.getContext must be a function`,
|
|
316
|
+
);
|
|
317
|
+
return false;
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
return true;
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
/**
|
|
324
|
+
* Validate reporter extension.
|
|
325
|
+
*/
|
|
326
|
+
function validateReporter(pluginName: string, reporter: unknown): boolean {
|
|
327
|
+
if (typeof reporter !== "object" || reporter === null) {
|
|
328
|
+
getSafeLogger()?.warn("plugins", `Plugin '${pluginName}' validation failed: reporter extension must be an object`);
|
|
329
|
+
return false;
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
const rep = reporter as Record<string, unknown>;
|
|
333
|
+
|
|
334
|
+
if (typeof rep.name !== "string") {
|
|
335
|
+
getSafeLogger()?.warn("plugins", `Plugin '${pluginName}' validation failed: reporter.name must be a string`);
|
|
336
|
+
return false;
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
// At least one event handler is optional, but all must be functions if present
|
|
340
|
+
const eventHandlers = ["onRunStart", "onStoryComplete", "onRunEnd"];
|
|
341
|
+
for (const handler of eventHandlers) {
|
|
342
|
+
if (handler in rep && typeof rep[handler] !== "function") {
|
|
343
|
+
getSafeLogger()?.warn(
|
|
344
|
+
"plugins",
|
|
345
|
+
`Plugin '${pluginName}' validation failed: reporter.${handler} must be a function`,
|
|
346
|
+
);
|
|
347
|
+
return false;
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
return true;
|
|
352
|
+
}
|