@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,228 @@
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+ """req.py - 需求生命周期管理 (/wl-req 命令)
4
+
5
+ M3 workspace 重构的核心。解决"找不到产出"痛点:
6
+ 一个需求的所有产出(PRD/原型/任务/规格)聚合在 REQ-ID 容器,
7
+ /wl-req 一条命令看到全貌, 不用跨目录翻。
8
+
9
+ 设计 (PM 只需记 2 个姿势):
10
+ /wl-req 列出所有需求 (默认=list, 不用记)
11
+ /wl-req REQ-2026-001 显示该需求全部产出 (show)
12
+ /wl-req REQ-2026-001 发布 promote+publish一步到位 (草稿→产出→禅道)
13
+
14
+ promote 不单独暴露(合并进"发布"), list 不用记(无参数即是)。
15
+
16
+ 用法:
17
+ python req.py # 列需求
18
+ python req.py REQ-2026-001 # 看某需求全貌
19
+ python req.py REQ-2026-001 发布 # 发布(promote+publish)
20
+ python req.py REQ-2026-001 promote # 仅草稿→产出(内部用)
21
+ """
22
+ # v3.0 路径自举
23
+ import os as _o, sys as _s
24
+ _f = _o.path.abspath(__file__)
25
+ for _ in range(10):
26
+ _f = _o.path.dirname(_f)
27
+ _cp = _o.path.join(_f, 'foundation')
28
+ if _o.path.isfile(_o.path.join(_cp, 'bootstrap.py')):
29
+ break
30
+ if _cp not in _s.path: _s.path.insert(0, _cp)
31
+ from bootstrap import setup; setup()
32
+ from foundation.core.paths import get_repo_root
33
+
34
+ import os
35
+ import sys
36
+ from pathlib import Path
37
+ from foundation.core.paths import (
38
+ get_repo_root, get_developer, MEMBERS_DIR,
39
+ get_time_bucket, get_time_bucket_path, get_req_dir,
40
+ get_outputs_dir, find_req_dirs,
41
+ )
42
+
43
+ BASE = get_repo_root()
44
+
45
+
46
+ def _list_all_reqs(dev=None):
47
+ """列出某开发者的所有需求 (跨 drafts/outputs/所有时间桶)。
48
+
49
+ Returns:
50
+ [{req_id, area, bucket, path, files: [文件名]}, ...]
51
+ """
52
+ dev = dev or get_developer(BASE) or "unknown"
53
+ dev_dir = MEMBERS_DIR / dev
54
+ if not dev_dir.is_dir():
55
+ return []
56
+ seen = {} # req_id → entry (去重, drafts优先)
57
+ for area in ("drafts", "outputs"):
58
+ area_root = dev_dir / area
59
+ if not area_root.is_dir():
60
+ continue
61
+ # 扫所有 REQ-* 目录 (跨时间桶)
62
+ for found in area_root.rglob("REQ-*"):
63
+ if not found.is_dir():
64
+ continue
65
+ req_id = found.name
66
+ # 清理标题后缀只取 REQ-YYYY-NNN (但保留完整名做显示)
67
+ import re
68
+ m = re.match(r"(REQ-\d{4}-\d{3,})", req_id)
69
+ clean_id = m.group(1) if m else req_id
70
+ if clean_id in seen:
71
+ # 已有, 但如果当前是outputs且已有的是drafts, 补充信息
72
+ continue
73
+ files = sorted(f.name for f in found.iterdir() if f.is_file())
74
+ subdirs = sorted(d.name for d in found.iterdir() if d.is_dir())
75
+ rel_bucket = str(found.parent.relative_to(area_root))
76
+ seen[clean_id] = {
77
+ "req_id": req_id,
78
+ "clean_id": clean_id,
79
+ "area": area,
80
+ "bucket": rel_bucket,
81
+ "path": found,
82
+ "files": files,
83
+ "subdirs": subdirs,
84
+ }
85
+ return list(seen.values())
86
+
87
+
88
+ def cmd_list(dev=None):
89
+ """列出所有需求。"""
90
+ reqs = _list_all_reqs(dev)
91
+ if not reqs:
92
+ dev_name = dev or get_developer(BASE) or "unknown"
93
+ print("暂无需求。写 PRD 时会自动创建 REQ-ID 容器。")
94
+ print(" 开发者: %s" % dev_name)
95
+ return 0
96
+ print("=" * 56)
97
+ print("需求列表 (%d 个)" % len(reqs))
98
+ print("=" * 56)
99
+ for r in sorted(reqs, key=lambda x: x["clean_id"]):
100
+ area_tag = "草稿" if r["area"] == "drafts" else "产出"
101
+ n_files = len(r["files"]) + len(r["subdirs"])
102
+ print(" %s [%s] %s (%d 项)" % (r["clean_id"], area_tag, r["bucket"], n_files))
103
+ print()
104
+ print("看详情: /wl-req <REQ-ID>")
105
+ return 0
106
+
107
+
108
+ def cmd_show(req_id, dev=None):
109
+ """显示某需求的全部产出 (跨 drafts/outputs/历史桶)。"""
110
+ found = find_req_dirs(req_id, dev, BASE)
111
+ if not found:
112
+ print("未找到需求: %s" % req_id)
113
+ print(" 用 /wl-req 看所有需求列表")
114
+ return 1
115
+ print("=" * 56)
116
+ print("需求: %s" % req_id)
117
+ print("=" * 56)
118
+ for entry in found:
119
+ area_cn = "草稿" if entry["area"] == "drafts" else "产出"
120
+ p = entry["path"]
121
+ print()
122
+ print("【%s · %s】" % (area_cn, entry["bucket"]))
123
+ print(" 路径: %s" % p)
124
+ # 列文件
125
+ files = sorted(f.name for f in p.iterdir() if f.is_file())
126
+ subdirs = sorted(d.name for d in p.iterdir() if d.is_dir())
127
+ if files:
128
+ print(" 文件:")
129
+ for f in files:
130
+ print(" - %s" % f)
131
+ if subdirs:
132
+ print(" 子目录:")
133
+ for d in subdirs:
134
+ # 列子目录里的文件数
135
+ sub_path = p / d
136
+ n = sum(1 for _ in sub_path.rglob("*") if _.is_file())
137
+ print(" - %s/ (%d 个文件)" % (d, n))
138
+ return 0
139
+
140
+
141
+ def cmd_promote(req_id, dev=None):
142
+ """草稿 → 产出 (drafts/桶/REQ → outputs/同桶/REQ)。"""
143
+ dev = dev or get_developer(BASE) or "unknown"
144
+ # 找 drafts 里的
145
+ draft_found = [e for e in find_req_dirs(req_id, dev, BASE) if e["area"] == "drafts"]
146
+ if not draft_found:
147
+ print("草稿区未找到: %s (可能已在产出区, 或不存在)" % req_id)
148
+ return 1
149
+ src = draft_found[0]["path"]
150
+ bucket = draft_found[0]["bucket"]
151
+ # 目标: outputs/同桶/REQ
152
+ dev_dir = MEMBERS_DIR / dev
153
+ dst = dev_dir / "outputs" / bucket / src.name
154
+ if dst.exists():
155
+ print("产出区已存在: %s" % dst)
156
+ print(" 如要覆盖, 先手动删除产出区的旧版本")
157
+ return 1
158
+ import shutil
159
+ dst.parent.mkdir(parents=True, exist_ok=True)
160
+ shutil.move(str(src), str(dst))
161
+ print("✓ 已提升到产出区:")
162
+ print(" %s" % dst)
163
+ print(" (产出进 team_sync, 团队可见)")
164
+ return 0
165
+
166
+
167
+ def cmd_publish(req_id, dev=None):
168
+ """发布 = promote + 发禅道。PM 只用这个。"""
169
+ dev = dev or get_developer(BASE) or "unknown"
170
+ # 先确保在 outputs (不在则 promote)
171
+ out_found = [e for e in find_req_dirs(req_id, dev, BASE) if e["area"] == "outputs"]
172
+ if not out_found:
173
+ print("草稿 → 产出...")
174
+ rc = cmd_promote(req_id, dev)
175
+ if rc != 0:
176
+ return rc
177
+ out_found = [e for e in find_req_dirs(req_id, dev, BASE) if e["area"] == "outputs"]
178
+ if not out_found:
179
+ print("✗ 发布失败: 产出区找不到 %s" % req_id)
180
+ return 1
181
+ out_path = out_found[0]["path"]
182
+ print()
183
+ print("发禅道 (从产出区: %s)" % out_path)
184
+ print("-" * 40)
185
+ # 找禅道 MCP 创建需求
186
+ try:
187
+ sys.path.insert(0, str(BASE / ".qoder" / "scripts" / "mcp"))
188
+ # 禅道发布: 读 PRD, 调 zentao MCP 建 story
189
+ prd_file = None
190
+ for f in out_path.iterdir():
191
+ if f.is_file() and (f.name.endswith(".md") and "prd" in f.name.lower()):
192
+ prd_file = f
193
+ break
194
+ if f.is_file() and f.suffix == ".md" and not f.name.startswith("_"):
195
+ prd_file = f # 第一个 md 当 PRD
196
+ if prd_file:
197
+ print(" PRD: %s" % prd_file.name)
198
+ print()
199
+ print("⚠️ 禅道发布需要 QoderWork 的禅道 MCP (mcp__qoder-zentao__create_story)。")
200
+ print(" AI 会读产出的 PRD, 调禅道建需求。")
201
+ print(" 如果你在 QoderWork 里说'%s 发布', AI 会自动完成这步。" % req_id)
202
+ return 0
203
+ except Exception as e:
204
+ print("✗ 发布出错: %s" % e)
205
+ return 1
206
+
207
+
208
+ def main():
209
+ args = sys.argv[1:]
210
+ if not args:
211
+ return cmd_list()
212
+ req_id = args[0]
213
+ action = args[1] if len(args) > 1 else "show"
214
+ if action == "show":
215
+ return cmd_show(req_id)
216
+ elif action in ("发布", "publish", "fabu"):
217
+ return cmd_publish(req_id)
218
+ elif action == "promote":
219
+ return cmd_promote(req_id)
220
+ elif action == "list":
221
+ return cmd_list()
222
+ else:
223
+ # 默认当 show
224
+ return cmd_show(req_id)
225
+
226
+
227
+ if __name__ == "__main__":
228
+ sys.exit(main())
@@ -0,0 +1,11 @@
1
+ # 子包初始化: 注入 scripts/根 + 所有子包到 sys.path
2
+ # 让跨包 import (旧式不带前缀) 仍可用, 兼容现有代码
3
+ import os as _os, sys as _sys
4
+ _root = _os.path.dirname(_os.path.dirname(_os.path.abspath(__file__)))
5
+ if _root not in _sys.path:
6
+ _sys.path.insert(0, _root)
7
+ # 注入所有子包目录 (兼容旧式 import xxx)
8
+ for _sub in ('kg', 'build', 'test', 'setup', 'io'):
9
+ _subdir = _os.path.join(_root, _sub)
10
+ if _os.path.isdir(_subdir) and _subdir not in _sys.path:
11
+ _sys.path.insert(0, _subdir)
@@ -1,14 +1,26 @@
1
1
  #!/usr/bin/env python3
