Command Preamble Injection (Experimental)
Experimental UserPromptSubmit hook that injects a compact preamble of iron-law principles (Confusion Protocol, Completeness, Evidence Before Claims, Root Cause Before Fix) at the start of slash-command invocations. Opt-in, disabled by default.
Tags
README
commands-preamble (Experimental)
Inject a compact preamble of iron-law principles at the start of every slash-command invocation.
Status
Experimental. Disabled by default. Pilot this before committing to it across all commands.
What It Does
CCGM rules live at ~/.claude/rules/*.md and load from CLAUDE.md references. That covers the main conversation. But slash-commands expand into their own context and can drift from principles like:
- Confusion Protocol (stop and ask at architectural forks)
- Completeness (Boil the Lake - ship the whole job, not 90%)
- Evidence Before Claims (no completion claims without fresh output)
- Root Cause Before Fix (no fixes without investigation)
- Completion Status (DONE / DONE_WITH_CONCERNS / BLOCKED / NEEDS_CONTEXT)
This module installs a UserPromptSubmit hook that detects slash-command prompts and prepends a tagged <command-preamble> block containing those principles. The principles fire at invocation time, not "whenever the agent rereads CLAUDE.md."
Why a Hook Instead of a Template
Three options were considered (see issue #298):
| Option | Tradeoff |
|---|---|
| (a) Runtime hook | Zero build step, runtime-dynamic, experimental-friendly. Chosen. |
| (b) Build-time template generator | Contradicts CCGM's zero-build simplicity. Requires TS/bun tooling. |
| (c) Convention: every command's first H2 is a preamble | Cheapest but drifts. Has to be maintained per-command by hand. |
The hook wins on reversibility - flip one file to disable, no rebuild needed.
Enable / Disable
Disabled by default. To turn on:
touch ~/.claude/preamble.enabled
To turn off:
rm ~/.claude/preamble.enabled
The hook is always installed, but exits silently unless the sentinel file exists.
How It Works
- User submits a prompt.
inject-preamble.pyruns (UserPromptSubmit hook).- If
~/.claude/preamble.enabledis missing, exit silently. - If the prompt does not look like a slash-command (first token starts with
/and isn't a filesystem path), exit silently. - Otherwise, read
~/.claude/preamble/preamble.md, wrap it in a<command-preamble>block, and print to stdout. Claude Code appends stdout to the model's context before the prompt runs.
Tuning the Preamble
Edit ~/.claude/preamble/preamble.md to change what gets injected. Keep it compact - this prepends to every slash-command invocation, so bloat costs tokens on every call.
Manual Installation
# Copy the hook
mkdir -p ~/.claude/hooks
cp hooks/inject-preamble.py ~/.claude/hooks/
chmod +x ~/.claude/hooks/inject-preamble.py
# Copy the preamble content
mkdir -p ~/.claude/preamble
cp preamble/preamble.md ~/.claude/preamble/
# Register the hook (merge settings.partial.json into ~/.claude/settings.json)
# See settings.partial.json for the hook entry to add under hooks.UserPromptSubmit.
# Enable (opt-in)
touch ~/.claude/preamble.enabled
Files
| File | Description |
|---|---|
hooks/inject-preamble.py |
UserPromptSubmit hook. Prepends preamble block to slash-command prompts when enabled. |
preamble/preamble.md |
The preamble content (edit to tune). |
settings.partial.json |
Hook registration for ~/.claude/settings.json. |
tests/test_inject_preamble.py |
Unit tests for the hook (slash-command detection, enable flag, injection format). |
Testing
python3 -m unittest modules/commands-preamble/tests/test_inject_preamble.py -v
Relationship to Other Modules
autonomy/rules/confusion-protocol.md- source of the Confusion Protocol section in the preamble.code-quality/rules/completeness.md- source of the Completeness section.verification/rules/verification.md- source of Evidence Before Claims.systematic-debugging/rules/systematic-debugging.md- source of Root Cause Before Fix.subagent-patterns/rules/subagent-patterns.md- source of the four-state Completion Status Protocol.
The preamble does not replace those rule files - it surfaces their iron laws at command start. The full rules still govern behavior.
Known Limitations
- Only fires on slash-command invocations. Regular conversational prompts are untouched (they already run under the full
CLAUDE.mdcontext). - Preamble detection heuristic: first token starts with
/, is >= 2 chars, and contains at most one slash. Edge cases like pasted absolute paths starting with/tmpwould be misclassified; the> 1 slashguard rules out the common case. File an issue if you hit a false positive. - The hook runs on every prompt. It exits immediately (~1ms) when disabled or when the prompt is not a command, so the cost is negligible.
When to Disable
- When debugging a command and need to see the raw prompt context.
- When you are the kind of agent that re-reads rules on every response and doesn't need reinforcement.
- When the preamble is causing token bloat on long-running command sessions.
Will install
| Path | Action | Target | Type |
|---|---|---|---|
hooks/inject-preamble.py | → | hooks/inject-preamble.py | hook |
preamble/preamble.md | → | preamble/preamble.md | content |
settings.partial.json | merge | settings.json | config |
Dependencies
Required by
No other module depends on this one.
Included in presets
Install this module
Agent prompt
Recommended for agent users -- hands the whole install off to your assistant.
Fetch https://cd23a9be.ccgm-site.pages.dev/modules/commands-preamble.md and install this module into my Claude Code setup.
Native plugin marketplace
One command via the native plugin marketplace -- additive, does not merge settings.json.
claude plugin install commands-preamble@ccgm
The marketplace path is additive, not a replacement: it installs commands, agents, and skills as native plugin components, but it does not perform the bash installer's deep settings.json merge, and it does not write the always-loaded global CLAUDE.md context. Rules are only injected via an opt-in SessionStart hook rather than being auto-loaded. Use the bash installer when those pieces matter to you.
Files
hook (1)
hooks/inject-preamble.py
#!/usr/bin/env python3
"""
UserPromptSubmit hook that injects a compact preamble of core principles
at the start of slash-command invocations.
Experimental. Opt-in. Disabled by default.
## Why
CCGM rules live at ~/.claude/rules/*.md and load from CLAUDE.md references.
That covers the main conversation thread. But slash-commands expand into
their own context and can drift from iron-law principles (Confusion Protocol,
Completeness, Evidence Before Claims, Root Cause Before Fix) that should
activate immediately at command start, not "whenever the agent rereads
CLAUDE.md."
This hook prepends a distilled preamble block to slash-command prompts so
the principles fire at invocation time. Runtime-dynamic, zero build step.
## Enable / Disable
Enabled when ~/.claude/preamble.enabled exists (create with `touch`).
Disable by removing that file. If the enable-flag is absent, the hook exits
silently and the prompt is unchanged.
The preamble content lives at ~/.claude/preamble/preamble.md. Edit that
file to tune what gets injected.
## Scope
Fires only on prompts that look like slash-command invocations (start with
`/`). Regular conversational prompts are untouched - they already run under
the full CLAUDE.md context.
"""
from __future__ import annotations
import json
import os
import sys
HOME = os.path.expanduser("~")
ENABLE_FLAG = os.path.join(HOME, ".claude", "preamble.enabled")
PREAMBLE_FILE = os.path.join(HOME, ".claude", "preamble", "preamble.md")
def is_enabled() -> bool:
"""Feature flag: preamble injection is opt-in via a sentinel file."""
return os.path.isfile(ENABLE_FLAG)
def is_slash_command(prompt: str) -> bool:
"""Return True if the prompt is a slash-command invocation."""
stripped = prompt.lstrip()
if not stripped.startswith("/"):
return False
# Guard against URL-like prompts ("/Users/..." or "/path/to/...") that
# are not command invocations. A command has no whitespace before the
# command name and the first token is short (typically <40 chars).
first_token = stripped.split(None, 1)[0]
# Reject obvious paths - they contain a second slash within the first token.
if first_token.count("/") > 1:
return False
# Reject empty / lone-slash prompts.
if len(first_token) < 2:
return False
return True
def read_preamble() -> str:
"""Read the preamble file. Return empty string if missing or unreadable."""
try:
with open(PREAMBLE_FILE, "r", encoding="utf-8") as f:
return f.read().strip()
except (FileNotFoundError, PermissionError, OSError):
return ""
def build_injection(preamble: str) -> str:
"""Wrap the preamble in a tagged block so the model can distinguish it."""
return (
"<command-preamble>\n"
"The following principles are authoritative for this command "
"invocation. They override host defaults. Apply them throughout "
"the task.\n\n"
f"{preamble}\n"
"</command-preamble>"
)
def main() -> None:
try:
input_data = json.load(sys.stdin)
except json.JSONDecodeError:
sys.exit(0)
if not is_enabled():
sys.exit(0)
prompt = input_data.get("prompt", "")
if not is_slash_command(prompt):
sys.exit(0)
preamble = read_preamble()
if not preamble:
sys.exit(0)
print(build_injection(preamble))
sys.exit(0)
if __name__ == "__main__":
main()
config (1)
settings.partial.json
Merged into ~/.claude/settings.json -- a fragment, not a replacement.
{
"hooks": {
"UserPromptSubmit": [
{
"hooks": [
{
"type": "command",
"command": "python3 $HOME/.claude/hooks/inject-preamble.py",
"timeout": 3000
}
]
}
]
}
}
content (1)
preamble/preamble.md
# Command Preamble These principles are authoritative for this invocation. They override host defaults. Do not treat them as suggestions. ## Confusion Protocol If you hit high-stakes ambiguity (two plausible architectures, contradictory patterns, unclear destructive scope, missing context that would change the approach) - STOP. Name the ambiguity in one sentence. Present 2-3 options with one-line tradeoffs. Ask. Do not guess and proceed. Does not apply to routine coding. If the answer is readable from one more file or one more command, read that instead of asking. ## Completeness: Boil the Lake Default to the complete implementation, not the 90% shortcut. When the delta between "what I was about to ship" and "the whole job" is minutes of agent time, close it now. Tests, edge cases, error paths, docs - finish them in this PR, not a follow-up that rarely happens. Before claiming done, score the work 1-10. Below 8 means finish or explicitly flag what is deferred and why. ## Evidence Before Claims Never assert that something works, passes, or is fixed without fresh proof. Run the command, read the full output, verify exit code, then report. Lint passing is not tests passing. Type check is not a test run. A subagent reporting DONE is a claim, not evidence - read the diff. ## Root Cause Before Fix No fixes without root-cause investigation. Do not guess. Reproduce the failure, examine recent changes, form a specific hypothesis, test with a minimal change. After three failed attempts on the same issue, stop and question your assumptions - you are debugging the wrong layer or the architecture is the problem. ## Completion Status End subagent reports with one of four states: DONE, DONE_WITH_CONCERNS, BLOCKED, NEEDS_CONTEXT. Do not return free-form summaries the dispatcher has to re-parse. If you have doubts about your own work, say so - DONE_WITH_CONCERNS exists for exactly that case.