| 12345678910111213141516171819202122232425262728293031323334353637383940414243 |
- /** Freeze a reviewable local candidate. This script has no network or deployment actions. */
- import fs from 'node:fs';
- import path from 'node:path';
- import crypto from 'node:crypto';
- import assert from 'node:assert/strict';
- const root = path.resolve('.'), skill = 'skills/ksf-workshop-runtime';
- const out = 'docs/workshop-report-parity-20261111/candidate';
- assert.ok(!fs.existsSync(out), '候选目录已存在;不可覆盖已冻结快照');
- const digest = bytes => crypto.createHash('sha256').update(bytes).digest('hex');
- const original = 'docs/ksf-workshop-skill-20261110-r13-r2/package/' + skill;
- const protectedFiles = ['runtime-lock.json', ...['ksf.mjs','ksf-secure.mjs','ksf-workshop.mjs','ksf-identity.mjs','ksf-identity-bridge.mjs','describe-business-schema.mjs'].map(file => 'scripts/' + file)];
- for (const file of protectedFiles) assert.ok(fs.readFileSync(skill + '/' + file).equals(fs.readFileSync(original + '/' + file)), '受保护文件发生变化:' + file);
- const declared = fs.readFileSync(skill + '/FILES.sha256', 'utf8').trim().split(/\r?\n/).map(line => line.match(/^[a-f0-9]{64}\s+\*?(.+)$/)?.[1]);
- assert.ok(declared.every(file => file && !file.includes('..') && !path.isAbsolute(file)));
- const entries = [...new Set([...declared, 'scripts/report-contract.mjs', 'scripts/save-report.mjs', 'references/workshop-report-parity.md'])].sort();
- fs.writeFileSync(skill + '/FILES.sha256', entries.map(file => digest(fs.readFileSync(skill + '/' + file)) + ' ' + file).join('\n') + '\n');
- const links = [];
- for (const file of entries.filter(file => file.endsWith('.md'))) {
- const text = fs.readFileSync(skill + '/' + file, 'utf8');
- for (const [, target] of text.matchAll(/\[[^\]]+\]\(([^)]+)\)/g)) {
- if (/^(https?:|#|<)/.test(target)) continue;
- assert.ok(fs.existsSync(path.resolve(skill, path.dirname(file), target.split('#')[0])), '技能相对链接不存在:' + file + ' → ' + target);links.push({ file, target });
- }
- }
- const frontend = ['src/core/kfs/build.js','src/core/library/reports.js','src/core/library/types.js','src/core/report-delivery.js',
- 'src/core/report-attachments.js','src/core/report.js','src/core/workshop-outputs.js','src/pages/report-card.js','src/pages/view-report.js',
- 'src/pages/view-report-type.js','src/pages/view-reports.js','src/pages/view-workshop-outputs.js','src/layout/kf-app.js',
- 'src/layout/kf-nav.js','src/utils/router.js',skill + '/scripts/report-contract.mjs'];
- const backend = ['ksfSaveWorkshopTask','ksfGetIngestContract','ksfGetPublicBoard','ksfReadFile','ksfPrepareFileUpload'];
- const files = [...new Set([...entries.map(file => skill + '/' + file),skill + '/FILES.sha256',...frontend,
- ...backend.map(name => 'server/dist/' + name + '.js'),'server/references/runtime-data.json',
- 'docs/workshop-quality-r13-20261110/final-康师傅工作坊操作手册.html'])];
- const manifest = files.map(file => {
- const bytes = fs.readFileSync(file), target = out + '/' + file;fs.mkdirSync(path.dirname(target), { recursive: true });fs.writeFileSync(target, bytes);
- return { path: file, bytes: bytes.length, sha256: digest(bytes) };
- });
- const result = { schema: 'ksf.workshop-report-parity-local.v1', createdAt: new Date().toISOString(), version: 'r14-local',
- localCandidateOnly: true, productionDeployed: false, runtimeChanges: 0, protectedFilesVerified: protectedFiles,
- skillPublicFiles: entries.length + 1, skillRelativeLinksVerified: links.length,
- frontend, backend, schemaMigrations: 0,
- deploymentNote: '快照是待审本地候选,不含生产基线、生产凭据或自动发布器;部署须按目标当前代码建立定向计划并保留并发与回滚保护。后端/前端/Skill需成套升级,不能仅复制静态页面。', files: manifest };
- fs.writeFileSync(out + '/manifest.json', JSON.stringify(result, null, 2) + '\n');
- console.log(JSON.stringify({ out, files: manifest.length, skillPublicFiles: result.skillPublicFiles, relativeLinksVerified: links.length, protectedFilesVerified: protectedFiles.length, productionDeployed: false }));
|