llm-cli-council 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/marketplace.json +15 -0
- package/.claude-plugin/plugin.json +61 -0
- package/LICENSE +21 -0
- package/README.md +495 -0
- package/bin/install.js +299 -0
- package/config/providers.json +124 -0
- package/lib/platform-utils.sh +353 -0
- package/package.json +46 -0
- package/prompts/chairman-synthesis.md +153 -0
- package/prompts/code-review.md +134 -0
- package/prompts/plan-review.md +106 -0
- package/rules/council-orchestration.md +205 -0
- package/rules/synthesis-strategy.md +246 -0
- package/rules/triggers.md +182 -0
- package/skills/llm-cli-council.md +122 -0
- package/skills/review-code.md +225 -0
- package/skills/review-plan.md +191 -0
- package/skills/setup.md +164 -0
- package/skills/status.md +220 -0
- package/skills/uninstall.md +179 -0
package/bin/install.js
ADDED
|
@@ -0,0 +1,299 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
const fs = require('fs');
|
|
5
|
+
const path = require('path');
|
|
6
|
+
const os = require('os');
|
|
7
|
+
const { spawnSync, spawn } = require('child_process');
|
|
8
|
+
|
|
9
|
+
// ─── Colors ───────────────────────────────────────────────────────────────────
|
|
10
|
+
const c = {
|
|
11
|
+
reset: '\x1b[0m',
|
|
12
|
+
bold: '\x1b[1m',
|
|
13
|
+
dim: '\x1b[2m',
|
|
14
|
+
cyan: '\x1b[36m',
|
|
15
|
+
green: '\x1b[32m',
|
|
16
|
+
yellow: '\x1b[33m',
|
|
17
|
+
red: '\x1b[31m',
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
// ─── Banner ───────────────────────────────────────────────────────────────────
|
|
21
|
+
function printBanner(version) {
|
|
22
|
+
console.log('');
|
|
23
|
+
console.log(c.cyan + ' ██████╗ ██████╗ ██╗ ██╗███╗ ██╗ ██████╗ ██╗██╗ ' + c.reset);
|
|
24
|
+
console.log(c.cyan + ' ██╔════╝ ██╔═══██╗██║ ██║████╗ ██║██╔════╝ ██║██║ ' + c.reset);
|
|
25
|
+
console.log(c.cyan + ' ██║ ██║ ██║██║ ██║██╔██╗██║██║ ██║██║ ' + c.reset);
|
|
26
|
+
console.log(c.cyan + ' ██║ ██║ ██║██║ ██║██║╚████║██║ ██║██║ ' + c.reset);
|
|
27
|
+
console.log(c.cyan + ' ╚██████╗ ╚██████╔╝╚██████╔╝██║ ╚███║╚██████╗ ██║███████╗' + c.reset);
|
|
28
|
+
console.log(c.cyan + ' ╚═════╝ ╚═════╝ ╚═════╝ ╚═╝ ╚══╝ ╚═════╝ ╚═╝╚══════╝' + c.reset);
|
|
29
|
+
console.log('');
|
|
30
|
+
console.log(' llm-cli-council ' + c.dim + 'v' + version + c.reset);
|
|
31
|
+
console.log(' Orchestrate multiple LLMs as a review council in Claude Code.');
|
|
32
|
+
console.log('');
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// ─── Section headers & line items ─────────────────────────────────────────────
|
|
36
|
+
function printPhase(n, title) {
|
|
37
|
+
console.log(' ' + c.yellow + 'Phase ' + n + c.reset + ' ' + title);
|
|
38
|
+
console.log(' ' + c.dim + '─'.repeat(38) + c.reset);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function printItem(status, label, detail) {
|
|
42
|
+
const sym = status === 'ok' ? c.green + '✓' + c.reset
|
|
43
|
+
: status === 'fail' ? c.red + '✗' + c.reset
|
|
44
|
+
: status === 'warn' ? c.yellow + '⚠' + c.reset
|
|
45
|
+
: c.dim + '·' + c.reset;
|
|
46
|
+
const det = detail ? ' ' + c.dim + detail + c.reset : '';
|
|
47
|
+
console.log(' ' + sym + ' ' + label + det);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// ─── Claude directory detection ───────────────────────────────────────────────
|
|
51
|
+
function detectClaudeDir() {
|
|
52
|
+
if (process.env.CLAUDE_DIR) return process.env.CLAUDE_DIR;
|
|
53
|
+
if (process.env.CLAUDE_SKILLS_DIR) return path.dirname(process.env.CLAUDE_SKILLS_DIR);
|
|
54
|
+
return path.join(os.homedir(), '.claude');
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function ensureDirExists(dirPath) {
|
|
58
|
+
fs.mkdirSync(dirPath, { recursive: true });
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// ─── File copy ────────────────────────────────────────────────────────────────
|
|
62
|
+
const FILE_MAP = [
|
|
63
|
+
{ src: 'skills', dest: 'skills', type: 'dir' },
|
|
64
|
+
{ src: 'lib', dest: 'lib', type: 'dir' },
|
|
65
|
+
{ src: 'prompts', dest: 'prompts', type: 'dir' },
|
|
66
|
+
{ src: 'rules', dest: 'rules', type: 'dir' },
|
|
67
|
+
{ src: 'config/providers.json', dest: 'config/providers.json', type: 'file' },
|
|
68
|
+
];
|
|
69
|
+
|
|
70
|
+
const CHMOD_EXEC_FILES = ['lib/platform-utils.sh'];
|
|
71
|
+
|
|
72
|
+
function copyFiles(claudeDir, dryRun) {
|
|
73
|
+
const pkgRoot = path.join(__dirname, '..');
|
|
74
|
+
const results = [];
|
|
75
|
+
|
|
76
|
+
for (const entry of FILE_MAP) {
|
|
77
|
+
const srcPath = path.join(pkgRoot, entry.src);
|
|
78
|
+
const destPath = path.join(claudeDir, entry.dest);
|
|
79
|
+
|
|
80
|
+
if (!dryRun) {
|
|
81
|
+
ensureDirExists(path.dirname(destPath));
|
|
82
|
+
if (entry.type === 'dir') {
|
|
83
|
+
fs.cpSync(srcPath, destPath, { recursive: true });
|
|
84
|
+
} else {
|
|
85
|
+
fs.copyFileSync(srcPath, destPath);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
results.push({ src: entry.src, dest: destPath });
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
return results;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function chmodExec(claudeDir, dryRun) {
|
|
96
|
+
if (dryRun) return;
|
|
97
|
+
for (const relPath of CHMOD_EXEC_FILES) {
|
|
98
|
+
const fullPath = path.join(claudeDir, relPath);
|
|
99
|
+
if (fs.existsSync(fullPath)) fs.chmodSync(fullPath, 0o755);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// ─── Provider detection ───────────────────────────────────────────────────────
|
|
104
|
+
const PROVIDER_META = {
|
|
105
|
+
claude: { label: 'Anthropic Claude', cmd: 'claude', args: ['-p', 'Reply with one word: ok'], timeout: 8000 },
|
|
106
|
+
copilot: { label: 'GitHub Copilot', cmd: 'copilot', args: ['--prompt', 'Reply with one word: ok'], timeout: 8000 },
|
|
107
|
+
codex: { label: 'OpenAI Codex', cmd: 'codex', args: ['Reply with one word: ok'], timeout: 8000 },
|
|
108
|
+
gemini: { label: 'Google Gemini', cmd: 'gemini', args: ['Reply with one word: ok'], timeout: 8000 },
|
|
109
|
+
ollama: { label: 'Local (Ollama)', cmd: 'ollama', args: ['list'], timeout: 3000 },
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
function probeProvider(name, meta) {
|
|
113
|
+
return new Promise((resolve) => {
|
|
114
|
+
// Step 1: check binary exists
|
|
115
|
+
const which = spawnSync('which', [meta.cmd], { encoding: 'utf8' });
|
|
116
|
+
if (which.status !== 0) {
|
|
117
|
+
resolve({ name, active: false, reason: 'not found' });
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// Step 2: real probe with timeout
|
|
122
|
+
const proc = spawn(meta.cmd, meta.args, {
|
|
123
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
124
|
+
env: process.env,
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
const timer = setTimeout(() => {
|
|
128
|
+
proc.kill('SIGKILL');
|
|
129
|
+
resolve({ name, active: false, reason: 'timeout' });
|
|
130
|
+
}, meta.timeout);
|
|
131
|
+
|
|
132
|
+
proc.on('close', (code) => {
|
|
133
|
+
clearTimeout(timer);
|
|
134
|
+
resolve({ name, active: code === 0, reason: code === 0 ? 'active' : 'auth error' });
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
proc.on('error', () => {
|
|
138
|
+
clearTimeout(timer);
|
|
139
|
+
resolve({ name, active: false, reason: 'error' });
|
|
140
|
+
});
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
async function detectProviders() {
|
|
145
|
+
return Promise.all(
|
|
146
|
+
Object.entries(PROVIDER_META).map(([name, meta]) => probeProvider(name, meta))
|
|
147
|
+
);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
function updateProvidersConfig(claudeDir, results) {
|
|
151
|
+
const configPath = path.join(claudeDir, 'config', 'providers.json');
|
|
152
|
+
if (!fs.existsSync(configPath)) return;
|
|
153
|
+
const config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
|
|
154
|
+
for (const r of results) {
|
|
155
|
+
if (config.providers[r.name]) {
|
|
156
|
+
config.providers[r.name].active = r.active;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
fs.writeFileSync(configPath, JSON.stringify(config, null, 2) + '\n');
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// ─── CLI interface ────────────────────────────────────────────────────────────
|
|
163
|
+
function parseArgs(argv) {
|
|
164
|
+
return {
|
|
165
|
+
dryRun: argv.includes('--dry-run'),
|
|
166
|
+
yes: argv.includes('--yes') || argv.includes('-y'),
|
|
167
|
+
help: argv.includes('--help') || argv.includes('-h'),
|
|
168
|
+
skipDetect: argv.includes('--skip-detect'),
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
function printHelp(version) {
|
|
173
|
+
console.log('');
|
|
174
|
+
console.log('llm-cli-council v' + version + ' — installer');
|
|
175
|
+
console.log('');
|
|
176
|
+
console.log(c.yellow + 'Usage:' + c.reset + ' npx llm-cli-council [options]');
|
|
177
|
+
console.log('');
|
|
178
|
+
console.log(c.yellow + 'Options:' + c.reset);
|
|
179
|
+
console.log(' ' + c.cyan + '--dry-run ' + c.reset + 'Show what would be installed without installing');
|
|
180
|
+
console.log(' ' + c.cyan + '--yes, -y ' + c.reset + 'Skip confirmation prompt');
|
|
181
|
+
console.log(' ' + c.cyan + '--skip-detect ' + c.reset + 'Skip LLM provider detection step');
|
|
182
|
+
console.log(' ' + c.cyan + '--help, -h ' + c.reset + 'Show this help message');
|
|
183
|
+
console.log('');
|
|
184
|
+
console.log(c.yellow + 'Environment:' + c.reset);
|
|
185
|
+
console.log(' ' + c.cyan + 'CLAUDE_DIR ' + c.reset + 'Override install base directory');
|
|
186
|
+
console.log(' ' + c.cyan + 'CLAUDE_SKILLS_DIR ' + c.reset + 'Override skills dir (parent used as base)');
|
|
187
|
+
console.log('');
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
function confirm(question) {
|
|
191
|
+
return new Promise((resolve) => {
|
|
192
|
+
if (!process.stdin.isTTY) { resolve(true); return; }
|
|
193
|
+
const rl = require('readline').createInterface({
|
|
194
|
+
input: process.stdin,
|
|
195
|
+
output: process.stdout,
|
|
196
|
+
});
|
|
197
|
+
rl.question(' ' + question + ' ' + c.dim + '[y/N]' + c.reset + ' ', (answer) => {
|
|
198
|
+
rl.close();
|
|
199
|
+
resolve(answer.toLowerCase() === 'y' || answer.toLowerCase() === 'yes');
|
|
200
|
+
});
|
|
201
|
+
});
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// ─── Main ─────────────────────────────────────────────────────────────────────
|
|
205
|
+
async function main() {
|
|
206
|
+
const args = parseArgs(process.argv.slice(2));
|
|
207
|
+
const pkg = require('../package.json');
|
|
208
|
+
|
|
209
|
+
if (args.help) {
|
|
210
|
+
printHelp(pkg.version);
|
|
211
|
+
process.exit(0);
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
const claudeDir = detectClaudeDir();
|
|
215
|
+
|
|
216
|
+
printBanner(pkg.version);
|
|
217
|
+
|
|
218
|
+
if (args.dryRun) {
|
|
219
|
+
console.log(' ' + c.yellow + 'DRY RUN' + c.reset + ' — no files will be written\n');
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
console.log(' Installing to: ' + c.cyan + claudeDir + c.reset + '\n');
|
|
223
|
+
|
|
224
|
+
// Preview what will be installed
|
|
225
|
+
for (const entry of FILE_MAP) {
|
|
226
|
+
const dest = path.join(claudeDir, entry.dest);
|
|
227
|
+
const homeDir = os.homedir();
|
|
228
|
+
const shortDest = dest.startsWith(homeDir) ? '~' + dest.slice(homeDir.length) : dest;
|
|
229
|
+
console.log(' ' + c.dim + entry.src.padEnd(24) + '→ ' + shortDest + c.reset);
|
|
230
|
+
}
|
|
231
|
+
console.log('');
|
|
232
|
+
|
|
233
|
+
// Confirm unless --yes or --dry-run
|
|
234
|
+
if (!args.yes && !args.dryRun) {
|
|
235
|
+
const ok = await confirm('Proceed with installation?');
|
|
236
|
+
if (!ok) {
|
|
237
|
+
console.log('\n Aborted.\n');
|
|
238
|
+
process.exit(0);
|
|
239
|
+
}
|
|
240
|
+
console.log('');
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
// ── Phase 1: Install files ──────────────────────────────────────────────────
|
|
244
|
+
printPhase(1, 'Install plugin files');
|
|
245
|
+
|
|
246
|
+
const installed = copyFiles(claudeDir, args.dryRun);
|
|
247
|
+
chmodExec(claudeDir, args.dryRun);
|
|
248
|
+
|
|
249
|
+
for (const f of installed) {
|
|
250
|
+
const homeDir = os.homedir();
|
|
251
|
+
const shortDest = f.dest.startsWith(homeDir) ? '~' + f.dest.slice(homeDir.length) : f.dest;
|
|
252
|
+
printItem('ok', f.src.padEnd(24) + c.dim + '→ ' + shortDest + c.reset);
|
|
253
|
+
}
|
|
254
|
+
console.log('');
|
|
255
|
+
|
|
256
|
+
if (args.dryRun) {
|
|
257
|
+
console.log(' Dry run complete. ' + installed.length + ' items would be installed.\n');
|
|
258
|
+
return;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
// ── Phase 2: Detect LLM providers ──────────────────────────────────────────
|
|
262
|
+
if (!args.skipDetect) {
|
|
263
|
+
printPhase(2, 'Detect LLM providers');
|
|
264
|
+
console.log(' ' + c.dim + 'Probing ' + Object.keys(PROVIDER_META).length + ' providers in parallel...' + c.reset + '\n');
|
|
265
|
+
|
|
266
|
+
const results = await detectProviders();
|
|
267
|
+
|
|
268
|
+
for (const r of results) {
|
|
269
|
+
const meta = PROVIDER_META[r.name];
|
|
270
|
+
const nameCol = r.name.padEnd(9);
|
|
271
|
+
const labelCol = meta.label.padEnd(18);
|
|
272
|
+
printItem(r.active ? 'ok' : 'fail', nameCol + ' ' + labelCol, r.reason);
|
|
273
|
+
}
|
|
274
|
+
console.log('');
|
|
275
|
+
|
|
276
|
+
const activeCount = results.filter(r => r.active).length;
|
|
277
|
+
updateProvidersConfig(claudeDir, results);
|
|
278
|
+
|
|
279
|
+
const configShort = ('~/.claude/config/providers.json');
|
|
280
|
+
console.log(' ' + c.dim + activeCount + ' active provider' + (activeCount !== 1 ? 's' : '') + ' written to ' + configShort + c.reset);
|
|
281
|
+
console.log('');
|
|
282
|
+
|
|
283
|
+
if (activeCount === 0) {
|
|
284
|
+
console.log(' ' + c.yellow + '⚠' + c.reset + ' No providers found. Install claude, copilot, gemini, codex, or ollama CLI.');
|
|
285
|
+
console.log('');
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
// ── Done ────────────────────────────────────────────────────────────────────
|
|
290
|
+
console.log(c.green + ' Done!' + c.reset + ' Open Claude Code and run ' + c.cyan + '/llm-cli-council' + c.reset + ' to get started.');
|
|
291
|
+
console.log('');
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
if (require.main === module) {
|
|
295
|
+
main().catch((err) => {
|
|
296
|
+
console.error('\n ' + c.red + '✗' + c.reset + ' Installation failed: ' + err.message + '\n');
|
|
297
|
+
process.exit(1);
|
|
298
|
+
});
|
|
299
|
+
}
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
{
|
|
2
|
+
"providers": {
|
|
3
|
+
"claude": {
|
|
4
|
+
"name": "Claude",
|
|
5
|
+
"description": "Anthropic's Claude CLI",
|
|
6
|
+
"command": "claude",
|
|
7
|
+
"invocation": "claude -p --print \"{prompt}\"",
|
|
8
|
+
"detectCommand": "which claude",
|
|
9
|
+
"authCheck": "claude --version",
|
|
10
|
+
"strengths": ["reasoning", "analysis", "synthesis", "planning"],
|
|
11
|
+
"taskRouting": {
|
|
12
|
+
"plan-review": 1,
|
|
13
|
+
"code-review": 3,
|
|
14
|
+
"architecture": 1
|
|
15
|
+
},
|
|
16
|
+
"timeout": 120000
|
|
17
|
+
},
|
|
18
|
+
"copilot": {
|
|
19
|
+
"name": "Copilot",
|
|
20
|
+
"description": "GitHub Copilot CLI",
|
|
21
|
+
"command": "copilot",
|
|
22
|
+
"invocation": "copilot --prompt \"{prompt}\" --allow-all-tools",
|
|
23
|
+
"detectCommand": "which copilot",
|
|
24
|
+
"authCheck": "copilot --version",
|
|
25
|
+
"strengths": ["code-generation", "code-review", "github-integration"],
|
|
26
|
+
"taskRouting": {
|
|
27
|
+
"plan-review": 4,
|
|
28
|
+
"code-review": 2,
|
|
29
|
+
"architecture": 4
|
|
30
|
+
},
|
|
31
|
+
"timeout": 90000
|
|
32
|
+
},
|
|
33
|
+
"codex": {
|
|
34
|
+
"name": "Codex",
|
|
35
|
+
"description": "OpenAI Codex CLI",
|
|
36
|
+
"command": "codex",
|
|
37
|
+
"invocation": "codex exec \"{prompt}\"",
|
|
38
|
+
"detectCommand": "which codex",
|
|
39
|
+
"authCheck": "codex --version",
|
|
40
|
+
"strengths": ["code-analysis", "debugging", "implementation"],
|
|
41
|
+
"taskRouting": {
|
|
42
|
+
"plan-review": 2,
|
|
43
|
+
"code-review": 1,
|
|
44
|
+
"architecture": 2
|
|
45
|
+
},
|
|
46
|
+
"timeout": 120000
|
|
47
|
+
},
|
|
48
|
+
"gemini": {
|
|
49
|
+
"name": "Gemini",
|
|
50
|
+
"description": "Google Gemini CLI",
|
|
51
|
+
"command": "gemini",
|
|
52
|
+
"invocation": "gemini \"{prompt}\"",
|
|
53
|
+
"detectCommand": "which gemini",
|
|
54
|
+
"authCheck": "gemini --version",
|
|
55
|
+
"strengths": ["multimodal", "research", "broad-knowledge"],
|
|
56
|
+
"taskRouting": {
|
|
57
|
+
"plan-review": 3,
|
|
58
|
+
"code-review": 4,
|
|
59
|
+
"architecture": 3
|
|
60
|
+
},
|
|
61
|
+
"timeout": 90000
|
|
62
|
+
},
|
|
63
|
+
"ollama": {
|
|
64
|
+
"name": "Ollama",
|
|
65
|
+
"description": "Local LLM runner (privacy mode)",
|
|
66
|
+
"command": "ollama",
|
|
67
|
+
"invocation": "ollama run gpt-oss:20b \"{prompt}\"",
|
|
68
|
+
"detectCommand": "which ollama",
|
|
69
|
+
"authCheck": "ollama list",
|
|
70
|
+
"strengths": ["privacy", "offline", "local-execution"],
|
|
71
|
+
"taskRouting": {
|
|
72
|
+
"plan-review": 5,
|
|
73
|
+
"code-review": 5,
|
|
74
|
+
"architecture": 5
|
|
75
|
+
},
|
|
76
|
+
"timeout": 180000,
|
|
77
|
+
"flags": {
|
|
78
|
+
"privacy": true,
|
|
79
|
+
"local": true
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
},
|
|
83
|
+
"modes": {
|
|
84
|
+
"quick": {
|
|
85
|
+
"description": "Fast feedback with 2 best-match providers",
|
|
86
|
+
"providerCount": 2,
|
|
87
|
+
"selectionStrategy": "task-routing"
|
|
88
|
+
},
|
|
89
|
+
"full": {
|
|
90
|
+
"description": "Comprehensive review with all available providers",
|
|
91
|
+
"providerCount": "all",
|
|
92
|
+
"selectionStrategy": "all-available"
|
|
93
|
+
},
|
|
94
|
+
"privacy": {
|
|
95
|
+
"description": "Local-only execution, no data leaves machine",
|
|
96
|
+
"providerCount": 1,
|
|
97
|
+
"selectionStrategy": "privacy-only",
|
|
98
|
+
"requiredFlags": ["privacy"]
|
|
99
|
+
}
|
|
100
|
+
},
|
|
101
|
+
"taskRouting": {
|
|
102
|
+
"plan-review": {
|
|
103
|
+
"description": "Review implementation plans",
|
|
104
|
+
"preferred": ["claude", "codex"],
|
|
105
|
+
"fallback": ["gemini"]
|
|
106
|
+
},
|
|
107
|
+
"code-review": {
|
|
108
|
+
"description": "Review code changes",
|
|
109
|
+
"preferred": ["codex", "copilot"],
|
|
110
|
+
"fallback": ["claude"]
|
|
111
|
+
},
|
|
112
|
+
"architecture": {
|
|
113
|
+
"description": "Architecture and design decisions",
|
|
114
|
+
"preferred": ["claude", "codex", "gemini"],
|
|
115
|
+
"fallback": ["copilot"]
|
|
116
|
+
}
|
|
117
|
+
},
|
|
118
|
+
"defaults": {
|
|
119
|
+
"mode": "quick",
|
|
120
|
+
"minQuorum": 2,
|
|
121
|
+
"timeoutMs": 120000,
|
|
122
|
+
"parallelExecution": true
|
|
123
|
+
}
|
|
124
|
+
}
|