@tangle-network/agent-knowledge 0.1.1 → 1.1.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/AGENTS.md +9 -0
- package/README.md +58 -2
- package/dist/{chunk-XDXIBHPS.js → chunk-BEFAI2HO.js} +246 -70
- package/dist/chunk-BEFAI2HO.js.map +1 -0
- package/dist/cli.js +26 -1
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +326 -4
- package/dist/index.js +262 -1
- package/dist/index.js.map +1 -1
- package/dist/{types-C1FBNRIN.d.ts → types-CWbf3JSE.d.ts} +36 -2
- package/dist/viz/index.d.ts +1 -1
- package/docs/architecture.md +8 -0
- package/package.json +2 -2
- package/dist/chunk-XDXIBHPS.js.map +0 -1
package/AGENTS.md
CHANGED
|
@@ -23,6 +23,7 @@ agent-knowledge search "query" --json
|
|
|
23
23
|
agent-knowledge inspect --json
|
|
24
24
|
agent-knowledge explain knowledge/concepts/example.md --json
|
|
25
25
|
agent-knowledge lint --json
|
|
26
|
+
agent-knowledge validate --strict --json
|
|
26
27
|
agent-knowledge viz --json
|
|
27
28
|
```
|
|
28
29
|
|
|
@@ -49,3 +50,11 @@ The parser rejects absolute paths, `..`, control characters, and writes outside
|
|
|
49
50
|
## Eval Boundary
|
|
50
51
|
|
|
51
52
|
Use `runKnowledgeBaseOptimization()` when comparing candidate knowledge bases on an actual task corpus. It delegates to `@tangle-network/agent-eval` multi-shot optimization, so single-turn and multi-turn agents share the same path.
|
|
53
|
+
|
|
54
|
+
Use `knowledgeReleaseReportFromOptimization()` before promotion. It projects optimizer traces and `RunRecord` rows into `agent-eval` release confidence evidence.
|
|
55
|
+
|
|
56
|
+
## Integration Boundaries
|
|
57
|
+
|
|
58
|
+
- Use `KbStore` for storage. Implement D1 in the consuming app when needed.
|
|
59
|
+
- Use `KnowledgeDiscoveryDispatcher` for research workers. Production apps should wire this to their own swarm/fleet runtime.
|
|
60
|
+
- Do not bypass `lint` or `validate` before using generated knowledge in an agent.
|
package/README.md
CHANGED
|
@@ -23,6 +23,8 @@ agent-knowledge inspect --root .
|
|
|
23
23
|
agent-knowledge explain knowledge/concepts/risk.md --root .
|
|
24
24
|
agent-knowledge graph --root . --format json
|
|
25
25
|
agent-knowledge lint --root .
|
|
26
|
+
agent-knowledge validate --strict --root .
|
|
27
|
+
agent-knowledge export --root . --format json
|
|
26
28
|
agent-knowledge viz --root .
|
|
27
29
|
```
|
|
28
30
|
|
|
@@ -32,13 +34,20 @@ The default layout is:
|
|
|
32
34
|
raw/
|
|
33
35
|
sources/
|
|
34
36
|
knowledge/
|
|
35
|
-
index.md
|
|
36
|
-
log.md
|
|
37
|
+
index.md # scaffold: human-navigation only, excluded from the page index
|
|
38
|
+
log.md # scaffold: human-navigation only, excluded from the page index
|
|
37
39
|
.agent-knowledge/
|
|
38
40
|
sources.json
|
|
39
41
|
index.json
|
|
40
42
|
```
|
|
41
43
|
|
|
44
|
+
`initKnowledgeBase` writes `knowledge/index.md` and `knowledge/log.md` for
|
|
45
|
+
authors to curate by hand. They are deliberately excluded from
|
|
46
|
+
`buildKnowledgeIndex` / `searchKnowledge` so they do not inflate page counts
|
|
47
|
+
or pollute search hits. Any nested `<dir>/index.md` or `<dir>/log.md` is
|
|
48
|
+
treated the same way. The shared predicate is `isScaffoldPath`, exported
|
|
49
|
+
from `@tangle-network/agent-knowledge`.
|
|
50
|
+
|
|
42
51
|
## Design
|
|
43
52
|
|
|
44
53
|
- Raw sources are immutable evidence.
|
|
@@ -47,11 +56,58 @@ knowledge/
|
|
|
47
56
|
- Lint fails on pages that cite unknown source IDs.
|
|
48
57
|
- Text sources get deterministic anchors (`all`, `l1`, `l51`, ...) for precise citations like `[^src_id#all]`.
|
|
49
58
|
- Agent write proposals can be safely applied with `apply-write-blocks`.
|
|
59
|
+
- `KbStore` keeps storage consumer-owned; use `MemoryKbStore`, `FileSystemKbStore`, or implement D1 in the app.
|
|
60
|
+
- Discovery uses worker/dispatcher contracts, with a local dispatcher for dev and tests.
|
|
61
|
+
- Zod schemas define the stable wire shape.
|
|
50
62
|
- Graph/search/lint are deterministic and fast.
|
|
63
|
+
- `searchKnowledge` returns hits with three score fields. `score` and
|
|
64
|
+
`rrfScore` are the raw reciprocal-rank-fusion value (typically 0.01–0.05);
|
|
65
|
+
use them when intent matters or when fusing across queries.
|
|
66
|
+
`normalizedScore` is the same value scaled into [0, 1] relative to the top
|
|
67
|
+
hit *in this result set* (top hit = 1, others = score / topScore) — use it
|
|
68
|
+
when comparing against natural confidence thresholds. The normalization is
|
|
69
|
+
within-set ranking, not a cross-query absolute confidence.
|
|
51
70
|
- Optimization uses `@tangle-network/agent-eval` internally instead of reimplementing eval gates.
|
|
71
|
+
- `buildEvalKnowledgeBundle()` maps wiki/search evidence into
|
|
72
|
+
`agent-eval` `KnowledgeRequirement`, `KnowledgeBundle`, and
|
|
73
|
+
`KnowledgeReadinessReport` contracts so control loops can block, ask, or
|
|
74
|
+
acquire data before running an agent.
|
|
52
75
|
|
|
53
76
|
The `/viz` subpath exports graph insight helpers without UI dependencies.
|
|
54
77
|
|
|
55
78
|
## Agent-Eval Integration
|
|
56
79
|
|
|
57
80
|
Use `runKnowledgeBaseOptimization()` when the question is whether a candidate knowledge base actually improves agent task success. The candidate is passed through `runMultiShotOptimization`, so `n=1` single-turn tasks and variable-length multi-turn traces use the same path.
|
|
81
|
+
|
|
82
|
+
Use `knowledgeReleaseReportFromOptimization()` to turn optimizer output into release confidence evidence using `agent-eval` release gates and `RunRecord` validation.
|
|
83
|
+
|
|
84
|
+
Use `buildEvalKnowledgeBundle()` before execution when the question is whether
|
|
85
|
+
the agent has enough task-world context to run:
|
|
86
|
+
|
|
87
|
+
```ts
|
|
88
|
+
import { buildEvalKnowledgeBundle } from '@tangle-network/agent-knowledge'
|
|
89
|
+
|
|
90
|
+
const readiness = buildEvalKnowledgeBundle({
|
|
91
|
+
taskId: 'sdk-migration',
|
|
92
|
+
index,
|
|
93
|
+
specs: [{
|
|
94
|
+
id: 'repo-build-command',
|
|
95
|
+
description: 'Repository build and typecheck command',
|
|
96
|
+
query: 'build typecheck command',
|
|
97
|
+
requiredFor: ['coding'],
|
|
98
|
+
category: 'codebase_specific',
|
|
99
|
+
acquisitionMode: 'inspect_repo',
|
|
100
|
+
importance: 'blocking',
|
|
101
|
+
freshness: 'weekly',
|
|
102
|
+
sensitivity: 'public',
|
|
103
|
+
confidenceNeeded: 0.9,
|
|
104
|
+
minSources: 1,
|
|
105
|
+
}],
|
|
106
|
+
})
|
|
107
|
+
|
|
108
|
+
console.log(readiness.report.recommendedAction)
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
Pass `readiness.report` to `blockingKnowledgeEval()` from
|
|
112
|
+
`@tangle-network/agent-eval`; use `readiness.questions` and
|
|
113
|
+
`readiness.acquisitionPlans` to drive UI or connector workflows.
|
|
@@ -215,6 +215,176 @@ async function applyKnowledgeWriteBlocksFile(root, proposalPath) {
|
|
|
215
215
|
return applyKnowledgeWriteBlocks(root, await readFile(proposalPath, "utf8"));
|
|
216
216
|
}
|
|
217
217
|
|
|
218
|
+
// src/schemas.ts
|
|
219
|
+
import { z } from "zod";
|
|
220
|
+
var SourceAnchorSchema = z.object({
|
|
221
|
+
id: z.string().min(1),
|
|
222
|
+
sourceId: z.string().min(1),
|
|
223
|
+
label: z.string().optional(),
|
|
224
|
+
page: z.number().int().positive().optional(),
|
|
225
|
+
lineStart: z.number().int().positive().optional(),
|
|
226
|
+
lineEnd: z.number().int().positive().optional(),
|
|
227
|
+
charStart: z.number().int().nonnegative().optional(),
|
|
228
|
+
charEnd: z.number().int().nonnegative().optional(),
|
|
229
|
+
timestampMs: z.number().nonnegative().optional(),
|
|
230
|
+
metadata: z.record(z.string(), z.unknown()).optional()
|
|
231
|
+
});
|
|
232
|
+
var SourceRecordSchema = z.object({
|
|
233
|
+
id: z.string().min(1),
|
|
234
|
+
uri: z.string().min(1),
|
|
235
|
+
title: z.string().optional(),
|
|
236
|
+
mediaType: z.string().optional(),
|
|
237
|
+
contentHash: z.string().min(16),
|
|
238
|
+
text: z.string().optional(),
|
|
239
|
+
anchors: z.array(SourceAnchorSchema).optional(),
|
|
240
|
+
metadata: z.record(z.string(), z.unknown()).optional(),
|
|
241
|
+
createdAt: z.string().min(1)
|
|
242
|
+
});
|
|
243
|
+
var KnowledgePageSchema = z.object({
|
|
244
|
+
id: z.string().min(1),
|
|
245
|
+
path: z.string().min(1),
|
|
246
|
+
title: z.string().min(1),
|
|
247
|
+
text: z.string(),
|
|
248
|
+
frontmatter: z.record(z.string(), z.unknown()),
|
|
249
|
+
sourceIds: z.array(z.string()),
|
|
250
|
+
tags: z.array(z.string()),
|
|
251
|
+
outLinks: z.array(z.string())
|
|
252
|
+
});
|
|
253
|
+
var KnowledgeGraphNodeSchema = z.object({
|
|
254
|
+
id: z.string(),
|
|
255
|
+
title: z.string(),
|
|
256
|
+
path: z.string(),
|
|
257
|
+
tags: z.array(z.string()),
|
|
258
|
+
sourceIds: z.array(z.string()),
|
|
259
|
+
outDegree: z.number().int().nonnegative(),
|
|
260
|
+
inDegree: z.number().int().nonnegative()
|
|
261
|
+
});
|
|
262
|
+
var KnowledgeGraphEdgeSchema = z.object({
|
|
263
|
+
source: z.string(),
|
|
264
|
+
target: z.string(),
|
|
265
|
+
weight: z.number(),
|
|
266
|
+
reasons: z.array(z.string())
|
|
267
|
+
});
|
|
268
|
+
var KnowledgeIndexSchema = z.object({
|
|
269
|
+
root: z.string(),
|
|
270
|
+
generatedAt: z.string(),
|
|
271
|
+
sources: z.array(SourceRecordSchema),
|
|
272
|
+
pages: z.array(KnowledgePageSchema),
|
|
273
|
+
graph: z.object({
|
|
274
|
+
nodes: z.array(KnowledgeGraphNodeSchema),
|
|
275
|
+
edges: z.array(KnowledgeGraphEdgeSchema)
|
|
276
|
+
})
|
|
277
|
+
});
|
|
278
|
+
var KnowledgeEventSchema = z.object({
|
|
279
|
+
id: z.string().min(1),
|
|
280
|
+
type: z.enum(["source.added", "proposal.applied", "index.built", "lint.run", "optimization.run", "release.promoted", "release.rejected"]),
|
|
281
|
+
createdAt: z.string().min(1),
|
|
282
|
+
actor: z.string().optional(),
|
|
283
|
+
target: z.string().optional(),
|
|
284
|
+
metadata: z.record(z.string(), z.unknown()).optional()
|
|
285
|
+
});
|
|
286
|
+
var KnowledgeBaseCandidateSchema = z.object({
|
|
287
|
+
id: z.string().min(1),
|
|
288
|
+
units: z.array(z.object({
|
|
289
|
+
id: z.string().min(1),
|
|
290
|
+
title: z.string().min(1),
|
|
291
|
+
text: z.string(),
|
|
292
|
+
claims: z.array(z.object({
|
|
293
|
+
id: z.string().min(1),
|
|
294
|
+
text: z.string().min(1),
|
|
295
|
+
refs: z.array(z.object({
|
|
296
|
+
sourceId: z.string().min(1),
|
|
297
|
+
anchorId: z.string().optional(),
|
|
298
|
+
quote: z.string().optional()
|
|
299
|
+
})),
|
|
300
|
+
confidence: z.number().min(0).max(1).optional(),
|
|
301
|
+
status: z.enum(["draft", "active", "superseded", "rejected"]).optional(),
|
|
302
|
+
metadata: z.record(z.string(), z.unknown()).optional()
|
|
303
|
+
})).optional(),
|
|
304
|
+
relations: z.array(z.object({
|
|
305
|
+
sourceId: z.string(),
|
|
306
|
+
targetId: z.string(),
|
|
307
|
+
predicate: z.string(),
|
|
308
|
+
weight: z.number().optional(),
|
|
309
|
+
metadata: z.record(z.string(), z.unknown()).optional()
|
|
310
|
+
})).optional(),
|
|
311
|
+
sourceIds: z.array(z.string()).optional(),
|
|
312
|
+
tags: z.array(z.string()).optional(),
|
|
313
|
+
metadata: z.record(z.string(), z.unknown()).optional(),
|
|
314
|
+
updatedAt: z.string().optional()
|
|
315
|
+
})),
|
|
316
|
+
retrievalPolicy: z.string().optional(),
|
|
317
|
+
synthesisPolicy: z.string().optional(),
|
|
318
|
+
questionPolicy: z.string().optional(),
|
|
319
|
+
updatePolicy: z.string().optional(),
|
|
320
|
+
metadata: z.record(z.string(), z.unknown()).optional()
|
|
321
|
+
});
|
|
322
|
+
|
|
323
|
+
// src/graph.ts
|
|
324
|
+
function buildKnowledgeGraph(pages) {
|
|
325
|
+
const byId = /* @__PURE__ */ new Map();
|
|
326
|
+
const bySlug = /* @__PURE__ */ new Map();
|
|
327
|
+
for (const page of pages) {
|
|
328
|
+
byId.set(page.id, page);
|
|
329
|
+
bySlug.set(normalizeLinkTarget(page.id), page);
|
|
330
|
+
bySlug.set(normalizeLinkTarget(page.title), page);
|
|
331
|
+
bySlug.set(normalizeLinkTarget(page.path.split("/").pop().replace(/\.md$/, "")), page);
|
|
332
|
+
}
|
|
333
|
+
const incoming = /* @__PURE__ */ new Map();
|
|
334
|
+
const outgoing = /* @__PURE__ */ new Map();
|
|
335
|
+
const edgesByKey = /* @__PURE__ */ new Map();
|
|
336
|
+
for (const page of pages) {
|
|
337
|
+
outgoing.set(page.id, 0);
|
|
338
|
+
incoming.set(page.id, 0);
|
|
339
|
+
}
|
|
340
|
+
for (const page of pages) {
|
|
341
|
+
for (const raw of page.outLinks) {
|
|
342
|
+
const target = bySlug.get(normalizeLinkTarget(raw));
|
|
343
|
+
if (!target || target.id === page.id) continue;
|
|
344
|
+
const key = `${page.id}->${target.id}`;
|
|
345
|
+
const edge = edgesByKey.get(key);
|
|
346
|
+
if (edge) edge.weight += 1;
|
|
347
|
+
else edgesByKey.set(key, { source: page.id, target: target.id, weight: 1, reasons: ["wikilink"] });
|
|
348
|
+
outgoing.set(page.id, (outgoing.get(page.id) ?? 0) + 1);
|
|
349
|
+
incoming.set(target.id, (incoming.get(target.id) ?? 0) + 1);
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
addSourceOverlapEdges(pages, edgesByKey);
|
|
353
|
+
const nodes = pages.map((page) => ({
|
|
354
|
+
id: page.id,
|
|
355
|
+
title: page.title,
|
|
356
|
+
path: page.path,
|
|
357
|
+
tags: page.tags,
|
|
358
|
+
sourceIds: page.sourceIds,
|
|
359
|
+
outDegree: outgoing.get(page.id) ?? 0,
|
|
360
|
+
inDegree: incoming.get(page.id) ?? 0
|
|
361
|
+
}));
|
|
362
|
+
return { nodes, edges: [...edgesByKey.values()].sort((a, b) => b.weight - a.weight) };
|
|
363
|
+
}
|
|
364
|
+
function addSourceOverlapEdges(pages, edges) {
|
|
365
|
+
for (let i = 0; i < pages.length; i++) {
|
|
366
|
+
for (let j = i + 1; j < pages.length; j++) {
|
|
367
|
+
const a = pages[i];
|
|
368
|
+
const b = pages[j];
|
|
369
|
+
const overlap = a.sourceIds.filter((source) => b.sourceIds.includes(source));
|
|
370
|
+
if (overlap.length === 0) continue;
|
|
371
|
+
const key = `${a.id}->${b.id}`;
|
|
372
|
+
const edge = edges.get(key);
|
|
373
|
+
if (edge) {
|
|
374
|
+
edge.weight += overlap.length * 0.5;
|
|
375
|
+
edge.reasons.push("shared-source");
|
|
376
|
+
} else {
|
|
377
|
+
edges.set(key, {
|
|
378
|
+
source: a.id,
|
|
379
|
+
target: b.id,
|
|
380
|
+
weight: overlap.length * 0.5,
|
|
381
|
+
reasons: ["shared-source"]
|
|
382
|
+
});
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
|
|
218
388
|
// src/store.ts
|
|
219
389
|
import { mkdir as mkdir2, readFile as readFile2, readdir, stat, writeFile as writeFile2 } from "fs/promises";
|
|
220
390
|
import { dirname as dirname2, join as join2, relative } from "path";
|
|
@@ -229,6 +399,15 @@ function layoutFor(root) {
|
|
|
229
399
|
cacheDir: join2(root, ".agent-knowledge")
|
|
230
400
|
};
|
|
231
401
|
}
|
|
402
|
+
var SCAFFOLD_PAGE_BASENAMES = ["index.md", "log.md"];
|
|
403
|
+
function isScaffoldPath(path) {
|
|
404
|
+
const normalized = path.replace(/\\/g, "/");
|
|
405
|
+
for (const basename2 of SCAFFOLD_PAGE_BASENAMES) {
|
|
406
|
+
if (normalized === `knowledge/${basename2}`) return true;
|
|
407
|
+
if (normalized.endsWith(`/${basename2}`)) return true;
|
|
408
|
+
}
|
|
409
|
+
return false;
|
|
410
|
+
}
|
|
232
411
|
async function initKnowledgeBase(root) {
|
|
233
412
|
const layout = layoutFor(root);
|
|
234
413
|
await mkdir2(layout.knowledgeDir, { recursive: true });
|
|
@@ -244,9 +423,10 @@ async function loadKnowledgePages(root) {
|
|
|
244
423
|
const files = await listMarkdownFiles(layout.knowledgeDir);
|
|
245
424
|
const pages = [];
|
|
246
425
|
for (const file of files) {
|
|
426
|
+
const rel = relative(root, file).replace(/\\/g, "/");
|
|
427
|
+
if (isScaffoldPath(rel)) continue;
|
|
247
428
|
const content = await readFile2(file, "utf8");
|
|
248
429
|
const { frontmatter, body } = parseFrontmatter(content);
|
|
249
|
-
const rel = relative(root, file).replace(/\\/g, "/");
|
|
250
430
|
const title = stringField(frontmatter.title) ?? firstHeading(body) ?? rel.split("/").pop().replace(/\.md$/, "");
|
|
251
431
|
const sourceIds = arrayField(frontmatter.sources);
|
|
252
432
|
const tags = arrayField(frontmatter.tags);
|
|
@@ -400,71 +580,6 @@ function textPreview(fileName, bytes) {
|
|
|
400
580
|
return bytes.toString("utf8").slice(0, 2e5);
|
|
401
581
|
}
|
|
402
582
|
|
|
403
|
-
// src/graph.ts
|
|
404
|
-
function buildKnowledgeGraph(pages) {
|
|
405
|
-
const byId = /* @__PURE__ */ new Map();
|
|
406
|
-
const bySlug = /* @__PURE__ */ new Map();
|
|
407
|
-
for (const page of pages) {
|
|
408
|
-
byId.set(page.id, page);
|
|
409
|
-
bySlug.set(normalizeLinkTarget(page.id), page);
|
|
410
|
-
bySlug.set(normalizeLinkTarget(page.title), page);
|
|
411
|
-
bySlug.set(normalizeLinkTarget(page.path.split("/").pop().replace(/\.md$/, "")), page);
|
|
412
|
-
}
|
|
413
|
-
const incoming = /* @__PURE__ */ new Map();
|
|
414
|
-
const outgoing = /* @__PURE__ */ new Map();
|
|
415
|
-
const edgesByKey = /* @__PURE__ */ new Map();
|
|
416
|
-
for (const page of pages) {
|
|
417
|
-
outgoing.set(page.id, 0);
|
|
418
|
-
incoming.set(page.id, 0);
|
|
419
|
-
}
|
|
420
|
-
for (const page of pages) {
|
|
421
|
-
for (const raw of page.outLinks) {
|
|
422
|
-
const target = bySlug.get(normalizeLinkTarget(raw));
|
|
423
|
-
if (!target || target.id === page.id) continue;
|
|
424
|
-
const key = `${page.id}->${target.id}`;
|
|
425
|
-
const edge = edgesByKey.get(key);
|
|
426
|
-
if (edge) edge.weight += 1;
|
|
427
|
-
else edgesByKey.set(key, { source: page.id, target: target.id, weight: 1, reasons: ["wikilink"] });
|
|
428
|
-
outgoing.set(page.id, (outgoing.get(page.id) ?? 0) + 1);
|
|
429
|
-
incoming.set(target.id, (incoming.get(target.id) ?? 0) + 1);
|
|
430
|
-
}
|
|
431
|
-
}
|
|
432
|
-
addSourceOverlapEdges(pages, edgesByKey);
|
|
433
|
-
const nodes = pages.map((page) => ({
|
|
434
|
-
id: page.id,
|
|
435
|
-
title: page.title,
|
|
436
|
-
path: page.path,
|
|
437
|
-
tags: page.tags,
|
|
438
|
-
sourceIds: page.sourceIds,
|
|
439
|
-
outDegree: outgoing.get(page.id) ?? 0,
|
|
440
|
-
inDegree: incoming.get(page.id) ?? 0
|
|
441
|
-
}));
|
|
442
|
-
return { nodes, edges: [...edgesByKey.values()].sort((a, b) => b.weight - a.weight) };
|
|
443
|
-
}
|
|
444
|
-
function addSourceOverlapEdges(pages, edges) {
|
|
445
|
-
for (let i = 0; i < pages.length; i++) {
|
|
446
|
-
for (let j = i + 1; j < pages.length; j++) {
|
|
447
|
-
const a = pages[i];
|
|
448
|
-
const b = pages[j];
|
|
449
|
-
const overlap = a.sourceIds.filter((source) => b.sourceIds.includes(source));
|
|
450
|
-
if (overlap.length === 0) continue;
|
|
451
|
-
const key = `${a.id}->${b.id}`;
|
|
452
|
-
const edge = edges.get(key);
|
|
453
|
-
if (edge) {
|
|
454
|
-
edge.weight += overlap.length * 0.5;
|
|
455
|
-
edge.reasons.push("shared-source");
|
|
456
|
-
} else {
|
|
457
|
-
edges.set(key, {
|
|
458
|
-
source: a.id,
|
|
459
|
-
target: b.id,
|
|
460
|
-
weight: overlap.length * 0.5,
|
|
461
|
-
reasons: ["shared-source"]
|
|
462
|
-
});
|
|
463
|
-
}
|
|
464
|
-
}
|
|
465
|
-
}
|
|
466
|
-
}
|
|
467
|
-
|
|
468
583
|
// src/search.ts
|
|
469
584
|
var RRF_K = 60;
|
|
470
585
|
var STOP_WORDS = /* @__PURE__ */ new Set(["the", "is", "a", "an", "what", "how", "are", "was", "were", "to", "for", "of", "with", "by", "in", "on", "and"]);
|
|
@@ -475,9 +590,13 @@ function searchKnowledge(index, query, limit = 10) {
|
|
|
475
590
|
const graphRanked = rankByGraph(index.pages, tokenRanked);
|
|
476
591
|
const scores = reciprocalRankFusion([tokenRanked.map((p) => p.id), graphRanked.map((p) => p.id)]);
|
|
477
592
|
const byId = new Map(index.pages.map((page) => [page.id, page]));
|
|
478
|
-
|
|
593
|
+
const ranked = [...scores.entries()].map(([id, score]) => ({ page: byId.get(id), score })).filter((item) => Boolean(item.page)).sort((a, b) => b.score - a.score || a.page.path.localeCompare(b.page.path)).slice(0, limit);
|
|
594
|
+
const topScore = ranked[0]?.score ?? 0;
|
|
595
|
+
return ranked.map((item, i) => ({
|
|
479
596
|
page: item.page,
|
|
480
597
|
score: item.score,
|
|
598
|
+
rrfScore: item.score,
|
|
599
|
+
normalizedScore: topScore > 0 ? item.score / topScore : 0,
|
|
481
600
|
rank: i + 1,
|
|
482
601
|
snippet: buildSnippet(item.page.text, trimmed),
|
|
483
602
|
reasons: reasonsFor(item.page, trimmed)
|
|
@@ -579,13 +698,19 @@ function lintKnowledgeIndex(index) {
|
|
|
579
698
|
const titles = /* @__PURE__ */ new Map();
|
|
580
699
|
const sourceIds = new Set(index.sources.map((source) => source.id));
|
|
581
700
|
const anchorIds = new Map(index.sources.map((source) => [source.id, new Set((source.anchors ?? []).map((anchor) => anchor.id))]));
|
|
701
|
+
const pageIds = /* @__PURE__ */ new Map();
|
|
702
|
+
const sourceHashes = /* @__PURE__ */ new Map();
|
|
582
703
|
for (const page of index.pages) {
|
|
704
|
+
pageIds.set(page.id, [...pageIds.get(page.id) ?? [], page.path]);
|
|
583
705
|
byTarget.add(normalizeLinkTarget(page.id));
|
|
584
706
|
byTarget.add(normalizeLinkTarget(page.title));
|
|
585
707
|
byTarget.add(normalizeLinkTarget(page.path.split("/").pop().replace(/\.md$/, "")));
|
|
586
708
|
const titleKey = page.title.toLowerCase();
|
|
587
709
|
titles.set(titleKey, [...titles.get(titleKey) ?? [], page.path]);
|
|
588
710
|
}
|
|
711
|
+
for (const source of index.sources) {
|
|
712
|
+
sourceHashes.set(source.contentHash, [...sourceHashes.get(source.contentHash) ?? [], source.id]);
|
|
713
|
+
}
|
|
589
714
|
const inbound = /* @__PURE__ */ new Map();
|
|
590
715
|
for (const page of index.pages) inbound.set(page.id, 0);
|
|
591
716
|
for (const page of index.pages) {
|
|
@@ -624,6 +749,16 @@ function lintKnowledgeIndex(index) {
|
|
|
624
749
|
findings.push({ type: "duplicate-title", severity: "warning", message: `Duplicate title "${title}" in ${paths.join(", ")}.`, metadata: { paths } });
|
|
625
750
|
}
|
|
626
751
|
}
|
|
752
|
+
for (const [id, paths] of pageIds) {
|
|
753
|
+
if (id && paths.length > 1) {
|
|
754
|
+
findings.push({ type: "duplicate-page-id", severity: "error", message: `Duplicate page id "${id}" in ${paths.join(", ")}.`, metadata: { paths } });
|
|
755
|
+
}
|
|
756
|
+
}
|
|
757
|
+
for (const [hash, ids] of sourceHashes) {
|
|
758
|
+
if (hash && ids.length > 1) {
|
|
759
|
+
findings.push({ type: "duplicate-source-hash", severity: "warning", message: `Duplicate source content hash across ${ids.join(", ")}.`, metadata: { sourceIds: ids } });
|
|
760
|
+
}
|
|
761
|
+
}
|
|
627
762
|
return findings;
|
|
628
763
|
}
|
|
629
764
|
function isStructural(path) {
|
|
@@ -667,6 +802,36 @@ function explainKnowledgeTarget(index, target) {
|
|
|
667
802
|
};
|
|
668
803
|
}
|
|
669
804
|
|
|
805
|
+
// src/validate.ts
|
|
806
|
+
function validateKnowledgeIndex(index, options = {}) {
|
|
807
|
+
const findings = [...lintKnowledgeIndex(index)];
|
|
808
|
+
const parsed = KnowledgeIndexSchema.safeParse(index);
|
|
809
|
+
if (!parsed.success) {
|
|
810
|
+
findings.push({
|
|
811
|
+
type: "missing-frontmatter",
|
|
812
|
+
severity: "error",
|
|
813
|
+
message: parsed.error.issues.map((issue) => `${issue.path.join(".")}: ${issue.message}`).join("; ")
|
|
814
|
+
});
|
|
815
|
+
}
|
|
816
|
+
if (options.strict) {
|
|
817
|
+
for (const page of index.pages) {
|
|
818
|
+
if (isStructuralPage(page.path)) continue;
|
|
819
|
+
if (!page.frontmatter.id || !page.frontmatter.title) {
|
|
820
|
+
findings.push({
|
|
821
|
+
type: "missing-frontmatter",
|
|
822
|
+
severity: "error",
|
|
823
|
+
page: page.path,
|
|
824
|
+
message: "Strict mode requires id and title frontmatter."
|
|
825
|
+
});
|
|
826
|
+
}
|
|
827
|
+
}
|
|
828
|
+
}
|
|
829
|
+
return { ok: !findings.some((finding) => finding.severity === "error"), findings };
|
|
830
|
+
}
|
|
831
|
+
function isStructuralPage(path) {
|
|
832
|
+
return path === "knowledge/index.md" || path === "knowledge/log.md" || path.endsWith("/index.md") || path.endsWith("/log.md");
|
|
833
|
+
}
|
|
834
|
+
|
|
670
835
|
export {
|
|
671
836
|
sha256,
|
|
672
837
|
slugify,
|
|
@@ -682,7 +847,18 @@ export {
|
|
|
682
847
|
mediaTypeFor,
|
|
683
848
|
applyKnowledgeWriteBlocks,
|
|
684
849
|
applyKnowledgeWriteBlocksFile,
|
|
850
|
+
SourceAnchorSchema,
|
|
851
|
+
SourceRecordSchema,
|
|
852
|
+
KnowledgePageSchema,
|
|
853
|
+
KnowledgeGraphNodeSchema,
|
|
854
|
+
KnowledgeGraphEdgeSchema,
|
|
855
|
+
KnowledgeIndexSchema,
|
|
856
|
+
KnowledgeEventSchema,
|
|
857
|
+
KnowledgeBaseCandidateSchema,
|
|
858
|
+
buildKnowledgeGraph,
|
|
685
859
|
layoutFor,
|
|
860
|
+
SCAFFOLD_PAGE_BASENAMES,
|
|
861
|
+
isScaffoldPath,
|
|
686
862
|
initKnowledgeBase,
|
|
687
863
|
loadKnowledgePages,
|
|
688
864
|
writeJson,
|
|
@@ -690,7 +866,6 @@ export {
|
|
|
690
866
|
writeSourceRegistry,
|
|
691
867
|
addSourcePath,
|
|
692
868
|
sourceRegistryPath,
|
|
693
|
-
buildKnowledgeGraph,
|
|
694
869
|
searchKnowledge,
|
|
695
870
|
tokenizeQuery,
|
|
696
871
|
reciprocalRankFusion,
|
|
@@ -698,6 +873,7 @@ export {
|
|
|
698
873
|
writeKnowledgeIndex,
|
|
699
874
|
lintKnowledgeIndex,
|
|
700
875
|
inspectKnowledgeIndex,
|
|
701
|
-
explainKnowledgeTarget
|
|
876
|
+
explainKnowledgeTarget,
|
|
877
|
+
validateKnowledgeIndex
|
|
702
878
|
};
|
|
703
|
-
//# sourceMappingURL=chunk-
|
|
879
|
+
//# sourceMappingURL=chunk-BEFAI2HO.js.map
|