@tangle-network/agent-knowledge 1.12.1 → 2.0.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/README.md +32 -12
- package/dist/benchmarks/index.d.ts +1 -1
- package/dist/benchmarks/index.js +1 -1
- package/dist/chunk-QID2QVR5.js +2119 -0
- package/dist/chunk-QID2QVR5.js.map +1 -0
- package/dist/{chunk-ULXZI235.js → chunk-RIHIYCX2.js} +65 -67
- package/dist/chunk-RIHIYCX2.js.map +1 -0
- package/dist/cli.js +1 -1
- package/dist/{index-Cj5jRXOz.d.ts → index-BLxw1I_F.d.ts} +4 -1
- package/dist/index.d.ts +178 -32
- package/dist/index.js +1371 -337
- package/dist/index.js.map +1 -1
- package/package.json +10 -4
- package/dist/chunk-ULXZI235.js.map +0 -1
- package/dist/chunk-W6VWYTNH.js +0 -958
- package/dist/chunk-W6VWYTNH.js.map +0 -1
package/dist/chunk-W6VWYTNH.js
DELETED
|
@@ -1,958 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
searchKnowledge
|
|
3
|
-
} from "./chunk-DQ3PDMDP.js";
|
|
4
|
-
import {
|
|
5
|
-
sha256,
|
|
6
|
-
slugify,
|
|
7
|
-
stableId
|
|
8
|
-
} from "./chunk-YMKHCTS2.js";
|
|
9
|
-
|
|
10
|
-
// src/adapters.ts
|
|
11
|
-
var textSourceAdapter = {
|
|
12
|
-
id: "text",
|
|
13
|
-
canLoad: (input) => Boolean(input.text) || /\.(md|txt|json|csv)$/i.test(input.uri),
|
|
14
|
-
load: (input) => ({
|
|
15
|
-
title: input.uri.split("/").pop(),
|
|
16
|
-
mediaType: mediaTypeFor(input.uri),
|
|
17
|
-
text: decodeText(input),
|
|
18
|
-
anchors: anchorsForText(input.uri, decodeText(input)),
|
|
19
|
-
metadata: input.metadata
|
|
20
|
-
})
|
|
21
|
-
};
|
|
22
|
-
function mediaTypeFor(uri) {
|
|
23
|
-
const lower = uri.toLowerCase();
|
|
24
|
-
if (lower.endsWith(".md")) return "text/markdown";
|
|
25
|
-
if (lower.endsWith(".txt")) return "text/plain";
|
|
26
|
-
if (lower.endsWith(".json")) return "application/json";
|
|
27
|
-
if (lower.endsWith(".csv")) return "text/csv";
|
|
28
|
-
if (lower.endsWith(".pdf")) return "application/pdf";
|
|
29
|
-
return "application/octet-stream";
|
|
30
|
-
}
|
|
31
|
-
function decodeText(input) {
|
|
32
|
-
return input.text ?? (input.bytes ? new TextDecoder().decode(input.bytes).slice(0, 2e5) : void 0);
|
|
33
|
-
}
|
|
34
|
-
function anchorsForText(uri, text) {
|
|
35
|
-
if (!text) return [];
|
|
36
|
-
const lines = text.split("\n");
|
|
37
|
-
const anchors = [
|
|
38
|
-
{ id: "all", sourceId: "", label: "Full source", lineStart: 1, lineEnd: lines.length }
|
|
39
|
-
];
|
|
40
|
-
for (let i = 0; i < lines.length; i += 50) {
|
|
41
|
-
anchors.push({
|
|
42
|
-
id: `l${i + 1}`,
|
|
43
|
-
sourceId: "",
|
|
44
|
-
label: `${uri}:${i + 1}`,
|
|
45
|
-
lineStart: i + 1,
|
|
46
|
-
lineEnd: Math.min(lines.length, i + 50)
|
|
47
|
-
});
|
|
48
|
-
}
|
|
49
|
-
return anchors;
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
// src/frontmatter.ts
|
|
53
|
-
function parseFrontmatter(content) {
|
|
54
|
-
const normalized = content.replace(/\r\n/g, "\n");
|
|
55
|
-
if (!normalized.startsWith("---\n")) return { frontmatter: {}, body: normalized };
|
|
56
|
-
const end = normalized.indexOf("\n---", 4);
|
|
57
|
-
if (end < 0) return { frontmatter: {}, body: normalized };
|
|
58
|
-
const raw = normalized.slice(4, end);
|
|
59
|
-
const after = normalized.slice(end).replace(/^\n---\s*\n?/, "");
|
|
60
|
-
return { frontmatter: parseSimpleYaml(raw), body: after };
|
|
61
|
-
}
|
|
62
|
-
function formatFrontmatter(frontmatter, body) {
|
|
63
|
-
const lines = Object.entries(frontmatter).filter(([, value]) => value !== void 0).flatMap(([key, value]) => formatYamlField(key, value));
|
|
64
|
-
if (lines.length === 0) return body;
|
|
65
|
-
return `---
|
|
66
|
-
${lines.join("\n")}
|
|
67
|
-
---
|
|
68
|
-
${body.replace(/^\n+/, "")}`;
|
|
69
|
-
}
|
|
70
|
-
function parseSimpleYaml(raw) {
|
|
71
|
-
const out = {};
|
|
72
|
-
const lines = raw.split("\n");
|
|
73
|
-
for (let i = 0; i < lines.length; i++) {
|
|
74
|
-
const line = lines[i];
|
|
75
|
-
const scalar = /^([A-Za-z0-9_-]+):\s*(.*)$/.exec(line);
|
|
76
|
-
if (!scalar) continue;
|
|
77
|
-
const key = scalar[1];
|
|
78
|
-
const rest = scalar[2].trim();
|
|
79
|
-
if (rest === "") {
|
|
80
|
-
const items = [];
|
|
81
|
-
while (i + 1 < lines.length) {
|
|
82
|
-
const item = /^\s*-\s*(.+?)\s*$/.exec(lines[i + 1]);
|
|
83
|
-
if (!item) break;
|
|
84
|
-
items.push(unquote(item[1]));
|
|
85
|
-
i++;
|
|
86
|
-
}
|
|
87
|
-
out[key] = items;
|
|
88
|
-
continue;
|
|
89
|
-
}
|
|
90
|
-
if (rest.startsWith("[") && rest.endsWith("]")) {
|
|
91
|
-
out[key] = rest.slice(1, -1).split(",").map((part) => unquote(part.trim())).filter(Boolean);
|
|
92
|
-
} else if (rest === "true" || rest === "false") {
|
|
93
|
-
out[key] = rest === "true";
|
|
94
|
-
} else if (/^-?\d+(?:\.\d+)?$/.test(rest)) {
|
|
95
|
-
out[key] = Number(rest);
|
|
96
|
-
} else {
|
|
97
|
-
out[key] = unquote(rest);
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
return out;
|
|
101
|
-
}
|
|
102
|
-
function formatYamlField(key, value) {
|
|
103
|
-
if (Array.isArray(value)) {
|
|
104
|
-
return [`${key}:`, ...value.map((item) => ` - ${String(item)}`)];
|
|
105
|
-
}
|
|
106
|
-
if (typeof value === "string") return [`${key}: ${value}`];
|
|
107
|
-
if (typeof value === "number" || typeof value === "boolean") return [`${key}: ${String(value)}`];
|
|
108
|
-
return [`${key}: ${JSON.stringify(value)}`];
|
|
109
|
-
}
|
|
110
|
-
function unquote(value) {
|
|
111
|
-
return value.replace(/^['"]|['"]$/g, "");
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
// src/wikilinks.ts
|
|
115
|
-
var WIKILINK_REGEX = /\[\[([^\]|]+?)(?:\|[^\]]+?)?\]\]/g;
|
|
116
|
-
function extractWikilinks(content) {
|
|
117
|
-
const links = [];
|
|
118
|
-
const regex = new RegExp(WIKILINK_REGEX.source, "g");
|
|
119
|
-
let match;
|
|
120
|
-
match = regex.exec(content);
|
|
121
|
-
while (match !== null) {
|
|
122
|
-
links.push(match[1].trim());
|
|
123
|
-
match = regex.exec(content);
|
|
124
|
-
}
|
|
125
|
-
return [...new Set(links)];
|
|
126
|
-
}
|
|
127
|
-
function normalizeLinkTarget(target) {
|
|
128
|
-
return target.trim().replace(/\.md$/i, "").toLowerCase().replace(/\s+/g, "-");
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
// src/graph.ts
|
|
132
|
-
function buildKnowledgeGraph(pages) {
|
|
133
|
-
const byId = /* @__PURE__ */ new Map();
|
|
134
|
-
const bySlug = /* @__PURE__ */ new Map();
|
|
135
|
-
for (const page of pages) {
|
|
136
|
-
byId.set(page.id, page);
|
|
137
|
-
bySlug.set(normalizeLinkTarget(page.id), page);
|
|
138
|
-
bySlug.set(normalizeLinkTarget(page.title), page);
|
|
139
|
-
bySlug.set(normalizeLinkTarget(page.path.split("/").pop().replace(/\.md$/, "")), page);
|
|
140
|
-
}
|
|
141
|
-
const incoming = /* @__PURE__ */ new Map();
|
|
142
|
-
const outgoing = /* @__PURE__ */ new Map();
|
|
143
|
-
const edgesByKey = /* @__PURE__ */ new Map();
|
|
144
|
-
for (const page of pages) {
|
|
145
|
-
outgoing.set(page.id, 0);
|
|
146
|
-
incoming.set(page.id, 0);
|
|
147
|
-
}
|
|
148
|
-
for (const page of pages) {
|
|
149
|
-
for (const raw of page.outLinks) {
|
|
150
|
-
const target = bySlug.get(normalizeLinkTarget(raw));
|
|
151
|
-
if (!target || target.id === page.id) continue;
|
|
152
|
-
const key = `${page.id}->${target.id}`;
|
|
153
|
-
const edge = edgesByKey.get(key);
|
|
154
|
-
if (edge) edge.weight += 1;
|
|
155
|
-
else
|
|
156
|
-
edgesByKey.set(key, {
|
|
157
|
-
source: page.id,
|
|
158
|
-
target: target.id,
|
|
159
|
-
weight: 1,
|
|
160
|
-
reasons: ["wikilink"]
|
|
161
|
-
});
|
|
162
|
-
outgoing.set(page.id, (outgoing.get(page.id) ?? 0) + 1);
|
|
163
|
-
incoming.set(target.id, (incoming.get(target.id) ?? 0) + 1);
|
|
164
|
-
}
|
|
165
|
-
}
|
|
166
|
-
addSourceOverlapEdges(pages, edgesByKey);
|
|
167
|
-
const nodes = pages.map((page) => ({
|
|
168
|
-
id: page.id,
|
|
169
|
-
title: page.title,
|
|
170
|
-
path: page.path,
|
|
171
|
-
tags: page.tags,
|
|
172
|
-
sourceIds: page.sourceIds,
|
|
173
|
-
outDegree: outgoing.get(page.id) ?? 0,
|
|
174
|
-
inDegree: incoming.get(page.id) ?? 0
|
|
175
|
-
}));
|
|
176
|
-
return { nodes, edges: [...edgesByKey.values()].sort((a, b) => b.weight - a.weight) };
|
|
177
|
-
}
|
|
178
|
-
function addSourceOverlapEdges(pages, edges) {
|
|
179
|
-
for (let i = 0; i < pages.length; i++) {
|
|
180
|
-
for (let j = i + 1; j < pages.length; j++) {
|
|
181
|
-
const a = pages[i];
|
|
182
|
-
const b = pages[j];
|
|
183
|
-
const overlap = a.sourceIds.filter((source) => b.sourceIds.includes(source));
|
|
184
|
-
if (overlap.length === 0) continue;
|
|
185
|
-
const key = `${a.id}->${b.id}`;
|
|
186
|
-
const edge = edges.get(key);
|
|
187
|
-
if (edge) {
|
|
188
|
-
edge.weight += overlap.length * 0.5;
|
|
189
|
-
edge.reasons.push("shared-source");
|
|
190
|
-
} else {
|
|
191
|
-
edges.set(key, {
|
|
192
|
-
source: a.id,
|
|
193
|
-
target: b.id,
|
|
194
|
-
weight: overlap.length * 0.5,
|
|
195
|
-
reasons: ["shared-source"]
|
|
196
|
-
});
|
|
197
|
-
}
|
|
198
|
-
}
|
|
199
|
-
}
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
// src/store.ts
|
|
203
|
-
import { mkdir, readdir, readFile, stat, writeFile } from "fs/promises";
|
|
204
|
-
import { dirname, join, relative } from "path";
|
|
205
|
-
function layoutFor(root) {
|
|
206
|
-
return {
|
|
207
|
-
root,
|
|
208
|
-
knowledgeDir: join(root, "knowledge"),
|
|
209
|
-
rawSourcesDir: join(root, "raw", "sources"),
|
|
210
|
-
sourceRegistryPath: join(root, ".agent-knowledge", "sources.json"),
|
|
211
|
-
indexPath: join(root, "knowledge", "index.md"),
|
|
212
|
-
logPath: join(root, "knowledge", "log.md"),
|
|
213
|
-
cacheDir: join(root, ".agent-knowledge")
|
|
214
|
-
};
|
|
215
|
-
}
|
|
216
|
-
var SCAFFOLD_PAGE_BASENAMES = ["index.md", "log.md"];
|
|
217
|
-
function isScaffoldPath(path) {
|
|
218
|
-
const normalized = path.replace(/\\/g, "/");
|
|
219
|
-
for (const basename2 of SCAFFOLD_PAGE_BASENAMES) {
|
|
220
|
-
if (normalized === `knowledge/${basename2}`) return true;
|
|
221
|
-
if (normalized.endsWith(`/${basename2}`)) return true;
|
|
222
|
-
}
|
|
223
|
-
return false;
|
|
224
|
-
}
|
|
225
|
-
async function initKnowledgeBase(root) {
|
|
226
|
-
const layout = layoutFor(root);
|
|
227
|
-
await mkdir(layout.knowledgeDir, { recursive: true });
|
|
228
|
-
await mkdir(layout.rawSourcesDir, { recursive: true });
|
|
229
|
-
await mkdir(layout.cacheDir, { recursive: true });
|
|
230
|
-
await writeIfMissing(layout.indexPath, "# Knowledge Index\n\n");
|
|
231
|
-
await writeIfMissing(layout.logPath, "# Knowledge Log\n\n");
|
|
232
|
-
await writeIfMissing(
|
|
233
|
-
layout.sourceRegistryPath,
|
|
234
|
-
'{\n "generatedAt": "1970-01-01T00:00:00.000Z",\n "sources": []\n}\n'
|
|
235
|
-
);
|
|
236
|
-
return layout;
|
|
237
|
-
}
|
|
238
|
-
async function loadKnowledgePages(root) {
|
|
239
|
-
const layout = layoutFor(root);
|
|
240
|
-
const files = await listMarkdownFiles(layout.knowledgeDir);
|
|
241
|
-
const pages = [];
|
|
242
|
-
for (const file of files) {
|
|
243
|
-
const rel = relative(root, file).replace(/\\/g, "/");
|
|
244
|
-
if (isScaffoldPath(rel)) continue;
|
|
245
|
-
const content = await readFile(file, "utf8");
|
|
246
|
-
const { frontmatter, body } = parseFrontmatter(content);
|
|
247
|
-
const title = stringField(frontmatter.title) ?? firstHeading(body) ?? rel.split("/").pop().replace(/\.md$/, "");
|
|
248
|
-
const sourceIds = arrayField(frontmatter.sources);
|
|
249
|
-
const tags = arrayField(frontmatter.tags);
|
|
250
|
-
pages.push({
|
|
251
|
-
id: stringField(frontmatter.id) ?? slugify(rel.replace(/^knowledge\//, "").replace(/\.md$/, "")),
|
|
252
|
-
path: rel,
|
|
253
|
-
title,
|
|
254
|
-
text: body,
|
|
255
|
-
frontmatter,
|
|
256
|
-
sourceIds,
|
|
257
|
-
tags,
|
|
258
|
-
outLinks: extractWikilinks(body).map(normalizeLinkTarget)
|
|
259
|
-
});
|
|
260
|
-
}
|
|
261
|
-
pages.sort((a, b) => a.path.localeCompare(b.path));
|
|
262
|
-
return pages;
|
|
263
|
-
}
|
|
264
|
-
async function writeJson(path, value) {
|
|
265
|
-
await mkdir(dirname(path), { recursive: true });
|
|
266
|
-
await writeFile(path, `${JSON.stringify(value, null, 2)}
|
|
267
|
-
`, "utf8");
|
|
268
|
-
}
|
|
269
|
-
async function writeIfMissing(path, content) {
|
|
270
|
-
try {
|
|
271
|
-
await stat(path);
|
|
272
|
-
} catch {
|
|
273
|
-
await mkdir(dirname(path), { recursive: true });
|
|
274
|
-
await writeFile(path, content, "utf8");
|
|
275
|
-
}
|
|
276
|
-
}
|
|
277
|
-
async function listMarkdownFiles(root) {
|
|
278
|
-
try {
|
|
279
|
-
const entries = await readdir(root, { withFileTypes: true });
|
|
280
|
-
const out = [];
|
|
281
|
-
for (const entry of entries) {
|
|
282
|
-
const full = join(root, entry.name);
|
|
283
|
-
if (entry.isDirectory()) out.push(...await listMarkdownFiles(full));
|
|
284
|
-
else if (entry.isFile() && entry.name.endsWith(".md")) out.push(full);
|
|
285
|
-
}
|
|
286
|
-
return out;
|
|
287
|
-
} catch {
|
|
288
|
-
return [];
|
|
289
|
-
}
|
|
290
|
-
}
|
|
291
|
-
function stringField(value) {
|
|
292
|
-
return typeof value === "string" && value.trim() !== "" ? value.trim() : void 0;
|
|
293
|
-
}
|
|
294
|
-
function arrayField(value) {
|
|
295
|
-
return Array.isArray(value) ? value.filter((item) => typeof item === "string") : [];
|
|
296
|
-
}
|
|
297
|
-
function firstHeading(body) {
|
|
298
|
-
return /^#\s+(.+)$/m.exec(body)?.[1]?.trim();
|
|
299
|
-
}
|
|
300
|
-
|
|
301
|
-
// src/sources.ts
|
|
302
|
-
import { copyFile, mkdir as mkdir2, readdir as readdir2, readFile as readFile2, stat as stat2, writeFile as writeFile2 } from "fs/promises";
|
|
303
|
-
import { basename, dirname as dirname2, join as join2, relative as relative2 } from "path";
|
|
304
|
-
async function loadSourceRegistry(root) {
|
|
305
|
-
const path = sourceRegistryPath(root);
|
|
306
|
-
try {
|
|
307
|
-
const parsed = JSON.parse(await readFile2(path, "utf8"));
|
|
308
|
-
return {
|
|
309
|
-
generatedAt: typeof parsed.generatedAt === "string" ? parsed.generatedAt : (/* @__PURE__ */ new Date(0)).toISOString(),
|
|
310
|
-
sources: Array.isArray(parsed.sources) ? parsed.sources : []
|
|
311
|
-
};
|
|
312
|
-
} catch {
|
|
313
|
-
return { generatedAt: (/* @__PURE__ */ new Date(0)).toISOString(), sources: [] };
|
|
314
|
-
}
|
|
315
|
-
}
|
|
316
|
-
async function writeSourceRegistry(root, registry) {
|
|
317
|
-
const path = sourceRegistryPath(root);
|
|
318
|
-
await mkdir2(dirname2(path), { recursive: true });
|
|
319
|
-
await writeFile2(path, `${JSON.stringify(registry, null, 2)}
|
|
320
|
-
`, "utf8");
|
|
321
|
-
}
|
|
322
|
-
async function addSourcePath(root, sourcePath, options = {}) {
|
|
323
|
-
const s = await stat2(sourcePath);
|
|
324
|
-
if (s.isDirectory()) {
|
|
325
|
-
const out = [];
|
|
326
|
-
for (const file of await listFiles(sourcePath)) {
|
|
327
|
-
out.push(...await addSourcePath(root, file, options));
|
|
328
|
-
}
|
|
329
|
-
return out;
|
|
330
|
-
}
|
|
331
|
-
const layout = layoutFor(root);
|
|
332
|
-
await mkdir2(layout.rawSourcesDir, { recursive: true });
|
|
333
|
-
const bytes = await readFile2(sourcePath);
|
|
334
|
-
const contentHash = sha256(bytes.toString("base64"));
|
|
335
|
-
const fileName = basename(sourcePath);
|
|
336
|
-
const adapters = options.adapters ?? [textSourceAdapter];
|
|
337
|
-
const adapter = adapters.find((candidate) => candidate.canLoad({ uri: sourcePath, bytes }));
|
|
338
|
-
const loaded = adapter ? await adapter.load({ uri: sourcePath, bytes }) : {};
|
|
339
|
-
const id = stableId("src", `${contentHash}:${fileName}`);
|
|
340
|
-
const targetRel = join2(
|
|
341
|
-
"raw",
|
|
342
|
-
"sources",
|
|
343
|
-
`${slugify(fileName.replace(/\.[^.]+$/, ""))}-${contentHash.slice(0, 8)}${ext(fileName)}`
|
|
344
|
-
).replace(/\\/g, "/");
|
|
345
|
-
const targetAbs = join2(root, targetRel);
|
|
346
|
-
if (options.copyIntoRaw ?? true) {
|
|
347
|
-
await mkdir2(dirname2(targetAbs), { recursive: true });
|
|
348
|
-
await copyFile(sourcePath, targetAbs);
|
|
349
|
-
}
|
|
350
|
-
const existing = await loadSourceRegistry(root);
|
|
351
|
-
const record = {
|
|
352
|
-
id,
|
|
353
|
-
uri: targetRel,
|
|
354
|
-
title: loaded.title ?? fileName,
|
|
355
|
-
mediaType: loaded.mediaType ?? mediaTypeFor2(fileName),
|
|
356
|
-
contentHash,
|
|
357
|
-
text: loaded.text ?? textPreview(fileName, bytes),
|
|
358
|
-
anchors: (loaded.anchors ?? []).map((anchor) => ({ ...anchor, sourceId: id })),
|
|
359
|
-
createdAt: (options.now ?? (() => /* @__PURE__ */ new Date()))().toISOString(),
|
|
360
|
-
metadata: {
|
|
361
|
-
...loaded.metadata ?? {},
|
|
362
|
-
originalPath: sourcePath,
|
|
363
|
-
sizeBytes: bytes.length,
|
|
364
|
-
projectRelativePath: relative2(root, sourcePath).replace(/\\/g, "/")
|
|
365
|
-
}
|
|
366
|
-
};
|
|
367
|
-
const next = {
|
|
368
|
-
generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
369
|
-
sources: [record, ...existing.sources.filter((source) => source.id !== id)]
|
|
370
|
-
};
|
|
371
|
-
await writeSourceRegistry(root, next);
|
|
372
|
-
return [record];
|
|
373
|
-
}
|
|
374
|
-
async function addSourceText(root, input, options = {}) {
|
|
375
|
-
const text = input.text;
|
|
376
|
-
const contentHash = sha256(text);
|
|
377
|
-
const fileName = basename(input.uri) || `${slugify(input.title ?? input.uri)}.txt`;
|
|
378
|
-
const adapterInput = { uri: input.uri, text, metadata: input.metadata };
|
|
379
|
-
const adapter = (options.adapters ?? [textSourceAdapter]).find(
|
|
380
|
-
(candidate) => candidate.canLoad(adapterInput)
|
|
381
|
-
);
|
|
382
|
-
const loaded = adapter ? await adapter.load(adapterInput) : {};
|
|
383
|
-
const id = stableId("src", `${contentHash}:${input.uri}`);
|
|
384
|
-
const targetRel = join2(
|
|
385
|
-
"raw",
|
|
386
|
-
"sources",
|
|
387
|
-
`${slugify(fileName.replace(/\.[^.]+$/, ""))}-${contentHash.slice(0, 8)}.txt`
|
|
388
|
-
).replace(/\\/g, "/");
|
|
389
|
-
const targetAbs = join2(root, targetRel);
|
|
390
|
-
await mkdir2(dirname2(targetAbs), { recursive: true });
|
|
391
|
-
await writeFile2(targetAbs, text.endsWith("\n") ? text : `${text}
|
|
392
|
-
`, "utf8");
|
|
393
|
-
const existing = await loadSourceRegistry(root);
|
|
394
|
-
const record = {
|
|
395
|
-
id,
|
|
396
|
-
uri: targetRel,
|
|
397
|
-
title: input.title ?? loaded.title ?? fileName,
|
|
398
|
-
mediaType: input.mediaType ?? loaded.mediaType ?? "text/plain",
|
|
399
|
-
contentHash,
|
|
400
|
-
text: loaded.text ?? text.slice(0, 2e5),
|
|
401
|
-
anchors: (loaded.anchors ?? []).map((anchor) => ({ ...anchor, sourceId: id })),
|
|
402
|
-
validUntil: input.validUntil,
|
|
403
|
-
lastVerifiedAt: input.lastVerifiedAt,
|
|
404
|
-
createdAt: (options.now ?? (() => /* @__PURE__ */ new Date()))().toISOString(),
|
|
405
|
-
metadata: {
|
|
406
|
-
...loaded.metadata ?? {},
|
|
407
|
-
...input.metadata ?? {},
|
|
408
|
-
originalUri: input.uri,
|
|
409
|
-
sizeBytes: Buffer.byteLength(text, "utf8")
|
|
410
|
-
}
|
|
411
|
-
};
|
|
412
|
-
await writeSourceRegistry(root, {
|
|
413
|
-
generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
414
|
-
sources: [record, ...existing.sources.filter((source) => source.id !== id)]
|
|
415
|
-
});
|
|
416
|
-
return record;
|
|
417
|
-
}
|
|
418
|
-
function sourceRegistryPath(root) {
|
|
419
|
-
return join2(layoutFor(root).cacheDir, "sources.json");
|
|
420
|
-
}
|
|
421
|
-
async function listFiles(root) {
|
|
422
|
-
const entries = await readdir2(root, { withFileTypes: true });
|
|
423
|
-
const out = [];
|
|
424
|
-
for (const entry of entries) {
|
|
425
|
-
const full = join2(root, entry.name);
|
|
426
|
-
if (entry.isDirectory()) out.push(...await listFiles(full));
|
|
427
|
-
else if (entry.isFile()) out.push(full);
|
|
428
|
-
}
|
|
429
|
-
return out;
|
|
430
|
-
}
|
|
431
|
-
function ext(fileName) {
|
|
432
|
-
const idx = fileName.lastIndexOf(".");
|
|
433
|
-
return idx >= 0 ? fileName.slice(idx) : "";
|
|
434
|
-
}
|
|
435
|
-
function mediaTypeFor2(fileName) {
|
|
436
|
-
const lower = fileName.toLowerCase();
|
|
437
|
-
if (lower.endsWith(".md")) return "text/markdown";
|
|
438
|
-
if (lower.endsWith(".txt")) return "text/plain";
|
|
439
|
-
if (lower.endsWith(".json")) return "application/json";
|
|
440
|
-
if (lower.endsWith(".csv")) return "text/csv";
|
|
441
|
-
if (lower.endsWith(".pdf")) return "application/pdf";
|
|
442
|
-
return "application/octet-stream";
|
|
443
|
-
}
|
|
444
|
-
function textPreview(fileName, bytes) {
|
|
445
|
-
const mediaType = mediaTypeFor2(fileName);
|
|
446
|
-
if (!mediaType.startsWith("text/") && mediaType !== "application/json") return void 0;
|
|
447
|
-
return bytes.toString("utf8").slice(0, 2e5);
|
|
448
|
-
}
|
|
449
|
-
|
|
450
|
-
// src/indexer.ts
|
|
451
|
-
import { join as join3 } from "path";
|
|
452
|
-
async function buildKnowledgeIndex(root) {
|
|
453
|
-
const [pages, sourceRegistry] = await Promise.all([
|
|
454
|
-
loadKnowledgePages(root),
|
|
455
|
-
loadSourceRegistry(root)
|
|
456
|
-
]);
|
|
457
|
-
const index = {
|
|
458
|
-
root,
|
|
459
|
-
generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
460
|
-
sources: sourceRegistry.sources,
|
|
461
|
-
pages,
|
|
462
|
-
graph: buildKnowledgeGraph(pages)
|
|
463
|
-
};
|
|
464
|
-
return index;
|
|
465
|
-
}
|
|
466
|
-
async function writeKnowledgeIndex(root) {
|
|
467
|
-
const index = await buildKnowledgeIndex(root);
|
|
468
|
-
await writeJson(join3(layoutFor(root).cacheDir, "index.json"), index);
|
|
469
|
-
return index;
|
|
470
|
-
}
|
|
471
|
-
|
|
472
|
-
// src/lint.ts
|
|
473
|
-
function lintKnowledgeIndex(index) {
|
|
474
|
-
const findings = [];
|
|
475
|
-
const byTarget = /* @__PURE__ */ new Set();
|
|
476
|
-
const titles = /* @__PURE__ */ new Map();
|
|
477
|
-
const sourceIds = new Set(index.sources.map((source) => source.id));
|
|
478
|
-
const anchorIds = new Map(
|
|
479
|
-
index.sources.map((source) => [
|
|
480
|
-
source.id,
|
|
481
|
-
new Set((source.anchors ?? []).map((anchor) => anchor.id))
|
|
482
|
-
])
|
|
483
|
-
);
|
|
484
|
-
const pageIds = /* @__PURE__ */ new Map();
|
|
485
|
-
const sourceHashes = /* @__PURE__ */ new Map();
|
|
486
|
-
for (const page of index.pages) {
|
|
487
|
-
pageIds.set(page.id, [...pageIds.get(page.id) ?? [], page.path]);
|
|
488
|
-
byTarget.add(normalizeLinkTarget(page.id));
|
|
489
|
-
byTarget.add(normalizeLinkTarget(page.title));
|
|
490
|
-
byTarget.add(normalizeLinkTarget(page.path.split("/").pop().replace(/\.md$/, "")));
|
|
491
|
-
const titleKey = page.title.toLowerCase();
|
|
492
|
-
titles.set(titleKey, [...titles.get(titleKey) ?? [], page.path]);
|
|
493
|
-
}
|
|
494
|
-
for (const source of index.sources) {
|
|
495
|
-
sourceHashes.set(source.contentHash, [
|
|
496
|
-
...sourceHashes.get(source.contentHash) ?? [],
|
|
497
|
-
source.id
|
|
498
|
-
]);
|
|
499
|
-
}
|
|
500
|
-
const inbound = /* @__PURE__ */ new Map();
|
|
501
|
-
for (const page of index.pages) inbound.set(page.id, 0);
|
|
502
|
-
for (const page of index.pages) {
|
|
503
|
-
if (page.outLinks.length === 0 && !isScaffoldPath(page.path)) {
|
|
504
|
-
findings.push({
|
|
505
|
-
type: "no-outlinks",
|
|
506
|
-
severity: "info",
|
|
507
|
-
page: page.path,
|
|
508
|
-
message: "Page has no wikilinks to other knowledge pages."
|
|
509
|
-
});
|
|
510
|
-
}
|
|
511
|
-
for (const link of page.outLinks) {
|
|
512
|
-
if (!byTarget.has(normalizeLinkTarget(link))) {
|
|
513
|
-
findings.push({
|
|
514
|
-
type: "broken-link",
|
|
515
|
-
severity: "warning",
|
|
516
|
-
page: page.path,
|
|
517
|
-
message: `Broken wikilink [[${link}]].`
|
|
518
|
-
});
|
|
519
|
-
}
|
|
520
|
-
}
|
|
521
|
-
}
|
|
522
|
-
for (const edge of index.graph.edges)
|
|
523
|
-
inbound.set(edge.target, (inbound.get(edge.target) ?? 0) + 1);
|
|
524
|
-
for (const page of index.pages) {
|
|
525
|
-
if (!isScaffoldPath(page.path) && (inbound.get(page.id) ?? 0) === 0) {
|
|
526
|
-
findings.push({
|
|
527
|
-
type: "orphan",
|
|
528
|
-
severity: "info",
|
|
529
|
-
page: page.path,
|
|
530
|
-
message: "No other page links to this page."
|
|
531
|
-
});
|
|
532
|
-
}
|
|
533
|
-
if (/\bclaim\b/i.test(page.text) && page.sourceIds.length === 0) {
|
|
534
|
-
findings.push({
|
|
535
|
-
type: "uncited-claim",
|
|
536
|
-
severity: "warning",
|
|
537
|
-
page: page.path,
|
|
538
|
-
message: "Page appears to contain claims but has no sources frontmatter."
|
|
539
|
-
});
|
|
540
|
-
}
|
|
541
|
-
for (const sourceId of page.sourceIds) {
|
|
542
|
-
if (!sourceIds.has(sourceId)) {
|
|
543
|
-
findings.push({
|
|
544
|
-
type: "missing-source",
|
|
545
|
-
severity: "error",
|
|
546
|
-
page: page.path,
|
|
547
|
-
message: `Page cites unknown source "${sourceId}".`,
|
|
548
|
-
metadata: { sourceId }
|
|
549
|
-
});
|
|
550
|
-
}
|
|
551
|
-
}
|
|
552
|
-
for (const ref of extractSourceRefs(page.text)) {
|
|
553
|
-
if (!sourceIds.has(ref.sourceId)) {
|
|
554
|
-
findings.push({
|
|
555
|
-
type: "missing-source",
|
|
556
|
-
severity: "error",
|
|
557
|
-
page: page.path,
|
|
558
|
-
message: `Page cites unknown source "${ref.sourceId}".`,
|
|
559
|
-
metadata: ref
|
|
560
|
-
});
|
|
561
|
-
} else if (ref.anchorId && !anchorIds.get(ref.sourceId)?.has(ref.anchorId)) {
|
|
562
|
-
findings.push({
|
|
563
|
-
type: "missing-source",
|
|
564
|
-
severity: "error",
|
|
565
|
-
page: page.path,
|
|
566
|
-
message: `Page cites unknown source anchor "${ref.sourceId}#${ref.anchorId}".`,
|
|
567
|
-
metadata: ref
|
|
568
|
-
});
|
|
569
|
-
}
|
|
570
|
-
}
|
|
571
|
-
}
|
|
572
|
-
for (const [title, paths] of titles) {
|
|
573
|
-
if (title && paths.length > 1) {
|
|
574
|
-
findings.push({
|
|
575
|
-
type: "duplicate-title",
|
|
576
|
-
severity: "warning",
|
|
577
|
-
message: `Duplicate title "${title}" in ${paths.join(", ")}.`,
|
|
578
|
-
metadata: { paths }
|
|
579
|
-
});
|
|
580
|
-
}
|
|
581
|
-
}
|
|
582
|
-
for (const [id, paths] of pageIds) {
|
|
583
|
-
if (id && paths.length > 1) {
|
|
584
|
-
findings.push({
|
|
585
|
-
type: "duplicate-page-id",
|
|
586
|
-
severity: "error",
|
|
587
|
-
message: `Duplicate page id "${id}" in ${paths.join(", ")}.`,
|
|
588
|
-
metadata: { paths }
|
|
589
|
-
});
|
|
590
|
-
}
|
|
591
|
-
}
|
|
592
|
-
for (const [hash, ids] of sourceHashes) {
|
|
593
|
-
if (hash && ids.length > 1) {
|
|
594
|
-
findings.push({
|
|
595
|
-
type: "duplicate-source-hash",
|
|
596
|
-
severity: "warning",
|
|
597
|
-
message: `Duplicate source content hash across ${ids.join(", ")}.`,
|
|
598
|
-
metadata: { sourceIds: ids }
|
|
599
|
-
});
|
|
600
|
-
}
|
|
601
|
-
}
|
|
602
|
-
return findings;
|
|
603
|
-
}
|
|
604
|
-
function extractSourceRefs(text) {
|
|
605
|
-
const refs = [];
|
|
606
|
-
const regex = /\[\^([A-Za-z0-9_-]+)(?:#([A-Za-z0-9_.:-]+))?\]/g;
|
|
607
|
-
let match;
|
|
608
|
-
match = regex.exec(text);
|
|
609
|
-
while (match !== null) {
|
|
610
|
-
refs.push({ sourceId: match[1], anchorId: match[2] });
|
|
611
|
-
match = regex.exec(text);
|
|
612
|
-
}
|
|
613
|
-
return refs;
|
|
614
|
-
}
|
|
615
|
-
|
|
616
|
-
// src/metadata.ts
|
|
617
|
-
function stringMetadata(metadata, key) {
|
|
618
|
-
const value = metadata?.[key];
|
|
619
|
-
return typeof value === "string" ? value : void 0;
|
|
620
|
-
}
|
|
621
|
-
|
|
622
|
-
// src/inspect.ts
|
|
623
|
-
function inspectKnowledgeIndex(index, options = {}) {
|
|
624
|
-
const now = options.now ?? /* @__PURE__ */ new Date();
|
|
625
|
-
const findings = lintKnowledgeIndex(index);
|
|
626
|
-
const degree = new Map(index.graph.nodes.map((node) => [node.id, node.inDegree + node.outDegree]));
|
|
627
|
-
const sourceFreshness = index.sources.map((source) => inspectSourceFreshness(source, now));
|
|
628
|
-
return {
|
|
629
|
-
pageCount: index.pages.length,
|
|
630
|
-
sourceCount: index.sources.length,
|
|
631
|
-
expiredSourceCount: sourceFreshness.filter((source) => source.status === "expired").length,
|
|
632
|
-
staleSourceCount: sourceFreshness.filter((source) => source.status !== "fresh").length,
|
|
633
|
-
edgeCount: index.graph.edges.length,
|
|
634
|
-
findingCount: findings.length,
|
|
635
|
-
blockingFindingCount: findings.filter((finding) => finding.severity === "error").length,
|
|
636
|
-
topPages: [...index.pages].sort((a, b) => (degree.get(b.id) ?? 0) - (degree.get(a.id) ?? 0)).slice(0, 10).map((page) => ({
|
|
637
|
-
path: page.path,
|
|
638
|
-
title: page.title,
|
|
639
|
-
degree: degree.get(page.id) ?? 0,
|
|
640
|
-
sources: page.sourceIds.length
|
|
641
|
-
})),
|
|
642
|
-
sourceFreshness,
|
|
643
|
-
findings
|
|
644
|
-
};
|
|
645
|
-
}
|
|
646
|
-
function inspectSourceFreshness(source, now) {
|
|
647
|
-
const validUntil = source.validUntil ?? stringMetadata(source.metadata, "validUntil") ?? stringMetadata(source.metadata, "expiresAt");
|
|
648
|
-
const lastVerifiedAt = source.lastVerifiedAt ?? stringMetadata(source.metadata, "lastVerifiedAt");
|
|
649
|
-
const status = validUntil && Number.isFinite(Date.parse(validUntil)) ? Date.parse(validUntil) <= now.getTime() ? "expired" : "fresh" : "unknown";
|
|
650
|
-
return { id: source.id, title: source.title, uri: source.uri, status, validUntil, lastVerifiedAt };
|
|
651
|
-
}
|
|
652
|
-
function explainKnowledgeTarget(index, target) {
|
|
653
|
-
const page = index.pages.find(
|
|
654
|
-
(candidate) => candidate.path === target || candidate.id === target || candidate.title.toLowerCase() === target.toLowerCase()
|
|
655
|
-
);
|
|
656
|
-
const inbound = page ? index.graph.edges.filter((edge) => edge.target === page.id).map(
|
|
657
|
-
(edge) => index.pages.find((candidate) => candidate.id === edge.source)?.path ?? edge.source
|
|
658
|
-
) : [];
|
|
659
|
-
const related = page ? searchKnowledge(index, `${page.title} ${page.tags.join(" ")}`, 6).filter((result) => result.page.id !== page.id).map((result) => ({
|
|
660
|
-
path: result.page.path,
|
|
661
|
-
title: result.page.title,
|
|
662
|
-
score: result.score
|
|
663
|
-
})) : searchKnowledge(index, target, 6).map((result) => ({
|
|
664
|
-
path: result.page.path,
|
|
665
|
-
title: result.page.title,
|
|
666
|
-
score: result.score
|
|
667
|
-
}));
|
|
668
|
-
return {
|
|
669
|
-
target,
|
|
670
|
-
page,
|
|
671
|
-
sources: page ? index.sources.filter((source) => page.sourceIds.includes(source.id)).map((source) => ({ id: source.id, title: source.title, uri: source.uri })) : [],
|
|
672
|
-
links: page?.outLinks ?? [],
|
|
673
|
-
inbound,
|
|
674
|
-
related
|
|
675
|
-
};
|
|
676
|
-
}
|
|
677
|
-
|
|
678
|
-
// src/write-protocol.ts
|
|
679
|
-
var OPENER_LINE = /^---\s*FILE:\s*(.+?)\s*---\s*$/i;
|
|
680
|
-
var CLOSER_LINE = /^---\s*END\s+FILE\s*---\s*$/i;
|
|
681
|
-
var FENCE_LINE = /^\s{0,3}(```+|~~~+)/;
|
|
682
|
-
function isSafeKnowledgePath(path, allowedPrefixes = ["knowledge/"]) {
|
|
683
|
-
if (typeof path !== "string" || path.trim() === "") return false;
|
|
684
|
-
const controlRangeRegex = new RegExp(`[${String.fromCharCode(0)}-${String.fromCharCode(31)}]`);
|
|
685
|
-
if (controlRangeRegex.test(path)) return false;
|
|
686
|
-
if (path.startsWith("/") || path.startsWith("\\")) return false;
|
|
687
|
-
if (/^[a-zA-Z]:/.test(path)) return false;
|
|
688
|
-
const normalized = path.replace(/\\/g, "/");
|
|
689
|
-
if (normalized.split("/").some((part) => part === "..")) return false;
|
|
690
|
-
return allowedPrefixes.some((prefix) => normalized.startsWith(prefix));
|
|
691
|
-
}
|
|
692
|
-
function parseKnowledgeWriteBlocks(text, allowedPrefixes = ["knowledge/"]) {
|
|
693
|
-
const lines = text.replace(/\r\n/g, "\n").split("\n");
|
|
694
|
-
const blocks = [];
|
|
695
|
-
const warnings = [];
|
|
696
|
-
let i = 0;
|
|
697
|
-
while (i < lines.length) {
|
|
698
|
-
const opener = OPENER_LINE.exec(lines[i]);
|
|
699
|
-
if (!opener) {
|
|
700
|
-
i++;
|
|
701
|
-
continue;
|
|
702
|
-
}
|
|
703
|
-
const path = opener[1].trim();
|
|
704
|
-
i++;
|
|
705
|
-
const contentLines = [];
|
|
706
|
-
let fenceMarker = null;
|
|
707
|
-
let fenceLen = 0;
|
|
708
|
-
let closed = false;
|
|
709
|
-
while (i < lines.length) {
|
|
710
|
-
const line = lines[i];
|
|
711
|
-
const fence = FENCE_LINE.exec(line);
|
|
712
|
-
if (fence) {
|
|
713
|
-
const run = fence[1];
|
|
714
|
-
const char = run[0];
|
|
715
|
-
if (fenceMarker === null) {
|
|
716
|
-
fenceMarker = char;
|
|
717
|
-
fenceLen = run.length;
|
|
718
|
-
} else if (char === fenceMarker && run.length >= fenceLen) {
|
|
719
|
-
fenceMarker = null;
|
|
720
|
-
fenceLen = 0;
|
|
721
|
-
}
|
|
722
|
-
contentLines.push(line);
|
|
723
|
-
i++;
|
|
724
|
-
continue;
|
|
725
|
-
}
|
|
726
|
-
if (fenceMarker === null && CLOSER_LINE.test(line)) {
|
|
727
|
-
closed = true;
|
|
728
|
-
i++;
|
|
729
|
-
break;
|
|
730
|
-
}
|
|
731
|
-
contentLines.push(line);
|
|
732
|
-
i++;
|
|
733
|
-
}
|
|
734
|
-
if (!closed) {
|
|
735
|
-
warnings.push(`FILE block "${path || "(empty)"}" was not closed before end of stream.`);
|
|
736
|
-
continue;
|
|
737
|
-
}
|
|
738
|
-
if (!isSafeKnowledgePath(path, allowedPrefixes)) {
|
|
739
|
-
warnings.push(`FILE block with unsafe path "${path}" rejected.`);
|
|
740
|
-
continue;
|
|
741
|
-
}
|
|
742
|
-
blocks.push({ path, content: contentLines.join("\n") });
|
|
743
|
-
}
|
|
744
|
-
return { blocks, warnings };
|
|
745
|
-
}
|
|
746
|
-
|
|
747
|
-
// src/proposals.ts
|
|
748
|
-
import { mkdir as mkdir3, readFile as readFile3, writeFile as writeFile3 } from "fs/promises";
|
|
749
|
-
import { dirname as dirname3, join as join4 } from "path";
|
|
750
|
-
async function applyKnowledgeWriteBlocks(root, proposalText) {
|
|
751
|
-
const parsed = parseKnowledgeWriteBlocks(proposalText);
|
|
752
|
-
const written = [];
|
|
753
|
-
for (const block of parsed.blocks) {
|
|
754
|
-
const path = join4(root, block.path);
|
|
755
|
-
await mkdir3(dirname3(path), { recursive: true });
|
|
756
|
-
await writeFile3(
|
|
757
|
-
path,
|
|
758
|
-
block.content.endsWith("\n") ? block.content : `${block.content}
|
|
759
|
-
`,
|
|
760
|
-
"utf8"
|
|
761
|
-
);
|
|
762
|
-
written.push(block.path);
|
|
763
|
-
}
|
|
764
|
-
return { written, warnings: parsed.warnings };
|
|
765
|
-
}
|
|
766
|
-
async function applyKnowledgeWriteBlocksFile(root, proposalPath) {
|
|
767
|
-
return applyKnowledgeWriteBlocks(root, await readFile3(proposalPath, "utf8"));
|
|
768
|
-
}
|
|
769
|
-
|
|
770
|
-
// src/schemas.ts
|
|
771
|
-
import { z } from "zod";
|
|
772
|
-
var SourceAnchorSchema = z.object({
|
|
773
|
-
id: z.string().min(1),
|
|
774
|
-
sourceId: z.string().min(1),
|
|
775
|
-
label: z.string().optional(),
|
|
776
|
-
page: z.number().int().positive().optional(),
|
|
777
|
-
lineStart: z.number().int().positive().optional(),
|
|
778
|
-
lineEnd: z.number().int().positive().optional(),
|
|
779
|
-
charStart: z.number().int().nonnegative().optional(),
|
|
780
|
-
charEnd: z.number().int().nonnegative().optional(),
|
|
781
|
-
timestampMs: z.number().nonnegative().optional(),
|
|
782
|
-
metadata: z.record(z.string(), z.unknown()).optional()
|
|
783
|
-
});
|
|
784
|
-
var SourceRecordSchema = z.object({
|
|
785
|
-
id: z.string().min(1),
|
|
786
|
-
uri: z.string().min(1),
|
|
787
|
-
title: z.string().optional(),
|
|
788
|
-
mediaType: z.string().optional(),
|
|
789
|
-
contentHash: z.string().min(16),
|
|
790
|
-
text: z.string().optional(),
|
|
791
|
-
anchors: z.array(SourceAnchorSchema).optional(),
|
|
792
|
-
metadata: z.record(z.string(), z.unknown()).optional(),
|
|
793
|
-
createdAt: z.string().min(1)
|
|
794
|
-
});
|
|
795
|
-
var KnowledgePageSchema = z.object({
|
|
796
|
-
id: z.string().min(1),
|
|
797
|
-
path: z.string().min(1),
|
|
798
|
-
title: z.string().min(1),
|
|
799
|
-
text: z.string(),
|
|
800
|
-
frontmatter: z.record(z.string(), z.unknown()),
|
|
801
|
-
sourceIds: z.array(z.string()),
|
|
802
|
-
tags: z.array(z.string()),
|
|
803
|
-
outLinks: z.array(z.string())
|
|
804
|
-
});
|
|
805
|
-
var KnowledgeGraphNodeSchema = z.object({
|
|
806
|
-
id: z.string(),
|
|
807
|
-
title: z.string(),
|
|
808
|
-
path: z.string(),
|
|
809
|
-
tags: z.array(z.string()),
|
|
810
|
-
sourceIds: z.array(z.string()),
|
|
811
|
-
outDegree: z.number().int().nonnegative(),
|
|
812
|
-
inDegree: z.number().int().nonnegative()
|
|
813
|
-
});
|
|
814
|
-
var KnowledgeGraphEdgeSchema = z.object({
|
|
815
|
-
source: z.string(),
|
|
816
|
-
target: z.string(),
|
|
817
|
-
weight: z.number(),
|
|
818
|
-
reasons: z.array(z.string())
|
|
819
|
-
});
|
|
820
|
-
var KnowledgeIndexSchema = z.object({
|
|
821
|
-
root: z.string(),
|
|
822
|
-
generatedAt: z.string(),
|
|
823
|
-
sources: z.array(SourceRecordSchema),
|
|
824
|
-
pages: z.array(KnowledgePageSchema),
|
|
825
|
-
graph: z.object({
|
|
826
|
-
nodes: z.array(KnowledgeGraphNodeSchema),
|
|
827
|
-
edges: z.array(KnowledgeGraphEdgeSchema)
|
|
828
|
-
})
|
|
829
|
-
});
|
|
830
|
-
var KnowledgeEventSchema = z.object({
|
|
831
|
-
id: z.string().min(1),
|
|
832
|
-
type: z.enum([
|
|
833
|
-
"source.added",
|
|
834
|
-
"proposal.applied",
|
|
835
|
-
"index.built",
|
|
836
|
-
"lint.run",
|
|
837
|
-
"optimization.run",
|
|
838
|
-
"release.promoted",
|
|
839
|
-
"release.rejected"
|
|
840
|
-
]),
|
|
841
|
-
createdAt: z.string().min(1),
|
|
842
|
-
actor: z.string().optional(),
|
|
843
|
-
target: z.string().optional(),
|
|
844
|
-
metadata: z.record(z.string(), z.unknown()).optional()
|
|
845
|
-
});
|
|
846
|
-
var KnowledgeBaseCandidateSchema = z.object({
|
|
847
|
-
id: z.string().min(1),
|
|
848
|
-
units: z.array(
|
|
849
|
-
z.object({
|
|
850
|
-
id: z.string().min(1),
|
|
851
|
-
title: z.string().min(1),
|
|
852
|
-
text: z.string(),
|
|
853
|
-
claims: z.array(
|
|
854
|
-
z.object({
|
|
855
|
-
id: z.string().min(1),
|
|
856
|
-
text: z.string().min(1),
|
|
857
|
-
refs: z.array(
|
|
858
|
-
z.object({
|
|
859
|
-
sourceId: z.string().min(1),
|
|
860
|
-
anchorId: z.string().optional(),
|
|
861
|
-
quote: z.string().optional()
|
|
862
|
-
})
|
|
863
|
-
),
|
|
864
|
-
confidence: z.number().min(0).max(1).optional(),
|
|
865
|
-
status: z.enum(["draft", "active", "superseded", "rejected"]).optional(),
|
|
866
|
-
metadata: z.record(z.string(), z.unknown()).optional()
|
|
867
|
-
})
|
|
868
|
-
).optional(),
|
|
869
|
-
relations: z.array(
|
|
870
|
-
z.object({
|
|
871
|
-
sourceId: z.string(),
|
|
872
|
-
targetId: z.string(),
|
|
873
|
-
predicate: z.string(),
|
|
874
|
-
weight: z.number().optional(),
|
|
875
|
-
metadata: z.record(z.string(), z.unknown()).optional()
|
|
876
|
-
})
|
|
877
|
-
).optional(),
|
|
878
|
-
sourceIds: z.array(z.string()).optional(),
|
|
879
|
-
tags: z.array(z.string()).optional(),
|
|
880
|
-
metadata: z.record(z.string(), z.unknown()).optional(),
|
|
881
|
-
updatedAt: z.string().optional()
|
|
882
|
-
})
|
|
883
|
-
),
|
|
884
|
-
retrievalPolicy: z.string().optional(),
|
|
885
|
-
synthesisPolicy: z.string().optional(),
|
|
886
|
-
questionPolicy: z.string().optional(),
|
|
887
|
-
updatePolicy: z.string().optional(),
|
|
888
|
-
metadata: z.record(z.string(), z.unknown()).optional()
|
|
889
|
-
});
|
|
890
|
-
|
|
891
|
-
// src/validate.ts
|
|
892
|
-
function validateKnowledgeIndex(index, options = {}) {
|
|
893
|
-
const findings = [...lintKnowledgeIndex(index)];
|
|
894
|
-
const parsed = KnowledgeIndexSchema.safeParse(index);
|
|
895
|
-
if (!parsed.success) {
|
|
896
|
-
findings.push({
|
|
897
|
-
type: "missing-frontmatter",
|
|
898
|
-
severity: "error",
|
|
899
|
-
message: parsed.error.issues.map((issue) => `${issue.path.join(".")}: ${issue.message}`).join("; ")
|
|
900
|
-
});
|
|
901
|
-
}
|
|
902
|
-
if (options.strict) {
|
|
903
|
-
for (const page of index.pages) {
|
|
904
|
-
if (isScaffoldPath(page.path)) continue;
|
|
905
|
-
if (!page.frontmatter.id || !page.frontmatter.title) {
|
|
906
|
-
findings.push({
|
|
907
|
-
type: "missing-frontmatter",
|
|
908
|
-
severity: "error",
|
|
909
|
-
page: page.path,
|
|
910
|
-
message: "Strict mode requires id and title frontmatter."
|
|
911
|
-
});
|
|
912
|
-
}
|
|
913
|
-
}
|
|
914
|
-
}
|
|
915
|
-
return { ok: !findings.some((finding) => finding.severity === "error"), findings };
|
|
916
|
-
}
|
|
917
|
-
|
|
918
|
-
export {
|
|
919
|
-
textSourceAdapter,
|
|
920
|
-
mediaTypeFor,
|
|
921
|
-
stringMetadata,
|
|
922
|
-
parseFrontmatter,
|
|
923
|
-
formatFrontmatter,
|
|
924
|
-
WIKILINK_REGEX,
|
|
925
|
-
extractWikilinks,
|
|
926
|
-
normalizeLinkTarget,
|
|
927
|
-
buildKnowledgeGraph,
|
|
928
|
-
layoutFor,
|
|
929
|
-
SCAFFOLD_PAGE_BASENAMES,
|
|
930
|
-
isScaffoldPath,
|
|
931
|
-
initKnowledgeBase,
|
|
932
|
-
loadKnowledgePages,
|
|
933
|
-
writeJson,
|
|
934
|
-
loadSourceRegistry,
|
|
935
|
-
writeSourceRegistry,
|
|
936
|
-
addSourcePath,
|
|
937
|
-
addSourceText,
|
|
938
|
-
sourceRegistryPath,
|
|
939
|
-
buildKnowledgeIndex,
|
|
940
|
-
writeKnowledgeIndex,
|
|
941
|
-
lintKnowledgeIndex,
|
|
942
|
-
inspectKnowledgeIndex,
|
|
943
|
-
explainKnowledgeTarget,
|
|
944
|
-
isSafeKnowledgePath,
|
|
945
|
-
parseKnowledgeWriteBlocks,
|
|
946
|
-
applyKnowledgeWriteBlocks,
|
|
947
|
-
applyKnowledgeWriteBlocksFile,
|
|
948
|
-
SourceAnchorSchema,
|
|
949
|
-
SourceRecordSchema,
|
|
950
|
-
KnowledgePageSchema,
|
|
951
|
-
KnowledgeGraphNodeSchema,
|
|
952
|
-
KnowledgeGraphEdgeSchema,
|
|
953
|
-
KnowledgeIndexSchema,
|
|
954
|
-
KnowledgeEventSchema,
|
|
955
|
-
KnowledgeBaseCandidateSchema,
|
|
956
|
-
validateKnowledgeIndex
|
|
957
|
-
};
|
|
958
|
-
//# sourceMappingURL=chunk-W6VWYTNH.js.map
|