autosnippet 3.4.1 → 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/dist/lib/core/ast/lang-dart.js +118 -7
- package/dist/lib/core/ast/lang-java.js +25 -10
- package/dist/lib/core/ast/lang-javascript.js +103 -16
- package/dist/lib/core/ast/lang-objc.d.ts +1 -1
- package/dist/lib/core/ast/lang-objc.js +80 -3
- package/dist/lib/core/ast/lang-swift.d.ts +1 -1
- package/dist/lib/core/ast/lang-swift.js +184 -6
- 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/types/project-snapshot.d.ts +1 -0
- package/package.json +1 -1
- package/resources/grammars/tree-sitter-dart.wasm +0 -0
|
@@ -76,7 +76,16 @@ function _walkNode(node, ctx, parentClassName) {
|
|
|
76
76
|
}
|
|
77
77
|
break;
|
|
78
78
|
}
|
|
79
|
-
case 'function_signature':
|
|
79
|
+
case 'function_signature': {
|
|
80
|
+
// 顶层 function_signature 同样需要向前看兄弟 function_body
|
|
81
|
+
const nextSib = node.namedChild(i + 1);
|
|
82
|
+
const bodyNode = nextSib?.type === 'function_body' ? nextSib : null;
|
|
83
|
+
const func = _parseDartMethod(child, parentClassName, bodyNode);
|
|
84
|
+
if (func) {
|
|
85
|
+
ctx.methods.push(func);
|
|
86
|
+
}
|
|
87
|
+
break;
|
|
88
|
+
}
|
|
80
89
|
case 'function_definition':
|
|
81
90
|
case 'top_level_definition': {
|
|
82
91
|
const func = _parseFunctionDef(child, parentClassName);
|
|
@@ -167,9 +176,19 @@ function _walkClassBody(body, ctx, className) {
|
|
|
167
176
|
const child = body.namedChild(i);
|
|
168
177
|
switch (child.type) {
|
|
169
178
|
case 'method_signature':
|
|
170
|
-
case 'function_definition':
|
|
171
179
|
case 'getter_signature':
|
|
172
180
|
case 'setter_signature': {
|
|
181
|
+
// tree-sitter-dart: method_signature 和 function_body 是兄弟节点
|
|
182
|
+
// 需要向前看下一个兄弟,合并传递给解析器
|
|
183
|
+
const nextSibling = body.namedChild(i + 1);
|
|
184
|
+
const bodyNode = nextSibling?.type === 'function_body' ? nextSibling : null;
|
|
185
|
+
const method = _parseDartMethod(child, className, bodyNode);
|
|
186
|
+
if (method) {
|
|
187
|
+
ctx.methods.push(method);
|
|
188
|
+
}
|
|
189
|
+
break;
|
|
190
|
+
}
|
|
191
|
+
case 'function_definition': {
|
|
173
192
|
const method = _parseFunctionDef(child, className);
|
|
174
193
|
if (method) {
|
|
175
194
|
ctx.methods.push(method);
|
|
@@ -264,9 +283,72 @@ function _parseEnumDecl(node, ctx) {
|
|
|
264
283
|
});
|
|
265
284
|
}
|
|
266
285
|
// ── Function / Method ────────────────────────────────────────
|
|
286
|
+
/**
|
|
287
|
+
* 解析 Dart class body 中的 method_signature / getter_signature / setter_signature。
|
|
288
|
+
* tree-sitter-dart 中这些节点的 identifier 嵌在 function_signature 子节点内,
|
|
289
|
+
* 且 function_body 是兄弟节点而非子节点。
|
|
290
|
+
*/
|
|
291
|
+
function _parseDartMethod(sigNode, className, bodyNode) {
|
|
292
|
+
// 递归搜索第一个 identifier(跳过类型标识符)
|
|
293
|
+
const name = _findMethodName(sigNode);
|
|
294
|
+
if (!name) {
|
|
295
|
+
return null;
|
|
296
|
+
}
|
|
297
|
+
const text = sigNode.text;
|
|
298
|
+
const isStatic = text.includes('static ');
|
|
299
|
+
const isAsync = bodyNode
|
|
300
|
+
? bodyNode.text.includes('async') || bodyNode.text.includes('async*')
|
|
301
|
+
: false;
|
|
302
|
+
const isOverride = text.includes('@override');
|
|
303
|
+
const bodyLines = bodyNode ? bodyNode.endPosition.row - bodyNode.startPosition.row + 1 : 0;
|
|
304
|
+
const complexity = bodyNode ? _estimateComplexity(bodyNode) : 1;
|
|
305
|
+
const nestingDepth = bodyNode ? _maxNesting(bodyNode, 0) : 0;
|
|
306
|
+
let kind = 'definition';
|
|
307
|
+
if (sigNode.type === 'getter_signature') {
|
|
308
|
+
kind = 'getter';
|
|
309
|
+
}
|
|
310
|
+
if (sigNode.type === 'setter_signature') {
|
|
311
|
+
kind = 'setter';
|
|
312
|
+
}
|
|
313
|
+
return {
|
|
314
|
+
name,
|
|
315
|
+
className: className || null,
|
|
316
|
+
isClassMethod: isStatic,
|
|
317
|
+
isExported: !name.startsWith('_'),
|
|
318
|
+
isAsync,
|
|
319
|
+
isOverride,
|
|
320
|
+
paramCount: _countChildParams(sigNode),
|
|
321
|
+
bodyLines,
|
|
322
|
+
complexity,
|
|
323
|
+
nestingDepth,
|
|
324
|
+
line: sigNode.startPosition.row + 1,
|
|
325
|
+
kind,
|
|
326
|
+
};
|
|
327
|
+
}
|
|
328
|
+
function _findMethodName(node) {
|
|
329
|
+
// method_signature > function_signature > identifier
|
|
330
|
+
// getter_signature > identifier
|
|
331
|
+
// setter_signature > identifier
|
|
332
|
+
for (let i = 0; i < node.namedChildCount; i++) {
|
|
333
|
+
const c = node.namedChild(i);
|
|
334
|
+
if (c.type === 'identifier') {
|
|
335
|
+
return c.text;
|
|
336
|
+
}
|
|
337
|
+
if (c.type === 'function_signature') {
|
|
338
|
+
const id = c.namedChildren.find((n) => n.type === 'identifier');
|
|
339
|
+
if (id) {
|
|
340
|
+
return id.text;
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
return null;
|
|
345
|
+
}
|
|
267
346
|
function _parseFunctionDef(node, className) {
|
|
268
|
-
|
|
269
|
-
|
|
347
|
+
// 先尝试直接子节点,再递归搜索
|
|
348
|
+
let name = node.namedChildren.find((c) => c.type === 'identifier' || c.type === 'function_name')?.text;
|
|
349
|
+
if (!name) {
|
|
350
|
+
name = _findMethodName(node);
|
|
351
|
+
}
|
|
270
352
|
if (!name) {
|
|
271
353
|
return null;
|
|
272
354
|
}
|
|
@@ -294,11 +376,40 @@ function _parseFunctionDef(node, className) {
|
|
|
294
376
|
}
|
|
295
377
|
// ── Property / Field ─────────────────────────────────────────
|
|
296
378
|
function _parsePropertyDecl(node, className) {
|
|
297
|
-
|
|
298
|
-
|
|
379
|
+
// tree-sitter-dart property 结构:
|
|
380
|
+
// declaration > type_identifier + initialized_identifier_list > initialized_identifier > identifier
|
|
381
|
+
// declaration > identifier (简单)
|
|
382
|
+
// static_final_declaration > identifier
|
|
383
|
+
let name = null;
|
|
384
|
+
// 1. 直接子节点 identifier
|
|
385
|
+
const directId = node.namedChildren.find((c) => c.type === 'identifier');
|
|
386
|
+
if (directId) {
|
|
387
|
+
name = directId.text;
|
|
388
|
+
}
|
|
389
|
+
// 2. 嵌套在 initialized_identifier_list > initialized_identifier > identifier
|
|
390
|
+
if (!name) {
|
|
391
|
+
const idList = node.namedChildren.find((c) => c.type === 'initialized_identifier_list');
|
|
392
|
+
if (idList) {
|
|
393
|
+
const initId = idList.namedChildren.find((c) => c.type === 'initialized_identifier');
|
|
394
|
+
if (initId) {
|
|
395
|
+
const id = initId.namedChildren.find((c) => c.type === 'identifier');
|
|
396
|
+
if (id) {
|
|
397
|
+
name = id.text;
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
// 3. 直接 initialized_identifier
|
|
403
|
+
if (!name) {
|
|
404
|
+
const initId = node.namedChildren.find((c) => c.type === 'initialized_identifier');
|
|
405
|
+
if (initId) {
|
|
406
|
+
const id = initId.namedChildren.find((c) => c.type === 'identifier');
|
|
407
|
+
name = id?.text || initId.text;
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
if (!name) {
|
|
299
411
|
return null;
|
|
300
412
|
}
|
|
301
|
-
const name = nameNode.text;
|
|
302
413
|
const text = node.text;
|
|
303
414
|
const isFinal = text.includes('final ');
|
|
304
415
|
const isLate = text.includes('late ');
|
|
@@ -172,10 +172,8 @@ function _parseJavaClass(node) {
|
|
|
172
172
|
}
|
|
173
173
|
}
|
|
174
174
|
}
|
|
175
|
-
//
|
|
176
|
-
const annotations = node
|
|
177
|
-
.filter((c) => c.type === 'marker_annotation' || c.type === 'annotation')
|
|
178
|
-
.map((a) => a.text);
|
|
175
|
+
// 提取注解(tree-sitter-java: 注解在 modifiers 子节点内)
|
|
176
|
+
const annotations = _extractAnnotations(node);
|
|
179
177
|
// 修饰符
|
|
180
178
|
const modifiers = node.namedChildren.find((c) => c.type === 'modifiers');
|
|
181
179
|
const isAbstract = modifiers?.text?.includes('abstract') || false;
|
|
@@ -216,9 +214,7 @@ function _parseJavaMethod(node, className) {
|
|
|
216
214
|
const bodyLines = body ? body.endPosition.row - body.startPosition.row + 1 : 0;
|
|
217
215
|
const complexity = body ? _estimateComplexity(body) : 1;
|
|
218
216
|
const nestingDepth = body ? _maxNesting(body, 0) : 0;
|
|
219
|
-
const annotations = node
|
|
220
|
-
.filter((c) => c.type === 'marker_annotation' || c.type === 'annotation')
|
|
221
|
-
.map((a) => a.text);
|
|
217
|
+
const annotations = _extractAnnotations(node);
|
|
222
218
|
return {
|
|
223
219
|
name,
|
|
224
220
|
className,
|
|
@@ -241,9 +237,7 @@ function _parseJavaField(node, className) {
|
|
|
241
237
|
const isStatic = modifiers?.text?.includes('static') || false;
|
|
242
238
|
const isFinal = modifiers?.text?.includes('final') || false;
|
|
243
239
|
const isPrivate = modifiers?.text?.includes('private') || false;
|
|
244
|
-
const annotations = node
|
|
245
|
-
.filter((c) => c.type === 'marker_annotation' || c.type === 'annotation')
|
|
246
|
-
.map((a) => a.text);
|
|
240
|
+
const annotations = _extractAnnotations(node);
|
|
247
241
|
// Phase 5.3: Extract field type for DI resolution
|
|
248
242
|
// field_declaration: [modifiers] type_identifier variable_declarator
|
|
249
243
|
let typeAnnotation = null;
|
|
@@ -267,6 +261,27 @@ function _parseJavaField(node, className) {
|
|
|
267
261
|
line: node.startPosition.row + 1,
|
|
268
262
|
};
|
|
269
263
|
}
|
|
264
|
+
/**
|
|
265
|
+
* tree-sitter-java 中注解位于 modifiers 子节点内,而非声明节点的直接子节点。
|
|
266
|
+
* 此辅助函数同时搜索两层:node.namedChildren + modifiers.namedChildren。
|
|
267
|
+
*/
|
|
268
|
+
function _extractAnnotations(node) {
|
|
269
|
+
// 1. 直接子节点(兼容其他可能的 AST 结构)
|
|
270
|
+
const direct = node.namedChildren
|
|
271
|
+
.filter((c) => c.type === 'marker_annotation' || c.type === 'annotation')
|
|
272
|
+
.map((a) => a.text);
|
|
273
|
+
if (direct.length > 0) {
|
|
274
|
+
return direct;
|
|
275
|
+
}
|
|
276
|
+
// 2. modifiers 子节点
|
|
277
|
+
const modifiers = node.namedChildren.find((c) => c.type === 'modifiers');
|
|
278
|
+
if (modifiers) {
|
|
279
|
+
return modifiers.namedChildren
|
|
280
|
+
.filter((c) => c.type === 'marker_annotation' || c.type === 'annotation')
|
|
281
|
+
.map((a) => a.text);
|
|
282
|
+
}
|
|
283
|
+
return [];
|
|
284
|
+
}
|
|
270
285
|
// ── Java 模式检测 ──
|
|
271
286
|
function detectJavaPatterns(root, lang, methods, properties, classes) {
|
|
272
287
|
const patterns = [];
|
|
@@ -75,9 +75,69 @@ function _walkJSClassBody(body, ctx, className) {
|
|
|
75
75
|
else if (child.type === 'field_definition' || child.type === 'public_field_definition') {
|
|
76
76
|
const name = child.namedChildren.find((c) => c.type === 'property_identifier')?.text;
|
|
77
77
|
if (name) {
|
|
78
|
-
|
|
78
|
+
const isStatic = child.text.trimStart().startsWith('static');
|
|
79
|
+
ctx.properties.push({
|
|
80
|
+
name,
|
|
81
|
+
className,
|
|
82
|
+
isStatic,
|
|
83
|
+
isConstant: false,
|
|
84
|
+
line: child.startPosition.row + 1,
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
// 从 constructor 中提取 this.xxx = ... 赋值属性
|
|
90
|
+
_extractConstructorProperties(body, ctx, className);
|
|
91
|
+
}
|
|
92
|
+
function _extractConstructorProperties(body, ctx, className) {
|
|
93
|
+
if (!body) {
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
for (let i = 0; i < body.namedChildCount; i++) {
|
|
97
|
+
const child = body.namedChild(i);
|
|
98
|
+
if (child.type !== 'method_definition') {
|
|
99
|
+
continue;
|
|
100
|
+
}
|
|
101
|
+
const nameNode = child.namedChildren.find((c) => c.type === 'property_identifier' || c.type === 'identifier');
|
|
102
|
+
if (nameNode?.text !== 'constructor') {
|
|
103
|
+
continue;
|
|
104
|
+
}
|
|
105
|
+
const stmtBlock = child.namedChildren.find((c) => c.type === 'statement_block');
|
|
106
|
+
if (!stmtBlock) {
|
|
107
|
+
continue;
|
|
108
|
+
}
|
|
109
|
+
const seen = new Set(ctx.properties.filter((p) => p.className === className).map((p) => p.name));
|
|
110
|
+
_walkForThisAssignments(stmtBlock, ctx, className, seen);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
function _walkForThisAssignments(node, ctx, className, seen) {
|
|
114
|
+
for (let i = 0; i < node.childCount; i++) {
|
|
115
|
+
const child = node.child(i);
|
|
116
|
+
if (child.type === 'expression_statement') {
|
|
117
|
+
const expr = child.namedChildren.find((c) => c.type === 'assignment_expression');
|
|
118
|
+
if (expr) {
|
|
119
|
+
const left = expr.namedChildren[0];
|
|
120
|
+
if (left?.type === 'member_expression') {
|
|
121
|
+
const obj = left.namedChildren.find((c) => c.type === 'this');
|
|
122
|
+
const prop = left.namedChildren.find((c) => c.type === 'property_identifier');
|
|
123
|
+
if (obj && prop && !seen.has(prop.text)) {
|
|
124
|
+
seen.add(prop.text);
|
|
125
|
+
ctx.properties.push({
|
|
126
|
+
name: prop.text,
|
|
127
|
+
className,
|
|
128
|
+
isStatic: false,
|
|
129
|
+
isConstant: false,
|
|
130
|
+
line: child.startPosition.row + 1,
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
}
|
|
79
134
|
}
|
|
80
135
|
}
|
|
136
|
+
else if (child.namedChildCount > 0 &&
|
|
137
|
+
child.type !== 'function' &&
|
|
138
|
+
child.type !== 'arrow_function') {
|
|
139
|
+
_walkForThisAssignments(child, ctx, className, seen);
|
|
140
|
+
}
|
|
81
141
|
}
|
|
82
142
|
}
|
|
83
143
|
function _parseJSClass(node) {
|
|
@@ -85,12 +145,10 @@ function _parseJSClass(node) {
|
|
|
85
145
|
let superclass = null;
|
|
86
146
|
for (const child of node.namedChildren) {
|
|
87
147
|
if (child.type === 'class_heritage') {
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
superclass = typeNode.text;
|
|
93
|
-
}
|
|
148
|
+
// tree-sitter-javascript: class_heritage → identifier (直接子节点, 无 extends_clause 包装)
|
|
149
|
+
const typeNode = child.namedChildren.find((c) => c.type === 'identifier' || c.type === 'member_expression');
|
|
150
|
+
if (typeNode) {
|
|
151
|
+
superclass = typeNode.text;
|
|
94
152
|
}
|
|
95
153
|
}
|
|
96
154
|
}
|
|
@@ -142,6 +200,44 @@ function _parseJSVariableDecl(node, ctx, parentClassName) {
|
|
|
142
200
|
}
|
|
143
201
|
function detectJSPatterns(root, lang, methods, properties, classes) {
|
|
144
202
|
const patterns = [];
|
|
203
|
+
// 按 className 分组
|
|
204
|
+
const methodsByClass = new Map();
|
|
205
|
+
const propsByClass = new Map();
|
|
206
|
+
for (const m of methods) {
|
|
207
|
+
const k = m.className || '';
|
|
208
|
+
if (!methodsByClass.has(k)) {
|
|
209
|
+
methodsByClass.set(k, []);
|
|
210
|
+
}
|
|
211
|
+
methodsByClass.get(k).push(m);
|
|
212
|
+
}
|
|
213
|
+
for (const p of properties) {
|
|
214
|
+
const k = p.className || '';
|
|
215
|
+
if (!propsByClass.has(k)) {
|
|
216
|
+
propsByClass.set(k, []);
|
|
217
|
+
}
|
|
218
|
+
propsByClass.get(k).push(p);
|
|
219
|
+
}
|
|
220
|
+
for (const cls of classes) {
|
|
221
|
+
const clsMethods = methodsByClass.get(cls.name) || [];
|
|
222
|
+
const clsProps = propsByClass.get(cls.name) || [];
|
|
223
|
+
// ── Singleton: static getInstance() / static instance ──
|
|
224
|
+
const hasGetInstance = clsMethods.some((m) => m.isClassMethod && /^getInstance$|^shared$/.test(m.name));
|
|
225
|
+
const hasStaticInstance = clsProps.some((p) => p.isStatic && /^instance$|^shared$|^default$/.test(p.name));
|
|
226
|
+
if (hasGetInstance || hasStaticInstance) {
|
|
227
|
+
patterns.push({ type: 'singleton', className: cls.name, line: cls.line, confidence: 0.9 });
|
|
228
|
+
}
|
|
229
|
+
// ── Observer / EventEmitter ──
|
|
230
|
+
const emitMethods = clsMethods.filter((m) => /^on$|^emit$|^addEventListener$|^removeEventListener$|^subscribe$|^addListener$/.test(m.name));
|
|
231
|
+
if (emitMethods.length >= 2) {
|
|
232
|
+
patterns.push({ type: 'observer', className: cls.name, line: cls.line, confidence: 0.85 });
|
|
233
|
+
}
|
|
234
|
+
// ── Middleware ──
|
|
235
|
+
if (/middleware|interceptor/i.test(cls.name) ||
|
|
236
|
+
clsMethods.some((m) => /^use$|^handle$/.test(m.name) && m.isClassMethod === false)) {
|
|
237
|
+
patterns.push({ type: 'middleware', className: cls.name, line: cls.line, confidence: 0.8 });
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
// 自由函数级别的模式
|
|
145
241
|
for (const m of methods) {
|
|
146
242
|
if (/^use[A-Z]/.test(m.name) && !m.className) {
|
|
147
243
|
patterns.push({ type: 'react-hook', methodName: m.name, line: m.line, confidence: 0.9 });
|
|
@@ -155,15 +251,6 @@ function detectJSPatterns(root, lang, methods, properties, classes) {
|
|
|
155
251
|
confidence: 0.8,
|
|
156
252
|
});
|
|
157
253
|
}
|
|
158
|
-
if (/^on[A-Z]|^emit$|^addEventListener$|^subscribe$/.test(m.name)) {
|
|
159
|
-
patterns.push({
|
|
160
|
-
type: 'observer',
|
|
161
|
-
className: m.className,
|
|
162
|
-
methodName: m.name,
|
|
163
|
-
line: m.line,
|
|
164
|
-
confidence: 0.7,
|
|
165
|
-
});
|
|
166
|
-
}
|
|
167
254
|
}
|
|
168
255
|
return patterns;
|
|
169
256
|
}
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* @description ObjC AST Walker 插件 - 从 AstAnalyzer.js 迁移
|
|
4
4
|
*/
|
|
5
5
|
declare function walkObjC(root: any, ctx: any): void;
|
|
6
|
-
declare function detectObjCPatterns(root: any, lang: any, methods: any, properties: any, classes: any):
|
|
6
|
+
declare function detectObjCPatterns(root: any, lang: any, methods: any, properties: any, classes: any): any[];
|
|
7
7
|
declare function getGrammar(): any;
|
|
8
8
|
export declare function setGrammar(grammar: any): void;
|
|
9
9
|
export declare const plugin: {
|
|
@@ -257,9 +257,86 @@ function _parseObjCProperty(node, className) {
|
|
|
257
257
|
}
|
|
258
258
|
// ── ObjC 模式检测 ──
|
|
259
259
|
function detectObjCPatterns(root, lang, methods, properties, classes) {
|
|
260
|
-
|
|
261
|
-
//
|
|
262
|
-
|
|
260
|
+
const patterns = [];
|
|
261
|
+
// 按 className 分组
|
|
262
|
+
const methodsByClass = new Map();
|
|
263
|
+
const propsByClass = new Map();
|
|
264
|
+
for (const m of methods) {
|
|
265
|
+
const k = m.className || '';
|
|
266
|
+
if (!methodsByClass.has(k)) {
|
|
267
|
+
methodsByClass.set(k, []);
|
|
268
|
+
}
|
|
269
|
+
methodsByClass.get(k).push(m);
|
|
270
|
+
}
|
|
271
|
+
for (const p of properties) {
|
|
272
|
+
const k = p.className || '';
|
|
273
|
+
if (!propsByClass.has(k)) {
|
|
274
|
+
propsByClass.set(k, []);
|
|
275
|
+
}
|
|
276
|
+
propsByClass.get(k).push(p);
|
|
277
|
+
}
|
|
278
|
+
for (const cls of classes) {
|
|
279
|
+
const clsMethods = methodsByClass.get(cls.name) || [];
|
|
280
|
+
const clsProps = propsByClass.get(cls.name) || [];
|
|
281
|
+
// ── Singleton: +sharedInstance / +shared / +defaultManager ──
|
|
282
|
+
const singletonMethod = clsMethods.find((m) => m.isClassMethod && /^shared|^default|^current|^instance$/.test(m.name));
|
|
283
|
+
if (singletonMethod) {
|
|
284
|
+
patterns.push({
|
|
285
|
+
type: 'singleton',
|
|
286
|
+
className: cls.name,
|
|
287
|
+
methodName: singletonMethod.name,
|
|
288
|
+
line: singletonMethod.line,
|
|
289
|
+
confidence: 0.9,
|
|
290
|
+
});
|
|
291
|
+
}
|
|
292
|
+
// ── Delegate: @property (weak) id<XXXDelegate> delegate ──
|
|
293
|
+
for (const p of clsProps) {
|
|
294
|
+
if (/delegate/i.test(p.name)) {
|
|
295
|
+
const isWeak = (p.attributes || []).some((a) => a === 'weak');
|
|
296
|
+
patterns.push({
|
|
297
|
+
type: 'delegate',
|
|
298
|
+
className: cls.name,
|
|
299
|
+
propertyName: p.name,
|
|
300
|
+
isWeakRef: isWeak,
|
|
301
|
+
line: p.line,
|
|
302
|
+
confidence: 0.95,
|
|
303
|
+
});
|
|
304
|
+
}
|
|
305
|
+
if (/dataSource/i.test(p.name)) {
|
|
306
|
+
patterns.push({
|
|
307
|
+
type: 'delegate',
|
|
308
|
+
className: cls.name,
|
|
309
|
+
propertyName: p.name,
|
|
310
|
+
isWeakRef: true,
|
|
311
|
+
line: p.line,
|
|
312
|
+
confidence: 0.85,
|
|
313
|
+
});
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
// ── Factory: +classWithXxx / +xxxWithYyy (class factory methods) ──
|
|
317
|
+
for (const m of clsMethods) {
|
|
318
|
+
if (m.isClassMethod && /With[A-Z]/.test(m.name)) {
|
|
319
|
+
patterns.push({
|
|
320
|
+
type: 'factory',
|
|
321
|
+
className: cls.name,
|
|
322
|
+
methodName: m.name,
|
|
323
|
+
line: m.line,
|
|
324
|
+
confidence: 0.8,
|
|
325
|
+
});
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
// ── KVO Observer: observeValueForKeyPath / addObserver ──
|
|
329
|
+
const hasKVO = clsMethods.some((m) => /^observeValueForKeyPath$|^addObserver$|^removeObserver$/.test(m.name));
|
|
330
|
+
if (hasKVO) {
|
|
331
|
+
patterns.push({ type: 'observer', className: cls.name, line: cls.line, confidence: 0.85 });
|
|
332
|
+
}
|
|
333
|
+
// ── Notification Observer: NSNotificationCenter pattern ──
|
|
334
|
+
const hasNSNotif = clsMethods.some((m) => /notification|handleNotification|didReceiveNotification/i.test(m.name));
|
|
335
|
+
if (hasNSNotif) {
|
|
336
|
+
patterns.push({ type: 'observer', className: cls.name, line: cls.line, confidence: 0.7 });
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
return patterns;
|
|
263
340
|
}
|
|
264
341
|
// ── 工具函数 ──
|
|
265
342
|
function _findIdentifier(node) {
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* Phase 5: 新增 ImportRecord 结构化导入 + extractCallSites 调用点提取
|
|
6
6
|
*/
|
|
7
7
|
declare function walkSwift(root: any, ctx: any): void;
|
|
8
|
-
declare function detectSwiftPatterns(root: any,
|
|
8
|
+
declare function detectSwiftPatterns(root: any, _lang: any, methods: any, properties: any, classes: any): any[];
|
|
9
9
|
/**
|
|
10
10
|
* 从 Swift AST root 提取所有调用点
|
|
11
11
|
* 遍历 function_declaration 中的 function_body → call_expression
|
|
@@ -23,9 +23,29 @@ function _walkSwiftNode(node, ctx, parentClassName) {
|
|
|
23
23
|
case 'class_declaration':
|
|
24
24
|
case 'struct_declaration':
|
|
25
25
|
case 'enum_declaration': {
|
|
26
|
+
// tree-sitter-swift maps protocol/extension to class_declaration too.
|
|
27
|
+
// Detect the actual keyword child and dispatch accordingly.
|
|
28
|
+
const keyword = _findKeywordChild(child);
|
|
29
|
+
if (keyword === 'protocol') {
|
|
30
|
+
const protoInfo = _parseSwiftProtocol(child);
|
|
31
|
+
ctx.protocols.push(protoInfo);
|
|
32
|
+
break;
|
|
33
|
+
}
|
|
34
|
+
if (keyword === 'extension') {
|
|
35
|
+
const extInfo = _parseSwiftExtension(child);
|
|
36
|
+
ctx.categories.push(extInfo);
|
|
37
|
+
const extBody = child.namedChildren.find((c) => c.type === 'class_body' || c.type === 'extension_body');
|
|
38
|
+
if (extBody) {
|
|
39
|
+
_walkSwiftNode(extBody, ctx, extInfo.className);
|
|
40
|
+
}
|
|
41
|
+
break;
|
|
42
|
+
}
|
|
26
43
|
const classInfo = _parseSwiftTypeDecl(child);
|
|
27
44
|
ctx.classes.push(classInfo);
|
|
28
|
-
const body = child.namedChildren.find((c) => c.type === 'class_body' ||
|
|
45
|
+
const body = child.namedChildren.find((c) => c.type === 'class_body' ||
|
|
46
|
+
c.type === 'struct_body' ||
|
|
47
|
+
c.type === 'enum_body' ||
|
|
48
|
+
c.type === 'enum_class_body');
|
|
29
49
|
if (body) {
|
|
30
50
|
_walkSwiftNode(body, ctx, classInfo.name);
|
|
31
51
|
}
|
|
@@ -66,9 +86,22 @@ function _walkSwiftNode(node, ctx, parentClassName) {
|
|
|
66
86
|
}
|
|
67
87
|
}
|
|
68
88
|
}
|
|
89
|
+
/** Detect the actual Swift keyword from a class_declaration node's children. */
|
|
90
|
+
const _swiftKeywords = ['struct', 'class', 'enum', 'actor', 'protocol', 'extension'];
|
|
91
|
+
function _findKeywordChild(node) {
|
|
92
|
+
for (let i = 0; i < node.childCount; i++) {
|
|
93
|
+
const childType = node.child(i).type;
|
|
94
|
+
if (_swiftKeywords.includes(childType)) {
|
|
95
|
+
return childType;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
return 'class';
|
|
99
|
+
}
|
|
69
100
|
function _parseSwiftTypeDecl(node) {
|
|
70
101
|
const name = node.namedChildren.find((c) => c.type === 'type_identifier' || c.type === 'simple_identifier')?.text || 'Unknown';
|
|
71
|
-
|
|
102
|
+
// tree-sitter-swift maps struct/class/enum/actor all to `class_declaration`.
|
|
103
|
+
// Detect the actual kind from the keyword child node (e.g. struct="struct").
|
|
104
|
+
const kind = _findKeywordChild(node);
|
|
72
105
|
const _superclass = null;
|
|
73
106
|
const protocols = [];
|
|
74
107
|
for (const child of node.namedChildren) {
|
|
@@ -132,7 +165,7 @@ function _parseSwiftExtension(node) {
|
|
|
132
165
|
}
|
|
133
166
|
}
|
|
134
167
|
const methods = [];
|
|
135
|
-
const body = node.namedChildren.find((c) => c.type === 'extension_body');
|
|
168
|
+
const body = node.namedChildren.find((c) => c.type === 'extension_body' || c.type === 'class_body');
|
|
136
169
|
if (body) {
|
|
137
170
|
for (const child of body.namedChildren) {
|
|
138
171
|
if (child.type === 'function_declaration') {
|
|
@@ -196,8 +229,150 @@ function _parseSwiftProperty(node, className) {
|
|
|
196
229
|
};
|
|
197
230
|
}
|
|
198
231
|
// ── Swift 模式检测 ──
|
|
199
|
-
function detectSwiftPatterns(root,
|
|
200
|
-
|
|
232
|
+
function detectSwiftPatterns(root, _lang, methods, properties, classes) {
|
|
233
|
+
const patterns = [];
|
|
234
|
+
// Index properties by className for quick lookup
|
|
235
|
+
const propsByClass = new Map();
|
|
236
|
+
for (const p of properties) {
|
|
237
|
+
if (p.className) {
|
|
238
|
+
const arr = propsByClass.get(p.className) || [];
|
|
239
|
+
arr.push(p);
|
|
240
|
+
propsByClass.set(p.className, arr);
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
// Index methods by className
|
|
244
|
+
const methodsByClass = new Map();
|
|
245
|
+
for (const m of methods) {
|
|
246
|
+
if (m.className && m.kind === 'definition') {
|
|
247
|
+
const arr = methodsByClass.get(m.className) || [];
|
|
248
|
+
arr.push(m);
|
|
249
|
+
methodsByClass.set(m.className, arr);
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
for (const cls of classes) {
|
|
253
|
+
const kind = cls.kind;
|
|
254
|
+
const className = cls.name;
|
|
255
|
+
const clsProps = propsByClass.get(className) || [];
|
|
256
|
+
const clsMethods = methodsByClass.get(className) || [];
|
|
257
|
+
// ── 1. Singleton: `static let shared = ...` 属性 ──
|
|
258
|
+
const sharedProp = clsProps.find((p) => p.isStatic &&
|
|
259
|
+
(p.isConstant || p.isConst) &&
|
|
260
|
+
/^shared$|^default$|^instance$|^current$/.test(p.name));
|
|
261
|
+
if (sharedProp) {
|
|
262
|
+
patterns.push({
|
|
263
|
+
type: 'singleton',
|
|
264
|
+
className,
|
|
265
|
+
propertyName: sharedProp.name,
|
|
266
|
+
line: sharedProp.line,
|
|
267
|
+
confidence: 0.95,
|
|
268
|
+
});
|
|
269
|
+
}
|
|
270
|
+
// ── 2. Delegate: `weak var delegate` 属性或 protocol 命名以 Delegate/DataSource 结尾 ──
|
|
271
|
+
for (const p of clsProps) {
|
|
272
|
+
if (/delegate/i.test(p.name) && !p.name.startsWith('_')) {
|
|
273
|
+
patterns.push({
|
|
274
|
+
type: 'delegate',
|
|
275
|
+
className,
|
|
276
|
+
propertyName: p.name,
|
|
277
|
+
isWeakRef: /weak/.test(`${(p.attributes || []).join(' ')} ${p.modifiers || ''}`),
|
|
278
|
+
line: p.line,
|
|
279
|
+
confidence: 0.95,
|
|
280
|
+
});
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
// ── 3. Factory: `static func make/create/from/build` ──
|
|
284
|
+
for (const m of clsMethods) {
|
|
285
|
+
if (m.isClassMethod && /^make|^create|^from|^build/.test(m.name)) {
|
|
286
|
+
patterns.push({
|
|
287
|
+
type: 'factory',
|
|
288
|
+
className,
|
|
289
|
+
methodName: m.name,
|
|
290
|
+
line: m.line,
|
|
291
|
+
confidence: 0.85,
|
|
292
|
+
});
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
// ── 4. Actor: Swift concurrency primitive ──
|
|
296
|
+
if (kind === 'actor') {
|
|
297
|
+
patterns.push({
|
|
298
|
+
type: 'actor',
|
|
299
|
+
className,
|
|
300
|
+
line: cls.line,
|
|
301
|
+
confidence: 0.95,
|
|
302
|
+
});
|
|
303
|
+
}
|
|
304
|
+
// ── 5. Value type (struct): immutable-by-default semantics ──
|
|
305
|
+
if (kind === 'struct' && !className.endsWith('Error')) {
|
|
306
|
+
// Only flag structs with methods (not pure data containers)
|
|
307
|
+
if (clsMethods.length > 0) {
|
|
308
|
+
patterns.push({
|
|
309
|
+
type: 'value-type',
|
|
310
|
+
className,
|
|
311
|
+
methodCount: clsMethods.length,
|
|
312
|
+
line: cls.line,
|
|
313
|
+
confidence: 0.9,
|
|
314
|
+
});
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
// ── 6. Protocol-oriented default implementation ──
|
|
318
|
+
// (detected via categories/extensions that match a protocol name)
|
|
319
|
+
// Handled below after the class loop.
|
|
320
|
+
// ── 7. ViewModel: class name ends with ViewModel ──
|
|
321
|
+
if (/ViewModel$/.test(className)) {
|
|
322
|
+
patterns.push({
|
|
323
|
+
type: 'viewmodel',
|
|
324
|
+
className,
|
|
325
|
+
line: cls.line,
|
|
326
|
+
confidence: 0.9,
|
|
327
|
+
});
|
|
328
|
+
}
|
|
329
|
+
// ── 8. Coordinator pattern ──
|
|
330
|
+
if (/Coordinator$/.test(className)) {
|
|
331
|
+
patterns.push({
|
|
332
|
+
type: 'coordinator',
|
|
333
|
+
className,
|
|
334
|
+
line: cls.line,
|
|
335
|
+
confidence: 0.85,
|
|
336
|
+
});
|
|
337
|
+
}
|
|
338
|
+
// ── 9. Error type: enum conforming to Error ──
|
|
339
|
+
if (kind === 'enum') {
|
|
340
|
+
const conformsError = (cls.protocols || []).some((p) => p === 'Error' || p === 'LocalizedError');
|
|
341
|
+
if (conformsError || className.endsWith('Error')) {
|
|
342
|
+
patterns.push({
|
|
343
|
+
type: 'error-type',
|
|
344
|
+
className,
|
|
345
|
+
line: cls.line,
|
|
346
|
+
confidence: 0.9,
|
|
347
|
+
});
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
// ── 10. Middleware / Interceptor pattern ──
|
|
351
|
+
if (/Middleware$|Interceptor$/.test(className)) {
|
|
352
|
+
patterns.push({
|
|
353
|
+
type: 'middleware',
|
|
354
|
+
className,
|
|
355
|
+
line: cls.line,
|
|
356
|
+
confidence: 0.85,
|
|
357
|
+
});
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
// ── 11. Observer: methods matching didChange/willChange/observe/subscribe ──
|
|
361
|
+
for (const m of methods) {
|
|
362
|
+
if (m.kind !== 'definition') {
|
|
363
|
+
continue;
|
|
364
|
+
}
|
|
365
|
+
if (/^didChange|^willChange|^observe|^addObserver|^subscribe|^on[A-Z]/.test(m.name)) {
|
|
366
|
+
patterns.push({
|
|
367
|
+
type: 'observer',
|
|
368
|
+
className: m.className,
|
|
369
|
+
methodName: m.name,
|
|
370
|
+
line: m.line,
|
|
371
|
+
confidence: 0.7,
|
|
372
|
+
});
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
return patterns;
|
|
201
376
|
}
|
|
202
377
|
// ── 工具函数 ──
|
|
203
378
|
function _findIdentifier(node) {
|
|
@@ -281,7 +456,10 @@ function _collectSwiftScopes(root) {
|
|
|
281
456
|
child.type === 'struct_declaration' ||
|
|
282
457
|
child.type === 'enum_declaration') {
|
|
283
458
|
const name = child.namedChildren.find((c) => c.type === 'type_identifier' || c.type === 'simple_identifier')?.text;
|
|
284
|
-
const body = child.namedChildren.find((c) => c.type === 'class_body' ||
|
|
459
|
+
const body = child.namedChildren.find((c) => c.type === 'class_body' ||
|
|
460
|
+
c.type === 'struct_body' ||
|
|
461
|
+
c.type === 'enum_body' ||
|
|
462
|
+
c.type === 'enum_class_body');
|
|
285
463
|
if (body) {
|
|
286
464
|
visit(body, name || className);
|
|
287
465
|
}
|
|
@@ -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') {
|
package/package.json
CHANGED
|
Binary file
|