"""Sync only the current handbook's installation step to a verified publication.""" import hashlib import json from pathlib import Path ROOT = Path(__file__).resolve().parents[1] OUT = ROOT / 'docs/workshop-quality-r13-20261110' PUBLICATION = ROOT / 'docs/ksf-workshop-skill-20261110-r13-r2' KEY_EVIDENCE_FEEDBACK = '''反馈关键分析依据信息:涉及数据判断时,只呈现直接支撑结论或会改变建议的来源、分析对象与时间范围、实际有效样本,并用代表原话、产品对照或关键统计解释依据与判断的关系;比例注明n/N及口径,复用资料与本轮新增采集分开,关键反例和适用限制紧邻结论。未取得的依据如实标明,不补造数字。完整采集明细、逐条处理记录和工具日志按Skill保留供追溯,无需在对话中展开;本节核心报告与成果仍须完整交付。''' def sha(path): return hashlib.sha256(path.read_bytes()).hexdigest() def dump(path, value): path.write_text(json.dumps(value, ensure_ascii=False, indent=2) + '\n', encoding='utf8') def configure_install(data): if data['release'].get('entryMode') == 'fmode-enterprise-agent': raise ValueError('This handbook uses website onboarding; attendee installation is retired.') receipt = json.loads((PUBLICATION / 'online-release-receipt.json').read_bytes()) assert receipt['ok'] and receipt['publicResourcesPublished'] assert receipt['workshopVersion'] == 'r13' archive = receipt['archive'] assert sha(PUBLICATION / 'publish' / archive['path']) == archive['sha256'] prompt = (PUBLICATION / '员工安装正文.txt').read_text('utf8').strip() assert receipt['guideUrl'] in prompt data['release'].update( version='r13', installVersion='r13', guide=receipt['guideUrl'], installReleaseId=receipt['releaseId'], deploymentStatus='manual-local-skill-published') step = next(s for s in data['steps'] if s['id'] == 'install') step['prompt'] = prompt step['deliverables'][0] = '工作坊Skill正式r13的安装或核验结果(45个公开文件)' return receipt def configure_website_entry(data): data['release'].update(entryMode='fmode-enterprise-agent', skillManagedBy='organizer') group = next(g for g in data['groups'] if g['id'] == 'prepare') group.update(title='进入工作坊,明确本组课题', description='登录企业网站,在本组独立会话中使用统一的工作坊智能体。') step = next(s for s in data['steps'] if s['id'] == 'install') step.clear() step.update( id='install', group='prepare', kind='introduction', number='00', title='使用说明', introduction='本手册采用 fmode 企业网站的统一智能体。工作坊 Skill 和相关工具由组织方统一配置;学员无需安装 Skill,也无需复制安装提示词。', usage=[ {'title': '登录并进入智能体', 'text': '打开组织方提供的 fmode 企业网站入口,登录后进入指定的工作坊智能体。'}, {'title': '保留本组独立会话', 'text': '各组使用同一个智能体入口,每组保留独立会话。先说明小组名称和研究课题,后续在本组会话中接续,避免混入其他小组的成果。'}, {'title': '从 01 开始逐节推进', 'text': '复制各节提示词发送给智能体,回答必要的业务问题,检查本节产出后再进入下一节。不同小组按各自课题与证据展开研究。'}, {'title': '核对报告与成果', 'text': '每节检查完整报告和关键分析依据;保存后核对成果编号、回读内容和本组归属。若保存未成功,让智能体明确说明。'}, {'title': '确认方案后生成示意图', 'text': '最终方案经本组明确确认后,再由智能体调用 image 技能(image 2.6)生成示意图,并与对应方案一起保存。'} ], checks=['已找到组织方指定的工作坊智能体。', '本组使用独立会话,课题和小组名称清楚。'], prompt='') for index, item in enumerate(data['steps']): item['number'] = f'{index:02d}' def configure_evidence_feedback(data): if KEY_EVIDENCE_FEEDBACK not in data['common']: data['common'] += '\n\n' + KEY_EVIDENCE_FEEDBACK for step in data['steps']: if step['id'] != 'install' and step.get('prompt') and KEY_EVIDENCE_FEEDBACK not in step['prompt']: step['prompt'] += '\n\n' + KEY_EVIDENCE_FEEDBACK def render_manual(data, prompts): manual = OUT / 'manual' content = 'window.WORKSHOP_MANUAL_DATA = ' + json.dumps(data, ensure_ascii=False, indent=2) + ';\n' (manual / 'content.js').write_text(content, encoding='utf8') js = (manual / 'manual.js').read_text('utf8') start = js.index(' const promptMap = ') end = js.index('\n', start) mapping = {s['id']: s['fullPrompt'] for s in prompts['steps']} js = js[:start] + ' const promptMap = ' + json.dumps(mapping, ensure_ascii=False) + ';' + js[end:] (manual / 'manual.js').write_text(js, encoding='utf8') standalone_js = js.replace('href="../${escape(step.example)}"', 'href="${escape(step.example)}"').replace('href="../参考学习与类型对应.html"', 'href="参考学习与类型对应.html"') h = (manual / 'index.html').read_text('utf8') css = (manual / 'manual.css').read_text('utf8') standalone = h.replace('', '').replace('', '').replace('', '') standalone = standalone.replace('href="../index.html"', 'href="index.html"').replace('href="../案例报告合集.html"', 'href="案例报告合集.html"') safe = lambda text: text.replace('', '') html_path = OUT / '康师傅工作坊操作手册.html' html_path.write_text(standalone, encoding='utf8') return html_path def update_key_evidence(): source = OUT / 'manual-source.json' archive_path = OUT / 'prompts-and-outputs.json' data = json.loads(source.read_bytes()) prompts = json.loads(archive_path.read_bytes()) before = json.loads(json.dumps(data)) old_common = data['common'] skill = ROOT / 'skills/ksf-workshop-runtime' skill_hashes = {p.relative_to(skill).as_posix(): sha(p) for p in skill.rglob('*') if p.is_file()} configure_evidence_feedback(data) for step, saved in zip(data['steps'], prompts['steps'], strict=True): assert step['id'] == saved['id'] if step['id'] == 'install': assert step == before['steps'][0] assert saved['fullPrompt'] == step['prompt'] continue if step.get('prompt'): saved['prompt'] = step['prompt'] saved['fullPrompt'] = step['prompt'] else: assert saved['fullPrompt'].startswith(old_common) saved['fullPrompt'] = data['common'] + saved['fullPrompt'][len(old_common):] assert saved['fullPrompt'].count(KEY_EVIDENCE_FEEDBACK) == 1 for old, new in zip(before['steps'], data['steps'], strict=True): assert {k: v for k, v in old.items() if k != 'prompt'} == {k: v for k, v in new.items() if k != 'prompt'} dump(source, data) dump(archive_path, prompts) html_path = render_manual(data, prompts) assert skill_hashes == {p.relative_to(skill).as_posix(): sha(p) for p in skill.rglob('*') if p.is_file()} manifest_path = OUT / '本地发行清单.json' manifest = json.loads(manifest_path.read_bytes()) manifest.update(manualSha256=sha(html_path), feedbackRule='key-analysis-evidence') dump(manifest_path, manifest) update = dict(ok=True, scope='research-feedback-prompts-only', feedbackRule=KEY_EVIDENCE_FEEDBACK, researchPromptsUpdated=14, installPromptUnchanged=True, coreDeliverablesUnchanged=True, skillFilesUnchanged=len(skill_hashes), manualPublished=False, productionWrites=0) receipt_path = OUT / '关键分析依据反馈更新核验.json' dump(receipt_path, update) validation_path = OUT / '交付文件校验.json' validation = json.loads(validation_path.read_bytes()) validation['keyEvidenceFeedbackUpdate'] = update for name in ['manual-source.json', 'prompts-and-outputs.json', 'manual/content.js', 'manual/manual.js', html_path.name, manifest_path.name, receipt_path.name]: validation['files'][name] = sha(OUT / name) dump(validation_path, validation) print(json.dumps(update, ensure_ascii=False)) def main(): source = OUT / 'manual-source.json' archive_path = OUT / 'prompts-and-outputs.json' data = json.loads(source.read_bytes()) prompts = json.loads(archive_path.read_bytes()) before = {s['id']: s for s in prompts['steps'] if s['id'] != 'install'} before_sha = hashlib.sha256(json.dumps(before, ensure_ascii=False, sort_keys=True).encode()).hexdigest() receipt = configure_install(data) install = next(s for s in data['steps'] if s['id'] == 'install') for i, step in enumerate(prompts['steps']): if step['id'] == 'install': prompts['steps'][i] = {**install, 'fullPrompt': install['prompt']} assert before == {s['id']: s for s in prompts['steps'] if s['id'] != 'install'} dump(source, data) dump(archive_path, prompts) html_path = render_manual(data, prompts) manifest_path = OUT / '本地发行清单.json' manifest = json.loads(manifest_path.read_bytes()) manifest.update(manualSha256=sha(html_path), bundledSkillVersion='r13-local-quality', currentInstallVersion='r13', currentInstallReleaseId=receipt['releaseId'], currentInstallGuide=receipt['guideUrl']) dump(manifest_path, manifest) update = dict(ok=True, scope='installation-step-only', installVersion='r13', releaseId=receipt['releaseId'], guideUrl=receipt['guideUrl'], nonInstallStepsUnchanged=len(before), nonInstallStepsSha256=before_sha, installPromptSha256=hashlib.sha256(install['prompt'].encode()).hexdigest(), manualPublished=False, productionWrites=0) dump(OUT / '安装提示词更新核验.json', update) validation_path = OUT / '交付文件校验.json' validation = json.loads(validation_path.read_bytes()) validation['installationPromptUpdate'] = update for name in ['manual-source.json', 'prompts-and-outputs.json', 'manual/content.js', 'manual/manual.js', html_path.name, manifest_path.name, '安装提示词更新核验.json']: validation['files'][name] = sha(OUT / name) dump(validation_path, validation) print(json.dumps(update, ensure_ascii=False)) if __name__ == '__main__': import argparse parser = argparse.ArgumentParser(description=__doc__) parser.add_argument('--key-evidence', action='store_true') args = parser.parse_args() update_key_evidence() if args.key_evidence else main()