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.
Files changed (50) hide show
  1. package/README.md +35 -2
  2. package/dist/cli/kcode.d.ts +1 -0
  3. package/dist/cli/kcode.js +27 -4
  4. package/package.json +1 -1
  5. package/src/cli/kcode.ts +29 -4
  6. package/src/official/kingdee-skills.ts +60 -13
  7. package/src/rules/checker.ts +143 -0
  8. package/vendor/kingdee-skills/kingdee-cosmic-reviewer/SKILL.md +2 -2
  9. package/vendor/kingdee-skills/ok-cosmic/SKILL.md +52 -101
  10. package/vendor/kingdee-skills/ok-cosmic/agents/openai.yaml +4 -4
  11. package/vendor/kingdee-skills/ok-cosmic/manifest.json +21 -20
  12. package/vendor/kingdee-skills/ok-cosmic/ok-cosmic-intro.html +1 -1
  13. package/vendor/kingdee-skills/ok-cosmic/rules/a-layer-rules.json +1 -1
  14. package/vendor/kingdee-skills/ok-cosmic/rules/anti-patterns.md +2 -2
  15. package/vendor/kingdee-skills/ok-cosmic/rules/coding-preferences.md +4 -4
  16. package/vendor/kingdee-skills/ok-cosmic/rules/constraints.md +3 -3
  17. package/vendor/kingdee-skills/ok-cosmic/rules/decision-matrix.md +8 -8
  18. package/vendor/kingdee-skills/ok-cosmic/rules/intent-routing.md +1 -1
  19. package/vendor/kingdee-skills/ok-cosmic/rules/post-check.md +19 -18
  20. package/vendor/kingdee-skills/ok-ksql/SKILL.md +9 -9
  21. package/vendor/kingdee-skills/ok-ksql/manifest.json +2 -1
  22. package/vendor/kingdee-skills/ok-ksql/references/ksql-datafix.md +2 -2
  23. package/vendor/kingdee-skills/kingdee-cosmic-reviewer/scripts/pattern-matcher.py +0 -336
  24. package/vendor/kingdee-skills/kingdee-cosmic-reviewer/scripts/review-score-calculator.py +0 -121
  25. package/vendor/kingdee-skills/ok-cosmic/CHANGELOG.md +0 -295
  26. package/vendor/kingdee-skills/ok-cosmic/README.md +0 -460
  27. package/vendor/kingdee-skills/ok-cosmic/requirements.txt +0 -2
  28. package/vendor/kingdee-skills/ok-cosmic/scripts/config_loader.py +0 -204
  29. package/vendor/kingdee-skills/ok-cosmic/scripts/cosmic-api-knowledge.py +0 -910
  30. package/vendor/kingdee-skills/ok-cosmic/scripts/cosmic-basedata-query.py +0 -359
  31. package/vendor/kingdee-skills/ok-cosmic/scripts/cosmic-config-check.py +0 -181
  32. package/vendor/kingdee-skills/ok-cosmic/scripts/cosmic-extpoints-query.py +0 -389
  33. package/vendor/kingdee-skills/ok-cosmic/scripts/cosmic-form-metadata.py +0 -856
  34. package/vendor/kingdee-skills/ok-cosmic/scripts/cosmic-post-check.py +0 -262
  35. package/vendor/kingdee-skills/ok-cosmic/scripts/cosmic-post-lint.py +0 -293
  36. package/vendor/kingdee-skills/ok-cosmic/scripts/lint/__init__.py +0 -2
  37. package/vendor/kingdee-skills/ok-cosmic/scripts/lint/base.py +0 -393
  38. package/vendor/kingdee-skills/ok-cosmic/scripts/lint/resource_check.py +0 -176
  39. package/vendor/kingdee-skills/ok-cosmic/scripts/lint/scene_check.py +0 -375
  40. package/vendor/kingdee-skills/ok-cosmic/scripts/lint/style_check.py +0 -434
  41. package/vendor/kingdee-skills/ok-cosmic/scripts/lint/verify_check.py +0 -36
  42. package/vendor/kingdee-skills/ok-cosmic/scripts/route_client.py +0 -186
  43. package/vendor/kingdee-skills/ok-cosmic/scripts/script_utils.py +0 -40
  44. package/vendor/kingdee-skills/ok-cosmic/scripts/sqlite_cache.py +0 -142
  45. package/vendor/kingdee-skills/ok-cosmic/setup/cuslib/kd-cd-cosmic-commons.jar +0 -0
  46. package/vendor/kingdee-skills/ok-cosmic/setup/cuslib/kd-cd-cosmic-features.jar +0 -0
  47. package/vendor/kingdee-skills/ok-cosmic/setup/setup-mac.sh +0 -18
  48. package/vendor/kingdee-skills/ok-cosmic/setup/setup-windows.bat +0 -53
  49. package/vendor/kingdee-skills/ok-cosmic/setup/setup.jar +0 -0
  50. package/vendor/kingdee-skills/ok-ksql/scripts/ksql_lint.py +0 -363
