@hupan56/wlkj 3.0.0 → 3.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (237) hide show
  1. package/bin/cli.js +1435 -1373
  2. package/package.json +1 -1
  3. package/templates/qoder/commands/optional/wl-insight.md +6 -4
  4. package/templates/qoder/commands/optional/wl-report.md +20 -39
  5. package/templates/qoder/commands/optional/wl-status.md +4 -4
  6. package/templates/qoder/commands/wl-code.md +2 -2
  7. package/templates/qoder/commands/wl-design.md +5 -5
  8. package/templates/qoder/commands/wl-init.md +3 -3
  9. package/templates/qoder/commands/wl-prd.md +26 -13
  10. package/templates/qoder/commands/wl-req.md +3 -3
  11. package/templates/qoder/commands/wl-search.md +78 -47
  12. package/templates/qoder/commands/wl-task.md +343 -40
  13. package/templates/qoder/commands/wl-test.md +10 -10
  14. package/templates/qoder/config.yaml +35 -13
  15. package/templates/qoder/hooks/post-tool-use.py +181 -0
  16. package/templates/qoder/hooks/session-start.py +82 -1
  17. package/templates/qoder/hooks/stop-eval.py +207 -0
  18. package/templates/qoder/hooks/user-prompt-submit.py +139 -0
  19. package/templates/qoder/rules/wl-pipeline.md +70 -8
  20. package/templates/qoder/scripts/README.md +78 -115
  21. package/templates/qoder/scripts/capability/__init__.py +26 -0
  22. package/templates/qoder/scripts/capability/__main__.py +72 -0
  23. package/templates/qoder/scripts/capability/adapters/__init__.py +29 -0
  24. package/templates/qoder/scripts/capability/adapters/cli.py +316 -0
  25. package/templates/qoder/scripts/capability/adapters/mcp.py +334 -0
  26. package/templates/qoder/scripts/capability/adapters/qw.py +271 -0
  27. package/templates/qoder/scripts/capability/caps/__init__.py +27 -0
  28. package/templates/qoder/scripts/capability/caps/context.py +36 -0
  29. package/templates/qoder/scripts/capability/caps/cron.py +50 -0
  30. package/templates/qoder/scripts/capability/caps/identity.py +43 -0
  31. package/templates/qoder/scripts/capability/caps/memory.py +71 -0
  32. package/templates/qoder/scripts/capability/caps/notify.py +41 -0
  33. package/templates/qoder/scripts/capability/caps/present.py +48 -0
  34. package/templates/qoder/scripts/capability/caps/repo.py +31 -0
  35. package/templates/qoder/scripts/capability/caps/sandbox.py +43 -0
  36. package/templates/qoder/scripts/capability/chain.py +92 -0
  37. package/templates/qoder/scripts/capability/memory_chain.py +41 -0
  38. package/templates/qoder/scripts/capability/registry.py +246 -0
  39. package/templates/qoder/scripts/capability/registry_mcp.py +250 -0
  40. package/templates/qoder/scripts/capability/smoke_test.py +211 -0
  41. package/templates/qoder/scripts/capability/smoke_test_report.json +94 -0
  42. package/templates/qoder/scripts/{report → deployment/setup}/__init__.py +11 -11
  43. package/templates/qoder/scripts/{setup → deployment/setup}/carriers.py +33 -26
  44. package/templates/qoder/scripts/deployment/setup/carriers_verify.py +148 -0
  45. package/templates/qoder/scripts/{setup → deployment/setup}/init_doctor.py +40 -16
  46. package/templates/qoder/scripts/{setup → deployment/setup}/install_qoderwork.py +20 -12
  47. package/templates/qoder/scripts/{setup → deployment/setup}/platform_doctor.py +8 -2
  48. package/templates/qoder/scripts/{setup → deployment/setup}/repo_root.py +113 -112
  49. package/templates/qoder/scripts/{setup → deployment/setup}/setup.py +31 -11
  50. package/templates/qoder/scripts/{setup → deployment/setup}/setup_lanhu.py +13 -7
  51. package/templates/qoder/scripts/domain/__init__.py +0 -0
  52. package/templates/qoder/scripts/domain/design/__init__.py +0 -0
  53. package/templates/qoder/scripts/{report → domain/design}/fill_prototype.py +12 -6
  54. package/templates/qoder/scripts/{report → domain/design}/gen_design_doc.py +8 -2
  55. package/templates/qoder/scripts/{setup → domain/kg}/__init__.py +11 -11
  56. package/templates/qoder/scripts/domain/kg/build/__init__.py +0 -0
  57. package/templates/qoder/scripts/{kg → domain/kg/build}/build_entity_registry.py +7 -2
  58. package/templates/qoder/scripts/{kg → domain/kg/build}/build_relations.py +7 -2
  59. package/templates/qoder/scripts/{kg → domain/kg/build}/build_style_index.py +13 -7
  60. package/templates/qoder/scripts/{kg → domain/kg/build}/build_workflows.py +7 -2
  61. package/templates/qoder/scripts/{kg → domain/kg/build}/kg_build.py +76 -9
  62. package/templates/qoder/scripts/{kg → domain/kg/build}/kg_build_db.py +7 -2
  63. package/templates/qoder/scripts/{kg → domain/kg/build}/kg_incremental.py +11 -6
  64. package/templates/qoder/scripts/{report → domain/kg/build}/learn_aggregate.py +9 -4
  65. package/templates/qoder/scripts/domain/kg/extract/__init__.py +0 -0
  66. package/templates/qoder/scripts/{common → domain/kg/extract}/extract.py +430 -419
  67. package/templates/qoder/scripts/{common → domain/kg/extract}/test_extract.py +115 -115
  68. package/templates/qoder/scripts/{common → domain/kg/extract}/ts_extract.py +614 -536
  69. package/templates/qoder/scripts/domain/kg/graph/__init__.py +0 -0
  70. package/templates/qoder/scripts/{kg → domain/kg/graph}/kg_link_db.py +7 -2
  71. package/templates/qoder/scripts/domain/kg/graph/kg_semantic.py +683 -0
  72. package/templates/qoder/scripts/{kg → domain/kg}/kg.py +263 -30
  73. package/templates/qoder/scripts/{common → domain/kg}/kg_capabilities.py +182 -182
  74. package/templates/qoder/scripts/domain/kg/search/__init__.py +0 -0
  75. package/templates/qoder/scripts/{kg → domain/kg/search}/context_pack.py +30 -9
  76. package/templates/qoder/scripts/{kg → domain/kg/search}/enrich_prompt.py +10 -4
  77. package/templates/qoder/scripts/{kg → domain/kg/search}/prefetch.py +33 -8
  78. package/templates/qoder/scripts/{common → domain/kg/search}/search_engine.py +207 -205
  79. package/templates/qoder/scripts/{kg → domain/kg/search}/search_index.py +143 -77
  80. package/templates/qoder/scripts/domain/kg/server/__init__.py +0 -0
  81. package/templates/qoder/scripts/domain/kg/server/kgd.py +549 -0
  82. package/templates/qoder/scripts/domain/kg/server/perf_bench.py +197 -0
  83. package/templates/qoder/scripts/domain/kg/storage/__init__.py +0 -0
  84. package/templates/qoder/scripts/{kg → domain/kg/storage}/kg_duckdb.py +38 -14
  85. package/templates/qoder/scripts/domain/learning/__init__.py +0 -0
  86. package/templates/qoder/scripts/{report → domain/learning}/learn.py +8 -3
  87. package/templates/qoder/scripts/{mcp → domain/report}/__init__.py +11 -11
  88. package/templates/qoder/scripts/{report → domain/report}/add_session.py +9 -3
  89. package/templates/qoder/scripts/{report → domain/report}/export.py +10 -5
  90. package/templates/qoder/scripts/{report → domain/report}/report.py +9 -4
  91. package/templates/qoder/scripts/domain/report/report_snapshot.py +261 -0
  92. package/templates/qoder/scripts/{report → domain/report}/role.py +10 -4
  93. package/templates/qoder/scripts/{report → domain/report}/status.py +112 -9
  94. package/templates/qoder/scripts/domain/report/status_snapshot.py +191 -0
  95. package/templates/qoder/scripts/domain/requirement/__init__.py +0 -0
  96. package/templates/qoder/scripts/{report → domain/requirement}/archive_prd.py +8 -2
  97. package/templates/qoder/scripts/{report → domain/requirement}/req.py +228 -222
  98. package/templates/qoder/scripts/{kg → domain/task}/__init__.py +11 -11
  99. package/templates/qoder/scripts/{task → domain/task}/git_sync.py +40 -21
  100. package/templates/qoder/scripts/{task → domain/task}/syncgate.py +13 -8
  101. package/templates/qoder/scripts/{task → domain/task}/task.py +13 -5
  102. package/templates/qoder/scripts/{task → domain/task}/task_lifecycle.py +606 -596
  103. package/templates/qoder/scripts/{task → domain/task}/task_query.py +162 -161
  104. package/templates/qoder/scripts/{task → domain/task}/task_relations.py +425 -424
  105. package/templates/qoder/scripts/{task → domain/task}/team_sync.py +9 -4
  106. package/templates/qoder/scripts/domain/task/zentao_sync.py +370 -0
  107. package/templates/qoder/scripts/foundation/__init__.py +0 -0
  108. package/templates/qoder/scripts/{common → foundation}/bootstrap.py +10 -10
  109. package/templates/qoder/scripts/foundation/core/__init__.py +0 -0
  110. package/templates/qoder/scripts/{common → foundation/core}/cmd_registry.py +1 -1
  111. package/templates/qoder/scripts/{common → foundation/core}/config.py +1 -1
  112. package/templates/qoder/scripts/{common → foundation/core}/paths.py +706 -586
  113. package/templates/qoder/scripts/foundation/data/__init__.py +0 -0
  114. package/templates/qoder/scripts/{common → foundation/data}/contract.py +2 -2
  115. package/templates/qoder/scripts/{common → foundation/data}/events.py +2 -2
  116. package/templates/qoder/scripts/{common → foundation/data}/result.py +1 -1
  117. package/templates/qoder/scripts/{common → foundation/data}/task_utils.py +427 -404
  118. package/templates/qoder/scripts/foundation/identity/__init__.py +0 -0
  119. package/templates/qoder/scripts/{common → foundation/identity}/check_publish.py +103 -98
  120. package/templates/qoder/scripts/{common → foundation/identity}/developer.py +239 -239
  121. package/templates/qoder/scripts/{common → foundation/identity}/guard.py +2 -2
  122. package/templates/qoder/scripts/{common → foundation/identity}/identity.py +14 -10
  123. package/templates/qoder/scripts/{common → foundation/identity}/roles.py +1 -1
  124. package/templates/qoder/scripts/foundation/integrations/__init__.py +0 -0
  125. package/templates/qoder/scripts/{common → foundation/integrations}/active_task.py +30 -15
  126. package/templates/qoder/scripts/{common → foundation/integrations}/feishu.py +2 -2
  127. package/templates/qoder/scripts/{common → foundation/integrations}/terms.py +3 -3
  128. package/templates/qoder/scripts/foundation/integrations/zentao_client.py +220 -0
  129. package/templates/qoder/scripts/foundation/io/__init__.py +0 -0
  130. package/templates/qoder/scripts/{common → foundation/io}/filelock.py +1 -1
  131. package/templates/qoder/scripts/{common → foundation/io}/reqid.py +1 -1
  132. package/templates/qoder/scripts/foundation/protocol/__init__.py +0 -0
  133. package/templates/qoder/scripts/foundation/protocol/duckdb_conn.py +39 -0
  134. package/templates/qoder/scripts/{common → foundation/protocol}/mcp_base.py +1 -1
  135. package/templates/qoder/scripts/foundation/utils/__init__.py +0 -0
  136. package/templates/qoder/scripts/{common → foundation/utils}/eval_api.py +12 -1
  137. package/templates/qoder/scripts/{common → foundation/utils}/pip_install.py +23 -6
  138. package/templates/qoder/scripts/{common → foundation/utils}/platform_guard.py +1 -1
  139. package/templates/qoder/scripts/orchestration/__init__.py +0 -0
  140. package/templates/qoder/scripts/orchestration/wlkj.py +185 -0
  141. package/templates/qoder/scripts/protocol/__init__.py +0 -0
  142. package/templates/qoder/scripts/protocol/browser/README.md +207 -0
  143. package/templates/qoder/scripts/protocol/browser/SKILL.md +265 -0
  144. package/templates/qoder/scripts/protocol/browser/config.env +9 -0
  145. package/templates/qoder/scripts/protocol/browser/references/cdp-api.md +110 -0
  146. package/templates/qoder/scripts/protocol/browser/references/migration-2.5.3.md +72 -0
  147. package/templates/qoder/scripts/protocol/browser/references/site-patterns/.gitkeep +0 -0
  148. package/templates/qoder/scripts/protocol/browser/scripts/browser-discovery.mjs +138 -0
  149. package/templates/qoder/scripts/protocol/browser/scripts/cdp-proxy.mjs +672 -0
  150. package/templates/qoder/scripts/protocol/browser/scripts/check-deps.mjs +206 -0
  151. package/templates/qoder/scripts/protocol/browser/scripts/find-url.mjs +253 -0
  152. package/templates/qoder/scripts/protocol/browser/scripts/match-site.mjs +46 -0
  153. package/templates/qoder/scripts/protocol/browser/templates/config.env.template +9 -0
  154. package/templates/qoder/scripts/protocol/mcp/__init__.py +11 -0
  155. package/templates/qoder/scripts/protocol/mcp/kg_mcp_server.py +478 -0
  156. package/templates/qoder/scripts/{mcp → protocol/mcp}/lanhu_stdio_wrapper.py +11 -5
  157. package/templates/qoder/scripts/{mcp → protocol/mcp}/mcp_doctor.py +47 -14
  158. package/templates/qoder/scripts/protocol/mcp/mcp_launcher.py +600 -0
  159. package/templates/qoder/scripts/{mcp → protocol/mcp}/mysql_mcp_server.py +181 -67
  160. package/templates/qoder/scripts/protocol/mcp/zentao_mcp_server.py +1920 -0
  161. package/templates/qoder/scripts/protocol/transports/__init__.py +56 -0
  162. package/templates/qoder/scripts/protocol/transports/base.py +87 -0
  163. package/templates/qoder/scripts/protocol/transports/cli.py +132 -0
  164. package/templates/qoder/scripts/protocol/transports/http.py +141 -0
  165. package/templates/qoder/scripts/protocol/transports/stdio.py +293 -0
  166. package/templates/qoder/scripts/run_weekly_update.bat +27 -18
  167. package/templates/qoder/scripts/run_weekly_update.sh +22 -13
  168. package/templates/qoder/scripts/validation/__init__.py +0 -0
  169. package/templates/qoder/scripts/validation/eval/__init__.py +0 -0
  170. package/templates/qoder/scripts/validation/eval/perf-report-2026-06-24.md +139 -0
  171. package/templates/qoder/scripts/validation/eval/qwork_harness.py +617 -0
  172. package/templates/qoder/scripts/validation/eval/report-commands.md +220 -0
  173. package/templates/qoder/scripts/validation/eval/transcript_timing.py +386 -0
  174. package/templates/qoder/scripts/validation/metrics/__init__.py +0 -0
  175. package/templates/qoder/scripts/{report → validation/metrics}/eval_prd.py +17 -10
  176. package/templates/qoder/scripts/validation/metrics/usability_score.py +750 -0
  177. package/templates/qoder/scripts/validation/test/__init__.py +11 -0
  178. package/templates/qoder/scripts/validation/test/assertion_gen.py +551 -0
  179. package/templates/qoder/scripts/{test → validation/test}/autotest.py +64 -4
  180. package/templates/qoder/scripts/{common → validation/test}/autotest_auth.py +1 -1
  181. package/templates/qoder/scripts/{test → validation/test}/autotest_batch.py +16 -3
  182. package/templates/qoder/scripts/{test → validation/test}/autotest_data.py +17 -12
  183. package/templates/qoder/scripts/validation/test/autotest_opencli.py +646 -0
  184. package/templates/qoder/scripts/{test → validation/test}/autotest_run.py +17 -3
  185. package/templates/qoder/scripts/validation/test/autotest_webaccess.py +571 -0
  186. package/templates/qoder/scripts/{test → validation/test}/benchmark.py +12 -6
  187. package/templates/qoder/scripts/{test → validation/test}/kg_auto_login.py +8 -2
  188. package/templates/qoder/scripts/{test → validation/test}/kg_test_runner.py +7 -2
  189. package/templates/qoder/scripts/{test → validation/test}/page_probe.py +8 -3
  190. package/templates/qoder/scripts/validation/test/smoke_all_commands.py +170 -0
  191. package/templates/qoder/settings.json +30 -0
  192. package/templates/qoder/skills/design-import/SKILL.md +22 -13
  193. package/templates/qoder/skills/design-review/SKILL.md +19 -10
  194. package/templates/qoder/skills/prd-generator/SKILL.md +22 -18
  195. package/templates/qoder/skills/prd-review/SKILL.md +6 -4
  196. package/templates/qoder/skills/prototype-generator/SKILL.md +63 -14
  197. package/templates/qoder/skills/spec-coder/SKILL.md +22 -13
  198. package/templates/qoder/skills/spec-generator/SKILL.md +22 -10
  199. package/templates/qoder/skills/test-generator/SKILL.md +7 -5
  200. package/templates/qoder/skills/wl-code/SKILL.md +23 -11
  201. package/templates/qoder/skills/wl-commit/SKILL.md +9 -8
  202. package/templates/qoder/skills/wl-design/SKILL.md +5 -3
  203. package/templates/qoder/skills/wl-init/SKILL.md +6 -6
  204. package/templates/qoder/skills/wl-insight/SKILL.md +23 -10
  205. package/templates/qoder/skills/wl-prd-full/SKILL.md +57 -6
  206. package/templates/qoder/skills/wl-prd-quick/SKILL.md +3 -3
  207. package/templates/qoder/skills/wl-prd-review/SKILL.md +16 -4
  208. package/templates/qoder/skills/wl-report/SKILL.md +25 -23
  209. package/templates/qoder/skills/wl-search/SKILL.md +149 -78
  210. package/templates/qoder/skills/wl-spec/SKILL.md +7 -5
  211. package/templates/qoder/skills/wl-status/SKILL.md +48 -16
  212. package/templates/qoder/skills/wl-task/SKILL.md +92 -10
  213. package/templates/qoder/skills/wl-test/SKILL.md +75 -29
  214. package/templates/qoder/templates/prd-full-template.md +7 -0
  215. package/templates/root/AGENTS.md +275 -236
  216. package/templates/root/requirements.txt +3 -0
  217. package/templates/root//344/275/277/347/224/250/350/257/264/346/230/216.md +3 -3
  218. package/templates/root//346/226/260/346/211/213/346/214/207/345/215/227.md +2 -2
  219. package/templates/.qoder/.runtime/ctx-cache-5660152f1d6dd819.md +0 -23
  220. package/templates/.qoder/.runtime/ctx-cache-afdce0dac06b25b0.md +0 -23
  221. package/templates/.qoder/.runtime/search-cache-eae7644e7b122f35.txt +0 -1
  222. package/templates/.qoder/learning/eval-history.jsonl +0 -28
  223. package/templates/data/index/wiki-index.json +0 -8
  224. package/templates/qoder/nul +0 -4
  225. package/templates/qoder/scripts/kg/extract.py +0 -398
  226. package/templates/qoder/scripts/kg/kg_semantic.py +0 -156
  227. package/templates/qoder/scripts/mcp/kg_mcp_server.py +0 -606
  228. package/templates/qoder/scripts/mcp/mcp_launcher.py +0 -442
  229. package/templates/qoder/scripts/mcp/zentao_mcp_server.py +0 -384
  230. package/templates/qoder/scripts/task/__init__.py +0 -11
  231. package/templates/qoder/scripts/test/__init__.py +0 -11
  232. package/templates/qoder/scripts/wlkj.py +0 -116
  233. /package/templates/qoder/scripts/{common → deployment}/__init__.py +0 -0
  234. /package/templates/qoder/scripts/{common → domain/kg/graph}/graph_traverse.py +0 -0
  235. /package/templates/qoder/scripts/{common → domain/kg/server}/repowiki.py +0 -0
  236. /package/templates/qoder/scripts/{common → foundation/io}/atomicio.py +0 -0
  237. /package/templates/qoder/scripts/{test → validation/test}/secure-ls.js +0 -0
