feat(plugin): fix guardian hooks, add InstructionsLoaded enforcement, .claude/rules emitter, AGENTS.md interop

Closes the gap between ClaudeForge and the Anthropic docs at
code.claude.com/docs/en/memory (and its linked hooks / skills / plugins
pages). Five tightly-scoped changes, each verified by smoke test.

A. Fix guardian hook frontmatter shape (agent/claude-md-guardian.md)

   The previous array-of-{event, commands} shape did not match the
   documented schema (hooks: { EventName: [{ matcher, hooks: [{ type,
   command }] }] }), so the guardian's hooks silently did not fire.
   Rewritten to the canonical keyed-object shape with PostToolUse,
   PreToolUse, SessionStart, and the new InstructionsLoaded event.

B+C. Plugin-level hooks/hooks.json + hooks/validate-claude-md.py

   New deterministic enforcement of the 150-line cap. The validator
   script reads the hook payload from stdin, extracts any referenced
   CLAUDE.md path (supports both PostToolUse tool_input.file_path and
   InstructionsLoaded path / file shapes), and exits 2 with stderr
   feedback when the file is over 150 lines. Wired to both PostToolUse
   on Write|Edit and InstructionsLoaded on every load_reason
   (session_start, nested_traversal, path_glob_match, include, compact).
   The cap is now enforced at every load *and* write, not only when the
   guardian decides to run sync.

D. ContentGenerator.generate_rules_file() (skill/generator.py)

   Emits path-scoped .claude/rules/*.md instruction files with name,
   description, and paths: glob frontmatter. Claude loads these
   lazily — only when accessing files matching the globs — so
   file-type-specific guidance no longer has to live in the root
   CLAUDE.md. Validates that paths is non-empty (ValueError otherwise).

E. AGENTS.md / .cursorrules / .windsurfrules interop

   command/enhance-claude-md.md Phase 1 now lists which sibling
   instruction files exist. ContentGenerator.generate_root_file() reads
   project_context['existing_instruction_files'] and prepends an
   ## External Instructions section with @AGENTS.md (etc.) imports, so
   repos already using other agent tooling can adopt ClaudeForge
   without losing their existing instructions.

Smoke tests (all pass):
- Guardian hooks frontmatter parses as a dict with 4 events, each
  carrying matcher + nested hooks array of {type, command} entries.
- hooks.json is valid JSON; PostToolUse matcher = Write|Edit;
  InstructionsLoaded matcher covers all five load_reason values.
- validate-claude-md.py: small file -> rc 0, bloated file (200 lines)
  -> rc 2 with stderr referencing the 150 cap, InstructionsLoaded
  payload shape also handled, non-CLAUDE.md paths ignored, no stdin
  -> rc 0.
- generate_rules_file emits valid frontmatter with paths glob and
  raises ValueError when paths is empty.
- generate_root_file inserts @AGENTS.md and @.cursorrules imports
  when existing_instruction_files lists them; omits the section
  otherwise.
- Regression: large-fullstack root still 52 lines with chain imports
  intact; all five sub-CLAUDE.md files in this repo still pass
  validator (status = pass).

Docs:
- agent/CLAUDE.md updated to show the canonical hook shape and the
  hook-driven validator wiring.
- skill/CLAUDE.md notes generate_rules_file and AGENTS.md interop.
- CHANGELOG.md documents all five changes under Unreleased.
This commit is contained in:
Claude
2026-05-19 00:52:42 +00:00
parent c374cc28a1
commit 0a34178e22
8 changed files with 248 additions and 29 deletions
+17 -12
View File
@@ -26,29 +26,34 @@ Guidelines for the `claude-md-guardian` background-maintenance agent.
## v2.0.0+ Frontmatter Reference
Hooks use Anthropic's canonical keyed-object schema (event → array of `{ matcher, hooks: [{ type, command }] }`):
```yaml
---
name: claude-md-guardian
permissions: [Bash, Read, Write, Edit, Grep, Glob, Skill]
model: haiku
color: purple
fork_safe: true
hooks:
- SessionStart
- PreToolUse
- PostToolUse
---
PostToolUse:
- matcher: "Write|Edit"
hooks:
- type: command
command: "python3 ${CLAUDE_PLUGIN_ROOT}/hooks/validate-claude-md.py"
InstructionsLoaded:
- matcher: "session_start|nested_traversal|path_glob_match|include|compact"
hooks:
- type: command
command: "python3 ${CLAUDE_PLUGIN_ROOT}/hooks/validate-claude-md.py"
```
The array-of-`{event, commands}` shape used in earlier versions did not match the documented schema and silently did not fire.
## Skill ↔ Agent Integration
The agent uses `claude-md-enhancer` (the skill's frontmatter name; installed as `claudeforge-skill/`) as its core capability. Invoke it with `Skill: claude-md-enhancer` inside the agent workflow.
Hook responsibilities:
- **SessionStart** — check `git diff`; if significant change is detected, invoke the skill for an incremental update.
- **SessionStart** — check `git diff`; if significant drift is detected, invoke the skill for an incremental update.
- **PreToolUse** — validate before a CLAUDE.md edit lands.
- **PostToolUse** — after Edit/Write to any CLAUDE.md, run `BestPracticesValidator`, report the quality score, suggest improvements. Refuse to leave the file over 150 lines.
- **PostToolUse** — after `Edit`/`Write` to any CLAUDE.md, the plugin-level `hooks/hooks.json` runs `hooks/validate-claude-md.py`. The script exits `2` with stderr feedback when the file is over 150 lines; the guardian then proposes a `/sync-claude-md` run.
- **InstructionsLoaded** — same script fires on every `load_reason` (`session_start`, `nested_traversal`, `path_glob_match`, `include`, `compact`), so the cap is enforced deterministically at load time, not just at write time.
## Agent ↔ Git
+20 -12
View File
@@ -16,18 +16,26 @@ field: documentation
expertise: intermediate
fork_safe: true
hooks:
- event: SessionStart
commands:
- echo "Guardian: Checking for CLAUDE.md updates..."
once: false
- event: PreToolUse
matcher: Write
commands:
- echo "Guardian: Validating CLAUDE.md changes..."
- event: PostToolUse
matcher: Write
commands:
- echo "Guardian: CLAUDE.md update complete"
SessionStart:
- matcher: ""
hooks:
- type: command
command: "echo 'Guardian: checking for CLAUDE.md drift on session start'"
PreToolUse:
- matcher: "Write|Edit"
hooks:
- type: command
command: "echo 'Guardian: validating CLAUDE.md change'"
PostToolUse:
- matcher: "Write|Edit"
hooks:
- type: command
command: "python3 ${CLAUDE_PLUGIN_ROOT:-${CLAUDE_PROJECT_DIR}}/hooks/validate-claude-md.py"
InstructionsLoaded:
- matcher: "session_start|nested_traversal|path_glob_match|include|compact"
hooks:
- type: command
command: "python3 ${CLAUDE_PLUGIN_ROOT:-${CLAUDE_PROJECT_DIR}}/hooks/validate-claude-md.py"
---
# CLAUDE.md Guardian Agent