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,267 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* lib/wiki/lint.ts — producer-side health checks for the bundle.
|
|
3
|
+
*
|
|
4
|
+
* WHY this file exists:
|
|
5
|
+
* OKF consumers MUST tolerate broken links, missing indexes, unknown
|
|
6
|
+
* fields — the spec is forgiving on purpose. A PRODUCER that leans on
|
|
7
|
+
* that forgiveness rots: links dangle, pages orphan, indexes lag their
|
|
8
|
+
* directories. This is Karpathy's "lint" operation from the LLM-wiki
|
|
9
|
+
* pattern, run as code instead of vibes. It is also the gate every write
|
|
10
|
+
* passes through: `wiki_save` refuses drafts whose errors would degrade
|
|
11
|
+
* the bundle.
|
|
12
|
+
*
|
|
13
|
+
* Severity doctrine:
|
|
14
|
+
* error — violates OKF conformance or the profile's hard rules;
|
|
15
|
+
* blocks wiki_save and fails `wiki:build --check`.
|
|
16
|
+
* warning — the bundle works but is decaying (broken link, orphan,
|
|
17
|
+
* stale doc, index gap). Surfaced, never blocking.
|
|
18
|
+
* info — style guidance (relative links, unknown type).
|
|
19
|
+
*
|
|
20
|
+
* The private-closure rule is the publishing invariant: documents outside
|
|
21
|
+
* /private/ must never link into it, so exporting the bundle minus the
|
|
22
|
+
* private subtree can never produce a dangling reference the producer
|
|
23
|
+
* didn't choose. Extra `forbiddenPatterns` (names that must not appear in
|
|
24
|
+
* public docs) are supplied BY THE CALLER — this public module does not
|
|
25
|
+
* itself know any private vocabulary.
|
|
26
|
+
*/
|
|
27
|
+
import { posix } from 'node:path';
|
|
28
|
+
import { CONCEPT_TYPES, isStale, LOG_ENTRY_RE, } from "./format.js";
|
|
29
|
+
import { docsInDirectory, lookupTarget, resolveLinkTarget, } from "./vault.js";
|
|
30
|
+
function report(findings) {
|
|
31
|
+
const errors = findings.filter((f) => f.severity === 'error').length;
|
|
32
|
+
const warnings = findings.filter((f) => f.severity === 'warning').length;
|
|
33
|
+
return { findings, errors, warnings, ok: errors === 0 };
|
|
34
|
+
}
|
|
35
|
+
function todayIso() {
|
|
36
|
+
return new Date().toISOString().slice(0, 10);
|
|
37
|
+
}
|
|
38
|
+
// ── Per-document rules ───────────────────────────────────────────────────────
|
|
39
|
+
const KNOWN_TYPES = new Set(CONCEPT_TYPES);
|
|
40
|
+
/**
|
|
41
|
+
* Rules that need only the document itself (+ the vault for link targets).
|
|
42
|
+
* Used both by lintVault and by the wiki_save gate on unsaved drafts.
|
|
43
|
+
*/
|
|
44
|
+
export function lintDoc(vault, doc, options = {}) {
|
|
45
|
+
const findings = [];
|
|
46
|
+
const today = options.today ?? todayIso();
|
|
47
|
+
for (const issue of doc.fmIssues) {
|
|
48
|
+
findings.push({
|
|
49
|
+
severity: 'error',
|
|
50
|
+
rule: 'frontmatter',
|
|
51
|
+
docPath: doc.bundlePath,
|
|
52
|
+
message: issue,
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
for (const wl of doc.parsed.wikilinks) {
|
|
56
|
+
findings.push({
|
|
57
|
+
severity: 'error',
|
|
58
|
+
rule: 'wikilink-syntax',
|
|
59
|
+
docPath: doc.bundlePath,
|
|
60
|
+
line: wl.line,
|
|
61
|
+
message: `[[${wl.target}]] is not an OKF link — use [Title](/path/doc.md)`,
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
for (const marker of doc.parsed.unclosedMarkers) {
|
|
65
|
+
findings.push({
|
|
66
|
+
severity: 'error',
|
|
67
|
+
rule: 'unclosed-marker',
|
|
68
|
+
docPath: doc.bundlePath,
|
|
69
|
+
line: marker.line,
|
|
70
|
+
message: `wiki:${marker.kind} marker never closes`,
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
for (const link of doc.parsed.links) {
|
|
74
|
+
const resolved = resolveLinkTarget(doc.bundlePath, link.target);
|
|
75
|
+
if (resolved.kind === 'escapes-bundle') {
|
|
76
|
+
findings.push({
|
|
77
|
+
severity: 'error',
|
|
78
|
+
rule: 'link-escapes-bundle',
|
|
79
|
+
docPath: doc.bundlePath,
|
|
80
|
+
line: link.line,
|
|
81
|
+
message: `link target leaves the bundle: ${link.target}`,
|
|
82
|
+
});
|
|
83
|
+
continue;
|
|
84
|
+
}
|
|
85
|
+
if (resolved.kind !== 'internal')
|
|
86
|
+
continue;
|
|
87
|
+
if (!link.isImage && !vault.docs.has(resolved.bundlePath)) {
|
|
88
|
+
if (lookupTarget(vault, resolved.bundlePath) === null) {
|
|
89
|
+
findings.push({
|
|
90
|
+
severity: 'warning',
|
|
91
|
+
rule: 'broken-link',
|
|
92
|
+
docPath: doc.bundlePath,
|
|
93
|
+
line: link.line,
|
|
94
|
+
message: `target does not exist in the bundle: ${link.target}`,
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
if (!doc.isPrivate && resolved.bundlePath.startsWith('/private/')) {
|
|
99
|
+
findings.push({
|
|
100
|
+
severity: 'error',
|
|
101
|
+
rule: 'private-closure',
|
|
102
|
+
docPath: doc.bundlePath,
|
|
103
|
+
line: link.line,
|
|
104
|
+
message: `public document links into /private/: ${link.target}`,
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
if (!link.target.startsWith('/') && !link.isImage) {
|
|
108
|
+
findings.push({
|
|
109
|
+
severity: 'info',
|
|
110
|
+
rule: 'relative-link',
|
|
111
|
+
docPath: doc.bundlePath,
|
|
112
|
+
line: link.line,
|
|
113
|
+
message: `prefer bundle-absolute links: ${resolved.bundlePath}`,
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
if (doc.kind === 'concept' && doc.fm) {
|
|
118
|
+
if (!KNOWN_TYPES.has(doc.fm.type)) {
|
|
119
|
+
findings.push({
|
|
120
|
+
severity: 'info',
|
|
121
|
+
rule: 'unknown-type',
|
|
122
|
+
docPath: doc.bundlePath,
|
|
123
|
+
message: `type "${doc.fm.type}" is not in the profile vocabulary (allowed, but check for a near-miss)`,
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
if (isStale(doc.fm, today)) {
|
|
127
|
+
findings.push({
|
|
128
|
+
severity: 'warning',
|
|
129
|
+
rule: 'stale',
|
|
130
|
+
docPath: doc.bundlePath,
|
|
131
|
+
message: `stale since ${doc.fm.stale_after} — re-verify or update`,
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
if (doc.kind === 'log') {
|
|
136
|
+
const lines = doc.parsed.raw.split('\n');
|
|
137
|
+
for (let i = 0; i < lines.length; i++) {
|
|
138
|
+
if (lines[i].startsWith('## ') && !LOG_ENTRY_RE.test(lines[i])) {
|
|
139
|
+
findings.push({
|
|
140
|
+
severity: 'warning',
|
|
141
|
+
rule: 'log-format',
|
|
142
|
+
docPath: doc.bundlePath,
|
|
143
|
+
line: i + 1,
|
|
144
|
+
message: 'log entry must be "## [YYYY-MM-DD] <op> | <summary>"',
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
if (!doc.isPrivate && options.forbiddenPatterns) {
|
|
150
|
+
for (const pattern of options.forbiddenPatterns) {
|
|
151
|
+
const match = doc.parsed.raw.match(pattern);
|
|
152
|
+
if (match) {
|
|
153
|
+
findings.push({
|
|
154
|
+
severity: 'error',
|
|
155
|
+
rule: 'forbidden-term',
|
|
156
|
+
docPath: doc.bundlePath,
|
|
157
|
+
message: `public document mentions a private name: "${match[0]}"`,
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
return findings;
|
|
163
|
+
}
|
|
164
|
+
// ── Bundle-wide rules ────────────────────────────────────────────────────────
|
|
165
|
+
function lintIndexCoverage(vault) {
|
|
166
|
+
const findings = [];
|
|
167
|
+
const directories = new Set(['/']);
|
|
168
|
+
for (const doc of vault.docs.values()) {
|
|
169
|
+
const dir = posix.dirname(doc.bundlePath);
|
|
170
|
+
directories.add(dir === '/' ? '/' : `${dir}/`);
|
|
171
|
+
}
|
|
172
|
+
for (const dir of directories) {
|
|
173
|
+
const indexPath = posix.join(dir, 'index.md');
|
|
174
|
+
const index = vault.docs.get(indexPath);
|
|
175
|
+
if (!index) {
|
|
176
|
+
findings.push({
|
|
177
|
+
severity: 'warning',
|
|
178
|
+
rule: 'missing-index',
|
|
179
|
+
docPath: indexPath,
|
|
180
|
+
message: `directory ${dir} has no index.md (progressive disclosure gap)`,
|
|
181
|
+
});
|
|
182
|
+
continue;
|
|
183
|
+
}
|
|
184
|
+
// Every concept in the directory should be linked from its index; a
|
|
185
|
+
// private annex directory may be listed from the root but never the
|
|
186
|
+
// reverse (the closure rule handles direction).
|
|
187
|
+
const listed = new Set();
|
|
188
|
+
for (const link of index.parsed.links) {
|
|
189
|
+
const resolved = resolveLinkTarget(indexPath, link.target);
|
|
190
|
+
if (resolved.kind === 'internal') {
|
|
191
|
+
const target = lookupTarget(vault, resolved.bundlePath);
|
|
192
|
+
listed.add(target ? target.bundlePath : resolved.bundlePath);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
for (const doc of docsInDirectory(vault, dir)) {
|
|
196
|
+
if (doc.kind !== 'concept')
|
|
197
|
+
continue;
|
|
198
|
+
if (index.isPrivate === doc.isPrivate && !listed.has(doc.bundlePath)) {
|
|
199
|
+
findings.push({
|
|
200
|
+
severity: 'warning',
|
|
201
|
+
rule: 'index-coverage',
|
|
202
|
+
docPath: indexPath,
|
|
203
|
+
message: `${doc.bundlePath} is not listed in its directory index`,
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
// Root index should declare the spec version it targets.
|
|
209
|
+
const root = vault.docs.get('/index.md');
|
|
210
|
+
if (root && !root.parsed.frontmatter?.okf_version) {
|
|
211
|
+
findings.push({
|
|
212
|
+
severity: 'info',
|
|
213
|
+
rule: 'okf-version',
|
|
214
|
+
docPath: '/index.md',
|
|
215
|
+
message: 'root index.md should declare okf_version in frontmatter',
|
|
216
|
+
});
|
|
217
|
+
}
|
|
218
|
+
return findings;
|
|
219
|
+
}
|
|
220
|
+
function lintOrphans(vault) {
|
|
221
|
+
const inbound = new Set();
|
|
222
|
+
for (const doc of vault.docs.values()) {
|
|
223
|
+
for (const link of doc.parsed.links) {
|
|
224
|
+
const resolved = resolveLinkTarget(doc.bundlePath, link.target);
|
|
225
|
+
if (resolved.kind !== 'internal')
|
|
226
|
+
continue;
|
|
227
|
+
const target = lookupTarget(vault, resolved.bundlePath);
|
|
228
|
+
if (target && target.bundlePath !== doc.bundlePath) {
|
|
229
|
+
inbound.add(target.bundlePath);
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
const findings = [];
|
|
234
|
+
for (const doc of vault.docs.values()) {
|
|
235
|
+
if (doc.kind !== 'concept')
|
|
236
|
+
continue;
|
|
237
|
+
if (!inbound.has(doc.bundlePath)) {
|
|
238
|
+
findings.push({
|
|
239
|
+
severity: 'warning',
|
|
240
|
+
rule: 'orphan',
|
|
241
|
+
docPath: doc.bundlePath,
|
|
242
|
+
message: 'no document links here — connect it or reconsider it',
|
|
243
|
+
});
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
return findings;
|
|
247
|
+
}
|
|
248
|
+
// ── Entry points ─────────────────────────────────────────────────────────────
|
|
249
|
+
export function lintVault(vault, options = {}) {
|
|
250
|
+
const findings = [];
|
|
251
|
+
for (const doc of vault.docs.values()) {
|
|
252
|
+
findings.push(...lintDoc(vault, doc, options));
|
|
253
|
+
}
|
|
254
|
+
findings.push(...lintIndexCoverage(vault));
|
|
255
|
+
findings.push(...lintOrphans(vault));
|
|
256
|
+
findings.sort((a, b) => a.docPath === b.docPath
|
|
257
|
+
? (a.line ?? 0) - (b.line ?? 0)
|
|
258
|
+
: a.docPath.localeCompare(b.docPath));
|
|
259
|
+
return report(findings);
|
|
260
|
+
}
|
|
261
|
+
export function formatLintReport(result) {
|
|
262
|
+
if (result.findings.length === 0)
|
|
263
|
+
return 'lint: clean — no findings.';
|
|
264
|
+
const lines = result.findings.map((f) => `${f.severity.toUpperCase().padEnd(7)} ${f.rule.padEnd(20)} ${f.docPath}${f.line ? `:${f.line}` : ''} — ${f.message}`);
|
|
265
|
+
lines.push(`lint: ${result.errors} error(s), ${result.warnings} warning(s), ${result.findings.length - result.errors - result.warnings} info.`);
|
|
266
|
+
return lines.join('\n');
|
|
267
|
+
}
|
|
@@ -0,0 +1,127 @@
|
|
|
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
|
+
export interface Heading {
|
|
34
|
+
depth: number;
|
|
35
|
+
text: string;
|
|
36
|
+
slug: string;
|
|
37
|
+
/** 1-based line number in the document. */
|
|
38
|
+
line: number;
|
|
39
|
+
}
|
|
40
|
+
export interface LinkRef {
|
|
41
|
+
text: string;
|
|
42
|
+
/** Target exactly as written (before bundle resolution). */
|
|
43
|
+
target: string;
|
|
44
|
+
line: number;
|
|
45
|
+
isImage: boolean;
|
|
46
|
+
}
|
|
47
|
+
export interface Fence {
|
|
48
|
+
startLine: number;
|
|
49
|
+
/** Closing-fence line; equals the last doc line when unclosed. */
|
|
50
|
+
endLine: number;
|
|
51
|
+
lang: string;
|
|
52
|
+
}
|
|
53
|
+
export interface FillSlot {
|
|
54
|
+
id: string;
|
|
55
|
+
/** Line of the opening marker. */
|
|
56
|
+
startLine: number;
|
|
57
|
+
/** Line of the closing marker. */
|
|
58
|
+
endLine: number;
|
|
59
|
+
/** Interior text between the markers (without the marker lines). */
|
|
60
|
+
content: string;
|
|
61
|
+
}
|
|
62
|
+
export interface GeneratedBlock {
|
|
63
|
+
id: string;
|
|
64
|
+
source?: string;
|
|
65
|
+
startLine: number;
|
|
66
|
+
endLine: number;
|
|
67
|
+
content: string;
|
|
68
|
+
}
|
|
69
|
+
export interface ParsedDoc {
|
|
70
|
+
raw: string;
|
|
71
|
+
/** YAML text between the `---` fences, or null when absent. */
|
|
72
|
+
frontmatterRaw: string | null;
|
|
73
|
+
/** Parsed YAML mapping; null when absent or unparseable. */
|
|
74
|
+
frontmatter: Record<string, unknown> | null;
|
|
75
|
+
/** YAML parse failure, when the block exists but will not parse. */
|
|
76
|
+
frontmatterError?: string;
|
|
77
|
+
/** 1-based line where the body (after any frontmatter block) begins. */
|
|
78
|
+
bodyStartLine: number;
|
|
79
|
+
headings: Heading[];
|
|
80
|
+
links: LinkRef[];
|
|
81
|
+
/** `[[wikilink]]` occurrences — not OKF; lint rejects them. */
|
|
82
|
+
wikilinks: Array<{
|
|
83
|
+
target: string;
|
|
84
|
+
line: number;
|
|
85
|
+
}>;
|
|
86
|
+
fences: Fence[];
|
|
87
|
+
slots: FillSlot[];
|
|
88
|
+
generated: GeneratedBlock[];
|
|
89
|
+
/** Marker lines that opened but never closed (lint surfaces these). */
|
|
90
|
+
unclosedMarkers: Array<{
|
|
91
|
+
kind: 'fill' | 'generated';
|
|
92
|
+
line: number;
|
|
93
|
+
}>;
|
|
94
|
+
}
|
|
95
|
+
/** Sentinel left in an unfilled slot; the fill pass replaces it. */
|
|
96
|
+
export declare const TODO_SENTINEL = "_TODO(fill):";
|
|
97
|
+
export declare function slotIsUnfilled(slot: FillSlot): boolean;
|
|
98
|
+
/** GitHub-style anchor slug — for addressing sections by name. */
|
|
99
|
+
export declare function slugify(text: string): string;
|
|
100
|
+
export declare function parseDoc(raw: string): ParsedDoc;
|
|
101
|
+
export interface SectionSlice {
|
|
102
|
+
heading: Heading;
|
|
103
|
+
/** 1-based inclusive line range, heading line included. */
|
|
104
|
+
startLine: number;
|
|
105
|
+
endLine: number;
|
|
106
|
+
text: string;
|
|
107
|
+
}
|
|
108
|
+
/** The section under the heading whose slug or text matches (case-insensitive). */
|
|
109
|
+
export declare function sectionSlice(doc: ParsedDoc, nameOrSlug: string): SectionSlice | null;
|
|
110
|
+
export declare function emitFrontmatter(fm: Record<string, unknown>): string;
|
|
111
|
+
export declare function emitDoc(fm: Record<string, unknown>, body: string): string;
|
|
112
|
+
export declare function heading(depth: number, text: string): string;
|
|
113
|
+
export declare function mdLink(text: string, target: string): string;
|
|
114
|
+
export declare function table(headers: string[], rows: string[][]): string;
|
|
115
|
+
export declare function bulletList(items: string[]): string;
|
|
116
|
+
export declare function fillSlot(id: string, content?: string, hint?: string): string;
|
|
117
|
+
export declare function generatedBlock(id: string, source: string, content: string): string;
|
|
118
|
+
/** Splice `content` between a slot's markers. Null when the slot is absent. */
|
|
119
|
+
export declare function setSlotContent(raw: string, slotId: string, content: string): string | null;
|
|
120
|
+
/** Splice `content` between a generated block's markers. Null when absent. */
|
|
121
|
+
export declare function setGeneratedContent(raw: string, blockId: string, content: string): string | null;
|
|
122
|
+
/**
|
|
123
|
+
* Insert a log entry newest-first: after the preamble (everything before the
|
|
124
|
+
* first `## [` entry), before existing entries. log.md stays chronologically
|
|
125
|
+
* scannable from the top — the profile's reading order for recency.
|
|
126
|
+
*/
|
|
127
|
+
export declare function insertLogEntry(raw: string, entry: string): string;
|