@hupan56/wlkj 3.0.0 → 3.1.3

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 (247) 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 +55 -23
  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 +35 -12
  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/domain/task/__pycache__/syncgate.cpython-39.pyc +0 -0
  100. package/templates/qoder/scripts/{task → domain/task}/git_sync.py +40 -21
  101. package/templates/qoder/scripts/{task → domain/task}/syncgate.py +13 -8
  102. package/templates/qoder/scripts/{task → domain/task}/task.py +13 -5
  103. package/templates/qoder/scripts/{task → domain/task}/task_lifecycle.py +606 -596
  104. package/templates/qoder/scripts/{task → domain/task}/task_query.py +162 -161
  105. package/templates/qoder/scripts/{task → domain/task}/task_relations.py +425 -424
  106. package/templates/qoder/scripts/{task → domain/task}/team_sync.py +9 -4
  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/__pycache__/__init__.cpython-39.pyc +0 -0
  110. package/templates/qoder/scripts/foundation/__pycache__/bootstrap.cpython-39.pyc +0 -0
  111. package/templates/qoder/scripts/{common → foundation}/bootstrap.py +10 -10
  112. package/templates/qoder/scripts/foundation/core/__init__.py +0 -0
  113. package/templates/qoder/scripts/foundation/core/__pycache__/__init__.cpython-39.pyc +0 -0
  114. package/templates/qoder/scripts/foundation/core/__pycache__/paths.cpython-39.pyc +0 -0
  115. package/templates/qoder/scripts/{common → foundation/core}/cmd_registry.py +1 -1
  116. package/templates/qoder/scripts/{common → foundation/core}/config.py +1 -1
  117. package/templates/qoder/scripts/{common → foundation/core}/paths.py +706 -586
  118. package/templates/qoder/scripts/foundation/data/__init__.py +0 -0
  119. package/templates/qoder/scripts/{common → foundation/data}/contract.py +2 -2
  120. package/templates/qoder/scripts/{common → foundation/data}/events.py +2 -2
  121. package/templates/qoder/scripts/{common → foundation/data}/result.py +1 -1
  122. package/templates/qoder/scripts/{common → foundation/data}/task_utils.py +427 -404
  123. package/templates/qoder/scripts/foundation/identity/__init__.py +0 -0
  124. package/templates/qoder/scripts/foundation/identity/__pycache__/__init__.cpython-39.pyc +0 -0
  125. package/templates/qoder/scripts/foundation/identity/__pycache__/identity.cpython-39.pyc +0 -0
  126. package/templates/qoder/scripts/{common → foundation/identity}/check_publish.py +103 -98
  127. package/templates/qoder/scripts/{common → foundation/identity}/developer.py +239 -239
  128. package/templates/qoder/scripts/{common → foundation/identity}/guard.py +2 -2
  129. package/templates/qoder/scripts/{common → foundation/identity}/identity.py +14 -10
  130. package/templates/qoder/scripts/{common → foundation/identity}/roles.py +1 -1
  131. package/templates/qoder/scripts/foundation/integrations/__init__.py +0 -0
  132. package/templates/qoder/scripts/{common → foundation/integrations}/active_task.py +30 -15
  133. package/templates/qoder/scripts/{common → foundation/integrations}/feishu.py +2 -2
  134. package/templates/qoder/scripts/{common → foundation/integrations}/terms.py +3 -3
  135. package/templates/qoder/scripts/foundation/integrations/zentao_client.py +220 -0
  136. package/templates/qoder/scripts/foundation/io/__init__.py +0 -0
  137. package/templates/qoder/scripts/foundation/io/__pycache__/__init__.cpython-39.pyc +0 -0
  138. package/templates/qoder/scripts/foundation/io/__pycache__/atomicio.cpython-39.pyc +0 -0
  139. package/templates/qoder/scripts/foundation/io/__pycache__/filelock.cpython-39.pyc +0 -0
  140. package/templates/qoder/scripts/{common → foundation/io}/filelock.py +1 -1
  141. package/templates/qoder/scripts/{common → foundation/io}/reqid.py +1 -1
  142. package/templates/qoder/scripts/foundation/protocol/__init__.py +0 -0
  143. package/templates/qoder/scripts/foundation/protocol/duckdb_conn.py +39 -0
  144. package/templates/qoder/scripts/{common → foundation/protocol}/mcp_base.py +1 -1
  145. package/templates/qoder/scripts/foundation/utils/__init__.py +0 -0
  146. package/templates/qoder/scripts/{common → foundation/utils}/eval_api.py +12 -1
  147. package/templates/qoder/scripts/{common → foundation/utils}/pip_install.py +23 -6
  148. package/templates/qoder/scripts/{common → foundation/utils}/platform_guard.py +1 -1
  149. package/templates/qoder/scripts/orchestration/__init__.py +0 -0
  150. package/templates/qoder/scripts/orchestration/wlkj.py +185 -0
  151. package/templates/qoder/scripts/protocol/__init__.py +0 -0
  152. package/templates/qoder/scripts/protocol/browser/README.md +207 -0
  153. package/templates/qoder/scripts/protocol/browser/SKILL.md +265 -0
  154. package/templates/qoder/scripts/protocol/browser/config.env +9 -0
  155. package/templates/qoder/scripts/protocol/browser/references/cdp-api.md +110 -0
  156. package/templates/qoder/scripts/protocol/browser/references/migration-2.5.3.md +72 -0
  157. package/templates/qoder/scripts/protocol/browser/references/site-patterns/.gitkeep +0 -0
  158. package/templates/qoder/scripts/protocol/browser/scripts/browser-discovery.mjs +138 -0
  159. package/templates/qoder/scripts/protocol/browser/scripts/cdp-proxy.mjs +672 -0
  160. package/templates/qoder/scripts/protocol/browser/scripts/check-deps.mjs +206 -0
  161. package/templates/qoder/scripts/protocol/browser/scripts/find-url.mjs +253 -0
  162. package/templates/qoder/scripts/protocol/browser/scripts/match-site.mjs +46 -0
  163. package/templates/qoder/scripts/protocol/browser/templates/config.env.template +9 -0
  164. package/templates/qoder/scripts/protocol/mcp/__init__.py +11 -0
  165. package/templates/qoder/scripts/protocol/mcp/kg_mcp_server.py +478 -0
  166. package/templates/qoder/scripts/{mcp → protocol/mcp}/lanhu_stdio_wrapper.py +11 -5
  167. package/templates/qoder/scripts/{mcp → protocol/mcp}/mcp_doctor.py +47 -14
  168. package/templates/qoder/scripts/protocol/mcp/mcp_launcher.py +600 -0
  169. package/templates/qoder/scripts/{mcp → protocol/mcp}/mysql_mcp_server.py +181 -67
  170. package/templates/qoder/scripts/protocol/mcp/zentao_mcp_server.py +1920 -0
  171. package/templates/qoder/scripts/protocol/transports/__init__.py +56 -0
  172. package/templates/qoder/scripts/protocol/transports/base.py +87 -0
  173. package/templates/qoder/scripts/protocol/transports/cli.py +132 -0
  174. package/templates/qoder/scripts/protocol/transports/http.py +141 -0
  175. package/templates/qoder/scripts/protocol/transports/stdio.py +293 -0
  176. package/templates/qoder/scripts/run_weekly_update.bat +27 -18
  177. package/templates/qoder/scripts/run_weekly_update.sh +22 -13
  178. package/templates/qoder/scripts/validation/__init__.py +0 -0
  179. package/templates/qoder/scripts/validation/eval/__init__.py +0 -0
  180. package/templates/qoder/scripts/validation/eval/perf-report-2026-06-24.md +139 -0
  181. package/templates/qoder/scripts/validation/eval/qwork_harness.py +617 -0
  182. package/templates/qoder/scripts/validation/eval/report-commands.md +220 -0
  183. package/templates/qoder/scripts/validation/eval/transcript_timing.py +386 -0
  184. package/templates/qoder/scripts/validation/metrics/__init__.py +0 -0
  185. package/templates/qoder/scripts/{report → validation/metrics}/eval_prd.py +17 -10
  186. package/templates/qoder/scripts/validation/metrics/usability_score.py +750 -0
  187. package/templates/qoder/scripts/validation/test/__init__.py +11 -0
  188. package/templates/qoder/scripts/validation/test/assertion_gen.py +551 -0
  189. package/templates/qoder/scripts/{test → validation/test}/autotest.py +64 -4
  190. package/templates/qoder/scripts/{common → validation/test}/autotest_auth.py +1 -1
  191. package/templates/qoder/scripts/{test → validation/test}/autotest_batch.py +16 -3
  192. package/templates/qoder/scripts/{test → validation/test}/autotest_data.py +17 -12
  193. package/templates/qoder/scripts/validation/test/autotest_opencli.py +646 -0
  194. package/templates/qoder/scripts/{test → validation/test}/autotest_run.py +17 -3
  195. package/templates/qoder/scripts/validation/test/autotest_webaccess.py +571 -0
  196. package/templates/qoder/scripts/{test → validation/test}/benchmark.py +12 -6
  197. package/templates/qoder/scripts/{test → validation/test}/kg_auto_login.py +8 -2
  198. package/templates/qoder/scripts/{test → validation/test}/kg_test_runner.py +7 -2
  199. package/templates/qoder/scripts/{test → validation/test}/page_probe.py +8 -3
  200. package/templates/qoder/scripts/validation/test/smoke_all_commands.py +170 -0
  201. package/templates/qoder/settings.json +30 -0
  202. package/templates/qoder/skills/design-import/SKILL.md +22 -13
  203. package/templates/qoder/skills/design-review/SKILL.md +19 -10
  204. package/templates/qoder/skills/prd-generator/SKILL.md +22 -18
  205. package/templates/qoder/skills/prd-review/SKILL.md +6 -4
  206. package/templates/qoder/skills/prototype-generator/SKILL.md +63 -14
  207. package/templates/qoder/skills/spec-coder/SKILL.md +22 -13
  208. package/templates/qoder/skills/spec-generator/SKILL.md +22 -10
  209. package/templates/qoder/skills/test-generator/SKILL.md +7 -5
  210. package/templates/qoder/skills/wl-code/SKILL.md +23 -11
  211. package/templates/qoder/skills/wl-commit/SKILL.md +9 -8
  212. package/templates/qoder/skills/wl-design/SKILL.md +5 -3
  213. package/templates/qoder/skills/wl-init/SKILL.md +6 -6
  214. package/templates/qoder/skills/wl-insight/SKILL.md +23 -10
  215. package/templates/qoder/skills/wl-prd-full/SKILL.md +57 -6
  216. package/templates/qoder/skills/wl-prd-quick/SKILL.md +3 -3
  217. package/templates/qoder/skills/wl-prd-review/SKILL.md +16 -4
  218. package/templates/qoder/skills/wl-report/SKILL.md +25 -23
  219. package/templates/qoder/skills/wl-search/SKILL.md +149 -78
  220. package/templates/qoder/skills/wl-spec/SKILL.md +7 -5
  221. package/templates/qoder/skills/wl-status/SKILL.md +48 -16
  222. package/templates/qoder/skills/wl-task/SKILL.md +92 -10
  223. package/templates/qoder/skills/wl-test/SKILL.md +75 -29
  224. package/templates/qoder/templates/prd-full-template.md +7 -0
  225. package/templates/root/AGENTS.md +275 -236
  226. package/templates/root/requirements.txt +3 -0
  227. package/templates/root//344/275/277/347/224/250/350/257/264/346/230/216.md +3 -3
  228. package/templates/root//346/226/260/346/211/213/346/214/207/345/215/227.md +2 -2
  229. package/templates/.qoder/.runtime/ctx-cache-5660152f1d6dd819.md +0 -23
  230. package/templates/.qoder/.runtime/ctx-cache-afdce0dac06b25b0.md +0 -23
  231. package/templates/.qoder/.runtime/search-cache-eae7644e7b122f35.txt +0 -1
  232. package/templates/.qoder/learning/eval-history.jsonl +0 -28
  233. package/templates/data/index/wiki-index.json +0 -8
  234. package/templates/qoder/nul +0 -4
  235. package/templates/qoder/scripts/kg/extract.py +0 -398
  236. package/templates/qoder/scripts/kg/kg_semantic.py +0 -156
  237. package/templates/qoder/scripts/mcp/kg_mcp_server.py +0 -606
  238. package/templates/qoder/scripts/mcp/mcp_launcher.py +0 -442
  239. package/templates/qoder/scripts/mcp/zentao_mcp_server.py +0 -384
  240. package/templates/qoder/scripts/task/__init__.py +0 -11
  241. package/templates/qoder/scripts/test/__init__.py +0 -11
  242. package/templates/qoder/scripts/wlkj.py +0 -116
  243. /package/templates/qoder/scripts/{common → deployment}/__init__.py +0 -0
  244. /package/templates/qoder/scripts/{common → domain/kg/graph}/graph_traverse.py +0 -0
  245. /package/templates/qoder/scripts/{common → domain/kg/server}/repowiki.py +0 -0
  246. /package/templates/qoder/scripts/{common → foundation/io}/atomicio.py +0 -0
  247. /package/templates/qoder/scripts/{test → validation/test}/secure-ls.js +0 -0
