Skip to content
Merged
Show file tree
Hide file tree
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
41 changes: 33 additions & 8 deletions bin/mind_commit_guard.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,33 @@ def _mind_root(command: str, cwd: str) -> Path | None:
return None


def _clauses(command: str):
"""Token-level clause split that respects quoting (v1.1).

v1.0 split clauses with a regex over the raw text, which cut inside
quoted commit messages and heredoc bodies — its first live firing was a
false positive on its own author's closeout command, minutes after
deployment. shlex with punctuation_chars keeps `&&`/`;`/`|` as tokens
while quoted strings (commit messages, gh comment bodies) stay single
tokens that cannot leak trigger words or swallow the `--`.
"""
lex = shlex.shlex(command, posix=True, punctuation_chars=";&|")
lex.whitespace_split = True
clause: list[str] = []
try:
for tok in lex:
if tok and set(tok) <= set(";&|"):
if clause:
yield clause
clause = []
else:
clause.append(tok)
except ValueError:
return # unparseable (heredocs, unbalanced quotes) — fail open
if clause:
yield clause


def check_command(command: str, cwd: str = "") -> str | None:
"""Return a denial reason, or None to allow."""
if "PYAUTO_SKIP_MIND_GUARD=1" in command:
Expand All @@ -78,16 +105,14 @@ def check_command(command: str, cwd: str = "") -> str | None:
return None
root = _mind_root(command, cwd)

# Examine each `git ... commit ...` clause in the (possibly compound) command.
for clause in re.split(r"&&|\|\||;", command):
if not re.search(r"\bgit\b.*\bcommit\b", clause):
# Examine each clause of the (possibly compound) command at token level.
for tokens in _clauses(command):
if "git" not in tokens or "commit" not in tokens:
continue
if tokens.index("git") > tokens.index("commit"):
continue
if "--amend" in clause or "--dry-run" in clause:
if "--amend" in tokens or "--dry-run" in tokens:
continue
try:
tokens = shlex.split(clause)
except ValueError:
return None # unparseable — fail open
if "--" not in tokens:
return (
"PyAutoMind is a SHARED checkout: concurrent sessions stage into "
Expand Down
34 changes: 34 additions & 0 deletions tests/test_mind_commit_guard.py
Original file line number Diff line number Diff line change
Expand Up @@ -77,3 +77,37 @@ def test_amend_and_dry_run_are_exempt(tmp_path):
mind = tmp_path / "PyAutoMind"
mind.mkdir()
assert check_command(f'git -C {mind} commit --amend --no-edit') is None


# --- v1.1: the two live false positives from the guard's first hour ------------

def test_gh_comment_prose_does_not_trigger():
# First live firing: a `gh issue comment` whose BODY prose mentioned the
# trigger words. Quoted bodies are single tokens; no git-commit clause.
cmd = (
'gh issue comment 130 --repo X --body "the Mind commit guard denies '
'bare commits in the shared PyAutoMind checkout via git" '
"&& echo done"
)
assert check_command(cmd) is None


def test_semicolon_inside_commit_message_keeps_pathspecs(tmp_path):
# Second live firing: a `;` INSIDE the quoted -m message made v1.0's raw
# regex split strand the `--` in the next pseudo-clause.
mind = tmp_path / "PyAutoMind"
mind.mkdir()
(mind / "active.md").write_text("x")
cmd = (
f'cd {mind} && git pull --ff-only -q && '
f'git commit -q -m "prompt: task complete; all phases complete" '
f"-- active.md && git push -q origin main"
)
assert check_command(cmd) is None


def test_bare_commit_still_denied_in_compound_with_quotes(tmp_path):
mind = tmp_path / "PyAutoMind"
mind.mkdir()
cmd = f'cd {mind} && git commit -q -m "msg; with semicolon" && echo ok'
assert check_command(cmd) is not None