@hupan56/wlkj 2.7.12 → 3.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (253) hide show
  1. package/bin/cli.js +1435 -1107
  2. package/package.json +29 -29
  3. package/templates/qoder/agents/insight-planning.md +1 -1
  4. package/templates/qoder/agents/insight-research.md +28 -15
  5. package/templates/qoder/commands/optional/wl-insight.md +161 -161
  6. package/templates/qoder/commands/optional/wl-report.md +20 -39
  7. package/templates/qoder/commands/optional/wl-status.md +4 -4
  8. package/templates/qoder/commands/wl-code.md +2 -2
  9. package/templates/qoder/commands/wl-design.md +6 -6
  10. package/templates/qoder/commands/wl-init.md +3 -3
  11. package/templates/qoder/commands/wl-prd.md +26 -13
  12. package/templates/qoder/commands/wl-req.md +43 -0
  13. package/templates/qoder/commands/wl-search.md +78 -47
  14. package/templates/qoder/commands/wl-task.md +343 -40
  15. package/templates/qoder/commands/wl-test.md +43 -17
  16. package/templates/qoder/config.yaml +52 -14
  17. package/templates/qoder/hooks/post-tool-use.py +181 -0
  18. package/templates/qoder/hooks/session-start.py +94 -23
  19. package/templates/qoder/hooks/stop-eval.py +207 -0
  20. package/templates/qoder/hooks/user-prompt-submit.py +139 -0
  21. package/templates/qoder/rules/wl-pipeline.md +85 -49
  22. package/templates/qoder/scripts/README.md +102 -0
  23. package/templates/qoder/scripts/capability/__init__.py +26 -0
  24. package/templates/qoder/scripts/capability/__main__.py +72 -0
  25. package/templates/qoder/scripts/capability/adapters/__init__.py +29 -0
  26. package/templates/qoder/scripts/capability/adapters/cli.py +316 -0
  27. package/templates/qoder/scripts/capability/adapters/mcp.py +334 -0
  28. package/templates/qoder/scripts/capability/adapters/qw.py +271 -0
  29. package/templates/qoder/scripts/capability/caps/__init__.py +27 -0
  30. package/templates/qoder/scripts/capability/caps/context.py +36 -0
  31. package/templates/qoder/scripts/capability/caps/cron.py +50 -0
  32. package/templates/qoder/scripts/capability/caps/identity.py +43 -0
  33. package/templates/qoder/scripts/capability/caps/memory.py +71 -0
  34. package/templates/qoder/scripts/capability/caps/notify.py +41 -0
  35. package/templates/qoder/scripts/capability/caps/present.py +48 -0
  36. package/templates/qoder/scripts/capability/caps/repo.py +31 -0
  37. package/templates/qoder/scripts/capability/caps/sandbox.py +43 -0
  38. package/templates/qoder/scripts/capability/chain.py +92 -0
  39. package/templates/qoder/scripts/capability/memory_chain.py +41 -0
  40. package/templates/qoder/scripts/capability/registry.py +246 -0
  41. package/templates/qoder/scripts/capability/registry_mcp.py +250 -0
  42. package/templates/qoder/scripts/capability/smoke_test.py +211 -0
  43. package/templates/qoder/scripts/capability/smoke_test_report.json +94 -0
  44. package/templates/qoder/scripts/deployment/setup/__init__.py +11 -0
  45. package/templates/qoder/scripts/deployment/setup/carriers.py +669 -0
  46. package/templates/qoder/scripts/deployment/setup/carriers_verify.py +148 -0
  47. package/templates/qoder/scripts/{init_doctor.py → deployment/setup/init_doctor.py} +100 -39
  48. package/templates/qoder/scripts/{install_qoderwork.py → deployment/setup/install_qoderwork.py} +48 -30
  49. package/templates/qoder/scripts/{platform_doctor.py → deployment/setup/platform_doctor.py} +271 -259
  50. package/templates/qoder/scripts/{repo_root.py → deployment/setup/repo_root.py} +9 -2
  51. package/templates/qoder/scripts/{setup.py → deployment/setup/setup.py} +143 -14
  52. package/templates/qoder/scripts/{setup_lanhu.py → deployment/setup/setup_lanhu.py} +979 -963
  53. package/templates/qoder/scripts/domain/design/__init__.py +0 -0
  54. package/templates/qoder/scripts/{fill_prototype.py → domain/design/fill_prototype.py} +17 -5
  55. package/templates/qoder/scripts/{gen_design_doc.py → domain/design/gen_design_doc.py} +406 -394
  56. package/templates/qoder/scripts/domain/kg/__init__.py +11 -0
  57. package/templates/qoder/scripts/domain/kg/build/__init__.py +0 -0
  58. package/templates/qoder/scripts/domain/kg/build/build_entity_registry.py +201 -0
  59. package/templates/qoder/scripts/domain/kg/build/build_relations.py +132 -0
  60. package/templates/qoder/scripts/{build_style_index.py → domain/kg/build/build_style_index.py} +50 -15
  61. package/templates/qoder/scripts/domain/kg/build/build_workflows.py +149 -0
  62. package/templates/qoder/scripts/{kg_build.py → domain/kg/build/kg_build.py} +685 -612
  63. package/templates/qoder/scripts/{kg_build_db.py → domain/kg/build/kg_build_db.py} +338 -327
  64. package/templates/qoder/scripts/{kg_incremental.py → domain/kg/build/kg_incremental.py} +425 -393
  65. package/templates/qoder/scripts/{learn_aggregate.py → domain/kg/build/learn_aggregate.py} +212 -201
  66. package/templates/qoder/scripts/domain/kg/extract/__init__.py +0 -0
  67. package/templates/qoder/scripts/{common → domain/kg/extract}/extract.py +430 -419
  68. package/templates/qoder/scripts/domain/kg/extract/test_extract.py +115 -0
  69. package/templates/qoder/scripts/{common → domain/kg/extract}/ts_extract.py +614 -536
  70. package/templates/qoder/scripts/domain/kg/graph/__init__.py +0 -0
  71. package/templates/qoder/scripts/{kg_link_db.py → domain/kg/graph/kg_link_db.py} +235 -224
  72. package/templates/qoder/scripts/domain/kg/graph/kg_semantic.py +683 -0
  73. package/templates/qoder/scripts/{kg.py → domain/kg/kg.py} +871 -708
  74. package/templates/qoder/scripts/domain/kg/kg_capabilities.py +182 -0
  75. package/templates/qoder/scripts/domain/kg/search/__init__.py +0 -0
  76. package/templates/qoder/scripts/{context_pack.py → domain/kg/search/context_pack.py} +45 -17
  77. package/templates/qoder/scripts/{enrich_prompt.py → domain/kg/search/enrich_prompt.py} +238 -226
  78. package/templates/qoder/scripts/domain/kg/search/prefetch.py +384 -0
  79. package/templates/qoder/scripts/{common → domain/kg/search}/search_engine.py +207 -205
  80. package/templates/qoder/scripts/{search_index.py → domain/kg/search/search_index.py} +210 -88
  81. package/templates/qoder/scripts/domain/kg/server/__init__.py +0 -0
  82. package/templates/qoder/scripts/domain/kg/server/kgd.py +549 -0
  83. package/templates/qoder/scripts/domain/kg/server/perf_bench.py +197 -0
  84. package/templates/qoder/scripts/domain/kg/storage/__init__.py +0 -0
  85. package/templates/qoder/scripts/{kg_duckdb.py → domain/kg/storage/kg_duckdb.py} +70 -45
  86. package/templates/qoder/scripts/domain/learning/__init__.py +0 -0
  87. package/templates/qoder/scripts/{learn.py → domain/learning/learn.py} +157 -146
  88. package/templates/qoder/scripts/domain/report/__init__.py +11 -0
  89. package/templates/qoder/scripts/{add_session.py → domain/report/add_session.py} +256 -244
  90. package/templates/qoder/scripts/{export.py → domain/report/export.py} +72 -4
  91. package/templates/qoder/scripts/{report.py → domain/report/report.py} +292 -281
  92. package/templates/qoder/scripts/domain/report/report_snapshot.py +261 -0
  93. package/templates/qoder/scripts/domain/report/role.py +39 -0
  94. package/templates/qoder/scripts/{status.py → domain/report/status.py} +737 -628
  95. package/templates/qoder/scripts/domain/report/status_snapshot.py +191 -0
  96. package/templates/qoder/scripts/domain/requirement/__init__.py +0 -0
  97. package/templates/qoder/scripts/{archive_prd.py → domain/requirement/archive_prd.py} +389 -377
  98. package/templates/qoder/scripts/domain/requirement/req.py +228 -0
  99. package/templates/qoder/scripts/domain/task/__init__.py +11 -0
  100. package/templates/qoder/scripts/{git_sync.py → domain/task/git_sync.py} +90 -50
  101. package/templates/qoder/scripts/{syncgate.py → domain/task/syncgate.py} +18 -7
  102. package/templates/qoder/scripts/domain/task/task.py +229 -0
  103. package/templates/qoder/scripts/domain/task/task_lifecycle.py +606 -0
  104. package/templates/qoder/scripts/domain/task/task_query.py +162 -0
  105. package/templates/qoder/scripts/domain/task/task_relations.py +425 -0
  106. package/templates/qoder/scripts/{team_sync.py → domain/task/team_sync.py} +101 -23
  107. package/templates/qoder/scripts/domain/task/zentao_sync.py +370 -0
  108. package/templates/qoder/scripts/foundation/__init__.py +0 -0
  109. package/templates/qoder/scripts/foundation/bootstrap.py +145 -0
  110. package/templates/qoder/scripts/foundation/core/__init__.py +0 -0
  111. package/templates/qoder/scripts/foundation/core/cmd_registry.py +112 -0
  112. package/templates/qoder/scripts/foundation/core/config.py +187 -0
  113. package/templates/qoder/scripts/{common → foundation/core}/paths.py +706 -400
  114. package/templates/qoder/scripts/foundation/data/__init__.py +0 -0
  115. package/templates/qoder/scripts/foundation/data/contract.py +317 -0
  116. package/templates/qoder/scripts/{common → foundation/data}/events.py +2 -2
  117. package/templates/qoder/scripts/foundation/data/result.py +223 -0
  118. package/templates/qoder/scripts/{common → foundation/data}/task_utils.py +427 -392
  119. package/templates/qoder/scripts/foundation/identity/__init__.py +0 -0
  120. package/templates/qoder/scripts/foundation/identity/check_publish.py +103 -0
  121. package/templates/qoder/scripts/{common → foundation/identity}/developer.py +239 -238
  122. package/templates/qoder/scripts/foundation/identity/guard.py +159 -0
  123. package/templates/qoder/scripts/{common → foundation/identity}/identity.py +132 -9
  124. package/templates/qoder/scripts/foundation/identity/roles.py +60 -0
  125. package/templates/qoder/scripts/foundation/integrations/__init__.py +0 -0
  126. package/templates/qoder/scripts/{common → foundation/integrations}/active_task.py +30 -15
  127. package/templates/qoder/scripts/{common → foundation/integrations}/feishu.py +11 -10
  128. package/templates/qoder/scripts/{common → foundation/integrations}/terms.py +3 -3
  129. package/templates/qoder/scripts/foundation/integrations/zentao_client.py +220 -0
  130. package/templates/qoder/scripts/foundation/io/__init__.py +0 -0
  131. package/templates/qoder/scripts/{common → foundation/io}/filelock.py +1 -1
  132. package/templates/qoder/scripts/{common → foundation/io}/reqid.py +1 -1
  133. package/templates/qoder/scripts/foundation/protocol/__init__.py +0 -0
  134. package/templates/qoder/scripts/foundation/protocol/duckdb_conn.py +39 -0
  135. package/templates/qoder/scripts/foundation/protocol/mcp_base.py +268 -0
  136. package/templates/qoder/scripts/foundation/utils/__init__.py +0 -0
  137. package/templates/qoder/scripts/{common → foundation/utils}/eval_api.py +12 -1
  138. package/templates/qoder/scripts/{common → foundation/utils}/pip_install.py +23 -6
  139. package/templates/qoder/scripts/{common → foundation/utils}/platform_guard.py +1 -1
  140. package/templates/qoder/scripts/orchestration/__init__.py +0 -0
  141. package/templates/qoder/scripts/orchestration/wlkj.py +185 -0
  142. package/templates/qoder/scripts/protocol/__init__.py +0 -0
  143. package/templates/qoder/scripts/protocol/browser/README.md +207 -0
  144. package/templates/qoder/scripts/protocol/browser/SKILL.md +265 -0
  145. package/templates/qoder/scripts/protocol/browser/config.env +9 -0
  146. package/templates/qoder/scripts/protocol/browser/references/cdp-api.md +110 -0
  147. package/templates/qoder/scripts/protocol/browser/references/migration-2.5.3.md +72 -0
  148. package/templates/qoder/scripts/protocol/browser/references/site-patterns/.gitkeep +0 -0
  149. package/templates/qoder/scripts/protocol/browser/scripts/browser-discovery.mjs +138 -0
  150. package/templates/qoder/scripts/protocol/browser/scripts/cdp-proxy.mjs +672 -0
  151. package/templates/qoder/scripts/protocol/browser/scripts/check-deps.mjs +206 -0
  152. package/templates/qoder/scripts/protocol/browser/scripts/find-url.mjs +253 -0
  153. package/templates/qoder/scripts/protocol/browser/scripts/match-site.mjs +46 -0
  154. package/templates/qoder/scripts/protocol/browser/templates/config.env.template +9 -0
  155. package/templates/qoder/scripts/protocol/mcp/__init__.py +11 -0
  156. package/templates/qoder/scripts/protocol/mcp/kg_mcp_server.py +478 -0
  157. package/templates/qoder/scripts/{lanhu_stdio_wrapper.py → protocol/mcp/lanhu_stdio_wrapper.py} +131 -119
  158. package/templates/qoder/scripts/protocol/mcp/mcp_doctor.py +548 -0
  159. package/templates/qoder/scripts/protocol/mcp/mcp_launcher.py +600 -0
  160. package/templates/qoder/scripts/protocol/mcp/mysql_mcp_server.py +461 -0
  161. package/templates/qoder/scripts/protocol/mcp/zentao_mcp_server.py +1920 -0
  162. package/templates/qoder/scripts/protocol/transports/__init__.py +56 -0
  163. package/templates/qoder/scripts/protocol/transports/base.py +87 -0
  164. package/templates/qoder/scripts/protocol/transports/cli.py +132 -0
  165. package/templates/qoder/scripts/protocol/transports/http.py +141 -0
  166. package/templates/qoder/scripts/protocol/transports/stdio.py +293 -0
  167. package/templates/qoder/scripts/run_weekly_update.bat +27 -18
  168. package/templates/qoder/scripts/run_weekly_update.sh +22 -13
  169. package/templates/qoder/scripts/validation/__init__.py +0 -0
  170. package/templates/qoder/scripts/validation/eval/__init__.py +0 -0
  171. package/templates/qoder/scripts/validation/eval/perf-report-2026-06-24.md +139 -0
  172. package/templates/qoder/scripts/validation/eval/qwork_harness.py +617 -0
  173. package/templates/qoder/scripts/validation/eval/report-commands.md +220 -0
  174. package/templates/qoder/scripts/validation/eval/transcript_timing.py +386 -0
  175. package/templates/qoder/scripts/validation/metrics/__init__.py +0 -0
  176. package/templates/qoder/scripts/{eval_prd.py → validation/metrics/eval_prd.py} +84 -15
  177. package/templates/qoder/scripts/validation/metrics/usability_score.py +750 -0
  178. package/templates/qoder/scripts/validation/test/__init__.py +11 -0
  179. package/templates/qoder/scripts/validation/test/assertion_gen.py +551 -0
  180. package/templates/qoder/scripts/{autotest.py → validation/test/autotest.py} +1234 -1751
  181. package/templates/qoder/scripts/validation/test/autotest_auth.py +109 -0
  182. package/templates/qoder/scripts/{autotest_batch.py → validation/test/autotest_batch.py} +255 -224
  183. package/templates/qoder/scripts/validation/test/autotest_data.py +680 -0
  184. package/templates/qoder/scripts/validation/test/autotest_opencli.py +646 -0
  185. package/templates/qoder/scripts/{autotest_run.py → validation/test/autotest_run.py} +323 -297
  186. package/templates/qoder/scripts/validation/test/autotest_webaccess.py +571 -0
  187. package/templates/qoder/scripts/{benchmark.py → validation/test/benchmark.py} +17 -5
  188. package/templates/qoder/scripts/{kg_auto_login.py → validation/test/kg_auto_login.py} +208 -196
  189. package/templates/qoder/scripts/{kg_test_runner.py → validation/test/kg_test_runner.py} +13 -2
  190. package/templates/qoder/scripts/{page_probe.py → validation/test/page_probe.py} +470 -459
  191. package/templates/qoder/scripts/validation/test/smoke_all_commands.py +170 -0
  192. package/templates/qoder/settings.json +23 -2
  193. package/templates/qoder/skills/design-import/SKILL.md +235 -226
  194. package/templates/qoder/skills/design-review/SKILL.md +91 -82
  195. package/templates/qoder/skills/prd-generator/SKILL.md +38 -24
  196. package/templates/qoder/skills/prd-review/SKILL.md +8 -6
  197. package/templates/qoder/skills/prototype-generator/SKILL.md +296 -247
  198. package/templates/qoder/skills/spec-coder/SKILL.md +24 -15
  199. package/templates/qoder/skills/spec-generator/SKILL.md +24 -12
  200. package/templates/qoder/skills/test-generator/SKILL.md +9 -7
  201. package/templates/qoder/skills/wl-code/SKILL.md +25 -13
  202. package/templates/qoder/skills/wl-commit/SKILL.md +10 -9
  203. package/templates/qoder/skills/wl-design/SKILL.md +7 -5
  204. package/templates/qoder/skills/wl-init/SKILL.md +8 -8
  205. package/templates/qoder/skills/wl-insight/SKILL.md +25 -12
  206. package/templates/qoder/skills/wl-prd-full/SKILL.md +59 -8
  207. package/templates/qoder/skills/wl-prd-quick/SKILL.md +7 -7
  208. package/templates/qoder/skills/wl-prd-review/SKILL.md +18 -6
  209. package/templates/qoder/skills/wl-report/SKILL.md +27 -25
  210. package/templates/qoder/skills/wl-search/SKILL.md +151 -80
  211. package/templates/qoder/skills/wl-spec/SKILL.md +9 -7
  212. package/templates/qoder/skills/wl-status/SKILL.md +50 -18
  213. package/templates/qoder/skills/wl-task/SKILL.md +94 -12
  214. package/templates/qoder/skills/wl-test/SKILL.md +154 -45
  215. package/templates/qoder/templates/prd-full-template.md +7 -0
  216. package/templates/root/AGENTS.md +275 -237
  217. package/templates/root/requirements.txt +3 -0
  218. package/templates/root//344/275/277/347/224/250/350/257/264/346/230/216.md +3 -3
  219. package/templates/root//346/226/260/346/211/213/346/214/207/345/215/227.md +2 -2
  220. package/templates/qoder/hooks/inject-workflow-state.py +0 -169
  221. package/templates/qoder/scripts/__pycache__/check_mcp_launch.cpython-39.pyc +0 -0
  222. package/templates/qoder/scripts/__pycache__/install_qoderwork.cpython-39.pyc +0 -0
  223. package/templates/qoder/scripts/__pycache__/mcp_launcher.cpython-39.pyc +0 -0
  224. package/templates/qoder/scripts/__pycache__/platform_doctor.cpython-39.pyc +0 -0
  225. package/templates/qoder/scripts/check_carriers.py +0 -238
  226. package/templates/qoder/scripts/check_mcp.py +0 -298
  227. package/templates/qoder/scripts/check_mcp_launch.py +0 -183
  228. package/templates/qoder/scripts/check_qoderwork_consistency.py +0 -166
  229. package/templates/qoder/scripts/collect_prds.py +0 -31
  230. package/templates/qoder/scripts/common/mentions.py +0 -134
  231. package/templates/qoder/scripts/common/utf8.py +0 -38
  232. package/templates/qoder/scripts/extract_api_params.py +0 -246
  233. package/templates/qoder/scripts/extract_routes.py +0 -54
  234. package/templates/qoder/scripts/extract_routes_tree.py +0 -78
  235. package/templates/qoder/scripts/handoff.py +0 -22
  236. package/templates/qoder/scripts/init_developer.py +0 -76
  237. package/templates/qoder/scripts/kg_mcp_server.py +0 -801
  238. package/templates/qoder/scripts/kg_semantic.py +0 -150
  239. package/templates/qoder/scripts/mcp_launcher.py +0 -414
  240. package/templates/qoder/scripts/mysql_mcp_server.py +0 -396
  241. package/templates/qoder/scripts/parse_prds.py +0 -33
  242. package/templates/qoder/scripts/role.py +0 -51
  243. package/templates/qoder/scripts/sync_carriers.py +0 -259
  244. package/templates/qoder/scripts/task.py +0 -1261
  245. package/templates/qoder/scripts/workspace_init.py +0 -102
  246. package/templates/qoder/scripts/zentao_mcp_server.py +0 -424
  247. package/templates/qoder/skills/prompt-enrich/SKILL.md +0 -90
  248. /package/templates/qoder/scripts/{common → deployment}/__init__.py +0 -0
  249. /package/templates/qoder/{skills/prototype-generator/SKILL.md.zcode-79180-2af4721f-f9a6-412c-88db-c0af680d211b.tmp → scripts/domain/__init__.py} +0 -0
  250. /package/templates/qoder/scripts/{common → domain/kg/graph}/graph_traverse.py +0 -0
  251. /package/templates/qoder/scripts/{common → domain/kg/server}/repowiki.py +0 -0
  252. /package/templates/qoder/scripts/{common → foundation/io}/atomicio.py +0 -0
  253. /package/templates/qoder/scripts/{secure-ls.js → validation/test/secure-ls.js} +0 -0
