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.
Files changed (111) hide show
  1. package/AGENT_SETUP.md +182 -0
  2. package/DOCUMENTATION.md +614 -0
  3. package/LICENSE +21 -0
  4. package/QUICKSTART.md +164 -0
  5. package/README.md +151 -0
  6. package/config/agents/README.md +27 -0
  7. package/config/agents/examples/ares.yaml +74 -0
  8. package/config/agents/examples/cartographers.yaml +115 -0
  9. package/config/agents/examples/claude.yaml +17 -0
  10. package/config/agents/examples/council.yaml +65 -0
  11. package/config/agents/examples/critic.yaml +74 -0
  12. package/config/agents/examples/delegation.yaml +29 -0
  13. package/config/agents/examples/hierarchical.yaml +26 -0
  14. package/config/agents/examples/image_production.yaml +112 -0
  15. package/config/agents/examples/librarian.yaml +75 -0
  16. package/config/agents/examples/model_zoo.yaml +81 -0
  17. package/config/agents/examples/patient_advocate.yaml +107 -0
  18. package/config/agents/examples/scriptorium.yaml +96 -0
  19. package/config/agents/examples/style_council.yaml +83 -0
  20. package/config/agents/examples/syndicate.yaml +34 -0
  21. package/config/agents/examples/tutor.yaml +66 -0
  22. package/config/agents/syndicateSchema.yaml +291 -0
  23. package/dist/lib/config.d.ts +108 -0
  24. package/dist/lib/config.js +110 -0
  25. package/dist/lib/dispatch.d.ts +133 -0
  26. package/dist/lib/dispatch.js +182 -0
  27. package/dist/lib/grounding.d.ts +28 -0
  28. package/dist/lib/grounding.js +61 -0
  29. package/dist/lib/index.d.ts +14 -0
  30. package/dist/lib/index.js +13 -0
  31. package/dist/lib/loadEnv.d.ts +21 -0
  32. package/dist/lib/loadEnv.js +58 -0
  33. package/dist/lib/loadSyndicate.d.ts +177 -0
  34. package/dist/lib/loadSyndicate.js +264 -0
  35. package/dist/lib/memory/supabaseMemoryService.d.ts +118 -0
  36. package/dist/lib/memory/supabaseMemoryService.js +499 -0
  37. package/dist/lib/models/claudeLlm.d.ts +93 -0
  38. package/dist/lib/models/claudeLlm.js +332 -0
  39. package/dist/lib/models/gptLlm.d.ts +110 -0
  40. package/dist/lib/models/gptLlm.js +497 -0
  41. package/dist/lib/models/grokLlm.d.ts +64 -0
  42. package/dist/lib/models/grokLlm.js +86 -0
  43. package/dist/lib/models/ollamaLlm.d.ts +105 -0
  44. package/dist/lib/models/ollamaLlm.js +134 -0
  45. package/dist/lib/models/openAiCompatibleLlm.d.ts +125 -0
  46. package/dist/lib/models/openAiCompatibleLlm.js +331 -0
  47. package/dist/lib/models/providerMap.d.ts +28 -0
  48. package/dist/lib/models/providerMap.js +45 -0
  49. package/dist/lib/models/registry.d.ts +71 -0
  50. package/dist/lib/models/registry.js +159 -0
  51. package/dist/lib/models/schemaNormalize.d.ts +22 -0
  52. package/dist/lib/models/schemaNormalize.js +58 -0
  53. package/dist/lib/observability/supabaseSpanExporter.d.ts +35 -0
  54. package/dist/lib/observability/supabaseSpanExporter.js +115 -0
  55. package/dist/lib/observability/tracer.d.ts +44 -0
  56. package/dist/lib/observability/tracer.js +242 -0
  57. package/dist/lib/persistence/supabaseProvider.d.ts +44 -0
  58. package/dist/lib/persistence/supabaseProvider.js +88 -0
  59. package/dist/lib/session/supabaseSessionService.d.ts +26 -0
  60. package/dist/lib/session/supabaseSessionService.js +193 -0
  61. package/dist/lib/session/transcript.d.ts +193 -0
  62. package/dist/lib/session/transcript.js +309 -0
  63. package/dist/lib/toolRegistry.d.ts +15 -0
  64. package/dist/lib/toolRegistry.js +58 -0
  65. package/dist/lib/tools/collectionsSearchTool.d.ts +55 -0
  66. package/dist/lib/tools/collectionsSearchTool.js +86 -0
  67. package/dist/lib/tools/generateImageTool.d.ts +16 -0
  68. package/dist/lib/tools/generateImageTool.js +119 -0
  69. package/dist/lib/tools/inspectImageTool.d.ts +21 -0
  70. package/dist/lib/tools/inspectImageTool.js +97 -0
  71. package/dist/lib/tools/mcpToolFactory.d.ts +2 -0
  72. package/dist/lib/tools/mcpToolFactory.js +116 -0
  73. package/dist/lib/tools/toolContract.d.ts +78 -0
  74. package/dist/lib/tools/toolContract.js +129 -0
  75. package/dist/lib/tools/webExtractTool.d.ts +70 -0
  76. package/dist/lib/tools/webExtractTool.js +461 -0
  77. package/dist/lib/tools/webSearchTool.d.ts +53 -0
  78. package/dist/lib/tools/webSearchTool.js +126 -0
  79. package/dist/lib/tools/wikiTools.d.ts +112 -0
  80. package/dist/lib/tools/wikiTools.js +697 -0
  81. package/dist/lib/tools/xSearchTool.d.ts +61 -0
  82. package/dist/lib/tools/xSearchTool.js +125 -0
  83. package/dist/lib/wiki/agentRun.d.ts +39 -0
  84. package/dist/lib/wiki/agentRun.js +79 -0
  85. package/dist/lib/wiki/builder.d.ts +71 -0
  86. package/dist/lib/wiki/builder.js +165 -0
  87. package/dist/lib/wiki/dive.d.ts +56 -0
  88. package/dist/lib/wiki/dive.js +208 -0
  89. package/dist/lib/wiki/entities.d.ts +220 -0
  90. package/dist/lib/wiki/entities.js +661 -0
  91. package/dist/lib/wiki/extract.d.ts +97 -0
  92. package/dist/lib/wiki/extract.js +305 -0
  93. package/dist/lib/wiki/fill.d.ts +37 -0
  94. package/dist/lib/wiki/fill.js +175 -0
  95. package/dist/lib/wiki/format.d.ts +132 -0
  96. package/dist/lib/wiki/format.js +177 -0
  97. package/dist/lib/wiki/graph.d.ts +80 -0
  98. package/dist/lib/wiki/graph.js +147 -0
  99. package/dist/lib/wiki/lint.d.ts +58 -0
  100. package/dist/lib/wiki/lint.js +267 -0
  101. package/dist/lib/wiki/markdown.d.ts +127 -0
  102. package/dist/lib/wiki/markdown.js +336 -0
  103. package/dist/lib/wiki/pipeline.d.ts +69 -0
  104. package/dist/lib/wiki/pipeline.js +119 -0
  105. package/dist/lib/wiki/vault.d.ts +84 -0
  106. package/dist/lib/wiki/vault.js +194 -0
  107. package/dist/scripts/a2a_server.d.ts +2 -0
  108. package/dist/scripts/a2a_server.js +997 -0
  109. package/dist/scripts/syndicate_chat.d.ts +2 -0
  110. package/dist/scripts/syndicate_chat.js +485 -0
  111. package/package.json +96 -0
