@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,55 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""GOAL 4 统一入口: PRD LLM + 资产中心 5 表 + REQ-ID 锚定。
|
|
3
|
+
|
|
4
|
+
被 kg_build.py 调用, 或独立 CLI:
|
|
5
|
+
python -m domain.kg.extract.build_goal4 [--project-id PID] [--embed]
|
|
6
|
+
"""
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
import os, sys
|
|
9
|
+
|
|
10
|
+
_THIS = os.path.dirname(os.path.abspath(__file__))
|
|
11
|
+
for _i in range(8):
|
|
12
|
+
_p = os.path.dirname(_THIS)
|
|
13
|
+
if os.path.isfile(os.path.join(_p, 'foundation', 'bootstrap.py')):
|
|
14
|
+
sys.path.insert(0, _p); break
|
|
15
|
+
_THIS = _p
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def build(project_id: str = None, do_embed: bool = False, logger=print):
|
|
19
|
+
"""GOAL 4 主入口: 资产中心 5 表吸收。"""
|
|
20
|
+
from domain.kg.extract.asset import (prd_importer, prototype_importer,
|
|
21
|
+
discussion_importer, asset_tree, returns_importer)
|
|
22
|
+
|
|
23
|
+
logger(' [goal4] === PRD LLM 抽取 + REQ-ID 锚定 ===')
|
|
24
|
+
prd_r = prd_importer.import_all_prds(project_id, do_embed=do_embed, logger=logger)
|
|
25
|
+
logger(' [goal4] PRD: %s' % {k: v for k, v in prd_r.items() if k != 'upsert'})
|
|
26
|
+
|
|
27
|
+
logger(' [goal4] === 原型 + 标注 ===')
|
|
28
|
+
proto_r = prototype_importer.import_prototypes(project_id, logger=logger)
|
|
29
|
+
|
|
30
|
+
logger(' [goal4] === 讨论 ===')
|
|
31
|
+
disc_r = discussion_importer.import_discussions(project_id, logger=logger)
|
|
32
|
+
|
|
33
|
+
logger(' [goal4] === 资产树 ===')
|
|
34
|
+
tree_r = asset_tree.import_asset_tree(project_id, logger=logger)
|
|
35
|
+
|
|
36
|
+
logger(' [goal4] === 回流 ===')
|
|
37
|
+
ret_r = returns_importer.import_returns(project_id, logger=logger)
|
|
38
|
+
|
|
39
|
+
return {'prd': prd_r, 'prototype': proto_r, 'discussion': disc_r,
|
|
40
|
+
'asset_tree': tree_r, 'returns': ret_r}
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
if __name__ == '__main__':
|
|
44
|
+
import argparse, json
|
|
45
|
+
ap = argparse.ArgumentParser()
|
|
46
|
+
ap.add_argument('--project-id', default='')
|
|
47
|
+
ap.add_argument('--embed', action='store_true')
|
|
48
|
+
args = ap.parse_args()
|
|
49
|
+
try:
|
|
50
|
+
from dotenv import load_dotenv
|
|
51
|
+
for p in ['wlinkj-workspace/backend/.env', '.env']:
|
|
52
|
+
if os.path.isfile(p): load_dotenv(p); break
|
|
53
|
+
except: pass
|
|
54
|
+
st = build(project_id=args.project_id or None, do_embed=args.embed)
|
|
55
|
+
print(json.dumps(st, ensure_ascii=False, indent=2, default=str))
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""GOAL 5 统一入口: L3 弱关联推理 + C 方案重算。
|
|
3
|
+
|
|
4
|
+
CLI:
|
|
5
|
+
# 限量推理弱关联 (前500个无强关联实体)
|
|
6
|
+
python -m domain.kg.extract.build_goal5 --infer --limit 500
|
|
7
|
+
# C方案重算 (过期弱边续期/删除)
|
|
8
|
+
python -m domain.kg.extract.build_goal5 --recompute
|
|
9
|
+
# 升格 (删冗余弱边)
|
|
10
|
+
python -m domain.kg.extract.build_goal5 --promote
|
|
11
|
+
"""
|
|
12
|
+
from __future__ import annotations
|
|
13
|
+
import os, sys
|
|
14
|
+
|
|
15
|
+
_THIS = os.path.dirname(os.path.abspath(__file__))
|
|
16
|
+
for _i in range(8):
|
|
17
|
+
_p = os.path.dirname(_THIS)
|
|
18
|
+
if os.path.isfile(os.path.join(_p, 'foundation', 'bootstrap.py')):
|
|
19
|
+
sys.path.insert(0, _p); break
|
|
20
|
+
_THIS = _p
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def run_infer(limit: int = 500, logger=print):
|
|
24
|
+
"""推理弱关联 (限量)。"""
|
|
25
|
+
from domain.kg.extract.inference import weak_link
|
|
26
|
+
from domain.kg.extract.java import pg_upsert
|
|
27
|
+
from sqlalchemy import text
|
|
28
|
+
|
|
29
|
+
engine, _ = pg_upsert._get_pg_engine()
|
|
30
|
+
if engine is None:
|
|
31
|
+
return {'error': 'PG 不可用'}
|
|
32
|
+
|
|
33
|
+
with engine.connect() as conn:
|
|
34
|
+
# 取无强关联的代码实体
|
|
35
|
+
entities = conn.execute(text(f"""
|
|
36
|
+
SELECT id, repo_id, type, canonical, cn, props FROM entities
|
|
37
|
+
WHERE type IN ('FUNCTION','BUTTON','ENDPOINT','CONTROLLER','SERVICE',
|
|
38
|
+
'MAPPER','ENTITY','TABLE','COLUMN','FEATURE')
|
|
39
|
+
AND id NOT IN (
|
|
40
|
+
SELECT from_id FROM edges
|
|
41
|
+
WHERE source IN ('explicit','commit')
|
|
42
|
+
AND edge_type IN ('implements_req','anchors')
|
|
43
|
+
)
|
|
44
|
+
LIMIT {int(limit)}
|
|
45
|
+
""")).all()
|
|
46
|
+
# 取全部 anchors
|
|
47
|
+
anchors = conn.execute(text("SELECT id, title, kind FROM anchors")).all()
|
|
48
|
+
|
|
49
|
+
ent_list = [{'id': r[0], 'repo_id': r[1], 'type': r[2],
|
|
50
|
+
'canonical': r[3], 'cn': r[4], 'props': r[5] or {}} for r in entities]
|
|
51
|
+
anchor_list = [{'id': r[0], 'title': r[1], 'kind': r[2]} for r in anchors]
|
|
52
|
+
logger(' [goal5] %d 实体待推理, %d 锚点' % (len(ent_list), len(anchor_list)))
|
|
53
|
+
|
|
54
|
+
edges, st = weak_link.infer_batch(ent_list, anchor_list, logger=logger)
|
|
55
|
+
|
|
56
|
+
# 写 PG (用 pg_upsert, 它会处理 expires_at/fingerprint 字段)
|
|
57
|
+
upsert_r = pg_upsert.upsert_batch([], edges) if edges else {'ok': False, 'edges_ok': 0}
|
|
58
|
+
st['upsert'] = upsert_r
|
|
59
|
+
return st
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def run_recompute(logger=print):
|
|
63
|
+
"""C 方案重算。"""
|
|
64
|
+
from domain.kg.extract.inference import recompute
|
|
65
|
+
return recompute.recompute_expired(logger=logger)
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def run_promote(logger=print):
|
|
69
|
+
"""弱边升格。"""
|
|
70
|
+
from domain.kg.extract.inference import promote
|
|
71
|
+
return promote.promote_strong_over_weak(logger=logger)
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
if __name__ == '__main__':
|
|
75
|
+
import argparse, json
|
|
76
|
+
ap = argparse.ArgumentParser()
|
|
77
|
+
ap.add_argument('--infer', action='store_true')
|
|
78
|
+
ap.add_argument('--recompute', action='store_true')
|
|
79
|
+
ap.add_argument('--promote', action='store_true')
|
|
80
|
+
ap.add_argument('--limit', type=int, default=500)
|
|
81
|
+
args = ap.parse_args()
|
|
82
|
+
try:
|
|
83
|
+
from dotenv import load_dotenv
|
|
84
|
+
for p in ['wlinkj-workspace/backend/.env', '.env']:
|
|
85
|
+
if os.path.isfile(p): load_dotenv(p); break
|
|
86
|
+
except: pass
|
|
87
|
+
|
|
88
|
+
if args.infer:
|
|
89
|
+
print(json.dumps(run_infer(args.limit), ensure_ascii=False, indent=2, default=str))
|
|
90
|
+
elif args.recompute:
|
|
91
|
+
print(json.dumps(run_recompute(), ensure_ascii=False, indent=2, default=str))
|
|
92
|
+
elif args.promote:
|
|
93
|
+
print(json.dumps(run_promote(), ensure_ascii=False, indent=2, default=str))
|
|
94
|
+
else:
|
|
95
|
+
ap.print_help()
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""数据特征 profile (选做): 表行数、列 null 率、枚举分布。
|
|
3
|
+
|
|
4
|
+
价值: 让 KG 知道"这张表有 50 万行""这个字段 null 率 80%"——数据质量知识。
|
|
5
|
+
本轮 best-effort, 连不上 DB 跳过。
|
|
6
|
+
"""
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
from . import schema_extractor as _se
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def profile_tables(repo_id: str = 'fywl-ics', sample_size: int = 100, logger=print):
|
|
12
|
+
"""采样表的数据特征。Returns: list[dict] 连不上返回 []。"""
|
|
13
|
+
if not _se._can_connect():
|
|
14
|
+
return []
|
|
15
|
+
try:
|
|
16
|
+
import pymysql
|
|
17
|
+
except ImportError:
|
|
18
|
+
return []
|
|
19
|
+
# 本轮简化: 只返回行数 (table_rows 已在 schema_extractor 取过)
|
|
20
|
+
# 完整版可加: SELECT COUNT(*), 列 null 率, DISTINCT 值分布
|
|
21
|
+
logger(' [db-profile] 本轮跳过详细 profile (行数已在 schema_extractor 取)')
|
|
22
|
+
return []
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""外键关系提取器: KEY_COLUMN_USAGE → references 边。
|
|
3
|
+
|
|
4
|
+
让 table_impact 真正能做"改表影响谁"的跨表追溯。
|
|
5
|
+
"""
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
from . import schema_extractor as _se
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def extract_foreign_keys(repo_id: str = 'fywl-ics', logger=print):
|
|
11
|
+
"""连 MySQL → 提取外键关系 → references 边。
|
|
12
|
+
|
|
13
|
+
Returns: [{from_id, to_id, edge_type:'references', ...}] 连不上返回 []
|
|
14
|
+
"""
|
|
15
|
+
if not _se._can_connect():
|
|
16
|
+
return []
|
|
17
|
+
try:
|
|
18
|
+
import pymysql
|
|
19
|
+
except ImportError:
|
|
20
|
+
return []
|
|
21
|
+
|
|
22
|
+
conn = pymysql.connect(host=_se.MYSQL_HOST, port=_se.MYSQL_PORT, user=_se.MYSQL_USER,
|
|
23
|
+
password=_se.MYSQL_PASSWORD, charset='utf8mb4',
|
|
24
|
+
cursorclass=pymysql.cursors.DictCursor,
|
|
25
|
+
connect_timeout=5, read_timeout=30)
|
|
26
|
+
edges = []
|
|
27
|
+
try:
|
|
28
|
+
cur = conn.cursor()
|
|
29
|
+
cur.execute("SELECT schema_name FROM information_schema.schemata "
|
|
30
|
+
"WHERE schema_name LIKE %s", (_se.DB_NAME_PREFIX + '%',))
|
|
31
|
+
dbs = [r['schema_name'] for r in cur.fetchall()]
|
|
32
|
+
for db in dbs:
|
|
33
|
+
# 外键关系
|
|
34
|
+
cur.execute("""
|
|
35
|
+
SELECT TABLE_NAME, COLUMN_NAME, REFERENCED_TABLE_NAME, REFERENCED_COLUMN_NAME
|
|
36
|
+
FROM information_schema.KEY_COLUMN_USAGE
|
|
37
|
+
WHERE TABLE_SCHEMA=%s AND REFERENCED_TABLE_NAME IS NOT NULL
|
|
38
|
+
""", (db,))
|
|
39
|
+
for r in cur.fetchall():
|
|
40
|
+
from_tbl = r['TABLE_NAME'].lower()
|
|
41
|
+
to_tbl = r['REFERENCED_TABLE_NAME'].lower()
|
|
42
|
+
edges.append({
|
|
43
|
+
'from_id': 'TBL:%s:%s' % (repo_id, from_tbl),
|
|
44
|
+
'to_id': 'TBL:%s:%s' % (repo_id, to_tbl),
|
|
45
|
+
'edge_type': 'references',
|
|
46
|
+
'from_repo': repo_id, 'to_repo': repo_id,
|
|
47
|
+
'confidence': 1.0, 'source': 'db_fk',
|
|
48
|
+
'props': {'column': r['COLUMN_NAME'],
|
|
49
|
+
'ref_column': r['REFERENCED_COLUMN_NAME'],
|
|
50
|
+
'schema': db},
|
|
51
|
+
})
|
|
52
|
+
logger(' [db-fk] %d 条外键关系' % len(edges))
|
|
53
|
+
finally:
|
|
54
|
+
conn.close()
|
|
55
|
+
return edges
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""连 MySQL information_schema → 灌 db_tables / db_columns。
|
|
3
|
+
|
|
4
|
+
复用 kg_build_db.py 的 MySQL 连接配置 (内网 10.54.6.9, fywl_ics_* 库)。
|
|
5
|
+
连不上内网时 best-effort 返回空, 不阻塞 (GOAL 3 主链路是 mapper XML)。
|
|
6
|
+
"""
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
import os
|
|
9
|
+
import sys
|
|
10
|
+
import socket
|
|
11
|
+
|
|
12
|
+
_THIS = os.path.dirname(os.path.abspath(__file__))
|
|
13
|
+
for _i in range(8):
|
|
14
|
+
_p = os.path.dirname(_THIS)
|
|
15
|
+
if os.path.isfile(os.path.join(_p, 'foundation', 'bootstrap.py')):
|
|
16
|
+
sys.path.insert(0, _p); break
|
|
17
|
+
_THIS = _p
|
|
18
|
+
|
|
19
|
+
# 复用 kg_build_db 的配置
|
|
20
|
+
MYSQL_HOST = os.environ.get('MYSQL_HOST', '10.54.6.9')
|
|
21
|
+
MYSQL_PORT = int(os.environ.get('MYSQL_PORT', '3306'))
|
|
22
|
+
MYSQL_USER = os.environ.get('MYSQL_USER', 'fywl_ics_test')
|
|
23
|
+
MYSQL_PASSWORD = os.environ.get('MYSQL_PASSWORD', 'Fywl_ics_test56476')
|
|
24
|
+
DB_NAME_PREFIX = os.environ.get('DB_NAME_PREFIX', 'fywl_ics_')
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def _can_connect(timeout=3):
|
|
28
|
+
"""探测内网 MySQL 是否可达。"""
|
|
29
|
+
try:
|
|
30
|
+
with socket.create_connection((MYSQL_HOST, MYSQL_PORT), timeout=timeout):
|
|
31
|
+
return True
|
|
32
|
+
except Exception:
|
|
33
|
+
return False
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def extract_all_schema(repo_id: str = 'fywl-ics', logger=print):
|
|
37
|
+
"""连 MySQL → 灌 db_tables + db_columns。
|
|
38
|
+
|
|
39
|
+
Returns: (tables: list[dict], columns: list[dict]) 连不上返回 ([], [])
|
|
40
|
+
"""
|
|
41
|
+
if not _can_connect():
|
|
42
|
+
logger(' [db] ⚠️ 内网 MySQL 不可达 (%s:%s), 跳过 schema 提取' % (MYSQL_HOST, MYSQL_PORT))
|
|
43
|
+
return [], []
|
|
44
|
+
try:
|
|
45
|
+
import pymysql
|
|
46
|
+
except ImportError:
|
|
47
|
+
logger(' [db] pymysql 未装, 跳过')
|
|
48
|
+
return [], []
|
|
49
|
+
|
|
50
|
+
logger(' [db] 连接 MySQL %s:%s ...' % (MYSQL_HOST, MYSQL_PORT))
|
|
51
|
+
conn = pymysql.connect(host=MYSQL_HOST, port=MYSQL_PORT, user=MYSQL_USER,
|
|
52
|
+
password=MYSQL_PASSWORD, charset='utf8mb4',
|
|
53
|
+
cursorclass=pymysql.cursors.DictCursor,
|
|
54
|
+
connect_timeout=5, read_timeout=30)
|
|
55
|
+
tables, columns = [], []
|
|
56
|
+
try:
|
|
57
|
+
cur = conn.cursor()
|
|
58
|
+
cur.execute("SELECT schema_name FROM information_schema.schemata "
|
|
59
|
+
"WHERE schema_name LIKE %s ORDER BY schema_name",
|
|
60
|
+
(DB_NAME_PREFIX + '%'))
|
|
61
|
+
dbs = [r['schema_name'] for r in cur.fetchall()]
|
|
62
|
+
logger(' [db] 发现 %d 个业务库: %s' % (len(dbs), dbs[:3]))
|
|
63
|
+
|
|
64
|
+
for db in dbs:
|
|
65
|
+
# 表
|
|
66
|
+
cur.execute("SELECT table_name, table_comment, table_rows "
|
|
67
|
+
"FROM information_schema.tables WHERE table_schema=%s", (db,))
|
|
68
|
+
for r in cur.fetchall():
|
|
69
|
+
tables.append({
|
|
70
|
+
'repo_id': repo_id, 'schema_name': db,
|
|
71
|
+
'table_name': r['table_name'], 'table_comment': r.get('table_comment') or '',
|
|
72
|
+
'rows_count': r.get('table_rows'),
|
|
73
|
+
})
|
|
74
|
+
# 列
|
|
75
|
+
cur.execute("SELECT table_name, column_name, data_type, is_nullable, "
|
|
76
|
+
"column_comment, ordinal_position "
|
|
77
|
+
"FROM information_schema.columns WHERE table_schema=%s "
|
|
78
|
+
"ORDER BY table_name, ordinal_position", (db,))
|
|
79
|
+
for r in cur.fetchall():
|
|
80
|
+
columns.append({
|
|
81
|
+
'repo_id': repo_id, 'schema_name': db,
|
|
82
|
+
'table_name': r['table_name'], 'column_name': r['column_name'],
|
|
83
|
+
'data_type': r['data_type'], 'is_nullable': r['is_nullable'],
|
|
84
|
+
'column_comment': r.get('column_comment') or '',
|
|
85
|
+
'ordinal_position': r.get('ordinal_position'),
|
|
86
|
+
})
|
|
87
|
+
logger(' [db] 完成: %d 表 / %d 列' % (len(tables), len(columns)))
|
|
88
|
+
finally:
|
|
89
|
+
conn.close()
|
|
90
|
+
return tables, columns
|
|
@@ -428,3 +428,87 @@ def trace_clicks(vue_path, vue_content, api_table):
|
|
|
428
428
|
traces.append(trace)
|
|
429
429
|
|
|
430
430
|
return traces
|
|
431
|
+
|
|
432
|
+
|
|
433
|
+
# ============================================================
|
|
434
|
+
# GOAL 3 泛化: Carmg-H5 (Vant/H5) 风格 API + 调用提取
|
|
435
|
+
# ============================================================
|
|
436
|
+
# Carmg-H5 的 API 定义方式和 fywl-ui 不同:
|
|
437
|
+
# fywl-ui: enum Api { X='/url' } + requestClient.post(Api.X)
|
|
438
|
+
# Carmg-H5: export function fn(data){ const params={url:'/x',method:'post'}; return fetch(params) }
|
|
439
|
+
# 以及 van-button 的 @click/@tap 事件。
|
|
440
|
+
# 下面两个函数让 extract.py 能同时处理两种风格。
|
|
441
|
+
|
|
442
|
+
# Carmg-H5 API: export function fnName(...) { ... url: '/xxx' ... method: 'post' ... }
|
|
443
|
+
RE_H5_API_FN = re.compile(
|
|
444
|
+
r"export\s+function\s+(\w+)\s*\([^)]*\)\s*\{(?:(?!\}).)*?"
|
|
445
|
+
r"url:\s*['\"]([^'\"]+)['\"](?:(?!\}).)*?"
|
|
446
|
+
r"(?:method:\s*['\"](\w+)['\"])?",
|
|
447
|
+
re.DOTALL
|
|
448
|
+
)
|
|
449
|
+
|
|
450
|
+
|
|
451
|
+
def build_api_fn_table_h5(api_dir):
|
|
452
|
+
"""扫 src/api/**/*.js (Carmg-H5 风格) → {fnName: {url, verb, file}}。
|
|
453
|
+
|
|
454
|
+
识别 export function + url: 字面量模式。
|
|
455
|
+
"""
|
|
456
|
+
table = {}
|
|
457
|
+
if not os.path.isdir(api_dir):
|
|
458
|
+
return table
|
|
459
|
+
for root, dirs, files in os.walk(api_dir):
|
|
460
|
+
dirs[:] = [d for d in dirs if d not in ('dist', 'node_modules', '__pycache__')]
|
|
461
|
+
for f in files:
|
|
462
|
+
if not (f.endswith('.js') or f.endswith('.ts')):
|
|
463
|
+
continue
|
|
464
|
+
fp = os.path.join(root, f)
|
|
465
|
+
try:
|
|
466
|
+
content = open(fp, encoding='utf-8', errors='ignore').read()
|
|
467
|
+
except Exception:
|
|
468
|
+
continue
|
|
469
|
+
for m in RE_H5_API_FN.finditer(content):
|
|
470
|
+
fn_name, url, verb = m.group(1), m.group(2), m.group(3) or 'get'
|
|
471
|
+
table[fn_name] = {'url': url, 'verb': verb.lower(), 'file': fp}
|
|
472
|
+
return table
|
|
473
|
+
|
|
474
|
+
|
|
475
|
+
def trace_clicks_h5(vue_path, vue_content, api_table):
|
|
476
|
+
"""Vant/H5 风格的 click→api 追踪。
|
|
477
|
+
|
|
478
|
+
和 trace_clicks 区别:
|
|
479
|
+
- 同时认 @click 和 @tap (Vant 用 @tap)
|
|
480
|
+
- API 调用是 import { fn } from '@/api/xx' + fn(args) (不是 requestClient.xx)
|
|
481
|
+
"""
|
|
482
|
+
if not api_table:
|
|
483
|
+
return []
|
|
484
|
+
traces = []
|
|
485
|
+
# 收集 @click / @tap
|
|
486
|
+
clicks = list(re.finditer(r"@(?:click|tap)(?:\.\w+)?\s*=\s*['\"]([^'\"]+)['\"]", vue_content))
|
|
487
|
+
if not clicks:
|
|
488
|
+
return []
|
|
489
|
+
# 收集 import { fn1, fn2 } from '@/api/...'
|
|
490
|
+
imported = set()
|
|
491
|
+
for m in re.finditer(r"import\s*\{([^}]+)\}\s*from\s*['\"][^'\"]*api[^'\"]*['\"]", vue_content):
|
|
492
|
+
for n in re.split(r'[,\s]+', m.group(1)):
|
|
493
|
+
n = n.strip()
|
|
494
|
+
if n:
|
|
495
|
+
imported.add(n)
|
|
496
|
+
# script 里直接调用的 api 函数 (imported 集合内的)
|
|
497
|
+
for click in clicks:
|
|
498
|
+
handler = click.group(1).split('(')[0].strip()
|
|
499
|
+
if not handler:
|
|
500
|
+
continue
|
|
501
|
+
# 在 script 里找 handler 函数体, 看它调了哪些 imported api
|
|
502
|
+
handler_body = _get_fn_body(vue_content, handler)
|
|
503
|
+
if not handler_body:
|
|
504
|
+
continue
|
|
505
|
+
for api_fn in imported:
|
|
506
|
+
if api_fn in api_table and re.search(r'\b' + re.escape(api_fn) + r'\s*\(', handler_body):
|
|
507
|
+
info = api_table[api_fn]
|
|
508
|
+
traces.append({
|
|
509
|
+
'vue_file': vue_path, 'button_text': handler,
|
|
510
|
+
'handler': handler, 'api_fn': api_fn,
|
|
511
|
+
'endpoint': info['url'], 'verb': info['verb'],
|
|
512
|
+
'api_file': info['file'], 'confidence': 'high',
|
|
513
|
+
})
|
|
514
|
+
return traces
|