@hupan56/wlkj 3.0.0 → 3.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (237) hide show
  1. package/bin/cli.js +1435 -1373
  2. package/package.json +1 -1
  3. package/templates/qoder/commands/optional/wl-insight.md +6 -4
  4. package/templates/qoder/commands/optional/wl-report.md +20 -39
  5. package/templates/qoder/commands/optional/wl-status.md +4 -4
  6. package/templates/qoder/commands/wl-code.md +2 -2
  7. package/templates/qoder/commands/wl-design.md +5 -5
  8. package/templates/qoder/commands/wl-init.md +3 -3
  9. package/templates/qoder/commands/wl-prd.md +26 -13
  10. package/templates/qoder/commands/wl-req.md +3 -3
  11. package/templates/qoder/commands/wl-search.md +78 -47
  12. package/templates/qoder/commands/wl-task.md +343 -40
  13. package/templates/qoder/commands/wl-test.md +10 -10
  14. package/templates/qoder/config.yaml +35 -13
  15. package/templates/qoder/hooks/post-tool-use.py +181 -0
  16. package/templates/qoder/hooks/session-start.py +82 -1
  17. package/templates/qoder/hooks/stop-eval.py +207 -0
  18. package/templates/qoder/hooks/user-prompt-submit.py +139 -0
  19. package/templates/qoder/rules/wl-pipeline.md +70 -8
  20. package/templates/qoder/scripts/README.md +78 -115
  21. package/templates/qoder/scripts/capability/__init__.py +26 -0
  22. package/templates/qoder/scripts/capability/__main__.py +72 -0
  23. package/templates/qoder/scripts/capability/adapters/__init__.py +29 -0
  24. package/templates/qoder/scripts/capability/adapters/cli.py +316 -0
  25. package/templates/qoder/scripts/capability/adapters/mcp.py +334 -0
  26. package/templates/qoder/scripts/capability/adapters/qw.py +271 -0
  27. package/templates/qoder/scripts/capability/caps/__init__.py +27 -0
  28. package/templates/qoder/scripts/capability/caps/context.py +36 -0
  29. package/templates/qoder/scripts/capability/caps/cron.py +50 -0
  30. package/templates/qoder/scripts/capability/caps/identity.py +43 -0
  31. package/templates/qoder/scripts/capability/caps/memory.py +71 -0
  32. package/templates/qoder/scripts/capability/caps/notify.py +41 -0
  33. package/templates/qoder/scripts/capability/caps/present.py +48 -0
  34. package/templates/qoder/scripts/capability/caps/repo.py +31 -0
  35. package/templates/qoder/scripts/capability/caps/sandbox.py +43 -0
  36. package/templates/qoder/scripts/capability/chain.py +92 -0
  37. package/templates/qoder/scripts/capability/memory_chain.py +41 -0
  38. package/templates/qoder/scripts/capability/registry.py +246 -0
  39. package/templates/qoder/scripts/capability/registry_mcp.py +250 -0
  40. package/templates/qoder/scripts/capability/smoke_test.py +211 -0
  41. package/templates/qoder/scripts/capability/smoke_test_report.json +94 -0
  42. package/templates/qoder/scripts/{report → deployment/setup}/__init__.py +11 -11
  43. package/templates/qoder/scripts/{setup → deployment/setup}/carriers.py +33 -26
  44. package/templates/qoder/scripts/deployment/setup/carriers_verify.py +148 -0
  45. package/templates/qoder/scripts/{setup → deployment/setup}/init_doctor.py +40 -16
  46. package/templates/qoder/scripts/{setup → deployment/setup}/install_qoderwork.py +20 -12
  47. package/templates/qoder/scripts/{setup → deployment/setup}/platform_doctor.py +8 -2
  48. package/templates/qoder/scripts/{setup → deployment/setup}/repo_root.py +113 -112
  49. package/templates/qoder/scripts/{setup → deployment/setup}/setup.py +31 -11
  50. package/templates/qoder/scripts/{setup → deployment/setup}/setup_lanhu.py +13 -7
  51. package/templates/qoder/scripts/domain/__init__.py +0 -0
  52. package/templates/qoder/scripts/domain/design/__init__.py +0 -0
  53. package/templates/qoder/scripts/{report → domain/design}/fill_prototype.py +12 -6
  54. package/templates/qoder/scripts/{report → domain/design}/gen_design_doc.py +8 -2
  55. package/templates/qoder/scripts/{setup → domain/kg}/__init__.py +11 -11
  56. package/templates/qoder/scripts/domain/kg/build/__init__.py +0 -0
  57. package/templates/qoder/scripts/{kg → domain/kg/build}/build_entity_registry.py +7 -2
  58. package/templates/qoder/scripts/{kg → domain/kg/build}/build_relations.py +7 -2
  59. package/templates/qoder/scripts/{kg → domain/kg/build}/build_style_index.py +13 -7
  60. package/templates/qoder/scripts/{kg → domain/kg/build}/build_workflows.py +7 -2
  61. package/templates/qoder/scripts/{kg → domain/kg/build}/kg_build.py +76 -9
  62. package/templates/qoder/scripts/{kg → domain/kg/build}/kg_build_db.py +7 -2
  63. package/templates/qoder/scripts/{kg → domain/kg/build}/kg_incremental.py +11 -6
  64. package/templates/qoder/scripts/{report → domain/kg/build}/learn_aggregate.py +9 -4
  65. package/templates/qoder/scripts/domain/kg/extract/__init__.py +0 -0
  66. package/templates/qoder/scripts/{common → domain/kg/extract}/extract.py +430 -419
  67. package/templates/qoder/scripts/{common → domain/kg/extract}/test_extract.py +115 -115
  68. package/templates/qoder/scripts/{common → domain/kg/extract}/ts_extract.py +614 -536
  69. package/templates/qoder/scripts/domain/kg/graph/__init__.py +0 -0
  70. package/templates/qoder/scripts/{kg → domain/kg/graph}/kg_link_db.py +7 -2
  71. package/templates/qoder/scripts/domain/kg/graph/kg_semantic.py +683 -0
  72. package/templates/qoder/scripts/{kg → domain/kg}/kg.py +263 -30
  73. package/templates/qoder/scripts/{common → domain/kg}/kg_capabilities.py +182 -182
  74. package/templates/qoder/scripts/domain/kg/search/__init__.py +0 -0
  75. package/templates/qoder/scripts/{kg → domain/kg/search}/context_pack.py +30 -9
  76. package/templates/qoder/scripts/{kg → domain/kg/search}/enrich_prompt.py +10 -4
  77. package/templates/qoder/scripts/{kg → domain/kg/search}/prefetch.py +33 -8
  78. package/templates/qoder/scripts/{common → domain/kg/search}/search_engine.py +207 -205
  79. package/templates/qoder/scripts/{kg → domain/kg/search}/search_index.py +143 -77
  80. package/templates/qoder/scripts/domain/kg/server/__init__.py +0 -0
  81. package/templates/qoder/scripts/domain/kg/server/kgd.py +549 -0
  82. package/templates/qoder/scripts/domain/kg/server/perf_bench.py +197 -0
  83. package/templates/qoder/scripts/domain/kg/storage/__init__.py +0 -0
  84. package/templates/qoder/scripts/{kg → domain/kg/storage}/kg_duckdb.py +38 -14
  85. package/templates/qoder/scripts/domain/learning/__init__.py +0 -0
  86. package/templates/qoder/scripts/{report → domain/learning}/learn.py +8 -3
  87. package/templates/qoder/scripts/{mcp → domain/report}/__init__.py +11 -11
  88. package/templates/qoder/scripts/{report → domain/report}/add_session.py +9 -3
  89. package/templates/qoder/scripts/{report → domain/report}/export.py +10 -5
  90. package/templates/qoder/scripts/{report → domain/report}/report.py +9 -4
  91. package/templates/qoder/scripts/domain/report/report_snapshot.py +261 -0
  92. package/templates/qoder/scripts/{report → domain/report}/role.py +10 -4
  93. package/templates/qoder/scripts/{report → domain/report}/status.py +112 -9
  94. package/templates/qoder/scripts/domain/report/status_snapshot.py +191 -0
  95. package/templates/qoder/scripts/domain/requirement/__init__.py +0 -0
  96. package/templates/qoder/scripts/{report → domain/requirement}/archive_prd.py +8 -2
  97. package/templates/qoder/scripts/{report → domain/requirement}/req.py +228 -222
  98. package/templates/qoder/scripts/{kg → domain/task}/__init__.py +11 -11
  99. package/templates/qoder/scripts/{task → domain/task}/git_sync.py +40 -21
  100. package/templates/qoder/scripts/{task → domain/task}/syncgate.py +13 -8
  101. package/templates/qoder/scripts/{task → domain/task}/task.py +13 -5
  102. package/templates/qoder/scripts/{task → domain/task}/task_lifecycle.py +606 -596
  103. package/templates/qoder/scripts/{task → domain/task}/task_query.py +162 -161
  104. package/templates/qoder/scripts/{task → domain/task}/task_relations.py +425 -424
  105. package/templates/qoder/scripts/{task → domain/task}/team_sync.py +9 -4
  106. package/templates/qoder/scripts/domain/task/zentao_sync.py +370 -0
  107. package/templates/qoder/scripts/foundation/__init__.py +0 -0
  108. package/templates/qoder/scripts/{common → foundation}/bootstrap.py +10 -10
  109. package/templates/qoder/scripts/foundation/core/__init__.py +0 -0
  110. package/templates/qoder/scripts/{common → foundation/core}/cmd_registry.py +1 -1
  111. package/templates/qoder/scripts/{common → foundation/core}/config.py +1 -1
  112. package/templates/qoder/scripts/{common → foundation/core}/paths.py +706 -586
  113. package/templates/qoder/scripts/foundation/data/__init__.py +0 -0
  114. package/templates/qoder/scripts/{common → foundation/data}/contract.py +2 -2
  115. package/templates/qoder/scripts/{common → foundation/data}/events.py +2 -2
  116. package/templates/qoder/scripts/{common → foundation/data}/result.py +1 -1
  117. package/templates/qoder/scripts/{common → foundation/data}/task_utils.py +427 -404
  118. package/templates/qoder/scripts/foundation/identity/__init__.py +0 -0
  119. package/templates/qoder/scripts/{common → foundation/identity}/check_publish.py +103 -98
  120. package/templates/qoder/scripts/{common → foundation/identity}/developer.py +239 -239
  121. package/templates/qoder/scripts/{common → foundation/identity}/guard.py +2 -2
  122. package/templates/qoder/scripts/{common → foundation/identity}/identity.py +14 -10
  123. package/templates/qoder/scripts/{common → foundation/identity}/roles.py +1 -1
  124. package/templates/qoder/scripts/foundation/integrations/__init__.py +0 -0
  125. package/templates/qoder/scripts/{common → foundation/integrations}/active_task.py +30 -15
  126. package/templates/qoder/scripts/{common → foundation/integrations}/feishu.py +2 -2
  127. package/templates/qoder/scripts/{common → foundation/integrations}/terms.py +3 -3
  128. package/templates/qoder/scripts/foundation/integrations/zentao_client.py +220 -0
  129. package/templates/qoder/scripts/foundation/io/__init__.py +0 -0
  130. package/templates/qoder/scripts/{common → foundation/io}/filelock.py +1 -1
  131. package/templates/qoder/scripts/{common → foundation/io}/reqid.py +1 -1
  132. package/templates/qoder/scripts/foundation/protocol/__init__.py +0 -0
  133. package/templates/qoder/scripts/foundation/protocol/duckdb_conn.py +39 -0
  134. package/templates/qoder/scripts/{common → foundation/protocol}/mcp_base.py +1 -1
  135. package/templates/qoder/scripts/foundation/utils/__init__.py +0 -0
  136. package/templates/qoder/scripts/{common → foundation/utils}/eval_api.py +12 -1
  137. package/templates/qoder/scripts/{common → foundation/utils}/pip_install.py +23 -6
  138. package/templates/qoder/scripts/{common → foundation/utils}/platform_guard.py +1 -1
  139. package/templates/qoder/scripts/orchestration/__init__.py +0 -0
  140. package/templates/qoder/scripts/orchestration/wlkj.py +185 -0
  141. package/templates/qoder/scripts/protocol/__init__.py +0 -0
  142. package/templates/qoder/scripts/protocol/browser/README.md +207 -0
  143. package/templates/qoder/scripts/protocol/browser/SKILL.md +265 -0
  144. package/templates/qoder/scripts/protocol/browser/config.env +9 -0
  145. package/templates/qoder/scripts/protocol/browser/references/cdp-api.md +110 -0
  146. package/templates/qoder/scripts/protocol/browser/references/migration-2.5.3.md +72 -0
  147. package/templates/qoder/scripts/protocol/browser/references/site-patterns/.gitkeep +0 -0
  148. package/templates/qoder/scripts/protocol/browser/scripts/browser-discovery.mjs +138 -0
  149. package/templates/qoder/scripts/protocol/browser/scripts/cdp-proxy.mjs +672 -0
  150. package/templates/qoder/scripts/protocol/browser/scripts/check-deps.mjs +206 -0
  151. package/templates/qoder/scripts/protocol/browser/scripts/find-url.mjs +253 -0
  152. package/templates/qoder/scripts/protocol/browser/scripts/match-site.mjs +46 -0
  153. package/templates/qoder/scripts/protocol/browser/templates/config.env.template +9 -0
  154. package/templates/qoder/scripts/protocol/mcp/__init__.py +11 -0
  155. package/templates/qoder/scripts/protocol/mcp/kg_mcp_server.py +478 -0
  156. package/templates/qoder/scripts/{mcp → protocol/mcp}/lanhu_stdio_wrapper.py +11 -5
  157. package/templates/qoder/scripts/{mcp → protocol/mcp}/mcp_doctor.py +47 -14
  158. package/templates/qoder/scripts/protocol/mcp/mcp_launcher.py +600 -0
  159. package/templates/qoder/scripts/{mcp → protocol/mcp}/mysql_mcp_server.py +181 -67
  160. package/templates/qoder/scripts/protocol/mcp/zentao_mcp_server.py +1920 -0
  161. package/templates/qoder/scripts/protocol/transports/__init__.py +56 -0
  162. package/templates/qoder/scripts/protocol/transports/base.py +87 -0
  163. package/templates/qoder/scripts/protocol/transports/cli.py +132 -0
  164. package/templates/qoder/scripts/protocol/transports/http.py +141 -0
  165. package/templates/qoder/scripts/protocol/transports/stdio.py +293 -0
  166. package/templates/qoder/scripts/run_weekly_update.bat +27 -18
  167. package/templates/qoder/scripts/run_weekly_update.sh +22 -13
  168. package/templates/qoder/scripts/validation/__init__.py +0 -0
  169. package/templates/qoder/scripts/validation/eval/__init__.py +0 -0
  170. package/templates/qoder/scripts/validation/eval/perf-report-2026-06-24.md +139 -0
  171. package/templates/qoder/scripts/validation/eval/qwork_harness.py +617 -0
  172. package/templates/qoder/scripts/validation/eval/report-commands.md +220 -0
  173. package/templates/qoder/scripts/validation/eval/transcript_timing.py +386 -0
  174. package/templates/qoder/scripts/validation/metrics/__init__.py +0 -0
  175. package/templates/qoder/scripts/{report → validation/metrics}/eval_prd.py +17 -10
  176. package/templates/qoder/scripts/validation/metrics/usability_score.py +750 -0
  177. package/templates/qoder/scripts/validation/test/__init__.py +11 -0
  178. package/templates/qoder/scripts/validation/test/assertion_gen.py +551 -0
  179. package/templates/qoder/scripts/{test → validation/test}/autotest.py +64 -4
  180. package/templates/qoder/scripts/{common → validation/test}/autotest_auth.py +1 -1
  181. package/templates/qoder/scripts/{test → validation/test}/autotest_batch.py +16 -3
  182. package/templates/qoder/scripts/{test → validation/test}/autotest_data.py +17 -12
  183. package/templates/qoder/scripts/validation/test/autotest_opencli.py +646 -0
  184. package/templates/qoder/scripts/{test → validation/test}/autotest_run.py +17 -3
  185. package/templates/qoder/scripts/validation/test/autotest_webaccess.py +571 -0
  186. package/templates/qoder/scripts/{test → validation/test}/benchmark.py +12 -6
  187. package/templates/qoder/scripts/{test → validation/test}/kg_auto_login.py +8 -2
  188. package/templates/qoder/scripts/{test → validation/test}/kg_test_runner.py +7 -2
  189. package/templates/qoder/scripts/{test → validation/test}/page_probe.py +8 -3
  190. package/templates/qoder/scripts/validation/test/smoke_all_commands.py +170 -0
  191. package/templates/qoder/settings.json +30 -0
  192. package/templates/qoder/skills/design-import/SKILL.md +22 -13
  193. package/templates/qoder/skills/design-review/SKILL.md +19 -10
  194. package/templates/qoder/skills/prd-generator/SKILL.md +22 -18
  195. package/templates/qoder/skills/prd-review/SKILL.md +6 -4
  196. package/templates/qoder/skills/prototype-generator/SKILL.md +63 -14
  197. package/templates/qoder/skills/spec-coder/SKILL.md +22 -13
  198. package/templates/qoder/skills/spec-generator/SKILL.md +22 -10
  199. package/templates/qoder/skills/test-generator/SKILL.md +7 -5
  200. package/templates/qoder/skills/wl-code/SKILL.md +23 -11
  201. package/templates/qoder/skills/wl-commit/SKILL.md +9 -8
  202. package/templates/qoder/skills/wl-design/SKILL.md +5 -3
  203. package/templates/qoder/skills/wl-init/SKILL.md +6 -6
  204. package/templates/qoder/skills/wl-insight/SKILL.md +23 -10
  205. package/templates/qoder/skills/wl-prd-full/SKILL.md +57 -6
  206. package/templates/qoder/skills/wl-prd-quick/SKILL.md +3 -3
  207. package/templates/qoder/skills/wl-prd-review/SKILL.md +16 -4
  208. package/templates/qoder/skills/wl-report/SKILL.md +25 -23
  209. package/templates/qoder/skills/wl-search/SKILL.md +149 -78
  210. package/templates/qoder/skills/wl-spec/SKILL.md +7 -5
  211. package/templates/qoder/skills/wl-status/SKILL.md +48 -16
  212. package/templates/qoder/skills/wl-task/SKILL.md +92 -10
  213. package/templates/qoder/skills/wl-test/SKILL.md +75 -29
  214. package/templates/qoder/templates/prd-full-template.md +7 -0
  215. package/templates/root/AGENTS.md +275 -236
  216. package/templates/root/requirements.txt +3 -0
  217. package/templates/root//344/275/277/347/224/250/350/257/264/346/230/216.md +3 -3
  218. package/templates/root//346/226/260/346/211/213/346/214/207/345/215/227.md +2 -2
  219. package/templates/.qoder/.runtime/ctx-cache-5660152f1d6dd819.md +0 -23
  220. package/templates/.qoder/.runtime/ctx-cache-afdce0dac06b25b0.md +0 -23
  221. package/templates/.qoder/.runtime/search-cache-eae7644e7b122f35.txt +0 -1
  222. package/templates/.qoder/learning/eval-history.jsonl +0 -28
  223. package/templates/data/index/wiki-index.json +0 -8
  224. package/templates/qoder/nul +0 -4
  225. package/templates/qoder/scripts/kg/extract.py +0 -398
  226. package/templates/qoder/scripts/kg/kg_semantic.py +0 -156
  227. package/templates/qoder/scripts/mcp/kg_mcp_server.py +0 -606
  228. package/templates/qoder/scripts/mcp/mcp_launcher.py +0 -442
  229. package/templates/qoder/scripts/mcp/zentao_mcp_server.py +0 -384
  230. package/templates/qoder/scripts/task/__init__.py +0 -11
  231. package/templates/qoder/scripts/test/__init__.py +0 -11
  232. package/templates/qoder/scripts/wlkj.py +0 -116
  233. /package/templates/qoder/scripts/{common → deployment}/__init__.py +0 -0
  234. /package/templates/qoder/scripts/{common → domain/kg/graph}/graph_traverse.py +0 -0
  235. /package/templates/qoder/scripts/{common → domain/kg/server}/repowiki.py +0 -0
  236. /package/templates/qoder/scripts/{common → foundation/io}/atomicio.py +0 -0
  237. /package/templates/qoder/scripts/{test → validation/test}/secure-ls.js +0 -0