@@ -28,15 +28,30 @@ from pathlib import Path
28
28
  from typing import Any
29
29
 
30
30
  # 支持的 AI 平台及其会话环境变量
31
- _ENV_SESSION_KEYS: tuple[tuple[str, tuple[str, ...]], ...] = (
32
- ("qoder", ("QODER_SESSION_ID",)),
33
- ("codex", ("CODEX_SESSION_ID", "CODEX_THREAD_ID")),
34
- ("claude", ("CLAUDE_SESSION_ID", "CLAUDE_CODE_SESSION_ID")),
35
- ("cursor", ("CURSOR_SESSION_ID",)),
36
- ("opencode", ("OPENCODE_SESSION_ID", "OPENCODE_RUN_ID")),
37
- ("gemini", ("GEMINI_SESSION_ID",)),
38
- ("copilot", ("COPILOT_SESSION_ID",)),
39
- )
31
+ # -----------------------------------------------------------------------------
32
+ # 单一事实源: nexus.registry.HOST_TABLE(宿主能力抽象层)。
33
+ # 本表从 HOST_TABLE 派生,保持 tuple[tuple[str, tuple[str, ...]]] 格式兼容。
34
+ # 新增宿主只需改 nexus.HOST_TABLE 一处,active_task 自动跟随。
35
+ # nexus 不可用时回退到内联定义(向后兼容,零破坏)。
36
+ # -----------------------------------------------------------------------------
37
+ try:
38
+ from capability.registry import HOST_TABLE as _HOST_TABLE
39
+
40
+ _ENV_SESSION_KEYS: tuple[tuple[str, tuple[str, ...]], ...] = tuple(
41
+ (name, tuple(info.get("detect", ())))
42
+ for name, info in _HOST_TABLE.items()
43
+ if name != "cli" and info.get("detect")
44
+ )
45
+ except ImportError:
46
+ _ENV_SESSION_KEYS = (
47
+ ("qoderwork", ("QODER_SESSION_ID",)),
48
+ ("codex", ("CODEX_SESSION_ID", "CODEX_THREAD_ID")),
49
+ ("claude", ("CLAUDE_SESSION_ID", "CLAUDE_CODE_SESSION_ID")),
50
+ ("cursor", ("CURSOR_SESSION_ID",)),
51
+ ("opencode", ("OPENCODE_SESSION_ID", "OPENCODE_RUN_ID")),
52
+ ("gemini", ("GEMINI_SESSION_ID",)),
53
+ ("copilot", ("COPILOT_SESSION_ID",)),
54
+ )
40
55
 
