diff --git a/plugins/context-guard/.claude-plugin/plugin.json b/plugins/context-guard/.claude-plugin/plugin.json index d91afe7bf4..db4475cc14 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.4.6", + "version": "0.4.7", "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 inject continuation guidance once per transition into a worse zone (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", @@ -17,7 +17,6 @@ "session", "hooks" ], - "hooks": "./hooks/hooks.json", "userConfig": { "context_guard_hooks_enabled": { "type": "boolean", diff --git a/plugins/context-guard/CHANGELOG.md b/plugins/context-guard/CHANGELOG.md index 2245c7eb49..33336d2ad4 100644 --- a/plugins/context-guard/CHANGELOG.md +++ b/plugins/context-guard/CHANGELOG.md @@ -5,6 +5,20 @@ 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.4.7] + +### Fixed + +- **Every hook loads again; the manifest was pointing at a file Claude Code had already loaded + (#1985).** `plugin.json` set `"hooks": "./hooks/hooks.json"` — the default path the harness + discovers on its own. The second registration was rejected as a duplicate and the whole hook file + failed to load with it, so zone-crossing injection, the blocking gate, and the PostCompact + evidence-degraded marker were inert on every machine that installed the plugin, and the + `context_guard_hooks_enabled` and `zone_hook_mode` settings had nothing to switch. The manifest + field exists for hook files at non-default paths; the default one needs no entry. `claude-ops` and + `guardrails` already ship `hooks/hooks.json` with no manifest key, which is the shape this now + matches. + ## [0.4.6] ### Changed diff --git a/scripts/validate-plugin-contracts.mjs b/scripts/validate-plugin-contracts.mjs index 90e4d2ec6c..eb95fca2ed 100755 --- a/scripts/validate-plugin-contracts.mjs +++ b/scripts/validate-plugin-contracts.mjs @@ -238,6 +238,35 @@ if (existsSync(autonomyRoot)) { } } +// A manifest component field names hook files, skill directories, and the rest +// at NON-default paths. Pointing one at the path Claude Code already discovers +// re-registers a component the harness has loaded: for `hooks` that is a +// duplicate-file load error that takes the whole hook file down with it, and it +// is redundant for every other field. The failure is silent at load time, so a +// gate catches it and a reviewer does not. +const defaultComponentPaths = { + agents: ["agents", "agents/"], + commands: ["commands", "commands/"], + hooks: ["hooks/hooks.json"], + lspServers: [".lsp.json"], + mcpServers: [".mcp.json"], + skills: ["skills", "skills/"], +}; +for (const path of pluginFiles) { + if (!path.endsWith(`${sep}.claude-plugin${sep}plugin.json`)) continue; + const manifest = JSON.parse(read(path)); + for (const [field, defaults] of Object.entries(defaultComponentPaths)) { + const declared = [manifest[field] ?? []].flat(); + for (const value of declared) { + if (typeof value !== "string") continue; + const normalized = value.replace(/\\/g, "/").replace(/^\.\//, ""); + if (defaults.includes(normalized)) { + fail(path, `${field} must not name its auto-discovered default path (${value})`); + } + } + } +} + if (failures.length > 0) { console.error("Plugin contract validation failed:"); for (const failure of failures) console.error(`- ${failure}`);