@hupan56/wlkj 3.1.31 → 3.2.0
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.
- package/package.json +1 -1
- package/templates/qoder/agents/insight-planning.md +67 -67
- package/templates/qoder/agents/prd-reference.md +47 -47
- package/templates/qoder/commands/optional/wl-insight.md +276 -276
- package/templates/qoder/commands/optional/wl-spec.md +10 -2
- package/templates/qoder/commands/wl-code.md +63 -4
- package/templates/qoder/commands/wl-design.md +1 -1
- package/templates/qoder/commands/wl-prd.md +87 -13
- package/templates/qoder/commands/wl-req.md +10 -3
- package/templates/qoder/commands/wl-task.md +613 -613
- package/templates/qoder/commands/wl-test.md +2 -2
- package/templates/qoder/contracts/CHANGELOG.md +418 -0
- package/templates/qoder/contracts/README.md +180 -0
- package/templates/qoder/contracts/code.md +82 -0
- package/templates/qoder/contracts/commit.md +86 -0
- package/templates/qoder/contracts/contract-header.md +76 -0
- package/templates/qoder/contracts/design.md +106 -0
- package/templates/qoder/contracts/fallback.md +126 -0
- package/templates/qoder/contracts/isolation.md +119 -0
- package/templates/qoder/contracts/prd.md +118 -0
- package/templates/qoder/contracts/schemas/design-spec.schema.json +46 -0
- package/templates/qoder/contracts/schemas/prd.schema.json +36 -0
- package/templates/qoder/contracts/schemas/test-cases.schema.json +40 -0
- package/templates/qoder/contracts/spec.md +112 -0
- package/templates/qoder/contracts/task.md +125 -0
- package/templates/qoder/contracts/test.md +112 -0
- package/templates/qoder/hooks/post-tool-use.py +20 -0
- package/templates/qoder/hooks/stop-eval.py +47 -0
- package/templates/qoder/rules/wl-pipeline.md +37 -0
- package/templates/qoder/scripts/deployment/setup/install_qoderwork.py +11 -0
- package/templates/qoder/scripts/deployment/setup/setup.py +70 -0
- package/templates/qoder/scripts/deployment/setup/wlkj_shim.py +104 -0
- package/templates/qoder/scripts/domain/deployment/deploy_to_test.py +298 -0
- package/templates/qoder/scripts/domain/kg/build/kg_build.py +241 -22
- package/templates/qoder/scripts/domain/kg/build/kg_incremental.py +108 -3
- package/templates/qoder/scripts/domain/kg/build/kg_signatures.py +169 -0
- package/templates/qoder/scripts/domain/kg/extract/ts_extract.py +111 -0
- package/templates/qoder/scripts/domain/kg/storage/kg_duckdb.py +43 -0
- package/templates/qoder/scripts/domain/requirement/req.py +134 -28
- package/templates/qoder/scripts/domain/task/zentao_panel.py +688 -53
- package/templates/qoder/scripts/foundation/core/paths.py +102 -0
- package/templates/qoder/scripts/protocol/mcp/zentao_mcp_server.py +23 -10
- package/templates/qoder/scripts/validation/eval/qwork_harness.py +1 -1
- package/templates/qoder/scripts/validation/eval/report-commands.md +2 -2
- package/templates/qoder/settings.json +27 -9
- package/templates/qoder/skills/design-import/SKILL.md +3 -3
- package/templates/qoder/skills/design-review/SKILL.md +1 -1
- package/templates/qoder/skills/prd-generator/SKILL.md +4 -4
- package/templates/qoder/skills/prd-review/SKILL.md +1 -1
- package/templates/qoder/skills/prototype-generator/SKILL.md +3 -3
- package/templates/qoder/skills/spec-coder/SKILL.md +1 -1
- package/templates/qoder/skills/spec-generator/SKILL.md +80 -23
- package/templates/qoder/skills/test-generator/SKILL.md +1 -1
- package/templates/qoder/skills/wl-code/SKILL.md +13 -1
- package/templates/qoder/skills/wl-commit/SKILL.md +1 -1
- package/templates/qoder/skills/wl-design/SKILL.md +6 -6
- package/templates/qoder/skills/wl-init/SKILL.md +2 -2
- package/templates/qoder/skills/wl-insight/SKILL.md +5 -5
- package/templates/qoder/skills/wl-prd/SKILL.md +60 -0
- package/templates/qoder/skills/wl-report/SKILL.md +2 -2
- package/templates/qoder/skills/wl-search/SKILL.md +1 -1
- package/templates/qoder/skills/wl-spec/SKILL.md +2 -2
- package/templates/qoder/skills/wl-status/SKILL.md +2 -2
- package/templates/qoder/skills/wl-task/SKILL.md +3 -3
- package/templates/qoder/skills/wl-test/SKILL.md +2 -2
- package/templates/qoder/templates/spec-template.md +124 -0
- package/templates/root/AGENTS.md +32 -4
- package/templates/qoder/skills/wl-prd-full/SKILL.md +0 -121
- package/templates/qoder/skills/wl-prd-quick/SKILL.md +0 -50
- package/templates/qoder/skills/wl-prd-review/SKILL.md +0 -47
|
@@ -640,6 +640,108 @@ def get_module_spec_dir(module: Optional[str] = None) -> Path:
|
|
|
640
640
|
return spec_dir
|
|
641
641
|
|
|
642
642
|
|
|
643
|
+
# ── spec 检测 (v3.x wl-code 门禁依据) ──────────────────────────
|
|
644
|
+
# 设计原则: 纯读函数, 不写文件不改状态, 任何调用都安全。
|
|
645
|
+
# spec 只存 2 处: workspace/specs/REQ-{ID}-*.spec.md (全局) + 当前任务目录 spec.md。
|
|
646
|
+
# status 从契约头读 (@contract spec v1 / status: draft|confirmed), 无头视同 draft。
|
|
647
|
+
# ─────────────────────────────────────────────────────────────
|
|
648
|
+
|
|
649
|
+
def get_spec_status(spec_path) -> str:
|
|
650
|
+
"""读 spec 文件契约头的 status 字段。
|
|
651
|
+
|
|
652
|
+
返回: 'draft' | 'confirmed' | None(文件不存在/无法解析)
|
|
653
|
+
- 无 status 字段 或 无契约头 → 视同 'draft' (wl-code 走确认环节, 不直接写码)
|
|
654
|
+
- 文件不存在 → None (wl-code 视为无 spec, 触发生成)
|
|
655
|
+
"""
|
|
656
|
+
import re
|
|
657
|
+
try:
|
|
658
|
+
text = open(spec_path, encoding="utf-8").read()
|
|
659
|
+
except (OSError, IOError, TypeError):
|
|
660
|
+
return None
|
|
661
|
+
# 直接正则取 @contract spec 头里的 status (不依赖 contract.py, 避免循环 import)
|
|
662
|
+
m = re.search(r"<!--\s*@contract\s+spec(?:\s+v\d+)?\s*(.*?)-->", text, re.S)
|
|
663
|
+
if not m:
|
|
664
|
+
return "draft" # 无契约头 → 视同 draft (安全: 走确认环节)
|
|
665
|
+
body = m.group(1)
|
|
666
|
+
sm = re.search(r"^\s*status\s*:\s*(\S+)\s*$", body, re.M)
|
|
667
|
+
if not sm:
|
|
668
|
+
return "draft" # 有头但无 status 字段 → draft
|
|
669
|
+
val = sm.group(1).strip().lower()
|
|
670
|
+
return val if val in ("draft", "confirmed") else "draft"
|
|
671
|
+
|
|
672
|
+
|
|
673
|
+
def find_spec(req_id=None, developer_name=None, repo_root=None) -> list:
|
|
674
|
+
"""按 REQ-ID 或当前任务找 spec 文件 (wl-code gate 的核心检测)。
|
|
675
|
+
|
|
676
|
+
查 2 处 (spec 不存时间桶):
|
|
677
|
+
A. workspace/specs/REQ-{ID}-*.spec.md (全局 specs, glob 前缀匹配)
|
|
678
|
+
B. 当前任务目录的 spec.md (无 req_id 时, 读 .current-task)
|
|
679
|
+
|
|
680
|
+
Args:
|
|
681
|
+
req_id: REQ-YYYY-NNN 形式。传了就只查 A; 不传则查 B (当前任务)。
|
|
682
|
+
Returns:
|
|
683
|
+
[{path, status, source-req}, ...] 按优先级 (confirmed 优先)
|
|
684
|
+
status: 'draft' | 'confirmed' (从契约头读, 无头=draft)
|
|
685
|
+
"""
|
|
686
|
+
import re
|
|
687
|
+
if repo_root is None:
|
|
688
|
+
repo_root = PROJECT_ROOT
|
|
689
|
+
results = []
|
|
690
|
+
|
|
691
|
+
def _inspect(p):
|
|
692
|
+
"""读一个 spec 文件, 返回它的元信息 dict 或 None。"""
|
|
693
|
+
try:
|
|
694
|
+
text = open(p, encoding="utf-8").read()
|
|
695
|
+
except (OSError, IOError):
|
|
696
|
+
return None
|
|
697
|
+
m = re.search(r"<!--\s*@contract\s+spec(?:\s+v\d+)?\s*(.*?)-->", text, re.S)
|
|
698
|
+
body = m.group(1) if m else ""
|
|
699
|
+
status_m = re.search(r"^\s*status\s*:\s*(\S+)\s*$", body, re.M)
|
|
700
|
+
status = status_m.group(1).strip().lower() if status_m else "draft"
|
|
701
|
+
status = status if status in ("draft", "confirmed") else "draft"
|
|
702
|
+
sreq_m = re.search(r"^\s*source-req\s*:\s*(\S+)\s*$", body, re.M)
|
|
703
|
+
source_req = sreq_m.group(1).strip() if sreq_m else None
|
|
704
|
+
return {"path": str(p), "status": status, "source-req": source_req}
|
|
705
|
+
|
|
706
|
+
# A. 时间桶 REQ-ID 容器 (spec-generator 主写入位置, 优先查)
|
|
707
|
+
# 复用 find_req_dirs 跨桶找到容器, 再在容器里找 *.spec.md
|
|
708
|
+
if req_id:
|
|
709
|
+
clean_m = re.match(r"(REQ-\d{4}-\d{3,})", str(req_id))
|
|
710
|
+
clean = clean_m.group(1) if clean_m else str(req_id)
|
|
711
|
+
# A1. 时间桶容器 (members/{dev}/{drafts|outputs}/{桶}/{REQ-ID}/)
|
|
712
|
+
for hit in find_req_dirs(clean, developer_name=developer_name, repo_root=repo_root):
|
|
713
|
+
for f in sorted(Path(hit["path"]).glob("*.spec.md")):
|
|
714
|
+
info = _inspect(f)
|
|
715
|
+
if info:
|
|
716
|
+
results.append(info)
|
|
717
|
+
# A2. 兜底: 全局 .qoder/workspace/specs/ (老 spec 或不在桶里的)
|
|
718
|
+
specs_root = QODER_DIR / "workspace" / "specs"
|
|
719
|
+
if specs_root.is_dir():
|
|
720
|
+
for f in sorted(specs_root.glob("%s-*.spec.md" % clean)):
|
|
721
|
+
info = _inspect(f)
|
|
722
|
+
if info:
|
|
723
|
+
results.append(info)
|
|
724
|
+
for f in sorted(specs_root.glob("*.spec.md")):
|
|
725
|
+
info = _inspect(f)
|
|
726
|
+
if info and info not in results and info.get("source-req") == clean:
|
|
727
|
+
results.append(info)
|
|
728
|
+
|
|
729
|
+
# B. 当前任务目录的 spec.md (无 req_id 时主路径)
|
|
730
|
+
if not results:
|
|
731
|
+
ct = get_current_task()
|
|
732
|
+
if ct:
|
|
733
|
+
# ct 是相对 repo_root 的路径(如 workspace/tasks/xx), 但实际在 .qoder/ 下
|
|
734
|
+
task_spec = QODER_DIR / ct / "spec.md"
|
|
735
|
+
if task_spec.is_file():
|
|
736
|
+
info = _inspect(task_spec)
|
|
737
|
+
if info:
|
|
738
|
+
results.append(info)
|
|
739
|
+
|
|
740
|
+
# 排序: confirmed 优先 (让 wl-code gate 先看到可放行的)
|
|
741
|
+
results.sort(key=lambda x: 0 if x["status"] == "confirmed" else 1)
|
|
742
|
+
return results
|
|
743
|
+
|
|
744
|
+
|
|
643
745
|
def get_task_dir(slug: str) -> Path:
|
|
644
746
|
"""Get task directory by slug."""
|
|
645
747
|
from datetime import datetime
|
|
@@ -691,13 +691,23 @@ class ZentaoMCPServer(BaseMCPServer):
|
|
|
691
691
|
pid = args.get('product_id', 1)
|
|
692
692
|
path = '/products/%s/builds' % pid
|
|
693
693
|
scope = '产品 %s' % pid
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
)
|
|
694
|
+
r = _api('GET', path, None)
|
|
695
|
+
# 禅道部分维度可能不支持 builds 端点(404 not found) → 给明确引导而非裸"查询失败"
|
|
696
|
+
if r.status_code == 404 and not (args.get('project_id') or args.get('execution_id') or args.get('product_id')):
|
|
697
|
+
return ('⚠️ 查版本需要指定维度(禅道无"全量版本"端点)。\n'
|
|
698
|
+
' 常用: list_builds(project_id=N) 看项目级按周版本\n'
|
|
699
|
+
' 或: list_builds(product_id=N) / list_builds(execution_id=N)')
|
|
700
|
+
if r.status_code not in (200, 201):
|
|
701
|
+
return '查询失败: %s (该维度可能无版本)' % r.status_code
|
|
702
|
+
data = r.json()
|
|
703
|
+
items = data.get('builds') if isinstance(data, dict) else None
|
|
704
|
+
if not isinstance(items, list) or not items:
|
|
705
|
+
return '%s 暂无版本(build)。' % scope
|
|
706
|
+
lines = ['%s 的版本 (%d 个):' % (scope, len(items))]
|
|
707
|
+
for b in items:
|
|
708
|
+
lines.append(' [%s] %s (date=%s)' % (
|
|
709
|
+
b.get('id'), str(b.get('name', ''))[:20], b.get('date', '')))
|
|
710
|
+
return '\n'.join(lines)
|
|
701
711
|
|
|
702
712
|
@tool("get_build",
|
|
703
713
|
"查版本/构建详情。例: get_build(build_id=8)",
|
|
@@ -1288,7 +1298,9 @@ class ZentaoMCPServer(BaseMCPServer):
|
|
|
1288
1298
|
'可能权限不足或字段缺失')
|
|
1289
1299
|
|
|
1290
1300
|
@tool("update_task",
|
|
1291
|
-
"修改任务(
|
|
1301
|
+
"修改任务(名称/指派/工时/截止/描述等)。权限不足返回提示不报错。"
|
|
1302
|
+
"★spec 回填: 确认后的 spec 摘要可通过 desc 写入任务描述, 让禅道里能看到开发规格要点。"
|
|
1303
|
+
"例: update_task(task_id=301, name='导出PDF接口(改)', left=3, desc='spec摘要...')",
|
|
1292
1304
|
{"type": "object", "properties": {
|
|
1293
1305
|
"task_id": {"type": "integer", "description": "任务ID"},
|
|
1294
1306
|
"name": {"type": "string", "description": "新名称(可选)"},
|
|
@@ -1296,6 +1308,7 @@ class ZentaoMCPServer(BaseMCPServer):
|
|
|
1296
1308
|
"estimate": {"type": "number", "description": "预计工时(可选)"},
|
|
1297
1309
|
"left": {"type": "number", "description": "剩余工时(可选)"},
|
|
1298
1310
|
"deadline": {"type": "string", "description": "截止日期(可选)"},
|
|
1311
|
+
"desc": {"type": "string", "description": "任务描述(可选)。spec 回填用: 写 spec 摘要(接口清单/关键表/异常/AC映射) + 本地 spec 路径"},
|
|
1299
1312
|
}, "required": ["task_id"]})
|
|
1300
1313
|
def _t_update_task(self, args):
|
|
1301
1314
|
tid = args.get('task_id', 0)
|
|
@@ -1303,11 +1316,11 @@ class ZentaoMCPServer(BaseMCPServer):
|
|
|
1303
1316
|
if guard:
|
|
1304
1317
|
return guard
|
|
1305
1318
|
payload = {}
|
|
1306
|
-
for k in ('name', 'assignedTo', 'estimate', 'left', 'deadline'):
|
|
1319
|
+
for k in ('name', 'assignedTo', 'estimate', 'left', 'deadline', 'desc'):
|
|
1307
1320
|
if args.get(k) is not None:
|
|
1308
1321
|
payload[k] = args[k]
|
|
1309
1322
|
if not payload:
|
|
1310
|
-
return '⚠️ 没有要更新的字段(可选: name/assignedTo/estimate/left/deadline)。'
|
|
1323
|
+
return '⚠️ 没有要更新的字段(可选: name/assignedTo/estimate/left/deadline/desc)。'
|
|
1311
1324
|
return self._safe_write(
|
|
1312
1325
|
'PUT', '/tasks/%s' % tid, payload,
|
|
1313
1326
|
'✅ 任务 #%s 已更新' % tid,
|
|
@@ -15,7 +15,7 @@ qwork_harness.py — QoderWork 命令自动化测试框架 (P2)
|
|
|
15
15
|
python qwork_harness.py "/wl-search 考勤"
|
|
16
16
|
python qwork_harness.py "/wl-search 考勤" --auto-answer=0 # 选第1个
|
|
17
17
|
python qwork_harness.py "/wl-search 考勤" --no-answer # 只跑不等答
|
|
18
|
-
python qwork_harness.py "/wl-prd
|
|
18
|
+
python qwork_harness.py "/wl-prd 用户管理" --auto-answer=1
|
|
19
19
|
|
|
20
20
|
依赖: websocket-client (pip install websocket-client)
|
|
21
21
|
"""
|
|
@@ -143,7 +143,7 @@ AskUserQuestion 在 transcript 里的真实生命周期:
|
|
|
143
143
|
python qwork_harness.py "/wl-search 考勤"
|
|
144
144
|
|
|
145
145
|
# 强制选项序号(0-based)
|
|
146
|
-
python qwork_harness.py "/wl-prd
|
|
146
|
+
python qwork_harness.py "/wl-prd 用户管理" --auto-answer=0
|
|
147
147
|
|
|
148
148
|
# 只跑不代答(遇到提问就停)
|
|
149
149
|
python qwork_harness.py "/wl-search 考勤" --no-answer
|
|
@@ -214,7 +214,7 @@ python qwork_harness.py "/wl-search 考勤" --json
|
|
|
214
214
|
# 或直接 python qwork_harness.py "/wl-search 考勤" (注意 / 在 bash 下的转义)
|
|
215
215
|
|
|
216
216
|
python qwork_harness.py "/wl-search 考勤"
|
|
217
|
-
python qwork_harness.py "/wl-prd
|
|
217
|
+
python qwork_harness.py "/wl-prd 用户管理" --auto-answer=0 # 强制选项序号
|
|
218
218
|
python qwork_harness.py "/wl-search 考勤" --no-answer # 不代答
|
|
219
219
|
python qwork_harness.py "/wl-search 考勤" --json # JSON 输出
|
|
220
220
|
```
|
|
@@ -1,15 +1,33 @@
|
|
|
1
1
|
{
|
|
2
2
|
"permissions": {
|
|
3
|
+
"defaultMode": "acceptEdits",
|
|
3
4
|
"allow": [
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
"
|
|
7
|
-
"
|
|
8
|
-
"
|
|
9
|
-
"
|
|
10
|
-
"
|
|
11
|
-
"
|
|
12
|
-
"
|
|
5
|
+
"Read",
|
|
6
|
+
"Write",
|
|
7
|
+
"Edit",
|
|
8
|
+
"Glob",
|
|
9
|
+
"Grep",
|
|
10
|
+
"WebFetch",
|
|
11
|
+
"WebSearch",
|
|
12
|
+
"mcp__*",
|
|
13
|
+
"Bash(python:*)",
|
|
14
|
+
"Bash(python3:*)",
|
|
15
|
+
"Bash(py:*)",
|
|
16
|
+
"Bash(git:*)",
|
|
17
|
+
"Bash(npm:*)",
|
|
18
|
+
"Bash(npx:*)",
|
|
19
|
+
"Bash(node:*)",
|
|
20
|
+
"Bash(ls:*)",
|
|
21
|
+
"Bash(cat:*)",
|
|
22
|
+
"Bash(echo:*)",
|
|
23
|
+
"Bash(test:*)",
|
|
24
|
+
"Bash(mkdir:*)",
|
|
25
|
+
"Bash(cp:*)",
|
|
26
|
+
"Bash(mv:*)",
|
|
27
|
+
"Bash(find:*)",
|
|
28
|
+
"Bash(grep:*)",
|
|
29
|
+
"Bash(sed:*)",
|
|
30
|
+
"Bash(cd:*)"
|
|
13
31
|
],
|
|
14
32
|
"deny": []
|
|
15
33
|
},
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: design-import
|
|
3
|
-
description: "
|
|
4
|
-
trigger: "
|
|
3
|
+
description: "把设计师的设计稿录入工作流,生成设计规范 spec.json 并存入知识图谱。蓝湖链接直读为主(精确提取颜色/尺寸/字体/切图),无蓝湖可截图口述降级。/wl-design 预览模式走这里。"
|
|
4
|
+
trigger: "被 /wl-design 预览模式内部调用(实现层)"
|
|
5
5
|
---
|
|
6
6
|
|
|
7
7
|
|
|
@@ -48,7 +48,7 @@ ls "$R/data/style/"*-design-spec.json 2>/dev/null # 列所有 spec
|
|
|
48
48
|
|
|
49
49
|
## Step 0: 确认平台
|
|
50
50
|
|
|
51
|
-
跟 /wl-prd
|
|
51
|
+
跟 /wl-prd 一样,先问:Web 管理端 / APP 移动端 / 两端都要?
|
|
52
52
|
**问了就停,等设计师回答。**
|
|
53
53
|
|
|
54
54
|
## Step 1: 收集设计信息
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: prd-generator
|
|
3
|
-
description: "
|
|
4
|
-
trigger: "
|
|
3
|
+
description: "/wl-prd 完整档/快速档的实现层。生成 PRD + 平台匹配的原型,带 EVA 质量门禁。"
|
|
4
|
+
trigger: "被 /wl-prd 完整/快速模式内部调用(实现层,不直接响应自然语言)"
|
|
5
5
|
---
|
|
6
6
|
|
|
7
7
|
|
|
@@ -16,8 +16,8 @@ PY=$(python --version >/dev/null 2>&1 && echo python || echo python3)
|
|
|
16
16
|
|
|
17
17
|
# PRD Generator Skill
|
|
18
18
|
|
|
19
|
-
> 📌 **主入口:`/wl-prd
|
|
20
|
-
>
|
|
19
|
+
> 📌 **主入口:`/wl-prd` 命令(完整档 / 快速档 / 评审 三模式)。** 本 skill 是完整/快速模式的实现层 + Quest/QoderWork 自然语言路由。
|
|
20
|
+
> 命令见 `.qoder/commands/wl-prd.md`(参数提示更全、更稳定)。
|
|
21
21
|
> **模块契约**(输入/输出/校验):`.qoder/contracts/prd.md`
|
|
22
22
|
> **第一性原理:又快又准**(快=并行+砍模式,准=语义归档+质量锁)
|
|
23
23
|
> **定位:专注"已确定需求"的落地。** 探索/调研/规划/对标已移交 `/wl-insight`,
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: prototype-generator
|
|
3
3
|
description: "生成匹配项目 UI 风格的交互式 HTML 原型(Web 端 fywl-ui 用 Vben Admin + Ant Design Vue;APP 端 Carmg-H5 用 Vant 3)。Generate interactive HTML prototypes matching project UI style. "
|
|
4
|
-
trigger: "
|
|
4
|
+
trigger: "被 /wl-prd 或 /wl-design 原型生成内部调用(实现层)"
|
|
5
5
|
---
|
|
6
6
|
|
|
7
7
|
|
|
@@ -37,7 +37,7 @@ Generate production-ready HTML prototypes that match the project's actual UI sty
|
|
|
37
37
|
|
|
38
38
|
本 skill **只在用户明确选择"画原型"后才进入**。
|
|
39
39
|
|
|
40
|
-
- 如果上游 `/wl-prd
|
|
40
|
+
- 如果上游 `/wl-prd` 在「批量确认」环节已决定**不画**(PRD 第 1 行
|
|
41
41
|
`@contract` 头标记 `prototype: none`),**立即退出本 skill**,不生成任何 HTML。
|
|
42
42
|
- 如果上游没有 contract 头(独立调用本 skill),**先问用户**:
|
|
43
43
|
`这个需求要画原型吗?(画 / 不画)`
|
|
@@ -50,7 +50,7 @@ Generate production-ready HTML prototypes that match the project's actual UI sty
|
|
|
50
50
|
---
|
|
51
51
|
## Step 0: Know the Platform
|
|
52
52
|
|
|
53
|
-
The platform MUST already be decided (asked by /wl-prd
|
|
53
|
+
The platform MUST already be decided (asked by /wl-prd Step 0).
|
|
54
54
|
If you don't know it, STOP and ask the user (Web 管理端 / APP 移动端 / 两端都要).
|
|
55
55
|
|
|
56
56
|
| Platform | Project | Template | Design Tokens |
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: spec-generator
|
|
3
3
|
description: "从已确认的 PRD 自动生成开发 Spec 文档(技术规格说明)。Auto-generate development Spec documents from confirmed PRDs. "
|
|
4
|
-
trigger: "
|
|
4
|
+
trigger: "被 /wl-spec 内部调用(实现层)"
|
|
5
5
|
---
|
|
6
6
|
|
|
7
7
|
|
|
@@ -28,21 +28,46 @@ PY=$(python --version >/dev/null 2>&1 && echo python || echo python3)
|
|
|
28
28
|
再 `$PY "$R/.qoder/scripts/foundation/core/paths.py" whoami`(输出开发者名;空=未初始化)。
|
|
29
29
|
⚠️ 勿用裸相对路径 `.qoder/.developer`(QoderWork 桌面端 cwd 不在仓库根,会读不到而误报未初始化)。
|
|
30
30
|
- **活动任务**:当前任务(决定 spec 存到哪个 task 目录)。读法 `$PY "$R/.qoder/scripts/foundation/core/paths.py" current-task`(输出任务路径;空=无活动任务)。
|
|
31
|
-
- 扫描 PRD 来源目录:`data/docs/prd/`(已发布)+
|
|
31
|
+
- 扫描 PRD 来源目录:`data/docs/prd/`(已发布)+ PRD 所在的 REQ-ID 容器
|
|
32
|
+
(`workspace/members/{dev}/{drafts|outputs}/{YYYY}/{MM}/{Ww}/{REQ-ID}/`,用 `find_req_dirs` 跨桶查找)
|
|
32
33
|
找出还没有对应 Spec 的 PRD
|
|
33
34
|
- 风格/字段参考:`$PY "$R/.qoder/scripts/orchestration/wlkj.py" search --field <字段名>` 查现有字段命名约定
|
|
34
35
|
- 团队 Java 约定(MyBatis Plus + RESTful + BigDecimal 金额)写在本 skill 的 Step 3
|
|
35
36
|
|
|
36
|
-
## Step 1: 定位 PRD
|
|
37
|
+
## Step 1: 定位 PRD(优先禅道 task,本地兜底)
|
|
38
|
+
|
|
39
|
+
**核心:1 禅道 task ↔ 1 spec。PRD 真源是 task 关联的 story**(本地找不到别人提的 PRD 时,从禅道 story 读)。
|
|
40
|
+
|
|
41
|
+
### 优先级 1:从禅道 task 读(解决"别人提的 PRD 本地没有")
|
|
37
42
|
|
|
38
43
|
```bash
|
|
39
|
-
#
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
44
|
+
# 当前活动任务(panel / /wl-task 指向的禅道 task)
|
|
45
|
+
TASK=$($PY "$R/.qoder/scripts/foundation/core/paths.py" current-task) # 或直接给 task_id
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
走 MCP 读 task → story:
|
|
49
|
+
```python
|
|
50
|
+
cap.mcp.call("get_task", {"task_id": 1453}) # 拿 task 详情,取 story 字段
|
|
51
|
+
cap.mcp.call("get_story_detail", {"story_id": 761}) # 读 story.spec 字段 = PRD 数据源
|
|
43
52
|
```
|
|
53
|
+
story.spec 即 PRD 正文(验收标准/字段/操作)。**本地无该 PRD 文件也能生成 spec。**
|
|
44
54
|
|
|
45
|
-
|
|
55
|
+
### 优先级 2:本地 PRD 兜底
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
# task 无 story 关联,或禅道不可达 → 扫本地
|
|
59
|
+
ls data/docs/prd/REQ-*.md # 已发布
|
|
60
|
+
$PY "$R/.qoder/scripts/foundation/core/paths.py" whoami # 取开发者
|
|
61
|
+
# 草稿在 REQ-ID 容器: workspace/members/{DEV}/{drafts|outputs}/{桶}/{REQ-ID}/
|
|
62
|
+
```
|
|
63
|
+
跨桶找某 REQ:复用 `paths.find_req_dirs(req_id, DEV)`。
|
|
64
|
+
|
|
65
|
+
### 决定
|
|
66
|
+
|
|
67
|
+
找出"要生成 spec 的目标"后:
|
|
68
|
+
- **禅道 task 来的**:spec 主标识用 `task_id`,契约头写 `zentao: task=X story=Y`,第 0 章"禅道关联"填实。
|
|
69
|
+
- **本地 PRD 来的**:用 REQ-ID,`zentao:` 留空(后续 panel 回填 task 时补)。
|
|
70
|
+
- 多份候选时问用户先做哪个。
|
|
46
71
|
|
|
47
72
|
## Step 2: 读取上下文
|
|
48
73
|
|
|
@@ -57,41 +82,73 @@ cap.mcp.call("context_pack", {"keyword": "..."}) <PRD里的模块关键词> --pl
|
|
|
57
82
|
|
|
58
83
|
### 同时并发补这几项(与 context_pack 无依赖,同回合并发)
|
|
59
84
|
|
|
60
|
-
1. **PRD
|
|
61
|
-
2.
|
|
62
|
-
3.
|
|
85
|
+
1. **PRD 数据源**(必需):禅道 task 来的 = Step 1 读到的 story.spec;本地来的 = PRD 文件
|
|
86
|
+
2. **spec 模板**(必需):`$R/.qoder/templates/spec-template.md` —— **必须读它,照它的 6 章骨架逐节填,不自由发挥结构**
|
|
87
|
+
3. **历史 Spec**(推荐):扫 PRD 同容器或历史 REQ-ID 容器看同类需求的写法,照着写
|
|
88
|
+
4. **真实表结构**(强烈推荐):context_pack 不含 DB schema,写 API 字段前单独查
|
|
63
89
|
- 先 `cap.mcp.call("list_envs", {})` 问环境,再 `query_schema(table='quality_case', db='fywl_ics_quality')`
|
|
64
90
|
- 拿到真实列名/类型/注释(注释里常含枚举定义,如"0=小时,1=工作日")
|
|
65
91
|
|
|
66
92
|
> 数据字典 `data/docs/constitution/` 如存在顺手读,不存在跳过。
|
|
67
|
-
> 以上 PRD
|
|
93
|
+
> 以上 PRD源/模板/历史Spec/query_schema 四项相互独立 → **同一条消息并发发**,不要逐个等。
|
|
94
|
+
|
|
95
|
+
## Step 3: 照模板分析(不自由发挥结构)
|
|
68
96
|
|
|
69
|
-
|
|
97
|
+
**核心:读 `templates/spec-template.md` 的 6 章骨架,逐章节填空。** 不是从零生成结构。
|
|
70
98
|
|
|
71
|
-
从 PRD
|
|
72
|
-
- 每个用户故事 →
|
|
73
|
-
- 验收标准 →
|
|
74
|
-
- 数据需求 →
|
|
75
|
-
-
|
|
99
|
+
从 PRD 提取并填入对应章节:
|
|
100
|
+
- 每个用户故事 → 第 1 章一个或多个 API 接口
|
|
101
|
+
- 验收标准 → 第 1 章入参/出参 + 第 3 章异常场景 + 第 5 章测试用例
|
|
102
|
+
- 数据需求 → 第 2 章表设计(表名 `t_{module}_{entity}`,金额 BigDecimal)
|
|
103
|
+
- **第 3 章异常矩阵 ★强制**:每个操作**系统性枚举所有失败场景**(参数/业务/权限/并发/外部依赖/数据不存在/边界值),这是写代码前必须想清楚的,不可遗漏
|
|
104
|
+
- **第 4 章 AI 边界 ★强制**:明确划分 ✅可自动 / ⚠️必须人工确认 / 🚫禁止AI做
|
|
105
|
+
- **第 5 章测试用例 ★强制**:每条 PRD 验收标准 ≥1 个用例,输入/预期/断言齐全
|
|
106
|
+
- 第 6 章映射表:每条 PRD acceptance → 对应接口/数据/异常/用例,保证可追溯不漏
|
|
107
|
+
|
|
108
|
+
★强制章节不适用时写"无"并附原因(例:纯查询页异常章写"无(仅查询,校验由前端处理)")。
|
|
76
109
|
|
|
77
110
|
## Step 4: 输出
|
|
78
111
|
|
|
79
|
-
|
|
112
|
+
**存储位置(按来源二选一)**:
|
|
113
|
+
|
|
114
|
+
- **禅道 task 来的**(1 task ↔ 1 spec):`workspace/tasks/{task_id}/spec.md`(复用现有 task 目录,task_id 即禅道 task id)
|
|
115
|
+
- **本地 PRD 来的**:跟着 PRD 同容器,`workspace/members/{dev}/{drafts|outputs}/{YYYY}/{MM}/{Ww}/{REQ-ID}/REQ-{ID}-{module}.spec.md`
|
|
116
|
+
(复用 `paths.get_req_dir` / `find_req_dirs`,跨时间桶可查)
|
|
117
|
+
|
|
118
|
+
**契约头**(status 初始为 draft;禅道来的带 zentao 字段):
|
|
119
|
+
```markdown
|
|
120
|
+
<!-- @contract spec v1
|
|
121
|
+
source-req: REQ-2025-0042
|
|
122
|
+
platform: web
|
|
123
|
+
module: policy
|
|
124
|
+
endpoints: [POST /api/policy/amend, GET /api/policy/amend/{id}]
|
|
125
|
+
status: draft
|
|
126
|
+
zentao: task=1453 story=761
|
|
127
|
+
-->
|
|
128
|
+
```
|
|
129
|
+
> `zentao:` 字段:禅道 task 来的必须填(task+story);本地来的留空,待 panel 回填 task 时补。
|
|
130
|
+
|
|
131
|
+
> 兜底:若本地 PRD 不在时间桶容器(老 PRD 在 `data/docs/prd/`),spec 写到 `workspace/specs/REQ-{ID}-{module}.spec.md`(向后兼容)。
|
|
80
132
|
|
|
81
|
-
|
|
82
|
-
- 每条 PRD 验收标准都映射到一个 Spec
|
|
133
|
+
规则(模板已固化,这里只强调重点):
|
|
134
|
+
- 每条 PRD 验收标准都映射到一个 Spec 测试用例(第 6 章)
|
|
83
135
|
- API 路径遵循 RESTful 约定
|
|
84
136
|
- 表名遵循 `t_{module}_{entity}`
|
|
85
137
|
- 金额字段必须用 `BigDecimal`
|
|
86
138
|
- 不写 PRD 未定义的功能
|
|
87
139
|
- Entity → Mapper → Service → Controller 全栈结构
|
|
88
140
|
|
|
89
|
-
## Step 5:
|
|
141
|
+
## Step 5: 通知并提交确认
|
|
90
142
|
|
|
91
|
-
|
|
143
|
+
生成后 `status: draft`。报告给被分配的开发者评审:
|
|
144
|
+
1. 展示 spec 全文 + 列出关键决策点(接口设计 / 表结构 / 异常枚举 / AI 边界)
|
|
145
|
+
2. 提示:可直接修改 spec 文件(路径见输出),改完告知
|
|
146
|
+
3. **⚠️ status 保持 draft,不要自动改 confirmed** —— 用户在 `/wl-code` 的 spec gate 确认后才升级
|
|
147
|
+
4. **不在此回填禅道** —— spec 回填禅道 task 的 `desc` 由 `/wl-code` 在用户确认后做(见 wl-code Step 1),本 skill 只负责生成
|
|
92
148
|
|
|
93
149
|
## 触发条件
|
|
94
150
|
|
|
95
151
|
- 手动:用户说"生成 Spec"、"写技术规格"、"/wl-spec"
|
|
96
152
|
- 任务驱动:PRD 评审通过后,下一步通常就是生成 Spec
|
|
153
|
+
- `/wl-code` gate:无 spec 或 status≠confirmed 时,`/wl-code` 自动调本 skill 生成 draft
|
|
97
154
|
- 子 agent:可被 prd-generator 在 PRD 确认后调度
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: wl-code
|
|
3
3
|
description: "按开发 Spec 严格实现代码(/wl-code 命令入口)。Implement code following a Spec strictly. 需用户确认(DANGEROUS)。"
|
|
4
|
-
trigger: "
|
|
4
|
+
trigger: "用户显式打 /wl-code; 或自然语言"写代码""实现这个功能""按spec开发"。需确认"
|
|
5
5
|
---
|
|
6
6
|
|
|
7
7
|
|
|
@@ -53,6 +53,18 @@ cap.mcp.call("context_pack", {"keyword": "..."}) <模块关键词> --platform <w
|
|
|
53
53
|
- 拿真实列名/类型/注释(含枚举定义),别靠推断。
|
|
54
54
|
- [QAS环境] 表结构与生产一致可放心用。
|
|
55
55
|
|
|
56
|
+
### 排查/修 bug 类先对齐链路(kg 取证,1-2 步定位,禁止空转假设)
|
|
57
|
+
|
|
58
|
+
> 遵守 wl-pipeline 规则 2.6:取证优先用 kg(`cap.mcp.call`),**不许"假设A→假设B→假设C"连环空转**。
|
|
59
|
+
|
|
60
|
+
| 场景 | 对齐链路 | kg 先取证 | 最常见根因 |
|
|
61
|
+
|------|---------|----------|-----------|
|
|
62
|
+
| 后端报错/类型异常 | 前端格式→BO→Entity→DB列 | `query_schema`(DB列类型) + `context_pack`(BO/Entity文件) | String/Date/Long 不一致 |
|
|
63
|
+
| 接口不通/400 | 路由→Controller→Service→Mapper→SQL | `code-api`(路由/参数) + `table_impact`(影响面) | 参数名不匹配/缺@RequestBody |
|
|
64
|
+
| 前端白屏/NaN | 组件→API→响应→字段映射 | `context_pack`(组件/API) + `field_map`(字段映射) | null未处理/字段名拼错 |
|
|
65
|
+
|
|
66
|
+
kg 取证后 Read 命中文件找**不一致点**,定位即动手,**别发散推理**。
|
|
67
|
+
|
|
56
68
|
## 执行
|
|
57
69
|
|
|
58
70
|
1. **加载 Spec**:按 REQ-ID 找 spec;没参数就读 `.qoder/.current-task`
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: wl-design
|
|
3
|
-
description: "
|
|
4
|
-
trigger: "
|
|
3
|
+
description: "设计工序站: 预览(蓝湖稿→HTML预览,默认) / 扫描(看现有系统页面组件风格) / 评审(检查原型是否用系统真源)。"
|
|
4
|
+
trigger: "用户显式打 /wl-design; 或自然语言"出原型""设计稿""预览设计""扫描页面风格""
|
|
5
5
|
---
|
|
6
6
|
|
|
7
7
|
|
|
@@ -28,12 +28,12 @@ PY=$(python --version >/dev/null 2>&1 && echo python || echo python3)
|
|
|
28
28
|
|
|
29
29
|
| 用户说 | 模式 | 走哪个 skill |
|
|
30
30
|
|--------|------|-------------|
|
|
31
|
-
| "
|
|
32
|
-
| "
|
|
33
|
-
| "
|
|
31
|
+
| "预览""出预览""蓝湖链接""看看设计稿" 或 含 lanhuapp.com | **预览**(默认) | design-import |
|
|
32
|
+
| "扫描""看系统""有哪些页面""什么风格" | **扫描** | 看现有系统页面/组件/风格 |
|
|
33
|
+
| "评审""检查""合规""颜色对不对" | **评审** | design-review |
|
|
34
34
|
| 模糊 | 先问意图 | — |
|
|
35
35
|
|
|
36
|
-
默认
|
|
36
|
+
默认 预览(最常见入口)。
|
|
37
37
|
|
|
38
38
|
## ⚙️ 自取上下文(Quest / QoderWork 无 hook 注入,必须自读)
|
|
39
39
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: wl-init
|
|
3
3
|
description: "初始化/切换开发者身份 + 环境体检。Initialize/switch developer identity + environment health check. "
|
|
4
|
-
trigger: "
|
|
4
|
+
trigger: "用户显式打 /wl-init; 身份初始化"
|
|
5
5
|
---
|
|
6
6
|
|
|
7
7
|
|
|
@@ -63,7 +63,7 @@ $PY "$R/.qoder/scripts/orchestration/wlkj.py" doctor --fix
|
|
|
63
63
|
|
|
64
64
|
## 输出规则
|
|
65
65
|
|
|
66
|
-
- 体检完打印一句总结:「环境健康,可以开始工作:/wl-prd
|
|
66
|
+
- 体检完打印一句总结:「环境健康,可以开始工作:/wl-prd <需求>」或「N 个问题待修,加 --fix 自愈」
|
|
67
67
|
- 如果用户没给名字但 `.developer` 不存在:先问"你叫什么名字?角色是 pm/dev/design?"
|
|
68
68
|
- 如果 git user.name 与开发者名不一致:在体检里 `[WARN]` 提示,`--fix` 会自动改
|
|
69
69
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: wl-insight
|
|
3
|
-
description: "
|
|
4
|
-
trigger: "
|
|
3
|
+
description: "需求上游探索站。默认澄清(产品听不懂时对焦,必要时出原型帮理解); discover(发现机会) / research(调研竞品) / plan(规划roadmap)。说'转PRD'落地。"
|
|
4
|
+
trigger: "用户显式打 /wl-insight; 或自然语言需求澄清(产品听不懂时)"
|
|
5
5
|
---
|
|
6
6
|
|
|
7
7
|
|
|
@@ -38,7 +38,7 @@ insight(探索/调研/规划)→ [用户确认转PRD] → prd(落地)→
|
|
|
38
38
|
```
|
|
39
39
|
|
|
40
40
|
**insight 不产 PRD,不画原型,不分配 REQ-ID。** insight 的产出是**洞察报告**(.md)。
|
|
41
|
-
报告写完,提示用户"要把某个机会转成 PRD 吗?" → 是 → 自动进 `/wl-prd
|
|
41
|
+
报告写完,提示用户"要把某个机会转成 PRD 吗?" → 是 → 自动进 `/wl-prd` 参考模式(mode: reference)。
|
|
42
42
|
|
|
43
43
|
---
|
|
44
44
|
|
|
@@ -154,12 +154,12 @@ OST ├─ Opportunity(用户机会/痛点)─ discover ──┤ → i
|
|
|
154
154
|
insight 报告末尾**永远**给一句:
|
|
155
155
|
|
|
156
156
|
```
|
|
157
|
-
要把握住哪个机会?说"转 PRD"我把它作为现状输入,进 /wl-prd
|
|
157
|
+
要把握住哪个机会?说"转 PRD"我把它作为现状输入,进 /wl-prd 参考模式落地成可执行 PRD。
|
|
158
158
|
```
|
|
159
159
|
|
|
160
160
|
用户说"转 PRD"或指定某个机会 → 触发:
|
|
161
161
|
```
|
|
162
|
-
/wl-prd
|
|
162
|
+
/wl-prd 参考:{报告路径}#{机会编号}
|
|
163
163
|
```
|
|
164
164
|
prd 以 reference 模式(mode: reference)承接,读取 insight 报告作为现状背景,走"轻反思+批量确认"流程落地。
|
|
165
165
|
|