Principle of Least Astonishment (POLA) - ensure code changes behave as users and developers expect. Activate when making code changes, refactoring, modifying APIs, renaming functions/variables, changing file structure, or reviewing proposed implementations. Guides predictable, convention-following changes.
Auto-activate when: Making code changes, refactoring, modifying existing functions, changing APIs, renaming things, moving files, or reviewing implementations. Should activate alongside development-philosophy for any non-trivial changes.
Every change should be predictable to someone familiar with the codebase.
If a change would surprise a developer who knows the project, either:
Ask yourself:
Would this surprise someone reading a diff?
Does this follow existing patterns?
Is the scope what was requested?
Would the function/API do what its name suggests?
Asked: "Fix the null check in validateUser"
Did: Fixed null check + refactored 3 other functions + added logging
Why surprising: Reviewer expects a small fix, gets a large diff.
def get_user(id):
user = db.find(id)
user.last_accessed = now() # Mutation in a "get" function
db.save(user)
return user
Why surprising: "get" implies read-only; this writes.
# Existing codebase uses:
fetch_user(), fetch_orders(), fetch_products()
# New code adds:
retrieve_customer() # Different verb AND different noun
Why surprising: Breaks established vocabulary.
PR title: "Update README"
Files changed: README.md, config.py, utils.py, test_utils.py
Why surprising: Unrelated files modified.
# Before: returned empty list on error
# After: raises exception on error
Why surprising: Callers expecting old behavior will break.
Change only what's necessary. If you notice something else that needs fixing, mention it separately.
# Codebase uses "fetch_*" pattern
def fetch_payments(): # Follows convention
...
# Instead of hidden side effect:
def get_user(id):
return db.find(id)
def get_and_update_access(id): # Name reveals behavior
user = db.find(id)
user.last_accessed = now()
db.save(user)
return user
# Adding parameter with default preserves existing behavior
def process(data, validate=True): # Old callers unaffected
...
# Always return same type
def find_users(query) -> list[User]:
# Return [] not None when empty
return results or []
Sometimes breaking POLA is necessary. When it is:
| Situation | POLA-Compliant Approach | |-----------|------------------------| | Fixing a bug | Fix only that bug | | Adding a feature | Add only that feature | | Refactoring | Only when explicitly requested | | Renaming | Match existing conventions | | Changing return types | Add new function, deprecate old | | Modifying APIs | Backward-compatible by default |
Do what the code reader expects. Match existing patterns. Keep changes focused. No surprises.
Search for places (restaurants, cafes, etc.) via Google Places API proxy on localhost.
Interact with GitHub using the `gh` CLI. Use `gh issue`, `gh pr`, `gh run`, and `gh api` for issues, PRs, CI runs, and advanced queries.
Create or update AgentSkills. Use when designing, structuring, or packaging skills with scripts, references, and assets.
Start voice calls via the OpenClaw voice-call plugin.
Notion API for creating and managing pages, databases, and blocks.
Gemini CLI for one-shot Q&A, summaries, and generation.
Category:developer