@tangle-network/agent-knowledge 8.0.5 → 8.0.7
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/CHANGELOG.md +6 -0
- package/dist/benchmarks/index.d.ts +1 -1
- package/dist/benchmarks/index.js +1 -1
- package/dist/{benchmarks-CPl4AO1k.js → benchmarks-B_07vX0Q.js} +2 -2
- package/dist/{benchmarks-CPl4AO1k.js.map → benchmarks-B_07vX0Q.js.map} +1 -1
- package/dist/cli.js +2 -2
- package/dist/{index-BEVmwlfJ.d.ts → index-BPNsQVSx.d.ts} +3 -3
- package/dist/{index-BEVmwlfJ.d.ts.map → index-BPNsQVSx.d.ts.map} +1 -1
- package/dist/{index-D0wc4GYg.d.ts → index-CLNBGN_f.d.ts} +2 -2
- package/dist/{index-D0wc4GYg.d.ts.map → index-CLNBGN_f.d.ts.map} +1 -1
- package/dist/index.d.ts +214 -23
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +201 -122
- package/dist/index.js.map +1 -1
- package/dist/{inspect-CJQGYuKa.js → inspect-B7AJ7CZS.js} +528 -216
- package/dist/inspect-B7AJ7CZS.js.map +1 -0
- package/dist/memory/index.d.ts +2 -2
- package/dist/memory/index.js +2 -2
- package/dist/{memory-BtZWplh5.js → memory-BPgPIEhj.js} +2 -2
- package/dist/{memory-BtZWplh5.js.map → memory-BPgPIEhj.js.map} +1 -1
- package/dist/{search-CP0QtBJZ.js → search-CtVJ0PKX.js} +24 -5
- package/dist/search-CtVJ0PKX.js.map +1 -0
- package/dist/{types-BOfmvDe-.d.ts → types-CXZS0w00.d.ts} +23 -3
- package/dist/types-CXZS0w00.d.ts.map +1 -0
- package/dist/viz/index.d.ts +1 -1
- package/package.json +3 -3
- package/dist/inspect-CJQGYuKa.js.map +0 -1
- package/dist/search-CP0QtBJZ.js.map +0 -1
- package/dist/types-BOfmvDe-.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 { n as searchKnowledge } from "./search-
|
|
2
|
+
import { n as searchKnowledge } from "./search-CtVJ0PKX.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";
|
|
@@ -982,232 +982,47 @@ function parseSimpleYaml(raw) {
|
|
|
982
982
|
while (i + 1 < lines.length) {
|
|
983
983
|
const item = /^\s*-\s*(.+?)\s*$/.exec(lines[i + 1]);
|
|
984
984
|
if (!item) break;
|
|
985
|
-
items.push(
|
|
985
|
+
items.push(parseYamlScalar(item[1]));
|
|
986
986
|
i++;
|
|
987
987
|
}
|
|
988
988
|
out[key] = items;
|
|
989
989
|
continue;
|
|
990
990
|
}
|
|
991
|
-
|
|
992
|
-
else if (rest === "true" || rest === "false") out[key] = rest === "true";
|
|
993
|
-
else if (/^-?\d+(?:\.\d+)?$/.test(rest)) out[key] = Number(rest);
|
|
994
|
-
else out[key] = unquote(rest);
|
|
991
|
+
out[key] = parseYamlScalar(rest);
|
|
995
992
|
}
|
|
996
993
|
return out;
|
|
997
994
|
}
|
|
995
|
+
function parseYamlScalar(value) {
|
|
996
|
+
const trimmed = value.trim();
|
|
997
|
+
if (trimmed.startsWith("\"") && trimmed.endsWith("\"") || trimmed.startsWith("[") && trimmed.endsWith("]") || trimmed.startsWith("{") && trimmed.endsWith("}")) try {
|
|
998
|
+
return JSON.parse(trimmed);
|
|
999
|
+
} catch {}
|
|
1000
|
+
if (trimmed.startsWith("[") && trimmed.endsWith("]")) return trimmed.slice(1, -1).split(",").map((part) => unquote(part.trim())).filter(Boolean);
|
|
1001
|
+
if (trimmed === "true" || trimmed === "false") return trimmed === "true";
|
|
1002
|
+
if (/^-?\d+(?:\.\d+)?$/.test(trimmed)) return Number(trimmed);
|
|
1003
|
+
return unquote(trimmed);
|
|
1004
|
+
}
|
|
998
1005
|
function formatYamlField(key, value) {
|
|
999
|
-
if (Array.isArray(value)) return [`${key}:`, ...value.map((item) => ` - ${
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1006
|
+
if (Array.isArray(value)) return [`${key}:`, ...value.map((item) => ` - ${formatYamlScalar(item)}`)];
|
|
1007
|
+
return [`${key}: ${formatYamlScalar(value)}`];
|
|
1008
|
+
}
|
|
1009
|
+
function formatYamlScalar(value) {
|
|
1010
|
+
if (typeof value === "string") return stringNeedsJsonEncoding(value) ? JSON.stringify(value) : value;
|
|
1011
|
+
if (typeof value === "number" || typeof value === "boolean") return String(value);
|
|
1012
|
+
const encoded = JSON.stringify(value);
|
|
1013
|
+
if (encoded === void 0) throw new TypeError(`frontmatter value of type ${typeof value} is not JSON-serializable`);
|
|
1014
|
+
return encoded;
|
|
1015
|
+
}
|
|
1016
|
+
function stringNeedsJsonEncoding(value) {
|
|
1017
|
+
if (value.length === 0 || value !== value.trim()) return true;
|
|
1018
|
+
if (value.includes("\n") || value.includes("\r")) return true;
|
|
1019
|
+
if (value === "true" || value === "false" || /^-?\d+(?:\.\d+)?$/.test(value)) return true;
|
|
1020
|
+
return /^[[{"']/.test(value) || /["']$/.test(value);
|
|
1003
1021
|
}
|
|
1004
1022
|
function unquote(value) {
|
|
1005
1023
|
return value.replace(/^['"]|['"]$/g, "");
|
|
1006
1024
|
}
|
|
1007
1025
|
//#endregion
|
|
1008
|
-
//#region src/wikilinks.ts
|
|
1009
|
-
const WIKILINK_REGEX = /\[\[([^\]|]+?)(?:\|[^\]]+?)?\]\]/g;
|
|
1010
|
-
function extractWikilinks(content) {
|
|
1011
|
-
const links = [];
|
|
1012
|
-
const regex = new RegExp(WIKILINK_REGEX.source, "g");
|
|
1013
|
-
let match;
|
|
1014
|
-
match = regex.exec(content);
|
|
1015
|
-
while (match !== null) {
|
|
1016
|
-
links.push(match[1].trim());
|
|
1017
|
-
match = regex.exec(content);
|
|
1018
|
-
}
|
|
1019
|
-
return [...new Set(links)];
|
|
1020
|
-
}
|
|
1021
|
-
function normalizeLinkTarget(target) {
|
|
1022
|
-
return target.trim().replace(/\.md$/i, "").toLowerCase().replace(/\s+/g, "-");
|
|
1023
|
-
}
|
|
1024
|
-
//#endregion
|
|
1025
|
-
//#region src/store.ts
|
|
1026
|
-
function layoutFor(root) {
|
|
1027
|
-
return {
|
|
1028
|
-
root,
|
|
1029
|
-
knowledgeDir: join(root, "knowledge"),
|
|
1030
|
-
rawSourcesDir: join(root, "raw", "sources"),
|
|
1031
|
-
sourceRegistryPath: join(root, ".agent-knowledge", "sources.json"),
|
|
1032
|
-
indexPath: join(root, "knowledge", "index.md"),
|
|
1033
|
-
logPath: join(root, "knowledge", "log.md"),
|
|
1034
|
-
cacheDir: join(root, ".agent-knowledge")
|
|
1035
|
-
};
|
|
1036
|
-
}
|
|
1037
|
-
/**
|
|
1038
|
-
* Filenames that `initKnowledgeBase` writes as human-navigation scaffolding.
|
|
1039
|
-
* These are excluded from the page index — they exist on disk so authors can
|
|
1040
|
-
* curate their vault, but they are not searchable content.
|
|
1041
|
-
*
|
|
1042
|
-
* Add new scaffold filenames here (and only here) to keep lint, validate, viz,
|
|
1043
|
-
* and the indexer consistent.
|
|
1044
|
-
*/
|
|
1045
|
-
const SCAFFOLD_PAGE_BASENAMES = ["index.md", "log.md"];
|
|
1046
|
-
/**
|
|
1047
|
-
* True when a knowledge-relative path points at a scaffold file rather than
|
|
1048
|
-
* authored content. Accepts both repo-relative paths (`knowledge/index.md`)
|
|
1049
|
-
* and any nested `<dir>/index.md` or `<dir>/log.md` (for example
|
|
1050
|
-
* `knowledge/concepts/index.md`) so subdirectory README-style scaffolds are
|
|
1051
|
-
* also excluded.
|
|
1052
|
-
*/
|
|
1053
|
-
function isScaffoldPath(path) {
|
|
1054
|
-
const normalized = path.replace(/\\/g, "/");
|
|
1055
|
-
for (const basename of SCAFFOLD_PAGE_BASENAMES) {
|
|
1056
|
-
if (normalized === `knowledge/${basename}`) return true;
|
|
1057
|
-
if (normalized.endsWith(`/${basename}`)) return true;
|
|
1058
|
-
}
|
|
1059
|
-
return false;
|
|
1060
|
-
}
|
|
1061
|
-
async function initKnowledgeBase(root) {
|
|
1062
|
-
const layout = layoutFor(root);
|
|
1063
|
-
if (await knowledgeBaseInitialized(layout)) return layout;
|
|
1064
|
-
return withKnowledgeMutation(root, () => initKnowledgeBaseUnlocked(root));
|
|
1065
|
-
}
|
|
1066
|
-
async function initKnowledgeBaseUnlocked(root) {
|
|
1067
|
-
const layout = layoutFor(root);
|
|
1068
|
-
await withSafeDirectory(root, "knowledge", true, () => void 0);
|
|
1069
|
-
await withSafeDirectory(root, "raw/sources", true, () => void 0);
|
|
1070
|
-
await withSafeDirectory(root, ".agent-knowledge", true, () => void 0);
|
|
1071
|
-
await writeIfMissing(root, "knowledge/index.md", "# Knowledge Index\n\n");
|
|
1072
|
-
await writeIfMissing(root, "knowledge/log.md", "# Knowledge Log\n\n");
|
|
1073
|
-
await writeIfMissing(root, ".agent-knowledge/sources.json", "{\n \"generatedAt\": \"1970-01-01T00:00:00.000Z\",\n \"sources\": []\n}\n");
|
|
1074
|
-
return layout;
|
|
1075
|
-
}
|
|
1076
|
-
async function loadKnowledgePages(root) {
|
|
1077
|
-
return withKnowledgeRead(root, () => loadKnowledgePagesUnlocked(root));
|
|
1078
|
-
}
|
|
1079
|
-
async function loadKnowledgePagesUnlocked(root) {
|
|
1080
|
-
let files;
|
|
1081
|
-
try {
|
|
1082
|
-
files = await listRegularFilesWithinRoot(root, "knowledge");
|
|
1083
|
-
} catch (error) {
|
|
1084
|
-
if (isMissingFile(error)) return [];
|
|
1085
|
-
throw error;
|
|
1086
|
-
}
|
|
1087
|
-
const pages = [];
|
|
1088
|
-
for (const file of files) {
|
|
1089
|
-
const rel = file.path;
|
|
1090
|
-
if (!rel.endsWith(".md")) continue;
|
|
1091
|
-
if (isScaffoldPath(rel)) continue;
|
|
1092
|
-
const { frontmatter, body } = parseFrontmatter(file.bytes.toString("utf8"));
|
|
1093
|
-
const title = stringField(frontmatter.title) ?? firstHeading(body) ?? rel.split("/").pop().replace(/\.md$/, "");
|
|
1094
|
-
const sourceIds = arrayField(frontmatter.sources);
|
|
1095
|
-
const tags = arrayField(frontmatter.tags);
|
|
1096
|
-
pages.push({
|
|
1097
|
-
id: stringField(frontmatter.id) ?? slugify(rel.replace(/^knowledge\//, "").replace(/\.md$/, "")),
|
|
1098
|
-
path: rel,
|
|
1099
|
-
title,
|
|
1100
|
-
text: body,
|
|
1101
|
-
frontmatter,
|
|
1102
|
-
sourceIds,
|
|
1103
|
-
tags,
|
|
1104
|
-
outLinks: extractWikilinks(body).map(normalizeLinkTarget)
|
|
1105
|
-
});
|
|
1106
|
-
}
|
|
1107
|
-
pages.sort((a, b) => a.path.localeCompare(b.path));
|
|
1108
|
-
return pages;
|
|
1109
|
-
}
|
|
1110
|
-
async function writeJson(path, value) {
|
|
1111
|
-
await writeJsonDurable(path, value);
|
|
1112
|
-
}
|
|
1113
|
-
async function writeIfMissing(root, relativePath, content) {
|
|
1114
|
-
try {
|
|
1115
|
-
await readRegularFileWithinRoot(root, relativePath);
|
|
1116
|
-
} catch (error) {
|
|
1117
|
-
if (!isMissingFile(error)) throw error;
|
|
1118
|
-
await writeFileDurableWithinRoot(root, relativePath, content, { encoding: "utf8" });
|
|
1119
|
-
}
|
|
1120
|
-
}
|
|
1121
|
-
async function knowledgeBaseInitialized(layout) {
|
|
1122
|
-
try {
|
|
1123
|
-
await withSafeDirectory(layout.root, "knowledge", false, () => void 0);
|
|
1124
|
-
await withSafeDirectory(layout.root, "raw/sources", false, () => void 0);
|
|
1125
|
-
await withSafeDirectory(layout.root, ".agent-knowledge", false, () => void 0);
|
|
1126
|
-
await readRegularFileWithinRoot(layout.root, "knowledge/index.md");
|
|
1127
|
-
await readRegularFileWithinRoot(layout.root, "knowledge/log.md");
|
|
1128
|
-
await readRegularFileWithinRoot(layout.root, ".agent-knowledge/sources.json");
|
|
1129
|
-
return true;
|
|
1130
|
-
} catch (error) {
|
|
1131
|
-
if (isMissingFile(error)) return false;
|
|
1132
|
-
throw error;
|
|
1133
|
-
}
|
|
1134
|
-
}
|
|
1135
|
-
function stringField(value) {
|
|
1136
|
-
return typeof value === "string" && value.trim() !== "" ? value.trim() : void 0;
|
|
1137
|
-
}
|
|
1138
|
-
function arrayField(value) {
|
|
1139
|
-
return Array.isArray(value) ? value.filter((item) => typeof item === "string") : [];
|
|
1140
|
-
}
|
|
1141
|
-
function firstHeading(body) {
|
|
1142
|
-
return /^#\s+(.+)$/m.exec(body)?.[1]?.trim();
|
|
1143
|
-
}
|
|
1144
|
-
//#endregion
|
|
1145
|
-
//#region src/graph.ts
|
|
1146
|
-
function buildKnowledgeGraph(pages) {
|
|
1147
|
-
const byId = /* @__PURE__ */ new Map();
|
|
1148
|
-
const bySlug = /* @__PURE__ */ new Map();
|
|
1149
|
-
for (const page of pages) {
|
|
1150
|
-
byId.set(page.id, page);
|
|
1151
|
-
bySlug.set(normalizeLinkTarget(page.id), page);
|
|
1152
|
-
bySlug.set(normalizeLinkTarget(page.title), page);
|
|
1153
|
-
bySlug.set(normalizeLinkTarget(page.path.split("/").pop().replace(/\.md$/, "")), page);
|
|
1154
|
-
}
|
|
1155
|
-
const incoming = /* @__PURE__ */ new Map();
|
|
1156
|
-
const outgoing = /* @__PURE__ */ new Map();
|
|
1157
|
-
const edgesByKey = /* @__PURE__ */ new Map();
|
|
1158
|
-
for (const page of pages) {
|
|
1159
|
-
outgoing.set(page.id, 0);
|
|
1160
|
-
incoming.set(page.id, 0);
|
|
1161
|
-
}
|
|
1162
|
-
for (const page of pages) for (const raw of page.outLinks) {
|
|
1163
|
-
const target = bySlug.get(normalizeLinkTarget(raw));
|
|
1164
|
-
if (!target || target.id === page.id) continue;
|
|
1165
|
-
const key = `${page.id}->${target.id}`;
|
|
1166
|
-
const edge = edgesByKey.get(key);
|
|
1167
|
-
if (edge) edge.weight += 1;
|
|
1168
|
-
else edgesByKey.set(key, {
|
|
1169
|
-
source: page.id,
|
|
1170
|
-
target: target.id,
|
|
1171
|
-
weight: 1,
|
|
1172
|
-
reasons: ["wikilink"]
|
|
1173
|
-
});
|
|
1174
|
-
outgoing.set(page.id, (outgoing.get(page.id) ?? 0) + 1);
|
|
1175
|
-
incoming.set(target.id, (incoming.get(target.id) ?? 0) + 1);
|
|
1176
|
-
}
|
|
1177
|
-
addSourceOverlapEdges(pages, edgesByKey);
|
|
1178
|
-
return {
|
|
1179
|
-
nodes: pages.map((page) => ({
|
|
1180
|
-
id: page.id,
|
|
1181
|
-
title: page.title,
|
|
1182
|
-
path: page.path,
|
|
1183
|
-
tags: page.tags,
|
|
1184
|
-
sourceIds: page.sourceIds,
|
|
1185
|
-
outDegree: outgoing.get(page.id) ?? 0,
|
|
1186
|
-
inDegree: incoming.get(page.id) ?? 0
|
|
1187
|
-
})),
|
|
1188
|
-
edges: [...edgesByKey.values()].sort((a, b) => b.weight - a.weight)
|
|
1189
|
-
};
|
|
1190
|
-
}
|
|
1191
|
-
function addSourceOverlapEdges(pages, edges) {
|
|
1192
|
-
for (let i = 0; i < pages.length; i++) for (let j = i + 1; j < pages.length; j++) {
|
|
1193
|
-
const a = pages[i];
|
|
1194
|
-
const b = pages[j];
|
|
1195
|
-
const overlap = a.sourceIds.filter((source) => b.sourceIds.includes(source));
|
|
1196
|
-
if (overlap.length === 0) continue;
|
|
1197
|
-
const key = `${a.id}->${b.id}`;
|
|
1198
|
-
const edge = edges.get(key);
|
|
1199
|
-
if (edge) {
|
|
1200
|
-
edge.weight += overlap.length * .5;
|
|
1201
|
-
edge.reasons.push("shared-source");
|
|
1202
|
-
} else edges.set(key, {
|
|
1203
|
-
source: a.id,
|
|
1204
|
-
target: b.id,
|
|
1205
|
-
weight: overlap.length * .5,
|
|
1206
|
-
reasons: ["shared-source"]
|
|
1207
|
-
});
|
|
1208
|
-
}
|
|
1209
|
-
}
|
|
1210
|
-
//#endregion
|
|
1211
1026
|
//#region src/claim-ledger.ts
|
|
1212
1027
|
/**
|
|
1213
1028
|
* The algebra of a research claim ledger: claim identity, and how two ledgers
|
|
@@ -1641,6 +1456,14 @@ const SourceRecordSchema = z.object({
|
|
|
1641
1456
|
metadata: z.record(z.string(), z.unknown()).optional(),
|
|
1642
1457
|
createdAt: z.string().min(1)
|
|
1643
1458
|
});
|
|
1459
|
+
const KnowledgePageInvalidationSchema = z.object({
|
|
1460
|
+
verdict: z.literal("contradicted"),
|
|
1461
|
+
observedAt: z.iso.datetime(),
|
|
1462
|
+
reason: z.string().trim().min(1),
|
|
1463
|
+
evidencePath: z.string().trim().min(1).optional(),
|
|
1464
|
+
grader: z.string().trim().min(1).optional(),
|
|
1465
|
+
metadata: z.record(z.string(), z.unknown()).optional()
|
|
1466
|
+
}).strict();
|
|
1644
1467
|
const KnowledgePageSchema = z.object({
|
|
1645
1468
|
id: z.string().min(1),
|
|
1646
1469
|
path: z.string().min(1),
|
|
@@ -1649,7 +1472,9 @@ const KnowledgePageSchema = z.object({
|
|
|
1649
1472
|
frontmatter: z.record(z.string(), z.unknown()),
|
|
1650
1473
|
sourceIds: z.array(z.string()),
|
|
1651
1474
|
tags: z.array(z.string()),
|
|
1652
|
-
outLinks: z.array(z.string())
|
|
1475
|
+
outLinks: z.array(z.string()),
|
|
1476
|
+
contradicts: z.array(z.string().min(1)).optional(),
|
|
1477
|
+
invalidation: KnowledgePageInvalidationSchema.optional()
|
|
1653
1478
|
});
|
|
1654
1479
|
const KnowledgeGraphNodeSchema = z.object({
|
|
1655
1480
|
id: z.string(),
|
|
@@ -1793,6 +1618,225 @@ const KnowledgeBaseCandidateSchema = z.object({
|
|
|
1793
1618
|
metadata: z.record(z.string(), z.unknown()).optional()
|
|
1794
1619
|
});
|
|
1795
1620
|
//#endregion
|
|
1621
|
+
//#region src/wikilinks.ts
|
|
1622
|
+
const WIKILINK_REGEX = /\[\[([^\]|]+?)(?:\|[^\]]+?)?\]\]/g;
|
|
1623
|
+
function extractWikilinks(content) {
|
|
1624
|
+
const links = [];
|
|
1625
|
+
const regex = new RegExp(WIKILINK_REGEX.source, "g");
|
|
1626
|
+
let match;
|
|
1627
|
+
match = regex.exec(content);
|
|
1628
|
+
while (match !== null) {
|
|
1629
|
+
links.push(match[1].trim());
|
|
1630
|
+
match = regex.exec(content);
|
|
1631
|
+
}
|
|
1632
|
+
return [...new Set(links)];
|
|
1633
|
+
}
|
|
1634
|
+
function normalizeLinkTarget(target) {
|
|
1635
|
+
return target.trim().replace(/\.md$/i, "").toLowerCase().replace(/\s+/g, "-");
|
|
1636
|
+
}
|
|
1637
|
+
//#endregion
|
|
1638
|
+
//#region src/store.ts
|
|
1639
|
+
function layoutFor(root) {
|
|
1640
|
+
return {
|
|
1641
|
+
root,
|
|
1642
|
+
knowledgeDir: join(root, "knowledge"),
|
|
1643
|
+
rawSourcesDir: join(root, "raw", "sources"),
|
|
1644
|
+
sourceRegistryPath: join(root, ".agent-knowledge", "sources.json"),
|
|
1645
|
+
indexPath: join(root, "knowledge", "index.md"),
|
|
1646
|
+
logPath: join(root, "knowledge", "log.md"),
|
|
1647
|
+
cacheDir: join(root, ".agent-knowledge")
|
|
1648
|
+
};
|
|
1649
|
+
}
|
|
1650
|
+
/**
|
|
1651
|
+
* Filenames that `initKnowledgeBase` writes as human-navigation scaffolding.
|
|
1652
|
+
* These are excluded from the page index — they exist on disk so authors can
|
|
1653
|
+
* curate their vault, but they are not searchable content.
|
|
1654
|
+
*
|
|
1655
|
+
* Add new scaffold filenames here (and only here) to keep lint, validate, viz,
|
|
1656
|
+
* and the indexer consistent.
|
|
1657
|
+
*/
|
|
1658
|
+
const SCAFFOLD_PAGE_BASENAMES = ["index.md", "log.md"];
|
|
1659
|
+
/**
|
|
1660
|
+
* True when a knowledge-relative path points at a scaffold file rather than
|
|
1661
|
+
* authored content. Accepts both repo-relative paths (`knowledge/index.md`)
|
|
1662
|
+
* and any nested `<dir>/index.md` or `<dir>/log.md` (for example
|
|
1663
|
+
* `knowledge/concepts/index.md`) so subdirectory README-style scaffolds are
|
|
1664
|
+
* also excluded.
|
|
1665
|
+
*/
|
|
1666
|
+
function isScaffoldPath(path) {
|
|
1667
|
+
const normalized = path.replace(/\\/g, "/");
|
|
1668
|
+
for (const basename of SCAFFOLD_PAGE_BASENAMES) {
|
|
1669
|
+
if (normalized === `knowledge/${basename}`) return true;
|
|
1670
|
+
if (normalized.endsWith(`/${basename}`)) return true;
|
|
1671
|
+
}
|
|
1672
|
+
return false;
|
|
1673
|
+
}
|
|
1674
|
+
async function initKnowledgeBase(root) {
|
|
1675
|
+
const layout = layoutFor(root);
|
|
1676
|
+
if (await knowledgeBaseInitialized(layout)) return layout;
|
|
1677
|
+
return withKnowledgeMutation(root, () => initKnowledgeBaseUnlocked(root));
|
|
1678
|
+
}
|
|
1679
|
+
async function initKnowledgeBaseUnlocked(root) {
|
|
1680
|
+
const layout = layoutFor(root);
|
|
1681
|
+
await withSafeDirectory(root, "knowledge", true, () => void 0);
|
|
1682
|
+
await withSafeDirectory(root, "raw/sources", true, () => void 0);
|
|
1683
|
+
await withSafeDirectory(root, ".agent-knowledge", true, () => void 0);
|
|
1684
|
+
await writeIfMissing(root, "knowledge/index.md", "# Knowledge Index\n\n");
|
|
1685
|
+
await writeIfMissing(root, "knowledge/log.md", "# Knowledge Log\n\n");
|
|
1686
|
+
await writeIfMissing(root, ".agent-knowledge/sources.json", "{\n \"generatedAt\": \"1970-01-01T00:00:00.000Z\",\n \"sources\": []\n}\n");
|
|
1687
|
+
return layout;
|
|
1688
|
+
}
|
|
1689
|
+
async function loadKnowledgePages(root, options = {}) {
|
|
1690
|
+
return withKnowledgeRead(root, () => loadKnowledgePagesUnlocked(root, options));
|
|
1691
|
+
}
|
|
1692
|
+
async function loadKnowledgePagesUnlocked(root, options) {
|
|
1693
|
+
const pagesDirectory = normalizePagesDirectory(options.pagesDirectory ?? "knowledge");
|
|
1694
|
+
let files;
|
|
1695
|
+
try {
|
|
1696
|
+
files = await listRegularFilesWithinRoot(root, pagesDirectory);
|
|
1697
|
+
} catch (error) {
|
|
1698
|
+
if (isMissingFile(error)) return [];
|
|
1699
|
+
throw error;
|
|
1700
|
+
}
|
|
1701
|
+
const pages = [];
|
|
1702
|
+
const pagesPrefix = `${pagesDirectory}/`;
|
|
1703
|
+
for (const file of files) {
|
|
1704
|
+
const rel = file.path.replace(/\\/g, "/");
|
|
1705
|
+
if (!rel.endsWith(".md")) continue;
|
|
1706
|
+
if (isScaffoldPath(rel)) continue;
|
|
1707
|
+
const { frontmatter, body } = parseFrontmatter(file.bytes.toString("utf8"));
|
|
1708
|
+
const title = stringField(frontmatter.title) ?? firstHeading(body) ?? rel.split("/").pop().replace(/\.md$/, "");
|
|
1709
|
+
const sourceIds = arrayField(frontmatter.sources);
|
|
1710
|
+
const tags = arrayField(frontmatter.tags);
|
|
1711
|
+
const contradicts = idListField(frontmatter.contradicts);
|
|
1712
|
+
const invalidation = KnowledgePageInvalidationSchema.safeParse(frontmatter.invalidation);
|
|
1713
|
+
const pageRelativePath = rel.startsWith(pagesPrefix) ? rel.slice(pagesPrefix.length) : rel;
|
|
1714
|
+
pages.push({
|
|
1715
|
+
id: stringField(frontmatter.id) ?? slugify(pageRelativePath.replace(/\.md$/, "")),
|
|
1716
|
+
path: rel,
|
|
1717
|
+
title,
|
|
1718
|
+
text: body,
|
|
1719
|
+
frontmatter,
|
|
1720
|
+
sourceIds,
|
|
1721
|
+
tags,
|
|
1722
|
+
outLinks: extractWikilinks(body).map(normalizeLinkTarget),
|
|
1723
|
+
...contradicts.length > 0 ? { contradicts } : {},
|
|
1724
|
+
...invalidation.success ? { invalidation: invalidation.data } : {}
|
|
1725
|
+
});
|
|
1726
|
+
}
|
|
1727
|
+
pages.sort((a, b) => a.path.localeCompare(b.path));
|
|
1728
|
+
return pages;
|
|
1729
|
+
}
|
|
1730
|
+
async function writeJson(path, value) {
|
|
1731
|
+
await writeJsonDurable(path, value);
|
|
1732
|
+
}
|
|
1733
|
+
async function writeIfMissing(root, relativePath, content) {
|
|
1734
|
+
try {
|
|
1735
|
+
await readRegularFileWithinRoot(root, relativePath);
|
|
1736
|
+
} catch (error) {
|
|
1737
|
+
if (!isMissingFile(error)) throw error;
|
|
1738
|
+
await writeFileDurableWithinRoot(root, relativePath, content, { encoding: "utf8" });
|
|
1739
|
+
}
|
|
1740
|
+
}
|
|
1741
|
+
async function knowledgeBaseInitialized(layout) {
|
|
1742
|
+
try {
|
|
1743
|
+
await withSafeDirectory(layout.root, "knowledge", false, () => void 0);
|
|
1744
|
+
await withSafeDirectory(layout.root, "raw/sources", false, () => void 0);
|
|
1745
|
+
await withSafeDirectory(layout.root, ".agent-knowledge", false, () => void 0);
|
|
1746
|
+
await readRegularFileWithinRoot(layout.root, "knowledge/index.md");
|
|
1747
|
+
await readRegularFileWithinRoot(layout.root, "knowledge/log.md");
|
|
1748
|
+
await readRegularFileWithinRoot(layout.root, ".agent-knowledge/sources.json");
|
|
1749
|
+
return true;
|
|
1750
|
+
} catch (error) {
|
|
1751
|
+
if (isMissingFile(error)) return false;
|
|
1752
|
+
throw error;
|
|
1753
|
+
}
|
|
1754
|
+
}
|
|
1755
|
+
function normalizePagesDirectory(value) {
|
|
1756
|
+
const normalized = value.replace(/\\/g, "/").replace(/^\.\//, "").replace(/\/+$/, "");
|
|
1757
|
+
if (normalized.length === 0 || normalized === ".") throw new Error("pagesDirectory must name a root-relative directory");
|
|
1758
|
+
return normalized;
|
|
1759
|
+
}
|
|
1760
|
+
function stringField(value) {
|
|
1761
|
+
return typeof value === "string" && value.trim() !== "" ? value.trim() : void 0;
|
|
1762
|
+
}
|
|
1763
|
+
function arrayField(value) {
|
|
1764
|
+
return Array.isArray(value) ? value.filter((item) => typeof item === "string") : [];
|
|
1765
|
+
}
|
|
1766
|
+
function idListField(value) {
|
|
1767
|
+
const values = typeof value === "string" ? [value] : arrayField(value);
|
|
1768
|
+
return [...new Set(values.map((item) => item.trim()).filter(Boolean))];
|
|
1769
|
+
}
|
|
1770
|
+
function firstHeading(body) {
|
|
1771
|
+
return /^#\s+(.+)$/m.exec(body)?.[1]?.trim();
|
|
1772
|
+
}
|
|
1773
|
+
//#endregion
|
|
1774
|
+
//#region src/graph.ts
|
|
1775
|
+
function buildKnowledgeGraph(pages) {
|
|
1776
|
+
const byId = /* @__PURE__ */ new Map();
|
|
1777
|
+
const bySlug = /* @__PURE__ */ new Map();
|
|
1778
|
+
for (const page of pages) {
|
|
1779
|
+
byId.set(page.id, page);
|
|
1780
|
+
bySlug.set(normalizeLinkTarget(page.id), page);
|
|
1781
|
+
bySlug.set(normalizeLinkTarget(page.title), page);
|
|
1782
|
+
bySlug.set(normalizeLinkTarget(page.path.split("/").pop().replace(/\.md$/, "")), page);
|
|
1783
|
+
}
|
|
1784
|
+
const incoming = /* @__PURE__ */ new Map();
|
|
1785
|
+
const outgoing = /* @__PURE__ */ new Map();
|
|
1786
|
+
const edgesByKey = /* @__PURE__ */ new Map();
|
|
1787
|
+
for (const page of pages) {
|
|
1788
|
+
outgoing.set(page.id, 0);
|
|
1789
|
+
incoming.set(page.id, 0);
|
|
1790
|
+
}
|
|
1791
|
+
for (const page of pages) for (const raw of page.outLinks) {
|
|
1792
|
+
const target = bySlug.get(normalizeLinkTarget(raw));
|
|
1793
|
+
if (!target || target.id === page.id) continue;
|
|
1794
|
+
const key = `${page.id}->${target.id}`;
|
|
1795
|
+
const edge = edgesByKey.get(key);
|
|
1796
|
+
if (edge) edge.weight += 1;
|
|
1797
|
+
else edgesByKey.set(key, {
|
|
1798
|
+
source: page.id,
|
|
1799
|
+
target: target.id,
|
|
1800
|
+
weight: 1,
|
|
1801
|
+
reasons: ["wikilink"]
|
|
1802
|
+
});
|
|
1803
|
+
outgoing.set(page.id, (outgoing.get(page.id) ?? 0) + 1);
|
|
1804
|
+
incoming.set(target.id, (incoming.get(target.id) ?? 0) + 1);
|
|
1805
|
+
}
|
|
1806
|
+
addSourceOverlapEdges(pages, edgesByKey);
|
|
1807
|
+
return {
|
|
1808
|
+
nodes: pages.map((page) => ({
|
|
1809
|
+
id: page.id,
|
|
1810
|
+
title: page.title,
|
|
1811
|
+
path: page.path,
|
|
1812
|
+
tags: page.tags,
|
|
1813
|
+
sourceIds: page.sourceIds,
|
|
1814
|
+
outDegree: outgoing.get(page.id) ?? 0,
|
|
1815
|
+
inDegree: incoming.get(page.id) ?? 0
|
|
1816
|
+
})),
|
|
1817
|
+
edges: [...edgesByKey.values()].sort((a, b) => b.weight - a.weight)
|
|
1818
|
+
};
|
|
1819
|
+
}
|
|
1820
|
+
function addSourceOverlapEdges(pages, edges) {
|
|
1821
|
+
for (let i = 0; i < pages.length; i++) for (let j = i + 1; j < pages.length; j++) {
|
|
1822
|
+
const a = pages[i];
|
|
1823
|
+
const b = pages[j];
|
|
1824
|
+
const overlap = a.sourceIds.filter((source) => b.sourceIds.includes(source));
|
|
1825
|
+
if (overlap.length === 0) continue;
|
|
1826
|
+
const key = `${a.id}->${b.id}`;
|
|
1827
|
+
const edge = edges.get(key);
|
|
1828
|
+
if (edge) {
|
|
1829
|
+
edge.weight += overlap.length * .5;
|
|
1830
|
+
edge.reasons.push("shared-source");
|
|
1831
|
+
} else edges.set(key, {
|
|
1832
|
+
source: a.id,
|
|
1833
|
+
target: b.id,
|
|
1834
|
+
weight: overlap.length * .5,
|
|
1835
|
+
reasons: ["shared-source"]
|
|
1836
|
+
});
|
|
1837
|
+
}
|
|
1838
|
+
}
|
|
1839
|
+
//#endregion
|
|
1796
1840
|
//#region src/kb-store.ts
|
|
1797
1841
|
/**
|
|
1798
1842
|
* Where a filesystem knowledge base keeps its machine-written records, relative
|
|
@@ -2352,7 +2396,188 @@ async function writeKnowledgeIndex(root) {
|
|
|
2352
2396
|
});
|
|
2353
2397
|
}
|
|
2354
2398
|
//#endregion
|
|
2399
|
+
//#region src/claim-evidence.ts
|
|
2400
|
+
/** The rung at and above which a claim must be machine-checkable to be recorded at that rung. */
|
|
2401
|
+
const CHECKABLE_RUNG_THRESHOLD = 4;
|
|
2402
|
+
/**
|
|
2403
|
+
* The refusal vocabulary, shared by both boundaries. Record time and grade time refuse the same
|
|
2404
|
+
* static shapes for the same reasons, so they read from one detector and one set of notes. A
|
|
2405
|
+
* second definition would let the two boundaries drift, and a claim the recorder admitted but the
|
|
2406
|
+
* grader refuses is exactly the gap this section exists to close.
|
|
2407
|
+
*/
|
|
2408
|
+
/** The commands that succeed unconditionally, so their exit code carries no information. */
|
|
2409
|
+
const ALWAYS_SUCCEEDS = /* @__PURE__ */ new Set(["true", ":"]);
|
|
2410
|
+
/** The commands that print their arguments back, so their output carries only their arguments. */
|
|
2411
|
+
const PRINTS_ITS_ARGUMENTS = /* @__PURE__ */ new Set(["echo", "printf"]);
|
|
2412
|
+
/**
|
|
2413
|
+
* A character that makes an argument depend on something outside the command line: a pipe, a
|
|
2414
|
+
* command separator, a redirection from a file, a backtick or `$(` substitution, or a variable
|
|
2415
|
+
* reference. `$'` and `$"` are quoting sigils, not variable references.
|
|
2416
|
+
*/
|
|
2417
|
+
const READS_SOMETHING_ELSE = /[|;&\n<`]|\$(?!['"])/;
|
|
2418
|
+
/**
|
|
2419
|
+
* Whether a check emits a constant, and therefore cannot fail whatever the claim's subject does.
|
|
2420
|
+
*
|
|
2421
|
+
* The test is deliberately narrow and mechanical. It recognizes exactly two shapes: the whole
|
|
2422
|
+
* command is `true` or `:`, or the whole command is one `echo` or `printf` whose arguments read
|
|
2423
|
+
* nothing outside the command line. Anything else is treated as a real check.
|
|
2424
|
+
*
|
|
2425
|
+
* This CANNOT catch every check that cannot fail. A script that prints a hard-coded number, a
|
|
2426
|
+
* command whose output an author copied into `expect`, and an `echo` behind a shell alias all look
|
|
2427
|
+
* identical to a real check from here. Catching those is not this function's job: it needs an
|
|
2428
|
+
* independent party to re-derive the value, which is what rung 5 means. This function refuses only
|
|
2429
|
+
* the shapes that carry zero information on their face, where refusing costs the author nothing
|
|
2430
|
+
* but a rewrite of the command.
|
|
2431
|
+
*/
|
|
2432
|
+
function isConstantEmitter(check) {
|
|
2433
|
+
const command = check.trim();
|
|
2434
|
+
if (ALWAYS_SUCCEEDS.has(command)) return true;
|
|
2435
|
+
const argumentStart = command.search(/\s/);
|
|
2436
|
+
const head = argumentStart === -1 ? command : command.slice(0, argumentStart);
|
|
2437
|
+
if (!PRINTS_ITS_ARGUMENTS.has(head)) return false;
|
|
2438
|
+
const argumentText = argumentStart === -1 ? "" : command.slice(argumentStart + 1);
|
|
2439
|
+
return !READS_SOMETHING_ELSE.test(argumentText);
|
|
2440
|
+
}
|
|
2441
|
+
const NO_CHECK_NOTE = "a claim at this rung asserts that a command reproduces a value, and no command was recorded — record the command that re-establishes it (evidence.check); without one the claim is a self-grade, which is rung 3 at most";
|
|
2442
|
+
const NO_EXPECTATION_NOTE = "exit code alone cannot verify a claim at this rung — record the value the check must print";
|
|
2443
|
+
const CONSTANT_EMITTER_NOTE = "the check is a constant emitter: it prints a fixed string and exits zero whatever the claim describes, so it can never fail — record a command that reads the artifact the claim is about";
|
|
2444
|
+
const MULTILINE_EXPECTATION_NOTE = "the expected value spans multiple lines, but the grader performs one contiguous-substring comparison — record one decisive single-line value per claim";
|
|
2445
|
+
const MULTI_VALUE_EXPECTATION_NOTE = "the expected value carries three or more key=value tokens, so any extra token in the check output breaks an otherwise correct claim — record one decisive value per claim";
|
|
2446
|
+
const LONG_FIRST_WORD_NOTE = "the check begins with a shell word longer than 200 characters; this is the escaped-newline failure shape where \\n text was recorded instead of real line breaks — write the script to a file or record a real heredoc and a short invocation";
|
|
2447
|
+
const INVALID_SHELL_SYNTAX_NOTE = "the recorded check does not parse under bash and would fail independently of the claim — record the exact command only after bash -n accepts it";
|
|
2448
|
+
/**
|
|
2449
|
+
* The evidence recorded for a claim cannot be graded at the rung it was recorded at.
|
|
2450
|
+
*
|
|
2451
|
+
* One error type for every mechanically refused shape, because the grader reaches one verdict for
|
|
2452
|
+
* all of them: `uncheckable`. `note` names which shape was refused and what to record instead, in
|
|
2453
|
+
* the same words the grader reports, so an author reads one message wherever the claim is stopped.
|
|
2454
|
+
*/
|
|
2455
|
+
var UncheckableClaimError = class extends Error {
|
|
2456
|
+
/** The rung whose evidence rule the recorded claim failed. */
|
|
2457
|
+
rung;
|
|
2458
|
+
/** The refused shape and the fix for it, in the grader's vocabulary. */
|
|
2459
|
+
note;
|
|
2460
|
+
constructor(rung, note) {
|
|
2461
|
+
super(`a claim at rung ${rung} cannot be recorded at that rung: ${note}`);
|
|
2462
|
+
this.name = "UncheckableClaimError";
|
|
2463
|
+
this.rung = rung;
|
|
2464
|
+
this.note = note;
|
|
2465
|
+
}
|
|
2466
|
+
};
|
|
2467
|
+
/**
|
|
2468
|
+
* Refuse the evidence shapes that made self-grading and false refutation possible. Call at record
|
|
2469
|
+
* time, not grade time: by grading it is too late — the ungradeable claim has already circulated.
|
|
2470
|
+
*
|
|
2471
|
+
* At and above `CHECKABLE_RUNG_THRESHOLD` this refuses every statically recognizable shape that
|
|
2472
|
+
* `gradeFor` grades `uncheckable`: no check, a check that cannot fail, an unusable expectation,
|
|
2473
|
+
* or the escaped-newline command shape. Below the threshold nothing is refused.
|
|
2474
|
+
*
|
|
2475
|
+
* Four additional rules are calibrated on downstream losses, not taste: four correct claims died
|
|
2476
|
+
* from multiline expectations, two from brittle multi-value expectations, one campaign recorded
|
|
2477
|
+
* an enormous first word made of literal `\\n` sequences, and a 299-run fleet autopsy reduced ten
|
|
2478
|
+
* reported grader failures to checks that were never executed before their authors disappeared.
|
|
2479
|
+
* The dynamic half of that last rule is `verifyGradeableEvidence` below.
|
|
2480
|
+
*/
|
|
2481
|
+
function assertGradeableEvidence(evidence) {
|
|
2482
|
+
if (evidence.rung < 4) return evidence;
|
|
2483
|
+
const note = checkRefusalNote(evidence.check) ?? expectationRefusalNote(evidence.expect);
|
|
2484
|
+
if (note) throw new UncheckableClaimError(evidence.rung, note);
|
|
2485
|
+
return evidence;
|
|
2486
|
+
}
|
|
2487
|
+
/** The error signatures that mean the check could not run, as opposed to ran and failed. */
|
|
2488
|
+
const UNRUNNABLE_SIGNATURES = /No such file|FileNotFoundError|ModuleNotFoundError|command not found|ENOENT|AbortError|timed out|ERR_CHILD_PROCESS_STDIO_MAXBUFFER/;
|
|
2489
|
+
/**
|
|
2490
|
+
* The calibrated verdict function, pure so every grader shares one semantics. Callers execute the
|
|
2491
|
+
* check however their environment requires and pass the observation; this function only judges.
|
|
2492
|
+
*/
|
|
2493
|
+
function gradeFor(evidence, execution) {
|
|
2494
|
+
const mustBeCheckable = evidence.rung >= 4;
|
|
2495
|
+
if (mustBeCheckable) {
|
|
2496
|
+
const note = checkRefusalNote(evidence.check);
|
|
2497
|
+
if (note) return {
|
|
2498
|
+
verdict: "uncheckable",
|
|
2499
|
+
note
|
|
2500
|
+
};
|
|
2501
|
+
}
|
|
2502
|
+
if (!execution) return { verdict: "unrunnable" };
|
|
2503
|
+
const output = `${execution.stdout}\n${execution.stderr}`.trim();
|
|
2504
|
+
if (execution.exitCode !== 0) return { verdict: UNRUNNABLE_SIGNATURES.test(output) ? "unrunnable" : "contradicted" };
|
|
2505
|
+
if (mustBeCheckable) {
|
|
2506
|
+
const note = expectationRefusalNote(evidence.expect);
|
|
2507
|
+
if (note) return {
|
|
2508
|
+
verdict: "uncheckable",
|
|
2509
|
+
note
|
|
2510
|
+
};
|
|
2511
|
+
}
|
|
2512
|
+
if (evidence.expect && !output.includes(evidence.expect)) return { verdict: output === "" ? "silent-check" : "contradicted" };
|
|
2513
|
+
return { verdict: "verified" };
|
|
2514
|
+
}
|
|
2515
|
+
/** The verdict alone, for graders that report a lattice member and not a reason. */
|
|
2516
|
+
function verdictFor(evidence, execution) {
|
|
2517
|
+
return gradeFor(evidence, execution).verdict;
|
|
2518
|
+
}
|
|
2519
|
+
/**
|
|
2520
|
+
* Parse, execute, and grade evidence at intake using the same cwd and environment the blind grader
|
|
2521
|
+
* will use later.
|
|
2522
|
+
*
|
|
2523
|
+
* This function executes `evidence.check` verbatim. It is intentionally opt-in, Node-only at call
|
|
2524
|
+
* time, and dynamically imports `node:child_process` so edge consumers that never invoke it remain
|
|
2525
|
+
* importable. Callers must apply their own sandbox and capability policy before passing untrusted
|
|
2526
|
+
* commands here.
|
|
2527
|
+
*/
|
|
2528
|
+
async function verifyGradeableEvidence(evidence, options) {
|
|
2529
|
+
const accepted = assertGradeableEvidence(evidence);
|
|
2530
|
+
const check = accepted.check;
|
|
2531
|
+
const syntax = await runBash([
|
|
2532
|
+
"-n",
|
|
2533
|
+
"-c",
|
|
2534
|
+
check
|
|
2535
|
+
], options);
|
|
2536
|
+
if (syntax.exitCode !== 0) {
|
|
2537
|
+
const detail = `${syntax.stdout}\n${syntax.stderr}`.trim();
|
|
2538
|
+
const note = detail ? `${INVALID_SHELL_SYNTAX_NOTE}: ${detail}` : INVALID_SHELL_SYNTAX_NOTE;
|
|
2539
|
+
throw new UncheckableClaimError(accepted.rung, note);
|
|
2540
|
+
}
|
|
2541
|
+
const execution = await runBash(["-c", check], options);
|
|
2542
|
+
return {
|
|
2543
|
+
evidence: accepted,
|
|
2544
|
+
execution,
|
|
2545
|
+
grade: gradeFor(accepted, execution)
|
|
2546
|
+
};
|
|
2547
|
+
}
|
|
2548
|
+
function checkRefusalNote(check) {
|
|
2549
|
+
if (!check) return NO_CHECK_NOTE;
|
|
2550
|
+
if (isConstantEmitter(check)) return CONSTANT_EMITTER_NOTE;
|
|
2551
|
+
if ((check.trimStart().split(/\s/, 1)[0] ?? "").length > 200) return LONG_FIRST_WORD_NOTE;
|
|
2552
|
+
}
|
|
2553
|
+
function expectationRefusalNote(expect) {
|
|
2554
|
+
if (!expect?.trim()) return NO_EXPECTATION_NOTE;
|
|
2555
|
+
if (expect.includes("\n") || expect.includes("\r")) return MULTILINE_EXPECTATION_NOTE;
|
|
2556
|
+
if (expect.trim().split(/\s+/).filter((token) => token.includes("=")).length >= 3) return MULTI_VALUE_EXPECTATION_NOTE;
|
|
2557
|
+
}
|
|
2558
|
+
async function runBash(args, options) {
|
|
2559
|
+
const { execFile } = await import("node:child_process");
|
|
2560
|
+
return new Promise((resolve) => {
|
|
2561
|
+
execFile(options.bashPath ?? "bash", args, {
|
|
2562
|
+
cwd: options.cwd,
|
|
2563
|
+
env: options.env ?? {},
|
|
2564
|
+
timeout: options.timeoutMs ?? 3e4,
|
|
2565
|
+
maxBuffer: options.maxBufferBytes ?? 1024 * 1024,
|
|
2566
|
+
signal: options.signal,
|
|
2567
|
+
encoding: "utf8"
|
|
2568
|
+
}, (error, stdout, stderr) => {
|
|
2569
|
+
const diagnostic = error && typeof error.code !== "number" ? `${stderr}\n${error}` : stderr;
|
|
2570
|
+
resolve({
|
|
2571
|
+
exitCode: typeof error?.code === "number" ? error.code : error ? 127 : 0,
|
|
2572
|
+
stdout: String(stdout ?? ""),
|
|
2573
|
+
stderr: String(diagnostic ?? "")
|
|
2574
|
+
});
|
|
2575
|
+
});
|
|
2576
|
+
});
|
|
2577
|
+
}
|
|
2578
|
+
//#endregion
|
|
2355
2579
|
//#region src/lint.ts
|
|
2580
|
+
const ABSOLUTE_PATH_TOKEN = /(?:^|[\s"'=(])(?:\/(?!dev\/null\b)[^\s"'();]+|[A-Za-z]:\\[^\s"'();]+)/m;
|
|
2356
2581
|
function lintKnowledgeIndex(index) {
|
|
2357
2582
|
const findings = [];
|
|
2358
2583
|
const byTarget = /* @__PURE__ */ new Set();
|
|
@@ -2421,6 +2646,9 @@ function lintKnowledgeIndex(index) {
|
|
|
2421
2646
|
message: `Page cites unknown source anchor "${ref.sourceId}#${ref.anchorId}".`,
|
|
2422
2647
|
metadata: ref
|
|
2423
2648
|
});
|
|
2649
|
+
findings.push(...lintPageEvidence(page));
|
|
2650
|
+
findings.push(...lintPageContradictions(page, pageIds));
|
|
2651
|
+
findings.push(...lintPageInvalidation(page));
|
|
2424
2652
|
}
|
|
2425
2653
|
for (const [title, paths] of titles) if (title && paths.length > 1) findings.push({
|
|
2426
2654
|
type: "duplicate-title",
|
|
@@ -2442,6 +2670,90 @@ function lintKnowledgeIndex(index) {
|
|
|
2442
2670
|
});
|
|
2443
2671
|
return findings;
|
|
2444
2672
|
}
|
|
2673
|
+
function lintPageEvidence(page) {
|
|
2674
|
+
if (page.frontmatter.rung === void 0) return [];
|
|
2675
|
+
const rung = evidenceRung(page.frontmatter.rung);
|
|
2676
|
+
if (rung === void 0) return [{
|
|
2677
|
+
type: "ungradeable-evidence",
|
|
2678
|
+
severity: "error",
|
|
2679
|
+
page: page.path,
|
|
2680
|
+
message: "Evidence rung must be an integer from 1 through 5.",
|
|
2681
|
+
metadata: { rung: page.frontmatter.rung }
|
|
2682
|
+
}];
|
|
2683
|
+
const check = stringValue(page.frontmatter.check);
|
|
2684
|
+
const expect = stringValue(page.frontmatter.expect);
|
|
2685
|
+
const evidencePath = stringValue(page.frontmatter.evidencePath);
|
|
2686
|
+
const evidence = {
|
|
2687
|
+
rung,
|
|
2688
|
+
...check ? { check } : {},
|
|
2689
|
+
...expect ? { expect } : {},
|
|
2690
|
+
...evidencePath ? { evidencePath } : {}
|
|
2691
|
+
};
|
|
2692
|
+
const findings = [];
|
|
2693
|
+
try {
|
|
2694
|
+
assertGradeableEvidence(evidence);
|
|
2695
|
+
} catch (error) {
|
|
2696
|
+
if (!(error instanceof UncheckableClaimError)) throw error;
|
|
2697
|
+
findings.push({
|
|
2698
|
+
type: "ungradeable-evidence",
|
|
2699
|
+
severity: "error",
|
|
2700
|
+
page: page.path,
|
|
2701
|
+
message: error.note,
|
|
2702
|
+
metadata: { rung: error.rung }
|
|
2703
|
+
});
|
|
2704
|
+
}
|
|
2705
|
+
if (rung >= 4 && !evidence.evidencePath) findings.push({
|
|
2706
|
+
type: "missing-evidence-path",
|
|
2707
|
+
severity: "warning",
|
|
2708
|
+
page: page.path,
|
|
2709
|
+
message: `Rung ${rung} evidence has no evidencePath for a human to inspect.`,
|
|
2710
|
+
metadata: { rung }
|
|
2711
|
+
});
|
|
2712
|
+
for (const [field, value] of [["check", evidence.check], ["evidencePath", evidence.evidencePath]]) if (value && ABSOLUTE_PATH_TOKEN.test(value)) findings.push({
|
|
2713
|
+
type: "nonportable-evidence",
|
|
2714
|
+
severity: "warning",
|
|
2715
|
+
page: page.path,
|
|
2716
|
+
message: `${field} contains an absolute path and may not re-run outside the author machine.`,
|
|
2717
|
+
metadata: {
|
|
2718
|
+
rung,
|
|
2719
|
+
field
|
|
2720
|
+
}
|
|
2721
|
+
});
|
|
2722
|
+
return findings;
|
|
2723
|
+
}
|
|
2724
|
+
function lintPageContradictions(page, pageIds) {
|
|
2725
|
+
const findings = [];
|
|
2726
|
+
for (const targetId of page.contradicts ?? []) {
|
|
2727
|
+
const selfReference = targetId === page.id;
|
|
2728
|
+
if (selfReference || !pageIds.has(targetId)) findings.push({
|
|
2729
|
+
type: "broken-contradiction",
|
|
2730
|
+
severity: "error",
|
|
2731
|
+
page: page.path,
|
|
2732
|
+
message: selfReference ? `Page "${page.id}" cannot contradict itself.` : `Page contradicts unknown page id "${targetId}".`,
|
|
2733
|
+
metadata: { targetId }
|
|
2734
|
+
});
|
|
2735
|
+
}
|
|
2736
|
+
return findings;
|
|
2737
|
+
}
|
|
2738
|
+
function lintPageInvalidation(page) {
|
|
2739
|
+
if (page.frontmatter.invalidation === void 0) return [];
|
|
2740
|
+
const parsed = KnowledgePageInvalidationSchema.safeParse(page.frontmatter.invalidation);
|
|
2741
|
+
if (parsed.success) return [];
|
|
2742
|
+
return [{
|
|
2743
|
+
type: "invalid-invalidation",
|
|
2744
|
+
severity: "error",
|
|
2745
|
+
page: page.path,
|
|
2746
|
+
message: "Page invalidation must record verdict=contradicted, an ISO observedAt timestamp, and a non-empty reason.",
|
|
2747
|
+
metadata: { issues: parsed.error.issues }
|
|
2748
|
+
}];
|
|
2749
|
+
}
|
|
2750
|
+
function evidenceRung(value) {
|
|
2751
|
+
const parsed = typeof value === "string" && /^\d+$/.test(value) ? Number(value) : value;
|
|
2752
|
+
return parsed === 1 || parsed === 2 || parsed === 3 || parsed === 4 || parsed === 5 ? parsed : void 0;
|
|
2753
|
+
}
|
|
2754
|
+
function stringValue(value) {
|
|
2755
|
+
return typeof value === "string" && value.trim().length > 0 ? value : void 0;
|
|
2756
|
+
}
|
|
2445
2757
|
function extractSourceRefs(text) {
|
|
2446
2758
|
const refs = [];
|
|
2447
2759
|
const regex = /\[\^([A-Za-z0-9_-]+)(?:#([A-Za-z0-9_.:-]+))?\]/g;
|
|
@@ -2657,6 +2969,6 @@ function explainKnowledgeTarget(index, target) {
|
|
|
2657
2969
|
};
|
|
2658
2970
|
}
|
|
2659
2971
|
//#endregion
|
|
2660
|
-
export {
|
|
2972
|
+
export { ResearchSourceVersionSchema as $, MemoryKbStore as A, prepareKnowledgeFileTransaction as At, extractWikilinks as B, withSafeDescendant as Bt, writeSourceRegistry as C, recoverPendingKnowledgeMutation as Ct, KB_EVENTS_PATH as D, assertKnowledgeMutationPath as Dt, KB_CLAIM_LEDGER_DIR as E, applyKnowledgeFileTransaction as Et, isScaffoldPath as F, readRegularFileNoFollow as Ft, KnowledgeGraphEdgeSchema as G, writeJsonDurableWithinRoot as Gt, DeepQuestionSchema as H, writeFileDurable as Ht, layoutFor as I, readRegularFileWithinRoot as It, KnowledgePageInvalidationSchema as J, KnowledgeGraphNodeSchema as K, mediaTypeFor$1 as Kt, loadKnowledgePages as L, removeDurable as Lt, buildKnowledgeGraph as M, isKernelAnchoredPath as Mt, SCAFFOLD_PAGE_BASENAMES as N, isMissingFile as Nt, KB_INDEX_PATH as O, finishKnowledgeFileTransaction as Ot, initKnowledgeBase as P, listRegularFilesWithinRoot as Pt, ResearchClaimRecordSchema as Q, writeJson as R, renameDurable as Rt, sourceRegistryPath as S, inspectPendingKnowledgeMutation as St, FileSystemKbStore as T, withKnowledgeRead as Tt, KnowledgeBaseCandidateSchema as U, writeFileDurableWithinRoot as Ut, normalizeLinkTarget as V, withSafeDirectory as Vt, KnowledgeEventSchema as W, writeJsonDurable as Wt, ResearchClaimEvidenceSchema as X, KnowledgePageSchema as Y, ResearchClaimLedgerSchema as Z, writeKnowledgeIndex as _, normalizeClaimText as _t, applyKnowledgeWriteBlocksFile as a, assertResearchClaimEvidenceIntegrity as at, loadSourceRegistry as b, parseFrontmatter as bt, validateKnowledgeIndex as c, claimEvidenceId as ct, UncheckableClaimError as d, deepQuestionId as dt, SourceAnchorSchema as et, assertGradeableEvidence as f, emptyClaimLedger as ft, buildKnowledgeIndex as g, mergeTrackedClaims as gt, verifyGradeableEvidence as h, mergeClaimLedgers as ht, applyKnowledgeWriteBlocks as i, assertDeepQuestionIntegrity as it, assertClaimLedgerId as j, rollbackKnowledgeFileTransaction as jt, KB_STORE_DIR as k, knowledgeFileTransactionPlanHash as kt, lintKnowledgeIndex as l, claimId as lt, verdictFor as m, materializeRegisteredClaimEvidence as mt, inspectKnowledgeIndex as n, KNOWLEDGE_EVENT_TYPES as nt, isSafeKnowledgePath as o, assertResearchClaimLedgerIntegrity as ot, gradeFor as p, linkClaimContradictions as pt, KnowledgeIndexSchema as q, textSourceAdapter as qt, stringMetadata as r, ClaimLedgerGoalConflictError as rt, parseKnowledgeWriteBlocks as s, assertTrackedClaimIntegrity as st, explainKnowledgeTarget as t, SourceRecordSchema as tt, CHECKABLE_RUNG_THRESHOLD as u, claimSourceHost as ut, addSourcePath as v, researchSourceVersionKey as vt, ClaimLedgerMigrationRequiredError as w, withKnowledgeMutation as wt, snapshotSourceTextInput as x, acquireDurableFileLock as xt, addSourceText as y, formatFrontmatter as yt, WIKILINK_REGEX as z, syncDirectory as zt };
|
|
2661
2973
|
|
|
2662
|
-
//# sourceMappingURL=inspect-
|
|
2974
|
+
//# sourceMappingURL=inspect-B7AJ7CZS.js.map
|