aiwcli 0.15.7 → 0.17.0
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 +106 -1125
- package/bin/run.js +0 -4
- package/dist/capabilities/installation/control-plane/clear-command.d.ts +2 -0
- package/dist/capabilities/installation/control-plane/clear-command.js +32 -3
- package/dist/capabilities/installation/control-plane/init-command.js +2 -2
- package/dist/capabilities/launch/contracts.d.ts +39 -4
- package/dist/capabilities/launch/control-plane/execute-launch.js +158 -119
- package/dist/capabilities/launch/runtime-core/launch-decisions.d.ts +82 -0
- package/dist/capabilities/launch/runtime-core/launch-decisions.js +202 -0
- package/dist/commands/branch.d.ts +1 -1
- package/dist/commands/branch.js +1 -1
- package/dist/commands/launch.d.ts +0 -5
- package/dist/commands/launch.js +2 -37
- package/dist/lib/config.js +1 -2
- package/dist/lib/context/context-store.js +28 -2
- package/dist/lib/core-installer.d.ts +1 -1
- package/dist/lib/core-installer.js +6 -27
- package/dist/lib/debug.d.ts +0 -10
- package/dist/lib/debug.js +0 -10
- package/dist/lib/env-sanitizer.d.ts +25 -0
- package/dist/lib/env-sanitizer.js +46 -0
- package/dist/lib/errors.d.ts +0 -13
- package/dist/lib/errors.js +0 -15
- package/dist/lib/git-exclude-manager.js +1 -1
- package/dist/lib/hooks/context-monitor-logic.d.ts +6 -0
- package/dist/lib/hooks/context-monitor-logic.js +25 -0
- package/dist/lib/hooks/hook-utils.js +11 -0
- package/dist/lib/hooks/prompt-binding-logic.d.ts +7 -0
- package/dist/lib/hooks/prompt-binding-logic.js +50 -0
- package/dist/lib/hooks/session-end-logic.js +2 -14
- package/dist/lib/install-state.js +6 -13
- package/dist/lib/json-io.d.ts +12 -0
- package/dist/lib/json-io.js +30 -0
- package/dist/lib/multiplexer.d.ts +43 -35
- package/dist/lib/multiplexer.js +21 -2
- package/dist/lib/multiplexers/psmux.d.ts +14 -34
- package/dist/lib/multiplexers/psmux.js +70 -130
- package/dist/lib/multiplexers/tmux.d.ts +11 -19
- package/dist/lib/multiplexers/tmux.js +79 -120
- package/dist/lib/multiplexers/wezterm.d.ts +38 -0
- package/dist/lib/multiplexers/wezterm.js +225 -0
- package/dist/lib/mux-utils.d.ts +4 -3
- package/dist/lib/mux-utils.js +7 -13
- package/dist/lib/prompt-file-manager.d.ts +23 -0
- package/dist/lib/prompt-file-manager.js +41 -0
- package/dist/lib/runtime/agent-launcher.d.ts +67 -0
- package/dist/lib/runtime/agent-launcher.js +262 -0
- package/dist/lib/runtime/aiw-cli.d.ts +2 -0
- package/dist/lib/runtime/aiw-cli.js +3 -1
- package/dist/lib/runtime/cli-args.d.ts +5 -2
- package/dist/lib/runtime/cli-args.js +18 -3
- package/dist/lib/runtime/inference.js +3 -14
- package/dist/lib/runtime/models.d.ts +6 -0
- package/dist/lib/runtime/models.js +6 -0
- package/dist/lib/runtime/state-io.d.ts +2 -1
- package/dist/lib/runtime/state-io.js +9 -4
- package/dist/lib/runtime/utils.d.ts +8 -0
- package/dist/lib/runtime/utils.js +31 -1
- package/dist/lib/schemas.d.ts +250 -0
- package/dist/lib/schemas.js +216 -0
- package/dist/lib/sentinel-manager.d.ts +32 -0
- package/dist/lib/sentinel-manager.js +62 -0
- package/dist/lib/sentinel-wrapper.d.ts +1 -0
- package/dist/lib/sentinel-wrapper.js +12 -3
- package/dist/lib/settings-hierarchy.js +3 -20
- package/dist/lib/shell-adapters/bash-adapter.d.ts +18 -0
- package/dist/lib/shell-adapters/bash-adapter.js +69 -0
- package/dist/lib/shell-adapters/index.d.ts +5 -0
- package/dist/lib/shell-adapters/index.js +7 -0
- package/dist/lib/shell-adapters/powershell-adapter.d.ts +18 -0
- package/dist/lib/shell-adapters/powershell-adapter.js +62 -0
- package/dist/lib/shell-adapters/shell-adapter.d.ts +45 -0
- package/dist/lib/shell-adapters/shell-adapter.js +5 -0
- package/dist/lib/spawn-errors.d.ts +3 -0
- package/dist/lib/spawn-errors.js +15 -1
- package/dist/lib/spinner.d.ts +0 -5
- package/dist/lib/spinner.js +0 -16
- package/dist/lib/template-installer.d.ts +10 -0
- package/dist/lib/template-installer.js +4 -4
- package/dist/lib/terminal-strategy.d.ts +1 -0
- package/dist/lib/terminal-strategy.js +12 -6
- package/dist/lib/terminal.d.ts +7 -5
- package/dist/lib/terminal.js +42 -19
- package/dist/lib/tmux-primitives.d.ts +0 -2
- package/dist/lib/tmux-primitives.js +0 -4
- package/dist/lib/tmux-session.js +2 -1
- package/dist/lib/windsurf-hooks-hierarchy.js +6 -23
- package/dist/platform/launch.d.ts +2 -1
- package/dist/platform/launch.js +1 -0
- package/dist/templates/CLAUDE.md +0 -1
- package/dist/templates/cc-native/.claude/settings.json +0 -10
- package/dist/templates/cc-native/TEMPLATE-SCHEMA.md +11 -4
- package/dist/templates/cc-native/_cc-native/cc-native.config.json +3 -7
- package/dist/templates/cc-native/_cc-native/hooks/CLAUDE.md +26 -47
- package/dist/templates/cc-native/_cc-native/hooks/cc-native-plan-review.ts +7 -9
- package/dist/templates/cc-native/_cc-native/hooks/enhance_plan_post_write.ts +2 -3
- package/dist/templates/cc-native/_cc-native/hooks/mark_questions_asked.ts +2 -2
- package/dist/templates/cc-native/_cc-native/hooks/plan_questions_early.ts +0 -25
- package/dist/templates/cc-native/_cc-native/hooks/validate_task_prompt.ts +4 -4
- package/dist/templates/cc-native/_cc-native/lib-ts/.mocharc.json +9 -0
- package/dist/templates/cc-native/_cc-native/lib-ts/__tests__/aggregate-agents.test.ts +118 -0
- package/dist/templates/cc-native/_cc-native/lib-ts/__tests__/artifacts.test.ts +234 -0
- package/dist/templates/cc-native/_cc-native/lib-ts/__tests__/cc-native-state.test.ts +170 -0
- package/dist/templates/cc-native/_cc-native/lib-ts/__tests__/cli-output-parser.test.ts +73 -0
- package/dist/templates/cc-native/_cc-native/lib-ts/__tests__/config.test.ts +64 -0
- package/dist/templates/cc-native/_cc-native/lib-ts/__tests__/constants.test.ts +40 -0
- package/dist/templates/cc-native/_cc-native/lib-ts/__tests__/debug.test.ts +42 -0
- package/dist/templates/cc-native/_cc-native/lib-ts/__tests__/exports.test.ts +58 -0
- package/dist/templates/cc-native/_cc-native/lib-ts/__tests__/helpers.ts +107 -0
- package/dist/templates/cc-native/_cc-native/lib-ts/__tests__/hooks/add-plan-context.hook.test.ts +97 -0
- package/dist/templates/cc-native/_cc-native/lib-ts/__tests__/hooks/plan-questions.hook.test.ts +81 -0
- package/dist/templates/cc-native/_cc-native/lib-ts/__tests__/hooks/plan-review.hook.test.ts +71 -0
- package/dist/templates/cc-native/_cc-native/lib-ts/__tests__/json-parser.test.ts +99 -0
- package/dist/templates/cc-native/_cc-native/lib-ts/__tests__/orchestrator-agent.test.ts +288 -0
- package/dist/templates/cc-native/_cc-native/lib-ts/__tests__/orchestrator.test.ts +48 -0
- package/dist/templates/cc-native/_cc-native/lib-ts/__tests__/reviewers.test.ts +32 -0
- package/dist/templates/cc-native/_cc-native/lib-ts/__tests__/state.test.ts +124 -0
- package/dist/templates/cc-native/_cc-native/lib-ts/__tests__/verdict.test.ts +93 -0
- package/dist/templates/cc-native/_cc-native/lib-ts/agent-selection.ts +163 -0
- package/dist/templates/cc-native/_cc-native/lib-ts/aggregate-agents.ts +6 -14
- package/dist/templates/cc-native/_cc-native/{artifacts/lib → lib-ts/artifacts}/format.ts +597 -599
- package/dist/templates/cc-native/_cc-native/{artifacts/lib → lib-ts/artifacts}/index.ts +26 -26
- package/dist/templates/cc-native/_cc-native/{artifacts/lib → lib-ts/artifacts}/tracker.ts +106 -107
- package/dist/templates/cc-native/_cc-native/{artifacts/lib → lib-ts/artifacts}/write.ts +118 -119
- package/dist/templates/cc-native/_cc-native/lib-ts/artifacts.ts +21 -0
- package/dist/templates/cc-native/_cc-native/lib-ts/cc-native-state.ts +16 -15
- package/dist/templates/cc-native/_cc-native/lib-ts/cli-output-parser.ts +132 -10
- package/dist/templates/cc-native/_cc-native/lib-ts/constants.ts +6 -6
- package/dist/templates/cc-native/_cc-native/{plan-review/lib → lib-ts}/corroboration.ts +119 -119
- package/dist/templates/cc-native/_cc-native/lib-ts/debug.ts +1 -2
- package/dist/templates/cc-native/_cc-native/{plan-review/lib → lib-ts}/graduation.ts +132 -132
- package/dist/templates/cc-native/_cc-native/lib-ts/index.ts +88 -86
- package/dist/templates/cc-native/_cc-native/lib-ts/json-parser.ts +5 -6
- package/dist/templates/cc-native/_cc-native/{plan-review/lib → lib-ts}/orchestrator.ts +70 -70
- package/dist/templates/cc-native/_cc-native/{plan-review/lib → lib-ts}/output-builder.ts +130 -121
- package/dist/templates/cc-native/_cc-native/lib-ts/package-lock.json +1679 -0
- package/dist/templates/cc-native/_cc-native/lib-ts/package.json +24 -0
- package/dist/templates/cc-native/_cc-native/lib-ts/plan-discovery.ts +4 -4
- package/dist/templates/cc-native/_cc-native/lib-ts/plan-enhancement.ts +1 -6
- package/dist/templates/cc-native/_cc-native/{plan-review/lib → lib-ts}/plan-questions.ts +101 -101
- package/dist/templates/cc-native/_cc-native/{plan-review/lib → lib-ts}/review-pipeline.ts +511 -543
- package/dist/templates/cc-native/_cc-native/lib-ts/reviewers/__tests__/agent-providers.test.ts +262 -0
- package/dist/templates/cc-native/_cc-native/{plan-review/lib → lib-ts}/reviewers/agent.ts +71 -85
- package/dist/templates/{core/lib-ts/agent-exec → cc-native/_cc-native/lib-ts/reviewers/base}/base-agent.ts +138 -152
- package/dist/templates/cc-native/_cc-native/{plan-review/lib → lib-ts}/reviewers/index.ts +12 -12
- package/dist/templates/cc-native/_cc-native/{plan-review/lib → lib-ts}/reviewers/providers/claude-agent.ts +66 -57
- package/dist/templates/cc-native/_cc-native/{plan-review/lib → lib-ts}/reviewers/providers/codex-agent.ts +185 -200
- package/dist/templates/cc-native/_cc-native/{plan-review/lib → lib-ts}/reviewers/providers/gemini-agent.ts +39 -40
- package/dist/templates/cc-native/_cc-native/{plan-review/lib → lib-ts}/reviewers/providers/orchestrator-claude-agent.ts +196 -224
- package/dist/templates/cc-native/_cc-native/{plan-review/lib → lib-ts}/reviewers/schemas.ts +201 -201
- package/dist/templates/cc-native/_cc-native/{plan-review/lib → lib-ts}/reviewers/types.ts +21 -23
- package/dist/templates/cc-native/_cc-native/lib-ts/rlm/__tests__/hyde.test.ts +365 -0
- package/dist/templates/cc-native/_cc-native/lib-ts/rlm/__tests__/ollama-client.test.ts +223 -0
- package/dist/templates/cc-native/_cc-native/lib-ts/rlm/embedding-indexer.ts +12 -16
- package/dist/templates/cc-native/_cc-native/lib-ts/rlm/hyde.ts +3 -2
- package/dist/templates/cc-native/_cc-native/lib-ts/rlm/index.ts +31 -31
- package/dist/templates/cc-native/_cc-native/lib-ts/rlm/logger.ts +6 -7
- package/dist/templates/cc-native/_cc-native/lib-ts/rlm/ollama-client.ts +7 -9
- package/dist/templates/cc-native/_cc-native/lib-ts/rlm/retrieval-pipeline.ts +14 -17
- package/dist/templates/cc-native/_cc-native/lib-ts/rlm/transcript-indexer.ts +37 -41
- package/dist/templates/cc-native/_cc-native/lib-ts/rlm/transcript-loader.ts +33 -43
- package/dist/templates/cc-native/_cc-native/lib-ts/rlm/transcript-searcher.ts +20 -20
- package/dist/templates/cc-native/_cc-native/lib-ts/rlm/types.ts +8 -9
- package/dist/templates/cc-native/_cc-native/lib-ts/rlm/vector-store.ts +3 -4
- package/dist/templates/cc-native/_cc-native/lib-ts/settings.ts +50 -126
- package/dist/templates/cc-native/_cc-native/lib-ts/state.ts +19 -21
- package/dist/templates/cc-native/_cc-native/lib-ts/types.ts +13 -88
- package/dist/templates/cc-native/_cc-native/{plan-review/lib → lib-ts}/verdict.ts +72 -72
- package/dist/templates/cc-native/_cc-native/plan-review/CLAUDE.md +35 -0
- package/dist/templates/cc-native/_cc-native/plan-review/lib/agent-selection.ts +1 -1
- package/dist/templates/cc-native/_cc-native/scripts/council_debate.ts +242 -0
- package/dist/templates/cc-native/_cc-native/scripts/council_debate_simple.ts +294 -0
- package/dist/templates/cc-native/_cc-native/{plan-review/workflows → workflows}/specdev.md +9 -9
- package/dist/templates/core/.claude/skills/codex/SKILL.md +25 -0
- package/dist/templates/core/.claude/skills/devin/SKILL.md +25 -0
- package/dist/templates/core/.claude/skills/handoff/SKILL.md +11 -0
- package/dist/templates/core/.claude/skills/handoff-resume/SKILL.md +11 -0
- package/dist/templates/core/.claude/skills/meta-plan/SKILL.md +13 -0
- package/dist/templates/core/.codex/skills/codex/SKILL.md +13 -0
- package/dist/templates/core/.codex/skills/devin/SKILL.md +19 -0
- package/dist/templates/core/.codex/skills/handoff/SKILL.md +11 -0
- package/dist/templates/core/.codex/skills/handoff-resume/SKILL.md +11 -0
- package/dist/templates/core/.codex/{workflows/meta-plan.md → skills/meta-plan/SKILL.md} +6 -0
- package/dist/templates/core/{.cognition → .devin}/AGENTS.md +2 -2
- package/dist/templates/core/.devin/skills/codex/SKILL.md +19 -0
- package/dist/templates/core/.devin/skills/devin/SKILL.md +13 -0
- package/dist/templates/core/.devin/skills/handoff/SKILL.md +11 -0
- package/dist/templates/core/.devin/skills/handoff-resume/SKILL.md +11 -0
- package/dist/templates/core/.devin/skills/meta-plan/SKILL.md +13 -0
- package/dist/templates/core/.windsurf/workflows/handoff-resume.md +9 -0
- package/dist/templates/core/hooks-ts/archive_plan.ts +1 -21
- package/dist/templates/core/hooks-ts/file-suggestion.ts +1 -19
- package/dist/templates/core/hooks-ts/pre_compact.ts +5 -18
- package/dist/templates/core/lib-ts/context/context-store.ts +29 -2
- package/dist/templates/core/lib-ts/hooks/hook-utils.ts +11 -0
- package/dist/templates/core/lib-ts/hooks/session-end-logic.ts +2 -13
- package/dist/templates/core/lib-ts/runtime/agent-launcher.ts +74 -0
- package/dist/templates/core/lib-ts/runtime/aiw-cli.ts +4 -2
- package/dist/templates/core/lib-ts/runtime/cli-args.ts +18 -4
- package/dist/templates/core/lib-ts/runtime/inference.ts +3 -15
- package/dist/templates/core/lib-ts/runtime/models.ts +7 -0
- package/dist/templates/core/lib-ts/runtime/state-io.ts +9 -4
- package/dist/templates/core/lib-ts/runtime/utils.ts +30 -1
- package/dist/templates/core/lib-ts/schemas.ts +233 -0
- package/dist/templates/core/scripts/resolve-run.ts +34 -2
- package/dist/templates/core/scripts/status_line.ts +1 -1
- package/dist/templates/core/skills/codex/CLAUDE.md +9 -4
- package/dist/templates/core/skills/codex/SKILL.md +6 -0
- package/dist/templates/core/skills/codex/lib/codex-watcher.ts +3 -10
- package/dist/templates/core/skills/codex/scripts/launch-codex.ts +26 -26
- package/dist/templates/core/skills/devin/CLAUDE.md +63 -6
- package/dist/templates/core/skills/devin/lib/devin-watcher.ts +116 -96
- package/dist/templates/core/skills/devin/scripts/launch-devin.ts +22 -21
- package/dist/templates/core/skills/handoff-system/CLAUDE.md +1 -1
- package/oclif.manifest.json +4 -4
- package/package.json +4 -4
- package/dist/lib/base-command.d.ts +0 -1
- package/dist/lib/base-command.js +0 -1
- package/dist/lib/env-compat.d.ts +0 -18
- package/dist/lib/env-compat.js +0 -23
- package/dist/lib/launch-options.d.ts +0 -1
- package/dist/lib/launch-options.js +0 -1
- package/dist/lib/stdin.d.ts +0 -48
- package/dist/lib/stdin.js +0 -60
- package/dist/templates/cc-native/_cc-native/CLAUDE.md +0 -73
- package/dist/templates/cc-native/_cc-native/artifacts/CLAUDE.md +0 -64
- package/dist/templates/cc-native/_cc-native/lib-ts/CLAUDE.md +0 -70
- package/dist/templates/cc-native/_cc-native/plan-review/CODING-STANDARDS-CHECKLIST.md +0 -75
- package/dist/templates/cc-native/_cc-native/plan-review/agents/CLAUDE.md +0 -143
- package/dist/templates/cc-native/_cc-native/plan-review/agents/PLAN-ORCHESTRATOR.md +0 -213
- package/dist/templates/cc-native/_cc-native/plan-review/agents/plan-questions/PLAN-QUESTIONER.md +0 -70
- package/dist/templates/cc-native/_cc-native/plan-review/agents/plan-review/ARCH-EVOLUTION.md +0 -62
- package/dist/templates/cc-native/_cc-native/plan-review/agents/plan-review/ARCH-PATTERNS.md +0 -61
- package/dist/templates/cc-native/_cc-native/plan-review/agents/plan-review/ARCH-STRUCTURE.md +0 -62
- package/dist/templates/cc-native/_cc-native/plan-review/agents/plan-review/ASSUMPTION-TRACER.md +0 -56
- package/dist/templates/cc-native/_cc-native/plan-review/agents/plan-review/CLARITY-AUDITOR.md +0 -53
- package/dist/templates/cc-native/_cc-native/plan-review/agents/plan-review/COMPLETENESS-FEASIBILITY.md +0 -66
- package/dist/templates/cc-native/_cc-native/plan-review/agents/plan-review/COMPLETENESS-GAPS.md +0 -70
- package/dist/templates/cc-native/_cc-native/plan-review/agents/plan-review/COMPLETENESS-ORDERING.md +0 -62
- package/dist/templates/cc-native/_cc-native/plan-review/agents/plan-review/CONSTRAINT-VALIDATOR.md +0 -72
- package/dist/templates/cc-native/_cc-native/plan-review/agents/plan-review/DESIGN-ADR-VALIDATOR.md +0 -61
- package/dist/templates/cc-native/_cc-native/plan-review/agents/plan-review/DESIGN-SCALE-MATCHER.md +0 -64
- package/dist/templates/cc-native/_cc-native/plan-review/agents/plan-review/DEVILS-ADVOCATE.md +0 -56
- package/dist/templates/cc-native/_cc-native/plan-review/agents/plan-review/DOCUMENTATION-PHILOSOPHY.md +0 -86
- package/dist/templates/cc-native/_cc-native/plan-review/agents/plan-review/HANDOFF-READINESS.md +0 -59
- package/dist/templates/cc-native/_cc-native/plan-review/agents/plan-review/HIDDEN-COMPLEXITY.md +0 -58
- package/dist/templates/cc-native/_cc-native/plan-review/agents/plan-review/INCREMENTAL-DELIVERY.md +0 -66
- package/dist/templates/cc-native/_cc-native/plan-review/agents/plan-review/RISK-DEPENDENCY.md +0 -62
- package/dist/templates/cc-native/_cc-native/plan-review/agents/plan-review/RISK-FMEA.md +0 -66
- package/dist/templates/cc-native/_cc-native/plan-review/agents/plan-review/RISK-PREMORTEM.md +0 -71
- package/dist/templates/cc-native/_cc-native/plan-review/agents/plan-review/RISK-REVERSIBILITY.md +0 -74
- package/dist/templates/cc-native/_cc-native/plan-review/agents/plan-review/SCOPE-BOUNDARY.md +0 -77
- package/dist/templates/cc-native/_cc-native/plan-review/agents/plan-review/SIMPLICITY-GUARDIAN.md +0 -62
- package/dist/templates/cc-native/_cc-native/plan-review/agents/plan-review/SKEPTIC.md +0 -68
- package/dist/templates/cc-native/_cc-native/plan-review/agents/plan-review/TESTDRIVEN-BEHAVIOR-AUDITOR.md +0 -61
- package/dist/templates/cc-native/_cc-native/plan-review/agents/plan-review/TESTDRIVEN-CHARACTERIZATION.md +0 -71
- package/dist/templates/cc-native/_cc-native/plan-review/agents/plan-review/TESTDRIVEN-FIRST-VALIDATOR.md +0 -61
- package/dist/templates/cc-native/_cc-native/plan-review/agents/plan-review/TESTDRIVEN-PYRAMID-ANALYZER.md +0 -61
- package/dist/templates/cc-native/_cc-native/plan-review/agents/plan-review/TRADEOFF-COSTS.md +0 -67
- package/dist/templates/cc-native/_cc-native/plan-review/agents/plan-review/TRADEOFF-STAKEHOLDERS.md +0 -65
- package/dist/templates/cc-native/_cc-native/plan-review/agents/plan-review/VERIFY-COVERAGE.md +0 -74
- package/dist/templates/cc-native/_cc-native/plan-review/agents/plan-review/VERIFY-STRENGTH.md +0 -69
- package/dist/templates/cc-native/_cc-native/plan-review/lib/reviewers/base/base-agent.ts +0 -7
- package/dist/templates/core/.codex/workflows/codex.md +0 -17
- package/dist/templates/core/.codex/workflows/handoff.md +0 -5
- package/dist/templates/core/lib-ts/agent-exec/backends/headless.ts +0 -34
- package/dist/templates/core/lib-ts/agent-exec/backends/index.ts +0 -6
- package/dist/templates/core/lib-ts/agent-exec/backends/tmux.ts +0 -148
- package/dist/templates/core/lib-ts/agent-exec/execution-backend.ts +0 -50
- package/dist/templates/core/lib-ts/agent-exec/index.ts +0 -6
- package/dist/templates/core/lib-ts/agent-exec/structured-output.ts +0 -165
- /package/dist/templates/core/{.cognition → .devin}/config.json +0 -0
|
@@ -1,543 +1,511 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Review pipeline: orchestrates the full plan review lifecycle.
|
|
3
|
-
* Wires together plan-discovery, settings, agent-selection, graduation,
|
|
4
|
-
* output-builder, and existing modules (orchestrator, corroboration, etc.).
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
import * as fs from "node:fs";
|
|
8
|
-
import * as path from "node:path";
|
|
9
|
-
|
|
10
|
-
import {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
import {
|
|
17
|
-
import {
|
|
18
|
-
import {
|
|
19
|
-
import {
|
|
20
|
-
|
|
21
|
-
import {
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
import {
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
} from "
|
|
40
|
-
|
|
41
|
-
import {
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
} from "
|
|
57
|
-
import {
|
|
58
|
-
|
|
59
|
-
const HOOK = "review-pipeline";
|
|
60
|
-
|
|
61
|
-
// ---------------------------------------------------------------------------
|
|
62
|
-
// Context Lookup (private — only used here)
|
|
63
|
-
// ---------------------------------------------------------------------------
|
|
64
|
-
|
|
65
|
-
function getActiveContextForReview(sessionId: string, projectRoot: string): ContextState | null {
|
|
66
|
-
const ctx = getContextBySessionId(sessionId, projectRoot);
|
|
67
|
-
if (ctx) {
|
|
68
|
-
logInfo(HOOK, `Found context by session_id: ${ctx.id}`);
|
|
69
|
-
return ctx;
|
|
70
|
-
}
|
|
71
|
-
const allActive = getAllContexts("active", projectRoot);
|
|
72
|
-
const planning = allActive.filter(c => c.mode === "active" || c.mode === "
|
|
73
|
-
if (planning.length === 1) {
|
|
74
|
-
logInfo(HOOK, `Found single planning context: ${planning[0]!.id}`);
|
|
75
|
-
return planning[0]!;
|
|
76
|
-
}
|
|
77
|
-
if (planning.length > 1) {
|
|
78
|
-
logWarn(HOOK, `Multiple planning contexts (${planning.length}), cannot determine which to use`);
|
|
79
|
-
} else if (allActive.length > 0) {
|
|
80
|
-
logInfo(HOOK, `Found ${allActive.length} active context(s) but none in planning mode`);
|
|
81
|
-
} else {
|
|
82
|
-
logInfo(HOOK, "No active contexts found");
|
|
83
|
-
}
|
|
84
|
-
return null;
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
// ---------------------------------------------------------------------------
|
|
88
|
-
// Pipeline
|
|
89
|
-
// ---------------------------------------------------------------------------
|
|
90
|
-
|
|
91
|
-
export async function runReviewPipeline(input: PipelineInput): Promise<PipelineResult> {
|
|
92
|
-
const { sessionId, base, aiwcliDir, transcriptPath, payload } = input;
|
|
93
|
-
|
|
94
|
-
// 1. Load settings
|
|
95
|
-
const settings
|
|
96
|
-
const planSettings
|
|
97
|
-
const agentSettings
|
|
98
|
-
|
|
99
|
-
const planReviewEnabled = planSettings.enabled ?? true;
|
|
100
|
-
const agentReviewEnabled = agentSettings.enabled ?? true;
|
|
101
|
-
|
|
102
|
-
if (!planReviewEnabled && !agentReviewEnabled) {
|
|
103
|
-
return { action: "skip", reason: "Both plan and agent review disabled" };
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
// 2. Discover plan
|
|
107
|
-
const discovered = discoverPlan(transcriptPath);
|
|
108
|
-
if (!discovered) {
|
|
109
|
-
return { action: "skip", reason: "No plan file found in ~/.claude/plans/. The plan may not have been written yet." };
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
const { content: plan, hash: planHash, path: planPath } = discovered;
|
|
113
|
-
|
|
114
|
-
// 3. Find active context (moved before questions gate for plan path change detection)
|
|
115
|
-
const activeContext = getActiveContextForReview(sessionId, base);
|
|
116
|
-
if (!activeContext) {
|
|
117
|
-
return { action: "skip", reason: "No active planning context found for this session." };
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
const contextId = activeContext.id;
|
|
121
|
-
const reviewsDir = path.join(getContextReviewsDir(contextId, base), "cc-native");
|
|
122
|
-
const contextPath = getContextDir(contextId, base);
|
|
123
|
-
|
|
124
|
-
// 4a. Load iteration state
|
|
125
|
-
let iterationState: IterationState | null = loadIterationState(reviewsDir);
|
|
126
|
-
|
|
127
|
-
// 4a-migration. Backfill sessionId for old iteration files
|
|
128
|
-
if (iterationState && !iterationState.sessionId) {
|
|
129
|
-
logInfo(HOOK, `Migrating iteration state: adding sessionId=${sessionId}`);
|
|
130
|
-
iterationState.sessionId = sessionId;
|
|
131
|
-
saveIterationState(reviewsDir, iterationState); // Persist migration
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
// 4a-session. Detect session change — reset iteration state for new planning session
|
|
135
|
-
if (iterationState && iterationState.sessionId && iterationState.sessionId !== sessionId) {
|
|
136
|
-
logInfo(HOOK, `Session changed (${iterationState.sessionId} → ${sessionId}), resetting iteration state`);
|
|
137
|
-
iterationState = null; // Force fresh state creation below
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
// 4a-init. Initialize if null
|
|
141
|
-
if (!iterationState) {
|
|
142
|
-
iterationState = {
|
|
143
|
-
current: 1, max: 1, complexity: "medium",
|
|
144
|
-
history: [], graduated: [], passStreaks: {},
|
|
145
|
-
lastPlanHash: "", lastPlanPath: "",
|
|
146
|
-
sessionId,
|
|
147
|
-
};
|
|
148
|
-
saveIterationState(reviewsDir, iterationState);
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
// 4b. Detect plan file change — reset iteration state for new plan topic
|
|
152
|
-
const lastPath = iterationState.lastPlanPath ?? "";
|
|
153
|
-
if (lastPath && lastPath !== planPath) {
|
|
154
|
-
logInfo(HOOK, `Plan file changed (${path.basename(lastPath)}→${path.basename(planPath)}), resetting iteration state for new plan`);
|
|
155
|
-
iterationState = {
|
|
156
|
-
current: 1, max: 1, complexity: "medium",
|
|
157
|
-
history: [], graduated: [], passStreaks: {},
|
|
158
|
-
lastPlanHash: "", lastPlanPath: "",
|
|
159
|
-
sessionId,
|
|
160
|
-
};
|
|
161
|
-
saveIterationState(reviewsDir, iterationState);
|
|
162
|
-
resetPlanQuestionsAsked(sessionId, base);
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
// 5. Questions gate
|
|
166
|
-
if (!wasPlanQuestionsAgentAsked(sessionId, base)) {
|
|
167
|
-
logInfo(HOOK, "Questions gate: plan-questions agent has not run yet, running now");
|
|
168
|
-
const
|
|
169
|
-
const questionsResult = await runPlanQuestions(plan, aiwcliDir,
|
|
170
|
-
|
|
171
|
-
markQuestionsAsked(sessionId, base, "agent");
|
|
172
|
-
|
|
173
|
-
const hasQuestions = questionsResult && (
|
|
174
|
-
questionsResult.questions.length > 0 ||
|
|
175
|
-
questionsResult.assumptions.length > 0 ||
|
|
176
|
-
questionsResult.ambiguities.length > 0
|
|
177
|
-
);
|
|
178
|
-
|
|
179
|
-
if (hasQuestions) {
|
|
180
|
-
const questionsList = questionsResult.questions.map((q: string, i: number) => `${i + 1}. ${q}`).join("\n");
|
|
181
|
-
const assumptionsList = questionsResult.assumptions.length > 0
|
|
182
|
-
? `\n\nAssumptions detected:\n${questionsResult.assumptions.map((a: string) => `- ${a}`).join("\n")}`
|
|
183
|
-
: "";
|
|
184
|
-
const ambiguitiesList = questionsResult.ambiguities.length > 0
|
|
185
|
-
? `\n\nAmbiguities detected:\n${questionsResult.ambiguities.map((a: string) => `- ${a}`).join("\n")}`
|
|
186
|
-
: "";
|
|
187
|
-
const contextMsg = `## Plan Questions (from independent review)\n\nAn agent reviewed your plan in a fresh context — without access to your session history or codebase exploration. It identified these questions:\n\n${questionsList}${assumptionsList}${ambiguitiesList}\n\nAsk the user these questions using AskUserQuestion before submitting the plan.`;
|
|
188
|
-
return {
|
|
189
|
-
action: "block",
|
|
190
|
-
contextText: contextMsg,
|
|
191
|
-
blockReason: "Ask the user clarifying questions before submitting the plan. Use AskUserQuestion with the questions above.",
|
|
192
|
-
};
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
logInfo(HOOK, "Questions gate: no questions generated, proceeding to review");
|
|
196
|
-
} else {
|
|
197
|
-
logInfo(HOOK, "Questions gate: agent already ran, skipping");
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
// 6. Hash + dedup
|
|
201
|
-
logDiagnostic(HOOK, "receive", `plan_size=${plan.length}, session=${sessionId.slice(0, 8)}`, {
|
|
202
|
-
inputs: { plan_hash: planHash, plan_size: plan.length, session_id: sessionId.slice(0, 12) },
|
|
203
|
-
});
|
|
204
|
-
|
|
205
|
-
// Plan-hash deduplication
|
|
206
|
-
logDebug(HOOK, `Plan hash: ${planHash}`);
|
|
207
|
-
if (isPlanAlreadyReviewed(sessionId, planHash, base)) {
|
|
208
|
-
const lastReview = getLastPlanReview(sessionId, planHash, base);
|
|
209
|
-
|
|
210
|
-
if (wasPlanPreviouslyDenied(sessionId, planHash, base)) {
|
|
211
|
-
return {
|
|
212
|
-
action: "block",
|
|
213
|
-
contextText: "[Plan Review] Plan content unchanged since last review which found issues.",
|
|
214
|
-
blockReason: "Plan unchanged since denial. Modify the plan to address review findings, then attempt ExitPlanMode again.",
|
|
215
|
-
};
|
|
216
|
-
}
|
|
217
|
-
const verdict = lastReview?.iteration?.latest_verdict || "pass";
|
|
218
|
-
return { action: "skip", reason: `Plan already reviewed (verdict: ${verdict}). Skipping re-review.` };
|
|
219
|
-
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
// 7. Iteration bounds check
|
|
223
|
-
if (iterationState.current > iterationState.max) {
|
|
224
|
-
return { action: "skip", reason: `Max review iterations reached (${iterationState.current - 1}/${iterationState.max}), allowing plan through.` };
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
// Initialize result containers
|
|
228
|
-
let orchResult: OrchestratorResult | null = null;
|
|
229
|
-
const agentResults: Record<string, ReviewerResult> = {};
|
|
230
|
-
let detectedComplexity = "medium";
|
|
231
|
-
|
|
232
|
-
//
|
|
233
|
-
const
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
}
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
logInfo(HOOK,
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
const
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
const
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
//
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
timestamp: new Date().toISOString().replace("T", " ").slice(0, 16),
|
|
513
|
-
planHash,
|
|
514
|
-
verdict: combinedResult.overall_verdict,
|
|
515
|
-
decision: trackerDecision,
|
|
516
|
-
score: reviewScore,
|
|
517
|
-
topIssues: topIssuesList,
|
|
518
|
-
reviewFolder,
|
|
519
|
-
};
|
|
520
|
-
writeReviewTracker(ccNativeReviewsDir, trackerEntry);
|
|
521
|
-
logInfo(HOOK, `Updated review tracker: ${path.join(ccNativeReviewsDir, "review-tracker.md")}`);
|
|
522
|
-
|
|
523
|
-
// Build final output
|
|
524
|
-
const output = buildReviewOutput({
|
|
525
|
-
combinedResult,
|
|
526
|
-
corroborationResult,
|
|
527
|
-
iterationState: { ...iterationState, current: currentIteration },
|
|
528
|
-
reviewFile,
|
|
529
|
-
highIssuesPath,
|
|
530
|
-
});
|
|
531
|
-
|
|
532
|
-
// Mark plan reviewed
|
|
533
|
-
const disposition = shouldDeny
|
|
534
|
-
? `hook_deny_iter_${currentIteration}`
|
|
535
|
-
: `hook_allow_iter_${currentIteration}`;
|
|
536
|
-
markPlanReviewed(sessionId, planHash, base, HOOK, { ...iterationState, current: currentIteration }, disposition);
|
|
537
|
-
|
|
538
|
-
return {
|
|
539
|
-
action: "block",
|
|
540
|
-
contextText: output.contextText,
|
|
541
|
-
blockReason: output.blockReason,
|
|
542
|
-
};
|
|
543
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* Review pipeline: orchestrates the full plan review lifecycle.
|
|
3
|
+
* Wires together plan-discovery, settings, agent-selection, graduation,
|
|
4
|
+
* output-builder, and existing modules (orchestrator, corroboration, etc.).
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import * as fs from "node:fs";
|
|
8
|
+
import * as path from "node:path";
|
|
9
|
+
|
|
10
|
+
import {
|
|
11
|
+
logDebug,
|
|
12
|
+
logInfo,
|
|
13
|
+
logWarn,
|
|
14
|
+
logError,
|
|
15
|
+
} from "../../_core/lib-ts/runtime/logger.js";
|
|
16
|
+
import { logDiagnostic } from "../../_core/lib-ts/hooks/hook-utils.js";
|
|
17
|
+
import { eprint } from "../../_core/lib-ts/runtime/utils.js";
|
|
18
|
+
import { getContextReviewsDir, getContextDir, getReviewFolderPath } from "../../_core/lib-ts/runtime/constants.js";
|
|
19
|
+
import { getContextBySessionId, getAllContexts } from "../../_core/lib-ts/context/context-store.js";
|
|
20
|
+
|
|
21
|
+
import type {
|
|
22
|
+
AgentConfig,
|
|
23
|
+
OrchestratorConfig,
|
|
24
|
+
ReviewerResult,
|
|
25
|
+
CombinedReviewResult,
|
|
26
|
+
OrchestratorResult,
|
|
27
|
+
IterationState,
|
|
28
|
+
PipelineInput,
|
|
29
|
+
PipelineResult,
|
|
30
|
+
} from "./types.js";
|
|
31
|
+
import { REVIEW_SCHEMA } from "./types.js";
|
|
32
|
+
import type { ContextState } from "../../_core/lib-ts/types.js";
|
|
33
|
+
|
|
34
|
+
import { discoverPlan } from "./plan-discovery.js";
|
|
35
|
+
import { loadSettings, loadModelsConfig, loadAgentLibrary, DEFAULT_ORCHESTRATOR } from "./settings.js";
|
|
36
|
+
import { resolveMandatoryAgents, assignModelsToAgents, selectAgents } from "./agent-selection.js";
|
|
37
|
+
import { computePassEligible, extractTopIssuesForTracker, advanceIterationState } from "./graduation.js";
|
|
38
|
+
import { truncateAgentIssues, overrideVerdictsByThreshold, buildReviewOutput } from "./output-builder.js";
|
|
39
|
+
import { DEFAULT_REVIEW_ITERATIONS, loadIterationState, saveIterationState } from "./state.js";
|
|
40
|
+
|
|
41
|
+
import {
|
|
42
|
+
isPlanAlreadyReviewed,
|
|
43
|
+
wasPlanPreviouslyDenied,
|
|
44
|
+
getLastPlanReview,
|
|
45
|
+
markPlanReviewed,
|
|
46
|
+
wasPlanQuestionsAgentAsked,
|
|
47
|
+
markQuestionsAsked,
|
|
48
|
+
resetPlanQuestionsAsked,
|
|
49
|
+
} from "./cc-native-state.js";
|
|
50
|
+
|
|
51
|
+
import { computeCorroboratedDecision } from "./corroboration.js";
|
|
52
|
+
import { runOrchestrator } from "./orchestrator.js";
|
|
53
|
+
import { debugLog } from "./debug.js";
|
|
54
|
+
import { writeCombinedArtifacts, buildCorroborationReport, buildHighIssuesDocument, writeReviewTracker } from "./artifacts.js";
|
|
55
|
+
import type { ReviewTrackerEntry } from "./artifacts.js";
|
|
56
|
+
import { runAgentReview } from "./reviewers/index.js";
|
|
57
|
+
import { runPlanQuestions } from "./plan-questions.js";
|
|
58
|
+
|
|
59
|
+
const HOOK = "review-pipeline";
|
|
60
|
+
|
|
61
|
+
// ---------------------------------------------------------------------------
|
|
62
|
+
// Context Lookup (private — only used here)
|
|
63
|
+
// ---------------------------------------------------------------------------
|
|
64
|
+
|
|
65
|
+
function getActiveContextForReview(sessionId: string, projectRoot: string): ContextState | null {
|
|
66
|
+
const ctx = getContextBySessionId(sessionId, projectRoot);
|
|
67
|
+
if (ctx) {
|
|
68
|
+
logInfo(HOOK, `Found context by session_id: ${ctx.id}`);
|
|
69
|
+
return ctx;
|
|
70
|
+
}
|
|
71
|
+
const allActive = getAllContexts("active", projectRoot);
|
|
72
|
+
const planning = allActive.filter(c => c.mode === "active" || c.mode === "has_plan");
|
|
73
|
+
if (planning.length === 1) {
|
|
74
|
+
logInfo(HOOK, `Found single planning context: ${planning[0]!.id}`);
|
|
75
|
+
return planning[0]!;
|
|
76
|
+
}
|
|
77
|
+
if (planning.length > 1) {
|
|
78
|
+
logWarn(HOOK, `Multiple planning contexts (${planning.length}), cannot determine which to use`);
|
|
79
|
+
} else if (allActive.length > 0) {
|
|
80
|
+
logInfo(HOOK, `Found ${allActive.length} active context(s) but none in planning mode`);
|
|
81
|
+
} else {
|
|
82
|
+
logInfo(HOOK, "No active contexts found");
|
|
83
|
+
}
|
|
84
|
+
return null;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// ---------------------------------------------------------------------------
|
|
88
|
+
// Pipeline
|
|
89
|
+
// ---------------------------------------------------------------------------
|
|
90
|
+
|
|
91
|
+
export async function runReviewPipeline(input: PipelineInput): Promise<PipelineResult> {
|
|
92
|
+
const { sessionId, base, aiwcliDir, transcriptPath, payload } = input;
|
|
93
|
+
|
|
94
|
+
// 1. Load settings
|
|
95
|
+
const settings = loadSettings(aiwcliDir);
|
|
96
|
+
const planSettings = settings.planReview ?? {};
|
|
97
|
+
const agentSettings = settings.agentReview ?? {};
|
|
98
|
+
|
|
99
|
+
const planReviewEnabled = planSettings.enabled ?? true;
|
|
100
|
+
const agentReviewEnabled = agentSettings.enabled ?? true;
|
|
101
|
+
|
|
102
|
+
if (!planReviewEnabled && !agentReviewEnabled) {
|
|
103
|
+
return { action: "skip", reason: "Both plan and agent review disabled" };
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// 2. Discover plan
|
|
107
|
+
const discovered = discoverPlan(transcriptPath);
|
|
108
|
+
if (!discovered) {
|
|
109
|
+
return { action: "skip", reason: "No plan file found in ~/.claude/plans/. The plan may not have been written yet." };
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
const { content: plan, hash: planHash, path: planPath } = discovered;
|
|
113
|
+
|
|
114
|
+
// 3. Find active context (moved before questions gate for plan path change detection)
|
|
115
|
+
const activeContext = getActiveContextForReview(sessionId, base);
|
|
116
|
+
if (!activeContext) {
|
|
117
|
+
return { action: "skip", reason: "No active planning context found for this session." };
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
const contextId = activeContext.id;
|
|
121
|
+
const reviewsDir = path.join(getContextReviewsDir(contextId, base), "cc-native");
|
|
122
|
+
const contextPath = getContextDir(contextId, base);
|
|
123
|
+
|
|
124
|
+
// 4a. Load iteration state
|
|
125
|
+
let iterationState: IterationState | null = loadIterationState(reviewsDir);
|
|
126
|
+
|
|
127
|
+
// 4a-migration. Backfill sessionId for old iteration files
|
|
128
|
+
if (iterationState && !iterationState.sessionId) {
|
|
129
|
+
logInfo(HOOK, `Migrating iteration state: adding sessionId=${sessionId}`);
|
|
130
|
+
iterationState.sessionId = sessionId;
|
|
131
|
+
saveIterationState(reviewsDir, iterationState); // Persist migration
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// 4a-session. Detect session change — reset iteration state for new planning session
|
|
135
|
+
if (iterationState && iterationState.sessionId && iterationState.sessionId !== sessionId) {
|
|
136
|
+
logInfo(HOOK, `Session changed (${iterationState.sessionId} → ${sessionId}), resetting iteration state`);
|
|
137
|
+
iterationState = null; // Force fresh state creation below
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// 4a-init. Initialize if null
|
|
141
|
+
if (!iterationState) {
|
|
142
|
+
iterationState = {
|
|
143
|
+
current: 1, max: 1, complexity: "medium",
|
|
144
|
+
history: [], graduated: [], passStreaks: {},
|
|
145
|
+
lastPlanHash: "", lastPlanPath: "",
|
|
146
|
+
sessionId: sessionId,
|
|
147
|
+
};
|
|
148
|
+
saveIterationState(reviewsDir, iterationState);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// 4b. Detect plan file change — reset iteration state for new plan topic
|
|
152
|
+
const lastPath = iterationState.lastPlanPath ?? "";
|
|
153
|
+
if (lastPath && lastPath !== planPath) {
|
|
154
|
+
logInfo(HOOK, `Plan file changed (${path.basename(lastPath)}→${path.basename(planPath)}), resetting iteration state for new plan`);
|
|
155
|
+
iterationState = {
|
|
156
|
+
current: 1, max: 1, complexity: "medium",
|
|
157
|
+
history: [], graduated: [], passStreaks: {},
|
|
158
|
+
lastPlanHash: "", lastPlanPath: "",
|
|
159
|
+
sessionId: sessionId,
|
|
160
|
+
};
|
|
161
|
+
saveIterationState(reviewsDir, iterationState);
|
|
162
|
+
resetPlanQuestionsAsked(sessionId, base);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// 5. Questions gate
|
|
166
|
+
if (!wasPlanQuestionsAgentAsked(sessionId, base)) {
|
|
167
|
+
logInfo(HOOK, "Questions gate: plan-questions agent has not run yet, running now");
|
|
168
|
+
const timeout = typeof agentSettings.timeout === "number" ? agentSettings.timeout : 120;
|
|
169
|
+
const questionsResult = await runPlanQuestions(plan, aiwcliDir, timeout, undefined, sessionId);
|
|
170
|
+
|
|
171
|
+
markQuestionsAsked(sessionId, base, "agent");
|
|
172
|
+
|
|
173
|
+
const hasQuestions = questionsResult && (
|
|
174
|
+
questionsResult.questions.length > 0 ||
|
|
175
|
+
questionsResult.assumptions.length > 0 ||
|
|
176
|
+
questionsResult.ambiguities.length > 0
|
|
177
|
+
);
|
|
178
|
+
|
|
179
|
+
if (hasQuestions) {
|
|
180
|
+
const questionsList = questionsResult.questions.map((q: string, i: number) => `${i + 1}. ${q}`).join("\n");
|
|
181
|
+
const assumptionsList = questionsResult.assumptions.length > 0
|
|
182
|
+
? `\n\nAssumptions detected:\n${questionsResult.assumptions.map((a: string) => `- ${a}`).join("\n")}`
|
|
183
|
+
: "";
|
|
184
|
+
const ambiguitiesList = questionsResult.ambiguities.length > 0
|
|
185
|
+
? `\n\nAmbiguities detected:\n${questionsResult.ambiguities.map((a: string) => `- ${a}`).join("\n")}`
|
|
186
|
+
: "";
|
|
187
|
+
const contextMsg = `## Plan Questions (from independent review)\n\nAn agent reviewed your plan in a fresh context — without access to your session history or codebase exploration. It identified these questions:\n\n${questionsList}${assumptionsList}${ambiguitiesList}\n\nAsk the user these questions using AskUserQuestion before submitting the plan.`;
|
|
188
|
+
return {
|
|
189
|
+
action: "block",
|
|
190
|
+
contextText: contextMsg,
|
|
191
|
+
blockReason: "Ask the user clarifying questions before submitting the plan. Use AskUserQuestion with the questions above.",
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
logInfo(HOOK, "Questions gate: no questions generated, proceeding to review");
|
|
196
|
+
} else {
|
|
197
|
+
logInfo(HOOK, "Questions gate: agent already ran, skipping");
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// 6. Hash + dedup
|
|
201
|
+
logDiagnostic(HOOK, "receive", `plan_size=${plan.length}, session=${sessionId.slice(0, 8)}`, {
|
|
202
|
+
inputs: { plan_hash: planHash, plan_size: plan.length, session_id: sessionId.slice(0, 12) },
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
// Plan-hash deduplication
|
|
206
|
+
logDebug(HOOK, `Plan hash: ${planHash}`);
|
|
207
|
+
if (isPlanAlreadyReviewed(sessionId, planHash, base)) {
|
|
208
|
+
const lastReview = getLastPlanReview(sessionId, planHash, base);
|
|
209
|
+
|
|
210
|
+
if (wasPlanPreviouslyDenied(sessionId, planHash, base)) {
|
|
211
|
+
return {
|
|
212
|
+
action: "block",
|
|
213
|
+
contextText: "[Plan Review] Plan content unchanged since last review which found issues.",
|
|
214
|
+
blockReason: "Plan unchanged since denial. Modify the plan to address review findings, then attempt ExitPlanMode again.",
|
|
215
|
+
};
|
|
216
|
+
} else {
|
|
217
|
+
const verdict = lastReview?.iteration?.latest_verdict || "pass";
|
|
218
|
+
return { action: "skip", reason: `Plan already reviewed (verdict: ${verdict}). Skipping re-review.` };
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
// 7. Iteration bounds check
|
|
223
|
+
if (iterationState.current > iterationState.max) {
|
|
224
|
+
return { action: "skip", reason: `Max review iterations reached (${iterationState.current - 1}/${iterationState.max}), allowing plan through.` };
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
// Initialize result containers
|
|
228
|
+
let orchResult: OrchestratorResult | null = null;
|
|
229
|
+
const agentResults: Record<string, ReviewerResult> = {};
|
|
230
|
+
let detectedComplexity = "medium";
|
|
231
|
+
|
|
232
|
+
// 7. PHASE 1: Orchestrator
|
|
233
|
+
const graduatedSet = new Set(iterationState.graduated);
|
|
234
|
+
if (graduatedSet.size > 0) {
|
|
235
|
+
logInfo(HOOK, `Graduated agents from previous iterations: ${[...graduatedSet].sort().join(", ")}`);
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
const agentLibrary = agentReviewEnabled ? loadAgentLibrary(aiwcliDir, agentSettings) : [];
|
|
239
|
+
const originalAgentCount = agentLibrary.length;
|
|
240
|
+
const enabledAgents = agentLibrary.filter(a => !graduatedSet.has(a.name));
|
|
241
|
+
const timeout = typeof agentSettings.timeout === "number" ? agentSettings.timeout : 120;
|
|
242
|
+
const legacyMode = agentSettings.legacyMode === true;
|
|
243
|
+
|
|
244
|
+
const orchSettings = agentSettings.orchestrator ?? DEFAULT_ORCHESTRATOR;
|
|
245
|
+
const orchestratorConfig: OrchestratorConfig = {
|
|
246
|
+
enabled: (orchSettings.enabled ?? true) && agentReviewEnabled,
|
|
247
|
+
model: orchSettings.model ?? "haiku",
|
|
248
|
+
timeout: orchSettings.timeout ?? 30,
|
|
249
|
+
};
|
|
250
|
+
|
|
251
|
+
const mandatoryConfig = agentSettings.mandatoryAgents ?? ["handoff-readiness", "clarity-auditor", "skeptic"];
|
|
252
|
+
const alwaysMandatory = resolveMandatoryAgents(mandatoryConfig, "simple");
|
|
253
|
+
|
|
254
|
+
logDebug(HOOK, `Agent library: ${agentLibrary.map(a => a.name)}`);
|
|
255
|
+
logDebug(HOOK, `Mandatory agents: ${[...alwaysMandatory].sort()}`);
|
|
256
|
+
logDebug(HOOK, `Orchestrator enabled: ${orchestratorConfig.enabled}`);
|
|
257
|
+
|
|
258
|
+
const phase1Promises: Array<{ name: string; promise: Promise<ReviewerResult | OrchestratorResult> }> = [];
|
|
259
|
+
|
|
260
|
+
if (orchestratorConfig.enabled && enabledAgents.length > 0 && !legacyMode) {
|
|
261
|
+
phase1Promises.push({
|
|
262
|
+
name: "orchestrator",
|
|
263
|
+
promise: runOrchestrator(plan, enabledAgents, orchestratorConfig, agentSettings, alwaysMandatory),
|
|
264
|
+
});
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
logInfo(HOOK, `=== PHASE 1: Running ${phase1Promises.length} tasks in parallel ===`);
|
|
268
|
+
|
|
269
|
+
if (phase1Promises.length > 0) {
|
|
270
|
+
const results = await Promise.allSettled(
|
|
271
|
+
phase1Promises.map(async ({ name, promise }) => {
|
|
272
|
+
const result = await promise;
|
|
273
|
+
return { name, result };
|
|
274
|
+
}),
|
|
275
|
+
);
|
|
276
|
+
for (const [i, r] of results.entries()) {
|
|
277
|
+
if (r.status === "fulfilled") {
|
|
278
|
+
if (r.value.name === "orchestrator") orchResult = r.value.result as OrchestratorResult;
|
|
279
|
+
logInfo(HOOK, `${r.value.name} completed`);
|
|
280
|
+
} else {
|
|
281
|
+
const failedName = phase1Promises[i]?.name ?? "unknown";
|
|
282
|
+
logError(HOOK, `${failedName} failed: ${r.reason}`);
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
// 8. PHASE 2: Agent Selection
|
|
288
|
+
let selectedAgents: AgentConfig[] = [];
|
|
289
|
+
let mandatoryNames = alwaysMandatory;
|
|
290
|
+
|
|
291
|
+
if (agentReviewEnabled) {
|
|
292
|
+
logInfo(HOOK, "=== PHASE 2: Agent Selection ===");
|
|
293
|
+
|
|
294
|
+
const selectionResult = selectAgents({
|
|
295
|
+
enabledAgents,
|
|
296
|
+
orchResult,
|
|
297
|
+
mandatoryConfig,
|
|
298
|
+
agentSettings,
|
|
299
|
+
legacyMode,
|
|
300
|
+
});
|
|
301
|
+
|
|
302
|
+
selectedAgents = selectionResult.selectedAgents;
|
|
303
|
+
mandatoryNames = selectionResult.mandatoryNames;
|
|
304
|
+
detectedComplexity = selectionResult.detectedComplexity;
|
|
305
|
+
|
|
306
|
+
logDiagnostic(HOOK, "decide", `Selected ${selectedAgents.length} agents, complexity=${detectedComplexity}`, {
|
|
307
|
+
decision: "agents_selected",
|
|
308
|
+
reasoning: `orchestrator=${orchResult !== null}, legacy=${legacyMode}`,
|
|
309
|
+
inputs: {
|
|
310
|
+
agents: selectedAgents.map(a => a.name),
|
|
311
|
+
complexity: detectedComplexity,
|
|
312
|
+
mandatory_count: selectedAgents.filter(a => mandatoryNames.has(a.name)).length,
|
|
313
|
+
},
|
|
314
|
+
});
|
|
315
|
+
|
|
316
|
+
// Update iteration state with complexity/max
|
|
317
|
+
const reviewIterations: Record<string, number> = {
|
|
318
|
+
...DEFAULT_REVIEW_ITERATIONS,
|
|
319
|
+
...(agentSettings.reviewIterations ?? {}),
|
|
320
|
+
};
|
|
321
|
+
iterationState.complexity = detectedComplexity;
|
|
322
|
+
iterationState.max = reviewIterations[detectedComplexity] ?? iterationState.max;
|
|
323
|
+
logDebug(HOOK, `Iteration state: ${iterationState.current}/${iterationState.max} (${detectedComplexity})`);
|
|
324
|
+
|
|
325
|
+
// Assign random providers + models
|
|
326
|
+
const modelsConfig = loadModelsConfig(settings);
|
|
327
|
+
selectedAgents = assignModelsToAgents(selectedAgents, modelsConfig);
|
|
328
|
+
logInfo(HOOK, `Model assignments: ${selectedAgents.map(a => `${a.name}→${a.provider}:${a.model}`).join(", ")}`);
|
|
329
|
+
|
|
330
|
+
// 9. PHASE 3: Run agents
|
|
331
|
+
if (selectedAgents.length > 0) {
|
|
332
|
+
logInfo(HOOK, "=== PHASE 3: Agent Reviews ===");
|
|
333
|
+
logInfo(HOOK, `Launching ${selectedAgents.length} agents in parallel`);
|
|
334
|
+
|
|
335
|
+
debugLog(contextPath, sessionId, "hook", "agent_review_start", {
|
|
336
|
+
agents: selectedAgents.map(a => a.name),
|
|
337
|
+
timeout,
|
|
338
|
+
complexity: detectedComplexity,
|
|
339
|
+
});
|
|
340
|
+
|
|
341
|
+
const agentPromises = selectedAgents.map(async agent => {
|
|
342
|
+
const result = await runAgentReview(plan, agent, REVIEW_SCHEMA, timeout, contextPath, sessionId);
|
|
343
|
+
return { agent, result };
|
|
344
|
+
});
|
|
345
|
+
|
|
346
|
+
const agentSettled = await Promise.allSettled(agentPromises);
|
|
347
|
+
for (const [i, r] of agentSettled.entries()) {
|
|
348
|
+
if (r.status === "fulfilled") {
|
|
349
|
+
const { agent, result } = r.value;
|
|
350
|
+
agentResults[agent.name] = result;
|
|
351
|
+
logInfo(HOOK, `${agent.name} completed with verdict: ${result.verdict}`);
|
|
352
|
+
} else {
|
|
353
|
+
const failedAgent = selectedAgents[i]!;
|
|
354
|
+
logError(HOOK, `${failedAgent.name} failed with exception: ${r.reason}`);
|
|
355
|
+
agentResults[failedAgent.name] = {
|
|
356
|
+
name: failedAgent.name,
|
|
357
|
+
ok: false,
|
|
358
|
+
verdict: "error",
|
|
359
|
+
data: {},
|
|
360
|
+
raw: "",
|
|
361
|
+
err: String(r.reason),
|
|
362
|
+
};
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
// 10. Issue truncation + verdict override
|
|
369
|
+
const maxIssuesPerAgent = typeof agentSettings.maxIssuesPerAgent === "number"
|
|
370
|
+
? agentSettings.maxIssuesPerAgent : 3;
|
|
371
|
+
truncateAgentIssues(agentResults, maxIssuesPerAgent);
|
|
372
|
+
|
|
373
|
+
const passEligible = computePassEligible(agentResults);
|
|
374
|
+
if (passEligible.length > 0) {
|
|
375
|
+
logInfo(HOOK, `Pass-eligible agents this iteration: ${passEligible.join(", ")}`);
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
const highIssueThreshold = typeof agentSettings.highIssueThreshold === "number" ? agentSettings.highIssueThreshold : 3;
|
|
379
|
+
overrideVerdictsByThreshold(agentResults, highIssueThreshold);
|
|
380
|
+
|
|
381
|
+
// PHASE 4: Generate Output
|
|
382
|
+
logInfo(HOOK, "=== PHASE 4: Generate Output ===");
|
|
383
|
+
|
|
384
|
+
if (Object.keys(agentResults).length === 0) {
|
|
385
|
+
if (graduatedSet.size > 0 && originalAgentCount > 0) {
|
|
386
|
+
return { action: "skip", reason: "All agent reviewers graduated from previous iterations — no review needed." };
|
|
387
|
+
}
|
|
388
|
+
return { action: "skip", reason: "All reviewers failed to produce results. Check stderr logs for details." };
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
// 11. Corroboration
|
|
392
|
+
const corroborationResult = computeCorroboratedDecision(agentResults);
|
|
393
|
+
const overall = corroborationResult.verdict;
|
|
394
|
+
|
|
395
|
+
const combinedResult: CombinedReviewResult = {
|
|
396
|
+
plan_hash: planHash,
|
|
397
|
+
overall_verdict: overall,
|
|
398
|
+
orchestration: orchResult,
|
|
399
|
+
agents: agentResults,
|
|
400
|
+
timestamp: new Date().toISOString(),
|
|
401
|
+
};
|
|
402
|
+
|
|
403
|
+
const displaySettings = {
|
|
404
|
+
...(planSettings.display ?? {}),
|
|
405
|
+
...(agentSettings.display ?? {}),
|
|
406
|
+
};
|
|
407
|
+
const combinedSettings = { display: displaySettings };
|
|
408
|
+
|
|
409
|
+
const currentIteration = iterationState.current;
|
|
410
|
+
|
|
411
|
+
// 12. Write artifacts
|
|
412
|
+
const reviewFolder = getReviewFolderPath(contextId, currentIteration, base);
|
|
413
|
+
fs.mkdirSync(reviewFolder, { recursive: true });
|
|
414
|
+
logInfo(HOOK, `Created review folder: ${reviewFolder}`);
|
|
415
|
+
|
|
416
|
+
const reviewFile = writeCombinedArtifacts(
|
|
417
|
+
base, plan, combinedResult, payload, combinedSettings,
|
|
418
|
+
undefined, reviewFolder, currentIteration, corroborationResult,
|
|
419
|
+
);
|
|
420
|
+
logInfo(HOOK, `Saved review: ${reviewFile}`);
|
|
421
|
+
|
|
422
|
+
const corroborationReport = buildCorroborationReport(corroborationResult);
|
|
423
|
+
const corroborationPath = path.join(reviewFolder, "corroboration.md");
|
|
424
|
+
fs.writeFileSync(corroborationPath, corroborationReport, "utf-8");
|
|
425
|
+
logInfo(HOOK, `Saved corroboration report: ${corroborationPath}`);
|
|
426
|
+
|
|
427
|
+
try {
|
|
428
|
+
fs.writeFileSync(path.join(reviewFolder, "plan.md"), plan, "utf-8");
|
|
429
|
+
logDebug(HOOK, `Saved plan snapshot: ${path.join(reviewFolder, "plan.md")}`);
|
|
430
|
+
} catch (e) {
|
|
431
|
+
logWarn(HOOK, `Failed to save plan snapshot: ${e}`);
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
// Build high-issues document
|
|
435
|
+
const highIssuesDoc = buildHighIssuesDocument(combinedResult, corroborationResult);
|
|
436
|
+
const highIssuesPath = path.join(reviewFolder, "high-issues.md");
|
|
437
|
+
fs.writeFileSync(highIssuesPath, highIssuesDoc, "utf-8");
|
|
438
|
+
|
|
439
|
+
// 15. Build output
|
|
440
|
+
const shouldDeny = corroborationResult.blocking.length > 0;
|
|
441
|
+
const reviewScore = shouldDeny ? 1.0 : 0.0;
|
|
442
|
+
const denyReason = shouldDeny ? "corroborated_issues" : "no_corroboration";
|
|
443
|
+
|
|
444
|
+
logInfo(HOOK, `REVIEW_DECISION: verdict=${combinedResult.overall_verdict}, deny=${shouldDeny}, reason=${denyReason}, score=${reviewScore.toFixed(2)}`);
|
|
445
|
+
logDiagnostic(HOOK, "result", `verdict=${combinedResult.overall_verdict}, deny=${shouldDeny}, reason=${denyReason}`, {
|
|
446
|
+
decision: shouldDeny ? "deny" : "allow",
|
|
447
|
+
reasoning: `reason=${denyReason}, score=${reviewScore.toFixed(2)}`,
|
|
448
|
+
inputs: {
|
|
449
|
+
overall_verdict: combinedResult.overall_verdict,
|
|
450
|
+
review_score: Math.round(reviewScore * 100) / 100,
|
|
451
|
+
agent_count: Object.keys(agentResults).length,
|
|
452
|
+
},
|
|
453
|
+
});
|
|
454
|
+
|
|
455
|
+
// Terminal progress
|
|
456
|
+
const verdictEmoji = shouldDeny ? "❌" : "✅";
|
|
457
|
+
eprint(`[plan-review] ${verdictEmoji} ${combinedResult.overall_verdict.toUpperCase()} (score=${reviewScore.toFixed(2)})`);
|
|
458
|
+
if (shouldDeny) {
|
|
459
|
+
eprint(`[plan-review] Blocking ExitPlanMode — ${denyReason}`);
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
// 13. Advance iteration
|
|
463
|
+
const advancement = advanceIterationState(
|
|
464
|
+
iterationState, planHash, planPath, overall, shouldDeny, passEligible, agentResults,
|
|
465
|
+
);
|
|
466
|
+
iterationState = advancement.updatedState;
|
|
467
|
+
if (advancement.newGraduates.length > 0) {
|
|
468
|
+
logInfo(HOOK, `Newly graduated (2 consecutive passes): ${advancement.newGraduates.join(", ")}`);
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
// 14. Save iteration state
|
|
472
|
+
saveIterationState(reviewsDir, iterationState);
|
|
473
|
+
|
|
474
|
+
// Write review tracker
|
|
475
|
+
const ccNativeReviewsDir = path.dirname(reviewFolder);
|
|
476
|
+
const trackerDecision = shouldDeny ? "blocked" : "allow";
|
|
477
|
+
const topIssuesList = extractTopIssuesForTracker(combinedResult, 5);
|
|
478
|
+
const trackerEntry: ReviewTrackerEntry = {
|
|
479
|
+
iteration: currentIteration,
|
|
480
|
+
timestamp: new Date().toISOString().replace("T", " ").slice(0, 16),
|
|
481
|
+
planHash,
|
|
482
|
+
verdict: combinedResult.overall_verdict,
|
|
483
|
+
decision: trackerDecision,
|
|
484
|
+
score: reviewScore,
|
|
485
|
+
topIssues: topIssuesList,
|
|
486
|
+
reviewFolder,
|
|
487
|
+
};
|
|
488
|
+
writeReviewTracker(ccNativeReviewsDir, trackerEntry);
|
|
489
|
+
logInfo(HOOK, `Updated review tracker: ${path.join(ccNativeReviewsDir, "review-tracker.md")}`);
|
|
490
|
+
|
|
491
|
+
// Build final output
|
|
492
|
+
const output = buildReviewOutput({
|
|
493
|
+
combinedResult,
|
|
494
|
+
corroborationResult,
|
|
495
|
+
iterationState: { ...iterationState, current: currentIteration },
|
|
496
|
+
reviewFile,
|
|
497
|
+
highIssuesPath,
|
|
498
|
+
});
|
|
499
|
+
|
|
500
|
+
// Mark plan reviewed
|
|
501
|
+
const disposition = shouldDeny
|
|
502
|
+
? `hook_deny_iter_${currentIteration}`
|
|
503
|
+
: `hook_allow_iter_${currentIteration}`;
|
|
504
|
+
markPlanReviewed(sessionId, planHash, base, HOOK, { ...iterationState, current: currentIteration }, disposition);
|
|
505
|
+
|
|
506
|
+
return {
|
|
507
|
+
action: "block",
|
|
508
|
+
contextText: output.contextText,
|
|
509
|
+
blockReason: output.blockReason,
|
|
510
|
+
};
|
|
511
|
+
}
|