rules-enforcer 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (33) hide show
  1. package/README.md +58 -0
  2. package/detector/README.md +212 -0
  3. package/detector/decision-engine/README.md +203 -0
  4. package/detector/decision-engine/conflict-resolver.js +336 -0
  5. package/detector/decision-engine/de-verify.js +461 -0
  6. package/detector/decision-engine/index.js +204 -0
  7. package/detector/decision-engine/optimizer.js +325 -0
  8. package/detector/decision-engine/scorer.js +359 -0
  9. package/detector/knowledge-base/README.md +140 -0
  10. package/detector/knowledge-base/agent-knowledge.json +62 -0
  11. package/detector/knowledge-base/index.js +332 -0
  12. package/detector/knowledge-base/kb-verify.js +287 -0
  13. package/detector/knowledge-base/mcp-knowledge.json +135 -0
  14. package/detector/knowledge-base/rules-knowledge.json +184 -0
  15. package/detector/mcp-server.js +157 -0
  16. package/detector/mcp-service.js +118 -0
  17. package/detector/package.json +13 -0
  18. package/detector/plugin.json +122 -0
  19. package/detector/project-detector.js +710 -0
  20. package/detector/render-engine/ag-config-render.js +195 -0
  21. package/detector/render-engine/index.js +124 -0
  22. package/detector/render-engine/render-core.js +200 -0
  23. package/detector/render-engine/render-verify.js +282 -0
  24. package/detector/render-engine/rule-render.js +231 -0
  25. package/detector/test-exceptions.js +366 -0
  26. package/detector/verify-plugin.js +233 -0
  27. package/hooks/chain-invoker.js +98 -0
  28. package/hooks/custom-hook-server.js +312 -0
  29. package/hooks/mcp-hooks.js +153 -0
  30. package/hooks/validate-chain.js +147 -0
  31. package/package.json +35 -0
  32. package/rules-server.js +350 -0
  33. package/test/test-mcp-full.js +193 -0
