Skip to content

Hooks and Guardrails

Ali Sadeghi edited this page May 28, 2026 · 3 revisions

Hooks & Guardrails

KMPilot ships two Claude Code hooks that enforce the architecture without you having to think about it. Both live in .claude/hooks/ and are wired up by .claude/settings.json (committed to the repo — no per-developer setup).

Hook Type What it does
protect-feature-files.sh PreToolUse (matcher Edit|Write) Blocks direct edits to feature/ source files unless an implementation skill is active
reinject-on-compact.sh SessionStart (matcher compact) Re-injects the 11 critical rules and 4 integration points after Claude compacts the conversation, and clears any stale skill marker

Feature File Protection

A PreToolUse hook intercepts every Edit or Write and checks the target path. If the path is under feature/, the edit is blocked with:

Blocked: Cannot edit feature source files directly.
Use /creating-kmp-feature or /modifying-kmp-feature skill first.

What's bypassed

The hook allows the edit through (no skill marker required) for:

  • */commonTest/*, */desktopTest/*, */androidTest/*, */test/* — test files
  • Any build.gradle.kts — so test-dependency setup and integration agent work

These exemptions exist because the test generation agents need to write directly and the integration agent needs to edit gradle files.

How a skill bypasses the lock

An implementation skill activates a marker file before editing, then removes it on completion:

# Activate
touch /tmp/.claude-kmpilot-skill-active

# (… do work …)

# Cleanup
rm -f /tmp/.claude-kmpilot-skill-active
Aspect Value
Marker path /tmp/.claude-kmpilot-skill-active
Staleness window 2 hours — older markers are auto-removed by the hook
Skills that activate it /creating-kmp-feature, /modifying-kmp-feature
Tools allowlisted to manage it Bash(touch:*), Bash(rm -f /tmp/.claude-kmpilot-skill-active)

Test agents write test files directly (bypassed by path rule), so they do not need the marker.

Context Re-injection on Compact

In long conversations Claude Code automatically compacts earlier messages to free context. During compaction, project-specific rules can disappear. The SessionStart hook with the compact matcher runs reinject-on-compact.sh, which:

  1. Removes any stale /tmp/.claude-kmpilot-skill-active marker.
  2. Prints the 11 critical rules and 4 integration points so they re-enter context.

So Claude never "forgets" the project's patterns mid-session.

What gets re-injected

11 Rules:
1. Interface + Impl pairs for DataSource/Repository
2. Either<T> for errors - NEVER throw exceptions
3. setState { copy() } - NEVER _uiModel.value =
4. 4 UI states: Uninitialized / Loading / Success / Failed
5. X-components from :core:designsystem - NO Material3
6. ImmutableList with .toImmutableList()
7. Lowercase packages only
8. DI: singleOf(::Impl).bind<Interface>() + BaseFeature
9. No UseCases - ViewModels call repositories directly
10. Callback params (onBackClick) - not navController
11. Single *UiModel + DTO-wrapped UiState<T> - NO *UiState.kt; data/ never imports presentation/

4 Integration Points (all required):
1. settings.gradle.kts - include(":feature:{name}")
2. composeApp/build.gradle.kts - implementation(project(":feature:{name}"))
3. initKoin.kt - {Feature}Modules.initialize()
4. BaseAppNavHost.kt - {featurename}(onBackClick = {...})

Plus a pointer to @.claude/skills/_shared/patterns.md for the full reference.

Configuration

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Edit|Write",
        "hooks": [
          {
            "type": "command",
            "command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/protect-feature-files.sh"
          }
        ]
      }
    ],
    "SessionStart": [
      {
        "matcher": "compact",
        "hooks": [
          {
            "type": "command",
            "command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/reinject-on-compact.sh"
          }
        ]
      }
    ]
  }
}

$CLAUDE_PROJECT_DIR is set by Claude Code to the repository root, so the same settings.json works for every developer.

Why This Matters

Without the hooks, the architecture relies on Claude remembering the rules — which works most of the time, but failure modes are unpredictable. The hooks make the rules enforceable:

  • Skills are the only path that can edit feature/ source.
  • Long sessions don't drift away from the conventions.
  • Tests and gradle files remain editable to keep iteration fast.

If you ever see "Blocked: Cannot edit feature source files directly," that's the system working as designed. Run the relevant skill instead.

Back to Home

Clone this wiki locally