@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.
- package/bin/cli.js +117 -0
- package/package.json +1 -1
- package/templates/qoder/agents/insight-planning.md +67 -67
- package/templates/qoder/agents/prd-reference.md +47 -47
- package/templates/qoder/commands/optional/wl-insight.md +4 -4
- package/templates/qoder/commands/optional/wl-report.md +1 -1
- package/templates/qoder/commands/optional/wl-spec.md +23 -3
- package/templates/qoder/commands/optional/wl-status.md +12 -1
- package/templates/qoder/commands/wl-code.md +138 -7
- package/templates/qoder/commands/wl-commit.md +12 -1
- package/templates/qoder/commands/wl-design.md +70 -6
- package/templates/qoder/commands/wl-init.md +27 -0
- package/templates/qoder/commands/wl-prd.md +230 -15
- package/templates/qoder/commands/wl-req.md +10 -3
- package/templates/qoder/commands/wl-search.md +74 -20
- package/templates/qoder/commands/wl-task.md +3 -3
- package/templates/qoder/commands/wl-test.md +17 -2
- package/templates/qoder/contracts/CHANGELOG.md +418 -0
- package/templates/qoder/contracts/README.md +180 -0
- package/templates/qoder/contracts/code.md +82 -0
- package/templates/qoder/contracts/commit.md +86 -0
- package/templates/qoder/contracts/contract-header.md +76 -0
- package/templates/qoder/contracts/design.md +106 -0
- package/templates/qoder/contracts/fallback.md +126 -0
- package/templates/qoder/contracts/isolation.md +119 -0
- package/templates/qoder/contracts/prd.md +118 -0
- package/templates/qoder/contracts/schemas/design-spec.schema.json +46 -0
- package/templates/qoder/contracts/schemas/prd.schema.json +36 -0
- package/templates/qoder/contracts/schemas/test-cases.schema.json +40 -0
- package/templates/qoder/contracts/spec.md +116 -0
- package/templates/qoder/contracts/task.md +125 -0
- package/templates/qoder/contracts/test.md +112 -0
- package/templates/qoder/hooks/post-tool-use.py +61 -0
- package/templates/qoder/hooks/session-start.py +34 -66
- package/templates/qoder/hooks/stop-eval.py +47 -0
- package/templates/qoder/rules/wl-pipeline.md +37 -0
- package/templates/qoder/scripts/capability/__pycache__/__init__.cpython-39.pyc +0 -0
- package/templates/qoder/scripts/capability/__pycache__/registry.cpython-39.pyc +0 -0
- package/templates/qoder/scripts/capability/__pycache__/registry_mcp.cpython-39.pyc +0 -0
- package/templates/qoder/scripts/capability/adapters/__init__.py +1 -1
- package/templates/qoder/scripts/capability/adapters/__pycache__/__init__.cpython-39.pyc +0 -0
- package/templates/qoder/scripts/capability/adapters/__pycache__/cli.cpython-39.pyc +0 -0
- package/templates/qoder/scripts/capability/adapters/__pycache__/mcp.cpython-39.pyc +0 -0
- package/templates/qoder/scripts/capability/adapters/__pycache__/qw.cpython-39.pyc +0 -0
- package/templates/qoder/scripts/capability/adapters/mcp.py +76 -100
- package/templates/qoder/scripts/capability/adapters/qw.py +295 -295
- package/templates/qoder/scripts/capability/caps/__init__.py +1 -1
- package/templates/qoder/scripts/capability/caps/__pycache__/__init__.cpython-39.pyc +0 -0
- package/templates/qoder/scripts/capability/caps/__pycache__/context.cpython-39.pyc +0 -0
- package/templates/qoder/scripts/capability/caps/__pycache__/cron.cpython-39.pyc +0 -0
- package/templates/qoder/scripts/capability/caps/__pycache__/identity.cpython-39.pyc +0 -0
- package/templates/qoder/scripts/capability/caps/__pycache__/memory.cpython-39.pyc +0 -0
- package/templates/qoder/scripts/capability/caps/__pycache__/notify.cpython-39.pyc +0 -0
- package/templates/qoder/scripts/capability/caps/__pycache__/present.cpython-39.pyc +0 -0
- package/templates/qoder/scripts/capability/caps/__pycache__/repo.cpython-39.pyc +0 -0
- package/templates/qoder/scripts/capability/caps/__pycache__/sandbox.cpython-39.pyc +0 -0
- package/templates/qoder/scripts/capability/caps/memory.py +1 -1
- package/templates/qoder/scripts/capability/registry.py +21 -23
- package/templates/qoder/scripts/capability/registry_mcp.py +69 -5
- package/templates/qoder/scripts/capability/smoke_test_report.json +34 -20
- package/templates/qoder/scripts/deployment/setup/carriers.py +3 -1
- package/templates/qoder/scripts/deployment/setup/init_doctor.py +10 -3
- package/templates/qoder/scripts/deployment/setup/install_qoderwork.py +11 -0
- package/templates/qoder/scripts/deployment/setup/wlkj_shim.py +104 -0
- package/templates/qoder/scripts/domain/__pycache__/__init__.cpython-39.pyc +0 -0
- package/templates/qoder/scripts/domain/deployment/deploy_to_test.py +298 -0
- package/templates/qoder/scripts/domain/integration/__init__.py +0 -0
- package/templates/qoder/scripts/domain/integration/__pycache__/__init__.cpython-39.pyc +0 -0
- package/templates/qoder/scripts/domain/integration/__pycache__/return_to_platform.cpython-39.pyc +0 -0
- package/templates/qoder/scripts/domain/integration/return_to_platform.py +392 -0
- package/templates/qoder/scripts/domain/integration/spec_upload.py +209 -0
- package/templates/qoder/scripts/domain/kg/build/kg_build.py +268 -25
- package/templates/qoder/scripts/domain/kg/build/kg_incremental.py +108 -3
- package/templates/qoder/scripts/domain/kg/build/kg_signatures.py +169 -0
- package/templates/qoder/scripts/domain/kg/extract/asset/__init__.py +10 -0
- package/templates/qoder/scripts/domain/kg/extract/asset/asset_tree.py +57 -0
- package/templates/qoder/scripts/domain/kg/extract/asset/discussion_importer.py +62 -0
- package/templates/qoder/scripts/domain/kg/extract/asset/prd_importer.py +146 -0
- package/templates/qoder/scripts/domain/kg/extract/asset/prototype_importer.py +64 -0
- package/templates/qoder/scripts/domain/kg/extract/asset/returns_importer.py +52 -0
- package/templates/qoder/scripts/domain/kg/extract/build_goal3.py +104 -0
- package/templates/qoder/scripts/domain/kg/extract/build_goal4.py +55 -0
- package/templates/qoder/scripts/domain/kg/extract/build_goal5.py +95 -0
- package/templates/qoder/scripts/domain/kg/extract/db/__init__.py +8 -0
- package/templates/qoder/scripts/domain/kg/extract/db/data_profile.py +22 -0
- package/templates/qoder/scripts/domain/kg/extract/db/fk_extractor.py +55 -0
- package/templates/qoder/scripts/domain/kg/extract/db/schema_extractor.py +90 -0
- package/templates/qoder/scripts/domain/kg/extract/extract.py +84 -0
- package/templates/qoder/scripts/domain/kg/extract/extract.py.bak +430 -0
- package/templates/qoder/scripts/domain/kg/extract/inference/__init__.py +9 -0
- package/templates/qoder/scripts/domain/kg/extract/inference/community_summarizer.py +206 -0
- package/templates/qoder/scripts/domain/kg/extract/inference/embed_builder.py +132 -0
- package/templates/qoder/scripts/domain/kg/extract/inference/naming_matcher.py +80 -0
- package/templates/qoder/scripts/domain/kg/extract/inference/promote.py +59 -0
- package/templates/qoder/scripts/domain/kg/extract/inference/recompute.py +93 -0
- package/templates/qoder/scripts/domain/kg/extract/inference/weak_link.py +421 -0
- package/templates/qoder/scripts/domain/kg/extract/java/__init__.py +15 -0
- package/templates/qoder/scripts/domain/kg/extract/java/_parser.py +271 -0
- package/templates/qoder/scripts/domain/kg/extract/java/all.py +145 -0
- package/templates/qoder/scripts/domain/kg/extract/java/build_java_to_pg.py +102 -0
- package/templates/qoder/scripts/domain/kg/extract/java/call_chain.py +49 -0
- package/templates/qoder/scripts/domain/kg/extract/java/class_extractor.py +141 -0
- package/templates/qoder/scripts/domain/kg/extract/java/domain_extractor.py +148 -0
- package/templates/qoder/scripts/domain/kg/extract/java/dubbo_extractor.py +33 -0
- package/templates/qoder/scripts/domain/kg/extract/java/endpoint_extractor.py +36 -0
- package/templates/qoder/scripts/domain/kg/extract/java/javadoc_extractor.py +110 -0
- package/templates/qoder/scripts/domain/kg/extract/java/llm_cn_filler.py +150 -0
- package/templates/qoder/scripts/domain/kg/extract/java/member_extractor.py +157 -0
- package/templates/qoder/scripts/domain/kg/extract/java/mybatisplus_extractor.py +34 -0
- package/templates/qoder/scripts/domain/kg/extract/java/pg_upsert.py +165 -0
- package/templates/qoder/scripts/domain/kg/extract/java/satoken_extractor.py +30 -0
- package/templates/qoder/scripts/domain/kg/extract/java/spring_extractor.py +39 -0
- package/templates/qoder/scripts/domain/kg/extract/java/validation_extractor.py +33 -0
- package/templates/qoder/scripts/domain/kg/extract/mybatis/__init__.py +9 -0
- package/templates/qoder/scripts/domain/kg/extract/mybatis/all.py +79 -0
- package/templates/qoder/scripts/domain/kg/extract/mybatis/mapper_parser.py +99 -0
- package/templates/qoder/scripts/domain/kg/extract/mybatis/relation_builder.py +69 -0
- package/templates/qoder/scripts/domain/kg/extract/mybatis/sql_extractor.py +78 -0
- package/templates/qoder/scripts/domain/kg/extract/prd/__init__.py +8 -0
- package/templates/qoder/scripts/domain/kg/extract/prd/prd_chunk_embed.py +105 -0
- package/templates/qoder/scripts/domain/kg/extract/prd/prd_llm_extract.py +153 -0
- package/templates/qoder/scripts/domain/kg/extract/prd/req_anchor.py +120 -0
- package/templates/qoder/scripts/domain/kg/extract/ts_extract.py +111 -0
- package/templates/qoder/scripts/domain/kg/graph/kg_semantic.py +4 -2
- package/templates/qoder/scripts/domain/kg/kg.py +42 -5
- package/templates/qoder/scripts/domain/kg/search/_remote.py +187 -0
- package/templates/qoder/scripts/domain/kg/search/context_pack.py +32 -2
- package/templates/qoder/scripts/domain/kg/search/search_index.py +74 -20
- package/templates/qoder/scripts/domain/kg/storage/kg_duckdb.py +43 -0
- package/templates/qoder/scripts/domain/kg/switch_project.py +159 -0
- package/templates/qoder/scripts/domain/kg/sync_repowiki.py +109 -0
- package/templates/qoder/scripts/domain/requirement/req.py +134 -28
- package/templates/qoder/scripts/domain/task/__pycache__/wlkj_panel.cpython-39.pyc +0 -0
- package/templates/qoder/scripts/domain/task/wlkj_panel.py +1348 -0
- package/templates/qoder/scripts/engine/poller.py +219 -0
- package/templates/qoder/scripts/foundation/__pycache__/__init__.cpython-39.pyc +0 -0
- package/templates/qoder/scripts/foundation/core/__pycache__/__init__.cpython-39.pyc +0 -0
- package/templates/qoder/scripts/foundation/core/__pycache__/paths.cpython-39.pyc +0 -0
- package/templates/qoder/scripts/foundation/core/paths.py +102 -0
- package/templates/qoder/scripts/foundation/integrations/active_task.py +2 -1
- package/templates/qoder/scripts/orchestration/wlkj.py +4 -0
- package/templates/qoder/scripts/protocol/__pycache__/__init__.cpython-39.pyc +0 -0
- package/templates/qoder/scripts/protocol/mcp/zentao_mcp_server.py +24 -11
- package/templates/qoder/scripts/protocol/transports/__pycache__/__init__.cpython-39.pyc +0 -0
- package/templates/qoder/scripts/protocol/transports/__pycache__/base.cpython-39.pyc +0 -0
- package/templates/qoder/scripts/protocol/transports/__pycache__/cli.cpython-39.pyc +0 -0
- package/templates/qoder/scripts/protocol/transports/__pycache__/http.cpython-39.pyc +0 -0
- package/templates/qoder/scripts/protocol/transports/__pycache__/stdio.cpython-39.pyc +0 -0
- package/templates/qoder/scripts/protocol/transports/http.py +7 -1
- package/templates/qoder/scripts/validation/eval/qwork_harness.py +1 -1
- package/templates/qoder/scripts/validation/eval/report-commands.md +2 -2
- package/templates/qoder/settings.json +27 -9
- package/templates/qoder/skills/design-import/SKILL.md +3 -3
- package/templates/qoder/skills/design-review/SKILL.md +1 -1
- package/templates/qoder/skills/prd-generator/SKILL.md +4 -4
- package/templates/qoder/skills/prd-review/SKILL.md +1 -1
- package/templates/qoder/skills/prototype-generator/SKILL.md +3 -3
- package/templates/qoder/skills/spec-coder/SKILL.md +1 -1
- package/templates/qoder/skills/spec-generator/SKILL.md +80 -23
- package/templates/qoder/skills/test-generator/SKILL.md +1 -1
- package/templates/qoder/skills/wl-code/SKILL.md +13 -1
- package/templates/qoder/skills/wl-commit/SKILL.md +1 -1
- package/templates/qoder/skills/wl-design/SKILL.md +6 -6
- package/templates/qoder/skills/wl-init/SKILL.md +2 -2
- package/templates/qoder/skills/wl-insight/SKILL.md +5 -5
- package/templates/qoder/skills/wl-prd/SKILL.md +60 -0
- package/templates/qoder/skills/wl-report/SKILL.md +2 -2
- package/templates/qoder/skills/wl-search/SKILL.md +1 -1
- package/templates/qoder/skills/wl-spec/SKILL.md +2 -2
- package/templates/qoder/skills/wl-status/SKILL.md +2 -2
- package/templates/qoder/skills/wl-task/SKILL.md +3 -3
- package/templates/qoder/skills/wl-test/SKILL.md +2 -2
- package/templates/qoder/templates/spec-template.md +124 -0
- package/templates/root/AGENTS.md +41 -14
- package/templates/qoder/scripts/domain/task/zentao_panel.py +0 -451
- package/templates/qoder/skills/wl-prd-full/SKILL.md +0 -121
- package/templates/qoder/skills/wl-prd-quick/SKILL.md +0 -50
- 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 [])
|