Files
funplay-cocos-mcp/scripts/generate-tool-docs.js
T
2026-05-20 20:13:18 -07:00

165 lines
4.7 KiB
JavaScript

#!/usr/bin/env node
'use strict';
const fs = require('fs');
const path = require('path');
const { createToolRegistry } = require('../lib/tool-registry');
const ROOT = path.resolve(__dirname, '..');
const OUTPUT_PATH = path.join(ROOT, 'docs', 'TOOLS.md');
function createRegistry(profile) {
return createToolRegistry({
getRuntimeContext: () => ({
config: { toolProfile: profile },
projectPath: '/tmp/funplay-cocos-docs-project',
version: '0.0.0-docs',
}),
interactionLog: { add() {} },
runtimeLog: { add() {}, list: () => [], clear: () => 0 },
sceneBridge: { call: async () => ({ ok: true }) },
editorExecutor: async () => ({ ok: true }),
});
}
function buildToolModel() {
const fullRegistry = createRegistry('full');
const coreNames = new Set(createRegistry('core').listTools().map((tool) => tool.name));
const fullNames = new Set(fullRegistry.listTools().map((tool) => tool.name));
const catalog = fullRegistry.listToolCatalog()
.map((tool) => ({
name: tool.name,
category: tool.category || 'other',
profile: tool.profile || 'full',
enabledInCore: coreNames.has(tool.name),
enabledInFull: fullNames.has(tool.name),
readOnly: Boolean(tool.annotations && tool.annotations.readOnlyHint),
destructive: Boolean(tool.annotations && tool.annotations.destructiveHint),
description: normalizeDescription(tool.description),
}))
.sort((a, b) => a.category.localeCompare(b.category) || a.name.localeCompare(b.name));
return {
coreCount: coreNames.size,
fullCount: fullNames.size,
catalog,
};
}
function normalizeDescription(description) {
return String(description || '')
.replace(/\s+/g, ' ')
.trim();
}
function buildMarkdown(model) {
const categories = groupBy(model.catalog, (tool) => tool.category);
const lines = [
'# Tool Reference',
'',
'<!-- This file is generated by `npm run docs:generate`. Do not edit by hand. -->',
'',
`Generated from \`lib/tool-registry.js\`. The default \`core\` profile exposes ${model.coreCount} tools; the \`full\` profile exposes ${model.fullCount} tools.`,
'',
'## Profile Summary',
'',
'| Profile | Tool Count | Purpose |',
'|---|---:|---|',
`| \`core\` | ${model.coreCount} | Focused default surface for common editor automation. |`,
`| \`full\` | ${model.fullCount} | All built-in tools, including destructive and low-level helpers. |`,
'',
'## Core Tools',
'',
model.catalog
.filter((tool) => tool.enabledInCore)
.sort((a, b) => a.name.localeCompare(b.name))
.map((tool) => `\`${tool.name}\``)
.join(', '),
'',
'## Tools By Category',
'',
];
for (const category of Object.keys(categories).sort()) {
const tools = categories[category];
lines.push(`### ${titleCase(category)}`);
lines.push('');
lines.push('| Tool | Profiles | Access | Description |');
lines.push('|---|---|---|---|');
for (const tool of tools) {
lines.push(`| \`${tool.name}\` | ${profileLabel(tool)} | ${accessLabel(tool)} | ${escapeTableCell(tool.description)} |`);
}
lines.push('');
}
return `${lines.join('\n').replace(/\n{3,}/g, '\n\n')}\n`;
}
function profileLabel(tool) {
return tool.enabledInCore ? '`core`, `full`' : '`full`';
}
function accessLabel(tool) {
if (tool.readOnly) {
return 'read-only';
}
if (tool.destructive) {
return 'mutating';
}
return 'stateful';
}
function titleCase(value) {
return String(value)
.split(/[-_\s]+/)
.filter(Boolean)
.map((part) => `${part.charAt(0).toUpperCase()}${part.slice(1)}`)
.join(' ');
}
function escapeTableCell(value) {
return String(value || '')
.replace(/\|/g, '\\|')
.replace(/\n/g, '<br>');
}
function groupBy(values, getKey) {
return values.reduce((groups, value) => {
const key = getKey(value);
if (!groups[key]) {
groups[key] = [];
}
groups[key].push(value);
return groups;
}, {});
}
function writeDocs(markdown) {
const directory = path.dirname(OUTPUT_PATH);
if (!fs.existsSync(directory)) {
fs.mkdirSync(directory, { recursive: true });
}
fs.writeFileSync(OUTPUT_PATH, markdown, 'utf8');
}
function checkDocs(markdown) {
const existing = fs.existsSync(OUTPUT_PATH) ? fs.readFileSync(OUTPUT_PATH, 'utf8') : '';
if (existing !== markdown) {
console.error('docs/TOOLS.md is out of date. Run `npm run docs:generate`.');
process.exitCode = 1;
}
}
function main() {
const args = process.argv.slice(2);
const markdown = buildMarkdown(buildToolModel());
if (args.includes('--check')) {
checkDocs(markdown);
return;
}
writeDocs(markdown);
console.log(`Wrote ${path.relative(ROOT, OUTPUT_PATH)}`);
}
main();