SpecSheriff
rule · _guidelines/tools/3 · s-7unxytshhc4md54

Setting a repository up and taking SpecSheriff out of it

Checkedconfirmed by its owner

sheriff init sets a repository up for the SpecSheriff tools. It writes the git hooks into .githooks/ (§s-4mej6ccn7xu3d3p); stages with the executable mode, 100755, each hook there that holds what it would write and that git's index does not already hold with that mode, naming each one, or, where it cannot stage a hook, names it and says how to stage it executable; and turns them on.

For the agent --agent names, claude or codex, claude where the option is not given, sheriff init adds the marketplace and the plugin to .claude/settings.json as a project declares them (§s-zh2vcr3xjumppg5), keeping every other key there, or writes the agent's hooks for Codex into .codex/hooks.json (§s-n76wt2bkqdmh78x), keeping every other hook there. It refuses another name, naming the two it takes.

Where they are missing, sheriff init writes a sheriff.config.json and a specs/_guidelines/project.json naming as the specification's owner the email git is configured with. It replaces a hook or a declaration that holds what an earlier release of sheriff init wrote, naming each, and no other file that differs from what it would write, naming each one instead; so run again it changes nothing. A hook or a declaration holds what an earlier release wrote where, line endings aside, it equals one of the texts the earlier releases wrote, which the SpecSheriff tools keep.

sheriff remove takes out of a repository what sheriff init writes outside specs/, for both agents: each git hook, the marketplace and the plugin in .claude/settings.json and the agent's hooks for Codex in .codex/hooks.json, where each holds what sheriff init writes now or wrote in an earlier release; core.hooksPath where it names .githooks; a key left holding nothing, a file left holding no key, and .githooks/, .claude/ or .codex/ where that leaves it empty; and sheriff.config.json, whatever it holds. It leaves each hook and declaration that differs, naming it, and leaves specs/ as it is, naming it. It stages nothing, and run again it changes nothing.

Why

On a system whose files keep an executable bit, git runs a hook only where its file has the bit, and a clone checks a file out with the mode its commit records. Git takes that mode from the file when it stages it where files keep the bit, and records it as not executable where they keep none, as on Windows, where git runs a hook whatever its mode; so a hook set up there would reach the clones unable to run, and nothing on the machine that set it up would show it. A mode reaches the clones only staged and committed, and git stages a mode only with the file's content, so sheriff init stages the hooks it holds and nothing else: what else a commit holds stays its author's choice.

Claude Code reads its plugin from .claude/settings.json and Codex its hooks from .codex/hooks.json, so sheriff init writes for the agent the project is set up with, and a project moving to another agent runs it again for that one. A hook or a declaration written by an earlier release is the tools' own, so it is replaced; one that differs otherwise was changed by hand and is the project's.

sheriff remove takes out what sheriff init writes and no more. sheriff.config.json goes whatever it holds, because a repository holding it is one the plugins act in (§s-zh2vcr3xjumppg5) and its settings serve only the tools; specs/ stays, being the project's own content. What sheriff remove takes out is in git's history, and what a commit holds stays its author's choice, so it stages nothing.

The links it makes

The links made to it

The decisions taken on it

2026-11-02 · textual
Problem

sheriff init made the hooks executable with chmod, which git on Windows does not see, so a project set up there committed them as not executable, and a clone on Linux or macOS ran none of its checks, git skipping each hook with a hint: the worked example's recorded run went through there unrefused.

Ruling

The owner: sheriff init stages each hook it holds with the executable mode where git's index does not already hold it so, naming each, and nothing else, and where it cannot stage a hook says how to.

The tests bound to it