@cloud411716/fancy-webnovel 1.0.45 → 2.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (134) hide show
  1. package/client.js +183 -0
  2. package/cordis.patch.yml +0 -14
  3. package/index.js +21 -37
  4. package/lib/chinese-num.js +111 -0
  5. package/lib/deployed-state.js +141 -0
  6. package/lib/error-codes.js +104 -0
  7. package/lib/infra.js +569 -0
  8. package/lib/llm-wiki.js +109 -0
  9. package/lib/safe-rm.js +94 -0
  10. package/lib/scraper-utils.js +165 -0
  11. package/package.json +37 -5
  12. package/plugins/fancy-analyze/command-input.js +65 -0
  13. package/plugins/fancy-analyze/index.js +973 -0
  14. package/plugins/fancy-analyze/schema-analyze.md +379 -0
  15. package/plugins/fancy-analyze/schema-write.md +178 -0
  16. package/plugins/fancy-analyze/scripts/split_source.js +292 -0
  17. package/plugins/fancy-analyze/summary-progress.js +431 -0
  18. package/plugins/fancy-analyze/templates.js +881 -0
  19. package/plugins/fancy-bootstrap/index.js +168 -0
  20. package/plugins/fancy-bootstrap/scripts/init.js +143 -0
  21. package/plugins/fancy-bootstrap/scripts/path-utils.js +21 -0
  22. package/plugins/fancy-bootstrap/templates.js +54 -0
  23. package/plugins/fancy-import/import-core.js +1118 -0
  24. package/plugins/fancy-import/import-driver.js +581 -0
  25. package/plugins/fancy-import/index.js +730 -0
  26. package/plugins/fancy-import/prompts/01-import-prompt.md +106 -0
  27. package/plugins/fancy-import/templates.js +49 -0
  28. package/plugins/fancy-scan/index.js +319 -0
  29. package/plugins/fancy-scan/scripts/run-scan.js +520 -0
  30. package/plugins/fancy-scan/scripts/scrapers/fanqie-rank-scraper.js +794 -0
  31. package/plugins/fancy-scan/scripts/scrapers/jjwxc-rank-scraper.js +339 -0
  32. package/plugins/fancy-scan/scripts/scrapers/qimao-rank-scraper.js +611 -0
  33. package/plugins/fancy-scan/templates.js +295 -0
  34. package/plugins/fancy-story-builder/deploy-manifest.js +239 -0
  35. package/plugins/fancy-story-builder/deslop-driver.js +821 -0
  36. package/plugins/fancy-story-builder/flesh-output.js +155 -0
  37. package/plugins/fancy-story-builder/index.js +3152 -0
  38. package/plugins/fancy-story-builder/prompts/01-skeleton-prompt.md +553 -0
  39. package/plugins/fancy-story-builder/prompts/02-flesh-prompt.md +540 -0
  40. package/plugins/fancy-story-builder/prompts/03-rhythm-prompt.md +504 -0
  41. package/plugins/fancy-story-builder/prompts/04-writing-prompt.md +188 -0
  42. package/plugins/fancy-story-builder/prompts/05-deslop-prompt.md +96 -0
  43. package/plugins/fancy-story-builder/purpose-template.md +107 -0
  44. package/plugins/fancy-story-builder/references/formulas-rhythm.json +302 -0
  45. package/plugins/fancy-story-builder/references/formulas-skeleton.json +229 -0
  46. package/plugins/fancy-story-builder/references/glossary.md +585 -0
  47. package/plugins/fancy-story-builder/references/index.md +490 -0
  48. package/plugins/fancy-story-builder/references//345/206/231/344/275/234/346/214/207/345/257/274/00-index.md +174 -0
  49. package/plugins/fancy-story-builder/references//345/206/231/344/275/234/346/214/207/345/257/274/01-scene.md +150 -0
  50. package/plugins/fancy-story-builder/references//345/206/231/344/275/234/346/214/207/345/257/274/02-chapter-workflow.md +99 -0
  51. package/plugins/fancy-story-builder/references//345/206/231/344/275/234/346/214/207/345/257/274/03-opening-chapter.md +104 -0
  52. package/plugins/fancy-story-builder/references//345/206/231/344/275/234/346/214/207/345/257/274/04-dialogue-mastery.md +274 -0
  53. package/plugins/fancy-story-builder/references//345/206/231/344/275/234/346/214/207/345/257/274/05-genre-formulas.md +432 -0
  54. package/plugins/fancy-story-builder/references//345/206/231/344/275/234/346/214/207/345/257/274/06-tone-ensemble.md +259 -0
  55. package/plugins/fancy-story-builder/references//345/206/231/344/275/234/346/214/207/345/257/274/07-ledger-audit.md +222 -0
  56. package/plugins/fancy-story-builder/references//345/206/231/344/275/234/346/214/207/345/257/274/08-diagnosis-revision.md +205 -0
  57. package/plugins/fancy-story-builder/references//345/206/231/344/275/234/346/214/207/345/257/274/09-reading-power.md +135 -0
  58. package/plugins/fancy-story-builder/references//345/206/231/344/275/234/346/214/207/345/257/274/10-deslop.md +129 -0
  59. package/plugins/fancy-story-builder/references//350/212/202/345/245/217/346/214/207/345/257/274/00-index.md +98 -0
  60. package/plugins/fancy-story-builder/references//350/212/202/345/245/217/346/214/207/345/257/274/01-upgrade-rhythm.md +300 -0
  61. package/plugins/fancy-story-builder/references//350/212/202/345/245/217/346/214/207/345/257/274/02-arc-rhythm.md +168 -0
  62. package/plugins/fancy-story-builder/references//350/212/202/345/245/217/346/214/207/345/257/274/03-highpoint-hooks.md +189 -0
  63. package/plugins/fancy-story-builder/references//350/212/202/345/245/217/346/214/207/345/257/274/04-satisfaction.md +226 -0
  64. package/plugins/fancy-story-builder/references//350/212/202/345/245/217/346/214/207/345/257/274/05-foreshadow-density.md +195 -0
  65. package/plugins/fancy-story-builder/references//350/212/202/345/245/217/346/214/207/345/257/274/06-chapter-outline.md +226 -0
  66. package/plugins/fancy-story-builder/references//350/212/202/345/245/217/346/214/207/345/257/274/07-audit.md +163 -0
  67. package/plugins/fancy-story-builder/references//350/241/200/350/202/211/346/214/207/345/257/274/00-index.md +100 -0
  68. package/plugins/fancy-story-builder/references//350/241/200/350/202/211/346/214/207/345/257/274/01-character.md +239 -0
  69. package/plugins/fancy-story-builder/references//350/241/200/350/202/211/346/214/207/345/257/274/02-worldbuilding.md +245 -0
  70. package/plugins/fancy-story-builder/references//350/241/200/350/202/211/346/214/207/345/257/274/03-relations.md +181 -0
  71. package/plugins/fancy-story-builder/references//350/241/200/350/202/211/346/214/207/345/257/274/04-emotion-methods.md +286 -0
  72. package/plugins/fancy-story-builder/references//350/241/200/350/202/211/346/214/207/345/257/274/05-audit.md +177 -0
  73. package/plugins/fancy-story-builder/references//351/252/250/346/236/266/346/214/207/345/257/274/00-index.md +91 -0
  74. package/plugins/fancy-story-builder/references//351/252/250/346/236/266/346/214/207/345/257/274/01-positioning.md +208 -0
  75. package/plugins/fancy-story-builder/references//351/252/250/346/236/266/346/214/207/345/257/274/02-packaging.md +135 -0
  76. package/plugins/fancy-story-builder/references//351/252/250/346/236/266/346/214/207/345/257/274/03-entities.md +305 -0
  77. package/plugins/fancy-story-builder/references//351/252/250/346/236/266/346/214/207/345/257/274/04-conflict.md +328 -0
  78. package/plugins/fancy-story-builder/references//351/252/250/346/236/266/346/214/207/345/257/274/05-quota.md +187 -0
  79. package/plugins/fancy-story-builder/references//351/252/250/346/236/266/346/214/207/345/257/274/06-audit.md +142 -0
  80. package/plugins/fancy-story-builder/rhythm-output.js +140 -0
  81. package/plugins/fancy-story-builder/schema-setting.md +324 -0
  82. package/plugins/fancy-story-builder/scripts/ai-patterns.js +619 -0
  83. package/plugins/fancy-story-builder/scripts/check-guide-compliance.js +289 -0
  84. package/plugins/fancy-story-builder/scripts/content-quality.js +413 -0
  85. package/plugins/fancy-story-builder/scripts/deslop-validate.js +143 -0
  86. package/plugins/fancy-story-builder/scripts/error-classes.js +112 -0
  87. package/plugins/fancy-story-builder/scripts/flesh-validate.js +1189 -0
  88. package/plugins/fancy-story-builder/scripts/formula-engine.js +112 -0
  89. package/plugins/fancy-story-builder/scripts/graph-utils.js +1385 -0
  90. package/plugins/fancy-story-builder/scripts/label-coverage.js +171 -0
  91. package/plugins/fancy-story-builder/scripts/prompt-drift-check.js +441 -0
  92. package/plugins/fancy-story-builder/scripts/reading-power.js +298 -0
  93. package/plugins/fancy-story-builder/scripts/rhythm-validate.js +1216 -0
  94. package/plugins/fancy-story-builder/scripts/semantic-lint.js +377 -0
  95. package/plugins/fancy-story-builder/scripts/skeleton-quota.js +145 -0
  96. package/plugins/fancy-story-builder/scripts/skeleton-validate.js +1832 -0
  97. package/plugins/fancy-story-builder/scripts/stage-batches.js +1371 -0
  98. package/plugins/fancy-story-builder/scripts/stage-contract.js +417 -0
  99. package/plugins/fancy-story-builder/scripts/upstream-query.js +198 -0
  100. package/plugins/fancy-story-builder/scripts/writing-rules.js +167 -0
  101. package/plugins/fancy-story-builder/scripts/writing-validate.js +682 -0
  102. package/plugins/fancy-story-builder/skeleton-output.js +199 -0
  103. package/plugins/fancy-story-builder/stage-driver.js +1049 -0
  104. package/plugins/fancy-story-builder/templates//350/212/202/345/245/217//346/227/266/345/272/217/346/230/240/345/260/204/346/250/241/346/235/277.json +204 -0
  105. package/plugins/fancy-story-builder/templates//350/212/202/345/245/217//347/253/240/350/212/202/347/273/206/347/272/262/346/250/241/346/235/277.json +34 -0
  106. package/plugins/fancy-story-builder/templates//350/212/202/345/245/217//347/253/240/350/212/202/350/247/204/345/210/222/346/250/241/346/235/277.json +24 -0
  107. package/plugins/fancy-story-builder/templates//350/212/202/345/245/217//350/212/202/345/245/217/345/217/202/346/225/260/346/250/241/346/235/277.json +27 -0
  108. package/plugins/fancy-story-builder/templates//350/241/200/350/202/211//344/270/226/347/225/214/350/247/202/350/247/204/345/210/231/346/250/241/346/235/277.json +66 -0
  109. package/plugins/fancy-story-builder/templates//350/241/200/350/202/211//345/205/263/351/224/256/351/201/223/345/205/267/346/250/241/346/235/277.json +76 -0
  110. package/plugins/fancy-story-builder/templates//350/241/200/350/202/211//345/206/262/347/252/201/346/250/241/346/235/277.json +72 -0
  111. package/plugins/fancy-story-builder/templates//350/241/200/350/202/211//345/211/247/346/203/205/346/250/241/346/235/277.json +37 -0
  112. package/plugins/fancy-story-builder/templates//350/241/200/350/202/211//345/212/277/345/212/233/346/250/241/346/235/277.json +53 -0
  113. package/plugins/fancy-story-builder/templates//350/241/200/350/202/211//345/234/260/347/202/271/346/250/241/346/235/277.json +40 -0
  114. package/plugins/fancy-story-builder/templates//350/241/200/350/202/211//347/273/223/345/261/200/346/250/241/346/235/277.json +79 -0
  115. package/plugins/fancy-story-builder/templates//350/241/200/350/202/211//350/247/222/350/211/262/346/250/241/346/235/277.json +132 -0
  116. package/plugins/fancy-story-builder/templates//351/252/250/346/236/266//345/205/263/347/263/273/346/250/241/346/235/277.json +133 -0
  117. package/plugins/fancy-story-builder/templates//351/252/250/346/236/266//345/256/236/344/275/223/346/250/241/346/235/277.json +160 -0
  118. package/plugins/fancy-story-builder/templates//351/252/250/346/236/266//345/261/202/347/272/247/346/250/241/346/235/277.json +65 -0
  119. package/plugins/fancy-story-builder/templates//351/252/250/346/236/266//346/241/206/346/236/266/346/250/241/346/235/277.json +134 -0
  120. package/plugins/fancy-story-builder/templates.js +1248 -0
  121. package/plugins/fancy-story-builder/wiki-references.js +204 -0
  122. package/plugins/fancy-story-builder/writing-driver.js +1061 -0
  123. package/plugins/fancy-topic/index.js +704 -0
  124. package/plugins/fancy-topic/scripts/select-topic.js +274 -0
  125. package/plugins/fancy-topic/templates.js +373 -0
  126. package/commands/bootstrap/index.js +0 -160
  127. package/commands/bootstrap/init-project.js +0 -121
  128. package/commands/scan/index.js +0 -275
  129. package/commands/scan/platforms/fanqie.js +0 -146
  130. package/commands/scan/platforms/jinjiang.js +0 -143
  131. package/commands/scan/platforms/qidian.js +0 -102
  132. package/commands/scan/platforms/qimao.js +0 -127
  133. package/commands/scan/scraper.js +0 -195
  134. package/infra.js +0 -160
