openclaw-cortex-memory 0.1.0-Alpha.30 → 0.1.0-Alpha.32

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 (79) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +46 -12
  3. package/SIGNATURE.md +7 -0
  4. package/SKILL.md +18 -3
  5. package/dist/index.d.ts +8 -0
  6. package/dist/index.d.ts.map +1 -1
  7. package/dist/index.js +148 -6
  8. package/dist/index.js.map +1 -1
  9. package/dist/openclaw.plugin.json +120 -4
  10. package/dist/src/engine/memory_engine.d.ts +5 -1
  11. package/dist/src/engine/memory_engine.d.ts.map +1 -1
  12. package/dist/src/engine/ts_engine.d.ts +116 -0
  13. package/dist/src/engine/ts_engine.d.ts.map +1 -1
  14. package/dist/src/engine/ts_engine.js +417 -102
  15. package/dist/src/engine/ts_engine.js.map +1 -1
  16. package/dist/src/engine/types.d.ts +17 -0
  17. package/dist/src/engine/types.d.ts.map +1 -1
  18. package/dist/src/graph/ontology.d.ts +23 -1
  19. package/dist/src/graph/ontology.d.ts.map +1 -1
  20. package/dist/src/graph/ontology.js +743 -70
  21. package/dist/src/graph/ontology.js.map +1 -1
  22. package/dist/src/quality/llm_output_validator.d.ts +20 -2
  23. package/dist/src/quality/llm_output_validator.d.ts.map +1 -1
  24. package/dist/src/quality/llm_output_validator.js +296 -41
  25. package/dist/src/quality/llm_output_validator.js.map +1 -1
  26. package/dist/src/store/archive_store.d.ts +8 -0
  27. package/dist/src/store/archive_store.d.ts.map +1 -1
  28. package/dist/src/store/archive_store.js +244 -84
  29. package/dist/src/store/archive_store.js.map +1 -1
  30. package/dist/src/store/graph_memory_store.d.ts +72 -2
  31. package/dist/src/store/graph_memory_store.d.ts.map +1 -1
  32. package/dist/src/store/graph_memory_store.js +723 -50
  33. package/dist/src/store/graph_memory_store.js.map +1 -1
  34. package/dist/src/store/read_store.d.ts +3 -0
  35. package/dist/src/store/read_store.d.ts.map +1 -1
  36. package/dist/src/store/read_store.js +1004 -209
  37. package/dist/src/store/read_store.js.map +1 -1
  38. package/dist/src/store/vector_store.d.ts +1 -0
  39. package/dist/src/store/vector_store.d.ts.map +1 -1
  40. package/dist/src/store/vector_store.js +1 -0
  41. package/dist/src/store/vector_store.js.map +1 -1
  42. package/dist/src/store/write_store.d.ts +2 -0
  43. package/dist/src/store/write_store.d.ts.map +1 -1
  44. package/dist/src/store/write_store.js +45 -3
  45. package/dist/src/store/write_store.js.map +1 -1
  46. package/dist/src/sync/session_sync.d.ts +20 -1
  47. package/dist/src/sync/session_sync.d.ts.map +1 -1
  48. package/dist/src/sync/session_sync.js +1810 -161
  49. package/dist/src/sync/session_sync.js.map +1 -1
  50. package/dist/src/wiki/wiki_linter.d.ts +25 -0
  51. package/dist/src/wiki/wiki_linter.d.ts.map +1 -0
  52. package/dist/src/wiki/wiki_linter.js +268 -0
  53. package/dist/src/wiki/wiki_linter.js.map +1 -0
  54. package/dist/src/wiki/wiki_logger.d.ts +10 -0
  55. package/dist/src/wiki/wiki_logger.d.ts.map +1 -0
  56. package/dist/src/wiki/wiki_logger.js +78 -0
  57. package/dist/src/wiki/wiki_logger.js.map +1 -0
  58. package/dist/src/wiki/wiki_maintainer.d.ts +36 -0
  59. package/dist/src/wiki/wiki_maintainer.d.ts.map +1 -0
  60. package/dist/src/wiki/wiki_maintainer.js +38 -0
  61. package/dist/src/wiki/wiki_maintainer.js.map +1 -0
  62. package/dist/src/wiki/wiki_projector.d.ts +33 -0
  63. package/dist/src/wiki/wiki_projector.d.ts.map +1 -0
  64. package/dist/src/wiki/wiki_projector.js +633 -0
  65. package/dist/src/wiki/wiki_projector.js.map +1 -0
  66. package/dist/src/wiki/wiki_queue.d.ts +29 -0
  67. package/dist/src/wiki/wiki_queue.d.ts.map +1 -0
  68. package/dist/src/wiki/wiki_queue.js +137 -0
  69. package/dist/src/wiki/wiki_queue.js.map +1 -0
  70. package/openclaw.plugin.json +120 -4
  71. package/package.json +8 -4
  72. package/schema/graph.schema.yaml +188 -33
  73. package/skills/cortex-memory/SKILL.md +49 -0
  74. package/skills/cortex-memory/references/agent-manual.md +115 -0
  75. package/skills/cortex-memory/references/configuration.md +92 -0
  76. package/skills/cortex-memory/references/publish-checklist.md +46 -0
  77. package/skills/cortex-memory/references/system-prompt-template.md +27 -0
  78. package/skills/cortex-memory/references/tools.md +181 -0
  79. package/skills/cortex-memory/scripts/smoke-check.ps1 +56 -0
