@hupan56/wlkj 3.3.0 → 3.3.2
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 +29 -29
- package/templates/.qoder/.runtime/hook-errors.log +4 -0
- package/templates/qoder/commands/optional/wl-status.md +2 -0
- package/templates/qoder/commands/wl-code.md +45 -10
- package/templates/qoder/commands/wl-init.md +129 -129
- package/templates/qoder/commands/wl-prd.md +27 -12
- package/templates/qoder/commands/wl-search.md +32 -0
- package/templates/qoder/commands/wl-task.md +8 -4
- package/templates/qoder/commands/wl-test.md +4 -0
- package/templates/qoder/hooks/post-tool-use.py +31 -1
- package/templates/qoder/hooks/pre-tool-use.py +136 -0
- package/templates/qoder/hooks/session-start.py +365 -365
- package/templates/qoder/scripts/capability/__pycache__/present_html.cpython-39.pyc +0 -0
- package/templates/qoder/scripts/capability/__pycache__/registry.cpython-39.pyc +0 -0
- package/templates/qoder/scripts/capability/caps/__init__.py +4 -4
- package/templates/qoder/scripts/capability/caps/__pycache__/__init__.cpython-39.pyc +0 -0
- package/templates/qoder/scripts/capability/caps/__pycache__/notify.cpython-39.pyc +0 -0
- package/templates/qoder/scripts/capability/caps/__pycache__/sandbox.cpython-39.pyc +0 -0
- package/templates/qoder/scripts/capability/caps/notify.py +64 -0
- package/templates/qoder/scripts/capability/caps/sandbox.py +38 -0
- package/templates/qoder/scripts/capability/present_html.py +68 -0
- package/templates/qoder/scripts/capability/registry.py +6 -2
- package/templates/qoder/scripts/capability/registry_mcp.py +312 -314
- package/templates/qoder/scripts/domain/integration/__pycache__/return_to_platform.cpython-39.pyc +0 -0
- package/templates/qoder/scripts/domain/integration/return_to_platform.py +394 -392
- package/templates/qoder/scripts/domain/integration/spec_upload.py +208 -209
- package/templates/qoder/scripts/domain/kg/switch_project.py +158 -159
- package/templates/qoder/scripts/engine/poller.py +219 -219
- package/templates/qoder/scripts/validation/metrics/__pycache__/present_board.cpython-39.pyc +0 -0
- package/templates/qoder/scripts/validation/metrics/present_board.py +180 -0
- package/templates/qoder/settings.json +10 -0
- package/templates/qoder/scripts/domain/kg/extract/extract.py.bak +0 -430
|
@@ -116,7 +116,7 @@ def _auto_learn_from_prd(prd_path):
|
|
|
116
116
|
try:
|
|
117
117
|
title = os.path.basename(prd_path).replace('.md', '')
|
|
118
118
|
# 读 mcp_config 拿 token
|
|
119
|
-
cfg_path = os.path.join(BASE, '
|
|
119
|
+
cfg_path = os.path.join(BASE, '.qoder', 'mcp_config.json')
|
|
120
120
|
if not os.path.isfile(cfg_path):
|
|
121
121
|
return
|
|
122
122
|
import json as _j
|
|
@@ -143,6 +143,32 @@ def _auto_learn_from_prd(prd_path):
|
|
|
143
143
|
pass # 铁律:静默
|
|
144
144
|
|
|
145
145
|
|
|
146
|
+
def _emit_ask_before_return(kind, path):
|
|
147
|
+
"""PRD/原型写完 → 注入"先问用户再回流"指令(用户硬要求:上传前必须先问)。
|
|
148
|
+
|
|
149
|
+
hook 无法弹窗等 Y/n(工具执行后跑、输出即退),但能**确定性注入指令**让 AI
|
|
150
|
+
每次必问、默认不上传。stdout 注入会话上下文,AI 看到后必须先问用户。
|
|
151
|
+
"""
|
|
152
|
+
name = os.path.basename(path)
|
|
153
|
+
if kind == 'prd':
|
|
154
|
+
cmd = '$PY "$R/.qoder/scripts/orchestration/wlkj.py" return prd "%s" "<标题>"' % path
|
|
155
|
+
what = 'PRD'
|
|
156
|
+
else:
|
|
157
|
+
cmd = '$PY "$R/.qoder/scripts/orchestration/wlkj.py" return prototype "%s" "<功能>"' % path
|
|
158
|
+
what = '原型(HTML)'
|
|
159
|
+
msg = (
|
|
160
|
+
"\n⚠️ 【回流前必须先问用户】刚写了 %s:%s\n"
|
|
161
|
+
" → 必须先问用户:「要把这份 %s 回流到平台吗?」\n"
|
|
162
|
+
" → 用户【明确同意】(说「回流 / 上传 / 确认」)才执行:%s\n"
|
|
163
|
+
" → 用户拒绝 / 未明确同意 → 只留本地,绝不默认上传。\n"
|
|
164
|
+
" (宿主确定性要求:不问就传 = 违规;这是用户明确要的确认环节)\n"
|
|
165
|
+
) % (what, name, what, cmd)
|
|
166
|
+
try:
|
|
167
|
+
sys.stdout.write(msg)
|
|
168
|
+
except Exception:
|
|
169
|
+
pass
|
|
170
|
+
|
|
171
|
+
|
|
146
172
|
def _append_event(event, data):
|
|
147
173
|
"""best-effort append 一条事件到当前 dev 的 feedback.jsonl。避开 learn import 断链, 直接写文件。"""
|
|
148
174
|
try:
|
|
@@ -198,6 +224,8 @@ def main():
|
|
|
198
224
|
_append_event('prd_written', {'prd': os.path.basename(p), 'tool': tool_name})
|
|
199
225
|
# ★ 宿主能力接入:PRD 写完 → 自动调 MCP learn 沉淀业务规则
|
|
200
226
|
_auto_learn_from_prd(p)
|
|
227
|
+
# ★ 用户硬要求:PRD 回流平台前必须先问(不问就传=违规)
|
|
228
|
+
_emit_ask_before_return('prd', p)
|
|
201
229
|
if plow.endswith('task.json'):
|
|
202
230
|
_append_event('task_touched', {'task_json': os.path.basename(os.path.dirname(p)), 'tool': tool_name})
|
|
203
231
|
# ★ 宿主能力接入:代码文件改完 → 自动记 code_changed(知识层下次检测过时用)
|
|
@@ -205,6 +233,8 @@ def main():
|
|
|
205
233
|
_append_event('code_changed', {'file': os.path.basename(p), 'path': plow[:200]})
|
|
206
234
|
# v3.1 软提示: prototype HTML 写完但缺来源锚定 → stderr 提醒(不阻断)
|
|
207
235
|
if 'prototype' in os.path.basename(plow) and plow.endswith('.html'):
|
|
236
|
+
# ★ 用户硬要求:原型回流平台前必须先问(不问就传=违规)
|
|
237
|
+
_emit_ask_before_return('prototype', p)
|
|
208
238
|
try:
|
|
209
239
|
if os.path.isfile(p):
|
|
210
240
|
with open(p, encoding='utf-8', errors='ignore') as _f:
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
"""pre-tool-use.py - PreToolUse hook: 改码前 get_impact 影响面 priming(#16 / CLAUDE.md 铁律#1 宿主级强制)
|
|
4
|
+
|
|
5
|
+
吸收宿主确定性 hook 能力:把"改代码前必调 get_impact"从"靠 AI 记得(老忘→裸改崩一片)"
|
|
6
|
+
升级为"宿主在 Write/Edit/MultiEdit 落到代码文件前,确定性提取符号 + 强提醒"。
|
|
7
|
+
|
|
8
|
+
做法(确定性、零外部依赖、不依赖后端在线):
|
|
9
|
+
1. 解析 stdin {tool_name, tool_input:{file_path}}
|
|
10
|
+
2. 只对代码文件(*.py/java/vue/js/ts/go/kt 等,非 .md/.json/.txt)触发
|
|
11
|
+
3. 正则提取文件里的 class/def/function 名(要改的符号)
|
|
12
|
+
4. 输出影响面 advisory 到会话:列符号 + 强制提醒改前调 get_impact(entity=<主符号>)
|
|
13
|
+
实际 get_impact 由工作流 cap.mcp.call 跑(robust,处理 token/SSE)。
|
|
14
|
+
|
|
15
|
+
铁律:
|
|
16
|
+
- 永远 exit 0(advisory 不阻断;阻断会卡死工作流)
|
|
17
|
+
- 失败 try/except 静默(hook 崩绝不阻塞会话)
|
|
18
|
+
- 非 .py/.java 等代码文件 / 非编辑类工具 → 静默放行(不滥提醒)
|
|
19
|
+
|
|
20
|
+
stdin 字段名官方未公开,兼容 tool_name/tool_input 等多种写法(同 post-tool-use.py)。
|
|
21
|
+
"""
|
|
22
|
+
import json
|
|
23
|
+
import os
|
|
24
|
+
import re
|
|
25
|
+
import sys
|
|
26
|
+
|
|
27
|
+
if sys.platform == 'win32':
|
|
28
|
+
try:
|
|
29
|
+
sys.stdout.reconfigure(encoding='utf-8')
|
|
30
|
+
sys.stderr.reconfigure(encoding='utf-8')
|
|
31
|
+
except Exception:
|
|
32
|
+
pass
|
|
33
|
+
|
|
34
|
+
NL = chr(10)
|
|
35
|
+
BASE = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
|
36
|
+
HOOK_ERRORS_LOG = os.path.join(BASE, '.qoder', '.runtime', 'hook-errors.log')
|
|
37
|
+
|
|
38
|
+
# 触发的工具 + 代码文件后缀
|
|
39
|
+
_EDIT_TOOLS = {"Write", "Edit", "MultiEdit", "write", "edit", "multi_edit"}
|
|
40
|
+
_CODE_EXT = {".py", ".java", ".vue", ".js", ".ts", ".tsx", ".jsx", ".go", ".kt", ".scala", ".cs", ".c", ".cpp", ".h", ".rs"}
|
|
41
|
+
|
|
42
|
+
# 符号提取正则(class/def/function/interface)
|
|
43
|
+
_SYM_RE = re.compile(
|
|
44
|
+
r"^\s*(?:export\s+)?(?:public|private|protected|static|final|abstract|async|\s)*"
|
|
45
|
+
r"(?:class|def|function|interface|struct|enum|object)\s+([A-Za-z_][A-Za-z0-9_]*)",
|
|
46
|
+
re.MULTILINE,
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def _log_err(line):
|
|
51
|
+
from datetime import datetime
|
|
52
|
+
try:
|
|
53
|
+
os.makedirs(os.path.dirname(HOOK_ERRORS_LOG), exist_ok=True)
|
|
54
|
+
with open(HOOK_ERRORS_LOG, 'a', encoding='utf-8') as f:
|
|
55
|
+
f.write('[{}] pre-tool-use: {}{}'.format(
|
|
56
|
+
datetime.now().strftime('%Y-%m-%d %H:%M:%S'), str(line)[:200], NL))
|
|
57
|
+
except Exception:
|
|
58
|
+
pass
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def _read_stdin():
|
|
62
|
+
try:
|
|
63
|
+
raw = sys.stdin.read()
|
|
64
|
+
if raw and raw.strip():
|
|
65
|
+
return json.loads(raw)
|
|
66
|
+
except Exception as e:
|
|
67
|
+
_log_err("stdin parse: %s" % str(e)[:120])
|
|
68
|
+
return {}
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def _is_code(path):
|
|
72
|
+
_, ext = os.path.splitext(path.lower())
|
|
73
|
+
return ext in _CODE_EXT
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def _extract_symbols(path, limit=8):
|
|
77
|
+
"""从文件提取 class/def/function 名(要改的符号)。best-effort,读不到返空。"""
|
|
78
|
+
try:
|
|
79
|
+
if not os.path.isfile(path):
|
|
80
|
+
# 相对路径 → 试 BASE 下定位
|
|
81
|
+
cand = path
|
|
82
|
+
if not os.path.isabs(path):
|
|
83
|
+
for root in (BASE, os.getcwd()):
|
|
84
|
+
c = os.path.join(root, path)
|
|
85
|
+
if os.path.isfile(c):
|
|
86
|
+
cand = c
|
|
87
|
+
break
|
|
88
|
+
if not os.path.isfile(cand):
|
|
89
|
+
return []
|
|
90
|
+
path = cand
|
|
91
|
+
with open(path, 'r', encoding='utf-8', errors='replace') as f:
|
|
92
|
+
text = f.read()[:200000] # 限 200K 防超大文件慢
|
|
93
|
+
return _SYM_RE.findall(text)[:limit]
|
|
94
|
+
except Exception as e:
|
|
95
|
+
_log_err("extract %s: %s" % (path, str(e)[:80]))
|
|
96
|
+
return []
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def main():
|
|
100
|
+
data = _read_stdin()
|
|
101
|
+
tool = data.get("tool_name") or data.get("tool") or ""
|
|
102
|
+
tinput = data.get("tool_input") or data.get("input") or {}
|
|
103
|
+
path = tinput.get("file_path") or tinput.get("path") or ""
|
|
104
|
+
|
|
105
|
+
# 非编辑类工具 / 非代码文件 → 静默放行
|
|
106
|
+
if tool not in _EDIT_TOOLS or not path or not _is_code(path):
|
|
107
|
+
sys.exit(0)
|
|
108
|
+
|
|
109
|
+
syms = _extract_symbols(path)
|
|
110
|
+
rel = path
|
|
111
|
+
try:
|
|
112
|
+
rel = os.path.relpath(path, BASE)
|
|
113
|
+
except Exception:
|
|
114
|
+
pass
|
|
115
|
+
|
|
116
|
+
lines = []
|
|
117
|
+
lines.append("⚠️ **改码前影响面 priming**(CLAUDE.md 铁律#1:改代码前必调 get_impact)")
|
|
118
|
+
lines.append("即将编辑代码文件:`%s`" % rel)
|
|
119
|
+
if syms:
|
|
120
|
+
lines.append("含符号 %d 个:%s" % (len(syms), ", ".join("`%s`" % s for s in syms)))
|
|
121
|
+
lines.append("→ **改前必调** `cap.mcp.call('get_impact', {entity: '%s', depth: 2})` 看 upstream(谁依赖我)/downstream(我依赖谁)。" % syms[0])
|
|
122
|
+
lines.append(" 影响面大 / risk level≥high → 先提示风险再动手;高风险裸改须拦停问用户。")
|
|
123
|
+
else:
|
|
124
|
+
lines.append("(未提取到已知符号,可能是新文件)→ 仍建议改前 rag_search 找相关实现 + get_impact 看上下游。")
|
|
125
|
+
|
|
126
|
+
# advisory 输出到 stdout(注入会话上下文),exit 0 不阻断
|
|
127
|
+
sys.stdout.write(NL.join(lines) + NL)
|
|
128
|
+
sys.exit(0)
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
if __name__ == "__main__":
|
|
132
|
+
try:
|
|
133
|
+
main()
|
|
134
|
+
except Exception as e:
|
|
135
|
+
_log_err("fatal: %s" % str(e)[:150])
|
|
136
|
+
sys.exit(0) # 永不阻塞
|