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.
@@ -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
- const nameNode = node.namedChildren.find((c) => c.type === 'identifier' || c.type === 'function_name');
269
- const name = nameNode?.text;
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
- const nameNode = node.namedChildren.find((c) => c.type === 'identifier' || c.type === 'initialized_identifier');
298
- if (!nameNode) {
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.namedChildren
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.namedChildren
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.namedChildren
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
- ctx.properties.push({ name, className, line: child.startPosition.row + 1 });
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
- const ext = child.namedChildren.find((c) => c.type === 'extends_clause');
89
- if (ext) {
90
- const typeNode = ext.namedChildren.find((c) => c.type === 'identifier' || c.type === 'member_expression');
91
- if (typeNode) {
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): never[];
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
- // ObjC 特有的模式检测交由 AstAnalyzer 内的通用 _detectPatterns 处理
261
- // 此函数可扩展 ObjC 特有模式(如 Category 模式)
262
- return [];
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, lang: any, methods: any, properties: any, classes: any): never[];
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' || c.type === 'struct_body' || c.type === 'enum_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
- const kind = node.type.replace('_declaration', '');
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, lang, methods, properties, classes) {
200
- return [];
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' || c.type === 'struct_body' || c.type === 'enum_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
- 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') {
@@ -63,6 +63,7 @@ export interface AstSummary {
63
63
  }
64
64
  export interface AstClassInfo {
65
65
  name: string;
66
+ kind?: string;
66
67
  superclass?: string;
67
68
  methodCount?: number;
68
69
  methods?: unknown[];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "autosnippet",
3
- "version": "3.4.1",
3
+ "version": "3.4.2",
4
4
  "description": "Extract code patterns into a knowledge base for AI coding assistants",
5
5
  "type": "module",
6
6
  "main": "dist/lib/bootstrap.js",