pdd-skills 3.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 (261) hide show
  1. package/README.md +1478 -0
  2. package/bin/pdd.js +354 -0
  3. package/config/bpmn-rules.yaml +166 -0
  4. package/config/checkstyle.xml +105 -0
  5. package/config/eslint.config.js +48 -0
  6. package/config/pmd.xml +91 -0
  7. package/config/prd-rules.yaml +113 -0
  8. package/config/ruff.toml +45 -0
  9. package/config/sqlfluff.cfg +82 -0
  10. package/hooks/hook-executor.js +332 -0
  11. package/index.js +43 -0
  12. package/lib/api-routes.js +750 -0
  13. package/lib/api-server.js +408 -0
  14. package/lib/cache/cache-config.js +209 -0
  15. package/lib/cache/system-cache.js +852 -0
  16. package/lib/config-manager.js +373 -0
  17. package/lib/generate.js +528 -0
  18. package/lib/grpc/grpc-routes.js +1134 -0
  19. package/lib/grpc/grpc-server.js +912 -0
  20. package/lib/grpc/proto-definitions.js +1033 -0
  21. package/lib/init.js +172 -0
  22. package/lib/iteration/auto-fixer.js +1025 -0
  23. package/lib/iteration/auto-reviewer.js +923 -0
  24. package/lib/iteration/controller.js +577 -0
  25. package/lib/list.js +130 -0
  26. package/lib/mcp-server.js +548 -0
  27. package/lib/openclaw/api-integration.js +535 -0
  28. package/lib/openclaw/cli-integration.js +567 -0
  29. package/lib/openclaw/data-sync.js +845 -0
  30. package/lib/openclaw/openclaw-adapter.js +783 -0
  31. package/lib/plugin/example-plugins/code-stats/index.js +332 -0
  32. package/lib/plugin/example-plugins/code-stats/plugin.json +1 -0
  33. package/lib/plugin/example-plugins/custom-linter/index.js +472 -0
  34. package/lib/plugin/example-plugins/custom-linter/plugin.json +1 -0
  35. package/lib/plugin/example-plugins/hello-world/index.js +86 -0
  36. package/lib/plugin/example-plugins/hello-world/plugin.json +1 -0
  37. package/lib/plugin/plugin-manager.js +655 -0
  38. package/lib/plugin/plugin-sdk.js +565 -0
  39. package/lib/plugin/sandbox.js +627 -0
  40. package/lib/quality/rules/maintainability.js +418 -0
  41. package/lib/quality/rules/performance.js +498 -0
  42. package/lib/quality/rules/readability.js +441 -0
  43. package/lib/quality/rules/robustness.js +504 -0
  44. package/lib/quality/rules/security.js +444 -0
  45. package/lib/quality/scorer.js +576 -0
  46. package/lib/report.js +669 -0
  47. package/lib/sdk-base.js +301 -0
  48. package/lib/sdk-js.js +446 -0
  49. package/lib/sdk-python/README.md +546 -0
  50. package/lib/sdk-python/examples/basic_usage.py +450 -0
  51. package/lib/sdk-python/pdd_sdk/__init__.py +180 -0
  52. package/lib/sdk-python/pdd_sdk/client.py +1170 -0
  53. package/lib/sdk-python/pdd_sdk/events.py +423 -0
  54. package/lib/sdk-python/pdd_sdk/exceptions.py +158 -0
  55. package/lib/sdk-python/pdd_sdk/models.py +518 -0
  56. package/lib/sdk-python/pdd_sdk/utils.py +759 -0
  57. package/lib/token/budget-alert.js +367 -0
  58. package/lib/token/budget-manager.js +485 -0
  59. package/lib/update.js +54 -0
  60. package/lib/utils/logger.js +88 -0
  61. package/lib/verify.js +741 -0
  62. package/lib/version.js +52 -0
  63. package/lib/vm/README.md +102 -0
  64. package/lib/vm/dashboard/api-routes.js +669 -0
  65. package/lib/vm/dashboard/server.js +391 -0
  66. package/lib/vm/dashboard/sse.js +358 -0
  67. package/lib/vm/dashboard/static/css/dashboard.css +1378 -0
  68. package/lib/vm/dashboard/static/index.html +118 -0
  69. package/lib/vm/dashboard/static/js/app.js +949 -0
  70. package/lib/vm/dashboard/static/js/charts.js +913 -0
  71. package/lib/vm/dashboard/static/js/kanban-view.js +1053 -0
  72. package/lib/vm/dashboard/static/js/pipeline-view.js +463 -0
  73. package/lib/vm/dashboard/static/js/quality-view.js +598 -0
  74. package/lib/vm/dashboard/static/js/system-view.js +1021 -0
  75. package/lib/vm/data-provider.js +1191 -0
  76. package/lib/vm/event-bus.js +402 -0
  77. package/lib/vm/hooks/extract-hook.js +307 -0
  78. package/lib/vm/hooks/generate-hook.js +374 -0
  79. package/lib/vm/hooks/hook-interface.js +458 -0
  80. package/lib/vm/hooks/report-hook.js +331 -0
  81. package/lib/vm/hooks/verify-hook.js +454 -0
  82. package/lib/vm/models.js +1003 -0
  83. package/lib/vm/reconciler.js +855 -0
  84. package/lib/vm/scanner.js +988 -0
  85. package/lib/vm/state-schema.js +955 -0
  86. package/lib/vm/state-store.js +733 -0
  87. package/lib/vm/tui/components/card.js +339 -0
  88. package/lib/vm/tui/components/progress-bar.js +368 -0
  89. package/lib/vm/tui/components/sparkline.js +327 -0
  90. package/lib/vm/tui/components/status-light.js +294 -0
  91. package/lib/vm/tui/components/table.js +370 -0
  92. package/lib/vm/tui/input.js +335 -0
  93. package/lib/vm/tui/renderer.js +548 -0
  94. package/lib/vm/tui/screens/kanban-screen.js +397 -0
  95. package/lib/vm/tui/screens/overview-screen.js +357 -0
  96. package/lib/vm/tui/screens/quality-screen.js +336 -0
  97. package/lib/vm/tui/screens/system-screen.js +379 -0
  98. package/lib/vm/tui/tui.js +805 -0
  99. package/package.json +1 -0
  100. package/scripts/cso-analyzer.js +198 -0
  101. package/scripts/eval-runner.js +359 -0
  102. package/scripts/i18n-checker.js +109 -0
  103. package/scripts/linter/activiti-linter.js +272 -0
  104. package/scripts/linter/prd-linter.js +162 -0
  105. package/scripts/linter/report-generator.js +207 -0
  106. package/scripts/linter/run-linters.js +285 -0
  107. package/scripts/linter/sql-linter.js +166 -0
  108. package/scripts/token-analyzer.js +162 -0
  109. package/scripts/vm-test.js +180 -0
  110. package/skills/core/official-doc-writer/LICENSE +21 -0
  111. package/skills/core/official-doc-writer/README.md +232 -0
  112. package/skills/core/official-doc-writer/SKILL.md +475 -0
  113. package/skills/core/official-doc-writer/_meta.json +1 -0
  114. package/skills/core/official-doc-writer/document_generator.py +580 -0
  115. package/skills/core/official-doc-writer/evals/default-evals.json +1 -0
  116. package/skills/core/official-doc-writer/examples.md +150 -0
  117. package/skills/core/official-doc-writer/fonts/FONTS_LIST.md +45 -0
  118. package/skills/core/official-doc-writer/fonts/README.md +141 -0
  119. package/skills/core/official-doc-writer/fonts/SIMFANG.TTF +0 -0
  120. package/skills/core/official-doc-writer/fonts/SIMHEI.TTF +0 -0
  121. package/skills/core/official-doc-writer/fonts/SIMKAI.TTF +0 -0
  122. package/skills/core/official-doc-writer/fonts/SIMSUN.TTC +0 -0
  123. package/skills/core/official-doc-writer/fonts//346/226/271/346/255/243/345/260/217/346/240/207/345/256/213GBK.TTF +0 -0
  124. package/skills/core/official-doc-writer/references/GBT_9704-2012_/345/205/232/346/224/277/346/234/272/345/205/263/345/205/254/346/226/207/346/240/274/345/274/217.md +422 -0
  125. package/skills/core/official-doc-writer/scripts/__pycache__/generate_official_doc.cpython-313.pyc +0 -0
  126. package/skills/core/official-doc-writer/scripts/dialog_manager.py +564 -0
  127. package/skills/core/official-doc-writer/scripts/generate_official_doc.py +252 -0
  128. package/skills/core/official-doc-writer/scripts/install_fonts.py +390 -0
  129. package/skills/core/official-doc-writer/scripts/smart_prompts.py +363 -0
  130. package/skills/core/pdd-ba/SKILL.md +305 -0
  131. package/skills/core/pdd-ba/_meta.json +1 -0
  132. package/skills/core/pdd-ba/evals/default-evals.json +1 -0
  133. package/skills/core/pdd-code-reviewer/SKILL.md +378 -0
  134. package/skills/core/pdd-code-reviewer/_meta.json +1 -0
  135. package/skills/core/pdd-code-reviewer/evals/default-evals.json +1 -0
  136. package/skills/core/pdd-doc-change/SKILL.md +350 -0
  137. package/skills/core/pdd-doc-change/_meta.json +1 -0
  138. package/skills/core/pdd-doc-change/evals/default-evals.json +1 -0
  139. package/skills/core/pdd-doc-gardener/SKILL.md +248 -0
  140. package/skills/core/pdd-doc-gardener/_meta.json +1 -0
  141. package/skills/core/pdd-doc-gardener/evals/default-evals.json +1 -0
  142. package/skills/core/pdd-entropy-reduction/SKILL.md +360 -0
  143. package/skills/core/pdd-entropy-reduction/_meta.json +1 -0
  144. package/skills/core/pdd-entropy-reduction/evals/default-evals.json +1 -0
  145. package/skills/core/pdd-entropy-reduction/references/entropy-report-template.md +287 -0
  146. package/skills/core/pdd-entropy-reduction/references/golden-principles.md +573 -0
  147. package/skills/core/pdd-entropy-reduction/scripts/entropy_scan.py +712 -0
  148. package/skills/core/pdd-extract-features/SKILL.md +320 -0
  149. package/skills/core/pdd-extract-features/_meta.json +1 -0
  150. package/skills/core/pdd-extract-features/evals/default-evals.json +1 -0
  151. package/skills/core/pdd-generate-spec/SKILL.md +418 -0
  152. package/skills/core/pdd-generate-spec/_meta.json +1 -0
  153. package/skills/core/pdd-generate-spec/evals/default-evals.json +1 -0
  154. package/skills/core/pdd-implement-feature/SKILL.md +332 -0
  155. package/skills/core/pdd-implement-feature/_meta.json +1 -0
  156. package/skills/core/pdd-implement-feature/evals/default-evals.json +1 -0
  157. package/skills/core/pdd-main/SKILL.md +540 -0
  158. package/skills/core/pdd-main/_meta.json +1 -0
  159. package/skills/core/pdd-main/evals/default-evals.json +1 -0
  160. package/skills/core/pdd-main/evals/evals.json +215 -0
  161. package/skills/core/pdd-verify-feature/SKILL.md +474 -0
  162. package/skills/core/pdd-verify-feature/_meta.json +1 -0
  163. package/skills/core/pdd-verify-feature/evals/default-evals.json +1 -0
  164. package/skills/core/pdd-vm/evals/default-evals.json +1 -0
  165. package/skills/core/traffic-accident-assessor/LICENSE +29 -0
  166. package/skills/core/traffic-accident-assessor/SKILL.md +439 -0
  167. package/skills/core/traffic-accident-assessor/evals/evals.json +1 -0
  168. package/skills/core/traffic-accident-assessor/references/accident-types.md +369 -0
  169. package/skills/core/traffic-accident-assessor/references/liability-rules.md +287 -0
  170. package/skills/core/traffic-accident-assessor/references/traffic-laws.md +226 -0
  171. package/skills/core/traffic-accident-assessor/references//351/253/230/345/260/224/345/244/253/350/257/264/346/230/216/344/271/246.pdf +32576 -106
  172. package/skills/core/traffic-accident-assessor/scripts/generate_official_statement.py +588 -0
  173. package/skills/core/traffic-accident-assessor/scripts/generate_report.py +495 -0
  174. package/skills/core/traffic-accident-assessor/scripts/generate_statement.py +528 -0
  175. package/skills/core/traffic-accident-assessor.zip +0 -0
  176. package/skills/entropy/expert-arch-enforcer/SKILL.md +292 -0
  177. package/skills/entropy/expert-arch-enforcer/_meta.json +1 -0
  178. package/skills/entropy/expert-arch-enforcer/evals/default-evals.json +1 -0
  179. package/skills/entropy/expert-auto-refactor/SKILL.md +327 -0
  180. package/skills/entropy/expert-auto-refactor/_meta.json +1 -0
  181. package/skills/entropy/expert-auto-refactor/evals/default-evals.json +1 -0
  182. package/skills/entropy/expert-code-quality/SKILL.md +468 -0
  183. package/skills/entropy/expert-code-quality/_meta.json +1 -0
  184. package/skills/entropy/expert-code-quality/evals/default-evals.json +1 -0
  185. package/skills/entropy/expert-code-quality/evals/evals.json +109 -0
  186. package/skills/entropy/expert-code-quality/references/code-smells.md +605 -0
  187. package/skills/entropy/expert-code-quality/references/design-patterns.md +1111 -0
  188. package/skills/entropy/expert-code-quality/references/refactoring-catalog.md +1281 -0
  189. package/skills/entropy/expert-code-quality/references/solid-principles.md +524 -0
  190. package/skills/entropy/expert-entropy-auditor/SKILL.md +276 -0
  191. package/skills/entropy/expert-entropy-auditor/_meta.json +1 -0
  192. package/skills/entropy/expert-entropy-auditor/evals/default-evals.json +1 -0
  193. package/skills/expert/expert-activiti/SKILL.md +497 -0
  194. package/skills/expert/expert-activiti/_meta.json +1 -0
  195. package/skills/expert/expert-mysql/SKILL.md +832 -0
  196. package/skills/expert/expert-mysql/_meta.json +1 -0
  197. package/skills/expert/expert-performance/SKILL.md +379 -0
  198. package/skills/expert/expert-performance/_meta.json +1 -0
  199. package/skills/expert/expert-performance/evals/default-evals.json +1 -0
  200. package/skills/expert/expert-ruoyi/SKILL.md +472 -0
  201. package/skills/expert/expert-ruoyi/_meta.json +1 -0
  202. package/skills/expert/expert-security/SKILL.md +1341 -0
  203. package/skills/expert/expert-security/_meta.json +1 -0
  204. package/skills/expert/expert-security/evals/default-evals.json +1 -0
  205. package/skills/expert/software-architect/SKILL.md +350 -0
  206. package/skills/expert/software-architect/_meta.json +1 -0
  207. package/skills/expert/software-engineer/SKILL.md +437 -0
  208. package/skills/expert/software-engineer/_meta.json +1 -0
  209. package/skills/expert/software-engineer/architecture.md +130 -0
  210. package/skills/expert/software-engineer/patterns.md +151 -0
  211. package/skills/expert/software-engineer/testing.md +135 -0
  212. package/skills/expert/system-architect/SKILL.md +628 -0
  213. package/skills/expert/system-architect/_meta.json +1 -0
  214. package/skills/expert/system-architect/assets/templates/ARCHITECTURE.md +25 -0
  215. package/skills/expert/system-architect/assets/templates/README.md +44 -0
  216. package/skills/expert/system-architect/references/js-ts-standards.md +18 -0
  217. package/skills/expert/system-architect/references/python-standards.md +19 -0
  218. package/skills/expert/system-architect/references/scaffolding.md +61 -0
  219. package/skills/expert/system-architect/references/security-checklist.md +21 -0
  220. package/skills/openspec/openspec-apply-change/SKILL.md +156 -0
  221. package/skills/openspec/openspec-apply-change/_meta.json +1 -0
  222. package/skills/openspec/openspec-archive-change/SKILL.md +114 -0
  223. package/skills/openspec/openspec-archive-change/_meta.json +1 -0
  224. package/skills/openspec/openspec-bulk-archive-change/SKILL.md +246 -0
  225. package/skills/openspec/openspec-bulk-archive-change/_meta.json +1 -0
  226. package/skills/openspec/openspec-continue-change/SKILL.md +118 -0
  227. package/skills/openspec/openspec-continue-change/_meta.json +1 -0
  228. package/skills/openspec/openspec-explore/SKILL.md +288 -0
  229. package/skills/openspec/openspec-explore/_meta.json +1 -0
  230. package/skills/openspec/openspec-ff-change/SKILL.md +101 -0
  231. package/skills/openspec/openspec-ff-change/_meta.json +1 -0
  232. package/skills/openspec/openspec-new-change/SKILL.md +74 -0
  233. package/skills/openspec/openspec-new-change/_meta.json +1 -0
  234. package/skills/openspec/openspec-onboard/SKILL.md +554 -0
  235. package/skills/openspec/openspec-onboard/_meta.json +1 -0
  236. package/skills/openspec/openspec-sync-specs/SKILL.md +138 -0
  237. package/skills/openspec/openspec-sync-specs/_meta.json +1 -0
  238. package/skills/openspec/openspec-verify-change/SKILL.md +168 -0
  239. package/skills/openspec/openspec-verify-change/_meta.json +1 -0
  240. package/skills/pr/pdd-multi-review/SKILL.md +534 -0
  241. package/skills/pr/pdd-multi-review/_meta.json +1 -0
  242. package/skills/pr/pdd-pr-batch/SKILL.md +303 -0
  243. package/skills/pr/pdd-pr-batch/_meta.json +1 -0
  244. package/skills/pr/pdd-pr-create/SKILL.md +344 -0
  245. package/skills/pr/pdd-pr-create/_meta.json +1 -0
  246. package/skills/pr/pdd-pr-merge/SKILL.md +286 -0
  247. package/skills/pr/pdd-pr-merge/_meta.json +1 -0
  248. package/skills/pr/pdd-pr-review/SKILL.md +217 -0
  249. package/skills/pr/pdd-pr-review/_meta.json +1 -0
  250. package/skills/pr/pdd-task-manager/SKILL.md +636 -0
  251. package/skills/pr/pdd-task-manager/_meta.json +1 -0
  252. package/skills/pr/pdd-template-engine/SKILL.md +306 -0
  253. package/skills/pr/pdd-template-engine/_meta.json +1 -0
  254. package/templates/behavior-shaping/iron-law-template.md +87 -0
  255. package/templates/behavior-shaping/rationalization-template.md +62 -0
  256. package/templates/behavior-shaping/red-flags-template.md +70 -0
  257. package/templates/bilingual-template.md +139 -0
  258. package/templates/config/default.yaml +47 -0
  259. package/templates/project/default/README.md +31 -0
  260. package/templates/project/frontend/README.md +46 -0
  261. package/templates/project/java/README.md +48 -0
