Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 24 additions & 1 deletion git-guards/scripts/git-permission-guard.py
Original file line number Diff line number Diff line change
Expand Up @@ -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."""
Expand Down Expand Up @@ -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)

Expand Down
Loading