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.
Files changed (211) hide show
  1. package/README.md +1 -0
  2. package/dashboard/dist/assets/icons-BMNb0V6L.js +1 -0
  3. package/dashboard/dist/assets/index-DHJ1Dj7u.css +1 -0
  4. package/dashboard/dist/assets/index-DV8biUkH.js +112 -0
  5. package/dashboard/dist/index.html +3 -3
  6. package/dist/bin/cli.js +7 -4
  7. package/dist/lib/agent/core/ChatAgentPrompts.js +57 -21
  8. package/dist/lib/agent/core/LoopContext.d.ts +1 -0
  9. package/dist/lib/agent/core/ToolExecutionPipeline.js +13 -0
  10. package/dist/lib/agent/memory/ActiveContext.d.ts +0 -2
  11. package/dist/lib/agent/memory/ActiveContext.js +0 -2
  12. package/dist/lib/agent/memory/MemoryEmbeddingStore.d.ts +49 -0
  13. package/dist/lib/agent/memory/MemoryEmbeddingStore.js +159 -0
  14. package/dist/lib/agent/memory/MemoryRetriever.d.ts +2 -0
  15. package/dist/lib/agent/memory/MemoryRetriever.js +25 -11
  16. package/dist/lib/agent/memory/MemoryStore.d.ts +8 -41
  17. package/dist/lib/agent/memory/MemoryStore.js +196 -261
  18. package/dist/lib/agent/memory/PersistentMemory.d.ts +2 -0
  19. package/dist/lib/agent/memory/PersistentMemory.js +4 -5
  20. package/dist/lib/agent/memory/SessionStore.d.ts +0 -2
  21. package/dist/lib/agent/memory/SessionStore.js +0 -2
  22. package/dist/lib/agent/tools/ast-graph.js +21 -19
  23. package/dist/lib/agent/tools/infrastructure.js +3 -2
  24. package/dist/lib/agent/tools/project-access.d.ts +2 -2
  25. package/dist/lib/agent/tools/project-access.js +5 -4
  26. package/dist/lib/bootstrap.js +2 -1
  27. package/dist/lib/cli/AiScanService.js +4 -17
  28. package/dist/lib/cli/KnowledgeSyncService.d.ts +7 -37
  29. package/dist/lib/cli/KnowledgeSyncService.js +23 -51
  30. package/dist/lib/core/ast/ProjectGraph.js +5 -27
  31. package/dist/lib/core/discovery/CustomConfigDiscoverer.d.ts +0 -2
  32. package/dist/lib/core/discovery/CustomConfigDiscoverer.js +0 -2
  33. package/dist/lib/domain/dimension/DimensionRegistry.d.ts +0 -2
  34. package/dist/lib/domain/dimension/DimensionRegistry.js +0 -2
  35. package/dist/lib/domain/dimension/DimensionSop.js +44 -33
  36. package/dist/lib/domain/dimension/UnifiedDimension.d.ts +0 -2
  37. package/dist/lib/domain/dimension/UnifiedDimension.js +0 -2
  38. package/dist/lib/domain/knowledge/Lifecycle.d.ts +26 -0
  39. package/dist/lib/domain/knowledge/Lifecycle.js +42 -0
  40. package/dist/lib/domain/knowledge/index.d.ts +2 -1
  41. package/dist/lib/domain/knowledge/index.js +1 -1
  42. package/dist/lib/external/mcp/handlers/bootstrap/pipeline/BootstrapSnapshot.d.ts +2 -1
  43. package/dist/lib/external/mcp/handlers/bootstrap/pipeline/BootstrapSnapshot.js +102 -153
  44. package/dist/lib/external/mcp/handlers/bootstrap/pipeline/orchestrator.js +33 -16
  45. package/dist/lib/external/mcp/handlers/bootstrap/shared/bootstrap-phases.d.ts +1 -1
  46. package/dist/lib/external/mcp/handlers/bootstrap/shared/bootstrap-phases.js +41 -37
  47. package/dist/lib/external/mcp/handlers/bootstrap-external.js +1 -1
  48. package/dist/lib/external/mcp/handlers/dimension-complete-external.js +7 -3
  49. package/dist/lib/external/mcp/handlers/evolve-external.d.ts +1 -0
  50. package/dist/lib/external/mcp/handlers/evolve-external.js +13 -16
  51. package/dist/lib/external/mcp/handlers/guard.js +15 -24
  52. package/dist/lib/external/mcp/handlers/panorama.js +9 -9
  53. package/dist/lib/external/mcp/handlers/rescan-external.js +7 -6
  54. package/dist/lib/external/mcp/handlers/rescan-internal.js +9 -5
  55. package/dist/lib/external/mcp/handlers/search.js +3 -1
  56. package/dist/lib/external/mcp/handlers/skill.js +4 -4
  57. package/dist/lib/external/mcp/handlers/structure.js +8 -12
  58. package/dist/lib/external/mcp/handlers/system.js +10 -34
  59. package/dist/lib/http/routes/ai.js +11 -13
  60. package/dist/lib/http/routes/guardReport.js +3 -5
  61. package/dist/lib/http/routes/panorama.js +12 -12
  62. package/dist/lib/http/routes/recipes.js +59 -8
  63. package/dist/lib/http/routes/remote.js +3 -13
  64. package/dist/lib/http/routes/search.js +11 -8
  65. package/dist/lib/infrastructure/audit/AuditLogger.d.ts +20 -3
  66. package/dist/lib/infrastructure/audit/AuditStore.d.ts +28 -29
  67. package/dist/lib/infrastructure/audit/AuditStore.js +81 -88
  68. package/dist/lib/infrastructure/database/drizzle/schema.d.ts +180 -2
  69. package/dist/lib/infrastructure/database/drizzle/schema.js +23 -3
  70. package/dist/lib/injection/ServiceContainer.js +7 -4
  71. package/dist/lib/injection/ServiceMap.d.ts +20 -0
  72. package/dist/lib/injection/modules/AppModule.js +2 -1
  73. package/dist/lib/injection/modules/GuardModule.js +5 -5
  74. package/dist/lib/injection/modules/InfraModule.js +60 -0
  75. package/dist/lib/injection/modules/KnowledgeModule.js +86 -51
  76. package/dist/lib/injection/modules/PanoramaModule.js +16 -10
  77. package/dist/lib/injection/modules/VectorModule.js +3 -0
  78. package/dist/lib/repository/audit/AuditRepository.d.ts +107 -0
  79. package/dist/lib/repository/audit/AuditRepository.js +272 -0
  80. package/dist/lib/repository/base/RepositoryBase.d.ts +46 -0
  81. package/dist/lib/repository/base/RepositoryBase.js +32 -0
  82. package/dist/lib/repository/bootstrap/BootstrapRepository.d.ts +94 -0
  83. package/dist/lib/repository/bootstrap/BootstrapRepository.js +246 -0
  84. package/dist/lib/repository/code/CodeEntityRepository.d.ts +91 -0
  85. package/dist/lib/repository/code/CodeEntityRepository.js +361 -0
  86. package/dist/lib/repository/delivery/DeliveryRepoAdapter.d.ts +39 -0
  87. package/dist/lib/repository/delivery/DeliveryRepoAdapter.js +23 -0
  88. package/dist/lib/repository/evolution/LifecycleEventRepository.d.ts +51 -0
  89. package/dist/lib/repository/evolution/LifecycleEventRepository.js +119 -0
  90. package/dist/lib/repository/evolution/ProposalRepository.d.ts +9 -12
  91. package/dist/lib/repository/evolution/ProposalRepository.js +114 -57
  92. package/dist/lib/repository/guard/GuardViolationRepository.d.ts +104 -0
  93. package/dist/lib/repository/guard/GuardViolationRepository.js +217 -0
  94. package/dist/lib/repository/knowledge/KnowledgeEdgeRepository.d.ts +129 -0
  95. package/dist/lib/repository/knowledge/KnowledgeEdgeRepository.js +475 -0
  96. package/dist/lib/repository/knowledge/KnowledgeFileStore.d.ts +39 -0
  97. package/dist/lib/repository/knowledge/KnowledgeFileStore.js +12 -0
  98. package/dist/lib/repository/knowledge/KnowledgeRepository.impl.d.ts +295 -11
  99. package/dist/lib/repository/knowledge/KnowledgeRepository.impl.js +608 -13
  100. package/dist/lib/repository/knowledge/KnowledgeUnitOfWork.d.ts +61 -0
  101. package/dist/lib/repository/knowledge/KnowledgeUnitOfWork.js +156 -0
  102. package/dist/lib/repository/memory/MemoryRepository.d.ts +90 -0
  103. package/dist/lib/repository/memory/MemoryRepository.js +260 -0
  104. package/dist/lib/repository/search/SearchRepoAdapter.d.ts +92 -0
  105. package/dist/lib/repository/search/SearchRepoAdapter.js +124 -0
  106. package/dist/lib/repository/session/SessionRepository.d.ts +46 -0
  107. package/dist/lib/repository/session/SessionRepository.js +110 -0
  108. package/dist/lib/repository/sourceref/RecipeSourceRefRepository.d.ts +66 -0
  109. package/dist/lib/repository/sourceref/RecipeSourceRefRepository.js +182 -0
  110. package/dist/lib/repository/sync/SyncRepoAdapter.d.ts +58 -0
  111. package/dist/lib/repository/sync/SyncRepoAdapter.js +58 -0
  112. package/dist/lib/service/bootstrap/UiStartupTasks.js +5 -6
  113. package/dist/lib/service/bootstrap/bootstrap-event-types.d.ts +0 -1
  114. package/dist/lib/service/bootstrap/bootstrap-event-types.js +0 -1
  115. package/dist/lib/service/cleanup/CleanupService.js +8 -4
  116. package/dist/lib/service/delivery/CursorDeliveryPipeline.js +6 -8
  117. package/dist/lib/service/evolution/ConsolidationAdvisor.d.ts +4 -9
  118. package/dist/lib/service/evolution/ConsolidationAdvisor.js +34 -70
  119. package/dist/lib/service/evolution/ContentPatcher.d.ts +4 -12
  120. package/dist/lib/service/evolution/ContentPatcher.js +48 -19
  121. package/dist/lib/service/evolution/ContradictionDetector.d.ts +3 -7
  122. package/dist/lib/service/evolution/ContradictionDetector.js +17 -24
  123. package/dist/lib/service/evolution/DecayDetector.d.ts +10 -9
  124. package/dist/lib/service/evolution/DecayDetector.js +63 -57
  125. package/dist/lib/service/evolution/EnhancementSuggester.d.ts +3 -9
  126. package/dist/lib/service/evolution/EnhancementSuggester.js +42 -86
  127. package/dist/lib/service/evolution/KnowledgeMetabolism.d.ts +4 -4
  128. package/dist/lib/service/evolution/KnowledgeMetabolism.js +102 -71
  129. package/dist/lib/service/evolution/ProposalExecutor.d.ts +5 -12
  130. package/dist/lib/service/evolution/ProposalExecutor.js +64 -69
  131. package/dist/lib/service/evolution/RecipeLifecycleSupervisor.d.ts +9 -14
  132. package/dist/lib/service/evolution/RecipeLifecycleSupervisor.js +94 -155
  133. package/dist/lib/service/evolution/RecipeRelevanceAuditor.d.ts +4 -1
  134. package/dist/lib/service/evolution/RecipeRelevanceAuditor.js +50 -49
  135. package/dist/lib/service/evolution/RedundancyAnalyzer.d.ts +3 -7
  136. package/dist/lib/service/evolution/RedundancyAnalyzer.js +15 -22
  137. package/dist/lib/service/evolution/StagingManager.d.ts +6 -15
  138. package/dist/lib/service/evolution/StagingManager.js +37 -95
  139. package/dist/lib/service/evolution/createSupersedeProposal.d.ts +1 -1
  140. package/dist/lib/service/evolution/createSupersedeProposal.js +7 -8
  141. package/dist/lib/service/guard/CoverageAnalyzer.d.ts +3 -7
  142. package/dist/lib/service/guard/CoverageAnalyzer.js +9 -11
  143. package/dist/lib/service/guard/GuardCheckEngine.d.ts +3 -0
  144. package/dist/lib/service/guard/GuardCheckEngine.js +14 -22
  145. package/dist/lib/service/guard/ReverseGuard.d.ts +4 -7
  146. package/dist/lib/service/guard/ReverseGuard.js +21 -31
  147. package/dist/lib/service/guard/ViolationsStore.d.ts +15 -21
  148. package/dist/lib/service/guard/ViolationsStore.js +75 -69
  149. package/dist/lib/service/knowledge/CodeEntityGraph.d.ts +39 -63
  150. package/dist/lib/service/knowledge/CodeEntityGraph.js +418 -512
  151. package/dist/lib/service/knowledge/ConfidenceRouter.js +18 -9
  152. package/dist/lib/service/knowledge/KnowledgeFileWriter.d.ts +2 -1
  153. package/dist/lib/service/knowledge/KnowledgeGraphService.d.ts +18 -60
  154. package/dist/lib/service/knowledge/KnowledgeGraphService.js +58 -109
  155. package/dist/lib/service/knowledge/KnowledgeService.d.ts +15 -1
  156. package/dist/lib/service/knowledge/KnowledgeService.js +76 -38
  157. package/dist/lib/service/knowledge/RecipeProductionGateway.d.ts +0 -2
  158. package/dist/lib/service/knowledge/RecipeProductionGateway.js +0 -2
  159. package/dist/lib/service/knowledge/SourceRefReconciler.d.ts +5 -13
  160. package/dist/lib/service/knowledge/SourceRefReconciler.js +58 -78
  161. package/dist/lib/service/panorama/CouplingAnalyzer.d.ts +5 -3
  162. package/dist/lib/service/panorama/CouplingAnalyzer.js +102 -39
  163. package/dist/lib/service/panorama/DimensionAnalyzer.d.ts +7 -4
  164. package/dist/lib/service/panorama/DimensionAnalyzer.js +72 -25
  165. package/dist/lib/service/panorama/LayerInferrer.js +1 -1
  166. package/dist/lib/service/panorama/ModuleDiscoverer.d.ts +7 -6
  167. package/dist/lib/service/panorama/ModuleDiscoverer.js +174 -82
  168. package/dist/lib/service/panorama/PanoramaAggregator.d.ts +10 -3
  169. package/dist/lib/service/panorama/PanoramaAggregator.js +67 -79
  170. package/dist/lib/service/panorama/PanoramaScanner.d.ts +5 -1
  171. package/dist/lib/service/panorama/PanoramaScanner.js +32 -31
  172. package/dist/lib/service/panorama/PanoramaService.d.ts +11 -8
  173. package/dist/lib/service/panorama/PanoramaService.js +41 -66
  174. package/dist/lib/service/panorama/PanoramaTypes.d.ts +3 -0
  175. package/dist/lib/service/panorama/RoleRefiner.d.ts +8 -5
  176. package/dist/lib/service/panorama/RoleRefiner.js +52 -283
  177. package/dist/lib/service/panorama/TechStackProfiler.js +7 -119
  178. package/dist/lib/service/quality/QualityScorer.d.ts +45 -26
  179. package/dist/lib/service/quality/QualityScorer.js +157 -83
  180. package/dist/lib/service/search/SearchEngine.d.ts +1 -0
  181. package/dist/lib/service/search/SearchEngine.js +32 -37
  182. package/dist/lib/service/signal/HitRecorder.js +5 -5
  183. package/dist/lib/service/skills/RuleRecallStrategy.js +7 -3
  184. package/dist/lib/service/skills/SignalCollector.d.ts +5 -8
  185. package/dist/lib/service/skills/SignalCollector.js +28 -55
  186. package/dist/lib/service/skills/SkillAdvisor.d.ts +7 -13
  187. package/dist/lib/service/skills/SkillAdvisor.js +30 -79
  188. package/dist/lib/service/vector/SyncCoordinator.d.ts +3 -1
  189. package/dist/lib/service/vector/SyncCoordinator.js +25 -3
  190. package/dist/lib/service/vector/VectorService.d.ts +2 -0
  191. package/dist/lib/service/vector/VectorService.js +3 -0
  192. package/dist/lib/service/wiki/WikiGenerator.js +1 -1
  193. package/dist/lib/shared/LanguageProfiles.d.ts +109 -0
  194. package/dist/lib/shared/LanguageProfiles.js +939 -0
  195. package/dist/lib/shared/LanguageService.d.ts +6 -0
  196. package/dist/lib/shared/LanguageService.js +16 -0
  197. package/dist/lib/shared/constants.d.ts +19 -19
  198. package/dist/lib/shared/constants.js +10 -10
  199. package/dist/lib/shared/schemas/mcp-tools.d.ts +1 -1
  200. package/dist/lib/types/project-snapshot-builder.d.ts +0 -1
  201. package/dist/lib/types/project-snapshot-builder.js +0 -1
  202. package/dist/lib/types/project-snapshot.d.ts +0 -1
  203. package/dist/lib/types/project-snapshot.js +0 -1
  204. package/dist/lib/types/snapshot-views.d.ts +0 -2
  205. package/dist/lib/types/snapshot-views.js +0 -1
  206. package/package.json +2 -1
  207. package/dashboard/dist/assets/icons-FHns2ypa.js +0 -1
  208. package/dashboard/dist/assets/index-BRJv5Y3r.js +0 -135
  209. package/dashboard/dist/assets/index-DzoB7kxK.css +0 -1
  210. package/dist/lib/repository/base/BaseRepository.d.ts +0 -53
  211. 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
