autosnippet 3.4.0 → 3.4.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (49) hide show
  1. package/README.md +43 -18
  2. package/dashboard/dist/assets/{index-8b1Gf3Bb.js → index-BX6r2fiy.js} +40 -40
  3. package/dashboard/dist/assets/index-BvZcGN02.css +1 -0
  4. package/dashboard/dist/index.html +2 -2
  5. package/dist/lib/core/AstAnalyzer.js +0 -1
  6. package/dist/lib/core/ast/lang-dart.js +118 -8
  7. package/dist/lib/core/ast/lang-go.js +0 -1
  8. package/dist/lib/core/ast/lang-java.js +25 -11
  9. package/dist/lib/core/ast/lang-javascript.js +103 -17
  10. package/dist/lib/core/ast/lang-objc.d.ts +1 -1
  11. package/dist/lib/core/ast/lang-objc.js +80 -4
  12. package/dist/lib/core/ast/lang-python.js +0 -1
  13. package/dist/lib/core/ast/lang-rust.js +0 -1
  14. package/dist/lib/core/ast/lang-swift.d.ts +1 -1
  15. package/dist/lib/core/ast/lang-swift.js +184 -7
  16. package/dist/lib/core/ast/lang-typescript.js +0 -1
  17. package/dist/lib/external/ai/AiFactory.d.ts +14 -0
  18. package/dist/lib/external/ai/AiFactory.js +33 -1
  19. package/dist/lib/external/ai/providers/GoogleGeminiProvider.js +7 -3
  20. package/dist/lib/external/ai/providers/OpenAiProvider.js +1 -1
  21. package/dist/lib/external/mcp/handlers/bootstrap/MissionBriefingBuilder.d.ts +33 -1
  22. package/dist/lib/external/mcp/handlers/bootstrap/MissionBriefingBuilder.js +392 -19
  23. package/dist/lib/external/mcp/handlers/bootstrap/pipeline/orchestrator.d.ts +1 -0
  24. package/dist/lib/external/mcp/handlers/bootstrap/pipeline/orchestrator.js +2 -1
  25. package/dist/lib/external/mcp/handlers/bootstrap/shared/bootstrap-phases.d.ts +2 -0
  26. package/dist/lib/external/mcp/handlers/bootstrap/shared/bootstrap-phases.js +4 -0
  27. package/dist/lib/external/mcp/handlers/guard.js +11 -6
  28. package/dist/lib/http/routes/ai.js +18 -1
  29. package/dist/lib/infrastructure/vector/IndexingPipeline.js +6 -1
  30. package/dist/lib/injection/modules/AiModule.js +22 -1
  31. package/dist/lib/service/bootstrap/BootstrapTaskManager.d.ts +7 -0
  32. package/dist/lib/service/bootstrap/BootstrapTaskManager.js +17 -0
  33. package/dist/lib/service/guard/ComplianceReporter.js +5 -1
  34. package/dist/lib/service/guard/GuardCheckEngine.d.ts +12 -1
  35. package/dist/lib/service/guard/GuardCheckEngine.js +36 -4
  36. package/dist/lib/service/guard/GuardCodeChecks.js +27 -9
  37. package/dist/lib/service/guard/SourceFileCollector.d.ts +3 -2
  38. package/dist/lib/service/guard/SourceFileCollector.js +3 -3
  39. package/dist/lib/service/search/SearchEngine.js +165 -61
  40. package/dist/lib/service/task/PrimeSearchPipeline.js +17 -2
  41. package/dist/lib/service/vector/VectorService.js +10 -1
  42. package/dist/lib/shared/LanguageService.d.ts +12 -0
  43. package/dist/lib/shared/LanguageService.js +85 -0
  44. package/dist/lib/shared/schemas/http-requests.d.ts +4 -0
  45. package/dist/lib/shared/schemas/http-requests.js +4 -0
  46. package/dist/lib/types/project-snapshot.d.ts +1 -0
  47. package/package.json +1 -1
  48. package/resources/grammars/tree-sitter-dart.wasm +0 -0
  49. package/dashboard/dist/assets/index-DHJ1Dj7u.css +0 -1
@@ -42,9 +42,11 @@ export function createProvider(options = {}) {
42
42
  break;
43
43
  case 'ollama':
44
44
  config.name = 'ollama';
45
- config.baseUrl = config.baseUrl || 'http://localhost:11434/v1';
45
+ config.baseUrl =
46
+ config.baseUrl || process.env.ASD_OLLAMA_BASE_URL || 'http://localhost:11434/v1';
46
47
  config.apiKey = config.apiKey || 'ollama';
47
48
  config.model = config.model || 'llama3';
49
+ config.embedModel = config.embedModel || 'qwen3-embedding:0.6b';
48
50
  break;
49
51
  default:
50
52
  break;
@@ -172,10 +174,37 @@ export async function getProviderWithFallback() {
172
174
  }
173
175
  return primary;
174
176
  }
