@hupan56/wlkj 2.0.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/bin/cli.js +213 -0
- package/package.json +11 -0
- package/templates/cli.js +198 -0
- package/templates/qoder/commands/wl-code.md +43 -0
- package/templates/qoder/commands/wl-commit.md +30 -0
- package/templates/qoder/commands/wl-init.md +80 -0
- package/templates/qoder/commands/wl-insight.md +51 -0
- package/templates/qoder/commands/wl-prd.md +199 -0
- package/templates/qoder/commands/wl-report.md +166 -0
- package/templates/qoder/commands/wl-search.md +52 -0
- package/templates/qoder/commands/wl-spec.md +18 -0
- package/templates/qoder/commands/wl-status.md +51 -0
- package/templates/qoder/commands/wl-task.md +71 -0
- package/templates/qoder/commands/wl-test.md +42 -0
- package/templates/qoder/config.toml +5 -0
- package/templates/qoder/config.yaml +141 -0
- package/templates/qoder/hooks/inject-workflow-state.py +117 -0
- package/templates/qoder/hooks/session-start.py +204 -0
- package/templates/qoder/rules/wl-pipeline.md +105 -0
- package/templates/qoder/scripts/add_session.py +245 -0
- package/templates/qoder/scripts/benchmark.py +209 -0
- package/templates/qoder/scripts/build_style_index.py +268 -0
- package/templates/qoder/scripts/code_index.py +41 -0
- package/templates/qoder/scripts/collect_prds.py +31 -0
- package/templates/qoder/scripts/common/__init__.py +0 -0
- package/templates/qoder/scripts/common/active_task.py +230 -0
- package/templates/qoder/scripts/common/atomicio.py +172 -0
- package/templates/qoder/scripts/common/developer.py +161 -0
- package/templates/qoder/scripts/common/eval_api.py +144 -0
- package/templates/qoder/scripts/common/feishu.py +278 -0
- package/templates/qoder/scripts/common/filelock.py +211 -0
- package/templates/qoder/scripts/common/identity.py +285 -0
- package/templates/qoder/scripts/common/mentions.py +134 -0
- package/templates/qoder/scripts/common/paths.py +311 -0
- package/templates/qoder/scripts/common/reqid.py +218 -0
- package/templates/qoder/scripts/common/search_engine.py +205 -0
- package/templates/qoder/scripts/common/task_utils.py +342 -0
- package/templates/qoder/scripts/common/terms.py +234 -0
- package/templates/qoder/scripts/common/utf8.py +38 -0
- package/templates/qoder/scripts/context_pack.py +196 -0
- package/templates/qoder/scripts/eval_prd.py +225 -0
- package/templates/qoder/scripts/export.py +487 -0
- package/templates/qoder/scripts/git_sync.py +1087 -0
- package/templates/qoder/scripts/handoff.py +22 -0
- package/templates/qoder/scripts/init_developer.py +76 -0
- package/templates/qoder/scripts/init_doctor.py +527 -0
- package/templates/qoder/scripts/install_qoderwork.py +339 -0
- package/templates/qoder/scripts/learn.py +67 -0
- package/templates/qoder/scripts/notify.py +5 -0
- package/templates/qoder/scripts/parse_prds.py +33 -0
- package/templates/qoder/scripts/report.py +281 -0
- package/templates/qoder/scripts/role.py +39 -0
- package/templates/qoder/scripts/run_weekly_update.bat +17 -0
- package/templates/qoder/scripts/run_weekly_update.sh +20 -0
- package/templates/qoder/scripts/search_index.py +352 -0
- package/templates/qoder/scripts/setup.py +453 -0
- package/templates/qoder/scripts/setup_weekly_cron.bat +22 -0
- package/templates/qoder/scripts/setup_weekly_cron.sh +19 -0
- package/templates/qoder/scripts/status.py +389 -0
- package/templates/qoder/scripts/syncgate.py +330 -0
- package/templates/qoder/scripts/task.py +954 -0
- package/templates/qoder/scripts/team.py +29 -0
- package/templates/qoder/scripts/team_sync.py +419 -0
- package/templates/qoder/scripts/workspace_init.py +102 -0
- package/templates/qoder/settings.json +53 -0
- package/templates/qoder/skills/design-review/SKILL.md +25 -0
- package/templates/qoder/skills/prd-generator/SKILL.md +180 -0
- package/templates/qoder/skills/prd-review/SKILL.md +36 -0
- package/templates/qoder/skills/prototype-generator/SKILL.md +141 -0
- package/templates/qoder/skills/spec-coder/SKILL.md +69 -0
- package/templates/qoder/skills/spec-generator/SKILL.md +67 -0
- package/templates/qoder/skills/test-generator/SKILL.md +72 -0
- package/templates/qoder/skills/wl-commit/SKILL.md +76 -0
- package/templates/qoder/skills/wl-init/SKILL.md +67 -0
- package/templates/qoder/skills/wl-insight/SKILL.md +81 -0
- package/templates/qoder/skills/wl-report/SKILL.md +87 -0
- package/templates/qoder/skills/wl-search/SKILL.md +75 -0
- package/templates/qoder/skills/wl-status/SKILL.md +61 -0
- package/templates/qoder/skills/wl-task/SKILL.md +58 -0
- package/templates/qoder/templates/prd-full-template.md +103 -0
- package/templates/qoder/templates/prd-quick-template.md +69 -0
- package/templates/qoder/templates/prototype-app.html +344 -0
- package/templates/qoder/templates/prototype-web.html +310 -0
- package/templates/root/AGENTS.md +182 -0
- package/templates/root/README-pipeline.md +56 -0
- package/templates/root/ROLES.md +85 -0
- package/templates/root//346/226/260/346/211/213/346/214/207/345/215/227.md +186 -0
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
"""
|
|
4
|
+
QODER EVA - PRD/原型 自动评估 (快且准的"准"侧自动指标)
|
|
5
|
+
|
|
6
|
+
Usage:
|
|
7
|
+
python eval_prd.py <prd.md路径> [原型.html路径]
|
|
8
|
+
|
|
9
|
+
自动评分项 (满分 100):
|
|
10
|
+
A1 现实锚定 (40分): PRD 中引用的字段/API 在知识图谱中真实存在的比例
|
|
11
|
+
-> 防止 AI 编造不存在的字段和接口
|
|
12
|
+
A2 风格保真 (30分): 原型中的颜色值来自真源(模板/风格参考)的比例
|
|
13
|
+
-> 防止 AI 发明项目里不存在的颜色
|
|
14
|
+
A3 模板完整 (30分): PRD 包含团队模板必需章节的比例
|
|
15
|
+
-> 保证团队章程的模板结构不丢
|
|
16
|
+
|
|
17
|
+
结果追加到 .qoder/learning/eval-history.jsonl (本机), 供 /wl-status 趋势展示。
|
|
18
|
+
Exit: 0 = 总分>=80, 1 = 不达标 (发布前应修复)
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
import os
|
|
22
|
+
import sys
|
|
23
|
+
import json
|
|
24
|
+
import re
|
|
25
|
+
from datetime import datetime
|
|
26
|
+
|
|
27
|
+
if sys.platform == 'win32':
|
|
28
|
+
try:
|
|
29
|
+
sys.stdout.reconfigure(encoding='utf-8')
|
|
30
|
+
except (AttributeError, IOError):
|
|
31
|
+
pass
|
|
32
|
+
|
|
33
|
+
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
|
34
|
+
|
|
35
|
+
BASE = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
|
36
|
+
INDEX_DIR = os.path.join(BASE, 'data', 'index')
|
|
37
|
+
TPL_DIR = os.path.join(BASE, '.qoder', 'templates')
|
|
38
|
+
HISTORY = os.path.join(BASE, '.qoder', 'learning', 'eval-history.jsonl')
|
|
39
|
+
|
|
40
|
+
# 完整模板必需章节 (prd-full-template.md) / Mini 模板必需章节
|
|
41
|
+
FULL_SECTIONS = ['版本信息', '变更日志', '名词解释', '需求背景', '需求来源', '产品现状',
|
|
42
|
+
'需求范围', '功能清单', '字段规格', '操作与校验', '非功能性需求',
|
|
43
|
+
'埋点', '版本计划']
|
|
44
|
+
QUICK_SECTIONS = ['功能入口', '需求背景', '需求说明', '影响范围', '验收标准', '不在本次范围']
|
|
45
|
+
|
|
46
|
+
# 中性色白名单 (不算"发明颜色")
|
|
47
|
+
NEUTRAL_COLORS = {'#fff', '#ffffff', '#000', '#000000', '#333', '#666', '#999',
|
|
48
|
+
'#1f1f1f', '#f0f0f0', '#fafafa', '#d9d9d9', '#e8e8e8', '#f5f5f5'}
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def load_json(path):
|
|
52
|
+
try:
|
|
53
|
+
with open(path, encoding='utf-8') as f:
|
|
54
|
+
return json.load(f)
|
|
55
|
+
except Exception:
|
|
56
|
+
return {}
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def read_text(path):
|
|
60
|
+
with open(path, encoding='utf-8', errors='ignore') as f:
|
|
61
|
+
return f.read()
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
# ============================================================
|
|
65
|
+
# A1: 现实锚定 - PRD 引用的字段/API 是否真实存在
|
|
66
|
+
# ============================================================
|
|
67
|
+
|
|
68
|
+
def score_reality(prd_text):
|
|
69
|
+
fm = load_json(os.path.join(INDEX_DIR, 'style-index.json')).get('field_map', {})
|
|
70
|
+
apis = load_json(os.path.join(INDEX_DIR, 'api-index.json'))
|
|
71
|
+
fields_lower = {k.lower() for k in fm}
|
|
72
|
+
api_paths = set()
|
|
73
|
+
for ep in apis:
|
|
74
|
+
api_paths.add(ep.lower())
|
|
75
|
+
for seg in ep.lower().split('/'):
|
|
76
|
+
if len(seg) >= 4:
|
|
77
|
+
api_paths.add(seg)
|
|
78
|
+
|
|
79
|
+
# 只检查表格单元格里的 camelCase 标识符 (字段规格表的内容)
|
|
80
|
+
# 跳过"新增/规划"上下文的行 —— 新需求规划的路径本来就不存在
|
|
81
|
+
NEW_MARKS = ('新增', '新建', '规划', '待开发', '本次新增', 'NEW')
|
|
82
|
+
candidates = set()
|
|
83
|
+
api_refs = set()
|
|
84
|
+
for line in prd_text.splitlines():
|
|
85
|
+
is_new = any(m in line for m in NEW_MARKS)
|
|
86
|
+
if '|' in line and not is_new:
|
|
87
|
+
for cell in line.split('|'):
|
|
88
|
+
for tok in re.findall(r'\b[a-z][a-zA-Z0-9]{3,30}\b', cell.strip()):
|
|
89
|
+
if any(c.isupper() for c in tok): # camelCase 才像字段名
|
|
90
|
+
candidates.add(tok)
|
|
91
|
+
if not is_new:
|
|
92
|
+
for ref in re.findall(r'/[a-zA-Z][a-zA-Z0-9/]{3,60}', line):
|
|
93
|
+
# 前端组件新路径以 /apps|/views|/components 开头的不检查
|
|
94
|
+
if not re.search(r'(apps|views|components|modules)/', ref):
|
|
95
|
+
api_refs.add(ref)
|
|
96
|
+
|
|
97
|
+
checked, hits, misses = 0, 0, []
|
|
98
|
+
for tok in sorted(candidates):
|
|
99
|
+
checked += 1
|
|
100
|
+
tl = tok.lower()
|
|
101
|
+
if tl in fields_lower or any(tl in f or f in tl for f in fields_lower):
|
|
102
|
+
hits += 1
|
|
103
|
+
else:
|
|
104
|
+
misses.append(tok)
|
|
105
|
+
for ref in sorted(api_refs):
|
|
106
|
+
checked += 1
|
|
107
|
+
rl = ref.lower()
|
|
108
|
+
if rl in api_paths or any(seg in api_paths for seg in rl.split('/') if len(seg) >= 4):
|
|
109
|
+
hits += 1
|
|
110
|
+
else:
|
|
111
|
+
misses.append(ref)
|
|
112
|
+
|
|
113
|
+
if checked == 0:
|
|
114
|
+
return 1.0, '未引用具体字段/API (零星文案类需求正常)', []
|
|
115
|
+
ratio = hits / checked
|
|
116
|
+
return ratio, '{}/{} 个字段/API 引用可在图谱中找到'.format(hits, checked), misses[:8]
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
# ============================================================
|
|
120
|
+
# A2: 风格保真 - 原型颜色是否来自真源
|
|
121
|
+
# ============================================================
|
|
122
|
+
|
|
123
|
+
def allowed_colors():
|
|
124
|
+
"""真源颜色集合 = 平台模板 + 风格参考 JSON 中出现过的所有色值"""
|
|
125
|
+
allowed = set(NEUTRAL_COLORS)
|
|
126
|
+
sources = [
|
|
127
|
+
os.path.join(TPL_DIR, 'prototype-web.html'),
|
|
128
|
+
os.path.join(TPL_DIR, 'prototype-app.html'),
|
|
129
|
+
os.path.join(INDEX_DIR, 'chart-style-reference.json'),
|
|
130
|
+
os.path.join(INDEX_DIR, 'vben-style-reference.json'),
|
|
131
|
+
]
|
|
132
|
+
for p in sources:
|
|
133
|
+
if os.path.isfile(p):
|
|
134
|
+
text = read_text(p)
|
|
135
|
+
for c in re.findall(r'#[0-9a-fA-F]{3,8}\b', text):
|
|
136
|
+
allowed.add(c.lower())
|
|
137
|
+
return allowed
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
def score_style(html_text):
|
|
141
|
+
# emoji = 没有使用项目真实图标体系, 直接重罚
|
|
142
|
+
emojis = re.findall(r'[\U0001F300-\U0001FAFF☀-➿⬀-⯿]', html_text)
|
|
143
|
+
allowed = allowed_colors()
|
|
144
|
+
used = [c.lower() for c in re.findall(r'#[0-9a-fA-F]{3,8}\b', html_text)]
|
|
145
|
+
if not used and not emojis:
|
|
146
|
+
return 1.0, '原型未使用硬编码颜色 (纯 CSS 变量, 最佳)', []
|
|
147
|
+
uniq = sorted(set(used))
|
|
148
|
+
unknown = [c for c in uniq if c not in allowed]
|
|
149
|
+
ratio = 1 - (len(unknown) / len(uniq) if uniq else 0)
|
|
150
|
+
notes = []
|
|
151
|
+
if emojis:
|
|
152
|
+
ratio = min(ratio, 0.3)
|
|
153
|
+
notes.append('发现 {} 个 emoji (必须用 icon-reference.json 的真实图标)'.format(len(set(emojis))))
|
|
154
|
+
msg = '{}/{} 种颜色来自真源'.format(len(uniq) - len(unknown), len(uniq))
|
|
155
|
+
if notes:
|
|
156
|
+
msg += '; ' + '; '.join(notes)
|
|
157
|
+
return ratio, msg, unknown[:10]
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
# ============================================================
|
|
161
|
+
# A3: 模板完整 - 团队模板章节是否齐全
|
|
162
|
+
# ============================================================
|
|
163
|
+
|
|
164
|
+
def score_template(prd_text):
|
|
165
|
+
is_quick = ('Mini-PRD' in prd_text or '零星需求' in prd_text
|
|
166
|
+
or len(prd_text) < 3000)
|
|
167
|
+
sections = QUICK_SECTIONS if is_quick else FULL_SECTIONS
|
|
168
|
+
kind = '零星模板' if is_quick else '完整模板'
|
|
169
|
+
missing = [s for s in sections if s not in prd_text]
|
|
170
|
+
ratio = 1 - len(missing) / len(sections)
|
|
171
|
+
return ratio, '{} {}/{} 章节齐全'.format(kind, len(sections) - len(missing), len(sections)), missing
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
# ============================================================
|
|
175
|
+
# Main
|
|
176
|
+
# ============================================================
|
|
177
|
+
|
|
178
|
+
def main():
|
|
179
|
+
if len(sys.argv) < 2:
|
|
180
|
+
print(__doc__)
|
|
181
|
+
return 1
|
|
182
|
+
prd_path = sys.argv[1]
|
|
183
|
+
html_path = sys.argv[2] if len(sys.argv) > 2 else None
|
|
184
|
+
|
|
185
|
+
if not os.path.isfile(prd_path):
|
|
186
|
+
print('PRD 不存在: ' + prd_path)
|
|
187
|
+
return 1
|
|
188
|
+
|
|
189
|
+
# 用程序化 API 评估 (重构后 main 只是 CLI 包装)
|
|
190
|
+
from common.eval_api import evaluate, format_report
|
|
191
|
+
try:
|
|
192
|
+
result = evaluate(prd_path, html_path)
|
|
193
|
+
except FileNotFoundError as e:
|
|
194
|
+
print(str(e))
|
|
195
|
+
return 1
|
|
196
|
+
|
|
197
|
+
# 打印人类可读报告
|
|
198
|
+
print(format_report(result))
|
|
199
|
+
|
|
200
|
+
# 详细 misses (补充 format_report 之外的细节)
|
|
201
|
+
if result.get('msg2') and '非真源颜色' in str(result.get('msg2', '')):
|
|
202
|
+
# msg2 已含信息, 此处可补 unknown 列表 —— 但 evaluate 已合并进 misses
|
|
203
|
+
pass
|
|
204
|
+
|
|
205
|
+
# 记录历史 (原子追加, 防并发损坏)
|
|
206
|
+
try:
|
|
207
|
+
from common.atomicio import atomic_append_jsonl
|
|
208
|
+
os.makedirs(os.path.dirname(HISTORY), exist_ok=True)
|
|
209
|
+
atomic_append_jsonl(HISTORY, {
|
|
210
|
+
'time': datetime.now().isoformat(),
|
|
211
|
+
'prd': result['prd'],
|
|
212
|
+
'a1_reality': result['r1'],
|
|
213
|
+
'a2_style': result['r2'],
|
|
214
|
+
'a3_template': result['r3'],
|
|
215
|
+
'score_pct': result['pct'],
|
|
216
|
+
'passed': result['passed'],
|
|
217
|
+
}, timeout=10)
|
|
218
|
+
except Exception:
|
|
219
|
+
pass # 历史记录失败不影响评估结果
|
|
220
|
+
|
|
221
|
+
return 0 if result['passed'] else 1
|
|
222
|
+
|
|
223
|
+
|
|
224
|
+
if __name__ == '__main__':
|
|
225
|
+
sys.exit(main())
|