@hupan56/wlkj 3.3.13 → 3.3.15

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 (54) hide show
  1. package/bin/cli.js +0 -7
  2. package/package.json +29 -29
  3. package/templates/qoder/commands/optional/wl-spec.md +0 -8
  4. package/templates/qoder/commands/optional/wl-status.md +0 -8
  5. package/templates/qoder/commands/wl-code.md +0 -8
  6. package/templates/qoder/commands/wl-commit.md +0 -8
  7. package/templates/qoder/commands/wl-init.md +129 -129
  8. package/templates/qoder/commands/wl-search.md +0 -8
  9. package/templates/qoder/commands/wl-task.md +0 -8
  10. package/templates/qoder/commands/wl-test.md +0 -8
  11. package/templates/qoder/config.yaml +2 -3
  12. package/templates/qoder/contracts/insight.md +55 -55
  13. package/templates/qoder/hooks/pre-tool-use-commit.py +124 -124
  14. package/templates/qoder/hooks/session-start.py +384 -384
  15. package/templates/qoder/hooks/stop-eval.py +383 -383
  16. package/templates/qoder/scripts/capability/adapters/mcp.py +322 -322
  17. package/templates/qoder/scripts/capability/registry.py +245 -245
  18. package/templates/qoder/scripts/deployment/setup/install_qoderwork.py +14 -0
  19. package/templates/qoder/scripts/deployment/setup/setup.py +0 -11
  20. package/templates/qoder/scripts/domain/task/wlkj_panel.py +1503 -1503
  21. package/templates/qoder/scripts/foundation/bootstrap.py +145 -156
  22. package/templates/qoder/scripts/foundation/io/context_cache.py +94 -94
  23. package/templates/qoder/scripts/orchestration/wlkj.py +295 -306
  24. package/templates/qoder/scripts/tool_guide.md +70 -70
  25. package/templates/qoder/scripts/validation/eval/alignment_matrix.py +176 -176
  26. package/templates/qoder/scripts/validation/eval/bf2_content_fidelity.py +110 -110
  27. package/templates/qoder/scripts/validation/eval/bf2_llmjudge.py +104 -104
  28. package/templates/qoder/scripts/validation/eval/bf_score.py +218 -218
  29. package/templates/qoder/scripts/validation/eval/code_flywheel.py +150 -150
  30. package/templates/qoder/scripts/validation/eval/dispatcher_ab.py +156 -156
  31. package/templates/qoder/scripts/validation/eval/dispatcher_ab_2026-07-21.json +23 -23
  32. package/templates/qoder/scripts/validation/eval/feature_fidelity_flywheel.py +143 -143
  33. package/templates/qoder/scripts/validation/eval/gradient_matrix.py +261 -261
  34. package/templates/qoder/scripts/validation/eval/gradient_matrix_baseline_2026-07-21.json +33 -33
  35. package/templates/qoder/scripts/validation/eval/metrics_dashboard.py +105 -105
  36. package/templates/qoder/scripts/validation/eval/multi_turn_flywheel.py +118 -118
  37. package/templates/qoder/scripts/validation/eval/prd_fidelity_flywheel.py +128 -128
  38. package/templates/qoder/scripts/validation/eval/prd_flywheel.py +148 -148
  39. package/templates/qoder/scripts/validation/eval/prototype_fidelity_flywheel.py +166 -166
  40. package/templates/qoder/scripts/validation/eval/recall_flywheel.py +148 -148
  41. package/templates/qoder/scripts/validation/eval/robustness_flywheel.py +139 -139
  42. package/templates/qoder/scripts/validation/eval/speed_accuracy_flywheel.py +188 -188
  43. package/templates/qoder/scripts/validation/eval/task_flywheel.py +124 -124
  44. package/templates/qoder/scripts/validation/eval/token_flywheel.py +88 -88
  45. package/templates/qoder/scripts/validation/metrics/eval_code_ac.py +177 -177
  46. package/templates/qoder/scripts/validation/metrics/lint_cases.py +170 -170
  47. package/templates/qoder/scripts/validation/test/test_context_cache.py +78 -78
  48. package/templates/qoder/scripts/validation/test/test_lint_cases.py +60 -60
  49. package/templates/qoder/scripts/validation/test/test_pre_tool_use_commit.py +70 -70
  50. package/templates/qoder/commands/wl-data.md +0 -46
  51. package/templates/qoder/commands/wl-fix.md +0 -47
  52. package/templates/qoder/commands/wl-knowledge.md +0 -41
  53. package/templates/qoder/commands/wl-review.md +0 -49
  54. package/templates/workspace/specs/REQ-TEST-CTX/.context-cache.json +0 -1
