@tangle-network/agent-knowledge 8.0.10 → 10.7.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 +14 -2
- package/CHANGELOG.md +106 -0
- package/README.md +127 -5
- package/dist/benchmarks/index.d.ts +1 -1
- package/dist/benchmarks/index.js +1 -1
- package/dist/{benchmarks-B_07vX0Q.js → benchmarks-B6fCb6AD.js} +2 -2
- package/dist/{benchmarks-B_07vX0Q.js.map → benchmarks-B6fCb6AD.js.map} +1 -1
- package/dist/cli.js +53 -21
- package/dist/cli.js.map +1 -1
- package/dist/{index-DypNCZtP.d.ts → index-DZeFm-BP.d.ts} +2 -2
- package/dist/{index-DypNCZtP.d.ts.map → index-DZeFm-BP.d.ts.map} +1 -1
- package/dist/{index-Ij4giqqj.d.ts → index-eDsIXWyM.d.ts} +3 -3
- package/dist/{index-Ij4giqqj.d.ts.map → index-eDsIXWyM.d.ts.map} +1 -1
- package/dist/index.d.ts +911 -291
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1020 -392
- package/dist/index.js.map +1 -1
- package/dist/{inspect-0tmv3xV8.js → inspect-DALsvG10.js} +680 -108
- package/dist/inspect-DALsvG10.js.map +1 -0
- package/dist/memory/index.d.ts +2 -2
- package/dist/memory/index.js +2 -2
- package/dist/{memory-BPgPIEhj.js → memory-BRGsy2QN.js} +2 -2
- package/dist/{memory-BPgPIEhj.js.map → memory-BRGsy2QN.js.map} +1 -1
- package/dist/search-Cw6eYfSd.js +258 -0
- package/dist/search-Cw6eYfSd.js.map +1 -0
- package/dist/{types-CTT16XnO.d.ts → types-m2QB86fF.d.ts} +17 -3
- package/dist/types-m2QB86fF.d.ts.map +1 -0
- package/dist/viz/index.d.ts +1 -1
- package/docs/architecture.md +1 -0
- package/docs/knowledge-use-receipts.md +50 -12
- package/package.json +5 -5
- package/dist/inspect-0tmv3xV8.js.map +0 -1
- package/dist/search-CtVJ0PKX.js +0 -132
- package/dist/search-CtVJ0PKX.js.map +0 -1
- package/dist/types-CTT16XnO.d.ts.map +0 -1
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { i as textSourceId, n as slugify, r as stableId, t as sha256 } from "./ids-Bevz_pXV.js";
|
|
2
|
-
import {
|
|
2
|
+
import { r as searchKnowledge } from "./search-Cw6eYfSd.js";
|
|
3
3
|
import { createHash, randomUUID } from "node:crypto";
|
|
4
4
|
import { lstat, mkdir, mkdtemp, open, readFile, readdir, realpath, rename, rm } from "node:fs/promises";
|
|
5
5
|
import { basename, dirname, isAbsolute, join, relative, resolve, sep } from "node:path";
|
|
@@ -273,10 +273,52 @@ function normalizeRelativePath(path) {
|
|
|
273
273
|
return normalized;
|
|
274
274
|
}
|
|
275
275
|
//#endregion
|
|
276
|
+
//#region src/pages-directory.ts
|
|
277
|
+
/** Root-relative directory that holds Markdown pages when a caller names none. */
|
|
278
|
+
const DEFAULT_PAGES_DIRECTORY = "knowledge";
|
|
279
|
+
/**
|
|
280
|
+
* Validate a pages directory and return its canonical root-relative form.
|
|
281
|
+
*
|
|
282
|
+
* The result is a write allowlist prefix as well as a read location, so the
|
|
283
|
+
* check refuses every value that could reach outside the pages tree: absolute
|
|
284
|
+
* paths, drive letters, `..` or `.` segments, empty segments, control
|
|
285
|
+
* characters, and the package-owned `.agent-knowledge` and `raw` trees.
|
|
286
|
+
*/
|
|
287
|
+
function normalizePagesDirectory(value = DEFAULT_PAGES_DIRECTORY) {
|
|
288
|
+
if (typeof value !== "string") throw new Error("pagesDirectory must be a string");
|
|
289
|
+
const normalized = value.replace(/\\/g, "/").replace(/^\.\//, "").replace(/\/+$/, "");
|
|
290
|
+
if (normalized.length === 0 || normalized === ".") throw new Error("pagesDirectory must name a root-relative directory");
|
|
291
|
+
if (new RegExp(`[${String.fromCharCode(0)}-${String.fromCharCode(31)}]`).test(normalized)) throw new Error("pagesDirectory must not contain control characters");
|
|
292
|
+
if (normalized.startsWith("/") || /^[A-Za-z]:/.test(normalized)) throw new Error(`pagesDirectory must be root-relative, not absolute: ${value}`);
|
|
293
|
+
const segments = normalized.split("/");
|
|
294
|
+
if (segments.some((segment) => segment === "" || segment === "." || segment === "..")) throw new Error(`pagesDirectory must not contain empty or dot segments: ${value}`);
|
|
295
|
+
if (segments[0] === ".agent-knowledge" || segments[0] === "raw") throw new Error(`pagesDirectory must not name the package-owned ${segments[0]} tree: ${value}`);
|
|
296
|
+
return normalized;
|
|
297
|
+
}
|
|
298
|
+
//#endregion
|
|
276
299
|
//#region src/file-transaction.ts
|
|
277
300
|
const digestSchema = z.string().regex(/^[a-f0-9]{64}$/);
|
|
278
301
|
const fileModeSchema = z.number().int().min(0).max(511);
|
|
279
302
|
const DEFAULT_FILE_MODE = 420;
|
|
303
|
+
const SOURCE_REGISTRY_PATH = ".agent-knowledge/sources.json";
|
|
304
|
+
/**
|
|
305
|
+
* A journal states the pages directory it was prepared under, already in
|
|
306
|
+
* canonical form, so replay and recovery enforce the same allowlist as the
|
|
307
|
+
* prepare step without any caller-supplied value.
|
|
308
|
+
*/
|
|
309
|
+
const pagesDirectorySchema = z.string().superRefine((value, context) => {
|
|
310
|
+
try {
|
|
311
|
+
if (normalizePagesDirectory(value) !== value) context.addIssue({
|
|
312
|
+
code: "custom",
|
|
313
|
+
message: "pagesDirectory is not in canonical form"
|
|
314
|
+
});
|
|
315
|
+
} catch (error) {
|
|
316
|
+
context.addIssue({
|
|
317
|
+
code: "custom",
|
|
318
|
+
message: error instanceof Error ? error.message : String(error)
|
|
319
|
+
});
|
|
320
|
+
}
|
|
321
|
+
});
|
|
280
322
|
const transactionEntrySchema = z.object({
|
|
281
323
|
index: z.number().int().nonnegative(),
|
|
282
324
|
path: z.string().min(1),
|
|
@@ -290,6 +332,7 @@ const transactionSchema = z.object({
|
|
|
290
332
|
transactionId: z.string().uuid(),
|
|
291
333
|
purpose: z.string().min(1),
|
|
292
334
|
recoveryOwner: z.string().min(1).max(256).optional(),
|
|
335
|
+
pagesDirectory: pagesDirectorySchema.optional(),
|
|
293
336
|
createdAt: z.string().min(1),
|
|
294
337
|
entries: z.array(transactionEntrySchema).min(1)
|
|
295
338
|
}).strict();
|
|
@@ -304,12 +347,13 @@ function knowledgeFileTransactionPlanHash(entries) {
|
|
|
304
347
|
}
|
|
305
348
|
async function prepareKnowledgeFileTransaction(input) {
|
|
306
349
|
if (input.mutations.length === 0) throw new Error("knowledge file transaction has no mutations");
|
|
350
|
+
const pagesDirectory = input.pagesDirectory === void 0 ? void 0 : normalizePagesDirectory(input.pagesDirectory);
|
|
307
351
|
return withTransactionRoot(input.root, input.transactionRoot, true, async (transactionRoot) => {
|
|
308
352
|
const activeTransactions = await activeTransactionDirectoryNames(transactionRoot);
|
|
309
353
|
if (activeTransactions.length > 0) throw new Error(`knowledge file transaction is already active: ${activeTransactions.join(", ")}`);
|
|
310
354
|
const paths = /* @__PURE__ */ new Set();
|
|
311
355
|
const changed = (await Promise.all(input.mutations.map(async (mutation, index) => {
|
|
312
|
-
const path = assertKnowledgeMutationPath(mutation.path);
|
|
356
|
+
const path = assertKnowledgeMutationPath(mutation.path, pagesDirectory);
|
|
313
357
|
if (paths.has(path)) throw new Error(`knowledge file transaction repeats path: ${path}`);
|
|
314
358
|
if (mutation.content === null && mutation.mode !== void 0) throw new Error(`deleted knowledge file cannot declare a mode: ${path}`);
|
|
315
359
|
paths.add(path);
|
|
@@ -343,6 +387,7 @@ async function prepareKnowledgeFileTransaction(input) {
|
|
|
343
387
|
transactionId: randomUUID(),
|
|
344
388
|
purpose: input.purpose,
|
|
345
389
|
...input.recoveryOwner ? { recoveryOwner: input.recoveryOwner } : {},
|
|
390
|
+
...pagesDirectory === void 0 ? {} : { pagesDirectory },
|
|
346
391
|
createdAt: (input.now ?? (() => /* @__PURE__ */ new Date()))().toISOString(),
|
|
347
392
|
entries: changed.map((item) => item.entry)
|
|
348
393
|
});
|
|
@@ -377,6 +422,7 @@ async function commitKnowledgeFileMutations(input) {
|
|
|
377
422
|
transactionRoot: input.transactionRoot,
|
|
378
423
|
purpose: input.purpose,
|
|
379
424
|
mutations: input.mutations,
|
|
425
|
+
...input.pagesDirectory === void 0 ? {} : { pagesDirectory: input.pagesDirectory },
|
|
380
426
|
now: input.now
|
|
381
427
|
});
|
|
382
428
|
if (!transaction) return false;
|
|
@@ -600,7 +646,7 @@ function assertTransactionEntries(transaction) {
|
|
|
600
646
|
const indexes = /* @__PURE__ */ new Set();
|
|
601
647
|
const paths = /* @__PURE__ */ new Set();
|
|
602
648
|
for (const entry of transaction.entries) {
|
|
603
|
-
const normalized = assertKnowledgeMutationPath(entry.path);
|
|
649
|
+
const normalized = assertKnowledgeMutationPath(entry.path, transaction.pagesDirectory);
|
|
604
650
|
if (entry.path !== normalized || indexes.has(entry.index) || paths.has(entry.path)) throw new Error("knowledge file transaction has duplicate or unsafe entries");
|
|
605
651
|
assertHashModePair(entry.path, "before", entry.beforeHash, entry.beforeMode);
|
|
606
652
|
assertHashModePair(entry.path, "after", entry.afterHash, entry.afterMode);
|
|
@@ -624,9 +670,17 @@ function assertHashModePair(path, side, hash, mode) {
|
|
|
624
670
|
if (hash === null !== (mode === void 0)) throw new Error(`knowledge transaction ${side} hash and mode disagree: ${path}`);
|
|
625
671
|
if (mode !== void 0) fileModeSchema.parse(mode);
|
|
626
672
|
}
|
|
627
|
-
|
|
673
|
+
/**
|
|
674
|
+
* Root-relative prefixes a knowledge transaction may write under. The pages
|
|
675
|
+
* directory is the only caller-selected member; the source registry file and
|
|
676
|
+
* the `raw/` evidence tree are package-owned and always present.
|
|
677
|
+
*/
|
|
678
|
+
function knowledgeMutationPathPrefixes(pagesDirectory) {
|
|
679
|
+
return [`${normalizePagesDirectory(pagesDirectory)}/`, "raw/"];
|
|
680
|
+
}
|
|
681
|
+
function assertKnowledgeMutationPath(path, pagesDirectory) {
|
|
628
682
|
const normalized = normalizeTransactionPath(path);
|
|
629
|
-
if (normalized ===
|
|
683
|
+
if (normalized === SOURCE_REGISTRY_PATH || knowledgeMutationPathPrefixes(pagesDirectory).some((prefix) => normalized.startsWith(prefix))) return normalized;
|
|
630
684
|
throw new Error(`knowledge transaction contains an unsupported path: ${path}`);
|
|
631
685
|
}
|
|
632
686
|
function normalizeTransactionPath(path) {
|
|
@@ -1492,6 +1546,23 @@ const KnowledgeGraphEdgeSchema = z.object({
|
|
|
1492
1546
|
weight: z.number(),
|
|
1493
1547
|
reasons: z.array(z.string())
|
|
1494
1548
|
});
|
|
1549
|
+
const KnowledgeRelationSchema = z.object({
|
|
1550
|
+
sourceId: z.string(),
|
|
1551
|
+
targetId: z.string(),
|
|
1552
|
+
predicate: z.string(),
|
|
1553
|
+
weight: z.number().optional(),
|
|
1554
|
+
metadata: z.record(z.string(), z.unknown()).optional()
|
|
1555
|
+
});
|
|
1556
|
+
const KnowledgeRelationNodeSchema = z.object({
|
|
1557
|
+
id: z.string(),
|
|
1558
|
+
kind: z.string(),
|
|
1559
|
+
label: z.string().optional(),
|
|
1560
|
+
metadata: z.record(z.string(), z.unknown()).optional()
|
|
1561
|
+
});
|
|
1562
|
+
const KnowledgeRelationGraphSchema = z.object({
|
|
1563
|
+
nodes: z.array(KnowledgeRelationNodeSchema),
|
|
1564
|
+
edges: z.array(KnowledgeRelationSchema)
|
|
1565
|
+
});
|
|
1495
1566
|
const KnowledgeIndexSchema = z.object({
|
|
1496
1567
|
root: z.string(),
|
|
1497
1568
|
generatedAt: z.string(),
|
|
@@ -1600,13 +1671,7 @@ const KnowledgeBaseCandidateSchema = z.object({
|
|
|
1600
1671
|
]).optional(),
|
|
1601
1672
|
metadata: z.record(z.string(), z.unknown()).optional()
|
|
1602
1673
|
})).optional(),
|
|
1603
|
-
relations: z.array(
|
|
1604
|
-
sourceId: z.string(),
|
|
1605
|
-
targetId: z.string(),
|
|
1606
|
-
predicate: z.string(),
|
|
1607
|
-
weight: z.number().optional(),
|
|
1608
|
-
metadata: z.record(z.string(), z.unknown()).optional()
|
|
1609
|
-
})).optional(),
|
|
1674
|
+
relations: z.array(KnowledgeRelationSchema).optional(),
|
|
1610
1675
|
sourceIds: z.array(z.string()).optional(),
|
|
1611
1676
|
tags: z.array(z.string()).optional(),
|
|
1612
1677
|
metadata: z.record(z.string(), z.unknown()).optional(),
|
|
@@ -1691,7 +1756,7 @@ async function loadKnowledgePages(root, options = {}) {
|
|
|
1691
1756
|
return withKnowledgeRead(root, () => loadKnowledgePagesUnlocked(root, options));
|
|
1692
1757
|
}
|
|
1693
1758
|
async function loadKnowledgePagesUnlocked(root, options) {
|
|
1694
|
-
const pagesDirectory = normalizePagesDirectory(options.pagesDirectory
|
|
1759
|
+
const pagesDirectory = normalizePagesDirectory(options.pagesDirectory);
|
|
1695
1760
|
let files;
|
|
1696
1761
|
try {
|
|
1697
1762
|
files = await listRegularFilesWithinRoot(root, pagesDirectory);
|
|
@@ -1700,36 +1765,48 @@ async function loadKnowledgePagesUnlocked(root, options) {
|
|
|
1700
1765
|
throw error;
|
|
1701
1766
|
}
|
|
1702
1767
|
const pages = [];
|
|
1703
|
-
const pagesPrefix = `${pagesDirectory}/`;
|
|
1704
1768
|
for (const file of files) {
|
|
1705
1769
|
const rel = file.path.replace(/\\/g, "/");
|
|
1706
|
-
if (!rel
|
|
1707
|
-
|
|
1708
|
-
const { frontmatter, body } = parseFrontmatter(file.bytes.toString("utf8"));
|
|
1709
|
-
const title = stringField(frontmatter.title) ?? firstHeading(body) ?? rel.split("/").pop().replace(/\.md$/, "");
|
|
1710
|
-
const sourceIds = arrayField(frontmatter.sources);
|
|
1711
|
-
const tags = arrayField(frontmatter.tags);
|
|
1712
|
-
const cites = idListField(frontmatter.cites);
|
|
1713
|
-
const contradicts = idListField(frontmatter.contradicts);
|
|
1714
|
-
const invalidation = KnowledgePageInvalidationSchema.safeParse(frontmatter.invalidation);
|
|
1715
|
-
const pageRelativePath = rel.startsWith(pagesPrefix) ? rel.slice(pagesPrefix.length) : rel;
|
|
1716
|
-
pages.push({
|
|
1717
|
-
id: stringField(frontmatter.id) ?? slugify(pageRelativePath.replace(/\.md$/, "")),
|
|
1718
|
-
path: rel,
|
|
1719
|
-
title,
|
|
1720
|
-
text: body,
|
|
1721
|
-
frontmatter,
|
|
1722
|
-
sourceIds,
|
|
1723
|
-
tags,
|
|
1724
|
-
outLinks: extractWikilinks(body).map(normalizeLinkTarget),
|
|
1725
|
-
...cites.length > 0 ? { cites } : {},
|
|
1726
|
-
...contradicts.length > 0 ? { contradicts } : {},
|
|
1727
|
-
...invalidation.success ? { invalidation: invalidation.data } : {}
|
|
1728
|
-
});
|
|
1770
|
+
if (!isKnowledgePagePath(rel)) continue;
|
|
1771
|
+
pages.push(knowledgePageFromMarkdown(rel, file.bytes.toString("utf8"), pagesDirectory));
|
|
1729
1772
|
}
|
|
1730
1773
|
pages.sort((a, b) => a.path.localeCompare(b.path));
|
|
1731
1774
|
return pages;
|
|
1732
1775
|
}
|
|
1776
|
+
/** True when a root-relative path names authored Markdown rather than scaffolding. */
|
|
1777
|
+
function isKnowledgePagePath(path) {
|
|
1778
|
+
const normalized = path.replace(/\\/g, "/");
|
|
1779
|
+
return normalized.endsWith(".md") && !isScaffoldPath(normalized);
|
|
1780
|
+
}
|
|
1781
|
+
/**
|
|
1782
|
+
* Build a page from the bytes of one Markdown file.
|
|
1783
|
+
*
|
|
1784
|
+
* The reader and every gate that inspects a page before it is written share
|
|
1785
|
+
* this constructor, so what a gate judges is exactly what the store loads back.
|
|
1786
|
+
*/
|
|
1787
|
+
function knowledgePageFromMarkdown(path, content, pagesDirectory) {
|
|
1788
|
+
const rel = path.replace(/\\/g, "/");
|
|
1789
|
+
const pagesPrefix = `${normalizePagesDirectory(pagesDirectory)}/`;
|
|
1790
|
+
const { frontmatter, body } = parseFrontmatter(content);
|
|
1791
|
+
const title = stringField(frontmatter.title) ?? firstHeading(body) ?? rel.split("/").pop().replace(/\.md$/, "");
|
|
1792
|
+
const cites = idListField(frontmatter.cites);
|
|
1793
|
+
const contradicts = idListField(frontmatter.contradicts);
|
|
1794
|
+
const invalidation = KnowledgePageInvalidationSchema.safeParse(frontmatter.invalidation);
|
|
1795
|
+
const pageRelativePath = rel.startsWith(pagesPrefix) ? rel.slice(pagesPrefix.length) : rel;
|
|
1796
|
+
return {
|
|
1797
|
+
id: stringField(frontmatter.id) ?? slugify(pageRelativePath.replace(/\.md$/, "")),
|
|
1798
|
+
path: rel,
|
|
1799
|
+
title,
|
|
1800
|
+
text: body,
|
|
1801
|
+
frontmatter,
|
|
1802
|
+
sourceIds: arrayField(frontmatter.sources),
|
|
1803
|
+
tags: arrayField(frontmatter.tags),
|
|
1804
|
+
outLinks: extractWikilinks(body).map(normalizeLinkTarget),
|
|
1805
|
+
...cites.length > 0 ? { cites } : {},
|
|
1806
|
+
...contradicts.length > 0 ? { contradicts } : {},
|
|
1807
|
+
...invalidation.success ? { invalidation: invalidation.data } : {}
|
|
1808
|
+
};
|
|
1809
|
+
}
|
|
1733
1810
|
async function writeJson(path, value) {
|
|
1734
1811
|
await writeJsonDurable(path, value);
|
|
1735
1812
|
}
|
|
@@ -1755,11 +1832,6 @@ async function knowledgeBaseInitialized(layout) {
|
|
|
1755
1832
|
throw error;
|
|
1756
1833
|
}
|
|
1757
1834
|
}
|
|
1758
|
-
function normalizePagesDirectory(value) {
|
|
1759
|
-
const normalized = value.replace(/\\/g, "/").replace(/^\.\//, "").replace(/\/+$/, "");
|
|
1760
|
-
if (normalized.length === 0 || normalized === ".") throw new Error("pagesDirectory must name a root-relative directory");
|
|
1761
|
-
return normalized;
|
|
1762
|
-
}
|
|
1763
1835
|
function stringField(value) {
|
|
1764
1836
|
return typeof value === "string" && value.trim() !== "" ? value.trim() : void 0;
|
|
1765
1837
|
}
|
|
@@ -1927,7 +1999,18 @@ function isPageOrigin(value) {
|
|
|
1927
1999
|
}
|
|
1928
2000
|
//#endregion
|
|
1929
2001
|
//#region src/graph.ts
|
|
1930
|
-
|
|
2002
|
+
/**
|
|
2003
|
+
* The labeled relations between pages, one per `(sourceId, targetId, predicate)`.
|
|
2004
|
+
*
|
|
2005
|
+
* `wikilink`, `citation`, and `contradicts` follow an authored link, `cites`
|
|
2006
|
+
* entry, or `contradicts` entry to the one page it resolves to; a missing,
|
|
2007
|
+
* ambiguous, or self-referencing target emits nothing. `shared-source` joins
|
|
2008
|
+
* every pair of pages that registers a common source, in page order, weighted
|
|
2009
|
+
* 0.5 per shared source id, with the shared ids in `metadata.sourceIds`. It is
|
|
2010
|
+
* implicit, so it is listed after the authored relations and never counts
|
|
2011
|
+
* toward a page's degree in `buildKnowledgeGraph`.
|
|
2012
|
+
*/
|
|
2013
|
+
function knowledgePageRelations(pages) {
|
|
1931
2014
|
const bySlug = /* @__PURE__ */ new Map();
|
|
1932
2015
|
const byId = /* @__PURE__ */ new Map();
|
|
1933
2016
|
for (const page of pages) {
|
|
@@ -1936,26 +2019,93 @@ function buildKnowledgeGraph(pages) {
|
|
|
1936
2019
|
bySlug.set(normalizeLinkTarget(page.title), page);
|
|
1937
2020
|
bySlug.set(normalizeLinkTarget(page.path.split("/").pop().replace(/\.md$/, "")), page);
|
|
1938
2021
|
}
|
|
2022
|
+
const authored = /* @__PURE__ */ new Map();
|
|
2023
|
+
const count = (source, target, predicate) => {
|
|
2024
|
+
const key = JSON.stringify([
|
|
2025
|
+
source.id,
|
|
2026
|
+
target.id,
|
|
2027
|
+
predicate
|
|
2028
|
+
]);
|
|
2029
|
+
const relation = authored.get(key);
|
|
2030
|
+
if (relation) relation.weight += 1;
|
|
2031
|
+
else authored.set(key, {
|
|
2032
|
+
sourceId: source.id,
|
|
2033
|
+
targetId: target.id,
|
|
2034
|
+
predicate,
|
|
2035
|
+
weight: 1
|
|
2036
|
+
});
|
|
2037
|
+
};
|
|
2038
|
+
const uniqueTarget = (id) => {
|
|
2039
|
+
const targets = byId.get(id) ?? [];
|
|
2040
|
+
return targets.length === 1 ? targets[0] : void 0;
|
|
2041
|
+
};
|
|
2042
|
+
for (const page of pages) {
|
|
2043
|
+
for (const raw of page.outLinks) {
|
|
2044
|
+
const target = bySlug.get(normalizeLinkTarget(raw));
|
|
2045
|
+
if (!target || target.id === page.id) continue;
|
|
2046
|
+
count(page, target, "wikilink");
|
|
2047
|
+
}
|
|
2048
|
+
for (const persisted of page.cites ?? []) {
|
|
2049
|
+
const target = uniqueTarget(citedPageId(persisted));
|
|
2050
|
+
if (!target || target.id === page.id) continue;
|
|
2051
|
+
count(page, target, "citation");
|
|
2052
|
+
}
|
|
2053
|
+
for (const contradicted of page.contradicts ?? []) {
|
|
2054
|
+
const target = uniqueTarget(contradicted);
|
|
2055
|
+
if (!target || target.id === page.id) continue;
|
|
2056
|
+
count(page, target, "contradicts");
|
|
2057
|
+
}
|
|
2058
|
+
}
|
|
2059
|
+
const relations = [...authored.values()];
|
|
2060
|
+
for (let i = 0; i < pages.length; i++) for (let j = i + 1; j < pages.length; j++) {
|
|
2061
|
+
const a = pages[i];
|
|
2062
|
+
const b = pages[j];
|
|
2063
|
+
const overlap = a.sourceIds.filter((source) => b.sourceIds.includes(source));
|
|
2064
|
+
if (overlap.length === 0) continue;
|
|
2065
|
+
relations.push({
|
|
2066
|
+
sourceId: a.id,
|
|
2067
|
+
targetId: b.id,
|
|
2068
|
+
predicate: "shared-source",
|
|
2069
|
+
weight: overlap.length * .5,
|
|
2070
|
+
metadata: { sourceIds: overlap }
|
|
2071
|
+
});
|
|
2072
|
+
}
|
|
2073
|
+
return relations;
|
|
2074
|
+
}
|
|
2075
|
+
/**
|
|
2076
|
+
* The weighted page graph: `knowledgePageRelations` collapsed to one edge per
|
|
2077
|
+
* ordered page pair, with the predicates merged into `reasons` and the weights
|
|
2078
|
+
* summed. `contradicts` stays out of this projection; read it from the relation
|
|
2079
|
+
* list. Node degrees count the authored `wikilink` and `citation` occurrences.
|
|
2080
|
+
*/
|
|
2081
|
+
function buildKnowledgeGraph(pages) {
|
|
1939
2082
|
const incoming = /* @__PURE__ */ new Map();
|
|
1940
2083
|
const outgoing = /* @__PURE__ */ new Map();
|
|
1941
|
-
const edgesByKey = /* @__PURE__ */ new Map();
|
|
1942
2084
|
for (const page of pages) {
|
|
1943
2085
|
outgoing.set(page.id, 0);
|
|
1944
2086
|
incoming.set(page.id, 0);
|
|
1945
2087
|
}
|
|
1946
|
-
|
|
1947
|
-
|
|
1948
|
-
|
|
1949
|
-
|
|
1950
|
-
|
|
2088
|
+
const edgesByKey = /* @__PURE__ */ new Map();
|
|
2089
|
+
for (const relation of knowledgePageRelations(pages)) {
|
|
2090
|
+
if (relation.predicate === "contradicts") continue;
|
|
2091
|
+
const key = `${relation.sourceId}->${relation.targetId}`;
|
|
2092
|
+
let edge = edgesByKey.get(key);
|
|
2093
|
+
if (!edge) {
|
|
2094
|
+
edge = {
|
|
2095
|
+
source: relation.sourceId,
|
|
2096
|
+
target: relation.targetId,
|
|
2097
|
+
weight: 0,
|
|
2098
|
+
reasons: []
|
|
2099
|
+
};
|
|
2100
|
+
edgesByKey.set(key, edge);
|
|
1951
2101
|
}
|
|
1952
|
-
|
|
1953
|
-
|
|
1954
|
-
|
|
1955
|
-
|
|
2102
|
+
edge.weight += relation.weight;
|
|
2103
|
+
if (!edge.reasons.includes(relation.predicate)) edge.reasons.push(relation.predicate);
|
|
2104
|
+
if (relation.predicate === "wikilink" || relation.predicate === "citation") {
|
|
2105
|
+
outgoing.set(relation.sourceId, (outgoing.get(relation.sourceId) ?? 0) + relation.weight);
|
|
2106
|
+
incoming.set(relation.targetId, (incoming.get(relation.targetId) ?? 0) + relation.weight);
|
|
1956
2107
|
}
|
|
1957
2108
|
}
|
|
1958
|
-
addSourceOverlapEdges(pages, edgesByKey);
|
|
1959
2109
|
return {
|
|
1960
2110
|
nodes: pages.map((page) => ({
|
|
1961
2111
|
id: page.id,
|
|
@@ -1982,40 +2132,6 @@ function citedPageId(persisted) {
|
|
|
1982
2132
|
return persisted;
|
|
1983
2133
|
}
|
|
1984
2134
|
}
|
|
1985
|
-
function addDirectedEdge(source, target, reason, edges, incoming, outgoing) {
|
|
1986
|
-
const key = `${source.id}->${target.id}`;
|
|
1987
|
-
const edge = edges.get(key);
|
|
1988
|
-
if (edge) {
|
|
1989
|
-
edge.weight += 1;
|
|
1990
|
-
if (!edge.reasons.includes(reason)) edge.reasons.push(reason);
|
|
1991
|
-
} else edges.set(key, {
|
|
1992
|
-
source: source.id,
|
|
1993
|
-
target: target.id,
|
|
1994
|
-
weight: 1,
|
|
1995
|
-
reasons: [reason]
|
|
1996
|
-
});
|
|
1997
|
-
outgoing.set(source.id, (outgoing.get(source.id) ?? 0) + 1);
|
|
1998
|
-
incoming.set(target.id, (incoming.get(target.id) ?? 0) + 1);
|
|
1999
|
-
}
|
|
2000
|
-
function addSourceOverlapEdges(pages, edges) {
|
|
2001
|
-
for (let i = 0; i < pages.length; i++) for (let j = i + 1; j < pages.length; j++) {
|
|
2002
|
-
const a = pages[i];
|
|
2003
|
-
const b = pages[j];
|
|
2004
|
-
const overlap = a.sourceIds.filter((source) => b.sourceIds.includes(source));
|
|
2005
|
-
if (overlap.length === 0) continue;
|
|
2006
|
-
const key = `${a.id}->${b.id}`;
|
|
2007
|
-
const edge = edges.get(key);
|
|
2008
|
-
if (edge) {
|
|
2009
|
-
edge.weight += overlap.length * .5;
|
|
2010
|
-
if (!edge.reasons.includes("shared-source")) edge.reasons.push("shared-source");
|
|
2011
|
-
} else edges.set(key, {
|
|
2012
|
-
source: a.id,
|
|
2013
|
-
target: b.id,
|
|
2014
|
-
weight: overlap.length * .5,
|
|
2015
|
-
reasons: ["shared-source"]
|
|
2016
|
-
});
|
|
2017
|
-
}
|
|
2018
|
-
}
|
|
2019
2135
|
//#endregion
|
|
2020
2136
|
//#region src/kb-store.ts
|
|
2021
2137
|
/**
|
|
@@ -2546,11 +2662,11 @@ function textPreview(fileName, bytes) {
|
|
|
2546
2662
|
}
|
|
2547
2663
|
//#endregion
|
|
2548
2664
|
//#region src/indexer.ts
|
|
2549
|
-
async function buildKnowledgeIndex(root) {
|
|
2550
|
-
return withKnowledgeRead(root, () => buildKnowledgeIndexUnlocked(root));
|
|
2665
|
+
async function buildKnowledgeIndex(root, options = {}) {
|
|
2666
|
+
return withKnowledgeRead(root, () => buildKnowledgeIndexUnlocked(root, options));
|
|
2551
2667
|
}
|
|
2552
|
-
async function buildKnowledgeIndexUnlocked(root) {
|
|
2553
|
-
const [pages, sourceRegistry] = await Promise.all([loadKnowledgePages(root), loadSourceRegistry(root)]);
|
|
2668
|
+
async function buildKnowledgeIndexUnlocked(root, options) {
|
|
2669
|
+
const [pages, sourceRegistry] = await Promise.all([loadKnowledgePages(root, options), loadSourceRegistry(root)]);
|
|
2554
2670
|
return {
|
|
2555
2671
|
root,
|
|
2556
2672
|
generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -2568,9 +2684,9 @@ async function buildKnowledgeIndexUnlocked(root) {
|
|
|
2568
2684
|
* a store-based reader saw none of the indexer's work. One writer now, and it
|
|
2569
2685
|
* validates through `KnowledgeIndexSchema` on the way out.
|
|
2570
2686
|
*/
|
|
2571
|
-
async function writeKnowledgeIndex(root) {
|
|
2687
|
+
async function writeKnowledgeIndex(root, options = {}) {
|
|
2572
2688
|
return withKnowledgeMutation(root, async () => {
|
|
2573
|
-
const index = await buildKnowledgeIndexUnlocked(root);
|
|
2689
|
+
const index = await buildKnowledgeIndexUnlocked(root, options);
|
|
2574
2690
|
await new FileSystemKbStore({ root }).putIndex(index);
|
|
2575
2691
|
return index;
|
|
2576
2692
|
});
|
|
@@ -2765,9 +2881,11 @@ function lintKnowledgeIndex(index) {
|
|
|
2765
2881
|
const sourceIds = new Set(index.sources.map((source) => source.id));
|
|
2766
2882
|
const anchorIds = new Map(index.sources.map((source) => [source.id, new Set((source.anchors ?? []).map((anchor) => anchor.id))]));
|
|
2767
2883
|
const pageIds = /* @__PURE__ */ new Map();
|
|
2884
|
+
const invalidatedIds = /* @__PURE__ */ new Set();
|
|
2768
2885
|
const sourceHashes = /* @__PURE__ */ new Map();
|
|
2769
2886
|
for (const page of index.pages) {
|
|
2770
2887
|
pageIds.set(page.id, [...pageIds.get(page.id) ?? [], page.path]);
|
|
2888
|
+
if (page.invalidation !== void 0) invalidatedIds.add(page.id);
|
|
2771
2889
|
byTarget.add(normalizeLinkTarget(page.id));
|
|
2772
2890
|
byTarget.add(normalizeLinkTarget(page.title));
|
|
2773
2891
|
byTarget.add(normalizeLinkTarget(page.path.split("/").pop().replace(/\.md$/, "")));
|
|
@@ -2829,6 +2947,7 @@ function lintKnowledgeIndex(index) {
|
|
|
2829
2947
|
findings.push(...lintPageEvidence(page));
|
|
2830
2948
|
findings.push(...lintPageContradictions(page, pageIds));
|
|
2831
2949
|
findings.push(...lintPageInvalidation(page));
|
|
2950
|
+
findings.push(...lintPageInvalidatedCitations(page, pageIds, invalidatedIds));
|
|
2832
2951
|
}
|
|
2833
2952
|
for (const [title, paths] of titles) if (title && paths.length > 1) findings.push({
|
|
2834
2953
|
type: "duplicate-title",
|
|
@@ -2915,6 +3034,24 @@ function lintPageContradictions(page, pageIds) {
|
|
|
2915
3034
|
}
|
|
2916
3035
|
return findings;
|
|
2917
3036
|
}
|
|
3037
|
+
/**
|
|
3038
|
+
* A citation into a page its own evidence refuted.
|
|
3039
|
+
*
|
|
3040
|
+
* The verdict lives on the cited page, so a reader arriving through the
|
|
3041
|
+
* citation never meets it. Ambiguous ids are left to the citation audit, which
|
|
3042
|
+
* owns that verdict.
|
|
3043
|
+
*/
|
|
3044
|
+
function lintPageInvalidatedCitations(page, pageIds, invalidatedIds) {
|
|
3045
|
+
const targetIds = [...new Set((page.cites ?? []).map((persisted) => parseKnowledgeCitationReference(persisted).pageId))].filter((targetId) => targetId !== page.id && pageIds.get(targetId)?.length === 1 && invalidatedIds.has(targetId)).sort();
|
|
3046
|
+
if (targetIds.length === 0) return [];
|
|
3047
|
+
return [{
|
|
3048
|
+
type: "cites-invalidated",
|
|
3049
|
+
severity: "warning",
|
|
3050
|
+
page: page.path,
|
|
3051
|
+
message: `Page cites invalidated ${targetIds.length === 1 ? "page" : "pages"} ${targetIds.join(", ")}.`,
|
|
3052
|
+
metadata: { targetIds }
|
|
3053
|
+
}];
|
|
3054
|
+
}
|
|
2918
3055
|
function lintPageInvalidation(page) {
|
|
2919
3056
|
if (page.frontmatter.invalidation === void 0) return [];
|
|
2920
3057
|
const parsed = KnowledgePageInvalidationSchema.safeParse(page.frontmatter.invalidation);
|
|
@@ -2973,6 +3110,420 @@ function validateKnowledgeIndex(index, options = {}) {
|
|
|
2973
3110
|
};
|
|
2974
3111
|
}
|
|
2975
3112
|
//#endregion
|
|
3113
|
+
//#region src/rag-eval/near-duplicates.ts
|
|
3114
|
+
/**
|
|
3115
|
+
* Detect exact and near-duplicate pages without a model, embedding service, or clock.
|
|
3116
|
+
*
|
|
3117
|
+
* The detector uses word shingles when prose has enough tokens and Unicode
|
|
3118
|
+
* character shingles otherwise. An inverted index proposes candidate pairs;
|
|
3119
|
+
* high-document-frequency shingles are ignored so common boilerplate cannot
|
|
3120
|
+
* make candidate generation quadratic. Exact normalized copies are always
|
|
3121
|
+
* proposed before ordinary shingle candidates, even when every shingle is
|
|
3122
|
+
* common. Every limit and sort order is explicit so the same corpus produces
|
|
3123
|
+
* the same report on every run.
|
|
3124
|
+
*/
|
|
3125
|
+
function detectNearDuplicatePages(pages, options = {}) {
|
|
3126
|
+
const threshold = finiteUnitInterval(options.threshold ?? .82, "threshold");
|
|
3127
|
+
const minCharacters = nonNegativeInteger(options.minCharacters ?? 80, "minCharacters");
|
|
3128
|
+
const wordShingleSize = positiveInteger(options.wordShingleSize ?? 5, "wordShingleSize");
|
|
3129
|
+
const characterShingleSize = positiveInteger(options.characterShingleSize ?? 16, "characterShingleSize");
|
|
3130
|
+
const maxDocumentFrequency = positiveInteger(options.maxDocumentFrequency ?? 64, "maxDocumentFrequency");
|
|
3131
|
+
const maxCandidatePairs = positiveInteger(options.maxCandidatePairs ?? 25e4, "maxCandidatePairs");
|
|
3132
|
+
const maxReportedPairs = nonNegativeInteger(options.maxReportedPairs ?? 1e3, "maxReportedPairs");
|
|
3133
|
+
const prepared = pages.map((page) => preparePage(page, wordShingleSize, characterShingleSize)).filter((item) => item !== null && item.normalized.length >= minCharacters).sort((left, right) => left.page.path.localeCompare(right.page.path));
|
|
3134
|
+
const candidateKeys = /* @__PURE__ */ new Set();
|
|
3135
|
+
const exactGroups = /* @__PURE__ */ new Map();
|
|
3136
|
+
const byShingle = /* @__PURE__ */ new Map();
|
|
3137
|
+
for (const [index, item] of prepared.entries()) {
|
|
3138
|
+
exactGroups.set(item.normalized, [...exactGroups.get(item.normalized) ?? [], index]);
|
|
3139
|
+
for (const shingle of item.shingles) byShingle.set(shingle, [...byShingle.get(shingle) ?? [], index]);
|
|
3140
|
+
}
|
|
3141
|
+
let truncated = false;
|
|
3142
|
+
const addPair = (left, right) => {
|
|
3143
|
+
if (left === right) return true;
|
|
3144
|
+
const key = pairKey(Math.min(left, right), Math.max(left, right));
|
|
3145
|
+
if (candidateKeys.has(key)) return true;
|
|
3146
|
+
if (candidateKeys.size >= maxCandidatePairs) {
|
|
3147
|
+
truncated = true;
|
|
3148
|
+
return false;
|
|
3149
|
+
}
|
|
3150
|
+
candidateKeys.add(key);
|
|
3151
|
+
return true;
|
|
3152
|
+
};
|
|
3153
|
+
for (const normalized of [...exactGroups.keys()].sort()) if (!addGroupPairs(exactGroups.get(normalized), addPair)) break;
|
|
3154
|
+
if (!truncated) for (const shingle of [...byShingle.keys()].sort()) {
|
|
3155
|
+
const group = byShingle.get(shingle);
|
|
3156
|
+
if (group.length > maxDocumentFrequency) continue;
|
|
3157
|
+
if (!addGroupPairs(group, addPair)) break;
|
|
3158
|
+
}
|
|
3159
|
+
const duplicatePageIndices = /* @__PURE__ */ new Set();
|
|
3160
|
+
const pairs = [];
|
|
3161
|
+
let duplicatePairCount = 0;
|
|
3162
|
+
let comparedPairCount = 0;
|
|
3163
|
+
for (const key of [...candidateKeys].sort(pairKeyOrder)) {
|
|
3164
|
+
const [leftIndex, rightIndex] = parsePairKey(key);
|
|
3165
|
+
const left = prepared[leftIndex];
|
|
3166
|
+
const right = prepared[rightIndex];
|
|
3167
|
+
comparedPairCount += 1;
|
|
3168
|
+
const exact = left.normalized === right.normalized;
|
|
3169
|
+
const overlap = jaccard(left.shingles, right.shingles);
|
|
3170
|
+
if (!exact && overlap.similarity < threshold) continue;
|
|
3171
|
+
duplicatePairCount += 1;
|
|
3172
|
+
duplicatePageIndices.add(leftIndex);
|
|
3173
|
+
duplicatePageIndices.add(rightIndex);
|
|
3174
|
+
if (pairs.length >= maxReportedPairs) {
|
|
3175
|
+
truncated = true;
|
|
3176
|
+
continue;
|
|
3177
|
+
}
|
|
3178
|
+
pairs.push({
|
|
3179
|
+
leftPageId: left.page.id,
|
|
3180
|
+
leftPath: left.page.path,
|
|
3181
|
+
rightPageId: right.page.id,
|
|
3182
|
+
rightPath: right.page.path,
|
|
3183
|
+
similarity: exact ? 1 : round(overlap.similarity),
|
|
3184
|
+
intersectionSize: overlap.intersectionSize,
|
|
3185
|
+
unionSize: overlap.unionSize,
|
|
3186
|
+
exact
|
|
3187
|
+
});
|
|
3188
|
+
}
|
|
3189
|
+
pairs.sort((left, right) => right.similarity - left.similarity || left.leftPath.localeCompare(right.leftPath) || left.rightPath.localeCompare(right.rightPath));
|
|
3190
|
+
return {
|
|
3191
|
+
algorithm: "deterministic-shingle-jaccard-v1",
|
|
3192
|
+
threshold,
|
|
3193
|
+
pageCount: pages.length,
|
|
3194
|
+
eligiblePageCount: prepared.length,
|
|
3195
|
+
candidatePairCount: candidateKeys.size,
|
|
3196
|
+
comparedPairCount,
|
|
3197
|
+
duplicatePairCount,
|
|
3198
|
+
duplicatePageCount: duplicatePageIndices.size,
|
|
3199
|
+
duplicatePageRate: prepared.length === 0 ? 0 : round(duplicatePageIndices.size / prepared.length),
|
|
3200
|
+
truncated,
|
|
3201
|
+
pairs
|
|
3202
|
+
};
|
|
3203
|
+
}
|
|
3204
|
+
function preparePage(page, wordShingleSize, characterShingleSize) {
|
|
3205
|
+
const normalized = normalizePageText(`${page.title}\n${page.text}`);
|
|
3206
|
+
if (normalized.length === 0) return null;
|
|
3207
|
+
const words = normalized.match(/[\p{L}\p{N}]+/gu) ?? [];
|
|
3208
|
+
const shingles = words.length >= wordShingleSize + 2 ? sequenceShingles(words, wordShingleSize) : characterShingles(normalized.replace(/\s+/g, ""), characterShingleSize);
|
|
3209
|
+
if (shingles.size === 0) shingles.add(normalized);
|
|
3210
|
+
return {
|
|
3211
|
+
page,
|
|
3212
|
+
normalized,
|
|
3213
|
+
shingles
|
|
3214
|
+
};
|
|
3215
|
+
}
|
|
3216
|
+
function normalizePageText(value) {
|
|
3217
|
+
return value.normalize("NFKC").toLowerCase().replace(/https?:\/\/\S+/g, "<url>").replace(/\s+/g, " ").trim();
|
|
3218
|
+
}
|
|
3219
|
+
function sequenceShingles(items, width) {
|
|
3220
|
+
const out = /* @__PURE__ */ new Set();
|
|
3221
|
+
if (items.length < width) return out;
|
|
3222
|
+
for (let index = 0; index <= items.length - width; index += 1) out.add(items.slice(index, index + width).join(""));
|
|
3223
|
+
return out;
|
|
3224
|
+
}
|
|
3225
|
+
function characterShingles(value, width) {
|
|
3226
|
+
const chars = [...value];
|
|
3227
|
+
const out = /* @__PURE__ */ new Set();
|
|
3228
|
+
if (chars.length < width) return out;
|
|
3229
|
+
for (let index = 0; index <= chars.length - width; index += 1) out.add(chars.slice(index, index + width).join(""));
|
|
3230
|
+
return out;
|
|
3231
|
+
}
|
|
3232
|
+
function addGroupPairs(group, add) {
|
|
3233
|
+
for (let left = 0; left < group.length; left += 1) for (let right = left + 1; right < group.length; right += 1) if (!add(group[left], group[right])) return false;
|
|
3234
|
+
return true;
|
|
3235
|
+
}
|
|
3236
|
+
function jaccard(left, right) {
|
|
3237
|
+
const [small, large] = left.size <= right.size ? [left, right] : [right, left];
|
|
3238
|
+
let intersectionSize = 0;
|
|
3239
|
+
for (const item of small) if (large.has(item)) intersectionSize += 1;
|
|
3240
|
+
const unionSize = left.size + right.size - intersectionSize;
|
|
3241
|
+
return {
|
|
3242
|
+
similarity: unionSize === 0 ? 1 : intersectionSize / unionSize,
|
|
3243
|
+
intersectionSize,
|
|
3244
|
+
unionSize
|
|
3245
|
+
};
|
|
3246
|
+
}
|
|
3247
|
+
function pairKey(left, right) {
|
|
3248
|
+
return `${left}:${right}`;
|
|
3249
|
+
}
|
|
3250
|
+
function parsePairKey(value) {
|
|
3251
|
+
const [left, right] = value.split(":");
|
|
3252
|
+
return [Number(left), Number(right)];
|
|
3253
|
+
}
|
|
3254
|
+
function pairKeyOrder(left, right) {
|
|
3255
|
+
const [leftA, leftB] = parsePairKey(left);
|
|
3256
|
+
const [rightA, rightB] = parsePairKey(right);
|
|
3257
|
+
return leftA - rightA || leftB - rightB;
|
|
3258
|
+
}
|
|
3259
|
+
function finiteUnitInterval(value, name) {
|
|
3260
|
+
if (!Number.isFinite(value) || value < 0 || value > 1) throw new Error(`${name} must be a finite number in [0, 1]`);
|
|
3261
|
+
return value;
|
|
3262
|
+
}
|
|
3263
|
+
function positiveInteger(value, name) {
|
|
3264
|
+
if (!Number.isInteger(value) || value < 1) throw new Error(`${name} must be a positive integer`);
|
|
3265
|
+
return value;
|
|
3266
|
+
}
|
|
3267
|
+
function nonNegativeInteger(value, name) {
|
|
3268
|
+
if (!Number.isInteger(value) || value < 0) throw new Error(`${name} must be a non-negative integer`);
|
|
3269
|
+
return value;
|
|
3270
|
+
}
|
|
3271
|
+
function round(value) {
|
|
3272
|
+
return Math.round(value * 1e6) / 1e6;
|
|
3273
|
+
}
|
|
3274
|
+
//#endregion
|
|
3275
|
+
//#region src/run-scoped.ts
|
|
3276
|
+
/**
|
|
3277
|
+
* Run-scoped knowledge stores with lineage-chain reads.
|
|
3278
|
+
*
|
|
3279
|
+
* One store exists per run, physically isolated, with inheritance only through
|
|
3280
|
+
* declared ancestry. Reads expose the current store, every declared ancestor,
|
|
3281
|
+
* and an optional curated shared store with an explicit origin label.
|
|
3282
|
+
*/
|
|
3283
|
+
/**
|
|
3284
|
+
* Present plain pages as a visibility chain of one origin.
|
|
3285
|
+
*
|
|
3286
|
+
* A store read directly is the `here` origin of a chain with no ancestry, so
|
|
3287
|
+
* the chain-shaped APIs — citation resolution, the write intake gate,
|
|
3288
|
+
* invalidation propagation — take one page shape whether or not the caller
|
|
3289
|
+
* runs run-scoped stores.
|
|
3290
|
+
*/
|
|
3291
|
+
function originatedPages(pages, origin = "here") {
|
|
3292
|
+
return pages.map((page) => ({
|
|
3293
|
+
page,
|
|
3294
|
+
origin
|
|
3295
|
+
}));
|
|
3296
|
+
}
|
|
3297
|
+
const RUN_LINEAGE_BASENAME = "lineage.json";
|
|
3298
|
+
/** Ancestry beyond this bound is invalid durable state. */
|
|
3299
|
+
const MAX_LINEAGE_HOPS = 64;
|
|
3300
|
+
function createRunScopedStores(options) {
|
|
3301
|
+
if (!options || typeof options !== "object") throw new TypeError("createRunScopedStores options are required");
|
|
3302
|
+
if (typeof options.root !== "string" || options.root.trim().length === 0) throw new TypeError("createRunScopedStores root must be a non-empty string");
|
|
3303
|
+
if (options.runStorePath !== void 0 && typeof options.runStorePath !== "function") throw new TypeError("createRunScopedStores runStorePath must be a function when present");
|
|
3304
|
+
if (options.lineageAuthority !== void 0) validateLineageAuthority(options.lineageAuthority);
|
|
3305
|
+
const pagesDirectory = normalizePagesDirectory(options.pagesDirectory);
|
|
3306
|
+
const storePath = options.runStorePath ?? ((runId) => join(options.root, runId, "knowledge-base"));
|
|
3307
|
+
const internalAuthority = createFileRunLineageAuthority(storePath);
|
|
3308
|
+
const authority = options.lineageAuthority ?? internalAuthority;
|
|
3309
|
+
const resolveLineage = async (runId) => {
|
|
3310
|
+
assertRunId(runId);
|
|
3311
|
+
const chain = [];
|
|
3312
|
+
const seen = /* @__PURE__ */ new Set([runId]);
|
|
3313
|
+
let current = runId;
|
|
3314
|
+
for (let hop = 0; hop <= MAX_LINEAGE_HOPS; hop += 1) {
|
|
3315
|
+
const parent = await authority.parentOf(current);
|
|
3316
|
+
if (parent === null) return chain;
|
|
3317
|
+
assertRunId(parent, `parent of '${current}'`);
|
|
3318
|
+
if (seen.has(parent)) throw new Error(`run lineage cycle: ${parent} is its own ancestor (via ${runId})`);
|
|
3319
|
+
seen.add(parent);
|
|
3320
|
+
chain.push(parent);
|
|
3321
|
+
current = parent;
|
|
3322
|
+
}
|
|
3323
|
+
throw new Error(`run lineage for '${runId}' exceeds ${MAX_LINEAGE_HOPS} ancestors`);
|
|
3324
|
+
};
|
|
3325
|
+
return {
|
|
3326
|
+
storePath(runId) {
|
|
3327
|
+
assertRunId(runId);
|
|
3328
|
+
return storePath(runId);
|
|
3329
|
+
},
|
|
3330
|
+
async init(runId, initOptions = {}) {
|
|
3331
|
+
assertRunId(runId);
|
|
3332
|
+
const parentRunId = initOptions.parentRunId ?? null;
|
|
3333
|
+
if (parentRunId !== null) assertRunId(parentRunId, `parent of '${runId}'`);
|
|
3334
|
+
if (!authority.record) {
|
|
3335
|
+
const existingParent = await authority.parentOf(runId);
|
|
3336
|
+
if (existingParent !== parentRunId) throw new Error(`external lineage authority disagrees for '${runId}': expected ${renderParent(parentRunId)}, observed ${renderParent(existingParent)}`);
|
|
3337
|
+
}
|
|
3338
|
+
const layout = await initKnowledgeBase(storePath(runId));
|
|
3339
|
+
if (authority.record) await authority.record(Object.freeze({
|
|
3340
|
+
runId,
|
|
3341
|
+
parentRunId,
|
|
3342
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
3343
|
+
}));
|
|
3344
|
+
return layout;
|
|
3345
|
+
},
|
|
3346
|
+
lineage: resolveLineage,
|
|
3347
|
+
async loadChain(runId) {
|
|
3348
|
+
assertRunId(runId);
|
|
3349
|
+
const out = [];
|
|
3350
|
+
const readInto = async (root, origin) => {
|
|
3351
|
+
let pages;
|
|
3352
|
+
try {
|
|
3353
|
+
pages = await loadKnowledgePages(root, { pagesDirectory });
|
|
3354
|
+
} catch (error) {
|
|
3355
|
+
if (isMissingFile(error)) return;
|
|
3356
|
+
throw error;
|
|
3357
|
+
}
|
|
3358
|
+
for (const page of pages) out.push({
|
|
3359
|
+
page,
|
|
3360
|
+
origin
|
|
3361
|
+
});
|
|
3362
|
+
};
|
|
3363
|
+
await readInto(storePath(runId), "here");
|
|
3364
|
+
for (const ancestor of await resolveLineage(runId)) await readInto(storePath(ancestor), `inherited:${ancestor}`);
|
|
3365
|
+
if (options.sharedRoot) await readInto(options.sharedRoot, "shared");
|
|
3366
|
+
return out;
|
|
3367
|
+
}
|
|
3368
|
+
};
|
|
3369
|
+
}
|
|
3370
|
+
/** File-backed authority used when the product has no separate run manifest. */
|
|
3371
|
+
function createFileRunLineageAuthority(runStorePath) {
|
|
3372
|
+
if (typeof runStorePath !== "function") throw new TypeError("createFileRunLineageAuthority requires a runStorePath function");
|
|
3373
|
+
const readRecord = async (runId) => {
|
|
3374
|
+
assertRunId(runId);
|
|
3375
|
+
const root = runStorePath(runId);
|
|
3376
|
+
try {
|
|
3377
|
+
return parseLineageRecord((await readRegularFileWithinRoot(root, RUN_LINEAGE_BASENAME)).bytes.toString("utf8"), runId);
|
|
3378
|
+
} catch (error) {
|
|
3379
|
+
if (isMissingFile(error)) return null;
|
|
3380
|
+
throw error;
|
|
3381
|
+
}
|
|
3382
|
+
};
|
|
3383
|
+
return {
|
|
3384
|
+
async parentOf(runId) {
|
|
3385
|
+
return (await readRecord(runId))?.parentRunId ?? null;
|
|
3386
|
+
},
|
|
3387
|
+
async record(record) {
|
|
3388
|
+
const validated = validateLineageRecord(record);
|
|
3389
|
+
const root = runStorePath(validated.runId);
|
|
3390
|
+
await withKnowledgeMutation(root, async () => {
|
|
3391
|
+
const existing = await readRecord(validated.runId);
|
|
3392
|
+
if (existing) {
|
|
3393
|
+
if (existing.parentRunId !== validated.parentRunId) throw new Error(`run lineage conflict for '${validated.runId}': existing parent ${renderParent(existing.parentRunId)}, requested ${renderParent(validated.parentRunId)}`);
|
|
3394
|
+
return;
|
|
3395
|
+
}
|
|
3396
|
+
await writeJson(join(root, RUN_LINEAGE_BASENAME), validated);
|
|
3397
|
+
});
|
|
3398
|
+
}
|
|
3399
|
+
};
|
|
3400
|
+
}
|
|
3401
|
+
function validateLineageAuthority(authority) {
|
|
3402
|
+
if (!authority || typeof authority !== "object") throw new TypeError("lineageAuthority must be an object");
|
|
3403
|
+
if (typeof authority.parentOf !== "function") throw new TypeError("lineageAuthority.parentOf must be a function");
|
|
3404
|
+
if (authority.record !== void 0 && typeof authority.record !== "function") throw new TypeError("lineageAuthority.record must be a function when present");
|
|
3405
|
+
}
|
|
3406
|
+
function parseLineageRecord(text, expectedRunId) {
|
|
3407
|
+
let parsed;
|
|
3408
|
+
try {
|
|
3409
|
+
parsed = JSON.parse(text);
|
|
3410
|
+
} catch (cause) {
|
|
3411
|
+
throw new Error(`invalid lineage JSON for '${expectedRunId}'`, { cause });
|
|
3412
|
+
}
|
|
3413
|
+
const record = validateLineageRecord(parsed);
|
|
3414
|
+
if (record.runId !== expectedRunId) throw new Error(`lineage record identity mismatch: expected '${expectedRunId}', found '${record.runId}'`);
|
|
3415
|
+
return record;
|
|
3416
|
+
}
|
|
3417
|
+
function validateLineageRecord(value) {
|
|
3418
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) throw new TypeError("run lineage record must be an object");
|
|
3419
|
+
const record = value;
|
|
3420
|
+
assertRunId(record.runId, "lineage record runId");
|
|
3421
|
+
if (record.parentRunId !== null) assertRunId(record.parentRunId, `parent of '${record.runId}'`);
|
|
3422
|
+
if (typeof record.createdAt !== "string" || record.createdAt.trim().length === 0 || !Number.isFinite(Date.parse(record.createdAt))) throw new TypeError("run lineage record createdAt must be an ISO timestamp");
|
|
3423
|
+
return {
|
|
3424
|
+
runId: record.runId,
|
|
3425
|
+
parentRunId: record.parentRunId,
|
|
3426
|
+
createdAt: record.createdAt
|
|
3427
|
+
};
|
|
3428
|
+
}
|
|
3429
|
+
function assertRunId(value, label = "runId") {
|
|
3430
|
+
if (typeof value !== "string" || value.trim().length === 0) throw new TypeError(`${label} must be a non-empty string`);
|
|
3431
|
+
const trimmed = value.trim();
|
|
3432
|
+
if (trimmed === "." || trimmed === ".." || /[/\\\0]/.test(value)) throw new TypeError(`${label} must not contain path separators or dot segments`);
|
|
3433
|
+
}
|
|
3434
|
+
function renderParent(parentRunId) {
|
|
3435
|
+
return parentRunId === null ? "none" : `'${parentRunId}'`;
|
|
3436
|
+
}
|
|
3437
|
+
//#endregion
|
|
3438
|
+
//#region src/write-intake.ts
|
|
3439
|
+
/**
|
|
3440
|
+
* Write-time intake gate.
|
|
3441
|
+
*
|
|
3442
|
+
* A store degrades in two ways that no post-hoc report reverses: a page lands
|
|
3443
|
+
* that restates knowledge already present without relating itself to it, and a
|
|
3444
|
+
* page lands citing an id that exists nowhere. Both are cheap to refuse at the
|
|
3445
|
+
* moment of the write and expensive to repair afterwards, so the gate runs
|
|
3446
|
+
* before bytes are committed.
|
|
3447
|
+
*/
|
|
3448
|
+
/**
|
|
3449
|
+
* A write duplicates visible knowledge and declares no relation to it.
|
|
3450
|
+
*
|
|
3451
|
+
* Every listed pair is cleared by one authoring action: cite the matched page,
|
|
3452
|
+
* name it in `contradicts`, or give the candidate the matched page's id so the
|
|
3453
|
+
* write is an update of it.
|
|
3454
|
+
*/
|
|
3455
|
+
var KnowledgeDuplicateIntakeError = class extends Error {
|
|
3456
|
+
pairs;
|
|
3457
|
+
threshold;
|
|
3458
|
+
constructor(pairs, threshold) {
|
|
3459
|
+
const summary = pairs.map((pair) => `${pair.candidatePath} ~ ${pair.matchedPath} (${pair.exact ? "exact copy" : pair.similarity.toFixed(3)})`).join("; ");
|
|
3460
|
+
super(`knowledge write intake refused ${pairs.length} duplicate ${pairs.length === 1 ? "pair" : "pairs"}: ${summary}. Cite the matched page, name it in contradicts, or reuse its id to update it.`);
|
|
3461
|
+
this.name = "KnowledgeDuplicateIntakeError";
|
|
3462
|
+
this.pairs = Object.freeze([...pairs]);
|
|
3463
|
+
this.threshold = threshold;
|
|
3464
|
+
}
|
|
3465
|
+
};
|
|
3466
|
+
/**
|
|
3467
|
+
* Refuse a write that duplicates visible knowledge or cites a page that exists
|
|
3468
|
+
* nowhere.
|
|
3469
|
+
*
|
|
3470
|
+
* The candidates are part of the corpus both checks see, so a batch may cite a
|
|
3471
|
+
* page it writes in the same call, and two candidates that duplicate each other
|
|
3472
|
+
* are caught before either lands.
|
|
3473
|
+
*
|
|
3474
|
+
* Returns the resolved citation candidates in the order the candidates declare
|
|
3475
|
+
* them.
|
|
3476
|
+
*/
|
|
3477
|
+
function assertKnowledgeWriteIntake(candidates, options) {
|
|
3478
|
+
if (!Array.isArray(candidates)) throw new TypeError("knowledge write intake candidates must be an array");
|
|
3479
|
+
if (!options || typeof options !== "object" || !Array.isArray(options.visiblePages)) throw new TypeError("knowledge write intake requires the visible pages of the target chain");
|
|
3480
|
+
const candidatePaths = new Set(candidates.map((candidate) => candidate.path));
|
|
3481
|
+
const corpus = [...options.visiblePages.filter((entry) => !(entry.origin === "here" && candidatePaths.has(entry.page.path))), ...candidates.map((page) => ({
|
|
3482
|
+
page,
|
|
3483
|
+
origin: "here"
|
|
3484
|
+
}))];
|
|
3485
|
+
assertNoUnrelatedDuplicate(candidatePaths, corpus, options.nearDuplicates ?? {});
|
|
3486
|
+
if (options.citations === false) return Object.freeze([]);
|
|
3487
|
+
return assertKnowledgeCitationsResolved(corpus, candidates.flatMap((candidate) => (candidate.cites ?? []).map((persisted) => parseKnowledgeCitationReference(persisted))));
|
|
3488
|
+
}
|
|
3489
|
+
function assertNoUnrelatedDuplicate(candidatePaths, corpus, nearDuplicates) {
|
|
3490
|
+
if (candidatePaths.size === 0) return;
|
|
3491
|
+
const report = detectNearDuplicatePages(corpus.map((entry) => entry.page), nearDuplicates);
|
|
3492
|
+
const byIdentity = /* @__PURE__ */ new Map();
|
|
3493
|
+
for (const entry of corpus) {
|
|
3494
|
+
const key = identityKey(entry.page.path, entry.page.id);
|
|
3495
|
+
if (!byIdentity.has(key)) byIdentity.set(key, entry.page);
|
|
3496
|
+
}
|
|
3497
|
+
const offending = [];
|
|
3498
|
+
for (const pair of report.pairs) {
|
|
3499
|
+
const leftIsCandidate = candidatePaths.has(pair.leftPath);
|
|
3500
|
+
if (!leftIsCandidate && !candidatePaths.has(pair.rightPath)) continue;
|
|
3501
|
+
if (pair.leftPageId === pair.rightPageId) continue;
|
|
3502
|
+
const left = byIdentity.get(identityKey(pair.leftPath, pair.leftPageId));
|
|
3503
|
+
const right = byIdentity.get(identityKey(pair.rightPath, pair.rightPageId));
|
|
3504
|
+
if (declaresRelation(left, right.id) || declaresRelation(right, left.id)) continue;
|
|
3505
|
+
const candidate = leftIsCandidate ? left : right;
|
|
3506
|
+
const matched = leftIsCandidate ? right : left;
|
|
3507
|
+
offending.push({
|
|
3508
|
+
candidatePageId: candidate.id,
|
|
3509
|
+
candidatePath: candidate.path,
|
|
3510
|
+
matchedPageId: matched.id,
|
|
3511
|
+
matchedPath: matched.path,
|
|
3512
|
+
similarity: pair.similarity,
|
|
3513
|
+
exact: pair.exact
|
|
3514
|
+
});
|
|
3515
|
+
}
|
|
3516
|
+
if (offending.length > 0) throw new KnowledgeDuplicateIntakeError(offending, report.threshold);
|
|
3517
|
+
}
|
|
3518
|
+
function identityKey(path, id) {
|
|
3519
|
+
return `${path}\u0000${id}`;
|
|
3520
|
+
}
|
|
3521
|
+
/** Whether `page` already relates itself to `targetId` through `cites` or `contradicts`. */
|
|
3522
|
+
function declaresRelation(page, targetId) {
|
|
3523
|
+
for (const persisted of page.cites ?? []) if (parseKnowledgeCitationReference(persisted).pageId === targetId) return true;
|
|
3524
|
+
return (page.contradicts ?? []).includes(targetId);
|
|
3525
|
+
}
|
|
3526
|
+
//#endregion
|
|
2976
3527
|
//#region src/write-protocol.ts
|
|
2977
3528
|
const OPENER_LINE = /^---\s*FILE:\s*(.+?)\s*---\s*$/i;
|
|
2978
3529
|
const CLOSER_LINE = /^---\s*END\s+FILE\s*---\s*$/i;
|
|
@@ -3048,28 +3599,49 @@ function parseKnowledgeWriteBlocks(text, allowedPrefixes = ["knowledge/"]) {
|
|
|
3048
3599
|
}
|
|
3049
3600
|
//#endregion
|
|
3050
3601
|
//#region src/proposals.ts
|
|
3051
|
-
|
|
3052
|
-
|
|
3602
|
+
/**
|
|
3603
|
+
* Apply the FILE blocks of a proposal under the pages directory.
|
|
3604
|
+
*
|
|
3605
|
+
* A block whose path lies outside `<pagesDirectory>/` is refused by the parser
|
|
3606
|
+
* and again by the file transaction, so one option value bounds the whole
|
|
3607
|
+
* write.
|
|
3608
|
+
*/
|
|
3609
|
+
async function applyKnowledgeWriteBlocks(root, proposalText, options = {}) {
|
|
3610
|
+
const pagesDirectory = normalizePagesDirectory(options.pagesDirectory);
|
|
3611
|
+
const parsed = parseKnowledgeWriteBlocks(proposalText, [`${pagesDirectory}/`]);
|
|
3053
3612
|
const purpose = `knowledge-proposal:${contentHash(parsed.blocks)}`;
|
|
3613
|
+
const intake = options.intake;
|
|
3054
3614
|
return withKnowledgeMutation(root, async (lock) => {
|
|
3055
|
-
if (parsed.blocks.length > 0)
|
|
3056
|
-
|
|
3057
|
-
transactionRoot: lock.transactionRoot,
|
|
3058
|
-
purpose,
|
|
3059
|
-
mutations: parsed.blocks.map((block) => ({
|
|
3615
|
+
if (parsed.blocks.length > 0) {
|
|
3616
|
+
const mutations = parsed.blocks.map((block) => ({
|
|
3060
3617
|
path: block.path,
|
|
3061
3618
|
content: block.content.endsWith("\n") ? block.content : `${block.content}\n`
|
|
3062
|
-
}))
|
|
3063
|
-
|
|
3064
|
-
|
|
3619
|
+
}));
|
|
3620
|
+
if (intake) {
|
|
3621
|
+
const { inheritedPages = [], ...settings } = intake;
|
|
3622
|
+
const here = await loadKnowledgePages(root, { pagesDirectory });
|
|
3623
|
+
assertKnowledgeWriteIntake(mutations.filter((mutation) => isKnowledgePagePath(mutation.path)).map((mutation) => knowledgePageFromMarkdown(mutation.path, mutation.content, pagesDirectory)), {
|
|
3624
|
+
...settings,
|
|
3625
|
+
visiblePages: [...originatedPages(here), ...inheritedPages]
|
|
3626
|
+
});
|
|
3627
|
+
}
|
|
3628
|
+
await commitKnowledgeFileMutations({
|
|
3629
|
+
root,
|
|
3630
|
+
transactionRoot: lock.transactionRoot,
|
|
3631
|
+
purpose,
|
|
3632
|
+
mutations,
|
|
3633
|
+
pagesDirectory,
|
|
3634
|
+
assertOwned: lock.assertOwned
|
|
3635
|
+
});
|
|
3636
|
+
}
|
|
3065
3637
|
return {
|
|
3066
3638
|
written: parsed.blocks.map((block) => block.path),
|
|
3067
3639
|
warnings: parsed.warnings
|
|
3068
3640
|
};
|
|
3069
3641
|
}, { resumeTransaction: { purpose } });
|
|
3070
3642
|
}
|
|
3071
|
-
async function applyKnowledgeWriteBlocksFile(root, proposalPath) {
|
|
3072
|
-
return applyKnowledgeWriteBlocks(root, await readFile(proposalPath, "utf8"));
|
|
3643
|
+
async function applyKnowledgeWriteBlocksFile(root, proposalPath, options = {}) {
|
|
3644
|
+
return applyKnowledgeWriteBlocks(root, await readFile(proposalPath, "utf8"), options);
|
|
3073
3645
|
}
|
|
3074
3646
|
//#endregion
|
|
3075
3647
|
//#region src/metadata.ts
|
|
@@ -3149,6 +3721,6 @@ function explainKnowledgeTarget(index, target) {
|
|
|
3149
3721
|
};
|
|
3150
3722
|
}
|
|
3151
3723
|
//#endregion
|
|
3152
|
-
export {
|
|
3724
|
+
export { resolveKnowledgeCitations as $, withKnowledgeRead as $t, sourceRegistryPath as A, KNOWLEDGE_EVENT_TYPES as At, buildKnowledgeGraph as B, emptyClaimLedger as Bt, verifyGradeableEvidence as C, mediaTypeFor$1 as Cn, KnowledgeRelationSchema as Ct, addSourceText as D, ResearchSourceVersionSchema as Dt, addSourcePath as E, ResearchClaimRecordSchema as Et, KB_EVENTS_PATH as F, assertTrackedClaimIntegrity as Ft, assertKnowledgeCitationAudit as G, normalizeClaimText as Gt, KnowledgeCitationAuditError as H, materializeRegisteredClaimEvidence as Ht, KB_INDEX_PATH as I, claimEvidenceId as It, auditCurrentRunCitations as J, parseFrontmatter as Jt, assertKnowledgeCitationsResolved as K, researchSourceVersionKey as Kt, KB_STORE_DIR as L, claimId as Lt, ClaimLedgerMigrationRequiredError as M, assertDeepQuestionIntegrity as Mt, FileSystemKbStore as N, assertResearchClaimEvidenceIntegrity as Nt, loadSourceRegistry as O, SourceAnchorSchema as Ot, KB_CLAIM_LEDGER_DIR as P, assertResearchClaimLedgerIntegrity as Pt, resolveKnowledgeCitation as Q, withKnowledgeMutation as Qt, MemoryKbStore as R, claimSourceHost as Rt, verdictFor as S, writeJsonDurableWithinRoot as Sn, KnowledgeRelationNodeSchema as St, writeKnowledgeIndex as T, ResearchClaimLedgerSchema as Tt, KnowledgeCitationResolutionError as U, mergeClaimLedgers as Ut, knowledgePageRelations as V, linkClaimContradictions as Vt, assertCurrentRunCitationsResolved as W, mergeTrackedClaims as Wt, formatKnowledgeCitationReference as X, inspectPendingKnowledgeMutation as Xt, auditKnowledgeCitations as Y, acquireDurableFileLock as Yt, parseKnowledgeCitationReference as Z, recoverPendingKnowledgeMutation as Zt, lintKnowledgeIndex as _, withSafeDescendant as _n, KnowledgeGraphNodeSchema as _t, applyKnowledgeWriteBlocksFile as a, prepareKnowledgeFileTransaction as an, isScaffoldPath as at, assertGradeableEvidence as b, writeFileDurableWithinRoot as bn, KnowledgePageSchema as bt, KnowledgeDuplicateIntakeError as c, normalizePagesDirectory as cn, loadKnowledgePages as ct, createFileRunLineageAuthority as d, listRegularFilesWithinRoot as dn, extractWikilinks as dt, applyKnowledgeFileTransaction as en, resolveRunScopedCitation as et, createRunScopedStores as f, readRegularFileNoFollow as fn, normalizeLinkTarget as ft, validateKnowledgeIndex as g, syncDirectory as gn, KnowledgeGraphEdgeSchema as gt, normalizePageText as h, renameDurable as hn, KnowledgeEventSchema as ht, applyKnowledgeWriteBlocks as i, knowledgeFileTransactionPlanHash as in, isKnowledgePagePath as it, writeSourceRegistry as j, ClaimLedgerGoalConflictError as jt, snapshotSourceTextInput as k, SourceRecordSchema as kt, assertKnowledgeWriteIntake as l, isKernelAnchoredPath as ln, writeJson as lt, detectNearDuplicatePages as m, removeDurable as mn, KnowledgeBaseCandidateSchema as mt, inspectKnowledgeIndex as n, commitKnowledgeFileMutations as nn, SCAFFOLD_PAGE_BASENAMES as nt, isSafeKnowledgePath as o, rollbackKnowledgeFileTransaction as on, knowledgePageFromMarkdown as ot, originatedPages as p, readRegularFileWithinRoot as pn, DeepQuestionSchema as pt, assertRunScopedCitationsResolved as q, formatFrontmatter as qt, stringMetadata as r, finishKnowledgeFileTransaction as rn, initKnowledgeBase as rt, parseKnowledgeWriteBlocks as s, DEFAULT_PAGES_DIRECTORY as sn, layoutFor as st, explainKnowledgeTarget as t, assertKnowledgeMutationPath as tn, resolveRunScopedCitations as tt, RUN_LINEAGE_BASENAME as u, isMissingFile as un, WIKILINK_REGEX as ut, CHECKABLE_RUNG_THRESHOLD as v, withSafeDirectory as vn, KnowledgeIndexSchema as vt, buildKnowledgeIndex as w, textSourceAdapter as wn, ResearchClaimEvidenceSchema as wt, gradeFor as x, writeJsonDurable as xn, KnowledgeRelationGraphSchema as xt, UncheckableClaimError as y, writeFileDurable as yn, KnowledgePageInvalidationSchema as yt, assertClaimLedgerId as z, deepQuestionId as zt };
|
|
3153
3725
|
|
|
3154
|
-
//# sourceMappingURL=inspect-
|
|
3726
|
+
//# sourceMappingURL=inspect-DALsvG10.js.map
|