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.
- package/dist/templates/_shared/.claude/skills/codex/SKILL.md +19 -26
- package/dist/templates/_shared/.codex/workflows/codex.md +11 -0
- package/dist/templates/_shared/lib-ts/agent-exec/index.ts +2 -0
- package/dist/templates/_shared/lib-ts/agent-exec/structured-output.ts +166 -0
- package/dist/templates/_shared/lib-ts/base/cli-args.ts +4 -0
- package/dist/templates/_shared/lib-ts/base/state-io.ts +1 -1
- package/dist/templates/_shared/lib-ts/base/subprocess-utils.ts +4 -3
- package/dist/templates/_shared/lib-ts/base/tmux-driver.ts +70 -3
- package/dist/templates/_shared/lib-ts/base/tmux-pane-placement.ts +70 -0
- package/dist/templates/_shared/lib-ts/context/context-store.ts +3 -0
- package/dist/templates/_shared/scripts/resolve-run.ts +1 -1
- package/dist/templates/_shared/scripts/status_line.ts +36 -19
- package/dist/templates/_shared/skills/codex/CLAUDE.md +77 -0
- package/dist/templates/_shared/skills/codex/SKILL.md +71 -0
- package/dist/templates/_shared/skills/codex/lib/codex-watcher.ts +226 -0
- package/dist/templates/_shared/skills/{prompt-codex → codex}/scripts/launch-codex.ts +175 -8
- package/dist/templates/_shared/skills/codex/scripts/watch-codex.ts +42 -0
- package/dist/templates/cc-native/_cc-native/lib-ts/cli-output-parser.ts +9 -133
- package/dist/templates/cc-native/_cc-native/lib-ts/settings.ts +118 -42
- package/dist/templates/cc-native/_cc-native/lib-ts/state.ts +1 -0
- package/dist/templates/cc-native/_cc-native/lib-ts/types.ts +61 -0
- package/dist/templates/cc-native/_cc-native/plan-review/lib/agent-selection.ts +5 -4
- package/dist/templates/cc-native/_cc-native/plan-review/lib/orchestrator.ts +4 -4
- package/dist/templates/cc-native/_cc-native/plan-review/lib/review-pipeline.ts +16 -13
- package/dist/templates/cc-native/_cc-native/plan-review/lib/reviewers/providers/orchestrator-claude-agent.ts +54 -23
- package/oclif.manifest.json +1 -1
- package/package.json +1 -1
- package/dist/templates/_shared/.claude/skills/codex/prompt.md +0 -10
- package/dist/templates/_shared/skills/prompt-codex/CLAUDE.md +0 -46
|
@@ -1,144 +1,20 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
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 {
|
|
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
|
|
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
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
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
|
-
|
|
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
|
|
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:
|
|
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
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
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)
|
|
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
|
|
112
|
-
const mergedPlan = { ...
|
|
113
|
-
if (
|
|
114
|
-
mergedPlan.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
|
|
120
|
-
const
|
|
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
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
mergedAgent.
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
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:
|
|
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?:
|
|
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;
|
|
@@ -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[]
|
|
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
|
|
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:
|
|
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:
|
|
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 =
|
|
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 === "
|
|
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
|
|
166
|
-
const questionsResult = await runPlanQuestions(plan, aiwcliDir,
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
410
|
+
const highIssueThreshold = agentSettings.highIssueThreshold ?? 3;
|
|
408
411
|
overrideVerdictsByThreshold(agentResults, highIssueThreshold);
|
|
409
412
|
|
|
410
413
|
// PHASE 4: Generate Output
|