@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.
Files changed (108) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +556 -0
  3. package/bin/kondi-chat +56 -0
  4. package/bin/kondi-chat.js +72 -0
  5. package/package.json +55 -0
  6. package/scripts/demo.tape +49 -0
  7. package/scripts/postinstall.cjs +103 -0
  8. package/src/audit/analytics.ts +261 -0
  9. package/src/audit/ledger.ts +253 -0
  10. package/src/audit/telemetry.ts +165 -0
  11. package/src/cli/backend.ts +675 -0
  12. package/src/cli/commands.ts +419 -0
  13. package/src/cli/help.ts +182 -0
  14. package/src/cli/submit-helpers.ts +159 -0
  15. package/src/cli/submit.ts +539 -0
  16. package/src/cli/wizard.ts +121 -0
  17. package/src/context/bootstrap.ts +138 -0
  18. package/src/context/budget.ts +100 -0
  19. package/src/context/manager.ts +666 -0
  20. package/src/context/memory.ts +160 -0
  21. package/src/context/preflight.ts +176 -0
  22. package/src/context/project-brain.ts +101 -0
  23. package/src/context/receipts.ts +108 -0
  24. package/src/context/skills.ts +154 -0
  25. package/src/context/symbol-index.ts +240 -0
  26. package/src/council/profiles.ts +137 -0
  27. package/src/council/tool.ts +138 -0
  28. package/src/council-engine/cli/council-artifacts.ts +230 -0
  29. package/src/council-engine/cli/council-config.ts +178 -0
  30. package/src/council-engine/cli/council-session-export.ts +116 -0
  31. package/src/council-engine/cli/kondi.ts +98 -0
  32. package/src/council-engine/cli/llm-caller.ts +229 -0
  33. package/src/council-engine/cli/localStorage-shim.ts +119 -0
  34. package/src/council-engine/cli/node-platform.ts +68 -0
  35. package/src/council-engine/cli/run-council.ts +481 -0
  36. package/src/council-engine/cli/run-pipeline.ts +772 -0
  37. package/src/council-engine/cli/session-export.ts +153 -0
  38. package/src/council-engine/configs/councils/analysis.json +101 -0
  39. package/src/council-engine/configs/councils/code-planning.json +86 -0
  40. package/src/council-engine/configs/councils/coding.json +89 -0
  41. package/src/council-engine/configs/councils/debate.json +97 -0
  42. package/src/council-engine/configs/councils/solo-claude.json +34 -0
  43. package/src/council-engine/configs/councils/solo-gpt.json +34 -0
  44. package/src/council-engine/council/coding-orchestrator.ts +1205 -0
  45. package/src/council-engine/council/context-bootstrap.ts +147 -0
  46. package/src/council-engine/council/context-inspection.ts +42 -0
  47. package/src/council-engine/council/context-store.ts +763 -0
  48. package/src/council-engine/council/deliberation-orchestrator.ts +2762 -0
  49. package/src/council-engine/council/factory.ts +164 -0
  50. package/src/council-engine/council/index.ts +201 -0
  51. package/src/council-engine/council/ledger-store.ts +438 -0
  52. package/src/council-engine/council/prompts.ts +1689 -0
  53. package/src/council-engine/council/storage-cleanup.ts +164 -0
  54. package/src/council-engine/council/store.ts +1110 -0
  55. package/src/council-engine/council/synthesis.ts +291 -0
  56. package/src/council-engine/council/types.ts +845 -0
  57. package/src/council-engine/council/validation.ts +613 -0
  58. package/src/council-engine/pipeline/build-detect.ts +73 -0
  59. package/src/council-engine/pipeline/executor.ts +1048 -0
  60. package/src/council-engine/pipeline/index.ts +9 -0
  61. package/src/council-engine/pipeline/install-detect.ts +84 -0
  62. package/src/council-engine/pipeline/memory-store.ts +182 -0
  63. package/src/council-engine/pipeline/output-parsers.ts +146 -0
  64. package/src/council-engine/pipeline/run-output.ts +149 -0
  65. package/src/council-engine/pipeline/session-import.ts +177 -0
  66. package/src/council-engine/pipeline/store.ts +753 -0
  67. package/src/council-engine/pipeline/test-detect.ts +82 -0
  68. package/src/council-engine/pipeline/types.ts +401 -0
  69. package/src/council-engine/services/deliberationSummary.ts +114 -0
  70. package/src/council-engine/tsconfig.json +16 -0
  71. package/src/council-engine/types/mcp.ts +122 -0
  72. package/src/council-engine/utils/filterTools.ts +73 -0
  73. package/src/engine/apply.ts +238 -0
  74. package/src/engine/checkpoints.ts +237 -0
  75. package/src/engine/consultants.ts +347 -0
  76. package/src/engine/diff.ts +171 -0
  77. package/src/engine/errors.ts +102 -0
  78. package/src/engine/git-tools.ts +246 -0
  79. package/src/engine/hooks.ts +181 -0
  80. package/src/engine/loop-guard.ts +155 -0
  81. package/src/engine/permissions.ts +293 -0
  82. package/src/engine/pipeline.ts +376 -0
  83. package/src/engine/sub-agents.ts +133 -0
  84. package/src/engine/task-card.ts +185 -0
  85. package/src/engine/task-router.ts +256 -0
  86. package/src/engine/task-store.ts +86 -0
  87. package/src/engine/tools.ts +783 -0
  88. package/src/engine/verify.ts +111 -0
  89. package/src/mcp/client.ts +225 -0
  90. package/src/mcp/config.ts +120 -0
  91. package/src/mcp/tool-manager.ts +192 -0
  92. package/src/mcp/types.ts +61 -0
  93. package/src/providers/llm-caller.ts +943 -0
  94. package/src/providers/rate-limiter.ts +238 -0
  95. package/src/router/NOTES.md +28 -0
  96. package/src/router/collector.ts +474 -0
  97. package/src/router/embeddings.ts +286 -0
  98. package/src/router/index.ts +299 -0
  99. package/src/router/intent-router.ts +225 -0
  100. package/src/router/nn-router.ts +205 -0
  101. package/src/router/profiles.ts +309 -0
  102. package/src/router/registry.ts +565 -0
  103. package/src/router/rules.ts +274 -0
  104. package/src/router/train.py +408 -0
  105. package/src/session/store.ts +211 -0
  106. package/src/test-utils/mock-llm.ts +39 -0
  107. package/src/types.ts +322 -0
  108. package/src/web/manager.ts +311 -0
