autosnippet 3.0.10 → 3.0.13

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 (56) hide show
  1. package/bin/cli.js +64 -1
  2. package/config/default.json +9 -0
  3. package/dashboard/dist/assets/{index-I2ySoCmF.js → index-Bnm26ulL.js} +47 -47
  4. package/dashboard/dist/index.html +1 -1
  5. package/lib/cli/SetupService.js +92 -5
  6. package/lib/cli/UpgradeService.js +14 -5
  7. package/lib/core/discovery/GenericDiscoverer.js +4 -28
  8. package/lib/external/mcp/handlers/bootstrap/base-dimensions.js +246 -0
  9. package/lib/external/mcp/handlers/bootstrap/pipeline/checkpoint.js +80 -0
  10. package/lib/external/mcp/handlers/bootstrap/pipeline/dimension-configs.js +275 -0
  11. package/lib/external/mcp/handlers/bootstrap/pipeline/noAiFallback.js +600 -0
  12. package/lib/external/mcp/handlers/bootstrap/pipeline/orchestrator.js +125 -342
  13. package/lib/external/mcp/handlers/bootstrap/refine.js +362 -0
  14. package/lib/external/mcp/handlers/bootstrap.js +6 -590
  15. package/lib/external/mcp/handlers/browse.js +119 -9
  16. package/lib/external/mcp/handlers/guard.js +25 -6
  17. package/lib/external/mcp/handlers/search.js +56 -24
  18. package/lib/http/routes/guardRules.js +9 -17
  19. package/lib/injection/ServiceContainer.js +12 -3
  20. package/lib/platform/ios/xcode/XcodeImportResolver.js +434 -0
  21. package/lib/platform/ios/xcode/XcodeIntegration.js +40 -659
  22. package/lib/platform/ios/xcode/XcodeWriteUtils.js +220 -0
  23. package/lib/service/chat/ChatAgent.js +39 -418
  24. package/lib/service/chat/ChatAgentPrompts.js +149 -0
  25. package/lib/service/chat/ChatAgentTasks.js +297 -0
  26. package/lib/service/chat/tools/_shared.js +61 -0
  27. package/lib/service/chat/tools/ai-analysis.js +284 -0
  28. package/lib/service/chat/tools/ast-graph.js +681 -0
  29. package/lib/service/chat/tools/composite.js +496 -0
  30. package/lib/service/chat/tools/guard.js +265 -0
  31. package/lib/service/chat/tools/index.js +250 -0
  32. package/lib/service/chat/tools/infrastructure.js +222 -0
  33. package/lib/service/chat/tools/knowledge-graph.js +234 -0
  34. package/lib/service/chat/tools/lifecycle.js +469 -0
  35. package/lib/service/chat/tools/project-access.js +923 -0
  36. package/lib/service/chat/tools/query.js +264 -0
  37. package/lib/service/chat/tools.js +14 -3994
  38. package/lib/service/cursor/AgentInstructionsGenerator.js +395 -0
  39. package/lib/service/cursor/CursorDeliveryPipeline.js +70 -11
  40. package/lib/service/cursor/FileProtection.js +116 -0
  41. package/lib/service/cursor/KnowledgeCompressor.js +61 -11
  42. package/lib/service/cursor/SkillsSyncer.js +5 -3
  43. package/lib/service/cursor/TopicClassifier.js +19 -3
  44. package/lib/service/guard/ExclusionManager.js +26 -2
  45. package/lib/service/guard/GuardCheckEngine.js +38 -370
  46. package/lib/service/guard/GuardCodeChecks.js +362 -0
  47. package/lib/service/guard/GuardCrossFileChecks.js +307 -0
  48. package/lib/service/guard/GuardPatternUtils.js +180 -0
  49. package/lib/service/guard/GuardService.js +80 -38
  50. package/lib/service/module/ModuleService.js +1 -0
  51. package/lib/service/search/SearchEngine.js +10 -2
  52. package/lib/service/wiki/WikiGenerator.js +226 -1532
  53. package/lib/service/wiki/WikiRenderers.js +1878 -0
  54. package/lib/service/wiki/WikiUtils.js +907 -0
  55. package/lib/shared/LanguageService.js +299 -0
  56. package/package.json +1 -1
@@ -35,6 +35,22 @@ import fs from 'node:fs';
35
35
  import path from 'node:path';
36
36
  import Logger from '../../infrastructure/logging/Logger.js';
37
37
  import { LanguageService } from '../../shared/LanguageService.js';
38
+ import {
39
+ slug,
40
+ walkDir,
41
+ inferModuleFromPath,
42
+ getModuleSourceFiles,
43
+ getInheritanceRoots,
44
+ dedup,
45
+ detectBuildSystems,
46
+ getLangTerms,
47
+ profileFolders,
48
+ } from './WikiUtils.js';
49
+ import {
50
+ buildArticlePrompt,
51
+ buildFallbackArticle,
52
+ buildAiSystemPrompt,
53
+ } from './WikiRenderers.js';
38
54
 
39
55
  const logger = Logger.getInstance();
40
56
 
