@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.
Files changed (253) hide show
  1. package/bin/cli.js +1435 -1107
  2. package/package.json +29 -29
  3. package/templates/qoder/agents/insight-planning.md +1 -1
  4. package/templates/qoder/agents/insight-research.md +28 -15
  5. package/templates/qoder/commands/optional/wl-insight.md +161 -161
  6. package/templates/qoder/commands/optional/wl-report.md +20 -39
  7. package/templates/qoder/commands/optional/wl-status.md +4 -4
  8. package/templates/qoder/commands/wl-code.md +2 -2
  9. package/templates/qoder/commands/wl-design.md +6 -6
  10. package/templates/qoder/commands/wl-init.md +3 -3
  11. package/templates/qoder/commands/wl-prd.md +26 -13
  12. package/templates/qoder/commands/wl-req.md +43 -0
  13. package/templates/qoder/commands/wl-search.md +78 -47
  14. package/templates/qoder/commands/wl-task.md +343 -40
  15. package/templates/qoder/commands/wl-test.md +43 -17
  16. package/templates/qoder/config.yaml +52 -14
  17. package/templates/qoder/hooks/post-tool-use.py +181 -0
  18. package/templates/qoder/hooks/session-start.py +94 -23
  19. package/templates/qoder/hooks/stop-eval.py +207 -0
  20. package/templates/qoder/hooks/user-prompt-submit.py +139 -0
  21. package/templates/qoder/rules/wl-pipeline.md +85 -49
  22. package/templates/qoder/scripts/README.md +102 -0
  23. package/templates/qoder/scripts/capability/__init__.py +26 -0
  24. package/templates/qoder/scripts/capability/__main__.py +72 -0
  25. package/templates/qoder/scripts/capability/adapters/__init__.py +29 -0
  26. package/templates/qoder/scripts/capability/adapters/cli.py +316 -0
  27. package/templates/qoder/scripts/capability/adapters/mcp.py +334 -0
  28. package/templates/qoder/scripts/capability/adapters/qw.py +271 -0
  29. package/templates/qoder/scripts/capability/caps/__init__.py +27 -0
  30. package/templates/qoder/scripts/capability/caps/context.py +36 -0
  31. package/templates/qoder/scripts/capability/caps/cron.py +50 -0
  32. package/templates/qoder/scripts/capability/caps/identity.py +43 -0
  33. package/templates/qoder/scripts/capability/caps/memory.py +71 -0
  34. package/templates/qoder/scripts/capability/caps/notify.py +41 -0
  35. package/templates/qoder/scripts/capability/caps/present.py +48 -0
  36. package/templates/qoder/scripts/capability/caps/repo.py +31 -0
  37. package/templates/qoder/scripts/capability/caps/sandbox.py +43 -0
  38. package/templates/qoder/scripts/capability/chain.py +92 -0
  39. package/templates/qoder/scripts/capability/memory_chain.py +41 -0
  40. package/templates/qoder/scripts/capability/registry.py +246 -0
  41. package/templates/qoder/scripts/capability/registry_mcp.py +250 -0
  42. package/templates/qoder/scripts/capability/smoke_test.py +211 -0
  43. package/templates/qoder/scripts/capability/smoke_test_report.json +94 -0
  44. package/templates/qoder/scripts/deployment/setup/__init__.py +11 -0
  45. package/templates/qoder/scripts/deployment/setup/carriers.py +669 -0
  46. package/templates/qoder/scripts/deployment/setup/carriers_verify.py +148 -0
  47. package/templates/qoder/scripts/{init_doctor.py → deployment/setup/init_doctor.py} +100 -39
  48. package/templates/qoder/scripts/{install_qoderwork.py → deployment/setup/install_qoderwork.py} +48 -30
  49. package/templates/qoder/scripts/{platform_doctor.py → deployment/setup/platform_doctor.py} +271 -259
  50. package/templates/qoder/scripts/{repo_root.py → deployment/setup/repo_root.py} +9 -2
  51. package/templates/qoder/scripts/{setup.py → deployment/setup/setup.py} +143 -14
  52. package/templates/qoder/scripts/{setup_lanhu.py → deployment/setup/setup_lanhu.py} +979 -963
  53. package/templates/qoder/scripts/domain/design/__init__.py +0 -0
  54. package/templates/qoder/scripts/{fill_prototype.py → domain/design/fill_prototype.py} +17 -5
  55. package/templates/qoder/scripts/{gen_design_doc.py → domain/design/gen_design_doc.py} +406 -394
  56. package/templates/qoder/scripts/domain/kg/__init__.py +11 -0
  57. package/templates/qoder/scripts/domain/kg/build/__init__.py +0 -0
  58. package/templates/qoder/scripts/domain/kg/build/build_entity_registry.py +201 -0
  59. package/templates/qoder/scripts/domain/kg/build/build_relations.py +132 -0
  60. package/templates/qoder/scripts/{build_style_index.py → domain/kg/build/build_style_index.py} +50 -15
  61. package/templates/qoder/scripts/domain/kg/build/build_workflows.py +149 -0
  62. package/templates/qoder/scripts/{kg_build.py → domain/kg/build/kg_build.py} +685 -612
  63. package/templates/qoder/scripts/{kg_build_db.py → domain/kg/build/kg_build_db.py} +338 -327
  64. package/templates/qoder/scripts/{kg_incremental.py → domain/kg/build/kg_incremental.py} +425 -393
  65. package/templates/qoder/scripts/{learn_aggregate.py → domain/kg/build/learn_aggregate.py} +212 -201
  66. package/templates/qoder/scripts/domain/kg/extract/__init__.py +0 -0
  67. package/templates/qoder/scripts/{common → domain/kg/extract}/extract.py +430 -419
  68. package/templates/qoder/scripts/domain/kg/extract/test_extract.py +115 -0
  69. package/templates/qoder/scripts/{common → domain/kg/extract}/ts_extract.py +614 -536
  70. package/templates/qoder/scripts/domain/kg/graph/__init__.py +0 -0
  71. package/templates/qoder/scripts/{kg_link_db.py → domain/kg/graph/kg_link_db.py} +235 -224
  72. package/templates/qoder/scripts/domain/kg/graph/kg_semantic.py +683 -0
  73. package/templates/qoder/scripts/{kg.py → domain/kg/kg.py} +871 -708
  74. package/templates/qoder/scripts/domain/kg/kg_capabilities.py +182 -0
  75. package/templates/qoder/scripts/domain/kg/search/__init__.py +0 -0
  76. package/templates/qoder/scripts/{context_pack.py → domain/kg/search/context_pack.py} +45 -17
  77. package/templates/qoder/scripts/{enrich_prompt.py → domain/kg/search/enrich_prompt.py} +238 -226
  78. package/templates/qoder/scripts/domain/kg/search/prefetch.py +384 -0
  79. package/templates/qoder/scripts/{common → domain/kg/search}/search_engine.py +207 -205
  80. package/templates/qoder/scripts/{search_index.py → domain/kg/search/search_index.py} +210 -88
  81. package/templates/qoder/scripts/domain/kg/server/__init__.py +0 -0
  82. package/templates/qoder/scripts/domain/kg/server/kgd.py +549 -0
  83. package/templates/qoder/scripts/domain/kg/server/perf_bench.py +197 -0
  84. package/templates/qoder/scripts/domain/kg/storage/__init__.py +0 -0
  85. package/templates/qoder/scripts/{kg_duckdb.py → domain/kg/storage/kg_duckdb.py} +70 -45
  86. package/templates/qoder/scripts/domain/learning/__init__.py +0 -0
  87. package/templates/qoder/scripts/{learn.py → domain/learning/learn.py} +157 -146
  88. package/templates/qoder/scripts/domain/report/__init__.py +11 -0
  89. package/templates/qoder/scripts/{add_session.py → domain/report/add_session.py} +256 -244
  90. package/templates/qoder/scripts/{export.py → domain/report/export.py} +72 -4
  91. package/templates/qoder/scripts/{report.py → domain/report/report.py} +292 -281
  92. package/templates/qoder/scripts/domain/report/report_snapshot.py +261 -0
  93. package/templates/qoder/scripts/domain/report/role.py +39 -0
  94. package/templates/qoder/scripts/{status.py → domain/report/status.py} +737 -628
  95. package/templates/qoder/scripts/domain/report/status_snapshot.py +191 -0
  96. package/templates/qoder/scripts/domain/requirement/__init__.py +0 -0
  97. package/templates/qoder/scripts/{archive_prd.py → domain/requirement/archive_prd.py} +389 -377
  98. package/templates/qoder/scripts/domain/requirement/req.py +228 -0
  99. package/templates/qoder/scripts/domain/task/__init__.py +11 -0
  100. package/templates/qoder/scripts/{git_sync.py → domain/task/git_sync.py} +90 -50
  101. package/templates/qoder/scripts/{syncgate.py → domain/task/syncgate.py} +18 -7
  102. package/templates/qoder/scripts/domain/task/task.py +229 -0
  103. package/templates/qoder/scripts/domain/task/task_lifecycle.py +606 -0
  104. package/templates/qoder/scripts/domain/task/task_query.py +162 -0
  105. package/templates/qoder/scripts/domain/task/task_relations.py +425 -0
  106. package/templates/qoder/scripts/{team_sync.py → domain/task/team_sync.py} +101 -23
  107. package/templates/qoder/scripts/domain/task/zentao_sync.py +370 -0
  108. package/templates/qoder/scripts/foundation/__init__.py +0 -0
  109. package/templates/qoder/scripts/foundation/bootstrap.py +145 -0
  110. package/templates/qoder/scripts/foundation/core/__init__.py +0 -0
  111. package/templates/qoder/scripts/foundation/core/cmd_registry.py +112 -0
  112. package/templates/qoder/scripts/foundation/core/config.py +187 -0
  113. package/templates/qoder/scripts/{common → foundation/core}/paths.py +706 -400
  114. package/templates/qoder/scripts/foundation/data/__init__.py +0 -0
  115. package/templates/qoder/scripts/foundation/data/contract.py +317 -0
  116. package/templates/qoder/scripts/{common → foundation/data}/events.py +2 -2
  117. package/templates/qoder/scripts/foundation/data/result.py +223 -0
  118. package/templates/qoder/scripts/{common → foundation/data}/task_utils.py +427 -392
  119. package/templates/qoder/scripts/foundation/identity/__init__.py +0 -0
  120. package/templates/qoder/scripts/foundation/identity/check_publish.py +103 -0
  121. package/templates/qoder/scripts/{common → foundation/identity}/developer.py +239 -238
  122. package/templates/qoder/scripts/foundation/identity/guard.py +159 -0
  123. package/templates/qoder/scripts/{common → foundation/identity}/identity.py +132 -9
  124. package/templates/qoder/scripts/foundation/identity/roles.py +60 -0
  125. package/templates/qoder/scripts/foundation/integrations/__init__.py +0 -0
  126. package/templates/qoder/scripts/{common → foundation/integrations}/active_task.py +30 -15
  127. package/templates/qoder/scripts/{common → foundation/integrations}/feishu.py +11 -10
  128. package/templates/qoder/scripts/{common → foundation/integrations}/terms.py +3 -3
  129. package/templates/qoder/scripts/foundation/integrations/zentao_client.py +220 -0
  130. package/templates/qoder/scripts/foundation/io/__init__.py +0 -0
  131. package/templates/qoder/scripts/{common → foundation/io}/filelock.py +1 -1
  132. package/templates/qoder/scripts/{common → foundation/io}/reqid.py +1 -1
  133. package/templates/qoder/scripts/foundation/protocol/__init__.py +0 -0
  134. package/templates/qoder/scripts/foundation/protocol/duckdb_conn.py +39 -0
  135. package/templates/qoder/scripts/foundation/protocol/mcp_base.py +268 -0
  136. package/templates/qoder/scripts/foundation/utils/__init__.py +0 -0
  137. package/templates/qoder/scripts/{common → foundation/utils}/eval_api.py +12 -1
  138. package/templates/qoder/scripts/{common → foundation/utils}/pip_install.py +23 -6
  139. package/templates/qoder/scripts/{common → foundation/utils}/platform_guard.py +1 -1
  140. package/templates/qoder/scripts/orchestration/__init__.py +0 -0
  141. package/templates/qoder/scripts/orchestration/wlkj.py +185 -0
  142. package/templates/qoder/scripts/protocol/__init__.py +0 -0
  143. package/templates/qoder/scripts/protocol/browser/README.md +207 -0
  144. package/templates/qoder/scripts/protocol/browser/SKILL.md +265 -0
  145. package/templates/qoder/scripts/protocol/browser/config.env +9 -0
  146. package/templates/qoder/scripts/protocol/browser/references/cdp-api.md +110 -0
  147. package/templates/qoder/scripts/protocol/browser/references/migration-2.5.3.md +72 -0
  148. package/templates/qoder/scripts/protocol/browser/references/site-patterns/.gitkeep +0 -0
  149. package/templates/qoder/scripts/protocol/browser/scripts/browser-discovery.mjs +138 -0
  150. package/templates/qoder/scripts/protocol/browser/scripts/cdp-proxy.mjs +672 -0
  151. package/templates/qoder/scripts/protocol/browser/scripts/check-deps.mjs +206 -0
  152. package/templates/qoder/scripts/protocol/browser/scripts/find-url.mjs +253 -0
  153. package/templates/qoder/scripts/protocol/browser/scripts/match-site.mjs +46 -0
  154. package/templates/qoder/scripts/protocol/browser/templates/config.env.template +9 -0
  155. package/templates/qoder/scripts/protocol/mcp/__init__.py +11 -0
  156. package/templates/qoder/scripts/protocol/mcp/kg_mcp_server.py +478 -0
  157. package/templates/qoder/scripts/{lanhu_stdio_wrapper.py → protocol/mcp/lanhu_stdio_wrapper.py} +131 -119
  158. package/templates/qoder/scripts/protocol/mcp/mcp_doctor.py +548 -0
  159. package/templates/qoder/scripts/protocol/mcp/mcp_launcher.py +600 -0
  160. package/templates/qoder/scripts/protocol/mcp/mysql_mcp_server.py +461 -0
  161. package/templates/qoder/scripts/protocol/mcp/zentao_mcp_server.py +1920 -0
  162. package/templates/qoder/scripts/protocol/transports/__init__.py +56 -0
  163. package/templates/qoder/scripts/protocol/transports/base.py +87 -0
  164. package/templates/qoder/scripts/protocol/transports/cli.py +132 -0
  165. package/templates/qoder/scripts/protocol/transports/http.py +141 -0
  166. package/templates/qoder/scripts/protocol/transports/stdio.py +293 -0
  167. package/templates/qoder/scripts/run_weekly_update.bat +27 -18
  168. package/templates/qoder/scripts/run_weekly_update.sh +22 -13
  169. package/templates/qoder/scripts/validation/__init__.py +0 -0
  170. package/templates/qoder/scripts/validation/eval/__init__.py +0 -0
  171. package/templates/qoder/scripts/validation/eval/perf-report-2026-06-24.md +139 -0
  172. package/templates/qoder/scripts/validation/eval/qwork_harness.py +617 -0
  173. package/templates/qoder/scripts/validation/eval/report-commands.md +220 -0
  174. package/templates/qoder/scripts/validation/eval/transcript_timing.py +386 -0
  175. package/templates/qoder/scripts/validation/metrics/__init__.py +0 -0
  176. package/templates/qoder/scripts/{eval_prd.py → validation/metrics/eval_prd.py} +84 -15
  177. package/templates/qoder/scripts/validation/metrics/usability_score.py +750 -0
  178. package/templates/qoder/scripts/validation/test/__init__.py +11 -0
  179. package/templates/qoder/scripts/validation/test/assertion_gen.py +551 -0
  180. package/templates/qoder/scripts/{autotest.py → validation/test/autotest.py} +1234 -1751
  181. package/templates/qoder/scripts/validation/test/autotest_auth.py +109 -0
  182. package/templates/qoder/scripts/{autotest_batch.py → validation/test/autotest_batch.py} +255 -224
  183. package/templates/qoder/scripts/validation/test/autotest_data.py +680 -0
  184. package/templates/qoder/scripts/validation/test/autotest_opencli.py +646 -0
  185. package/templates/qoder/scripts/{autotest_run.py → validation/test/autotest_run.py} +323 -297
  186. package/templates/qoder/scripts/validation/test/autotest_webaccess.py +571 -0
  187. package/templates/qoder/scripts/{benchmark.py → validation/test/benchmark.py} +17 -5
  188. package/templates/qoder/scripts/{kg_auto_login.py → validation/test/kg_auto_login.py} +208 -196
  189. package/templates/qoder/scripts/{kg_test_runner.py → validation/test/kg_test_runner.py} +13 -2
  190. package/templates/qoder/scripts/{page_probe.py → validation/test/page_probe.py} +470 -459
  191. package/templates/qoder/scripts/validation/test/smoke_all_commands.py +170 -0
  192. package/templates/qoder/settings.json +23 -2
  193. package/templates/qoder/skills/design-import/SKILL.md +235 -226
  194. package/templates/qoder/skills/design-review/SKILL.md +91 -82
  195. package/templates/qoder/skills/prd-generator/SKILL.md +38 -24
  196. package/templates/qoder/skills/prd-review/SKILL.md +8 -6
  197. package/templates/qoder/skills/prototype-generator/SKILL.md +296 -247
  198. package/templates/qoder/skills/spec-coder/SKILL.md +24 -15
  199. package/templates/qoder/skills/spec-generator/SKILL.md +24 -12
  200. package/templates/qoder/skills/test-generator/SKILL.md +9 -7
  201. package/templates/qoder/skills/wl-code/SKILL.md +25 -13
  202. package/templates/qoder/skills/wl-commit/SKILL.md +10 -9
  203. package/templates/qoder/skills/wl-design/SKILL.md +7 -5
  204. package/templates/qoder/skills/wl-init/SKILL.md +8 -8
  205. package/templates/qoder/skills/wl-insight/SKILL.md +25 -12
  206. package/templates/qoder/skills/wl-prd-full/SKILL.md +59 -8
  207. package/templates/qoder/skills/wl-prd-quick/SKILL.md +7 -7
  208. package/templates/qoder/skills/wl-prd-review/SKILL.md +18 -6
  209. package/templates/qoder/skills/wl-report/SKILL.md +27 -25
  210. package/templates/qoder/skills/wl-search/SKILL.md +151 -80
  211. package/templates/qoder/skills/wl-spec/SKILL.md +9 -7
  212. package/templates/qoder/skills/wl-status/SKILL.md +50 -18
  213. package/templates/qoder/skills/wl-task/SKILL.md +94 -12
  214. package/templates/qoder/skills/wl-test/SKILL.md +154 -45
  215. package/templates/qoder/templates/prd-full-template.md +7 -0
  216. package/templates/root/AGENTS.md +275 -237
  217. package/templates/root/requirements.txt +3 -0
  218. package/templates/root//344/275/277/347/224/250/350/257/264/346/230/216.md +3 -3
  219. package/templates/root//346/226/260/346/211/213/346/214/207/345/215/227.md +2 -2
  220. package/templates/qoder/hooks/inject-workflow-state.py +0 -169
  221. package/templates/qoder/scripts/__pycache__/check_mcp_launch.cpython-39.pyc +0 -0
  222. package/templates/qoder/scripts/__pycache__/install_qoderwork.cpython-39.pyc +0 -0
  223. package/templates/qoder/scripts/__pycache__/mcp_launcher.cpython-39.pyc +0 -0
  224. package/templates/qoder/scripts/__pycache__/platform_doctor.cpython-39.pyc +0 -0
  225. package/templates/qoder/scripts/check_carriers.py +0 -238
  226. package/templates/qoder/scripts/check_mcp.py +0 -298
  227. package/templates/qoder/scripts/check_mcp_launch.py +0 -183
  228. package/templates/qoder/scripts/check_qoderwork_consistency.py +0 -166
  229. package/templates/qoder/scripts/collect_prds.py +0 -31
  230. package/templates/qoder/scripts/common/mentions.py +0 -134
  231. package/templates/qoder/scripts/common/utf8.py +0 -38
  232. package/templates/qoder/scripts/extract_api_params.py +0 -246
  233. package/templates/qoder/scripts/extract_routes.py +0 -54
  234. package/templates/qoder/scripts/extract_routes_tree.py +0 -78
  235. package/templates/qoder/scripts/handoff.py +0 -22
  236. package/templates/qoder/scripts/init_developer.py +0 -76
  237. package/templates/qoder/scripts/kg_mcp_server.py +0 -801
  238. package/templates/qoder/scripts/kg_semantic.py +0 -150
  239. package/templates/qoder/scripts/mcp_launcher.py +0 -414
  240. package/templates/qoder/scripts/mysql_mcp_server.py +0 -396
  241. package/templates/qoder/scripts/parse_prds.py +0 -33
  242. package/templates/qoder/scripts/role.py +0 -51
  243. package/templates/qoder/scripts/sync_carriers.py +0 -259
  244. package/templates/qoder/scripts/task.py +0 -1261
  245. package/templates/qoder/scripts/workspace_init.py +0 -102
  246. package/templates/qoder/scripts/zentao_mcp_server.py +0 -424
  247. package/templates/qoder/skills/prompt-enrich/SKILL.md +0 -90
  248. /package/templates/qoder/scripts/{common → deployment}/__init__.py +0 -0
  249. /package/templates/qoder/{skills/prototype-generator/SKILL.md.zcode-79180-2af4721f-f9a6-412c-88db-c0af680d211b.tmp → scripts/domain/__init__.py} +0 -0
  250. /package/templates/qoder/scripts/{common → domain/kg/graph}/graph_traverse.py +0 -0
  251. /package/templates/qoder/scripts/{common → domain/kg/server}/repowiki.py +0 -0
  252. /package/templates/qoder/scripts/{common → foundation/io}/atomicio.py +0 -0
  253. /package/templates/qoder/scripts/{secure-ls.js → validation/test/secure-ls.js} +0 -0
