lint-character-aura.mjs 3.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121
  1. import { readdirSync, readFileSync, existsSync, statSync } from "fs"
  2. import { resolve, join } from "path"
  3. import process from "process"
  4. const ROOT = process.argv[2] || process.cwd()
  5. const NVWA = resolve(ROOT, "skills", "soulskill")
  6. const CHARACTER_AURA_PATH = resolve(ROOT, "src", "lib", "novel", "character-aura.ts")
  7. const CURLY_LEFT = "\u201C"
  8. const CURLY_RIGHT = "\u201D"
  9. const ENGLISH_BASED_SLUGS = new Set([
  10. "elon-musk", "feynman", "munger", "charlie-munger",
  11. "naval", "steve-jobs", "taleb", "trump",
  12. "paul-graham", "andrei-kapasi", "ilya-sutskaver",
  13. "mr-beast", "sun-yuchen",
  14. ])
  15. let errors = 0
  16. function report(file, message) {
  17. console.error(` ${file} ${message}`)
  18. errors++
  19. }
  20. function checkCurlyQuotes(content, file) {
  21. const lines = content.split("\n")
  22. for (let i = 0; i < lines.length; i++) {
  23. if (lines[i].includes(CURLY_LEFT) || lines[i].includes(CURLY_RIGHT)) {
  24. report(file, `第${i + 1}行 包含中文弯引号 ""(oxc 不兼容),请替换为 「」`)
  25. }
  26. }
  27. }
  28. function stripFrontmatter(content) {
  29. const start = content.indexOf("---")
  30. if (start !== 0) return content
  31. const end = content.indexOf("---", start + 3)
  32. if (end === -1) return content
  33. return content.slice(end + 3)
  34. }
  35. function isEnglishBased(folderName) {
  36. return ENGLISH_BASED_SLUGS.has(folderName)
  37. }
  38. function walk(dir, cb, depth = 0) {
  39. if (!existsSync(dir)) return
  40. const entries = readdirSync(dir)
  41. for (const entry of entries) {
  42. if (entry.startsWith(".")) continue
  43. const full = join(dir, entry)
  44. const stat = statSync(full)
  45. if (stat.isDirectory()) {
  46. if (entry.startsWith("x-") || entry.startsWith("SKILL")) continue
  47. if (depth === 0 && isEnglishBased(entry)) continue
  48. walk(full, cb, depth + 1)
  49. } else if (entry.endsWith(".md")) {
  50. if (depth >= 1 && isEnglishBased(full.split("\\").slice(-3, -2)[0] || "")) continue
  51. cb(full)
  52. }
  53. }
  54. }
  55. console.log("=== 角色灵魂内容校验 ===")
  56. console.log()
  57. console.log("--- character-aura.ts 弯引号 + 字段英文检查 ---")
  58. const auraContent = readFileSync(CHARACTER_AURA_PATH, "utf8")
  59. checkCurlyQuotes(auraContent, "src/lib/novel/character-aura.ts")
  60. const createBuiltInCalls = auraContent.match(/createBuiltInAura\([\s\S]*?\)\s*,?\s*$/gm) || []
  61. for (const call of createBuiltInCalls) {
  62. const stringArgs = call.match(/"([^"]*)"/g) || []
  63. if (stringArgs.length < 8) continue
  64. const fields = {
  65. name: stringArgs[2].replace(/"/g, ""),
  66. corpus: stringArgs[3].replace(/"/g, ""),
  67. expressionDna: stringArgs[4].replace(/"/g, ""),
  68. mentalModel: stringArgs[5].replace(/"/g, ""),
  69. decisionHeuristics: stringArgs[6].replace(/"/g, ""),
  70. valueAntiPatterns: stringArgs[7].replace(/"/g, ""),
  71. }
  72. for (const [fieldName, value] of Object.entries(fields)) {
  73. if (/[A-Za-z]/.test(value)) {
  74. report("src/lib/novel/character-aura.ts", `角色 ${fields.name} 字段 ${fieldName} 包含英文: ${value.slice(0, 60)}`)
  75. }
  76. if (value.includes(CURLY_LEFT) || value.includes(CURLY_RIGHT)) {
  77. report("src/lib/novel/character-aura.ts", `角色 ${fields.name} 字段 ${fieldName} 包含弯引号`)
  78. }
  79. }
  80. }
  81. console.log()
  82. console.log("--- 中文角色 SKILL.md 弯引号检查 ---")
  83. walk(NVWA, (file) => {
  84. if (!file.endsWith("SKILL.md")) return
  85. const content = readFileSync(file, "utf8")
  86. const relPath = file.replace(ROOT, "").replace(/^[\\/]/, "")
  87. checkCurlyQuotes(stripFrontmatter(content), relPath)
  88. })
  89. console.log()
  90. console.log("--- 中文角色研究文件弯引号检查 ---")
  91. walk(NVWA, (file) => {
  92. if (!file.includes("research") || !file.endsWith(".md")) return
  93. const content = readFileSync(file, "utf8")
  94. const relPath = file.replace(ROOT, "").replace(/^[\\/]/, "")
  95. checkCurlyQuotes(content, relPath)
  96. })
  97. console.log()
  98. if (errors === 0) {
  99. console.log("✓ 全部通过!没有发现英文或弯引号问题。")
  100. process.exit(0)
  101. } else {
  102. console.log(`✗ 发现 ${errors} 个问题,请修复后重新运行。`)
  103. process.exit(1)
  104. }