autosnippet 3.0.0 → 3.0.2
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/README.md +230 -324
- package/bin/api-server.js +1 -1
- package/bin/cli.js +204 -244
- package/bin/mcp-server.js +5 -3
- package/config/knowledge-base.config.js +132 -132
- package/dashboard/dist/assets/{icons-CEfgGaZi.js → icons-Cdq22n2i.js} +95 -100
- package/dashboard/dist/assets/index-ClkyPkDX.js +133 -0
- package/dashboard/dist/assets/index-t4QrJwv1.css +1 -0
- package/dashboard/dist/index.html +3 -3
- package/lib/bootstrap.js +8 -8
- package/lib/cli/AiScanService.js +86 -40
- package/lib/cli/KnowledgeSyncService.js +113 -74
- package/lib/cli/SetupService.js +439 -277
- package/lib/cli/UpgradeService.js +63 -100
- package/lib/core/AstAnalyzer.js +276 -597
- package/lib/core/ast/ProjectGraph.js +101 -40
- package/lib/core/ast/ensure-grammars.js +232 -0
- package/lib/core/ast/index.js +115 -0
- package/lib/core/ast/lang-dart.js +661 -0
- package/lib/core/ast/lang-go.js +530 -0
- package/lib/core/ast/lang-java.js +435 -0
- package/lib/core/ast/lang-javascript.js +272 -0
- package/lib/core/ast/lang-kotlin.js +423 -0
- package/lib/core/ast/lang-objc.js +388 -0
- package/lib/core/ast/lang-python.js +371 -0
- package/lib/core/ast/lang-swift.js +337 -0
- package/lib/core/ast/lang-typescript.js +503 -0
- package/lib/core/capability/CapabilityProbe.js +18 -9
- package/lib/core/constitution/Constitution.js +2 -3
- package/lib/core/constitution/ConstitutionValidator.js +65 -24
- package/lib/core/discovery/DartDiscoverer.js +534 -0
- package/lib/core/discovery/DiscovererRegistry.js +83 -0
- package/lib/core/discovery/GenericDiscoverer.js +225 -0
- package/lib/core/discovery/GoDiscoverer.js +541 -0
- package/lib/core/discovery/JvmDiscoverer.js +506 -0
- package/lib/core/discovery/NodeDiscoverer.js +466 -0
- package/lib/core/discovery/ProjectDiscoverer.js +93 -0
- package/lib/core/discovery/PythonDiscoverer.js +338 -0
- package/lib/core/discovery/SpmDiscoverer.js +5 -0
- package/lib/core/discovery/index.js +53 -0
- package/lib/core/enhancement/EnhancementPack.js +71 -0
- package/lib/core/enhancement/EnhancementRegistry.js +47 -0
- package/lib/core/enhancement/android-enhancement.js +102 -0
- package/lib/core/enhancement/django-enhancement.js +70 -0
- package/lib/core/enhancement/fastapi-enhancement.js +63 -0
- package/lib/core/enhancement/go-grpc-enhancement.js +152 -0
- package/lib/core/enhancement/go-web-enhancement.js +201 -0
- package/lib/core/enhancement/index.js +65 -0
- package/lib/core/enhancement/node-server-enhancement.js +88 -0
- package/lib/core/enhancement/react-enhancement.js +86 -0
- package/lib/core/enhancement/spring-enhancement.js +112 -0
- package/lib/core/enhancement/vue-enhancement.js +96 -0
- package/lib/core/gateway/Gateway.js +8 -9
- package/lib/core/gateway/GatewayActionRegistry.js +1 -1
- package/lib/core/permission/PermissionManager.js +12 -8
- package/lib/domain/index.js +13 -9
- package/lib/domain/knowledge/KnowledgeEntry.js +111 -101
- package/lib/domain/knowledge/KnowledgeRepository.js +0 -1
- package/lib/domain/knowledge/Lifecycle.js +22 -22
- package/lib/domain/knowledge/index.js +9 -12
- package/lib/domain/knowledge/values/Constraints.js +31 -21
- package/lib/domain/knowledge/values/Content.js +21 -13
- package/lib/domain/knowledge/values/Quality.js +31 -18
- package/lib/domain/knowledge/values/Reasoning.js +20 -12
- package/lib/domain/knowledge/values/Relations.js +37 -25
- package/lib/domain/knowledge/values/Stats.js +18 -12
- package/lib/domain/knowledge/values/index.js +4 -3
- package/lib/domain/snippet/Snippet.js +35 -10
- package/lib/external/ai/AiFactory.js +48 -16
- package/lib/external/ai/AiProvider.js +184 -90
- package/lib/external/ai/providers/ClaudeProvider.js +25 -12
- package/lib/external/ai/providers/GoogleGeminiProvider.js +59 -30
- package/lib/external/ai/providers/MockProvider.js +9 -3
- package/lib/external/ai/providers/OpenAiProvider.js +51 -29
- package/lib/external/mcp/McpServer.js +66 -36
- package/lib/external/mcp/errorHandler.js +23 -11
- package/lib/external/mcp/handlers/LanguageExtensions.js +138 -53
- package/lib/external/mcp/handlers/TargetClassifier.js +52 -16
- package/lib/external/mcp/handlers/bootstrap/pipeline/BootstrapSnapshot.js +81 -20
- package/lib/external/mcp/handlers/bootstrap/pipeline/EpisodicMemory.js +71 -42
- package/lib/external/mcp/handlers/bootstrap/pipeline/IncrementalBootstrap.js +9 -17
- package/lib/external/mcp/handlers/bootstrap/pipeline/ToolResultCache.js +14 -9
- package/lib/external/mcp/handlers/bootstrap/pipeline/dimension-context.js +15 -7
- package/lib/external/mcp/handlers/bootstrap/pipeline/orchestrator.js +352 -153
- package/lib/external/mcp/handlers/bootstrap/pipeline/tier-scheduler.js +52 -12
- package/lib/external/mcp/handlers/bootstrap/skills.js +143 -39
- package/lib/external/mcp/handlers/bootstrap.js +691 -168
- package/lib/external/mcp/handlers/browse.js +66 -22
- package/lib/external/mcp/handlers/candidate.js +118 -35
- package/lib/external/mcp/handlers/consolidated.js +49 -17
- package/lib/external/mcp/handlers/guard.js +104 -39
- package/lib/external/mcp/handlers/knowledge.js +60 -36
- package/lib/external/mcp/handlers/search.js +43 -14
- package/lib/external/mcp/handlers/skill.js +120 -45
- package/lib/external/mcp/handlers/structure.js +240 -86
- package/lib/external/mcp/handlers/system.js +42 -12
- package/lib/external/mcp/handlers/wiki.js +58 -33
- package/lib/external/mcp/tools.js +306 -123
- package/lib/http/HttpServer.js +72 -47
- package/lib/http/middleware/RateLimiter.js +5 -3
- package/lib/http/middleware/errorHandler.js +6 -1
- package/lib/http/middleware/requestLogger.js +14 -3
- package/lib/http/middleware/roleResolver.js +30 -23
- package/lib/http/routes/ai.js +387 -265
- package/lib/http/routes/auth.js +81 -61
- package/lib/http/routes/candidates.js +430 -320
- package/lib/http/routes/commands.js +289 -189
- package/lib/http/routes/extract.js +158 -125
- package/lib/http/routes/guardRules.js +309 -217
- package/lib/http/routes/knowledge.js +213 -154
- package/lib/http/routes/modules.js +578 -0
- package/lib/http/routes/monitoring.js +6 -6
- package/lib/http/routes/recipes.js +104 -93
- package/lib/http/routes/search.js +361 -305
- package/lib/http/routes/skills.js +145 -98
- package/lib/http/routes/snippets.js +42 -30
- package/lib/http/routes/spm.js +3 -405
- package/lib/http/routes/violations.js +113 -93
- package/lib/http/routes/wiki.js +211 -170
- package/lib/http/utils/routeHelpers.js +3 -1
- package/lib/http/utils/sse-sessions.js +16 -6
- package/lib/http/utils/sse.js +15 -5
- package/lib/infrastructure/audit/AuditLogger.js +5 -2
- package/lib/infrastructure/audit/AuditStore.js +10 -7
- package/lib/infrastructure/cache/CacheService.js +3 -1
- package/lib/infrastructure/cache/GraphCache.js +8 -4
- package/lib/infrastructure/cache/UnifiedCacheAdapter.js +1 -1
- package/lib/infrastructure/config/ConfigLoader.js +9 -5
- package/lib/infrastructure/config/Defaults.js +30 -10
- package/lib/infrastructure/config/Paths.js +28 -8
- package/lib/infrastructure/config/TriggerSymbol.js +22 -10
- package/lib/infrastructure/database/DatabaseConnection.js +15 -10
- package/lib/infrastructure/database/migrations/001_initial_schema.js +0 -1
- package/lib/infrastructure/external/ClipboardManager.js +6 -2
- package/lib/infrastructure/external/NativeUi.js +50 -43
- package/lib/infrastructure/external/OpenBrowser.js +14 -17
- package/lib/infrastructure/external/XcodeAutomation.js +14 -258
- package/lib/infrastructure/logging/Logger.js +46 -30
- package/lib/infrastructure/monitoring/ErrorTracker.js +7 -5
- package/lib/infrastructure/monitoring/PerformanceMonitor.js +12 -4
- package/lib/infrastructure/paths/HeaderResolver.js +25 -9
- package/lib/infrastructure/paths/PathFinder.js +34 -12
- package/lib/infrastructure/plugin/PluginManager.js +26 -8
- package/lib/infrastructure/realtime/RealtimeService.js +2 -2
- package/lib/infrastructure/vector/Chunker.js +22 -7
- package/lib/infrastructure/vector/IndexingPipeline.js +46 -22
- package/lib/infrastructure/vector/JsonVectorAdapter.js +90 -53
- package/lib/infrastructure/vector/VectorStore.js +28 -10
- package/lib/injection/ServiceContainer.js +247 -93
- package/lib/platform/ios/index.js +63 -0
- package/lib/platform/ios/routes/spm.js +437 -0
- package/lib/platform/ios/snippet/PlaceholderConverter.js +55 -0
- package/lib/platform/ios/snippet/XcodeCodec.js +112 -0
- package/lib/{service → platform/ios}/spm/DependencyGraph.js +41 -17
- package/lib/{service → platform/ios}/spm/PackageSwiftParser.js +41 -14
- package/lib/{service → platform/ios}/spm/PolicyEngine.js +9 -4
- package/lib/platform/ios/spm/SpmDiscoverer.js +122 -0
- package/lib/{service → platform/ios}/spm/SpmService.js +385 -127
- package/lib/{service/automation → platform/ios/xcode}/SaveEventFilter.js +8 -7
- package/lib/platform/ios/xcode/XcodeAutomation.js +350 -0
- package/lib/{service/automation → platform/ios/xcode}/XcodeIntegration.js +325 -145
- package/lib/repository/base/BaseRepository.js +7 -9
- package/lib/repository/knowledge/KnowledgeRepository.impl.js +98 -75
- package/lib/repository/token/TokenUsageStore.js +4 -2
- package/lib/service/automation/ActionPipeline.js +1 -1
- package/lib/service/automation/AutomationOrchestrator.js +8 -4
- package/lib/service/automation/ContextCollector.js +7 -5
- package/lib/service/automation/DirectiveDetector.js +23 -16
- package/lib/service/automation/FileWatcher.js +112 -56
- package/lib/service/automation/TriggerResolver.js +6 -4
- package/lib/service/automation/handlers/AlinkHandler.js +24 -12
- package/lib/service/automation/handlers/CreateHandler.js +19 -20
- package/lib/service/automation/handlers/DraftHandler.js +14 -8
- package/lib/service/automation/handlers/GuardHandler.js +93 -63
- package/lib/service/automation/handlers/HeaderHandler.js +1 -6
- package/lib/service/automation/handlers/SearchHandler.js +155 -88
- package/lib/service/bootstrap/BootstrapTaskManager.js +77 -35
- package/lib/service/candidate/SimilarityService.js +25 -9
- package/lib/service/chat/AnalystAgent.js +50 -24
- package/lib/service/chat/CandidateGuardrail.js +143 -17
- package/lib/service/chat/ChatAgent.js +759 -243
- package/lib/service/chat/ContextWindow.js +116 -71
- package/lib/service/chat/ConversationStore.js +77 -36
- package/lib/service/chat/EpisodicConsolidator.js +47 -23
- package/lib/service/chat/HandoffProtocol.js +98 -22
- package/lib/service/chat/Memory.js +34 -14
- package/lib/service/chat/ProducerAgent.js +40 -20
- package/lib/service/chat/ProjectSemanticMemory.js +109 -78
- package/lib/service/chat/ReasoningLayer.js +148 -70
- package/lib/service/chat/ReasoningTrace.js +44 -32
- package/lib/service/chat/TaskPipeline.js +39 -19
- package/lib/service/chat/ToolRegistry.js +48 -29
- package/lib/service/chat/WorkingMemory.js +44 -18
- package/lib/service/chat/tools.js +1096 -494
- package/lib/service/context/RecipeExtractor.js +132 -51
- package/lib/service/cursor/CursorDeliveryPipeline.js +82 -37
- package/lib/service/cursor/KnowledgeCompressor.js +25 -22
- package/lib/service/cursor/RulesGenerator.js +13 -7
- package/lib/service/cursor/SkillsSyncer.js +77 -27
- package/lib/service/cursor/TokenBudget.js +2 -2
- package/lib/service/cursor/TopicClassifier.js +54 -20
- package/lib/service/guard/ComplianceReporter.js +55 -43
- package/lib/service/guard/ExclusionManager.js +67 -29
- package/lib/service/guard/GuardCheckEngine.js +381 -86
- package/lib/service/guard/GuardFeedbackLoop.js +22 -10
- package/lib/service/guard/GuardService.js +29 -19
- package/lib/service/guard/RuleLearner.js +55 -23
- package/lib/service/guard/SourceFileCollector.js +27 -20
- package/lib/service/guard/ViolationsStore.js +43 -38
- package/lib/service/knowledge/CodeEntityGraph.js +147 -82
- package/lib/service/knowledge/ConfidenceRouter.js +12 -10
- package/lib/service/knowledge/KnowledgeFileWriter.js +147 -56
- package/lib/service/knowledge/KnowledgeGraphService.js +81 -34
- package/lib/service/knowledge/KnowledgeService.js +222 -112
- package/lib/service/module/ModuleService.js +969 -0
- package/lib/service/quality/FeedbackCollector.js +27 -15
- package/lib/service/quality/QualityScorer.js +78 -24
- package/lib/service/recipe/RecipeCandidateValidator.js +110 -44
- package/lib/service/recipe/RecipeParser.js +78 -45
- package/lib/service/search/CoarseRanker.js +43 -28
- package/lib/service/search/CrossEncoderReranker.js +32 -21
- package/lib/service/search/InvertedIndex.js +21 -7
- package/lib/service/search/MultiSignalRanker.js +90 -28
- package/lib/service/search/RetrievalFunnel.js +45 -24
- package/lib/service/search/SearchEngine.js +255 -103
- package/lib/service/skills/EventAggregator.js +32 -15
- package/lib/service/skills/SignalCollector.js +140 -64
- package/lib/service/skills/SkillAdvisor.js +79 -42
- package/lib/service/skills/SkillHooks.js +16 -14
- package/lib/service/snippet/PlaceholderConverter.js +5 -0
- package/lib/service/snippet/SnippetFactory.js +116 -99
- package/lib/service/snippet/SnippetInstaller.js +234 -62
- package/lib/service/snippet/codecs/SnippetCodec.js +67 -0
- package/lib/service/snippet/codecs/VSCodeCodec.js +102 -0
- package/lib/service/snippet/codecs/XcodeCodec.js +5 -0
- package/lib/service/wiki/WikiGenerator.js +637 -263
- package/lib/shared/DimensionCopyRegistry.js +472 -0
- package/lib/shared/LanguageService.js +399 -0
- package/lib/shared/PathGuard.js +45 -28
- package/lib/shared/RecipeReadinessChecker.js +72 -12
- package/lib/shared/constants.js +41 -41
- package/lib/shared/errors/BaseError.js +2 -2
- package/lib/shared/errors/index.js +4 -4
- package/lib/shared/similarity.js +25 -8
- package/lib/shared/token-utils.js +6 -2
- package/lib/shared/utils/common.js +12 -4
- package/package.json +49 -13
- package/scripts/bench-real-projects.mjs +256 -0
- package/scripts/build-native-ui.js +30 -30
- package/scripts/clear-old-vector-index.js +5 -35
- package/scripts/clear-vector-cache.js +7 -37
- package/scripts/collect-test-project-stats.mjs +160 -0
- package/scripts/diagnose-mcp.js +41 -32
- package/scripts/ensure-parse-package.js +6 -9
- package/scripts/generate-recipe-drafts.js +116 -77
- package/scripts/init-db.js +3 -20
- package/scripts/init-snippets.js +305 -0
- package/scripts/init-vector-db.js +173 -170
- package/scripts/install-cursor-skill.js +148 -104
- package/scripts/install-full.js +8 -21
- package/scripts/install-vscode-copilot.js +146 -145
- package/scripts/migrate-md-to-knowledge.mjs +139 -151
- package/scripts/postinstall-safe.js +5 -17
- package/scripts/recipe-audit.js +106 -82
- package/scripts/release.js +283 -323
- package/scripts/setup-mcp-config.js +60 -52
- package/scripts/verify-context-api.js +20 -20
- package/skills/autosnippet-analysis/SKILL.md +10 -6
- package/skills/autosnippet-candidates/SKILL.md +27 -26
- package/skills/autosnippet-coldstart/SKILL.md +555 -38
- package/skills/autosnippet-concepts/SKILL.md +349 -337
- package/skills/autosnippet-create/SKILL.md +5 -5
- package/skills/autosnippet-reference-dart/SKILL.md +543 -0
- package/skills/autosnippet-reference-go/SKILL.md +539 -0
- package/skills/autosnippet-reference-java/SKILL.md +534 -0
- package/skills/autosnippet-reference-jsts/SKILL.md +41 -9
- package/skills/autosnippet-reference-kotlin/SKILL.md +526 -0
- package/skills/autosnippet-reference-objc/SKILL.md +29 -6
- package/skills/autosnippet-reference-python/SKILL.md +800 -0
- package/skills/autosnippet-reference-swift/SKILL.md +70 -14
- package/skills/autosnippet-structure/SKILL.md +4 -4
- package/templates/cursor-rules/autosnippet-conventions.mdc +2 -2
- package/templates/recipes-setup/README.md +2 -2
- package/templates/recipes-setup/_template.md +1 -1
- package/dashboard/dist/assets/index-Bun3ld_J.css +0 -1
- package/dashboard/dist/assets/index-_Sk_Dmg3.js +0 -143
- package/resources/asd-entry/main.swift +0 -159
- package/scripts/build-asd-entry.js +0 -51
- package/scripts/init-xcode-snippets.js +0 -311
- package/template.json +0 -39
|
@@ -30,34 +30,35 @@
|
|
|
30
30
|
* @module WikiGenerator
|
|
31
31
|
*/
|
|
32
32
|
|
|
33
|
+
import { createHash } from 'node:crypto';
|
|
33
34
|
import fs from 'node:fs';
|
|
34
35
|
import path from 'node:path';
|
|
35
|
-
import { createHash } from 'node:crypto';
|
|
36
36
|
import Logger from '../../infrastructure/logging/Logger.js';
|
|
37
|
+
import { LanguageService } from '../../shared/LanguageService.js';
|
|
37
38
|
|
|
38
39
|
const logger = Logger.getInstance();
|
|
39
40
|
|
|
40
41
|
// ─── Wiki 生成阶段 ──────────────────────────────────────────
|
|
41
42
|
|
|
42
43
|
export const WikiPhase = Object.freeze({
|
|
43
|
-
INIT:
|
|
44
|
-
SCAN:
|
|
45
|
-
AST_ANALYZE:
|
|
46
|
-
SPM_PARSE:
|
|
47
|
-
KNOWLEDGE:
|
|
48
|
-
GENERATE:
|
|
49
|
-
AI_COMPOSE:
|
|
50
|
-
SYNC_DOCS:
|
|
51
|
-
DEDUP:
|
|
52
|
-
FINALIZE:
|
|
44
|
+
INIT: 'init', // 初始化 & 自检
|
|
45
|
+
SCAN: 'scan', // 扫描项目结构
|
|
46
|
+
AST_ANALYZE: 'ast-analyze', // AST 深度分析
|
|
47
|
+
SPM_PARSE: 'spm-parse', // SPM 依赖解析
|
|
48
|
+
KNOWLEDGE: 'knowledge', // 整合已有 Recipes
|
|
49
|
+
GENERATE: 'generate', // 生成 Markdown 骨架
|
|
50
|
+
AI_COMPOSE: 'ai-compose', // AI 合成写作增强
|
|
51
|
+
SYNC_DOCS: 'sync-docs', // 同步 Cursor 端 MD
|
|
52
|
+
DEDUP: 'dedup', // 去重
|
|
53
|
+
FINALIZE: 'finalize', // 写入 meta.json
|
|
53
54
|
});
|
|
54
55
|
|
|
55
56
|
// ─── 默认配置 ────────────────────────────────────────────────
|
|
56
57
|
|
|
57
58
|
const DEFAULTS = {
|
|
58
|
-
wikiDir:
|
|
59
|
-
language:
|
|
60
|
-
maxFiles:
|
|
59
|
+
wikiDir: 'AutoSnippet/wiki',
|
|
60
|
+
language: 'zh', // 'zh' | 'en'
|
|
61
|
+
maxFiles: 500,
|
|
61
62
|
includeRecipes: true,
|
|
62
63
|
includeDepGraph: true,
|
|
63
64
|
includeComponents: true,
|
|
@@ -69,7 +70,8 @@ export class WikiGenerator {
|
|
|
69
70
|
/**
|
|
70
71
|
* @param {object} deps
|
|
71
72
|
* @param {string} deps.projectRoot
|
|
72
|
-
* @param {import('../../service/
|
|
73
|
+
* @param {import('../../service/module/ModuleService.js').ModuleService} [deps.moduleService]
|
|
74
|
+
* @param {import('../../platform/ios/spm/SpmService.js').SpmService} [deps.spmService] — 向后兼容
|
|
73
75
|
* @param {import('../../service/knowledge/KnowledgeService.js').KnowledgeService} [deps.knowledgeService]
|
|
74
76
|
* @param {import('../../core/ast/ProjectGraph.js').default} [deps.projectGraph]
|
|
75
77
|
* @param {import('../../service/knowledge/CodeEntityGraph.js').CodeEntityGraph} [deps.codeEntityGraph]
|
|
@@ -78,14 +80,14 @@ export class WikiGenerator {
|
|
|
78
80
|
* @param {object} [deps.options]
|
|
79
81
|
*/
|
|
80
82
|
constructor(deps) {
|
|
81
|
-
this.projectRoot
|
|
82
|
-
this.
|
|
83
|
+
this.projectRoot = deps.projectRoot;
|
|
84
|
+
this.moduleService = deps.moduleService || null;
|
|
83
85
|
this.knowledgeService = deps.knowledgeService || null;
|
|
84
|
-
this.projectGraph
|
|
85
|
-
this.codeEntityGraph
|
|
86
|
-
this.aiProvider
|
|
87
|
-
this.onProgress
|
|
88
|
-
this.options
|
|
86
|
+
this.projectGraph = deps.projectGraph || null;
|
|
87
|
+
this.codeEntityGraph = deps.codeEntityGraph || null;
|
|
88
|
+
this.aiProvider = deps.aiProvider || null;
|
|
89
|
+
this.onProgress = deps.onProgress || (() => {});
|
|
90
|
+
this.options = { ...DEFAULTS, ...deps.options };
|
|
89
91
|
|
|
90
92
|
this.wikiDir = path.join(this.projectRoot, this.options.wikiDir);
|
|
91
93
|
this.metaPath = path.join(this.wikiDir, 'meta.json');
|
|
@@ -111,39 +113,53 @@ export class WikiGenerator {
|
|
|
111
113
|
// Phase 2: Scan project
|
|
112
114
|
this._emit(WikiPhase.SCAN, 5, '扫描项目结构...');
|
|
113
115
|
const projectInfo = await this._scanProject();
|
|
114
|
-
if (this._aborted)
|
|
116
|
+
if (this._aborted) {
|
|
117
|
+
return this._abortedResult();
|
|
118
|
+
}
|
|
115
119
|
|
|
116
120
|
// Phase 3: AST analyze
|
|
117
121
|
this._emit(WikiPhase.AST_ANALYZE, 15, '执行 AST 深度分析...');
|
|
118
122
|
const astInfo = await this._analyzeAST();
|
|
119
|
-
if (this._aborted)
|
|
123
|
+
if (this._aborted) {
|
|
124
|
+
return this._abortedResult();
|
|
125
|
+
}
|
|
120
126
|
|
|
121
|
-
// Phase 4: SPM parse
|
|
122
|
-
this._emit(WikiPhase.SPM_PARSE, 30, '
|
|
127
|
+
// Phase 4: Module/SPM parse
|
|
128
|
+
this._emit(WikiPhase.SPM_PARSE, 30, '解析模块依赖关系...');
|
|
123
129
|
const spmInfo = await this._parseSPM();
|
|
124
|
-
if (this._aborted)
|
|
130
|
+
if (this._aborted) {
|
|
131
|
+
return this._abortedResult();
|
|
132
|
+
}
|
|
125
133
|
|
|
126
134
|
// Phase 5: Knowledge integration
|
|
127
135
|
this._emit(WikiPhase.KNOWLEDGE, 45, '整合知识库 Recipes...');
|
|
128
136
|
const knowledgeInfo = await this._integrateKnowledge();
|
|
129
|
-
if (this._aborted)
|
|
137
|
+
if (this._aborted) {
|
|
138
|
+
return this._abortedResult();
|
|
139
|
+
}
|
|
130
140
|
|
|
131
141
|
// Phase 6: Content-driven topic discovery (V3)
|
|
132
142
|
this._emit(WikiPhase.GENERATE, 50, '分析项目数据,发现文档主题...');
|
|
133
143
|
const structuredData = { projectInfo, astInfo, spmInfo, knowledgeInfo };
|
|
134
144
|
const topics = this._discoverTopics(projectInfo, astInfo, spmInfo, knowledgeInfo);
|
|
135
|
-
if (this._aborted)
|
|
145
|
+
if (this._aborted) {
|
|
146
|
+
return this._abortedResult();
|
|
147
|
+
}
|
|
136
148
|
|
|
137
149
|
// Phase 7: AI-first article composition (V3)
|
|
138
150
|
this._emit(WikiPhase.AI_COMPOSE, 55, `撰写 ${topics.length} 篇文档...`);
|
|
139
151
|
const files = await this._composeArticles(topics, structuredData);
|
|
140
|
-
if (this._aborted)
|
|
152
|
+
if (this._aborted) {
|
|
153
|
+
return this._abortedResult();
|
|
154
|
+
}
|
|
141
155
|
|
|
142
156
|
// Phase 8: Sync Cursor docs
|
|
143
157
|
this._emit(WikiPhase.SYNC_DOCS, 80, '同步 Cursor 端文档...');
|
|
144
158
|
const syncedFiles = this._syncCursorDocs();
|
|
145
159
|
files.push(...syncedFiles);
|
|
146
|
-
if (this._aborted)
|
|
160
|
+
if (this._aborted) {
|
|
161
|
+
return this._abortedResult();
|
|
162
|
+
}
|
|
147
163
|
|
|
148
164
|
// Phase 9: Dedup
|
|
149
165
|
this._emit(WikiPhase.DEDUP, 90, '去重检查...');
|
|
@@ -159,7 +175,7 @@ export class WikiGenerator {
|
|
|
159
175
|
return {
|
|
160
176
|
success: true,
|
|
161
177
|
filesGenerated: files.length,
|
|
162
|
-
aiComposed: files.filter(f => f.polished).length,
|
|
178
|
+
aiComposed: files.filter((f) => f.polished).length,
|
|
163
179
|
syncedDocs: syncedFiles.length,
|
|
164
180
|
dedup: dedupResult,
|
|
165
181
|
duration,
|
|
@@ -237,20 +253,34 @@ export class WikiGenerator {
|
|
|
237
253
|
// 检测项目类型
|
|
238
254
|
const entries = fs.readdirSync(this.projectRoot, { withFileTypes: true });
|
|
239
255
|
for (const e of entries) {
|
|
240
|
-
if (e.name === 'Package.swift')
|
|
241
|
-
|
|
242
|
-
|
|
256
|
+
if (e.name === 'Package.swift') {
|
|
257
|
+
info.hasPackageSwift = true;
|
|
258
|
+
}
|
|
259
|
+
if (e.name === 'Podfile') {
|
|
260
|
+
info.hasPodfile = true;
|
|
261
|
+
}
|
|
262
|
+
if (e.name.endsWith('.xcodeproj') || e.name.endsWith('.xcworkspace')) {
|
|
263
|
+
info.hasXcodeproj = true;
|
|
264
|
+
}
|
|
243
265
|
}
|
|
244
266
|
|
|
245
267
|
// 统计源文件
|
|
246
|
-
const extMap = {
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
268
|
+
const extMap = {};
|
|
269
|
+
for (const ext of LanguageService.sourceExts) {
|
|
270
|
+
extMap[ext] = LanguageService.displayNameFromExt(ext) || ext;
|
|
271
|
+
}
|
|
272
|
+
this._walkDir(
|
|
273
|
+
this.projectRoot,
|
|
274
|
+
(filePath) => {
|
|
275
|
+
const ext = path.extname(filePath);
|
|
276
|
+
if (extMap[ext]) {
|
|
277
|
+
info.sourceFiles.push(path.relative(this.projectRoot, filePath));
|
|
278
|
+
const displayLang = LanguageService.displayNameFromExt(ext);
|
|
279
|
+
info.languages[displayLang] = (info.languages[displayLang] || 0) + 1;
|
|
280
|
+
}
|
|
281
|
+
},
|
|
282
|
+
this.options.maxFiles
|
|
283
|
+
);
|
|
254
284
|
|
|
255
285
|
// 按模块/Target 分组源文件 (SPM 约定: Sources/{ModuleName}/...)
|
|
256
286
|
info.sourceFilesByModule = {};
|
|
@@ -266,7 +296,9 @@ export class WikiGenerator {
|
|
|
266
296
|
mod = parts.length > 1 ? parts[0] : null;
|
|
267
297
|
}
|
|
268
298
|
if (mod) {
|
|
269
|
-
if (!info.sourceFilesByModule[mod])
|
|
299
|
+
if (!info.sourceFilesByModule[mod]) {
|
|
300
|
+
info.sourceFilesByModule[mod] = [];
|
|
301
|
+
}
|
|
270
302
|
info.sourceFilesByModule[mod].push(f);
|
|
271
303
|
}
|
|
272
304
|
}
|
|
@@ -293,7 +325,9 @@ export class WikiGenerator {
|
|
|
293
325
|
if (info?.filePath) {
|
|
294
326
|
const mod = this._inferModuleFromPath(info.filePath);
|
|
295
327
|
if (mod) {
|
|
296
|
-
if (!classNamesByModule[mod])
|
|
328
|
+
if (!classNamesByModule[mod]) {
|
|
329
|
+
classNamesByModule[mod] = [];
|
|
330
|
+
}
|
|
297
331
|
classNamesByModule[mod].push(name);
|
|
298
332
|
}
|
|
299
333
|
}
|
|
@@ -304,13 +338,19 @@ export class WikiGenerator {
|
|
|
304
338
|
if (info?.filePath) {
|
|
305
339
|
const mod = this._inferModuleFromPath(info.filePath);
|
|
306
340
|
if (mod) {
|
|
307
|
-
if (!protocolNamesByModule[mod])
|
|
341
|
+
if (!protocolNamesByModule[mod]) {
|
|
342
|
+
protocolNamesByModule[mod] = [];
|
|
343
|
+
}
|
|
308
344
|
protocolNamesByModule[mod].push(name);
|
|
309
345
|
}
|
|
310
346
|
}
|
|
311
347
|
}
|
|
312
348
|
|
|
313
|
-
this._emit(
|
|
349
|
+
this._emit(
|
|
350
|
+
WikiPhase.AST_ANALYZE,
|
|
351
|
+
25,
|
|
352
|
+
`AST 分析: ${overview.totalClasses} 个类, ${overview.totalProtocols} 个协议`
|
|
353
|
+
);
|
|
314
354
|
return {
|
|
315
355
|
overview,
|
|
316
356
|
classes: allClasses,
|
|
@@ -321,27 +361,40 @@ export class WikiGenerator {
|
|
|
321
361
|
}
|
|
322
362
|
|
|
323
363
|
// 没有现成的 ProjectGraph — 返回空壳(不阻塞生成)
|
|
324
|
-
return {
|
|
364
|
+
return {
|
|
365
|
+
overview: null,
|
|
366
|
+
classes: [],
|
|
367
|
+
protocols: [],
|
|
368
|
+
classNamesByModule: {},
|
|
369
|
+
protocolNamesByModule: {},
|
|
370
|
+
};
|
|
325
371
|
}
|
|
326
372
|
|
|
327
373
|
/**
|
|
328
|
-
*
|
|
374
|
+
* 模块依赖解析
|
|
375
|
+
* 通过 moduleService 统一处理所有语言的模块扫描
|
|
329
376
|
*/
|
|
330
377
|
async _parseSPM() {
|
|
331
|
-
if (!this.
|
|
378
|
+
if (!this.moduleService) {
|
|
379
|
+
return { targets: [], depGraph: null };
|
|
380
|
+
}
|
|
332
381
|
|
|
333
382
|
try {
|
|
334
|
-
|
|
383
|
+
await this.moduleService.load();
|
|
384
|
+
const targets = await this.moduleService.listTargets();
|
|
335
385
|
let depGraph = null;
|
|
336
386
|
if (this.options.includeDepGraph) {
|
|
337
387
|
try {
|
|
338
|
-
depGraph = await this.
|
|
339
|
-
} catch {
|
|
388
|
+
depGraph = await this.moduleService.getDependencyGraph({ level: 'target' });
|
|
389
|
+
} catch {
|
|
390
|
+
/* non-critical */
|
|
391
|
+
}
|
|
340
392
|
}
|
|
341
|
-
|
|
342
|
-
|
|
393
|
+
const info = this.moduleService.getProjectInfo();
|
|
394
|
+
this._emit(WikiPhase.SPM_PARSE, 40, `模块: ${targets.length} 个 (${info.primaryLanguage})`);
|
|
395
|
+
return { targets, depGraph, projectInfo: info };
|
|
343
396
|
} catch (err) {
|
|
344
|
-
logger.warn('[WikiGenerator]
|
|
397
|
+
logger.warn('[WikiGenerator] ModuleService parse failed', { error: err.message });
|
|
345
398
|
return { targets: [], depGraph: null };
|
|
346
399
|
}
|
|
347
400
|
}
|
|
@@ -361,7 +414,7 @@ export class WikiGenerator {
|
|
|
361
414
|
offset: 0,
|
|
362
415
|
});
|
|
363
416
|
const recipes = result.items || result || [];
|
|
364
|
-
const stats = await this.knowledgeService.getStats?.() || null;
|
|
417
|
+
const stats = (await this.knowledgeService.getStats?.()) || null;
|
|
365
418
|
this._emit(WikiPhase.KNOWLEDGE, 55, `知识库: ${recipes.length} 条活跃 Recipe`);
|
|
366
419
|
return { recipes: Array.isArray(recipes) ? recipes : [], stats };
|
|
367
420
|
} catch (err) {
|
|
@@ -411,7 +464,8 @@ export class WikiGenerator {
|
|
|
411
464
|
|
|
412
465
|
// ── 3. 快速上手 (需要构建配置或入口点) ──
|
|
413
466
|
const hasEntryPoints = (astInfo.overview?.entryPoints?.length || 0) > 0;
|
|
414
|
-
const hasBuildSystem =
|
|
467
|
+
const hasBuildSystem =
|
|
468
|
+
projectInfo.hasPackageSwift || projectInfo.hasPodfile || projectInfo.hasXcodeproj;
|
|
415
469
|
|
|
416
470
|
if (hasEntryPoints || hasBuildSystem) {
|
|
417
471
|
topics.push({
|
|
@@ -434,7 +488,9 @@ export class WikiGenerator {
|
|
|
434
488
|
const richness = moduleFiles.length + classCount * 2 + protoCount * 2 + depCount;
|
|
435
489
|
|
|
436
490
|
// 跳过过于单薄的模块 (少于3分不值得独立文档)
|
|
437
|
-
if (richness < 3)
|
|
491
|
+
if (richness < 3) {
|
|
492
|
+
continue;
|
|
493
|
+
}
|
|
438
494
|
|
|
439
495
|
topics.push({
|
|
440
496
|
id: `module-${_slug(target.name)}`,
|
|
@@ -452,7 +508,9 @@ export class WikiGenerator {
|
|
|
452
508
|
for (const r of knowledgeInfo.recipes) {
|
|
453
509
|
const json = r.toJSON ? r.toJSON() : r;
|
|
454
510
|
const cat = json.category || 'Other';
|
|
455
|
-
if (!groups[cat])
|
|
511
|
+
if (!groups[cat]) {
|
|
512
|
+
groups[cat] = [];
|
|
513
|
+
}
|
|
456
514
|
groups[cat].push(json);
|
|
457
515
|
}
|
|
458
516
|
|
|
@@ -470,7 +528,9 @@ export class WikiGenerator {
|
|
|
470
528
|
} else {
|
|
471
529
|
// 按分类拆分为多篇
|
|
472
530
|
for (const [cat, items] of catEntries) {
|
|
473
|
-
if (items.length < 2)
|
|
531
|
+
if (items.length < 2) {
|
|
532
|
+
continue;
|
|
533
|
+
}
|
|
474
534
|
topics.push({
|
|
475
535
|
id: `pattern-${_slug(cat)}`,
|
|
476
536
|
path: `patterns/${_slug(cat)}.md`,
|
|
@@ -497,7 +557,9 @@ export class WikiGenerator {
|
|
|
497
557
|
// 按优先级排序
|
|
498
558
|
topics.sort((a, b) => b.priority - a.priority);
|
|
499
559
|
|
|
500
|
-
logger.info(
|
|
560
|
+
logger.info(
|
|
561
|
+
`[WikiGenerator] Discovered ${topics.length} topics: ${topics.map((t) => t.id).join(', ')}`
|
|
562
|
+
);
|
|
501
563
|
this._emit(WikiPhase.GENERATE, 55, `发现 ${topics.length} 个文档主题`);
|
|
502
564
|
return topics;
|
|
503
565
|
}
|
|
@@ -521,20 +583,28 @@ export class WikiGenerator {
|
|
|
521
583
|
|
|
522
584
|
// 确保必要的子目录存在
|
|
523
585
|
this._ensureDir(this.wikiDir);
|
|
524
|
-
const needsModulesDir = topics.some(t => t.path.startsWith('modules/'));
|
|
525
|
-
const needsPatternsDir = topics.some(t => t.path.startsWith('patterns/'));
|
|
526
|
-
if (needsModulesDir)
|
|
527
|
-
|
|
586
|
+
const needsModulesDir = topics.some((t) => t.path.startsWith('modules/'));
|
|
587
|
+
const needsPatternsDir = topics.some((t) => t.path.startsWith('patterns/'));
|
|
588
|
+
if (needsModulesDir) {
|
|
589
|
+
this._ensureDir(path.join(this.wikiDir, 'modules'));
|
|
590
|
+
}
|
|
591
|
+
if (needsPatternsDir) {
|
|
592
|
+
this._ensureDir(path.join(this.wikiDir, 'patterns'));
|
|
593
|
+
}
|
|
528
594
|
|
|
529
595
|
let composed = 0;
|
|
530
596
|
const systemPrompt = this._buildAiSystemPrompt(isZh);
|
|
531
597
|
|
|
532
598
|
for (let i = 0; i < topics.length; i++) {
|
|
533
|
-
if (this._aborted)
|
|
599
|
+
if (this._aborted) {
|
|
600
|
+
break;
|
|
601
|
+
}
|
|
534
602
|
|
|
535
603
|
const topic = topics[i];
|
|
536
604
|
// 将全部主题列表注入 overview,用于生成导航
|
|
537
|
-
if (topic.type === 'overview')
|
|
605
|
+
if (topic.type === 'overview') {
|
|
606
|
+
topic._allTopics = topics;
|
|
607
|
+
}
|
|
538
608
|
|
|
539
609
|
const progress = 58 + Math.round((i / topics.length) * 22);
|
|
540
610
|
this._emit(WikiPhase.AI_COMPOSE, progress, `撰写: ${topic.title}`);
|
|
@@ -547,7 +617,9 @@ export class WikiGenerator {
|
|
|
547
617
|
const prompt = this._buildArticlePrompt(topic, structuredData, isZh);
|
|
548
618
|
const aiResult = await Promise.race([
|
|
549
619
|
this.aiProvider.chat(prompt, { systemPrompt, temperature: 0.3, maxTokens: 4096 }),
|
|
550
|
-
new Promise((_, reject) =>
|
|
620
|
+
new Promise((_, reject) =>
|
|
621
|
+
setTimeout(() => reject(new Error('AI compose timeout')), 45_000)
|
|
622
|
+
),
|
|
551
623
|
]);
|
|
552
624
|
|
|
553
625
|
if (aiResult && typeof aiResult === 'string' && aiResult.length >= MIN_ARTICLE_CHARS) {
|
|
@@ -566,7 +638,9 @@ export class WikiGenerator {
|
|
|
566
638
|
|
|
567
639
|
// === 3. 质量关卡 ===
|
|
568
640
|
if (!content || content.length < MIN_ARTICLE_CHARS) {
|
|
569
|
-
logger.info(
|
|
641
|
+
logger.info(
|
|
642
|
+
`[WikiGenerator] Skipping thin topic: ${topic.id} (${content?.length || 0} chars)`
|
|
643
|
+
);
|
|
570
644
|
continue;
|
|
571
645
|
}
|
|
572
646
|
|
|
@@ -579,7 +653,11 @@ export class WikiGenerator {
|
|
|
579
653
|
}
|
|
580
654
|
|
|
581
655
|
logger.info(`[WikiGenerator] Composed ${files.length} articles (${composed} AI-enhanced)`);
|
|
582
|
-
this._emit(
|
|
656
|
+
this._emit(
|
|
657
|
+
WikiPhase.AI_COMPOSE,
|
|
658
|
+
80,
|
|
659
|
+
`撰写完成: ${files.length} 篇文档 (${composed} 篇 AI 增强)`
|
|
660
|
+
);
|
|
583
661
|
return files;
|
|
584
662
|
}
|
|
585
663
|
|
|
@@ -596,9 +674,16 @@ export class WikiGenerator {
|
|
|
596
674
|
|
|
597
675
|
// 公共项目上下文
|
|
598
676
|
parts.push(`# 项目: ${projectInfo.name}`);
|
|
599
|
-
parts.push(
|
|
677
|
+
parts.push(
|
|
678
|
+
`源文件数: ${projectInfo.sourceFiles.length}, SPM Targets: ${spmInfo.targets.length}, 活跃知识条目: ${knowledgeInfo.recipes.length}`
|
|
679
|
+
);
|
|
600
680
|
if (projectInfo.languages) {
|
|
601
|
-
parts.push(
|
|
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
|
+
);
|
|
602
687
|
}
|
|
603
688
|
parts.push('');
|
|
604
689
|
|
|
@@ -609,10 +694,18 @@ export class WikiGenerator {
|
|
|
609
694
|
|
|
610
695
|
// 项目类型
|
|
611
696
|
const types = [];
|
|
612
|
-
if (projectInfo.hasPackageSwift)
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
if (
|
|
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
|
+
}
|
|
616
709
|
parts.push('');
|
|
617
710
|
|
|
618
711
|
// 模块结构
|
|
@@ -621,8 +714,12 @@ export class WikiGenerator {
|
|
|
621
714
|
for (const t of spmInfo.targets) {
|
|
622
715
|
const files = this._getModuleSourceFiles(t, projectInfo);
|
|
623
716
|
const cls = astInfo.classNamesByModule?.[t.name]?.length || 0;
|
|
624
|
-
const deps = (t.dependencies || t.info?.dependencies || []).map(d =>
|
|
625
|
-
|
|
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
|
+
);
|
|
626
723
|
}
|
|
627
724
|
parts.push('');
|
|
628
725
|
}
|
|
@@ -630,12 +727,14 @@ export class WikiGenerator {
|
|
|
630
727
|
// AST 概况
|
|
631
728
|
if (astInfo.overview) {
|
|
632
729
|
parts.push('### 代码规模');
|
|
633
|
-
parts.push(
|
|
730
|
+
parts.push(
|
|
731
|
+
`类/结构体: ${astInfo.overview.totalClasses || 0}, 协议: ${astInfo.overview.totalProtocols || 0}, 方法: ${astInfo.overview.totalMethods || 0}`
|
|
732
|
+
);
|
|
634
733
|
parts.push('');
|
|
635
734
|
}
|
|
636
735
|
|
|
637
736
|
// 可用的其他文档(用于导航链接)
|
|
638
|
-
const otherTopics = (topic._allTopics || []).filter(t => t.type !== 'overview');
|
|
737
|
+
const otherTopics = (topic._allTopics || []).filter((t) => t.type !== 'overview');
|
|
639
738
|
if (otherTopics.length > 0) {
|
|
640
739
|
parts.push('### 需要包含的导航链接');
|
|
641
740
|
for (const t of otherTopics) {
|
|
@@ -645,7 +744,9 @@ export class WikiGenerator {
|
|
|
645
744
|
}
|
|
646
745
|
|
|
647
746
|
parts.push('要求: 撰写完整的项目概述文档。');
|
|
648
|
-
parts.push(
|
|
747
|
+
parts.push(
|
|
748
|
+
'包含: 项目简介(解释项目做什么)、模块总览(表格形式)、技术栈分析、核心数据指标、文档导航索引。'
|
|
749
|
+
);
|
|
649
750
|
parts.push('不要只列数据 — 要解释项目的定位、各模块的职责和协作关系。');
|
|
650
751
|
break;
|
|
651
752
|
}
|
|
@@ -657,8 +758,12 @@ export class WikiGenerator {
|
|
|
657
758
|
if (spmInfo.targets.length > 0) {
|
|
658
759
|
parts.push('### 模块及依赖关系');
|
|
659
760
|
for (const t of spmInfo.targets) {
|
|
660
|
-
const deps = (t.dependencies || t.info?.dependencies || []).map(d =>
|
|
661
|
-
|
|
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
|
+
);
|
|
662
767
|
}
|
|
663
768
|
parts.push('');
|
|
664
769
|
}
|
|
@@ -687,7 +792,9 @@ export class WikiGenerator {
|
|
|
687
792
|
}
|
|
688
793
|
|
|
689
794
|
parts.push('要求: 撰写架构分析文档。');
|
|
690
|
-
parts.push(
|
|
795
|
+
parts.push(
|
|
796
|
+
'包含: 模块依赖图(使用 Mermaid graph TD 语法)、分层架构分析(解释每层的职责)、模块间协作关系、架构设计决策阐述。'
|
|
797
|
+
);
|
|
691
798
|
parts.push('用 Mermaid 绘制依赖关系图和继承层次图。分析为什么采用这种架构。');
|
|
692
799
|
break;
|
|
693
800
|
}
|
|
@@ -705,15 +812,21 @@ export class WikiGenerator {
|
|
|
705
812
|
parts.push('### 模块基本信息');
|
|
706
813
|
parts.push(`- 类型: ${target.type || 'target'}`);
|
|
707
814
|
const tPath = target.path || target.info?.path;
|
|
708
|
-
if (tPath)
|
|
709
|
-
|
|
815
|
+
if (tPath) {
|
|
816
|
+
parts.push(`- 路径: ${tPath}`);
|
|
817
|
+
}
|
|
818
|
+
if (target.packageName) {
|
|
819
|
+
parts.push(`- 所属包: ${target.packageName}`);
|
|
820
|
+
}
|
|
710
821
|
parts.push(`- 源文件: ${moduleFiles.length} 个`);
|
|
711
822
|
parts.push(`- 类/结构体: ${moduleClasses.length} 个`);
|
|
712
823
|
parts.push(`- 协议: ${moduleProtocols.length} 个`);
|
|
713
824
|
parts.push('');
|
|
714
825
|
|
|
715
826
|
if (deps.length > 0) {
|
|
716
|
-
parts.push(
|
|
827
|
+
parts.push(
|
|
828
|
+
`### 依赖: ${deps.map((d) => (typeof d === 'string' ? d : d.name)).join(', ')}`
|
|
829
|
+
);
|
|
717
830
|
parts.push('');
|
|
718
831
|
}
|
|
719
832
|
|
|
@@ -729,28 +842,36 @@ export class WikiGenerator {
|
|
|
729
842
|
|
|
730
843
|
// 关键源文件名(帮助 AI 推断模块功能)
|
|
731
844
|
if (moduleFiles.length > 0) {
|
|
732
|
-
const keyFiles = moduleFiles.slice(0, 25).map(f => path.basename(f));
|
|
845
|
+
const keyFiles = moduleFiles.slice(0, 25).map((f) => path.basename(f));
|
|
733
846
|
parts.push(`### 关键源文件: ${keyFiles.join(', ')}`);
|
|
734
847
|
parts.push('');
|
|
735
848
|
}
|
|
736
849
|
|
|
737
850
|
// 相关 recipes
|
|
738
|
-
const related = knowledgeInfo.recipes.filter(r => {
|
|
851
|
+
const related = knowledgeInfo.recipes.filter((r) => {
|
|
739
852
|
const json = r.toJSON ? r.toJSON() : r;
|
|
740
|
-
return
|
|
853
|
+
return (
|
|
854
|
+
json.moduleName === target.name ||
|
|
855
|
+
json.tags?.includes(target.name) ||
|
|
856
|
+
json.title?.includes(target.name)
|
|
857
|
+
);
|
|
741
858
|
});
|
|
742
859
|
if (related.length > 0) {
|
|
743
860
|
parts.push(`### 相关知识条目 (${related.length})`);
|
|
744
861
|
for (const r of related.slice(0, 10)) {
|
|
745
862
|
const json = r.toJSON ? r.toJSON() : r;
|
|
746
863
|
parts.push(`- ${json.title}: ${json.description || ''}`);
|
|
747
|
-
if (json.reasoning?.whyStandard)
|
|
864
|
+
if (json.reasoning?.whyStandard) {
|
|
865
|
+
parts.push(` 为什么: ${json.reasoning.whyStandard}`);
|
|
866
|
+
}
|
|
748
867
|
}
|
|
749
868
|
parts.push('');
|
|
750
869
|
}
|
|
751
870
|
|
|
752
871
|
parts.push('要求: 撰写模块深度分析文档。');
|
|
753
|
-
parts.push(
|
|
872
|
+
parts.push(
|
|
873
|
+
'包含: 模块职责说明(从文件名和类名推断功能意图)、核心类型分析(不是简单罗列而是解释每个类的角色)、依赖关系分析、设计模式识别。'
|
|
874
|
+
);
|
|
754
875
|
parts.push('如果能推断出数据流或协作关系,请用 Mermaid 图表展示。');
|
|
755
876
|
break;
|
|
756
877
|
}
|
|
@@ -759,16 +880,26 @@ export class WikiGenerator {
|
|
|
759
880
|
parts.push('## 任务: 撰写快速上手指南');
|
|
760
881
|
parts.push('');
|
|
761
882
|
|
|
762
|
-
if (projectInfo.hasPackageSwift)
|
|
763
|
-
|
|
764
|
-
|
|
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
|
+
}
|
|
765
892
|
parts.push('');
|
|
766
893
|
|
|
767
894
|
if (spmInfo.targets.length > 0) {
|
|
768
|
-
const mainTargets = spmInfo.targets.filter(t => t.type !== 'test');
|
|
769
|
-
const testTargets = spmInfo.targets.filter(t => t.type === 'test');
|
|
770
|
-
if (mainTargets.length > 0)
|
|
771
|
-
|
|
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
|
+
}
|
|
772
903
|
parts.push('');
|
|
773
904
|
}
|
|
774
905
|
|
|
@@ -778,7 +909,9 @@ export class WikiGenerator {
|
|
|
778
909
|
}
|
|
779
910
|
|
|
780
911
|
parts.push('要求: 撰写开发者快速上手指南。');
|
|
781
|
-
parts.push(
|
|
912
|
+
parts.push(
|
|
913
|
+
'包含: 环境要求、项目获取、依赖安装、构建步骤(具体命令)、运行测试、项目目录结构说明。'
|
|
914
|
+
);
|
|
782
915
|
parts.push('语句清晰,步骤明确,适合新人阅读。');
|
|
783
916
|
break;
|
|
784
917
|
}
|
|
@@ -791,7 +924,9 @@ export class WikiGenerator {
|
|
|
791
924
|
for (const r of knowledgeInfo.recipes) {
|
|
792
925
|
const json = r.toJSON ? r.toJSON() : r;
|
|
793
926
|
const cat = json.category || 'Other';
|
|
794
|
-
if (!groups[cat])
|
|
927
|
+
if (!groups[cat]) {
|
|
928
|
+
groups[cat] = [];
|
|
929
|
+
}
|
|
795
930
|
groups[cat].push(json);
|
|
796
931
|
}
|
|
797
932
|
|
|
@@ -799,15 +934,23 @@ export class WikiGenerator {
|
|
|
799
934
|
parts.push(`### ${cat} (${items.length} 条)`);
|
|
800
935
|
for (const item of items.slice(0, 8)) {
|
|
801
936
|
parts.push(`- ${item.title}: ${item.description || 'N/A'}`);
|
|
802
|
-
if (item.doClause)
|
|
803
|
-
|
|
804
|
-
|
|
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
|
+
}
|
|
805
946
|
}
|
|
806
947
|
parts.push('');
|
|
807
948
|
}
|
|
808
949
|
|
|
809
950
|
parts.push('要求: 撰写代码模式文档。对每个分类进行总结分析,解释模式的意义和应用场景。');
|
|
810
|
-
parts.push(
|
|
951
|
+
parts.push(
|
|
952
|
+
'不要只列出条目 — 为每个分类写一段总结,解释该类模式的整体意图。附带代码示例(从数据中取)。'
|
|
953
|
+
);
|
|
811
954
|
break;
|
|
812
955
|
}
|
|
813
956
|
|
|
@@ -818,10 +961,18 @@ export class WikiGenerator {
|
|
|
818
961
|
|
|
819
962
|
for (const item of pd.recipes) {
|
|
820
963
|
parts.push(`### ${item.title}`);
|
|
821
|
-
if (item.description)
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
if (item.
|
|
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
|
+
}
|
|
825
976
|
if (item.content?.pattern) {
|
|
826
977
|
parts.push('代码:');
|
|
827
978
|
parts.push('```');
|
|
@@ -832,7 +983,9 @@ export class WikiGenerator {
|
|
|
832
983
|
}
|
|
833
984
|
|
|
834
985
|
parts.push('要求: 撰写该分类的详细代码模式文档。');
|
|
835
|
-
parts.push(
|
|
986
|
+
parts.push(
|
|
987
|
+
'先写一段总结性概述,然后对每个模式做分析,解释为什么要遵循,给出正确和错误的对比示例。'
|
|
988
|
+
);
|
|
836
989
|
break;
|
|
837
990
|
}
|
|
838
991
|
|
|
@@ -847,9 +1000,13 @@ export class WikiGenerator {
|
|
|
847
1000
|
}
|
|
848
1001
|
}
|
|
849
1002
|
parts.push('');
|
|
850
|
-
parts.push(
|
|
1003
|
+
parts.push(
|
|
1004
|
+
`总计: ${astInfo.protocols.length} 个协议, ${astInfo.classes.length} 个类/结构体`
|
|
1005
|
+
);
|
|
851
1006
|
parts.push('');
|
|
852
|
-
parts.push(
|
|
1007
|
+
parts.push(
|
|
1008
|
+
'要求: 撰写协议参考文档。按模块分组,分析每个协议的用途和意义,描述协议之间的关系和设计意图。'
|
|
1009
|
+
);
|
|
853
1010
|
break;
|
|
854
1011
|
}
|
|
855
1012
|
}
|
|
@@ -866,13 +1023,26 @@ export class WikiGenerator {
|
|
|
866
1023
|
|
|
867
1024
|
switch (topic.type) {
|
|
868
1025
|
case 'overview':
|
|
869
|
-
return this._renderIndex(
|
|
1026
|
+
return this._renderIndex(
|
|
1027
|
+
projectInfo,
|
|
1028
|
+
astInfo,
|
|
1029
|
+
spmInfo,
|
|
1030
|
+
knowledgeInfo,
|
|
1031
|
+
isZh,
|
|
1032
|
+
topic._allTopics
|
|
1033
|
+
);
|
|
870
1034
|
case 'architecture':
|
|
871
1035
|
return this._renderArchitecture(projectInfo, astInfo, spmInfo, isZh);
|
|
872
1036
|
case 'getting-started':
|
|
873
1037
|
return this._renderGettingStarted(projectInfo, spmInfo, astInfo, isZh);
|
|
874
1038
|
case 'module':
|
|
875
|
-
return this._renderModule(
|
|
1039
|
+
return this._renderModule(
|
|
1040
|
+
topic._moduleData.target,
|
|
1041
|
+
astInfo,
|
|
1042
|
+
knowledgeInfo,
|
|
1043
|
+
isZh,
|
|
1044
|
+
projectInfo
|
|
1045
|
+
);
|
|
876
1046
|
case 'patterns':
|
|
877
1047
|
return this._renderPatterns(knowledgeInfo, isZh);
|
|
878
1048
|
case 'pattern-category':
|
|
@@ -902,37 +1072,49 @@ export class WikiGenerator {
|
|
|
902
1072
|
lines.push('');
|
|
903
1073
|
|
|
904
1074
|
const types = [];
|
|
905
|
-
if (project.hasPackageSwift)
|
|
906
|
-
|
|
907
|
-
|
|
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
|
+
}
|
|
908
1084
|
|
|
909
1085
|
const overview = ast.overview || {};
|
|
910
|
-
const mainTargets = spm.targets.filter(t => t.type !== 'test');
|
|
911
|
-
const testTargets = spm.targets.filter(t => t.type === 'test');
|
|
1086
|
+
const mainTargets = spm.targets.filter((t) => t.type !== 'test');
|
|
1087
|
+
const testTargets = spm.targets.filter((t) => t.type === 'test');
|
|
912
1088
|
|
|
913
1089
|
if (isZh) {
|
|
914
|
-
lines.push(
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
1090
|
+
lines.push(
|
|
1091
|
+
`**${project.name}** 是一个 ${types.join(' + ') || 'iOS'} 项目,` +
|
|
1092
|
+
`包含 ${project.sourceFiles.length} 个源文件` +
|
|
1093
|
+
(overview.totalClasses ? `、${overview.totalClasses} 个类/结构体` : '') +
|
|
1094
|
+
(overview.totalProtocols ? `、${overview.totalProtocols} 个协议` : '') +
|
|
1095
|
+
`。`
|
|
919
1096
|
);
|
|
920
1097
|
if (mainTargets.length > 0) {
|
|
921
|
-
lines.push(
|
|
922
|
-
|
|
923
|
-
|
|
1098
|
+
lines.push(
|
|
1099
|
+
`项目由 ${mainTargets.length} 个功能模块组成` +
|
|
1100
|
+
(testTargets.length > 0 ? `,配备 ${testTargets.length} 个测试模块` : '') +
|
|
1101
|
+
`。`
|
|
1102
|
+
);
|
|
924
1103
|
}
|
|
925
1104
|
} else {
|
|
926
|
-
lines.push(
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
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
|
+
`.`
|
|
931
1111
|
);
|
|
932
1112
|
if (mainTargets.length > 0) {
|
|
933
|
-
lines.push(
|
|
934
|
-
|
|
935
|
-
|
|
1113
|
+
lines.push(
|
|
1114
|
+
`The project consists of ${mainTargets.length} functional modules` +
|
|
1115
|
+
(testTargets.length > 0 ? ` with ${testTargets.length} test modules` : '') +
|
|
1116
|
+
`.`
|
|
1117
|
+
);
|
|
936
1118
|
}
|
|
937
1119
|
}
|
|
938
1120
|
lines.push('');
|
|
@@ -941,15 +1123,21 @@ export class WikiGenerator {
|
|
|
941
1123
|
if (spm.targets.length > 0) {
|
|
942
1124
|
lines.push(`## ${isZh ? '模块总览' : 'Module Overview'}`);
|
|
943
1125
|
lines.push('');
|
|
944
|
-
lines.push(
|
|
1126
|
+
lines.push(
|
|
1127
|
+
`| ${isZh ? '模块' : 'Module'} | ${isZh ? '类型' : 'Type'} | ${isZh ? '源文件' : 'Files'} | ${isZh ? '类数' : 'Classes'} | ${isZh ? '协议数' : 'Protocols'} |`
|
|
1128
|
+
);
|
|
945
1129
|
lines.push('|--------|------|--------|--------|----------|');
|
|
946
1130
|
for (const t of spm.targets) {
|
|
947
1131
|
const moduleFiles = this._getModuleSourceFiles(t, project);
|
|
948
1132
|
const classCount = ast.classNamesByModule?.[t.name]?.length || 0;
|
|
949
1133
|
const protoCount = ast.protocolNamesByModule?.[t.name]?.length || 0;
|
|
950
|
-
const hasDoc = allTopics?.some(
|
|
1134
|
+
const hasDoc = allTopics?.some(
|
|
1135
|
+
(tp) => tp.type === 'module' && tp._moduleData?.target.name === t.name
|
|
1136
|
+
);
|
|
951
1137
|
const nameCol = hasDoc ? `[${t.name}](modules/${_slug(t.name)}.md)` : t.name;
|
|
952
|
-
lines.push(
|
|
1138
|
+
lines.push(
|
|
1139
|
+
`| ${nameCol} | ${t.type || 'target'} | ${moduleFiles.length || '-'} | ${classCount || '-'} | ${protoCount || '-'} |`
|
|
1140
|
+
);
|
|
953
1141
|
}
|
|
954
1142
|
lines.push('');
|
|
955
1143
|
}
|
|
@@ -958,7 +1146,9 @@ export class WikiGenerator {
|
|
|
958
1146
|
lines.push(`## ${isZh ? '技术栈' : 'Tech Stack'}`);
|
|
959
1147
|
lines.push('');
|
|
960
1148
|
if (project.languages && Object.keys(project.languages).length > 0) {
|
|
961
|
-
lines.push(
|
|
1149
|
+
lines.push(
|
|
1150
|
+
`| ${isZh ? '语言' : 'Language'} | ${isZh ? '文件数' : 'Files'} | ${isZh ? '占比' : 'Share'} |`
|
|
1151
|
+
);
|
|
962
1152
|
lines.push('|--------|-------|------|');
|
|
963
1153
|
const total = Object.values(project.languages).reduce((a, b) => a + b, 0);
|
|
964
1154
|
for (const [lang, count] of Object.entries(project.languages).sort((a, b) => b[1] - a[1])) {
|
|
@@ -974,15 +1164,25 @@ export class WikiGenerator {
|
|
|
974
1164
|
lines.push(`| ${isZh ? '指标' : 'Metric'} | ${isZh ? '数量' : 'Count'} |`);
|
|
975
1165
|
lines.push('|--------|-------|');
|
|
976
1166
|
lines.push(`| ${isZh ? '源文件数' : 'Source Files'} | ${project.sourceFiles.length} |`);
|
|
977
|
-
if (overview.totalClasses)
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
if (
|
|
981
|
-
|
|
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
|
+
}
|
|
982
1182
|
lines.push('');
|
|
983
1183
|
|
|
984
1184
|
// ── 文档导航 (动态,基于实际生成的主题) ──
|
|
985
|
-
const navTopics = (allTopics || []).filter(t => t.type !== 'overview');
|
|
1185
|
+
const navTopics = (allTopics || []).filter((t) => t.type !== 'overview');
|
|
986
1186
|
if (navTopics.length > 0) {
|
|
987
1187
|
lines.push('---');
|
|
988
1188
|
lines.push('');
|
|
@@ -1017,7 +1217,10 @@ export class WikiGenerator {
|
|
|
1017
1217
|
for (const target of spm.targets) {
|
|
1018
1218
|
const sid = _mermaidId(target.name);
|
|
1019
1219
|
if (!rendered.has(sid)) {
|
|
1020
|
-
const shape =
|
|
1220
|
+
const shape =
|
|
1221
|
+
target.type === 'test'
|
|
1222
|
+
? `${sid}[["${target.name} (Test)"]]`
|
|
1223
|
+
: `${sid}["${target.name}"]`;
|
|
1021
1224
|
lines.push(` ${shape}`);
|
|
1022
1225
|
rendered.add(sid);
|
|
1023
1226
|
}
|
|
@@ -1083,7 +1286,9 @@ export class WikiGenerator {
|
|
|
1083
1286
|
lines.push('```');
|
|
1084
1287
|
lines.push('');
|
|
1085
1288
|
}
|
|
1086
|
-
} catch {
|
|
1289
|
+
} catch {
|
|
1290
|
+
/* non-critical */
|
|
1291
|
+
}
|
|
1087
1292
|
}
|
|
1088
1293
|
|
|
1089
1294
|
lines.push(`[← ${isZh ? '返回概述' : 'Back to Overview'}](index.md)`);
|
|
@@ -1110,16 +1315,23 @@ export class WikiGenerator {
|
|
|
1110
1315
|
lines.push('');
|
|
1111
1316
|
|
|
1112
1317
|
// 推断模块功能 (基于名称和内容)
|
|
1113
|
-
const purpose = this._inferModulePurpose(
|
|
1318
|
+
const purpose = this._inferModulePurpose(
|
|
1319
|
+
target.name,
|
|
1320
|
+
moduleClasses,
|
|
1321
|
+
moduleProtocols,
|
|
1322
|
+
moduleFiles
|
|
1323
|
+
);
|
|
1114
1324
|
if (purpose) {
|
|
1115
|
-
lines.push(
|
|
1116
|
-
|
|
1117
|
-
|
|
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` : ''}.`
|
|
1118
1329
|
);
|
|
1119
1330
|
} else {
|
|
1120
|
-
lines.push(
|
|
1121
|
-
|
|
1122
|
-
|
|
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.`
|
|
1123
1335
|
);
|
|
1124
1336
|
}
|
|
1125
1337
|
lines.push('');
|
|
@@ -1128,21 +1340,34 @@ export class WikiGenerator {
|
|
|
1128
1340
|
lines.push(`| ${isZh ? '属性' : 'Property'} | ${isZh ? '值' : 'Value'} |`);
|
|
1129
1341
|
lines.push('|--------|------|');
|
|
1130
1342
|
lines.push(`| ${isZh ? '类型' : 'Type'} | ${target.type || 'target'} |`);
|
|
1131
|
-
if (target.packageName)
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
if (
|
|
1135
|
-
|
|
1136
|
-
|
|
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
|
+
}
|
|
1137
1361
|
lines.push('');
|
|
1138
1362
|
|
|
1139
1363
|
// ── 依赖 ──
|
|
1140
1364
|
if (deps.length > 0) {
|
|
1141
1365
|
lines.push(`## ${isZh ? '依赖关系' : 'Dependencies'}`);
|
|
1142
1366
|
lines.push('');
|
|
1143
|
-
lines.push(
|
|
1144
|
-
|
|
1145
|
-
|
|
1367
|
+
lines.push(
|
|
1368
|
+
isZh
|
|
1369
|
+
? `${target.name} 依赖以下 ${deps.length} 个模块:`
|
|
1370
|
+
: `${target.name} depends on ${deps.length} module(s):`
|
|
1146
1371
|
);
|
|
1147
1372
|
lines.push('');
|
|
1148
1373
|
for (const dep of deps) {
|
|
@@ -1160,9 +1385,10 @@ export class WikiGenerator {
|
|
|
1160
1385
|
if (moduleProtocols.length > 0) {
|
|
1161
1386
|
lines.push(`### ${isZh ? '协议' : 'Protocols'} (${moduleProtocols.length})`);
|
|
1162
1387
|
lines.push('');
|
|
1163
|
-
lines.push(
|
|
1164
|
-
|
|
1165
|
-
|
|
1388
|
+
lines.push(
|
|
1389
|
+
isZh
|
|
1390
|
+
? `${target.name} 定义了 ${moduleProtocols.length} 个协议,用于规范模块的接口边界:`
|
|
1391
|
+
: `${target.name} defines ${moduleProtocols.length} protocols establishing the module's interface contracts:`
|
|
1166
1392
|
);
|
|
1167
1393
|
lines.push('');
|
|
1168
1394
|
const sorted = [...moduleProtocols].sort();
|
|
@@ -1170,7 +1396,9 @@ export class WikiGenerator {
|
|
|
1170
1396
|
lines.push(`- \`${p}\``);
|
|
1171
1397
|
}
|
|
1172
1398
|
if (sorted.length > 20) {
|
|
1173
|
-
lines.push(
|
|
1399
|
+
lines.push(
|
|
1400
|
+
`- ... ${isZh ? `还有 ${sorted.length - 20} 个` : `and ${sorted.length - 20} more`}`
|
|
1401
|
+
);
|
|
1174
1402
|
}
|
|
1175
1403
|
lines.push('');
|
|
1176
1404
|
}
|
|
@@ -1183,7 +1411,9 @@ export class WikiGenerator {
|
|
|
1183
1411
|
lines.push(`- \`${c}\``);
|
|
1184
1412
|
}
|
|
1185
1413
|
if (sorted.length > 30) {
|
|
1186
|
-
lines.push(
|
|
1414
|
+
lines.push(
|
|
1415
|
+
`- ... ${isZh ? `还有 ${sorted.length - 30} 个` : `and ${sorted.length - 30} more`}`
|
|
1416
|
+
);
|
|
1187
1417
|
}
|
|
1188
1418
|
lines.push('');
|
|
1189
1419
|
}
|
|
@@ -1198,7 +1428,7 @@ export class WikiGenerator {
|
|
|
1198
1428
|
const langCount = {};
|
|
1199
1429
|
for (const f of moduleFiles) {
|
|
1200
1430
|
const ext = path.extname(f);
|
|
1201
|
-
const lang =
|
|
1431
|
+
const lang = LanguageService.displayNameFromExt(ext);
|
|
1202
1432
|
langCount[lang] = (langCount[lang] || 0) + 1;
|
|
1203
1433
|
}
|
|
1204
1434
|
|
|
@@ -1212,27 +1442,36 @@ export class WikiGenerator {
|
|
|
1212
1442
|
|
|
1213
1443
|
// ── 该模块相关的 Recipes ──
|
|
1214
1444
|
if (knowledge.recipes.length > 0) {
|
|
1215
|
-
const related = knowledge.recipes.filter(r => {
|
|
1445
|
+
const related = knowledge.recipes.filter((r) => {
|
|
1216
1446
|
const json = r.toJSON ? r.toJSON() : r;
|
|
1217
|
-
return
|
|
1218
|
-
|
|
1219
|
-
|
|
1447
|
+
return (
|
|
1448
|
+
json.moduleName === target.name ||
|
|
1449
|
+
json.tags?.includes(target.name) ||
|
|
1450
|
+
json.title?.includes(target.name)
|
|
1451
|
+
);
|
|
1220
1452
|
});
|
|
1221
1453
|
if (related.length > 0) {
|
|
1222
1454
|
lines.push(`## ${isZh ? '相关知识条目' : 'Related Recipes'}`);
|
|
1223
1455
|
lines.push('');
|
|
1224
|
-
lines.push(
|
|
1225
|
-
|
|
1226
|
-
|
|
1456
|
+
lines.push(
|
|
1457
|
+
isZh
|
|
1458
|
+
? `团队知识库中有 ${related.length} 条与 ${target.name} 相关的条目:`
|
|
1459
|
+
: `The team knowledge base contains ${related.length} entries related to ${target.name}:`
|
|
1227
1460
|
);
|
|
1228
1461
|
lines.push('');
|
|
1229
1462
|
for (const r of related) {
|
|
1230
1463
|
const json = r.toJSON ? r.toJSON() : r;
|
|
1231
1464
|
lines.push(`### ${json.title}`);
|
|
1232
1465
|
lines.push('');
|
|
1233
|
-
if (json.description)
|
|
1234
|
-
|
|
1235
|
-
|
|
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
|
+
}
|
|
1236
1475
|
lines.push('');
|
|
1237
1476
|
}
|
|
1238
1477
|
}
|
|
@@ -1249,33 +1488,81 @@ export class WikiGenerator {
|
|
|
1249
1488
|
*/
|
|
1250
1489
|
_inferModulePurpose(name, classes, protocols, files) {
|
|
1251
1490
|
const lower = name.toLowerCase();
|
|
1252
|
-
const
|
|
1491
|
+
const _fileNames = files.map((f) => path.basename(f).toLowerCase());
|
|
1253
1492
|
|
|
1254
1493
|
// 常见模块功能推断规则
|
|
1255
1494
|
const rules = [
|
|
1256
|
-
{
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
{
|
|
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
|
+
},
|
|
1262
1525
|
{ match: /test|spec|mock/i, zh: '包含单元测试和 Mock', en: 'contains unit tests and mocks' },
|
|
1263
|
-
{
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
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
|
+
},
|
|
1268
1551
|
];
|
|
1269
1552
|
|
|
1270
1553
|
// 先按模块名匹配
|
|
1271
1554
|
for (const rule of rules) {
|
|
1272
|
-
if (rule.match.test(lower))
|
|
1555
|
+
if (rule.match.test(lower)) {
|
|
1556
|
+
return rule;
|
|
1557
|
+
}
|
|
1273
1558
|
}
|
|
1274
1559
|
|
|
1275
1560
|
// 再按类名匹配
|
|
1276
1561
|
const classStr = classes.join(' ');
|
|
1277
1562
|
for (const rule of rules) {
|
|
1278
|
-
if (rule.match.test(classStr))
|
|
1563
|
+
if (rule.match.test(classStr)) {
|
|
1564
|
+
return rule;
|
|
1565
|
+
}
|
|
1279
1566
|
}
|
|
1280
1567
|
|
|
1281
1568
|
return null;
|
|
@@ -1296,16 +1583,19 @@ export class WikiGenerator {
|
|
|
1296
1583
|
for (const r of knowledge.recipes) {
|
|
1297
1584
|
const json = r.toJSON ? r.toJSON() : r;
|
|
1298
1585
|
const cat = json.category || 'Other';
|
|
1299
|
-
if (!groups[cat])
|
|
1586
|
+
if (!groups[cat]) {
|
|
1587
|
+
groups[cat] = [];
|
|
1588
|
+
}
|
|
1300
1589
|
groups[cat].push(json);
|
|
1301
1590
|
}
|
|
1302
1591
|
|
|
1303
1592
|
// 总结
|
|
1304
1593
|
const totalRecipes = knowledge.recipes.length;
|
|
1305
1594
|
const catCount = Object.keys(groups).length;
|
|
1306
|
-
lines.push(
|
|
1307
|
-
|
|
1308
|
-
|
|
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.`
|
|
1309
1599
|
);
|
|
1310
1600
|
lines.push('');
|
|
1311
1601
|
|
|
@@ -1314,9 +1604,10 @@ export class WikiGenerator {
|
|
|
1314
1604
|
lines.push('');
|
|
1315
1605
|
|
|
1316
1606
|
// 分类概述
|
|
1317
|
-
lines.push(
|
|
1318
|
-
|
|
1319
|
-
|
|
1607
|
+
lines.push(
|
|
1608
|
+
isZh
|
|
1609
|
+
? `${cat} 分类包含 ${items.length} 条规则,覆盖了该领域的核心规范。`
|
|
1610
|
+
: `The ${cat} category contains ${items.length} rules covering core conventions in this area.`
|
|
1320
1611
|
);
|
|
1321
1612
|
lines.push('');
|
|
1322
1613
|
|
|
@@ -1328,7 +1619,7 @@ export class WikiGenerator {
|
|
|
1328
1619
|
lines.push('');
|
|
1329
1620
|
}
|
|
1330
1621
|
if (item.content?.pattern) {
|
|
1331
|
-
lines.push(
|
|
1622
|
+
lines.push(`\`\`\`${item.language || 'swift'}`);
|
|
1332
1623
|
lines.push(item.content.pattern);
|
|
1333
1624
|
lines.push('```');
|
|
1334
1625
|
lines.push('');
|
|
@@ -1387,12 +1678,13 @@ export class WikiGenerator {
|
|
|
1387
1678
|
lines.push('```');
|
|
1388
1679
|
lines.push(`${project.name}/`);
|
|
1389
1680
|
if (spm.targets.length > 0) {
|
|
1390
|
-
const mainTargets = spm.targets.filter(t => t.type !== 'test');
|
|
1391
|
-
const testTargets = spm.targets.filter(t => t.type === 'test');
|
|
1681
|
+
const mainTargets = spm.targets.filter((t) => t.type !== 'test');
|
|
1682
|
+
const testTargets = spm.targets.filter((t) => t.type === 'test');
|
|
1392
1683
|
if (mainTargets.length > 0) {
|
|
1393
1684
|
lines.push('├── Sources/');
|
|
1394
1685
|
for (let i = 0; i < mainTargets.length; i++) {
|
|
1395
|
-
const prefix =
|
|
1686
|
+
const prefix =
|
|
1687
|
+
i === mainTargets.length - 1 && testTargets.length === 0 ? '│ └──' : '│ ├──';
|
|
1396
1688
|
lines.push(`${prefix} ${mainTargets[i].name}/`);
|
|
1397
1689
|
}
|
|
1398
1690
|
}
|
|
@@ -1404,8 +1696,12 @@ export class WikiGenerator {
|
|
|
1404
1696
|
}
|
|
1405
1697
|
}
|
|
1406
1698
|
}
|
|
1407
|
-
if (project.hasPackageSwift)
|
|
1408
|
-
|
|
1699
|
+
if (project.hasPackageSwift) {
|
|
1700
|
+
lines.push('├── Package.swift');
|
|
1701
|
+
}
|
|
1702
|
+
if (project.hasPodfile) {
|
|
1703
|
+
lines.push('├── Podfile');
|
|
1704
|
+
}
|
|
1409
1705
|
lines.push('```');
|
|
1410
1706
|
lines.push('');
|
|
1411
1707
|
|
|
@@ -1443,15 +1739,22 @@ export class WikiGenerator {
|
|
|
1443
1739
|
|
|
1444
1740
|
// 模块说明
|
|
1445
1741
|
if (spm.targets.length > 0) {
|
|
1446
|
-
const mainTargets = spm.targets.filter(t => t.type !== 'test');
|
|
1742
|
+
const mainTargets = spm.targets.filter((t) => t.type !== 'test');
|
|
1447
1743
|
if (mainTargets.length > 0) {
|
|
1448
1744
|
lines.push(`## ${isZh ? '核心模块' : 'Core Modules'}`);
|
|
1449
1745
|
lines.push('');
|
|
1450
|
-
lines.push(
|
|
1746
|
+
lines.push(
|
|
1747
|
+
`| ${isZh ? '模块' : 'Module'} | ${isZh ? '类型' : 'Type'} | ${isZh ? '类型数' : 'Types'} | ${isZh ? '说明' : 'Description'} |`
|
|
1748
|
+
);
|
|
1451
1749
|
lines.push('|--------|------|--------|------|');
|
|
1452
1750
|
for (const t of mainTargets) {
|
|
1453
1751
|
const cls = (ast.classNamesByModule?.[t.name] || []).length;
|
|
1454
|
-
const purpose = this._inferModulePurpose(
|
|
1752
|
+
const purpose = this._inferModulePurpose(
|
|
1753
|
+
t.name,
|
|
1754
|
+
ast.classNamesByModule?.[t.name] || [],
|
|
1755
|
+
ast.protocolNamesByModule?.[t.name] || [],
|
|
1756
|
+
[]
|
|
1757
|
+
);
|
|
1455
1758
|
const desc = purpose ? (isZh ? purpose.zh : purpose.en) : '-';
|
|
1456
1759
|
lines.push(`| ${t.name} | ${t.type || 'library'} | ${cls} | ${desc} |`);
|
|
1457
1760
|
}
|
|
@@ -1477,9 +1780,10 @@ export class WikiGenerator {
|
|
|
1477
1780
|
];
|
|
1478
1781
|
|
|
1479
1782
|
// 分类概述
|
|
1480
|
-
lines.push(
|
|
1481
|
-
|
|
1482
|
-
|
|
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.`
|
|
1483
1787
|
);
|
|
1484
1788
|
lines.push('');
|
|
1485
1789
|
|
|
@@ -1499,7 +1803,7 @@ export class WikiGenerator {
|
|
|
1499
1803
|
lines.push('');
|
|
1500
1804
|
}
|
|
1501
1805
|
if (item.content?.pattern) {
|
|
1502
|
-
lines.push(
|
|
1806
|
+
lines.push(`\`\`\`${item.language || 'text'}`);
|
|
1503
1807
|
lines.push(item.content.pattern);
|
|
1504
1808
|
lines.push('```');
|
|
1505
1809
|
lines.push('');
|
|
@@ -1526,9 +1830,10 @@ export class WikiGenerator {
|
|
|
1526
1830
|
'',
|
|
1527
1831
|
];
|
|
1528
1832
|
|
|
1529
|
-
lines.push(
|
|
1530
|
-
|
|
1531
|
-
|
|
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.`
|
|
1532
1837
|
);
|
|
1533
1838
|
lines.push('');
|
|
1534
1839
|
|
|
@@ -1537,12 +1842,15 @@ export class WikiGenerator {
|
|
|
1537
1842
|
const grouped = new Set();
|
|
1538
1843
|
|
|
1539
1844
|
for (const [mod, protos] of Object.entries(protoByModule).sort()) {
|
|
1540
|
-
if (protos.length === 0)
|
|
1845
|
+
if (protos.length === 0) {
|
|
1846
|
+
continue;
|
|
1847
|
+
}
|
|
1541
1848
|
lines.push(`## ${mod}`);
|
|
1542
1849
|
lines.push('');
|
|
1543
|
-
lines.push(
|
|
1544
|
-
|
|
1545
|
-
|
|
1850
|
+
lines.push(
|
|
1851
|
+
isZh
|
|
1852
|
+
? `${mod} 模块定义了 ${protos.length} 个协议:`
|
|
1853
|
+
: `${mod} module defines ${protos.length} protocols:`
|
|
1546
1854
|
);
|
|
1547
1855
|
lines.push('');
|
|
1548
1856
|
for (const p of protos.sort()) {
|
|
@@ -1553,7 +1861,7 @@ export class WikiGenerator {
|
|
|
1553
1861
|
}
|
|
1554
1862
|
|
|
1555
1863
|
// 未分组的协议
|
|
1556
|
-
const ungrouped = ast.protocols.filter(p => !grouped.has(p));
|
|
1864
|
+
const ungrouped = ast.protocols.filter((p) => !grouped.has(p));
|
|
1557
1865
|
if (ungrouped.length > 0) {
|
|
1558
1866
|
lines.push(`## ${isZh ? '其他协议' : 'Other Protocols'}`);
|
|
1559
1867
|
lines.push('');
|
|
@@ -1627,10 +1935,16 @@ export class WikiGenerator {
|
|
|
1627
1935
|
const isZh = this.options.language === 'zh';
|
|
1628
1936
|
|
|
1629
1937
|
// ── Source 1: Channel D devdocs ──
|
|
1630
|
-
const devdocsDir = path.join(
|
|
1938
|
+
const devdocsDir = path.join(
|
|
1939
|
+
this.projectRoot,
|
|
1940
|
+
'.cursor',
|
|
1941
|
+
'skills',
|
|
1942
|
+
'autosnippet-devdocs',
|
|
1943
|
+
'references'
|
|
1944
|
+
);
|
|
1631
1945
|
if (fs.existsSync(devdocsDir)) {
|
|
1632
1946
|
this._ensureDir(path.join(this.wikiDir, 'documents'));
|
|
1633
|
-
const files = fs.readdirSync(devdocsDir).filter(f => f.endsWith('.md'));
|
|
1947
|
+
const files = fs.readdirSync(devdocsDir).filter((f) => f.endsWith('.md'));
|
|
1634
1948
|
for (const file of files) {
|
|
1635
1949
|
try {
|
|
1636
1950
|
const content = fs.readFileSync(path.join(devdocsDir, file), 'utf-8');
|
|
@@ -1638,7 +1952,9 @@ export class WikiGenerator {
|
|
|
1638
1952
|
const result = this._writeFile(`documents/${file}`, header + content);
|
|
1639
1953
|
result.source = 'cursor-devdocs';
|
|
1640
1954
|
synced.push(result);
|
|
1641
|
-
} catch {
|
|
1955
|
+
} catch {
|
|
1956
|
+
/* skip */
|
|
1957
|
+
}
|
|
1642
1958
|
}
|
|
1643
1959
|
}
|
|
1644
1960
|
|
|
@@ -1654,7 +1970,7 @@ export class WikiGenerator {
|
|
|
1654
1970
|
* 为同步目录生成索引页
|
|
1655
1971
|
*/
|
|
1656
1972
|
_generateSyncIndex(synced, isZh) {
|
|
1657
|
-
const docFiles = synced.filter(f => f.path.startsWith('documents/'));
|
|
1973
|
+
const docFiles = synced.filter((f) => f.path.startsWith('documents/'));
|
|
1658
1974
|
|
|
1659
1975
|
if (docFiles.length > 0) {
|
|
1660
1976
|
const lines = [
|
|
@@ -1697,9 +2013,15 @@ export class WikiGenerator {
|
|
|
1697
2013
|
// 完全相同 hash → 移除后来的
|
|
1698
2014
|
if (existing.hash === file.hash) {
|
|
1699
2015
|
const fullPath = path.join(this.wikiDir, file.path);
|
|
1700
|
-
try {
|
|
2016
|
+
try {
|
|
2017
|
+
fs.unlinkSync(fullPath);
|
|
2018
|
+
} catch {
|
|
2019
|
+
/* skip */
|
|
2020
|
+
}
|
|
1701
2021
|
removed.push(file.path);
|
|
1702
|
-
logger.info(
|
|
2022
|
+
logger.info(
|
|
2023
|
+
`[WikiGenerator] Dedup: removed ${file.path} (same hash as ${existing.path})`
|
|
2024
|
+
);
|
|
1703
2025
|
}
|
|
1704
2026
|
// hash 不同 → 保留两个(不同目录允许同名)
|
|
1705
2027
|
} else {
|
|
@@ -1710,19 +2032,28 @@ export class WikiGenerator {
|
|
|
1710
2032
|
// Layer 2: content hash 碰撞(不同文件名但内容相同)
|
|
1711
2033
|
const hashMap = new Map(); // hash → first file path
|
|
1712
2034
|
for (const file of files) {
|
|
1713
|
-
if (removed.includes(file.path))
|
|
2035
|
+
if (removed.includes(file.path)) {
|
|
2036
|
+
continue;
|
|
2037
|
+
}
|
|
1714
2038
|
if (hashMap.has(file.hash)) {
|
|
1715
2039
|
const firstPath = hashMap.get(file.hash);
|
|
1716
2040
|
// 优先保留代码生成的(非 synced)
|
|
1717
2041
|
const isFirstSynced = firstPath.startsWith('documents/') || firstPath.startsWith('skills/');
|
|
1718
|
-
const isCurrentSynced =
|
|
2042
|
+
const isCurrentSynced =
|
|
2043
|
+
file.path.startsWith('documents/') || file.path.startsWith('skills/');
|
|
1719
2044
|
|
|
1720
2045
|
if (isCurrentSynced && !isFirstSynced) {
|
|
1721
2046
|
// 当前是 synced,first 是 codegen → 删除 synced
|
|
1722
2047
|
const fullPath = path.join(this.wikiDir, file.path);
|
|
1723
|
-
try {
|
|
2048
|
+
try {
|
|
2049
|
+
fs.unlinkSync(fullPath);
|
|
2050
|
+
} catch {
|
|
2051
|
+
/* skip */
|
|
2052
|
+
}
|
|
1724
2053
|
removed.push(file.path);
|
|
1725
|
-
logger.info(
|
|
2054
|
+
logger.info(
|
|
2055
|
+
`[WikiGenerator] Dedup: removed synced ${file.path} (same content as ${firstPath})`
|
|
2056
|
+
);
|
|
1726
2057
|
}
|
|
1727
2058
|
// 其他情况保留两个
|
|
1728
2059
|
} else {
|
|
@@ -1750,16 +2081,21 @@ export class WikiGenerator {
|
|
|
1750
2081
|
|
|
1751
2082
|
/** 从 CodeEntityGraph 提取继承根节点 */
|
|
1752
2083
|
_getInheritanceRoots() {
|
|
1753
|
-
if (!this.codeEntityGraph)
|
|
2084
|
+
if (!this.codeEntityGraph) {
|
|
2085
|
+
return [];
|
|
2086
|
+
}
|
|
1754
2087
|
try {
|
|
1755
2088
|
// 尝试查询继承关系
|
|
1756
|
-
const entities =
|
|
2089
|
+
const entities =
|
|
2090
|
+
this.codeEntityGraph.queryEntities?.({ entityType: 'class', limit: 50 }) || [];
|
|
1757
2091
|
const roots = [];
|
|
1758
2092
|
for (const e of entities) {
|
|
1759
|
-
const
|
|
1760
|
-
|
|
2093
|
+
const _parents =
|
|
2094
|
+
this.codeEntityGraph.queryEdges?.({ toId: e.entityId, relation: 'inherits' }) || [];
|
|
2095
|
+
const children =
|
|
2096
|
+
this.codeEntityGraph.queryEdges?.({ fromId: e.entityId, relation: 'inherits' }) || [];
|
|
1761
2097
|
if (children.length > 0) {
|
|
1762
|
-
roots.push({ name: e.name, children: children.map(c => c.toId || c.to_id) });
|
|
2098
|
+
roots.push({ name: e.name, children: children.map((c) => c.toId || c.to_id) });
|
|
1763
2099
|
}
|
|
1764
2100
|
}
|
|
1765
2101
|
return roots.sort((a, b) => (b.children?.length || 0) - (a.children?.length || 0));
|
|
@@ -1771,7 +2107,9 @@ export class WikiGenerator {
|
|
|
1771
2107
|
_emit(phase, progress, message) {
|
|
1772
2108
|
try {
|
|
1773
2109
|
this.onProgress(phase, progress, message);
|
|
1774
|
-
} catch {
|
|
2110
|
+
} catch {
|
|
2111
|
+
/* non-critical */
|
|
2112
|
+
}
|
|
1775
2113
|
}
|
|
1776
2114
|
|
|
1777
2115
|
_ensureDir(dir) {
|
|
@@ -1797,7 +2135,7 @@ export class WikiGenerator {
|
|
|
1797
2135
|
duration: Date.now() - startTime,
|
|
1798
2136
|
projectRoot: this.projectRoot,
|
|
1799
2137
|
language: this.options.language,
|
|
1800
|
-
files: files.map(f => ({
|
|
2138
|
+
files: files.map((f) => ({
|
|
1801
2139
|
path: f.path,
|
|
1802
2140
|
hash: f.hash,
|
|
1803
2141
|
size: f.size,
|
|
@@ -1813,7 +2151,9 @@ export class WikiGenerator {
|
|
|
1813
2151
|
|
|
1814
2152
|
_readMeta() {
|
|
1815
2153
|
try {
|
|
1816
|
-
if (!fs.existsSync(this.metaPath))
|
|
2154
|
+
if (!fs.existsSync(this.metaPath)) {
|
|
2155
|
+
return null;
|
|
2156
|
+
}
|
|
1817
2157
|
return JSON.parse(fs.readFileSync(this.metaPath, 'utf-8'));
|
|
1818
2158
|
} catch {
|
|
1819
2159
|
return null;
|
|
@@ -1822,27 +2162,33 @@ export class WikiGenerator {
|
|
|
1822
2162
|
|
|
1823
2163
|
/** 检测源码是否有变更(简化:对比 sourceHash) */
|
|
1824
2164
|
_detectChanges(meta) {
|
|
1825
|
-
if (!meta?.sourceHash)
|
|
2165
|
+
if (!meta?.sourceHash) {
|
|
2166
|
+
return true;
|
|
2167
|
+
}
|
|
1826
2168
|
return meta.sourceHash !== this._computeSourceHash();
|
|
1827
2169
|
}
|
|
1828
2170
|
|
|
1829
2171
|
/** 计算项目源文件的简易 hash(基于文件名列表 + 总大小) */
|
|
1830
2172
|
_computeSourceHash() {
|
|
1831
2173
|
try {
|
|
1832
|
-
const extSet =
|
|
2174
|
+
const extSet = LanguageService.sourceExts;
|
|
1833
2175
|
let totalSize = 0;
|
|
1834
2176
|
const names = [];
|
|
1835
|
-
this._walkDir(
|
|
1836
|
-
|
|
1837
|
-
|
|
1838
|
-
const
|
|
1839
|
-
|
|
1840
|
-
|
|
1841
|
-
|
|
1842
|
-
|
|
2177
|
+
this._walkDir(
|
|
2178
|
+
this.projectRoot,
|
|
2179
|
+
(filePath) => {
|
|
2180
|
+
const ext = path.extname(filePath);
|
|
2181
|
+
if (extSet.has(ext)) {
|
|
2182
|
+
const stat = fs.statSync(filePath);
|
|
2183
|
+
totalSize += stat.size;
|
|
2184
|
+
names.push(path.relative(this.projectRoot, filePath));
|
|
2185
|
+
}
|
|
2186
|
+
},
|
|
2187
|
+
2000
|
|
2188
|
+
);
|
|
1843
2189
|
|
|
1844
2190
|
names.sort();
|
|
1845
|
-
const payload = names.join('\n')
|
|
2191
|
+
const payload = `${names.join('\n')}\n${totalSize}`;
|
|
1846
2192
|
return createHash('sha256').update(payload).digest('hex').slice(0, 16);
|
|
1847
2193
|
} catch {
|
|
1848
2194
|
return 'unknown';
|
|
@@ -1854,20 +2200,42 @@ export class WikiGenerator {
|
|
|
1854
2200
|
*/
|
|
1855
2201
|
_walkDir(dir, callback, maxFiles = 500) {
|
|
1856
2202
|
const excludeNames = new Set([
|
|
1857
|
-
'Pods',
|
|
1858
|
-
'
|
|
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',
|
|
1859
2215
|
]);
|
|
1860
2216
|
let count = 0;
|
|
1861
2217
|
|
|
1862
2218
|
const walk = (d) => {
|
|
1863
|
-
if (count >= maxFiles)
|
|
2219
|
+
if (count >= maxFiles) {
|
|
2220
|
+
return;
|
|
2221
|
+
}
|
|
1864
2222
|
let entries;
|
|
1865
|
-
try {
|
|
2223
|
+
try {
|
|
2224
|
+
entries = fs.readdirSync(d, { withFileTypes: true });
|
|
2225
|
+
} catch {
|
|
2226
|
+
return;
|
|
2227
|
+
}
|
|
1866
2228
|
|
|
1867
2229
|
for (const entry of entries) {
|
|
1868
|
-
if (count >= maxFiles)
|
|
1869
|
-
|
|
1870
|
-
|
|
2230
|
+
if (count >= maxFiles) {
|
|
2231
|
+
return;
|
|
2232
|
+
}
|
|
2233
|
+
if (excludeNames.has(entry.name)) {
|
|
2234
|
+
continue;
|
|
2235
|
+
}
|
|
2236
|
+
if (entry.name.startsWith('.')) {
|
|
2237
|
+
continue;
|
|
2238
|
+
}
|
|
1871
2239
|
|
|
1872
2240
|
const fullPath = path.join(d, entry.name);
|
|
1873
2241
|
if (entry.isDirectory()) {
|
|
@@ -1909,21 +2277,27 @@ export class WikiGenerator {
|
|
|
1909
2277
|
const name = target.name;
|
|
1910
2278
|
|
|
1911
2279
|
// 1. 按模块名直接匹配(最常见: Sources/{name}/ 解析出的 key)
|
|
1912
|
-
if (sfm[name]?.length > 0)
|
|
2280
|
+
if (sfm[name]?.length > 0) {
|
|
2281
|
+
return sfm[name];
|
|
2282
|
+
}
|
|
1913
2283
|
|
|
1914
2284
|
// 2. 通过 target.path 或 target.info.path 匹配
|
|
1915
2285
|
const targetPath = target.path || target.info?.path;
|
|
1916
2286
|
if (targetPath) {
|
|
1917
|
-
const matched = (projectInfo.sourceFiles || []).filter(
|
|
1918
|
-
f.startsWith(targetPath
|
|
2287
|
+
const matched = (projectInfo.sourceFiles || []).filter(
|
|
2288
|
+
(f) => f.startsWith(`${targetPath}/`) || f.startsWith(targetPath + path.sep)
|
|
1919
2289
|
);
|
|
1920
|
-
if (matched.length > 0)
|
|
2290
|
+
if (matched.length > 0) {
|
|
2291
|
+
return matched;
|
|
2292
|
+
}
|
|
1921
2293
|
}
|
|
1922
2294
|
|
|
1923
2295
|
// 3. 大小写不敏感模糊匹配
|
|
1924
2296
|
const lower = name.toLowerCase();
|
|
1925
2297
|
for (const [key, files] of Object.entries(sfm)) {
|
|
1926
|
-
if (key.toLowerCase() === lower)
|
|
2298
|
+
if (key.toLowerCase() === lower) {
|
|
2299
|
+
return files;
|
|
2300
|
+
}
|
|
1927
2301
|
}
|
|
1928
2302
|
|
|
1929
2303
|
return [];
|