import { readFileSync, readdirSync } from 'node:fs' const root = '.scratch/cockpit-shell-panel-layout/' const files = readdirSync(`${root}issues`).filter((file) => /^\d+-.*\.md$/.test(file)) const rows = files.map((file) => { const text = readFileSync(`${root}issues/${file}`, 'utf8').replace(/^\uFEFF/, '') const title = /^#\s+(.+)$/m.exec(text)?.[1] const status = /^Status:\s*(.+)$/m.exec(text)?.[1]?.trim().toLowerCase() if (!title || !status) throw new Error(`Missing title/status: ${file}`) return { file, title, closed: ['resolved', 'closed', 'done', 'wontfix'].includes(status) } }).sort((a, b) => parseInt(a.file) - parseInt(b.file)) console.log(JSON.stringify({ total: rows.length, closed: rows.filter((row) => row.closed).length })) const map = readFileSync(`${root}map.md`, 'utf8') if (process.argv.includes('--check')) { if (!/^## Destination$/m.test(map)) throw new Error('Destination missing') const matches = [...map.matchAll(/^- \[([ x])\] \[.+\]\(issues\/([^\s)]+)\)$/gm)] if (matches.length !== rows.length) throw new Error(`Checklist ${matches.length} != children ${rows.length}`) const references = new Map(matches.map((match) => [match[2], match[1] === 'x'])) if (references.size !== rows.length) throw new Error('Duplicate checklist ticket') for (const row of rows) { if (!references.has(row.file) || references.get(row.file) !== row.closed) throw new Error(`Checklist mismatch: ${row.file}`) } console.log(`PASS checklist=${rows.length}, closed=${rows.filter((row) => row.closed).length}, Destination retained`) } else { console.log('## 任务清单\n\n本节为全部子票的唯一完整任务清单;每票一项,历史说明保持在原节,状态以票面和地图快照为准。\n') for (const row of rows) console.log(`- [${row.closed ? 'x' : ' '}] [${row.title}](issues/${row.file})`) }