@hupan56/wlkj 3.3.8 → 3.3.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (120) hide show
  1. package/package.json +1 -1
  2. package/templates/qoder/commands/optional/wl-spec.md +12 -2
  3. package/templates/qoder/commands/optional/wl-status.md +8 -0
  4. package/templates/qoder/commands/wl-code.md +8 -0
  5. package/templates/qoder/commands/wl-commit.md +11 -1
  6. package/templates/qoder/commands/wl-data.md +46 -0
  7. package/templates/qoder/commands/wl-fix.md +47 -0
  8. package/templates/qoder/commands/wl-init.md +2 -2
  9. package/templates/qoder/commands/wl-knowledge.md +41 -0
  10. package/templates/qoder/commands/wl-prd.md +23 -22
  11. package/templates/qoder/commands/wl-review.md +49 -0
  12. package/templates/qoder/commands/wl-search.md +23 -27
  13. package/templates/qoder/commands/wl-task.md +11 -18
  14. package/templates/qoder/commands/wl-test.md +8 -0
  15. package/templates/qoder/contracts/insight.md +55 -0
  16. package/templates/qoder/hooks/__pycache__/pre-tool-use-commit.cpython-39.pyc +0 -0
  17. package/templates/qoder/hooks/__pycache__/pre-tool-use.cpython-39.pyc +0 -0
  18. package/templates/qoder/hooks/__pycache__/session-start.cpython-39.pyc +0 -0
  19. package/templates/qoder/hooks/__pycache__/stop-eval.cpython-39.pyc +0 -0
  20. package/templates/qoder/hooks/pre-tool-use-commit.py +124 -0
  21. package/templates/qoder/hooks/session-start.py +20 -1
  22. package/templates/qoder/hooks/stop-eval.py +129 -0
  23. package/templates/qoder/scripts/capability/adapters/mcp.py +1 -0
  24. package/templates/qoder/scripts/capability/registry.py +0 -3
  25. package/templates/qoder/scripts/domain/integration/return_to_platform.py +1 -0
  26. package/templates/qoder/scripts/domain/integration/spec_upload.py +3 -3
  27. package/templates/qoder/scripts/domain/kg/build/build_entity_registry.py +12 -12
  28. package/templates/qoder/scripts/domain/kg/build/build_relations.py +12 -12
  29. package/templates/qoder/scripts/foundation/__pycache__/__init__.cpython-39.pyc +0 -0
  30. package/templates/qoder/scripts/foundation/io/__pycache__/__init__.cpython-39.pyc +0 -0
  31. package/templates/qoder/scripts/foundation/io/__pycache__/context_cache.cpython-39.pyc +0 -0
  32. package/templates/qoder/scripts/foundation/io/context_cache.py +94 -0
  33. package/templates/qoder/scripts/orchestration/__pycache__/wlkj.cpython-39.pyc +0 -0
  34. package/templates/qoder/scripts/orchestration/wlkj.py +95 -0
  35. package/templates/qoder/scripts/tool_guide.md +70 -0
  36. package/templates/qoder/scripts/validation/eval/__pycache__/alignment_matrix.cpython-39.pyc +0 -0
  37. package/templates/qoder/scripts/validation/eval/__pycache__/bf2_content_fidelity.cpython-39.pyc +0 -0
  38. package/templates/qoder/scripts/validation/eval/__pycache__/bf2_llmjudge.cpython-39.pyc +0 -0
  39. package/templates/qoder/scripts/validation/eval/__pycache__/bf_score.cpython-39.pyc +0 -0
  40. package/templates/qoder/scripts/validation/eval/__pycache__/code_flywheel.cpython-39.pyc +0 -0
  41. package/templates/qoder/scripts/validation/eval/__pycache__/dispatcher_ab.cpython-39.pyc +0 -0
  42. package/templates/qoder/scripts/validation/eval/__pycache__/feature_fidelity_flywheel.cpython-39.pyc +0 -0
  43. package/templates/qoder/scripts/validation/eval/__pycache__/gradient_matrix.cpython-39.pyc +0 -0
  44. package/templates/qoder/scripts/validation/eval/__pycache__/metrics_dashboard.cpython-39.pyc +0 -0
  45. package/templates/qoder/scripts/validation/eval/__pycache__/multi_turn_flywheel.cpython-39.pyc +0 -0
  46. package/templates/qoder/scripts/validation/eval/__pycache__/prd_fidelity_flywheel.cpython-39.pyc +0 -0
  47. package/templates/qoder/scripts/validation/eval/__pycache__/prd_flywheel.cpython-39.pyc +0 -0
  48. package/templates/qoder/scripts/validation/eval/__pycache__/prototype_fidelity_flywheel.cpython-39.pyc +0 -0
  49. package/templates/qoder/scripts/validation/eval/__pycache__/recall_flywheel.cpython-39.pyc +0 -0
  50. package/templates/qoder/scripts/validation/eval/__pycache__/robustness_flywheel.cpython-39.pyc +0 -0
  51. package/templates/qoder/scripts/validation/eval/__pycache__/speed_accuracy_flywheel.cpython-39.pyc +0 -0
  52. package/templates/qoder/scripts/validation/eval/__pycache__/task_flywheel.cpython-39.pyc +0 -0
  53. package/templates/qoder/scripts/validation/eval/__pycache__/token_flywheel.cpython-39.pyc +0 -0
  54. package/templates/qoder/scripts/validation/eval/alignment_matrix.py +176 -0
  55. package/templates/qoder/scripts/validation/eval/bf2_content_fidelity.py +110 -0
  56. package/templates/qoder/scripts/validation/eval/bf2_llmjudge.py +104 -0
  57. package/templates/qoder/scripts/validation/eval/bf_score.py +218 -0
  58. package/templates/qoder/scripts/validation/eval/code_flywheel.py +150 -0
  59. package/templates/qoder/scripts/validation/eval/dispatcher_ab.py +156 -0
  60. package/templates/qoder/scripts/validation/eval/dispatcher_ab_2026-07-21.json +23 -0
  61. package/templates/qoder/scripts/validation/eval/feature_fidelity_flywheel.py +143 -0
  62. package/templates/qoder/scripts/validation/eval/gradient_matrix.py +261 -0
  63. package/templates/qoder/scripts/validation/eval/gradient_matrix_baseline_2026-07-21.json +33 -0
  64. package/templates/qoder/scripts/validation/eval/metrics_dashboard.py +105 -0
  65. package/templates/qoder/scripts/validation/eval/multi_turn_flywheel.py +118 -0
  66. package/templates/qoder/scripts/validation/eval/prd_fidelity_flywheel.py +128 -0
  67. package/templates/qoder/scripts/validation/eval/prd_flywheel.py +148 -0
  68. package/templates/qoder/scripts/validation/eval/prototype_fidelity_flywheel.py +166 -0
  69. package/templates/qoder/scripts/validation/eval/recall_flywheel.py +148 -0
  70. package/templates/qoder/scripts/validation/eval/robustness_flywheel.py +139 -0
  71. package/templates/qoder/scripts/validation/eval/speed_accuracy_flywheel.py +188 -0
  72. package/templates/qoder/scripts/validation/eval/task_flywheel.py +124 -0
  73. package/templates/qoder/scripts/validation/eval/token_flywheel.py +88 -0
  74. package/templates/qoder/scripts/validation/metrics/__pycache__/lint_cases.cpython-39.pyc +0 -0
  75. package/templates/qoder/scripts/validation/metrics/eval_code_ac.py +177 -0
  76. package/templates/qoder/scripts/validation/metrics/lint_cases.py +170 -0
  77. package/templates/qoder/scripts/validation/test/__pycache__/test_context_cache.cpython-39.pyc +0 -0
  78. package/templates/qoder/scripts/validation/test/test_context_cache.py +78 -0
  79. package/templates/qoder/scripts/validation/test/test_lint_cases.py +60 -0
  80. package/templates/qoder/scripts/validation/test/test_pre_tool_use_commit.py +70 -0
  81. package/templates/qoder/settings.json +8 -0
  82. package/templates/qoder/skills/wl-spec/SKILL.md +1 -1
  83. package/templates/workspace/specs/REQ-TEST-CTX/.context-cache.json +1 -0
  84. package/templates/qoder/agents/design-agent.md +0 -20
  85. package/templates/qoder/agents/spec-generator.md +0 -21
  86. package/templates/qoder/scripts/capability/smoke_test_report.json.new +0 -94
  87. package/templates/qoder/scripts/domain/kg/extract/asset/__init__.py +0 -10
  88. package/templates/qoder/scripts/domain/kg/extract/asset/asset_tree.py +0 -57
  89. package/templates/qoder/scripts/domain/kg/extract/asset/discussion_importer.py +0 -62
  90. package/templates/qoder/scripts/domain/kg/extract/asset/prd_importer.py +0 -146
  91. package/templates/qoder/scripts/domain/kg/extract/asset/prototype_importer.py +0 -64
  92. package/templates/qoder/scripts/domain/kg/extract/asset/returns_importer.py +0 -52
  93. package/templates/qoder/scripts/domain/kg/extract/build_goal3.py +0 -104
  94. package/templates/qoder/scripts/domain/kg/extract/build_goal4.py +0 -55
  95. package/templates/qoder/scripts/domain/kg/extract/build_goal5.py +0 -95
  96. package/templates/qoder/scripts/domain/kg/extract/db/__init__.py +0 -8
  97. package/templates/qoder/scripts/domain/kg/extract/db/data_profile.py +0 -22
  98. package/templates/qoder/scripts/domain/kg/extract/db/fk_extractor.py +0 -55
  99. package/templates/qoder/scripts/domain/kg/extract/db/schema_extractor.py +0 -90
  100. package/templates/qoder/scripts/domain/kg/extract/inference/__init__.py +0 -9
  101. package/templates/qoder/scripts/domain/kg/extract/inference/community_summarizer.py +0 -206
  102. package/templates/qoder/scripts/domain/kg/extract/inference/embed_builder.py +0 -132
  103. package/templates/qoder/scripts/domain/kg/extract/inference/naming_matcher.py +0 -80
  104. package/templates/qoder/scripts/domain/kg/extract/inference/promote.py +0 -59
  105. package/templates/qoder/scripts/domain/kg/extract/inference/recompute.py +0 -93
  106. package/templates/qoder/scripts/domain/kg/extract/inference/weak_link.py +0 -421
  107. package/templates/qoder/scripts/domain/kg/extract/mybatis/__init__.py +0 -9
  108. package/templates/qoder/scripts/domain/kg/extract/mybatis/all.py +0 -79
  109. package/templates/qoder/scripts/domain/kg/extract/mybatis/mapper_parser.py +0 -99
  110. package/templates/qoder/scripts/domain/kg/extract/mybatis/relation_builder.py +0 -69
  111. package/templates/qoder/scripts/domain/kg/extract/mybatis/sql_extractor.py +0 -78
  112. package/templates/qoder/scripts/domain/kg/extract/prd/__init__.py +0 -8
  113. package/templates/qoder/scripts/domain/kg/extract/prd/prd_chunk_embed.py +0 -105
  114. package/templates/qoder/scripts/domain/kg/extract/prd/prd_llm_extract.py +0 -153
  115. package/templates/qoder/scripts/domain/kg/extract/prd/req_anchor.py +0 -120
  116. package/templates/qoder/scripts/domain/kg/graph/kg_link_db.py +0 -235
  117. package/templates/qoder/scripts/domain/kg/search/enrich_prompt.py +0 -238
  118. package/templates/qoder/scripts/domain/kg/server/perf_bench.py +0 -197
  119. package/templates/qoder/scripts/domain/kg/sync_repowiki.py +0 -109
  120. package/templates/qoder/scripts/engine/poller.py +0 -219