@@ -0,0 +1,197 @@
1
+ # -*- coding: utf-8 -*-
2
+ # v3.0 路径自举: 引导到 common/bootstrap, 统一 sys.path 逻辑
3
+ import os as _o, sys as _s
4
+ _f = _o.path.abspath(__file__)
5
+ for _ in range(10):
6
+ _f = _o.path.dirname(_f)
7
+ _cp = _o.path.join(_f, 'foundation')
8
+ if _o.path.isfile(_o.path.join(_cp, 'bootstrap.py')):
9
+ break
10
+ if _cp not in _s.path: _s.path.insert(0, _cp)
11
+ from bootstrap import setup; setup()
12
+ from foundation.core.paths import get_repo_root
13
+
14
+ """
15
+ perf_bench.py — 知识图谱关键路径性能基准
16
+
17
+ 诊断"用了工作流反而更慢"的根因, 并验证常驻化(daemon)加速层的效果。
18
+
19
+ 两种测量模式:
20
+ 1. 冷启动 (subprocess 跑 kg.py) — 模拟真实用户体验: 起进程→import→连库→跑
21
+ 2. daemon 热路径 (kgd_client) — daemon 常驻时的 IPC 调用, 模型/连接已就绪
22
+
23
+ 用法:
24
+ python perf_bench.py # 默认: 测冷启动 (--no-daemon)
25
+ python perf_bench.py --with-daemon # 同时测 daemon 热路径 (需先 kgd.py start)
26
+ python perf_bench.py --quick # 跳过 semantic (26s), 只测轻量路径
27
+ """
28
+ import io
29
+ import os
30
+ import subprocess
31
+ import sys
32
+ import time
33
+
34
+ # UTF-8 输出 (Windows 终端默认 GBK 会乱码)
35
+ try:
36
+ sys.stdout.reconfigure(encoding='utf-8', errors='replace')
37
+ except Exception:
38
+ pass
39
+
40
+ KG_PY = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'kg.py')
41
+ REPO_ROOT = get_repo_root()
42
+
43
+ # ── 测试用例 (覆盖四层) ──────────────────────────────────────────────
44
+ # (名称, kg子命令, 参数列表, 预期层)
45
+ CASES = [
46
+ ('search-考勤(web)', 'search', ['考勤', '--platform', 'web'], '结构层'),
47
+ ('search-车辆(web)', 'search', ['车辆', '--platform', 'web'], '结构层'),
48
+ ('search-报销(app)', 'search', ['报销', '--platform', 'app'], '结构层'),
49
+ ('api-asset', 'api', ['asset'], '结构层'),
50
+ ('context_pack-车辆', 'context', ['车辆', '--platform', 'web'], '①③+设计'),
51
+ ('impact-/asset', 'impact', ['/asset'], '图谱层'),
52
+ ('feature-资产管理', 'feature', ['资产管理'], '图谱层'),
53
+ ('semantic-报销', 'semantic', ['报销'], '向量层'), # 26s 慢王
54
+ ]
55
+
56
+
57
+ def _run_kg(args, use_daemon=True, timeout=120):
58
+ """跑一次 kg.py 子命令, 返回 (耗时秒, 返回码)。
59
+
60
+ use_daemon=True : 走默认 (kg.py 内部会优先 daemon)
61
+ use_daemon=False : 加 --no-daemon 强制冷路径
62
+ """
63
+ cmd = [sys.executable, KG_PY] + args
64
+ if not use_daemon:
65
+ cmd.append('--no-daemon')
66
+ env = dict(os.environ)
67
+ env['PYTHONIOENCODING'] = 'utf-8'
68
+ t0 = time.time()
69
+ try:
70
+ # stdout 重定向到 DEVNULL, 只测耗时, 不看内容
71
+ r = subprocess.run(cmd, stdout=subprocess.DEVNULL,
72
+ stderr=subprocess.DEVNULL, cwd=REPO_ROOT,
73
+ env=env, timeout=timeout)
74
+ return time.time() - t0, r.returncode
75
+ except subprocess.TimeoutExpired:
76
+ return float(timeout), -1
77
+
78
+
79
+ def _median(samples):
80
+ """取中位数 (抗抖动)。"""
81
+ s = sorted(samples)
82
+ n = len(s)
83
+ return s[n // 2] if n % 2 else (s[n // 2 - 1] + s[n // 2]) / 2.0
84
+
85
+
86
+ def bench_case(name, cmd, args, cold=True, daemon=False, repeats=3):
87
+ """跑单个用例, 返回 (中位数耗时, 状态)。
88
+
89
+ cold=True : 测冷启动 (--no-daemon)
90
+ daemon=True: 测 daemon 热路径 (不加 --no-daemon, 但需 daemon 在线)
91
+ """
92
+ if cold and daemon:
93
+ raise ValueError('cold 和 daemon 互斥')
94
+ # 重量级命令 (semantic 冷启动 ~27s) 只跑 1 次, 避免基准脚本本身过慢
95
+ if cmd == 'semantic' and cold:
96
+ repeats = 1
97
+ use_daemon = daemon
98
+ samples = []
99
+ status = 'ok'
100
+ for _ in range(repeats):
101
+ elapsed, rc = _run_kg([cmd] + args, use_daemon=use_daemon)
102
+ samples.append(elapsed)
103
+ if rc != 0:
104
+ status = 'rc=%d' % rc
105
+ return _median(samples), status
106
+
107
+
108
+ def daemon_online():
109
+ """探测 daemon 是否在线 (通过 kg.py status, 1s 超时)。"""
110
+ try:
111
+ from kgd import kgd_client
112
+ return kgd_client.ping(timeout=1.0)
113
+ except Exception:
114
+ return False
115
+
116
+
117
+ def fmt_speedup(cold_s, hot_s):
118
+ """格式化加速比。"""
119
+ if hot_s <= 0.001:
120
+ return '-'
121
+ return '%.0fx' % (cold_s / hot_s)
122
+
123
+
124
+ def main():
125
+ args = sys.argv[1:]
126
+ quick = '--quick' in args
127
+ want_daemon = '--with-daemon' in args
128
+
129
+ print('=' * 72)
130
+ print('知识图谱性能基准 (repo: %s)' % os.path.basename(REPO_ROOT))
131
+ print('=' * 72)
132
+
133
+ if quick:
134
+ print('[quick 模式] 跳过 semantic (向量层 26s), 只测轻量路径\n')
135
+ cases = [c for c in CASES if c[1] != 'semantic']
136
+ else:
137
+ cases = CASES
138
+
139
+ cold_results = [] # [(name, secs, status, layer)]
140
+ hot_results = {} # name -> (secs, status)
141
+
142
+ # ── 1. 冷启动基线 (真实用户体验: 每次起一个进程) ──
143
+ print('[1/2] 冷启动基线 (--no-daemon, 模拟现状) ...')
144
+ for name, cmd, cargs, layer in cases:
145
+ secs, status = bench_case(name, cmd, cargs, cold=True)
146
+ cold_results.append((name, secs, status, layer))
147
+ marker = ' ' if status == 'ok' else '! '
148
+ print(' %s%-22s %6.2fs [%s] %s' % (marker, name, secs, layer, status))
149
+ print()
150
+
151
+ # ── 2. daemon 热路径 (常驻加速层) ──
152
+ if want_daemon:
153
+ online = daemon_online()
154
+ if not online:
155
+ print('[2/2] daemon 未在线 — 跳过热路径测试。')
156
+ print(' (先启动: python .qoder/scripts/domain/kg/server/kgd.py start)')
157
+ else:
158
+ print('[2/2] daemon 在线, 测热路径 ...')
159
+ for name, cmd, cargs, layer in cases:
160
+ secs, status = bench_case(name, cmd, cargs, cold=False, daemon=True)
161
+ hot_results[name] = (secs, status)
162
+ marker = ' ' if status == 'ok' else '! '
163
+ print(' %s%-22s %6.2fs [%s] %s' % (marker, name, secs, layer, status))
164
+ print()
165
+
166
+ # ── 汇总对比表 ──
167
+ print('-' * 72)
168
+ print('%-22s %10s %10s %10s' % ('用例', '冷启动', 'daemon', '加速'))
169
+ print('%-22s %10s %10s %10s' % ('─' * 18, '─' * 8, '─' * 8, '─' * 6))
170
+ for name, cold_s, status, layer in cold_results:
171
+ hot_s = hot_results.get(name, (None, None))[0]
172
+ hot_str = '%.2fs' % hot_s if hot_s else '-'
173
+ speed = fmt_speedup(cold_s, hot_s) if hot_s else '-'
174
+ print('%-22s %8.2fs %10s %10s' % (name, cold_s, hot_str, speed))
175
+ print('-' * 72)
176
+
177
+ # ── 诊断结论 ──
178
+ print('\n诊断:')
179
+ semantic_s = next((s for n, s, _, _ in cold_results if 'semantic' in n), None)
180
+ if semantic_s and semantic_s > 10:
181
+ print(' 🔴 semantic %.1fs — 模型冷加载占 99%%, 是头号慢源' % semantic_s)
182
+ print(' 解法: kgd.py start 常驻模型 → <0.5s')
183
+ elif semantic_s:
184
+ print(' 🟢 semantic %.1fs — 在可接受范围' % semantic_s)
185
+
186
+ total_cold = sum(s for _, s, _, _ in cold_results)
187
+ print(' 冷启动累积: %.1fs (一次 PRD 流程的隐性等待)' % total_cold)
188
+ if hot_results:
189
+ total_hot = sum(hot_results[n][0] for n in hot_results)
190
+ print(' daemon 累积: %.1fs → 整体提速 %.0fx' %
191
+ (total_hot, total_cold / max(total_hot, 0.01)))
192
+
193
+ return 0
194
+
195
+
196
+ if __name__ == '__main__':
197
+ sys.exit(main())
@@ -1,4 +1,15 @@
1
1
  # -*- coding: utf-8 -*-
2
+ # v3.0 路径自举: 引导到 common/bootstrap, 统一 sys.path 逻辑
3
+ import os as _o, sys as _s
4
+ _f = _o.path.abspath(__file__)
5
+ for _ in range(10):
6
+ _f = _o.path.dirname(_f)
7
+ _cp = _o.path.join(_f, 'foundation')
8
+ if _o.path.isfile(_o.path.join(_cp, 'bootstrap.py')):
9
+ break
10
+ if _cp not in _s.path: _s.path.insert(0, _cp)
11
+ from bootstrap import setup; setup()
12
+
2
13
  """
3
14
  DuckDB knowledge graph store — replaces the JSON flat files with a single
4
15
  embedded database for fast incremental updates.
@@ -21,17 +32,42 @@ import sys
21
32
 
22
33
  import duckdb
23
34
 
24
- SCRIPTS = os.path.join(os.path.dirname(os.path.abspath(__file__)), '.qoder', 'scripts')
25
- sys.path.insert(0, SCRIPTS)
26
- from common.paths import DATA_INDEX_DIR
27
-
28
- DB_PATH = str(DATA_INDEX_DIR / 'kg.duckdb')
35
+ # 深度 4: scripts/ → domain/ → kg/ → storage/ → kg_duckdb.py
36
+ SCRIPTS = os.path.dirname(os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))) # storage→kg→domain→scripts/根
37
+ if SCRIPTS not in sys.path:
38
+ sys.path.insert(0, SCRIPTS)
39
+ from foundation.core.paths import DATA_INDEX_DIR
40
+ from foundation.protocol.duckdb_conn import get_db, get_db_readonly, DB_PATH # L0 基础设施
29
41
 
30
42
 
31
- def get_db():
32
- """Open DuckDB connection (creates file if not exists)."""
33
- os.makedirs(os.path.dirname(DB_PATH), exist_ok=True)
34
- return duckdb.connect(DB_PATH)
43
+ def _load_index_json(name):
44
+ """统一加载索引 JSON。build_style_index 把产物写到 _build_cache/,
45
+ 旧布局/手动放的可能在 INDEX 直接目录。两个位置都查, 优先 _build_cache。
46
+ 所有 import_*_layer 共用这个, 避免各写各的 load() 导致路径不一致。"""
47
+ for d in (str(DATA_INDEX_DIR / '_build_cache'), str(DATA_INDEX_DIR)):
48
+ p = os.path.join(d, name)
49
+ if os.path.isfile(p):
50
+ try:
51
+ return json.load(open(p, encoding='utf-8'))
52
+ except (json.JSONDecodeError, OSError):
53
+ pass
54
+ return {}
55
+
56
+
57
+ def get_db_readonly():
58
+ """Open read-only DuckDB connection. Returns None if file missing.
59
+
60
+ 语义检索 (semantic_search / semantic_search_files) 只读向量数据,
61
+ 但 search_index.py 已用 read_only=True 打开了同一个 kg.duckdb。
62
+ DuckDB 不允许对同一文件混用 read_only 和 read_write 连接, 所以
63
+ 只读路径必须全程用 read_only, 避免连接冲突。文件不存在则返回 None。
64
+ """
65
+ if not os.path.isfile(DB_PATH):
66
+ return None
67
+ try:
68
+ return duckdb.connect(DB_PATH, read_only=True)
69
+ except Exception:
70
+ return None
35
71
 
36
72
 
37
73
  def init_schema(con):
@@ -115,7 +151,7 @@ def init_schema(con):
115
151
  # 自动补列: events.py 注册的新统计列, 表里没有就加 (ALTER ... ADD COLUMN IF NOT EXISTS)
116
152
  # 这样 events.py 加埋点不用手动改表结构。
117
153
  try:
118
- from common.events import event_to_sql_filter
154
+ from foundation.data.events import event_to_sql_filter
119
155
  existing = {r[0] for r in con.execute(
120
156
  "SELECT column_name FROM information_schema.columns "
121
157
  "WHERE table_name = 'learning_stats'"
@@ -128,6 +164,19 @@ def init_schema(con):
128
164
  )
129
165
  except Exception:
130
166
  pass # events.py 不可用 (循环导入等) = 退化为固定 5 列, 不影响
167
+
168
+ # 语义检索向量表 (v3.1: bge-small-zh embedding)
169
+ # entity_id 关联 entities.id; text 是被 embed 的文本 (canonical + cn);
170
+ # vec 存 FLOAT 数组 (DuckDB 用 list 类型, 运行时 cast)。
171
+ con.execute("""
172
+ CREATE TABLE IF NOT EXISTS embeddings (
173
+ entity_id TEXT NOT NULL,
174
+ text TEXT NOT NULL,
175
+ vec FLOAT[],
176
+ model TEXT DEFAULT 'bge-small-zh-v1.5'
177
+ )
178
+ """)
179
+
131
180
  # Indexes
132
181
  con.execute("CREATE INDEX IF NOT EXISTS idx_ent_type ON entities(type)")
133
182
  con.execute("CREATE INDEX IF NOT EXISTS idx_alias ON aliases(alias)")
@@ -245,20 +294,12 @@ def multi_hop(con, start_id, edge_types=None, max_depth=5):
245
294
 
246
295
  def bulk_import_from_json(con):
247
296
  """One-time full import from existing JSON indexes into DuckDB."""
248
- INDEX = str(DATA_INDEX_DIR / '_build_cache')
249
-
250
- def load(name):
251
- p = os.path.join(INDEX, name)
252
- if os.path.isfile(p):
253
- return json.load(open(p, encoding='utf-8'))
254
- return {}
255
-
256
297
  con.execute("DELETE FROM entities")
257
298
  con.execute("DELETE FROM aliases")
258
299
  con.execute("DELETE FROM edges")
259
300
 
260
- er = load('entity-registry.json')
261
- rel = load('relations.json')
301
+ er = _load_index_json('entity-registry.json')
302
+ rel = _load_index_json('relations.json')
262
303
  edges_data = rel.get('edges', {})
263
304
 
264
305
  entity_rows = []
@@ -330,27 +371,22 @@ def bulk_import_from_json(con):
330
371
 
331
372
  def import_search_layer(con):
332
373
  """导入搜索层: keywords/endpoints/prds/wiki → DuckDB 表。"""
333
- INDEX = str(DATA_INDEX_DIR)
334
- def load(name):
335
- p = os.path.join(INDEX, name)
336
- return json.load(open(p, encoding='utf-8')) if os.path.isfile(p) else {}
337
-
338
374
  # keywords (code-keyword.json: {keyword: [files]})
339
- kw = load('code-keyword.json')
375
+ kw = _load_index_json('code-keyword.json')
340
376
  kw_rows = [(k, f) for k, files in kw.items() for f in files]
341
377
  con.execute("DELETE FROM keywords")
342
378
  if kw_rows:
343
379
  _batch_insert(con, 'keywords', kw_rows)
344
380
 
345
381
  # endpoints (code-api.json: {endpoint: controller_file})
346
- api = load('code-api.json')
382
+ api = _load_index_json('code-api.json')
347
383
  con.execute("DELETE FROM endpoints")
348
384
  ep_rows = [(ep, f) for ep, f in api.items()]
349
385
  if ep_rows:
350
386
  _batch_insert(con, 'endpoints', ep_rows, ignore=True)
351
387
 
352
388
  # prds (prd-index.json)
353
- prds = load('prd-index.json')
389
+ prds = _load_index_json('prd-index.json')
354
390
  con.execute("DELETE FROM prds")
355
391
  prd_rows = [(f, info.get('title', ''),
356
392
  json.dumps(info.get('keywords', []), ensure_ascii=False),
@@ -361,7 +397,7 @@ def import_search_layer(con):
361
397
  _batch_insert(con, 'prds', prd_rows, ignore=True)
362
398
 
363
399
  # wiki (wiki-index.json)
364
- wiki = load('wiki-index.json')
400
+ wiki = _load_index_json('wiki-index.json')
365
401
  con.execute("DELETE FROM wiki")
366
402
  wiki_rows = []
367
403
  for kw, entries in (wiki.get('index', {}) if isinstance(wiki, dict) else {}).items():
@@ -376,12 +412,7 @@ def import_search_layer(con):
376
412
 
377
413
  def import_ui_layer(con):
378
414
  """导入UI层: pages/fields/modules → DuckDB 表。"""
379
- INDEX = str(DATA_INDEX_DIR)
380
- def load(name):
381
- p = os.path.join(INDEX, name)
382
- return json.load(open(p, encoding='utf-8')) if os.path.isfile(p) else {}
383
-
384
- si = load('ui-style.json')
415
+ si = _load_index_json('ui-style.json')
385
416
 
386
417
  # pages (from ui-style projects.page_examples + page_types)
387
418
  con.execute("DELETE FROM pages")
@@ -408,7 +439,7 @@ def import_ui_layer(con):
408
439
  _batch_insert(con, 'fields', field_rows)
409
440
 
410
441
  # modules (from ui-modules.json)
411
- mi = load('ui-modules.json')
442
+ mi = _load_index_json('ui-modules.json')
412
443
  con.execute("DELETE FROM modules")
413
444
  mod_rows = [(m['dir'], m['cn'], proj, m['path'])
414
445
  for proj, mods in mi.get('projects', {}).items() for m in mods]
@@ -434,9 +465,7 @@ def import_ui_layer(con):
434
465
 
435
466
  def import_trace_layer(con):
436
467
  """导入trace层: traces/api_functions → DuckDB 表。"""
437
- INDEX = str(DATA_INDEX_DIR)
438
- tc = json.load(open(os.path.join(INDEX, 'trace-chain.json'), encoding='utf-8')) \
439
- if os.path.isfile(os.path.join(INDEX, 'trace-chain.json')) else {}
468
+ tc = _load_index_json('trace-chain.json')
440
469
 
441
470
  con.execute("DELETE FROM traces")
442
471
  trace_rows = [(
@@ -458,9 +487,7 @@ def import_trace_layer(con):
458
487
 
459
488
  def import_test_layer(con):
460
489
  """导入test层: tests → DuckDB 表。"""
461
- INDEX = str(DATA_INDEX_DIR)
462
- ta = json.load(open(os.path.join(INDEX, 'test-assertions.json'), encoding='utf-8')) \
463
- if os.path.isfile(os.path.join(INDEX, 'test-assertions.json')) else {}
490
+ ta = _load_index_json('test-assertions.json')
464
491
 
465
492
  con.execute("DELETE FROM tests")
466
493
  test_rows = [(
@@ -478,9 +505,7 @@ def import_test_layer(con):
478
505
 
479
506
  def import_workflow_layer(con):
480
507
  """导入workflow层 → DuckDB 表。"""
481
- INDEX = str(DATA_INDEX_DIR)
482
- wf = json.load(open(os.path.join(INDEX, 'workflow-index.json'), encoding='utf-8')) \
483
- if os.path.isfile(os.path.join(INDEX, 'workflow-index.json')) else {}
508
+ wf = _load_index_json('workflow-index.json')
484
509
 
485
510
  con.execute("DELETE FROM workflows")
486
511
  wf_rows = []