@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,291 @@
1
+ /**
2
+ * Council: Synthesis Generation
3
+ * Generate summaries, resolutions, and consensus analysis
4
+ */
5
+
6
+ import type { Council, Persona, Resolution, CouncilMessage } from './types';
7
+ import { buildSynthesisPrompt } from './prompts';
8
+
9
+ /**
10
+ * Default synthesis model (can be overridden)
11
+ */
12
+ const DEFAULT_SYNTHESIS_MODEL = 'claude-sonnet-4-20250514';
13
+ const DEFAULT_SYNTHESIS_PROVIDER = 'anthropic';
14
+
15
+ /**
16
+ * Parse synthesis response from LLM
17
+ */
18
+ export function parseSynthesisResponse(response: string): Resolution | null {
19
+ try {
20
+ // Try to extract JSON from the response
21
+ const jsonMatch = response.match(/\{[\s\S]*\}/);
22
+ if (!jsonMatch) {
23
+ console.error('[Synthesis] No JSON found in response');
24
+ return null;
25
+ }
26
+
27
+ const data = JSON.parse(jsonMatch[0]);
28
+
29
+ return {
30
+ summary: data.summary || '',
31
+ consensusLevel: typeof data.consensusLevel === 'number' ? data.consensusLevel : 0.5,
32
+ keyDecisions: Array.isArray(data.keyDecisions) ? data.keyDecisions : [],
33
+ agreements: Array.isArray(data.agreements) ? data.agreements : [],
34
+ tensions: Array.isArray(data.tensions) ? data.tensions : [],
35
+ dissent: Array.isArray(data.dissent) ? data.dissent : [],
36
+ nextSteps: Array.isArray(data.nextSteps) ? data.nextSteps : [],
37
+ generatedBy: 'system',
38
+ };
39
+ } catch (error) {
40
+ console.error('[Synthesis] Failed to parse response:', error);
41
+ return null;
42
+ }
43
+ }
44
+
45
+ /**
46
+ * Calculate consensus level from message sentiments
47
+ */
48
+ export function calculateConsensus(council: Council): number {
49
+ const personaMessages = council.messages.filter((m) => m.speakerType === 'persona');
50
+ if (personaMessages.length === 0) return 0;
51
+
52
+ // Count agreements and disagreements
53
+ let agreements = 0;
54
+ let disagreements = 0;
55
+
56
+ for (const message of personaMessages) {
57
+ if (message.sentiment === 'agree') agreements++;
58
+ else if (message.sentiment === 'disagree') disagreements++;
59
+ else if (message.sentiment === 'partial') {
60
+ agreements += 0.5;
61
+ disagreements += 0.5;
62
+ }
63
+ }
64
+
65
+ const total = agreements + disagreements;
66
+ if (total === 0) return 0.5; // Neutral if no explicit sentiments
67
+
68
+ return agreements / total;
69
+ }
70
+
71
+ /**
72
+ * Identify key claims made in the discussion
73
+ */
74
+ export function extractKeyClaims(council: Council): string[] {
75
+ const claims: string[] = [];
76
+
77
+ for (const message of council.messages) {
78
+ if (message.claims) {
79
+ for (const claim of message.claims) {
80
+ if (claim.type === 'assertion' || claim.type === 'proposal') {
81
+ claims.push(claim.text);
82
+ }
83
+ }
84
+ }
85
+ }
86
+
87
+ return claims;
88
+ }
89
+
90
+ /**
91
+ * Identify points of agreement across personas
92
+ */
93
+ export function findAgreements(council: Council): string[] {
94
+ // Look for messages where sentiment is 'agree' and extract what they agree on
95
+ const agreements: string[] = [];
96
+
97
+ for (let i = 1; i < council.messages.length; i++) {
98
+ const message = council.messages[i];
99
+ if (message.sentiment === 'agree' && message.replyingTo) {
100
+ const original = council.messages.find((m) => m.id === message.replyingTo);
101
+ if (original) {
102
+ // Extract first sentence as the point of agreement
103
+ const firstSentence = original.content.split(/[.!?]/)[0];
104
+ if (firstSentence && firstSentence.length > 10) {
105
+ agreements.push(firstSentence.trim());
106
+ }
107
+ }
108
+ }
109
+ }
110
+
111
+ return [...new Set(agreements)];
112
+ }
113
+
114
+ /**
115
+ * Identify key tensions/disagreements
116
+ */
117
+ export function findTensions(council: Council): Array<{ between: [string, string]; topic: string }> {
118
+ const tensions: Array<{ between: [string, string]; topic: string }> = [];
119
+
120
+ for (let i = 1; i < council.messages.length; i++) {
121
+ const message = council.messages[i];
122
+ if (message.sentiment === 'disagree' && message.replyingTo) {
123
+ const original = council.messages.find((m) => m.id === message.replyingTo);
124
+ if (original && original.speakerType === 'persona') {
125
+ const speaker1 = council.personas.find((p) => p.id === original.speakerId);
126
+ const speaker2 = council.personas.find((p) => p.id === message.speakerId);
127
+
128
+ if (speaker1 && speaker2) {
129
+ tensions.push({
130
+ between: [speaker1.name, speaker2.name],
131
+ topic: original.content.slice(0, 100),
132
+ });
133
+ }
134
+ }
135
+ }
136
+ }
137
+
138
+ return tensions;
139
+ }
140
+
141
+ /**
142
+ * Generate position summary for each persona
143
+ */
144
+ export function summarizePositions(
145
+ council: Council
146
+ ): Array<{ persona: string; position: string; confidence: number }> {
147
+ const positions: Array<{ persona: string; position: string; confidence: number }> = [];
148
+
149
+ for (const persona of council.personas) {
150
+ const personaMessages = council.messages
151
+ .filter((m) => m.speakerId === persona.id)
152
+ .slice(-3);
153
+
154
+ if (personaMessages.length === 0) {
155
+ positions.push({
156
+ persona: persona.name,
157
+ position: 'Has not spoken yet',
158
+ confidence: 0,
159
+ });
160
+ continue;
161
+ }
162
+
163
+ // Use the last message's stance or summarize
164
+ const lastMessage = personaMessages[personaMessages.length - 1];
165
+ positions.push({
166
+ persona: persona.name,
167
+ position: lastMessage.stance || lastMessage.content.slice(0, 150),
168
+ confidence: lastMessage.confidence || 0.5,
169
+ });
170
+ }
171
+
172
+ return positions;
173
+ }
174
+
175
+ /**
176
+ * Configuration for synthesis generation
177
+ */
178
+ export interface SynthesisConfig {
179
+ synthesizer?: 'system' | Persona;
180
+ model?: string;
181
+ provider?: string;
182
+ includeVotes?: boolean;
183
+ }
184
+
185
+ /**
186
+ * Generate synthesis request parameters
187
+ * Returns the prompt and model configuration for the caller to execute
188
+ */
189
+ export function prepareSynthesisRequest(
190
+ council: Council,
191
+ config?: SynthesisConfig
192
+ ): {
193
+ prompt: string;
194
+ model: string;
195
+ provider: string;
196
+ synthesizerId: string;
197
+ } {
198
+ const synthesizer = config?.synthesizer;
199
+ const isPersona = synthesizer && synthesizer !== 'system';
200
+
201
+ return {
202
+ prompt: buildSynthesisPrompt(council),
203
+ model: isPersona ? (synthesizer as Persona).model : (config?.model || DEFAULT_SYNTHESIS_MODEL),
204
+ provider: isPersona ? (synthesizer as Persona).provider : (config?.provider || DEFAULT_SYNTHESIS_PROVIDER),
205
+ synthesizerId: isPersona ? (synthesizer as Persona).id : 'system',
206
+ };
207
+ }
208
+
209
+ /**
210
+ * Quick consensus check (heuristic-based, no LLM call)
211
+ */
212
+ export function quickConsensusCheck(council: Council): {
213
+ level: number;
214
+ status: 'converging' | 'diverging' | 'stable';
215
+ recommendation: string;
216
+ } {
217
+ const recentMessages = council.messages.slice(-10);
218
+ const personaMessages = recentMessages.filter((m) => m.speakerType === 'persona');
219
+
220
+ if (personaMessages.length < 3) {
221
+ return {
222
+ level: 0.5,
223
+ status: 'stable',
224
+ recommendation: 'Continue discussion to gather more perspectives',
225
+ };
226
+ }
227
+
228
+ // Count sentiment trends
229
+ let recentAgree = 0;
230
+ let recentDisagree = 0;
231
+
232
+ for (const msg of personaMessages.slice(-5)) {
233
+ if (msg.sentiment === 'agree') recentAgree++;
234
+ if (msg.sentiment === 'disagree') recentDisagree++;
235
+ }
236
+
237
+ const level = recentAgree / (recentAgree + recentDisagree || 1);
238
+
239
+ if (recentAgree > recentDisagree * 2) {
240
+ return {
241
+ level,
242
+ status: 'converging',
243
+ recommendation: 'Consider generating a resolution',
244
+ };
245
+ }
246
+
247
+ if (recentDisagree > recentAgree * 2) {
248
+ return {
249
+ level,
250
+ status: 'diverging',
251
+ recommendation: 'Consider a synthesis to identify common ground',
252
+ };
253
+ }
254
+
255
+ return {
256
+ level,
257
+ status: 'stable',
258
+ recommendation: 'Continue discussion or ask specific questions',
259
+ };
260
+ }
261
+
262
+ /**
263
+ * Create a round summary message
264
+ */
265
+ export function createRoundSummary(
266
+ council: Council,
267
+ roundNumber: number
268
+ ): CouncilMessage {
269
+ const consensus = quickConsensusCheck(council);
270
+ const positions = summarizePositions(council);
271
+
272
+ const positionSummary = positions
273
+ .map((p) => `• **${p.persona}**: ${p.position.slice(0, 100)}${p.position.length > 100 ? '...' : ''}`)
274
+ .join('\n');
275
+
276
+ return {
277
+ id: crypto.randomUUID(),
278
+ timestamp: new Date().toISOString(),
279
+ speakerId: 'system',
280
+ speakerType: 'system',
281
+ content: `**Round ${roundNumber} Summary**
282
+
283
+ ${positionSummary}
284
+
285
+ Consensus level: ${Math.round(consensus.level * 100)}%
286
+ Status: ${consensus.status}
287
+ ${consensus.recommendation}`,
288
+ tokensUsed: 0,
289
+ latencyMs: 0,
290
+ };
291
+ }