@hupan56/wlkj 3.0.0 → 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 (237) hide show
  1. package/bin/cli.js +1435 -1373
  2. package/package.json +1 -1
  3. package/templates/qoder/commands/optional/wl-insight.md +6 -4
  4. package/templates/qoder/commands/optional/wl-report.md +20 -39
  5. package/templates/qoder/commands/optional/wl-status.md +4 -4
  6. package/templates/qoder/commands/wl-code.md +2 -2
  7. package/templates/qoder/commands/wl-design.md +5 -5
  8. package/templates/qoder/commands/wl-init.md +3 -3
  9. package/templates/qoder/commands/wl-prd.md +26 -13
  10. package/templates/qoder/commands/wl-req.md +3 -3
  11. package/templates/qoder/commands/wl-search.md +78 -47
  12. package/templates/qoder/commands/wl-task.md +343 -40
  13. package/templates/qoder/commands/wl-test.md +10 -10
  14. package/templates/qoder/config.yaml +35 -13
  15. package/templates/qoder/hooks/post-tool-use.py +181 -0
  16. package/templates/qoder/hooks/session-start.py +82 -1
  17. package/templates/qoder/hooks/stop-eval.py +207 -0
  18. package/templates/qoder/hooks/user-prompt-submit.py +139 -0
  19. package/templates/qoder/rules/wl-pipeline.md +70 -8
  20. package/templates/qoder/scripts/README.md +78 -115
  21. package/templates/qoder/scripts/capability/__init__.py +26 -0
  22. package/templates/qoder/scripts/capability/__main__.py +72 -0
  23. package/templates/qoder/scripts/capability/adapters/__init__.py +29 -0
  24. package/templates/qoder/scripts/capability/adapters/cli.py +316 -0
  25. package/templates/qoder/scripts/capability/adapters/mcp.py +334 -0
  26. package/templates/qoder/scripts/capability/adapters/qw.py +271 -0
  27. package/templates/qoder/scripts/capability/caps/__init__.py +27 -0
  28. package/templates/qoder/scripts/capability/caps/context.py +36 -0
  29. package/templates/qoder/scripts/capability/caps/cron.py +50 -0
  30. package/templates/qoder/scripts/capability/caps/identity.py +43 -0
  31. package/templates/qoder/scripts/capability/caps/memory.py +71 -0
  32. package/templates/qoder/scripts/capability/caps/notify.py +41 -0
  33. package/templates/qoder/scripts/capability/caps/present.py +48 -0
  34. package/templates/qoder/scripts/capability/caps/repo.py +31 -0
  35. package/templates/qoder/scripts/capability/caps/sandbox.py +43 -0
  36. package/templates/qoder/scripts/capability/chain.py +92 -0
  37. package/templates/qoder/scripts/capability/memory_chain.py +41 -0
  38. package/templates/qoder/scripts/capability/registry.py +246 -0
  39. package/templates/qoder/scripts/capability/registry_mcp.py +250 -0
  40. package/templates/qoder/scripts/capability/smoke_test.py +211 -0
  41. package/templates/qoder/scripts/capability/smoke_test_report.json +94 -0
  42. package/templates/qoder/scripts/{report → deployment/setup}/__init__.py +11 -11
  43. package/templates/qoder/scripts/{setup → deployment/setup}/carriers.py +33 -26
  44. package/templates/qoder/scripts/deployment/setup/carriers_verify.py +148 -0
  45. package/templates/qoder/scripts/{setup → deployment/setup}/init_doctor.py +40 -16
  46. package/templates/qoder/scripts/{setup → deployment/setup}/install_qoderwork.py +20 -12
  47. package/templates/qoder/scripts/{setup → deployment/setup}/platform_doctor.py +8 -2
  48. package/templates/qoder/scripts/{setup → deployment/setup}/repo_root.py +113 -112
  49. package/templates/qoder/scripts/{setup → deployment/setup}/setup.py +31 -11
  50. package/templates/qoder/scripts/{setup → deployment/setup}/setup_lanhu.py +13 -7
  51. package/templates/qoder/scripts/domain/__init__.py +0 -0
  52. package/templates/qoder/scripts/domain/design/__init__.py +0 -0
  53. package/templates/qoder/scripts/{report → domain/design}/fill_prototype.py +12 -6
  54. package/templates/qoder/scripts/{report → domain/design}/gen_design_doc.py +8 -2
  55. package/templates/qoder/scripts/{setup → domain/kg}/__init__.py +11 -11
  56. package/templates/qoder/scripts/domain/kg/build/__init__.py +0 -0
  57. package/templates/qoder/scripts/{kg → domain/kg/build}/build_entity_registry.py +7 -2
  58. package/templates/qoder/scripts/{kg → domain/kg/build}/build_relations.py +7 -2
  59. package/templates/qoder/scripts/{kg → domain/kg/build}/build_style_index.py +13 -7
  60. package/templates/qoder/scripts/{kg → domain/kg/build}/build_workflows.py +7 -2
  61. package/templates/qoder/scripts/{kg → domain/kg/build}/kg_build.py +76 -9
  62. package/templates/qoder/scripts/{kg → domain/kg/build}/kg_build_db.py +7 -2
  63. package/templates/qoder/scripts/{kg → domain/kg/build}/kg_incremental.py +11 -6
  64. package/templates/qoder/scripts/{report → domain/kg/build}/learn_aggregate.py +9 -4
  65. package/templates/qoder/scripts/domain/kg/extract/__init__.py +0 -0
  66. package/templates/qoder/scripts/{common → domain/kg/extract}/extract.py +430 -419
  67. package/templates/qoder/scripts/{common → domain/kg/extract}/test_extract.py +115 -115
  68. package/templates/qoder/scripts/{common → domain/kg/extract}/ts_extract.py +614 -536
  69. package/templates/qoder/scripts/domain/kg/graph/__init__.py +0 -0
  70. package/templates/qoder/scripts/{kg → domain/kg/graph}/kg_link_db.py +7 -2
  71. package/templates/qoder/scripts/domain/kg/graph/kg_semantic.py +683 -0
  72. package/templates/qoder/scripts/{kg → domain/kg}/kg.py +263 -30
  73. package/templates/qoder/scripts/{common → domain/kg}/kg_capabilities.py +182 -182
  74. package/templates/qoder/scripts/domain/kg/search/__init__.py +0 -0
  75. package/templates/qoder/scripts/{kg → domain/kg/search}/context_pack.py +30 -9
  76. package/templates/qoder/scripts/{kg → domain/kg/search}/enrich_prompt.py +10 -4
  77. package/templates/qoder/scripts/{kg → domain/kg/search}/prefetch.py +33 -8
  78. package/templates/qoder/scripts/{common → domain/kg/search}/search_engine.py +207 -205
  79. package/templates/qoder/scripts/{kg → domain/kg/search}/search_index.py +143 -77
  80. package/templates/qoder/scripts/domain/kg/server/__init__.py +0 -0
  81. package/templates/qoder/scripts/domain/kg/server/kgd.py +549 -0
  82. package/templates/qoder/scripts/domain/kg/server/perf_bench.py +197 -0
  83. package/templates/qoder/scripts/domain/kg/storage/__init__.py +0 -0
  84. package/templates/qoder/scripts/{kg → domain/kg/storage}/kg_duckdb.py +38 -14
  85. package/templates/qoder/scripts/domain/learning/__init__.py +0 -0
  86. package/templates/qoder/scripts/{report → domain/learning}/learn.py +8 -3
  87. package/templates/qoder/scripts/{mcp → domain/report}/__init__.py +11 -11
  88. package/templates/qoder/scripts/{report → domain/report}/add_session.py +9 -3
  89. package/templates/qoder/scripts/{report → domain/report}/export.py +10 -5
  90. package/templates/qoder/scripts/{report → domain/report}/report.py +9 -4
  91. package/templates/qoder/scripts/domain/report/report_snapshot.py +261 -0
  92. package/templates/qoder/scripts/{report → domain/report}/role.py +10 -4
  93. package/templates/qoder/scripts/{report → domain/report}/status.py +112 -9
  94. package/templates/qoder/scripts/domain/report/status_snapshot.py +191 -0
  95. package/templates/qoder/scripts/domain/requirement/__init__.py +0 -0
  96. package/templates/qoder/scripts/{report → domain/requirement}/archive_prd.py +8 -2
  97. package/templates/qoder/scripts/{report → domain/requirement}/req.py +228 -222
  98. package/templates/qoder/scripts/{kg → domain/task}/__init__.py +11 -11
  99. package/templates/qoder/scripts/{task → domain/task}/git_sync.py +40 -21
  100. package/templates/qoder/scripts/{task → domain/task}/syncgate.py +13 -8
  101. package/templates/qoder/scripts/{task → domain/task}/task.py +13 -5
  102. package/templates/qoder/scripts/{task → domain/task}/task_lifecycle.py +606 -596
  103. package/templates/qoder/scripts/{task → domain/task}/task_query.py +162 -161
  104. package/templates/qoder/scripts/{task → domain/task}/task_relations.py +425 -424
  105. package/templates/qoder/scripts/{task → domain/task}/team_sync.py +9 -4
  106. package/templates/qoder/scripts/domain/task/zentao_sync.py +370 -0
  107. package/templates/qoder/scripts/foundation/__init__.py +0 -0
  108. package/templates/qoder/scripts/{common → foundation}/bootstrap.py +10 -10
  109. package/templates/qoder/scripts/foundation/core/__init__.py +0 -0
  110. package/templates/qoder/scripts/{common → foundation/core}/cmd_registry.py +1 -1
  111. package/templates/qoder/scripts/{common → foundation/core}/config.py +1 -1
  112. package/templates/qoder/scripts/{common → foundation/core}/paths.py +706 -586
  113. package/templates/qoder/scripts/foundation/data/__init__.py +0 -0
  114. package/templates/qoder/scripts/{common → foundation/data}/contract.py +2 -2
  115. package/templates/qoder/scripts/{common → foundation/data}/events.py +2 -2
  116. package/templates/qoder/scripts/{common → foundation/data}/result.py +1 -1
  117. package/templates/qoder/scripts/{common → foundation/data}/task_utils.py +427 -404
  118. package/templates/qoder/scripts/foundation/identity/__init__.py +0 -0
  119. package/templates/qoder/scripts/{common → foundation/identity}/check_publish.py +103 -98
  120. package/templates/qoder/scripts/{common → foundation/identity}/developer.py +239 -239
  121. package/templates/qoder/scripts/{common → foundation/identity}/guard.py +2 -2
  122. package/templates/qoder/scripts/{common → foundation/identity}/identity.py +14 -10
  123. package/templates/qoder/scripts/{common → foundation/identity}/roles.py +1 -1
  124. package/templates/qoder/scripts/foundation/integrations/__init__.py +0 -0
  125. package/templates/qoder/scripts/{common → foundation/integrations}/active_task.py +30 -15
  126. package/templates/qoder/scripts/{common → foundation/integrations}/feishu.py +2 -2
  127. package/templates/qoder/scripts/{common → foundation/integrations}/terms.py +3 -3
  128. package/templates/qoder/scripts/foundation/integrations/zentao_client.py +220 -0
  129. package/templates/qoder/scripts/foundation/io/__init__.py +0 -0
  130. package/templates/qoder/scripts/{common → foundation/io}/filelock.py +1 -1
  131. package/templates/qoder/scripts/{common → foundation/io}/reqid.py +1 -1
  132. package/templates/qoder/scripts/foundation/protocol/__init__.py +0 -0
  133. package/templates/qoder/scripts/foundation/protocol/duckdb_conn.py +39 -0
  134. package/templates/qoder/scripts/{common → foundation/protocol}/mcp_base.py +1 -1
  135. package/templates/qoder/scripts/foundation/utils/__init__.py +0 -0
  136. package/templates/qoder/scripts/{common → foundation/utils}/eval_api.py +12 -1
  137. package/templates/qoder/scripts/{common → foundation/utils}/pip_install.py +23 -6
  138. package/templates/qoder/scripts/{common → foundation/utils}/platform_guard.py +1 -1
  139. package/templates/qoder/scripts/orchestration/__init__.py +0 -0
  140. package/templates/qoder/scripts/orchestration/wlkj.py +185 -0
  141. package/templates/qoder/scripts/protocol/__init__.py +0 -0
  142. package/templates/qoder/scripts/protocol/browser/README.md +207 -0
  143. package/templates/qoder/scripts/protocol/browser/SKILL.md +265 -0
  144. package/templates/qoder/scripts/protocol/browser/config.env +9 -0
  145. package/templates/qoder/scripts/protocol/browser/references/cdp-api.md +110 -0
  146. package/templates/qoder/scripts/protocol/browser/references/migration-2.5.3.md +72 -0
  147. package/templates/qoder/scripts/protocol/browser/references/site-patterns/.gitkeep +0 -0
  148. package/templates/qoder/scripts/protocol/browser/scripts/browser-discovery.mjs +138 -0
  149. package/templates/qoder/scripts/protocol/browser/scripts/cdp-proxy.mjs +672 -0
  150. package/templates/qoder/scripts/protocol/browser/scripts/check-deps.mjs +206 -0
  151. package/templates/qoder/scripts/protocol/browser/scripts/find-url.mjs +253 -0
  152. package/templates/qoder/scripts/protocol/browser/scripts/match-site.mjs +46 -0
  153. package/templates/qoder/scripts/protocol/browser/templates/config.env.template +9 -0
  154. package/templates/qoder/scripts/protocol/mcp/__init__.py +11 -0
  155. package/templates/qoder/scripts/protocol/mcp/kg_mcp_server.py +478 -0
  156. package/templates/qoder/scripts/{mcp → protocol/mcp}/lanhu_stdio_wrapper.py +11 -5
  157. package/templates/qoder/scripts/{mcp → protocol/mcp}/mcp_doctor.py +47 -14
  158. package/templates/qoder/scripts/protocol/mcp/mcp_launcher.py +600 -0
  159. package/templates/qoder/scripts/{mcp → protocol/mcp}/mysql_mcp_server.py +181 -67
  160. package/templates/qoder/scripts/protocol/mcp/zentao_mcp_server.py +1920 -0
  161. package/templates/qoder/scripts/protocol/transports/__init__.py +56 -0
  162. package/templates/qoder/scripts/protocol/transports/base.py +87 -0
  163. package/templates/qoder/scripts/protocol/transports/cli.py +132 -0
  164. package/templates/qoder/scripts/protocol/transports/http.py +141 -0
  165. package/templates/qoder/scripts/protocol/transports/stdio.py +293 -0
  166. package/templates/qoder/scripts/run_weekly_update.bat +27 -18
  167. package/templates/qoder/scripts/run_weekly_update.sh +22 -13
  168. package/templates/qoder/scripts/validation/__init__.py +0 -0
  169. package/templates/qoder/scripts/validation/eval/__init__.py +0 -0
  170. package/templates/qoder/scripts/validation/eval/perf-report-2026-06-24.md +139 -0
  171. package/templates/qoder/scripts/validation/eval/qwork_harness.py +617 -0
  172. package/templates/qoder/scripts/validation/eval/report-commands.md +220 -0
  173. package/templates/qoder/scripts/validation/eval/transcript_timing.py +386 -0
  174. package/templates/qoder/scripts/validation/metrics/__init__.py +0 -0
  175. package/templates/qoder/scripts/{report → validation/metrics}/eval_prd.py +17 -10
  176. package/templates/qoder/scripts/validation/metrics/usability_score.py +750 -0
  177. package/templates/qoder/scripts/validation/test/__init__.py +11 -0
  178. package/templates/qoder/scripts/validation/test/assertion_gen.py +551 -0
  179. package/templates/qoder/scripts/{test → validation/test}/autotest.py +64 -4
  180. package/templates/qoder/scripts/{common → validation/test}/autotest_auth.py +1 -1
  181. package/templates/qoder/scripts/{test → validation/test}/autotest_batch.py +16 -3
  182. package/templates/qoder/scripts/{test → validation/test}/autotest_data.py +17 -12
  183. package/templates/qoder/scripts/validation/test/autotest_opencli.py +646 -0
  184. package/templates/qoder/scripts/{test → validation/test}/autotest_run.py +17 -3
  185. package/templates/qoder/scripts/validation/test/autotest_webaccess.py +571 -0
  186. package/templates/qoder/scripts/{test → validation/test}/benchmark.py +12 -6
  187. package/templates/qoder/scripts/{test → validation/test}/kg_auto_login.py +8 -2
  188. package/templates/qoder/scripts/{test → validation/test}/kg_test_runner.py +7 -2
  189. package/templates/qoder/scripts/{test → validation/test}/page_probe.py +8 -3
  190. package/templates/qoder/scripts/validation/test/smoke_all_commands.py +170 -0
  191. package/templates/qoder/settings.json +30 -0
  192. package/templates/qoder/skills/design-import/SKILL.md +22 -13
  193. package/templates/qoder/skills/design-review/SKILL.md +19 -10
  194. package/templates/qoder/skills/prd-generator/SKILL.md +22 -18
  195. package/templates/qoder/skills/prd-review/SKILL.md +6 -4
  196. package/templates/qoder/skills/prototype-generator/SKILL.md +63 -14
  197. package/templates/qoder/skills/spec-coder/SKILL.md +22 -13
  198. package/templates/qoder/skills/spec-generator/SKILL.md +22 -10
  199. package/templates/qoder/skills/test-generator/SKILL.md +7 -5
  200. package/templates/qoder/skills/wl-code/SKILL.md +23 -11
  201. package/templates/qoder/skills/wl-commit/SKILL.md +9 -8
  202. package/templates/qoder/skills/wl-design/SKILL.md +5 -3
  203. package/templates/qoder/skills/wl-init/SKILL.md +6 -6
  204. package/templates/qoder/skills/wl-insight/SKILL.md +23 -10
  205. package/templates/qoder/skills/wl-prd-full/SKILL.md +57 -6
  206. package/templates/qoder/skills/wl-prd-quick/SKILL.md +3 -3
  207. package/templates/qoder/skills/wl-prd-review/SKILL.md +16 -4
  208. package/templates/qoder/skills/wl-report/SKILL.md +25 -23
  209. package/templates/qoder/skills/wl-search/SKILL.md +149 -78
  210. package/templates/qoder/skills/wl-spec/SKILL.md +7 -5
  211. package/templates/qoder/skills/wl-status/SKILL.md +48 -16
  212. package/templates/qoder/skills/wl-task/SKILL.md +92 -10
  213. package/templates/qoder/skills/wl-test/SKILL.md +75 -29
  214. package/templates/qoder/templates/prd-full-template.md +7 -0
  215. package/templates/root/AGENTS.md +275 -236
  216. package/templates/root/requirements.txt +3 -0
  217. package/templates/root//344/275/277/347/224/250/350/257/264/346/230/216.md +3 -3
  218. package/templates/root//346/226/260/346/211/213/346/214/207/345/215/227.md +2 -2
  219. package/templates/.qoder/.runtime/ctx-cache-5660152f1d6dd819.md +0 -23
  220. package/templates/.qoder/.runtime/ctx-cache-afdce0dac06b25b0.md +0 -23
  221. package/templates/.qoder/.runtime/search-cache-eae7644e7b122f35.txt +0 -1
  222. package/templates/.qoder/learning/eval-history.jsonl +0 -28
  223. package/templates/data/index/wiki-index.json +0 -8
  224. package/templates/qoder/nul +0 -4
  225. package/templates/qoder/scripts/kg/extract.py +0 -398
  226. package/templates/qoder/scripts/kg/kg_semantic.py +0 -156
  227. package/templates/qoder/scripts/mcp/kg_mcp_server.py +0 -606
  228. package/templates/qoder/scripts/mcp/mcp_launcher.py +0 -442
  229. package/templates/qoder/scripts/mcp/zentao_mcp_server.py +0 -384
  230. package/templates/qoder/scripts/task/__init__.py +0 -11
  231. package/templates/qoder/scripts/test/__init__.py +0 -11
  232. package/templates/qoder/scripts/wlkj.py +0 -116
  233. /package/templates/qoder/scripts/{common → deployment}/__init__.py +0 -0
  234. /package/templates/qoder/scripts/{common → domain/kg/graph}/graph_traverse.py +0 -0
  235. /package/templates/qoder/scripts/{common → domain/kg/server}/repowiki.py +0 -0
  236. /package/templates/qoder/scripts/{common → foundation/io}/atomicio.py +0 -0
  237. /package/templates/qoder/scripts/{test → validation/test}/secure-ls.js +0 -0
