openclaw-cortex-memory 0.1.0-Alpha.9 → 0.1.1
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 +347 -290
- package/SIGNATURE.md +7 -0
- package/SKILL.md +96 -345
- package/dist/index.d.ts +69 -23
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1130 -1330
- package/dist/index.js.map +1 -1
- package/dist/openclaw.plugin.json +397 -18
- package/dist/src/dedup/three_stage_deduplicator.d.ts.map +1 -1
- package/dist/src/dedup/three_stage_deduplicator.js +13 -3
- package/dist/src/dedup/three_stage_deduplicator.js.map +1 -1
- package/dist/src/engine/memory_engine.d.ts +5 -1
- package/dist/src/engine/memory_engine.d.ts.map +1 -1
- package/dist/src/engine/ts_engine.d.ts +149 -0
- package/dist/src/engine/ts_engine.d.ts.map +1 -1
- package/dist/src/engine/ts_engine.js +863 -203
- package/dist/src/engine/ts_engine.js.map +1 -1
- package/dist/src/engine/types.d.ts +20 -0
- package/dist/src/engine/types.d.ts.map +1 -1
- package/dist/src/graph/ontology.d.ts +87 -15
- package/dist/src/graph/ontology.d.ts.map +1 -1
- package/dist/src/graph/ontology.js +999 -12
- package/dist/src/graph/ontology.js.map +1 -1
- package/dist/src/net/http_post.d.ts +17 -0
- package/dist/src/net/http_post.d.ts.map +1 -0
- package/dist/src/net/http_post.js +56 -0
- package/dist/src/net/http_post.js.map +1 -0
- package/dist/src/quality/llm_output_validator.d.ts +65 -0
- package/dist/src/quality/llm_output_validator.d.ts.map +1 -0
- package/dist/src/quality/llm_output_validator.js +635 -0
- package/dist/src/quality/llm_output_validator.js.map +1 -0
- package/dist/src/reflect/reflector.d.ts.map +1 -1
- package/dist/src/reflect/reflector.js +296 -26
- package/dist/src/reflect/reflector.js.map +1 -1
- package/dist/src/rules/rule_store.d.ts.map +1 -1
- package/dist/src/rules/rule_store.js +75 -16
- package/dist/src/rules/rule_store.js.map +1 -1
- package/dist/src/session/session_end.d.ts +20 -42
- package/dist/src/session/session_end.d.ts.map +1 -1
- package/dist/src/session/session_end.js +21 -218
- package/dist/src/session/session_end.js.map +1 -1
- package/dist/src/store/archive_store.d.ts +28 -7
- package/dist/src/store/archive_store.d.ts.map +1 -1
- package/dist/src/store/archive_store.js +367 -130
- package/dist/src/store/archive_store.js.map +1 -1
- package/dist/src/store/graph_memory_store.d.ts +115 -0
- package/dist/src/store/graph_memory_store.d.ts.map +1 -0
- package/dist/src/store/graph_memory_store.js +1061 -0
- package/dist/src/store/graph_memory_store.js.map +1 -0
- package/dist/src/store/read_store.d.ts +75 -0
- package/dist/src/store/read_store.d.ts.map +1 -1
- package/dist/src/store/read_store.js +1837 -312
- package/dist/src/store/read_store.js.map +1 -1
- package/dist/src/store/vector_store.d.ts +2 -0
- package/dist/src/store/vector_store.d.ts.map +1 -1
- package/dist/src/store/vector_store.js +19 -3
- package/dist/src/store/vector_store.js.map +1 -1
- package/dist/src/store/write_store.d.ts +11 -0
- package/dist/src/store/write_store.d.ts.map +1 -1
- package/dist/src/store/write_store.js +242 -42
- package/dist/src/store/write_store.js.map +1 -1
- package/dist/src/sync/session_sync.d.ts +72 -1
- package/dist/src/sync/session_sync.d.ts.map +1 -1
- package/dist/src/sync/session_sync.js +2246 -126
- package/dist/src/sync/session_sync.js.map +1 -1
- package/dist/src/wiki/wiki_linter.d.ts +26 -0
- package/dist/src/wiki/wiki_linter.d.ts.map +1 -0
- package/dist/src/wiki/wiki_linter.js +339 -0
- package/dist/src/wiki/wiki_linter.js.map +1 -0
- package/dist/src/wiki/wiki_logger.d.ts +10 -0
- package/dist/src/wiki/wiki_logger.d.ts.map +1 -0
- package/dist/src/wiki/wiki_logger.js +78 -0
- package/dist/src/wiki/wiki_logger.js.map +1 -0
- package/dist/src/wiki/wiki_maintainer.d.ts +39 -0
- package/dist/src/wiki/wiki_maintainer.d.ts.map +1 -0
- package/dist/src/wiki/wiki_maintainer.js +38 -0
- package/dist/src/wiki/wiki_maintainer.js.map +1 -0
- package/dist/src/wiki/wiki_projector.d.ts +35 -0
- package/dist/src/wiki/wiki_projector.d.ts.map +1 -0
- package/dist/src/wiki/wiki_projector.js +1151 -0
- package/dist/src/wiki/wiki_projector.js.map +1 -0
- package/dist/src/wiki/wiki_queue.d.ts +29 -0
- package/dist/src/wiki/wiki_queue.d.ts.map +1 -0
- package/dist/src/wiki/wiki_queue.js +137 -0
- package/dist/src/wiki/wiki_queue.js.map +1 -0
- package/openclaw.plugin.json +397 -18
- package/package.json +51 -5
- package/schema/graph.schema.yaml +330 -0
- package/scripts/cli.js +67 -13
- package/scripts/repair-memory.js +321 -0
- package/skills/cortex-memory/SKILL.md +83 -0
- package/skills/cortex-memory/references/agent-manual.md +127 -0
- package/skills/cortex-memory/references/configuration.md +109 -0
- package/skills/cortex-memory/references/publish-checklist.md +45 -0
- package/skills/cortex-memory/references/system-prompt-template.md +27 -0
- package/skills/cortex-memory/references/tools.md +191 -0
|
@@ -0,0 +1,1151 @@
|
|
|
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 normalizeOptionalText(value) {
|
|
97
|
+
if (typeof value !== "string")
|
|
98
|
+
return undefined;
|
|
99
|
+
const text = value.trim().replace(/\s+/g, " ");
|
|
100
|
+
return text || undefined;
|
|
101
|
+
}
|
|
102
|
+
function buildArchiveEventLookup(memoryRoot) {
|
|
103
|
+
const archivePath = path.join(memoryRoot, "sessions", "archive", "sessions.jsonl");
|
|
104
|
+
const events = readJsonl(archivePath);
|
|
105
|
+
const lookup = new Map();
|
|
106
|
+
for (const event of events) {
|
|
107
|
+
const id = normalizeOptionalText(event.id);
|
|
108
|
+
if (!id)
|
|
109
|
+
continue;
|
|
110
|
+
const detail = {
|
|
111
|
+
archive_event_id: id,
|
|
112
|
+
archive_timestamp: normalizeOptionalText(event.timestamp),
|
|
113
|
+
archive_event_type: normalizeOptionalText(event.event_type),
|
|
114
|
+
archive_summary: normalizeOptionalText(event.summary),
|
|
115
|
+
archive_cause: normalizeOptionalText(event.cause),
|
|
116
|
+
archive_process: normalizeOptionalText(event.process),
|
|
117
|
+
archive_result: normalizeOptionalText(event.result || event.outcome),
|
|
118
|
+
archive_outcome: normalizeOptionalText(event.outcome),
|
|
119
|
+
archive_source_file: normalizeOptionalText(event.source_file),
|
|
120
|
+
archive_confidence: typeof event.confidence === "number" ? event.confidence : undefined,
|
|
121
|
+
};
|
|
122
|
+
const keys = [
|
|
123
|
+
id,
|
|
124
|
+
normalizeOptionalText(event.source_event_id),
|
|
125
|
+
].filter((item) => !!item);
|
|
126
|
+
for (const key of keys) {
|
|
127
|
+
lookup.set(normalizeKey(key), detail);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
return lookup;
|
|
131
|
+
}
|
|
132
|
+
function findArchiveEventDetail(lookup, keys) {
|
|
133
|
+
for (const key of keys) {
|
|
134
|
+
const normalized = normalizeKey(key);
|
|
135
|
+
if (!normalized)
|
|
136
|
+
continue;
|
|
137
|
+
const hit = lookup.get(normalized);
|
|
138
|
+
if (hit)
|
|
139
|
+
return hit;
|
|
140
|
+
}
|
|
141
|
+
return undefined;
|
|
142
|
+
}
|
|
143
|
+
function archiveEventFields(detail) {
|
|
144
|
+
if (!detail)
|
|
145
|
+
return {};
|
|
146
|
+
return {
|
|
147
|
+
archive_event_id: detail.archive_event_id,
|
|
148
|
+
archive_timestamp: detail.archive_timestamp,
|
|
149
|
+
archive_event_type: detail.archive_event_type,
|
|
150
|
+
archive_summary: detail.archive_summary,
|
|
151
|
+
archive_cause: detail.archive_cause,
|
|
152
|
+
archive_process: detail.archive_process,
|
|
153
|
+
archive_result: detail.archive_result,
|
|
154
|
+
archive_outcome: detail.archive_outcome,
|
|
155
|
+
archive_source_file: detail.archive_source_file,
|
|
156
|
+
archive_confidence: detail.archive_confidence,
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
function sanitizeInline(value, fallback = "n/a") {
|
|
160
|
+
const text = String(value || "").trim().replace(/\s+/g, " ");
|
|
161
|
+
return text || fallback;
|
|
162
|
+
}
|
|
163
|
+
function escapeMermaidText(value) {
|
|
164
|
+
return value.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
|
|
165
|
+
}
|
|
166
|
+
function buildMermaidNetwork(view) {
|
|
167
|
+
const lines = ["graph LR"];
|
|
168
|
+
const nodeIdByName = new Map();
|
|
169
|
+
let nodeIndex = 0;
|
|
170
|
+
for (const node of view.nodes || []) {
|
|
171
|
+
const name = String(node.id || "").trim();
|
|
172
|
+
if (!name)
|
|
173
|
+
continue;
|
|
174
|
+
const key = name.toLowerCase();
|
|
175
|
+
if (!nodeIdByName.has(key)) {
|
|
176
|
+
nodeIdByName.set(key, `n${nodeIndex}`);
|
|
177
|
+
nodeIndex += 1;
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
const linkStyles = [];
|
|
181
|
+
let edgeIndex = 0;
|
|
182
|
+
for (const edge of view.edges || []) {
|
|
183
|
+
const sourceName = String(edge.source || "").trim();
|
|
184
|
+
const targetName = String(edge.target || "").trim();
|
|
185
|
+
if (!sourceName || !targetName)
|
|
186
|
+
continue;
|
|
187
|
+
const sourceKey = sourceName.toLowerCase();
|
|
188
|
+
const targetKey = targetName.toLowerCase();
|
|
189
|
+
if (!nodeIdByName.has(sourceKey)) {
|
|
190
|
+
nodeIdByName.set(sourceKey, `n${nodeIndex}`);
|
|
191
|
+
nodeIndex += 1;
|
|
192
|
+
}
|
|
193
|
+
if (!nodeIdByName.has(targetKey)) {
|
|
194
|
+
nodeIdByName.set(targetKey, `n${nodeIndex}`);
|
|
195
|
+
nodeIndex += 1;
|
|
196
|
+
}
|
|
197
|
+
const sourceNode = nodeIdByName.get(sourceKey);
|
|
198
|
+
const targetNode = nodeIdByName.get(targetKey);
|
|
199
|
+
const label = escapeMermaidText(`${edge.type} [${edge.status}]`);
|
|
200
|
+
lines.push(` ${sourceNode}["${escapeMermaidText(sourceName)}"] -->|"${label}"| ${targetNode}["${escapeMermaidText(targetName)}"]`);
|
|
201
|
+
if (edge.status === "active") {
|
|
202
|
+
linkStyles.push(` linkStyle ${edgeIndex} stroke:#2a9d8f,stroke-width:2px;`);
|
|
203
|
+
}
|
|
204
|
+
else if (edge.status === "pending_conflict") {
|
|
205
|
+
linkStyles.push(` linkStyle ${edgeIndex} stroke:#f4a261,stroke-width:2px,stroke-dasharray: 5 4;`);
|
|
206
|
+
}
|
|
207
|
+
else if (edge.status === "superseded") {
|
|
208
|
+
linkStyles.push(` linkStyle ${edgeIndex} stroke:#8d99ae,stroke-width:2px,stroke-dasharray: 2 4;`);
|
|
209
|
+
}
|
|
210
|
+
else {
|
|
211
|
+
linkStyles.push(` linkStyle ${edgeIndex} stroke:#e63946,stroke-width:2px,stroke-dasharray: 5 4;`);
|
|
212
|
+
}
|
|
213
|
+
edgeIndex += 1;
|
|
214
|
+
}
|
|
215
|
+
if (edgeIndex === 0) {
|
|
216
|
+
lines.push(" empty_graph[\"No graph edges\"]");
|
|
217
|
+
}
|
|
218
|
+
lines.push(...linkStyles);
|
|
219
|
+
return `${lines.join("\n")}\n`;
|
|
220
|
+
}
|
|
221
|
+
function writeGraphViewProjection(args) {
|
|
222
|
+
const graphDir = path.join(args.memoryRoot, "wiki", "graph");
|
|
223
|
+
const viewPath = path.join(graphDir, "view.json");
|
|
224
|
+
const timelinePath = path.join(graphDir, "timeline.jsonl");
|
|
225
|
+
const mermaidPath = path.join(graphDir, "network.mmd");
|
|
226
|
+
const networkMarkdownPath = path.join(graphDir, "network.md");
|
|
227
|
+
const nowIso = new Date().toISOString();
|
|
228
|
+
const snapshotId = `gview_${Date.now().toString(36)}`;
|
|
229
|
+
ensureDir(graphDir);
|
|
230
|
+
const viewPayload = {
|
|
231
|
+
...args.view,
|
|
232
|
+
generated_at: nowIso,
|
|
233
|
+
snapshot_id: snapshotId,
|
|
234
|
+
};
|
|
235
|
+
fs.writeFileSync(viewPath, `${JSON.stringify(viewPayload, null, 2)}\n`, "utf-8");
|
|
236
|
+
const timelineEntry = {
|
|
237
|
+
snapshot_id: snapshotId,
|
|
238
|
+
generated_at: nowIso,
|
|
239
|
+
graph_updated_at: args.view.updated_at,
|
|
240
|
+
nodes: args.view.nodes.length,
|
|
241
|
+
edges: args.view.edges.length,
|
|
242
|
+
status_counts: args.view.status_counts,
|
|
243
|
+
};
|
|
244
|
+
fs.appendFileSync(timelinePath, `${JSON.stringify(timelineEntry)}\n`, "utf-8");
|
|
245
|
+
const mermaid = buildMermaidNetwork(args.view);
|
|
246
|
+
fs.writeFileSync(mermaidPath, mermaid, "utf-8");
|
|
247
|
+
const markdownBody = [
|
|
248
|
+
"# Graph Network",
|
|
249
|
+
"",
|
|
250
|
+
`Generated at: ${nowIso}`,
|
|
251
|
+
`Snapshot ID: ${snapshotId}`,
|
|
252
|
+
"",
|
|
253
|
+
"```mermaid",
|
|
254
|
+
mermaid.trimEnd(),
|
|
255
|
+
"```",
|
|
256
|
+
"",
|
|
257
|
+
].join("\n");
|
|
258
|
+
fs.writeFileSync(networkMarkdownPath, markdownBody, "utf-8");
|
|
259
|
+
return {
|
|
260
|
+
view_path: viewPath,
|
|
261
|
+
timeline_path: timelinePath,
|
|
262
|
+
snapshot_id: snapshotId,
|
|
263
|
+
mermaid_path: mermaidPath,
|
|
264
|
+
network_markdown_path: networkMarkdownPath,
|
|
265
|
+
};
|
|
266
|
+
}
|
|
267
|
+
function relationCluster(typeRaw) {
|
|
268
|
+
const type = normalizeKey(typeRaw);
|
|
269
|
+
if (type === "resolves" || type === "solved_with")
|
|
270
|
+
return "resolution";
|
|
271
|
+
if (type === "plans_to" || type === "planned_for" || type === "scheduled_for")
|
|
272
|
+
return "planning";
|
|
273
|
+
return type;
|
|
274
|
+
}
|
|
275
|
+
function normalizeForSimilarity(value) {
|
|
276
|
+
return value.toLowerCase().replace(/[^a-z0-9\u4e00-\u9fff]+/g, " ").replace(/\s+/g, " ").trim();
|
|
277
|
+
}
|
|
278
|
+
function overlapRatio(left, right) {
|
|
279
|
+
const a = new Set(normalizeForSimilarity(left).split(" ").filter(token => token.length >= 2));
|
|
280
|
+
const b = new Set(normalizeForSimilarity(right).split(" ").filter(token => token.length >= 2));
|
|
281
|
+
if (a.size === 0 || b.size === 0)
|
|
282
|
+
return 0;
|
|
283
|
+
let hit = 0;
|
|
284
|
+
for (const token of a) {
|
|
285
|
+
if (b.has(token))
|
|
286
|
+
hit += 1;
|
|
287
|
+
}
|
|
288
|
+
return hit / Math.max(1, Math.min(a.size, b.size));
|
|
289
|
+
}
|
|
290
|
+
function diceSimilarity(leftRaw, rightRaw) {
|
|
291
|
+
const left = normalizeForSimilarity(leftRaw).replace(/\s+/g, "");
|
|
292
|
+
const right = normalizeForSimilarity(rightRaw).replace(/\s+/g, "");
|
|
293
|
+
if (!left || !right)
|
|
294
|
+
return 0;
|
|
295
|
+
if (left === right)
|
|
296
|
+
return 1;
|
|
297
|
+
const grams = (value) => {
|
|
298
|
+
if (value.length < 2)
|
|
299
|
+
return [value];
|
|
300
|
+
const out = [];
|
|
301
|
+
for (let i = 0; i < value.length - 1; i += 1) {
|
|
302
|
+
out.push(value.slice(i, i + 2));
|
|
303
|
+
}
|
|
304
|
+
return out;
|
|
305
|
+
};
|
|
306
|
+
const lg = grams(left);
|
|
307
|
+
const rg = grams(right);
|
|
308
|
+
const bag = new Map();
|
|
309
|
+
for (const gram of rg) {
|
|
310
|
+
bag.set(gram, (bag.get(gram) || 0) + 1);
|
|
311
|
+
}
|
|
312
|
+
let matches = 0;
|
|
313
|
+
for (const gram of lg) {
|
|
314
|
+
const remain = bag.get(gram) || 0;
|
|
315
|
+
if (remain > 0) {
|
|
316
|
+
matches += 1;
|
|
317
|
+
bag.set(gram, remain - 1);
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
return (2 * matches) / (lg.length + rg.length);
|
|
321
|
+
}
|
|
322
|
+
function withinDays(leftIso, rightIso, days) {
|
|
323
|
+
const left = Date.parse(leftIso);
|
|
324
|
+
const right = Date.parse(rightIso);
|
|
325
|
+
if (!Number.isFinite(left) || !Number.isFinite(right))
|
|
326
|
+
return false;
|
|
327
|
+
return Math.abs(left - right) <= days * 24 * 60 * 60 * 1000;
|
|
328
|
+
}
|
|
329
|
+
function buildProjectedRelations(args) {
|
|
330
|
+
const nowIso = new Date().toISOString();
|
|
331
|
+
const records = readJsonl(path.join(args.memoryRoot, "graph", "memory.jsonl"));
|
|
332
|
+
const conflicts = readJsonl(path.join(args.memoryRoot, "graph", "conflict_queue.jsonl"));
|
|
333
|
+
const superseded = readJsonl(path.join(args.memoryRoot, "graph", "superseded_relations.jsonl"));
|
|
334
|
+
const archiveEvents = buildArchiveEventLookup(args.memoryRoot);
|
|
335
|
+
const byEvent = new Map();
|
|
336
|
+
const byRelation = new Map();
|
|
337
|
+
const byConflict = new Map();
|
|
338
|
+
const conflictUpdatedAt = new Map();
|
|
339
|
+
const supersededByRelation = new Map();
|
|
340
|
+
const supersededByComposite = new Map();
|
|
341
|
+
const upsert = (target, key, next) => {
|
|
342
|
+
const prev = target.get(key);
|
|
343
|
+
if (!prev) {
|
|
344
|
+
target.set(key, next);
|
|
345
|
+
return;
|
|
346
|
+
}
|
|
347
|
+
const prevMs = Date.parse(prev.timestamp || "");
|
|
348
|
+
const nextMs = Date.parse(next.timestamp || "");
|
|
349
|
+
if (!Number.isFinite(prevMs) || (Number.isFinite(nextMs) && nextMs >= prevMs)) {
|
|
350
|
+
target.set(key, next);
|
|
351
|
+
}
|
|
352
|
+
};
|
|
353
|
+
for (const record of records) {
|
|
354
|
+
const sourceEventId = String(record.source_event_id || "").trim();
|
|
355
|
+
const timestamp = toIso(record.timestamp, nowIso);
|
|
356
|
+
const typeMap = entityTypeLookup(record.entity_types);
|
|
357
|
+
const archiveEvent = record.source_layer === "archive_event"
|
|
358
|
+
? findArchiveEventDetail(archiveEvents, [
|
|
359
|
+
record.archive_event_id,
|
|
360
|
+
record.source_event_id,
|
|
361
|
+
record.source_text_nav?.source_memory_id,
|
|
362
|
+
record.source_text_nav?.source_event_id,
|
|
363
|
+
])
|
|
364
|
+
: undefined;
|
|
365
|
+
for (const rel of record.relations || []) {
|
|
366
|
+
const source = String(rel.source || "").trim();
|
|
367
|
+
const target = String(rel.target || "").trim();
|
|
368
|
+
const type = String(rel.type || "").trim().toLowerCase();
|
|
369
|
+
if (!source || !target || !type)
|
|
370
|
+
continue;
|
|
371
|
+
const key = relationKey(source, type, target);
|
|
372
|
+
const detail = {
|
|
373
|
+
source_event_id: sourceEventId || undefined,
|
|
374
|
+
source_layer: record.source_layer,
|
|
375
|
+
source_file: record.source_file,
|
|
376
|
+
timestamp,
|
|
377
|
+
summary: typeof record.summary === "string" ? record.summary.trim() : undefined,
|
|
378
|
+
source_text_nav: record.source_text_nav,
|
|
379
|
+
source_type: typeMap.get(normalizeKey(source)),
|
|
380
|
+
target_type: typeMap.get(normalizeKey(target)),
|
|
381
|
+
relation_origin: rel.relation_origin,
|
|
382
|
+
relation_definition: typeof rel.relation_definition === "string" ? rel.relation_definition.trim() : undefined,
|
|
383
|
+
evidence_span: typeof rel.evidence_span === "string" ? rel.evidence_span.trim() : undefined,
|
|
384
|
+
context_chunk: typeof rel.context_chunk === "string" ? rel.context_chunk.trim() : undefined,
|
|
385
|
+
confidence: typeof rel.confidence === "number" ? rel.confidence : undefined,
|
|
386
|
+
...archiveEventFields(archiveEvent),
|
|
387
|
+
};
|
|
388
|
+
if (sourceEventId) {
|
|
389
|
+
upsert(byEvent, relationEventKey(key, sourceEventId), detail);
|
|
390
|
+
}
|
|
391
|
+
upsert(byRelation, key, detail);
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
for (const conflict of conflicts) {
|
|
395
|
+
const conflictId = String(conflict.conflict_id || "").trim();
|
|
396
|
+
if (!conflictId)
|
|
397
|
+
continue;
|
|
398
|
+
const updatedAt = toIso(conflict.updated_at || conflict.created_at, nowIso);
|
|
399
|
+
conflictUpdatedAt.set(conflictId, updatedAt);
|
|
400
|
+
const candidate = conflict.candidate || {};
|
|
401
|
+
const typeMap = entityTypeLookup(candidate.entity_types);
|
|
402
|
+
const archiveEvent = conflict.source_layer === "archive_event"
|
|
403
|
+
? findArchiveEventDetail(archiveEvents, [
|
|
404
|
+
conflict.source_event_id,
|
|
405
|
+
candidate.source_text_nav?.source_memory_id,
|
|
406
|
+
candidate.source_text_nav?.source_event_id,
|
|
407
|
+
])
|
|
408
|
+
: undefined;
|
|
409
|
+
for (const rel of candidate.relations || []) {
|
|
410
|
+
const source = String(rel.source || "").trim();
|
|
411
|
+
const target = String(rel.target || "").trim();
|
|
412
|
+
const type = String(rel.type || "").trim().toLowerCase();
|
|
413
|
+
if (!source || !target || !type)
|
|
414
|
+
continue;
|
|
415
|
+
const key = relationKey(source, type, target);
|
|
416
|
+
byConflict.set(conflictRelationKey(conflictId, key), {
|
|
417
|
+
source_event_id: typeof conflict.source_event_id === "string" ? conflict.source_event_id.trim() || undefined : undefined,
|
|
418
|
+
source_layer: conflict.source_layer,
|
|
419
|
+
source_file: conflict.source_file,
|
|
420
|
+
timestamp: updatedAt,
|
|
421
|
+
summary: typeof candidate.summary === "string" ? candidate.summary.trim() : undefined,
|
|
422
|
+
source_text_nav: candidate.source_text_nav,
|
|
423
|
+
source_type: typeMap.get(normalizeKey(source)),
|
|
424
|
+
target_type: typeMap.get(normalizeKey(target)),
|
|
425
|
+
relation_origin: rel.relation_origin,
|
|
426
|
+
relation_definition: typeof rel.relation_definition === "string" ? rel.relation_definition.trim() : undefined,
|
|
427
|
+
evidence_span: typeof rel.evidence_span === "string" ? rel.evidence_span.trim() : undefined,
|
|
428
|
+
context_chunk: typeof rel.context_chunk === "string" ? rel.context_chunk.trim() : undefined,
|
|
429
|
+
confidence: typeof rel.confidence === "number" ? rel.confidence : undefined,
|
|
430
|
+
conflict_id: conflictId,
|
|
431
|
+
...archiveEventFields(archiveEvent),
|
|
432
|
+
});
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
for (const entry of superseded) {
|
|
436
|
+
const key = normalizeKey(entry.relation_key);
|
|
437
|
+
if (!key)
|
|
438
|
+
continue;
|
|
439
|
+
const at = toIso(entry.superseded_at, nowIso);
|
|
440
|
+
const conflictId = normalizeKey(entry.conflict_id);
|
|
441
|
+
supersededByRelation.set(key, at);
|
|
442
|
+
if (conflictId) {
|
|
443
|
+
supersededByComposite.set(conflictRelationKey(conflictId, key), at);
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
const output = [];
|
|
447
|
+
for (const edge of args.graphView.edges) {
|
|
448
|
+
const source = String(edge.source || "").trim();
|
|
449
|
+
const target = String(edge.target || "").trim();
|
|
450
|
+
const type = String(edge.type || "").trim().toLowerCase();
|
|
451
|
+
if (!source || !target || !type)
|
|
452
|
+
continue;
|
|
453
|
+
const key = normalizeKey(edge.relation_key) || relationKey(source, type, target);
|
|
454
|
+
const sourceEventId = typeof edge.source_event_id === "string" ? edge.source_event_id.trim() : "";
|
|
455
|
+
const conflictId = typeof edge.conflict_id === "string" ? edge.conflict_id.trim() : "";
|
|
456
|
+
const status = edge.status;
|
|
457
|
+
let detail;
|
|
458
|
+
if ((status === "pending_conflict" || status === "rejected") && conflictId) {
|
|
459
|
+
detail = byConflict.get(conflictRelationKey(conflictId, key));
|
|
460
|
+
}
|
|
461
|
+
if (!detail && sourceEventId) {
|
|
462
|
+
detail = byEvent.get(relationEventKey(key, sourceEventId));
|
|
463
|
+
}
|
|
464
|
+
if (!detail) {
|
|
465
|
+
detail = byRelation.get(key);
|
|
466
|
+
}
|
|
467
|
+
let timestamp = detail?.timestamp || nowIso;
|
|
468
|
+
if (status === "superseded") {
|
|
469
|
+
timestamp = supersededByComposite.get(conflictRelationKey(conflictId, key)) || supersededByRelation.get(key) || timestamp;
|
|
470
|
+
}
|
|
471
|
+
else if ((status === "pending_conflict" || status === "rejected") && conflictId) {
|
|
472
|
+
timestamp = conflictUpdatedAt.get(conflictId) || timestamp;
|
|
473
|
+
}
|
|
474
|
+
output.push({
|
|
475
|
+
source,
|
|
476
|
+
target,
|
|
477
|
+
type,
|
|
478
|
+
status,
|
|
479
|
+
relation_key: key,
|
|
480
|
+
source_event_id: sourceEventId || detail?.source_event_id,
|
|
481
|
+
conflict_id: conflictId || detail?.conflict_id,
|
|
482
|
+
timestamp: toIso(timestamp, nowIso),
|
|
483
|
+
summary: detail?.summary || `${source} ${type} ${target}`,
|
|
484
|
+
source_text_nav: detail?.source_text_nav,
|
|
485
|
+
relation_origin: detail?.relation_origin,
|
|
486
|
+
relation_definition: detail?.relation_definition,
|
|
487
|
+
evidence_span: detail?.evidence_span || (typeof edge.evidence_span === "string" ? edge.evidence_span : undefined),
|
|
488
|
+
context_chunk: detail?.context_chunk || (typeof edge.evidence_span === "string" ? edge.evidence_span : undefined),
|
|
489
|
+
confidence: typeof edge.confidence === "number" ? edge.confidence : detail?.confidence,
|
|
490
|
+
source_type: detail?.source_type,
|
|
491
|
+
target_type: detail?.target_type,
|
|
492
|
+
archive_event_id: detail?.archive_event_id,
|
|
493
|
+
archive_timestamp: detail?.archive_timestamp,
|
|
494
|
+
archive_event_type: detail?.archive_event_type,
|
|
495
|
+
archive_summary: detail?.archive_summary,
|
|
496
|
+
archive_cause: detail?.archive_cause,
|
|
497
|
+
archive_process: detail?.archive_process,
|
|
498
|
+
archive_result: detail?.archive_result,
|
|
499
|
+
archive_outcome: detail?.archive_outcome,
|
|
500
|
+
archive_source_file: detail?.archive_source_file,
|
|
501
|
+
archive_confidence: detail?.archive_confidence,
|
|
502
|
+
});
|
|
503
|
+
}
|
|
504
|
+
return output.sort((a, b) => Date.parse(a.timestamp) - Date.parse(b.timestamp));
|
|
505
|
+
}
|
|
506
|
+
function hardMatch(group, relation) {
|
|
507
|
+
if (group.source_key !== normalizeKey(relation.source))
|
|
508
|
+
return false;
|
|
509
|
+
if (group.relation_cluster !== relationCluster(relation.type))
|
|
510
|
+
return false;
|
|
511
|
+
const targetKey = normalizeKey(relation.target);
|
|
512
|
+
const targetClass = normalizeKey(relation.target_type || "");
|
|
513
|
+
return group.targets.has(targetKey) || (!!group.target_class && !!targetClass && group.target_class === targetClass);
|
|
514
|
+
}
|
|
515
|
+
function softScore(group, relation) {
|
|
516
|
+
const latest = group.relations[group.relations.length - 1];
|
|
517
|
+
if (!latest)
|
|
518
|
+
return 0;
|
|
519
|
+
let score = 0;
|
|
520
|
+
if (withinDays(latest.timestamp, relation.timestamp, 30))
|
|
521
|
+
score += 1;
|
|
522
|
+
if (overlapRatio(`${latest.evidence_span || ""} ${latest.context_chunk || ""}`, `${relation.evidence_span || ""} ${relation.context_chunk || ""}`) >= 0.2)
|
|
523
|
+
score += 1;
|
|
524
|
+
if (diceSimilarity(`${latest.summary || ""} ${latest.context_chunk || ""}`, `${relation.summary || ""} ${relation.context_chunk || ""}`) >= 0.82)
|
|
525
|
+
score += 1;
|
|
526
|
+
return score;
|
|
527
|
+
}
|
|
528
|
+
function buildTimelineGroups(relations) {
|
|
529
|
+
const groups = [];
|
|
530
|
+
const sorted = [...relations].sort((a, b) => Date.parse(a.timestamp) - Date.parse(b.timestamp));
|
|
531
|
+
for (const relation of sorted) {
|
|
532
|
+
let best = null;
|
|
533
|
+
let bestScore = -1;
|
|
534
|
+
for (const group of groups) {
|
|
535
|
+
if (!hardMatch(group, relation))
|
|
536
|
+
continue;
|
|
537
|
+
const score = softScore(group, relation);
|
|
538
|
+
if (score >= 2 && score > bestScore) {
|
|
539
|
+
best = group;
|
|
540
|
+
bestScore = score;
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
if (!best) {
|
|
544
|
+
groups.push({
|
|
545
|
+
source: relation.source,
|
|
546
|
+
source_key: normalizeKey(relation.source),
|
|
547
|
+
relation_type: relation.type,
|
|
548
|
+
relation_cluster: relationCluster(relation.type),
|
|
549
|
+
target_class: normalizeKey(relation.target_type || ""),
|
|
550
|
+
targets: new Set([normalizeKey(relation.target)]),
|
|
551
|
+
relations: [relation],
|
|
552
|
+
timeline_id: "",
|
|
553
|
+
});
|
|
554
|
+
continue;
|
|
555
|
+
}
|
|
556
|
+
best.relations.push(relation);
|
|
557
|
+
best.targets.add(normalizeKey(relation.target));
|
|
558
|
+
}
|
|
559
|
+
const used = new Set();
|
|
560
|
+
for (const group of groups) {
|
|
561
|
+
const targetScope = group.targets.size > 1 ? (group.target_class || "multi_target") : (group.relations[0]?.target || "target");
|
|
562
|
+
const base = slugify(`${group.source}.${targetScope}.${group.relation_type}`);
|
|
563
|
+
let id = base;
|
|
564
|
+
let idx = 2;
|
|
565
|
+
while (used.has(id)) {
|
|
566
|
+
id = `${base}_${idx}`;
|
|
567
|
+
idx += 1;
|
|
568
|
+
}
|
|
569
|
+
used.add(id);
|
|
570
|
+
group.timeline_id = id;
|
|
571
|
+
}
|
|
572
|
+
return groups.sort((a, b) => a.timeline_id.localeCompare(b.timeline_id));
|
|
573
|
+
}
|
|
574
|
+
function escapeMarkdownLabel(value) {
|
|
575
|
+
return value.replace(/\\/g, "\\\\").replace(/\[/g, "\\[").replace(/\]/g, "\\]").replace(/\|/g, "\\|");
|
|
576
|
+
}
|
|
577
|
+
function entityLinkPath(entity, page) {
|
|
578
|
+
const fileName = `${slugify(entity)}.md`;
|
|
579
|
+
if (page === "entity")
|
|
580
|
+
return `./${fileName}`;
|
|
581
|
+
return `../entities/${fileName}`;
|
|
582
|
+
}
|
|
583
|
+
function topicLinkPath(topic, page) {
|
|
584
|
+
const fileName = `${slugify(topic)}.md`;
|
|
585
|
+
if (page === "topic")
|
|
586
|
+
return `./${fileName}`;
|
|
587
|
+
return `../topics/${fileName}`;
|
|
588
|
+
}
|
|
589
|
+
function timelineLinkPath(fileName, page) {
|
|
590
|
+
if (page === "timeline")
|
|
591
|
+
return `./${fileName}`;
|
|
592
|
+
return `../timelines/${fileName}`;
|
|
593
|
+
}
|
|
594
|
+
function markdownLink(label, target) {
|
|
595
|
+
return `[${escapeMarkdownLabel(label)}](${target})`;
|
|
596
|
+
}
|
|
597
|
+
function renderRelationLine(args) {
|
|
598
|
+
const relation = args.relation;
|
|
599
|
+
const attrs = [
|
|
600
|
+
`evidence=${sanitizeInline(relation.evidence_span)}`,
|
|
601
|
+
`confidence=${typeof relation.confidence === "number" ? relation.confidence : "n/a"}`,
|
|
602
|
+
`source_event_id=${sanitizeInline(relation.source_event_id)}`,
|
|
603
|
+
];
|
|
604
|
+
if (relation.conflict_id)
|
|
605
|
+
attrs.push(`conflict_id=${sanitizeInline(relation.conflict_id)}`);
|
|
606
|
+
if (relation.relation_origin)
|
|
607
|
+
attrs.push(`relation_origin=${relation.relation_origin}`);
|
|
608
|
+
if (relation.relation_origin === "llm_custom" && relation.relation_definition) {
|
|
609
|
+
attrs.push(`relation_definition=${sanitizeInline(relation.relation_definition).replace(/,/g, ";")}`);
|
|
610
|
+
}
|
|
611
|
+
const sourceLabel = sanitizeInline(relation.source, relation.source);
|
|
612
|
+
const targetLabel = sanitizeInline(relation.target, relation.target);
|
|
613
|
+
const source = normalizeKey(sourceLabel) === normalizeKey(args.currentEntity || "")
|
|
614
|
+
? sourceLabel
|
|
615
|
+
: markdownLink(sourceLabel, entityLinkPath(sourceLabel, args.page));
|
|
616
|
+
const target = normalizeKey(targetLabel) === normalizeKey(args.currentEntity || "")
|
|
617
|
+
? targetLabel
|
|
618
|
+
: markdownLink(targetLabel, entityLinkPath(targetLabel, args.page));
|
|
619
|
+
const typeLabel = sanitizeInline(relation.type, relation.type);
|
|
620
|
+
const typeLink = normalizeKey(typeLabel) === normalizeKey(args.currentTopic || "")
|
|
621
|
+
? typeLabel
|
|
622
|
+
: markdownLink(typeLabel, topicLinkPath(typeLabel, args.page));
|
|
623
|
+
return `- ${source} --${typeLink}/${relation.status}--> ${target} (${attrs.join(", ")})`;
|
|
624
|
+
}
|
|
625
|
+
const RELATION_STATUS_ORDER = ["active", "pending_conflict", "superseded", "rejected"];
|
|
626
|
+
function relationStatusCounts(relations) {
|
|
627
|
+
return {
|
|
628
|
+
active: relations.filter(item => item.status === "active").length,
|
|
629
|
+
pending_conflict: relations.filter(item => item.status === "pending_conflict").length,
|
|
630
|
+
superseded: relations.filter(item => item.status === "superseded").length,
|
|
631
|
+
rejected: relations.filter(item => item.status === "rejected").length,
|
|
632
|
+
};
|
|
633
|
+
}
|
|
634
|
+
function formatStatusCounts(relations) {
|
|
635
|
+
const counts = relationStatusCounts(relations);
|
|
636
|
+
return `active=${counts.active}, pending_conflict=${counts.pending_conflict}, superseded=${counts.superseded}, rejected=${counts.rejected}`;
|
|
637
|
+
}
|
|
638
|
+
function sortRecentDesc(relations) {
|
|
639
|
+
return [...relations].sort((a, b) => Date.parse(b.timestamp) - Date.parse(a.timestamp));
|
|
640
|
+
}
|
|
641
|
+
function sortByConfidenceDesc(relations) {
|
|
642
|
+
return [...relations].sort((a, b) => {
|
|
643
|
+
const bc = typeof b.confidence === "number" ? b.confidence : -1;
|
|
644
|
+
const ac = typeof a.confidence === "number" ? a.confidence : -1;
|
|
645
|
+
if (bc !== ac)
|
|
646
|
+
return bc - ac;
|
|
647
|
+
return Date.parse(b.timestamp) - Date.parse(a.timestamp);
|
|
648
|
+
});
|
|
649
|
+
}
|
|
650
|
+
function latestRelation(relations) {
|
|
651
|
+
return sortRecentDesc(relations)[0];
|
|
652
|
+
}
|
|
653
|
+
function relationPlain(relation) {
|
|
654
|
+
return `${sanitizeInline(relation.source)} --${sanitizeInline(relation.type)}/${relation.status}--> ${sanitizeInline(relation.target)}`;
|
|
655
|
+
}
|
|
656
|
+
function relationNarrative(relation) {
|
|
657
|
+
return sanitizeInline(relation.archive_result || relation.summary || relation.archive_summary || relation.context_chunk || relation.evidence_span);
|
|
658
|
+
}
|
|
659
|
+
function renderCurrentConclusionLines(args) {
|
|
660
|
+
if (args.relations.length === 0)
|
|
661
|
+
return ["- (none)"];
|
|
662
|
+
const latest = latestRelation(args.relations);
|
|
663
|
+
const active = sortByConfidenceDesc(args.relations.filter(item => item.status === "active")).slice(0, 3);
|
|
664
|
+
const pending = args.relations.filter(item => item.status === "pending_conflict");
|
|
665
|
+
const lines = [
|
|
666
|
+
`- Status counts: ${formatStatusCounts(args.relations)}${latest ? `; latest=${latest.timestamp}` : ""}`,
|
|
667
|
+
];
|
|
668
|
+
if (active.length > 0) {
|
|
669
|
+
lines.push("- Current accepted facts:");
|
|
670
|
+
lines.push(...active.map(item => ` ${renderRelationLine({
|
|
671
|
+
relation: item,
|
|
672
|
+
page: args.page,
|
|
673
|
+
currentEntity: args.currentEntity,
|
|
674
|
+
currentTopic: args.currentTopic,
|
|
675
|
+
})}`));
|
|
676
|
+
}
|
|
677
|
+
else if (latest) {
|
|
678
|
+
lines.push(`- No active fact yet. Latest recorded item: ${relationPlain(latest)}.`);
|
|
679
|
+
}
|
|
680
|
+
if (pending.length > 0) {
|
|
681
|
+
lines.push(`- Needs review: ${pending.length} pending conflict(s) should be resolved before treating them as current.`);
|
|
682
|
+
}
|
|
683
|
+
return lines;
|
|
684
|
+
}
|
|
685
|
+
function renderRecentChangeLines(relations, limit = 6) {
|
|
686
|
+
const recent = sortRecentDesc(relations).slice(0, limit);
|
|
687
|
+
if (recent.length === 0)
|
|
688
|
+
return ["- (none)"];
|
|
689
|
+
const lines = [];
|
|
690
|
+
for (const relation of recent) {
|
|
691
|
+
lines.push(`- ${relation.timestamp} | ${relationPlain(relation)} | ${relationNarrative(relation)}`);
|
|
692
|
+
if (relation.archive_event_type || relation.archive_event_id) {
|
|
693
|
+
lines.push(` archive_event: ${sanitizeInline(relation.archive_event_type)} ${sanitizeInline(relation.archive_event_id)}`);
|
|
694
|
+
}
|
|
695
|
+
}
|
|
696
|
+
return lines;
|
|
697
|
+
}
|
|
698
|
+
function renderHighConfidenceLines(args) {
|
|
699
|
+
const candidates = sortByConfidenceDesc(args.relations.filter(item => item.status === "active" && typeof item.confidence === "number" && item.confidence >= 0.75)).slice(0, args.limit || 6);
|
|
700
|
+
if (candidates.length === 0)
|
|
701
|
+
return ["- (none)"];
|
|
702
|
+
return candidates.map(item => renderRelationLine({
|
|
703
|
+
relation: item,
|
|
704
|
+
page: args.page,
|
|
705
|
+
currentEntity: args.currentEntity,
|
|
706
|
+
currentTopic: args.currentTopic,
|
|
707
|
+
}));
|
|
708
|
+
}
|
|
709
|
+
function renderEvidenceExcerptLines(relations, limit = 8) {
|
|
710
|
+
const candidates = sortByConfidenceDesc(relations)
|
|
711
|
+
.filter(item => item.evidence_span || item.context_chunk || item.archive_cause || item.archive_process || item.archive_result)
|
|
712
|
+
.slice(0, limit);
|
|
713
|
+
if (candidates.length === 0)
|
|
714
|
+
return ["- (none)"];
|
|
715
|
+
const lines = [];
|
|
716
|
+
for (const relation of candidates) {
|
|
717
|
+
const fields = [
|
|
718
|
+
`confidence=${typeof relation.confidence === "number" ? relation.confidence : "n/a"}`,
|
|
719
|
+
`evidence=${sanitizeInline(relation.evidence_span)}`,
|
|
720
|
+
`context=${sanitizeInline(relation.context_chunk || relation.evidence_span)}`,
|
|
721
|
+
`source_event_id=${sanitizeInline(relation.source_event_id)}`,
|
|
722
|
+
];
|
|
723
|
+
if (relation.archive_event_id)
|
|
724
|
+
fields.push(`archive_event_id=${sanitizeInline(relation.archive_event_id)}`);
|
|
725
|
+
lines.push(`- ${relationPlain(relation)} | ${fields.join(" | ")}`);
|
|
726
|
+
const archiveBits = [
|
|
727
|
+
relation.archive_cause ? `cause=${sanitizeInline(relation.archive_cause)}` : "",
|
|
728
|
+
relation.archive_process ? `process=${sanitizeInline(relation.archive_process)}` : "",
|
|
729
|
+
relation.archive_result ? `result=${sanitizeInline(relation.archive_result)}` : "",
|
|
730
|
+
].filter(Boolean);
|
|
731
|
+
if (archiveBits.length > 0) {
|
|
732
|
+
lines.push(` archive_flow: ${archiveBits.join(" | ")}`);
|
|
733
|
+
}
|
|
734
|
+
}
|
|
735
|
+
return lines;
|
|
736
|
+
}
|
|
737
|
+
function renderStatusGroupLines(args) {
|
|
738
|
+
if (args.relations.length === 0)
|
|
739
|
+
return ["- (none)"];
|
|
740
|
+
const lines = [];
|
|
741
|
+
for (const status of RELATION_STATUS_ORDER) {
|
|
742
|
+
const items = sortByConfidenceDesc(args.relations.filter(item => item.status === status));
|
|
743
|
+
lines.push(`- ${status}: ${items.length}`);
|
|
744
|
+
lines.push(...items.slice(0, 4).map(item => ` ${renderRelationLine({
|
|
745
|
+
relation: item,
|
|
746
|
+
page: args.page,
|
|
747
|
+
currentEntity: args.currentEntity,
|
|
748
|
+
currentTopic: args.currentTopic,
|
|
749
|
+
})}`));
|
|
750
|
+
}
|
|
751
|
+
return lines;
|
|
752
|
+
}
|
|
753
|
+
function timelineLines(relations) {
|
|
754
|
+
if (relations.length === 0)
|
|
755
|
+
return ["- (none)"];
|
|
756
|
+
const out = [];
|
|
757
|
+
for (const relation of [...relations].sort((a, b) => Date.parse(a.timestamp) - Date.parse(b.timestamp))) {
|
|
758
|
+
const evidenceIds = [
|
|
759
|
+
`graph:relation:${relation.relation_key}`,
|
|
760
|
+
relation.source_event_id ? `graph:event:${relation.source_event_id}` : "",
|
|
761
|
+
relation.conflict_id ? `graph:conflict:${relation.conflict_id}` : "",
|
|
762
|
+
].filter(Boolean).join(", ");
|
|
763
|
+
out.push(`- ${relation.timestamp} | ${relation.status}`);
|
|
764
|
+
out.push(` evidence_ids: ${evidenceIds || "n/a"}`);
|
|
765
|
+
out.push(` summary: ${relationNarrative(relation)}`);
|
|
766
|
+
if (relation.archive_cause)
|
|
767
|
+
out.push(` cause: ${sanitizeInline(relation.archive_cause)}`);
|
|
768
|
+
if (relation.archive_process)
|
|
769
|
+
out.push(` process: ${sanitizeInline(relation.archive_process)}`);
|
|
770
|
+
if (relation.archive_result)
|
|
771
|
+
out.push(` result: ${sanitizeInline(relation.archive_result)}`);
|
|
772
|
+
out.push(` context_chunk: ${sanitizeInline(relation.context_chunk || relation.evidence_span)}`);
|
|
773
|
+
}
|
|
774
|
+
return out;
|
|
775
|
+
}
|
|
776
|
+
function eventFlowLines(relations) {
|
|
777
|
+
if (relations.length === 0)
|
|
778
|
+
return ["- (none)"];
|
|
779
|
+
const out = [];
|
|
780
|
+
for (const relation of [...relations].sort((a, b) => Date.parse(a.timestamp) - Date.parse(b.timestamp))) {
|
|
781
|
+
out.push(`- ${relation.timestamp} | ${relationPlain(relation)}`);
|
|
782
|
+
out.push(` summary: ${relationNarrative(relation)}`);
|
|
783
|
+
if (relation.archive_cause)
|
|
784
|
+
out.push(` cause: ${sanitizeInline(relation.archive_cause)}`);
|
|
785
|
+
if (relation.archive_process)
|
|
786
|
+
out.push(` process: ${sanitizeInline(relation.archive_process)}`);
|
|
787
|
+
if (relation.archive_result)
|
|
788
|
+
out.push(` result: ${sanitizeInline(relation.archive_result)}`);
|
|
789
|
+
out.push(` evidence: ${sanitizeInline(relation.evidence_span || relation.context_chunk)}`);
|
|
790
|
+
}
|
|
791
|
+
return out;
|
|
792
|
+
}
|
|
793
|
+
function latestStatus(relations) {
|
|
794
|
+
if (relations.length === 0)
|
|
795
|
+
return "active";
|
|
796
|
+
return [...relations].sort((a, b) => Date.parse(a.timestamp) - Date.parse(b.timestamp)).slice(-1)[0].status;
|
|
797
|
+
}
|
|
798
|
+
function sourceRefs(relations) {
|
|
799
|
+
const dedupe = new Set();
|
|
800
|
+
const out = [];
|
|
801
|
+
for (const relation of relations) {
|
|
802
|
+
const nav = relation.source_text_nav;
|
|
803
|
+
const line = nav
|
|
804
|
+
? `- 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)}` : ""}`
|
|
805
|
+
: `- source_event_id=${sanitizeInline(relation.source_event_id)} | fulltext_nav_missing=true`;
|
|
806
|
+
if (!dedupe.has(line)) {
|
|
807
|
+
dedupe.add(line);
|
|
808
|
+
out.push(line);
|
|
809
|
+
}
|
|
810
|
+
}
|
|
811
|
+
return out.length > 0 ? out : ["- (none)"];
|
|
812
|
+
}
|
|
813
|
+
function section(title, lines) {
|
|
814
|
+
return [`## ${title}`, "", ...(lines.length > 0 ? lines : ["- (none)"]), ""];
|
|
815
|
+
}
|
|
816
|
+
function relatedEntitiesSectionLines(args) {
|
|
817
|
+
const current = normalizeKey(args.currentEntity || "");
|
|
818
|
+
const set = new Set();
|
|
819
|
+
for (const relation of args.relations) {
|
|
820
|
+
for (const entity of [relation.source, relation.target]) {
|
|
821
|
+
const display = String(entity || "").trim();
|
|
822
|
+
const key = normalizeKey(display);
|
|
823
|
+
if (!display || !key || key === current)
|
|
824
|
+
continue;
|
|
825
|
+
set.add(display);
|
|
826
|
+
}
|
|
827
|
+
}
|
|
828
|
+
const values = [...set].sort((a, b) => a.localeCompare(b));
|
|
829
|
+
return values.length > 0
|
|
830
|
+
? values.map(value => `- ${markdownLink(value, entityLinkPath(value, args.page))}`)
|
|
831
|
+
: ["- (none)"];
|
|
832
|
+
}
|
|
833
|
+
function relatedTopicsSectionLines(args) {
|
|
834
|
+
const current = normalizeKey(args.currentTopic || "");
|
|
835
|
+
const set = new Set();
|
|
836
|
+
for (const relation of args.relations) {
|
|
837
|
+
const topic = String(relation.type || "").trim();
|
|
838
|
+
const key = normalizeKey(topic);
|
|
839
|
+
if (!topic || !key || key === current)
|
|
840
|
+
continue;
|
|
841
|
+
set.add(topic);
|
|
842
|
+
}
|
|
843
|
+
const values = [...set].sort((a, b) => a.localeCompare(b));
|
|
844
|
+
return values.length > 0
|
|
845
|
+
? values.map(value => `- ${markdownLink(value, topicLinkPath(value, args.page))}`)
|
|
846
|
+
: ["- (none)"];
|
|
847
|
+
}
|
|
848
|
+
function relatedTimelineSectionLines(args) {
|
|
849
|
+
const set = new Map();
|
|
850
|
+
for (const relation of args.relations) {
|
|
851
|
+
const key = relationIdentity(relation);
|
|
852
|
+
const hit = args.relationTimelineMap.get(key);
|
|
853
|
+
if (!hit)
|
|
854
|
+
continue;
|
|
855
|
+
if (args.currentTimelineFile && hit.file === args.currentTimelineFile)
|
|
856
|
+
continue;
|
|
857
|
+
set.set(hit.file, hit);
|
|
858
|
+
}
|
|
859
|
+
const values = [...set.values()].sort((a, b) => a.id.localeCompare(b.id));
|
|
860
|
+
return values.length > 0
|
|
861
|
+
? values.map(value => `- ${markdownLink(value.id, timelineLinkPath(value.file, args.page))}`)
|
|
862
|
+
: ["- (none)"];
|
|
863
|
+
}
|
|
864
|
+
function relationIdentity(relation) {
|
|
865
|
+
return `${relation.relation_key}|${relation.source_event_id || ""}|${relation.timestamp}|${relation.status}`;
|
|
866
|
+
}
|
|
867
|
+
function projectWikiKnowledge(args) {
|
|
868
|
+
const wikiRoot = path.join(args.memoryRoot, "wiki");
|
|
869
|
+
const entitiesDir = path.join(wikiRoot, "entities");
|
|
870
|
+
const topicsDir = path.join(wikiRoot, "topics");
|
|
871
|
+
const timelinesDir = path.join(wikiRoot, "timelines");
|
|
872
|
+
const indexPath = path.join(wikiRoot, "index.md");
|
|
873
|
+
const projectionIndexPath = path.join(wikiRoot, ".projection_index.json");
|
|
874
|
+
ensureDir(wikiRoot);
|
|
875
|
+
ensureDir(entitiesDir);
|
|
876
|
+
ensureDir(topicsDir);
|
|
877
|
+
ensureDir(timelinesDir);
|
|
878
|
+
const relations = buildProjectedRelations({
|
|
879
|
+
memoryRoot: args.memoryRoot,
|
|
880
|
+
graphView: args.graphView,
|
|
881
|
+
});
|
|
882
|
+
const byEntity = new Map();
|
|
883
|
+
const byTopic = new Map();
|
|
884
|
+
for (const relation of relations) {
|
|
885
|
+
for (const entity of [relation.source, relation.target]) {
|
|
886
|
+
const display = entity.trim();
|
|
887
|
+
const key = display.toLowerCase();
|
|
888
|
+
if (!key)
|
|
889
|
+
continue;
|
|
890
|
+
if (!byEntity.has(key)) {
|
|
891
|
+
byEntity.set(key, { display, relations: [] });
|
|
892
|
+
}
|
|
893
|
+
byEntity.get(key)?.relations.push(relation);
|
|
894
|
+
}
|
|
895
|
+
if (!byTopic.has(relation.type)) {
|
|
896
|
+
byTopic.set(relation.type, []);
|
|
897
|
+
}
|
|
898
|
+
byTopic.get(relation.type)?.push(relation);
|
|
899
|
+
}
|
|
900
|
+
const timelineGroups = buildTimelineGroups(relations);
|
|
901
|
+
const timelineMeta = timelineGroups.map(group => {
|
|
902
|
+
const targetScope = group.targets.size > 1 ? (group.target_class || "multi_target") : (group.relations[0]?.target || "target");
|
|
903
|
+
const timelineId = `${group.source}.${targetScope}.${group.relation_type}`;
|
|
904
|
+
const fileName = `${group.timeline_id}.md`;
|
|
905
|
+
const latest = latestStatus(group.relations);
|
|
906
|
+
return {
|
|
907
|
+
group,
|
|
908
|
+
targetScope,
|
|
909
|
+
timelineId,
|
|
910
|
+
fileName,
|
|
911
|
+
latest,
|
|
912
|
+
};
|
|
913
|
+
});
|
|
914
|
+
const relationTimelineMap = new Map();
|
|
915
|
+
for (const item of timelineMeta) {
|
|
916
|
+
for (const relation of item.group.relations) {
|
|
917
|
+
relationTimelineMap.set(relationIdentity(relation), { id: item.timelineId, file: item.fileName });
|
|
918
|
+
}
|
|
919
|
+
}
|
|
920
|
+
const entityEntries = [];
|
|
921
|
+
const topicEntries = [];
|
|
922
|
+
const timelineEntries = [];
|
|
923
|
+
for (const [, value] of sortByString([...byEntity.entries()], item => item[0])) {
|
|
924
|
+
const entity = value.display;
|
|
925
|
+
const fileName = `${slugify(entity)}.md`;
|
|
926
|
+
const filePath = path.join(entitiesDir, fileName);
|
|
927
|
+
entityEntries.push({ name: entity, file: fileName });
|
|
928
|
+
const active = value.relations
|
|
929
|
+
.filter(item => item.status === "active")
|
|
930
|
+
.map(item => renderRelationLine({ relation: item, page: "entity", currentEntity: entity }));
|
|
931
|
+
const pending = value.relations
|
|
932
|
+
.filter(item => item.status === "pending_conflict")
|
|
933
|
+
.map(item => renderRelationLine({ relation: item, page: "entity", currentEntity: entity }));
|
|
934
|
+
const history = value.relations
|
|
935
|
+
.filter(item => item.status === "superseded" || item.status === "rejected")
|
|
936
|
+
.map(item => renderRelationLine({ relation: item, page: "entity", currentEntity: entity }));
|
|
937
|
+
const body = [
|
|
938
|
+
`# Entity: ${entity}`,
|
|
939
|
+
"",
|
|
940
|
+
...section("Current Conclusion", renderCurrentConclusionLines({
|
|
941
|
+
relations: value.relations,
|
|
942
|
+
page: "entity",
|
|
943
|
+
currentEntity: entity,
|
|
944
|
+
})),
|
|
945
|
+
"## Summary",
|
|
946
|
+
"",
|
|
947
|
+
`${entity} has ${value.relations.length} related facts in graph projection (${formatStatusCounts(value.relations)}).`,
|
|
948
|
+
"",
|
|
949
|
+
...section("Recent Changes", renderRecentChangeLines(value.relations)),
|
|
950
|
+
...section("High Confidence Facts", renderHighConfidenceLines({
|
|
951
|
+
relations: value.relations,
|
|
952
|
+
page: "entity",
|
|
953
|
+
currentEntity: entity,
|
|
954
|
+
})),
|
|
955
|
+
...section("Current Facts", active),
|
|
956
|
+
...section("Open Conflicts", pending),
|
|
957
|
+
...section("Disputed Facts", pending),
|
|
958
|
+
...section("History", history),
|
|
959
|
+
...section("Evidence Excerpts", renderEvidenceExcerptLines(value.relations)),
|
|
960
|
+
...section("Related Entities", relatedEntitiesSectionLines({
|
|
961
|
+
relations: value.relations,
|
|
962
|
+
page: "entity",
|
|
963
|
+
currentEntity: entity,
|
|
964
|
+
})),
|
|
965
|
+
...section("Related Topics", relatedTopicsSectionLines({
|
|
966
|
+
relations: value.relations,
|
|
967
|
+
page: "entity",
|
|
968
|
+
})),
|
|
969
|
+
...section("Related Timelines", relatedTimelineSectionLines({
|
|
970
|
+
relations: value.relations,
|
|
971
|
+
page: "entity",
|
|
972
|
+
relationTimelineMap,
|
|
973
|
+
})),
|
|
974
|
+
...section("Source References", sourceRefs(value.relations)),
|
|
975
|
+
"## Updated At",
|
|
976
|
+
"",
|
|
977
|
+
`- ${args.graphView.updated_at}`,
|
|
978
|
+
"",
|
|
979
|
+
];
|
|
980
|
+
fs.writeFileSync(filePath, `${body.join("\n")}\n`, "utf-8");
|
|
981
|
+
}
|
|
982
|
+
for (const [topic, topicRelations] of sortByString([...byTopic.entries()], item => item[0])) {
|
|
983
|
+
const fileName = `${slugify(topic)}.md`;
|
|
984
|
+
const filePath = path.join(topicsDir, fileName);
|
|
985
|
+
topicEntries.push({ type: topic, file: fileName });
|
|
986
|
+
const body = [
|
|
987
|
+
`# Topic: ${topic}`,
|
|
988
|
+
"",
|
|
989
|
+
"## Summary",
|
|
990
|
+
"",
|
|
991
|
+
`${topic} has ${topicRelations.length} relations (${formatStatusCounts(topicRelations)}). Latest status is ${latestStatus(topicRelations)}.`,
|
|
992
|
+
"",
|
|
993
|
+
"## Latest Status",
|
|
994
|
+
"",
|
|
995
|
+
`- ${latestStatus(topicRelations)}`,
|
|
996
|
+
"",
|
|
997
|
+
...section("Current Conclusion", renderCurrentConclusionLines({
|
|
998
|
+
relations: topicRelations,
|
|
999
|
+
page: "topic",
|
|
1000
|
+
currentTopic: topic,
|
|
1001
|
+
})),
|
|
1002
|
+
...section("Status Groups", renderStatusGroupLines({
|
|
1003
|
+
relations: topicRelations,
|
|
1004
|
+
page: "topic",
|
|
1005
|
+
currentTopic: topic,
|
|
1006
|
+
})),
|
|
1007
|
+
...section("Timeline", timelineLines(topicRelations)),
|
|
1008
|
+
...section("Relations", topicRelations.map(item => renderRelationLine({
|
|
1009
|
+
relation: item,
|
|
1010
|
+
page: "topic",
|
|
1011
|
+
currentTopic: topic,
|
|
1012
|
+
}))),
|
|
1013
|
+
...section("Evidence Excerpts", renderEvidenceExcerptLines(topicRelations)),
|
|
1014
|
+
...section("Related Entities", relatedEntitiesSectionLines({
|
|
1015
|
+
relations: topicRelations,
|
|
1016
|
+
page: "topic",
|
|
1017
|
+
})),
|
|
1018
|
+
...section("Related Timelines", relatedTimelineSectionLines({
|
|
1019
|
+
relations: topicRelations,
|
|
1020
|
+
page: "topic",
|
|
1021
|
+
relationTimelineMap,
|
|
1022
|
+
})),
|
|
1023
|
+
...section("Source References", sourceRefs(topicRelations)),
|
|
1024
|
+
"## Updated At",
|
|
1025
|
+
"",
|
|
1026
|
+
`- ${args.graphView.updated_at}`,
|
|
1027
|
+
"",
|
|
1028
|
+
];
|
|
1029
|
+
fs.writeFileSync(filePath, `${body.join("\n")}\n`, "utf-8");
|
|
1030
|
+
}
|
|
1031
|
+
for (const item of timelineMeta) {
|
|
1032
|
+
const group = item.group;
|
|
1033
|
+
const timelineId = item.timelineId;
|
|
1034
|
+
const fileName = item.fileName;
|
|
1035
|
+
const targetScope = item.targetScope;
|
|
1036
|
+
const filePath = path.join(timelinesDir, fileName);
|
|
1037
|
+
const latest = item.latest;
|
|
1038
|
+
timelineEntries.push({
|
|
1039
|
+
id: timelineId,
|
|
1040
|
+
file: fileName,
|
|
1041
|
+
relation_type: group.relation_type,
|
|
1042
|
+
source: group.source,
|
|
1043
|
+
target_scope: targetScope,
|
|
1044
|
+
latest_status: latest,
|
|
1045
|
+
});
|
|
1046
|
+
const body = [
|
|
1047
|
+
`# Timeline: ${timelineId}`,
|
|
1048
|
+
"",
|
|
1049
|
+
"## Summary",
|
|
1050
|
+
"",
|
|
1051
|
+
`${group.source} ${group.relation_type} timeline has ${group.relations.length} entries (${formatStatusCounts(group.relations)}). Latest status is ${latest}.`,
|
|
1052
|
+
"",
|
|
1053
|
+
"## Latest Status",
|
|
1054
|
+
"",
|
|
1055
|
+
`- ${latest}`,
|
|
1056
|
+
"",
|
|
1057
|
+
...section("Current Conclusion", renderCurrentConclusionLines({
|
|
1058
|
+
relations: group.relations,
|
|
1059
|
+
page: "timeline",
|
|
1060
|
+
})),
|
|
1061
|
+
...section("Event Flow", eventFlowLines(group.relations)),
|
|
1062
|
+
...section("Timeline", timelineLines(group.relations)),
|
|
1063
|
+
...section("Relations", group.relations.map(rel => renderRelationLine({
|
|
1064
|
+
relation: rel,
|
|
1065
|
+
page: "timeline",
|
|
1066
|
+
}))),
|
|
1067
|
+
...section("Evidence Excerpts", renderEvidenceExcerptLines(group.relations)),
|
|
1068
|
+
...section("Related Entities", relatedEntitiesSectionLines({
|
|
1069
|
+
relations: group.relations,
|
|
1070
|
+
page: "timeline",
|
|
1071
|
+
})),
|
|
1072
|
+
...section("Related Topics", relatedTopicsSectionLines({
|
|
1073
|
+
relations: group.relations,
|
|
1074
|
+
page: "timeline",
|
|
1075
|
+
})),
|
|
1076
|
+
...section("Related Timelines", relatedTimelineSectionLines({
|
|
1077
|
+
relations: group.relations,
|
|
1078
|
+
page: "timeline",
|
|
1079
|
+
relationTimelineMap,
|
|
1080
|
+
currentTimelineFile: fileName,
|
|
1081
|
+
})),
|
|
1082
|
+
...section("Source References", sourceRefs(group.relations)),
|
|
1083
|
+
"## Updated At",
|
|
1084
|
+
"",
|
|
1085
|
+
`- ${args.graphView.updated_at}`,
|
|
1086
|
+
"",
|
|
1087
|
+
];
|
|
1088
|
+
fs.writeFileSync(filePath, `${body.join("\n")}\n`, "utf-8");
|
|
1089
|
+
}
|
|
1090
|
+
const indexBody = [
|
|
1091
|
+
"# Memory Wiki Index",
|
|
1092
|
+
"",
|
|
1093
|
+
`Generated at: ${new Date().toISOString()}`,
|
|
1094
|
+
"",
|
|
1095
|
+
"## Quality Snapshot",
|
|
1096
|
+
"",
|
|
1097
|
+
`- Relations: ${relations.length} (${formatStatusCounts(relations)})`,
|
|
1098
|
+
`- Pages: entities=${entityEntries.length}, topics=${topicEntries.length}, timelines=${timelineEntries.length}`,
|
|
1099
|
+
"",
|
|
1100
|
+
"## Entities",
|
|
1101
|
+
"",
|
|
1102
|
+
...(entityEntries.length > 0 ? entityEntries.map(item => `- [${item.name}](entities/${item.file})`) : ["- (none)"]),
|
|
1103
|
+
"",
|
|
1104
|
+
"## Topics",
|
|
1105
|
+
"",
|
|
1106
|
+
...(topicEntries.length > 0 ? topicEntries.map(item => `- [${item.type}](topics/${item.file})`) : ["- (none)"]),
|
|
1107
|
+
"",
|
|
1108
|
+
"## Timelines",
|
|
1109
|
+
"",
|
|
1110
|
+
...(timelineEntries.length > 0 ? timelineEntries.map(item => `- [${item.id}](timelines/${item.file})`) : ["- (none)"]),
|
|
1111
|
+
"",
|
|
1112
|
+
];
|
|
1113
|
+
fs.writeFileSync(indexPath, `${indexBody.join("\n")}\n`, "utf-8");
|
|
1114
|
+
const projectionIndex = {
|
|
1115
|
+
updated_at: new Date().toISOString(),
|
|
1116
|
+
graph_updated_at: args.graphView.updated_at,
|
|
1117
|
+
quality_summary: {
|
|
1118
|
+
relations: relations.length,
|
|
1119
|
+
status_counts: relationStatusCounts(relations),
|
|
1120
|
+
entities: entityEntries.length,
|
|
1121
|
+
topics: topicEntries.length,
|
|
1122
|
+
timelines: timelineEntries.length,
|
|
1123
|
+
},
|
|
1124
|
+
entities: entityEntries.map(item => ({ name: item.name, path: `entities/${item.file}` })),
|
|
1125
|
+
topics: topicEntries.map(item => ({ type: item.type, path: `topics/${item.file}` })),
|
|
1126
|
+
timelines: timelineEntries.map(item => ({
|
|
1127
|
+
id: item.id,
|
|
1128
|
+
relation_type: item.relation_type,
|
|
1129
|
+
source: item.source,
|
|
1130
|
+
target_scope: item.target_scope,
|
|
1131
|
+
latest_status: item.latest_status,
|
|
1132
|
+
path: `timelines/${item.file}`,
|
|
1133
|
+
})),
|
|
1134
|
+
queue_events: args.queueEvents.map(item => ({ id: item.id, type: item.type, at: item.at })),
|
|
1135
|
+
};
|
|
1136
|
+
fs.writeFileSync(projectionIndexPath, `${JSON.stringify(projectionIndex, null, 2)}\n`, "utf-8");
|
|
1137
|
+
return {
|
|
1138
|
+
updated_at: projectionIndex.updated_at,
|
|
1139
|
+
entities_count: entityEntries.length,
|
|
1140
|
+
topics_count: topicEntries.length,
|
|
1141
|
+
timelines_count: timelineEntries.length,
|
|
1142
|
+
files: {
|
|
1143
|
+
index: indexPath,
|
|
1144
|
+
projection_index: projectionIndexPath,
|
|
1145
|
+
entities_dir: entitiesDir,
|
|
1146
|
+
topics_dir: topicsDir,
|
|
1147
|
+
timelines_dir: timelinesDir,
|
|
1148
|
+
},
|
|
1149
|
+
};
|
|
1150
|
+
}
|
|
1151
|
+
//# sourceMappingURL=wiki_projector.js.map
|