| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101 |
- 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('<strong>工作坊操作手册</strong>', '<strong>操作手册 · 本地定稿</strong>')
- .replace('康师傅产品创新工作坊 · 操作手册</span>', '联合优化稿 r11-local · 当前线上安装资源 r10 · 本稿尚未发布</span>');
- 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('<link rel="stylesheet" href="manual.css">', '<style>' + fs.readFileSync(path.join(manual, 'manual.css'), 'utf8') + '</style>')
- .replace('<script src="content.js" defer></script>', '')
- .replace('<script src="manual.js" defer></script>', '')
- .replace('</body>', '<script>' + safeScript(fs.readFileSync(path.join(manual, 'content.js'), 'utf8')) + '</script>\n<script>' + safeScript(js) + '</script>\n</body>');
- 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'
- ? `<h3>${esc(s.title)}</h3><div class="table-wrap"><table><thead><tr>${(s.data?.cols || []).map(c => '<th>' + esc(c) + '</th>').join('')}</tr></thead><tbody>${(s.data?.rows || []).map(row => '<tr>' + row.map(c => '<td>' + esc(typeof c === 'object' ? JSON.stringify(c) : c) + '</td>').join('') + '</tr>').join('')}</tbody></table></div>`
- : s.text ? `<h3>${esc(s.title || '')}</h3><p>${esc(s.text)}</p>` : '').join('');
- const showcase = `<!doctype html><html lang="zh-CN"><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>提示词与反馈 · 联合定稿预览</title><style>
- *{box-sizing:border-box}body{margin:0;font:15px/1.8 "Microsoft YaHei",system-ui;color:#223242;background:#f3f5f8}main{max-width:1100px;margin:auto;padding:28px 22px}header{padding:24px 0}h1{font-size:26px;margin:0 0 10px}h2{font-size:21px;margin:8px 0 18px}h3{font-size:16px;margin:20px 0 8px}p{margin:8px 0}a{color:#a51f30}select{width:100%;padding:12px;border:1px solid #ccd4dc;border-radius:8px;font:inherit;background:white}article{display:none;background:#fff;border:1px solid #dfe4e9;border-radius:12px;padding:26px;margin-top:20px;min-width:0}article.active{display:block}.note{font-size:13px;color:#64748b}.finding{padding:16px;background:#edf5f2;border-left:3px solid #2b7b63;font-size:18px;font-weight:600}.table-wrap{overflow:auto}table{border-collapse:collapse;min-width:580px;width:100%;font-size:13px}td,th{padding:10px;text-align:left;border-bottom:1px solid #e3e8ed;vertical-align:top}th{background:#f3f5f8}details{margin-top:20px}summary{cursor:pointer;color:#a51f30}pre{white-space:pre-wrap;overflow-wrap:anywhere;font:13px/1.9 "Microsoft YaHei",system-ui;background:#f4f6f8;padding:16px;border-radius:8px}.scenario{background:#fff;border:1px solid #dfe4e9;border-radius:12px;padding:22px;margin:20px 0}@media(max-width:650px){main{padding:16px 12px}article{padding:18px}h1{font-size:22px}}
- </style><main><header><h1>提示词与反馈 · 联合定稿预览</h1><p>看每一节是否带来有用的产品认识、可讨论的取舍与下一项判别。</p><p class="note">${esc(review.notice)} ${esc(review.evidenceType)}。新版Skill暂不发布。</p><a href="康师傅工作坊操作手册-本地定稿.html">打开完整操作手册 ↗</a></header>
- <section class="scenario"><h2>相同提示词,方向由本组条件决定</h2><div class="table-wrap"><table><thead><tr><th>本组条件</th><th>优先比较</th><th>有帮助的反馈</th></tr></thead><tbody><tr><td>办公室,只有热水,不能开火</td><td>器具、冲泡、倒水与收拾;杯装与袋装的操作取舍</td><td>杯装免另备碗不等于免倒水。先确认容器与排水条件,再比较具体改动;无操作测试不能声称更安全。</td></tr><tr><td>在家有灶,愿意煮,重视口感</td><td>制备方法、口感依据、时间和清理代价</td><td>需煮产品可进入比较;少步骤不作统一最高目标。宽面并不天然更优,仍需同条件体验依据。</td></tr></tbody></table></div><p class="note">方向来自输入、现有替代与证据。已知条件不重复问,只有会改变路线的歧义才问1—3项。</p></section>
- <label for="module">查看环节与反馈</label><select id="module">${coverage.filter(s => s.module).map(s => `<option value="${s.module}">${s.module} · ${esc(s.title)}</option>`).join('')}</select>
- ${review.outputs.map((o, i) => { const step = coverage.find(s => s.module === o.id);return `<article id="${o.id}" class="${i === 0 ? 'active' : ''}"><p class="note">构造样例 · ${o.id}</p><h2>${esc(o.title)}</h2><div class="finding">${esc(o.finding)}</div><h3>对本组产品判断的意义</h3><p>${esc(o.meaning)}</p>${cardSections(o.sections)}<h3>下一项判别与接续</h3><p>${esc(o.next)}</p><details><summary>查看本节完整提示词</summary><pre>${esc(step.prompt)}</pre></details><details><summary>查看样例完整报告正文</summary><pre>${esc(o.rawText)}</pre></details><p class="note">正文中的local编号仅为历史隔离测试定位,未写入生产,不是可访问的正式成果链接。</p></article>`; }).join('')}
- </main><script>document.getElementById('module').addEventListener('change',e=>document.querySelectorAll('article').forEach(a=>a.classList.toggle('active',a.id===e.target.value)));</script></html>`;
- 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 }));
|