@hupan56/wlkj 3.3.0 → 3.3.1

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 (32) hide show
  1. package/package.json +2 -2
  2. package/templates/.qoder/.runtime/hook-errors.log +4 -0
  3. package/templates/qoder/commands/optional/wl-status.md +2 -0
  4. package/templates/qoder/commands/wl-code.md +45 -10
  5. package/templates/qoder/commands/wl-init.md +129 -129
  6. package/templates/qoder/commands/wl-prd.md +27 -12
  7. package/templates/qoder/commands/wl-search.md +32 -0
  8. package/templates/qoder/commands/wl-task.md +8 -4
  9. package/templates/qoder/commands/wl-test.md +4 -0
  10. package/templates/qoder/hooks/post-tool-use.py +31 -1
  11. package/templates/qoder/hooks/pre-tool-use.py +136 -0
  12. package/templates/qoder/hooks/session-start.py +365 -365
  13. package/templates/qoder/scripts/capability/__pycache__/present_html.cpython-39.pyc +0 -0
  14. package/templates/qoder/scripts/capability/__pycache__/registry.cpython-39.pyc +0 -0
  15. package/templates/qoder/scripts/capability/caps/__init__.py +4 -4
  16. package/templates/qoder/scripts/capability/caps/__pycache__/__init__.cpython-39.pyc +0 -0
  17. package/templates/qoder/scripts/capability/caps/__pycache__/notify.cpython-39.pyc +0 -0
  18. package/templates/qoder/scripts/capability/caps/__pycache__/sandbox.cpython-39.pyc +0 -0
  19. package/templates/qoder/scripts/capability/caps/notify.py +64 -0
  20. package/templates/qoder/scripts/capability/caps/sandbox.py +38 -0
  21. package/templates/qoder/scripts/capability/present_html.py +68 -0
  22. package/templates/qoder/scripts/capability/registry.py +6 -2
  23. package/templates/qoder/scripts/capability/registry_mcp.py +4 -6
  24. package/templates/qoder/scripts/domain/integration/__pycache__/return_to_platform.cpython-39.pyc +0 -0
  25. package/templates/qoder/scripts/domain/integration/return_to_platform.py +9 -6
  26. package/templates/qoder/scripts/domain/integration/spec_upload.py +1 -2
  27. package/templates/qoder/scripts/domain/kg/switch_project.py +5 -6
  28. package/templates/qoder/scripts/engine/poller.py +1 -1
  29. package/templates/qoder/scripts/validation/metrics/__pycache__/present_board.cpython-39.pyc +0 -0
  30. package/templates/qoder/scripts/validation/metrics/present_board.py +180 -0
  31. package/templates/qoder/settings.json +10 -0
  32. package/templates/qoder/scripts/domain/kg/extract/extract.py.bak +0 -430
@@ -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) # 永不阻塞