Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
15 commits
Select commit Hold shift + click to select a range
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
9 changes: 9 additions & 0 deletions .claude/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,15 @@
"command": "~/.local/bin/graphify hook-guard read"
}
]
},
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "\"$CLAUDE_PROJECT_DIR\"/tools/agent-guard/git_guard.py"
}
]
}
]
},
Expand Down
9 changes: 9 additions & 0 deletions .codex/hooks.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,15 @@
"command": "~/.local/bin/graphify hook-check"
}
]
},
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "\"$(git rev-parse --show-toplevel)\"/tools/agent-guard/git_guard.py"
}
]
}
]
}
Expand Down
4 changes: 3 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -90,13 +90,15 @@ jobs:
steps:
- name: Check out repository
uses: actions/checkout@v7
# Pure Node, no toolchain setup needed.
# Node and the runner's Python 3, no toolchain setup needed.
- name: Run the classifier self-tests
run: node --test .github/scripts/changed-paths.test.mjs
- name: Run the image-scan issue logic self-tests
run: node --test .github/scripts/image-scan-issues.test.mjs
- name: Run the release-line decision self-tests
run: node --test .github/scripts/release-line.test.mjs
- name: Run the agent git-guard table test
run: python3 tools/agent-guard/git_guard_test.py

changes:
name: Classify the changed paths
Expand Down
1 change: 1 addition & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,7 @@ CI rejects a PR when a **production** dependency has a known **high+** advisory:

- `origin` = GitHub (`github.com/mforce/cluckwork`); `gitea` = backup mirror. Use `gh` for PRs.
- **`main` is protected** — work on a branch, push it, and open a PR; never commit to `main`. Use `feat/…`, `chore/…`, `docs/…`, or `spec/…` branch names. PRs squash-merge.
- **A hook refuses forbidden git commands from agents.** `tools/agent-guard/git_guard.py` runs before every Bash call in Claude Code (`.claude/settings.json`) and Codex (`.codex/hooks.json`). A command that mentions `git push`, `git commit` or a PR merge, or could spell one through `$`, a backtick or a glob, must be one simple command, optionally after `cd <dir> &&`; chains, pipes, subshells, `$(…)`, variables and wrappers are refused, not parsed. In that form it blocks `gh pr merge`, a non-GET `gh api` call to `pulls/<n>/merge`, a literal `mergePullRequest` GraphQL call, `gh alias set`, `src:dst` and `+` refspecs, `--force`/`-f`/`--mirror`/`--all`/`--prune`, pushes whose source or configured destination is `main`, and `git commit` on `main`. It does not see gh or git aliases defined earlier, a merge query read from a file, commits made by merge, rebase, cherry-pick or pull, a push destination the remote resolves as a symbolic ref to `main`, or a Codex `workdir`. `git_guard_test.py` is its table test in CI. Codex skips a new or changed hook until it is trusted in `/hooks`.
- **The PR title is the release note.** It is always the squashed commit subject (`squash_merge_commit_title=PR_TITLE`), which release-please parses for the changelog. A typo or non-conventional prefix silently drops the entry.
- **The version comes from the branch, not from commit types (#351).** Every release from `main` bumps the middle digit (`"versioning": "always-bump-minor"` in `release-please-config.json`); every release from a hotfix line `release/vX.Y.x` bumps the last digit (`always-bump-patch` in that line's own copy). The first digit moves only by hand, with `Release-As: 2.0.0` added **at merge time** (`gh pr merge <N> --squash --body "Release-As: 2.0.0"` or the squash dialog's extended description): squash commits here carry the PR title and an empty body (`squash_merge_commit_message=BLANK`), so a footer in a branch commit or the PR description is dropped. Commit types choose only the changelog section. A `!` in the PR title marks a breaking change there and moves no digit; a `BREAKING CHANGE:` footer is dropped like any other body text.
- **A commit-body parse error drops the whole commit** (no changelog entry, green run). A body reaches `main` only as a description typed at merge time; in one, never start a line with `word(` that has another `(` inside it. `.githooks/commit-msg` checks branch commits, which no longer reach `main`, and no local hook sees a **PR title** or merge-time text.
Expand Down
Loading