2
2
  # -*- coding: utf-8 -*-
3
+ # v3.0 路径自举: 引导到 common/bootstrap, 统一 sys.path 逻辑
4
+ import os as _o, sys as _s
5
+ _f = _o.path.abspath(__file__)
6
+ for _ in range(10):
7
+ _f = _o.path.dirname(_f)
8
+ _cp = _o.path.join(_f, 'foundation')
9
+ if _o.path.isfile(_o.path.join(_cp, 'bootstrap.py')):
10
+ break
11
+ if _cp not in _s.path: _s.path.insert(0, _cp)
12
+ from bootstrap import setup; setup()
13
+ from foundation.core.paths import get_repo_root
14
+
3
15
  """
4
16
  Weekly Knowledge Graph Update
5
17
 
6
18
  Usage:
7
- python git_sync.py # Full update: git + PRD + index
8
- python git_sync.py --sync-only # Git pull only
9
- python git_sync.py --index-only # Re-index only (no git pull)
10
- python git_sync.py --prd-only # Collect + parse PRDs only
11
- python git_sync.py --project fywl-ics # Sync single project
19
+ python .qoder/scripts/domain/task/git_sync.py # Full update: git + PRD + index
20
+ python .qoder/scripts/domain/task/git_sync.py --sync-only # Git pull only
21
+ python .qoder/scripts/domain/task/git_sync.py --index-only # Re-index only (no git pull)
22
+ python .qoder/scripts/domain/task/git_sync.py --prd-only # Collect + parse PRDs only
23
+ python .qoder/scripts/domain/task/git_sync.py --project fywl-ics # Sync single project
12
24
 
13
25
  What it does (Friday Update):
14
26
  1. Git pull latest code for each project
@@ -21,7 +33,7 @@ Config: .qoder/config.yaml -> git_sync section
21
33
  """
