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.
Files changed (118) hide show
  1. package/dist/commands/clear.d.ts +8 -0
  2. package/dist/commands/clear.js +86 -0
  3. package/dist/lib/bmad-installer.d.ts +2 -27
  4. package/dist/lib/bmad-installer.js +3 -43
  5. package/dist/lib/claude-settings-types.d.ts +2 -1
  6. package/dist/lib/env-compat.d.ts +0 -8
  7. package/dist/lib/env-compat.js +0 -12
  8. package/dist/lib/git/index.d.ts +0 -1
  9. package/dist/lib/gitignore-manager.d.ts +0 -2
  10. package/dist/lib/gitignore-manager.js +1 -1
  11. package/dist/lib/hooks-merger.d.ts +1 -15
  12. package/dist/lib/hooks-merger.js +1 -1
  13. package/dist/lib/index.d.ts +3 -7
  14. package/dist/lib/index.js +3 -11
  15. package/dist/lib/output.d.ts +2 -1
  16. package/dist/lib/settings-hierarchy.d.ts +1 -13
  17. package/dist/lib/settings-hierarchy.js +1 -1
  18. package/dist/lib/template-installer.d.ts +5 -9
  19. package/dist/lib/template-installer.js +2 -12
  20. package/dist/lib/template-linter.d.ts +3 -10
  21. package/dist/lib/template-linter.js +2 -2
  22. package/dist/lib/template-resolver.d.ts +6 -0
  23. package/dist/lib/template-resolver.js +10 -0
  24. package/dist/lib/template-settings-reconstructor.d.ts +1 -1
  25. package/dist/lib/template-settings-reconstructor.js +17 -24
  26. package/dist/lib/terminal.d.ts +3 -14
  27. package/dist/lib/terminal.js +0 -4
  28. package/dist/lib/version.d.ts +2 -11
  29. package/dist/lib/version.js +2 -2
  30. package/dist/lib/windsurf-hooks-merger.d.ts +1 -15
  31. package/dist/lib/windsurf-hooks-merger.js +1 -1
  32. package/dist/templates/_shared/.codex/workflows/handoff.md +1 -1
  33. package/dist/templates/_shared/.windsurf/workflows/handoff.md +1 -1
  34. package/dist/templates/_shared/hooks-ts/session_end.ts +4 -1
  35. package/dist/templates/_shared/hooks-ts/session_start.ts +15 -20
  36. package/dist/templates/_shared/hooks-ts/user_prompt_submit.ts +12 -14
  37. package/dist/templates/_shared/lib-ts/CLAUDE.md +56 -7
  38. package/dist/templates/_shared/lib-ts/base/git-state.ts +1 -1
  39. package/dist/templates/_shared/lib-ts/base/hook-utils.ts +174 -43
  40. package/dist/templates/_shared/lib-ts/base/logger.ts +15 -18
  41. package/dist/templates/_shared/lib-ts/base/state-io.ts +11 -2
  42. package/dist/templates/_shared/lib-ts/base/subprocess-utils.ts +181 -162
  43. package/dist/templates/_shared/lib-ts/context/plan-manager.ts +26 -30
  44. package/dist/templates/_shared/lib-ts/handoff/handoff-reader.ts +12 -13
  45. package/dist/templates/_shared/lib-ts/package.json +1 -2
  46. package/dist/templates/_shared/lib-ts/templates/plan-context.ts +27 -34
  47. package/dist/templates/_shared/lib-ts/types.ts +17 -2
  48. package/dist/templates/_shared/scripts/resume_handoff.ts +62 -38
  49. package/dist/templates/_shared/scripts/save_handoff.ts +24 -24
  50. package/dist/templates/_shared/scripts/status_line.ts +102 -148
  51. package/dist/templates/_shared/workflows/handoff.md +1 -1
  52. package/dist/templates/cc-native/.claude/settings.json +183 -175
  53. package/dist/templates/cc-native/_cc-native/agents/CLAUDE.md +23 -1
  54. package/dist/templates/cc-native/_cc-native/agents/plan-questions/PLAN-QUESTIONER.md +70 -0
  55. package/dist/templates/cc-native/_cc-native/hooks/CLAUDE.md +6 -1
  56. package/dist/templates/cc-native/_cc-native/hooks/cc-native-plan-review.ts +316 -176
  57. package/dist/templates/cc-native/_cc-native/hooks/enhance_plan_post_subagent.ts +38 -0
  58. package/dist/templates/cc-native/_cc-native/hooks/enhance_plan_post_write.ts +51 -0
  59. package/dist/templates/cc-native/_cc-native/hooks/mark_questions_asked.ts +53 -0
  60. package/dist/templates/cc-native/_cc-native/hooks/plan_questions_early.ts +15 -15
  61. package/dist/templates/cc-native/_cc-native/lib-ts/aggregate-agents.ts +11 -12
  62. package/dist/templates/cc-native/_cc-native/lib-ts/artifacts.ts +227 -114
  63. package/dist/templates/cc-native/_cc-native/lib-ts/cc-native-state.ts +64 -16
  64. package/dist/templates/cc-native/_cc-native/lib-ts/cli-output-parser.ts +23 -3
  65. package/dist/templates/cc-native/_cc-native/lib-ts/corroboration.ts +119 -0
  66. package/dist/templates/cc-native/_cc-native/lib-ts/index.ts +1 -4
  67. package/dist/templates/cc-native/_cc-native/lib-ts/json-parser.ts +7 -1
  68. package/dist/templates/cc-native/_cc-native/lib-ts/orchestrator.ts +40 -218
  69. package/dist/templates/cc-native/_cc-native/lib-ts/plan-enhancement.ts +41 -0
  70. package/dist/templates/cc-native/_cc-native/lib-ts/plan-questions.ts +101 -0
  71. package/dist/templates/cc-native/_cc-native/lib-ts/reviewers/agent.ts +27 -111
  72. package/dist/templates/cc-native/_cc-native/lib-ts/reviewers/base/base-agent.ts +217 -0
  73. package/dist/templates/cc-native/_cc-native/lib-ts/reviewers/index.ts +5 -3
  74. package/dist/templates/cc-native/_cc-native/lib-ts/reviewers/providers/claude-agent.ts +65 -0
  75. package/dist/templates/cc-native/_cc-native/lib-ts/reviewers/providers/codex-agent.ts +184 -0
  76. package/dist/templates/cc-native/_cc-native/lib-ts/reviewers/providers/gemini-agent.ts +39 -0
  77. package/dist/templates/cc-native/_cc-native/lib-ts/reviewers/providers/orchestrator-claude-agent.ts +195 -0
  78. package/dist/templates/cc-native/_cc-native/lib-ts/reviewers/schemas.ts +201 -0
  79. package/dist/templates/cc-native/_cc-native/lib-ts/reviewers/types.ts +3 -5
  80. package/dist/templates/cc-native/_cc-native/lib-ts/state.ts +30 -33
  81. package/dist/templates/cc-native/_cc-native/lib-ts/types.ts +104 -132
  82. package/dist/templates/cc-native/_cc-native/plan-review.config.json +22 -13
  83. package/oclif.manifest.json +1 -1
  84. package/package.json +2 -3
  85. package/dist/templates/cc-native/_cc-native/hooks/add_plan_context.ts +0 -119
  86. package/dist/templates/cc-native/_cc-native/lib-ts/reviewers/codex.ts +0 -130
  87. package/dist/templates/cc-native/_cc-native/lib-ts/reviewers/gemini.ts +0 -106
  88. /package/dist/templates/cc-native/_cc-native/agents/{ARCH-EVOLUTION.md → plan-review/ARCH-EVOLUTION.md} +0 -0
  89. /package/dist/templates/cc-native/_cc-native/agents/{ARCH-PATTERNS.md → plan-review/ARCH-PATTERNS.md} +0 -0
  90. /package/dist/templates/cc-native/_cc-native/agents/{ARCH-STRUCTURE.md → plan-review/ARCH-STRUCTURE.md} +0 -0
  91. /package/dist/templates/cc-native/_cc-native/agents/{ASSUMPTION-TRACER.md → plan-review/ASSUMPTION-TRACER.md} +0 -0
  92. /package/dist/templates/cc-native/_cc-native/agents/{CLARITY-AUDITOR.md → plan-review/CLARITY-AUDITOR.md} +0 -0
  93. /package/dist/templates/cc-native/_cc-native/agents/{COMPLETENESS-FEASIBILITY.md → plan-review/COMPLETENESS-FEASIBILITY.md} +0 -0
  94. /package/dist/templates/cc-native/_cc-native/agents/{COMPLETENESS-GAPS.md → plan-review/COMPLETENESS-GAPS.md} +0 -0
  95. /package/dist/templates/cc-native/_cc-native/agents/{COMPLETENESS-ORDERING.md → plan-review/COMPLETENESS-ORDERING.md} +0 -0
  96. /package/dist/templates/cc-native/_cc-native/agents/{CONSTRAINT-VALIDATOR.md → plan-review/CONSTRAINT-VALIDATOR.md} +0 -0
  97. /package/dist/templates/cc-native/_cc-native/agents/{DESIGN-ADR-VALIDATOR.md → plan-review/DESIGN-ADR-VALIDATOR.md} +0 -0
  98. /package/dist/templates/cc-native/_cc-native/agents/{DESIGN-SCALE-MATCHER.md → plan-review/DESIGN-SCALE-MATCHER.md} +0 -0
  99. /package/dist/templates/cc-native/_cc-native/agents/{DEVILS-ADVOCATE.md → plan-review/DEVILS-ADVOCATE.md} +0 -0
  100. /package/dist/templates/cc-native/_cc-native/agents/{DOCUMENTATION-PHILOSOPHY.md → plan-review/DOCUMENTATION-PHILOSOPHY.md} +0 -0
  101. /package/dist/templates/cc-native/_cc-native/agents/{HANDOFF-READINESS.md → plan-review/HANDOFF-READINESS.md} +0 -0
  102. /package/dist/templates/cc-native/_cc-native/agents/{HIDDEN-COMPLEXITY.md → plan-review/HIDDEN-COMPLEXITY.md} +0 -0
  103. /package/dist/templates/cc-native/_cc-native/agents/{INCREMENTAL-DELIVERY.md → plan-review/INCREMENTAL-DELIVERY.md} +0 -0
  104. /package/dist/templates/cc-native/_cc-native/agents/{RISK-DEPENDENCY.md → plan-review/RISK-DEPENDENCY.md} +0 -0
  105. /package/dist/templates/cc-native/_cc-native/agents/{RISK-FMEA.md → plan-review/RISK-FMEA.md} +0 -0
  106. /package/dist/templates/cc-native/_cc-native/agents/{RISK-PREMORTEM.md → plan-review/RISK-PREMORTEM.md} +0 -0
  107. /package/dist/templates/cc-native/_cc-native/agents/{RISK-REVERSIBILITY.md → plan-review/RISK-REVERSIBILITY.md} +0 -0
  108. /package/dist/templates/cc-native/_cc-native/agents/{SCOPE-BOUNDARY.md → plan-review/SCOPE-BOUNDARY.md} +0 -0
  109. /package/dist/templates/cc-native/_cc-native/agents/{SIMPLICITY-GUARDIAN.md → plan-review/SIMPLICITY-GUARDIAN.md} +0 -0
  110. /package/dist/templates/cc-native/_cc-native/agents/{SKEPTIC.md → plan-review/SKEPTIC.md} +0 -0
  111. /package/dist/templates/cc-native/_cc-native/agents/{TESTDRIVEN-BEHAVIOR-AUDITOR.md → plan-review/TESTDRIVEN-BEHAVIOR-AUDITOR.md} +0 -0
  112. /package/dist/templates/cc-native/_cc-native/agents/{TESTDRIVEN-CHARACTERIZATION.md → plan-review/TESTDRIVEN-CHARACTERIZATION.md} +0 -0
  113. /package/dist/templates/cc-native/_cc-native/agents/{TESTDRIVEN-FIRST-VALIDATOR.md → plan-review/TESTDRIVEN-FIRST-VALIDATOR.md} +0 -0
  114. /package/dist/templates/cc-native/_cc-native/agents/{TESTDRIVEN-PYRAMID-ANALYZER.md → plan-review/TESTDRIVEN-PYRAMID-ANALYZER.md} +0 -0
  115. /package/dist/templates/cc-native/_cc-native/agents/{TRADEOFF-COSTS.md → plan-review/TRADEOFF-COSTS.md} +0 -0
  116. /package/dist/templates/cc-native/_cc-native/agents/{TRADEOFF-STAKEHOLDERS.md → plan-review/TRADEOFF-STAKEHOLDERS.md} +0 -0
  117. /package/dist/templates/cc-native/_cc-native/agents/{VERIFY-COVERAGE.md → plan-review/VERIFY-COVERAGE.md} +0 -0
  118. /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 for cc-native hooks.
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 as _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 (error: unknown) {
66
- logWarn("utils", `Failed to save cc_native state: ${error}`);
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 {history} = iterationState;
148
+ const history = iterationState.history;
134
149
  if (history && history.length > 0) {
135
- const lastEntry = history.at(-1);
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 (error: unknown) {
156
- logWarn(hookName, `Failed to mark plan reviewed: ${error}`);
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 (error: unknown) {
195
- logWarn("utils", `Failed to mark questions asked: ${error}`);
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.py and reviewers/agent.py.
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 { parseCliOutput } from "./cli-output-parser.js";
7
- import type { AgentConfig, ComplexityCategory, OrchestratorConfig, OrchestratorResult } from "./types.js";
8
- import { ORCHESTRATOR_SCHEMA } from "./types.js";
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
- // Constants
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
- // Filter out mandatory agents they always run
89
- const nonMandatory = agentLibrary.filter(
90
- (a) => a.enabled && !mandatory.has(a.name),
91
- );
92
- const validNames = nonMandatory.map((a) => a.name);
93
-
94
- logDebug("orchestrator", `Mandatory agents (always run): ${[...mandatory].sort().join(", ")}`);
95
- logDebug("orchestrator", `Non-mandatory agents for selection: ${validNames.join(", ")}`);
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
- const claudePath = findExecutable("claude");
98
- if (!claudePath) {
99
- logWarn(
100
- "orchestrator",
101
- "Claude CLI not found on PATH, falling back to medium complexity",
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
- Call StructuredOutput now with: complexity, category, selectedAgents, reasoning`;
152
-
153
- const schema =
154
- validNames.length > 0
155
- ? buildOrchestratorSchema(validNames, categories)
156
- : ORCHESTRATOR_SCHEMA;
157
- const schemaJson = JSON.stringify(schema);
158
-
159
- const cmdArgs = [
160
- "--model", config.model,
161
- "--output-format", "json",
162
- "--json-schema", schemaJson,
163
- "--max-turns", "3",
164
- "--setting-sources", "",
165
- "--system-prompt", systemPrompt,
166
- "-p",
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
+ }