prepare-workshop-final-r12.py 9.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110
  1. """Freeze the local paired manual/Skill. Does not publish or modify host installations."""
  2. import hashlib
  3. import json
  4. import shutil
  5. import zipfile
  6. from pathlib import Path
  7. ROOT = Path(__file__).resolve().parents[1]
  8. OUT = ROOT / 'docs/workshop-validated-final-20261110'
  9. SOURCE = ROOT / 'skills/ksf-workshop-runtime'
  10. OLD = ROOT / 'work/workshop-self-run-20261110/baseline-source-skill'
  11. PKG_OLD = ROOT / 'work/workshop-self-run-20261110/install/package'
  12. VERSION = 'r12-local-final'
  13. sha = lambda p: hashlib.sha256(p.read_bytes()).hexdigest()
  14. dump = lambda p, x: p.write_text(json.dumps(x, ensure_ascii=False, indent=2) + '\n', encoding='utf-8')
  15. def freeze():
  16. OUT.mkdir(exist_ok=True)
  17. names = [line.split(' ', 1)[1] for line in (OLD / 'FILES.sha256').read_text('utf-8').splitlines()]
  18. protected = [n for n in names if n.startswith('scripts/') or n == 'runtime-lock.json']
  19. for n in protected:
  20. assert sha(SOURCE/n) == sha(OLD/n), n
  21. (SOURCE/'FILES.sha256').write_text(''.join(sha(SOURCE/n)+' '+n+'\n' for n in sorted(names)), encoding='utf-8')
  22. names = sorted(names + ['FILES.sha256'])
  23. pkg = OUT/'installation'
  24. pkg.mkdir(exist_ok=True)
  25. skill = pkg/'skills/ksf-workshop-runtime'
  26. for n in names:
  27. p = skill/n; p.parent.mkdir(parents=True, exist_ok=True); shutil.copyfile(SOURCE/n, p)
  28. for n in ['install.py', 'check-voc.cjs']:
  29. shutil.copyfile(PKG_OLD/n, pkg/n)
  30. meta = json.loads((PKG_OLD/'release.json').read_bytes())
  31. meta['releaseId'] = 'ksf-workshop-local-final-20261110-r12'
  32. meta['workshop']['version'] = VERSION
  33. meta['workshop']['files'] = {n: sha(skill/n) for n in names}
  34. meta['upgradeFrom'] = {'r11-local-exact': {n: sha(OLD/n) for n in names}}
  35. meta['localOnly'] = True
  36. dump(pkg/'release.json', meta)
  37. (pkg/'FILES.sha256').write_text(''.join(sha(p)+' '+p.relative_to(pkg).as_posix()+'\n' for p in sorted(pkg.rglob('*')) if p.is_file() and p!=pkg/'FILES.sha256'), encoding='utf-8')
  38. install_md = f'''# 本地定稿资源 · {VERSION}
  39. 本机可读资源:`{pkg.as_posix()}`。包内install.py与已核验r10发行相同,业务运行脚本未修改;本地发行清单更新为本次定稿。校验FILES.sha256后运行安装器。只升级release.json明确支持且逐文件一致的r11-local基线;r7/r8/r9/r10、未知或本地改动版本停止并报告,不强制覆盖。保留本机身份/授权/连接及其他技能,不恢复旧内部研究Skill。
  40. 先执行只读plan:
  41. ```text
  42. python -B install.py --skills-dir <宿主实际支持的用户技能根> --out <本地回执目录>
  43. ```
  44. plan明确为create/verify-only,或匹配受支持基线的upgrade后,按包内参数正常安装/备份升级。`--prepare-runtime`仅准备并检查VOC本地运行,不表示注册为原生工具或已开通真实访问。真实宿主目录未知时报告缺项,不安装进受管内置目录。自演可以使用明确标注的隔离目录,不能称为宿主加载成功。
  45. 这套资源仅在本地定稿、核验;没有线上r12下载地址,也没有要求学员上传附件。后续正式发行时替换安装说明的资源地址并重新核验,不把旧线上r10称为r12。企业账套留待后续接入;现有身份和授权机制保持,资料读取/业务保存按实际能力分别测试。
  46. '''
  47. (OUT/'INSTALL-本地定稿.md').write_text(install_md, encoding='utf-8')
  48. data = json.loads((ROOT/'docs/workshop-self-run-20261110/manual-source.json').read_bytes())
  49. data['release'].update(version=VERSION, contentVersion=VERSION, installVersion=VERSION,
  50. guide=(OUT/'INSTALL-本地定稿.md').as_uri(), deploymentStatus='local-only')
  51. data['common'] = '''使用康师傅产品创新工作坊 Skill,沿同一课题、已知条件和前序成果完成当前环节。先恢复已有信息;只反问会改变本节判断且必须由业务方回答的1—3项,已答内容不再问。产品事实、比较方法、补证和计算由你完成。
  52. 先回答本节问题:本节新增或修订了什么,对产品选择有什么影响?以具体产品、原话和适用条件解释得失;研究线索、产品设想、当前建议分别使用。关键缺口先作能执行的核查;需要真人/样品的,给可操作的比较与继续/修改/停止条件,不假装已验证。
  53. 交付下列实质成果,完整依据可回查;说明拟改什么、保留什么、预期收益与代价。按Skill定位七类成果章节,不为凑数量建空报告。沿实际能力保存;未接通则交同等质量的本地成果。末尾更新简短课题摘要、成果位置和下一节输入;本轮停在当前环节。'''
  54. by = {s['id']: s for s in data['steps']}
  55. by['install']['prompt'] = f'''请安装或核验康师傅产品创新工作坊本地定稿 {VERSION}。先读取本机已提供的固定资源说明:
  56. {(OUT/'INSTALL-本地定稿.md').as_posix()}
  57. 按说明核验配套installation/FILES.sha256与release.json,确认工作坊版本为{VERSION}、唯一内部入口为ksf-workshop-runtime,VOC沿清单的官方0.3.42。由你识别宿主实际支持的用户技能目录;只升级清单支持且哈希一致的基线,未知/改动版本报告差异停止覆盖,保留身份、授权、连接和其他技能,不另装旧内部研究入口,不索要数据库密钥。资源不可读时如实报告,不用旧线上r10冒充本次定稿。
  58. 分别实际核文件安装、宿主加载、VOC本地执行/原生注册、资料读取与成果保存。不改平台校验,不新建企业账套或声称已实名。仅初始化,不新增采集、不创建会话/项目/报告。用简短业务语言说现在能做什么;隔离目录自演与真实宿主安装分开。'''
  59. by['install']['deliverables'][0] = f'工作坊 Skill {VERSION} 的本地安装或核验结果'
  60. by['install']['checks'][1] = '仅升级清单支持且逐文件匹配的基线;本机身份、授权、连接保留。'
  61. by['topic']['prompt'] += '\n\n将业务方给定、资料观察、研究假设分别记录。摘要用于后续接续,不要求我反复填条件表;不要把你的自设限制变成我的业务约束。'
  62. by['tasks']['task'] += ' 对当前关键缺口优先给能实际执行的最小核查,执行状态与研究建议分开。'
  63. by['opportunities']['task'] += ' 只有观察任务的保留为任务;尚未成立的产品关系可列待证设想,不把三条研究线索报成三个已验证商业机会。'
  64. by['feasibility']['task'] += ' 无真实收入和报价时,主表给变量与临界条件;纯算术示例放附录,不进入企业盈利结论或排序。'
  65. by['proposal']['task'] += ' 让评审先看到当前建议、现有方案与拟改方案的差异,以及会改变建议的关键结果;完整过程与方法后置。'
  66. manual = OUT/'manual'; manual.mkdir(exist_ok=True)
  67. (manual/'content.js').write_text('window.WORKSHOP_MANUAL_DATA = '+json.dumps(data, ensure_ascii=False, indent=2)+';\n', encoding='utf-8')
  68. src = ROOT/'docs/workshop-final-local-20261110/manual'
  69. for n in ['index.html', 'manual.css', 'manual.js']:
  70. shutil.copyfile(src/n, manual/n)
  71. html = (manual/'index.html').read_text('utf-8').replace('操作手册 · 本地定稿','操作手册 · r12 定稿').replace('联合优化稿 r11-local · 当前线上安装资源 r10 · 本稿尚未发布','r12 本地定稿 · Skill与手册配套 · 企业账套待后续接入')
  72. (manual/'index.html').write_text(html, encoding='utf-8')
  73. js = (manual/'manual.js').read_text('utf-8').replace('ksf-workshop-manual-r11-local-20261110','ksf-workshop-manual-r12-local-final-20261110')
  74. (manual/'manual.js').write_text(js, encoding='utf-8')
  75. # Keep the compact, one-route design and always-visible full prompts.
  76. css = (manual/'manual.css').read_text('utf-8')
  77. css += '\n/* Legibility within the established compact layout. */\n.prompt-text{font-size:14px;line-height:1.85}.quality li{font-size:13px}@media(max-width:540px){.prompt-text{font-size:14px}.quality li{font-size:12px}}\n'
  78. (manual/'manual.css').write_text(css, encoding='utf-8')
  79. safe = lambda s: s.replace('</script', '<\\/script')
  80. standalone = html.replace('<link rel="stylesheet" href="manual.css">','<style>'+css+'</style>').replace('<script src="content.js" defer></script>','').replace('<script src="manual.js" defer></script>','')
  81. standalone = standalone.replace('</body>','<script>'+safe((manual/'content.js').read_text('utf-8'))+'</script><script>'+safe(js)+'</script></body>')
  82. (OUT/'康师傅工作坊操作手册-r12本地定稿.html').write_text(standalone, encoding='utf-8')
  83. dump(OUT/'manual-source.json', data)
  84. prompts = [{**s, 'fullPrompt': s.get('prompt') or f"{data['common']}\n\n本节要回答:{s['question']}\n\n先复用:{s['inputs']}\n执行细则:{s['skillReference']}\n\n本节任务:\n{s['task']}\n\n本节应交付:\n"+'\n'.join('• '+d for d in s['deliverables'])} for s in data['steps']]
  85. dump(OUT/'prompts-and-outputs.json', {'version': VERSION, 'steps': prompts})
  86. with zipfile.ZipFile(OUT/'康师傅工作坊Skill-r12本地定稿.zip', 'w', zipfile.ZIP_DEFLATED) as z:
  87. for p in sorted(pkg.rglob('*')):
  88. if p.is_file(): z.write(p, p.relative_to(pkg).as_posix())
  89. dump(OUT/'定稿清单.json', {'version':VERSION, 'deployment':'local-only', 'manualSteps':15, 'researchSteps':13,
  90. 'skillPublicFiles':len(names), 'skillEntrySha256':sha(skill/'SKILL.md'),
  91. 'manualSha256':sha(OUT/'康师傅工作坊操作手册-r12本地定稿.html'),
  92. 'protectedFiles':[{'file':n,'sha256':sha(skill/n),'unchanged':sha(skill/n)==sha(OLD/n)} for n in protected],
  93. 'upgradeSupport':'exact r11-local baseline only; other installed versions stop safely',
  94. 'installerUnchanged':sha(pkg/'install.py')==sha(PKG_OLD/'install.py'), 'productionWrites':0})
  95. print(json.dumps({'ok':True,'version':VERSION,'files':len(names),'root':str(OUT)},ensure_ascii=False))
  96. if __name__ == '__main__': freeze()