@@ -1,222 +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
- _cp = _o.path.join(_o.path.dirname(_o.path.dirname(_o.path.abspath(__file__))), 'common')
25
- if _cp not in _s.path: _s.path.insert(0, _cp)
26
- from bootstrap import setup; setup()
27
-
28
- import os
29
- import sys
30
- from pathlib import Path
31
- from common.paths import (
32
- get_repo_root, get_developer, MEMBERS_DIR,
33
- get_time_bucket, get_time_bucket_path, get_req_dir,
34
- get_outputs_dir, find_req_dirs,
35
- )
36
-
37
- BASE = get_repo_root()
38
-
39
-
40
- def _list_all_reqs(dev=None):
41
- """列出某开发者的所有需求 (跨 drafts/outputs/所有时间桶)
42
-
43
- Returns:
44
- [{req_id, area, bucket, path, files: [文件名]}, ...]
45
- """
46
- dev = dev or get_developer(BASE) or "unknown"
47
- dev_dir = MEMBERS_DIR / dev
48
- if not dev_dir.is_dir():
49
- return []
50
- seen = {} # req_id entry (去重, drafts优先)
51
- for area in ("drafts", "outputs"):
52
- area_root = dev_dir / area
53
- if not area_root.is_dir():
54
- continue
55
- # 扫所有 REQ-* 目录 (跨时间桶)
56
- for found in area_root.rglob("REQ-*"):
57
- if not found.is_dir():
58
- continue
59
- req_id = found.name
60
- # 清理标题后缀只取 REQ-YYYY-NNN (但保留完整名做显示)
61
- import re
62
- m = re.match(r"(REQ-\d{4}-\d{3,})", req_id)
63
- clean_id = m.group(1) if m else req_id
64
- if clean_id in seen:
65
- # 已有, 但如果当前是outputs且已有的是drafts, 补充信息
66
- continue
67
- files = sorted(f.name for f in found.iterdir() if f.is_file())
68
- subdirs = sorted(d.name for d in found.iterdir() if d.is_dir())
69
- rel_bucket = str(found.parent.relative_to(area_root))
70
- seen[clean_id] = {
71
- "req_id": req_id,
72
- "clean_id": clean_id,
73
- "area": area,
74
- "bucket": rel_bucket,
75
- "path": found,
76
- "files": files,
77
- "subdirs": subdirs,
78
- }
79
- return list(seen.values())
80
-
81
-
82
- def cmd_list(dev=None):
83
- """列出所有需求。"""
84
- reqs = _list_all_reqs(dev)
85
- if not reqs:
86
- dev_name = dev or get_developer(BASE) or "unknown"
87
- print("暂无需求。写 PRD 时会自动创建 REQ-ID 容器。")
88
- print(" 开发者: %s" % dev_name)
89
- return 0
90
- print("=" * 56)
91
- print("需求列表 (%d 个)" % len(reqs))
92
- print("=" * 56)
93
- for r in sorted(reqs, key=lambda x: x["clean_id"]):
94
- area_tag = "草稿" if r["area"] == "drafts" else "产出"
95
- n_files = len(r["files"]) + len(r["subdirs"])
96
- print(" %s [%s] %s (%d 项)" % (r["clean_id"], area_tag, r["bucket"], n_files))
97
- print()
98
- print("看详情: /wl-req <REQ-ID>")
99
- return 0
100
-
101
-
102
- def cmd_show(req_id, dev=None):
103
- """显示某需求的全部产出 (跨 drafts/outputs/历史桶)。"""
104
- found = find_req_dirs(req_id, dev, BASE)
105
- if not found:
106
- print("未找到需求: %s" % req_id)
107
- print(" 用 /wl-req 看所有需求列表")
108
- return 1
109
- print("=" * 56)
110
- print("需求: %s" % req_id)
111
- print("=" * 56)
112
- for entry in found:
113
- area_cn = "草稿" if entry["area"] == "drafts" else "产出"
114
- p = entry["path"]
115
- print()
116
- print("【%s · %s" % (area_cn, entry["bucket"]))
117
- print(" 路径: %s" % p)
118
- # 列文件
119
- files = sorted(f.name for f in p.iterdir() if f.is_file())
120
- subdirs = sorted(d.name for d in p.iterdir() if d.is_dir())
121
- if files:
122
- print(" 文件:")
123
- for f in files:
124
- print(" - %s" % f)
125
- if subdirs:
126
- print(" 子目录:")
127
- for d in subdirs:
128
- # 列子目录里的文件数
129
- sub_path = p / d
130
- n = sum(1 for _ in sub_path.rglob("*") if _.is_file())
131
- print(" - %s/ (%d 个文件)" % (d, n))
132
- return 0
133
-
134
-
135
- def cmd_promote(req_id, dev=None):
136
- """草稿 产出 (drafts/桶/REQ outputs/同桶/REQ)。"""
137
- dev = dev or get_developer(BASE) or "unknown"
138
- # 找 drafts 里的
139
- draft_found = [e for e in find_req_dirs(req_id, dev, BASE) if e["area"] == "drafts"]
140
- if not draft_found:
141
- print("草稿区未找到: %s (可能已在产出区, 或不存在)" % req_id)
142
- return 1
143
- src = draft_found[0]["path"]
144
- bucket = draft_found[0]["bucket"]
145
- # 目标: outputs/同桶/REQ
146
- dev_dir = MEMBERS_DIR / dev
147
- dst = dev_dir / "outputs" / bucket / src.name
148
- if dst.exists():
149
- print("产出区已存在: %s" % dst)
150
- print(" 如要覆盖, 先手动删除产出区的旧版本")
151
- return 1
152
- import shutil
153
- dst.parent.mkdir(parents=True, exist_ok=True)
154
- shutil.move(str(src), str(dst))
155
- print(" 已提升到产出区:")
156
- print(" %s" % dst)
157
- print(" (产出进 team_sync, 团队可见)")
158
- return 0
159
-
160
-
161
- def cmd_publish(req_id, dev=None):
162
- """发布 = promote + 发禅道。PM 只用这个。"""
163
- dev = dev or get_developer(BASE) or "unknown"
164
- # 先确保在 outputs (不在则 promote)
165
- out_found = [e for e in find_req_dirs(req_id, dev, BASE) if e["area"] == "outputs"]
166
- if not out_found:
167
- print("草稿 → 产出...")
168
- rc = cmd_promote(req_id, dev)
169
- if rc != 0:
170
- return rc
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(" 发布失败: 产出区找不到 %s" % req_id)
174
- return 1
175
- out_path = out_found[0]["path"]
176
- print()
177
- print("发禅道 (从产出区: %s)" % out_path)
178
- print("-" * 40)
179
- # 找禅道 MCP 创建需求
180
- try:
181
- sys.path.insert(0, str(BASE / ".qoder" / "scripts" / "mcp"))
182
- # 禅道发布: 读 PRD, 调 zentao MCP 建 story
183
- prd_file = None
184
- for f in out_path.iterdir():
185
- if f.is_file() and (f.name.endswith(".md") and "prd" in f.name.lower()):
186
- prd_file = f
187
- break
188
- if f.is_file() and f.suffix == ".md" and not f.name.startswith("_"):
189
- prd_file = f # 第一个 md 当 PRD
190
- if prd_file:
191
- print(" PRD: %s" % prd_file.name)
192
- print()
193
- print("⚠️ 禅道发布需要 QoderWork 的禅道 MCP (mcp__qoder-zentao__create_story)。")
194
- print(" AI 会读产出的 PRD, 调禅道建需求。")
195
- print(" 如果你在 QoderWork 里说'%s 发布', AI 会自动完成这步。" % req_id)
196
- return 0
197
- except Exception as e:
198
- print("✗ 发布出错: %s" % e)
199
- return 1
200
-
201
-
202
- def main():
203
- args = sys.argv[1:]
204
- if not args:
205
- return cmd_list()
206
- req_id = args[0]
207
- action = args[1] if len(args) > 1 else "show"
208
- if action == "show":
209
- return cmd_show(req_id)
210
- elif action in ("发布", "publish", "fabu"):
211
- return cmd_publish(req_id)
212
- elif action == "promote":
213
- return cmd_promote(req_id)
214
- elif action == "list":
215
- return cmd_list()
216
- else:
217
- # 默认当 show
218
- return cmd_show(req_id)
219
-
220
-
221
- if __name__ == "__main__":
222
- sys.exit(main())
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())
@@ -1,11 +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
+ # 子包初始化: 注入 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)
@@ -2,19 +2,25 @@
2
2
  # -*- coding: utf-8 -*-
