Files
agent-skills/hooks/README.md
T
naps62 a1f95a1161 feat: one writing contract for Claude Code and Codex
Replaces public-comms.md and code-comments.md (749 words, ~25 rules,
almost all prohibitions, no examples) with a single writing.md that
leads with worked examples.

The rule is selection, not compression: keep output short by cutting
whole ideas that don't change what the reader does next, then write what
survives as plain sentences. Not by dropping articles or writing
fragments, which Anthropic's Fable 5 guide calls out as the wrong lever.
Prompt style leaks into output style, so the file is written in the
voice it asks for.

Drops the BLUF ask-line rule entirely: everyone on a PR already knows
who reviews and who merges, and read literally it produced openers like
"Ask: reviewers please merge".

Links the contract to ~/.agents/AGENTS.md, which Codex had nothing in at
all, and to the nix module for the NixOS machine.
2026-08-01 13:06:38 +00:00

1.8 KiB

hooks

Claude Code hooks. Claude-only — Codex ignores. bin/link.sh / nix/home.nix symlink these into ~/.claude/hooks/; wiring is manual, see below.

hook event what
comms-lint.py PreToolUse / Bash Gates gh issue|pr create|edit|comment|review. Lints body against claude-md/writing.md (150-word target / 300 hard cap above fold, no reviewer-addressing opener, plain diction, ≤4 bold spans, no essay headings). Exit 2 blocks, stderr becomes feedback.
comment-lint.py PostToolUse / Write|Edit|MultiEdit Lints newly-added comment lines in code files against claude-md/writing.md. Exit 2 = revise nudge (edit already applied). Long-comment-run finding (>3 lines) is advisory, delivered via additionalContext.

Both fail open on anything they can't parse. Debug with COMMS_LINT_DEBUG=1 / COMMENT_LINT_DEBUG=1.

Wiring

~/.claude/settings.json is machine-local (MCP servers, statusline, per-box hooks), so this repo does not own it. Merge into .hooks:

{
  "hooks": {
    "PreToolUse": [
      { "matcher": "Bash",
        "hooks": [{ "type": "command", "command": "~/.claude/hooks/comms-lint.py" }] }
    ],
    "PostToolUse": [
      { "matcher": "Write|Edit|MultiEdit",
        "hooks": [{ "type": "command", "command": "~/.claude/hooks/comment-lint.py" }] }
    ]
  }
}

Contract prose lives in claude-md/writing.md, linked to ~/.claude/writing.md and imported from ~/.claude/CLAUDE.md via @writing.md. Both linters cite that path in their block message — moving the fragment means updating the linter string too.

Testing a hook

Feed it the payload shape Claude Code sends:

echo '{"tool_name":"Bash","tool_input":{"command":"gh pr comment 1 --body \"short\""}}' \
  | ~/.claude/hooks/comms-lint.py; echo "exit=$?"