Public repo had absolute paths revealing username: - 5 _manifests/*.toml — companion_memory_files had author-time hardcoded ~/.claude/projects/-Users-<user>/memory/... paths - 5 _generated/*.md — same paths rendered through to public output - docs/DNA-INDEX.md — 107 absolute paths (kei-dna-index emits absolute for atoms but relative for primitives — generator inconsistency) - skills/escalate-recurrence/SKILL.md — 2 instructional path examples Substitution: ~/.claude/projects/-Users-<user>/memory/ -> ~/.claude/memory/ /Users/<user>/Projects/KeiSeiKit-public/ -> <relative> Defence-in-depth: - .github/workflows/leak-check.yml — CI gate (PR + push to main) - (local) .git/hooks/pre-commit — maintainer-side guard with allowlist for legitimate detection-rule files (the hook + the workflow itself) NOTICE + README byline allowlisted (intentional copyright). No secrets exposed — only metadata (username + private-memory filenames). DNA-INDEX root-cause fix in kei-dna-index Rust binary tracked as TODO. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
12 KiB
| name | description |
|---|---|
| escalate-recurrence | Triggered when Claude detects the same mistake ≥2× in one session, or the user says "опять / again / second time / already said". Interactively codifies the pattern into a rule + hook + wiki entry via option-picker questions. Pure-click flow — no free-text decisions. Writes rule file, generates hook scaffold at user-chosen severity, registers hook in settings.json via update-config. See ~/.claude/rules/recurrence-escalate.md. |
Escalate Recurrence — Interactive Codifier
You are converting an observed repeating mistake into a permanent guardrail: a rule file, a wiki entry, and (optionally) an enforcement hook. The entire decision chain is AskUserQuestion — the user clicks, you generate.
Phase 0 — Sanity check (do this BEFORE asking anything)
- State in ONE paragraph what you observed:
- The mistake pattern (abstract, not specific instance)
- Two or more concrete instances with file:line or tool-call evidence
- Your root-cause hypothesis
- Grep existing rules for coverage:
grep -rinE "<3-5 distinctive keywords from the pattern>" ~/.claude/rules/ 2>/dev/null | head -10 - If an existing rule ALREADY covers the pattern → offer
(A) extend that rule / (B) new sibling rule / (C) cancelvia AskUserQuestion and branch. Do NOT duplicate rules.
Phase 1 — Pure-click decisions (AskUserQuestion, SINGLE call)
Emit all four questions in one AskUserQuestion invocation. Use multiSelect: false.
{
"questions": [
{
"question": "Codify this pattern as a rule?",
"header": "Rule",
"multiSelect": false,
"options": [
{"label": "Yes — write rule file", "description": "New file at ~/.claude/rules/<topic>.md"},
{"label": "Extend existing rule", "description": "Append section to the closest matching rule"},
{"label": "No — session-only", "description": "Note in chat, don't persist"},
{"label": "Later — remind me", "description": "Skip now, flag in recurrence-log for next session"}
]
},
{
"question": "Wikify (add to indices)?",
"header": "Wiki",
"multiSelect": false,
"options": [
{"label": "Yes — full (RULES.md + MEMORY.md + CLAUDE.md Rules Index)", "description": "Discoverable from all three entry points"},
{"label": "Partial — RULES.md only", "description": "Registry entry only, no MEMORY.md pointer"},
{"label": "No", "description": "Rule file exists but not indexed"}
]
},
{
"question": "Create enforcement hook?",
"header": "Hook severity",
"multiSelect": false,
"options": [
{"label": "block (exit 2)", "description": "Hard deny. Reserve for data-loss / secret / IP patterns."},
{"label": "enforce (exit 1)", "description": "Error — Claude must fix before retry"},
{"label": "warn (exit 0 + stderr)", "description": "Advisory; tool call proceeds. Safest first step."},
{"label": "remind (UserPromptSubmit)", "description": "Passive reminder on every user turn"},
{"label": "No hook", "description": "Rule only, no automation"}
]
},
{
"question": "Hook event type?",
"header": "Hook event",
"multiSelect": false,
"options": [
{"label": "PreToolUse:Bash", "description": "Gate on shell commands — cmd-string pattern match"},
{"label": "PreToolUse:Edit|Write", "description": "Gate on file modifications — path / content pattern match"},
{"label": "PostToolUse:*", "description": "React AFTER a tool call (e.g. auto-rebuild, auto-log)"},
{"label": "UserPromptSubmit", "description": "Runs on every user turn — for remind-level hooks"},
{"label": "Stop", "description": "Runs when Claude finishes — for end-of-turn reminders"},
{"label": "N/A — no hook chosen", "description": "If previous answer was 'No hook'"}
]
}
]
}
Store as R, W, S, E (rule / wiki / severity / event).
If S == "No hook" and E != "N/A — no hook chosen" → re-ask just the event question with a reminder. Never silently fix user inconsistency.
Phase 2 — Generate artefacts (in memory — do NOT Write yet)
Compose all files the user will see in the diff preview:
2.1 — Rule file (if R is Yes or Extend)
Path: ~/.claude/rules/<slug>.md (slug = kebab-case short pattern name, 2–4 words).
Template:
# RULE — <Human Pattern Name> (YYYY-MM-DD ADDED)
> <one-sentence pattern description>.
## Incident
<The recurrence evidence: 2+ concrete instances with file:line or tool-call refs.>
## The Rule
<Actionable rule, imperative voice.>
## Triggers
- <Specific detectable phrase / pattern 1>
- <Specific detectable phrase / pattern 2>
## Enforcement
- Hook: `~/.claude/hooks/<slug>-guard.sh` (<event>, severity <S>)
- Bypass: <bypass env var name, if applicable>
## Why this and not "remember to check"
<One sentence on why a hook beats memory for this pattern.>
## Rule lock
YYYY-MM-DD. Never override without explicit user revocation.
Fill every <...> placeholder from Phase 0 + Phase 1 answers. Literal {{placeholder}} is forbidden — the assembler validator.rs rejects it.
2.2 — Hook scaffold (if S != "No hook")
Path: ~/.claude/hooks/<slug>-guard.sh.
Template per event type:
PreToolUse:Bash:
#!/bin/sh
# <slug>-guard — <one-line purpose>
# Severity: <S>; Event: PreToolUse:Bash
# Rule: ~/.claude/rules/<slug>.md
command -v jq >/dev/null 2>&1 || exit 0
set -eu
CMD=$(jq -r '.tool_input.command // empty')
[ -n "$CMD" ] || exit 0
# Trigger pattern — tighten during dogfooding
case "$CMD" in
*<pattern-literal>*)
echo "[<slug>] <reminder text>. See ~/.claude/rules/<slug>.md" >&2
exit <exit-code-from-severity>
;;
esac
exit 0
PreToolUse:Edit|Write:
#!/bin/sh
command -v jq >/dev/null 2>&1 || exit 0
set -eu
FILE=$(jq -r '.tool_input.file_path // empty')
CONTENT=$(jq -r '.tool_input.content // .tool_input.new_string // empty')
[ -n "$FILE" ] || exit 0
case "$FILE" in
*<path-pattern>*)
printf '%s' "$CONTENT" | grep -qE '<content-pattern>' || exit 0
echo "[<slug>] <reminder>" >&2
exit <exit-code>
;;
esac
exit 0
UserPromptSubmit:
#!/bin/sh
command -v jq >/dev/null 2>&1 || exit 0
PROMPT=$(jq -r '.prompt // empty')
case "$PROMPT" in
*<trigger-phrase>*)
echo "[<slug>] reminder: <text>. See ~/.claude/rules/<slug>.md" >&2
;;
esac
exit 0
Exit code → severity map:
- block →
exit 2 - enforce →
exit 1 - warn →
exit 0(but still echo to stderr) - remind →
exit 0(UserPromptSubmit only — echo to stderr)
Replace every <...> with the pattern-specific literal extracted from the Phase 0 evidence. Narrow match preferred over broad match — false-positive fatigue kills a hook faster than any bug.
2.3 — Wikification diffs (if W != No)
Compute the diff lines to add:
~/.claude/rules/RULES.md (append to table body, keep alphabetical order if easy; otherwise end):
| <slug>.md | <one-line coverage> | Rules Index (added YYYY-MM-DD) | ~/.claude/hooks/<slug>-guard.sh |
~/.claude/memory/MEMORY.md (one-liner under ## Rules & Feedback):
- [[../../../../rules/<slug>]] — <one-line coverage>
~/.claude/CLAUDE.md Rules Index table (if W == "Yes — full"):
| <short topic> | [[<slug>]] | <one-line covers> |
2.4 — Settings registration (if S != "No hook")
Compute the JSON hook entry:
{
"matcher": "<event matcher>",
"hooks": [
{
"type": "command",
"command": "~/.claude/hooks/<slug>-guard.sh",
"statusMessage": "<slug> <severity> check..."
}
]
}
This gets merged into ~/.claude/settings.json under hooks.PreToolUse / hooks.UserPromptSubmit / etc.
Phase 3 — Confirm via diff preview (AskUserQuestion — keep it click-only)
Show all generated artefacts inline in ONE message, THEN emit a single AskUserQuestion:
=== GENERATED — REVIEW BEFORE WRITE ===
1. New rule: ~/.claude/rules/<slug>.md
<full content, 30–80 lines>
2. New hook: ~/.claude/hooks/<slug>-guard.sh (severity: <S>, event: <E>)
<full content, 15–30 lines>
3. RULES.md append:
<one row>
4. MEMORY.md append:
<one line>
5. settings.json merge (PreToolUse / UserPromptSubmit / etc):
<json block>
Then:
{
"questions": [
{
"question": "Write these artefacts + register hook?",
"header": "Confirm",
"multiSelect": false,
"options": [
{"label": "Confirm — write all", "description": "Phase 4 runs: files written, hook registered via update-config"},
{"label": "Edit rule (1)", "description": "Regenerate the rule file with changes you specify"},
{"label": "Edit hook (2)", "description": "Regenerate the hook scaffold with changes you specify"},
{"label": "Edit wiki diffs (3-4)", "description": "Regenerate RULES.md / MEMORY.md entries"},
{"label": "Edit settings (5)", "description": "Regenerate the settings.json merge block"},
{"label": "Abort", "description": "Stop — nothing gets written, no files touched"}
]
}
]
}
On Confirm → Phase 4. On any Edit <N> → ask ONE free-text line for what to change in that artefact (unavoidable — arbitrary content edit), regenerate, re-preview. On Abort → stop, write nothing, do NOT touch ~/.claude/.
Phase 4 — Write + register (on confirm only)
Execute in order (each via its right tool — do NOT shell out when a tool exists):
Write→~/.claude/rules/<slug>.md- If hook:
Write→~/.claude/hooks/<slug>-guard.sh, thenBash→chmod +x ~/.claude/hooks/<slug>-guard.sh Edit→~/.claude/rules/RULES.md(append the row)Edit→~/.claude/memory/MEMORY.md(append the line)Edit→~/.claude/CLAUDE.md(add Rules Index row) — only ifW == "Yes — full"- If hook: invoke the
update-configskill with the settings-merge spec. Do NOT directly hand-editsettings.json— the skill knows how to merge without clobbering foreign entries. Edit→ append one line to~/.claude/memory/recurrence-log.md(create if absent):YYYY-MM-DD | <slug> | severity=<S> | event=<E> | pattern="<one-line>" | instances=<N>
Phase 5 — Report
✓ Rule codified: ~/.claude/rules/<slug>.md
✓ Hook registered: ~/.claude/hooks/<slug>-guard.sh (severity <S>, event <E>)
✓ Wikified: RULES.md, MEMORY.md<, CLAUDE.md if full>
✓ Log entry: ~/.claude/memory/recurrence-log.md
Next time this pattern triggers, the hook <blocks | errors | warns | reminds>.
To upgrade severity: invoke /escalate-recurrence again on the same pattern.
To disable: remove the entry from settings.json (or set "enabled": false).
Rules (apply throughout)
- Pure click wherever possible. Only slug and pattern-literal require typing. Everything structural is AskUserQuestion.
- Narrow match preferred. A hook with false-positives gets ignored, then removed. Start narrow, widen on next recurrence.
- Start at lowest severity that solves the pain. Upgrade on re-trigger, never auto-promote.
- Never write
{{placeholder}}literals. The assemblervalidator.rsrejects them; same hygiene applies here. - No Patching. A new rule codifies a pattern, it doesn't paper over an existing rule's gap. If the gap is in an existing rule → extend that rule (Phase 1 option "Extend existing").
- Escape hatch. Every hook MUST document its bypass mechanism (env var like
<SLUG>_BYPASS=1or marker string). Without escape, emergency work stalls.