autosnippet 2.5.0 → 2.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (72) hide show
  1. package/bin/cli.js +35 -0
  2. package/dashboard/dist/assets/{icons-Dtm0E6DS.js → icons-Cq4-iQhP.js} +152 -87
  3. package/dashboard/dist/assets/index-DBxH7pVn.css +1 -0
  4. package/dashboard/dist/assets/index-Dw2F6qAS.js +197 -0
  5. package/dashboard/dist/assets/{react-markdown-CWxUbOf4.js → react-markdown-BA6FB2NP.js} +1 -1
  6. package/dashboard/dist/assets/{syntax-highlighter-CJ2drQQb.js → syntax-highlighter-CVLHn9O5.js} +1 -1
  7. package/dashboard/dist/assets/{vendor-f83ah6cm.js → vendor-BotF760a.js} +61 -61
  8. package/dashboard/dist/index.html +6 -6
  9. package/lib/bootstrap.js +1 -1
  10. package/lib/cli/SetupService.js +33 -8
  11. package/lib/cli/UpgradeService.js +139 -2
  12. package/lib/core/ast/ProjectGraph.js +599 -0
  13. package/lib/core/gateway/Gateway.js +19 -4
  14. package/lib/core/gateway/GatewayActionRegistry.js +2 -2
  15. package/lib/domain/recipe/Recipe.js +3 -0
  16. package/lib/external/ai/AiProvider.js +117 -10
  17. package/lib/external/ai/providers/ClaudeProvider.js +197 -0
  18. package/lib/external/ai/providers/GoogleGeminiProvider.js +235 -1
  19. package/lib/external/ai/providers/OpenAiProvider.js +131 -0
  20. package/lib/external/mcp/McpServer.js +2 -1
  21. package/lib/external/mcp/handlers/bootstrap/pipeline/dimension-context.js +216 -0
  22. package/lib/external/mcp/handlers/bootstrap/pipeline/orchestrator.js +468 -0
  23. package/lib/external/mcp/handlers/bootstrap/pipeline/tier-scheduler.js +162 -0
  24. package/lib/external/mcp/handlers/bootstrap/skills.js +225 -0
  25. package/lib/external/mcp/handlers/bootstrap.js +151 -1634
  26. package/lib/external/mcp/handlers/browse.js +1 -1
  27. package/lib/external/mcp/handlers/candidate.js +1 -33
  28. package/lib/external/mcp/handlers/skill.js +126 -31
  29. package/lib/external/mcp/tools.js +25 -3
  30. package/lib/http/middleware/requestLogger.js +23 -4
  31. package/lib/http/routes/ai.js +3 -1
  32. package/lib/http/routes/auth.js +3 -2
  33. package/lib/http/routes/candidates.js +49 -25
  34. package/lib/http/routes/commands.js +0 -8
  35. package/lib/http/routes/guardRules.js +1 -16
  36. package/lib/http/routes/recipes.js +4 -17
  37. package/lib/http/routes/search.js +16 -22
  38. package/lib/http/routes/skills.js +40 -3
  39. package/lib/http/routes/snippets.js +0 -33
  40. package/lib/http/routes/spm.js +37 -63
  41. package/lib/http/utils/routeHelpers.js +31 -0
  42. package/lib/infrastructure/audit/AuditStore.js +18 -0
  43. package/lib/infrastructure/config/Paths.js +9 -0
  44. package/lib/infrastructure/logging/Logger.js +86 -3
  45. package/lib/infrastructure/realtime/RealtimeService.js +2 -5
  46. package/lib/infrastructure/vector/JsonVectorAdapter.js +24 -1
  47. package/lib/injection/ServiceContainer.js +62 -3
  48. package/lib/service/bootstrap/BootstrapTaskManager.js +400 -0
  49. package/lib/service/candidate/CandidateFileWriter.js +68 -27
  50. package/lib/service/candidate/CandidateService.js +156 -10
  51. package/lib/service/chat/AnalystAgent.js +216 -0
  52. package/lib/service/chat/CandidateGuardrail.js +134 -0
  53. package/lib/service/chat/ChatAgent.js +1272 -155
  54. package/lib/service/chat/ContextWindow.js +730 -0
  55. package/lib/service/chat/ConversationStore.js +377 -0
  56. package/lib/service/chat/HandoffProtocol.js +180 -0
  57. package/lib/service/chat/Memory.js +40 -10
  58. package/lib/service/chat/ProducerAgent.js +240 -0
  59. package/lib/service/chat/ToolRegistry.js +149 -5
  60. package/lib/service/chat/tools.js +1493 -60
  61. package/lib/service/recipe/RecipeFileWriter.js +12 -1
  62. package/lib/service/skills/EventAggregator.js +187 -0
  63. package/lib/service/skills/SignalCollector.js +549 -0
  64. package/lib/service/skills/SkillAdvisor.js +324 -0
  65. package/lib/service/skills/SkillHooks.js +13 -5
  66. package/lib/service/spm/SpmService.js +2 -2
  67. package/package.json +1 -1
  68. package/templates/copilot-instructions.md +20 -3
  69. package/templates/cursor-rules/autosnippet-conventions.mdc +21 -4
  70. package/templates/cursor-rules/autosnippet-skills.mdc +45 -0
  71. package/dashboard/dist/assets/index-B7VpZOCz.css +0 -1
  72. package/dashboard/dist/assets/index-D87IZTmZ.js +0 -187
