@kentwynn/kgraph 0.2.9 → 0.2.11
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/README.md +33 -2
- package/dist/cli/commands/blame.d.ts +2 -0
- package/dist/cli/commands/blame.js +52 -0
- package/dist/cli/commands/doctor.js +17 -0
- package/dist/cli/commands/init.js +2 -0
- package/dist/cli/commands/knowledge.d.ts +2 -0
- package/dist/cli/commands/knowledge.js +137 -0
- package/dist/cli/commands/pack.d.ts +2 -0
- package/dist/cli/commands/pack.js +49 -0
- package/dist/cli/commands/stale.d.ts +2 -0
- package/dist/cli/commands/stale.js +33 -0
- package/dist/cli/help.js +6 -0
- package/dist/cli/index.js +8 -0
- package/dist/cognition/cognition-updater.js +14 -0
- package/dist/cognition/compact.js +129 -28
- package/dist/cognition/conclusion.d.ts +2 -0
- package/dist/cognition/conclusion.js +22 -0
- package/dist/context/context-pack.d.ts +3 -0
- package/dist/context/context-pack.js +71 -0
- package/dist/context/context-query.js +53 -28
- package/dist/integrations/adapters/claude-code.js +21 -1
- package/dist/integrations/adapters/codex.js +1 -1
- package/dist/integrations/adapters/copilot.js +44 -1
- package/dist/integrations/workflow-steps.js +16 -7
- package/dist/knowledge/atom-store.d.ts +60 -0
- package/dist/knowledge/atom-store.js +484 -0
- package/dist/storage/kgraph-paths.js +5 -2
- package/dist/types/config.d.ts +1 -0
- package/dist/types/knowledge.d.ts +92 -0
- package/dist/types/knowledge.js +1 -0
- package/package.json +1 -1
|
@@ -0,0 +1,484 @@
|
|
|
1
|
+
import { mkdir, readFile, writeFile } from 'node:fs/promises';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { KGraphError } from '../cli/errors.js';
|
|
4
|
+
import { getCurrentCommit } from '../scanner/git-utils.js';
|
|
5
|
+
import { readCognitionNotes } from '../storage/cognition-store.js';
|
|
6
|
+
import { pathExists } from '../storage/kgraph-paths.js';
|
|
7
|
+
export const KNOWLEDGE_SCHEMA_VERSION = 1;
|
|
8
|
+
export async function ensureKnowledgeStore(workspace) {
|
|
9
|
+
await mkdir(indexesPath(workspace), { recursive: true });
|
|
10
|
+
if (!(await pathExists(schemaPath(workspace)))) {
|
|
11
|
+
const now = new Date().toISOString();
|
|
12
|
+
await writeSchema(workspace, {
|
|
13
|
+
version: KNOWLEDGE_SCHEMA_VERSION,
|
|
14
|
+
createdAt: now,
|
|
15
|
+
updatedAt: now,
|
|
16
|
+
migrations: [{ id: 'init-knowledge-v1', appliedAt: now }],
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
if (!(await pathExists(atomsPath(workspace)))) {
|
|
20
|
+
await writeFile(atomsPath(workspace), '', 'utf8');
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
export async function readKnowledgeAtoms(workspace) {
|
|
24
|
+
await migrateLegacyCognitionToAtoms(workspace);
|
|
25
|
+
return readAtomsFile(workspace);
|
|
26
|
+
}
|
|
27
|
+
export async function readAtomsFile(workspace) {
|
|
28
|
+
await ensureKnowledgeStore(workspace);
|
|
29
|
+
const raw = await readFile(atomsPath(workspace), 'utf8');
|
|
30
|
+
return parseAtomsJsonl(raw);
|
|
31
|
+
}
|
|
32
|
+
export function parseAtomsJsonl(raw) {
|
|
33
|
+
const atoms = [];
|
|
34
|
+
for (const [index, line] of raw.split('\n').entries()) {
|
|
35
|
+
if (!line.trim())
|
|
36
|
+
continue;
|
|
37
|
+
try {
|
|
38
|
+
atoms.push(JSON.parse(line));
|
|
39
|
+
}
|
|
40
|
+
catch (error) {
|
|
41
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
42
|
+
throw new KGraphError(`Invalid atoms.jsonl line ${index + 1}: ${message}`);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
return atoms;
|
|
46
|
+
}
|
|
47
|
+
export async function writeKnowledgeAtoms(workspace, atoms) {
|
|
48
|
+
await ensureKnowledgeStore(workspace);
|
|
49
|
+
await writeFile(atomsPath(workspace), atoms.map((atom) => JSON.stringify(atom)).join('\n') +
|
|
50
|
+
(atoms.length > 0 ? '\n' : ''), 'utf8');
|
|
51
|
+
await writeKnowledgeIndexes(workspace, atoms);
|
|
52
|
+
await touchSchema(workspace);
|
|
53
|
+
}
|
|
54
|
+
export async function appendKnowledgeAtom(workspace, atom) {
|
|
55
|
+
const atoms = await readAtomsFile(workspace);
|
|
56
|
+
atoms.push(atom);
|
|
57
|
+
await writeKnowledgeAtoms(workspace, atoms);
|
|
58
|
+
return atom;
|
|
59
|
+
}
|
|
60
|
+
export async function createKnowledgeAtom(workspace, input, maps) {
|
|
61
|
+
const createdAt = input.createdAt ?? new Date().toISOString();
|
|
62
|
+
const commit = input.commit ?? (await getCurrentCommit(workspace.rootPath)) ?? undefined;
|
|
63
|
+
const evidenceRefs = buildEvidenceRefs(input, maps);
|
|
64
|
+
const status = evaluateAtomStatus(evidenceRefs, maps);
|
|
65
|
+
const atom = {
|
|
66
|
+
id: buildAtomId(createdAt, input.idSeed ?? input.topic),
|
|
67
|
+
type: input.type,
|
|
68
|
+
topic: input.topic,
|
|
69
|
+
claim: input.claim,
|
|
70
|
+
summary: input.summary,
|
|
71
|
+
confidence: computeConfidence(input.confidence ?? 'medium', status),
|
|
72
|
+
status,
|
|
73
|
+
evidenceRefs,
|
|
74
|
+
scopeRefs: {
|
|
75
|
+
files: input.files ?? [],
|
|
76
|
+
symbols: input.symbols ?? [],
|
|
77
|
+
domains: input.domains ?? [],
|
|
78
|
+
packages: input.packages ?? [],
|
|
79
|
+
},
|
|
80
|
+
provenance: {
|
|
81
|
+
sourceCommand: input.sourceCommand,
|
|
82
|
+
agent: input.agent,
|
|
83
|
+
sessionId: input.sessionId,
|
|
84
|
+
commit,
|
|
85
|
+
createdAt,
|
|
86
|
+
},
|
|
87
|
+
lifecycle: {
|
|
88
|
+
supersedes: [],
|
|
89
|
+
},
|
|
90
|
+
};
|
|
91
|
+
return appendKnowledgeAtom(workspace, atom);
|
|
92
|
+
}
|
|
93
|
+
export async function migrateLegacyCognitionToAtoms(workspace) {
|
|
94
|
+
await ensureKnowledgeStore(workspace);
|
|
95
|
+
const [existing, notes] = await Promise.all([
|
|
96
|
+
readAtomsFile(workspace),
|
|
97
|
+
readCognitionNotes(workspace),
|
|
98
|
+
]);
|
|
99
|
+
const existingIds = new Set(existing.map((atom) => atom.id));
|
|
100
|
+
const migrated = [];
|
|
101
|
+
for (const note of notes) {
|
|
102
|
+
const id = legacyAtomId(note);
|
|
103
|
+
if (existingIds.has(id))
|
|
104
|
+
continue;
|
|
105
|
+
if (existing.some((atom) => atom.topic === note.title &&
|
|
106
|
+
atom.claim === (note.summary ?? note.title) &&
|
|
107
|
+
atom.provenance.createdAt === note.createdAt)) {
|
|
108
|
+
continue;
|
|
109
|
+
}
|
|
110
|
+
migrated.push(legacyNoteToAtom(note, id));
|
|
111
|
+
}
|
|
112
|
+
if (migrated.length > 0) {
|
|
113
|
+
await writeKnowledgeAtoms(workspace, [...existing, ...migrated]);
|
|
114
|
+
}
|
|
115
|
+
else {
|
|
116
|
+
await writeKnowledgeIndexes(workspace, existing);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
export async function updateKnowledgeAtom(workspace, atomId, updater) {
|
|
120
|
+
const atoms = await readKnowledgeAtoms(workspace);
|
|
121
|
+
const index = atoms.findIndex((atom) => atom.id === atomId);
|
|
122
|
+
if (index === -1) {
|
|
123
|
+
throw new KGraphError(`Knowledge atom not found: ${atomId}`);
|
|
124
|
+
}
|
|
125
|
+
atoms[index] = updater(atoms[index]);
|
|
126
|
+
await writeKnowledgeAtoms(workspace, atoms);
|
|
127
|
+
return atoms[index];
|
|
128
|
+
}
|
|
129
|
+
export async function refreshKnowledgeAtomStatuses(workspace, maps, dryRun = false) {
|
|
130
|
+
const atoms = await readKnowledgeAtoms(workspace);
|
|
131
|
+
const now = new Date().toISOString();
|
|
132
|
+
const updated = [];
|
|
133
|
+
const nextAtoms = atoms.map((atom) => {
|
|
134
|
+
if (atom.status === 'archived')
|
|
135
|
+
return atom;
|
|
136
|
+
const health = evaluateAtomHealth(atom.evidenceRefs, maps);
|
|
137
|
+
const nextConfidence = computeConfidence(atom.confidence, health.status, atom);
|
|
138
|
+
const nextLifecycle = {
|
|
139
|
+
...atom.lifecycle,
|
|
140
|
+
...(health.reasons.length > 0 ? { invalidatedBy: health.reasons } : {}),
|
|
141
|
+
};
|
|
142
|
+
if (health.status === 'active') {
|
|
143
|
+
delete nextLifecycle.invalidatedBy;
|
|
144
|
+
}
|
|
145
|
+
const statusChanged = health.status !== atom.status;
|
|
146
|
+
const confidenceChanged = nextConfidence !== atom.confidence;
|
|
147
|
+
const invalidationChanged = JSON.stringify(nextLifecycle.invalidatedBy ?? []) !==
|
|
148
|
+
JSON.stringify(atom.lifecycle.invalidatedBy ?? []);
|
|
149
|
+
const nextAtom = {
|
|
150
|
+
...atom,
|
|
151
|
+
status: health.status,
|
|
152
|
+
confidence: nextConfidence,
|
|
153
|
+
lifecycle: nextLifecycle,
|
|
154
|
+
provenance: {
|
|
155
|
+
...atom.provenance,
|
|
156
|
+
...(statusChanged || confidenceChanged || invalidationChanged
|
|
157
|
+
? { updatedAt: now }
|
|
158
|
+
: {}),
|
|
159
|
+
},
|
|
160
|
+
};
|
|
161
|
+
if (statusChanged || confidenceChanged || invalidationChanged) {
|
|
162
|
+
updated.push({
|
|
163
|
+
atomId: atom.id,
|
|
164
|
+
previousStatus: atom.status,
|
|
165
|
+
nextStatus: health.status,
|
|
166
|
+
reasons: health.reasons,
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
return nextAtom;
|
|
170
|
+
});
|
|
171
|
+
if (!dryRun && updated.length > 0) {
|
|
172
|
+
await writeKnowledgeAtoms(workspace, nextAtoms);
|
|
173
|
+
}
|
|
174
|
+
return { atoms: nextAtoms, updated };
|
|
175
|
+
}
|
|
176
|
+
export async function validateKnowledgeStore(workspace, maps) {
|
|
177
|
+
const issues = [];
|
|
178
|
+
if (!(await pathExists(schemaPath(workspace)))) {
|
|
179
|
+
issues.push({ code: 'missing-schema', message: 'missing knowledge/schema.json' });
|
|
180
|
+
}
|
|
181
|
+
else {
|
|
182
|
+
try {
|
|
183
|
+
const schema = JSON.parse(await readFile(schemaPath(workspace), 'utf8'));
|
|
184
|
+
if (schema.version < KNOWLEDGE_SCHEMA_VERSION) {
|
|
185
|
+
issues.push({
|
|
186
|
+
code: 'old-schema',
|
|
187
|
+
message: `knowledge schema ${schema.version} is older than ${KNOWLEDGE_SCHEMA_VERSION}`,
|
|
188
|
+
});
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
catch (error) {
|
|
192
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
193
|
+
issues.push({ code: 'missing-schema', message: `invalid knowledge schema: ${message}` });
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
let atoms = [];
|
|
197
|
+
try {
|
|
198
|
+
atoms = await readKnowledgeAtoms(workspace);
|
|
199
|
+
}
|
|
200
|
+
catch (error) {
|
|
201
|
+
issues.push({
|
|
202
|
+
code: 'invalid-jsonl',
|
|
203
|
+
message: error instanceof Error ? error.message : String(error),
|
|
204
|
+
});
|
|
205
|
+
return issues;
|
|
206
|
+
}
|
|
207
|
+
if (maps) {
|
|
208
|
+
const fileByPath = new Map(maps.fileMap.files.map((file) => [file.path, file]));
|
|
209
|
+
const symbolNames = new Set(maps.symbolMap.symbols.map((symbol) => symbol.name));
|
|
210
|
+
const symbolIds = new Set(maps.symbolMap.symbols.map((symbol) => symbol.id));
|
|
211
|
+
for (const atom of atoms) {
|
|
212
|
+
for (const ref of atom.evidenceRefs) {
|
|
213
|
+
if (ref.type === 'file') {
|
|
214
|
+
const file = fileByPath.get(ref.path);
|
|
215
|
+
if (!file) {
|
|
216
|
+
issues.push({
|
|
217
|
+
code: 'broken-file-ref',
|
|
218
|
+
atomId: atom.id,
|
|
219
|
+
message: `${atom.id} references missing file ${ref.path}`,
|
|
220
|
+
});
|
|
221
|
+
}
|
|
222
|
+
else if (ref.contentHash && ref.contentHash !== file.contentHash) {
|
|
223
|
+
issues.push({
|
|
224
|
+
code: 'stale-file-hash',
|
|
225
|
+
atomId: atom.id,
|
|
226
|
+
message: `${atom.id} references changed file ${ref.path}`,
|
|
227
|
+
});
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
if (ref.type === 'symbol') {
|
|
231
|
+
const exists = (ref.symbolId && symbolIds.has(ref.symbolId)) ||
|
|
232
|
+
symbolNames.has(ref.name);
|
|
233
|
+
if (!exists) {
|
|
234
|
+
issues.push({
|
|
235
|
+
code: 'broken-symbol-ref',
|
|
236
|
+
atomId: atom.id,
|
|
237
|
+
message: `${atom.id} references missing symbol ${ref.name}`,
|
|
238
|
+
});
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
return issues;
|
|
245
|
+
}
|
|
246
|
+
export function atomToCognitionNote(atom) {
|
|
247
|
+
return {
|
|
248
|
+
id: atom.id,
|
|
249
|
+
title: atom.topic,
|
|
250
|
+
kind: atom.type,
|
|
251
|
+
confidence: atom.confidence,
|
|
252
|
+
domain: atom.scopeRefs.domains[0],
|
|
253
|
+
tags: [],
|
|
254
|
+
summary: atom.summary ?? atom.claim,
|
|
255
|
+
sections: { Summary: atom.summary ?? atom.claim },
|
|
256
|
+
relatedFiles: atom.scopeRefs.files,
|
|
257
|
+
relatedSymbols: atom.scopeRefs.symbols,
|
|
258
|
+
warnings: [],
|
|
259
|
+
sourceInboxPath: '',
|
|
260
|
+
processedPath: '',
|
|
261
|
+
createdAt: atom.provenance.createdAt,
|
|
262
|
+
updatedAt: atom.provenance.updatedAt,
|
|
263
|
+
source: atom.provenance.sourceCommand === 'legacy-migration' ||
|
|
264
|
+
atom.provenance.sourceCommand === 'update'
|
|
265
|
+
? 'inbox'
|
|
266
|
+
: atom.provenance.sourceCommand,
|
|
267
|
+
supersedes: atom.lifecycle.supersedes,
|
|
268
|
+
supersededBy: atom.lifecycle.supersededBy,
|
|
269
|
+
referencesStatus: atom.status === 'active'
|
|
270
|
+
? 'current'
|
|
271
|
+
: atom.status === 'needs-review'
|
|
272
|
+
? 'mixed'
|
|
273
|
+
: atom.status === 'stale'
|
|
274
|
+
? 'stale'
|
|
275
|
+
: 'unresolved',
|
|
276
|
+
};
|
|
277
|
+
}
|
|
278
|
+
export function knowledgePaths(workspace) {
|
|
279
|
+
return {
|
|
280
|
+
atoms: atomsPath(workspace),
|
|
281
|
+
schema: schemaPath(workspace),
|
|
282
|
+
indexes: indexesPath(workspace),
|
|
283
|
+
terms: path.join(indexesPath(workspace), 'terms.json'),
|
|
284
|
+
refs: path.join(indexesPath(workspace), 'refs.json'),
|
|
285
|
+
topics: path.join(indexesPath(workspace), 'topics.json'),
|
|
286
|
+
};
|
|
287
|
+
}
|
|
288
|
+
function buildEvidenceRefs(input, maps) {
|
|
289
|
+
const fileByPath = new Map(maps?.fileMap.files.map((file) => [file.path, file]) ?? []);
|
|
290
|
+
const symbolsByName = new Map(maps?.symbolMap.symbols.map((symbol) => [symbol.name, symbol]) ?? []);
|
|
291
|
+
const refs = [];
|
|
292
|
+
for (const filePath of input.files ?? []) {
|
|
293
|
+
const file = fileByPath.get(filePath);
|
|
294
|
+
refs.push({
|
|
295
|
+
type: 'file',
|
|
296
|
+
path: filePath,
|
|
297
|
+
...(file?.contentHash ? { contentHash: file.contentHash } : {}),
|
|
298
|
+
});
|
|
299
|
+
}
|
|
300
|
+
for (const name of input.symbols ?? []) {
|
|
301
|
+
const symbol = symbolsByName.get(name);
|
|
302
|
+
refs.push({
|
|
303
|
+
type: 'symbol',
|
|
304
|
+
name,
|
|
305
|
+
...(symbol?.filePath ? { filePath: symbol.filePath } : {}),
|
|
306
|
+
...(symbol?.id ? { symbolId: symbol.id } : {}),
|
|
307
|
+
...(symbol?.startLine ? { startLine: symbol.startLine } : {}),
|
|
308
|
+
...(symbol?.endLine ? { endLine: symbol.endLine } : {}),
|
|
309
|
+
});
|
|
310
|
+
}
|
|
311
|
+
if (input.commit) {
|
|
312
|
+
refs.push({ type: 'git', commit: input.commit });
|
|
313
|
+
}
|
|
314
|
+
if (input.sessionId || input.agent) {
|
|
315
|
+
refs.push({ type: 'session', sessionId: input.sessionId, agent: input.agent });
|
|
316
|
+
}
|
|
317
|
+
return refs;
|
|
318
|
+
}
|
|
319
|
+
function evaluateAtomStatus(refs, maps) {
|
|
320
|
+
return evaluateAtomHealth(refs, maps).status;
|
|
321
|
+
}
|
|
322
|
+
function evaluateAtomHealth(refs, maps) {
|
|
323
|
+
if (!maps || refs.length === 0)
|
|
324
|
+
return { status: 'active', reasons: [] };
|
|
325
|
+
const fileByPath = new Map(maps.fileMap.files.map((file) => [file.path, file]));
|
|
326
|
+
const symbolNames = new Set(maps.symbolMap.symbols.map((symbol) => symbol.name));
|
|
327
|
+
const symbolIds = new Set(maps.symbolMap.symbols.map((symbol) => symbol.id));
|
|
328
|
+
let stale = false;
|
|
329
|
+
let needsReview = false;
|
|
330
|
+
const reasons = [];
|
|
331
|
+
for (const ref of refs) {
|
|
332
|
+
if (ref.type === 'file') {
|
|
333
|
+
const file = fileByPath.get(ref.path);
|
|
334
|
+
if (!file) {
|
|
335
|
+
stale = true;
|
|
336
|
+
reasons.push(`missing file:${ref.path}`);
|
|
337
|
+
}
|
|
338
|
+
else if (ref.contentHash && ref.contentHash !== file.contentHash) {
|
|
339
|
+
needsReview = true;
|
|
340
|
+
reasons.push(`changed file:${ref.path}`);
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
if (ref.type === 'symbol') {
|
|
344
|
+
const exists = (ref.symbolId && symbolIds.has(ref.symbolId)) || symbolNames.has(ref.name);
|
|
345
|
+
if (!exists) {
|
|
346
|
+
stale = true;
|
|
347
|
+
reasons.push(`missing symbol:${ref.name}`);
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
if (stale)
|
|
352
|
+
return { status: 'stale', reasons };
|
|
353
|
+
if (needsReview)
|
|
354
|
+
return { status: 'needs-review', reasons };
|
|
355
|
+
return { status: 'active', reasons };
|
|
356
|
+
}
|
|
357
|
+
function computeConfidence(initial, status, atom) {
|
|
358
|
+
if (atom?.lifecycle.supersededBy || atom?.status === 'archived')
|
|
359
|
+
return 'low';
|
|
360
|
+
if (status === 'stale')
|
|
361
|
+
return 'low';
|
|
362
|
+
if (status === 'needs-review' && initial === 'high')
|
|
363
|
+
return 'medium';
|
|
364
|
+
if (atom?.provenance.sourceCommand === 'legacy-migration' && initial === 'high') {
|
|
365
|
+
return 'medium';
|
|
366
|
+
}
|
|
367
|
+
return initial;
|
|
368
|
+
}
|
|
369
|
+
function legacyNoteToAtom(note, id) {
|
|
370
|
+
return {
|
|
371
|
+
id,
|
|
372
|
+
type: note.kind,
|
|
373
|
+
topic: note.title,
|
|
374
|
+
claim: note.summary ?? note.title,
|
|
375
|
+
summary: note.summary,
|
|
376
|
+
confidence: note.confidence,
|
|
377
|
+
status: note.referencesStatus === 'current'
|
|
378
|
+
? 'active'
|
|
379
|
+
: note.referencesStatus === 'mixed'
|
|
380
|
+
? 'needs-review'
|
|
381
|
+
: note.referencesStatus === 'stale'
|
|
382
|
+
? 'stale'
|
|
383
|
+
: 'needs-review',
|
|
384
|
+
evidenceRefs: [
|
|
385
|
+
...note.relatedFiles.map((file) => ({ type: 'file', path: file })),
|
|
386
|
+
...note.relatedSymbols.map((symbol) => ({
|
|
387
|
+
type: 'symbol',
|
|
388
|
+
name: symbol,
|
|
389
|
+
})),
|
|
390
|
+
],
|
|
391
|
+
scopeRefs: {
|
|
392
|
+
files: note.relatedFiles,
|
|
393
|
+
symbols: note.relatedSymbols,
|
|
394
|
+
domains: note.domain ? [note.domain] : [],
|
|
395
|
+
packages: [],
|
|
396
|
+
},
|
|
397
|
+
provenance: {
|
|
398
|
+
sourceCommand: 'legacy-migration',
|
|
399
|
+
createdAt: note.createdAt,
|
|
400
|
+
updatedAt: note.updatedAt,
|
|
401
|
+
},
|
|
402
|
+
lifecycle: {
|
|
403
|
+
supersedes: note.supersedes ?? [],
|
|
404
|
+
supersededBy: note.supersededBy,
|
|
405
|
+
},
|
|
406
|
+
};
|
|
407
|
+
}
|
|
408
|
+
function legacyAtomId(note) {
|
|
409
|
+
return `legacy-${note.id}`;
|
|
410
|
+
}
|
|
411
|
+
async function writeKnowledgeIndexes(workspace, atoms) {
|
|
412
|
+
await mkdir(indexesPath(workspace), { recursive: true });
|
|
413
|
+
const indexes = buildIndexes(atoms);
|
|
414
|
+
await Promise.all([
|
|
415
|
+
writeFile(path.join(indexesPath(workspace), 'terms.json'), JSON.stringify(indexes.terms, null, 2) + '\n', 'utf8'),
|
|
416
|
+
writeFile(path.join(indexesPath(workspace), 'refs.json'), JSON.stringify(indexes.refs, null, 2) + '\n', 'utf8'),
|
|
417
|
+
writeFile(path.join(indexesPath(workspace), 'topics.json'), JSON.stringify(indexes.topics, null, 2) + '\n', 'utf8'),
|
|
418
|
+
]);
|
|
419
|
+
}
|
|
420
|
+
function buildIndexes(atoms) {
|
|
421
|
+
const terms = {};
|
|
422
|
+
const refs = {};
|
|
423
|
+
const topics = {};
|
|
424
|
+
for (const atom of atoms) {
|
|
425
|
+
for (const term of tokenize([atom.topic, atom.claim, atom.summary ?? ''].join(' '))) {
|
|
426
|
+
addIndex(terms, term, atom.id);
|
|
427
|
+
}
|
|
428
|
+
addIndex(topics, atom.topic.toLowerCase(), atom.id);
|
|
429
|
+
for (const file of atom.scopeRefs.files)
|
|
430
|
+
addIndex(refs, `file:${file}`, atom.id);
|
|
431
|
+
for (const symbol of atom.scopeRefs.symbols)
|
|
432
|
+
addIndex(refs, `symbol:${symbol}`, atom.id);
|
|
433
|
+
}
|
|
434
|
+
return { terms, refs, topics };
|
|
435
|
+
}
|
|
436
|
+
function addIndex(index, key, atomId) {
|
|
437
|
+
const values = index[key] ?? [];
|
|
438
|
+
if (!values.includes(atomId))
|
|
439
|
+
values.push(atomId);
|
|
440
|
+
index[key] = values;
|
|
441
|
+
}
|
|
442
|
+
async function touchSchema(workspace) {
|
|
443
|
+
const schema = await readSchema(workspace);
|
|
444
|
+
await writeSchema(workspace, {
|
|
445
|
+
...schema,
|
|
446
|
+
updatedAt: new Date().toISOString(),
|
|
447
|
+
});
|
|
448
|
+
}
|
|
449
|
+
async function readSchema(workspace) {
|
|
450
|
+
await ensureKnowledgeStore(workspace);
|
|
451
|
+
return JSON.parse(await readFile(schemaPath(workspace), 'utf8'));
|
|
452
|
+
}
|
|
453
|
+
async function writeSchema(workspace, schema) {
|
|
454
|
+
await mkdir(workspace.knowledgePath, { recursive: true });
|
|
455
|
+
await writeFile(schemaPath(workspace), JSON.stringify(schema, null, 2) + '\n', 'utf8');
|
|
456
|
+
}
|
|
457
|
+
function buildAtomId(createdAt, seed) {
|
|
458
|
+
return `${createdAt.replace(/[:.]/g, '-')}-${slugify(seed) || 'atom'}`;
|
|
459
|
+
}
|
|
460
|
+
function tokenize(value) {
|
|
461
|
+
return [
|
|
462
|
+
...new Set(value
|
|
463
|
+
.toLowerCase()
|
|
464
|
+
.split(/[^a-z0-9_$./-]+/)
|
|
465
|
+
.map((token) => token.trim())
|
|
466
|
+
.filter((token) => token.length > 1)),
|
|
467
|
+
];
|
|
468
|
+
}
|
|
469
|
+
function slugify(value) {
|
|
470
|
+
return value
|
|
471
|
+
.trim()
|
|
472
|
+
.toLowerCase()
|
|
473
|
+
.replace(/[^a-z0-9]+/g, '-')
|
|
474
|
+
.replace(/^-|-$/g, '');
|
|
475
|
+
}
|
|
476
|
+
function atomsPath(workspace) {
|
|
477
|
+
return path.join(workspace.knowledgePath, 'atoms.jsonl');
|
|
478
|
+
}
|
|
479
|
+
function schemaPath(workspace) {
|
|
480
|
+
return path.join(workspace.knowledgePath, 'schema.json');
|
|
481
|
+
}
|
|
482
|
+
function indexesPath(workspace) {
|
|
483
|
+
return path.join(workspace.knowledgePath, 'indexes');
|
|
484
|
+
}
|
|
@@ -8,7 +8,9 @@ const WORKSPACE_DIRS = [
|
|
|
8
8
|
"inbox",
|
|
9
9
|
"interactions/processed",
|
|
10
10
|
"context",
|
|
11
|
-
"sessions"
|
|
11
|
+
"sessions",
|
|
12
|
+
"knowledge",
|
|
13
|
+
"knowledge/indexes"
|
|
12
14
|
];
|
|
13
15
|
export function resolveWorkspace(rootPath = process.cwd()) {
|
|
14
16
|
const kgraphPath = path.join(rootPath, ".kgraph");
|
|
@@ -22,7 +24,8 @@ export function resolveWorkspace(rootPath = process.cwd()) {
|
|
|
22
24
|
inboxPath: path.join(kgraphPath, "inbox"),
|
|
23
25
|
processedInteractionsPath: path.join(kgraphPath, "interactions", "processed"),
|
|
24
26
|
contextPath: path.join(kgraphPath, "context"),
|
|
25
|
-
sessionsPath: path.join(kgraphPath, "sessions")
|
|
27
|
+
sessionsPath: path.join(kgraphPath, "sessions"),
|
|
28
|
+
knowledgePath: path.join(kgraphPath, "knowledge")
|
|
26
29
|
};
|
|
27
30
|
}
|
|
28
31
|
export async function pathExists(targetPath) {
|
package/dist/types/config.d.ts
CHANGED
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import type { CognitionConfidence, CognitionKind } from './cognition.js';
|
|
2
|
+
export type KnowledgeAtomStatus = 'active' | 'stale' | 'needs-review' | 'archived';
|
|
3
|
+
export type KnowledgeEvidenceRef = {
|
|
4
|
+
type: 'file';
|
|
5
|
+
path: string;
|
|
6
|
+
startLine?: number;
|
|
7
|
+
endLine?: number;
|
|
8
|
+
contentHash?: string;
|
|
9
|
+
} | {
|
|
10
|
+
type: 'symbol';
|
|
11
|
+
name: string;
|
|
12
|
+
filePath?: string;
|
|
13
|
+
symbolId?: string;
|
|
14
|
+
startLine?: number;
|
|
15
|
+
endLine?: number;
|
|
16
|
+
} | {
|
|
17
|
+
type: 'git';
|
|
18
|
+
commit?: string;
|
|
19
|
+
path?: string;
|
|
20
|
+
} | {
|
|
21
|
+
type: 'session';
|
|
22
|
+
sessionId?: string;
|
|
23
|
+
agent?: string;
|
|
24
|
+
};
|
|
25
|
+
export interface KnowledgeScopeRefs {
|
|
26
|
+
files: string[];
|
|
27
|
+
symbols: string[];
|
|
28
|
+
domains: string[];
|
|
29
|
+
packages: string[];
|
|
30
|
+
}
|
|
31
|
+
export interface KnowledgeProvenance {
|
|
32
|
+
sourceCommand: 'update' | 'conclude' | 'session-conclude' | 'legacy-migration' | 'compact';
|
|
33
|
+
agent?: string;
|
|
34
|
+
sessionId?: string;
|
|
35
|
+
commit?: string;
|
|
36
|
+
createdAt: string;
|
|
37
|
+
updatedAt?: string;
|
|
38
|
+
}
|
|
39
|
+
export interface KnowledgeLifecycle {
|
|
40
|
+
supersedes: string[];
|
|
41
|
+
supersededBy?: string;
|
|
42
|
+
invalidatedBy?: string[];
|
|
43
|
+
archivedAt?: string;
|
|
44
|
+
}
|
|
45
|
+
export interface KnowledgeAtom {
|
|
46
|
+
id: string;
|
|
47
|
+
type: CognitionKind;
|
|
48
|
+
topic: string;
|
|
49
|
+
claim: string;
|
|
50
|
+
summary?: string;
|
|
51
|
+
confidence: CognitionConfidence;
|
|
52
|
+
status: KnowledgeAtomStatus;
|
|
53
|
+
evidenceRefs: KnowledgeEvidenceRef[];
|
|
54
|
+
scopeRefs: KnowledgeScopeRefs;
|
|
55
|
+
provenance: KnowledgeProvenance;
|
|
56
|
+
lifecycle: KnowledgeLifecycle;
|
|
57
|
+
}
|
|
58
|
+
export interface KnowledgeSchema {
|
|
59
|
+
version: number;
|
|
60
|
+
createdAt: string;
|
|
61
|
+
updatedAt: string;
|
|
62
|
+
migrations: Array<{
|
|
63
|
+
id: string;
|
|
64
|
+
appliedAt: string;
|
|
65
|
+
}>;
|
|
66
|
+
}
|
|
67
|
+
export interface KnowledgeIndexes {
|
|
68
|
+
terms: Record<string, string[]>;
|
|
69
|
+
refs: Record<string, string[]>;
|
|
70
|
+
topics: Record<string, string[]>;
|
|
71
|
+
}
|
|
72
|
+
export interface KnowledgeValidationIssue {
|
|
73
|
+
code: 'missing-schema' | 'old-schema' | 'invalid-jsonl' | 'broken-file-ref' | 'broken-symbol-ref' | 'stale-file-hash';
|
|
74
|
+
message: string;
|
|
75
|
+
atomId?: string;
|
|
76
|
+
}
|
|
77
|
+
export interface ContextPackItem {
|
|
78
|
+
kind: 'file' | 'symbol' | 'atom' | 'relationship' | 'git-change';
|
|
79
|
+
id: string;
|
|
80
|
+
title: string;
|
|
81
|
+
tokenEstimate: number;
|
|
82
|
+
reasons: string[];
|
|
83
|
+
data: unknown;
|
|
84
|
+
}
|
|
85
|
+
export interface ContextPack {
|
|
86
|
+
task: string;
|
|
87
|
+
budget: number;
|
|
88
|
+
usedTokens: number;
|
|
89
|
+
items: ContextPackItem[];
|
|
90
|
+
omitted: ContextPackItem[];
|
|
91
|
+
warnings: string[];
|
|
92
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|