aiwcli 0.11.0 → 0.12.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/dist/commands/clear.d.ts +8 -0
- package/dist/commands/clear.js +86 -0
- package/dist/lib/bmad-installer.d.ts +2 -27
- package/dist/lib/bmad-installer.js +3 -43
- package/dist/lib/claude-settings-types.d.ts +2 -1
- package/dist/lib/env-compat.d.ts +0 -8
- package/dist/lib/env-compat.js +0 -12
- package/dist/lib/git/index.d.ts +0 -1
- package/dist/lib/gitignore-manager.d.ts +0 -2
- package/dist/lib/gitignore-manager.js +1 -1
- package/dist/lib/hooks-merger.d.ts +1 -15
- package/dist/lib/hooks-merger.js +1 -1
- package/dist/lib/index.d.ts +3 -7
- package/dist/lib/index.js +3 -11
- package/dist/lib/output.d.ts +2 -1
- package/dist/lib/settings-hierarchy.d.ts +1 -13
- package/dist/lib/settings-hierarchy.js +1 -1
- package/dist/lib/template-installer.d.ts +5 -9
- package/dist/lib/template-installer.js +2 -12
- package/dist/lib/template-linter.d.ts +3 -10
- package/dist/lib/template-linter.js +2 -2
- package/dist/lib/template-resolver.d.ts +6 -0
- package/dist/lib/template-resolver.js +10 -0
- package/dist/lib/template-settings-reconstructor.d.ts +1 -1
- package/dist/lib/template-settings-reconstructor.js +17 -24
- package/dist/lib/terminal.d.ts +3 -14
- package/dist/lib/terminal.js +0 -4
- package/dist/lib/version.d.ts +2 -11
- package/dist/lib/version.js +2 -2
- package/dist/lib/windsurf-hooks-merger.d.ts +1 -15
- package/dist/lib/windsurf-hooks-merger.js +1 -1
- package/dist/templates/_shared/.codex/workflows/handoff.md +1 -1
- package/dist/templates/_shared/.windsurf/workflows/handoff.md +1 -1
- package/dist/templates/_shared/hooks-ts/session_end.ts +4 -1
- package/dist/templates/_shared/hooks-ts/session_start.ts +15 -20
- package/dist/templates/_shared/hooks-ts/user_prompt_submit.ts +12 -14
- package/dist/templates/_shared/lib-ts/CLAUDE.md +56 -7
- package/dist/templates/_shared/lib-ts/base/git-state.ts +1 -1
- package/dist/templates/_shared/lib-ts/base/hook-utils.ts +174 -43
- package/dist/templates/_shared/lib-ts/base/logger.ts +15 -18
- package/dist/templates/_shared/lib-ts/base/state-io.ts +11 -2
- package/dist/templates/_shared/lib-ts/base/subprocess-utils.ts +181 -162
- package/dist/templates/_shared/lib-ts/context/plan-manager.ts +26 -30
- package/dist/templates/_shared/lib-ts/handoff/handoff-reader.ts +12 -13
- package/dist/templates/_shared/lib-ts/package.json +1 -2
- package/dist/templates/_shared/lib-ts/templates/plan-context.ts +27 -34
- package/dist/templates/_shared/lib-ts/types.ts +17 -2
- package/dist/templates/_shared/scripts/resume_handoff.ts +62 -38
- package/dist/templates/_shared/scripts/save_handoff.ts +24 -24
- package/dist/templates/_shared/scripts/status_line.ts +102 -148
- package/dist/templates/_shared/workflows/handoff.md +1 -1
- package/dist/templates/cc-native/.claude/settings.json +183 -175
- package/dist/templates/cc-native/_cc-native/agents/CLAUDE.md +23 -1
- package/dist/templates/cc-native/_cc-native/agents/plan-questions/PLAN-QUESTIONER.md +70 -0
- package/dist/templates/cc-native/_cc-native/hooks/CLAUDE.md +6 -1
- package/dist/templates/cc-native/_cc-native/hooks/cc-native-plan-review.ts +316 -176
- package/dist/templates/cc-native/_cc-native/hooks/enhance_plan_post_subagent.ts +38 -0
- package/dist/templates/cc-native/_cc-native/hooks/enhance_plan_post_write.ts +51 -0
- package/dist/templates/cc-native/_cc-native/hooks/mark_questions_asked.ts +53 -0
- package/dist/templates/cc-native/_cc-native/hooks/plan_questions_early.ts +15 -15
- package/dist/templates/cc-native/_cc-native/lib-ts/aggregate-agents.ts +11 -12
- package/dist/templates/cc-native/_cc-native/lib-ts/artifacts.ts +227 -114
- package/dist/templates/cc-native/_cc-native/lib-ts/cc-native-state.ts +64 -16
- package/dist/templates/cc-native/_cc-native/lib-ts/cli-output-parser.ts +23 -3
- package/dist/templates/cc-native/_cc-native/lib-ts/corroboration.ts +119 -0
- package/dist/templates/cc-native/_cc-native/lib-ts/index.ts +1 -4
- package/dist/templates/cc-native/_cc-native/lib-ts/json-parser.ts +7 -1
- package/dist/templates/cc-native/_cc-native/lib-ts/orchestrator.ts +40 -218
- package/dist/templates/cc-native/_cc-native/lib-ts/plan-enhancement.ts +41 -0
- package/dist/templates/cc-native/_cc-native/lib-ts/plan-questions.ts +101 -0
- package/dist/templates/cc-native/_cc-native/lib-ts/reviewers/agent.ts +27 -111
- package/dist/templates/cc-native/_cc-native/lib-ts/reviewers/base/base-agent.ts +217 -0
- package/dist/templates/cc-native/_cc-native/lib-ts/reviewers/index.ts +5 -3
- package/dist/templates/cc-native/_cc-native/lib-ts/reviewers/providers/claude-agent.ts +65 -0
- package/dist/templates/cc-native/_cc-native/lib-ts/reviewers/providers/codex-agent.ts +184 -0
- package/dist/templates/cc-native/_cc-native/lib-ts/reviewers/providers/gemini-agent.ts +39 -0
- package/dist/templates/cc-native/_cc-native/lib-ts/reviewers/providers/orchestrator-claude-agent.ts +195 -0
- package/dist/templates/cc-native/_cc-native/lib-ts/reviewers/schemas.ts +201 -0
- package/dist/templates/cc-native/_cc-native/lib-ts/reviewers/types.ts +3 -5
- package/dist/templates/cc-native/_cc-native/lib-ts/state.ts +30 -33
- package/dist/templates/cc-native/_cc-native/lib-ts/types.ts +104 -132
- package/dist/templates/cc-native/_cc-native/plan-review.config.json +22 -13
- package/oclif.manifest.json +1 -1
- package/package.json +2 -3
- package/dist/templates/cc-native/_cc-native/hooks/add_plan_context.ts +0 -119
- package/dist/templates/cc-native/_cc-native/lib-ts/reviewers/codex.ts +0 -130
- package/dist/templates/cc-native/_cc-native/lib-ts/reviewers/gemini.ts +0 -106
- /package/dist/templates/cc-native/_cc-native/agents/{ARCH-EVOLUTION.md → plan-review/ARCH-EVOLUTION.md} +0 -0
- /package/dist/templates/cc-native/_cc-native/agents/{ARCH-PATTERNS.md → plan-review/ARCH-PATTERNS.md} +0 -0
- /package/dist/templates/cc-native/_cc-native/agents/{ARCH-STRUCTURE.md → plan-review/ARCH-STRUCTURE.md} +0 -0
- /package/dist/templates/cc-native/_cc-native/agents/{ASSUMPTION-TRACER.md → plan-review/ASSUMPTION-TRACER.md} +0 -0
- /package/dist/templates/cc-native/_cc-native/agents/{CLARITY-AUDITOR.md → plan-review/CLARITY-AUDITOR.md} +0 -0
- /package/dist/templates/cc-native/_cc-native/agents/{COMPLETENESS-FEASIBILITY.md → plan-review/COMPLETENESS-FEASIBILITY.md} +0 -0
- /package/dist/templates/cc-native/_cc-native/agents/{COMPLETENESS-GAPS.md → plan-review/COMPLETENESS-GAPS.md} +0 -0
- /package/dist/templates/cc-native/_cc-native/agents/{COMPLETENESS-ORDERING.md → plan-review/COMPLETENESS-ORDERING.md} +0 -0
- /package/dist/templates/cc-native/_cc-native/agents/{CONSTRAINT-VALIDATOR.md → plan-review/CONSTRAINT-VALIDATOR.md} +0 -0
- /package/dist/templates/cc-native/_cc-native/agents/{DESIGN-ADR-VALIDATOR.md → plan-review/DESIGN-ADR-VALIDATOR.md} +0 -0
- /package/dist/templates/cc-native/_cc-native/agents/{DESIGN-SCALE-MATCHER.md → plan-review/DESIGN-SCALE-MATCHER.md} +0 -0
- /package/dist/templates/cc-native/_cc-native/agents/{DEVILS-ADVOCATE.md → plan-review/DEVILS-ADVOCATE.md} +0 -0
- /package/dist/templates/cc-native/_cc-native/agents/{DOCUMENTATION-PHILOSOPHY.md → plan-review/DOCUMENTATION-PHILOSOPHY.md} +0 -0
- /package/dist/templates/cc-native/_cc-native/agents/{HANDOFF-READINESS.md → plan-review/HANDOFF-READINESS.md} +0 -0
- /package/dist/templates/cc-native/_cc-native/agents/{HIDDEN-COMPLEXITY.md → plan-review/HIDDEN-COMPLEXITY.md} +0 -0
- /package/dist/templates/cc-native/_cc-native/agents/{INCREMENTAL-DELIVERY.md → plan-review/INCREMENTAL-DELIVERY.md} +0 -0
- /package/dist/templates/cc-native/_cc-native/agents/{RISK-DEPENDENCY.md → plan-review/RISK-DEPENDENCY.md} +0 -0
- /package/dist/templates/cc-native/_cc-native/agents/{RISK-FMEA.md → plan-review/RISK-FMEA.md} +0 -0
- /package/dist/templates/cc-native/_cc-native/agents/{RISK-PREMORTEM.md → plan-review/RISK-PREMORTEM.md} +0 -0
- /package/dist/templates/cc-native/_cc-native/agents/{RISK-REVERSIBILITY.md → plan-review/RISK-REVERSIBILITY.md} +0 -0
- /package/dist/templates/cc-native/_cc-native/agents/{SCOPE-BOUNDARY.md → plan-review/SCOPE-BOUNDARY.md} +0 -0
- /package/dist/templates/cc-native/_cc-native/agents/{SIMPLICITY-GUARDIAN.md → plan-review/SIMPLICITY-GUARDIAN.md} +0 -0
- /package/dist/templates/cc-native/_cc-native/agents/{SKEPTIC.md → plan-review/SKEPTIC.md} +0 -0
- /package/dist/templates/cc-native/_cc-native/agents/{TESTDRIVEN-BEHAVIOR-AUDITOR.md → plan-review/TESTDRIVEN-BEHAVIOR-AUDITOR.md} +0 -0
- /package/dist/templates/cc-native/_cc-native/agents/{TESTDRIVEN-CHARACTERIZATION.md → plan-review/TESTDRIVEN-CHARACTERIZATION.md} +0 -0
- /package/dist/templates/cc-native/_cc-native/agents/{TESTDRIVEN-FIRST-VALIDATOR.md → plan-review/TESTDRIVEN-FIRST-VALIDATOR.md} +0 -0
- /package/dist/templates/cc-native/_cc-native/agents/{TESTDRIVEN-PYRAMID-ANALYZER.md → plan-review/TESTDRIVEN-PYRAMID-ANALYZER.md} +0 -0
- /package/dist/templates/cc-native/_cc-native/agents/{TRADEOFF-COSTS.md → plan-review/TRADEOFF-COSTS.md} +0 -0
- /package/dist/templates/cc-native/_cc-native/agents/{TRADEOFF-STAKEHOLDERS.md → plan-review/TRADEOFF-STAKEHOLDERS.md} +0 -0
- /package/dist/templates/cc-native/_cc-native/agents/{VERIFY-COVERAGE.md → plan-review/VERIFY-COVERAGE.md} +0 -0
- /package/dist/templates/cc-native/_cc-native/agents/{VERIFY-STRENGTH.md → plan-review/VERIFY-STRENGTH.md} +0 -0
|
@@ -1,18 +1,19 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* CC-native state accessor for context state.json.
|
|
3
|
-
* Deduplicates state access patterns
|
|
3
|
+
* Deduplicates state access patterns from utils.py and suggest-fresh-perspective.py.
|
|
4
4
|
* See cc-native-plan-review-spec.md §4.5
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
+
import { getContextBySessionId, saveState } from "../../_shared/lib-ts/context/context-store.js";
|
|
8
|
+
import { logInfo, logWarn } from "../../_shared/lib-ts/base/logger.js";
|
|
9
|
+
import { nowIso } from "../../_shared/lib-ts/base/utils.js";
|
|
7
10
|
import type {
|
|
8
11
|
CcNativeState,
|
|
9
|
-
IterationState,
|
|
10
12
|
PlanReviewState,
|
|
11
|
-
QuestionsAskedState
|
|
13
|
+
QuestionsAskedState,
|
|
14
|
+
IterationState,
|
|
15
|
+
StuckDetectionState,
|
|
12
16
|
} from "./types.js";
|
|
13
|
-
import { logInfo, logWarn } from "../../_shared/lib-ts/base/logger.js";
|
|
14
|
-
import { nowIso } from "../../_shared/lib-ts/base/utils.js";
|
|
15
|
-
import { getContextBySessionId, saveState } from "../../_shared/lib-ts/context/context-store.js";
|
|
16
17
|
import type { ContextState } from "../../_shared/lib-ts/types.js";
|
|
17
18
|
|
|
18
19
|
/**
|
|
@@ -42,7 +43,6 @@ export function getCcNativeState(
|
|
|
42
43
|
} catch {
|
|
43
44
|
// Fail-safe: return null
|
|
44
45
|
}
|
|
45
|
-
|
|
46
46
|
return null;
|
|
47
47
|
}
|
|
48
48
|
|
|
@@ -62,10 +62,9 @@ export function saveCcNativeState(
|
|
|
62
62
|
saveState(state.id, state, projectRoot);
|
|
63
63
|
return true;
|
|
64
64
|
}
|
|
65
|
-
} catch (
|
|
66
|
-
logWarn("utils", `Failed to save cc_native state: ${
|
|
65
|
+
} catch (e: unknown) {
|
|
66
|
+
logWarn("utils", `Failed to save cc_native state: ${e}`);
|
|
67
67
|
}
|
|
68
|
-
|
|
69
68
|
return false;
|
|
70
69
|
}
|
|
71
70
|
|
|
@@ -104,6 +103,22 @@ export function wasPlanPreviouslyDenied(
|
|
|
104
103
|
return decision === "deny" || decision.startsWith("hook_deny");
|
|
105
104
|
}
|
|
106
105
|
|
|
106
|
+
/**
|
|
107
|
+
* Get the last plan review state for this session.
|
|
108
|
+
* Returns null if no review state exists or plan hash doesn't match.
|
|
109
|
+
*/
|
|
110
|
+
export function getLastPlanReview(
|
|
111
|
+
sessionId: string,
|
|
112
|
+
planHash: string,
|
|
113
|
+
projectRoot: string,
|
|
114
|
+
): PlanReviewState | null {
|
|
115
|
+
const ccNative = getCcNativeState(sessionId, projectRoot);
|
|
116
|
+
if (!ccNative) return null;
|
|
117
|
+
const reviewState = ccNative.plan_review;
|
|
118
|
+
if (reviewState?.plan_hash !== planHash) return null;
|
|
119
|
+
return reviewState;
|
|
120
|
+
}
|
|
121
|
+
|
|
107
122
|
/**
|
|
108
123
|
* Mark this plan as reviewed (stores hash and decision in state.json).
|
|
109
124
|
*/
|
|
@@ -130,9 +145,9 @@ export function markPlanReviewed(
|
|
|
130
145
|
max: iterationState.max ?? 1,
|
|
131
146
|
complexity: iterationState.complexity ?? "unknown",
|
|
132
147
|
};
|
|
133
|
-
const
|
|
148
|
+
const history = iterationState.history;
|
|
134
149
|
if (history && history.length > 0) {
|
|
135
|
-
const lastEntry = history.
|
|
150
|
+
const lastEntry = history[history.length - 1];
|
|
136
151
|
if (lastEntry) {
|
|
137
152
|
reviewData.iteration.latest_verdict = lastEntry.verdict ?? "unknown";
|
|
138
153
|
}
|
|
@@ -152,8 +167,8 @@ export function markPlanReviewed(
|
|
|
152
167
|
`Failed to save plan review state for session ${sessionId}`,
|
|
153
168
|
);
|
|
154
169
|
}
|
|
155
|
-
} catch (
|
|
156
|
-
logWarn(hookName, `Failed to mark plan reviewed: ${
|
|
170
|
+
} catch (e: unknown) {
|
|
171
|
+
logWarn(hookName, `Failed to mark plan reviewed: ${e}`);
|
|
157
172
|
}
|
|
158
173
|
}
|
|
159
174
|
|
|
@@ -191,8 +206,41 @@ export function markQuestionsAsked(
|
|
|
191
206
|
};
|
|
192
207
|
|
|
193
208
|
return saveCcNativeState(sessionId, projectRoot, ccNative);
|
|
194
|
-
} catch (
|
|
195
|
-
logWarn("utils", `Failed to mark questions asked: ${
|
|
209
|
+
} catch (e: unknown) {
|
|
210
|
+
logWarn("utils", `Failed to mark questions asked: ${e}`);
|
|
211
|
+
return false;
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
// ---------------------------------------------------------------------------
|
|
216
|
+
// Stuck Detection State
|
|
217
|
+
// ---------------------------------------------------------------------------
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* Get stuck detection state from cc_native.
|
|
221
|
+
*/
|
|
222
|
+
export function getStuckDetectionState(
|
|
223
|
+
sessionId: string,
|
|
224
|
+
projectRoot: string,
|
|
225
|
+
): StuckDetectionState | null {
|
|
226
|
+
const ccNative = getCcNativeState(sessionId, projectRoot);
|
|
227
|
+
return ccNative?.stuck_detection ?? null;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
/**
|
|
231
|
+
* Update stuck detection state.
|
|
232
|
+
*/
|
|
233
|
+
export function updateStuckDetectionState(
|
|
234
|
+
sessionId: string,
|
|
235
|
+
projectRoot: string,
|
|
236
|
+
stuckState: StuckDetectionState,
|
|
237
|
+
): boolean {
|
|
238
|
+
try {
|
|
239
|
+
const ccNative = getCcNativeState(sessionId, projectRoot) ?? {};
|
|
240
|
+
ccNative.stuck_detection = stuckState;
|
|
241
|
+
return saveCcNativeState(sessionId, projectRoot, ccNative);
|
|
242
|
+
} catch (e: unknown) {
|
|
243
|
+
logWarn("utils", `Failed to update stuck detection state: ${e}`);
|
|
196
244
|
return false;
|
|
197
245
|
}
|
|
198
246
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Unified Claude CLI output parser.
|
|
3
|
-
* Deduplicates identical logic from orchestrator.
|
|
3
|
+
* Deduplicates identical logic from orchestrator.ts and reviewers/agent.ts.
|
|
4
4
|
* See cc-native-plan-review-spec.md §4.6
|
|
5
5
|
*/
|
|
6
6
|
|
|
@@ -36,6 +36,26 @@ export function parseCliOutput(
|
|
|
36
36
|
return dict.structured_output as Record<string, unknown>;
|
|
37
37
|
}
|
|
38
38
|
|
|
39
|
+
// Strategy 1.5: Session result envelope (type: "result")
|
|
40
|
+
// When the model fails to call StructuredOutput, the CLI returns a
|
|
41
|
+
// session metadata object with type/subtype/duration_ms/usage/etc.
|
|
42
|
+
// but no structured_output key. Check for the `result` text field
|
|
43
|
+
// (model may have written review as text) and for error states.
|
|
44
|
+
if (dict.type === "result" || ("duration_ms" in dict && "session_id" in dict)) {
|
|
45
|
+
if (dict.is_error === true || (Array.isArray(dict.errors) && (dict.errors as unknown[]).length > 0)) {
|
|
46
|
+
logWarn("cli_parser", `CLI returned error result: ${JSON.stringify(dict.errors ?? "is_error=true")}`);
|
|
47
|
+
return null;
|
|
48
|
+
}
|
|
49
|
+
if (typeof dict.result === "string" && dict.result) {
|
|
50
|
+
logDebug("cli_parser", "Found result text in session envelope, attempting JSON extraction");
|
|
51
|
+
const extracted = parseJsonMaybe(dict.result as string, requireFields);
|
|
52
|
+
if (extracted) return extracted;
|
|
53
|
+
logWarn("cli_parser", "Session envelope result text contained no extractable JSON");
|
|
54
|
+
}
|
|
55
|
+
logDebug("cli_parser", "Session result envelope with no structured_output or extractable result");
|
|
56
|
+
return null;
|
|
57
|
+
}
|
|
58
|
+
|
|
39
59
|
// Strategy 2: Assistant message with StructuredOutput tool use
|
|
40
60
|
if (dict.type === "assistant") {
|
|
41
61
|
const message = dict.message as Record<string, unknown> | undefined;
|
|
@@ -57,7 +77,7 @@ export function parseCliOutput(
|
|
|
57
77
|
>;
|
|
58
78
|
}
|
|
59
79
|
}
|
|
60
|
-
}
|
|
80
|
+
}
|
|
61
81
|
|
|
62
82
|
logDebug(
|
|
63
83
|
"cli_parser",
|
|
@@ -97,7 +117,7 @@ export function parseCliOutput(
|
|
|
97
117
|
}
|
|
98
118
|
}
|
|
99
119
|
}
|
|
100
|
-
}
|
|
120
|
+
}
|
|
101
121
|
|
|
102
122
|
logDebug(
|
|
103
123
|
"cli_parser",
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Corroboration-based verdict computation for plan review.
|
|
3
|
+
*
|
|
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.
|
|
7
|
+
*
|
|
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.
|
|
13
|
+
*
|
|
14
|
+
* **Convergence problem this solves:**
|
|
15
|
+
* Agents with opposing philosophies (simplicity-guardian vs completeness-gaps)
|
|
16
|
+
* produce contradictory high-severity issues. Because the old system treated
|
|
17
|
+
* every agent's finding as independently authoritative, plans oscillated —
|
|
18
|
+
* addressing one agent's feedback triggered the opposing agent.
|
|
19
|
+
*
|
|
20
|
+
* **Revert path:** Change one line in cc-native-plan-review.ts back to
|
|
21
|
+
* `computeReviewDecision(allVerdicts)`. Old function kept in verdict.ts.
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
import type {
|
|
25
|
+
ReviewerResult,
|
|
26
|
+
ReviewIssue,
|
|
27
|
+
IssueDimension,
|
|
28
|
+
CorroborationResult,
|
|
29
|
+
CorroboratedGroup,
|
|
30
|
+
SoloFinding,
|
|
31
|
+
} from "./types.js";
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Compute a corroboration-based review decision from all reviewer results.
|
|
35
|
+
*
|
|
36
|
+
* Algorithm:
|
|
37
|
+
* 1. Collect all high-severity issues with a `dimension` field
|
|
38
|
+
* 2. Group by dimension, tracking distinct agent names per group
|
|
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)
|
|
42
|
+
*
|
|
43
|
+
* @param allResults - Map of reviewer name → ReviewerResult (CLI + agent)
|
|
44
|
+
* @returns CorroborationResult with blocking groups, solo findings, and verdict
|
|
45
|
+
*/
|
|
46
|
+
export function computeCorroboratedDecision(
|
|
47
|
+
allResults: Record<string, ReviewerResult>,
|
|
48
|
+
): CorroborationResult {
|
|
49
|
+
// Accumulator: dimension → { issues, agentNames }
|
|
50
|
+
const dimMap = new Map<
|
|
51
|
+
IssueDimension,
|
|
52
|
+
{
|
|
53
|
+
issues: Array<{ agent: string; issue: ReviewIssue }>;
|
|
54
|
+
agentNames: Set<string>;
|
|
55
|
+
}
|
|
56
|
+
>();
|
|
57
|
+
|
|
58
|
+
const unclassified: Array<{ agent: string; issue: ReviewIssue }> = [];
|
|
59
|
+
|
|
60
|
+
for (const [agentName, result] of Object.entries(allResults)) {
|
|
61
|
+
if (!result.data) continue;
|
|
62
|
+
const issues = result.data.issues as ReviewIssue[] | undefined;
|
|
63
|
+
if (!Array.isArray(issues)) continue;
|
|
64
|
+
|
|
65
|
+
for (const issue of issues) {
|
|
66
|
+
// Only high-severity issues participate in corroboration
|
|
67
|
+
if (issue.severity !== "high") continue;
|
|
68
|
+
|
|
69
|
+
// Issues without dimension are unclassified — cannot block
|
|
70
|
+
if (!issue.dimension) {
|
|
71
|
+
unclassified.push({ agent: agentName, issue });
|
|
72
|
+
continue;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
let group = dimMap.get(issue.dimension);
|
|
76
|
+
if (!group) {
|
|
77
|
+
group = { issues: [], agentNames: new Set() };
|
|
78
|
+
dimMap.set(issue.dimension, group);
|
|
79
|
+
}
|
|
80
|
+
group.issues.push({ agent: agentName, issue });
|
|
81
|
+
group.agentNames.add(agentName);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const blocking: CorroboratedGroup[] = [];
|
|
86
|
+
const solo: SoloFinding[] = [];
|
|
87
|
+
|
|
88
|
+
for (const [dimension, group] of dimMap) {
|
|
89
|
+
const agentCount = group.agentNames.size;
|
|
90
|
+
const threshold = 2 * agentCount;
|
|
91
|
+
|
|
92
|
+
if (group.issues.length >= threshold) {
|
|
93
|
+
blocking.push({
|
|
94
|
+
dimension,
|
|
95
|
+
issues: group.issues,
|
|
96
|
+
agentCount,
|
|
97
|
+
threshold,
|
|
98
|
+
});
|
|
99
|
+
} else {
|
|
100
|
+
solo.push({
|
|
101
|
+
dimension,
|
|
102
|
+
issues: group.issues,
|
|
103
|
+
agentCount,
|
|
104
|
+
threshold,
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
return {
|
|
110
|
+
blocking,
|
|
111
|
+
solo,
|
|
112
|
+
unclassified,
|
|
113
|
+
verdict: blocking.length > 0
|
|
114
|
+
? "fail"
|
|
115
|
+
: solo.length > 0
|
|
116
|
+
? "warn"
|
|
117
|
+
: "pass",
|
|
118
|
+
};
|
|
119
|
+
}
|
|
@@ -64,15 +64,12 @@ export { buildOrchestratorSchema, runOrchestrator } from "./orchestrator.js";
|
|
|
64
64
|
// Reviewers
|
|
65
65
|
export {
|
|
66
66
|
AgentReviewer,
|
|
67
|
-
CodexReviewer,
|
|
68
|
-
GeminiReviewer,
|
|
69
67
|
runAgentReview,
|
|
70
|
-
runCodexReview,
|
|
71
|
-
runGeminiReview,
|
|
72
68
|
} from "./reviewers/index.js";
|
|
73
69
|
|
|
74
70
|
// Iteration state
|
|
75
71
|
export {
|
|
72
|
+
DEFAULT_REVIEW_ITERATIONS,
|
|
76
73
|
deleteState,
|
|
77
74
|
getIterationState,
|
|
78
75
|
getStateFilePath,
|
|
@@ -73,6 +73,12 @@ export function parseJsonMaybe(
|
|
|
73
73
|
`Parsed JSON (${parseMethod}) missing/empty fields: ${JSON.stringify(missing)}`,
|
|
74
74
|
);
|
|
75
75
|
logDebug("parse", `Keys present: ${JSON.stringify(Object.keys(obj))}`);
|
|
76
|
+
// Heuristic extraction grabbed the wrong object — reject it.
|
|
77
|
+
// Strict parse still returns partial objects (caller handles defaults).
|
|
78
|
+
if (parseMethod === "heuristic") {
|
|
79
|
+
logWarn("parse", "Rejecting heuristic result due to missing required fields");
|
|
80
|
+
return null;
|
|
81
|
+
}
|
|
76
82
|
}
|
|
77
83
|
}
|
|
78
84
|
|
|
@@ -137,7 +143,7 @@ export function coerceToReview(
|
|
|
137
143
|
"coerce",
|
|
138
144
|
`verdict=${obj.verdict}, issues_count=${Array.isArray(obj.issues) ? (obj.issues as unknown[]).length : 0}`,
|
|
139
145
|
);
|
|
140
|
-
}
|
|
146
|
+
}
|
|
141
147
|
|
|
142
148
|
if (!obj.issues) {
|
|
143
149
|
logDebug("coerce", "issues array empty or missing");
|
|
@@ -1,67 +1,15 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Plan orchestrator — analyzes complexity and selects reviewer agents.
|
|
3
|
+
* Uses OrchestratorClaudeAgent (BaseCliAgent framework) for subprocess execution.
|
|
3
4
|
* See cc-native-plan-review-spec.md §4.8
|
|
4
5
|
*/
|
|
5
6
|
|
|
6
|
-
import {
|
|
7
|
-
import type { AgentConfig,
|
|
8
|
-
import {
|
|
9
|
-
import { logDebug, logError, logInfo, logWarn } from "../../_shared/lib-ts/base/logger.js";
|
|
10
|
-
import { execFileAsync, findExecutable, getInternalSubprocessEnv } from "../../_shared/lib-ts/base/subprocess-utils.js";
|
|
7
|
+
import { logInfo, logWarn } from "../../_shared/lib-ts/base/logger.js";
|
|
8
|
+
import type { AgentConfig, OrchestratorConfig, OrchestratorResult } from "./types.js";
|
|
9
|
+
import { OrchestratorClaudeAgent } from "./reviewers/providers/orchestrator-claude-agent.js";
|
|
11
10
|
|
|
12
|
-
//
|
|
13
|
-
|
|
14
|
-
// ---------------------------------------------------------------------------
|
|
15
|
-
|
|
16
|
-
const DEFAULT_AGENT_SELECTION: Record<string, unknown> = {
|
|
17
|
-
simple: { min: 3, max: 3 },
|
|
18
|
-
medium: { min: 8, max: 8 },
|
|
19
|
-
high: { min: 12, max: 12 },
|
|
20
|
-
fallbackCount: 3,
|
|
21
|
-
};
|
|
22
|
-
|
|
23
|
-
const DEFAULT_COMPLEXITY_CATEGORIES = [
|
|
24
|
-
"code",
|
|
25
|
-
"infrastructure",
|
|
26
|
-
"documentation",
|
|
27
|
-
"life",
|
|
28
|
-
"business",
|
|
29
|
-
"design",
|
|
30
|
-
"research",
|
|
31
|
-
];
|
|
32
|
-
|
|
33
|
-
// ---------------------------------------------------------------------------
|
|
34
|
-
// Schema Builder
|
|
35
|
-
// ---------------------------------------------------------------------------
|
|
36
|
-
|
|
37
|
-
/**
|
|
38
|
-
* Build orchestrator JSON schema with enum-constrained agent names.
|
|
39
|
-
*/
|
|
40
|
-
export function buildOrchestratorSchema(
|
|
41
|
-
validAgentNames: string[],
|
|
42
|
-
categories: string[],
|
|
43
|
-
): Record<string, unknown> {
|
|
44
|
-
const itemsSchema: Record<string, unknown> = { type: "string" };
|
|
45
|
-
if (validAgentNames.length > 0) {
|
|
46
|
-
itemsSchema.enum = validAgentNames;
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
return {
|
|
50
|
-
type: "object",
|
|
51
|
-
properties: {
|
|
52
|
-
complexity: { type: "string", enum: ["simple", "medium", "high"] },
|
|
53
|
-
category: { type: "string", enum: categories },
|
|
54
|
-
selectedAgents: {
|
|
55
|
-
type: "array",
|
|
56
|
-
items: itemsSchema,
|
|
57
|
-
},
|
|
58
|
-
reasoning: { type: "string" },
|
|
59
|
-
skipReason: { type: "string" },
|
|
60
|
-
},
|
|
61
|
-
required: ["complexity", "category", "selectedAgents", "reasoning"],
|
|
62
|
-
additionalProperties: false,
|
|
63
|
-
};
|
|
64
|
-
}
|
|
11
|
+
// Re-export for backward compatibility (moved to reviewers/schemas.ts)
|
|
12
|
+
export { buildOrchestratorSchema } from "./reviewers/schemas.js";
|
|
65
13
|
|
|
66
14
|
// ---------------------------------------------------------------------------
|
|
67
15
|
// Orchestrator
|
|
@@ -81,169 +29,43 @@ export async function runOrchestrator(
|
|
|
81
29
|
logInfo("orchestrator", "Starting plan analysis...");
|
|
82
30
|
|
|
83
31
|
const mandatory = mandatoryNames ?? new Set<string>();
|
|
84
|
-
const selection = (settings.agentSelection as Record<string, unknown>) ?? DEFAULT_AGENT_SELECTION;
|
|
85
|
-
const categories = (settings.complexityCategories as string[]) ?? DEFAULT_COMPLEXITY_CATEGORIES;
|
|
86
|
-
const fallbackCount = (selection.fallbackCount as number) ?? 2;
|
|
87
32
|
|
|
88
|
-
//
|
|
89
|
-
const
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
33
|
+
// Create a synthetic AgentConfig for the orchestrator
|
|
34
|
+
const orchestratorAgent: AgentConfig = {
|
|
35
|
+
name: "orchestrator",
|
|
36
|
+
model: config.model,
|
|
37
|
+
provider: "claude",
|
|
38
|
+
focus: "plan analysis and agent selection",
|
|
39
|
+
enabled: config.enabled,
|
|
40
|
+
categories: [],
|
|
41
|
+
description: "Plan orchestrator",
|
|
42
|
+
system_prompt: "",
|
|
43
|
+
};
|
|
96
44
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
45
|
+
try {
|
|
46
|
+
const agent = new OrchestratorClaudeAgent(
|
|
47
|
+
orchestratorAgent,
|
|
48
|
+
agentLibrary,
|
|
49
|
+
mandatory,
|
|
50
|
+
settings,
|
|
51
|
+
config.timeout,
|
|
102
52
|
);
|
|
103
|
-
return makeFallback(nonMandatory, fallbackCount, "Orchestrator skipped - Claude CLI not found", "claude CLI not found on PATH");
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
logDebug("orchestrator", `Found Claude CLI at: ${claudePath}`);
|
|
107
|
-
|
|
108
|
-
// Build agent list from non-mandatory agents
|
|
109
|
-
const agentList = nonMandatory
|
|
110
|
-
.map(
|
|
111
|
-
(a) =>
|
|
112
|
-
`- ${a.name} [${a.categories.join(", ")}]\n Focus: ${a.focus}\n Expertise: ${a.description}`,
|
|
113
|
-
)
|
|
114
|
-
.join("\n");
|
|
115
|
-
const categoryList = categories.join("/");
|
|
116
|
-
|
|
117
|
-
// Compute additional agent counts
|
|
118
|
-
const mandatoryCount = agentLibrary.filter((a) => mandatory.has(a.name)).length;
|
|
119
|
-
const simpleAdditional = Math.max(0, ((selection.simple as Record<string, number> | undefined)?.max ?? 3) - mandatoryCount);
|
|
120
|
-
const mediumAdditional = Math.max(0, ((selection.medium as Record<string, number> | undefined)?.max ?? 8) - mandatoryCount);
|
|
121
|
-
const highAdditional = Math.max(0, ((selection.high as Record<string, number> | undefined)?.max ?? 12) - mandatoryCount);
|
|
122
|
-
|
|
123
|
-
const systemPrompt = `You are a plan orchestrator for code review. Your job is to analyze plans and select appropriate reviewer agents.
|
|
124
|
-
|
|
125
|
-
You MUST call StructuredOutput immediately with your analysis. Do NOT ask questions or use any other tools.
|
|
126
|
-
|
|
127
|
-
When selecting agents:
|
|
128
|
-
- Match agent expertise to plan requirements
|
|
129
|
-
- Consider what each agent specializes in
|
|
130
|
-
- Only select agents whose categories match the plan category
|
|
131
|
-
- Fewer agents for simple plans, more for complex plans`;
|
|
132
|
-
|
|
133
|
-
const prompt = `Analyze this plan and select appropriate reviewer agents.
|
|
134
|
-
|
|
135
|
-
Available agents (select ONLY from this list):
|
|
136
|
-
${agentList}
|
|
137
|
-
|
|
138
|
-
Selection rules (number of ADDITIONAL agents to select from the list above):
|
|
139
|
-
- simple complexity = ${simpleAdditional} agents
|
|
140
|
-
- medium complexity = ${mediumAdditional} agents
|
|
141
|
-
- high complexity = ${highAdditional} agents
|
|
142
|
-
- Only select agents whose categories match the plan category (${categoryList})
|
|
143
|
-
- Non-technical plans (life, business) typically need 0 code-focused agents
|
|
144
|
-
- Note: mandatory agents run separately and are NOT listed above
|
|
145
|
-
|
|
146
|
-
PLAN:
|
|
147
|
-
<<<
|
|
148
|
-
${plan}
|
|
149
|
-
>>>
|
|
150
53
|
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
];
|
|
168
|
-
|
|
169
|
-
logInfo("orchestrator", `Running with model: ${config.model}, timeout: ${config.timeout}s`);
|
|
170
|
-
|
|
171
|
-
const env = getInternalSubprocessEnv();
|
|
172
|
-
|
|
173
|
-
const result = await execFileAsync(claudePath, cmdArgs, {
|
|
174
|
-
input: prompt,
|
|
175
|
-
timeout: config.timeout * 1000,
|
|
176
|
-
env: env as Record<string, string>,
|
|
177
|
-
maxBuffer: 10 * 1024 * 1024,
|
|
178
|
-
});
|
|
179
|
-
|
|
180
|
-
if (result.killed || result.signal === "SIGTERM") {
|
|
181
|
-
logWarn("orchestrator", `TIMEOUT after ${config.timeout}s, falling back to medium complexity`);
|
|
182
|
-
return makeFallback(nonMandatory, fallbackCount, "Orchestrator timed out - defaulting to medium complexity", `Orchestrator timed out after ${config.timeout}s`);
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
const raw = result.stdout.trim();
|
|
186
|
-
if (result.stderr) logDebug("orchestrator", `stderr: ${result.stderr.slice(0, 300)}`);
|
|
187
|
-
|
|
188
|
-
if (!raw && !result.stderr && result.exitCode !== 0) {
|
|
189
|
-
logError("orchestrator", `Process exited with code ${result.exitCode}, falling back to medium complexity`);
|
|
190
|
-
return makeFallback(nonMandatory, fallbackCount, `Orchestrator failed (exit ${result.exitCode})`, `Exit code ${result.exitCode}`);
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
const obj = parseCliOutput(raw);
|
|
194
|
-
|
|
195
|
-
logDebug("orchestrator", `Raw output length: ${raw.length} chars`);
|
|
196
|
-
if (raw) logDebug("orchestrator", `Raw output (first 500 chars): ${raw.slice(0, 500)}`);
|
|
197
|
-
logDebug("orchestrator", `Parsed obj: ${JSON.stringify(obj)}`);
|
|
198
|
-
|
|
199
|
-
if (!obj) {
|
|
200
|
-
logWarn("orchestrator", "Failed to parse output, falling back to medium complexity");
|
|
201
|
-
return makeFallback(nonMandatory, fallbackCount, "Orchestrator output could not be parsed", "Failed to parse orchestrator output");
|
|
54
|
+
const result = await agent.review(plan);
|
|
55
|
+
|
|
56
|
+
logInfo("orchestrator", `Result: complexity=${result.complexity}, category=${result.category}, agents=${JSON.stringify(result.selected_agents)}`);
|
|
57
|
+
|
|
58
|
+
return result;
|
|
59
|
+
} catch (e) {
|
|
60
|
+
logWarn("orchestrator", `Unexpected error: ${e}`);
|
|
61
|
+
const nonMandatory = agentLibrary.filter((a) => a.enabled && !mandatory.has(a.name));
|
|
62
|
+
const fallbackCount = ((settings.agentSelection as Record<string, unknown>)?.fallbackCount as number) ?? 2;
|
|
63
|
+
return {
|
|
64
|
+
complexity: "medium",
|
|
65
|
+
category: "code",
|
|
66
|
+
selected_agents: nonMandatory.slice(0, fallbackCount).map((a) => a.name),
|
|
67
|
+
reasoning: `Orchestrator failed: ${e}`,
|
|
68
|
+
error: String(e),
|
|
69
|
+
};
|
|
202
70
|
}
|
|
203
|
-
|
|
204
|
-
// Extract and validate fields
|
|
205
|
-
const rawComplexity = String(obj.complexity ?? "medium");
|
|
206
|
-
const complexity: ComplexityCategory =
|
|
207
|
-
rawComplexity === "simple" || rawComplexity === "medium" || rawComplexity === "high"
|
|
208
|
-
? rawComplexity
|
|
209
|
-
: "medium";
|
|
210
|
-
|
|
211
|
-
let category = (obj.category as string) ?? "code";
|
|
212
|
-
if (!categories.includes(category)) category = "code";
|
|
213
|
-
|
|
214
|
-
let {selectedAgents} = obj;
|
|
215
|
-
if (!Array.isArray(selectedAgents)) selectedAgents = [];
|
|
216
|
-
|
|
217
|
-
const reasoning = String(obj.reasoning ?? "").trim() || "No reasoning provided";
|
|
218
|
-
const skipReason = obj.skipReason as string | undefined;
|
|
219
|
-
|
|
220
|
-
logInfo("orchestrator", `Result: complexity=${complexity}, category=${category}, agents=${JSON.stringify(selectedAgents)}`);
|
|
221
|
-
logDebug("orchestrator", `Reasoning: ${reasoning}`);
|
|
222
|
-
|
|
223
|
-
return {
|
|
224
|
-
complexity,
|
|
225
|
-
category,
|
|
226
|
-
selected_agents: selectedAgents as string[],
|
|
227
|
-
reasoning,
|
|
228
|
-
skip_reason: skipReason || undefined,
|
|
229
|
-
};
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
// ---------------------------------------------------------------------------
|
|
233
|
-
// Helpers
|
|
234
|
-
// ---------------------------------------------------------------------------
|
|
235
|
-
|
|
236
|
-
function makeFallback(
|
|
237
|
-
nonMandatory: AgentConfig[],
|
|
238
|
-
fallbackCount: number,
|
|
239
|
-
reasoning: string,
|
|
240
|
-
error: string,
|
|
241
|
-
): OrchestratorResult {
|
|
242
|
-
return {
|
|
243
|
-
complexity: "medium",
|
|
244
|
-
category: "code",
|
|
245
|
-
selected_agents: nonMandatory.slice(0, fallbackCount).map((a) => a.name),
|
|
246
|
-
reasoning,
|
|
247
|
-
error,
|
|
248
|
-
};
|
|
249
71
|
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Plan quality guidance for context emission.
|
|
3
|
+
*
|
|
4
|
+
* Provides prompt text that guides the main agent to review plans before
|
|
5
|
+
* presenting them to the user. Emitted via emitContext() — NOT appended to plan files.
|
|
6
|
+
*
|
|
7
|
+
* Used by both SubagentStop hook (Plan agents) and PostToolUse:Write hook (direct writes).
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Returns the plan quality review prompt to emit as context after a plan is written.
|
|
12
|
+
* This guides the main agent to review the plan before calling ExitPlanMode.
|
|
13
|
+
*
|
|
14
|
+
* Design principles:
|
|
15
|
+
* - No hardcoded skill names — agent discovers relevant skills from system-reminders
|
|
16
|
+
* - Documentation focuses on WHY (preserve decisions) not WHERE (file paths)
|
|
17
|
+
* - Concise — every token in emitted context costs attention budget
|
|
18
|
+
* - Trusts the agent's judgment — guidance, not mandate
|
|
19
|
+
*/
|
|
20
|
+
export function getPlanQualityReviewContext(): string {
|
|
21
|
+
return `## Plan Quality Review
|
|
22
|
+
|
|
23
|
+
Before presenting this plan, review it from the perspective of an agent with zero conversation history.
|
|
24
|
+
|
|
25
|
+
### Self-Check
|
|
26
|
+
- File paths are absolute and verified (not "the auth file" or "as discussed")
|
|
27
|
+
- Function and class names are exact references (not "the handler" or "it")
|
|
28
|
+
- Each step is specific enough to execute without this conversation's context
|
|
29
|
+
- Verification steps are binary-testable (pass/fail in one check)
|
|
30
|
+
|
|
31
|
+
### Skills Integration
|
|
32
|
+
Review the skills listed in your system-reminder messages. Where a step would benefit from a specific skill, reference it inline (e.g., "Use \`SkillName\` skill for [specific purpose]"). Only reference skills relevant to this plan's domain.
|
|
33
|
+
|
|
34
|
+
### Documentation Reasoning
|
|
35
|
+
Evaluate whether the plan captures decisions that would be lost when this session ends. The implementation agent should understand:
|
|
36
|
+
- What was decided and why alternatives were rejected
|
|
37
|
+
- What constraints exist that aren't obvious from the code
|
|
38
|
+
- What would break if assumptions change
|
|
39
|
+
|
|
40
|
+
If the plan has gaps, address them before presenting to the user.`;
|
|
41
|
+
}
|