@pigcloud/skills 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 (161) hide show
  1. package/CHANGELOG.md +70 -0
  2. package/LICENSE +201 -0
  3. package/README.en.md +84 -0
  4. package/README.md +79 -0
  5. package/bin/cli.js +663 -0
  6. package/bin/postinstall.js +60 -0
  7. package/bin/rules-loader.js +484 -0
  8. package/bin/runtime-bootstrap.js +121 -0
  9. package/index.js +7 -0
  10. package/install.cmd +5 -0
  11. package/install.ps1 +74 -0
  12. package/install.sh +168 -0
  13. package/package.json +70 -0
  14. package/pig-cloud-skills-commands/.codex-plugin/plugin.json +35 -0
  15. package/pig-cloud-skills-commands/README.md +24 -0
  16. package/pig-cloud-skills-commands/commands/analyze.md +21 -0
  17. package/pig-cloud-skills-commands/commands/build.md +21 -0
  18. package/pig-cloud-skills-commands/commands/design.md +21 -0
  19. package/pig-cloud-skills-commands/commands/distill.md +21 -0
  20. package/pig-cloud-skills-commands/commands/doc.md +21 -0
  21. package/pig-cloud-skills-commands/commands/infra.md +21 -0
  22. package/pig-cloud-skills-commands/commands/init.md +20 -0
  23. package/pig-cloud-skills-commands/commands/kb.md +20 -0
  24. package/pig-cloud-skills-commands/commands/perf.md +20 -0
  25. package/pig-cloud-skills-commands/commands/prd.md +21 -0
  26. package/pig-cloud-skills-commands/commands/review.md +21 -0
  27. package/pig-cloud-skills-commands/commands/security.md +21 -0
  28. package/pig-cloud-skills-commands/commands/test.md +21 -0
  29. package/pig-cloud-skills-commands/commands/workflow.md +20 -0
  30. package/rules/bundles.json +358 -0
  31. package/rules/coding/analysis.md +27 -0
  32. package/rules/coding/backend/cache-invalidation.md +30 -0
  33. package/rules/coding/backend/cache-keying.md +30 -0
  34. package/rules/coding/backend/cache.md +37 -0
  35. package/rules/coding/backend/database.md +32 -0
  36. package/rules/coding/backend/feign.md +30 -0
  37. package/rules/coding/backend/index.md +42 -0
  38. package/rules/coding/backend/query.md +32 -0
  39. package/rules/coding/backend/remote.md +33 -0
  40. package/rules/coding/backend/transaction-boundary.md +30 -0
  41. package/rules/coding/backend/transaction-rollback.md +30 -0
  42. package/rules/coding/backend/transaction.md +38 -0
  43. package/rules/coding/boundary.md +25 -0
  44. package/rules/coding/implementation.md +30 -0
  45. package/rules/coding/index.md +38 -0
  46. package/rules/coding/scaffold.md +28 -0
  47. package/rules/coding/testing.md +29 -0
  48. package/rules/coding/validation.md +29 -0
  49. package/rules/core/code-quality.md +30 -0
  50. package/rules/core/evidence.md +26 -0
  51. package/rules/core/index.md +41 -0
  52. package/rules/core/interface.md +26 -0
  53. package/rules/core/iteration.md +26 -0
  54. package/rules/core/layer-boundary.md +25 -0
  55. package/rules/core/logging.md +26 -0
  56. package/rules/core/security.md +26 -0
  57. package/rules/core/task-boundary.md +27 -0
  58. package/rules/docs/api.md +34 -0
  59. package/rules/docs/capture-summary.md +29 -0
  60. package/rules/docs/capture.md +34 -0
  61. package/rules/docs/contract.md +30 -0
  62. package/rules/docs/decision-log.md +32 -0
  63. package/rules/docs/examples.md +28 -0
  64. package/rules/docs/index.md +49 -0
  65. package/rules/docs/reference.md +32 -0
  66. package/rules/index.md +46 -0
  67. package/rules/overlays/index.md +28 -0
  68. package/rules/overlays/pig-cloud/controller.md +33 -0
  69. package/rules/overlays/pig-cloud/dto-vo.md +33 -0
  70. package/rules/overlays/pig-cloud/entity.md +32 -0
  71. package/rules/overlays/pig-cloud/exception.md +32 -0
  72. package/rules/overlays/pig-cloud/layering.md +31 -0
  73. package/rules/overlays/pig-cloud/mapper.md +32 -0
  74. package/rules/overlays/pig-cloud/query-style.md +32 -0
  75. package/rules/overlays/pig-cloud/rest-response.md +33 -0
  76. package/rules/overlays/pig-cloud/service.md +33 -0
  77. package/rules/overlays/pig-cloud/transactions.md +32 -0
  78. package/rules/overlays/pig-cloud/validation.md +33 -0
  79. package/rules/overlays/pig-cloud.md +45 -0
  80. package/rules/product/acceptance.md +25 -0
  81. package/rules/product/briefing.md +27 -0
  82. package/rules/product/index.md +36 -0
  83. package/rules/product/intake.md +27 -0
  84. package/rules/product/modeling.md +25 -0
  85. package/rules/product/project-context.md +29 -0
  86. package/rules/review/code.md +35 -0
  87. package/rules/review/evidence.md +31 -0
  88. package/rules/review/index.md +50 -0
  89. package/rules/review/java.md +42 -0
  90. package/rules/review/performance.md +38 -0
  91. package/rules/review/rubric.md +28 -0
  92. package/rules/review/security.md +38 -0
  93. package/rules/review/ts.md +33 -0
  94. package/rules/review/vue.md +33 -0
  95. package/rules/skill-profile-map.json +58 -0
  96. package/rules/skill-profile-map.md +28 -0
  97. package/rules/workflow/handoff.md +25 -0
  98. package/rules/workflow/index.md +37 -0
  99. package/rules/workflow/refinement.md +29 -0
  100. package/rules/workflow/router.md +25 -0
  101. package/rules/workflow/selection.md +25 -0
  102. package/rules/workflow/stop.md +25 -0
  103. package/scripts/ci-validator.sh +114 -0
  104. package/scripts/run-golden-replays.js +312 -0
  105. package/scripts/validate-rules.js +125 -0
  106. package/scripts/validate-skill-replay-signals.js +75 -0
  107. package/scripts/validate-skill-shapes.js +141 -0
  108. package/scripts/validate-skill-stop-rules.js +139 -0
  109. package/scripts/validate-skills.cmd +3 -0
  110. package/scripts/validate-skills.ps1 +42 -0
  111. package/scripts/validate-skills.sh +36 -0
  112. package/skills/api-docs/SKILL.md +76 -0
  113. package/skills/code-review/SKILL.md +135 -0
  114. package/skills/code-review/references/findings-template.md +51 -0
  115. package/skills/code-review/references/performance-checklist.md +213 -0
  116. package/skills/code-review/references/rubric.md +232 -0
  117. package/skills/code-review/references/rules.md +32 -0
  118. package/skills/code-review/references/security-checklist.md +178 -0
  119. package/skills/code-review/references/stack-notes.md +25 -0
  120. package/skills/code-review/references/template-review.md +214 -0
  121. package/skills/code-review/scripts/lint-code-review.mjs +431 -0
  122. package/skills/domain-modeling/SKILL.md +80 -0
  123. package/skills/domain-modeling/references/README.md +134 -0
  124. package/skills/domain-modeling/references/distillation-checklist.md +152 -0
  125. package/skills/domain-modeling/references/test-cases-template.md +128 -0
  126. package/skills/environment-deploy/SKILL.md +81 -0
  127. package/skills/feature-build/SKILL.md +122 -0
  128. package/skills/feature-build/references/coding-checklist.md +97 -0
  129. package/skills/feature-build/references/comment-specification.md +102 -0
  130. package/skills/knowledge-capture/SKILL.md +84 -0
  131. package/skills/performance-check/SKILL.md +117 -0
  132. package/skills/product-intake/SKILL.md +98 -0
  133. package/skills/project-bootstrap/SKILL.md +80 -0
  134. package/skills/references/agent-personas.md +34 -0
  135. package/skills/references/anti-rationalization.md +144 -0
  136. package/skills/references/engineering-delivery-method.md +63 -0
  137. package/skills/references/engineering-delivery-template.md +80 -0
  138. package/skills/references/flow-test-cases.md +62 -0
  139. package/skills/references/full-chain-replay-scenarios.md +79 -0
  140. package/skills/references/golden-prompt-suite.js +385 -0
  141. package/skills/references/golden-prompt-suite.md +33 -0
  142. package/skills/references/hooks.md +67 -0
  143. package/skills/references/negative-replay-scenarios.md +49 -0
  144. package/skills/references/project-requirement-alignment.md +41 -0
  145. package/skills/references/prompt-replay-checklist.md +128 -0
  146. package/skills/references/requirements-separation-map.md +71 -0
  147. package/skills/references/rule-loading-map.md +108 -0
  148. package/skills/references/skill-authoring-standard.md +73 -0
  149. package/skills/references/skill-boundary-template.md +38 -0
  150. package/skills/references/skill-enhanced-template.md +53 -0
  151. package/skills/references/skill-reference-matrix.md +53 -0
  152. package/skills/references/slash-commands.md +34 -0
  153. package/skills/security-review/SKILL.md +117 -0
  154. package/skills/spec-refinement/SKILL.md +143 -0
  155. package/skills/spec-refinement/references/ears-syntax.md +127 -0
  156. package/skills/spec-refinement/references/requirement-checklist.md +139 -0
  157. package/skills/spec-refinement/references/spec-workbook.md +75 -0
  158. package/skills/technical-design/SKILL.md +105 -0
  159. package/skills/technical-design/references/solid-checklist.md +199 -0
  160. package/skills/test-design/SKILL.md +91 -0
  161. package/skills/workflow-router/SKILL.md +86 -0