@@ -0,0 +1,261 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ report_snapshot.py — /wl-report 的一次性数据聚合器 (性能优化)
4
+
5
+ 问题: wl-report skill 让 AI 逐个 cat/ls/git log 收集数据, 实测产生 5-7 次工具调用、
6
+ 多轮思考。本脚本把 report 需要的全部数据源**一次返回**, 把 7 步顺序采集压成 2 步。
7
+
8
+ 与 status_snapshot.py 同构 (Fast Path 模式), 只是数据源换成 report 需要的:
9
+ 1. .developer — 当前开发者 (报告归属 + 全局过滤键)
10
+ 2. task.py list --mine — 我创建/被指派的任务
11
+ 3. journal/feedback — 今天的学习/反馈事件
12
+ 4. git log --author — 我的提交 (扫 data/code/ 各项目)
13
+ 5. drafts/ + specs/prd — 我写的 PRD/草稿
14
+
15
+ 输出: JSON, 缺失的字段标 null 并注明原因。AI 拿到后直接写报告, 不用再探查。
16
+ 所有数据严格按 .developer 里的开发者名过滤 (report 的隔离铁律)。
17
+
18
+ 用法:
19
+ python report_snapshot.py # 当前仓库, 默认今天
20
+ python report_snapshot.py /path/repo # 指定仓库
21
+ python report_snapshot.py --days 7 # 近 7 天 (周报)
22
+ """
23
+ import json
24
+ import os
25
+ import subprocess
26
+ import sys
27
+ from datetime import datetime, timedelta
28
+
29
+
30
+ def _repo_root():
31
+ """优先用命令行参数, 否则从环境反推 (.qoder 标记)。"""
32
+ args = [a for a in sys.argv[1:] if not a.startswith("--")]
33
+ if args and os.path.isdir(args[0]):
34
+ return os.path.abspath(args[0])
35
+ d = os.getcwd()
36
+ for _ in range(8):
37
+ if os.path.isdir(os.path.join(d, ".qoder")):
38
+ return d
39
+ d = os.path.dirname(d)
40
+ return os.getcwd()
41
+
42
+
43
+ def _days():
44
+ """--days N 参数, 默认 1 (今天/日报)。"""
45
+ for i, a in enumerate(sys.argv):
46
+ if a == "--days" and i + 1 < len(sys.argv):
47
+ try:
48
+ return int(sys.argv[i + 1])
49
+ except ValueError:
50
+ pass
51
+ return 1
52
+
53
+
54
+ def _read_text(path, limit=4000):
55
+ """安全读文本文件, 不存在返回 None。"""
56
+ if not os.path.exists(path):
57
+ return None
58
+ try:
59
+ with open(path, "r", encoding="utf-8") as f:
60
+ return f.read(limit)
61
+ except Exception as e:
62
+ return f"<read_error: {e}>"
63
+
64
+
65
+ def _parse_developer(text):
66
+ """从 .developer 文本里取 name= 行的值; 无则取首行。"""
67
+ if not text:
68
+ return None
69
+ for line in text.splitlines():
70
+ line = line.strip()
71
+ if line.startswith("name="):
72
+ return line[5:].strip()
73
+ first = text.splitlines()[0].strip() if text.strip() else None
74
+ return first or None
75
+
76
+
77
+ def _run_script(script_rel, repo, args=None, timeout=20):
78
+ """运行 .qoder/scripts 下的脚本, 返回 (stdout, ok)。"""
79
+ script = os.path.join(repo, script_rel)
80
+ if not os.path.exists(script):
81
+ return None, False
82
+ try:
83
+ cmd = [sys.executable, script] + (args or [])
84
+ r = subprocess.run(
85
+ cmd, cwd=repo, capture_output=True, text=True, timeout=timeout,
86
+ encoding="utf-8", errors="replace",
87
+ )
88
+ return (r.stdout.strip() if r.returncode == 0 else r.stderr.strip()), r.returncode == 0
89
+ except Exception as e:
90
+ return f"<run_error: {e}>", False
91
+
92
+
93
+ def _git_log_for_author(repo, author, days):
94
+ """扫 data/code/ 下各项目的 git log, 按作者过滤。返回 {project: [commits]}。"""
95
+ code_root = os.path.join(repo, "data", "code")
96
+ if not os.path.isdir(code_root):
97
+ return None
98
+ since = (datetime.now() - timedelta(days=days)).strftime("%Y-%m-%d")
99
+ result = {}
100
+ try:
101
+ for name in sorted(os.listdir(code_root)):
102
+ proj = os.path.join(code_root, name)
103
+ if not os.path.isdir(os.path.join(proj, ".git")):
104
+ continue
105
+ try:
106
+ r = subprocess.run(
107
+ ["git", "-C", proj, "log", f"--author={author}",
108
+ f"--since={since}", "--oneline", "-n", "50"],
109
+ capture_output=True, text=True, timeout=8,
110
+ encoding="utf-8", errors="replace",
111
+ )
112
+ out = r.stdout.strip() if r.returncode == 0 else ""
113
+ if out:
114
+ commits = [l for l in out.splitlines() if l.strip()]
115
+ result[name] = commits[:50]
116
+ except Exception:
117
+ continue
118
+ except Exception:
119
+ pass
120
+ return result if result else {}
121
+
122
+
123
+ def _list_prds_by_author(repo, author, days):
124
+ """收集 workspace/specs/prd/*.md 和 members/*/drafts 里 author==developer 的 PRD。
125
+ frontmatter 里有 author 字段就按它过滤; 草稿目录按开发者归属过滤。"""
126
+ since = datetime.now() - timedelta(days=days + 30) # PRD 按文件宽松一点
127
+ hits = []
128
+
129
+ # 1. 团队归档的 PRD (按 frontmatter author 过滤)
130
+ prd_dir = os.path.join(repo, "workspace", "specs", "prd")
131
+ if os.path.isdir(prd_dir):
132
+ for name in sorted(os.listdir(prd_dir)):
133
+ if not name.endswith(".md"):
134
+ continue
135
+ fp = os.path.join(prd_dir, name)
136
+ try:
137
+ mtime = datetime.fromtimestamp(os.path.getmtime(fp))
138
+ if mtime < since:
139
+ continue
140
+ head = _read_text(fp, limit=600) or ""
141
+ # frontmatter 里找 author:
142
+ is_mine = False
143
+ if "author:" in head.lower() or "author =" in head.lower():
144
+ is_mine = author.lower() in head.lower()
145
+ if is_mine:
146
+ title = name[:-3]
147
+ hits.append({"file": f"workspace/specs/prd/{name}",
148
+ "title": title, "mtime": mtime.strftime("%Y-%m-%d")})
149
+ except Exception:
150
+ continue
151
+
152
+ # 2. 个人草稿 (按目录归属天然隔离, 整个目录都是该开发者的)
153
+ dev_drafts = os.path.join(repo, "workspace", "members", author, "drafts")
154
+ if os.path.isdir(dev_drafts):
155
+ for root, _dirs, files in os.walk(dev_drafts):
156
+ for fn in files:
157
+ if fn.endswith(".md"):
158
+ fp = os.path.join(root, fn)
159
+ try:
160
+ mtime = datetime.fromtimestamp(os.path.getmtime(fp))
161
+ if mtime >= since:
162
+ rel = os.path.relpath(fp, repo).replace("\\", "/")
163
+ hits.append({"file": rel, "title": fn[:-3],
164
+ "mtime": mtime.strftime("%Y-%m-%d")})
165
+ except Exception:
166
+ continue
167
+ return hits[:50] if hits else []
168
+
169
+
170
+ def _journal_today(repo, author, days):
171
+ """读该开发者 journal 目录近 days 天的 feedback/learning 事件。"""
172
+ journal = os.path.join(repo, "workspace", "members", author, "journal")
173
+ if not os.path.isdir(journal):
174
+ return None
175
+ since = datetime.now() - timedelta(days=days)
176
+ events = []
177
+ for fn in sorted(os.listdir(journal)):
178
+ fp = os.path.join(journal, fn)
179
+ if not os.path.isfile(fp):
180
+ continue
181
+ try:
182
+ mtime = datetime.fromtimestamp(os.path.getmtime(fp))
183
+ if mtime < since:
184
+ continue
185
+ if fn.endswith(".jsonl"):
186
+ # feedback.jsonl: 读近几天的事件行
187
+ with open(fp, encoding="utf-8") as f:
188
+ for line in f:
189
+ line = line.strip()
190
+ if not line:
191
+ continue
192
+ try:
193
+ ev = json.loads(line)
194
+ # 事件带时间戳则按时间过滤, 否则按文件 mtime 已过滤
195
+ events.append(ev)
196
+ except json.JSONDecodeError:
197
+ continue
198
+ elif fn.endswith((".yaml", ".yml")):
199
+ txt = _read_text(fp, limit=2000)
200
+ if txt:
201
+ events.append({"_file": fn, "_summary": txt[:500]})
202
+ except Exception:
203
+ continue
204
+ return events[:100] if events else []
205
+
206
+
207
+ def snapshot(repo=None, days=None):
208
+ """收集 wl-report 需要的全部数据, 返回 dict。"""
209
+ repo = repo or _repo_root()
210
+ days = days or _days()
211
+ qd = os.path.join(repo, ".qoder")
212
+
213
+ data = {"_repo": repo, "_ts": datetime.now().isoformat(timespec="seconds"),
214
+ "_days": days}
215
+
216
+ # 1. 开发者身份 (report 的全局过滤键)
217
+ dev_text = _read_text(os.path.join(qd, ".developer"))
218
+ developer = _parse_developer(dev_text)
219
+ data["developer"] = developer or "(未设置)"
220
+ data["developer_raw"] = dev_text
221
+
222
+ if not developer or developer == "(未设置)":
223
+ data["_note"] = "未设置 .developer, 无法按开发者过滤。先 /wl-init。"
224
+ return data
225
+
226
+ # 2-5 四个独立数据源并行采集 (task list 最慢~440ms, 并行后总耗时≈最慢的那个)
227
+ # task list / git log / journal / prds 互不依赖 → 用线程池同时跑, 砍掉串行等待。
228
+ from concurrent.futures import ThreadPoolExecutor
229
+ with ThreadPoolExecutor(max_workers=4) as pool:
230
+ f_tasks = pool.submit(_run_script, ".qoder/scripts/domain/task/task.py", repo,
231
+ ["list", "--mine"])
232
+ f_journal = pool.submit(_journal_today, repo, developer, days)
233
+ f_commits = pool.submit(_git_log_for_author, repo, developer, days)
234
+ f_prds = pool.submit(_list_prds_by_author, repo, developer, days)
235
+
236
+ task_out, task_ok = f_tasks.result()
237
+ data["my_tasks"] = task_out if task_ok else None
238
+ data["my_tasks_error"] = None if task_ok else task_out
239
+ data["journal"] = f_journal.result()
240
+ data["commits"] = f_commits.result()
241
+ data["my_prds"] = f_prds.result()
242
+
243
+ # 6. 报告日期范围 (供 AI 套模板)
244
+ today = datetime.now().strftime("%Y-%m-%d")
245
+ start = (datetime.now() - timedelta(days=days - 1)).strftime("%Y-%m-%d")
246
+ data["date_range"] = f"{start} ~ {today}" if days > 1 else today
247
+
248
+ return data
249
+
250
+
251
+ def main():
252
+ repo = None
253
+ args = [a for a in sys.argv[1:] if not a.startswith("--")]
254
+ if args:
255
+ repo = args[0]
256
+ data = snapshot(repo)
257
+ print(json.dumps(data, ensure_ascii=False, indent=2, default=str))
258
+
259
+
260
+ if __name__ == "__main__":
261
+ main()
@@ -1,8 +1,14 @@
1
1
  # v3.0 路径自举: 引导到 common/bootstrap, 统一 sys.path 逻辑
2
2
  import os as _o, sys as _s
3
- _cp = _o.path.join(_o.path.dirname(_o.path.dirname(_o.path.abspath(__file__))), 'common')
3
+ _f = _o.path.abspath(__file__)
4
+ for _ in range(10):
5
+ _f = _o.path.dirname(_f)
6
+ _cp = _o.path.join(_f, 'foundation')
7
+ if _o.path.isfile(_o.path.join(_cp, 'bootstrap.py')):
8
+ break
4
9
  if _cp not in _s.path: _s.path.insert(0, _cp)
5
10
  from bootstrap import setup; setup()
11
+ from foundation.core.paths import get_repo_root
6
12
 
7
13
  # role.py - 角色解析 (v3.0: 已并入 common/identity.py, 本文件为兼容转发层)
8
14
  #
@@ -11,15 +17,15 @@ from bootstrap import setup; setup()
11
17
  # 1. CLI 兼容: skill 脚本里 `ROLE=$(python role.py)` 仍能输出角色名
12
18
  # 2. import 兼容: `from role import get_role` 仍可用 (转发到 identity)
13
19
  #
14
- # ⚠️ 新代码请直接用: from common.identity import get_role / from common.guard import ...
20
+ # ⚠️ 新代码请直接用: from foundation.identity.identity import get_role / from foundation.identity.guard import ...
15
21
  # 本文件未来随 M2 引擎重构可能移除。
16
22
  import os
17
23
  import sys
18
24
 
19
25
  sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
20
- from common.identity import get_role, get_permissions, get_capabilities, can_produce, can_consume # noqa: E402,F401
26
+ from foundation.identity.identity import get_role, get_permissions, get_capabilities, can_produce, can_consume # noqa: E402,F401
21
27
 
22
- BASE = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
28
+ BASE = get_repo_root()
23
29
 
24
30
 
25
31
  if __name__ == '__main__':
@@ -2,7 +2,12 @@
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()
8
13
 
@@ -34,9 +39,9 @@ except (AttributeError, TypeError, OSError):
34
39
 
35
40
  THIS_DIR = os.path.dirname(os.path.abspath(__file__))
36
41
  sys.path.insert(0, THIS_DIR)
37
- from common.paths import get_repo_root, get_developer, get_tasks_dir, MEMBERS_DIR
38
- from common.task_utils import load_task_json
39
- from common.atomicio import safe_read_json
42
+ from foundation.core.paths import get_repo_root, get_developer, get_tasks_dir, MEMBERS_DIR, EVAL_HISTORY_PATH
43
+ from foundation.data.task_utils import load_task_json
44
+ from foundation.io.atomicio import safe_read_json
40
45
 
41
46
  BASE = get_repo_root()
42
47
  INDEX_DIR = BASE / 'data' / 'index'
@@ -230,6 +235,100 @@ def _count_all_feedback():
230
235
  return total
231
236
 
232
237
 
238
+ def _compute_adoption_score():
239
+ """工作流采纳度 (0-5), 供 compute_health 的 adoption 维度用。
240
+
241
+ 与 usability_score.py 的 adoption_score 同源逻辑 (避免 import 验证脚本产生环依赖, 内联):
242
+ 取能算的自动采纳指标 (U2一次过审/U3返工/U4违规/U6活跃), 按达标率映射 0-5。
243
+ 无数据 → 中性 2.5。
244
+ """
245
+ # U2 一次过审率 + U3 返工 (从 eval-history)
246
+ records = []
247
+ if EVAL_HISTORY_PATH.is_file():
248
+ try:
249
+ with open(EVAL_HISTORY_PATH, encoding='utf-8') as f:
250
+ for line in f:
251
+ line = line.strip()
252
+ if line:
253
+ records.append(json.loads(line))
254
+ except Exception:
255
+ pass
256
+
257
+ scored = [] # 每项 1.0(绿)/0.5(黄)/0(红)
258
+
259
+ # U2: 首轮过审率
260
+ by_prd = {}
261
+ for r in records:
262
+ prd = r.get('prd')
263
+ if not prd:
264
+ continue
265
+ t = r.get('time', '')
266
+ if prd not in by_prd or (t and t < by_prd[prd][0]):
267
+ by_prd[prd] = (t, r)
268
+ if by_prd:
269
+ first_pass = sum(1 for (t, row) in by_prd.values() if row.get('passed'))
270
+ u2_rate = 100.0 * first_pass / len(by_prd)
271
+ scored.append(1.0 if u2_rate >= 60 else (0.5 if u2_rate >= 30 else 0.0))
272
+
273
+ # U3: 平均返工轮数 (越低越好)
274
+ if by_prd:
275
+ per_prd = {}
276
+ for r in records:
277
+ if r.get('prd'):
278
+ per_prd.setdefault(r['prd'], []).append(r)
279
+ total_evals = sum(len(v) for v in per_prd.values())
280
+ avg_rework = total_evals / len(per_prd) - 1.0
281
+ avg_rework = max(avg_rework, 0.0)
282
+ scored.append(1.0 if avg_rework <= 1.0 else (0.5 if avg_rework <= 2.0 else 0.0))
283
+
284
+ # U4 规则违反率 + U6 活跃 (从 feedback)
285
+ violations = 0
286
+ prds_gen = 0
287
+ active_devs = set()
288
+ now = datetime.now()
289
+ week_ago = now - timedelta(days=7)
290
+ if MEMBERS_DIR.is_dir():
291
+ for dev_dir in MEMBERS_DIR.iterdir():
292
+ if not dev_dir.is_dir():
293
+ continue
294
+ fb = dev_dir / 'journal' / 'feedback.jsonl'
295
+ if not fb.is_file():
296
+ continue
297
+ try:
298
+ with open(fb, encoding='utf-8') as f:
299
+ for line in f:
300
+ line = line.strip()
301
+ if not line:
302
+ continue
303
+ e = json.loads(line)
304
+ if e.get('event') == 'rule_violation':
305
+ violations += 1
306
+ if e.get('event') == 'prd_accepted':
307
+ prds_gen += 1
308
+ # U6: 近7天有事件的 dev
309
+ ts = e.get('ts', '')
310
+ try:
311
+ t = datetime.fromisoformat(str(ts).split('.')[0])
312
+ if t >= week_ago:
313
+ active_devs.add(e.get('dev'))
314
+ except (ValueError, TypeError):
315
+ pass
316
+ except Exception:
317
+ continue
318
+ # U4
319
+ if prds_gen:
320
+ vrate = 100.0 * violations / prds_gen
321
+ scored.append(1.0 if vrate <= 10 else (0.5 if vrate <= 25 else 0.0))
322
+ # U6
323
+ n_devs = len(active_devs)
324
+ scored.append(1.0 if n_devs >= 2 else (0.5 if n_devs == 1 else 0.0))
325
+
326
+ if not scored:
327
+ return 2.5 # 无数据, 中性
328
+ # 达标率 → 0-5 (1.0全绿=5, 全红=0)
329
+ return round(5.0 * sum(scored) / len(scored), 2)
330
+
331
+
233
332
  # ============================================================
234
333
  # 健康分 (综合)
235
334
  # ============================================================
@@ -243,7 +342,7 @@ def compute_health(tasks):
243
342
 
244
343
  # 1. EVA 合格率 (25%)
245
344
  # eval-history 是 jsonl (每行一个 JSON), 不能用 safe_read_json
246
- eval_path = BASE / '.qoder' / 'learning' / 'eval-history.jsonl'
345
+ eval_path = EVAL_HISTORY_PATH
247
346
  if eval_path.is_file():
248
347
  records = []
249
348
  try:
@@ -301,17 +400,21 @@ def compute_health(tasks):
301
400
  else:
302
401
  scores['pipeline'] = 2.0
303
402
 
304
- # 6. 学习 (10%)
403
+ # 6. 学习 (5%)
305
404
  # 修 bug: 旧代码读 data/learning/feedback.jsonl (旧路径, 已废弃),
306
405
  # 但 learn.py 实际写 workspace/members/{dev}/journal/feedback.jsonl。
307
406
  # 迁移了写路径没迁移读路径 → 学习度恒为 2.0。改为扫全部 dev 的 journal。
308
407
  fb_count = _count_all_feedback()
309
408
  scores['learning'] = min(5.0, 2.0 + fb_count * 0.1) if fb_count else 2.0
310
409
 
410
+ # 7. 工作流采纳 (10%) — 与 EVA/学习并列, 回答"用不用得起来"
411
+ # 复用 usability_score.py 的采纳分逻辑 (不 import 验证脚本, 内联等价计算避免环依赖)
412
+ scores['adoption'] = _compute_adoption_score()
413
+
311
414
  # 加权
312
415
  weights = {
313
416
  'eva': 0.25, 'index': 0.20, 'on_time': 0.20,
314
- 'sync': 0.15, 'pipeline': 0.10, 'learning': 0.10,
417
+ 'sync': 0.15, 'pipeline': 0.05, 'learning': 0.05, 'adoption': 0.10,
315
418
  }
316
419
  total = sum(scores[k] * weights[k] for k in weights)
317
420
  return {
@@ -356,7 +459,7 @@ def _analyze_eva_rounds():
356
459
  返回 {samples, rework_count, avg_rounds}。
357
460
  rework = 连续 passed=false 后出现 passed=true 的序列。
358
461
  """