22
34
 
23
35
  import os
24
- from common.paths import BUILD_CACHE_DIR
36
+ from foundation.core.paths import BUILD_CACHE_DIR
25
37
  import sys
26
38
  import json
27
39
  import subprocess
@@ -32,7 +44,7 @@ from datetime import datetime
32
44
  if sys.platform == 'win32':
33
45
  sys.stdout.reconfigure(encoding='utf-8')
34
46
 
35
- BASE = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
47
+ BASE = get_repo_root()
36
48
  DATA_DIR = os.path.join(BASE, 'data')
37
49
  CODE_DIR = os.path.join(DATA_DIR, 'code')
38
50
  INDEX_DIR = os.path.join(DATA_DIR, 'index')
@@ -50,7 +62,7 @@ def load_json(path, default=None):
50
62
  try:
51
63
  with open(path, 'r', encoding='utf-8') as f:
52
64
  return json.load(f)
53
- except:
65
+ except Exception:
54
66
  pass
55
67
  return default or {}
56
68
 
@@ -63,15 +75,43 @@ def save_json(path, data):
63
75
 
64
76
 
65
77
  def load_config():
66
- """Load config from config.yaml"""
67
- if not os.path.exists(CONFIG_PATH):
68
- return {}
78
+ """Load config from config.yaml (v3.0: 转发到 Config 单例, 带缓存)。"""
69
79
  try:
