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.
- package/bin/api-server.js +2 -0
- package/bin/cli.js +24 -19
- package/config/default.json +1 -1
- package/lib/bootstrap.js +4 -4
- package/lib/cli/SetupService.js +29 -29
- package/lib/cli/UpgradeService.js +3 -2
- package/lib/core/AstAnalyzer.js +1 -1
- package/lib/core/ast/ensure-grammars.js +1 -1
- package/lib/core/ast/index.js +62 -11
- package/lib/core/ast/lang-dart.js +27 -21
- package/lib/core/ast/lang-go.js +6 -20
- package/lib/core/ast/lang-rust.js +53 -28
- package/lib/core/ast/parser-init.js +9 -5
- package/lib/core/discovery/DartDiscoverer.js +4 -10
- package/lib/core/discovery/GoDiscoverer.js +45 -25
- package/lib/core/discovery/NodeDiscoverer.js +1 -3
- package/lib/core/discovery/PythonDiscoverer.js +7 -1
- package/lib/core/discovery/RustDiscoverer.js +111 -38
- package/lib/core/discovery/index.js +2 -2
- package/lib/core/enhancement/django-enhancement.js +10 -4
- package/lib/core/enhancement/fastapi-enhancement.js +16 -9
- package/lib/core/enhancement/go-grpc-enhancement.js +2 -1
- package/lib/core/enhancement/go-web-enhancement.js +3 -6
- package/lib/core/enhancement/ml-enhancement.js +6 -3
- package/lib/core/enhancement/nextjs-enhancement.js +17 -7
- package/lib/core/enhancement/node-server-enhancement.js +4 -2
- package/lib/core/enhancement/react-enhancement.js +6 -3
- package/lib/core/enhancement/rust-tokio-enhancement.js +6 -2
- package/lib/core/enhancement/rust-web-enhancement.js +13 -7
- package/lib/core/enhancement/vue-enhancement.js +10 -5
- package/lib/external/ai/AiFactory.js +3 -1
- package/lib/external/ai/AiProvider.js +3 -1
- package/lib/external/mcp/McpServer.js +2 -0
- package/lib/external/mcp/handlers/bootstrap/base-dimensions.js +1 -2
- package/lib/external/mcp/handlers/bootstrap/pipeline/checkpoint.js +7 -1
- package/lib/external/mcp/handlers/bootstrap/pipeline/noAiFallback.js +55 -26
- package/lib/external/mcp/handlers/bootstrap/pipeline/orchestrator.js +8 -8
- package/lib/external/mcp/handlers/bootstrap/refine.js +3 -1
- package/lib/external/mcp/handlers/bootstrap.js +4 -10
- package/lib/external/mcp/handlers/browse.js +6 -2
- package/lib/external/mcp/handlers/guard.js +6 -2
- package/lib/external/mcp/handlers/skill.js +6 -2
- package/lib/http/HttpServer.js +1 -1
- package/lib/http/routes/candidates.js +3 -1
- package/lib/http/routes/extract.js +4 -5
- package/lib/http/routes/guardRules.js +1 -1
- package/lib/http/routes/modules.js +9 -3
- package/lib/http/routes/skills.js +54 -6
- package/lib/http/routes/violations.js +4 -3
- package/lib/infrastructure/external/ClipboardManager.js +24 -7
- package/lib/infrastructure/external/NativeUi.js +3 -1
- package/lib/infrastructure/external/OpenBrowser.js +1 -0
- package/lib/infrastructure/external/XcodeAutomation.js +5 -5
- package/lib/infrastructure/vector/IndexingPipeline.js +14 -5
- package/lib/injection/ServiceContainer.js +34 -11
- package/lib/platform/ios/index.js +20 -25
- package/lib/platform/ios/routes/spm.js +6 -3
- package/lib/platform/ios/snippet/PlaceholderConverter.js +6 -2
- package/lib/platform/ios/snippet/XcodeCodec.js +4 -2
- package/lib/platform/ios/spm/SpmDiscoverer.js +1 -1
- package/lib/platform/ios/spm/SpmService.js +3 -1
- package/lib/platform/ios/xcode/XcodeIntegration.js +10 -12
- package/lib/platform/ios/xcode/XcodeWriteUtils.js +6 -1
- package/lib/service/automation/FileWatcher.js +1 -3
- package/lib/service/automation/handlers/CreateHandler.js +3 -5
- package/lib/service/automation/handlers/GuardHandler.js +11 -32
- package/lib/service/automation/handlers/SearchHandler.js +9 -9
- package/lib/service/chat/CandidateGuardrail.js +11 -6
- package/lib/service/chat/ChatAgent.js +31 -22
- package/lib/service/chat/HandoffProtocol.js +5 -2
- package/lib/service/chat/tools/composite.js +3 -2
- package/lib/service/chat/tools/index.js +60 -71
- package/lib/service/chat/tools/infrastructure.js +9 -4
- package/lib/service/chat/tools/lifecycle.js +22 -5
- package/lib/service/chat/tools/project-access.js +5 -9
- package/lib/service/chat/tools.js +1 -2
- package/lib/service/cursor/AgentInstructionsGenerator.js +33 -15
- package/lib/service/cursor/CursorDeliveryPipeline.js +2 -1
- package/lib/service/cursor/KnowledgeCompressor.js +16 -7
- package/lib/service/guard/ComplianceReporter.js +5 -2
- package/lib/service/guard/GuardCheckEngine.js +53 -26
- package/lib/service/guard/GuardCodeChecks.js +217 -188
- package/lib/service/guard/GuardCrossFileChecks.js +203 -184
- package/lib/service/guard/GuardPatternUtils.js +17 -10
- package/lib/service/module/ModuleService.js +180 -56
- package/lib/service/recipe/RecipeCandidateValidator.js +11 -8
- package/lib/service/snippet/SnippetFactory.js +3 -3
- package/lib/service/snippet/SnippetInstaller.js +35 -11
- package/lib/service/snippet/codecs/VSCodeCodec.js +2 -2
- package/lib/service/wiki/WikiGenerator.js +67 -40
- package/lib/service/wiki/WikiRenderers.js +105 -80
- package/lib/service/wiki/WikiUtils.js +217 -80
- package/lib/shared/LanguageService.js +111 -53
- package/lib/shared/PathGuard.js +0 -8
- package/package.json +3 -9
- package/scripts/bench-real-projects.mjs +29 -29
- package/scripts/generate-recipe-drafts.js +17 -27
- package/scripts/init-snippets.js +43 -24
- package/scripts/install-vscode-copilot.js +3 -19
- 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: [],
|
|
258
|
+
buildSystems: [], // [{eco, buildTool}]
|
|
264
259
|
sourceFiles: [],
|
|
265
260
|
languages: {},
|
|
266
|
-
langProfile: null,
|
|
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(
|
|
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 =
|
|
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 ||
|
|
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)
|
|
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)
|
|
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. 文件夹画像文档
|
|
638
|
-
//
|
|
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
|
|
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)
|
|
680
|
-
|
|
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 =
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
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
|
|
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 (
|
|
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(
|
|
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
|
-
|
|
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)
|
|
259
|
-
|
|
260
|
-
|
|
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(
|
|
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(
|
|
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)
|
|
566
|
-
|
|
567
|
-
|
|
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 =
|
|
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(
|
|
636
|
-
|
|
637
|
-
|
|
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(
|
|
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)
|
|
1170
|
-
|
|
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)
|
|
1229
|
+
if (marker) {
|
|
1230
|
+
lines.push(`├── ${marker}`);
|
|
1231
|
+
}
|
|
1226
1232
|
}
|
|
1227
1233
|
// legacy 兜底
|
|
1228
1234
|
if (bs.length === 0) {
|
|
1229
|
-
if (project.hasPackageSwift)
|
|
1230
|
-
|
|
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 =
|
|
1599
|
-
|
|
1600
|
-
|
|
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
|
-
|
|
1608
|
-
|
|
1609
|
-
|
|
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(
|
|
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
|
-
?
|
|
1691
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 =
|
|
1712
|
-
|
|
1713
|
-
|
|
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(
|
|
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(
|
|
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(
|
|
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
|
}
|