41
56
 
42
57
  @dataclass(frozen=True)
@@ -127,7 +142,7 @@ def resolve_active_task(repo_root: Path | None = None) -> ActiveTask:
127
142
  ActiveTask 对象。
128
143
  """
129
144
  if repo_root is None:
130
- from common.paths import get_repo_root
145
+ from foundation.core.paths import get_repo_root
131
146
  repo_root = get_repo_root()
132
147
 
133
148
  # 1. 尝试会话级
@@ -147,7 +162,7 @@ def resolve_active_task(repo_root: Path | None = None) -> ActiveTask:
147
162
  pass
148
163
 
149
164
  # 2. 后备: 全局文件
150
- from common.paths import get_current_task
165
+ from foundation.core.paths import get_current_task
151
166
  global_task = get_current_task(repo_root)
152
167
  if global_task:
153
168
  return ActiveTask(
@@ -170,13 +185,13 @@ def set_active_task(task_path: str, repo_root: Path | None = None) -> ActiveTask
170
185
  ActiveTask 对象, 无会话身份则返回 None。
171
186
  """
172
187
  if repo_root is None:
173
- from common.paths import get_repo_root
188
+ from foundation.core.paths import get_repo_root
174
189
  repo_root = get_repo_root()
175
190
 
176
191
  session_key = resolve_context_key()