@@ -1,306 +1,295 @@
1
- #!/usr/bin/env python3
2
- # -*- coding: utf-8 -*-
3
- # Windows 控制台 UTF-8 修复(ctypes 直调 Windows API,非子shell chcp)
4
- import sys as _s
5
- if _s.platform == 'win32':
6
- import ctypes as _ct, io as _io
7
- try:
8
- _ct.windll.kernel32.SetConsoleOutputCP(65001)
9
- _ct.windll.kernel32.SetConsoleCP(65001)
10
- _s.stdout = _io.TextIOWrapper(_s.stdout.buffer, encoding='utf-8', errors='replace')
11
- _s.stderr = _io.TextIOWrapper(_s.stderr.buffer, encoding='utf-8', errors='replace')
12
- except Exception:
13
- pass
14
- """
15
- wlkj.py - 工作流顶层调度器 (Facade 统一入口)
16
-
17
- 角色: 所有 Qoder 载体 (commands/skills/rules 里写的脚本调用) 统一走本入口,
18
- 由 DISPATCH 表路由到 scripts/ 下分层子目录的真实脚本。这样:
19
- - 载体文件只认一个路径 (orchestration/wlkj.py); scripts/ 重组目录时
20
- 只需改 DISPATCH 一处, 载体文件不受影响 (根治"目录一重组就断链")
21
- - 脚本内部互调 (Python 进程间) 仍直接用分层路径, 不绕本入口 (避免 subprocess 嵌套)。
22
-
23
- 用法:
24
- python .qoder/scripts/orchestration/wlkj.py kg search 保险 --platform web
25
- python .qoder/scripts/orchestration/wlkj.py task list
26
- python .qoder/scripts/orchestration/wlkj.py doctor --fix
27
- python .qoder/scripts/orchestration/wlkj.py search 考勤
28
- python .qoder/scripts/orchestration/wlkj.py status
29
-
30
- 分组 (逻辑导航, 非物理目录):
31
- kg/* 知识图谱 (kg.py 的子命令直接透传)
32
- task 任务系统
33
- search 代码搜索
34
- doctor 环境体检
35
- status 项目状态
36
- sync 团队同步
37
- ...
38
-
39
- 设计:
40
- - 透明转发: 不改变任何子脚本的行为, 只是把 argv 转过去
41
- - 单一真源: 脚本物理路径只在 DISPATCH 出现一次, 载体层不写死路径
42
- - 零维护: 加新命令只需在 DISPATCH 表加一行
43
- """
44
-
45
- import os
46
- import subprocess
47
- import sys
48
-
49
- # 命令 → 脚本 映射 (加新命令只需在此加一行)
50
- # v3.0: 脚本已分子目录, 路径更新为 子包/脚本
51
- DISPATCH = {
52
- # ── 知识图谱 ──
53
- 'kg': 'domain/kg/kg.py',
54
- 'search': 'domain/kg/search/search_index.py',
55
- 'context': 'domain/kg/search/context_pack.py',
56
- 'prefetch': 'domain/kg/search/prefetch.py',
57
- 'kg-build': 'domain/kg/build/kg_build.py',
58
- 'kg-incremental': 'domain/kg/build/kg_incremental.py',
59
- 'kg-semantic': 'domain/kg/graph/kg_semantic.py',
60
- 'semantic': 'domain/kg/graph/kg_semantic.py',
61
- # ★ MCP 名别名(AI 常把 MCP 工具名当 wlkj.py 命令调 → 未知命令。归一化后两边名都认)
62
- 'search_code': 'domain/kg/search/search_index.py',
63
- 'search_api': 'domain/kg/search/search_index.py',
64
- 'search_prds': 'domain/kg/search/search_index.py',
65
- 'search_field': 'domain/kg/search/search_index.py',
66
- 'search_style': 'domain/kg/search/search_index.py',
67
- 'rag_search': 'domain/kg/graph/kg_semantic.py',
68
- 'ask_corpus': 'domain/kg/graph/kg_semantic.py',
69
- 'context_pack': 'domain/kg/search/context_pack.py',
70
- 'context_360': 'domain/kg/search/context_pack.py',
71
- 'get_impact': 'domain/kg/search/search_index.py',
72
- 'style': 'domain/kg/build/build_style_index.py',
73
- # ── 任务 / 同步 ──
74
- 'task': 'domain/task/task.py',
75
- 'sync': 'domain/task/team_sync.py',
76
- 'team-sync': 'domain/task/team_sync.py',
77
- 'git-sync': 'domain/task/git_sync.py',
78
- 'zentao-sync': 'domain/task/zentao_sync.py',
79
- # ── 需求 / PRD ──
80
- 'req': 'domain/requirement/req.py',
81
- 'archive-prd': 'domain/requirement/archive_prd.py',
82
- # ── 回流平台 (引擎产出 → 平台 AI回流Tab) ──
83
- # 用法: wlkj.py return prd <路径> <标题> | return prototype <路径> <功能名> [--platform web]
84
- 'return': 'domain/integration/return_to_platform.py',
85
- 'spec-upload': 'domain/integration/spec_upload.py',
86
- # ── 设计 ──
87
- 'fill-prototype': 'domain/design/fill_prototype.py',
88
- 'design-doc': 'domain/design/gen_design_doc.py',
89
- # ── 评估 / 学习 (飞轮) ──
90
- 'eval': 'validation/metrics/eval_prd.py',
91
- 'eva': 'validation/metrics/eval_prd.py',
92
- 'usability': 'validation/metrics/usability_score.py',
93
- 'learn': 'domain/learning/learn.py',
94
- 'learn-aggregate': 'domain/kg/build/learn_aggregate.py',
95
- # ── 报告 / 状态 ──
96
- 'status': 'domain/report/status.py',
97
- 'status-snapshot': 'domain/report/status_snapshot.py',
98
- 'report': 'domain/report/report.py',
99
- 'report-snapshot': 'domain/report/report_snapshot.py',
100
- 'session': 'domain/report/add_session.py',
101
- 'add-session': 'domain/report/add_session.py',
102
- 'role': 'domain/report/role.py',
103
- 'export': 'domain/report/export.py',
104
- # ── 测试 ──
105
- 'test': 'validation/test/autotest.py',
106
- 'autotest': 'validation/test/autotest.py',
107
- 'autotest-batch': 'validation/test/autotest_batch.py',
108
- 'benchmark': 'validation/test/benchmark.py',
109
- # ── 环境 / 初始化 ──
110
- 'switch-project': 'domain/kg/switch_project.py',
111
- 'doctor': 'deployment/setup/init_doctor.py',
112
- 'init': 'deployment/setup/setup.py',
113
- 'install-qw': 'deployment/setup/install_qoderwork.py',
114
- 'setup-lanhu': 'deployment/setup/setup_lanhu.py',
115
- }
116
-
117
-
118
-
119
-
120
- def _find_script_by_name(scripts_root, name):
121
- """未知命令时, 当作脚本名在 scripts/ 下递归查找。
122
- 支撑 'wlkj.py <脚本名>' 万能跑 (cli.js 的 run 命令底层依赖)。
123
- 返回绝对路径或 None。"""
124
- import glob
125
- if not name.endswith('.py'):
126
- name = name + '.py'
127
- # 递归找 scripts/**/name
128
- pattern = os.path.join(scripts_root, '**', name)
129
- hits = glob.glob(pattern, recursive=True)
130
- return hits[0] if hits else None
131
-
132
- def _usage():
133
- lines = [
134
- 'wlkj.py — 工作流顶层调度器',
135
- '',
136
- '用法: python .qoder/scripts/orchestration/wlkj.py <命令> [参数...]',
137
- '',
138
- '命令分组 (转发到对应脚本):',
139
- ' 知识图谱: kg <子命令> (kg.py, 13+ 能力)',
140
- ' search <词> (search_index.py)',
141
- ' context <词> (context_pack.py)',
142
- ' kg-build (全量构建图谱)',
143
- ' kg-incremental (增量构建, 定时任务用)',
144
- ' 任务: task <子命令> (task.py, create/list/start/finish)',
145
- ' 同步: sync pull|push (team_sync.py)',
146
- ' 需求: req <子命令> (req.py, 需求生命周期)',
147
- ' archive-prd (PRD 归档)',
148
- ' 回流平台: return prd|prototype <文件> <标题> [--platform web] (PRD/原型回流到平台 AI回流Tab)',
149
- ' 设计: fill-prototype <tpl> (真实数据预填原型)',
150
- ' design-doc (生成 DESIGN.md)',
151
- ' 评估/学习: eval <prd.md> (eval_prd.py, EVA 评分)',
152
- ' usability [--days N] (usability_score.py, 工作流可用性: 采纳+流程)',
153
- ' learn record/status (个人埋点)',
154
- ' learn-aggregate (团队聚合 kg.duckdb)',
155
- ' 状态/报告: status (status.py, 健康度)',
156
- ' report daily|weekly (report.py)',
157
- ' export <格式> (export.py)',
158
- ' 测试: test <子命令> (autotest.py)',
159
- ' autotest-batch <f> (批量串测)',
160
- ' benchmark (性能基线)',
161
- ' 体检: doctor [--fix] (init_doctor.py)',
162
- '',
163
- '示例:',
164
- ' wlkj.py kg search 保险 --platform web',
165
- ' wlkj.py task create "登录优化"',
166
- ' wlkj.py doctor --fix',
167
- ' wlkj.py learn record prd_accepted \'{"req_id":"REQ-001","score":87}\'',
168
- '',
169
- '注: 所有 Qoder 载体 (命令/skills/rules) 统一走本入口; 脚本物理位置见 DISPATCH 表。',
170
- ]
171
- return '\n'.join(lines)
172
-
173
-
174
- def _ctx_cache_cmd(rest, scripts_root):
175
- """wlkj.py ctx-cache <REQ-ID> <query> [--platform web|app] — 按 REQ 缓存 context_pack 结果(T1.3)。
176
- HIT 回放缓存(省一次 KG/MCP 往返);MISS 跑 context_pack 捕获 stdout 写缓存再回放。
177
- wlkj.py ctx-cache <REQ-ID> --clear 清该 REQ 缓存(PRD/spec 改动后失效)。
178
- 任何异常 fail-open 降级直拉(缓存坏了不影响主流程)。
179
- """
180
- do_clear = "--clear" in rest
181
- rest = [a for a in rest if a != "--clear"]
182
-
183
- req_id = None
184
- query_parts, passthrough = [], []
185
- i = 0
186
- while i < len(rest):
187
- a = rest[i]
188
- if a == "--platform" and i + 1 < len(rest):
189
- passthrough.extend([a, rest[i + 1]]); i += 2; continue
190
- if a.startswith("-"):
191
- passthrough.append(a); i += 1; continue
192
- if req_id is None:
193
- req_id = a; i += 1; continue
194
- query_parts.append(a); i += 1
195
- query = " ".join(query_parts).strip()
196
- if not req_id or (not query and not do_clear):
197
- sys.stderr.write("用法: wlkj.py ctx-cache <REQ-ID> <query> [--platform web|app] | ctx-cache <REQ-ID> --clear\n")
198
- return 1
199
-
200
- cc = None
201
- try:
202
- if scripts_root not in sys.path:
203
- sys.path.insert(0, scripts_root)
204
- from foundation.io import context_cache as _cc # noqa
205
- cc = _cc
206
- except Exception as e:
207
- sys.stderr.write("[cache] 模块导入失败, 降级直拉: %s\n" % str(e)[:80])
208
-
209
- # --clear: 清该 REQ 缓存
210
- if do_clear:
211
- if cc is None:
212
- sys.stderr.write("[cache] 模块不可用, 无法 clear\n"); return 1
213
- cc.invalidate(req_id)
214
- sys.stderr.write("[cache] CLEARED req=%s\n" % req_id)
215
- return 0
216
-
217
- if cc is not None:
218
- try:
219
- cached = cc.get(req_id, query)
220
- if cached is not None:
221
- sys.stdout.write(cached if cached.endswith("\n") else cached + "\n")
222
- sys.stderr.write("[cache] HIT req=%s q=%s (省一次 context_pack 往返)\n" % (req_id, query[:30]))
223
- return 0
224
- except Exception as e:
225
- sys.stderr.write("[cache] get 失败, 降级直拉: %s\n" % str(e)[:80])
226
-
227
- # MISS / 降级:跑 context_pack,捕获 stdout,写缓存,回放
228
- script_path = os.path.join(scripts_root, DISPATCH["context"])
229
- r = subprocess.run([sys.executable, script_path, query] + passthrough, capture_output=True)
230
- sys.stderr.write(r.stderr.decode(errors="replace"))
231
- out = r.stdout.decode(errors="replace")
232
- sys.stdout.write(out)
233
- if out.strip() and cc is not None:
234
- try:
235
- cc.put(req_id, query, out)
236
- sys.stderr.write("[cache] MISS+PUT req=%s q=%s\n" % (req_id, query[:30]))
237
- except Exception as e:
238
- sys.stderr.write("[cache] put 失败(不影响本次输出): %s\n" % str(e)[:80])
239
- return r.returncode
240
-
241
-
242
- def _bf_score_cmd(rest, scripts_root):
243
- """wlkj.py bf-score <prd|proto|code|search> <产物文件> [--feature 主题] — 业务保真打分(T1/FW12)。
244
- 转发 bf_score.py 对真命令产出文件打 BF 分(PRD→BF1/2, 原型→BF3/4, code/search→BF5/6)。
245
- bf_score.py 跑在云容器(接真 db_tables/design_system/真PRD);本地无业务数据时降级提示。"""
246
- script = os.path.join(scripts_root, "validation", "eval", "bf_score.py")
247
- if not os.path.isfile(script):
248
- sys.stderr.write("[bf-score] bf_score.py 不在,跳过\n")
249
- return 1
250
- r = subprocess.run([sys.executable, script] + rest)
251
- return r.returncode
252
-
253
-
254
- # custom 逻辑的命令(在 DISPATCH 透明转发前拦截)
255
- SPECIAL = {
256
- 'ctx-cache': _ctx_cache_cmd,
257
- 'context-cache': _ctx_cache_cmd,
258
- 'bf-score': _bf_score_cmd,
259
- 'bfscore': _bf_score_cmd,
260
- }
261
-
262
-
263
- def main():
264
- argv = sys.argv[1:]
265
- if not argv or argv[0] in ('-h', '--help', 'help'):
266
- print(_usage())
267
- return 0
268
-
269
- cmd = argv[0]
270
- rest = argv[1:]
271
-
272
- # scripts/ 根 = __file__ 的上两级 (orchestration/ -> scripts/)
273
- scripts_root = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
274
-
275
- # T1.3 custom 逻辑的命令(如 ctx-cache 缓存)在透明转发前拦截
276
- if cmd in SPECIAL:
277
- return SPECIAL[cmd](rest, scripts_root)
278
-
279
- script = DISPATCH.get(cmd)
280
- if not script:
281
- # Fallback: 当作脚本名在 scripts/ 下递归找 (支撑 'wlkj.py run <脚本>')
282
- # 这样 cli.js 'run' 和直接 'wlkj.py xxx.py' 都能走单一入口, 不需另维护路径表
283
- hit = _find_script_by_name(scripts_root, cmd)
284
- if hit:
285
- script_path = hit
286
- r = subprocess.run([sys.executable, script_path] + rest)
287
- return r.returncode
288
- sys.stderr.write('未知命令: %s\n' % cmd)
289
- sys.stderr.write('可用命令: %s\n' % ', '.join(sorted(DISPATCH.keys())))
290
- sys.stderr.write('提示: 也可用 wlkj.py <脚本名.py> 递归查找脚本\n')
291
- return 1
292
-
293
- # scripts/ 根 = __file__ 的上两级 (orchestration/ → scripts/)
294
- scripts_root = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
295
- script_path = os.path.join(scripts_root, script)
296
- if not os.path.isfile(script_path):
297
- sys.stderr.write('脚本不存在: %s\n' % script_path)
298
- return 1
299
-
300
- # 透明转发: 用当前 python 跑目标脚本, 透传所有参数和 exit code
301
- r = subprocess.run([sys.executable, script_path] + rest)
302
- return r.returncode
303
-
304
-
305
- if __name__ == '__main__':
306
- sys.exit(main())
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+ """
4
+ wlkj.py - 工作流顶层调度器 (Facade 统一入口)
5
+
6
+ 角色: 所有 Qoder 载体 (commands/skills/rules 里写的脚本调用) 统一走本入口,
7
+ 由 DISPATCH 表路由到 scripts/ 下分层子目录的真实脚本。这样:
8
+ - 载体文件只认一个路径 (orchestration/wlkj.py); scripts/ 重组目录时
9
+ 只需改 DISPATCH 一处, 载体文件不受影响 (根治"目录一重组就断链")
10
+ - 脚本内部互调 (Python 进程间) 仍直接用分层路径, 不绕本入口 (避免 subprocess 嵌套)
11
+
12
+ 用法:
13
+ python .qoder/scripts/orchestration/wlkj.py kg search 保险 --platform web
14
+ python .qoder/scripts/orchestration/wlkj.py task list
15
+ python .qoder/scripts/orchestration/wlkj.py doctor --fix
16
+ python .qoder/scripts/orchestration/wlkj.py search 考勤
17
+ python .qoder/scripts/orchestration/wlkj.py status
18
+
19
+ 分组 (逻辑导航, 非物理目录):
20
+ kg/* 知识图谱 (kg.py 的子命令直接透传)
21
+ task 任务系统
22
+ search 代码搜索
23
+ doctor 环境体检
24
+ status 项目状态
25
+ sync 团队同步
26
+ ...
27
+
28
+ 设计:
29
+ - 透明转发: 不改变任何子脚本的行为, 只是把 argv 转过去
30
+ - 单一真源: 脚本物理路径只在 DISPATCH 出现一次, 载体层不写死路径
31
+ - 零维护: 加新命令只需在 DISPATCH 表加一行
32
+ """
33
+
34
+ import os
35
+ import subprocess
36
+ import sys
37
+
38
+ # 命令 → 脚本 映射 (加新命令只需在此加一行)
39
+ # v3.0: 脚本已分子目录, 路径更新为 子包/脚本
40
+ DISPATCH = {
41
+ # ── 知识图谱 ──
42
+ 'kg': 'domain/kg/kg.py',
43
+ 'search': 'domain/kg/search/search_index.py',
44
+ 'context': 'domain/kg/search/context_pack.py',
45
+ 'prefetch': 'domain/kg/search/prefetch.py',
46
+ 'kg-build': 'domain/kg/build/kg_build.py',
47
+ 'kg-incremental': 'domain/kg/build/kg_incremental.py',
48
+ 'kg-semantic': 'domain/kg/graph/kg_semantic.py',
49
+ 'semantic': 'domain/kg/graph/kg_semantic.py',
50
+ # ★ MCP 名别名(AI 常把 MCP 工具名当 wlkj.py 命令调 未知命令。归一化后两边名都认)
51
+ 'search_code': 'domain/kg/search/search_index.py',
52
+ 'search_api': 'domain/kg/search/search_index.py',
53
+ 'search_prds': 'domain/kg/search/search_index.py',
54
+ 'search_field': 'domain/kg/search/search_index.py',
55
+ 'search_style': 'domain/kg/search/search_index.py',
56
+ 'rag_search': 'domain/kg/graph/kg_semantic.py',
57
+ 'ask_corpus': 'domain/kg/graph/kg_semantic.py',
58
+ 'context_pack': 'domain/kg/search/context_pack.py',
59
+ 'context_360': 'domain/kg/search/context_pack.py',
60
+ 'get_impact': 'domain/kg/search/search_index.py',
61
+ 'style': 'domain/kg/build/build_style_index.py',
62
+ # ── 任务 / 同步 ──
63
+ 'task': 'domain/task/task.py',
64
+ 'sync': 'domain/task/team_sync.py',
65
+ 'team-sync': 'domain/task/team_sync.py',
66
+ 'git-sync': 'domain/task/git_sync.py',
67
+ 'zentao-sync': 'domain/task/zentao_sync.py',
68
+ # ── 需求 / PRD ──
69
+ 'req': 'domain/requirement/req.py',
70
+ 'archive-prd': 'domain/requirement/archive_prd.py',
71
+ # ── 回流平台 (引擎产出 → 平台 AI回流Tab) ──
72
+ # 用法: wlkj.py return prd <路径> <标题> | return prototype <路径> <功能名> [--platform web]
73
+ 'return': 'domain/integration/return_to_platform.py',
74
+ 'spec-upload': 'domain/integration/spec_upload.py',
75
+ # ── 设计 ──
76
+ 'fill-prototype': 'domain/design/fill_prototype.py',
77
+ 'design-doc': 'domain/design/gen_design_doc.py',
78
+ # ── 评估 / 学习 (飞轮) ──
79
+ 'eval': 'validation/metrics/eval_prd.py',
80
+ 'eva': 'validation/metrics/eval_prd.py',
81
+ 'usability': 'validation/metrics/usability_score.py',
82
+ 'learn': 'domain/learning/learn.py',
83
+ 'learn-aggregate': 'domain/kg/build/learn_aggregate.py',
84
+ # ── 报告 / 状态 ──
85
+ 'status': 'domain/report/status.py',
86
+ 'status-snapshot': 'domain/report/status_snapshot.py',
87
+ 'report': 'domain/report/report.py',
88
+ 'report-snapshot': 'domain/report/report_snapshot.py',
89
+ 'session': 'domain/report/add_session.py',
90
+ 'add-session': 'domain/report/add_session.py',
91
+ 'role': 'domain/report/role.py',
92
+ 'export': 'domain/report/export.py',
93
+ # ── 测试 ──
94
+ 'test': 'validation/test/autotest.py',
95
+ 'autotest': 'validation/test/autotest.py',
96
+ 'autotest-batch': 'validation/test/autotest_batch.py',
97
+ 'benchmark': 'validation/test/benchmark.py',
98
+ # ── 环境 / 初始化 ──
99
+ 'switch-project': 'domain/kg/switch_project.py',
100
+ 'doctor': 'deployment/setup/init_doctor.py',
101
+ 'init': 'deployment/setup/setup.py',
102
+ 'install-qw': 'deployment/setup/install_qoderwork.py',
103
+ 'setup-lanhu': 'deployment/setup/setup_lanhu.py',
104
+ }
105
+
106
+
107
+
108
+
109
+ def _find_script_by_name(scripts_root, name):
110
+ """未知命令时, 当作脚本名在 scripts/ 下递归查找。
111
+ 支撑 'wlkj.py <脚本名>' 万能跑 (cli.js 的 run 命令底层依赖)。
112
+ 返回绝对路径或 None。"""
113
+ import glob
114
+ if not name.endswith('.py'):
115
+ name = name + '.py'
116
+ # 递归找 scripts/**/name
117
+ pattern = os.path.join(scripts_root, '**', name)
118
+ hits = glob.glob(pattern, recursive=True)
119
+ return hits[0] if hits else None
120
+
121
+ def _usage():
122
+ lines = [
123
+ 'wlkj.py — 工作流顶层调度器',
124
+ '',
125
+ '用法: python .qoder/scripts/orchestration/wlkj.py <命令> [参数...]',
126
+ '',
127
+ '命令分组 (转发到对应脚本):',
128
+ ' 知识图谱: kg <子命令> (kg.py, 13+ 能力)',
129
+ ' search <词> (search_index.py)',
130
+ ' context <词> (context_pack.py)',
131
+ ' kg-build (全量构建图谱)',
132
+ ' kg-incremental (增量构建, 定时任务用)',
133
+ ' 任务: task <子命令> (task.py, create/list/start/finish)',
134
+ ' 同步: sync pull|push (team_sync.py)',
135
+ ' 需求: req <子命令> (req.py, 需求生命周期)',
136
+ ' archive-prd (PRD 归档)',
137
+ ' 回流平台: return prd|prototype <文件> <标题> [--platform web] (PRD/原型回流到平台 AI回流Tab)',
138
+ ' 设计: fill-prototype <tpl> (真实数据预填原型)',
139
+ ' design-doc (生成 DESIGN.md)',
140
+ ' 评估/学习: eval <prd.md> (eval_prd.py, EVA 评分)',
141
+ ' usability [--days N] (usability_score.py, 工作流可用性: 采纳+流程)',
142
+ ' learn record/status (个人埋点)',
143
+ ' learn-aggregate (团队聚合 → kg.duckdb)',
144
+ ' 状态/报告: status (status.py, 健康度)',
145
+ ' report daily|weekly (report.py)',
146
+ ' export <格式> (export.py)',
147
+ ' 测试: test <子命令> (autotest.py)',
148
+ ' autotest-batch <f> (批量串测)',
149
+ ' benchmark (性能基线)',
150
+ ' 体检: doctor [--fix] (init_doctor.py)',
151
+ '',
152
+ '示例:',
153
+ ' wlkj.py kg search 保险 --platform web',
154
+ ' wlkj.py task create "登录优化"',
155
+ ' wlkj.py doctor --fix',
156
+ ' wlkj.py learn record prd_accepted \'{"req_id":"REQ-001","score":87}\'',
157
+ '',
158
+ '注: 所有 Qoder 载体 (命令/skills/rules) 统一走本入口; 脚本物理位置见 DISPATCH 表。',
159
+ ]
160
+ return '\n'.join(lines)
161
+
162
+
163
+ def _ctx_cache_cmd(rest, scripts_root):
164
+ """wlkj.py ctx-cache <REQ-ID> <query> [--platform web|app] — 按 REQ 缓存 context_pack 结果(T1.3)。
165
+ HIT 回放缓存(省一次 KG/MCP 往返);MISS 跑 context_pack 捕获 stdout 写缓存再回放。
166
+ wlkj.py ctx-cache <REQ-ID> --clear — 清该 REQ 缓存(PRD/spec 改动后失效)。
167
+ 任何异常 fail-open 降级直拉(缓存坏了不影响主流程)。
168
+ """
169
+ do_clear = "--clear" in rest
170
+ rest = [a for a in rest if a != "--clear"]
171
+
172
+ req_id = None
173
+ query_parts, passthrough = [], []
174
+ i = 0
175
+ while i < len(rest):
176
+ a = rest[i]
177
+ if a == "--platform" and i + 1 < len(rest):
178
+ passthrough.extend([a, rest[i + 1]]); i += 2; continue
179
+ if a.startswith("-"):
180
+ passthrough.append(a); i += 1; continue
181
+ if req_id is None:
182
+ req_id = a; i += 1; continue
183
+ query_parts.append(a); i += 1
184
+ query = " ".join(query_parts).strip()
185
+ if not req_id or (not query and not do_clear):
186
+ sys.stderr.write("用法: wlkj.py ctx-cache <REQ-ID> <query> [--platform web|app] | ctx-cache <REQ-ID> --clear\n")
187
+ return 1
188
+
189
+ cc = None
190
+ try:
191
+ if scripts_root not in sys.path:
192
+ sys.path.insert(0, scripts_root)
193
+ from foundation.io import context_cache as _cc # noqa
194
+ cc = _cc
195
+ except Exception as e:
196
+ sys.stderr.write("[cache] 模块导入失败, 降级直拉: %s\n" % str(e)[:80])
197
+
198
+ # --clear: 清该 REQ 缓存
199
+ if do_clear:
200
+ if cc is None:
201
+ sys.stderr.write("[cache] 模块不可用, 无法 clear\n"); return 1
202
+ cc.invalidate(req_id)
203
+ sys.stderr.write("[cache] CLEARED req=%s\n" % req_id)
204
+ return 0
205
+
206
+ if cc is not None:
207
+ try:
208
+ cached = cc.get(req_id, query)
209
+ if cached is not None:
210
+ sys.stdout.write(cached if cached.endswith("\n") else cached + "\n")
211
+ sys.stderr.write("[cache] HIT req=%s q=%s (省一次 context_pack 往返)\n" % (req_id, query[:30]))
212
+ return 0
213
+ except Exception as e:
214
+ sys.stderr.write("[cache] get 失败, 降级直拉: %s\n" % str(e)[:80])
215
+
216
+ # MISS / 降级:跑 context_pack,捕获 stdout,写缓存,回放
217
+ script_path = os.path.join(scripts_root, DISPATCH["context"])
218
+ r = subprocess.run([sys.executable, script_path, query] + passthrough, capture_output=True)
219
+ sys.stderr.write(r.stderr.decode(errors="replace"))
220
+ out = r.stdout.decode(errors="replace")
221
+ sys.stdout.write(out)
222
+ if out.strip() and cc is not None:
223
+ try:
224
+ cc.put(req_id, query, out)
225
+ sys.stderr.write("[cache] MISS+PUT req=%s q=%s\n" % (req_id, query[:30]))
226
+ except Exception as e:
227
+ sys.stderr.write("[cache] put 失败(不影响本次输出): %s\n" % str(e)[:80])
228
+ return r.returncode
229
+
230
+
231
+ def _bf_score_cmd(rest, scripts_root):
232
+ """wlkj.py bf-score <prd|proto|code|search> <产物文件> [--feature 主题] — 业务保真打分(T1/FW12)。
233
+ 转发 bf_score.py 对真命令产出文件打 BF 分(PRD→BF1/2, 原型→BF3/4, code/search→BF5/6)。
234
+ bf_score.py 跑在云容器(接真 db_tables/design_system/真PRD);本地无业务数据时降级提示。"""
235
+ script = os.path.join(scripts_root, "validation", "eval", "bf_score.py")
236
+ if not os.path.isfile(script):
237
+ sys.stderr.write("[bf-score] bf_score.py 不在,跳过\n")
238
+ return 1
239
+ r = subprocess.run([sys.executable, script] + rest)
240
+ return r.returncode
241
+
242
+
243
+ # custom 逻辑的命令(在 DISPATCH 透明转发前拦截)
244
+ SPECIAL = {
245
+ 'ctx-cache': _ctx_cache_cmd,
246
+ 'context-cache': _ctx_cache_cmd,
247
+ 'bf-score': _bf_score_cmd,
248
+ 'bfscore': _bf_score_cmd,
249
+ }
250
+
251
+
252
+ def main():
253
+ argv = sys.argv[1:]
254
+ if not argv or argv[0] in ('-h', '--help', 'help'):
255
+ print(_usage())
256
+ return 0
257
+
258
+ cmd = argv[0]
259
+ rest = argv[1:]
260
+
261
+ # scripts/ 根 = __file__ 的上两级 (orchestration/ -> scripts/)
262
+ scripts_root = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
263
+
264
+ # T1.3 带 custom 逻辑的命令(如 ctx-cache 缓存)在透明转发前拦截
265
+ if cmd in SPECIAL:
266
+ return SPECIAL[cmd](rest, scripts_root)
267
+
268
+ script = DISPATCH.get(cmd)
269
+ if not script:
270
+ # Fallback: 当作脚本名在 scripts/ 下递归找 (支撑 'wlkj.py run <脚本>')
271
+ # 这样 cli.js 的 'run' 和直接 'wlkj.py xxx.py' 都能走单一入口, 不需另维护路径表
272
+ hit = _find_script_by_name(scripts_root, cmd)
273
+ if hit:
274
+ script_path = hit
275
+ r = subprocess.run([sys.executable, script_path] + rest)
276
+ return r.returncode
277
+ sys.stderr.write('未知命令: %s\n' % cmd)
278
+ sys.stderr.write('可用命令: %s\n' % ', '.join(sorted(DISPATCH.keys())))
279
+ sys.stderr.write('提示: 也可用 wlkj.py <脚本名.py> 递归查找脚本\n')
280
+ return 1
281
+
282
+ # scripts/ = __file__ 的上两级 (orchestration/ scripts/)
283
+ scripts_root = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
284
+ script_path = os.path.join(scripts_root, script)
285
+ if not os.path.isfile(script_path):
286
+ sys.stderr.write('脚本不存在: %s\n' % script_path)
287
+ return 1
288
+
289
+ # 透明转发: 用当前 python 跑目标脚本, 透传所有参数和 exit code
290
+ r = subprocess.run([sys.executable, script_path] + rest)
291
+ return r.returncode
292
+
293
+
294
+ if __name__ == '__main__':
295
+ sys.exit(main())