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.
- package/bin/cli.js +35 -0
- package/dashboard/dist/assets/{icons-Dtm0E6DS.js → icons-Cq4-iQhP.js} +152 -87
- package/dashboard/dist/assets/index-DBxH7pVn.css +1 -0
- package/dashboard/dist/assets/index-Dw2F6qAS.js +197 -0
- package/dashboard/dist/assets/{react-markdown-CWxUbOf4.js → react-markdown-BA6FB2NP.js} +1 -1
- package/dashboard/dist/assets/{syntax-highlighter-CJ2drQQb.js → syntax-highlighter-CVLHn9O5.js} +1 -1
- package/dashboard/dist/assets/{vendor-f83ah6cm.js → vendor-BotF760a.js} +61 -61
- package/dashboard/dist/index.html +6 -6
- package/lib/bootstrap.js +1 -1
- package/lib/cli/SetupService.js +33 -8
- package/lib/cli/UpgradeService.js +139 -2
- package/lib/core/ast/ProjectGraph.js +599 -0
- package/lib/core/gateway/Gateway.js +19 -4
- package/lib/core/gateway/GatewayActionRegistry.js +2 -2
- package/lib/domain/recipe/Recipe.js +3 -0
- package/lib/external/ai/AiProvider.js +117 -10
- package/lib/external/ai/providers/ClaudeProvider.js +197 -0
- package/lib/external/ai/providers/GoogleGeminiProvider.js +235 -1
- package/lib/external/ai/providers/OpenAiProvider.js +131 -0
- package/lib/external/mcp/McpServer.js +2 -1
- package/lib/external/mcp/handlers/bootstrap/pipeline/dimension-context.js +216 -0
- package/lib/external/mcp/handlers/bootstrap/pipeline/orchestrator.js +468 -0
- package/lib/external/mcp/handlers/bootstrap/pipeline/tier-scheduler.js +162 -0
- package/lib/external/mcp/handlers/bootstrap/skills.js +225 -0
- package/lib/external/mcp/handlers/bootstrap.js +151 -1634
- package/lib/external/mcp/handlers/browse.js +1 -1
- package/lib/external/mcp/handlers/candidate.js +1 -33
- package/lib/external/mcp/handlers/skill.js +126 -31
- package/lib/external/mcp/tools.js +25 -3
- package/lib/http/middleware/requestLogger.js +23 -4
- package/lib/http/routes/ai.js +3 -1
- package/lib/http/routes/auth.js +3 -2
- package/lib/http/routes/candidates.js +49 -25
- package/lib/http/routes/commands.js +0 -8
- package/lib/http/routes/guardRules.js +1 -16
- package/lib/http/routes/recipes.js +4 -17
- package/lib/http/routes/search.js +16 -22
- package/lib/http/routes/skills.js +40 -3
- package/lib/http/routes/snippets.js +0 -33
- package/lib/http/routes/spm.js +37 -63
- package/lib/http/utils/routeHelpers.js +31 -0
- package/lib/infrastructure/audit/AuditStore.js +18 -0
- package/lib/infrastructure/config/Paths.js +9 -0
- package/lib/infrastructure/logging/Logger.js +86 -3
- package/lib/infrastructure/realtime/RealtimeService.js +2 -5
- package/lib/infrastructure/vector/JsonVectorAdapter.js +24 -1
- package/lib/injection/ServiceContainer.js +62 -3
- package/lib/service/bootstrap/BootstrapTaskManager.js +400 -0
- package/lib/service/candidate/CandidateFileWriter.js +68 -27
- package/lib/service/candidate/CandidateService.js +156 -10
- package/lib/service/chat/AnalystAgent.js +216 -0
- package/lib/service/chat/CandidateGuardrail.js +134 -0
- package/lib/service/chat/ChatAgent.js +1272 -155
- package/lib/service/chat/ContextWindow.js +730 -0
- package/lib/service/chat/ConversationStore.js +377 -0
- package/lib/service/chat/HandoffProtocol.js +180 -0
- package/lib/service/chat/Memory.js +40 -10
- package/lib/service/chat/ProducerAgent.js +240 -0
- package/lib/service/chat/ToolRegistry.js +149 -5
- package/lib/service/chat/tools.js +1493 -60
- package/lib/service/recipe/RecipeFileWriter.js +12 -1
- package/lib/service/skills/EventAggregator.js +187 -0
- package/lib/service/skills/SignalCollector.js +549 -0
- package/lib/service/skills/SkillAdvisor.js +324 -0
- package/lib/service/skills/SkillHooks.js +13 -5
- package/lib/service/spm/SpmService.js +2 -2
- package/package.json +1 -1
- package/templates/copilot-instructions.md +20 -3
- package/templates/cursor-rules/autosnippet-conventions.mdc +21 -4
- package/templates/cursor-rules/autosnippet-skills.mdc +45 -0
- package/dashboard/dist/assets/index-B7VpZOCz.css +0 -1
- 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
|
-
|
|
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
|
-
|
|
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
|
|
74
|
-
return
|
|
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
|
/** 复杂度 */
|