aiwcli 0.14.0 → 0.15.2

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 (29) hide show
  1. package/dist/templates/_shared/.claude/skills/codex/SKILL.md +19 -26
  2. package/dist/templates/_shared/.codex/workflows/codex.md +11 -0
  3. package/dist/templates/_shared/lib-ts/agent-exec/index.ts +2 -0
  4. package/dist/templates/_shared/lib-ts/agent-exec/structured-output.ts +166 -0
  5. package/dist/templates/_shared/lib-ts/base/cli-args.ts +4 -0
  6. package/dist/templates/_shared/lib-ts/base/state-io.ts +1 -1
  7. package/dist/templates/_shared/lib-ts/base/subprocess-utils.ts +4 -3
  8. package/dist/templates/_shared/lib-ts/base/tmux-driver.ts +70 -3
  9. package/dist/templates/_shared/lib-ts/base/tmux-pane-placement.ts +70 -0
  10. package/dist/templates/_shared/lib-ts/context/context-store.ts +3 -0
  11. package/dist/templates/_shared/scripts/resolve-run.ts +1 -1
  12. package/dist/templates/_shared/scripts/status_line.ts +36 -19
  13. package/dist/templates/_shared/skills/codex/CLAUDE.md +77 -0
  14. package/dist/templates/_shared/skills/codex/SKILL.md +71 -0
  15. package/dist/templates/_shared/skills/codex/lib/codex-watcher.ts +226 -0
  16. package/dist/templates/_shared/skills/{prompt-codex → codex}/scripts/launch-codex.ts +175 -8
  17. package/dist/templates/_shared/skills/codex/scripts/watch-codex.ts +42 -0
  18. package/dist/templates/cc-native/_cc-native/lib-ts/cli-output-parser.ts +9 -133
  19. package/dist/templates/cc-native/_cc-native/lib-ts/settings.ts +118 -42
  20. package/dist/templates/cc-native/_cc-native/lib-ts/state.ts +1 -0
  21. package/dist/templates/cc-native/_cc-native/lib-ts/types.ts +61 -0
  22. package/dist/templates/cc-native/_cc-native/plan-review/lib/agent-selection.ts +5 -4
  23. package/dist/templates/cc-native/_cc-native/plan-review/lib/orchestrator.ts +4 -4
  24. package/dist/templates/cc-native/_cc-native/plan-review/lib/review-pipeline.ts +16 -13
  25. package/dist/templates/cc-native/_cc-native/plan-review/lib/reviewers/providers/orchestrator-claude-agent.ts +54 -23
  26. package/oclif.manifest.json +1 -1
  27. package/package.json +1 -1
  28. package/dist/templates/_shared/.claude/skills/codex/prompt.md +0 -10
  29. package/dist/templates/_shared/skills/prompt-codex/CLAUDE.md +0 -46
@@ -1,144 +1,20 @@
1
1
  /**
2
- * Unified Claude CLI output parser.
3
- * Deduplicates identical logic from orchestrator.ts and reviewers/agent.ts.
4
- * See cc-native-plan-review-spec.md §4.6
2
+ * CC-native wrapper around shared structured-output parsing.
3
+ * Keeps existing import surface stable for provider implementations.
5
4
  */
6
5
 
7
- import { parseJsonMaybe } from "./json-parser.js";
8
- import { logDebug, logError, logWarn } from "../../_shared/lib-ts/base/logger.js";
6
+ import { parseStructuredOutput } from "../../_shared/lib-ts/agent-exec/structured-output.js";
9
7
 
10
8
  /**
11
- * Parse Claude CLI JSON output, handling various formats.
12
- *
13
- * Claude CLI can output in several formats:
14
- * - Direct structured_output dict
15
- * - Assistant message with StructuredOutput tool use
16
- * - List of events with assistant messages
17
- * - Raw text with embedded JSON (heuristic fallback)
18
- *
19
- * @param raw - Raw stdout from Claude CLI
20
- * @param requireFields - Optional fields to validate in heuristic fallback
21
- * @returns Parsed JSON dict or null if parsing failed
9
+ * Parse CLI JSON output into a structured object.
10
+ * Delegates to shared parser with cc-native logging tag.
22
11
  */
