@mycodemap/mycodemap 0.2.0 → 0.3.4

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 (99) hide show
  1. package/CHANGELOG.md +85 -0
  2. package/dist/cli/commands/ship/analyzer.d.ts +26 -0
  3. package/dist/cli/commands/ship/analyzer.d.ts.map +1 -0
  4. package/dist/cli/commands/ship/analyzer.js +143 -0
  5. package/dist/cli/commands/ship/analyzer.js.map +1 -0
  6. package/dist/cli/commands/ship/checker.d.ts +20 -0
  7. package/dist/cli/commands/ship/checker.d.ts.map +1 -0
  8. package/dist/cli/commands/ship/checker.js +86 -0
  9. package/dist/cli/commands/ship/checker.js.map +1 -0
  10. package/dist/cli/commands/ship/index.d.ts +17 -0
  11. package/dist/cli/commands/ship/index.d.ts.map +1 -0
  12. package/dist/cli/commands/ship/index.js +51 -0
  13. package/dist/cli/commands/ship/index.js.map +1 -0
  14. package/dist/cli/commands/ship/monitor.d.ts +19 -0
  15. package/dist/cli/commands/ship/monitor.d.ts.map +1 -0
  16. package/dist/cli/commands/ship/monitor.js +105 -0
  17. package/dist/cli/commands/ship/monitor.js.map +1 -0
  18. package/dist/cli/commands/ship/pipeline.d.ts +23 -0
  19. package/dist/cli/commands/ship/pipeline.d.ts.map +1 -0
  20. package/dist/cli/commands/ship/pipeline.js +146 -0
  21. package/dist/cli/commands/ship/pipeline.js.map +1 -0
  22. package/dist/cli/commands/ship/publisher.d.ts +11 -0
  23. package/dist/cli/commands/ship/publisher.d.ts.map +1 -0
  24. package/dist/cli/commands/ship/publisher.js +75 -0
  25. package/dist/cli/commands/ship/publisher.js.map +1 -0
  26. package/dist/cli/commands/ship/rules/confidence-rules.d.ts +48 -0
  27. package/dist/cli/commands/ship/rules/confidence-rules.d.ts.map +1 -0
  28. package/dist/cli/commands/ship/rules/confidence-rules.js +122 -0
  29. package/dist/cli/commands/ship/rules/confidence-rules.js.map +1 -0
  30. package/dist/cli/commands/ship/rules/quality-rules.d.ts +25 -0
  31. package/dist/cli/commands/ship/rules/quality-rules.d.ts.map +1 -0
  32. package/dist/cli/commands/ship/rules/quality-rules.js +134 -0
  33. package/dist/cli/commands/ship/rules/quality-rules.js.map +1 -0
  34. package/dist/cli/commands/ship/rules/version-rules.d.ts +24 -0
  35. package/dist/cli/commands/ship/rules/version-rules.d.ts.map +1 -0
  36. package/dist/cli/commands/ship/rules/version-rules.js +75 -0
  37. package/dist/cli/commands/ship/rules/version-rules.js.map +1 -0
  38. package/dist/cli/commands/ship/versioner.d.ts +12 -0
  39. package/dist/cli/commands/ship/versioner.d.ts.map +1 -0
  40. package/dist/cli/commands/ship/versioner.js +92 -0
  41. package/dist/cli/commands/ship/versioner.js.map +1 -0
  42. package/dist/cli/index.js +9 -0
  43. package/dist/cli/index.js.map +1 -1
  44. package/docs/PUBLISHING.md +344 -34
  45. package/docs/ai-guide/COMMANDS.md +34 -0
  46. package/docs/rules/pre-release-checklist.md +426 -0
  47. package/package.json +8 -2
  48. package/scripts/.githooks/commit-msg +31 -0
  49. package/scripts/.githooks/pre-commit +55 -0
  50. package/scripts/benchmark.ts +209 -0
  51. package/scripts/hooks/commit-msg +24 -0
  52. package/scripts/hooks/install-hooks.sh +29 -0
  53. package/scripts/hooks/pre-commit +60 -0
  54. package/scripts/pre-release-check.js +717 -0
  55. package/scripts/release.sh +142 -0
  56. package/scripts/run-benchmark.sh +29 -0
  57. package/scripts/validate-ai-docs.js +294 -0
  58. package/scripts/validate-docs.js +238 -0
  59. package/scripts/validate-pack.js +86 -0
  60. package/dist/ai/claude.d.ts +0 -38
  61. package/dist/ai/claude.d.ts.map +0 -1
  62. package/dist/ai/claude.js +0 -169
  63. package/dist/ai/claude.js.map +0 -1
  64. package/dist/ai/codex.d.ts +0 -38
  65. package/dist/ai/codex.d.ts.map +0 -1
  66. package/dist/ai/codex.js +0 -169
  67. package/dist/ai/codex.js.map +0 -1
  68. package/dist/ai/factory.d.ts +0 -48
  69. package/dist/ai/factory.d.ts.map +0 -1
  70. package/dist/ai/factory.js +0 -95
  71. package/dist/ai/factory.js.map +0 -1
  72. package/dist/ai/index.d.ts +0 -12
  73. package/dist/ai/index.d.ts.map +0 -1
  74. package/dist/ai/index.js +0 -29
  75. package/dist/ai/index.js.map +0 -1
  76. package/dist/ai/provider.d.ts +0 -70
  77. package/dist/ai/provider.d.ts.map +0 -1
  78. package/dist/ai/provider.js +0 -31
  79. package/dist/ai/provider.js.map +0 -1
  80. package/dist/ai/subagent-caller.d.ts +0 -90
  81. package/dist/ai/subagent-caller.d.ts.map +0 -1
  82. package/dist/ai/subagent-caller.js +0 -280
  83. package/dist/ai/subagent-caller.js.map +0 -1
  84. package/dist/ai/types.d.ts +0 -70
  85. package/dist/ai/types.d.ts.map +0 -1
  86. package/dist/ai/types.js +0 -5
  87. package/dist/ai/types.js.map +0 -1
  88. package/dist/generator/ai-overview.d.ts +0 -51
  89. package/dist/generator/ai-overview.d.ts.map +0 -1
  90. package/dist/generator/ai-overview.js +0 -160
  91. package/dist/generator/ai-overview.js.map +0 -1
  92. package/dist/orchestrator/ai-feed-generator.d.ts +0 -210
  93. package/dist/orchestrator/ai-feed-generator.d.ts.map +0 -1
  94. package/dist/orchestrator/ai-feed-generator.js +0 -377
  95. package/dist/orchestrator/ai-feed-generator.js.map +0 -1
  96. package/docs/archive/test-report-symbol-search.md +0 -384
  97. package/docs/archive/test-scenario-4-complexity-analysis.md +0 -460
  98. package/docs/archive/test_report_scenario5.md +0 -615
  99. package/docs/archive/test_scenario_3_impact_analysis_report.md +0 -520
