import fs from 'node:fs'; import crypto from 'node:crypto'; const root='skills/ksf-workshop-runtime',sha=x=>crypto.createHash('sha256').update(x).digest('hex'); // Maintain final-SKILL.md; keep the conventional loader entry synchronized. const finalEntry=fs.readFileSync(root+'/final-SKILL.md'); if(!fs.existsSync(root+'/SKILL.md')||!fs.readFileSync(root+'/SKILL.md').equals(finalEntry))fs.writeFileSync(root+'/SKILL.md',finalEntry); // Ship a self-contained browser copy in src/; both renderers use this one maintained source. const browserStatistics='// Generated by scripts/build-workshop-skill.mjs; maintain the Skill report-statistics.mjs source.\n'+fs.readFileSync(root+'/scripts/report-statistics.mjs','utf8'); if(!fs.existsSync('src/core/report-statistics.js')||fs.readFileSync('src/core/report-statistics.js','utf8')!==browserStatistics)fs.writeFileSync('src/core/report-statistics.js',browserStatistics); const browserAssessment='// Generated by scripts/build-workshop-skill.mjs; maintain the Skill course-assessment.mjs source.\n'+fs.readFileSync(root+'/scripts/course-assessment.mjs','utf8'); if(!fs.existsSync('src/core/course-assessment.js')||fs.readFileSync('src/core/course-assessment.js','utf8')!==browserAssessment)fs.writeFileSync('src/core/course-assessment.js',browserAssessment); const hash=sha(fs.readFileSync(root+'/scripts/ksf-workshop.mjs')); const identityHash=sha(fs.readFileSync(root+'/scripts/ksf-identity.mjs')); const file=root+'/scripts/ksf-secure.mjs'; fs.writeFileSync(file,fs.readFileSync(file,'utf8') .replace(/const WORKSHOP_ENTRY_HASH = '[^']+';/,"const WORKSHOP_ENTRY_HASH = '"+hash+"';") .replace(/const ACCOUNT_ENTRY_HASH = '[^']+';/,"const ACCOUNT_ENTRY_HASH = '"+sha(fs.readFileSync(root+'/scripts/ksf-account.mjs'))+"';") .replace(/const IDENTITY_MODULE_HASH = '[^']+';/,"const IDENTITY_MODULE_HASH = '"+identityHash+"';")); // Keep the declared public package complete; never enumerate local identity/credential files. const declared=fs.existsSync(root+'/FILES.sha256')?fs.readFileSync(root+'/FILES.sha256','utf8').trim().split(/\r?\n/).filter(Boolean).map(line=>{ const match=line.match(/^[a-f0-9]{64}\s+\*?(.+)$/);if(!match||match[1].includes('..')||/^[\\/]|:/.test(match[1]))throw Error('非法公开清单路径');return match[1]; }).filter(file=>!['references/workshop-dialogue-prompts.json','references/workshop-case-evidence.md'].includes(file)):[]; const files=[...new Set([...declared,'SKILL.md','references/identity.md','references/workshop-tasks.md','references/write-tasks.md','references/business-data-dictionary.json','references/full-schema-and-intake.md','runtime-lock.json','scripts/describe-business-schema.mjs','scripts/ksf-identity.mjs','scripts/ksf-identity-bridge.mjs','scripts/ksf-secure.mjs','scripts/ksf-workshop.mjs','scripts/ksf.mjs','scripts/report-contract.mjs','scripts/save-report.mjs','references/workshop-report-parity.md'])].sort(); for (const file of ['references/workshop-dialogue.md','references/workshop-evidence-counting.md','references/workshop-v3-outputs.md','scripts/course-deliverables.mjs','scripts/course-assessment.mjs','scripts/ksf-account.mjs','scripts/render-report-html.mjs','scripts/report-statistics.mjs']) if (!files.includes(file)) files.push(file); files.sort(); const entries=files.map(file=>sha(fs.readFileSync(root+'/'+file))+' '+file); fs.writeFileSync(root+'/FILES.sha256',entries.join('\n')+'\n'); console.log(JSON.stringify({skill:root,files:entries.length,workshopEntrySha256:hash,identityModuleSha256:identityHash,legacyEntryPreserved:sha(fs.readFileSync(root+'/scripts/ksf.mjs'))==='7ef128f7d022bfbc15c0ec813bfd87995c6297c6efe407a563c840f014aba477'}));