23
12
  export function parseCliOutput(
24
13
  raw: string,
25
14
  requireFields?: string[],
26
15
  ): null | Record<string, unknown> {
27
- try {
28
- const result: unknown = JSON.parse(raw);
29
-
30
- if (result !== null && typeof result === "object" && !Array.isArray(result)) {
31
- const dict = result as Record<string, unknown>;
32
-
33
- // Strategy 1: Direct structured_output key
34
- if ("structured_output" in dict) {
35
- logDebug("cli_parser", "Found structured_output in root dict");
36
- return dict.structured_output as Record<string, unknown>;
37
- }
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
-
59
- // Strategy 2: Assistant message with StructuredOutput tool use
60
- if (dict.type === "assistant") {
61
- const message = dict.message as Record<string, unknown> | undefined;
62
- const content = message?.content;
63
- if (Array.isArray(content)) {
64
- for (const item of content) {
65
- if (
66
- item !== null &&
67
- typeof item === "object" &&
68
- (item as Record<string, unknown>).name === "StructuredOutput"
69
- ) {
70
- logDebug(
71
- "cli_parser",
72
- "Found StructuredOutput in assistant message content",
73
- );
74
- return (item as Record<string, unknown>).input as Record<
75
- string,
76
- unknown
77
- >;
78
- }
79
- }
80
- }
81
-
82
- logDebug(
83
- "cli_parser",
84
- "Assistant message found but no StructuredOutput tool use in content",
85
- );
86
- }
87
- } else if (Array.isArray(result)) {
88
- // Strategy 3: List of events with assistant messages
89
- logDebug(
90
- "cli_parser",
91
- `Received list of ${(result as unknown[]).length} events, searching for assistant message`,
92
- );
93
- for (let i = 0; i < (result as unknown[]).length; i++) {
94
- const event = (result as unknown[])[i];
95
- if (event === null || typeof event !== "object") continue;
96
-
97
- const dict = event as Record<string, unknown>;
98
- if (dict.type === "assistant") {
99
- const message = dict.message as Record<string, unknown> | undefined;
100
- const content = message?.content;
101
- if (Array.isArray(content)) {
102
- for (const item of content) {
103
- if (
104
- item !== null &&
105
- typeof item === "object" &&
106
- (item as Record<string, unknown>).name === "StructuredOutput"
107
- ) {
108
- logDebug(
109
- "cli_parser",
110
- `Found StructuredOutput in event[${i}] assistant message`,
111
- );
112
- return (item as Record<string, unknown>).input as Record<
113
- string,
114
- unknown
115
- >;
116
- }
117
- }
118
- }
119
- }
120
- }
121
-
122
- logDebug(
123
- "cli_parser",
124
- "No StructuredOutput found in any assistant message in event list",
125
- );
126
- }
127
- } catch (error: unknown) {
128
- if (error instanceof SyntaxError) {
129
- logWarn("cli_parser", `JSON decode error: ${error.message}`);
130
- } else {
131
- logError(
132
- "cli_parser",
133
- `Unexpected error during structured parsing: ${error}`,
134
- );
135
- }
136
- }
137
-
138
- // Strategy 4: Heuristic {…} extraction fallback
139
- logDebug(
140
- "cli_parser",
141
- "No structured output found, falling back to heuristic JSON extraction",
142
- );
143
- return parseJsonMaybe(raw, requireFields);
16
+ return parseStructuredOutput(raw, {
17
+ requireFields,
18
+ loggerTag: "cli_parser",
19
+ });
144
20
  }
@@ -11,8 +11,12 @@ import { loadConfig, getDisplaySettings } from "./config.js";
11
11
  import { DEFAULT_REVIEW_ITERATIONS } from "./state.js";
12
12
  import type {
13
13
  AgentConfig,
14
- ProviderConfig,
14
+ AgentReviewSettings,
15
+ AgentSelectionConfig,
16
+ LoadedSettings,
15
17
  ModelsConfig,
18
+ PlanReviewSettings,
19
+ ProviderConfig,
16
20
  } from "./types.js";
17
21
  import { DEFAULT_DISPLAY, DEFAULT_SANITIZATION } from "./types.js";
18
22
  import { logInfo } from "../../_shared/lib-ts/base/logger.js";
@@ -57,10 +61,10 @@ export const DEFAULT_AGENTS: Array<{ name: string; model: string; provider: stri
57
61
  { ...AGENT_DEFAULTS, name: "constraint-validator", focus: "constraint identification and satisfaction", categories: ALL_CATEGORIES },
58
62
  ];
59
63
 
60
- export const DEFAULT_ORCHESTRATOR: { enabled: boolean; model: string; timeout: number } = { enabled: true, model: "opus", timeout: 60 };
64
+ export const DEFAULT_ORCHESTRATOR = { enabled: true, model: CODEX_MODELS.codex, provider: "codex", timeout: 60 } as const;
61
65
  export const DEFAULT_AGENT_MODEL = "sonnet";