3
3
  # v3.0 路径自举: 引导到 common/bootstrap, 统一 sys.path 逻辑
4
4
  import os as _o, sys as _s
5
- _cp = _o.path.join(_o.path.dirname(_o.path.dirname(_o.path.abspath(__file__))), 'common')
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
6
11
  if _cp not in _s.path: _s.path.insert(0, _cp)
7
12
  from bootstrap import setup; setup()
13
+ from foundation.core.paths import get_repo_root
8
14
 
9
15
  """
10
16
  Weekly Knowledge Graph Update
11
17
 
12
18
  Usage:
13
- python git_sync.py # Full update: git + PRD + index
14
- python git_sync.py --sync-only # Git pull only
15
- python git_sync.py --index-only # Re-index only (no git pull)
16
- python git_sync.py --prd-only # Collect + parse PRDs only
17
- 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
18
24
 
19
25
  What it does (Friday Update):
20
26
  1. Git pull latest code for each project
@@ -27,7 +33,7 @@ Config: .qoder/config.yaml -> git_sync section
27
33
  """
28
34
 
29
35
  import os
30
- from common.paths import BUILD_CACHE_DIR
36
+ from foundation.core.paths import BUILD_CACHE_DIR
31
37
  import sys
32
38
  import json
33
39
  import subprocess
