@hupan56/wlkj 3.1.3 → 3.1.5

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 (31) hide show
  1. package/package.json +1 -1
  2. package/templates/qoder/commands/optional/wl-insight.md +1 -1
  3. package/templates/qoder/commands/optional/wl-report.md +2 -2
  4. package/templates/qoder/commands/optional/wl-status.md +1 -1
  5. package/templates/qoder/commands/wl-code.md +1 -1
  6. package/templates/qoder/commands/wl-design.md +2 -0
  7. package/templates/qoder/commands/wl-init.md +1 -1
  8. package/templates/qoder/commands/wl-prd.md +4 -0
  9. package/templates/qoder/commands/wl-req.md +3 -0
  10. package/templates/qoder/commands/wl-search.md +2 -0
  11. package/templates/qoder/commands/wl-task.md +1 -1
  12. package/templates/qoder/commands/wl-test.md +1 -1
  13. package/templates/qoder/contracts/CHANGELOG.md +418 -0
  14. package/templates/qoder/contracts/README.md +184 -0
  15. package/templates/qoder/contracts/code.md +81 -0
  16. package/templates/qoder/contracts/commit.md +86 -0
  17. package/templates/qoder/contracts/contract-header.md +76 -0
  18. package/templates/qoder/contracts/design.md +106 -0
  19. package/templates/qoder/contracts/fallback.md +126 -0
  20. package/templates/qoder/contracts/isolation.md +119 -0
  21. package/templates/qoder/contracts/prd.md +118 -0
  22. package/templates/qoder/contracts/schemas/design-spec.schema.json +46 -0
  23. package/templates/qoder/contracts/schemas/prd.schema.json +36 -0
  24. package/templates/qoder/contracts/schemas/test-cases.schema.json +40 -0
  25. package/templates/qoder/contracts/spec.md +81 -0
  26. package/templates/qoder/contracts/task.md +125 -0
  27. package/templates/qoder/contracts/test.md +112 -0
  28. package/templates/qoder/scripts/deployment/setup/repo_root.py +17 -4
  29. package/templates/qoder/scripts/validation/test/autotest.py +3 -0
  30. package/templates/qoder/scripts/validation/test/autotest_data.py +3 -1
  31. package/templates/qoder/skills/wl-prd-full/SKILL.md +50 -50
