diff --git a/docs/conventions/hook-budget/README.md b/docs/conventions/hook-budget/README.md index a89ee0399c..f90d74df13 100644 --- a/docs/conventions/hook-budget/README.md +++ b/docs/conventions/hook-budget/README.md @@ -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 diff --git a/plugins/context-guard/.claude-plugin/plugin.json b/plugins/context-guard/.claude-plugin/plugin.json index 3cd39ab28b..a7b7cf83cd 100644 --- a/plugins/context-guard/.claude-plugin/plugin.json +++ b/plugins/context-guard/.claude-plugin/plugin.json @@ -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", diff --git a/plugins/context-guard/CHANGELOG.md b/plugins/context-guard/CHANGELOG.md index 9874359748..aa47342f04 100644 --- a/plugins/context-guard/CHANGELOG.md +++ b/plugins/context-guard/CHANGELOG.md @@ -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 diff --git a/plugins/context-guard/README.md b/plugins/context-guard/README.md index b92388b46c..7232279fbf 100644 --- a/plugins/context-guard/README.md +++ b/plugins/context-guard/README.md @@ -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 +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 diff --git a/plugins/eol-normalizer/.claude-plugin/plugin.json b/plugins/eol-normalizer/.claude-plugin/plugin.json index c27a275963..bf62f3298d 100644 --- a/plugins/eol-normalizer/.claude-plugin/plugin.json +++ b/plugins/eol-normalizer/.claude-plugin/plugin.json @@ -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", diff --git a/plugins/eol-normalizer/CHANGELOG.md b/plugins/eol-normalizer/CHANGELOG.md index 5747087108..42da64382c 100644 --- a/plugins/eol-normalizer/CHANGELOG.md +++ b/plugins/eol-normalizer/CHANGELOG.md @@ -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 diff --git a/plugins/eol-normalizer/README.md b/plugins/eol-normalizer/README.md index 7c11f29335..e058738ab3 100644 --- a/plugins/eol-normalizer/README.md +++ b/plugins/eol-normalizer/README.md @@ -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 diff --git a/plugins/markdown-format/.claude-plugin/plugin.json b/plugins/markdown-format/.claude-plugin/plugin.json index f5f3d427e3..9f5739dd2d 100644 --- a/plugins/markdown-format/.claude-plugin/plugin.json +++ b/plugins/markdown-format/.claude-plugin/plugin.json @@ -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", diff --git a/plugins/markdown-format/CHANGELOG.md b/plugins/markdown-format/CHANGELOG.md index b6e97e3ec1..aaf2f08c55 100644 --- a/plugins/markdown-format/CHANGELOG.md +++ b/plugins/markdown-format/CHANGELOG.md @@ -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 diff --git a/plugins/markdown-format/README.md b/plugins/markdown-format/README.md index f0e5a1a87d..71c77ac83b 100644 --- a/plugins/markdown-format/README.md +++ b/plugins/markdown-format/README.md @@ -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 diff --git a/plugins/typos-format/.claude-plugin/plugin.json b/plugins/typos-format/.claude-plugin/plugin.json index 6687182969..c393994668 100644 --- a/plugins/typos-format/.claude-plugin/plugin.json +++ b/plugins/typos-format/.claude-plugin/plugin.json @@ -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", diff --git a/plugins/typos-format/CHANGELOG.md b/plugins/typos-format/CHANGELOG.md index 3661bbe5ea..14d000b718 100644 --- a/plugins/typos-format/CHANGELOG.md +++ b/plugins/typos-format/CHANGELOG.md @@ -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 diff --git a/plugins/typos-format/README.md b/plugins/typos-format/README.md index 6b774bfb49..3198b04eb4 100644 --- a/plugins/typos-format/README.md +++ b/plugins/typos-format/README.md @@ -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