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
49 changes: 49 additions & 0 deletions docs/conventions/hook-budget/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,55 @@ three guardrails verifiers) ≈ 1.9 s. #1809's single-writer change removes per-
repos without a markdownlint config; in an opted-in repo the per-Write set is unchanged (typos-format
still scans in report-only mode), so these figures remain the binding accounting until re-measured.

## Reference figures (2026-09-02, after the hook-performance program)

The fleet's binding measurement is now the dotfiles fan-out harness
(`common/measure-claude-hook-fanout.sh`, sha256
`5a254b50a67a9e1b158ce9ff7bd3c7c53f7eade80e55a1b2f8c5075026067178`), which samples 22 events
against the installed plugin cache, times each hook process in-process with `EPOCHREALTIME`, and
interleaves a `bash -c :` spawn floor S with every sample. A run is valid at S at or below 160 ms;
figures below are spawn-equivalents (hook wall divided by the same-run S), which is the number that
survives a change of host, with the reference-host conversion at S = 80 ms beside it. Every hook
stays `type: command` in its plugin's `hooks/hooks.json`; the program removed no check, added no
`async` row, and narrowed no matcher. The eight guardrails per-Bash-call guards and the three
per-Write verifier guards run through one dispatcher process per event; the six formatter plugins
carry one `if: Edit(*.ext)` row per extension so a Write to any other file spawns nothing.

| Surface (benign payload) | Before (`main` 2026-09-02 morning, S = 33 ms) | After (`main` at `5e3d749cb`, 2026-09-03, S = 18 ms, quiet host) | After at S = 80 ms |
| --- | --- | --- | --- |
| PreToolUse `Bash` (`git status --short`), slowest hook | 75.0 | 88.8 (1,599 ms) | about 7.1 s |
| PreToolUse `Write` (in-repo `.md`), slowest hook | 23.4 | 75.6 (1,360 ms) | about 6.0 s |
| PostToolUse `Write` (in-repo `.md`), slowest hook | 36.7 (out-of-repo sample; the in-repo pre-program figure is 13,225 ms, about 400 S) | 108.3 (1,949 ms) | about 8.7 s |
| PreToolUse `Edit` (in-repo `.md`), slowest hook | 34.8 | 114.6 (2,062 ms) | about 9.2 s |
| PostToolUse `Edit` (in-repo `.md`), slowest hook | 41.6 (out-of-repo sample; the in-repo pre-program figure is 17,192 ms, about 520 S) | 169.3 (3,048 ms) | about 13.5 s |
| PostToolBatch (per turn) | 38.0 | 15.7 (282 ms) | about 1.3 s |
| UserPromptSubmit (per turn) | 29.5 | 16.5 (297 ms) | about 1.3 s |
| Stop, slowest of four (per turn) | 11.4 | 22.8 (410 ms) | about 1.8 s |
| SessionStart `startup`, slowest | 2.7 | 3.3 (60 ms) | about 0.26 s |

The budget table above sums PreToolUse and PostToolUse for one tool call and says the Windows
reference-host figures are binding, so the reading is: **no per-tool-call surface meets the
budget, and on the reference host no per-turn surface does either.** On the measuring host at
S = 18 ms an in-repo Markdown Write costs 1,360 plus 1,949 = 3,309 ms and an in-repo Edit 2,062
plus 3,048 = 5,110 ms, both above the 2 s worst case; a benign Bash call costs 1,599 ms (no
PostToolUse hook fires), above the 1 s typical ceiling and inside the worst case. Scaled to the
80 ms reference host those pairs are about 14.7 s and 22.7 s, and the per-turn rows land at 1.3 to
1.8 s against 500 ms. What the program changed is the size of the overage: in the pre-program shape
the in-repo Write pair was 773 plus 13,225 = 14.0 s and the Edit pair 1,147 plus 17,192 = 18.3 s
on the same host, and PostToolBatch and UserPromptSubmit were 1,254 and 975 ms per turn. The
"after" spawn-equivalents read higher than "before" on the Write and Edit rows because the before
run's samples lived outside the repository, so every Write and verifier guard early-exited and
measured a no-op; the harness now writes its samples under the measured cwd. The remaining
per-tool-call cost is the guardrails dispatcher, 1,360 to 3,048 ms per fire on this host across
the Write, Edit and Bash rows (eight guards per Bash call and three per Write or Edit, each still
sourcing the library and building its telemetry data), followed by markdown-format's
`markdownlint-cli2` Node process; rule 2 stands, and that overage is the guardrails plugin's
remediation work, named in its README. Per-plugin READMEs carry
the paired before-and-after figures for each change (guardrails, context-guard,
rate-limit-guard, typos-format, eol-normalizer, markdown-format). The run's transcript, the
installed versions and shas, the per-file cache compare and every `hooks.json` entry measured are
recorded in the hook-performance program's DEVIATIONS log.

