@thispointon/kondi-chat 0.1.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/LICENSE +21 -0
- package/README.md +556 -0
- package/bin/kondi-chat +56 -0
- package/bin/kondi-chat.js +72 -0
- package/package.json +55 -0
- package/scripts/demo.tape +49 -0
- package/scripts/postinstall.cjs +103 -0
- package/src/audit/analytics.ts +261 -0
- package/src/audit/ledger.ts +253 -0
- package/src/audit/telemetry.ts +165 -0
- package/src/cli/backend.ts +675 -0
- package/src/cli/commands.ts +419 -0
- package/src/cli/help.ts +182 -0
- package/src/cli/submit-helpers.ts +159 -0
- package/src/cli/submit.ts +539 -0
- package/src/cli/wizard.ts +121 -0
- package/src/context/bootstrap.ts +138 -0
- package/src/context/budget.ts +100 -0
- package/src/context/manager.ts +666 -0
- package/src/context/memory.ts +160 -0
- package/src/context/preflight.ts +176 -0
- package/src/context/project-brain.ts +101 -0
- package/src/context/receipts.ts +108 -0
- package/src/context/skills.ts +154 -0
- package/src/context/symbol-index.ts +240 -0
- package/src/council/profiles.ts +137 -0
- package/src/council/tool.ts +138 -0
- package/src/council-engine/cli/council-artifacts.ts +230 -0
- package/src/council-engine/cli/council-config.ts +178 -0
- package/src/council-engine/cli/council-session-export.ts +116 -0
- package/src/council-engine/cli/kondi.ts +98 -0
- package/src/council-engine/cli/llm-caller.ts +229 -0
- package/src/council-engine/cli/localStorage-shim.ts +119 -0
- package/src/council-engine/cli/node-platform.ts +68 -0
- package/src/council-engine/cli/run-council.ts +481 -0
- package/src/council-engine/cli/run-pipeline.ts +772 -0
- package/src/council-engine/cli/session-export.ts +153 -0
- package/src/council-engine/configs/councils/analysis.json +101 -0
- package/src/council-engine/configs/councils/code-planning.json +86 -0
- package/src/council-engine/configs/councils/coding.json +89 -0
- package/src/council-engine/configs/councils/debate.json +97 -0
- package/src/council-engine/configs/councils/solo-claude.json +34 -0
- package/src/council-engine/configs/councils/solo-gpt.json +34 -0
- package/src/council-engine/council/coding-orchestrator.ts +1205 -0
- package/src/council-engine/council/context-bootstrap.ts +147 -0
- package/src/council-engine/council/context-inspection.ts +42 -0
- package/src/council-engine/council/context-store.ts +763 -0
- package/src/council-engine/council/deliberation-orchestrator.ts +2762 -0
- package/src/council-engine/council/factory.ts +164 -0
- package/src/council-engine/council/index.ts +201 -0
- package/src/council-engine/council/ledger-store.ts +438 -0
- package/src/council-engine/council/prompts.ts +1689 -0
- package/src/council-engine/council/storage-cleanup.ts +164 -0
- package/src/council-engine/council/store.ts +1110 -0
- package/src/council-engine/council/synthesis.ts +291 -0
- package/src/council-engine/council/types.ts +845 -0
- package/src/council-engine/council/validation.ts +613 -0
- package/src/council-engine/pipeline/build-detect.ts +73 -0
- package/src/council-engine/pipeline/executor.ts +1048 -0
- package/src/council-engine/pipeline/index.ts +9 -0
- package/src/council-engine/pipeline/install-detect.ts +84 -0
- package/src/council-engine/pipeline/memory-store.ts +182 -0
- package/src/council-engine/pipeline/output-parsers.ts +146 -0
- package/src/council-engine/pipeline/run-output.ts +149 -0
- package/src/council-engine/pipeline/session-import.ts +177 -0
- package/src/council-engine/pipeline/store.ts +753 -0
- package/src/council-engine/pipeline/test-detect.ts +82 -0
- package/src/council-engine/pipeline/types.ts +401 -0
- package/src/council-engine/services/deliberationSummary.ts +114 -0
- package/src/council-engine/tsconfig.json +16 -0
- package/src/council-engine/types/mcp.ts +122 -0
- package/src/council-engine/utils/filterTools.ts +73 -0
- package/src/engine/apply.ts +238 -0
- package/src/engine/checkpoints.ts +237 -0
- package/src/engine/consultants.ts +347 -0
- package/src/engine/diff.ts +171 -0
- package/src/engine/errors.ts +102 -0
- package/src/engine/git-tools.ts +246 -0
- package/src/engine/hooks.ts +181 -0
- package/src/engine/loop-guard.ts +155 -0
- package/src/engine/permissions.ts +293 -0
- package/src/engine/pipeline.ts +376 -0
- package/src/engine/sub-agents.ts +133 -0
- package/src/engine/task-card.ts +185 -0
- package/src/engine/task-router.ts +256 -0
- package/src/engine/task-store.ts +86 -0
- package/src/engine/tools.ts +783 -0
- package/src/engine/verify.ts +111 -0
- package/src/mcp/client.ts +225 -0
- package/src/mcp/config.ts +120 -0
- package/src/mcp/tool-manager.ts +192 -0
- package/src/mcp/types.ts +61 -0
- package/src/providers/llm-caller.ts +943 -0
- package/src/providers/rate-limiter.ts +238 -0
- package/src/router/NOTES.md +28 -0
- package/src/router/collector.ts +474 -0
- package/src/router/embeddings.ts +286 -0
- package/src/router/index.ts +299 -0
- package/src/router/intent-router.ts +225 -0
- package/src/router/nn-router.ts +205 -0
- package/src/router/profiles.ts +309 -0
- package/src/router/registry.ts +565 -0
- package/src/router/rules.ts +274 -0
- package/src/router/train.py +408 -0
- package/src/session/store.ts +211 -0
- package/src/test-utils/mock-llm.ts +39 -0
- package/src/types.ts +322 -0
- package/src/web/manager.ts +311 -0
|
@@ -0,0 +1,481 @@
|
|
|
1
|
+
#!/usr/bin/env -S npx tsx
|
|
2
|
+
/**
|
|
3
|
+
* CLI Council Runner
|
|
4
|
+
*
|
|
5
|
+
* Usage:
|
|
6
|
+
* kondi council <council.json> [options]
|
|
7
|
+
* kondi council --task "Your task here" [options]
|
|
8
|
+
* kondi council --config <council-config.json> [options]
|
|
9
|
+
* kondi council [options] (auto-discovers council.json in cwd)
|
|
10
|
+
*
|
|
11
|
+
* Runs a standalone council deliberation. Supports all council types:
|
|
12
|
+
* council, coding, analysis, review, agent, enrich, code_planning.
|
|
13
|
+
*
|
|
14
|
+
* Examples:
|
|
15
|
+
* kondi council --task "Review this codebase for security issues" --working-dir ./myapp --type review
|
|
16
|
+
* kondi council --task "Build a REST API" --working-dir ./project --type coding
|
|
17
|
+
* kondi council --config ~/configs/security-review.json --working-dir ./target
|
|
18
|
+
* kondi council exported-council.json
|
|
19
|
+
* kondi council --task "Analyze logs" --output json --json-stdout --quiet
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
// ── localStorage shim MUST be imported first ──
|
|
23
|
+
import { storage } from './localStorage-shim';
|
|
24
|
+
|
|
25
|
+
import fs from 'node:fs';
|
|
26
|
+
import path from 'node:path';
|
|
27
|
+
import { councilStore } from '../council/store';
|
|
28
|
+
import { createCouncilFromSetup } from '../council/factory';
|
|
29
|
+
import { DeliberationOrchestrator } from '../council/deliberation-orchestrator';
|
|
30
|
+
import { CodingOrchestrator } from '../council/coding-orchestrator';
|
|
31
|
+
import { ledgerStore } from '../council/ledger-store';
|
|
32
|
+
import { buildAbbreviatedSummary } from '../services/deliberationSummary';
|
|
33
|
+
import { callLLM, DEFAULT_MODELS, type CallerResult } from './llm-caller';
|
|
34
|
+
import { loadCouncilConfig, mergeConfigWithArgs } from './council-config';
|
|
35
|
+
import { writeCouncilArtifacts, buildJsonResult } from './council-artifacts';
|
|
36
|
+
import { exportCouncilSession } from './council-session-export';
|
|
37
|
+
import type { CouncilCliArgs, OutputFormat } from './council-config';
|
|
38
|
+
import type { Council, Persona, CouncilStepType } from '../council/types';
|
|
39
|
+
|
|
40
|
+
// ── ANSI colors ──
|
|
41
|
+
const C = {
|
|
42
|
+
reset: '\x1b[0m',
|
|
43
|
+
bold: '\x1b[1m',
|
|
44
|
+
dim: '\x1b[2m',
|
|
45
|
+
green: '\x1b[32m',
|
|
46
|
+
yellow: '\x1b[33m',
|
|
47
|
+
red: '\x1b[31m',
|
|
48
|
+
cyan: '\x1b[36m',
|
|
49
|
+
magenta: '\x1b[35m',
|
|
50
|
+
blue: '\x1b[34m',
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
let quietMode = false;
|
|
54
|
+
|
|
55
|
+
function log(color: string, prefix: string, msg: string) {
|
|
56
|
+
if (quietMode) return;
|
|
57
|
+
const ts = new Date().toLocaleTimeString();
|
|
58
|
+
console.log(`${C.dim}${ts}${C.reset} ${color}${C.bold}[${prefix}]${C.reset} ${msg}`);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// ── Parse CLI args ──
|
|
62
|
+
function parseArgs(): CouncilCliArgs {
|
|
63
|
+
const args = process.argv.slice(2);
|
|
64
|
+
const parsed: CouncilCliArgs = {};
|
|
65
|
+
|
|
66
|
+
for (let i = 0; i < args.length; i++) {
|
|
67
|
+
const arg = args[i];
|
|
68
|
+
const next = args[i + 1];
|
|
69
|
+
|
|
70
|
+
switch (arg) {
|
|
71
|
+
case '--working-dir':
|
|
72
|
+
parsed.workingDir = next ? path.resolve(args[++i]) : undefined;
|
|
73
|
+
break;
|
|
74
|
+
case '--type':
|
|
75
|
+
parsed.type = next as CouncilStepType;
|
|
76
|
+
i++;
|
|
77
|
+
break;
|
|
78
|
+
case '--task':
|
|
79
|
+
parsed.task = args[++i];
|
|
80
|
+
break;
|
|
81
|
+
case '--model':
|
|
82
|
+
parsed.model = args[++i];
|
|
83
|
+
break;
|
|
84
|
+
case '--provider':
|
|
85
|
+
parsed.provider = args[++i];
|
|
86
|
+
break;
|
|
87
|
+
case '--config':
|
|
88
|
+
parsed.configPath = args[++i];
|
|
89
|
+
break;
|
|
90
|
+
case '--output':
|
|
91
|
+
parsed.outputFormat = next as OutputFormat;
|
|
92
|
+
i++;
|
|
93
|
+
break;
|
|
94
|
+
case '--output-dir':
|
|
95
|
+
parsed.outputDir = path.resolve(args[++i]);
|
|
96
|
+
break;
|
|
97
|
+
case '--no-session-export':
|
|
98
|
+
parsed.noSessionExport = true;
|
|
99
|
+
break;
|
|
100
|
+
case '--dry-run':
|
|
101
|
+
parsed.dryRun = true;
|
|
102
|
+
break;
|
|
103
|
+
case '--quiet':
|
|
104
|
+
parsed.quiet = true;
|
|
105
|
+
break;
|
|
106
|
+
case '--json-stdout':
|
|
107
|
+
parsed.jsonStdout = true;
|
|
108
|
+
break;
|
|
109
|
+
case '--help':
|
|
110
|
+
case '-h':
|
|
111
|
+
printHelp();
|
|
112
|
+
process.exit(0);
|
|
113
|
+
break;
|
|
114
|
+
default:
|
|
115
|
+
if (!arg.startsWith('--') && !parsed.councilJsonPath) {
|
|
116
|
+
parsed.councilJsonPath = path.resolve(arg);
|
|
117
|
+
}
|
|
118
|
+
break;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
return parsed;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function printHelp() {
|
|
126
|
+
console.log(`
|
|
127
|
+
${C.bold}Kondi Council Runner${C.reset}
|
|
128
|
+
|
|
129
|
+
Usage:
|
|
130
|
+
kondi council <council.json> [options]
|
|
131
|
+
kondi council --task "Your task" [options]
|
|
132
|
+
kondi council --config <config.json> [options]
|
|
133
|
+
kondi council [options] (auto-discovers council.json)
|
|
134
|
+
|
|
135
|
+
Options:
|
|
136
|
+
--config <path> Path to council config file (JSON)
|
|
137
|
+
--task "..." Task/problem for the council to work on
|
|
138
|
+
--type <type> Council type: council, coding, review, analysis, agent (default: council)
|
|
139
|
+
--working-dir <path> Working directory for file operations
|
|
140
|
+
--model <model> Override model for all personas
|
|
141
|
+
--provider <provider> Override provider for all personas
|
|
142
|
+
--output <format> Output format: full, abbreviated, output-only, json, none (default: full)
|
|
143
|
+
--output-dir <path> Override artifact output directory
|
|
144
|
+
--no-session-export Skip session export to ~/.local/share/kondi/sessions/
|
|
145
|
+
--dry-run Print council structure without running
|
|
146
|
+
--quiet Suppress progress output (for automation)
|
|
147
|
+
--json-stdout Print structured JSON result to stdout
|
|
148
|
+
--help Show this help
|
|
149
|
+
|
|
150
|
+
Config auto-discovery:
|
|
151
|
+
If no --config, --task, or council.json is provided, searches for:
|
|
152
|
+
1. ./council.json (current directory)
|
|
153
|
+
2. ~/.config/kondi/council.json
|
|
154
|
+
|
|
155
|
+
Output formats:
|
|
156
|
+
full deliberation.md + decision.md + output.md
|
|
157
|
+
abbreviated summary.md only
|
|
158
|
+
output-only output.md only (worker's final deliverable)
|
|
159
|
+
json council-result.json (structured data)
|
|
160
|
+
none no file output
|
|
161
|
+
`);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// ── Print council structure (for --dry-run) ──
|
|
165
|
+
function printCouncilStructure(council: Council, councilType: string, task: string, workingDir?: string) {
|
|
166
|
+
console.log(`\n${C.bold}${C.cyan}Council: ${council.name}${C.reset}`);
|
|
167
|
+
console.log(`${C.dim}Type: ${councilType}${C.reset}`);
|
|
168
|
+
console.log(`${C.dim}Task: ${task.slice(0, 300)}${task.length > 300 ? '...' : ''}${C.reset}`);
|
|
169
|
+
if (workingDir) console.log(`${C.dim}Working dir: ${workingDir}${C.reset}`);
|
|
170
|
+
|
|
171
|
+
console.log(`\n${C.bold}Personas:${C.reset}`);
|
|
172
|
+
for (const p of council.personas) {
|
|
173
|
+
const role = p.preferredDeliberationRole || 'unassigned';
|
|
174
|
+
const roleColor = role === 'manager' ? C.blue :
|
|
175
|
+
role === 'worker' ? C.yellow :
|
|
176
|
+
role === 'reviewer' ? C.cyan : C.green;
|
|
177
|
+
console.log(` ${roleColor}${p.avatar || ''} ${p.name}${C.reset} (${role}) — ${p.model} [${p.provider}]`);
|
|
178
|
+
if (p.predisposition?.domain) console.log(` ${C.dim}Domain: ${p.predisposition.domain}${C.reset}`);
|
|
179
|
+
if (p.predisposition?.traits?.length) console.log(` ${C.dim}Traits: ${p.predisposition.traits.join(', ')}${C.reset}`);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
if (council.deliberation) {
|
|
183
|
+
console.log(`\n${C.bold}Orchestration:${C.reset}`);
|
|
184
|
+
console.log(` ${C.dim}Max rounds: ${council.deliberation.maxRounds}${C.reset}`);
|
|
185
|
+
console.log(` ${C.dim}Max revisions: ${council.deliberation.maxRevisions}${C.reset}`);
|
|
186
|
+
console.log(` ${C.dim}Context budget: ${council.deliberation.contextTokenBudget} tokens${C.reset}`);
|
|
187
|
+
}
|
|
188
|
+
console.log();
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// ── Main ──
|
|
192
|
+
async function main() {
|
|
193
|
+
const args = parseArgs();
|
|
194
|
+
quietMode = args.quiet || false;
|
|
195
|
+
|
|
196
|
+
// ── Resolve council source ──
|
|
197
|
+
let council: Council;
|
|
198
|
+
let councilType: CouncilStepType = args.type || 'council';
|
|
199
|
+
let task: string;
|
|
200
|
+
let workingDir = args.workingDir;
|
|
201
|
+
let outputFormat: OutputFormat = args.outputFormat || 'full';
|
|
202
|
+
let outputDir = args.outputDir;
|
|
203
|
+
let sessionExport = !args.noSessionExport;
|
|
204
|
+
|
|
205
|
+
if (args.councilJsonPath) {
|
|
206
|
+
// Path 1: Explicit council JSON export
|
|
207
|
+
if (!fs.existsSync(args.councilJsonPath)) {
|
|
208
|
+
console.error(`File not found: ${args.councilJsonPath}`);
|
|
209
|
+
process.exit(1);
|
|
210
|
+
}
|
|
211
|
+
let raw: any;
|
|
212
|
+
try {
|
|
213
|
+
raw = JSON.parse(fs.readFileSync(args.councilJsonPath, 'utf-8'));
|
|
214
|
+
} catch (e) {
|
|
215
|
+
console.error(`Invalid JSON in council file: ${args.councilJsonPath}`);
|
|
216
|
+
process.exit(1);
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
if (raw.personas && raw.deliberation) {
|
|
220
|
+
council = raw as Council;
|
|
221
|
+
councilStore.create({
|
|
222
|
+
name: council.name,
|
|
223
|
+
topic: council.topic || council.name,
|
|
224
|
+
personas: council.personas,
|
|
225
|
+
orchestration: council.orchestration,
|
|
226
|
+
deliberation: council.deliberation,
|
|
227
|
+
});
|
|
228
|
+
council = councilStore.getAll().at(-1)!;
|
|
229
|
+
} else {
|
|
230
|
+
console.error('Invalid council JSON format — expected personas and deliberation fields');
|
|
231
|
+
process.exit(1);
|
|
232
|
+
}
|
|
233
|
+
task = args.task || council.deliberation?.savedProblem || council.topic;
|
|
234
|
+
|
|
235
|
+
} else {
|
|
236
|
+
// Path 2: Config file (explicit or auto-discovered)
|
|
237
|
+
const configFile = loadCouncilConfig(args.configPath);
|
|
238
|
+
|
|
239
|
+
if (configFile) {
|
|
240
|
+
const resolved = mergeConfigWithArgs(configFile, args);
|
|
241
|
+
councilType = resolved.type;
|
|
242
|
+
task = resolved.task;
|
|
243
|
+
outputFormat = resolved.outputFormat;
|
|
244
|
+
outputDir = resolved.outputDir;
|
|
245
|
+
sessionExport = resolved.sessionExport;
|
|
246
|
+
|
|
247
|
+
const defaultProvider = resolved.provider || 'anthropic-api';
|
|
248
|
+
|
|
249
|
+
council = createCouncilFromSetup({
|
|
250
|
+
name: configFile.name,
|
|
251
|
+
topic: task,
|
|
252
|
+
personas: configFile.personas.map(p => {
|
|
253
|
+
const prov = p.provider || defaultProvider;
|
|
254
|
+
return {
|
|
255
|
+
name: p.name,
|
|
256
|
+
role: p.role,
|
|
257
|
+
provider: prov,
|
|
258
|
+
model: p.model || resolved.model || DEFAULT_MODELS[prov] || '',
|
|
259
|
+
avatar: p.avatar,
|
|
260
|
+
systemPrompt: p.systemPrompt || `You are ${p.name}.`,
|
|
261
|
+
traits: p.traits || [],
|
|
262
|
+
stance: p.stance,
|
|
263
|
+
domain: p.domain,
|
|
264
|
+
temperature: p.temperature,
|
|
265
|
+
suppressPersona: p.suppressPersona,
|
|
266
|
+
toolAccess: p.toolAccess,
|
|
267
|
+
}}),
|
|
268
|
+
workingDirectory: workingDir,
|
|
269
|
+
stepType: councilType,
|
|
270
|
+
contextTokenBudget: configFile.orchestration?.contextTokenBudget || 80000,
|
|
271
|
+
summarizeAfterRound: configFile.orchestration?.summarizeAfterRound || 2,
|
|
272
|
+
maxRounds: configFile.orchestration?.maxRounds,
|
|
273
|
+
maxRevisions: configFile.orchestration?.maxRevisions,
|
|
274
|
+
consultantExecution: configFile.orchestration?.consultantExecution,
|
|
275
|
+
evolveContext: configFile.orchestration?.evolveContext,
|
|
276
|
+
bootstrapContext: configFile.orchestration?.bootstrapContext,
|
|
277
|
+
expectedOutput: configFile.expectedOutput,
|
|
278
|
+
decisionCriteria: configFile.decisionCriteria,
|
|
279
|
+
testCommand: configFile.testCommand,
|
|
280
|
+
maxDebugCycles: configFile.maxDebugCycles,
|
|
281
|
+
maxReviewCycles: configFile.maxReviewCycles,
|
|
282
|
+
});
|
|
283
|
+
|
|
284
|
+
} else if (args.task) {
|
|
285
|
+
// Path 3: Inline task with default personas
|
|
286
|
+
task = args.task;
|
|
287
|
+
const defaultProvider = args.provider || 'anthropic-api';
|
|
288
|
+
const defaultModel = args.model || 'claude-sonnet-4-5-20250929';
|
|
289
|
+
|
|
290
|
+
council = createCouncilFromSetup({
|
|
291
|
+
name: task.slice(0, 60),
|
|
292
|
+
topic: task,
|
|
293
|
+
personas: [
|
|
294
|
+
{ name: 'Manager', role: 'manager', provider: defaultProvider, model: defaultModel, avatar: '👔', systemPrompt: 'You are the manager.', traits: ['analytical'], suppressPersona: true },
|
|
295
|
+
{ name: 'Worker', role: 'worker', provider: defaultProvider, model: defaultModel, avatar: '🔧', systemPrompt: 'You are the worker.', traits: ['precise'], temperature: 0.5, suppressPersona: true },
|
|
296
|
+
{ name: 'Consultant', role: 'consultant', provider: defaultProvider, model: defaultModel, avatar: '🌟', systemPrompt: 'You are a consultant.', traits: ['creative'], stance: 'advocate' },
|
|
297
|
+
],
|
|
298
|
+
workingDirectory: workingDir,
|
|
299
|
+
stepType: councilType,
|
|
300
|
+
contextTokenBudget: 80000,
|
|
301
|
+
summarizeAfterRound: 2,
|
|
302
|
+
});
|
|
303
|
+
|
|
304
|
+
} else {
|
|
305
|
+
console.error('No council source provided. Use --task, --config, or pass a council.json file.');
|
|
306
|
+
console.error('Run with --help for usage information.');
|
|
307
|
+
process.exit(1);
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
// Apply overrides
|
|
312
|
+
if (args.model) {
|
|
313
|
+
council.personas = council.personas.map(p => ({ ...p, model: args.model! }));
|
|
314
|
+
}
|
|
315
|
+
if (args.provider) {
|
|
316
|
+
council.personas = council.personas.map(p => ({ ...p, provider: args.provider! }));
|
|
317
|
+
}
|
|
318
|
+
if (workingDir && council.deliberation) {
|
|
319
|
+
council.deliberation.workingDirectory = workingDir;
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
const rawProblem = task!;
|
|
323
|
+
const effectiveWorkingDir = workingDir || process.cwd();
|
|
324
|
+
|
|
325
|
+
// ── Dry run ──
|
|
326
|
+
if (args.dryRun) {
|
|
327
|
+
printCouncilStructure(council, councilType, rawProblem, workingDir);
|
|
328
|
+
console.log(`${C.dim}Output format: ${outputFormat}${C.reset}`);
|
|
329
|
+
console.log(`${C.dim}Session export: ${sessionExport}${C.reset}`);
|
|
330
|
+
console.log(`${C.dim}--dry-run: exiting without executing.${C.reset}`);
|
|
331
|
+
process.exit(0);
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
// ── Log startup ──
|
|
335
|
+
log(C.green, 'Council', `"${council.name}" — ${councilType} mode`);
|
|
336
|
+
log(C.green, 'Council', `Personas: ${council.personas.map(p => p.name).join(', ')}`);
|
|
337
|
+
if (workingDir) log(C.green, 'Council', `Working dir: ${workingDir}`);
|
|
338
|
+
log(C.green, 'Council', `Task: ${rawProblem.slice(0, 200)}${rawProblem.length > 200 ? '...' : ''}`);
|
|
339
|
+
log(C.green, 'Council', `Output: ${outputFormat}`);
|
|
340
|
+
if (!quietMode) console.log('');
|
|
341
|
+
|
|
342
|
+
// Council calls are stateless one-shot — no session resumption.
|
|
343
|
+
// Each persona call gets a fresh context. See CLAUDE.md rule #2.
|
|
344
|
+
const startTime = Date.now();
|
|
345
|
+
|
|
346
|
+
const invokeAgent = async (invocation: any, persona: Persona) => {
|
|
347
|
+
log(C.cyan, persona.name, `Thinking... (${persona.model})`);
|
|
348
|
+
|
|
349
|
+
const result = await callLLM({
|
|
350
|
+
provider: persona.provider || 'anthropic-api',
|
|
351
|
+
systemPrompt: invocation.systemPrompt,
|
|
352
|
+
userMessage: invocation.userMessage,
|
|
353
|
+
model: persona.model,
|
|
354
|
+
workingDir: invocation.workingDirectory || workingDir,
|
|
355
|
+
skipTools: invocation.skipTools,
|
|
356
|
+
allowedTools: invocation.allowedTools,
|
|
357
|
+
timeoutMs: invocation.timeoutMs || 900_000,
|
|
358
|
+
});
|
|
359
|
+
|
|
360
|
+
log(C.cyan, persona.name, `Done (${result.tokensUsed} tokens, ${(result.latencyMs / 1000).toFixed(1)}s)`);
|
|
361
|
+
return { ...result, sessionId: result.sessionId };
|
|
362
|
+
};
|
|
363
|
+
|
|
364
|
+
const callbacks = {
|
|
365
|
+
invokeAgent,
|
|
366
|
+
onPhaseChange: (from: string, to: string) => log(C.yellow, 'Phase', `${from} → ${to}`),
|
|
367
|
+
onError: (err: Error, ctx: string) => log(C.red, 'Error', `${ctx}: ${err.message}`),
|
|
368
|
+
onAgentThinkingStart: (_persona: Persona) => {},
|
|
369
|
+
onAgentThinkingEnd: (_persona: Persona) => {},
|
|
370
|
+
};
|
|
371
|
+
|
|
372
|
+
// ── Execute ──
|
|
373
|
+
let status: 'completed' | 'failed' = 'completed';
|
|
374
|
+
let errorMsg: string | undefined;
|
|
375
|
+
|
|
376
|
+
try {
|
|
377
|
+
if (councilType === 'coding') {
|
|
378
|
+
const orchestrator = new CodingOrchestrator({
|
|
379
|
+
...callbacks,
|
|
380
|
+
runCommand: async (cmd: string, cwd?: string) => {
|
|
381
|
+
const { execFileSync } = await import('node:child_process');
|
|
382
|
+
try {
|
|
383
|
+
const stdout = execFileSync('/bin/sh', ['-c', cmd], { cwd: cwd || workingDir, encoding: 'utf-8', timeout: 120_000 });
|
|
384
|
+
return { stdout, stderr: '', exitCode: 0 };
|
|
385
|
+
} catch (err: any) {
|
|
386
|
+
return { stdout: err.stdout || '', stderr: err.stderr || err.message, exitCode: err.status || 1 };
|
|
387
|
+
}
|
|
388
|
+
},
|
|
389
|
+
readFile: async (filePath: string) => {
|
|
390
|
+
const resolved = path.resolve(filePath);
|
|
391
|
+
const base = path.resolve(effectiveWorkingDir);
|
|
392
|
+
if (!resolved.startsWith(base + path.sep) && resolved !== base) {
|
|
393
|
+
throw new Error(`Path traversal blocked: ${filePath} escapes working directory`);
|
|
394
|
+
}
|
|
395
|
+
return fs.readFileSync(filePath, 'utf-8');
|
|
396
|
+
},
|
|
397
|
+
});
|
|
398
|
+
await orchestrator.runCodingWorkflow(council, rawProblem);
|
|
399
|
+
} else {
|
|
400
|
+
const deliberator = new DeliberationOrchestrator(callbacks);
|
|
401
|
+
await deliberator.runFullDeliberation(council, rawProblem);
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
log(C.green, 'Done', 'Council completed');
|
|
405
|
+
} catch (error) {
|
|
406
|
+
status = 'failed';
|
|
407
|
+
errorMsg = error instanceof Error ? error.message : String(error);
|
|
408
|
+
log(C.red, 'Fatal', errorMsg);
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
const durationMs = Date.now() - startTime;
|
|
412
|
+
|
|
413
|
+
// ── Print summary ──
|
|
414
|
+
if (!quietMode) {
|
|
415
|
+
const completed = councilStore.get(council.id);
|
|
416
|
+
if (completed) {
|
|
417
|
+
const summary = buildAbbreviatedSummary(completed);
|
|
418
|
+
console.log('\n' + C.bold + '═══ Summary ═══' + C.reset);
|
|
419
|
+
console.log(summary);
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
const entries = ledgerStore.getAll(council.id);
|
|
423
|
+
const totalTokens = entries.reduce((s, e) => s + (e.tokensUsed || 0), 0);
|
|
424
|
+
console.log(`\n${C.dim}Entries: ${entries.length} | Tokens: ${totalTokens.toLocaleString()} | Time: ${(durationMs / 1000).toFixed(0)}s${C.reset}`);
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
// ── Write artifacts ──
|
|
428
|
+
let artifactPaths: string[] = [];
|
|
429
|
+
try {
|
|
430
|
+
artifactPaths = writeCouncilArtifacts(council, {
|
|
431
|
+
format: outputFormat,
|
|
432
|
+
outputDir,
|
|
433
|
+
workingDir: effectiveWorkingDir,
|
|
434
|
+
});
|
|
435
|
+
if (artifactPaths.length > 0 && !quietMode) {
|
|
436
|
+
log(C.cyan, 'Artifacts', `Written ${artifactPaths.length} file(s):`);
|
|
437
|
+
for (const p of artifactPaths) {
|
|
438
|
+
console.log(` ${C.dim}${p}${C.reset}`);
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
} catch (err) {
|
|
442
|
+
console.error(`${C.red}Failed to write artifacts:${C.reset}`, err);
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
// ── Session export ──
|
|
446
|
+
if (sessionExport) {
|
|
447
|
+
try {
|
|
448
|
+
const sessionPath = exportCouncilSession(council.id, storage, {
|
|
449
|
+
status,
|
|
450
|
+
startedAt: new Date(startTime).toISOString(),
|
|
451
|
+
completedAt: new Date().toISOString(),
|
|
452
|
+
durationMs,
|
|
453
|
+
workingDirectory: effectiveWorkingDir,
|
|
454
|
+
});
|
|
455
|
+
if (sessionPath && !quietMode) {
|
|
456
|
+
log(C.cyan, 'Session', `Exported to: ${sessionPath}`);
|
|
457
|
+
}
|
|
458
|
+
} catch (err) {
|
|
459
|
+
console.error(`${C.red}Failed to export session:${C.reset}`, err);
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
// ── JSON stdout ──
|
|
464
|
+
if (args.jsonStdout) {
|
|
465
|
+
const jsonResult = buildJsonResult(council, artifactPaths, {
|
|
466
|
+
status,
|
|
467
|
+
durationMs,
|
|
468
|
+
error: errorMsg,
|
|
469
|
+
});
|
|
470
|
+
console.log(JSON.stringify(jsonResult));
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
if (status === 'failed') {
|
|
474
|
+
process.exit(1);
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
main().catch((err) => {
|
|
479
|
+
console.error(`${C.red}Unhandled error:${C.reset}`, err);
|
|
480
|
+
process.exit(1);
|
|
481
|
+
});
|