@@ -0,0 +1,336 @@
1
+ /**
2
+ * lib/wiki/markdown.ts — structural markdown: one node model, two directions.
3
+ *
4
+ * WHY this file exists:
5
+ * The wiki needs to READ markdown structurally (frontmatter, headings,
6
+ * links, fenced code, edit markers — enough to lint, graph, and navigate)
7
+ * and to WRITE it structurally (docs assembled from typed parts, so links
8
+ * and frontmatter are correct by construction). Both directions share the
9
+ * shapes in this file.
10
+ *
11
+ * Deliberately NOT a CommonMark AST. A full parser (remark/unified) would
12
+ * add a dependency tree to a repo that has none for markdown, to answer
13
+ * questions we don't ask — inline emphasis, HTML blocks, tables-as-trees.
14
+ * The wiki's questions are structural: what does this doc declare, where
15
+ * are its sections, what does it link to, which regions are machine-owned.
16
+ * ~300 lines answer all of them with zero new dependencies
17
+ * (wiki/decisions/0002). Frontmatter YAML goes through the same `yaml`
18
+ * package the syndicate loader already uses.
19
+ *
20
+ * Editing is SURGICAL, never a re-serialise of the whole document: the
21
+ * parse keeps line positions, and edits splice exact line ranges (a fill
22
+ * slot's interior, a generated block's interior, a log insertion point).
23
+ * Prose the machine doesn't own is never reflowed.
24
+ *
25
+ * Marker grammar (HTML comments — invisible in rendered markdown):
26
+ * <!-- wiki:generated section="<id>" source="<path>" -->…<!-- /wiki:generated -->
27
+ * Machine-owned region; `wiki:build` rewrites its interior. `source`
28
+ * names the repo truth it derives from.
29
+ * <!-- wiki:fill slot="<id>" -->…<!-- /wiki:fill -->
30
+ * Prose slot. Structural builds leave its interior alone; the LLM pass
31
+ * (lib/wiki/fill.ts) replaces it while it still holds a TODO sentinel.
32
+ */
33
+ import { parse as parseYaml, stringify as stringifyYaml } from 'yaml';
34
+ /** Sentinel left in an unfilled slot; the fill pass replaces it. */
35
+ export const TODO_SENTINEL = '_TODO(fill):';
36
+ export function slotIsUnfilled(slot) {
37
+ const text = slot.content.trim();
38
+ return text === '' || text.startsWith(TODO_SENTINEL);
39
+ }
40
+ // ── Parsing ──────────────────────────────────────────────────────────────────
41
+ const FENCE_RE = /^ {0,3}(`{3,}|~{3,})\s*(\S*)/;
42
+ const HEADING_RE = /^(#{1,6})\s+(.+?)\s*#*\s*$/;
43
+ const MARKER_OPEN_RE = /^<!--\s*wiki:(fill|generated)\s+([^>]*?)\s*-->\s*$/;
44
+ const MARKER_CLOSE_RE = /^<!--\s*\/wiki:(fill|generated)\s*-->\s*$/;
45
+ const ATTR_RE = /([a-zA-Z_]+)="([^"]*)"/g;
46
+ const INLINE_LINK_RE = /(!?)\[([^\]]*)\]\(<?([^()<>\s]+(?:\([^()]*\))?)>?(?:\s+"[^"]*")?\)/g;
47
+ const REF_DEF_RE = /^ {0,3}\[([^\]]+)\]:\s+(\S+)/;
48
+ const REF_USE_RE = /(!?)\[([^\]]+)\]\[([^\]]*)\]/g;
49
+ const WIKILINK_RE = /\[\[([^\]]+)\]\]/g;
50
+ /** GitHub-style anchor slug — for addressing sections by name. */
51
+ export function slugify(text) {
52
+ return text
53
+ .toLowerCase()
54
+ .replace(/[`*_~]/g, '')
55
+ .replace(/[^\p{L}\p{N}\s-]/gu, '')
56
+ .trim()
57
+ .replace(/\s+/g, '-');
58
+ }
59
+ /** Mask `inline code` so syntax examples inside backticks never parse as links. */
60
+ function maskInlineCode(line) {
61
+ return line.replace(/`[^`]*`/g, (m) => ' '.repeat(m.length));
62
+ }
63
+ export function parseDoc(raw) {
64
+ const lines = raw.split('\n');
65
+ // Frontmatter block: `---` on line 1, closed by `---` or `...`.
66
+ let frontmatterRaw = null;
67
+ let frontmatter = null;
68
+ let frontmatterError;
69
+ let bodyStartLine = 1;
70
+ if (lines[0]?.trim() === '---') {
71
+ for (let i = 1; i < lines.length; i++) {
72
+ const t = lines[i].trim();
73
+ if (t === '---' || t === '...') {
74
+ frontmatterRaw = lines.slice(1, i).join('\n');
75
+ bodyStartLine = i + 2;
76
+ break;
77
+ }
78
+ }
79
+ if (frontmatterRaw !== null) {
80
+ try {
81
+ const parsed = parseYaml(frontmatterRaw);
82
+ if (parsed !== null && typeof parsed === 'object' && !Array.isArray(parsed)) {
83
+ frontmatter = parsed;
84
+ }
85
+ else if (parsed !== null) {
86
+ frontmatterError = 'frontmatter is not a YAML mapping';
87
+ }
88
+ }
89
+ catch (err) {
90
+ frontmatterError = err instanceof Error ? err.message : String(err);
91
+ }
92
+ }
93
+ }
94
+ const headings = [];
95
+ const links = [];
96
+ const wikilinks = [];
97
+ const fences = [];
98
+ const slots = [];
99
+ const generated = [];
100
+ const unclosedMarkers = [];
101
+ const refDefs = new Map();
102
+ let openFence = null;
103
+ let openMarker = null;
104
+ const refUses = [];
105
+ for (let i = bodyStartLine - 1; i < lines.length; i++) {
106
+ const line = lines[i];
107
+ const lineNo = i + 1;
108
+ // Fences first — nothing inside a fence is structure.
109
+ const fenceMatch = line.match(FENCE_RE);
110
+ if (fenceMatch) {
111
+ const [, marks, lang] = fenceMatch;
112
+ if (!openFence) {
113
+ openFence = { char: marks[0], len: marks.length, startLine: lineNo, lang };
114
+ continue;
115
+ }
116
+ if (marks[0] === openFence.char && marks.length >= openFence.len && lang === '') {
117
+ fences.push({ startLine: openFence.startLine, endLine: lineNo, lang: openFence.lang });
118
+ openFence = null;
119
+ continue;
120
+ }
121
+ }
122
+ if (openFence)
123
+ continue;
124
+ // Edit markers.
125
+ const open = line.match(MARKER_OPEN_RE);
126
+ if (open) {
127
+ if (openMarker)
128
+ unclosedMarkers.push({ kind: openMarker.kind, line: openMarker.line });
129
+ const attrs = {};
130
+ for (const m of open[2].matchAll(ATTR_RE))
131
+ attrs[m[1]] = m[2];
132
+ openMarker = {
133
+ kind: open[1],
134
+ id: attrs[open[1] === 'fill' ? 'slot' : 'section'] ?? '',
135
+ source: attrs.source,
136
+ line: lineNo,
137
+ };
138
+ continue;
139
+ }
140
+ const close = line.match(MARKER_CLOSE_RE);
141
+ if (close && openMarker && close[1] === openMarker.kind) {
142
+ const content = lines.slice(openMarker.line, lineNo - 1).join('\n');
143
+ if (openMarker.kind === 'fill') {
144
+ slots.push({ id: openMarker.id, startLine: openMarker.line, endLine: lineNo, content });
145
+ }
146
+ else {
147
+ generated.push({
148
+ id: openMarker.id,
149
+ source: openMarker.source,
150
+ startLine: openMarker.line,
151
+ endLine: lineNo,
152
+ content,
153
+ });
154
+ }
155
+ openMarker = null;
156
+ continue;
157
+ }
158
+ const headingMatch = line.match(HEADING_RE);
159
+ if (headingMatch) {
160
+ headings.push({
161
+ depth: headingMatch[1].length,
162
+ text: headingMatch[2],
163
+ slug: slugify(headingMatch[2]),
164
+ line: lineNo,
165
+ });
166
+ // Headings can hold links too — fall through.
167
+ }
168
+ const refDef = line.match(REF_DEF_RE);
169
+ if (refDef) {
170
+ refDefs.set(refDef[1].toLowerCase(), refDef[2]);
171
+ continue;
172
+ }
173
+ const masked = maskInlineCode(line);
174
+ for (const m of masked.matchAll(WIKILINK_RE)) {
175
+ wikilinks.push({ target: m[1], line: lineNo });
176
+ }
177
+ for (const m of masked.matchAll(INLINE_LINK_RE)) {
178
+ links.push({ isImage: m[1] === '!', text: m[2], target: m[3], line: lineNo });
179
+ }
180
+ // Reference-style uses resolve after all definitions are collected.
181
+ const noInline = masked.replace(INLINE_LINK_RE, (s) => ' '.repeat(s.length));
182
+ for (const m of noInline.matchAll(REF_USE_RE)) {
183
+ refUses.push({
184
+ isImage: m[1] === '!',
185
+ text: m[2],
186
+ label: (m[3] || m[2]).toLowerCase(),
187
+ line: lineNo,
188
+ });
189
+ }
190
+ }
191
+ if (openFence) {
192
+ fences.push({ startLine: openFence.startLine, endLine: lines.length, lang: openFence.lang });
193
+ }
194
+ if (openMarker)
195
+ unclosedMarkers.push({ kind: openMarker.kind, line: openMarker.line });
196
+ for (const use of refUses) {
197
+ const target = refDefs.get(use.label);
198
+ if (target) {
199
+ links.push({ isImage: use.isImage, text: use.text, target, line: use.line });
200
+ }
201
+ }
202
+ return {
203
+ raw,
204
+ frontmatterRaw,
205
+ frontmatter,
206
+ ...(frontmatterError !== undefined ? { frontmatterError } : {}),
207
+ bodyStartLine,
208
+ headings,
209
+ links,
210
+ wikilinks,
211
+ fences,
212
+ slots,
213
+ generated,
214
+ unclosedMarkers,
215
+ };
216
+ }
217
+ /** The section under the heading whose slug or text matches (case-insensitive). */
218
+ export function sectionSlice(doc, nameOrSlug) {
219
+ const want = nameOrSlug.toLowerCase();
220
+ const idx = doc.headings.findIndex((h) => h.slug === slugify(nameOrSlug) || h.text.toLowerCase() === want);
221
+ if (idx === -1)
222
+ return null;
223
+ const heading = doc.headings[idx];
224
+ const next = doc.headings.slice(idx + 1).find((h) => h.depth <= heading.depth);
225
+ const lines = doc.raw.split('\n');
226
+ const endLine = next ? next.line - 1 : lines.length;
227
+ return {
228
+ heading,
229
+ startLine: heading.line,
230
+ endLine,
231
+ text: lines.slice(heading.line - 1, endLine).join('\n'),
232
+ };
233
+ }
234
+ // ── Building (docs correct by construction) ──────────────────────────────────
235
+ /** Frontmatter key order — spec-reserved fields first, extras after. */
236
+ const FM_KEY_ORDER = [
237
+ 'okf_version',
238
+ 'type',
239
+ 'title',
240
+ 'description',
241
+ 'resource',
242
+ 'tags',
243
+ 'status',
244
+ 'stale_after',
245
+ 'generated',
246
+ 'verified',
247
+ 'sources',
248
+ ];
249
+ export function emitFrontmatter(fm) {
250
+ const ordered = {};
251
+ for (const key of FM_KEY_ORDER) {
252
+ if (fm[key] !== undefined)
253
+ ordered[key] = fm[key];
254
+ }
255
+ for (const key of Object.keys(fm)) {
256
+ if (!(key in ordered) && fm[key] !== undefined)
257
+ ordered[key] = fm[key];
258
+ }
259
+ // lineWidth 0: never fold long descriptions into YAML continuation lines.
260
+ return `---\n${stringifyYaml(ordered, { lineWidth: 0 }).trimEnd()}\n---\n`;
261
+ }
262
+ export function emitDoc(fm, body) {
263
+ return `${emitFrontmatter(fm)}\n${body.trim()}\n`;
264
+ }
265
+ export function heading(depth, text) {
266
+ return `${'#'.repeat(Math.min(6, Math.max(1, depth)))} ${text}`;
267
+ }
268
+ export function mdLink(text, target) {
269
+ return `[${text}](${target})`;
270
+ }
271
+ function escapeCell(cell) {
272
+ return cell.replace(/\|/g, '\\|').replace(/\n/g, ' ');
273
+ }
274
+ export function table(headers, rows) {
275
+ const head = `| ${headers.map(escapeCell).join(' | ')} |`;
276
+ const rule = `|${headers.map(() => '---').join('|')}|`;
277
+ const body = rows.map((r) => `| ${r.map(escapeCell).join(' | ')} |`);
278
+ return [head, rule, ...body].join('\n');
279
+ }
280
+ export function bulletList(items) {
281
+ return items.map((i) => `- ${i}`).join('\n');
282
+ }
283
+ export function fillSlot(id, content, hint) {
284
+ const interior = content && content.trim() !== ''
285
+ ? content.trim()
286
+ : `${TODO_SENTINEL} ${hint ?? id}_`;
287
+ return `<!-- wiki:fill slot="${id}" -->\n${interior}\n<!-- /wiki:fill -->`;
288
+ }
289
+ export function generatedBlock(id, source, content) {
290
+ return `<!-- wiki:generated section="${id}" source="${source}" -->\n${content.trim()}\n<!-- /wiki:generated -->`;
291
+ }
292
+ // ── Surgical edits ───────────────────────────────────────────────────────────
293
+ /** Splice `content` between a slot's markers. Null when the slot is absent. */
294
+ export function setSlotContent(raw, slotId, content) {
295
+ const doc = parseDoc(raw);
296
+ const slot = doc.slots.find((s) => s.id === slotId);
297
+ if (!slot)
298
+ return null;
299
+ const lines = raw.split('\n');
300
+ return [
301
+ ...lines.slice(0, slot.startLine),
302
+ content.trim(),
303
+ ...lines.slice(slot.endLine - 1),
304
+ ].join('\n');
305
+ }
306
+ /** Splice `content` between a generated block's markers. Null when absent. */
307
+ export function setGeneratedContent(raw, blockId, content) {
308
+ const doc = parseDoc(raw);
309
+ const block = doc.generated.find((b) => b.id === blockId);
310
+ if (!block)
311
+ return null;
312
+ const lines = raw.split('\n');
313
+ return [
314
+ ...lines.slice(0, block.startLine),
315
+ content.trim(),
316
+ ...lines.slice(block.endLine - 1),
317
+ ].join('\n');
318
+ }
319
+ /**
320
+ * Insert a log entry newest-first: after the preamble (everything before the
321
+ * first `## [` entry), before existing entries. log.md stays chronologically
322
+ * scannable from the top — the profile's reading order for recency.
323
+ */
324
+ export function insertLogEntry(raw, entry) {
325
+ const lines = raw.split('\n');
326
+ let insertAt = lines.length;
327
+ for (let i = 0; i < lines.length; i++) {
328
+ if (/^## \[/.test(lines[i])) {
329
+ insertAt = i;
330
+ break;
331
+ }
332
+ }
333
+ const before = lines.slice(0, insertAt).join('\n').trimEnd();
334
+ const after = lines.slice(insertAt).join('\n').trim();
335
+ return `${before}\n\n${entry.trim()}\n${after ? `\n${after}` : ''}`;
336
+ }
@@ -0,0 +1,69 @@
1
+ /**
2
+ * lib/wiki/pipeline.ts — the parts of a build every bundle shares.
3
+ *
4
+ * WHY this file exists:
5
+ * A bundle's build script is supposed to be small and entirely about ONE
6
+ * repo: which files are the truth, and what documents derive from them.
7
+ * Everything around that is identical wherever the engine runs —
8
+ * deriving the entity graph, merging the asserted relations, writing the
9
+ * snapshot, linting it, reporting the census, and the document that
10
+ * publishes the graph vocabulary. Those live here so a second and third
11
+ * bundle cost a page of repo-specific mapping instead of a copied
12
+ * pipeline that immediately starts to drift.
13
+ *
14
+ * The repo-specific half arrives as an EntityLayer: nodes and edges the
15
+ * caller extracted from its own truth. This module never guesses what a
16
+ * repo contains.
17
+ */
18
+ import { type EntityEdge, type EntityGraph, type EntityNode, type EntityStats, type GraphFinding } from './entities.ts';
19
+ import type { DocSpec } from './builder.ts';
20
+ export interface EntityLayer {
21
+ nodes: EntityNode[];
22
+ edges: EntityEdge[];
23
+ /** Things the extractor noticed but could not resolve (typos, dead names). */
24
+ warnings: string[];
25
+ }
26
+ export interface EntityPassResult {
27
+ graph: EntityGraph;
28
+ stats: EntityStats;
29
+ findings: GraphFinding[];
30
+ warnings: string[];
31
+ inferred: number;
32
+ }
33
+ /**
34
+ * Merge the derived layer with the asserted relations and (unless this is a
35
+ * lint-only run) refresh the snapshot the wiki tools read. The snapshot
36
+ * lives inside the bundle at `.graph/graph.json` — it travels with
37
+ * WIKI_ROOT, the vault walker ignores dot-directories, and a markdown-only
38
+ * export cannot ship it by accident.
39
+ */
40
+ export declare function entityPass(options: {
41
+ wikiRoot: string;
42
+ layer: EntityLayer;
43
+ actor: string;
44
+ date: string;
45
+ write: boolean;
46
+ }): EntityPassResult;
47
+ export declare function reportEntityPass(pass: EntityPassResult, log?: (line: string) => void): void;
48
+ export declare function graphErrorCount(pass: EntityPassResult): number;
49
+ /**
50
+ * The graph's own reference page: what kinds of node exist, what the
51
+ * relations mean, and how many of each the bundle holds right now. Both
52
+ * tables are machine-owned, so the vocabulary in the document can never
53
+ * disagree with the vocabulary in the code. Prose around them belongs to
54
+ * whoever writes it.
55
+ */
56
+ export declare function graphDocSpec(options: {
57
+ bundlePath: string;
58
+ title: string;
59
+ description: string;
60
+ stats: EntityStats;
61
+ sources: string[];
62
+ /** Prose written into the document the first time it is created. */
63
+ intro: string;
64
+ /** Prose appended after the tables on creation. */
65
+ outro: string;
66
+ tags?: string[];
67
+ /** Node kinds this bundle adds beyond the shared vocabulary, with glosses. */
68
+ extraKinds?: Record<string, string>;
69
+ }): DocSpec;
@@ -0,0 +1,119 @@
1
+ /**
2
+ * lib/wiki/pipeline.ts — the parts of a build every bundle shares.
3
+ *
4
+ * WHY this file exists:
5
+ * A bundle's build script is supposed to be small and entirely about ONE
6
+ * repo: which files are the truth, and what documents derive from them.
7
+ * Everything around that is identical wherever the engine runs —
8
+ * deriving the entity graph, merging the asserted relations, writing the
9
+ * snapshot, linting it, reporting the census, and the document that
10
+ * publishes the graph vocabulary. Those live here so a second and third
11
+ * bundle cost a page of repo-specific mapping instead of a copied
12
+ * pipeline that immediately starts to drift.
13
+ *
14
+ * The repo-specific half arrives as an EntityLayer: nodes and edges the
15
+ * caller extracted from its own truth. This module never guesses what a
16
+ * repo contains.
17
+ */
18
+ import { buildEntityGraph, entityStats, lintEntityGraph, loadRelations, NODE_KINDS, relationEdges, RELATIONS, writeSnapshot, } from "./entities.js";
19
+ import { table } from "./markdown.js";
20
+ /**
21
+ * Merge the derived layer with the asserted relations and (unless this is a
22
+ * lint-only run) refresh the snapshot the wiki tools read. The snapshot
23
+ * lives inside the bundle at `.graph/graph.json` — it travels with
24
+ * WIKI_ROOT, the vault walker ignores dot-directories, and a markdown-only
25
+ * export cannot ship it by accident.
26
+ */
27
+ export function entityPass(options) {
28
+ const relations = loadRelations(options.wikiRoot);
29
+ const graph = buildEntityGraph(options.layer.nodes, [
30
+ ...options.layer.edges,
31
+ ...relationEdges(relations.records),
32
+ ]);
33
+ if (options.write) {
34
+ writeSnapshot(options.wikiRoot, graph, { by: options.actor, at: options.date });
35
+ }
36
+ return {
37
+ graph,
38
+ stats: entityStats(graph),
39
+ findings: lintEntityGraph(graph),
40
+ warnings: [...options.layer.warnings, ...relations.issues],
41
+ inferred: relations.records.length,
42
+ };
43
+ }
44
+ export function reportEntityPass(pass, log = console.log) {
45
+ const kinds = Object.entries(pass.stats.byKind)
46
+ .sort((a, b) => b[1] - a[1])
47
+ .map(([kind, n]) => `${kind}(${n})`)
48
+ .join(', ');
49
+ log(`entities: ${pass.stats.nodes} nodes, ${pass.stats.edges} edges (${pass.stats.byTier.extracted} extracted, ${pass.stats.byTier.inferred} inferred), ${pass.stats.isolated} isolated`);
50
+ log(` kinds: ${kinds}`);
51
+ for (const warning of pass.warnings)
52
+ log(` warning: ${warning}`);
53
+ const errors = pass.findings.filter((f) => f.severity === 'error');
54
+ const others = pass.findings.filter((f) => f.severity !== 'error');
55
+ for (const finding of [...errors, ...others].slice(0, 12)) {
56
+ log(` graph-${finding.severity}: ${finding.rule} — ${finding.message}`);
57
+ }
58
+ if (pass.findings.length > 12) {
59
+ log(` …and ${pass.findings.length - 12} more graph finding(s)`);
60
+ }
61
+ }
62
+ export function graphErrorCount(pass) {
63
+ return pass.findings.filter((f) => f.severity === 'error').length;
64
+ }
65
+ // ── The document that publishes the vocabulary ───────────────────────────────
66
+ /**
67
+ * The graph's own reference page: what kinds of node exist, what the
68
+ * relations mean, and how many of each the bundle holds right now. Both
69
+ * tables are machine-owned, so the vocabulary in the document can never
70
+ * disagree with the vocabulary in the code. Prose around them belongs to
71
+ * whoever writes it.
72
+ */
73
+ export function graphDocSpec(options) {
74
+ const vocabulary = { ...NODE_KINDS, ...(options.extraKinds ?? {}) };
75
+ const kindRows = Object.entries(vocabulary)
76
+ .sort((a, b) => (options.stats.byKind[b[0]] ?? 0) - (options.stats.byKind[a[0]] ?? 0))
77
+ .map(([kind, gloss]) => [
78
+ `\`${kind}\``,
79
+ kind === 'doc' ? '`/dir/doc.md`' : `\`${kind}:<name>\``,
80
+ String(options.stats.byKind[kind] ?? 0),
81
+ gloss,
82
+ ]);
83
+ const relationRows = Object.entries(RELATIONS)
84
+ .sort((a, b) => a[1].tier.localeCompare(b[1].tier) ||
85
+ (options.stats.byRelation[b[0]] ?? 0) - (options.stats.byRelation[a[0]] ?? 0))
86
+ .map(([id, spec]) => [
87
+ `\`${id}\``,
88
+ spec.tier,
89
+ `A ${spec.phrase} B`,
90
+ String(options.stats.byRelation[id] ?? 0),
91
+ spec.gloss,
92
+ ]);
93
+ return {
94
+ bundlePath: options.bundlePath,
95
+ fm: {
96
+ type: 'meta',
97
+ title: options.title,
98
+ description: options.description,
99
+ tags: options.tags ?? ['meta', 'graph'],
100
+ sources: options.sources.map((resource) => ({ resource })),
101
+ },
102
+ body: [
103
+ { kind: 'prose', markdown: options.intro },
104
+ {
105
+ kind: 'generated',
106
+ id: 'node-kinds',
107
+ source: 'lib/wiki/entities.ts',
108
+ markdown: `## What the graph knows about\n\n${table(['Kind', 'Id form', 'Now', 'What it is'], kindRows)}\n\nA document keeps its bundle path as its identity, so the two namespaces cannot collide.`,
109
+ },
110
+ {
111
+ kind: 'generated',
112
+ id: 'relations',
113
+ source: 'lib/wiki/entities.ts',
114
+ markdown: `## The relation vocabulary\n\n${table(['Relation', 'Tier', 'Reads as', 'Now', 'Meaning'], relationRows)}`,
115
+ },
116
+ { kind: 'prose', markdown: options.outro },
117
+ ],
118
+ };
119
+ }
@@ -0,0 +1,84 @@
1
+ /**
2
+ * lib/wiki/vault.ts — load an OKF bundle from disk and resolve its links.
3
+ *
4
+ * WHY this file exists:
5
+ * Every wiki operation (lint, graph, search, dive, garden) starts from the
6
+ * same picture: all documents of one bundle, parsed, with frontmatter
7
+ * validated against the profile and every link resolved to a
8
+ * bundle-absolute path. This module builds that picture once, so the
9
+ * layers above never touch the filesystem or path arithmetic themselves.
10
+ *
11
+ * Identity: a document IS its bundle-absolute path ('/memory/schema.md') —
12
+ * the OKF rule that "the file path is the concept's identity." All link
13
+ * resolution normalises to that form.
14
+ *
15
+ * The bundle root is configurable (WIKI_ROOT) so the same tooling serves
16
+ * any OKF bundle — this repo's wiki/ is merely the default. Writes are
17
+ * jailed to the bundle root: a resolved path escaping it is refused, since
18
+ * garden instructions and MCP callers are untrusted input.
19
+ */
20
+ import { type ConceptFrontmatter } from './format.ts';
21
+ import { type ParsedDoc } from './markdown.ts';
22
+ export type DocKind = 'concept' | 'index' | 'log';
23
+ export interface WikiDoc {
24
+ /** Bundle-absolute identity, e.g. '/memory/schema.md'. */
25
+ bundlePath: string;
26
+ /** Absolute filesystem path. */
27
+ absPath: string;
28
+ kind: DocKind;
29
+ parsed: ParsedDoc;
30
+ /** Profile-validated frontmatter — null for reserved files or when invalid. */
31
+ fm: ConceptFrontmatter | null;
32
+ /** Validation issues (consumed by lint; empty when fm parsed clean). */
33
+ fmIssues: string[];
34
+ title: string;
35
+ wordCount: number;
36
+ isPrivate: boolean;
37
+ }
38
+ export interface Vault {
39
+ /** Absolute filesystem path of the bundle root. */
40
+ root: string;
41
+ /** Keyed by bundlePath. */
42
+ docs: Map<string, WikiDoc>;
43
+ }
44
+ export type ResolvedLink = {
45
+ kind: 'internal';
46
+ bundlePath: string;
47
+ } | {
48
+ kind: 'external';
49
+ } | {
50
+ kind: 'anchor';
51
+ } | {
52
+ kind: 'escapes-bundle';
53
+ };
54
+ /**
55
+ * WIKI_ROOT env var wins; default is `<cwd>/wiki`. The default used to be
56
+ * derived from this module's location (lib/wiki/ → repo root), which is
57
+ * wrong the moment the engine ships as a package (node_modules/<pkg>/wiki
58
+ * exists for no one) — and every real caller either sets WIKI_ROOT or runs
59
+ * from the repo root via npm scripts, where cwd IS the repo root.
60
+ */
61
+ export declare function resolveWikiRoot(): string;
62
+ export declare function classifyDoc(bundlePath: string): DocKind;
63
+ export declare function loadDoc(root: string, absPath: string): WikiDoc;
64
+ /** Build a WikiDoc from raw text — also used to evaluate an unsaved draft. */
65
+ export declare function makeWikiDoc(bundlePath: string, absPath: string, raw: string): WikiDoc;
66
+ export declare function loadVault(root?: string): Vault;
67
+ /**
68
+ * Resolve a link target as written to bundle-absolute form. Directory links
69
+ * ('/agents/' or '/agents') resolve to that directory's index.md — the OKF
70
+ * progressive-disclosure convention.
71
+ */
72
+ export declare function resolveLinkTarget(fromBundlePath: string, target: string): ResolvedLink;
73
+ /** Existing doc for an internal target, trying `/dir` → `/dir/index.md`. */
74
+ export declare function lookupTarget(vault: Vault, bundlePath: string): WikiDoc | null;
75
+ /**
76
+ * Absolute filesystem path for a bundle path, refusing anything that
77
+ * resolves outside the bundle root. Callers pass user/agent-supplied paths.
78
+ */
79
+ export declare function jailedAbsPath(root: string, bundlePath: string): string;
80
+ export declare function writeDocFile(root: string, bundlePath: string, raw: string): void;
81
+ export declare function conceptDocs(vault: Vault): WikiDoc[];
82
+ export declare function indexDocs(vault: Vault): WikiDoc[];
83
+ export declare function docsInDirectory(vault: Vault, dirBundlePath: string): WikiDoc[];
84
+ export declare function isReservedDoc(doc: WikiDoc): boolean;