kcode-pi 0.1.5 → 0.1.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +35 -2
- package/dist/cli/kcode.d.ts +1 -0
- package/dist/cli/kcode.js +27 -4
- package/package.json +1 -1
- package/src/cli/kcode.ts +29 -4
- package/src/official/kingdee-skills.ts +60 -13
- package/src/rules/checker.ts +143 -0
- package/vendor/kingdee-skills/kingdee-cosmic-reviewer/SKILL.md +2 -2
- package/vendor/kingdee-skills/ok-cosmic/SKILL.md +52 -101
- package/vendor/kingdee-skills/ok-cosmic/agents/openai.yaml +4 -4
- package/vendor/kingdee-skills/ok-cosmic/manifest.json +21 -20
- package/vendor/kingdee-skills/ok-cosmic/ok-cosmic-intro.html +1 -1
- package/vendor/kingdee-skills/ok-cosmic/rules/a-layer-rules.json +1 -1
- package/vendor/kingdee-skills/ok-cosmic/rules/anti-patterns.md +2 -2
- package/vendor/kingdee-skills/ok-cosmic/rules/coding-preferences.md +4 -4
- package/vendor/kingdee-skills/ok-cosmic/rules/constraints.md +3 -3
- package/vendor/kingdee-skills/ok-cosmic/rules/decision-matrix.md +8 -8
- package/vendor/kingdee-skills/ok-cosmic/rules/intent-routing.md +1 -1
- package/vendor/kingdee-skills/ok-cosmic/rules/post-check.md +19 -18
- package/vendor/kingdee-skills/ok-ksql/SKILL.md +9 -9
- package/vendor/kingdee-skills/ok-ksql/manifest.json +2 -1
- package/vendor/kingdee-skills/ok-ksql/references/ksql-datafix.md +2 -2
- package/vendor/kingdee-skills/kingdee-cosmic-reviewer/scripts/pattern-matcher.py +0 -336
- package/vendor/kingdee-skills/kingdee-cosmic-reviewer/scripts/review-score-calculator.py +0 -121
- package/vendor/kingdee-skills/ok-cosmic/CHANGELOG.md +0 -295
- package/vendor/kingdee-skills/ok-cosmic/README.md +0 -460
- package/vendor/kingdee-skills/ok-cosmic/requirements.txt +0 -2
- package/vendor/kingdee-skills/ok-cosmic/scripts/config_loader.py +0 -204
- package/vendor/kingdee-skills/ok-cosmic/scripts/cosmic-api-knowledge.py +0 -910
- package/vendor/kingdee-skills/ok-cosmic/scripts/cosmic-basedata-query.py +0 -359
- package/vendor/kingdee-skills/ok-cosmic/scripts/cosmic-config-check.py +0 -181
- package/vendor/kingdee-skills/ok-cosmic/scripts/cosmic-extpoints-query.py +0 -389
- package/vendor/kingdee-skills/ok-cosmic/scripts/cosmic-form-metadata.py +0 -856
- package/vendor/kingdee-skills/ok-cosmic/scripts/cosmic-post-check.py +0 -262
- package/vendor/kingdee-skills/ok-cosmic/scripts/cosmic-post-lint.py +0 -293
- package/vendor/kingdee-skills/ok-cosmic/scripts/lint/__init__.py +0 -2
- package/vendor/kingdee-skills/ok-cosmic/scripts/lint/base.py +0 -393
- package/vendor/kingdee-skills/ok-cosmic/scripts/lint/resource_check.py +0 -176
- package/vendor/kingdee-skills/ok-cosmic/scripts/lint/scene_check.py +0 -375
- package/vendor/kingdee-skills/ok-cosmic/scripts/lint/style_check.py +0 -434
- package/vendor/kingdee-skills/ok-cosmic/scripts/lint/verify_check.py +0 -36
- package/vendor/kingdee-skills/ok-cosmic/scripts/route_client.py +0 -186
- package/vendor/kingdee-skills/ok-cosmic/scripts/script_utils.py +0 -40
- package/vendor/kingdee-skills/ok-cosmic/scripts/sqlite_cache.py +0 -142
- package/vendor/kingdee-skills/ok-cosmic/setup/cuslib/kd-cd-cosmic-commons.jar +0 -0
- package/vendor/kingdee-skills/ok-cosmic/setup/cuslib/kd-cd-cosmic-features.jar +0 -0
- package/vendor/kingdee-skills/ok-cosmic/setup/setup-mac.sh +0 -18
- package/vendor/kingdee-skills/ok-cosmic/setup/setup-windows.bat +0 -53
- package/vendor/kingdee-skills/ok-cosmic/setup/setup.jar +0 -0
- package/vendor/kingdee-skills/ok-ksql/scripts/ksql_lint.py +0 -363
|
@@ -10,11 +10,11 @@ description: "金蝶云苍穹 KSQL/SQL 数据修复与批量数据变更 Skill
|
|
|
10
10
|
## 路径约定
|
|
11
11
|
|
|
12
12
|
- `<SKILL_ROOT>` = 当前 `SKILL.md` 所在目录(即 `skills/ok-ksql/`)
|
|
13
|
-
-
|
|
13
|
+
- `kd_ksql_lint` = 最终 SQL 静态检查工具。
|
|
14
14
|
|
|
15
15
|
## 依赖关系
|
|
16
16
|
|
|
17
|
-
- 本 Skill
|
|
17
|
+
- 本 Skill 不自带、不复制、不生成 Python 元数据脚本。
|
|
18
18
|
- 表名、`dbKey`、字段类型、枚举/状态值、基础资料落库字段确认,统一依赖 `$ok-cosmic` 的元数据查询能力。
|
|
19
19
|
- 如果 `$ok-cosmic` 不可用,且用户也没有提供已确认的元数据结果,必须停止并列出待确认项。
|
|
20
20
|
|
|
@@ -22,22 +22,22 @@ description: "金蝶云苍穹 KSQL/SQL 数据修复与批量数据变更 Skill
|
|
|
22
22
|
|
|
23
23
|
处理任何 KSQL/SQL 数据修复请求时,必须先完整读取 [references/ksql-datafix.md](references/ksql-datafix.md),并按其中“确认卡片”模板输出。
|
|
24
24
|
|
|
25
|
-
##
|
|
25
|
+
## 辅助工具
|
|
26
26
|
|
|
27
|
-
- 最终 SQL 文件生成后,优先运行 `
|
|
27
|
+
- 最终 SQL 文件生成后,优先运行 `kd_ksql_lint path=<SQL文件>` 做静态检查。
|
|
28
28
|
- 若 lint 输出 `ERROR`,必须修复 SQL 后再交付;若输出 `WARN`,必须按偏好修复或在结果中说明保留原因。
|
|
29
|
-
- `
|
|
29
|
+
- `kd_ksql_lint` 会检查高风险项:无 `WHERE` 的 `UPDATE/DELETE`、非备份场景 `SELECT *`、备份语句/备份表命名、时间戳一致性、`EXISTS` 偏好、`NULL` 判断和 PostgreSQL 多表更新风格。
|
|
30
30
|
|
|
31
31
|
## Step 0. 依赖预检
|
|
32
32
|
|
|
33
33
|
1. 先使用 `$ok-cosmic` 完成其 Step 0 配置预检。
|
|
34
34
|
2. 若 `$ok-cosmic` 预检返回 `ERROR`,不得生成最终 KSQL;只说明缺失配置,并把需要用户补齐的信息列入“待确认项”。
|
|
35
|
-
3. 生成最终 KSQL 前,必须通过 `$ok-cosmic` 的元数据能力确认表名和字段名;具体命令以 `$ok-cosmic`
|
|
35
|
+
3. 生成最终 KSQL 前,必须通过 `$ok-cosmic` / `kd_cosmic_metadata` 的元数据能力确认表名和字段名;具体命令以 `$ok-cosmic` 的 KCode 工具路由为准。
|
|
36
36
|
|
|
37
37
|
## 工作流硬约束
|
|
38
38
|
|
|
39
39
|
1. 先拆解自然语言意图,明确目标对象、操作类型、目标字段、条件字段、新值来源和风险边界。
|
|
40
|
-
2. 必须通过 `$ok-cosmic` 元数据能力(`
|
|
40
|
+
2. 必须通过 `$ok-cosmic` 元数据能力(`kd_cosmic_metadata sql=true`)精确确认每个单据的表名和每个数据库字段名;用户给中文或英文都必须查。
|
|
41
41
|
3. 写 SQL 前必须先判断所有参与表的 `dbName` 是否一致;一致才按普通单库 SQL 处理,不一致就是分库/跨库场景。
|
|
42
42
|
4. 分库/跨库场景不得直接生成普通更新 SQL;必须先让用户确认 dblink / postgres_fdw / 导出导入临时表等处理方式。
|
|
43
43
|
5. 所有表名、`dbKey`、`dbName`、枚举值、状态值、基础资料落库字段全部确认后,才允许生成最终 KSQL。
|
|
@@ -51,7 +51,7 @@ description: "金蝶云苍穹 KSQL/SQL 数据修复与批量数据变更 Skill
|
|
|
51
51
|
13. 所有确认卡片均为 `✔️` 后,最终必须在用户桌面生成 SQL 文本文件;任一卡片为 `✖️` 时,不生成文件。
|
|
52
52
|
14. 默认使用 PostgreSQL 语法生成 SQL;除非用户明确指定其他数据库方言,否则不要输出其他方言写法。
|
|
53
53
|
15. SQL 可读性偏好:成员关系/半连接条件默认使用 `IN`(值列表或子查询),避免使用 `EXISTS`;只有 `IN` 会改变语义或无法表达时才保留 `EXISTS`,并说明原因。
|
|
54
|
-
16. 最终 SQL 文件生成后必须尽量运行 `
|
|
54
|
+
16. 最终 SQL 文件生成后必须尽量运行 `kd_ksql_lint`;如因环境限制无法运行,最终回复中说明未运行原因。
|
|
55
55
|
|
|
56
56
|
## 输出要求
|
|
57
57
|
|
|
@@ -78,4 +78,4 @@ description: "金蝶云苍穹 KSQL/SQL 数据修复与批量数据变更 Skill
|
|
|
78
78
|
- 时间:文件名里的分钟必须取当前生成时间,与备份表名时间戳保持一致。
|
|
79
79
|
- 风格:SQL 文件使用 `-- ============================================` 分隔章节;文件开头写业务标题、关键条件和执行前提醒;SQL 关键字大写;文件末尾补充“字段映射(来自元数据)”。
|
|
80
80
|
- 禁止:待确认项非空、任一确认卡片为 `✖️` 时,不得创建桌面文件。
|
|
81
|
-
- 检查:文件生成后运行 `
|
|
81
|
+
- 检查:文件生成后运行 `kd_ksql_lint path=<SQL文件>`,无 `ERROR` 后再交付。
|
|
@@ -140,7 +140,7 @@
|
|
|
140
140
|
## 确认卡片 2:元数据确认
|
|
141
141
|
|
|
142
142
|
- 确认状态:✔️/✖️
|
|
143
|
-
- 已通过 `$ok-cosmic` 元数据能力确认:`
|
|
143
|
+
- 已通过 `$ok-cosmic` 元数据能力确认:`kd_cosmic_metadata sql=true`
|
|
144
144
|
- 实际查询命令/依据:
|
|
145
145
|
- 确认结果:
|
|
146
146
|
|
|
@@ -254,7 +254,7 @@
|
|
|
254
254
|
|
|
255
255
|
## 生成 SQL 示例
|
|
256
256
|
|
|
257
|
-
> 示例仅用于展示输出形态。示例里的表名、字段名、枚举值均假设已通过 `$ok-cosmic` 元数据能力(`
|
|
257
|
+
> 示例仅用于展示输出形态。示例里的表名、字段名、枚举值均假设已通过 `$ok-cosmic` 元数据能力(`kd_cosmic_metadata sql=true`)确认,真实生成时不得照搬。示例假设当前生成时间为 `202604301148`。
|
|
258
258
|
|
|
259
259
|
### SQL 风格约定
|
|
260
260
|
|
|
@@ -1,336 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env python3
|
|
2
|
-
"""
|
|
3
|
-
金蝶代码问题模式匹配器
|
|
4
|
-
用于快速识别代码中的严重问题
|
|
5
|
-
|
|
6
|
-
使用方法:
|
|
7
|
-
python pattern-matcher.py <file_path>
|
|
8
|
-
|
|
9
|
-
输出:
|
|
10
|
-
- P0 级问题(阻断/严重)
|
|
11
|
-
- P1 级问题(高危/性能)
|
|
12
|
-
- P2 级问题(规范/建议)
|
|
13
|
-
"""
|
|
14
|
-
|
|
15
|
-
import re
|
|
16
|
-
import sys
|
|
17
|
-
from typing import List, Tuple, Dict, Optional
|
|
18
|
-
|
|
19
|
-
# ==========================================
|
|
20
|
-
# 上下文验证函数(减少误判)
|
|
21
|
-
# ==========================================
|
|
22
|
-
|
|
23
|
-
def _is_in_try_with_resources(code: str, match_start: int) -> bool:
|
|
24
|
-
"""检查匹配位置是否在 try-with-resources 块内"""
|
|
25
|
-
# 向前查找最近的 try( 语句
|
|
26
|
-
before = code[:match_start]
|
|
27
|
-
# 查找最近的 try 块
|
|
28
|
-
try_pos = before.rfind('try (')
|
|
29
|
-
try_pos2 = before.rfind('try(')
|
|
30
|
-
try_pos = max(try_pos, try_pos2)
|
|
31
|
-
if try_pos == -1:
|
|
32
|
-
return False
|
|
33
|
-
# 检查 try 和 match 之间是否有闭合的 }
|
|
34
|
-
between = before[try_pos:]
|
|
35
|
-
open_braces = between.count('{') - between.count('}')
|
|
36
|
-
return open_braces > 0
|
|
37
|
-
|
|
38
|
-
def _is_in_comment(code: str, match_start: int) -> bool:
|
|
39
|
-
"""检查匹配位置是否在注释中"""
|
|
40
|
-
# 检查是否在单行注释中
|
|
41
|
-
line_start = code.rfind('\n', 0, match_start) + 1
|
|
42
|
-
line = code[line_start:match_start]
|
|
43
|
-
if '//' in line:
|
|
44
|
-
return True
|
|
45
|
-
# 检查是否在多行注释中
|
|
46
|
-
last_open = code.rfind('/*', 0, match_start)
|
|
47
|
-
if last_open != -1:
|
|
48
|
-
last_close = code.rfind('*/', 0, match_start)
|
|
49
|
-
if last_close < last_open:
|
|
50
|
-
return True
|
|
51
|
-
return False
|
|
52
|
-
|
|
53
|
-
def _is_org_user_id_field(match_text: str) -> bool:
|
|
54
|
-
"""检查是否是组织/用户/部门相关字段(真正的硬编码ID问题)"""
|
|
55
|
-
id_field_patterns = [
|
|
56
|
-
r'"[^"]*org[^"]*"', # 组织相关
|
|
57
|
-
r'"[^"]*user[^"]*"', # 用户相关
|
|
58
|
-
r'"[^"]*dept[^"]*"', # 部门相关
|
|
59
|
-
r'"[^"]*creator[^"]*"', # 创建人
|
|
60
|
-
r'"[^"]*modifier[^"]*"', # 修改人
|
|
61
|
-
r'"[^"]*approver[^"]*"', # 审核人
|
|
62
|
-
]
|
|
63
|
-
for pattern in id_field_patterns:
|
|
64
|
-
if re.search(pattern, match_text, re.IGNORECASE):
|
|
65
|
-
return True
|
|
66
|
-
return False
|
|
67
|
-
|
|
68
|
-
def _is_null_checked_before(code: str, var_name: str, match_start: int) -> bool:
|
|
69
|
-
"""检查变量在使用前是否已做过空检查"""
|
|
70
|
-
before = code[:match_start]
|
|
71
|
-
# 查找最近100个字符内是否有空检查
|
|
72
|
-
check_range = before[-200:] if len(before) > 200 else before
|
|
73
|
-
null_check_patterns = [
|
|
74
|
-
rf'{var_name}\s*!=\s*null',
|
|
75
|
-
rf'{var_name}\s*==\s*null',
|
|
76
|
-
rf'Optional\.ofNullable\s*\(\s*{var_name}',
|
|
77
|
-
rf'if\s*\(\s*{var_name}\s*!=\s*null',
|
|
78
|
-
]
|
|
79
|
-
for pattern in null_check_patterns:
|
|
80
|
-
if re.search(pattern, check_range):
|
|
81
|
-
return True
|
|
82
|
-
return False
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
# ==========================================
|
|
86
|
-
# P0 级问题模式定义(阻断/严重)
|
|
87
|
-
# ==========================================
|
|
88
|
-
|
|
89
|
-
PATTERNS_P0 = [
|
|
90
|
-
# 生命周期问题
|
|
91
|
-
(r'public\s+void\s+initialize\s*\([^)]*\)\s*\{[^}]*addItemClickListener',
|
|
92
|
-
'Initialize 注册监听', 'lifecycle-checklist.md', '内存泄漏,监听器无法正确释放'),
|
|
93
|
-
(r'public\s+void\s+initialize\s*\([^)]*\)\s*\{[^}]*getView\(\)',
|
|
94
|
-
'Initialize 操作 UI', 'lifecycle-checklist.md', '生命周期违规'),
|
|
95
|
-
|
|
96
|
-
# 事务问题
|
|
97
|
-
(r'beforeExecuteOperationTransaction[^}]*SaveServiceHelper\.save',
|
|
98
|
-
'操作插件独立保存破坏事务', 'data-transaction-checklist.md', '事务分离,数据不一致,无法回滚'),
|
|
99
|
-
(r'afterExecuteOperationTransaction[^}]*SaveServiceHelper\.save',
|
|
100
|
-
'操作插件独立保存破坏事务', 'data-transaction-checklist.md', '事务分离,数据不一致'),
|
|
101
|
-
|
|
102
|
-
# 资源泄漏 - DataSet(需上下文验证:排除已在 try-with-resources 中的情况)
|
|
103
|
-
(r'DataSet\s+\w+\s*=\s*QueryServiceHelper\.',
|
|
104
|
-
'DataSet 查询未使用 try-with-resources', 'data-transaction-checklist.md', '连接池耗尽,系统崩溃',
|
|
105
|
-
'check_try_with_resources'),
|
|
106
|
-
(r'DataSet\s+\w+\s*=\s*Algo\.',
|
|
107
|
-
'DataSet (Algo) 未使用 try-with-resources', 'data-transaction-checklist.md', '计算资源泄漏',
|
|
108
|
-
'check_try_with_resources'),
|
|
109
|
-
|
|
110
|
-
# 资源泄漏 - AlgoContext(需上下文验证)
|
|
111
|
-
(r'AlgoContext\s+\w+\s*=\s*Algo\.newContext\(\)',
|
|
112
|
-
'AlgoContext 可能未关闭', 'data-transaction-checklist.md', '计算资源泄漏',
|
|
113
|
-
'check_try_with_resources'),
|
|
114
|
-
|
|
115
|
-
# 硬编码组织/用户 ID(缩小匹配范围,仅匹配 org/user/dept 相关字段)
|
|
116
|
-
(r'\.set\s*\(\s*"[^"]*(org|creator|modifier|approver|dept|user)[^"]*"\s*,\s*\d{4,}L?\s*\)',
|
|
117
|
-
'硬编码组织/用户 ID', 'data-transaction-checklist.md', '跨环境部署失败'),
|
|
118
|
-
|
|
119
|
-
# 空指针风险 - P0级(链式调用未判空)
|
|
120
|
-
(r'getDynamicObject\s*\(\s*"[^"]+"\s*\)\.getPkValue\s*\(',
|
|
121
|
-
'getDynamicObject() 结果未判空调用 getPkValue', 'coding-standard-checklist.md', 'NullPointerException'),
|
|
122
|
-
(r'getDynamicObject\s*\(\s*"[^"]+"\s*\)\.getString\s*\(',
|
|
123
|
-
'getDynamicObject() 结果未判空调用 getString', 'coding-standard-checklist.md', 'NullPointerException'),
|
|
124
|
-
(r'getDynamicObject\s*\(\s*"[^"]+"\s*\)\.getBigDecimal\s*\(',
|
|
125
|
-
'getDynamicObject() 结果未判空调用 getBigDecimal', 'coding-standard-checklist.md', 'NullPointerException'),
|
|
126
|
-
(r'getDynamicObject\s*\(\s*"[^"]+"\s*\)\.getLong\s*\(',
|
|
127
|
-
'getDynamicObject() 结果未判空调用 getLong', 'coding-standard-checklist.md', 'NullPointerException'),
|
|
128
|
-
|
|
129
|
-
# 分布式锁未在 finally 释放
|
|
130
|
-
(r'\.lock\s*\(\s*\)[^}]*(?!finally)',
|
|
131
|
-
'分布式锁可能未在 finally 中释放', 'infra-checklist.md', '锁永久不释放,线程阻塞'),
|
|
132
|
-
]
|
|
133
|
-
|
|
134
|
-
# ==========================================
|
|
135
|
-
# P1 级问题模式定义(高危/性能)
|
|
136
|
-
# ==========================================
|
|
137
|
-
|
|
138
|
-
PATTERNS_P1 = [
|
|
139
|
-
# 性能问题 - UI
|
|
140
|
-
(r'for\s*\([^)]+\)\s*\{[^}]*updateView\s*\(',
|
|
141
|
-
'循环内调用 updateView', 'ui-performance-checklist.md', '界面卡顿'),
|
|
142
|
-
(r'while\s*\([^)]+\)\s*\{[^}]*updateView\s*\(',
|
|
143
|
-
'循环内调用 updateView', 'ui-performance-checklist.md', '界面卡顿'),
|
|
144
|
-
(r'for\s*\([^)]+\)\s*\{[^}]*getFieldIndex\s*\(',
|
|
145
|
-
'循环内获取 FieldIndex', 'ui-performance-checklist.md', '性能损耗'),
|
|
146
|
-
|
|
147
|
-
# 性能问题 - 数据库
|
|
148
|
-
(r'for\s*\([^)]+\)\s*\{[^}]*BusinessDataServiceHelper\.loadSingle',
|
|
149
|
-
'循环内调用 loadSingle', 'data-transaction-checklist.md', 'N+1 查询问题'),
|
|
150
|
-
(r'for\s*\([^)]+\)\s*\{[^}]*BusinessDataServiceHelper\.load\s*\(',
|
|
151
|
-
'循环内调用 load', 'data-transaction-checklist.md', 'N+1 查询问题'),
|
|
152
|
-
(r'for\s*\([^)]+\)\s*\{[^}]*SaveServiceHelper\.save',
|
|
153
|
-
'循环内调用 save', 'data-transaction-checklist.md', '性能问题+事务风险'),
|
|
154
|
-
(r'for\s*\([^)]+\)\s*\{[^}]*QueryServiceHelper\.query',
|
|
155
|
-
'循环内调用 QueryServiceHelper', 'data-transaction-checklist.md', 'N+1 查询问题'),
|
|
156
|
-
(r'for\s*\([^)]+\)\s*\{[^}]*DispatchServiceHelper\.invoke',
|
|
157
|
-
'循环内调用微服务', 'data-transaction-checklist.md', 'N+1 远程调用'),
|
|
158
|
-
|
|
159
|
-
# 空指针风险 - P1级
|
|
160
|
-
(r'getDataEntities\s*\(\s*\)\s*\[\s*\d+\s*\]',
|
|
161
|
-
'getDataEntities() 数组访问未判空', 'coding-standard-checklist.md', 'ArrayIndexOutOfBoundsException'),
|
|
162
|
-
(r'this\.dataEntities\s*\[',
|
|
163
|
-
'dataEntities 数组访问未判空', 'coding-standard-checklist.md', '空指针风险'),
|
|
164
|
-
|
|
165
|
-
# JDK 原生线程
|
|
166
|
-
(r'new\s+Thread\s*\(',
|
|
167
|
-
'使用 JDK 原生 Thread', 'infra-checklist.md', '绕过平台线程管理'),
|
|
168
|
-
(r'Executors\.\s*new\w+Pool',
|
|
169
|
-
'使用 JDK 原生线程池', 'infra-checklist.md', '绕过平台线程管理'),
|
|
170
|
-
|
|
171
|
-
# System.out
|
|
172
|
-
(r'System\s*\.\s*out\s*\.\s*print',
|
|
173
|
-
'使用 System.out 而非平台日志', 'coding-standard-checklist.md', '日志不规范'),
|
|
174
|
-
|
|
175
|
-
# 集合问题
|
|
176
|
-
(r'\.size\s*\(\s*\)\s*>\s*0',
|
|
177
|
-
'建议使用 !isEmpty() 替代 size()>0', 'coding-standard-checklist.md', '代码可读性'),
|
|
178
|
-
|
|
179
|
-
# MQ publisher 未关闭
|
|
180
|
-
(r'createSimplePublisher\s*\([^)]*\)',
|
|
181
|
-
'MQ Publisher 可能未关闭', 'infra-checklist.md', '资源泄漏',
|
|
182
|
-
'check_try_with_resources'),
|
|
183
|
-
]
|
|
184
|
-
|
|
185
|
-
# ==========================================
|
|
186
|
-
# P2 级问题模式定义(规范/建议)
|
|
187
|
-
# ==========================================
|
|
188
|
-
|
|
189
|
-
PATTERNS_P2 = [
|
|
190
|
-
# 编码规范
|
|
191
|
-
(r'private\s+final\s+\w+\s+ZERO\s*=\s*BigDecimal\.ZERO',
|
|
192
|
-
'ZERO 字段应声明为 static final', 'coding-standard-checklist.md', '内存浪费'),
|
|
193
|
-
(r'public\s+\w+\s*\(\s*\)\s*\{\s*super\s*\(\s*\)\s*;\s*\}',
|
|
194
|
-
'冗余的空构造函数', 'coding-standard-checklist.md', '代码冗余'),
|
|
195
|
-
(r'logger\.error\s*\(\s*"[^"]+"\s*\)',
|
|
196
|
-
'异常日志缺少堆栈信息', 'coding-standard-checklist.md', '排查困难'),
|
|
197
|
-
(r'new\s+Object\[\s*0\s*\]',
|
|
198
|
-
'new Object[0] 可简化', 'coding-standard-checklist.md', '代码冗余'),
|
|
199
|
-
|
|
200
|
-
# e.printStackTrace
|
|
201
|
-
(r'\.printStackTrace\s*\(\s*\)',
|
|
202
|
-
'禁止直接调用 printStackTrace', 'coding-standard-checklist.md', '日志不规范'),
|
|
203
|
-
|
|
204
|
-
# 空 catch 块
|
|
205
|
-
(r'catch\s*\(\s*\w+\s+\w+\s*\)\s*\{\s*\}',
|
|
206
|
-
'空 catch 块隐藏异常', 'coding-standard-checklist.md', '异常被吞'),
|
|
207
|
-
|
|
208
|
-
# BigDecimal 问题(降级为 P2 - 仅在 DataSet.Row 场景下才是真正风险)
|
|
209
|
-
(r'row\.getBigDecimal\s*\(\s*"[^"]+"\s*\)\.compareTo',
|
|
210
|
-
'DataSet Row.getBigDecimal() 结果可能为 null', 'coding-standard-checklist.md', '空指针风险'),
|
|
211
|
-
]
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
def match_patterns(code: str, patterns: List[Tuple], level: str) -> List[dict]:
|
|
215
|
-
"""匹配代码中的问题模式,支持上下文验证减少误判"""
|
|
216
|
-
issues = []
|
|
217
|
-
for pattern_data in patterns:
|
|
218
|
-
# 支持带上下文验证的5元组格式和普通4/3元组格式
|
|
219
|
-
context_check = None
|
|
220
|
-
if len(pattern_data) == 5:
|
|
221
|
-
pattern, desc, ref, risk, context_check = pattern_data
|
|
222
|
-
elif len(pattern_data) == 4:
|
|
223
|
-
pattern, desc, ref, risk = pattern_data
|
|
224
|
-
else:
|
|
225
|
-
pattern, desc, ref = pattern_data
|
|
226
|
-
risk = '未指定'
|
|
227
|
-
|
|
228
|
-
matches = re.finditer(pattern, code, re.DOTALL | re.IGNORECASE)
|
|
229
|
-
for match in matches:
|
|
230
|
-
# 跳过注释中的匹配
|
|
231
|
-
if _is_in_comment(code, match.start()):
|
|
232
|
-
continue
|
|
233
|
-
|
|
234
|
-
# 上下文验证:检查是否在 try-with-resources 中
|
|
235
|
-
if context_check == 'check_try_with_resources':
|
|
236
|
-
if _is_in_try_with_resources(code, match.start()):
|
|
237
|
-
continue # 已在 try-with-resources 中,不是问题
|
|
238
|
-
|
|
239
|
-
issues.append({
|
|
240
|
-
'level': level,
|
|
241
|
-
'description': desc,
|
|
242
|
-
'reference': ref,
|
|
243
|
-
'risk': risk,
|
|
244
|
-
'match': match.group()[:100] + '...' if len(match.group()) > 100 else match.group(),
|
|
245
|
-
'start': match.start(),
|
|
246
|
-
'end': match.end()
|
|
247
|
-
})
|
|
248
|
-
return issues
|
|
249
|
-
|
|
250
|
-
def analyze_file(filepath: str) -> List[dict]:
|
|
251
|
-
"""分析单个文件"""
|
|
252
|
-
try:
|
|
253
|
-
with open(filepath, 'r', encoding='utf-8') as f:
|
|
254
|
-
code = f.read()
|
|
255
|
-
|
|
256
|
-
p0_issues = match_patterns(code, PATTERNS_P0, 'P0')
|
|
257
|
-
p1_issues = match_patterns(code, PATTERNS_P1, 'P1')
|
|
258
|
-
p2_issues = match_patterns(code, PATTERNS_P2, 'P2')
|
|
259
|
-
|
|
260
|
-
return p0_issues + p1_issues + p2_issues
|
|
261
|
-
except Exception as e:
|
|
262
|
-
print(f"Error reading file: {e}")
|
|
263
|
-
return []
|
|
264
|
-
|
|
265
|
-
def print_report(issues: List[dict]):
|
|
266
|
-
"""打印格式化的报告"""
|
|
267
|
-
if not issues:
|
|
268
|
-
print("\n✅ 未发现 P0/P1/P2 级问题模式\n")
|
|
269
|
-
return
|
|
270
|
-
|
|
271
|
-
# 按级别分组
|
|
272
|
-
p0 = [i for i in issues if i['level'] == 'P0']
|
|
273
|
-
p1 = [i for i in issues if i['level'] == 'P1']
|
|
274
|
-
p2 = [i for i in issues if i['level'] == 'P2']
|
|
275
|
-
|
|
276
|
-
print(f"\n{'='*60}")
|
|
277
|
-
print(f"📊 模式匹配扫描报告")
|
|
278
|
-
print(f"{'='*60}")
|
|
279
|
-
print(f" 总计发现 {len(issues)} 个潜在问题\n")
|
|
280
|
-
|
|
281
|
-
if p0:
|
|
282
|
-
print(f"🔴 P0 级问题 ({len(p0)} 个):")
|
|
283
|
-
for i, issue in enumerate(p0, 1):
|
|
284
|
-
print(f" {i}. {issue['description']}")
|
|
285
|
-
print(f" 风险: {issue['risk']}")
|
|
286
|
-
print(f" 参考: references/{issue['reference']}")
|
|
287
|
-
print()
|
|
288
|
-
|
|
289
|
-
if p1:
|
|
290
|
-
print(f"🟠 P1 级问题 ({len(p1)} 个):")
|
|
291
|
-
for i, issue in enumerate(p1, 1):
|
|
292
|
-
print(f" {i}. {issue['description']}")
|
|
293
|
-
print(f" 风险: {issue['risk']}")
|
|
294
|
-
print(f" 参考: references/{issue['reference']}")
|
|
295
|
-
print()
|
|
296
|
-
|
|
297
|
-
if p2:
|
|
298
|
-
print(f"🟡 P2 级问题 ({len(p2)} 个):")
|
|
299
|
-
for i, issue in enumerate(p2, 1):
|
|
300
|
-
print(f" {i}. {issue['description']}")
|
|
301
|
-
print(f" 风险: {issue['risk']}")
|
|
302
|
-
print(f" 参考: references/{issue['reference']}")
|
|
303
|
-
print()
|
|
304
|
-
|
|
305
|
-
# 计算评分
|
|
306
|
-
base_score = 100
|
|
307
|
-
score = base_score - len(p0) * 15 - len(p1) * 8 - len(p2) * 3
|
|
308
|
-
score = max(0, score)
|
|
309
|
-
|
|
310
|
-
print(f"{'='*60}")
|
|
311
|
-
print(f"📐 综合评分: {score} 分", end="")
|
|
312
|
-
if score >= 90:
|
|
313
|
-
print(" (优秀)")
|
|
314
|
-
elif score >= 75:
|
|
315
|
-
print(" (良好)")
|
|
316
|
-
elif score >= 60:
|
|
317
|
-
print(" (中等)")
|
|
318
|
-
else:
|
|
319
|
-
print(" (需改进)")
|
|
320
|
-
print(f"{'='*60}\n")
|
|
321
|
-
|
|
322
|
-
def main():
|
|
323
|
-
if len(sys.argv) < 2:
|
|
324
|
-
print("Usage: python pattern-matcher.py <file_path>")
|
|
325
|
-
print("\n示例:")
|
|
326
|
-
print(" python pattern-matcher.py MyPlugin.java")
|
|
327
|
-
sys.exit(1)
|
|
328
|
-
|
|
329
|
-
filepath = sys.argv[1]
|
|
330
|
-
print(f"\n🔍 正在扫描文件: {filepath}")
|
|
331
|
-
|
|
332
|
-
issues = analyze_file(filepath)
|
|
333
|
-
print_report(issues)
|
|
334
|
-
|
|
335
|
-
if __name__ == '__main__':
|
|
336
|
-
main()
|
|
@@ -1,121 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env python3
|
|
2
|
-
"""
|
|
3
|
-
金蝶代码审查评分计算器
|
|
4
|
-
根据发现的问题数量和严重程度计算综合评分
|
|
5
|
-
"""
|
|
6
|
-
|
|
7
|
-
from typing import List, Dict
|
|
8
|
-
|
|
9
|
-
# 问题扣分权重
|
|
10
|
-
WEIGHTS = {
|
|
11
|
-
'P0': 25, # 阻断问题 - 严重扣分
|
|
12
|
-
'P1': 10, # 高危问题 - 中等扣分
|
|
13
|
-
'P2': 3, # 规范问题 - 轻微扣分
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
# 评分等级描述
|
|
17
|
-
GRADE_DESCRIPTIONS = {
|
|
18
|
-
(90, 100): '🟢 优秀 - 代码质量高,可上线',
|
|
19
|
-
(70, 89): '🟡 良好 - 存在改进空间',
|
|
20
|
-
(50, 69): '🟠 一般 - 需要修复部分问题',
|
|
21
|
-
(0, 49): '🔴 差 - 必须修复后才能上线',
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
def calculate_score(issues: List[Dict]) -> int:
|
|
25
|
-
"""
|
|
26
|
-
计算代码评分
|
|
27
|
-
|
|
28
|
-
Args:
|
|
29
|
-
issues: 问题列表,每个问题包含 level 字段
|
|
30
|
-
|
|
31
|
-
Returns:
|
|
32
|
-
评分 (0-100)
|
|
33
|
-
"""
|
|
34
|
-
score = 100
|
|
35
|
-
for issue in issues:
|
|
36
|
-
level = issue.get('level', 'P2')
|
|
37
|
-
score -= WEIGHTS.get(level, 0)
|
|
38
|
-
return max(0, score)
|
|
39
|
-
|
|
40
|
-
def get_grade(score: int) -> str:
|
|
41
|
-
"""
|
|
42
|
-
获取评分等级描述
|
|
43
|
-
|
|
44
|
-
Args:
|
|
45
|
-
score: 评分 (0-100)
|
|
46
|
-
|
|
47
|
-
Returns:
|
|
48
|
-
等级描述
|
|
49
|
-
"""
|
|
50
|
-
for (low, high), desc in GRADE_DESCRIPTIONS.items():
|
|
51
|
-
if low <= score <= high:
|
|
52
|
-
return desc
|
|
53
|
-
return '🔴 差 - 必须修复后才能上线'
|
|
54
|
-
|
|
55
|
-
def get_statistics(issues: List[Dict]) -> Dict:
|
|
56
|
-
"""
|
|
57
|
-
获取问题统计
|
|
58
|
-
|
|
59
|
-
Args:
|
|
60
|
-
issues: 问题列表
|
|
61
|
-
|
|
62
|
-
Returns:
|
|
63
|
-
统计信息字典
|
|
64
|
-
"""
|
|
65
|
-
stats = {
|
|
66
|
-
'total': len(issues),
|
|
67
|
-
'P0': 0,
|
|
68
|
-
'P1': 0,
|
|
69
|
-
'P2': 0,
|
|
70
|
-
}
|
|
71
|
-
for issue in issues:
|
|
72
|
-
level = issue.get('level', 'P2')
|
|
73
|
-
stats[level] = stats.get(level, 0) + 1
|
|
74
|
-
return stats
|
|
75
|
-
|
|
76
|
-
def generate_report(issues: List[Dict], filename: str = '') -> str:
|
|
77
|
-
"""
|
|
78
|
-
生成审查报告摘要
|
|
79
|
-
|
|
80
|
-
Args:
|
|
81
|
-
issues: 问题列表
|
|
82
|
-
filename: 文件名
|
|
83
|
-
|
|
84
|
-
Returns:
|
|
85
|
-
报告摘要字符串
|
|
86
|
-
"""
|
|
87
|
-
stats = get_statistics(issues)
|
|
88
|
-
score = calculate_score(issues)
|
|
89
|
-
grade = get_grade(score)
|
|
90
|
-
|
|
91
|
-
report = f"""
|
|
92
|
-
# 🛡️ 代码审查摘要
|
|
93
|
-
|
|
94
|
-
## 📊 综述
|
|
95
|
-
- **审查文件**: {filename or '未指定'}
|
|
96
|
-
- **发现问题总数**: {stats['total']} (🔴 P0: {stats['P0']} | 🟠 P1: {stats['P1']} | 🟡 P2: {stats['P2']})
|
|
97
|
-
- **综合评分**: {score} 分
|
|
98
|
-
- **评级**: {grade}
|
|
99
|
-
|
|
100
|
-
## 📋 改进建议
|
|
101
|
-
"""
|
|
102
|
-
|
|
103
|
-
if stats['P0'] > 0:
|
|
104
|
-
report += f"- ⚠️ 发现 {stats['P0']} 个 P0 级阻断问题,必须修复后才能上线\n"
|
|
105
|
-
if stats['P1'] > 0:
|
|
106
|
-
report += f"- ⚡ 发现 {stats['P1']} 个 P1 级高危问题,强烈建议修复\n"
|
|
107
|
-
if stats['P2'] > 0:
|
|
108
|
-
report += f"- 📝 发现 {stats['P2']} 个 P2 级规范问题,建议优化\n"
|
|
109
|
-
|
|
110
|
-
return report
|
|
111
|
-
|
|
112
|
-
# 示例用法
|
|
113
|
-
if __name__ == '__main__':
|
|
114
|
-
# 测试数据
|
|
115
|
-
test_issues = [
|
|
116
|
-
{'level': 'P0', 'description': 'Initialize 注册监听'},
|
|
117
|
-
{'level': 'P1', 'description': '循环内调用 updateView'},
|
|
118
|
-
{'level': 'P2', 'description': '硬编码中文字符串'},
|
|
119
|
-
]
|
|
120
|
-
|
|
121
|
-
print(generate_report(test_issues, 'TestPlugin.java'))
|