Metadata-Version: 2.4
Name: edit-guard
Version: 0.2.0
Summary: A Claude Code hook combo: block stale concurrent edits (pre-edit) + inject recorded decisions into new sessions (session-start)
Author: hao li
License: MIT
Project-URL: Homepage, https://github.com/hahahahahahahahah6/edit-guard
Keywords: claude-code,hooks,concurrency,developer-tools,agents
Requires-Python: >=3.9
Description-Content-Type: text/markdown
License-File: LICENSE
Dynamic: license-file

# edit-guard

A tiny [Claude Code](https://docs.anthropic.com/en/docs/claude-code) hook that
stops one agent session from editing a file another session changed
underneath it.

The failure mode, quoted from a real-world report: *"With several agent
sessions on one repository, session A reads `createSession`, session B
changes it, and A then edits on the old assumption."* edit-guard watches every
`Read`/`Edit`/`Write` through the hook, remembers what each session last saw,
and blocks the write when the file moved on without you — telling the agent to
re-read first.

Zero dependencies. Python standard library only. Everything stays local.

## Two halves: block stale edits, inject old decisions

edit-guard is one tool with two complementary moves — defense and offense:

- **Defense (pre-edit):** the `PreToolUse`/`PostToolUse` hook described above
  blocks a session from writing a file another session changed underneath it.
- **Offense (session-start):** a new session re-reads the project's recorded
  architecture decisions at startup, so it doesn't violate choices made in a
  previous session. This half is inspired by uehara's ADR hook idea: at
  session start, re-read the ADRs instead of re-litigating them.

### SessionStart hook setup

`edit-guard inject --session-start` prints a compact markdown summary of your
project's `docs/adr/*.md` (or `adr/`) and `DECISIONS.md` — one bullet per
decision: title, status, and the gist of the `## Decision` section. Claude
Code adds a `SessionStart` hook's stdout to the session context automatically,
so merge this into `~/.claude/settings.json`:

```json
{
  "hooks": {
    "SessionStart": [
      {
        "hooks": [
          { "type": "command", "command": "edit-guard inject --session-start" }
        ]
      }
    ]
  }
}
```

Tuning flags:

- `--adr-dir DIR` — use this directory instead of `docs/adr`/`adr`
- `--decisions-file FILE` — use this file instead of `./DECISIONS.md`
  (pass an empty string to disable it)
- `--max-chars N` — cap the output size (default 4000)

With no decision files present, the command prints nothing and exits 0 —
a repo without ADRs gets no noise in its session context.

## How it differs

The heavyweight answers to multi-session coordination are session buses and
file-reservation servers (e.g. a worktree claim guard: a Mod-style,
consultative tool — it asks who owns what, and never refuses a write).
edit-guard takes the opposite stance on both axes:

- **Refusal instead of consultation.** A stale write is *blocked* (exit 2),
  not warned about. Warnings get read, noted, and edited over anyway.
- **Proactive instead of only reactive.** The session-start inject pushes
  recorded decisions *into* the new session before it acts, instead of only
  catching violations after the fact.

Block at write time, inject at start time. That's the whole design.

## Install

```bash
pip install edit-guard
edit-guard install
```

Or from source:

```bash
git clone https://github.com/hahahahahahahahah6/edit-guard
cd edit-guard
pip install .
edit-guard install
```

`install` merges three hook entries into `~/.claude/settings.json` (backing it up
first, never clobbering your existing settings):

- `PreToolUse` on `Edit|Write|MultiEdit|NotebookEdit` → `edit-guard hook` (may block)
- `PostToolUse` on `Edit|Write|MultiEdit|NotebookEdit` → `edit-guard hook --post-write`
  (records the digest *after* your write, never blocks — this is what keeps
  your own consecutive edits from looking stale)
- `PreToolUse` on `Read` → `edit-guard hook --observe` (records what you saw, never blocks)

Restart Claude Code afterwards. That's it — no MCP server, no daemon, no
accounts.

## How it works

Each hook invocation appends one line to `~/.config/edit-guard/writes.jsonl`:
`{session_id, tool, path, digest_seen, timestamp}`. When a session tries to
write a file:

1. Hash the file's current content (sha256; mtime+size for files over 50 MB).
2. Compare against the digest **that session** last saw for that path.
3. If it changed **and** another session touched the file in between → block
   (exit 2) with: *"Stale edit blocked: '…' changed since you last saw it
   (last modified by session '…'). Re-read the file before editing."*

Same-session rewrites, new files, and files only you touched are always
allowed. The hook **fails open**: any internal error (corrupt log, unreadable
file, malformed input) means "allow".

Inspect what's being tracked:

```bash
edit-guard log            # recent guarded file events
edit-guard status         # state dir / log / settings paths
```

## Honest limitations

- **Write-after-write staleness only.** The guard compares against what your
  session last *saw through the hook*. Edits made outside any hooked session
  (your editor, a script) are treated as your own session's drift and allowed.
- **Not a lock.** This is advisory staleness detection, not mutual exclusion.
  Two sessions can still race within the same second; the loser is blocked,
  the winner wins.
- **Digest, not semantics.** A reformat that changes bytes but not meaning
  still counts as "changed" and can block you. Re-read and retry.
- **Hook protocol is undocumented.** The PreToolUse stdin shape (`session_id`,
  `tool_name`, `tool_input`) and the exit-2-blocks convention come from
  community documentation, not a stable API. If Claude Code changes the
  protocol, the hook degrades to fail-open allow.
- **Per-machine only.** Sessions on different machines don't share the log.
- The log is append-only with light pruning (keeps the last 10k of 20k lines);
  it is not a backup or audit trail.

## Development

```bash
python3 -m unittest discover -s tests
```

## Changelog

- **0.2.0** — new `edit-guard inject --session-start`: summarize
  `docs/adr/*.md` / `adr/` / `DECISIONS.md` into session context via a
  Claude Code `SessionStart` hook (offense to complement the pre-edit
  refusal hook). `--adr-dir`, `--decisions-file`, `--max-chars` flags.
- **0.1.0** — pre-edit stale-write blocking across sessions.

## License

MIT