70
- import yaml
71
- with open(CONFIG_PATH, 'r', encoding='utf-8') as f:
72
- return yaml.safe_load(f) or {}
73
- except:
74
- return {}
80
+ from foundation.core.config import Config
81
+ return Config.load().raw
82
+ except Exception:
83
+ # 回退: 直接读 (sys.path 未就绪时)
84
+ if not os.path.exists(CONFIG_PATH):
85
+ return {}
86
+ try:
87
+ import yaml
88
+ with open(CONFIG_PATH, 'r', encoding='utf-8') as f:
89
+ return yaml.safe_load(f) or {}
90
+ except Exception:
91
+ return {}
92
+
93
+
94
+ def _git(*args, cwd=None, timeout=None):
95
+ """git 命令包装(复刻 team_sync.git 风格)。
96
+
97
+ 网络/远程操作 (fetch/pull/push/clone) 必须传 timeout —— 50 人团队周五 cron
98
+ 跑时, 远程要认证但凭证没缓存 / 弱网 / 仓库不存在, 会让裸 subprocess.run 永久
99
+ 阻塞, 整个周五同步挂死。本地操作 (rev-parse/diff/log) 可不传。
100
+
101
+ timeout 到期返回 rc=124 (与 timeout 命令一致), 不让 cron 无限阻塞。
102
+ git 未安装返回 rc=127, 不崩溃。
103
+ """
104
+ try:
105
+ kw = dict(cwd=cwd, capture_output=True, text=True, encoding='utf-8', errors='replace')
106
+ if timeout:
107
+ kw['timeout'] = timeout
108
+ return subprocess.run(['git'] + list(args), **kw)
109
+ except FileNotFoundError:
110
+ import types
111
+ return types.SimpleNamespace(returncode=127, stdout='', stderr='git not installed')
112
+ except subprocess.TimeoutExpired:
113
+ import types
114
+ return types.SimpleNamespace(returncode=124, stdout='', stderr='git {} timed out after {}s'.format(' '.join(args[:3]), timeout))
75
115
 
76
116
 
77
117
  # ============================================================
@@ -86,32 +126,22 @@ def git_pull_project(project_name):
86
126
  return []
87
127
 
88
128
  # Get current commit before pull
89
- result = subprocess.run(
90
- ['git', 'rev-parse', 'HEAD'],
91
- cwd=project_dir, capture_output=True, text=True, encoding='utf-8'
92
- )
129
+ result = _git('rev-parse', 'HEAD', cwd=project_dir)
93
130
  old_commit = result.stdout.strip()[:8] if result.returncode == 0 else ''
94
131
 
95
- # Fetch + pull
132
+ # Fetch + pull (网络操作必须带 timeout, 防弱网/认证弹窗挂死周五 cron)
96
133
  print(f' {project_name}: Fetching...')
97
- subprocess.run(['git', 'fetch', 'origin'],
98
- cwd=project_dir, capture_output=True)
134
+ _git('fetch', 'origin', cwd=project_dir, timeout=120)
99
135
 
100
136
  print(f' {project_name}: Pulling...')