@@ -0,0 +1,293 @@
1
+ # -*- coding: utf-8 -*-
2
+ """
3
+ transports.stdio —— 本地 stdio MCP 传输策略(现状默认)。
4
+
5
+ 设计模式: Strategy(策略模式的本地实现)
6
+
7
+ 机制:
8
+ 通过 mcp_launcher.py spawn 本地 MCP server 子进程,
9
+ 维持 stdin/stdout 长连接,复 JSON-RPC 2.0 协议。
10
+ 复用 mcp_base.py 已有的协议骨架,不重造轮子。
11
+
12
+ 进程复用 (Flyweight/Pool):
13
+ 每个 server 名对应一个 StdioTransport 实例,子进程长驻,
14
+ 多次 call_tool 复用同一进程,避免冷启开销(kg 有 duckdb 冷启)。
15
+ 子进程崩溃 (poll() != None) 自动重启。
16
+
17
+ 线程安全:
18
+ 每个 transport 内部一把锁,保证请求-响应串行配对
19
+ (MCP 协议要求 id 匹配,并发交错会乱)。
20
+
21
+ 协议(与 mcp_base.py _handle 对齐):
22
+ 1. initialize 握手 → capabilities
23
+ 2. notifications/initialized 通知
24
+ 3. tools/list → 缓存工具清单
25
+ 4. tools/call → 返回 content[0].text
26
+ """
27
+ from __future__ import annotations
28
+
29
+ import json
30
+ import os
31
+ import subprocess
32
+ import sys
33
+ import threading
34
+ from pathlib import Path
35
+ from typing import Optional
36
+
37
+ from .base import ToolResult
38
+
39
+
40
+ # MCP 协议版本(与 mcp_base.py:PROTOCOL_VERSION 对齐)
41
+ _PROTOCOL_VERSION = "2024-11-05"
42
+
43
+ # 读超时 (秒)。冷启动 + 握手 + tools/list 的总等待上限。
44
+ # 历史 bug: readline 无超时会永久阻塞 —— 并发冷启动竞态下某 server 响应迟迟不来,
45
+ # 整个 _build_index 的 ex.map 卡死或时序错乱 → 间歇性 0 工具。加超时后超时即失败可重试。
46
+ _READ_TIMEOUT = 30.0
47
+
48
+
49
+ def _get_repo_root() -> Path:
50
+ """从 common.paths 获取仓库根(复用既有工具,不重复造轮子)。"""
51
+ try:
52
+ from foundation.core.paths import get_repo_root
53
+ return get_repo_root()
54
+ except Exception:
55
+ return Path(__file__).resolve().parent.parent.parent.parent
56
+
57
+
58
+ class StdioTransport:
59
+ """本地 stdio MCP 传输策略。
60
+
61
+ Args:
62
+ server_name: MCP server 名(对应 mcp_launcher 的参数,如 'kg'/'zentao')。
63
+ repo_root: 仓库根(定位 mcp_launcher.py),默认自动探测。
64
+ launcher_subprocess_name: launcher 接受的子名(多数情况同 server_name)。
65
+ """
66
+
67
+ def __init__(self, server_name: str, repo_root: Optional[Path] = None,
68
+ launcher_subprocess_name: Optional[str] = None):
69
+ self._server = server_name
70
+ self._launcher_arg = launcher_subprocess_name or server_name
71
+ self._repo = repo_root or _get_repo_root()
72
+ self._proc: Optional[subprocess.Popen] = None
73
+ self._lock = threading.RLock() # 可重入:_send_recv→_ensure_alive→_spawn 会递归加锁
74
+ self._tools_cache: Optional[list[dict]] = None
75
+ self._tool_index: Optional[set[str]] = None
76
+ self._req_id = 0
77
+ self._err: Optional[str] = None
78
+ self._handshook = False
79
+ self._stderr_lines: list[str] = [] # 排水收集(诊断用)
80
+ self._stderr_thread: Optional[threading.Thread] = None
81
+
82
+ @property
83
+ def available(self) -> bool:
84
+ """此 transport 是否可用(子进程能拉起 + 握手成功)。"""
85
+ if self._err is not None:
86
+ return False
87
+ return self._ensure_alive()
88
+
89
+ # ------------------------------------------------------------------
90
+ # 进程管理
91
+ # ------------------------------------------------------------------
92
+
93
+ def _launcher_path(self) -> Path:
94
+ return self._repo / ".qoder" / "scripts" / "protocol" / "mcp" / "mcp_launcher.py"
95
+
96
+ def _ensure_alive(self) -> bool:
97
+ """确保子进程存活且已握手。返回是否就绪。"""
98
+ if self._proc is not None and self._proc.poll() is None and self._handshook:
99
+ return True
100
+ return self._spawn()
101
+
102
+ def _spawn(self) -> bool:
103
+ """spawn 子进程并握手。失败记 _err。"""
104
+ launcher = self._launcher_path()
105
+ if not launcher.is_file():
106
+ self._err = "mcp_launcher.py 不存在: %s" % launcher
107
+ return False
108
+ try:
109
+ self._proc = subprocess.Popen(
110
+ [sys.executable, str(launcher), self._launcher_arg],
111
+ stdin=subprocess.PIPE,
112
+ stdout=subprocess.PIPE,
113
+ stderr=subprocess.PIPE,
114
+ cwd=str(self._repo),
115
+ bufsize=1,
116
+ encoding="utf-8",
117
+ errors="replace",
118
+ # 关键:关掉 Python 子进程的 stdin 缓冲,避免 launcher in-proc 模式下
119
+ # runpy.run_path 的 server 读 stdin 时和 pipe 缓冲交互异常。
120
+ # text 模式下 close_fds 在 Win 上受限,但 PIPE 已隔离。
121
+ )
122
+ except Exception as e:
123
+ self._err = "拉起失败: %s" % str(e)[:120]
124
+ self._proc = None
125
+ return False
126
+
127
+ # 排空 stderr:launcher/server 诊断输出多,不排会撑满 pipe 缓冲阻塞 server。
128
+ # 守护线程持续读(与裸 subprocess 复刻保持一致的极简形态,先保证不阻塞)。
129
+ proc_ref = self._proc # 闭包捕获局部引用,避免 self._proc 重赋值竞态
130
+ stderr_ref = self._stderr_lines
131
+ def _drain():
132
+ assert proc_ref is not None and proc_ref.stderr is not None
133
+ try:
134
+ for line in proc_ref.stderr:
135
+ stderr_ref.append(line.rstrip())
136
+ if len(stderr_ref) > 200:
137
+ del stderr_ref[:-100]
138
+ except Exception:
139
+ pass
140
+ self._stderr_thread = threading.Thread(target=_drain, daemon=True)
141
+ self._stderr_thread.start()
142
+
143
+ # 握手 initialize(直接底层收发,不走 _send_recv——避免 _ensure_alive 递归 _spawn)
144
+ with self._lock:
145
+ try:
146
+ req = {"jsonrpc": "2.0", "id": 1, "method": "initialize",
147
+ "params": {"capabilities": {}, "protocolVersion": _PROTOCOL_VERSION,
148
+ "clientInfo": {"name": "nexus", "version": "1.0"}}}
149
+ self._write(req)
150
+ resp = self._readline_matching(1)
151
+ if "result" not in resp:
152
+ self._err = "initialize 无 result: %s" % str(resp)[:120]
153
+ self._kill_unlocked()
154
+ return False
155
+ # 发 initialized 通知(无 id,无响应)
156
+ self._write({"jsonrpc": "2.0", "method": "notifications/initialized"})
157
+ self._handshook = True
158
+ # 握手后重置 tools 缓存(新进程)
159
+ self._tools_cache = None
160
+ self._tool_index = None
161
+ return True
162
+ except Exception as e:
163
+ self._err = "握手失败: %s" % str(e)[:120]
164
+ self._kill_unlocked()
165
+ return False
166
+
167
+ def _kill_unlocked(self) -> None:
168
+ """_kill 的无锁版本(调用方已持锁,如 _spawn 内握手失败清理)。"""
169
+ if self._proc is not None:
170
+ try:
171
+ self._proc.stdin.close()
172
+ except Exception:
173
+ pass
174
+ try:
175
+ self._proc.terminate()
176
+ self._proc.wait(timeout=2)
177
+ except Exception:
178
+ try:
179
+ self._proc.kill()
180
+ except Exception:
181
+ pass
182
+ self._proc = None
183
+ self._handshook = False
184
+
185
+ def _kill(self) -> None:
186
+ """杀子进程(持锁版本,close/外部调用用)。"""
187
+ with self._lock:
188
+ self._kill_unlocked()
189
+
190
+ # ------------------------------------------------------------------
191
+ # JSON-RPC 收发
192
+ # ------------------------------------------------------------------
193
+
194
+ def _next_id(self) -> int:
195
+ self._req_id += 1
196
+ return self._req_id
197
+
198
+ def _write(self, obj: dict) -> None:
199
+ """写一行 JSON 到 stdin(调用方持锁)。"""
200
+ if self._proc is None or self._proc.stdin is None:
201
+ raise RuntimeError("子进程未就绪")
202
+ self._proc.stdin.write(json.dumps(obj, ensure_ascii=False) + "\n")
203
+ self._proc.stdin.flush()
204
+
205
+ def _readline_matching(self, rid: int, timeout: float = _READ_TIMEOUT) -> dict:
206
+ """读 stdout 直到拿到匹配 rid 的响应(跳过通知/其他行),带 wall-clock 超时。
207
+
208
+ 实测结论 (2026-06): 用裸 subprocess 并发 spawn 4 个 server, 阻塞 readline 全部
209
+ 在 2-5s 内成功 (kg/zentao/mysql/lanhu)。原版阻塞 readline 本身没问题。
210
+ 故保持阻塞读, 仅加进程存活检查 (poll) + wall-clock 超时兜底 (防极端卡死):
211
+ - readline 返回空 (EOF) 且进程已死 → 立即失败
212
+ - 累计耗时超 timeout → 失败 (上层 _build_index 空索引可重试)
213
+ 之前尝试的"后台 reader 线程"版反而在并发下引入竞态 (与 _drain stderr 线程争用),
214
+ 导致全部 30s 超时, 已废弃。
215
+ """
216
+ import time
217
+ assert self._proc is not None and self._proc.stdout is not None
218
+ deadline = time.monotonic() + timeout
219
+ while True:
220
+ line = self._proc.stdout.readline()
221
+ if not line:
222
+ # EOF: 进程已退出或关连接
223
+ raise RuntimeError("MCP server 关闭连接 (EOF)")
224
+ line = line.strip()
225
+ if not line:
226
+ continue
227
+ try:
228
+ resp = json.loads(line)
229
+ except json.JSONDecodeError:
230
+ continue # 非 JSON 行(诊断输出等)跳过
231
+ if resp.get("id") == rid:
232
+ return resp
233
+ # 非 id 匹配(通知/其他响应)忽略,继续读
234
+ if time.monotonic() > deadline:
235
+ raise RuntimeError("读响应超时 (rid=%s)" % rid)
236
+
237
+ def _send_recv(self, method: str, params: dict | None = None,
238
+ req_id: int | None = None) -> dict:
239
+ """发请求读响应(线程安全,持锁)。"""
240
+ with self._lock:
241
+ if not self._ensure_alive():
242
+ raise RuntimeError("transport 不可用: %s" % (self._err or "未知"))
243
+ rid = req_id if req_id is not None else self._next_id()
244
+ req = {"jsonrpc": "2.0", "id": rid, "method": method}
245
+ if params is not None:
246
+ req["params"] = params
247
+ try:
248
+ self._write(req)
249
+ return self._readline_matching(rid)
250
+ except Exception:
251
+ # 通信失败,标记进程可疑,下次 _ensure_alive 会重 spawn
252
+ self._handshook = False
253
+ raise
254
+
255
+ # ------------------------------------------------------------------
256
+ # Transport 接口实现
257
+ # ------------------------------------------------------------------
258
+
259
+ def list_tools(self) -> list[dict]:
260
+ """枚举工具(缓存,每个进程只查一次)。"""
261
+ if self._tools_cache is not None:
262
+ return self._tools_cache
263
+ resp = self._send_recv("tools/list")
264
+ tools = resp.get("result", {}).get("tools", [])
265
+ self._tools_cache = tools
266
+ self._tool_index = {t.get("name", "") for t in tools}
267
+ return tools
268
+
269
+ def has_tool(self, name: str) -> bool:
270
+ if self._tool_index is None:
271
+ try:
272
+ self.list_tools()
273
+ except Exception:
274
+ return False
275
+ return name in (self._tool_index or set())
276
+
277
+ def call_tool(self, name: str, args: dict | None = None) -> ToolResult:
278
+ try:
279
+ resp = self._send_recv("tools/call", {"name": name, "arguments": args or {}})
280
+ except Exception as e:
281
+ return ToolResult(text="transport 调用失败: %s" % str(e)[:120], is_error=True)
282
+
283
+ if "error" in resp:
284
+ msg = resp["error"].get("message", "unknown error")
285
+ return ToolResult(text=str(msg), is_error=True, raw=resp)
286
+
287
+ result = resp.get("result", {})
288
+ content = result.get("content", [])
289
+ text = content[0].get("text", "") if content else ""
290
+ return ToolResult(text=text, is_error=result.get("isError", False), raw=resp)
291
+
292
+ def close(self) -> None:
293
+ self._kill() # _kill 内部自行加锁
@@ -1,22 +1,31 @@
1
1
  @echo off