@@ -0,0 +1,317 @@
1
+ # -*- coding: utf-8 -*-
2
+ """
3
+ contract.py - 契约校验器 (解耦模块的核心)
4
+
5
+ 把"君子协定"变成机器可校验。每个产出 (PRD/原型/spec/design-spec/test-cases)
6
+ 带 @contract 头, 本模块解析头部 + 按 contracts/schemas/*.schema.json 校验。
7
+
8
+ 设计原则 (与方案一致):
9
+ - 解耦: 模块间只读契约产出, 不读对方实现。实现可任意替换。
10
+ - 降级而非阻塞: 校验 FAIL 只提示, 不让模块停摆 (符合 contract-header.md)
11
+ - 轻量: 不引 jsonschema 重依赖, 用 stdlib 实现核心校验 (类型/必填/枚举/正则)
12
+ - 兼容现有: 复用现有 @contract 头格式 (MD 的 HTML 注释 / JSON 的 _contract 字段)
13
+
14
+ 核心 API:
15
+ parse_contract(path) -> (contract_dict, source_text)
16
+ 解析文件的契约头 (MD/JSON 两种)
17
+ validate_contract(path) -> Result
18
+ 解析 + 按 schema 校验, 返回统一 Result
19
+ extract_contract_header(md_text) -> dict | None
20
+ 从 Markdown 正文提取 <!-- @contract ... --> 块
21
+
22
+ Usage:
23
+ from foundation.data.contract import validate_contract
24
+ r = validate_contract('workspace/.../REQ-2026-001-prd.md')
25
+ if r.is_warn:
26
+ print('警告:', r.msg) # 软门禁: 带警告继续
27
+ """
28
+
29
+ import json
30
+ import os
31
+ import re
32
+ import sys
33
+ from pathlib import Path
34
+ from typing import Any, Dict, Optional, Tuple
35
+
36
+ # 相对导入
37
+ try:
38
+ from .result import Result, CODE
39
+ from foundation.io.atomicio import safe_read_json
40
+ except ImportError:
41
+ _COMMON_DIR = os.path.dirname(os.path.abspath(__file__))
42
+ if _COMMON_DIR not in sys.path:
43
+ sys.path.insert(0, _COMMON_DIR)
44
+ from result import Result, CODE
45
+ from atomicio import safe_read_json
46
+
47
+ __all__ = [
48
+ "parse_contract",
49
+ "validate_contract",
50
+ "extract_contract_header",
51
+ "extract_json_contract",
52
+ "SCHEMAS_DIR",
53
+ ]
54
+
55
+ # schemas 目录 (相对仓库根: .qoder/contracts/schemas/)
56
+ def _find_schemas_dir() -> Path:
57
+ """定位 contracts/schemas/。向上找 .qoder/contracts/schemas/。"""
58
+ here = Path(__file__).resolve().parent
59
+ for _ in range(8):
60
+ cand = here / "contracts" / "schemas"
61
+ if cand.is_dir():
62
+ return cand
63
+ # common/ 在 .qoder/scripts/common/, schemas 在 .qoder/contracts/schemas/
64
+ cand2 = here.parent.parent / "contracts" / "schemas"
65
+ if cand2.is_dir():
66
+ return cand2
67
+ parent = here.parent
68
+ if parent == here:
69
+ break
70
+ here = parent
71
+ return Path(".qoder/contracts/schemas")
72
+
73
+
74
+ SCHEMAS_DIR = _find_schemas_dir()
75
+
76
+ # 契约类型 → schema 文件名映射
77
+ _SCHEMA_FILES = {
78
+ "prd": "prd.schema.json",
79
+ "design-spec": "design-spec.schema.json",
80
+ "design": "design-spec.schema.json", # 别名
81
+ "test-cases": "test-cases.schema.json",
82
+ "test": "test-cases.schema.json", # 别名
83
+ }
84
+
85
+
86
+ # ============================================================
87
+ # 1. 契约头解析
88
+ # ============================================================
89
+
90
+ # MD 契约头: <!-- @contract prd v1\n platform: web\n ... -->
91
+ _MD_CONTRACT_RE = re.compile(
92
+ r"<!--\s*@contract\s+(\S+)(?:\s+v?(\d+))?\s*\n(.*?)-->",
93
+ re.DOTALL,
94
+ )
95
+ # 契约头内的 key: value 行
96
+ _KV_RE = re.compile(r"^\s*([\w\-]+)\s*:\s*(.+?)\s*$")
97
+
98
+
99
+ def extract_contract_header(md_text: str) -> Optional[Dict[str, Any]]:
100
+ """从 Markdown 正文提取 @contract 块。
101
+
102
+ 格式 (见 contract-header.md):
103
+ <!-- @contract prd v1
104
+ platform: web
105
+ req-id: REQ-2025-0042
106
+ title: 保单批改功能
107
+ acceptance: [批改后实时生效, 批改需审批]
108
+ -->
109
+ """
110
+ m = _MD_CONTRACT_RE.search(md_text)
111
+ if not m:
112
+ return None
113
+ ctype = m.group(1).strip()
114
+ version = int(m.group(2)) if m.group(2) else 1
115
+ body = m.group(3)
116
+ fields: Dict[str, Any] = {"type": ctype, "version": version}
117
+ for line in body.splitlines():
118
+ line = line.strip()
119
+ if not line:
120
+ continue
121
+ kv = _KV_RE.match(line)
122
+ if kv:
123
+ key = kv.group(1).strip()
124
+ val = kv.group(2).strip()
125
+ # acceptance: [a, b] → 列表
126
+ if val.startswith("[") and val.endswith("]"):
127
+ inner = val[1:-1]
128
+ fields[key] = [x.strip() for x in inner.split(",") if x.strip()]
129
+ else:
130
+ fields[key] = val
131
+ return fields
132
+
133
+
134
+ def extract_json_contract(data: dict) -> Optional[Dict[str, Any]]:
135
+ """从 JSON dict 提取 _contract 字段 (design-spec / test-cases 用)。"""
136
+ if isinstance(data, dict) and isinstance(data.get("_contract"), dict):
137
+ c = dict(data["_contract"])
138
+ # 补默认 type/version (schema 里 required, 但容错)
139
+ c.setdefault("type", c.get("type", "unknown"))
140
+ c.setdefault("version", 1)
141
+ return c
142
+ return None
143
+
144
+
145
+ def parse_contract(path) -> Tuple[Optional[Dict[str, Any]], str]:
146
+ """解析任意文件的契约头。
147
+
148
+ Returns:
149
+ (contract_dict, source_text)
150
+ - contract_dict: 解析出的契约字段, 无则 None
151
+ - source_text: 'md' / 'json' / 'unknown'
152
+ """
153
+ path = Path(path)
154
+ if not path.is_file():
155
+ return None, "unknown"
156
+ name = path.name.lower()
157
+ try:
158
+ if name.endswith(".json"):
159
+ data = safe_read_json(path, default=None)
160
+ if data is None:
161
+ return None, "json"
162
+ return extract_json_contract(data), "json"
163
+ # 当作文本 (md 等)
164
+ text = path.read_text(encoding="utf-8", errors="replace")
165
+ hdr = extract_contract_header(text)
166
+ return hdr, "md"
167
+ except Exception:
168
+ return None, "unknown"
169
+
170
+
171
+ # ============================================================
172
+ # 2. 轻量 schema 校验 (不引 jsonschema, stdlib 实现)
173
+ # ============================================================
174
+
175
+ def _load_schema(contract_type: str) -> Optional[dict]:
176
+ """按契约类型加载对应 schema.json。"""
177
+ fname = _SCHEMA_FILES.get(contract_type)
178
+ if not fname:
179
+ return None
180
+ sp = SCHEMAS_DIR / fname
181
+ if not sp.is_file():
182
+ return None
183
+ try:
184
+ return json.loads(sp.read_text(encoding="utf-8"))
185
+ except Exception:
186
+ return None
187
+
188
+
189
+ def _check_field(field: str, value: Any, spec: dict, errors: list, warnings: list):
190
+ """校验单个字段 (类型/枚举/正则/最小长度)。"""
191
+ if spec.get("const") is not None and value != spec["const"]:
192
+ errors.append("字段 {} 必须为常量 {!r}, 实际 {!r}".format(field, spec["const"], value))
193
+ return
194
+ t = spec.get("type")
195
+ if t == "string" and not isinstance(value, str):
196
+ errors.append("字段 {} 应为 string".format(field))
197
+ return
198
+ if t == "integer" and not isinstance(value, int):
199
+ errors.append("字段 {} 应为 integer".format(field))
200
+ return
201
+ if t == "array" and not isinstance(value, list):
202
+ errors.append("字段 {} 应为 array".format(field))
203
+ return
204
+ if t == "object" and not isinstance(value, dict):
205
+ errors.append("字段 {} 应为 object".format(field))
206
+ return
207
+ if "enum" in spec and value not in spec["enum"]:
208
+ errors.append("字段 {} 值 {!r} 不在允许范围 {}".format(field, value, spec["enum"]))
209
+ return
210
+ if "pattern" in spec and isinstance(value, str):
211
+ pat = spec["pattern"]
212
+ if not re.match(pat, value):
213
+ errors.append("字段 {} 值 {!r} 不匹配模式 {}".format(field, value, pat))
214
+ return
215
+ if "minLength" in spec and isinstance(value, str) and len(value) < spec["minLength"]:
216
+ warnings.append("字段 {} 长度 {} 小于最小 {} ".format(field, len(value), spec["minLength"]))
217
+ if "minItems" in spec and isinstance(value, list) and len(value) < spec["minItems"]:
218
+ warnings.append("字段 {} 项数 {} 小于最小 {} ".format(field, len(value), spec["minItems"]))
219
+ if "minimum" in spec and isinstance(value, (int, float)) and value < spec["minimum"]:
220
+ errors.append("字段 {} 值 {} 小于最小 {}".format(field, value, spec["minimum"]))
221
+
222
+
223
+ def _validate_against_schema(data: dict, schema: dict) -> Tuple[list, list]:
224
+ """按 schema 校验 data。返回 (errors, warnings)。"""
225
+ errors: list = []
226
+ warnings: list = []
227
+ required = schema.get("required", [])
228
+ props = schema.get("properties", {})
229
+ # required 检查
230
+ for f in required:
231
+ if f not in data:
232
+ errors.append("缺少必填字段: {}".format(f))
233
+ # 逐字段校验
234
+ for f, val in data.items():
235
+ if f in props:
236
+ _check_field(f, val, props[f], errors, warnings)
237
+ return errors, warnings
238
+
239
+
240
+ # ============================================================
241
+ # 3. 对外主接口
242
+ # ============================================================
243
+
244
+ def validate_contract(path) -> Result:
245
+ """校验一个产出文件的契约。
246
+
247
+ Returns:
248
+ Result:
249
+ - ok + CODE.OK: 合法契约, 校验通过
250
+ - ok + WARN: 有警告 (如字段长度不足), 但可继续 (软门禁)
251
+ - fail: 有错误 (缺必填/类型错/枚举越界)
252
+ - ok + code='no_contract': 无契约头 (降级, 不当错误, 返回 None 提示)
253
+
254
+ 降级原则: 无契约头或 schema 缺失 → 不当错误 (符合 contract-header.md)
255
+ """
256
+ path = Path(path)
257
+ if not path.is_file():
258
+ return Result.failure(
259
+ "文件不存在: {}".format(path),
260
+ code=CODE.NOT_FOUND,
261
+ hint="检查路径",
262
+ )
263
+ contract, src = parse_contract(path)
264
+ if not contract:
265
+ # 无契约头: 降级, 不当错误
266
+ return Result.success(
267
+ msg="⚠️ {} 无 @contract 头, 已降级 (从头文件名/正文推断)".format(path.name),
268
+ code="no_contract",
269
+ )
270
+ ctype = contract.get("type", "")
271
+ schema = _load_schema(ctype)
272
+ if not schema:
273
+ return Result.warning(
274
+ "契约类型 {!r} 无对应 schema, 跳过校验".format(ctype),
275
+ code="no_schema",
276
+ )
277
+ errors, warnings = _validate_against_schema(contract, schema)
278
+ if errors:
279
+ return Result.failure(
280
+ "契约校验失败 ({}): {}".format(path.name, "; ".join(errors[:3])),
281
+ code=CODE.GENERIC,
282
+ hint="按 contracts/schemas/{} 修正".format(_SCHEMA_FILES.get(ctype, ctype + ".schema.json")),
283
+ )
284
+ if warnings:
285
+ return Result.warning(
286
+ "契约有警告 ({}): {}".format(path.name, "; ".join(warnings[:3])),
287
+ code=CODE.BELOW_THRESH,
288
+ )
289
+ return Result.success(
290
+ data={"type": ctype, "req-id": contract.get("req-id"), "platform": contract.get("platform")},
291
+ msg="✓ 契约校验通过 ({} v{})".format(ctype, contract.get("version", 1)),
292
+ )
293
+
294
+
295
+ # ============================================================
296
+ # CLI
297
+ # ============================================================
298
+
299
+ if __name__ == "__main__":
300
+ if len(sys.argv) < 2:
301
+ print("用法: python contract.py <产出文件>")
302
+ print(" 校验 PRD/原型/spec/design-spec/test-cases 的 @contract 头")
303
+ sys.exit(1)
304
+ # 简单 CLI: 复用 graceful_error 风格
305
+ target = sys.argv[1]
306
+ r = validate_contract(target)
307
+ if r.is_warn:
308
+ print("[WARN] {}".format(r.msg))
309
+ if r.hint:
310
+ print(" -> {}".format(r.hint))
311
+ sys.exit(0)
312
+ if not r.ok:
313
+ print("[FAIL] {}".format(r.msg))
314
+ if r.hint:
315
+ print(" -> {}".format(r.hint))
316
+ sys.exit(1)
317
+ print(r.msg)
@@ -18,8 +18,8 @@
18
18
  # 新增埋点: 在这里加一行 → learn.py 计数 + learn_aggregate.py SQL 自动生效
