#!/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', '', '', '', `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, '
'); } 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();