2
- REM QODER Weekly Knowledge Graph Update
3
- REM Runs every Friday at 18:00
4
- REM %~dp0 = this script directory; project root is two levels up
5
-
2
+ REM QODER Weekly Knowledge Graph Update (Windows)
3
+ REM Runs every Friday at 18:00. %~dp0 = this script dir; project root = 2 levels up (安装后 .qoder/scripts/ 上2级)
4
+ REM v3.x: wlkj.py 单一真源调度器 (不再用扁平路径, 防引擎分层重组后断链)
5
+ REM 重建后自动 commit + git gc (普通模式), 防松散对象膨胀 (见 ARCHITECTURE.md)
6
6
  cd /d "%~dp0..\.."
7
-
8
- echo [%date% %time%] Starting weekly update... >> .qoder\logs\weekly-update.log
9
-
10
- REM Step 1: Git pull + PRD collect + Index rebuild
11
- python .qoder\scripts\git_sync.py >> .qoder\logs\weekly-update.log 2>&1
12
-
13
- REM Step 2: Rebuild style index (layout fingerprint + module index + page patterns).
14
- REM git_sync only rebuilds keyword/api/module indexes; style index must be rebuilt
15
- REM separately so layout_fingerprint/module-index pick up code changes each week.
16
- python .qoder\scripts\build_style_index.py >> .qoder\logs\weekly-update.log 2>&1
17
-
7
+ if not exist ".qoder\scripts\orchestration\wlkj.py" (
8
+ echo [ERROR] 引擎未安装, 请先 npx @hupan56/wlkj init
9
+ exit /b 1
10
+ )
11
+ set LOG=.qoder\logs\weekly-update.log
12
+ if not exist ".qoder\logs" mkdir ".qoder\logs"
13
+ echo [%date% %time%] Starting weekly update... >> "%LOG%"
14
+ REM Step 1: Git pull + PRD collect
15
+ python .qoder\scripts\orchestration\wlkj.py git-sync >> "%LOG%" 2>&1
16
+ REM Step 2: 重建组件级 style index (ui-style.json, 被 search/context_pack 消费, 不可删)
17
+ python .qoder\scripts\orchestration\wlkj.py style >> "%LOG%" 2>&1
18
+ REM Step 3: 知识图谱全量重建
19
+ python .qoder\scripts\orchestration\wlkj.py kg-build >> "%LOG%" 2>&1
18
20
  if errorlevel 1 (
19
- echo [%date% %time%] UPDATE FAILED - check log above >> .qoder\logs\weekly-update.log
20
- ) else (
21
- echo [%date% %time%] Update complete. >> .qoder\logs\weekly-update.log
21
+ echo [%date% %time%] UPDATE FAILED - check log above >> "%LOG%"
22
+ exit /b 1
22
23
  )
