diff --git a/git-guards/scripts/git-permission-guard.py b/git-guards/scripts/git-permission-guard.py index aea1488..22131a5 100755 --- a/git-guards/scripts/git-permission-guard.py +++ b/git-guards/scripts/git-permission-guard.py @@ -56,6 +56,21 @@ ("pr merge", "Merges PR - ONLY do when user EXPLICITLY requests"), ] +DENY_GH = [ + ("pr comment", ( + "gh pr comment creates top-level issue comments that cannot be resolved or tracked.\n" + "\n" + "For code review feedback, you MUST use review threads (line-specific, resolvable comments) instead.\n" + "\n" + "Use the documented thread workflows for creating review comments, replying, and resolving threads:\n" + " - github-workflows/skills/resolve-pr-threads/graphql-queries.md\n" + " - github-workflows/skills/resolve-pr-threads/rest-api-patterns.md\n" + "\n" + "These workflows create resolvable, line-specific review threads — the only acceptable way to post review\n" + "feedback on PRs." + )), +] + def deny(reason: str) -> None: """Output deny decision and exit.""" @@ -134,13 +149,21 @@ def main(): else: subcommand = command[3:] if command.startswith("gh ") else "" + sub_tokens = subcommand.split() + + # Check DENY_GH patterns (token prefix match on gh subcommand) + if is_gh: + for pattern, reason in DENY_GH: + tokens = pattern.split() + if tokens and sub_tokens[:len(tokens)] == tokens: + deny(reason) + # Check ASK patterns - use word boundaries to avoid false matches # (e.g., "merge" shouldn't match "emergency") patterns = ASK_GIT if is_git else ASK_GH for cmd, risk in patterns: # Match as exact token sequence at start of subcommand cmd_tokens = cmd.split() - sub_tokens = subcommand.split() if len(sub_tokens) >= len(cmd_tokens) and sub_tokens[:len(cmd_tokens)] == cmd_tokens: ask(command, risk)