## Rules

1. **A plugin adding or widening an always-on hook states its measured share** (method above) in
Expand Down
2 changes: 1 addition & 1 deletion plugins/context-guard/.claude-plugin/plugin.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"$schema": "https://json.schemastore.org/claude-code-plugin-manifest.json",
"name": "context-guard",
"version": "0.7.35",
"version": "0.7.36",
"description": "Per-session context-window observability plus the first shipped consumer: a statusline wrapper tees each session's context_window fields to a per-session snapshot file, a zone resolver classifies usage into smart/acceptable/dumb bands (percentage bands plus window-class token bands, conservative-min combination, zones.json SSOT with shipped defaults), a reader contract fixes how consuming sessions interpret the snapshots, and zone-crossing hooks report once per transition into a worse zone across two channels \u2014 the continuation menu to the operator, who owns that choice, and to the model only the zone determination plus the counter-steer that a zone word is not a decay signal (advisory by default; an optional blocking mode gates new mutating work on a fresh dumb-zone snapshot with handoff-writing exempt), with a PostCompact hook persisting an evidence-degraded marker.",
"author": {
"name": "Melodic Software",
Expand Down
9 changes: 9 additions & 0 deletions plugins/context-guard/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,15 @@ All notable changes to the `context-guard` plugin.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project
adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [0.7.36]

### Changed

- **README carries the hook budget accounting rows.** The 0.7.34 process counts for the
PostToolBatch, PreToolUse, PostCompact rows and the zone resolver, and the kept 60-second timeout
rationale, now sit under Requirements, per the hook-budget convention's rule 1. Documentation
only.

## [0.7.35]

### Changed
Expand Down
19 changes: 19 additions & 0 deletions plugins/context-guard/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,25 @@ cost is process-spawn bound on `jq` and `date`, and correspondingly less on nati
the input path, so this is display latency, not typing latency; `refreshInterval` in your settings
governs how often it runs.

### Hook budget accounting

Per [`docs/conventions/hook-budget/README.md`](../../docs/conventions/hook-budget/README.md),
the PostToolBatch and UserPromptSubmit rows are per-turn hooks and the PreToolUse row is
Comment thread
kyle-sexton marked this conversation as resolved.
per-tool-call, all always-on. Measured on Windows 11 under Git Bash, twelve trials against an
interleaved `bash -c :` floor, old and new interleaved in one loop (2026-09-02):

| Event | Fires | Spawn-equivalents | What changed |
| --- | --- | --- | --- |
| PostToolBatch, steady zone (`zone-crossing-inject.sh`) | 1 | 18.9 before, 10.8 after (0.7.34) | 11 processes to 2: one `jq` reading both payload fields, `dirname` and `tr` pipelines replaced by expansions, `mkdir -p` behind a `-d` guard |
| UserPromptSubmit, steady zone (the same `zone-crossing-inject.sh`) | 1 | 18.4 before, 11.8 after (0.7.34) | same script, same cuts; measured separately because the payload differs |
| PreToolUse `Write`/`Edit`, advisory mode (`zone-gate.sh`) | 1 | 2.5 before, 1.4 after (0.7.34) | no process spawned in the default posture |
| PostCompact (`post-compact-mark.sh`) | 1 | 9.4 before, 5.7 after (0.7.34) | 9 processes to 4: `date` replaced by printf's clock with a `date` fallback; `mkdir` and `rm` behind existence guards |
| Zone resolver (`scripts/context-zone.sh`, called by the rows above) | per resolve | 9.5 before, 2.3 after (0.7.34) | six processes to one `jq`; a whole steady PostToolBatch fire is 3 processes, down from 15 |

The two advisory rows keep their 60-second timeout: the 0.4.8 measurement put this script at
22.0 s on Windows with Defender real-time protection, and a timeout caps a stalled hook without
speeding a normal one.

## Configuration

Three `userConfig` options, all hook-scoped: `context_guard_hooks_enabled` (kill switch, default
Expand Down
2 changes: 1 addition & 1 deletion plugins/eol-normalizer/.claude-plugin/plugin.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"$schema": "https://json.schemastore.org/claude-code-plugin-manifest.json",
"name": "eol-normalizer",
"version": "0.6.29",
"version": "0.6.30",
"description": "Normalize a written file's working-tree line endings to its .gitattributes eol value on edit \u2014 symmetric CRLF/LF driven by git check-attr, advisory and never blocking.",
"author": {
"name": "Melodic Software",
Expand Down
8 changes: 8 additions & 0 deletions plugins/eol-normalizer/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,14 @@
All notable changes to the `eol-normalizer` plugin are documented here. Format follows
[Keep a Changelog](https://keepachangelog.com/en/1.1.0/); this plugin uses semantic versioning.

## [0.6.30]

### Changed

- **README carries the hook budget accounting row.** The measured 41.0 to 21.5 spawn-equivalents
of 0.6.28 and the residual now sit under Requirements, per the hook-budget convention's rule 1.
Documentation only.

## [0.6.29]

### Changed
Expand Down
14 changes: 14 additions & 0 deletions plugins/eol-normalizer/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,20 @@ The hook itself runs on Bash 3.2+. Telemetry timing uses `EPOCHREALTIME`
(Bash 5.0+); on older bash the telemetry envelope is skipped while normalization
still runs.

### Hook budget accounting

Per [`docs/conventions/hook-budget/README.md`](../../docs/conventions/hook-budget/README.md),
this hook is always-on for every `Write` and `Edit`, so its cost on the path where there is
nothing to rewrite is the figure that counts. Measured on Windows 11 under Git Bash, twelve
interleaved trials against an interleaved `bash -c :` floor (2026-09-02):

| Event | Fires | Spawn-equivalents | What changed |
| --- | --- | --- | --- |
| PostToolUse `Write`, already-normalized `.md` | 1 | 41.0 before, 21.5 after (0.6.28) | sixteen of twenty-seven processes gone: one `git check-attr` for both attributes, `dirname` and `basename` as parameter expansions, the NUL sniff as one `read`, and no temp file, `cp`, `cmp` or `rm` for a file that needs no rewrite |

The residual is the shared library's payload reader and telemetry emitter, cut in 0.6.29 by the
vendored `hook-utils.sh` (one batched `realpath`, no jq on the envelope).

## Install

```shell
Expand Down
2 changes: 1 addition & 1 deletion plugins/markdown-format/.claude-plugin/plugin.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"$schema": "https://json.schemastore.org/claude-code-plugin-manifest.json",
"name": "markdown-format",
"version": "0.11.39",
"version": "0.11.40",
"description": "Auto-format and lint Markdown on edit via markdownlint-cli2 \u2014 only in repos that carry their own markdownlint config.",
"author": {
"name": "Melodic Software",
Expand Down
8 changes: 8 additions & 0 deletions plugins/markdown-format/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,14 @@
All notable changes to the `markdown-format` plugin are documented here. Format follows
[Keep a Changelog](https://keepachangelog.com/en/1.1.0/); this plugin uses semantic versioning.

## [0.11.40]

### Changed

- **README carries the hook budget accounting rows.** The measured 41.6 to 32.0 spawn-equivalents
of 0.11.38, the `if`-row skip for non-Markdown files and the residual now sit under Requirements,
per the hook-budget convention's rule 1. Documentation only.

## [0.11.39]

### Changed
Expand Down
16 changes: 16 additions & 0 deletions plugins/markdown-format/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,22 @@ key), the gate fails closed and the lint run stays skipped. Declarative
rule-only JSONC/YAML configuration is unaffected and lints immediately.
Prefer it when executable configuration is unnecessary.

### Hook budget accounting

Per [`docs/conventions/hook-budget/README.md`](../../docs/conventions/hook-budget/README.md),
this hook is always-on for every `Write` and `Edit` of a `.md` or `.mdc` file (the two `if`
rows in `hooks/hooks.json` keep every other extension from spawning it), so its cost on a clean
Markdown edit is the figure that counts. Measured on Windows 11 under Git Bash, twelve interleaved
trials against an interleaved `bash -c :` floor (2026-09-02):

| Event | Fires | Spawn-equivalents | What changed |
| --- | --- | --- | --- |
| PostToolUse `Write`, clean `.md` in a repo with a markdownlint config | 1 | 41.6 before, 32.0 after (0.11.38) | seven of twenty-one processes gone: six `dirname` calls and one `basename` are parameter expansions, including inside the config-discovery and risky-config walks |
| PostToolUse `Write`, non-Markdown file | 0 | skipped by the `if` rows | one entry per extension since 0.11.35 |

The residual is `markdownlint-cli2` itself (one Node process per fire) plus the shared library's
payload reader and telemetry emitter, cut in 0.11.39 by the vendored `hook-utils.sh`.

## Install

```shell
Expand Down
2 changes: 1 addition & 1 deletion plugins/typos-format/.claude-plugin/plugin.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"$schema": "https://json.schemastore.org/claude-code-plugin-manifest.json",
"name": "typos-format",
"version": "0.6.36",
"version": "0.6.37",
"description": "Spell-check on edit via typos-cli, unconditionally \u2014 report-only by default, honoring the consuming repo's own typos configuration when one is present.",
"author": {
"name": "Melodic Software",
Expand Down
8 changes: 8 additions & 0 deletions plugins/typos-format/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,14 @@
All notable changes to the `typos-format` plugin are documented here. Format follows
[Keep a Changelog](https://keepachangelog.com/en/1.1.0/); this plugin uses semantic versioning.

## [0.6.37]

### Changed

- **README carries the hook budget accounting row.** The measured 36.3 to 26.0 spawn-equivalents
of 0.6.35 and the residual now sit under Requirements, per the hook-budget convention's rule 1.
Documentation only.

## [0.6.36]

### Changed
Expand Down
15 changes: 15 additions & 0 deletions plugins/typos-format/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,21 @@ The hook itself runs on Bash 3.2+. Telemetry timing uses `EPOCHREALTIME`
(Bash 5.0+); on older bash the telemetry envelope is skipped while typo
fixing still runs.

### Hook budget accounting

Per [`docs/conventions/hook-budget/README.md`](../../docs/conventions/hook-budget/README.md),
this hook is always-on for every `Write`, `Edit` and `NotebookEdit`, so its cost on the path
where `typos` finds nothing is the figure that counts. Measured on Windows 11 under Git Bash,
twelve interleaved trials against an interleaved `bash -c :` floor (2026-09-02):

| Event | Fires | Spawn-equivalents | What changed |
| --- | --- | --- | --- |
| PostToolUse `Write`, clean `.md` | 1 | 36.3 before, 26.0 after (0.6.35) | three of sixteen processes gone: two `dirname` calls became parameter expansions and the `notebook_path` copy runs only for a payload that carries one |

The residual is the shared library's payload reader and telemetry emitter, cut in 0.6.36 by the
vendored `hook-utils.sh` (one batched `realpath`, no jq on the envelope), and the `typos` binary
itself.

## Install

```shell
Expand Down