@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
@@ -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
 
@@ -40,8 +45,8 @@ if sys.platform == 'win32':
40
45
  pass
41
46
 
42
47
  sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
43
- from common.paths import get_repo_root, get_developer
44
- from common.filelock import FileLock, LockTimeoutError
48
+ from foundation.core.paths import get_repo_root, get_developer
49
+ from foundation.io.filelock import FileLock, LockTimeoutError
45
50
  from syncgate import run_gates, SAFE_EXTENSIONS
46
51
 
47
52
  BASE = str(get_repo_root())
@@ -114,7 +119,7 @@ def _notify_prd_publications(staged_files):
114
119
  """
115
120
  import re as _re
116
121
  try:
117
- from common.feishu import notify_prd_published, is_enabled
122
+ from foundation.integrations.feishu import notify_prd_published, is_enabled
118
123
  except ImportError as e:
119
124
  print('[sync] WARN: feishu 模块缺失, 跳过 PRD 发布通知: %s' % e, file=sys.stderr)
120
125
  return
@@ -0,0 +1,370 @@
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+ """
4
+ zentao_sync.py - 本地任务 ↔ 禅道 双向同步(安全优先)
5
+
6
+ 设计目标 (回应团队最担心的「拉错/覆盖队友」):
7
+ - 拉取(禅道→本地): 强制 assignedTo == 我的禅道账号 过滤, 物理上拉不到队友任务。
8
+ - 推送(本地→禅道): 默认 --dry-run, 仅推 creator==我 的任务, 且禅道同名任务
9
+ assignedTo 须与我一致才更新, 不一致拒绝 (防误改队友任务)。
10
+ - 幂等: task.json 的 zentao_id 做主键, 已有则跳过 create。
11
+
12
+ 隔离契约: contracts/isolation.md「禅道同步」红线。
13
+ 归属映射: member.json 的 zentao_account 字段 (本地 dev 名 ↔ 禅道 account)。
14
+
15
+ 用法:
16
+ python .qoder/scripts/domain/task/zentao_sync.py pull <execution_id> [--apply] # 默认 dry-run 预览
17
+ python .qoder/scripts/domain/task/zentao_sync.py push [--execution_id N] [--apply]
18
+ python .qoder/scripts/domain/task/zentao_sync.py status # 看同步状态概览
19
+
20
+ 退出码:
21
+ 0 成功 (含 dry-run 预览); 2 busy; 3 配置/归属错误; 4 权限拒绝; 5 网络不通
22
+ """
23
+ import os
24
+ import sys
25
+
26
+ # ── 路径自举: 让脚本能独立运行 (复用 bootstrap 模式) ──
27
+ # task_utils/identity 用相对导入 (.paths), 必须从 common 包上下文导入,
28
+ # 因此把 scripts/ 加入 path, 用 common.xxx / task.xxx 访问。
29
+ _HERE = os.path.dirname(os.path.abspath(__file__))
30
+ _SCRIPTS = os.path.dirname(_HERE) # .qoder/scripts
31
+ _COMMON = os.path.join(_SCRIPTS, 'foundation')
32
+ for _p in (_SCRIPTS, _COMMON):
33
+ if _p not in sys.path:
34
+ sys.path.insert(0, _p)
35
+ try:
36
+ from bootstrap import setup; setup()
37
+ except Exception:
38
+ pass
39
+
40
+ import json
41
+ import argparse
42
+
43
+ from foundation.core.paths import get_repo_root, get_tasks_dir, get_developer
44
+ from foundation.identity.identity import get_member
45
+ from foundation.data.task_utils import modify_task_json, load_task_json, write_task_json
46
+ from foundation.integrations.zentao_client import ZentaoClient
47
+ from datetime import datetime
48
+ from pathlib import Path
49
+
50
+
51
+ def _create_local_task(repo_root, title, slug_seed, zentao_id, developer):
52
+ """直接创建本地任务目录 + task.json (含 zentao_id), 不走 cmd_create。
53
+
54
+ 自包含, 避免 cmd_create 的 argparse Namespace 签名 + team_sync stage 副作用。
55
+ 返回新建的 task_dir 或 None。"""
56
+ date_prefix = datetime.now().strftime("%m-%d")
57
+ tasks_dir = get_tasks_dir(repo_root)
58
+ task_dir_name = "%s-%s" % (date_prefix, slug_seed)
59
+ task_dir = tasks_dir / task_dir_name
60
+ if task_dir.exists():
61
+ # 撞名加后缀
62
+ task_dir_name = "%s-%s-%s" % (date_prefix, slug_seed, developer[:6])
63
+ task_dir = tasks_dir / task_dir_name
64
+ task_dir.mkdir(parents=True, exist_ok=True)
65
+ now_iso = datetime.now().isoformat()
66
+ task_data = {
67
+ "title": title, "slug": slug_seed, "status": "planning", "priority": "P2",
68
+ "assignee": developer, "creator": developer,
69
+ "created_at": now_iso, "updated_at": now_iso,
70
+ "branch": None, "base_branch": None, "scope": None,
71
+ "parent": None, "children": [], "tags": [],
72
+ "due_date": None, "start_date": None, "estimate_hours": None,
73
+ "blocked_by": [], "blocks": [],
74
+ "stage_ts": {"created": now_iso, "started": None, "completed": None},
75
+ "zentao_id": zentao_id,
76
+ }
77
+ if write_task_json(task_dir, task_data):
78
+ return task_dir
79
+ return None
80
+
81
+
82
+ # ============================================================
83
+ # 配置与归属解析
84
+ # ============================================================
85
+
86
+ def _zentao_creds():
87
+ """构建禅道客户端。凭据不全返回 None。
88
+ 底层 HTTP 逻辑收敛在 common.zentao_client.ZentaoClient (与 mcp_server 共享)。"""
89
+ return ZentaoClient.from_config()
90
+
91
+
92
+ def _resolve_my_account(developer):
93
+ """解析当前本地 developer → 禅道 account。
94
+ 无 member.json 或无 zentao_account → 返回 (None, 原因)。"""
95
+ mem = get_member(developer)
96
+ if not mem:
97
+ return None, '当前开发者 %s 未注册 (无 member.json)。请先 /wl-init。' % developer
98
+ acc = (mem.get('zentao_account') or '').strip()
99
+ if not acc:
100
+ return None, ('%s 的 member.json 未配 zentao_account (本地名↔禅道账号映射)。'
101
+ '\n 手动编辑 workspace/members/%s/member.json 加 "zentao_account": "你的禅道账号"。'
102
+ % (developer, developer))
103
+ return acc, None
104
+
105
+
106
+ # ============================================================
107
+ # 禅道 HTTP 层已收敛到 common.zentao_client.ZentaoClient (与 mcp_server 共享同一份)
108
+ # 下面 cmd_* 通过 creds (ZentaoClient 实例) 的 .api() / .check_intranet() 调用
109
+ # ============================================================
110
+
111
+ # ============================================================
112
+ # 本地任务遍历
113
+ # ============================================================
114
+
115
+ def _iter_local_tasks(repo_root):
116
+ """遍历 workspace/tasks/*/task.json, yield (task_dir, data)。"""
117
+ tdir = get_tasks_dir(repo_root)
118
+ if not tdir.is_dir():
119
+ return
120
+ for child in sorted(tdir.iterdir()):
121
+ if not child.is_dir():
122
+ continue
123
+ data = load_task_json(child)
124
+ if data:
125
+ yield child, data
126
+
127
+
128
+ # ============================================================
129
+ # 拉取: 禅道 → 本地 (归属硬过滤)
130
+ # ============================================================
131
+
132
+ def cmd_pull(args, creds, my_account):
133
+ """拉取某执行下 assignedTo==我 的任务到本地。默认 dry-run。"""
134
+ eid = args.execution_id
135
+ print('═══ 拉取 执行 #%s (仅 assignedTo=%s) ═══' % (eid, my_account))
136
+ if not args.apply:
137
+ print('[DRY-RUN] 仅预览, 不写本地。加 --apply 才真写。\n')
138
+
139
+ r = creds.api('GET', '/executions/%s/tasks' % eid, {'limit': 100})
140
+ if r.status_code != 200:
141
+ print('查询失败: %s %s' % (r.status_code, r.text[:120]))
142
+ return 3
143
+ data = r.json()
144
+ tasks = data.get('tasks') if isinstance(data, dict) else None
145
+ if not isinstance(tasks, list):
146
+ print('响应格式异常 (无 tasks 列表)。')
147
+ return 3
148
+
149
+ # ── 归属硬过滤: 只取 assignedTo == 我 ──
150
+ mine = [t for t in tasks if isinstance(t, dict)
151
+ and str(t.get('assignedTo', '')) == my_account]
152
+ skipped = len(tasks) - len(mine)
153
+ print('执行 #%s 共 %d 个任务, 归属我的 %d 个 (跳过队友 %d 个, 不拉)。\n'
154
+ % (eid, len(tasks), len(mine), skipped))
155
+
156
+ if not mine:
157
+ print('没有归属你的任务, 无需同步。')
158
+ return 0
159
+
160
+ # 建立本地 zentao_id 索引 (防重复 create)
161
+ repo_root = get_repo_root()
162
+ existing_zids = {}
163
+ for td, d in _iter_local_tasks(repo_root):
164
+ zid = d.get('zentao_id')
165
+ if zid:
166
+ existing_zids[int(zid)] = td
167
+
168
+ created = skipped_dup = 0
169
+ for t in mine:
170
+ zid = int(t.get('id', 0))
171
+ name = str(t.get('name', ''))[:50]
172
+ status = t.get('status', '')
173
+ if zid in existing_zids:
174
+ print(' ⊘ 跳过 #%s「%s」(本地已有 zentao_id=%s)' % (zid, name, zid))
175
+ skipped_dup += 1
176
+ continue
177
+ if not args.apply:
178
+ print(' + [将创建] #%s「%s」status=%s' % (zid, name, status))
179
+ created += 1
180
+ continue
181
+ # 真写: 直接创建本地任务 (含 zentao_id), 自包含不走 cmd_create
182
+ slug_seed = 'zentao-%s' % zid
183
+ new_dir = _create_local_task(repo_root, 'zentao#%s %s' % (zid, name),
184
+ slug_seed, zid, developer)
185
+ if new_dir:
186
+ print(' ✅ 创建 #%s「%s」并回填 zentao_id -> %s' % (zid, name, new_dir.name))
187
+ created += 1
188
+ else:
189
+ print(' ⚠️ 创建 #%s 失败' % zid)
190
+
191
+ print('\n小结: 创建 %d, 幂等跳过 %d, 队友任务跳过 %d。%s'
192
+ % (created, skipped_dup, skipped,
193
+ '(dry-run, 未真写)' if not args.apply else ''))
194
+ return 0
195
+
196
+
197
+ # ============================================================
198
+ # 推送: 本地 → 禅道 (默认 dry-run + 双校验)
199
+ # ============================================================
200
+
201
+ def cmd_push(args, creds, my_account, developer):
202
+ """推送本地 creator==我 的任务到禅道。默认 dry-run。"""
203
+ print('═══ 推送 本地 → 禅道 (仅 creator=%s, 强制 assignedTo=%s) ═══'
204
+ % (developer, my_account))
205
+ if not args.apply:
206
+ print('[DRY-RUN] 仅预览, 不写禅道。加 --apply 才真推。\n')
207
+
208
+ repo_root = get_repo_root()
209
+ candidates = []
210
+ for td, d in _iter_local_tasks(repo_root):
211
+ # ── 校验 1: 只推我创建的 (复用本地归属) ──
212
+ if (d.get('creator') or '') != developer:
213
+ continue
214
+ candidates.append((td, d))
215
+
216
+ if not candidates:
217
+ print('没有你创建的本地任务可推送。')
218
+ return 0
219
+
220
+ print('本地归属你的任务 %d 个:\n' % len(candidates))
221
+ pushed = skipped = 0
222
+ for td, d in candidates:
223
+ zid = d.get('zentao_id')
224
+ title = str(d.get('title', ''))[:50]
225
+ if zid:
226
+ # 已有 zentao_id → 更新前校验归属一致性
227
+ chk = creds.api('GET', '/tasks/%s' % zid)
228
+ if chk.status_code == 200:
229
+ remote_assign = str(chk.json().get('assignedTo', ''))
230
+ if remote_assign != my_account:
231
+ print(' 🚫 拒绝更新 #%s「%s」: 禅道 assignedTo=%s ≠ 你(%s)'
232
+ ' (防误改队友任务)' % (zid, title, remote_assign, my_account))
233
+ skipped += 1
234
+ continue
235
+ if not args.apply:
236
+ print(' ⟳ [将更新] #%s「%s」' % (zid, title))
237
+ pushed += 1
238
+ continue
239
+ payload = {'name': d.get('title', ''), 'assignedTo': my_account}
240
+ if d.get('estimate_hours'):
241
+ payload['estimate'] = d['estimate_hours']
242
+ if d.get('due_date'):
243
+ payload['deadline'] = d['due_date']
244
+ upd = creds.api('PUT', '/tasks/%s' % zid, payload)
245
+ print(' %s 更新 #%s「%s」%s'
246
+ % ('✅' if upd.status_code in (200, 201) else '⚠️', zid, title,
247
+ '' if upd.status_code in (200, 201) else '(%s)' % upd.status_code))
248
+ pushed += 1
249
+ else:
250
+ print(' ⚠️ 查询 #%s 失败 (%s), 跳过' % (zid, chk.status_code))
251
+ skipped += 1
252
+ else:
253
+ # 无 zentao_id → 新建 (强制 assignedTo=我)
254
+ if not args.execution_id:
255
+ print(' ⊙ 跳过「%s」(无 zentao_id, 且未给 --execution_id, 无法新建)'
256
+ % title)
257
+ skipped += 1
258
+ continue
259
+ if not args.apply:
260
+ print(' + [将新建]「%s」→ 执行 #%s' % (title, args.execution_id))
261
+ pushed += 1
262
+ continue
263
+ payload = {'execution': args.execution_id, 'name': d.get('title', ''),
264
+ 'assignedTo': my_account, 'type': 'devel', 'pri': 3}
265
+ cre = creds.api('POST', '/tasks', payload)
266
+ if cre.status_code in (200, 201):
267
+ new_zid = cre.json().get('id')
268
+ def _set(d, _z=new_zid):
269
+ d['zentao_id'] = _z
270
+ modify_task_json(td, _set)
271
+ print(' ✅ 新建「%s」→ 禅道 #%s, 回填 zentao_id' % (title, new_zid))
272
+ pushed += 1
273
+ else:
274
+ print(' ⚠️ 新建「%s」失败 (%s)' % (title, cre.status_code))
275
+ skipped += 1
276
+
277
+ print('\n小结: 推送 %d, 跳过 %d。%s'
278
+ % (pushed, skipped, '(dry-run, 未真写)' if not args.apply else ''))
279
+ return 0
280
+
281
+
282
+ # ============================================================
283
+ # status 概览
284
+ # ============================================================
285
+
286
+ def cmd_status(args, creds, my_account, developer):
287
+ """看本地任务与禅道的关联状态概览 (只读)。"""
288
+ repo_root = get_repo_root()
289
+ total = mine = linked = unlinked = 0
290
+ for td, d in _iter_local_tasks(repo_root):
291
+ total += 1
292
+ if (d.get('creator') or '') == developer:
293
+ mine += 1
294
+ if d.get('zentao_id'):
295
+ linked += 1
296
+ else:
297
+ unlinked += 1
298
+ print('═══ 同步状态 (%s / 禅道账号 %s) ═══' % (developer, my_account))
299
+ print('本地任务总数: %d' % total)
300
+ print(' 归属你的: %d' % mine)
301
+ print(' 已关联禅道 (有 zentao_id): %d' % linked)
302
+ print(' 未关联禅道 (推送时需 --execution_id 新建): %d' % unlinked)
303
+ ok, _ = creds.check_intranet()
304
+ print('\n禅道连通: %s (%s)' % ('✅ 可达' if ok else '❌ 不可达', creds.url))
305
+ return 0
306
+
307
+
308
+ # ============================================================
309
+ # 主入口
310
+ # ============================================================
311
+
312
+ def main(argv=None):
313
+ p = argparse.ArgumentParser(description='本地任务 ↔ 禅道安全同步',
314
+ formatter_class=argparse.RawDescriptionHelpFormatter,
315
+ epilog='安全原则: 拉取只取 assignedTo=我; 推送默认 dry-run 且'
316
+ ' 仅推 creator=我, 同名任务归属不一致则拒绝。')
317
+ sub = p.add_subparsers(dest='cmd')
318
+
319
+ pp = sub.add_parser('pull', help='拉取禅道任务到本地 (仅 assignedTo=我)')
320
+ pp.add_argument('execution_id', type=int, help='执行ID')
321
+ pp.add_argument('--apply', action='store_true', help='真写 (默认 dry-run 仅预览)')
322
+
323
+ sp = sub.add_parser('push', help='推送本地任务到禅道 (默认 dry-run)')
324
+ sp.add_argument('--execution_id', type=int, help='新建任务落到哪个执行 (无 zentao_id 的任务需要)')
325
+ sp.add_argument('--apply', action='store_true', help='真推 (默认 dry-run 仅预览)')
326
+
327
+ sub.add_parser('status', help='看同步状态概览 (只读)')
328
+
329
+ args = p.parse_args(argv)
330
+ if not args.cmd:
331
+ p.print_help()
332
+ return 0
333
+
334
+ developer = get_developer()
335
+ if not developer:
336
+ print('ERROR: 未设置当前开发者 (无 .developer)。请先 /wl-init。', file=sys.stderr)
337
+ return 4
338
+
339
+ my_account, err = _resolve_my_account(developer)
340
+ if err:
341
+ print('ERROR: %s' % err, file=sys.stderr)
342
+ return 3
343
+
344
+ creds = _zentao_creds()
345
+ if not creds:
346
+ print('ERROR: 禅道凭据未配置。请在 .qoder/config.yaml 的 zentao: 段填 url/user/password。',
347
+ file=sys.stderr)
348
+ return 3
349
+
350
+ # 连通性预检
351
+ ok, msg = creds.check_intranet()
352
+ if not ok:
353
+ print('ERROR: %s' % msg, file=sys.stderr)
354
+ return 5
355
+
356
+ try:
357
+ if args.cmd == 'pull':
358
+ return cmd_pull(args, creds, my_account)
359
+ elif args.cmd == 'push':
360
+ return cmd_push(args, creds, my_account, developer)
361
+ elif args.cmd == 'status':
362
+ return cmd_status(args, creds, my_account, developer)
363
+ except RuntimeError as e:
364
+ print('ERROR: %s' % e, file=sys.stderr)
365
+ return 3
366
+ return 0
367
+
368
+
369
+ if __name__ == '__main__':
370
+ sys.exit(main())
File without changes
@@ -4,23 +4,23 @@ bootstrap.py - 统一 CLI 启动样板 (DRY) + 子目录化路径自举 (v3.0)
4
4
 
5
5
  治"70处 sys.path + 50处 reconfigure" + 子目录化后的路径定位。
6
6
 
7
- 子目录化后, 脚本在 scripts/kg/kg.py, 它的 dirname(__file__)=scripts/kg/,
8
- 直接 sys.path.insert(scripts/kg/) 后 `from common.x` 会断 (common 在 scripts/common/)。
7
+ 子目录化后, 脚本在 scripts/domain/kg/kg.py, 它的 dirname(__file__)=scripts/domain/kg/,
8
+ 直接 sys.path.insert(scripts/domain/kg/) 后 `from foundation.x` 会断 (foundation 在 scripts/foundation/)。
9
9
 
10
10
  本模块的 setup() 解决: 无论脚本在 scripts/ 根还是 scripts/某子包/,
11
- 都能正确定位 scripts/ 根并注入, 让 `from common.x` 和跨包 import 都能用。
11
+ 都能正确定位 scripts/ 根并注入, 让 `from foundation.x` 和跨包 import 都能用。
12
12
 
13
- 核心: _find_scripts_root() 向上找含 'common/' 子目录的那个目录 = scripts/ 根。
13
+ 核心: _find_scripts_root() 向上找含 'foundation/' 子目录的那个目录 = scripts/ 根。
14
14
 
15
15
  Usage (两种姿势):
16
16
  # 姿势1: 子包脚本开头 (推荐)
17
17
  import os, sys
18
18
  _S = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) # 子包→scripts
19
19
  if _S not in sys.path: sys.path.insert(0, _S)
20
- from common.bootstrap import setup; setup()
20
+ from bootstrap import setup; setup()
21
21
 
22
22
  # 姿势2: scripts/ 根脚本 (老姿势仍可用)
23
- from common.bootstrap import setup; setup()
23
+ from bootstrap import setup; setup()
24
24
  """
25
25
 
26
26
  import os
@@ -40,7 +40,7 @@ def find_scripts_root(start=None):
40
40
  cur = start
41
41
  for _ in range(10):
42
42
  # scripts/ 根的标志: 含 common/bootstrap.py
43
- if os.path.isfile(os.path.join(cur, "common", "bootstrap.py")):
43
+ if os.path.isfile(os.path.join(cur, "foundation", "bootstrap.py")):
44
44
  return cur
45
45
  parent = os.path.dirname(cur)
46
46
  if parent == cur:
@@ -62,7 +62,7 @@ def find_scripts_root(start=None):
62
62
  def ensure_scripts_path(start=None):
63
63
  """确保 scripts/ 根在 sys.path 最前。幂等。
64
64
 
65
- 子包脚本调用此函数后, `from common.x` 即可用。
65
+ 子包脚本调用此函数后, `from foundation.x` 即可用。
66
66
  """
67
67
  root = find_scripts_root(start)
68
68
  if root not in sys.path:
@@ -107,7 +107,7 @@ def setup():
107
107
  scripts_dir = ensure_scripts_root = find_scripts_root()
108
108
  if scripts_dir not in sys.path:
109
109
  sys.path.insert(0, scripts_dir)
110
- common_dir = os.path.join(scripts_dir, "common")
110
+ common_dir = os.path.join(scripts_dir, "foundation")
111
111
  if os.path.isdir(common_dir) and common_dir not in sys.path:
112
112
  sys.path.insert(0, common_dir)
113
113
  # 注入所有子包目录 (kg/build/mcp/test/setup/carriers/task/search/report)
@@ -142,4 +142,4 @@ def setup():
142
142
 
143
143
  # 模块级常量
144
144
  SCRIPTS_DIR = find_scripts_root()
145
- REPO_ROOT = os.path.dirname(os.path.dirname(SCRIPTS_DIR))
145
+ REPO_ROOT = os.path.dirname(os.path.dirname(os.path.dirname(os.path.dirname(SCRIPTS_DIR))))
@@ -17,7 +17,7 @@ args_spec 格式 (简化 argparse.add_argument):
17
17
  ]
18
18
 
19
19
  Usage:
20
- from common.cmd_registry import build_parser
20
+ from foundation.core.cmd_registry import build_parser
21
21
 
22
22
  COMMANDS = [
23
23
  ('create', '建任务', [
@@ -12,7 +12,7 @@ config.py - Config 单例 (Singleton 模式, 治"4份 load_config + 明文凭据
12
12
  - 凭据从代码移除: mysql/zentao 密码只在 config.yaml, 不在源码默认值
13
13
 
14
14
  Usage:
15
- from common.config import Config, SECTIONS
15
+ from foundation.core.config import Config, SECTIONS
16
16
  cfg = Config.load() # 单例, 全进程共享
17
17
  feishu = cfg.section('feishu') # {'enabled': false, ...}
18
18
  mysql = cfg.mysql # 类型化便捷访问