@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,230 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CLI Council Artifact Writer
|
|
3
|
+
*
|
|
4
|
+
* Writes council deliberation artifacts to disk using node:fs.
|
|
5
|
+
* Supports 5 output formats: full, abbreviated, output-only, json, none.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import fs from 'node:fs';
|
|
9
|
+
import path from 'node:path';
|
|
10
|
+
import type { Council } from '../council/types';
|
|
11
|
+
import { buildFullDeliberation, buildAbbreviatedSummary } from '../services/deliberationSummary';
|
|
12
|
+
import { getDecision, getLatestOutput } from '../council/context-store';
|
|
13
|
+
import { getAllEntries } from '../council/ledger-store';
|
|
14
|
+
import type { OutputFormat } from './council-config';
|
|
15
|
+
|
|
16
|
+
// ============================================================================
|
|
17
|
+
// Helpers (mirrored from deliberationSaveService.ts, Tauri-free)
|
|
18
|
+
// ============================================================================
|
|
19
|
+
|
|
20
|
+
function sanitizeName(name: string): string {
|
|
21
|
+
return name
|
|
22
|
+
.toLowerCase()
|
|
23
|
+
.replace(/[^a-z0-9_-]/g, '_')
|
|
24
|
+
.replace(/_+/g, '_')
|
|
25
|
+
.replace(/^_|_$/g, '')
|
|
26
|
+
.slice(0, 50);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function timestampSlug(): string {
|
|
30
|
+
const d = new Date();
|
|
31
|
+
const pad = (n: number) => String(n).padStart(2, '0');
|
|
32
|
+
return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}_${pad(d.getHours())}-${pad(d.getMinutes())}-${pad(d.getSeconds())}`;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function resolveOutputDir(workingDir: string, councilName: string, overrideDir?: string): string {
|
|
36
|
+
if (overrideDir) {
|
|
37
|
+
return path.resolve(overrideDir);
|
|
38
|
+
}
|
|
39
|
+
const safeName = sanitizeName(councilName);
|
|
40
|
+
const ts = timestampSlug();
|
|
41
|
+
const base = workingDir.replace(/\/$/, '');
|
|
42
|
+
return `${base}/.kondi/outputs/${safeName}_${ts}`;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function ensureDir(dir: string): void {
|
|
46
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function writeFile(filePath: string, content: string): void {
|
|
50
|
+
ensureDir(path.dirname(filePath));
|
|
51
|
+
fs.writeFileSync(filePath, content, 'utf-8');
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// ============================================================================
|
|
55
|
+
// Format Writers
|
|
56
|
+
// ============================================================================
|
|
57
|
+
|
|
58
|
+
function writeFull(council: Council, outputDir: string): string[] {
|
|
59
|
+
const files: string[] = [];
|
|
60
|
+
|
|
61
|
+
// deliberation.md
|
|
62
|
+
const deliberationMd = buildFullDeliberation(council);
|
|
63
|
+
const deliberationPath = path.join(outputDir, 'deliberation.md');
|
|
64
|
+
writeFile(deliberationPath, deliberationMd);
|
|
65
|
+
files.push(deliberationPath);
|
|
66
|
+
|
|
67
|
+
// decision.md
|
|
68
|
+
const decision = getDecision(council.id);
|
|
69
|
+
let decisionMd = `# Decision\n\n`;
|
|
70
|
+
if (decision) {
|
|
71
|
+
decisionMd += decision.content;
|
|
72
|
+
if (decision.acceptanceCriteria) {
|
|
73
|
+
decisionMd += `\n\n## Acceptance Criteria\n\n${decision.acceptanceCriteria}`;
|
|
74
|
+
}
|
|
75
|
+
} else {
|
|
76
|
+
decisionMd += 'No decision recorded.';
|
|
77
|
+
}
|
|
78
|
+
const decisionPath = path.join(outputDir, 'decision.md');
|
|
79
|
+
writeFile(decisionPath, decisionMd);
|
|
80
|
+
files.push(decisionPath);
|
|
81
|
+
|
|
82
|
+
// output.md
|
|
83
|
+
const output = getLatestOutput(council.id);
|
|
84
|
+
let outputMd = `# Output\n\n`;
|
|
85
|
+
if (output) {
|
|
86
|
+
outputMd += output.content;
|
|
87
|
+
} else {
|
|
88
|
+
outputMd += 'No output recorded.';
|
|
89
|
+
}
|
|
90
|
+
const outputPath = path.join(outputDir, 'output.md');
|
|
91
|
+
writeFile(outputPath, outputMd);
|
|
92
|
+
files.push(outputPath);
|
|
93
|
+
|
|
94
|
+
return files;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function writeAbbreviated(council: Council, outputDir: string): string[] {
|
|
98
|
+
const summaryMd = buildAbbreviatedSummary(council);
|
|
99
|
+
const summaryPath = path.join(outputDir, 'summary.md');
|
|
100
|
+
writeFile(summaryPath, summaryMd);
|
|
101
|
+
return [summaryPath];
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function writeOutputOnly(council: Council, outputDir: string): string[] {
|
|
105
|
+
const output = getLatestOutput(council.id);
|
|
106
|
+
let outputMd = `# Output\n\n`;
|
|
107
|
+
if (output) {
|
|
108
|
+
outputMd += output.content;
|
|
109
|
+
} else {
|
|
110
|
+
outputMd += 'No output recorded.';
|
|
111
|
+
}
|
|
112
|
+
const outputPath = path.join(outputDir, 'output.md');
|
|
113
|
+
writeFile(outputPath, outputMd);
|
|
114
|
+
return [outputPath];
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function writeJson(council: Council, outputDir: string): string[] {
|
|
118
|
+
const entries = getAllEntries(council.id);
|
|
119
|
+
const decision = getDecision(council.id);
|
|
120
|
+
const output = getLatestOutput(council.id);
|
|
121
|
+
|
|
122
|
+
const result = {
|
|
123
|
+
council: {
|
|
124
|
+
id: council.id,
|
|
125
|
+
name: council.name,
|
|
126
|
+
topic: council.topic,
|
|
127
|
+
createdAt: council.createdAt,
|
|
128
|
+
status: council.status,
|
|
129
|
+
totalTokensUsed: council.totalTokensUsed,
|
|
130
|
+
personas: council.personas.map(p => ({
|
|
131
|
+
id: p.id,
|
|
132
|
+
name: p.name,
|
|
133
|
+
provider: p.provider,
|
|
134
|
+
model: p.model,
|
|
135
|
+
role: p.preferredDeliberationRole,
|
|
136
|
+
})),
|
|
137
|
+
},
|
|
138
|
+
deliberation: {
|
|
139
|
+
rounds: council.deliberationState?.currentRound ?? 0,
|
|
140
|
+
revisions: council.deliberationState?.revisionCount ?? 0,
|
|
141
|
+
phase: council.deliberationState?.currentPhase,
|
|
142
|
+
entryCount: entries.length,
|
|
143
|
+
},
|
|
144
|
+
entries: entries.map(e => ({
|
|
145
|
+
timestamp: e.timestamp,
|
|
146
|
+
author: e.authorPersonaId,
|
|
147
|
+
entryType: e.entryType,
|
|
148
|
+
round: e.roundNumber,
|
|
149
|
+
content: e.content,
|
|
150
|
+
tokensUsed: e.tokensUsed,
|
|
151
|
+
latencyMs: e.latencyMs,
|
|
152
|
+
})),
|
|
153
|
+
decision: decision ? {
|
|
154
|
+
content: decision.content,
|
|
155
|
+
acceptanceCriteria: decision.acceptanceCriteria,
|
|
156
|
+
} : null,
|
|
157
|
+
output: output ? {
|
|
158
|
+
content: output.content,
|
|
159
|
+
version: output.version,
|
|
160
|
+
} : null,
|
|
161
|
+
};
|
|
162
|
+
|
|
163
|
+
const jsonPath = path.join(outputDir, 'council-result.json');
|
|
164
|
+
writeFile(jsonPath, JSON.stringify(result, null, 2));
|
|
165
|
+
return [jsonPath];
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// ============================================================================
|
|
169
|
+
// Public API
|
|
170
|
+
// ============================================================================
|
|
171
|
+
|
|
172
|
+
export interface WriteArtifactsOpts {
|
|
173
|
+
format: OutputFormat;
|
|
174
|
+
outputDir?: string;
|
|
175
|
+
workingDir: string;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* Write council artifacts to disk.
|
|
180
|
+
* Returns array of written file paths (empty for 'none' format).
|
|
181
|
+
*/
|
|
182
|
+
export function writeCouncilArtifacts(council: Council, opts: WriteArtifactsOpts): string[] {
|
|
183
|
+
if (opts.format === 'none') return [];
|
|
184
|
+
|
|
185
|
+
const outputDir = resolveOutputDir(opts.workingDir, council.name, opts.outputDir);
|
|
186
|
+
|
|
187
|
+
switch (opts.format) {
|
|
188
|
+
case 'full':
|
|
189
|
+
return writeFull(council, outputDir);
|
|
190
|
+
case 'abbreviated':
|
|
191
|
+
return writeAbbreviated(council, outputDir);
|
|
192
|
+
case 'output-only':
|
|
193
|
+
return writeOutputOnly(council, outputDir);
|
|
194
|
+
case 'json':
|
|
195
|
+
return writeJson(council, outputDir);
|
|
196
|
+
default:
|
|
197
|
+
return writeFull(council, outputDir);
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* Build structured JSON result for --json-stdout output.
|
|
203
|
+
*/
|
|
204
|
+
export function buildJsonResult(council: Council, artifactPaths: string[], executionInfo: {
|
|
205
|
+
status: 'completed' | 'failed';
|
|
206
|
+
durationMs: number;
|
|
207
|
+
error?: string;
|
|
208
|
+
}): object {
|
|
209
|
+
const entries = getAllEntries(council.id);
|
|
210
|
+
const decision = getDecision(council.id);
|
|
211
|
+
const output = getLatestOutput(council.id);
|
|
212
|
+
|
|
213
|
+
return {
|
|
214
|
+
status: executionInfo.status,
|
|
215
|
+
durationMs: executionInfo.durationMs,
|
|
216
|
+
error: executionInfo.error,
|
|
217
|
+
council: {
|
|
218
|
+
id: council.id,
|
|
219
|
+
name: council.name,
|
|
220
|
+
topic: council.topic,
|
|
221
|
+
totalTokensUsed: council.totalTokensUsed,
|
|
222
|
+
rounds: council.deliberationState?.currentRound ?? 0,
|
|
223
|
+
revisions: council.deliberationState?.revisionCount ?? 0,
|
|
224
|
+
entryCount: entries.length,
|
|
225
|
+
},
|
|
226
|
+
decision: decision?.content ?? null,
|
|
227
|
+
output: output?.content ?? null,
|
|
228
|
+
artifacts: artifactPaths,
|
|
229
|
+
};
|
|
230
|
+
}
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CLI Council Config Loader
|
|
3
|
+
*
|
|
4
|
+
* Loads council configuration from JSON files with auto-discovery.
|
|
5
|
+
* Search order: explicit --config path, cwd/council.json, ~/.config/kondi/council.json
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import fs from 'node:fs';
|
|
9
|
+
import path from 'node:path';
|
|
10
|
+
import os from 'node:os';
|
|
11
|
+
import type { CouncilStepType } from '../pipeline/types';
|
|
12
|
+
|
|
13
|
+
// ============================================================================
|
|
14
|
+
// Config Schema
|
|
15
|
+
// ============================================================================
|
|
16
|
+
|
|
17
|
+
export type OutputFormat = 'full' | 'abbreviated' | 'output-only' | 'json' | 'none';
|
|
18
|
+
|
|
19
|
+
export interface CouncilPersonaConfig {
|
|
20
|
+
name: string;
|
|
21
|
+
role: 'manager' | 'worker' | 'consultant' | 'reviewer';
|
|
22
|
+
provider?: string;
|
|
23
|
+
model?: string;
|
|
24
|
+
avatar?: string;
|
|
25
|
+
systemPrompt?: string;
|
|
26
|
+
traits?: string[];
|
|
27
|
+
stance?: 'advocate' | 'critic' | 'neutral' | 'wildcard';
|
|
28
|
+
domain?: string;
|
|
29
|
+
temperature?: number;
|
|
30
|
+
suppressPersona?: boolean;
|
|
31
|
+
toolAccess?: 'full' | 'none';
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export interface CouncilConfigFile {
|
|
35
|
+
name: string;
|
|
36
|
+
task?: string;
|
|
37
|
+
type?: CouncilStepType;
|
|
38
|
+
personas: CouncilPersonaConfig[];
|
|
39
|
+
orchestration?: {
|
|
40
|
+
maxRounds?: number;
|
|
41
|
+
maxRevisions?: number;
|
|
42
|
+
contextTokenBudget?: number;
|
|
43
|
+
summarizeAfterRound?: number;
|
|
44
|
+
summaryMode?: string;
|
|
45
|
+
consultantExecution?: 'sequential' | 'parallel';
|
|
46
|
+
evolveContext?: boolean;
|
|
47
|
+
bootstrapContext?: boolean;
|
|
48
|
+
};
|
|
49
|
+
output?: {
|
|
50
|
+
format?: OutputFormat;
|
|
51
|
+
directory?: string;
|
|
52
|
+
sessionExport?: boolean;
|
|
53
|
+
};
|
|
54
|
+
expectedOutput?: string;
|
|
55
|
+
decisionCriteria?: string[];
|
|
56
|
+
testCommand?: string;
|
|
57
|
+
maxDebugCycles?: number;
|
|
58
|
+
maxReviewCycles?: number;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// ============================================================================
|
|
62
|
+
// CLI Args (parsed externally, merged here)
|
|
63
|
+
// ============================================================================
|
|
64
|
+
|
|
65
|
+
export interface CouncilCliArgs {
|
|
66
|
+
configPath?: string;
|
|
67
|
+
councilJsonPath?: string;
|
|
68
|
+
task?: string;
|
|
69
|
+
type?: CouncilStepType;
|
|
70
|
+
workingDir?: string;
|
|
71
|
+
model?: string;
|
|
72
|
+
provider?: string;
|
|
73
|
+
outputFormat?: OutputFormat;
|
|
74
|
+
outputDir?: string;
|
|
75
|
+
noSessionExport?: boolean;
|
|
76
|
+
noCache?: boolean;
|
|
77
|
+
dryRun?: boolean;
|
|
78
|
+
quiet?: boolean;
|
|
79
|
+
jsonStdout?: boolean;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// ============================================================================
|
|
83
|
+
// Loader
|
|
84
|
+
// ============================================================================
|
|
85
|
+
|
|
86
|
+
const SEARCH_PATHS = [
|
|
87
|
+
() => path.join(process.cwd(), 'council.json'),
|
|
88
|
+
() => path.join(os.homedir(), '.config', 'kondi', 'council.json'),
|
|
89
|
+
];
|
|
90
|
+
|
|
91
|
+
export function loadCouncilConfig(configPath?: string): CouncilConfigFile | null {
|
|
92
|
+
// Explicit path
|
|
93
|
+
if (configPath) {
|
|
94
|
+
const resolved = path.resolve(configPath);
|
|
95
|
+
if (!fs.existsSync(resolved)) {
|
|
96
|
+
throw new Error(`Config file not found: ${resolved}`);
|
|
97
|
+
}
|
|
98
|
+
return parseAndValidate(resolved);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// Auto-discovery
|
|
102
|
+
for (const getPath of SEARCH_PATHS) {
|
|
103
|
+
const p = getPath();
|
|
104
|
+
if (fs.existsSync(p)) {
|
|
105
|
+
return parseAndValidate(p);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
return null;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function parseAndValidate(filePath: string): CouncilConfigFile {
|
|
113
|
+
const raw = fs.readFileSync(filePath, 'utf-8');
|
|
114
|
+
let parsed: any;
|
|
115
|
+
try {
|
|
116
|
+
parsed = JSON.parse(raw);
|
|
117
|
+
} catch {
|
|
118
|
+
throw new Error(`Invalid JSON in config file: ${filePath}`);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// Validate required fields
|
|
122
|
+
if (!parsed.name || typeof parsed.name !== 'string') {
|
|
123
|
+
throw new Error(`Config file missing required "name" field: ${filePath}`);
|
|
124
|
+
}
|
|
125
|
+
if (!Array.isArray(parsed.personas) || parsed.personas.length === 0) {
|
|
126
|
+
throw new Error(`Config file must have at least one persona: ${filePath}`);
|
|
127
|
+
}
|
|
128
|
+
for (const p of parsed.personas) {
|
|
129
|
+
if (!p.name || !p.role) {
|
|
130
|
+
throw new Error(`Each persona must have "name" and "role": ${filePath}`);
|
|
131
|
+
}
|
|
132
|
+
const validRoles = ['manager', 'worker', 'consultant', 'reviewer'];
|
|
133
|
+
if (!validRoles.includes(p.role)) {
|
|
134
|
+
throw new Error(`Invalid persona role "${p.role}" for "${p.name}". Must be: ${validRoles.join(', ')}`);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
return parsed as CouncilConfigFile;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// ============================================================================
|
|
142
|
+
// Merge config with CLI args (CLI wins)
|
|
143
|
+
// ============================================================================
|
|
144
|
+
|
|
145
|
+
export interface ResolvedCouncilConfig {
|
|
146
|
+
config: CouncilConfigFile;
|
|
147
|
+
task: string;
|
|
148
|
+
type: CouncilStepType;
|
|
149
|
+
workingDir?: string;
|
|
150
|
+
model?: string;
|
|
151
|
+
provider?: string;
|
|
152
|
+
outputFormat: OutputFormat;
|
|
153
|
+
outputDir?: string;
|
|
154
|
+
sessionExport: boolean;
|
|
155
|
+
dryRun: boolean;
|
|
156
|
+
quiet: boolean;
|
|
157
|
+
jsonStdout: boolean;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
export function mergeConfigWithArgs(
|
|
161
|
+
config: CouncilConfigFile,
|
|
162
|
+
args: CouncilCliArgs,
|
|
163
|
+
): ResolvedCouncilConfig {
|
|
164
|
+
return {
|
|
165
|
+
config,
|
|
166
|
+
task: args.task || config.task || config.name,
|
|
167
|
+
type: args.type || config.type || 'council',
|
|
168
|
+
workingDir: args.workingDir,
|
|
169
|
+
model: args.model,
|
|
170
|
+
provider: args.provider,
|
|
171
|
+
outputFormat: args.outputFormat || config.output?.format || 'full',
|
|
172
|
+
outputDir: args.outputDir || config.output?.directory,
|
|
173
|
+
sessionExport: args.noSessionExport ? false : (config.output?.sessionExport ?? true),
|
|
174
|
+
dryRun: args.dryRun || false,
|
|
175
|
+
quiet: args.quiet || false,
|
|
176
|
+
jsonStdout: args.jsonStdout || false,
|
|
177
|
+
};
|
|
178
|
+
}
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CLI Council Session Export
|
|
3
|
+
*
|
|
4
|
+
* Exports a standalone council session for GUI import.
|
|
5
|
+
* Parallel to session-export.ts but council-focused (no pipeline wrapper).
|
|
6
|
+
*
|
|
7
|
+
* Output: ~/.local/share/kondi/sessions/council-<councilId>-<timestamp>.json
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import fs from 'node:fs';
|
|
11
|
+
import path from 'node:path';
|
|
12
|
+
import os from 'node:os';
|
|
13
|
+
|
|
14
|
+
const SESSIONS_DIR = path.join(os.homedir(), '.local', 'share', 'kondi', 'sessions');
|
|
15
|
+
|
|
16
|
+
/** localStorage key conventions — must match store modules */
|
|
17
|
+
const KEYS = {
|
|
18
|
+
councils: 'mcp-councils',
|
|
19
|
+
ledgerIndex: (id: string) => `ledger-index-${id}`,
|
|
20
|
+
ledgerChunk: (id: string, n: number) => `ledger-chunk-${id}-${n}`,
|
|
21
|
+
context: (id: string) => `context-${id}`,
|
|
22
|
+
contextHistory: (id: string) => `context-history-${id}`,
|
|
23
|
+
contextPatches: (id: string) => `context-patches-${id}`,
|
|
24
|
+
decision: (id: string) => `decision-${id}`,
|
|
25
|
+
plan: (id: string) => `plan-${id}`,
|
|
26
|
+
directive: (id: string) => `directive-${id}`,
|
|
27
|
+
outputs: (id: string) => `outputs-${id}`,
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
interface CouncilExecutionInfo {
|
|
31
|
+
status: 'completed' | 'failed';
|
|
32
|
+
startedAt: string;
|
|
33
|
+
completedAt: string;
|
|
34
|
+
durationMs: number;
|
|
35
|
+
workingDirectory?: string;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Export a standalone council session to a JSON file.
|
|
40
|
+
* Reads all council data from the file-backed localStorage shim.
|
|
41
|
+
*/
|
|
42
|
+
export function exportCouncilSession(
|
|
43
|
+
councilId: string,
|
|
44
|
+
storage: Storage,
|
|
45
|
+
execution: CouncilExecutionInfo,
|
|
46
|
+
): string | null {
|
|
47
|
+
try {
|
|
48
|
+
// Load council from store
|
|
49
|
+
const councilsRaw = storage.getItem(KEYS.councils);
|
|
50
|
+
const councilsData = councilsRaw ? JSON.parse(councilsRaw) : { councils: [] };
|
|
51
|
+
const allCouncils = councilsData.councils || [];
|
|
52
|
+
const council = allCouncils.find((c: any) => c.id === councilId);
|
|
53
|
+
if (!council) {
|
|
54
|
+
console.error('[CouncilSessionExport] Council not found:', councilId);
|
|
55
|
+
return null;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// Collect ledger data
|
|
59
|
+
const ledgerIndexRaw = storage.getItem(KEYS.ledgerIndex(councilId));
|
|
60
|
+
const ledgerIndex = ledgerIndexRaw ? JSON.parse(ledgerIndexRaw) : null;
|
|
61
|
+
|
|
62
|
+
const ledgerChunks: Record<number, any[]> = {};
|
|
63
|
+
if (ledgerIndex) {
|
|
64
|
+
const chunkCount = ledgerIndex.chunkCount ?? 0;
|
|
65
|
+
for (let n = 0; n <= chunkCount; n++) {
|
|
66
|
+
const chunkRaw = storage.getItem(KEYS.ledgerChunk(councilId, n));
|
|
67
|
+
if (chunkRaw) {
|
|
68
|
+
ledgerChunks[n] = JSON.parse(chunkRaw);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// Collect context artifacts
|
|
74
|
+
const contextRaw = storage.getItem(KEYS.context(councilId));
|
|
75
|
+
const contextHistoryRaw = storage.getItem(KEYS.contextHistory(councilId));
|
|
76
|
+
const contextPatchesRaw = storage.getItem(KEYS.contextPatches(councilId));
|
|
77
|
+
const decisionRaw = storage.getItem(KEYS.decision(councilId));
|
|
78
|
+
const planRaw = storage.getItem(KEYS.plan(councilId));
|
|
79
|
+
const directiveRaw = storage.getItem(KEYS.directive(councilId));
|
|
80
|
+
const outputsRaw = storage.getItem(KEYS.outputs(councilId));
|
|
81
|
+
|
|
82
|
+
const councilData = {
|
|
83
|
+
ledgerIndex,
|
|
84
|
+
ledgerChunks,
|
|
85
|
+
context: contextRaw ? JSON.parse(contextRaw) : null,
|
|
86
|
+
contextHistory: contextHistoryRaw ? JSON.parse(contextHistoryRaw) : [],
|
|
87
|
+
contextPatches: contextPatchesRaw ? JSON.parse(contextPatchesRaw) : [],
|
|
88
|
+
decision: decisionRaw ? JSON.parse(decisionRaw) : null,
|
|
89
|
+
plan: planRaw ? JSON.parse(planRaw) : null,
|
|
90
|
+
directive: directiveRaw ? JSON.parse(directiveRaw) : null,
|
|
91
|
+
outputs: outputsRaw ? JSON.parse(outputsRaw) : [],
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
// Build session
|
|
95
|
+
const session = {
|
|
96
|
+
version: 1,
|
|
97
|
+
exportedAt: new Date().toISOString(),
|
|
98
|
+
source: 'cli-council',
|
|
99
|
+
council,
|
|
100
|
+
councilData: { [councilId]: councilData },
|
|
101
|
+
execution,
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
// Write to sessions directory
|
|
105
|
+
fs.mkdirSync(SESSIONS_DIR, { recursive: true });
|
|
106
|
+
const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
|
|
107
|
+
const filename = `council-${councilId}-${timestamp}.json`;
|
|
108
|
+
const filePath = path.join(SESSIONS_DIR, filename);
|
|
109
|
+
fs.writeFileSync(filePath, JSON.stringify(session, null, 2), 'utf-8');
|
|
110
|
+
|
|
111
|
+
return filePath;
|
|
112
|
+
} catch (err) {
|
|
113
|
+
console.error('[CouncilSessionExport] Failed to export session:', err);
|
|
114
|
+
return null;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
#!/usr/bin/env -S npx tsx
|
|
2
|
+
/**
|
|
3
|
+
* Kondi CLI — Multi-LLM Council Platform
|
|
4
|
+
*
|
|
5
|
+
* Usage:
|
|
6
|
+
* kondi council [options] Run a council deliberation
|
|
7
|
+
* kondi pipeline [options] Run a pipeline
|
|
8
|
+
* kondi --help Show help
|
|
9
|
+
* kondi --version Show version
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
// Load .env if present (API keys)
|
|
13
|
+
import { readFileSync, existsSync } from 'node:fs';
|
|
14
|
+
import { resolve, dirname } from 'node:path';
|
|
15
|
+
import { fileURLToPath } from 'node:url';
|
|
16
|
+
const __dirnameLocal = dirname(fileURLToPath(import.meta.url));
|
|
17
|
+
for (const envPath of [resolve(process.cwd(), '.env'), resolve(__dirnameLocal, '..', '..', '.env')]) {
|
|
18
|
+
if (existsSync(envPath)) {
|
|
19
|
+
for (const line of readFileSync(envPath, 'utf-8').split('\n')) {
|
|
20
|
+
const m = line.match(/^([A-Z_]+)=(.+)$/);
|
|
21
|
+
if (m && !process.env[m[1]]) process.env[m[1]] = m[2];
|
|
22
|
+
}
|
|
23
|
+
break;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const C = {
|
|
28
|
+
reset: '\x1b[0m',
|
|
29
|
+
bold: '\x1b[1m',
|
|
30
|
+
dim: '\x1b[2m',
|
|
31
|
+
cyan: '\x1b[36m',
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
function printHelp() {
|
|
35
|
+
console.log(`
|
|
36
|
+
${C.bold}${C.cyan}Kondi${C.reset} — Multi-LLM Council Platform
|
|
37
|
+
|
|
38
|
+
${C.bold}Commands:${C.reset}
|
|
39
|
+
council Run a council deliberation
|
|
40
|
+
pipeline Run a pipeline from JSON
|
|
41
|
+
|
|
42
|
+
${C.bold}Usage:${C.reset}
|
|
43
|
+
kondi council --task "Review this code" --working-dir ./myapp
|
|
44
|
+
kondi council --config council.json
|
|
45
|
+
kondi council exported-council.json
|
|
46
|
+
kondi pipeline pipeline.json --working-dir ./project
|
|
47
|
+
|
|
48
|
+
${C.bold}Options:${C.reset}
|
|
49
|
+
--help, -h Show help (use "kondi council --help" for subcommand help)
|
|
50
|
+
--version, -v Show version
|
|
51
|
+
|
|
52
|
+
Run ${C.dim}kondi <command> --help${C.reset} for subcommand-specific options.
|
|
53
|
+
`);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
async function main() {
|
|
57
|
+
const subcommand = process.argv[2];
|
|
58
|
+
|
|
59
|
+
if (!subcommand || subcommand === '--help' || subcommand === '-h') {
|
|
60
|
+
printHelp();
|
|
61
|
+
process.exit(0);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
if (subcommand === '--version' || subcommand === '-v') {
|
|
65
|
+
try {
|
|
66
|
+
const { readFileSync } = await import('node:fs');
|
|
67
|
+
const { join, dirname } = await import('node:path');
|
|
68
|
+
const { fileURLToPath } = await import('node:url');
|
|
69
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
70
|
+
const pkg = JSON.parse(readFileSync(join(__dirname, '..', '..', 'package.json'), 'utf-8'));
|
|
71
|
+
console.log(`kondi ${pkg.version || '0.0.0'}`);
|
|
72
|
+
} catch {
|
|
73
|
+
console.log('kondi 0.0.0');
|
|
74
|
+
}
|
|
75
|
+
process.exit(0);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// Strip the subcommand from argv so the sub-runner sees the right args
|
|
79
|
+
process.argv = [process.argv[0], process.argv[1], ...process.argv.slice(3)];
|
|
80
|
+
|
|
81
|
+
switch (subcommand) {
|
|
82
|
+
case 'council':
|
|
83
|
+
await import('./run-council');
|
|
84
|
+
break;
|
|
85
|
+
case 'pipeline':
|
|
86
|
+
await import('./run-pipeline');
|
|
87
|
+
break;
|
|
88
|
+
default:
|
|
89
|
+
console.error(`Unknown command: ${subcommand}`);
|
|
90
|
+
console.error(`Run "kondi --help" for available commands.`);
|
|
91
|
+
process.exit(1);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
main().catch((err) => {
|
|
96
|
+
console.error('Unhandled error:', err);
|
|
97
|
+
process.exit(1);
|
|
98
|
+
});
|