101
- result = subprocess.run(
102
- ['git', 'pull', 'origin'],
103
- cwd=project_dir, capture_output=True, text=True, encoding='utf-8'
104
- )
137
+ result = _git('pull', 'origin', cwd=project_dir, timeout=180)
105
138
 
106
139
  if result.returncode != 0:
107
- print(f' {project_name}: Pull failed - {result.stderr[:100]}')
140
+ print(f' {project_name}: Pull failed - {(result.stderr or "")[:100]}')
108
141
  return []
109
142
 
110
143
  # Get new commit
111
- result = subprocess.run(
112
- ['git', 'rev-parse', 'HEAD'],
113
- cwd=project_dir, capture_output=True, text=True, encoding='utf-8'
114
- )
144
+ result = _git('rev-parse', 'HEAD', cwd=project_dir)
115
145
  new_commit = result.stdout.strip()[:8] if result.returncode == 0 else ''
116
146
 
117
147
  if old_commit == new_commit:
@@ -119,10 +149,7 @@ def git_pull_project(project_name):
119
149
  return []
120
150
 
121
151
  # Get changed files
122
- result = subprocess.run(
123
- ['git', 'diff', '--name-only', old_commit, new_commit],
124
- cwd=project_dir, capture_output=True, text=True, encoding='utf-8'
125
- )
152
+ result = _git('diff', '--name-only', old_commit, new_commit, cwd=project_dir)
126
153
  changed = [f.strip() for f in result.stdout.strip().split('\n') if f.strip()]
127
154
  print(f' {project_name}: {old_commit} -> {new_commit}, {len(changed)} files changed')
128
155
  return changed
@@ -182,18 +209,31 @@ def collect_prds():
182
209
  if not os.path.isdir(user_dir):
183
210
  continue
184
211
 
185
- drafts_dir = os.path.join(user_dir, 'drafts')
186
- if not os.path.isdir(drafts_dir):
187
- continue
188
-
189
- user_prds = []
190
- for f in os.listdir(drafts_dir):
191
- if (f.startswith('REQ-') or f.startswith('PRD-') or f.startswith('prd-')) and f.endswith('.md'):
192
- filepath = os.path.join(drafts_dir, f)
193
- mtime = os.path.getmtime(filepath)
194
- user_prds.append({'file': f, 'path': filepath, 'user': user_name, 'mtime': mtime})
195
-
196
- for prd in user_prds:
212
+ # 递归扫 drafts + outputs 两个区 (PRD 可能落在时间桶
213
+ # drafts/{YYYY}/{MM}/{Ww}/{REQ-ID}/REQ-*.md, 也可能已 publish 到 outputs/)
214
+ # outputs 是发布版, 同名优先 outputs; 同区递归找 (os.walk) 兼容旧扁平结构。
215
+ user_prds = {}
216
+ for area in ('drafts', 'outputs'):
217
+ area_dir = os.path.join(user_dir, area)
218
+ if not os.path.isdir(area_dir):
219
+ continue
220
+ for root, dirs, files in os.walk(area_dir):
221
+ # 跳过隐藏目录 / node_modules (保险)
222
+ dirs[:] = [d for d in dirs if not d.startswith('.') and d != 'node_modules']
223
+ for f in files:
224
+ if (f.startswith('REQ-') or f.startswith('PRD-') or f.startswith('prd-')) and f.endswith('.md'):
225
+ filepath = os.path.join(root, f)
226
+ try:
227
+ mtime = os.path.getmtime(filepath)
228
+ except OSError:
229
+ continue
230
+ entry = {'file': f, 'path': filepath, 'user': user_name, 'mtime': mtime, 'area': area}
231
+ existing = user_prds.get(f)
232
+ # outputs 优先; 否则取较新的 mtime
233
+ if existing is None or area == 'outputs' or mtime > existing['mtime']:
234
+ user_prds[f] = entry
235
+
236
+ for prd in user_prds.values():
197
237
  key = prd['file']
198
238
  is_new = key not in collected
199
239
  is_updated = not is_new and collected[key].get('mtime', 0) < prd['mtime']
@@ -383,7 +423,7 @@ def parse_java_file(filepath):
383
423
  'keywords': re.findall('[A-Z]?[a-z]+', class_name or '') if class_name else []
384
424
  }
385
425
  entities.append(entity)
386
- except:
426
+ except Exception:
387
427
  pass
388
428
  return entities
389
429
 
@@ -411,7 +451,7 @@ def parse_frontend_file(filepath):
411
451
  'type': 'vue' if filepath.endswith('.vue') else 'js/ts'
412
452
  }
