import fs from 'node:fs'; import path from 'node:path'; import vm from 'node:vm'; import crypto from 'node:crypto'; import { WORKSHOP_OUTPUTS } from '../src/core/workshop-outputs.js'; // Local editorial build only. Existing sealed archives and online release metadata stay intact. const root = path.resolve('docs/workshop-final-local-20261110'); const manual = path.join(root, 'manual'); fs.mkdirSync(manual, { recursive: true }); const source = path.resolve('docs/workshop-manual-20261109'); const scope = { window: {} }; vm.runInNewContext(fs.readFileSync(path.join(source, 'content.js'), 'utf8'), scope); const data = scope.window.WORKSHOP_MANUAL_DATA; data.release.contentVersion = 'r11-local'; data.release.installVersion = 'r10'; data.release.deploymentStatus = 'local-only'; data.common = `使用康师傅产品创新工作坊 Skill,沿本组课题、已知约束与前序成果完成当前环节。先恢复已有信息,只追问会改变本节判断的一到三个业务条件;产品、规格、价格和评价等可查事实由你查证,不把研究方案设计交给员工填写。 先拆题并提出可检验的解释,再用已有材料核对,针对决定性缺口和反证补充研究、修订结论。具体说明依据属于谁、何种情境、哪款产品;用户需求、商品宣称、产品实测和研究设想分别使用。不以资料数量、漂亮图表或泛泛“继续调研”代替判断。 先回答本节业务问题,再展示能支持讨论的比较:改变什么、预期改善什么、保留什么、付出什么代价,哪项观察会改变选择。结论随本组目标、条件和证据而变;新条件出现时只修订受影响判断,保留原约束。没有足够依据选定方向时,给条件式建议与决定性未知,不代员工选择。 交齐下列实质成果。按 Skill 的“七类产出与成果导航”将已有内容定位到报告章节,不造空报告凑数。沿实际通道保存并核正文、版本、来源和归属;未接通则交可用本地成果与真实状态。末尾留简短接续摘要:本组问题与约束、关键判断、真实成果引用、未决项及下一节输入。本轮停在当前环节。`; data.steps.find(s => s.id === 'proposal').checks.push('负责人能沿七类导航找到具体正文;缺项、过程材料与独立方案保存状态据实标明。'); data.steps.find(s => s.id === 'voc').task += ' 有社媒原件时另保留其场景需求和来源;仅有电商评论时明确社媒缺项,不混用平台或推定品牌归属。'; fs.writeFileSync(path.join(manual, 'content.js'), 'window.WORKSHOP_MANUAL_DATA = ' + JSON.stringify(data, null, 2) + ';\n'); for (const name of ['manual.css', 'manual.js', 'index.html']) fs.copyFileSync(path.join(source, name), path.join(manual, name)); let index = fs.readFileSync(path.join(manual, 'index.html'), 'utf8'); index = index.replace('工作坊操作手册', '操作手册 · 本地定稿') .replace('康师傅产品创新工作坊 · 操作手册', '联合优化稿 r11-local · 当前线上安装资源 r10 · 本稿尚未发布'); fs.writeFileSync(path.join(manual, 'index.html'), index); let js = fs.readFileSync(path.join(manual, 'manual.js'), 'utf8'); js = js.replace("const storageKey = 'ksf-workshop-manual-r1-20261109';", "const storageKey = 'ksf-workshop-manual-r11-local-20261110';"); fs.writeFileSync(path.join(manual, 'manual.js'), js); const safeScript = value => value.replace(/<\/script/gi, '<\\/script'); const standalone = index.replace('', '') .replace('', '') .replace('', '') .replace('', '\n\n'); fs.writeFileSync(path.join(root, '康师傅工作坊操作手册-本地定稿.html'), standalone); const mapping = { 'D1-01': ['market'], 'D1-02': [], 'D1-03': ['market'], 'D1-04': ['market'], 'D1-N': ['evidence'], 'D2-01': [], 'D2-02': ['social', 'evidence'], 'D2-03': [], 'D2-04': ['opportunities'], 'D2-05': ['validation'], 'D2-06': ['validation'], 'D2-07': ['value'], 'D2-08': ['validation', 'proposal'], }; const prompt = s => s.prompt || `${data.common}\n\n本节要回答:${s.question}\n\n先复用:${s.inputs}\n执行细则:${s.skillReference}\n\n本节任务:\n${s.task}\n\n本节应交付:\n${s.deliverables.map(d => '• ' + d).join('\n')}`; const coverage = data.steps.map(s => ({ id: s.id, module: s.module || '', title: s.title, deliverables: s.deliverables, outputs: (mapping[s.module] || []).map(k => WORKSHOP_OUTPUTS.find(o => o.key === k)), prompt: prompt(s) })); fs.writeFileSync(path.join(root, 'prompts-and-outputs.json'), JSON.stringify({ contentVersion: 'r11-local', notice: '本地定稿;安装仍指向已核验r10;未发布新版资源', steps: coverage }, null, 2) + '\n'); const lines = ['# 手册、Skill与看板产出对应', '', '本地联合定稿r11-local,尚未部署。安装提示词继续指向已核验r10,不把未发布版本写成已可安装。', '', '| 环节 | 实质产出 | 七类导航 |', '|---|---|---|']; for (const s of coverage) lines.push(`| ${s.module || '准备'} ${s.title} | ${s.deliverables.join(';')} | ${s.outputs.map(o => o.name).join('、') || '按实际用途保留,不强行归类'} |`); lines.push('', '社媒章节仅在取得真实社媒材料时建立。七类表示成果导航,不等于七个独立保存请求或质量通过。配置卡、任务包、调研验收和矩阵仍保留在原任务中。', '', '所有研究提示词共用课题、约束、证据和判断规则;比较轴随输入而变。只在方向有歧义时反问1—3项,员工无需更换一整套提示词。'); fs.writeFileSync(path.join(root, '产出对应表.md'), lines.join('\n') + '\n'); const oldSamples = JSON.parse(fs.readFileSync('docs/workshop-skill-local-acceptance-20261109/sample-outputs.json', 'utf8')); // Editorial copies only. Preserve the original test archive, remove the superseded after-class plan from this classroom preview. const clean = value => { if (Array.isArray(value)) return value.filter(v => !(Array.isArray(v) && /49天/.test(String(v[0])))).map(clean); if (value && typeof value === 'object') return Object.fromEntries(Object.entries(value).map(([key, v]) => [key, clean(v)])); if (typeof value === 'string') return value.split('\n').filter(line => !/49天/.test(line)).join('\n'); return value; }; const review = { notice: oldSamples.notice, evidenceType: '主助手对构造材料的内容审阅,复用既有13节样张;不是独立模型、真实员工或宿主试跑', fixture: clean(oldSamples.fixture), outputs: clean(oldSamples.outputs) }; fs.writeFileSync(path.join(root, 'review-samples.json'), JSON.stringify(review, null, 2) + '\n'); fs.copyFileSync('docs/workshop-manual-20261109/同题与分组情境走查.md', path.join(root, '同题不同方向-情境审阅.md')); const esc = value => String(value ?? '').replace(/[&<>"']/g, c => ({ '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' }[c])); const cardSections = sections => sections.map(s => s.kind === 'table' || s.kind === 'matrix' ? `