@@ -0,0 +1,599 @@
1
+ /**
2
+ * @module ProjectGraph
3
+ * @description 基于 Tree-sitter 的项目结构图 — v3.0 AI-First Bootstrap 核心组件
4
+ *
5
+ * 职责:
6
+ * 1. 扫描项目源码文件 → 调用 AstAnalyzer 解析
7
+ * 2. 构建 类/协议/Category 的查询索引
8
+ * 3. 提供查询 API 供 Analyst Agent 工具调用
9
+ *
10
+ * 生命周期:
11
+ * - 在 Bootstrap Phase 1 一次性构建 (ProjectGraph.build())
12
+ * - 所有维度共享同一个实例
13
+ * - 构建后只读
14
+ */
15
+
16
+ import { analyzeFile, isAvailable } from '../AstAnalyzer.js';
17
+ import fs from 'fs';
18
+ import path from 'path';
19
+
20
+ // ──────────────────────────────────────────────────────────────────
21
+ // 默认配置
22
+ // ──────────────────────────────────────────────────────────────────
23
+
24
+ const DEFAULTS = {
25
+ maxFiles: 500,
26
+ maxFileSizeBytes: 500_000, // 500KB — 跳过超大文件
27
+ excludePatterns: [
28
+ 'Pods/', 'Carthage/', 'node_modules/', '.build/', 'build/',
29
+ 'DerivedData/', 'vendor/', '.git/', '__tests__/', 'Tests/',
30
+ ],
31
+ extensionToLang: {
32
+ '.m': 'objectivec',
33
+ '.h': 'objectivec',
34
+ '.swift': 'swift',
35
+ },
36
+ };
37
+
38
+ // ──────────────────────────────────────────────────────────────────
39
+ // ProjectGraph
40
+ // ──────────────────────────────────────────────────────────────────
41
+
42
+ export default class ProjectGraph {
43
+
44
+ /** @type {Map<string, ClassInfo>} */
45
+ #classes = new Map();
46
+
47
+ /** @type {Map<string, ProtocolInfo>} */
48
+ #protocols = new Map();
49
+
50
+ /** @type {Map<string, CategoryInfo[]>} */
51
+ #categories = new Map();
52
+
53
+ /** @type {Map<string, string>} 子类 → 父类 */
54
+ #inheritance = new Map();
55
+
56
+ /** @type {Map<string, Set<string>>} 类 → 遵循的协议集合 */
57
+ #conformance = new Map();
58
+
59
+ /** @type {Map<string, FileSymbols>} 文件路径 → 文件级符号 */
60
+ #files = new Map();
61
+
62
+ /** @type {Map<string, MethodInfo[]>} className → 方法列表 (含 impl 中的方法) */
63
+ #methodsByClass = new Map();
64
+
65
+ /** @type {ProjectOverview} 项目统计缓存 */
66
+ #overview = null;
67
+
68
+ /** @type {string} 项目根目录 */
69
+ #projectRoot;
70
+
71
+ /** @type {number} 构建耗时 ms */
72
+ #buildTimeMs = 0;
73
+
74
+ // ── 静态工厂 ──────────────────────────────────────────────────
75
+
76
+ /**
77
+ * 扫描项目并构建 ProjectGraph
78
+ * @param {string} projectRoot 项目根目录
79
+ * @param {object} [options]
80
+ * @param {number} [options.maxFiles=500]
81
+ * @param {string[]} [options.excludePatterns]
82
+ * @param {string[]} [options.extensions] 例如 ['.m', '.h', '.swift']
83
+ * @param {Function} [options.onProgress] (parsed, total) => void
84
+ * @param {number} [options.timeoutMs=30000]
85
+ * @returns {Promise<ProjectGraph>}
86
+ */
87
+ static async build(projectRoot, options = {}) {
88
+ if (!isAvailable()) {
89
+ throw new Error('Tree-sitter not available — cannot build ProjectGraph');
90
+ }
91
+
92
+ const startTime = Date.now();
93
+ const opts = { ...DEFAULTS, ...options };
94
+
95
+ // 1. 收集文件列表
96
+ const extToLang = opts.extensionToLang || DEFAULTS.extensionToLang;
97
+ const extensions = options.extensions
98
+ ? options.extensions
99
+ : Object.keys(extToLang);
100
+
101
+ const files = collectSourceFiles(projectRoot, extensions, opts);
102
+
103
+ // 2. 逐文件解析
104
+ const graph = new ProjectGraph();
105
+ graph.#projectRoot = projectRoot;
106
+ let parsed = 0;
107
+
108
+ for (const filePath of files) {
109
+ if (opts.timeoutMs && (Date.now() - startTime) > opts.timeoutMs) {
110
+ break; // 超时 — 返回部分结果
111
+ }
112
+
113
+ try {
114
+ const content = fs.readFileSync(filePath, 'utf-8');
115
+ const ext = path.extname(filePath);
116
+ const lang = extToLang[ext];
117
+ if (!lang) continue;
118
+
119
+ const relativePath = path.relative(projectRoot, filePath);
120
+ const summary = analyzeFile(content, lang);
121
+ if (!summary) continue;
122
+
123
+ graph.#indexFileSummary(relativePath, summary);
124
+ parsed++;
125
+ opts.onProgress?.(parsed, files.length);
126
+ } catch {
127
+ // 单文件解析失败不阻塞
128
+ }
129
+ }
130
+
131
+ // 3. 构建反向索引
132
+ graph.#buildReverseIndices();
133
+ graph.#buildTimeMs = Date.now() - startTime;
134
+
135
+ return graph;
136
+ }
137
+
138
+ // ── 查询 API ──────────────────────────────────────────────────
139
+
140
+ /**
141
+ * 获取类的完整信息
142
+ * @param {string} className
143
+ * @returns {ClassInfo|null}
144
+ */
145
+ getClassInfo(className) {
146
+ return this.#classes.get(className) || null;
147
+ }
148
+
149
+ /**
150
+ * 获取协议定义 + 所有遵循者
151
+ * @param {string} protocolName
152
+ * @returns {ProtocolInfo|null}
153
+ */
154
+ getProtocolInfo(protocolName) {
155
+ return this.#protocols.get(protocolName) || null;
156
+ }
157
+
158
+ /**
159
+ * 获取继承链 (向上到根类)
160
+ * @param {string} className
161
+ * @returns {string[]} [className, parent, grandparent, ...]
162
+ */
163
+ getInheritanceChain(className) {
164
+ const chain = [];
165
+ let current = className;
166
+ const visited = new Set();
167
+ while (current && !visited.has(current)) {
168
+ chain.push(current);
169
+ visited.add(current);
170
+ current = this.#inheritance.get(current) || null;
171
+ }
172
+ return chain;
173
+ }
174
+
175
+ /**
176
+ * 获取直接子类
177
+ * @param {string} className
178
+ * @returns {string[]}
179
+ */
180
+ getSubclasses(className) {
181
+ const subs = [];
182
+ for (const [child, parent] of this.#inheritance) {
183
+ if (parent === className) subs.push(child);
184
+ }
185
+ return subs;
186
+ }
187
+
188
+ /**
189
+ * 递归获取所有后代类
190
+ * @param {string} className
191
+ * @returns {string[]}
192
+ */
193
+ getAllDescendants(className) {
194
+ const result = [];
195
+ const queue = [className];
196
+ const visited = new Set();
197
+ while (queue.length > 0) {
198
+ const current = queue.shift();
199
+ if (visited.has(current)) continue;
200
+ visited.add(current);
201
+ const subs = this.getSubclasses(current);
202
+ result.push(...subs);
203
+ queue.push(...subs);
204
+ }
205
+ return result;
206
+ }
207
+
208
+ /**
209
+ * 获取类的所有 Category 扩展
210
+ * @param {string} className
211
+ * @returns {CategoryInfo[]}
212
+ */
213
+ getCategoryExtensions(className) {
214
+ return this.#categories.get(className) || [];
215
+ }
216
+
217
+ /**
218
+ * 查找覆写了指定方法的所有后代类
219
+ * @param {string} className
220
+ * @param {string} methodName 方法名或 selector
221
+ * @returns {OverrideInfo[]}
222
+ */
223
+ getMethodOverrides(className, methodName) {
224
+ const descendants = this.getAllDescendants(className);
225
+ const overrides = [];
226
+
227
+ for (const desc of descendants) {
228
+ const methods = this.#methodsByClass.get(desc) || [];
229
+ const match = methods.find(m =>
230
+ m.name === methodName || m.selector === methodName
231
+ );
232
+ if (match) {
233
+ overrides.push({
234
+ className: desc,
235
+ method: match,
236
+ filePath: this.#classes.get(desc)?.filePath || 'unknown',
237
+ });
238
+ }
239
+ }
240
+
241
+ return overrides;
242
+ }
243
+
244
+ /**
245
+ * 获取类的所有方法
246
+ * @param {string} className
247
+ * @returns {MethodInfo[]}
248
+ */
249
+ getClassMethods(className) {
250
+ return this.#methodsByClass.get(className) || [];
251
+ }
252
+
253
+ /**
254
+ * 获取文件的符号摘要
255
+ * @param {string} relativePath
256
+ * @returns {FileSymbols|null}
257
+ */
258
+ getFileSymbols(relativePath) {
259
+ return this.#files.get(relativePath) || null;
260
+ }
261
+
262
+ /**
263
+ * 搜索类名 (模糊匹配)
264
+ * @param {string} query
265
+ * @param {number} [limit=20]
266
+ * @returns {string[]}
267
+ */
268
+ searchClasses(query, limit = 20) {
269
+ const lower = query.toLowerCase();
270
+ const results = [];
271
+ for (const name of this.#classes.keys()) {
272
+ if (name.toLowerCase().includes(lower)) {
273
+ results.push(name);
274
+ if (results.length >= limit) break;
275
+ }
276
+ }
277
+ return results;
278
+ }
279
+
280
+ /**
281
+ * 获取项目概览统计
282
+ * @returns {ProjectOverview}
283
+ */
284
+ getOverview() {
285
+ if (this.#overview) return this.#overview;
286
+
287
+ // 按模块 (顶层目录) 统计
288
+ const classesPerModule = {};
289
+ const topModules = new Set();
290
+ const entryPoints = [];
291
+
292
+ for (const [filePath, symbols] of this.#files) {
293
+ const parts = filePath.split('/');
294
+ const module = parts.length > 1 ? parts[0] : '(root)';
295
+ topModules.add(module);
296
+
297
+ if (!classesPerModule[module]) classesPerModule[module] = 0;
298
+ classesPerModule[module] += symbols.classes.length;
299
+
300
+ // 入口点检测
301
+ const base = path.basename(filePath);
302
+ if (/^(AppDelegate|main|SceneDelegate)\.(m|swift)$/.test(base)) {
303
+ entryPoints.push(filePath);
304
+ }
305
+ }
306
+
307
+ this.#overview = {
308
+ totalFiles: this.#files.size,
309
+ totalClasses: this.#classes.size,
310
+ totalProtocols: this.#protocols.size,
311
+ totalCategories: [...this.#categories.values()].reduce((s, arr) => s + arr.length, 0),
312
+ totalMethods: [...this.#methodsByClass.values()].reduce((s, arr) => s + arr.length, 0),
313
+ topLevelModules: [...topModules].sort(),
314
+ entryPoints,
315
+ classesPerModule,
316
+ buildTimeMs: this.#buildTimeMs,
317
+ };
318
+
319
+ return this.#overview;
320
+ }
321
+
322
+ /**
323
+ * 获取所有类名
324
+ * @returns {string[]}
325
+ */
326
+ getAllClassNames() {
327
+ return [...this.#classes.keys()];
328
+ }
329
+
330
+ /**
331
+ * 获取所有协议名
332
+ * @returns {string[]}
333
+ */
334
+ getAllProtocolNames() {
335
+ return [...this.#protocols.keys()];
336
+ }
337
+
338
+ // ── 内部索引构建 ──────────────────────────────────────────────
339
+
340
+ /**
341
+ * 索引单个文件的解析结果
342
+ */
343
+ #indexFileSummary(relativePath, summary) {
344
+ const fileSymbols = {
345
+ path: relativePath,
346
+ lang: summary.lang,
347
+ classes: [],
348
+ protocols: [],
349
+ categories: [],
350
+ imports: summary.imports || [],
351
+ };
352
+
353
+ // 索引类
354
+ for (const cls of summary.classes) {
355
+ const classInfo = {
356
+ name: cls.name,
357
+ filePath: relativePath,
358
+ line: cls.line,
359
+ endLine: cls.endLine,
360
+ superClass: cls.superclass || null,
361
+ protocols: cls.protocols || [],
362
+ properties: [],
363
+ methods: [],
364
+ imports: summary.imports || [],
365
+ };
366
+
367
+ // 收集该类的属性
368
+ for (const prop of (summary.properties || [])) {
369
+ if (prop.className === cls.name) {
370
+ classInfo.properties.push({
371
+ name: prop.name,
372
+ type: prop.type || 'id',
373
+ attributes: prop.attributes || [],
374
+ line: prop.line,
375
+ });
376
+ }
377
+ }
378
+
379
+ // 收集该类的方法 (声明 + 定义去重)
380
+ const methodSet = new Set();
381
+ for (const m of (summary.methods || [])) {
382
+ if (m.className === cls.name) {
383
+ const key = `${m.isClassMethod ? '+' : '-'}${m.name}`;
384
+ if (!methodSet.has(key)) {
385
+ methodSet.add(key);
386
+ classInfo.methods.push({
387
+ name: m.name,
388
+ selector: m.selector || m.name,
389
+ line: m.line,
390
+ isClassMethod: m.isClassMethod || false,
391
+ returnType: m.returnType || 'void',
392
+ paramCount: m.paramCount || 0,
393
+ bodyLines: m.bodyLines || 0,
394
+ complexity: m.complexity || 1,
395
+ });
396
+ }
397
+ }
398
+ }
399
+
400
+ this.#classes.set(cls.name, classInfo);
401
+
402
+ // 继承关系
403
+ if (cls.superclass) {
404
+ this.#inheritance.set(cls.name, cls.superclass);
405
+ }
406
+
407
+ // 协议遵循
408
+ if (cls.protocols && cls.protocols.length > 0) {
409
+ if (!this.#conformance.has(cls.name)) {
410
+ this.#conformance.set(cls.name, new Set());
411
+ }
412
+ for (const p of cls.protocols) {
413
+ this.#conformance.get(cls.name).add(p);
414
+ }
415
+ }
416
+
417
+ fileSymbols.classes.push(cls.name);
418
+ }
419
+
420
+ // 索引协议
421
+ for (const proto of summary.protocols) {
422
+ const protoInfo = {
423
+ name: proto.name,
424
+ filePath: relativePath,
425
+ line: proto.line,
426
+ inherits: proto.inherits || [],
427
+ requiredMethods: [],
428
+ optionalMethods: [],
429
+ conformers: [], // 稍后在 buildReverseIndices 中填充
430
+ };
431
+
432
+ for (const m of (proto.methods || [])) {
433
+ const methodInfo = {
434
+ name: m.name,
435
+ selector: m.selector || m.name,
436
+ line: m.line,
437
+ isClassMethod: m.isClassMethod || false,
438
+ returnType: m.returnType || 'void',
439
+ paramCount: m.paramCount || 0,
440
+ };
441
+ if (m.isOptional) {
442
+ protoInfo.optionalMethods.push(methodInfo);
443
+ } else {
444
+ protoInfo.requiredMethods.push(methodInfo);
445
+ }
446
+ }
447
+
448
+ this.#protocols.set(proto.name, protoInfo);
449
+ fileSymbols.protocols.push(proto.name);
450
+ }
451
+
452
+ // 索引 Category
453
+ for (const cat of summary.categories) {
454
+ const catInfo = {
455
+ className: cat.className || cat.name,
456
+ categoryName: cat.categoryName || 'ext',
457
+ filePath: relativePath,
458
+ line: cat.line,
459
+ methods: (cat.methods || []).map(m => ({
460
+ name: m.name,
461
+ selector: m.selector || m.name,
462
+ line: m.line,
463
+ isClassMethod: m.isClassMethod || false,
464
+ returnType: m.returnType || 'void',
465
+ paramCount: m.paramCount || 0,
466
+ })),
467
+ properties: [],
468
+ protocols: cat.protocols || [],
469
+ };
470
+
471
+ const key = catInfo.className;
472
+ if (!this.#categories.has(key)) {
473
+ this.#categories.set(key, []);
474
+ }
475
+ this.#categories.get(key).push(catInfo);
476
+
477
+ // Category 遵循的协议也记录到类的遵循关系
478
+ if (catInfo.protocols.length > 0) {
479
+ if (!this.#conformance.has(key)) {
480
+ this.#conformance.set(key, new Set());
481
+ }
482
+ for (const p of catInfo.protocols) {
483
+ this.#conformance.get(key).add(p);
484
+ }
485
+ }
486
+
487
+ fileSymbols.categories.push(`${catInfo.className}(${catInfo.categoryName})`);
488
+ }
489
+
490
+ // 索引方法 (按类名分组)
491
+ for (const m of (summary.methods || [])) {
492
+ if (!m.className) continue;
493
+ if (!this.#methodsByClass.has(m.className)) {
494
+ this.#methodsByClass.set(m.className, []);
495
+ }
496
+ this.#methodsByClass.get(m.className).push({
497
+ name: m.name,
498
+ selector: m.selector || m.name,
499
+ line: m.line,
500
+ isClassMethod: m.isClassMethod || false,
501
+ returnType: m.returnType || 'void',
502
+ paramCount: m.paramCount || 0,
503
+ bodyLines: m.bodyLines || 0,
504
+ complexity: m.complexity || 1,
505
+ filePath: relativePath,
506
+ });
507
+ }
508
+
509
+ this.#files.set(relativePath, fileSymbols);
510
+ }
511
+
512
+ /**
513
+ * 构建反向索引 — 协议遵循者列表
514
+ */
515
+ #buildReverseIndices() {
516
+ // 填充 protocol.conformers
517
+ for (const [className, protos] of this.#conformance) {
518
+ for (const protoName of protos) {
519
+ const proto = this.#protocols.get(protoName);
520
+ if (proto && !proto.conformers.includes(className)) {
521
+ proto.conformers.push(className);
522
+ }
523
+ }
524
+ }
525
+
526
+ // 补充 classInfo 中的 methods (从 methodsByClass 合并)
527
+ for (const [className, classInfo] of this.#classes) {
528
+ const allMethods = this.#methodsByClass.get(className) || [];
529
+ // 只补充 classInfo.methods 中没有的方法
530
+ const existingNames = new Set(classInfo.methods.map(m => `${m.isClassMethod ? '+' : '-'}${m.name}`));
531
+ for (const m of allMethods) {
532
+ const key = `${m.isClassMethod ? '+' : '-'}${m.name}`;
533
+ if (!existingNames.has(key)) {
534
+ classInfo.methods.push(m);
535
+ existingNames.add(key);
536
+ }
537
+ }
538
+ }
539
+ }
540
+ }
541
+
542
+ // ──────────────────────────────────────────────────────────────────
543
+ // 工具函数 — 文件收集
544
+ // ──────────────────────────────────────────────────────────────────
545
+
546
+ /**
547
+ * 递归收集匹配扩展名的源文件
548
+ * @param {string} dir
549
+ * @param {string[]} extensions
550
+ * @param {object} opts
551
+ * @returns {string[]}
552
+ */
553
+ function collectSourceFiles(dir, extensions, opts) {
554
+ const results = [];
555
+ const extSet = new Set(extensions);
556
+
557
+ function walk(currentDir) {
558
+ if (results.length >= opts.maxFiles) return;
559
+
560
+ let entries;
561
+ try {
562
+ entries = fs.readdirSync(currentDir, { withFileTypes: true });
563
+ } catch {
564
+ return;
565
+ }
566
+
567
+ for (const entry of entries) {
568
+ if (results.length >= opts.maxFiles) return;
569
+
570
+ const fullPath = path.join(currentDir, entry.name);
571
+ const relativePath = path.relative(dir, fullPath);
572
+
573
+ // 排除模式检查
574
+ if (opts.excludePatterns.some(p => relativePath.includes(p))) {
575
+ continue;
576
+ }
577
+
578
+ if (entry.isDirectory()) {
579
+ walk(fullPath);
580
+ } else if (entry.isFile()) {
581
+ const ext = path.extname(entry.name);
582
+ if (!extSet.has(ext)) continue;
583
+
584
+ // 跳过过大的文件
585
+ try {
586
+ const stat = fs.statSync(fullPath);
587
+ if (stat.size > opts.maxFileSizeBytes) continue;
588
+ } catch {
589
+ continue;
590
+ }
591
+
592
+ results.push(fullPath);
593
+ }
594
+ }
595
+ }
596
+
597
+ walk(dir);
598
+ return results;
599
+ }
@@ -22,6 +22,7 @@ export class Gateway extends EventEmitter {
22
22
  this.constitutionValidator = null;
23
23
  this.permissionManager = null;
24
24
  this.auditLogger = null;
25
+ this.eventBus = null; // 可选:外部注入 EventBus 实例
25
26
  }
