@hupan56/wlkj 2.7.12 → 3.1.2
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 +1435 -1107
- package/package.json +29 -29
- 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 +161 -161
- package/templates/qoder/commands/optional/wl-report.md +20 -39
- package/templates/qoder/commands/optional/wl-status.md +4 -4
- package/templates/qoder/commands/wl-code.md +2 -2
- package/templates/qoder/commands/wl-design.md +6 -6
- package/templates/qoder/commands/wl-init.md +3 -3
- package/templates/qoder/commands/wl-prd.md +26 -13
- package/templates/qoder/commands/wl-req.md +43 -0
- package/templates/qoder/commands/wl-search.md +78 -47
- package/templates/qoder/commands/wl-task.md +343 -40
- package/templates/qoder/commands/wl-test.md +43 -17
- package/templates/qoder/config.yaml +52 -14
- package/templates/qoder/hooks/post-tool-use.py +181 -0
- package/templates/qoder/hooks/session-start.py +94 -23
- package/templates/qoder/hooks/stop-eval.py +207 -0
- package/templates/qoder/hooks/user-prompt-submit.py +139 -0
- package/templates/qoder/rules/wl-pipeline.md +85 -49
- package/templates/qoder/scripts/README.md +102 -0
- package/templates/qoder/scripts/capability/__init__.py +26 -0
- package/templates/qoder/scripts/capability/__main__.py +72 -0
- package/templates/qoder/scripts/capability/adapters/__init__.py +29 -0
- package/templates/qoder/scripts/capability/adapters/cli.py +316 -0
- package/templates/qoder/scripts/capability/adapters/mcp.py +334 -0
- package/templates/qoder/scripts/capability/adapters/qw.py +271 -0
- package/templates/qoder/scripts/capability/caps/__init__.py +27 -0
- package/templates/qoder/scripts/capability/caps/context.py +36 -0
- package/templates/qoder/scripts/capability/caps/cron.py +50 -0
- package/templates/qoder/scripts/capability/caps/identity.py +43 -0
- package/templates/qoder/scripts/capability/caps/memory.py +71 -0
- package/templates/qoder/scripts/capability/caps/notify.py +41 -0
- package/templates/qoder/scripts/capability/caps/present.py +48 -0
- package/templates/qoder/scripts/capability/caps/repo.py +31 -0
- package/templates/qoder/scripts/capability/caps/sandbox.py +43 -0
- package/templates/qoder/scripts/capability/chain.py +92 -0
- package/templates/qoder/scripts/capability/memory_chain.py +41 -0
- package/templates/qoder/scripts/capability/registry.py +246 -0
- package/templates/qoder/scripts/capability/registry_mcp.py +250 -0
- package/templates/qoder/scripts/capability/smoke_test.py +211 -0
- package/templates/qoder/scripts/capability/smoke_test_report.json +94 -0
- package/templates/qoder/scripts/deployment/setup/__init__.py +11 -0
- package/templates/qoder/scripts/deployment/setup/carriers.py +669 -0
- package/templates/qoder/scripts/deployment/setup/carriers_verify.py +148 -0
- package/templates/qoder/scripts/{init_doctor.py → deployment/setup/init_doctor.py} +100 -39
- package/templates/qoder/scripts/{install_qoderwork.py → deployment/setup/install_qoderwork.py} +48 -30
- package/templates/qoder/scripts/{platform_doctor.py → deployment/setup/platform_doctor.py} +271 -259
- package/templates/qoder/scripts/{repo_root.py → deployment/setup/repo_root.py} +9 -2
- package/templates/qoder/scripts/{setup.py → deployment/setup/setup.py} +143 -14
- package/templates/qoder/scripts/{setup_lanhu.py → deployment/setup/setup_lanhu.py} +979 -963
- package/templates/qoder/scripts/domain/design/__init__.py +0 -0
- package/templates/qoder/scripts/{fill_prototype.py → domain/design/fill_prototype.py} +17 -5
- package/templates/qoder/scripts/{gen_design_doc.py → domain/design/gen_design_doc.py} +406 -394
- package/templates/qoder/scripts/domain/kg/__init__.py +11 -0
- package/templates/qoder/scripts/domain/kg/build/__init__.py +0 -0
- package/templates/qoder/scripts/domain/kg/build/build_entity_registry.py +201 -0
- package/templates/qoder/scripts/domain/kg/build/build_relations.py +132 -0
- package/templates/qoder/scripts/{build_style_index.py → domain/kg/build/build_style_index.py} +50 -15
- package/templates/qoder/scripts/domain/kg/build/build_workflows.py +149 -0
- package/templates/qoder/scripts/{kg_build.py → domain/kg/build/kg_build.py} +685 -612
- package/templates/qoder/scripts/{kg_build_db.py → domain/kg/build/kg_build_db.py} +338 -327
- package/templates/qoder/scripts/{kg_incremental.py → domain/kg/build/kg_incremental.py} +425 -393
- package/templates/qoder/scripts/{learn_aggregate.py → domain/kg/build/learn_aggregate.py} +212 -201
- package/templates/qoder/scripts/domain/kg/extract/__init__.py +0 -0
- package/templates/qoder/scripts/{common → domain/kg/extract}/extract.py +430 -419
- package/templates/qoder/scripts/domain/kg/extract/test_extract.py +115 -0
- package/templates/qoder/scripts/{common → domain/kg/extract}/ts_extract.py +614 -536
- package/templates/qoder/scripts/domain/kg/graph/__init__.py +0 -0
- package/templates/qoder/scripts/{kg_link_db.py → domain/kg/graph/kg_link_db.py} +235 -224
- package/templates/qoder/scripts/domain/kg/graph/kg_semantic.py +683 -0
- package/templates/qoder/scripts/{kg.py → domain/kg/kg.py} +871 -708
- package/templates/qoder/scripts/domain/kg/kg_capabilities.py +182 -0
- package/templates/qoder/scripts/domain/kg/search/__init__.py +0 -0
- package/templates/qoder/scripts/{context_pack.py → domain/kg/search/context_pack.py} +45 -17
- package/templates/qoder/scripts/{enrich_prompt.py → domain/kg/search/enrich_prompt.py} +238 -226
- package/templates/qoder/scripts/domain/kg/search/prefetch.py +384 -0
- package/templates/qoder/scripts/{common → domain/kg/search}/search_engine.py +207 -205
- package/templates/qoder/scripts/{search_index.py → domain/kg/search/search_index.py} +210 -88
- package/templates/qoder/scripts/domain/kg/server/__init__.py +0 -0
- package/templates/qoder/scripts/domain/kg/server/kgd.py +549 -0
- package/templates/qoder/scripts/domain/kg/server/perf_bench.py +197 -0
- package/templates/qoder/scripts/domain/kg/storage/__init__.py +0 -0
- package/templates/qoder/scripts/{kg_duckdb.py → domain/kg/storage/kg_duckdb.py} +70 -45
- package/templates/qoder/scripts/domain/learning/__init__.py +0 -0
- package/templates/qoder/scripts/{learn.py → domain/learning/learn.py} +157 -146
- package/templates/qoder/scripts/domain/report/__init__.py +11 -0
- package/templates/qoder/scripts/{add_session.py → domain/report/add_session.py} +256 -244
- package/templates/qoder/scripts/{export.py → domain/report/export.py} +72 -4
- package/templates/qoder/scripts/{report.py → domain/report/report.py} +292 -281
- package/templates/qoder/scripts/domain/report/report_snapshot.py +261 -0
- package/templates/qoder/scripts/domain/report/role.py +39 -0
- package/templates/qoder/scripts/{status.py → domain/report/status.py} +737 -628
- package/templates/qoder/scripts/domain/report/status_snapshot.py +191 -0
- package/templates/qoder/scripts/domain/requirement/__init__.py +0 -0
- package/templates/qoder/scripts/{archive_prd.py → domain/requirement/archive_prd.py} +389 -377
- package/templates/qoder/scripts/domain/requirement/req.py +228 -0
- package/templates/qoder/scripts/domain/task/__init__.py +11 -0
- package/templates/qoder/scripts/{git_sync.py → domain/task/git_sync.py} +90 -50
- package/templates/qoder/scripts/{syncgate.py → domain/task/syncgate.py} +18 -7
- package/templates/qoder/scripts/domain/task/task.py +229 -0
- package/templates/qoder/scripts/domain/task/task_lifecycle.py +606 -0
- package/templates/qoder/scripts/domain/task/task_query.py +162 -0
- package/templates/qoder/scripts/domain/task/task_relations.py +425 -0
- package/templates/qoder/scripts/{team_sync.py → domain/task/team_sync.py} +101 -23
- package/templates/qoder/scripts/domain/task/zentao_sync.py +370 -0
- package/templates/qoder/scripts/foundation/__init__.py +0 -0
- package/templates/qoder/scripts/foundation/bootstrap.py +145 -0
- package/templates/qoder/scripts/foundation/core/__init__.py +0 -0
- package/templates/qoder/scripts/foundation/core/cmd_registry.py +112 -0
- package/templates/qoder/scripts/foundation/core/config.py +187 -0
- package/templates/qoder/scripts/{common → foundation/core}/paths.py +706 -400
- package/templates/qoder/scripts/foundation/data/__init__.py +0 -0
- package/templates/qoder/scripts/foundation/data/contract.py +317 -0
- package/templates/qoder/scripts/{common → foundation/data}/events.py +2 -2
- package/templates/qoder/scripts/foundation/data/result.py +223 -0
- package/templates/qoder/scripts/{common → foundation/data}/task_utils.py +427 -392
- package/templates/qoder/scripts/foundation/identity/__init__.py +0 -0
- package/templates/qoder/scripts/foundation/identity/check_publish.py +103 -0
- package/templates/qoder/scripts/{common → foundation/identity}/developer.py +239 -238
- package/templates/qoder/scripts/foundation/identity/guard.py +159 -0
- package/templates/qoder/scripts/{common → foundation/identity}/identity.py +132 -9
- package/templates/qoder/scripts/foundation/identity/roles.py +60 -0
- package/templates/qoder/scripts/foundation/integrations/__init__.py +0 -0
- package/templates/qoder/scripts/{common → foundation/integrations}/active_task.py +30 -15
- package/templates/qoder/scripts/{common → foundation/integrations}/feishu.py +11 -10
- package/templates/qoder/scripts/{common → foundation/integrations}/terms.py +3 -3
- package/templates/qoder/scripts/foundation/integrations/zentao_client.py +220 -0
- package/templates/qoder/scripts/foundation/io/__init__.py +0 -0
- package/templates/qoder/scripts/{common → foundation/io}/filelock.py +1 -1
- package/templates/qoder/scripts/{common → foundation/io}/reqid.py +1 -1
- package/templates/qoder/scripts/foundation/protocol/__init__.py +0 -0
- package/templates/qoder/scripts/foundation/protocol/duckdb_conn.py +39 -0
- package/templates/qoder/scripts/foundation/protocol/mcp_base.py +268 -0
- package/templates/qoder/scripts/foundation/utils/__init__.py +0 -0
- package/templates/qoder/scripts/{common → foundation/utils}/eval_api.py +12 -1
- package/templates/qoder/scripts/{common → foundation/utils}/pip_install.py +23 -6
- package/templates/qoder/scripts/{common → foundation/utils}/platform_guard.py +1 -1
- package/templates/qoder/scripts/orchestration/__init__.py +0 -0
- package/templates/qoder/scripts/orchestration/wlkj.py +185 -0
- package/templates/qoder/scripts/protocol/__init__.py +0 -0
- package/templates/qoder/scripts/protocol/browser/README.md +207 -0
- package/templates/qoder/scripts/protocol/browser/SKILL.md +265 -0
- package/templates/qoder/scripts/protocol/browser/config.env +9 -0
- package/templates/qoder/scripts/protocol/browser/references/cdp-api.md +110 -0
- package/templates/qoder/scripts/protocol/browser/references/migration-2.5.3.md +72 -0
- package/templates/qoder/scripts/protocol/browser/references/site-patterns/.gitkeep +0 -0
- package/templates/qoder/scripts/protocol/browser/scripts/browser-discovery.mjs +138 -0
- package/templates/qoder/scripts/protocol/browser/scripts/cdp-proxy.mjs +672 -0
- package/templates/qoder/scripts/protocol/browser/scripts/check-deps.mjs +206 -0
- package/templates/qoder/scripts/protocol/browser/scripts/find-url.mjs +253 -0
- package/templates/qoder/scripts/protocol/browser/scripts/match-site.mjs +46 -0
- package/templates/qoder/scripts/protocol/browser/templates/config.env.template +9 -0
- package/templates/qoder/scripts/protocol/mcp/__init__.py +11 -0
- package/templates/qoder/scripts/protocol/mcp/kg_mcp_server.py +478 -0
- package/templates/qoder/scripts/{lanhu_stdio_wrapper.py → protocol/mcp/lanhu_stdio_wrapper.py} +131 -119
- package/templates/qoder/scripts/protocol/mcp/mcp_doctor.py +548 -0
- package/templates/qoder/scripts/protocol/mcp/mcp_launcher.py +600 -0
- package/templates/qoder/scripts/protocol/mcp/mysql_mcp_server.py +461 -0
- package/templates/qoder/scripts/protocol/mcp/zentao_mcp_server.py +1920 -0
- package/templates/qoder/scripts/protocol/transports/__init__.py +56 -0
- package/templates/qoder/scripts/protocol/transports/base.py +87 -0
- package/templates/qoder/scripts/protocol/transports/cli.py +132 -0
- package/templates/qoder/scripts/protocol/transports/http.py +141 -0
- package/templates/qoder/scripts/protocol/transports/stdio.py +293 -0
- package/templates/qoder/scripts/run_weekly_update.bat +27 -18
- package/templates/qoder/scripts/run_weekly_update.sh +22 -13
- package/templates/qoder/scripts/validation/__init__.py +0 -0
- package/templates/qoder/scripts/validation/eval/__init__.py +0 -0
- package/templates/qoder/scripts/validation/eval/perf-report-2026-06-24.md +139 -0
- package/templates/qoder/scripts/validation/eval/qwork_harness.py +617 -0
- package/templates/qoder/scripts/validation/eval/report-commands.md +220 -0
- package/templates/qoder/scripts/validation/eval/transcript_timing.py +386 -0
- package/templates/qoder/scripts/validation/metrics/__init__.py +0 -0
- package/templates/qoder/scripts/{eval_prd.py → validation/metrics/eval_prd.py} +84 -15
- package/templates/qoder/scripts/validation/metrics/usability_score.py +750 -0
- package/templates/qoder/scripts/validation/test/__init__.py +11 -0
- package/templates/qoder/scripts/validation/test/assertion_gen.py +551 -0
- package/templates/qoder/scripts/{autotest.py → validation/test/autotest.py} +1234 -1751
- package/templates/qoder/scripts/validation/test/autotest_auth.py +109 -0
- package/templates/qoder/scripts/{autotest_batch.py → validation/test/autotest_batch.py} +255 -224
- package/templates/qoder/scripts/validation/test/autotest_data.py +680 -0
- package/templates/qoder/scripts/validation/test/autotest_opencli.py +646 -0
- package/templates/qoder/scripts/{autotest_run.py → validation/test/autotest_run.py} +323 -297
- package/templates/qoder/scripts/validation/test/autotest_webaccess.py +571 -0
- package/templates/qoder/scripts/{benchmark.py → validation/test/benchmark.py} +17 -5
- package/templates/qoder/scripts/{kg_auto_login.py → validation/test/kg_auto_login.py} +208 -196
- package/templates/qoder/scripts/{kg_test_runner.py → validation/test/kg_test_runner.py} +13 -2
- package/templates/qoder/scripts/{page_probe.py → validation/test/page_probe.py} +470 -459
- package/templates/qoder/scripts/validation/test/smoke_all_commands.py +170 -0
- package/templates/qoder/settings.json +23 -2
- package/templates/qoder/skills/design-import/SKILL.md +235 -226
- package/templates/qoder/skills/design-review/SKILL.md +91 -82
- package/templates/qoder/skills/prd-generator/SKILL.md +38 -24
- package/templates/qoder/skills/prd-review/SKILL.md +8 -6
- package/templates/qoder/skills/prototype-generator/SKILL.md +296 -247
- package/templates/qoder/skills/spec-coder/SKILL.md +24 -15
- package/templates/qoder/skills/spec-generator/SKILL.md +24 -12
- package/templates/qoder/skills/test-generator/SKILL.md +9 -7
- package/templates/qoder/skills/wl-code/SKILL.md +25 -13
- package/templates/qoder/skills/wl-commit/SKILL.md +10 -9
- package/templates/qoder/skills/wl-design/SKILL.md +7 -5
- package/templates/qoder/skills/wl-init/SKILL.md +8 -8
- package/templates/qoder/skills/wl-insight/SKILL.md +25 -12
- package/templates/qoder/skills/wl-prd-full/SKILL.md +59 -8
- package/templates/qoder/skills/wl-prd-quick/SKILL.md +7 -7
- package/templates/qoder/skills/wl-prd-review/SKILL.md +18 -6
- package/templates/qoder/skills/wl-report/SKILL.md +27 -25
- package/templates/qoder/skills/wl-search/SKILL.md +151 -80
- package/templates/qoder/skills/wl-spec/SKILL.md +9 -7
- package/templates/qoder/skills/wl-status/SKILL.md +50 -18
- package/templates/qoder/skills/wl-task/SKILL.md +94 -12
- package/templates/qoder/skills/wl-test/SKILL.md +154 -45
- package/templates/qoder/templates/prd-full-template.md +7 -0
- package/templates/root/AGENTS.md +275 -237
- package/templates/root/requirements.txt +3 -0
- package/templates/root//344/275/277/347/224/250/350/257/264/346/230/216.md +3 -3
- package/templates/root//346/226/260/346/211/213/346/214/207/345/215/227.md +2 -2
- 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.py +0 -298
- 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_api_params.py +0 -246
- 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/kg_mcp_server.py +0 -801
- package/templates/qoder/scripts/kg_semantic.py +0 -150
- package/templates/qoder/scripts/mcp_launcher.py +0 -414
- package/templates/qoder/scripts/mysql_mcp_server.py +0 -396
- 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/scripts/zentao_mcp_server.py +0 -424
- package/templates/qoder/skills/prompt-enrich/SKILL.md +0 -90
- /package/templates/qoder/scripts/{common → deployment}/__init__.py +0 -0
- /package/templates/qoder/{skills/prototype-generator/SKILL.md.zcode-79180-2af4721f-f9a6-412c-88db-c0af680d211b.tmp → scripts/domain/__init__.py} +0 -0
- /package/templates/qoder/scripts/{common → domain/kg/graph}/graph_traverse.py +0 -0
- /package/templates/qoder/scripts/{common → domain/kg/server}/repowiki.py +0 -0
- /package/templates/qoder/scripts/{common → foundation/io}/atomicio.py +0 -0
- /package/templates/qoder/scripts/{secure-ls.js → validation/test/secure-ls.js} +0 -0
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# 子包初始化: 注入 scripts/根 + 所有子包到 sys.path
|
|
2
|
+
# 让跨包 import (旧式不带前缀) 仍可用, 兼容现有代码
|
|
3
|
+
import os as _os, sys as _sys
|
|
4
|
+
_root = _os.path.dirname(_os.path.dirname(_os.path.abspath(__file__)))
|
|
5
|
+
if _root not in _sys.path:
|
|
6
|
+
_sys.path.insert(0, _root)
|
|
7
|
+
# 注入所有子包目录 (兼容旧式 import xxx)
|
|
8
|
+
for _sub in ('kg', 'build', 'test', 'setup', 'io'):
|
|
9
|
+
_subdir = _os.path.join(_root, _sub)
|
|
10
|
+
if _os.path.isdir(_subdir) and _subdir not in _sys.path:
|
|
11
|
+
_sys.path.insert(0, _subdir)
|
|
File without changes
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""build_entity_registry.py — 从 ui-modules + code-api 构建 entity-registry.json。
|
|
3
|
+
|
|
4
|
+
# v3.0 路径自举: 引导到 common/bootstrap, 统一 sys.path 逻辑
|
|
5
|
+
import os as _o, sys as _s
|
|
6
|
+
_f = _o.path.abspath(__file__)
|
|
7
|
+
for _ in range(10):
|
|
8
|
+
_f = _o.path.dirname(_f)
|
|
9
|
+
_cp = _o.path.join(_f, 'foundation')
|
|
10
|
+
if _o.path.isfile(_o.path.join(_cp, 'bootstrap.py')):
|
|
11
|
+
break
|
|
12
|
+
if _cp not in _s.path: _s.path.insert(0, _cp)
|
|
13
|
+
from bootstrap import setup; setup()
|
|
14
|
+
|
|
15
|
+
产出 data/index/_build_cache/entity-registry.json, 供 kg_duckdb 导入 entities/aliases 表,
|
|
16
|
+
让 kg.py feature <功能名> 能返回功能画像。
|
|
17
|
+
|
|
18
|
+
结构 (与 kg_duckdb.bulk_import_from_json 契约对齐):
|
|
19
|
+
{
|
|
20
|
+
"entities": {
|
|
21
|
+
"F:<dir>": {"type":"FEATURE","canonical":"<dir>","cn":"<中文名>","aliases":[...],"properties":{...}},
|
|
22
|
+
"E:<endpoint>": {"type":"ENDPOINT","canonical":"<endpoint>","cn":"","aliases":[],"properties":{}},
|
|
23
|
+
"C:<controller>": {"type":"CONTROLLER",...}
|
|
24
|
+
},
|
|
25
|
+
"feature_adjacency": {"F:<dir>": {"endpoints": ["E:<ep>", ...]}}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
数据源:
|
|
29
|
+
- ui-modules.json 的 modules (每个 module → FEATURE entity, cn=中文名)
|
|
30
|
+
- code-api.json 的 endpoints (每个 → ENDPOINT entity)
|
|
31
|
+
- trace-chain.json 的 traces (推导 FEATURE → ENDPOINT 邻接)
|
|
32
|
+
|
|
33
|
+
注意: 这是基础实现 (从模块结构推导)。更丰富的语义 (controller/test 关联、
|
|
34
|
+
业务流程) 由 build_relations.py/build_features.py/build_workflows.py 补充,
|
|
35
|
+
那些脚本未实现时不影响本脚本的 entity 基础层。
|
|
36
|
+
"""
|
|
37
|
+
import json
|
|
38
|
+
import os
|
|
39
|
+
import sys
|
|
40
|
+
from datetime import datetime
|
|
41
|
+
|
|
42
|
+
# 路径自适应 (和兄弟脚本一致)
|
|
43
|
+
THIS_DIR = os.path.dirname(os.path.abspath(__file__))
|
|
44
|
+
sys.path.insert(0, os.path.join(THIS_DIR, '..'))
|
|
45
|
+
from foundation.core.paths import DATA_INDEX_DIR, BUILD_CACHE_DIR
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def load(name):
|
|
49
|
+
"""从 _build_cache 读索引 JSON (build_style_index 的产物位置)。"""
|
|
50
|
+
for d in (str(BUILD_CACHE_DIR), str(DATA_INDEX_DIR)):
|
|
51
|
+
p = os.path.join(d, name)
|
|
52
|
+
if os.path.isfile(p):
|
|
53
|
+
try:
|
|
54
|
+
return json.load(open(p, encoding='utf-8'))
|
|
55
|
+
except (json.JSONDecodeError, OSError):
|
|
56
|
+
pass
|
|
57
|
+
return {}
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def build():
|
|
61
|
+
entities = {}
|
|
62
|
+
aliases_rows = [] # (alias_lower, entity_id)
|
|
63
|
+
feature_adjacency = {}
|
|
64
|
+
|
|
65
|
+
# 1. FEATURE entities (from ui-modules.json)
|
|
66
|
+
mi = load('ui-modules.json')
|
|
67
|
+
feat_count = 0
|
|
68
|
+
for proj, mods in mi.get('projects', {}).items():
|
|
69
|
+
for m in mods:
|
|
70
|
+
if not isinstance(m, dict):
|
|
71
|
+
continue
|
|
72
|
+
mdir = m.get('dir', '').strip()
|
|
73
|
+
if not mdir:
|
|
74
|
+
continue
|
|
75
|
+
eid = 'F:%s' % mdir
|
|
76
|
+
cn = m.get('cn', '').strip()
|
|
77
|
+
# cn 为空或与 dir 相同时不算有中文名
|
|
78
|
+
if not cn or cn == mdir:
|
|
79
|
+
cn = ''
|
|
80
|
+
entities[eid] = {
|
|
81
|
+
'type': 'FEATURE',
|
|
82
|
+
'canonical': mdir,
|
|
83
|
+
'cn': cn,
|
|
84
|
+
'aliases': [mdir, mdir.lower()],
|
|
85
|
+
'properties': {'project': proj, 'path': m.get('path', '')},
|
|
86
|
+
}
|
|
87
|
+
for a in entities[eid]['aliases']:
|
|
88
|
+
aliases_rows.append((a.lower(), eid))
|
|
89
|
+
feature_adjacency[eid] = {'endpoints': [], 'buttons': []}
|
|
90
|
+
feat_count += 1
|
|
91
|
+
|
|
92
|
+
# 2. ENDPOINT entities (from code-api.json + trace-chain.json 补充)
|
|
93
|
+
# trace 的 endpoint 比 code-api 多且粒度不同, 两者都收录, 避免邻接匹配落空。
|
|
94
|
+
api = load('code-api.json')
|
|
95
|
+
tc = load('trace-chain.json')
|
|
96
|
+
ep_count = 0
|
|
97
|
+
for ep, ctrl_file in api.items():
|
|
98
|
+
eid = 'E:%s' % ep
|
|
99
|
+
entities[eid] = {
|
|
100
|
+
'type': 'ENDPOINT',
|
|
101
|
+
'canonical': ep,
|
|
102
|
+
'cn': '',
|
|
103
|
+
'aliases': [ep],
|
|
104
|
+
'properties': {'controller_file': ctrl_file},
|
|
105
|
+
}
|
|
106
|
+
aliases_rows.append((ep.lower(), eid))
|
|
107
|
+
ep_count += 1
|
|
108
|
+
# 补充 trace 里独有的 endpoint
|
|
109
|
+
for t in tc.get('traces', []):
|
|
110
|
+
ep = t.get('endpoint', '')
|
|
111
|
+
if not ep:
|
|
112
|
+
continue
|
|
113
|
+
eid = 'E:%s' % ep
|
|
114
|
+
if eid not in entities:
|
|
115
|
+
entities[eid] = {
|
|
116
|
+
'type': 'ENDPOINT',
|
|
117
|
+
'canonical': ep,
|
|
118
|
+
'cn': '',
|
|
119
|
+
'aliases': [ep],
|
|
120
|
+
'properties': {'source': 'trace', 'verb': t.get('verb', '')},
|
|
121
|
+
}
|
|
122
|
+
aliases_rows.append((ep.lower(), eid))
|
|
123
|
+
ep_count += 1
|
|
124
|
+
|
|
125
|
+
# 3. FEATURE -> ENDPOINT 邻接 (from trace-chain.json, tc 已在 step 2 加载)
|
|
126
|
+
# trace 里 button 的 vue_file 路径含 module dir, 可反推它属于哪个 FEATURE。
|
|
127
|
+
feat_canonicals = [(fid, ent['canonical']) for fid, ent in entities.items()
|
|
128
|
+
if ent['type'] == 'FEATURE' and ent.get('canonical')]
|
|
129
|
+
trace_matched = 0
|
|
130
|
+
for t in tc.get('traces', []):
|
|
131
|
+
vue = (t.get('vue_file') or '').replace('\\', '/')
|
|
132
|
+
endpoint = t.get('endpoint', '')
|
|
133
|
+
if not vue or not endpoint:
|
|
134
|
+
continue
|
|
135
|
+
# 从 vue 路径找匹配的 module dir (最长匹配优先, 避免 cost 误匹配 costDetail)
|
|
136
|
+
matched_feat = None
|
|
137
|
+
best_len = 0
|
|
138
|
+
for fid, d in feat_canonicals:
|
|
139
|
+
if ('/%s/' % d) in vue or vue.endswith('/%s' % d) or ('/%s.' % d) in vue:
|
|
140
|
+
if len(d) > best_len: # 最长匹配
|
|
141
|
+
matched_feat = fid
|
|
142
|
+
best_len = len(d)
|
|
143
|
+
if matched_feat:
|
|
144
|
+
trace_matched += 1
|
|
145
|
+
adj = feature_adjacency.setdefault(matched_feat, {})
|
|
146
|
+
adj.setdefault('endpoints', [])
|
|
147
|
+
adj.setdefault('buttons', [])
|
|
148
|
+
ep_eid = 'E:%s' % endpoint
|
|
149
|
+
if ep_eid in entities and ep_eid not in adj['endpoints']:
|
|
150
|
+
adj['endpoints'].append(ep_eid)
|
|
151
|
+
# button 邻接: vue_short:handler 归属此 feature
|
|
152
|
+
vue_short = vue.split('/views/')[-1] if '/views/' in vue else vue
|
|
153
|
+
handler = t.get('handler', '')
|
|
154
|
+
btn_eid = 'B:%s:%s' % (vue_short, handler)
|
|
155
|
+
if btn_eid not in adj['buttons']:
|
|
156
|
+
adj['buttons'].append(btn_eid)
|
|
157
|
+
|
|
158
|
+
# 4. 去重 aliases
|
|
159
|
+
seen_alias = set()
|
|
160
|
+
unique_aliases = []
|
|
161
|
+
for a, eid in aliases_rows:
|
|
162
|
+
key = (a, eid)
|
|
163
|
+
if key not in seen_alias:
|
|
164
|
+
seen_alias.add(key)
|
|
165
|
+
unique_aliases.append({'alias': a, 'entity_id': eid})
|
|
166
|
+
|
|
167
|
+
registry = {
|
|
168
|
+
'version': '1.0',
|
|
169
|
+
'updated': datetime.now().isoformat(),
|
|
170
|
+
'entities': entities,
|
|
171
|
+
'aliases': unique_aliases,
|
|
172
|
+
'feature_adjacency': feature_adjacency,
|
|
173
|
+
'stats': {
|
|
174
|
+
'features': feat_count,
|
|
175
|
+
'endpoints': ep_count,
|
|
176
|
+
'total_entities': len(entities),
|
|
177
|
+
'features_with_cn': sum(1 for e in entities.values()
|
|
178
|
+
if e['type'] == 'FEATURE' and e.get('cn')),
|
|
179
|
+
'trace_matched': trace_matched,
|
|
180
|
+
'features_with_endpoints': sum(1 for v in feature_adjacency.values() if v.get('endpoints')),
|
|
181
|
+
},
|
|
182
|
+
}
|
|
183
|
+
return registry
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
def main():
|
|
187
|
+
print('Building entity registry...')
|
|
188
|
+
registry = build()
|
|
189
|
+
out = os.path.join(str(BUILD_CACHE_DIR), 'entity-registry.json')
|
|
190
|
+
os.makedirs(os.path.dirname(out), exist_ok=True)
|
|
191
|
+
with open(out, 'w', encoding='utf-8') as f:
|
|
192
|
+
json.dump(registry, f, ensure_ascii=False, indent=2, sort_keys=True)
|
|
193
|
+
s = registry['stats']
|
|
194
|
+
print(' FEATURE entities: %d (%d 有中文名)' % (s['features'], s['features_with_cn']))
|
|
195
|
+
print(' ENDPOINT entities: %d' % s['endpoints'])
|
|
196
|
+
print(' aliases: %d' % len(registry['aliases']))
|
|
197
|
+
print(' Saved to: %s' % out)
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
if __name__ == '__main__':
|
|
201
|
+
main()
|
|
@@ -0,0 +1,132 @@
|
|
|
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
|
+
_f = _o.path.abspath(__file__)
|
|
7
|
+
for _ in range(10):
|
|
8
|
+
_f = _o.path.dirname(_f)
|
|
9
|
+
_cp = _o.path.join(_f, 'foundation')
|
|
10
|
+
if _o.path.isfile(_o.path.join(_cp, 'bootstrap.py')):
|
|
11
|
+
break
|
|
12
|
+
if _cp not in _s.path: _s.path.insert(0, _cp)
|
|
13
|
+
from bootstrap import setup; setup()
|
|
14
|
+
|
|
15
|
+
产出 data/index/_build_cache/relations.json, 供 kg_duckdb 导入 edges 表,
|
|
16
|
+
让 kg.py feature 的端点/按钮数据非零, kg.py hop 多跳遍历有边可走。
|
|
17
|
+
|
|
18
|
+
结构 (与 kg_duckdb.bulk_import_from_json 的 edges_data 契约对齐):
|
|
19
|
+
{
|
|
20
|
+
"edges": {
|
|
21
|
+
"click_to_api": [
|
|
22
|
+
{"vue_file":..., "button":..., "handler":..., "endpoint":..., "confidence":...}
|
|
23
|
+
],
|
|
24
|
+
"api_to_controller": {"<endpoint>": "<controller_file>"},
|
|
25
|
+
"controller_to_tests": {"<controller>": [{"test":..., "dep":..., "assertions":[...]}]}
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
数据源:
|
|
30
|
+
- trace-chain.json: traces (vue_file→button→handler→api_fn→endpoint→verb→confidence)
|
|
31
|
+
api_functions (fnName→{url,verb,file})
|
|
32
|
+
- code-api.json: {endpoint: controller_file} (api_to_controller 直接用)
|
|
33
|
+
- test-assertions.json: tests (controller_to_tests 推导)
|
|
34
|
+
"""
|
|
35
|
+
import json
|
|
36
|
+
import os
|
|
37
|
+
import sys
|
|
38
|
+
from datetime import datetime
|
|
39
|
+
|
|
40
|
+
THIS_DIR = os.path.dirname(os.path.abspath(__file__))
|
|
41
|
+
sys.path.insert(0, os.path.join(THIS_DIR, '..'))
|
|
42
|
+
from foundation.core.paths import DATA_INDEX_DIR, BUILD_CACHE_DIR
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def load(name):
|
|
46
|
+
for d in (str(BUILD_CACHE_DIR), str(DATA_INDEX_DIR)):
|
|
47
|
+
p = os.path.join(d, name)
|
|
48
|
+
if os.path.isfile(p):
|
|
49
|
+
try:
|
|
50
|
+
return json.load(open(p, encoding='utf-8'))
|
|
51
|
+
except (json.JSONDecodeError, OSError):
|
|
52
|
+
pass
|
|
53
|
+
return {}
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def build():
|
|
57
|
+
tc = load('trace-chain.json')
|
|
58
|
+
api = load('code-api.json')
|
|
59
|
+
|
|
60
|
+
# 1. click_to_api: 直接从 traces 转 (字段名对齐 kg_duckdb 期望)
|
|
61
|
+
click_to_api = []
|
|
62
|
+
for t in tc.get('traces', []):
|
|
63
|
+
if not t.get('endpoint'):
|
|
64
|
+
continue
|
|
65
|
+
click_to_api.append({
|
|
66
|
+
'vue_file': t.get('vue_file', ''),
|
|
67
|
+
'button': t.get('button', ''),
|
|
68
|
+
'handler': t.get('handler', ''),
|
|
69
|
+
'api_fn': t.get('api_fn', ''),
|
|
70
|
+
'endpoint': t.get('endpoint', ''),
|
|
71
|
+
'verb': t.get('verb', ''),
|
|
72
|
+
'confidence': t.get('confidence', ''),
|
|
73
|
+
})
|
|
74
|
+
|
|
75
|
+
# 2. api_to_controller: code-api.json 已经是 {endpoint: controller_file}
|
|
76
|
+
# 过滤掉空值
|
|
77
|
+
api_to_controller = {ep: f for ep, f in api.items() if ep and f}
|
|
78
|
+
|
|
79
|
+
# 3. controller_to_tests: 从 test-assertions.json 推导
|
|
80
|
+
# test 有 package/class 信息, controller 名从 controller_file 提取, 反向匹配 test
|
|
81
|
+
ta = load('test-assertions.json')
|
|
82
|
+
controller_to_tests = {}
|
|
83
|
+
# 建 controller 名 → test 的映射 (test 的 package 含 controller 关键字则关联)
|
|
84
|
+
ctrl_names = set()
|
|
85
|
+
for ctrl_file in api_to_controller.values():
|
|
86
|
+
cname = ctrl_file.split('/')[-1].replace('.java', '')
|
|
87
|
+
if cname:
|
|
88
|
+
ctrl_names.add(cname)
|
|
89
|
+
for t in ta.get('tests', []):
|
|
90
|
+
test_loc = (t.get('package', '') + '.' + t.get('class', '')).lower()
|
|
91
|
+
for cname in ctrl_names:
|
|
92
|
+
if cname.lower() in test_loc:
|
|
93
|
+
controller_to_tests.setdefault(cname, []).append({
|
|
94
|
+
'test': t.get('display_name') or (t.get('class', '') + '.' + t.get('method', '')),
|
|
95
|
+
'dep': '',
|
|
96
|
+
'assertions': t.get('assertions', []),
|
|
97
|
+
})
|
|
98
|
+
break # 一个 test 只关联一个 controller
|
|
99
|
+
|
|
100
|
+
relations = {
|
|
101
|
+
'version': '1.0',
|
|
102
|
+
'updated': datetime.now().isoformat(),
|
|
103
|
+
'edges': {
|
|
104
|
+
'click_to_api': click_to_api,
|
|
105
|
+
'api_to_controller': api_to_controller,
|
|
106
|
+
'controller_to_tests': controller_to_tests,
|
|
107
|
+
},
|
|
108
|
+
'stats': {
|
|
109
|
+
'click_edges': len(click_to_api),
|
|
110
|
+
'api_to_ctrl': len(api_to_controller),
|
|
111
|
+
'ctrl_to_test': len(controller_to_tests),
|
|
112
|
+
},
|
|
113
|
+
}
|
|
114
|
+
return relations
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def main():
|
|
118
|
+
print('Building relations...')
|
|
119
|
+
rel = build()
|
|
120
|
+
out = os.path.join(str(BUILD_CACHE_DIR), 'relations.json')
|
|
121
|
+
os.makedirs(os.path.dirname(out), exist_ok=True)
|
|
122
|
+
with open(out, 'w', encoding='utf-8') as f:
|
|
123
|
+
json.dump(rel, f, ensure_ascii=False, indent=2, sort_keys=True)
|
|
124
|
+
s = rel['stats']
|
|
125
|
+
print(' click_to_api edges: %d' % s['click_edges'])
|
|
126
|
+
print(' api_to_controller: %d' % s['api_to_ctrl'])
|
|
127
|
+
print(' controller_to_tests: %d' % s['ctrl_to_test'])
|
|
128
|
+
print(' Saved to: %s' % out)
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
if __name__ == '__main__':
|
|
132
|
+
main()
|
package/templates/qoder/scripts/{build_style_index.py → domain/kg/build/build_style_index.py}
RENAMED
|
@@ -1,5 +1,17 @@
|
|
|
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
|
+
_f = _o.path.abspath(__file__)
|
|
6
|
+
for _ in range(10):
|
|
7
|
+
_f = _o.path.dirname(_f)
|
|
8
|
+
_cp = _o.path.join(_f, 'foundation')
|
|
9
|
+
if _o.path.isfile(_o.path.join(_cp, 'bootstrap.py')):
|
|
10
|
+
break
|
|
11
|
+
if _cp not in _s.path: _s.path.insert(0, _cp)
|
|
12
|
+
from bootstrap import setup; setup()
|
|
13
|
+
from foundation.core.paths import get_repo_root
|
|
14
|
+
|
|
3
15
|
"""
|
|
4
16
|
Build style index from Vue/HTML files.
|
|
5
17
|
Extracts UI patterns: tables, forms, layouts, colors, components.
|
|
@@ -8,7 +20,7 @@ Usage:
|
|
|
8
20
|
python build_style_index.py
|
|
9
21
|
"""
|
|
10
22
|
import os, json, re, sys
|
|
11
|
-
from
|
|
23
|
+
from foundation.core.paths import BUILD_CACHE_DIR
|
|
12
24
|
from datetime import datetime
|
|
13
25
|
|
|
14
26
|
# UTF-8 stdio (防御性: stdout 被捕获时不崩溃)
|
|
@@ -20,7 +32,7 @@ except (AttributeError, TypeError, OSError, IOError):
|
|
|
20
32
|
except Exception:
|
|
21
33
|
pass
|
|
22
34
|
|
|
23
|
-
BASE =
|
|
35
|
+
BASE = get_repo_root()
|
|
24
36
|
DATA_CODE = os.path.join(BASE, 'data', 'code')
|
|
25
37
|
INDEX_DIR = os.path.join(BASE, 'data', 'index')
|
|
26
38
|
STYLE_INDEX_PATH = os.path.join(BUILD_CACHE_DIR, 'ui-style.json')
|
|
@@ -33,13 +45,20 @@ TEST_ASSERTIONS_PATH = os.path.join(BUILD_CACHE_DIR, 'test-assertions.json')
|
|
|
33
45
|
# E2: prefer tree-sitter extraction (31%->70%+ coverage); fallback to regex
|
|
34
46
|
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
|
35
47
|
try:
|
|
36
|
-
from
|
|
37
|
-
from
|
|
48
|
+
from domain.kg.extract.ts_extract import ts_build_api_fn_table as build_api_fn_table
|
|
49
|
+
from domain.kg.extract.ts_extract import ts_trace_clicks as trace_clicks
|
|
38
50
|
_EXTRACT_ENGINE = 'tree-sitter'
|
|
39
51
|
except Exception:
|
|
40
|
-
from
|
|
52
|
+
from domain.kg.extract.extract import build_api_fn_table, trace_clicks
|
|
41
53
|
_EXTRACT_ENGINE = 'regex (tree-sitter not installed)'
|
|
42
|
-
|
|
54
|
+
# test_extract 是可选增强层 (test-assertions 索引), 模块可能未提供。
|
|
55
|
+
# 用 lazy import + 降级: 没有就跳过 test-assertions, 不阻断 ui-style 主线构建。
|
|
56
|
+
try:
|
|
57
|
+
from domain.kg.extract.test_extract import extract_test_assertions # noqa: E402
|
|
58
|
+
_HAS_TEST_EXTRACT = True
|
|
59
|
+
except ImportError:
|
|
60
|
+
extract_test_assertions = None
|
|
61
|
+
_HAS_TEST_EXTRACT = False
|
|
43
62
|
|
|
44
63
|
|
|
45
64
|
def extract_vue_patterns(filepath, content):
|
|
@@ -377,7 +396,12 @@ def scan_project(proj_name, proj_path):
|
|
|
377
396
|
|
|
378
397
|
for f in files:
|
|
379
398
|
filepath = os.path.join(root, f)
|
|
380
|
-
|
|
399
|
+
# relpath 在 Windows 上对特殊路径 (\\.\nul 等设备/跨盘符) 会抛 ValueError。
|
|
400
|
+
# 这些根本不是源码, 跳过即可, 不让一个异常文件炸掉整个扫描。
|
|
401
|
+
try:
|
|
402
|
+
rel_path = os.path.relpath(filepath, proj_path).replace('\\', '/')
|
|
403
|
+
except ValueError:
|
|
404
|
+
continue
|
|
381
405
|
|
|
382
406
|
if f.endswith('.vue'):
|
|
383
407
|
vue_count += 1
|
|
@@ -415,7 +439,7 @@ def scan_project(proj_name, proj_path):
|
|
|
415
439
|
comp_m = re.findall(r"component:\s*['\"]([^'\"]+)['\"]", content)
|
|
416
440
|
for comp in comp_m:
|
|
417
441
|
form_components[comp] = form_components.get(comp, 0) + 1
|
|
418
|
-
except:
|
|
442
|
+
except Exception:
|
|
419
443
|
pass
|
|
420
444
|
|
|
421
445
|
return {
|
|
@@ -662,13 +686,20 @@ def main():
|
|
|
662
686
|
|
|
663
687
|
# test-assertions.json — E1: 测试代码提取 (Given-When-Then + 断言 + 注入依赖)
|
|
664
688
|
# 第二层知识: 生产代码提取不到的业务预期和断言标准, 补上'怎么算对'天花板
|
|
665
|
-
|
|
689
|
+
# 依赖 common.test_extract (可选模块); 缺失则跳过, 不阻断 ui-style 等主线。
|
|
666
690
|
all_tests = []
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
691
|
+
if not _HAS_TEST_EXTRACT:
|
|
692
|
+
print('Skipping test assertions index (common.test_extract not available - optional)')
|
|
693
|
+
else:
|
|
694
|
+
print('Building test assertions index...')
|
|
695
|
+
for proj in sorted(os.listdir(DATA_CODE)):
|
|
696
|
+
proj_path = os.path.join(DATA_CODE, proj)
|
|
697
|
+
if os.path.isdir(proj_path):
|
|
698
|
+
try:
|
|
699
|
+
tests = extract_test_assertions(proj_path)
|
|
700
|
+
all_tests.extend(tests)
|
|
701
|
+
except Exception as e:
|
|
702
|
+
print(' WARN: test_extract failed for {}: {}'.format(proj, str(e)[:80]))
|
|
672
703
|
test_index = {
|
|
673
704
|
'version': '1.0', 'updated': datetime.now().isoformat(),
|
|
674
705
|
'tests': all_tests,
|
|
@@ -679,6 +710,8 @@ def main():
|
|
|
679
710
|
json.dump(test_index, f, ensure_ascii=False, indent=2, sort_keys=True)
|
|
680
711
|
|
|
681
712
|
# ── 知识图谱层: relations + features + entity-registry + DuckDB ──
|
|
713
|
+
# build_entity_registry 已实现 (FEATURE/ENDPOINT entity 基础层)。
|
|
714
|
+
# build_relations/features/workflows 是增强层, 未实现时 entities 表仍有基础数据。
|
|
682
715
|
print('Building knowledge graph layers...')
|
|
683
716
|
import subprocess
|
|
684
717
|
for script in ['build_relations.py', 'build_features.py',
|
|
@@ -690,6 +723,8 @@ def main():
|
|
|
690
723
|
print(' %s OK' % script)
|
|
691
724
|
except Exception as e:
|
|
692
725
|
print(' %s SKIP: %s' % (script, str(e)[:60]))
|
|
726
|
+
elif script != 'kg_duckdb.py':
|
|
727
|
+
print(' %s N/A (语义层脚本未实现, entities/workflows 表将空)' % script)
|
|
693
728
|
|
|
694
729
|
print()
|
|
695
730
|
print('=== Done ===')
|
|
@@ -0,0 +1,149 @@
|
|
|
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
|
+
_f = _o.path.abspath(__file__)
|
|
7
|
+
for _ in range(10):
|
|
8
|
+
_f = _o.path.dirname(_f)
|
|
9
|
+
_cp = _o.path.join(_f, 'foundation')
|
|
10
|
+
if _o.path.isfile(_o.path.join(_cp, 'bootstrap.py')):
|
|
11
|
+
break
|
|
12
|
+
if _cp not in _s.path: _s.path.insert(0, _cp)
|
|
13
|
+
from bootstrap import setup; setup()
|
|
14
|
+
|
|
15
|
+
产出 data/index/_build_cache/workflow-index.json, 供 kg_duckdb 导入 workflows 表,
|
|
16
|
+
让 kg.py workflow <模块> 能返回该模块的状态流转链 (query→create→audit→...)。
|
|
17
|
+
|
|
18
|
+
workflows 表结构: module, state, seq, button, endpoint
|
|
19
|
+
|
|
20
|
+
推导逻辑 (基于 verb + 路径关键词的启发式状态推断):
|
|
21
|
+
- GET/read/list/page/export → "query" (查询)
|
|
22
|
+
- POST/add/create/save/submit/apply → "create" (新增/提交)
|
|
23
|
+
- PUT/update/edit/audit/approve/reject → "audit" (审核/修改)
|
|
24
|
+
- DELETE/remove/cancel/delete/del → "cancel" (取消/删除)
|
|
25
|
+
|
|
26
|
+
数据源: trace-chain.json (vue_file→button→endpoint→verb) + entity-registry.json
|
|
27
|
+
(module→endpoint 邻接, 用于把 endpoint 归到 module)。
|
|
28
|
+
"""
|
|
29
|
+
import json
|
|
30
|
+
import os
|
|
31
|
+
import sys
|
|
32
|
+
from datetime import datetime
|
|
33
|
+
|
|
34
|
+
THIS_DIR = os.path.dirname(os.path.abspath(__file__))
|
|
35
|
+
sys.path.insert(0, os.path.join(THIS_DIR, '..'))
|
|
36
|
+
from foundation.core.paths import DATA_INDEX_DIR, BUILD_CACHE_DIR
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def load(name):
|
|
40
|
+
for d in (str(BUILD_CACHE_DIR), str(DATA_INDEX_DIR)):
|
|
41
|
+
p = os.path.join(d, name)
|
|
42
|
+
if os.path.isfile(p):
|
|
43
|
+
try:
|
|
44
|
+
return json.load(open(p, encoding='utf-8'))
|
|
45
|
+
except (json.JSONDecodeError, OSError):
|
|
46
|
+
pass
|
|
47
|
+
return {}
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
# 状态推断规则 (按优先级, 先匹配先用)
|
|
51
|
+
STATE_RULES = [
|
|
52
|
+
('query', ['get', 'read', 'list', 'page', 'export', 'detail', 'info', 'count']),
|
|
53
|
+
('create', ['post', 'add', 'create', 'save', 'submit', 'apply', 'import', 'write']),
|
|
54
|
+
('audit', ['put', 'update', 'edit', 'audit', 'approve', 'reject', 'review', 'confirm', 'send']),
|
|
55
|
+
('cancel', ['delete', 'remove', 'cancel', 'del', 'revoke', 'close']),
|
|
56
|
+
]
|
|
57
|
+
# 状态排序 (业务流程顺序)
|
|
58
|
+
STATE_ORDER = {'query': 1, 'create': 2, 'audit': 3, 'cancel': 4}
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def infer_state(verb, endpoint):
|
|
62
|
+
"""从 HTTP verb + endpoint 路径推断业务状态。"""
|
|
63
|
+
key = ((verb or '') + ' ' + (endpoint or '')).lower()
|
|
64
|
+
for state, patterns in STATE_RULES:
|
|
65
|
+
for p in patterns:
|
|
66
|
+
if p in key:
|
|
67
|
+
return state
|
|
68
|
+
return 'other'
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def build():
|
|
72
|
+
er = load('entity-registry.json')
|
|
73
|
+
tc = load('trace-chain.json')
|
|
74
|
+
|
|
75
|
+
# module → [(state, button, endpoint)] 从 feature_adjacency + traces 推导
|
|
76
|
+
# feature_adjacency 已有 endpoints + buttons, 配合 trace 的 verb 推断 state
|
|
77
|
+
fa = er.get('feature_adjacency', {})
|
|
78
|
+
entities = er.get('entities', {})
|
|
79
|
+
|
|
80
|
+
# 建 endpoint → (verb, button) 映射 (从 traces)
|
|
81
|
+
ep_info = {} # endpoint -> {verb, buttons:set}
|
|
82
|
+
for t in tc.get('traces', []):
|
|
83
|
+
ep = t.get('endpoint', '')
|
|
84
|
+
if not ep:
|
|
85
|
+
continue
|
|
86
|
+
info = ep_info.setdefault(ep, {'verb': '', 'buttons': set()})
|
|
87
|
+
if t.get('verb') and not info['verb']:
|
|
88
|
+
info['verb'] = t.get('verb', '')
|
|
89
|
+
if t.get('button'):
|
|
90
|
+
info['buttons'].add(t.get('button'))
|
|
91
|
+
|
|
92
|
+
# 按 kg_duckdb.import_workflow_layer 期望的结构组装:
|
|
93
|
+
# {module: {chain: [{state, operations: [{button, endpoint}]}]}}
|
|
94
|
+
workflows = {}
|
|
95
|
+
module_states = {}
|
|
96
|
+
total_steps = 0
|
|
97
|
+
for feat_eid, adj in fa.items():
|
|
98
|
+
if not isinstance(adj, dict):
|
|
99
|
+
continue
|
|
100
|
+
ent = entities.get(feat_eid, {})
|
|
101
|
+
module = ent.get('canonical', feat_eid.replace('F:', ''))
|
|
102
|
+
eps = adj.get('endpoints', [])
|
|
103
|
+
if not eps:
|
|
104
|
+
continue
|
|
105
|
+
# 按 state 分组端点
|
|
106
|
+
state_ops = {} # state -> [{button, endpoint}]
|
|
107
|
+
for ep_eid in eps:
|
|
108
|
+
ep = ep_eid.replace('E:', '', 1)
|
|
109
|
+
info = ep_info.get(ep, {'verb': '', 'buttons': set()})
|
|
110
|
+
state = infer_state(info['verb'], ep)
|
|
111
|
+
btn = next(iter(info['buttons']), '') if info['buttons'] else ''
|
|
112
|
+
state_ops.setdefault(state, []).append({'button': btn, 'endpoint': ep})
|
|
113
|
+
# 按 STATE_ORDER 排序成 chain
|
|
114
|
+
chain = []
|
|
115
|
+
for state in sorted(state_ops.keys(), key=lambda s: STATE_ORDER.get(s, 5)):
|
|
116
|
+
chain.append({'state': state, 'operations': state_ops[state]})
|
|
117
|
+
total_steps += len(state_ops[state])
|
|
118
|
+
if chain:
|
|
119
|
+
workflows[module] = {'chain': chain}
|
|
120
|
+
module_states[module] = len(state_ops)
|
|
121
|
+
|
|
122
|
+
result = {
|
|
123
|
+
'version': '1.0',
|
|
124
|
+
'updated': datetime.now().isoformat(),
|
|
125
|
+
'workflows': workflows,
|
|
126
|
+
'stats': {
|
|
127
|
+
'modules': len(module_states),
|
|
128
|
+
'flow_steps': total_steps,
|
|
129
|
+
'modules_with_multi_state': sum(1 for n in module_states.values() if n > 1),
|
|
130
|
+
},
|
|
131
|
+
}
|
|
132
|
+
return result
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
def main():
|
|
136
|
+
print('Building workflows...')
|
|
137
|
+
wf = build()
|
|
138
|
+
out = os.path.join(str(BUILD_CACHE_DIR), 'workflow-index.json')
|
|
139
|
+
os.makedirs(os.path.dirname(out), exist_ok=True)
|
|
140
|
+
with open(out, 'w', encoding='utf-8') as f:
|
|
141
|
+
json.dump(wf, f, ensure_ascii=False, indent=2, sort_keys=True)
|
|
142
|
+
s = wf['stats']
|
|
143
|
+
print(' modules: %d (%d 有多状态流转)' % (s['modules'], s['modules_with_multi_state']))
|
|
144
|
+
print(' flow steps: %d' % s['flow_steps'])
|
|
145
|
+
print(' Saved to: %s' % out)
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
if __name__ == '__main__':
|
|
149
|
+
main()
|