@@ -1,375 +0,0 @@
1
- # -*- coding: utf-8 -*-
2
- """场景错配检查 (SCENE-*) — 来源: anti-patterns.md 场景错配表"""
3
-
4
- import re
5
- from typing import List, Optional, Set, Tuple
6
-
7
- from .base import (
8
- LintIssue,
9
- Severity,
10
- analyze_java_context,
11
- analyze_plugin_context,
12
- classify_lines,
13
- code_for_structure,
14
- detect_listener_interfaces,
15
- detect_plugin_type,
16
- parse_java,
17
- )
18
-
19
-
20
- INITIALIZE_UI_PATTERN = re.compile(
21
- r"getView\s*\(\)\s*\.\s*"
22
- r"(setEnable|setVisible|updateView|show\w+|setFocus|setMustInput|setCaption)\s*\("
23
- )
24
- INITIALIZE_LISTENER_PATTERN = re.compile(r"add\w*Listener[s]?\s*\(")
25
- BIND_MUTATION_PATTERN = re.compile(
26
- r"(?:(?:getModel\s*\(\)|\bmodel)\s*\.\s*"
27
- r"(setValue|deleteEntryRow|createNewEntryRow|insertEntryRow|batchCreateNewEntryRow|setDataEntity)\s*\()"
28
- r"|(?:\.\s*setValueFast\s*\()"
29
- )
30
- META_TABLE_PATTERN = re.compile(r"\bt_meta_[A-Za-z0-9_]+\b", re.IGNORECASE)
31
- ENTITY_METADATA_CREATE_PATTERN = re.compile(
32
- r"EntityMetadataCache\s*\.\s*getDataEntityType\s*\([^)]*\)\s*\.\s*createInstance\s*\("
33
- )
34
- # SCENE-012: propertyChanged 中无条件 setValue 检测
35
- _PC_SETVALUE_PATTERN = re.compile(
36
- r"(?:getModel\s*\(\)\s*\.\s*setValue|model\s*\.\s*setValue|\.\s*setValueFast)\s*\("
37
- )
38
- _PC_GUARD_PATTERN = re.compile(r"getProperty\s*\(\s*\)|getChangeSet\s*\(\s*\)")
39
-
40
-
41
- def check(filepath: str, lines: List[str]) -> List[LintIssue]:
42
- """执行场景错配检查,返回问题列表。"""
43
- issues: List[LintIssue] = []
44
- listeners = detect_listener_interfaces(lines)
45
- tree, lang = parse_java(lines)
46
- method_context, _ = analyze_java_context(lines, tree=tree)
47
- plugin_context = analyze_plugin_context(lines)
48
- skip_lines = classify_lines(lines)
49
-
50
- # 有状态上下文
51
- _model_alias_vars: Set[str] = set() # 追踪操作插件中 getModel() 赋值的变量名
52
- current_method: Optional[str] = None
53
-
54
- # ── 逐行检查 ──
55
- for i, line in enumerate(lines):
56
- lineno = i + 1
57
- code_line = code_for_structure(line)
58
- method_name = (method_context[i] or "").lower()
59
- is_op_line = plugin_context[i] == "op"
60
-
61
- if method_context[i] != current_method:
62
- _model_alias_vars.clear()
63
- current_method = method_context[i]
64
-
65
- # SCENE-001: 操作插件不应调 getView()
66
- if is_op_line and not skip_lines[i]:
67
- if re.search(r"this\s*\.\s*getView\s*\(\s*\)", code_line) or re.search(r"getView\(\)", code_line):
68
- if not re.search(r"(log\.|getContextSample\s*\()", code_line):
69
- issues.append(LintIssue(
70
- file=filepath, line=lineno,
71
- severity=Severity.ERROR,
72
- rule_id="SCENE-001",
73
- message="操作插件中调用 getView():操作插件无 UI 上下文",
74
- fix_hint="使用 log 记录日志或 OpUtils.addErrorMessage() 报告错误",
75
- source_line=line.strip(),
76
- ))
77
-
78
- # SCENE-002: 操作插件不应通过 model 操作
79
- if is_op_line and not skip_lines[i]:
80
- if re.search(r"this\s*\.\s*getModel\s*\(\s*\)\s*\.\s*setValue", code_line):
81
- issues.append(LintIssue(
82
- file=filepath, line=lineno,
83
- severity=Severity.ERROR,
84
- rule_id="SCENE-002",
85
- message="操作插件中调用 getModel().setValue():操作插件不通过 model 操作",
86
- fix_hint="直接操作 DynamicObject 数据包: dataEntity.set(\"field\", value)",
87
- source_line=line.strip(),
88
- ))
89
- else:
90
- # 追踪 model 别名: Type varName = this.getModel() 或 getModel()
91
- _model_assign = re.search(
92
- r'\b(\w+)\s*=\s*(?:this\s*\.\s*)?getModel\s*\(\s*\)', code_line
93
- )
94
- if _model_assign:
95
- _model_alias_vars.add(_model_assign.group(1))
96
- # 检测 model 别名调用 setValue
97
- for _mvar in _model_alias_vars:
98
- if re.search(rf'\b{re.escape(_mvar)}\s*\.\s*setValue\s*\(', code_line):
99
- issues.append(LintIssue(
100
- file=filepath, line=lineno,
101
- severity=Severity.ERROR,
102
- rule_id="SCENE-002",
103
- message=f"操作插件中通过 model 变量 '{_mvar}' 调用 setValue():操作插件不通过 model 操作",
104
- fix_hint="直接操作 DynamicObject 数据包: dataEntity.set(\"field\", value)",
105
- source_line=line.strip(),
106
- ))
107
- break
108
-
109
- # SCENE-003: registerListener 中读数据
110
- if method_name == "registerlistener" and re.search(r"(getModel|getValue)\s*\(", code_line) and not skip_lines[i]:
111
- issues.append(LintIssue(
112
- file=filepath, line=lineno,
113
- severity=Severity.WARNING,
114
- rule_id="SCENE-003",
115
- message="在 registerListener 中调用 getValue():此时数据尚未绑定",
116
- fix_hint="推迟到 afterBindData() 中处理",
117
- source_line=line.strip(),
118
- ))
119
-
120
- # SCENE-006: initialize 中注册监听
121
- if method_name == "initialize" and INITIALIZE_LISTENER_PATTERN.search(code_line):
122
- issues.append(LintIssue(
123
- file=filepath, line=lineno,
124
- severity=Severity.ERROR,
125
- rule_id="SCENE-006",
126
- message="禁止在 initialize() 中注册监听事件",
127
- fix_hint="将 addXxxListener(...) 挪到 registerListener() 中",
128
- source_line=line.strip(),
129
- ))
130
-
131
- # SCENE-007: initialize 中做 UI 状态逻辑
132
- if method_name == "initialize" and INITIALIZE_UI_PATTERN.search(code_line):
133
- issues.append(LintIssue(
134
- file=filepath, line=lineno,
135
- severity=Severity.ERROR,
136
- rule_id="SCENE-007",
137
- message="禁止在 initialize() 中处理 UI 状态或弹窗逻辑",
138
- fix_hint="将界面控制逻辑挪到 afterBindData() 或正确的 UI 事件中",
139
- source_line=line.strip(),
140
- ))
141
-
142
- # SCENE-008: before/afterBindData 中修改数据对象
143
- if method_name in {"beforebinddata", "afterbinddata"} and BIND_MUTATION_PATTERN.search(code_line):
144
- issues.append(LintIssue(
145
- file=filepath, line=lineno,
146
- severity=Severity.ERROR,
147
- rule_id="SCENE-008",
148
- message="禁止在 beforeBindData()/afterBindData() 中修改数据对象",
149
- fix_hint="将 setValue/分录增删等数据变更挪到 createNewData、afterCreateNewData、propertyChanged 或正确的业务事件",
150
- source_line=line.strip(),
151
- ))
152
-
153
- # SCENE-009: 直接访问平台元数据表
154
- if META_TABLE_PATTERN.search(code_line) and not skip_lines[i]:
155
- issues.append(LintIssue(
156
- file=filepath, line=lineno,
157
- severity=Severity.ERROR,
158
- rule_id="SCENE-009",
159
- message="业务代码禁止直接访问平台元数据表 t_meta_xxx",
160
- fix_hint="使用元数据脚本或平台 API 获取字段/实体信息,不要直接查 t_meta_xxx",
161
- source_line=line.strip(),
162
- ))
163
-
164
- # SCENE-010: 用其他实体 createInstance() 构造引用对象
165
- if ENTITY_METADATA_CREATE_PATTERN.search(code_line):
166
- issues.append(LintIssue(
167
- file=filepath, line=lineno,
168
- severity=Severity.ERROR,
169
- rule_id="SCENE-010",
170
- message="直接用 EntityMetadataCache.getDataEntityType(...).createInstance() 构造对象,容易出现引用类型不一致",
171
- fix_hint="优先通过当前实体属性复杂类型或当前实体元数据 createInstance()",
172
- source_line=line.strip(),
173
- ))
174
-
175
- # ── 方法级检查: propertyChanged 无条件 setValue ──
176
- _check_unguarded_setvalue_in_property_changed(filepath, lines, method_context, skip_lines, issues)
177
-
178
- # ── 文件级检查: Listener 注册 ──
179
- if listeners:
180
- has_register = any(
181
- re.search(r"void\s+registerListener\s*\(", l) for l in lines
182
- )
183
- if not has_register:
184
- # SCENE-004: 实现了 Listener 但没有 registerListener 方法
185
- issues.append(LintIssue(
186
- file=filepath, line=1,
187
- severity=Severity.WARNING,
188
- rule_id="SCENE-004",
189
- message=f"实现了 Listener 接口 ({', '.join(listeners)}) 但未找到 registerListener 方法",
190
- fix_hint="在 registerListener() 中调用 addXxxListener() 注册监听",
191
- ))
192
- else:
193
- # SCENE-005: 有 registerListener 但没有 addXxxListener 调用
194
- # 使用 AST 树精确定位 registerListener 方法体
195
- has_add_listener = False
196
- try:
197
- import tree_sitter
198
- q = tree_sitter.Query(lang, "(method_declaration name: (identifier) @name body: (block) @body)")
199
- for match in q.matches(tree.root_node):
200
- if len(match) > 1 and isinstance(match[1], dict):
201
- match_dict = match[1]
202
- m_name_nodes = match_dict.get("name", [])
203
- m_body_nodes = match_dict.get("body", [])
204
- if m_name_nodes and m_body_nodes:
205
- if m_name_nodes[0].text.decode('utf-8') == "registerListener":
206
- raw_body_text = m_body_nodes[0].text.decode('utf-8')
207
- clean_body_lines = [code_for_structure(l) for l in raw_body_text.splitlines()]
208
- body_text = "\n".join(clean_body_lines)
209
- if re.search(r"add\w*Listener[s]?\s*\(", body_text):
210
- has_add_listener = True
211
- except Exception:
212
- # 降级:如果 AST 失败,假设通过避免误报
213
- has_add_listener = True
214
-
215
- if not has_add_listener:
216
- issues.append(LintIssue(
217
- file=filepath, line=1,
218
- severity=Severity.WARNING,
219
- rule_id="SCENE-005",
220
- message=f"实现了 Listener 接口 ({', '.join(listeners)}) 但 registerListener 中未调用 addXxxListener()",
221
- fix_hint="在 registerListener() 中调用对应的 addXxxListener(this) 注册",
222
- ))
223
-
224
- # ── 文件级检查: 继承型插件 @Override 不调 super ──
225
- is_op, is_ui, is_other = detect_plugin_type(lines)
226
- is_inheritable = is_op or is_ui or is_other
227
- if is_inheritable:
228
- _check_missing_super(filepath, lines, plugin_context, issues, tree=tree, lang=lang)
229
-
230
- return issues
231
-
232
-
233
- def _check_unguarded_setvalue_in_property_changed(
234
- filepath: str, lines: List[str],
235
- method_context: List, skip_lines: List[bool], issues: List[LintIssue],
236
- ):
237
- """SCENE-012: propertyChanged 中调用 setValue 但未判断 getProperty() 或 getChangeSet(),死循环风险。"""
238
- # 收集 propertyChanged 方法体的行范围
239
- ranges: List[Tuple[int, int]] = []
240
- start: int = -1
241
- for i, mc in enumerate(method_context):
242
- if mc and mc.lower() == "propertychanged":
243
- if start < 0:
244
- start = i
245
- else:
246
- if start >= 0:
247
- ranges.append((start, i))
248
- start = -1
249
- if start >= 0:
250
- ranges.append((start, len(lines)))
251
-
252
- for s, e in ranges:
253
- has_set_value = False
254
- has_guard = False
255
- first_sv_line: int = -1
256
- for i in range(s, e):
257
- if skip_lines[i]:
258
- continue
259
- code = code_for_structure(lines[i])
260
- if _PC_SETVALUE_PATTERN.search(code):
261
- if not has_set_value:
262
- first_sv_line = i
263
- has_set_value = True
264
- if _PC_GUARD_PATTERN.search(code):
265
- has_guard = True
266
- if has_set_value and not has_guard:
267
- issues.append(LintIssue(
268
- file=filepath, line=first_sv_line + 1,
269
- severity=Severity.ERROR,
270
- rule_id="SCENE-012",
271
- message="propertyChanged 中调用 setValue 但未判断 e.getProperty().getName(),可能导致死循环(栈溢出)",
272
- fix_hint="先用 e.getProperty().getName().equals(\"目标字段\") 守卫,赋值前比对新旧值避免无限递归",
273
- source_line=lines[first_sv_line].strip(),
274
- ))
275
-
276
-
277
- # 继承型插件生命周期方法(需要调 super 的方法名,全小写)
278
- _LIFECYCLE_METHODS = {
279
- "initialize", "registerlistener", "preopenform",
280
- "createnewdata", "aftercreatenewdata", "loaddata", "afterloaddata",
281
- "beforebinddata", "afterbinddata", "aftercopydata",
282
- "propertychanged", "beforedooperation", "afterdooperation",
283
- "beforeitemclick", "itemclick", "beforeclick", "click",
284
- "beforef7select", "confirmcallback", "closedcallback",
285
- "clientcallback", "beforeclosed", "customevent",
286
- "afteraddrow", "afterdeleterow", "afterdeleteentry", "destory",
287
- "onpreparepropertys", "onaddvalidators",
288
- "beforeexecuteoperationtransaction", "beginoperationtransaction",
289
- "endoperationtransaction", "afterexecuteoperationtransaction",
290
- "onreturnoperation",
291
- "setfilter", "beforedoselectrow",
292
- }
293
-
294
- _OVERRIDE_PATTERN = re.compile(r"@Override")
295
- _METHOD_NAME_PATTERN = re.compile(
296
- r"(?:public|protected)\s+\w[\w<>\[\], ?.]*\s+([A-Za-z_]\w*)\s*\("
297
- )
298
- _SUPER_CALL_PATTERN_TEMPLATE = r"\bsuper\s*\.\s*{method}\s*\("
299
-
300
-
301
- def _check_missing_super(filepath: str, lines: List[str],
302
- plugin_context: List, issues: List[LintIssue],
303
- tree=None, lang=None):
304
- """检测继承型插件中 @Override 生命周期方法是否遗漏 super 调用。"""
305
- try:
306
- import tree_sitter
307
- if tree is None or lang is None:
308
- tree, lang = parse_java(lines)
309
-
310
- q = tree_sitter.Query(lang, """
311
- (method_declaration
312
- name: (identifier) @name
313
- body: (block) @body
314
- ) @method
315
- """)
316
-
317
- for match in q.matches(tree.root_node):
318
- if len(match) < 2 or not isinstance(match[1], dict):
319
- continue
320
- match_dict = match[1]
321
-
322
- method_nodes = match_dict.get("method", [])
323
- if not method_nodes: continue
324
- method_node = method_nodes[0]
325
-
326
- # Check @Override
327
- is_override = False
328
- for child in method_node.children:
329
- if child.type == "modifiers":
330
- for mod in child.children:
331
- if mod.type == "marker_annotation" and "Override" in mod.text.decode('utf-8'):
332
- is_override = True
333
-
334
- if not is_override:
335
- continue
336
-
337
-
338
- # Get Method Name
339
- name_nodes = match_dict.get("name", [])
340
- if not name_nodes: continue
341
- method_name = name_nodes[0].text.decode('utf-8')
342
-
343
- if method_name.lower() not in _LIFECYCLE_METHODS:
344
- continue
345
-
346
- # Check Plugin Context for this method
347
- # Just take the context of the line where method name is
348
- method_line = name_nodes[0].start_point[0]
349
- if method_line < len(plugin_context) and plugin_context[method_line] not in {"op", "ui", "other"}:
350
- continue
351
-
352
- # Check if super.methodName() exists in the body
353
- body_nodes = match_dict.get("body", [])
354
- if not body_nodes: continue
355
- raw_body_text = body_nodes[0].text.decode('utf-8')
356
-
357
- clean_body_lines = [code_for_structure(l) for l in raw_body_text.splitlines()]
358
- body_text = "\n".join(clean_body_lines)
359
-
360
- super_pattern = re.compile(
361
- _SUPER_CALL_PATTERN_TEMPLATE.format(method=re.escape(method_name))
362
- )
363
-
364
- if not super_pattern.search(body_text):
365
- issues.append(LintIssue(
366
- file=filepath, line=method_line + 1,
367
- severity=Severity.WARNING,
368
- rule_id="SCENE-011",
369
- message=f"继承型插件 @Override {method_name}() 未调用 super.{method_name}(),基类初始化逻辑可能不执行",
370
- fix_hint=f"在方法体首行添加 super.{method_name}(...);接口型插件(如 IWorkflowPlugin)无需此调用",
371
- source_line=method_name,
372
- ))
373
- except Exception:
374
- # 降级:如果 AST 失败,不做检查,避免由于解析失败导致的误报
375
- pass