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.
- package/bin/cli.js +64 -1
- package/config/default.json +9 -0
- package/dashboard/dist/assets/{index-I2ySoCmF.js → index-Bnm26ulL.js} +47 -47
- package/dashboard/dist/index.html +1 -1
- package/lib/cli/SetupService.js +92 -5
- package/lib/cli/UpgradeService.js +14 -5
- package/lib/core/discovery/GenericDiscoverer.js +4 -28
- package/lib/external/mcp/handlers/bootstrap/base-dimensions.js +246 -0
- package/lib/external/mcp/handlers/bootstrap/pipeline/checkpoint.js +80 -0
- package/lib/external/mcp/handlers/bootstrap/pipeline/dimension-configs.js +275 -0
- package/lib/external/mcp/handlers/bootstrap/pipeline/noAiFallback.js +600 -0
- package/lib/external/mcp/handlers/bootstrap/pipeline/orchestrator.js +125 -342
- package/lib/external/mcp/handlers/bootstrap/refine.js +362 -0
- package/lib/external/mcp/handlers/bootstrap.js +6 -590
- package/lib/external/mcp/handlers/browse.js +119 -9
- package/lib/external/mcp/handlers/guard.js +25 -6
- package/lib/external/mcp/handlers/search.js +56 -24
- package/lib/http/routes/guardRules.js +9 -17
- package/lib/injection/ServiceContainer.js +12 -3
- package/lib/platform/ios/xcode/XcodeImportResolver.js +434 -0
- package/lib/platform/ios/xcode/XcodeIntegration.js +40 -659
- package/lib/platform/ios/xcode/XcodeWriteUtils.js +220 -0
- package/lib/service/chat/ChatAgent.js +39 -418
- package/lib/service/chat/ChatAgentPrompts.js +149 -0
- package/lib/service/chat/ChatAgentTasks.js +297 -0
- package/lib/service/chat/tools/_shared.js +61 -0
- package/lib/service/chat/tools/ai-analysis.js +284 -0
- package/lib/service/chat/tools/ast-graph.js +681 -0
- package/lib/service/chat/tools/composite.js +496 -0
- package/lib/service/chat/tools/guard.js +265 -0
- package/lib/service/chat/tools/index.js +250 -0
- package/lib/service/chat/tools/infrastructure.js +222 -0
- package/lib/service/chat/tools/knowledge-graph.js +234 -0
- package/lib/service/chat/tools/lifecycle.js +469 -0
- package/lib/service/chat/tools/project-access.js +923 -0
- package/lib/service/chat/tools/query.js +264 -0
- package/lib/service/chat/tools.js +14 -3994
- package/lib/service/cursor/AgentInstructionsGenerator.js +395 -0
- package/lib/service/cursor/CursorDeliveryPipeline.js +70 -11
- package/lib/service/cursor/FileProtection.js +116 -0
- package/lib/service/cursor/KnowledgeCompressor.js +61 -11
- package/lib/service/cursor/SkillsSyncer.js +5 -3
- package/lib/service/cursor/TopicClassifier.js +19 -3
- package/lib/service/guard/ExclusionManager.js +26 -2
- package/lib/service/guard/GuardCheckEngine.js +38 -370
- package/lib/service/guard/GuardCodeChecks.js +362 -0
- package/lib/service/guard/GuardCrossFileChecks.js +307 -0
- package/lib/service/guard/GuardPatternUtils.js +180 -0
- package/lib/service/guard/GuardService.js +80 -38
- package/lib/service/module/ModuleService.js +1 -0
- package/lib/service/search/SearchEngine.js +10 -2
- package/lib/service/wiki/WikiGenerator.js +226 -1532
- package/lib/service/wiki/WikiRenderers.js +1878 -0
- package/lib/service/wiki/WikiUtils.js +907 -0
- package/lib/shared/LanguageService.js +299 -0
- 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
|
|
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,
|
|
144
|
-
const topics = this._discoverTopics(projectInfo, astInfo,
|
|
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.
|
|
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
|
-
|
|
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
|
-
//
|
|
296
|
-
mod =
|
|
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
|
-
|
|
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 =
|
|
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 =
|
|
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
|
|
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,
|
|
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
|
|
452
|
-
const
|
|
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
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
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
|
-
|
|
491
|
-
|
|
492
|
-
|
|
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-${
|
|
536
|
-
path: `patterns/${
|
|
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 ?
|
|
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,
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 !==
|
|
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
|
-
|
|
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;
|