26
27
 
27
28
  /**
@@ -219,7 +220,7 @@ export class Gateway extends EventEmitter {
219
220
  async auditSuccess(context, result) {
220
221
  if (!this.auditLogger) return;
221
222
 
222
- await this.auditLogger.log({
223
+ const entry = {
223
224
  requestId: context.requestId,
224
225
  actor: context.actor,
225
226
  action: context.action,
@@ -227,7 +228,14 @@ export class Gateway extends EventEmitter {
227
228
  result: 'success',
228
229
  duration: Date.now() - context.startTime,
229
230
  context: { session: context.session },
230
- });
231
+ };
232
+ await this.auditLogger.log(entry);
233
+
234
+ // 向 EventBus 发送 Gateway 操作完成事件(供 SignalCollector 等监听)
235
+ if (this.eventBus) {
236
+ this.emit('gateway:action:completed', { ...entry, timestamp: Date.now() });
237
+ this.eventBus.emit('gateway:action:completed', { ...entry, timestamp: Date.now() });
238
+ }
231
239
  }
232
240
 
233
241
  /**
@@ -236,7 +244,7 @@ export class Gateway extends EventEmitter {
236
244
  async auditFailure(context, error) {
237
245
  if (!this.auditLogger) return;
238
246
 
239
- await this.auditLogger.log({
247
+ const entry = {
240
248
  requestId: context.requestId,
241
249
  actor: context.actor,
242
250
  action: context.action,
@@ -245,7 +253,14 @@ export class Gateway extends EventEmitter {
245
253
  error: error.message,
246
254
  duration: Date.now() - context.startTime,
247
255
  context: { session: context.session },
248
- });
256
+ };
257
+ await this.auditLogger.log(entry);
258
+
259
+ // 向 EventBus 发送 Gateway 操作失败事件
260
+ if (this.eventBus) {
261
+ this.emit('gateway:action:failed', { ...entry, timestamp: Date.now() });
262
+ this.eventBus.emit('gateway:action:failed', { ...entry, timestamp: Date.now() });
263
+ }
249
264
  }
250
265
 
251
266
  /**
@@ -70,8 +70,8 @@ export function registerGatewayActions(gateway, container) {
70
70
  });
71
71
 
72
72
  gateway.register('candidate:delete', async (ctx) => {
73
- const repo = container.get('candidateRepository');
74
- return repo.delete(ctx.data.candidateId);
73
+ const service = container.get('candidateService');
74
+ return service.deleteCandidate(ctx.data.candidateId, { userId: ctx.actor });
75
75
  });
76
76
 
77
77
  // ========== Recipe Actions ==========
@@ -40,6 +40,7 @@ const KIND_MAP = {
40
40
  'code-pattern': Kind.PATTERN,
41
41
  'architecture': Kind.PATTERN,
42
42
  'solution': Kind.PATTERN,
43
+ 'anti-pattern': Kind.PATTERN,
43
44
  'code-relation': Kind.FACT,
44
45
  'inheritance': Kind.FACT,
45
46
  'call-chain': Kind.FACT,
@@ -87,6 +88,8 @@ export const KnowledgeType = {
87
88
  CODE_STYLE: 'code-style',
88
89
  /** 问题解决方案: 具体 Bug/性能/迁移问题的解决办法 */
89
90
  SOLUTION: 'solution',
91
+ /** 反模式: force-unwrap、超长方法、循环引用等代码风险 */
92
+ ANTI_PATTERN: 'anti-pattern',
90
93
  };
91
94
 
92
95
  /** 复杂度 */