359
- eval_path = BASE / '.qoder' / 'learning' / 'eval-history.jsonl'
462
+ eval_path = EVAL_HISTORY_PATH
360
463
  if not eval_path.is_file():
361
464
  return {'samples': 0, 'rework_count': 0, 'avg_rounds': 0}
362
465
  records = []
@@ -466,7 +569,7 @@ def compute_role_guide():
466
569
  role = None
467
570
  try:
468
571
  sys.path.insert(0, str(BASE / '.qoder' / 'scripts'))
469
- from common.identity import get_role
572
+ from foundation.identity.identity import get_role
470
573
  role = get_role()
471
574
  except Exception:
472
575
  pass
@@ -0,0 +1,191 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ status_snapshot.py — /wl-status 的一次性数据聚合器 (性能优化)
4
+
5
+ 问题: wl-status skill 让 AI 逐个 cat/ls 收集数据, 实测产生 5 轮迭代、10+ 次工具调用。
6
+ 本脚本把 status 需要的全部数据源**一次返回**, 把 5 轮迭代压成 1 轮。
7
+
8
+ 聚合的数据源 (来自 wl-status SKILL.md):
9
+ 1. .developer — 当前开发者
10
+ 2. .index-meta.json — 图谱新鲜度
11
+ 3. eval-history.jsonl — EVA 评估历史 (健康度趋势)
12
+ 4. feedback.jsonl — 学习反馈记录
13
+ 5. config.yaml — 项目配置
14
+ 6. task.py list — 任务列表
15
+ 7. team_sync.py status — 团队同步状态
16
+ 8. tasks 目录 — 任务文件清单
17
+ 9. learning/patterns — 学习模式
18
+
19
+ 输出: JSON, 缺失的字段标 null 并注明原因。AI 拿到后直接算健康度, 不用再探查。
20
+
21
+ 用法:
22
+ python status_snapshot.py # 当前仓库
23
+ python status_snapshot.py /path/to/repo
24
+ """
25
+ import json
26
+ import os
27
+ import subprocess
28
+ import sys
29
+ from datetime import datetime
30
+
31
+
32
+ def _repo_root():
33
+ """优先用命令行参数, 否则从环境反推。"""
34
+ if len(sys.argv) > 1 and os.path.isdir(sys.argv[1]):
35
+ return os.path.abspath(sys.argv[1])
36
+ # 同 task.py 的逻辑: 找 .qoder 标记
37
+ d = os.getcwd()
38
+ for _ in range(8):
39
+ if os.path.isdir(os.path.join(d, ".qoder")):
40
+ return d
41
+ d = os.path.dirname(d)
42
+ return os.getcwd()
43
+
44
+
45
+ def _read_text(path, limit=20000):
46
+ """安全读文本文件, 不存在返回 None。"""
47
+ if not os.path.exists(path):
48
+ return None
49
+ try:
50
+ with open(path, "r", encoding="utf-8") as f:
51
+ return f.read(limit)
52
+ except Exception as e:
53
+ return f"<read_error: {e}>"
54
+
55
+
56
+ def _read_json(path):
57
+ if not os.path.exists(path):
58
+ return None
59
+ try:
60
+ with open(path, "r", encoding="utf-8") as f:
61
+ return json.load(f)
62
+ except Exception as e:
63
+ return {"_error": str(e)}
64
+
65
+
66
+ def _read_jsonl_tail(path, n=10):
67
+ """读 jsonl 最后 n 行 (评估/反馈历史, 只关心最近的)。"""
68
+ if not os.path.exists(path):
69
+ return None
70
+ try:
71
+ with open(path, "r", encoding="utf-8") as f:
72
+ lines = [l.strip() for l in f if l.strip()]
73
+ out = []
74
+ for l in lines[-n:]:
75
+ try:
76
+ out.append(json.loads(l))
77
+ except json.JSONDecodeError:
78
+ continue
79
+ return out
80
+ except Exception:
81
+ return None
82
+
83
+
84
+ def _run_script(script_rel, repo, args=None, timeout=20):
85
+ """运行 .qoder/scripts 下的脚本, 返回 (stdout, ok)。"""
86
+ script = os.path.join(repo, script_rel)
87
+ if not os.path.exists(script):
88
+ return None, False
89
+ try:
90
+ cmd = [sys.executable, script] + (args or [])
91
+ r = subprocess.run(
92
+ cmd, cwd=repo, capture_output=True, text=True, timeout=timeout,
93
+ encoding="utf-8", errors="replace",
94
+ )
95
+ return r.stdout.strip() if r.returncode == 0 else r.stderr.strip(), r.returncode == 0
96
+ except Exception as e:
97
+ return f"<run_error: {e}>", False
98
+
99
+
100
+ def _list_dir(path):
101
+ if not os.path.isdir(path):
102
+ return None
103
+ try:
104
+ items = sorted(os.listdir(path))
105
+ return items[:50] # 限 50 项防爆
106
+ except Exception:
107
+ return None
108
+
109
+
110
+ def _index_freshness(meta):
111
+ """从 .index-meta.json 算新鲜度(距今天数)。"""
112
+ if not isinstance(meta, dict):
113
+ return None
114
+ last = meta.get("last_sync") or meta.get("last_build") or meta.get("updated")
115
+ if not last:
116
+ return None
117
+ try:
118
+ # 兼容多种时间格式
119
+ ts = str(last).replace("Z", "+00:00")
120
+ dt = datetime.fromisoformat(ts)
121
+ if dt.tzinfo:
122
+ dt = dt.replace(tzinfo=None)
123
+ return (datetime.now() - dt).days
124
+ except Exception:
125
+ return None
126
+
127
+
128
+ def snapshot(repo=None):
129
+ """收集 wl-status 需要的全部数据, 返回 dict。"""
130
+ repo = repo or _repo_root()
131
+ qd = os.path.join(repo, ".qoder")
132
+
133
+ data = {"_repo": repo, "_ts": datetime.now().isoformat(timespec="seconds")}
134
+
135
+ # 1. 开发者身份
136
+ data["developer"] = _read_text(os.path.join(qd, ".developer")) or "(未设置)"
137
+
138
+ # 2. 图谱索引新鲜度
139
+ meta = _read_json(os.path.join(repo, "data", "index", ".index-meta.json"))
140
+ data["index_meta"] = meta
141
+ data["index_age_days"] = _index_freshness(meta)
142
+
143
+ # 3. EVA 评估历史 (健康度趋势)
144
+ data["eval_history"] = _read_jsonl_tail(
145
+ os.path.join(qd, "learning", "eval-history.jsonl"), n=10)
146
+
147
+ # 4. 学习反馈
148
+ data["feedback_count"] = None
149
+ fb = os.path.join(qd, "learning", "feedback.jsonl")
150
+ if os.path.exists(fb):
151
+ try:
152
+ with open(fb, encoding="utf-8") as f:
153
+ data["feedback_count"] = sum(1 for _ in f)
154
+ except Exception:
155
+ pass
156
+
157
+ # 5. 项目配置 (只取关键摘要, 防过大)
158
+ cfg = _read_text(os.path.join(qd, "config.yaml"), limit=4000)
159
+ data["config_summary"] = cfg[:4000] if cfg else None
160
+
161
+ # 6. 任务列表 (调 task.py list)
162
+ task_out, task_ok = _run_script(".qoder/scripts/domain/task/task.py", repo, args=["list"])
163
+ data["task_list"] = task_out if task_ok else None
164
+ data["task_list_error"] = None if task_ok else task_out
165
+
166
+ # 7. 团队同步状态 (调 team_sync.py status)
167
+ sync_out, sync_ok = _run_script(".qoder/scripts/domain/task/team_sync.py", repo,
168
+ args=["status"], timeout=15)
169
+ data["team_sync"] = sync_out if sync_ok else None
170
+ data["team_sync_error"] = None if sync_ok else sync_out
171
+
172
+ # 8. 任务目录结构
173
+ data["tasks_dir"] = _list_dir(os.path.join(qd, "workspace", "tasks"))
174
+
175
+ # 9. 学习模式
176
+ data["patterns"] = _list_dir(os.path.join(qd, "learning", "patterns"))
177
+
178
+ # 10. 成员目录 (团队规模)
179
+ data["members"] = _list_dir(os.path.join(qd, "workspace", "members"))
180
+
181
+ return data
182
+
183
+
184
+ def main():
185
+ repo = sys.argv[1] if len(sys.argv) > 1 else None
186
+ data = snapshot(repo)
187
+ print(json.dumps(data, ensure_ascii=False, indent=2, default=str))
188
+
189
+
190
+ if __name__ == "__main__":
191
+ main()
@@ -2,9 +2,15 @@
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
  archive_prd.py - PRD 按时间归档到个人工作区
@@ -41,7 +47,7 @@ except (AttributeError, TypeError, OSError, IOError):
41
47
 
42
48
  # 让 common/ 可被 import(直接当脚本跑时)
43
49
  _THIS_DIR = os.path.dirname(os.path.abspath(__file__))
44
- _COMMON = os.path.join(_THIS_DIR, "common")
50
+ _COMMON = os.path.join(_THIS_DIR, "foundation")
45
51
  if _COMMON not in sys.path:
46
52
  sys.path.insert(0, _COMMON)
47
53