@@ -0,0 +1,923 @@
1
+ // lib/iteration/auto-reviewer.js - 自动审查模块
2
+ // 集成代码审查逻辑,自动化执行多维度代码质量检测
3
+ // 支持静态分析、规范检查、安全扫描、性能评估
4
+
5
+ import chalk from 'chalk';
6
+ import crypto from 'crypto';
7
+
8
+ /**
9
+ * 问题类别枚举
10
+ */
11
+ export const IssueCategory = {
12
+ SYNTAX: 'syntax', // 语法错误
13
+ LOGIC: 'logic', // 逻辑错误
14
+ SECURITY: 'security', // 安全问题
15
+ PERFORMANCE: 'performance', // 性能问题
16
+ STYLE: 'style', // 代码风格
17
+ SPEC_VIOLATION: 'spec_violation', // 规格违反
18
+ BEST_PRACTICE: 'best_practice' // 最佳实践
19
+ };
20
+
21
+ /**
22
+ * 问题严重级别(与controller.js保持一致)
23
+ */
24
+ export const Severity = {
25
+ CRITICAL: 'critical', // 阻塞性问题,必须修复
26
+ MAJOR: 'major', // 重要问题,强烈建议修复
27
+ MINOR: 'minor', // 次要问题,建议修复
28
+ INFO: 'info' // 信息性提示
29
+ };
30
+
31
+ /**
32
+ * 单个审查发现的问题
33
+ */
34
+ class Issue {
35
+ /**
36
+ * @param {Object} params
37
+ * @param {string} params.id - 唯一标识
38
+ * @param {string} params.category - 问题类别
39
+ * @param {string} params.severity - 严重级别
40
+ * @param {string} params.message - 问题描述
41
+ * @param {number} [params.line] - 所在行号
42
+ * @param {number} [params.column] - 所在列号
43
+ * @param {string} [params.codeSnippet] - 相关代码片段
44
+ * @param {string} [params.rule] - 规则名称
45
+ * @param {string} [params.suggestion] - 修复建议
46
+ * @param {boolean} [params.fixable] - 是否可自动修复
47
+ */
48
+ constructor(params) {
49
+ this.id = params.id || crypto.randomUUID();
50
+ this.category = params.category;
51
+ this.severity = params.severity;
52
+ this.message = params.message;
53
+ this.line = params.line || null;
54
+ this.column = params.column || null;
55
+ this.codeSnippet = params.codeSnippet || null;
56
+ this.rule = params.rule || null;
57
+ this.suggestion = params.suggestion || null;
58
+ this.fixable = params.fixable !== undefined ? params.fixable : true;
59
+ this.timestamp = Date.now();
60
+ }
61
+
62
+ toJSON() {
63
+ return {
64
+ id: this.id,
65
+ category: this.category,
66
+ severity: this.severity,
67
+ message: this.message,
68
+ line: this.line,
69
+ column: this.column,
70
+ codeSnippet: this.codeSnippet,
71
+ rule: this.rule,
72
+ suggestion: this.suggestion,
73
+ fixable: this.fixable
74
+ };
75
+ }
76
+ }
77
+
78
+ /**
79
+ * 审查结果
80
+ */
81
+ class ReviewResult {
82
+ constructor() {
83
+ this.issues = [];
84
+ this.metrics = {};
85
+ this.codeHash = null;
86
+ this.reviewTime = 0;
87
+ this.passed = false;
88
+ }
89
+
90
+ addIssue(issue) {
91
+ this.issues.push(issue);
92
+ }
93
+
94
+ setMetrics(metrics) {
95
+ this.metrics = metrics;
96
+ }
97
+
98
+ getSummary() {
99
+ const bySeverity = {};
100
+ for (const sev of Object.values(Severity)) {
101
+ bySeverity[sev] = this.issues.filter(i => i.severity === sev).length;
102
+ }
103
+ const byCategory = {};
104
+ for (const cat of Object.values(IssueCategory)) {
105
+ byCategory[cat] = this.issues.filter(i => i.category === cat).length;
106
+ }
107
+ return {
108
+ totalIssues: this.issues.length,
109
+ bySeverity,
110
+ byCategory,
111
+ passed: this.issues.filter(i => i.severity === Severity.CRITICAL).length === 0,
112
+ criticalCount: bySeverity[Severity.CRITICAL] || 0,
113
+ majorCount: bySeverity[Severity.MAJOR] || 0
114
+ };
115
+ }
116
+ }
117
+
118
+ /**
119
+ * AutoReviewer - 自动代码审查器
120
+ *
121
+ * 审查维度:
122
+ * 1. 静态分析 - 语法、结构、复杂度
123
+ * 2. 安全扫描 - 注入、XSS、敏感信息泄露
124
+ * 3. 性能检测 - 循环、内存泄漏风险
125
+ * 4. 规范检查 - 命名、格式、注释
126
+ * 5. 规格验证 - 与开发规格的一致性
127
+ *
128
+ * 设计原则:
129
+ * - 不执行代码,只做静态分析
130
+ * - 可配置规则集和阈值
131
+ * - 输出结构化的问题报告
132
+ * - 支持增量审查(基于hash)
133
+ */
134
+ export class AutoReviewer {
135
+ /**
136
+ * @param {Object} config - 配置选项
137
+ * @param {boolean} config.enableSecurityScan - 启用安全扫描 (默认true)
138
+ * @param {boolean} config.enablePerformanceCheck - 启用性能检查 (默认true)
139
+ * @param {boolean} config.enableStyleCheck - 启用风格检查 (默认true)
140
+ * @param {number} config.maxComplexity - 最大圈复杂度阈值 (默认15)
141
+ * @param {number} config.maxLineLength - 最大行长度 (默认120)
142
+ * @param {number} config.maxFunctionLength - 函数最大行数 (默认50)
143
+ * @param {Array<string>} config.disabledRules - 禁用的规则列表
144
+ * @param {Object} config.customRules - 自定义规则
145
+ */
146
+ constructor(config = {}) {
147
+ this.config = {
148
+ enableSecurityScan: config.enableSecurityScan !== false,
149
+ enablePerformanceCheck: config.enablePerformanceCheck !== false,
150
+ enableStyleCheck: config.enableStyleCheck !== false,
151
+ maxComplexity: config.maxComplexity || 15,
152
+ maxLineLength: config.maxLineLength || 120,
153
+ maxFunctionLength: config.maxFunctionLength || 50,
154
+ disabledRules: new Set(config.disabledRules || []),
155
+ customRules: config.customRules || {}
156
+ };
157
+
158
+ // 规则注册表
159
+ this.rules = this._initRules();
160
+
161
+ // 统计信息
162
+ this.stats = {
163
+ totalReviews: 0,
164
+ totalIssuesFound: 0,
165
+ avgReviewTime: 0
166
+ };
167
+ }
168
+
169
+ /**
170
+ * 执行代码审查
171
+ *
172
+ * @param {string} code - 待审查的代码
173
+ * @param {Object} spec - 开发规格/验收标准 (可选)
174
+ * @returns {Promise<ReviewResult>} 审查结果
175
+ */
176
+ async review(code, spec = {}) {
177
+ const startTime = Date.now();
178
+ const result = new ReviewResult();
179
+
180
+ console.log(chalk.blue(' 🔍 AutoReviewer 开始审查...'));
181
+
182
+ try {
183
+ // 计算代码哈希(用于增量审查)
184
+ result.codeHash = this._computeHash(code);
185
+
186
+ // 执行各维度审查
187
+ await this._runAllChecks(code, spec, result);
188
+
189
+ // 计算代码指标
190
+ result.setMetrics(this._analyzeMetrics(code));
191
+
192
+ // 判断是否通过
193
+ const summary = result.getSummary();
194
+ result.passed = summary.criticalCount === 0;
195
+
196
+ // 更新统计
197
+ result.reviewTime = Date.now() - startTime;
198
+ this._updateStats(result);
199
+
200
+ console.log(` ✅ 审查完成: 发现 ${summary.totalIssues} 个问题 (${summary.criticalCount} 严重, ${summary.majorCount} 重要)`);
201
+
202
+ return result;
203
+
204
+ } catch (error) {
205
+ console.error(chalk.red(` ❌ 审查过程出错: ${error.message}`));
206
+ result.addIssue(new Issue({
207
+ category: IssueCategory.SYNTAX,
208
+ severity: Severity.CRITICAL,
209
+ message: `审查引擎异常: ${error.message}`,
210
+ fixable: false
211
+ }));
212
+ return result;
213
+ }
214
+ }
215
+
216
+ /**
217
+ * 执行所有检查项
218
+ * @private
219
+ */
220
+ async _runAllChecks(code, spec, result) {
221
+ const lines = code.split('\n');
222
+
223
+ // 1. 基础语法/结构检查
224
+ this._checkBasicStructure(code, lines, result);
225
+
226
+ // 2. 复杂度分析
227
+ this._checkComplexity(code, lines, result);
228
+
229
+ // 3. 安全扫描
230
+ if (this.config.enableSecurityScan) {
231
+ this._checkSecurity(code, lines, result);
232
+ }
233
+
234
+ // 4. 性能检查
235
+ if (this.config.enablePerformanceCheck) {
236
+ this._checkPerformance(code, lines, result);
237
+ }
238
+
239
+ // 5. 代码风格检查
240
+ if (this.config.enableStyleCheck) {
241
+ this._checkStyle(code, lines, result);
242
+ }
243
+
244
+ // 6. 规格一致性检查
245
+ if (spec && Object.keys(spec).length > 0) {
246
+ this._checkSpecCompliance(code, spec, result);
247
+ }
248
+
249
+ // 7. 自定义规则检查
250
+ this._checkCustomRules(code, lines, result);
251
+ }
252
+
253
+ // ==================== 检查规则实现 ====================
254
+
255
+ /**
256
+ * 基础结构检查
257
+ * @private
258
+ */
259
+ _checkBasicStructure(code, lines, result) {
260
+ // 检查空文件
261
+ if (!code.trim()) {
262
+ result.addIssue(new Issue({
263
+ category: IssueCategory.SYNTAX,
264
+ severity: Severity.CRITICAL,
265
+ message: '文件为空或仅包含空白字符',
266
+ rule: 'no-empty-file',
267
+ fixable: false
268
+ }));
269
+ return;
270
+ }
271
+
272
+ // 检查过长的单行代码
273
+ lines.forEach((line, idx) => {
274
+ if (line.length > this.config.maxLineLength) {
275
+ result.addIssue(new Issue({
276
+ category: IssueCategory.STYLE,
277
+ severity: Severity.MINOR,
278
+ message: `行过长 (${line.length}/${this.config.maxLineLength}字符)`,
279
+ line: idx + 1,
280
+ column: this.config.maxLineLength + 1,
281
+ codeSnippet: line.substring(0, 100) + '...',
282
+ rule: 'max-line-length',
283
+ suggestion: `将此行拆分为多行,每行不超过${this.config.maxLineLength}字符`
284
+ }));
285
+ }
286
+ });
287
+
288
+ // 检查文件总行数
289
+ if (lines.length > 1000) {
290
+ result.addIssue(new Issue({
291
+ category: IssueCategory.BEST_PRACTICE,
292
+ severity: Severity.INFO,
293
+ message: `文件较大 (${lines.length}行),建议考虑拆分`,
294
+ rule: 'file-length',
295
+ suggestion: '将大文件拆分为多个模块'
296
+ }));
297
+ }
298
+
299
+ // 检查是否有console.log残留(生产代码应移除)
300
+ const consoleLogPattern = /console\.(log|debug|info)\s*\(/g;
301
+ let match;
302
+ while ((match = consoleLogPattern.exec(code)) !== null) {
303
+ const lineNum = this._getLineNumber(code, match.index);
304
+ result.addIssue(new Issue({
305
+ category: IssueCategory.BEST_PRACTICE,
306
+ severity: Severity.MINOR,
307
+ message: '发现console.log/debug语句,建议在生产环境移除',
308
+ line: lineNum,
309
+ codeSnippet: code.substring(match.index, match.index + 50),
310
+ rule: 'no-console-log',
311
+ suggestion: '使用日志框架替代或在构建时移除debug语句'
312
+ }));
313
+ }
314
+ }
315
+
316
+ /**
317
+ * 复杂度分析
318
+ * @private
319
+ */
320
+ _checkComplexity(code, lines, result) {
321
+ // 检测嵌套层级过深
322
+ const deepNestPattern = /^(\s{2,})/gm;
323
+ let maxIndent = 0;
324
+ let match;
325
+ while ((match = deepNestPattern.exec(code)) !== null) {
326
+ const indent = match[1].length;
327
+ if (indent > maxIndent) maxIndent = indent;
328
+ }
329
+
330
+ // 估算最大嵌套深度(假设2空格缩进)
331
+ const estimatedDepth = Math.floor(maxIndent / 2);
332
+ if (estimatedDepth > 5) {
333
+ result.addIssue(new Issue({
334
+ category: IssueCategory.LOGIC,
335
+ severity: Severity.MAJOR,
336
+ message: `代码嵌套层级过深 (约${estimatedDepth}层),建议重构以降低复杂度`,
337
+ rule: 'max-nesting-depth',
338
+ suggestion: '使用早返回(early return)、提取方法、策略模式等方式降低嵌套'
339
+ }));
340
+ }
341
+
342
+ // 检测函数长度
343
+ const functionPattern = /(async\s+)?function\s+\w+|=>\s*\{|^\s*(?:const|let|var)\s+\w+\s*=\s*(?:async\s*)?\(/gm;
344
+ let funcMatch;
345
+ const functions = [];
346
+
347
+ while ((funcMatch = functionPattern.exec(code)) !== null) {
348
+ functions.push({ start: funcMatch.index, name: funcMatch[0].trim() });
349
+ }
350
+
351
+ // 简单估算函数范围(不精确但有效)
352
+ for (let i = 0; i < functions.length; i++) {
353
+ const func = functions[i];
354
+ const nextFuncStart = functions[i + 1]?.start || code.length;
355
+ const funcCode = code.substring(func.start, nextFuncStart);
356
+ const funcLines = funcCode.split('\n').length;
357
+
358
+ if (funcLines > this.config.maxFunctionLength) {
359
+ const lineNum = this._getLineNumber(code, func.start);
360
+ result.addIssue(new Issue({
361
+ category: IssueCategory.LOGIC,
362
+ severity: Severity.MAJOR,
363
+ message: `函数/块过长 (${funcLines}行/${this.config.maxFunctionLength}行)`,
364
+ line: lineNum,
365
+ rule: 'max-function-length',
366
+ suggestion: `将长函数拆分为多个职责单一的小函数(建议不超过${this.config.maxFunctionLength}行)`
367
+ }));
368
+ }
369
+ }
370
+
371
+ // 检测重复代码(简单模式:重复3次以上的相同行)
372
+ const lineFrequency = {};
373
+ lines.forEach(line => {
374
+ const trimmed = line.trim();
375
+ if (trimmed.length > 10) { // 忽略短行
376
+ lineFrequency[trimmed] = (lineFrequency[trimmed] || 0) + 1;
377
+ }
378
+ });
379
+
380
+ for (const [lineText, count] of Object.entries(lineFrequency)) {
381
+ if (count >= 3) {
382
+ result.addIssue(new Issue({
383
+ category: IssueCategory.BEST_PRACTICE,
384
+ severity: Severity.MINOR,
385
+ message: `发现重复代码片段 (出现${count}次)`,
386
+ codeSnippet: lineText.substring(0, 80),
387
+ rule: 'no-duplicated-code',
388
+ suggestion: '将重复代码提取为共享函数或常量'
389
+ }));
390
+ break; // 只报告一次
391
+ }
392
+ }
393
+ }
394
+
395
+ /**
396
+ * 安全扫描
397
+ * @private
398
+ */
399
+ _checkSecurity(code, lines, result) {
400
+ // SQL注入风险
401
+ const sqlPatterns = [
402
+ { pattern: /\$\{.*\}\s*(?:INTO|FROM|WHERE|SELECT|INSERT|UPDATE|DELETE)/i, name: 'sql-injection-template' },
403
+ { pattern: /(?:query|execute)\s*\(\s*["'].*\+.*["']/i, name: 'sql-injection-concat' },
404
+ { pattern: /(?:query|execute)\s*\(\s*[^,]+,\s*\[/i, name: 'sql-injection-array' }
405
+ ];
406
+
407
+ for (const { pattern, name } of sqlPatterns) {
408
+ let match;
409
+ while ((match = pattern.exec(code)) !== null) {
410
+ const lineNum = this._getLineNumber(code, match.index);
411
+ result.addIssue(new Issue({
412
+ category: IssueCategory.SECURITY,
413
+ severity: Severity.CRITICAL,
414
+ message: `潜在的SQL注入风险 (${name})`,
415
+ line: lineNum,
416
+ codeSnippet: code.substring(match.index, match.index + 60),
417
+ rule: name,
418
+ suggestion: '使用参数化查询(prepared statement)替代字符串拼接'
419
+ }));
420
+ }
421
+ }
422
+
423
+ // XSS风险
424
+ const xssPatterns = [
425
+ { pattern: /innerHTML\s*=\s*(?!['"])/, name: 'xss-innerHTML' },
426
+ { pattern: /document\.write\s*\(/, name: 'xss-document-write' },
427
+ { pattern: /\.html\s*\([^)]*\+/, name: 'xss-jquery-html-concat' }
428
+ ];
429
+
430
+ for (const { pattern, name } of xssPatterns) {
431
+ let match;
432
+ while ((match = pattern.exec(code)) !== null) {
433
+ const lineNum = this._getLineNumber(code, match.index);
434
+ result.addIssue(new Issue({
435
+ category: IssueCategory.SECURITY,
436
+ severity: Severity.MAJOR,
437
+ message: `潜在的XSS跨站脚本攻击风险 (${name})`,
438
+ line: lineNum,
439
+ codeSnippet: code.substring(match.index, match.index + 50),
440
+ rule: name,
441
+ suggestion: '使用textContent或DOMPurify等安全库处理用户输入'
442
+ }));
443
+ }
444
+ }
445
+
446
+ // 敏感信息硬编码
447
+ const sensitivePatterns = [
448
+ { pattern: /(?:password|passwd|pwd)\s*[:=]\s*["'][^"']+["']/i, name: 'hardcoded-password' },
449
+ { pattern: /(?:api[_-]?key|apikey)\s*[:=]\s*["'][^"']+["']/i, name: 'hardcoded-api-key' },
450
+ { pattern: /(?:secret|token)\s*[:=]\s*["'][^"']+["']/i, name: 'hardcoded-secret' },
451
+ { pattern: /(?:mongodb|mysql|postgres|redis):\/\/[^\/\s]+:[^@]+@/i, name: 'hardcoded-connection-string' }
452
+ ];
453
+
454
+ for (const { pattern, name } of sensitivePatterns) {
455
+ let match;
456
+ while ((match = pattern.exec(code)) !== null) {
457
+ const lineNum = this._getLineNumber(code, match.index);
458
+ result.addIssue(new Issue({
459
+ category: IssueCategory.SECURITY,
460
+ severity: Severity.CRITICAL,
461
+ message: `敏感信息硬编码: ${name}`,
462
+ line: lineNum,
463
+ codeSnippet: code.substring(match.index, Math.min(match.index + 70, code.length)),
464
+ rule: name,
465
+ suggestion: '使用环境变量或密钥管理服务存储敏感信息'
466
+ }));
467
+ }
468
+ }
469
+
470
+ // eval()使用
471
+ const evalPattern = /\beval\s*\(/g;
472
+ let evalMatch;
473
+ while ((evalMatch = evalPattern.exec(code)) !== null) {
474
+ const lineNum = this._getLineNumber(code, evalMatch.index);
475
+ result.addIssue(new Issue({
476
+ category: IssueCategory.SECURITY,
477
+ severity: Severity.CRITICAL,
478
+ message: '使用了eval(),存在代码注入风险',
479
+ line: lineNum,
480
+ codeSnippet: code.substring(evalMatch.index, evalMatch.index + 30),
481
+ rule: 'no-eval',
482
+ suggestion: '避免使用eval(),改用JSON.parse()或其他安全的替代方案'
483
+ }));
484
+ }
485
+ }
486
+
487
+ /**
488
+ * 性能检查
489
+ * @private
490
+ */
491
+ _checkPerformance(code, lines, result) {
492
+ // 同步循环中的耗时操作
493
+ const syncHeavyPattern = /for\s*\([^)]*\)\s*\{[\s\S]{0,200}(?:readFileSync|writeFileSync|execSync)/g;
494
+ let match;
495
+ while ((match = syncHeavyPattern.exec(code)) !== null) {
496
+ const lineNum = this._getLineNumber(code, match.index);
497
+ result.addIssue(new Issue({
498
+ category: IssueCategory.PERFORMANCE,
499
+ severity: Severity.MAJOR,
500
+ message: '在循环中使用同步I/O操作可能导致性能瓶颈',
501
+ line: lineNum,
502
+ rule: 'no-sync-in-loop',
503
+ suggestion: '使用异步版本(readFile/writeFile)或将I/O操作移到循环外部'
504
+ }));
505
+ }
506
+
507
+ // 大数组操作警告
508
+ const largeArrayPattern = /\.(?:map|filter|reduce|forEach|sort)\s*\((?!\s*(?:e?\s*=>|function))/g;
509
+ while ((match = largeArrayPattern.exec(code)) !== null) {
510
+ // 简单启发式:如果上下文提到大数据
511
+ const context = code.substring(Math.max(0, match.index - 100), match.index + 50);
512
+ if (/large|big|huge|batch|all\s*\w+|every/i.test(context)) {
513
+ const lineNum = this._getLineNumber(code, match.index);
514
+ result.addIssue(new Issue({
515
+ category: IssueCategory.PERFORMANCE,
516
+ severity: Severity.MINOR,
517
+ message: '对可能的大数组进行链式操作,注意性能影响',
518
+ line: lineNum,
519
+ rule: 'large-array-chain',
520
+ suggestion: '考虑使用for循环或流式处理替代链式调用'
521
+ }));
522
+ }
523
+ }
524
+
525
+ // 内存泄漏风险:事件监听器未清理
526
+ const eventListenerPattern = /addEventListener\s*\(/g;
527
+ let listenerCount = 0;
528
+ while ((match = eventListenerPattern.exec(code)) !== null) {
529
+ listenerCount++;
530
+ }
531
+
532
+ const removeListenerPattern = /removeEventListener\s*\(/g;
533
+ let removeCount = 0;
534
+ while ((match = removeListenerPattern.exec(code)) !== null) {
535
+ removeCount++;
536
+ }
537
+
538
+ if (listenerCount > removeCount && listenerCount > 3) {
539
+ result.addIssue(new Issue({
540
+ category: IssueCategory.PERFORMANCE,
541
+ severity: Severity.MAJOR,
542
+ message: `潜在内存泄漏: ${listenerCount}个addEventListener但只有${removeCount}个removeEventListener`,
543
+ rule: 'event-listener-leak',
544
+ suggestion: '确保在组件卸载时清理所有事件监听器'
545
+ }));
546
+ }
547
+
548
+ // N+1查询模式检测(简单启发式)
549
+ const queryInLoopPattern = /(?:find|query|select|get|fetch)\s*\([^)]*\)[^;]*;/g;
550
+ let queryMatches = [];
551
+ while ((match = queryInLoopPattern.exec(code)) !== null) {
552
+ queryMatches.push({ index: match.index, text: match[0] });
553
+ }
554
+
555
+ if (queryMatches.length >= 3) {
556
+ // 检查是否在相近位置(可能在同一循环内)
557
+ const positions = queryMatches.map(m => m.index);
558
+ const avgGap = positions.slice(1).reduce((sum, p, i) => sum + (p - positions[i]), 0) / (positions.length - 1);
559
+ if (avgGap < 500) { // 相对集中
560
+ const lineNum = this._getLineNumber(code, queryMatches[0].index);
561
+ result.addIssue(new Issue({
562
+ category: IssueCategory.PERFORMANCE,
563
+ severity: Severity.MAJOR,
564
+ message: `可能的N+1查询问题: 在小范围内发现${queryMatches.length}次数据库查询`,
565
+ line: lineNum,
566
+ rule: 'n-plus-one-query',
567
+ suggestion: '使用批量查询(eager loading)或JOIN替代循环中的单独查询'
568
+ }));
569
+ }
570
+ }
571
+ }
572
+
573
+ /**
574
+ * 代码风格检查
575
+ * @private
576
+ */
577
+ _checkStyle(code, lines, result) {
578
+ // 检查缺少分号的语句(JavaScript特定)
579
+ const noSemicolonPattern = /^\s*(?:let|const|var|return|throw)\s+[^;=]*$/gm;
580
+ let match;
581
+ while ((match = noSemicolonPattern.exec(code)) !== null) {
582
+ const nextChar = code.charAt(match.index + match[0].length);
583
+ if (nextChar !== '\n' && nextChar !== ';' && nextChar !== '}') continue;
584
+
585
+ const lineNum = this._getLineNumber(code, match.index);
586
+ result.addIssue(new Issue({
587
+ category: IssueCategory.STYLE,
588
+ severity: Severity.INFO,
589
+ message: '语句末尾缺少分号(如使用分号风格)',
590
+ line: lineNum,
591
+ codeSnippet: match[0].trim(),
592
+ rule: 'semi-style',
593
+ suggestion: '确保团队代码风格一致(统一使用或不使用分号)'
594
+ }));
595
+ }
596
+
597
+ // 检查TODO/FIXME/HACK注释
598
+ const todoPattern = /\/\/\s*(TODO|FIXME|HACK|XXX|BUG):?\s*.*/gi;
599
+ while ((match = todoPattern.exec(code)) !== null) {
600
+ const lineNum = this._getLineNumber(code, match.index);
601
+ const type = match[1].toUpperCase();
602
+ const severity = type === 'FIXME' || type === 'BUG' ? Severity.MAJOR :
603
+ type === 'HACK' || type === 'XXX' ? Severity.MAJOR : Severity.MINOR;
604
+
605
+ result.addIssue(new Issue({
606
+ category: IssueCategory.BEST_PRACTICE,
607
+ severity: severity,
608
+ message: `代码标记: ${match[0].trim()}`,
609
+ line: lineNum,
610
+ rule: `marked-as-${type.toLowerCase()}`,
611
+ suggestion: `${type === 'TODO' ? '跟踪并完成待办事项' : type === 'FIXME' ? '尽快修复已知问题' : '重构或清理hack代码'}`
612
+ }));
613
+ }
614
+
615
+ // 检查未使用的import(简单启发式)
616
+ const importPattern = /^import\s+.+?\s+from\s+['"][^'"]+['"]/gm;
617
+ const imports = [];
618
+ while ((match = importPattern.exec(code)) !== null) {
619
+ imports.push({
620
+ full: match[0],
621
+ names: match[0].match(/\{([^}]+)\}|(\w+)/)?.[1] || match[0].match(/\bimport\s+(\w+)/)?.[1]
622
+ });
623
+ }
624
+
625
+ // 简单检查:如果import了很多但代码很短,可能有未使用的导入
626
+ if (imports.length > 5 && lines.length < 30) {
627
+ result.addIssue(new Issue({
628
+ category: IssueCategory.STYLE,
629
+ severity: Severity.INFO,
630
+ message: `导入了${imports.length}个模块但文件较短(${lines.length}行),可能存在未使用的导入`,
631
+ rule: 'unused-imports',
632
+ suggestion: '检查并移除未使用的import语句'
633
+ }));
634
+ }
635
+
636
+ // 检查魔法数字
637
+ const magicNumberPattern = /(?<![$\w])(?:\d{2,}|0[xX][\da-fA-F]{4,})(?![\d.a-fA-F])/g;
638
+ const excludedContext = /(?:version|port|status|code|type|level|index|count|size|length|max|min|limit|offset|page|timeout|retry|delay|buffer|capacity|=|\(|\[|,|\s)(?:\d{2,})/;
639
+ let magicMatch;
640
+ let magicCount = 0;
641
+ while ((magicMatch = magicNumberPattern.exec(code)) !== null) {
642
+ const contextBefore = code.substring(Math.max(0, magicMatch.index - 20), magicMatch.index);
643
+ if (!excludedContext.test(contextBefore)) {
644
+ magicCount++;
645
+ if (magicCount <= 3) {
646
+ const lineNum = this._getLineNumber(code, magicMatch.index);
647
+ result.addIssue(new Issue({
648
+ category: IssueCategory.BEST_PRACTICE,
649
+ severity: Severity.MINOR,
650
+ message: `魔法数字: ${magicMatch[0]} (建议提取为命名常量)`,
651
+ line: lineNum,
652
+ codeSnippet: code.substring(magicMatch.index - 10, magicMatch.index + 20),
653
+ rule: 'no-magic-numbers',
654
+ suggestion: `将数字 ${magicMatch[0]} 提取为有意义的命名常量`
655
+ }));
656
+ }
657
+ }
658
+ }
659
+ }
660
+
661
+ /**
662
+ * 规格一致性检查
663
+ * @private
664
+ */
665
+ _checkSpecCompliance(code, spec, result) {
666
+ // 检查必需的函数是否存在
667
+ if (spec.requiredFunctions && Array.isArray(spec.requiredFunctions)) {
668
+ for (const funcName of spec.requiredFunctions) {
669
+ const funcPattern = new RegExp(`(?:function\\s+${funcName}|${funcName}\\s*[=(]|export\\s+(?:async\\s+)?function\\s+${funcName})`);
670
+ if (!funcPattern.test(code)) {
671
+ result.addIssue(new Issue({
672
+ category: IssueCategory.SPEC_VIOLATION,
673
+ severity: Severity.CRITICAL,
674
+ message: `缺少必需的函数: ${funcName}`,
675
+ rule: 'required-function-missing',
676
+ suggestion: `按照规格要求实现 ${funcName} 函数`
677
+ }));
678
+ }
679
+ }
680
+ }
681
+
682
+ // 检查必需的导出
683
+ if (spec.exports && Array.isArray(spec.exports)) {
684
+ for (const exportName of spec.exports) {
685
+ const exportPattern = new RegExp(`export\\s+(?:const|let|var|function|class|default)\\s+${exportName}`);
686
+ if (!exportPattern.test(code)) {
687
+ result.addIssue(new Issue({
688
+ category: IssueCategory.SPEC_VIOLATION,
689
+ severity: Severity.MAJOR,
690
+ message: `缺少必需的导出: ${exportName}`,
691
+ rule: 'required-export-missing',
692
+ suggestion: `添加 export 关键字导出 ${exportName}`
693
+ }));
694
+ }
695
+ }
696
+ }
697
+
698
+ // 检查禁止使用的API
699
+ if (spec.forbiddenAPIs && Array.isArray(spec.forbiddenAPIs)) {
700
+ for (const api of spec.forbiddenAPIs) {
701
+ const apiPattern = new RegExp(`\\b${api.replace('.', '\\.')}\\b`);
702
+ if (apiPattern.test(code)) {
703
+ const apiIndex = code.search(apiPattern);
704
+ const lineNum = this._getLineNumber(code, apiIndex);
705
+ result.addIssue(new Issue({
706
+ category: IssueCategory.SPEC_VIOLATION,
707
+ severity: Severity.MAJOR,
708
+ message: `使用了禁止的API: ${api}`,
709
+ line: lineNum,
710
+ rule: 'forbidden-api-used',
711
+ suggestion: `替换 ${api} 为允许的替代方案`
712
+ }));
713
+ }
714
+ }
715
+ }
716
+
717
+ // 检查代码覆盖要求
718
+ if (spec.minLines && lines = code.split('\n').length < spec.minLines) {
719
+ result.addIssue(new Issue({
720
+ category: IssueCategory.SPEC_VIOLATION,
721
+ severity: Severity.MAJOR,
722
+ message: `代码行数不足: 当前${lines.length}行,要求至少${spec.minLines}行`,
723
+ rule: 'min-lines-not-met',
724
+ suggestion: `完善实现以满足最低${spec.minLines}行的要求`
725
+ }));
726
+ }
727
+ }
728
+
729
+ /**
730
+ * 自定义规则检查
731
+ * @private
732
+ */
733
+ _checkCustomRules(code, lines, result) {
734
+ const customRules = this.config.customRules;
735
+ if (!customRules || typeof customRules !== 'object') return;
736
+
737
+ for (const [ruleName, ruleConfig] of Object.entries(customRules)) {
738
+ if (this.config.disabledRules.has(ruleName)) continue;
739
+
740
+ try {
741
+ const { pattern, severity, message, category } = ruleConfig;
742
+
743
+ if (typeof pattern === 'string') {
744
+ const regex = new RegExp(pattern, 'g');
745
+ let match;
746
+ while ((match = regex.exec(code)) !== null) {
747
+ const lineNum = this._getLineNumber(code, match.index);
748
+ result.addIssue(new Issue({
749
+ category: category || IssueCategory.BEST_PRACTICE,
750
+ severity: severity || Severity.MINOR,
751
+ message: message || `自定义规则[${ruleName}]触发`,
752
+ line: lineNum,
753
+ codeSnippet: code.substring(match.index, match.index + 50),
754
+ rule: `custom:${ruleName}`,
755
+ suggestion: ruleConfig.suggestion || `参考规则 ${ruleName} 的说明进行调整`
756
+ }));
757
+ }
758
+ } else if (typeof pattern === 'function') {
759
+ // 支持自定义检查函数
760
+ const customIssues = await pattern(code, lines, spec);
761
+ if (Array.isArray(customIssues)) {
762
+ for (const issueData of customIssues) {
763
+ result.addIssue(new Issue({
764
+ ...issueData,
765
+ rule: `custom:${ruleName}`
766
+ }));
767
+ }
768
+ }
769
+ }
770
+ } catch (err) {
771
+ console.error(chalk.yellow(` ⚠️ 自定义规则[${ruleName}]执行失败: ${err.message}`));
772
+ }
773
+ }
774
+ }
775
+
776
+ // ==================== 工具方法 ====================
777
+
778
+ /**
779
+ * 分析代码指标
780
+ * @private
781
+ */
782
+ _analyzeMetrics(code) {
783
+ const lines = code.split('\n');
784
+ const nonEmptyLines = lines.filter(l => l.trim().length > 0);
785
+ const commentLines = lines.filter(l => /^\s*(\/\/|#|\/\*|\*|<!--)/.test(l.trim()));
786
+
787
+ return {
788
+ totalLines: lines.length,
789
+ nonEmptyLines: nonEmptyLines.length,
790
+ commentLines: commentLines.length,
791
+ blankLines: lines.length - nonEmptyLines.length - commentLines.length,
792
+
793
+ // 复杂度估算(简化版圈复杂度)
794
+ complexity: this._estimateComplexity(code),
795
+
796
+ // 代码密度
797
+ density: nonEmptyLines.length / Math.max(lines.length, 1),
798
+
799
+ // 字符统计
800
+ totalChars: code.length,
801
+ avgLineLength: lines.reduce((sum, l) => sum + l.length, 0) / Math.max(lines.length, 1)
802
+ };
803
+ }
804
+
805
+ /**
806
+ * 估算圈复杂度(简化版)
807
+ * @private
808
+ */
809
+ _estimateComplexity(code) {
810
+ // 基于控制流关键字的数量估算
811
+ const keywords = ['if', 'else', 'for', 'while', 'case', 'catch', '&&', '\\|\\|', '\\?'];
812
+ let complexity = 1; // 基础复杂度
813
+
814
+ for (const kw of keywords) {
815
+ const matches = code.match(new RegExp(`\\b${kw}\\b`, 'g'));
816
+ complexity += (matches?.length || 0);
817
+ }
818
+
819
+ return complexity;
820
+ }
821
+
822
+ /**
823
+ * 计算代码哈希值
824
+ * @private
825
+ */
826
+ _computeHash(code) {
827
+ return crypto.createHash('md5').update(code).digest('hex').substring(0, 12);
828
+ }
829
+
830
+ /**
831
+ * 获取字符位置对应的行号
832
+ * @private
833
+ */
834
+ _getLineNumber(code, position) {
835
+ const lines = code.substring(0, position).split('\n');
836
+ return lines.length;
837
+ }
838
+
839
+ /**
840
+ * 初始化内置规则集
841
+ * @private
842
+ */
843
+ _initRules() {
844
+ return {
845
+ 'no-empty-file': { enabled: true, category: IssueCategory.SYNTAX, severity: Severity.CRITICAL },
846
+ 'max-line-length': { enabled: true, category: IssueCategory.STYLE, severity: Severity.MINOR },
847
+ 'max-nesting-depth': { enabled: true, category: IssueCategory.LOGIC, severity: Severity.MAJOR },
848
+ 'max-function-length': { enabled: true, category: IssueCategory.LOGIC, severity: Severity.MAJOR },
849
+ 'no-duplicated-code': { enabled: true, category: IssueCategory.BEST_PRACTICE, severity: Severity.MINOR },
850
+ 'sql-injection': { enabled: true, category: IssueCategory.SECURITY, severity: Severity.CRITICAL },
851
+ 'xss-risk': { enabled: true, category: IssueCategory.SECURITY, severity: Severity.MAJOR },
852
+ 'hardcoded-secret': { enabled: true, category: IssueCategory.SECURITY, severity: Severity.CRITICAL },
853
+ 'no-eval': { enabled: true, category: IssueCategory.SECURITY, severity: Severity.CRITICAL },
854
+ 'no-sync-in-loop': { enabled: true, category: IssueCategory.PERFORMANCE, severity: Severity.MAJOR },
855
+ 'n-plus-one-query': { enabled: true, category: IssueCategory.PERFORMANCE, severity: Severity.MAJOR },
856
+ 'event-listener-leak': { enabled: true, category: IssueCategory.PERFORMANCE, severity: Severity.MAJOR },
857
+ 'no-console-log': { enabled: true, category: IssueCategory.BEST_PRACTICE, severity: Severity.MINOR },
858
+ 'no-magic-numbers': { enabled: true, category: IssueCategory.BEST_PRACTICE, severity: Severity.MINOR },
859
+ 'todo-fixme-hack': { enabled: true, category: IssueCategory.BEST_PRACTICE, severity: Severity.MINOR },
860
+ 'unused-imports': { enabled: true, category: IssueCategory.STYLE, severity: Severity.INFO },
861
+ 'required-function': { enabled: true, category: IssueCategory.SPEC_VIOLATION, severity: Severity.CRITICAL },
862
+ 'forbidden-api': { enabled: true, category: IssueCategory.SPEC_VIOLATION, severity: Severity.MAJOR }
863
+ };
864
+ }
865
+
866
+ /**
867
+ * 更新运行统计
868
+ * @private
869
+ */
870
+ _updateStats(result) {
871
+ this.stats.totalReviews++;
872
+ this.stats.totalIssuesFound += result.issues.length;
873
+ this.stats.avgReviewTime =
874
+ ((this.stats.avgReviewTime * (this.stats.totalReviews - 1)) + result.reviewTime) /
875
+ this.stats.totalReviews;
876
+ }
877
+
878
+ /**
879
+ * 获取审查统计信息
880
+ * @returns {Object} 统计数据
881
+ */
882
+ getStats() {
883
+ return { ...this.stats };
884
+ }
885
+
886
+ /**
887
+ * 重置统计信息
888
+ */
889
+ resetStats() {
890
+ this.stats = {
891
+ totalReviews: 0,
892
+ totalIssuesFound: 0,
893
+ avgReviewTime: 0
894
+ };
895
+ }
896
+
897
+ /**
898
+ * 配置规则启用/禁用
899
+ * @param {string} ruleName - 规则名称
900
+ * @param {boolean} enabled - 是否启用
901
+ */
902
+ toggleRule(ruleName, enabled) {
903
+ if (enabled) {
904
+ this.config.disabledRules.delete(ruleName);
905
+ } else {
906
+ this.config.disabledRules.add(ruleName);
907
+ }
908
+ }
909
+
910
+ /**
911
+ * 添加自定义规则
912
+ * @param {string} name - 规则名称
913
+ * @param {Object} config - 规则配置 { pattern, severity, message, category, suggestion }
914
+ */
915
+ addCustomRule(name, config) {
916
+ this.config.customRules[name] = config;
917
+ }
918
+ }
919
+
920
+ /**
921
+ * 默认导出
922
+ */
923
+ export default AutoReviewer;