my 2nd response to TLDR - Prompts don't scale. MCPs don't scale. Hooks do.
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) - 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:
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:
2. Architecture Injection on First Read
Source Code:
What AI sees when blocked:
3. Locality Enforcement (MVI Pattern)
Source Code:
What AI sees when blocked:
Summary
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)
Last updated
Was this helpful?