| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687 |
- """Seal the local reviewable result after all real checks finish. No publication."""
- import hashlib
- import importlib.util
- import json
- import re
- import zipfile
- from html.parser import HTMLParser
- from pathlib import Path
- ROOT=Path(__file__).resolve().parents[1]
- OUT=ROOT/'docs/workshop-validated-final-20261110'
- BASE=ROOT/'docs/workshop-self-run-20261110'
- 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')
- spec=importlib.util.spec_from_file_location('installer',OUT/'installation/install.py')
- installer=importlib.util.module_from_spec(spec);spec.loader.exec_module(installer)
- meta=installer.verify_package(OUT/'installation')
- for n,h in meta['workshop']['files'].items():
- assert sha(OUT/'installation/skills/ksf-workshop-runtime'/n)==h
- assert sha(ROOT/'work/workshop-r12-rerun-20261110/isolated-skills/ksf-workshop-runtime'/n)==h
- for n in [n for n in meta['workshop']['files'] if n.startswith('scripts/') or n=='runtime-lock.json']:
- assert sha(OUT/'installation/skills/ksf-workshop-runtime'/n)==sha(ROOT/'work/workshop-self-run-20261110/isolated-skills/ksf-workshop-runtime'/n)
- checks=json.loads((OUT/'内容与安装核验.json').read_bytes())
- browser=json.loads((OUT/'浏览器核验.json').read_bytes())
- assert checks['ok'] and browser['ok']
- before=json.loads((BASE/'对话与产出.json').read_bytes())
- after=json.loads((OUT/'rerun/对话与产出.json').read_bytes())
- assert len(before['steps'])==len(after['steps'])==15
- original=ROOT/'docs/workshop-final-local-20261110/康师傅工作坊操作手册-本地定稿.html'
- assert sha(original)=='8c8942c3d1331693570928b8f54300f13f635387cfa3dcf1422bde3cec1740ab'
- comparison=[]
- previous=ROOT/'work/workshop-self-run-20261110/baseline-source-skill'
- for n in meta['workshop']['files']:
- if sha(previous/n)!=meta['workshop']['files'][n]:comparison.append({'path':n,'beforeSha256':sha(previous/n),'afterSha256':meta['workshop']['files'][n]})
- dump(OUT/'定稿修改文件.json',comparison)
- nav=[('market-analysis','市场报告','tasks'),('social-needs','社媒场景报告','voc'),('evidence-pool','用户与竞品证言池','voc'),('opportunity-cards','三源研究卡','opportunities'),('opportunity-validation','产品论证机会清单','feasibility'),('buying-points','场景买点方案','value'),('product-proposal','产品概念提案','proposal')]
- dump(OUT/'七类成果导航.json',[{'chapterId':key,'name':name,'url':'rerun/index.html#s-'+step,'version':'local-v1','savedToBusiness':False} for key,name,step in nav])
- first_chars=sum(len(s['prompt']) for s in before['steps']);second_chars=sum(len(s['prompt']) for s in after['steps'])
- dump(OUT/'核验结果.json',{
- 'ok':True,'version':'r12-local-final','scope':'local paired content freeze + two one-assistant self-roleplays',
- 'before':{'manualContent':'r11-local','installedSkill':'r10','steps':15,'promptCharacters':first_chars},
- 'after':{'manualContent':'r12-local-final','installedSkill':'r12-local-final','steps':15,'promptCharacters':second_chars},
- 'promptReductionCharacters':first_chars-second_chars,
- 'promptReductionPercent':round((first_chars-second_chars)/first_chars*100,2),
- 'decisiveBusinessQuestions':{'beforeBatches':4,'beforeItems':6,'afterBatches':3,'afterItems':5,'basis':'authored roleplay; not real employee efficiency measurement'},
- 'dataChecks':checks,'browserChecks':browser,
- 'originalManualSha256':sha(original),'r12ManualSha256':sha(OUT/'康师傅工作坊操作手册-r12本地定稿.html'),
- 'r12SkillArchiveSha256':sha(OUT/'康师傅工作坊Skill-r12本地定稿.zip'),
- 'skillCreatorQuickValidation':'Skill is valid!',
- 'contentReview':'same assistant; not blind or independent evaluation',
- 'contentReviewFinding':'clearer version binding, question reuse, maturity labels, scenario separation and executable observation tool; unchanged evidence-based product verdict',
- 'notVerified':['native host skill discovery','real employee/Doubao use','real consumer or engineering tests','production saves or group board attribution','enterprise account integration'],
- 'productionWrites':0,'published':False})
- for n in ['核验结果.json','浏览器核验.json','定稿清单.json','七类成果导航.json','定稿修改文件.json']:
- assert (OUT/n).is_file()
- # Check local links in every rendered page, including report source footnotes.
- links_checked=0
- class Links(HTMLParser):
- def __init__(self):super().__init__();self.links=[]
- def handle_starttag(self,tag,attrs):
- if tag=='a':
- href=dict(attrs).get('href')
- if href:self.links.append(href)
- for p in OUT.rglob('*.html'):
- if p.name=='使用的原手册.html':continue # preserve the user's exact archived original
- parser=Links();parser.feed(p.read_text('utf-8'))
- for href in parser.links:
- if not href or href.startswith('#') or re.match(r'^[a-z]+:',href,re.I):continue
- name=href.split('#',1)[0]
- if name=='本地定稿与两轮记录.zip':continue
- assert (p.parent/name).is_file(),(p,href)
- links_checked+=1
- archive=OUT/'本地定稿与两轮记录.zip'
- manifest=OUT/'FILES.sha256'
- excluded={archive,manifest,OUT/'整包.sha256'}
- files=[p for p in sorted(OUT.rglob('*')) if p.is_file() and p not in excluded and '__pycache__' not in p.parts]
- assert not any(p.name in ['credential-binding.json','.env','.pgpass'] or 'node_modules' in p.parts for p in files)
- manifest.write_text(''.join(sha(p)+' '+p.relative_to(OUT).as_posix()+'\n' for p in files),encoding='utf-8')
- with zipfile.ZipFile(archive,'w',zipfile.ZIP_DEFLATED) as z:
- for p in files+[manifest]:z.write(p,p.relative_to(OUT).as_posix())
- with zipfile.ZipFile(archive) as z:
- assert z.testzip() is None
- for line in z.read('FILES.sha256').decode('utf-8').splitlines():
- h,name=line.split(' ',1);assert hashlib.sha256(z.read(name)).hexdigest()==h
- (OUT/'整包.sha256').write_text(sha(archive)+' '+archive.name+'\n',encoding='utf-8')
- print(json.dumps({'ok':True,'localLinksChecked':links_checked,'archiveFiles':len(files)+1,'zipBytes':archive.stat().st_size,'zipSha256':sha(archive),'skillChangedPublicFiles':len(comparison),'productionWrites':0,'published':False},ensure_ascii=False))
|