autosnippet 3.0.13 → 3.1.1

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 (100) hide show
  1. package/bin/api-server.js +2 -0
  2. package/bin/cli.js +24 -19
  3. package/config/default.json +1 -1
  4. package/lib/bootstrap.js +4 -4
  5. package/lib/cli/SetupService.js +29 -29
  6. package/lib/cli/UpgradeService.js +3 -2
  7. package/lib/core/AstAnalyzer.js +1 -1
  8. package/lib/core/ast/ensure-grammars.js +1 -1
  9. package/lib/core/ast/index.js +62 -11
  10. package/lib/core/ast/lang-dart.js +27 -21
  11. package/lib/core/ast/lang-go.js +6 -20
  12. package/lib/core/ast/lang-rust.js +53 -28
  13. package/lib/core/ast/parser-init.js +9 -5
  14. package/lib/core/discovery/DartDiscoverer.js +4 -10
  15. package/lib/core/discovery/GoDiscoverer.js +45 -25
  16. package/lib/core/discovery/NodeDiscoverer.js +1 -3
  17. package/lib/core/discovery/PythonDiscoverer.js +7 -1
  18. package/lib/core/discovery/RustDiscoverer.js +111 -38
  19. package/lib/core/discovery/index.js +2 -2
  20. package/lib/core/enhancement/django-enhancement.js +10 -4
  21. package/lib/core/enhancement/fastapi-enhancement.js +16 -9
  22. package/lib/core/enhancement/go-grpc-enhancement.js +2 -1
  23. package/lib/core/enhancement/go-web-enhancement.js +3 -6
  24. package/lib/core/enhancement/ml-enhancement.js +6 -3
  25. package/lib/core/enhancement/nextjs-enhancement.js +17 -7
  26. package/lib/core/enhancement/node-server-enhancement.js +4 -2
  27. package/lib/core/enhancement/react-enhancement.js +6 -3
  28. package/lib/core/enhancement/rust-tokio-enhancement.js +6 -2
  29. package/lib/core/enhancement/rust-web-enhancement.js +13 -7
  30. package/lib/core/enhancement/vue-enhancement.js +10 -5
  31. package/lib/external/ai/AiFactory.js +3 -1
  32. package/lib/external/ai/AiProvider.js +3 -1
  33. package/lib/external/mcp/McpServer.js +2 -0
  34. package/lib/external/mcp/handlers/bootstrap/base-dimensions.js +1 -2
  35. package/lib/external/mcp/handlers/bootstrap/pipeline/checkpoint.js +7 -1
  36. package/lib/external/mcp/handlers/bootstrap/pipeline/noAiFallback.js +55 -26
  37. package/lib/external/mcp/handlers/bootstrap/pipeline/orchestrator.js +8 -8
  38. package/lib/external/mcp/handlers/bootstrap/refine.js +3 -1
  39. package/lib/external/mcp/handlers/bootstrap.js +4 -10
  40. package/lib/external/mcp/handlers/browse.js +6 -2
  41. package/lib/external/mcp/handlers/guard.js +6 -2
  42. package/lib/external/mcp/handlers/skill.js +6 -2
  43. package/lib/http/HttpServer.js +1 -1
  44. package/lib/http/routes/candidates.js +3 -1
  45. package/lib/http/routes/extract.js +4 -5
  46. package/lib/http/routes/guardRules.js +1 -1
  47. package/lib/http/routes/modules.js +9 -3
  48. package/lib/http/routes/skills.js +54 -6
  49. package/lib/http/routes/violations.js +4 -3
  50. package/lib/infrastructure/external/ClipboardManager.js +24 -7
  51. package/lib/infrastructure/external/NativeUi.js +3 -1
  52. package/lib/infrastructure/external/OpenBrowser.js +1 -0
  53. package/lib/infrastructure/external/XcodeAutomation.js +5 -5
  54. package/lib/infrastructure/vector/IndexingPipeline.js +14 -5
  55. package/lib/injection/ServiceContainer.js +34 -11
  56. package/lib/platform/ios/index.js +20 -25
  57. package/lib/platform/ios/routes/spm.js +6 -3
  58. package/lib/platform/ios/snippet/PlaceholderConverter.js +6 -2
  59. package/lib/platform/ios/snippet/XcodeCodec.js +4 -2
  60. package/lib/platform/ios/spm/SpmDiscoverer.js +1 -1
  61. package/lib/platform/ios/spm/SpmService.js +3 -1
  62. package/lib/platform/ios/xcode/XcodeIntegration.js +10 -12
  63. package/lib/platform/ios/xcode/XcodeWriteUtils.js +6 -1
  64. package/lib/service/automation/FileWatcher.js +1 -3
  65. package/lib/service/automation/handlers/CreateHandler.js +3 -5
  66. package/lib/service/automation/handlers/GuardHandler.js +11 -32
  67. package/lib/service/automation/handlers/SearchHandler.js +9 -9
  68. package/lib/service/chat/CandidateGuardrail.js +11 -6
  69. package/lib/service/chat/ChatAgent.js +31 -22
  70. package/lib/service/chat/HandoffProtocol.js +5 -2
  71. package/lib/service/chat/tools/composite.js +3 -2
  72. package/lib/service/chat/tools/index.js +60 -71
  73. package/lib/service/chat/tools/infrastructure.js +9 -4
  74. package/lib/service/chat/tools/lifecycle.js +22 -5
  75. package/lib/service/chat/tools/project-access.js +5 -9
  76. package/lib/service/chat/tools.js +1 -2
  77. package/lib/service/cursor/AgentInstructionsGenerator.js +33 -15
  78. package/lib/service/cursor/CursorDeliveryPipeline.js +2 -1
  79. package/lib/service/cursor/KnowledgeCompressor.js +16 -7
  80. package/lib/service/guard/ComplianceReporter.js +5 -2
  81. package/lib/service/guard/GuardCheckEngine.js +53 -26
  82. package/lib/service/guard/GuardCodeChecks.js +217 -188
  83. package/lib/service/guard/GuardCrossFileChecks.js +203 -184
  84. package/lib/service/guard/GuardPatternUtils.js +17 -10
  85. package/lib/service/module/ModuleService.js +180 -56
  86. package/lib/service/recipe/RecipeCandidateValidator.js +11 -8
  87. package/lib/service/snippet/SnippetFactory.js +3 -3
  88. package/lib/service/snippet/SnippetInstaller.js +35 -11
  89. package/lib/service/snippet/codecs/VSCodeCodec.js +2 -2
  90. package/lib/service/wiki/WikiGenerator.js +67 -40
  91. package/lib/service/wiki/WikiRenderers.js +105 -80
  92. package/lib/service/wiki/WikiUtils.js +217 -80
  93. package/lib/shared/LanguageService.js +111 -53
  94. package/lib/shared/PathGuard.js +0 -8
  95. package/package.json +3 -9
  96. package/scripts/bench-real-projects.mjs +29 -29
  97. package/scripts/generate-recipe-drafts.js +17 -27
  98. package/scripts/init-snippets.js +43 -24
  99. package/scripts/install-vscode-copilot.js +3 -19
  100. package/scripts/setup-mcp-config.js +0 -4
