clawvault 3.3.0 → 3.4.0
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/bin/command-registration.test.js +2 -1
- package/bin/help-contract.test.js +2 -0
- package/bin/register-maintenance-commands.js +27 -1
- package/bin/register-query-commands.js +35 -0
- package/bin/register-query-commands.test.js +15 -0
- package/dist/chunk-35JCYSRR.js +158 -0
- package/dist/{chunk-TDWFBDAQ.js → chunk-D5U3Q4N5.js} +7 -151
- package/dist/{chunk-YCUVAOFC.js → chunk-DCF4KMFD.js} +4 -4
- package/dist/chunk-NSXYM6EZ.js +255 -0
- package/dist/chunk-QYQAGBTM.js +2097 -0
- package/dist/chunk-RL2L6I6K.js +223 -0
- package/dist/chunk-YTRZNA64.js +37 -0
- package/dist/cli/index.js +6 -5
- package/dist/commands/entities.d.ts +8 -1
- package/dist/commands/entities.js +44 -1
- package/dist/commands/link.js +5 -5
- package/dist/commands/maintain.js +2 -1
- package/dist/commands/recall.d.ts +14 -0
- package/dist/commands/recall.js +15 -0
- package/dist/index.d.ts +59 -1
- package/dist/index.js +56 -11
- package/dist/openclaw-plugin--gqA2BZw.d.ts +267 -0
- package/dist/openclaw-plugin.d.ts +4 -8
- package/dist/openclaw-plugin.js +16 -10
- package/dist/types-CbL-wIKi.d.ts +36 -0
- package/openclaw.plugin.json +39 -0
- package/package.json +4 -4
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ClawVault
|
|
3
|
+
} from "./chunk-ECGJYWNA.js";
|
|
4
|
+
|
|
5
|
+
// src/entities/synthesis.ts
|
|
6
|
+
import * as fs from "fs";
|
|
7
|
+
import * as path from "path";
|
|
8
|
+
import matter from "gray-matter";
|
|
9
|
+
function slugify(value) {
|
|
10
|
+
return value.toLowerCase().replace(/[^\w\s-]/g, "").replace(/\s+/g, "-").replace(/-+/g, "-").trim();
|
|
11
|
+
}
|
|
12
|
+
function cleanEntityName(value) {
|
|
13
|
+
const stripped = value.replace(/[#*_`]/g, "").trim();
|
|
14
|
+
if (!stripped) return stripped;
|
|
15
|
+
if (/[A-Z]/.test(stripped)) {
|
|
16
|
+
return stripped;
|
|
17
|
+
}
|
|
18
|
+
return stripped.replace(/[-_]+/g, " ").split(/\s+/).filter(Boolean).map((token) => token.charAt(0).toUpperCase() + token.slice(1)).join(" ");
|
|
19
|
+
}
|
|
20
|
+
function uniqueStrings(values) {
|
|
21
|
+
const seen = /* @__PURE__ */ new Set();
|
|
22
|
+
const out = [];
|
|
23
|
+
for (const value of values) {
|
|
24
|
+
const normalized = value.trim();
|
|
25
|
+
if (!normalized) continue;
|
|
26
|
+
const key = normalized.toLowerCase();
|
|
27
|
+
if (seen.has(key)) continue;
|
|
28
|
+
seen.add(key);
|
|
29
|
+
out.push(normalized);
|
|
30
|
+
}
|
|
31
|
+
return out;
|
|
32
|
+
}
|
|
33
|
+
function extractBestSnippet(document) {
|
|
34
|
+
const content = document.content.replace(/\s+/g, " ").trim();
|
|
35
|
+
if (!content) return `Referenced in ${document.id}`;
|
|
36
|
+
return content.slice(0, 180);
|
|
37
|
+
}
|
|
38
|
+
function inferEntityKind(name, mentions) {
|
|
39
|
+
const categoryCounts = /* @__PURE__ */ new Map();
|
|
40
|
+
for (const mention of mentions) {
|
|
41
|
+
categoryCounts.set(
|
|
42
|
+
mention.document.category,
|
|
43
|
+
(categoryCounts.get(mention.document.category) ?? 0) + 1
|
|
44
|
+
);
|
|
45
|
+
}
|
|
46
|
+
if ((categoryCounts.get("people") ?? 0) >= (categoryCounts.get("projects") ?? 0) && (categoryCounts.get("people") ?? 0) > 0) {
|
|
47
|
+
return "person";
|
|
48
|
+
}
|
|
49
|
+
if ((categoryCounts.get("projects") ?? 0) > 0) {
|
|
50
|
+
return "project";
|
|
51
|
+
}
|
|
52
|
+
if (/\b(inc|corp|llc|ltd|org|foundation|company)\b/i.test(name)) {
|
|
53
|
+
return "org";
|
|
54
|
+
}
|
|
55
|
+
if (/\b(city|county|street|park|mount|river|lake|bay)\b/i.test(name)) {
|
|
56
|
+
return "place";
|
|
57
|
+
}
|
|
58
|
+
return "unknown";
|
|
59
|
+
}
|
|
60
|
+
function buildSummary(name, mentions) {
|
|
61
|
+
if (mentions.length === 0) {
|
|
62
|
+
return `${name} is tracked in ClawVault memories.`;
|
|
63
|
+
}
|
|
64
|
+
const newest = [...mentions].sort(
|
|
65
|
+
(left, right) => right.document.modified.getTime() - left.document.modified.getTime()
|
|
66
|
+
)[0];
|
|
67
|
+
const snippet = newest?.snippet ?? `${name} is referenced in memory files.`;
|
|
68
|
+
return snippet.endsWith(".") ? snippet : `${snippet}.`;
|
|
69
|
+
}
|
|
70
|
+
function toIsoDate(value) {
|
|
71
|
+
return value.toISOString();
|
|
72
|
+
}
|
|
73
|
+
function buildRelationshipGraph(entityMentions) {
|
|
74
|
+
const graph = /* @__PURE__ */ new Map();
|
|
75
|
+
for (const [entityName, mentions] of entityMentions.entries()) {
|
|
76
|
+
for (const mention of mentions) {
|
|
77
|
+
const coMentioned = uniqueStrings(
|
|
78
|
+
mention.document.links.map((link) => cleanEntityName(link))
|
|
79
|
+
).filter((candidate) => candidate.toLowerCase() !== entityName.toLowerCase());
|
|
80
|
+
if (coMentioned.length === 0) continue;
|
|
81
|
+
if (!graph.has(entityName)) {
|
|
82
|
+
graph.set(entityName, /* @__PURE__ */ new Map());
|
|
83
|
+
}
|
|
84
|
+
const adjacency = graph.get(entityName);
|
|
85
|
+
for (const related of coMentioned) {
|
|
86
|
+
const current = adjacency.get(related) ?? {
|
|
87
|
+
target: related,
|
|
88
|
+
count: 0,
|
|
89
|
+
evidence: /* @__PURE__ */ new Set()
|
|
90
|
+
};
|
|
91
|
+
current.count += 1;
|
|
92
|
+
current.evidence.add(mention.document.id);
|
|
93
|
+
adjacency.set(related, current);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
return graph;
|
|
98
|
+
}
|
|
99
|
+
function toRelationships(accumulator) {
|
|
100
|
+
if (!accumulator) return [];
|
|
101
|
+
return [...accumulator.values()].sort((left, right) => right.count - left.count).slice(0, 8).map((entry) => ({
|
|
102
|
+
target: entry.target,
|
|
103
|
+
strength: entry.count,
|
|
104
|
+
evidence: [...entry.evidence].slice(0, 5)
|
|
105
|
+
}));
|
|
106
|
+
}
|
|
107
|
+
function renderEntityProfileMarkdown(profile) {
|
|
108
|
+
const frontmatter = {
|
|
109
|
+
title: profile.name,
|
|
110
|
+
aliases: profile.aliases,
|
|
111
|
+
kind: profile.kind,
|
|
112
|
+
lastMentioned: profile.lastMentioned,
|
|
113
|
+
relationships: profile.relationships.map((relationship) => ({
|
|
114
|
+
target: relationship.target,
|
|
115
|
+
strength: relationship.strength,
|
|
116
|
+
evidence: relationship.evidence
|
|
117
|
+
}))
|
|
118
|
+
};
|
|
119
|
+
const relationshipLines = profile.relationships.length > 0 ? profile.relationships.map((relationship) => `- [[${relationship.target}]] (strength: ${relationship.strength})`).join("\n") : "- No relationships recorded yet.";
|
|
120
|
+
const body = [
|
|
121
|
+
`# ${profile.name}`,
|
|
122
|
+
"",
|
|
123
|
+
profile.summary,
|
|
124
|
+
"",
|
|
125
|
+
"## Relationships",
|
|
126
|
+
relationshipLines,
|
|
127
|
+
"",
|
|
128
|
+
"## Metadata",
|
|
129
|
+
`- Kind: ${profile.kind}`,
|
|
130
|
+
`- Last mentioned: ${profile.lastMentioned}`
|
|
131
|
+
].join("\n");
|
|
132
|
+
return matter.stringify(body, frontmatter);
|
|
133
|
+
}
|
|
134
|
+
function parseEntityProfile(filePath) {
|
|
135
|
+
const parsed = matter(fs.readFileSync(filePath, "utf-8"));
|
|
136
|
+
const title = typeof parsed.data.title === "string" ? parsed.data.title : path.basename(filePath, ".md");
|
|
137
|
+
const aliases = Array.isArray(parsed.data.aliases) ? parsed.data.aliases.map((value) => String(value)) : [title];
|
|
138
|
+
const kind = typeof parsed.data.kind === "string" ? parsed.data.kind : "unknown";
|
|
139
|
+
const relationships = Array.isArray(parsed.data.relationships) ? parsed.data.relationships.map((value) => {
|
|
140
|
+
if (!value || typeof value !== "object") return null;
|
|
141
|
+
const record = value;
|
|
142
|
+
if (typeof record.target !== "string") return null;
|
|
143
|
+
const strengthValue = typeof record.strength === "number" ? record.strength : Number.parseFloat(String(record.strength ?? "1"));
|
|
144
|
+
return {
|
|
145
|
+
target: record.target,
|
|
146
|
+
strength: Number.isFinite(strengthValue) ? strengthValue : 1,
|
|
147
|
+
evidence: Array.isArray(record.evidence) ? record.evidence.map((entry) => String(entry)) : []
|
|
148
|
+
};
|
|
149
|
+
}).filter((value) => Boolean(value)) : [];
|
|
150
|
+
const summary = parsed.content.split(/\r?\n/).map((line) => line.trim()).find((line) => line && !line.startsWith("#") && !line.startsWith("-")) ?? `${title} profile`;
|
|
151
|
+
const lastMentioned = typeof parsed.data.lastMentioned === "string" ? parsed.data.lastMentioned : (/* @__PURE__ */ new Date(0)).toISOString();
|
|
152
|
+
return {
|
|
153
|
+
name: title,
|
|
154
|
+
aliases: uniqueStrings([title, ...aliases]),
|
|
155
|
+
kind,
|
|
156
|
+
summary,
|
|
157
|
+
relationships,
|
|
158
|
+
lastMentioned
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
async function collectEntityMentions(vaultPath) {
|
|
162
|
+
const vault = new ClawVault(vaultPath);
|
|
163
|
+
await vault.load();
|
|
164
|
+
const docs = await vault.list();
|
|
165
|
+
const map = /* @__PURE__ */ new Map();
|
|
166
|
+
for (const document of docs) {
|
|
167
|
+
const names = uniqueStrings(
|
|
168
|
+
document.category === "people" || document.category === "projects" ? [document.title, ...document.links.map((link) => cleanEntityName(link))] : document.links.map((link) => cleanEntityName(link))
|
|
169
|
+
);
|
|
170
|
+
for (const name of names) {
|
|
171
|
+
if (!map.has(name)) {
|
|
172
|
+
map.set(name, []);
|
|
173
|
+
}
|
|
174
|
+
map.get(name).push({
|
|
175
|
+
document,
|
|
176
|
+
snippet: extractBestSnippet(document)
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
return map;
|
|
181
|
+
}
|
|
182
|
+
async function synthesizeEntityProfiles(vaultPath, options = {}) {
|
|
183
|
+
const mentions = await collectEntityMentions(vaultPath);
|
|
184
|
+
const relationshipGraph = buildRelationshipGraph(mentions);
|
|
185
|
+
const profiles = [];
|
|
186
|
+
for (const [name, records] of mentions.entries()) {
|
|
187
|
+
const kind = inferEntityKind(name, records);
|
|
188
|
+
const relationships = toRelationships(relationshipGraph.get(name));
|
|
189
|
+
const aliases = uniqueStrings([
|
|
190
|
+
name,
|
|
191
|
+
slugify(name).replace(/-/g, " ")
|
|
192
|
+
]);
|
|
193
|
+
const lastMentioned = toIsoDate(
|
|
194
|
+
records.reduce(
|
|
195
|
+
(latest, record) => record.document.modified.getTime() > latest.getTime() ? record.document.modified : latest,
|
|
196
|
+
/* @__PURE__ */ new Date(0)
|
|
197
|
+
)
|
|
198
|
+
);
|
|
199
|
+
profiles.push({
|
|
200
|
+
name,
|
|
201
|
+
aliases,
|
|
202
|
+
kind,
|
|
203
|
+
summary: buildSummary(name, records),
|
|
204
|
+
relationships,
|
|
205
|
+
lastMentioned
|
|
206
|
+
});
|
|
207
|
+
}
|
|
208
|
+
profiles.sort((left, right) => right.lastMentioned.localeCompare(left.lastMentioned));
|
|
209
|
+
if (options.writeFiles ?? true) {
|
|
210
|
+
const entitiesDir = path.join(vaultPath, "entities");
|
|
211
|
+
if (!fs.existsSync(entitiesDir)) {
|
|
212
|
+
fs.mkdirSync(entitiesDir, { recursive: true });
|
|
213
|
+
}
|
|
214
|
+
for (const profile of profiles) {
|
|
215
|
+
const targetPath = path.join(entitiesDir, `${slugify(profile.name)}.md`);
|
|
216
|
+
fs.writeFileSync(targetPath, renderEntityProfileMarkdown(profile), "utf-8");
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
return profiles;
|
|
220
|
+
}
|
|
221
|
+
function readEntityProfiles(vaultPath) {
|
|
222
|
+
const entitiesDir = path.join(vaultPath, "entities");
|
|
223
|
+
if (!fs.existsSync(entitiesDir)) {
|
|
224
|
+
return [];
|
|
225
|
+
}
|
|
226
|
+
const files = fs.readdirSync(entitiesDir).filter((entry) => entry.endsWith(".md"));
|
|
227
|
+
return files.map((entry) => parseEntityProfile(path.join(entitiesDir, entry))).sort((left, right) => right.lastMentioned.localeCompare(left.lastMentioned));
|
|
228
|
+
}
|
|
229
|
+
async function ensureEntityProfiles(vaultPath) {
|
|
230
|
+
const existing = readEntityProfiles(vaultPath);
|
|
231
|
+
if (existing.length > 0) {
|
|
232
|
+
return existing;
|
|
233
|
+
}
|
|
234
|
+
return synthesizeEntityProfiles(vaultPath, { writeFiles: true });
|
|
235
|
+
}
|
|
236
|
+
async function readEntityProfile(vaultPath, name) {
|
|
237
|
+
const profiles = await ensureEntityProfiles(vaultPath);
|
|
238
|
+
const normalized = name.trim().toLowerCase();
|
|
239
|
+
for (const profile of profiles) {
|
|
240
|
+
if (profile.name.toLowerCase() === normalized) {
|
|
241
|
+
return profile;
|
|
242
|
+
}
|
|
243
|
+
if (profile.aliases.some((alias) => alias.toLowerCase() === normalized)) {
|
|
244
|
+
return profile;
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
return null;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
export {
|
|
251
|
+
synthesizeEntityProfiles,
|
|
252
|
+
readEntityProfiles,
|
|
253
|
+
ensureEntityProfiles,
|
|
254
|
+
readEntityProfile
|
|
255
|
+
};
|