@hupan56/wlkj 3.1.32 → 3.3.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 (178) hide show
  1. package/bin/cli.js +117 -0
  2. package/package.json +1 -1
  3. package/templates/qoder/agents/insight-planning.md +67 -67
  4. package/templates/qoder/agents/prd-reference.md +47 -47
  5. package/templates/qoder/commands/optional/wl-insight.md +4 -4
  6. package/templates/qoder/commands/optional/wl-report.md +1 -1
  7. package/templates/qoder/commands/optional/wl-spec.md +23 -3
  8. package/templates/qoder/commands/optional/wl-status.md +12 -1
  9. package/templates/qoder/commands/wl-code.md +138 -7
  10. package/templates/qoder/commands/wl-commit.md +12 -1
  11. package/templates/qoder/commands/wl-design.md +70 -6
  12. package/templates/qoder/commands/wl-init.md +27 -0
  13. package/templates/qoder/commands/wl-prd.md +230 -15
  14. package/templates/qoder/commands/wl-req.md +10 -3
  15. package/templates/qoder/commands/wl-search.md +74 -20
  16. package/templates/qoder/commands/wl-task.md +3 -3
  17. package/templates/qoder/commands/wl-test.md +17 -2
  18. package/templates/qoder/contracts/CHANGELOG.md +418 -0
  19. package/templates/qoder/contracts/README.md +180 -0
  20. package/templates/qoder/contracts/code.md +82 -0
  21. package/templates/qoder/contracts/commit.md +86 -0
  22. package/templates/qoder/contracts/contract-header.md +76 -0
  23. package/templates/qoder/contracts/design.md +106 -0
  24. package/templates/qoder/contracts/fallback.md +126 -0
  25. package/templates/qoder/contracts/isolation.md +119 -0
  26. package/templates/qoder/contracts/prd.md +118 -0
  27. package/templates/qoder/contracts/schemas/design-spec.schema.json +46 -0
  28. package/templates/qoder/contracts/schemas/prd.schema.json +36 -0
  29. package/templates/qoder/contracts/schemas/test-cases.schema.json +40 -0
  30. package/templates/qoder/contracts/spec.md +116 -0
  31. package/templates/qoder/contracts/task.md +125 -0
  32. package/templates/qoder/contracts/test.md +112 -0
  33. package/templates/qoder/hooks/post-tool-use.py +61 -0
  34. package/templates/qoder/hooks/session-start.py +34 -66
  35. package/templates/qoder/hooks/stop-eval.py +47 -0
  36. package/templates/qoder/rules/wl-pipeline.md +37 -0
  37. package/templates/qoder/scripts/capability/__pycache__/__init__.cpython-39.pyc +0 -0
  38. package/templates/qoder/scripts/capability/__pycache__/registry.cpython-39.pyc +0 -0
  39. package/templates/qoder/scripts/capability/__pycache__/registry_mcp.cpython-39.pyc +0 -0
  40. package/templates/qoder/scripts/capability/adapters/__init__.py +1 -1
  41. package/templates/qoder/scripts/capability/adapters/__pycache__/__init__.cpython-39.pyc +0 -0
  42. package/templates/qoder/scripts/capability/adapters/__pycache__/cli.cpython-39.pyc +0 -0
  43. package/templates/qoder/scripts/capability/adapters/__pycache__/mcp.cpython-39.pyc +0 -0
  44. package/templates/qoder/scripts/capability/adapters/__pycache__/qw.cpython-39.pyc +0 -0
  45. package/templates/qoder/scripts/capability/adapters/mcp.py +76 -100
  46. package/templates/qoder/scripts/capability/adapters/qw.py +295 -295
  47. package/templates/qoder/scripts/capability/caps/__init__.py +1 -1
  48. package/templates/qoder/scripts/capability/caps/__pycache__/__init__.cpython-39.pyc +0 -0
  49. package/templates/qoder/scripts/capability/caps/__pycache__/context.cpython-39.pyc +0 -0
  50. package/templates/qoder/scripts/capability/caps/__pycache__/cron.cpython-39.pyc +0 -0
  51. package/templates/qoder/scripts/capability/caps/__pycache__/identity.cpython-39.pyc +0 -0
  52. package/templates/qoder/scripts/capability/caps/__pycache__/memory.cpython-39.pyc +0 -0
  53. package/templates/qoder/scripts/capability/caps/__pycache__/notify.cpython-39.pyc +0 -0
  54. package/templates/qoder/scripts/capability/caps/__pycache__/present.cpython-39.pyc +0 -0
  55. package/templates/qoder/scripts/capability/caps/__pycache__/repo.cpython-39.pyc +0 -0
  56. package/templates/qoder/scripts/capability/caps/__pycache__/sandbox.cpython-39.pyc +0 -0
  57. package/templates/qoder/scripts/capability/caps/memory.py +1 -1
  58. package/templates/qoder/scripts/capability/registry.py +21 -23
  59. package/templates/qoder/scripts/capability/registry_mcp.py +69 -5
  60. package/templates/qoder/scripts/capability/smoke_test_report.json +34 -20
  61. package/templates/qoder/scripts/deployment/setup/carriers.py +3 -1
  62. package/templates/qoder/scripts/deployment/setup/init_doctor.py +10 -3
  63. package/templates/qoder/scripts/deployment/setup/install_qoderwork.py +11 -0
  64. package/templates/qoder/scripts/deployment/setup/wlkj_shim.py +104 -0
  65. package/templates/qoder/scripts/domain/__pycache__/__init__.cpython-39.pyc +0 -0
  66. package/templates/qoder/scripts/domain/deployment/deploy_to_test.py +298 -0
  67. package/templates/qoder/scripts/domain/integration/__init__.py +0 -0
  68. package/templates/qoder/scripts/domain/integration/__pycache__/__init__.cpython-39.pyc +0 -0
  69. package/templates/qoder/scripts/domain/integration/__pycache__/return_to_platform.cpython-39.pyc +0 -0
  70. package/templates/qoder/scripts/domain/integration/return_to_platform.py +392 -0
  71. package/templates/qoder/scripts/domain/integration/spec_upload.py +209 -0
  72. package/templates/qoder/scripts/domain/kg/build/kg_build.py +268 -25
  73. package/templates/qoder/scripts/domain/kg/build/kg_incremental.py +108 -3
  74. package/templates/qoder/scripts/domain/kg/build/kg_signatures.py +169 -0
  75. package/templates/qoder/scripts/domain/kg/extract/asset/__init__.py +10 -0
  76. package/templates/qoder/scripts/domain/kg/extract/asset/asset_tree.py +57 -0
  77. package/templates/qoder/scripts/domain/kg/extract/asset/discussion_importer.py +62 -0
  78. package/templates/qoder/scripts/domain/kg/extract/asset/prd_importer.py +146 -0
  79. package/templates/qoder/scripts/domain/kg/extract/asset/prototype_importer.py +64 -0
  80. package/templates/qoder/scripts/domain/kg/extract/asset/returns_importer.py +52 -0
  81. package/templates/qoder/scripts/domain/kg/extract/build_goal3.py +104 -0
  82. package/templates/qoder/scripts/domain/kg/extract/build_goal4.py +55 -0
  83. package/templates/qoder/scripts/domain/kg/extract/build_goal5.py +95 -0
  84. package/templates/qoder/scripts/domain/kg/extract/db/__init__.py +8 -0
  85. package/templates/qoder/scripts/domain/kg/extract/db/data_profile.py +22 -0
  86. package/templates/qoder/scripts/domain/kg/extract/db/fk_extractor.py +55 -0
  87. package/templates/qoder/scripts/domain/kg/extract/db/schema_extractor.py +90 -0
  88. package/templates/qoder/scripts/domain/kg/extract/extract.py +84 -0
  89. package/templates/qoder/scripts/domain/kg/extract/extract.py.bak +430 -0
  90. package/templates/qoder/scripts/domain/kg/extract/inference/__init__.py +9 -0
  91. package/templates/qoder/scripts/domain/kg/extract/inference/community_summarizer.py +206 -0
  92. package/templates/qoder/scripts/domain/kg/extract/inference/embed_builder.py +132 -0
  93. package/templates/qoder/scripts/domain/kg/extract/inference/naming_matcher.py +80 -0
  94. package/templates/qoder/scripts/domain/kg/extract/inference/promote.py +59 -0
  95. package/templates/qoder/scripts/domain/kg/extract/inference/recompute.py +93 -0
  96. package/templates/qoder/scripts/domain/kg/extract/inference/weak_link.py +421 -0
  97. package/templates/qoder/scripts/domain/kg/extract/java/__init__.py +15 -0
  98. package/templates/qoder/scripts/domain/kg/extract/java/_parser.py +271 -0
  99. package/templates/qoder/scripts/domain/kg/extract/java/all.py +145 -0
  100. package/templates/qoder/scripts/domain/kg/extract/java/build_java_to_pg.py +102 -0
  101. package/templates/qoder/scripts/domain/kg/extract/java/call_chain.py +49 -0
  102. package/templates/qoder/scripts/domain/kg/extract/java/class_extractor.py +141 -0
  103. package/templates/qoder/scripts/domain/kg/extract/java/domain_extractor.py +148 -0
  104. package/templates/qoder/scripts/domain/kg/extract/java/dubbo_extractor.py +33 -0
  105. package/templates/qoder/scripts/domain/kg/extract/java/endpoint_extractor.py +36 -0
  106. package/templates/qoder/scripts/domain/kg/extract/java/javadoc_extractor.py +110 -0
  107. package/templates/qoder/scripts/domain/kg/extract/java/llm_cn_filler.py +150 -0
  108. package/templates/qoder/scripts/domain/kg/extract/java/member_extractor.py +157 -0
  109. package/templates/qoder/scripts/domain/kg/extract/java/mybatisplus_extractor.py +34 -0
  110. package/templates/qoder/scripts/domain/kg/extract/java/pg_upsert.py +165 -0
  111. package/templates/qoder/scripts/domain/kg/extract/java/satoken_extractor.py +30 -0
  112. package/templates/qoder/scripts/domain/kg/extract/java/spring_extractor.py +39 -0
  113. package/templates/qoder/scripts/domain/kg/extract/java/validation_extractor.py +33 -0
  114. package/templates/qoder/scripts/domain/kg/extract/mybatis/__init__.py +9 -0
  115. package/templates/qoder/scripts/domain/kg/extract/mybatis/all.py +79 -0
  116. package/templates/qoder/scripts/domain/kg/extract/mybatis/mapper_parser.py +99 -0
  117. package/templates/qoder/scripts/domain/kg/extract/mybatis/relation_builder.py +69 -0
  118. package/templates/qoder/scripts/domain/kg/extract/mybatis/sql_extractor.py +78 -0
  119. package/templates/qoder/scripts/domain/kg/extract/prd/__init__.py +8 -0
  120. package/templates/qoder/scripts/domain/kg/extract/prd/prd_chunk_embed.py +105 -0
  121. package/templates/qoder/scripts/domain/kg/extract/prd/prd_llm_extract.py +153 -0
  122. package/templates/qoder/scripts/domain/kg/extract/prd/req_anchor.py +120 -0
  123. package/templates/qoder/scripts/domain/kg/extract/ts_extract.py +111 -0
  124. package/templates/qoder/scripts/domain/kg/graph/kg_semantic.py +4 -2
  125. package/templates/qoder/scripts/domain/kg/kg.py +42 -5
  126. package/templates/qoder/scripts/domain/kg/search/_remote.py +187 -0
  127. package/templates/qoder/scripts/domain/kg/search/context_pack.py +32 -2
  128. package/templates/qoder/scripts/domain/kg/search/search_index.py +74 -20
  129. package/templates/qoder/scripts/domain/kg/storage/kg_duckdb.py +43 -0
  130. package/templates/qoder/scripts/domain/kg/switch_project.py +159 -0
  131. package/templates/qoder/scripts/domain/kg/sync_repowiki.py +109 -0
  132. package/templates/qoder/scripts/domain/requirement/req.py +134 -28
  133. package/templates/qoder/scripts/domain/task/__pycache__/wlkj_panel.cpython-39.pyc +0 -0
  134. package/templates/qoder/scripts/domain/task/wlkj_panel.py +1348 -0
  135. package/templates/qoder/scripts/engine/poller.py +219 -0
  136. package/templates/qoder/scripts/foundation/__pycache__/__init__.cpython-39.pyc +0 -0
  137. package/templates/qoder/scripts/foundation/core/__pycache__/__init__.cpython-39.pyc +0 -0
  138. package/templates/qoder/scripts/foundation/core/__pycache__/paths.cpython-39.pyc +0 -0
  139. package/templates/qoder/scripts/foundation/core/paths.py +102 -0
  140. package/templates/qoder/scripts/foundation/integrations/active_task.py +2 -1
  141. package/templates/qoder/scripts/orchestration/wlkj.py +4 -0
  142. package/templates/qoder/scripts/protocol/__pycache__/__init__.cpython-39.pyc +0 -0
  143. package/templates/qoder/scripts/protocol/mcp/zentao_mcp_server.py +24 -11
  144. package/templates/qoder/scripts/protocol/transports/__pycache__/__init__.cpython-39.pyc +0 -0
  145. package/templates/qoder/scripts/protocol/transports/__pycache__/base.cpython-39.pyc +0 -0
  146. package/templates/qoder/scripts/protocol/transports/__pycache__/cli.cpython-39.pyc +0 -0
  147. package/templates/qoder/scripts/protocol/transports/__pycache__/http.cpython-39.pyc +0 -0
  148. package/templates/qoder/scripts/protocol/transports/__pycache__/stdio.cpython-39.pyc +0 -0
  149. package/templates/qoder/scripts/protocol/transports/http.py +7 -1
  150. package/templates/qoder/scripts/validation/eval/qwork_harness.py +1 -1
  151. package/templates/qoder/scripts/validation/eval/report-commands.md +2 -2
  152. package/templates/qoder/settings.json +27 -9
  153. package/templates/qoder/skills/design-import/SKILL.md +3 -3
  154. package/templates/qoder/skills/design-review/SKILL.md +1 -1
  155. package/templates/qoder/skills/prd-generator/SKILL.md +4 -4
  156. package/templates/qoder/skills/prd-review/SKILL.md +1 -1
  157. package/templates/qoder/skills/prototype-generator/SKILL.md +3 -3
  158. package/templates/qoder/skills/spec-coder/SKILL.md +1 -1
  159. package/templates/qoder/skills/spec-generator/SKILL.md +80 -23
  160. package/templates/qoder/skills/test-generator/SKILL.md +1 -1
  161. package/templates/qoder/skills/wl-code/SKILL.md +13 -1
  162. package/templates/qoder/skills/wl-commit/SKILL.md +1 -1
  163. package/templates/qoder/skills/wl-design/SKILL.md +6 -6
  164. package/templates/qoder/skills/wl-init/SKILL.md +2 -2
  165. package/templates/qoder/skills/wl-insight/SKILL.md +5 -5
  166. package/templates/qoder/skills/wl-prd/SKILL.md +60 -0
  167. package/templates/qoder/skills/wl-report/SKILL.md +2 -2
  168. package/templates/qoder/skills/wl-search/SKILL.md +1 -1
  169. package/templates/qoder/skills/wl-spec/SKILL.md +2 -2
  170. package/templates/qoder/skills/wl-status/SKILL.md +2 -2
  171. package/templates/qoder/skills/wl-task/SKILL.md +3 -3
  172. package/templates/qoder/skills/wl-test/SKILL.md +2 -2
  173. package/templates/qoder/templates/spec-template.md +124 -0
  174. package/templates/root/AGENTS.md +41 -14
  175. package/templates/qoder/scripts/domain/task/zentao_panel.py +0 -451
  176. package/templates/qoder/skills/wl-prd-full/SKILL.md +0 -121
  177. package/templates/qoder/skills/wl-prd-quick/SKILL.md +0 -50
  178. package/templates/qoder/skills/wl-prd-review/SKILL.md +0 -47
