melchizedek-agents 0.9.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/AGENT_SETUP.md +182 -0
- package/DOCUMENTATION.md +614 -0
- package/LICENSE +21 -0
- package/QUICKSTART.md +164 -0
- package/README.md +151 -0
- package/config/agents/README.md +27 -0
- package/config/agents/examples/ares.yaml +74 -0
- package/config/agents/examples/cartographers.yaml +115 -0
- package/config/agents/examples/claude.yaml +17 -0
- package/config/agents/examples/council.yaml +65 -0
- package/config/agents/examples/critic.yaml +74 -0
- package/config/agents/examples/delegation.yaml +29 -0
- package/config/agents/examples/hierarchical.yaml +26 -0
- package/config/agents/examples/image_production.yaml +112 -0
- package/config/agents/examples/librarian.yaml +75 -0
- package/config/agents/examples/model_zoo.yaml +81 -0
- package/config/agents/examples/patient_advocate.yaml +107 -0
- package/config/agents/examples/scriptorium.yaml +96 -0
- package/config/agents/examples/style_council.yaml +83 -0
- package/config/agents/examples/syndicate.yaml +34 -0
- package/config/agents/examples/tutor.yaml +66 -0
- package/config/agents/syndicateSchema.yaml +291 -0
- package/dist/lib/config.d.ts +108 -0
- package/dist/lib/config.js +110 -0
- package/dist/lib/dispatch.d.ts +133 -0
- package/dist/lib/dispatch.js +182 -0
- package/dist/lib/grounding.d.ts +28 -0
- package/dist/lib/grounding.js +61 -0
- package/dist/lib/index.d.ts +14 -0
- package/dist/lib/index.js +13 -0
- package/dist/lib/loadEnv.d.ts +21 -0
- package/dist/lib/loadEnv.js +58 -0
- package/dist/lib/loadSyndicate.d.ts +177 -0
- package/dist/lib/loadSyndicate.js +264 -0
- package/dist/lib/memory/supabaseMemoryService.d.ts +118 -0
- package/dist/lib/memory/supabaseMemoryService.js +499 -0
- package/dist/lib/models/claudeLlm.d.ts +93 -0
- package/dist/lib/models/claudeLlm.js +332 -0
- package/dist/lib/models/gptLlm.d.ts +110 -0
- package/dist/lib/models/gptLlm.js +497 -0
- package/dist/lib/models/grokLlm.d.ts +64 -0
- package/dist/lib/models/grokLlm.js +86 -0
- package/dist/lib/models/ollamaLlm.d.ts +105 -0
- package/dist/lib/models/ollamaLlm.js +134 -0
- package/dist/lib/models/openAiCompatibleLlm.d.ts +125 -0
- package/dist/lib/models/openAiCompatibleLlm.js +331 -0
- package/dist/lib/models/providerMap.d.ts +28 -0
- package/dist/lib/models/providerMap.js +45 -0
- package/dist/lib/models/registry.d.ts +71 -0
- package/dist/lib/models/registry.js +159 -0
- package/dist/lib/models/schemaNormalize.d.ts +22 -0
- package/dist/lib/models/schemaNormalize.js +58 -0
- package/dist/lib/observability/supabaseSpanExporter.d.ts +35 -0
- package/dist/lib/observability/supabaseSpanExporter.js +115 -0
- package/dist/lib/observability/tracer.d.ts +44 -0
- package/dist/lib/observability/tracer.js +242 -0
- package/dist/lib/persistence/supabaseProvider.d.ts +44 -0
- package/dist/lib/persistence/supabaseProvider.js +88 -0
- package/dist/lib/session/supabaseSessionService.d.ts +26 -0
- package/dist/lib/session/supabaseSessionService.js +193 -0
- package/dist/lib/session/transcript.d.ts +193 -0
- package/dist/lib/session/transcript.js +309 -0
- package/dist/lib/toolRegistry.d.ts +15 -0
- package/dist/lib/toolRegistry.js +58 -0
- package/dist/lib/tools/collectionsSearchTool.d.ts +55 -0
- package/dist/lib/tools/collectionsSearchTool.js +86 -0
- package/dist/lib/tools/generateImageTool.d.ts +16 -0
- package/dist/lib/tools/generateImageTool.js +119 -0
- package/dist/lib/tools/inspectImageTool.d.ts +21 -0
- package/dist/lib/tools/inspectImageTool.js +97 -0
- package/dist/lib/tools/mcpToolFactory.d.ts +2 -0
- package/dist/lib/tools/mcpToolFactory.js +116 -0
- package/dist/lib/tools/toolContract.d.ts +78 -0
- package/dist/lib/tools/toolContract.js +129 -0
- package/dist/lib/tools/webExtractTool.d.ts +70 -0
- package/dist/lib/tools/webExtractTool.js +461 -0
- package/dist/lib/tools/webSearchTool.d.ts +53 -0
- package/dist/lib/tools/webSearchTool.js +126 -0
- package/dist/lib/tools/wikiTools.d.ts +112 -0
- package/dist/lib/tools/wikiTools.js +697 -0
- package/dist/lib/tools/xSearchTool.d.ts +61 -0
- package/dist/lib/tools/xSearchTool.js +125 -0
- package/dist/lib/wiki/agentRun.d.ts +39 -0
- package/dist/lib/wiki/agentRun.js +79 -0
- package/dist/lib/wiki/builder.d.ts +71 -0
- package/dist/lib/wiki/builder.js +165 -0
- package/dist/lib/wiki/dive.d.ts +56 -0
- package/dist/lib/wiki/dive.js +208 -0
- package/dist/lib/wiki/entities.d.ts +220 -0
- package/dist/lib/wiki/entities.js +661 -0
- package/dist/lib/wiki/extract.d.ts +97 -0
- package/dist/lib/wiki/extract.js +305 -0
- package/dist/lib/wiki/fill.d.ts +37 -0
- package/dist/lib/wiki/fill.js +175 -0
- package/dist/lib/wiki/format.d.ts +132 -0
- package/dist/lib/wiki/format.js +177 -0
- package/dist/lib/wiki/graph.d.ts +80 -0
- package/dist/lib/wiki/graph.js +147 -0
- package/dist/lib/wiki/lint.d.ts +58 -0
- package/dist/lib/wiki/lint.js +267 -0
- package/dist/lib/wiki/markdown.d.ts +127 -0
- package/dist/lib/wiki/markdown.js +336 -0
- package/dist/lib/wiki/pipeline.d.ts +69 -0
- package/dist/lib/wiki/pipeline.js +119 -0
- package/dist/lib/wiki/vault.d.ts +84 -0
- package/dist/lib/wiki/vault.js +194 -0
- package/dist/scripts/a2a_server.d.ts +2 -0
- package/dist/scripts/a2a_server.js +997 -0
- package/dist/scripts/syndicate_chat.d.ts +2 -0
- package/dist/scripts/syndicate_chat.js +485 -0
- package/package.json +96 -0
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* lib/wiki/dive.ts — search scoring and "repo dive" reading paths.
|
|
3
|
+
*
|
|
4
|
+
* WHY this file exists:
|
|
5
|
+
* The two navigation questions an agent asks a wiki are "which documents
|
|
6
|
+
* mention X?" (search) and "I have a TASK — what should I read, in what
|
|
7
|
+
* order, within a budget?" (dive). Both are answered here with plain
|
|
8
|
+
* lexical scoring plus graph propagation — deliberately NO model call and
|
|
9
|
+
* NO embeddings: navigation must be instant, deterministic, and free, so
|
|
10
|
+
* an agent can call it dozens of times while working. Semantic answering
|
|
11
|
+
* belongs to wiki_query (an agent using these primitives), not here.
|
|
12
|
+
*
|
|
13
|
+
* Dive = scored seeds + link expansion. Seeds are the best lexical
|
|
14
|
+
* matches; the knowledge graph then pulls in what the seeds are wired to
|
|
15
|
+
* (score decaying with link distance), because in a well-gardened bundle
|
|
16
|
+
* PROXIMITY IN THE GRAPH IS RELEVANCE. The result is an ordered reading
|
|
17
|
+
* plan — orientation first (indexes), then matched concepts, then linked
|
|
18
|
+
* context — trimmed to a word budget.
|
|
19
|
+
*/
|
|
20
|
+
import { buildGraph } from "./graph.js";
|
|
21
|
+
import { conceptDocs } from "./vault.js";
|
|
22
|
+
// ── Query tokenisation ───────────────────────────────────────────────────────
|
|
23
|
+
const STOPWORDS = new Set([
|
|
24
|
+
'a', 'an', 'and', 'are', 'as', 'at', 'be', 'by', 'do', 'does', 'for',
|
|
25
|
+
'from', 'how', 'i', 'in', 'is', 'it', 'my', 'of', 'on', 'or', 'that',
|
|
26
|
+
'the', 'this', 'to', 'us', 'was', 'we', 'what', 'when', 'where', 'which', 'why',
|
|
27
|
+
'with', 'you',
|
|
28
|
+
]);
|
|
29
|
+
export function tokenize(text) {
|
|
30
|
+
return [
|
|
31
|
+
...new Set(text
|
|
32
|
+
.toLowerCase()
|
|
33
|
+
.split(/[^a-z0-9_-]+/)
|
|
34
|
+
.filter((t) => t.length > 1 && !STOPWORDS.has(t))),
|
|
35
|
+
];
|
|
36
|
+
}
|
|
37
|
+
function fieldTokens(doc) {
|
|
38
|
+
return {
|
|
39
|
+
title: new Set(tokenize(doc.title)),
|
|
40
|
+
tags: new Set((doc.fm?.tags ?? []).flatMap(tokenize)),
|
|
41
|
+
description: new Set(tokenize(doc.fm?.description ?? '')),
|
|
42
|
+
headings: new Set(doc.parsed.headings.flatMap((h) => tokenize(h.text))),
|
|
43
|
+
path: new Set(tokenize(doc.bundlePath.replace(/\.md$/, ''))),
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
function findSnippet(doc, tokens) {
|
|
47
|
+
const lines = doc.parsed.raw.split('\n');
|
|
48
|
+
for (let i = doc.parsed.bodyStartLine - 1; i < lines.length; i++) {
|
|
49
|
+
const line = lines[i].trim();
|
|
50
|
+
if (line === '' || line.startsWith('#') || line.startsWith('<!--'))
|
|
51
|
+
continue;
|
|
52
|
+
const lower = line.toLowerCase();
|
|
53
|
+
if (tokens.some((t) => lower.includes(t))) {
|
|
54
|
+
return line.length > 180 ? `${line.slice(0, 177)}...` : line;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
return doc.fm?.description ?? '';
|
|
58
|
+
}
|
|
59
|
+
export function scoreDocs(vault, query) {
|
|
60
|
+
const tokens = tokenize(query);
|
|
61
|
+
if (tokens.length === 0)
|
|
62
|
+
return [];
|
|
63
|
+
const results = [];
|
|
64
|
+
for (const doc of conceptDocs(vault)) {
|
|
65
|
+
const fields = fieldTokens(doc);
|
|
66
|
+
const bodyLower = doc.parsed.raw.toLowerCase();
|
|
67
|
+
let score = 0;
|
|
68
|
+
const matched = new Set();
|
|
69
|
+
for (const token of tokens) {
|
|
70
|
+
let hit = false;
|
|
71
|
+
if (fields.title.has(token)) {
|
|
72
|
+
score += 5;
|
|
73
|
+
hit = true;
|
|
74
|
+
}
|
|
75
|
+
if (fields.tags.has(token)) {
|
|
76
|
+
score += 4;
|
|
77
|
+
hit = true;
|
|
78
|
+
}
|
|
79
|
+
if (fields.description.has(token)) {
|
|
80
|
+
score += 3;
|
|
81
|
+
hit = true;
|
|
82
|
+
}
|
|
83
|
+
if (fields.headings.has(token)) {
|
|
84
|
+
score += 2;
|
|
85
|
+
hit = true;
|
|
86
|
+
}
|
|
87
|
+
if (fields.path.has(token)) {
|
|
88
|
+
score += 2;
|
|
89
|
+
hit = true;
|
|
90
|
+
}
|
|
91
|
+
if (doc.fm?.type === token) {
|
|
92
|
+
score += 3;
|
|
93
|
+
hit = true;
|
|
94
|
+
}
|
|
95
|
+
if (!hit) {
|
|
96
|
+
// Body-only hits count, weakly, capped so length can't dominate.
|
|
97
|
+
let count = 0;
|
|
98
|
+
let idx = bodyLower.indexOf(token);
|
|
99
|
+
while (idx !== -1 && count < 5) {
|
|
100
|
+
count++;
|
|
101
|
+
idx = bodyLower.indexOf(token, idx + token.length);
|
|
102
|
+
}
|
|
103
|
+
if (count > 0) {
|
|
104
|
+
score += count * 0.5;
|
|
105
|
+
hit = true;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
if (hit)
|
|
109
|
+
matched.add(token);
|
|
110
|
+
}
|
|
111
|
+
// Multi-token queries reward docs matching MORE distinct tokens.
|
|
112
|
+
if (matched.size > 1)
|
|
113
|
+
score *= 1 + 0.25 * (matched.size - 1);
|
|
114
|
+
if (score > 0) {
|
|
115
|
+
results.push({
|
|
116
|
+
doc,
|
|
117
|
+
score: Math.round(score * 100) / 100,
|
|
118
|
+
matched: [...matched],
|
|
119
|
+
snippet: findSnippet(doc, tokens),
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
return results.sort((a, b) => b.score - a.score);
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Assemble an ordered reading plan for a task. Deterministic and
|
|
127
|
+
* model-free; the caller (an agent on a knowledge task) does the reading.
|
|
128
|
+
*/
|
|
129
|
+
export function repoDive(vault, task, budgetWords = 6000, graph = buildGraph(vault)) {
|
|
130
|
+
const scored = scoreDocs(vault, task);
|
|
131
|
+
const seeds = scored.slice(0, 5);
|
|
132
|
+
// Propagate seed scores along resolved links, decaying by distance.
|
|
133
|
+
const propagated = new Map();
|
|
134
|
+
for (const seed of seeds) {
|
|
135
|
+
let frontier = [seed.doc.bundlePath];
|
|
136
|
+
const seen = new Set(frontier);
|
|
137
|
+
for (let dist = 1; dist <= 2; dist++) {
|
|
138
|
+
const next = [];
|
|
139
|
+
for (const path of frontier) {
|
|
140
|
+
const edges = [
|
|
141
|
+
...(graph.outbound.get(path) ?? []),
|
|
142
|
+
...(graph.inbound.get(path) ?? []).map((e) => ({ ...e, target: e.source })),
|
|
143
|
+
];
|
|
144
|
+
for (const edge of edges) {
|
|
145
|
+
if (!edge.resolved || seen.has(edge.target))
|
|
146
|
+
continue;
|
|
147
|
+
seen.add(edge.target);
|
|
148
|
+
const gain = seed.score * Math.pow(0.4, dist);
|
|
149
|
+
const prev = propagated.get(edge.target);
|
|
150
|
+
if (!prev || prev.score < gain) {
|
|
151
|
+
propagated.set(edge.target, { score: gain, via: seed.doc.bundlePath, dist });
|
|
152
|
+
}
|
|
153
|
+
next.push(edge.target);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
frontier = next;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
const stops = [];
|
|
160
|
+
const included = new Set();
|
|
161
|
+
let totalWords = 0;
|
|
162
|
+
const overflow = [];
|
|
163
|
+
const push = (doc, role, reason) => {
|
|
164
|
+
if (included.has(doc.bundlePath))
|
|
165
|
+
return true;
|
|
166
|
+
if (totalWords + doc.wordCount > budgetWords && stops.length > 0) {
|
|
167
|
+
overflow.push({ path: doc.bundlePath, title: doc.title });
|
|
168
|
+
return false;
|
|
169
|
+
}
|
|
170
|
+
included.add(doc.bundlePath);
|
|
171
|
+
totalWords += doc.wordCount;
|
|
172
|
+
stops.push({
|
|
173
|
+
order: stops.length + 1,
|
|
174
|
+
path: doc.bundlePath,
|
|
175
|
+
title: doc.title,
|
|
176
|
+
wordCount: doc.wordCount,
|
|
177
|
+
reason,
|
|
178
|
+
role,
|
|
179
|
+
});
|
|
180
|
+
return true;
|
|
181
|
+
};
|
|
182
|
+
// 1. Orient: the root index, then the indexes of directories holding seeds.
|
|
183
|
+
const rootIndex = vault.docs.get('/index.md');
|
|
184
|
+
if (rootIndex)
|
|
185
|
+
push(rootIndex, 'orient', 'bundle map — start here');
|
|
186
|
+
const seedDirs = new Set(seeds.map((s) => s.doc.bundlePath.replace(/\/[^/]+$/, '/index.md')));
|
|
187
|
+
for (const dirIndex of seedDirs) {
|
|
188
|
+
const doc = vault.docs.get(dirIndex);
|
|
189
|
+
if (doc && dirIndex !== '/index.md') {
|
|
190
|
+
push(doc, 'orient', 'directory of matched concepts');
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
// 2. Core: the matched concepts, best first.
|
|
194
|
+
for (const seed of seeds) {
|
|
195
|
+
push(seed.doc, 'core', `matched: ${seed.matched.join(', ')}`);
|
|
196
|
+
}
|
|
197
|
+
// 3. Context: what the core is wired to, best propagated score first.
|
|
198
|
+
const contextRanked = [...propagated.entries()]
|
|
199
|
+
.filter(([path]) => !included.has(path))
|
|
200
|
+
.sort((a, b) => b[1].score - a[1].score);
|
|
201
|
+
for (const [path, info] of contextRanked) {
|
|
202
|
+
const doc = vault.docs.get(path);
|
|
203
|
+
if (!doc || doc.kind === 'log')
|
|
204
|
+
continue;
|
|
205
|
+
push(doc, 'context', `linked ${info.dist} hop(s) from ${info.via}`);
|
|
206
|
+
}
|
|
207
|
+
return { task, stops, totalWords, budgetWords, overflow };
|
|
208
|
+
}
|
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* lib/wiki/entities.ts — the entity layer: a typed graph over repo truth.
|
|
3
|
+
*
|
|
4
|
+
* WHY this file exists:
|
|
5
|
+
* lib/wiki/graph.ts derives ONE graph: documents joined by markdown
|
|
6
|
+
* links. That graph answers "what should I read next", but it cannot
|
|
7
|
+
* answer "which syndicates call web_search", "what breaks without
|
|
8
|
+
* XAI_API_KEY", or "which decision constrains the export pipeline" —
|
|
9
|
+
* because the things those questions name (agents, tools, models,
|
|
10
|
+
* tables, env vars, decisions) are not documents. They are ENTITIES,
|
|
11
|
+
* and the relations between them are typed, not hyperlinks.
|
|
12
|
+
*
|
|
13
|
+
* This module is that second layer, and it keeps the same discipline as
|
|
14
|
+
* the first: nodes and edges are DERIVED from repo truth wherever a
|
|
15
|
+
* parser can see them, and merely ASSERTED where only judgment can.
|
|
16
|
+
* The two tiers are labelled, never mixed:
|
|
17
|
+
*
|
|
18
|
+
* extracted — a parser read it out of a YAML, a contract, DDL, an
|
|
19
|
+
* import statement, a link. Rebuildable, disposable,
|
|
20
|
+
* always regenerated by `wiki:build --graph`.
|
|
21
|
+
* inferred — a human or an agent read prose and asserted it, with
|
|
22
|
+
* evidence and an actor id. Never machine-overwritten;
|
|
23
|
+
* lives in .graph/relations.json beside the snapshot.
|
|
24
|
+
*
|
|
25
|
+
* Everything here is bundle-agnostic. WHAT to extract from (this repo's
|
|
26
|
+
* agent YAMLs, its SQL, its package.json) is the caller's business —
|
|
27
|
+
* scripts/wiki/build.ts does that privately. This module owns the
|
|
28
|
+
* vocabulary, the graph mechanics, the two stores, and the lint rules,
|
|
29
|
+
* including the same private-closure invariant the document lint
|
|
30
|
+
* enforces (wiki/decisions/0003): a public node may never point at a
|
|
31
|
+
* private one, so any public slice of this graph is closed by
|
|
32
|
+
* construction.
|
|
33
|
+
*/
|
|
34
|
+
import { z } from 'zod';
|
|
35
|
+
import type { WikiGraph } from './graph.ts';
|
|
36
|
+
/**
|
|
37
|
+
* What kinds of thing this graph knows about. A document keeps its OKF
|
|
38
|
+
* identity (the bundle path); every other node is `kind:name`, so the two
|
|
39
|
+
* namespaces can never collide — a doc id always starts with '/'.
|
|
40
|
+
*/
|
|
41
|
+
export declare const NODE_KINDS: Record<string, string>;
|
|
42
|
+
export declare function entityId(kind: string, name: string): string;
|
|
43
|
+
export declare function kindOf(id: string): string;
|
|
44
|
+
export declare function nameOf(id: string): string;
|
|
45
|
+
export type RelationTier = 'extracted' | 'inferred';
|
|
46
|
+
export interface RelationSpec {
|
|
47
|
+
tier: RelationTier;
|
|
48
|
+
/** Reads as `<from> <phrase> <to>`. */
|
|
49
|
+
phrase: string;
|
|
50
|
+
gloss: string;
|
|
51
|
+
/** Node kinds this relation may start from (extracted tier only). */
|
|
52
|
+
from?: string[];
|
|
53
|
+
/** Node kinds this relation may point at (extracted tier only). */
|
|
54
|
+
to?: string[];
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* The whole relation vocabulary. Extracted relations carry kind
|
|
58
|
+
* constraints because a parser produces them and a violation is a bug;
|
|
59
|
+
* inferred relations do not, because judgment legitimately connects
|
|
60
|
+
* anything to anything — the evidence field is what disciplines them.
|
|
61
|
+
*/
|
|
62
|
+
export declare const RELATIONS: Record<string, RelationSpec>;
|
|
63
|
+
export declare function relationsByTier(tier: RelationTier): string[];
|
|
64
|
+
export interface EntityNode {
|
|
65
|
+
id: string;
|
|
66
|
+
kind: string;
|
|
67
|
+
label: string;
|
|
68
|
+
/** Repo-relative path or bundle path where this node's truth lives. */
|
|
69
|
+
source?: string;
|
|
70
|
+
/** True when the node belongs to the never-exported annex. */
|
|
71
|
+
private?: boolean;
|
|
72
|
+
attrs?: Record<string, unknown>;
|
|
73
|
+
}
|
|
74
|
+
export interface EntityEdge {
|
|
75
|
+
from: string;
|
|
76
|
+
to: string;
|
|
77
|
+
rel: string;
|
|
78
|
+
tier: RelationTier;
|
|
79
|
+
/** Where the claim comes from: a repo path, a line, or a quotation. */
|
|
80
|
+
evidence?: string;
|
|
81
|
+
/** Actor id — required on inferred edges. */
|
|
82
|
+
by?: string;
|
|
83
|
+
at?: string;
|
|
84
|
+
}
|
|
85
|
+
export interface EntityGraph {
|
|
86
|
+
nodes: Map<string, EntityNode>;
|
|
87
|
+
edges: EntityEdge[];
|
|
88
|
+
outbound: Map<string, EntityEdge[]>;
|
|
89
|
+
inbound: Map<string, EntityEdge[]>;
|
|
90
|
+
}
|
|
91
|
+
export declare function edgeKey(edge: Pick<EntityEdge, 'from' | 'rel' | 'to'>): string;
|
|
92
|
+
export declare function buildEntityGraph(nodes: EntityNode[], edges: EntityEdge[]): EntityGraph;
|
|
93
|
+
/** The document layer, restated as entities so both layers share one graph. */
|
|
94
|
+
export declare function docLayer(docGraph: WikiGraph): {
|
|
95
|
+
nodes: EntityNode[];
|
|
96
|
+
edges: EntityEdge[];
|
|
97
|
+
};
|
|
98
|
+
export interface NeighborStep {
|
|
99
|
+
node: EntityNode;
|
|
100
|
+
rel: string;
|
|
101
|
+
direction: 'out' | 'in';
|
|
102
|
+
distance: number;
|
|
103
|
+
via: string;
|
|
104
|
+
evidence?: string;
|
|
105
|
+
tier: RelationTier;
|
|
106
|
+
}
|
|
107
|
+
export interface NeighborOptions {
|
|
108
|
+
depth?: number;
|
|
109
|
+
direction?: 'out' | 'in' | 'both';
|
|
110
|
+
/** Restrict the walk to these relation ids. */
|
|
111
|
+
relations?: string[];
|
|
112
|
+
}
|
|
113
|
+
export declare function neighbors(graph: EntityGraph, originId: string, options?: NeighborOptions): NeighborStep[];
|
|
114
|
+
export interface PathStep {
|
|
115
|
+
node: EntityNode;
|
|
116
|
+
/** The relation that reached this node ('' for the origin). */
|
|
117
|
+
rel: string;
|
|
118
|
+
direction: 'out' | 'in' | 'origin';
|
|
119
|
+
}
|
|
120
|
+
/** Shortest connection between two nodes, walking edges in either direction. */
|
|
121
|
+
export declare function shortestPath(graph: EntityGraph, fromId: string, toId: string, maxDepth?: number): PathStep[] | null;
|
|
122
|
+
export interface NodeMatch {
|
|
123
|
+
node: EntityNode;
|
|
124
|
+
score: number;
|
|
125
|
+
}
|
|
126
|
+
/** Lexical node lookup: exact id, then id/label substring, then attrs. */
|
|
127
|
+
export declare function findNodes(graph: EntityGraph, query: string, limit?: number): NodeMatch[];
|
|
128
|
+
export declare function nodesOfKind(graph: EntityGraph, kind: string): EntityNode[];
|
|
129
|
+
export interface EntityStats {
|
|
130
|
+
nodes: number;
|
|
131
|
+
edges: number;
|
|
132
|
+
byKind: Record<string, number>;
|
|
133
|
+
byRelation: Record<string, number>;
|
|
134
|
+
byTier: Record<RelationTier, number>;
|
|
135
|
+
isolated: number;
|
|
136
|
+
}
|
|
137
|
+
export declare function entityStats(graph: EntityGraph): EntityStats;
|
|
138
|
+
export interface GraphFinding {
|
|
139
|
+
severity: 'error' | 'warning' | 'info';
|
|
140
|
+
rule: string;
|
|
141
|
+
message: string;
|
|
142
|
+
}
|
|
143
|
+
/** True for nodes that must never be referenced from a public one. */
|
|
144
|
+
export declare function isPrivateNode(node: EntityNode): boolean;
|
|
145
|
+
export declare function lintEntityGraph(graph: EntityGraph): GraphFinding[];
|
|
146
|
+
/**
|
|
147
|
+
* Both stores live in a dot-directory inside the bundle: the vault walker
|
|
148
|
+
* skips dotfiles, so they are invisible to every document operation, they
|
|
149
|
+
* travel with WIKI_ROOT, and the export pipeline (which copies *.md only)
|
|
150
|
+
* cannot ship them by accident.
|
|
151
|
+
*/
|
|
152
|
+
export declare function graphDir(wikiRoot: string): string;
|
|
153
|
+
export declare function snapshotPath(wikiRoot: string): string;
|
|
154
|
+
export declare function relationsPath(wikiRoot: string): string;
|
|
155
|
+
export declare const relationRecordSchema: z.ZodObject<{
|
|
156
|
+
from: z.ZodString;
|
|
157
|
+
to: z.ZodString;
|
|
158
|
+
rel: z.ZodString;
|
|
159
|
+
evidence: z.ZodString;
|
|
160
|
+
by: z.ZodString;
|
|
161
|
+
at: z.ZodOptional<z.ZodString>;
|
|
162
|
+
note: z.ZodOptional<z.ZodString>;
|
|
163
|
+
}, z.core.$loose>;
|
|
164
|
+
export type RelationRecord = z.infer<typeof relationRecordSchema>;
|
|
165
|
+
export declare const relationStoreSchema: z.ZodObject<{
|
|
166
|
+
version: z.ZodDefault<z.ZodNumber>;
|
|
167
|
+
relations: z.ZodDefault<z.ZodArray<z.ZodObject<{
|
|
168
|
+
from: z.ZodString;
|
|
169
|
+
to: z.ZodString;
|
|
170
|
+
rel: z.ZodString;
|
|
171
|
+
evidence: z.ZodString;
|
|
172
|
+
by: z.ZodString;
|
|
173
|
+
at: z.ZodOptional<z.ZodString>;
|
|
174
|
+
note: z.ZodOptional<z.ZodString>;
|
|
175
|
+
}, z.core.$loose>>>;
|
|
176
|
+
}, z.core.$loose>;
|
|
177
|
+
export type RelationStore = z.infer<typeof relationStoreSchema>;
|
|
178
|
+
export interface LoadedRelations {
|
|
179
|
+
records: RelationRecord[];
|
|
180
|
+
/** Malformed entries are reported, never thrown — one bad row can't blind the graph. */
|
|
181
|
+
issues: string[];
|
|
182
|
+
}
|
|
183
|
+
export declare function loadRelations(wikiRoot: string): LoadedRelations;
|
|
184
|
+
export declare function relationEdges(records: RelationRecord[]): EntityEdge[];
|
|
185
|
+
export declare function saveRelations(wikiRoot: string, records: RelationRecord[]): void;
|
|
186
|
+
export interface GraphSnapshot {
|
|
187
|
+
version: number;
|
|
188
|
+
generated: {
|
|
189
|
+
by: string;
|
|
190
|
+
at: string;
|
|
191
|
+
};
|
|
192
|
+
stats: EntityStats;
|
|
193
|
+
nodes: EntityNode[];
|
|
194
|
+
/** Extracted edges only — inferred ones are read live from relations.json. */
|
|
195
|
+
edges: EntityEdge[];
|
|
196
|
+
}
|
|
197
|
+
export declare function writeSnapshot(wikiRoot: string, graph: EntityGraph, generated: {
|
|
198
|
+
by: string;
|
|
199
|
+
at: string;
|
|
200
|
+
}): string;
|
|
201
|
+
export declare function readSnapshot(wikiRoot: string): GraphSnapshot | null;
|
|
202
|
+
export interface LoadedGraph {
|
|
203
|
+
graph: EntityGraph;
|
|
204
|
+
snapshot: GraphSnapshot | null;
|
|
205
|
+
/** Problems reading the asserted store — surfaced, never fatal. */
|
|
206
|
+
issues: string[];
|
|
207
|
+
inferredCount: number;
|
|
208
|
+
}
|
|
209
|
+
/**
|
|
210
|
+
* The read path every consumer uses: derived snapshot (structure, as of the
|
|
211
|
+
* last build) + asserted relations (read live, so a fresh assertion is
|
|
212
|
+
* visible immediately). Missing snapshot yields an empty graph plus the
|
|
213
|
+
* asserted edges, which lint will flag as dangling — honest, not silent.
|
|
214
|
+
*/
|
|
215
|
+
export declare function loadEntityGraph(wikiRoot: string): LoadedGraph;
|
|
216
|
+
/** Documents the snapshot has not seen yet — the staleness signal. */
|
|
217
|
+
export declare function snapshotDrift(snapshot: GraphSnapshot | null, currentDocPaths: string[]): {
|
|
218
|
+
missing: string[];
|
|
219
|
+
removed: string[];
|
|
220
|
+
};
|