@@ -0,0 +1,214 @@
1
+ # 代码评审报告模板
2
+
3
+ > 用于 code-review Skill 输出的标准格式
4
+
5
+ ---
6
+
7
+ ## 模板内容
8
+
9
+ ```markdown
10
+ # 代码评审报告
11
+
12
+ ## 评审概要
13
+
14
+ | 字段 | 内容 |
15
+ |------|------|
16
+ | 评审时间 | {datetime} |
17
+ | 评审文件数 | {count} |
18
+ | 变更行数 | +{add} / -{del} |
19
+ | 评审耗时 | {duration} |
20
+ | 规则覆盖率 | {coverage}% |
21
+ | Rubric 评分 | {score}/10 |
22
+
23
+ ---
24
+
25
+ ## 问题列表
26
+
27
+ ### P0 问题(阻断性 - 必须修复)
28
+
29
+ #### 问题 #1
30
+
31
+ | 字段 | 内容 |
32
+ |------|------|
33
+ | 文件 | `{filePath}` |
34
+ | 行号 | {lineNumber} |
35
+ | 规则 | {ruleId}: {ruleName} |
36
+ | 类型 | MUST / NEVER |
37
+ | 问题 | {问题描述} |
38
+ | 代码 | `{问题代码片段}` |
39
+ | 修复 | `{修复建议}` |
40
+ | 依据 | [规则定义](references/rules.md#{ruleId}) |
41
+
42
+ ---
43
+
44
+ ### P1 问题(严重 - 必须修复)
45
+
46
+ #### 问题 #2
47
+
48
+ | 字段 | 内容 |
49
+ |------|------|
50
+ | 文件 | `{filePath}` |
51
+ | 行号 | {lineNumber} |
52
+ | 规则 | {ruleId}: {ruleName} |
53
+ | 类型 | MUST / NEVER |
54
+ | 问题 | {问题描述} |
55
+ | 代码 | `{问题代码片段}` |
56
+ | 修复 | `{修复建议}` |
57
+ | 依据 | [规则定义](references/rules.md#{ruleId}) |
58
+
59
+ ---
60
+
61
+ ### P2 问题(最佳实践 - 建议修复)
62
+
63
+ #### 问题 #3
64
+
65
+ | 字段 | 内容 |
66
+ |------|------|
67
+ | 文件 | `{filePath}` |
68
+ | 行号 | {lineNumber} |
69
+ | 规则 | {ruleId}: {ruleName} |
70
+ | 问题 | {问题描述} |
71
+ | 修复 | `{修复建议}` |
72
+
73
+ ---
74
+
75
+ ### P3 问题(建议改进)
76
+
77
+ #### 问题 #4
78
+
79
+ | 字段 | 内容 |
80
+ |------|------|
81
+ | 文件 | `{filePath}` |
82
+ | 规则 | {ruleId}: {ruleName} |
83
+ | 问题 | {问题描述} |
84
+ | 建议 | `{改进建议}` |
85
+
86
+ ---
87
+
88
+ ## 跨模块影响分析
89
+
90
+ ### 变更文件
91
+
92
+ | 文件 | 类型 | 模块 |
93
+ |------|------|------|
94
+ | `{filePath}` | 新增 / 修改 / 删除 | `{module}` |
95
+
96
+ ### 影响模块
97
+
98
+ | 模块 | 影响类型 | 变更内容 | 建议 |
99
+ |------|----------|----------|------|
100
+ | `{module}` | API变更 / 依赖变更 / 数据变更 | `{content}` | `{suggestion}` |
101
+
102
+ ### 依赖关系图
103
+
104
+ ```
105
+ {moduleA} → {moduleB} → {moduleC}
106
+
107
+ {moduleD}
108
+ ```
109
+
110
+ ---
111
+
112
+ ## 安全检查结果
113
+
114
+ | 维度 | 结果 | 问题数 | 详情 |
115
+ |------|------|--------|------|
116
+ | 硬编码 | ✅ / ⚠️ | {count} | {details} |
117
+ | 日志 | ✅ / ⚠️ | {count} | {details} |
118
+ | SQL注入 | ✅ / ⚠️ | {count} | {details} |
119
+ | XSS | ✅ / ⚠️ | {count} | {details} |
120
+
121
+ ---
122
+
123
+ ## 性能检查结果
124
+
125
+ | 维度 | 结果 | 问题数 | 详情 |
126
+ |------|------|--------|------|
127
+ | N+1查询 | ✅ / ⚠️ | {count} | {details} |
128
+ | Left-fuzzy LIKE | ✅ / ⚠️ | {count} | {details} |
129
+ | 批量操作 | ✅ / ⚠️ | {count} | {details} |
130
+
131
+ ---
132
+
133
+ ## Rubric 自检结果
134
+
135
+ | 规则 | 结果 | 说明 |
136
+ |------|------|------|
137
+ | R1: 渐进式加载 | ✅ / ⚠️ | {message} |
138
+ | R2: 规则覆盖率 | ✅ / ⚠️ | {coverage}% |
139
+ | R3: 位置准确性 | ✅ / ⚠️ | 偏差 {diff} 行 |
140
+ | R4: 可解释性 | ✅ / ⚠️ | {coverage}% |
141
+ | R5: 问题分级 | ✅ / ⚠️ | {message} |
142
+ | R6: 跨模块影响 | ✅ / ⚠️ | {message} |
143
+ | R7: 输出格式 | ✅ / ⚠️ | {message} |
144
+ | R8: 确认节点 | ✅ / ⚠️ | {message} |
145
+ | R9: Pre-Delivery | ✅ / ⚠️ | {message} |
146
+ | R10: 反 Slop | ✅ / ⚠️ | {message} |
147
+
148
+ ---
149
+
150
+ ## 评审结论
151
+
152
+ | 结论 | 条件 | 当前状态 |
153
+ |------|------|----------|
154
+ | ✅ 通过 | 0 个 P0/P1 问题 | {status} |
155
+ | ⚠️ 需修改后通过 | 有 P0/P1 问题,修复后重新评审 | {status} |
156
+ | ❌ 不通过 | >5 个 P0 问题 | {status} |
157
+
158
+ ---
159
+
160
+ ## 下一步建议
161
+
162
+ 1. {修复建议1}
163
+ 2. {修复建议2}
164
+ 3. 执行 `mvn compile -q` 验证编译
165
+ 4. 执行 `mvn test -q` 验证测试
166
+ 5. 重新提交评审
167
+
168
+ ---
169
+
170
+ ## 评审人
171
+
172
+ | 字段 | 内容 |
173
+ |------|------|
174
+ | 评审工具 | pig-skills code-review v{version} |
175
+ | 评审模型 | {model} |
176
+ | Rubric 评分 | {score}/10 |
177
+
178
+ ---
179
+
180
+ *报告生成时间: {timestamp}*
181
+ ```
182
+
183
+ ---
184
+
185
+ ## 使用说明
186
+
187
+ ### 填充变量
188
+
189
+ | 变量 | 来源 |
190
+ |------|------|
191
+ | `{datetime}` | 当前时间 |
192
+ | `{count}` | Git diff 文件数 |
193
+ | `{add}` / `{del}` | Git diff 行数统计 |
194
+ | `{duration}` | 评审耗时 |
195
+ | `{coverage}` | lint 脚本计算 |
196
+ | `{score}` | Rubric 自检结果 |
197
+ | `{filePath}` | 变更文件路径 |
198
+ | `{lineNumber}` | lint 脚本定位 |
199
+ | `{ruleId}` | 规则ID(MUST-001 等) |
200
+ | `{ruleName}` | 规则名称 |
201
+ | `{module}` | 模块名称(包路径) |
202
+
203
+ ### 输出位置
204
+
205
+ ```
206
+ .agents/specs/REVIEW-{timestamp}.md
207
+ ```
208
+
209
+ ### 命名规则
210
+
211
+ ```
212
+ REVIEW-{YYYYMMDD}-{HHMMSS}.md
213
+ 例如: REVIEW-20260616-143000.md
214
+ ```
@@ -0,0 +1,431 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * lint-code-review.mjs - 零依赖代码审查 Lint 脚本
5
+ *
6
+ * 用法:
7
+ * node lint-code-review.mjs --files <file-list>
8
+ * node lint-code-review.mjs --files <file-list> --json
9
+ * node lint-code-review.mjs --files <file-list> --quick
10
+ *
11
+ * 输出:
12
+ * P0-P3 分级问题列表
13
+ * JSON 格式支持 CI 集成
14
+ */
15
+
16
+ import { readFileSync, existsSync, statSync } from 'fs';
17
+ import { join, basename } from 'path';
18
+
19
+ // 命令行参数解析
20
+ const args = process.argv.slice(2);
21
+ const options = {
22
+ files: [],
23
+ json: false,
24
+ quick: false
25
+ };
26
+
27
+ for (let i = 0; i < args.length; i++) {
28
+ if (args[i] === '--files') {
29
+ options.files = args[i + 1]?.split(',').filter(f => f) || [];
30
+ i++;
31
+ } else if (args[i] === '--json') {
32
+ options.json = true;
33
+ } else if (args[i] === '--quick') {
34
+ options.quick = true;
35
+ }
36
+ }
37
+
38
+ // 规则定义
39
+ const RULES = {
40
+ // MUST 规则
41
+ 'MUST-001': {
42
+ level: 'P1',
43
+ name: 'Entity 基类',
44
+ pattern: /class\s+\w+Entity\s+extends\s+/,
45
+ check: (content, filePath) => {
46
+ if (!filePath.includes('Entity')) return null;
47
+ if (!content.includes('MpRealDelEntity') && !content.includes('MpLogicDelEntity')) {
48
+ return {
49
+ message: 'Entity 未继承 MpRealDelEntity 或 MpLogicDelEntity',
50
+ fix: 'extends MpRealDelEntity<Entity>'
51
+ };
52
+ }
53
+ return null;
54
+ }
55
+ },
56
+ 'MUST-002': {
57
+ level: 'P1',
58
+ name: '主键注解',
59
+ pattern: /@TableId/,
60
+ check: (content, filePath) => {
61
+ if (!filePath.includes('Entity')) return null;
62
+ if (content.includes('@TableId') && !content.includes('IdType.ASSIGN_ID')) {
63
+ return {
64
+ message: '主键未使用雪花算法',
65
+ fix: '@TableId(type = IdType.ASSIGN_ID)'
66
+ };
67
+ }
68
+ return null;
69
+ }
70
+ },
71
+ 'MUST-004': {
72
+ level: 'P0',
73
+ name: '分层规范',
74
+ pattern: /@Autowired|@Resource/,
75
+ check: (content, filePath) => {
76
+ if (!filePath.includes('Controller')) return null;
77
+ // Controller 直接注入 Mapper
78
+ const mapperInject = content.match(/@(Autowired|Resource)\s*(private\s+)?(\w+Mapper)/);
79
+ if (mapperInject) {
80
+ const lineNum = getLineNumber(content, mapperInject[0]);
81
+ return {
82
+ line: lineNum,
83
+ message: 'Controller 直接注入 Mapper,违反分层规范',
84
+ code: mapperInject[0],
85
+ fix: '通过 Service 层访问数据'
86
+ };
87
+ }
88
+ return null;
89
+ }
90
+ },
91
+ 'MUST-005': {
92
+ level: 'P1',
93
+ name: '返回规范',
94
+ pattern: /public\s+\w+\s+\w+\s*\(/,
95
+ check: (content, filePath) => {
96
+ if (!filePath.includes('Controller')) return null;
97
+ // 检查 REST 方法返回类型
98
+ const methods = content.matchAll(/@(GetMapping|PostMapping|PutMapping|DeleteMapping|RequestMapping)[\s\S]*?public\s+(\w+(?:<[^>]+>)?)\s+\w+\s*\(/g);
99
+ for (const method of methods) {
100
+ const returnType = method[2];
101
+ if (!returnType.includes('Result') && returnType !== 'void') {
102
+ const lineNum = getLineNumber(content, method[0]);
103
+ return {
104
+ line: lineNum,
105
+ message: `REST 方法返回类型应为 Result<T>,当前为 ${returnType}`,
106
+ fix: '返回 Result<T>'
107
+ };
108
+ }
109
+ }
110
+ return null;
111
+ }
112
+ },
113
+ 'MUST-006': {
114
+ level: 'P1',
115
+ name: '事务注解',
116
+ pattern: /@Transactional/,
117
+ check: (content, filePath) => {
118
+ if (!filePath.includes('Service')) return null;
119
+ // 检查是否有跨表操作但无事务
120
+ const hasMultiTableOp = content.includes('insert') && content.includes('update') ||
121
+ content.includes('insert') && content.includes('delete') ||
122
+ content.includes('mapper') && content.includes('mapper');
123
+ if (hasMultiTableOp && !content.includes('@Transactional')) {
124
+ return {
125
+ message: '跨表操作未添加事务注解',
126
+ fix: '@Transactional(rollbackFor = Exception.class)'
127
+ };
128
+ }
129
+ return null;
130
+ }
131
+ },
132
+
133
+ // NEVER 规则
134
+ 'NEVER-001': {
135
+ level: 'P0',
136
+ name: 'Controller 直接访问 Mapper',
137
+ pattern: /Mapper\s+mapper/,
138
+ check: (content, filePath) => {
139
+ if (!filePath.includes('Controller')) return null;
140
+ const mapperRef = content.match(/(\w+Mapper)\s+\w+;/);
141
+ if (mapperRef) {
142
+ const lineNum = getLineNumber(content, mapperRef[0]);
143
+ return {
144
+ line: lineNum,
145
+ message: 'Controller 直接访问 Mapper',
146
+ code: mapperRef[0],
147
+ fix: '通过 Service 层访问'
148
+ };
149
+ }
150
+ return null;
151
+ }
152
+ },
153
+ 'NEVER-002': {
154
+ level: 'P0',
155
+ name: '事务中远程调用',
156
+ pattern: /@Transactional[\s\S]*?(FeignClient|RestTemplate|HttpClient|WebClient)/,
157
+ check: (content, filePath) => {
158
+ if (!filePath.includes('Service')) return null;
159
+ // 检查事务方法中是否有远程调用
160
+ const transactionalBlock = content.match(/@Transactional[\s\S]{0,500}(?:@FeignClient|restTemplate|httpClient|webClient|\.api\()/);
161
+ if (transactionalBlock) {
162
+ const lineNum = getLineNumber(content, transactionalBlock[0]);
163
+ return {
164
+ line: lineNum,
165
+ message: '事务方法中执行远程调用',
166
+ fix: '将远程调用移到事务方法外部'
167
+ };
168
+ }
169
+ return null;
170
+ }
171
+ },
172
+ 'NEVER-003': {
173
+ level: 'P1',
174
+ name: 'Mapper 业务逻辑',
175
+ pattern: /default\s+(void|Result|boolean)/,
176
+ check: (content, filePath) => {
177
+ if (!filePath.includes('Mapper')) return null;
178
+ // Mapper 中有 default 方法实现业务逻辑
179
+ const defaultMethod = content.match(/default\s+(?!class|interface)\w+\s+\w+\s*\([^)]*\)\s*\{[\s\S]{50,}/);
180
+ if (defaultMethod && !defaultMethod[0].includes('selectById') && !defaultMethod[0].includes('insert')) {
181
+ const lineNum = getLineNumber(content, defaultMethod[0]);
182
+ return {
183
+ line: lineNum,
184
+ message: 'Mapper 中放置业务逻辑',
185
+ fix: '将业务逻辑移到 Service 层'
186
+ };
187
+ }
188
+ return null;
189
+ }
190
+ },
191
+ 'NEVER-004': {
192
+ level: 'P1',
193
+ name: 'String column names',
194
+ pattern: /\.eq\("|\.\s*like\("|\.\s*ne\("|\.\s*gt\("|\.\s*lt\("|\.\s*ge\("|\.\s*le\("/,
195
+ check: (content, filePath) => {
196
+ const stringColumn = content.match(/queryWrapper\.(?:eq|ne|gt|lt|ge|le|like)\s*\(\s*"/);
197
+ if (stringColumn) {
198
+ const lineNum = getLineNumber(content, stringColumn[0]);
199
+ return {
200
+ line: lineNum,
201
+ message: '使用 string column names 查询',
202
+ code: stringColumn[0],
203
+ fix: '使用 lambdaQuery().eq(Entity::getField, value)'
204
+ };
205
+ }
206
+ return null;
207
+ }
208
+ },
209
+ 'NEVER-005': {
210
+ level: 'P2',
211
+ name: 'Left-fuzzy LIKE',
212
+ pattern: /\.like\("%|\.likeLeft\(/,
213
+ check: (content) => {
214
+ const leftFuzzy = content.match(/\.like\s*\(\s*["']%/);
215
+ if (leftFuzzy) {
216
+ const lineNum = getLineNumber(content, leftFuzzy[0]);
217
+ return {
218
+ line: lineNum,
219
+ message: '使用 left-fuzzy LIKE,性能较差',
220
+ fix: '使用 likeRight 或避免左模糊查询'
221
+ };
222
+ }
223
+ return null;
224
+ }
225
+ },
226
+ 'NEVER-006': {
227
+ level: 'P2',
228
+ name: 'Magic values',
229
+ pattern: /==\s*[0-9]+\s*\)|==\s*"[^"]+"\s*\)/,
230
+ check: (content) => {
231
+ const magicValue = content.match(/(?:status|type|state|level)\s*==\s*(?:[0-9]+|"[^"]+")\s*\)/);
232
+ if (magicValue) {
233
+ const lineNum = getLineNumber(content, magicValue[0]);
234
+ return {
235
+ line: lineNum,
236
+ message: '使用 magic values 状态比较',
237
+ code: magicValue[0],
238
+ fix: '使用 @EnumValue + CodeMessageEnumable'
239
+ };
240
+ }
241
+ return null;
242
+ }
243
+ },
244
+
245
+ // 安全检查
246
+ 'SEC-001': {
247
+ level: 'P0',
248
+ name: '硬编码密码',
249
+ pattern: /password\s*=\s*"[^"]+"/,
250
+ check: (content) => {
251
+ const hardcoded = content.match(/(?:password|passwd|pwd|secret|token|key)\s*=\s*"[^"]{8,}"/);
252
+ if (hardcoded && !hardcoded[0].includes('${') && !hardcoded[0].includes('placeholder')) {
253
+ const lineNum = getLineNumber(content, hardcoded[0]);
254
+ return {
255
+ line: lineNum,
256
+ message: '硬编码敏感信息',
257
+ fix: '使用配置文件或环境变量'
258
+ };
259
+ }
260
+ return null;
261
+ }
262
+ },
263
+ 'SEC-002': {
264
+ level: 'P0',
265
+ name: 'SQL 注入',
266
+ pattern: /\$\{[^}]+\}/,
267
+ check: (content) => {
268
+ // 检查 Mapper XML 中的 ${} 参数
269
+ if (content.includes('${') && !content.includes('${orderBy')) {
270
+ const sqlInjection = content.match(/\$\{[^}]+\}/);
271
+ if (sqlInjection) {
272
+ const lineNum = getLineNumber(content, sqlInjection[0]);
273
+ return {
274
+ line: lineNum,
275
+ message: 'SQL 注入风险:使用 ${} 参数',
276
+ fix: '使用 #{} 参数替代'
277
+ };
278
+ }
279
+ }
280
+ return null;
281
+ }
282
+ }
283
+ };
284
+
285
+ // 获取行号
286
+ function getLineNumber(content, searchStr) {
287
+ const lines = content.split('\n');
288
+ for (let i = 0; i < lines.length; i++) {
289
+ if (lines[i].includes(searchStr.substring(0, 50))) {
290
+ return i + 1;
291
+ }
292
+ }
293
+ return 1;
294
+ }
295
+
296
+ // 检查单个文件
297
+ function checkFile(filePath) {
298
+ const issues = [];
299
+
300
+ if (!existsSync(filePath)) {
301
+ return issues;
302
+ }
303
+
304
+ const content = readFileSync(filePath, 'utf-8');
305
+
306
+ for (const [ruleId, rule] of Object.entries(RULES)) {
307
+ // Quick 模式只检查 P0 规则
308
+ if (options.quick && rule.level !== 'P0') {
309
+ continue;
310
+ }
311
+
312
+ try {
313
+ const result = rule.check(content, filePath);
314
+ if (result) {
315
+ issues.push({
316
+ ruleId,
317
+ ruleName: rule.name,
318
+ level: rule.level,
319
+ file: filePath,
320
+ line: result.line || 1,
321
+ message: result.message,
322
+ code: result.code || '',
323
+ fix: result.fix || ''
324
+ });
325
+ }
326
+ } catch (e) {
327
+ // 规则检查失败,跳过
328
+ }
329
+ }
330
+
331
+ return issues;
332
+ }
333
+
334
+ // 主函数
335
+ function lint() {
336
+ const results = {
337
+ p0Count: 0,
338
+ p1Count: 0,
339
+ p2Count: 0,
340
+ p3Count: 0,
341
+ issues: [],
342
+ filesChecked: options.files.length,
343
+ timestamp: new Date().toISOString()
344
+ };
345
+
346
+ for (const filePath of options.files) {
347
+ const fileIssues = checkFile(filePath);
348
+ results.issues.push(...fileIssues);
349
+
350
+ for (const issue of fileIssues) {
351
+ if (issue.level === 'P0') results.p0Count++;
352
+ else if (issue.level === 'P1') results.p1Count++;
353
+ else if (issue.level === 'P2') results.p2Count++;
354
+ else results.p3Count++;
355
+ }
356
+ }
357
+
358
+ return results;
359
+ }
360
+
361
+ // 输出结果
362
+ function output(results) {
363
+ if (options.json) {
364
+ console.log(JSON.stringify(results, null, 2));
365
+ } else {
366
+ console.log('\n=== Code Review Lint ===\n');
367
+ console.log(`检查文件数: ${results.filesChecked}`);
368
+ console.log(`P0 问题: ${results.p0Count}`);
369
+ console.log(`P1 问题: ${results.p1Count}`);
370
+ console.log(`P2 问题: ${results.p2Count}`);
371
+ console.log(`P3 问题: ${results.p3Count}`);
372
+
373
+ if (results.issues.length > 0) {
374
+ console.log('\n--- 问题列表 ---\n');
375
+
376
+ const p0Issues = results.issues.filter(i => i.level === 'P0');
377
+ const p1Issues = results.issues.filter(i => i.level === 'P1');
378
+ const p2Issues = results.issues.filter(i => i.level === 'P2');
379
+
380
+ if (p0Issues.length > 0) {
381
+ console.log('### P0 问题(阻断性 - 必须修复)\n');
382
+ for (const issue of p0Issues) {
383
+ console.log(`[${issue.ruleId}] ${issue.file}:${issue.line}`);
384
+ console.log(` 问题: ${issue.message}`);
385
+ console.log(` 修复: ${issue.fix}\n`);
386
+ }
387
+ }
388
+
389
+ if (p1Issues.length > 0) {
390
+ console.log('### P1 问题(严重 - 必须修复)\n');
391
+ for (const issue of p1Issues) {
392
+ console.log(`[${issue.ruleId}] ${issue.file}:${issue.line}`);
393
+ console.log(` 问题: ${issue.message}`);
394
+ console.log(` 修复: ${issue.fix}\n`);
395
+ }
396
+ }
397
+
398
+ if (p2Issues.length > 0 && !options.quick) {
399
+ console.log('### P2 问题(最佳实践 - 建议修复)\n');
400
+ for (const issue of p2Issues) {
401
+ console.log(`[${issue.ruleId}] ${issue.file}:${issue.line}`);
402
+ console.log(` 问题: ${issue.message}`);
403
+ console.log(` 修复: ${issue.fix}\n`);
404
+ }
405
+ }
406
+ }
407
+
408
+ console.log('--- 结论 ---');
409
+ if (results.p0Count + results.p1Count === 0) {
410
+ console.log('✅ 通过');
411
+ } else {
412
+ console.log(`⚠️ 需修复 ${results.p0Count + results.p1Count} 个 P0/P1 问题后重新评审`);
413
+ }
414
+ }
415
+ }
416
+
417
+ // 执行
418
+ if (options.files.length === 0) {
419
+ console.error('用法: node lint-code-review.mjs --files <file-list> [--json] [--quick]');
420
+ process.exit(1);
421
+ }
422
+
423
+ const results = lint();
424
+ output(results);
425
+
426
+ // CI 退出码
427
+ if (results.p0Count + results.p1Count > 0) {
428
+ process.exit(1);
429
+ } else {
430
+ process.exit(0);
431
+ }