24
+ REM Step 4: 提交重建产物 + git gc (防松散对象膨胀, kg.duckdb delta 压缩有效)
25
+ echo [%date% %time%] Committing rebuilt graph + git gc... >> "%LOG%"
26
+ git add data\index\kg.duckdb data\index\kg_db.duckdb data\index\ui-style.json >> "%LOG%" 2>&1
27
+ git commit -m "[kg] weekly rebuild %date%" >> "%LOG%" 2>&1
28
+ git gc --quiet >> "%LOG%" 2>&1
29
+ echo [%date% %time%] gc done (松散对象已打包) >> "%LOG%"
30
+ echo [%date% %time%] Update complete. >> "%LOG%"
31
+ exit /b 0
@@ -1,25 +1,34 @@
1
1
  #!/usr/bin/env bash
2
2
  # QODER Weekly Knowledge Graph Update (macOS / Linux)
3
3
  # Runs every Friday at 18:00 via cron (see setup_weekly_cron.sh)
4
+ # v3.x: 走 wlkj.py 单一真源调度器 (不再用扁平路径, 防引擎分层重组后断链)
5
+ # 重建后自动 commit + git gc (普通模式), 防松散对象膨胀
4
6
  set -u
5
7
  cd "$(dirname "$0")/../.." || exit 1
