@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,145 @@
1
+ # -*- coding: utf-8 -*-
2
+ """
3
+ bootstrap.py - 统一 CLI 启动样板 (DRY) + 子目录化路径自举 (v3.0)
4
+
5
+ 治"70处 sys.path + 50处 reconfigure" + 子目录化后的路径定位。
6
+
7
+ 子目录化后, 脚本在 scripts/domain/kg/kg.py, 它的 dirname(__file__)=scripts/domain/kg/,
8
+ 直接 sys.path.insert(scripts/domain/kg/) 后 `from foundation.x` 会断 (foundation 在 scripts/foundation/)。
9
+
10
+ 本模块的 setup() 解决: 无论脚本在 scripts/ 根还是 scripts/某子包/,
11
+ 都能正确定位 scripts/ 根并注入, 让 `from foundation.x` 和跨包 import 都能用。
12
+
13
+ 核心: _find_scripts_root() 向上找含 'foundation/' 子目录的那个目录 = scripts/ 根。
14
+
15
+ Usage (两种姿势):
16
+ # 姿势1: 子包脚本开头 (推荐)
17
+ import os, sys
18
+ _S = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) # 子包→scripts
19
+ if _S not in sys.path: sys.path.insert(0, _S)
20
+ from bootstrap import setup; setup()
21
+
22
+ # 姿势2: scripts/ 根脚本 (老姿势仍可用)
23
+ from bootstrap import setup; setup()
24
+ """
25
+
26
+ import os
27
+ import sys
28
+
29
+ __all__ = ["setup", "SCRIPTS_DIR", "REPO_ROOT", "find_scripts_root", "ensure_scripts_path"]
30
+
31
+
32
+ def find_scripts_root(start=None):
33
+ """定位 .qoder/scripts/ 根目录。
34
+
35
+ 策略: 从 start (默认 __file__ 的目录) 向上找, 直到某目录含 'common/' 子目录
36
+ 且该 common/ 下有 bootstrap.py (即本文件) → 那就是 scripts/ 根。
37
+ 兜底: 找含 '.qoder' 的目录 + '/scripts'。
38
+ """
39
+ start = start or os.path.dirname(os.path.abspath(__file__))
40
+ cur = start
41
+ for _ in range(10):
42
+ # scripts/ 根的标志: 含 common/bootstrap.py
43
+ if os.path.isfile(os.path.join(cur, "foundation", "bootstrap.py")):
44
+ return cur
45
+ parent = os.path.dirname(cur)
46
+ if parent == cur:
47
+ break
48
+ cur = parent
49
+ # 兜底: cwd 向上找 .qoder/scripts
50
+ cwd = os.getcwd()
51
+ for _ in range(10):
52
+ cand = os.path.join(cwd, ".qoder", "scripts")
53
+ if os.path.isdir(cand):
54
+ return cand
55
+ parent = os.path.dirname(cwd)
56
+ if parent == cwd:
57
+ break
58
+ cwd = parent
59
+ return start
60
+
61
+
62
+ def ensure_scripts_path(start=None):
63
+ """确保 scripts/ 根在 sys.path 最前。幂等。
64
+
65
+ 子包脚本调用此函数后, `from foundation.x` 即可用。
66
+ """
67
+ root = find_scripts_root(start)
68
+ if root not in sys.path:
69
+ sys.path.insert(0, root)
70
+ return root
71
+
72
+
73
+ def _find_scripts_dir() -> str:
74
+ """[内部] 定位 scripts/ 根 (兼容旧名)。"""
75
+ return find_scripts_root()
76
+
77
+
78
+ def ensure_utf8_stdio():
79
+ """UTF-8 stdio 配置 (v3.0: 从 utf8.py 并入)。
80
+
81
+ 把 stdout/stderr 切到 UTF-8, 失败则忽略 (不崩溃)。
82
+ 幂等: 多次调用无害。
83
+ """
84
+ for stream_name in ("stdout", "stderr"):
85
+ stream = getattr(sys, stream_name, None)
86
+ if stream is None:
87
+ continue
88
+ reconfigure = getattr(stream, "reconfigure", None)
89
+ if reconfigure is None:
90
+ continue
91
+ try:
92
+ reconfigure(encoding="utf-8", errors="replace")
93
+ except (TypeError, ValueError, OSError, IOError):
94
+ try:
95
+ reconfigure(encoding="utf-8")
96
+ except Exception:
97
+ pass
98
+
99
+
100
+ def setup():
101
+ """统一 CLI 启动初始化。幂等。
102
+
103
+ 做: ① ensure_utf8_stdio ② scripts/ 根注入 sys.path ③ scripts/common/ 注入
104
+ Returns: (SCRIPTS_DIR, REPO_ROOT)
105
+ """
106
+ # 先确保 scripts/ 根在 path (本文件可能在 common/ 或被引用)
107
+ scripts_dir = ensure_scripts_root = find_scripts_root()
108
+ if scripts_dir not in sys.path:
109
+ sys.path.insert(0, scripts_dir)
110
+ common_dir = os.path.join(scripts_dir, "foundation")
111
+ if os.path.isdir(common_dir) and common_dir not in sys.path:
112
+ sys.path.insert(0, common_dir)
113
+ # 注入所有子包目录 (kg/build/mcp/test/setup/carriers/task/search/report)
114
+ # 让跨包 import (旧式不带前缀, 如 import search_index) 仍可用
115
+ try:
116
+ for _name in os.listdir(scripts_dir):
117
+ _subdir = os.path.join(scripts_dir, _name)
118
+ if os.path.isdir(_subdir) and _subdir not in sys.path:
119
+ sys.path.insert(0, _subdir)
120
+ except OSError:
121
+ pass
122
+
123
+ # 1. UTF-8 stdio (v3.0: utf8.py 已并入 bootstrap, 内联实现)
124
+ try:
125
+ ensure_utf8_stdio()
126
+ except Exception:
127
+ for sname in ("stdout", "stderr"):
128
+ s = getattr(sys, sname, None)
129
+ rc = getattr(s, "reconfigure", None) if s else None
130
+ if rc:
131
+ try:
132
+ rc(encoding="utf-8", errors="replace")
133
+ except Exception:
134
+ try:
135
+ rc(encoding="utf-8")
136
+ except Exception:
137
+ pass
138
+
139
+ repo_root = os.path.dirname(os.path.dirname(scripts_dir))
140
+ return scripts_dir, repo_root
141
+
142
+
143
+ # 模块级常量
144
+ SCRIPTS_DIR = find_scripts_root()
145
+ REPO_ROOT = os.path.dirname(os.path.dirname(os.path.dirname(os.path.dirname(SCRIPTS_DIR))))
@@ -0,0 +1,112 @@
1
+ # -*- coding: utf-8 -*-
2
+ """
3
+ cmd_registry.py - 命令注册器 (Command Pattern, 治 argparse 样板重复)
4
+
5
+ 现状: task.py 14 子命令(81行样板) + autotest.py 12 子命令(105行样板),
6
+ 每个子命令都要 add_parser + add_argument×N + set_defaults(func=) 三行,
7
+ 新增命令要改 3 处 (函数定义 + add_parser + set_defaults), 容易漏。
8
+
9
+ 本模块引入声明式注册: 用 (name, help, args_spec, func) 元组表 + build_parser
10
+ 一次性构建 subparser, 消除样板。
11
+
12
+ args_spec 格式 (简化 argparse.add_argument):
13
+ [
14
+ ('title', {...}), # 位置参数
15
+ ('--slug', {'help': 'slug'}), # 可选参数
16
+ ('--pri', {'choices':[1,2,3,4], 'type':int, 'default':3}),
17
+ ]
18
+
19
+ Usage:
20
+ from foundation.core.cmd_registry import build_parser
21
+
22
+ COMMANDS = [
23
+ ('create', '建任务', [
24
+ ('title', {'help': '任务标题'}),
25
+ ('--slug', {'help': 'URL slug'}),
26
+ ], cmd_create),
27
+ ('list', '列任务', [], cmd_list),
28
+ ]
29
+ parser = build_parser('task', '任务系统', COMMANDS)
30
+ args = parser.parse_args()
31
+ args.func(args) # set_defaults 已自动绑定
32
+
33
+ 收益: task/autotest 各省 ~60-80 行样板, 新增命令从改 3 处降到改 1 处元组行。
34
+ """
35
+
36
+ import argparse
37
+ import sys
38
+ from typing import Any, Callable, Dict, List, Sequence, Tuple
39
+
40
+ __all__ = ["build_parser", "run_command"]
41
+
42
+ # 类型别名: (name, help, args_spec, handler)
43
+ CommandSpec = Tuple[str, str, List[Tuple[str, Dict[str, Any]]], Callable]
44
+
45
+
46
+ def _add_args(subparser: argparse.ArgumentParser, args_spec: List[Tuple[str, Dict[str, Any]]]) -> None:
47
+ """把 args_spec 转成 subparser.add_argument 调用。"""
48
+ for name, opts in args_spec:
49
+ # 拆出 positional vs optional 的特殊处理
50
+ kwargs = dict(opts) if opts else {}
51
+ try:
52
+ subparser.add_argument(name, **kwargs)
53
+ except (TypeError, ValueError):
54
+ # 容错: 个别参数键不兼容时降级
55
+ subparser.add_argument(name)
56
+
57
+
58
+ def build_parser(
59
+ prog: str,
60
+ description: str,
61
+ commands: Sequence[CommandSpec],
62
+ *,
63
+ common_args: List[Tuple[str, Dict[str, Any]]] = None,
64
+ ) -> argparse.ArgumentParser:
65
+ """从命令元组表构建 argparse parser。
66
+
67
+ Args:
68
+ prog: 程序名 (如 'task')
69
+ description: 顶部描述 (如 '任务系统')
70
+ commands: [(name, help, args_spec, handler), ...] 命令元组表
71
+ common_args: 所有子命令共享的可选参数 (如 --platform)
72
+
73
+ Returns:
74
+ 配置好的 ArgumentParser, parse_args 后 args.func 即处理函数。
75
+ """
76
+ parser = argparse.ArgumentParser(prog=prog, description=description)
77
+ sub = parser.add_subparsers(dest="command")
78
+
79
+ for name, help_text, args_spec, handler in commands:
80
+ sp = sub.add_parser(name, help=help_text, description=help_text)
81
+ _add_args(sp, args_spec)
82
+ # 共享参数
83
+ if common_args:
84
+ _add_args(sp, common_args)
85
+ sp.set_defaults(func=handler)
86
+
87
+ return parser
88
+
89
+
90
+ def run_command(
91
+ commands: Sequence[CommandSpec],
92
+ argv: List[str] = None,
93
+ *,
94
+ prog: str = "",
95
+ description: str = "",
96
+ common_args: List[Tuple[str, Dict[str, Any]]] = None,
97
+ ) -> int:
98
+ """便捷运行: 构建 parser + parse + 调用 func。
99
+
100
+ Returns:
101
+ 处理函数的返回值 (通常是 int exit code), 无命令时返回 0。
102
+ """
103
+ parser = build_parser(prog or "cmd", description, commands, common_args=common_args)
104
+ args = parser.parse_args(argv if argv is not None else sys.argv[1:])
105
+ if not getattr(args, "command", None):
106
+ parser.print_help()
107
+ return 0
108
+ func = getattr(args, "func", None)
109
+ if func is None:
110
+ return 1
111
+ ret = func(args)
112
+ return int(ret) if isinstance(ret, int) else 0
@@ -0,0 +1,187 @@
1
+ # -*- coding: utf-8 -*-
2
+ """
3
+ config.py - Config 单例 (Singleton 模式, 治"4份 load_config + 明文凭据")
4
+
5
+ 现状: config.yaml 被至少 4 处各自重读 (autotest/git_sync/init_doctor/feishu),
6
+ 每次都 open + yaml.safe_load, 无缓存, 且段名用裸字符串散落各处 (拼写风险)。
7
+
8
+ 本模块引入单例 Config:
9
+ - 单次 yaml.safe_load + mtime 缓存 (config 改了自动重读)
10
+ - 14 段名抽常量 (Config.SECTIONS), 消拼写风险
11
+ - 类型化访问: Config.load().feishu / .autotest / .mysql ...
12
+ - 凭据从代码移除: mysql/zentao 密码只在 config.yaml, 不在源码默认值
13
+
14
+ Usage:
15
+ from foundation.core.config import Config, SECTIONS
16
+ cfg = Config.load() # 单例, 全进程共享
17
+ feishu = cfg.section('feishu') # {'enabled': false, ...}
18
+ mysql = cfg.mysql # 类型化便捷访问
19
+ if not feishu.get('enabled'): ...
20
+
21
+ 向后兼容: load_config() / load_config_section() 旧函数保留, 转发到 Config。
22
+ """
23
+
24
+ import os
25
+ import sys
26
+ import threading
27
+ from typing import Any, Dict, Optional
28
+
29
+ __all__ = ["Config", "SECTIONS", "load_config", "load_config_section"]
30
+
31
+ # 相对导入
32
+ try:
33
+ from .paths import get_repo_root
34
+ except ImportError:
35
+ _COMMON_DIR = os.path.dirname(os.path.abspath(__file__))
36
+ if _COMMON_DIR not in sys.path:
37
+ sys.path.insert(0, _COMMON_DIR)
38
+ from paths import get_repo_root # type: ignore
39
+
40
+
41
+ # ============================================================
42
+ # 14 个顶层段名常量 (单一真源, 消拼写风险)
43
+ # ============================================================
44
+
45
+ class SECTIONS:
46
+ """config.yaml 顶层段名。调用方用 SECTIONS.FEISHU 而非裸字符串 'feishu'。"""
47
+ ROLES = "roles"
48
+ PIPELINE = "pipeline"
49
+ MYSQL = "mysql"
50
+ ZENTAO = "zentao"
51
+ PLATFORMS = "platforms"
52
+ AUTOTEST = "autotest"
53
+ TEAM_REMOTE = "team_remote"
54
+ GIT_SYNC = "git_sync"
55
+ PRD_MODES = "prd_modes"
56
+ TASK = "task"
57
+ STATUS = "status"
58
+ INSIGHT = "insight"
59
+ FEISHU = "feishu"
60
+ NOTIFICATION = "notification"
61
+
62
+
63
+ # ============================================================
64
+ # Config 单例
65
+ # ============================================================
66
+
67
+ class _ConfigData:
68
+ """config.yaml 的类型化包装。由 Config.load() 返回实例。"""
69
+
70
+ def __init__(self, raw: Dict[str, Any]):
71
+ self._raw = raw or {}
72
+
73
+ def section(self, name: str, default: Optional[dict] = None) -> dict:
74
+ """取某段 (顶层 key)。不存在返回 default (默认 {})。"""
75
+ val = self._raw.get(name, {} if default is None else default)
76
+ return val if isinstance(val, dict) else ({} if default is None else default)
77
+
78
+ def get(self, name: str, default: Any = None) -> Any:
79
+ """取任意顶层 key (非段, 如标量)。"""
80
+ return self._raw.get(name, default)
81
+
82
+ @property
83
+ def raw(self) -> Dict[str, Any]:
84
+ """原始整份 dict (兼容旧 load_config() 返回)。"""
85
+ return self._raw
86
+
87
+ # ---- 类型化便捷访问 (常用段) ----
88
+ @property
89
+ def roles(self) -> dict:
90
+ return self.section(SECTIONS.ROLES)
91
+
92
+ @property
93
+ def mysql(self) -> dict:
94
+ return self.section(SECTIONS.MYSQL)
95
+
96
+ @property
97
+ def zentao(self) -> dict:
98
+ return self.section(SECTIONS.ZENTAO)
99
+
100
+ @property
101
+ def autotest(self) -> dict:
102
+ return self.section(SECTIONS.AUTOTEST)
103
+
104
+ @property
105
+ def platforms(self) -> dict:
106
+ return self.section(SECTIONS.PLATFORMS)
107
+
108
+ @property
109
+ def git_sync(self) -> dict:
110
+ return self.section(SECTIONS.GIT_SYNC)
111
+
112
+ @property
113
+ def feishu(self) -> dict:
114
+ return self.section(SECTIONS.FEISHU)
115
+
116
+ @property
117
+ def team_remote(self) -> dict:
118
+ return self.section(SECTIONS.TEAM_REMOTE)
119
+
120
+
121
+ class Config:
122
+ """config.yaml 单例加载器 (mtime 缓存)。
123
+
124
+ - 首次 load() 读文件 + yaml.safe_load, 缓存结果 + 文件 mtime
125
+ - 后续 load() 检查 mtime, 变了才重读 (热重载, 无需重启)
126
+ - config.yaml 不存在 → 返回空 ConfigData (软降级, 不崩)
127
+ - 线程安全 (Lock 保护首次加载)
128
+ """
129
+
130
+ _instance: Optional[_ConfigData] = None
131
+ _mtime: Optional[float] = None
132
+ _path: Optional[str] = None
133
+ _lock = threading.Lock()
134
+
135
+ @classmethod
136
+ def _config_path(cls) -> str:
137
+ if cls._path is None:
138
+ cls._path = str(get_repo_root() / ".qoder" / "config.yaml")
139
+ return cls._path
140
+
141
+ @classmethod
142
+ def load(cls) -> _ConfigData:
143
+ """加载/返回缓存的 config。mtime 变化时自动重读。"""
144
+ path = cls._config_path()
145
+ try:
146
+ cur_mtime = os.path.getmtime(path)
147
+ except OSError:
148
+ # 文件不存在 → 空配置 (软降级)
149
+ return _ConfigData({})
150
+ with cls._lock:
151
+ if cls._instance is not None and cls._mtime == cur_mtime:
152
+ return cls._instance
153
+ # (重)读
154
+ try:
155
+ import yaml
156
+ with open(path, encoding="utf-8") as f:
157
+ raw = yaml.safe_load(f) or {}
158
+ except Exception:
159
+ raw = {}
160
+ cls._instance = _ConfigData(raw)
161
+ cls._mtime = cur_mtime
162
+ return cls._instance
163
+
164
+ @classmethod
165
+ def reload(cls) -> _ConfigData:
166
+ """强制重读 (忽略缓存)。测试/配置热更新用。"""
167
+ cls._mtime = None
168
+ return cls.load()
169
+
170
+
171
+ # ============================================================
172
+ # 向后兼容: 旧函数签名转发到 Config 单例
173
+ # ============================================================
174
+
175
+ def load_config() -> dict:
176
+ """[兼容] 返回整份 config dict (替代 git_sync/init_doctor 各自的 load_config)。"""
177
+ return Config.load().raw
178
+
179
+
180
+ def load_config_section(section: str = SECTIONS.AUTOTEST, default: Optional[dict] = None) -> dict:
181
+ """[兼容] 返回某段 dict (替代 autotest/feishu 各自的 load_config_section/_load_config)。
182
+
183
+ Args:
184
+ section: 段名 (用 SECTIONS.XXX 常量, 不要裸字符串)
185
+ default: 段不存在时的返回值 (默认 {})
186
+ """
187
+ return Config.load().section(section, default if default is not None else {})