@@ -0,0 +1,633 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.writeGraphViewProjection = writeGraphViewProjection;
37
+ exports.projectWikiKnowledge = projectWikiKnowledge;
38
+ const fs = __importStar(require("fs"));
39
+ const path = __importStar(require("path"));
40
+ function ensureDir(dirPath) {
41
+ if (!fs.existsSync(dirPath)) {
42
+ fs.mkdirSync(dirPath, { recursive: true });
43
+ }
44
+ }
45
+ function slugify(value) {
46
+ const normalized = (value || "").trim().toLowerCase();
47
+ if (!normalized)
48
+ return "unknown";
49
+ return normalized.replace(/[^a-z0-9]+/g, "_").replace(/^_+|_+$/g, "") || "unknown";
50
+ }
51
+ function sortByString(items, getter) {
52
+ return [...items].sort((a, b) => getter(a).localeCompare(getter(b)));
53
+ }
54
+ function readJsonl(filePath) {
55
+ if (!fs.existsSync(filePath))
56
+ return [];
57
+ const lines = fs.readFileSync(filePath, "utf-8").split(/\r?\n/).filter(Boolean);
58
+ const output = [];
59
+ for (const line of lines) {
60
+ try {
61
+ output.push(JSON.parse(line));
62
+ }
63
+ catch {
64
+ // ignore malformed lines
65
+ }
66
+ }
67
+ return output;
68
+ }
69
+ function normalizeKey(value) {
70
+ return String(value || "").trim().toLowerCase();
71
+ }
72
+ function toIso(raw, fallback) {
73
+ const ms = Date.parse(raw || "");
74
+ return Number.isFinite(ms) ? new Date(ms).toISOString() : fallback;
75
+ }
76
+ function relationKey(source, type, target) {
77
+ return `${normalizeKey(source)}|${normalizeKey(type)}|${normalizeKey(target)}`;
78
+ }
79
+ function relationEventKey(relKey, sourceEventId) {
80
+ return `${normalizeKey(relKey)}|${normalizeKey(sourceEventId)}`;
81
+ }
82
+ function conflictRelationKey(conflictId, relKey) {
83
+ return `${normalizeKey(conflictId)}|${normalizeKey(relKey)}`;
84
+ }
85
+ function entityTypeLookup(types) {
86
+ const output = new Map();
87
+ for (const [name, type] of Object.entries(types || {})) {
88
+ const key = normalizeKey(name);
89
+ const value = String(type || "").trim();
90
+ if (key && value) {
91
+ output.set(key, value);
92
+ }
93
+ }
94
+ return output;
95
+ }
96
+ function sanitizeInline(value, fallback = "n/a") {
97
+ const text = String(value || "").trim().replace(/\s+/g, " ");
98
+ return text || fallback;
99
+ }
100
+ function writeGraphViewProjection(args) {
101
+ const graphDir = path.join(args.memoryRoot, "wiki", "graph");
102
+ const viewPath = path.join(graphDir, "view.json");
103
+ const timelinePath = path.join(graphDir, "timeline.jsonl");
104
+ const nowIso = new Date().toISOString();
105
+ const snapshotId = `gview_${Date.now().toString(36)}`;
106
+ ensureDir(graphDir);
107
+ const viewPayload = {
108
+ ...args.view,
109
+ generated_at: nowIso,
110
+ snapshot_id: snapshotId,
111
+ };
112
+ fs.writeFileSync(viewPath, `${JSON.stringify(viewPayload, null, 2)}\n`, "utf-8");
113
+ const timelineEntry = {
114
+ snapshot_id: snapshotId,
115
+ generated_at: nowIso,
116
+ graph_updated_at: args.view.updated_at,
117
+ nodes: args.view.nodes.length,
118
+ edges: args.view.edges.length,
119
+ status_counts: args.view.status_counts,
120
+ };
121
+ fs.appendFileSync(timelinePath, `${JSON.stringify(timelineEntry)}\n`, "utf-8");
122
+ return {
123
+ view_path: viewPath,
124
+ timeline_path: timelinePath,
125
+ snapshot_id: snapshotId,
126
+ };
127
+ }
128
+ function relationCluster(typeRaw) {
129
+ const type = normalizeKey(typeRaw);
130
+ if (type === "resolves" || type === "solved_with")
131
+ return "resolution";
132
+ if (type === "plans_to" || type === "planned_for" || type === "scheduled_for")
133
+ return "planning";
134
+ return type;
135
+ }
136
+ function normalizeForSimilarity(value) {
137
+ return value.toLowerCase().replace(/[^a-z0-9\u4e00-\u9fff]+/g, " ").replace(/\s+/g, " ").trim();
138
+ }
139
+ function overlapRatio(left, right) {
140
+ const a = new Set(normalizeForSimilarity(left).split(" ").filter(token => token.length >= 2));
141
+ const b = new Set(normalizeForSimilarity(right).split(" ").filter(token => token.length >= 2));
142
+ if (a.size === 0 || b.size === 0)
143
+ return 0;
144
+ let hit = 0;
145
+ for (const token of a) {
146
+ if (b.has(token))
147
+ hit += 1;
148
+ }
149
+ return hit / Math.max(1, Math.min(a.size, b.size));
150
+ }
151
+ function diceSimilarity(leftRaw, rightRaw) {
152
+ const left = normalizeForSimilarity(leftRaw).replace(/\s+/g, "");
153
+ const right = normalizeForSimilarity(rightRaw).replace(/\s+/g, "");
154
+ if (!left || !right)
155
+ return 0;
156
+ if (left === right)
157
+ return 1;
158
+ const grams = (value) => {
159
+ if (value.length < 2)
160
+ return [value];
161
+ const out = [];
162
+ for (let i = 0; i < value.length - 1; i += 1) {
163
+ out.push(value.slice(i, i + 2));
164
+ }
165
+ return out;
166
+ };
167
+ const lg = grams(left);
168
+ const rg = grams(right);
169
+ const bag = new Map();
170
+ for (const gram of rg) {
171
+ bag.set(gram, (bag.get(gram) || 0) + 1);
172
+ }
173
+ let matches = 0;
174
+ for (const gram of lg) {
175
+ const remain = bag.get(gram) || 0;
176
+ if (remain > 0) {
177
+ matches += 1;
178
+ bag.set(gram, remain - 1);
179
+ }
180
+ }
181
+ return (2 * matches) / (lg.length + rg.length);
182
+ }
183
+ function withinDays(leftIso, rightIso, days) {
184
+ const left = Date.parse(leftIso);
185
+ const right = Date.parse(rightIso);
186
+ if (!Number.isFinite(left) || !Number.isFinite(right))
187
+ return false;
188
+ return Math.abs(left - right) <= days * 24 * 60 * 60 * 1000;
189
+ }
190
+ function buildProjectedRelations(args) {
191
+ const nowIso = new Date().toISOString();
192
+ const records = readJsonl(path.join(args.memoryRoot, "graph", "memory.jsonl"));
193
+ const conflicts = readJsonl(path.join(args.memoryRoot, "graph", "conflict_queue.jsonl"));
194
+ const superseded = readJsonl(path.join(args.memoryRoot, "graph", "superseded_relations.jsonl"));
195
+ const byEvent = new Map();
196
+ const byRelation = new Map();
197
+ const byConflict = new Map();
198
+ const conflictUpdatedAt = new Map();
199
+ const supersededByRelation = new Map();
200
+ const supersededByComposite = new Map();
201
+ const upsert = (target, key, next) => {
202
+ const prev = target.get(key);
203
+ if (!prev) {
204
+ target.set(key, next);
205
+ return;
206
+ }
207
+ const prevMs = Date.parse(prev.timestamp || "");
208
+ const nextMs = Date.parse(next.timestamp || "");
209
+ if (!Number.isFinite(prevMs) || (Number.isFinite(nextMs) && nextMs >= prevMs)) {
210
+ target.set(key, next);
211
+ }
212
+ };
213
+ for (const record of records) {
214
+ const sourceEventId = String(record.source_event_id || "").trim();
215
+ const timestamp = toIso(record.timestamp, nowIso);
216
+ const typeMap = entityTypeLookup(record.entity_types);
217
+ for (const rel of record.relations || []) {
218
+ const source = String(rel.source || "").trim();
219
+ const target = String(rel.target || "").trim();
220
+ const type = String(rel.type || "").trim().toLowerCase();
221
+ if (!source || !target || !type)
222
+ continue;
223
+ const key = relationKey(source, type, target);
224
+ const detail = {
225
+ source_event_id: sourceEventId || undefined,
226
+ source_layer: record.source_layer,
227
+ source_file: record.source_file,
228
+ timestamp,
229
+ summary: typeof record.summary === "string" ? record.summary.trim() : undefined,
230
+ source_text_nav: record.source_text_nav,
231
+ source_type: typeMap.get(normalizeKey(source)),
232
+ target_type: typeMap.get(normalizeKey(target)),
233
+ relation_origin: rel.relation_origin,
234
+ relation_definition: typeof rel.relation_definition === "string" ? rel.relation_definition.trim() : undefined,
235
+ evidence_span: typeof rel.evidence_span === "string" ? rel.evidence_span.trim() : undefined,
236
+ context_chunk: typeof rel.context_chunk === "string" ? rel.context_chunk.trim() : undefined,
237
+ confidence: typeof rel.confidence === "number" ? rel.confidence : undefined,
238
+ };
239
+ if (sourceEventId) {
240
+ upsert(byEvent, relationEventKey(key, sourceEventId), detail);
241
+ }
242
+ upsert(byRelation, key, detail);
243
+ }
244
+ }
245
+ for (const conflict of conflicts) {
246
+ const conflictId = String(conflict.conflict_id || "").trim();
247
+ if (!conflictId)
248
+ continue;
249
+ const updatedAt = toIso(conflict.updated_at || conflict.created_at, nowIso);
250
+ conflictUpdatedAt.set(conflictId, updatedAt);
251
+ const candidate = conflict.candidate || {};
252
+ const typeMap = entityTypeLookup(candidate.entity_types);
253
+ for (const rel of candidate.relations || []) {
254
+ const source = String(rel.source || "").trim();
255
+ const target = String(rel.target || "").trim();
256
+ const type = String(rel.type || "").trim().toLowerCase();
257
+ if (!source || !target || !type)
258
+ continue;
259
+ const key = relationKey(source, type, target);
260
+ byConflict.set(conflictRelationKey(conflictId, key), {
261
+ source_event_id: typeof conflict.source_event_id === "string" ? conflict.source_event_id.trim() || undefined : undefined,
262
+ source_layer: conflict.source_layer,
263
+ source_file: conflict.source_file,
264
+ timestamp: updatedAt,
265
+ summary: typeof candidate.summary === "string" ? candidate.summary.trim() : undefined,
266
+ source_text_nav: candidate.source_text_nav,
267
+ source_type: typeMap.get(normalizeKey(source)),
268
+ target_type: typeMap.get(normalizeKey(target)),
269
+ relation_origin: rel.relation_origin,
270
+ relation_definition: typeof rel.relation_definition === "string" ? rel.relation_definition.trim() : undefined,
271
+ evidence_span: typeof rel.evidence_span === "string" ? rel.evidence_span.trim() : undefined,
272
+ context_chunk: typeof rel.context_chunk === "string" ? rel.context_chunk.trim() : undefined,
273
+ confidence: typeof rel.confidence === "number" ? rel.confidence : undefined,
274
+ conflict_id: conflictId,
275
+ });
276
+ }
277
+ }
278
+ for (const entry of superseded) {
279
+ const key = normalizeKey(entry.relation_key);
280
+ if (!key)
281
+ continue;
282
+ const at = toIso(entry.superseded_at, nowIso);
283
+ const conflictId = normalizeKey(entry.conflict_id);
284
+ supersededByRelation.set(key, at);
285
+ if (conflictId) {
286
+ supersededByComposite.set(conflictRelationKey(conflictId, key), at);
287
+ }
288
+ }
289
+ const output = [];
290
+ for (const edge of args.graphView.edges) {
291
+ const source = String(edge.source || "").trim();
292
+ const target = String(edge.target || "").trim();
293
+ const type = String(edge.type || "").trim().toLowerCase();
294
+ if (!source || !target || !type)
295
+ continue;
296
+ const key = normalizeKey(edge.relation_key) || relationKey(source, type, target);
297
+ const sourceEventId = typeof edge.source_event_id === "string" ? edge.source_event_id.trim() : "";
298
+ const conflictId = typeof edge.conflict_id === "string" ? edge.conflict_id.trim() : "";
299
+ const status = edge.status;
300
+ let detail;
301
+ if ((status === "pending_conflict" || status === "rejected") && conflictId) {
302
+ detail = byConflict.get(conflictRelationKey(conflictId, key));
303
+ }
304
+ if (!detail && sourceEventId) {
305
+ detail = byEvent.get(relationEventKey(key, sourceEventId));
306
+ }
307
+ if (!detail) {
308
+ detail = byRelation.get(key);
309
+ }
310
+ let timestamp = detail?.timestamp || nowIso;
311
+ if (status === "superseded") {
312
+ timestamp = supersededByComposite.get(conflictRelationKey(conflictId, key)) || supersededByRelation.get(key) || timestamp;
313
+ }
314
+ else if ((status === "pending_conflict" || status === "rejected") && conflictId) {
315
+ timestamp = conflictUpdatedAt.get(conflictId) || timestamp;
316
+ }
317
+ output.push({
318
+ source,
319
+ target,
320
+ type,
321
+ status,
322
+ relation_key: key,
323
+ source_event_id: sourceEventId || detail?.source_event_id,
324
+ conflict_id: conflictId || detail?.conflict_id,
325
+ timestamp: toIso(timestamp, nowIso),
326
+ summary: detail?.summary || `${source} ${type} ${target}`,
327
+ source_text_nav: detail?.source_text_nav,
328
+ relation_origin: detail?.relation_origin,
329
+ relation_definition: detail?.relation_definition,
330
+ evidence_span: detail?.evidence_span || (typeof edge.evidence_span === "string" ? edge.evidence_span : undefined),
331
+ context_chunk: detail?.context_chunk || (typeof edge.evidence_span === "string" ? edge.evidence_span : undefined),
332
+ confidence: typeof edge.confidence === "number" ? edge.confidence : detail?.confidence,
333
+ source_type: detail?.source_type,
334
+ target_type: detail?.target_type,
335
+ });
336
+ }
337
+ return output.sort((a, b) => Date.parse(a.timestamp) - Date.parse(b.timestamp));
338
+ }
339
+ function hardMatch(group, relation) {
340
+ if (group.source_key !== normalizeKey(relation.source))
341
+ return false;
342
+ if (group.relation_cluster !== relationCluster(relation.type))
343
+ return false;
344
+ const targetKey = normalizeKey(relation.target);
345
+ const targetClass = normalizeKey(relation.target_type || "");
346
+ return group.targets.has(targetKey) || (!!group.target_class && !!targetClass && group.target_class === targetClass);
347
+ }
348
+ function softScore(group, relation) {
349
+ const latest = group.relations[group.relations.length - 1];
350
+ if (!latest)
351
+ return 0;
352
+ let score = 0;
353
+ if (withinDays(latest.timestamp, relation.timestamp, 30))
354
+ score += 1;
355
+ if (overlapRatio(`${latest.evidence_span || ""} ${latest.context_chunk || ""}`, `${relation.evidence_span || ""} ${relation.context_chunk || ""}`) >= 0.2)
356
+ score += 1;
357
+ if (diceSimilarity(`${latest.summary || ""} ${latest.context_chunk || ""}`, `${relation.summary || ""} ${relation.context_chunk || ""}`) >= 0.82)
358
+ score += 1;
359
+ return score;
360
+ }
361
+ function buildTimelineGroups(relations) {
362
+ const groups = [];
363
+ const sorted = [...relations].sort((a, b) => Date.parse(a.timestamp) - Date.parse(b.timestamp));
364
+ for (const relation of sorted) {
365
+ let best = null;
366
+ let bestScore = -1;
367
+ for (const group of groups) {
368
+ if (!hardMatch(group, relation))
369
+ continue;
370
+ const score = softScore(group, relation);
371
+ if (score >= 2 && score > bestScore) {
372
+ best = group;
373
+ bestScore = score;
374
+ }
375
+ }
376
+ if (!best) {
377
+ groups.push({
378
+ source: relation.source,
379
+ source_key: normalizeKey(relation.source),
380
+ relation_type: relation.type,
381
+ relation_cluster: relationCluster(relation.type),
382
+ target_class: normalizeKey(relation.target_type || ""),
383
+ targets: new Set([normalizeKey(relation.target)]),
384
+ relations: [relation],
385
+ timeline_id: "",
386
+ });
387
+ continue;
388
+ }
389
+ best.relations.push(relation);
390
+ best.targets.add(normalizeKey(relation.target));
391
+ }
392
+ const used = new Set();
393
+ for (const group of groups) {
394
+ const targetScope = group.targets.size > 1 ? (group.target_class || "multi_target") : (group.relations[0]?.target || "target");
395
+ const base = slugify(`${group.source}.${targetScope}.${group.relation_type}`);
396
+ let id = base;
397
+ let idx = 2;
398
+ while (used.has(id)) {
399
+ id = `${base}_${idx}`;
400
+ idx += 1;
401
+ }
402
+ used.add(id);
403
+ group.timeline_id = id;
404
+ }
405
+ return groups.sort((a, b) => a.timeline_id.localeCompare(b.timeline_id));
406
+ }
407
+ function renderRelationLine(relation) {
408
+ const attrs = [
409
+ `evidence=${sanitizeInline(relation.evidence_span)}`,
410
+ `confidence=${typeof relation.confidence === "number" ? relation.confidence : "n/a"}`,
411
+ `source_event_id=${sanitizeInline(relation.source_event_id)}`,
412
+ ];
413
+ if (relation.conflict_id)
414
+ attrs.push(`conflict_id=${sanitizeInline(relation.conflict_id)}`);
415
+ if (relation.relation_origin)
416
+ attrs.push(`relation_origin=${relation.relation_origin}`);
417
+ if (relation.relation_origin === "llm_custom" && relation.relation_definition) {
418
+ attrs.push(`relation_definition=${sanitizeInline(relation.relation_definition).replace(/,/g, ";")}`);
419
+ }
420
+ return `- ${relation.source} --${relation.type}/${relation.status}--> ${relation.target} (${attrs.join(", ")})`;
421
+ }
422
+ function timelineLines(relations) {
423
+ if (relations.length === 0)
424
+ return ["- (none)"];
425
+ const out = [];
426
+ for (const relation of [...relations].sort((a, b) => Date.parse(a.timestamp) - Date.parse(b.timestamp))) {
427
+ const evidenceIds = [
428
+ `graph:relation:${relation.relation_key}`,
429
+ relation.source_event_id ? `graph:event:${relation.source_event_id}` : "",
430
+ relation.conflict_id ? `graph:conflict:${relation.conflict_id}` : "",
431
+ ].filter(Boolean).join(", ");
432
+ out.push(`- ${relation.timestamp} | ${relation.status}`);
433
+ out.push(` evidence_ids: ${evidenceIds || "n/a"}`);
434
+ out.push(` context_chunk: ${sanitizeInline(relation.context_chunk || relation.evidence_span)}`);
435
+ }
436
+ return out;
437
+ }
438
+ function latestStatus(relations) {
439
+ if (relations.length === 0)
440
+ return "active";
441
+ return [...relations].sort((a, b) => Date.parse(a.timestamp) - Date.parse(b.timestamp)).slice(-1)[0].status;
442
+ }
443
+ function sourceRefs(relations) {
444
+ const dedupe = new Set();
445
+ const out = [];
446
+ for (const relation of relations) {
447
+ const nav = relation.source_text_nav;
448
+ const line = nav
449
+ ? `- source_event_id=${sanitizeInline(relation.source_event_id)} | layer=${sanitizeInline(nav.layer)} | source_file=${sanitizeInline(nav.source_file)} | source_memory_id=${sanitizeInline(nav.source_memory_id)}${nav.fulltext_anchor ? ` | fulltext_anchor=${sanitizeInline(nav.fulltext_anchor)}` : ""}`
450
+ : `- source_event_id=${sanitizeInline(relation.source_event_id)} | fulltext_nav_missing=true`;
451
+ if (!dedupe.has(line)) {
452
+ dedupe.add(line);
453
+ out.push(line);
454
+ }
455
+ }
456
+ return out.length > 0 ? out : ["- (none)"];
457
+ }
458
+ function section(title, lines) {
459
+ return [`## ${title}`, "", ...(lines.length > 0 ? lines : ["- (none)"]), ""];
460
+ }
461
+ function projectWikiKnowledge(args) {
462
+ const wikiRoot = path.join(args.memoryRoot, "wiki");
463
+ const entitiesDir = path.join(wikiRoot, "entities");
464
+ const topicsDir = path.join(wikiRoot, "topics");
465
+ const timelinesDir = path.join(wikiRoot, "timelines");
466
+ const indexPath = path.join(wikiRoot, "index.md");
467
+ const projectionIndexPath = path.join(wikiRoot, ".projection_index.json");
468
+ ensureDir(wikiRoot);
469
+ ensureDir(entitiesDir);
470
+ ensureDir(topicsDir);
471
+ ensureDir(timelinesDir);
472
+ const relations = buildProjectedRelations({
473
+ memoryRoot: args.memoryRoot,
474
+ graphView: args.graphView,
475
+ });
476
+ const byEntity = new Map();
477
+ const byTopic = new Map();
478
+ for (const relation of relations) {
479
+ for (const entity of [relation.source, relation.target]) {
480
+ const display = entity.trim();
481
+ const key = display.toLowerCase();
482
+ if (!key)
483
+ continue;
484
+ if (!byEntity.has(key)) {
485
+ byEntity.set(key, { display, relations: [] });
486
+ }
487
+ byEntity.get(key)?.relations.push(relation);
488
+ }
489
+ if (!byTopic.has(relation.type)) {
490
+ byTopic.set(relation.type, []);
491
+ }
492
+ byTopic.get(relation.type)?.push(relation);
493
+ }
494
+ const entityEntries = [];
495
+ const topicEntries = [];
496
+ const timelineEntries = [];
497
+ for (const [, value] of sortByString([...byEntity.entries()], item => item[0])) {
498
+ const entity = value.display;
499
+ const fileName = `${slugify(entity)}.md`;
500
+ const filePath = path.join(entitiesDir, fileName);
501
+ entityEntries.push({ name: entity, file: fileName });
502
+ const active = value.relations.filter(item => item.status === "active").map(renderRelationLine);
503
+ const pending = value.relations.filter(item => item.status === "pending_conflict").map(renderRelationLine);
504
+ const history = value.relations.filter(item => item.status === "superseded" || item.status === "rejected").map(renderRelationLine);
505
+ const body = [
506
+ `# Entity: ${entity}`,
507
+ "",
508
+ "## Summary",
509
+ "",
510
+ `${entity} has ${value.relations.length} related facts in graph projection.`,
511
+ "",
512
+ ...section("Current Facts", active),
513
+ ...section("Disputed Facts", pending),
514
+ ...section("History", history),
515
+ ...section("Source References", sourceRefs(value.relations)),
516
+ "## Updated At",
517
+ "",
518
+ `- ${args.graphView.updated_at}`,
519
+ "",
520
+ ];
521
+ fs.writeFileSync(filePath, `${body.join("\n")}\n`, "utf-8");
522
+ }
523
+ for (const [topic, topicRelations] of sortByString([...byTopic.entries()], item => item[0])) {
524
+ const fileName = `${slugify(topic)}.md`;
525
+ const filePath = path.join(topicsDir, fileName);
526
+ topicEntries.push({ type: topic, file: fileName });
527
+ const body = [
528
+ `# Topic: ${topic}`,
529
+ "",
530
+ "## Summary",
531
+ "",
532
+ `${topic} has ${topicRelations.length} relations. Latest status is ${latestStatus(topicRelations)}.`,
533
+ "",
534
+ ...section("Timeline", timelineLines(topicRelations)),
535
+ "## Latest Status",
536
+ "",
537
+ `- ${latestStatus(topicRelations)}`,
538
+ "",
539
+ ...section("Relations", topicRelations.map(renderRelationLine)),
540
+ ...section("Source References", sourceRefs(topicRelations)),
541
+ "## Updated At",
542
+ "",
543
+ `- ${args.graphView.updated_at}`,
544
+ "",
545
+ ];
546
+ fs.writeFileSync(filePath, `${body.join("\n")}\n`, "utf-8");
547
+ }
548
+ const timelineGroups = buildTimelineGroups(relations);
549
+ for (const group of timelineGroups) {
550
+ const targetScope = group.targets.size > 1 ? (group.target_class || "multi_target") : (group.relations[0]?.target || "target");
551
+ const timelineId = `${group.source}.${targetScope}.${group.relation_type}`;
552
+ const fileName = `${group.timeline_id}.md`;
553
+ const filePath = path.join(timelinesDir, fileName);
554
+ const latest = latestStatus(group.relations);
555
+ timelineEntries.push({
556
+ id: timelineId,
557
+ file: fileName,
558
+ relation_type: group.relation_type,
559
+ source: group.source,
560
+ target_scope: targetScope,
561
+ latest_status: latest,
562
+ });
563
+ const body = [
564
+ `# Timeline: ${timelineId}`,
565
+ "",
566
+ "## Summary",
567
+ "",
568
+ `${group.source} ${group.relation_type} timeline has ${group.relations.length} entries. Latest status is ${latest}.`,
569
+ "",
570
+ ...section("Timeline", timelineLines(group.relations)),
571
+ "## Latest Status",
572
+ "",
573
+ `- ${latest}`,
574
+ "",
575
+ ...section("Relations", group.relations.map(renderRelationLine)),
576
+ ...section("Source References", sourceRefs(group.relations)),
577
+ "## Updated At",
578
+ "",
579
+ `- ${args.graphView.updated_at}`,
580
+ "",
581
+ ];
582
+ fs.writeFileSync(filePath, `${body.join("\n")}\n`, "utf-8");
583
+ }
584
+ const indexBody = [
585
+ "# Memory Wiki Index",
586
+ "",
587
+ `Generated at: ${new Date().toISOString()}`,
588
+ "",
589
+ "## Entities",
590
+ "",
591
+ ...(entityEntries.length > 0 ? entityEntries.map(item => `- [${item.name}](entities/${item.file})`) : ["- (none)"]),
592
+ "",
593
+ "## Topics",
594
+ "",
595
+ ...(topicEntries.length > 0 ? topicEntries.map(item => `- [${item.type}](topics/${item.file})`) : ["- (none)"]),
596
+ "",
597
+ "## Timelines",
598
+ "",
599
+ ...(timelineEntries.length > 0 ? timelineEntries.map(item => `- [${item.id}](timelines/${item.file})`) : ["- (none)"]),
600
+ "",
601
+ ];
602
+ fs.writeFileSync(indexPath, `${indexBody.join("\n")}\n`, "utf-8");
603
+ const projectionIndex = {
604
+ updated_at: new Date().toISOString(),
605
+ graph_updated_at: args.graphView.updated_at,
606
+ entities: entityEntries.map(item => ({ name: item.name, path: `entities/${item.file}` })),
607
+ topics: topicEntries.map(item => ({ type: item.type, path: `topics/${item.file}` })),
608
+ timelines: timelineEntries.map(item => ({
609
+ id: item.id,
610
+ relation_type: item.relation_type,
611
+ source: item.source,
612
+ target_scope: item.target_scope,
613
+ latest_status: item.latest_status,
614
+ path: `timelines/${item.file}`,
615
+ })),
616
+ queue_events: args.queueEvents.map(item => ({ id: item.id, type: item.type, at: item.at })),
617
+ };
618
+ fs.writeFileSync(projectionIndexPath, `${JSON.stringify(projectionIndex, null, 2)}\n`, "utf-8");
619
+ return {
620
+ updated_at: projectionIndex.updated_at,
621
+ entities_count: entityEntries.length,
622
+ topics_count: topicEntries.length,
623
+ timelines_count: timelineEntries.length,
624
+ files: {
625
+ index: indexPath,
626
+ projection_index: projectionIndexPath,
627
+ entities_dir: entitiesDir,
628
+ topics_dir: topicsDir,
629
+ timelines_dir: timelinesDir,
630
+ },
631
+ };
632
+ }
633
+ //# sourceMappingURL=wiki_projector.js.map