${esc(s.title)}

${(s.data?.cols || []).map(c => '').join('')}${(s.data?.rows || []).map(row => '' + row.map(c => '').join('') + '').join('')}
' + esc(c) + '
' + esc(typeof c === 'object' ? JSON.stringify(c) : c) + '
` : s.text ? `

${esc(s.title || '')}

${esc(s.text)}

` : '').join(''); const showcase = `提示词与反馈 · 联合定稿预览

提示词与反馈 · 联合定稿预览

看每一节是否带来有用的产品认识、可讨论的取舍与下一项判别。

${esc(review.notice)} ${esc(review.evidenceType)}。新版Skill暂不发布。

打开完整操作手册 ↗

相同提示词,方向由本组条件决定

本组条件优先比较有帮助的反馈
办公室,只有热水,不能开火器具、冲泡、倒水与收拾;杯装与袋装的操作取舍杯装免另备碗不等于免倒水。先确认容器与排水条件,再比较具体改动;无操作测试不能声称更安全。
在家有灶,愿意煮,重视口感制备方法、口感依据、时间和清理代价需煮产品可进入比较;少步骤不作统一最高目标。宽面并不天然更优,仍需同条件体验依据。

方向来自输入、现有替代与证据。已知条件不重复问,只有会改变路线的歧义才问1—3项。

${review.outputs.map((o, i) => { const step = coverage.find(s => s.module === o.id);return `

构造样例 · ${o.id}

${esc(o.title)}

${esc(o.finding)}

对本组产品判断的意义

${esc(o.meaning)}

${cardSections(o.sections)}

下一项判别与接续

${esc(o.next)}

查看本节完整提示词
${esc(step.prompt)}
查看样例完整报告正文
${esc(o.rawText)}

正文中的local编号仅为历史隔离测试定位,未写入生产,不是可访问的正式成果链接。

`; }).join('')}
`; fs.writeFileSync(path.join(root, '提示词与高质反馈预览.html'), showcase); const skill = path.resolve('skills/ksf-workshop-runtime'); const sha = file => crypto.createHash('sha256').update(fs.readFileSync(file)).digest('hex'); const protectedPaths = ['runtime-lock.json', ...['ksf', 'ksf-secure', 'ksf-workshop', 'ksf-identity', 'ksf-identity-bridge', 'describe-business-schema'].map(n => 'scripts/' + n + '.mjs')]; const baseline = path.resolve('docs/workshop-onboarding-20261109-r5/package/skills/ksf-workshop-runtime'); const protectedFiles = protectedPaths.map(file => ({ file, sha256: sha(path.join(skill, file)), baselineSha256: sha(path.join(baseline, file)) })); if (protectedFiles.some(f => f.sha256 !== f.baselineSha256)) throw Error('Protected runtime changed from sealed r10'); const files = []; function scan(dir) { for (const entry of fs.readdirSync(dir, { withFileTypes: true })) { const full = path.join(dir, entry.name); if (entry.isDirectory()) scan(full); else if (entry.name !== 'FILES.sha256') files.push(path.relative(skill, full).replaceAll('\\', '/')); } } scan(skill); files.sort(); fs.writeFileSync(path.join(skill, 'FILES.sha256'), files.map(f => sha(path.join(skill, f)) + ' ' + f).join('\n') + '\n'); fs.writeFileSync(path.join(root, 'build.json'), JSON.stringify({ contentVersion: 'r11-local', deployment: 'deferred-by-user', chapters: data.steps.length, researchModules: data.steps.filter(s => s.module).length, skillPublicFiles: files.length + 1, protectedFiles, runtimeFilesUnchanged: protectedFiles.every(f => f.sha256 === f.baselineSha256), evidenceType: 'editorial-review-and-local-tests; not actual host or employee trial', productionWrites: 0 }, null, 2) + '\n'); console.log(JSON.stringify({ root, chapters: data.steps.length, skillFiles: files.length + 1, protectedFilesUnchanged: true }));