19
19
  EVENT_TYPES = {
20
20
  # PRD 链路
21
- 'prd_accepted': {'stat': 'prds_generated', 'skill': 'wl-prd-full', 'label': 'PRD 发布'},
22
- 'prd_rejected': {'stat': 'rejections', 'skill': 'wl-prd-full', 'label': 'PRD 打回'},
21
+ 'prd_accepted': {'stat': 'prds_generated', 'skill': 'prd-generator', 'label': 'PRD 发布'},
22
+ 'prd_rejected': {'stat': 'rejections', 'skill': 'prd-generator', 'label': 'PRD 打回'},
23
23
  'review_done': {'stat': 'reviews_done', 'skill': 'prd-review', 'label': 'PRD 评审'},
24
24
  # 设计链路
25
25
  'design_import': {'stat': 'designs_imported','skill': 'design-import', 'label': '设计录入'},
@@ -0,0 +1,223 @@
1
+ # -*- coding: utf-8 -*-
2
+ """
3
+ result.py - 统一结果类型 + 优雅错误处理
4
+
5
+ 治"容错率低"的底层基建。现状各脚本错误风格极不统一:
6
+ - 裸 except Exception: pass 吞异常 (build_style_index / eval_prd / team_sync)
7
+ - exit code 含义不一致 (eval_prd 1=不达标或文件不存在; team_sync 0/1/2/3 四档)
8
+ - 调用方只能 parse 中文文案, 无法程序化决策
9
+
10
+ 本模块引入:
11
+ 1. Result 数据类: ok / code / msg / data, 语义化 code
12
+ 2. graceful_error 装饰器: 把 Result/异常翻译成"人类/AI 可读文本 + 一致 exit code"
13
+ 3. 统一 exit code 表 (替代散落各处的魔法数字)
14
+
15
+ 设计原则 (与方案一致):
16
+ - 失败不崩, 输出可读诊断 + 建议下一步
17
+ - 调用方能据 code 程序化决策, 不靠 parse 中文
18
+ - eval_prd 门禁从"硬阻塞"改"软门禁" (WARN 带警告发布)
19
+
20
+ Usage:
21
+ from foundation.data.result import Result, OK, fail, warn, graceful_error
22
+
23
+ @graceful_error
24
+ def main():
25
+ r = do_something()
26
+ if not r.ok:
27
+ return r # 装饰器翻译成可读输出 + exit code
28
+ return Result.ok(data=r.data)
29
+
30
+ Exit code 约定 (全脚本统一, 替代散落魔法数字):
31
+ 0 OK 成功
32
+ 1 GENERIC 通用失败 (未分类)
33
+ 2 BUSY 资源占用 (如 kg_db 被占)
34
+ 3 CONFLICT 冲突需人工 (如 team_sync SYNC_CONFLICT)
35
+ 4 AUTHZ 身份/权限拒绝 (与 identity.EXIT_AUTHZ 对齐)
36
+ 5 NOT_FOUND 文件/资源不存在
37
+ 6 INDEX_MISSING 索引缺失 (需先 init/doctor)
38
+ 7 BELOW_THRESH 门禁未达标 (eval_prd, 软门禁可带警告继续)
39
+ 8 NETWORK 网络/外部服务不可达
40
+ 9 DEPS_MISSING 依赖未装 (pip 包缺失)
41
+ """
42
+
43
+ import functools
44
+ import sys
45
+ import traceback
46
+ from dataclasses import dataclass, field
47
+ from typing import Any, Optional
48
+
49
+ __all__ = [
50
+ "Result",
51
+ "OK",
52
+ "ResultError",
53
+ "ok",
54
+ "fail",
55
+ "warn",
56
+ "graceful_error",
57
+ "EXIT",
58
+ "code_to_exit",
59
+ "CODE",
60
+ ]
61
+
62
+
63
+ # ============================================================
64
+ # 语义化 code 常量 (调用方据此程序化决策)
65
+ # ============================================================
66
+
67
+ class CODE:
68
+ """结果 code 常量。小写字符串, 语义化, 跨脚本一致。"""
69
+ OK = "ok"
70
+ GENERIC = "generic" # 通用未分类
71
+ BUSY = "busy" # 资源占用 (kg_db 锁)
72
+ CONFLICT = "conflict" # 冲突需人工
73
+ AUTHZ = "authz" # 身份/权限
74
+ NOT_FOUND = "not_found" # 文件/资源不存在
75
+ INDEX_MISSING = "index_missing" # 索引缺失
76
+ BELOW_THRESH = "below_thresh" # 门禁未达标
77
+ NETWORK = "network" # 网络不可达
78
+ DEPS_MISSING = "deps_missing" # 依赖未装
79
+
80
+
81
+ # ============================================================
82
+ # code → exit code 映射 (一处定义, 全脚本统一)
83
+ # ============================================================
84
+
85
+ EXIT = {
86
+ CODE.OK: 0,
87
+ CODE.GENERIC: 1,
88
+ CODE.BUSY: 2,
89
+ CODE.CONFLICT: 3,
90
+ CODE.AUTHZ: 4,
91
+ CODE.NOT_FOUND: 5,
92
+ CODE.INDEX_MISSING: 6,
93
+ CODE.BELOW_THRESH: 7,
94
+ CODE.NETWORK: 8,
95
+ CODE.DEPS_MISSING: 9,
96
+ }
97
+
98
+
99
+ def code_to_exit(code: str) -> int:
100
+ """code → exit code, 未知 code 回退 1。"""
101
+ return EXIT.get(code, 1)
102
+
103
+
104
+ # ============================================================
105
+ # Result 数据类
106
+ # ============================================================
107
+
108
+ @dataclass
109
+ class Result:
110
+ """统一结果。
111
+
112
+ Attributes:
113
+ ok: 是否成功
114
+ code: 语义化 code (见 CODE 常量), 调用方据此决策
115
+ msg: 人类可读消息
116
+ data: 成功时携带的数据 (任意)
117
+ hint: 失败时的"下一步建议" (可选, 输出给用户/AI)
118
+ """
119
+ ok: bool
120
+ code: str = CODE.GENERIC
121
+ msg: str = ""
122
+ data: Any = None
123
+ hint: Optional[str] = None
124
+
125
+ # ---- 工厂方法 ----
126
+ @classmethod
127
+ def success(cls, data: Any = None, msg: str = "", code: str = CODE.OK) -> "Result":
128
+ return cls(ok=True, code=code, msg=msg, data=data)
129
+
130
+ @classmethod
131
+ def failure(cls, msg: str, code: str = CODE.GENERIC,
132
+ hint: Optional[str] = None, data: Any = None) -> "Result":
133
+ return cls(ok=False, code=code, msg=msg, hint=hint, data=data)
134
+
135
+ @classmethod
136
+ def warning(cls, msg: str, code: str = CODE.BELOW_THRESH,
137
+ hint: Optional[str] = None, data: Any = None) -> "Result":
138
+ """WARN: 软门禁/带警告继续。ok=True (可继续), 但带警告。"""
139
+ return cls(ok=True, code=code, msg=msg, hint=hint, data=data)
140
+
141
+ # ---- 便捷判断 ----
142
+ @property
143
+ def is_warn(self) -> bool:
144
+ """是否为"成功但有警告"(如门禁 WARN 档)。"""
145
+ return self.ok and self.code != CODE.OK
146
+
147
+ def __bool__(self) -> bool:
148
+ return self.ok
149
+
150
+
151
+ # 模块级便捷别名 (常见用法)
152
+ OK = Result.success()
153
+ ResultError = type("ResultError", (Exception,), {}) # 预留: 未来 raise Result(r)
154
+
155
+
156
+ def ok(data: Any = None, msg: str = "") -> Result:
157
+ return Result.success(data=data, msg=msg)
158
+
159
+
160
+ def fail(msg: str, code: str = CODE.GENERIC, hint: Optional[str] = None) -> Result:
161
+ return Result.failure(msg=msg, code=code, hint=hint)
162
+
163
+
164
+ def warn(msg: str, code: str = CODE.BELOW_THRESH, hint: Optional[str] = None) -> Result:
165
+ return Result.warning(msg=msg, code=code, hint=hint)
166
+
167
+
168
+ # ============================================================
169
+ # graceful_error 装饰器
170
+ # ============================================================
171
+
172
+ def _emit(r: Result, *, exit_on_fail: bool = True) -> int:
173
+ """把 Result 翻译成可读输出 + 返回 exit code。"""
174
+ if r.ok and not r.is_warn:
175
+ if r.msg:
176
+ print(r.msg)
177
+ return 0
178
+ if r.is_warn:
179
+ # WARN: 打印警告但不当失败
180
+ print("[WARN] {}".format(r.msg))
181
+ if r.hint:
182
+ print(" -> {}".format(r.hint))
183
+ return code_to_exit(r.code) if False else 0 # WARN 视为成功 (exit 0)
184
+ # FAIL
185
+ tag = r.code.upper() if r.code != CODE.GENERIC else "ERROR"
186
+ print("[{}] {}".format(tag, r.msg))
187
+ if r.hint:
188
+ print(" -> {}".format(r.hint))
189
+ if exit_on_fail:
190
+ sys.exit(code_to_exit(r.code))
191
+ return code_to_exit(r.code)
192
+
193
+
194
+ def graceful_error(func):
195
+ """装饰 main(): 统一把返回值 (Result/None/int) 翻译成可读输出 + exit code。
196
+
197
+ - 返回 Result: 按 ok/code 翻译
198
+ - 返回 int: 直接当 exit code
199
+ - 返回 None: 视为成功 (exit 0)
200
+ - 抛异常: 捕获, 输出诊断 + traceback (失败不崩 → 但 main 层崩溃要可见)
201
+ """
202
+ @functools.wraps(func)
203
+ def wrapper(*args, **kwargs):
204
+ try:
205
+ ret = func(*args, **kwargs)
206
+ except SystemExit:
207
+ raise
208
+ except KeyboardInterrupt:
209
+ print("\n[中断] 用户取消")
210
+ sys.exit(130)
211
+ except Exception as e:
212
+ # 未捕获异常: 输出诊断 + 简短 traceback, 不静默吞
213
+ print("[ERROR] 未预期异常: {}".format(e))
214
+ print(" -> 这是 bug, 请反馈。traceback:")
215
+ traceback.print_exc()
216
+ sys.exit(1)
217
+ if isinstance(ret, Result):
218
+ return _emit(ret)
219
+ if isinstance(ret, int):
220
+ sys.exit(ret)
221
+ # None / 其他: 成功
222
+ return 0
223
+ return wrapper