Injects steering rules into context based on event type and keyword matching
Injects individual steering rule files into context based on event type and keyword matching. Rules are .md files with YAML frontmatter declaring when they should fire. Each rule injects at most once per session, tracked via a gitignored JSON file.
Registered for SessionStart, UserPromptSubmit, PreToolUse, PostToolUse, SubagentStart, and Stop. Skips subagent sessions.
SessionStart — always-inject rules with empty keywords fire at session initializationUserPromptSubmit — keyword-matched against prompt text; rules with matching keywords inject as contextPreToolUse — keyword-matched against tool_name and file_path; returns SyncHookJSONOutput with hookSpecificOutput.additionalContext and hookEventName: "PreToolUse"PostToolUse — keyword-matched against tool_name and file_path; returns SyncHookJSONOutput with hookSpecificOutput.additionalContext and hookEventName: "PostToolUse"SubagentStart — always-inject rules with empty keywords fire when a subagent is spawnedStop — keyword-matched against last_assistant_message; blocks with matched rule as reason (Stop hooks cannot inject context)SessionStart for rules with empty keywords (always-inject)UserPromptSubmit when a rule's keywords match the prompt text (case-insensitive substring)PreToolUse when rule keywords match tool_name or file path (case-insensitive substring)PostToolUse when rule keywords match tool_name or file path (case-insensitive substring)SubagentStart for rules with empty keywords (always-inject)Stop when rule keywords match last_assistant_message text (case-insensitive substring)It does not fire when:
enabled: falseUserPromptSubmit/Stop, rules with empty keywords are skipped (must have at least one keyword)PreToolUse/PostToolUse, rules with empty keywords are skipped (must have at least one keyword)Reads config from hookConfig.steeringRuleInjector (with defaults)
Resolves rule files from includes glob patterns (supports ${ENV_VAR} expansion)
Parses YAML frontmatter from each file to extract name, events, and keywords
Filters rules by current event type
Filters by keyword match — prompt text for UserPromptSubmit; tool_name + file_path + skill for PreToolUse/PostToolUse; last_assistant_message for Stop; empty keywords pass through for SessionStart and SubagentStart
Checks per-session injection tracker — skips already-injected rules
Concatenates matched rule bodies into output — SyncHookJSONOutput with hookSpecificOutput.additionalContext for context-injecting events; { decision: "block", reason } for Stop events (Stop hooks cannot inject context)
Records injected rules to tracker file at {trackerDir}/injections-{sessionId}.json
SessionStart in their events and empty keywords. Those rules inject as context from the first interaction.git-safety rule declares keywords [push, remote, origin]. "push" matches, so the rule injects. On the next prompt mentioning "push", the tracker prevents re-injection.PreToolUse event fires with tool_name: Edit and file_path: src/styles/theme.css. The browser-mandatory rule declares keywords [.css, styles]. ".css" matches the file path, so the rule injects as additionalContext before the tool runs.SubagentStart event fires when spawning a new agent. Rules with SubagentStart in their events and empty keywords inject automatically, surfacing least-privilege and role-boundary rules at the start of every subagent session.Stop event fires with last_assistant_message containing that text. The always-proper-fix rule declares keywords [quick fix, workaround]. "quick fix" matches, so the hook blocks with the rule as the reason, forcing Claude to retry without presenting shortcuts.lib/hook-config — reads hookConfig.steeringRuleInjector from settings.jsonlib/environment — isSubagent() to skip subagent sessionslib/paths — getPaiDir() for tracker file locationcore/adapters/fs — readFile, readJson, writeJson, fileExists for rule and tracker I/Ocore/types/hook-input-schema — Effect Schema for discriminated input parsing (replaces field-sniffing)Bun.Glob — resolves include patterns to file pathsThe contract logs fallback events to stderr via deps.stderr:
resolveEvent logs when Effect Schema parsing fails and falls back to "SessionStart"getMatchText logs when input parsing fails and falls back to empty stringreadTracker logs when tracker JSON parsing fails and falls back to empty trackerThis surfaces silent failures for debugging while preserving fail-open behavior.