@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,438 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Council: Ledger Store
|
|
3
|
+
* Append-only, chunked storage for deliberation audit trail
|
|
4
|
+
*
|
|
5
|
+
* Storage layout:
|
|
6
|
+
* - ledger-index-{councilId}: LedgerIndex (entry count, chunk boundaries, total tokens)
|
|
7
|
+
* - ledger-chunk-{councilId}-{n}: LedgerEntry[] (chunk of entries)
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import type { LedgerEntry, LedgerEntryType, LedgerIndex, DeliberationPhase } from './types';
|
|
11
|
+
import { councilDataStore } from './storage-cleanup';
|
|
12
|
+
|
|
13
|
+
const LEDGER_INDEX_PREFIX = 'ledger-index-';
|
|
14
|
+
const LEDGER_CHUNK_PREFIX = 'ledger-chunk-';
|
|
15
|
+
const ENTRIES_PER_CHUNK = 20;
|
|
16
|
+
|
|
17
|
+
// ============================================================================
|
|
18
|
+
// Ledger Index Operations
|
|
19
|
+
// ============================================================================
|
|
20
|
+
|
|
21
|
+
function getLedgerIndexKey(councilId: string): string {
|
|
22
|
+
return `${LEDGER_INDEX_PREFIX}${councilId}`;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function getLedgerChunkKey(councilId: string, chunkIndex: number): string {
|
|
26
|
+
return `${LEDGER_CHUNK_PREFIX}${councilId}-${chunkIndex}`;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function loadLedgerIndex(councilId: string): LedgerIndex {
|
|
30
|
+
try {
|
|
31
|
+
const key = getLedgerIndexKey(councilId);
|
|
32
|
+
const raw = councilDataStore.getItem(key);
|
|
33
|
+
if (!raw) {
|
|
34
|
+
return createEmptyIndex(councilId);
|
|
35
|
+
}
|
|
36
|
+
return JSON.parse(raw) as LedgerIndex;
|
|
37
|
+
} catch (error) {
|
|
38
|
+
console.error('[LedgerStore] Failed to load index:', error);
|
|
39
|
+
return createEmptyIndex(councilId);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function saveLedgerIndex(index: LedgerIndex): void {
|
|
44
|
+
const key = getLedgerIndexKey(index.councilId);
|
|
45
|
+
index.lastUpdated = new Date().toISOString();
|
|
46
|
+
councilDataStore.setItem(key, JSON.stringify(index));
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function createEmptyIndex(councilId: string): LedgerIndex {
|
|
50
|
+
return {
|
|
51
|
+
councilId,
|
|
52
|
+
entryCount: 0,
|
|
53
|
+
chunkCount: 0,
|
|
54
|
+
chunkBoundaries: [],
|
|
55
|
+
totalTokens: 0,
|
|
56
|
+
lastUpdated: new Date().toISOString(),
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// ============================================================================
|
|
61
|
+
// Chunk Operations
|
|
62
|
+
// ============================================================================
|
|
63
|
+
|
|
64
|
+
function loadChunk(councilId: string, chunkIndex: number): LedgerEntry[] {
|
|
65
|
+
try {
|
|
66
|
+
const key = getLedgerChunkKey(councilId, chunkIndex);
|
|
67
|
+
const raw = councilDataStore.getItem(key);
|
|
68
|
+
if (!raw) {
|
|
69
|
+
return [];
|
|
70
|
+
}
|
|
71
|
+
return JSON.parse(raw) as LedgerEntry[];
|
|
72
|
+
} catch (error) {
|
|
73
|
+
console.error('[LedgerStore] Failed to load chunk:', chunkIndex, error);
|
|
74
|
+
return [];
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function saveChunk(councilId: string, chunkIndex: number, entries: LedgerEntry[]): void {
|
|
79
|
+
const key = getLedgerChunkKey(councilId, chunkIndex);
|
|
80
|
+
const data = JSON.stringify(entries);
|
|
81
|
+
councilDataStore.setItem(key, data);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function deleteChunk(councilId: string, chunkIndex: number): void {
|
|
85
|
+
try {
|
|
86
|
+
const key = getLedgerChunkKey(councilId, chunkIndex);
|
|
87
|
+
councilDataStore.removeItem(key);
|
|
88
|
+
} catch (error) {
|
|
89
|
+
console.error('[LedgerStore] Failed to delete chunk:', chunkIndex, error);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// ============================================================================
|
|
94
|
+
// Public API
|
|
95
|
+
// ============================================================================
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Append a new entry to the ledger (append-only)
|
|
99
|
+
*/
|
|
100
|
+
export function appendEntry(councilId: string, entry: LedgerEntry): void {
|
|
101
|
+
const index = loadLedgerIndex(councilId);
|
|
102
|
+
|
|
103
|
+
// Determine which chunk to write to
|
|
104
|
+
let currentChunkIndex = index.chunkCount > 0 ? index.chunkCount - 1 : 0;
|
|
105
|
+
let chunk = loadChunk(councilId, currentChunkIndex);
|
|
106
|
+
|
|
107
|
+
// Check if we need a new chunk
|
|
108
|
+
if (chunk.length >= ENTRIES_PER_CHUNK) {
|
|
109
|
+
currentChunkIndex++;
|
|
110
|
+
index.chunkBoundaries.push(index.entryCount);
|
|
111
|
+
chunk = [];
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// Append entry
|
|
115
|
+
chunk.push(entry);
|
|
116
|
+
saveChunk(councilId, currentChunkIndex, chunk);
|
|
117
|
+
|
|
118
|
+
// Update index
|
|
119
|
+
index.entryCount++;
|
|
120
|
+
index.chunkCount = currentChunkIndex + 1;
|
|
121
|
+
index.totalTokens += entry.tokensUsed ?? 0;
|
|
122
|
+
saveLedgerIndex(index);
|
|
123
|
+
|
|
124
|
+
console.log('[LedgerStore] Appended entry:', entry.id, 'type:', entry.entryType);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Get entries with optional filtering
|
|
129
|
+
*/
|
|
130
|
+
export function getEntries(
|
|
131
|
+
councilId: string,
|
|
132
|
+
options?: {
|
|
133
|
+
types?: LedgerEntryType[];
|
|
134
|
+
phase?: DeliberationPhase;
|
|
135
|
+
round?: number;
|
|
136
|
+
authorPersonaId?: string;
|
|
137
|
+
limit?: number;
|
|
138
|
+
offset?: number;
|
|
139
|
+
}
|
|
140
|
+
): LedgerEntry[] {
|
|
141
|
+
const index = loadLedgerIndex(councilId);
|
|
142
|
+
|
|
143
|
+
if (index.entryCount === 0) {
|
|
144
|
+
return [];
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// Load all entries
|
|
148
|
+
let allEntries: LedgerEntry[] = [];
|
|
149
|
+
for (let i = 0; i < index.chunkCount; i++) {
|
|
150
|
+
const chunk = loadChunk(councilId, i);
|
|
151
|
+
allEntries = allEntries.concat(chunk);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// Apply filters
|
|
155
|
+
let filtered = allEntries;
|
|
156
|
+
|
|
157
|
+
if (options?.types && options.types.length > 0) {
|
|
158
|
+
filtered = filtered.filter((e) => options.types!.includes(e.entryType));
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
if (options?.phase) {
|
|
162
|
+
filtered = filtered.filter((e) => e.phase === options.phase);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
if (options?.round !== undefined) {
|
|
166
|
+
filtered = filtered.filter((e) => e.roundNumber === options.round);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
if (options?.authorPersonaId) {
|
|
170
|
+
filtered = filtered.filter((e) => e.authorPersonaId === options.authorPersonaId);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// Apply offset and limit
|
|
174
|
+
if (options?.offset) {
|
|
175
|
+
filtered = filtered.slice(options.offset);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
if (options?.limit) {
|
|
179
|
+
filtered = filtered.slice(0, options.limit);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
return filtered;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* Get all entries (no filtering)
|
|
187
|
+
*/
|
|
188
|
+
export function getAllEntries(councilId: string): LedgerEntry[] {
|
|
189
|
+
return getEntries(councilId);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* Get a single entry by ID
|
|
194
|
+
*/
|
|
195
|
+
export function getEntry(councilId: string, entryId: string): LedgerEntry | null {
|
|
196
|
+
const entries = getEntries(councilId);
|
|
197
|
+
return entries.find((e) => e.id === entryId) ?? null;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* Get the most recent entry of a specific type
|
|
202
|
+
*/
|
|
203
|
+
export function getLatestOfType(councilId: string, type: LedgerEntryType): LedgerEntry | null {
|
|
204
|
+
const entries = getEntries(councilId, { types: [type] });
|
|
205
|
+
return entries.length > 0 ? entries[entries.length - 1] : null;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* Get entries for a specific round
|
|
210
|
+
*/
|
|
211
|
+
export function getEntriesForRound(councilId: string, round: number): LedgerEntry[] {
|
|
212
|
+
return getEntries(councilId, { round });
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* Get entries by author
|
|
217
|
+
*/
|
|
218
|
+
export function getEntriesByAuthor(councilId: string, authorPersonaId: string): LedgerEntry[] {
|
|
219
|
+
return getEntries(councilId, { authorPersonaId });
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
/**
|
|
223
|
+
* Get the total token count for the ledger
|
|
224
|
+
*/
|
|
225
|
+
export function getLedgerTokenCount(councilId: string): number {
|
|
226
|
+
const index = loadLedgerIndex(councilId);
|
|
227
|
+
return index.totalTokens;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
/**
|
|
231
|
+
* Get the entry count for the ledger
|
|
232
|
+
*/
|
|
233
|
+
export function getLedgerEntryCount(councilId: string): number {
|
|
234
|
+
const index = loadLedgerIndex(councilId);
|
|
235
|
+
return index.entryCount;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
/**
|
|
239
|
+
* Get the ledger index
|
|
240
|
+
*/
|
|
241
|
+
export function getLedgerIndex(councilId: string): LedgerIndex {
|
|
242
|
+
return loadLedgerIndex(councilId);
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
/**
|
|
246
|
+
* Get entries for recent rounds (for context building)
|
|
247
|
+
* Returns entries from the most recent N rounds
|
|
248
|
+
*/
|
|
249
|
+
export function getRecentRoundEntries(councilId: string, numRounds: number): LedgerEntry[] {
|
|
250
|
+
const allEntries = getEntries(councilId);
|
|
251
|
+
|
|
252
|
+
// Find all unique round numbers
|
|
253
|
+
const rounds = new Set<number>();
|
|
254
|
+
for (const entry of allEntries) {
|
|
255
|
+
if (entry.roundNumber !== undefined) {
|
|
256
|
+
rounds.add(entry.roundNumber);
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
// Get the most recent N rounds
|
|
261
|
+
const sortedRounds = Array.from(rounds).sort((a, b) => b - a);
|
|
262
|
+
const recentRounds = sortedRounds.slice(0, numRounds);
|
|
263
|
+
|
|
264
|
+
// Filter entries to only include recent rounds
|
|
265
|
+
return allEntries.filter(
|
|
266
|
+
(e) => e.roundNumber !== undefined && recentRounds.includes(e.roundNumber)
|
|
267
|
+
);
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
/**
|
|
271
|
+
* Get manager notes (questions and redirects) - these are never summarized
|
|
272
|
+
*/
|
|
273
|
+
export function getManagerNotes(councilId: string, beforeRound?: number): LedgerEntry[] {
|
|
274
|
+
const entries = getEntries(councilId, {
|
|
275
|
+
types: ['manager_question', 'manager_redirect'],
|
|
276
|
+
});
|
|
277
|
+
|
|
278
|
+
if (beforeRound !== undefined) {
|
|
279
|
+
return entries.filter((e) => (e.roundNumber ?? 0) < beforeRound);
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
return entries;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
/**
|
|
286
|
+
* Clear all entries for a council (use with caution - violates append-only principle)
|
|
287
|
+
* Only for testing or explicit user request
|
|
288
|
+
*/
|
|
289
|
+
export function clearLedger(councilId: string): void {
|
|
290
|
+
const index = loadLedgerIndex(councilId);
|
|
291
|
+
|
|
292
|
+
// Delete all chunks
|
|
293
|
+
for (let i = 0; i < index.chunkCount; i++) {
|
|
294
|
+
deleteChunk(councilId, i);
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
// Reset index
|
|
298
|
+
saveLedgerIndex(createEmptyIndex(councilId));
|
|
299
|
+
|
|
300
|
+
console.log('[LedgerStore] Cleared ledger for council:', councilId);
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
/**
|
|
304
|
+
* Delete the entire ledger for a council
|
|
305
|
+
*/
|
|
306
|
+
export function deleteLedger(councilId: string): void {
|
|
307
|
+
const index = loadLedgerIndex(councilId);
|
|
308
|
+
|
|
309
|
+
// Delete all chunks
|
|
310
|
+
for (let i = 0; i < index.chunkCount; i++) {
|
|
311
|
+
deleteChunk(councilId, i);
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
// Delete index
|
|
315
|
+
try {
|
|
316
|
+
const key = getLedgerIndexKey(councilId);
|
|
317
|
+
councilDataStore.removeItem(key);
|
|
318
|
+
} catch (error) {
|
|
319
|
+
console.error('[LedgerStore] Failed to delete index:', error);
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
console.log('[LedgerStore] Deleted ledger for council:', councilId);
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
/**
|
|
326
|
+
* Check if a ledger exists for a council
|
|
327
|
+
*/
|
|
328
|
+
export function ledgerExists(councilId: string): boolean {
|
|
329
|
+
const key = getLedgerIndexKey(councilId);
|
|
330
|
+
return councilDataStore.getItem(key) !== null;
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
/**
|
|
334
|
+
* Get paginated entries (for UI)
|
|
335
|
+
*/
|
|
336
|
+
export function getPaginatedEntries(
|
|
337
|
+
councilId: string,
|
|
338
|
+
page: number,
|
|
339
|
+
pageSize: number = 20
|
|
340
|
+
): { entries: LedgerEntry[]; total: number; hasMore: boolean } {
|
|
341
|
+
const index = loadLedgerIndex(councilId);
|
|
342
|
+
const offset = page * pageSize;
|
|
343
|
+
const entries = getEntries(councilId, { offset, limit: pageSize });
|
|
344
|
+
|
|
345
|
+
return {
|
|
346
|
+
entries,
|
|
347
|
+
total: index.entryCount,
|
|
348
|
+
hasMore: offset + entries.length < index.entryCount,
|
|
349
|
+
};
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
/**
|
|
353
|
+
* Format entries for context building
|
|
354
|
+
*/
|
|
355
|
+
export function formatEntriesForContext(entries: LedgerEntry[]): string {
|
|
356
|
+
return entries
|
|
357
|
+
.map((e) => {
|
|
358
|
+
const roundLabel = e.roundNumber !== undefined ? `, Round ${e.roundNumber}` : '';
|
|
359
|
+
return `[${e.authorPersonaId}${roundLabel}, ${e.entryType}]:\n${e.content}`;
|
|
360
|
+
})
|
|
361
|
+
.join('\n\n');
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
/**
|
|
365
|
+
* Build mechanical summary (no API call)
|
|
366
|
+
*/
|
|
367
|
+
export function buildMechanicalSummary(entries: LedgerEntry[]): string {
|
|
368
|
+
return entries
|
|
369
|
+
.filter((e) => ['analysis', 'response', 'proposal'].includes(e.entryType))
|
|
370
|
+
.map((e) => {
|
|
371
|
+
const sentences = e.content.split(/[.!?]\s/);
|
|
372
|
+
const firstTwo = sentences.slice(0, 2).join('. ') + '.';
|
|
373
|
+
return `${e.authorPersonaId} (${e.entryType}): ${firstTwo}`;
|
|
374
|
+
})
|
|
375
|
+
.join('\n\n');
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
// ============================================================================
|
|
379
|
+
// Ledger Store Class (for React integration)
|
|
380
|
+
// ============================================================================
|
|
381
|
+
|
|
382
|
+
export class LedgerStore {
|
|
383
|
+
private listeners: Map<string, Set<() => void>> = new Map();
|
|
384
|
+
|
|
385
|
+
subscribe(councilId: string, listener: () => void): () => void {
|
|
386
|
+
if (!this.listeners.has(councilId)) {
|
|
387
|
+
this.listeners.set(councilId, new Set());
|
|
388
|
+
}
|
|
389
|
+
this.listeners.get(councilId)!.add(listener);
|
|
390
|
+
return () => this.listeners.get(councilId)?.delete(listener);
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
private notify(councilId: string): void {
|
|
394
|
+
this.listeners.get(councilId)?.forEach((listener) => listener());
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
append(councilId: string, entry: LedgerEntry): void {
|
|
398
|
+
appendEntry(councilId, entry);
|
|
399
|
+
this.notify(councilId);
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
getAll(councilId: string): LedgerEntry[] {
|
|
403
|
+
return getAllEntries(councilId);
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
get(councilId: string, entryId: string): LedgerEntry | null {
|
|
407
|
+
return getEntry(councilId, entryId);
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
getByType(councilId: string, type: LedgerEntryType): LedgerEntry[] {
|
|
411
|
+
return getEntries(councilId, { types: [type] });
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
getByRound(councilId: string, round: number): LedgerEntry[] {
|
|
415
|
+
return getEntriesForRound(councilId, round);
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
getLatest(councilId: string, type: LedgerEntryType): LedgerEntry | null {
|
|
419
|
+
return getLatestOfType(councilId, type);
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
getTokenCount(councilId: string): number {
|
|
423
|
+
return getLedgerTokenCount(councilId);
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
clear(councilId: string): void {
|
|
427
|
+
clearLedger(councilId);
|
|
428
|
+
this.notify(councilId);
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
delete(councilId: string): void {
|
|
432
|
+
deleteLedger(councilId);
|
|
433
|
+
this.listeners.delete(councilId);
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
// Singleton instance for app-wide use
|
|
438
|
+
export const ledgerStore = new LedgerStore();
|