@@ -0,0 +1,112 @@
1
+ # 模块契约:test(测试)
2
+
3
+ > 测试模块。三条线:quick(默认)/ browser / unit。
4
+ > 测试用例锚定到 PRD 的验收标准编号。
5
+
6
+ ---
7
+
8
+ ## Input(吃什么)
9
+
10
+ 三条线,输入不同:
11
+
12
+ | 线 | 输入 | 怎么用 |
13
+ |----|------|--------|
14
+ | **quick**(默认) | 零输入,用户直接给用例 | `autotest.py quick --cases '<JSON>' --desc "..." --platform <web\|app>` |
15
+ | **browser** | 任务名 | 读任务 PRD 的验收标准,**不搜源码**;`autotest.py generate <task>` / `run <task>` |
16
+ | **unit** | 代码关键词 | 搜代码读源码,test-generator skill + `search_index.py` |
17
+ | **assertion-gen** | PRD 验收章节 / GWT 文本 | `autotest.py assertion-gen --task <任务> --page <业务词>`(详见下方) |
18
+
19
+ ### 降级链路
20
+
21
+ | 优先级 | 输入 |
22
+ |--------|------|
23
+ | **完整** | spec 文件(接口清晰) |
24
+ | 降级 1 | PRD(读 `acceptance` 列表) |
25
+ | 降级 2 | 任务 PRD(browser 线) |
26
+ | 降级 3 ★ | 一句话 + 全索引(搜相关页面/字段,凑出验收点) |
27
+
28
+ **环境**:域名/环境来自 `.qoder/config.yaml` 的 `autotest:` 段;`--url` 可临时覆盖。
29
+ **测试数据**:个人数据在 `workspace/members/{名字}/autotest-data.yaml`(gitignored);
30
+ 真实数据用 `{{ask:描述}}` 占位,**绝不编造**。
31
+
32
+ ---
33
+
34
+ ## Output(吐什么)
35
+
36
+ ### quick / browser
37
+
38
+ ```
39
+ 用例 JSON + 执行计划
40
+ 回传:autotest.py ... --record '{"Q-1":"pass"}'
41
+ browser 骨架写到任务的 autotest-cases.jsonl
42
+ ```
43
+
44
+ ### unit
45
+
46
+ ```
47
+ 路径:tests/{package-path}/{ClassName}Test.java
48
+ 风格:Given-When-Then,@DisplayName 中文,AssertJ,全 Mock
49
+ ```
50
+
51
+ ### assertion-gen(业务规则断言)
52
+
53
+ ```
54
+ 输出:workspace/tasks/<task>/assertions.json
55
+ 结构:{ _contract:{type:assertions}, assertions:[{intent:"assert", expect:{...}, source, desc}] }
56
+ 每条断言带 source 置信度标记:
57
+ - 规则·句中含接口 / 图谱·按钮画像 / 图谱·字段表命中 = 可信
58
+ - 需确认·字段未命中 / 需确认·接口未指明 = 待补 {{ask:}} 占位
59
+ expect 字段对齐 autotest_webaccess.py _check_expect 契约:
60
+ fetch 断言:{fetch, jsonpath, equals} 必填 + {method?, body?} 可选 (默认 POST/{})
61
+ dom 断言: {dom, jsonpath, equals} 三字段齐全
62
+ 注: assertion_gen 只产必填 3 字段, method/body 走 _check_expect 默认值。
63
+ ```
64
+
65
+ **红线:断言不编造。** PRD 无结构化验收(空/纯描述)→ 输出"无可抽取的结构化断言"并退出码 2,
66
+ **绝不**让 LLM 瞜编 `expect.equals`。图谱/字段表只补字段不编语义。
67
+
68
+ ### 用例契约头(JSON)
69
+
70
+ ```json
71
+ {
72
+ "_contract": { "type": "test-cases", "version": 1, "source": "REQ-2025-0042" },
73
+ "cases": [
74
+ { "id": "Q-1", "anchor": "REQ-2025-0042#验收点1", "step": "...", "expect": "..." }
75
+ ]
76
+ }
77
+ ```
78
+
79
+ **每个用例的 `anchor` 字段锚定到 PRD 验收标准编号**——这是测试可追溯的关键。
80
+
81
+ ---
82
+
83
+ ## 校验
84
+
85
+ - ❌ **无独立校验脚本**。靠自检清单:
86
+ - [ ] 正常路径 + 错误路径 100% 覆盖
87
+ - [ ] 核心边界覆盖
88
+ - [ ] 无外部依赖(全 Mock)
89
+ - [ ] 测试方法间无依赖
90
+ - [ ] 每个用例有 anchor 指向验收标准
91
+
92
+ **铁律**:`autotest.py` 绝不 import playwright/selenium。
93
+ 浏览器执行由 QoderWork 连接器承担,无连接器 → 降级为人工核对清单(不报错)。
94
+
95
+ ---
96
+
97
+ ## 与上下游的接口
98
+
99
+ | 方向 | 接口 |
100
+ |------|------|
101
+ | 上游 prd | 读 acceptance 列表 |
102
+ | 上游 spec | 读接口定义 |
103
+ | 上游 code | unit 线读源码 |
104
+ | 下游 commit | 测试通过是 commit 门禁之一 |
105
+
106
+ ---
107
+
108
+ ## 现状提醒
109
+
110
+ - ✅ 三线分工清晰。
111
+ - 🧩 QoderWork 增强(可选):browser 线的浏览器执行。无连接器 → 降级人工核对。
112
+ - ⚠️ 测试数据绝不能编造——这条是红线。
@@ -64,11 +64,17 @@ def find_repo_root():
64
64
  here = parent
65
65
 
66
66
  # 方法4: 本脚本所在位置(若它被放到仓库 .qoder/scripts/ 下)
67
+ # ⚠ 仅当脚本真在 <repo>/.qoder/scripts/ 结构内才成立。
68
+ # 安装到 ~/.qoderwork/repo_root.py 的副本不能走这步 ——
69
+ # s 上三级会解析到用户主目录的父目录(非仓库)。用目录名校验避免误判。
67
70
  try:
68
71
  s = os.path.abspath(__file__)
