autosnippet 3.2.7 → 3.2.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 (60) hide show
  1. package/bin/cli.js +7 -0
  2. package/dashboard/dist/assets/index-D5jiDBQG.css +1 -0
  3. package/dashboard/dist/assets/{index-DfHY_3ln.js → index-e5OKj-Ni.js} +38 -38
  4. package/dashboard/dist/index.html +2 -2
  5. package/lib/cli/AiScanService.js +3 -3
  6. package/lib/core/AstAnalyzer.js +26 -4
  7. package/lib/core/analysis/CallEdgeResolver.js +402 -0
  8. package/lib/core/analysis/CallGraphAnalyzer.js +367 -0
  9. package/lib/core/analysis/CallSiteExtractor.js +629 -0
  10. package/lib/core/analysis/DataFlowInferrer.js +57 -0
  11. package/lib/core/analysis/ImportPathResolver.js +189 -0
  12. package/lib/core/analysis/ImportRecord.js +105 -0
  13. package/lib/core/analysis/SymbolTableBuilder.js +211 -0
  14. package/lib/core/ast/ProjectGraph.js +8 -0
  15. package/lib/core/ast/lang-dart.js +352 -5
  16. package/lib/core/ast/lang-go.js +212 -10
  17. package/lib/core/ast/lang-java.js +205 -1
  18. package/lib/core/ast/lang-kotlin.js +330 -1
  19. package/lib/core/ast/lang-python.js +31 -2
  20. package/lib/core/ast/lang-rust.js +284 -3
  21. package/lib/core/ast/lang-swift.js +180 -1
  22. package/lib/core/ast/lang-typescript.js +290 -1
  23. package/lib/external/mcp/McpServer.js +1 -0
  24. package/lib/external/mcp/handlers/bootstrap/MissionBriefingBuilder.js +21 -0
  25. package/lib/external/mcp/handlers/bootstrap/pipeline/EpisodicMemory.js +5 -4
  26. package/lib/external/mcp/handlers/bootstrap/pipeline/dimension-configs.js +2 -1
  27. package/lib/external/mcp/handlers/bootstrap/pipeline/orchestrator.js +70 -4
  28. package/lib/external/mcp/handlers/bootstrap/shared/bootstrap-phases.js +95 -1
  29. package/lib/external/mcp/handlers/bootstrap-external.js +9 -2
  30. package/lib/external/mcp/handlers/bootstrap-internal.js +17 -6
  31. package/lib/external/mcp/handlers/consolidated.js +9 -0
  32. package/lib/external/mcp/handlers/guard.js +3 -3
  33. package/lib/external/mcp/handlers/structure.js +62 -0
  34. package/lib/external/mcp/handlers/wiki-external.js +66 -3
  35. package/lib/external/mcp/tools.js +36 -1
  36. package/lib/http/routes/remote.js +15 -15
  37. package/lib/injection/ServiceContainer.js +6 -11
  38. package/lib/platform/ios/index.js +2 -2
  39. package/lib/platform/ios/spm/PackageSwiftParser.js +14 -3
  40. package/lib/platform/ios/spm/SpmDiscoverer.js +123 -17
  41. package/lib/platform/ios/spm/{SpmService.js → SpmHelper.js} +43 -675
  42. package/lib/platform/ios/xcode/XcodeWriteUtils.js +1 -1
  43. package/lib/service/chat/ChatAgent.js +1 -1
  44. package/lib/service/chat/ChatAgentPrompts.js +13 -1
  45. package/lib/service/chat/ExplorationTracker.js +52 -8
  46. package/lib/service/chat/HandoffProtocol.js +19 -1
  47. package/lib/service/chat/WorkingMemory.js +3 -1
  48. package/lib/service/chat/memory/ActiveContext.js +3 -1
  49. package/lib/service/chat/memory/SessionStore.js +4 -3
  50. package/lib/service/chat/tools/ast-graph.js +229 -32
  51. package/lib/service/chat/tools/index.js +6 -1
  52. package/lib/service/chat/tools/infrastructure.js +5 -0
  53. package/lib/service/cursor/CursorDeliveryPipeline.js +167 -1
  54. package/lib/service/knowledge/CodeEntityGraph.js +327 -2
  55. package/lib/service/knowledge/KnowledgeService.js +5 -1
  56. package/lib/service/module/ModuleService.js +9 -0
  57. package/lib/service/wiki/WikiGenerator.js +1 -1
  58. package/lib/shared/PathGuard.js +1 -1
  59. package/package.json +1 -1
  60. package/dashboard/dist/assets/index-BaGY7kJI.css +0 -1