@@ -1,79 +0,0 @@
1
- # -*- coding: utf-8 -*-
2
- """MyBatis XML 提取协调器。
3
-
4
- 扫所有 *Mapper.xml → 解析 → 建关系 → 返回 (entities, edges)。
5
- """
6
- from __future__ import annotations
7
- import os
8
- import sys
9
- import glob
10
- import time
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
- from domain.kg.extract.mybatis import mapper_parser as mp
20
- from domain.kg.extract.mybatis import relation_builder as rb
21
-
22
-
23
- def extract_file(filepath: str, repo_id: str):
24
- """单文件提取 → (entities, edges)。"""
25
- parsed = mp.parse_mapper_xml(filepath)
26
- if not parsed.get('namespace_short'):
27
- return [], []
28
- return rb.build_relations(parsed, repo_id, filepath)
29
-
30
-
31
- def extract_repo(repo_dir: str, repo_id: str, logger=print):
32
- """扫 repo 下所有 mapper XML → 提取。"""
33
- t0 = time.time()
34
- patterns = [
35
- os.path.join(repo_dir, '**', '*Mapper.xml'),
36
- os.path.join(repo_dir, '**', '*mapper*.xml'),
37
- os.path.join(repo_dir, '**', 'mapper', '*.xml'),
38
- ]
39
- xml_files = set()
40
- for pat in patterns:
41
- xml_files.update(glob.glob(pat, recursive=True))
42
- # 跳过编译产物 target/classes 和测试资源
43
- xml_files = [f for f in xml_files if '/target/' not in f.replace('\\', '/')
44
- and '\\target\\' not in f.lower()
45
- and '/test/' not in f.replace('\\', '/').lower()]
46
- total = len(xml_files)
47
- logger(' [mybatis] 扫描 %d 个 mapper XML (repo=%s)' % (total, repo_id))
48
-
49
- all_entities, all_edges = [], []
50
- err = 0
51
- for xf in xml_files:
52
- try:
53
- es, eds = extract_file(xf, repo_id)
54
- all_entities += es
55
- all_edges += eds
56
- except Exception as e:
57
- err += 1
58
- if err <= 2:
59
- logger(' [mybatis] 失败 %s: %s' % (xf, str(e)[:50]))
60
-
61
- dt = time.time() - t0
62
- tables = set()
63
- for e in all_entities:
64
- if e['type'] == 'TABLE':
65
- tables.add(e['canonical'])
66
- logger(' [mybatis] 完成: %d 实体 / %d 边 / %d 表 / %d 错 / %.1fs' % (
67
- len(all_entities), len(all_edges), len(tables), err, dt))
68
- return all_entities, all_edges
69
-
70
-
71
- if __name__ == '__main__':
72
- import argparse, json
73
- ap = argparse.ArgumentParser()
74
- ap.add_argument('repo_dir')
75
- ap.add_argument('--repo-id', default='')
76
- args = ap.parse_args()
77
- rid = args.repo_id or os.path.basename(args.repo_dir.rstrip('/\\'))
78
- es, eds = extract_repo(args.repo_dir, rid)
79
- print(json.dumps({'entities': len(es), 'edges': len(eds)}, ensure_ascii=False, indent=2))
@@ -1,99 +0,0 @@
1
- # -*- coding: utf-8 -*-
2
- """mapper XML 解析器: namespace + resultMap + 方法→SQL 映射。
3
-
4
- 输出每个 mapper 文件的结构化信息, 供 relation_builder 建 queries 边 + COLUMN 实体。
5
- """
6
- from __future__ import annotations
7
- import os
8
- import re
9
- import xml.etree.ElementTree as ET
10
-
11
- from . import sql_extractor as _sq
12
-
13
-
14
- def _extract_table_names(sql):
15
- """从 SQL 提取表名 (转发到 sql_extractor)。"""
16
- return _sq.extract_table_names(sql)
17
-
18
-
19
- def parse_mapper_xml(filepath: str) -> dict:
20
- """解析一个 mapper XML。
21
-
22
- Returns:
23
- {
24
- 'namespace': 'com.fywl.ics.demo.mapper.TestDemoMapper',
25
- 'namespace_short': 'TestDemoMapper',
26
- 'statements': [{'id':'customPageList','type':'select','sql':'SELECT * FROM test_demo...', 'tables':['test_demo']}],
27
- 'result_maps': {'TestDemoResult': [{'property':'id','column':'id'}, ...]},
28
- 'result_types': ['com.fywl.ics.demo.domain.vo.TestDemoVo'],
29
- }
30
- """
31
- try:
32
- tree = ET.parse(filepath)
33
- root = tree.getroot()
34
- except Exception:
35
- return _empty_result()
36
-
37
- namespace = root.get('namespace', '') or ''
38
- namespace_short = namespace.rsplit('.', 1)[-1] if namespace else os.path.basename(filepath).replace('.xml', '')
39
-
40
- statements = []
41
- result_maps = {}
42
- result_types = set()
43
-
44
- for tag in ('select', 'insert', 'update', 'delete'):
45
- for stmt in root.findall(tag):
46
- sid = stmt.get('id', '')
47
- sql_text = _extract_sql_text(stmt)
48
- tables = _extract_table_names(sql_text)
49
- if stmt.get('resultType'):
50
- result_types.add(stmt.get('resultType'))
51
- statements.append({
52
- 'id': sid, 'type': tag, 'sql': sql_text, 'tables': tables,
53
- })
54
-
55
- for rm in root.findall('resultMap'):
56
- rm_id = rm.get('id', '')
57
- cols = []
58
- for child_tag in ('id', 'result'):
59
- for child in rm.findall(child_tag):
60
- cols.append({
61
- 'property': child.get('property', ''),
62
- 'column': child.get('column', ''),
63
- })
64
- if rm.get('type'):
65
- result_types.add(rm.get('type'))
66
- result_maps[rm_id] = cols
67
-
68
- return {
69
- 'namespace': namespace,
70
- 'namespace_short': namespace_short,
71
- 'statements': statements,
72
- 'result_maps': result_maps,
73
- 'result_types': list(result_types),
74
- }
75
-
76
-
77
- def _extract_sql_text(node) -> str:
78
- """提取 statement 节点的完整 SQL 文本(含 <if>/<foreach> 展开)。"""
79
- parts = []
80
- if node.text:
81
- parts.append(node.text)
82
- for child in node:
83
- # <if>/<foreach>/<choose>/<where>/<set>/<trim> 等, 取 text + tail
84
- if child.text:
85
- parts.append(child.text)
86
- # 递归子节点 (foreach 里可能有 if)
87
- for sub in child:
88
- if sub.text:
89
- parts.append(sub.text)
90
- if sub.tail:
91
- parts.append(sub.tail)
92
- if child.tail:
93
- parts.append(child.tail)
94
- return ' '.join(p.strip() for p in parts if p and p.strip())
95
-
96
-
97
- def _empty_result():
98
- return {'namespace': '', 'namespace_short': '', 'statements': [],
99
- 'result_maps': {}, 'result_types': []}
@@ -1,69 +0,0 @@
1
- # -*- coding: utf-8 -*-
2
- """Mapper ↔ Entity ↔ Table 三向关联构建器。
3
-
4
- 把 mapper_parser 的结构化信息 + sql_extractor 的表名 →
5
- 生成 entities (TABLE/COLUMN) + edges (queries/maps_to/returns)。
6
- """
7
- from __future__ import annotations
8
- from . import mapper_parser as mp
9
- from . import sql_extractor as sq
10
-
11
-
12
- def build_relations(parsed: dict, repo_id: str, filepath: str):
13
- """从单个 mapper XML 的解析结果构建实体 + 边。
14
-
15
- Returns: (entities, edges)
16
- entities: TABLE / COLUMN 实体
17
- edges: queries (Mapper→Table) / column_maps (COLUMN 关联)
18
- """
19
- entities, edges = [], []
20
- ns_short = parsed.get('namespace_short', '')
21
- ns_full = parsed.get('namespace', '')
22
- if not ns_short:
23
- return entities, edges
24
-
25
- # Mapper 实体 id (对齐 GOAL 2 的 entity_id 规则: MAP:repo:Name)
26
- mapper_id = 'MAP:%s:%s' % (repo_id, ns_short)
27
-
28
- # ===== queries 边: Mapper → 每个被访问的 TABLE =====
29
- all_tables = set()
30
- for stmt in parsed.get('statements', []):
31
- for tbl in stmt.get('tables', []):
32
- all_tables.add(tbl)
33
-
34
- for tbl in all_tables:
35
- tbl_id = 'TBL:%s:%s' % (repo_id, tbl)
36
- # TABLE 实体 (如果 GOAL 2 没建过, 这里补建)
37
- entities.append({
38
- 'id': tbl_id, 'repo_id': repo_id, 'type': 'TABLE',
39
- 'canonical': tbl, 'cn': '',
40
- 'props': {'from': 'mapper_xml', 'file': filepath},
41
- })
42
- # queries 边
43
- edges.append({
44
- 'from_id': mapper_id, 'to_id': tbl_id,
45
- 'edge_type': 'queries',
46
- 'from_repo': repo_id, 'to_repo': repo_id,
47
- 'confidence': 1.0, 'source': 'mapper_xml',
48
- 'props': {'via': 'mybatis_xml'},
49
- })
50
-
51
- # ===== COLUMN 实体 (从 resultMap 提取) =====
52
- for rm_id, cols in parsed.get('result_maps', {}).items():
53
- for col_map in cols:
54
- col_name = col_map.get('column', '')
55
- if not col_name or '$' in col_name:
56
- continue
57
- # COLUMN 实体 (关联到第一个被访问的表, 兜底)
58
- hint_table = list(all_tables)[:1]
59
- tbl_for_col = hint_table[0] if hint_table else 'unknown'
60
- col_id = 'COL:%s:%s.%s' % (repo_id, tbl_for_col, col_name)
61
- entities.append({
62
- 'id': col_id, 'repo_id': repo_id, 'type': 'COLUMN',
63
- 'canonical': col_name, 'cn': '',
64
- 'props': {'table': tbl_for_col,
65
- 'property': col_map.get('property', ''),
66
- 'from': 'result_map', 'file': filepath},
67
- })
68
-
69
- return entities, edges
@@ -1,78 +0,0 @@
1
- # -*- coding: utf-8 -*-
2
- """SQL 文本提取器: 从 SQL 提取表名 + 列引用 + 操作类型。
3
-
4
- 正则匹配 MyBatis 动态 SQL 里残留的 ${ew.customSqlSegment} 等,
5
- 但 FROM/INTO/UPDATE/JOIN 后的表名通常稳定可提。
6
- """
7
- from __future__ import annotations
8
- import re
9
-
10
- # 表名提取 (FROM/JOIN/INTO/UPDATE 后跟标识符)
11
- _TABLE_PATTERNS = [
12
- re.compile(r'\bFROM\s+`?([a-z_][a-z0-9_]*)`?', re.IGNORECASE),
13
- re.compile(r'\bJOIN\s+`?([a-z_][a-z0-9_]*)`?', re.IGNORECASE),
14
- re.compile(r'\bINTO\s+`?([a-z_][a-z0-9_]*)`?', re.IGNORECASE),
15
- re.compile(r'\bUPDATE\s+`?([a-z_][a-z0-9_]*)`?\s+SET', re.IGNORECASE),
16
- ]
17
-
18
- # 排除: 显然不是表名的 (变量/占位符/SQL关键字)
19
- _NOT_TABLES = {'dual', 'select', 'where', 'set', 'values', 'from', 'join',
20
- 'and', 'or', 'not', 'in', 'as', 'on', 'left', 'right', 'inner',
21
- 'outer', 'group', 'order', 'limit', 'distinct', 'case', 'when',
22
- 'then', 'else', 'end', 'null', 'true', 'false'}
23
-
24
-
25
- def extract_table_names(sql: str) -> list:
26
- """从 SQL 文本提取所有被访问的表名。
27
-
28
- Returns: ['test_demo', 'sys_user'] (去重保序)
29
- """
30
- if not sql:
31
- return []
32
- found = []
33
- seen = set()
34
- for pat in _TABLE_PATTERNS:
35
- for m in pat.finditer(sql):
36
- tbl = m.group(1).lower()
37
- if tbl in _NOT_TABLES or tbl.startswith('$') or tbl.startswith('#'):
38
- continue
39
- # 排除太短或含特殊字符的 (变量插值残留)
40
- if len(tbl) < 2 or '$' in tbl or '{' in tbl:
41
- continue
42
- if tbl not in seen:
43
- seen.add(tbl)
44
- found.append(tbl)
45
- return found
46
-
47
-
48
- def extract_column_refs(sql: str) -> list:
49
- """提取 SQL 里引用的列名 (粗粒度, 主要看 SELECT 字段列表)。
50
-
51
- Returns: ['id', 'user_id', 'name'] (去重)
52
- """
53
- if not sql:
54
- return []
55
- cols = set()
56
- # SELECT field1, field2 FROM ... (字段列表)
57
- m = re.search(r'SELECT\s+(DISTINCT\s+)?(.*?)\s+FROM', sql, re.IGNORECASE | re.DOTALL)
58
- if m:
59
- field_part = m.group(2)
60
- if field_part.strip() != '*':
61
- for token in re.split(r'[,\s]+', field_part):
62
- token = token.strip().strip('`')
63
- # 别名 as xx → 取前半
64
- token = token.split(' as ')[0].split(' AS ')[0].strip()
65
- if re.match(r'^[a-z_][a-z0-9_]*$', token, re.I) and len(token) >= 2:
66
- cols.add(token.lower())
67
- # xx.column 形式 (表别名.列)
68
- for m in re.finditer(r'\b[a-z]+\s*\.\s*([a-z_][a-z0-9_]*)', sql, re.IGNORECASE):
69
- cols.add(m.group(1).lower())
70
- return sorted(cols)
71
-
72
-
73
- def classify_operation(stmt_type: str) -> str:
74
- """MyBatis statement 类型 → 操作分类。"""
75
- return {
76
- 'select': 'READ', 'insert': 'WRITE',
77
- 'update': 'WRITE', 'delete': 'WRITE',
78
- }.get(stmt_type.lower(), 'UNKNOWN')
@@ -1,8 +0,0 @@
1
- # -*- coding: utf-8 -*-
2
- """PRD 业务知识提取 (GOAL 4)。
3
-
4
- 子模块:
5
- prd_llm_extract PRD 正文 → LLM 抽功能点/字段/规则/端点/权限
6
- prd_chunk_embed PRD 分块 + embedding (LightRAG vector 路)
7
- req_anchor REQ-ID 强关联 (PRD/commit/代码注释 → anchors 表)
8
- """
@@ -1,105 +0,0 @@
1
- # -*- coding: utf-8 -*-
2
- """PRD 分块 + embedding → LightRAG vector 路。
3
-
4
- 让 rag_search 能搜到 PRD 正文 (语义召回), 不再只搜标题。
5
- 复用工作台 rag.py 的 embed_texts (DashScope text-embedding-v3, 1024维)。
6
- """
7
- from __future__ import annotations
8
- import os
9
- import sys
10
-
11
- _THIS = os.path.dirname(os.path.abspath(__file__))
12
- for _i in range(8):
13
- _p = os.path.dirname(_THIS)
14
- if os.path.isfile(os.path.join(_p, 'foundation', 'bootstrap.py')):
15
- sys.path.insert(0, _p); break
16
- _THIS = _p
17
-
18
-
19
- def chunk_text(text: str, size: int = 500, overlap: int = 50) -> list:
20
- """滑窗分块。Returns: [chunk1, chunk2, ...]"""
21
- if not text:
22
- return []
23
- chunks = []
24
- i = 0
25
- while i < len(text):
26
- chunk = text[i:i + size]
27
- chunks.append(chunk)
28
- i += size - overlap
29
- return chunks
30
-
31
-
32
- def embed_prd(prd_id: str, content_md: str, repo_id: str = ''):
33
- """PRD 分块 + embedding → 写 PG embeddings 表。
34
-
35
- Returns: {chunks, embedded} 失败返回 {'chunks':0,'embedded':0}
36
- """
37
- chunks = chunk_text(content_md)
38
- if not chunks:
39
- return {'chunks': 0, 'embedded': 0}
40
-
41
- # 复用工作台 rag.py 的 embed_texts
42
- try:
43
- ws_backend = os.path.join(os.getcwd(), 'wlinkj-workspace', 'backend')
44
- sys.path.insert(0, ws_backend)
45
- from app.rag import embed_texts
46
- except Exception:
47
- # 兜底: 直接调 DashScope embedding API
48
- return _embed_fallback(prd_id, chunks, repo_id)
49
-
50
- vecs = embed_texts(chunks)
51
- if not vecs:
52
- return {'chunks': len(chunks), 'embedded': 0}
53
-
54
- # 写 PG embeddings 表
55
- _upsert_embeddings(prd_id, chunks, vecs, repo_id)
56
- return {'chunks': len(chunks), 'embedded': len(vecs)}
57
-
58
-
59
- def _embed_fallback(prd_id, chunks, repo_id):
60
- """直接调 DashScope text-embedding-v3 (rag.py 不可用时)。"""
61
- import json, urllib.request
62
- key = os.environ.get('DASHSCOPE_API_KEY', '')
63
- if not key:
64
- return {'chunks': len(chunks), 'embedded': 0}
65
- # DashScope embedding API
66
- url = 'https://dashscope.aliyuncs.com/compatible-mode/v1/embeddings'
67
- try:
68
- req = urllib.request.Request(
69
- url,
70
- data=json.dumps({
71
- 'model': 'text-embedding-v3',
72
- 'input': chunks[:10], # 单次上限10条
73
- 'dimensions': 1024,
74
- }).encode('utf-8'),
75
- headers={'Authorization': 'Bearer ' + key, 'Content-Type': 'application/json'},
76
- )
77
- with urllib.request.urlopen(req, timeout=30) as resp:
78
- r = json.loads(resp.read())
79
- vecs = [d['embedding'] for d in r['data']]
80
- _upsert_embeddings(prd_id, chunks[:len(vecs)], vecs, repo_id)
81
- return {'chunks': len(chunks), 'embedded': len(vecs)}
82
- except Exception:
83
- return {'chunks': len(chunks), 'embedded': 0}
84
-
85
-
86
- def _upsert_embeddings(prd_id, chunks, vecs, repo_id):
87
- """写 PG embeddings 表 (pgvector)。"""
88
- try:
89
- from domain.kg.extract.java import pg_upsert
90
- engine, _ = pg_upsert._get_pg_engine()
91
- if engine is None:
92
- return
93
- from sqlalchemy import text
94
- import json
95
- with engine.begin() as conn:
96
- for chunk, vec in zip(chunks, vecs):
97
- vec_str = '[' + ','.join(str(v) for v in vec) + ']'
98
- eid = 'PRDEMB:%s:%d' % (prd_id[:12], hash(chunk) & 0xffff)
99
- conn.execute(text("""
100
- INSERT INTO embeddings (entity_id, repo_id, kind, content, vec, embedded_at)
101
- VALUES (:id, :repo, 'prd', :content, CAST(:vec AS VECTOR), now())
102
- ON CONFLICT (entity_id) DO UPDATE SET content=EXCLUDED.content, vec=EXCLUDED.vec, embedded_at=now()
103
- """), {'id': eid, 'repo': repo_id, 'content': chunk[:500], 'vec': vec_str})
104
- except Exception as e:
105
- print(' [prd-embed] 写入跳过:', str(e)[:60])
@@ -1,153 +0,0 @@
1
- # -*- coding: utf-8 -*-
2
- """PRD 正文 → LLM 抽取功能点/字段/规则/端点/权限。
3
-
4
- 输入: PRD markdown 正文
5
- 输出: 结构化知识 (FEATURE/CONSTRAINT/PERMISSION 实体)
6
- 用 DashScope qwen-plus (已接入) 做结构化抽取。
7
-
8
- 被 asset/prd_importer.py 调用 (灌资产中心 prds 表时, 每个PRD正文过一遍LLM)。
9
- """
10
- from __future__ import annotations
11
- import os
12
- import sys
13
- import json
14
- import re
15
- import urllib.request
16
-
17
- _THIS = os.path.dirname(os.path.abspath(__file__))
18
- for _i in range(8):
19
- _p = os.path.dirname(_THIS)
20
- if os.path.isfile(os.path.join(_p, 'foundation', 'bootstrap.py')):
21
- sys.path.insert(0, _p); break
22
- _THIS = _p
23
-
24
- PRD_EXTRACT_PROMPT = """从以下PRD正文提取结构化知识, 只返回严格JSON(不要markdown代码块, 不要解释), 格式:
25
- {"features":[{"name":"资产列表分页","desc":"支持按页查看"}],"fields":[{"name":"owner_id","label":"资产归属人","rule":"必填"}],"rules":[{"rule":"金额>1万需二次审批","type":"business"}],"endpoints":[{"path":"/asset/list","method":"GET","desc":"资产列表"}],"permissions":[{"code":"asset:list","desc":"资产查看权限"}]}
26
- 若某类提取不到就返回空数组。PRD正文:
27
- ---
28
- %s
29
- ---"""
30
-
31
-
32
- def _call_qwen(prompt: str, timeout: int = 60) -> str:
33
- """调 DashScope qwen-plus, 返回文本。失败返回 ''。"""
34
- key = os.environ.get('DASHSCOPE_API_KEY', '')
35
- base = os.environ.get('ALI_CHAT_BASE', '').rstrip('/')
36
- model = os.environ.get('ALI_CHAT_MODEL', 'qwen-plus')
37
- if not key or not base:
38
- return ''
39
- req = urllib.request.Request(
40
- base + '/chat/completions',
41
- data=json.dumps({
42
- 'model': model,
43
- 'messages': [{'role': 'user', 'content': prompt}],
44
- 'max_tokens': 2000, 'temperature': 0.1,
45
- }).encode('utf-8'),
46
- headers={'Authorization': 'Bearer ' + key, 'Content-Type': 'application/json'},
47
- )
48
- try:
49
- with urllib.request.urlopen(req, timeout=timeout) as resp:
50
- r = json.loads(resp.read())
51
- return r['choices'][0]['message']['content']
52
- except Exception:
53
- return ''
54
-
55
-
56
- def _parse_json_lenient(text: str) -> dict:
57
- """宽松解析 LLM 返回的 JSON (可能有 markdown 包裹或前后缀)。"""
58
- if not text:
59
- return {}
60
- # 剥 markdown 代码块
61
- text = re.sub(r'^```(?:json)?\s*', '', text.strip())
62
- text = re.sub(r'\s*```$', '', text.strip())
63
- # 找第一个 { 到最后一个 }
64
- s = text.find('{')
65
- e = text.rfind('}')
66
- if s >= 0 and e > s:
67
- text = text[s:e + 1]
68
- try:
69
- return json.loads(text)
70
- except Exception:
71
- return {}
72
-
73
-
74
- def extract_prd_knowledge(content_md: str) -> dict:
75
- """PRD 正文 → LLM 抽取 → 结构化 dict。
76
-
77
- Returns: {features, fields, rules, endpoints, permissions}
78
- """
79
- if not content_md or len(content_md) < 50:
80
- return {'features': [], 'fields': [], 'rules': [], 'endpoints': [], 'permissions': []}
81
- # 截断过长正文 (避免超 token)
82
- content = content_md[:6000]
83
- raw = _call_qwen(PRD_EXTRACT_PROMPT % content)
84
- parsed = _parse_json_lenient(raw)
85
- # 兜底确保字段齐全
86
- return {
87
- 'features': parsed.get('features', []),
88
- 'fields': parsed.get('fields', []),
89
- 'rules': parsed.get('rules', []),
90
- 'endpoints': parsed.get('endpoints', []),
91
- 'permissions': parsed.get('permissions', []),
92
- }
93
-
94
-
95
- def to_entities_edges(extracted: dict, prd_id: str, project_id: str, repo_id: str = ''):
96
- """把 LLM 抽取结果转成 entities + edges。
97
-
98
- entities: FEATURE / CONSTRAINT / PERMISSION
99
- edges: 全部锚到 prd_id (强关联, confidence=1.0)
100
- """
101
- entities, edges = [], []
102
- base = 'FEAT:%s' % prd_id[:12] if prd_id else 'FEAT:unknown'
103
-
104
- # 功能点 → FEATURE
105
- for f in extracted.get('features', []):
106
- fname = f.get('name', '').strip()
107
- if not fname:
108
- continue
109
- fid = '%s:%s' % (base, fname[:20])
110
- entities.append({
111
- 'id': fid, 'repo_id': repo_id, 'type': 'FEATURE',
112
- 'canonical': fname, 'cn': f.get('desc', ''),
113
- 'props': {'source': 'prd_llm', 'prd_id': prd_id},
114
- })
115
- edges.append(_anchor_edge(fid, prd_id, repo_id))
116
-
117
- # 业务规则 → CONSTRAINT
118
- for r in extracted.get('rules', []):
119
- rule = r.get('rule', '').strip()
120
- if not rule:
121
- continue
122
- cid = 'CONST:%s:%s' % (prd_id[:8], rule[:20])
123
- entities.append({
124
- 'id': cid, 'repo_id': repo_id, 'type': 'CONSTRAINT',
125
- 'canonical': rule[:50], 'cn': r.get('type', ''),
126
- 'props': {'source': 'prd_llm', 'prd_id': prd_id, 'rule': rule},
127
- })
128
- edges.append(_anchor_edge(cid, prd_id, repo_id))
129
-
130
- # 权限 → PERMISSION
131
- for p in extracted.get('permissions', []):
132
- code = p.get('code', '').strip()
133
- if not code:
134
- continue
135
- pid = 'PERM:PRD:%s:%s' % (prd_id[:8], code[:20])
136
- entities.append({
137
- 'id': pid, 'repo_id': repo_id, 'type': 'PERMISSION',
138
- 'canonical': code, 'cn': p.get('desc', ''),
139
- 'props': {'source': 'prd_llm', 'prd_id': prd_id},
140
- })
141
- edges.append(_anchor_edge(pid, prd_id, repo_id))
142
-
143
- return entities, edges
144
-
145
-
146
- def _anchor_edge(from_id: str, prd_id: str, repo_id: str) -> dict:
147
- """实体 → PRD 的强关联边。"""
148
- return {
149
- 'from_id': from_id, 'to_id': 'prd:%s' % prd_id,
150
- 'edge_type': 'anchors', 'from_repo': repo_id, 'to_repo': repo_id,
151
- 'confidence': 1.0, 'source': 'explicit',
152
- 'props': {'via': 'prd_llm'},
153
- }