69
- root = os.path.dirname(os.path.dirname(os.path.dirname(s)))
70
- if os.path.isdir(os.path.join(root, '.qoder', 'scripts')):
71
- return root
72
+ s_dir = os.path.dirname(s)
73
+ # 校验: 本脚本是否真在 .../<repo>/.qoder/scripts/
74
+ if os.path.basename(s_dir) == 'scripts' and os.path.basename(os.path.dirname(s_dir)) == '.qoder':
75
+ root = os.path.dirname(os.path.dirname(s_dir))
76
+ if os.path.isdir(os.path.join(root, '.qoder', 'scripts')):
77
+ return root
72
78
  except Exception:
73
79
  pass
74
80
 
@@ -78,7 +84,14 @@ def find_repo_root():
78
84
  def main():
79
85
  root = find_repo_root()
80
86
  if not root:
81
- print('ERROR: 无法定位 QODER 仓库根。请确认 ~/.qoderwork/mcp.json 存在且指向有效的 kg_mcp_server.py', file=sys.stderr)
87
+ # 清晰诊断, 不静默 (命令里 R= 回退 '.' 但那常是会话临时目录, 后续脚本全失败)
88
+ import sys as _sys
89
+ anchor = os.path.join(os.path.expanduser('~'), '.qoderwork', '.repo-root')
90
+ _sys.stderr.write('ERROR: repo_root.py 无法定位 QODER 仓库根。\n')
91
+ _sys.stderr.write(' cwd: %s\n' % os.getcwd())
92
+ _sys.stderr.write(' 锚点 %s 存在? %s\n' % (anchor, os.path.isfile(anchor)))
93
+ _sys.stderr.write(' 修复: 在项目根跑 "npx @hupan56/wlkj init" (写 .repo-root 锚点),\n')
94
+ _sys.stderr.write(' 或设环境变量 QODER_REPO=<项目根>。\n')
82
95
  return 1
83
96
 
84
97
  # 子路径模式: --script / --template
@@ -96,6 +96,9 @@ from foundation.data.task_utils import load_task_json, resolve_task_dir # noqa:
96
96
 
97
97
  # v3.0 f5: 数据层拆到 autotest_data.py
98
98
  from autotest_data import * # noqa: F401,F403 (常量+数据函数)
99
+ # 显式导入下划线 helper: `import *` 默认不导入下划线名, 但本文件直接调用了它们
100
+ # (旧 bug: 未显式导入 → /wl-test quick/recall 调用时 NameError)
101
+ from autotest_data import _normalize_url_pattern, _cases_path # noqa: F401
99
102
 
100
103
  def _resolve_env_platform(args):
101
104
  """解析域名 + 校验。返回 (base, src) 或 (None, None)。"""
@@ -17,11 +17,13 @@ if _cp not in _s.path: _s.path.insert(0, _cp)
17
17
  from bootstrap import setup; setup()
18
18
 
19
19
  import os, sys, json, shutil
20
+ from datetime import datetime
20
21
  from pathlib import Path
21
- from foundation.core.paths import get_repo_root, get_developer
22
+ from foundation.core.paths import get_repo_root, get_developer, MEMBERS_DIR, index_path
22
23
  from foundation.data.task_utils import load_task_json, resolve_task_dir
23
24
 
24
25
 
26
+
25
27
  # 路径常量
26
28
  # ---------------------------------------------------------------------------
27
29
 
@@ -38,49 +38,49 @@ R=$(python ~/.qoderwork/repo_root.py 2>/dev/null) || R=.
38
38
  | 2 / APP / H5 / 移动端 | APP | `--platform app` | prototype-app.html |
39
39
  | 3 / 都要 / 两端 | Both | 都跑 | 两个模板,两份原型 |
40
40
 
