openmatrix 0.1.12 → 0.1.13

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.
@@ -0,0 +1,1184 @@
1
+ "use strict";
2
+ // src/orchestrator/upgrade-detector.ts
3
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
4
+ if (k2 === undefined) k2 = k;
5
+ var desc = Object.getOwnPropertyDescriptor(m, k);
6
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
7
+ desc = { enumerable: true, get: function() { return m[k]; } };
8
+ }
9
+ Object.defineProperty(o, k2, desc);
10
+ }) : (function(o, m, k, k2) {
11
+ if (k2 === undefined) k2 = k;
12
+ o[k2] = m[k];
13
+ }));
14
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
15
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
16
+ }) : function(o, v) {
17
+ o["default"] = v;
18
+ });
19
+ var __importStar = (this && this.__importStar) || (function () {
20
+ var ownKeys = function(o) {
21
+ ownKeys = Object.getOwnPropertyNames || function (o) {
22
+ var ar = [];
23
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
24
+ return ar;
25
+ };
26
+ return ownKeys(o);
27
+ };
28
+ return function (mod) {
29
+ if (mod && mod.__esModule) return mod;
30
+ var result = {};
31
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
32
+ __setModuleDefault(result, mod);
33
+ return result;
34
+ };
35
+ })();
36
+ Object.defineProperty(exports, "__esModule", { value: true });
37
+ exports.UpgradeDetector = exports.DEFAULT_DETECTOR_CONFIG = void 0;
38
+ const fs = __importStar(require("fs/promises"));
39
+ const path = __importStar(require("path"));
40
+ /**
41
+ * 默认配置
42
+ */
43
+ exports.DEFAULT_DETECTOR_CONFIG = {
44
+ scanDirs: ['src', 'skills', 'tests', 'docs', 'prompts', '.claude', '.cursor'],
45
+ excludeDirs: ['node_modules', 'dist', '.git', '.openmatrix'],
46
+ categories: ['bug', 'quality', 'capability', 'ux', 'style', 'security', 'common', 'prompt', 'skill', 'agent'],
47
+ minPriority: 'low'
48
+ };
49
+ /**
50
+ * 升级检测器
51
+ *
52
+ * 自动扫描项目代码,检测可改进点,支持多维度分析。
53
+ */
54
+ class UpgradeDetector {
55
+ config;
56
+ projectRoot;
57
+ suggestionIdCounter = 0;
58
+ constructor(projectRoot, config = {}) {
59
+ this.projectRoot = projectRoot;
60
+ this.config = { ...exports.DEFAULT_DETECTOR_CONFIG, ...config };
61
+ }
62
+ /**
63
+ * 执行完整检测
64
+ */
65
+ async detect() {
66
+ const projectType = await this.detectProjectType();
67
+ const projectName = await this.getProjectName();
68
+ const suggestions = [];
69
+ // 并行执行所有检测器
70
+ const detectors = [
71
+ this.detectBugs(),
72
+ this.detectQualityIssues(),
73
+ this.detectMissingCapabilities(),
74
+ this.detectUXIssues(),
75
+ this.detectStyleIssues(),
76
+ this.detectSecurityIssues(),
77
+ this.detectCommonIssues(),
78
+ // AI 项目专用检测器
79
+ this.detectPromptIssues(),
80
+ this.detectSkillIssues(),
81
+ this.detectAgentConfigIssues()
82
+ ];
83
+ const results = await Promise.all(detectors);
84
+ for (const result of results) {
85
+ suggestions.push(...result);
86
+ }
87
+ // 根据用户提示过滤/排序
88
+ let filtered = this.applyUserHint(suggestions);
89
+ // 按优先级排序
90
+ filtered = this.sortByPriority(filtered);
91
+ // 限制数量
92
+ if (this.config.maxSuggestions) {
93
+ filtered = filtered.slice(0, this.config.maxSuggestions);
94
+ }
95
+ return {
96
+ projectType,
97
+ projectName,
98
+ scanPath: this.projectRoot,
99
+ timestamp: new Date().toISOString(),
100
+ suggestions: filtered,
101
+ summary: this.generateSummary(filtered)
102
+ };
103
+ }
104
+ /**
105
+ * 检测项目类型
106
+ */
107
+ async detectProjectType() {
108
+ try {
109
+ // 检查是否是 OpenMatrix 项目
110
+ const omPath = path.join(this.projectRoot, '.openmatrix');
111
+ const packageJsonPath = path.join(this.projectRoot, 'package.json');
112
+ try {
113
+ await fs.access(omPath);
114
+ // 检查是否有 openmatrix 特征
115
+ const packageJson = JSON.parse(await fs.readFile(packageJsonPath, 'utf-8'));
116
+ if (packageJson.name === 'openmatrix' ||
117
+ packageJson.description?.includes('OpenMatrix')) {
118
+ return 'openmatrix';
119
+ }
120
+ }
121
+ catch {
122
+ // 不是 OpenMatrix 项目
123
+ }
124
+ // 检查是否是 AI 项目
125
+ const aiIndicators = [
126
+ '.claude',
127
+ '.cursor',
128
+ 'skills',
129
+ 'prompts',
130
+ '.cursorrules',
131
+ 'CLAUDE.md',
132
+ 'AGENTS.md',
133
+ 'GEMINI.md',
134
+ '.mcp'
135
+ ];
136
+ let aiIndicatorCount = 0;
137
+ for (const indicator of aiIndicators) {
138
+ try {
139
+ await fs.access(path.join(this.projectRoot, indicator));
140
+ aiIndicatorCount++;
141
+ }
142
+ catch {
143
+ // 不存在
144
+ }
145
+ }
146
+ // 如果有 2 个或以上 AI 指标,认为是 AI 项目
147
+ if (aiIndicatorCount >= 2) {
148
+ return 'ai-project';
149
+ }
150
+ // 检查 package.json
151
+ try {
152
+ const packageJson = JSON.parse(await fs.readFile(packageJsonPath, 'utf-8'));
153
+ // 检查是否有 AI 相关依赖
154
+ const aiDeps = [
155
+ 'anthropic',
156
+ '@anthropic-ai/sdk',
157
+ 'openai',
158
+ '@langchain',
159
+ 'llamaindex',
160
+ 'claude-agent-sdk'
161
+ ];
162
+ const allDeps = {
163
+ ...packageJson.dependencies,
164
+ ...packageJson.devDependencies
165
+ };
166
+ for (const dep of aiDeps) {
167
+ if (allDeps[dep]) {
168
+ return 'ai-project';
169
+ }
170
+ }
171
+ // 检查 TypeScript
172
+ if (packageJson.devDependencies?.typescript ||
173
+ packageJson.dependencies?.typescript) {
174
+ return 'typescript';
175
+ }
176
+ return 'nodejs';
177
+ }
178
+ catch {
179
+ // 没有 package.json
180
+ }
181
+ // 检查 Python
182
+ try {
183
+ await fs.access(path.join(this.projectRoot, 'pyproject.toml'));
184
+ return 'python';
185
+ }
186
+ catch {
187
+ // 不是 Python
188
+ }
189
+ try {
190
+ await fs.access(path.join(this.projectRoot, 'requirements.txt'));
191
+ return 'python';
192
+ }
193
+ catch {
194
+ // 不是 Python
195
+ }
196
+ // 检查 Go
197
+ try {
198
+ await fs.access(path.join(this.projectRoot, 'go.mod'));
199
+ return 'go';
200
+ }
201
+ catch {
202
+ // 不是 Go
203
+ }
204
+ // 检查 Rust
205
+ try {
206
+ await fs.access(path.join(this.projectRoot, 'Cargo.toml'));
207
+ return 'rust';
208
+ }
209
+ catch {
210
+ // 不是 Rust
211
+ }
212
+ // 检查 Java
213
+ try {
214
+ await fs.access(path.join(this.projectRoot, 'pom.xml'));
215
+ return 'java';
216
+ }
217
+ catch {
218
+ // 不是 Java (Maven)
219
+ }
220
+ try {
221
+ await fs.access(path.join(this.projectRoot, 'build.gradle'));
222
+ return 'java';
223
+ }
224
+ catch {
225
+ // 不是 Java (Gradle)
226
+ }
227
+ try {
228
+ await fs.access(path.join(this.projectRoot, 'build.gradle.kts'));
229
+ return 'java';
230
+ }
231
+ catch {
232
+ // 不是 Java (Gradle Kotlin DSL)
233
+ }
234
+ // 检查 C#
235
+ try {
236
+ const files = await fs.readdir(this.projectRoot);
237
+ if (files.some(f => f.endsWith('.sln') || f.endsWith('.csproj'))) {
238
+ return 'csharp';
239
+ }
240
+ }
241
+ catch {
242
+ // 不是 C#
243
+ }
244
+ // 检查 C/C++
245
+ try {
246
+ await fs.access(path.join(this.projectRoot, 'CMakeLists.txt'));
247
+ return 'cpp';
248
+ }
249
+ catch {
250
+ // 不是 CMake 项目
251
+ }
252
+ try {
253
+ await fs.access(path.join(this.projectRoot, 'Makefile'));
254
+ return 'cpp';
255
+ }
256
+ catch {
257
+ // 不是 Make 项目
258
+ }
259
+ // 检查 PHP
260
+ try {
261
+ await fs.access(path.join(this.projectRoot, 'composer.json'));
262
+ return 'php';
263
+ }
264
+ catch {
265
+ // 不是 PHP
266
+ }
267
+ // 检查 Dart
268
+ try {
269
+ await fs.access(path.join(this.projectRoot, 'pubspec.yaml'));
270
+ return 'dart';
271
+ }
272
+ catch {
273
+ // 不是 Dart
274
+ }
275
+ return 'unknown';
276
+ }
277
+ catch {
278
+ return 'unknown';
279
+ }
280
+ }
281
+ /**
282
+ * 获取项目名称
283
+ */
284
+ async getProjectName() {
285
+ try {
286
+ const packageJsonPath = path.join(this.projectRoot, 'package.json');
287
+ const packageJson = JSON.parse(await fs.readFile(packageJsonPath, 'utf-8'));
288
+ return packageJson.name || path.basename(this.projectRoot);
289
+ }
290
+ catch {
291
+ return path.basename(this.projectRoot);
292
+ }
293
+ }
294
+ /**
295
+ * 检测代码缺陷
296
+ */
297
+ async detectBugs() {
298
+ const suggestions = [];
299
+ const files = await this.scanFiles(['.ts', '.js', '.tsx', '.jsx']);
300
+ for (const file of files) {
301
+ const content = await fs.readFile(file, 'utf-8');
302
+ const lines = content.split('\n');
303
+ lines.forEach((line, index) => {
304
+ // TODO 检测
305
+ const todoMatch = line.match(/\/\/\s*TODO(?:\(([^)]+)\))?:?\s*(.+)/i);
306
+ if (todoMatch) {
307
+ suggestions.push(this.createSuggestion({
308
+ category: 'bug',
309
+ priority: todoMatch[1] === 'critical' ? 'critical' : 'medium',
310
+ title: `待完成: ${todoMatch[2]}`,
311
+ description: `发现 TODO 标记: ${todoMatch[2]}`,
312
+ location: { file: this.getRelativePath(file), line: index + 1 },
313
+ suggestion: `完成或移除 TODO: ${todoMatch[2]}`,
314
+ autoFixable: false,
315
+ impact: '可能影响功能完整性',
316
+ effort: 'small'
317
+ }));
318
+ }
319
+ // FIXME 检测
320
+ const fixmeMatch = line.match(/\/\/\s*FIXME(?:\(([^)]+)\))?:?\s*(.+)/i);
321
+ if (fixmeMatch) {
322
+ suggestions.push(this.createSuggestion({
323
+ category: 'bug',
324
+ priority: 'high',
325
+ title: `需修复: ${fixmeMatch[2]}`,
326
+ description: `发现 FIXME 标记: ${fixmeMatch[2]}`,
327
+ location: { file: this.getRelativePath(file), line: index + 1 },
328
+ suggestion: `修复已知问题: ${fixmeMatch[2]}`,
329
+ autoFixable: false,
330
+ impact: '可能导致运行时错误',
331
+ effort: 'medium'
332
+ }));
333
+ }
334
+ // HACK 检测
335
+ const hackMatch = line.match(/\/\/\s*HACK:?\s*(.+)/i);
336
+ if (hackMatch) {
337
+ suggestions.push(this.createSuggestion({
338
+ category: 'bug',
339
+ priority: 'medium',
340
+ title: `临时方案: ${hackMatch[1]}`,
341
+ description: `发现 HACK 标记,表示使用了临时解决方案`,
342
+ location: { file: this.getRelativePath(file), line: index + 1 },
343
+ suggestion: `替换为正式实现`,
344
+ autoFixable: false,
345
+ impact: '技术债务累积',
346
+ effort: 'medium'
347
+ }));
348
+ }
349
+ // any 类型检测 (TypeScript)
350
+ if (file.endsWith('.ts') || file.endsWith('.tsx')) {
351
+ const anyMatch = line.match(/:\s*any\b/);
352
+ if (anyMatch && !line.includes('// eslint-disable')) {
353
+ suggestions.push(this.createSuggestion({
354
+ category: 'quality',
355
+ priority: 'low',
356
+ title: '使用 any 类型',
357
+ description: '使用 any 类型会丢失类型安全性',
358
+ location: { file: this.getRelativePath(file), line: index + 1 },
359
+ suggestion: '替换为具体类型或使用 unknown',
360
+ autoFixable: false,
361
+ impact: '降低类型安全性',
362
+ effort: 'trivial'
363
+ }));
364
+ }
365
+ }
366
+ });
367
+ }
368
+ return suggestions;
369
+ }
370
+ /**
371
+ * 检测代码质量问题
372
+ */
373
+ async detectQualityIssues() {
374
+ const suggestions = [];
375
+ const files = await this.scanFiles(['.ts', '.js', '.tsx', '.jsx']);
376
+ for (const file of files) {
377
+ const content = await fs.readFile(file, 'utf-8');
378
+ const lines = content.split('\n');
379
+ lines.forEach((line, index) => {
380
+ // 过长函数检测 (简化版)
381
+ if (line.trim().startsWith('function ') ||
382
+ line.trim().startsWith('const ') && line.includes('=>')) {
383
+ // 检查后续行数
384
+ let depth = 0;
385
+ let lineCount = 0;
386
+ for (let i = index; i < lines.length && lineCount < 100; i++) {
387
+ const currentLine = lines[i];
388
+ if (currentLine.includes('{'))
389
+ depth++;
390
+ if (currentLine.includes('}'))
391
+ depth--;
392
+ lineCount++;
393
+ if (depth === 0 && i > index)
394
+ break;
395
+ }
396
+ if (lineCount > 50) {
397
+ suggestions.push(this.createSuggestion({
398
+ category: 'quality',
399
+ priority: 'medium',
400
+ title: '函数过长',
401
+ description: `函数超过 ${lineCount} 行,建议拆分`,
402
+ location: { file: this.getRelativePath(file), line: index + 1 },
403
+ suggestion: '将函数拆分为更小的函数',
404
+ autoFixable: false,
405
+ impact: '降低可读性和可维护性',
406
+ effort: 'medium'
407
+ }));
408
+ }
409
+ }
410
+ // console.log 检测 (生产代码)
411
+ if (line.includes('console.log') && !file.includes('test') && !file.includes('spec')) {
412
+ suggestions.push(this.createSuggestion({
413
+ category: 'quality',
414
+ priority: 'low',
415
+ title: '调试日志',
416
+ description: '生产代码中存在 console.log',
417
+ location: { file: this.getRelativePath(file), line: index + 1 },
418
+ suggestion: '移除或替换为正式日志系统',
419
+ autoFixable: true,
420
+ impact: '可能泄露敏感信息',
421
+ effort: 'trivial'
422
+ }));
423
+ }
424
+ // 空 catch 块
425
+ if (line.trim() === 'catch' || line.includes('catch (')) {
426
+ const nextLine = lines[index + 1]?.trim();
427
+ if (nextLine === '{}' || nextLine === '') {
428
+ suggestions.push(this.createSuggestion({
429
+ category: 'quality',
430
+ priority: 'high',
431
+ title: '空 catch 块',
432
+ description: 'catch 块为空,可能隐藏错误',
433
+ location: { file: this.getRelativePath(file), line: index + 1 },
434
+ suggestion: '添加错误处理或至少记录日志',
435
+ autoFixable: false,
436
+ impact: '可能隐藏运行时错误',
437
+ effort: 'small'
438
+ }));
439
+ }
440
+ }
441
+ });
442
+ }
443
+ return suggestions;
444
+ }
445
+ /**
446
+ * 检测缺失能力
447
+ */
448
+ async detectMissingCapabilities() {
449
+ const suggestions = [];
450
+ // 检查测试覆盖率
451
+ const testDir = path.join(this.projectRoot, 'tests');
452
+ const srcDir = path.join(this.projectRoot, 'src');
453
+ try {
454
+ await fs.access(testDir);
455
+ }
456
+ catch {
457
+ // 没有测试目录
458
+ try {
459
+ await fs.access(srcDir);
460
+ suggestions.push(this.createSuggestion({
461
+ category: 'capability',
462
+ priority: 'high',
463
+ title: '缺少测试目录',
464
+ description: '项目没有测试目录,建议添加测试',
465
+ location: { file: this.getRelativePath(this.projectRoot) },
466
+ suggestion: '创建 tests/ 目录并添加单元测试',
467
+ autoFixable: false,
468
+ impact: '无法验证代码正确性',
469
+ effort: 'large'
470
+ }));
471
+ }
472
+ catch {
473
+ // 没有 src 目录,跳过
474
+ }
475
+ }
476
+ // 检查 README
477
+ try {
478
+ await fs.access(path.join(this.projectRoot, 'README.md'));
479
+ }
480
+ catch {
481
+ suggestions.push(this.createSuggestion({
482
+ category: 'capability',
483
+ priority: 'medium',
484
+ title: '缺少 README',
485
+ description: '项目没有 README 文档',
486
+ location: { file: this.getRelativePath(this.projectRoot) },
487
+ suggestion: '添加 README.md 说明项目用途和使用方法',
488
+ autoFixable: false,
489
+ impact: '降低项目可发现性',
490
+ effort: 'small'
491
+ }));
492
+ }
493
+ // 检查 CLAUDE.md (对于 OpenMatrix 相关项目)
494
+ try {
495
+ await fs.access(path.join(this.projectRoot, '.openmatrix'));
496
+ try {
497
+ await fs.access(path.join(this.projectRoot, 'CLAUDE.md'));
498
+ }
499
+ catch {
500
+ suggestions.push(this.createSuggestion({
501
+ category: 'capability',
502
+ priority: 'medium',
503
+ title: '缺少 CLAUDE.md',
504
+ description: 'OpenMatrix 项目建议添加 CLAUDE.md 提供项目指引',
505
+ location: { file: this.getRelativePath(this.projectRoot) },
506
+ suggestion: '添加 CLAUDE.md 说明项目结构和开发规范',
507
+ autoFixable: false,
508
+ impact: '降低 AI 助手协作效率',
509
+ effort: 'small'
510
+ }));
511
+ }
512
+ }
513
+ catch {
514
+ // 不是 OpenMatrix 项目
515
+ }
516
+ return suggestions;
517
+ }
518
+ /**
519
+ * 检测用户体验问题
520
+ */
521
+ async detectUXIssues() {
522
+ const suggestions = [];
523
+ const files = await this.scanFiles(['.ts', '.js', '.tsx', '.jsx']);
524
+ for (const file of files) {
525
+ // 只检查 CLI 相关文件
526
+ if (!file.includes('cli') && !file.includes('command'))
527
+ continue;
528
+ const content = await fs.readFile(file, 'utf-8');
529
+ const lines = content.split('\n');
530
+ lines.forEach((line, index) => {
531
+ // 检查错误消息是否友好
532
+ if (line.includes('throw new Error') || line.includes('console.error')) {
533
+ const errorMatch = line.match(/Error\(['"`]([^'"`]+)['"`]\)/);
534
+ if (errorMatch && errorMatch[1].length < 10) {
535
+ suggestions.push(this.createSuggestion({
536
+ category: 'ux',
537
+ priority: 'medium',
538
+ title: '错误消息过于简短',
539
+ description: `错误消息 "${errorMatch[1]}" 过于简短,不利于用户理解`,
540
+ location: { file: this.getRelativePath(file), line: index + 1 },
541
+ suggestion: '提供更详细的错误消息,包含解决建议',
542
+ autoFixable: false,
543
+ impact: '降低用户体验',
544
+ effort: 'trivial'
545
+ }));
546
+ }
547
+ }
548
+ // 检查是否缺少帮助信息
549
+ if (file.includes('command') && line.includes('.description(')) {
550
+ const nextLines = lines.slice(index, index + 10).join('\n');
551
+ if (!nextLines.includes('.option(') && !nextLines.includes('--help')) {
552
+ // 命令可能缺少选项说明
553
+ }
554
+ }
555
+ });
556
+ }
557
+ return suggestions;
558
+ }
559
+ /**
560
+ * 检测代码风格问题
561
+ */
562
+ async detectStyleIssues() {
563
+ const suggestions = [];
564
+ const files = await this.scanFiles(['.ts', '.js', '.tsx', '.jsx']);
565
+ for (const file of files) {
566
+ const content = await fs.readFile(file, 'utf-8');
567
+ const lines = content.split('\n');
568
+ lines.forEach((line, index) => {
569
+ // 检查行长度
570
+ if (line.length > 120) {
571
+ suggestions.push(this.createSuggestion({
572
+ category: 'style',
573
+ priority: 'low',
574
+ title: '行过长',
575
+ description: `行长度 ${line.length} 超过 120 字符`,
576
+ location: { file: this.getRelativePath(file), line: index + 1 },
577
+ suggestion: '将长行拆分为多行',
578
+ autoFixable: true,
579
+ impact: '降低代码可读性',
580
+ effort: 'trivial'
581
+ }));
582
+ }
583
+ // 检查命名规范 (简化版)
584
+ const varMatch = line.match(/(?:const|let|var)\s+([a-zA-Z_][a-zA-Z0-9_]*)/);
585
+ if (varMatch) {
586
+ const varName = varMatch[1];
587
+ // 检查是否使用了不推荐的命名
588
+ if (varName.length === 1 && !['i', 'j', 'k', 'x', 'y', 'z'].includes(varName)) {
589
+ suggestions.push(this.createSuggestion({
590
+ category: 'style',
591
+ priority: 'low',
592
+ title: '变量名过短',
593
+ description: `变量名 "${varName}" 过于简短,建议使用更有意义的名称`,
594
+ location: { file: this.getRelativePath(file), line: index + 1 },
595
+ suggestion: '使用描述性的变量名',
596
+ autoFixable: false,
597
+ impact: '降低代码可读性',
598
+ effort: 'trivial'
599
+ }));
600
+ }
601
+ }
602
+ });
603
+ }
604
+ return suggestions;
605
+ }
606
+ /**
607
+ * 检测安全问题
608
+ */
609
+ async detectSecurityIssues() {
610
+ const suggestions = [];
611
+ const files = await this.scanFiles(['.ts', '.js', '.tsx', '.jsx']);
612
+ for (const file of files) {
613
+ const content = await fs.readFile(file, 'utf-8');
614
+ const lines = content.split('\n');
615
+ lines.forEach((line, index) => {
616
+ // 检查硬编码密钥
617
+ const keyPatterns = [
618
+ /api[_-]?key\s*[:=]\s*['"][^'" ]+['"]/i,
619
+ /secret[_-]?key\s*[:=]\s*['"][^'" ]+['"]/i,
620
+ /password\s*[:=]\s*['"][^'" ]+['"]/i,
621
+ /token\s*[:=]\s*['"][^'" ]+['"]/i
622
+ ];
623
+ for (const pattern of keyPatterns) {
624
+ if (pattern.test(line) && !line.includes('process.env')) {
625
+ suggestions.push(this.createSuggestion({
626
+ category: 'security',
627
+ priority: 'critical',
628
+ title: '硬编码密钥',
629
+ description: '发现硬编码的密钥或敏感信息',
630
+ location: { file: this.getRelativePath(file), line: index + 1 },
631
+ suggestion: '使用环境变量存储敏感信息',
632
+ autoFixable: false,
633
+ impact: '可能导致密钥泄露',
634
+ effort: 'small'
635
+ }));
636
+ break;
637
+ }
638
+ }
639
+ // 检查 eval 使用 - 排除字符串字面量和注释中的匹配
640
+ const evalMatch = line.match(/\beval\s*\(/);
641
+ if (evalMatch && !line.includes('// safe') && !line.includes("'eval") && !line.includes('"eval')) {
642
+ suggestions.push(this.createSuggestion({
643
+ category: 'security',
644
+ priority: 'critical',
645
+ title: '使用 eval 函数',
646
+ description: 'eval() 可能导致代码注入漏洞',
647
+ location: { file: this.getRelativePath(file), line: index + 1 },
648
+ suggestion: '避免使用 eval,使用更安全的替代方案',
649
+ autoFixable: false,
650
+ impact: '可能导致代码注入',
651
+ effort: 'medium'
652
+ }));
653
+ }
654
+ // 检查 SQL 注入风险
655
+ if (line.includes('query(') && line.includes('${') && !line.includes('parameterized')) {
656
+ suggestions.push(this.createSuggestion({
657
+ category: 'security',
658
+ priority: 'high',
659
+ title: '潜在 SQL 注入',
660
+ description: 'SQL 查询中使用了字符串插值',
661
+ location: { file: this.getRelativePath(file), line: index + 1 },
662
+ suggestion: '使用参数化查询',
663
+ autoFixable: false,
664
+ impact: '可能导致 SQL 注入攻击',
665
+ effort: 'small'
666
+ }));
667
+ }
668
+ });
669
+ }
670
+ return suggestions;
671
+ }
672
+ /**
673
+ * 检测常见问题
674
+ */
675
+ async detectCommonIssues() {
676
+ const suggestions = [];
677
+ const files = await this.scanFiles(['.ts', '.js', '.tsx', '.jsx']);
678
+ // 检测重复代码 (简化版 - 检测相似的代码块)
679
+ const codeBlocks = new Map();
680
+ for (const file of files) {
681
+ const content = await fs.readFile(file, 'utf-8');
682
+ const lines = content.split('\n');
683
+ // 检测硬编码字符串
684
+ lines.forEach((line, index) => {
685
+ // 硬编码路径
686
+ if (line.includes('C:\\') || line.includes('/home/') || line.includes('/Users/')) {
687
+ if (!line.includes('example') && !line.includes('test')) {
688
+ suggestions.push(this.createSuggestion({
689
+ category: 'common',
690
+ priority: 'medium',
691
+ title: '硬编码路径',
692
+ description: '发现硬编码的文件路径',
693
+ location: { file: this.getRelativePath(file), line: index + 1 },
694
+ suggestion: '使用相对路径或配置文件',
695
+ autoFixable: false,
696
+ impact: '降低代码可移植性',
697
+ effort: 'trivial'
698
+ }));
699
+ }
700
+ }
701
+ // 魔法数字
702
+ const numberMatch = line.match(/[^a-zA-Z_](\d{3,})[^a-zA-Z_0-9]/);
703
+ if (numberMatch && !line.includes('const') && !line.includes('enum')) {
704
+ const num = parseInt(numberMatch[1]);
705
+ if (num > 100 && num !== 1000 && num !== 1024) {
706
+ suggestions.push(this.createSuggestion({
707
+ category: 'common',
708
+ priority: 'low',
709
+ title: '魔法数字',
710
+ description: `发现魔法数字 ${num}`,
711
+ location: { file: this.getRelativePath(file), line: index + 1 },
712
+ suggestion: '使用命名常量替代',
713
+ autoFixable: false,
714
+ impact: '降低代码可读性',
715
+ effort: 'trivial'
716
+ }));
717
+ }
718
+ }
719
+ });
720
+ }
721
+ return suggestions;
722
+ }
723
+ /**
724
+ * 检测 Prompt 问题 (AI 项目)
725
+ */
726
+ async detectPromptIssues() {
727
+ const suggestions = [];
728
+ // 扫描 prompts 目录和 .md 文件
729
+ const promptFiles = await this.scanFiles(['.md', '.txt']);
730
+ for (const file of promptFiles) {
731
+ // 只处理可能包含 prompt 的文件
732
+ if (!file.includes('prompt') && !file.includes('PROMPT') &&
733
+ !file.includes('.claude') && !file.includes('prompts/')) {
734
+ continue;
735
+ }
736
+ const content = await fs.readFile(file, 'utf-8');
737
+ const lines = content.split('\n');
738
+ lines.forEach((line, index) => {
739
+ // 检测过于简短的 prompt
740
+ if (line.trim().length > 0 && line.trim().length < 20 &&
741
+ !line.startsWith('#') && !line.startsWith('<!--')) {
742
+ suggestions.push(this.createSuggestion({
743
+ category: 'prompt',
744
+ priority: 'low',
745
+ title: 'Prompt 过于简短',
746
+ description: `Prompt "${line.trim().substring(0, 30)}..." 过于简短,可能导致模型理解不充分`,
747
+ location: { file: this.getRelativePath(file), line: index + 1 },
748
+ suggestion: '扩展 prompt 内容,添加更多上下文和示例',
749
+ autoFixable: false,
750
+ impact: '可能导致模型输出不符合预期',
751
+ effort: 'small'
752
+ }));
753
+ }
754
+ // 检测缺少输出格式说明
755
+ const promptKeywords = ['请', 'write', 'generate', 'create', '实现', '生成'];
756
+ if (promptKeywords.some(kw => line.toLowerCase().includes(kw))) {
757
+ const nextLines = lines.slice(index, index + 10).join('\n').toLowerCase();
758
+ if (!nextLines.includes('格式') && !nextLines.includes('format') &&
759
+ !nextLines.includes('输出') && !nextLines.includes('output')) {
760
+ suggestions.push(this.createSuggestion({
761
+ category: 'prompt',
762
+ priority: 'medium',
763
+ title: 'Prompt 缺少输出格式说明',
764
+ description: 'Prompt 没有明确指定输出格式',
765
+ location: { file: this.getRelativePath(file), line: index + 1 },
766
+ suggestion: '添加输出格式说明,如 JSON、Markdown 或具体结构',
767
+ autoFixable: false,
768
+ impact: '模型输出格式可能不一致',
769
+ effort: 'trivial'
770
+ }));
771
+ }
772
+ }
773
+ // 检测潜在的 Prompt 注入风险
774
+ const injectionPatterns = [
775
+ /\{\{.*user.*\}\}/i,
776
+ /\$\{.*input.*\}/i,
777
+ /user.*input/i,
778
+ /用户.*输入/i
779
+ ];
780
+ for (const pattern of injectionPatterns) {
781
+ if (pattern.test(line)) {
782
+ suggestions.push(this.createSuggestion({
783
+ category: 'prompt',
784
+ priority: 'high',
785
+ title: '潜在 Prompt 注入风险',
786
+ description: 'Prompt 中直接使用用户输入,可能导致注入攻击',
787
+ location: { file: this.getRelativePath(file), line: index + 1 },
788
+ suggestion: '对用户输入进行验证和清理,或使用结构化 prompt',
789
+ autoFixable: false,
790
+ impact: '可能导致 Prompt 注入攻击',
791
+ effort: 'medium'
792
+ }));
793
+ break;
794
+ }
795
+ }
796
+ });
797
+ // 检测缺少示例
798
+ if (content.length > 200 && !content.includes('示例') &&
799
+ !content.includes('example') && !content.includes('Example')) {
800
+ suggestions.push(this.createSuggestion({
801
+ category: 'prompt',
802
+ priority: 'medium',
803
+ title: 'Prompt 缺少示例',
804
+ description: 'Prompt 内容较长但没有提供示例',
805
+ location: { file: this.getRelativePath(file) },
806
+ suggestion: '添加输入输出示例,帮助模型更好理解任务',
807
+ autoFixable: false,
808
+ impact: '可能影响模型输出质量',
809
+ effort: 'small'
810
+ }));
811
+ }
812
+ }
813
+ return suggestions;
814
+ }
815
+ /**
816
+ * 检测 Skill 问题 (AI 项目)
817
+ */
818
+ async detectSkillIssues() {
819
+ const suggestions = [];
820
+ // 扫描 skills 目录
821
+ const skillFiles = await this.scanFiles(['.md']);
822
+ for (const file of skillFiles) {
823
+ if (!file.includes('skills/') && !file.includes('skill')) {
824
+ continue;
825
+ }
826
+ const content = await fs.readFile(file, 'utf-8');
827
+ const fileName = path.basename(file);
828
+ // 检测 Skill 文件结构
829
+ const hasFrontmatter = content.startsWith('---');
830
+ if (!hasFrontmatter) {
831
+ suggestions.push(this.createSuggestion({
832
+ category: 'skill',
833
+ priority: 'high',
834
+ title: 'Skill 缺少 frontmatter',
835
+ description: `Skill 文件 ${fileName} 缺少 YAML frontmatter`,
836
+ location: { file: this.getRelativePath(file) },
837
+ suggestion: '添加 frontmatter,包含 name 和 description 字段',
838
+ autoFixable: false,
839
+ impact: 'Skill 可能无法被正确识别和加载',
840
+ effort: 'trivial'
841
+ }));
842
+ }
843
+ else {
844
+ // 解析 frontmatter
845
+ const frontmatterMatch = content.match(/^---\n([\s\S]*?)\n---/);
846
+ if (frontmatterMatch) {
847
+ const frontmatter = frontmatterMatch[1];
848
+ // 检查必需字段
849
+ if (!frontmatter.includes('name:')) {
850
+ suggestions.push(this.createSuggestion({
851
+ category: 'skill',
852
+ priority: 'high',
853
+ title: 'Skill 缺少 name 字段',
854
+ description: `Skill 文件 ${fileName} 缺少 name 字段`,
855
+ location: { file: this.getRelativePath(file) },
856
+ suggestion: '在 frontmatter 中添加 name 字段',
857
+ autoFixable: false,
858
+ impact: 'Skill 可能无法被正确识别',
859
+ effort: 'trivial'
860
+ }));
861
+ }
862
+ if (!frontmatter.includes('description:')) {
863
+ suggestions.push(this.createSuggestion({
864
+ category: 'skill',
865
+ priority: 'medium',
866
+ title: 'Skill 缺少 description 字段',
867
+ description: `Skill 文件 ${fileName} 缺少 description 字段`,
868
+ location: { file: this.getRelativePath(file) },
869
+ suggestion: '在 frontmatter 中添加 description 字段',
870
+ autoFixable: false,
871
+ impact: '用户难以理解 Skill 用途',
872
+ effort: 'trivial'
873
+ }));
874
+ }
875
+ }
876
+ }
877
+ // 检测缺少 <objective> 标签
878
+ if (!content.includes('<objective>')) {
879
+ suggestions.push(this.createSuggestion({
880
+ category: 'skill',
881
+ priority: 'medium',
882
+ title: 'Skill 缺少 objective 标签',
883
+ description: `Skill 文件 ${fileName} 缺少明确的 objective 标签`,
884
+ location: { file: this.getRelativePath(file) },
885
+ suggestion: '添加 <objective> 标签说明 Skill 的目标',
886
+ autoFixable: false,
887
+ impact: 'AI 可能无法正确理解 Skill 用途',
888
+ effort: 'trivial'
889
+ }));
890
+ }
891
+ // 检测缺少 <process> 标签
892
+ if (!content.includes('<process>')) {
893
+ suggestions.push(this.createSuggestion({
894
+ category: 'skill',
895
+ priority: 'medium',
896
+ title: 'Skill 缺少 process 标签',
897
+ description: `Skill 文件 ${fileName} 缺少 process 标签`,
898
+ location: { file: this.getRelativePath(file) },
899
+ suggestion: '添加 <process> 标签说明执行步骤',
900
+ autoFixable: false,
901
+ impact: 'AI 可能无法正确执行 Skill',
902
+ effort: 'small'
903
+ }));
904
+ }
905
+ // 检测缺少示例
906
+ if (!content.includes('<examples>') && !content.includes('示例')) {
907
+ suggestions.push(this.createSuggestion({
908
+ category: 'skill',
909
+ priority: 'low',
910
+ title: 'Skill 缺少示例',
911
+ description: `Skill 文件 ${fileName} 缺少使用示例`,
912
+ location: { file: this.getRelativePath(file) },
913
+ suggestion: '添加 <examples> 标签展示使用方式',
914
+ autoFixable: false,
915
+ impact: '用户可能不清楚如何使用 Skill',
916
+ effort: 'trivial'
917
+ }));
918
+ }
919
+ // 检测 trigger-conditions (对于自动触发的 Skill)
920
+ if (content.includes('TRIGGER') || content.includes('trigger-conditions')) {
921
+ if (!content.includes('<trigger-conditions>')) {
922
+ suggestions.push(this.createSuggestion({
923
+ category: 'skill',
924
+ priority: 'high',
925
+ title: 'Skill 触发条件格式不规范',
926
+ description: `Skill 文件 ${fileName} 使用了 TRIGGER 但没有 <trigger-conditions> 标签`,
927
+ location: { file: this.getRelativePath(file) },
928
+ suggestion: '使用 <trigger-conditions> 标签规范触发条件',
929
+ autoFixable: false,
930
+ impact: 'Skill 可能无法正确自动触发',
931
+ effort: 'trivial'
932
+ }));
933
+ }
934
+ }
935
+ }
936
+ return suggestions;
937
+ }
938
+ /**
939
+ * 检测 Agent 配置问题 (AI 项目)
940
+ */
941
+ async detectAgentConfigIssues() {
942
+ const suggestions = [];
943
+ // 检查 CLAUDE.md
944
+ try {
945
+ const claudeMdPath = path.join(this.projectRoot, 'CLAUDE.md');
946
+ const content = await fs.readFile(claudeMdPath, 'utf-8');
947
+ // 检测是否包含必要的项目信息
948
+ if (!content.includes('Build') && !content.includes('构建') &&
949
+ !content.includes('npm run') && !content.includes('构建命令')) {
950
+ suggestions.push(this.createSuggestion({
951
+ category: 'agent',
952
+ priority: 'high',
953
+ title: 'CLAUDE.md 缺少构建命令',
954
+ description: 'CLAUDE.md 没有说明项目的构建命令',
955
+ location: { file: 'CLAUDE.md' },
956
+ suggestion: '添加 Build 部分说明项目的构建和测试命令',
957
+ autoFixable: false,
958
+ impact: 'AI 可能无法正确构建和测试项目',
959
+ effort: 'trivial'
960
+ }));
961
+ }
962
+ // 检测是否包含项目概述
963
+ if (content.length < 200) {
964
+ suggestions.push(this.createSuggestion({
965
+ category: 'agent',
966
+ priority: 'medium',
967
+ title: 'CLAUDE.md 内容过于简短',
968
+ description: 'CLAUDE.md 内容过短,可能缺少重要的项目信息',
969
+ location: { file: 'CLAUDE.md' },
970
+ suggestion: '扩展 CLAUDE.md,添加项目概述、架构说明和开发规范',
971
+ autoFixable: false,
972
+ impact: 'AI 可能无法充分理解项目上下文',
973
+ effort: 'small'
974
+ }));
975
+ }
976
+ // 检测是否包含代码风格指南
977
+ if (!content.includes('style') && !content.includes('风格') &&
978
+ !content.includes('convention') && !content.includes('规范')) {
979
+ suggestions.push(this.createSuggestion({
980
+ category: 'agent',
981
+ priority: 'low',
982
+ title: 'CLAUDE.md 缺少代码风格指南',
983
+ description: 'CLAUDE.md 没有说明代码风格和规范',
984
+ location: { file: 'CLAUDE.md' },
985
+ suggestion: '添加代码风格指南部分',
986
+ autoFixable: false,
987
+ impact: 'AI 生成的代码可能不符合项目风格',
988
+ effort: 'trivial'
989
+ }));
990
+ }
991
+ }
992
+ catch {
993
+ // 没有 CLAUDE.md,但这在其他地方已经检测
994
+ }
995
+ // 检查 .cursorrules
996
+ try {
997
+ const cursorrulesPath = path.join(this.projectRoot, '.cursorrules');
998
+ const content = await fs.readFile(cursorrulesPath, 'utf-8');
999
+ if (content.length < 50) {
1000
+ suggestions.push(this.createSuggestion({
1001
+ category: 'agent',
1002
+ priority: 'low',
1003
+ title: '.cursorrules 内容过于简短',
1004
+ description: '.cursorrules 内容过短',
1005
+ location: { file: '.cursorrules' },
1006
+ suggestion: '扩展 .cursorrules,添加更多项目规则',
1007
+ autoFixable: false,
1008
+ impact: 'Cursor AI 可能无法充分理解项目规则',
1009
+ effort: 'trivial'
1010
+ }));
1011
+ }
1012
+ }
1013
+ catch {
1014
+ // 没有 .cursorrules
1015
+ }
1016
+ // 检查 MCP 配置
1017
+ try {
1018
+ const mcpPath = path.join(this.projectRoot, '.mcp', 'settings.json');
1019
+ const content = await fs.readFile(mcpPath, 'utf-8');
1020
+ const mcpConfig = JSON.parse(content);
1021
+ // 检测 MCP 服务器配置
1022
+ if (!mcpConfig.mcpServers || Object.keys(mcpConfig.mcpServers || {}).length === 0) {
1023
+ suggestions.push(this.createSuggestion({
1024
+ category: 'agent',
1025
+ priority: 'medium',
1026
+ title: 'MCP 配置中没有服务器',
1027
+ description: '.mcp/settings.json 没有配置任何 MCP 服务器',
1028
+ location: { file: '.mcp/settings.json' },
1029
+ suggestion: '添加 MCP 服务器配置以扩展 AI 能力',
1030
+ autoFixable: false,
1031
+ impact: 'AI 能力受限',
1032
+ effort: 'small'
1033
+ }));
1034
+ }
1035
+ }
1036
+ catch {
1037
+ // 没有 MCP 配置
1038
+ }
1039
+ // 检查 settings.json (Claude Code)
1040
+ try {
1041
+ const settingsPath = path.join(this.projectRoot, '.claude', 'settings.json');
1042
+ const content = await fs.readFile(settingsPath, 'utf-8');
1043
+ const settings = JSON.parse(content);
1044
+ // 检测权限配置
1045
+ if (!settings.permissions || settings.permissions.length === 0) {
1046
+ suggestions.push(this.createSuggestion({
1047
+ category: 'agent',
1048
+ priority: 'low',
1049
+ title: 'Claude Code 设置缺少权限配置',
1050
+ description: '.claude/settings.json 没有配置权限',
1051
+ location: { file: '.claude/settings.json' },
1052
+ suggestion: '添加权限配置以优化开发体验',
1053
+ autoFixable: false,
1054
+ impact: '可能需要频繁手动批准操作',
1055
+ effort: 'trivial'
1056
+ }));
1057
+ }
1058
+ }
1059
+ catch {
1060
+ // 没有设置文件
1061
+ }
1062
+ return suggestions;
1063
+ }
1064
+ /**
1065
+ * 扫描文件
1066
+ */
1067
+ async scanFiles(extensions) {
1068
+ const files = [];
1069
+ const scan = async (dir) => {
1070
+ try {
1071
+ const entries = await fs.readdir(dir, { withFileTypes: true });
1072
+ for (const entry of entries) {
1073
+ const fullPath = path.join(dir, entry.name);
1074
+ if (entry.isDirectory()) {
1075
+ if (!this.config.excludeDirs.includes(entry.name)) {
1076
+ await scan(fullPath);
1077
+ }
1078
+ }
1079
+ else if (entry.isFile()) {
1080
+ const ext = path.extname(entry.name);
1081
+ if (extensions.includes(ext)) {
1082
+ files.push(fullPath);
1083
+ }
1084
+ }
1085
+ }
1086
+ }
1087
+ catch {
1088
+ // 目录不存在或无权限,跳过
1089
+ }
1090
+ };
1091
+ for (const dir of this.config.scanDirs) {
1092
+ await scan(path.join(this.projectRoot, dir));
1093
+ }
1094
+ return files;
1095
+ }
1096
+ /**
1097
+ * 创建建议
1098
+ */
1099
+ createSuggestion(partial) {
1100
+ return {
1101
+ id: `UPG-${String(++this.suggestionIdCounter).padStart(3, '0')}`,
1102
+ ...partial
1103
+ };
1104
+ }
1105
+ /**
1106
+ * 获取相对路径
1107
+ */
1108
+ getRelativePath(absolutePath) {
1109
+ return path.relative(this.projectRoot, absolutePath);
1110
+ }
1111
+ /**
1112
+ * 应用用户提示过滤
1113
+ */
1114
+ applyUserHint(suggestions) {
1115
+ if (!this.config.userHint) {
1116
+ return suggestions;
1117
+ }
1118
+ const hint = this.config.userHint.toLowerCase();
1119
+ // 根据用户提示排序相关建议
1120
+ return suggestions.sort((a, b) => {
1121
+ const aRelevance = this.calculateRelevance(a, hint);
1122
+ const bRelevance = this.calculateRelevance(b, hint);
1123
+ return bRelevance - aRelevance;
1124
+ });
1125
+ }
1126
+ /**
1127
+ * 计算建议与提示的相关性
1128
+ */
1129
+ calculateRelevance(suggestion, hint) {
1130
+ let score = 0;
1131
+ const keywords = hint.split(/\s+/);
1132
+ const title = suggestion.title.toLowerCase();
1133
+ const desc = suggestion.description.toLowerCase();
1134
+ for (const keyword of keywords) {
1135
+ if (title.includes(keyword))
1136
+ score += 2;
1137
+ if (desc.includes(keyword))
1138
+ score += 1;
1139
+ if (suggestion.category.includes(keyword))
1140
+ score += 3;
1141
+ }
1142
+ return score;
1143
+ }
1144
+ /**
1145
+ * 按优先级排序
1146
+ */
1147
+ sortByPriority(suggestions) {
1148
+ const priorityOrder = {
1149
+ critical: 0,
1150
+ high: 1,
1151
+ medium: 2,
1152
+ low: 3
1153
+ };
1154
+ return suggestions.sort((a, b) => {
1155
+ return priorityOrder[a.priority] - priorityOrder[b.priority];
1156
+ });
1157
+ }
1158
+ /**
1159
+ * 生成摘要
1160
+ */
1161
+ generateSummary(suggestions) {
1162
+ const byCategory = {
1163
+ bug: 0, quality: 0, capability: 0, ux: 0, style: 0, security: 0, common: 0,
1164
+ prompt: 0, skill: 0, agent: 0
1165
+ };
1166
+ const byPriority = {
1167
+ critical: 0, high: 0, medium: 0, low: 0
1168
+ };
1169
+ let autoFixable = 0;
1170
+ for (const s of suggestions) {
1171
+ byCategory[s.category]++;
1172
+ byPriority[s.priority]++;
1173
+ if (s.autoFixable)
1174
+ autoFixable++;
1175
+ }
1176
+ return {
1177
+ total: suggestions.length,
1178
+ byCategory,
1179
+ byPriority,
1180
+ autoFixable
1181
+ };
1182
+ }
1183
+ }
1184
+ exports.UpgradeDetector = UpgradeDetector;