@@ -664,19 +664,281 @@ export class CodeEntityGraph {
664
664
  lines.push('');
665
665
  }
666
666
 
667
+ // 调用图热路径 (Phase 5)
668
+ try {
669
+ const hotCallees = this.db
670
+ .prepare(
671
+ `SELECT to_id, COUNT(*) as call_count
672
+ FROM knowledge_edges
673
+ WHERE relation = 'calls'
674
+ GROUP BY to_id
675
+ ORDER BY call_count DESC
676
+ LIMIT 15`
677
+ )
678
+ .all();
679
+
680
+ if (hotCallees.length > 0) {
681
+ lines.push('### 调用图热路径 (Call Graph Hot Paths)');
682
+ for (const row of hotCallees) {
683
+ // 查找前几个调用者
684
+ const topCallers = this.db
685
+ .prepare(
686
+ `SELECT from_id FROM knowledge_edges
687
+ WHERE relation = 'calls' AND to_id = ?
688
+ LIMIT 3`
689
+ )
690
+ .all(row.to_id);
691
+ const callerNames = topCallers.map((c) => `\`${c.from_id}\``).join(', ');
692
+ lines.push(
693
+ `- \`${row.to_id}\` ← ${row.call_count} 次调用 (${callerNames}${topCallers.length < row.call_count ? '...' : ''})`
694
+ );
695
+ }
696
+ lines.push('');
697
+ }
698
+
699
+ // 数据流边摘要
700
+ const dataFlowCount = this.db
701
+ .prepare(
702
+ `SELECT COUNT(*) as cnt FROM knowledge_edges WHERE relation = 'data_flow'`
703
+ )
704
+ .get();
705
+
706
+ if (dataFlowCount?.cnt > 0) {
707
+ lines.push(`### 数据流`);
708
+ lines.push(`- 数据流边: ${dataFlowCount.cnt} 条`);
709
+ lines.push('');
710
+ }
711
+ } catch (_e) {
712
+ // 调用图数据可能尚未填充, 静默跳过
713
+ }
714
+
667
715
  return lines.join('\n');
668
716
  }
669
717
 
