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
3 changes: 1 addition & 2 deletions 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.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",
Expand All @@ -17,7 +17,6 @@
"session",
"hooks"
],
"hooks": "./hooks/hooks.json",
"userConfig": {
"context_guard_hooks_enabled": {
"type": "boolean",
Expand Down
14 changes: 14 additions & 0 deletions plugins/context-guard/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
29 changes: 29 additions & 0 deletions scripts/validate-plugin-contracts.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -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}`);
Expand Down