@seanhogg/builderforce-memory 2026.10.28 → 2026.10.29

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.
Files changed (253) hide show
  1. package/dist/ingest/IngestionPipeline.d.ts +1 -8
  2. package/dist/ingest/IngestionPipeline.d.ts.map +1 -1
  3. package/dist/ingest/IngestionPipeline.js +3 -15
  4. package/dist/ingest/IngestionPipeline.js.map +1 -1
  5. package/dist/ingest/docsExtract.d.ts +30 -0
  6. package/dist/ingest/docsExtract.d.ts.map +1 -0
  7. package/dist/ingest/docsExtract.js +221 -0
  8. package/dist/ingest/docsExtract.js.map +1 -0
  9. package/dist/ingest/hash.d.ts +15 -0
  10. package/dist/ingest/hash.d.ts.map +1 -0
  11. package/dist/ingest/hash.js +22 -0
  12. package/dist/ingest/hash.js.map +1 -0
  13. package/dist/ingest/parsers.d.ts.map +1 -1
  14. package/dist/ingest/parsers.js +4 -0
  15. package/dist/ingest/parsers.js.map +1 -1
  16. package/dist/ingest/types.d.ts +7 -0
  17. package/dist/ingest/types.d.ts.map +1 -1
  18. package/dist/ingest/types.js.map +1 -1
  19. package/dist/knowledge/build.d.ts +24 -0
  20. package/dist/knowledge/build.d.ts.map +1 -0
  21. package/dist/knowledge/build.js +263 -0
  22. package/dist/knowledge/build.js.map +1 -0
  23. package/dist/knowledge/cards.d.ts +17 -0
  24. package/dist/knowledge/cards.d.ts.map +1 -0
  25. package/dist/knowledge/cards.js +118 -0
  26. package/dist/knowledge/cards.js.map +1 -0
  27. package/dist/knowledge/findings.d.ts +24 -0
  28. package/dist/knowledge/findings.d.ts.map +1 -0
  29. package/dist/knowledge/findings.js +152 -0
  30. package/dist/knowledge/findings.js.map +1 -0
  31. package/dist/knowledge/importance.d.ts +21 -0
  32. package/dist/knowledge/importance.d.ts.map +1 -0
  33. package/dist/knowledge/importance.js +70 -0
  34. package/dist/knowledge/importance.js.map +1 -0
  35. package/dist/knowledge/index.d.ts +31 -0
  36. package/dist/knowledge/index.d.ts.map +1 -0
  37. package/dist/knowledge/index.js +26 -0
  38. package/dist/knowledge/index.js.map +1 -0
  39. package/dist/knowledge/judge.d.ts +86 -0
  40. package/dist/knowledge/judge.d.ts.map +1 -0
  41. package/dist/knowledge/judge.js +213 -0
  42. package/dist/knowledge/judge.js.map +1 -0
  43. package/dist/knowledge/model.d.ts +333 -0
  44. package/dist/knowledge/model.d.ts.map +1 -0
  45. package/dist/knowledge/model.js +13 -0
  46. package/dist/knowledge/model.js.map +1 -0
  47. package/dist/knowledge/packs/docs.d.ts +17 -0
  48. package/dist/knowledge/packs/docs.d.ts.map +1 -0
  49. package/dist/knowledge/packs/docs.js +89 -0
  50. package/dist/knowledge/packs/docs.js.map +1 -0
  51. package/dist/knowledge/partitions.d.ts +31 -0
  52. package/dist/knowledge/partitions.d.ts.map +1 -0
  53. package/dist/knowledge/partitions.js +135 -0
  54. package/dist/knowledge/partitions.js.map +1 -0
  55. package/dist/knowledge/plan.d.ts +33 -0
  56. package/dist/knowledge/plan.d.ts.map +1 -0
  57. package/dist/knowledge/plan.js +89 -0
  58. package/dist/knowledge/plan.js.map +1 -0
  59. package/dist/knowledge/refs.d.ts +40 -0
  60. package/dist/knowledge/refs.d.ts.map +1 -0
  61. package/dist/knowledge/refs.js +102 -0
  62. package/dist/knowledge/refs.js.map +1 -0
  63. package/dist/knowledge/resolve.d.ts +35 -0
  64. package/dist/knowledge/resolve.d.ts.map +1 -0
  65. package/dist/knowledge/resolve.js +67 -0
  66. package/dist/knowledge/resolve.js.map +1 -0
  67. package/dist/knowledge/review.d.ts +23 -0
  68. package/dist/knowledge/review.d.ts.map +1 -0
  69. package/dist/knowledge/review.js +233 -0
  70. package/dist/knowledge/review.js.map +1 -0
  71. package/dist/knowledge/sources.d.ts +47 -0
  72. package/dist/knowledge/sources.d.ts.map +1 -0
  73. package/dist/knowledge/sources.js +136 -0
  74. package/dist/knowledge/sources.js.map +1 -0
  75. package/dist/knowledge/stores/localSnapshotStore.d.ts +50 -0
  76. package/dist/knowledge/stores/localSnapshotStore.d.ts.map +1 -0
  77. package/dist/knowledge/stores/localSnapshotStore.js +125 -0
  78. package/dist/knowledge/stores/localSnapshotStore.js.map +1 -0
  79. package/dist/knowledge/trace.d.ts +32 -0
  80. package/dist/knowledge/trace.d.ts.map +1 -0
  81. package/dist/knowledge/trace.js +102 -0
  82. package/dist/knowledge/trace.js.map +1 -0
  83. package/dist/knowledge/truncate.d.ts +24 -0
  84. package/dist/knowledge/truncate.d.ts.map +1 -0
  85. package/dist/knowledge/truncate.js +128 -0
  86. package/dist/knowledge/truncate.js.map +1 -0
  87. package/dist/memory/SemanticRecall.d.ts.map +1 -1
  88. package/dist/memory/SemanticRecall.js +3 -1
  89. package/dist/memory/SemanticRecall.js.map +1 -1
  90. package/dist/retrieval/HybridRetriever.d.ts.map +1 -1
  91. package/dist/retrieval/HybridRetriever.js +3 -1
  92. package/dist/retrieval/HybridRetriever.js.map +1 -1
  93. package/dist/retrieval/bm25.d.ts +3 -2
  94. package/dist/retrieval/bm25.d.ts.map +1 -1
  95. package/dist/retrieval/bm25.js +5 -3
  96. package/dist/retrieval/bm25.js.map +1 -1
  97. package/dist/retrieval/fusion.d.ts +2 -1
  98. package/dist/retrieval/fusion.d.ts.map +1 -1
  99. package/dist/retrieval/fusion.js +4 -2
  100. package/dist/retrieval/fusion.js.map +1 -1
  101. package/dist/retrieval/hnsw.d.ts +49 -11
  102. package/dist/retrieval/hnsw.d.ts.map +1 -1
  103. package/dist/retrieval/hnsw.js +57 -29
  104. package/dist/retrieval/hnsw.js.map +1 -1
  105. package/dist/retrieval/index.d.ts +7 -3
  106. package/dist/retrieval/index.d.ts.map +1 -1
  107. package/dist/retrieval/index.js +5 -2
  108. package/dist/retrieval/index.js.map +1 -1
  109. package/dist/retrieval/knowledge-index/bytes.d.ts +43 -0
  110. package/dist/retrieval/knowledge-index/bytes.d.ts.map +1 -0
  111. package/dist/retrieval/knowledge-index/bytes.js +151 -0
  112. package/dist/retrieval/knowledge-index/bytes.js.map +1 -0
  113. package/dist/retrieval/knowledge-index/cascade.d.ts +37 -0
  114. package/dist/retrieval/knowledge-index/cascade.d.ts.map +1 -0
  115. package/dist/retrieval/knowledge-index/cascade.js +111 -0
  116. package/dist/retrieval/knowledge-index/cascade.js.map +1 -0
  117. package/dist/retrieval/knowledge-index/compact.d.ts +18 -0
  118. package/dist/retrieval/knowledge-index/compact.d.ts.map +1 -0
  119. package/dist/retrieval/knowledge-index/compact.js +64 -0
  120. package/dist/retrieval/knowledge-index/compact.js.map +1 -0
  121. package/dist/retrieval/knowledge-index/csr.d.ts +41 -0
  122. package/dist/retrieval/knowledge-index/csr.d.ts.map +1 -0
  123. package/dist/retrieval/knowledge-index/csr.js +76 -0
  124. package/dist/retrieval/knowledge-index/csr.js.map +1 -0
  125. package/dist/retrieval/knowledge-index/digest.d.ts +21 -0
  126. package/dist/retrieval/knowledge-index/digest.d.ts.map +1 -0
  127. package/dist/retrieval/knowledge-index/digest.js +50 -0
  128. package/dist/retrieval/knowledge-index/digest.js.map +1 -0
  129. package/dist/retrieval/knowledge-index/embeddings.d.ts +32 -0
  130. package/dist/retrieval/knowledge-index/embeddings.d.ts.map +1 -0
  131. package/dist/retrieval/knowledge-index/embeddings.js +78 -0
  132. package/dist/retrieval/knowledge-index/embeddings.js.map +1 -0
  133. package/dist/retrieval/knowledge-index/expand.d.ts +19 -0
  134. package/dist/retrieval/knowledge-index/expand.d.ts.map +1 -0
  135. package/dist/retrieval/knowledge-index/expand.js +49 -0
  136. package/dist/retrieval/knowledge-index/expand.js.map +1 -0
  137. package/dist/retrieval/knowledge-index/index.d.ts +40 -0
  138. package/dist/retrieval/knowledge-index/index.d.ts.map +1 -0
  139. package/dist/retrieval/knowledge-index/index.js +134 -0
  140. package/dist/retrieval/knowledge-index/index.js.map +1 -0
  141. package/dist/retrieval/knowledge-index/lexical.d.ts +26 -0
  142. package/dist/retrieval/knowledge-index/lexical.d.ts.map +1 -0
  143. package/dist/retrieval/knowledge-index/lexical.js +76 -0
  144. package/dist/retrieval/knowledge-index/lexical.js.map +1 -0
  145. package/dist/retrieval/knowledge-index/pairs.d.ts +22 -0
  146. package/dist/retrieval/knowledge-index/pairs.d.ts.map +1 -0
  147. package/dist/retrieval/knowledge-index/pairs.js +51 -0
  148. package/dist/retrieval/knowledge-index/pairs.js.map +1 -0
  149. package/dist/retrieval/knowledge-index/prune.d.ts +21 -0
  150. package/dist/retrieval/knowledge-index/prune.d.ts.map +1 -0
  151. package/dist/retrieval/knowledge-index/prune.js +43 -0
  152. package/dist/retrieval/knowledge-index/prune.js.map +1 -0
  153. package/dist/retrieval/knowledge-index/quantize.d.ts +37 -0
  154. package/dist/retrieval/knowledge-index/quantize.d.ts.map +1 -0
  155. package/dist/retrieval/knowledge-index/quantize.js +77 -0
  156. package/dist/retrieval/knowledge-index/quantize.js.map +1 -0
  157. package/dist/retrieval/knowledge-index/serialize.d.ts +18 -0
  158. package/dist/retrieval/knowledge-index/serialize.d.ts.map +1 -0
  159. package/dist/retrieval/knowledge-index/serialize.js +123 -0
  160. package/dist/retrieval/knowledge-index/serialize.js.map +1 -0
  161. package/dist/retrieval/knowledge-index/sha256.d.ts +30 -0
  162. package/dist/retrieval/knowledge-index/sha256.d.ts.map +1 -0
  163. package/dist/retrieval/knowledge-index/sha256.js +130 -0
  164. package/dist/retrieval/knowledge-index/sha256.js.map +1 -0
  165. package/dist/retrieval/knowledge-index/state.d.ts +55 -0
  166. package/dist/retrieval/knowledge-index/state.d.ts.map +1 -0
  167. package/dist/retrieval/knowledge-index/state.js +67 -0
  168. package/dist/retrieval/knowledge-index/state.js.map +1 -0
  169. package/dist/retrieval/knowledge-index/tokenize.d.ts +20 -0
  170. package/dist/retrieval/knowledge-index/tokenize.d.ts.map +1 -0
  171. package/dist/retrieval/knowledge-index/tokenize.js +42 -0
  172. package/dist/retrieval/knowledge-index/tokenize.js.map +1 -0
  173. package/dist/retrieval/knowledge-index/traverse.d.ts +26 -0
  174. package/dist/retrieval/knowledge-index/traverse.d.ts.map +1 -0
  175. package/dist/retrieval/knowledge-index/traverse.js +90 -0
  176. package/dist/retrieval/knowledge-index/traverse.js.map +1 -0
  177. package/dist/retrieval/knowledge-index/types.d.ts +85 -0
  178. package/dist/retrieval/knowledge-index/types.d.ts.map +1 -0
  179. package/dist/retrieval/knowledge-index/types.js +9 -0
  180. package/dist/retrieval/knowledge-index/types.js.map +1 -0
  181. package/dist/retrieval/mmr.d.ts +11 -4
  182. package/dist/retrieval/mmr.d.ts.map +1 -1
  183. package/dist/retrieval/mmr.js +28 -8
  184. package/dist/retrieval/mmr.js.map +1 -1
  185. package/dist/retrieval/order.d.ts +41 -0
  186. package/dist/retrieval/order.d.ts.map +1 -0
  187. package/dist/retrieval/order.js +54 -0
  188. package/dist/retrieval/order.js.map +1 -0
  189. package/dist/retrieval/weightedFusion.d.ts +2 -1
  190. package/dist/retrieval/weightedFusion.d.ts.map +1 -1
  191. package/dist/retrieval/weightedFusion.js +7 -5
  192. package/dist/retrieval/weightedFusion.js.map +1 -1
  193. package/dist/vectorstore/MemoryVectorStore.d.ts.map +1 -1
  194. package/dist/vectorstore/MemoryVectorStore.js +2 -1
  195. package/dist/vectorstore/MemoryVectorStore.js.map +1 -1
  196. package/package.json +8 -3
  197. package/src/ingest/IngestionPipeline.ts +3 -15
  198. package/src/ingest/docsExtract.ts +219 -0
  199. package/src/ingest/hash.ts +22 -0
  200. package/src/ingest/parsers.ts +4 -0
  201. package/src/ingest/types.ts +7 -0
  202. package/src/knowledge/build.test.ts +150 -0
  203. package/src/knowledge/build.ts +266 -0
  204. package/src/knowledge/cards.ts +130 -0
  205. package/src/knowledge/docs.test.ts +118 -0
  206. package/src/knowledge/findings.ts +159 -0
  207. package/src/knowledge/importance.ts +72 -0
  208. package/src/knowledge/index.ts +53 -0
  209. package/src/knowledge/judge.ts +231 -0
  210. package/src/knowledge/model.ts +312 -0
  211. package/src/knowledge/packs/docs.ts +91 -0
  212. package/src/knowledge/partitions.test.ts +98 -0
  213. package/src/knowledge/partitions.ts +119 -0
  214. package/src/knowledge/plan.ts +115 -0
  215. package/src/knowledge/refs.ts +108 -0
  216. package/src/knowledge/resolve.ts +78 -0
  217. package/src/knowledge/review.test.ts +180 -0
  218. package/src/knowledge/review.ts +235 -0
  219. package/src/knowledge/sources.ts +146 -0
  220. package/src/knowledge/stores/localSnapshotStore.test.ts +82 -0
  221. package/src/knowledge/stores/localSnapshotStore.ts +159 -0
  222. package/src/knowledge/testkit.test-util.ts +92 -0
  223. package/src/knowledge/trace.test.ts +91 -0
  224. package/src/knowledge/trace.ts +111 -0
  225. package/src/knowledge/truncate.ts +118 -0
  226. package/src/memory/SemanticRecall.ts +3 -1
  227. package/src/retrieval/HybridRetriever.ts +3 -1
  228. package/src/retrieval/bm25.ts +5 -3
  229. package/src/retrieval/fusion.ts +5 -2
  230. package/src/retrieval/hnsw.ts +82 -35
  231. package/src/retrieval/index.ts +17 -3
  232. package/src/retrieval/knowledge-index/bytes.ts +166 -0
  233. package/src/retrieval/knowledge-index/cascade.ts +125 -0
  234. package/src/retrieval/knowledge-index/compact.ts +68 -0
  235. package/src/retrieval/knowledge-index/csr.ts +102 -0
  236. package/src/retrieval/knowledge-index/digest.ts +54 -0
  237. package/src/retrieval/knowledge-index/embeddings.ts +85 -0
  238. package/src/retrieval/knowledge-index/expand.ts +55 -0
  239. package/src/retrieval/knowledge-index/index.ts +162 -0
  240. package/src/retrieval/knowledge-index/lexical.ts +78 -0
  241. package/src/retrieval/knowledge-index/pairs.ts +57 -0
  242. package/src/retrieval/knowledge-index/prune.ts +48 -0
  243. package/src/retrieval/knowledge-index/quantize.ts +78 -0
  244. package/src/retrieval/knowledge-index/serialize.ts +124 -0
  245. package/src/retrieval/knowledge-index/sha256.ts +122 -0
  246. package/src/retrieval/knowledge-index/state.ts +108 -0
  247. package/src/retrieval/knowledge-index/tokenize.ts +41 -0
  248. package/src/retrieval/knowledge-index/traverse.ts +95 -0
  249. package/src/retrieval/knowledge-index/types.ts +78 -0
  250. package/src/retrieval/mmr.ts +35 -10
  251. package/src/retrieval/order.ts +68 -0
  252. package/src/retrieval/weightedFusion.ts +8 -5
  253. package/src/vectorstore/MemoryVectorStore.ts +2 -1
