autosnippet 3.3.7 → 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 +7 -4
- 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/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 +4 -17
- 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/CustomConfigDiscoverer.d.ts +0 -2
- package/dist/lib/core/discovery/CustomConfigDiscoverer.js +0 -2
- 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/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/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/orchestrator.js +33 -16
- 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.js +1 -1
- package/dist/lib/external/mcp/handlers/dimension-complete-external.js +7 -3
- package/dist/lib/external/mcp/handlers/evolve-external.d.ts +1 -0
- package/dist/lib/external/mcp/handlers/evolve-external.js +13 -16
- package/dist/lib/external/mcp/handlers/guard.js +15 -24
- 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 +11 -13
- package/dist/lib/http/routes/guardReport.js +3 -5
- package/dist/lib/http/routes/panorama.js +12 -12
- package/dist/lib/http/routes/recipes.js +59 -8
- package/dist/lib/http/routes/remote.js +3 -13
- package/dist/lib/http/routes/search.js +11 -8
- 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.js +7 -4
- package/dist/lib/injection/ServiceMap.d.ts +20 -0
- 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.js +8 -4
- 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 +39 -63
- package/dist/lib/service/knowledge/CodeEntityGraph.js +418 -512
- 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 +76 -38
- 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/panorama/CouplingAnalyzer.d.ts +5 -3
- package/dist/lib/service/panorama/CouplingAnalyzer.js +102 -39
- package/dist/lib/service/panorama/DimensionAnalyzer.d.ts +7 -4
- package/dist/lib/service/panorama/DimensionAnalyzer.js +72 -25
- package/dist/lib/service/panorama/LayerInferrer.js +1 -1
- package/dist/lib/service/panorama/ModuleDiscoverer.d.ts +7 -6
- package/dist/lib/service/panorama/ModuleDiscoverer.js +174 -82
- package/dist/lib/service/panorama/PanoramaAggregator.d.ts +10 -3
- package/dist/lib/service/panorama/PanoramaAggregator.js +67 -79
- package/dist/lib/service/panorama/PanoramaScanner.d.ts +5 -1
- package/dist/lib/service/panorama/PanoramaScanner.js +32 -31
- package/dist/lib/service/panorama/PanoramaService.d.ts +11 -8
- package/dist/lib/service/panorama/PanoramaService.js +41 -66
- package/dist/lib/service/panorama/PanoramaTypes.d.ts +3 -0
- package/dist/lib/service/panorama/RoleRefiner.d.ts +8 -5
- package/dist/lib/service/panorama/RoleRefiner.js +52 -283
- package/dist/lib/service/panorama/TechStackProfiler.js +7 -119
- 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 +5 -8
- package/dist/lib/service/skills/SignalCollector.js +28 -55
- package/dist/lib/service/skills/SkillAdvisor.d.ts +7 -13
- package/dist/lib/service/skills/SkillAdvisor.js +30 -79
- 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 +16 -0
- package/dist/lib/shared/constants.d.ts +19 -19
- package/dist/lib/shared/constants.js +10 -10
- package/dist/lib/shared/schemas/mcp-tools.d.ts +1 -1
- 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-FHns2ypa.js +0 -1
- package/dashboard/dist/assets/index-BRJv5Y3r.js +0 -135
- package/dashboard/dist/assets/index-DzoB7kxK.css +0 -1
- package/dist/lib/repository/base/BaseRepository.d.ts +0 -53
- package/dist/lib/repository/base/BaseRepository.js +0 -226
|
@@ -26,15 +26,14 @@ import Logger from '../../infrastructure/logging/Logger.js';
|
|
|
26
26
|
const logger = Logger.getInstance();
|
|
27
27
|
export class CodeEntityGraph {
|
|
28
28
|
projectRoot;
|
|
29
|
-
|
|
29
|
+
#entityRepo;
|
|
30
|
+
#edgeRepo;
|
|
30
31
|
log;
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
this
|
|
32
|
+
constructor(entityRepo, edgeRepo, options = {}) {
|
|
33
|
+
this.#entityRepo = entityRepo;
|
|
34
|
+
this.#edgeRepo = edgeRepo;
|
|
34
35
|
this.projectRoot = options.projectRoot || '';
|
|
35
36
|
this.log = options.logger || logger;
|
|
36
|
-
this.#ensureTable();
|
|
37
|
-
this.#prepareStatements();
|
|
38
37
|
}
|
|
39
38
|
// ────────────────────────────────────────────
|
|
40
39
|
// Public API — 图谱构建
|
|
@@ -46,102 +45,99 @@ export class CodeEntityGraph {
|
|
|
46
45
|
*
|
|
47
46
|
* @param astSummary analyzeProject() 产出的 ProjectAstSummary
|
|
48
47
|
*/
|
|
49
|
-
populateFromAst(astSummary) {
|
|
48
|
+
async populateFromAst(astSummary) {
|
|
50
49
|
if (!astSummary) {
|
|
51
50
|
return { entitiesUpserted: 0, edgesCreated: 0, durationMs: 0 };
|
|
52
51
|
}
|
|
53
52
|
const t0 = Date.now();
|
|
54
53
|
let entities = 0;
|
|
55
54
|
let edges = 0;
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
edges++;
|
|
140
|
-
}
|
|
55
|
+
// ── 类 ──
|
|
56
|
+
for (const cls of astSummary.classes || []) {
|
|
57
|
+
await this.#upsertEntity({
|
|
58
|
+
entityId: cls.name,
|
|
59
|
+
entityType: cls.isCategory ? 'category' : 'class',
|
|
60
|
+
name: cls.name,
|
|
61
|
+
filePath: cls.file || null,
|
|
62
|
+
line: cls.line || null,
|
|
63
|
+
superclass: cls.superclass || null,
|
|
64
|
+
protocols: cls.protocols || [],
|
|
65
|
+
metadata: {
|
|
66
|
+
endLine: cls.endLine,
|
|
67
|
+
isCategory: cls.isCategory || false,
|
|
68
|
+
},
|
|
69
|
+
});
|
|
70
|
+
entities++;
|
|
71
|
+
}
|
|
72
|
+
// ── 协议 ──
|
|
73
|
+
for (const proto of astSummary.protocols || []) {
|
|
74
|
+
await this.#upsertEntity({
|
|
75
|
+
entityId: proto.name,
|
|
76
|
+
entityType: 'protocol',
|
|
77
|
+
name: proto.name,
|
|
78
|
+
filePath: proto.file || null,
|
|
79
|
+
line: proto.line || null,
|
|
80
|
+
protocols: proto.inherits || [],
|
|
81
|
+
metadata: {
|
|
82
|
+
methodCount: proto.methods?.length || 0,
|
|
83
|
+
},
|
|
84
|
+
});
|
|
85
|
+
entities++;
|
|
86
|
+
}
|
|
87
|
+
// ── Category ──
|
|
88
|
+
for (const cat of astSummary.categories || []) {
|
|
89
|
+
const catId = `${cat.className}(${cat.categoryName})`;
|
|
90
|
+
await this.#upsertEntity({
|
|
91
|
+
entityId: catId,
|
|
92
|
+
entityType: 'category',
|
|
93
|
+
name: catId,
|
|
94
|
+
filePath: cat.file || null,
|
|
95
|
+
line: cat.line || null,
|
|
96
|
+
protocols: cat.protocols || [],
|
|
97
|
+
metadata: {
|
|
98
|
+
className: cat.className,
|
|
99
|
+
categoryName: cat.categoryName,
|
|
100
|
+
methodCount: cat.methods?.length || 0,
|
|
101
|
+
},
|
|
102
|
+
});
|
|
103
|
+
entities++;
|
|
104
|
+
}
|
|
105
|
+
// ── 继承/遵循/扩展 边 (从 AST inheritanceGraph) ──
|
|
106
|
+
for (const edge of astSummary.inheritanceGraph || []) {
|
|
107
|
+
const fromType = this.#inferEntityType(edge.from, astSummary);
|
|
108
|
+
const toType = this.#inferEntityType(edge.to, astSummary);
|
|
109
|
+
await this.#addEdge(edge.from, fromType, edge.to, toType, edge.type, {
|
|
110
|
+
weight: 1.0,
|
|
111
|
+
source: 'ast-bootstrap',
|
|
112
|
+
});
|
|
113
|
+
edges++;
|
|
114
|
+
}
|
|
115
|
+
// ── 设计模式 (从 patternStats) ──
|
|
116
|
+
for (const [patternType, stat] of Object.entries(astSummary.patternStats || {})) {
|
|
117
|
+
const patternId = `pattern:${patternType}`;
|
|
118
|
+
await this.#upsertEntity({
|
|
119
|
+
entityId: patternId,
|
|
120
|
+
entityType: 'pattern',
|
|
121
|
+
name: patternType,
|
|
122
|
+
metadata: {
|
|
123
|
+
count: stat.count,
|
|
124
|
+
files: stat.files?.slice(0, 10),
|
|
125
|
+
},
|
|
126
|
+
});
|
|
127
|
+
entities++;
|
|
128
|
+
// 实例 → uses_pattern 边
|
|
129
|
+
for (const inst of (stat.instances || []).slice(0, 50)) {
|
|
130
|
+
const className = inst.className || inst.name;
|
|
131
|
+
if (className) {
|
|
132
|
+
await this.#addEdge(className, 'class', patternId, 'pattern', 'uses_pattern', {
|
|
133
|
+
weight: 0.8,
|
|
134
|
+
source: 'ast-pattern-detection',
|
|
135
|
+
file: inst.file,
|
|
136
|
+
});
|
|
137
|
+
edges++;
|
|
141
138
|
}
|
|
142
139
|
}
|
|
143
|
-
}
|
|
144
|
-
run();
|
|
140
|
+
}
|
|
145
141
|
const result = { entitiesUpserted: entities, edgesCreated: edges, durationMs: Date.now() - t0 };
|
|
146
142
|
this.log.info(`[CodeEntityGraph] AST populate: ${entities} entities, ${edges} edges (${result.durationMs}ms)`);
|
|
147
143
|
return result;
|
|
@@ -154,43 +150,40 @@ export class CodeEntityGraph {
|
|
|
154
150
|
*
|
|
155
151
|
* @param depGraphData spm.getDependencyGraph() 产出
|
|
156
152
|
*/
|
|
157
|
-
populateFromSpm(depGraphData) {
|
|
153
|
+
async populateFromSpm(depGraphData) {
|
|
158
154
|
if (!depGraphData) {
|
|
159
155
|
return { entitiesUpserted: 0, edgesCreated: 0, durationMs: 0 };
|
|
160
156
|
}
|
|
161
157
|
const t0 = Date.now();
|
|
162
158
|
let entities = 0;
|
|
163
|
-
const
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
}
|
|
192
|
-
});
|
|
193
|
-
run();
|
|
159
|
+
for (const node of depGraphData.nodes || []) {
|
|
160
|
+
const nodeObj = typeof node === 'string' ? { id: node, label: node } : node;
|
|
161
|
+
await this.#upsertEntity({
|
|
162
|
+
entityId: nodeObj.id || nodeObj.label || String(node),
|
|
163
|
+
entityType: 'module',
|
|
164
|
+
name: nodeObj.label || nodeObj.id || String(node),
|
|
165
|
+
metadata: {
|
|
166
|
+
nodeType: nodeObj.type || 'module',
|
|
167
|
+
...(nodeObj.layer != null ? { layer: nodeObj.layer } : {}),
|
|
168
|
+
...(nodeObj.version != null ? { version: nodeObj.version } : {}),
|
|
169
|
+
...(nodeObj.group != null ? { group: nodeObj.group } : {}),
|
|
170
|
+
...(nodeObj.fullPath != null ? { fullPath: nodeObj.fullPath } : {}),
|
|
171
|
+
...(nodeObj.indirect != null ? { indirect: nodeObj.indirect } : {}),
|
|
172
|
+
},
|
|
173
|
+
});
|
|
174
|
+
entities++;
|
|
175
|
+
}
|
|
176
|
+
// 存储 layers 元数据(如果存在)到特殊实体
|
|
177
|
+
const layers = depGraphData.layers;
|
|
178
|
+
if (layers?.length) {
|
|
179
|
+
await this.#upsertEntity({
|
|
180
|
+
entityId: '__config_layers__',
|
|
181
|
+
entityType: 'config',
|
|
182
|
+
name: 'Config Layers',
|
|
183
|
+
metadata: { layers },
|
|
184
|
+
});
|
|
185
|
+
entities++;
|
|
186
|
+
}
|
|
194
187
|
const result = { entitiesUpserted: entities, edgesCreated: 0, durationMs: Date.now() - t0 };
|
|
195
188
|
this.log.info(`[CodeEntityGraph] SPM populate: ${entities} module entities (${result.durationMs}ms)`);
|
|
196
189
|
return result;
|
|
@@ -200,55 +193,52 @@ export class CodeEntityGraph {
|
|
|
200
193
|
*
|
|
201
194
|
* @param candidates 扁平关系数组或 Relations 对象
|
|
202
195
|
*/
|
|
203
|
-
populateFromCandidateRelations(candidates) {
|
|
196
|
+
async populateFromCandidateRelations(candidates) {
|
|
204
197
|
if (!candidates?.length) {
|
|
205
198
|
return { entitiesUpserted: 0, edgesCreated: 0, durationMs: 0 };
|
|
206
199
|
}
|
|
207
200
|
const t0 = Date.now();
|
|
208
201
|
let edges = 0;
|
|
209
|
-
const
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
for (const
|
|
228
|
-
|
|
229
|
-
flatRelations.push({ type, target: r.target, description: r.description });
|
|
230
|
-
}
|
|
202
|
+
for (const candidate of candidates) {
|
|
203
|
+
const title = candidate.title || candidate.id || '';
|
|
204
|
+
if (!title) {
|
|
205
|
+
continue;
|
|
206
|
+
}
|
|
207
|
+
// 处理 Relations 对象或扁平数组
|
|
208
|
+
let flatRelations;
|
|
209
|
+
const rels = candidate.relations;
|
|
210
|
+
if (typeof rels?.toFlatArray === 'function') {
|
|
211
|
+
flatRelations = rels.toFlatArray();
|
|
212
|
+
}
|
|
213
|
+
else if (Array.isArray(candidate.relations)) {
|
|
214
|
+
flatRelations = candidate.relations;
|
|
215
|
+
}
|
|
216
|
+
else if (candidate.relations && typeof candidate.relations === 'object') {
|
|
217
|
+
// 桶结构 → 扁平
|
|
218
|
+
flatRelations = [];
|
|
219
|
+
for (const [type, list] of Object.entries(candidate.relations)) {
|
|
220
|
+
for (const r of Array.isArray(list) ? list : []) {
|
|
221
|
+
flatRelations.push({ type, target: r.target, description: r.description });
|
|
231
222
|
}
|
|
232
223
|
}
|
|
233
|
-
|
|
224
|
+
}
|
|
225
|
+
else {
|
|
226
|
+
continue;
|
|
227
|
+
}
|
|
228
|
+
for (const rel of flatRelations) {
|
|
229
|
+
if (!rel.target) {
|
|
234
230
|
continue;
|
|
235
231
|
}
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
source: 'candidate-relations',
|
|
245
|
-
description: rel.description || '',
|
|
246
|
-
});
|
|
247
|
-
edges++;
|
|
248
|
-
}
|
|
232
|
+
// 映射关系类型到边类型
|
|
233
|
+
const relation = this.#mapRelationType(rel.type);
|
|
234
|
+
await this.#addEdge(title, 'recipe', rel.target, 'recipe', relation, {
|
|
235
|
+
weight: 0.7,
|
|
236
|
+
source: 'candidate-relations',
|
|
237
|
+
description: rel.description || '',
|
|
238
|
+
});
|
|
239
|
+
edges++;
|
|
249
240
|
}
|
|
250
|
-
}
|
|
251
|
-
run();
|
|
241
|
+
}
|
|
252
242
|
const result = { entitiesUpserted: 0, edgesCreated: edges, durationMs: Date.now() - t0 };
|
|
253
243
|
this.log.info(`[CodeEntityGraph] Candidate relations: ${edges} edges (${result.durationMs}ms)`);
|
|
254
244
|
return result;
|
|
@@ -257,97 +247,83 @@ export class CodeEntityGraph {
|
|
|
257
247
|
// Public API — 图谱查询
|
|
258
248
|
// ────────────────────────────────────────────
|
|
259
249
|
/** 获取单个实体信息 */
|
|
260
|
-
getEntity(entityId, entityType) {
|
|
261
|
-
let
|
|
250
|
+
async getEntity(entityId, entityType) {
|
|
251
|
+
let entity;
|
|
262
252
|
if (entityType) {
|
|
263
|
-
|
|
253
|
+
entity = await this.#entityRepo.findByEntityId(entityId, entityType, this.projectRoot);
|
|
264
254
|
}
|
|
265
255
|
else {
|
|
266
|
-
|
|
267
|
-
.prepare(`SELECT * FROM code_entities WHERE entity_id = ? AND project_root = ? LIMIT 1`)
|
|
268
|
-
.get(entityId, this.projectRoot);
|
|
256
|
+
entity = await this.#entityRepo.findByEntityIdOnly(entityId, this.projectRoot);
|
|
269
257
|
}
|
|
270
|
-
return
|
|
258
|
+
return entity ? this.#mapRepoEntity(entity) : null;
|
|
271
259
|
}
|
|
272
260
|
/**
|
|
273
261
|
* 按类型列出所有实体
|
|
274
262
|
* @param entityType 'class'|'protocol'|'category'|'module'|'pattern'
|
|
275
263
|
*/
|
|
276
|
-
listEntities(entityType, limit = 200) {
|
|
277
|
-
const
|
|
278
|
-
return
|
|
264
|
+
async listEntities(entityType, limit = 200) {
|
|
265
|
+
const entities = await this.#entityRepo.listByType(entityType, this.projectRoot, limit);
|
|
266
|
+
return entities.map((e) => this.#mapRepoEntity(e));
|
|
279
267
|
}
|
|
280
268
|
/**
|
|
281
269
|
* 搜索实体 (名称模糊匹配)
|
|
282
270
|
* @param [options.type] 过滤类型
|
|
283
271
|
*/
|
|
284
|
-
searchEntities(query, options = {}) {
|
|
285
|
-
const
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
params.push(options.type);
|
|
291
|
-
}
|
|
292
|
-
sql += ` ORDER BY name LIMIT ?`;
|
|
293
|
-
params.push(options.limit || 20);
|
|
294
|
-
return this.db
|
|
295
|
-
.prepare(sql)
|
|
296
|
-
.all(...params)
|
|
297
|
-
.map((r) => this.#mapEntity(r));
|
|
272
|
+
async searchEntities(query, options = {}) {
|
|
273
|
+
const entities = await this.#entityRepo.searchByName(query, this.projectRoot, {
|
|
274
|
+
entityType: options.type,
|
|
275
|
+
limit: options.limit || 20,
|
|
276
|
+
});
|
|
277
|
+
return entities.map((e) => this.#mapRepoEntity(e));
|
|
298
278
|
}
|
|
299
279
|
/**
|
|
300
280
|
* 获取实体的所有关系边
|
|
301
|
-
* @returns }
|
|
302
281
|
*/
|
|
303
|
-
getEntityEdges(entityId, entityType, direction = 'both') {
|
|
282
|
+
async getEntityEdges(entityId, entityType, direction = 'both') {
|
|
304
283
|
const outgoing = direction === 'both' || direction === 'out'
|
|
305
|
-
? this.
|
|
306
|
-
.prepare(`SELECT * FROM knowledge_edges WHERE from_id = ? AND from_type = ?`)
|
|
307
|
-
.all(entityId, entityType)
|
|
308
|
-
.map((row) => this.#mapEdge(row))
|
|
284
|
+
? await this.#edgeRepo.findOutgoing(entityId, entityType)
|
|
309
285
|
: [];
|
|
310
286
|
const incoming = direction === 'both' || direction === 'in'
|
|
311
|
-
? this.
|
|
312
|
-
.prepare(`SELECT * FROM knowledge_edges WHERE to_id = ? AND to_type = ?`)
|
|
313
|
-
.all(entityId, entityType)
|
|
314
|
-
.map((row) => this.#mapEdge(row))
|
|
287
|
+
? await this.#edgeRepo.findIncoming(entityId, entityType)
|
|
315
288
|
: [];
|
|
316
|
-
return {
|
|
289
|
+
return {
|
|
290
|
+
outgoing: outgoing.map((e) => this.#mapRepoEdge(e)),
|
|
291
|
+
incoming: incoming.map((e) => this.#mapRepoEdge(e)),
|
|
292
|
+
};
|
|
317
293
|
}
|
|
318
294
|
/**
|
|
319
295
|
* 获取继承链 (向上遍历 inherits 边)
|
|
320
296
|
* @returns 继承链 [class, parent, grandparent, ...]
|
|
321
297
|
*/
|
|
322
|
-
getInheritanceChain(className, maxDepth = 10) {
|
|
298
|
+
async getInheritanceChain(className, maxDepth = 10) {
|
|
323
299
|
const chain = [className];
|
|
324
300
|
let current = className;
|
|
325
301
|
for (let i = 0; i < maxDepth; i++) {
|
|
326
|
-
const
|
|
327
|
-
|
|
328
|
-
WHERE from_id = ? AND from_type = 'class' AND relation = 'inherits' LIMIT 1`)
|
|
329
|
-
.get(current);
|
|
330
|
-
if (!parent) {
|
|
302
|
+
const parentId = await this.#edgeRepo.findOutgoingToId(current, 'class', 'inherits');
|
|
303
|
+
if (!parentId) {
|
|
331
304
|
break;
|
|
332
305
|
}
|
|
333
|
-
chain.push(
|
|
334
|
-
current =
|
|
306
|
+
chain.push(parentId);
|
|
307
|
+
current = parentId;
|
|
335
308
|
}
|
|
336
309
|
return chain;
|
|
337
310
|
}
|
|
338
311
|
/**
|
|
339
312
|
* 获取所有子类/实现者 (向下遍历)
|
|
340
313
|
* @param entityType 'class'|'protocol'
|
|
341
|
-
* @returns >}
|
|
342
314
|
*/
|
|
343
|
-
getDescendants(entityId, entityType, maxDepth = 3) {
|
|
315
|
+
async getDescendants(entityId, entityType, maxDepth = 3) {
|
|
344
316
|
const results = [];
|
|
345
317
|
const visited = new Set();
|
|
346
318
|
const queue = [{ id: entityId, type: entityType, depth: 0 }];
|
|
347
319
|
// 类的子类/Category + 协议的遵循者
|
|
348
320
|
const relations = entityType === 'protocol' ? ['conforms', 'inherits'] : ['inherits', 'extends'];
|
|
349
321
|
while (queue.length > 0) {
|
|
350
|
-
const
|
|
322
|
+
const current = queue.shift();
|
|
323
|
+
if (!current) {
|
|
324
|
+
break;
|
|
325
|
+
}
|
|
326
|
+
const { id, type, depth } = current;
|
|
351
327
|
if (depth >= maxDepth) {
|
|
352
328
|
continue;
|
|
353
329
|
}
|
|
@@ -357,22 +333,19 @@ export class CodeEntityGraph {
|
|
|
357
333
|
}
|
|
358
334
|
visited.add(key);
|
|
359
335
|
for (const rel of relations) {
|
|
360
|
-
const children = this.
|
|
361
|
-
.prepare(`SELECT from_id, from_type FROM knowledge_edges
|
|
362
|
-
WHERE to_id = ? AND to_type = ? AND relation = ?`)
|
|
363
|
-
.all(id, type, rel);
|
|
336
|
+
const children = await this.#edgeRepo.findIncomingByFromTypes(id, type, rel);
|
|
364
337
|
for (const child of children) {
|
|
365
|
-
const childKey = `${child.
|
|
338
|
+
const childKey = `${child.fromType}:${child.fromId}`;
|
|
366
339
|
if (!visited.has(childKey)) {
|
|
367
340
|
results.push({
|
|
368
|
-
id: child.
|
|
369
|
-
type: child.
|
|
341
|
+
id: child.fromId,
|
|
342
|
+
type: child.fromType,
|
|
370
343
|
depth: depth + 1,
|
|
371
344
|
relation: rel,
|
|
372
345
|
});
|
|
373
346
|
queue.push({
|
|
374
|
-
id: child.
|
|
375
|
-
type: child.
|
|
347
|
+
id: child.fromId,
|
|
348
|
+
type: child.fromType,
|
|
376
349
|
depth: depth + 1,
|
|
377
350
|
});
|
|
378
351
|
}
|
|
@@ -382,18 +355,13 @@ export class CodeEntityGraph {
|
|
|
382
355
|
return results;
|
|
383
356
|
}
|
|
384
357
|
/** 获取协议遵循关系 (className → 遵循的协议列表) */
|
|
385
|
-
getConformances(className) {
|
|
386
|
-
|
|
387
|
-
.prepare(`SELECT to_id FROM knowledge_edges
|
|
388
|
-
WHERE from_id = ? AND from_type IN ('class', 'category') AND relation = 'conforms'`)
|
|
389
|
-
.all(className);
|
|
390
|
-
return rows.map((r) => r.to_id);
|
|
358
|
+
async getConformances(className) {
|
|
359
|
+
return this.#edgeRepo.findConformances(className);
|
|
391
360
|
}
|
|
392
361
|
/**
|
|
393
362
|
* 查找两个实体间的路径 (BFS)
|
|
394
|
-
* @returns }
|
|
395
363
|
*/
|
|
396
|
-
findPath(fromId, fromType, toId, toType, maxDepth = 5) {
|
|
364
|
+
async findPath(fromId, fromType, toId, toType, maxDepth = 5) {
|
|
397
365
|
const visited = new Set();
|
|
398
366
|
const queue = [
|
|
399
367
|
{
|
|
@@ -403,7 +371,11 @@ export class CodeEntityGraph {
|
|
|
403
371
|
},
|
|
404
372
|
];
|
|
405
373
|
while (queue.length > 0) {
|
|
406
|
-
const
|
|
374
|
+
const current = queue.shift();
|
|
375
|
+
if (!current) {
|
|
376
|
+
break;
|
|
377
|
+
}
|
|
378
|
+
const { id, type, path } = current;
|
|
407
379
|
if (path.length >= maxDepth) {
|
|
408
380
|
continue;
|
|
409
381
|
}
|
|
@@ -412,20 +384,18 @@ export class CodeEntityGraph {
|
|
|
412
384
|
continue;
|
|
413
385
|
}
|
|
414
386
|
visited.add(key);
|
|
415
|
-
const neighbors = this.
|
|
416
|
-
.prepare(`SELECT to_id, to_type, relation, weight FROM knowledge_edges WHERE from_id = ? AND from_type = ?`)
|
|
417
|
-
.all(id, type);
|
|
387
|
+
const neighbors = await this.#edgeRepo.findOutgoing(id, type);
|
|
418
388
|
for (const n of neighbors) {
|
|
419
389
|
const step = {
|
|
420
390
|
from: { id, type },
|
|
421
|
-
to: { id: n.
|
|
391
|
+
to: { id: n.toId, type: n.toType },
|
|
422
392
|
relation: n.relation,
|
|
423
393
|
};
|
|
424
394
|
const newPath = [...path, step];
|
|
425
|
-
if (n.
|
|
395
|
+
if (n.toId === toId && n.toType === toType) {
|
|
426
396
|
return { found: true, path: newPath, depth: newPath.length };
|
|
427
397
|
}
|
|
428
|
-
queue.push({ id: n.
|
|
398
|
+
queue.push({ id: n.toId, type: n.toType, path: newPath });
|
|
429
399
|
}
|
|
430
400
|
}
|
|
431
401
|
return {
|
|
@@ -436,14 +406,17 @@ export class CodeEntityGraph {
|
|
|
436
406
|
}
|
|
437
407
|
/**
|
|
438
408
|
* 影响分析: 修改某实体后,哪些实体可能受影响
|
|
439
|
-
* @returns >}
|
|
440
409
|
*/
|
|
441
|
-
getImpactRadius(entityId, entityType, maxDepth = 3) {
|
|
410
|
+
async getImpactRadius(entityId, entityType, maxDepth = 3) {
|
|
442
411
|
const impacted = [];
|
|
443
412
|
const visited = new Set();
|
|
444
413
|
const queue = [{ id: entityId, type: entityType, depth: 0 }];
|
|
445
414
|
while (queue.length > 0) {
|
|
446
|
-
const
|
|
415
|
+
const current = queue.shift();
|
|
416
|
+
if (!current) {
|
|
417
|
+
break;
|
|
418
|
+
}
|
|
419
|
+
const { id, type, depth } = current;
|
|
447
420
|
if (depth >= maxDepth) {
|
|
448
421
|
continue;
|
|
449
422
|
}
|
|
@@ -453,22 +426,19 @@ export class CodeEntityGraph {
|
|
|
453
426
|
}
|
|
454
427
|
visited.add(key);
|
|
455
428
|
// 找出所有"依赖/引用此实体"的上游
|
|
456
|
-
const dependents = this.
|
|
457
|
-
.prepare(`SELECT from_id, from_type, relation FROM knowledge_edges
|
|
458
|
-
WHERE to_id = ? AND to_type = ?`)
|
|
459
|
-
.all(id, type);
|
|
429
|
+
const dependents = await this.#edgeRepo.findIncoming(id, type);
|
|
460
430
|
for (const dep of dependents) {
|
|
461
|
-
const depKey = `${dep.
|
|
431
|
+
const depKey = `${dep.fromType}:${dep.fromId}`;
|
|
462
432
|
if (!visited.has(depKey)) {
|
|
463
433
|
impacted.push({
|
|
464
|
-
id: dep.
|
|
465
|
-
type: dep.
|
|
434
|
+
id: dep.fromId,
|
|
435
|
+
type: dep.fromType,
|
|
466
436
|
relation: dep.relation,
|
|
467
437
|
depth: depth + 1,
|
|
468
438
|
});
|
|
469
439
|
queue.push({
|
|
470
|
-
id: dep.
|
|
471
|
-
type: dep.
|
|
440
|
+
id: dep.fromId,
|
|
441
|
+
type: dep.fromType,
|
|
472
442
|
depth: depth + 1,
|
|
473
443
|
});
|
|
474
444
|
}
|
|
@@ -477,44 +447,28 @@ export class CodeEntityGraph {
|
|
|
477
447
|
return impacted;
|
|
478
448
|
}
|
|
479
449
|
/** 项目拓扑概览 — 统计信息 + 关键度排名 */
|
|
480
|
-
getTopology() {
|
|
481
|
-
const entityStats = this.
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
const
|
|
486
|
-
.prepare(`SELECT relation, COUNT(*) as count FROM knowledge_edges GROUP BY relation`)
|
|
487
|
-
.all();
|
|
488
|
-
// 入度最高的实体 = 被依赖最多
|
|
489
|
-
const hotNodes = this.db
|
|
490
|
-
.prepare(`SELECT to_id, to_type, COUNT(*) as in_degree
|
|
491
|
-
FROM knowledge_edges
|
|
492
|
-
GROUP BY to_id, to_type
|
|
493
|
-
ORDER BY in_degree DESC LIMIT 15`)
|
|
494
|
-
.all();
|
|
450
|
+
async getTopology() {
|
|
451
|
+
const entityStats = await this.#entityRepo.countByType(this.projectRoot);
|
|
452
|
+
const edgeStats = await this.#edgeRepo.countByRelation();
|
|
453
|
+
const hotNodes = await this.#edgeRepo.getHotNodes(15);
|
|
454
|
+
const totalEntities = Object.values(entityStats).reduce((sum, c) => sum + c, 0);
|
|
455
|
+
const totalEdges = Object.values(edgeStats).reduce((sum, c) => sum + c, 0);
|
|
495
456
|
return {
|
|
496
|
-
entities:
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
edges: Object.fromEntries(edgeStats.map((s) => [
|
|
501
|
-
s.relation,
|
|
502
|
-
s.count,
|
|
503
|
-
])),
|
|
504
|
-
totalEntities: entityStats.reduce((sum, s) => sum + (s.count || 0), 0),
|
|
505
|
-
totalEdges: edgeStats.reduce((sum, s) => sum + (s.count || 0), 0),
|
|
457
|
+
entities: entityStats,
|
|
458
|
+
edges: edgeStats,
|
|
459
|
+
totalEntities,
|
|
460
|
+
totalEdges,
|
|
506
461
|
hotNodes: hotNodes.map((n) => ({
|
|
507
|
-
id: n.
|
|
508
|
-
type: n.
|
|
509
|
-
inDegree: n.
|
|
462
|
+
id: n.id,
|
|
463
|
+
type: n.type,
|
|
464
|
+
inDegree: n.inDegree,
|
|
510
465
|
})),
|
|
511
466
|
};
|
|
512
467
|
}
|
|
513
468
|
/** 生成 Agent 可用的图谱上下文 (Markdown) */
|
|
514
|
-
generateContextForAgent(options = {}) {
|
|
469
|
+
async generateContextForAgent(options = {}) {
|
|
515
470
|
const maxEntities = options.maxEntities || 30;
|
|
516
|
-
const
|
|
517
|
-
const topo = this.getTopology();
|
|
471
|
+
const topo = await this.getTopology();
|
|
518
472
|
if (topo.totalEntities === 0) {
|
|
519
473
|
return '';
|
|
520
474
|
}
|
|
@@ -535,11 +489,11 @@ export class CodeEntityGraph {
|
|
|
535
489
|
lines.push('');
|
|
536
490
|
}
|
|
537
491
|
// 类继承概览
|
|
538
|
-
const classes = this.listEntities('class', maxEntities);
|
|
492
|
+
const classes = await this.listEntities('class', maxEntities);
|
|
539
493
|
if (classes.length > 0) {
|
|
540
494
|
lines.push('### 类继承关系');
|
|
541
495
|
for (const cls of classes) {
|
|
542
|
-
const chain = this.getInheritanceChain(cls.entityId, 5);
|
|
496
|
+
const chain = await this.getInheritanceChain(cls.entityId, 5);
|
|
543
497
|
if (chain.length > 1) {
|
|
544
498
|
lines.push(`- \`${chain.join(' → ')}\``);
|
|
545
499
|
}
|
|
@@ -547,11 +501,11 @@ export class CodeEntityGraph {
|
|
|
547
501
|
lines.push('');
|
|
548
502
|
}
|
|
549
503
|
// 协议
|
|
550
|
-
const protocols = this.listEntities('protocol', 15);
|
|
504
|
+
const protocols = await this.listEntities('protocol', 15);
|
|
551
505
|
if (protocols.length > 0) {
|
|
552
506
|
lines.push('### 协议');
|
|
553
507
|
for (const p of protocols) {
|
|
554
|
-
const conformers = this.getDescendants(p.entityId, 'protocol', 1);
|
|
508
|
+
const conformers = await this.getDescendants(p.entityId, 'protocol', 1);
|
|
555
509
|
const cNames = conformers.map((c) => c.id).slice(0, 5);
|
|
556
510
|
lines.push(`- \`${p.name}\` ← ${cNames.length > 0 ? cNames.map((n) => `\`${n}\``).join(', ') : '(无遵循者)'}`);
|
|
557
511
|
}
|
|
@@ -559,37 +513,36 @@ export class CodeEntityGraph {
|
|
|
559
513
|
}
|
|
560
514
|
// 调用图热路径 (Phase 5)
|
|
561
515
|
try {
|
|
562
|
-
const hotCallees = this.
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
.all();
|
|
570
|
-
if (hotCallees.length > 0) {
|
|
571
|
-
lines.push('### 调用图热路径 (Call Graph Hot Paths)');
|
|
572
|
-
for (const row of hotCallees) {
|
|
573
|
-
// 查找前几个调用者
|
|
574
|
-
const topCallers = this.db
|
|
575
|
-
.prepare(`SELECT from_id FROM knowledge_edges
|
|
576
|
-
WHERE relation = 'calls' AND to_id = ?
|
|
577
|
-
LIMIT 3`)
|
|
578
|
-
.all(row.to_id);
|
|
516
|
+
const hotCallees = await this.#edgeRepo.getHotNodes(15);
|
|
517
|
+
// Filter for 'calls' relation — use countIncomingByRelation for each
|
|
518
|
+
const callHotPaths = [];
|
|
519
|
+
for (const node of hotCallees) {
|
|
520
|
+
const callCount = await this.#edgeRepo.countIncomingByRelation(node.id, 'calls');
|
|
521
|
+
if (callCount > 0) {
|
|
522
|
+
const topCallers = await this.#edgeRepo.findIncomingByRelation(node.id, 'calls');
|
|
579
523
|
const callerNames = topCallers
|
|
580
|
-
.
|
|
524
|
+
.slice(0, 3)
|
|
525
|
+
.map((c) => `\`${c.fromId}\``)
|
|
581
526
|
.join(', ');
|
|
582
|
-
|
|
527
|
+
callHotPaths.push({
|
|
528
|
+
toId: node.id,
|
|
529
|
+
callCount,
|
|
530
|
+
callerNames: `${callerNames}${topCallers.length > 3 ? '...' : ''}`,
|
|
531
|
+
});
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
if (callHotPaths.length > 0) {
|
|
535
|
+
lines.push('### 调用图热路径 (Call Graph Hot Paths)');
|
|
536
|
+
for (const row of callHotPaths.slice(0, 15)) {
|
|
537
|
+
lines.push(`- \`${row.toId}\` ← ${row.callCount} 次调用 (${row.callerNames})`);
|
|
583
538
|
}
|
|
584
539
|
lines.push('');
|
|
585
540
|
}
|
|
586
541
|
// 数据流边摘要
|
|
587
|
-
const dataFlowCount = this.
|
|
588
|
-
|
|
589
|
-
.get();
|
|
590
|
-
if (dataFlowCount && dataFlowCount.cnt > 0) {
|
|
542
|
+
const dataFlowCount = await this.#edgeRepo.countByRelationType('data_flow');
|
|
543
|
+
if (dataFlowCount > 0) {
|
|
591
544
|
lines.push(`### 数据流`);
|
|
592
|
-
lines.push(`- 数据流边: ${dataFlowCount
|
|
545
|
+
lines.push(`- 数据流边: ${dataFlowCount} 条`);
|
|
593
546
|
lines.push('');
|
|
594
547
|
}
|
|
595
548
|
}
|
|
@@ -607,90 +560,87 @@ export class CodeEntityGraph {
|
|
|
607
560
|
* @param callEdges
|
|
608
561
|
* @param dataFlowEdges
|
|
609
562
|
*/
|
|
610
|
-
populateCallGraph(callEdges, dataFlowEdges) {
|
|
563
|
+
async populateCallGraph(callEdges, dataFlowEdges) {
|
|
611
564
|
const t0 = Date.now();
|
|
612
565
|
let edges = 0;
|
|
613
566
|
let entities = 0;
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
for (const
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
continue;
|
|
621
|
-
}
|
|
622
|
-
registeredMethods.add(fqn);
|
|
623
|
-
const entityId = this._extractEntityId(fqn);
|
|
624
|
-
const entityName = entityId; // 短名
|
|
625
|
-
const filePath = fqn.includes('::') ? fqn.split('::')[0] : null;
|
|
626
|
-
this.#upsertEntity({
|
|
627
|
-
entityId,
|
|
628
|
-
entityType: 'method',
|
|
629
|
-
name: entityName,
|
|
630
|
-
filePath,
|
|
631
|
-
metadata: { fqn, source: 'phase5-call-graph' },
|
|
632
|
-
});
|
|
633
|
-
entities++;
|
|
567
|
+
// ── 注册方法实体 (确保 from/to 的 entity 存在) ──
|
|
568
|
+
const registeredMethods = new Set();
|
|
569
|
+
for (const edge of callEdges) {
|
|
570
|
+
for (const fqn of [edge.caller, edge.callee]) {
|
|
571
|
+
if (registeredMethods.has(fqn)) {
|
|
572
|
+
continue;
|
|
634
573
|
}
|
|
574
|
+
registeredMethods.add(fqn);
|
|
575
|
+
const entityId = this._extractEntityId(fqn);
|
|
576
|
+
const entityName = entityId; // 短名
|
|
577
|
+
const filePath = fqn.includes('::') ? fqn.split('::')[0] : null;
|
|
578
|
+
await this.#upsertEntity({
|
|
579
|
+
entityId,
|
|
580
|
+
entityType: 'method',
|
|
581
|
+
name: entityName,
|
|
582
|
+
filePath,
|
|
583
|
+
metadata: { fqn, source: 'phase5-call-graph' },
|
|
584
|
+
});
|
|
585
|
+
entities++;
|
|
635
586
|
}
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
if (edge.isAwait) {
|
|
651
|
-
agg.hasAwait = true;
|
|
652
|
-
}
|
|
587
|
+
}
|
|
588
|
+
// ── 调用边 (聚合同一 caller-callee 对的多次调用,解决 Issue #4) ──
|
|
589
|
+
const aggregated = new Map(); // key = "callerId|calleeId" → aggregated metadata
|
|
590
|
+
for (const edge of callEdges) {
|
|
591
|
+
const callerId = this._extractEntityId(edge.caller);
|
|
592
|
+
const calleeId = this._extractEntityId(edge.callee);
|
|
593
|
+
const key = `${callerId}|${calleeId}`;
|
|
594
|
+
if (aggregated.has(key)) {
|
|
595
|
+
const agg = aggregated.get(key);
|
|
596
|
+
agg.callCount++;
|
|
597
|
+
agg.callSites.push({ line: edge.line, isAwait: edge.isAwait });
|
|
598
|
+
// 提升权重: direct 优先
|
|
599
|
+
if (edge.resolveMethod === 'direct') {
|
|
600
|
+
agg.resolveMethod = 'direct';
|
|
653
601
|
}
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
callerId,
|
|
657
|
-
calleeId,
|
|
658
|
-
callType: edge.callType,
|
|
659
|
-
resolveMethod: edge.resolveMethod,
|
|
660
|
-
file: edge.file,
|
|
661
|
-
hasAwait: edge.isAwait,
|
|
662
|
-
callCount: 1,
|
|
663
|
-
callSites: [{ line: edge.line, isAwait: edge.isAwait }],
|
|
664
|
-
});
|
|
602
|
+
if (edge.isAwait) {
|
|
603
|
+
agg.hasAwait = true;
|
|
665
604
|
}
|
|
666
605
|
}
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
callType:
|
|
672
|
-
resolveMethod:
|
|
673
|
-
file:
|
|
674
|
-
|
|
675
|
-
callCount:
|
|
676
|
-
callSites:
|
|
606
|
+
else {
|
|
607
|
+
aggregated.set(key, {
|
|
608
|
+
callerId,
|
|
609
|
+
calleeId,
|
|
610
|
+
callType: edge.callType,
|
|
611
|
+
resolveMethod: edge.resolveMethod,
|
|
612
|
+
file: edge.file,
|
|
613
|
+
hasAwait: edge.isAwait,
|
|
614
|
+
callCount: 1,
|
|
615
|
+
callSites: [{ line: edge.line, isAwait: edge.isAwait }],
|
|
677
616
|
});
|
|
678
|
-
edges++;
|
|
679
617
|
}
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
}
|
|
692
|
-
|
|
693
|
-
|
|
618
|
+
}
|
|
619
|
+
for (const agg of aggregated.values()) {
|
|
620
|
+
await this.#addEdge(agg.callerId, 'method', agg.calleeId, 'method', 'calls', {
|
|
621
|
+
weight: agg.resolveMethod === 'direct' ? 1.0 : 0.6,
|
|
622
|
+
source: 'phase5-call-graph',
|
|
623
|
+
callType: agg.callType,
|
|
624
|
+
resolveMethod: agg.resolveMethod,
|
|
625
|
+
file: agg.file,
|
|
626
|
+
isAwait: agg.hasAwait,
|
|
627
|
+
callCount: agg.callCount,
|
|
628
|
+
callSites: agg.callSites.slice(0, 10), // 最多保留 10 个调用点
|
|
629
|
+
});
|
|
630
|
+
edges++;
|
|
631
|
+
}
|
|
632
|
+
// ── 数据流边 ──
|
|
633
|
+
for (const flow of dataFlowEdges) {
|
|
634
|
+
const fromId = this._extractEntityId(flow.from || '');
|
|
635
|
+
const toId = this._extractEntityId(flow.to || '');
|
|
636
|
+
await this.#addEdge(fromId, 'method', toId, 'method', 'data_flow', {
|
|
637
|
+
weight: 0.5,
|
|
638
|
+
source: 'phase5-data-flow',
|
|
639
|
+
flowType: flow.flowType || '',
|
|
640
|
+
direction: flow.direction || '',
|
|
641
|
+
});
|
|
642
|
+
edges++;
|
|
643
|
+
}
|
|
694
644
|
const result = { entitiesUpserted: entities, edgesCreated: edges, durationMs: Date.now() - t0 };
|
|
695
645
|
this.log.info(`[CodeEntityGraph] Call graph: ${callEdges.length} call edges, ${dataFlowEdges.length} data flow edges, ${entities} method entities (${result.durationMs}ms)`);
|
|
696
646
|
return result;
|
|
@@ -701,26 +651,29 @@ export class CodeEntityGraph {
|
|
|
701
651
|
* @param methodId "ClassName.methodName" 或 FQN
|
|
702
652
|
* @returns >}
|
|
703
653
|
*/
|
|
704
|
-
getCallers(methodId, maxDepth = 2) {
|
|
654
|
+
async getCallers(methodId, maxDepth = 2) {
|
|
705
655
|
const results = [];
|
|
706
656
|
const visited = new Set();
|
|
707
657
|
const queue = [{ id: methodId, depth: 0 }];
|
|
708
658
|
while (queue.length > 0) {
|
|
709
|
-
const
|
|
659
|
+
const current = queue.shift();
|
|
660
|
+
if (!current) {
|
|
661
|
+
break;
|
|
662
|
+
}
|
|
663
|
+
const { id, depth } = current;
|
|
710
664
|
if (depth >= maxDepth || visited.has(id)) {
|
|
711
665
|
continue;
|
|
712
666
|
}
|
|
713
667
|
visited.add(id);
|
|
714
|
-
const callers = this.
|
|
715
|
-
for (const
|
|
716
|
-
const meta = JSON.parse(row.metadata_json || '{}');
|
|
668
|
+
const callers = await this.#edgeRepo.findIncomingByRelation(id, 'calls');
|
|
669
|
+
for (const edge of callers) {
|
|
717
670
|
results.push({
|
|
718
|
-
caller:
|
|
671
|
+
caller: edge.fromId,
|
|
719
672
|
depth: depth + 1,
|
|
720
|
-
callType:
|
|
673
|
+
callType: edge.metadata?.callType || 'unknown',
|
|
721
674
|
});
|
|
722
675
|
if (depth + 1 < maxDepth) {
|
|
723
|
-
queue.push({ id:
|
|
676
|
+
queue.push({ id: edge.fromId, depth: depth + 1 });
|
|
724
677
|
}
|
|
725
678
|
}
|
|
726
679
|
}
|
|
@@ -732,26 +685,29 @@ export class CodeEntityGraph {
|
|
|
732
685
|
* @param methodId "ClassName.methodName" 或 FQN
|
|
733
686
|
* @returns >}
|
|
734
687
|
*/
|
|
735
|
-
getCallees(methodId, maxDepth = 2) {
|
|
688
|
+
async getCallees(methodId, maxDepth = 2) {
|
|
736
689
|
const results = [];
|
|
737
690
|
const visited = new Set();
|
|
738
691
|
const queue = [{ id: methodId, depth: 0 }];
|
|
739
692
|
while (queue.length > 0) {
|
|
740
|
-
const
|
|
693
|
+
const current = queue.shift();
|
|
694
|
+
if (!current) {
|
|
695
|
+
break;
|
|
696
|
+
}
|
|
697
|
+
const { id, depth } = current;
|
|
741
698
|
if (depth >= maxDepth || visited.has(id)) {
|
|
742
699
|
continue;
|
|
743
700
|
}
|
|
744
701
|
visited.add(id);
|
|
745
|
-
const callees = this.
|
|
746
|
-
for (const
|
|
747
|
-
const meta = JSON.parse(row.metadata_json || '{}');
|
|
702
|
+
const callees = await this.#edgeRepo.findOutgoingByRelation(id, 'calls');
|
|
703
|
+
for (const edge of callees) {
|
|
748
704
|
results.push({
|
|
749
|
-
callee:
|
|
705
|
+
callee: edge.toId,
|
|
750
706
|
depth: depth + 1,
|
|
751
|
-
callType:
|
|
707
|
+
callType: edge.metadata?.callType || 'unknown',
|
|
752
708
|
});
|
|
753
709
|
if (depth + 1 < maxDepth) {
|
|
754
|
-
queue.push({ id:
|
|
710
|
+
queue.push({ id: edge.toId, depth: depth + 1 });
|
|
755
711
|
}
|
|
756
712
|
}
|
|
757
713
|
}
|
|
@@ -764,11 +720,11 @@ export class CodeEntityGraph {
|
|
|
764
720
|
* @param methodId "ClassName.methodName"
|
|
765
721
|
* @returns }
|
|
766
722
|
*/
|
|
767
|
-
getCallImpactRadius(methodId) {
|
|
768
|
-
const callers = this.getCallers(methodId, 3);
|
|
723
|
+
async getCallImpactRadius(methodId) {
|
|
724
|
+
const callers = await this.getCallers(methodId, 3);
|
|
769
725
|
const affectedFiles = new Set();
|
|
770
726
|
for (const c of callers) {
|
|
771
|
-
const entity = this.getEntity(c.caller, 'method');
|
|
727
|
+
const entity = await this.getEntity(c.caller, 'method');
|
|
772
728
|
if (entity?.filePath) {
|
|
773
729
|
affectedFiles.add(entity.filePath);
|
|
774
730
|
}
|
|
@@ -792,15 +748,12 @@ export class CodeEntityGraph {
|
|
|
792
748
|
return fqn;
|
|
793
749
|
}
|
|
794
750
|
/** 清除项目的所有代码实体 (重新 populate 前调用) */
|
|
795
|
-
clearProject() {
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
.run();
|
|
802
|
-
});
|
|
803
|
-
run();
|
|
751
|
+
async clearProject() {
|
|
752
|
+
await this.#entityRepo.clearProject(this.projectRoot);
|
|
753
|
+
// 清除 AST 产出的边 + Phase 5 调用图边 (保留 recipe/module 边)
|
|
754
|
+
await this.#edgeRepo.deleteByMetadataLike('%ast-bootstrap%');
|
|
755
|
+
await this.#edgeRepo.deleteByMetadataLike('%ast-pattern-detection%');
|
|
756
|
+
await this.#edgeRepo.deleteByMetadataLike('%phase5-%');
|
|
804
757
|
this.log.info(`[CodeEntityGraph] Cleared entities for project: ${this.projectRoot}`);
|
|
805
758
|
}
|
|
806
759
|
/**
|
|
@@ -809,104 +762,57 @@ export class CodeEntityGraph {
|
|
|
809
762
|
* @param filePaths 变更文件的相对路径列表
|
|
810
763
|
* @returns }
|
|
811
764
|
*/
|
|
812
|
-
clearCallGraphForFiles(filePaths) {
|
|
765
|
+
async clearCallGraphForFiles(filePaths) {
|
|
813
766
|
if (!filePaths?.length) {
|
|
814
767
|
return { deletedEdges: 0, deletedEntities: 0 };
|
|
815
768
|
}
|
|
816
769
|
let deletedEdges = 0;
|
|
817
770
|
let deletedEntities = 0;
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
for (const filePath of filePaths) {
|
|
833
|
-
const result = deleteEntitiesStmt.run(filePath, this.projectRoot);
|
|
834
|
-
deletedEntities += result.changes;
|
|
835
|
-
}
|
|
836
|
-
});
|
|
837
|
-
run();
|
|
771
|
+
// 1. 删除相关 call edges (metadata_json 包含 file 字段)
|
|
772
|
+
for (const filePath of filePaths) {
|
|
773
|
+
// 匹配 metadata 中 "file":"xxx" 字段
|
|
774
|
+
const changes = await this.#edgeRepo.deleteByMetadataLike(`%"file":"${filePath}"%`, [
|
|
775
|
+
'calls',
|
|
776
|
+
'data_flow',
|
|
777
|
+
]);
|
|
778
|
+
deletedEdges += changes;
|
|
779
|
+
}
|
|
780
|
+
// 2. 删除相关 method 实体
|
|
781
|
+
for (const filePath of filePaths) {
|
|
782
|
+
const changes = await this.#entityRepo.deleteByFileAndType(filePath, 'method', this.projectRoot);
|
|
783
|
+
deletedEntities += changes;
|
|
784
|
+
}
|
|
838
785
|
this.log.info(`[CodeEntityGraph] Incremental clear: ${deletedEdges} edges, ${deletedEntities} entities ` +
|
|
839
786
|
`for ${filePaths.length} files`);
|
|
840
787
|
return { deletedEdges, deletedEntities };
|
|
841
788
|
}
|
|
842
789
|
// ────────────────────────────────────────────
|
|
843
|
-
// Private — Schema & Statements
|
|
844
|
-
// ────────────────────────────────────────────
|
|
845
|
-
#ensureTable() {
|
|
846
|
-
this.db.exec(`
|
|
847
|
-
CREATE TABLE IF NOT EXISTS code_entities (
|
|
848
|
-
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
849
|
-
entity_id TEXT NOT NULL,
|
|
850
|
-
entity_type TEXT NOT NULL,
|
|
851
|
-
project_root TEXT NOT NULL,
|
|
852
|
-
name TEXT NOT NULL,
|
|
853
|
-
file_path TEXT,
|
|
854
|
-
line_number INTEGER,
|
|
855
|
-
superclass TEXT,
|
|
856
|
-
protocols TEXT DEFAULT '[]',
|
|
857
|
-
metadata_json TEXT DEFAULT '{}',
|
|
858
|
-
created_at INTEGER NOT NULL,
|
|
859
|
-
updated_at INTEGER NOT NULL,
|
|
860
|
-
UNIQUE (entity_id, entity_type, project_root)
|
|
861
|
-
);
|
|
862
|
-
CREATE INDEX IF NOT EXISTS idx_ce_project ON code_entities(project_root);
|
|
863
|
-
CREATE INDEX IF NOT EXISTS idx_ce_type ON code_entities(entity_type);
|
|
864
|
-
CREATE INDEX IF NOT EXISTS idx_ce_name ON code_entities(name);
|
|
865
|
-
CREATE INDEX IF NOT EXISTS idx_ce_file ON code_entities(file_path);
|
|
866
|
-
CREATE INDEX IF NOT EXISTS idx_ce_superclass ON code_entities(superclass);
|
|
867
|
-
`);
|
|
868
|
-
}
|
|
869
|
-
#prepareStatements() {
|
|
870
|
-
this.stmts = {
|
|
871
|
-
upsert: this.db.prepare(`
|
|
872
|
-
INSERT INTO code_entities (entity_id, entity_type, project_root, name, file_path, line_number, superclass, protocols, metadata_json, created_at, updated_at)
|
|
873
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
874
|
-
ON CONFLICT (entity_id, entity_type, project_root) DO UPDATE SET
|
|
875
|
-
name = excluded.name,
|
|
876
|
-
file_path = COALESCE(excluded.file_path, code_entities.file_path),
|
|
877
|
-
line_number = COALESCE(excluded.line_number, code_entities.line_number),
|
|
878
|
-
superclass = COALESCE(excluded.superclass, code_entities.superclass),
|
|
879
|
-
protocols = excluded.protocols,
|
|
880
|
-
metadata_json = excluded.metadata_json,
|
|
881
|
-
updated_at = excluded.updated_at
|
|
882
|
-
`),
|
|
883
|
-
getEntity: this.db.prepare(`SELECT * FROM code_entities WHERE entity_id = ? AND entity_type = ? AND project_root = ?`),
|
|
884
|
-
listByType: this.db.prepare(`SELECT * FROM code_entities WHERE entity_type = ? AND project_root = ? ORDER BY name LIMIT ?`),
|
|
885
|
-
clearEntities: this.db.prepare(`DELETE FROM code_entities WHERE project_root = ?`),
|
|
886
|
-
addEdge: this.db.prepare(`
|
|
887
|
-
INSERT OR REPLACE INTO knowledge_edges (from_id, from_type, to_id, to_type, relation, weight, metadata_json, created_at, updated_at)
|
|
888
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
889
|
-
`),
|
|
890
|
-
// Phase 5: 调用图查询 (pre-prepared 避免每次调用都创建)
|
|
891
|
-
getCallers: this.db.prepare(`SELECT from_id, from_type, metadata_json FROM knowledge_edges
|
|
892
|
-
WHERE to_id = ? AND relation = 'calls'`),
|
|
893
|
-
getCallees: this.db.prepare(`SELECT to_id, to_type, metadata_json FROM knowledge_edges
|
|
894
|
-
WHERE from_id = ? AND relation = 'calls'`),
|
|
895
|
-
getEdge: this.db.prepare(`SELECT metadata_json FROM knowledge_edges
|
|
896
|
-
WHERE from_id = ? AND from_type = ? AND to_id = ? AND to_type = ? AND relation = ?`),
|
|
897
|
-
};
|
|
898
|
-
}
|
|
899
|
-
// ────────────────────────────────────────────
|
|
900
790
|
// Private — Helpers
|
|
901
791
|
// ────────────────────────────────────────────
|
|
902
|
-
#upsertEntity(entity) {
|
|
903
|
-
|
|
904
|
-
|
|
792
|
+
async #upsertEntity(entity) {
|
|
793
|
+
await this.#entityRepo.upsert({
|
|
794
|
+
entityId: entity.entityId,
|
|
795
|
+
entityType: entity.entityType,
|
|
796
|
+
projectRoot: this.projectRoot,
|
|
797
|
+
name: entity.name,
|
|
798
|
+
filePath: entity.filePath ?? null,
|
|
799
|
+
lineNumber: entity.line ?? null,
|
|
800
|
+
superclass: entity.superclass ?? null,
|
|
801
|
+
protocols: entity.protocols ?? [],
|
|
802
|
+
metadata: entity.metadata ?? {},
|
|
803
|
+
});
|
|
905
804
|
}
|
|
906
|
-
#addEdge(fromId, fromType, toId, toType, relation, metadata = {}) {
|
|
907
|
-
const now = Math.floor(Date.now() / 1000);
|
|
805
|
+
async #addEdge(fromId, fromType, toId, toType, relation, metadata = {}) {
|
|
908
806
|
try {
|
|
909
|
-
this.
|
|
807
|
+
await this.#edgeRepo.upsertEdge({
|
|
808
|
+
fromId,
|
|
809
|
+
fromType,
|
|
810
|
+
toId,
|
|
811
|
+
toType,
|
|
812
|
+
relation,
|
|
813
|
+
weight: metadata.weight || 1.0,
|
|
814
|
+
metadata,
|
|
815
|
+
});
|
|
910
816
|
}
|
|
911
817
|
catch (err) {
|
|
912
818
|
// Ignore duplicate edge errors
|
|
@@ -948,30 +854,30 @@ export class CodeEntityGraph {
|
|
|
948
854
|
};
|
|
949
855
|
return mapping[type] || 'related';
|
|
950
856
|
}
|
|
951
|
-
#
|
|
857
|
+
#mapRepoEdge(edge) {
|
|
952
858
|
return {
|
|
953
|
-
fromId:
|
|
954
|
-
fromType:
|
|
955
|
-
toId:
|
|
956
|
-
toType:
|
|
957
|
-
relation:
|
|
958
|
-
weight:
|
|
959
|
-
metadata:
|
|
859
|
+
fromId: edge.fromId,
|
|
860
|
+
fromType: edge.fromType,
|
|
861
|
+
toId: edge.toId,
|
|
862
|
+
toType: edge.toType,
|
|
863
|
+
relation: edge.relation,
|
|
864
|
+
weight: edge.weight,
|
|
865
|
+
metadata: edge.metadata,
|
|
960
866
|
};
|
|
961
867
|
}
|
|
962
|
-
#
|
|
868
|
+
#mapRepoEntity(entity) {
|
|
963
869
|
return {
|
|
964
|
-
entityId:
|
|
965
|
-
entityType:
|
|
966
|
-
name:
|
|
967
|
-
filePath:
|
|
968
|
-
line:
|
|
969
|
-
superclass:
|
|
970
|
-
protocols:
|
|
971
|
-
metadata:
|
|
972
|
-
projectRoot:
|
|
973
|
-
createdAt:
|
|
974
|
-
updatedAt:
|
|
870
|
+
entityId: entity.entityId,
|
|
871
|
+
entityType: entity.entityType,
|
|
872
|
+
name: entity.name,
|
|
873
|
+
filePath: entity.filePath,
|
|
874
|
+
line: entity.lineNumber,
|
|
875
|
+
superclass: entity.superclass,
|
|
876
|
+
protocols: entity.protocols,
|
|
877
|
+
metadata: entity.metadata,
|
|
878
|
+
projectRoot: entity.projectRoot,
|
|
879
|
+
createdAt: entity.createdAt,
|
|
880
|
+
updatedAt: entity.updatedAt,
|
|
975
881
|
};
|
|
976
882
|
}
|
|
977
883
|
}
|