@@ -126,7 +142,7 @@ export class WikiGenerator {
126
142
 
127
143
  // Phase 4: Module/SPM parse
128
144
  this._emit(WikiPhase.SPM_PARSE, 30, '解析模块依赖关系...');
129
- const spmInfo = await this._parseSPM();
145
+ const moduleInfo = await this._parseModules();
130
146
  if (this._aborted) {
131
147
  return this._abortedResult();
132
148
  }
@@ -140,8 +156,8 @@ export class WikiGenerator {
140
156
 
141
157
  // Phase 6: Content-driven topic discovery (V3)
142
158
  this._emit(WikiPhase.GENERATE, 50, '分析项目数据,发现文档主题...');
143
- const structuredData = { projectInfo, astInfo, spmInfo, knowledgeInfo };
144
- const topics = this._discoverTopics(projectInfo, astInfo, spmInfo, knowledgeInfo);
159
+ const structuredData = { projectInfo, astInfo, moduleInfo, knowledgeInfo };
160
+ const topics = this._discoverTopics(projectInfo, astInfo, moduleInfo, knowledgeInfo);
145
161
  if (this._aborted) {
146
162
  return this._abortedResult();
147
163
  }
@@ -163,7 +179,7 @@ export class WikiGenerator {
163
179
 
164
180
  // Phase 9: Dedup
165
181
  this._emit(WikiPhase.DEDUP, 90, '去重检查...');
166
- const dedupResult = this._dedup(files);
182
+ const dedupResult = dedup(files, this.wikiDir, this._emit.bind(this));
167
183
 
168
184
  // Phase 10: Finalize
169
185
  this._emit(WikiPhase.FINALIZE, 95, '写入元数据...');
@@ -243,15 +259,26 @@ export class WikiGenerator {
243
259
  const info = {
244
260
  name: path.basename(this.projectRoot),
245
261
  root: this.projectRoot,
262
+ // 通用构建系统检测(替代硬编码 iOS 三件套)
263
+ buildSystems: [], // [{eco, buildTool}]
264
+ sourceFiles: [],
265
+ languages: {},
266
+ langProfile: null, // LanguageService.detectProfile() 结果
267
+ primaryLanguage: 'unknown',
268
+ // 保留向后兼容字段
246
269
  hasPackageSwift: false,
247
270
  hasPodfile: false,
248
271
  hasXcodeproj: false,
249
- sourceFiles: [],
250
- languages: {},
251
272
  };
252
273
 
253
274
  // 检测项目类型
254
275
  const entries = fs.readdirSync(this.projectRoot, { withFileTypes: true });
276
+ const entryNames = entries.map((e) => e.name);
277
+
278
+ // 通用构建系统检测 (支持一级子目录 monorepo)
279
+ info.buildSystems = detectBuildSystems(entryNames, this.projectRoot);
280
+
281
+ // 向后兼容三字段
255
282
  for (const e of entries) {
256
283
  if (e.name === 'Package.swift') {
257
284
  info.hasPackageSwift = true;
@@ -269,7 +296,7 @@ export class WikiGenerator {
269
296
  for (const ext of LanguageService.sourceExts) {
270
297
  extMap[ext] = LanguageService.displayNameFromExt(ext) || ext;
271
298
  }
272
- this._walkDir(
299
+ walkDir(
273
300
  this.projectRoot,
274
301
  (filePath) => {
275
302
  const ext = path.extname(filePath);
@@ -292,8 +319,8 @@ export class WikiGenerator {
292
319
  // SPM 标准结构: Sources/{ModuleName}/...
293
320
  mod = parts[sourcesIdx + 1];
294
321
  } else {
295
- // SPM: 取第一级目录名
296
- mod = parts.length > 1 ? parts[0] : null;
322
+ // 通用: 使用多语言路径推断
323
+ mod = inferModuleFromPath(f);
297
324
  }
298
325
  if (mod) {
299
326
  if (!info.sourceFilesByModule[mod]) {
@@ -303,7 +330,18 @@ export class WikiGenerator {
303
330
  }
304
331
  }
305
332
 
306
- this._emit(WikiPhase.SCAN, 12, `发现 ${info.sourceFiles.length} 个源文件`);
333
+ // 利用 LanguageService.detectProfile() 获取多语言画像
334
+ const bareStats = {};
335
+ for (const f of info.sourceFiles) {
336
+ const ext = path.extname(f).replace('.', '');
337
+ if (ext) {
338
+ bareStats[ext] = (bareStats[ext] || 0) + 1;
339
+ }
340
+ }
341
+ info.langProfile = LanguageService.detectProfile(bareStats);
342
+ info.primaryLanguage = info.langProfile.primary;
343
+
344
+ this._emit(WikiPhase.SCAN, 12, `发现 ${info.sourceFiles.length} 个源文件 (${LanguageService.displayName(info.primaryLanguage)})`);
307
345
  return info;
308
346
  }
309
347
 
@@ -323,7 +361,7 @@ export class WikiGenerator {
323
361
  for (const name of allClasses) {
324
362
  const info = this.projectGraph.getClassInfo(name);
325
363
  if (info?.filePath) {
326
- const mod = this._inferModuleFromPath(info.filePath);
364
+ const mod = inferModuleFromPath(info.filePath);
327
365
  if (mod) {
328
366
  if (!classNamesByModule[mod]) {
329
367
  classNamesByModule[mod] = [];
@@ -336,7 +374,7 @@ export class WikiGenerator {
336
374
  for (const name of allProtocols) {
337
375
  const info = this.projectGraph.getProtocolInfo(name);
338
376
  if (info?.filePath) {
339
- const mod = this._inferModuleFromPath(info.filePath);
377
+ const mod = inferModuleFromPath(info.filePath);
340
378
  if (mod) {
341
379
  if (!protocolNamesByModule[mod]) {
342
380
  protocolNamesByModule[mod] = [];
@@ -374,7 +412,7 @@ export class WikiGenerator {
374
412
  * 模块依赖解析
375
413
  * 通过 moduleService 统一处理所有语言的模块扫描
376
414
  */
377
- async _parseSPM() {
415
+ async _parseModules() {
378
416
  if (!this.moduleService) {
379
417
  return { targets: [], depGraph: null };
380
418
  }
@@ -433,9 +471,10 @@ export class WikiGenerator {
433
471
  *
434
472
  * @returns {Array<{id: string, path: string, title: string, type: string, priority: number}>}
435
473
  */
436
- _discoverTopics(projectInfo, astInfo, spmInfo, knowledgeInfo) {
474
+ _discoverTopics(projectInfo, astInfo, moduleInfo, knowledgeInfo) {
437
475
  const topics = [];
438
476
  const isZh = this.options.language === 'zh';
477
+ const langTerms = getLangTerms(projectInfo.primaryLanguage);
439
478
 
440
479
  // ── 1. 项目概览 (始终生成) ──
441
480
  topics.push({
@@ -448,8 +487,9 @@ export class WikiGenerator {
448
487
 
449
488
  // ── 2. 架构概览 (需要模块/依赖关系) ──
450
489
  const moduleKeys = Object.keys(astInfo.classNamesByModule || {});
451
- const hasMultiModule = spmInfo.targets.length >= 2 || moduleKeys.length >= 2;
452
- const hasDepGraph = spmInfo.depGraph != null;
490
+ const sourceModuleKeys = Object.keys(projectInfo.sourceFilesByModule || {});
491
+ const hasMultiModule = moduleInfo.targets.length >= 2 || moduleKeys.length >= 2 || sourceModuleKeys.length >= 2;
492
+ const hasDepGraph = moduleInfo.depGraph != null;
453
493
  const hasInheritance = this.codeEntityGraph != null;
454
494
 
455
495
  if (hasMultiModule || hasDepGraph || hasInheritance) {
@@ -465,6 +505,7 @@ export class WikiGenerator {
465
505
  // ── 3. 快速上手 (需要构建配置或入口点) ──
466
506
  const hasEntryPoints = (astInfo.overview?.entryPoints?.length || 0) > 0;
467
507
  const hasBuildSystem =
508
+ projectInfo.buildSystems.length > 0 ||
468
509
  projectInfo.hasPackageSwift || projectInfo.hasPodfile || projectInfo.hasXcodeproj;
469
510
 
470
511
  if (hasEntryPoints || hasBuildSystem) {
@@ -478,28 +519,66 @@ export class WikiGenerator {
478
519
  }
479
520
 
480
521
  // ── 4. 模块深度文档 (仅对实质性模块生成) ──
481
- for (const target of spmInfo.targets) {
482
- const moduleFiles = this._getModuleSourceFiles(target, projectInfo);
483
- const classCount = (astInfo.classNamesByModule?.[target.name] || []).length;
484
- const protoCount = (astInfo.protocolNamesByModule?.[target.name] || []).length;
485
- const depCount = (target.dependencies || target.info?.dependencies || []).length;
486
-
487
- // 丰富度评分: 文件数 + 类数×2 + 协议数×2 + 依赖数
488
- const richness = moduleFiles.length + classCount * 2 + protoCount * 2 + depCount;
522
+ const discoverers = moduleInfo.projectInfo?.discoverers || [];
523
+ const genericOnlyDiscovery =
524
+ discoverers.length === 1 && discoverers[0]?.id === 'generic';
525
+ const monolithSingleTarget =
526
+ moduleInfo.targets.length === 1 &&
527
+ (moduleInfo.targets[0]?.path === projectInfo.root ||
528
+ moduleInfo.targets[0]?.name === projectInfo.name);
529
+ const shouldUseInferredModules =
530
+ sourceModuleKeys.length >= 2 &&
531
+ (moduleInfo.targets.length === 0 || (genericOnlyDiscovery && monolithSingleTarget));
532
+
533
+ if (moduleInfo.targets.length > 0 && !shouldUseInferredModules) {
534
+ // 使用 moduleService 发现的 targets
535
+ for (const target of moduleInfo.targets) {
536
+ const moduleFiles = getModuleSourceFiles(target, projectInfo);
537
+ const classCount = (astInfo.classNamesByModule?.[target.name] || []).length;
538
+ const protoCount = (astInfo.protocolNamesByModule?.[target.name] || []).length;
539
+ const depCount = (target.dependencies || target.info?.dependencies || []).length;
540
+
541
+ // 丰富度评分: 文件数 + 类数×2 + 协议数×2 + 依赖数
542
+ const richness = moduleFiles.length + classCount * 2 + protoCount * 2 + depCount;
543
+
544
+ // 跳过过于单薄的模块 (少于3分不值得独立文档)
545
+ if (richness < 3) {
546
+ continue;
547
+ }
489
548
 
490
- // 跳过过于单薄的模块 (少于3分不值得独立文档)
491
- if (richness < 3) {
492
- continue;
549
+ topics.push({
550
+ id: `module-${slug(target.name)}`,
551
+ path: `modules/${slug(target.name)}.md`,
552
+ title: target.name,
553
+ type: 'module',
554
+ priority: 50 + Math.min(richness, 30),
555
+ _moduleData: { target, moduleFiles, classCount, protoCount },
556
+ });
557
+ }
558
+ } else if (shouldUseInferredModules) {
559
+ // 无有效模块边界(无 targets 或 generic 单 target) → 从 sourceFilesByModule 推断模块
560
+ const sfm = projectInfo.sourceFilesByModule || {};
561
+ const sorted = Object.entries(sfm).sort((a, b) => b[1].length - a[1].length);
562
+ for (const [modName, modFiles] of sorted) {
563
+ if (modFiles.length < 2) continue;
564
+ const classCount = (astInfo.classNamesByModule?.[modName] || []).length;
565
+ const protoCount = (astInfo.protocolNamesByModule?.[modName] || []).length;
566
+ const richness = modFiles.length + classCount * 2 + protoCount * 2;
567
+ if (richness < 3) continue;
568
+ topics.push({
569
+ id: `module-${slug(modName)}`,
570
+ path: `modules/${slug(modName)}.md`,
571
+ title: modName,
572
+ type: 'module',
573
+ priority: 50 + Math.min(richness, 30),
574
+ _moduleData: {
575
+ target: { name: modName, type: 'inferred' },
576
+ moduleFiles: modFiles,
577
+ classCount,
578
+ protoCount,
579
+ },
580
+ });
493
581
  }
494
-
495
- topics.push({
496
- id: `module-${_slug(target.name)}`,
497
- path: `modules/${_slug(target.name)}.md`,
498
- title: target.name,
499
- type: 'module',
500
- priority: 50 + Math.min(richness, 30),
501
- _moduleData: { target, moduleFiles, classCount, protoCount },
502
- });
503
582
  }
504
583
 
505
584
  // ── 5. 代码模式/最佳实践 (来自知识库 Recipes) ──
@@ -532,8 +611,8 @@ export class WikiGenerator {
532
611
  continue;
533
612
  }
534
613
  topics.push({
535
- id: `pattern-${_slug(cat)}`,
536
- path: `patterns/${_slug(cat)}.md`,
614
+ id: `pattern-${slug(cat)}`,
615
+ path: `patterns/${slug(cat)}.md`,
537
616
  title: isZh ? `${cat} 模式` : `${cat} Patterns`,
538
617
  type: 'pattern-category',
539
618
  priority: 30 + items.length,
@@ -543,17 +622,92 @@ export class WikiGenerator {
543
622
  }
544
623
  }
545
624
 
546
- // ── 6. 协议参考 (协议数量足够多时) ──
625
+ // ── 6. 协议/接口参考 (数量足够多时) ──
547
626
  if (astInfo.protocols.length >= 8) {
627
+ const ifaceLabel = isZh ? langTerms.interfaceLabel.zh : langTerms.interfaceLabel.en;
548
628
  topics.push({
549
629
  id: 'protocols',
550
630
  path: 'protocols.md',
551
- title: isZh ? '协议参考' : 'Protocol Reference',
631
+ title: isZh ? `${ifaceLabel}参考` : `${ifaceLabel} Reference`,
552
632
  type: 'reference',
553
633
  priority: 35,
554
634
  });
555
635
  }
556
636
 
637
+ // ── 7. 文件夹画像文档 (AST 稀疏项目的降级策略) ──
638
+ // 当 AST 无法提取足够的 类/函数/协议 时,转为文件夹级分析
639
+ const astEntityCount = (astInfo.classes?.length || 0) + (astInfo.protocols?.length || 0);
640
+ const hasModuleDocs = topics.some(t => t.type === 'module');
641
+ const astSparse = astEntityCount < 5 && !hasModuleDocs;
642
+ const shouldProfileForGenericMonolith =
643
+ genericOnlyDiscovery && monolithSingleTarget && sourceModuleKeys.length >= 2;
644
+ const shouldEnableFolderProfiling = astSparse || shouldProfileForGenericMonolith;
645
+
646
+ if (shouldEnableFolderProfiling) {
647
+ const rawFolderProfiles = profileFolders(projectInfo, {
648
+ minFiles: 3,
649
+ maxFolders: 15,
650
+ });
651
+
652
+ // 按 relPath 去重,避免同一路径重复产出同名文档
653
+ const folderProfiles = [];
654
+ const seenFolderRelPath = new Set();
655
+ for (const fp of rawFolderProfiles) {
656
+ if (seenFolderRelPath.has(fp.relPath)) {
657
+ continue;
658
+ }
659
+ seenFolderRelPath.add(fp.relPath);
660
+ folderProfiles.push(fp);
661
+ }
662
+
663
+ if (folderProfiles.length > 0) {
664
+ // 总览文档: 文件夹结构分析
665
+ topics.push({
666
+ id: 'folder-overview',
667
+ path: 'folder-structure.md',
668
+ title: isZh ? '项目结构分析' : 'Project Structure Analysis',
669
+ type: 'folder-overview',
670
+ priority: 80,
671
+ _folderProfiles: folderProfiles,
672
+ });
673
+
674
+ // 为每个重要文件夹生成独立文档 (仅 fileCount ≥ 5 的大文件夹)
675
+ // 限制最多 10 个 folder-profile 文档,避免碎片化
676
+ const MAX_FOLDER_DOCS = 10;
677
+ let folderDocCount = 0;
678
+ for (const fp of folderProfiles) {
679
+ if (folderDocCount >= MAX_FOLDER_DOCS) break;
680
+ if (fp.fileCount < 5) continue;
681
+ const folderDocSlug = slug(fp.relPath.replaceAll('/', '-'));
682
+ // 文件夹丰富度评分: 文件数 + 入口点×3 + 命名模式数×2 + imports数 + headerComments数×2 + (有README +5)
683
+ const richness = fp.fileCount
684
+ + fp.entryPoints.length * 3
685
+ + fp.namingPatterns.length * 2
686
+ + fp.imports.length
687
+ + fp.headerComments.length * 2
688
+ + (fp.readme ? 5 : 0);
689
+
690
+ if (richness < 10) continue; // 过于单薄的文件夹不值得独立文档
691
+
692
+ topics.push({
693
+ id: `folder-${folderDocSlug}`,
694
+ path: `folders/${folderDocSlug}.md`,
695
+ title: fp.relPath,
696
+ type: 'folder-profile',
697
+ priority: 45 + Math.min(richness, 25),
698
+ _folderProfile: fp,
699
+ });
700
+ folderDocCount++;
701
+ }
702
+
703
+ const folderProfileReason = astSparse ? 'AST sparse' : 'generic monolith';
704
+ logger.info(
705
+ `[WikiGenerator] Folder profiling (${folderProfileReason}): ${folderProfiles.length} folders analyzed, ` +
706
+ `${topics.filter(t => t.type === 'folder-profile').length} folder docs planned`
707
+ );
708
+ }
709
+ }
710
+
557
711
  // 按优先级排序
558
712
  topics.sort((a, b) => b.priority - a.priority);
559
713
 
@@ -573,7 +727,7 @@ export class WikiGenerator {
573
727
  * 3. 质量关卡: 最终内容不足 MIN_ARTICLE_CHARS 则跳过
574
728
  *
575
729
  * @param {Array} topics - _discoverTopics() 的输出
576
- * @param {object} structuredData - { projectInfo, astInfo, spmInfo, knowledgeInfo }
730
+ * @param {object} structuredData - { projectInfo, astInfo, moduleInfo, knowledgeInfo }
577
731
  * @returns {Array<{path: string, hash: string, size: number}>}
578
732
  */
579
733
  async _composeArticles(topics, structuredData) {
@@ -585,15 +739,23 @@ export class WikiGenerator {
585
739
  this._ensureDir(this.wikiDir);
586
740
  const needsModulesDir = topics.some((t) => t.path.startsWith('modules/'));
587
741
  const needsPatternsDir = topics.some((t) => t.path.startsWith('patterns/'));
742
+ const needsFoldersDir = topics.some((t) => t.path.startsWith('folders/'));
588
743
  if (needsModulesDir) {
589
744
  this._ensureDir(path.join(this.wikiDir, 'modules'));
590
745
  }
591
746
  if (needsPatternsDir) {
592
747
  this._ensureDir(path.join(this.wikiDir, 'patterns'));
593
748
  }
749
+ if (needsFoldersDir) {
750
+ this._ensureDir(path.join(this.wikiDir, 'folders'));
751
+ }
594
752
 
595
753
  let composed = 0;
596
- const systemPrompt = this._buildAiSystemPrompt(isZh);
754
+ const systemPrompt = buildAiSystemPrompt(isZh);
755
+
756
+ // 跟踪实际写入的主题 (用于 overview 导航)
757
+ const writtenTopics = [];
758
+ let overviewTopicIdx = -1;
597
759
 
598
760
  for (let i = 0; i < topics.length; i++) {
599
761
  if (this._aborted) {
@@ -601,8 +763,9 @@ export class WikiGenerator {
601
763
  }
602
764
 
603
765
  const topic = topics[i];
604
- // 将全部主题列表注入 overview,用于生成导航
605
766
  if (topic.type === 'overview') {
767
+ overviewTopicIdx = i;
768
+ // 先用全部主题作为占位,最后回写
606
769
  topic._allTopics = topics;
607
770
  }
608
771
 
@@ -614,7 +777,7 @@ export class WikiGenerator {
614
777
  // === 1. 尝试 AI 撰写完整文章 ===
615
778
  if (this.aiProvider) {
616
779
  try {
617
- const prompt = this._buildArticlePrompt(topic, structuredData, isZh);
780
+ const prompt = buildArticlePrompt(topic, structuredData, isZh, this.codeEntityGraph);
618
781
  const aiResult = await Promise.race([
619
782
  this.aiProvider.chat(prompt, { systemPrompt, temperature: 0.3, maxTokens: 4096 }),
620
783
  new Promise((_, reject) =>
@@ -633,7 +796,7 @@ export class WikiGenerator {
633
796
 
634
797
  // === 2. 降级: 丰富的模板内容 ===
635
798
  if (!content) {
636
- content = this._buildFallbackArticle(topic, structuredData, isZh);
799
+ content = buildFallbackArticle(topic, structuredData, isZh, this.codeEntityGraph);
637
800
  }
638
801
 
639
802
  // === 3. 质量关卡 ===
@@ -646,10 +809,26 @@ export class WikiGenerator {
646
809
 
647
810
  // 写入文件
648
811
  const fileInfo = this._writeFile(topic.path, content);
649
- if (composed > 0 && content !== this._buildFallbackArticle(topic, structuredData, isZh)) {
812
+ if (composed > 0 && content !== buildFallbackArticle(topic, structuredData, isZh, this.codeEntityGraph)) {
650
813
  fileInfo.polished = true;
651
814
  }
652
815
  files.push(fileInfo);
816
+ writtenTopics.push(topic);
817
+ }
818
+
819
+ // 回写 overview: 只包含实际生成的页面导航 (避免断链)
820
+ if (overviewTopicIdx >= 0 && writtenTopics.length > 0) {
821
+ const overviewTopic = topics[overviewTopicIdx];
822
+ overviewTopic._allTopics = writtenTopics;
823
+ let overviewContent = null;
824
+ // overview 始终存在于 files 中(因为 priority 最高且始终生成)
825
+ // 重新用实际 writtenTopics 渲染
826
+ overviewContent = buildFallbackArticle(overviewTopic, structuredData, isZh, this.codeEntityGraph);
827
+ // 如果之前 AI compose 过 overview,保留 AI 版本(AI 版本已在初次写入时处理导航)
828
+ const overviewFile = files.find(f => f.path === overviewTopic.path);
829
+ if (overviewFile && !overviewFile.polished && overviewContent) {
830
+ this._writeFile(overviewTopic.path, overviewContent);
831
+ }
653
832
  }
654
833
 
655
834
  logger.info(`[WikiGenerator] Composed ${files.length} articles (${composed} AI-enhanced)`);
@@ -661,1265 +840,6 @@ export class WikiGenerator {
661
840
  return files;
662
841
  }
663
842
 
664
- // ═══ AI Prompt 构建 ════════════════════════════════════════
665
-
666
- /**
667
- * 为特定主题构建 AI 撰写 prompt (V3 AI-first 核心)
668
- *
669
- * 关键区别: 不是润色骨架,而是提供丰富数据让 AI 写完整文章
670
- */
671
- _buildArticlePrompt(topic, data, isZh) {
672
- const { projectInfo, astInfo, spmInfo, knowledgeInfo } = data;
673
- const parts = [];
674
-
675
- // 公共项目上下文
676
- parts.push(`# 项目: ${projectInfo.name}`);
677
- parts.push(
678
- `源文件数: ${projectInfo.sourceFiles.length}, SPM Targets: ${spmInfo.targets.length}, 活跃知识条目: ${knowledgeInfo.recipes.length}`
679
- );
680
- if (projectInfo.languages) {
681
- parts.push(
682
- `语言分布: ${Object.entries(projectInfo.languages)
683
- .sort((a, b) => b[1] - a[1])
684
- .map(([l, c]) => `${l}(${c})`)
685
- .join(', ')}`
686
- );
687
- }
688
- parts.push('');
689
-
690
- switch (topic.type) {
691
- case 'overview': {
692
- parts.push('## 任务: 撰写项目概述文档');
693
- parts.push('');
694
-
695
- // 项目类型
696
- const types = [];
697
- if (projectInfo.hasPackageSwift) {
698
- types.push('SPM');
699
- }
700
- if (projectInfo.hasPodfile) {
701
- types.push('CocoaPods');
702
- }
703
- if (projectInfo.hasXcodeproj) {
704
- types.push('Xcode Project');
705
- }
706
- if (types.length > 0) {
707
- parts.push(`构建系统: ${types.join(' + ')}`);
708
- }
709
- parts.push('');
710
-
711
- // 模块结构
712
- if (spmInfo.targets.length > 0) {
713
- parts.push('### 模块列表');
714
- for (const t of spmInfo.targets) {
715
- const files = this._getModuleSourceFiles(t, projectInfo);
716
- const cls = astInfo.classNamesByModule?.[t.name]?.length || 0;
717
- const deps = (t.dependencies || t.info?.dependencies || []).map((d) =>
718
- typeof d === 'string' ? d : d.name
719
- );
720
- parts.push(
721
- `- ${t.name} (${t.type || 'target'}): ${files.length} 文件, ${cls} 个类型${deps.length > 0 ? `, 依赖: ${deps.join(', ')}` : ''}`
722
- );
723
- }
724
- parts.push('');
725
- }
726
-
727
- // AST 概况
728
- if (astInfo.overview) {
729
- parts.push('### 代码规模');
730
- parts.push(
731
- `类/结构体: ${astInfo.overview.totalClasses || 0}, 协议: ${astInfo.overview.totalProtocols || 0}, 方法: ${astInfo.overview.totalMethods || 0}`
732
- );
733
- parts.push('');
734
- }
735
-
736
- // 可用的其他文档(用于导航链接)
737
- const otherTopics = (topic._allTopics || []).filter((t) => t.type !== 'overview');
738
- if (otherTopics.length > 0) {
739
- parts.push('### 需要包含的导航链接');
740
- for (const t of otherTopics) {
741
- parts.push(`- [${t.title}](${t.path})`);
742
- }
743
- parts.push('');
744
- }
745
-
746
- parts.push('要求: 撰写完整的项目概述文档。');
747
- parts.push(
748
- '包含: 项目简介(解释项目做什么)、模块总览(表格形式)、技术栈分析、核心数据指标、文档导航索引。'
749
- );
750
- parts.push('不要只列数据 — 要解释项目的定位、各模块的职责和协作关系。');
751
- break;
752
- }
753
-
754
- case 'architecture': {
755
- parts.push('## 任务: 撰写架构分析文档');
756
- parts.push('');
757
-
758
- if (spmInfo.targets.length > 0) {
759
- parts.push('### 模块及依赖关系');
760
- for (const t of spmInfo.targets) {
761
- const deps = (t.dependencies || t.info?.dependencies || []).map((d) =>
762
- typeof d === 'string' ? d : d.name
763
- );
764
- parts.push(
765
- `- ${t.name} (${t.type || 'target'})${deps.length > 0 ? ` → 依赖: ${deps.join(', ')}` : ''}`
766
- );
767
- }
768
- parts.push('');
769
- }
770
-
771
- if (astInfo.overview?.topLevelModules?.length > 0) {
772
- parts.push(`### 顶层模块: ${astInfo.overview.topLevelModules.join(', ')}`);
773
- const cpm = astInfo.overview.classesPerModule || {};
774
- for (const mod of astInfo.overview.topLevelModules) {
775
- parts.push(` ${mod}: ${cpm[mod] || 0} 个类`);
776
- }
777
- parts.push('');
778
- }
779
-
780
- if (astInfo.overview?.entryPoints?.length > 0) {
781
- parts.push(`### 入口点: ${astInfo.overview.entryPoints.join(', ')}`);
782
- parts.push('');
783
- }
784
-
785
- const roots = this._getInheritanceRoots();
786
- if (roots.length > 0) {
787
- parts.push('### 核心继承关系');
788
- for (const r of roots.slice(0, 10)) {
789
- parts.push(`- ${r.name} → ${(r.children || []).slice(0, 5).join(', ')}`);
790
- }
791
- parts.push('');
792
- }
793
-
794
- parts.push('要求: 撰写架构分析文档。');
795
- parts.push(
796
- '包含: 模块依赖图(使用 Mermaid graph TD 语法)、分层架构分析(解释每层的职责)、模块间协作关系、架构设计决策阐述。'
797
- );
798
- parts.push('用 Mermaid 绘制依赖关系图和继承层次图。分析为什么采用这种架构。');
799
- break;
800
- }
801
-
802
- case 'module': {
803
- const md = topic._moduleData;
804
- const target = md.target;
805
- const moduleFiles = md.moduleFiles;
806
- const moduleClasses = astInfo.classNamesByModule?.[target.name] || [];
807
- const moduleProtocols = astInfo.protocolNamesByModule?.[target.name] || [];
808
- const deps = target.dependencies || target.info?.dependencies || [];
809
-
810
- parts.push(`## 任务: 撰写 "${target.name}" 模块的深度文档`);
811
- parts.push('');
812
- parts.push('### 模块基本信息');
813
- parts.push(`- 类型: ${target.type || 'target'}`);
814
- const tPath = target.path || target.info?.path;
815
- if (tPath) {
816
- parts.push(`- 路径: ${tPath}`);
817
- }
818
- if (target.packageName) {
819
- parts.push(`- 所属包: ${target.packageName}`);
820
- }
821
- parts.push(`- 源文件: ${moduleFiles.length} 个`);
822
- parts.push(`- 类/结构体: ${moduleClasses.length} 个`);
823
- parts.push(`- 协议: ${moduleProtocols.length} 个`);
824
- parts.push('');
825
-
826
- if (deps.length > 0) {
827
- parts.push(
828
- `### 依赖: ${deps.map((d) => (typeof d === 'string' ? d : d.name)).join(', ')}`
829
- );
830
- parts.push('');
831
- }
832
-
833
- if (moduleClasses.length > 0) {
834
- parts.push(`### 类型列表: ${moduleClasses.slice(0, 30).join(', ')}`);
835
- parts.push('');
836
- }
837
-
838
- if (moduleProtocols.length > 0) {
839
- parts.push(`### 协议列表: ${moduleProtocols.slice(0, 20).join(', ')}`);
840
- parts.push('');
841
- }
842
-
843
- // 关键源文件名(帮助 AI 推断模块功能)
844
- if (moduleFiles.length > 0) {
845
- const keyFiles = moduleFiles.slice(0, 25).map((f) => path.basename(f));
846
- parts.push(`### 关键源文件: ${keyFiles.join(', ')}`);
847
- parts.push('');
848
- }
849
-
850
- // 相关 recipes
851
- const related = knowledgeInfo.recipes.filter((r) => {
852
- const json = r.toJSON ? r.toJSON() : r;
853
- return (
854
- json.moduleName === target.name ||
855
- json.tags?.includes(target.name) ||
856
- json.title?.includes(target.name)
857
- );
858
- });
859
- if (related.length > 0) {
860
- parts.push(`### 相关知识条目 (${related.length})`);
861
- for (const r of related.slice(0, 10)) {
862
- const json = r.toJSON ? r.toJSON() : r;
863
- parts.push(`- ${json.title}: ${json.description || ''}`);
864
- if (json.reasoning?.whyStandard) {
865
- parts.push(` 为什么: ${json.reasoning.whyStandard}`);
866
- }
867
- }
868
- parts.push('');
869
- }
870
-
871
- parts.push('要求: 撰写模块深度分析文档。');
872
- parts.push(
873
- '包含: 模块职责说明(从文件名和类名推断功能意图)、核心类型分析(不是简单罗列而是解释每个类的角色)、依赖关系分析、设计模式识别。'
874
- );
875
- parts.push('如果能推断出数据流或协作关系,请用 Mermaid 图表展示。');
876
- break;
877
- }
878
-
879
- case 'getting-started': {
880
- parts.push('## 任务: 撰写快速上手指南');
881
- parts.push('');
882
-
883
- if (projectInfo.hasPackageSwift) {
884
- parts.push('构建系统: Swift Package Manager');
885
- }
886
- if (projectInfo.hasPodfile) {
887
- parts.push('构建系统: CocoaPods');
888
- }
889
- if (projectInfo.hasXcodeproj) {
890
- parts.push('构建系统: Xcode Project');
891
- }
892
- parts.push('');
893
-
894
- if (spmInfo.targets.length > 0) {
895
- const mainTargets = spmInfo.targets.filter((t) => t.type !== 'test');
896
- const testTargets = spmInfo.targets.filter((t) => t.type === 'test');
897
- if (mainTargets.length > 0) {
898
- parts.push(`主要 Target: ${mainTargets.map((t) => t.name).join(', ')}`);
899
- }
900
- if (testTargets.length > 0) {
901
- parts.push(`测试 Target: ${testTargets.map((t) => t.name).join(', ')}`);
902
- }
903
- parts.push('');
904
- }
905
-
906
- if (astInfo.overview?.entryPoints?.length > 0) {
907
- parts.push(`入口点: ${astInfo.overview.entryPoints.join(', ')}`);
908
- parts.push('');
909
- }
910
-
911
- parts.push('要求: 撰写开发者快速上手指南。');
912
- parts.push(
913
- '包含: 环境要求、项目获取、依赖安装、构建步骤(具体命令)、运行测试、项目目录结构说明。'
914
- );
915
- parts.push('语句清晰,步骤明确,适合新人阅读。');
916
- break;
917
- }
918
-
919
- case 'patterns': {
920
- parts.push('## 任务: 撰写代码模式与最佳实践文档');
921
- parts.push('');
922
-
923
- const groups = {};
924
- for (const r of knowledgeInfo.recipes) {
925
- const json = r.toJSON ? r.toJSON() : r;
926
- const cat = json.category || 'Other';
927
- if (!groups[cat]) {
928
- groups[cat] = [];
929
- }
930
- groups[cat].push(json);
931
- }
932
-
933
- for (const [cat, items] of Object.entries(groups).sort()) {
934
- parts.push(`### ${cat} (${items.length} 条)`);
935
- for (const item of items.slice(0, 8)) {
936
- parts.push(`- ${item.title}: ${item.description || 'N/A'}`);
937
- if (item.doClause) {
938
- parts.push(` 应当: ${item.doClause}`);
939
- }
940
- if (item.dontClause) {
941
- parts.push(` 避免: ${item.dontClause}`);
942
- }
943
- if (item.content?.pattern) {
944
- parts.push(` 代码片段: ${item.content.pattern.slice(0, 200)}`);
945
- }
946
- }
947
- parts.push('');
948
- }
949
-
950
- parts.push('要求: 撰写代码模式文档。对每个分类进行总结分析,解释模式的意义和应用场景。');
951
- parts.push(
952
- '不要只列出条目 — 为每个分类写一段总结,解释该类模式的整体意图。附带代码示例(从数据中取)。'
953
- );
954
- break;
955
- }
956
-
957
- case 'pattern-category': {
958
- const pd = topic._patternData;
959
- parts.push(`## 任务: 撰写 "${pd.category}" 分类的代码模式文档`);
960
- parts.push('');
961
-
962
- for (const item of pd.recipes) {
963
- parts.push(`### ${item.title}`);
964
- if (item.description) {
965
- parts.push(`描述: ${item.description}`);
966
- }
967
- if (item.doClause) {
968
- parts.push(`应当: ${item.doClause}`);
969
- }
970
- if (item.dontClause) {
971
- parts.push(`避免: ${item.dontClause}`);
972
- }
973
- if (item.reasoning?.whyStandard) {
974
- parts.push(`原因: ${item.reasoning.whyStandard}`);
975
- }
976
- if (item.content?.pattern) {
977
- parts.push('代码:');
978
- parts.push('```');
979
- parts.push(item.content.pattern.slice(0, 500));
980
- parts.push('```');
981
- }
982
- parts.push('');
983
- }
984
-
985
- parts.push('要求: 撰写该分类的详细代码模式文档。');
986
- parts.push(
987
- '先写一段总结性概述,然后对每个模式做分析,解释为什么要遵循,给出正确和错误的对比示例。'
988
- );
989
- break;
990
- }
991
-
992
- case 'reference': {
993
- parts.push('## 任务: 撰写协议参考文档');
994
- parts.push('');
995
-
996
- const protoByModule = astInfo.protocolNamesByModule || {};
997
- for (const [mod, protos] of Object.entries(protoByModule).sort()) {
998
- if (protos.length > 0) {
999
- parts.push(`### ${mod} 模块: ${protos.join(', ')}`);
1000
- }
1001
- }
1002
- parts.push('');
1003
- parts.push(
1004
- `总计: ${astInfo.protocols.length} 个协议, ${astInfo.classes.length} 个类/结构体`
1005
- );
1006
- parts.push('');
1007
- parts.push(
1008
- '要求: 撰写协议参考文档。按模块分组,分析每个协议的用途和意义,描述协议之间的关系和设计意图。'
1009
- );
1010
- break;
1011
- }
1012
- }
1013
-
1014
- return parts.join('\n');
1015
- }
1016
-
1017
- /**
1018
- * 构建非 AI 降级的丰富模板内容
1019
- * 即使没有 AI,也要产出有意义的内容 (不是只有列表罗列)
1020
- */
1021
- _buildFallbackArticle(topic, data, isZh) {
1022
- const { projectInfo, astInfo, spmInfo, knowledgeInfo } = data;
1023
-
1024
- switch (topic.type) {
1025
- case 'overview':
1026
- return this._renderIndex(
1027
- projectInfo,
1028
- astInfo,
1029
- spmInfo,
1030
- knowledgeInfo,
1031
- isZh,
1032
- topic._allTopics
1033
- );
1034
- case 'architecture':
1035
- return this._renderArchitecture(projectInfo, astInfo, spmInfo, isZh);
1036
- case 'getting-started':
1037
- return this._renderGettingStarted(projectInfo, spmInfo, astInfo, isZh);
1038
- case 'module':
1039
- return this._renderModule(
1040
- topic._moduleData.target,
1041
- astInfo,
1042
- knowledgeInfo,
1043
- isZh,
1044
- projectInfo
1045
- );
1046
- case 'patterns':
1047
- return this._renderPatterns(knowledgeInfo, isZh);
1048
- case 'pattern-category':
1049
- return this._renderPatternCategory(topic._patternData, isZh);
1050
- case 'reference':
1051
- return this._renderProtocolReference(astInfo, isZh);
1052
- default:
1053
- return '';
1054
- }
1055
- }
1056
-
1057
- // ═══ Markdown 渲染器 ═══════════════════════════════════════
1058
-
1059
- _renderIndex(project, ast, spm, knowledge, isZh, allTopics) {
1060
- const title = isZh ? '项目概述' : 'Project Overview';
1061
-
1062
- const lines = [
1063
- `# ${project.name} — ${title}`,
1064
- '',
1065
- `> ${isZh ? '本文档由 AutoSnippet Repo Wiki 自动生成' : 'Auto-generated by AutoSnippet Repo Wiki'}`,
1066
- `> ${isZh ? '生成时间' : 'Generated at'}: ${new Date().toISOString()}`,
1067
- '',
1068
- ];
1069
-
1070
- // ── 项目简介 ──
1071
- lines.push(`## ${isZh ? '简介' : 'Introduction'}`);
1072
- lines.push('');
1073
-
1074
- const types = [];
1075
- if (project.hasPackageSwift) {
1076
- types.push('SPM (Swift Package Manager)');
1077
- }
1078
- if (project.hasPodfile) {
1079
- types.push('CocoaPods');
1080
- }
1081
- if (project.hasXcodeproj) {
1082
- types.push('Xcode Project');
1083
- }
1084
-
1085
- const overview = ast.overview || {};
1086
- const mainTargets = spm.targets.filter((t) => t.type !== 'test');
1087
- const testTargets = spm.targets.filter((t) => t.type === 'test');
1088
-
1089
- if (isZh) {
1090
- lines.push(
1091
- `**${project.name}** 是一个 ${types.join(' + ') || 'iOS'} 项目,` +
1092
- `包含 ${project.sourceFiles.length} 个源文件` +
1093
- (overview.totalClasses ? `、${overview.totalClasses} 个类/结构体` : '') +
1094
- (overview.totalProtocols ? `、${overview.totalProtocols} 个协议` : '') +
1095
- `。`
1096
- );
1097
- if (mainTargets.length > 0) {
1098
- lines.push(
1099
- `项目由 ${mainTargets.length} 个功能模块组成` +
1100
- (testTargets.length > 0 ? `,配备 ${testTargets.length} 个测试模块` : '') +
1101
- `。`
1102
- );
1103
- }
1104
- } else {
1105
- lines.push(
1106
- `**${project.name}** is a ${types.join(' + ') || 'iOS'} project ` +
1107
- `containing ${project.sourceFiles.length} source files` +
1108
- (overview.totalClasses ? `, ${overview.totalClasses} classes/structs` : '') +
1109
- (overview.totalProtocols ? `, ${overview.totalProtocols} protocols` : '') +
1110
- `.`
1111
- );
1112
- if (mainTargets.length > 0) {
1113
- lines.push(
1114
- `The project consists of ${mainTargets.length} functional modules` +
1115
- (testTargets.length > 0 ? ` with ${testTargets.length} test modules` : '') +
1116
- `.`
1117
- );
1118
- }
1119
- }
1120
- lines.push('');
1121
-
1122
- // ── 模块总览 ──
1123
- if (spm.targets.length > 0) {
1124
- lines.push(`## ${isZh ? '模块总览' : 'Module Overview'}`);
1125
- lines.push('');
1126
- lines.push(
1127
- `| ${isZh ? '模块' : 'Module'} | ${isZh ? '类型' : 'Type'} | ${isZh ? '源文件' : 'Files'} | ${isZh ? '类数' : 'Classes'} | ${isZh ? '协议数' : 'Protocols'} |`
1128
- );
1129
- lines.push('|--------|------|--------|--------|----------|');
1130
- for (const t of spm.targets) {
1131
- const moduleFiles = this._getModuleSourceFiles(t, project);
1132
- const classCount = ast.classNamesByModule?.[t.name]?.length || 0;
1133
- const protoCount = ast.protocolNamesByModule?.[t.name]?.length || 0;
1134
- const hasDoc = allTopics?.some(
1135
- (tp) => tp.type === 'module' && tp._moduleData?.target.name === t.name
1136
- );
1137
- const nameCol = hasDoc ? `[${t.name}](modules/${_slug(t.name)}.md)` : t.name;
1138
- lines.push(
1139
- `| ${nameCol} | ${t.type || 'target'} | ${moduleFiles.length || '-'} | ${classCount || '-'} | ${protoCount || '-'} |`
1140
- );
1141
- }
1142
- lines.push('');
1143
- }
1144
-
1145
- // ── 技术栈 ──
1146
- lines.push(`## ${isZh ? '技术栈' : 'Tech Stack'}`);
1147
- lines.push('');
1148
- if (project.languages && Object.keys(project.languages).length > 0) {
1149
- lines.push(
1150
- `| ${isZh ? '语言' : 'Language'} | ${isZh ? '文件数' : 'Files'} | ${isZh ? '占比' : 'Share'} |`
1151
- );
1152
- lines.push('|--------|-------|------|');
1153
- const total = Object.values(project.languages).reduce((a, b) => a + b, 0);
1154
- for (const [lang, count] of Object.entries(project.languages).sort((a, b) => b[1] - a[1])) {
1155
- const pct = total > 0 ? ((count / total) * 100).toFixed(1) : 0;
1156
- lines.push(`| ${lang} | ${count} | ${pct}% |`);
1157
- }
1158
- lines.push('');
1159
- }
1160
-
1161
- // ── 核心数据 ──
1162
- lines.push(`## ${isZh ? '核心数据' : 'Key Metrics'}`);
1163
- lines.push('');
1164
- lines.push(`| ${isZh ? '指标' : 'Metric'} | ${isZh ? '数量' : 'Count'} |`);
1165
- lines.push('|--------|-------|');
1166
- lines.push(`| ${isZh ? '源文件数' : 'Source Files'} | ${project.sourceFiles.length} |`);
1167
- if (overview.totalClasses) {
1168
- lines.push(`| ${isZh ? '类/结构体' : 'Classes/Structs'} | ${overview.totalClasses} |`);
1169
- }
1170
- if (overview.totalProtocols) {
1171
- lines.push(`| ${isZh ? '协议' : 'Protocols'} | ${overview.totalProtocols} |`);
1172
- }
1173
- if (overview.totalMethods) {
1174
- lines.push(`| ${isZh ? '方法总数' : 'Methods'} | ${overview.totalMethods} |`);
1175
- }
1176
- if (spm.targets.length > 0) {
1177
- lines.push(`| SPM Targets | ${spm.targets.length} |`);
1178
- }
1179
- if (knowledge.recipes.length > 0) {
1180
- lines.push(`| ${isZh ? '知识库条目' : 'KB Recipes'} | ${knowledge.recipes.length} |`);
1181
- }
1182
- lines.push('');
1183
-
1184
- // ── 文档导航 (动态,基于实际生成的主题) ──
1185
- const navTopics = (allTopics || []).filter((t) => t.type !== 'overview');
1186
- if (navTopics.length > 0) {
1187
- lines.push('---');
1188
- lines.push('');
1189
- lines.push(`## ${isZh ? '📖 文档导航' : '📖 Documentation'}`);
1190
- lines.push('');
1191
- for (const t of navTopics) {
1192
- lines.push(`- [${t.title}](${t.path})`);
1193
- }
1194
- lines.push('');
1195
- }
1196
-
1197
- return lines.join('\n');
1198
- }
1199
-
1200
- _renderArchitecture(project, ast, spm, isZh) {
1201
- const lines = [
1202
- `# ${isZh ? '架构总览' : 'Architecture Overview'}`,
1203
- '',
1204
- `> ${isZh ? '本文档由 AutoSnippet Repo Wiki 自动生成' : 'Auto-generated by AutoSnippet Repo Wiki'}`,
1205
- '',
1206
- ];
1207
-
1208
- // 依赖图 (Mermaid)
1209
- if (spm.targets.length > 0) {
1210
- lines.push(`## ${isZh ? '模块依赖图' : 'Module Dependency Graph'}`);
1211
- lines.push('');
1212
- lines.push('```mermaid');
1213
- lines.push('graph TD');
1214
-
1215
- // 渲染 target 节点和依赖边
1216
- const rendered = new Set();
1217
- for (const target of spm.targets) {
1218
- const sid = _mermaidId(target.name);
1219
- if (!rendered.has(sid)) {
1220
- const shape =
1221
- target.type === 'test'
1222
- ? `${sid}[["${target.name} (Test)"]]`
1223
- : `${sid}["${target.name}"]`;
1224
- lines.push(` ${shape}`);
1225
- rendered.add(sid);
1226
- }
1227
- }
1228
-
1229
- // 如果有依赖图数据,渲染边
1230
- if (spm.depGraph) {
1231
- const edges = spm.depGraph.edges || [];
1232
- for (const edge of Array.isArray(edges) ? edges : []) {
1233
- if (edge.from && edge.to) {
1234
- const fromId = _mermaidId(edge.from.split('::').pop() || edge.from);
1235
- const toId = _mermaidId(edge.to.split('::').pop() || edge.to);
1236
- lines.push(` ${fromId} --> ${toId}`);
1237
- }
1238
- }
1239
- }
1240
-
1241
- lines.push('```');
1242
- lines.push('');
1243
- }
1244
-
1245
- // 分层架构
1246
- if (ast.overview) {
1247
- const modules = ast.overview.topLevelModules || [];
1248
- if (modules.length > 0) {
1249
- lines.push(`## ${isZh ? '顶层模块' : 'Top-Level Modules'}`);
1250
- lines.push('');
1251
- lines.push(`| ${isZh ? '模块' : 'Module'} | ${isZh ? '类数量' : 'Classes'} |`);
1252
- lines.push('|--------|---------|');
1253
- const cpm = ast.overview.classesPerModule || {};
1254
- for (const mod of modules) {
1255
- lines.push(`| ${mod} | ${cpm[mod] || 0} |`);
1256
- }
1257
- lines.push('');
1258
- }
1259
-
1260
- // 入口点
1261
- if (ast.overview.entryPoints?.length > 0) {
1262
- lines.push(`## ${isZh ? '入口点' : 'Entry Points'}`);
1263
- lines.push('');
1264
- for (const ep of ast.overview.entryPoints) {
1265
- lines.push(`- \`${ep}\``);
1266
- }
1267
- lines.push('');
1268
- }
1269
- }
1270
-
1271
- // 继承层次 (from CodeEntityGraph)
1272
- if (this.codeEntityGraph) {
1273
- try {
1274
- const topClasses = this._getInheritanceRoots();
1275
- if (topClasses.length > 0) {
1276
- lines.push(`## ${isZh ? '核心继承层次' : 'Key Inheritance Hierarchy'}`);
1277
- lines.push('');
1278
- lines.push('```mermaid');
1279
- lines.push('classDiagram');
1280
- for (const root of topClasses.slice(0, 20)) {
1281
- lines.push(` class ${_mermaidId(root.name)}`);
1282
- for (const child of root.children || []) {
1283
- lines.push(` ${_mermaidId(root.name)} <|-- ${_mermaidId(child)}`);
1284
- }
1285
- }
1286
- lines.push('```');
1287
- lines.push('');
1288
- }
1289
- } catch {
1290
- /* non-critical */
1291
- }
1292
- }
1293
-
1294
- lines.push(`[← ${isZh ? '返回概述' : 'Back to Overview'}](index.md)`);
1295
- lines.push('');
1296
- return lines.join('\n');
1297
- }
1298
-
1299
- _renderModule(target, ast, knowledge, isZh, projectInfo) {
1300
- const lines = [
1301
- `# ${target.name}`,
1302
- '',
1303
- `> ${isZh ? '模块文档 — 由 AutoSnippet Repo Wiki 自动生成' : 'Module doc — Auto-generated by AutoSnippet Repo Wiki'}`,
1304
- '',
1305
- ];
1306
-
1307
- // 收集模块数据
1308
- const moduleFiles = projectInfo ? this._getModuleSourceFiles(target, projectInfo) : [];
1309
- const moduleClasses = ast.classNamesByModule?.[target.name] || [];
1310
- const moduleProtocols = ast.protocolNamesByModule?.[target.name] || [];
1311
- const deps = target.dependencies || target.info?.dependencies || [];
1312
-
1313
- // ── 模块概述 ──
1314
- lines.push(`## ${isZh ? '概述' : 'Overview'}`);
1315
- lines.push('');
1316
-
1317
- // 推断模块功能 (基于名称和内容)
1318
- const purpose = this._inferModulePurpose(
1319
- target.name,
1320
- moduleClasses,
1321
- moduleProtocols,
1322
- moduleFiles
1323
- );
1324
- if (purpose) {
1325
- lines.push(
1326
- isZh
1327
- ? `**${target.name}** ${purpose.zh},包含 ${moduleFiles.length} 个源文件、${moduleClasses.length} 个类/结构体${moduleProtocols.length > 0 ? `、${moduleProtocols.length} 个协议` : ''}。`
1328
- : `**${target.name}** ${purpose.en}, containing ${moduleFiles.length} source files, ${moduleClasses.length} classes/structs${moduleProtocols.length > 0 ? `, ${moduleProtocols.length} protocols` : ''}.`
1329
- );
1330
- } else {
1331
- lines.push(
1332
- isZh
1333
- ? `**${target.name}** 是项目中的一个 ${target.type || 'target'} 模块,包含 ${moduleFiles.length} 个源文件、${moduleClasses.length} 个类/结构体。`
1334
- : `**${target.name}** is a ${target.type || 'target'} module in the project, containing ${moduleFiles.length} source files and ${moduleClasses.length} classes/structs.`
1335
- );
1336
- }
1337
- lines.push('');
1338
-
1339
- // ── 模块信息表 ──
1340
- lines.push(`| ${isZh ? '属性' : 'Property'} | ${isZh ? '值' : 'Value'} |`);
1341
- lines.push('|--------|------|');
1342
- lines.push(`| ${isZh ? '类型' : 'Type'} | ${target.type || 'target'} |`);
1343
- if (target.packageName) {
1344
- lines.push(`| ${isZh ? '所属包' : 'Package'} | ${target.packageName} |`);
1345
- }
1346
- if (target.path || target.info?.path) {
1347
- lines.push(`| ${isZh ? '路径' : 'Path'} | \`${target.path || target.info.path}\` |`);
1348
- }
1349
- if (moduleFiles.length > 0) {
1350
- lines.push(`| ${isZh ? '源文件数' : 'Source Files'} | ${moduleFiles.length} |`);
1351
- }
1352
- if (moduleClasses.length > 0) {
1353
- lines.push(`| ${isZh ? '类/结构体' : 'Classes/Structs'} | ${moduleClasses.length} |`);
1354
- }
1355
- if (moduleProtocols.length > 0) {
1356
- lines.push(`| ${isZh ? '协议' : 'Protocols'} | ${moduleProtocols.length} |`);
1357
- }
1358
- if (deps.length > 0) {
1359
- lines.push(`| ${isZh ? '依赖数' : 'Dependencies'} | ${deps.length} |`);
1360
- }
1361
- lines.push('');
1362
-
1363
- // ── 依赖 ──
1364
- if (deps.length > 0) {
1365
- lines.push(`## ${isZh ? '依赖关系' : 'Dependencies'}`);
1366
- lines.push('');
1367
- lines.push(
1368
- isZh
1369
- ? `${target.name} 依赖以下 ${deps.length} 个模块:`
1370
- : `${target.name} depends on ${deps.length} module(s):`
1371
- );
1372
- lines.push('');
1373
- for (const dep of deps) {
1374
- const depName = typeof dep === 'string' ? dep : dep.name || String(dep);
1375
- lines.push(`- \`${depName}\``);
1376
- }
1377
- lines.push('');
1378
- }
1379
-
1380
- // ── 核心类型分析 ──
1381
- if (moduleClasses.length > 0 || moduleProtocols.length > 0) {
1382
- lines.push(`## ${isZh ? '核心类型' : 'Core Types'}`);
1383
- lines.push('');
1384
-
1385
- if (moduleProtocols.length > 0) {
1386
- lines.push(`### ${isZh ? '协议' : 'Protocols'} (${moduleProtocols.length})`);
1387
- lines.push('');
1388
- lines.push(
1389
- isZh
1390
- ? `${target.name} 定义了 ${moduleProtocols.length} 个协议,用于规范模块的接口边界:`
1391
- : `${target.name} defines ${moduleProtocols.length} protocols establishing the module's interface contracts:`
1392
- );
1393
- lines.push('');
1394
- const sorted = [...moduleProtocols].sort();
1395
- for (const p of sorted.slice(0, 20)) {
1396
- lines.push(`- \`${p}\``);
1397
- }
1398
- if (sorted.length > 20) {
1399
- lines.push(
1400
- `- ... ${isZh ? `还有 ${sorted.length - 20} 个` : `and ${sorted.length - 20} more`}`
1401
- );
1402
- }
1403
- lines.push('');
1404
- }
1405
-
1406
- if (moduleClasses.length > 0) {
1407
- lines.push(`### ${isZh ? '类/结构体' : 'Classes/Structs'} (${moduleClasses.length})`);
1408
- lines.push('');
1409
- const sorted = [...moduleClasses].sort();
1410
- for (const c of sorted.slice(0, 30)) {
1411
- lines.push(`- \`${c}\``);
1412
- }
1413
- if (sorted.length > 30) {
1414
- lines.push(
1415
- `- ... ${isZh ? `还有 ${sorted.length - 30} 个` : `and ${sorted.length - 30} more`}`
1416
- );
1417
- }
1418
- lines.push('');
1419
- }
1420
- }
1421
-
1422
- // ── 源文件分布 ──
1423
- if (moduleFiles.length > 0) {
1424
- lines.push(`## ${isZh ? '源文件分布' : 'Source File Distribution'}`);
1425
- lines.push('');
1426
-
1427
- // 按语言统计
1428
- const langCount = {};
1429
- for (const f of moduleFiles) {
1430
- const ext = path.extname(f);
1431
- const lang = LanguageService.displayNameFromExt(ext);
1432
- langCount[lang] = (langCount[lang] || 0) + 1;
1433
- }
1434
-
1435
- lines.push(`| ${isZh ? '语言' : 'Language'} | ${isZh ? '文件数' : 'Files'} |`);
1436
- lines.push('|--------|-------|');
1437
- for (const [lang, count] of Object.entries(langCount).sort((a, b) => b - a)) {
1438
- lines.push(`| ${lang} | ${count} |`);
1439
- }
1440
- lines.push('');
1441
- }
1442
-
1443
- // ── 该模块相关的 Recipes ──
1444
- if (knowledge.recipes.length > 0) {
1445
- const related = knowledge.recipes.filter((r) => {
1446
- const json = r.toJSON ? r.toJSON() : r;
1447
- return (
1448
- json.moduleName === target.name ||
1449
- json.tags?.includes(target.name) ||
1450
- json.title?.includes(target.name)
1451
- );
1452
- });
1453
- if (related.length > 0) {
1454
- lines.push(`## ${isZh ? '相关知识条目' : 'Related Recipes'}`);
1455
- lines.push('');
1456
- lines.push(
1457
- isZh
1458
- ? `团队知识库中有 ${related.length} 条与 ${target.name} 相关的条目:`
1459
- : `The team knowledge base contains ${related.length} entries related to ${target.name}:`
1460
- );
1461
- lines.push('');
1462
- for (const r of related) {
1463
- const json = r.toJSON ? r.toJSON() : r;
1464
- lines.push(`### ${json.title}`);
1465
- lines.push('');
1466
- if (json.description) {
1467
- lines.push(json.description);
1468
- }
1469
- if (json.doClause) {
1470
- lines.push(`\n**${isZh ? '✅ 应当' : '✅ Do'}**: ${json.doClause}`);
1471
- }
1472
- if (json.dontClause) {
1473
- lines.push(`**${isZh ? '❌ 避免' : "❌ Don't"}**: ${json.dontClause}`);
1474
- }
1475
- lines.push('');
1476
- }
1477
- }
1478
- }
1479
-
1480
- lines.push(`[← ${isZh ? '返回概述' : 'Back to Overview'}](../index.md)`);
1481
- lines.push('');
1482
- return lines.join('\n');
1483
- }
1484
-
1485
- /**
1486
- * 基于模块名称和内容推断模块功能
1487
- * 对常见命名模式做智能推断
1488
- */
1489
- _inferModulePurpose(name, classes, protocols, files) {
1490
- const lower = name.toLowerCase();
1491
- const _fileNames = files.map((f) => path.basename(f).toLowerCase());
1492
-
1493
- // 常见模块功能推断规则
1494
- const rules = [
1495
- {
1496
- match: /network|http|api|client|request|fetch/i,
1497
- zh: '负责网络通信和 API 调用',
1498
- en: 'handles network communication and API calls',
1499
- },
1500
- {
1501
- match: /ui|view|component|widget|screen|page/i,
1502
- zh: '提供用户界面组件',
1503
- en: 'provides user interface components',
1504
- },
1505
- {
1506
- match: /model|entity|domain|data/i,
1507
- zh: '定义数据模型和领域实体',
1508
- en: 'defines data models and domain entities',
1509
- },
1510
- {
1511
- match: /storage|database|cache|persist|core\s*data|realm/i,
1512
- zh: '负责数据持久化和存储',
1513
- en: 'manages data persistence and storage',
1514
- },
1515
- {
1516
- match: /auth|login|session|token|credential/i,
1517
- zh: '处理认证授权和会话管理',
1518
- en: 'handles authentication and session management',
1519
- },
1520
- {
1521
- match: /util|helper|extension|common|shared|foundation/i,
1522
- zh: '提供公共工具类和扩展方法',
1523
- en: 'provides common utilities and extensions',
1524
- },
1525
- { match: /test|spec|mock/i, zh: '包含单元测试和 Mock', en: 'contains unit tests and mocks' },
1526
- {
1527
- match: /router|navigation|coordinator|flow/i,
1528
- zh: '管理页面路由和导航流',
1529
- en: 'manages page routing and navigation flow',
1530
- },
1531
- {
1532
- match: /config|setting|preference|env/i,
1533
- zh: '管理应用配置和环境设置',
1534
- en: 'manages app configuration and environment settings',
1535
- },
1536
- {
1537
- match: /log|analytics|track|monitor/i,
1538
- zh: '提供日志记录和数据分析能力',
1539
- en: 'provides logging and analytics capabilities',
1540
- },
1541
- {
1542
- match: /media|image|video|audio|player/i,
1543
- zh: '处理多媒体资源',
1544
- en: 'handles multimedia resources',
1545
- },
1546
- {
1547
- match: /service|manager|provider/i,
1548
- zh: '提供核心业务服务',
1549
- en: 'provides core business services',
1550
- },
1551
- ];
1552
-
1553
- // 先按模块名匹配
1554
- for (const rule of rules) {
1555
- if (rule.match.test(lower)) {
1556
- return rule;
1557
- }
1558
- }
1559
-
1560
- // 再按类名匹配
1561
- const classStr = classes.join(' ');
1562
- for (const rule of rules) {
1563
- if (rule.match.test(classStr)) {
1564
- return rule;
1565
- }
1566
- }
1567
-
1568
- return null;
1569
- }
1570
-
1571
- // _renderComponents removed in V3 — components are now part of module docs
1572
-
1573
- _renderPatterns(knowledge, isZh) {
1574
- const lines = [
1575
- `# ${isZh ? '代码模式与最佳实践' : 'Code Patterns & Best Practices'}`,
1576
- '',
1577
- `> ${isZh ? '团队沉淀的代码模式与最佳实践(来自 AutoSnippet 知识库)' : 'Code patterns and best practices from AutoSnippet knowledge base'}`,
1578
- '',
1579
- ];
1580
-
1581
- // 按 category 分组
1582
- const groups = {};
1583
- for (const r of knowledge.recipes) {
1584
- const json = r.toJSON ? r.toJSON() : r;
1585
- const cat = json.category || 'Other';
1586
- if (!groups[cat]) {
1587
- groups[cat] = [];
1588
- }
1589
- groups[cat].push(json);
1590
- }
1591
-
1592
- // 总结
1593
- const totalRecipes = knowledge.recipes.length;
1594
- const catCount = Object.keys(groups).length;
1595
- lines.push(
1596
- isZh
1597
- ? `本项目团队在 ${catCount} 个分类下共沉淀了 **${totalRecipes}** 条代码模式和最佳实践。以下按分类进行展示和分析。`
1598
- : `The team has accumulated **${totalRecipes}** code patterns across ${catCount} categories. Below they are organized and analyzed by category.`
1599
- );
1600
- lines.push('');
1601
-
1602
- for (const [cat, items] of Object.entries(groups).sort()) {
1603
- lines.push(`## ${cat} (${items.length})`);
1604
- lines.push('');
1605
-
1606
- // 分类概述
1607
- lines.push(
1608
- isZh
1609
- ? `${cat} 分类包含 ${items.length} 条规则,覆盖了该领域的核心规范。`
1610
- : `The ${cat} category contains ${items.length} rules covering core conventions in this area.`
1611
- );
1612
- lines.push('');
1613
-
1614
- for (const item of items) {
1615
- lines.push(`### ${item.title}`);
1616
- lines.push('');
1617
- if (item.description) {
1618
- lines.push(item.description);
1619
- lines.push('');
1620
- }
1621
- if (item.content?.pattern) {
1622
- lines.push(`\`\`\`${item.language || 'swift'}`);
1623
- lines.push(item.content.pattern);
1624
- lines.push('```');
1625
- lines.push('');
1626
- }
1627
- if (item.doClause) {
1628
- lines.push(`**${isZh ? '✅ 应当' : '✅ Do'}**: ${item.doClause}`);
1629
- lines.push('');
1630
- }
1631
- if (item.dontClause) {
1632
- lines.push(`**${isZh ? '❌ 避免' : "❌ Don't"}**: ${item.dontClause}`);
1633
- lines.push('');
1634
- }
1635
- if (item.reasoning?.whyStandard) {
1636
- lines.push(`> ${isZh ? '💡 原因' : '💡 Rationale'}: ${item.reasoning.whyStandard}`);
1637
- lines.push('');
1638
- }
1639
- }
1640
- }
1641
-
1642
- lines.push(`[← ${isZh ? '返回概述' : 'Back to Overview'}](index.md)`);
1643
- lines.push('');
1644
- return lines.join('\n');
1645
- }
1646
-
1647
- // ═══ V3 新增渲染器 ════════════════════════════════════════
1648
-
1649
- /**
1650
- * 快速上手指南 (非 AI 降级模板)
1651
- */
1652
- _renderGettingStarted(project, spm, ast, isZh) {
1653
- const lines = [
1654
- `# ${isZh ? '快速上手' : 'Getting Started'}`,
1655
- '',
1656
- `> ${isZh ? '本文档由 AutoSnippet Repo Wiki 自动生成' : 'Auto-generated by AutoSnippet Repo Wiki'}`,
1657
- '',
1658
- ];
1659
-
1660
- // 环境要求
1661
- lines.push(`## ${isZh ? '环境要求' : 'Prerequisites'}`);
1662
- lines.push('');
1663
- if (project.hasPackageSwift) {
1664
- lines.push(isZh ? '- Swift 5.5+ (推荐 Swift 5.9+)' : '- Swift 5.5+ (Swift 5.9+ recommended)');
1665
- lines.push(isZh ? '- Xcode 14+' : '- Xcode 14+');
1666
- }
1667
- if (project.hasPodfile) {
1668
- lines.push(isZh ? '- CocoaPods 1.10+' : '- CocoaPods 1.10+');
1669
- }
1670
- if (project.hasXcodeproj) {
1671
- lines.push(isZh ? '- Xcode (最新稳定版)' : '- Xcode (latest stable version)');
1672
- }
1673
- lines.push('');
1674
-
1675
- // 项目目录结构
1676
- lines.push(`## ${isZh ? '项目结构' : 'Project Structure'}`);
1677
- lines.push('');
1678
- lines.push('```');
1679
- lines.push(`${project.name}/`);
1680
- if (spm.targets.length > 0) {
1681
- const mainTargets = spm.targets.filter((t) => t.type !== 'test');
1682
- const testTargets = spm.targets.filter((t) => t.type === 'test');
1683
- if (mainTargets.length > 0) {
1684
- lines.push('├── Sources/');
1685
- for (let i = 0; i < mainTargets.length; i++) {
1686
- const prefix =
1687
- i === mainTargets.length - 1 && testTargets.length === 0 ? '│ └──' : '│ ├──';
1688
- lines.push(`${prefix} ${mainTargets[i].name}/`);
1689
- }
1690
- }
1691
- if (testTargets.length > 0) {
1692
- lines.push('├── Tests/');
1693
- for (let i = 0; i < testTargets.length; i++) {
1694
- const prefix = i === testTargets.length - 1 ? '│ └──' : '│ ├──';
1695
- lines.push(`${prefix} ${testTargets[i].name}/`);
1696
- }
1697
- }
1698
- }
1699
- if (project.hasPackageSwift) {
1700
- lines.push('├── Package.swift');
1701
- }
1702
- if (project.hasPodfile) {
1703
- lines.push('├── Podfile');
1704
- }
1705
- lines.push('```');
1706
- lines.push('');
1707
-
1708
- // 构建步骤
1709
- lines.push(`## ${isZh ? '构建与运行' : 'Build & Run'}`);
1710
- lines.push('');
1711
- if (project.hasPackageSwift) {
1712
- lines.push(isZh ? '### 使用 Swift Package Manager' : '### Using Swift Package Manager');
1713
- lines.push('');
1714
- lines.push('```bash');
1715
- lines.push(isZh ? '# 获取项目' : '# Clone the project');
1716
- lines.push(`git clone <repository-url>`);
1717
- lines.push(`cd ${project.name}`);
1718
- lines.push('');
1719
- lines.push(isZh ? '# 解析依赖' : '# Resolve dependencies');
1720
- lines.push('swift package resolve');
1721
- lines.push('');
1722
- lines.push(isZh ? '# 构建' : '# Build');
1723
- lines.push('swift build');
1724
- lines.push('');
1725
- lines.push(isZh ? '# 运行测试' : '# Run tests');
1726
- lines.push('swift test');
1727
- lines.push('```');
1728
- lines.push('');
1729
- }
1730
- if (project.hasPodfile) {
1731
- lines.push(isZh ? '### 使用 CocoaPods' : '### Using CocoaPods');
1732
- lines.push('');
1733
- lines.push('```bash');
1734
- lines.push('pod install');
1735
- lines.push('open *.xcworkspace');
1736
- lines.push('```');
1737
- lines.push('');
1738
- }
1739
-
1740
- // 模块说明
1741
- if (spm.targets.length > 0) {
1742
- const mainTargets = spm.targets.filter((t) => t.type !== 'test');
1743
- if (mainTargets.length > 0) {
1744
- lines.push(`## ${isZh ? '核心模块' : 'Core Modules'}`);
1745
- lines.push('');
1746
- lines.push(
1747
- `| ${isZh ? '模块' : 'Module'} | ${isZh ? '类型' : 'Type'} | ${isZh ? '类型数' : 'Types'} | ${isZh ? '说明' : 'Description'} |`
1748
- );
1749
- lines.push('|--------|------|--------|------|');
1750
- for (const t of mainTargets) {
1751
- const cls = (ast.classNamesByModule?.[t.name] || []).length;
1752
- const purpose = this._inferModulePurpose(
1753
- t.name,
1754
- ast.classNamesByModule?.[t.name] || [],
1755
- ast.protocolNamesByModule?.[t.name] || [],
1756
- []
1757
- );
1758
- const desc = purpose ? (isZh ? purpose.zh : purpose.en) : '-';
1759
- lines.push(`| ${t.name} | ${t.type || 'library'} | ${cls} | ${desc} |`);
1760
- }
1761
- lines.push('');
1762
- }
1763
- }
1764
-
1765
- lines.push(`[← ${isZh ? '返回概述' : 'Back to Overview'}](index.md)`);
1766
- lines.push('');
1767
- return lines.join('\n');
1768
- }
1769
-
1770
- /**
1771
- * 按分类拆分的代码模式文档
1772
- */
1773
- _renderPatternCategory(patternData, isZh) {
1774
- const { category, recipes } = patternData;
1775
- const lines = [
1776
- `# ${category}`,
1777
- '',
1778
- `> ${isZh ? `${category} 分类下的 ${recipes.length} 条代码模式(来自 AutoSnippet 知识库)` : `${recipes.length} code patterns in ${category} category (from AutoSnippet KB)`}`,
1779
- '',
1780
- ];
1781
-
1782
- // 分类概述
1783
- lines.push(
1784
- isZh
1785
- ? `本文档收录了 ${category} 分类下的 ${recipes.length} 条代码模式和规范,这些规则由团队在开发实践中总结沉淀。`
1786
- : `This document covers ${recipes.length} code patterns and conventions in the ${category} category, distilled from team development practices.`
1787
- );
1788
- lines.push('');
1789
-
1790
- for (const item of recipes) {
1791
- lines.push(`## ${item.title}`);
1792
- lines.push('');
1793
- if (item.description) {
1794
- lines.push(item.description);
1795
- lines.push('');
1796
- }
1797
- if (item.doClause) {
1798
- lines.push(`**${isZh ? '✅ 应当' : '✅ Do'}**: ${item.doClause}`);
1799
- lines.push('');
1800
- }
1801
- if (item.dontClause) {
1802
- lines.push(`**${isZh ? '❌ 避免' : "❌ Don't"}**: ${item.dontClause}`);
1803
- lines.push('');
1804
- }
1805
- if (item.content?.pattern) {
1806
- lines.push(`\`\`\`${item.language || 'text'}`);
1807
- lines.push(item.content.pattern);
1808
- lines.push('```');
1809
- lines.push('');
1810
- }
1811
- if (item.reasoning?.whyStandard) {
1812
- lines.push(`> ${isZh ? '💡 原因' : '💡 Rationale'}: ${item.reasoning.whyStandard}`);
1813
- lines.push('');
1814
- }
1815
- }
1816
-
1817
- lines.push(`[← ${isZh ? '返回概述' : 'Back to Overview'}](../index.md)`);
1818
- lines.push('');
1819
- return lines.join('\n');
1820
- }
1821
-
1822
- /**
1823
- * 协议参考文档
1824
- */
1825
- _renderProtocolReference(ast, isZh) {
1826
- const lines = [
1827
- `# ${isZh ? '协议参考' : 'Protocol Reference'}`,
1828
- '',
1829
- `> ${isZh ? `项目中定义的 ${ast.protocols.length} 个协议` : `${ast.protocols.length} protocols defined in the project`}`,
1830
- '',
1831
- ];
1832
-
1833
- lines.push(
1834
- isZh
1835
- ? `协议(Protocol)定义了类型需要遵循的接口契约。本项目共定义了 ${ast.protocols.length} 个协议,以下按模块分组展示。`
1836
- : `Protocols define interface contracts that types must conform to. This project defines ${ast.protocols.length} protocols, organized by module below.`
1837
- );
1838
- lines.push('');
1839
-
1840
- // 按模块分组
1841
- const protoByModule = ast.protocolNamesByModule || {};
1842
- const grouped = new Set();
1843
-
1844
- for (const [mod, protos] of Object.entries(protoByModule).sort()) {
1845
- if (protos.length === 0) {
1846
- continue;
1847
- }
1848
- lines.push(`## ${mod}`);
1849
- lines.push('');
1850
- lines.push(
1851
- isZh
1852
- ? `${mod} 模块定义了 ${protos.length} 个协议:`
1853
- : `${mod} module defines ${protos.length} protocols:`
1854
- );
1855
- lines.push('');
1856
- for (const p of protos.sort()) {
1857
- lines.push(`- \`${p}\``);
1858
- grouped.add(p);
1859
- }
1860
- lines.push('');
1861
- }
1862
-
1863
- // 未分组的协议
1864
- const ungrouped = ast.protocols.filter((p) => !grouped.has(p));
1865
- if (ungrouped.length > 0) {
1866
- lines.push(`## ${isZh ? '其他协议' : 'Other Protocols'}`);
1867
- lines.push('');
1868
- for (const p of ungrouped.sort()) {
1869
- lines.push(`- \`${p}\``);
1870
- }
1871
- lines.push('');
1872
- }
1873
-
1874
- lines.push(`[← ${isZh ? '返回概述' : 'Back to Overview'}](index.md)`);
1875
- lines.push('');
1876
- return lines.join('\n');
1877
- }
1878
-
1879
- // ═══ V3 AI 系统 Prompt ═══════════════════════════════════
1880
-
1881
- /**
1882
- * 构建 AI 系统 Prompt (V3 — 撰写完整文章,非润色骨架)
1883
- */
1884
- _buildAiSystemPrompt(isZh) {
1885
- if (isZh) {
1886
- return [
1887
- '你是 AutoSnippet Repo Wiki 文档撰写专家。',
1888
- '',
1889
- '任务: 基于代码分析数据,撰写高质量、有深度的项目文档。',
1890
- '',
1891
- '写作原则:',
1892
- '1. 所有类名、文件名、数字必须来自提供的数据,严禁编造',
1893
- '2. 不要简单罗列数据 — 要分析和解释,描述"为什么这样设计"、"模块的职责是什么"',
1894
- '3. 从文件名和类名推断功能意图,给出有见地的分析',
1895
- '4. 用自然语言连贯行文,包含过渡段落和总结性描述',
1896
- '5. 合理使用 Mermaid 图表(graph TD / classDiagram)、表格、代码块来辅助说明',
1897
- '6. 用中文撰写',
1898
- '7. 输出纯 Markdown,不要包裹在代码块中',
1899
- '8. 每篇文章以一级标题 (#) 开始,结构清晰',
1900
- '9. 篇幅适中:300-2000 字(根据主题复杂度调整)',
1901
- '10. 文末包含返回链接: [← 返回概述](index.md) 或 [← 返回概述](../index.md)',
1902
- ].join('\n');
1903
- }
1904
- return [
1905
- 'You are the AutoSnippet Repo Wiki documentation expert.',
1906
- '',
1907
- 'Task: Write high-quality, insightful project documentation based on code analysis data.',
1908
- '',
1909
- 'Writing principles:',
1910
- '1. All class names, file names, and numbers must come from the provided data — never fabricate',
1911
- '2. Do not simply list data — analyze and explain: describe design rationale, module responsibilities',
1912
- '3. Infer functional intent from file names and class names, provide insightful analysis',
1913
- '4. Write coherent prose with transition paragraphs and summaries',
1914
- '5. Use Mermaid diagrams (graph TD / classDiagram), tables, and code blocks judiciously',
1915
- '6. Write in English',
1916
- '7. Output pure Markdown — do not wrap in code blocks',
1917
- '8. Start each article with a level-1 heading (#), maintain clear structure',
1918
- '9. Appropriate length: 300-2000 words (adjust by topic complexity)',
1919
- '10. End with a back link: [← Back to Overview](index.md) or [← Back to Overview](../index.md)',
1920
- ].join('\n');
1921
- }
1922
-
1923
843
  // ═══ Phase 8: 同步 Cursor 端 MD ═══════════════════════════
1924
844
 
1925
845
  /**
@@ -1990,119 +910,6 @@ export class WikiGenerator {
1990
910
  }
1991
911
  }
1992
912
 
1993
- // ═══ Phase 9: 去重 ═════════════════════════════════════════
1994
-
1995
- /**
1996
- * 两层去重
1997
- *
1998
- * Layer 1: Title slug 碰撞 — 同名文件不同目录 → hash 相同则删除副本
1999
- * Layer 2: Content hash — 跨文件内容完全相同 → 仅保留第一个
2000
- *
2001
- * @param {Array} files
2002
- * @returns {{ removed: string[], kept: number }}
2003
- */
2004
- _dedup(files) {
2005
- const removed = [];
2006
-
2007
- // Layer 1: slug 碰撞(同名文件跨目录)
2008
- const slugMap = new Map(); // slug → first file
2009
- for (const file of files) {
2010
- const slug = path.basename(file.path, path.extname(file.path)).toLowerCase();
2011
- if (slugMap.has(slug)) {
2012
- const existing = slugMap.get(slug);
2013
- // 完全相同 hash → 移除后来的
2014
- if (existing.hash === file.hash) {
2015
- const fullPath = path.join(this.wikiDir, file.path);
2016
- try {
2017
- fs.unlinkSync(fullPath);
2018
- } catch {
2019
- /* skip */
2020
- }
2021
- removed.push(file.path);
2022
- logger.info(
2023
- `[WikiGenerator] Dedup: removed ${file.path} (same hash as ${existing.path})`
2024
- );
2025
- }
2026
- // hash 不同 → 保留两个(不同目录允许同名)
2027
- } else {
2028
- slugMap.set(slug, file);
2029
- }
2030
- }
2031
-
2032
- // Layer 2: content hash 碰撞(不同文件名但内容相同)
2033
- const hashMap = new Map(); // hash → first file path
2034
- for (const file of files) {
2035
- if (removed.includes(file.path)) {
2036
- continue;
2037
- }
2038
- if (hashMap.has(file.hash)) {
2039
- const firstPath = hashMap.get(file.hash);
2040
- // 优先保留代码生成的(非 synced)
2041
- const isFirstSynced = firstPath.startsWith('documents/') || firstPath.startsWith('skills/');
2042
- const isCurrentSynced =
2043
- file.path.startsWith('documents/') || file.path.startsWith('skills/');
2044
-
2045
- if (isCurrentSynced && !isFirstSynced) {
2046
- // 当前是 synced,first 是 codegen → 删除 synced
2047
- const fullPath = path.join(this.wikiDir, file.path);
2048
- try {
2049
- fs.unlinkSync(fullPath);
2050
- } catch {
2051
- /* skip */
2052
- }
2053
- removed.push(file.path);
2054
- logger.info(
2055
- `[WikiGenerator] Dedup: removed synced ${file.path} (same content as ${firstPath})`
2056
- );
2057
- }
2058
- // 其他情况保留两个
2059
- } else {
2060
- hashMap.set(file.hash, file.path);
2061
- }
2062
- }
2063
-
2064
- // 从 files 数组中移除已删除的
2065
- for (let i = files.length - 1; i >= 0; i--) {
2066
- if (removed.includes(files[i].path)) {
2067
- files.splice(i, 1);
2068
- }
2069
- }
2070
-
2071
- if (removed.length > 0) {
2072
- this._emit(WikiPhase.DEDUP, 93, `去重: 移除 ${removed.length} 个重复文件`);
2073
- } else {
2074
- this._emit(WikiPhase.DEDUP, 93, '无重复文件');
2075
- }
2076
-
2077
- return { removed, kept: files.length };
2078
- }
2079
-
2080
- // ═══ 辅助方法 ══════════════════════════════════════════════
2081
-
2082
- /** 从 CodeEntityGraph 提取继承根节点 */
2083
- _getInheritanceRoots() {
2084
- if (!this.codeEntityGraph) {
2085
- return [];
2086
- }
2087
- try {
2088
- // 尝试查询继承关系
2089
- const entities =
2090
- this.codeEntityGraph.queryEntities?.({ entityType: 'class', limit: 50 }) || [];
2091
- const roots = [];
2092
- for (const e of entities) {
2093
- const _parents =
2094
- this.codeEntityGraph.queryEdges?.({ toId: e.entityId, relation: 'inherits' }) || [];
2095
- const children =
2096
- this.codeEntityGraph.queryEdges?.({ fromId: e.entityId, relation: 'inherits' }) || [];
2097
- if (children.length > 0) {
2098
- roots.push({ name: e.name, children: children.map((c) => c.toId || c.to_id) });
2099
- }
2100
- }
2101
- return roots.sort((a, b) => (b.children?.length || 0) - (a.children?.length || 0));
2102
- } catch {
2103
- return [];
2104
- }
2105
- }
2106
913
 
2107
914
  _emit(phase, progress, message) {
2108
915
  try {
@@ -2174,7 +981,7 @@ export class WikiGenerator {
2174
981
  const extSet = LanguageService.sourceExts;
2175
982
  let totalSize = 0;
2176
983
  const names = [];
2177
- this._walkDir(
984
+ walkDir(
2178
985
  this.projectRoot,
2179
986
  (filePath) => {
2180
987
  const ext = path.extname(filePath);
@@ -2195,123 +1002,10 @@ export class WikiGenerator {
2195
1002
  }
2196
1003
  }
2197
1004
 
2198
- /**
2199
- * 遍历目录(排除 build/Pods/DerivedData 等)
2200
- */
2201
- _walkDir(dir, callback, maxFiles = 500) {
2202
- const excludeNames = new Set([
2203
- 'Pods',
2204
- 'Carthage',
2205
- 'node_modules',
2206
- '.build',
2207
- 'build',
2208
- 'DerivedData',
2209
- 'vendor',
2210
- '.git',
2211
- '__tests__',
2212
- 'Tests',
2213
- 'AutoSnippet',
2214
- '.cursor',
2215
- ]);
2216
- let count = 0;
2217
-
2218
- const walk = (d) => {
2219
- if (count >= maxFiles) {
2220
- return;
2221
- }
2222
- let entries;
2223
- try {
2224
- entries = fs.readdirSync(d, { withFileTypes: true });
2225
- } catch {
2226
- return;
2227
- }
2228
-
2229
- for (const entry of entries) {
2230
- if (count >= maxFiles) {
2231
- return;
2232
- }
2233
- if (excludeNames.has(entry.name)) {
2234
- continue;
2235
- }
2236
- if (entry.name.startsWith('.')) {
2237
- continue;
2238
- }
2239
-
2240
- const fullPath = path.join(d, entry.name);
2241
- if (entry.isDirectory()) {
2242
- walk(fullPath);
2243
- } else if (entry.isFile()) {
2244
- callback(fullPath);
2245
- count++;
2246
- }
2247
- }
2248
- };
2249
-
2250
- walk(dir);
2251
- }
2252
-
2253
1005
  _abortedResult() {
2254
1006
  return { success: false, error: 'aborted', duration: 0 };
2255
1007
  }
2256
1008
 
2257
- /**
2258
- * 从文件相对路径推断所属模块名
2259
- * SPM 约定: Sources/{ModuleName}/... → ModuleName
2260
- * 否则取第一级目录名
2261
- */
2262
- _inferModuleFromPath(filePath) {
2263
- const parts = filePath.split('/');
2264
- const sourcesIdx = parts.indexOf('Sources');
2265
- if (sourcesIdx >= 0 && sourcesIdx + 1 < parts.length) {
2266
- return parts[sourcesIdx + 1];
2267
- }
2268
- return parts.length > 1 ? parts[0] : null;
2269
- }
2270
-
2271
- /**
2272
- * 获取某个 Target 对应的源文件列表
2273
- * 按优先级匹配: target.path → target.info.path → sourceFilesByModule[name]
2274
- */
2275
- _getModuleSourceFiles(target, projectInfo) {
2276
- const sfm = projectInfo.sourceFilesByModule || {};
2277
- const name = target.name;
2278
-
2279
- // 1. 按模块名直接匹配(最常见: Sources/{name}/ 解析出的 key)
2280
- if (sfm[name]?.length > 0) {
2281
- return sfm[name];
2282
- }
2283
-
2284
- // 2. 通过 target.path 或 target.info.path 匹配
2285
- const targetPath = target.path || target.info?.path;
2286
- if (targetPath) {
2287
- const matched = (projectInfo.sourceFiles || []).filter(
2288
- (f) => f.startsWith(`${targetPath}/`) || f.startsWith(targetPath + path.sep)
2289
- );
2290
- if (matched.length > 0) {
2291
- return matched;
2292
- }
2293
- }
2294
-
2295
- // 3. 大小写不敏感模糊匹配
2296
- const lower = name.toLowerCase();
2297
- for (const [key, files] of Object.entries(sfm)) {
2298
- if (key.toLowerCase() === lower) {
2299
- return files;
2300
- }
2301
- }
2302
-
2303
- return [];
2304
- }
2305
- }
2306
-
2307
- // ─── 工具函数 ────────────────────────────────────────────────
2308
-
2309
- function _slug(name) {
2310
- return name.replace(/[^a-zA-Z0-9_-]/g, '_').toLowerCase();
2311
- }
2312
-
2313
- function _mermaidId(name) {
2314
- return name.replace(/[^a-zA-Z0-9]/g, '_');
2315
1009
  }
2316
1010
 
2317
1011
  export default WikiGenerator;