@@ -0,0 +1,717 @@
1
+ // [META] since:2026-03-22 | owner:release-team | stable:true
2
+ // [WHY] 发布前全面检查 - 基于 AI_FRIENDLINESS_AUDIT.md 的强制护栏
3
+ // 确保每次发布都符合 AI 友好文档的行业标准
4
+
5
+ import { existsSync, readFileSync, statSync } from 'node:fs';
6
+ import { readdir } from 'node:fs/promises';
7
+ import path from 'node:path';
8
+ import { fileURLToPath } from 'node:url';
9
+
10
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
11
+ const rootDir = path.resolve(__dirname, '..');
12
+
13
+ // 颜色输出
14
+ const colors = {
15
+ red: (s) => `\x1b[31m${s}\x1b[0m`,
16
+ green: (s) => `\x1b[32m${s}\x1b[0m`,
17
+ yellow: (s) => `\x1b[33m${s}\x1b[0m`,
18
+ blue: (s) => `\x1b[34m${s}\x1b[0m`,
19
+ bold: (s) => `\x1b[1m${s}\x1b[0m`,
20
+ };
21
+
22
+ const { red, green, yellow, blue, bold } = colors;
23
+
24
+ // ============================================
25
+ // 检查配置
26
+ // ============================================
27
+
28
+ const MAX_FILE_SIZE_KB = 50; // 单个AI文档最大50KB
29
+ const MAX_TOTAL_AI_DOCS_KB = 500; // 所有AI文档总计最大500KB
30
+ const MAX_LLMSTXT_TOKEN_ESTIMATE = 5000; // llms.txt 估算token限制
31
+
32
+ const requiredAIDocs = [
33
+ { file: 'llms.txt', required: true, minSize: 100, maxSize: 10000 },
34
+ { file: 'AI_GUIDE.md', required: true, minSize: 1000, maxSize: 50000 },
35
+ { file: 'AI_DISCOVERY.md', required: true, minSize: 1000, maxSize: 50000 },
36
+ { file: 'ai-document-index.yaml', required: true, minSize: 500, maxSize: 50000 },
37
+ { file: 'docs/ai-guide/README.md', required: true, minSize: 500, maxSize: 30000 },
38
+ { file: 'docs/ai-guide/QUICKSTART.md', required: true, minSize: 1000, maxSize: 50000 },
39
+ { file: 'docs/ai-guide/COMMANDS.md', required: true, minSize: 2000, maxSize: 100000 },
40
+ { file: 'docs/ai-guide/OUTPUT.md', required: true, minSize: 2000, maxSize: 100000 },
41
+ { file: 'docs/ai-guide/PATTERNS.md', required: true, minSize: 2000, maxSize: 100000 },
42
+ { file: 'docs/ai-guide/PROMPTS.md', required: true, minSize: 2000, maxSize: 100000 },
43
+ { file: 'docs/ai-guide/INTEGRATION.md', required: true, minSize: 2000, maxSize: 150000 },
44
+ ];
45
+
46
+ // 版本必须一致的文件
47
+ const versionFiles = [
48
+ { file: 'package.json', extractor: (c) => JSON.parse(c).version },
49
+ { file: 'llms.txt', extractor: extractVersionFromText },
50
+ { file: 'ai-document-index.yaml', extractor: extractVersionFromYAML },
51
+ { file: 'AI_GUIDE.md', extractor: extractVersionFromText },
52
+ { file: 'AI_DISCOVERY.md', extractor: extractVersionFromText },
53
+ ];
54
+
55
+ // 必需的发布相关文件检查
56
+ const releaseRequirements = {
57
+ changelog: { file: 'CHANGELOG.md', required: true },
58
+ license: { file: 'LICENSE', required: true },
59
+ readme: { file: 'README.md', required: true },
60
+ };
61
+
62
+ // 必须存在的交叉引用
63
+ const requiredCrossRefs = [
64
+ { from: 'llms.txt', mustReference: ['AI_GUIDE.md', 'ai-document-index.yaml'] },
65
+ { from: 'AI_GUIDE.md', mustReference: ['AI_DISCOVERY.md', 'ai-document-index.yaml', 'docs/ai-guide/'] },
66
+ { from: 'README.md', mustReference: ['AI_GUIDE.md'] },
67
+ { from: 'AGENTS.md', mustReference: ['AI 友好'] },
68
+ { from: 'CLAUDE.md', mustReference: ['AI_GUIDE.md', 'docs/ai-guide/'] },
69
+ ];
70
+
71
+ // llms.txt 标准要求 (基于 llmstxt.org)
72
+ const llmsTxtStandards = {
73
+ mustHaveH1: /^#\s+\w+/m,
74
+ mustHaveSummary: /^>\s+/m,
75
+ mustHaveDocsSection: /##\s*(?:文档|Docs|快速开始|Quick Start)/i,
76
+ mustHaveLinks: /\[.+\]\(.+\)/,
77
+ recommendedSections: ['快速开始', '完整文档', 'Full Documentation', '可选', 'Optional'],
78
+ };
79
+
80
+ // ============================================
81
+ // 工具函数
82
+ // ============================================
83
+
84
+ function readText(filePath) {
85
+ const absolutePath = path.join(rootDir, filePath);
86
+ if (!existsSync(absolutePath)) return null;
87
+ return readFileSync(absolutePath, 'utf8');
88
+ }
89
+
90
+ function getFileSize(filePath) {
91
+ const absolutePath = path.join(rootDir, filePath);
92
+ if (!existsSync(absolutePath)) return 0;
93
+ return statSync(absolutePath).size;
94
+ }
95
+
96
+ function extractVersionFromText(content) {
97
+ // 匹配 "version: x.x.x" 或 "版本: x.x.x" 或 "Version x.x.x"
98
+ const match = content.match(/(?:version|版本)[:\s]+v?(\d+\.\d+\.\d+)/i);
99
+ return match ? match[1] : null;
100
+ }
101
+
102
+ function extractVersionFromYAML(content) {
103
+ // 匹配 YAML 中的 version: "x.x.x" 或 version: x.x.x
104
+ const match = content.match(/version[:\s]+["']?(\d+\.\d+\.\d+)["']?/i);
105
+ return match ? match[1] : null;
106
+ }
107
+
108
+ function estimateTokens(text) {
109
+ // 简化的token估算:英文单词 + 中文字符
110
+ const englishWords = (text.match(/\b\w+\b/g) || []).length;
111
+ const chineseChars = (text.match(/[\u4e00-\u9fa5]/g) || []).length;
112
+ return Math.ceil(englishWords * 1.3 + chineseChars * 2);
113
+ }
114
+
115
+ // ============================================
116
+ // 检查项
117
+ // ============================================
118
+
119
+ async function checkRequiredFiles(failures) {
120
+ console.log(blue('\n📁 1. AI文档完整性检查\n'));
121
+
122
+ let totalSize = 0;
123
+
124
+ for (const { file, required, minSize, maxSize } of requiredAIDocs) {
125
+ const content = readText(file);
126
+ const size = getFileSize(file);
127
+ totalSize += size;
128
+
129
+ if (!content) {
130
+ if (required) {
131
+ failures.push({
132
+ type: 'missing_required_file',
133
+ file,
134
+ message: `缺少必需的AI文档: ${file}`,
135
+ });
136
+ console.log(red(` ❌ ${file} - 文件不存在`));
137
+ } else {
138
+ console.log(yellow(` ⚠️ ${file} - 可选文件不存在`));
139
+ }
140
+ continue;
141
+ }
142
+
143
+ if (size < minSize) {
144
+ failures.push({
145
+ type: 'file_too_small',
146
+ file,
147
+ message: `${file} 太小 (${size} bytes, 最小要求 ${minSize})`,
148
+ });
149
+ console.log(red(` ❌ ${file} - 文件太小 (${size} bytes)`));
150
+ } else if (size > maxSize) {
151
+ failures.push({
152
+ type: 'file_too_large',
153
+ file,
154
+ message: `${file} 太大 (${Math.round(size/1024)}KB, 最大允许 ${Math.round(maxSize/1024)}KB)`,
155
+ });
156
+ console.log(red(` ❌ ${file} - 文件太大 (${Math.round(size/1024)}KB)`));
157
+ } else {
158
+ console.log(green(` ✅ ${file} (${Math.round(size/1024)}KB)`));
159
+ }
160
+ }
161
+
162
+ // 总大小检查
163
+ const totalSizeKB = Math.round(totalSize / 1024);
164
+ if (totalSizeKB > MAX_TOTAL_AI_DOCS_KB) {
165
+ failures.push({
166
+ type: 'total_size_exceeded',
167
+ message: `AI文档总大小 ${totalSizeKB}KB 超过限制 ${MAX_TOTAL_AI_DOCS_KB}KB`,
168
+ });
169
+ console.log(red(`\n ❌ 总大小: ${totalSizeKB}KB (限制: ${MAX_TOTAL_AI_DOCS_KB}KB)`));
170
+ } else {
171
+ console.log(green(`\n ✅ 总大小: ${totalSizeKB}KB (限制: ${MAX_TOTAL_AI_DOCS_KB}KB)`));
172
+ }
173
+ }
174
+
175
+ async function checkLlmsTxtStandards(failures) {
176
+ console.log(blue('\n📋 2. llms.txt 标准格式检查\n'));
177
+
178
+ const content = readText('llms.txt');
179
+ if (!content) {
180
+ failures.push({
181
+ type: 'missing_llms_txt',
182
+ message: '缺少 llms.txt 文件 (llmstxt.org 标准要求)',
183
+ });
184
+ console.log(red(' ❌ llms.txt 不存在'));
185
+ return;
186
+ }
187
+
188
+ const checks = [
189
+ { name: 'H1标题', test: llmsTxtStandards.mustHaveH1, required: true },
190
+ { name: '摘要引用', test: llmsTxtStandards.mustHaveSummary, required: true },
191
+ { name: '文档章节', test: llmsTxtStandards.mustHaveDocsSection, required: true },
192
+ { name: '链接格式', test: llmsTxtStandards.mustHaveLinks, required: true },
193
+ ];
194
+
195
+ for (const { name, test, required } of checks) {
196
+ if (test.test(content)) {
197
+ console.log(green(` ✅ ${name}`));
198
+ } else if (required) {
199
+ failures.push({
200
+ type: 'llms_txt_standard_violation',
201
+ file: 'llms.txt',
202
+ message: `llms.txt 缺少必需的 ${name}`,
203
+ });
204
+ console.log(red(` ❌ ${name} (必需)`));
205
+ } else {
206
+ console.log(yellow(` ⚠️ ${name} (建议)`));
207
+ }
208
+ }
209
+
210
+ // Token效率检查
211
+ const tokens = estimateTokens(content);
212
+ if (tokens > MAX_LLMSTXT_TOKEN_ESTIMATE) {
213
+ failures.push({
214
+ type: 'llms_txt_token_limit',
215
+ file: 'llms.txt',
216
+ message: `llms.txt 估算 token 数 ${tokens} 超过限制 ${MAX_LLMSTXT_TOKEN_ESTIMATE}`,
217
+ });
218
+ console.log(red(` ❌ Token估算: ${tokens} (限制: ${MAX_LLMSTXT_TOKEN_ESTIMATE})`));
219
+ } else {
220
+ console.log(green(` ✅ Token估算: ${tokens} (限制: ${MAX_LLMSTXT_TOKEN_ESTIMATE})`));
221
+ }
222
+ }
223
+
224
+ async function checkVersionConsistency(failures) {
225
+ console.log(blue('\n🔢 3. 版本一致性检查\n'));
226
+
227
+ const versions = {};
228
+
229
+ for (const { file, extractor } of versionFiles) {
230
+ const content = readText(file);
231
+ if (!content) {
232
+ console.log(yellow(` ⚠️ ${file} - 文件不存在`));
233
+ continue;
234
+ }
235
+
236
+ const version = extractor(content);
237
+ if (version) {
238
+ versions[file] = version;
239
+ console.log(` 📄 ${file}: v${version}`);
240
+ } else {
241
+ console.log(yellow(` ⚠️ ${file}: 未找到版本号`));
242
+ }
243
+ }
244
+
245
+ const uniqueVersions = [...new Set(Object.values(versions))];
246
+ if (uniqueVersions.length === 0) {
247
+ failures.push({
248
+ type: 'no_version_found',
249
+ message: '未在任何文件中找到版本号',
250
+ });
251
+ console.log(red('\n ❌ 未找到任何版本号'));
252
+ } else if (uniqueVersions.length > 1) {
253
+ failures.push({
254
+ type: 'version_mismatch',
255
+ message: `版本号不一致: ${uniqueVersions.join(', ')}`,
256
+ details: versions,
257
+ });
258
+ console.log(red(`\n ❌ 版本号不一致: ${uniqueVersions.join(', ')}`));
259
+ console.log(red(` 请确保所有文件版本号一致`));
260
+ } else {
261
+ console.log(green(`\n ✅ 所有文件版本一致: v${uniqueVersions[0]}`));
262
+ }
263
+
264
+ // 语义版本格式检查
265
+ const pkgVersion = versions['package.json'];
266
+ if (pkgVersion && !/^\d+\.\d+\.\d+(-[\w.]+)?$/.test(pkgVersion)) {
267
+ failures.push({
268
+ type: 'invalid_semver',
269
+ message: `package.json 版本号 "${pkgVersion}" 不符合语义化版本规范`,
270
+ });
271
+ console.log(red(` ❌ 版本号格式无效: ${pkgVersion}`));
272
+ }
273
+ }
274
+
275
+ async function checkCrossReferences(failures) {
276
+ console.log(blue('\n🔗 4. 交叉引用有效性检查\n'));
277
+
278
+ for (const { from, mustReference } of requiredCrossRefs) {
279
+ const content = readText(from);
280
+ if (!content) {
281
+ console.log(yellow(` ⚠️ ${from} - 文件不存在,跳过引用检查`));
282
+ continue;
283
+ }
284
+
285
+ const missing = mustReference.filter(ref => !content.includes(ref));
286
+ if (missing.length > 0) {
287
+ failures.push({
288
+ type: 'missing_cross_reference',
289
+ file: from,
290
+ message: `${from} 缺少必需引用: ${missing.join(', ')}`,
291
+ });
292
+ console.log(red(` ❌ ${from} - 缺少: ${missing.join(', ')}`));
293
+ } else {
294
+ console.log(green(` ✅ ${from} - 所有必需引用存在`));
295
+ }
296
+ }
297
+
298
+ // 验证链接目标文件存在
299
+ console.log(blue('\n 验证链接目标文件存在性:\n'));
300
+
301
+ const allContent = requiredAIDocs.map(d => readText(d.file)).filter(Boolean).join('\n');
302
+ const linkPattern = /\[.*?\]\((.*?)\)/g;
303
+ const links = [];
304
+ let match;
305
+
306
+ while ((match = linkPattern.exec(allContent)) !== null) {
307
+ const link = match[1];
308
+ // 只检查相对路径的本地文件
309
+ if (!link.startsWith('http') && !link.startsWith('#')) {
310
+ links.push(link.replace(/^\.\//, '').split('#')[0]);
311
+ }
312
+ }
313
+
314
+ const uniqueLinks = [...new Set(links)];
315
+ let brokenLinks = 0;
316
+
317
+ for (const link of uniqueLinks.slice(0, 30)) { // 限制检查数量
318
+ // 跳过锚点链接和特殊路径
319
+ if (link.includes('#') || link === 'LICENSE' || link === 'CHANGELOG.md') {
320
+ continue;
321
+ }
322
+
323
+ const linkPath = path.join(rootDir, link);
324
+ if (!existsSync(linkPath)) {
325
+ // 可能是目录
326
+ if (!existsSync(path.join(linkPath, 'README.md'))) {
327
+ console.log(yellow(` ⚠️ 可能损坏的链接: ${link}`));
328
+ brokenLinks++;
329
+ }
330
+ }
331
+ }
332
+
333
+ if (brokenLinks === 0) {
334
+ console.log(green(` ✅ 所有检查的内部链接有效`));
335
+ } else {
336
+ console.log(yellow(` ⚠️ 发现 ${brokenLinks} 个可能损坏的链接`));
337
+ }
338
+ }
339
+
340
+ async function checkAIFriendliness(failures) {
341
+ console.log(blue('\n🤖 5. AI友好性检查\n'));
342
+
343
+ const aiGuide = readText('AI_GUIDE.md');
344
+ if (!aiGuide) {
345
+ console.log(red(' ❌ AI_GUIDE.md 不存在'));
346
+ return;
347
+ }
348
+
349
+ const checks = [
350
+ { name: '层级标题 (##)', pattern: /^##+\s+/m },
351
+ { name: '表格 (速查表)', pattern: /\|.*\|.*\|/ },
352
+ { name: '代码块', pattern: /```(?:bash|typescript|json)/ },
353
+ { name: 'TypeScript接口', pattern: /interface\s+\w+/ },
354
+ { name: '决策树/流程图', pattern: /决策树|流程图|```mermaid/ },
355
+ { name: '提示词模板', pattern: /提示词|模板|prompt/i },
356
+ ];
357
+
358
+ for (const { name, pattern } of checks) {
359
+ if (pattern.test(aiGuide)) {
360
+ console.log(green(` ✅ ${name}`));
361
+ } else {
362
+ console.log(yellow(` ⚠️ ${name} (建议添加)`));
363
+ }
364
+ }
365
+ }
366
+
367
+ async function checkChangelogSync(failures) {
368
+ console.log(blue('\n📝 6. CHANGELOG同步检查\n'));
369
+
370
+ const changelog = readText('CHANGELOG.md');
371
+ const pkg = readText('package.json');
372
+
373
+ if (!changelog) {
374
+ failures.push({
375
+ type: 'missing_changelog',
376
+ message: '缺少 CHANGELOG.md',
377
+ });
378
+ console.log(red(' ❌ CHANGELOG.md 不存在'));
379
+ return;
380
+ }
381
+
382
+ if (!pkg) {
383
+ console.log(red(' ❌ package.json 不存在'));
384
+ return;
385
+ }
386
+
387
+ const pkgVersion = JSON.parse(pkg).version;
388
+
389
+ // 检查CHANGELOG中是否有当前版本
390
+ const versionHeader = new RegExp(`##?\\s*\\[?${pkgVersion}\\]?`, 'i');
391
+ if (versionHeader.test(changelog)) {
392
+ console.log(green(` ✅ CHANGELOG 包含当前版本 v${pkgVersion}`));
393
+ } else {
394
+ failures.push({
395
+ type: 'changelog_not_synced',
396
+ message: `CHANGELOG.md 缺少当前版本 v${pkgVersion} 的条目`,
397
+ });
398
+ console.log(red(` ❌ CHANGELOG 缺少 v${pkgVersion} 条目`));
399
+ }
400
+
401
+ // 检查CHANGELOG是否包含AI文档更新记录
402
+ const hasAIDocsMention = /AI.*文档|llms\.txt|AI_GUIDE/i.test(changelog);
403
+ if (hasAIDocsMention) {
404
+ console.log(green(` ✅ CHANGELOG 包含AI文档相关记录`));
405
+ } else {
406
+ console.log(yellow(` ⚠️ CHANGELOG 未提及AI文档更新 (建议)`));
407
+ }
408
+ }
409
+
410
+ async function checkYamlIndex(failures) {
411
+ console.log(blue('\n📊 7. YAML索引有效性检查\n'));
412
+
413
+ const content = readText('ai-document-index.yaml');
414
+ if (!content) {
415
+ console.log(red(' ❌ ai-document-index.yaml 不存在'));
416
+ return;
417
+ }
418
+
419
+ const checks = [
420
+ { name: 'project 字段', pattern: /^project:/m },
421
+ { name: 'version 字段', pattern: /^\s+version:/m },
422
+ { name: 'documentation 字段', pattern: /^documentation:/m },
423
+ { name: 'ai_documents 列表', pattern: /ai_documents:/ },
424
+ { name: 'cli_commands 索引', pattern: /cli_commands:/ },
425
+ { name: 'navigation 配置', pattern: /navigation:/ },
426
+ ];
427
+
428
+ for (const { name, pattern } of checks) {
429
+ if (pattern.test(content)) {
430
+ console.log(green(` ✅ ${name}`));
431
+ } else {
432
+ failures.push({
433
+ type: 'yaml_structure_missing',
434
+ file: 'ai-document-index.yaml',
435
+ message: `缺少 ${name}`,
436
+ });
437
+ console.log(red(` ❌ ${name}`));
438
+ }
439
+ }
440
+
441
+ // 检查引用的文件是否都存在
442
+ console.log(blue('\n 验证YAML中引用的文件:\n'));
443
+ const docMatches = content.matchAll(/path:\s*["']?([^"'\n]+)["']?/g);
444
+ const paths = [...docMatches]
445
+ .map(m => m[1])
446
+ .filter(p => !p.startsWith('http'))
447
+ .map(p => p.split('#')[0]); // 移除锚点
448
+
449
+ let missingFiles = 0;
450
+ for (const p of paths) {
451
+ if (!existsSync(path.join(rootDir, p))) {
452
+ console.log(red(` ❌ 文件不存在: ${p}`));
453
+ missingFiles++;
454
+ }
455
+ }
456
+
457
+ if (missingFiles === 0) {
458
+ console.log(green(` ✅ 所有引用的文件存在 (${paths.length} 个)`));
459
+ } else {
460
+ failures.push({
461
+ type: 'yaml_missing_files',
462
+ message: `YAML索引引用了 ${missingFiles} 个不存在的文件`,
463
+ });
464
+ }
465
+ }
466
+
467
+ async function checkDocumentationStandards(failures) {
468
+ console.log(blue('\n📚 8. AGENTS.md 文档规范检查\n'));
469
+
470
+ const agents = readText('AGENTS.md');
471
+ if (!agents) {
472
+ console.log(red(' ❌ AGENTS.md 不存在'));
473
+ return;
474
+ }
475
+
476
+ const requiredSections = [
477
+ { name: 'AI友好文档规范', pattern: /AI.*友好.*文档|AI.*文档.*规范/i },
478
+ { name: '结构清晰要求', pattern: /结构清晰|层级标题/i },
479
+ { name: '决策树要求', pattern: /决策树|流程图/i },
480
+ { name: '速查表要求', pattern: /速查表|表格/i },
481
+ { name: '代码可复现要求', pattern: /代码.*复现|可执行.*代码/i },
482
+ { name: '类型定义要求', pattern: /类型定义|TypeScript.*接口/i },
483
+ { name: '提示词模板要求', pattern: /提示词|模板/i },
484
+ ];
485
+
486
+ for (const { name, pattern } of requiredSections) {
487
+ if (pattern.test(agents)) {
488
+ console.log(green(` ✅ ${name}`));
489
+ } else {
490
+ failures.push({
491
+ type: 'agents_requirement_missing',
492
+ message: `AGENTS.md 缺少 ${name}`,
493
+ });
494
+ console.log(red(` ❌ ${name}`));
495
+ }
496
+ }
497
+ }
498
+
499
+ async function checkReleaseFiles(failures) {
500
+ console.log(blue('\n📦 9. 发布必需文件检查\n'));
501
+
502
+ for (const [key, { file, required }] of Object.entries(releaseRequirements)) {
503
+ const content = readText(file);
504
+
505
+ if (!content) {
506
+ if (required) {
507
+ failures.push({
508
+ type: 'missing_release_file',
509
+ file,
510
+ message: `缺少发布必需文件: ${file}`,
511
+ });
512
+ console.log(red(` ❌ ${file} (必需)`));
513
+ } else {
514
+ console.log(yellow(` ⚠️ ${file} (建议)`));
515
+ }
516
+ } else {
517
+ const size = getFileSize(file);
518
+ console.log(green(` ✅ ${file} (${Math.round(size/1024)}KB)`));
519
+ }
520
+ }
521
+ }
522
+
523
+ async function checkGitTag(failures) {
524
+ console.log(blue('\n🏷️ 10. Git Tag 一致性检查\n'));
525
+
526
+ // 获取 package.json 版本
527
+ const pkg = readText('package.json');
528
+ if (!pkg) {
529
+ console.log(red(' ❌ package.json 不存在'));
530
+ return;
531
+ }
532
+
533
+ const pkgVersion = JSON.parse(pkg).version;
534
+ const expectedTag = `v${pkgVersion}`;
535
+
536
+ console.log(` 📦 package.json 版本: v${pkgVersion}`);
537
+ console.log(` 🏷️ 期望的 tag: ${expectedTag}`);
538
+
539
+ // 检查本地 tag 是否存在
540
+ try {
541
+ const { execSync } = await import('node:child_process');
542
+
543
+ // 检查本地 tag
544
+ try {
545
+ const localTag = execSync(`git tag -l "${expectedTag}"`, { encoding: 'utf8', cwd: rootDir }).trim();
546
+ if (localTag === expectedTag) {
547
+ console.log(green(` ✅ 本地 tag ${expectedTag} 已存在`));
548
+
549
+ // 检查 tag 是否指向当前 commit
550
+ const tagCommit = execSync(`git rev-list -n1 "${expectedTag}"`, { encoding: 'utf8', cwd: rootDir }).trim();
551
+ const currentCommit = execSync('git rev-parse HEAD', { encoding: 'utf8', cwd: rootDir }).trim();
552
+
553
+ if (tagCommit === currentCommit) {
554
+ console.log(green(` ✅ Tag 指向当前 commit`));
555
+ } else {
556
+ console.log(yellow(` ⚠️ Tag 指向不同 commit`));
557
+ console.log(yellow(` Tag commit: ${tagCommit.substring(0, 8)}`));
558
+ console.log(yellow(` Current: ${currentCommit.substring(0, 8)}`));
559
+ }
560
+ } else {
561
+ console.log(yellow(` ⚠️ 本地 tag ${expectedTag} 不存在`));
562
+ console.log(yellow(` 运行: git tag -a "${expectedTag}" -m "Release ${expectedTag}"`));
563
+ }
564
+ } catch (e) {
565
+ console.log(yellow(` ⚠️ 无法检查本地 tag`));
566
+ }
567
+
568
+ // 检查远程 tag
569
+ try {
570
+ const remoteTag = execSync(`git ls-remote --tags origin "refs/tags/${expectedTag}"`, { encoding: 'utf8', cwd: rootDir }).trim();
571
+ if (remoteTag.includes(expectedTag)) {
572
+ console.log(green(` ✅ 远程 tag ${expectedTag} 已存在`));
573
+ } else {
574
+ console.log(yellow(` ⚠️ 远程 tag ${expectedTag} 不存在`));
575
+ console.log(yellow(` 推送命令: git push origin "${expectedTag}"`));
576
+ }
577
+ } catch (e) {
578
+ console.log(yellow(` ⚠️ 无法检查远程 tag (可能没有远程仓库)`));
579
+ }
580
+
581
+ // 检查 GitHub Release (如果配置了 gh CLI)
582
+ try {
583
+ const ghCheck = execSync('which gh', { encoding: 'utf8', stdio: 'pipe' });
584
+ if (ghCheck) {
585
+ try {
586
+ const releaseInfo = execSync(`gh release view "${expectedTag}" 2>&1`, { encoding: 'utf8', cwd: rootDir, stdio: 'pipe' });
587
+ if (releaseInfo.includes(expectedTag)) {
588
+ console.log(green(` ✅ GitHub Release ${expectedTag} 已存在`));
589
+ }
590
+ } catch (e) {
591
+ console.log(yellow(` ⚠️ GitHub Release ${expectedTag} 不存在`));
592
+ console.log(yellow(` 将在 tag 推送后自动创建`));
593
+ }
594
+ }
595
+ } catch (e) {
596
+ // gh CLI 未安装,跳过
597
+ }
598
+
599
+ } catch (error) {
600
+ console.log(yellow(` ⚠️ Git 检查失败: ${error.message}`));
601
+ }
602
+
603
+ // 检查当前分支
604
+ try {
605
+ const { execSync } = await import('node:child_process');
606
+ const currentBranch = execSync('git branch --show-current', { encoding: 'utf8', cwd: rootDir }).trim();
607
+ console.log(` 🌿 当前分支: ${currentBranch}`);
608
+
609
+ if (currentBranch !== 'main' && currentBranch !== 'master') {
610
+ console.log(yellow(` ⚠️ 不在 main/master 分支,建议切换到 main 分支发布`));
611
+ } else {
612
+ console.log(green(` ✅ 在 ${currentBranch} 分支`));
613
+ }
614
+ } catch (e) {
615
+ // 忽略
616
+ }
617
+ }
618
+
619
+ // ============================================
620
+ // 主函数
621
+ // ============================================
622
+
623
+ async function runPreReleaseChecks() {
624
+ console.log(bold('╔════════════════════════════════════════════════════════════╗'));
625
+ console.log(bold('║ 发布前检查 - AI友好文档护栏 (基于审计报告) ║'));
626
+ console.log(bold('╚════════════════════════════════════════════════════════════╝'));
627
+
628
+ const failures = [];
629
+ const startTime = Date.now();
630
+
631
+ try {
632
+ await checkRequiredFiles(failures);
633
+ await checkLlmsTxtStandards(failures);
634
+ await checkVersionConsistency(failures);
635
+ await checkCrossReferences(failures);
636
+ await checkAIFriendliness(failures);
637
+ await checkChangelogSync(failures);
638
+ await checkYamlIndex(failures);
639
+ await checkDocumentationStandards(failures);
640
+ await checkReleaseFiles(failures);
641
+ await checkGitTag(failures);
642
+ } catch (error) {
643
+ console.error(red(`\n检查过程中发生错误: ${error.message}`));
644
+ process.exit(1);
645
+ }
646
+
647
+ const duration = Date.now() - startTime;
648
+
649
+ // 结果汇总
650
+ console.log(bold('\n╔════════════════════════════════════════════════════════════╗'));
651
+ console.log(bold('║ 检查结果汇总 ║'));
652
+ console.log(bold('╚════════════════════════════════════════════════════════════╝'));
653
+
654
+ const criticalFailures = failures.filter(f =>
655
+ f.type === 'missing_required_file' ||
656
+ f.type === 'missing_llms_txt' ||
657
+ f.type === 'version_mismatch' ||
658
+ f.type === 'llms_txt_standard_violation' ||
659
+ f.type === 'missing_release_file'
660
+ );
661
+
662
+ const warnings = failures.filter(f =>
663
+ !criticalFailures.includes(f)
664
+ );
665
+
666
+ console.log(`\n⏱️ 耗时: ${duration}ms`);
667
+ console.log(`📝 总检查项: 10`);
668
+ console.log(`❌ 关键错误: ${criticalFailures.length}`);
669
+ console.log(`⚠️ 警告: ${warnings.length}`);
670
+
671
+ if (criticalFailures.length > 0) {
672
+ console.log(red('\n🚫 关键错误 (阻止发布):\n'));
673
+ criticalFailures.forEach((f, i) => {
674
+ console.log(red(` ${i + 1}. [${f.type}] ${f.message}`));
675
+ });
676
+ }
677
+
678
+ if (warnings.length > 0) {
679
+ console.log(yellow('\n⚠️ 警告 (建议修复):\n'));
680
+ warnings.forEach((f, i) => {
681
+ console.log(yellow(` ${i + 1}. [${f.type}] ${f.message}`));
682
+ });
683
+ }
684
+
685
+ // 发布指南
686
+ console.log(bold('\n╔════════════════════════════════════════════════════════════╗'));
687
+ console.log(bold('║ 发布流程指南 ║'));
688
+ console.log(bold('╚════════════════════════════════════════════════════════════╝'));
689
+
690
+ const pkg = readText('package.json');
691
+ if (pkg) {
692
+ const pkgVersion = JSON.parse(pkg).version;
693
+ console.log(`\n当前版本: v${pkgVersion}`);
694
+ console.log('\n📋 发布步骤:');
695
+ console.log(' 1. npm run docs:check:pre-release # 运行此检查');
696
+ console.log(' 2. ./scripts/release.sh patch|minor|major # 或指定版本');
697
+ console.log(' 3. git push origin main --tags # 推送 tag');
698
+ console.log(' 4. GitHub Actions 自动: 构建 → 测试 → 发布 → 创建 Release');
699
+ console.log('\n🔗 相关链接:');
700
+ console.log(` - GitHub Actions: https://github.com/mycodemap/mycodemap/actions`);
701
+ console.log(` - NPM 包: https://www.npmjs.com/package/@mycodemap/mycodemap`);
702
+ }
703
+
704
+ console.log(bold('\n╔════════════════════════════════════════════════════════════╗'));
705
+
706
+ if (criticalFailures.length === 0) {
707
+ console.log(green('║ ✅ 所有关键检查通过 - 可以安全发布! ║'));
708
+ console.log(bold('╚════════════════════════════════════════════════════════════╝'));
709
+ process.exit(0);
710
+ } else {
711
+ console.log(red('║ ❌ 存在关键错误 - 请修复后再发布! ║'));
712
+ console.log(bold('╚════════════════════════════════════════════════════════════╝'));
713
+ process.exit(1);
714
+ }
715
+ }
716
+
717
+ runPreReleaseChecks();