@hupan56/wlkj 3.3.7 → 3.3.9
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/commands/optional/wl-spec.md +12 -2
- package/templates/qoder/commands/optional/wl-status.md +8 -0
- package/templates/qoder/commands/wl-code.md +8 -0
- package/templates/qoder/commands/wl-commit.md +11 -1
- package/templates/qoder/commands/wl-data.md +46 -0
- package/templates/qoder/commands/wl-fix.md +47 -0
- package/templates/qoder/commands/wl-init.md +2 -2
- package/templates/qoder/commands/wl-knowledge.md +41 -0
- package/templates/qoder/commands/wl-prd.md +23 -22
- package/templates/qoder/commands/wl-review.md +49 -0
- package/templates/qoder/commands/wl-search.md +23 -27
- package/templates/qoder/commands/wl-task.md +11 -1
- package/templates/qoder/commands/wl-test.md +8 -0
- package/templates/qoder/contracts/insight.md +55 -0
- package/templates/qoder/hooks/__pycache__/pre-tool-use-commit.cpython-39.pyc +0 -0
- package/templates/qoder/hooks/__pycache__/pre-tool-use.cpython-39.pyc +0 -0
- package/templates/qoder/hooks/__pycache__/session-start.cpython-39.pyc +0 -0
- package/templates/qoder/hooks/__pycache__/stop-eval.cpython-39.pyc +0 -0
- package/templates/qoder/hooks/pre-tool-use-commit.py +124 -0
- package/templates/qoder/hooks/session-start.py +20 -1
- package/templates/qoder/hooks/stop-eval.py +129 -0
- package/templates/qoder/scripts/capability/adapters/mcp.py +1 -0
- package/templates/qoder/scripts/capability/registry.py +0 -3
- package/templates/qoder/scripts/domain/integration/return_to_platform.py +1 -0
- package/templates/qoder/scripts/domain/integration/spec_upload.py +3 -3
- package/templates/qoder/scripts/domain/kg/build/build_entity_registry.py +12 -12
- package/templates/qoder/scripts/domain/kg/build/build_relations.py +12 -12
- package/templates/qoder/scripts/foundation/__pycache__/__init__.cpython-39.pyc +0 -0
- package/templates/qoder/scripts/foundation/io/__pycache__/__init__.cpython-39.pyc +0 -0
- package/templates/qoder/scripts/foundation/io/__pycache__/context_cache.cpython-39.pyc +0 -0
- package/templates/qoder/scripts/foundation/io/context_cache.py +94 -0
- package/templates/qoder/scripts/orchestration/__pycache__/wlkj.cpython-39.pyc +0 -0
- package/templates/qoder/scripts/orchestration/wlkj.py +95 -0
- package/templates/qoder/scripts/tool_guide.md +70 -0
- package/templates/qoder/scripts/validation/eval/__pycache__/alignment_matrix.cpython-39.pyc +0 -0
- package/templates/qoder/scripts/validation/eval/__pycache__/bf2_content_fidelity.cpython-39.pyc +0 -0
- package/templates/qoder/scripts/validation/eval/__pycache__/bf2_llmjudge.cpython-39.pyc +0 -0
- package/templates/qoder/scripts/validation/eval/__pycache__/bf_score.cpython-39.pyc +0 -0
- package/templates/qoder/scripts/validation/eval/__pycache__/code_flywheel.cpython-39.pyc +0 -0
- package/templates/qoder/scripts/validation/eval/__pycache__/dispatcher_ab.cpython-39.pyc +0 -0
- package/templates/qoder/scripts/validation/eval/__pycache__/feature_fidelity_flywheel.cpython-39.pyc +0 -0
- package/templates/qoder/scripts/validation/eval/__pycache__/gradient_matrix.cpython-39.pyc +0 -0
- package/templates/qoder/scripts/validation/eval/__pycache__/metrics_dashboard.cpython-39.pyc +0 -0
- package/templates/qoder/scripts/validation/eval/__pycache__/multi_turn_flywheel.cpython-39.pyc +0 -0
- package/templates/qoder/scripts/validation/eval/__pycache__/prd_fidelity_flywheel.cpython-39.pyc +0 -0
- package/templates/qoder/scripts/validation/eval/__pycache__/prd_flywheel.cpython-39.pyc +0 -0
- package/templates/qoder/scripts/validation/eval/__pycache__/prototype_fidelity_flywheel.cpython-39.pyc +0 -0
- package/templates/qoder/scripts/validation/eval/__pycache__/recall_flywheel.cpython-39.pyc +0 -0
- package/templates/qoder/scripts/validation/eval/__pycache__/robustness_flywheel.cpython-39.pyc +0 -0
- package/templates/qoder/scripts/validation/eval/__pycache__/speed_accuracy_flywheel.cpython-39.pyc +0 -0
- package/templates/qoder/scripts/validation/eval/__pycache__/task_flywheel.cpython-39.pyc +0 -0
- package/templates/qoder/scripts/validation/eval/__pycache__/token_flywheel.cpython-39.pyc +0 -0
- package/templates/qoder/scripts/validation/eval/alignment_matrix.py +176 -0
- package/templates/qoder/scripts/validation/eval/bf2_content_fidelity.py +110 -0
- package/templates/qoder/scripts/validation/eval/bf2_llmjudge.py +104 -0
- package/templates/qoder/scripts/validation/eval/bf_score.py +218 -0
- package/templates/qoder/scripts/validation/eval/code_flywheel.py +150 -0
- package/templates/qoder/scripts/validation/eval/dispatcher_ab.py +156 -0
- package/templates/qoder/scripts/validation/eval/dispatcher_ab_2026-07-21.json +23 -0
- package/templates/qoder/scripts/validation/eval/feature_fidelity_flywheel.py +143 -0
- package/templates/qoder/scripts/validation/eval/gradient_matrix.py +261 -0
- package/templates/qoder/scripts/validation/eval/gradient_matrix_baseline_2026-07-21.json +33 -0
- package/templates/qoder/scripts/validation/eval/metrics_dashboard.py +105 -0
- package/templates/qoder/scripts/validation/eval/multi_turn_flywheel.py +118 -0
- package/templates/qoder/scripts/validation/eval/prd_fidelity_flywheel.py +128 -0
- package/templates/qoder/scripts/validation/eval/prd_flywheel.py +148 -0
- package/templates/qoder/scripts/validation/eval/prototype_fidelity_flywheel.py +166 -0
- package/templates/qoder/scripts/validation/eval/recall_flywheel.py +148 -0
- package/templates/qoder/scripts/validation/eval/robustness_flywheel.py +139 -0
- package/templates/qoder/scripts/validation/eval/speed_accuracy_flywheel.py +188 -0
- package/templates/qoder/scripts/validation/eval/task_flywheel.py +124 -0
- package/templates/qoder/scripts/validation/eval/token_flywheel.py +88 -0
- package/templates/qoder/scripts/validation/metrics/__pycache__/lint_cases.cpython-39.pyc +0 -0
- package/templates/qoder/scripts/validation/metrics/eval_code_ac.py +177 -0
- package/templates/qoder/scripts/validation/metrics/lint_cases.py +170 -0
- package/templates/qoder/scripts/validation/test/__pycache__/test_context_cache.cpython-39.pyc +0 -0
- package/templates/qoder/scripts/validation/test/test_context_cache.py +78 -0
- package/templates/qoder/scripts/validation/test/test_lint_cases.py +60 -0
- package/templates/qoder/scripts/validation/test/test_pre_tool_use_commit.py +70 -0
- package/templates/qoder/settings.json +8 -0
- package/templates/qoder/skills/wl-spec/SKILL.md +1 -1
- package/templates/workspace/specs/REQ-TEST-CTX/.context-cache.json +1 -0
- package/templates/qoder/agents/design-agent.md +0 -20
- package/templates/qoder/agents/spec-generator.md +0 -21
- package/templates/qoder/scripts/capability/smoke_test_report.json.new +0 -94
- package/templates/qoder/scripts/domain/kg/extract/asset/__init__.py +0 -10
- package/templates/qoder/scripts/domain/kg/extract/asset/asset_tree.py +0 -57
- package/templates/qoder/scripts/domain/kg/extract/asset/discussion_importer.py +0 -62
- package/templates/qoder/scripts/domain/kg/extract/asset/prd_importer.py +0 -146
- package/templates/qoder/scripts/domain/kg/extract/asset/prototype_importer.py +0 -64
- package/templates/qoder/scripts/domain/kg/extract/asset/returns_importer.py +0 -52
- package/templates/qoder/scripts/domain/kg/extract/build_goal3.py +0 -104
- package/templates/qoder/scripts/domain/kg/extract/build_goal4.py +0 -55
- package/templates/qoder/scripts/domain/kg/extract/build_goal5.py +0 -95
- package/templates/qoder/scripts/domain/kg/extract/db/__init__.py +0 -8
- package/templates/qoder/scripts/domain/kg/extract/db/data_profile.py +0 -22
- package/templates/qoder/scripts/domain/kg/extract/db/fk_extractor.py +0 -55
- package/templates/qoder/scripts/domain/kg/extract/db/schema_extractor.py +0 -90
- package/templates/qoder/scripts/domain/kg/extract/inference/__init__.py +0 -9
- package/templates/qoder/scripts/domain/kg/extract/inference/community_summarizer.py +0 -206
- package/templates/qoder/scripts/domain/kg/extract/inference/embed_builder.py +0 -132
- package/templates/qoder/scripts/domain/kg/extract/inference/naming_matcher.py +0 -80
- package/templates/qoder/scripts/domain/kg/extract/inference/promote.py +0 -59
- package/templates/qoder/scripts/domain/kg/extract/inference/recompute.py +0 -93
- package/templates/qoder/scripts/domain/kg/extract/inference/weak_link.py +0 -421
- package/templates/qoder/scripts/domain/kg/extract/mybatis/__init__.py +0 -9
- package/templates/qoder/scripts/domain/kg/extract/mybatis/all.py +0 -79
- package/templates/qoder/scripts/domain/kg/extract/mybatis/mapper_parser.py +0 -99
- package/templates/qoder/scripts/domain/kg/extract/mybatis/relation_builder.py +0 -69
- package/templates/qoder/scripts/domain/kg/extract/mybatis/sql_extractor.py +0 -78
- package/templates/qoder/scripts/domain/kg/extract/prd/__init__.py +0 -8
- package/templates/qoder/scripts/domain/kg/extract/prd/prd_chunk_embed.py +0 -105
- package/templates/qoder/scripts/domain/kg/extract/prd/prd_llm_extract.py +0 -153
- package/templates/qoder/scripts/domain/kg/extract/prd/req_anchor.py +0 -120
- package/templates/qoder/scripts/domain/kg/graph/kg_link_db.py +0 -235
- package/templates/qoder/scripts/domain/kg/search/enrich_prompt.py +0 -238
- package/templates/qoder/scripts/domain/kg/server/perf_bench.py +0 -197
- package/templates/qoder/scripts/domain/kg/sync_repowiki.py +0 -109
- package/templates/qoder/scripts/engine/poller.py +0 -219
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
"""pre-tool-use-commit.py - P0-3 w3: wl-commit msg 门禁(PreToolUse on Bash git commit)
|
|
4
|
+
|
|
5
|
+
stop-eval.py 里 _enforce_commit_msg_gate 的**正确落点**:pre-tool-use(commit 执行前验 msg),
|
|
6
|
+
而非 stop-eval(后者 git log -1 不分"本轮 wl-commit" vs 平台开发/工具 commit → 误拦)。
|
|
7
|
+
|
|
8
|
+
做法(确定性 + 不误拦其他):
|
|
9
|
+
1. PreToolUse 命中 Bash + command 含 "git commit"
|
|
10
|
+
2. 提取 -m 消息(兼容 -m "x" / -m 'x' / --message=x / 多 -m)
|
|
11
|
+
3. 查需求关联标记 [#REQ-<uuid>] (S3) 或 [#<禅道号>] (S5.1)
|
|
12
|
+
4. 缺标记:
|
|
13
|
+
- 默认 ADVISORY(exit 0 + stdout 强提醒)—— 不阻断,避免误拦工具/平台/文档 commit
|
|
14
|
+
- 环境变量 WLCOMMIT_GATE_BLOCK=1 时 → exit 2 硬拦(wl-commit 强流程场景才开)
|
|
15
|
+
|
|
16
|
+
铁律:永远 exit 0(除非 BLOCK 模式显式开);失败 try/except 静默(hook 崩绝不阻塞)。
|
|
17
|
+
非 git commit 命令 / 非 Bash → exit 0。
|
|
18
|
+
"""
|
|
19
|
+
import json
|
|
20
|
+
import os
|
|
21
|
+
import re
|
|
22
|
+
import sys
|
|
23
|
+
|
|
24
|
+
if sys.platform == 'win32':
|
|
25
|
+
try:
|
|
26
|
+
sys.stdout.reconfigure(encoding='utf-8')
|
|
27
|
+
sys.stderr.reconfigure(encoding='utf-8')
|
|
28
|
+
except Exception:
|
|
29
|
+
pass
|
|
30
|
+
|
|
31
|
+
NL = chr(10)
|
|
32
|
+
BASE = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
|
33
|
+
HOOK_ERRORS_LOG = os.path.join(BASE, '.qoder', '.runtime', 'hook-errors.log')
|
|
34
|
+
|
|
35
|
+
# 需求关联标记:[#REQ-xxx] 或 [#123](禅道号)
|
|
36
|
+
_REQ_TAG_RE = re.compile(r'\[#REQ-[\w-]+\]|\[#\d+\]')
|
|
37
|
+
# 提取 git commit -m 的消息(兼容引号/多段/--message= / 组合短旗 -am)
|
|
38
|
+
# (?:^|\s)-[a-zA-Z]*m 匹配 -m / -am / -cm 等(单 dash 短旗组合;空白前缀避 --message 误匹)
|
|
39
|
+
_M_QUOTED = re.compile(r'''(?:^|\s)-[a-zA-Z]*m\s*(['"])(.*?)\1''', re.S)
|
|
40
|
+
_M_BARE = re.compile(r'(?:^|\s)-[a-zA-Z]*m\s+(\S.*?)\s*(?=(?:^|\s)-[a-zA-Z]*m\s|$)', re.S)
|
|
41
|
+
_MSG_EQ = re.compile(r'(?:^|\s)--message=(\S.*?)\s*(?:--|$)', re.S)
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def _read_stdin():
|
|
45
|
+
try:
|
|
46
|
+
raw = sys.stdin.read()
|
|
47
|
+
if raw and raw.strip():
|
|
48
|
+
return json.loads(raw)
|
|
49
|
+
except Exception:
|
|
50
|
+
pass
|
|
51
|
+
return {}
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def _extract_msg(command):
|
|
55
|
+
"""从 git commit 命令里提取 commit message(拼多 -m)。提不到返 ''。"""
|
|
56
|
+
if "--message=" in command:
|
|
57
|
+
m = _MSG_EQ.search(command)
|
|
58
|
+
if m:
|
|
59
|
+
return m.group(1).strip()
|
|
60
|
+
# 所有 -m "..." / -m '...' 段拼接
|
|
61
|
+
parts = _M_QUOTED.findall(command) # [(quote, msg), ...]
|
|
62
|
+
if parts:
|
|
63
|
+
return "\n".join(p[1] for p in parts).strip()
|
|
64
|
+
# 裸 -m word(无引号)
|
|
65
|
+
m = _M_BARE.search(command)
|
|
66
|
+
if m:
|
|
67
|
+
return m.group(1).strip()
|
|
68
|
+
return ""
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def _log_err(line):
|
|
72
|
+
from datetime import datetime
|
|
73
|
+
try:
|
|
74
|
+
os.makedirs(os.path.dirname(HOOK_ERRORS_LOG), exist_ok=True)
|
|
75
|
+
with open(HOOK_ERRORS_LOG, 'a', encoding='utf-8') as f:
|
|
76
|
+
f.write('[{}] pre-tool-use-commit: {}{}'.format(
|
|
77
|
+
datetime.now().strftime('%Y-%m-%d %H:%M:%S'), str(line)[:200], NL))
|
|
78
|
+
except Exception:
|
|
79
|
+
pass
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def main():
|
|
83
|
+
data = _read_stdin()
|
|
84
|
+
tool = data.get("tool_name") or data.get("tool") or ""
|
|
85
|
+
if tool.lower() not in ("bash", "shell", "execute_bash"):
|
|
86
|
+
sys.exit(0)
|
|
87
|
+
tinput = data.get("tool_input") or data.get("input") or {}
|
|
88
|
+
command = tinput.get("command") or tinput.get("cmd") or ""
|
|
89
|
+
if not isinstance(command, str) or "git commit" not in command:
|
|
90
|
+
sys.exit(0)
|
|
91
|
+
# 跳过 --amend 无 -m / 自动生成(如 merge commit)—— 只验带 -m 的显式 commit
|
|
92
|
+
msg = _extract_msg(command)
|
|
93
|
+
if not msg:
|
|
94
|
+
sys.exit(0) # 提不到 -m(可能 -F 文件或 amend),不拦
|
|
95
|
+
if _REQ_TAG_RE.search(msg):
|
|
96
|
+
sys.exit(0) # 含需求标记,放行
|
|
97
|
+
|
|
98
|
+
# 缺标记
|
|
99
|
+
block_mode = os.environ.get("WLCOMMIT_GATE_BLOCK", "").lower() in ("1", "true", "yes")
|
|
100
|
+
preview = msg.replace("\n", " ")[:100]
|
|
101
|
+
if block_mode:
|
|
102
|
+
sys.stderr.write(
|
|
103
|
+
'【wl-commit msg 门禁·硬拦】commit msg 缺需求关联标记:\n %s\n'
|
|
104
|
+
'修复: git commit --amend 补 [#REQ-<uuid>] (S3) 或 [#<禅道号>] (S5.1 push 后)。\n'
|
|
105
|
+
'(此硬拦由 WLCOMMIT_GATE_BLOCK=1 开启;平台/工具/文档 commit 关该 env 或走非 wl-commit 流程。)\n'
|
|
106
|
+
% preview)
|
|
107
|
+
sys.exit(2)
|
|
108
|
+
# 默认 advisory:不阻断,强提醒
|
|
109
|
+
sys.stdout.write(
|
|
110
|
+
'⚠️ 【wl-commit msg 提醒】本次 commit msg 缺需求关联标记 [#REQ-xxx]/[#禅道号]:\n'
|
|
111
|
+
' %s\n'
|
|
112
|
+
'若这是 /wl-commit 流程,建议 amend 补标记关联需求(F1 闭环数据);'
|
|
113
|
+
'若是平台/工具/文档 commit 可忽略本提醒。\n' % preview)
|
|
114
|
+
sys.exit(0)
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
if __name__ == "__main__":
|
|
118
|
+
try:
|
|
119
|
+
main()
|
|
120
|
+
except SystemExit:
|
|
121
|
+
raise
|
|
122
|
+
except Exception as e:
|
|
123
|
+
_log_err("fatal: %s" % str(e)[:150])
|
|
124
|
+
sys.exit(0) # 永不阻塞
|
|
@@ -129,7 +129,7 @@ def get_index_info():
|
|
|
129
129
|
recent = errors[-10:] # 取最后 10 行检查
|
|
130
130
|
if len(recent) >= 3:
|
|
131
131
|
lines.append('[WARN] hook 最近频繁失败 (%d 条错误), 详情见 .qoder/.runtime/hook-errors.log' % len(recent))
|
|
132
|
-
lines.append(' 跑 python .qoder/scripts/
|
|
132
|
+
lines.append(' 跑 python .qoder/scripts/orchestration/wlkj.py doctor --fix 排查')
|
|
133
133
|
except OSError:
|
|
134
134
|
pass
|
|
135
135
|
|
|
@@ -289,6 +289,22 @@ def get_usability_summary():
|
|
|
289
289
|
return NL.join(lines) if lines else ''
|
|
290
290
|
|
|
291
291
|
|
|
292
|
+
def get_tool_guide():
|
|
293
|
+
"""注入 tool_guide 精简版(共用工具用法关键执行位), 命令正文不再重复(单一信源, T1.2)。
|
|
294
|
+
完整版见 .qoder/scripts/tool_guide.md。纯静态文本, 失败绝不阻塞。"""
|
|
295
|
+
return NL.join([
|
|
296
|
+
'## Tool Guide (共用工具用法 · 完整见 .qoder/scripts/tool_guide.md)',
|
|
297
|
+
'环境自检(任何脚本前先跑, 定 R/$PY):',
|
|
298
|
+
' R=$(python ~/.qoderwork/repo_root.py 2>/dev/null || python3 ~/.qoderwork/repo_root.py 2>/dev/null) || R=.',
|
|
299
|
+
' PY=$(python --version >/dev/null 2>&1 && echo python || echo python3)',
|
|
300
|
+
' 后续统一 $PY "$R/.qoder/scripts/orchestration/wlkj.py" <命令>',
|
|
301
|
+
'接口三套名互通: wlkj.py / kg.py / MCP 名字都认(search/semantic/context/impact), 别因"未知子命令"慌',
|
|
302
|
+
'中文歧义词先提炼再查: 保险->车险/商业险/交强险 · 异常->告警/报错 · 计划->作业/养护',
|
|
303
|
+
'禁全盘扫: 不要 find / grep -r / findstr /s / os.walk data/code, 那是知识层 search 的活',
|
|
304
|
+
'优先级口诀: 精确搜用Grep · 语义找用rag_search · 取全用context_pack · 查库先list_envs · 影响用get_impact',
|
|
305
|
+
])
|
|
306
|
+
|
|
307
|
+
|
|
292
308
|
def main():
|
|
293
309
|
parts = []
|
|
294
310
|
parts.append('<qoder-context>')
|
|
@@ -345,6 +361,9 @@ def main():
|
|
|
345
361
|
parts.append('')
|
|
346
362
|
parts.append(style_info)
|
|
347
363
|
|
|
364
|
+
parts.append('')
|
|
365
|
+
parts.append(get_tool_guide())
|
|
366
|
+
|
|
348
367
|
parts.append('</qoder-context>')
|
|
349
368
|
# Hook-1: 健康标记 — AI 能感知 hook 是否真的跑了
|
|
350
369
|
# QoderWork/Quest 里看不到 [hook-ok] 就知道 hook 没生效, 要自取上下文
|
|
@@ -179,6 +179,127 @@ def _last_record_by_prd():
|
|
|
179
179
|
return last_by_prd
|
|
180
180
|
|
|
181
181
|
|
|
182
|
+
def _enforce_code_ac_gate():
|
|
183
|
+
"""P0-3: wl-code spec AC 门禁。找近 spec + eval_code_ac.py 对账, AC<100% 阻断。
|
|
184
|
+
|
|
185
|
+
治"代码漏实现 spec AC"(wl-code 自检全打勾反例)。无 spec / 脚本缺失 / 崩溃 → 放行(不阻塞)。
|
|
186
|
+
"""
|
|
187
|
+
import subprocess
|
|
188
|
+
qoder_script = os.path.join(BASE, '.qoder', 'scripts', 'validation', 'metrics', 'eval_code_ac.py')
|
|
189
|
+
pkg_script = os.path.join(BASE, 'packages', 'wlkj', 'templates', 'qoder',
|
|
190
|
+
'scripts', 'validation', 'metrics', 'eval_code_ac.py')
|
|
191
|
+
script = qoder_script if os.path.isfile(qoder_script) else pkg_script
|
|
192
|
+
if not os.path.isfile(script):
|
|
193
|
+
return # 脚本不在, 放行
|
|
194
|
+
try:
|
|
195
|
+
r = subprocess.run([sys.executable, script], cwd=BASE,
|
|
196
|
+
capture_output=True, text=True, timeout=30)
|
|
197
|
+
except Exception:
|
|
198
|
+
return # 崩溃绝不阻塞
|
|
199
|
+
if r.returncode != 2:
|
|
200
|
+
return # exit 0 = 无 spec / AC 全覆盖, 放行
|
|
201
|
+
out_lines = [l for l in (r.stdout or '').split('\n') if l.strip()]
|
|
202
|
+
try:
|
|
203
|
+
data = json.loads(out_lines[-1]) if out_lines else {}
|
|
204
|
+
except Exception:
|
|
205
|
+
data = {}
|
|
206
|
+
missed = data.get('missed', []) or []
|
|
207
|
+
rate = data.get('rate', 0)
|
|
208
|
+
miss_str = '\n'.join(' - ' + str(m)[:80] for m in missed[:5])
|
|
209
|
+
sys.stderr.write(
|
|
210
|
+
'【wl-code AC 门禁】检测到 spec 验收标准未被代码覆盖 (覆盖率 %s%%):\n%s\n'
|
|
211
|
+
'修复: 对照 spec 第6章 PRD 验收标准映射表, 把未实现的 AC 补进代码, 重跑至 100%%。\n'
|
|
212
|
+
% (int(rate * 100), miss_str))
|
|
213
|
+
sys.exit(2)
|
|
214
|
+
|
|
215
|
+
|
|
216
|
+
def _enforce_cases_lint_gate():
|
|
217
|
+
"""P0-3 w2: wl-test cases 格式门禁。扫近 10min cases 文件(含 intent/action/anchor 键的 json),
|
|
218
|
+
跑 lint_cases.py,旧格式(action+CSS selector) → exit 2。无 cases/脚本缺/崩溃 → 放行(不阻塞)。"""
|
|
219
|
+
import subprocess
|
|
220
|
+
qoder_script = os.path.join(BASE, '.qoder', 'scripts', 'validation', 'metrics', 'lint_cases.py')
|
|
221
|
+
pkg_script = os.path.join(BASE, 'packages', 'wlkj', 'templates', 'qoder',
|
|
222
|
+
'scripts', 'validation', 'metrics', 'lint_cases.py')
|
|
223
|
+
script = qoder_script if os.path.isfile(qoder_script) else pkg_script
|
|
224
|
+
if not os.path.isfile(script):
|
|
225
|
+
return
|
|
226
|
+
now = time.time()
|
|
227
|
+
cases_files = []
|
|
228
|
+
for root in (os.path.join(BASE, 'workspace', 'members'), os.path.join(BASE, 'workspace', 'tasks')):
|
|
229
|
+
if not os.path.isdir(root):
|
|
230
|
+
continue
|
|
231
|
+
for dirpath, dirs, files in os.walk(root):
|
|
232
|
+
dirs[:] = [d for d in dirs if d not in ('node_modules', '.git', '__pycache__', '.secrets')]
|
|
233
|
+
for fn in files:
|
|
234
|
+
if not fn.endswith('.json'):
|
|
235
|
+
continue
|
|
236
|
+
fp = os.path.join(dirpath, fn)
|
|
237
|
+
try:
|
|
238
|
+
if now - os.path.getmtime(fp) > 600:
|
|
239
|
+
continue
|
|
240
|
+
except OSError:
|
|
241
|
+
continue
|
|
242
|
+
try:
|
|
243
|
+
head = open(fp, encoding='utf-8', errors='replace').read(2000)
|
|
244
|
+
except Exception:
|
|
245
|
+
continue
|
|
246
|
+
if '"intent"' in head or '"action"' in head or '"anchor"' in head:
|
|
247
|
+
cases_files.append(fp)
|
|
248
|
+
for fp in cases_files[:5]:
|
|
249
|
+
try:
|
|
250
|
+
r = subprocess.run([sys.executable, script, fp, '--json'], cwd=BASE,
|
|
251
|
+
capture_output=True, text=True, timeout=15)
|
|
252
|
+
except Exception:
|
|
253
|
+
continue
|
|
254
|
+
if r.returncode != 2:
|
|
255
|
+
continue
|
|
256
|
+
out_lines = [l for l in (r.stdout or '').split('\n') if l.strip()]
|
|
257
|
+
try:
|
|
258
|
+
data = json.loads(out_lines[-1]) if out_lines else {}
|
|
259
|
+
except Exception:
|
|
260
|
+
data = {}
|
|
261
|
+
blocks = [v for v in (data.get('violations') or []) if v.get('severity') == 'BLOCK']
|
|
262
|
+
if blocks:
|
|
263
|
+
detail = '\n'.join(' - case#%s %s' % (v.get('idx'), str(v.get('detail', ''))[:60]) for v in blocks[:5])
|
|
264
|
+
sys.stderr.write(
|
|
265
|
+
'【wl-test cases 门禁】%s 检测到旧格式用例(action+CSS selector,新 Playwright 管线不认):\n%s\n'
|
|
266
|
+
'修复: 改 intent+anchor 格式,anchor={role,name}(来自 recall/snapshot),凭据用 {{ask:key}}。\n'
|
|
267
|
+
% (os.path.basename(fp), detail))
|
|
268
|
+
sys.exit(2)
|
|
269
|
+
|
|
270
|
+
|
|
271
|
+
def _enforce_commit_msg_gate():
|
|
272
|
+
"""P0-3 w3: wl-commit msg 门禁。近 30min commit msg 须含 [#REQ-xxx] 或 [#禅道号]。
|
|
273
|
+
|
|
274
|
+
治"commit 不关联需求"(F1 闭环数据断)。memory commit-convention-zentao-id:
|
|
275
|
+
S3 [#REQ-<uuid8>] / S5.1 push 后 [#<禅道号>]。无标记 → exit 2 提示 amend。
|
|
276
|
+
"""
|
|
277
|
+
import re as _re
|
|
278
|
+
import subprocess
|
|
279
|
+
try:
|
|
280
|
+
r = subprocess.run(['git', 'log', '-1', '--format=%B|||%ct'], cwd=BASE,
|
|
281
|
+
capture_output=True, text=True, timeout=10)
|
|
282
|
+
out = (r.stdout or '').strip()
|
|
283
|
+
except Exception:
|
|
284
|
+
return # 崩溃不阻塞
|
|
285
|
+
if '|||' not in out:
|
|
286
|
+
return
|
|
287
|
+
msg, _, ts_s = out.rpartition('|||')
|
|
288
|
+
try:
|
|
289
|
+
ts = int(ts_s)
|
|
290
|
+
except ValueError:
|
|
291
|
+
ts = 0
|
|
292
|
+
if ts and time.time() - ts > 1800:
|
|
293
|
+
return # 非本轮 commit
|
|
294
|
+
if _re.search(r'\[#REQ-[\w-]+\]|\[#\d+\]', msg):
|
|
295
|
+
return # 含需求标记, 放行
|
|
296
|
+
sys.stderr.write(
|
|
297
|
+
'【wl-commit msg 门禁】最近 commit msg 缺需求关联标记:\n %s\n'
|
|
298
|
+
'修复: git commit --amend 补 [#REQ-<uuid>] (S3) 或 [#<禅道号>] (S5.1 push 后)。\n'
|
|
299
|
+
% msg.replace('\n', ' ')[:120])
|
|
300
|
+
sys.exit(2)
|
|
301
|
+
|
|
302
|
+
|
|
182
303
|
def main():
|
|
183
304
|
try:
|
|
184
305
|
data = _read_stdin()
|
|
@@ -186,6 +307,14 @@ def main():
|
|
|
186
307
|
if _is_reentry(data):
|
|
187
308
|
sys.exit(0)
|
|
188
309
|
|
|
310
|
+
# ★ P0-3 wl-code spec AC 门禁(代码漏实现 AC 检测, 治自检全打勾)
|
|
311
|
+
_enforce_code_ac_gate()
|
|
312
|
+
# P0-3 w2 wl-test cases 格式门禁(旧 action+CSS selector → 拦)
|
|
313
|
+
_enforce_cases_lint_gate()
|
|
314
|
+
# w3 wl-commit msg 门禁: 应在 pre-tool-use(git commit 命令前验 msg)而非 stop-eval——
|
|
315
|
+
# stop-eval 的 git log -1 不分"本轮 wl-commit"vs 其他 commit, 会在平台开发等非 wl-commit
|
|
316
|
+
# 场景误拦。函数 _enforce_commit_msg_gate 已定义待 pre-tool-use 细化接入。
|
|
317
|
+
|
|
189
318
|
# 找近 10 分钟产出的 PRD, 检查是否已【通过】门禁
|
|
190
319
|
recent = _recent_prd_files()
|
|
191
320
|
if not recent:
|
|
@@ -35,6 +35,7 @@ def _load_mcp_config() -> dict:
|
|
|
35
35
|
except Exception:
|
|
36
36
|
repo = Path(__file__).resolve().parent.parent.parent.parent.parent
|
|
37
37
|
for cand in [
|
|
38
|
+
repo / ".qoder" / "mcp_config.json", # ★ switch_project 写这里,优先查(否则读不到 token → 回流误报"无 X-Engine-Token")
|
|
38
39
|
repo / "mcp_config.json",
|
|
39
40
|
]:
|
|
40
41
|
try:
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
契约: wlinkj-workspace/docs/SPEC-MCP-CONTRACT.md v1(create/confirm/update/get_spec)
|
|
6
6
|
|
|
7
7
|
流程: 解析契约头 → 有platform_spec_id走update/无走create → confirm → 回写幂等锚
|
|
8
|
-
传输: POST /mcp direct JSON-RPC + X-MCP-Token
|
|
8
|
+
传输: POST /mcp direct JSON-RPC + X-MCP-Token 头
|
|
9
9
|
"""
|
|
10
10
|
from __future__ import annotations
|
|
11
11
|
import json, os, re, sys, urllib.request, urllib.error
|
|
@@ -24,8 +24,8 @@ def _load_mcp_config() -> dict:
|
|
|
24
24
|
repo = get_repo_root()
|
|
25
25
|
except Exception:
|
|
26
26
|
repo = Path(__file__).resolve().parent.parent.parent.parent.parent
|
|
27
|
-
for cand in [repo / "mcp_config.json",
|
|
28
|
-
repo / "
|
|
27
|
+
for cand in [repo / ".qoder" / "mcp_config.json", # ★ switch_project 写这里,优先(同 return_to_platform,否则可能读到 repo/ 旧文件)
|
|
28
|
+
repo / "mcp_config.json"]:
|
|
29
29
|
try:
|
|
30
30
|
if cand.is_file():
|
|
31
31
|
with open(cand, encoding="utf-8") as f:
|
|
@@ -1,17 +1,6 @@
|
|
|
1
1
|
# -*- coding: utf-8 -*-
|
|
2
2
|
"""build_entity_registry.py — 从 ui-modules + code-api 构建 entity-registry.json。
|
|
3
3
|
|
|
4
|
-
# v3.0 路径自举: 引导到 common/bootstrap, 统一 sys.path 逻辑
|
|
5
|
-
import os as _o, sys as _s
|
|
6
|
-
_f = _o.path.abspath(__file__)
|
|
7
|
-
for _ in range(10):
|
|
8
|
-
_f = _o.path.dirname(_f)
|
|
9
|
-
_cp = _o.path.join(_f, 'foundation')
|
|
10
|
-
if _o.path.isfile(_o.path.join(_cp, 'bootstrap.py')):
|
|
11
|
-
break
|
|
12
|
-
if _cp not in _s.path: _s.path.insert(0, _cp)
|
|
13
|
-
from bootstrap import setup; setup()
|
|
14
|
-
|
|
15
4
|
产出 data/index/_build_cache/entity-registry.json, 供 kg_duckdb 导入 entities/aliases 表,
|
|
16
5
|
让 kg.py feature <功能名> 能返回功能画像。
|
|
17
6
|
|
|
@@ -34,6 +23,18 @@ from bootstrap import setup; setup()
|
|
|
34
23
|
业务流程) 由 build_relations.py/build_features.py/build_workflows.py 补充,
|
|
35
24
|
那些脚本未实现时不影响本脚本的 entity 基础层。
|
|
36
25
|
"""
|
|
26
|
+
# v3.0 路径自举: 引导到 foundation/bootstrap, 统一 sys.path 逻辑
|
|
27
|
+
# ⚠ 必须在 module docstring 之外! 旧版把这块包进 docstring → bootstrap 永不执行 → 跑死。
|
|
28
|
+
import os as _o, sys as _s
|
|
29
|
+
_f = _o.path.abspath(__file__)
|
|
30
|
+
for _ in range(10):
|
|
31
|
+
_f = _o.path.dirname(_f)
|
|
32
|
+
_cp = _o.path.join(_f, 'foundation')
|
|
33
|
+
if _o.path.isfile(_o.path.join(_cp, 'bootstrap.py')):
|
|
34
|
+
break
|
|
35
|
+
if _cp not in _s.path: _s.path.insert(0, _cp)
|
|
36
|
+
from bootstrap import setup; setup()
|
|
37
|
+
|
|
37
38
|
import json
|
|
38
39
|
import os
|
|
39
40
|
import sys
|
|
@@ -41,7 +42,6 @@ from datetime import datetime
|
|
|
41
42
|
|
|
42
43
|
# 路径自适应 (和兄弟脚本一致)
|
|
43
44
|
THIS_DIR = os.path.dirname(os.path.abspath(__file__))
|
|
44
|
-
sys.path.insert(0, os.path.join(THIS_DIR, '..'))
|
|
45
45
|
from foundation.core.paths import DATA_INDEX_DIR, BUILD_CACHE_DIR
|
|
46
46
|
|
|
47
47
|
|
|
@@ -1,17 +1,6 @@
|
|
|
1
1
|
# -*- coding: utf-8 -*-
|
|
2
2
|
"""build_relations.py — 从 trace-chain + code-api 推导关系边, 生成 relations.json。
|
|
3
3
|
|
|
4
|
-
# v3.0 路径自举: 引导到 common/bootstrap, 统一 sys.path 逻辑
|
|
5
|
-
import os as _o, sys as _s
|
|
6
|
-
_f = _o.path.abspath(__file__)
|
|
7
|
-
for _ in range(10):
|
|
8
|
-
_f = _o.path.dirname(_f)
|
|
9
|
-
_cp = _o.path.join(_f, 'foundation')
|
|
10
|
-
if _o.path.isfile(_o.path.join(_cp, 'bootstrap.py')):
|
|
11
|
-
break
|
|
12
|
-
if _cp not in _s.path: _s.path.insert(0, _cp)
|
|
13
|
-
from bootstrap import setup; setup()
|
|
14
|
-
|
|
15
4
|
产出 data/index/_build_cache/relations.json, 供 kg_duckdb 导入 edges 表,
|
|
16
5
|
让 kg.py feature 的端点/按钮数据非零, kg.py hop 多跳遍历有边可走。
|
|
17
6
|
|
|
@@ -32,13 +21,24 @@ from bootstrap import setup; setup()
|
|
|
32
21
|
- code-api.json: {endpoint: controller_file} (api_to_controller 直接用)
|
|
33
22
|
- test-assertions.json: tests (controller_to_tests 推导)
|
|
34
23
|
"""
|
|
24
|
+
# v3.0 路径自举: 引导到 foundation/bootstrap, 统一 sys.path 逻辑
|
|
25
|
+
# ⚠ 必须在 module docstring 之外! 旧版把这块包进 docstring → bootstrap 永不执行 → 跑死。
|
|
26
|
+
import os as _o, sys as _s
|
|
27
|
+
_f = _o.path.abspath(__file__)
|
|
28
|
+
for _ in range(10):
|
|
29
|
+
_f = _o.path.dirname(_f)
|
|
30
|
+
_cp = _o.path.join(_f, 'foundation')
|
|
31
|
+
if _o.path.isfile(_o.path.join(_cp, 'bootstrap.py')):
|
|
32
|
+
break
|
|
33
|
+
if _cp not in _s.path: _s.path.insert(0, _cp)
|
|
34
|
+
from bootstrap import setup; setup()
|
|
35
|
+
|
|
35
36
|
import json
|
|
36
37
|
import os
|
|
37
38
|
import sys
|
|
38
39
|
from datetime import datetime
|
|
39
40
|
|
|
40
41
|
THIS_DIR = os.path.dirname(os.path.abspath(__file__))
|
|
41
|
-
sys.path.insert(0, os.path.join(THIS_DIR, '..'))
|
|
42
42
|
from foundation.core.paths import DATA_INDEX_DIR, BUILD_CACHE_DIR
|
|
43
43
|
|
|
44
44
|
|
|
Binary file
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
"""context_cache.py — 按 REQ-id 缓存 context_pack/rag_search 结果(T1.3 跨命令上下文缓存)。
|
|
2
|
+
|
|
3
|
+
PRD→spec→code 链路里同一 REQ 反复拉同一份上下文(3 次 KG/MCP 往返)→ 缓存复用,省 token + 减反复搜。
|
|
4
|
+
|
|
5
|
+
存储: workspace/specs/{REQ}/.context-cache.json
|
|
6
|
+
结构: {query_hash: {"query":..., "text":..., "ts":...}}
|
|
7
|
+
TTL 默认 24h(PRD/spec 改动后用 invalidate(req) 显式失效)。
|
|
8
|
+
|
|
9
|
+
纯模块,无外部依赖;任何异常 fail-open(缓存坏了不影响主流程,调用方降级直拉)。
|
|
10
|
+
"""
|
|
11
|
+
import os
|
|
12
|
+
import json
|
|
13
|
+
import time
|
|
14
|
+
import hashlib
|
|
15
|
+
|
|
16
|
+
DEFAULT_TTL = 24 * 3600 # 24h
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def _repo_root():
|
|
20
|
+
"""从本文件向上找含 workspace/ 或 .qoder/ 的目录作仓库根。"""
|
|
21
|
+
d = os.path.dirname(os.path.abspath(__file__))
|
|
22
|
+
for _ in range(7):
|
|
23
|
+
if os.path.isdir(os.path.join(d, "workspace")) or os.path.isdir(os.path.join(d, ".qoder")):
|
|
24
|
+
return d
|
|
25
|
+
parent = os.path.dirname(d)
|
|
26
|
+
if parent == d:
|
|
27
|
+
break
|
|
28
|
+
d = parent
|
|
29
|
+
return os.getcwd()
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def _sanitize(req_id):
|
|
33
|
+
return "".join(c for c in str(req_id) if c.isalnum() or c in "-_") or "unknown"
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def cache_path(req_id, base=None):
|
|
37
|
+
base = base or _repo_root()
|
|
38
|
+
return os.path.join(base, "workspace", "specs", _sanitize(req_id), ".context-cache.json")
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def _load(path):
|
|
42
|
+
try:
|
|
43
|
+
with open(path, encoding="utf-8") as f:
|
|
44
|
+
data = json.load(f)
|
|
45
|
+
return data if isinstance(data, dict) else {}
|
|
46
|
+
except Exception:
|
|
47
|
+
return {}
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def _save(path, data):
|
|
51
|
+
try:
|
|
52
|
+
os.makedirs(os.path.dirname(path), exist_ok=True)
|
|
53
|
+
tmp = path + ".tmp"
|
|
54
|
+
with open(tmp, "w", encoding="utf-8") as f:
|
|
55
|
+
json.dump(data, f, ensure_ascii=False, indent=2)
|
|
56
|
+
os.replace(tmp, path) # 原子写
|
|
57
|
+
return True
|
|
58
|
+
except Exception:
|
|
59
|
+
return False
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def _qhash(query):
|
|
63
|
+
return hashlib.md5(query.strip().lower().encode("utf-8")).hexdigest()[:12]
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def get(req_id, query, ttl=DEFAULT_TTL, base=None):
|
|
67
|
+
"""命中且未过期 → 返回 text;否则 None。"""
|
|
68
|
+
data = _load(cache_path(req_id, base))
|
|
69
|
+
entry = data.get(_qhash(query))
|
|
70
|
+
if not entry:
|
|
71
|
+
return None
|
|
72
|
+
if ttl and (time.time() - float(entry.get("ts", 0))) > ttl:
|
|
73
|
+
return None
|
|
74
|
+
return entry.get("text")
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def put(req_id, query, text, base=None):
|
|
78
|
+
"""写一条缓存(覆盖同 query 旧值)。"""
|
|
79
|
+
path = cache_path(req_id, base)
|
|
80
|
+
data = _load(path)
|
|
81
|
+
data[_qhash(query)] = {"query": query, "text": text, "ts": time.time()}
|
|
82
|
+
return _save(path, data)
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def invalidate(req_id, base=None):
|
|
86
|
+
"""清整个 REQ 的缓存(PRD/spec 改动后调)。"""
|
|
87
|
+
return _save(cache_path(req_id, base), {})
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def stats(req_id, base=None):
|
|
91
|
+
"""缓存条数 + query 摘要(调试/观测命中率用)。"""
|
|
92
|
+
data = _load(cache_path(req_id, base))
|
|
93
|
+
return {"entries": len(data),
|
|
94
|
+
"queries": [str(e.get("query", ""))[:40] for e in data.values()]}
|
|
@@ -71,6 +71,7 @@ DISPATCH = {
|
|
|
71
71
|
# ── 回流平台 (引擎产出 → 平台 AI回流Tab) ──
|
|
72
72
|
# 用法: wlkj.py return prd <路径> <标题> | return prototype <路径> <功能名> [--platform web]
|
|
73
73
|
'return': 'domain/integration/return_to_platform.py',
|
|
74
|
+
'spec-upload': 'domain/integration/spec_upload.py',
|
|
74
75
|
# ── 设计 ──
|
|
75
76
|
'fill-prototype': 'domain/design/fill_prototype.py',
|
|
76
77
|
'design-doc': 'domain/design/gen_design_doc.py',
|
|
@@ -95,6 +96,7 @@ DISPATCH = {
|
|
|
95
96
|
'autotest-batch': 'validation/test/autotest_batch.py',
|
|
96
97
|
'benchmark': 'validation/test/benchmark.py',
|
|
97
98
|
# ── 环境 / 初始化 ──
|
|
99
|
+
'switch-project': 'domain/kg/switch_project.py',
|
|
98
100
|
'doctor': 'deployment/setup/init_doctor.py',
|
|
99
101
|
'init': 'deployment/setup/setup.py',
|
|
100
102
|
'install-qw': 'deployment/setup/install_qoderwork.py',
|
|
@@ -158,6 +160,95 @@ def _usage():
|
|
|
158
160
|
return '\n'.join(lines)
|
|
159
161
|
|
|
160
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
|
+
|
|
161
252
|
def main():
|
|
162
253
|
argv = sys.argv[1:]
|
|
163
254
|
if not argv or argv[0] in ('-h', '--help', 'help'):
|
|
@@ -170,6 +261,10 @@ def main():
|
|
|
170
261
|
# scripts/ 根 = __file__ 的上两级 (orchestration/ -> scripts/)
|
|
171
262
|
scripts_root = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
|
172
263
|
|
|
264
|
+
# T1.3 带 custom 逻辑的命令(如 ctx-cache 缓存)在透明转发前拦截
|
|
265
|
+
if cmd in SPECIAL:
|
|
266
|
+
return SPECIAL[cmd](rest, scripts_root)
|
|
267
|
+
|
|
173
268
|
script = DISPATCH.get(cmd)
|
|
174
269
|
if not script:
|
|
175
270
|
# Fallback: 当作脚本名在 scripts/ 下递归找 (支撑 'wlkj.py run <脚本>')
|