refactor(hooks): remove periodic context re-injection hook

The `claude-md-reminder.sh` hook, which triggered on `UserPromptSubmit`,
is removed to simplify the system and reduce token overhead.

The hook was designed to combat context drift by periodically
re-injecting core instruction files like `CLAUDE.md` into the prompt.
This strategy proved to be too costly in terms of tokens and added
unnecessary noise to the context.

Removing this hook simplifies the developer workflow. For example, the
process for updating the code reviewer pool is now a simpler seven-step
process instead of eight.

All related configuration in `hooks.json` and documentation in
`ARCHITECTURE.md` and `CLAUDE.md` are updated to reflect this removal.
This commit is contained in:
Fred Amaral 2026-04-18 20:41:28 -03:00
parent b672c5fac0
commit 252934dc27
No known key found for this signature in database
GPG key ID: ADFE56C96F4AC56A
4 changed files with 12 additions and 344 deletions

View file

@ -253,16 +253,14 @@ All ring-dev-team agents include a `## Standards Compliance` section in their ou
```
default/hooks/
├── hooks.json # Hook configuration (SessionStart, UserPromptSubmit)
├── hooks.json # Hook configuration (SessionStart)
├── session-start.sh # Main initialization script
├── generate-skills-ref.py # Dynamic skill reference generator
└── claude-md-reminder.sh # CLAUDE.md reminder on prompt submit
└── generate-skills-ref.py # Dynamic skill reference generator
```
**Key Characteristics:**
- Triggers on SessionStart events (startup|resume, clear|compact)
- Triggers on UserPromptSubmit for reminders
- Injects skills context into Claude's memory
- Auto-generates skills quick reference from frontmatter
- Ensures mandatory workflows are loaded

View file