@@ -35,22 +35,17 @@ 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 { buildAiSystemPrompt, buildArticlePrompt, buildFallbackArticle } from './WikiRenderers.js';
38
39
  import {
39
- slug,
40
- walkDir,
41
- inferModuleFromPath,
42
- getModuleSourceFiles,
43
- getInheritanceRoots,
44
40
  dedup,
45
41
  detectBuildSystems,
46
42
  getLangTerms,
43
+ getModuleSourceFiles,
44
+ inferModuleFromPath,
47
45
  profileFolders,
46
+ slug,
47
+ walkDir,
48
48
  } from './WikiUtils.js';
49
- import {
50
- buildArticlePrompt,
51
- buildFallbackArticle,
52
- buildAiSystemPrompt,
53
- } from './WikiRenderers.js';
54
49
 
55
50
  const logger = Logger.getInstance();
56
51
 
@@ -260,10 +255,10 @@ export class WikiGenerator {
260
255
  name: path.basename(this.projectRoot),
261
256
  root: this.projectRoot,
262
257
  // 通用构建系统检测(替代硬编码 iOS 三件套)
263
- buildSystems: [], // [{eco, buildTool}]
258
+ buildSystems: [], // [{eco, buildTool}]
264
259
  sourceFiles: [],
265
260
  languages: {},
266
- langProfile: null, // LanguageService.detectProfile() 结果
261
+ langProfile: null, // LanguageService.detectProfile() 结果
267
262
  primaryLanguage: 'unknown',
268
263
  // 保留向后兼容字段
269
264
  hasPackageSwift: false,
@@ -341,7 +336,11 @@ export class WikiGenerator {
341
336
  info.langProfile = LanguageService.detectProfile(bareStats);
342
337
  info.primaryLanguage = info.langProfile.primary;
343
338
 
344
- this._emit(WikiPhase.SCAN, 12, `发现 ${info.sourceFiles.length} 个源文件 (${LanguageService.displayName(info.primaryLanguage)})`);
339
+ this._emit(
340
+ WikiPhase.SCAN,
341
+ 12,
342
+ `发现 ${info.sourceFiles.length} 个源文件 (${LanguageService.displayName(info.primaryLanguage)})`
343
+ );
345
344
  return info;
346
345
  }
347
346
 
@@ -488,7 +487,8 @@ export class WikiGenerator {
488
487
  // ── 2. 架构概览 (需要模块/依赖关系) ──
489
488
  const moduleKeys = Object.keys(astInfo.classNamesByModule || {});
490
489
  const sourceModuleKeys = Object.keys(projectInfo.sourceFilesByModule || {});
491
- const hasMultiModule = moduleInfo.targets.length >= 2 || moduleKeys.length >= 2 || sourceModuleKeys.length >= 2;
490
+ const hasMultiModule =
491
+ moduleInfo.targets.length >= 2 || moduleKeys.length >= 2 || sourceModuleKeys.length >= 2;
492
492
  const hasDepGraph = moduleInfo.depGraph != null;
493
493
  const hasInheritance = this.codeEntityGraph != null;
494
494
 
@@ -506,7 +506,9 @@ export class WikiGenerator {
506
506
  const hasEntryPoints = (astInfo.overview?.entryPoints?.length || 0) > 0;
507
507
  const hasBuildSystem =
508
508
  projectInfo.buildSystems.length > 0 ||
509
- projectInfo.hasPackageSwift || projectInfo.hasPodfile || projectInfo.hasXcodeproj;
509
+ projectInfo.hasPackageSwift ||
510
+ projectInfo.hasPodfile ||
511
+ projectInfo.hasXcodeproj;
510
512
 
511
513
  if (hasEntryPoints || hasBuildSystem) {
512
514
  topics.push({
@@ -520,8 +522,7 @@ export class WikiGenerator {
520
522
 
521
523
  // ── 4. 模块深度文档 (仅对实质性模块生成) ──
522
524
  const discoverers = moduleInfo.projectInfo?.discoverers || [];
523
- const genericOnlyDiscovery =
524
- discoverers.length === 1 && discoverers[0]?.id === 'generic';
525
+ const genericOnlyDiscovery = discoverers.length === 1 && discoverers[0]?.id === 'generic';
525
526
  const monolithSingleTarget =
526
527
  moduleInfo.targets.length === 1 &&
527
528
  (moduleInfo.targets[0]?.path === projectInfo.root ||
@@ -560,11 +561,15 @@ export class WikiGenerator {
560
561
  const sfm = projectInfo.sourceFilesByModule || {};
561
562
  const sorted = Object.entries(sfm).sort((a, b) => b[1].length - a[1].length);
562
563
  for (const [modName, modFiles] of sorted) {
563
- if (modFiles.length < 2) continue;
564
+ if (modFiles.length < 2) {
565
+ continue;
566
+ }
564
567
  const classCount = (astInfo.classNamesByModule?.[modName] || []).length;
565
568
  const protoCount = (astInfo.protocolNamesByModule?.[modName] || []).length;
566
569
  const richness = modFiles.length + classCount * 2 + protoCount * 2;
567
- if (richness < 3) continue;
570
+ if (richness < 3) {
571
+ continue;
572
+ }
568
573
  topics.push({
569
574
  id: `module-${slug(modName)}`,
570
575
  path: `modules/${slug(modName)}.md`,
@@ -634,14 +639,19 @@ export class WikiGenerator {
634
639
  });
635
640
  }
636
641
 
637
- // ── 7. 文件夹画像文档 (AST 稀疏项目的降级策略) ──
638
- // AST 无法提取足够的 类/函数/协议 时,转为文件夹级分析
642
+ // ── 7. 文件夹画像文档 ──
643
+ // 触发条件 (满足任一即启用):
644
+ // a) AST 稀疏: 类/协议 < 5 且无模块文档
645
+ // b) generic monolith: 仅 generic discoverer + 单 target + 多目录
646
+ // c) 核心文章过少: 当前主题 ≤ 4 篇 → 用文件夹分析补充内容丰富度
639
647
  const astEntityCount = (astInfo.classes?.length || 0) + (astInfo.protocols?.length || 0);
640
- const hasModuleDocs = topics.some(t => t.type === 'module');
648
+ const hasModuleDocs = topics.some((t) => t.type === 'module');
641
649
  const astSparse = astEntityCount < 5 && !hasModuleDocs;
642
650
  const shouldProfileForGenericMonolith =
643
651
  genericOnlyDiscovery && monolithSingleTarget && sourceModuleKeys.length >= 2;
644
- const shouldEnableFolderProfiling = astSparse || shouldProfileForGenericMonolith;
652
+ const tooFewCoreArticles = topics.length <= 4 && sourceModuleKeys.length >= 2;
653
+ const shouldEnableFolderProfiling =
654
+ astSparse || shouldProfileForGenericMonolith || tooFewCoreArticles;
645
655
 
646
656
  if (shouldEnableFolderProfiling) {
647
657
  const rawFolderProfiles = profileFolders(projectInfo, {
@@ -676,18 +686,25 @@ export class WikiGenerator {
676
686
  const MAX_FOLDER_DOCS = 10;
677
687
  let folderDocCount = 0;
678
688
  for (const fp of folderProfiles) {
679
- if (folderDocCount >= MAX_FOLDER_DOCS) break;
680
- if (fp.fileCount < 5) continue;
689
+ if (folderDocCount >= MAX_FOLDER_DOCS) {
690
+ break;
691
+ }
692
+ if (fp.fileCount < 5) {
693
+ continue;
694
+ }
681
695
  const folderDocSlug = slug(fp.relPath.replaceAll('/', '-'));
682
696
  // 文件夹丰富度评分: 文件数 + 入口点×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; // 过于单薄的文件夹不值得独立文档
697
+ const richness =
698
+ fp.fileCount +
699
+ fp.entryPoints.length * 3 +
700
+ fp.namingPatterns.length * 2 +
701
+ fp.imports.length +
702
+ fp.headerComments.length * 2 +
703
+ (fp.readme ? 5 : 0);
704
+
705
+ if (richness < 10) {
706
+ continue; // 过于单薄的文件夹不值得独立文档
707
+ }
691
708
 
692
709
  topics.push({
693
710
  id: `folder-${folderDocSlug}`,
@@ -700,10 +717,14 @@ export class WikiGenerator {
700
717
  folderDocCount++;
701
718
  }
702
719
 
703
- const folderProfileReason = astSparse ? 'AST sparse' : 'generic monolith';
720
+ const folderProfileReason = astSparse
721
+ ? 'AST sparse'
722
+ : tooFewCoreArticles
723
+ ? `few core articles (${topics.length - topics.filter((t) => t.type === 'folder-overview' || t.type === 'folder-profile').length} core)`
724
+ : 'generic monolith';
704
725
  logger.info(
705
726
  `[WikiGenerator] Folder profiling (${folderProfileReason}): ${folderProfiles.length} folders analyzed, ` +
706
- `${topics.filter(t => t.type === 'folder-profile').length} folder docs planned`
727
+ `${topics.filter((t) => t.type === 'folder-profile').length} folder docs planned`
707
728
  );
708
729
  }
709
730
  }
@@ -809,7 +830,10 @@ export class WikiGenerator {
809
830
 
810
831
  // 写入文件
811
832
  const fileInfo = this._writeFile(topic.path, content);
812
- if (composed > 0 && content !== buildFallbackArticle(topic, structuredData, isZh, this.codeEntityGraph)) {
833
+ if (
834
+ composed > 0 &&
835
+ content !== buildFallbackArticle(topic, structuredData, isZh, this.codeEntityGraph)
836
+ ) {
813
837
  fileInfo.polished = true;
814
838
  }
815
839
  files.push(fileInfo);
@@ -823,9 +847,14 @@ export class WikiGenerator {
823
847
  let overviewContent = null;
824
848
  // overview 始终存在于 files 中(因为 priority 最高且始终生成)
825
849
  // 重新用实际 writtenTopics 渲染
826
- overviewContent = buildFallbackArticle(overviewTopic, structuredData, isZh, this.codeEntityGraph);
850
+ overviewContent = buildFallbackArticle(
851
+ overviewTopic,
852
+ structuredData,
853
+ isZh,
854
+ this.codeEntityGraph
855
+ );
827
856
  // 如果之前 AI compose 过 overview,保留 AI 版本(AI 版本已在初次写入时处理导航)
828
- const overviewFile = files.find(f => f.path === overviewTopic.path);
857
+ const overviewFile = files.find((f) => f.path === overviewTopic.path);
829
858
  if (overviewFile && !overviewFile.polished && overviewContent) {
830
859
  this._writeFile(overviewTopic.path, overviewContent);
831
860
  }
@@ -910,7 +939,6 @@ export class WikiGenerator {
910
939
  }
911
940
  }
912
941
 
913
-
914
942
  _emit(phase, progress, message) {
915
943
  try {
916
944
  this.onProgress(phase, progress, message);
@@ -1005,7 +1033,6 @@ export class WikiGenerator {
1005
1033
  _abortedResult() {
1006
1034
  return { success: false, error: 'aborted', duration: 0 };
1007
1035
  }
1008
-
1009
1036
  }
1010
1037
 
1011
1038
  export default WikiGenerator;
@@ -10,16 +10,16 @@
10
10
  import path from 'node:path';
11
11
  import { LanguageService } from '../../shared/LanguageService.js';
12
12
  import {
13
- slug,
14
- mermaidId,
15
- getModuleSourceFiles,
16
13
  getInheritanceRoots,
17
- inferModulePurpose,
18
14
  getLangTerms,
15
+ getModuleSourceFiles,
16
+ inferModulePurpose,
17
+ mermaidId,
18
+ slug,
19
19
  } from './WikiUtils.js';
20
20
 
21
21
  // Re-export BUILD_SYSTEM_MARKERS for renderGettingStarted internals
22
- import { BUILD_SYSTEM_MARKERS } from './WikiUtils.js';
22
+ // NOTE: BUILD_SYSTEM_MARKERS is accessed via LanguageService directly where needed
23
23
 
24
24
  // ═══ AI Prompt 构建 ════════════════════════════════════════
25
25
 
@@ -193,9 +193,7 @@ export function buildArticlePrompt(topic, data, isZh, codeEntityGraph) {
193
193
  parts.push('');
194
194
 
195
195
  if (deps.length > 0) {
196
- parts.push(
197
- `### 依赖: ${deps.map((d) => (typeof d === 'string' ? d : d.name)).join(', ')}`
198
- );
196
+ parts.push(`### 依赖: ${deps.map((d) => (typeof d === 'string' ? d : d.name)).join(', ')}`);
199
197
  parts.push('');
200
198
  }
201
199
 
@@ -255,9 +253,15 @@ export function buildArticlePrompt(topic, data, isZh, codeEntityGraph) {
255
253
  parts.push(`构建系统: ${bs.map((b) => b.buildTool).join(', ')}`);
256
254
  } else {
257
255
  // 兼容旧数据
258
- if (projectInfo.hasPackageSwift) parts.push('构建系统: Swift Package Manager');
259
- if (projectInfo.hasPodfile) parts.push('构建系统: CocoaPods');
260
- if (projectInfo.hasXcodeproj) parts.push('构建系统: Xcode Project');
256
+ if (projectInfo.hasPackageSwift) {
257
+ parts.push('构建系统: Swift Package Manager');
258
+ }
259
+ if (projectInfo.hasPodfile) {
260
+ parts.push('构建系统: CocoaPods');
261
+ }
262
+ if (projectInfo.hasXcodeproj) {
263
+ parts.push('构建系统: Xcode Project');
264
+ }
261
265
  }
262
266
  parts.push('');
263
267
 
@@ -370,9 +374,7 @@ export function buildArticlePrompt(topic, data, isZh, codeEntityGraph) {
370
374
  }
371
375
  }
372
376
  parts.push('');
373
- parts.push(
374
- `总计: ${astInfo.protocols.length} 个${il}, ${astInfo.classes.length} 个${tl}`
375
- );
377
+ parts.push(`总计: ${astInfo.protocols.length} 个${il}, ${astInfo.classes.length} 个${tl}`);
376
378
  parts.push('');
377
379
  parts.push(
378
380
  `要求: 撰写${il}参考文档。按模块分组,分析每个${il}的用途和意义,描述${il}之间的关系和设计意图。`
@@ -392,7 +394,11 @@ export function buildArticlePrompt(topic, data, isZh, codeEntityGraph) {
392
394
  for (const fp of profiles) {
393
395
  parts.push(`#### ${fp.relPath}`);
394
396
  parts.push(`- 源文件: ${fp.fileCount} 个, 总大小: ${(fp.totalSize / 1024).toFixed(1)}KB`);
395
- parts.push(`- 语言分布: ${Object.entries(fp.langBreakdown).map(([l, c]) => `${l}(${c})`).join(', ')}`);
397
+ parts.push(
398
+ `- 语言分布: ${Object.entries(fp.langBreakdown)
399
+ .map(([l, c]) => `${l}(${c})`)
400
+ .join(', ')}`
401
+ );
396
402
  if (fp.entryPoints.length > 0) {
397
403
  parts.push(`- 入口文件: ${fp.entryPoints.join(', ')}`);
398
404
  }
@@ -433,7 +439,11 @@ export function buildArticlePrompt(topic, data, isZh, codeEntityGraph) {
433
439
  parts.push(`- 路径: ${fp.relPath}`);
434
440
  parts.push(`- 源文件: ${fp.fileCount} 个`);
435
441
  parts.push(`- 总大小: ${(fp.totalSize / 1024).toFixed(1)}KB`);
436
- parts.push(`- 语言分布: ${Object.entries(fp.langBreakdown).map(([l, c]) => `${l}(${c})`).join(', ')}`);
442
+ parts.push(
443
+ `- 语言分布: ${Object.entries(fp.langBreakdown)
444
+ .map(([l, c]) => `${l}(${c})`)
445
+ .join(', ')}`
446
+ );
437
447
  parts.push('');
438
448
 
439
449
  if (fp.entryPoints.length > 0) {
@@ -497,26 +507,13 @@ export function buildFallbackArticle(topic, data, isZh, codeEntityGraph) {
497
507
 
498
508
  switch (topic.type) {
499
509
  case 'overview':
500
- return renderIndex(
501
- projectInfo,
502
- astInfo,
503
- moduleInfo,
504
- knowledgeInfo,
505
- isZh,
506
- topic._allTopics
507
- );
510
+ return renderIndex(projectInfo, astInfo, moduleInfo, knowledgeInfo, isZh, topic._allTopics);
508
511
  case 'architecture':
509
512
  return renderArchitecture(projectInfo, astInfo, moduleInfo, isZh, codeEntityGraph);
510
513
  case 'getting-started':
511
514
  return renderGettingStarted(projectInfo, moduleInfo, astInfo, isZh);
512
515
  case 'module':
513
- return renderModule(
514
- topic._moduleData.target,
515
- astInfo,
516
- knowledgeInfo,
517
- isZh,
518
- projectInfo
519
- );
516
+ return renderModule(topic._moduleData.target, astInfo, knowledgeInfo, isZh, projectInfo);
520
517
  case 'patterns':
521
518
  return renderPatterns(knowledgeInfo, isZh);
522
519
  case 'pattern-category':
@@ -562,11 +559,19 @@ export function renderIndex(project, ast, modules, knowledge, isZh, allTopics) {
562
559
  types.push(bs.buildTool);
563
560
  }
564
561
  } else {
565
- if (project.hasPackageSwift) types.push('SPM');
566
- if (project.hasPodfile) types.push('CocoaPods');
567
- if (project.hasXcodeproj) types.push('Xcode Project');
562
+ if (project.hasPackageSwift) {
563
+ types.push('SPM');
564
+ }
565
+ if (project.hasPodfile) {
566
+ types.push('CocoaPods');
567
+ }
568
+ if (project.hasXcodeproj) {
569
+ types.push('Xcode Project');
570
+ }
568
571
  }
569
- const projectTypeLabel = types.join(' + ') || (project.primaryLanguage ? LanguageService.displayName(project.primaryLanguage) : 'Software');
572
+ const projectTypeLabel =
573
+ types.join(' + ') ||
574
+ (project.primaryLanguage ? LanguageService.displayName(project.primaryLanguage) : 'Software');
570
575
 
571
576
  const overview = ast.overview || {};
572
577
  const mainTargets = modules.targets.filter((t) => t.type !== 'test');
@@ -632,9 +637,10 @@ export function renderIndex(project, ast, modules, knowledge, isZh, allTopics) {
632
637
  const sorted = Object.entries(sfm).sort((a, b) => b[1].length - a[1].length);
633
638
  lines.push(`## ${isZh ? '模块总览' : 'Module Overview'}`);
634
639
  lines.push('');
635
- lines.push(isZh
636
- ? `项目代码按目录结构可划分为 ${sorted.length} 个模块:`
637
- : `The project code is organized into ${sorted.length} modules:`
640
+ lines.push(
641
+ isZh
642
+ ? `项目代码按目录结构可划分为 ${sorted.length} 个模块:`
643
+ : `The project code is organized into ${sorted.length} modules:`
638
644
  );
639
645
  lines.push('');
640
646
  lines.push(
@@ -642,9 +648,7 @@ export function renderIndex(project, ast, modules, knowledge, isZh, allTopics) {
642
648
  );
643
649
  lines.push('|--------|--------|------|');
644
650
  for (const [modName, modFiles] of sorted.slice(0, 15)) {
645
- const hasDoc = allTopics?.some(
646
- (tp) => tp.type === 'module' && tp.title === modName
647
- );
651
+ const hasDoc = allTopics?.some((tp) => tp.type === 'module' && tp.title === modName);
648
652
  const nameCol = hasDoc ? `[${modName}](modules/${slug(modName)}.md)` : modName;
649
653
  const purpose = inferModulePurpose(modName, [], [], modFiles);
650
654
  const desc = purpose ? (isZh ? purpose.zh : purpose.en) : '-';
@@ -738,9 +742,7 @@ export function renderArchitecture(project, ast, modules, isZh, codeEntityGraph)
738
742
  const sid = mermaidId(target.name);
739
743
  if (!rendered.has(sid)) {
740
744
  const shape =
741
- target.type === 'test'
742
- ? `${sid}[["${target.name} (Test)"]]`
743
- : `${sid}["${target.name}"]`;
745
+ target.type === 'test' ? `${sid}[["${target.name} (Test)"]]` : `${sid}["${target.name}"]`;
744
746
  lines.push(` ${shape}`);
745
747
  rendered.add(sid);
746
748
  }
@@ -876,12 +878,7 @@ export function renderModule(target, ast, knowledge, isZh, projectInfo) {
876
878
  lines.push('');
877
879
 
878
880
  // 推断模块功能 (基于名称和内容)
879
- const purpose = inferModulePurpose(
880
- target.name,
881
- moduleClasses,
882
- moduleProtocols,
883
- moduleFiles
884
- );
881
+ const purpose = inferModulePurpose(target.name, moduleClasses, moduleProtocols, moduleFiles);
885
882
  if (purpose) {
886
883
  lines.push(
887
884
  isZh
@@ -1153,7 +1150,11 @@ export function renderGettingStarted(project, modules, ast, isZh) {
1153
1150
  // Xcode 项目额外环境提示
1154
1151
  if (!ecoSet.has('spm') && !project.hasPackageSwift && !project.hasPodfile) {
1155
1152
  lines.push(isZh ? '- macOS (建议最新版本)' : '- macOS (latest version recommended)');
1156
- lines.push(isZh ? '- Apple Developer Account (如需真机调试)' : '- Apple Developer Account (for device testing)');
1153
+ lines.push(
1154
+ isZh
1155
+ ? '- Apple Developer Account (如需真机调试)'
1156
+ : '- Apple Developer Account (for device testing)'
1157
+ );
1157
1158
  }
1158
1159
  }
1159
1160
  if (ecoSet.has('node')) {
@@ -1166,8 +1167,11 @@ export function renderGettingStarted(project, modules, ast, isZh) {
1166
1167
  lines.push(isZh ? '- Python 3.8+' : '- Python 3.8+');
1167
1168
  const hasPipenv = bs.some((b) => b.buildTool === 'Pipenv');
1168
1169
  const hasPoetry = bs.some((b) => b.buildTool === 'Poetry');
1169
- if (hasPipenv) lines.push('- Pipenv');
1170
- else if (hasPoetry) lines.push('- Poetry');
1170
+ if (hasPipenv) {
1171
+ lines.push('- Pipenv');
1172
+ } else if (hasPoetry) {
1173
+ lines.push('- Poetry');
1174
+ }
1171
1175
  }
1172
1176
  if (ecoSet.has('go')) {
1173
1177
  lines.push(isZh ? '- Go 1.21+' : '- Go 1.21+');
@@ -1222,12 +1226,18 @@ export function renderGettingStarted(project, modules, ast, isZh) {
1222
1226
  // 显示构建配置文件
1223
1227
  for (const b of bs) {
1224
1228
  const marker = BUILD_SYSTEM_FILES[b.buildTool];
1225
- if (marker) lines.push(`├── ${marker}`);
1229
+ if (marker) {
1230
+ lines.push(`├── ${marker}`);
1231
+ }
1226
1232
  }
1227
1233
  // legacy 兜底
1228
1234
  if (bs.length === 0) {
1229
- if (project.hasPackageSwift) lines.push('├── Package.swift');
1230
- if (project.hasPodfile) lines.push('├── Podfile');
1235
+ if (project.hasPackageSwift) {
1236
+ lines.push('├── Package.swift');
1237
+ }
1238
+ if (project.hasPodfile) {
1239
+ lines.push('├── Podfile');
1240
+ }
1231
1241
  }
1232
1242
  lines.push('```');
1233
1243
  lines.push('');
@@ -1560,8 +1570,7 @@ export function renderFolderOverview(profiles, projectInfo, isZh) {
1560
1570
  lines.push(` Root["${projectInfo.name}"]`);
1561
1571
 
1562
1572
  // 只显示深度 = 1 的顶层文件夹
1563
- const topLevel = profiles.filter(fp => fp.depth === 1);
1564
- const deeper = profiles.filter(fp => fp.depth > 1);
1573
+ const topLevel = profiles.filter((fp) => fp.depth === 1);
1565
1574
 
1566
1575
  for (const fp of topLevel) {
1567
1576
  const sid = mermaidId(fp.name);
@@ -1570,7 +1579,7 @@ export function renderFolderOverview(profiles, projectInfo, isZh) {
1570
1579
  }
1571
1580
 
1572
1581
  // 画 import 关系边
1573
- const folderNames = new Set(profiles.map(fp => fp.name));
1582
+ const folderNames = new Set(profiles.map((fp) => fp.name));
1574
1583
  for (const fp of profiles) {
1575
1584
  const fromId = mermaidId(fp.name);
1576
1585
  for (const imp of fp.imports) {
@@ -1595,18 +1604,19 @@ export function renderFolderOverview(profiles, projectInfo, isZh) {
1595
1604
  const hasDoc = fp.fileCount >= 5;
1596
1605
  const folderDocSlug = slug(fp.relPath.replaceAll('/', '-'));
1597
1606
  const nameCol = hasDoc ? `[${fp.relPath}](folders/${folderDocSlug}.md)` : fp.relPath;
1598
- const sizeStr = fp.totalSize > 1024 * 1024
1599
- ? `${(fp.totalSize / 1024 / 1024).toFixed(1)}MB`
1600
- : `${(fp.totalSize / 1024).toFixed(1)}KB`;
1607
+ const sizeStr =
1608
+ fp.totalSize > 1024 * 1024
1609
+ ? `${(fp.totalSize / 1024 / 1024).toFixed(1)}MB`
1610
+ : `${(fp.totalSize / 1024).toFixed(1)}KB`;
1601
1611
  const langs = Object.entries(fp.langBreakdown)
1602
1612
  .sort((a, b) => b[1] - a[1])
1603
1613
  .slice(0, 3)
1604
1614
  .map(([l]) => l)
1605
1615
  .join(', ');
1606
- const desc = fp.purpose
1607
- ? (isZh ? fp.purpose.zh : fp.purpose.en)
1608
- : '-';
1609
- lines.push(`| ${nameCol} | \`${fp.relPath}\` | ${fp.fileCount} | ${sizeStr} | ${langs} | ${desc} |`);
1616
+ const desc = fp.purpose ? (isZh ? fp.purpose.zh : fp.purpose.en) : '-';
1617
+ lines.push(
1618
+ `| ${nameCol} | \`${fp.relPath}\` | ${fp.fileCount} | ${sizeStr} | ${langs} | ${desc} |`
1619
+ );
1610
1620
  }
1611
1621
  lines.push('');
1612
1622
 
@@ -1631,7 +1641,9 @@ export function renderFolderOverview(profiles, projectInfo, isZh) {
1631
1641
  );
1632
1642
  lines.push('');
1633
1643
  for (const [pattern, count] of commonPatterns) {
1634
- lines.push(`- **${pattern}** — ${isZh ? `出现在 ${count} 个文件夹` : `found in ${count} folders`}`);
1644
+ lines.push(
1645
+ `- **${pattern}** — ${isZh ? `出现在 ${count} 个文件夹` : `found in ${count} folders`}`
1646
+ );
1635
1647
  }
1636
1648
  lines.push('');
1637
1649
  }
@@ -1687,18 +1699,22 @@ export function renderFolderProfile(fp, projectInfo, isZh) {
1687
1699
  lines.push('');
1688
1700
 
1689
1701
  const purposeStr = fp.purpose
1690
- ? (isZh ? fp.purpose.zh : fp.purpose.en)
1691
- : (isZh ? '通过文件夹画像分析推断其功能' : 'functionality inferred from folder profiling');
1702
+ ? isZh
1703
+ ? fp.purpose.zh
1704
+ : fp.purpose.en
1705
+ : isZh
1706
+ ? '通过文件夹画像分析推断其功能'
1707
+ : 'functionality inferred from folder profiling';
1692
1708
 
1693
1709
  if (isZh) {
1694
1710
  lines.push(
1695
1711
  `**${fp.name}** 位于 \`${fp.relPath}\`,${purposeStr}。` +
1696
- `包含 ${fp.fileCount} 个源文件,总大小 ${(fp.totalSize / 1024).toFixed(1)}KB。`
1712
+ `包含 ${fp.fileCount} 个源文件,总大小 ${(fp.totalSize / 1024).toFixed(1)}KB。`
1697
1713
  );
1698
1714
  } else {
1699
1715
  lines.push(
1700
1716
  `**${fp.name}** is located at \`${fp.relPath}\`, ${purposeStr}. ` +
1701
- `Contains ${fp.fileCount} source files totaling ${(fp.totalSize / 1024).toFixed(1)}KB.`
1717
+ `Contains ${fp.fileCount} source files totaling ${(fp.totalSize / 1024).toFixed(1)}KB.`
1702
1718
  );
1703
1719
  }
1704
1720
  lines.push('');
@@ -1708,9 +1724,10 @@ export function renderFolderProfile(fp, projectInfo, isZh) {
1708
1724
  lines.push('|--------|------|');
1709
1725
  lines.push(`| ${isZh ? '路径' : 'Path'} | \`${fp.relPath}\` |`);
1710
1726
  lines.push(`| ${isZh ? '源文件数' : 'Source Files'} | ${fp.fileCount} |`);
1711
- const sizeStr = fp.totalSize > 1024 * 1024
1712
- ? `${(fp.totalSize / 1024 / 1024).toFixed(1)}MB`
1713
- : `${(fp.totalSize / 1024).toFixed(1)}KB`;
1727
+ const sizeStr =
1728
+ fp.totalSize > 1024 * 1024
1729
+ ? `${(fp.totalSize / 1024 / 1024).toFixed(1)}MB`
1730
+ : `${(fp.totalSize / 1024).toFixed(1)}KB`;
1714
1731
  lines.push(`| ${isZh ? '总大小' : 'Total Size'} | ${sizeStr} |`);
1715
1732
  if (fp.entryPoints.length > 0) {
1716
1733
  lines.push(`| ${isZh ? '入口文件' : 'Entry Points'} | ${fp.entryPoints.join(', ')} |`);
@@ -1721,14 +1738,22 @@ export function renderFolderProfile(fp, projectInfo, isZh) {
1721
1738
  if (fp.readme) {
1722
1739
  lines.push(`## ${isZh ? '目录说明' : 'Directory README'}`);
1723
1740
  lines.push('');
1724
- lines.push('> ' + fp.readme.split('\n').filter(l => l.trim()).slice(0, 5).join('\n> '));
1741
+ lines.push(
1742
+ `> ${fp.readme
1743
+ .split('\n')
1744
+ .filter((l) => l.trim())
1745
+ .slice(0, 5)
1746
+ .join('\n> ')}`
1747
+ );
1725
1748
  lines.push('');
1726
1749
  }
1727
1750
 
1728
1751
  // ── 语言分布 ──
1729
1752
  lines.push(`## ${isZh ? '语言分布' : 'Language Distribution'}`);
1730
1753
  lines.push('');
1731
- lines.push(`| ${isZh ? '语言' : 'Language'} | ${isZh ? '文件数' : 'Files'} | ${isZh ? '占比' : 'Share'} |`);
1754
+ lines.push(
1755
+ `| ${isZh ? '语言' : 'Language'} | ${isZh ? '文件数' : 'Files'} | ${isZh ? '占比' : 'Share'} |`
1756
+ );
1732
1757
  lines.push('|--------|-------|------|');
1733
1758
  const total = Object.values(fp.langBreakdown).reduce((a, b) => a + b, 0);
1734
1759
  for (const [lang, count] of Object.entries(fp.langBreakdown).sort((a, b) => b[1] - a[1])) {
@@ -1817,9 +1842,7 @@ export function renderFolderProfile(fp, projectInfo, isZh) {
1817
1842
  lines.push(`## ${isZh ? '代码注释摘要' : 'Code Comments Summary'}`);
1818
1843
  lines.push('');
1819
1844
  lines.push(
1820
- isZh
1821
- ? '从关键文件头部提取的注释信息:'
1822
- : 'Comments extracted from key file headers:'
1845
+ isZh ? '从关键文件头部提取的注释信息:' : 'Comments extracted from key file headers:'
1823
1846
  );
1824
1847
  lines.push('');
1825
1848
  for (const hc of fp.headerComments) {
@@ -1828,7 +1851,9 @@ export function renderFolderProfile(fp, projectInfo, isZh) {
1828
1851
  lines.push('');
1829
1852
  }
1830
1853
 
1831
- lines.push(`[← ${isZh ? '返回结构分析' : 'Back to Structure Analysis'}](../folder-structure.md) | [← ${isZh ? '返回概述' : 'Back to Overview'}](../index.md)`);
1854
+ lines.push(
1855
+ `[← ${isZh ? '返回结构分析' : 'Back to Structure Analysis'}](../folder-structure.md) | [← ${isZh ? '返回概述' : 'Back to Overview'}](../index.md)`
1856
+ );
1832
1857
  lines.push('');
1833
1858
  return lines.join('\n');
1834
1859
  }