"""Freeze the local paired manual/Skill. Does not publish or modify host installations."""
import hashlib
import json
import shutil
import zipfile
from pathlib import Path
ROOT = Path(__file__).resolve().parents[1]
OUT = ROOT / 'docs/workshop-validated-final-20261110'
SOURCE = ROOT / 'skills/ksf-workshop-runtime'
OLD = ROOT / 'work/workshop-self-run-20261110/baseline-source-skill'
PKG_OLD = ROOT / 'work/workshop-self-run-20261110/install/package'
VERSION = 'r12-local-final'
sha = lambda p: hashlib.sha256(p.read_bytes()).hexdigest()
dump = lambda p, x: p.write_text(json.dumps(x, ensure_ascii=False, indent=2) + '\n', encoding='utf-8')
def freeze():
OUT.mkdir(exist_ok=True)
names = [line.split(' ', 1)[1] for line in (OLD / 'FILES.sha256').read_text('utf-8').splitlines()]
protected = [n for n in names if n.startswith('scripts/') or n == 'runtime-lock.json']
for n in protected:
assert sha(SOURCE/n) == sha(OLD/n), n
(SOURCE/'FILES.sha256').write_text(''.join(sha(SOURCE/n)+' '+n+'\n' for n in sorted(names)), encoding='utf-8')
names = sorted(names + ['FILES.sha256'])
pkg = OUT/'installation'
pkg.mkdir(exist_ok=True)
skill = pkg/'skills/ksf-workshop-runtime'
for n in names:
p = skill/n; p.parent.mkdir(parents=True, exist_ok=True); shutil.copyfile(SOURCE/n, p)
for n in ['install.py', 'check-voc.cjs']:
shutil.copyfile(PKG_OLD/n, pkg/n)
meta = json.loads((PKG_OLD/'release.json').read_bytes())
meta['releaseId'] = 'ksf-workshop-local-final-20261110-r12'
meta['workshop']['version'] = VERSION
meta['workshop']['files'] = {n: sha(skill/n) for n in names}
meta['upgradeFrom'] = {'r11-local-exact': {n: sha(OLD/n) for n in names}}
meta['localOnly'] = True
dump(pkg/'release.json', meta)
(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')
install_md = f'''# 本地定稿资源 · {VERSION}
本机可读资源:`{pkg.as_posix()}`。包内install.py与已核验r10发行相同,业务运行脚本未修改;本地发行清单更新为本次定稿。校验FILES.sha256后运行安装器。只升级release.json明确支持且逐文件一致的r11-local基线;r7/r8/r9/r10、未知或本地改动版本停止并报告,不强制覆盖。保留本机身份/授权/连接及其他技能,不恢复旧内部研究Skill。
先执行只读plan:
```text
python -B install.py --skills-dir <宿主实际支持的用户技能根> --out <本地回执目录>
```
plan明确为create/verify-only,或匹配受支持基线的upgrade后,按包内参数正常安装/备份升级。`--prepare-runtime`仅准备并检查VOC本地运行,不表示注册为原生工具或已开通真实访问。真实宿主目录未知时报告缺项,不安装进受管内置目录。自演可以使用明确标注的隔离目录,不能称为宿主加载成功。
这套资源仅在本地定稿、核验;没有线上r12下载地址,也没有要求学员上传附件。后续正式发行时替换安装说明的资源地址并重新核验,不把旧线上r10称为r12。企业账套留待后续接入;现有身份和授权机制保持,资料读取/业务保存按实际能力分别测试。
'''
(OUT/'INSTALL-本地定稿.md').write_text(install_md, encoding='utf-8')
data = json.loads((ROOT/'docs/workshop-self-run-20261110/manual-source.json').read_bytes())
data['release'].update(version=VERSION, contentVersion=VERSION, installVersion=VERSION,
guide=(OUT/'INSTALL-本地定稿.md').as_uri(), deploymentStatus='local-only')
data['common'] = '''使用康师傅产品创新工作坊 Skill,沿同一课题、已知条件和前序成果完成当前环节。先恢复已有信息;只反问会改变本节判断且必须由业务方回答的1—3项,已答内容不再问。产品事实、比较方法、补证和计算由你完成。
先回答本节问题:本节新增或修订了什么,对产品选择有什么影响?以具体产品、原话和适用条件解释得失;研究线索、产品设想、当前建议分别使用。关键缺口先作能执行的核查;需要真人/样品的,给可操作的比较与继续/修改/停止条件,不假装已验证。
交付下列实质成果,完整依据可回查;说明拟改什么、保留什么、预期收益与代价。按Skill定位七类成果章节,不为凑数量建空报告。沿实际能力保存;未接通则交同等质量的本地成果。末尾更新简短课题摘要、成果位置和下一节输入;本轮停在当前环节。'''
by = {s['id']: s for s in data['steps']}
by['install']['prompt'] = f'''请安装或核验康师傅产品创新工作坊本地定稿 {VERSION}。先读取本机已提供的固定资源说明:
{(OUT/'INSTALL-本地定稿.md').as_posix()}
按说明核验配套installation/FILES.sha256与release.json,确认工作坊版本为{VERSION}、唯一内部入口为ksf-workshop-runtime,VOC沿清单的官方0.3.42。由你识别宿主实际支持的用户技能目录;只升级清单支持且哈希一致的基线,未知/改动版本报告差异停止覆盖,保留身份、授权、连接和其他技能,不另装旧内部研究入口,不索要数据库密钥。资源不可读时如实报告,不用旧线上r10冒充本次定稿。
分别实际核文件安装、宿主加载、VOC本地执行/原生注册、资料读取与成果保存。不改平台校验,不新建企业账套或声称已实名。仅初始化,不新增采集、不创建会话/项目/报告。用简短业务语言说现在能做什么;隔离目录自演与真实宿主安装分开。'''
by['install']['deliverables'][0] = f'工作坊 Skill {VERSION} 的本地安装或核验结果'
by['install']['checks'][1] = '仅升级清单支持且逐文件匹配的基线;本机身份、授权、连接保留。'
by['topic']['prompt'] += '\n\n将业务方给定、资料观察、研究假设分别记录。摘要用于后续接续,不要求我反复填条件表;不要把你的自设限制变成我的业务约束。'
by['tasks']['task'] += ' 对当前关键缺口优先给能实际执行的最小核查,执行状态与研究建议分开。'
by['opportunities']['task'] += ' 只有观察任务的保留为任务;尚未成立的产品关系可列待证设想,不把三条研究线索报成三个已验证商业机会。'
by['feasibility']['task'] += ' 无真实收入和报价时,主表给变量与临界条件;纯算术示例放附录,不进入企业盈利结论或排序。'
by['proposal']['task'] += ' 让评审先看到当前建议、现有方案与拟改方案的差异,以及会改变建议的关键结果;完整过程与方法后置。'
manual = OUT/'manual'; manual.mkdir(exist_ok=True)
(manual/'content.js').write_text('window.WORKSHOP_MANUAL_DATA = '+json.dumps(data, ensure_ascii=False, indent=2)+';\n', encoding='utf-8')
src = ROOT/'docs/workshop-final-local-20261110/manual'
for n in ['index.html', 'manual.css', 'manual.js']:
shutil.copyfile(src/n, manual/n)
html = (manual/'index.html').read_text('utf-8').replace('操作手册 · 本地定稿','操作手册 · r12 定稿').replace('联合优化稿 r11-local · 当前线上安装资源 r10 · 本稿尚未发布','r12 本地定稿 · Skill与手册配套 · 企业账套待后续接入')
(manual/'index.html').write_text(html, encoding='utf-8')
js = (manual/'manual.js').read_text('utf-8').replace('ksf-workshop-manual-r11-local-20261110','ksf-workshop-manual-r12-local-final-20261110')
(manual/'manual.js').write_text(js, encoding='utf-8')
# Keep the compact, one-route design and always-visible full prompts.
css = (manual/'manual.css').read_text('utf-8')
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'
(manual/'manual.css').write_text(css, encoding='utf-8')
safe = lambda s: s.replace('','').replace('','').replace('','')
standalone = standalone.replace('