@@ -0,0 +1,710 @@
1
+ /**
2
+ * Project Detector Plugin for Trae IDE
3
+ * 插件路径: .trae/plugins/project-detector/
4
+ * 功能: 只读扫描项目结构,识别技术栈、目录拓扑、已有组件
5
+ * 约束: 全程只读,不修改项目任何源码与配置
6
+ */
7
+
8
+ const fs = require('fs');
9
+ const path = require('path');
10
+
11
+ class ProjectDetector {
12
+ constructor(projectRoot) {
13
+ if (!projectRoot || typeof projectRoot !== 'string') {
14
+ throw new Error('[ProjectDetector] 项目路径参数无效');
15
+ }
16
+
17
+ this.root = path.resolve(projectRoot);
18
+
19
+ if (!fs.existsSync(this.root) || !fs.statSync(this.root).isDirectory()) {
20
+ throw new Error(`[ProjectDetector] 项目路径不存在或不是目录: ${this.root}`);
21
+ }
22
+
23
+ this.techStack = [];
24
+ this.structure = null;
25
+ this.fullTopology = [];
26
+ this.existingComponents = {
27
+ mcp: [],
28
+ agents: [],
29
+ rules: []
30
+ };
31
+ this.fileErrors = [];
32
+ }
33
+
34
+ /**
35
+ * 记录文件读取异常
36
+ */
37
+ logFileError(filePath, error, context) {
38
+ this.fileErrors.push({
39
+ file: filePath,
40
+ error: String(error),
41
+ context: context,
42
+ timestamp: new Date().toISOString()
43
+ });
44
+ }
45
+
46
+ /**
47
+ * 扫描技术栈
48
+ */
49
+ scanTechStack() {
50
+ const checks = [
51
+ { file: 'package.json', detect: (content) => this.detectNodeJS(content) },
52
+ { file: 'tsconfig.json', detect: () => 'TypeScript' },
53
+ { file: 'requirements.txt', detect: () => 'Python' },
54
+ { file: 'go.mod', detect: () => 'Go' },
55
+ { file: 'Cargo.toml', detect: () => 'Rust' },
56
+ { file: 'pom.xml', detect: () => 'Maven/Java' }
57
+ ];
58
+
59
+ for (const check of checks) {
60
+ const filePath = path.join(this.root, check.file);
61
+ if (fs.existsSync(filePath)) {
62
+ try {
63
+ const content = fs.readFileSync(filePath, 'utf8');
64
+ const detected = check.detect(content);
65
+ if (detected) {
66
+ const stacks = Array.isArray(detected) ? detected : [detected];
67
+ for (const stack of stacks) {
68
+ if (stack && !this.techStack.includes(stack)) {
69
+ this.techStack.push(stack);
70
+ }
71
+ }
72
+ }
73
+ } catch (e) {
74
+ this.logFileError(check.file, e, 'scanTechStack');
75
+ }
76
+ }
77
+ }
78
+
79
+ return this.techStack;
80
+ }
81
+
82
+ /**
83
+ * 解析Node.js项目依赖
84
+ */
85
+ detectNodeJS(pkgContent) {
86
+ try {
87
+ const pkg = JSON.parse(pkgContent);
88
+ const deps = pkg.dependencies || {};
89
+ const frameworks = [];
90
+
91
+ if (deps.express || deps['express-']) frameworks.push('Express.js');
92
+ if (deps.koa) frameworks.push('Koa');
93
+ if (deps.fastify) frameworks.push('Fastify');
94
+ if (deps.next) frameworks.push('Next.js');
95
+ if (deps.nuxt) frameworks.push('Nuxt.js');
96
+ if (deps.vite) frameworks.push('Vite');
97
+ if (deps.webpack) frameworks.push('Webpack');
98
+ if (deps.react) frameworks.push('React');
99
+ if (deps.vue) frameworks.push('Vue.js');
100
+ if (deps.sequelize) frameworks.push('Sequelize/ORM');
101
+ if (deps.typeorm) frameworks.push('TypeORM');
102
+ if (deps['better-sqlite3'] || deps.sqlite3) frameworks.push('SQLite');
103
+ if (deps.mongoose) frameworks.push('MongoDB/Mongoose');
104
+
105
+ return ['Node.js', ...frameworks];
106
+ } catch {
107
+ return ['Node.js'];
108
+ }
109
+ }
110
+
111
+ /**
112
+ * 扫描目录结构
113
+ */
114
+ scanDirectoryStructure(maxDepth = 4) {
115
+ this.fullTopology = [];
116
+
117
+ try {
118
+ this.structure = this._scanDir(this.root, 0, maxDepth);
119
+ } catch (e) {
120
+ this.logFileError(this.root, e, 'scanDirectoryStructure');
121
+ this.structure = null;
122
+ }
123
+
124
+ return this.structure;
125
+ }
126
+
127
+ /**
128
+ * 递归扫描目录
129
+ */
130
+ _scanDir(dir, depth, maxDepth) {
131
+ if (depth > maxDepth) return null;
132
+
133
+ const name = path.basename(dir);
134
+ const relativePath = path.relative(this.root, dir) || '.';
135
+
136
+ const node = {
137
+ name,
138
+ path: relativePath,
139
+ depth,
140
+ type: this.categorizeDir(name),
141
+ files: [],
142
+ dirs: []
143
+ };
144
+
145
+ this.fullTopology.push({
146
+ path: relativePath,
147
+ depth,
148
+ type: 'directory',
149
+ category: node.type
150
+ });
151
+
152
+ try {
153
+ const entries = fs.readdirSync(dir, { withFileTypes: true });
154
+
155
+ for (const entry of entries) {
156
+ if (entry.name.startsWith('.') || entry.name === 'node_modules') {
157
+ continue;
158
+ }
159
+
160
+ const fullPath = path.join(dir, entry.name);
161
+
162
+ if (entry.isDirectory()) {
163
+ const childNode = this._scanDir(fullPath, depth + 1, maxDepth);
164
+ if (childNode) {
165
+ node.dirs.push(childNode);
166
+ }
167
+ } else {
168
+ const fileInfo = {
169
+ name: entry.name,
170
+ path: path.relative(this.root, fullPath),
171
+ type: this.categorizeFile(entry.name)
172
+ };
173
+ node.files.push(fileInfo);
174
+
175
+ this.fullTopology.push({
176
+ path: fileInfo.path,
177
+ depth: depth + 1,
178
+ type: 'file',
179
+ category: fileInfo.type
180
+ });
181
+ }
182
+ }
183
+ } catch (e) {
184
+ this.logFileError(dir, e, `_scanDir depth=${depth}`);
185
+ }
186
+
187
+ return node;
188
+ }
189
+
190
+ /**
191
+ * 目录分类
192
+ */
193
+ categorizeDir(name) {
194
+ const categories = {
195
+ 'src': 'business_code',
196
+ 'lib': 'business_code',
197
+ 'app': 'business_code',
198
+ 'pages': 'frontend_pages',
199
+ 'components': 'frontend_components',
200
+ 'views': 'frontend_views',
201
+ 'test': 'test_directory',
202
+ 'tests': 'test_directory',
203
+ '__tests__': 'test_directory',
204
+ 'spec': 'test_directory',
205
+ 'scripts': 'deployment_scripts',
206
+ 'config': 'configuration',
207
+ 'configs': 'configuration',
208
+ 'docs': 'documentation',
209
+ 'public': 'static_assets',
210
+ 'static': 'static_assets',
211
+ 'dist': 'build_output',
212
+ 'build': 'build_output',
213
+ '.trae': 'trae_config',
214
+ '.claude': 'claude_config',
215
+ 'database': 'data_storage',
216
+ 'mcp': 'mcp_services',
217
+ 'rules': 'rules_engine',
218
+ 'utils': 'utilities',
219
+ 'middleware': 'middleware',
220
+ 'routes': 'routing'
221
+ };
222
+ return categories[name] || 'other';
223
+ }
224
+
225
+ /**
226
+ * 文件分类
227
+ */
228
+ categorizeFile(name) {
229
+ if (name.endsWith('.test.js') || name.endsWith('.spec.js')) return 'test_file';
230
+ if (name.endsWith('.test.ts') || name.endsWith('.spec.ts')) return 'test_file';
231
+ if (name === 'package.json') return 'dependency_manifest';
232
+ if (name === 'AGENTS.md' || name === 'CLAUDE.md') return 'trae_config';
233
+ if (name === 'user_rules.md') return 'rules_L1';
234
+ if (name === 'project_rules.md') return 'rules_L2';
235
+ if (name.endsWith('.yml') || name.endsWith('.yaml')) return 'config_yaml';
236
+ if (name.endsWith('.json')) return 'config_json';
237
+ if (name.endsWith('.js') || name.endsWith('.ts')) return 'source_code';
238
+ if (name.endsWith('.md')) return 'documentation';
239
+ return 'other';
240
+ }
241
+
242
+ /**
243
+ * 扫描MCP服务
244
+ */
245
+ scanMCP() {
246
+ const mcpConfigPaths = [
247
+ path.join(this.root, '.trae', 'mcp', 'mcp.json'),
248
+ path.join(this.root, '.trae', 'mcp.json'),
249
+ path.join(process.env.APPDATA || '', 'Trae', 'User', 'globalStorage', 'mcp.json')
250
+ ];
251
+
252
+ for (const configPath of mcpConfigPaths) {
253
+ if (fs.existsSync(configPath)) {
254
+ try {
255
+ const content = fs.readFileSync(configPath, 'utf8');
256
+ const config = JSON.parse(content);
257
+ if (config.mcpServers) {
258
+ this.existingComponents.mcp = Object.keys(config.mcpServers);
259
+ break;
260
+ }
261
+ } catch (e) {
262
+ this.logFileError(configPath, e, 'scanMCP');
263
+ }
264
+ }
265
+ }
266
+
267
+ return this.existingComponents.mcp;
268
+ }
269
+
270
+ /**
271
+ * 扫描Agent配置
272
+ */
273
+ scanAgents() {
274
+ const rootAgentsPath = path.join(this.root, 'AGENTS.md');
275
+ if (fs.existsSync(rootAgentsPath)) {
276
+ this.existingComponents.agents.push({
277
+ name: 'AGENTS.md',
278
+ path: 'AGENTS.md',
279
+ type: 'root_config'
280
+ });
281
+ }
282
+
283
+ const agentsDir = path.join(this.root, '.trae', 'agents');
284
+ if (fs.existsSync(agentsDir)) {
285
+ try {
286
+ const files = fs.readdirSync(agentsDir);
287
+ for (const file of files) {
288
+ if (file.endsWith('.md')) {
289
+ this.existingComponents.agents.push({
290
+ name: file,
291
+ path: path.join('.trae', 'agents', file),
292
+ type: 'sub_agent'
293
+ });
294
+ }
295
+ }
296
+ } catch (e) {
297
+ this.logFileError(agentsDir, e, 'scanAgents');
298
+ }
299
+ }
300
+
301
+ return this.existingComponents.agents;
302
+ }
303
+
304
+ /**
305
+ * 扫描规则文件
306
+ */
307
+ scanRules() {
308
+ const rulesDir = path.join(this.root, '.trae', 'rules');
309
+
310
+ if (!fs.existsSync(rulesDir)) {
311
+ this.fileErrors.push({
312
+ file: rulesDir,
313
+ error: '目录不存在',
314
+ context: 'scanRules',
315
+ timestamp: new Date().toISOString()
316
+ });
317
+ return this.existingComponents.rules;
318
+ }
319
+
320
+ // L1/L2目录规则
321
+ const levelRules = [
322
+ { dir: 'L1', level: 'L1', suffix: '-user-rules.md' },
323
+ { dir: 'L2', level: 'L2', suffix: '-project-rules.md' }
324
+ ];
325
+
326
+ for (const rule of levelRules) {
327
+ const levelDir = path.join(rulesDir, rule.dir);
328
+ if (fs.existsSync(levelDir)) {
329
+ try {
330
+ const files = fs.readdirSync(levelDir);
331
+ for (const file of files) {
332
+ if (file.endsWith('.md')) {
333
+ const filePath = path.join(levelDir, file);
334
+ try {
335
+ fs.readFileSync(filePath, 'utf8');
336
+ this.existingComponents.rules.push({
337
+ name: file,
338
+ path: path.join('.trae', 'rules', rule.dir, file),
339
+ level: rule.level
340
+ });
341
+ } catch (readErr) {
342
+ this.logFileError(filePath, readErr, `scanRules/${rule.dir}/read`);
343
+ }
344
+ }
345
+ }
346
+ } catch (e) {
347
+ this.fileErrors.push({
348
+ file: levelDir,
349
+ error: e.message,
350
+ context: `scanRules/${rule.dir}`,
351
+ timestamp: new Date().toISOString()
352
+ });
353
+ }
354
+ }
355
+ }
356
+
357
+ const l3Dirs = ['L3/RZero', 'custom_config'];
358
+ for (const l3Dir of l3Dirs) {
359
+ const l3Path = path.join(rulesDir, l3Dir);
360
+
361
+ if (!fs.existsSync(l3Path)) {
362
+ this.fileErrors.push({
363
+ file: l3Path,
364
+ error: 'L3子目录不存在',
365
+ context: `scanRules/${l3Dir}`,
366
+ timestamp: new Date().toISOString()
367
+ });
368
+ continue;
369
+ }
370
+
371
+ try {
372
+ const files = fs.readdirSync(l3Path);
373
+ for (const file of files) {
374
+ if (file.endsWith('.md') || file.endsWith('.yml') || file.endsWith('.yaml')) {
375
+ const filePath = path.join(l3Path, file);
376
+ try {
377
+ fs.readFileSync(filePath, 'utf8');
378
+ this.existingComponents.rules.push({
379
+ name: file,
380
+ path: path.join('.trae', 'rules', l3Dir, file),
381
+ level: 'L3',
382
+ module: l3Dir.includes('RZero') ? 'RZero' : 'custom_config',
383
+ readable: true
384
+ });
385
+ } catch (readErr) {
386
+ this.logFileError(filePath, readErr, `scanRules/${l3Dir}/read`);
387
+ }
388
+ }
389
+ }
390
+ } catch (e) {
391
+ this.fileErrors.push({
392
+ file: l3Path,
393
+ error: e.message,
394
+ context: `scanRules/${l3Dir}/scan`,
395
+ timestamp: new Date().toISOString()
396
+ });
397
+ }
398
+ }
399
+
400
+ const attnPath = path.join(rulesDir, 'attention_optim_rule.md');
401
+ if (fs.existsSync(attnPath)) {
402
+ this.existingComponents.rules.push({
403
+ name: 'attention_optim_rule.md',
404
+ path: path.join('.trae', 'rules', 'attention_optim_rule.md'),
405
+ level: 'custom',
406
+ module: 'attention_optimization'
407
+ });
408
+ }
409
+
410
+ return this.existingComponents.rules;
411
+ }
412
+
413
+ /**
414
+ * 生成拓扑树字符串
415
+ */
416
+ generateTopologyTree() {
417
+ const lines = [];
418
+
419
+ const printNode = (node, prefix = '', isLast = true) => {
420
+ const connector = isLast ? '└── ' : '├── ';
421
+ const newPrefix = prefix + (isLast ? ' ' : '│ ');
422
+
423
+ lines.push(`${prefix}${connector}${node.name}/ [${node.type}]`);
424
+
425
+ if (node.files) {
426
+ for (let i = 0; i < node.files.length; i++) {
427
+ const file = node.files[i];
428
+ const fileConnector = i === node.files.length - 1 && (!node.dirs || node.dirs.length === 0) ? '└── ' : '├── ';
429
+ lines.push(`${newPrefix}${fileConnector}${file.name} [${file.type}]`);
430
+ }
431
+ }
432
+
433
+ if (node.dirs) {
434
+ for (let i = 0; i < node.dirs.length; i++) {
435
+ printNode(node.dirs[i], newPrefix, i === node.dirs.length - 1);
436
+ }
437
+ }
438
+ };
439
+
440
+ if (this.structure) {
441
+ lines.push(`${this.structure.name}/ [${this.structure.type}]`);
442
+ for (let i = 0; i < this.structure.dirs.length; i++) {
443
+ printNode(this.structure.dirs[i], '', i === this.structure.dirs.length - 1);
444
+ }
445
+ }
446
+
447
+ return lines.join('\n');
448
+ }
449
+
450
+ /**
451
+ * 执行完整扫描
452
+ */
453
+ scan() {
454
+ console.log('[ProjectDetector] ========== 项目探测开始 ==========');
455
+ console.log(`[ProjectDetector] 项目路径: ${this.root}`);
456
+
457
+ this.techStack = this.scanTechStack();
458
+ console.log(`[ProjectDetector] 技术栈: ${this.techStack.join(', ') || '未识别'}`);
459
+
460
+ this.scanDirectoryStructure();
461
+ console.log(`[ProjectDetector] 目录拓扑: ${this.fullTopology.length}个节点`);
462
+
463
+ this.scanMCP();
464
+ console.log(`[ProjectDetector] MCP服务: ${this.existingComponents.mcp.join(', ') || '无'}`);
465
+
466
+ this.scanAgents();
467
+ console.log(`[ProjectDetector] Agent配置: ${this.existingComponents.agents.length}个`);
468
+
469
+ this.scanRules();
470
+ console.log(`[ProjectDetector] 规则文件: ${this.existingComponents.rules.length}个`);
471
+
472
+ if (this.fileErrors.length > 0) {
473
+ console.log(`[ProjectDetector] 文件异常: ${this.fileErrors.length}个`);
474
+ }
475
+
476
+ console.log('[ProjectDetector] ========== 探测完成 ==========');
477
+
478
+ return this.getReport();
479
+ }
480
+
481
+ /**
482
+ * 获取报告
483
+ */
484
+ getReport() {
485
+ return {
486
+ projectRoot: this.root,
487
+ techStack: this.techStack,
488
+ structure: this.structure,
489
+ fullTopology: this.fullTopology,
490
+ topologyTree: this.generateTopologyTree(),
491
+ existingComponents: this.existingComponents,
492
+ fileErrors: this.fileErrors,
493
+ stats: {
494
+ mcpCount: this.existingComponents.mcp.length,
495
+ agentCount: this.existingComponents.agents.length,
496
+ ruleCount: this.existingComponents.rules.length,
497
+ l1Rules: this.existingComponents.rules.filter(r => r.level === 'L1').length,
498
+ l2Rules: this.existingComponents.rules.filter(r => r.level === 'L2').length,
499
+ l3Rules: this.existingComponents.rules.filter(r => r.level === 'L3').length,
500
+ customRules: this.existingComponents.rules.filter(r => r.level === 'custom').length,
501
+ fileErrorCount: this.fileErrors.length,
502
+ topologyNodeCount: this.fullTopology.length
503
+ },
504
+ timestamp: new Date().toISOString()
505
+ };
506
+ }
507
+
508
+ /**
509
+ * 输出给rules-engine使用
510
+ */
511
+ exportForRulesEngine() {
512
+ return {
513
+ tech_features: {
514
+ is_nodejs: this.techStack.includes('Node.js'),
515
+ is_typescript: this.techStack.includes('TypeScript'),
516
+ is_express: this.techStack.includes('Express.js'),
517
+ is_react: this.techStack.includes('React'),
518
+ is_vue: this.techStack.includes('Vue.js'),
519
+ is_database: this.techStack.some(t => t.includes('SQLite') || t.includes('MongoDB'))
520
+ },
521
+ existing_mcp: this.existingComponents.mcp,
522
+ existing_agents: this.existingComponents.agents.map(a => a.name),
523
+ existing_rules_levels: {
524
+ L1: this.existingComponents.rules.filter(r => r.level === 'L1').length,
525
+ L2: this.existingComponents.rules.filter(r => r.level === 'L2').length,
526
+ L3: this.existingComponents.rules.filter(r => r.level === 'L3').length
527
+ },
528
+ complexity: this.estimateComplexity(),
529
+ fileErrors: this.fileErrors
530
+ };
531
+ }
532
+
533
+ /**
534
+ * 评估复杂度
535
+ */
536
+ estimateComplexity() {
537
+ let score = 0;
538
+ score += this.techStack.length * 5;
539
+ score += Math.min(this.fullTopology.length, 100);
540
+ score += this.existingComponents.mcp.length * 10;
541
+ score += this.existingComponents.agents.length * 5;
542
+ score += this.existingComponents.rules.length * 3;
543
+
544
+ if (score < 30) return 'simple';
545
+ if (score < 80) return 'medium';
546
+ return 'complex';
547
+ }
548
+
549
+ /**
550
+ * 对接知识库,获取配置推荐
551
+ */
552
+ getKnowledgeBaseRecommendations() {
553
+ try {
554
+ const { KnowledgeBase } = require('./knowledge-base/index.js');
555
+ const kb = new KnowledgeBase(path.join(__dirname, 'knowledge-base'));
556
+ return kb.generateRecommendations(this.getReport());
557
+ } catch (e) {
558
+ return {
559
+ error: `知识库对接失败: ${e.message}`,
560
+ recommendations: null
561
+ };
562
+ }
563
+ }
564
+
565
+ /**
566
+ * 打印知识库推荐结果
567
+ */
568
+ printKnowledgeRecommendations() {
569
+ const recommendations = this.getKnowledgeBaseRecommendations();
570
+
571
+ if (recommendations.error) {
572
+ console.log(`[ProjectDetector] ⚠️ ${recommendations.error}`);
573
+ return;
574
+ }
575
+
576
+ console.log('\n[ProjectDetector] ========== 知识库推荐 ==========');
577
+
578
+ const { recommendations: rec, summary } = recommendations;
579
+
580
+ console.log('\n📦 推荐新增MCP服务:');
581
+ if (rec.newMcpServices.length === 0) {
582
+ console.log(' 无需新增MCP服务(已有服务已足够)');
583
+ } else {
584
+ for (const mcp of rec.newMcpServices) {
585
+ console.log(` - ${mcp.name} (${mcp.type}) - 匹配度: ${mcp.matchScore}`);
586
+ }
587
+ }
588
+
589
+ console.log('\n📋 推荐新增规则:');
590
+ if (rec.newRules.length === 0) {
591
+ console.log(' 无需新增规则(已有规则已足够)');
592
+ } else {
593
+ for (const rule of rec.newRules) {
594
+ console.log(` - ${rule.name} [${rule.level}] - ${rule.description}`);
595
+ }
596
+ }
597
+
598
+ console.log('\n👤 推荐Agent配置:');
599
+ if (rec.agentProfile) {
600
+ console.log(` ${rec.agentProfile.name}`);
601
+ console.log(` MCP: ${rec.agentProfile.recommendedMcp.join(', ')}`);
602
+ console.log(` 规则: ${rec.agentProfile.recommendedRules.join(', ')}`);
603
+ } else {
604
+ console.log(' 无Agent推荐');
605
+ }
606
+
607
+ console.log('\n===========================================');
608
+ return recommendations;
609
+ }
610
+
611
+ /**
612
+ * 对接决策引擎,获取最优配置决策
613
+ */
614
+ getDecisionEngineResult() {
615
+ try {
616
+ const { DecisionEngine } = require('./decision-engine/index.js');
617
+ const { KnowledgeBase } = require('./knowledge-base/index.js');
618
+
619
+ const engine = new DecisionEngine();
620
+ const kb = new KnowledgeBase(path.join(__dirname, 'knowledge-base'));
621
+ kb.load();
622
+
623
+ return engine.decide(kb, this.getReport());
624
+ } catch (e) {
625
+ return {
626
+ error: `决策引擎对接失败: ${e.message}`,
627
+ decision: null
628
+ };
629
+ }
630
+ }
631
+
632
+ /**
633
+ * 打印决策引擎结果
634
+ */
635
+ printDecisionResult() {
636
+ const result = this.getDecisionEngineResult();
637
+
638
+ if (result.error) {
639
+ console.log(`[ProjectDetector] ⚠️ ${result.error}`);
640
+ return;
641
+ }
642
+
643
+ const { DecisionEngine } = require('./decision-engine/index.js');
644
+ const engine = new DecisionEngine();
645
+ engine.printDecision(result);
646
+
647
+ // 自动调用渲染引擎生成配置 - 已禁用,避免自动生成YML与MD冲突
648
+ // this.executeRender(result);
649
+
650
+ return result;
651
+ }
652
+
653
+ /**
654
+ * 执行渲染引擎,生成配置
655
+ */
656
+ executeRender(decisionResult) {
657
+ try {
658
+ const { RenderEngine } = require('./render-engine/index.js');
659
+ const renderEngine = new RenderEngine({ outputDir: this.projectRoot });
660
+ renderEngine.render(decisionResult);
661
+ } catch (e) {
662
+ console.log(`[ProjectDetector] ⚠️ 渲染引擎调用失败: ${e.message}`);
663
+ }
664
+ }
665
+ }
666
+
667
+ // 导出
668
+ module.exports = { ProjectDetector };
669
+
670
+ // CLI入口
671
+ if (require.main === module) {
672
+ const args = process.argv.slice(2);
673
+ let projectRoot;
674
+
675
+ if (args.length === 0) {
676
+ projectRoot = process.cwd();
677
+ console.log('[ProjectDetector] 未指定项目路径,使用当前目录:', projectRoot);
678
+ } else if (args[0] && typeof args[0] === 'string' && args[0].trim() !== '') {
679
+ projectRoot = path.resolve(args[0].trim());
680
+ } else {
681
+ console.error('[ProjectDetector] 错误: 无效的项目路径参数');
682
+ console.log('用法: node project-detector.js [项目路径]');
683
+ process.exit(1);
684
+ }
685
+
686
+ try {
687
+ const detector = new ProjectDetector(projectRoot);
688
+ const report = detector.scan();
689
+
690
+ console.log('\n========== 探测报告 ==========');
691
+ console.log(JSON.stringify(report, null, 2));
692
+
693
+ console.log('\n========== 目录拓扑树 ==========');
694
+ console.log(report.topologyTree);
695
+
696
+ if (report.fileErrors.length > 0) {
697
+ console.log('\n========== 文件异常日志 ==========');
698
+ console.log(JSON.stringify(report.fileErrors, null, 2));
699
+ }
700
+
701
+ // 输出知识库推荐
702
+ detector.printKnowledgeRecommendations();
703
+
704
+ // 输出决策引擎结果
705
+ detector.printDecisionResult();
706
+ } catch (err) {
707
+ console.error('[ProjectDetector] 扫描失败:', err.message);
708
+ process.exit(1);
709
+ }
710
+ }