aiwcli 0.15.5 → 0.15.7
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/README.md +6 -3
- package/dist/capabilities/branch/adapters.d.ts +2 -0
- package/dist/capabilities/branch/adapters.js +21 -0
- package/dist/capabilities/branch/contracts.d.ts +57 -0
- package/dist/capabilities/branch/contracts.js +1 -0
- package/dist/capabilities/branch/control-plane.d.ts +2 -0
- package/dist/capabilities/branch/control-plane.js +343 -0
- package/dist/capabilities/branch/runtime-core.d.ts +5 -0
- package/dist/capabilities/branch/runtime-core.js +36 -0
- package/dist/capabilities/installation/control-plane/clean-command.d.ts +41 -0
- package/dist/capabilities/installation/control-plane/clean-command.js +196 -0
- package/dist/capabilities/installation/control-plane/clear-command.d.ts +160 -0
- package/dist/capabilities/installation/control-plane/clear-command.js +1220 -0
- package/dist/capabilities/installation/control-plane/init-command.d.ts +81 -0
- package/dist/capabilities/installation/control-plane/init-command.js +449 -0
- package/dist/capabilities/launch/contracts.d.ts +51 -0
- package/dist/capabilities/launch/contracts.js +1 -0
- package/dist/capabilities/launch/control-plane/execute-launch.d.ts +2 -0
- package/dist/capabilities/launch/control-plane/execute-launch.js +222 -0
- package/dist/capabilities/launch/runtime-core/launch-options.d.ts +14 -0
- package/dist/capabilities/launch/runtime-core/launch-options.js +69 -0
- package/dist/cli/base-command.d.ts +18 -0
- package/dist/cli/base-command.js +55 -0
- package/dist/commands/branch.d.ts +0 -20
- package/dist/commands/branch.js +24 -416
- package/dist/commands/clean.d.ts +1 -41
- package/dist/commands/clean.js +1 -196
- package/dist/commands/clear.d.ts +1 -161
- package/dist/commands/clear.js +1 -1121
- package/dist/commands/init/index.d.ts +1 -98
- package/dist/commands/init/index.js +4 -478
- package/dist/commands/launch.d.ts +36 -11
- package/dist/commands/launch.js +135 -159
- package/dist/lib/base-command.d.ts +1 -114
- package/dist/lib/base-command.js +1 -153
- package/dist/lib/claude-settings-types.d.ts +31 -19
- package/dist/lib/context/context-formatter.d.ts +74 -0
- package/dist/lib/context/context-formatter.js +493 -0
- package/dist/lib/context/context-selector.d.ts +42 -0
- package/dist/lib/context/context-selector.js +451 -0
- package/dist/lib/context/context-store.d.ts +100 -0
- package/dist/lib/context/context-store.js +618 -0
- package/dist/lib/context/plan-manager.d.ts +54 -0
- package/dist/lib/context/plan-manager.js +282 -0
- package/dist/lib/context/task-tracker.d.ts +44 -0
- package/dist/lib/context/task-tracker.js +146 -0
- package/dist/lib/core-ide-base.d.ts +4 -0
- package/dist/lib/core-ide-base.js +77 -0
- package/dist/lib/core-installer.d.ts +5 -0
- package/dist/lib/core-installer.js +54 -0
- package/dist/lib/git-exclude-manager.d.ts +2 -2
- package/dist/lib/git-exclude-manager.js +3 -3
- package/dist/lib/hooks/hook-utils.d.ts +143 -0
- package/dist/lib/hooks/hook-utils.js +609 -0
- package/dist/lib/hooks/session-end-logic.d.ts +5 -0
- package/dist/lib/hooks/session-end-logic.js +63 -0
- package/dist/lib/hooks-merger.js +25 -19
- package/dist/lib/ide-path-resolver.d.ts +19 -7
- package/dist/lib/ide-path-resolver.js +25 -9
- package/dist/lib/install-state.d.ts +34 -0
- package/dist/lib/install-state.js +161 -0
- package/dist/lib/launch-options.d.ts +1 -0
- package/dist/lib/launch-options.js +1 -0
- package/dist/lib/lsp-patch.d.ts +12 -0
- package/dist/lib/lsp-patch.js +156 -0
- package/dist/lib/multiplexer.d.ts +57 -0
- package/dist/lib/multiplexer.js +19 -0
- package/dist/lib/multiplexers/psmux.d.ts +75 -0
- package/dist/lib/multiplexers/psmux.js +384 -0
- package/dist/lib/multiplexers/tmux.d.ts +44 -0
- package/dist/lib/multiplexers/tmux.js +262 -0
- package/dist/lib/mux-utils.d.ts +5 -0
- package/dist/lib/mux-utils.js +42 -0
- package/dist/lib/paths.d.ts +2 -2
- package/dist/lib/paths.js +2 -2
- package/dist/lib/platform-commands.d.ts +27 -0
- package/dist/lib/platform-commands.js +49 -0
- package/dist/lib/runtime/aiw-cli.d.ts +37 -0
- package/dist/lib/runtime/aiw-cli.js +74 -0
- package/dist/lib/runtime/atomic-write.d.ts +19 -0
- package/dist/lib/runtime/atomic-write.js +121 -0
- package/dist/lib/runtime/cli-args.d.ts +55 -0
- package/dist/lib/runtime/cli-args.js +185 -0
- package/dist/lib/runtime/constants.d.ts +56 -0
- package/dist/lib/runtime/constants.js +230 -0
- package/dist/lib/runtime/executable-policy.d.ts +16 -0
- package/dist/lib/runtime/executable-policy.js +57 -0
- package/dist/lib/runtime/git-state.d.ts +9 -0
- package/dist/lib/runtime/git-state.js +59 -0
- package/dist/lib/runtime/inference.d.ts +37 -0
- package/dist/lib/runtime/inference.js +262 -0
- package/dist/lib/runtime/lint-dispatch.d.ts +40 -0
- package/dist/lib/runtime/lint-dispatch.js +285 -0
- package/dist/lib/runtime/logger.d.ts +66 -0
- package/dist/lib/runtime/logger.js +201 -0
- package/dist/lib/runtime/models.d.ts +14 -0
- package/dist/lib/runtime/models.js +14 -0
- package/dist/lib/runtime/platform-adapter.d.ts +7 -0
- package/dist/lib/runtime/platform-adapter.js +21 -0
- package/dist/lib/runtime/preflight.d.ts +24 -0
- package/dist/lib/runtime/preflight.js +65 -0
- package/dist/lib/runtime/sentinel-ipc.d.ts +14 -0
- package/dist/lib/runtime/sentinel-ipc.js +67 -0
- package/dist/lib/runtime/state-io.d.ts +30 -0
- package/dist/lib/runtime/state-io.js +174 -0
- package/dist/lib/runtime/stop-words.d.ts +20 -0
- package/dist/lib/runtime/stop-words.js +150 -0
- package/dist/lib/runtime/subprocess-utils.d.ts +29 -0
- package/dist/lib/runtime/subprocess-utils.js +96 -0
- package/dist/lib/runtime/tmux-preflight.d.ts +13 -0
- package/dist/lib/runtime/tmux-preflight.js +78 -0
- package/dist/lib/runtime/utils.d.ts +54 -0
- package/dist/lib/runtime/utils.js +162 -0
- package/dist/lib/sentinel-wrapper.d.ts +9 -0
- package/dist/lib/sentinel-wrapper.js +20 -0
- package/dist/lib/shell-quoting.d.ts +5 -0
- package/dist/lib/shell-quoting.js +17 -0
- package/dist/lib/spawn-errors.d.ts +6 -0
- package/dist/lib/spawn-errors.js +15 -0
- package/dist/lib/spawn.js +5 -11
- package/dist/lib/template-installer.d.ts +4 -5
- package/dist/lib/template-installer.js +36 -34
- package/dist/lib/template-resolver.d.ts +6 -7
- package/dist/lib/template-resolver.js +26 -21
- package/dist/lib/template-settings-reconstructor.d.ts +7 -2
- package/dist/lib/template-settings-reconstructor.js +76 -45
- package/dist/lib/terminal-strategy.d.ts +11 -0
- package/dist/lib/terminal-strategy.js +49 -0
- package/dist/lib/terminal.d.ts +28 -0
- package/dist/lib/terminal.js +162 -112
- package/dist/lib/tmux-pane-placement.d.ts +17 -0
- package/dist/lib/tmux-pane-placement.js +58 -0
- package/dist/lib/tmux-primitives.d.ts +5 -0
- package/dist/lib/tmux-primitives.js +15 -0
- package/dist/lib/tmux-session.d.ts +32 -0
- package/dist/lib/tmux-session.js +86 -0
- package/dist/lib/tty-detection.js +1 -1
- package/dist/lib/types.d.ts +168 -0
- package/dist/lib/types.js +6 -0
- package/dist/lib/version.d.ts +1 -1
- package/dist/lib/version.js +1 -1
- package/dist/platform/launch.d.ts +10 -0
- package/dist/platform/launch.js +10 -0
- package/dist/templates/CLAUDE.md +31 -40
- package/dist/templates/cc-native/.claude/settings.json +27 -27
- package/dist/templates/cc-native/CC-NATIVE-README.md +1 -1
- package/dist/templates/cc-native/TEMPLATE-SCHEMA.md +10 -9
- package/dist/templates/cc-native/_cc-native/CLAUDE.md +18 -18
- package/dist/templates/cc-native/_cc-native/artifacts/CLAUDE.md +3 -3
- package/dist/templates/cc-native/_cc-native/artifacts/lib/format.ts +14 -14
- package/dist/templates/cc-native/_cc-native/artifacts/lib/tracker.ts +1 -1
- package/dist/templates/cc-native/_cc-native/artifacts/lib/write.ts +3 -3
- package/dist/templates/cc-native/_cc-native/cc-native.config.json +3 -3
- package/dist/templates/cc-native/_cc-native/hooks/CLAUDE.md +16 -15
- package/dist/templates/cc-native/_cc-native/hooks/cc-native-plan-review.ts +3 -3
- package/dist/templates/cc-native/_cc-native/hooks/enhance_plan_post_subagent.ts +2 -2
- package/dist/templates/cc-native/_cc-native/hooks/enhance_plan_post_write.ts +2 -2
- package/dist/templates/cc-native/_cc-native/hooks/mark_questions_asked.ts +3 -3
- package/dist/templates/cc-native/_cc-native/hooks/plan_questions_early.ts +2 -2
- package/dist/templates/cc-native/_cc-native/hooks/validate_task_prompt.ts +3 -3
- package/dist/templates/cc-native/_cc-native/lib-ts/CLAUDE.md +8 -8
- package/dist/templates/cc-native/_cc-native/lib-ts/aggregate-agents.ts +1 -1
- package/dist/templates/cc-native/_cc-native/lib-ts/cc-native-state.ts +4 -4
- package/dist/templates/cc-native/_cc-native/lib-ts/cli-output-parser.ts +1 -1
- package/dist/templates/cc-native/_cc-native/lib-ts/config.ts +1 -1
- package/dist/templates/cc-native/_cc-native/lib-ts/debug.ts +1 -1
- package/dist/templates/cc-native/_cc-native/lib-ts/json-parser.ts +1 -1
- package/dist/templates/cc-native/_cc-native/lib-ts/plan-discovery.ts +2 -2
- package/dist/templates/cc-native/_cc-native/lib-ts/rlm/logger.ts +1 -1
- package/dist/templates/cc-native/_cc-native/lib-ts/rlm/retrieval-pipeline.ts +2 -2
- package/dist/templates/cc-native/_cc-native/lib-ts/rlm/types.ts +1 -1
- package/dist/templates/cc-native/_cc-native/lib-ts/settings.ts +8 -8
- package/dist/templates/cc-native/_cc-native/lib-ts/state.ts +3 -3
- package/dist/templates/cc-native/_cc-native/lib-ts/tsconfig.json +2 -2
- package/dist/templates/cc-native/_cc-native/lib-ts/types.ts +3 -3
- package/dist/templates/cc-native/_cc-native/plan-review/CLAUDE.md +3 -1
- package/dist/templates/cc-native/_cc-native/plan-review/lib/__tests__/agent-selection.test.ts +345 -0
- package/dist/templates/cc-native/_cc-native/plan-review/lib/__tests__/preflight.test.ts +344 -0
- package/dist/templates/cc-native/_cc-native/plan-review/lib/agent-selection.ts +37 -15
- package/dist/templates/cc-native/_cc-native/plan-review/lib/corroboration.ts +16 -69
- package/dist/templates/cc-native/_cc-native/plan-review/lib/orchestrator.ts +1 -1
- package/dist/templates/cc-native/_cc-native/plan-review/lib/output-builder.ts +1 -1
- package/dist/templates/cc-native/_cc-native/plan-review/lib/plan-questions.ts +2 -2
- package/dist/templates/cc-native/_cc-native/plan-review/lib/preflight.ts +56 -26
- package/dist/templates/cc-native/_cc-native/plan-review/lib/review-pipeline.ts +7 -7
- package/dist/templates/cc-native/_cc-native/plan-review/lib/reviewers/agent.ts +4 -4
- package/dist/templates/cc-native/_cc-native/plan-review/lib/reviewers/base/base-agent.ts +3 -3
- package/dist/templates/cc-native/_cc-native/plan-review/lib/reviewers/index.ts +1 -1
- package/dist/templates/cc-native/_cc-native/plan-review/lib/reviewers/providers/claude-agent.ts +2 -2
- package/dist/templates/cc-native/_cc-native/plan-review/lib/reviewers/providers/codex-agent.ts +4 -4
- package/dist/templates/cc-native/_cc-native/plan-review/lib/reviewers/providers/gemini-agent.ts +1 -1
- package/dist/templates/cc-native/_cc-native/plan-review/lib/reviewers/providers/orchestrator-claude-agent.ts +5 -6
- package/dist/templates/core/.codex/workflows/codex.md +17 -0
- package/dist/templates/core/.codex/workflows/handoff.md +5 -0
- package/dist/templates/core/.codex/workflows/meta-plan.md +7 -0
- package/dist/templates/core/.cognition/AGENTS.md +5 -0
- package/dist/templates/core/.cognition/config.json +12 -0
- package/dist/templates/{_shared → core}/.windsurf/workflows/handoff.md +1 -1
- package/dist/templates/{_shared → core}/.windsurf/workflows/meta-plan.md +1 -1
- package/dist/templates/core/hooks-ts/_utils/git-state.ts +2 -0
- package/dist/templates/{_shared → core}/hooks-ts/archive_plan.ts +14 -23
- package/dist/templates/core/hooks-ts/codex_explorer.ts +160 -0
- package/dist/templates/{_shared → core}/hooks-ts/context_monitor.ts +23 -55
- package/dist/templates/{_shared → core}/hooks-ts/file-suggestion.ts +4 -3
- package/dist/templates/{_shared → core}/hooks-ts/lint_after_edit.ts +7 -9
- package/dist/templates/{_shared → core}/hooks-ts/pre_compact.ts +5 -5
- package/dist/templates/{_shared → core}/hooks-ts/session_end.ts +38 -78
- package/dist/templates/{_shared → core}/hooks-ts/session_start.ts +5 -5
- package/dist/templates/core/hooks-ts/task_create_capture.ts +32 -0
- package/dist/templates/{_shared → core}/hooks-ts/task_update_capture.ts +9 -24
- package/dist/templates/core/hooks-ts/user_prompt_submit.ts +46 -0
- package/dist/templates/{_shared → core}/lib-ts/CLAUDE.md +27 -16
- package/dist/templates/{_shared → core}/lib-ts/agent-exec/backends/headless.ts +3 -2
- package/dist/templates/{_shared → core}/lib-ts/agent-exec/backends/tmux.ts +44 -15
- package/dist/templates/{_shared → core}/lib-ts/agent-exec/base-agent.ts +6 -4
- package/dist/templates/{_shared → core}/lib-ts/agent-exec/execution-backend.ts +1 -1
- package/dist/templates/{_shared → core}/lib-ts/agent-exec/index.ts +2 -2
- package/dist/templates/{_shared → core}/lib-ts/agent-exec/structured-output.ts +4 -5
- package/dist/templates/{_shared → core}/lib-ts/context/CLAUDE.md +9 -6
- package/dist/templates/{_shared → core}/lib-ts/context/context-formatter.ts +16 -21
- package/dist/templates/{_shared → core}/lib-ts/context/context-selector.ts +8 -6
- package/dist/templates/{_shared → core}/lib-ts/context/context-store.ts +32 -20
- package/dist/templates/{_shared → core}/lib-ts/context/plan-manager.ts +19 -15
- package/dist/templates/{_shared → core}/lib-ts/context/task-tracker.ts +3 -3
- package/dist/templates/core/lib-ts/hooks/context-monitor-logic.ts +32 -0
- package/dist/templates/{_shared/lib-ts/base → core/lib-ts/hooks}/hook-utils.ts +168 -41
- package/dist/templates/core/lib-ts/hooks/prompt-binding-logic.ts +80 -0
- package/dist/templates/core/lib-ts/hooks/session-end-logic.ts +93 -0
- package/dist/templates/core/lib-ts/package.json +19 -0
- package/dist/templates/core/lib-ts/runtime/agent-launcher.ts +295 -0
- package/dist/templates/core/lib-ts/runtime/aiw-cli.ts +106 -0
- package/dist/templates/{_shared/lib-ts/base → core/lib-ts/runtime}/atomic-write.ts +12 -7
- package/dist/templates/{_shared/lib-ts/base → core/lib-ts/runtime}/cli-args.ts +8 -6
- package/dist/templates/{_shared/lib-ts/base → core/lib-ts/runtime}/constants.ts +326 -324
- package/dist/templates/core/lib-ts/runtime/executable-policy.ts +89 -0
- package/dist/templates/{_shared/lib-ts/base → core/lib-ts/runtime}/git-state.ts +6 -4
- package/dist/templates/{_shared/lib-ts/base → core/lib-ts/runtime}/inference.ts +59 -10
- package/dist/templates/{_shared/lib-ts/base → core/lib-ts/runtime}/lint-dispatch.ts +25 -23
- package/dist/templates/{_shared/lib-ts/base → core/lib-ts/runtime}/logger.ts +32 -29
- package/dist/templates/{_shared/lib-ts/base → core/lib-ts/runtime}/models.ts +2 -2
- package/dist/templates/core/lib-ts/runtime/platform-adapter.ts +33 -0
- package/dist/templates/{_shared/lib-ts/base → core/lib-ts/runtime}/preflight.ts +4 -3
- package/dist/templates/core/lib-ts/runtime/sentinel-ipc.ts +91 -0
- package/dist/templates/{_shared/lib-ts/base → core/lib-ts/runtime}/state-io.ts +11 -7
- package/dist/templates/core/lib-ts/runtime/stop-words.ts +185 -0
- package/dist/templates/core/lib-ts/runtime/subprocess-utils.ts +147 -0
- package/dist/templates/core/lib-ts/runtime/tmux-preflight.ts +93 -0
- package/dist/templates/{_shared/lib-ts/base → core/lib-ts/runtime}/utils.ts +4 -3
- package/dist/templates/{_shared → core}/lib-ts/templates/formatters.ts +7 -5
- package/dist/templates/{_shared → core}/lib-ts/templates/plan-context.ts +2 -1
- package/dist/templates/{_shared → core}/lib-ts/tsconfig.json +3 -1
- package/dist/templates/{_shared → core}/lib-ts/types.ts +78 -77
- package/dist/templates/core/scripts/resolve-run.ts +61 -0
- package/dist/templates/{_shared → core}/scripts/resolve_context.ts +3 -3
- package/dist/templates/{_shared → core}/scripts/status_line.ts +25 -20
- package/dist/templates/core/skills/codex/CLAUDE.md +78 -0
- package/dist/templates/{_shared → core}/skills/codex/SKILL.md +21 -18
- package/dist/templates/{_shared → core}/skills/codex/lib/codex-watcher.ts +76 -103
- package/dist/templates/{_shared → core}/skills/codex/scripts/launch-codex.ts +119 -133
- package/dist/templates/{_shared → core}/skills/codex/scripts/watch-codex.ts +6 -4
- package/dist/templates/core/skills/devin/CLAUDE.md +65 -0
- package/dist/templates/core/skills/devin/SKILL.md +73 -0
- package/dist/templates/core/skills/devin/lib/devin-watcher.ts +280 -0
- package/dist/templates/core/skills/devin/scripts/launch-devin.ts +257 -0
- package/dist/templates/{_shared → core}/skills/handoff-system/CLAUDE.md +436 -433
- package/dist/templates/{_shared → core}/skills/handoff-system/lib/document-generator.ts +9 -7
- package/dist/templates/{_shared → core}/skills/handoff-system/lib/handoff-reader.ts +6 -4
- package/dist/templates/{_shared → core}/skills/handoff-system/scripts/resume_handoff.ts +10 -8
- package/dist/templates/{_shared → core}/skills/handoff-system/scripts/save_handoff.ts +12 -10
- package/dist/templates/{_shared → core}/skills/handoff-system/workflows/handoff-resume.md +2 -2
- package/dist/templates/{_shared → core}/skills/handoff-system/workflows/handoff.md +6 -5
- package/dist/templates/{_shared → core}/skills/meta-plan/CLAUDE.md +2 -1
- package/dist/templates/{_shared → core}/skills/meta-plan/workflows/meta-plan.md +8 -7
- package/oclif.manifest.json +89 -13
- package/package.json +13 -12
- package/dist/templates/_shared/.claude/settings.json +0 -120
- package/dist/templates/_shared/.claude/skills/codex/SKILL.md +0 -35
- package/dist/templates/_shared/.claude/skills/handoff/SKILL.md +0 -13
- package/dist/templates/_shared/.claude/skills/handoff-resume/SKILL.md +0 -13
- package/dist/templates/_shared/.claude/skills/meta-plan/SKILL.md +0 -43
- package/dist/templates/_shared/.codex/workflows/codex.md +0 -11
- package/dist/templates/_shared/.codex/workflows/handoff.md +0 -226
- package/dist/templates/_shared/.codex/workflows/meta-plan.md +0 -347
- package/dist/templates/_shared/hooks-ts/_utils/git-state.ts +0 -2
- package/dist/templates/_shared/hooks-ts/task_create_capture.ts +0 -48
- package/dist/templates/_shared/hooks-ts/user_prompt_submit.ts +0 -93
- package/dist/templates/_shared/lib-ts/base/launchers/tmux-launcher.ts +0 -173
- package/dist/templates/_shared/lib-ts/base/launchers/window-launcher.ts +0 -93
- package/dist/templates/_shared/lib-ts/base/launchers/wt-launcher.ts +0 -64
- package/dist/templates/_shared/lib-ts/base/pane-launcher.ts +0 -55
- package/dist/templates/_shared/lib-ts/base/sentinel-ipc.ts +0 -87
- package/dist/templates/_shared/lib-ts/base/stop-words.ts +0 -184
- package/dist/templates/_shared/lib-ts/base/subprocess-utils.ts +0 -249
- package/dist/templates/_shared/lib-ts/base/tmux-driver.ts +0 -341
- package/dist/templates/_shared/lib-ts/base/tmux-pane-placement.ts +0 -78
- package/dist/templates/_shared/lib-ts/package.json +0 -20
- package/dist/templates/_shared/scripts/resolve-run.ts +0 -62
- package/dist/templates/_shared/skills/codex/CLAUDE.md +0 -70
- /package/dist/templates/{_shared → core}/lib-ts/agent-exec/backends/index.ts +0 -0
|
@@ -0,0 +1,344 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for preflight health checks.
|
|
3
|
+
* Tests pure functions (collectPreflightChecks, buildPreflightReport) directly.
|
|
4
|
+
* Tests classifyError from shared preflight module directly.
|
|
5
|
+
* Slim integration tests for runPreflight mock only checkProviderModel (network boundary).
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { describe, it, expect, mock, beforeEach } from "bun:test";
|
|
9
|
+
|
|
10
|
+
import type { ModelsConfig, PreflightCheckResult, PreflightReport } from "../../../lib-ts/types.js";
|
|
11
|
+
|
|
12
|
+
// ---------------------------------------------------------------------------
|
|
13
|
+
// Mock logger for noise suppression only (no assertions on logger calls)
|
|
14
|
+
// ---------------------------------------------------------------------------
|
|
15
|
+
|
|
16
|
+
mock.module("../../../../_core/lib-ts/runtime/logger.js", () => ({
|
|
17
|
+
hookLog() {},
|
|
18
|
+
logDebug() {},
|
|
19
|
+
logInfo() {},
|
|
20
|
+
logWarn() {},
|
|
21
|
+
logError() {},
|
|
22
|
+
logBlocking() {},
|
|
23
|
+
logDiagnostic() {},
|
|
24
|
+
logHookError() {},
|
|
25
|
+
setSessionId() {},
|
|
26
|
+
setContextPath() {},
|
|
27
|
+
getContextPath: () => null,
|
|
28
|
+
}));
|
|
29
|
+
|
|
30
|
+
// ---------------------------------------------------------------------------
|
|
31
|
+
// Import pure functions under test (no mocks needed)
|
|
32
|
+
// ---------------------------------------------------------------------------
|
|
33
|
+
|
|
34
|
+
const { collectPreflightChecks, buildPreflightReport } = await import("../preflight.js");
|
|
35
|
+
const { classifyError } = await import("../../../../_core/lib-ts/runtime/preflight.js");
|
|
36
|
+
|
|
37
|
+
// ---------------------------------------------------------------------------
|
|
38
|
+
// Helpers
|
|
39
|
+
// ---------------------------------------------------------------------------
|
|
40
|
+
|
|
41
|
+
function makeModelsConfig(providers: Record<string, { enabled: boolean; models: string[] }>): ModelsConfig {
|
|
42
|
+
return { providers };
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// ---------------------------------------------------------------------------
|
|
46
|
+
// collectPreflightChecks — pure, zero mocks
|
|
47
|
+
// ---------------------------------------------------------------------------
|
|
48
|
+
|
|
49
|
+
describe("preflight", () => {
|
|
50
|
+
describe("collectPreflightChecks", () => {
|
|
51
|
+
const knownProviders = new Set(["claude", "codex"]);
|
|
52
|
+
|
|
53
|
+
it("collects enabled providers with known names", () => {
|
|
54
|
+
const config = makeModelsConfig({
|
|
55
|
+
claude: { enabled: true, models: ["sonnet"] },
|
|
56
|
+
codex: { enabled: true, models: ["codex-mini-latest"] },
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
const { checks, skippedProviders } = collectPreflightChecks(config, knownProviders);
|
|
60
|
+
|
|
61
|
+
expect(checks).toEqual([
|
|
62
|
+
{ provider: "claude", model: "sonnet" },
|
|
63
|
+
{ provider: "codex", model: "codex-mini-latest" },
|
|
64
|
+
]);
|
|
65
|
+
expect(skippedProviders).toEqual([]);
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
it("skips disabled providers", () => {
|
|
69
|
+
const config = makeModelsConfig({
|
|
70
|
+
claude: { enabled: false, models: ["sonnet"] },
|
|
71
|
+
codex: { enabled: true, models: ["codex-mini-latest"] },
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
const { checks } = collectPreflightChecks(config, knownProviders);
|
|
75
|
+
|
|
76
|
+
expect(checks).toEqual([{ provider: "codex", model: "codex-mini-latest" }]);
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
it("skips providers with empty model lists", () => {
|
|
80
|
+
const config = makeModelsConfig({
|
|
81
|
+
claude: { enabled: true, models: [] },
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
const { checks } = collectPreflightChecks(config, knownProviders);
|
|
85
|
+
|
|
86
|
+
expect(checks).toEqual([]);
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
it("reports unknown providers in skippedProviders", () => {
|
|
90
|
+
const config = makeModelsConfig({
|
|
91
|
+
unknown_provider: { enabled: true, models: ["some-model"] },
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
const { checks, skippedProviders } = collectPreflightChecks(config, knownProviders);
|
|
95
|
+
|
|
96
|
+
expect(checks).toEqual([]);
|
|
97
|
+
expect(skippedProviders).toEqual(["unknown_provider"]);
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
it("deduplicates same provider:model combo", () => {
|
|
101
|
+
const config = makeModelsConfig({
|
|
102
|
+
claude: { enabled: true, models: ["sonnet", "sonnet"] },
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
const { checks } = collectPreflightChecks(config, knownProviders);
|
|
106
|
+
|
|
107
|
+
expect(checks).toEqual([{ provider: "claude", model: "sonnet" }]);
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
it("collects multiple models per provider", () => {
|
|
111
|
+
const config = makeModelsConfig({
|
|
112
|
+
claude: { enabled: true, models: ["sonnet", "opus"] },
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
const { checks } = collectPreflightChecks(config, knownProviders);
|
|
116
|
+
|
|
117
|
+
expect(checks).toEqual([
|
|
118
|
+
{ provider: "claude", model: "sonnet" },
|
|
119
|
+
{ provider: "claude", model: "opus" },
|
|
120
|
+
]);
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
it("returns empty checks when no providers are enabled", () => {
|
|
124
|
+
const config = makeModelsConfig({
|
|
125
|
+
claude: { enabled: false, models: ["sonnet"] },
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
const { checks, skippedProviders } = collectPreflightChecks(config, knownProviders);
|
|
129
|
+
|
|
130
|
+
expect(checks).toEqual([]);
|
|
131
|
+
expect(skippedProviders).toEqual([]);
|
|
132
|
+
});
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
// ---------------------------------------------------------------------------
|
|
136
|
+
// buildPreflightReport — pure, zero mocks
|
|
137
|
+
// ---------------------------------------------------------------------------
|
|
138
|
+
|
|
139
|
+
describe("buildPreflightReport", () => {
|
|
140
|
+
it("builds available map from passing results", () => {
|
|
141
|
+
const results = [
|
|
142
|
+
{ provider: "claude", model: "sonnet", available: true, latencyMs: 50 },
|
|
143
|
+
{ provider: "codex", model: "codex-mini-latest", available: true, latencyMs: 30 },
|
|
144
|
+
];
|
|
145
|
+
|
|
146
|
+
const report = buildPreflightReport(results, 100);
|
|
147
|
+
|
|
148
|
+
expect(report.allFailed).toBe(false);
|
|
149
|
+
expect(report.available.size).toBe(2);
|
|
150
|
+
expect(report.available.get("claude")?.has("sonnet")).toBe(true);
|
|
151
|
+
expect(report.available.get("codex")?.has("codex-mini-latest")).toBe(true);
|
|
152
|
+
expect(report.totalMs).toBe(100);
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
it("reports allFailed when all results fail", () => {
|
|
156
|
+
const results = [
|
|
157
|
+
{ provider: "claude", model: "sonnet", available: false, error: "Auth failed", latencyMs: 10 },
|
|
158
|
+
{ provider: "codex", model: "codex-mini", available: false, error: "Timeout", latencyMs: 15_000 },
|
|
159
|
+
];
|
|
160
|
+
|
|
161
|
+
const report = buildPreflightReport(results, 15_000);
|
|
162
|
+
|
|
163
|
+
expect(report.allFailed).toBe(true);
|
|
164
|
+
expect(report.available.size).toBe(0);
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
it("reports allFailed=true for empty results", () => {
|
|
168
|
+
const report = buildPreflightReport([], 5);
|
|
169
|
+
|
|
170
|
+
expect(report.allFailed).toBe(true);
|
|
171
|
+
expect(report.available.size).toBe(0);
|
|
172
|
+
expect(report.checks).toEqual([]);
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
it("groups multiple models under same provider", () => {
|
|
176
|
+
const results = [
|
|
177
|
+
{ provider: "claude", model: "sonnet", available: true, latencyMs: 50 },
|
|
178
|
+
{ provider: "claude", model: "opus", available: true, latencyMs: 80 },
|
|
179
|
+
];
|
|
180
|
+
|
|
181
|
+
const report = buildPreflightReport(results, 80);
|
|
182
|
+
|
|
183
|
+
expect(report.available.get("claude")?.size).toBe(2);
|
|
184
|
+
expect(report.available.get("claude")?.has("sonnet")).toBe(true);
|
|
185
|
+
expect(report.available.get("claude")?.has("opus")).toBe(true);
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
it("excludes failed models from available map", () => {
|
|
189
|
+
const results = [
|
|
190
|
+
{ provider: "claude", model: "sonnet", available: true, latencyMs: 50 },
|
|
191
|
+
{ provider: "claude", model: "opus", available: false, error: "Rate limited", latencyMs: 10 },
|
|
192
|
+
];
|
|
193
|
+
|
|
194
|
+
const report = buildPreflightReport(results, 50);
|
|
195
|
+
|
|
196
|
+
expect(report.allFailed).toBe(false);
|
|
197
|
+
expect(report.available.get("claude")?.has("sonnet")).toBe(true);
|
|
198
|
+
expect(report.available.get("claude")?.has("opus")).toBe(false);
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
it("preserves check results in output", () => {
|
|
202
|
+
const results = [
|
|
203
|
+
{ provider: "claude", model: "sonnet", available: true, latencyMs: 42 },
|
|
204
|
+
];
|
|
205
|
+
|
|
206
|
+
const report = buildPreflightReport(results, 42);
|
|
207
|
+
|
|
208
|
+
expect(report.checks.length).toBe(1);
|
|
209
|
+
expect(report.checks[0]!.provider).toBe("claude");
|
|
210
|
+
expect(report.checks[0]!.model).toBe("sonnet");
|
|
211
|
+
expect(report.checks[0]!.available).toBe(true);
|
|
212
|
+
expect(report.checks[0]!.latencyMs).toBe(42);
|
|
213
|
+
});
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
// ---------------------------------------------------------------------------
|
|
217
|
+
// classifyError — pure, zero mocks (from shared _core/lib-ts/runtime/preflight.ts)
|
|
218
|
+
// ---------------------------------------------------------------------------
|
|
219
|
+
|
|
220
|
+
describe("classifyError", () => {
|
|
221
|
+
it("classifies timeout (killed + SIGTERM)", () => {
|
|
222
|
+
expect(classifyError("", null, true, "SIGTERM")).toBe("Preflight timed out");
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
it("classifies timeout (killed without signal)", () => {
|
|
226
|
+
expect(classifyError("", null, true, null)).toBe("Preflight timed out");
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
it("classifies model not found", () => {
|
|
230
|
+
expect(classifyError("model not found", 1, false, null)).toBe("Model not available for this account");
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
it("classifies not available", () => {
|
|
234
|
+
expect(classifyError("not available for your plan", 1, false, null)).toBe("Model not available for this account");
|
|
235
|
+
});
|
|
236
|
+
|
|
237
|
+
it("classifies rate limit by text", () => {
|
|
238
|
+
expect(classifyError("rate limit exceeded", 1, false, null)).toBe("Rate limited");
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
it("classifies rate limit by 429", () => {
|
|
242
|
+
expect(classifyError("error 429", 1, false, null)).toBe("Rate limited");
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
it("classifies auth errors", () => {
|
|
246
|
+
expect(classifyError("invalid api key", 1, false, null)).toBe("Authentication failed");
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
it("classifies 401 as auth", () => {
|
|
250
|
+
expect(classifyError("HTTP 401 Unauthorized", 1, false, null)).toBe("Authentication failed");
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
it("classifies quota errors", () => {
|
|
254
|
+
expect(classifyError("billing quota exceeded", 1, false, null)).toBe("Quota/billing issue");
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
it("falls back to exit code for unknown errors", () => {
|
|
258
|
+
expect(classifyError("something unexpected", 42, false, null)).toBe("Exit code 42");
|
|
259
|
+
});
|
|
260
|
+
});
|
|
261
|
+
|
|
262
|
+
// ---------------------------------------------------------------------------
|
|
263
|
+
// runPreflight — slim integration (mock only checkProviderModel)
|
|
264
|
+
// ---------------------------------------------------------------------------
|
|
265
|
+
|
|
266
|
+
describe("runPreflight", () => {
|
|
267
|
+
// Mock the network boundary: checkProviderModel and subprocess-utils
|
|
268
|
+
const mockCheckProviderModel = mock(
|
|
269
|
+
async (provider: string, model: string) =>
|
|
270
|
+
({ provider, model, available: true, latencyMs: 10 }) as PreflightCheckResult,
|
|
271
|
+
);
|
|
272
|
+
|
|
273
|
+
const mockFindExecutable = mock(() => "/usr/bin/mock-cli" as string | null);
|
|
274
|
+
|
|
275
|
+
// Re-mock the modules to inject our mock for integration tests
|
|
276
|
+
mock.module("../../../../_core/lib-ts/runtime/preflight.js", () => ({
|
|
277
|
+
checkProviderModel: mockCheckProviderModel,
|
|
278
|
+
classifyError,
|
|
279
|
+
}));
|
|
280
|
+
|
|
281
|
+
mock.module("../../../../_core/lib-ts/runtime/subprocess-utils.js", () => ({
|
|
282
|
+
findExecutable: mockFindExecutable,
|
|
283
|
+
execFileAsync: mock(() => Promise.resolve({ stdout: "ok", stderr: "", exitCode: 0, killed: false, signal: null })),
|
|
284
|
+
isInternalCall: () => false,
|
|
285
|
+
getInternalSubprocessEnv: () => ({}),
|
|
286
|
+
normalizePathForCli: (p: string) => p,
|
|
287
|
+
shellQuoteWin: (arg: string) => arg,
|
|
288
|
+
isExecSyncError: () => false,
|
|
289
|
+
}));
|
|
290
|
+
|
|
291
|
+
// Re-import to pick up the mocked checkProviderModel
|
|
292
|
+
let runPreflight: (config: ModelsConfig, timeoutMs?: number) => Promise<PreflightReport>;
|
|
293
|
+
|
|
294
|
+
beforeEach(async () => {
|
|
295
|
+
mockCheckProviderModel.mockReset();
|
|
296
|
+
mockCheckProviderModel.mockImplementation(
|
|
297
|
+
async (provider: string, model: string) =>
|
|
298
|
+
({ provider, model, available: true, latencyMs: 10 }) as PreflightCheckResult,
|
|
299
|
+
);
|
|
300
|
+
// Force fresh import to pick up mocks
|
|
301
|
+
const mod = await import("../preflight.js");
|
|
302
|
+
runPreflight = mod.runPreflight;
|
|
303
|
+
});
|
|
304
|
+
|
|
305
|
+
it("returns available report when checks pass", async () => {
|
|
306
|
+
const config = makeModelsConfig({
|
|
307
|
+
claude: { enabled: true, models: ["sonnet"] },
|
|
308
|
+
codex: { enabled: true, models: ["codex-mini-latest"] },
|
|
309
|
+
});
|
|
310
|
+
|
|
311
|
+
const report = await runPreflight(config);
|
|
312
|
+
|
|
313
|
+
expect(report.allFailed).toBe(false);
|
|
314
|
+
expect(report.available.size).toBe(2);
|
|
315
|
+
});
|
|
316
|
+
|
|
317
|
+
it("reports allFailed when all checks fail", async () => {
|
|
318
|
+
mockCheckProviderModel.mockImplementation(
|
|
319
|
+
async (provider: string, model: string) =>
|
|
320
|
+
({ provider, model, available: false, latencyMs: 10, error: "Auth failed" }) as PreflightCheckResult,
|
|
321
|
+
);
|
|
322
|
+
|
|
323
|
+
const config = makeModelsConfig({
|
|
324
|
+
claude: { enabled: true, models: ["sonnet"] },
|
|
325
|
+
});
|
|
326
|
+
|
|
327
|
+
const report = await runPreflight(config);
|
|
328
|
+
|
|
329
|
+
expect(report.allFailed).toBe(true);
|
|
330
|
+
expect(report.available.size).toBe(0);
|
|
331
|
+
});
|
|
332
|
+
|
|
333
|
+
it("returns empty checks when no providers enabled", async () => {
|
|
334
|
+
const config = makeModelsConfig({
|
|
335
|
+
claude: { enabled: false, models: ["sonnet"] },
|
|
336
|
+
});
|
|
337
|
+
|
|
338
|
+
const report = await runPreflight(config);
|
|
339
|
+
|
|
340
|
+
expect(report.allFailed).toBe(true);
|
|
341
|
+
expect(report.checks.length).toBe(0);
|
|
342
|
+
});
|
|
343
|
+
});
|
|
344
|
+
});
|
|
@@ -3,8 +3,8 @@
|
|
|
3
3
|
* Extracted from cc-native-plan-review.ts.
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import { logDebug, logInfo, logWarn } from "../../../
|
|
7
|
-
import { findExecutable } from "../../../
|
|
6
|
+
import { logDebug, logInfo, logWarn } from "../../../_core/lib-ts/runtime/logger.js";
|
|
7
|
+
import { findExecutable } from "../../../_core/lib-ts/runtime/subprocess-utils.js";
|
|
8
8
|
import type {
|
|
9
9
|
AgentConfig,
|
|
10
10
|
AgentReviewSettings,
|
|
@@ -51,22 +51,21 @@ export function resolveMandatoryAgents(
|
|
|
51
51
|
/** Provider priority order: codex first (cheaper/faster), claude as fallback */
|
|
52
52
|
const PROVIDER_PRIORITY = ["codex", "claude"];
|
|
53
53
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
export function assignModelsToAgents(
|
|
61
|
-
agents: AgentConfig[],
|
|
54
|
+
// ---------------------------------------------------------------------------
|
|
55
|
+
// Pure Provider Resolution (extracted for direct testing)
|
|
56
|
+
// ---------------------------------------------------------------------------
|
|
57
|
+
|
|
58
|
+
/** @internal */
|
|
59
|
+
export function resolveEnabledProviders(
|
|
62
60
|
modelsConfig: ModelsConfig,
|
|
61
|
+
isCliAvailable: (cliName: string) => boolean,
|
|
63
62
|
preflightAvailable?: Map<string, Set<string>>,
|
|
64
|
-
):
|
|
65
|
-
|
|
63
|
+
): Array<[string, { enabled: boolean; models: string[] }]> {
|
|
64
|
+
const result = Object.entries(modelsConfig.providers)
|
|
66
65
|
.filter(([name, config]) => {
|
|
67
66
|
if (!config.enabled || config.models.length === 0) return false;
|
|
68
67
|
const cliName = name === "claude" ? "claude" : name;
|
|
69
|
-
const found =
|
|
68
|
+
const found = isCliAvailable(cliName);
|
|
70
69
|
if (!found) {
|
|
71
70
|
logWarn(HOOK, `Provider '${name}' enabled but CLI '${cliName}' not found on PATH — skipping`);
|
|
72
71
|
return false;
|
|
@@ -93,12 +92,35 @@ export function assignModelsToAgents(
|
|
|
93
92
|
.filter((entry): entry is [string, { enabled: boolean; models: string[] }] => entry !== null);
|
|
94
93
|
|
|
95
94
|
// Sort by provider priority (codex first)
|
|
96
|
-
|
|
95
|
+
result.sort((a, b) => {
|
|
97
96
|
const aIdx = PROVIDER_PRIORITY.indexOf(a[0]);
|
|
98
97
|
const bIdx = PROVIDER_PRIORITY.indexOf(b[0]);
|
|
99
98
|
return (aIdx === -1 ? 999 : aIdx) - (bIdx === -1 ? 999 : bIdx);
|
|
100
99
|
});
|
|
101
100
|
|
|
101
|
+
return result;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Assign providers and models to agents.
|
|
106
|
+
* When preflightAvailable is provided, filters to only models that passed preflight.
|
|
107
|
+
* Providers are ordered by PROVIDER_PRIORITY (codex first, claude fallback).
|
|
108
|
+
* All agents get the first available provider; random model within that provider.
|
|
109
|
+
*/
|
|
110
|
+
export function assignModelsToAgents(
|
|
111
|
+
agents: AgentConfig[],
|
|
112
|
+
modelsConfig: ModelsConfig,
|
|
113
|
+
preflightAvailable?: Map<string, Set<string>>,
|
|
114
|
+
options?: {
|
|
115
|
+
isCliAvailable?: (name: string) => boolean;
|
|
116
|
+
randomFn?: () => number;
|
|
117
|
+
},
|
|
118
|
+
): AgentConfig[] {
|
|
119
|
+
const isCliAvailable = options?.isCliAvailable ?? ((name: string) => Boolean(findExecutable(name)));
|
|
120
|
+
const randomFn = options?.randomFn ?? Math.random;
|
|
121
|
+
|
|
122
|
+
const enabledProviders = resolveEnabledProviders(modelsConfig, isCliAvailable, preflightAvailable);
|
|
123
|
+
|
|
102
124
|
if (enabledProviders.length === 0) {
|
|
103
125
|
logWarn(HOOK, "No providers with available CLI found, falling back to Claude with agent defaults");
|
|
104
126
|
return agents.map(a => ({ ...a, provider: "claude" }));
|
|
@@ -109,7 +131,7 @@ export function assignModelsToAgents(
|
|
|
109
131
|
logInfo(HOOK, `Using provider: ${providerName} (models: ${providerConfig.models.join(", ")})`);
|
|
110
132
|
|
|
111
133
|
return agents.map(agent => {
|
|
112
|
-
const modelIdx = Math.floor(
|
|
134
|
+
const modelIdx = Math.floor(randomFn() * providerConfig.models.length);
|
|
113
135
|
const model = providerConfig.models[modelIdx] ?? providerConfig.models[0] ?? agent.model;
|
|
114
136
|
return { ...agent, provider: providerName, model };
|
|
115
137
|
});
|
|
@@ -1,35 +1,23 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Corroboration-based verdict computation for plan review.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
* density (one verbose agent floods a dimension).
|
|
4
|
+
* Replaces the old per-verdict aggregation with proportional thresholding:
|
|
5
|
+
* high-severity issues in a dimension only block when the total count
|
|
6
|
+
* exceeds 2× the number of distinct agents contributing to that dimension.
|
|
8
7
|
*
|
|
9
|
-
* **
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
* - At 6 agents: threshold=3 (50% must agree)
|
|
15
|
-
* - At 10 agents: threshold=4 (40% must agree)
|
|
16
|
-
* - At 20 agents: threshold=8 (40% must agree)
|
|
17
|
-
*
|
|
18
|
-
* **Why agent-agreement over issue-density:**
|
|
19
|
-
* The previous system (issues >= 2×agents_in_dimension) allowed a single agent
|
|
20
|
-
* to self-corroborate by raising 2+ issues, and made blocking harder as more
|
|
21
|
-
* agents covered a dimension (inverted scaling). Agent-agreement fixes both:
|
|
22
|
-
* a single agent can never self-corroborate, and more agents agreeing is a
|
|
23
|
-
* stronger signal, not a weaker one.
|
|
8
|
+
* **Why proportional thresholding:**
|
|
9
|
+
* The agent pool has dimensional imbalance (e.g., 10 completeness agents vs
|
|
10
|
+
* 1 maintainability agent). A fixed "2+ agents agree = block" would mean
|
|
11
|
+
* any 2 completeness agents always block. Proportional scaling (issues > 2×agents)
|
|
12
|
+
* sets a fair bar regardless of how many agents focus on each dimension.
|
|
24
13
|
*
|
|
25
14
|
* **Convergence problem this solves:**
|
|
26
15
|
* Agents with opposing philosophies (simplicity-guardian vs completeness-gaps)
|
|
27
16
|
* produce contradictory high-severity issues. Because the old system treated
|
|
28
17
|
* every agent's finding as independently authoritative, plans oscillated —
|
|
29
|
-
* addressing one agent's feedback triggered the opposing agent.
|
|
30
|
-
* floor prevents any single agent's philosophy from blocking alone.
|
|
18
|
+
* addressing one agent's feedback triggered the opposing agent.
|
|
31
19
|
*
|
|
32
|
-
* **Revert path:** Change one line in review
|
|
20
|
+
* **Revert path:** Change one line in cc-native-plan-review.ts back to
|
|
33
21
|
* `computeReviewDecision(allVerdicts)`. Old function kept in verdict.ts.
|
|
34
22
|
*/
|
|
35
23
|
|
|
@@ -42,55 +30,22 @@ import type {
|
|
|
42
30
|
SoloFinding,
|
|
43
31
|
} from "../../lib-ts/types.js";
|
|
44
32
|
|
|
45
|
-
/** Configuration for corroboration thresholds */
|
|
46
|
-
export interface CorroborationConfig {
|
|
47
|
-
/** Minimum distinct agents that must agree to trigger blocking (default: 2) */
|
|
48
|
-
minAgreement?: number;
|
|
49
|
-
/** Minimum fraction of total agent pool that must agree (default: 0.40) */
|
|
50
|
-
minRatio?: number;
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
const DEFAULT_MIN_AGREEMENT = 2;
|
|
54
|
-
const DEFAULT_MIN_RATIO = 0.40;
|
|
55
|
-
|
|
56
|
-
/**
|
|
57
|
-
* Compute the effective blocking threshold for a given agent pool size.
|
|
58
|
-
*
|
|
59
|
-
* Returns `max(minAgreement, ceil(minRatio × totalAgents))`.
|
|
60
|
-
* This ensures a fixed floor (no single-agent self-corroboration) while
|
|
61
|
-
* scaling proportionally at larger pool sizes.
|
|
62
|
-
*/
|
|
63
|
-
export function getEffectiveThreshold(
|
|
64
|
-
totalAgents: number,
|
|
65
|
-
config: CorroborationConfig = {},
|
|
66
|
-
): number {
|
|
67
|
-
const minAgreement = config.minAgreement ?? DEFAULT_MIN_AGREEMENT;
|
|
68
|
-
const minRatio = config.minRatio ?? DEFAULT_MIN_RATIO;
|
|
69
|
-
return Math.max(minAgreement, Math.ceil(totalAgents * minRatio));
|
|
70
|
-
}
|
|
71
|
-
|
|
72
33
|
/**
|
|
73
34
|
* Compute a corroboration-based review decision from all reviewer results.
|
|
74
35
|
*
|
|
75
36
|
* Algorithm:
|
|
76
37
|
* 1. Collect all high-severity issues with a `dimension` field
|
|
77
38
|
* 2. Group by dimension, tracking distinct agent names per group
|
|
78
|
-
* 3.
|
|
79
|
-
* 4.
|
|
80
|
-
* 5.
|
|
81
|
-
* 6. Non-high issues are ignored (informational only)
|
|
39
|
+
* 3. For each dimension: block if `issues.length > 2 × agentCount`
|
|
40
|
+
* 4. Issues without `dimension` are unclassified (never block)
|
|
41
|
+
* 5. Non-high issues are ignored (informational only)
|
|
82
42
|
*
|
|
83
43
|
* @param allResults - Map of reviewer name → ReviewerResult (CLI + agent)
|
|
84
|
-
* @param config - Optional threshold configuration
|
|
85
44
|
* @returns CorroborationResult with blocking groups, solo findings, and verdict
|
|
86
45
|
*/
|
|
87
46
|
export function computeCorroboratedDecision(
|
|
88
47
|
allResults: Record<string, ReviewerResult>,
|
|
89
|
-
config: CorroborationConfig = {},
|
|
90
48
|
): CorroborationResult {
|
|
91
|
-
const totalAgents = Object.keys(allResults).length;
|
|
92
|
-
const threshold = getEffectiveThreshold(totalAgents, config);
|
|
93
|
-
|
|
94
49
|
// Accumulator: dimension → { issues, agentNames }
|
|
95
50
|
const dimMap = new Map<
|
|
96
51
|
IssueDimension,
|
|
@@ -111,7 +66,7 @@ export function computeCorroboratedDecision(
|
|
|
111
66
|
// Only high-severity issues participate in corroboration
|
|
112
67
|
if (issue.severity !== "high") continue;
|
|
113
68
|
|
|
114
|
-
// Issues without dimension are unclassified —
|
|
69
|
+
// Issues without dimension are unclassified — cannot block
|
|
115
70
|
if (!issue.dimension) {
|
|
116
71
|
unclassified.push({ agent: agentName, issue });
|
|
117
72
|
continue;
|
|
@@ -127,22 +82,14 @@ export function computeCorroboratedDecision(
|
|
|
127
82
|
}
|
|
128
83
|
}
|
|
129
84
|
|
|
130
|
-
// Warn about unclassified issues so they don't silently disappear
|
|
131
|
-
if (unclassified.length > 0) {
|
|
132
|
-
const agents = [...new Set(unclassified.map(u => u.agent))];
|
|
133
|
-
process.stderr.write(
|
|
134
|
-
`[corroboration] WARNING: ${unclassified.length} high-severity issue(s) from [${agents.join(", ")}] lack dimension classification and cannot participate in corroboration\n`,
|
|
135
|
-
);
|
|
136
|
-
}
|
|
137
|
-
|
|
138
85
|
const blocking: CorroboratedGroup[] = [];
|
|
139
86
|
const solo: SoloFinding[] = [];
|
|
140
87
|
|
|
141
88
|
for (const [dimension, group] of dimMap) {
|
|
142
89
|
const agentCount = group.agentNames.size;
|
|
90
|
+
const threshold = 2 * agentCount;
|
|
143
91
|
|
|
144
|
-
|
|
145
|
-
if (agentCount >= threshold) {
|
|
92
|
+
if (group.issues.length >= threshold) {
|
|
146
93
|
blocking.push({
|
|
147
94
|
dimension,
|
|
148
95
|
issues: group.issues,
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
import { OrchestratorClaudeAgent } from "./reviewers/providers/orchestrator-claude-agent.js";
|
|
8
|
-
import { logInfo, logWarn } from "../../../
|
|
8
|
+
import { logInfo, logWarn } from "../../../_core/lib-ts/runtime/logger.js";
|
|
9
9
|
import type { AgentConfig, AgentReviewSettings, OrchestratorConfig, OrchestratorResult } from "../../lib-ts/types.js";
|
|
10
10
|
|
|
11
11
|
// Re-export for backward compatibility (moved to reviewers/schemas.ts)
|
|
@@ -8,8 +8,8 @@ import * as path from "node:path";
|
|
|
8
8
|
|
|
9
9
|
import { runAgentReview } from "./reviewers/index.js";
|
|
10
10
|
import { QUESTIONS_SCHEMA } from "./reviewers/schemas.js";
|
|
11
|
-
import { logInfo, logWarn, logError } from "../../../
|
|
12
|
-
import { findExecutable } from "../../../
|
|
11
|
+
import { logInfo, logWarn, logError } from "../../../_core/lib-ts/runtime/logger.js";
|
|
12
|
+
import { findExecutable } from "../../../_core/lib-ts/runtime/subprocess-utils.js";
|
|
13
13
|
import { aggregateAgents } from "../../lib-ts/aggregate-agents.js";
|
|
14
14
|
import type { AgentConfig } from "../../lib-ts/types.js";
|
|
15
15
|
|