@@ -0,0 +1,271 @@
1
+ # -*- coding: utf-8 -*-
2
+ """tree-sitter Java 引擎 + AST 通用遍历工具。
3
+
4
+ 所有 java/* extractor 共用这个模块,避免重复 parse。
5
+ 一次解析一个文件 → root node → 各 extractor 各自遍历提取所需信息。
6
+
7
+ tree-sitter-java 0.23.5 API: tsj.language() (不是 language_java)
8
+ """
9
+ from __future__ import annotations
10
+ import os
11
+ import hashlib
12
+ from typing import Optional
13
+
14
+ try:
15
+ from tree_sitter import Language, Parser
16
+ import tree_sitter_java as tsj
17
+ _LANG = Language(tsj.language())
18
+ _PARSER = Parser(_LANG)
19
+ AVAILABLE = True
20
+ except Exception as _e: # pragma: no cover
21
+ AVAILABLE = False
22
+ _PARSER = None
23
+ _LAST_ERR = str(_e)
24
+
25
+
26
+ def parse(content: str):
27
+ """解析 Java 源码 → tree-sitter root node(不可用返回 None)。
28
+
29
+ 注意: _PARSER.parse 返回 Tree, 这里取 .root_node 返回 Node。
30
+ """
31
+ if not AVAILABLE or not content:
32
+ return None
33
+ try:
34
+ tree = _PARSER.parse(content.encode('utf-8', errors='replace'))
35
+ return tree.root_node if tree is not None else None
36
+ except Exception:
37
+ return None
38
+
39
+
40
+ # ============ AST 遍历工具 ============
41
+
42
+ def _text(node) -> str:
43
+ """取节点文本(tree-sitter 0.23 node.text 可用)。"""
44
+ try:
45
+ return node.text.decode('utf-8', errors='replace')
46
+ except Exception:
47
+ return ''
48
+
49
+
50
+ def find_all(node, node_type: str):
51
+ """递归找所有指定类型的子节点。"""
52
+ out = []
53
+ if node is None:
54
+ return out
55
+ stack = [node]
56
+ while stack:
57
+ n = stack.pop()
58
+ if n.type == node_type:
59
+ out.append(n)
60
+ for c in n.children:
61
+ stack.append(c)
62
+ return out
63
+
64
+
65
+ def find_first(node, node_type: str):
66
+ """找第一个匹配类型的子节点。"""
67
+ rs = find_all(node, node_type)
68
+ return rs[0] if rs else None
69
+
70
+
71
+ def get_class_decls(root):
72
+ """所有 class_declaration / interface_declaration / enum_declaration。"""
73
+ return (find_all(root, 'class_declaration')
74
+ + find_all(root, 'interface_declaration')
75
+ + find_all(root, 'enum_declaration'))
76
+
77
+
78
+ def get_class_name(cls_node) -> Optional[str]:
79
+ """类名(identifier 子节点)。"""
80
+ for c in cls_node.children:
81
+ if c.type == 'identifier':
82
+ return _text(c)
83
+ return None
84
+
85
+
86
+ def get_superclass(cls_node) -> Optional[str]:
87
+ """父类(extends)。"""
88
+ sup = find_first(cls_node, 'superclass')
89
+ if sup:
90
+ ti = find_first(sup, 'type_identifier') or find_first(sup, 'generic_type')
91
+ if ti:
92
+ return _text(ti).split('<')[0]
93
+ return None
94
+
95
+
96
+ def get_super_interfaces(cls_node) -> list:
97
+ """implements 的接口列表。"""
98
+ out = []
99
+ for tl in find_all(cls_node, 'super_interfaces'):
100
+ for ti in find_all(tl, 'type_identifier'):
101
+ out.append(_text(ti))
102
+ return out
103
+
104
+
105
+ def get_annotations(node) -> dict:
106
+ """提取节点的所有注解 → {注解名(不含@): 参数文本 or ''}。
107
+
108
+ marker_annotation: @Service (无参数)
109
+ annotation: @GetMapping("/x") (有参数)
110
+ """
111
+ anns = {}
112
+ # 注解只在 modifiers / 直接子节点里
113
+ mods = None
114
+ for c in node.children:
115
+ if c.type in ('modifiers', 'annotation', 'marker_annotation'):
116
+ if c.type == 'modifiers':
117
+ mods = c
118
+ else:
119
+ _collect_one_annotation(c, anns)
120
+ if mods:
121
+ for c in mods.children:
122
+ if c.type in ('annotation', 'marker_annotation'):
123
+ _collect_one_annotation(c, anns)
124
+ return anns
125
+
126
+
127
+ def _collect_one_annotation(ann_node, out: dict):
128
+ """收集单个注解。结构:
129
+ marker_annotation: @ + identifier
130
+ annotation: @ + identifier + (可选) annotation_argument_list
131
+ """
132
+ name = None
133
+ arg_text = ''
134
+ for c in ann_node.children:
135
+ if c.type == 'identifier' and name is None:
136
+ name = _text(c)
137
+ elif c.type == 'annotation_argument_list':
138
+ arg_text = _text(c).strip('()').strip()
139
+ if name:
140
+ out[name] = arg_text
141
+
142
+
143
+ def get_fields(cls_node) -> list:
144
+ """类里所有 field_declaration → [{name,type,annotations,is_final,is_static}]。"""
145
+ out = []
146
+ for fd in find_all(cls_node, 'field_declaration'):
147
+ mods = find_first(fd, 'modifiers')
148
+ anns = {}
149
+ is_final = is_static = False
150
+ if mods:
151
+ for c in mods.children:
152
+ if c.type in ('annotation', 'marker_annotation'):
153
+ _collect_one_annotation(c, anns)
154
+ elif c.type == 'final_modifier':
155
+ is_final = True
156
+ elif c.type == 'static' or _text(c) == 'static':
157
+ is_static = True
158
+ # 类型
159
+ ftype = ''
160
+ ti = find_first(fd, 'type_identifier') or find_first(fd, 'generic_type') \
161
+ or find_first(fd, 'array_type') or find_first(fd, 'basic_type')
162
+ if ti:
163
+ ftype = _text(ti).split('<')[0].strip('[]')
164
+ # 字段名(variable_declarator 内 identifier)
165
+ names = [_text(n) for n in find_all(fd, 'identifier')
166
+ if n.parent and n.parent.type == 'variable_declarator']
167
+ # javadoc 注释(字段中文说明)
168
+ comment = _get_leading_comment(fd)
169
+ for nm in names:
170
+ out.append({
171
+ 'name': nm, 'type': ftype, 'annotations': anns,
172
+ 'is_final': is_final, 'is_static': is_static,
173
+ 'comment': comment,
174
+ })
175
+ return out
176
+
177
+
178
+ def get_methods(cls_node) -> list:
179
+ """类里所有 method_declaration → [{name, annotations, return_type, body_node}]。"""
180
+ out = []
181
+ for md in find_all(cls_node, 'method_declaration'):
182
+ name = None
183
+ for c in md.children:
184
+ if c.type == 'identifier' and name is None:
185
+ name = _text(c)
186
+ break
187
+ anns = get_annotations(md)
188
+ # 返回类型
189
+ rtype = ''
190
+ for c in md.children:
191
+ if c.type in ('type_identifier', 'generic_type', 'void_type', 'basic_type', 'array_type'):
192
+ rtype = _text(c).split('<')[0].strip('[]')
193
+ break
194
+ body = find_first(md, 'block')
195
+ out.append({
196
+ 'name': name, 'annotations': anns,
197
+ 'return_type': rtype, 'body_node': body,
198
+ })
199
+ return out
200
+
201
+
202
+ def get_method_calls(body_node) -> list:
203
+ """方法体内所有方法调用 → [{object, method}]。
204
+
205
+ remoteClientService.getById(x) → {object:'remoteClientService', method:'getById'}
206
+ method_invocation 直接子节点顺序: identifier(object) . identifier(method) argument_list
207
+ """
208
+ out = []
209
+ if body_node is None:
210
+ return out
211
+ for mi in find_all(body_node, 'method_invocation'):
212
+ # 取 method_invocation 的直接子 identifier(按出现顺序)
213
+ ids = []
214
+ for c in mi.children:
215
+ if c.type == 'identifier':
216
+ ids.append(_text(c))
217
+ if len(ids) >= 2:
218
+ out.append({'object': ids[0], 'method': ids[1]})
219
+ elif len(ids) == 1:
220
+ out.append({'object': '', 'method': ids[0]})
221
+ return out
222
+
223
+
224
+ def _get_leading_comment(node) -> str:
225
+ """取节点前的 javadoc/块注释(字段中文含义来源)。
226
+
227
+ tree-sitter 的 comment 节点通常是兄弟节点。这里用 prev_sibling 兜底。
228
+ """
229
+ try:
230
+ prev = node.prev_sibling
231
+ if prev and prev.type in ('comment', 'block_comment'):
232
+ txt = _text(prev).strip()
233
+ # 清理 /** ... */ 和 *
234
+ txt = txt.strip('/*').strip()
235
+ lines = [l.strip().lstrip('*').strip() for l in txt.split('\n')]
236
+ return ' '.join(l for l in lines if l)
237
+ except Exception:
238
+ pass
239
+ return ''
240
+
241
+
242
+ # ============ 辅助: entity_id / repo_id ============
243
+
244
+ def repo_id_from_path(filepath: str) -> str:
245
+ """从文件路径解析 repo_id(顶层项目目录名)。
246
+
247
+ data/code/fywl-ics/.../Foo.java → 'fywl-ics'
248
+ data/code/Carmg-H5/.../bar.vue → 'Carmg-H5'
249
+ """
250
+ parts = filepath.replace('\\', '/').split('/')
251
+ for i, p in enumerate(parts):
252
+ if p == 'code' and i + 1 < len(parts):
253
+ return parts[i + 1]
254
+ # 兜底: 取 data/ 之后的第一段
255
+ for i, p in enumerate(parts):
256
+ if p == 'data' and i + 1 < len(parts):
257
+ return parts[i + 1]
258
+ return 'unknown'
259
+
260
+
261
+ def entity_id(repo_id: str, etype: str, canonical: str) -> str:
262
+ """构造实体 id(对齐现有规则: C:EsCrudController / F:fywl-ui:system)。
263
+
264
+ Java 层用: SVC:fywl-ics:FooServiceImpl / MAP:fywl-ics:AssetMapper
265
+ """
266
+ return '%s:%s:%s' % (etype, repo_id, canonical)
267
+
268
+
269
+ def file_fingerprint(content: str) -> str:
270
+ """文件内容 hash(C 方案弱关联重算依赖)。"""
271
+ return hashlib.md5(content.encode('utf-8', errors='replace')).hexdigest()[:16]
@@ -0,0 +1,145 @@
1
+ # -*- coding: utf-8 -*-
2
+ """Java 全栈提取协调器。
3
+
4
+ 单文件一次 tree-sitter 解析 → 多 extractor 各自提取 → 累积 → 小批量 PG upsert。
5
+
6
+ 使用:
7
+ from domain.kg.extract.java.all import extract_repo
8
+ extract_repo(repo_dir='/data/code/fywl-ics', repo_id='fywl-ics')
9
+ """
10
+ from __future__ import annotations
11
+ import os
12
+ import sys
13
+ import glob
14
+ import time
15
+ import json
16
+
17
+ # 路径自举 (兼容被 kg_build / WlkjBridge / 独立脚本调用)
18
+ _THIS = os.path.dirname(os.path.abspath(__file__))
19
+ for _i in range(8):
20
+ _p = os.path.dirname(_THIS)
21
+ if os.path.isfile(os.path.join(_p, 'foundation', 'bootstrap.py')):
22
+ sys.path.insert(0, _p)
23
+ break
24
+ _THIS = _p
25
+
26
+ from domain.kg.extract.java import _parser as P
27
+ from domain.kg.extract.java import class_extractor as ce
28
+ from domain.kg.extract.java import member_extractor as me
29
+ from domain.kg.extract.java import domain_extractor as de
30
+
31
+
32
+ def extract_file(filepath: str, repo_id: str):
33
+ """单文件提取 → (entities, edges)。
34
+
35
+ 一次 parse, 多 extractor 复用 root node。
36
+ """
37
+ try:
38
+ with open(filepath, encoding='utf-8', errors='replace') as f:
39
+ content = f.read()
40
+ except Exception:
41
+ return [], []
42
+ if not content.strip():
43
+ return [], []
44
+
45
+ root = P.parse(content)
46
+ if root is None:
47
+ return [], []
48
+
49
+ # ① 类级提取 (实体 + 注入)
50
+ cls_entities, cls_edges = ce.extract(root, repo_id, filepath)
51
+ # 构造 owner 信息给 member_extractor
52
+ owner_classes = []
53
+ for e in cls_entities:
54
+ owner_classes.append({
55
+ 'name': e['canonical'],
56
+ 'etype': e['type'],
57
+ 'id': e['id'],
58
+ 'anns_dict': e['props'].get('annotations', []),
59
+ })
60
+ # 类级 RequestMapping 在 props.base_path, member 要用
61
+ for oc in owner_classes:
62
+ for e in cls_entities:
63
+ if e['canonical'] == oc['name']:
64
+ # 把类级注解 dict 重建 (从 props.annotations list)
65
+ oc['anns_dict'] = {'RequestMapping': e['props'].get('base_path', '')} if e['props'].get('base_path') else {}
66
+
67
+ # ② 方法级提取 (端点 + 权限 + 调用链)
68
+ mem_entities, mem_edges = me.extract(root, repo_id, filepath, owner_classes)
69
+
70
+ # ③ 领域对象提取 (Entity/DTO + 字段映射 + 校验)
71
+ dom_entities, dom_edges = de.extract(root, repo_id, filepath)
72
+
73
+ all_entities = cls_entities + mem_entities + dom_entities
74
+ all_edges = cls_edges + mem_edges + dom_edges
75
+
76
+ # 给每个实体附 file (member/domain extractor 已加, class_extractor 也在 props.file)
77
+ for e in all_entities:
78
+ if 'props' not in e:
79
+ e['props'] = {}
80
+ if 'file' not in e['props']:
81
+ e['props']['file'] = filepath
82
+
83
+ return all_entities, all_edges
84
+
85
+
86
+ def extract_repo(repo_dir: str, repo_id: str, logger=print):
87
+ """扫整个仓库的 .java → 提取 → 返回 (entities, edges, stats)。
88
+
89
+ 不直接写 PG (写库由调用方决定, 便于测试和复用)。
90
+ """
91
+ t0 = time.time()
92
+ java_files = glob.glob(os.path.join(repo_dir, '**', '*.java'), recursive=True)
93
+ # 跳过 test 目录
94
+ java_files = [f for f in java_files if '/test/' not in f.replace('\\', '/').lower()
95
+ and '\\test\\' not in f.lower()
96
+ and not f.endswith('Test.java')
97
+ and not f.endswith('Tests.java')]
98
+ total_files = len(java_files)
99
+ logger(' [java] 扫描 %d 个 .java 文件 (repo=%s)' % (total_files, repo_id))
100
+
101
+ all_entities, all_edges = [], []
102
+ err_count = 0
103
+ for i, jf in enumerate(java_files):
104
+ try:
105
+ es, eds = extract_file(jf, repo_id)
106
+ all_entities += es
107
+ all_edges += eds
108
+ except Exception as e:
109
+ err_count += 1
110
+ if err_count <= 3:
111
+ logger(' [java] 提取失败 %s: %s' % (jf, str(e)[:60]))
112
+ if (i + 1) % 2000 == 0:
113
+ logger(' [java] 进度 %d/%d (实体%d 边%d, 错%d)' % (
114
+ i + 1, total_files, len(all_entities), len(all_edges), err_count))
115
+
116
+ dt = time.time() - t0
117
+ type_dist = {}
118
+ for e in all_entities:
119
+ type_dist[e['type']] = type_dist.get(e['type'], 0) + 1
120
+ edge_dist = {}
121
+ for e in all_edges:
122
+ edge_dist[e['edge_type']] = edge_dist.get(e['edge_type'], 0) + 1
123
+
124
+ stats = {
125
+ 'files': total_files, 'entities': len(all_entities),
126
+ 'edges': len(all_edges), 'errors': err_count, 'seconds': round(dt, 1),
127
+ 'type_dist': type_dist, 'edge_dist': edge_dist,
128
+ }
129
+ logger(' [java] 完成: %d 实体 / %d 边 / %d 错 / %.1fs' % (
130
+ len(all_entities), len(all_edges), err_count, dt))
131
+ logger(' [java] 实体分布: %s' % type_dist)
132
+ logger(' [java] 边分布: %s' % edge_dist)
133
+ return all_entities, all_edges, stats
134
+
135
+
136
+ if __name__ == '__main__':
137
+ # CLI: 直接对某 repo 跑一次, 不写库, 只看统计
138
+ import argparse
139
+ ap = argparse.ArgumentParser()
140
+ ap.add_argument('repo_dir')
141
+ ap.add_argument('--repo-id', default='')
142
+ args = ap.parse_args()
143
+ rid = args.repo_id or os.path.basename(args.repo_dir.rstrip('/\\'))
144
+ es, eds, st = extract_repo(args.repo_dir, rid)
145
+ print(json.dumps(st, ensure_ascii=False, indent=2))
@@ -0,0 +1,102 @@
1
+ # -*- coding: utf-8 -*-
2
+ """Java 全栈提取 + PG 写入 一体化入口。
3
+
4
+ 被 kg_build.py 或 WlkjBridge.rebuild_kg 调用, 也可独立 CLI 运行:
5
+ python -m domain.kg.extract.java.build_java_to_pg --repo-dir data/code/fywl-ics --repo-id fywl-ics
6
+
7
+ 作用: 扫描 repo 下所有 .java → 提取后端全栈实体/边 → upsert 到 PG。
8
+ 这是 GOAL 2 的集成交付物, 让 11905 个 Java 文件从 0 吸收到全吸收。
9
+ """
10
+ from __future__ import annotations
11
+ import os
12
+ import sys
13
+
14
+ _THIS = os.path.dirname(os.path.abspath(__file__))
15
+ for _i in range(8):
16
+ _p = os.path.dirname(_THIS)
17
+ if os.path.isfile(os.path.join(_p, 'foundation', 'bootstrap.py')):
18
+ sys.path.insert(0, _p)
19
+ break
20
+ _THIS = _p
21
+
22
+ from domain.kg.extract.java.all import extract_repo
23
+ from domain.kg.extract.java import pg_upsert
24
+
25
+
26
+ def build(repo_dir: str, repo_id: str, write_pg: bool = True, logger=print):
27
+ """提取一个 repo 的 Java → 写 PG。
28
+
29
+ Args:
30
+ repo_dir: 代码目录 (data/code/fywl-ics)
31
+ repo_id: 仓库 id (fywl-ics)
32
+ write_pg: True 写库; False 只提取不写 (测试用)
33
+ Returns: dict {stats, upsert_result}
34
+ """
35
+ logger(' [java-build] 开始提取 %s (%s)' % (repo_id, repo_dir))
36
+ entities, edges, stats = extract_repo(repo_dir, repo_id, logger=logger)
37
+
38
+ if write_pg:
39
+ logger(' [java-build] 写入 PG (%d 实体 / %d 边)...' % (len(entities), len(edges)))
40
+ result = pg_upsert.upsert_batch(entities, edges)
41
+ logger(' [java-build] PG 写入: %s' % result)
42
+ # 新鲜度追踪
43
+ try:
44
+ pg_upsert.upsert_freshness(entities, {})
45
+ except Exception as e:
46
+ logger(' [java-build] freshness 写入跳过: %s' % str(e)[:50])
47
+ stats['upsert'] = result
48
+ return stats
49
+
50
+
51
+ def build_all_repos(code_root: str = None, write_pg: bool = True, logger=print):
52
+ """扫 code_root 下所有 repo 目录, 逐个提取。
53
+
54
+ code_root: 默认 data/code (引擎根)
55
+ """
56
+ if code_root is None:
57
+ from foundation.core.paths import PROJECT_ROOT
58
+ code_root = os.path.join(str(PROJECT_ROOT), 'data', 'code')
59
+ if not os.path.isdir(code_root):
60
+ logger(' [java-build] code_root 不存在: %s' % code_root)
61
+ return
62
+
63
+ repos = [d for d in os.listdir(code_root)
64
+ if os.path.isdir(os.path.join(code_root, d)) and not d.startswith('.')]
65
+ logger(' [java-build] 发现 %d 个 repo: %s' % (len(repos), repos))
66
+ total_stats = []
67
+ for rid in repos:
68
+ rdir = os.path.join(code_root, rid)
69
+ # 只处理含 .java 的 repo
70
+ has_java = any(f.endswith('.java') for f in os.listdir(rdir)) or \
71
+ any(f.endswith('.java') for _, _, fs in os.walk(rdir) for f in fs)
72
+ if not has_java:
73
+ logger(' [java-build] %s 无 .java, 跳过' % rid)
74
+ continue
75
+ try:
76
+ st = build(rdir, rid, write_pg=write_pg, logger=logger)
77
+ total_stats.append({'repo': rid, **st})
78
+ except Exception as e:
79
+ logger(' [java-build] %s 失败: %s' % (rid, str(e)[:80]))
80
+ return total_stats
81
+
82
+
83
+ if __name__ == '__main__':
84
+ import argparse
85
+ ap = argparse.ArgumentParser()
86
+ ap.add_argument('--repo-dir', help='单个 repo 目录')
87
+ ap.add_argument('--repo-id', help='repo id')
88
+ ap.add_argument('--all', action='store_true', help='扫所有 repo')
89
+ ap.add_argument('--dry-run', action='store_true', help='只提取不写库')
90
+ args = ap.parse_args()
91
+
92
+ if args.all:
93
+ rs = build_all_repos(write_pg=not args.dry_run)
94
+ import json
95
+ print(json.dumps(rs, ensure_ascii=False, indent=2))
96
+ elif args.repo_dir:
97
+ rid = args.repo_id or os.path.basename(args.repo_dir.rstrip('/\\'))
98
+ st = build(args.repo_dir, rid, write_pg=not args.dry_run)
99
+ import json
100
+ print(json.dumps(st, ensure_ascii=False, indent=2))
101
+ else:
102
+ ap.print_help()
@@ -0,0 +1,49 @@
1
+ # -*- coding: utf-8 -*-
2
+ """调用链提取器(方法体内方法调用 → calls 边)。
3
+
4
+ 职责: 把 Service.method() 内部对 Mapper/其他 Service 的调用提取为 calls 边。
5
+ 这是 get_impact 5 层穿透的关键链路 (Controller→Service→Mapper→Table)。
6
+ 共享 member_extractor 的方法遍历逻辑。
7
+ """
8
+ from __future__ import annotations
9
+ from . import _parser as P
10
+ from . import member_extractor as _me
11
+
12
+
13
+ def extract_calls_in_method(body_node, field_types: dict, owner_id: str, repo_id: str) -> list:
14
+ """从方法体提取调用边。
15
+
16
+ Args:
17
+ body_node: tree-sitter block 节点
18
+ field_types: {字段名: 字段类型} (从类的 field_declaration 解析)
19
+ owner_id: 调用方实体 id
20
+ repo_id: 仓库 id
21
+ Returns:
22
+ [{from_id, to_id, edge_type:'calls', props:{method, callee_field}}]
23
+ """
24
+ edges = []
25
+ if body_node is None:
26
+ return edges
27
+ for call in P.get_method_calls(body_node):
28
+ obj_name = call['object']
29
+ method = call['method']
30
+ if not obj_name:
31
+ continue
32
+ dep_type = field_types.get(obj_name) or _me._guess_type_by_name(obj_name)
33
+ if not dep_type:
34
+ continue
35
+ dep_prefix = _me._guess_dep_prefix(dep_type)
36
+ edges.append({
37
+ 'from_id': owner_id,
38
+ 'to_id': '%s:%s:%s' % (dep_prefix, repo_id, dep_type),
39
+ 'edge_type': 'calls',
40
+ 'from_repo': repo_id, 'to_repo': repo_id,
41
+ 'confidence': 1.0, 'source': 'explicit',
42
+ 'props': {'method': method, 'callee_field': obj_name},
43
+ })
44
+ return edges
45
+
46
+
47
+ def extract(root, repo_id: str, filepath: str, owner_classes: list = None):
48
+ """调用链提取(转发到 member_extractor, calls 边在那里产出)。"""
49
+ return _me.extract(root, repo_id, filepath, owner_classes or [])