import fs from 'node:fs'; import path from 'node:path'; import crypto from 'node:crypto'; import vm from 'node:vm'; import { fileURLToPath } from 'node:url'; const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..'); const evidence = path.join(root, 'docs/workshop-v3-alignment-20261112'); const target = path.join(root, 'docs/workshop-quality-r13-20261110/final-康师傅工作坊操作手册.html'); const promptsFile = path.join(path.dirname(target), 'manual/workshop-dialogue-prompts.json'); const sha = bytes => crypto.createHash('sha256').update(bytes).digest('hex'); const source = fs.readFileSync(path.join(evidence, 'source-manual-v3.html')); const sourceSha256 = sha(source); if (sourceSha256 !== 'e8ed9dbd979c17f5caa88fb820f64f94a2e54518f810d7482b6b82f39d88041d') throw Error('V3_SOURCE_CHANGED'); const url = 'https://s3.fmode.cn/user/K0xKEZANOG/kangshifu/manual-v3/index.html'; const sourceText = source.toString('utf8'); const promptBlock = sourceText.match(/window\.PROMPTS\s*=\s*(\{[\s\S]*?\n\});/); if (!promptBlock) throw Error('V3_PROMPTS_NOT_FOUND'); const prompts = vm.runInNewContext('(' + promptBlock[1] + ')', {}, { timeout: 1000 }); const archived = JSON.parse(fs.readFileSync(path.join(evidence, 'source-prompts-v3.json'), 'utf8')); if (JSON.stringify(prompts) !== JSON.stringify(archived.prompts) || Object.keys(prompts).length !== 13) throw Error('V3_PROMPTS_DIFFER'); // Preserve in-page anchors; resolve only the overview links for the local final filename. const linked = sourceText.replaceAll('href="../overview/index.html"', 'href="https://s3.fmode.cn/user/K0xKEZANOG/kangshifu/overview/index.html"'); if (linked === sourceText) throw Error('OVERVIEW_LINK_NOT_FOUND'); // Some source tables lack the existing scroll wrapper; preserve their content and columns. const responsiveTables = `\n`; const html = linked.replace('', responsiveTables + ''); const backup = path.join(evidence, 'retired-manual-before-v3'); fs.mkdirSync(backup, { recursive: true }); for (const file of [target, promptsFile]) { const name = path.join(backup, path.basename(file)); if (!fs.existsSync(name)) fs.copyFileSync(file, name); } fs.writeFileSync(target, html); fs.writeFileSync(promptsFile, JSON.stringify({ schema: 'ksf.workshop-prompts.v3', source: url, sourceSha256, prompts }, null, 2) + '\n'); const receipt = { ok: true, source: url, sourceSha256, finalManualSha256: sha(Buffer.from(html)), finalManualBytes: Buffer.byteLength(html), promptCount: 13, teachingContentChanged: false, adjustment: 'absolute overview links and scroll wrappers for unwrapped tables; all teaching text and prompts preserved', oldManualArchived: true, deployed: false }; fs.writeFileSync(path.join(evidence, 'manual-sync.json'), JSON.stringify(receipt, null, 2) + '\n'); console.log(JSON.stringify(receipt, null, 2));