@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,763 @@
1
+ /**
2
+ * Council: Context & Artifact Store
3
+ * CRUD operations for context artifacts, patches, and other deliberation artifacts
4
+ *
5
+ * Storage layout:
6
+ * - context-{councilId}: Current ContextArtifact (latest version)
7
+ * - context-history-{councilId}: ContextArtifact[] (all versions)
8
+ * - context-patches-{councilId}: ContextPatch[] (all proposals)
9
+ * - decision-{councilId}: DecisionArtifact
10
+ * - plan-{councilId}: PlanArtifact
11
+ * - directive-{councilId}: DirectiveArtifact
12
+ * - outputs-{councilId}: OutputArtifact[] (all outputs including revisions)
13
+ */
14
+
15
+ import type {
16
+ ContextArtifact,
17
+ ContextPatch,
18
+ DecisionArtifact,
19
+ PlanArtifact,
20
+ DirectiveArtifact,
21
+ OutputArtifact,
22
+ DeliberationRole,
23
+ } from './types';
24
+ import { councilDataStore } from './storage-cleanup';
25
+
26
+ // Storage key prefixes
27
+ const CONTEXT_PREFIX = 'context-';
28
+ const CONTEXT_HISTORY_PREFIX = 'context-history-';
29
+ const CONTEXT_PATCHES_PREFIX = 'context-patches-';
30
+ const DECISION_PREFIX = 'decision-';
31
+ const PLAN_PREFIX = 'plan-';
32
+ const DIRECTIVE_PREFIX = 'directive-';
33
+ const OUTPUTS_PREFIX = 'outputs-';
34
+
35
+ // ============================================================================
36
+ // Helper Functions
37
+ // ============================================================================
38
+
39
+ function loadJson<T>(key: string, defaultValue: T): T {
40
+ try {
41
+ const raw = councilDataStore.getItem(key);
42
+ if (!raw) return defaultValue;
43
+ return JSON.parse(raw) as T;
44
+ } catch (error) {
45
+ console.error('[ContextStore] Failed to load:', key, error);
46
+ return defaultValue;
47
+ }
48
+ }
49
+
50
+ function saveJson<T>(key: string, value: T): void {
51
+ councilDataStore.setItem(key, JSON.stringify(value));
52
+ }
53
+
54
+ function removeKey(key: string): void {
55
+ councilDataStore.removeItem(key);
56
+ }
57
+
58
+ // ============================================================================
59
+ // Context Artifact Operations
60
+ // ============================================================================
61
+
62
+ /**
63
+ * Get the current (latest) context artifact for a council
64
+ */
65
+ export function getCurrentContext(councilId: string): ContextArtifact | null {
66
+ const key = `${CONTEXT_PREFIX}${councilId}`;
67
+ return loadJson<ContextArtifact | null>(key, null);
68
+ }
69
+
70
+ /**
71
+ * Get all context versions for a council
72
+ */
73
+ export function getContextHistory(councilId: string): ContextArtifact[] {
74
+ const key = `${CONTEXT_HISTORY_PREFIX}${councilId}`;
75
+ return loadJson<ContextArtifact[]>(key, []);
76
+ }
77
+
78
+ /**
79
+ * Get a specific context version
80
+ */
81
+ export function getContextVersion(councilId: string, version: number): ContextArtifact | null {
82
+ const history = getContextHistory(councilId);
83
+ return history.find((c) => c.version === version) ?? null;
84
+ }
85
+
86
+ /**
87
+ * Create the initial context (version 1) from problem statement
88
+ */
89
+ export function createInitialContext(
90
+ councilId: string,
91
+ content: string,
92
+ authorPersonaId?: string
93
+ ): ContextArtifact {
94
+ const context: ContextArtifact = {
95
+ id: crypto.randomUUID(),
96
+ councilId,
97
+ version: 1,
98
+ content,
99
+ changeSummary: 'Initial problem statement',
100
+ authorRole: 'manager',
101
+ authorPersonaId,
102
+ createdAt: new Date().toISOString(),
103
+ };
104
+
105
+ // Save as current and to history
106
+ saveJson(`${CONTEXT_PREFIX}${councilId}`, context);
107
+ saveJson(`${CONTEXT_HISTORY_PREFIX}${councilId}`, [context]);
108
+
109
+ console.log('[ContextStore] Created initial context v1 for council:', councilId);
110
+ return context;
111
+ }
112
+
113
+ /**
114
+ * Create a new context version from an accepted patch
115
+ */
116
+ export function createContextVersion(
117
+ councilId: string,
118
+ newContent: string,
119
+ changeSummary: string,
120
+ authorRole: DeliberationRole,
121
+ authorPersonaId?: string,
122
+ roundNumber?: number
123
+ ): ContextArtifact {
124
+ const current = getCurrentContext(councilId);
125
+ const baseVersion = current?.version ?? 0;
126
+
127
+ const context: ContextArtifact = {
128
+ id: crypto.randomUUID(),
129
+ councilId,
130
+ version: baseVersion + 1,
131
+ content: newContent,
132
+ createdFromVersion: baseVersion,
133
+ changeSummary,
134
+ authorRole,
135
+ authorPersonaId,
136
+ roundNumber,
137
+ createdAt: new Date().toISOString(),
138
+ };
139
+
140
+ // Save as current
141
+ saveJson(`${CONTEXT_PREFIX}${councilId}`, context);
142
+
143
+ // Append to history
144
+ const history = getContextHistory(councilId);
145
+ history.push(context);
146
+ saveJson(`${CONTEXT_HISTORY_PREFIX}${councilId}`, history);
147
+
148
+ console.log('[ContextStore] Created context v', context.version, 'for council:', councilId);
149
+ return context;
150
+ }
151
+
152
+ /**
153
+ * Get diff between two context versions (simple text comparison)
154
+ */
155
+ export function getContextDiff(
156
+ councilId: string,
157
+ fromVersion: number,
158
+ toVersion: number
159
+ ): { from: string; to: string; changeSummary: string } | null {
160
+ const fromContext = getContextVersion(councilId, fromVersion);
161
+ const toContext = getContextVersion(councilId, toVersion);
162
+
163
+ if (!fromContext || !toContext) return null;
164
+
165
+ return {
166
+ from: fromContext.content,
167
+ to: toContext.content,
168
+ changeSummary: toContext.changeSummary,
169
+ };
170
+ }
171
+
172
+ // ============================================================================
173
+ // Context Patch Operations
174
+ // ============================================================================
175
+
176
+ /**
177
+ * Get all patches for a council
178
+ */
179
+ export function getAllPatches(councilId: string): ContextPatch[] {
180
+ const key = `${CONTEXT_PATCHES_PREFIX}${councilId}`;
181
+ return loadJson<ContextPatch[]>(key, []);
182
+ }
183
+
184
+ /**
185
+ * Get pending patches for a council
186
+ */
187
+ export function getPendingPatches(councilId: string): ContextPatch[] {
188
+ return getAllPatches(councilId).filter((p) => p.status === 'pending');
189
+ }
190
+
191
+ /**
192
+ * Get a patch by ID
193
+ */
194
+ export function getPatch(councilId: string, patchId: string): ContextPatch | null {
195
+ const patches = getAllPatches(councilId);
196
+ return patches.find((p) => p.id === patchId) ?? null;
197
+ }
198
+
199
+ /**
200
+ * Create a new context patch proposal
201
+ */
202
+ export function createPatch(
203
+ councilId: string,
204
+ targetContextId: string,
205
+ baseVersion: number,
206
+ diff: string,
207
+ rationale: string,
208
+ authorPersonaId: string,
209
+ roundNumber: number
210
+ ): ContextPatch {
211
+ const patch: ContextPatch = {
212
+ id: crypto.randomUUID(),
213
+ councilId,
214
+ targetContextId,
215
+ baseVersion,
216
+ diff,
217
+ rationale,
218
+ authorPersonaId,
219
+ roundNumber,
220
+ status: 'pending',
221
+ createdAt: new Date().toISOString(),
222
+ };
223
+
224
+ const patches = getAllPatches(councilId);
225
+ patches.push(patch);
226
+ saveJson(`${CONTEXT_PATCHES_PREFIX}${councilId}`, patches);
227
+
228
+ console.log('[ContextStore] Created patch:', patch.id, 'for council:', councilId);
229
+ return patch;
230
+ }
231
+
232
+ /**
233
+ * Accept a patch and create new context version
234
+ *
235
+ * @param councilId - The council ID
236
+ * @param patchId - The patch to accept
237
+ * @param reviewedBy - The persona ID of the reviewer (manager)
238
+ * @param reviewReason - The reason for acceptance
239
+ * @param newContent - The new context content with patch applied
240
+ * @param options - Optional settings
241
+ * @param options.allowStale - If true, accept stale patches with a warning (default: false throws error)
242
+ * @param options.changeSummary - Override the change summary (default: uses patch rationale)
243
+ */
244
+ export function acceptPatch(
245
+ councilId: string,
246
+ patchId: string,
247
+ reviewedBy: string,
248
+ reviewReason: string,
249
+ newContent: string,
250
+ options?: {
251
+ allowStale?: boolean;
252
+ changeSummary?: string;
253
+ }
254
+ ): { patch: ContextPatch; newContext: ContextArtifact; wasStale: boolean } {
255
+ const patches = getAllPatches(councilId);
256
+ const patchIndex = patches.findIndex((p) => p.id === patchId);
257
+
258
+ if (patchIndex === -1) {
259
+ throw new Error(`Patch not found: ${patchId}`);
260
+ }
261
+
262
+ const patch = patches[patchIndex];
263
+ const current = getCurrentContext(councilId);
264
+ const wasStale = current ? patch.baseVersion < current.version : false;
265
+
266
+ // Check if patch is stale (base version no longer current)
267
+ if (wasStale) {
268
+ if (!options?.allowStale) {
269
+ throw new Error(
270
+ `Patch is stale: baseVersion ${patch.baseVersion} < currentVersion ${current!.version}. ` +
271
+ `The patch needs to be rebased or explicitly accepted with allowStale: true.`
272
+ );
273
+ }
274
+ console.warn(
275
+ '[ContextStore] Accepting stale patch - base version:',
276
+ patch.baseVersion,
277
+ 'current:',
278
+ current!.version
279
+ );
280
+ }
281
+
282
+ // Update patch status
283
+ patch.status = 'accepted';
284
+ patch.reviewedBy = reviewedBy;
285
+ patch.reviewReason = reviewReason;
286
+ patch.reviewedAt = new Date().toISOString();
287
+ patches[patchIndex] = patch;
288
+ saveJson(`${CONTEXT_PATCHES_PREFIX}${councilId}`, patches);
289
+
290
+ // Use the patch's rationale as the change summary, or allow override
291
+ const changeSummary = options?.changeSummary || patch.rationale || `Accepted change from consultant`;
292
+
293
+ // Create new context version
294
+ const newContext = createContextVersion(
295
+ councilId,
296
+ newContent,
297
+ changeSummary,
298
+ 'manager',
299
+ reviewedBy,
300
+ patch.roundNumber
301
+ );
302
+
303
+ console.log(
304
+ '[ContextStore] Accepted patch:',
305
+ patchId,
306
+ '-> context v',
307
+ newContext.version,
308
+ wasStale ? '(STALE)' : ''
309
+ );
310
+ return { patch, newContext, wasStale };
311
+ }
312
+
313
+ /**
314
+ * Reject a patch
315
+ */
316
+ export function rejectPatch(
317
+ councilId: string,
318
+ patchId: string,
319
+ reviewedBy: string,
320
+ reviewReason: string
321
+ ): ContextPatch {
322
+ const patches = getAllPatches(councilId);
323
+ const patchIndex = patches.findIndex((p) => p.id === patchId);
324
+
325
+ if (patchIndex === -1) {
326
+ throw new Error(`Patch not found: ${patchId}`);
327
+ }
328
+
329
+ const patch = patches[patchIndex];
330
+ patch.status = 'rejected';
331
+ patch.reviewedBy = reviewedBy;
332
+ patch.reviewReason = reviewReason;
333
+ patch.reviewedAt = new Date().toISOString();
334
+ patches[patchIndex] = patch;
335
+ saveJson(`${CONTEXT_PATCHES_PREFIX}${councilId}`, patches);
336
+
337
+ console.log('[ContextStore] Rejected patch:', patchId);
338
+ return patch;
339
+ }
340
+
341
+ /**
342
+ * Check if a patch is stale (base version is no longer current)
343
+ */
344
+ export function isPatchStale(councilId: string, patchId: string): boolean {
345
+ const patch = getPatch(councilId, patchId);
346
+ const current = getCurrentContext(councilId);
347
+
348
+ if (!patch || !current) return false;
349
+ return patch.baseVersion < current.version;
350
+ }
351
+
352
+ // ============================================================================
353
+ // Decision Artifact Operations
354
+ // ============================================================================
355
+
356
+ /**
357
+ * Get the decision artifact for a council
358
+ */
359
+ export function getDecision(councilId: string): DecisionArtifact | null {
360
+ const key = `${DECISION_PREFIX}${councilId}`;
361
+ return loadJson<DecisionArtifact | null>(key, null);
362
+ }
363
+
364
+ /**
365
+ * Create a decision artifact
366
+ */
367
+ export function createDecision(
368
+ councilId: string,
369
+ content: string,
370
+ contextVersionAtDecision: number,
371
+ acceptanceCriteria?: string
372
+ ): DecisionArtifact {
373
+ const decision: DecisionArtifact = {
374
+ id: crypto.randomUUID(),
375
+ councilId,
376
+ content,
377
+ contextVersionAtDecision,
378
+ acceptanceCriteria,
379
+ createdAt: new Date().toISOString(),
380
+ };
381
+
382
+ saveJson(`${DECISION_PREFIX}${councilId}`, decision);
383
+ console.log('[ContextStore] Created decision for council:', councilId);
384
+ return decision;
385
+ }
386
+
387
+ // ============================================================================
388
+ // Plan Artifact Operations
389
+ // ============================================================================
390
+
391
+ /**
392
+ * Get the plan artifact for a council
393
+ */
394
+ export function getPlan(councilId: string): PlanArtifact | null {
395
+ const key = `${PLAN_PREFIX}${councilId}`;
396
+ return loadJson<PlanArtifact | null>(key, null);
397
+ }
398
+
399
+ /**
400
+ * Create a plan artifact
401
+ */
402
+ export function createPlan(
403
+ councilId: string,
404
+ content: string,
405
+ decisionId: string
406
+ ): PlanArtifact {
407
+ const plan: PlanArtifact = {
408
+ id: crypto.randomUUID(),
409
+ councilId,
410
+ content,
411
+ decisionId,
412
+ createdAt: new Date().toISOString(),
413
+ };
414
+
415
+ saveJson(`${PLAN_PREFIX}${councilId}`, plan);
416
+ console.log('[ContextStore] Created plan for council:', councilId);
417
+ return plan;
418
+ }
419
+
420
+ // ============================================================================
421
+ // Directive Artifact Operations
422
+ // ============================================================================
423
+
424
+ /**
425
+ * Get the directive artifact for a council
426
+ */
427
+ export function getDirective(councilId: string): DirectiveArtifact | null {
428
+ const key = `${DIRECTIVE_PREFIX}${councilId}`;
429
+ return loadJson<DirectiveArtifact | null>(key, null);
430
+ }
431
+
432
+ /**
433
+ * Create a directive artifact
434
+ */
435
+ export function createDirective(
436
+ councilId: string,
437
+ content: string,
438
+ decisionId: string,
439
+ planId?: string
440
+ ): DirectiveArtifact {
441
+ const directive: DirectiveArtifact = {
442
+ id: crypto.randomUUID(),
443
+ councilId,
444
+ content,
445
+ decisionId,
446
+ planId,
447
+ createdAt: new Date().toISOString(),
448
+ };
449
+
450
+ saveJson(`${DIRECTIVE_PREFIX}${councilId}`, directive);
451
+ console.log('[ContextStore] Created directive for council:', councilId);
452
+ return directive;
453
+ }
454
+
455
+ // ============================================================================
456
+ // Output Artifact Operations
457
+ // ============================================================================
458
+
459
+ /**
460
+ * Get all outputs for a council
461
+ */
462
+ export function getAllOutputs(councilId: string): OutputArtifact[] {
463
+ const key = `${OUTPUTS_PREFIX}${councilId}`;
464
+ return loadJson<OutputArtifact[]>(key, []);
465
+ }
466
+
467
+ /**
468
+ * Get the latest output for a council
469
+ */
470
+ export function getLatestOutput(councilId: string): OutputArtifact | null {
471
+ const outputs = getAllOutputs(councilId);
472
+ return outputs.length > 0 ? outputs[outputs.length - 1] : null;
473
+ }
474
+
475
+ /**
476
+ * Get an output by ID
477
+ */
478
+ export function getOutput(councilId: string, outputId: string): OutputArtifact | null {
479
+ const outputs = getAllOutputs(councilId);
480
+ return outputs.find((o) => o.id === outputId) ?? null;
481
+ }
482
+
483
+ /**
484
+ * Create initial work output
485
+ */
486
+ export function createOutput(
487
+ councilId: string,
488
+ content: string,
489
+ directiveId: string
490
+ ): OutputArtifact {
491
+ const output: OutputArtifact = {
492
+ id: crypto.randomUUID(),
493
+ councilId,
494
+ content,
495
+ directiveId,
496
+ version: 1,
497
+ isRevision: false,
498
+ createdAt: new Date().toISOString(),
499
+ };
500
+
501
+ const outputs = getAllOutputs(councilId);
502
+ outputs.push(output);
503
+ saveJson(`${OUTPUTS_PREFIX}${councilId}`, outputs);
504
+
505
+ console.log('[ContextStore] Created output v1 for council:', councilId);
506
+ return output;
507
+ }
508
+
509
+ /**
510
+ * Create a revision output
511
+ */
512
+ export function createRevisionOutput(
513
+ councilId: string,
514
+ content: string,
515
+ directiveId: string,
516
+ previousOutputId: string
517
+ ): OutputArtifact {
518
+ const outputs = getAllOutputs(councilId);
519
+ const previousOutput = outputs.find((o) => o.id === previousOutputId);
520
+
521
+ if (!previousOutput) {
522
+ throw new Error(`Previous output not found: ${previousOutputId}`);
523
+ }
524
+
525
+ const output: OutputArtifact = {
526
+ id: crypto.randomUUID(),
527
+ councilId,
528
+ content,
529
+ directiveId,
530
+ version: previousOutput.version + 1,
531
+ isRevision: true,
532
+ previousOutputId,
533
+ createdAt: new Date().toISOString(),
534
+ };
535
+
536
+ outputs.push(output);
537
+ saveJson(`${OUTPUTS_PREFIX}${councilId}`, outputs);
538
+
539
+ console.log('[ContextStore] Created revision output v', output.version, 'for council:', councilId);
540
+ return output;
541
+ }
542
+
543
+ // ============================================================================
544
+ // Cleanup Operations
545
+ // ============================================================================
546
+
547
+ /**
548
+ * Delete all artifacts for a council
549
+ */
550
+ export function deleteAllArtifacts(councilId: string): void {
551
+ removeKey(`${CONTEXT_PREFIX}${councilId}`);
552
+ removeKey(`${CONTEXT_HISTORY_PREFIX}${councilId}`);
553
+ removeKey(`${CONTEXT_PATCHES_PREFIX}${councilId}`);
554
+ removeKey(`${DECISION_PREFIX}${councilId}`);
555
+ removeKey(`${PLAN_PREFIX}${councilId}`);
556
+ removeKey(`${DIRECTIVE_PREFIX}${councilId}`);
557
+ removeKey(`${OUTPUTS_PREFIX}${councilId}`);
558
+
559
+ console.log('[ContextStore] Deleted all artifacts for council:', councilId);
560
+ }
561
+
562
+ /**
563
+ * Check if artifacts exist for a council
564
+ */
565
+ export function hasArtifacts(councilId: string): boolean {
566
+ return getCurrentContext(councilId) !== null;
567
+ }
568
+
569
+ // ============================================================================
570
+ // Context Store Class (for React integration)
571
+ // ============================================================================
572
+
573
+ export class ContextStore {
574
+ private listeners: Map<string, Set<() => void>> = new Map();
575
+
576
+ subscribe(councilId: string, listener: () => void): () => void {
577
+ if (!this.listeners.has(councilId)) {
578
+ this.listeners.set(councilId, new Set());
579
+ }
580
+ this.listeners.get(councilId)!.add(listener);
581
+ return () => this.listeners.get(councilId)?.delete(listener);
582
+ }
583
+
584
+ private notify(councilId: string): void {
585
+ this.listeners.get(councilId)?.forEach((listener) => listener());
586
+ }
587
+
588
+ // Context operations
589
+ getCurrentContext(councilId: string): ContextArtifact | null {
590
+ return getCurrentContext(councilId);
591
+ }
592
+
593
+ getContextHistory(councilId: string): ContextArtifact[] {
594
+ return getContextHistory(councilId);
595
+ }
596
+
597
+ getContextVersion(councilId: string, version: number): ContextArtifact | null {
598
+ return getContextVersion(councilId, version);
599
+ }
600
+
601
+ createInitialContext(
602
+ councilId: string,
603
+ content: string,
604
+ authorPersonaId?: string
605
+ ): ContextArtifact {
606
+ const context = createInitialContext(councilId, content, authorPersonaId);
607
+ this.notify(councilId);
608
+ return context;
609
+ }
610
+
611
+ createContextVersion(
612
+ councilId: string,
613
+ newContent: string,
614
+ changeSummary: string,
615
+ authorRole: DeliberationRole,
616
+ authorPersonaId?: string,
617
+ roundNumber?: number
618
+ ): ContextArtifact {
619
+ const context = createContextVersion(
620
+ councilId,
621
+ newContent,
622
+ changeSummary,
623
+ authorRole,
624
+ authorPersonaId,
625
+ roundNumber
626
+ );
627
+ this.notify(councilId);
628
+ return context;
629
+ }
630
+
631
+ // Patch operations
632
+ getPendingPatches(councilId: string): ContextPatch[] {
633
+ return getPendingPatches(councilId);
634
+ }
635
+
636
+ getAllPatches(councilId: string): ContextPatch[] {
637
+ return getAllPatches(councilId);
638
+ }
639
+
640
+ createPatch(
641
+ councilId: string,
642
+ targetContextId: string,
643
+ baseVersion: number,
644
+ diff: string,
645
+ rationale: string,
646
+ authorPersonaId: string,
647
+ roundNumber: number
648
+ ): ContextPatch {
649
+ const patch = createPatch(
650
+ councilId,
651
+ targetContextId,
652
+ baseVersion,
653
+ diff,
654
+ rationale,
655
+ authorPersonaId,
656
+ roundNumber
657
+ );
658
+ this.notify(councilId);
659
+ return patch;
660
+ }
661
+
662
+ acceptPatch(
663
+ councilId: string,
664
+ patchId: string,
665
+ reviewedBy: string,
666
+ reviewReason: string,
667
+ newContent: string,
668
+ options?: { allowStale?: boolean; changeSummary?: string }
669
+ ): { patch: ContextPatch; newContext: ContextArtifact; wasStale: boolean } {
670
+ const result = acceptPatch(councilId, patchId, reviewedBy, reviewReason, newContent, options);
671
+ this.notify(councilId);
672
+ return result;
673
+ }
674
+
675
+ rejectPatch(
676
+ councilId: string,
677
+ patchId: string,
678
+ reviewedBy: string,
679
+ reviewReason: string
680
+ ): ContextPatch {
681
+ const patch = rejectPatch(councilId, patchId, reviewedBy, reviewReason);
682
+ this.notify(councilId);
683
+ return patch;
684
+ }
685
+
686
+ // Decision operations
687
+ getDecision(councilId: string): DecisionArtifact | null {
688
+ return getDecision(councilId);
689
+ }
690
+
691
+ createDecision(
692
+ councilId: string,
693
+ content: string,
694
+ contextVersionAtDecision: number,
695
+ acceptanceCriteria?: string
696
+ ): DecisionArtifact {
697
+ const decision = createDecision(councilId, content, contextVersionAtDecision, acceptanceCriteria);
698
+ this.notify(councilId);
699
+ return decision;
700
+ }
701
+
702
+ // Plan operations
703
+ getPlan(councilId: string): PlanArtifact | null {
704
+ return getPlan(councilId);
705
+ }
706
+
707
+ createPlan(councilId: string, content: string, decisionId: string): PlanArtifact {
708
+ const plan = createPlan(councilId, content, decisionId);
709
+ this.notify(councilId);
710
+ return plan;
711
+ }
712
+
713
+ // Directive operations
714
+ getDirective(councilId: string): DirectiveArtifact | null {
715
+ return getDirective(councilId);
716
+ }
717
+
718
+ createDirective(
719
+ councilId: string,
720
+ content: string,
721
+ decisionId: string,
722
+ planId?: string
723
+ ): DirectiveArtifact {
724
+ const directive = createDirective(councilId, content, decisionId, planId);
725
+ this.notify(councilId);
726
+ return directive;
727
+ }
728
+
729
+ // Output operations
730
+ getLatestOutput(councilId: string): OutputArtifact | null {
731
+ return getLatestOutput(councilId);
732
+ }
733
+
734
+ getAllOutputs(councilId: string): OutputArtifact[] {
735
+ return getAllOutputs(councilId);
736
+ }
737
+
738
+ createOutput(councilId: string, content: string, directiveId: string): OutputArtifact {
739
+ const output = createOutput(councilId, content, directiveId);
740
+ this.notify(councilId);
741
+ return output;
742
+ }
743
+
744
+ createRevisionOutput(
745
+ councilId: string,
746
+ content: string,
747
+ directiveId: string,
748
+ previousOutputId: string
749
+ ): OutputArtifact {
750
+ const output = createRevisionOutput(councilId, content, directiveId, previousOutputId);
751
+ this.notify(councilId);
752
+ return output;
753
+ }
754
+
755
+ // Cleanup
756
+ deleteAll(councilId: string): void {
757
+ deleteAllArtifacts(councilId);
758
+ this.listeners.delete(councilId);
759
+ }
760
+ }
761
+
762
+ // Singleton instance for app-wide use
763
+ export const contextStore = new ContextStore();