6
-
8
+ WLKJ=".qoder/scripts/orchestration/wlkj.py"
9
+ if [ ! -f "$WLKJ" ]; then
10
+ echo "[ERROR] 引擎未安装, 请先 npx @hupan56/wlkj init"
11
+ exit 1
12
+ fi
7
13
  mkdir -p .qoder/logs
8
14
  LOG=.qoder/logs/weekly-update.log
9
-
10
15
  echo "[$(date '+%Y-%m-%d %H:%M:%S')] Starting weekly update..." >> "$LOG"
11
-
12
- python3 .qoder/scripts/git_sync.py >> "$LOG" 2>&1
13
-
14
- # Step 2: Rebuild style index (layout fingerprint + module index + page patterns).
15
- # git_sync only rebuilds keyword/api/module indexes; style index must be rebuilt
16
- # separately so layout_fingerprint/module-index pick up code changes each week.
17
- python3 .qoder/scripts/build_style_index.py >> "$LOG" 2>&1
16
+ # Step 1: Git pull + PRD collect
17
+ python3 "$WLKJ" git-sync >> "$LOG" 2>&1
18
+ # Step 2: 重建组件级 style index (ui-style.json, 被 search/context_pack 消费, 不可删)
19
+ python3 "$WLKJ" style >> "$LOG" 2>&1
20
+ # Step 3: 知识图谱全量重建
21
+ python3 "$WLKJ" kg-build >> "$LOG" 2>&1
18
22
  STATUS=$?
