# my 2nd response to TLDR - Prompts don't scale. MCPs don't scale. Hooks do.

> [reddit/1pet056](https://www.reddit.com/r/ClaudeAI/comments/1pet056/)

You're right - those 3 examples are "lintable". I picked them because they're easy to explain.

**Context:** I'm building a macOS app ([yemreak-macos](https://github.com/yemreak/yemreak-macos)) - voice dictation with clipboard context for AI conversations. This is my first Swift project, so I'm learning the patterns as I go. Every time I discover a rule that AI keeps violating, I write a hook for it. These examples come from that process.

**Think of hooks as a personalized linter** - project-specific rules that only make sense in YOUR codebase.

eslint = universal rules (syntax, types) hooks = YOUR rules (architecture, naming conventions, layer boundaries)

eslint can't know "this module shouldn't import that module based on my folder structure"

## Concrete Examples

### 1. Layer Enforcement (Transport can't call Capability)

**Source Code:**

```typescript
const CAPABILITY_PATTERN = /\w+Capability\.shared\./

export const noCapabilityInTransportRule: FileRule = {
  name: 'noCapabilityInTransport',
  active: true,

  check(params: FileRuleCheckParams): RuleCheckResult {
    // Only check Transport files
    const isTransport = params.filePath.includes('/Transport/')
                     || params.filePath.endsWith('HTTP.swift')
    if (!isTransport) return { matched: false }

    const match = params.content.match(CAPABILITY_PATTERN)
    if (match) {
      return {
        matched: true,
        violation: `[swift] noCapabilityInTransport violation

File: ${params.filePath}
Found: ${match[0]}

CONSTRAINT: Transport -> Intent (not Capability)

CORRECT:
await IntentExecutor.shared.trigger(.context(.action), source: .http)`
      }
    }
    return { matched: false }
  }
}
```

**What AI sees when blocked:**

```
[swift] noCapabilityInTransport violation

File: MenuBar.swift
Found: AudioCapability.shared.start()

CONSTRAINT: Transport -> Intent (not Capability)

CORRECT:
await IntentExecutor.shared.trigger(.context(.action), source: .http)
```

### 2. Architecture Injection on First Read

**Source Code:**

```typescript
export const architectureRule: GeneralRule = {
  key: 'architecture',
  toolMatcher: '^(Read|Write|Edit)$',

  async decision(params: RuleContext): Promise<RuleResult> {
    const archFiles = await findArchitectureFiles({
      cwd: params.cwd,
      filePath: filePathResult.path,
      scopeType // Read or Write
    })
    if (archFiles.length === 0) return { type: 'pass' }

    // Check if already reminded this session
    const reminded = await loadReminded({ cwd: params.cwd, sessionId: params.sessionId })
    const newFiles = archFiles.filter(f => !reminded.includes(f.path))
    if (newFiles.length === 0) return { type: 'pass' }

    // Mark as reminded, then block with architecture content
    for (const f of newFiles) {
      await markReminded({ cwd: params.cwd, sessionId: params.sessionId, archPath: f.path })
    }

    return {
      type: 'violation',
      decision: 'deny',
      reason: formatArchitectureAction(newFiles) // <be-aware>...</be-aware>
    }
  }
}
```

**What AI sees when blocked:**

```
<be-aware from="macos/Sources/Contexts/ARCHITECTURE.md">
Transport → Intent → Capability → (return) → Intent → Feedback

Transport: Keyboard entry, menu clicks, HTTP handlers
Intent: Single entry point, orchestration
Capability: External side effects (audio, API, file)
</be-aware>
<retry>Retry action with that awareness</retry>
```

### 3. Locality Enforcement (MVI Pattern)

**Source Code:**

```typescript
const LOCALITY: Record<LocalityType, LocalityConfig> = {
  hotkeyLocality: {
    patterns: [/\.keyboardShortcut\s*\(/, /addLocalMonitorForEvents/],
    allowedSuffix: 'Hotkeys.swift',
    allowedExceptions: ['HotkeyCapability.swift'],
    message: 'Hotkey code must be in *Hotkeys.swift'
  },

  panelEscape: {
    patterns: [/\.onKeyPress\s*\(\s*\.escape/, /\.keyboardShortcut\s*\(\s*\.escape/],
    allowedSuffix: '__NONE__',
    allowedExceptions: ['BasePanelController.swift'],
    message: 'Panel cannot handle Escape - use Tier 2'
  }
}

// In check():
for (const [localityType, config] of Object.entries(LOCALITY)) {
  if (fileName.endsWith(config.allowedSuffix)) continue
  if (config.allowedExceptions.includes(fileName)) continue

  for (const pattern of config.patterns) {
    if (pattern.test(line)) {
      return { matched: true, violation: generateLocalityViolation(...) }
    }
  }
}
```

**What AI sees when blocked:**

```
[swift] OnMviViolation (panelEscape)

File: DictationPanel.swift
CONSTRAINT: Panel cannot handle Escape - use Tier 2

Found:
Line 23: .keyboardShortcut(.escape)

CORRECT:
Move to BasePanelController (Tier 2 handles Escape)
```

## Summary

| Category               | Example                             | Linter can do?      |
| ---------------------- | ----------------------------------- | ------------------- |
| Layer enforcement      | Transport can't call Capability     | No                  |
| Architecture injection | Show ARCHITECTURE.md on first read  | No                  |
| Locality               | Hotkey code only in \*Hotkeys.swift | No                  |
| Lint-like              | no-magic-number                     | Yes (examples only) |