@@ -38,7 +44,7 @@ from datetime import datetime
38
44
  if sys.platform == 'win32':
39
45
  sys.stdout.reconfigure(encoding='utf-8')
40
46
 
41
- BASE = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
47
+ BASE = get_repo_root()
42
48
  DATA_DIR = os.path.join(BASE, 'data')
43
49
  CODE_DIR = os.path.join(DATA_DIR, 'code')
44
50
  INDEX_DIR = os.path.join(DATA_DIR, 'index')
@@ -71,7 +77,7 @@ def save_json(path, data):
71
77
  def load_config():
72
78
  """Load config from config.yaml (v3.0: 转发到 Config 单例, 带缓存)。"""
73
79
  try:
74
- from common.config import Config
80
+ from foundation.core.config import Config
75
81
  return Config.load().raw
76
82
  except Exception:
77
83
  # 回退: 直接读 (sys.path 未就绪时)
@@ -203,18 +209,31 @@ def collect_prds():
203
209
  if not os.path.isdir(user_dir):
204
210
  continue
205
211
 
206
- drafts_dir = os.path.join(user_dir, 'drafts')
207
- if not os.path.isdir(drafts_dir):
208
- continue
209
-
210
- user_prds = []
211
- for f in os.listdir(drafts_dir):
212
- if (f.startswith('REQ-') or f.startswith('PRD-') or f.startswith('prd-')) and f.endswith('.md'):
213
- filepath = os.path.join(drafts_dir, f)
214
- mtime = os.path.getmtime(filepath)
215
- user_prds.append({'file': f, 'path': filepath, 'user': user_name, 'mtime': mtime})
216
-
217
- 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():
218
237
  key = prd['file']
219
238
  is_new = key not in collected
220
239
  is_updated = not is_new and collected[key].get('mtime', 0) < prd['mtime']