- db;
29
+ #entityRepo;
30
+ #edgeRepo;
30
31
  log;
31
- stmts;
32
- constructor(db, options = {}) {
33
- this.db = typeof db?.getDb === 'function' ? db.getDb() : db;
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
- const run = this.db.transaction(() => {
57
- // ── ──
58
- for (const cls of astSummary.classes || []) {
59
- this.#upsertEntity({
60
- entityId: cls.name,
61
- entityType: cls.isCategory ? 'category' : 'class',
62
- name: cls.name,
63
- filePath: cls.file || null,
64
- line: cls.line || null,
65
- superclass: cls.superclass || null,
66
- protocols: cls.protocols || [],
67
- metadata: {
68
- endLine: cls.endLine,
69
- isCategory: cls.isCategory || false,
70
- },
71
- });
72
- entities++;
73
- }
74
- // ── 协议 ──
75
- for (const proto of astSummary.protocols || []) {
76
- this.#upsertEntity({
77
- entityId: proto.name,
78
- entityType: 'protocol',
79
- name: proto.name,
80
- filePath: proto.file || null,
81
- line: proto.line || null,
82
- protocols: proto.inherits || [],
83
- metadata: {
84
- methodCount: proto.methods?.length || 0,
85
- },
86
- });
87
- entities++;
88
- }
89
- // ── Category ──
90
- for (const cat of astSummary.categories || []) {
91
- const catId = `${cat.className}(${cat.categoryName})`;
92
- this.#upsertEntity({
93
- entityId: catId,
94
- entityType: 'category',
95
- name: catId,
96
- filePath: cat.file || null,
97
- line: cat.line || null,
98
- protocols: cat.protocols || [],
99
- metadata: {
100
- className: cat.className,
101
- categoryName: cat.categoryName,
102
- methodCount: cat.methods?.length || 0,
103
- },
104
- });
105
- entities++;
106
- }
107
- // ── 继承/遵循/扩展 (从 AST inheritanceGraph) ──
108
- for (const edge of astSummary.inheritanceGraph || []) {
109
- const fromType = this.#inferEntityType(edge.from, astSummary);
110
- const toType = this.#inferEntityType(edge.to, astSummary);
111
- this.#addEdge(edge.from, fromType, edge.to, toType, edge.type, {
112
- weight: 1.0,
113
- source: 'ast-bootstrap',
114
- });
115
- edges++;
116
- }
117
- // ── 设计模式 ( patternStats) ──
118
- for (const [patternType, stat] of Object.entries(astSummary.patternStats || {})) {
119
- const patternId = `pattern:${patternType}`;
120
- this.#upsertEntity({
121
- entityId: patternId,
122
- entityType: 'pattern',
123
- name: patternType,
124
- metadata: {
125
- count: stat.count,
126
- files: stat.files?.slice(0, 10),
127
- },
128
- });
129
- entities++;
130
- // 实例 uses_pattern
131
- for (const inst of (stat.instances || []).slice(0, 50)) {
132
- const className = inst.className || inst.name;
133
- if (className) {
134
- this.#addEdge(className, 'class', patternId, 'pattern', 'uses_pattern', {
135
- weight: 0.8,
136
- source: 'ast-pattern-detection',
137
- file: inst.file,
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 run = this.db.transaction(() => {
164
- for (const node of depGraphData.nodes || []) {
165
- const nodeObj = typeof node === 'string' ? { id: node, label: node } : node;
166
- this.#upsertEntity({
167
- entityId: nodeObj.id || nodeObj.label || String(node),
168
- entityType: 'module',
169
- name: nodeObj.label || nodeObj.id || String(node),
170
- metadata: {
171
- nodeType: nodeObj.type || 'module',
172
- ...(nodeObj.layer != null ? { layer: nodeObj.layer } : {}),
173
- ...(nodeObj.version != null ? { version: nodeObj.version } : {}),
174
- ...(nodeObj.group != null ? { group: nodeObj.group } : {}),
175
- ...(nodeObj.fullPath != null ? { fullPath: nodeObj.fullPath } : {}),
176
- ...(nodeObj.indirect != null ? { indirect: nodeObj.indirect } : {}),
177
- },
178
- });
179
- entities++;
180
- }
181
- // 存储 layers 元数据(如果存在)到特殊实体
182
- const layers = depGraphData.layers;
183
- if (layers?.length) {
184
- this.#upsertEntity({
185
- entityId: '__config_layers__',
186
- entityType: 'config',
187
- name: 'Config Layers',
188
- metadata: { layers },
189
- });
190
- entities++;
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 run = this.db.transaction(() => {
210
- for (const candidate of candidates) {
211
- const title = candidate.title || candidate.id || '';
212
- if (!title) {
213
- continue;
214
- }
215
- // 处理 Relations 对象或扁平数组
216
- let flatRelations;
217
- const rels = candidate.relations;
218
- if (typeof rels?.toFlatArray === 'function') {
219
- flatRelations = rels.toFlatArray();
220
- }
221
- else if (Array.isArray(candidate.relations)) {
222
- flatRelations = candidate.relations;
223
- }
224
- else if (candidate.relations && typeof candidate.relations === 'object') {
225
- // 桶结构 → 扁平
226
- flatRelations = [];
227
- for (const [type, list] of Object.entries(candidate.relations)) {
228
- for (const r of Array.isArray(list) ? list : []) {
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
- else {
224
+ }
225
+ else {
226
+ continue;
227
+ }
228
+ for (const rel of flatRelations) {
229
+ if (!rel.target) {
234
230
  continue;
235
231
  }
236
- for (const rel of flatRelations) {
237
- if (!rel.target) {
238
- continue;
239
- }
240
- // 映射关系类型到边类型
241
- const relation = this.#mapRelationType(rel.type);
242
- this.#addEdge(title, 'recipe', rel.target, 'recipe', relation, {
243
- weight: 0.7,
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 row;
250
+ async getEntity(entityId, entityType) {
251
+ let entity;
262
252
  if (entityType) {
263
- row = this.stmts.getEntity.get(entityId, entityType, this.projectRoot);
253
+ entity = await this.#entityRepo.findByEntityId(entityId, entityType, this.projectRoot);
264
254
  }
265
255
  else {
266
- row = this.db
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 row ? this.#mapEntity(row) : null;
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 rows = this.stmts.listByType.all(entityType, this.projectRoot, limit);
278
- return rows.map((r) => this.#mapEntity(r));
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 pattern = `%${query}%`;
286
- let sql = `SELECT * FROM code_entities WHERE project_root = ? AND name LIKE ?`;
287
- const params = [this.projectRoot, pattern];
288
- if (options.type) {
289
- sql += ` AND entity_type = ?`;
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.db
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.db
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 { outgoing, incoming };
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 parent = this.db
327
- .prepare(`SELECT to_id FROM knowledge_edges
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(parent.to_id);
334
- current = parent.to_id;
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 { id, type, depth } = queue.shift();
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.db
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.from_type}:${child.from_id}`;
338
+ const childKey = `${child.fromType}:${child.fromId}`;
366
339
  if (!visited.has(childKey)) {
367
340
  results.push({
368
- id: child.from_id,
369
- type: child.from_type,
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.from_id,
375
- type: child.from_type,
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
- const rows = this.db
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 { id, type, path } = queue.shift();
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.db
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.to_id, type: n.to_type },
391
+ to: { id: n.toId, type: n.toType },
422
392
  relation: n.relation,
423
393
  };
424
394
  const newPath = [...path, step];
425
- if (n.to_id === toId && n.to_type === toType) {
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.to_id, type: n.to_type, path: newPath });
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 { id, type, depth } = queue.shift();
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.db
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.from_type}:${dep.from_id}`;
431
+ const depKey = `${dep.fromType}:${dep.fromId}`;
462
432
  if (!visited.has(depKey)) {
463
433
  impacted.push({
464
- id: dep.from_id,
465
- type: dep.from_type,
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.from_id,
471
- type: dep.from_type,
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.db
482
- .prepare(`SELECT entity_type, COUNT(*) as count FROM code_entities
483
- WHERE project_root = ? GROUP BY entity_type`)
484
- .all(this.projectRoot);
485
- const edgeStats = this.db
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: Object.fromEntries(entityStats.map((s) => [
497
- s.entity_type,
498
- s.count,
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.to_id,
508
- type: n.to_type,
509
- inDegree: n.in_degree,
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 _maxEdges = options.maxEdges || 50;
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.db
563
- .prepare(`SELECT to_id, COUNT(*) as call_count
564
- FROM knowledge_edges
565
- WHERE relation = 'calls'
566
- GROUP BY to_id
567
- ORDER BY call_count DESC
568
- LIMIT 15`)
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
- .map((c) => `\`${c.from_id}\``)
524
+ .slice(0, 3)
525
+ .map((c) => `\`${c.fromId}\``)
581
526
  .join(', ');
582
- lines.push(`- \`${row.to_id}\` ← ${row.call_count} 次调用 (${callerNames}${topCallers.length < row.call_count ? '...' : ''})`);
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.db
588
- .prepare(`SELECT COUNT(*) as cnt FROM knowledge_edges WHERE relation = 'data_flow'`)
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.cnt} 条`);
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
- const run = this.db.transaction(() => {
615
- // ── 注册方法实体 (确保 from/to 的 entity 存在) ──
616
- const registeredMethods = new Set();
617
- for (const edge of callEdges) {
618
- for (const fqn of [edge.caller, edge.callee]) {
619
- if (registeredMethods.has(fqn)) {
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
- // ── 调用边 (聚合同一 caller-callee 对的多次调用,解决 Issue #4) ──
637
- const aggregated = new Map(); // key = "callerId|calleeId" → aggregated metadata
638
- for (const edge of callEdges) {
639
- const callerId = this._extractEntityId(edge.caller);
640
- const calleeId = this._extractEntityId(edge.callee);
641
- const key = `${callerId}|${calleeId}`;
642
- if (aggregated.has(key)) {
643
- const agg = aggregated.get(key);
644
- agg.callCount++;
645
- agg.callSites.push({ line: edge.line, isAwait: edge.isAwait });
646
- // 提升权重: direct 优先
647
- if (edge.resolveMethod === 'direct') {
648
- agg.resolveMethod = 'direct';
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
- else {
655
- aggregated.set(key, {
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
- for (const agg of aggregated.values()) {
668
- this.#addEdge(agg.callerId, 'method', agg.calleeId, 'method', 'calls', {
669
- weight: agg.resolveMethod === 'direct' ? 1.0 : 0.6,
670
- source: 'phase5-call-graph',
671
- callType: agg.callType,
672
- resolveMethod: agg.resolveMethod,
673
- file: agg.file,
674
- isAwait: agg.hasAwait,
675
- callCount: agg.callCount,
676
- callSites: agg.callSites.slice(0, 10), // 最多保留 10 个调用点
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
- for (const flow of dataFlowEdges) {
682
- const fromId = this._extractEntityId(flow.from || '');
683
- const toId = this._extractEntityId(flow.to || '');
684
- this.#addEdge(fromId, 'method', toId, 'method', 'data_flow', {
685
- weight: 0.5,
686
- source: 'phase5-data-flow',
687
- flowType: flow.flowType || '',
688
- direction: flow.direction || '',
689
- });
690
- edges++;
691
- }
692
- });
693
- run();
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 { id, depth } = queue.shift();
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.stmts.getCallers.all(id);
715
- for (const row of callers) {
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: row.from_id,
671
+ caller: edge.fromId,
719
672
  depth: depth + 1,
720
- callType: meta.callType || 'unknown',
673
+ callType: edge.metadata?.callType || 'unknown',
721
674
  });
722
675
  if (depth + 1 < maxDepth) {
723
- queue.push({ id: row.from_id, depth: depth + 1 });
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 { id, depth } = queue.shift();
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.stmts.getCallees.all(id);
746
- for (const row of callees) {
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: row.to_id,
705
+ callee: edge.toId,
750
706
  depth: depth + 1,
751
- callType: meta.callType || 'unknown',
707
+ callType: edge.metadata?.callType || 'unknown',
752
708
  });
753
709
  if (depth + 1 < maxDepth) {
754
- queue.push({ id: row.to_id, depth: depth + 1 });
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
- const run = this.db.transaction(() => {
797
- this.stmts.clearEntities.run(this.projectRoot);
798
- // 清除 AST 产出的边 + Phase 5 调用图边 (保留 recipe/module 边)
799
- this.db
800
- .prepare(`DELETE FROM knowledge_edges WHERE metadata_json LIKE '%ast-bootstrap%' OR metadata_json LIKE '%ast-pattern-detection%' OR metadata_json LIKE '%phase5-%'`)
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
- const run = this.db.transaction(() => {
819
- // 1. 删除相关 call edges (metadata_json 包含 file 字段)
820
- const deleteEdgesStmt = this.db.prepare(`DELETE FROM knowledge_edges
821
- WHERE metadata_json LIKE ?
822
- AND (relation = 'calls' OR relation = 'data_flow')
823
- AND metadata_json LIKE '%phase5-%'`);
824
- for (const filePath of filePaths) {
825
- // 匹配 metadata 中 "file":"xxx" 字段
826
- const result = deleteEdgesStmt.run(`%"file":"${filePath}"%`);
827
- deletedEdges += result.changes;
828
- }
829
- // 2. 删除相关 method 实体
830
- const deleteEntitiesStmt = this.db.prepare(`DELETE FROM code_entities
831
- WHERE file_path = ? AND entity_type = 'method' AND project_root = ?`);
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
- const now = Math.floor(Date.now() / 1000);
904
- this.stmts.upsert.run(entity.entityId, entity.entityType, this.projectRoot, entity.name, entity.filePath || null, entity.line || null, entity.superclass || null, JSON.stringify(entity.protocols || []), JSON.stringify(entity.metadata || {}), now, now);
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.stmts.addEdge.run(fromId, fromType, toId, toType, relation, metadata.weight || 1.0, JSON.stringify(metadata), now, now);
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
- #mapEdge(row) {
857
+ #mapRepoEdge(edge) {
952
858
  return {
953
- fromId: row.from_id,
954
- fromType: row.from_type,
955
- toId: row.to_id,
956
- toType: row.to_type,
957
- relation: row.relation,
958
- weight: row.weight,
959
- metadata: JSON.parse(row.metadata_json || '{}'),
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
- #mapEntity(row) {
868
+ #mapRepoEntity(entity) {
963
869
  return {
964
- entityId: row.entity_id,
965
- entityType: row.entity_type,
966
- name: row.name,
967
- filePath: row.file_path,
968
- line: row.line_number,
969
- superclass: row.superclass,
970
- protocols: JSON.parse(row.protocols || '[]'),
971
- metadata: JSON.parse(row.metadata_json || '{}'),
972
- projectRoot: row.project_root,
973
- createdAt: row.created_at,
974
- updatedAt: row.updated_at,
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
  }