@@ -0,0 +1,289 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * check-guide-compliance.js v1.2
4
+ * 源码中零反引号。
5
+ *
6
+ * v1.2 变更(修复批次 P2-2 / P2-3 + 消除字面反引号):
7
+ * - 原版 RANGE_SKIP_KEYWORDS、错误代码正则、字符串字面量中含字面反引号,
8
+ * 本身违反本文件检查的规则,且可能破坏 markdown 渲染。
9
+ * 本版统一用 String.fromCharCode(96) 生成反引号字符与正则。
10
+ * - P2-2:路径含 audit 的文件豁免 ERROR_CODE_IN_BODY 检查
11
+ * (audit 文件本质是错误代码处理手册,错误代码出现在正文是必要的)
12
+ * - P2-3:路径含 写作指导 的文件豁免 RANGE_IN_GUIDE 检查
13
+ * (写作指导明确允许具体字数/章节号作为写法参考)
14
+ * - ERROR_CODE_ALLOWED_SECTION 补充 audit 文件的主要节
15
+ *
16
+ * 检查五条禁令:
17
+ * 1. 不出现数字区间(示例节、代码块、表格、引用段除外)
18
+ * 2. 不出现 - [ ] / - [x] 检查清单
19
+ * 3. 错误代码只允许出现在白名单节(audit 文件整体豁免)
20
+ * 4. 每个指导文件必须有"与其他文件的关系"节
21
+ * 5. 不重复枚举值(只检查显式的"X 个值:..."列举模式)
22
+ *
23
+ * 用法:
24
+ * node check-guide-compliance.js --dir=references/骨架指导
25
+ * node check-guide-compliance.js --file=references/骨架指导/01-positioning.md
26
+ * node check-guide-compliance.js --dir=references/骨架指导 --glossary=references/glossary.md
27
+ * 退出码:0=通过 1=有问题 2=脚本异常
28
+ */
29
+
30
+ import fs from 'node:fs';
31
+ import path from 'node:path';
32
+ import { fileURLToPath } from 'node:url';
33
+ import { parseGlossaryFromMd } from './graph-utils.js';
34
+
35
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
36
+
37
+ // ============================================================
38
+ // 反引号字符与正则(零反引号约定)
39
+ // ============================================================
40
+ const BT_CHAR = String.fromCharCode(96);
41
+ const BT_RE_GLOBAL = new RegExp(BT_CHAR, 'g');
42
+
43
+ // ============================================================
44
+ // 参数解析
45
+ // ============================================================
46
+ function parseArgs() {
47
+ const args = process.argv.slice(2);
48
+ const result = { dir: null, file: null, glossary: null };
49
+ for (const arg of args) {
50
+ const m = arg.match(/^--(\w+)=(.+)$/);
51
+ if (m) result[m[1]] = m[2];
52
+ }
53
+ return result;
54
+ }
55
+
56
+ // ============================================================
57
+ // 路径分类(P2-2 / P2-3)
58
+ // ============================================================
59
+ function classifyPath(filePath) {
60
+ const norm = filePath.replace(/\\/g, '/');
61
+ return {
62
+ isAudit: /\/\d+-audit\.md$/i.test(norm) || /\/audit\.md$/i.test(norm),
63
+ isWritingGuide: /\/写作指导\//.test(norm)
64
+ };
65
+ }
66
+
67
+ // ============================================================
68
+ // 检查规则配置
69
+ // ============================================================
70
+
71
+ // 注意:示例类关键词("示例"/"例如"/"比如")只用于 inExampleSection 的补充,
72
+ // 正文段落中如果写"例如:每卷 5-8 个单元"仍会被报(这是正确行为)
73
+ const RANGE_SKIP_KEYWORDS = [
74
+ '见 formulas-', '由 formulas-',
75
+ '见 模板/', '由 模板/',
76
+ '见 glossary', '由 glossary',
77
+ '见 ' + BT_CHAR + 'formulas-',
78
+ '由 ' + BT_CHAR + 'formulas-',
79
+ '见 ' + BT_CHAR + '模板/',
80
+ '由 ' + BT_CHAR + '模板/',
81
+ '见 ' + BT_CHAR + 'glossary'
82
+ ];
83
+
84
+ const RANGE_PATTERNS = [
85
+ { re: /至少\s*\d+\s*[个条卷章人字次位天]/, label: '至少 N 个' },
86
+ { re: /至多\s*\d+/, label: '至多 N' },
87
+ { re: /不超过\s*\d+/, label: '不超过 N' },
88
+ { re: /不低于\s*\d+/, label: '不低于 N' },
89
+ { re: /\d+\s*[-~到至]\s*\d+\s*[个条卷章人字次位天]/, label: 'N-M 区间' },
90
+ { re: /\bceil\(|\bfloor\(/, label: '数学函数' }
91
+ ];
92
+
93
+ // 错误代码前缀
94
+ const ERROR_CODE_PREFIXES = /^(EXTRA_FIELD|REQUIRED|TYPE|PATTERN|ENUM|ENUM_MISSING|ID_DUP|ID_ORPHAN|NAME_DUP|SKELETON_|FLESH_|G_|DERIVATION_|QUANTITY_|CAPACITY_|VOLUME_|SPAN_|RHYTHM_|CHAP_|TIMING|ARC_|XREF|ANTAG_|SELF_|BRANCH_|NODE_|MAIN_|HOOK_|ACT_|FACTION_|ENDING_|BOOK_|LOCATION_|CHARSET_|PLACEHOLDER|ARG|READ|TEMPLATE|GLOSSARY_LOAD|FORMULAS_LOAD|UPSTREAM_|FLESH_GROUP|FLESH_FREETEXT|ENUM_VALUE_|RHYTHM_COVERAGE|RHYTHM_ACT|NO_SELF_RELATION|EMPTY_HARD_CONSTRAINTS|CROSS_TYPE_NAME_DUP)/;
95
+
96
+ // v1.2:白名单节补充 audit 文件的主要节(双保险,audit 文件也会被整体豁免)
97
+ const ERROR_CODE_ALLOWED_SECTION = /与其他文件的关系|错误代码速查|常见错误|故障排查|BLOCKED|全量校验后的处理|跨文件错误的特殊处理|单文件校验失败的处理|前置检查失败的处理/;
98
+
99
+ // 错误代码匹配正则(零反引号:用 BT_CHAR 拼接)
100
+ const ERROR_CODE_RE = new RegExp(BT_CHAR + '([A-Z][A-Z_]{2,})' + BT_CHAR, 'g');
101
+
102
+ // ============================================================
103
+ // 单文件检查
104
+ // ============================================================
105
+ function checkFile(filePath, glossaryEnums) {
106
+ const content = fs.readFileSync(filePath, 'utf-8');
107
+ const lines = content.split('\n');
108
+
109
+ const errors = [];
110
+ const warnings = [];
111
+
112
+ const { isAudit, isWritingGuide } = classifyPath(filePath);
113
+
114
+ let inCodeBlock = false;
115
+ let inExampleSection = false;
116
+ let inErrorCodeAllowedSection = false;
117
+ let hasRelationSection = false;
118
+
119
+ for (let i = 0; i < lines.length; i++) {
120
+ const line = lines[i];
121
+ const lineNum = i + 1;
122
+ const trimmed = line.trim();
123
+
124
+ if (trimmed.startsWith('```')) {
125
+ inCodeBlock = !inCodeBlock;
126
+ continue;
127
+ }
128
+ if (inCodeBlock) continue;
129
+
130
+ if (line.startsWith('## ')) {
131
+ inExampleSection = /示例/.test(line);
132
+ inErrorCodeAllowedSection = ERROR_CODE_ALLOWED_SECTION.test(line);
133
+ if (/与其他文件的关系/.test(line)) hasRelationSection = true;
134
+ continue;
135
+ }
136
+
137
+ // 检查 2:检查清单
138
+ if (/^\s*-\s*\[[ xX]\]/.test(line)) {
139
+ errors.push({
140
+ line: lineNum,
141
+ code: 'CHECKLIST_IN_GUIDE',
142
+ message: '出现检查清单项: ' + trimmed.slice(0, 60)
143
+ });
144
+ continue;
145
+ }
146
+
147
+ // 检查 1:数字区间(P2-3:写作指导豁免)
148
+ if (!isWritingGuide && !inExampleSection && !trimmed.startsWith('|')) {
149
+ let skip = false;
150
+ for (const kw of RANGE_SKIP_KEYWORDS) {
151
+ if (line.includes(kw)) { skip = true; break; }
152
+ }
153
+ if (!skip) {
154
+ for (const { re, label } of RANGE_PATTERNS) {
155
+ if (re.test(line)) {
156
+ warnings.push({
157
+ line: lineNum,
158
+ code: 'RANGE_IN_GUIDE',
159
+ message: '疑似数字区间(' + label + '): ' + trimmed.slice(0, 60)
160
+ });
161
+ break;
162
+ }
163
+ }
164
+ }
165
+ }
166
+
167
+ // 检查 3:错误代码(P2-2:audit 文件整体豁免;其余看白名单节)
168
+ if (!isAudit && !inErrorCodeAllowedSection) {
169
+ ERROR_CODE_RE.lastIndex = 0;
170
+ let m;
171
+ while ((m = ERROR_CODE_RE.exec(line)) !== null) {
172
+ const code = m[1];
173
+ if (ERROR_CODE_PREFIXES.test(code)) {
174
+ warnings.push({
175
+ line: lineNum,
176
+ code: 'ERROR_CODE_IN_BODY',
177
+ message: '正文中出现错误代码 ' + BT_CHAR + code + BT_CHAR + '(应在"与其他文件的关系"或"错误代码速查/常见错误/故障排查/BLOCKED/全量校验后的处理/跨文件错误的特殊处理"节)'
178
+ });
179
+ }
180
+ }
181
+ }
182
+ }
183
+
184
+ // 检查 4:必须有"与其他文件的关系"节
185
+ if (!hasRelationSection) {
186
+ errors.push({
187
+ line: 0,
188
+ code: 'MISSING_RELATION_SECTION',
189
+ message: '缺少"与其他文件的关系"节'
190
+ });
191
+ }
192
+
193
+ // 检查 5:枚举值连续列举
194
+ const listMatches = [...content.matchAll(/(\d+)\s*个值[::]\s*([^\n]+)/g)];
195
+ for (const m of listMatches) {
196
+ const listPart = m[2];
197
+ for (const [name, vals] of Object.entries(glossaryEnums)) {
198
+ if (vals.length < 2) continue;
199
+ if (vals.every(v => listPart.includes(v))) {
200
+ warnings.push({
201
+ line: 0,
202
+ code: 'ENUM_LIST_IN_GUIDE',
203
+ message: '可能重复枚举 ' + name + ' 的所有值(应改为"见 glossary 的 ' + name + '")'
204
+ });
205
+ }
206
+ }
207
+ }
208
+
209
+ return { errors, warnings };
210
+ }
211
+
212
+ // ============================================================
213
+ // 主流程
214
+ // ============================================================
215
+ function main() {
216
+ const args = parseArgs();
217
+
218
+ if (!args.dir && !args.file) {
219
+ process.stderr.write('需要 --dir 或 --file\n');
220
+ return process.exit(2);
221
+ }
222
+
223
+ if (!args.glossary) {
224
+ args.glossary = path.join(__dirname, 'glossary.md');
225
+ }
226
+
227
+ if (!fs.existsSync(args.glossary)) {
228
+ process.stderr.write('glossary.md 不存在: ' + args.glossary + '\n');
229
+ return process.exit(2);
230
+ }
231
+
232
+ let glossaryEnums = {};
233
+ try {
234
+ const parsed = parseGlossaryFromMd(fs.readFileSync(args.glossary, 'utf-8'));
235
+ glossaryEnums = parsed.enums;
236
+ } catch (e) {
237
+ process.stderr.write('glossary.md 解析失败: ' + e.message + '\n');
238
+ return process.exit(2);
239
+ }
240
+
241
+ const files = [];
242
+ if (args.file) {
243
+ files.push(args.file);
244
+ } else {
245
+ if (!fs.existsSync(args.dir)) {
246
+ process.stderr.write('目录不存在: ' + args.dir + '\n');
247
+ return process.exit(2);
248
+ }
249
+ for (const f of fs.readdirSync(args.dir)) {
250
+ if (f.endsWith('.md')) {
251
+ files.push(path.join(args.dir, f));
252
+ }
253
+ }
254
+ }
255
+
256
+ const results = [];
257
+ let totalErrors = 0;
258
+ let totalWarnings = 0;
259
+
260
+ for (const file of files) {
261
+ if (!fs.existsSync(file)) {
262
+ results.push({ file: file, errors: [{ code: 'READ', message: '文件不存在' }], warnings: [] });
263
+ totalErrors++;
264
+ continue;
265
+ }
266
+ try {
267
+ const result = checkFile(file, glossaryEnums);
268
+ results.push({ file: file, errors: result.errors, warnings: result.warnings });
269
+ totalErrors += result.errors.length;
270
+ totalWarnings += result.warnings.length;
271
+ } catch (e) {
272
+ results.push({ file: file, errors: [{ code: 'CHECK_FAILED', message: e.message }], warnings: [] });
273
+ totalErrors++;
274
+ }
275
+ }
276
+
277
+ const output = {
278
+ status: totalErrors === 0 ? 0 : 1,
279
+ total_files: files.length,
280
+ total_errors: totalErrors,
281
+ total_warnings: totalWarnings,
282
+ results: results
283
+ };
284
+
285
+ process.stdout.write(JSON.stringify(output, null, 2) + '\n');
286
+ process.exitCode = totalErrors === 0 ? 0 : 1;
287
+ }
288
+
289
+ main();