718
+ // ────────────────────────────────────────────
719
+ // Public API — Phase 5: 调用图
720
+ // ────────────────────────────────────────────
721
+
722
+ /**
723
+ * 从解析后的调用边填充图谱 (Phase 5)
724
+ *
725
+ * @param {Array<{ caller: string, callee: string, callType: string, resolveMethod: string, line: number, file: string, isAwait: boolean }>} callEdges
726
+ * @param {Array<{ from: string, to: string, flowType: string, direction: string }>} dataFlowEdges
727
+ * @returns {GraphPopulateResult}
728
+ */
729
+ populateCallGraph(callEdges, dataFlowEdges) {
730
+ const t0 = Date.now();
731
+ let edges = 0;
732
+ let entities = 0;
733
+
734
+ const run = this.db.transaction(() => {
735
+ // ── 注册方法实体 (确保 from/to 的 entity 存在) ──
736
+ const registeredMethods = new Set();
737
+ for (const edge of callEdges) {
738
+ for (const fqn of [edge.caller, edge.callee]) {
739
+ if (registeredMethods.has(fqn)) continue;
740
+ registeredMethods.add(fqn);
741
+
742
+ const entityId = this._extractEntityId(fqn);
743
+ const entityName = entityId; // 短名
744
+ const filePath = fqn.includes('::') ? fqn.split('::')[0] : null;
745
+
746
+ this.#upsertEntity({
747
+ entityId,
748
+ entityType: 'method',
749
+ name: entityName,
750
+ filePath,
751
+ metadata: { fqn, source: 'phase5-call-graph' },
752
+ });
753
+ entities++;
754
+ }
755
+ }
756
+
757
+ // ── 调用边 (聚合同一 caller-callee 对的多次调用,解决 Issue #4) ──
758
+ const aggregated = new Map(); // key = "callerId|calleeId" → aggregated metadata
759
+ for (const edge of callEdges) {
760
+ const callerId = this._extractEntityId(edge.caller);
761
+ const calleeId = this._extractEntityId(edge.callee);
762
+ const key = `${callerId}|${calleeId}`;
763
+
764
+ if (aggregated.has(key)) {
765
+ const agg = aggregated.get(key);
766
+ agg.callCount++;
767
+ agg.callSites.push({ line: edge.line, isAwait: edge.isAwait });
768
+ // 提升权重: direct 优先
769
+ if (edge.resolveMethod === 'direct') agg.resolveMethod = 'direct';
770
+ if (edge.isAwait) agg.hasAwait = true;
771
+ } else {
772
+ aggregated.set(key, {
773
+ callerId,
774
+ calleeId,
775
+ callType: edge.callType,
776
+ resolveMethod: edge.resolveMethod,
777
+ file: edge.file,
778
+ hasAwait: edge.isAwait,
779
+ callCount: 1,
780
+ callSites: [{ line: edge.line, isAwait: edge.isAwait }],
781
+ });
782
+ }
783
+ }
784
+
785
+ for (const agg of aggregated.values()) {
786
+ this.#addEdge(agg.callerId, 'method', agg.calleeId, 'method', 'calls', {
787
+ weight: agg.resolveMethod === 'direct' ? 1.0 : 0.6,
788
+ source: 'phase5-call-graph',
789
+ callType: agg.callType,
790
+ resolveMethod: agg.resolveMethod,
791
+ file: agg.file,
792
+ isAwait: agg.hasAwait,
793
+ callCount: agg.callCount,
794
+ callSites: agg.callSites.slice(0, 10), // 最多保留 10 个调用点
795
+ });
796
+ edges++;
797
+ }
798
+
799
+ // ── 数据流边 ──
800
+ for (const flow of dataFlowEdges) {
801
+ const fromId = this._extractEntityId(flow.from);
802
+ const toId = this._extractEntityId(flow.to);
803
+
804
+ this.#addEdge(fromId, 'method', toId, 'method', 'data_flow', {
805
+ weight: 0.5,
806
+ source: 'phase5-data-flow',
807
+ flowType: flow.flowType,
808
+ direction: flow.direction,
809
+ });
810
+ edges++;
811
+ }
812
+ });
813
+
814
+ run();
815
+
816
+ const result = { entitiesUpserted: entities, edgesCreated: edges, durationMs: Date.now() - t0 };
817
+ this.log.info(
818
+ `[CodeEntityGraph] Call graph: ${callEdges.length} call edges, ${dataFlowEdges.length} data flow edges, ${entities} method entities (${result.durationMs}ms)`
819
+ );
820
+ return result;
821
+ }
822
+
823
+ /**
824
+ * 获取调用者 — 谁调用了这个方法?
825
+ *
826
+ * @param {string} methodId — "ClassName.methodName" 或 FQN
827
+ * @param {number} [maxDepth=2]
828
+ * @returns {Array<{ caller: string, depth: number, callType: string }>}
829
+ */
830
+ getCallers(methodId, maxDepth = 2) {
831
+ const results = [];
832
+ const visited = new Set();
833
+ const queue = [{ id: methodId, depth: 0 }];
834
+
835
+ while (queue.length > 0) {
836
+ const { id, depth } = queue.shift();
837
+ if (depth >= maxDepth || visited.has(id)) continue;
838
+ visited.add(id);
839
+
840
+ const callers = this.stmts.getCallers.all(id);
841
+
842
+ for (const row of callers) {
843
+ const meta = JSON.parse(row.metadata_json || '{}');
844
+ results.push({
845
+ caller: row.from_id,
846
+ depth: depth + 1,
847
+ callType: meta.callType || 'unknown',
848
+ });
849
+ if (depth + 1 < maxDepth) {
850
+ queue.push({ id: row.from_id, depth: depth + 1 });
851
+ }
852
+ }
853
+ }
854
+
855
+ return results;
856
+ }
857
+
858
+ /**
859
+ * 获取被调用者 — 这个方法调用了谁?
860
+ *
861
+ * @param {string} methodId — "ClassName.methodName" 或 FQN
862
+ * @param {number} [maxDepth=2]
863
+ * @returns {Array<{ callee: string, depth: number, callType: string }>}
864
+ */
865
+ getCallees(methodId, maxDepth = 2) {
866
+ const results = [];
867
+ const visited = new Set();
868
+ const queue = [{ id: methodId, depth: 0 }];
869
+
870
+ while (queue.length > 0) {
871
+ const { id, depth } = queue.shift();
872
+ if (depth >= maxDepth || visited.has(id)) continue;
873
+ visited.add(id);
874
+
875
+ const callees = this.stmts.getCallees.all(id);
876
+
877
+ for (const row of callees) {
878
+ const meta = JSON.parse(row.metadata_json || '{}');
879
+ results.push({
880
+ callee: row.to_id,
881
+ depth: depth + 1,
882
+ callType: meta.callType || 'unknown',
883
+ });
884
+ if (depth + 1 < maxDepth) {
885
+ queue.push({ id: row.to_id, depth: depth + 1 });
886
+ }
887
+ }
888
+ }
889
+
890
+ return results;
891
+ }
892
+
893
+ /**
894
+ * 获取方法的 Impact Radius (基于调用图)
895
+ * — 修改此方法可能影响哪些上游方法?
896
+ *
897
+ * @param {string} methodId — "ClassName.methodName"
898
+ * @returns {{ directCallers: number, transitiveCallers: number, affectedFiles: string[] }}
899
+ */
900
+ getCallImpactRadius(methodId) {
901
+ const callers = this.getCallers(methodId, 3);
902
+ const affectedFiles = new Set();
903
+
904
+ for (const c of callers) {
905
+ const entity = this.getEntity(c.caller, 'method');
906
+ if (entity?.filePath) affectedFiles.add(entity.filePath);
907
+ }
908
+
909
+ return {
910
+ directCallers: callers.filter((c) => c.depth === 1).length,
911
+ transitiveCallers: callers.length,
912
+ affectedFiles: [...affectedFiles],
913
+ };
914
+ }
915
+
916
+ /**
917
+ * 从 FQN 中提取短 Entity ID
918
+ *
919
+ * "src/service/UserService.ts::UserService.getUser" → "UserService.getUser"
920
+ * "src/utils/helpers.ts::formatDate" → "formatDate"
921
+ *
922
+ * @param {string} fqn
923
+ * @returns {string}
924
+ */
925
+ _extractEntityId(fqn) {
926
+ if (fqn.includes('::')) {
927
+ return fqn.split('::')[1];
928
+ }
929
+ return fqn;
930
+ }
931
+
670
932
  /**
671
933
  * 清除项目的所有代码实体 (重新 populate 前调用)
672
934
  */