177
+ /**
178
+ * 创建独立的 Embedding Provider
179
+ *
180
+ * 当 ASD_EMBED_PROVIDER 被设置时,创建一个专用于 embedding 的 provider 实例,
181
+ * 使 embedding 和 LLM 生成可以使用不同的提供商/模型。
182
+ *
183
+ * 典型场景:LLM 用 Google Gemini,Embedding 用本地 Ollama + qwen3-embedding
184
+ *
185
+ * @returns 独立的 embed provider,或 null(未配置时)
186
+ */
187
+ export function createEmbedProvider() {
188
+ const embedProviderName = process.env.ASD_EMBED_PROVIDER;
189
+ if (!embedProviderName) {
190
+ return null;
191
+ }
192
+ const logger = Logger.getInstance();
193
+ logger.info(`[AiFactory] Creating dedicated embed provider: ${embedProviderName}`);
194
+ return createProvider({
195
+ provider: embedProviderName,
196
+ model: process.env.ASD_EMBED_MODEL || undefined,
197
+ baseUrl: process.env.ASD_EMBED_BASE_URL || undefined,
198
+ apiKey: process.env.ASD_EMBED_API_KEY || undefined,
199
+ embedModel: process.env.ASD_EMBED_MODEL || undefined,
200
+ });
201
+ }
175
202
  /** 获取当前 AI 配置信息(同步,用于 UI 展示) */