19
-
20
23
  if [ $STATUS -ne 0 ]; then
21
24
  echo "[$(date '+%Y-%m-%d %H:%M:%S')] UPDATE FAILED (exit $STATUS) - check log above" >> "$LOG"
22
- else
23
- echo "[$(date '+%Y-%m-%d %H:%M:%S')] Update complete." >> "$LOG"
25
+ exit $STATUS
24
26
  fi
25
- exit $STATUS
27
+ # Step 4: 提交重建产物 + git gc (防松散对象膨胀, kg.duckdb delta 压缩有效)
28
+ echo "[$(date '+%Y-%m-%d %H:%M:%S')] Committing rebuilt graph + git gc..." >> "$LOG"
29
+ git add data/index/kg.duckdb data/index/kg_db.duckdb data/index/ui-style.json >> "$LOG" 2>&1
30
+ git commit -m "[kg] weekly rebuild $(date +%Y-%m-%d)" >> "$LOG" 2>&1
31
+ git gc --quiet >> "$LOG" 2>&1
32
+ echo "[$(date '+%Y-%m-%d %H:%M:%S')] gc done (松散对象已打包)" >> "$LOG"
33
+ echo "[$(date '+%Y-%m-%d %H:%M:%S')] Update complete." >> "$LOG"
34
+ exit 0
File without changes
@@ -0,0 +1,139 @@
1
+ # QoderWork 工作流性能报告(v2 — 聚焦工作流本身)
2
+
3
+ > 剔除模型抖动(reason=error)和首 token 延迟,只看**工作流自身的效率**。
4
+ > 数据:2026-06-24 用户手动实跑 `/wl-*` 命令的真实 transcript。
5
+
6
+ ## 核心结论:问题不在慢,在"碎"
7
+
8
+ 工作流最大的低效不是单次调用慢,而是 **AI 在用"挤牙膏"式串行摸索**——
9
+ 每个文件/脚本单独调一次 Bash,调完思考一轮,再调下一个。
10
+
11
+ 实测每个 skill 都在**重复调用同一个目标**:
12
+
13
+ | Skill | 重复调用 | 次数 | 现象 |
14
+ |-------|---------|------|------|
15
+ | /wl-search | `kg.py`(知识图谱) | **×5** | 同一个脚本连调5遍 |
16
+ | /wl-status | `ls 目录` | **×5** | 反复 ls 探路 |
17
+ | /wl-status | `cat 文件` | **×5** | 配置/任务/反馈一个一个 cat |
18
+ | /wl-task | `task.py` | **×3** | 同脚本3次 |
19
+ | /wl-insight | `search_index.py` | ×2 | 同脚本2次 |
20
+
21
+ **对比基线**:简单命令(prd-quick / prd-review)只需 **1 轮迭代、0 次工具调用**,44s 完成。
22
+ 复杂命令却要 **5-6 轮迭代、7-10 次调用**——多出来的全是碎片化摸索。
23
+
24
+ ## 碎片化的真实样子(/wl-status 为例)
25
+
26
+ ```
27
+ 轮1: TodoWrite(列计划)
28
+ Bash: repo_root.py ← 找根目录(每次都找)
29
+ Bash: ls .qoder/ ← 探路
30
+ 轮2: TodoWrite(更新)
31
+ Bash: cat .developer ← 查身份
32
+ Bash: ls tasks/ ← 列任务
33
+ Bash: cat feedback.jsonl ← 查反馈
34
+ Bash: cat eval-history ← 查评估
35
+ Bash: cat .index-meta ← 查索引元数据
36
+ 轮3: TodoWrite(更新)
37
+ Bash: ls members/hupan56/ ← 查成员
38
+ Bash: ls patterns/ ← 查模式
39
+ Bash: cat config.yaml ← 查配置
40
+ 轮4-5: ...
41
+ ```
42
+
43
+ 8 个 `cat`/`ls` 本可以**合并成 1 个脚本**一次拿全,却拆成 5 轮、10+ 次调用。
44
+
45
+ ## 迭代轮数 = 成本放大器
46
+
47
+ 每多一轮迭代,就多一次:
48
+ - 完整的模型请求(输入 token = 之前所有上下文累加)
49
+ - 工具往返延迟
50
+ - TodoWrite 进度更新(本身也占一次调用)
51
+
52
+ | 命令 | 迭代轮 | 工具调用 | vs 基线 |
53
+ |------|--------|---------|---------|
54
+ | prd-quick(基线) | **1** | 0 | — |
55
+ | /wl-task | 2 | 1-2 | 2× |
56
+ | /wl-report | 2 | 2 | 2× |
57
+ | /wl-insight | 3 | 4 | 3× |
58
+ | /wl-status | **5** | 10 | **5×** |
59
+ | /wl-search | **6** | 7-9 | **6×** |
60
+
61
+ 迭代轮数和 token 消耗是**指数关系**——第 N 轮要把前 N-1 轮的所有内容重新喂给模型。
62
+ 6 轮迭代 ≈ 把上下文喂了 6 遍。
63
+
64
+ ## 优化方案(按收益排序)
65
+
66
+ ### P0:给每个 skill 写"一次拿全"的聚合脚本(砍 60-80% 调用)
67
+
68
+ 现状:AI 用 cat/ls 一个个摸。
69
+ 目标:一个脚本一次返回 skill 需要的所有数据。
70
+
71
+ **`/wl-status` 示例**——合并成一个 `status_snapshot.py`:
72
+ ```python
73
+ # 一次返回: 身份 + 任务 + 反馈 + 评估 + 索引 + 配置 + 成员
74
+ # 现在: 8次 cat/ls + 5轮迭代
75
+ # 优化后: 1次调用 + 1轮迭代
76
+ ```
77
+
78
+ **`/wl-search` 示例**——kg.py 被调5次,应合并成一次:
79
+ ```python
80
+ # 现在: kg.py search → kg.py api → kg.py impact → kg.py context → kg.py style (5次)
81
+ # 优化: kg.py all-in-one "考勤" → 一次返回全部维度
82
+ # (context_pack.py 本就是干这个的, 但 skill 没用足)
83
+ ```
84
+
85
+ **预期**:迭代从 5-6 轮降到 1-2 轮,调用从 7-10 次降到 1-3 次。
86
+
87
+ ### P1:缓存"不变量"(身份/根目录/配置)
88
+
89
+ `repo_root.py`、`cat .developer`、`cat config.yaml` 这些**会话内不变**的数据,
90
+ 每次命令都重新查。应缓存到环境变量或临时文件,session 内复用。
91
+
92
+ 现状 `/wl-status` 第一步永远是 `repo_root.py`——这是纯浪费。
93
+
94
+ ### P2:强制 skill 用 Fast Path(context_pack)
95
+
96
+ AGENTS.md 已写了 Fast Path:
97
+ > `context_pack.py <业务词>` 一次返回 代码+页面+字段+PRD+API+样式
98
+
99
+ 但实测 `/wl-search` 没用它,而是连调 5 次 `kg.py`。
100
+ 应在 skill 指令里**强制**:搜索类命令第一步必须调 `context_pack.py`,禁止拆开调。
101
+
102
+ ### P3:砍掉冗余 TodoWrite
103
+
104
+ 实测 TodoWrite 占了 12% 调用。在 search/status 这类只读命令里,
105
+ TodoWrite 纯粹是"给用户看进度",但每次更新都消耗一次工具往返 + 触发新一轮模型思考。
106
+ **建议**:只读命令不用 TodoWrite,或合并到最后一次性输出。
107
+
108
+ ### P4:飞轮闭环(持续优化)
109
+
110
+ ```
111
+ 你跑命令 → transcript 自动记录
112
+
113
+ 每周: python transcript_timing.py <session目录> --json
114
+
115
+ 对比上周: 迭代轮数↓了没? 调用次数↓了没?
116
+
117
+ 没降 → 继续优化对应 skill
118
+ ```
119
+
120
+ 前 3 环已通(采集→分析→归因)。缺"自动对比上周"脚本,可后续补。
121
+
122
+ ## 预期收益
123
+
124
+ | 优化 | 迭代轮数 | 工具调用 | token |
125
+ |------|---------|---------|-------|
126
+ | 现状(/wl-status) | 5 | 10 | 基准 |
127
+ | +P0 聚合脚本 | 2 | 3 | -50% |
128
+ | +P1 缓存不变量 | 2 | 2 | -55% |
129
+ | +P3 砍 TodoWrite | 2 | 1 | -60% |
130
+ | **全做** | **1-2** | **1-2** | **-60%** |
131
+
132
+ > 注:token 收益主要来自减少迭代轮数(少喂几遍上下文),
133
+ > 而非单次调用的大小。
134
+
135
+ ## 落地优先级
136
+
137
+ 1. **先改 /wl-search 和 /wl-status**(最碎的两个,收益最大)
138
+ 2. 每个写一个聚合脚本,skill 指令改成"只调聚合脚本"
139
+ 3. 改完用 transcript_timing.py 验证:迭代和调用数应该明显下降