673
935
  clearProject() {
674
936
  const run = this.db.transaction(() => {
675
937
  this.stmts.clearEntities.run(this.projectRoot);
676
- // 只清除 AST 产出的边 (保留 recipe/module 边)
938
+ // 清除 AST 产出的边 + Phase 5 调用图边 (保留 recipe/module 边)
677
939
  this.db
678
940
  .prepare(
679
- `DELETE FROM knowledge_edges WHERE metadata_json LIKE '%ast-bootstrap%' OR metadata_json LIKE '%ast-pattern-detection%'`
941
+ `DELETE FROM knowledge_edges WHERE metadata_json LIKE '%ast-bootstrap%' OR metadata_json LIKE '%ast-pattern-detection%' OR metadata_json LIKE '%phase5-%'`
680
942
  )
681
943
  .run();
682
944
  });
@@ -684,6 +946,55 @@ export class CodeEntityGraph {
684
946
  this.log.info(`[CodeEntityGraph] Cleared entities for project: ${this.projectRoot}`);
685
947
  }
686
948
 
949
+ /**
950
+ * 增量清除 — 仅删除指定文件的 call graph 边和 method 实体
951
+ *
952
+ * @param {string[]} filePaths — 变更文件的相对路径列表
953
+ * @returns {{ deletedEdges: number, deletedEntities: number }}
954
+ */
955
+ clearCallGraphForFiles(filePaths) {
956
+ if (!filePaths?.length) return { deletedEdges: 0, deletedEntities: 0 };
957
+
958
+ let deletedEdges = 0;
959
+ let deletedEntities = 0;
960
+
961
+ const run = this.db.transaction(() => {
962
+ // 1. 删除相关 call edges (metadata_json 包含 file 字段)
963
+ const deleteEdgesStmt = this.db.prepare(
964
+ `DELETE FROM knowledge_edges
965
+ WHERE metadata_json LIKE ?
966
+ AND (relation = 'calls' OR relation = 'data_flow')
967
+ AND metadata_json LIKE '%phase5-%'`
968
+ );
969
+
970
+ for (const filePath of filePaths) {
971
+ // 匹配 metadata 中 "file":"xxx" 字段
972
+ const result = deleteEdgesStmt.run(`%"file":"${filePath}"%`);
973
+ deletedEdges += result.changes;
974
+ }
975
+
976
+ // 2. 删除相关 method 实体
977
+ const deleteEntitiesStmt = this.db.prepare(
978
+ `DELETE FROM code_entities
979
+ WHERE file_path = ? AND entity_type = 'method' AND project_root = ?`
980
+ );
981
+
982
+ for (const filePath of filePaths) {
983
+ const result = deleteEntitiesStmt.run(filePath, this.projectRoot);
984
+ deletedEntities += result.changes;
985
+ }
986
+ });
987
+
988
+ run();
989
+
990
+ this.log.info(
991
+ `[CodeEntityGraph] Incremental clear: ${deletedEdges} edges, ${deletedEntities} entities ` +
992
+ `for ${filePaths.length} files`
993
+ );
994
+
995
+ return { deletedEdges, deletedEntities };
996
+ }
997
+
687
998
  // ────────────────────────────────────────────
688
999
  // Private — Schema & Statements
689
1000
  // ────────────────────────────────────────────
@@ -738,6 +1049,19 @@ export class CodeEntityGraph {
738
1049
  INSERT OR REPLACE INTO knowledge_edges (from_id, from_type, to_id, to_type, relation, weight, metadata_json, created_at, updated_at)
739
1050
  VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
740
1051
  `),
1052
+ // Phase 5: 调用图查询 (pre-prepared 避免每次调用都创建)
1053
+ getCallers: this.db.prepare(
1054
+ `SELECT from_id, from_type, metadata_json FROM knowledge_edges
1055
+ WHERE to_id = ? AND relation = 'calls'`
1056
+ ),
1057
+ getCallees: this.db.prepare(
1058
+ `SELECT to_id, to_type, metadata_json FROM knowledge_edges
1059
+ WHERE from_id = ? AND relation = 'calls'`
1060
+ ),
1061
+ getEdge: this.db.prepare(
1062
+ `SELECT metadata_json FROM knowledge_edges
1063
+ WHERE from_id = ? AND from_type = ? AND to_id = ? AND to_type = ? AND relation = ?`
1064
+ ),
741
1065
  };
742
1066
  }
743
1067
 
@@ -788,6 +1112,7 @@ export class CodeEntityGraph {
788
1112
  * 从 AST 数据推断实体类型
789
1113
  */
790
1114
  #inferEntityType(name, astSummary) {
1115
+ if (!name) return 'class'; // guard against undefined
791
1116
  if (astSummary.protocols?.some((p) => p.name === name)) {
792
1117
  return 'protocol';
793
1118
  }
@@ -75,7 +75,7 @@ export class KnowledgeService {
75
75
  }
76
76
  }
77
77
 
78
- // ── ConfidenceRouter — 仅标记 auto_approvable,不改变 lifecycle ──
78
+ // ── ConfidenceRouter — 标记 auto_approvable ──
79
79
  if (this._confidenceRouter) {
80
80
  const route = await this._confidenceRouter.route(entry);
81
81
  if (route.action === 'auto_approve') {
@@ -84,6 +84,10 @@ export class KnowledgeService {
84
84
  // reject / pending 都保持 pending 状态,等待人工审核
85
85
  }
86
86
 
87
+ // 注意: Bootstrap 候选保持 pending 状态,由 Dashboard 审核后发布。
88
+ // autoApprovable 标记保留,供前端显示「推荐批准」徽章。
89
+ // CursorDelivery 已支持高置信度 pending 条目的交付。
90
+
87
91
  const saved = await this.repository.create(entry);
88
92
 
89
93
  // 同步 relations → knowledge_edges
@@ -314,7 +314,16 @@ export class ModuleService {
314
314
  const allNodes = [];
315
315
  const allEdges = [];
316
316
 
317
+ // 如果有专业 Discoverer(非 generic),则跳过 GenericDiscoverer 的依赖图
318
+ // 避免 generic fallback 生成的冗余根节点(如项目名本身)干扰图结构
319
+ const hasSpecializedDiscoverer = this.#activeDiscoverers.some(
320
+ ({ discoverer }) => discoverer.id !== 'generic'
321
+ );
322
+
317
323
  for (const { discoverer } of this.#activeDiscoverers) {
324
+ if (hasSpecializedDiscoverer && discoverer.id === 'generic') {
325
+ continue;
326
+ }
318
327
  try {
319
328
  const graph = await discoverer.getDependencyGraph();
320
329
  for (const n of graph.nodes || []) {
@@ -82,7 +82,7 @@ export class WikiGenerator {
82
82
  * @param {object} deps
83
83
  * @param {string} deps.projectRoot
84
84
  * @param {import('../../service/module/ModuleService.js').ModuleService} [deps.moduleService]
85
- * @param {import('../../platform/ios/spm/SpmService.js').SpmService} [deps.spmService] — 向后兼容
85
+ * @param {import('../../platform/ios/spm/SpmHelper.js').SpmHelper} [deps.spmService] — 向后兼容
86
86
  * @param {import('../../service/knowledge/KnowledgeService.js').KnowledgeService} [deps.knowledgeService]
87
87
  * @param {import('../../core/ast/ProjectGraph.js').default} [deps.projectGraph]
88
88
  * @param {import('../../service/knowledge/CodeEntityGraph.js').CodeEntityGraph} [deps.codeEntityGraph]
@@ -141,7 +141,7 @@ class PathGuard {
141
141
 
142
142
  /**
143
143
  * Layer 1: 断言路径在允许的边界范围内
144
- * 用于修改已有文件的场景(如 XcodeIntegration 插入 header、SpmService 修改 Package.swift)
144
+ * 用于修改已有文件的场景(如 XcodeIntegration 插入 header、SpmHelper 修改 Package.swift)
145
145
  * @param {string} targetPath - 要写入的绝对路径
146
146
  * @throws {PathGuardError}
147
147
  */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "autosnippet",
3
- "version": "3.2.7",
3
+ "version": "3.2.8",
4
4
  "description": "Extract code patterns into a knowledge base for AI coding assistants",
5
5
  "type": "module",
6
6
  "main": "lib/bootstrap.js",