@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,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();
|