#!/usr/bin/env python3"""無人実行プロンプトの点検。許可ダイアログを出すツールが「使う指示」として書かれていないかを数える。 - 使う指示の疑い(suspect) がある -> exit 1(実行前に直す) - 冒頭の禁止ヘッダが無いだけ -> 警告のみ(exit 0)"""import json, pathlib, re, sysDIALOG_WORDS = ["AskUserQuestion", "request_cowork_directory", "request_access", "allow_cowork_file_delete", "computer-use"]FILE_TOOLS = re.compile(r"(?<![A-Za-z_])(Read|Write|Edit)(?![a-z_])")# 語幹で照合する。「使わない/使わず」を同じ語幹 使わ で拾うため。# 「絶対」は強調であって否定ではないので入れない(「絶対に使う」を禁止と誤認する)。NEGATION = re.compile(r"禁止|呼ばな|呼び出さな|使わ|使用しな|してはなら|回避|" r"[Nn]ever|[Dd]o not|[Dd]on't")BAN_HEADER = "権限ダイアログ禁止"def is_task_prompt(text: str) -> bool: m = re.match(r"---\n(.*?)\n---", text, re.S) return bool(m and re.search(r"^name:", m.group(1), re.M))def scan_text(name: str, text: str) -> dict: res = {"file": name, "ban_header": BAN_HEADER in text, "suspects": [], "bans": 0} for no, line in enumerate(text.splitlines(), 1): if not (any(w in line for w in DIALOG_WORDS) or FILE_TOOLS.search(line)): continue if NEGATION.search(line): res["bans"] += 1 else: res["suspects"].append((no, line.strip()[:90])) return resdef main(root: str) -> int: rows = [] for p in sorted(pathlib.Path(root).glob("*.md")): t = p.read_text(encoding="utf-8", errors="replace") if is_task_prompt(t): rows.append(scan_text(p.name, t)) fail = [r for r in rows if r["suspects"]] warn = [r for r in rows if not r["ban_header"]] print(json.dumps({"task_prompts": len(rows), "fail": len(fail), "warn_no_header": len(warn)}, ensure_ascii=False)) for r in fail: for no, l in r["suspects"][:3]: print(f'FAIL {r["file"]}:L{no}: {l}') for r in warn: print(f'warn {r["file"]}: 冒頭の禁止ヘッダなし(本文の禁止文は {r["bans"]} 行)') return 1 if fail else 0if __name__ == "__main__": sys.exit(main(sys.argv[1]))