@takazudo/zudo-doc 5.8.0 → 5.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (48) hide show
  1. package/CHANGELOG.md +18 -0
  2. package/dist/config.d.ts +18 -0
  3. package/dist/config.js +1 -0
  4. package/dist/i18n-defaults/index.js +3 -0
  5. package/dist/plugins/codex-resources.d.ts +4 -0
  6. package/dist/plugins/codex-resources.js +31 -0
  7. package/dist/plugins/internal/claude-resources/generate.js +20 -291
  8. package/dist/plugins/internal/codex-resources/agents-md.d.ts +6 -0
  9. package/dist/plugins/internal/codex-resources/agents-md.js +70 -0
  10. package/dist/plugins/internal/codex-resources/agents.d.ts +7 -0
  11. package/dist/plugins/internal/codex-resources/agents.js +104 -0
  12. package/dist/plugins/internal/codex-resources/config.d.ts +6 -0
  13. package/dist/plugins/internal/codex-resources/config.js +111 -0
  14. package/dist/plugins/internal/codex-resources/generate.d.ts +20 -0
  15. package/dist/plugins/internal/codex-resources/generate.js +34 -0
  16. package/dist/plugins/internal/codex-resources/hooks.d.ts +6 -0
  17. package/dist/plugins/internal/codex-resources/hooks.js +174 -0
  18. package/dist/plugins/internal/codex-resources/index.d.ts +24 -0
  19. package/dist/plugins/internal/codex-resources/index.js +21 -0
  20. package/dist/plugins/internal/codex-resources/overview.d.ts +10 -0
  21. package/dist/plugins/internal/codex-resources/overview.js +33 -0
  22. package/dist/plugins/internal/codex-resources/rules.d.ts +6 -0
  23. package/dist/plugins/internal/codex-resources/rules.js +255 -0
  24. package/dist/plugins/internal/codex-resources/skills.d.ts +2 -0
  25. package/dist/plugins/internal/codex-resources/skills.js +84 -0
  26. package/dist/plugins/internal/codex-resources/utils.d.ts +14 -0
  27. package/dist/plugins/internal/codex-resources/utils.js +80 -0
  28. package/dist/plugins/internal/resource-docs-shared/fs.d.ts +3 -0
  29. package/dist/plugins/internal/resource-docs-shared/fs.js +19 -0
  30. package/dist/plugins/internal/resource-docs-shared/index.d.ts +7 -0
  31. package/dist/plugins/internal/resource-docs-shared/index.js +44 -0
  32. package/dist/plugins/internal/resource-docs-shared/links.d.ts +22 -0
  33. package/dist/plugins/internal/resource-docs-shared/links.js +44 -0
  34. package/dist/plugins/internal/resource-docs-shared/markdown-structure.d.ts +9 -0
  35. package/dist/plugins/internal/resource-docs-shared/markdown-structure.js +22 -0
  36. package/dist/plugins/internal/resource-docs-shared/mdx.d.ts +23 -0
  37. package/dist/plugins/internal/resource-docs-shared/mdx.js +63 -0
  38. package/dist/plugins/internal/resource-docs-shared/skills.d.ts +37 -0
  39. package/dist/plugins/internal/resource-docs-shared/skills.js +318 -0
  40. package/dist/plugins/internal/resource-docs-shared/walk.d.ts +20 -0
  41. package/dist/plugins/internal/resource-docs-shared/walk.js +41 -0
  42. package/dist/preset.d.ts +8 -0
  43. package/dist/preset.js +11 -0
  44. package/dist/safelist.css +1 -1
  45. package/dist/settings.d.ts +5 -0
  46. package/package.json +7 -2
  47. /package/dist/plugins/internal/{claude-resources → resource-docs-shared}/escape-for-mdx.d.ts +0 -0
  48. /package/dist/plugins/internal/{claude-resources → resource-docs-shared}/escape-for-mdx.js +0 -0
