@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.
- package/CHANGELOG.md +18 -0
- package/dist/config.d.ts +18 -0
- package/dist/config.js +1 -0
- package/dist/i18n-defaults/index.js +3 -0
- package/dist/plugins/codex-resources.d.ts +4 -0
- package/dist/plugins/codex-resources.js +31 -0
- package/dist/plugins/internal/claude-resources/generate.js +20 -291
- package/dist/plugins/internal/codex-resources/agents-md.d.ts +6 -0
- package/dist/plugins/internal/codex-resources/agents-md.js +70 -0
- package/dist/plugins/internal/codex-resources/agents.d.ts +7 -0
- package/dist/plugins/internal/codex-resources/agents.js +104 -0
- package/dist/plugins/internal/codex-resources/config.d.ts +6 -0
- package/dist/plugins/internal/codex-resources/config.js +111 -0
- package/dist/plugins/internal/codex-resources/generate.d.ts +20 -0
- package/dist/plugins/internal/codex-resources/generate.js +34 -0
- package/dist/plugins/internal/codex-resources/hooks.d.ts +6 -0
- package/dist/plugins/internal/codex-resources/hooks.js +174 -0
- package/dist/plugins/internal/codex-resources/index.d.ts +24 -0
- package/dist/plugins/internal/codex-resources/index.js +21 -0
- package/dist/plugins/internal/codex-resources/overview.d.ts +10 -0
- package/dist/plugins/internal/codex-resources/overview.js +33 -0
- package/dist/plugins/internal/codex-resources/rules.d.ts +6 -0
- package/dist/plugins/internal/codex-resources/rules.js +255 -0
- package/dist/plugins/internal/codex-resources/skills.d.ts +2 -0
- package/dist/plugins/internal/codex-resources/skills.js +84 -0
- package/dist/plugins/internal/codex-resources/utils.d.ts +14 -0
- package/dist/plugins/internal/codex-resources/utils.js +80 -0
- package/dist/plugins/internal/resource-docs-shared/fs.d.ts +3 -0
- package/dist/plugins/internal/resource-docs-shared/fs.js +19 -0
- package/dist/plugins/internal/resource-docs-shared/index.d.ts +7 -0
- package/dist/plugins/internal/resource-docs-shared/index.js +44 -0
- package/dist/plugins/internal/resource-docs-shared/links.d.ts +22 -0
- package/dist/plugins/internal/resource-docs-shared/links.js +44 -0
- package/dist/plugins/internal/resource-docs-shared/markdown-structure.d.ts +9 -0
- package/dist/plugins/internal/resource-docs-shared/markdown-structure.js +22 -0
- package/dist/plugins/internal/resource-docs-shared/mdx.d.ts +23 -0
- package/dist/plugins/internal/resource-docs-shared/mdx.js +63 -0
- package/dist/plugins/internal/resource-docs-shared/skills.d.ts +37 -0
- package/dist/plugins/internal/resource-docs-shared/skills.js +318 -0
- package/dist/plugins/internal/resource-docs-shared/walk.d.ts +20 -0
- package/dist/plugins/internal/resource-docs-shared/walk.js +41 -0
- package/dist/preset.d.ts +8 -0
- package/dist/preset.js +11 -0
- package/dist/safelist.css +1 -1
- package/dist/settings.d.ts +5 -0
- package/package.json +7 -2
- /package/dist/plugins/internal/{claude-resources → resource-docs-shared}/escape-for-mdx.d.ts +0 -0
- /package/dist/plugins/internal/{claude-resources → resource-docs-shared}/escape-for-mdx.js +0 -0
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,24 @@ All notable changes to `@takazudo/zudo-doc` are documented in this file.
|
|
|
4
4
|
|
|
5
5
|
The format is based on Keep a Changelog, and release notes are generated from the changelog MDX pages.
|
|
6
6
|
|
|
7
|
+
## [5.9.0] - 2026-08-20
|
|
8
|
+
|
|
9
|
+
### Features
|
|
10
|
+
|
|
11
|
+
- Added Codex Resources generation for `AGENTS.md`, Codex configuration, custom agents, hooks, rules, and skills, with dedicated parsing and rendering for each resource type. (`666c12059`)
|
|
12
|
+
- Added the public Codex Resources plugin and wired it through package settings, presets, `create-zudo-doc`, and generated project configuration. (`752a3fe5a`, `a7fa47906`, `5831cd73e`)
|
|
13
|
+
- Added Codex Resources showcase data, navigation, localization, development watching, and complete English and Japanese documentation. (`a4dca1e40`, `1a2e6d5bc`, `f1396e7a1`)
|
|
14
|
+
|
|
15
|
+
### Bug Fixes
|
|
16
|
+
|
|
17
|
+
- Added type validation and deterministic fallbacks for malformed skill frontmatter values. (`710a1d225`)
|
|
18
|
+
- Made generated Codex Resources frontmatter formatter-stable while preserving YAML string semantics and existing Claude Resources output.
|
|
19
|
+
|
|
20
|
+
### Other Changes
|
|
21
|
+
|
|
22
|
+
- Extracted shared resource-documentation helpers while preserving existing Claude Resources output byte-for-byte. (`c9165d5cb`)
|
|
23
|
+
- Expanded generator, scaffold, build, and responsive navigation regression coverage for Codex Resources. (`142c11aa4`)
|
|
24
|
+
|
|
7
25
|
## [5.8.0] - 2026-08-20
|
|
8
26
|
|
|
9
27
|
### Features
|
package/dist/config.d.ts
CHANGED
|
@@ -72,6 +72,12 @@ type ClaudeResourcesConfig = {
|
|
|
72
72
|
projectRoot?: string;
|
|
73
73
|
scanRoot?: string;
|
|
74
74
|
} | false;
|
|
75
|
+
/** The `settings.codexResources` block (or `false` when disabled). */
|
|
76
|
+
type CodexResourcesConfig = {
|
|
77
|
+
codexDir: string;
|
|
78
|
+
projectRoot?: string;
|
|
79
|
+
scanRoot?: string;
|
|
80
|
+
} | false;
|
|
75
81
|
export declare const DEFAULT_SETTINGS: Settings;
|
|
76
82
|
export interface ZudoDocConfig {
|
|
77
83
|
/**
|
|
@@ -372,6 +378,18 @@ export interface ZudoDocConfig {
|
|
|
372
378
|
* @default false
|
|
373
379
|
*/
|
|
374
380
|
claudeResources?: ClaudeResourcesConfig;
|
|
381
|
+
/**
|
|
382
|
+
* Codex-resources ingestion config, or `false` to disable.
|
|
383
|
+
* `codexDir` is the `.codex/` directory, resolved against `projectRoot`.
|
|
384
|
+
* `projectRoot` anchors relative paths and the output base. `scanRoot` is
|
|
385
|
+
* the repo-wide discovery root for the `AGENTS.md` / `AGENTS.override.md`
|
|
386
|
+
* walk (including its relPath base for titles/slugs) and the repo-level
|
|
387
|
+
* `.agents/skills/` root; it defaults to `projectRoot`. This lets a doc site
|
|
388
|
+
* in a repo subdirectory scan repo-wide instructions and team skills while
|
|
389
|
+
* writing into its own collection (#2558).
|
|
390
|
+
* @default false
|
|
391
|
+
*/
|
|
392
|
+
codexResources?: CodexResourcesConfig;
|
|
375
393
|
/**
|
|
376
394
|
* Route prefixes served only in the default locale (never locale-prefixed).
|
|
377
395
|
* @default []
|
package/dist/config.js
CHANGED
|
@@ -6,6 +6,7 @@ const defaultTranslations = {
|
|
|
6
6
|
"nav.components": "Components",
|
|
7
7
|
"nav.reference": "Reference",
|
|
8
8
|
"nav.claude": "Claude",
|
|
9
|
+
"nav.codex": "Codex",
|
|
9
10
|
"nav.changelog": "Changelog",
|
|
10
11
|
"nav.develop": "Develop",
|
|
11
12
|
"nav.previous": "Previous",
|
|
@@ -65,6 +66,7 @@ const defaultTranslations = {
|
|
|
65
66
|
"nav.components": "\u30B3\u30F3\u30DD\u30FC\u30CD\u30F3\u30C8",
|
|
66
67
|
"nav.reference": "\u30EA\u30D5\u30A1\u30EC\u30F3\u30B9",
|
|
67
68
|
"nav.claude": "Claude",
|
|
69
|
+
"nav.codex": "Codex",
|
|
68
70
|
"nav.changelog": "\u5909\u66F4\u5C65\u6B74",
|
|
69
71
|
"nav.develop": "\u958B\u767A",
|
|
70
72
|
"nav.previous": "\u524D\u3078",
|
|
@@ -124,6 +126,7 @@ const defaultTranslations = {
|
|
|
124
126
|
"nav.components": "Komponenten",
|
|
125
127
|
"nav.reference": "Referenz",
|
|
126
128
|
"nav.claude": "Claude",
|
|
129
|
+
"nav.codex": "Codex",
|
|
127
130
|
"nav.changelog": "Changelog",
|
|
128
131
|
"nav.develop": "Entwicklung",
|
|
129
132
|
"nav.previous": "Zur\xFCck",
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { runCodexResourcesPreStep } from "./internal/codex-resources/index.js";
|
|
2
|
+
import { runCodexResourcesPreStep as runCodexResourcesPreStep2 } from "./internal/codex-resources/index.js";
|
|
3
|
+
const PLUGIN_NAME = "@takazudo/zudo-doc-codex-resources";
|
|
4
|
+
const plugin = {
|
|
5
|
+
name: PLUGIN_NAME,
|
|
6
|
+
async preBuild(ctx) {
|
|
7
|
+
const codexDir = ctx.options["codexDir"];
|
|
8
|
+
if (typeof codexDir !== "string" || codexDir.length === 0) {
|
|
9
|
+
throw new Error(
|
|
10
|
+
`[${PLUGIN_NAME}] preBuild: options.codexDir must be a non-empty string (got ${JSON.stringify(codexDir)})`
|
|
11
|
+
);
|
|
12
|
+
}
|
|
13
|
+
const projectRootOpt = ctx.options["projectRoot"];
|
|
14
|
+
const scanRootOpt = ctx.options["scanRoot"];
|
|
15
|
+
const docsDirOpt = ctx.options["docsDir"];
|
|
16
|
+
const result = runCodexResourcesPreStep({
|
|
17
|
+
codexDir,
|
|
18
|
+
projectRoot: typeof projectRootOpt === "string" ? projectRootOpt : ctx.projectRoot,
|
|
19
|
+
scanRoot: typeof scanRootOpt === "string" ? scanRootOpt : void 0,
|
|
20
|
+
docsDir: typeof docsDirOpt === "string" ? docsDirOpt : "src/content/docs"
|
|
21
|
+
});
|
|
22
|
+
ctx.logger.info(
|
|
23
|
+
`codex-resources: ${result.agentsMd} AGENTS.md, ${result.config} config, ${result.agents} agents, ${result.hooks} hooks, ${result.rules} rules, ${result.skills} skills`
|
|
24
|
+
);
|
|
25
|
+
}
|
|
26
|
+
};
|
|
27
|
+
var codex_resources_default = plugin;
|
|
28
|
+
export {
|
|
29
|
+
codex_resources_default as default,
|
|
30
|
+
runCodexResourcesPreStep2 as runCodexResourcesPreStep
|
|
31
|
+
};
|
|
@@ -1,133 +1,17 @@
|
|
|
1
1
|
import fs from "node:fs";
|
|
2
2
|
import path from "node:path";
|
|
3
|
-
import
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
try {
|
|
16
|
-
return matter(content);
|
|
17
|
-
} catch {
|
|
18
|
-
return null;
|
|
19
|
-
}
|
|
20
|
-
}
|
|
21
|
-
function escapeTitle(s) {
|
|
22
|
-
return s.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
|
|
23
|
-
}
|
|
24
|
-
function listFiles(dir) {
|
|
25
|
-
if (!fs.existsSync(dir)) return [];
|
|
26
|
-
return fs.readdirSync(dir, { withFileTypes: true }).filter((d) => d.isFile()).map((d) => d.name).sort();
|
|
27
|
-
}
|
|
28
|
-
function writeCategoryIndex(outputDir, label, position, description) {
|
|
29
|
-
const mdx = `---
|
|
30
|
-
title: "${escapeTitle(label)}"
|
|
31
|
-
description: "${escapeTitle(description)}"
|
|
32
|
-
sidebar_position: ${position}
|
|
33
|
-
category_no_page: true
|
|
34
|
-
generated: true
|
|
35
|
-
---
|
|
36
|
-
`;
|
|
37
|
-
fs.writeFileSync(path.join(outputDir, "index.mdx"), mdx);
|
|
38
|
-
}
|
|
39
|
-
function writeUnlistedSubPage(outputPath, title, body) {
|
|
40
|
-
fs.writeFileSync(
|
|
41
|
-
outputPath,
|
|
42
|
-
`---
|
|
43
|
-
title: "${escapeTitle(title)}"
|
|
44
|
-
unlisted: true
|
|
45
|
-
generated: true
|
|
46
|
-
---
|
|
47
|
-
|
|
48
|
-
${body}
|
|
49
|
-
`
|
|
50
|
-
);
|
|
51
|
-
}
|
|
52
|
-
function assertNotIndexReserved(nameOrSlug, errorMessage) {
|
|
53
|
-
if (nameOrSlug === "index") {
|
|
54
|
-
throw new Error(errorMessage);
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
function isRepoRelativeLink(url) {
|
|
58
|
-
const trimmed = url.trim();
|
|
59
|
-
if (trimmed === "") return false;
|
|
60
|
-
if (trimmed.startsWith("#")) return false;
|
|
61
|
-
if (trimmed.startsWith("/")) return false;
|
|
62
|
-
if (/^[a-zA-Z][a-zA-Z0-9+.-]*:/.test(trimmed)) return false;
|
|
63
|
-
return true;
|
|
64
|
-
}
|
|
65
|
-
function downgradeRepoRelativeLinks(content) {
|
|
66
|
-
const blockPlaceholder = "\0CRLINK_BLOCK_";
|
|
67
|
-
const inlinePlaceholder = "\0CRLINK_INLINE_";
|
|
68
|
-
const codeBlocks = [];
|
|
69
|
-
const withBlocks = content.replace(/(`{3,}|~{3,})[^\n]*\n[\s\S]*?\1/g, (match) => {
|
|
70
|
-
codeBlocks.push(match);
|
|
71
|
-
return `${blockPlaceholder}${codeBlocks.length - 1}\0`;
|
|
72
|
-
});
|
|
73
|
-
const transformed = withBlocks.split(new RegExp(`(${blockPlaceholder}\\d+\0)`, "g")).map((part) => {
|
|
74
|
-
if (new RegExp(`^${blockPlaceholder}\\d+\0$`).test(part)) return part;
|
|
75
|
-
const inlineCodes = [];
|
|
76
|
-
const withInline = part.replace(
|
|
77
|
-
/(`{1,3})(?!`)([\s\S]*?[^`])\1(?!`)/g,
|
|
78
|
-
(match) => {
|
|
79
|
-
inlineCodes.push(match);
|
|
80
|
-
return `${inlinePlaceholder}${inlineCodes.length - 1}\0`;
|
|
81
|
-
}
|
|
82
|
-
);
|
|
83
|
-
const rewritten = withInline.replace(
|
|
84
|
-
/!?\[([^\]]*)\]\(([^)]+)\)/g,
|
|
85
|
-
(match, text, url) => isRepoRelativeLink(url) ? `\`${text}\`` : match
|
|
86
|
-
);
|
|
87
|
-
return rewritten.replace(
|
|
88
|
-
new RegExp(`${inlinePlaceholder}(\\d+)\0`, "g"),
|
|
89
|
-
(_, idx) => inlineCodes[Number(idx)] ?? ""
|
|
90
|
-
);
|
|
91
|
-
}).join("");
|
|
92
|
-
return transformed.replace(
|
|
93
|
-
new RegExp(`${blockPlaceholder}(\\d+)\0`, "g"),
|
|
94
|
-
(_, idx) => codeBlocks[Number(idx)] ?? ""
|
|
95
|
-
);
|
|
96
|
-
}
|
|
97
|
-
const EXCLUDED_DIR_NAMES = /* @__PURE__ */ new Set([
|
|
98
|
-
"node_modules",
|
|
99
|
-
"worktrees",
|
|
100
|
-
"dist",
|
|
101
|
-
"out",
|
|
102
|
-
"public",
|
|
103
|
-
"__inbox",
|
|
104
|
-
"test-results"
|
|
105
|
-
]);
|
|
106
|
-
function findClaudeMdFiles(dir, excludeDirs) {
|
|
107
|
-
const results = [];
|
|
108
|
-
if (!fs.existsSync(dir)) return results;
|
|
109
|
-
const excludes = excludeDirs.map(
|
|
110
|
-
(d) => d.endsWith(path.sep) ? d.slice(0, -path.sep.length) : d
|
|
111
|
-
);
|
|
112
|
-
for (const item of fs.readdirSync(dir)) {
|
|
113
|
-
if (EXCLUDED_DIR_NAMES.has(item)) continue;
|
|
114
|
-
if (item.startsWith(".")) continue;
|
|
115
|
-
const itemPath = path.join(dir, item);
|
|
116
|
-
if (excludes.some((d) => itemPath === d || itemPath.startsWith(d + path.sep))) continue;
|
|
117
|
-
let stat;
|
|
118
|
-
try {
|
|
119
|
-
stat = fs.lstatSync(itemPath);
|
|
120
|
-
} catch {
|
|
121
|
-
continue;
|
|
122
|
-
}
|
|
123
|
-
if (stat.isDirectory()) {
|
|
124
|
-
results.push(...findClaudeMdFiles(itemPath, excludes));
|
|
125
|
-
} else if (stat.isFile() && item === "CLAUDE.md") {
|
|
126
|
-
results.push(itemPath);
|
|
127
|
-
}
|
|
128
|
-
}
|
|
129
|
-
return results;
|
|
130
|
-
}
|
|
3
|
+
import {
|
|
4
|
+
assertNotIndexReserved,
|
|
5
|
+
cleanDir,
|
|
6
|
+
downgradeRepoRelativeLinks,
|
|
7
|
+
ensureDir,
|
|
8
|
+
escapeForMdx,
|
|
9
|
+
escapeTitle,
|
|
10
|
+
findNamedFiles,
|
|
11
|
+
generateSkillsCategory,
|
|
12
|
+
parseFrontmatter,
|
|
13
|
+
writeCategoryIndex
|
|
14
|
+
} from "../resource-docs-shared/index.js";
|
|
131
15
|
function generateClaudemdDocs(config) {
|
|
132
16
|
const projectRoot = config.projectRoot ?? config.claudeDir;
|
|
133
17
|
const scanRoot = config.scanRoot ?? projectRoot;
|
|
@@ -138,7 +22,7 @@ function generateClaudemdDocs(config) {
|
|
|
138
22
|
path.join(projectRoot, "e2e", "fixtures"),
|
|
139
23
|
path.join(config.docsDir)
|
|
140
24
|
];
|
|
141
|
-
const files =
|
|
25
|
+
const files = findNamedFiles(scanRoot, excludeDirs, ["CLAUDE.md"]);
|
|
142
26
|
if (files.length === 0) return [];
|
|
143
27
|
ensureDir(outputDir);
|
|
144
28
|
const items = [];
|
|
@@ -220,170 +104,15 @@ ${escapeForMdx(parsed.content.trim())}
|
|
|
220
104
|
writeCategoryIndex(outputDir, "Commands", 901, "Custom slash commands");
|
|
221
105
|
return items;
|
|
222
106
|
}
|
|
223
|
-
function getSkillFileTree(skillDir, subDirs) {
|
|
224
|
-
const lines = [`${skillDir}/`];
|
|
225
|
-
const entries = [{ isDir: false, name: "SKILL.md" }];
|
|
226
|
-
for (const sub of subDirs) {
|
|
227
|
-
entries.push({ isDir: true, name: sub.name, children: sub.files });
|
|
228
|
-
}
|
|
229
|
-
for (let i = 0; i < entries.length; i++) {
|
|
230
|
-
const entry = entries[i];
|
|
231
|
-
if (!entry) continue;
|
|
232
|
-
const isLast = i === entries.length - 1;
|
|
233
|
-
const prefix = isLast ? "\u2514\u2500\u2500 " : "\u251C\u2500\u2500 ";
|
|
234
|
-
if (!entry.isDir) {
|
|
235
|
-
lines.push(`${prefix}${entry.name}`);
|
|
236
|
-
} else {
|
|
237
|
-
lines.push(`${prefix}${entry.name}/`);
|
|
238
|
-
for (let j = 0; j < entry.children.length; j++) {
|
|
239
|
-
const child = entry.children[j];
|
|
240
|
-
if (!child) continue;
|
|
241
|
-
const childIsLast = j === entry.children.length - 1;
|
|
242
|
-
const continuation = isLast ? " " : "\u2502 ";
|
|
243
|
-
const childPrefix = childIsLast ? "\u2514\u2500\u2500 " : "\u251C\u2500\u2500 ";
|
|
244
|
-
lines.push(`${continuation}${childPrefix}${child}`);
|
|
245
|
-
}
|
|
246
|
-
}
|
|
247
|
-
}
|
|
248
|
-
return lines.join("\n");
|
|
249
|
-
}
|
|
250
|
-
function getScriptDescription(filePath) {
|
|
251
|
-
try {
|
|
252
|
-
const topLines = fs.readFileSync(filePath, "utf8").split("\n", 2);
|
|
253
|
-
const firstLine = topLines[0] ?? "";
|
|
254
|
-
const commentLine = firstLine.startsWith("#!") ? topLines[1] ?? "" : firstLine;
|
|
255
|
-
const match = commentLine.match(/^(?:#|\/\/)\s*(.+)/);
|
|
256
|
-
return match ? ` \u2014 ${match[1]}` : "";
|
|
257
|
-
} catch {
|
|
258
|
-
return "";
|
|
259
|
-
}
|
|
260
|
-
}
|
|
261
|
-
function getSkillReferences(skillsDir, skillDir) {
|
|
262
|
-
const refsDir = path.join(skillsDir, skillDir, "references");
|
|
263
|
-
if (!fs.existsSync(refsDir)) return [];
|
|
264
|
-
return fs.readdirSync(refsDir).filter((f) => f.endsWith(".md")).map((f) => {
|
|
265
|
-
const content = fs.readFileSync(path.join(refsDir, f), "utf8");
|
|
266
|
-
const name = f.replace(/\.md$/, "");
|
|
267
|
-
const h1Match = content.match(/^#\s+(.+)$/m);
|
|
268
|
-
const title = h1Match?.[1] ?? name;
|
|
269
|
-
return { name, title, content };
|
|
270
|
-
}).sort((a, b) => a.name.localeCompare(b.name));
|
|
271
|
-
}
|
|
272
107
|
function generateSkillsDocs(config) {
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
108
|
+
return generateSkillsCategory({
|
|
109
|
+
skillsDirs: [path.join(config.claudeDir, "skills")],
|
|
110
|
+
outputDir: path.join(config.docsDir, "claude-skills"),
|
|
111
|
+
label: "Skills",
|
|
112
|
+
position: 902,
|
|
113
|
+
description: "Skill packages",
|
|
114
|
+
sourceLabel: ".claude/skills"
|
|
280
115
|
});
|
|
281
|
-
if (dirs.length === 0) return [];
|
|
282
|
-
ensureDir(outputDir);
|
|
283
|
-
const items = [];
|
|
284
|
-
for (const dir of dirs) {
|
|
285
|
-
assertNotIndexReserved(
|
|
286
|
-
dir,
|
|
287
|
-
`claude-resources: skill directory ".claude/skills/index/" uses the reserved name "index", which is used for the category metadata file. Rename the skill directory to resolve the conflict.`
|
|
288
|
-
);
|
|
289
|
-
const content = fs.readFileSync(
|
|
290
|
-
path.join(skillsDir, dir, "SKILL.md"),
|
|
291
|
-
"utf8"
|
|
292
|
-
);
|
|
293
|
-
const parsed = parseFrontmatter(content);
|
|
294
|
-
if (!parsed) continue;
|
|
295
|
-
const name = parsed.data.name || dir;
|
|
296
|
-
const description = parsed.data.description || "";
|
|
297
|
-
const references = getSkillReferences(skillsDir, dir);
|
|
298
|
-
items.push({ name, dir, description, references });
|
|
299
|
-
const scriptFiles = listFiles(path.join(skillsDir, dir, "scripts"));
|
|
300
|
-
const assetFiles = listFiles(path.join(skillsDir, dir, "assets"));
|
|
301
|
-
const refFiles = references.map((r) => `${r.name}.md`);
|
|
302
|
-
const subDirs = [];
|
|
303
|
-
if (scriptFiles.length > 0) subDirs.push({ name: "scripts", files: scriptFiles });
|
|
304
|
-
if (refFiles.length > 0) subDirs.push({ name: "references", files: refFiles });
|
|
305
|
-
if (assetFiles.length > 0) subDirs.push({ name: "assets", files: assetFiles });
|
|
306
|
-
let fileStructureSection = "";
|
|
307
|
-
if (subDirs.length > 0) {
|
|
308
|
-
const tree = `\`\`\`
|
|
309
|
-
${getSkillFileTree(dir, subDirs)}
|
|
310
|
-
\`\`\``;
|
|
311
|
-
const links = [];
|
|
312
|
-
for (const ref of references) {
|
|
313
|
-
links.push(`- [references/${ref.name}.md](./ref-${ref.name})`);
|
|
314
|
-
}
|
|
315
|
-
for (const f of scriptFiles.filter((s) => s.endsWith(".md"))) {
|
|
316
|
-
const slug = f.replace(/\.md$/, "");
|
|
317
|
-
links.push(`- [scripts/${f}](./script-${slug})`);
|
|
318
|
-
}
|
|
319
|
-
for (const f of assetFiles.filter((a) => a.endsWith(".md"))) {
|
|
320
|
-
const slug = f.replace(/\.md$/, "");
|
|
321
|
-
links.push(`- [assets/${f}](./asset-${slug})`);
|
|
322
|
-
}
|
|
323
|
-
const linkList = links.length > 0 ? `
|
|
324
|
-
|
|
325
|
-
${links.join("\n")}` : "";
|
|
326
|
-
fileStructureSection = `## File Structure
|
|
327
|
-
|
|
328
|
-
${tree}${linkList}`;
|
|
329
|
-
}
|
|
330
|
-
const shortDesc = description.length > 200 ? description.substring(0, 200) + "..." : description;
|
|
331
|
-
let skillBody = parsed.content.trim();
|
|
332
|
-
skillBody = skillBody.replace(/\]\(references\/([^)]+)\.md\)/g, "](./ref-$1)").replace(/\]\(scripts\/([^)]+)\.md\)/g, "](./script-$1)").replace(/\]\(assets\/([^)]+)\.md\)/g, "](./asset-$1)");
|
|
333
|
-
const body = [
|
|
334
|
-
fileStructureSection,
|
|
335
|
-
escapeForMdx(skillBody)
|
|
336
|
-
].filter(Boolean).join("\n\n");
|
|
337
|
-
const mdx = `---
|
|
338
|
-
title: "${escapeTitle(name)}"
|
|
339
|
-
description: "${escapeTitle(shortDesc)}"
|
|
340
|
-
sidebar_label: "${escapeTitle(name)}"
|
|
341
|
-
generated: true
|
|
342
|
-
---
|
|
343
|
-
|
|
344
|
-
${body}`;
|
|
345
|
-
const skillDirOut = path.join(outputDir, dir);
|
|
346
|
-
ensureDir(skillDirOut);
|
|
347
|
-
fs.writeFileSync(path.join(skillDirOut, "index.mdx"), mdx);
|
|
348
|
-
for (const ref of references) {
|
|
349
|
-
writeUnlistedSubPage(
|
|
350
|
-
path.join(skillDirOut, `ref-${ref.name}.mdx`),
|
|
351
|
-
ref.title,
|
|
352
|
-
escapeForMdx(ref.content.trim())
|
|
353
|
-
);
|
|
354
|
-
}
|
|
355
|
-
for (const f of scriptFiles.filter((s) => s.endsWith(".md"))) {
|
|
356
|
-
const slug = f.replace(/\.md$/, "");
|
|
357
|
-
const raw = fs.readFileSync(
|
|
358
|
-
path.join(skillsDir, dir, "scripts", f),
|
|
359
|
-
"utf8"
|
|
360
|
-
);
|
|
361
|
-
const h1Match = raw.match(/^#\s+(.+)$/m);
|
|
362
|
-
const title = h1Match?.[1] ?? slug;
|
|
363
|
-
writeUnlistedSubPage(
|
|
364
|
-
path.join(skillDirOut, `script-${slug}.mdx`),
|
|
365
|
-
title,
|
|
366
|
-
escapeForMdx(raw.trim())
|
|
367
|
-
);
|
|
368
|
-
}
|
|
369
|
-
for (const f of assetFiles.filter((a) => a.endsWith(".md"))) {
|
|
370
|
-
const slug = f.replace(/\.md$/, "");
|
|
371
|
-
const raw = fs.readFileSync(
|
|
372
|
-
path.join(skillsDir, dir, "assets", f),
|
|
373
|
-
"utf8"
|
|
374
|
-
);
|
|
375
|
-
const h1Match = raw.match(/^#\s+(.+)$/m);
|
|
376
|
-
const title = h1Match?.[1] ?? slug;
|
|
377
|
-
writeUnlistedSubPage(
|
|
378
|
-
path.join(skillDirOut, `asset-${slug}.mdx`),
|
|
379
|
-
title,
|
|
380
|
-
escapeForMdx(raw.trim())
|
|
381
|
-
);
|
|
382
|
-
}
|
|
383
|
-
}
|
|
384
|
-
items.sort((a, b) => a.name.localeCompare(b.name));
|
|
385
|
-
writeCategoryIndex(outputDir, "Skills", 902, "Skill packages");
|
|
386
|
-
return items;
|
|
387
116
|
}
|
|
388
117
|
function generateAgentsDocs(config) {
|
|
389
118
|
const agentsDir = path.join(config.claudeDir, "agents");
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import {
|
|
4
|
+
formatFrontmatterString,
|
|
5
|
+
cleanDir,
|
|
6
|
+
downgradeRepoRelativeLinks,
|
|
7
|
+
ensureDir,
|
|
8
|
+
escapeForMdx,
|
|
9
|
+
escapeMarkdownTableCell,
|
|
10
|
+
findNamedFiles,
|
|
11
|
+
writeCategoryIndex
|
|
12
|
+
} from "../resource-docs-shared/index.js";
|
|
13
|
+
import { assertUniqueSlug, writeGeneratedPage } from "./utils.js";
|
|
14
|
+
function generateAgentsMdCategory(config) {
|
|
15
|
+
const projectRoot = config.projectRoot ?? config.codexDir;
|
|
16
|
+
const scanRoot = config.scanRoot ?? projectRoot;
|
|
17
|
+
const outputDir = path.join(config.docsDir, "codex-agents-md");
|
|
18
|
+
cleanDir(outputDir);
|
|
19
|
+
const excludeDirs = [
|
|
20
|
+
path.join(scanRoot, "e2e", "fixtures"),
|
|
21
|
+
path.join(projectRoot, "e2e", "fixtures"),
|
|
22
|
+
config.docsDir
|
|
23
|
+
];
|
|
24
|
+
const files = findNamedFiles(scanRoot, excludeDirs, [
|
|
25
|
+
"AGENTS.md",
|
|
26
|
+
"AGENTS.override.md"
|
|
27
|
+
]);
|
|
28
|
+
if (files.length === 0) return [];
|
|
29
|
+
const items = files.map((absPath) => {
|
|
30
|
+
const relPath = path.relative(scanRoot, absPath);
|
|
31
|
+
const dirPart = path.dirname(relPath);
|
|
32
|
+
const baseSlug = dirPart === "." ? "root" : dirPart.split(path.sep).join("--");
|
|
33
|
+
const slug = path.basename(absPath) === "AGENTS.override.md" ? `${baseSlug}--override` : baseSlug;
|
|
34
|
+
return { absPath, relPath, slug };
|
|
35
|
+
});
|
|
36
|
+
items.sort((a, b) => {
|
|
37
|
+
if (a.slug === "root") return -1;
|
|
38
|
+
if (b.slug === "root") return 1;
|
|
39
|
+
return a.relPath.localeCompare(b.relPath);
|
|
40
|
+
});
|
|
41
|
+
ensureDir(outputDir);
|
|
42
|
+
const emitted = /* @__PURE__ */ new Map();
|
|
43
|
+
items.forEach((item, index) => {
|
|
44
|
+
assertUniqueSlug("AGENTS.md", item.slug, item.relPath, emitted);
|
|
45
|
+
const content = fs.readFileSync(item.absPath, "utf8");
|
|
46
|
+
writeGeneratedPage({
|
|
47
|
+
outputPath: path.join(outputDir, `${item.slug}.mdx`),
|
|
48
|
+
title: `/${item.relPath}`,
|
|
49
|
+
description: `Codex instructions at /${item.relPath}`,
|
|
50
|
+
sidebarPosition: index + 1,
|
|
51
|
+
sidebarLabel: item.relPath,
|
|
52
|
+
body: `**Path:** ${escapeMarkdownTableCell(item.relPath)}
|
|
53
|
+
|
|
54
|
+
${escapeForMdx(
|
|
55
|
+
downgradeRepoRelativeLinks(content.trim())
|
|
56
|
+
)}`
|
|
57
|
+
});
|
|
58
|
+
});
|
|
59
|
+
writeCategoryIndex(
|
|
60
|
+
outputDir,
|
|
61
|
+
"AGENTS.md",
|
|
62
|
+
905,
|
|
63
|
+
"Project instructions for Codex",
|
|
64
|
+
formatFrontmatterString
|
|
65
|
+
);
|
|
66
|
+
return items.map(({ relPath, slug }) => ({ relPath, slug }));
|
|
67
|
+
}
|
|
68
|
+
export {
|
|
69
|
+
generateAgentsMdCategory
|
|
70
|
+
};
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { parse } from "smol-toml";
|
|
4
|
+
import {
|
|
5
|
+
formatFrontmatterString,
|
|
6
|
+
cleanDir,
|
|
7
|
+
ensureDir,
|
|
8
|
+
escapeForMdx,
|
|
9
|
+
escapeMarkdownTableCell,
|
|
10
|
+
renderCodeFence,
|
|
11
|
+
writeCategoryIndex
|
|
12
|
+
} from "../resource-docs-shared/index.js";
|
|
13
|
+
import {
|
|
14
|
+
assertUniqueSlug,
|
|
15
|
+
filenameSlug,
|
|
16
|
+
isRecord,
|
|
17
|
+
warn,
|
|
18
|
+
writeGeneratedPage
|
|
19
|
+
} from "./utils.js";
|
|
20
|
+
function optionalString(record, field, filePath) {
|
|
21
|
+
const value = record[field];
|
|
22
|
+
if (value === void 0) return void 0;
|
|
23
|
+
if (typeof value === "string") return value;
|
|
24
|
+
warn(filePath, `${field} must be a string; omitting it`);
|
|
25
|
+
return void 0;
|
|
26
|
+
}
|
|
27
|
+
function generateAgentsCategory(config) {
|
|
28
|
+
const agentsDir = path.join(config.codexDir, "agents");
|
|
29
|
+
const outputDir = path.join(config.docsDir, "codex-agents");
|
|
30
|
+
cleanDir(outputDir);
|
|
31
|
+
if (!fs.existsSync(agentsDir)) return [];
|
|
32
|
+
let files;
|
|
33
|
+
try {
|
|
34
|
+
files = fs.readdirSync(agentsDir, { withFileTypes: true }).filter((entry) => entry.isFile() && entry.name.endsWith(".toml")).map((entry) => entry.name).sort();
|
|
35
|
+
} catch (error) {
|
|
36
|
+
warn(agentsDir, `unable to read agents directory: ${String(error)}`);
|
|
37
|
+
return [];
|
|
38
|
+
}
|
|
39
|
+
if (files.length === 0) return [];
|
|
40
|
+
ensureDir(outputDir);
|
|
41
|
+
const items = [];
|
|
42
|
+
const emitted = /* @__PURE__ */ new Map();
|
|
43
|
+
for (const filename of files) {
|
|
44
|
+
const filePath = path.join(agentsDir, filename);
|
|
45
|
+
let source;
|
|
46
|
+
let parsed;
|
|
47
|
+
try {
|
|
48
|
+
source = fs.readFileSync(filePath, "utf8");
|
|
49
|
+
parsed = parse(source);
|
|
50
|
+
} catch (error) {
|
|
51
|
+
warn(filePath, `unable to parse TOML, skipping file: ${String(error)}`);
|
|
52
|
+
continue;
|
|
53
|
+
}
|
|
54
|
+
if (!isRecord(parsed)) {
|
|
55
|
+
warn(filePath, "parsed TOML root was not an object, skipping file");
|
|
56
|
+
continue;
|
|
57
|
+
}
|
|
58
|
+
const slug = filenameSlug(filename.replace(/\.toml$/, ""));
|
|
59
|
+
assertUniqueSlug("agent", slug, filename, emitted);
|
|
60
|
+
const name = optionalString(parsed, "name", filePath) ?? slug;
|
|
61
|
+
const description = optionalString(parsed, "description", filePath) ?? "";
|
|
62
|
+
const model = optionalString(parsed, "model", filePath);
|
|
63
|
+
const reasoning = optionalString(parsed, "model_reasoning_effort", filePath);
|
|
64
|
+
const sandbox = optionalString(parsed, "sandbox_mode", filePath);
|
|
65
|
+
const instructions = optionalString(parsed, "developer_instructions", filePath);
|
|
66
|
+
const badges = [
|
|
67
|
+
model ? `**Model:** ${escapeMarkdownTableCell(model)}` : "",
|
|
68
|
+
reasoning ? `**Reasoning effort:** ${escapeMarkdownTableCell(reasoning)}` : "",
|
|
69
|
+
sandbox ? `**Sandbox:** ${escapeMarkdownTableCell(sandbox)}` : ""
|
|
70
|
+
].filter(Boolean);
|
|
71
|
+
const sections = [badges.join("\n")];
|
|
72
|
+
if (instructions !== void 0) {
|
|
73
|
+
sections.push(
|
|
74
|
+
`## Developer instructions
|
|
75
|
+
|
|
76
|
+
${escapeForMdx(instructions.trim())}`
|
|
77
|
+
);
|
|
78
|
+
}
|
|
79
|
+
sections.push(`## Source
|
|
80
|
+
|
|
81
|
+
${renderCodeFence(source, "toml")}`);
|
|
82
|
+
writeGeneratedPage({
|
|
83
|
+
outputPath: path.join(outputDir, `${slug}.mdx`),
|
|
84
|
+
title: name,
|
|
85
|
+
description,
|
|
86
|
+
sidebarLabel: name,
|
|
87
|
+
body: sections.filter(Boolean).join("\n\n")
|
|
88
|
+
});
|
|
89
|
+
items.push({ filename, slug, name });
|
|
90
|
+
}
|
|
91
|
+
if (items.length > 0) {
|
|
92
|
+
writeCategoryIndex(
|
|
93
|
+
outputDir,
|
|
94
|
+
"Agents",
|
|
95
|
+
907,
|
|
96
|
+
"Custom subagents",
|
|
97
|
+
formatFrontmatterString
|
|
98
|
+
);
|
|
99
|
+
}
|
|
100
|
+
return items;
|
|
101
|
+
}
|
|
102
|
+
export {
|
|
103
|
+
generateAgentsCategory
|
|
104
|
+
};
|