v0.1.3: encoding remediation - restore 11 core files to clean UTF-8 + add check:encoding-health guard

This commit is contained in:
octopus
2026-09-16 13:13:18 +08:00
parent 445940ea63
commit 4518f41fd4
14 changed files with 437 additions and 341 deletions
+94
View File
@@ -0,0 +1,94 @@
#!/usr/bin/env bun
import fs from "node:fs"
import path from "node:path"
const ROOT = path.resolve(import.meta.dir, "..")
const CORE_DIR = path.join(ROOT, "core")
const SCAN_EXTENSIONS = new Set([".md", ".json", ".ts", ".tsx", ".js", ".mjs", ".cjs", ".yaml", ".yml", ".txt"])
export const MOJIBAKE_SIGNATURES: { name: string; pattern: RegExp }[] = [
{ name: "em-dash-mojibake", pattern: /\u9225/ },
{ name: "right-arrow-mojibake", pattern: /\u922B/ },
{ name: "check-mark-mojibake", pattern: /\u9257|\u9253/ },
{ name: "cross-mark-mojibake", pattern: /\u9242/ },
{ name: "geq-mojibake", pattern: /\u922E/ },
{ name: "multiplication-mojibake", pattern: /\u8137/ },
{ name: "replacement-char", pattern: /\uFFFD/ },
{ name: "truncated-mojibake", pattern: /[\u9000-\u9FFF]\?/ },
{ name: "gbk-cjk-bigram", pattern: /椤圭|鍒濆|閰嶇|鐨勶|鎷夎|浼氳瘽|鏃犲|淇濇|缁戝|瀹屾|涓珛|鏀瑰啓|瀹炰緥|鎵归|寤惰|璋冨|绀轰|鍘熸|閰嶉|鎹薄|閫昏|椤甸|绔犵|宸插|鍒嗛|鍏抽|鍚岀|鍚庣|鍓嶇|杩斿|杩涘|閫変|鎸夌|鏉冮|鏂囦|鏂囨/ },
]
export interface EncodingViolation {
file: string
line: number
signature: string
match: string
}
export function scanFile(filePath: string): EncodingViolation[] {
const text = fs.readFileSync(filePath, "utf8")
const violations: EncodingViolation[] = []
const lines = text.split(/\r?\n/)
for (let i = 0; i < lines.length; i++) {
for (const sig of MOJIBAKE_SIGNATURES) {
const m = lines[i]!.match(sig.pattern)
if (m) {
violations.push({
file: filePath,
line: i + 1,
signature: sig.name,
match: m[0]!,
})
}
}
}
return violations
}
export function collectFiles(dir: string): string[] {
const out: string[] = []
if (!fs.existsSync(dir)) return out
const walk = (d: string) => {
for (const entry of fs.readdirSync(d, { withFileTypes: true })) {
const full = path.join(d, entry.name)
if (entry.isDirectory()) walk(full)
else if (SCAN_EXTENSIONS.has(path.extname(entry.name))) out.push(full)
}
}
walk(dir)
return out.sort()
}
function main(): void {
if (!fs.existsSync(CORE_DIR)) {
console.error(`check-encoding-health: core/ directory not found at ${CORE_DIR} — cannot verify (fail-closed)`)
process.exit(1)
}
const files = collectFiles(CORE_DIR)
if (files.length === 0) {
console.error(`check-encoding-health: 0 scannable files under ${CORE_DIR} — cannot verify (fail-closed)`)
process.exit(1)
}
const violations: EncodingViolation[] = []
for (const f of files) {
try {
violations.push(...scanFile(f))
} catch (err) {
console.error(`check-encoding-health: failed to read ${f}: ${(err as Error).message}`)
process.exit(1)
}
}
if (violations.length > 0) {
console.error(`check-encoding-health: ${violations.length} mojibake violation(s) in core/:`)
for (const v of violations) {
const rel = path.relative(ROOT, v.file)
console.error(` ${rel}:${v.line} [${v.signature}] ${JSON.stringify(v.match)}`)
}
process.exit(1)
}
console.log(`check-encoding-health: OK (${files.length} files scanned, 0 violations)`)
}
if (import.meta.main) main()