autosnippet 2.19.4 → 2.19.6
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 +177 -0
- package/lib/http/HttpServer.js +6 -26
- package/lib/http/routes/commands.js +10 -8
- package/lib/infrastructure/cache/UnifiedCacheAdapter.js +15 -102
- package/lib/infrastructure/config/Defaults.js +1 -1
- package/package.json +1 -4
- package/lib/infrastructure/cache/RedisService.js +0 -365
package/bin/cli.js
CHANGED
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
*
|
|
6
6
|
* Usage:
|
|
7
7
|
* asd setup - 初始化项目
|
|
8
|
+
* asd coldstart - 冷启动知识库(9 维度分析 + AI 填充)
|
|
8
9
|
* asd ais [Target] - AI 扫描 Target → 直接发布 Recipes
|
|
9
10
|
* asd search <query> - 搜索知识库
|
|
10
11
|
* asd guard <file> - Guard 检查
|
|
@@ -73,6 +74,182 @@ program
|
|
|
73
74
|
service.printSummary();
|
|
74
75
|
});
|
|
75
76
|
|
|
77
|
+
// ─────────────────────────────────────────────────────
|
|
78
|
+
// coldstart 命令 (Knowledge Bootstrap)
|
|
79
|
+
// ─────────────────────────────────────────────────────
|
|
80
|
+
program
|
|
81
|
+
.command('coldstart')
|
|
82
|
+
.description('冷启动知识库:9 维度项目分析 + AI 异步填充(与 Dashboard 点击冷启动流程一致)')
|
|
83
|
+
.option('-d, --dir <path>', '项目目录', '.')
|
|
84
|
+
.option('-m, --max-files <n>', '最大扫描文件数', '500')
|
|
85
|
+
.option('--skip-guard', '跳过 Guard 审计')
|
|
86
|
+
.option('--no-skills', '禁用 Skill 加载')
|
|
87
|
+
.option('--wait', '等待 AI 异步填充完成(默认骨架完成即退出)')
|
|
88
|
+
.option('--json', '以 JSON 格式输出结果')
|
|
89
|
+
.action(async (opts) => {
|
|
90
|
+
const projectRoot = resolve(opts.dir);
|
|
91
|
+
console.log(`\n🚀 AutoSnippet — 冷启动知识库`);
|
|
92
|
+
console.log(` 项目: ${basename(projectRoot)}`);
|
|
93
|
+
console.log(` 路径: ${projectRoot}`);
|
|
94
|
+
console.log(` 最大文件数: ${opts.maxFiles}`);
|
|
95
|
+
if (opts.skipGuard) console.log(' Guard 审计: 跳过');
|
|
96
|
+
console.log('');
|
|
97
|
+
|
|
98
|
+
try {
|
|
99
|
+
const { bootstrap, container } = await initContainer({ projectRoot });
|
|
100
|
+
|
|
101
|
+
// 使用与前端 POST /spm/bootstrap 完全相同的入口: chatAgent.executeTool('bootstrap_knowledge')
|
|
102
|
+
const chatAgent = container.get('chatAgent');
|
|
103
|
+
|
|
104
|
+
const ora = (await import('ora')).default;
|
|
105
|
+
const spinner = ora('Phase 1-4: 收集文件、AST 分析、SPM 依赖、Guard 审计...').start();
|
|
106
|
+
|
|
107
|
+
const result = await chatAgent.executeTool('bootstrap_knowledge', {
|
|
108
|
+
maxFiles: parseInt(opts.maxFiles, 10),
|
|
109
|
+
skipGuard: opts.skipGuard || false,
|
|
110
|
+
contentMaxLines: 120,
|
|
111
|
+
loadSkills: opts.skills !== false,
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
spinner.stop();
|
|
115
|
+
|
|
116
|
+
if (opts.json) {
|
|
117
|
+
console.log(JSON.stringify(result, null, 2));
|
|
118
|
+
} else {
|
|
119
|
+
// 输出骨架报告
|
|
120
|
+
const report = result.report || {};
|
|
121
|
+
const targets = result.targets || [];
|
|
122
|
+
const langStats = result.languageStats || {};
|
|
123
|
+
const guardSummary = result.guardSummary;
|
|
124
|
+
const astSummary = result.astSummary;
|
|
125
|
+
const bootstrapCandidates = result.bootstrapCandidates || {};
|
|
126
|
+
const framework = result.analysisFramework || {};
|
|
127
|
+
|
|
128
|
+
console.log('✅ 冷启动骨架已创建\n');
|
|
129
|
+
|
|
130
|
+
// 项目概况
|
|
131
|
+
console.log('📊 项目概况');
|
|
132
|
+
console.log(` Targets: ${targets.length}`);
|
|
133
|
+
console.log(` 文件: ${report.totals?.files || 0}`);
|
|
134
|
+
console.log(` 主语言: ${result.primaryLanguage || 'unknown'}`);
|
|
135
|
+
if (Object.keys(langStats).length > 0) {
|
|
136
|
+
const langParts = Object.entries(langStats)
|
|
137
|
+
.sort((a, b) => b[1] - a[1])
|
|
138
|
+
.slice(0, 5)
|
|
139
|
+
.map(([ext, count]) => `${ext}(${count})`);
|
|
140
|
+
console.log(` 语言分布: ${langParts.join(', ')}`);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// AST 分析
|
|
144
|
+
if (astSummary) {
|
|
145
|
+
console.log(`\n🔬 AST 分析`);
|
|
146
|
+
console.log(` 类: ${astSummary.classes}, 协议: ${astSummary.protocols}, Category: ${astSummary.categories}`);
|
|
147
|
+
if (astSummary.metrics) {
|
|
148
|
+
console.log(` 方法总数: ${astSummary.metrics.totalMethods}, 复杂方法: ${astSummary.metrics.complexMethods}`);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// SPM 依赖
|
|
153
|
+
if (report.phases?.spmDependencyGraph) {
|
|
154
|
+
console.log(`\n📦 SPM 依赖`);
|
|
155
|
+
console.log(` 依赖边: ${report.phases.spmDependencyGraph.edgesWritten}`);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// Guard 审计
|
|
159
|
+
if (guardSummary) {
|
|
160
|
+
console.log(`\n🛡️ Guard 审计`);
|
|
161
|
+
console.log(` 违规: ${guardSummary.totalViolations} (${guardSummary.errors} errors, ${guardSummary.warnings} warnings)`);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// 维度分析框架
|
|
165
|
+
if (framework.dimensions) {
|
|
166
|
+
console.log(`\n📋 分析维度: ${framework.dimensions.length} 个`);
|
|
167
|
+
for (const dim of framework.dimensions) {
|
|
168
|
+
const type = dim.skillWorthy ? (dim.dualOutput ? 'Dual' : 'Skill') : 'Candidate';
|
|
169
|
+
console.log(` • ${dim.label} [${type}]`);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// AI 异步填充状态
|
|
174
|
+
console.log(`\n🤖 AI 异步填充: ${result.asyncFill ? '已启动' : '未启用'}`);
|
|
175
|
+
if (result.bootstrapSession) {
|
|
176
|
+
const session = result.bootstrapSession;
|
|
177
|
+
console.log(` 会话: ${session.id}`);
|
|
178
|
+
console.log(` 任务: ${session.tasks?.length || 0} 个维度`);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
console.log('');
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// 等待模式: 轮询 BootstrapTaskManager 直到所有维度完成
|
|
185
|
+
if (opts.wait && result.bootstrapSession) {
|
|
186
|
+
const ora2 = (await import('ora')).default;
|
|
187
|
+
const waitSpinner = ora2('Phase 5: AI 正在逐维度填充知识...').start();
|
|
188
|
+
let lastStatus = '';
|
|
189
|
+
let attempts = 0;
|
|
190
|
+
const maxAttempts = 600; // 最多等 10 分钟(每秒轮询)
|
|
191
|
+
|
|
192
|
+
while (attempts < maxAttempts) {
|
|
193
|
+
await new Promise(r => setTimeout(r, 1000));
|
|
194
|
+
attempts++;
|
|
195
|
+
|
|
196
|
+
try {
|
|
197
|
+
const taskManager = container.get('bootstrapTaskManager');
|
|
198
|
+
const sessionStatus = taskManager.getSessionStatus();
|
|
199
|
+
|
|
200
|
+
if (!sessionStatus || !sessionStatus.tasks) break;
|
|
201
|
+
|
|
202
|
+
const total = sessionStatus.tasks.length;
|
|
203
|
+
const done = sessionStatus.tasks.filter(t => t.status === 'done' || t.status === 'error').length;
|
|
204
|
+
const current = sessionStatus.tasks.find(t => t.status === 'running');
|
|
205
|
+
const statusText = current
|
|
206
|
+
? `[${done}/${total}] 正在处理: ${current.meta?.label || current.id}`
|
|
207
|
+
: `[${done}/${total}] 等待中...`;
|
|
208
|
+
|
|
209
|
+
if (statusText !== lastStatus) {
|
|
210
|
+
waitSpinner.text = statusText;
|
|
211
|
+
lastStatus = statusText;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
if (done >= total) {
|
|
215
|
+
waitSpinner.succeed(`AI 填充完成: ${total} 个维度`);
|
|
216
|
+
|
|
217
|
+
// 输出各维度结果
|
|
218
|
+
if (!opts.json) {
|
|
219
|
+
const succeeded = sessionStatus.tasks.filter(t => t.status === 'done').length;
|
|
220
|
+
const failed = sessionStatus.tasks.filter(t => t.status === 'error').length;
|
|
221
|
+
console.log(` ✅ 成功: ${succeeded}, ❌ 失败: ${failed}`);
|
|
222
|
+
for (const t of sessionStatus.tasks) {
|
|
223
|
+
const icon = t.status === 'done' ? '✅' : '❌';
|
|
224
|
+
console.log(` ${icon} ${t.meta?.label || t.id}`);
|
|
225
|
+
}
|
|
226
|
+
console.log('');
|
|
227
|
+
}
|
|
228
|
+
break;
|
|
229
|
+
}
|
|
230
|
+
} catch {
|
|
231
|
+
// bootstrapTaskManager 可能还没就绪
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
if (attempts >= maxAttempts) {
|
|
236
|
+
waitSpinner.warn('AI 填充超时(10 分钟),可通过 asd ui 查看进度');
|
|
237
|
+
}
|
|
238
|
+
} else if (!opts.json) {
|
|
239
|
+
console.log('💡 后台 AI 正在逐维度填充知识,可通过以下方式查看进度:');
|
|
240
|
+
console.log(' • asd ui -d . → Dashboard 实时查看');
|
|
241
|
+
console.log(' • 再次运行 asd coldstart --wait 等待完成');
|
|
242
|
+
console.log('');
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
await bootstrap.shutdown();
|
|
246
|
+
} catch (err) {
|
|
247
|
+
console.error(`\n❌ ${err.message}`);
|
|
248
|
+
if (process.env.ASD_DEBUG === '1') console.error(err.stack);
|
|
249
|
+
process.exit(1);
|
|
250
|
+
}
|
|
251
|
+
});
|
|
252
|
+
|
|
76
253
|
// ─────────────────────────────────────────────────────
|
|
77
254
|
// ais 命令 (AI Scan)
|
|
78
255
|
// ─────────────────────────────────────────────────────
|
package/lib/http/HttpServer.js
CHANGED
|
@@ -31,7 +31,6 @@ import knowledgeRouter from './routes/knowledge.js';
|
|
|
31
31
|
import recipesRouter from './routes/recipes.js';
|
|
32
32
|
import wikiRouter from './routes/wiki.js';
|
|
33
33
|
import apiSpec from './api-spec.js';
|
|
34
|
-
import { initRedisService } from '../infrastructure/cache/RedisService.js';
|
|
35
34
|
import { initCacheAdapter } from '../infrastructure/cache/UnifiedCacheAdapter.js';
|
|
36
35
|
import { initPerformanceMonitor } from '../infrastructure/monitoring/PerformanceMonitor.js';
|
|
37
36
|
import { initErrorTracker } from '../infrastructure/monitoring/ErrorTracker.js';
|
|
@@ -44,9 +43,8 @@ export class HttpServer {
|
|
|
44
43
|
this.config = {
|
|
45
44
|
port: config.port || 3000,
|
|
46
45
|
host: config.host || 'localhost',
|
|
47
|
-
enableRedis: config.enableRedis !== false,
|
|
48
46
|
enableMonitoring: config.enableMonitoring !== false,
|
|
49
|
-
cacheMode:
|
|
47
|
+
cacheMode: 'memory',
|
|
50
48
|
...config,
|
|
51
49
|
};
|
|
52
50
|
|
|
@@ -92,27 +90,9 @@ export class HttpServer {
|
|
|
92
90
|
*/
|
|
93
91
|
async initializeServices() {
|
|
94
92
|
try {
|
|
95
|
-
//
|
|
96
|
-
if (this.config.enableRedis && this.config.cacheMode === 'redis') {
|
|
97
|
-
try {
|
|
98
|
-
await initRedisService({
|
|
99
|
-
host: process.env.REDIS_HOST,
|
|
100
|
-
port: process.env.REDIS_PORT,
|
|
101
|
-
password: process.env.REDIS_PASSWORD,
|
|
102
|
-
});
|
|
103
|
-
this.logger.info('Redis service initialized');
|
|
104
|
-
} catch (error) {
|
|
105
|
-
this.logger.warn('Redis initialization failed, falling back to memory cache', {
|
|
106
|
-
error: error.message,
|
|
107
|
-
});
|
|
108
|
-
this.config.cacheMode = 'memory';
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
// 初始化缓存适配器
|
|
93
|
+
// 初始化缓存适配器(纯内存模式)
|
|
113
94
|
this.cacheAdapter = await initCacheAdapter({
|
|
114
|
-
mode:
|
|
115
|
-
fallbackToMemory: true,
|
|
95
|
+
mode: 'memory',
|
|
116
96
|
});
|
|
117
97
|
this.logger.info('Cache adapter initialized');
|
|
118
98
|
|
|
@@ -288,13 +268,13 @@ export class HttpServer {
|
|
|
288
268
|
});
|
|
289
269
|
});
|
|
290
270
|
|
|
291
|
-
// 404
|
|
292
|
-
this.app.
|
|
271
|
+
// 404 处理(使用 app.all 确保 layer.route 存在,mountDashboard 依赖此属性定位并重排路由栈)
|
|
272
|
+
this.app.all('*', (req, res) => {
|
|
293
273
|
res.status(404).json({
|
|
294
274
|
success: false,
|
|
295
275
|
error: {
|
|
296
276
|
code: 'NOT_FOUND',
|
|
297
|
-
message: `Route not found: ${req.method} ${req.
|
|
277
|
+
message: `Route not found: ${req.method} ${req.originalUrl}`,
|
|
298
278
|
},
|
|
299
279
|
});
|
|
300
280
|
});
|
|
@@ -26,14 +26,16 @@ router.post('/install', asyncHandler(async (req, res) => {
|
|
|
26
26
|
{ status: 'active' },
|
|
27
27
|
{ page: 1, pageSize: 9999 },
|
|
28
28
|
);
|
|
29
|
-
const recipes = (result?.data || result?.items || [])
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
29
|
+
const recipes = (result?.data || result?.items || [])
|
|
30
|
+
.map(r => ({
|
|
31
|
+
id: r.id,
|
|
32
|
+
title: r.title,
|
|
33
|
+
trigger: r.trigger,
|
|
34
|
+
code: r.content?.pattern || '',
|
|
35
|
+
description: r.description || r.summaryCn || '',
|
|
36
|
+
language: r.language || 'swift',
|
|
37
|
+
}))
|
|
38
|
+
.filter(r => r.code.trim().length > 0); // 跳过没有代码内容的 Recipe
|
|
37
39
|
|
|
38
40
|
const installResult = snippetInstaller.installFromRecipes(recipes);
|
|
39
41
|
|
|
@@ -1,77 +1,30 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* 统一缓存适配器
|
|
3
|
-
*
|
|
3
|
+
* 内存缓存模式
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
import { cacheService as memoryCacheService } from './CacheService.js';
|
|
7
|
-
import { getRedisService } from './RedisService.js';
|
|
8
7
|
import Logger from '../logging/Logger.js';
|
|
9
8
|
|
|
10
9
|
export class UnifiedCacheAdapter {
|
|
11
|
-
constructor(
|
|
12
|
-
this.mode =
|
|
13
|
-
this.fallbackToMemory = options.fallbackToMemory !== false;
|
|
14
|
-
this.redisService = null;
|
|
10
|
+
constructor() {
|
|
11
|
+
this.mode = 'memory';
|
|
15
12
|
this.memoryService = memoryCacheService;
|
|
16
|
-
|
|
17
|
-
Logger.info(`缓存模式: ${this.mode}${this.fallbackToMemory ? ' (支持降级到内存)' : ''}`);
|
|
18
13
|
}
|
|
19
14
|
|
|
20
15
|
/**
|
|
21
16
|
* 初始化缓存服务
|
|
22
17
|
*/
|
|
23
18
|
async initialize() {
|
|
24
|
-
|
|
25
|
-
try {
|
|
26
|
-
this.redisService = getRedisService();
|
|
27
|
-
const health = await this.redisService.healthCheck();
|
|
28
|
-
if (!health.healthy) {
|
|
29
|
-
throw new Error(health.message);
|
|
30
|
-
}
|
|
31
|
-
Logger.info('✅ Redis 缓存已启用');
|
|
32
|
-
} catch (error) {
|
|
33
|
-
Logger.error('Redis 初始化失败:', { error: error.message });
|
|
34
|
-
if (this.fallbackToMemory) {
|
|
35
|
-
Logger.warn('⚠️ 降级为内存缓存模式');
|
|
36
|
-
this.mode = 'memory';
|
|
37
|
-
} else {
|
|
38
|
-
throw error;
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
} else {
|
|
42
|
-
Logger.info('✅ 内存缓存已启用');
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
/**
|
|
47
|
-
* 获取当前活动的缓存服务
|
|
48
|
-
*/
|
|
49
|
-
_getActiveService() {
|
|
50
|
-
if (this.mode === 'redis' && this.redisService && this.redisService.isConnected) {
|
|
51
|
-
return this.redisService;
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
// memory 模式下直接返回内存服务,或 redis 回退到内存
|
|
55
|
-
if (this.mode === 'memory' || this.fallbackToMemory) {
|
|
56
|
-
return this.memoryService;
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
return null;
|
|
19
|
+
Logger.info('✅ 内存缓存已启用');
|
|
60
20
|
}
|
|
61
21
|
|
|
62
22
|
/**
|
|
63
23
|
* 获取缓存值
|
|
64
24
|
*/
|
|
65
25
|
async get(key) {
|
|
66
|
-
const service = this._getActiveService();
|
|
67
|
-
if (!service) return null;
|
|
68
|
-
|
|
69
26
|
try {
|
|
70
|
-
|
|
71
|
-
return await service.get(key);
|
|
72
|
-
} else {
|
|
73
|
-
return service.get(key);
|
|
74
|
-
}
|
|
27
|
+
return this.memoryService.get(key);
|
|
75
28
|
} catch (error) {
|
|
76
29
|
Logger.error(`缓存获取失败 (${key}):`, { error: error.message });
|
|
77
30
|
return null;
|
|
@@ -82,16 +35,9 @@ export class UnifiedCacheAdapter {
|
|
|
82
35
|
* 设置缓存值
|
|
83
36
|
*/
|
|
84
37
|
async set(key, value, ttlSeconds = 300) {
|
|
85
|
-
const service = this._getActiveService();
|
|
86
|
-
if (!service) return false;
|
|
87
|
-
|
|
88
38
|
try {
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
} else {
|
|
92
|
-
service.set(key, value, ttlSeconds);
|
|
93
|
-
return true;
|
|
94
|
-
}
|
|
39
|
+
this.memoryService.set(key, value, ttlSeconds);
|
|
40
|
+
return true;
|
|
95
41
|
} catch (error) {
|
|
96
42
|
Logger.error(`缓存设置失败 (${key}):`, { error: error.message });
|
|
97
43
|
return false;
|
|
@@ -102,15 +48,8 @@ export class UnifiedCacheAdapter {
|
|
|
102
48
|
* 删除缓存
|
|
103
49
|
*/
|
|
104
50
|
async delete(key) {
|
|
105
|
-
const service = this._getActiveService();
|
|
106
|
-
if (!service) return false;
|
|
107
|
-
|
|
108
51
|
try {
|
|
109
|
-
|
|
110
|
-
return await service.delete(key);
|
|
111
|
-
} else {
|
|
112
|
-
return service.delete(key);
|
|
113
|
-
}
|
|
52
|
+
return this.memoryService.delete(key);
|
|
114
53
|
} catch (error) {
|
|
115
54
|
Logger.error(`缓存删除失败 (${key}):`, { error: error.message });
|
|
116
55
|
return false;
|
|
@@ -121,16 +60,9 @@ export class UnifiedCacheAdapter {
|
|
|
121
60
|
* 清空所有缓存
|
|
122
61
|
*/
|
|
123
62
|
async clear() {
|
|
124
|
-
const service = this._getActiveService();
|
|
125
|
-
if (!service) return false;
|
|
126
|
-
|
|
127
63
|
try {
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
} else {
|
|
131
|
-
service.clear();
|
|
132
|
-
return true;
|
|
133
|
-
}
|
|
64
|
+
this.memoryService.clear();
|
|
65
|
+
return true;
|
|
134
66
|
} catch (error) {
|
|
135
67
|
Logger.error('缓存清空失败:', { error: error.message });
|
|
136
68
|
return false;
|
|
@@ -141,34 +73,15 @@ export class UnifiedCacheAdapter {
|
|
|
141
73
|
* 获取统计信息
|
|
142
74
|
*/
|
|
143
75
|
getStats() {
|
|
144
|
-
const
|
|
145
|
-
|
|
146
|
-
return { mode: this.mode, available: false };
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
const stats = service.getStats();
|
|
150
|
-
return { mode: this.mode, available: true, ...stats };
|
|
76
|
+
const stats = this.memoryService.getStats();
|
|
77
|
+
return { mode: 'memory', available: true, ...stats };
|
|
151
78
|
}
|
|
152
79
|
|
|
153
80
|
/**
|
|
154
81
|
* 健康检查
|
|
155
82
|
*/
|
|
156
83
|
async healthCheck() {
|
|
157
|
-
|
|
158
|
-
if (!service) {
|
|
159
|
-
return { healthy: false, mode: this.mode, message: '缓存服务不可用' };
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
try {
|
|
163
|
-
if (this.mode === 'redis') {
|
|
164
|
-
const health = await service.healthCheck();
|
|
165
|
-
return { ...health, mode: 'redis' };
|
|
166
|
-
} else {
|
|
167
|
-
return { healthy: true, mode: 'memory', message: '内存缓存运行正常' };
|
|
168
|
-
}
|
|
169
|
-
} catch (error) {
|
|
170
|
-
return { healthy: false, mode: this.mode, message: error.message };
|
|
171
|
-
}
|
|
84
|
+
return { healthy: true, mode: 'memory', message: '内存缓存运行正常' };
|
|
172
85
|
}
|
|
173
86
|
}
|
|
174
87
|
|
|
@@ -178,13 +91,13 @@ let cacheAdapterInstance = null;
|
|
|
178
91
|
/**
|
|
179
92
|
* 初始化统一缓存适配器
|
|
180
93
|
*/
|
|
181
|
-
export async function initCacheAdapter(
|
|
94
|
+
export async function initCacheAdapter() {
|
|
182
95
|
if (cacheAdapterInstance) {
|
|
183
96
|
Logger.warn('缓存适配器已初始化');
|
|
184
97
|
return cacheAdapterInstance;
|
|
185
98
|
}
|
|
186
99
|
|
|
187
|
-
cacheAdapterInstance = new UnifiedCacheAdapter(
|
|
100
|
+
cacheAdapterInstance = new UnifiedCacheAdapter();
|
|
188
101
|
await cacheAdapterInstance.initialize();
|
|
189
102
|
return cacheAdapterInstance;
|
|
190
103
|
}
|
|
@@ -14,7 +14,7 @@ export const SPMMAP_PATH = `AutoSnippet/${SPMMAP_FILENAME}`;
|
|
|
14
14
|
|
|
15
15
|
// ─── Context 存储 ────────────────────────────────────────────
|
|
16
16
|
export const DEFAULT_STORAGE_ADAPTER = 'json';
|
|
17
|
-
export const STORAGE_ADAPTERS = ['json'
|
|
17
|
+
export const STORAGE_ADAPTERS = ['json'];
|
|
18
18
|
|
|
19
19
|
// ─── Context 索引 ────────────────────────────────────────────
|
|
20
20
|
export const SOURCE_TYPES = ['recipe', 'doc', 'target-readme'];
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "autosnippet",
|
|
3
|
-
"version": "2.19.
|
|
3
|
+
"version": "2.19.6",
|
|
4
4
|
"description": "AutoSnippet - 连接开发者、AI 与项目知识库的工具",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "lib/bootstrap.js",
|
|
@@ -55,7 +55,6 @@
|
|
|
55
55
|
"dependencies": {
|
|
56
56
|
"@google/generative-ai": "^0.21.0",
|
|
57
57
|
"@modelcontextprotocol/sdk": "^1.25.3",
|
|
58
|
-
"@zilliz/milvus2-sdk-node": "^2.5.10",
|
|
59
58
|
"axios": "^1.6.7",
|
|
60
59
|
"better-sqlite3": "^9.2.2",
|
|
61
60
|
"chalk": "^5.3.0",
|
|
@@ -70,7 +69,6 @@
|
|
|
70
69
|
"minimist": "^1.2.5",
|
|
71
70
|
"open": "^8.0.4",
|
|
72
71
|
"ora": "^8.0.1",
|
|
73
|
-
"redis": "^4.7.0",
|
|
74
72
|
"socket.io": "^4.8.1",
|
|
75
73
|
"tree-sitter": "^0.22.4",
|
|
76
74
|
"tree-sitter-objc": "^3.0.2",
|
|
@@ -78,7 +76,6 @@
|
|
|
78
76
|
"undici": "^6.23.0",
|
|
79
77
|
"uuid": "^9.0.1",
|
|
80
78
|
"winston": "^3.11.0",
|
|
81
|
-
"xml2js": "^0.6.2",
|
|
82
79
|
"zod": "^4.3.6"
|
|
83
80
|
},
|
|
84
81
|
"bugs": {
|
|
@@ -1,365 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Redis 缓存服务
|
|
3
|
-
* 提供 Redis 客户端封装,支持分布式缓存
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import { createClient } from 'redis';
|
|
7
|
-
import Logger from '../logging/Logger.js';
|
|
8
|
-
|
|
9
|
-
export class RedisService {
|
|
10
|
-
constructor(config = {}) {
|
|
11
|
-
this.config = {
|
|
12
|
-
host: config.host || process.env.REDIS_HOST || 'localhost',
|
|
13
|
-
port: parseInt(config.port || process.env.REDIS_PORT || 6379, 10),
|
|
14
|
-
password: config.password || process.env.REDIS_PASSWORD,
|
|
15
|
-
db: parseInt(config.db || process.env.REDIS_DB || 0, 10),
|
|
16
|
-
keyPrefix: config.keyPrefix || 'asd:',
|
|
17
|
-
retryStrategy: config.retryStrategy || this.defaultRetryStrategy.bind(this),
|
|
18
|
-
connectTimeout: config.connectTimeout || 10000,
|
|
19
|
-
};
|
|
20
|
-
|
|
21
|
-
this.client = null;
|
|
22
|
-
this.isConnected = false;
|
|
23
|
-
this.stats = {
|
|
24
|
-
hits: 0,
|
|
25
|
-
misses: 0,
|
|
26
|
-
sets: 0,
|
|
27
|
-
deletes: 0,
|
|
28
|
-
errors: 0,
|
|
29
|
-
};
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
/**
|
|
33
|
-
* 默认重试策略
|
|
34
|
-
*/
|
|
35
|
-
defaultRetryStrategy(retries) {
|
|
36
|
-
if (retries > 10) {
|
|
37
|
-
Logger.error('Redis 重连失败次数过多,停止重试');
|
|
38
|
-
return new Error('Redis 重连失败');
|
|
39
|
-
}
|
|
40
|
-
const delay = Math.min(retries * 100, 3000);
|
|
41
|
-
Logger.info(`Redis 重连中... (第 ${retries} 次,延迟 ${delay}ms)`);
|
|
42
|
-
return delay;
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
/**
|
|
46
|
-
* 连接到 Redis
|
|
47
|
-
*/
|
|
48
|
-
async connect() {
|
|
49
|
-
if (this.isConnected) {
|
|
50
|
-
return;
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
try {
|
|
54
|
-
this.client = createClient({
|
|
55
|
-
socket: {
|
|
56
|
-
host: this.config.host,
|
|
57
|
-
port: this.config.port,
|
|
58
|
-
connectTimeout: this.config.connectTimeout,
|
|
59
|
-
reconnectStrategy: (retries) => this.config.retryStrategy(retries),
|
|
60
|
-
},
|
|
61
|
-
password: this.config.password || undefined,
|
|
62
|
-
database: this.config.db,
|
|
63
|
-
});
|
|
64
|
-
|
|
65
|
-
this.client.on('error', (err) => {
|
|
66
|
-
Logger.error('Redis 客户端错误:', { error: err.message });
|
|
67
|
-
this.stats.errors++;
|
|
68
|
-
this.isConnected = false;
|
|
69
|
-
});
|
|
70
|
-
|
|
71
|
-
this.client.on('connect', () => {
|
|
72
|
-
Logger.info(`Redis 连接成功: ${this.config.host}:${this.config.port}`);
|
|
73
|
-
});
|
|
74
|
-
|
|
75
|
-
this.client.on('ready', () => {
|
|
76
|
-
Logger.info('Redis 客户端准备就绪');
|
|
77
|
-
this.isConnected = true;
|
|
78
|
-
});
|
|
79
|
-
|
|
80
|
-
this.client.on('reconnecting', () => {
|
|
81
|
-
Logger.warn('Redis 重新连接中...');
|
|
82
|
-
this.isConnected = false;
|
|
83
|
-
});
|
|
84
|
-
|
|
85
|
-
this.client.on('end', () => {
|
|
86
|
-
Logger.warn('Redis 连接已关闭');
|
|
87
|
-
this.isConnected = false;
|
|
88
|
-
});
|
|
89
|
-
|
|
90
|
-
await this.client.connect();
|
|
91
|
-
Logger.info('✅ Redis 服务已启动');
|
|
92
|
-
} catch (error) {
|
|
93
|
-
Logger.error('Redis 连接失败:', { error: error.message });
|
|
94
|
-
this.stats.errors++;
|
|
95
|
-
throw error;
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
/**
|
|
100
|
-
* 生成完整键名
|
|
101
|
-
*/
|
|
102
|
-
_getKey(key) {
|
|
103
|
-
return `${this.config.keyPrefix}${key}`;
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
/**
|
|
107
|
-
* 获取缓存值
|
|
108
|
-
*/
|
|
109
|
-
async get(key) {
|
|
110
|
-
if (!this.isConnected) {
|
|
111
|
-
this.stats.misses++;
|
|
112
|
-
return null;
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
try {
|
|
116
|
-
const fullKey = this._getKey(key);
|
|
117
|
-
const value = await this.client.get(fullKey);
|
|
118
|
-
|
|
119
|
-
if (value === null) {
|
|
120
|
-
this.stats.misses++;
|
|
121
|
-
return null;
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
this.stats.hits++;
|
|
125
|
-
return JSON.parse(value);
|
|
126
|
-
} catch (error) {
|
|
127
|
-
Logger.error(`Redis GET 失败 (${key}):`, { error: error.message });
|
|
128
|
-
this.stats.errors++;
|
|
129
|
-
this.stats.misses++;
|
|
130
|
-
return null;
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
/**
|
|
135
|
-
* 设置缓存值
|
|
136
|
-
*/
|
|
137
|
-
async set(key, value, ttlSeconds = 300) {
|
|
138
|
-
if (!this.isConnected) {
|
|
139
|
-
return false;
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
try {
|
|
143
|
-
const fullKey = this._getKey(key);
|
|
144
|
-
const serialized = JSON.stringify(value);
|
|
145
|
-
|
|
146
|
-
if (ttlSeconds > 0) {
|
|
147
|
-
await this.client.setEx(fullKey, ttlSeconds, serialized);
|
|
148
|
-
} else {
|
|
149
|
-
await this.client.set(fullKey, serialized);
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
this.stats.sets++;
|
|
153
|
-
return true;
|
|
154
|
-
} catch (error) {
|
|
155
|
-
Logger.error(`Redis SET 失败 (${key}):`, { error: error.message });
|
|
156
|
-
this.stats.errors++;
|
|
157
|
-
return false;
|
|
158
|
-
}
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
/**
|
|
162
|
-
* 删除缓存
|
|
163
|
-
*/
|
|
164
|
-
async delete(key) {
|
|
165
|
-
if (!this.isConnected) {
|
|
166
|
-
return false;
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
try {
|
|
170
|
-
const fullKey = this._getKey(key);
|
|
171
|
-
await this.client.del(fullKey);
|
|
172
|
-
this.stats.deletes++;
|
|
173
|
-
return true;
|
|
174
|
-
} catch (error) {
|
|
175
|
-
Logger.error(`Redis DEL 失败 (${key}):`, { error: error.message });
|
|
176
|
-
this.stats.errors++;
|
|
177
|
-
return false;
|
|
178
|
-
}
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
/**
|
|
182
|
-
* 批量删除(通配符,使用 SCAN)
|
|
183
|
-
*/
|
|
184
|
-
async deletePattern(pattern) {
|
|
185
|
-
if (!this.isConnected) {
|
|
186
|
-
return 0;
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
try {
|
|
190
|
-
const fullPattern = this._getKey(pattern);
|
|
191
|
-
const keys = [];
|
|
192
|
-
|
|
193
|
-
for await (const key of this.client.scanIterator({
|
|
194
|
-
MATCH: fullPattern,
|
|
195
|
-
COUNT: 100,
|
|
196
|
-
})) {
|
|
197
|
-
keys.push(key);
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
if (keys.length > 0) {
|
|
201
|
-
await this.client.del(keys);
|
|
202
|
-
this.stats.deletes += keys.length;
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
return keys.length;
|
|
206
|
-
} catch (error) {
|
|
207
|
-
Logger.error(`Redis 批量删除失败 (${pattern}):`, { error: error.message });
|
|
208
|
-
this.stats.errors++;
|
|
209
|
-
return 0;
|
|
210
|
-
}
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
/**
|
|
214
|
-
* 清空所有缓存
|
|
215
|
-
*/
|
|
216
|
-
async clear() {
|
|
217
|
-
if (!this.isConnected) {
|
|
218
|
-
return false;
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
try {
|
|
222
|
-
const deletedCount = await this.deletePattern('*');
|
|
223
|
-
Logger.info(`Redis 缓存已清空 (${deletedCount} 个键)`);
|
|
224
|
-
return true;
|
|
225
|
-
} catch (error) {
|
|
226
|
-
Logger.error('Redis 清空缓存失败:', { error: error.message });
|
|
227
|
-
this.stats.errors++;
|
|
228
|
-
return false;
|
|
229
|
-
}
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
/**
|
|
233
|
-
* 检查键是否存在
|
|
234
|
-
*/
|
|
235
|
-
async exists(key) {
|
|
236
|
-
if (!this.isConnected) {
|
|
237
|
-
return false;
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
try {
|
|
241
|
-
const fullKey = this._getKey(key);
|
|
242
|
-
const result = await this.client.exists(fullKey);
|
|
243
|
-
return result === 1;
|
|
244
|
-
} catch (error) {
|
|
245
|
-
Logger.error(`Redis EXISTS 失败 (${key}):`, { error: error.message });
|
|
246
|
-
this.stats.errors++;
|
|
247
|
-
return false;
|
|
248
|
-
}
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
/**
|
|
252
|
-
* 获取剩余 TTL
|
|
253
|
-
*/
|
|
254
|
-
async ttl(key) {
|
|
255
|
-
if (!this.isConnected) {
|
|
256
|
-
return -1;
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
-
try {
|
|
260
|
-
const fullKey = this._getKey(key);
|
|
261
|
-
return await this.client.ttl(fullKey);
|
|
262
|
-
} catch (error) {
|
|
263
|
-
Logger.error(`Redis TTL 失败 (${key}):`, { error: error.message });
|
|
264
|
-
this.stats.errors++;
|
|
265
|
-
return -1;
|
|
266
|
-
}
|
|
267
|
-
}
|
|
268
|
-
|
|
269
|
-
/**
|
|
270
|
-
* 获取统计信息
|
|
271
|
-
*/
|
|
272
|
-
getStats() {
|
|
273
|
-
const totalRequests = this.stats.hits + this.stats.misses;
|
|
274
|
-
const hitRate = totalRequests > 0 ? (this.stats.hits / totalRequests) * 100 : 0;
|
|
275
|
-
|
|
276
|
-
return {
|
|
277
|
-
...this.stats,
|
|
278
|
-
totalRequests,
|
|
279
|
-
hitRate: hitRate.toFixed(2) + '%',
|
|
280
|
-
isConnected: this.isConnected,
|
|
281
|
-
config: {
|
|
282
|
-
host: this.config.host,
|
|
283
|
-
port: this.config.port,
|
|
284
|
-
db: this.config.db,
|
|
285
|
-
keyPrefix: this.config.keyPrefix,
|
|
286
|
-
},
|
|
287
|
-
};
|
|
288
|
-
}
|
|
289
|
-
|
|
290
|
-
/**
|
|
291
|
-
* 重置统计信息
|
|
292
|
-
*/
|
|
293
|
-
resetStats() {
|
|
294
|
-
this.stats = { hits: 0, misses: 0, sets: 0, deletes: 0, errors: 0 };
|
|
295
|
-
}
|
|
296
|
-
|
|
297
|
-
/**
|
|
298
|
-
* 健康检查
|
|
299
|
-
*/
|
|
300
|
-
async healthCheck() {
|
|
301
|
-
if (!this.isConnected) {
|
|
302
|
-
return { healthy: false, message: 'Redis 未连接' };
|
|
303
|
-
}
|
|
304
|
-
|
|
305
|
-
try {
|
|
306
|
-
const testKey = this._getKey('health:check');
|
|
307
|
-
await this.client.set(testKey, 'OK');
|
|
308
|
-
const value = await this.client.get(testKey);
|
|
309
|
-
await this.client.del(testKey);
|
|
310
|
-
|
|
311
|
-
return {
|
|
312
|
-
healthy: value === 'OK',
|
|
313
|
-
message: 'Redis 健康检查通过',
|
|
314
|
-
};
|
|
315
|
-
} catch (error) {
|
|
316
|
-
Logger.error('Redis 健康检查失败:', { error: error.message });
|
|
317
|
-
return { healthy: false, message: error.message };
|
|
318
|
-
}
|
|
319
|
-
}
|
|
320
|
-
|
|
321
|
-
/**
|
|
322
|
-
* 断开连接
|
|
323
|
-
*/
|
|
324
|
-
async disconnect() {
|
|
325
|
-
if (this.client && this.isConnected) {
|
|
326
|
-
try {
|
|
327
|
-
await this.client.quit();
|
|
328
|
-
Logger.info('Redis 连接已关闭');
|
|
329
|
-
} catch (error) {
|
|
330
|
-
Logger.error('Redis 断开连接失败:', { error: error.message });
|
|
331
|
-
await this.client.disconnect();
|
|
332
|
-
}
|
|
333
|
-
}
|
|
334
|
-
this.isConnected = false;
|
|
335
|
-
}
|
|
336
|
-
}
|
|
337
|
-
|
|
338
|
-
// 单例实例
|
|
339
|
-
let redisServiceInstance = null;
|
|
340
|
-
|
|
341
|
-
/**
|
|
342
|
-
* 初始化 Redis 服务
|
|
343
|
-
*/
|
|
344
|
-
export async function initRedisService(config) {
|
|
345
|
-
if (redisServiceInstance) {
|
|
346
|
-
Logger.warn('Redis 服务已初始化');
|
|
347
|
-
return redisServiceInstance;
|
|
348
|
-
}
|
|
349
|
-
|
|
350
|
-
redisServiceInstance = new RedisService(config);
|
|
351
|
-
await redisServiceInstance.connect();
|
|
352
|
-
return redisServiceInstance;
|
|
353
|
-
}
|
|
354
|
-
|
|
355
|
-
/**
|
|
356
|
-
* 获取 Redis 服务实例
|
|
357
|
-
*/
|
|
358
|
-
export function getRedisService() {
|
|
359
|
-
if (!redisServiceInstance) {
|
|
360
|
-
throw new Error('Redis 服务未初始化,请先调用 initRedisService()');
|
|
361
|
-
}
|
|
362
|
-
return redisServiceInstance;
|
|
363
|
-
}
|
|
364
|
-
|
|
365
|
-
export default RedisService;
|