@@ -0,0 +1,255 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ import {
4
+ formatFrontmatterString,
5
+ cleanDir,
6
+ ensureDir,
7
+ renderCodeFence,
8
+ writeCategoryIndex
9
+ } from "../resource-docs-shared/index.js";
10
+ import {
11
+ assertUniqueSlug,
12
+ renderTableRow,
13
+ warn,
14
+ writeGeneratedPage
15
+ } from "./utils.js";
16
+ function freshState() {
17
+ return { quote: null, triple: false, escaped: false, comment: false };
18
+ }
19
+ function advanceState(text, index, state) {
20
+ const char = text[index] ?? "";
21
+ if (state.comment) {
22
+ if (char === "\n") state.comment = false;
23
+ return 1;
24
+ }
25
+ if (state.quote !== null) {
26
+ if (state.escaped) {
27
+ state.escaped = false;
28
+ return 1;
29
+ }
30
+ if (char === "\\") {
31
+ state.escaped = true;
32
+ return 1;
33
+ }
34
+ if (state.triple && text.slice(index, index + 3) === state.quote.repeat(3)) {
35
+ state.quote = null;
36
+ state.triple = false;
37
+ return 3;
38
+ }
39
+ if (!state.triple && char === state.quote) state.quote = null;
40
+ return 1;
41
+ }
42
+ if (char === "#") {
43
+ state.comment = true;
44
+ return 1;
45
+ }
46
+ if (char === "'" || char === '"') {
47
+ state.quote = char;
48
+ state.triple = text.slice(index, index + 3) === char.repeat(3);
49
+ return state.triple ? 3 : 1;
50
+ }
51
+ return 1;
52
+ }
53
+ function findPrefixRuleBodies(source) {
54
+ const bodies = [];
55
+ const state = freshState();
56
+ let index = 0;
57
+ while (index < source.length) {
58
+ if (state.quote === null && !state.comment && source.startsWith("prefix_rule", index)) {
59
+ const before = source[index - 1];
60
+ const after = source[index + "prefix_rule".length];
61
+ if ((!before || !/[\w]/.test(before)) && (!after || !/[\w]/.test(after))) {
62
+ let open = index + "prefix_rule".length;
63
+ while (/\s/.test(source[open] ?? "")) open++;
64
+ if (source[open] === "(") {
65
+ const callState = freshState();
66
+ let depth = 1;
67
+ let cursor = open + 1;
68
+ const bodyStart = cursor;
69
+ while (cursor < source.length) {
70
+ if (callState.quote === null && !callState.comment) {
71
+ if (source[cursor] === "(") depth++;
72
+ if (source[cursor] === ")") {
73
+ depth--;
74
+ if (depth === 0) break;
75
+ }
76
+ }
77
+ cursor += advanceState(source, cursor, callState);
78
+ }
79
+ bodies.push(source.slice(bodyStart, cursor));
80
+ index = cursor < source.length ? cursor + 1 : source.length;
81
+ continue;
82
+ }
83
+ }
84
+ }
85
+ index += advanceState(source, index, state);
86
+ }
87
+ return bodies;
88
+ }
89
+ function splitTopLevel(text, delimiter) {
90
+ const parts = [];
91
+ const state = freshState();
92
+ let square = 0;
93
+ let round = 0;
94
+ let curly = 0;
95
+ let start = 0;
96
+ let index = 0;
97
+ while (index < text.length) {
98
+ if (state.quote === null && !state.comment) {
99
+ const char = text[index];
100
+ if (char === "[") square++;
101
+ else if (char === "]") square--;
102
+ else if (char === "(") round++;
103
+ else if (char === ")") round--;
104
+ else if (char === "{") curly++;
105
+ else if (char === "}") curly--;
106
+ else if (char === delimiter && square === 0 && round === 0 && curly === 0) {
107
+ parts.push(text.slice(start, index));
108
+ start = index + 1;
109
+ }
110
+ }
111
+ index += advanceState(text, index, state);
112
+ }
113
+ parts.push(text.slice(start));
114
+ return parts;
115
+ }
116
+ function parseStringLiteral(value) {
117
+ const text = value.trim();
118
+ const quote = text[0];
119
+ if (quote !== '"' && quote !== "'" || text.at(-1) !== quote) return void 0;
120
+ if (text.startsWith(quote.repeat(3)) && text.endsWith(quote.repeat(3))) {
121
+ return text.slice(3, -3);
122
+ }
123
+ const inner = text.slice(1, -1);
124
+ let result = "";
125
+ for (let i = 0; i < inner.length; i++) {
126
+ const char = inner[i];
127
+ if (char !== "\\") {
128
+ result += char;
129
+ continue;
130
+ }
131
+ const next = inner[++i];
132
+ if (next === void 0) return void 0;
133
+ const escapes = {
134
+ n: "\n",
135
+ r: "\r",
136
+ t: " ",
137
+ "\\": "\\",
138
+ "'": "'",
139
+ '"': '"'
140
+ };
141
+ result += escapes[next] ?? next;
142
+ }
143
+ return result;
144
+ }
145
+ function parsePatternValue(text) {
146
+ const trimmed = text.trim();
147
+ const stringValue = parseStringLiteral(trimmed);
148
+ if (stringValue !== void 0) return stringValue;
149
+ if (!trimmed.startsWith("[") || !trimmed.endsWith("]")) return void 0;
150
+ const inner = trimmed.slice(1, -1);
151
+ if (inner.trim() === "") return [];
152
+ const values = [];
153
+ for (const part of splitTopLevel(inner, ",")) {
154
+ if (part.trim() === "") continue;
155
+ const parsed = parsePatternValue(part);
156
+ if (parsed === void 0) return void 0;
157
+ values.push(parsed);
158
+ }
159
+ return values;
160
+ }
161
+ function formatPattern(value) {
162
+ if (!Array.isArray(value)) return value;
163
+ const parts = [];
164
+ for (const element of value) {
165
+ if (Array.isArray(element)) {
166
+ if (element.some(Array.isArray)) return void 0;
167
+ parts.push(element.join("|"));
168
+ } else {
169
+ parts.push(element);
170
+ }
171
+ }
172
+ return parts.join(" ");
173
+ }
174
+ function parseRule(body) {
175
+ const args = /* @__PURE__ */ new Map();
176
+ for (const part of splitTopLevel(body, ",")) {
177
+ const assignment = splitTopLevel(part, "=");
178
+ if (assignment.length !== 2) continue;
179
+ const key = assignment[0]?.trim();
180
+ const value = assignment[1];
181
+ if (key && value !== void 0) args.set(key, value.trim());
182
+ }
183
+ const rawPattern = args.get("pattern");
184
+ const parsedPattern = rawPattern === void 0 ? void 0 : parsePatternValue(rawPattern);
185
+ const decision = args.has("decision") ? parseStringLiteral(args.get("decision") ?? "") : "allow";
186
+ return {
187
+ pattern: parsedPattern === void 0 ? void 0 : formatPattern(parsedPattern),
188
+ decision,
189
+ justification: args.has("justification") ? parseStringLiteral(args.get("justification") ?? "") : void 0
190
+ };
191
+ }
192
+ function generateRulesCategory(config) {
193
+ const rulesDir = path.join(config.codexDir, "rules");
194
+ const outputDir = path.join(config.docsDir, "codex-rules");
195
+ cleanDir(outputDir);
196
+ if (!fs.existsSync(rulesDir)) return [];
197
+ let files;
198
+ try {
199
+ files = fs.readdirSync(rulesDir, { withFileTypes: true }).filter((entry) => entry.isFile() && entry.name.endsWith(".rules")).map((entry) => entry.name).sort();
200
+ } catch (error) {
201
+ warn(rulesDir, `unable to read rules directory: ${String(error)}`);
202
+ return [];
203
+ }
204
+ if (files.length === 0) return [];
205
+ ensureDir(outputDir);
206
+ const items = [];
207
+ const emitted = /* @__PURE__ */ new Map();
208
+ for (const filename of files) {
209
+ const filePath = path.join(rulesDir, filename);
210
+ const slug = filename.replace(/\.rules$/, "");
211
+ assertUniqueSlug("rule", slug, filePath, emitted);
212
+ let source;
213
+ try {
214
+ source = fs.readFileSync(filePath, "utf8");
215
+ } catch (error) {
216
+ warn(filePath, `unable to read rules file, skipping it: ${String(error)}`);
217
+ continue;
218
+ }
219
+ const rows = findPrefixRuleBodies(source).map(parseRule);
220
+ const body = [
221
+ "## Rules",
222
+ "",
223
+ "| Pattern | Decision | Justification |",
224
+ "| --- | --- | --- |",
225
+ ...rows.length > 0 ? rows.map(
226
+ (row) => renderTableRow([row.pattern, row.decision, row.justification])
227
+ ) : [renderTableRow(Array.from({ length: 3 }))],
228
+ "",
229
+ "## Source",
230
+ "",
231
+ renderCodeFence(source, "python")
232
+ ].join("\n");
233
+ writeGeneratedPage({
234
+ outputPath: path.join(outputDir, `${slug}.mdx`),
235
+ title: filename,
236
+ description: `Command approval rules from ${filename}`,
237
+ sidebarLabel: filename,
238
+ body
239
+ });
240
+ items.push({ filename, slug });
241
+ }
242
+ if (items.length > 0) {
243
+ writeCategoryIndex(
244
+ outputDir,
245
+ "Rules",
246
+ 909,
247
+ "Command approval rules",
248
+ formatFrontmatterString
249
+ );
250
+ }
251
+ return items;
252
+ }
253
+ export {
254
+ generateRulesCategory
255
+ };
@@ -0,0 +1,2 @@
1
+ import type { CodexResourcesConfig } from "./generate.js";
2
+ export declare function generateSkillsCategoryDocs(config: CodexResourcesConfig): import("../resource-docs-shared/skills.js").SkillItem[];
@@ -0,0 +1,84 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ import matter from "gray-matter";
4
+ import {
5
+ escapeForMdx,
6
+ formatFrontmatterString,
7
+ generateSkillsCategory
8
+ } from "../resource-docs-shared/index.js";
9
+ import { isRecord, warn } from "./utils.js";
10
+ function optionalMetadataString(record, field, filePath) {
11
+ const value = record[field];
12
+ if (value === void 0) return void 0;
13
+ if (typeof value === "string") return value;
14
+ warn(filePath, `${field} must be a string; omitting it`);
15
+ return void 0;
16
+ }
17
+ function renderOpenAiMetadata(skillAbsDir) {
18
+ const filePath = path.join(skillAbsDir, "agents", "openai.yaml");
19
+ if (!fs.existsSync(filePath)) return "";
20
+ let data;
21
+ try {
22
+ const yaml = fs.readFileSync(filePath, "utf8");
23
+ data = matter(`---
24
+ ${yaml}
25
+ ---
26
+ `).data;
27
+ } catch (error) {
28
+ warn(filePath, `unable to parse YAML, omitting metadata: ${String(error)}`);
29
+ return "";
30
+ }
31
+ if (!isRecord(data)) {
32
+ warn(filePath, "YAML root must be an object; omitting metadata");
33
+ return "";
34
+ }
35
+ let interfaceData;
36
+ if (data.interface !== void 0) {
37
+ if (isRecord(data.interface)) interfaceData = data.interface;
38
+ else warn(filePath, "interface must be an object; omitting interface metadata");
39
+ }
40
+ let policy;
41
+ if (data.policy !== void 0) {
42
+ if (isRecord(data.policy)) policy = data.policy;
43
+ else warn(filePath, "policy must be an object; omitting policy metadata");
44
+ }
45
+ const displayName = interfaceData ? optionalMetadataString(interfaceData, "display_name", filePath) : void 0;
46
+ const shortDescription = interfaceData ? optionalMetadataString(interfaceData, "short_description", filePath) : void 0;
47
+ let explicitOnly = false;
48
+ if (policy?.allow_implicit_invocation !== void 0) {
49
+ if (typeof policy.allow_implicit_invocation === "boolean") {
50
+ explicitOnly = policy.allow_implicit_invocation === false;
51
+ } else {
52
+ warn(
53
+ filePath,
54
+ "policy.allow_implicit_invocation must be a boolean; omitting invocation metadata"
55
+ );
56
+ }
57
+ }
58
+ return [
59
+ displayName ? `**Display name:** ${escapeForMdx(displayName)}` : "",
60
+ shortDescription ? `**Short description:** ${escapeForMdx(shortDescription)}` : "",
61
+ explicitOnly ? `**Invocation:** explicit only (\`$${path.basename(skillAbsDir)}\`)` : ""
62
+ ].filter(Boolean).join("\n");
63
+ }
64
+ function generateSkillsCategoryDocs(config) {
65
+ const projectRoot = config.projectRoot ?? config.codexDir;
66
+ const scanRoot = config.scanRoot ?? projectRoot;
67
+ return generateSkillsCategory({
68
+ skillsDirs: [
69
+ path.join(config.codexDir, "skills"),
70
+ path.join(projectRoot, ".agents", "skills"),
71
+ path.join(scanRoot, ".agents", "skills")
72
+ ],
73
+ outputDir: path.join(config.docsDir, "codex-skills"),
74
+ label: "Skills",
75
+ position: 910,
76
+ description: "Skill packages",
77
+ sourceLabel: ".codex/skills",
78
+ renderExtraHeader: renderOpenAiMetadata,
79
+ renderFrontmatterString: formatFrontmatterString
80
+ });
81
+ }
82
+ export {
83
+ generateSkillsCategoryDocs
84
+ };
@@ -0,0 +1,14 @@
1
+ export declare function warn(filePath: string, message: string): void;
2
+ export declare function isRecord(value: unknown): value is Record<string, unknown>;
3
+ export declare function filenameSlug(filename: string): string;
4
+ export declare function renderTableRow(values: unknown[]): string;
5
+ export declare function assertUniqueSlug(resource: string, slug: string, filePath: string, emitted: Map<string, string>): void;
6
+ export declare function writeGeneratedPage({ outputPath, title, description, sidebarLabel, sidebarPosition, body, }: {
7
+ outputPath: string;
8
+ title: string;
9
+ description: string;
10
+ sidebarLabel?: string;
11
+ sidebarPosition?: number;
12
+ body: string;
13
+ }): void;
14
+ export declare function languageForFilename(filename: string): string;
@@ -0,0 +1,80 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ import {
4
+ assertNotIndexReserved,
5
+ escapeMarkdownTableCell,
6
+ formatFrontmatterString
7
+ } from "../resource-docs-shared/index.js";
8
+ function warn(filePath, message) {
9
+ console.warn(`${filePath}: ${message}`);
10
+ }
11
+ function isRecord(value) {
12
+ return typeof value === "object" && value !== null && !Array.isArray(value);
13
+ }
14
+ function filenameSlug(filename) {
15
+ return filename.replace(/\./g, "-");
16
+ }
17
+ function renderTableRow(values) {
18
+ return `| ${values.map(escapeMarkdownTableCell).join(" | ")} |`;
19
+ }
20
+ function assertUniqueSlug(resource, slug, filePath, emitted) {
21
+ assertNotIndexReserved(
22
+ slug,
23
+ `codex-resources: "${filePath}" maps to the reserved slug "index", which is used for the category metadata file. Rename the file or directory to resolve the conflict.`
24
+ );
25
+ const previous = emitted.get(slug);
26
+ if (previous !== void 0) {
27
+ throw new Error(
28
+ `codex-resources: ${resource} slug collision \u2014 "${slug}" is produced by both "${previous}" and "${filePath}". Rename one of the files to resolve the conflict.`
29
+ );
30
+ }
31
+ emitted.set(slug, filePath);
32
+ }
33
+ function writeGeneratedPage({
34
+ outputPath,
35
+ title,
36
+ description,
37
+ sidebarLabel,
38
+ sidebarPosition,
39
+ body
40
+ }) {
41
+ const position = sidebarPosition === void 0 ? "" : `sidebar_position: ${sidebarPosition}
42
+ `;
43
+ const label = sidebarLabel === void 0 ? "" : `sidebar_label: ${formatFrontmatterString(sidebarLabel)}
44
+ `;
45
+ fs.writeFileSync(
46
+ outputPath,
47
+ `---
48
+ title: ${formatFrontmatterString(title)}
49
+ description: ${formatFrontmatterString(description)}
50
+ ${position}${label}generated: true
51
+ ---
52
+
53
+ ${body.trim()}
54
+ `
55
+ );
56
+ }
57
+ function languageForFilename(filename) {
58
+ switch (path.extname(filename).toLowerCase()) {
59
+ case ".sh":
60
+ return "bash";
61
+ case ".py":
62
+ return "python";
63
+ case ".js":
64
+ case ".mjs":
65
+ return "javascript";
66
+ case ".ts":
67
+ return "typescript";
68
+ default:
69
+ return "";
70
+ }
71
+ }
72
+ export {
73
+ assertUniqueSlug,
74
+ filenameSlug,
75
+ isRecord,
76
+ languageForFilename,
77
+ renderTableRow,
78
+ warn,
79
+ writeGeneratedPage
80
+ };
@@ -0,0 +1,3 @@
1
+ export declare function ensureDir(dir: string): void;
2
+ export declare function cleanDir(dir: string): void;
3
+ export declare function listFiles(dir: string): string[];
@@ -0,0 +1,19 @@
1
+ import fs from "node:fs";
2
+ function ensureDir(dir) {
3
+ if (!fs.existsSync(dir)) {
4
+ fs.mkdirSync(dir, { recursive: true });
5
+ }
6
+ }
7
+ function cleanDir(dir) {
8
+ if (!fs.existsSync(dir)) return;
9
+ fs.rmSync(dir, { recursive: true, force: true });
10
+ }
11
+ function listFiles(dir) {
12
+ if (!fs.existsSync(dir)) return [];
13
+ return fs.readdirSync(dir, { withFileTypes: true }).filter((d) => d.isFile()).map((d) => d.name).sort();
14
+ }
15
+ export {
16
+ cleanDir,
17
+ ensureDir,
18
+ listFiles
19
+ };
@@ -0,0 +1,7 @@
1
+ export { escapeForMdx } from "./escape-for-mdx.js";
2
+ export { cleanDir, ensureDir, listFiles } from "./fs.js";
3
+ export { assertNotIndexReserved, escapeTitle, formatFrontmatterString, parseFrontmatter, type FrontmatterStringRenderer, writeCategoryIndex, writeUnlistedSubPage, } from "./mdx.js";
4
+ export { isRepoRelativeLink, downgradeRepoRelativeLinks } from "./links.js";
5
+ export { escapeMarkdownTableCell, renderCodeFence, } from "./markdown-structure.js";
6
+ export { EXCLUDED_DIR_NAMES, findNamedFiles } from "./walk.js";
7
+ export { generateSkillsCategory, getScriptDescription, getSkillFileTree, getSkillReferences, type GenerateSkillsCategoryOptions, type RenderExtraHeader, type SkillItem, type SkillReference, } from "./skills.js";
@@ -0,0 +1,44 @@
1
+ import { escapeForMdx } from "./escape-for-mdx.js";
2
+ import { cleanDir, ensureDir, listFiles } from "./fs.js";
3
+ import {
4
+ assertNotIndexReserved,
5
+ escapeTitle,
6
+ formatFrontmatterString,
7
+ parseFrontmatter,
8
+ writeCategoryIndex,
9
+ writeUnlistedSubPage
10
+ } from "./mdx.js";
11
+ import { isRepoRelativeLink, downgradeRepoRelativeLinks } from "./links.js";
12
+ import {
13
+ escapeMarkdownTableCell,
14
+ renderCodeFence
15
+ } from "./markdown-structure.js";
16
+ import { EXCLUDED_DIR_NAMES, findNamedFiles } from "./walk.js";
17
+ import {
18
+ generateSkillsCategory,
19
+ getScriptDescription,
20
+ getSkillFileTree,
21
+ getSkillReferences
22
+ } from "./skills.js";
23
+ export {
24
+ EXCLUDED_DIR_NAMES,
25
+ assertNotIndexReserved,
26
+ cleanDir,
27
+ downgradeRepoRelativeLinks,
28
+ ensureDir,
29
+ escapeForMdx,
30
+ escapeMarkdownTableCell,
31
+ escapeTitle,
32
+ findNamedFiles,
33
+ formatFrontmatterString,
34
+ generateSkillsCategory,
35
+ getScriptDescription,
36
+ getSkillFileTree,
37
+ getSkillReferences,
38
+ isRepoRelativeLink,
39
+ listFiles,
40
+ parseFrontmatter,
41
+ renderCodeFence,
42
+ writeCategoryIndex,
43
+ writeUnlistedSubPage
44
+ };
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Whether a markdown link target is a repo-relative file reference
3
+ * (`./wrangler.toml`, `../../schema/photos.sql`, `foo/bar.md`) rather than
4
+ * something the doc site can resolve: an absolute URL (`https://…`), a
5
+ * protocol-relative URL (`//…`), a site-absolute path (`/docs/…`), a pure
6
+ * anchor (`#…`), or a scheme (`mailto:`, `tel:`).
7
+ */
8
+ export declare function isRepoRelativeLink(url: string): boolean;
9
+ /**
10
+ * Downgrade repo-relative markdown links in a mirrored `CLAUDE.md` body to
11
+ * inline code so they don't dangle in the flattened mirror tree (#2411).
12
+ *
13
+ * A `CLAUDE.md`'s relative links point at real repo files (correct for an
14
+ * in-repo reader), but the mirror flattens each file into a single
15
+ * `claude-md/<name>.mdx` page with no counterpart for those targets — left as
16
+ * links they surface as `broken link:` warnings on every affected page. Inline
17
+ * code keeps the reference legible (`` `wrangler.toml` ``) without a href.
18
+ *
19
+ * Code spans are preserved verbatim: a `[x](./y)` inside a fenced block or an
20
+ * inline-code span is literal text, not a link, and must not be rewritten.
21
+ */
22
+ export declare function downgradeRepoRelativeLinks(content: string): string;
@@ -0,0 +1,44 @@
1
+ function isRepoRelativeLink(url) {
2
+ const trimmed = url.trim();
3
+ if (trimmed === "") return false;
4
+ if (trimmed.startsWith("#")) return false;
5
+ if (trimmed.startsWith("/")) return false;
6
+ if (/^[a-zA-Z][a-zA-Z0-9+.-]*:/.test(trimmed)) return false;
7
+ return true;
8
+ }
9
+ function downgradeRepoRelativeLinks(content) {
10
+ const blockPlaceholder = "\0CRLINK_BLOCK_";
11
+ const inlinePlaceholder = "\0CRLINK_INLINE_";
12
+ const codeBlocks = [];
13
+ const withBlocks = content.replace(/(`{3,}|~{3,})[^\n]*\n[\s\S]*?\1/g, (match) => {
14
+ codeBlocks.push(match);
15
+ return `${blockPlaceholder}${codeBlocks.length - 1}\0`;
16
+ });
17
+ const transformed = withBlocks.split(new RegExp(`(${blockPlaceholder}\\d+\0)`, "g")).map((part) => {
18
+ if (new RegExp(`^${blockPlaceholder}\\d+\0$`).test(part)) return part;
19
+ const inlineCodes = [];
20
+ const withInline = part.replace(
21
+ /(`{1,3})(?!`)([\s\S]*?[^`])\1(?!`)/g,
22
+ (match) => {
23
+ inlineCodes.push(match);
24
+ return `${inlinePlaceholder}${inlineCodes.length - 1}\0`;
25
+ }
26
+ );
27
+ const rewritten = withInline.replace(
28
+ /!?\[([^\]]*)\]\(([^)]+)\)/g,
29
+ (match, text, url) => isRepoRelativeLink(url) ? `\`${text}\`` : match
30
+ );
31
+ return rewritten.replace(
32
+ new RegExp(`${inlinePlaceholder}(\\d+)\0`, "g"),
33
+ (_, idx) => inlineCodes[Number(idx)] ?? ""
34
+ );
35
+ }).join("");
36
+ return transformed.replace(
37
+ new RegExp(`${blockPlaceholder}(\\d+)\0`, "g"),
38
+ (_, idx) => codeBlocks[Number(idx)] ?? ""
39
+ );
40
+ }
41
+ export {
42
+ downgradeRepoRelativeLinks,
43
+ isRepoRelativeLink
44
+ };
@@ -0,0 +1,9 @@
1
+ /** Render source inside a Markdown fence that cannot be closed by the source. */
2
+ export declare function renderCodeFence(source: string, language?: string): string;
3
+ /**
4
+ * Render an arbitrary value as a structurally safe Markdown table cell.
5
+ *
6
+ * Code-span delimiters are chosen from the value itself. Pipes are escaped
7
+ * for the surrounding table and physical newlines are collapsed to spaces.
8
+ */
9
+ export declare function escapeMarkdownTableCell(value: unknown): string;
@@ -0,0 +1,22 @@
1
+ function renderCodeFence(source, language = "") {
2
+ const runs = source.match(/`+/g) ?? [];
3
+ const longestRun = runs.reduce((max, run) => Math.max(max, run.length), 0);
4
+ const fence = "`".repeat(Math.max(3, longestRun + 1));
5
+ const trailingNewline = source.endsWith("\n") ? "" : "\n";
6
+ return `${fence}${language}
7
+ ${source}${trailingNewline}${fence}`;
8
+ }
9
+ function escapeMarkdownTableCell(value) {
10
+ if (value === void 0 || value === null || value === "") return "\u2014";
11
+ const content = String(value).replace(/\r\n?|\n/g, " ").replace(/\|/g, "\\|");
12
+ if (content === "") return "\u2014";
13
+ const runs = content.match(/`+/g) ?? [];
14
+ const longestRun = runs.reduce((max, run) => Math.max(max, run.length), 0);
15
+ const delimiter = "`".repeat(Math.max(1, longestRun + 1));
16
+ const needsPadding = /^[` ]|[` ]$/.test(content);
17
+ return needsPadding ? `${delimiter} ${content} ${delimiter}` : `${delimiter}${content}${delimiter}`;
18
+ }
19
+ export {
20
+ escapeMarkdownTableCell,
21
+ renderCodeFence
22
+ };
@@ -0,0 +1,23 @@
1
+ import matter from "gray-matter";
2
+ export declare function parseFrontmatter(content: string): matter.GrayMatterFile<string> | null;
3
+ export declare function escapeTitle(s: string): string;
4
+ export type FrontmatterStringRenderer = (value: string) => string;
5
+ export declare function formatFrontmatterString(value: string): string;
6
+ export declare function writeCategoryIndex(outputDir: string, label: string, position: number, description: string, renderString?: FrontmatterStringRenderer): void;
7
+ /**
8
+ * Writes an unlisted sub-page MDX file. Used for skill references, scripts,
9
+ * and assets.
10
+ *
11
+ * The route is derived from the file's path within the content collection —
12
+ * deliberately NOT from an explicit `slug:`. zfb's `resolveMarkdownLinks`
13
+ * resolves relative links against the source file path, so the on-disk
14
+ * location of these pages must match the URL the skill page links to. Writing
15
+ * them at `<dir>/ref-<name>.mdx` (siblings of the skill's `index.mdx`) is what
16
+ * makes the `./ref-<name>` links resolve (#2411).
17
+ */
18
+ export declare function writeUnlistedSubPage(outputPath: string, title: string, body: string, renderString?: FrontmatterStringRenderer): void;
19
+ /**
20
+ * Guards that the given name/slug is not the reserved "index" value.
21
+ * Throws with a contextual message if it is.
22
+ */
23
+ export declare function assertNotIndexReserved(nameOrSlug: string, errorMessage: string): void;