177
192
  if not session_key:
178
193
  # 无会话身份, 使用全局后备
179
- from common.paths import set_current_task
194
+ from foundation.core.paths import set_current_task
180
195
  set_current_task(task_path, repo_root)
181
196
  return ActiveTask(
182
197
  task_path=task_path,
@@ -208,7 +223,7 @@ def clear_active_task(repo_root: Path | None = None) -> ActiveTask:
208
223
  清除前的 ActiveTask 状态。
209
224
  """
210
225
  if repo_root is None:
211
- from common.paths import get_repo_root
226
+ from foundation.core.paths import get_repo_root
212
227
  repo_root = get_repo_root()
213
228
 
214
229
  # 获取清除前的状态
@@ -224,7 +239,7 @@ def clear_active_task(repo_root: Path | None = None) -> ActiveTask:
224
239
  pass
225
240
 
226
241
  # 也清除全局后备
227
- from common.paths import set_current_task
242
+ from foundation.core.paths import set_current_task
228
243
  set_current_task(None, repo_root)
229
244
 
230
245
  return active
@@ -64,14 +64,14 @@ def _load_config():
64
64
  config = {}
65
65
  try:
66
66
  # 优先: Config 单例 (mtime 缓存, 避免每次重读)
67
- from .config import Config, SECTIONS
67
+ from foundation.core.config import Config, SECTIONS
68
68
  config = Config.load().section(SECTIONS.FEISHU, {})
69
69
  except Exception:
70
70
  pass
71
71
  if not config:
72
72
  # 回退: 简单文本解析 (PyYAML 不可用时)
73
73
  try:
74
- from .paths import get_repo_root
74
+ from foundation.core.paths import get_repo_root
75
75
  cfg_path = os.path.join(str(get_repo_root()), '.qoder', 'config.yaml')
76
76
  in_feishu = False
77
77
  with open(cfg_path, encoding='utf-8') as f:
@@ -12,7 +12,7 @@ of these maps in other scripts - import from here.
12
12
  # 性能优化 A6: 从 40 词扩展到 ~120 词, 覆盖库存/采购/供应商/客户/财务/工单等通用业务域
13
13
  CN_MAP = {
14
14
  # 考勤/人事
15
- '考勤': 'attendance clock', '打卡': 'clock', '签到': 'sign',
15
+ '考勤': 'attendance', '打卡': 'clock', '签到': 'sign',
16
16
  '薪资': 'salary', '工资': 'salary', '请假': 'leave', '休假': 'leave',
17
17
  '加班': 'overtime', '员工': 'employee staff', '用户': 'user',
18
18
  '组织': 'org department', '部门': 'department', '岗位': 'position role',
@@ -108,8 +108,8 @@ CN_MAP = {
108
108
  # so building and querying use the same semantics.
109
109
  BUSINESS_PATH_MAP = {
110
110
  '保险': ['insurance', 'Insurance', 'ins-', '/ins/', 'vehlife/insurance'],
111
- '考勤': ['attendance', 'Attendance', 'clock', 'Clock', 'time/clock', 'TimeClock'],
112
- '打卡': ['clock', 'Clock', 'photoClock', 'PhotoClock'],
111
+ '考勤': ['attendance', 'Attendance', 'Attend', 'timeManage', 'TimeWork'],
112
+ '打卡': ['clock', 'Clock', 'photoClock', 'PhotoClock', 'time/clock', 'TimeClock'],
113
113
  '车辆': ['vehicle', 'Vehicle', 'veh', 'vehlife', '/veh/'],
114
114
  '品质': ['quality', 'Quality', 'QualityInspection', 'patrol', 'Patrol'],
115
115
  '巡查': ['inspection', 'Inspection', 'patrol', 'Patrol'],
@@ -0,0 +1,220 @@
1
+ # -*- coding: utf-8 -*-
2
+ """
3
+ zentao_client.py —— 禅道 HTTP 客户端(唯一真相源)。
4
+
5
+ 消除重复: zentao_sync.py (CLI 批量同步) 和 zentao_mcp_server.py (MCP server)
6
+ 原本各自实现了一份禅道 HTTP 层(内网探测/登录/token缓存/API/401重登),
7
+ 现统一收敛到本类。
8
+
9
+ 设计:
10
+ - ZentaoClient(url, user, password) —— 显式构造
11
+ - ZentaoClient.from_env() —— 从 ZENTAO_URL/USER/PASSWORD 环境变量 (mcp_launcher 注入)
12
+ - ZentaoClient.from_config() —— 从 .qoder/config.yaml 的 zentao 段 (CLI 用)
13
+ - 凭据不全时工厂返回 None(调用方自行降级/报错)
14
+
15
+ client.check_intranet() -> (ok, msg) 带缓存,避免连续 ping
16
+ client.api(method, path, data) -> Response 内含 401 自动重登
17
+
18
+ 与 nexus 的关系:
19
+ 禅道 MCP server (zentao_mcp_server.py) 是 nexus L1 层的 MCP 资产。
20
+ 本 client 是其 HTTP 底座;未来 McpCap 实现后,工作流统一走
21
+ nexus.resolve().mcp.call("list_execution_tasks"),本 client 仍是底层实现。
22
+ """
23
+ from __future__ import annotations
24
+
25
+ import os
26
+ import socket
27
+ from typing import Optional
28
+
29
+
30
+ class ZentaoClient:
31
+ """禅道 REST API 客户端(登录/token/401重登/内网探测)。
32
+
33
+ Attributes:
34
+ url: 禅道根地址(构造时已去尾斜杠)。
35
+ user/password: 账号密码。
36
+ """
37
+
38
+ def __init__(self, url: str, user: str, password: str, intranet_ttl: int = 60):
39
+ self.url = (url or '').rstrip('/')
40
+ self.user = (user or '').strip()
41
+ self.password = password or ''
42
+ self._token: Optional[str] = None
43
+ # 内网可达性缓存 [timestamp, ok, msg],list 原地写避免 global 声明
44
+ self._intranet_cache: list = []
45
+ self._intranet_ttl = intranet_ttl
46
+
47
+ # ------------------------------------------------------------------
48
+ # 工厂
49
+ # ------------------------------------------------------------------
50
+
51
+ @classmethod
52
+ def from_env(cls) -> Optional['ZentaoClient']:
53
+ """从环境变量构造(mcp_launcher 注入)。凭据不全返回 None。"""
54
+ url = os.environ.get('ZENTAO_URL', '').strip()
55
+ user = os.environ.get('ZENTAO_USER', '').strip()
56
+ pwd = os.environ.get('ZENTAO_PASSWORD', '')
57
+ if not url or not user or not pwd:
58
+ return None
59
+ return cls(url, user, pwd)
60
+
61
+ @classmethod
62
+ def from_config(cls) -> Optional['ZentaoClient']:
63
+ """从 .qoder/config.yaml 的 zentao 段构造(CLI 用)。凭据不全返回 None。"""
64
+ try:
65
+ from foundation.core.config import load_config_section, SECTIONS
66
+ cfg = load_config_section(SECTIONS.ZENTAO)
67
+ except Exception:
68
+ return None
69
+ url = (cfg.get('url') or '').strip()
70
+ user = (cfg.get('user') or '').strip()
71
+ pwd = (cfg.get('password') or '')
72
+ if not url or not user or not pwd:
73
+ return None
74
+ return cls(url, user, pwd)
75
+
76
+ # ------------------------------------------------------------------
77
+ # 校验
78
+ # ------------------------------------------------------------------
79
+
80
+ def credentials_ok(self) -> tuple[bool, str]:
81
+ """凭据完整性校验。返回 (ok, msg)。"""
82
+ missing = []
83
+ if not self.url:
84
+ missing.append('url')
85
+ if not self.user:
86
+ missing.append('user')
87
+ if not self.password:
88
+ missing.append('password')
89
+ if missing:
90
+ return False, '禅道凭据未配置 (缺 %s)。请在 .qoder/config.yaml 的 zentao: 段填写。' % '/'.join(missing)
91
+ return True, ''
92
+
93
+ # ------------------------------------------------------------------
94
+ # 内网探测
95
+ # ------------------------------------------------------------------
96
+
97
+ def _ping_target(self) -> tuple[str, int]:
98
+ """从 url 解析 (host, port)。"""
99
+ host = self.url.replace('http://', '').replace('https://', '').split('/')[0]
100
+ try:
101
+ port = 80
102
+ if self.url.count(':') >= 2:
103
+ port = int(self.url.split(':')[2].split('/')[0])
104
+ except (ValueError, IndexError):
105
+ port = 80
106
+ return host, port
107
+
108
+ def check_intranet(self) -> tuple[bool, str]:
109
+ """内网连通性(带 TTL 缓存,失败不缓存)。
110
+
111
+ 多工具连续调用时只 ping 一次。失败不缓存,便于网络恢复后立即重试。
112
+ """
113
+ import time
114
+ now = time.time()
115
+ cached = self._intranet_cache
116
+ if cached and now - cached[0] < self._intranet_ttl and cached[1]:
117
+ return cached[1], cached[2]
118
+ host, port = self._ping_target()
119
+ try:
120
+ with socket.create_connection((host, port), timeout=3):
121
+ self._intranet_cache[:] = [now, True, '']
122
+ return True, ''
123
+ except Exception as e:
124
+ return False, '无法连接禅道 (%s): %s。请确认已连内网。' % (self.url, str(e)[:60])
125
+
126
+ # ------------------------------------------------------------------
127
+ # HTTP
128
+ # ------------------------------------------------------------------
129
+
130
+ @staticmethod
131
+ def _ensure_requests():
132
+ """延迟导入 requests(未装时报清晰错误,而非启动即崩)。"""
133
+ try:
134
+ import requests
135
+ return requests
136
+ except ImportError:
137
+ raise RuntimeError('缺少 requests 库。请运行: pip install requests')
138
+
139
+ def login(self) -> str:
140
+ """登录拿 token(进程内缓存)。"""
141
+ if self._token:
142
+ return self._token
143
+ requests = self._ensure_requests()
144
+ r = requests.post(self.url + '/api.php/v1/tokens',
145
+ json={'account': self.user, 'password': self.password},
146
+ timeout=10)
147
+ if r.status_code in (200, 201):
148
+ self._token = r.json().get('token')
149
+ return self._token
150
+ raise RuntimeError('禅道登录失败: %s %s' % (r.status_code, r.text[:100]))
151
+
152
+ def api(self, method: str, path: str, data: dict | None = None):
153
+ """调禅道 REST API。401 自动重登一次。
154
+
155
+ Args:
156
+ method: GET/POST/PUT/DELETE
157
+ path: /api.php/v1 之后的路径(如 /executions/12/tasks)
158
+ data: GET 时作 query params,POST/PUT 时作 json body
159
+ Returns:
160
+ requests.Response
161
+ """
162
+ requests = self._ensure_requests()
163
+ token = self.login()
164
+ full = self.url + '/api.php/v1' + path
165
+ kwargs = dict(
166
+ headers={'Token': token},
167
+ json=data if method in ('POST', 'PUT') else None,
168
+ params=data if method == 'GET' else None,
169
+ timeout=15,
170
+ )
171
+ r = requests.request(method, full, **kwargs)
172
+ if r.status_code == 401:
173
+ self._token = None
174
+ token = self.login()
175
+ kwargs['headers'] = {'Token': token}
176
+ r = requests.request(method, full, **kwargs)
177
+ return r
178
+
179
+ def upload_file(self, file_path: str, title: str = '', extra_fields: dict | None = None):
180
+ """上传文件到禅道 (multipart/form-data, POST /api.php/v1/files)。
181
+
182
+ 禅道文件接口需 multipart 而非 JSON, api() 不支持, 故单独实现。
183
+ 用于: 上传原型图 HTML / PRD 附件到需求。
184
+
185
+ Args:
186
+ file_path: 本地文件绝对路径
187
+ title: 文件标题 (可选, 默认取文件名)
188
+ extra_fields: 额外表单字段 (如 {'objectType':'story','objectID':812} 关联到对象)
189
+ Returns:
190
+ requests.Response
191
+ """
192
+ import os
193
+ requests = self._ensure_requests()
194
+ token = self.login()
195
+ full = self.url + '/api.php/v1/files'
196
+ fname = os.path.basename(file_path)
197
+ title = title or fname
198
+ fields = {'title': title}
199
+ if extra_fields:
200
+ fields.update(extra_fields)
201
+ with open(file_path, 'rb') as f:
202
+ files = {'file': (fname, f, 'application/octet-stream')}
203
+ r = requests.post(full, headers={'Token': token},
204
+ data=fields, files=files, timeout=30)
205
+ if r.status_code == 401:
206
+ self._token = None
207
+ token = self.login()
208
+ with open(file_path, 'rb') as f:
209
+ files = {'file': (fname, f, 'application/octet-stream')}
210
+ r = requests.post(full, headers={'Token': token},
211
+ data=fields, files=files, timeout=30)
212
+ return r
213
+
214
+ @staticmethod
215
+ def err_text(r) -> str:
216
+ """从 response 提取错误文本(容错)。"""
217
+ try:
218
+ return r.json().get('error', r.text[:120])
219
+ except Exception:
220
+ return r.text[:120]
@@ -6,7 +6,7 @@ filelock.py - 跨平台原子文件锁
6
6
  Windows / macOS / Linux 均无需管理员权限。
7
7
 
8
8
  用法:
9
- from common.filelock import FileLock
9
+ from foundation.io.filelock import FileLock
10
10
  with FileLock('.qoder/.runtime/team-sync.lock', timeout=30):
11
11
  # 临界区:同一时刻只有一个进程能进入
12
12
  do_critical_work()
@@ -36,7 +36,7 @@ if _THIS_DIR not in sys.path:
36
36
  try:
37
37
  from .atomicio import atomic_write_json, safe_read_json
38
38
  from .filelock import FileLock, LockTimeoutError
39
- from .paths import get_repo_root
39
+ from foundation.core.paths import get_repo_root
40
40
  except ImportError:
41
41
  # 被当作脚本直接运行时
42
42
  from atomicio import atomic_write_json, safe_read_json
@@ -0,0 +1,39 @@
1
+ # -*- coding: utf-8 -*-
2
+ """
3
+ duckdb_conn —— DuckDB 连接基础设施(L0 基础设施层)。
4
+
5
+ 设计模式: Utility(纯连接管理,无领域语义)
6
+
7
+ 职责:打开/关闭 kg.duckdb 连接。不知道表结构,不知道业务逻辑。
8
+ 被 L2(nexus)和 L3(kg/learning)共用,避免 L2 反向依赖 L3。
9
+
10
+ init_schema 等领域 DDL 留在各域(kg_duckdb.py 定义图谱表,
11
+ nexus/adapters/mcp.py 定义自己的 learning_events 表)。
12
+ """
13
+ import os
14
+
15
+ from foundation.core.paths import DATA_INDEX_DIR
16
+
17
+ DB_PATH = str(DATA_INDEX_DIR / 'kg.duckdb')
18
+
19
+
20
+ def get_db():
21
+ """Open DuckDB read-write connection (creates file if not exists)."""
22
+ os.makedirs(os.path.dirname(DB_PATH), exist_ok=True)
23
+ import duckdb
24
+ return duckdb.connect(DB_PATH)
25
+
26
+
27
+ def get_db_readonly():
28
+ """Open read-only DuckDB connection. Returns None if file missing.
29
+
30
+ DuckDB 不允许对同一文件混用 read_only 和 read_write 连接,
31
+ 所以只读路径必须全程用 read_only。
32
+ """
33
+ import duckdb
34
+ if not os.path.isfile(DB_PATH):
35
+ return None
36
+ try:
37
+ return duckdb.connect(DB_PATH, read_only=True)
38
+ except Exception:
39
+ return None
@@ -21,7 +21,7 @@ mcp_base.py - MCP Server 基类 (Template Method 模式)
21
21
  - on_tool_error(e) -> (text, is_error): 自定义异常分类 (默认通用)
22
22
 
23
23
  Usage (子类示例):
24
- from common.mcp_base import BaseMCPServer
24
+ from foundation.protocol.mcp_base import BaseMCPServer
25
25
 
26
26
  class ZentaoMCPServer(BaseMCPServer):
27
27
  SERVER_NAME = "qoder-zentao"
@@ -26,6 +26,7 @@ eval_prd.py 的 main() 也改为调用本模块。
26
26
 
27
27
  import os
28
28
  import sys
29
+ import re
29
30
 
30
31
  _THIS_DIR = os.path.dirname(os.path.abspath(__file__))
31
32
  _SCRIPTS_DIR = os.path.dirname(_THIS_DIR)
@@ -33,7 +34,7 @@ if _SCRIPTS_DIR not in sys.path:
33
34
  sys.path.insert(0, _SCRIPTS_DIR)
34
35
 
35
36
  # 复用 eval_prd.py 的评分函数 (不重复实现)
36
- from eval_prd import (
37
+ from validation.metrics.eval_prd import (
37
38
  score_reality, score_style, score_template, read_text,
38
39
  FULL_SECTIONS, QUICK_SECTIONS,
39
40
  )
@@ -75,6 +76,16 @@ def evaluate(prd_path, html_path=None):
75
76
  try:
76
77
  r2, msg2, miss2 = score_style(read_text(html_path))
77
78
  a2 = round(r2 * 30)
79
+ # v3.1 来源锚定检查: HTML 必须含 @prototype-source 或 x-source-pages 注释
80
+ # 没有 = AI 没做 Step 2 真实页面锚定, 直接从模板手抄 → 扣 5 分
81
+ html_text = read_text(html_path)
82
+ has_source = bool(re.search(r'@prototype-source:|x-source-pages', html_text))
83
+ if not has_source:
84
+ a2 = max(0, a2 - 5)
85
+ msg2 = (msg2 or '') + '; 缺来源锚定(@prototype-source 注释), 扣 5 分'
86
+ if miss2 is None:
87
+ miss2 = []
88
+ miss2 = list(miss2) + ['缺 @prototype-source 注释']
78
89
  except Exception as e:
79
90
  r2, msg2, miss2 = 0.0, "评估异常: %s" % e, []
80
91
  a2 = 0
@@ -35,17 +35,34 @@ def _run_pip(args, index_url=None, timeout=MIRROR_TIMEOUT):
35
35
  """跑一次 pip, 返回 (returncode, stdout, stderr)。
36
36
  index_url 非 None 时加 -i 指定镜像源。
37
37
  用 trusted-host 避免某些镜像的 SSL 证书警告阻断。
38
+
39
+ ⚠ 不用 subprocess.run / communicate:
40
+ Anaconda Python 3.9 + Windows 下, subprocess 的 communicate() 内部 join 读子进程
41
+ 输出的线程时, 会触发 "PyEval_SaveThread: GIL released" 致命崩溃
42
+ (pip 跑 tree-sitter/playwright/ddddocr 这类大包时尤其稳定复现)。
43
+ 50 人不同环境都要跑这步, 必须跨版本稳。
44
+
45
+ 解法: Popen + wait(), stdout/stderr 全丢 DEVNULL。
46
+ pip 的啰嗦输出对我们无用 (成功与否以 _verify_packages 的 import 验证为准),
47
+ 丢了反而避免大输出撑爆管道 + 触发 GIL 崩溃。超时用独立的计时线程判, 不走 communicate。
38
48
  """
39
- cmd = [sys.executable, '-m', 'pip', 'install'] + args
49
+ cmd = [sys.executable, '-m', 'pip', 'install', '--no-input',
50
+ '--progress-bar', 'off'] + args
40
51
  if index_url:
41
52
  cmd += ['-i', index_url,
42
53
  '--trusted-host', _host_of(index_url)]
43
54
  try:
44
- r = subprocess.run(cmd, capture_output=True, text=True,
45
- encoding='utf-8', errors='replace', timeout=timeout)
46
- return r.returncode, r.stdout or '', r.stderr or ''
47
- except subprocess.TimeoutExpired:
48
- return 1, '', '安装超时 (%ds)' % timeout
55
+ proc = subprocess.Popen(cmd, stdout=subprocess.DEVNULL,
56
+ stderr=subprocess.DEVNULL)
57
+ try:
58
+ rc = proc.wait(timeout=timeout)
59
+ return rc, '', ''
60
+ except subprocess.TimeoutExpired:
61
+ try:
62
+ proc.kill()
63
+ except Exception:
64
+ pass
65
+ return 1, '', '安装超时 (%ds)' % timeout
49
66
  except Exception as e:
50
67
  return 1, '', str(e)
51
68
 
@@ -26,7 +26,7 @@ def _platform_explicitly_passed(argv=None):
26
26
  return '--platform' in argv
27
27
 
28
28
 
29
- def assert_platform_selected(skill='wl-prd-full', argv=None):
29
+ def assert_platform_selected(skill='wl-prd', argv=None):
30
30
  """硬墙: 没显式传 --platform 就调用 PRD 链路脚本 = 违规。
31
31
 
32
32
  1. best-effort 上报 rule_violation 事件 (不阻塞, 失败静默)