@@ -7,7 +7,9 @@ can observe, block, or annotate the action that triggered it.
77
88This document records the Cursor hooks contract, what Copse honours today, and what
99remains for fuller parity. It is the hooks counterpart to
10- [ ` docs/cursor-plugins.md ` ] ( ./cursor-plugins.md ) .
10+ [ ` docs/cursor-plugins.md ` ] ( ./cursor-plugins.md ) . For the cross-cutting architecture — the
11+ unified registry, canonical events, executors, async/budget/epoch, spine, sandbox, and UI
12+ that are dialect-agnostic — see [ ` docs/hooks.md ` ] ( ./hooks.md ) .
1113
1214## On-disk layout
1315
@@ -246,6 +248,55 @@ This is the same trust boundary described in
246248[ ` docs/supply-chain-security.md ` ] ( ./supply-chain-security.md ) : trusting a workspace means
247249trusting the code it can cause to run.
248250
251+ ## Vendored upstream schemas & drift detection (G3)
252+
253+ Copse pins committed copies of the upstream hook-config JSON schemas for both
254+ foreign dialects under [ ` schemas/vendor/ ` ] ( ../schemas/vendor/ ) —
255+ ` claude-code-settings.schema.json ` (Claude Code, from SchemaStore) and
256+ ` cursor-hooks.schema.json ` (the community ` cursor-hooks ` npm schema). See
257+ [ ` schemas/vendor/README.md ` ] ( ../schemas/vendor/README.md ) for provenance, pins,
258+ and the re-vendoring steps.
259+
260+ These exist for exactly two purposes, and are subject to two hard rules — they
261+ are ** never fetched over the network** at runtime or in CI, and they are ** never
262+ a load gate** (a config that violates an upstream schema still loads):
263+
264+ 1 . ** Warn-level authoring lint.** Parsing a foreign config uses the schema's
265+ published event list to warn when a hooks group targets an event the vendor
266+ recognises but Copse does not act on yet (vs an outright typo). The valid
267+ hooks still load; the warning surfaces in Settings → Sources.
268+ 2 . ** CI drift detector** (` src/main/services/hooks/vendor-schema-drift.test.ts ` )
269+ diffs each vendored schema's published events against the events our adapters
270+ wire. Every published event must be either wired or listed in an explicit
271+ intentionally-unsupported set (` src/shared/hooks/vendored-hook-schemas.ts ` ); an
272+ upstream release adding an unaccounted event fails CI until it is wired or
273+ documented. Copse currently wires Claude ` PreToolUse ` + ` SessionStart ` and the
274+ Cursor events above; the long tail of Claude events (` Notification ` ,
275+ ` TeammateIdle ` , …) is intentionally-unsupported v1.
276+
277+ ## Wire payload snapshots (G4)
278+
279+ Every dialect wire ** request** payload — the stdin JSON a Cursor / Claude / Copse
280+ hook actually receives — is snapshot-tested against a committed golden fixture
281+ [ ` src/main/services/hooks/__snapshots__/wire-payloads.json ` ] ( ../src/main/services/hooks/__snapshots__/wire-payloads.json )
282+ by [ ` src/main/services/hooks/payload-snapshots.test.ts ` ] ( ../src/main/services/hooks/payload-snapshots.test.ts ) .
283+ The test marshals a fixed synthetic payload (with a fixed agent-session identity,
284+ so the B4 ` model ` fields are captured) for every canonical event each dialect
285+ declares a marshaller for — including the tool-flavor splits (shell / MCP / read
286+ for ` toolGate ` , shell / MCP for ` afterToolUse ` ) — and asserts the result is
287+ byte-identical to the fixture.
288+
289+ This implements ** decision 14** of
290+ [ ` docs/plans/hooks-and-feature-packs.md ` ] ( plans/hooks-and-feature-packs.md ) :
291+ pre-v1 with zero consumers we do not version payloads, but the request direction
292+ is the stability contract, so ** changing a snapshot is a publish-time stability
293+ audit** — the reviewed JSON diff of the golden fixture _ is_ the stability
294+ declaration. Regenerate the fixture (and review the diff) with:
295+
296+ ``` bash
297+ UPDATE_HOOK_PAYLOAD_SNAPSHOTS=1 npm test
298+ ```
299+
249300## Gaps and future work
250301
2513021 . ** Content rewriting** — ` updated_input ` on tool gates (rewrite the proposed tool
@@ -272,7 +323,13 @@ trusting the code it can cause to run.
272323- ` src/main/services/security/permission-gate.ts ` — calls the tool-gate hooks
273324- ` src/shared/types/cursor-hooks.ts ` — ` CursorHookEvent ` / ` CursorHookSummary `
274325- ` src/shared/types/hooks.ts ` — shared ` HookSummary ` for Sources / ` hooks:list `
326+ - ` src/shared/hooks/vendored-hook-schemas.ts ` — published-event mirrors + intentionally-unsupported sets (G3)
327+ - ` schemas/vendor/ ` — pinned upstream Cursor + Claude hook schemas (G3); see its ` README.md `
328+ - ` src/main/services/hooks/vendor-schema-drift.test.ts ` — CI drift detector (G3)
329+ - ` src/main/services/hooks/payload-snapshots.test.ts ` — dialect wire payload snapshot tests (G4)
330+ - ` src/main/services/hooks/__snapshots__/wire-payloads.json ` — committed golden wire-payload fixture (G4)
275331- ` src/main/services/exec/child-process-env.ts ` — secret-scrubbed env for hook processes
332+ - ` docs/hooks.md ` — dialect-agnostic hooks architecture umbrella
276333- ` docs/claude-hooks.md ` — Claude Code hooks contract
277334- ` docs/cursor-plugins.md ` — sibling exploration of Cursor plugin support
278335- ` docs/supply-chain-security.md ` — trust boundaries for executed code
0 commit comments