41
- ## ⚠️ STEP 0.5: 原型要不要画?(融入 Step 2 批量确认,不另开轮次)
42
-
43
- **不交给 AI 默默判断**(避免"AI 觉得不需要、用户其实想要"的扯皮),但也**不额外开一轮问答**——
44
- 合并进 Step 2「轻反思 + 批量确认」那一步,跟背景/目标/指标一起一次性问完。
45
-
46
- ### 关键词触发矩阵(给默认建议,用户可一键改)
47
-
48
- | 模式 | 关键词信号 | 默认建议 | 例子 |
49
- |------|-----------|---------|------|
50
- | 评审 | (任何) | ❌ 不画 | 评审别人的 PRD,本就不画 |
51
- | 快速 | 加字段/按钮/文案/导出/接口 | ❌ 不画 | "列表加个导出按钮" |
52
- | 快速 | 含"页面/弹窗/表单/交互" | ✅ 画 | "弹窗里加 3 个字段" |
53
- | 完整 | 含"页面/菜单/详情/列表/向导/看板/大屏" | ✅ 画 | "新出库单页面" |
54
- | 完整 | 纯"规则/接口/定时/对账/计算/开关/参数/性能" | ❌ 不画 | "出交车点检固定为总部全局规则" |
55
- | 完整 | 混合(既有页面又有规则) | ✅ 画(只画页面那部分) | "新增审批流 + 审批页" |
56
-
57
- ### Step 2 问法(合并到批量确认)
58
-
59
- ```
60
- 确认 3 件事就开工:
61
- 1. 背景/目标:……(AI 已补,要改吗?)
62
- 2. 关键指标:……(AI 已补,要改吗?)
63
- 3. 原型图:我建议【画/不画】,因为……(理由)。
64
- 要改吗?(画 / 不画 / 两端各画一份)
65
- ```
66
-
67
- ### 两端都画时的边界(v3.1)
68
-
69
- 「完整模式 + 两端都要」如果用户选了"画",**默认只画主端一份**(用户在 PRD 里标的主端),
70
- 另一端在 PRD 第 11 章写"复用主端规则,UI 适配另出"。用户明确说"两端各画一份"才画 2 份。
71
-
72
- ### 不画原型时的下游连锁(必须对齐)
73
-
74
- | 产物 | 处理 |
75
- |------|------|
76
- | PRD 第 11 章「原型地址」 | 写 `无({原因},经用户确认豁免)`,**保留章节标题**不省略 |
77
- | PRD 第 1 行 `@contract` 头 | 标记 `prototype: none` |
78
- | eval_prd | 检测到 `prototype: none` → A2 自动跳过,满分从 100 降到 70,照样能 PASS |
79
- | /wl-design | 读到 `prototype: none` 跳过原型环节 |
80
- | /wl-code | 直接以 PRD 字段表为准 |
81
-
82
- ---
83
-
41
+ ## ⚠️ STEP 0.5: 原型要不要画?(融入 Step 2 批量确认,不另开轮次)
42
+
43
+ **不交给 AI 默默判断**(避免"AI 觉得不需要、用户其实想要"的扯皮),但也**不额外开一轮问答**——
44
+ 合并进 Step 2「轻反思 + 批量确认」那一步,跟背景/目标/指标一起一次性问完。
45
+
46
+ ### 关键词触发矩阵(给默认建议,用户可一键改)
47
+
48
+ | 模式 | 关键词信号 | 默认建议 | 例子 |
49
+ |------|-----------|---------|------|
50
+ | 评审 | (任何) | ❌ 不画 | 评审别人的 PRD,本就不画 |
51
+ | 快速 | 加字段/按钮/文案/导出/接口 | ❌ 不画 | "列表加个导出按钮" |
52
+ | 快速 | 含"页面/弹窗/表单/交互" | ✅ 画 | "弹窗里加 3 个字段" |
53
+ | 完整 | 含"页面/菜单/详情/列表/向导/看板/大屏" | ✅ 画 | "新出库单页面" |
54
+ | 完整 | 纯"规则/接口/定时/对账/计算/开关/参数/性能" | ❌ 不画 | "出交车点检固定为总部全局规则" |
55
+ | 完整 | 混合(既有页面又有规则) | ✅ 画(只画页面那部分) | "新增审批流 + 审批页" |
56
+
57
+ ### Step 2 问法(合并到批量确认)
58
+
59
+ ```
60
+ 确认 3 件事就开工:
61
+ 1. 背景/目标:……(AI 已补,要改吗?)
62
+ 2. 关键指标:……(AI 已补,要改吗?)
63
+ 3. 原型图:我建议【画/不画】,因为……(理由)。
64
+ 要改吗?(画 / 不画 / 两端各画一份)
65
+ ```
66
+
67
+ ### 两端都画时的边界(v3.1)
68
+
69
+ 「完整模式 + 两端都要」如果用户选了"画",**默认只画主端一份**(用户在 PRD 里标的主端),
70
+ 另一端在 PRD 第 11 章写"复用主端规则,UI 适配另出"。用户明确说"两端各画一份"才画 2 份。
71
+
72
+ ### 不画原型时的下游连锁(必须对齐)
73
+
74
+ | 产物 | 处理 |
75
+ |------|------|
76
+ | PRD 第 11 章「原型地址」 | 写 `无({原因},经用户确认豁免)`,**保留章节标题**不省略 |
77
+ | PRD 第 1 行 `@contract` 头 | 标记 `prototype: none` |
78
+ | eval_prd | 检测到 `prototype: none` → A2 自动跳过,满分从 100 降到 70,照样能 PASS |
79
+ | /wl-design | 读到 `prototype: none` 跳过原型环节 |
80
+ | /wl-code | 直接以 PRD 字段表为准 |
81
+
82
+ ---
83
+
84
84
  ## 衔接 insight(参考模式)