@@ -0,0 +1,82 @@
1
+ /**
2
+ * Test Command Auto-Detection
3
+ * Scans project files to determine the appropriate test command.
4
+ * Accepts an optional readFile callback to abstract file I/O (Tauri / Node.js).
5
+ */
6
+
7
+ export type ReadFileFn = (path: string) => Promise<string | null>;
8
+
9
+ export interface DetectedTest {
10
+ command: string;
11
+ framework: string;
12
+ confidence: 'high' | 'medium' | 'low';
13
+ }
14
+
15
+ /**
16
+ * Detect the appropriate test command for a project directory.
17
+ * Checks common project files in priority order.
18
+ *
19
+ * @param workingDir Absolute path to the project root
20
+ * @param readFile Optional callback that reads a file and returns its content
21
+ * (or null if not found). When omitted, detection is skipped.
22
+ */
23
+ export async function detectTestCommand(
24
+ workingDir: string,
25
+ readFile?: ReadFileFn,
26
+ ): Promise<DetectedTest | null> {
27
+ if (!readFile) return null;
28
+
29
+ const fileExists = async (path: string): Promise<string | null> => {
30
+ try {
31
+ return await readFile(path);
32
+ } catch {
33
+ return null;
34
+ }
35
+ };
36
+
37
+ // 1. package.json — Node.js projects
38
+ const packageJson = await fileExists(`${workingDir}/package.json`);
39
+ if (packageJson) {
40
+ try {
41
+ const pkg = JSON.parse(packageJson);
42
+ if (pkg.scripts?.test && pkg.scripts.test !== 'echo "Error: no test specified" && exit 1') {
43
+ const deps = { ...pkg.dependencies, ...pkg.devDependencies };
44
+ let framework = 'npm test';
45
+ if (deps?.vitest) framework = 'vitest';
46
+ else if (deps?.jest) framework = 'jest';
47
+ else if (deps?.mocha) framework = 'mocha';
48
+ return { command: 'npm test', framework, confidence: 'high' };
49
+ }
50
+ } catch { /* invalid JSON, skip */ }
51
+ }
52
+
53
+ // 2. Cargo.toml — Rust projects
54
+ const cargoToml = await fileExists(`${workingDir}/Cargo.toml`);
55
+ if (cargoToml) {
56
+ return { command: 'cargo test', framework: 'cargo', confidence: 'high' };
57
+ }
58
+
59
+ // 3. go.mod — Go projects
60
+ const goMod = await fileExists(`${workingDir}/go.mod`);
61
+ if (goMod) {
62
+ return { command: 'go test ./...', framework: 'go test', confidence: 'high' };
63
+ }
64
+
65
+ // 4. Python — pytest or unittest
66
+ const pytestIni = await fileExists(`${workingDir}/pytest.ini`);
67
+ if (pytestIni) {
68
+ return { command: 'pytest', framework: 'pytest', confidence: 'high' };
69
+ }
70
+ const pyprojectToml = await fileExists(`${workingDir}/pyproject.toml`);
71
+ if (pyprojectToml && pyprojectToml.includes('[tool.pytest')) {
72
+ return { command: 'pytest', framework: 'pytest', confidence: 'high' };
73
+ }
74
+
75
+ // 5. Makefile with test target
76
+ const makefile = await fileExists(`${workingDir}/Makefile`);
77
+ if (makefile && /^test\s*:/m.test(makefile)) {
78
+ return { command: 'make test', framework: 'make', confidence: 'medium' };
79
+ }
80
+
81
+ return null;
82
+ }
@@ -0,0 +1,401 @@
1
+ /**
2
+ * Pipeline System: Type Definitions
3
+ * Councils as Workflow Steps — ordered stages with parallel steps
4
+ */
5
+
6
+ // ============================================================================
7
+ // Step & Pipeline Status
8
+ // ============================================================================
9
+
10
+ /** All pipeline step types. Gate, script, and condition are non-council types. */
11
+ export const PIPELINE_STEP_TYPES = ['council', 'code_planning', 'analysis', 'agent', 'coding', 'review', 'enrich', 'gate', 'script', 'condition'] as const;
12
+ export type PipelineStepType = (typeof PIPELINE_STEP_TYPES)[number];
13
+
14
+ /** Council-based step types (excludes gate, script, condition). */
15
+ export const COUNCIL_STEP_TYPES = ['council', 'code_planning', 'analysis', 'agent', 'coding', 'review', 'enrich'] as const;
16
+ export type CouncilStepType = (typeof COUNCIL_STEP_TYPES)[number];
17
+
18
+ /** Human-readable labels for each step type */
19
+ export const STEP_TYPE_LABELS: Record<PipelineStepType, string> = {
20
+ council: 'Council',
21
+ code_planning: 'Code Planning',
22
+ analysis: 'Analysis',
23
+ agent: 'Agent',
24
+ coding: 'Coding',
25
+ review: 'Review',
26
+ enrich: 'Enrich',
27
+ gate: 'Gate',
28
+ script: 'Script',
29
+ condition: 'Condition',
30
+ };
31
+
32
+ /** What kind of data a step produces for downstream consumption */
33
+ export type OutputType = 'string' | 'file' | 'directory' | 'json';
34
+
35
+ export type PipelineStepStatus =
36
+ | 'pending'
37
+ | 'running'
38
+ | 'completed'
39
+ | 'failed'
40
+ | 'skipped'
41
+ | 'waiting_approval';
42
+
43
+ export type PipelineStatus =
44
+ | 'draft'
45
+ | 'ready'
46
+ | 'running'
47
+ | 'completed'
48
+ | 'failed'
49
+ | 'paused';
50
+
51
+ // ============================================================================
52
+ // Step Artifacts (output produced by a completed step)
53
+ // ============================================================================
54
+
55
+ export interface StepArtifact {
56
+ stepId: string;
57
+ content: string;
58
+ artifactType: 'decision' | 'output' | 'llm_response' | 'approval';
59
+ metadata?: {
60
+ councilId?: string;
61
+ decisionId?: string;
62
+ outputId?: string;
63
+ model?: string;
64
+ tokensUsed?: number;
65
+ outputPath?: string; // file path where output was saved (planning/coding steps)
66
+ outputType?: OutputType; // what kind of data this artifact represents
67
+ stepName?: string; // human-readable name of the producing step
68
+ stepType?: string; // PipelineStepType value
69
+ };
70
+ createdAt: string;
71
+ }
72
+
73
+ // ============================================================================
74
+ // Memory Entry (persistent across scheduled runs)
75
+ // ============================================================================
76
+
77
+ export interface MemoryEntry {
78
+ runNumber: number;
79
+ runDate: string;
80
+ /** Captured artifacts from designated steps, keyed by sanitized step name */
81
+ captures: Record<string, string>;
82
+ /** True if this entry is a compressed summary of multiple older entries */
83
+ compressed?: boolean;
84
+ }
85
+
86
+ // ============================================================================
87
+ // Output Isolation Config
88
+ // ============================================================================
89
+
90
+ export interface PipelineOutputConfig {
91
+ /** Enable structured output directories (default: true for new pipelines) */
92
+ enabled: boolean;
93
+ /** Max runs to keep on disk before pruning oldest (default: 50, 0 = unlimited) */
94
+ maxRetainedRuns?: number;
95
+ /** What to save per step */
96
+ stepOutput: 'artifact_only' | 'artifact_and_deliberation';
97
+ }
98
+
99
+ /** Run manifest written to _manifest.json */
100
+ export interface RunManifest {
101
+ runNumber: number;
102
+ pipelineId: string;
103
+ pipelineName: string;
104
+ startedAt: string;
105
+ completedAt: string;
106
+ status: 'completed' | 'failed';
107
+ initialInput: string;
108
+ stageCount: number;
109
+ stepCount: number;
110
+ totalTokens: number;
111
+ totalDurationMs: number;
112
+ /** Whether memory was updated from this run */
113
+ memoryUpdated: boolean;
114
+ }
115
+
116
+ /** Step metadata written to _meta.json */
117
+ export interface StepMeta {
118
+ stepId: string;
119
+ stepName: string;
120
+ stepType: string;
121
+ stageIndex: number;
122
+ stepIndex: number;
123
+ startedAt?: string;
124
+ completedAt?: string;
125
+ status: PipelineStepStatus;
126
+ outputType: OutputType;
127
+ councilId?: string;
128
+ model?: string;
129
+ tokensUsed?: number;
130
+ durationMs?: number;
131
+ }
132
+
133
+ // ============================================================================
134
+ // Pipeline Persona (full-featured, matches council persona capabilities)
135
+ // ============================================================================
136
+
137
+ export interface PipelinePersona {
138
+ templateId?: string;
139
+ name: string;
140
+ role: 'manager' | 'consultant' | 'worker' | 'reviewer';
141
+ model: string;
142
+ provider: string;
143
+ avatar?: string;
144
+ color?: string;
145
+ systemPrompt?: string;
146
+ stance?: 'advocate' | 'critic' | 'neutral' | 'wildcard';
147
+ traits?: string[];
148
+ interactionStyle?: 'debate' | 'build' | 'question' | 'synthesize' | 'review';
149
+ domain?: string;
150
+ temperature?: number;
151
+ verbosity?: 'concise' | 'balanced' | 'thorough';
152
+ focusArea?: string;
153
+ startingStance?: string;
154
+ suppressPersona?: boolean;
155
+ /** Worker-only: save the worker's output to the working directory (default: true) */
156
+ saveOutput?: boolean;
157
+ /** MCP servers this persona can access (undefined = all servers) */
158
+ allowedServerIds?: string[];
159
+ /** Override default tool behavior: 'full' = enable all tools, 'none' = disable tools */
160
+ toolAccess?: 'full' | 'none';
161
+ }
162
+
163
+ // ============================================================================
164
+ // Step Configs
165
+ // ============================================================================
166
+
167
+ export interface CouncilStepConfig {
168
+ type: CouncilStepType;
169
+ councilSetup: {
170
+ name: string;
171
+ personas: PipelinePersona[];
172
+ maxRounds?: number;
173
+ maxRevisions?: number;
174
+ expectedOutput?: string;
175
+ decisionCriteria?: string[];
176
+ workingDirectory?: string;
177
+ directoryConstrained?: boolean;
178
+ // Coding orchestrator config
179
+ testCommand?: string;
180
+ maxDebugCycles?: number;
181
+ maxReviewCycles?: number;
182
+ /** MCP servers this step can access (undefined = all servers) */
183
+ allowedServerIds?: string[];
184
+ };
185
+ /** Standing instructions — what this step should DO (supplemental to input context) */
186
+ task?: string;
187
+ /** Template that renders previous step outputs as input context */
188
+ inputTemplate: string;
189
+ /** What kind of data this step produces (default: 'string') */
190
+ outputType?: OutputType;
191
+ /** Include pipeline's initial input as additional context, regardless of stage */
192
+ includePipelineInput?: boolean;
193
+ }
194
+
195
+ export interface LlmStepConfig {
196
+ type: 'analysis' | 'agent';
197
+ model: string;
198
+ provider: string;
199
+ systemPrompt: string;
200
+ inputTemplate: string;
201
+ workingDirectory?: string;
202
+ directoryConstrained?: boolean;
203
+ /** MCP servers this step can access (undefined = all servers) */
204
+ allowedServerIds?: string[];
205
+ }
206
+
207
+ export interface GateStepConfig {
208
+ type: 'gate';
209
+ approvalPrompt: string;
210
+ }
211
+
212
+ export interface ScriptStepConfig {
213
+ type: 'script';
214
+ /** Shell command to execute. Previous step output available as $KONDI_INPUT env var. */
215
+ command: string;
216
+ /** Template that renders previous step outputs into the $KONDI_INPUT env var */
217
+ inputTemplate: string;
218
+ /** What kind of data this step produces (default: 'string') */
219
+ outputType?: OutputType;
220
+ /** Include pipeline's initial input as additional context, regardless of stage */
221
+ includePipelineInput?: boolean;
222
+ }
223
+
224
+ export type ConditionMode = 'contains' | 'regex' | 'equals';
225
+ export type ConditionAction = 'continue' | 'skip_next_stage' | 'stop';
226
+
227
+ export interface ConditionStepConfig {
228
+ type: 'condition';
229
+ /** The expression to match against (string literal, regex pattern, or exact match) */
230
+ expression: string;
231
+ /** How to evaluate the expression against input */
232
+ mode: ConditionMode;
233
+ /** Template that renders previous step outputs as input to evaluate */
234
+ inputTemplate: string;
235
+ /** Action when expression matches */
236
+ trueAction: ConditionAction;
237
+ /** Action when expression does not match */
238
+ falseAction: ConditionAction;
239
+ /** Include pipeline's initial input as additional context, regardless of stage */
240
+ includePipelineInput?: boolean;
241
+ }
242
+
243
+ export type StepConfig = CouncilStepConfig | LlmStepConfig | GateStepConfig | ScriptStepConfig | ConditionStepConfig;
244
+
245
+ /** Helper: is this a council-based step type? Excludes gate, script, and condition. */
246
+ export function isCouncilType(type: PipelineStepType): boolean {
247
+ return type !== 'gate' && type !== 'script' && type !== 'condition';
248
+ }
249
+
250
+ /** Helper: is this a lightweight council (single-agent, 0-round)? */
251
+ export function isLightweightCouncilType(type: PipelineStepType): boolean {
252
+ return type === 'analysis' || type === 'agent';
253
+ }
254
+
255
+ /**
256
+ * @deprecated Use isLightweightCouncilType instead.
257
+ * Kept for backwards compatibility with existing code.
258
+ */
259
+ export function isLlmType(type: PipelineStepType): boolean {
260
+ return type === 'analysis' || type === 'agent';
261
+ }
262
+
263
+ /**
264
+ * Migrate legacy LlmStepConfig to CouncilStepConfig.
265
+ * Old pipelines may have { type: 'analysis'|'agent', model, provider, systemPrompt, ... }
266
+ * without a councilSetup. This creates one from the flat fields.
267
+ */
268
+ export function migrateLlmConfig(config: LlmStepConfig): CouncilStepConfig {
269
+ const isAnalysis = config.type === 'analysis';
270
+ return {
271
+ type: config.type,
272
+ councilSetup: {
273
+ name: isAnalysis ? 'Analysis' : 'Agent',
274
+ personas: [{
275
+ name: isAnalysis ? 'Analyst' : 'Executor',
276
+ role: isAnalysis ? 'manager' : 'worker',
277
+ model: config.model,
278
+ provider: config.provider,
279
+ systemPrompt: config.systemPrompt,
280
+ suppressPersona: true,
281
+ }],
282
+ maxRounds: 0,
283
+ maxRevisions: 0,
284
+ workingDirectory: config.workingDirectory,
285
+ directoryConstrained: config.directoryConstrained,
286
+ allowedServerIds: config.allowedServerIds,
287
+ },
288
+ inputTemplate: config.inputTemplate,
289
+ outputType: 'string',
290
+ };
291
+ }
292
+
293
+ // ============================================================================
294
+ // Pipeline Step
295
+ // ============================================================================
296
+
297
+ export interface PipelineStep {
298
+ id: string;
299
+ name: string;
300
+ description?: string;
301
+ config: StepConfig;
302
+ status: PipelineStepStatus;
303
+ artifact?: StepArtifact;
304
+ error?: string;
305
+ startedAt?: string;
306
+ completedAt?: string;
307
+ }
308
+
309
+ // ============================================================================
310
+ // Pipeline Stage
311
+ // ============================================================================
312
+
313
+ export interface PipelineStage {
314
+ id: string;
315
+ name: string;
316
+ steps: PipelineStep[];
317
+ /** How steps in this stage are executed (default: 'sequential') */
318
+ executionMode?: 'sequential' | 'parallel';
319
+ }
320
+
321
+ // ============================================================================
322
+ // Pipeline
323
+ // ============================================================================
324
+
325
+ // ============================================================================
326
+ // Schedule Config
327
+ // ============================================================================
328
+
329
+ export interface PipelineSchedule {
330
+ enabled: boolean;
331
+ /** Time of day in HH:MM (24-hour) format */
332
+ time: string;
333
+ /** Recurrence mode */
334
+ mode: 'once' | 'daily' | 'weekly';
335
+ /** For 'once' mode: ISO date string (YYYY-MM-DD) */
336
+ date?: string;
337
+ /** For 'weekly' mode: 0 = Sunday, 1 = Monday, ... 6 = Saturday */
338
+ dayOfWeek?: number;
339
+ /** ISO timestamp of the last scheduled run (to avoid double-fires) */
340
+ lastRunAt?: string;
341
+ /** Maintain memory across scheduled runs */
342
+ maintainMemory?: boolean;
343
+ /** Max detailed entries before older ones get compressed (default: 30) */
344
+ maxDetailedEntries?: number;
345
+ /** Which steps' artifacts to capture into memory (step IDs).
346
+ * If empty/undefined, captures only the last completed step. */
347
+ captureStepIds?: string[];
348
+ }
349
+
350
+ export interface Pipeline {
351
+ id: string;
352
+ name: string;
353
+ description?: string;
354
+ initialInput: string;
355
+ stages: PipelineStage[];
356
+ settings: {
357
+ workingDirectory?: string;
358
+ directoryConstrained?: boolean;
359
+ failurePolicy: 'stop' | 'skip_step';
360
+ schedule?: PipelineSchedule;
361
+ outputConfig?: PipelineOutputConfig;
362
+ };
363
+ status: PipelineStatus;
364
+ currentStageIndex: number;
365
+ createdAt: string;
366
+ updatedAt: string;
367
+ /** Where this pipeline was executed — 'cli' for CLI-imported sessions */
368
+ source?: 'cli' | 'gui';
369
+ }
370
+
371
+ // ============================================================================
372
+ // CLI ↔ GUI Session Export/Import
373
+ // ============================================================================
374
+
375
+ export interface KondiSessionCouncilData {
376
+ ledgerIndex: any;
377
+ ledgerChunks: Record<number, any[]>;
378
+ context: any | null;
379
+ contextHistory: any[];
380
+ contextPatches: any[];
381
+ decision: any | null;
382
+ plan: any | null;
383
+ directive: any | null;
384
+ outputs: any[];
385
+ }
386
+
387
+ export interface KondiSession {
388
+ version: 1;
389
+ exportedAt: string;
390
+ source: 'cli';
391
+ pipeline: Pipeline;
392
+ councils: any[];
393
+ councilData: Record<string, KondiSessionCouncilData>;
394
+ execution: {
395
+ status: 'completed' | 'failed';
396
+ startedAt: string;
397
+ completedAt: string;
398
+ durationMs: number;
399
+ workingDirectory: string;
400
+ };
401
+ }
@@ -0,0 +1,114 @@
1
+ /**
2
+ * Deliberation Summary Builder (Tauri-free)
3
+ * Extracted from deliberationSaveService.ts so it can be used by the pipeline
4
+ * executor without Tauri dependencies.
5
+ */
6
+
7
+ import type { Council } from '../council/types';
8
+ import { getAllEntries, buildMechanicalSummary } from '../council/ledger-store';
9
+ import { getDecision, getLatestOutput } from '../council/context-store';
10
+
11
+ // ============================================================================
12
+ // Full Deliberation Builder
13
+ // ============================================================================
14
+
15
+ export function buildFullDeliberation(council: Council): string {
16
+ const entries = getAllEntries(council.id);
17
+ if (entries.length === 0) return '# Deliberation\n\nNo entries recorded.';
18
+
19
+ // Group entries by round
20
+ const rounds = new Map<number | 'none', typeof entries>();
21
+ for (const entry of entries) {
22
+ const key = entry.roundNumber ?? 'none';
23
+ if (!rounds.has(key)) rounds.set(key, []);
24
+ rounds.get(key)!.push(entry);
25
+ }
26
+
27
+ const getPersonaName = (personaId: string): string => {
28
+ const persona = council.personas.find((p) => p.id === personaId);
29
+ return persona?.name || personaId;
30
+ };
31
+
32
+ const getRoleName = (personaId: string): string => {
33
+ const assignment = council.deliberation?.roleAssignments?.find(
34
+ (r) => r.personaId === personaId
35
+ );
36
+ return assignment?.role || 'unknown';
37
+ };
38
+
39
+ let md = `# Deliberation: ${council.name}\n\n`;
40
+ md += `**Topic:** ${council.topic}\n`;
41
+ md += `**Date:** ${new Date().toISOString().split('T')[0]}\n`;
42
+ md += `**Total Entries:** ${entries.length}\n\n---\n\n`;
43
+
44
+ // Non-round entries first
45
+ const noRound = rounds.get('none');
46
+ if (noRound && noRound.length > 0) {
47
+ for (const entry of noRound) {
48
+ md += `### ${getPersonaName(entry.authorPersonaId)} (${getRoleName(entry.authorPersonaId)}) — ${entry.entryType}\n`;
49
+ md += `*${new Date(entry.timestamp).toLocaleString()}*\n\n`;
50
+ md += `${entry.content}\n\n---\n\n`;
51
+ }
52
+ }
53
+
54
+ // Round entries
55
+ const sortedRounds = Array.from(rounds.keys())
56
+ .filter((k) => k !== 'none')
57
+ .sort((a, b) => (a as number) - (b as number));
58
+
59
+ for (const round of sortedRounds) {
60
+ md += `## Round ${round}\n\n`;
61
+ const roundEntries = rounds.get(round)!;
62
+ for (const entry of roundEntries) {
63
+ md += `### ${getPersonaName(entry.authorPersonaId)} (${getRoleName(entry.authorPersonaId)}) — ${entry.entryType}\n`;
64
+ md += `*${new Date(entry.timestamp).toLocaleString()}*\n\n`;
65
+ md += `${entry.content}\n\n---\n\n`;
66
+ }
67
+ }
68
+
69
+ return md;
70
+ }
71
+
72
+ // ============================================================================
73
+ // Abbreviated Summary Builder
74
+ // ============================================================================
75
+
76
+ export function buildAbbreviatedSummary(council: Council): string {
77
+ const entries = getAllEntries(council.id);
78
+ if (entries.length === 0) return 'No deliberation entries.';
79
+
80
+ let summary = `=== Deliberation Summary: ${council.name} ===\n\n`;
81
+
82
+ // Consultant highlights (mechanical summary)
83
+ const mechanicalSummary = buildMechanicalSummary(entries);
84
+ if (mechanicalSummary) {
85
+ // Replace persona IDs with names in the mechanical summary
86
+ let namedSummary = mechanicalSummary;
87
+ for (const p of council.personas) {
88
+ namedSummary = namedSummary.replace(new RegExp(p.id, 'g'), p.name);
89
+ }
90
+ summary += `--- Consultant Highlights ---\n${namedSummary}\n\n`;
91
+ }
92
+
93
+ // Decision
94
+ const decision = getDecision(council.id);
95
+ if (decision) {
96
+ summary += `--- Decision ---\n${decision.content}\n`;
97
+ if (decision.acceptanceCriteria) {
98
+ summary += `\nAcceptance Criteria: ${decision.acceptanceCriteria}\n`;
99
+ }
100
+ summary += '\n';
101
+ }
102
+
103
+ // Output (truncated)
104
+ const output = getLatestOutput(council.id);
105
+ if (output) {
106
+ const maxLen = 2000;
107
+ const truncated = output.content.length > maxLen
108
+ ? output.content.slice(0, maxLen) + '\n\n[... truncated ...]'
109
+ : output.content;
110
+ summary += `--- Output (v${output.version}) ---\n${truncated}\n`;
111
+ }
112
+
113
+ return summary;
114
+ }
@@ -0,0 +1,16 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "module": "ESNext",
5
+ "moduleResolution": "bundler",
6
+ "allowImportingTsExtensions": true,
7
+ "noEmit": true,
8
+ "strict": true,
9
+ "skipLibCheck": true,
10
+ "esModuleInterop": true,
11
+ "resolveJsonModule": true,
12
+ "isolatedModules": true,
13
+ "lib": ["ES2022"]
14
+ },
15
+ "include": ["."]
16
+ }