autosnippet 3.3.6 → 3.3.8
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 +1 -0
- package/dashboard/dist/assets/icons-BMNb0V6L.js +1 -0
- package/dashboard/dist/assets/index-DHJ1Dj7u.css +1 -0
- package/dashboard/dist/assets/index-DV8biUkH.js +112 -0
- package/dashboard/dist/index.html +3 -3
- package/dist/bin/cli.js +8 -4
- package/dist/lib/agent/AgentRuntime.d.ts +2 -2
- package/dist/lib/agent/AgentRuntime.js +26 -18
- package/dist/lib/agent/core/ChatAgentPrompts.js +57 -21
- package/dist/lib/agent/core/LoopContext.d.ts +1 -0
- package/dist/lib/agent/core/ToolExecutionPipeline.js +13 -0
- package/dist/lib/agent/domain/ChatAgentTasks.js +4 -0
- package/dist/lib/agent/forced-summary.js +7 -2
- package/dist/lib/agent/memory/ActiveContext.d.ts +0 -2
- package/dist/lib/agent/memory/ActiveContext.js +0 -2
- package/dist/lib/agent/memory/MemoryEmbeddingStore.d.ts +49 -0
- package/dist/lib/agent/memory/MemoryEmbeddingStore.js +159 -0
- package/dist/lib/agent/memory/MemoryRetriever.d.ts +2 -0
- package/dist/lib/agent/memory/MemoryRetriever.js +25 -11
- package/dist/lib/agent/memory/MemoryStore.d.ts +8 -41
- package/dist/lib/agent/memory/MemoryStore.js +196 -261
- package/dist/lib/agent/memory/PersistentMemory.d.ts +2 -0
- package/dist/lib/agent/memory/PersistentMemory.js +4 -5
- package/dist/lib/agent/memory/SessionStore.d.ts +0 -2
- package/dist/lib/agent/memory/SessionStore.js +0 -2
- package/dist/lib/agent/tools/ast-graph.js +21 -19
- package/dist/lib/agent/tools/infrastructure.js +3 -2
- package/dist/lib/agent/tools/project-access.d.ts +2 -2
- package/dist/lib/agent/tools/project-access.js +5 -4
- package/dist/lib/bootstrap.js +2 -1
- package/dist/lib/cli/AiScanService.js +8 -21
- package/dist/lib/cli/KnowledgeSyncService.d.ts +7 -37
- package/dist/lib/cli/KnowledgeSyncService.js +23 -51
- package/dist/lib/core/ast/ProjectGraph.js +5 -27
- package/dist/lib/core/discovery/ConfigWatcher.d.ts +64 -0
- package/dist/lib/core/discovery/ConfigWatcher.js +336 -0
- package/dist/lib/core/discovery/CustomConfigDiscoverer.d.ts +28 -0
- package/dist/lib/core/discovery/CustomConfigDiscoverer.js +1303 -0
- package/dist/lib/core/discovery/DiscovererPreference.d.ts +44 -0
- package/dist/lib/core/discovery/DiscovererPreference.js +141 -0
- package/dist/lib/core/discovery/DiscovererRegistry.d.ts +10 -1
- package/dist/lib/core/discovery/DiscovererRegistry.js +42 -2
- package/dist/lib/core/discovery/ProjectDiscoverer.d.ts +19 -0
- package/dist/lib/core/discovery/index.d.ts +2 -0
- package/dist/lib/core/discovery/index.js +4 -0
- package/dist/lib/core/discovery/parsers/CMakeParser.d.ts +32 -0
- package/dist/lib/core/discovery/parsers/CMakeParser.js +148 -0
- package/dist/lib/core/discovery/parsers/GradleDslParser.d.ts +43 -0
- package/dist/lib/core/discovery/parsers/GradleDslParser.js +171 -0
- package/dist/lib/core/discovery/parsers/JsonConfigParser.d.ts +45 -0
- package/dist/lib/core/discovery/parsers/JsonConfigParser.js +122 -0
- package/dist/lib/core/discovery/parsers/RubyDslParser.d.ts +49 -0
- package/dist/lib/core/discovery/parsers/RubyDslParser.js +282 -0
- package/dist/lib/core/discovery/parsers/StarlarkParser.d.ts +33 -0
- package/dist/lib/core/discovery/parsers/StarlarkParser.js +229 -0
- package/dist/lib/core/discovery/parsers/YamlConfigParser.d.ts +37 -0
- package/dist/lib/core/discovery/parsers/YamlConfigParser.js +212 -0
- package/dist/lib/domain/dimension/DimensionRegistry.d.ts +0 -2
- package/dist/lib/domain/dimension/DimensionRegistry.js +0 -2
- package/dist/lib/domain/dimension/DimensionSop.js +44 -33
- package/dist/lib/domain/dimension/UnifiedDimension.d.ts +0 -2
- package/dist/lib/domain/dimension/UnifiedDimension.js +0 -2
- package/dist/lib/domain/knowledge/KnowledgeEntry.d.ts +7 -1
- package/dist/lib/domain/knowledge/KnowledgeEntry.js +17 -3
- package/dist/lib/domain/knowledge/Lifecycle.d.ts +26 -0
- package/dist/lib/domain/knowledge/Lifecycle.js +42 -0
- package/dist/lib/domain/knowledge/index.d.ts +2 -1
- package/dist/lib/domain/knowledge/index.js +1 -1
- package/dist/lib/external/ai/AiProvider.d.ts +12 -0
- package/dist/lib/external/ai/AiProvider.js +24 -0
- package/dist/lib/external/ai/AiProviderManager.d.ts +101 -0
- package/dist/lib/external/ai/AiProviderManager.js +193 -0
- package/dist/lib/external/ai/providers/ClaudeProvider.js +11 -0
- package/dist/lib/external/ai/providers/GoogleGeminiProvider.js +18 -0
- package/dist/lib/external/ai/providers/MockProvider.d.ts +21 -3
- package/dist/lib/external/ai/providers/MockProvider.js +290 -14
- package/dist/lib/external/ai/providers/OpenAiProvider.js +16 -0
- package/dist/lib/external/lark/LarkTransport.d.ts +5 -1
- package/dist/lib/external/lark/LarkTransport.js +10 -2
- package/dist/lib/external/mcp/handlers/bootstrap/pipeline/BootstrapSnapshot.d.ts +2 -1
- package/dist/lib/external/mcp/handlers/bootstrap/pipeline/BootstrapSnapshot.js +102 -153
- package/dist/lib/external/mcp/handlers/bootstrap/pipeline/mock-pipeline.d.ts +20 -0
- package/dist/lib/external/mcp/handlers/bootstrap/pipeline/mock-pipeline.js +432 -0
- package/dist/lib/external/mcp/handlers/bootstrap/pipeline/orchestrator.js +49 -24
- package/dist/lib/external/mcp/handlers/bootstrap/refine.js +8 -0
- package/dist/lib/external/mcp/handlers/bootstrap/shared/bootstrap-phases.d.ts +1 -1
- package/dist/lib/external/mcp/handlers/bootstrap/shared/bootstrap-phases.js +41 -37
- package/dist/lib/external/mcp/handlers/bootstrap-external.d.ts +9 -0
- package/dist/lib/external/mcp/handlers/bootstrap-external.js +3 -1
- package/dist/lib/external/mcp/handlers/bootstrap-internal.js +2 -0
- package/dist/lib/external/mcp/handlers/consolidated.js +2 -1
- package/dist/lib/external/mcp/handlers/dimension-complete-external.js +9 -4
- package/dist/lib/external/mcp/handlers/evolve-external.d.ts +1 -0
- package/dist/lib/external/mcp/handlers/evolve-external.js +18 -18
- package/dist/lib/external/mcp/handlers/guard.js +15 -24
- package/dist/lib/external/mcp/handlers/knowledge.js +5 -4
- package/dist/lib/external/mcp/handlers/panorama.js +9 -9
- package/dist/lib/external/mcp/handlers/rescan-external.js +7 -6
- package/dist/lib/external/mcp/handlers/rescan-internal.js +9 -5
- package/dist/lib/external/mcp/handlers/search.js +3 -1
- package/dist/lib/external/mcp/handlers/skill.js +4 -4
- package/dist/lib/external/mcp/handlers/structure.js +8 -12
- package/dist/lib/external/mcp/handlers/system.js +10 -34
- package/dist/lib/http/routes/ai.js +109 -30
- package/dist/lib/http/routes/candidates.js +11 -4
- package/dist/lib/http/routes/commands.js +10 -1
- package/dist/lib/http/routes/guardReport.js +3 -5
- package/dist/lib/http/routes/health.js +11 -0
- package/dist/lib/http/routes/modules.js +27 -0
- package/dist/lib/http/routes/panorama.js +12 -12
- package/dist/lib/http/routes/recipes.js +66 -8
- package/dist/lib/http/routes/remote.js +3 -13
- package/dist/lib/http/routes/search.js +11 -8
- package/dist/lib/http/utils/routeHelpers.js +2 -1
- package/dist/lib/infrastructure/audit/AuditLogger.d.ts +20 -3
- package/dist/lib/infrastructure/audit/AuditStore.d.ts +28 -29
- package/dist/lib/infrastructure/audit/AuditStore.js +81 -88
- package/dist/lib/infrastructure/database/drizzle/schema.d.ts +180 -2
- package/dist/lib/infrastructure/database/drizzle/schema.js +23 -3
- package/dist/lib/injection/ServiceContainer.d.ts +6 -5
- package/dist/lib/injection/ServiceContainer.js +18 -31
- package/dist/lib/injection/ServiceMap.d.ts +22 -0
- package/dist/lib/injection/modules/AiModule.d.ts +6 -9
- package/dist/lib/injection/modules/AiModule.js +82 -39
- package/dist/lib/injection/modules/AppModule.js +2 -1
- package/dist/lib/injection/modules/GuardModule.js +5 -5
- package/dist/lib/injection/modules/InfraModule.js +60 -0
- package/dist/lib/injection/modules/KnowledgeModule.js +86 -51
- package/dist/lib/injection/modules/PanoramaModule.js +16 -10
- package/dist/lib/injection/modules/VectorModule.js +3 -0
- package/dist/lib/repository/audit/AuditRepository.d.ts +107 -0
- package/dist/lib/repository/audit/AuditRepository.js +272 -0
- package/dist/lib/repository/base/RepositoryBase.d.ts +46 -0
- package/dist/lib/repository/base/RepositoryBase.js +32 -0
- package/dist/lib/repository/bootstrap/BootstrapRepository.d.ts +94 -0
- package/dist/lib/repository/bootstrap/BootstrapRepository.js +246 -0
- package/dist/lib/repository/code/CodeEntityRepository.d.ts +91 -0
- package/dist/lib/repository/code/CodeEntityRepository.js +361 -0
- package/dist/lib/repository/delivery/DeliveryRepoAdapter.d.ts +39 -0
- package/dist/lib/repository/delivery/DeliveryRepoAdapter.js +23 -0
- package/dist/lib/repository/evolution/LifecycleEventRepository.d.ts +51 -0
- package/dist/lib/repository/evolution/LifecycleEventRepository.js +119 -0
- package/dist/lib/repository/evolution/ProposalRepository.d.ts +9 -12
- package/dist/lib/repository/evolution/ProposalRepository.js +114 -57
- package/dist/lib/repository/guard/GuardViolationRepository.d.ts +104 -0
- package/dist/lib/repository/guard/GuardViolationRepository.js +217 -0
- package/dist/lib/repository/knowledge/KnowledgeEdgeRepository.d.ts +129 -0
- package/dist/lib/repository/knowledge/KnowledgeEdgeRepository.js +475 -0
- package/dist/lib/repository/knowledge/KnowledgeFileStore.d.ts +39 -0
- package/dist/lib/repository/knowledge/KnowledgeFileStore.js +12 -0
- package/dist/lib/repository/knowledge/KnowledgeRepository.impl.d.ts +295 -11
- package/dist/lib/repository/knowledge/KnowledgeRepository.impl.js +608 -13
- package/dist/lib/repository/knowledge/KnowledgeUnitOfWork.d.ts +61 -0
- package/dist/lib/repository/knowledge/KnowledgeUnitOfWork.js +156 -0
- package/dist/lib/repository/memory/MemoryRepository.d.ts +90 -0
- package/dist/lib/repository/memory/MemoryRepository.js +260 -0
- package/dist/lib/repository/search/SearchRepoAdapter.d.ts +92 -0
- package/dist/lib/repository/search/SearchRepoAdapter.js +124 -0
- package/dist/lib/repository/session/SessionRepository.d.ts +46 -0
- package/dist/lib/repository/session/SessionRepository.js +110 -0
- package/dist/lib/repository/sourceref/RecipeSourceRefRepository.d.ts +66 -0
- package/dist/lib/repository/sourceref/RecipeSourceRefRepository.js +182 -0
- package/dist/lib/repository/sync/SyncRepoAdapter.d.ts +58 -0
- package/dist/lib/repository/sync/SyncRepoAdapter.js +58 -0
- package/dist/lib/service/bootstrap/UiStartupTasks.js +5 -6
- package/dist/lib/service/bootstrap/bootstrap-event-types.d.ts +0 -1
- package/dist/lib/service/bootstrap/bootstrap-event-types.js +0 -1
- package/dist/lib/service/cleanup/CleanupService.d.ts +54 -7
- package/dist/lib/service/cleanup/CleanupService.js +291 -40
- package/dist/lib/service/delivery/CursorDeliveryPipeline.js +6 -8
- package/dist/lib/service/evolution/ConsolidationAdvisor.d.ts +4 -9
- package/dist/lib/service/evolution/ConsolidationAdvisor.js +34 -70
- package/dist/lib/service/evolution/ContentPatcher.d.ts +4 -12
- package/dist/lib/service/evolution/ContentPatcher.js +48 -19
- package/dist/lib/service/evolution/ContradictionDetector.d.ts +3 -7
- package/dist/lib/service/evolution/ContradictionDetector.js +17 -24
- package/dist/lib/service/evolution/DecayDetector.d.ts +10 -9
- package/dist/lib/service/evolution/DecayDetector.js +63 -57
- package/dist/lib/service/evolution/EnhancementSuggester.d.ts +3 -9
- package/dist/lib/service/evolution/EnhancementSuggester.js +42 -86
- package/dist/lib/service/evolution/KnowledgeMetabolism.d.ts +4 -4
- package/dist/lib/service/evolution/KnowledgeMetabolism.js +102 -71
- package/dist/lib/service/evolution/ProposalExecutor.d.ts +5 -12
- package/dist/lib/service/evolution/ProposalExecutor.js +64 -69
- package/dist/lib/service/evolution/RecipeLifecycleSupervisor.d.ts +9 -14
- package/dist/lib/service/evolution/RecipeLifecycleSupervisor.js +94 -155
- package/dist/lib/service/evolution/RecipeRelevanceAuditor.d.ts +4 -1
- package/dist/lib/service/evolution/RecipeRelevanceAuditor.js +50 -49
- package/dist/lib/service/evolution/RedundancyAnalyzer.d.ts +3 -7
- package/dist/lib/service/evolution/RedundancyAnalyzer.js +15 -22
- package/dist/lib/service/evolution/StagingManager.d.ts +6 -15
- package/dist/lib/service/evolution/StagingManager.js +37 -95
- package/dist/lib/service/evolution/createSupersedeProposal.d.ts +1 -1
- package/dist/lib/service/evolution/createSupersedeProposal.js +7 -8
- package/dist/lib/service/guard/CoverageAnalyzer.d.ts +3 -7
- package/dist/lib/service/guard/CoverageAnalyzer.js +9 -11
- package/dist/lib/service/guard/GuardCheckEngine.d.ts +3 -0
- package/dist/lib/service/guard/GuardCheckEngine.js +14 -22
- package/dist/lib/service/guard/ReverseGuard.d.ts +4 -7
- package/dist/lib/service/guard/ReverseGuard.js +21 -31
- package/dist/lib/service/guard/ViolationsStore.d.ts +15 -21
- package/dist/lib/service/guard/ViolationsStore.js +75 -69
- package/dist/lib/service/knowledge/CodeEntityGraph.d.ts +45 -63
- package/dist/lib/service/knowledge/CodeEntityGraph.js +418 -496
- package/dist/lib/service/knowledge/ConfidenceRouter.js +18 -9
- package/dist/lib/service/knowledge/KnowledgeFileWriter.d.ts +2 -1
- package/dist/lib/service/knowledge/KnowledgeGraphService.d.ts +18 -60
- package/dist/lib/service/knowledge/KnowledgeGraphService.js +58 -109
- package/dist/lib/service/knowledge/KnowledgeService.d.ts +15 -1
- package/dist/lib/service/knowledge/KnowledgeService.js +97 -46
- package/dist/lib/service/knowledge/RecipeProductionGateway.d.ts +0 -2
- package/dist/lib/service/knowledge/RecipeProductionGateway.js +0 -2
- package/dist/lib/service/knowledge/SourceRefReconciler.d.ts +5 -13
- package/dist/lib/service/knowledge/SourceRefReconciler.js +58 -78
- package/dist/lib/service/module/ModuleService.js +10 -19
- package/dist/lib/service/panorama/CouplingAnalyzer.d.ts +14 -3
- package/dist/lib/service/panorama/CouplingAnalyzer.js +137 -32
- package/dist/lib/service/panorama/DimensionAnalyzer.d.ts +7 -4
- package/dist/lib/service/panorama/DimensionAnalyzer.js +94 -33
- package/dist/lib/service/panorama/LayerInferrer.d.ts +16 -1
- package/dist/lib/service/panorama/LayerInferrer.js +118 -1
- package/dist/lib/service/panorama/ModuleDiscoverer.d.ts +14 -4
- package/dist/lib/service/panorama/ModuleDiscoverer.js +209 -61
- package/dist/lib/service/panorama/PanoramaAggregator.d.ts +15 -4
- package/dist/lib/service/panorama/PanoramaAggregator.js +128 -62
- package/dist/lib/service/panorama/PanoramaScanner.d.ts +5 -1
- package/dist/lib/service/panorama/PanoramaScanner.js +60 -31
- package/dist/lib/service/panorama/PanoramaService.d.ts +11 -8
- package/dist/lib/service/panorama/PanoramaService.js +49 -69
- package/dist/lib/service/panorama/PanoramaTypes.d.ts +41 -0
- package/dist/lib/service/panorama/RoleRefiner.d.ts +10 -5
- package/dist/lib/service/panorama/RoleRefiner.js +92 -282
- package/dist/lib/service/panorama/TechStackProfiler.d.ts +13 -0
- package/dist/lib/service/panorama/TechStackProfiler.js +79 -0
- package/dist/lib/service/quality/QualityScorer.d.ts +45 -26
- package/dist/lib/service/quality/QualityScorer.js +157 -83
- package/dist/lib/service/search/SearchEngine.d.ts +1 -0
- package/dist/lib/service/search/SearchEngine.js +32 -37
- package/dist/lib/service/signal/HitRecorder.js +5 -5
- package/dist/lib/service/skills/RuleRecallStrategy.js +7 -3
- package/dist/lib/service/skills/SignalCollector.d.ts +6 -8
- package/dist/lib/service/skills/SignalCollector.js +34 -60
- package/dist/lib/service/skills/SkillAdvisor.d.ts +7 -13
- package/dist/lib/service/skills/SkillAdvisor.js +30 -79
- package/dist/lib/service/vector/ContextualEnricher.d.ts +1 -0
- package/dist/lib/service/vector/ContextualEnricher.js +4 -0
- package/dist/lib/service/vector/SyncCoordinator.d.ts +3 -1
- package/dist/lib/service/vector/SyncCoordinator.js +25 -3
- package/dist/lib/service/vector/VectorService.d.ts +2 -0
- package/dist/lib/service/vector/VectorService.js +3 -0
- package/dist/lib/service/wiki/WikiGenerator.js +1 -1
- package/dist/lib/shared/LanguageProfiles.d.ts +109 -0
- package/dist/lib/shared/LanguageProfiles.js +939 -0
- package/dist/lib/shared/LanguageService.d.ts +6 -0
- package/dist/lib/shared/LanguageService.js +19 -0
- package/dist/lib/shared/constants.d.ts +19 -19
- package/dist/lib/shared/constants.js +10 -10
- package/dist/lib/shared/developer-identity.d.ts +18 -0
- package/dist/lib/shared/developer-identity.js +62 -0
- package/dist/lib/shared/schemas/http-requests.d.ts +8 -17
- package/dist/lib/shared/schemas/http-requests.js +9 -6
- package/dist/lib/shared/schemas/mcp-tools.d.ts +1 -1
- package/dist/lib/types/knowledge-wire.d.ts +1 -0
- package/dist/lib/types/project-snapshot-builder.d.ts +0 -1
- package/dist/lib/types/project-snapshot-builder.js +0 -1
- package/dist/lib/types/project-snapshot.d.ts +0 -1
- package/dist/lib/types/project-snapshot.js +0 -1
- package/dist/lib/types/snapshot-views.d.ts +0 -2
- package/dist/lib/types/snapshot-views.js +0 -1
- package/package.json +2 -1
- package/dashboard/dist/assets/icons-D1aVZYFW.js +0 -1
- package/dashboard/dist/assets/index-CxHOu8Hd.css +0 -1
- package/dashboard/dist/assets/index-DDdAOpYT.js +0 -128
- package/dist/lib/repository/base/BaseRepository.d.ts +0 -53
- package/dist/lib/repository/base/BaseRepository.js +0 -226
|
@@ -6,22 +6,33 @@
|
|
|
6
6
|
*
|
|
7
7
|
* @module CouplingAnalyzer
|
|
8
8
|
*/
|
|
9
|
-
import type {
|
|
9
|
+
import type { CodeEntityRepositoryImpl } from '../../repository/code/CodeEntityRepository.js';
|
|
10
|
+
import type { KnowledgeEdgeRepositoryImpl } from '../../repository/knowledge/KnowledgeEdgeRepository.js';
|
|
11
|
+
import type { CyclicDependency, Edge } from './PanoramaTypes.js';
|
|
10
12
|
export interface CouplingMetrics {
|
|
11
13
|
fanIn: number;
|
|
12
14
|
fanOut: number;
|
|
13
15
|
}
|
|
16
|
+
export interface ExternalDepMetrics {
|
|
17
|
+
name: string;
|
|
18
|
+
fanIn: number;
|
|
19
|
+
/** 依赖此外部库的本地模块列表 */
|
|
20
|
+
dependedBy: string[];
|
|
21
|
+
}
|
|
14
22
|
export interface CouplingResult {
|
|
15
23
|
cycles: CyclicDependency[];
|
|
16
24
|
metrics: Map<string, CouplingMetrics>;
|
|
17
25
|
edges: Edge[];
|
|
26
|
+
/** 外部依赖 fan-in 统计(按 fan-in 降序排列) */
|
|
27
|
+
externalDeps: ExternalDepMetrics[];
|
|
18
28
|
}
|
|
19
29
|
export declare class CouplingAnalyzer {
|
|
20
30
|
#private;
|
|
21
|
-
constructor(
|
|
31
|
+
constructor(edgeRepo: KnowledgeEdgeRepositoryImpl, entityRepo: CodeEntityRepositoryImpl, projectRoot: string);
|
|
22
32
|
/**
|
|
23
33
|
* 分析模块间耦合关系
|
|
24
34
|
* @param moduleFiles - Map<moduleName, filePaths[]>
|
|
35
|
+
* @param externalModules - 外部模块名集合(无源码但参与依赖图)
|
|
25
36
|
*/
|
|
26
|
-
analyze(moduleFiles: Map<string, string[]>): CouplingResult
|
|
37
|
+
analyze(moduleFiles: Map<string, string[]>, externalModules?: Set<string>): Promise<CouplingResult>;
|
|
27
38
|
}
|
|
@@ -6,6 +6,8 @@
|
|
|
6
6
|
*
|
|
7
7
|
* @module CouplingAnalyzer
|
|
8
8
|
*/
|
|
9
|
+
import { readFileSync } from 'node:fs';
|
|
10
|
+
import { LanguageProfiles } from '#shared/LanguageProfiles.js';
|
|
9
11
|
/* ═══ Edge Weights ════════════════════════════════════════ */
|
|
10
12
|
const EDGE_WEIGHTS = {
|
|
11
13
|
depends_on: 0.5,
|
|
@@ -14,17 +16,20 @@ const EDGE_WEIGHTS = {
|
|
|
14
16
|
};
|
|
15
17
|
/* ═══ CouplingAnalyzer Class ══════════════════════════════ */
|
|
16
18
|
export class CouplingAnalyzer {
|
|
17
|
-
#
|
|
19
|
+
#edgeRepo;
|
|
20
|
+
#entityRepo;
|
|
18
21
|
#projectRoot;
|
|
19
|
-
constructor(
|
|
20
|
-
this.#
|
|
22
|
+
constructor(edgeRepo, entityRepo, projectRoot) {
|
|
23
|
+
this.#edgeRepo = edgeRepo;
|
|
24
|
+
this.#entityRepo = entityRepo;
|
|
21
25
|
this.#projectRoot = projectRoot;
|
|
22
26
|
}
|
|
23
27
|
/**
|
|
24
28
|
* 分析模块间耦合关系
|
|
25
29
|
* @param moduleFiles - Map<moduleName, filePaths[]>
|
|
30
|
+
* @param externalModules - 外部模块名集合(无源码但参与依赖图)
|
|
26
31
|
*/
|
|
27
|
-
analyze(moduleFiles) {
|
|
32
|
+
async analyze(moduleFiles, externalModules) {
|
|
28
33
|
// 1. 构建 file → module 反向索引
|
|
29
34
|
const fileToModule = new Map();
|
|
30
35
|
for (const [mod, files] of moduleFiles) {
|
|
@@ -33,7 +38,7 @@ export class CouplingAnalyzer {
|
|
|
33
38
|
}
|
|
34
39
|
}
|
|
35
40
|
// 2. 从 knowledge_edges 聚合模块间边
|
|
36
|
-
const edges = this.#buildModuleEdges(moduleFiles, fileToModule);
|
|
41
|
+
const edges = await this.#buildModuleEdges(moduleFiles, fileToModule);
|
|
37
42
|
// 3. 建图
|
|
38
43
|
const adjacency = new Map();
|
|
39
44
|
const allModules = new Set(moduleFiles.keys());
|
|
@@ -66,61 +71,56 @@ export class CouplingAnalyzer {
|
|
|
66
71
|
toM.fanIn++;
|
|
67
72
|
}
|
|
68
73
|
}
|
|
74
|
+
// 5.5 外部依赖 fan-in 统计
|
|
75
|
+
const externalDeps = await this.#computeExternalFanIn(moduleFiles, externalModules);
|
|
69
76
|
// 去重边 (同 from→to 聚合)
|
|
70
77
|
const dedupEdges = this.#deduplicateEdges(edges);
|
|
71
|
-
return { cycles, metrics, edges: dedupEdges };
|
|
78
|
+
return { cycles, metrics, edges: dedupEdges, externalDeps };
|
|
72
79
|
}
|
|
73
80
|
/* ─── Internal helpers ──────────────────────────── */
|
|
74
|
-
#buildModuleEdges(moduleFiles, fileToModule) {
|
|
81
|
+
async #buildModuleEdges(moduleFiles, fileToModule) {
|
|
75
82
|
const edges = [];
|
|
76
83
|
const relations = ['depends_on', 'calls', 'data_flow'];
|
|
84
|
+
// 跟踪哪些模块有 DB 边(用于判断是否需要 import 推断)
|
|
85
|
+
const modulesWithDbEdges = new Set();
|
|
77
86
|
for (const relation of relations) {
|
|
78
87
|
const weight = EDGE_WEIGHTS[relation] ?? 0.5;
|
|
79
88
|
// 查询该类型的边(仅限当前项目:至少 from 侧实体属于本项目)
|
|
80
|
-
const rows = this.#
|
|
81
|
-
.prepare(`SELECT ke.from_id, ke.from_type, ke.to_id, ke.to_type
|
|
82
|
-
FROM knowledge_edges ke
|
|
83
|
-
WHERE ke.relation = ?
|
|
84
|
-
AND (
|
|
85
|
-
ke.from_type = 'module'
|
|
86
|
-
OR EXISTS (
|
|
87
|
-
SELECT 1 FROM code_entities ce
|
|
88
|
-
WHERE ce.entity_id = ke.from_id AND ce.project_root = ?
|
|
89
|
-
)
|
|
90
|
-
)`)
|
|
91
|
-
.all(relation, this.#projectRoot);
|
|
89
|
+
const rows = await this.#edgeRepo.findEdgesFilteredByEntityExistence(relation, this.#projectRoot);
|
|
92
90
|
for (const row of rows) {
|
|
93
|
-
const fromId = row.
|
|
94
|
-
const toId = row.
|
|
95
|
-
const fromType = row.
|
|
96
|
-
const toType = row.
|
|
91
|
+
const fromId = row.fromId;
|
|
92
|
+
const toId = row.toId;
|
|
93
|
+
const fromType = row.fromType;
|
|
94
|
+
const toType = row.toType;
|
|
97
95
|
// module-to-module 直接边 (depends_on)
|
|
98
96
|
if (fromType === 'module' && toType === 'module') {
|
|
99
97
|
if (fromId !== toId && moduleFiles.has(fromId) && moduleFiles.has(toId)) {
|
|
100
98
|
edges.push({ from: fromId, to: toId, weight, relation });
|
|
99
|
+
modulesWithDbEdges.add(fromId);
|
|
101
100
|
}
|
|
102
101
|
continue;
|
|
103
102
|
}
|
|
104
103
|
// entity-to-entity 边 → 解析 file → module
|
|
105
|
-
const fromModule = this.#resolveEntityModule(fromId, fromType, fileToModule);
|
|
106
|
-
const toModule = this.#resolveEntityModule(toId, toType, fileToModule);
|
|
104
|
+
const fromModule = await this.#resolveEntityModule(fromId, fromType, fileToModule);
|
|
105
|
+
const toModule = await this.#resolveEntityModule(toId, toType, fileToModule);
|
|
107
106
|
if (fromModule && toModule && fromModule !== toModule) {
|
|
108
107
|
edges.push({ from: fromModule, to: toModule, weight, relation });
|
|
108
|
+
modulesWithDbEdges.add(fromModule);
|
|
109
109
|
}
|
|
110
110
|
}
|
|
111
111
|
}
|
|
112
|
+
// 对没有 DB 边的模块,通过 import 扫描推断依赖
|
|
113
|
+
const importEdges = this.#inferEdgesFromImports(moduleFiles, modulesWithDbEdges);
|
|
114
|
+
edges.push(...importEdges);
|
|
112
115
|
return edges;
|
|
113
116
|
}
|
|
114
|
-
#resolveEntityModule(entityId, _entityType, fileToModule) {
|
|
117
|
+
async #resolveEntityModule(entityId, _entityType, fileToModule) {
|
|
115
118
|
// 先查实体所在文件
|
|
116
|
-
const
|
|
117
|
-
|
|
118
|
-
WHERE entity_id = ? AND project_root = ? LIMIT 1`)
|
|
119
|
-
.get(entityId, this.#projectRoot);
|
|
120
|
-
if (!row?.file_path) {
|
|
119
|
+
const entity = await this.#entityRepo.findByEntityIdOnly(entityId, this.#projectRoot);
|
|
120
|
+
if (!entity?.filePath) {
|
|
121
121
|
return null;
|
|
122
122
|
}
|
|
123
|
-
return fileToModule.get(
|
|
123
|
+
return fileToModule.get(entity.filePath) ?? null;
|
|
124
124
|
}
|
|
125
125
|
/**
|
|
126
126
|
* Tarjan 强连通分量算法
|
|
@@ -174,6 +174,111 @@ export class CouplingAnalyzer {
|
|
|
174
174
|
severity: cycle.length > 3 ? 'error' : 'warning',
|
|
175
175
|
}));
|
|
176
176
|
}
|
|
177
|
+
/**
|
|
178
|
+
* 统计外部依赖的 fan-in(被多少本地模块依赖)
|
|
179
|
+
* 数据来源:knowledge_edges 中 from_type='module' AND to_type='module' 且 to 不在 moduleFiles 中
|
|
180
|
+
*/
|
|
181
|
+
async #computeExternalFanIn(moduleFiles, externalModules) {
|
|
182
|
+
const fanInMap = new Map();
|
|
183
|
+
// 从 DB 查询 module-to-module depends_on 边
|
|
184
|
+
const rows = await this.#edgeRepo.findModuleDependencyPairs();
|
|
185
|
+
for (const row of rows) {
|
|
186
|
+
const fromId = row.fromId;
|
|
187
|
+
const toId = row.toId;
|
|
188
|
+
// from 必须是本地模块, to 必须是外部模块
|
|
189
|
+
if (!moduleFiles.has(fromId)) {
|
|
190
|
+
continue;
|
|
191
|
+
}
|
|
192
|
+
if (moduleFiles.has(toId)) {
|
|
193
|
+
continue;
|
|
194
|
+
}
|
|
195
|
+
// 如果提供了 externalModules 集合,检查 toId 是否在其中
|
|
196
|
+
if (externalModules && !externalModules.has(toId)) {
|
|
197
|
+
continue;
|
|
198
|
+
}
|
|
199
|
+
if (!fanInMap.has(toId)) {
|
|
200
|
+
fanInMap.set(toId, new Set());
|
|
201
|
+
}
|
|
202
|
+
fanInMap.get(toId).add(fromId);
|
|
203
|
+
}
|
|
204
|
+
// 转换为排序数组(按 fan-in 降序)
|
|
205
|
+
return [...fanInMap.entries()]
|
|
206
|
+
.map(([name, deps]) => ({
|
|
207
|
+
name,
|
|
208
|
+
fanIn: deps.size,
|
|
209
|
+
dependedBy: [...deps].sort(),
|
|
210
|
+
}))
|
|
211
|
+
.sort((a, b) => b.fanIn - a.fanIn);
|
|
212
|
+
}
|
|
213
|
+
/**
|
|
214
|
+
* 对无 DB 边的模块,扫描源文件 import 语句推断依赖。
|
|
215
|
+
* 典型场景:iOS 宿主应用中未声明在 Boxfile 的子模块。
|
|
216
|
+
*/
|
|
217
|
+
#inferEdgesFromImports(moduleFiles, modulesWithDbEdges) {
|
|
218
|
+
const sourceExts = LanguageProfiles.sourceExts;
|
|
219
|
+
const importPatterns = LanguageProfiles.importPatterns;
|
|
220
|
+
const MAX_READ_BYTES = 8192; // 只读文件前 8KB(import 几乎总在文件头部)
|
|
221
|
+
const edges = [];
|
|
222
|
+
// 建立已知模块名集合(用于快速匹配 import 目标)
|
|
223
|
+
const knownModules = new Set(moduleFiles.keys());
|
|
224
|
+
for (const [modName, files] of moduleFiles) {
|
|
225
|
+
// 跳过已有 DB 边的模块——它们的依赖已由 Boxfile/配置声明
|
|
226
|
+
if (modulesWithDbEdges.has(modName)) {
|
|
227
|
+
continue;
|
|
228
|
+
}
|
|
229
|
+
const importedModules = new Set();
|
|
230
|
+
for (const filePath of files) {
|
|
231
|
+
// 只扫描源码文件
|
|
232
|
+
const dotIdx = filePath.lastIndexOf('.');
|
|
233
|
+
if (dotIdx < 0) {
|
|
234
|
+
continue;
|
|
235
|
+
}
|
|
236
|
+
const ext = filePath.slice(dotIdx).toLowerCase();
|
|
237
|
+
if (!sourceExts.has(ext)) {
|
|
238
|
+
continue;
|
|
239
|
+
}
|
|
240
|
+
let content;
|
|
241
|
+
try {
|
|
242
|
+
const fd = readFileSync(filePath, { flag: 'r' });
|
|
243
|
+
content =
|
|
244
|
+
fd.length > MAX_READ_BYTES
|
|
245
|
+
? fd.subarray(0, MAX_READ_BYTES).toString('utf8')
|
|
246
|
+
: fd.toString('utf8');
|
|
247
|
+
}
|
|
248
|
+
catch {
|
|
249
|
+
continue; // 文件不可读则跳过
|
|
250
|
+
}
|
|
251
|
+
// 逐行匹配 import 模式
|
|
252
|
+
const lines = content.split('\n');
|
|
253
|
+
for (const line of lines) {
|
|
254
|
+
const trimmed = line.trim();
|
|
255
|
+
for (const pattern of importPatterns) {
|
|
256
|
+
const m = pattern.regex.exec(trimmed);
|
|
257
|
+
if (m) {
|
|
258
|
+
const candidates = pattern.extract(m);
|
|
259
|
+
for (const c of candidates) {
|
|
260
|
+
if (c) {
|
|
261
|
+
importedModules.add(c);
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
// 生成边:仅在目标是已知模块且非自身时
|
|
269
|
+
for (const imported of importedModules) {
|
|
270
|
+
if (imported !== modName && knownModules.has(imported)) {
|
|
271
|
+
edges.push({
|
|
272
|
+
from: modName,
|
|
273
|
+
to: imported,
|
|
274
|
+
weight: EDGE_WEIGHTS.depends_on,
|
|
275
|
+
relation: 'depends_on',
|
|
276
|
+
});
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
return edges;
|
|
281
|
+
}
|
|
177
282
|
#deduplicateEdges(edges) {
|
|
178
283
|
const key = (e) => `${e.from}→${e.to}`;
|
|
179
284
|
const map = new Map();
|
|
@@ -13,17 +13,20 @@
|
|
|
13
13
|
*
|
|
14
14
|
* @module DimensionAnalyzer
|
|
15
15
|
*/
|
|
16
|
-
import type {
|
|
16
|
+
import type { BootstrapRepositoryImpl } from '../../repository/bootstrap/BootstrapRepository.js';
|
|
17
|
+
import type { CodeEntityRepositoryImpl } from '../../repository/code/CodeEntityRepository.js';
|
|
18
|
+
import type { KnowledgeRepositoryImpl } from '../../repository/knowledge/KnowledgeRepository.impl.js';
|
|
19
|
+
import type { HealthRadar, KnowledgeGap } from './PanoramaTypes.js';
|
|
17
20
|
export declare class DimensionAnalyzer {
|
|
18
21
|
#private;
|
|
19
|
-
constructor(
|
|
22
|
+
constructor(bootstrapRepo: BootstrapRepositoryImpl, entityRepo: CodeEntityRepositoryImpl, knowledgeRepo: KnowledgeRepositoryImpl, projectRoot: string);
|
|
20
23
|
/**
|
|
21
24
|
* 分析项目知识健康雷达
|
|
22
25
|
*
|
|
23
26
|
* @param moduleRoles — 项目中存在的模块角色 (用于 gap 优先级推断)
|
|
24
27
|
*/
|
|
25
|
-
analyze(moduleRoles: string[]): {
|
|
28
|
+
analyze(moduleRoles: string[]): Promise<{
|
|
26
29
|
radar: HealthRadar;
|
|
27
30
|
gaps: KnowledgeGap[];
|
|
28
|
-
}
|
|
31
|
+
}>;
|
|
29
32
|
}
|
|
@@ -13,31 +13,34 @@
|
|
|
13
13
|
*
|
|
14
14
|
* @module DimensionAnalyzer
|
|
15
15
|
*/
|
|
16
|
-
import { classifyRecipeToDimension, DIMENSION_REGISTRY } from '#domain/dimension/index.js';
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
* Panorama 使用全量维度注册表进行健康评估。
|
|
20
|
-
* 所有维度(含语言/框架条件维度)都参与评估 —
|
|
21
|
-
* 若该语言未激活但有 Recipe → 仍计入(只是不生成 gap 建议)。
|
|
22
|
-
*/
|
|
23
|
-
const DIMENSION_DEFS = DIMENSION_REGISTRY;
|
|
16
|
+
import { classifyRecipeToDimension, DIMENSION_REGISTRY, resolveActiveDimensions, } from '#domain/dimension/index.js';
|
|
17
|
+
import { LanguageService } from '#shared/LanguageService.js';
|
|
18
|
+
import { COUNTABLE_LIFECYCLES } from '../../domain/knowledge/Lifecycle.js';
|
|
24
19
|
/* ═══ DimensionAnalyzer Class ═════════════════════════════ */
|
|
25
20
|
export class DimensionAnalyzer {
|
|
26
|
-
#
|
|
27
|
-
|
|
28
|
-
|
|
21
|
+
#bootstrapRepo;
|
|
22
|
+
#entityRepo;
|
|
23
|
+
#knowledgeRepo;
|
|
24
|
+
#projectRoot;
|
|
25
|
+
constructor(bootstrapRepo, entityRepo, knowledgeRepo, projectRoot) {
|
|
26
|
+
this.#bootstrapRepo = bootstrapRepo;
|
|
27
|
+
this.#entityRepo = entityRepo;
|
|
28
|
+
this.#knowledgeRepo = knowledgeRepo;
|
|
29
|
+
this.#projectRoot = projectRoot;
|
|
29
30
|
}
|
|
30
31
|
/**
|
|
31
32
|
* 分析项目知识健康雷达
|
|
32
33
|
*
|
|
33
34
|
* @param moduleRoles — 项目中存在的模块角色 (用于 gap 优先级推断)
|
|
34
35
|
*/
|
|
35
|
-
analyze(moduleRoles) {
|
|
36
|
+
async analyze(moduleRoles) {
|
|
37
|
+
// 0. 按项目语言过滤活跃维度(排除无关语言/框架维度)
|
|
38
|
+
const activeDims = await this.#resolveActiveDims();
|
|
36
39
|
// 1. 从 DB 获取所有活跃 recipe 的维度分类信息
|
|
37
|
-
const recipes = this.#fetchRecipeMetadata();
|
|
40
|
+
const recipes = await this.#fetchRecipeMetadata();
|
|
38
41
|
// 2. 将每条 recipe 映射到维度
|
|
39
42
|
const dimensionCounts = new Map();
|
|
40
|
-
for (const def of
|
|
43
|
+
for (const def of activeDims) {
|
|
41
44
|
dimensionCounts.set(def.id, { count: 0, titles: [] });
|
|
42
45
|
}
|
|
43
46
|
let totalRecipes = 0;
|
|
@@ -53,16 +56,16 @@ export class DimensionAnalyzer {
|
|
|
53
56
|
}
|
|
54
57
|
}
|
|
55
58
|
// 3. 计算各维度得分与状态
|
|
56
|
-
const dimensions =
|
|
59
|
+
const dimensions = activeDims.map((def) => {
|
|
57
60
|
const entry = dimensionCounts.get(def.id);
|
|
58
61
|
return this.#scoreDimension(def, entry.count, entry.titles);
|
|
59
62
|
});
|
|
60
63
|
// 4. 加权平均健康分
|
|
61
64
|
let weightedSum = 0;
|
|
62
65
|
let weightTotal = 0;
|
|
63
|
-
for (let i = 0; i <
|
|
64
|
-
weightedSum += dimensions[i].score *
|
|
65
|
-
weightTotal +=
|
|
66
|
+
for (let i = 0; i < activeDims.length; i++) {
|
|
67
|
+
weightedSum += dimensions[i].score * activeDims[i].weight;
|
|
68
|
+
weightTotal += activeDims[i].weight;
|
|
66
69
|
}
|
|
67
70
|
const overallScore = weightTotal > 0 ? Math.round(weightedSum / weightTotal) : 0;
|
|
68
71
|
// 5. 统计覆盖
|
|
@@ -78,23 +81,81 @@ export class DimensionAnalyzer {
|
|
|
78
81
|
};
|
|
79
82
|
// 6. 生成维度空白 (gaps)
|
|
80
83
|
const roleSet = new Set(moduleRoles);
|
|
81
|
-
const gaps = this.#detectDimensionGaps(dimensions, roleSet);
|
|
84
|
+
const gaps = this.#detectDimensionGaps(dimensions, activeDims, roleSet);
|
|
82
85
|
return { radar, gaps };
|
|
83
86
|
}
|
|
87
|
+
/* ─── 按项目语言解析活跃维度 ───────────────────── */
|
|
88
|
+
async #resolveActiveDims() {
|
|
89
|
+
// 1. 优先从 bootstrap_snapshots 获取 primary_lang
|
|
90
|
+
try {
|
|
91
|
+
const primaryLang = await this.#bootstrapRepo.getLatestPrimaryLang(this.#projectRoot);
|
|
92
|
+
if (primaryLang) {
|
|
93
|
+
return resolveActiveDimensions(primaryLang);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
catch {
|
|
97
|
+
// 无 bootstrap 数据 → 继续尝试从 code_entities 推断
|
|
98
|
+
}
|
|
99
|
+
// 2. 从 code_entities 文件扩展名推断主语言
|
|
100
|
+
const inferredLang = await this.#inferLanguageFromEntities();
|
|
101
|
+
if (inferredLang) {
|
|
102
|
+
return resolveActiveDimensions(inferredLang);
|
|
103
|
+
}
|
|
104
|
+
return DIMENSION_REGISTRY;
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* 从 code_entities 文件扩展名统计推断项目主语言
|
|
108
|
+
*
|
|
109
|
+
* 当无 bootstrap_snapshots 时使用(如仅执行了 scan 但未 bootstrap 的项目)
|
|
110
|
+
*/
|
|
111
|
+
async #inferLanguageFromEntities() {
|
|
112
|
+
try {
|
|
113
|
+
const filePaths = await this.#entityRepo.findDistinctFilePaths(this.#projectRoot, 2000);
|
|
114
|
+
if (filePaths.length === 0) {
|
|
115
|
+
return null;
|
|
116
|
+
}
|
|
117
|
+
const langCounts = new Map();
|
|
118
|
+
for (const fp of filePaths) {
|
|
119
|
+
if (!fp) {
|
|
120
|
+
continue;
|
|
121
|
+
}
|
|
122
|
+
const dotIdx = fp.lastIndexOf('.');
|
|
123
|
+
if (dotIdx < 0) {
|
|
124
|
+
continue;
|
|
125
|
+
}
|
|
126
|
+
const ext = fp.slice(dotIdx).toLowerCase();
|
|
127
|
+
const lang = LanguageService.langFromExt(ext);
|
|
128
|
+
if (lang !== 'unknown') {
|
|
129
|
+
langCounts.set(lang, (langCounts.get(lang) ?? 0) + 1);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
// .h 文件可能属于 Swift/ObjC 项目 — 如果同时存在 .swift 文件则优先 swift
|
|
133
|
+
if (langCounts.has('swift') && langCounts.has('objectivec')) {
|
|
134
|
+
const swiftCount = langCounts.get('swift');
|
|
135
|
+
const objcCount = langCounts.get('objectivec');
|
|
136
|
+
if (swiftCount >= objcCount * 0.2) {
|
|
137
|
+
return 'swift';
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
// 选最多的语言
|
|
141
|
+
let bestLang = '';
|
|
142
|
+
let bestCount = 0;
|
|
143
|
+
for (const [lang, _count] of langCounts) {
|
|
144
|
+
if (_count > bestCount) {
|
|
145
|
+
bestLang = lang;
|
|
146
|
+
bestCount = _count;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
return bestLang || null;
|
|
150
|
+
}
|
|
151
|
+
catch {
|
|
152
|
+
return null;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
84
155
|
/* ─── 从 DB 获取 recipe 元数据 ─────────────────── */
|
|
85
|
-
#fetchRecipeMetadata() {
|
|
156
|
+
async #fetchRecipeMetadata() {
|
|
86
157
|
try {
|
|
87
|
-
|
|
88
|
-
.prepare(`SELECT title, category, topicHint, kind
|
|
89
|
-
FROM knowledge_entries
|
|
90
|
-
WHERE lifecycle IN ('active', 'pending')`)
|
|
91
|
-
.all();
|
|
92
|
-
return rows.map((r) => ({
|
|
93
|
-
title: String(r.title ?? ''),
|
|
94
|
-
category: String(r.category ?? ''),
|
|
95
|
-
topicHint: String(r.topicHint ?? ''),
|
|
96
|
-
kind: String(r.kind ?? ''),
|
|
97
|
-
}));
|
|
158
|
+
return await this.#knowledgeRepo.findRecipeMetadata(COUNTABLE_LIFECYCLES);
|
|
98
159
|
}
|
|
99
160
|
catch {
|
|
100
161
|
return [];
|
|
@@ -153,11 +214,11 @@ export class DimensionAnalyzer {
|
|
|
153
214
|
};
|
|
154
215
|
}
|
|
155
216
|
/* ─── 维度空白检测 ─────────────────────────────── */
|
|
156
|
-
#detectDimensionGaps(dimensions, moduleRoles) {
|
|
217
|
+
#detectDimensionGaps(dimensions, activeDims, moduleRoles) {
|
|
157
218
|
const gaps = [];
|
|
158
219
|
for (let i = 0; i < dimensions.length; i++) {
|
|
159
220
|
const dim = dimensions[i];
|
|
160
|
-
const def =
|
|
221
|
+
const def = activeDims[i];
|
|
161
222
|
if (dim.status !== 'missing' && dim.status !== 'weak') {
|
|
162
223
|
continue;
|
|
163
224
|
}
|
|
@@ -4,9 +4,23 @@
|
|
|
4
4
|
* 基于模块依赖图,通过去环 + 拓扑排序 + 最长路径法推断架构层级 (L0-Ln)。
|
|
5
5
|
* 底层 (L0) = Foundation/Core,顶层 = App/UI。
|
|
6
6
|
*
|
|
7
|
+
* 当配置文件声明了明确的层级结构时(如 Boxfile 的 layer 定义),
|
|
8
|
+
* 优先使用配置层级,仅对未覆盖的模块做拓扑推断。
|
|
9
|
+
*
|
|
7
10
|
* @module LayerInferrer
|
|
8
11
|
*/
|
|
9
12
|
import type { CyclicDependency, Edge, LayerHierarchy } from './PanoramaTypes.js';
|
|
13
|
+
export interface ConfigLayer {
|
|
14
|
+
name: string;
|
|
15
|
+
order: number;
|
|
16
|
+
accessibleLayers: string[];
|
|
17
|
+
}
|
|
18
|
+
export interface InferOptions {
|
|
19
|
+
/** 来自配置文件的层级定义(如 Boxfile、Project.swift 等) */
|
|
20
|
+
configLayers?: ConfigLayer[] | null;
|
|
21
|
+
/** 模块 → 配置层级名映射 */
|
|
22
|
+
moduleLayerMap?: Map<string, string>;
|
|
23
|
+
}
|
|
10
24
|
export declare class LayerInferrer {
|
|
11
25
|
#private;
|
|
12
26
|
/**
|
|
@@ -14,6 +28,7 @@ export declare class LayerInferrer {
|
|
|
14
28
|
* @param edges - 模块间依赖边 (from depends_on/calls/data_flow to)
|
|
15
29
|
* @param modules - 所有模块名
|
|
16
30
|
* @param cycles - 已检测到的循环依赖
|
|
31
|
+
* @param options - 可选配置:configLayers(配置声明的层级)和 moduleLayerMap(模块→层级映射)
|
|
17
32
|
*/
|
|
18
|
-
infer(edges: Edge[], modules: string[], cycles: CyclicDependency[]): LayerHierarchy;
|
|
33
|
+
infer(edges: Edge[], modules: string[], cycles: CyclicDependency[], options?: InferOptions): LayerHierarchy;
|
|
19
34
|
}
|
|
@@ -4,6 +4,9 @@
|
|
|
4
4
|
* 基于模块依赖图,通过去环 + 拓扑排序 + 最长路径法推断架构层级 (L0-Ln)。
|
|
5
5
|
* 底层 (L0) = Foundation/Core,顶层 = App/UI。
|
|
6
6
|
*
|
|
7
|
+
* 当配置文件声明了明确的层级结构时(如 Boxfile 的 layer 定义),
|
|
8
|
+
* 优先使用配置层级,仅对未覆盖的模块做拓扑推断。
|
|
9
|
+
*
|
|
7
10
|
* @module LayerInferrer
|
|
8
11
|
*/
|
|
9
12
|
/* ═══ Constants ═══════════════════════════════════════════ */
|
|
@@ -26,8 +29,122 @@ export class LayerInferrer {
|
|
|
26
29
|
* @param edges - 模块间依赖边 (from depends_on/calls/data_flow to)
|
|
27
30
|
* @param modules - 所有模块名
|
|
28
31
|
* @param cycles - 已检测到的循环依赖
|
|
32
|
+
* @param options - 可选配置:configLayers(配置声明的层级)和 moduleLayerMap(模块→层级映射)
|
|
33
|
+
*/
|
|
34
|
+
infer(edges, modules, cycles, options) {
|
|
35
|
+
// 如果有配置文件声明的层级且覆盖了大部分模块,优先使用
|
|
36
|
+
if (options?.configLayers?.length && options.moduleLayerMap?.size) {
|
|
37
|
+
const coverage = this.#computeConfigCoverage(modules, options.moduleLayerMap);
|
|
38
|
+
if (coverage >= 0.5) {
|
|
39
|
+
return this.#inferFromConfig(edges, modules, options.configLayers, options.moduleLayerMap);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
return this.#inferFromTopology(edges, modules, cycles);
|
|
43
|
+
}
|
|
44
|
+
/* ─── Config-based layer inference ──────────────── */
|
|
45
|
+
/**
|
|
46
|
+
* 基于配置文件声明的层级直接分配
|
|
47
|
+
* 未被配置覆盖的模块附加到最近匹配的层级
|
|
29
48
|
*/
|
|
30
|
-
|
|
49
|
+
#inferFromConfig(edges, modules, configLayers, moduleLayerMap) {
|
|
50
|
+
// 按 order 排序层级
|
|
51
|
+
const sortedLayers = [...configLayers].sort((a, b) => a.order - b.order);
|
|
52
|
+
// 构建层级名 → level 映射 (底层 = L0, 反序: order 最大的是底层)
|
|
53
|
+
// 配置中 order=0 是最高层 (如 Accessories), order=N 是最底层 (如 Vendors)
|
|
54
|
+
const maxOrder = sortedLayers.length > 0 ? sortedLayers[sortedLayers.length - 1].order : 0;
|
|
55
|
+
const layerNameToLevel = new Map();
|
|
56
|
+
for (const cl of sortedLayers) {
|
|
57
|
+
// 反转: 配置中 order 越大越底层 → level 越小
|
|
58
|
+
layerNameToLevel.set(cl.name, maxOrder - cl.order);
|
|
59
|
+
}
|
|
60
|
+
// 分配每个模块到层级
|
|
61
|
+
const moduleLevels = new Map();
|
|
62
|
+
const uncovered = [];
|
|
63
|
+
for (const mod of modules) {
|
|
64
|
+
const layerName = moduleLayerMap.get(mod);
|
|
65
|
+
if (layerName && layerNameToLevel.has(layerName)) {
|
|
66
|
+
moduleLevels.set(mod, layerNameToLevel.get(layerName));
|
|
67
|
+
}
|
|
68
|
+
else {
|
|
69
|
+
uncovered.push(mod);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
// 未覆盖的模块: 基于依赖关系推断最可能的层级
|
|
73
|
+
for (const mod of uncovered) {
|
|
74
|
+
const depEdges = edges.filter((e) => e.from === mod);
|
|
75
|
+
let bestLevel = maxOrder + 1; // 默认最高层
|
|
76
|
+
for (const e of depEdges) {
|
|
77
|
+
const targetLevel = moduleLevels.get(e.to);
|
|
78
|
+
if (targetLevel !== undefined) {
|
|
79
|
+
bestLevel = Math.min(bestLevel, targetLevel + 1);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
// 如果没有依赖线索, 查看谁依赖它
|
|
83
|
+
if (bestLevel > maxOrder) {
|
|
84
|
+
const reverseEdges = edges.filter((e) => e.to === mod);
|
|
85
|
+
for (const e of reverseEdges) {
|
|
86
|
+
const sourceLevel = moduleLevels.get(e.from);
|
|
87
|
+
if (sourceLevel !== undefined) {
|
|
88
|
+
bestLevel = Math.min(bestLevel, Math.max(0, sourceLevel - 1));
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
// 兜底: 放到最高层
|
|
93
|
+
if (bestLevel > maxOrder) {
|
|
94
|
+
bestLevel = maxOrder;
|
|
95
|
+
}
|
|
96
|
+
moduleLevels.set(mod, bestLevel);
|
|
97
|
+
}
|
|
98
|
+
// 聚合: 同层模块分组
|
|
99
|
+
const layerGroups = new Map();
|
|
100
|
+
for (const [mod, level] of moduleLevels) {
|
|
101
|
+
if (!layerGroups.has(level)) {
|
|
102
|
+
layerGroups.set(level, []);
|
|
103
|
+
}
|
|
104
|
+
layerGroups.get(level).push(mod);
|
|
105
|
+
}
|
|
106
|
+
// 构建 levelEntries, 使用配置中的层级名
|
|
107
|
+
const levelToConfigName = new Map();
|
|
108
|
+
for (const cl of sortedLayers) {
|
|
109
|
+
levelToConfigName.set(maxOrder - cl.order, cl.name);
|
|
110
|
+
}
|
|
111
|
+
const sortedLevels = [...layerGroups.entries()].sort((a, b) => a[0] - b[0]);
|
|
112
|
+
const levelEntries = sortedLevels.map(([level, mods]) => ({
|
|
113
|
+
level,
|
|
114
|
+
name: levelToConfigName.get(level) ?? this.#inferLayerName(mods, level, sortedLevels.length),
|
|
115
|
+
modules: mods.sort(),
|
|
116
|
+
}));
|
|
117
|
+
// 检测层级违规(基于 access 规则)
|
|
118
|
+
const violations = [];
|
|
119
|
+
for (const edge of edges) {
|
|
120
|
+
const fromLevel = moduleLevels.get(edge.from);
|
|
121
|
+
const toLevel = moduleLevels.get(edge.to);
|
|
122
|
+
if (fromLevel !== undefined && toLevel !== undefined && fromLevel < toLevel) {
|
|
123
|
+
violations.push({
|
|
124
|
+
from: edge.from,
|
|
125
|
+
to: edge.to,
|
|
126
|
+
fromLayer: fromLevel,
|
|
127
|
+
toLayer: toLevel,
|
|
128
|
+
relation: edge.relation,
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
return { levels: levelEntries, violations, configBased: true };
|
|
133
|
+
}
|
|
134
|
+
#computeConfigCoverage(modules, moduleLayerMap) {
|
|
135
|
+
if (modules.length === 0) {
|
|
136
|
+
return 0;
|
|
137
|
+
}
|
|
138
|
+
let covered = 0;
|
|
139
|
+
for (const mod of modules) {
|
|
140
|
+
if (moduleLayerMap.has(mod)) {
|
|
141
|
+
covered++;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
return covered / modules.length;
|
|
145
|
+
}
|
|
146
|
+
/* ─── Topology-based layer inference ────────────── */
|
|
147
|
+
#inferFromTopology(edges, modules, cycles) {
|
|
31
148
|
// 1. 建图 (邻接表: from → to[])
|
|
32
149
|
const adjacency = new Map();
|
|
33
150
|
const reverseAdj = new Map();
|
|
@@ -11,14 +11,24 @@
|
|
|
11
11
|
*
|
|
12
12
|
* @module ModuleDiscoverer
|
|
13
13
|
*/
|
|
14
|
-
import type {
|
|
14
|
+
import type { CodeEntityRepositoryImpl } from '../../repository/code/CodeEntityRepository.js';
|
|
15
|
+
import type { KnowledgeEdgeRepositoryImpl } from '../../repository/knowledge/KnowledgeEdgeRepository.js';
|
|
15
16
|
import type { ModuleCandidate } from './RoleRefiner.js';
|
|
16
17
|
export declare class ModuleDiscoverer {
|
|
17
18
|
#private;
|
|
18
|
-
constructor(
|
|
19
|
+
constructor(entityRepo: CodeEntityRepositoryImpl, edgeRepo: KnowledgeEdgeRepositoryImpl, projectRoot: string);
|
|
19
20
|
/**
|
|
20
21
|
* 从 DB 中读取已扫描的模块数据。
|
|
21
|
-
* 若无 module
|
|
22
|
+
* 若无 module 实体(含 host),返回空数组(让调用侧决定是否重新扫描)。
|
|
22
23
|
*/
|
|
23
|
-
discover(): ModuleCandidate[]
|
|
24
|
+
discover(): Promise<ModuleCandidate[]>;
|
|
25
|
+
/**
|
|
26
|
+
* 读取 config layers 元数据(如果存在)
|
|
27
|
+
* @returns 从 `__config_layers__` 实体中恢复的层级定义
|
|
28
|
+
*/
|
|
29
|
+
readConfigLayers(): Promise<Array<{
|
|
30
|
+
name: string;
|
|
31
|
+
order: number;
|
|
32
|
+
accessibleLayers: string[];
|
|
33
|
+
}> | null>;
|
|
24
34
|
}
|