@@ -0,0 +1,72 @@
1
+ /**
2
+ * knowledge/importance.ts — deterministic PageRank (node size in the view, the
3
+ * truncation order, the cards' "top neighbours").
4
+ *
5
+ * Fixed iterations, damping 0.85, weighted out-edges, dangling mass spread uniformly,
6
+ * nodes iterated in ref order so the floating-point sums are the same everywhere.
7
+ * Scores are normalized so the top node is 1, and rounded to 6 decimals.
8
+ */
9
+
10
+ import type { KNode, NodeRef } from './model.js';
11
+ import { compareStr, round6 } from './refs.js';
12
+
13
+ export const PAGERANK_DAMPING = 0.85;
14
+ export const PAGERANK_ITERATIONS = 40;
15
+
16
+ export function pageRank(
17
+ refs: readonly NodeRef[],
18
+ edges: ReadonlyArray<{ from: NodeRef; to: NodeRef; weight: number }>,
19
+ iterations: number = PAGERANK_ITERATIONS,
20
+ damping: number = PAGERANK_DAMPING,
21
+ ): Map<NodeRef, number> {
22
+ const order = [...new Set(refs)].sort(compareStr);
23
+ const n = order.length;
24
+ const out = new Map<NodeRef, number>();
25
+ if (n === 0) return out;
26
+ const index = new Map(order.map((r, i) => [r, i] as const));
27
+ const outWeight = new Float64Array(n);
28
+ const inbound: Array<Array<{ from: number; w: number }>> = order.map(() => []);
29
+ const sorted = [...edges].sort((a, b) => compareStr(a.from, b.from) || compareStr(a.to, b.to));
30
+ for (const e of sorted) {
31
+ const f = index.get(e.from), t = index.get(e.to);
32
+ const w = e.weight > 0 ? e.weight : 0;
33
+ if (f === undefined || t === undefined || f === t || w === 0) continue;
34
+ outWeight[f] += w;
35
+ inbound[t]!.push({ from: f, w });
36
+ }
37
+ let rank = new Float64Array(n).fill(1 / n);
38
+ for (let it = 0; it < iterations; it++) {
39
+ let dangling = 0;
40
+ for (let i = 0; i < n; i++) if (outWeight[i] === 0) dangling += rank[i]!;
41
+ const next = new Float64Array(n);
42
+ const base = (1 - damping) / n + (damping * dangling) / n;
43
+ for (let i = 0; i < n; i++) {
44
+ let sum = 0;
45
+ for (const { from, w } of inbound[i]!) sum += (rank[from]! * w) / outWeight[from]!;
46
+ next[i] = base + damping * sum;
47
+ }
48
+ rank = next;
49
+ }
50
+ let max = 0;
51
+ for (let i = 0; i < n; i++) max = Math.max(max, rank[i]!);
52
+ for (let i = 0; i < n; i++) out.set(order[i]!, max > 0 ? round6(rank[i]! / max) : 0);
53
+ return out;
54
+ }
55
+
56
+ /** Importance descending, then ref — the one order "top N" means everywhere. */
57
+ export function byImportance(a: Pick<KNode, 'importance' | 'ref'>, b: Pick<KNode, 'importance' | 'ref'>): number {
58
+ return b.importance - a.importance || compareStr(a.ref, b.ref);
59
+ }
60
+
61
+ /** Area importance: the sum of its members' importance, renormalized so the top area is 1. */
62
+ export function areaImportance(members: ReadonlyMap<NodeRef, readonly number[]>): Map<NodeRef, number> {
63
+ const sums = new Map<NodeRef, number>();
64
+ let max = 0;
65
+ for (const area of [...members.keys()].sort(compareStr)) {
66
+ const sum = members.get(area)!.reduce((s, v) => s + v, 0);
67
+ sums.set(area, sum);
68
+ max = Math.max(max, sum);
69
+ }
70
+ for (const [area, sum] of sums) sums.set(area, max > 0 ? round6(sum / max) : 0);
71
+ return sums;
72
+ }
@@ -0,0 +1,53 @@
1
+ /**
2
+ * `@seanhogg/builderforce-memory/knowledge` — the knowledge graph: facts connected to each
3
+ * other and to the sources they came from (PRD 38, Builderforce.ai/docs/specs/38-prd-knowledge-graph.md).
4
+ *
5
+ * Pure TypeScript: runs in the browser, a Worker and Node. Hosts supply ports only — a
6
+ * DirectorySource, a KnowledgeStore, a JudgementPort — never graph logic.
7
+ */
8
+
9
+ export type {
10
+ NodeRef, Origin, StoredOrigin, KNode, EdgeVerdict, KEdge, Extracted, ExtractContext, Finding, FindingRule,
11
+ PartitionRule, PartitionRegistry, JudgementSpec, JudgementRequest, JudgedClaim, JudgedEntity, JudgementResult,
12
+ JudgementPort, AnalyzerPort, KnowledgeParser, KnowledgePack, StoredFact, StoredRelation, Scope, ScopeRecord,
13
+ KnowledgeStore, DirectorySource, LiveOverlay, KnowledgeGraph, ReviewPlan, ReviewProgress, ReviewReport,
14
+ ReviewArgs, BuildGraphInput, BuildGraphOptions, TraceStep, Trace,
15
+ } from './model.js';
16
+
17
+ export { buildGraph, graphDigest, DEFAULT_MAX_NODES, MAX_NODES_CAP } from './build.js';
18
+ export { truncateGraph, TRUNCATION_HARD_CAP, UNANCHORED_AREA } from './truncate.js';
19
+ export type { TruncateOptions } from './truncate.js';
20
+ export { pageRank, byImportance, areaImportance, PAGERANK_DAMPING, PAGERANK_ITERATIONS } from './importance.js';
21
+ export {
22
+ loadPartitions, partitionOf, partitionOrder, verdictOf, globToRegExp, matchGlob, TOP_LEVEL_FOLDER,
23
+ } from './partitions.js';
24
+ export {
25
+ traceGraph, toMermaid, DEFAULT_TRACE_EDGE_KINDS, DEFAULT_TRACE_DEPTH, DEFAULT_TRACE_STEPS, UNPARTITIONED_LANE,
26
+ } from './trace.js';
27
+ export type { LanedTrace, TraceOptions } from './trace.js';
28
+ export {
29
+ computeFindings, genericFindings, findingId, requestKeyFor, components, CLONE_GROUP_BADGE, MAX_FINDING_NODES,
30
+ } from './findings.js';
31
+ export {
32
+ resolveEntities, knownKeysOf, possibleSameFindings, normalizeKey, entityName, entityKey,
33
+ } from './resolve.js';
34
+ export type { ResolvedEntity } from './resolve.js';
35
+ export { toCards } from './cards.js';
36
+ export { planReview, planDetail, AVG_SECTION_BYTES } from './plan.js';
37
+ export type { PlanOptions, PlannedFile, PlanDetail } from './plan.js';
38
+ export { reviewDirectory, assertStoredBatch } from './review.js';
39
+ export {
40
+ validateExtract, validateCompare, DEFAULT_REVIEW_MODEL_CALLS, REVIEW_FACT_SOURCE,
41
+ } from './judge.js';
42
+ export {
43
+ composePacks, ignoreMatcher, isBinaryPath, isBinaryText, mediaTypeOfPath, resolveRelativePath,
44
+ DEFAULT_MAX_FILE_BYTES, IGNORE_FILES, filesManifestKey, judgedManifestKey,
45
+ } from './sources.js';
46
+ export { srcRef, pathOfRef, fileRefOf, factRef, factKeyOf, slugOf, canonicalJson, compareStr } from './refs.js';
47
+ export { DOCS_PACK, DOCS_PACK_ID, brokenLinkRule, orphanDocumentRule } from './packs/docs.js';
48
+ export { frontMatterOf, SECTION_INDEX_BADGE } from '../ingest/docsExtract.js';
49
+ export { LocalSnapshotStore, localSnapshotStore } from './stores/localSnapshotStore.js';
50
+ export type { SnapshotPersistence, KnowledgeSnapshot, LocalSnapshotStoreOptions } from './stores/localSnapshotStore.js';
51
+
52
+ // The knowledge index (search, expand, near-duplicate pairs) ships on the same subpath.
53
+ export * from '../retrieval/knowledge-index/index.js';
@@ -0,0 +1,231 @@
1
+ /**
2
+ * knowledge/judge.ts — the judgement pass of a review: the only step that costs model
3
+ * calls (PRD 38 §4.3).
4
+ *
5
+ * • extract: one call per changed section of a judged kind → claims (`claim:<hash>`) and
6
+ * entities (`ent:<pack>:<name>`) as `model` facts anchored with the file's content
7
+ * hash, plus `about` / `mentions` relations;
8
+ * • compare: near-duplicate claim pairs → `contradicts` / `agrees` relations.
9
+ *
10
+ * Every response is schema-validated; invalid items are dropped and counted. The budget
11
+ * (calls and a deadline) is shared across the whole review.
12
+ */
13
+
14
+ import { hashText } from '../ingest/hash.js';
15
+ import type {
16
+ JudgedClaim, JudgedEntity, JudgementPort, JudgementResult, NodeRef, StoredFact, StoredRelation,
17
+ } from './model.js';
18
+ import type { SimilarPair } from '../retrieval/knowledge-index/types.js';
19
+ import { entityKey, normalizeKey } from './resolve.js';
20
+ import { compareStr, factRef } from './refs.js';
21
+
22
+ export const DEFAULT_REVIEW_MODEL_CALLS = 200;
23
+ export const REVIEW_FACT_SOURCE = 'knowledge-review';
24
+ const MAX_TERM = 200;
25
+ const MAX_NAME = 120;
26
+ const MAX_QUOTE_CHARS = 300;
27
+ const MAX_QUOTE_LINES = 3;
28
+
29
+ export interface JudgeBudget {
30
+ modelCalls: number;
31
+ deadline: number;
32
+ used: number;
33
+ now(): number;
34
+ }
35
+
36
+ export const budgetLeft = (b: JudgeBudget): boolean => b.used < b.modelCalls && b.now() < b.deadline;
37
+
38
+ /** Facts and relations a review will write, keyed for upsert. */
39
+ export class JudgedBatch {
40
+ readonly facts = new Map<string, StoredFact>();
41
+ readonly relations = new Map<string, StoredRelation>();
42
+ judged = 0;
43
+ unjudged = 0;
44
+ invalid = 0;
45
+
46
+ fact(f: StoredFact): void { this.facts.set(f.key, f); }
47
+
48
+ relation(r: StoredRelation): void { this.relations.set(`${r.from}\u0000${r.to}\u0000${r.kind}`, r); }
49
+
50
+ take(): { facts: StoredFact[]; relations: StoredRelation[] } {
51
+ const out = {
52
+ facts: [...this.facts.values()].sort((a, b) => compareStr(a.key, b.key)),
53
+ relations: [...this.relations.values()].sort((a, b) =>
54
+ compareStr(a.from, b.from) || compareStr(a.to, b.to) || compareStr(a.kind, b.kind)),
55
+ };
56
+ this.facts.clear();
57
+ this.relations.clear();
58
+ return out;
59
+ }
60
+ }
61
+
62
+ const str = (v: unknown, max: number, allowEmpty = false): string | undefined => {
63
+ if (typeof v !== 'string') return undefined;
64
+ const t = v.trim();
65
+ return (t || allowEmpty) && t.length <= max ? t : undefined;
66
+ };
67
+
68
+ function validClaim(c: unknown): JudgedClaim | undefined {
69
+ if (!c || typeof c !== 'object') return undefined;
70
+ const o = c as Record<string, unknown>;
71
+ const subject = str(o['subject'], MAX_TERM), predicate = str(o['predicate'], MAX_TERM), object = str(o['object'], MAX_TERM);
72
+ if (!subject || !predicate || !object || typeof o['quote'] !== 'string') return undefined;
73
+ const quote = (o['quote'] as string).split(/\r?\n/).slice(0, MAX_QUOTE_LINES).join('\n').trim().slice(0, MAX_QUOTE_CHARS);
74
+ return { subject, predicate, object, quote };
75
+ }
76
+
77
+ function validEntity(e: unknown): JudgedEntity | undefined {
78
+ if (!e || typeof e !== 'object') return undefined;
79
+ const o = e as Record<string, unknown>;
80
+ const name = str(o['name'], MAX_NAME), type = str(o['type'], MAX_NAME, true);
81
+ return name && normalizeKey(name) && type !== undefined ? { name, type: type || 'thing' } : undefined;
82
+ }
83
+
84
+ /** The valid claims and entities a response holds for `ref`; everything else counted invalid. */
85
+ export function validateExtract(result: unknown, ref: NodeRef): { claims: JudgedClaim[]; entities: JudgedEntity[]; invalid: number } {
86
+ const out = { claims: [] as JudgedClaim[], entities: [] as JudgedEntity[], invalid: 0 };
87
+ if (!result || typeof result !== 'object') { out.invalid = 1; return out; }
88
+ const r = result as Partial<JudgementResult>;
89
+ out.invalid += typeof r.invalid === 'number' && r.invalid > 0 ? Math.floor(r.invalid) : 0;
90
+ if (!Array.isArray(r.extract)) { out.invalid += 1; return out; }
91
+ for (const item of r.extract as unknown[]) {
92
+ const it = item as { ref?: unknown; claims?: unknown; entities?: unknown } | null;
93
+ if (!it || it.ref !== ref || !Array.isArray(it.claims) || !Array.isArray(it.entities)) { out.invalid += 1; continue; }
94
+ for (const c of it.claims) { const v = validClaim(c); if (v) out.claims.push(v); else out.invalid += 1; }
95
+ for (const e of it.entities) { const v = validEntity(e); if (v) out.entities.push(v); else out.invalid += 1; }
96
+ }
97
+ return out;
98
+ }
99
+
100
+ export function validateCompare(result: unknown, a: NodeRef, b: NodeRef): { relation?: 'contradicts' | 'agrees' | 'unrelated'; confidence: number; invalid: number } {
101
+ if (!result || typeof result !== 'object') return { confidence: 0, invalid: 1 };
102
+ const r = result as Partial<JudgementResult>;
103
+ let invalid = typeof r.invalid === 'number' && r.invalid > 0 ? Math.floor(r.invalid) : 0;
104
+ for (const item of Array.isArray(r.compare) ? r.compare : []) {
105
+ const ok = item && ((item.a === a && item.b === b) || (item.a === b && item.b === a))
106
+ && ['contradicts', 'agrees', 'unrelated'].includes(item.relation)
107
+ && typeof item.confidence === 'number' && item.confidence >= 0 && item.confidence <= 1;
108
+ if (ok) return { relation: item.relation, confidence: item.confidence, invalid };
109
+ invalid += 1;
110
+ }
111
+ return { confidence: 0, invalid: Math.max(invalid, 1) };
112
+ }
113
+
114
+ /** One model call, or a counted invalid when the port throws (an abort is re-thrown). */
115
+ async function call(port: JudgementPort, req: Parameters<JudgementPort['judge']>[0], b: JudgeBudget): Promise<unknown> {
116
+ b.used += 1;
117
+ try {
118
+ return await port.judge(req);
119
+ } catch (err) {
120
+ if (req.signal?.aborted) throw err;
121
+ return undefined;
122
+ }
123
+ }
124
+
125
+ export interface SectionToJudge { ref: NodeRef; text: string }
126
+
127
+ export interface ExtractArgs {
128
+ port: JudgementPort;
129
+ pack: string;
130
+ path: string;
131
+ fileHash: string;
132
+ sections: readonly SectionToJudge[];
133
+ budget: JudgeBudget;
134
+ batch: JudgedBatch;
135
+ /** Entity facts by key: stored ones plus this review's, merged across files. */
136
+ entities: Map<string, StoredFact>;
137
+ signal?: AbortSignal;
138
+ /** Called for each section actually judged (for the judged-sections manifest). */
139
+ onJudged(ref: NodeRef): void;
140
+ }
141
+
142
+ /** Judges each section within the budget; the rest are counted `unjudged`. */
143
+ export async function judgeSections(a: ExtractArgs): Promise<Array<{ ref: NodeRef; text: string }>> {
144
+ const claims: Array<{ ref: NodeRef; text: string }> = [];
145
+ const anchor = { path: a.path, contentHash: a.fileHash };
146
+ for (const section of a.sections) {
147
+ if (!budgetLeft(a.budget)) { a.batch.unjudged += 1; continue; }
148
+ const result = await call(a.port, {
149
+ kind: 'extract', items: [{ ref: section.ref, text: section.text, sourceHash: a.fileHash }],
150
+ ...(a.signal ? { signal: a.signal } : {}),
151
+ }, a.budget);
152
+ const v = validateExtract(result, section.ref);
153
+ a.batch.invalid += v.invalid;
154
+ a.batch.judged += 1;
155
+ a.onJudged(section.ref);
156
+ const rel = (from: NodeRef, to: NodeRef, kind: string) =>
157
+ a.batch.relation({ from, to, kind, weight: 1, origin: 'model', sourceHash: a.fileHash });
158
+
159
+ const entityRefs = new Map<string, NodeRef>();
160
+ for (const e of v.entities) {
161
+ const key = entityKey(a.pack, e.name);
162
+ const prior = a.entities.get(key);
163
+ const anchors = [...(prior?.fileAnchors ?? []).filter((x) => x.path !== a.path), anchor]
164
+ .sort((x, y) => compareStr(x.path, y.path));
165
+ const fact: StoredFact = {
166
+ key, content: prior?.content ?? `${e.name} (${e.type})`, source: REVIEW_FACT_SOURCE,
167
+ importance: prior?.importance ?? 0.5, origin: prior?.origin ?? 'model', fileAnchors: anchors,
168
+ };
169
+ a.entities.set(key, fact);
170
+ a.batch.fact(fact);
171
+ entityRefs.set(normalizeKey(e.name), key);
172
+ rel(section.ref, key, 'mentions');
173
+ }
174
+ for (const c of v.claims) {
175
+ const key = `claim:${hashText([section.ref, c.subject, c.predicate, c.object].join('\u0000'))}`;
176
+ const text = `${c.subject} ${c.predicate} ${c.object}`;
177
+ a.batch.fact({
178
+ key, content: c.quote ? `${text} — “${c.quote}”` : text, source: REVIEW_FACT_SOURCE,
179
+ importance: 0.5, origin: 'model', fileAnchors: [anchor],
180
+ });
181
+ const ref = factRef(key);
182
+ rel(ref, section.ref, 'about');
183
+ for (const term of [c.subject, c.object]) {
184
+ const ent = entityRefs.get(normalizeKey(term));
185
+ if (ent) rel(ref, ent, 'mentions');
186
+ }
187
+ claims.push({ ref, text });
188
+ }
189
+ }
190
+ return claims;
191
+ }
192
+
193
+ export interface CompareArgs {
194
+ port: JudgementPort;
195
+ pairs: readonly SimilarPair[];
196
+ /** Claim ref → text and the content hash it was judged from. */
197
+ claims: ReadonlyMap<string, { text: string; hash: string }>;
198
+ budget: JudgeBudget;
199
+ batch: JudgedBatch;
200
+ signal?: AbortSignal;
201
+ }
202
+
203
+ /** Near-duplicate claim pairs, most similar first, compared within the budget. */
204
+ export async function compareClaims(a: CompareArgs): Promise<void> {
205
+ const pairs = [...a.pairs]
206
+ .filter((p) => p.a !== p.b && a.claims.has(p.a) && a.claims.has(p.b))
207
+ .map((p) => (compareStr(p.a, p.b) <= 0 ? p : { a: p.b, b: p.a, similarity: p.similarity }))
208
+ .sort((x, y) => y.similarity - x.similarity || compareStr(x.a, y.a) || compareStr(x.b, y.b));
209
+ const seen = new Set<string>();
210
+ for (const p of pairs) {
211
+ const id = `${p.a}\u0000${p.b}`;
212
+ if (seen.has(id)) continue;
213
+ seen.add(id);
214
+ if (!budgetLeft(a.budget)) { a.batch.unjudged += 1; continue; }
215
+ const ca = a.claims.get(p.a)!, cb = a.claims.get(p.b)!;
216
+ const A = p.a as NodeRef, B = p.b as NodeRef;
217
+ const result = await call(a.port, {
218
+ kind: 'compare',
219
+ items: [{ ref: A, text: ca.text, sourceHash: ca.hash }, { ref: B, text: cb.text, sourceHash: cb.hash }],
220
+ ...(a.signal ? { signal: a.signal } : {}),
221
+ }, a.budget);
222
+ const v = validateCompare(result, A, B);
223
+ a.batch.invalid += v.invalid;
224
+ a.batch.judged += 1;
225
+ if (v.relation === 'contradicts' || v.relation === 'agrees') {
226
+ a.batch.relation({
227
+ from: A, to: B, kind: v.relation, weight: 1, origin: 'model', confidence: v.confidence, sourceHash: ca.hash,
228
+ });
229
+ }
230
+ }
231
+ }
@@ -0,0 +1,312 @@
1
+ /**
2
+ * The knowledge graph model — memory's structure for KNOWLEDGE: facts connected to each
3
+ * other and to the sources they came from (PRD: Builderforce.ai/docs/specs/38-prd-knowledge-graph.md).
4
+ *
5
+ * Generic: it knows sources, entities, facts and relations — never imports or headings.
6
+ * Packs (docs builtin, code registered by the host) supply that meaning as DATA.
7
+ *
8
+ * Store judgement, compute perception: `extracted` nodes/edges are recomputed from sources
9
+ * (cached by content hash) and are NEVER written to a KnowledgeStore; `model`/`human`/`agent`
10
+ * ones are stored with the content hash they were judged from and go stale when it changes.
11
+ */
12
+
13
+ import type { IngestManifest, SourceDocument, ParsedSection } from '../ingest/types.js';
14
+ import type { IndexDoc, SimilarPair } from '../retrieval/knowledge-index/types.js';
15
+
16
+ /** `src:${rootId}::${path}` · `ent:${pack}:${normalizedName}` · `fact:${key}` (claims are `fact:claim:${hash}`). */
17
+ export type NodeRef = `src:${string}` | `ent:${string}` | `fact:${string}`;
18
+
19
+ export type Origin = 'extracted' | 'model' | 'human' | 'agent';
20
+ export type StoredOrigin = Exclude<Origin, 'extracted'>;
21
+
22
+ export interface KNode {
23
+ ref: NodeRef;
24
+ /** Pack-defined: 'file' | 'section' | 'symbol' | 'area' | 'entity' | 'claim' | 'fact' … */
25
+ kind: string;
26
+ label: string;
27
+ pack: string;
28
+ partition?: string;
29
+ area?: NodeRef;
30
+ /** PageRank over the joined graph, normalized 0–1 (areas: sum of members, renormalized). */
31
+ importance: number;
32
+ origin: Origin;
33
+ sourceHash?: string;
34
+ stale?: boolean;
35
+ /** Pack data, e.g. { added, removed }, { unused: 'file' }, { line: 12 }. */
36
+ badges?: Record<string, number | string>;
37
+ }
38
+
39
+ export type EdgeVerdict = 'allowed' | 'same' | 'skips' | 'upward';
40
+
41
+ export interface KEdge {
42
+ from: NodeRef;
43
+ to: NodeRef;
44
+ /** 'references' | 'links' | 'contains' | 'mentions' | 'duplicate' | 'near-duplicate'
45
+ * | 'contradicts' | 'agrees' | 'supersedes' | 'about' | 'co-changed' — a new kind is a value. */
46
+ kind: string;
47
+ weight: number;
48
+ origin: Origin;
49
+ confidence?: number;
50
+ verdict?: EdgeVerdict;
51
+ cycle?: boolean;
52
+ }
53
+
54
+ /** Output of a pack's deterministic extractor (or analyzer) for one source. Never stored. */
55
+ export interface Extracted {
56
+ sourceRef: NodeRef;
57
+ sourceHash: string;
58
+ nodes: KNode[];
59
+ edges: KEdge[];
60
+ /** Exact keys for entity resolution and the index's exact tier. */
61
+ keys: Array<{ ref: NodeRef; key: string }>;
62
+ }
63
+
64
+ export interface ExtractContext {
65
+ rootId: string;
66
+ /** Normalized entity keys known so far (for `mentions`). */
67
+ knownKeys: ReadonlyMap<string, NodeRef>;
68
+ /** Resolves a relative link/import from `fromPath` to a root-relative path, or undefined. */
69
+ resolvePath(fromPath: string, target: string): string | undefined;
70
+ }
71
+
72
+ /** A finding a person can act on. `kind` is pack-defined; `requestKey` names an i18n template. */
73
+ export interface Finding {
74
+ id: string;
75
+ kind: string;
76
+ pack: string;
77
+ nodes: NodeRef[];
78
+ severity: 'info' | 'warn' | 'error';
79
+ /** Numbers/strings for the label and the Fix-with-agent template. */
80
+ params: Record<string, string | number>;
81
+ /** i18n key of the fixed Fix-with-agent request (e.g. 'knowledgeGraph.fix.cloneGroup'). */
82
+ requestKey?: string;
83
+ }
84
+
85
+ export interface FindingRule {
86
+ id: string;
87
+ find(graph: KnowledgeGraph): Finding[];
88
+ }
89
+
90
+ export interface PartitionRule {
91
+ id: string;
92
+ /** 0 = top. */
93
+ order: number;
94
+ /** Globs over the source path; first matching rule wins. */
95
+ match: string[];
96
+ }
97
+
98
+ export interface PartitionRegistry {
99
+ partitions: PartitionRule[];
100
+ /** from-partition → partitions it may use. Absent ⇒ no verdicts. */
101
+ allow?: Record<string, string[]>;
102
+ }
103
+
104
+ /** What the judgement model is asked for one section kind; output is schema-validated. */
105
+ export interface JudgementSpec {
106
+ /** Node kinds whose text is judged (e.g. ['section']). */
107
+ nodeKinds: string[];
108
+ /** Ask for claims[] / entities[] per node; and contradicts|agrees for near-duplicate claim pairs. */
109
+ claims: boolean;
110
+ entities: boolean;
111
+ contradictions: boolean;
112
+ }
113
+
114
+ export interface JudgementRequest {
115
+ kind: 'extract' | 'compare';
116
+ /** extract: one node's text; compare: two claims. */
117
+ items: Array<{ ref: NodeRef; text: string; sourceHash: string }>;
118
+ signal?: AbortSignal;
119
+ }
120
+
121
+ export interface JudgedClaim { subject: string; predicate: string; object: string; quote: string }
122
+ export interface JudgedEntity { name: string; type: string }
123
+
124
+ export interface JudgementResult {
125
+ extract?: Array<{ ref: NodeRef; claims: JudgedClaim[]; entities: JudgedEntity[] }>;
126
+ compare?: Array<{ a: NodeRef; b: NodeRef; relation: 'contradicts' | 'agrees' | 'unrelated'; confidence: number }>;
127
+ /** Items dropped for invalid output. */
128
+ invalid: number;
129
+ }
130
+
131
+ /** A routed model call. Hosts implement it (VS Code/on-prem: local engines → gateway; web: POST …/knowledge/judge). */
132
+ export interface JudgementPort {
133
+ judge(req: JudgementRequest): Promise<JudgementResult>;
134
+ }
135
+
136
+ /** Host-run tools for a pack (code: dependency-cruiser/jscpd/knip). Returns extra extracted edges/badges. */
137
+ export interface AnalyzerPort {
138
+ analyse(rootId: string, signal?: AbortSignal): Promise<Extracted[]>;
139
+ }
140
+
141
+ /** A host-supplied parser that also extracts graph structure. `extract` is the optional ingest extension. */
142
+ export interface KnowledgeParser {
143
+ id: string;
144
+ accepts(source: SourceDocument): boolean;
145
+ parse(source: SourceDocument): ParsedSection[];
146
+ extract?(source: SourceDocument, sections: ParsedSection[], ctx: ExtractContext): Extracted;
147
+ }
148
+
149
+ export interface KnowledgePack {
150
+ /** 'docs' | 'code' */
151
+ id: string;
152
+ parsers: KnowledgeParser[];
153
+ partitions?: PartitionRegistry;
154
+ findings?: FindingRule[];
155
+ judgement?: JudgementSpec;
156
+ analyzers?: AnalyzerPort;
157
+ }
158
+
159
+ export interface StoredFact {
160
+ /** project_facts.key — judgement nodes use `ent:<pack>:<name>` / `claim:<hash>` namespaces. */
161
+ key: string;
162
+ content: string;
163
+ source: string;
164
+ importance: number;
165
+ origin: StoredOrigin;
166
+ fileAnchors: Array<{ path: string; line?: number; contentHash?: string }>;
167
+ updatedAt?: string;
168
+ createdBy?: number | null;
169
+ }
170
+
171
+ export interface StoredRelation {
172
+ from: NodeRef;
173
+ to: NodeRef;
174
+ kind: string;
175
+ weight: number;
176
+ origin: StoredOrigin;
177
+ confidence?: number;
178
+ sourceHash?: string;
179
+ staleAt?: string | null;
180
+ }
181
+
182
+ export interface Scope {
183
+ rootId: string;
184
+ }
185
+
186
+ export interface ScopeRecord {
187
+ rootId: string;
188
+ title: string;
189
+ packs: string[];
190
+ lastDigest?: string;
191
+ lastReviewedAt?: string;
192
+ }
193
+
194
+ /** Where knowledge is persisted: the platform API adapter or the local snapshot adapter. */
195
+ export interface KnowledgeStore {
196
+ scopes(): Promise<ScopeRecord[]>;
197
+ facts(scope: Scope): Promise<StoredFact[]>;
198
+ relations(scope: Scope): Promise<StoredRelation[]>;
199
+ write(batch: { scope: ScopeRecord; facts: StoredFact[]; relations: StoredRelation[] }): Promise<void>;
200
+ /** Marks model facts/relations judged from `sourceRef` at a hash other than `currentHash` stale. */
201
+ markStale(scope: Scope, sourceRef: NodeRef, currentHash: string): Promise<void>;
202
+ /** Extracted output cache, by content hash (never knowledge — perception). */
203
+ getExtracted?(hash: string): Promise<Extracted[] | undefined>;
204
+ putExtracted?(hash: string, extracted: Extracted[]): Promise<void>;
205
+ }
206
+
207
+ /** Where files come from: one adapter per host. */
208
+ export interface DirectorySource {
209
+ rootId: string;
210
+ title: string;
211
+ list(): AsyncIterable<{ path: string; hash: string; size: number }>;
212
+ read(path: string): Promise<SourceDocument>;
213
+ watch?(cb: (paths: string[]) => void): () => void;
214
+ }
215
+
216
+ export interface LiveOverlay {
217
+ changes: Array<{ path: string; status: string; added: number; removed: number }>;
218
+ /** Files touched together in one run → co-changed edges. */
219
+ runFiles?: string[];
220
+ }
221
+
222
+ export interface KnowledgeGraph {
223
+ scope: { rootId: string; packs: string[]; digest: string };
224
+ nodes: KNode[];
225
+ edges: KEdge[];
226
+ findings: Finding[];
227
+ stats: { sources: number; nodes: number; edges: number; stale: number; truncated: boolean };
228
+ }
229
+
230
+ export interface ReviewPlan {
231
+ rootId: string;
232
+ files: number;
233
+ byPack: Record<string, number>;
234
+ unchanged: number;
235
+ changed: string[];
236
+ removed: string[];
237
+ skipped: Array<{ path: string; reason: 'ignored' | 'binary' | 'too-large' | 'no-pack' }>;
238
+ /** ≈ judgement calls for changed files (sections of judged kinds). */
239
+ estimatedModelCalls: number;
240
+ }
241
+
242
+ export interface ReviewProgress {
243
+ phase: 'plan' | 'extract' | 'resolve' | 'judge' | 'write' | 'index' | 'done';
244
+ done: number;
245
+ total: number;
246
+ path?: string;
247
+ }
248
+
249
+ export interface ReviewReport {
250
+ rootId: string;
251
+ digest: string;
252
+ extracted: number;
253
+ judged: number;
254
+ /** Sections left extracted-only because the budget ran out. */
255
+ unjudged: number;
256
+ invalidJudgements: number;
257
+ factsWritten: number;
258
+ relationsWritten: number;
259
+ staleMarked: number;
260
+ aborted: boolean;
261
+ durationMs: number;
262
+ }
263
+
264
+ export interface ReviewArgs {
265
+ source: DirectorySource;
266
+ store: KnowledgeStore;
267
+ packs: KnowledgePack[];
268
+ manifest: IngestManifest;
269
+ judgement?: JudgementPort;
270
+ /** Near-duplicate claim pairs to compare / possible-same entities; from the knowledge index. */
271
+ similar?: (ids: string[]) => Promise<SimilarPair[]>;
272
+ budget: { modelCalls: number; ms: number };
273
+ signal?: AbortSignal;
274
+ onProgress?(p: ReviewProgress): void;
275
+ }
276
+
277
+ export interface BuildGraphInput {
278
+ rootId: string;
279
+ extracted: Extracted[];
280
+ facts: StoredFact[];
281
+ relations: StoredRelation[];
282
+ packs: KnowledgePack[];
283
+ live?: LiveOverlay;
284
+ nearDuplicates?: SimilarPair[];
285
+ }
286
+
287
+ export interface BuildGraphOptions {
288
+ /** Default 400; cap 2000. */
289
+ maxNodes?: number;
290
+ focus?: NodeRef[];
291
+ }
292
+
293
+ export interface TraceStep {
294
+ from: NodeRef;
295
+ to: NodeRef;
296
+ depth: number;
297
+ kind: string;
298
+ verdict?: EdgeVerdict;
299
+ cycle?: boolean;
300
+ back?: boolean;
301
+ }
302
+
303
+ export interface Trace {
304
+ from: NodeRef;
305
+ /** Partitions present, in registry order (lanes). */
306
+ lanes: string[];
307
+ steps: TraceStep[];
308
+ truncated: boolean;
309
+ }
310
+
311
+ /** Re-exported for card consumers. */
312
+ export type { IndexDoc };