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.
- package/README.md +43 -18
- package/dashboard/dist/assets/{index-8b1Gf3Bb.js → index-BX6r2fiy.js} +40 -40
- package/dashboard/dist/assets/index-BvZcGN02.css +1 -0
- package/dashboard/dist/index.html +2 -2
- package/dist/lib/core/AstAnalyzer.js +0 -1
- package/dist/lib/core/ast/lang-dart.js +118 -8
- package/dist/lib/core/ast/lang-go.js +0 -1
- package/dist/lib/core/ast/lang-java.js +25 -11
- package/dist/lib/core/ast/lang-javascript.js +103 -17
- package/dist/lib/core/ast/lang-objc.d.ts +1 -1
- package/dist/lib/core/ast/lang-objc.js +80 -4
- package/dist/lib/core/ast/lang-python.js +0 -1
- package/dist/lib/core/ast/lang-rust.js +0 -1
- package/dist/lib/core/ast/lang-swift.d.ts +1 -1
- package/dist/lib/core/ast/lang-swift.js +184 -7
- package/dist/lib/core/ast/lang-typescript.js +0 -1
- package/dist/lib/external/ai/AiFactory.d.ts +14 -0
- package/dist/lib/external/ai/AiFactory.js +33 -1
- package/dist/lib/external/ai/providers/GoogleGeminiProvider.js +7 -3
- package/dist/lib/external/ai/providers/OpenAiProvider.js +1 -1
- package/dist/lib/external/mcp/handlers/bootstrap/MissionBriefingBuilder.d.ts +33 -1
- package/dist/lib/external/mcp/handlers/bootstrap/MissionBriefingBuilder.js +392 -19
- package/dist/lib/external/mcp/handlers/bootstrap/pipeline/orchestrator.d.ts +1 -0
- package/dist/lib/external/mcp/handlers/bootstrap/pipeline/orchestrator.js +2 -1
- package/dist/lib/external/mcp/handlers/bootstrap/shared/bootstrap-phases.d.ts +2 -0
- package/dist/lib/external/mcp/handlers/bootstrap/shared/bootstrap-phases.js +4 -0
- package/dist/lib/external/mcp/handlers/guard.js +11 -6
- package/dist/lib/http/routes/ai.js +18 -1
- package/dist/lib/infrastructure/vector/IndexingPipeline.js +6 -1
- package/dist/lib/injection/modules/AiModule.js +22 -1
- package/dist/lib/service/bootstrap/BootstrapTaskManager.d.ts +7 -0
- package/dist/lib/service/bootstrap/BootstrapTaskManager.js +17 -0
- package/dist/lib/service/guard/ComplianceReporter.js +5 -1
- package/dist/lib/service/guard/GuardCheckEngine.d.ts +12 -1
- package/dist/lib/service/guard/GuardCheckEngine.js +36 -4
- package/dist/lib/service/guard/GuardCodeChecks.js +27 -9
- package/dist/lib/service/guard/SourceFileCollector.d.ts +3 -2
- package/dist/lib/service/guard/SourceFileCollector.js +3 -3
- package/dist/lib/service/search/SearchEngine.js +165 -61
- package/dist/lib/service/task/PrimeSearchPipeline.js +17 -2
- package/dist/lib/service/vector/VectorService.js +10 -1
- package/dist/lib/shared/LanguageService.d.ts +12 -0
- package/dist/lib/shared/LanguageService.js +85 -0
- package/dist/lib/shared/schemas/http-requests.d.ts +4 -0
- package/dist/lib/shared/schemas/http-requests.js +4 -0
- package/dist/lib/types/project-snapshot.d.ts +1 -0
- package/package.json +1 -1
- package/resources/grammars/tree-sitter-dart.wasm +0 -0
- 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 =
|
|
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
|
|
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:
|
|
352
|
+
model: this.#embedModel,
|
|
349
353
|
content: { parts: [{ text: t.slice(0, 8000) }] },
|
|
350
354
|
}));
|
|
351
|
-
const url = `${GEMINI_BASE}/${
|
|
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
|
-
|
|
632
|
-
|
|
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
|
-
|
|
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;
|
|
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:
|
|
1317
|
+
dependencyGraph: dedupedDepNodes.length > 0
|
|
937
1318
|
? {
|
|
938
|
-
nodes:
|
|
939
|
-
|
|
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:
|
|
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 | {
|