"""Prepare/finalize the query-rule handoff without copying private databases.""" import argparse import hashlib import json from pathlib import Path import shutil import zipfile ROOT = Path(__file__).resolve().parents[1] DIST = Path('F:/文档/康师傅skill') WORK = ROOT / 'work/agent-query-20261106' OUT = ROOT / 'artifacts/数据库精准检索规则-20261106' NAME = 'ksf-agent-query-rules-20261106-r1' RELEASE = OUT / NAME SKILLS = ('ksf-workshop-runtime', 'ksf-voc-library') def sha(path): with path.open('rb') as stream: return hashlib.file_digest(stream, 'sha256').hexdigest() def write(path, data): path.parent.mkdir(parents=True, exist_ok=True) path.write_text(json.dumps(data, ensure_ascii=False, indent=2) + '\n', encoding='utf-8') def files(folder): return {p.relative_to(folder).as_posix(): sha(p) for p in sorted(folder.rglob('*')) if p.is_file() and '__pycache__' not in p.parts} def manifest(folder): entries = {name: value for name, value in files(folder).items() if name != 'FILES.sha256'} (folder / 'FILES.sha256').write_text(''.join(value + ' ' + name + '\n' for name, value in entries.items()), encoding='utf-8', newline='\n') def prepare(): if RELEASE.exists(): raise ValueError('RELEASE_ALREADY_PREPARED') for name in ('query_voc.py', 'voc_db.py', 'retrieval.py', 'schema.sql', 'taxonomy.json'): shutil.copyfile(ROOT / 'server/voc-library' / name, ROOT / 'skills/ksf-voc-library/scripts' / name) for name in SKILLS: manifest(ROOT / 'skills' / name) shutil.copytree(ROOT / 'skills' / name, RELEASE / 'skills' / name) baselines = {name: [files(WORK / 'baseline-skills' / name)] for name in SKILLS} with zipfile.ZipFile(DIST / '历史归档/20261104-先查库/ksf-workshop-runtime-20261104.zip') as archive: baselines['ksf-workshop-runtime'].append({name: hashlib.sha256(archive.read(name)).hexdigest() for name in archive.namelist() if not name.endswith('/')}) write(RELEASE / 'baselines.json', baselines) shutil.copyfile(ROOT / 'scripts/install-query-skills.py', RELEASE / 'install-query-skills.py') for source in (ROOT / 'docs/voc-agent-query-20261106').glob('*.md'): target = RELEASE / 'docs' / source.name target.parent.mkdir(parents=True, exist_ok=True) target.write_text(source.read_text(encoding='utf-8').replace('../../skills/', '../skills/'), encoding='utf-8') scope = dict(platform='jd', brand='ksf', category='banmian') examples = { '01-jd-ksf-banmian-products': dict(entities=['products'], filters=scope, limit=20), '02-jd-ksf-banmian-reviews': dict(entities=['reviews'], filters=scope, quality='analysis', limit=20), '03-jd-ksf-banmian-review-audit': dict(entities=['reviews'], filters=scope, quality='audit', groupBy=['isDefault', 'identityState', 'categoryState'], limit=100), '04-brand-products': dict(entities=['products'], filters=dict(brand='ksf'), limit=20), '05-nutrition-documents': dict(entities=['documents', 'clauses'], text=dict(any=['营养标签', '营养成分表']), limit=20), '06-jd-ksf-banmian-prices-specs': dict(entities=['prices', 'specs'], filters=scope, limit=20), '07-jd-ksf-banmian-portion': dict(entities=['reviews'], filters=scope, text=dict(concepts=['portion_size']), limit=20), '08-jd-ksf-banmian-review-statistics': dict(entities=['reviews'], filters=scope, groupBy=['platform', 'brand', 'category'], limit=100), '09-jd-ksf-banmian-coverage': dict(entities=['coverage'], filters=scope, limit=20), } for name, data in examples.items(): write(RELEASE / 'examples' / (name + '.json'), data) write(RELEASE / 'release.json', dict(version=NAME, agentRulesVersion='ksf.agent-data-rules.20261106.v1', schema='ksf.voc-library.v2', retrievalContract='ksf.voc-retrieval.v2', databaseIncluded=False, databaseMigrationRequired=False, productionInstalled=False, realMessageVerified=False, workshopRuntimeScriptsChanged=False, researchScriptsChanged=['voc_db.py', 'retrieval.py'])) print(json.dumps(dict(prepared=str(RELEASE), next='Run verify-query-skills.py before finalize'), ensure_ascii=False)) def finalize(): validation = json.loads((WORK / 'validation.json').read_text(encoding='utf-8')) if not validation['ok'] or (OUT / (NAME + '.zip')).exists(): raise ValueError('VALIDATION_REQUIRED_OR_FROZEN_ZIP_EXISTS') for name in SKILLS: if files(ROOT / 'skills' / name) != files(RELEASE / 'skills' / name): raise ValueError('SKILL_CHANGED_AFTER_PREPARATION') shutil.copyfile(WORK / 'validation.json', RELEASE / 'local-validation.json') manifest(RELEASE) archive = OUT / (NAME + '.zip') with zipfile.ZipFile(archive, 'x', zipfile.ZIP_DEFLATED) as z: for path in sorted(RELEASE.rglob('*')): if path.is_file(): z.write(path, path.relative_to(RELEASE).as_posix()) receipt = dict(release=NAME, filename=archive.name, bytes=archive.stat().st_size, sha256=sha(archive), files=len(files(RELEASE)), localValidated=True, productionInstalled=False, realMessageVerified=False) write(OUT / 'delivery-receipt.json', receipt) for name in SKILLS: source, target = ROOT / 'skills' / name, DIST / name for path in source.rglob('*'): if path.is_file(): out = target / path.relative_to(source) out.parent.mkdir(parents=True, exist_ok=True) shutil.copyfile(path, out) shutil.copyfile(archive, DIST / archive.name) write(DIST / '版本与校验.json', dict(preparedDate='2026-11-06', release=NAME, skills={name: dict(files=files(DIST / name), manifestSha256=sha(DIST / name / 'FILES.sha256')) for name in SKILLS}, zip=receipt, previousPreparedVersion='历史归档/20261105-r2-查询规则更新前.zip', databaseIncluded=False, productionInstalled=False, realMessageValidated=False)) (DIST / 'README.md').write_text('''# 当前Skill分发 · 2026-11-06 当前安装入口:ksf-agent-query-rules-20261106-r1.zip;ZIP、两份展开Skill与版本与校验.json一致。 - ksf-workshop-runtime:工作坊技能,研究底料先查研究库、报告和项目查业务库;仅说明和清单升级,身份、昵称、上传与运行脚本沿用原版。 - ksf-voc-library:只读研究查询,精确字段AND、商品评价关联、来源回读、统计口径及同一generation校验;支持与看板使用同一registry/current.json。 - 不含数据库,沿用已安装v2研究库。缺库时需先用原完整r2资料包建立授权库;不能只复制Skill就声称能查到资料。 本机准备和验证完成,未在王刚001生产实例安装/加载/真实消息验收。请将当前完整ZIP与包内docs/发给运维001.md一起转运维;安装后将docs/发给王刚001.md交本人真实消息验证。历史ZIP不再作为当前升级入口,旧r2展开内容已归档,原ZIP未修改。 新增同结构数据通过新代际更新,Agent新研究读取当前代际;新增字段/实体/分类定义才更新合同与Skill。没有新增采集工具,不调用付费上游。 ''', encoding='utf-8') handoff = Path('F:/文档/新数字人交接') (handoff / '10-精准查库规则增量-20261106.md').write_text('''# 精准查库规则增量 · 2026-11-06 当前增量包:F:/文档/康师傅skill/ksf-agent-query-rules-20261106-r1.zip。 原00—09、materials与旧ZIP保持历史快照;新增实例在完成原有身份/运行时开通和授权研究库后,使用此增量包对齐两份Skill。不要把旧materials直接当成当前查询规则,或复用其他员工身份/会话/凭据。 包内含精确基线升级器、两份Skill、数据与来源合同、9个查询示例、运维与王刚001验收单及本地验证回执。不含数据库,无Schema迁移;同一研究任务固定generation,新增数据经同一current.json取新代际。 目前仅本地准备和测试完成,生产安装、Agent实际加载、真实消息验收仍待运维与本人回报。 ''', encoding='utf-8') print(json.dumps(receipt, ensure_ascii=False, indent=2)) if __name__ == '__main__': parser = argparse.ArgumentParser(description=__doc__) parser.add_argument('phase', choices=['prepare', 'finalize']) args = parser.parse_args() prepare() if args.phase == 'prepare' else finalize()