@hupan56/wlkj 2.7.12 → 3.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/cli.js +344 -78
- package/package.json +29 -29
- package/templates/.qoder/.runtime/ctx-cache-5660152f1d6dd819.md +23 -0
- package/templates/.qoder/.runtime/ctx-cache-afdce0dac06b25b0.md +23 -0
- package/templates/.qoder/.runtime/search-cache-eae7644e7b122f35.txt +1 -0
- package/templates/.qoder/learning/eval-history.jsonl +28 -0
- package/templates/data/index/wiki-index.json +8 -0
- package/templates/qoder/agents/insight-planning.md +1 -1
- package/templates/qoder/agents/insight-research.md +28 -15
- package/templates/qoder/commands/optional/wl-insight.md +159 -161
- package/templates/qoder/commands/optional/wl-report.md +4 -4
- package/templates/qoder/commands/optional/wl-status.md +2 -2
- package/templates/qoder/commands/wl-code.md +2 -2
- package/templates/qoder/commands/wl-design.md +2 -2
- package/templates/qoder/commands/wl-init.md +3 -3
- package/templates/qoder/commands/wl-prd.md +2 -2
- package/templates/qoder/commands/wl-req.md +43 -0
- package/templates/qoder/commands/wl-search.md +8 -8
- package/templates/qoder/commands/wl-task.md +17 -17
- package/templates/qoder/commands/wl-test.md +41 -15
- package/templates/qoder/config.yaml +17 -1
- package/templates/qoder/hooks/session-start.py +12 -22
- package/templates/qoder/nul +4 -0
- package/templates/qoder/rules/wl-pipeline.md +22 -48
- package/templates/qoder/scripts/README.md +139 -0
- package/templates/qoder/scripts/common/autotest_auth.py +109 -0
- package/templates/qoder/scripts/common/bootstrap.py +145 -0
- package/templates/qoder/scripts/common/check_publish.py +98 -0
- package/templates/qoder/scripts/common/cmd_registry.py +112 -0
- package/templates/qoder/scripts/common/config.py +187 -0
- package/templates/qoder/scripts/common/contract.py +317 -0
- package/templates/qoder/scripts/common/developer.py +2 -1
- package/templates/qoder/scripts/common/feishu.py +10 -9
- package/templates/qoder/scripts/common/guard.py +159 -0
- package/templates/qoder/scripts/common/identity.py +121 -2
- package/templates/qoder/scripts/common/kg_capabilities.py +182 -0
- package/templates/qoder/scripts/common/mcp_base.py +268 -0
- package/templates/qoder/scripts/common/paths.py +187 -1
- package/templates/qoder/scripts/common/result.py +223 -0
- package/templates/qoder/scripts/common/roles.py +60 -0
- package/templates/qoder/scripts/common/task_utils.py +21 -9
- package/templates/qoder/scripts/common/test_extract.py +115 -0
- package/templates/qoder/scripts/kg/__init__.py +11 -0
- package/templates/qoder/scripts/kg/build_entity_registry.py +196 -0
- package/templates/qoder/scripts/kg/build_relations.py +127 -0
- package/templates/qoder/scripts/{build_style_index.py → kg/build_style_index.py} +39 -10
- package/templates/qoder/scripts/kg/build_workflows.py +144 -0
- package/templates/qoder/scripts/{context_pack.py → kg/context_pack.py} +18 -11
- package/templates/qoder/scripts/{enrich_prompt.py → kg/enrich_prompt.py} +232 -226
- package/templates/qoder/scripts/{extract_api_params.py → kg/extract.py} +398 -246
- package/templates/qoder/scripts/{kg.py → kg/kg.py} +638 -708
- package/templates/qoder/scripts/{kg_build.py → kg/kg_build.py} +618 -612
- package/templates/qoder/scripts/{kg_build_db.py → kg/kg_build_db.py} +333 -327
- package/templates/qoder/scripts/{kg_duckdb.py → kg/kg_duckdb.py} +38 -37
- package/templates/qoder/scripts/{kg_incremental.py → kg/kg_incremental.py} +420 -393
- package/templates/qoder/scripts/{kg_link_db.py → kg/kg_link_db.py} +230 -224
- package/templates/qoder/scripts/{kg_semantic.py → kg/kg_semantic.py} +156 -150
- package/templates/qoder/scripts/kg/prefetch.py +359 -0
- package/templates/qoder/scripts/{search_index.py → kg/search_index.py} +70 -14
- package/templates/qoder/scripts/mcp/__init__.py +11 -0
- package/templates/qoder/scripts/{kg_mcp_server.py → mcp/kg_mcp_server.py} +77 -272
- package/templates/qoder/scripts/{lanhu_stdio_wrapper.py → mcp/lanhu_stdio_wrapper.py} +125 -119
- package/templates/qoder/scripts/{check_mcp.py → mcp/mcp_doctor.py} +515 -298
- package/templates/qoder/scripts/{mcp_launcher.py → mcp/mcp_launcher.py} +442 -414
- package/templates/qoder/scripts/{mysql_mcp_server.py → mcp/mysql_mcp_server.py} +347 -396
- package/templates/qoder/scripts/{zentao_mcp_server.py → mcp/zentao_mcp_server.py} +384 -424
- package/templates/qoder/scripts/report/__init__.py +11 -0
- package/templates/qoder/scripts/{add_session.py → report/add_session.py} +250 -244
- package/templates/qoder/scripts/{archive_prd.py → report/archive_prd.py} +383 -377
- package/templates/qoder/scripts/{eval_prd.py → report/eval_prd.py} +73 -11
- package/templates/qoder/scripts/{export.py → report/export.py} +63 -0
- package/templates/qoder/scripts/{fill_prototype.py → report/fill_prototype.py} +6 -0
- package/templates/qoder/scripts/{gen_design_doc.py → report/gen_design_doc.py} +400 -394
- package/templates/qoder/scripts/{learn.py → report/learn.py} +152 -146
- package/templates/qoder/scripts/{learn_aggregate.py → report/learn_aggregate.py} +207 -201
- package/templates/qoder/scripts/{report.py → report/report.py} +287 -281
- package/templates/qoder/scripts/report/req.py +222 -0
- package/templates/qoder/scripts/report/role.py +33 -0
- package/templates/qoder/scripts/{status.py → report/status.py} +634 -628
- package/templates/qoder/scripts/setup/__init__.py +11 -0
- package/templates/qoder/scripts/setup/carriers.py +662 -0
- package/templates/qoder/scripts/{init_doctor.py → setup/init_doctor.py} +63 -26
- package/templates/qoder/scripts/{install_qoderwork.py → setup/install_qoderwork.py} +36 -26
- package/templates/qoder/scripts/{platform_doctor.py → setup/platform_doctor.py} +265 -259
- package/templates/qoder/scripts/{repo_root.py → setup/repo_root.py} +112 -106
- package/templates/qoder/scripts/{setup.py → setup/setup.py} +113 -4
- package/templates/qoder/scripts/{setup_lanhu.py → setup/setup_lanhu.py} +973 -963
- package/templates/qoder/scripts/task/__init__.py +11 -0
- package/templates/qoder/scripts/{git_sync.py → task/git_sync.py} +52 -31
- package/templates/qoder/scripts/{syncgate.py → task/syncgate.py} +6 -0
- package/templates/qoder/scripts/task/task.py +221 -0
- package/templates/qoder/scripts/task/task_lifecycle.py +596 -0
- package/templates/qoder/scripts/task/task_query.py +161 -0
- package/templates/qoder/scripts/task/task_relations.py +424 -0
- package/templates/qoder/scripts/{team_sync.py → task/team_sync.py} +93 -20
- package/templates/qoder/scripts/test/__init__.py +11 -0
- package/templates/qoder/scripts/{autotest.py → test/autotest.py} +1174 -1751
- package/templates/qoder/scripts/{autotest_batch.py → test/autotest_batch.py} +242 -224
- package/templates/qoder/scripts/test/autotest_data.py +675 -0
- package/templates/qoder/scripts/{autotest_run.py → test/autotest_run.py} +309 -297
- package/templates/qoder/scripts/{benchmark.py → test/benchmark.py} +6 -0
- package/templates/qoder/scripts/{kg_auto_login.py → test/kg_auto_login.py} +202 -196
- package/templates/qoder/scripts/{kg_test_runner.py → test/kg_test_runner.py} +7 -1
- package/templates/qoder/scripts/{page_probe.py → test/page_probe.py} +465 -459
- package/templates/qoder/scripts/wlkj.py +116 -0
- package/templates/qoder/settings.json +1 -10
- package/templates/qoder/skills/design-import/SKILL.md +226 -226
- package/templates/qoder/skills/design-review/SKILL.md +82 -82
- package/templates/qoder/skills/prd-generator/SKILL.md +26 -16
- package/templates/qoder/skills/prd-review/SKILL.md +5 -5
- package/templates/qoder/skills/prototype-generator/SKILL.md +256 -256
- package/templates/qoder/skills/spec-coder/SKILL.md +4 -4
- package/templates/qoder/skills/spec-generator/SKILL.md +4 -4
- package/templates/qoder/skills/test-generator/SKILL.md +5 -5
- package/templates/qoder/skills/wl-code/SKILL.md +4 -4
- package/templates/qoder/skills/wl-commit/SKILL.md +4 -4
- package/templates/qoder/skills/wl-design/SKILL.md +3 -3
- package/templates/qoder/skills/wl-init/SKILL.md +8 -8
- package/templates/qoder/skills/wl-insight/SKILL.md +5 -5
- package/templates/qoder/skills/wl-prd-full/SKILL.md +6 -6
- package/templates/qoder/skills/wl-prd-quick/SKILL.md +6 -6
- package/templates/qoder/skills/wl-prd-review/SKILL.md +4 -4
- package/templates/qoder/skills/wl-report/SKILL.md +7 -7
- package/templates/qoder/skills/wl-search/SKILL.md +13 -13
- package/templates/qoder/skills/wl-spec/SKILL.md +5 -5
- package/templates/qoder/skills/wl-status/SKILL.md +5 -5
- package/templates/qoder/skills/wl-task/SKILL.md +6 -6
- package/templates/qoder/skills/wl-test/SKILL.md +102 -39
- package/templates/root/AGENTS.md +39 -40
- package/templates/qoder/hooks/inject-workflow-state.py +0 -169
- package/templates/qoder/scripts/__pycache__/check_mcp_launch.cpython-39.pyc +0 -0
- package/templates/qoder/scripts/__pycache__/install_qoderwork.cpython-39.pyc +0 -0
- package/templates/qoder/scripts/__pycache__/mcp_launcher.cpython-39.pyc +0 -0
- package/templates/qoder/scripts/__pycache__/platform_doctor.cpython-39.pyc +0 -0
- package/templates/qoder/scripts/check_carriers.py +0 -238
- package/templates/qoder/scripts/check_mcp_launch.py +0 -183
- package/templates/qoder/scripts/check_qoderwork_consistency.py +0 -166
- package/templates/qoder/scripts/collect_prds.py +0 -31
- package/templates/qoder/scripts/common/mentions.py +0 -134
- package/templates/qoder/scripts/common/utf8.py +0 -38
- package/templates/qoder/scripts/extract_routes.py +0 -54
- package/templates/qoder/scripts/extract_routes_tree.py +0 -78
- package/templates/qoder/scripts/handoff.py +0 -22
- package/templates/qoder/scripts/init_developer.py +0 -76
- package/templates/qoder/scripts/parse_prds.py +0 -33
- package/templates/qoder/scripts/role.py +0 -51
- package/templates/qoder/scripts/sync_carriers.py +0 -259
- package/templates/qoder/scripts/task.py +0 -1261
- package/templates/qoder/scripts/workspace_init.py +0 -102
- package/templates/qoder/skills/prompt-enrich/SKILL.md +0 -90
- package/templates/qoder/skills/prototype-generator/SKILL.md.zcode-79180-2af4721f-f9a6-412c-88db-c0af680d211b.tmp +0 -0
- /package/templates/qoder/scripts/{secure-ls.js → test/secure-ls.js} +0 -0
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""build_relations.py — 从 trace-chain + code-api 推导关系边, 生成 relations.json。
|
|
3
|
+
|
|
4
|
+
# v3.0 路径自举: 引导到 common/bootstrap, 统一 sys.path 逻辑
|
|
5
|
+
import os as _o, sys as _s
|
|
6
|
+
_cp = _o.path.join(_o.path.dirname(_o.path.dirname(_o.path.abspath(__file__))), 'common')
|
|
7
|
+
if _cp not in _s.path: _s.path.insert(0, _cp)
|
|
8
|
+
from bootstrap import setup; setup()
|
|
9
|
+
|
|
10
|
+
产出 data/index/_build_cache/relations.json, 供 kg_duckdb 导入 edges 表,
|
|
11
|
+
让 kg.py feature 的端点/按钮数据非零, kg.py hop 多跳遍历有边可走。
|
|
12
|
+
|
|
13
|
+
结构 (与 kg_duckdb.bulk_import_from_json 的 edges_data 契约对齐):
|
|
14
|
+
{
|
|
15
|
+
"edges": {
|
|
16
|
+
"click_to_api": [
|
|
17
|
+
{"vue_file":..., "button":..., "handler":..., "endpoint":..., "confidence":...}
|
|
18
|
+
],
|
|
19
|
+
"api_to_controller": {"<endpoint>": "<controller_file>"},
|
|
20
|
+
"controller_to_tests": {"<controller>": [{"test":..., "dep":..., "assertions":[...]}]}
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
数据源:
|
|
25
|
+
- trace-chain.json: traces (vue_file→button→handler→api_fn→endpoint→verb→confidence)
|
|
26
|
+
api_functions (fnName→{url,verb,file})
|
|
27
|
+
- code-api.json: {endpoint: controller_file} (api_to_controller 直接用)
|
|
28
|
+
- test-assertions.json: tests (controller_to_tests 推导)
|
|
29
|
+
"""
|
|
30
|
+
import json
|
|
31
|
+
import os
|
|
32
|
+
import sys
|
|
33
|
+
from datetime import datetime
|
|
34
|
+
|
|
35
|
+
THIS_DIR = os.path.dirname(os.path.abspath(__file__))
|
|
36
|
+
sys.path.insert(0, os.path.join(THIS_DIR, '..'))
|
|
37
|
+
from common.paths import DATA_INDEX_DIR, BUILD_CACHE_DIR
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def load(name):
|
|
41
|
+
for d in (str(BUILD_CACHE_DIR), str(DATA_INDEX_DIR)):
|
|
42
|
+
p = os.path.join(d, name)
|
|
43
|
+
if os.path.isfile(p):
|
|
44
|
+
try:
|
|
45
|
+
return json.load(open(p, encoding='utf-8'))
|
|
46
|
+
except (json.JSONDecodeError, OSError):
|
|
47
|
+
pass
|
|
48
|
+
return {}
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def build():
|
|
52
|
+
tc = load('trace-chain.json')
|
|
53
|
+
api = load('code-api.json')
|
|
54
|
+
|
|
55
|
+
# 1. click_to_api: 直接从 traces 转 (字段名对齐 kg_duckdb 期望)
|
|
56
|
+
click_to_api = []
|
|
57
|
+
for t in tc.get('traces', []):
|
|
58
|
+
if not t.get('endpoint'):
|
|
59
|
+
continue
|
|
60
|
+
click_to_api.append({
|
|
61
|
+
'vue_file': t.get('vue_file', ''),
|
|
62
|
+
'button': t.get('button', ''),
|
|
63
|
+
'handler': t.get('handler', ''),
|
|
64
|
+
'api_fn': t.get('api_fn', ''),
|
|
65
|
+
'endpoint': t.get('endpoint', ''),
|
|
66
|
+
'verb': t.get('verb', ''),
|
|
67
|
+
'confidence': t.get('confidence', ''),
|
|
68
|
+
})
|
|
69
|
+
|
|
70
|
+
# 2. api_to_controller: code-api.json 已经是 {endpoint: controller_file}
|
|
71
|
+
# 过滤掉空值
|
|
72
|
+
api_to_controller = {ep: f for ep, f in api.items() if ep and f}
|
|
73
|
+
|
|
74
|
+
# 3. controller_to_tests: 从 test-assertions.json 推导
|
|
75
|
+
# test 有 package/class 信息, controller 名从 controller_file 提取, 反向匹配 test
|
|
76
|
+
ta = load('test-assertions.json')
|
|
77
|
+
controller_to_tests = {}
|
|
78
|
+
# 建 controller 名 → test 的映射 (test 的 package 含 controller 关键字则关联)
|
|
79
|
+
ctrl_names = set()
|
|
80
|
+
for ctrl_file in api_to_controller.values():
|
|
81
|
+
cname = ctrl_file.split('/')[-1].replace('.java', '')
|
|
82
|
+
if cname:
|
|
83
|
+
ctrl_names.add(cname)
|
|
84
|
+
for t in ta.get('tests', []):
|
|
85
|
+
test_loc = (t.get('package', '') + '.' + t.get('class', '')).lower()
|
|
86
|
+
for cname in ctrl_names:
|
|
87
|
+
if cname.lower() in test_loc:
|
|
88
|
+
controller_to_tests.setdefault(cname, []).append({
|
|
89
|
+
'test': t.get('display_name') or (t.get('class', '') + '.' + t.get('method', '')),
|
|
90
|
+
'dep': '',
|
|
91
|
+
'assertions': t.get('assertions', []),
|
|
92
|
+
})
|
|
93
|
+
break # 一个 test 只关联一个 controller
|
|
94
|
+
|
|
95
|
+
relations = {
|
|
96
|
+
'version': '1.0',
|
|
97
|
+
'updated': datetime.now().isoformat(),
|
|
98
|
+
'edges': {
|
|
99
|
+
'click_to_api': click_to_api,
|
|
100
|
+
'api_to_controller': api_to_controller,
|
|
101
|
+
'controller_to_tests': controller_to_tests,
|
|
102
|
+
},
|
|
103
|
+
'stats': {
|
|
104
|
+
'click_edges': len(click_to_api),
|
|
105
|
+
'api_to_ctrl': len(api_to_controller),
|
|
106
|
+
'ctrl_to_test': len(controller_to_tests),
|
|
107
|
+
},
|
|
108
|
+
}
|
|
109
|
+
return relations
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def main():
|
|
113
|
+
print('Building relations...')
|
|
114
|
+
rel = build()
|
|
115
|
+
out = os.path.join(str(BUILD_CACHE_DIR), 'relations.json')
|
|
116
|
+
os.makedirs(os.path.dirname(out), exist_ok=True)
|
|
117
|
+
with open(out, 'w', encoding='utf-8') as f:
|
|
118
|
+
json.dump(rel, f, ensure_ascii=False, indent=2, sort_keys=True)
|
|
119
|
+
s = rel['stats']
|
|
120
|
+
print(' click_to_api edges: %d' % s['click_edges'])
|
|
121
|
+
print(' api_to_controller: %d' % s['api_to_ctrl'])
|
|
122
|
+
print(' controller_to_tests: %d' % s['ctrl_to_test'])
|
|
123
|
+
print(' Saved to: %s' % out)
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
if __name__ == '__main__':
|
|
127
|
+
main()
|
|
@@ -1,5 +1,11 @@
|
|
|
1
|
-
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
2
|
# -*- coding: utf-8 -*-
|
|
3
|
+
# v3.0 路径自举: 引导到 common/bootstrap, 统一 sys.path 逻辑
|
|
4
|
+
import os as _o, sys as _s
|
|
5
|
+
_cp = _o.path.join(_o.path.dirname(_o.path.dirname(_o.path.abspath(__file__))), 'common')
|
|
6
|
+
if _cp not in _s.path: _s.path.insert(0, _cp)
|
|
7
|
+
from bootstrap import setup; setup()
|
|
8
|
+
|
|
3
9
|
"""
|
|
4
10
|
Build style index from Vue/HTML files.
|
|
5
11
|
Extracts UI patterns: tables, forms, layouts, colors, components.
|
|
@@ -39,7 +45,14 @@ try:
|
|
|
39
45
|
except Exception:
|
|
40
46
|
from common.extract import build_api_fn_table, trace_clicks
|
|
41
47
|
_EXTRACT_ENGINE = 'regex (tree-sitter not installed)'
|
|
42
|
-
|
|
48
|
+
# test_extract 是可选增强层 (test-assertions 索引), 模块可能未提供。
|
|
49
|
+
# 用 lazy import + 降级: 没有就跳过 test-assertions, 不阻断 ui-style 主线构建。
|
|
50
|
+
try:
|
|
51
|
+
from common.test_extract import extract_test_assertions # noqa: E402
|
|
52
|
+
_HAS_TEST_EXTRACT = True
|
|
53
|
+
except ImportError:
|
|
54
|
+
extract_test_assertions = None
|
|
55
|
+
_HAS_TEST_EXTRACT = False
|
|
43
56
|
|
|
44
57
|
|
|
45
58
|
def extract_vue_patterns(filepath, content):
|
|
@@ -377,7 +390,12 @@ def scan_project(proj_name, proj_path):
|
|
|
377
390
|
|
|
378
391
|
for f in files:
|
|
379
392
|
filepath = os.path.join(root, f)
|
|
380
|
-
|
|
393
|
+
# relpath 在 Windows 上对特殊路径 (\\.\nul 等设备/跨盘符) 会抛 ValueError。
|
|
394
|
+
# 这些根本不是源码, 跳过即可, 不让一个异常文件炸掉整个扫描。
|
|
395
|
+
try:
|
|
396
|
+
rel_path = os.path.relpath(filepath, proj_path).replace('\\', '/')
|
|
397
|
+
except ValueError:
|
|
398
|
+
continue
|
|
381
399
|
|
|
382
400
|
if f.endswith('.vue'):
|
|
383
401
|
vue_count += 1
|
|
@@ -415,7 +433,7 @@ def scan_project(proj_name, proj_path):
|
|
|
415
433
|
comp_m = re.findall(r"component:\s*['\"]([^'\"]+)['\"]", content)
|
|
416
434
|
for comp in comp_m:
|
|
417
435
|
form_components[comp] = form_components.get(comp, 0) + 1
|
|
418
|
-
except:
|
|
436
|
+
except Exception:
|
|
419
437
|
pass
|
|
420
438
|
|
|
421
439
|
return {
|
|
@@ -662,13 +680,20 @@ def main():
|
|
|
662
680
|
|
|
663
681
|
# test-assertions.json — E1: 测试代码提取 (Given-When-Then + 断言 + 注入依赖)
|
|
664
682
|
# 第二层知识: 生产代码提取不到的业务预期和断言标准, 补上'怎么算对'天花板
|
|
665
|
-
|
|
683
|
+
# 依赖 common.test_extract (可选模块); 缺失则跳过, 不阻断 ui-style 等主线。
|
|
666
684
|
all_tests = []
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
685
|
+
if not _HAS_TEST_EXTRACT:
|
|
686
|
+
print('Skipping test assertions index (common.test_extract not available - optional)')
|
|
687
|
+
else:
|
|
688
|
+
print('Building test assertions index...')
|
|
689
|
+
for proj in sorted(os.listdir(DATA_CODE)):
|
|
690
|
+
proj_path = os.path.join(DATA_CODE, proj)
|
|
691
|
+
if os.path.isdir(proj_path):
|
|
692
|
+
try:
|
|
693
|
+
tests = extract_test_assertions(proj_path)
|
|
694
|
+
all_tests.extend(tests)
|
|
695
|
+
except Exception as e:
|
|
696
|
+
print(' WARN: test_extract failed for {}: {}'.format(proj, str(e)[:80]))
|
|
672
697
|
test_index = {
|
|
673
698
|
'version': '1.0', 'updated': datetime.now().isoformat(),
|
|
674
699
|
'tests': all_tests,
|
|
@@ -679,6 +704,8 @@ def main():
|
|
|
679
704
|
json.dump(test_index, f, ensure_ascii=False, indent=2, sort_keys=True)
|
|
680
705
|
|
|
681
706
|
# ── 知识图谱层: relations + features + entity-registry + DuckDB ──
|
|
707
|
+
# build_entity_registry 已实现 (FEATURE/ENDPOINT entity 基础层)。
|
|
708
|
+
# build_relations/features/workflows 是增强层, 未实现时 entities 表仍有基础数据。
|
|
682
709
|
print('Building knowledge graph layers...')
|
|
683
710
|
import subprocess
|
|
684
711
|
for script in ['build_relations.py', 'build_features.py',
|
|
@@ -690,6 +717,8 @@ def main():
|
|
|
690
717
|
print(' %s OK' % script)
|
|
691
718
|
except Exception as e:
|
|
692
719
|
print(' %s SKIP: %s' % (script, str(e)[:60]))
|
|
720
|
+
elif script != 'kg_duckdb.py':
|
|
721
|
+
print(' %s N/A (语义层脚本未实现, entities/workflows 表将空)' % script)
|
|
693
722
|
|
|
694
723
|
print()
|
|
695
724
|
print('=== Done ===')
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""build_workflows.py — 从 traces + endpoints 推导业务流程链, 生成 workflow-index.json。
|
|
3
|
+
|
|
4
|
+
# v3.0 路径自举: 引导到 common/bootstrap, 统一 sys.path 逻辑
|
|
5
|
+
import os as _o, sys as _s
|
|
6
|
+
_cp = _o.path.join(_o.path.dirname(_o.path.dirname(_o.path.abspath(__file__))), 'common')
|
|
7
|
+
if _cp not in _s.path: _s.path.insert(0, _cp)
|
|
8
|
+
from bootstrap import setup; setup()
|
|
9
|
+
|
|
10
|
+
产出 data/index/_build_cache/workflow-index.json, 供 kg_duckdb 导入 workflows 表,
|
|
11
|
+
让 kg.py workflow <模块> 能返回该模块的状态流转链 (query→create→audit→...)。
|
|
12
|
+
|
|
13
|
+
workflows 表结构: module, state, seq, button, endpoint
|
|
14
|
+
|
|
15
|
+
推导逻辑 (基于 verb + 路径关键词的启发式状态推断):
|
|
16
|
+
- GET/read/list/page/export → "query" (查询)
|
|
17
|
+
- POST/add/create/save/submit/apply → "create" (新增/提交)
|
|
18
|
+
- PUT/update/edit/audit/approve/reject → "audit" (审核/修改)
|
|
19
|
+
- DELETE/remove/cancel/delete/del → "cancel" (取消/删除)
|
|
20
|
+
|
|
21
|
+
数据源: trace-chain.json (vue_file→button→endpoint→verb) + entity-registry.json
|
|
22
|
+
(module→endpoint 邻接, 用于把 endpoint 归到 module)。
|
|
23
|
+
"""
|
|
24
|
+
import json
|
|
25
|
+
import os
|
|
26
|
+
import sys
|
|
27
|
+
from datetime import datetime
|
|
28
|
+
|
|
29
|
+
THIS_DIR = os.path.dirname(os.path.abspath(__file__))
|
|
30
|
+
sys.path.insert(0, os.path.join(THIS_DIR, '..'))
|
|
31
|
+
from common.paths import DATA_INDEX_DIR, BUILD_CACHE_DIR
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def load(name):
|
|
35
|
+
for d in (str(BUILD_CACHE_DIR), str(DATA_INDEX_DIR)):
|
|
36
|
+
p = os.path.join(d, name)
|
|
37
|
+
if os.path.isfile(p):
|
|
38
|
+
try:
|
|
39
|
+
return json.load(open(p, encoding='utf-8'))
|
|
40
|
+
except (json.JSONDecodeError, OSError):
|
|
41
|
+
pass
|
|
42
|
+
return {}
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
# 状态推断规则 (按优先级, 先匹配先用)
|
|
46
|
+
STATE_RULES = [
|
|
47
|
+
('query', ['get', 'read', 'list', 'page', 'export', 'detail', 'info', 'count']),
|
|
48
|
+
('create', ['post', 'add', 'create', 'save', 'submit', 'apply', 'import', 'write']),
|
|
49
|
+
('audit', ['put', 'update', 'edit', 'audit', 'approve', 'reject', 'review', 'confirm', 'send']),
|
|
50
|
+
('cancel', ['delete', 'remove', 'cancel', 'del', 'revoke', 'close']),
|
|
51
|
+
]
|
|
52
|
+
# 状态排序 (业务流程顺序)
|
|
53
|
+
STATE_ORDER = {'query': 1, 'create': 2, 'audit': 3, 'cancel': 4}
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def infer_state(verb, endpoint):
|
|
57
|
+
"""从 HTTP verb + endpoint 路径推断业务状态。"""
|
|
58
|
+
key = ((verb or '') + ' ' + (endpoint or '')).lower()
|
|
59
|
+
for state, patterns in STATE_RULES:
|
|
60
|
+
for p in patterns:
|
|
61
|
+
if p in key:
|
|
62
|
+
return state
|
|
63
|
+
return 'other'
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def build():
|
|
67
|
+
er = load('entity-registry.json')
|
|
68
|
+
tc = load('trace-chain.json')
|
|
69
|
+
|
|
70
|
+
# module → [(state, button, endpoint)] 从 feature_adjacency + traces 推导
|
|
71
|
+
# feature_adjacency 已有 endpoints + buttons, 配合 trace 的 verb 推断 state
|
|
72
|
+
fa = er.get('feature_adjacency', {})
|
|
73
|
+
entities = er.get('entities', {})
|
|
74
|
+
|
|
75
|
+
# 建 endpoint → (verb, button) 映射 (从 traces)
|
|
76
|
+
ep_info = {} # endpoint -> {verb, buttons:set}
|
|
77
|
+
for t in tc.get('traces', []):
|
|
78
|
+
ep = t.get('endpoint', '')
|
|
79
|
+
if not ep:
|
|
80
|
+
continue
|
|
81
|
+
info = ep_info.setdefault(ep, {'verb': '', 'buttons': set()})
|
|
82
|
+
if t.get('verb') and not info['verb']:
|
|
83
|
+
info['verb'] = t.get('verb', '')
|
|
84
|
+
if t.get('button'):
|
|
85
|
+
info['buttons'].add(t.get('button'))
|
|
86
|
+
|
|
87
|
+
# 按 kg_duckdb.import_workflow_layer 期望的结构组装:
|
|
88
|
+
# {module: {chain: [{state, operations: [{button, endpoint}]}]}}
|
|
89
|
+
workflows = {}
|
|
90
|
+
module_states = {}
|
|
91
|
+
total_steps = 0
|
|
92
|
+
for feat_eid, adj in fa.items():
|
|
93
|
+
if not isinstance(adj, dict):
|
|
94
|
+
continue
|
|
95
|
+
ent = entities.get(feat_eid, {})
|
|
96
|
+
module = ent.get('canonical', feat_eid.replace('F:', ''))
|
|
97
|
+
eps = adj.get('endpoints', [])
|
|
98
|
+
if not eps:
|
|
99
|
+
continue
|
|
100
|
+
# 按 state 分组端点
|
|
101
|
+
state_ops = {} # state -> [{button, endpoint}]
|
|
102
|
+
for ep_eid in eps:
|
|
103
|
+
ep = ep_eid.replace('E:', '', 1)
|
|
104
|
+
info = ep_info.get(ep, {'verb': '', 'buttons': set()})
|
|
105
|
+
state = infer_state(info['verb'], ep)
|
|
106
|
+
btn = next(iter(info['buttons']), '') if info['buttons'] else ''
|
|
107
|
+
state_ops.setdefault(state, []).append({'button': btn, 'endpoint': ep})
|
|
108
|
+
# 按 STATE_ORDER 排序成 chain
|
|
109
|
+
chain = []
|
|
110
|
+
for state in sorted(state_ops.keys(), key=lambda s: STATE_ORDER.get(s, 5)):
|
|
111
|
+
chain.append({'state': state, 'operations': state_ops[state]})
|
|
112
|
+
total_steps += len(state_ops[state])
|
|
113
|
+
if chain:
|
|
114
|
+
workflows[module] = {'chain': chain}
|
|
115
|
+
module_states[module] = len(state_ops)
|
|
116
|
+
|
|
117
|
+
result = {
|
|
118
|
+
'version': '1.0',
|
|
119
|
+
'updated': datetime.now().isoformat(),
|
|
120
|
+
'workflows': workflows,
|
|
121
|
+
'stats': {
|
|
122
|
+
'modules': len(module_states),
|
|
123
|
+
'flow_steps': total_steps,
|
|
124
|
+
'modules_with_multi_state': sum(1 for n in module_states.values() if n > 1),
|
|
125
|
+
},
|
|
126
|
+
}
|
|
127
|
+
return result
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
def main():
|
|
131
|
+
print('Building workflows...')
|
|
132
|
+
wf = build()
|
|
133
|
+
out = os.path.join(str(BUILD_CACHE_DIR), 'workflow-index.json')
|
|
134
|
+
os.makedirs(os.path.dirname(out), exist_ok=True)
|
|
135
|
+
with open(out, 'w', encoding='utf-8') as f:
|
|
136
|
+
json.dump(wf, f, ensure_ascii=False, indent=2, sort_keys=True)
|
|
137
|
+
s = wf['stats']
|
|
138
|
+
print(' modules: %d (%d 有多状态流转)' % (s['modules'], s['modules_with_multi_state']))
|
|
139
|
+
print(' flow steps: %d' % s['flow_steps'])
|
|
140
|
+
print(' Saved to: %s' % out)
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
if __name__ == '__main__':
|
|
144
|
+
main()
|
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
#!/usr/bin/env python3
|
|
2
2
|
# -*- coding: utf-8 -*-
|
|
3
|
+
# v3.0 路径自举: 引导到 common/bootstrap, 统一 sys.path 逻辑
|
|
4
|
+
import os as _o, sys as _s
|
|
5
|
+
_cp = _o.path.join(_o.path.dirname(_o.path.dirname(_o.path.abspath(__file__))), 'common')
|
|
6
|
+
if _cp not in _s.path: _s.path.insert(0, _cp)
|
|
7
|
+
from bootstrap import setup; setup()
|
|
8
|
+
|
|
3
9
|
"""
|
|
4
10
|
QODER Context Pack - 一次调用返回写 PRD/原型所需的全部上下文
|
|
5
11
|
|
|
@@ -34,7 +40,7 @@ except (AttributeError, TypeError, OSError, IOError):
|
|
|
34
40
|
pass
|
|
35
41
|
|
|
36
42
|
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
|
37
|
-
from common.terms import CN_MAP, BUSINESS_PATH_MAP, CN_TO_EN, get_platform_map
|
|
43
|
+
from common.terms import CN_MAP, BUSINESS_PATH_MAP, CN_TO_EN, get_platform_map, expand_chinese_query
|
|
38
44
|
|
|
39
45
|
BASE = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
|
40
46
|
INDEX_DIR = os.path.join(BASE, 'data', 'index')
|
|
@@ -62,11 +68,14 @@ def load(filename):
|
|
|
62
68
|
|
|
63
69
|
|
|
64
70
|
def expand_query(query):
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
71
|
+
"""词扩展: 委托给 expand_chinese_query (三级匹配: 精确/分词/兜底)。
|
|
72
|
+
|
|
73
|
+
旧版只查精确匹配, 复合词如 "保险单OCR" / "异常记录" 查不到就 0 命中。
|
|
74
|
+
现在用 expand_chinese_query, 能拆 "保险单OCR" → "保险" + "ocr",
|
|
75
|
+
"异常记录" → "异常" + "记录", 大幅减少单词查询的 0 命中。
|
|
76
|
+
"""
|
|
77
|
+
words = expand_chinese_query(query)
|
|
78
|
+
# 兜底: 拆不出词就用原词小写 (让英文索引做子串匹配)
|
|
70
79
|
return sorted(set(w.lower() for w in words)) or [query.lower()]
|
|
71
80
|
|
|
72
81
|
|
|
@@ -315,10 +324,9 @@ def _compute_and_print(query, platform, page_type):
|
|
|
315
324
|
|
|
316
325
|
# ── 8. 数据库表结构 (从 kg_db.duckdb, 合并了原来要单独调 MCP 的查询) ──
|
|
317
326
|
try:
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
if
|
|
321
|
-
con = _ddb2.connect(db_path, read_only=True)
|
|
327
|
+
from search_index import _get_kg_db
|
|
328
|
+
con = _get_kg_db() # 惰性缓存的只读连接, 不在此 close (供后续调用复用)
|
|
329
|
+
if con is not None:
|
|
322
330
|
# 关键词匹配表名 (英文词或snake_case, camelCase自动转)
|
|
323
331
|
kw_lower = query.lower()
|
|
324
332
|
# camelCase → snake_case (qualityCase → quality_case)
|
|
@@ -349,7 +357,6 @@ def _compute_and_print(query, platform, page_type):
|
|
|
349
357
|
if ents:
|
|
350
358
|
print(' {} → Entity: {}'.format(
|
|
351
359
|
tn, ', '.join(e[0] for e in ents)))
|
|
352
|
-
con.close()
|
|
353
360
|
except Exception:
|
|
354
361
|
pass # kg_db不存在/被占用 → 跳过, 不影响其它段
|
|
355
362
|
|