@farming-labs/docs 0.2.71 → 0.2.73
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/dist/{agent-bof3qzKZ.mjs → agent-ClRpctMG.mjs} +3 -2
- package/dist/{agent-evals-Cr9TAkmB.mjs → agent-evals-BFHGypqh.mjs} +116 -20
- package/dist/{agent-export-7Ws4KqDE.mjs → agent-export-B9bah0AV.mjs} +9 -8
- package/dist/{agent-FIyl4-Oa.mjs → agent-o9OTadrf.mjs} +607 -769
- package/dist/agent-skills-bundle.d.mts +1 -1
- package/dist/{agent-skills-server-DTwblsLu.d.mts → agent-skills-server-DCbAx5WU.d.mts} +2 -2
- package/dist/{agent-skills-server-B4u3qy-1.mjs → agent-skills-server-noFn1l_p.mjs} +1 -1
- package/dist/agent-skills-vite.d.mts +3 -3
- package/dist/agent-skills-vite.mjs +2 -2
- package/dist/{agents-Be2ntaFr.mjs → agents-CR-Uasbc.mjs} +7 -6
- package/dist/cli/index.mjs +14 -14
- package/dist/client/react.d.mts +1 -1
- package/dist/{cloud-ask-ai-BfnX18rI.d.mts → cloud-ask-ai-yR9vZmSG.d.mts} +1 -1
- package/dist/docs-cloud-server.d.mts +2 -2
- package/dist/{doctor-DoPAofVP.mjs → doctor-BeyN3Ljh.mjs} +12 -11
- package/dist/{golden-evaluations-CPGGnXOV.mjs → golden-evaluations-CloP0pAe.mjs} +23 -2
- package/dist/index.d.mts +93 -6
- package/dist/index.mjs +9 -8
- package/dist/markdown-rendering.d.mts +75 -0
- package/dist/markdown-rendering.mjs +618 -0
- package/dist/markdown-sections-7OoA7ylx.mjs +1492 -0
- package/dist/{mcp-BLynUzE7.mjs → mcp-BLLXvEXB.mjs} +7 -6
- package/dist/mcp.d.mts +7 -2
- package/dist/mcp.mjs +130 -25
- package/dist/{metadata-bw8k-9x-.mjs → metadata-N252j5_a.mjs} +1 -1
- package/dist/{reading-time-DtNegNiq.mjs → prompt-references-Cz35rlOa.mjs} +158 -4
- package/dist/{review-NFnYbMRI.mjs → review-DaJLdPLh.mjs} +7 -6
- package/dist/{robots-DOqBwQw8.mjs → robots-BcuxnZqZ.mjs} +2 -2
- package/dist/{robots-B3e-aWPy.mjs → robots-DI-9MZz1.mjs} +4 -3
- package/dist/{search-CozHzW7v.d.mts → search-D1n8Sf25.d.mts} +45 -9
- package/dist/{search-DOGUUZvs.mjs → search-DeOJiMAy.mjs} +7 -6
- package/dist/{search-x5O0-9X3.mjs → search-RfPcG1if.mjs} +742 -42
- package/dist/server.d.mts +13 -5
- package/dist/server.mjs +8 -7
- package/dist/{sitemap-BpMKdDUd.mjs → sitemap-BGGbX_KE.mjs} +7 -6
- package/dist/{sitemap-server-D_hayOp6.mjs → sitemap-server-VefDnzEB.mjs} +1 -1
- package/dist/{standards-discovery-C0CIC4QI.d.mts → standards-discovery-83lp1lew.d.mts} +5 -4
- package/dist/{standards-discovery-CYfiQE13.mjs → standards-discovery-BwL5ffGx.mjs} +4 -3
- package/dist/{types-BQC05SQm.d.mts → types-_HCPD4wc.d.mts} +49 -1
- package/package.json +9 -1
- package/dist/agent-scope-C_U--OZ7.mjs +0 -283
- /package/dist/{i18n-CCaFUnAN.mjs → i18n-hHVWcflJ.mjs} +0 -0
|
@@ -0,0 +1,1492 @@
|
|
|
1
|
+
import { createProcessor } from "@mdx-js/mdx";
|
|
2
|
+
import { slug } from "github-slugger";
|
|
3
|
+
import { parseEntities } from "parse-entities";
|
|
4
|
+
import remarkGfm from "remark-gfm";
|
|
5
|
+
|
|
6
|
+
//#region src/agent-contract.ts
|
|
7
|
+
const PAGE_AGENT_STRUCTURED_CONTRACT_FIELDS = [
|
|
8
|
+
"task",
|
|
9
|
+
"outcome",
|
|
10
|
+
"appliesTo",
|
|
11
|
+
"prerequisites",
|
|
12
|
+
"files",
|
|
13
|
+
"commands",
|
|
14
|
+
"sideEffects",
|
|
15
|
+
"verification",
|
|
16
|
+
"rollback",
|
|
17
|
+
"failureModes"
|
|
18
|
+
];
|
|
19
|
+
const PAGE_AGENT_CONTRACT_FIELDS = ["tokenBudget", ...PAGE_AGENT_STRUCTURED_CONTRACT_FIELDS];
|
|
20
|
+
const PAGE_AGENT_CONTRACT_FIELD_SCHEMA = {
|
|
21
|
+
tokenBudget: "number",
|
|
22
|
+
task: "string",
|
|
23
|
+
outcome: "string",
|
|
24
|
+
appliesTo: {
|
|
25
|
+
framework: "string|string[]",
|
|
26
|
+
version: "string|string[]",
|
|
27
|
+
package: "string|string[]"
|
|
28
|
+
},
|
|
29
|
+
prerequisites: "string[]",
|
|
30
|
+
files: "string[]",
|
|
31
|
+
commands: "Array<string|{run,cwd?,description?}>",
|
|
32
|
+
sideEffects: "string[]",
|
|
33
|
+
verification: "Array<string|{description?,run?,expect?}>",
|
|
34
|
+
rollback: "string[]",
|
|
35
|
+
failureModes: "Array<string|{symptom,resolution?}>"
|
|
36
|
+
};
|
|
37
|
+
const APPLIES_TO_FIELDS = [
|
|
38
|
+
"framework",
|
|
39
|
+
"version",
|
|
40
|
+
"package"
|
|
41
|
+
];
|
|
42
|
+
const COMMAND_FIELDS = [
|
|
43
|
+
"run",
|
|
44
|
+
"cwd",
|
|
45
|
+
"description"
|
|
46
|
+
];
|
|
47
|
+
const VERIFICATION_FIELDS = [
|
|
48
|
+
"description",
|
|
49
|
+
"run",
|
|
50
|
+
"expect"
|
|
51
|
+
];
|
|
52
|
+
const FAILURE_MODE_FIELDS = ["symptom", "resolution"];
|
|
53
|
+
const PAGE_AGENT_CONTRACT_START_MARKER = "<!-- farming-labs:agent-contract:start -->";
|
|
54
|
+
const PAGE_AGENT_CONTRACT_END_MARKER = "<!-- farming-labs:agent-contract:end -->";
|
|
55
|
+
function isRecord(value) {
|
|
56
|
+
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
57
|
+
}
|
|
58
|
+
function normalizeString(value) {
|
|
59
|
+
if (typeof value !== "string") return void 0;
|
|
60
|
+
return value.trim() || void 0;
|
|
61
|
+
}
|
|
62
|
+
function normalizeStringList(value) {
|
|
63
|
+
if (!Array.isArray(value)) return void 0;
|
|
64
|
+
const seen = /* @__PURE__ */ new Set();
|
|
65
|
+
const normalized = [];
|
|
66
|
+
for (const item of value) {
|
|
67
|
+
const text = normalizeString(item);
|
|
68
|
+
if (!text || seen.has(text)) continue;
|
|
69
|
+
seen.add(text);
|
|
70
|
+
normalized.push(text);
|
|
71
|
+
}
|
|
72
|
+
return normalized.length > 0 ? normalized : void 0;
|
|
73
|
+
}
|
|
74
|
+
function normalizeStringOrList(value) {
|
|
75
|
+
const single = normalizeString(value);
|
|
76
|
+
if (single) return [single];
|
|
77
|
+
return normalizeStringList(value);
|
|
78
|
+
}
|
|
79
|
+
function normalizeAppliesTo(value) {
|
|
80
|
+
if (!isRecord(value)) return void 0;
|
|
81
|
+
const framework = normalizeStringOrList(value.framework);
|
|
82
|
+
const version = normalizeStringOrList(value.version);
|
|
83
|
+
const packageNames = normalizeStringOrList(value.package);
|
|
84
|
+
if (!framework && !version && !packageNames) return void 0;
|
|
85
|
+
return {
|
|
86
|
+
...framework ? { framework } : {},
|
|
87
|
+
...version ? { version } : {},
|
|
88
|
+
...packageNames ? { package: packageNames } : {}
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
function normalizeCommands(value) {
|
|
92
|
+
if (!Array.isArray(value)) return void 0;
|
|
93
|
+
const commands = [];
|
|
94
|
+
for (const item of value) {
|
|
95
|
+
const shorthand = normalizeString(item);
|
|
96
|
+
if (shorthand) {
|
|
97
|
+
commands.push(shorthand);
|
|
98
|
+
continue;
|
|
99
|
+
}
|
|
100
|
+
if (!isRecord(item)) continue;
|
|
101
|
+
const run = normalizeString(item.run);
|
|
102
|
+
if (!run) continue;
|
|
103
|
+
const cwd = normalizeString(item.cwd);
|
|
104
|
+
const description = normalizeString(item.description);
|
|
105
|
+
commands.push({
|
|
106
|
+
run,
|
|
107
|
+
...cwd ? { cwd } : {},
|
|
108
|
+
...description ? { description } : {}
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
return commands.length > 0 ? commands : void 0;
|
|
112
|
+
}
|
|
113
|
+
function normalizeVerification(value) {
|
|
114
|
+
if (!Array.isArray(value)) return void 0;
|
|
115
|
+
const verification = [];
|
|
116
|
+
for (const item of value) {
|
|
117
|
+
const shorthand = normalizeString(item);
|
|
118
|
+
if (shorthand) {
|
|
119
|
+
verification.push(shorthand);
|
|
120
|
+
continue;
|
|
121
|
+
}
|
|
122
|
+
if (!isRecord(item)) continue;
|
|
123
|
+
const description = normalizeString(item.description);
|
|
124
|
+
const run = normalizeString(item.run);
|
|
125
|
+
const expect = normalizeString(item.expect);
|
|
126
|
+
if (!description && !run && !expect) continue;
|
|
127
|
+
verification.push({
|
|
128
|
+
...description ? { description } : {},
|
|
129
|
+
...run ? { run } : {},
|
|
130
|
+
...expect ? { expect } : {}
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
return verification.length > 0 ? verification : void 0;
|
|
134
|
+
}
|
|
135
|
+
function normalizeFailureModes(value) {
|
|
136
|
+
if (!Array.isArray(value)) return void 0;
|
|
137
|
+
const failureModes = [];
|
|
138
|
+
for (const item of value) {
|
|
139
|
+
const shorthand = normalizeString(item);
|
|
140
|
+
if (shorthand) {
|
|
141
|
+
failureModes.push(shorthand);
|
|
142
|
+
continue;
|
|
143
|
+
}
|
|
144
|
+
if (!isRecord(item)) continue;
|
|
145
|
+
const symptom = normalizeString(item.symptom);
|
|
146
|
+
if (!symptom) continue;
|
|
147
|
+
const resolution = normalizeString(item.resolution);
|
|
148
|
+
failureModes.push({
|
|
149
|
+
symptom,
|
|
150
|
+
...resolution ? { resolution } : {}
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
return failureModes.length > 0 ? failureModes : void 0;
|
|
154
|
+
}
|
|
155
|
+
function normalizeTokenBudget(value) {
|
|
156
|
+
if (typeof value !== "number" || !Number.isFinite(value) || value <= 0) return void 0;
|
|
157
|
+
return Math.ceil(value);
|
|
158
|
+
}
|
|
159
|
+
/**
|
|
160
|
+
* Normalize untrusted page frontmatter into the stable agent contract shape.
|
|
161
|
+
* Invalid fields are omitted so malformed author input cannot break page delivery.
|
|
162
|
+
*/
|
|
163
|
+
function normalizePageAgentFrontmatter(value) {
|
|
164
|
+
if (!isRecord(value)) return void 0;
|
|
165
|
+
const tokenBudget = normalizeTokenBudget(value.tokenBudget);
|
|
166
|
+
const task = normalizeString(value.task);
|
|
167
|
+
const outcome = normalizeString(value.outcome);
|
|
168
|
+
const appliesTo = normalizeAppliesTo(value.appliesTo);
|
|
169
|
+
const prerequisites = normalizeStringList(value.prerequisites);
|
|
170
|
+
const files = normalizeStringList(value.files);
|
|
171
|
+
const commands = normalizeCommands(value.commands);
|
|
172
|
+
const sideEffects = normalizeStringList(value.sideEffects);
|
|
173
|
+
const verification = normalizeVerification(value.verification);
|
|
174
|
+
const rollback = normalizeStringList(value.rollback);
|
|
175
|
+
const failureModes = normalizeFailureModes(value.failureModes);
|
|
176
|
+
const normalized = {
|
|
177
|
+
...tokenBudget !== void 0 ? { tokenBudget } : {},
|
|
178
|
+
...task ? { task } : {},
|
|
179
|
+
...outcome ? { outcome } : {},
|
|
180
|
+
...appliesTo ? { appliesTo } : {},
|
|
181
|
+
...prerequisites ? { prerequisites } : {},
|
|
182
|
+
...files ? { files } : {},
|
|
183
|
+
...commands ? { commands } : {},
|
|
184
|
+
...sideEffects ? { sideEffects } : {},
|
|
185
|
+
...verification ? { verification } : {},
|
|
186
|
+
...rollback ? { rollback } : {},
|
|
187
|
+
...failureModes ? { failureModes } : {}
|
|
188
|
+
};
|
|
189
|
+
return Object.keys(normalized).length > 0 ? normalized : void 0;
|
|
190
|
+
}
|
|
191
|
+
function hasStructuredPageAgentContract(value) {
|
|
192
|
+
const normalized = normalizePageAgentFrontmatter(value);
|
|
193
|
+
if (!normalized) return false;
|
|
194
|
+
return PAGE_AGENT_STRUCTURED_CONTRACT_FIELDS.some((field) => normalized[field] !== void 0);
|
|
195
|
+
}
|
|
196
|
+
function addStringIssue(issues, object, field) {
|
|
197
|
+
if (!(field in object)) return;
|
|
198
|
+
if (!normalizeString(object[field])) issues.push({
|
|
199
|
+
path: `agent.${field}`,
|
|
200
|
+
message: "must be a non-empty string"
|
|
201
|
+
});
|
|
202
|
+
}
|
|
203
|
+
function addStringListIssues(issues, object, field) {
|
|
204
|
+
if (!(field in object)) return;
|
|
205
|
+
const value = object[field];
|
|
206
|
+
if (!Array.isArray(value) || value.length === 0) {
|
|
207
|
+
issues.push({
|
|
208
|
+
path: `agent.${field}`,
|
|
209
|
+
message: "must be a non-empty string array"
|
|
210
|
+
});
|
|
211
|
+
return;
|
|
212
|
+
}
|
|
213
|
+
value.forEach((item, index) => {
|
|
214
|
+
if (!normalizeString(item)) issues.push({
|
|
215
|
+
path: `agent.${field}[${index}]`,
|
|
216
|
+
message: "must be a non-empty string"
|
|
217
|
+
});
|
|
218
|
+
});
|
|
219
|
+
}
|
|
220
|
+
function editDistance(left, right) {
|
|
221
|
+
const previous = Array.from({ length: right.length + 1 }, (_, index) => index);
|
|
222
|
+
for (let leftIndex = 1; leftIndex <= left.length; leftIndex += 1) {
|
|
223
|
+
const current = [leftIndex];
|
|
224
|
+
for (let rightIndex = 1; rightIndex <= right.length; rightIndex += 1) current[rightIndex] = Math.min((current[rightIndex - 1] ?? 0) + 1, (previous[rightIndex] ?? 0) + 1, (previous[rightIndex - 1] ?? 0) + (left[leftIndex - 1] === right[rightIndex - 1] ? 0 : 1));
|
|
225
|
+
previous.splice(0, previous.length, ...current);
|
|
226
|
+
}
|
|
227
|
+
return previous[right.length] ?? 0;
|
|
228
|
+
}
|
|
229
|
+
function closestKnownField(field, allowed) {
|
|
230
|
+
const closest = allowed.map((candidate) => ({
|
|
231
|
+
candidate,
|
|
232
|
+
distance: editDistance(field, candidate)
|
|
233
|
+
})).sort((left, right) => left.distance - right.distance || left.candidate.localeCompare(right.candidate))[0];
|
|
234
|
+
if (!closest) return void 0;
|
|
235
|
+
const threshold = Math.max(1, Math.min(3, Math.floor(closest.candidate.length / 3)));
|
|
236
|
+
return closest.distance <= threshold ? closest.candidate : void 0;
|
|
237
|
+
}
|
|
238
|
+
function addUnknownKeyIssues(issues, object, allowed, path) {
|
|
239
|
+
for (const field of Object.keys(object)) {
|
|
240
|
+
if (allowed.includes(field)) continue;
|
|
241
|
+
const suggestion = closestKnownField(field, allowed);
|
|
242
|
+
issues.push({
|
|
243
|
+
path: `${path}.${field}`,
|
|
244
|
+
message: suggestion ? `is not recognized; did you mean "${path}.${suggestion}"?` : "is not recognized"
|
|
245
|
+
});
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
/** Return author-facing validation issues without throwing. */
|
|
249
|
+
function getPageAgentFrontmatterIssues(value) {
|
|
250
|
+
if (value === void 0) return [];
|
|
251
|
+
if (!isRecord(value)) return [{
|
|
252
|
+
path: "agent",
|
|
253
|
+
message: "must be an object"
|
|
254
|
+
}];
|
|
255
|
+
const issues = [];
|
|
256
|
+
addUnknownKeyIssues(issues, value, PAGE_AGENT_CONTRACT_FIELDS, "agent");
|
|
257
|
+
if ("tokenBudget" in value && (typeof value.tokenBudget !== "number" || !Number.isFinite(value.tokenBudget) || value.tokenBudget <= 0)) issues.push({
|
|
258
|
+
path: "agent.tokenBudget",
|
|
259
|
+
message: "must be a positive finite number"
|
|
260
|
+
});
|
|
261
|
+
addStringIssue(issues, value, "task");
|
|
262
|
+
addStringIssue(issues, value, "outcome");
|
|
263
|
+
if ("appliesTo" in value) if (!isRecord(value.appliesTo)) issues.push({
|
|
264
|
+
path: "agent.appliesTo",
|
|
265
|
+
message: "must be an object"
|
|
266
|
+
});
|
|
267
|
+
else {
|
|
268
|
+
addUnknownKeyIssues(issues, value.appliesTo, APPLIES_TO_FIELDS, "agent.appliesTo");
|
|
269
|
+
for (const field of [
|
|
270
|
+
"framework",
|
|
271
|
+
"version",
|
|
272
|
+
"package"
|
|
273
|
+
]) {
|
|
274
|
+
if (!(field in value.appliesTo)) continue;
|
|
275
|
+
const fieldValue = value.appliesTo[field];
|
|
276
|
+
if (!normalizeStringOrList(fieldValue)) {
|
|
277
|
+
issues.push({
|
|
278
|
+
path: `agent.appliesTo.${field}`,
|
|
279
|
+
message: "must be a non-empty string or string array"
|
|
280
|
+
});
|
|
281
|
+
continue;
|
|
282
|
+
}
|
|
283
|
+
if (Array.isArray(fieldValue)) fieldValue.forEach((item, index) => {
|
|
284
|
+
if (!normalizeString(item)) issues.push({
|
|
285
|
+
path: `agent.appliesTo.${field}[${index}]`,
|
|
286
|
+
message: "must be a non-empty string"
|
|
287
|
+
});
|
|
288
|
+
});
|
|
289
|
+
}
|
|
290
|
+
if (!normalizeAppliesTo(value.appliesTo)) issues.push({
|
|
291
|
+
path: "agent.appliesTo",
|
|
292
|
+
message: "must include framework, version, or package"
|
|
293
|
+
});
|
|
294
|
+
}
|
|
295
|
+
for (const field of [
|
|
296
|
+
"prerequisites",
|
|
297
|
+
"files",
|
|
298
|
+
"sideEffects",
|
|
299
|
+
"rollback"
|
|
300
|
+
]) addStringListIssues(issues, value, field);
|
|
301
|
+
if ("commands" in value) if (!Array.isArray(value.commands) || value.commands.length === 0) issues.push({
|
|
302
|
+
path: "agent.commands",
|
|
303
|
+
message: "must be a non-empty array"
|
|
304
|
+
});
|
|
305
|
+
else value.commands.forEach((command, index) => {
|
|
306
|
+
if (normalizeString(command)) return;
|
|
307
|
+
if (!isRecord(command)) {
|
|
308
|
+
issues.push({
|
|
309
|
+
path: `agent.commands[${index}]`,
|
|
310
|
+
message: "must be a command string or an object with a non-empty run field"
|
|
311
|
+
});
|
|
312
|
+
return;
|
|
313
|
+
}
|
|
314
|
+
addUnknownKeyIssues(issues, command, COMMAND_FIELDS, `agent.commands[${index}]`);
|
|
315
|
+
if (!normalizeString(command.run)) {
|
|
316
|
+
issues.push({
|
|
317
|
+
path: `agent.commands[${index}]`,
|
|
318
|
+
message: "must be a command string or an object with a non-empty run field"
|
|
319
|
+
});
|
|
320
|
+
return;
|
|
321
|
+
}
|
|
322
|
+
for (const field of ["cwd", "description"]) if (field in command && !normalizeString(command[field])) issues.push({
|
|
323
|
+
path: `agent.commands[${index}].${field}`,
|
|
324
|
+
message: "must be a non-empty string"
|
|
325
|
+
});
|
|
326
|
+
});
|
|
327
|
+
if ("verification" in value) if (!Array.isArray(value.verification) || value.verification.length === 0) issues.push({
|
|
328
|
+
path: "agent.verification",
|
|
329
|
+
message: "must be a non-empty array"
|
|
330
|
+
});
|
|
331
|
+
else value.verification.forEach((step, index) => {
|
|
332
|
+
if (normalizeString(step)) return;
|
|
333
|
+
if (!isRecord(step)) {
|
|
334
|
+
issues.push({
|
|
335
|
+
path: `agent.verification[${index}]`,
|
|
336
|
+
message: "must be a string or an object with description, run, or expect"
|
|
337
|
+
});
|
|
338
|
+
return;
|
|
339
|
+
}
|
|
340
|
+
addUnknownKeyIssues(issues, step, VERIFICATION_FIELDS, `agent.verification[${index}]`);
|
|
341
|
+
if (!normalizeVerification([step])) {
|
|
342
|
+
issues.push({
|
|
343
|
+
path: `agent.verification[${index}]`,
|
|
344
|
+
message: "must be a string or an object with description, run, or expect"
|
|
345
|
+
});
|
|
346
|
+
return;
|
|
347
|
+
}
|
|
348
|
+
for (const field of [
|
|
349
|
+
"description",
|
|
350
|
+
"run",
|
|
351
|
+
"expect"
|
|
352
|
+
]) if (field in step && !normalizeString(step[field])) issues.push({
|
|
353
|
+
path: `agent.verification[${index}].${field}`,
|
|
354
|
+
message: "must be a non-empty string"
|
|
355
|
+
});
|
|
356
|
+
});
|
|
357
|
+
if ("failureModes" in value) if (!Array.isArray(value.failureModes) || value.failureModes.length === 0) issues.push({
|
|
358
|
+
path: "agent.failureModes",
|
|
359
|
+
message: "must be a non-empty array"
|
|
360
|
+
});
|
|
361
|
+
else value.failureModes.forEach((mode, index) => {
|
|
362
|
+
if (normalizeString(mode)) return;
|
|
363
|
+
if (!isRecord(mode)) {
|
|
364
|
+
issues.push({
|
|
365
|
+
path: `agent.failureModes[${index}]`,
|
|
366
|
+
message: "must be a string or an object with a non-empty symptom field"
|
|
367
|
+
});
|
|
368
|
+
return;
|
|
369
|
+
}
|
|
370
|
+
addUnknownKeyIssues(issues, mode, FAILURE_MODE_FIELDS, `agent.failureModes[${index}]`);
|
|
371
|
+
if (!normalizeString(mode.symptom)) {
|
|
372
|
+
issues.push({
|
|
373
|
+
path: `agent.failureModes[${index}]`,
|
|
374
|
+
message: "must be a string or an object with a non-empty symptom field"
|
|
375
|
+
});
|
|
376
|
+
return;
|
|
377
|
+
}
|
|
378
|
+
if ("resolution" in mode && !normalizeString(mode.resolution)) issues.push({
|
|
379
|
+
path: `agent.failureModes[${index}].resolution`,
|
|
380
|
+
message: "must be a non-empty string"
|
|
381
|
+
});
|
|
382
|
+
});
|
|
383
|
+
return issues;
|
|
384
|
+
}
|
|
385
|
+
function yamlString(value) {
|
|
386
|
+
return JSON.stringify(value);
|
|
387
|
+
}
|
|
388
|
+
function renderYamlStringList(lines, indent, key, values) {
|
|
389
|
+
lines.push(`${indent}${key}:`);
|
|
390
|
+
for (const value of values) lines.push(`${indent} - ${yamlString(value)}`);
|
|
391
|
+
}
|
|
392
|
+
function renderYamlObjectList(lines, indent, key, values, fields) {
|
|
393
|
+
lines.push(`${indent}${key}:`);
|
|
394
|
+
for (const value of values) {
|
|
395
|
+
if (typeof value === "string") {
|
|
396
|
+
lines.push(`${indent} - ${yamlString(value)}`);
|
|
397
|
+
continue;
|
|
398
|
+
}
|
|
399
|
+
const record = value;
|
|
400
|
+
const firstField = fields.find((field) => normalizeString(record[field]));
|
|
401
|
+
if (!firstField) continue;
|
|
402
|
+
lines.push(`${indent} - ${firstField}: ${yamlString(normalizeString(record[firstField]))}`);
|
|
403
|
+
for (const field of fields) {
|
|
404
|
+
const fieldValue = normalizeString(record[field]);
|
|
405
|
+
if (field === firstField || !fieldValue) continue;
|
|
406
|
+
lines.push(`${indent} ${field}: ${yamlString(fieldValue)}`);
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
/** Render normalized `agent` YAML in a stable field order. */
|
|
411
|
+
function renderPageAgentFrontmatterYamlLines(value, indentation = 0) {
|
|
412
|
+
const agent = normalizePageAgentFrontmatter(value);
|
|
413
|
+
if (!agent) return [];
|
|
414
|
+
const indent = " ".repeat(Math.max(0, indentation));
|
|
415
|
+
const child = `${indent} `;
|
|
416
|
+
const lines = [`${indent}agent:`];
|
|
417
|
+
if (agent.tokenBudget !== void 0) lines.push(`${child}tokenBudget: ${agent.tokenBudget}`);
|
|
418
|
+
if (agent.task) lines.push(`${child}task: ${yamlString(agent.task)}`);
|
|
419
|
+
if (agent.outcome) lines.push(`${child}outcome: ${yamlString(agent.outcome)}`);
|
|
420
|
+
if (agent.appliesTo) {
|
|
421
|
+
lines.push(`${child}appliesTo:`);
|
|
422
|
+
const appliesIndent = `${child} `;
|
|
423
|
+
for (const field of [
|
|
424
|
+
"framework",
|
|
425
|
+
"version",
|
|
426
|
+
"package"
|
|
427
|
+
]) {
|
|
428
|
+
const values = normalizeStringOrList(agent.appliesTo[field]);
|
|
429
|
+
if (values) renderYamlStringList(lines, appliesIndent, field, values);
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
if (agent.prerequisites) renderYamlStringList(lines, child, "prerequisites", agent.prerequisites);
|
|
433
|
+
if (agent.files) renderYamlStringList(lines, child, "files", agent.files);
|
|
434
|
+
if (agent.commands) renderYamlObjectList(lines, child, "commands", agent.commands, [
|
|
435
|
+
"run",
|
|
436
|
+
"cwd",
|
|
437
|
+
"description"
|
|
438
|
+
]);
|
|
439
|
+
if (agent.sideEffects) renderYamlStringList(lines, child, "sideEffects", agent.sideEffects);
|
|
440
|
+
if (agent.verification) renderYamlObjectList(lines, child, "verification", agent.verification, [
|
|
441
|
+
"description",
|
|
442
|
+
"run",
|
|
443
|
+
"expect"
|
|
444
|
+
]);
|
|
445
|
+
if (agent.rollback) renderYamlStringList(lines, child, "rollback", agent.rollback);
|
|
446
|
+
if (agent.failureModes) renderYamlObjectList(lines, child, "failureModes", agent.failureModes, ["symptom", "resolution"]);
|
|
447
|
+
return lines;
|
|
448
|
+
}
|
|
449
|
+
function inlineCode(value) {
|
|
450
|
+
const longestRun = Math.max(0, ...[...value.matchAll(/`+/g)].map((match) => match[0].length));
|
|
451
|
+
const fence = "`".repeat(longestRun + 1);
|
|
452
|
+
const padding = value.startsWith("`") || value.endsWith("`") ? " " : "";
|
|
453
|
+
return `${fence}${padding}${value}${padding}${fence}`;
|
|
454
|
+
}
|
|
455
|
+
function renderTextList(lines, title, values, code = false) {
|
|
456
|
+
lines.push("", `### ${title}`, "");
|
|
457
|
+
for (const value of values) lines.push(`- ${code ? inlineCode(value) : value}`);
|
|
458
|
+
}
|
|
459
|
+
/** Render the structured contract as deterministic, compact machine-readable Markdown. */
|
|
460
|
+
function renderPageAgentContractMarkdown(value) {
|
|
461
|
+
const agent = normalizePageAgentFrontmatter(value);
|
|
462
|
+
if (!agent || !hasStructuredPageAgentContract(agent)) return "";
|
|
463
|
+
const lines = [PAGE_AGENT_CONTRACT_START_MARKER, "## Agent Contract"];
|
|
464
|
+
if (agent.task) lines.push("", `Task: ${agent.task}`);
|
|
465
|
+
if (agent.outcome) lines.push(`Outcome: ${agent.outcome}`);
|
|
466
|
+
if (agent.appliesTo) {
|
|
467
|
+
lines.push("", "### Applies To", "");
|
|
468
|
+
for (const [label, field] of [
|
|
469
|
+
["Framework", "framework"],
|
|
470
|
+
["Version", "version"],
|
|
471
|
+
["Package", "package"]
|
|
472
|
+
]) {
|
|
473
|
+
const values = normalizeStringOrList(agent.appliesTo[field]);
|
|
474
|
+
if (values) lines.push(`- ${label}: ${values.map(inlineCode).join(", ")}`);
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
if (agent.prerequisites) renderTextList(lines, "Prerequisites", agent.prerequisites);
|
|
478
|
+
if (agent.files) renderTextList(lines, "Files", agent.files, true);
|
|
479
|
+
if (agent.commands) {
|
|
480
|
+
lines.push("", "### Commands", "");
|
|
481
|
+
for (const command of agent.commands) {
|
|
482
|
+
if (typeof command === "string") {
|
|
483
|
+
lines.push(`- ${inlineCode(command)}`);
|
|
484
|
+
continue;
|
|
485
|
+
}
|
|
486
|
+
const details = [command.cwd ? `cwd ${inlineCode(command.cwd)}` : void 0, command.description].filter(Boolean);
|
|
487
|
+
lines.push(`- ${inlineCode(command.run)}${details.length ? ` — ${details.join("; ")}` : ""}`);
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
if (agent.sideEffects) renderTextList(lines, "Side Effects", agent.sideEffects);
|
|
491
|
+
if (agent.verification) {
|
|
492
|
+
lines.push("", "### Verification", "");
|
|
493
|
+
for (const step of agent.verification) {
|
|
494
|
+
if (typeof step === "string") {
|
|
495
|
+
lines.push(`- ${step}`);
|
|
496
|
+
continue;
|
|
497
|
+
}
|
|
498
|
+
const summary = step.description ?? (step.run ? `Run ${inlineCode(step.run)}` : step.expect ?? "Verification step");
|
|
499
|
+
lines.push(`- ${summary}`);
|
|
500
|
+
if (step.run && step.description) lines.push(` - Run: ${inlineCode(step.run)}`);
|
|
501
|
+
if (step.expect && step.expect !== summary) lines.push(` - Expected: ${step.expect}`);
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
if (agent.rollback) renderTextList(lines, "Rollback", agent.rollback);
|
|
505
|
+
if (agent.failureModes) {
|
|
506
|
+
lines.push("", "### Failure Modes", "");
|
|
507
|
+
for (const mode of agent.failureModes) {
|
|
508
|
+
if (typeof mode === "string") {
|
|
509
|
+
lines.push(`- ${mode}`);
|
|
510
|
+
continue;
|
|
511
|
+
}
|
|
512
|
+
lines.push(`- ${mode.symptom}${mode.resolution ? ` — Recovery: ${mode.resolution}` : ""}`);
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
lines.push(PAGE_AGENT_CONTRACT_END_MARKER);
|
|
516
|
+
return lines.join("\n");
|
|
517
|
+
}
|
|
518
|
+
/** Remove generated contract blocks while preserving handwritten guidance. */
|
|
519
|
+
function stripGeneratedPageAgentContractMarkdown(markdown) {
|
|
520
|
+
const newline = markdown.includes("\r\n") ? "\r\n" : "\n";
|
|
521
|
+
const output = [];
|
|
522
|
+
let fence;
|
|
523
|
+
let pendingBlock;
|
|
524
|
+
let skipEmptySeparator = false;
|
|
525
|
+
for (const line of markdown.split(/\r?\n/)) {
|
|
526
|
+
if (pendingBlock) {
|
|
527
|
+
pendingBlock.push(line);
|
|
528
|
+
if (isGeneratedAgentContractMarker(line, PAGE_AGENT_CONTRACT_END_MARKER)) {
|
|
529
|
+
pendingBlock = void 0;
|
|
530
|
+
skipEmptySeparator = true;
|
|
531
|
+
}
|
|
532
|
+
continue;
|
|
533
|
+
}
|
|
534
|
+
if (!fence && isGeneratedAgentContractMarker(line, PAGE_AGENT_CONTRACT_START_MARKER)) {
|
|
535
|
+
pendingBlock = [line];
|
|
536
|
+
continue;
|
|
537
|
+
}
|
|
538
|
+
if (skipEmptySeparator && line.length === 0) continue;
|
|
539
|
+
skipEmptySeparator = false;
|
|
540
|
+
output.push(line);
|
|
541
|
+
fence = advanceMarkdownFence(line, fence);
|
|
542
|
+
}
|
|
543
|
+
if (pendingBlock) output.push(...pendingBlock);
|
|
544
|
+
while (output[0] === "") output.shift();
|
|
545
|
+
return output.join(newline);
|
|
546
|
+
}
|
|
547
|
+
function isGeneratedAgentContractMarker(line, marker) {
|
|
548
|
+
if (line.trim() !== marker) return false;
|
|
549
|
+
const markerIndex = line.indexOf(marker);
|
|
550
|
+
return markerIndex <= 3 && /^ *$/.test(line.slice(0, markerIndex));
|
|
551
|
+
}
|
|
552
|
+
function advanceMarkdownFence(line, fence) {
|
|
553
|
+
if (fence) {
|
|
554
|
+
const closing = /^ {0,3}(`+|~+)[\t ]*$/.exec(line)?.[1];
|
|
555
|
+
if (closing?.[0] === fence.character && closing.length >= fence.length) return;
|
|
556
|
+
return fence;
|
|
557
|
+
}
|
|
558
|
+
const opening = /^ {0,3}(`{3,}|~{3,})(.*)$/.exec(line);
|
|
559
|
+
if (!opening?.[1]) return void 0;
|
|
560
|
+
if (opening[1][0] === "`" && opening[2]?.includes("`")) return void 0;
|
|
561
|
+
return {
|
|
562
|
+
character: opening[1][0],
|
|
563
|
+
length: opening[1].length
|
|
564
|
+
};
|
|
565
|
+
}
|
|
566
|
+
function isAgentContractHeadingText(value) {
|
|
567
|
+
return value.trim().toLowerCase() === "agent contract";
|
|
568
|
+
}
|
|
569
|
+
function hasPageAgentContractHeading(markdown) {
|
|
570
|
+
let fence;
|
|
571
|
+
let previousLine;
|
|
572
|
+
for (const line of markdown.split(/\r?\n/)) {
|
|
573
|
+
const nextFence = advanceMarkdownFence(line, fence);
|
|
574
|
+
if (fence || nextFence) {
|
|
575
|
+
fence = nextFence;
|
|
576
|
+
previousLine = void 0;
|
|
577
|
+
continue;
|
|
578
|
+
}
|
|
579
|
+
const atxText = /^ {0,3}#{1,6}(?:[\t ]+|$)(.*)$/.exec(line)?.[1]?.replace(/[\t ]+#+[\t ]*$/, "").trim();
|
|
580
|
+
if (atxText && isAgentContractHeadingText(atxText)) return true;
|
|
581
|
+
if (previousLine && /^ {0,3}(?:=+|-+)[\t ]*$/.test(line) && isAgentContractHeadingText(previousLine)) return true;
|
|
582
|
+
previousLine = line.trim().length > 0 && !/^ {4}/.test(line) && !/^ {0,3}\t/.test(line) ? line : void 0;
|
|
583
|
+
}
|
|
584
|
+
return false;
|
|
585
|
+
}
|
|
586
|
+
/** Insert one generated contract unless the document already has handwritten contract guidance. */
|
|
587
|
+
function upsertPageAgentContractMarkdown(markdown, value) {
|
|
588
|
+
const cleaned = stripGeneratedPageAgentContractMarkdown(markdown);
|
|
589
|
+
const contract = renderPageAgentContractMarkdown(value);
|
|
590
|
+
if (!contract || hasPageAgentContractHeading(cleaned)) return cleaned;
|
|
591
|
+
return cleaned ? `${contract}\n\n${cleaned.replace(/^\r?\n+/, "")}` : contract;
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
//#endregion
|
|
595
|
+
//#region src/markdown-sections.ts
|
|
596
|
+
const docsMarkdownMdxProcessor = createProcessor({ remarkPlugins: [remarkGfm] });
|
|
597
|
+
function isDocsMarkdownLiteralPrompt(node) {
|
|
598
|
+
return (node.type === "mdxJsxFlowElement" || node.type === "mdxJsxTextElement") && node.name === "Prompt";
|
|
599
|
+
}
|
|
600
|
+
function collectDocsMarkdownLiteralPromptHeadings(root) {
|
|
601
|
+
const headings = [];
|
|
602
|
+
const visit = (node, insidePrompt) => {
|
|
603
|
+
const literal = insidePrompt || isDocsMarkdownLiteralPrompt(node);
|
|
604
|
+
if (literal && node.type === "heading") {
|
|
605
|
+
headings.push(node);
|
|
606
|
+
return;
|
|
607
|
+
}
|
|
608
|
+
for (const child of node.children ?? []) visit(child, literal);
|
|
609
|
+
};
|
|
610
|
+
visit(root, false);
|
|
611
|
+
return headings;
|
|
612
|
+
}
|
|
613
|
+
/**
|
|
614
|
+
* Run a third-party heading visitor without exposing headings inside `<Prompt>`.
|
|
615
|
+
*
|
|
616
|
+
* Prompt children are literal, copyable input rather than rendered page content.
|
|
617
|
+
* The node types are restored synchronously even when the visitor throws.
|
|
618
|
+
*/
|
|
619
|
+
function withDocsMarkdownRenderableHeadings(root, visitor) {
|
|
620
|
+
if (!root || typeof root !== "object") return visitor();
|
|
621
|
+
const hiddenHeadings = collectDocsMarkdownLiteralPromptHeadings(root);
|
|
622
|
+
for (const heading of hiddenHeadings) heading.type = "docsLiteralPromptHeading";
|
|
623
|
+
try {
|
|
624
|
+
return visitor();
|
|
625
|
+
} finally {
|
|
626
|
+
for (const heading of hiddenHeadings) heading.type = "heading";
|
|
627
|
+
}
|
|
628
|
+
}
|
|
629
|
+
function protectDocsMarkdownCodeSpans(value) {
|
|
630
|
+
const spans = [];
|
|
631
|
+
return {
|
|
632
|
+
value: value.replace(/(`+)([^]*?)\1/gu, (_match, _ticks, code) => {
|
|
633
|
+
const normalized = code.replace(/\s+/gu, " ");
|
|
634
|
+
const visible = normalized.length > 2 && normalized.startsWith(" ") && normalized.endsWith(" ") && normalized.trim() ? normalized.slice(1, -1) : normalized;
|
|
635
|
+
return `\uE000${spans.push(visible) - 1}\uE001`;
|
|
636
|
+
}),
|
|
637
|
+
restore: (resolved) => resolved.replace(/\uE000(\d+)\uE001/gu, (_match, index) => spans[Number(index)] ?? "")
|
|
638
|
+
};
|
|
639
|
+
}
|
|
640
|
+
function protectDocsMarkdownEscapes(value) {
|
|
641
|
+
const escapes = [];
|
|
642
|
+
return {
|
|
643
|
+
value: value.replace(/\\([!"#$%&'()*+,\-./:;<=>?@[\]^_`{|}~])/gu, (_match, punctuation) => `\uE002${escapes.push(punctuation) - 1}\uE003`),
|
|
644
|
+
restore: (resolved) => resolved.replace(/\uE002(\d+)\uE003/gu, (_match, index) => escapes[Number(index)] ?? "")
|
|
645
|
+
};
|
|
646
|
+
}
|
|
647
|
+
function findDocsMarkdownClosingDelimiter(value, start, open, close) {
|
|
648
|
+
let depth = 0;
|
|
649
|
+
for (let index = start; index < value.length; index += 1) {
|
|
650
|
+
if (value[index] === "\\") {
|
|
651
|
+
index += 1;
|
|
652
|
+
continue;
|
|
653
|
+
}
|
|
654
|
+
if (value[index] === open) depth += 1;
|
|
655
|
+
if (value[index] !== close) continue;
|
|
656
|
+
depth -= 1;
|
|
657
|
+
if (depth === 0) return index;
|
|
658
|
+
}
|
|
659
|
+
return -1;
|
|
660
|
+
}
|
|
661
|
+
function normalizeDocsMarkdownReferenceLabel(value) {
|
|
662
|
+
return parseEntities(value.replace(/\\([!"#$%&'()*+,\-./:;<=>?@[\]^_`{|}~])/gu, "$1"), { nonTerminated: false }).replace(/\s+/gu, " ").trim().toLowerCase();
|
|
663
|
+
}
|
|
664
|
+
function cleanDocsMarkdownLinksAndImages(value, definedReferences, restoreEscapes = (input) => input) {
|
|
665
|
+
let output = "";
|
|
666
|
+
let index = 0;
|
|
667
|
+
while (index < value.length) {
|
|
668
|
+
const image = value[index] === "!" && value[index + 1] === "[";
|
|
669
|
+
const link = value[index] === "[";
|
|
670
|
+
if (!image && !link) {
|
|
671
|
+
output += value[index];
|
|
672
|
+
index += 1;
|
|
673
|
+
continue;
|
|
674
|
+
}
|
|
675
|
+
const labelStart = index + (image ? 2 : 1);
|
|
676
|
+
const labelEnd = findDocsMarkdownClosingDelimiter(value, labelStart - 1, "[", "]");
|
|
677
|
+
if (labelEnd < 0) {
|
|
678
|
+
output += value[index];
|
|
679
|
+
index += 1;
|
|
680
|
+
continue;
|
|
681
|
+
}
|
|
682
|
+
const label = value.slice(labelStart, labelEnd);
|
|
683
|
+
let end = labelEnd + 1;
|
|
684
|
+
let inlineDestination = false;
|
|
685
|
+
let referenceLabel;
|
|
686
|
+
if (value[end] === "(") {
|
|
687
|
+
const destinationEnd = findDocsMarkdownClosingDelimiter(value, end, "(", ")");
|
|
688
|
+
if (destinationEnd < 0) {
|
|
689
|
+
output += value[index];
|
|
690
|
+
index += 1;
|
|
691
|
+
continue;
|
|
692
|
+
}
|
|
693
|
+
end = destinationEnd + 1;
|
|
694
|
+
inlineDestination = true;
|
|
695
|
+
} else if (value[end] === "[") {
|
|
696
|
+
const referenceEnd = findDocsMarkdownClosingDelimiter(value, end, "[", "]");
|
|
697
|
+
if (referenceEnd < 0) {
|
|
698
|
+
output += value[index];
|
|
699
|
+
index += 1;
|
|
700
|
+
continue;
|
|
701
|
+
}
|
|
702
|
+
referenceLabel = value.slice(end + 1, referenceEnd) || label;
|
|
703
|
+
end = referenceEnd + 1;
|
|
704
|
+
} else referenceLabel = label;
|
|
705
|
+
const hasMatchingDefinition = referenceLabel !== void 0 && definedReferences?.has(normalizeDocsMarkdownReferenceLabel(restoreEscapes(referenceLabel))) === true;
|
|
706
|
+
if (!image && (inlineDestination || hasMatchingDefinition)) output += label;
|
|
707
|
+
else if (!inlineDestination && !hasMatchingDefinition) output += value.slice(index, end);
|
|
708
|
+
index = end;
|
|
709
|
+
}
|
|
710
|
+
return output;
|
|
711
|
+
}
|
|
712
|
+
function stripDocsMarkdownTags(value) {
|
|
713
|
+
let output = "";
|
|
714
|
+
let index = 0;
|
|
715
|
+
while (index < value.length) {
|
|
716
|
+
if (value[index] !== "<" || !/[A-Za-z/!?]/u.test(value[index + 1] ?? "")) {
|
|
717
|
+
output += value[index];
|
|
718
|
+
index += 1;
|
|
719
|
+
continue;
|
|
720
|
+
}
|
|
721
|
+
let quote;
|
|
722
|
+
let braceDepth = 0;
|
|
723
|
+
let end = index + 1;
|
|
724
|
+
for (; end < value.length; end += 1) {
|
|
725
|
+
const character = value[end];
|
|
726
|
+
if (quote) {
|
|
727
|
+
if (character === quote && value[end - 1] !== "\\") quote = void 0;
|
|
728
|
+
continue;
|
|
729
|
+
}
|
|
730
|
+
if (character === "'" || character === "\"") {
|
|
731
|
+
quote = character;
|
|
732
|
+
continue;
|
|
733
|
+
}
|
|
734
|
+
if (character === "{") {
|
|
735
|
+
braceDepth += 1;
|
|
736
|
+
continue;
|
|
737
|
+
}
|
|
738
|
+
if (character === "}") {
|
|
739
|
+
braceDepth = Math.max(0, braceDepth - 1);
|
|
740
|
+
continue;
|
|
741
|
+
}
|
|
742
|
+
if (character === ">" && braceDepth === 0) break;
|
|
743
|
+
}
|
|
744
|
+
if (end >= value.length) {
|
|
745
|
+
output += value[index];
|
|
746
|
+
index += 1;
|
|
747
|
+
continue;
|
|
748
|
+
}
|
|
749
|
+
index = end + 1;
|
|
750
|
+
}
|
|
751
|
+
return output;
|
|
752
|
+
}
|
|
753
|
+
/**
|
|
754
|
+
* Reduce inline Markdown in a heading to the visible label used by search and anchors.
|
|
755
|
+
* This intentionally covers the common inline constructs without adding a Markdown parser
|
|
756
|
+
* dependency to the runtime package.
|
|
757
|
+
*/
|
|
758
|
+
function cleanDocsMarkdownHeadingLabelWithReferences(value, definedReferences) {
|
|
759
|
+
const codeSpans = protectDocsMarkdownCodeSpans(value);
|
|
760
|
+
const escapes = protectDocsMarkdownEscapes(codeSpans.value);
|
|
761
|
+
const visible = stripDocsMarkdownTags(cleanDocsMarkdownLinksAndImages(escapes.value, definedReferences, escapes.restore).replace(/<((?:https?:\/\/|mailto:)[^>]+)>/giu, "$1")).replace(/`+([^`]*?)`+/gu, "$1").replace(/\*\*([^*\n]+)\*\*/gu, "$1").replace(/(?<![\\\p{L}\p{N}])__([^_\n]+)__(?![\p{L}\p{N}])/gu, "$1").replace(/\*([^*\n]+)\*/gu, "$1").replace(/(?<![\\\p{L}\p{N}])_([^_\n]+)_(?![\p{L}\p{N}])/gu, "$1").replace(/~~([^~\n]+)~~/gu, "$1").trim();
|
|
762
|
+
return codeSpans.restore(escapes.restore(parseEntities(visible, { nonTerminated: false }))).replace(/\s+/gu, " ").trim();
|
|
763
|
+
}
|
|
764
|
+
function cleanDocsMarkdownHeadingLabel(value) {
|
|
765
|
+
return cleanDocsMarkdownHeadingLabelWithReferences(value);
|
|
766
|
+
}
|
|
767
|
+
const EXPLICIT_DOCS_MARKDOWN_HEADING_ID = /\s*\\?\[#([^\]\r\n]+)\]\s*$/u;
|
|
768
|
+
function createDocsHeadingAnchorState() {
|
|
769
|
+
const used = /* @__PURE__ */ new Set();
|
|
770
|
+
const suffixes = /* @__PURE__ */ new Map();
|
|
771
|
+
let headingIndex = 0;
|
|
772
|
+
return (title, explicitAnchor) => {
|
|
773
|
+
const fallback = `section-${headingIndex}`;
|
|
774
|
+
headingIndex += 1;
|
|
775
|
+
const original = explicitAnchor?.trim() || slug(title) || fallback;
|
|
776
|
+
let anchor = original;
|
|
777
|
+
let suffix = suffixes.get(original) ?? 0;
|
|
778
|
+
while (used.has(anchor)) {
|
|
779
|
+
suffix += 1;
|
|
780
|
+
anchor = `${original}-${suffix}`;
|
|
781
|
+
}
|
|
782
|
+
suffixes.set(original, suffix);
|
|
783
|
+
used.add(anchor);
|
|
784
|
+
return anchor;
|
|
785
|
+
};
|
|
786
|
+
}
|
|
787
|
+
/** Normalize text that has already been flattened from a rendered heading AST. */
|
|
788
|
+
function cleanDocsRenderedHeadingLabel(value) {
|
|
789
|
+
return value.replace(/\s+/gu, " ").trim();
|
|
790
|
+
}
|
|
791
|
+
/**
|
|
792
|
+
* Create a stateful resolver for text already flattened by an MDX renderer.
|
|
793
|
+
*
|
|
794
|
+
* Unlike the raw-Markdown resolver, this does not reinterpret inline code or a
|
|
795
|
+
* literal `[#id]` as Markdown syntax.
|
|
796
|
+
*/
|
|
797
|
+
function createDocsRenderedHeadingAnchorResolver() {
|
|
798
|
+
const reserveAnchor = createDocsHeadingAnchorState();
|
|
799
|
+
return (value, options = {}) => {
|
|
800
|
+
const title = cleanDocsRenderedHeadingLabel(value);
|
|
801
|
+
const explicitAnchor = options.explicitAnchor?.trim();
|
|
802
|
+
return {
|
|
803
|
+
source: title,
|
|
804
|
+
title,
|
|
805
|
+
anchor: reserveAnchor(title, explicitAnchor),
|
|
806
|
+
explicit: Boolean(explicitAnchor)
|
|
807
|
+
};
|
|
808
|
+
};
|
|
809
|
+
}
|
|
810
|
+
/**
|
|
811
|
+
* Create a stateful heading resolver compatible with Fumadocs' GitHub-style rendered IDs.
|
|
812
|
+
*
|
|
813
|
+
* A fresh resolver must be used for each document so duplicate and collision suffixes
|
|
814
|
+
* remain deterministic in source order.
|
|
815
|
+
*/
|
|
816
|
+
function createDocsMarkdownHeadingAnchorResolverWithReferences(definedReferences) {
|
|
817
|
+
const reserveAnchor = createDocsHeadingAnchorState();
|
|
818
|
+
return (value) => {
|
|
819
|
+
const explicitMatch = value.match(EXPLICIT_DOCS_MARKDOWN_HEADING_ID);
|
|
820
|
+
const rawExplicitAnchor = explicitMatch?.[1]?.trim();
|
|
821
|
+
const explicitMarker = explicitMatch?.index === void 0 ? "" : value.slice(explicitMatch.index).trimStart();
|
|
822
|
+
const resolvesAsReference = Boolean(rawExplicitAnchor) && !explicitMarker.startsWith("\\[") && definedReferences?.has(normalizeDocsMarkdownReferenceLabel(`#${rawExplicitAnchor}`)) === true;
|
|
823
|
+
const explicitAnchor = rawExplicitAnchor && !resolvesAsReference ? parseEntities(rawExplicitAnchor, { nonTerminated: false }) : void 0;
|
|
824
|
+
const source = (explicitAnchor && explicitMatch ? value.slice(0, explicitMatch.index) : value).trim();
|
|
825
|
+
const title = cleanDocsMarkdownHeadingLabelWithReferences(source, definedReferences);
|
|
826
|
+
return {
|
|
827
|
+
source,
|
|
828
|
+
title,
|
|
829
|
+
anchor: reserveAnchor(title, explicitAnchor),
|
|
830
|
+
explicit: Boolean(explicitAnchor)
|
|
831
|
+
};
|
|
832
|
+
};
|
|
833
|
+
}
|
|
834
|
+
function createDocsMarkdownHeadingAnchorResolver() {
|
|
835
|
+
return createDocsMarkdownHeadingAnchorResolverWithReferences();
|
|
836
|
+
}
|
|
837
|
+
function collectDocsMarkdownAstReferenceDefinitions(root) {
|
|
838
|
+
const definitions = /* @__PURE__ */ new Set();
|
|
839
|
+
const visit = (node) => {
|
|
840
|
+
if (isDocsMarkdownLiteralPrompt(node)) return;
|
|
841
|
+
if (node.type === "definition" && typeof node.identifier === "string") definitions.add(normalizeDocsMarkdownReferenceLabel(node.identifier));
|
|
842
|
+
for (const child of node.children ?? []) visit(child);
|
|
843
|
+
};
|
|
844
|
+
visit(root);
|
|
845
|
+
return definitions;
|
|
846
|
+
}
|
|
847
|
+
function formatDocsMarkdownAstReference(node, definedReferences) {
|
|
848
|
+
const image = node.type === "imageReference";
|
|
849
|
+
const identifier = typeof node.identifier === "string" ? normalizeDocsMarkdownReferenceLabel(node.identifier) : "";
|
|
850
|
+
if (Boolean(identifier) && definedReferences.has(identifier)) return image ? "" : (node.children ?? []).map((child) => flattenDocsMarkdownAstNode(child, definedReferences)).join("");
|
|
851
|
+
const visibleLabel = image ? typeof node.alt === "string" ? node.alt : "" : (node.children ?? []).map((child) => flattenDocsMarkdownAstNode(child, definedReferences)).join("");
|
|
852
|
+
const referenceLabel = typeof node.label === "string" ? node.label : typeof node.identifier === "string" ? node.identifier : visibleLabel;
|
|
853
|
+
const prefix = image ? "!" : "";
|
|
854
|
+
if (node.referenceType === "collapsed") return `${prefix}[${visibleLabel}][]`;
|
|
855
|
+
if (node.referenceType === "shortcut") return `${prefix}[${visibleLabel}]`;
|
|
856
|
+
return `${prefix}[${visibleLabel}][${referenceLabel}]`;
|
|
857
|
+
}
|
|
858
|
+
function flattenDocsMarkdownAstNode(node, definedReferences) {
|
|
859
|
+
if (node.type === "linkReference" || node.type === "imageReference") return formatDocsMarkdownAstReference(node, definedReferences);
|
|
860
|
+
if (Array.isArray(node.children)) return node.children.map((child) => flattenDocsMarkdownAstNode(child, definedReferences)).join("");
|
|
861
|
+
return typeof node.value === "string" ? node.value : "";
|
|
862
|
+
}
|
|
863
|
+
function assignDocsMarkdownAstHeadingAnchors(root, options, deferHeading) {
|
|
864
|
+
const customId = options.customId ?? true;
|
|
865
|
+
const headings = [];
|
|
866
|
+
const definedReferences = collectDocsMarkdownAstReferenceDefinitions(root);
|
|
867
|
+
const visit = (node) => {
|
|
868
|
+
if (isDocsMarkdownLiteralPrompt(node)) return;
|
|
869
|
+
if (node.type === "heading") {
|
|
870
|
+
const lastNode = node.children?.at(-1);
|
|
871
|
+
let explicitAnchor;
|
|
872
|
+
if (customId && lastNode?.type === "text" && typeof lastNode.value === "string") {
|
|
873
|
+
const match = lastNode.value.match(EXPLICIT_DOCS_MARKDOWN_HEADING_ID);
|
|
874
|
+
explicitAnchor = match?.[1]?.trim();
|
|
875
|
+
if (explicitAnchor && match?.index !== void 0) lastNode.value = lastNode.value.slice(0, match.index);
|
|
876
|
+
}
|
|
877
|
+
const existingAnchor = node.data?.hProperties?.id;
|
|
878
|
+
headings.push({
|
|
879
|
+
node,
|
|
880
|
+
title: cleanDocsRenderedHeadingLabel(flattenDocsMarkdownAstNode(node, definedReferences)),
|
|
881
|
+
explicit: Boolean(explicitAnchor),
|
|
882
|
+
explicitAnchor: explicitAnchor || (typeof existingAnchor === "string" ? existingAnchor : void 0),
|
|
883
|
+
start: typeof node.position?.start?.line === "number" ? node.position.start.line - 1 : NaN
|
|
884
|
+
});
|
|
885
|
+
return;
|
|
886
|
+
}
|
|
887
|
+
for (const child of node.children ?? []) visit(child);
|
|
888
|
+
};
|
|
889
|
+
visit(root);
|
|
890
|
+
const ordered = deferHeading ? [...headings.filter((heading) => !deferHeading(heading.start)), ...headings.filter((heading) => deferHeading(heading.start))] : headings;
|
|
891
|
+
const resolveHeading = createDocsRenderedHeadingAnchorResolver();
|
|
892
|
+
for (const heading of ordered) {
|
|
893
|
+
const resolved = resolveHeading(heading.title, { explicitAnchor: heading.explicitAnchor });
|
|
894
|
+
heading.node.data ??= {};
|
|
895
|
+
heading.node.data.hProperties ??= {};
|
|
896
|
+
heading.node.data.hProperties.id = resolved.anchor;
|
|
897
|
+
}
|
|
898
|
+
return headings;
|
|
899
|
+
}
|
|
900
|
+
/**
|
|
901
|
+
* Assign canonical IDs to every heading in an MDX syntax tree.
|
|
902
|
+
*
|
|
903
|
+
* Render adapters call this before Fumadocs extracts its TOC, ensuring HTML,
|
|
904
|
+
* Markdown retrieval, search, Ask AI, and MCP all use the same fragments.
|
|
905
|
+
*/
|
|
906
|
+
function applyDocsMarkdownHeadingAnchors(root, options = {}) {
|
|
907
|
+
if (!root || typeof root !== "object") return;
|
|
908
|
+
assignDocsMarkdownAstHeadingAnchors(root, options);
|
|
909
|
+
}
|
|
910
|
+
/** Resolve one heading with the canonical GitHub-style anchor algorithm. */
|
|
911
|
+
function slugifyDocsMarkdownHeading(value) {
|
|
912
|
+
return createDocsMarkdownHeadingAnchorResolver()(value).anchor;
|
|
913
|
+
}
|
|
914
|
+
function readOpeningFence(line) {
|
|
915
|
+
const match = line.match(/^ {0,3}(`{3,}|~{3,})/u);
|
|
916
|
+
if (!match) return void 0;
|
|
917
|
+
return {
|
|
918
|
+
marker: match[1][0],
|
|
919
|
+
length: match[1].length
|
|
920
|
+
};
|
|
921
|
+
}
|
|
922
|
+
function isClosingFence(line, fence) {
|
|
923
|
+
const marker = fence.marker === "`" ? "`" : "~";
|
|
924
|
+
const match = line.match(new RegExp(`^ {0,3}(${marker}{${fence.length},})[\\t ]*$`, "u"));
|
|
925
|
+
return Boolean(match);
|
|
926
|
+
}
|
|
927
|
+
function splitDocsMarkdownContainerLine(line) {
|
|
928
|
+
let prefix = "";
|
|
929
|
+
let content = line;
|
|
930
|
+
const containers = [];
|
|
931
|
+
while (content) {
|
|
932
|
+
const blockquote = content.match(/^ {0,3}>[\t ]?/u);
|
|
933
|
+
if (blockquote) {
|
|
934
|
+
prefix += blockquote[0];
|
|
935
|
+
content = content.slice(blockquote[0].length);
|
|
936
|
+
containers.push({
|
|
937
|
+
type: "blockquote",
|
|
938
|
+
continuationIndent: 0
|
|
939
|
+
});
|
|
940
|
+
continue;
|
|
941
|
+
}
|
|
942
|
+
const listItem = content.match(/^ {0,3}(?:[-+*]|\d{1,9}[.)])[\t ]+/u);
|
|
943
|
+
if (listItem) {
|
|
944
|
+
prefix += listItem[0];
|
|
945
|
+
content = content.slice(listItem[0].length);
|
|
946
|
+
containers.push({
|
|
947
|
+
type: "list",
|
|
948
|
+
continuationIndent: listItem[0].length
|
|
949
|
+
});
|
|
950
|
+
continue;
|
|
951
|
+
}
|
|
952
|
+
break;
|
|
953
|
+
}
|
|
954
|
+
return {
|
|
955
|
+
prefix,
|
|
956
|
+
content,
|
|
957
|
+
containers
|
|
958
|
+
};
|
|
959
|
+
}
|
|
960
|
+
function continueDocsMarkdownContainerLine(line, containers) {
|
|
961
|
+
let prefix = "";
|
|
962
|
+
let content = line;
|
|
963
|
+
for (const container of containers) {
|
|
964
|
+
if (container.type === "blockquote") {
|
|
965
|
+
const blockquote = content.match(/^ {0,3}>[\t ]?/u);
|
|
966
|
+
if (!blockquote) return void 0;
|
|
967
|
+
prefix += blockquote[0];
|
|
968
|
+
content = content.slice(blockquote[0].length);
|
|
969
|
+
continue;
|
|
970
|
+
}
|
|
971
|
+
if (!content.trim()) {
|
|
972
|
+
prefix += content;
|
|
973
|
+
content = "";
|
|
974
|
+
continue;
|
|
975
|
+
}
|
|
976
|
+
const indentation = content.match(/^[\t ]*/u)?.[0] ?? "";
|
|
977
|
+
if (indentation.replace(/\t/gu, " ").length < container.continuationIndent) return void 0;
|
|
978
|
+
let consumedWidth = 0;
|
|
979
|
+
let consumedLength = 0;
|
|
980
|
+
while (consumedLength < indentation.length) {
|
|
981
|
+
consumedWidth += indentation[consumedLength] === " " ? 4 : 1;
|
|
982
|
+
consumedLength += 1;
|
|
983
|
+
if (consumedWidth >= container.continuationIndent) break;
|
|
984
|
+
}
|
|
985
|
+
const consumed = content.slice(0, consumedLength);
|
|
986
|
+
prefix += consumed;
|
|
987
|
+
content = content.slice(consumedLength);
|
|
988
|
+
}
|
|
989
|
+
return {
|
|
990
|
+
prefix,
|
|
991
|
+
content,
|
|
992
|
+
containers: containers.map((container) => ({ ...container }))
|
|
993
|
+
};
|
|
994
|
+
}
|
|
995
|
+
function readDocsMarkdownFenceLine(line, fence) {
|
|
996
|
+
if (fence.containers.length === 0) return {
|
|
997
|
+
prefix: "",
|
|
998
|
+
content: line,
|
|
999
|
+
containers: []
|
|
1000
|
+
};
|
|
1001
|
+
return continueDocsMarkdownContainerLine(line, fence.containers);
|
|
1002
|
+
}
|
|
1003
|
+
function readDocsMarkdownReferenceDefinition(line) {
|
|
1004
|
+
const content = line.trimStart();
|
|
1005
|
+
if (!content.startsWith("[")) return void 0;
|
|
1006
|
+
const labelEnd = findDocsMarkdownClosingDelimiter(content, 0, "[", "]");
|
|
1007
|
+
if (labelEnd <= 1 || content[labelEnd + 1] !== ":") return void 0;
|
|
1008
|
+
return normalizeDocsMarkdownReferenceLabel(content.slice(1, labelEnd)) || void 0;
|
|
1009
|
+
}
|
|
1010
|
+
function collectDocsMarkdownReferenceDefinitions(lines) {
|
|
1011
|
+
const definitions = /* @__PURE__ */ new Set();
|
|
1012
|
+
let openFence;
|
|
1013
|
+
for (const line of lines) {
|
|
1014
|
+
if (openFence) {
|
|
1015
|
+
const fenceLine = readDocsMarkdownFenceLine(line, openFence);
|
|
1016
|
+
if (fenceLine) {
|
|
1017
|
+
if (isClosingFence(fenceLine.content, openFence)) openFence = void 0;
|
|
1018
|
+
continue;
|
|
1019
|
+
}
|
|
1020
|
+
openFence = void 0;
|
|
1021
|
+
}
|
|
1022
|
+
const containerLine = splitDocsMarkdownContainerLine(line);
|
|
1023
|
+
const openingFence = readOpeningFence(containerLine.content);
|
|
1024
|
+
if (openingFence) {
|
|
1025
|
+
openFence = {
|
|
1026
|
+
...openingFence,
|
|
1027
|
+
containers: containerLine.containers
|
|
1028
|
+
};
|
|
1029
|
+
continue;
|
|
1030
|
+
}
|
|
1031
|
+
const definition = readDocsMarkdownReferenceDefinition(containerLine.content);
|
|
1032
|
+
if (definition) definitions.add(definition);
|
|
1033
|
+
}
|
|
1034
|
+
return definitions;
|
|
1035
|
+
}
|
|
1036
|
+
function scanDocsMarkdownFallbackEsmLine(line, state) {
|
|
1037
|
+
for (let index = 0; index < line.length; index += 1) {
|
|
1038
|
+
const character = line[index];
|
|
1039
|
+
const next = line[index + 1];
|
|
1040
|
+
if (state.blockComment) {
|
|
1041
|
+
if (character === "*" && next === "/") {
|
|
1042
|
+
state.blockComment = false;
|
|
1043
|
+
index += 1;
|
|
1044
|
+
}
|
|
1045
|
+
continue;
|
|
1046
|
+
}
|
|
1047
|
+
if (state.quote) {
|
|
1048
|
+
if (character === "\\") {
|
|
1049
|
+
index += 1;
|
|
1050
|
+
continue;
|
|
1051
|
+
}
|
|
1052
|
+
if (character === state.quote) state.quote = void 0;
|
|
1053
|
+
continue;
|
|
1054
|
+
}
|
|
1055
|
+
if (character === "/" && next === "*") {
|
|
1056
|
+
state.blockComment = true;
|
|
1057
|
+
index += 1;
|
|
1058
|
+
continue;
|
|
1059
|
+
}
|
|
1060
|
+
if (character === "/" && next === "/") break;
|
|
1061
|
+
if (character === "'" || character === "\"" || character === "`") {
|
|
1062
|
+
state.quote = character;
|
|
1063
|
+
continue;
|
|
1064
|
+
}
|
|
1065
|
+
if (character === "{") state.braceDepth += 1;
|
|
1066
|
+
if (character === "}") state.braceDepth = Math.max(0, state.braceDepth - 1);
|
|
1067
|
+
if (character === "[") state.bracketDepth += 1;
|
|
1068
|
+
if (character === "]") state.bracketDepth = Math.max(0, state.bracketDepth - 1);
|
|
1069
|
+
if (character === "(") state.parenDepth += 1;
|
|
1070
|
+
if (character === ")") state.parenDepth = Math.max(0, state.parenDepth - 1);
|
|
1071
|
+
}
|
|
1072
|
+
return Boolean(state.blockComment || state.quote || state.braceDepth || state.bracketDepth || state.parenDepth || /(?:[=,]|=>)\s*$/u.test(line));
|
|
1073
|
+
}
|
|
1074
|
+
function scanDocsMarkdownLiteralPromptTags(value, initialState) {
|
|
1075
|
+
let delta = 0;
|
|
1076
|
+
let found = Boolean(initialState);
|
|
1077
|
+
let pending = initialState;
|
|
1078
|
+
let index = 0;
|
|
1079
|
+
while (index < value.length) {
|
|
1080
|
+
if (!pending) {
|
|
1081
|
+
const tagStart = value.indexOf("<", index);
|
|
1082
|
+
if (tagStart < 0) break;
|
|
1083
|
+
if (!found && value.slice(0, tagStart).trim()) {
|
|
1084
|
+
index = tagStart + 1;
|
|
1085
|
+
continue;
|
|
1086
|
+
}
|
|
1087
|
+
const closing = value[tagStart + 1] === "/";
|
|
1088
|
+
const nameStart = tagStart + (closing ? 2 : 1);
|
|
1089
|
+
if (value.slice(nameStart, nameStart + 6) !== "Prompt") {
|
|
1090
|
+
index = tagStart + 1;
|
|
1091
|
+
continue;
|
|
1092
|
+
}
|
|
1093
|
+
const boundary = value[nameStart + 6];
|
|
1094
|
+
if (boundary && !/[\t\n\r />]/u.test(boundary)) {
|
|
1095
|
+
index = tagStart + 1;
|
|
1096
|
+
continue;
|
|
1097
|
+
}
|
|
1098
|
+
pending = {
|
|
1099
|
+
braceDepth: 0,
|
|
1100
|
+
closing,
|
|
1101
|
+
lastNonWhitespace: "t"
|
|
1102
|
+
};
|
|
1103
|
+
found = true;
|
|
1104
|
+
index = nameStart + 6;
|
|
1105
|
+
}
|
|
1106
|
+
while (pending && index < value.length) {
|
|
1107
|
+
const character = value[index];
|
|
1108
|
+
if (pending.quote) {
|
|
1109
|
+
if (character === "\\") {
|
|
1110
|
+
index += 2;
|
|
1111
|
+
continue;
|
|
1112
|
+
}
|
|
1113
|
+
if (character === pending.quote) pending.quote = void 0;
|
|
1114
|
+
index += 1;
|
|
1115
|
+
continue;
|
|
1116
|
+
}
|
|
1117
|
+
if (character === "'" || character === "\"" || character === "`") {
|
|
1118
|
+
pending.quote = character;
|
|
1119
|
+
index += 1;
|
|
1120
|
+
continue;
|
|
1121
|
+
}
|
|
1122
|
+
if (character === "{") {
|
|
1123
|
+
pending.braceDepth += 1;
|
|
1124
|
+
pending.lastNonWhitespace = character;
|
|
1125
|
+
index += 1;
|
|
1126
|
+
continue;
|
|
1127
|
+
}
|
|
1128
|
+
if (character === "}") {
|
|
1129
|
+
pending.braceDepth = Math.max(0, pending.braceDepth - 1);
|
|
1130
|
+
pending.lastNonWhitespace = character;
|
|
1131
|
+
index += 1;
|
|
1132
|
+
continue;
|
|
1133
|
+
}
|
|
1134
|
+
if (character === ">" && pending.braceDepth === 0) {
|
|
1135
|
+
if (pending.closing) delta -= 1;
|
|
1136
|
+
else if (pending.lastNonWhitespace !== "/") delta += 1;
|
|
1137
|
+
pending = void 0;
|
|
1138
|
+
index += 1;
|
|
1139
|
+
break;
|
|
1140
|
+
}
|
|
1141
|
+
if (!/\s/u.test(character)) pending.lastNonWhitespace = character;
|
|
1142
|
+
index += 1;
|
|
1143
|
+
}
|
|
1144
|
+
}
|
|
1145
|
+
return {
|
|
1146
|
+
delta,
|
|
1147
|
+
found,
|
|
1148
|
+
...pending ? { pending } : {}
|
|
1149
|
+
};
|
|
1150
|
+
}
|
|
1151
|
+
function collectDocsMarkdownFallbackIgnoredLines(lines) {
|
|
1152
|
+
const ignored = /* @__PURE__ */ new Set();
|
|
1153
|
+
let esmState;
|
|
1154
|
+
let htmlComment = false;
|
|
1155
|
+
let mdxComment = false;
|
|
1156
|
+
let literalPromptDepth = 0;
|
|
1157
|
+
let literalPromptTagContainers = [];
|
|
1158
|
+
let literalPromptTagState;
|
|
1159
|
+
let openFence;
|
|
1160
|
+
for (let index = 0; index < lines.length; index += 1) {
|
|
1161
|
+
const line = lines[index];
|
|
1162
|
+
if (literalPromptTagState) {
|
|
1163
|
+
const promptTags = scanDocsMarkdownLiteralPromptTags((literalPromptTagContainers.length > 0 ? continueDocsMarkdownContainerLine(line, literalPromptTagContainers) : { content: line })?.content ?? line, literalPromptTagState);
|
|
1164
|
+
ignored.add(index);
|
|
1165
|
+
literalPromptDepth = Math.max(0, literalPromptDepth + promptTags.delta);
|
|
1166
|
+
literalPromptTagState = promptTags.pending;
|
|
1167
|
+
if (!literalPromptTagState) literalPromptTagContainers = [];
|
|
1168
|
+
continue;
|
|
1169
|
+
}
|
|
1170
|
+
if (!esmState && !htmlComment && !mdxComment && openFence) {
|
|
1171
|
+
const fenceLine = readDocsMarkdownFenceLine(line, openFence);
|
|
1172
|
+
if (fenceLine) {
|
|
1173
|
+
if (isClosingFence(fenceLine.content, openFence)) openFence = void 0;
|
|
1174
|
+
continue;
|
|
1175
|
+
}
|
|
1176
|
+
openFence = void 0;
|
|
1177
|
+
}
|
|
1178
|
+
const containerLine = splitDocsMarkdownContainerLine(line);
|
|
1179
|
+
const logicalLine = containerLine.content;
|
|
1180
|
+
if (!esmState && !htmlComment && !mdxComment) {
|
|
1181
|
+
const openingFence = readOpeningFence(logicalLine);
|
|
1182
|
+
if (openingFence) {
|
|
1183
|
+
openFence = {
|
|
1184
|
+
...openingFence,
|
|
1185
|
+
containers: containerLine.containers
|
|
1186
|
+
};
|
|
1187
|
+
continue;
|
|
1188
|
+
}
|
|
1189
|
+
}
|
|
1190
|
+
if (mdxComment) {
|
|
1191
|
+
ignored.add(index);
|
|
1192
|
+
if (logicalLine.includes("*/}")) mdxComment = false;
|
|
1193
|
+
continue;
|
|
1194
|
+
}
|
|
1195
|
+
if (htmlComment) {
|
|
1196
|
+
ignored.add(index);
|
|
1197
|
+
if (logicalLine.includes("-->")) htmlComment = false;
|
|
1198
|
+
continue;
|
|
1199
|
+
}
|
|
1200
|
+
if (esmState) {
|
|
1201
|
+
ignored.add(index);
|
|
1202
|
+
if (!scanDocsMarkdownFallbackEsmLine(logicalLine, esmState)) esmState = void 0;
|
|
1203
|
+
continue;
|
|
1204
|
+
}
|
|
1205
|
+
const trimmed = logicalLine.trimStart();
|
|
1206
|
+
if (trimmed.startsWith("{/*")) {
|
|
1207
|
+
ignored.add(index);
|
|
1208
|
+
mdxComment = !trimmed.includes("*/}");
|
|
1209
|
+
continue;
|
|
1210
|
+
}
|
|
1211
|
+
if (trimmed.startsWith("<!--")) {
|
|
1212
|
+
ignored.add(index);
|
|
1213
|
+
htmlComment = !trimmed.includes("-->");
|
|
1214
|
+
continue;
|
|
1215
|
+
}
|
|
1216
|
+
if (/^(?:import|export)(?:[\t ]|[{*]|$)/u.test(trimmed)) {
|
|
1217
|
+
ignored.add(index);
|
|
1218
|
+
const state = {
|
|
1219
|
+
blockComment: false,
|
|
1220
|
+
braceDepth: 0,
|
|
1221
|
+
bracketDepth: 0,
|
|
1222
|
+
parenDepth: 0
|
|
1223
|
+
};
|
|
1224
|
+
if (scanDocsMarkdownFallbackEsmLine(logicalLine, state)) esmState = state;
|
|
1225
|
+
continue;
|
|
1226
|
+
}
|
|
1227
|
+
const promptTags = scanDocsMarkdownLiteralPromptTags(logicalLine);
|
|
1228
|
+
if (literalPromptDepth > 0 || promptTags.found) {
|
|
1229
|
+
ignored.add(index);
|
|
1230
|
+
literalPromptDepth = Math.max(0, literalPromptDepth + promptTags.delta);
|
|
1231
|
+
literalPromptTagState = promptTags.pending;
|
|
1232
|
+
if (literalPromptTagState) literalPromptTagContainers = containerLine.containers.map((container) => ({ ...container }));
|
|
1233
|
+
}
|
|
1234
|
+
}
|
|
1235
|
+
return ignored;
|
|
1236
|
+
}
|
|
1237
|
+
function readAtxHeading(line) {
|
|
1238
|
+
const match = line.match(/^ {0,3}(#{1,6})(?:[\t ]+|$)(.*)$/u);
|
|
1239
|
+
if (!match) return void 0;
|
|
1240
|
+
return {
|
|
1241
|
+
source: match[2].replace(/[\t ]+#+[\t ]*$/u, "").trim(),
|
|
1242
|
+
level: match[1].length
|
|
1243
|
+
};
|
|
1244
|
+
}
|
|
1245
|
+
function readSetextLevel(line) {
|
|
1246
|
+
const match = line.match(/^ {0,3}(=+|-+)[\t ]*$/u);
|
|
1247
|
+
if (!match) return void 0;
|
|
1248
|
+
return match[1][0] === "=" ? 1 : 2;
|
|
1249
|
+
}
|
|
1250
|
+
function normalizeDocsSectionSelector(value, extractFragment = true) {
|
|
1251
|
+
let selector = value.trim();
|
|
1252
|
+
const hashIndex = selector.indexOf("#");
|
|
1253
|
+
if (extractFragment && hashIndex >= 0) selector = selector.slice(hashIndex + 1);
|
|
1254
|
+
try {
|
|
1255
|
+
selector = decodeURIComponent(selector);
|
|
1256
|
+
} catch {}
|
|
1257
|
+
return cleanDocsMarkdownHeadingLabel(selector).toLowerCase();
|
|
1258
|
+
}
|
|
1259
|
+
function decodeDocsSectionSelector(value) {
|
|
1260
|
+
let selector = value.trim();
|
|
1261
|
+
const hashIndex = selector.indexOf("#");
|
|
1262
|
+
if (hashIndex >= 0) selector = selector.slice(hashIndex + 1);
|
|
1263
|
+
try {
|
|
1264
|
+
selector = decodeURIComponent(selector);
|
|
1265
|
+
} catch {}
|
|
1266
|
+
return selector;
|
|
1267
|
+
}
|
|
1268
|
+
function isTopLevelDocsMarkdownMarker(line, marker) {
|
|
1269
|
+
if (line.trim() !== marker) return false;
|
|
1270
|
+
const markerIndex = line.indexOf(marker);
|
|
1271
|
+
return markerIndex <= 3 && /^ *$/u.test(line.slice(0, markerIndex));
|
|
1272
|
+
}
|
|
1273
|
+
function collectGeneratedAgentContractRanges(markdown) {
|
|
1274
|
+
const lines = markdown.split(/\r?\n/u);
|
|
1275
|
+
const ranges = [];
|
|
1276
|
+
let openFence;
|
|
1277
|
+
let contractStart;
|
|
1278
|
+
for (let index = 0; index < lines.length; index += 1) {
|
|
1279
|
+
const line = lines[index];
|
|
1280
|
+
if (openFence) {
|
|
1281
|
+
const fenceLine = readDocsMarkdownFenceLine(line, openFence);
|
|
1282
|
+
if (fenceLine) {
|
|
1283
|
+
if (isClosingFence(fenceLine.content, openFence)) openFence = void 0;
|
|
1284
|
+
continue;
|
|
1285
|
+
}
|
|
1286
|
+
openFence = void 0;
|
|
1287
|
+
}
|
|
1288
|
+
const containerLine = splitDocsMarkdownContainerLine(line);
|
|
1289
|
+
const openingFence = readOpeningFence(containerLine.content);
|
|
1290
|
+
if (openingFence) {
|
|
1291
|
+
openFence = {
|
|
1292
|
+
...openingFence,
|
|
1293
|
+
containers: containerLine.containers
|
|
1294
|
+
};
|
|
1295
|
+
continue;
|
|
1296
|
+
}
|
|
1297
|
+
if (containerLine.containers.length > 0) continue;
|
|
1298
|
+
if (contractStart !== void 0) {
|
|
1299
|
+
if (isTopLevelDocsMarkdownMarker(line, PAGE_AGENT_CONTRACT_END_MARKER)) {
|
|
1300
|
+
ranges.push({
|
|
1301
|
+
start: contractStart,
|
|
1302
|
+
end: index
|
|
1303
|
+
});
|
|
1304
|
+
contractStart = void 0;
|
|
1305
|
+
}
|
|
1306
|
+
} else if (isTopLevelDocsMarkdownMarker(line, PAGE_AGENT_CONTRACT_START_MARKER)) contractStart = index;
|
|
1307
|
+
}
|
|
1308
|
+
return ranges;
|
|
1309
|
+
}
|
|
1310
|
+
/** Locate complete generated contract blocks while keeping fenced marker examples inert. */
|
|
1311
|
+
function findDocsGeneratedAgentContractRanges(markdown) {
|
|
1312
|
+
return collectGeneratedAgentContractRanges(markdown).map(({ start, end }) => ({
|
|
1313
|
+
startLine: start + 1,
|
|
1314
|
+
endLine: end + 1
|
|
1315
|
+
}));
|
|
1316
|
+
}
|
|
1317
|
+
/** Remove only generated boundary markers that overlap a full document or derived fragment. */
|
|
1318
|
+
function stripDocsGeneratedAgentContractMarkers(markdown, location) {
|
|
1319
|
+
const ranges = findDocsGeneratedAgentContractRanges(location?.sourceMarkdown ?? markdown);
|
|
1320
|
+
if (ranges.length === 0) return markdown;
|
|
1321
|
+
const markerLines = /* @__PURE__ */ new Set();
|
|
1322
|
+
for (const range of ranges) {
|
|
1323
|
+
markerLines.add(range.startLine);
|
|
1324
|
+
markerLines.add(range.endLine);
|
|
1325
|
+
}
|
|
1326
|
+
const newline = markdown.includes("\r\n") ? "\r\n" : "\n";
|
|
1327
|
+
const startLine = location?.startLine ?? 1;
|
|
1328
|
+
const output = markdown.split(/\r?\n/u).filter((_line, index) => !markerLines.has(startLine + index));
|
|
1329
|
+
while (output[0] === "") output.shift();
|
|
1330
|
+
return output.join(newline);
|
|
1331
|
+
}
|
|
1332
|
+
function isDocsMarkdownLineInRanges(line, ranges) {
|
|
1333
|
+
return ranges.some((range) => line >= range.start && line <= range.end);
|
|
1334
|
+
}
|
|
1335
|
+
/**
|
|
1336
|
+
* Parse heading sections once for search, Ask AI hydration, and MCP tools.
|
|
1337
|
+
* Supports CommonMark ATX indentation, Setext headings, fenced-code exclusion,
|
|
1338
|
+
* visible inline labels, and stable duplicate anchors.
|
|
1339
|
+
*/
|
|
1340
|
+
function parseDocsMarkdownSectionsFallback(markdown, generatedContractRanges) {
|
|
1341
|
+
const lines = markdown.split(/\r?\n/u);
|
|
1342
|
+
const headings = [];
|
|
1343
|
+
const ignoredLines = collectDocsMarkdownFallbackIgnoredLines(lines);
|
|
1344
|
+
const definedReferences = collectDocsMarkdownReferenceDefinitions(lines.map((line, index) => ignoredLines.has(index) ? "" : line));
|
|
1345
|
+
let openFence;
|
|
1346
|
+
let setextCandidate;
|
|
1347
|
+
const pushHeading = (value, level, start) => {
|
|
1348
|
+
headings.push({
|
|
1349
|
+
source: value,
|
|
1350
|
+
level,
|
|
1351
|
+
start
|
|
1352
|
+
});
|
|
1353
|
+
};
|
|
1354
|
+
for (let index = 0; index < lines.length; index += 1) {
|
|
1355
|
+
const line = lines[index];
|
|
1356
|
+
if (openFence) {
|
|
1357
|
+
const fenceLine = readDocsMarkdownFenceLine(line, openFence);
|
|
1358
|
+
if (fenceLine) {
|
|
1359
|
+
if (isClosingFence(fenceLine.content, openFence)) openFence = void 0;
|
|
1360
|
+
setextCandidate = void 0;
|
|
1361
|
+
continue;
|
|
1362
|
+
}
|
|
1363
|
+
openFence = void 0;
|
|
1364
|
+
}
|
|
1365
|
+
if (ignoredLines.has(index)) {
|
|
1366
|
+
setextCandidate = void 0;
|
|
1367
|
+
continue;
|
|
1368
|
+
}
|
|
1369
|
+
const containerLine = splitDocsMarkdownContainerLine(line);
|
|
1370
|
+
const logicalLine = containerLine.content;
|
|
1371
|
+
const openingFence = readOpeningFence(logicalLine);
|
|
1372
|
+
if (openingFence) {
|
|
1373
|
+
openFence = {
|
|
1374
|
+
...openingFence,
|
|
1375
|
+
containers: containerLine.containers
|
|
1376
|
+
};
|
|
1377
|
+
setextCandidate = void 0;
|
|
1378
|
+
continue;
|
|
1379
|
+
}
|
|
1380
|
+
const setextLine = setextCandidate ? continueDocsMarkdownContainerLine(line, setextCandidate.containers) : void 0;
|
|
1381
|
+
const setextLevel = setextLine ? readSetextLevel(setextLine.content) : void 0;
|
|
1382
|
+
if (setextLevel && setextCandidate?.lastIndex === index - 1) {
|
|
1383
|
+
const source = setextCandidate.values.join("\n").trim();
|
|
1384
|
+
if (!source.startsWith("<")) pushHeading(source, setextLevel, setextCandidate.index);
|
|
1385
|
+
setextCandidate = void 0;
|
|
1386
|
+
continue;
|
|
1387
|
+
}
|
|
1388
|
+
const atxHeading = readAtxHeading(logicalLine);
|
|
1389
|
+
if (atxHeading) {
|
|
1390
|
+
pushHeading(atxHeading.source, atxHeading.level, index);
|
|
1391
|
+
setextCandidate = void 0;
|
|
1392
|
+
continue;
|
|
1393
|
+
}
|
|
1394
|
+
if (!logicalLine.trim() || /^ {4}|^\t/u.test(logicalLine)) {
|
|
1395
|
+
setextCandidate = void 0;
|
|
1396
|
+
continue;
|
|
1397
|
+
}
|
|
1398
|
+
if (readDocsMarkdownReferenceDefinition(logicalLine)) {
|
|
1399
|
+
setextCandidate = void 0;
|
|
1400
|
+
continue;
|
|
1401
|
+
}
|
|
1402
|
+
const candidate = setextCandidate;
|
|
1403
|
+
const paragraphContinuation = candidate?.lastIndex === index - 1 ? continueDocsMarkdownContainerLine(line, candidate.containers) : void 0;
|
|
1404
|
+
if (candidate && paragraphContinuation) {
|
|
1405
|
+
candidate.values.push(paragraphContinuation.content);
|
|
1406
|
+
candidate.lastIndex = index;
|
|
1407
|
+
continue;
|
|
1408
|
+
}
|
|
1409
|
+
setextCandidate = {
|
|
1410
|
+
index,
|
|
1411
|
+
lastIndex: index,
|
|
1412
|
+
values: [logicalLine],
|
|
1413
|
+
containers: containerLine.containers
|
|
1414
|
+
};
|
|
1415
|
+
}
|
|
1416
|
+
const resolveHeadingAnchor = createDocsMarkdownHeadingAnchorResolverWithReferences(definedReferences);
|
|
1417
|
+
const orderedHeadings = [...headings.filter((heading) => !isDocsMarkdownLineInRanges(heading.start, generatedContractRanges)), ...headings.filter((heading) => isDocsMarkdownLineInRanges(heading.start, generatedContractRanges))];
|
|
1418
|
+
for (const heading of orderedHeadings) heading.resolved = resolveHeadingAnchor(heading.source);
|
|
1419
|
+
return headings.map((heading, index) => {
|
|
1420
|
+
const end = headings.slice(index + 1).find((candidate) => candidate.level <= heading.level)?.start ?? lines.length;
|
|
1421
|
+
const resolved = heading.resolved;
|
|
1422
|
+
if (!resolved) throw new Error("Failed to resolve a Markdown heading anchor.");
|
|
1423
|
+
return {
|
|
1424
|
+
title: resolved.title,
|
|
1425
|
+
anchor: resolved.anchor,
|
|
1426
|
+
explicit: resolved.explicit,
|
|
1427
|
+
level: heading.level,
|
|
1428
|
+
content: lines.slice(heading.start, end).join("\n").trim(),
|
|
1429
|
+
startLine: heading.start + 1,
|
|
1430
|
+
endLine: end
|
|
1431
|
+
};
|
|
1432
|
+
});
|
|
1433
|
+
}
|
|
1434
|
+
function stripDocsMarkdownSectionFrontmatter(markdown) {
|
|
1435
|
+
return markdown.match(/^\uFEFF?---\r?\n[\s\S]*?\r?\n---(?:\r?\n|$)([\s\S]*)$/u)?.[1] ?? markdown;
|
|
1436
|
+
}
|
|
1437
|
+
function parseDocsMarkdownAstSections(markdown, generatedContractRanges) {
|
|
1438
|
+
let root;
|
|
1439
|
+
try {
|
|
1440
|
+
root = docsMarkdownMdxProcessor.parse(markdown);
|
|
1441
|
+
} catch {
|
|
1442
|
+
return;
|
|
1443
|
+
}
|
|
1444
|
+
const headings = assignDocsMarkdownAstHeadingAnchors(root, {}, (start) => isDocsMarkdownLineInRanges(start, generatedContractRanges)).flatMap((heading) => {
|
|
1445
|
+
const level = typeof heading.node.depth === "number" ? heading.node.depth : NaN;
|
|
1446
|
+
const anchor = heading.node.data?.hProperties?.id;
|
|
1447
|
+
if (!Number.isInteger(level) || level < 1 || level > 6 || !Number.isInteger(heading.start) || heading.start < 0 || typeof anchor !== "string") return [];
|
|
1448
|
+
return [{
|
|
1449
|
+
title: heading.title,
|
|
1450
|
+
anchor,
|
|
1451
|
+
explicit: heading.explicit,
|
|
1452
|
+
level,
|
|
1453
|
+
start: heading.start
|
|
1454
|
+
}];
|
|
1455
|
+
});
|
|
1456
|
+
const lines = markdown.split(/\r?\n/u);
|
|
1457
|
+
return headings.map((heading, index) => {
|
|
1458
|
+
const end = headings.slice(index + 1).find((candidate) => candidate.level <= heading.level)?.start ?? lines.length;
|
|
1459
|
+
return {
|
|
1460
|
+
title: heading.title,
|
|
1461
|
+
anchor: heading.anchor,
|
|
1462
|
+
explicit: heading.explicit,
|
|
1463
|
+
level: heading.level,
|
|
1464
|
+
content: lines.slice(heading.start, end).join("\n").trim(),
|
|
1465
|
+
startLine: heading.start + 1,
|
|
1466
|
+
endLine: end
|
|
1467
|
+
};
|
|
1468
|
+
});
|
|
1469
|
+
}
|
|
1470
|
+
function parseDocsMarkdownSections(markdown) {
|
|
1471
|
+
const body = stripDocsMarkdownSectionFrontmatter(markdown);
|
|
1472
|
+
const generatedContractRanges = collectGeneratedAgentContractRanges(body);
|
|
1473
|
+
return parseDocsMarkdownAstSections(body, generatedContractRanges) ?? parseDocsMarkdownSectionsFallback(body, generatedContractRanges);
|
|
1474
|
+
}
|
|
1475
|
+
function findDocsMarkdownSection(markdown, requestedSection) {
|
|
1476
|
+
const sections = parseDocsMarkdownSections(markdown);
|
|
1477
|
+
const rawSelector = requestedSection.trim();
|
|
1478
|
+
const rawAnchor = sections.find((section) => section.anchor === rawSelector);
|
|
1479
|
+
if (rawAnchor) return rawAnchor;
|
|
1480
|
+
const decodedSelector = decodeDocsSectionSelector(requestedSection);
|
|
1481
|
+
const exactAnchor = sections.find((section) => section.anchor === decodedSelector);
|
|
1482
|
+
if (exactAnchor) return exactAnchor;
|
|
1483
|
+
const caseInsensitiveAnchors = sections.filter((section) => section.anchor.toLowerCase() === decodedSelector.toLowerCase());
|
|
1484
|
+
if (caseInsensitiveAnchors.length === 1) return caseInsensitiveAnchors[0];
|
|
1485
|
+
if (caseInsensitiveAnchors.length > 1) return void 0;
|
|
1486
|
+
const selector = normalizeDocsSectionSelector(requestedSection);
|
|
1487
|
+
if (!selector) return void 0;
|
|
1488
|
+
return sections.find((section) => normalizeDocsSectionSelector(section.title, false) === selector);
|
|
1489
|
+
}
|
|
1490
|
+
|
|
1491
|
+
//#endregion
|
|
1492
|
+
export { upsertPageAgentContractMarkdown as S, hasStructuredPageAgentContract as _, findDocsGeneratedAgentContractRanges as a, renderPageAgentFrontmatterYamlLines as b, slugifyDocsMarkdownHeading as c, PAGE_AGENT_CONTRACT_END_MARKER as d, PAGE_AGENT_CONTRACT_FIELDS as f, getPageAgentFrontmatterIssues as g, PAGE_AGENT_STRUCTURED_CONTRACT_FIELDS as h, createDocsRenderedHeadingAnchorResolver as i, stripDocsGeneratedAgentContractMarkers as l, PAGE_AGENT_CONTRACT_START_MARKER as m, cleanDocsRenderedHeadingLabel as n, findDocsMarkdownSection as o, PAGE_AGENT_CONTRACT_FIELD_SCHEMA as p, createDocsMarkdownHeadingAnchorResolver as r, parseDocsMarkdownSections as s, applyDocsMarkdownHeadingAnchors as t, withDocsMarkdownRenderableHeadings as u, normalizePageAgentFrontmatter as v, stripGeneratedPageAgentContractMarkdown as x, renderPageAgentContractMarkdown as y };
|