413
453
  entities.append(entity)
414
- except:
454
+ except Exception:
415
455
  pass
416
456
  return entities
417
457
 
@@ -1,4 +1,15 @@
1
1
  # -*- coding: utf-8 -*-
2
+ # v3.0 路径自举: 引导到 common/bootstrap, 统一 sys.path 逻辑
3
+ import os as _o, sys as _s
4
+ _f = _o.path.abspath(__file__)
5
+ for _ in range(10):
6
+ _f = _o.path.dirname(_f)
7
+ _cp = _o.path.join(_f, 'foundation')
8
+ if _o.path.isfile(_o.path.join(_cp, 'bootstrap.py')):
9
+ break
10
+ if _cp not in _s.path: _s.path.insert(0, _cp)
11
+ from bootstrap import setup; setup()
12
+
2
13
  """
3
14
  syncgate.py - team_sync 推送前的零信任门禁
4
15
 
@@ -22,11 +33,11 @@ from typing import List, Tuple, Optional
22
33
  _THIS_DIR = os.path.dirname(os.path.abspath(__file__))
23
34
  if _THIS_DIR not in sys.path:
24
35
  sys.path.insert(0, _THIS_DIR)
25
- _COMMON = os.path.join(_THIS_DIR, "common")
36
+ _COMMON = os.path.join(_THIS_DIR, "foundation")
26
37
  if _COMMON not in sys.path:
27
38
  sys.path.insert(0, _COMMON)
28
39
 
29
- from common.identity import (
40
+ from foundation.identity.identity import (
30
41
  get_member, verify_member, EXIT_AUTHZ, IdentityError,
31
42
  )
32
43
 
@@ -223,7 +234,7 @@ _PLATFORM_KEYWORDS = [
223
234
  def check_platform_gate(prd_paths: List[str], repo_root: str) -> None:
224
235
  """检查 PRD 文件是否标注了目标平台。未标注抛 GateFailure。
225
236
 
226
- 这是 /wl-prd-full "先问平台" 的程序级兜底:
237
+ 这是 /wl-prd "先问平台" 的程序级兜底:
227
238
  如果 AI 没问平台就生成了 PRD, push 时这里会拦住。
228
239
  """
229
240
  missing = []
@@ -242,7 +253,7 @@ def check_platform_gate(prd_paths: List[str], repo_root: str) -> None:
242
253
 
243
254
  if missing:
244
255
  raise GateFailure(
245
- "[gate] 拒绝: %d 个 PRD 未标注目标平台 (违反 /wl-prd-full 先问平台规则):\n"
256
+ "[gate] 拒绝: %d 个 PRD 未标注目标平台 (违反 /wl-prd 先问平台规则):\n"
246
257
  " %s\n"
247
258
  "修复: 在 PRD 头部加一行:\n"
248
259
  " - **平台**: Web管理端 (fywl-ui) # 或 APP移动端 / 两端 / 后端" % (
@@ -268,7 +279,7 @@ def check_eval_gate(
268
279
  if skip_eval or not prd_paths:
269
280
  return
270
281
  # 硬 import: eval_api 是必需模块,缺失说明引擎损坏,应报错而非静默跳过
271
- from common.eval_api import evaluate
282
+ from foundation.utils.eval_api import evaluate
272
283
 
273
284
  failures = []
274
285
  for prd in prd_paths:
@@ -313,7 +324,7 @@ def check_eval_gate(
313
324
  lines.append("修复 PRD 后重试, 或 admin 用 --skip-eval 跳过 (会记录审计)")
314
325
  # D2: 飞书通知 EVA 拒绝 (best-effort, 失败不阻塞门禁)
315
326
  try:
316
- from common.feishu import notify_eval_rejected
327
+ from foundation.integrations.feishu import notify_eval_rejected
317
328
  notify_eval_rejected(
318
329
  req_id=",".join(os.path.basename(p) for p, _ in failures[:3]),
319
330
  title="批量 EVA 拒绝",
@@ -356,7 +367,7 @@ def run_gates(
356
367
  ]
357
368
  if prd_files:
358
369
  # 硬 import: reqid 是必需模块,缺失说明引擎损坏
359
- from common.reqid import validate_req_ids, migrate_from_existing, ReqIdError
370
+ from foundation.io.reqid import validate_req_ids, migrate_from_existing, ReqIdError
360
371
  # 计数器缺失则先迁移 (幂等)
361
372
  try:
362
373
  validate_req_ids(prd_files, repo_root)