85
85
 
86
86
  **若用户说"按 insight 报告做 PRD""转 PRD"或带 `参考:<报告路径>`:**
@@ -98,12 +98,12 @@ Read 指定 insight 报告 → 把现状锚定 + 选中的机会/Gap 作为 PRD
98
98
  > 拿到后**按 $ROLE 视角看重点**:dev 看代码段、design 看页面+风格段、pm 看字段+历史PRD段。prefetch 之后不要再串行 search。
99
99
  > 不硬编码 pm:dev 写 PRD 能拿到代码视角、设计师拿到页面+风格视角,比统一 PM 视角更"准"。
100
100
  2. **轻反思 + 批量确认**:AI 自动补全背景/目标/指标,只把真缺失的 1-2 点一次性问完
101
- 3. **一次生成 + 3 道质量锁**:
102
- - PRD(prd-full-template.md,13 章)一次 Write
103
- - **原型按 Step 0.5 决策**:用户选"画"→ 走 prototype-generator(必须先 fill_prototype);
104
- 用户选"不画"→ 跳过,PRD 第 11 章写 `无` + contract 头标 `prototype: none`
105
- - 3 道质量锁:背景锁/字段锁/闭环锁 → eval_prd(**不要加 `2>/dev/null`,stderr 阻断原因要看**)→ 不过自动修
106
- - eval 命令:`python "$R/.qoder/scripts/orchestration/wlkj.py" eval <prd.md> [原型.html]`
101
+ 3. **一次生成 + 3 道质量锁**:
102
+ - PRD(prd-full-template.md,13 章)一次 Write
103
+ - **原型按 Step 0.5 决策**:用户选"画"→ 走 prototype-generator(必须先 fill_prototype);
104
+ 用户选"不画"→ 跳过,PRD 第 11 章写 `无` + contract 头标 `prototype: none`
105
+ - 3 道质量锁:背景锁/字段锁/闭环锁 → eval_prd(**不要加 `2>/dev/null`,stderr 阻断原因要看**)→ 不过自动修
106
+ - eval 命令:`python "$R/.qoder/scripts/orchestration/wlkj.py" eval <prd.md> [原型.html]`
107
107
  (wlkj.py eval 内部映射 validation/metrics/eval_prd.py,路径不会漂移)
108
108
  4. **发布 + 归档 + 同步**(3 合 1):collect_prds → team_sync push → archive_prd
109
109
 
@@ -116,5 +116,5 @@ Read 指定 insight 报告 → 把现状锚定 + 选中的机会/Gap 作为 PRD
116
116
  ## REQ-ID 分配(并发安全)
117
117
 
118
118
  ```bash
119
- python -c "import sys; sys.path.insert(0,'.qoder/scripts'); from common.reqid import allocate_req_id; n=allocate_req_id(); print('REQ-%d-%03d' % (__import__('datetime').date.today().year, n))"
119
+ python -c "import sys; sys.path.insert(0,'.qoder/scripts'); from foundation.io.reqid import allocate_req_id; n=allocate_req_id(); print('REQ-%d-%03d' % (__import__('datetime').date.today().year, n))"
120
120
  ```