@ -190,16 +190,15 @@ When content is reused across multiple skills within a plugin:
When adding or removing a code review agent in the `ring:codereview` pool:
**⛔ EIGHT-FILE UPDATE RULE:**
**⛔ SEVEN-FILE UPDATE RULE:**
1. Edit `default/skills/codereview/SKILL.md` — update dispatch step (add/remove Task block), state initialization (review_state.reviewers keys), count references ("N reviewers" throughout), output schema Reviewer Verdicts table
2. Edit frontmatter `description` in EVERY peer reviewer agent (`default/agents/*-reviewer.md` and `dev-team/agents/*-reviewer.md`) — "Runs in parallel with..." list must reflect new peer set
3. Edit body prose `## Your Role` section in EVERY peer reviewer agent — `**Position:**` and `**Critical:** You are one of N parallel reviewers` must reflect new count and peer list
4. Edit `dev-team/hooks/validate-gate-progression.sh` — reviewer array and count threshold
5. Edit `default/hooks/claude-md-reminder.sh` — line injecting reviewer list into every prompt context
6. Edit `dev-team/skills/dev-cycle/SKILL.md` — Gate 8 table, agent list, and "N reviewers" references throughout (~15 occurrences typical)
7. Edit `dev-team/skills/using-dev-team/SKILL.md` — gate tables (backend Gate 8 + frontend Gate 7) with reviewer count and peer enumeration
8. Edit shared-patterns that enumerate reviewers — `default/skills/shared-patterns/reviewer-slicing-strategy.md`, `dev-team/skills/shared-patterns/shared-anti-rationalization.md`, `dev-team/skills/shared-patterns/gate-cadence-classification.md`, `dev-team/skills/shared-patterns/custom-prompt-validation.md`
5. Edit `dev-team/skills/dev-cycle/SKILL.md` — Gate 8 table, agent list, and "N reviewers" references throughout (~15 occurrences typical)
6. Edit `dev-team/skills/using-dev-team/SKILL.md` — gate tables (backend Gate 8 + frontend Gate 7) with reviewer count and peer enumeration
7. Edit shared-patterns that enumerate reviewers — `default/skills/shared-patterns/reviewer-slicing-strategy.md`, `dev-team/skills/shared-patterns/shared-anti-rationalization.md`, `dev-team/skills/shared-patterns/gate-cadence-classification.md`, `dev-team/skills/shared-patterns/custom-prompt-validation.md`
**All files in same commit** — MUST NOT update one without the others.
@ -225,12 +224,11 @@ Before committing changes to the codereview pool:
[ ] 2. Updated frontmatter description in ALL peer reviewer agents?
[ ] 3. Updated body prose Position/Critical in ALL peer reviewer agents?
[ ] 4. Updated validate-gate-progression.sh (array + threshold)?
[ ] 5. Updated claude-md-reminder.sh (reviewer list injection)?
[ ] 6. Updated dev-cycle/SKILL.md (Gate 8 + all "N reviewers" refs)?
[ ] 7. Updated using-dev-team/SKILL.md (both gate tables)?
[ ] 8. Updated shared-patterns files enumerating reviewers?
[ ] 9. Swept secondary consumers (pr-review-multi-source, execute-plan, using-ring, write-plan, docs, marketplace.json)?
[ ] 10. Grep sanity: `grep -rn "N reviewer\|all N" --include="*.md" --include="*.sh"` returns zero stale counts?
[ ] 5. Updated dev-cycle/SKILL.md (Gate 8 + all "N reviewers" refs)?
[ ] 6. Updated using-dev-team/SKILL.md (both gate tables)?
[ ] 7. Updated shared-patterns files enumerating reviewers?
[ ] 8. Swept secondary consumers (pr-review-multi-source, execute-plan, using-ring, write-plan, docs, marketplace.json)?
[ ] 9. Grep sanity: `grep -rn "N reviewer\|all N" --include="*.md" --include="*.sh"` returns zero stale counts?
If any checkbox is no → Fix before committing.
```
@ -246,7 +244,7 @@ If any checkbox is no → Fix before committing.
| [CRITICAL RULES](#-critical-rules-read-first) | Non-negotiable requirements |
| [CLAUDE.md ↔ AGENTS.md Sync](#6-claudemd--agentsmd-synchronization-automatic-via-symlink) | Symlink ensures sync |
| [Content Duplication Prevention](#7-content-duplication-prevention-must-check) | Canonical sources + reference pattern |
| [Reviewer-Pool Synchronization](#8-reviewer-pool-synchronization-must-check) | Eight-file update rule for codereview pool changes |
| [Reviewer-Pool Synchronization](#8-reviewer-pool-synchronization-must-check) | Seven-file update rule for codereview pool changes |
| [Anti-Rationalization Tables](#anti-rationalization-tables-mandatory-for-all-agents) | Prevent AI from assuming/skipping |
| [Lexical Salience Guidelines](#lexical-salience-guidelines-mandatory) | Selective emphasis for effective prompts |
| [Agent Modification Verification](#agent-modification-verification-mandatory) | Checklist for agent changes |
@ -630,7 +628,6 @@ The system loads at SessionStart (from `default/` plugin):
1. `default/hooks/session-start.sh` - Loads skill quick reference via `generate-skills-ref.py`
2. `ring:using-ring` skill - Injected as mandatory workflow
3. `default/hooks/claude-md-reminder.sh` - Reminds about CLAUDE.md on prompt submit
**Monorepo Context:**

View file

@ -1,317 +0,0 @@
#!/usr/bin/env bash
# shellcheck disable=SC2034 # Unused variables OK for exported config
# UserPromptSubmit hook to periodically re-inject instruction files
# Combats context drift in long-running sessions by re-surfacing project instructions
# Supports: CLAUDE.md, AGENTS.md, PROJECT_RULES.md (dedupes symlinks to avoid double-injection)
set -euo pipefail
# Configuration constants
# Re-inject every 3 prompts - balances context freshness with token overhead
# Lower values = more frequent reminders but higher token cost
# Higher values = less overhead but risk of context being forgotten
readonly THROTTLE_INTERVAL=5
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]:-$0}")" && pwd)"
PROJECT_DIR="${CLAUDE_PROJECT_DIR:-.}"
# File types to discover
# PROJECT_RULES.md replaced RULES.md per Lerian standard (2026-04)
INSTRUCTION_FILES=("CLAUDE.md" "AGENTS.md" "PROJECT_RULES.md")
# Context window usage threshold (percentage). When harness reports context ≥ this value,
# force a refresh. Calibrated for 1M-token context windows: 15% ≈ 150k tokens consumed,
# which is already substantial drift territory. Lower = more aggressive refresh (safer,
# higher token cost). Raise if re-injection overhead becomes visible.
readonly CTX_PCT_THRESHOLD=30
# Transcript byte delta threshold (fallback when context_window field unavailable).
# ~200KB of transcript growth ≈ 50k tokens at 4 chars/token estimate.
readonly BYTE_THRESHOLD=300000
# Cooldown: non-temporal triggers require at least this many prompts since last injection.
# Prevents tight loops — a single injection adds 70KB+, context_pct would re-trigger immediately.
readonly MIN_PROMPT_COOLDOWN=3
# Use session-specific state files (per-session, not persistent)
# CLAUDE_SESSION_ID should be provided by Claude Code, fallback to PPID for session isolation
SESSION_ID="${CLAUDE_SESSION_ID:-$PPID}"
STATE_FILE="/tmp/claude-instruction-reminder-${SESSION_ID}.state"
BYTES_FILE="/tmp/claude-instruction-reminder-${SESSION_ID}.bytes"
LAST_INJECT_FILE="/tmp/claude-instruction-reminder-${SESSION_ID}.lastinject"
# Read UserPromptSubmit event JSON from stdin (non-blocking if no stdin).
# Claude Code provides: session_id, transcript_path, cwd, user_prompt, hook_event_name,
# and (recently added) context_window with .used_percentage / .used_tokens / .total_tokens.
hook_input=""
if [ ! -t 0 ]; then
hook_input=$(cat)
fi
# Extract context metrics from hook event JSON.
# PRIMARY signal: context_window.used_percentage (direct from harness, authoritative).
# SECONDARY signal: transcript_path → byte delta (heuristic fallback for older harness versions).
ctx_pct=0
ctx_tokens=0
transcript_path=""
if [ -n "$hook_input" ] && command -v jq >/dev/null 2>&1; then
ctx_pct=$(echo "$hook_input" | jq -r '.context_window.used_percentage // 0' 2>/dev/null || echo 0)
ctx_tokens=$(echo "$hook_input" | jq -r '.context_window.used_tokens // 0' 2>/dev/null || echo 0)
transcript_path=$(echo "$hook_input" | jq -r '.transcript_path // empty' 2>/dev/null || echo "")
fi
# Transcript byte measurement (fallback proxy when context_window unavailable).
current_bytes=0
if [ -n "$transcript_path" ] && [ -f "$transcript_path" ]; then
current_bytes=$(wc -c < "$transcript_path" 2>/dev/null | tr -d ' ' || echo 0)
fi
last_bytes=0
if [ -f "$BYTES_FILE" ]; then
last_bytes=$(cat "$BYTES_FILE" 2>/dev/null || echo 0)
fi
delta_bytes=$((current_bytes - last_bytes))
# Cumulative prompt count (for display and temporal trigger).
if [ -f "$STATE_FILE" ]; then
PROMPT_COUNT=$(cat "$STATE_FILE")
else
PROMPT_COUNT=0
fi
PROMPT_COUNT=$((PROMPT_COUNT + 1))
echo "$PROMPT_COUNT" > "$STATE_FILE"
# Prompts since last injection (for cooldown enforcement on non-temporal triggers).
LAST_INJECT_PROMPT=0
if [ -f "$LAST_INJECT_FILE" ]; then
LAST_INJECT_PROMPT=$(cat "$LAST_INJECT_FILE" 2>/dev/null || echo 0)
fi
prompts_since_inject=$((PROMPT_COUNT - LAST_INJECT_PROMPT))
# Trigger cascade (first match wins; order reflects signal quality and priority):
# 1. Temporal floor — guaranteed baseline, fires every THROTTLE_INTERVAL prompts.
# 2. Context-window saturation — most accurate signal, uses harness-reported usage.
# 3. Volumetric fallback — proxy via transcript bytes when context_window missing.
# Cooldown applies to (2) and (3) to prevent re-injection on consecutive prompts.
should_inject=false
trigger_reason=""
if [ $((PROMPT_COUNT % THROTTLE_INTERVAL)) -eq 0 ]; then
should_inject=true
trigger_reason="temporal (prompt ${PROMPT_COUNT})"
elif [ "$prompts_since_inject" -ge "$MIN_PROMPT_COOLDOWN" ] && [ "${ctx_pct:-0}" -ge "$CTX_PCT_THRESHOLD" ] 2>/dev/null; then
should_inject=true
trigger_reason="context-window (${ctx_pct}% used, ${ctx_tokens} tokens)"
elif [ "$prompts_since_inject" -ge "$MIN_PROMPT_COOLDOWN" ] && [ "$current_bytes" -gt 0 ] && [ "$delta_bytes" -gt "$BYTE_THRESHOLD" ]; then
should_inject=true
trigger_reason="volumetric (+${delta_bytes} bytes since last inject)"
fi
if [ "$should_inject" != true ]; then
# Not time to inject, return empty response.
cat <<EOF
{
"hookSpecificOutput": {
"hookEventName": "UserPromptSubmit"
}
}
EOF
exit 0
fi
# Injecting — record state for next invocation's delta/cooldown calculations.
echo "$PROMPT_COUNT" > "$LAST_INJECT_FILE"
if [ "$current_bytes" -gt 0 ]; then
echo "$current_bytes" > "$BYTES_FILE"
fi
# Time to inject! Find all instruction files
# Array to store all instruction file paths
declare -a instruction_files=()
# For each file type, discover global, project root, and subdirectories
for file_name in "${INSTRUCTION_FILES[@]}"; do
# 1. Global file (~/.claude/CLAUDE.md, ~/.claude/AGENTS.md, etc.)
global_file="${HOME}/.claude/${file_name}"
if [ -f "$global_file" ]; then
instruction_files+=("$global_file")
fi
# 2. Project root file
if [ -f "${PROJECT_DIR}/${file_name}" ]; then
instruction_files+=("${PROJECT_DIR}/${file_name}")
fi
# 3. All subdirectory files
# Use find to discover files in project tree (exclude hidden dirs and common ignores)
while IFS= read -r -d '' file; do
instruction_files+=("$file")
done < <(find "$PROJECT_DIR" \
-type f -not -type l \
-name "$file_name" \
-not -path "*/\.*" \
-not -path "*/node_modules/*" \
-not -path "*/vendor/*" \
-not -path "*/.venv/*" \
-not -path "*/dist/*" \
-not -path "*/build/*" \
-print0 2>/dev/null)
done
# Canonicalize path for symlink-aware dedup.
# Tries coreutils `realpath`, then Python, then raw path. Graceful fallback ensures
# the hook still functions (just without symlink-dedup) on minimal environments.
canonicalize_path() {
if command -v realpath >/dev/null 2>&1; then
realpath "$1" 2>/dev/null || echo "$1"
elif command -v python3 >/dev/null 2>&1; then
python3 -c "import os,sys; print(os.path.realpath(sys.argv[1]))" "$1" 2>/dev/null || echo "$1"
else
echo "$1"
fi
}
# JSON-encode file content for safe embedding in hook output.
# RFC 8259 mandates escaping all control characters U+0000..U+001F as \uXXXX when they
# lack short-form escapes (\n \r \t \b \f). Hand-rolled awk regex misses vertical tab,
# null bytes, ESC, etc. — CLAUDE.md files in the wild contain these (415 lines in Ring's
# project CLAUDE.md had unescaped control chars when audited). Cascade: jq → python3 → awk.
# jq -Rs: raw input, slurped as single string; `.` emits it as a JSON string literal.
# We strip the outer quotes to splice into a larger JSON string being built by the hook.
escape_for_json() {
local f="$1"
if command -v jq >/dev/null 2>&1; then
jq -Rs '.' < "$f" | sed -e '1s/^"//' -e '$s/"$//'
elif command -v python3 >/dev/null 2>&1; then
python3 -c '
import sys, json
with open(sys.argv[1], "r", encoding="utf-8", errors="replace") as fh:
# json.dumps wraps in quotes; strip them to match jq -Rs behavior
sys.stdout.write(json.dumps(fh.read())[1:-1])
' "$f"
else
# Last-resort awk escape. Known to miss some control characters — see RFC 8259.
awk '
BEGIN { ORS="" }
{
gsub(/\\/, "\\\\")
gsub(/"/, "\\\"")
gsub(/\t/, "\\t")
gsub(/\r/, "\\r")
gsub(/\f/, "\\f")
if (NR > 1) printf "\\n"
printf "%s", $0
}
END { printf "\\n" }
' "$f"
fi
}
# Dedup by canonical path to handle two cases:
# 1. Same file discovered via different methods (project root + find)
# 2. Symlinks (e.g., Ring convention: AGENTS.md -> CLAUDE.md) — avoid double-injection
# First occurrence wins, which preserves INSTRUCTION_FILES priority order
# (CLAUDE.md displayed before AGENTS.md when they share the same inode).
if [ "${#instruction_files[@]}" -gt 0 ]; then
unique_files=()
seen_paths=""
for f in "${instruction_files[@]}"; do
real_f=$(canonicalize_path "$f")
# Newline delimiters avoid substring collisions between similar paths
case "${seen_paths}" in
*$'\n'"${real_f}"$'\n'*)
# Already seen (likely symlink target) — skip silently
;;
*)
unique_files+=("$f")
seen_paths="${seen_paths}"$'\n'"${real_f}"$'\n'
;;
esac
done
instruction_files=("${unique_files[@]}")
fi
# Build reminder context
reminder="<instruction-files-reminder>\n"
reminder="${reminder}Re-reading instruction files to combat context drift — trigger: ${trigger_reason}\n\n"
for file in "${instruction_files[@]}"; do
# Get relative path for display
file_name=$(basename "$file")
if [[ "$file" == "${HOME}/.claude/"* ]]; then
display_path="~/.claude/${file_name} (global)"
else
# Create relative path (cross-platform compatible)
display_path="${file#$PROJECT_DIR/}"
# If the file IS the project dir (no relative path created), just show filename
if [[ "$display_path" == "$file" ]]; then
display_path="$file_name"
fi
fi
# Choose emoji based on file type
case "$file_name" in
CLAUDE.md)
emoji="📋"
;;
AGENTS.md)
emoji="🤖"
;;
PROJECT_RULES.md)
emoji="📜"
;;
*)
emoji="📄"
;;
esac
reminder="${reminder}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n"
reminder="${reminder}${emoji} ${display_path}\n"
reminder="${reminder}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n\n"
# JSON-encode file content (RFC 8259 compliant via jq/python3 cascade).
escaped_content=$(escape_for_json "$file")
reminder="${reminder}${escaped_content}\n\n"
done
reminder="${reminder}</instruction-files-reminder>\n"
# Add agent usage reminder (compact, ~200 tokens)
agent_reminder="<agent-usage-reminder>\n"
agent_reminder="${agent_reminder}CONTEXT CHECK: Before using Glob/Grep/Read chains, consider agents:\n\n"
agent_reminder="${agent_reminder}| Task | Agent |\n"
agent_reminder="${agent_reminder}|------|-------|\n"
agent_reminder="${agent_reminder}| Explore codebase | Explore |\n"
agent_reminder="${agent_reminder}| Multi-file search | Explore |\n"
agent_reminder="${agent_reminder}| Complex research | general-purpose |\n"
agent_reminder="${agent_reminder}| Code review | ALL 10 reviewers in PARALLEL (code, business-logic, security, test, nil-safety, consequences, dead-code, performance, multi-tenant, lib-commons) |\n"
agent_reminder="${agent_reminder}| Implementation plan | ring:write-plan |\n"
agent_reminder="${agent_reminder}| Deep architecture | ring:codebase-explorer |\n\n"
agent_reminder="${agent_reminder}**3-File Rule:** If reading >3 files, use an agent instead. 15x more context-efficient.\n"
agent_reminder="${agent_reminder}</agent-usage-reminder>\n"
reminder="${reminder}${agent_reminder}"
# Add duplication prevention reminder
duplication_guard="<duplication-prevention-guard>\n"
duplication_guard="${duplication_guard}**BEFORE ADDING CONTENT** to any file:\n"
duplication_guard="${duplication_guard}1. SEARCH FIRST: \`grep -r 'keyword' --include='*.md'\`\n"
duplication_guard="${duplication_guard}2. If exists -> REFERENCE it, don't copy\n"
duplication_guard="${duplication_guard}3. Canonical sources: CLAUDE.md (rules), docs/*.md (details)\n"
duplication_guard="${duplication_guard}4. NEVER duplicate - always link to single source of truth\n"
duplication_guard="${duplication_guard}</duplication-prevention-guard>\n"
reminder="${reminder}${duplication_guard}"
# Output hook response with injected context
cat <<EOF
{
"hookSpecificOutput": {
"hookEventName": "UserPromptSubmit",
"additionalContext": "${reminder}"
}
}
EOF
exit 0

View file

@ -10,16 +10,6 @@
}
]
}
],
"UserPromptSubmit": [
{
"hooks": [
{
"type": "command",
"command": "${CLAUDE_PLUGIN_ROOT}/hooks/claude-md-reminder.sh"
}
]
}
]
}
}