@hupan56/wlkj 2.7.12 → 3.0.0

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