| name | rules-to-hook |
| description | Author and maintain .claude/context-rules.json — the declarative config for the context-inject hook. Scans the codebase to propose an initial rule set, then guides interactive rule addition. Use with /rules-to-hook.
|
| argument-hint | [add | list | remove <index>] |
Hook Context Rules
Author and maintain .claude/context-rules.json for the context-inject.mjs hook.
The engine supports both Claude Code and VS Code Copilot hook payload formats.
VS Code Copilot support
The engine auto-detects payloads from VS Code Copilot (camelCase fields, toolArgs
as JSON string). Key differences from Claude Code:
- Event name: VS Code doesn't include the event in the payload. Pass it as a
CLI argument:
node .claude/hooks/context-inject.mjs preToolUse. The engine
normalizes camelCase (preToolUse) to PascalCase (PreToolUse) used in rules.
- Tool input: VS Code sends
toolArgs (JSON string), not tool_input (object).
The engine parses it automatically.
- Output: VS Code only supports
permissionDecision on preToolUse — flat
JSON, no hookSpecificOutput wrapper. Context injection (text, hint,
learnings) has no effect on VS Code.
- Supported inject types on VS Code: only
block and allow (PreToolUse).
VS Code hook configuration
{
"hooks": {
"preToolUse": [{
"type": "command",
"bash": "node .claude/hooks/context-inject.mjs preToolUse"
}],
"postToolUse": [{
"type": "command",
"bash": "node .claude/hooks/context-inject.mjs postToolUse"
}]
}
}
Config Format
Rules are a JSON array. Each rule has an on event, a when filter (all keys optional,
implicit AND), and an inject payload (exactly one key).
[
{
"on": "PreToolUse",
"when": {
"tool": "Write|Edit|MultiEdit|replace_string_in_file|create_file|multi_replace_string_in_file",
"path": "src/core/**"
},
"inject": {
"text": "core/ is wiring only — no business logic here."
}
},
{
"on": "PostToolUse",
"when": { "tool": "Read|read_file", "path": "src/stages/**" },
"inject": { "hint": "docs/stages-overview.md" }
}
]
Block and allow rules control tool execution (PreToolUse only):
[
{
"on": "PreToolUse",
"when": { "tool": "Bash", "command": "rm -rf" },
"inject": { "block": "Destructive rm -rf is not allowed." }
},
{
"on": "PreToolUse",
"when": { "tool": "Read", "path": "docs/**" },
"inject": { "allow": "Safe: reading documentation." }
}
]
Precedence: block > allow > context. If any matched rule is a block,
the tool call is denied even if other rules allow or inject context.
on values
PreToolUse / PostToolUse / UserPromptSubmit / SessionStart / SubagentStart / PostToolUseFailure / Stop / PreCompact
The installer registers hooks for all 8 events automatically.
when keys (all optional, implicit AND)
| Key | Type | Notes |
|---|
tool | string | Pipe-separated alternatives: "Edit|Write" |
path | glob | Matched against file_path / path in tool input |
command | regex | Bash tool only — matched against command string |
prompt | regex | UserPromptSubmit only |
source | string | Pipe-separated; SessionStart source field |
agent_type | string | Pipe-separated; SubagentStart agent type |
error | regex | PostToolUseFailure error message |
content | regex | Write content or Edit new_string field |
response | regex | Matched against JSON.stringify(tool_response) |
Platform Tool Names
Claude Code and VS Code Copilot use different tool names. Always include both
sets in tool patterns to ensure rules fire on both platforms.
| Action | Claude Code | VS Code Copilot |
|---|
| Write | Write | create_file |
| Edit | Edit | replace_string_in_file |
| Multi-edit | MultiEdit | multi_replace_string_in_file |
| Read | Read | read_file |
| Shell | Bash | run_in_terminal |
For write rules: Write|Edit|MultiEdit|replace_string_in_file|create_file|multi_replace_string_in_file
For read rules: Read|read_file
inject keys (exactly one)
| Key | Behavior | Events | VS Code |
|---|
text | Injected verbatim as additionalContext | All | No effect |
hint | Prefixed with "Related: " — agent decides whether to read | All | No effect |
learnings | Injects file-matched learnings from .claude/learnings.json | All | No effect |
block | Denies the tool call or blocks the event; value is the reason | PreToolUse, PostToolUse, UserPromptSubmit, Stop | Works (PreToolUse only) |
allow | Auto-approves the tool call; value is the reason shown to user | PreToolUse only | Works |
Block behavior per event
| Event | Block output | Notes |
|---|
PreToolUse | hookSpecificOutput.permissionDecision: 'deny' | Backward-compatible; denies the tool call |
PostToolUse | { decision: 'block', reason, hookSpecificOutput } | Blocks with optional context |
UserPromptSubmit | { decision: 'block', reason, hookSpecificOutput } | Blocks with optional context |
Stop | { decision: 'block', reason } | Prevents agent from stopping (no HSO) |
Stop safety guard
When a Stop hook blocks (meaning "don't stop, continue"), the agent retries stopping
with stop_hook_active: true in the payload. The engine detects this flag and exits
silently (process.exit(0)) before matching rules, allowing the agent to stop and
preventing infinite loops.
Learnings System
Agents can record file-bound learnings that are injected as context when other agents
interact with those files.
Storage
Learnings are stored in .claude/learnings.json:
[
{
"files": ["src/stages/**"],
"learning": "Stage transforms must use AST nodes, not string manipulation",
"timestamp": "2026-03-04T10:30:00Z"
}
]
Recording learnings (CLI)
node .claude/hooks/learn.mjs add --files 'src/stages/**' --learning 'Use AST nodes only'
node .claude/hooks/learn.mjs add --files 'src/core/**,src/stages/**' --learning 'Cross-cutting concern'
node .claude/hooks/learn.mjs list
node .claude/hooks/learn.mjs list --files 'src/stages/elision.ts'
node .claude/hooks/learn.mjs remove --index 0
node .claude/hooks/learn.mjs update --index 0 --learning 'Updated insight'
node .claude/hooks/learn.mjs repath --index 0 --files 'src/new-path/**'
node .claude/hooks/learn.mjs check
When adding, the CLI shows overlapping existing learnings so you can curate
(merge, update, or remove redundant entries).
When renaming or deleting files, run check to find orphaned learnings,
then repath or remove to fix them.
Injection
The learnings inject type in a context rule triggers lookup:
{
"on": "PostToolUse",
"when": { "tool": "Read|read_file", "path": "**" },
"inject": { "learnings": true }
}
When matched, the engine reads .claude/learnings.json, finds entries whose files
globs match the current file path, and injects them as additionalContext:
[Learnings for src/stages/elision.ts]
- Stage transforms must use AST nodes, not string manipulation
- Elision uses line-count thresholds
Prompt reminders
A UserPromptSubmit context rule reminds agents to record learnings on every
prompt. It has no when filter, so it fires unconditionally. This rule is
installed automatically by the installer.
Phase 0 — Bootstrap
Runs before any other phase, every time the skill is invoked.
Detection
Check whether the hook engine is installed:
.claude/hooks/context-inject.mjs exists
.claude/hooks/platform.mjs exists
.claude/hooks/learn.mjs exists
.claude/hooks/harness-eval.mjs exists
.claude/hooks/harness-format.mjs exists
.claude/settings.json has context-inject.mjs registered in all 8 events (PreToolUse, PostToolUse, UserPromptSubmit, SessionStart, SubagentStart, PostToolUseFailure, Stop, PreCompact)
.claude/hooks/node_modules/minimatch exists (dependency installed)
.claude/learnings.json exists
If all eight pass → skip to Phase 1.
If any is missing → run the installer:
node skills/rules-to-hook/install.mjs
The installer is idempotent. It:
- Creates
.claude/hooks/ and copies the engine from skills/rules-to-hook/engine.mjs
- Copies the platform module from
skills/rules-to-hook/platform.mjs
- Copies the learn CLI from
skills/rules-to-hook/learn.mjs
- Copies harness modules from
skills/rules-to-hook/harness-eval.mjs and skills/rules-to-hook/harness-format.mjs
- Ensures
package.json has minimatch and installs dependencies
- Merges hook registrations into
.claude/settings.json (all 8 events, .* matcher)
- Seeds an empty
.claude/context-rules.json if absent
- Seeds an empty
.claude/learnings.json if absent
- Seeds learnings context rules (injection on Read + prompt reminder) into
context-rules.json
After the installer completes, confirm:
"Hook engine installed. Proceeding to auto-discovery."
If the installer fails, stop and report the error — do not continue to later phases.
Phase 1 — Auto-discovery
Runs by default (and always first, even when invoked with add).
Discovery proceeds in five layers dispatched to subagents to keep the
main context clean. Only structured summaries flow back.
Do not propose rules until all layers complete.
Dispatch plan
Batch 1 (parallel): Layer 1 · Layer 2 · Layer 3
Batch 2 (parallel): Layer 3.5 · Layer 4 — both need Layer 3 output
Batch 3 (sequential): Layer 5 — needs Layer 3.5 + Layer 4 output
Create a tracked task for each batch (not each layer).
Batch 1 — Gather project data (3 parallel subagents)
Dispatch three general-purpose Task subagents in a single message.
Each prompt must include the project root path and end with:
"Return a structured summary only — not raw file contents."
Subagent → Layer 1: Project identity & toolchain
Prompt must instruct the subagent to:
- Glob
*.md at project root and read each file — extract purpose,
guidelines, invariants.
- Read the package manifest (
package.json, pyproject.toml,
Cargo.toml, or equivalent) — extract scripts, runtime, key deps.
- Read config files (
biome.json, .eslintrc*, tsconfig.json,
prettier.config.*, etc.) — note codified conventions.
Expected return:
- Purpose (one sentence)
- Runtime & package manager
- Key dependencies (name → role)
- Codified conventions (list)
Subagent → Layer 2: Architecture map
Prompt must instruct the subagent to:
- Run
npx @anduril-code/ctx tree --depth 3 — identify source, doc,
config, and test dirs.
- Glob
src/**/index.{ts,js} (or language equivalent) — read barrel
files for module boundaries and public surfaces.
- Check for
types/, interfaces/, or similar dirs — confirm whether
pure declarations (no runtime code, no cross-imports).
Expected return:
- Directory roles (table: dir → purpose)
- Module boundaries (what each barrel exports)
- Type-only directories (if any)
Subagent → Layer 3: Documentation deep-dive
Prompt must instruct the subagent to:
- Run
npx @anduril-code/ctx rank "architecture constraints invariants style" --maxResults 10.
Read every result — do not guess from filenames.
- Glob
docs/**/*.md and read all remaining docs. For each doc:
a. Use npx @anduril-code/ctx sections <file> to list headings.
b. Use npx @anduril-code/ctx extract <file> --onlySections '<heading>'
to read each section individually.
c. For each section that contains actionable rules or constraints, produce
a section record:
section — the heading name
file — source doc path
content — condensed actionable rules (max 500 chars, drop examples/rationale)
keywords — 3-5 code-relevant terms (identifiers, directory names,
patterns) that would appear in files this section governs
- Skip sections that are purely informational (overview, changelog, credits).
Expected return:
- Array of section records (JSON), each with
section, file, content, keywords
Wait for all three subagents. Collect their outputs.
Batch 2 — Path affinity + enforcement audit (2 parallel subagents)
Dispatch two general-purpose Task subagents in a single message.
Paste the section records from Layer 3 into both prompts.
Subagent → Layer 3.5: Path affinity via codebase grep
Prompt must instruct the subagent to:
- For each section record, Grep the codebase for each keyword.
- For each keyword hit, note which top-level source directory contains it
(e.g.
src/api, src/components, tests).
- Rank directories by hit density: hits for this section's keywords
divided by total files in the directory.
- Assign the top 1-3 directories as path globs (e.g.
src/api/**).
- If a section's keywords match across 5+ top-level directories with no
clear winner (no directory has >30% of hits), assign
src/** and flag
as broad: true.
- If a section's keywords produce zero hits, flag as
noMatch: true.
Expected return:
- Enriched section records: original fields plus
paths (string array of
globs) and optional broad / noMatch flags
Subagent → Layer 4: Enforcement audit
Prompt must instruct the subagent to:
- Read every file in
.claude/hooks/ — source code, not just filenames.
Understand what each hook enforces (blocks, rewrites, denies, injects).
- Read
.claude/context-rules.json if it exists — list every rule.
- Cross-reference each section record from Layer 3 against existing
hooks and rules. Mark each as:
- Enforced — already covered → skip
- Unenforced — no coverage → candidate
- Partial — partially covered → may need complementary rule
Expected return:
- Existing hooks summary (what each enforces)
- Existing rules list
- Section status table (section → Enforced / Unenforced / Partial)
Wait for both subagents. Collect their outputs.
Batch 3 — Verify & derive candidates (1 subagent)
Dispatch one general-purpose Task subagent.
Paste the enriched section records from Layer 3.5 and the section status
table from Layer 4 into the prompt.
Subagent → Layer 5: Verification & per-section rule generation
Prompt must instruct the subagent to:
- Filter to unenforced / partial sections only (from Layer 4 status).
- For every section that asserts a codebase property (e.g. "types/ is
pure", "no imports from cli/"), use Grep to confirm it actually holds.
Drop any section whose assertions do not hold.
- For each verified section, emit one rule per path in its
paths
array:
{
"on": "PreToolUse",
"when": { "tool": "Write|Edit|MultiEdit|replace_string_in_file|create_file|multi_replace_string_in_file", "path": "<glob>" },
"inject": { "text": "<Section Name>: <condensed content>" }
}
- Prefix
text with the section name for self-describing context.
- Cap
text at 500 characters. If longer, condense to actionable
rules only (drop examples and rationale).
- For each verified section, also emit one PostToolUse Read rule per
path in its
paths array:
{
"on": "PostToolUse",
"when": { "tool": "Read|read_file", "path":
Expected return:
- Verified sections (with Grep evidence)
- Candidate rules (JSON array, each annotated with source section,
file, path, and any flags)
Wait for completion.
Presentation
Use the Layer 5 output to present a numbered table of candidate rules.
Each row must include:
- Scope — the path glob this rule targets
- The rule (compact JSON or summary)
- Source — which doc/file and section established the constraint
- Verification — what Grep/check confirmed it
- Flags —
broad or no match if applicable
# on scope inject source
───────────────────────────────────────────────────────────────────────────
0 PreToolUse src/api/** text: "API Design: Use camel…" docs/style.md §API Design
1 PostToolUse src/api/** hint: docs/style.md docs/style.md §API Design
2 PreToolUse src/components/** text: "Components: Functional…" docs/style.md §Components
3 PostToolUse src/components/** hint: docs/style.md docs/style.md §Components
4 PreToolUse **/*.test.* text: "Test Patterns: descri…" docs/style.md §Test Patterns
5 PostToolUse **/*.test.* hint: docs/style.md docs/style.md §Test Patterns
6 PreToolUse src/** (broad) text: "Naming: Use snake_cas…" docs/style.md §Naming
7 PostToolUse src/** (broad) hint: docs/style.md docs/style.md §Naming
Ask: "Accept all (a), pick by number (e.g. 1,3), or skip (s)?"
For rules flagged (no match), prompt: "No matching code found for
'
'. Assign a path glob or skip? (glob / s)"
Write accepted rules to .claude/context-rules.json (create if absent,
merge if exists).
Phase 2 — User Additions
After auto-discovery (or when invoked with add), ask: "Anything else to add? (y/n)"
If yes, collect one answer at a time:
-
Event — PreToolUse / PostToolUse / UserPromptSubmit / SessionStart / SubagentStart / PostToolUseFailure / Stop
-
Trigger keys — show only valid keys for the chosen event:
- PreToolUse / PostToolUse:
tool, path, command
- UserPromptSubmit:
prompt
-
Inject type — text / hint / block / allow
- For
block: available on PreToolUse, PostToolUse, UserPromptSubmit, Stop
- For
allow: only available when event is PreToolUse
-
Inject content:
- For
hint: list existing .md files nearby and suggest one
-
Preview the JSON rule and ask: "Add this rule? (y/n)"
-
On confirm, append to .claude/context-rules.json.
Repeat until user says no.
Phase 3 — List
When invoked with list, display current rules as an ASCII table:
# on when inject
─────────────────────────────────────────────────────────────────
0 PreToolUse tool=Edit|Write path=src/** text: "..."
1 PostToolUse tool=Read path=src/stages/** hint: docs/...
2 UserPromptSubmit prompt=test text: "..."
If .claude/context-rules.json does not exist, print: No rules found.
Phase 4 — Remove
When invoked with remove <index>:
- Show the full rule at that index.
- Ask: "Remove this rule? (y/n)"
- On confirm, remove the entry and rewrite the file.
If the index is out of range, print: Index <n> is out of range (0–<max>).
Phase 5 — Smoke Test