#!/usr/bin/env node /** 汇总 parts-r/00..13 为《ClassHelper灵动岛重构-给ChatGPT的完整提示词.md》 */ import { readdirSync, readFileSync, writeFileSync, mkdirSync, copyFileSync } from 'node:fs'; import { join, dirname } from 'node:path'; import { fileURLToPath } from 'node:url'; const root = join(dirname(fileURLToPath(import.meta.url)), '..'); const partsDir = join(root, 'parts-r'); const outFile = join(root, 'ClassHelper灵动岛重构-给ChatGPT的完整提示词.md'); const files = readdirSync(partsDir) .filter((f) => /^\d{2}-.+\.md$/.test(f)) .sort((a, b) => a.localeCompare(b, 'en')); if (files.length !== 14) console.warn(`警告:parts-r 文件数 ${files.length},预期 14`); if (!files.some((f) => f.startsWith('00-'))) throw new Error('缺少 parts-r/00-preamble.md'); const sections = []; const toc = []; const stats = []; let preamble = ''; for (const file of files) { const content = readFileSync(join(partsDir, file), 'utf8').replace(/\r\n/g, '\n').trimEnd(); if (file.startsWith('00-')) { preamble = content; for (const line of content.split('\n')) if (/^## /.test(line)) toc.push(`- ${line.replace(/^##\s+/, '').trim()}`); continue; } for (const line of content.split('\n')) { if (/^## /.test(line)) toc.push(`- ${line.replace(/^##\s+/, '').trim()}`); else if (/^### /.test(line)) toc.push(` - ${line.replace(/^###\s+/, '').trim()}`); } const chars = [...content.replace(/\s/g, '')].length; const cjk = (content.match(/[\u4e00-\u9fff]/g) || []).length; stats.push({ file, chars, cjk, lines: content.split('\n').length }); sections.push(content); } if (!preamble.includes('')) throw new Error('preamble 缺少 TOC 占位符'); const tocBlock = ['## 目录', '', ...toc, ''].join('\n'); const body = [preamble.replace('', tocBlock), ...sections].join('\n\n---\n\n'); writeFileSync(outFile, body + '\n', 'utf8'); const totalChars = [...body.replace(/\s/g, '')].length; const totalCjk = (body.match(/[\u4e00-\u9fff]/g) || []).length; console.log(`输出:${outFile}`); console.log(`去空白字符:${totalChars};汉字:${totalCjk};行数:${body.split('\n').length}`); console.log(`粗略 token:${Math.round(totalCjk * 0.9 + (totalChars - totalCjk) * 0.3)}`); for (const s of stats) console.log(` ${s.file.padEnd(26)} ${String(s.chars).padStart(7)} 字符 ${String(s.cjk).padStart(6)} 汉字`); console.log(` 00-preamble.md ${String([...preamble.replace(/\s/g, '')].length).padStart(7)} 字符 ${String((preamble.match(/[\u4e00-\u9fff]/g) || []).length).padStart(6)} 汉字`); if (process.argv.includes('--split')) { const outDir = join(root, 'out'); const sectionsDir = join(outDir, 'refactor-sections'); mkdirSync(sectionsDir, { recursive: true }); const volumes = [ { name: 'vol1-任务声明与现状基线(00-02)', nums: [0, 1, 2] }, { name: 'vol2-基准语义与文件数据改造(03-05)', nums: [3, 4, 5] }, { name: 'vol3-tokens与组件规格(06-07)', nums: [6, 7] }, { name: 'vol4-消息课程与编辑器(08-09)', nums: [8, 9] }, { name: 'vol5-动画兼容与验收里程碑(10-13)', nums: [10, 11, 12, 13] }, ]; for (const v of volumes) { const chunks = []; if (v.nums.includes(0)) chunks.push(preamble.replace('', tocBlock)); for (const n of v.nums) { if (n === 0) continue; const f = files.find((x) => Number(x.slice(0, 2)) === n); if (f) chunks.push(readFileSync(join(partsDir, f), 'utf8').replace(/\r\n/g, '\n').trimEnd()); else console.warn(`警告:缺少 part ${n}`); } const p = join(outDir, `${v.name}.md`); writeFileSync(p, chunks.join('\n\n---\n\n') + '\n', 'utf8'); const c = readFileSync(p, 'utf8'); console.log(`分卷:${p}(${[...c.replace(/\s/g, '')].length} 字符)`); } for (const f of files) copyFileSync(join(partsDir, f), join(sectionsDir, f)); console.log(`逐节文件:${sectionsDir}(${files.length} 个)`); }