62
66
 
63
- export const DEFAULT_AGENT_SELECTION: Record<string, unknown> = {
67
+ export const DEFAULT_AGENT_SELECTION: AgentSelectionConfig = {
64
68
  simple: { min: 3, max: 3 },
65
69
  medium: { min: 5, max: 5 },
66
70
  high: { min: 7, max: 7 },
@@ -80,58 +84,131 @@ export const DEFAULT_MODELS_CONFIG: ModelsConfig = {
80
84
  // Settings Loading
81
85
  // ---------------------------------------------------------------------------
82
86
 
83
- export function loadSettings(projDir: string): Record<string, unknown> {
84
- const defaults: Record<string, unknown> = {
85
- planReview: {
86
- enabled: true,
87
- reviewers: {
88
- codex: { enabled: true, model: "", timeout: 120 },
89
- gemini: { enabled: false, model: "", timeout: 120 },
90
- },
91
- display: { ...DEFAULT_DISPLAY },
92
- },
93
- agentReview: {
94
- enabled: true,
95
- orchestrator: { ...DEFAULT_ORCHESTRATOR },
96
- timeout: 180,
97
- highIssueThreshold: 3,
98
- legacyMode: false,
99
- display: { ...DEFAULT_DISPLAY },
100
- agentSelection: { ...DEFAULT_AGENT_SELECTION },
101
- agentDefaults: { model: DEFAULT_AGENT_MODEL },
102
- complexityCategories: [...DEFAULT_COMPLEXITY_CATEGORIES],
103
- sanitization: { ...DEFAULT_SANITIZATION },
87
+ function asRecord(value: unknown): Record<string, unknown> | undefined {
88
+ return value && typeof value === "object" && !Array.isArray(value)
89
+ ? value as Record<string, unknown>
90
+ : undefined;
91
+ }
92
+
93
+ function asStringArray(value: unknown): string[] | undefined {
94
+ if (!Array.isArray(value)) return undefined;
95
+ const filtered = value.filter((item): item is string => typeof item === "string" && item.trim().length > 0);
96
+ return filtered.length > 0 ? filtered : undefined;
97
+ }
98
+
99
+ export function loadSettings(projDir: string): LoadedSettings {
100
+ const defaultPlan: PlanReviewSettings = {
101
+ enabled: true,
102
+ reviewers: {
103
+ codex: { enabled: true, model: "", timeout: 120 },
104
+ gemini: { enabled: false, model: "", timeout: 120 },
104
105
  },
106
+ display: { ...DEFAULT_DISPLAY },
107
+ };
108
+
109
+ const defaultAgent: AgentReviewSettings = {
110
+ enabled: true,
111
+ orchestrator: { ...DEFAULT_ORCHESTRATOR },
112
+ timeout: 180,
113
+ highIssueThreshold: 3,
114
+ legacyMode: false,
115
+ display: { ...DEFAULT_DISPLAY },
116
+ agentSelection: { ...DEFAULT_AGENT_SELECTION },
117
+ agentDefaults: { model: DEFAULT_AGENT_MODEL },
118
+ complexityCategories: [...DEFAULT_COMPLEXITY_CATEGORIES],
119
+ sanitization: { ...DEFAULT_SANITIZATION },
105
120
  };
106
121
 
107
122
  const config = loadConfig(projDir);
108
- if (!config || Object.keys(config).length === 0) return { ...defaults, models: {} };
123
+ if (!config || Object.keys(config).length === 0) {
124
+ return { planReview: defaultPlan, agentReview: defaultAgent, models: {} };
125
+ }
126
+
127
+ // Cast raw config to access arbitrary keys from JSON
128
+ const raw = config as Record<string, unknown>;
109
129
 
110
130
  // Merge planReview
111
- const planReview = config.planReview ?? {};
112
- const mergedPlan = { ...defaults.planReview, ...planReview };
113
- if (planReview.reviewers) {
114
- mergedPlan.reviewers = { ...defaults.planReview.reviewers, ...planReview.reviewers };
131
+ const planReviewRaw = (asRecord(raw.planReview) ?? {}) as Partial<PlanReviewSettings>;
132
+ const mergedPlan: PlanReviewSettings = { ...defaultPlan, ...planReviewRaw };
133
+ if (planReviewRaw.reviewers) {
134
+ mergedPlan.reviewers = { ...defaultPlan.reviewers, ...planReviewRaw.reviewers };
115
135
  }
116
136
  mergedPlan.display = getDisplaySettings(config, "planReview");
117
137
 
118
138
  // Merge agentReview
119
- const agentReview = (config as Record<string, unknown>).agentReview ?? {};
120
- const mergedAgent = { ...defaults.agentReview, ...agentReview };
139
+ const agentReviewRawRecord = asRecord(raw.agentReview);
140
+ const agentReviewRaw = (agentReviewRawRecord ?? {}) as Partial<AgentReviewSettings>;
141
+ const mergedAgent: AgentReviewSettings = { ...defaultAgent, ...agentReviewRaw };
121
142
  if (!mergedAgent.orchestrator || typeof mergedAgent.orchestrator !== "object") {
122
143
  mergedAgent.orchestrator = { ...DEFAULT_ORCHESTRATOR };
123
144
  } else {
124
145
  mergedAgent.orchestrator = { ...DEFAULT_ORCHESTRATOR, ...mergedAgent.orchestrator };
125
146
  }
126
147
  mergedAgent.display = getDisplaySettings(config, "agentReview");
127
- const configRecord = config as Record<string, unknown>;
128
- mergedAgent.agentSelection = { ...DEFAULT_AGENT_SELECTION, ...(configRecord.agentSelection as Record<string, unknown>) };
129
- mergedAgent.agentDefaults = { model: DEFAULT_AGENT_MODEL, ...(configRecord.agentDefaults as Record<string, unknown>) };
130
- mergedAgent.complexityCategories = (configRecord.complexityCategories as string[]) ?? [...DEFAULT_COMPLEXITY_CATEGORIES];
131
- mergedAgent.sanitization = { ...DEFAULT_SANITIZATION, ...(configRecord.sanitization as Record<string, unknown>) };
132
- mergedAgent.reviewIterations = { ...DEFAULT_REVIEW_ITERATIONS, ...agentReview.reviewIterations };
133
-
134
- const modelsRaw = (config as Record<string, unknown>).models ?? {};
148
+
149
+ const nestedAgentSelection = asRecord(agentReviewRawRecord?.agentSelection) as AgentSelectionConfig | undefined;
150
+ const topLevelAgentSelection = asRecord(raw.agentSelection) as AgentSelectionConfig | undefined;
151
+ mergedAgent.agentSelection = {
152
+ ...DEFAULT_AGENT_SELECTION,
153
+ ...nestedAgentSelection,
154
+ ...topLevelAgentSelection,
155
+ };
156
+
157
+ const nestedAgentDefaults = asRecord(agentReviewRawRecord?.agentDefaults) as { model?: string } | undefined;
158
+ const topLevelAgentDefaults = asRecord(raw.agentDefaults) as { model?: string } | undefined;
159
+ mergedAgent.agentDefaults = {
160
+ model: DEFAULT_AGENT_MODEL,
161
+ ...nestedAgentDefaults,
162
+ ...topLevelAgentDefaults,
163
+ };
164
+
165
+ const nestedComplexityCategories = asStringArray(agentReviewRawRecord?.complexityCategories);
166
+ const topLevelComplexityCategories = asStringArray(raw.complexityCategories);
167
+ mergedAgent.complexityCategories = topLevelComplexityCategories
168
+ ?? nestedComplexityCategories
169
+ ?? [...DEFAULT_COMPLEXITY_CATEGORIES];
170
+
171
+ const nestedSanitization = asRecord(agentReviewRawRecord?.sanitization);
172
+ const topLevelSanitization = asRecord(raw.sanitization);
173
+ mergedAgent.sanitization = {
174
+ ...DEFAULT_SANITIZATION,
175
+ ...nestedSanitization,
176
+ ...topLevelSanitization,
177
+ };
178
+
179
+ const nestedFallbackByComplexity = asRecord(agentReviewRawRecord?.fallbackByComplexity) as Record<string, number> | undefined;
180
+ const topLevelFallbackByComplexity = asRecord(raw.fallbackByComplexity) as Record<string, number> | undefined;
181
+ if (nestedFallbackByComplexity || topLevelFallbackByComplexity) {
182
+ mergedAgent.fallbackByComplexity = {
183
+ ...(nestedFallbackByComplexity ?? {}),
184
+ ...(topLevelFallbackByComplexity ?? {}),
185
+ };
186
+ }
187
+
188
+ const nestedMandatoryAgents = agentReviewRawRecord?.mandatoryAgents as AgentReviewSettings["mandatoryAgents"] | undefined;
189
+ const topLevelMandatoryAgents = raw.mandatoryAgents as AgentReviewSettings["mandatoryAgents"] | undefined;
190
+ if (nestedMandatoryAgents !== undefined || topLevelMandatoryAgents !== undefined) {
191
+ mergedAgent.mandatoryAgents = topLevelMandatoryAgents ?? nestedMandatoryAgents;
192
+ }
193
+
194
+ const nestedPreflight = asRecord(agentReviewRawRecord?.preflight);
195
+ const topLevelPreflight = asRecord(raw.preflight);
196
+ if (nestedPreflight || topLevelPreflight) {
197
+ mergedAgent.preflight = {
198
+ ...(nestedPreflight ?? {}),
199
+ ...(topLevelPreflight ?? {}),
200
+ };
201
+ }
202
+
203
+ const nestedReviewIterations = asRecord(agentReviewRawRecord?.reviewIterations) as Record<string, number> | undefined;
204
+ const topLevelReviewIterations = asRecord(raw.reviewIterations) as Record<string, number> | undefined;
205
+ mergedAgent.reviewIterations = {
206
+ ...DEFAULT_REVIEW_ITERATIONS,
207
+ ...(nestedReviewIterations ?? {}),
208
+ ...(topLevelReviewIterations ?? {}),
209
+ };
210
+
211
+ const modelsRaw = (raw.models ?? {}) as Record<string, unknown>;
135
212
  return { planReview: mergedPlan, agentReview: mergedAgent, models: modelsRaw };
136
213
  }
137
214
 
@@ -139,7 +216,7 @@ export function loadSettings(projDir: string): Record<string, unknown> {
139
216
  // Models Config
140
217
  // ---------------------------------------------------------------------------
141
218
 
142
- export function loadModelsConfig(settings: Record<string, unknown>): ModelsConfig {
219
+ export function loadModelsConfig(settings: LoadedSettings): ModelsConfig {
143
220
  const raw = settings.models as Record<string, unknown> | undefined;
144
221
  if (!raw?.providers || typeof raw.providers !== "object") {
145
222
  return DEFAULT_MODELS_CONFIG;
@@ -150,7 +227,6 @@ export function loadModelsConfig(settings: Record<string, unknown>): ModelsConfi
150
227
  providers[name] = {
151
228
  enabled: c.enabled !== false,
152
229
  models: Array.isArray(c.models) ? (c.models as string[]).filter(Boolean) : [],
153
- ...(typeof c.reasoningEffort === "string" && { reasoningEffort: c.reasoningEffort }),
154
230
  };
155
231
  }
156
232
  return { providers };
@@ -162,7 +238,7 @@ export function loadModelsConfig(settings: Record<string, unknown>): ModelsConfi
162
238
 
163
239
  export function loadAgentLibrary(
164
240
  projDir: string,
165
- settings?: Record<string, unknown>,
241
+ settings?: AgentReviewSettings,
166
242
  ): AgentConfig[] {
167
243
  const agentsData = aggregateAgents(path.join(projDir, "_cc-native", "plan-review", "agents", "plan-review"));
168
244
  const defaultModel = settings?.agentDefaults?.model ?? DEFAULT_AGENT_MODEL;
@@ -178,6 +178,7 @@ export function getIterationState(
178
178
  passStreaks: {},
179
179
  lastPlanHash: "",
180
180
  lastPlanPath: "",
181
+ sessionId: "",
181
182
  };
182
183
  }
183
184
 
@@ -128,6 +128,7 @@ export interface ReviewDecisionResult {
128
128
  export interface OrchestratorConfig {
129
129
  enabled: boolean;
130
130
  model: string;
131
+ provider?: string;
131
132
  timeout: number;
132
133
  }
133
134
 
@@ -142,6 +143,66 @@ export interface ModelsConfig {
142
143
  providers: Record<string, ProviderConfig>;
143
144
  }
144
145
 
146
+ // ---------------------------------------------------------------------------
147
+ // Settings Interfaces (typed output of loadSettings())
148
+ // ---------------------------------------------------------------------------
149
+
150
+ /** Agent selection count range for a single complexity tier */
151
+ export interface AgentSelectionRange {
152
+ min: number;
153
+ max: number;
154
+ }
155
+
156
+ /** Agent selection configuration (per-tier ranges + fallback) */
157
+ export interface AgentSelectionConfig {
158
+ simple?: AgentSelectionRange;
159
+ medium?: AgentSelectionRange;
160
+ high?: AgentSelectionRange;
161
+ fallbackCount?: number;
162
+ }
163
+
164
+ /** Preflight health-check configuration */
165
+ export interface PreflightSettings {
166
+ enabled?: boolean;
167
+ timeoutMs?: number;
168
+ }
169
+
170
+ /** Plan review section of merged settings (the "planReview" key) */
171
+ export interface PlanReviewSettings {
172
+ enabled?: boolean;
173
+ reviewers?: {
174
+ codex?: { enabled?: boolean; model?: string; timeout?: number };
175
+ gemini?: { enabled?: boolean; model?: string; timeout?: number };
176
+ };
177
+ display?: Partial<DisplaySettings>;
178
+ }
179
+
180
+ /** Agent review section of merged settings (the "agentReview" key) */
181
+ export interface AgentReviewSettings {
182
+ enabled?: boolean;
183
+ timeout?: number;
184
+ orchestrator?: OrchestratorConfig;
185
+ legacyMode?: boolean;
186
+ highIssueThreshold?: number;
187
+ maxIssuesPerAgent?: number;
188
+ mandatoryAgents?: string[] | Record<string, string[]>;
189
+ agentSelection?: AgentSelectionConfig;
190
+ agentDefaults?: { model?: string };
191
+ complexityCategories?: string[];
192
+ sanitization?: { maxSessionIdLength?: number; maxTitleLength?: number };
193
+ reviewIterations?: Record<string, number>;
194
+ display?: Partial<DisplaySettings>;
195
+ preflight?: PreflightSettings;
196
+ fallbackByComplexity?: Record<string, number>;
197
+ }
198
+
199
+ /** Top-level settings object returned by loadSettings() */
200
+ export interface LoadedSettings {
201
+ planReview: PlanReviewSettings;
202
+ agentReview: AgentReviewSettings;
203
+ models: Record<string, unknown>;
204
+ }
205
+
145
206
  // ---------------------------------------------------------------------------
146
207
  // State Interfaces
147
208
  // ---------------------------------------------------------------------------
@@ -7,6 +7,7 @@ import { logDebug, logInfo, logWarn } from "../../../_shared/lib-ts/base/logger.
7
7
  import { findExecutable } from "../../../_shared/lib-ts/base/subprocess-utils.js";
8
8
  import type {
9
9
  AgentConfig,
10
+ AgentReviewSettings,
10
11
  ModelsConfig,
11
12
  OrchestratorResult,
12
13
  AgentSelectionResult,
@@ -89,7 +90,7 @@ export function assignModelsToAgents(
89
90
  }
90
91
  return [name, config] as [string, typeof config];
91
92
  })
92
- .filter((entry): entry is [string, { enabled: boolean; models: string[]; reasoningEffort?: string }] => entry !== null);
93
+ .filter((entry): entry is [string, { enabled: boolean; models: string[] }] => entry !== null);
93
94
 
94
95
  // Sort by provider priority (codex first)
95
96
  enabledProviders.sort((a, b) => {
@@ -110,7 +111,7 @@ export function assignModelsToAgents(
110
111
  return agents.map(agent => {
111
112
  const modelIdx = Math.floor(Math.random() * providerConfig.models.length);
112
113
  const model = providerConfig.models[modelIdx] ?? providerConfig.models[0] ?? agent.model;
113
- return { ...agent, provider: providerName, model, reasoningEffort: providerConfig.reasoningEffort };
114
+ return { ...agent, provider: providerName, model };
114
115
  });
115
116
  }
116
117
 
@@ -122,7 +123,7 @@ export interface AgentSelectionInput {
122
123
  enabledAgents: AgentConfig[];
123
124
  orchResult: OrchestratorResult | null;
124
125
  mandatoryConfig: unknown;
125
- agentSettings: Record<string, unknown>;
126
+ agentSettings: AgentReviewSettings;
126
127
  legacyMode: boolean;
127
128
  }
128
129
 
@@ -168,7 +169,7 @@ export function selectAgents(input: AgentSelectionInput): AgentSelectionResult {
168
169
  }
169
170
 
170
171
  // Enforce minimum agent count
171
- const fallbackByComplexity = agentSettings.fallbackByComplexity ?? { simple: 0, medium: 2, high: 4 };
172
+ const fallbackByComplexity: Record<string, number> = agentSettings.fallbackByComplexity ?? { simple: 0, medium: 2, high: 4 };
172
173
  const minAdditional = fallbackByComplexity[detectedComplexity] ?? 5;
173
174
  if (orchSelected.length < minAdditional && nonMandatory.length > 0) {
174
175
  const remaining = nonMandatory.filter(a => !orchSelected.includes(a));
@@ -6,7 +6,7 @@
6
6
 
7
7
  import { OrchestratorClaudeAgent } from "./reviewers/providers/orchestrator-claude-agent.js";
8
8
  import { logInfo, logWarn } from "../../../_shared/lib-ts/base/logger.js";
9
- import type { AgentConfig, OrchestratorConfig, OrchestratorResult } from "../../lib-ts/types.js";
9
+ import type { AgentConfig, AgentReviewSettings, OrchestratorConfig, OrchestratorResult } from "../../lib-ts/types.js";
10
10
 
11
11
  // Re-export for backward compatibility (moved to reviewers/schemas.ts)
12
12
  export { buildOrchestratorSchema } from "./reviewers/schemas.js";
@@ -23,7 +23,7 @@ export async function runOrchestrator(
23
23
  plan: string,
24
24
  agentLibrary: AgentConfig[],
25
25
  config: OrchestratorConfig,
26
- settings: Record<string, unknown>,
26
+ settings: AgentReviewSettings,
27
27
  mandatoryNames?: Set<string>,
28
28
  ): Promise<OrchestratorResult> {
29
29
  logInfo("orchestrator", "Starting plan analysis...");
@@ -34,7 +34,7 @@ export async function runOrchestrator(
34
34
  const orchestratorAgent: AgentConfig = {
35
35
  name: "orchestrator",
36
36
  model: config.model,
37
- provider: "claude",
37
+ provider: config.provider ?? "claude",
38
38
  focus: "plan analysis and agent selection",
39
39
  categories: [],
40
40
  description: "Plan orchestrator",
@@ -58,7 +58,7 @@ export async function runOrchestrator(
58
58
  } catch (error) {
59
59
  logWarn("orchestrator", `Unexpected error: ${error}`);
60
60
  const nonMandatory = agentLibrary.filter((a) => !mandatory.has(a.name));
61
- const fallbackCount = ((settings.agentSelection as Record<string, unknown>)?.fallbackCount as number) ?? 2;
61
+ const fallbackCount = settings.agentSelection?.fallbackCount ?? 2;
62
62
  return {
63
63
  complexity: "medium",
64
64
  category: "code",
@@ -43,7 +43,10 @@ import { loadSettings, loadModelsConfig, loadAgentLibrary, DEFAULT_ORCHESTRATOR
43
43
  import { DEFAULT_REVIEW_ITERATIONS, loadIterationState, saveIterationState } from "../../lib-ts/state.js";
44
44
  import type {
45
45
  AgentConfig,
46
+ AgentReviewSettings,
47
+ LoadedSettings,
46
48
  OrchestratorConfig,
49
+ PlanReviewSettings,
47
50
  ReviewerResult,
48
51
  CombinedReviewResult,
49
52
  OrchestratorResult,
@@ -66,7 +69,7 @@ function getActiveContextForReview(sessionId: string, projectRoot: string): Cont
66
69
  return ctx;
67
70
  }
68
71
  const allActive = getAllContexts("active", projectRoot);
69
- const planning = allActive.filter(c => c.mode === "active" || c.mode === "has_plan");
72
+ const planning = allActive.filter(c => c.mode === "active" || c.mode === "has_staged_work");
70
73
  if (planning.length === 1) {
71
74
  logInfo(HOOK, `Found single planning context: ${planning[0]!.id}`);
72
75
  return planning[0]!;
@@ -89,9 +92,9 @@ export async function runReviewPipeline(input: PipelineInput): Promise<PipelineR
89
92
  const { sessionId, base, aiwcliDir, transcriptPath, payload } = input;
90
93
 
91
94
  // 1. Load settings
92
- const settings = loadSettings(aiwcliDir);
93
- const planSettings = settings.planReview ?? {};
94
- const agentSettings = settings.agentReview ?? {};
95
+ const settings: LoadedSettings = loadSettings(aiwcliDir);
96
+ const planSettings: PlanReviewSettings = settings.planReview;
97
+ const agentSettings: AgentReviewSettings = settings.agentReview;
95
98
 
96
99
  const planReviewEnabled = planSettings.enabled ?? true;
97
100
  const agentReviewEnabled = agentSettings.enabled ?? true;
@@ -162,8 +165,8 @@ export async function runReviewPipeline(input: PipelineInput): Promise<PipelineR
162
165
  // 5. Questions gate
163
166
  if (!wasPlanQuestionsAgentAsked(sessionId, base)) {
164
167
  logInfo(HOOK, "Questions gate: plan-questions agent has not run yet, running now");
165
- const timeout = typeof agentSettings.timeout === "number" ? agentSettings.timeout : 120;
166
- const questionsResult = await runPlanQuestions(plan, aiwcliDir, timeout, undefined, sessionId);
168
+ const questionsTimeout = agentSettings.timeout ?? 120;
169
+ const questionsResult = await runPlanQuestions(plan, aiwcliDir, questionsTimeout, undefined, sessionId);
167
170
 
168
171
  markQuestionsAsked(sessionId, base, "agent");
169
172
 
@@ -227,12 +230,12 @@ export async function runReviewPipeline(input: PipelineInput): Promise<PipelineR
227
230
  let detectedComplexity = "medium";
228
231
 
229
232
  // Preflight: validate provider+model combos before committing agents or orchestrator
230
- const preflightEnabled = (agentSettings.preflight as Record<string, unknown>)?.enabled ?? true;
233
+ const preflightEnabled = agentSettings.preflight?.enabled ?? true;
231
234
  let preflightAvailable: Map<string, Set<string>> | undefined;
232
235
 
233
236
  if (preflightEnabled && agentReviewEnabled) {
234
237
  logInfo(HOOK, "=== PREFLIGHT: Checking provider availability ===");
235
- const preflightTimeoutMs = (agentSettings.preflight as Record<string, unknown>)?.timeoutMs as number | undefined;
238
+ const preflightTimeoutMs = agentSettings.preflight?.timeoutMs;
236
239
  const modelsConfig = loadModelsConfig(settings);
237
240
  const preflightReport = await runPreflight(modelsConfig, preflightTimeoutMs);
238
241
 
@@ -257,13 +260,14 @@ export async function runReviewPipeline(input: PipelineInput): Promise<PipelineR
257
260
  const agentLibrary = agentReviewEnabled ? loadAgentLibrary(aiwcliDir, agentSettings) : [];
258
261
  const originalAgentCount = agentLibrary.length;
259
262
  const enabledAgents = agentLibrary.filter(a => !graduatedSet.has(a.name));
260
- const timeout = typeof agentSettings.timeout === "number" ? agentSettings.timeout : 120;
263
+ const timeout = agentSettings.timeout ?? 120;
261
264
  const legacyMode = agentSettings.legacyMode === true;
262
265
 
263
266
  const orchSettings = agentSettings.orchestrator ?? DEFAULT_ORCHESTRATOR;
264
267
  const orchestratorConfig: OrchestratorConfig = {
265
268
  enabled: (orchSettings.enabled ?? true) && agentReviewEnabled,
266
269
  model: orchSettings.model ?? "haiku",
270
+ provider: orchSettings.provider,
267
271
  timeout: orchSettings.timeout ?? 30,
268
272
  };
269
273
 
@@ -278,7 +282,7 @@ export async function runReviewPipeline(input: PipelineInput): Promise<PipelineR
278
282
 
279
283
  if (orchestratorConfig.enabled && enabledAgents.length > 0 && !legacyMode) {
280
284
  // Guard orchestrator against preflight failures (always uses claude provider)
281
- const orchProvider = "claude";
285
+ const orchProvider = orchestratorConfig.provider ?? "claude";
282
286
  const orchModel = orchestratorConfig.model;
283
287
  const orchPassed = !preflightAvailable ||
284
288
  (preflightAvailable.has(orchProvider) && preflightAvailable.get(orchProvider)!.has(orchModel));
@@ -395,8 +399,7 @@ export async function runReviewPipeline(input: PipelineInput): Promise<PipelineR
395
399
  }
396
400
 
397
401
  // 10. Issue truncation + verdict override
398
- const maxIssuesPerAgent = typeof agentSettings.maxIssuesPerAgent === "number"
399
- ? agentSettings.maxIssuesPerAgent : 3;
402
+ const maxIssuesPerAgent = agentSettings.maxIssuesPerAgent ?? 3;
400
403
  truncateAgentIssues(agentResults, maxIssuesPerAgent);
401
404
 
402
405
  const passEligible = computePassEligible(agentResults);
@@ -404,7 +407,7 @@ export async function runReviewPipeline(input: PipelineInput): Promise<PipelineR
404
407
  logInfo(HOOK, `Pass-eligible agents this iteration: ${passEligible.join(", ")}`);
405
408
  }
406
409
 
407
- const highIssueThreshold = typeof agentSettings.highIssueThreshold === "number" ? agentSettings.highIssueThreshold : 3;
410
+ const highIssueThreshold = agentSettings.highIssueThreshold ?? 3;
408
411
  overrideVerdictsByThreshold(agentResults, highIssueThreshold);
409
412
 
410
413
  // PHASE 4: Generate Output