chore(hooks): 输出门禁三脚本更新 + 新增技能加载闸门 skill-load-guard.py
This commit is contained in:
1 parent
c8b514832f
commit
bda9b10161
4 files changed
+385
-10
No files matched your search
@@ -152,6 +152,79 @@ def segments(cmd):
|
||||
return [s.strip() for s in re.split(r'(?:&&|\|\||;|\n|\|)', cmd) if s.strip()]
|
||||
|
||||
|
||||
# ── 批量活检测(2026-09-15 用户选 A)─────────────────────────────
|
||||
# 要打断的是:**"一句话需求 → 上百次工具调用"** —— 每次调用都把「命令 + 结果」永久留在上下文里。
|
||||
# 实测(会话 7057685c):逐处改注释 ⇒ Edit 142 次、痕迹 13.4 万字符;若改用一个脚本 ⇒ 约 8 千字符(省 94%)。
|
||||
# 实现:`PreToolUse` 里给 Edit/Write/Bash 记账;到档位且**同质** ⇒ deny 一刀并给出替代做法(我改路子)。
|
||||
BATCH_N = 20
|
||||
BATCH_TOOLS = ('Edit', 'Write', 'Bash')
|
||||
|
||||
|
||||
def _batch_state(root):
|
||||
p = os.path.join(root, '.workbuddy', 'batch-guard.json')
|
||||
try:
|
||||
d = json.load(io.open(p, encoding='utf-8'))
|
||||
except Exception:
|
||||
d = {}
|
||||
return p, d
|
||||
|
||||
|
||||
def _homogeneous(tool, st):
|
||||
"""同质性:Edit ⇒ 最近 20 次里同一文件出现 ≥3 次(= 同一文件反复改);Bash ⇒ 命令都很短。"""
|
||||
if tool == 'Edit':
|
||||
paths = st.get('paths') or []
|
||||
if not paths:
|
||||
return False
|
||||
c = {}
|
||||
for x in paths[-20:]:
|
||||
c[x] = c.get(x, 0) + 1
|
||||
return max(c.values()) >= 3
|
||||
if tool == 'Bash':
|
||||
lens = st.get('lens') or []
|
||||
return bool(lens) and (sum(lens[-20:]) / max(len(lens[-20:]), 1)) < 300
|
||||
return True
|
||||
|
||||
|
||||
def batch_check(root, payload, log_fn):
|
||||
"""到档位 + 同质 ⇒ 返回提示串(并记账,每档只提示一次);否则 ''。"""
|
||||
sid = str(payload.get('session_id') or '')
|
||||
tool = payload.get('tool_name') or ''
|
||||
if not sid or tool not in BATCH_TOOLS:
|
||||
return ''
|
||||
p, d = _batch_state(root)
|
||||
st = d.setdefault(sid, {})
|
||||
st[tool] = int(st.get(tool, 0)) + 1
|
||||
n = st[tool]
|
||||
ti = payload.get('tool_input') or {}
|
||||
if tool == 'Edit':
|
||||
fp = str(ti.get('file_path') or '')
|
||||
if fp:
|
||||
st['paths'] = (st.get('paths') or [])[-40:] + [fp]
|
||||
elif tool == 'Bash':
|
||||
st['lens'] = (st.get('lens') or [])[-40:] + [len(str(ti.get('command') or ''))]
|
||||
fired = st.setdefault('fired', [])
|
||||
hit = ''
|
||||
if n in (BATCH_N, BATCH_N * 2, BATCH_N * 4) and ('%s@%d' % (tool, n)) not in fired and _homogeneous(tool, st):
|
||||
fired.append('%s@%d' % (tool, n))
|
||||
files = len(set(st.get('paths') or []))
|
||||
hit = (
|
||||
'🛠【批量活提示】本会话 `%s` 已调用 **%d 次**%s ⇒ 这更像**批量活**。\n'
|
||||
'✅ 改用**一个脚本一次做完**,只回「改了 N 处 / 失败 M 处」—— **别逐处调用**:'
|
||||
'逐次调用会把 N 份「命令 + 结果」**永久留在会话上下文里、之后每轮全量重发**。\n'
|
||||
'ℹ️ 实测参照:某会话逐处改 142 次 ⇒ 痕迹 **13.4 万字符**;改用一个脚本 ⇒ 约 **8 千字符(省 94%%)**。'
|
||||
'改完**统一验一次**即可,不必改一处验一处。'
|
||||
% (tool, n, ('(改到的文件只有 %d 个)' % files) if (tool == 'Edit' and files) else ''))
|
||||
log_fn(str(root), 'DENY|批量活提示 %s@%d' % (tool, n))
|
||||
try:
|
||||
if len(d) > 30:
|
||||
for k in list(d)[:-30]:
|
||||
d.pop(k, None)
|
||||
io.open(p, 'w', encoding='utf-8', newline='\n').write(json.dumps(d, ensure_ascii=False))
|
||||
except Exception:
|
||||
pass
|
||||
return hit
|
||||
|
||||
|
||||
def reason_for(cmd, hard=False):
|
||||
segs = segments(cmd)
|
||||
for core, pat, why, fix in RULES:
|
||||
@@ -186,7 +259,7 @@ def main():
|
||||
if (payload.get('hook_event_name') or '') != 'PreToolUse':
|
||||
return
|
||||
tool = payload.get('tool_name') or ''
|
||||
if tool not in ('Bash', 'Read'): # 覆盖**所有**会灌大输出的工具(2026-09-15 由用户点出补 Read)
|
||||
if tool not in ('Bash', 'Read', 'Edit', 'Write'): # Bash/Read=拦大输出;Edit/Write=批量活记账
|
||||
return
|
||||
if os.environ.get('DSH_OUTPUT_GUARD_OFF'):
|
||||
return
|
||||
@@ -195,6 +268,13 @@ def main():
|
||||
return
|
||||
except Exception:
|
||||
pass
|
||||
if tool in ('Edit', 'Write'): # 批量活检测:deny 一刀,换掉"上百次逐处调用"
|
||||
chk = batch_check(str(root), payload, log)
|
||||
if chk:
|
||||
_emit({'hookSpecificOutput': {'hookEventName': 'PreToolUse',
|
||||
'permissionDecision': 'deny',
|
||||
'permissionDecisionReason': chk}})
|
||||
return
|
||||
if tool == 'Read':
|
||||
if read_too_big(payload):
|
||||
fp = str(((payload.get('tool_input') or {}).get('file_path')) or '')
|
||||
|
||||
Reference in new issue
Block a user