package-workshop-manual.py 8.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121
  1. """Seal the r10 workshop manual, including a standalone preview and deployment instructions."""
  2. import argparse
  3. import hashlib
  4. import json
  5. from pathlib import Path
  6. import zipfile
  7. ROOT = Path(__file__).resolve().parents[1]
  8. SOURCE = ROOT / 'docs/workshop-manual-20261109'
  9. OUT = ROOT / 'docs/workshop-manual-20261110-r1'
  10. RELEASE = 'ksf-workshop-manual-20261110-r1'
  11. NAMES = ['index.html', 'content.js', 'manual.js', 'manual.css']
  12. sha = lambda b: hashlib.sha256(b).hexdigest()
  13. encode = lambda v: (json.dumps(v, ensure_ascii=False, indent=2) + '\n').encode('utf-8')
  14. DEPLOY = '''# 康师傅工作坊 r10 · 联合上线执行单
  15. 本执行单只更新工作坊技能说明与静态操作手册;数据库迁移、后端运行补丁已经验收,不重跑迁移或重启数据服务。
  16. ## 资源
  17. 以本执行单所在的固定 Git 提交目录为根目录,读取 manual-release.json;下载其 archive.path,核字节数及 SHA-256。在新临时目录安全解压并逐项核 FILES.sha256,不覆盖未知目录或改用分支最新地址。包内 manual/ 是四文件站点,ksf-workshop-manual-r10.html 是同内容的单文件预览。
  18. 配套技能固定安装说明:
  19. {guide}
  20. 该说明对应 Skill r10、唯一内部入口 ksf-workshop-runtime,支持完整 r7/r8/r9 备份升级,外部官方 VOC 固定0.3.42。技能与手册联合版本均为r10。
  21. ## 1. 更新原工作坊Agent技能
  22. 在原Agent环境按上述安装说明核包、预检、备份升级,再验证43文件。使用已确认的实际技能根,不覆盖身份、授权、绑定及额外文件,不改其他技能。六个业务脚本与runtime-lock.json共7文件必须保持原指纹。保留已退役的旧研究目录为脚本兼容路径,不恢复其SKILL.md;已部署研究运行补丁保持原样。
  23. 同版只核验;未知版本或已改动说明停止覆盖并回传差异,不放宽校验。外部VOC如已符合固定版本及已记录的.mcp.json路径适配就复用。更新后的新任务应实际发现并加载r10;文件校验、宿主加载分别反馈。
  24. 仅做只读 smoke:默认研究 contract 核 backend=parse-postgresql、mode=ssh-channel(原Agent通道)及实际当前代际;业务 workshop-contract 读取成功。不要新建会话/项目/报告、采集、发布新代际或重跑验收保存记录。
  25. ## 2. 发布静态手册
  26. 建议正式地址为 https://kangshifu.fmode.cn/workshop-manual/ ,以维护人核实的Nginx/静态目录和路由为准。manual/四文件保持同目录,以新版本目录暂存后整体切换;存在旧手册先备份,不能直接覆盖来源不明的文件。独立静态路径不要误落回看板SPA首页;不更换看板根首页、不改数据库/API代理。亦可使用已有授权的静态发布域名,回传真实可访问地址。
  27. HTML用text/html、CSS用text/css、JS用application/javascript,均UTF-8。入口和版本资源及时回源;若存在CDN,只刷新这四个手册文件。该站点不含密钥、登录或业务数据,不会写数据库。
  28. ## 3. 一次回传验收
  29. - Skill:版本r10,43文件逐项一致,7运行文件未变;新任务实际加载状态;研究/业务只读合同结果。
  30. - 手册:真实网页URL,4文件HTTP200、Content-Type和SHA与清单一致;浏览器打开不是看板回退页。
  31. - 浏览器:15章导航、完整复制、桌面与390px,无脚本错误;安装提示词指向本执行单固定的r10资源。
  32. - 范围:准备2节、研究13节、三个半天单一路线;没有49天行动计划或两分支;个人勾选只存本机。
  33. - 保持:数据库和既有业务记录不改,身份/连接不改。挂站完成与豆包员工电脑安装生效分开反馈;全量小组展示和豆包实跑按后续任务验收。
  34. '''
  35. def stage():
  36. released = json.loads((ROOT / 'docs/workshop-onboarding-20261109-r5/online-release-receipt.json').read_bytes())
  37. assert released['published'] and released['allPublicFilesReadBack'] and released['workshopSkillVersion'] == 'r10'
  38. source = {n: (SOURCE / n).read_bytes() for n in NAMES}
  39. assert b'__R10_INSTALL_GUIDE__' not in source['content.js']
  40. assert released['guideUrl'].encode() in source['content.js']
  41. html = source['index.html'].decode('utf-8')
  42. html = html.replace('<link rel="stylesheet" href="manual.css">', '<style>\n' + source['manual.css'].decode('utf-8') + '\n</style>')
  43. for name in ['content.js', 'manual.js']:
  44. script = source[name].decode('utf-8').replace('</script', '<\\/script')
  45. html = html.replace('<script src="' + name + '" defer></script>', '')
  46. html = html.replace('</body>', '<script>\n' + script + '\n</script>\n</body>')
  47. package = {'manual/' + n: b for n, b in source.items()}
  48. package['ksf-workshop-manual-r10.html'] = html.encode('utf-8')
  49. package['DEPLOY.md'] = DEPLOY.format(guide=released['guideUrl']).encode('utf-8')
  50. meta = {'schema': 'ksf.workshop-manual.v1', 'releaseId': RELEASE, 'workshopSkillVersion': 'r10',
  51. 'installationGuide': released['guideUrl'], 'installationCommit': released['commit'],
  52. 'sourceFiles': {n: sha(b) for n, b in source.items()},
  53. 'manualFiles': {n: sha(b) for n, b in package.items()},
  54. 'halfDays': 3, 'preparationSteps': 2, 'researchSteps': 13,
  55. 'branches': 1, 'promptsExpanded': True, 'postWorkshopPlanIncluded': False,
  56. 'databaseIncluded': False, 'credentialsIncluded': False, 'writesBusinessData': False}
  57. package['release.json'] = encode(meta)
  58. package['FILES.sha256'] = ''.join(sha(b) + ' ' + n + '\n' for n, b in sorted(package.items())).encode('utf-8')
  59. for name, b in package.items():
  60. p = OUT / 'package' / name
  61. p.parent.mkdir(parents=True, exist_ok=True)
  62. p.write_bytes(b)
  63. # Convenient local preview, separate from the immutable public archive.
  64. (OUT / 'ksf-workshop-manual-r10.html').write_bytes(package['ksf-workshop-manual-r10.html'])
  65. return package, meta
  66. def main():
  67. parser = argparse.ArgumentParser(description=__doc__)
  68. parser.add_argument('--pack', action='store_true')
  69. args = parser.parse_args()
  70. archive = OUT / 'publish' / (RELEASE + '.zip')
  71. assert not archive.exists(), 'NEVER_OVERWRITE_RELEASE'
  72. package, meta = stage()
  73. if not args.pack:
  74. print(json.dumps({'ok': True, 'staged': len(package), 'preview': str(OUT / 'ksf-workshop-manual-r10.html')}))
  75. return
  76. browser = json.loads((SOURCE / 'browser-verification.json').read_bytes())
  77. assert browser['ok'] and browser['sourceFiles'] == meta['sourceFiles']
  78. assert browser['standaloneVerified'] and browser['standaloneSha256'] == sha(package['ksf-workshop-manual-r10.html'])
  79. archive.parent.mkdir(parents=True, exist_ok=True)
  80. with zipfile.ZipFile(archive, 'x', zipfile.ZIP_DEFLATED) as z:
  81. for name, b in sorted(package.items()):
  82. info = zipfile.ZipInfo(name, (2026, 11, 10, 0, 0, 0))
  83. info.external_attr = 0o100644 << 16
  84. z.writestr(info, b, compress_type=zipfile.ZIP_DEFLATED)
  85. with zipfile.ZipFile(archive) as z:
  86. assert z.testzip() is None and set(z.namelist()) == set(package)
  87. assert all(z.read(n) == b for n, b in package.items())
  88. distribution = {'schema': 'ksf.workshop-manual.distribution.v1', 'releaseId': RELEASE,
  89. 'archive': {'path': archive.name, 'bytes': archive.stat().st_size, 'sha256': sha(archive.read_bytes())},
  90. 'workshopSkillVersion': 'r10', 'installationGuide': meta['installationGuide'],
  91. 'manualFiles': meta['manualFiles'], 'browserLocalVerified': True,
  92. 'employeeWorkshopTrialCompleted': False, 'productionWebsiteDeployed': False,
  93. 'suggestedWebsiteUrl': 'https://kangshifu.fmode.cn/workshop-manual/'}
  94. (archive.parent / 'manual-release.json').write_bytes(encode(distribution))
  95. (archive.parent / 'DEPLOY.md').write_bytes(package['DEPLOY.md'])
  96. (OUT / 'build-receipt.json').write_bytes(encode(distribution))
  97. print(json.dumps(distribution, ensure_ascii=False, indent=2))
  98. if __name__ == '__main__':
  99. main()