176
203
  export function getAiConfigInfo() {
177
204
  const provider = process.env.ASD_AI_PROVIDER || 'auto';
178
205
  const model = process.env.ASD_AI_MODEL || '';
206
+ const embedProvider = process.env.ASD_EMBED_PROVIDER || '';
207
+ const embedModel = process.env.ASD_EMBED_MODEL || '';
179
208
  const hasGoogleKey = !!process.env.ASD_GOOGLE_API_KEY;
180
209
  const hasOpenAiKey = !!process.env.ASD_OPENAI_API_KEY;
181
210
  const hasClaudeKey = !!process.env.ASD_CLAUDE_API_KEY;
@@ -183,6 +212,8 @@ export function getAiConfigInfo() {
183
212
  return {
184
213
  provider,
185
214
  model,
215
+ embedProvider,
216
+ embedModel,
186
217
  hasKey: hasGoogleKey || hasOpenAiKey || hasClaudeKey || hasDeepSeekKey,
187
218
  keys: {
188
219
  google: hasGoogleKey,
@@ -200,6 +231,7 @@ export { MockProvider } from './providers/MockProvider.js';
200
231
  export { OpenAiProvider } from './providers/OpenAiProvider.js';
201
232
  export default {
202
233
  createProvider,
234
+ createEmbedProvider,
203
235
  autoDetectProvider,
204
236
  getAiConfigInfo,
205
237
  getProviderWithFallback,
@@ -9,8 +9,9 @@
9
9
  import Logger from '#infra/logging/Logger.js';
10
10
  import { AiProvider, } from '../AiProvider.js';
11
11
  const GEMINI_BASE = 'https://generativelanguage.googleapis.com/v1beta';
12
- const EMBED_MODEL = 'models/gemini-embedding-001';
12
+ const DEFAULT_EMBED_MODEL = 'models/gemini-embedding-001';
13
13
  export class GoogleGeminiProvider extends AiProvider {
14
+ #embedModel;
14
15
  constructor(config = {}) {
15
16
  super({
16
17
  ...config,
@@ -20,6 +21,9 @@ export class GoogleGeminiProvider extends AiProvider {
20
21
  this.name = 'google-gemini';
21
22
  this.model = config.model || 'gemini-3-flash-preview';
22
23
  this.apiKey = config.apiKey || process.env.ASD_GOOGLE_API_KEY || '';
24
+ this.#embedModel = config.embedModel
25
+ ? `models/${config.embedModel.replace(/^models\//, '')}`
26
+ : DEFAULT_EMBED_MODEL;
23
27
  this.logger = Logger.getInstance();
24
28
  }
25
29
  /** 是否支持原生结构化函数调用 */
@@ -345,10 +349,10 @@ export class GoogleGeminiProvider extends AiProvider {
345
349
  for (let i = 0; i < texts.length; i += 100) {
346
350
  const batch = texts.slice(i, i + 100);
347
351
  const requests = batch.map((t) => ({
348
- model: EMBED_MODEL,
352
+ model: this.#embedModel,
349
353
  content: { parts: [{ text: t.slice(0, 8000) }] },
350
354
  }));
351
- const url = `${GEMINI_BASE}/${EMBED_MODEL}:batchEmbedContents?key=${this.apiKey}`;
355
+ const url = `${GEMINI_BASE}/${this.#embedModel}:batchEmbedContents?key=${this.apiKey}`;
352
356
  const data = await this._post(url, { requests });
353
357
  if (data?.embeddings) {
354
358
  results.push(...data.embeddings.map((e) => e.values));
@@ -17,7 +17,7 @@ export class OpenAiProvider extends AiProvider {
17
17
  this.model = config.model || 'gpt-5.4-mini';
18
18
  this.apiKey = config.apiKey || process.env.ASD_OPENAI_API_KEY || '';
19
19
  this.baseUrl = config.baseUrl || OPENAI_BASE;
20
- this.embedModel = config.embedModel || 'text-embedding-3-small';
20
+ this.embedModel = config.embedModel || process.env.ASD_EMBED_MODEL || 'text-embedding-3-small';
21
21
  this.logger = Logger.getInstance();
22
22
  }
23
23
  /**
@@ -67,6 +67,7 @@ interface CompressedProtocol {
67
67
  /** 压缩后的 AST 类 */
68
68
  interface CompressedAstClass {
69
69
  name: string;
70
+ kind?: string;
70
71
  superclass?: string | null;
71
72
  file?: string | null;
72
73
  methodCount: number;
@@ -78,7 +79,11 @@ interface MissionBriefing {
78
79
  ast: {
79
80
  available: boolean;
80
81
  compressionLevel?: string;
81
- summary?: string;
82
+ summary?: string | {
83
+ text: string;
84
+ kindDistribution: Record<string, number>;
85
+ insight: string;
86
+ };
82
87
  classes: CompressedAstClass[];
83
88
  protocols: CompressedProtocol[];
84
89
  categories?: {
@@ -96,6 +101,32 @@ interface MissionBriefing {
96
101
  longMethods?: number;
97
102
  } | null;
98
103
  };
104
+ architectureOverview?: {
105
+ style: string;
106
+ layers: {
107
+ name: string;
108
+ modules: string[];
109
+ fileCount: number;
110
+ role: string;
111
+ }[];
112
+ externalDeps: {
113
+ name: string;
114
+ role: string;
115
+ }[];
116
+ keyInsights: string[];
117
+ } | null;
118
+ technologyStack?: {
119
+ name: string;
120
+ role: string;
121
+ usedBy: string[];
122
+ }[] | null;
123
+ keyAbstractions?: {
124
+ name: string;
125
+ kind: string;
126
+ module: string;
127
+ significance: string;
128
+ detail: string;
129
+ }[] | null;
99
130
  codeEntityGraph: {
100
131
  totalEntities: number;
101
132
  totalEdges: number;
@@ -110,6 +141,7 @@ interface MissionBriefing {
110
141
  id: string;
111
142
  label: string;
112
143
  fileCount?: number;
144
+ dependentCount?: number;
113
145
  }[];
114
146
  edges: unknown[];
115
147
  } | null;
@@ -609,6 +609,7 @@ function compressAstForBriefing(astProjectSummary, fileCount) {
609
609
  .slice(0, topN);
610
610
  const compressedClasses = sortedClasses.map((c) => ({
611
611
  name: c.name,
612
+ kind: c.kind || 'class',
612
613
  superclass: c.superclass || null,
613
614
  file: c.file || c.relativePath || null,
614
615
  methodCount: c.methodCount || c.methods?.length || 0,
@@ -628,8 +629,43 @@ function compressAstForBriefing(astProjectSummary, fileCount) {
628
629
  .map((m) => (typeof m === 'string' ? m : m.name))
629
630
  .slice(0, 10),
630
631
  }));
631
- const summary = `${classes.length} classes, ${protocols.length} protocols, ${categories.length} categories, ${astProjectSummary.projectMetrics?.totalMethods || 0} methods`;
632
- // 压缩 patternStats: 保留计数,移除详细列表
632
+ // ── 结构化 summary: kindDistribution + insight ──
633
+ const kindDist = {};
634
+ for (const c of dedupedClasses) {
635
+ const k = c.kind || 'class';
636
+ kindDist[k] = (kindDist[k] || 0) + 1;
637
+ }
638
+ const totalTypes = dedupedClasses.length;
639
+ const kindParts = Object.entries(kindDist)
640
+ .sort((a, b) => b[1] - a[1])
641
+ .map(([k, v]) => `${v} ${k}`);
642
+ const summaryText = `${totalTypes} types (${kindParts.join(', ')}), ${protocols.length} protocols, ${categories.length} ${categories.length > 0 && categories[0]?.baseClass ? 'categories' : 'extensions'}, ${astProjectSummary.projectMetrics?.totalMethods || 0} methods`;
643
+ // 生成 insight
644
+ const valueTypeCount = (kindDist.struct || 0) + (kindDist.enum || 0);
645
+ const refTypeCount = kindDist.class || 0;
646
+ const actorCount = kindDist.actor || 0;
647
+ let insight = '';
648
+ if (totalTypes > 0) {
649
+ const vtRatio = Math.round((valueTypeCount / totalTypes) * 100);
650
+ if (vtRatio >= 60) {
651
+ insight = `Value types (struct+enum) account for ${vtRatio}% — project favors value semantics`;
652
+ }
653
+ else if (refTypeCount > valueTypeCount) {
654
+ insight = `Reference types (class) account for ${Math.round((refTypeCount / totalTypes) * 100)}% — OOP-heavy codebase`;
655
+ }
656
+ else {
657
+ insight = `Balanced mix of value types (${vtRatio}%) and reference types (${Math.round((refTypeCount / totalTypes) * 100)}%)`;
658
+ }
659
+ if (actorCount > 0) {
660
+ insight += `; ${actorCount} actors indicate structured concurrency adoption`;
661
+ }
662
+ }
663
+ const summary = {
664
+ text: summaryText,
665
+ kindDistribution: kindDist,
666
+ insight,
667
+ };
668
+ // ── 压缩 patternStats: 保留计数 + 代表性类名 ──
633
669
  const rawPatterns = astProjectSummary.patternStats || {};
634
670
  const compressedPatterns = {};
635
671
  for (const [key, val] of Object.entries(rawPatterns)) {
@@ -640,17 +676,29 @@ function compressAstForBriefing(astProjectSummary, fileCount) {
640
676
  compressedPatterns[key] = val.length; // 数组 → 计数
641
677
  }
642
678
  else if (val && typeof val === 'object') {
643
- // 嵌套对象: 保留 count/总数,或递归压缩为浅层概要
644
679
  const sub = {};
645
680
  for (const [sk, sv] of Object.entries(val)) {
646
681
  if (typeof sv === 'number' || typeof sv === 'string' || typeof sv === 'boolean') {
647
682
  sub[sk] = sv;
648
683
  }
649
684
  else if (Array.isArray(sv)) {
650
- sub[sk] = sv.length;
685
+ // instances 数组: 提取 top-3 类名作为 representatives
686
+ if (sk === 'instances' && sv.length > 0 && typeof sv[0] === 'object') {
687
+ sub[sk] = sv.length;
688
+ const classNames = sv
689
+ .map((inst) => inst.className || '')
690
+ .filter(Boolean);
691
+ const unique = [...new Set(classNames)].slice(0, 3);
692
+ if (unique.length > 0) {
693
+ sub.representatives = unique.join(', ');
694
+ }
695
+ }
696
+ else {
697
+ sub[sk] = sv.length;
698
+ }
651
699
  }
652
700
  else if (sv && typeof sv === 'object') {
653
- sub[sk] = Object.keys(sv).length; // 深层对象 → key 计数
701
+ sub[sk] = Object.keys(sv).length;
654
702
  }
655
703
  }
656
704
  compressedPatterns[key] = sub;
@@ -804,6 +852,300 @@ function buildExecutionPlan(activeDimensions) {
804
852
  workflow: '对每个维度: (1) 用你的原生能力阅读代码分析 → (2) 调用 autosnippet_submit_knowledge_batch 批量提交候选(**每维度最少 3 条,目标 5 条**,将不同关注点拆分为独立候选,1-2 条视为不合格) → (3) 调用 autosnippet_dimension_complete 完成维度(必须传 referencedFiles=[分析过的文件路径] 和 keyFindings=[3-5条关键发现])',
805
853
  };
806
854
  }
855
+ // ── Architecture Overview 自动推断 ────────────────────────
856
+ /** 知名外部依赖的角色映射 */
857
+ const KNOWN_DEPENDENCIES = {
858
+ // Swift / iOS
859
+ alamofire: 'HTTP networking',
860
+ moya: 'Network abstraction over Alamofire',
861
+ rxswift: 'Reactive programming (ReactiveX)',
862
+ rxcocoa: 'RxSwift UIKit bindings',
863
+ combine: 'Apple reactive framework',
864
+ kingfisher: 'Image downloading & caching',
865
+ sdwebimage: 'Image downloading & caching',
866
+ snapkit: 'Auto Layout DSL',
867
+ lottie: 'Animation rendering',
868
+ realm: 'Mobile database',
869
+ coredata: 'Apple persistence framework',
870
+ swiftui: 'Declarative UI framework',
871
+ // JavaScript / TypeScript
872
+ react: 'UI component framework',
873
+ vue: 'Progressive UI framework',
874
+ angular: 'Full-featured UI framework',
875
+ express: 'HTTP server framework',
876
+ nestjs: 'Enterprise Node.js framework',
877
+ axios: 'HTTP client',
878
+ prisma: 'Database ORM',
879
+ sequelize: 'SQL ORM',
880
+ mongoose: 'MongoDB ODM',
881
+ tailwindcss: 'Utility-first CSS',
882
+ webpack: 'Module bundler',
883
+ vite: 'Frontend build tool',
884
+ jest: 'Testing framework',
885
+ vitest: 'Vite-native testing',
886
+ redux: 'State management',
887
+ zustand: 'Lightweight state management',
888
+ // Go
889
+ gin: 'HTTP web framework',
890
+ echo: 'HTTP web framework',
891
+ gorm: 'Go ORM',
892
+ cobra: 'CLI framework',
893
+ // Python
894
+ django: 'Full-stack web framework',
895
+ flask: 'Micro web framework',
896
+ fastapi: 'Async web framework',
897
+ sqlalchemy: 'SQL toolkit & ORM',
898
+ pytorch: 'Deep learning framework',
899
+ tensorflow: 'Machine learning framework',
900
+ pandas: 'Data analysis library',
901
+ numpy: 'Numerical computing',
902
+ };
903
+ /**
904
+ * 从 targets + depGraph + localPackageModules 自动推断架构概览
905
+ */
906
+ function buildArchitectureOverview(targets, depGraphData, localPackageModules) {
907
+ if (!targets || targets.length === 0) {
908
+ return null;
909
+ }
910
+ // ── 分层: 按 inferredRole 分组 ──
911
+ const roleGroups = {};
912
+ for (const t of targets) {
913
+ const role = t.inferredRole || 'unknown';
914
+ if (!roleGroups[role]) {
915
+ roleGroups[role] = { modules: [], fileCount: 0 };
916
+ }
917
+ roleGroups[role].modules.push(t.name);
918
+ roleGroups[role].fileCount += t.fileCount || 0;
919
+ }
920
+ // 层级命名映射
921
+ const ROLE_LAYER_MAP = {
922
+ app: {
923
+ name: 'App Shell',
924
+ priority: 0,
925
+ role: 'Application entry point, coordinators, DI assembly',
926
+ },
927
+ core: {
928
+ name: 'Core Infrastructure',
929
+ priority: 2,
930
+ role: 'Shared infrastructure libraries, base classes, utilities',
931
+ },
932
+ networking: {
933
+ name: 'Networking',
934
+ priority: 2,
935
+ role: 'Network client, middleware, API definitions',
936
+ },
937
+ feature: {
938
+ name: 'Feature Modules',
939
+ priority: 1,
940
+ role: 'Per-feature UI, view models, business logic',
941
+ },
942
+ ui: { name: 'UI Components', priority: 2, role: 'Shared UI components, themes, extensions' },
943
+ test: { name: 'Tests', priority: 3, role: 'Unit tests, integration tests, mocks' },
944
+ unknown: { name: 'Other', priority: 3, role: 'Uncategorized modules' },
945
+ };
946
+ const layers = [];
947
+ for (const [role, group] of Object.entries(roleGroups)) {
948
+ if (role === 'test') {
949
+ continue;
950
+ } // 测试模块不加入架构层
951
+ const layerDef = ROLE_LAYER_MAP[role] || ROLE_LAYER_MAP.unknown;
952
+ // 合并同优先级的层
953
+ const existing = layers.find((l) => l.name === layerDef.name);
954
+ if (existing) {
955
+ existing.modules.push(...group.modules);
956
+ existing.fileCount += group.fileCount;
957
+ }
958
+ else {
959
+ layers.push({
960
+ name: layerDef.name,
961
+ modules: [...group.modules],
962
+ fileCount: group.fileCount,
963
+ role: layerDef.role,
964
+ });
965
+ }
966
+ }
967
+ // 按 priority 排序 (App Shell → Features → Core → Other)
968
+ layers.sort((a, b) => {
969
+ const pa = Object.values(ROLE_LAYER_MAP).find((v) => v.name === a.name)?.priority ?? 99;
970
+ const pb = Object.values(ROLE_LAYER_MAP).find((v) => v.name === b.name)?.priority ?? 99;
971
+ return pa - pb;
972
+ });
973
+ // ── 外部依赖识别 ──
974
+ const externalDeps = [];
975
+ const localModuleNames = new Set(targets.map((t) => t.name));
976
+ if (depGraphData?.nodes) {
977
+ for (const n of depGraphData.nodes) {
978
+ const id = typeof n === 'string' ? n : n.id || '';
979
+ const label = typeof n === 'string' ? n : n.label || id;
980
+ if (!localModuleNames.has(id) && !localModuleNames.has(label)) {
981
+ const knownRole = KNOWN_DEPENDENCIES[label.toLowerCase()];
982
+ externalDeps.push({
983
+ name: label,
984
+ role: knownRole || 'third-party dependency',
985
+ });
986
+ }
987
+ }
988
+ }
989
+ // ── 关键洞察 ──
990
+ const insights = [];
991
+ const totalFiles = targets.reduce((s, t) => s + (t.fileCount || 0), 0);
992
+ const featureGroup = roleGroups.feature;
993
+ const coreGroup = roleGroups.core;
994
+ const networkGroup = roleGroups.networking;
995
+ // 本地子包占比
996
+ if (localPackageModules && localPackageModules.length > 0) {
997
+ const pkgFiles = localPackageModules.reduce((s, m) => s + m.fileCount, 0);
998
+ const pct = totalFiles > 0 ? Math.round((pkgFiles / totalFiles) * 100) : 0;
999
+ insights.push(`${localPackageModules.length} local packages provide ${pct}% of the codebase (${pkgFiles}/${totalFiles} files)`);
1000
+ }
1001
+ // Feature 模块特征
1002
+ if (featureGroup) {
1003
+ const avgFiles = Math.round(featureGroup.fileCount / featureGroup.modules.length);
1004
+ if (avgFiles <= 5) {
1005
+ insights.push(`Feature modules are thin (avg ${avgFiles} files) — business logic likely concentrates in core infrastructure`);
1006
+ }
1007
+ else {
1008
+ insights.push(`Feature modules average ${avgFiles} files each — self-contained feature architecture`);
1009
+ }
1010
+ }
1011
+ // 最大基础设施模块
1012
+ if (coreGroup || networkGroup) {
1013
+ const infraModules = [...(coreGroup?.modules || []), ...(networkGroup?.modules || [])];
1014
+ const heaviest = targets
1015
+ .filter((t) => infraModules.includes(t.name))
1016
+ .sort((a, b) => (b.fileCount || 0) - (a.fileCount || 0));
1017
+ if (heaviest.length > 0 && heaviest[0].fileCount) {
1018
+ insights.push(`${heaviest[0].name} (${heaviest[0].fileCount} files) is the heaviest infrastructure module`);
1019
+ }
1020
+ }
1021
+ // 推断架构风格
1022
+ let style = 'Monolithic application';
1023
+ if (localPackageModules && localPackageModules.length >= 2) {
1024
+ style = 'Modular monolith (local packages)';
1025
+ }
1026
+ else if (targets.length >= 5 && featureGroup && featureGroup.modules.length >= 3) {
1027
+ style = 'Feature-modular architecture';
1028
+ }
1029
+ return { style, layers, externalDeps, keyInsights: insights };
1030
+ }
1031
+ /**
1032
+ * 从外部依赖图中提取技术栈信息
1033
+ */
1034
+ function buildTechnologyStack(depGraphData, targets) {
1035
+ if (!depGraphData?.nodes || !depGraphData?.edges) {
1036
+ return null;
1037
+ }
1038
+ const localModuleNames = new Set(targets.map((t) => t.name));
1039
+ const stack = [];
1040
+ for (const n of depGraphData.nodes) {
1041
+ const id = typeof n === 'string' ? n : n.id || '';
1042
+ const label = typeof n === 'string' ? n : n.label || id;
1043
+ if (localModuleNames.has(id) || localModuleNames.has(label)) {
1044
+ continue;
1045
+ }
1046
+ const role = KNOWN_DEPENDENCIES[label.toLowerCase()] || 'third-party dependency';
1047
+ // 找出哪些模块依赖它
1048
+ const usedBy = (depGraphData.edges || [])
1049
+ .filter((e) => {
1050
+ const edge = e;
1051
+ return edge.to === id || edge.to === label;
1052
+ })
1053
+ .map((e) => e.from)
1054
+ .filter((f) => localModuleNames.has(f))
1055
+ .slice(0, 5);
1056
+ stack.push({ name: label, role, usedBy });
1057
+ }
1058
+ return stack.length > 0 ? stack : null;
1059
+ }
1060
+ /**
1061
+ * 提取项目关键抽象 — 从继承热点、协议遵从数、模块入口类中识别
1062
+ */
1063
+ function buildKeyAbstractions(astData, targets) {
1064
+ if (!astData) {
1065
+ return null;
1066
+ }
1067
+ const classes = astData.classes || [];
1068
+ const protocols = astData.protocols || [];
1069
+ const abstractions = [];
1070
+ // §1: 高继承热点 — 被多个子类继承的基类
1071
+ const subclassCount = {};
1072
+ for (const cls of classes) {
1073
+ if (cls.superclass) {
1074
+ subclassCount[cls.superclass] = (subclassCount[cls.superclass] || 0) + 1;
1075
+ }
1076
+ }
1077
+ const topBases = Object.entries(subclassCount)
1078
+ .filter(([, count]) => count >= 2)
1079
+ .sort((a, b) => b[1] - a[1])
1080
+ .slice(0, 5);
1081
+ for (const [baseName, count] of topBases) {
1082
+ const baseCls = classes.find((c) => c.name === baseName);
1083
+ const module = baseCls?.targetName || _inferModule(baseCls?.file || baseCls?.relativePath, targets);
1084
+ abstractions.push({
1085
+ name: baseName,
1086
+ kind: baseCls?.kind || 'class',
1087
+ module,
1088
+ significance: `Base class with ${count} subclasses`,
1089
+ detail: `Subclasses: ${classes
1090
+ .filter((c) => c.superclass === baseName)
1091
+ .map((c) => c.name)
1092
+ .slice(0, 5)
1093
+ .join(', ')}`,
1094
+ });
1095
+ }
1096
+ // §2: 高方法数类 — 复杂度热点
1097
+ const methodHeavy = classes
1098
+ .filter((c) => (c.methodCount || 0) >= 15)
1099
+ .sort((a, b) => (b.methodCount || 0) - (a.methodCount || 0))
1100
+ .slice(0, 3);
1101
+ for (const cls of methodHeavy) {
1102
+ // 跳过已在继承热点中出现的
1103
+ if (abstractions.some((a) => a.name === cls.name)) {
1104
+ continue;
1105
+ }
1106
+ const module = cls.targetName || _inferModule(cls.file || cls.relativePath, targets);
1107
+ abstractions.push({
1108
+ name: cls.name,
1109
+ kind: cls.kind || 'class',
1110
+ module,
1111
+ significance: `Complexity hotspot (${cls.methodCount} methods)`,
1112
+ detail: cls.superclass ? `extends ${cls.superclass}` : 'root class',
1113
+ });
1114
+ }
1115
+ // §3: 高遵从协议 — 核心抽象接口
1116
+ const protoWithConformers = protocols
1117
+ .filter((p) => (p.conformers?.length || 0) >= 2 || (p.methodCount || 0) >= 3)
1118
+ .sort((a, b) => (b.conformers?.length || 0) - (a.conformers?.length || 0))
1119
+ .slice(0, 5);
1120
+ for (const proto of protoWithConformers) {
1121
+ const module = proto.targetName || _inferModule(proto.file || proto.relativePath, targets);
1122
+ const conformerCount = proto.conformers?.length || 0;
1123
+ abstractions.push({
1124
+ name: proto.name,
1125
+ kind: 'protocol',
1126
+ module,
1127
+ significance: conformerCount > 0
1128
+ ? `Protocol with ${conformerCount} conformers`
1129
+ : `Protocol with ${proto.methodCount || 0} method requirements`,
1130
+ detail: conformerCount > 0
1131
+ ? `Conformers: ${proto.conformers.slice(0, 5).join(', ')}`
1132
+ : `${proto.methodCount || 0} required methods`,
1133
+ });
1134
+ }
1135
+ return abstractions.length > 0 ? abstractions.slice(0, 10) : null;
1136
+ }
1137
+ /** 从文件路径推断模块名 */
1138
+ function _inferModule(filePath, targets) {
1139
+ if (!filePath) {
1140
+ return 'unknown';
1141
+ }
1142
+ for (const t of targets) {
1143
+ if (filePath.includes(t.name)) {
1144
+ return t.name;
1145
+ }
1146
+ }
1147
+ return filePath.split('/')[0] || 'unknown';
1148
+ }
807
1149
  // ── Panorama 摘要构建 ──────────────────────────────────────
808
1150
  /**
809
1151
  * 从 PanoramaResult 提取 layers / couplingHotspots / cycles / gaps
@@ -928,28 +1270,58 @@ localPackageModules, // 本地子包模块信息
928
1270
  EXAMPLE_TEMPLATES[lang.toLowerCase()] ||
929
1271
  EXAMPLE_TEMPLATES._default;
930
1272
  // ── 组装 ──
1273
+ // ── 依赖图节点去重 ──
1274
+ const dedupedDepNodes = [];
1275
+ if (depGraphData?.nodes) {
1276
+ const nodeMap = new Map();
1277
+ for (const n of depGraphData.nodes) {
1278
+ const id = typeof n === 'string' ? n : n.id || '';
1279
+ const label = typeof n === 'string' ? n : n.label || id;
1280
+ const fileCount = typeof n === 'string' ? undefined : n.fileCount;
1281
+ if (!nodeMap.has(id)) {
1282
+ nodeMap.set(id, { id, label, fileCount, dependentCount: 0 });
1283
+ }
1284
+ else if (fileCount && !nodeMap.get(id).fileCount) {
1285
+ nodeMap.get(id).fileCount = fileCount;
1286
+ }
1287
+ }
1288
+ // 计算每个节点被多少模块依赖(fan-in)
1289
+ for (const e of depGraphData.edges || []) {
1290
+ const edge = e;
1291
+ if (edge.to && nodeMap.has(edge.to)) {
1292
+ nodeMap.get(edge.to).dependentCount++;
1293
+ }
1294
+ }
1295
+ for (const node of nodeMap.values()) {
1296
+ dedupedDepNodes.push(node);
1297
+ }
1298
+ }
1299
+ // ── targets 构建 ──
1300
+ const builtTargets = (targets || []).map((t) => ({
1301
+ name: typeof t === 'string' ? t : t.name,
1302
+ type: typeof t === 'string' ? 'target' : t.type || 'target',
1303
+ inferredRole: typeof t === 'string' ? undefined : t.inferredRole,
1304
+ fileCount: typeof t === 'string' ? undefined : t.fileCount,
1305
+ }));
931
1306
  const briefing = {
932
1307
  projectMeta,
933
1308
  ast: compressAstForBriefing(astData ?? null, projectMeta.fileCount || 0),
1309
+ // 高层次架构概览 — Agent 一目了然项目结构
1310
+ architectureOverview: buildArchitectureOverview(builtTargets, depGraphData ?? null, localPackageModules),
1311
+ // 技术栈 — 外部依赖的角色识别
1312
+ technologyStack: buildTechnologyStack(depGraphData ?? null, builtTargets),
1313
+ // 关键抽象 — Agent 优先分析的核心类/协议
1314
+ keyAbstractions: buildKeyAbstractions(astData ?? null, builtTargets),
934
1315
  codeEntityGraph: summarizeEntityGraph(codeEntityResult ?? null),
935
1316
  callGraph: summarizeCallGraph(callGraphResult ?? null),
936
- dependencyGraph: depGraphData
1317
+ dependencyGraph: dedupedDepNodes.length > 0
937
1318
  ? {
938
- nodes: (depGraphData.nodes || []).map((n) => ({
939
- id: typeof n === 'string' ? n : n.id || '',
940
- label: typeof n === 'string' ? n : n.label || '',
941
- fileCount: typeof n === 'string' ? undefined : n.fileCount,
942
- })),
943
- edges: (depGraphData.edges || []).slice(0, 100), // 限制边数
1319
+ nodes: dedupedDepNodes,
1320
+ edges: (depGraphData?.edges || []).slice(0, 100),
944
1321
  }
945
1322
  : null,
946
1323
  guardFindings: summarizeGuardFindings(guardAudit ?? null),
947
- targets: (targets || []).map((t) => ({
948
- name: typeof t === 'string' ? t : t.name,
949
- type: typeof t === 'string' ? 'target' : t.type || 'target',
950
- inferredRole: typeof t === 'string' ? undefined : t.inferredRole,
951
- fileCount: typeof t === 'string' ? undefined : t.fileCount,
952
- })),
1324
+ targets: builtTargets,
953
1325
  dimensions,
954
1326
  // §7.1: 语言扩展信息 (反模式、Guard 规则、Agent 注意事项)
955
1327
  languageExtension: languageExtension || null,
@@ -1022,10 +1394,11 @@ localPackageModules, // 本地子包模块信息
1022
1394
  }
1023
1395
  else {
1024
1396
  // ── Level 4-5: 高代价压缩 (移除辅助数据) ──
1025
- // Level 4: 移除 evidenceStarters (体积优先)
1397
+ // Level 4: 移除 evidenceStarters + technologyStack (体积优先)
1026
1398
  for (const dim of briefing.dimensions) {
1027
1399
  delete dim.evidenceStarters;
1028
1400
  }
1401
+ briefing.technologyStack = null;
1029
1402
  // Level 5: SOP 降级为紧凑文本 + 移除 FAIL_EXAMPLES
1030
1403
  for (const dim of briefing.dimensions) {
1031
1404
  if (dim.analysisGuide && typeof dim.analysisGuide === 'object') {
@@ -68,6 +68,7 @@ interface BootstrapFileEntry {
68
68
  /** Task manager minimal shape */
69
69
  interface TaskManagerLike {
70
70
  isSessionValid(sessionId: string): boolean;
71
+ getSessionAbortSignal?(): AbortSignal | null;
71
72
  emitProgress?(event: string, data: Record<string, unknown>): void;
72
73
  [key: string]: unknown;
73
74
  }
@@ -113,6 +113,7 @@ export async function fillDimensionsV3(view, dimensions) {
113
113
  /* not available */
114
114
  }
115
115
  const sessionId = view.bootstrapSession?.id ?? '';
116
+ const sessionAbortSignal = taskManager?.getSessionAbortSignal?.() ?? null;
116
117
  const isIncremental = incrementalPlan?.canIncremental && incrementalPlan?.mode === 'incremental';
117
118
  const emitter = new BootstrapEventEmitter(ctx.container);
118
119
  logger.info(`[Insight-v3] ═══ fillDimensionsV3 entered — ${isIncremental ? 'INCREMENTAL' : 'FULL'} pipeline`);
@@ -623,7 +624,7 @@ export async function fillDimensionsV3(view, dimensions) {
623
624
  // 外层超时 = 安全网 (各阶段已有独立超时: Analyst 300s + Producer 180s + 硬缓冲 60s)
624
625
  const outerTimeoutMs = 3_600_000; // 1 小时——维度分析本身耗时长
625
626
  const runResult = await Promise.race([
626
- runtime.execute(message, { strategyContext }),
627
+ runtime.execute(message, { strategyContext, abortSignal: sessionAbortSignal }),
627
628
  new Promise((_, reject) => setTimeout(() => reject(new Error(`Bootstrap runtime timeout for "${dimId}"`)), outerTimeoutMs)),
628
629
  ]);
629
630
  // ── 提取结果 ──
@@ -39,6 +39,8 @@ interface BootstrapFileEntry {
39
39
  relativePath: string;
40
40
  content: string;
41
41
  targetName: string;
42
+ /** Whether this file belongs to a test target or matches test file naming patterns */
43
+ isTest: boolean;
42
44
  }
43
45
  /** Target item — either a plain string or an object with metadata */
44
46
  type TargetItem = string | {