Coding Principles
Seven universal principles for writing production-quality code.
Apply these to every line, in every language. They are not a
post-hoc checklist — they shape decisions AS you write.
Stack skills (react, flutter, nextjs, etc.) add language-specific
idioms on top. These principles are the foundation.
Principle 1: Clarity Over Cleverness
Write code a stranger can read without asking you what it does.
Do:
- Name things for what they DO:
fetchActiveUsers(), isExpired,
MAX_RETRY_ATTEMPTS
- Functions do one thing. If you need "and" to describe it, split it.
- Comments explain WHY, not WHAT. The code says what; the comment
says why it's surprising or non-obvious.
- Prefer explicit over implicit.
if (user.role === "admin") not
if (checkAccess(user, 2)).
Don't:
- Magic numbers:
if (retries > 3) → if (retries > MAX_RETRIES)
- Clever one-liners that save a line but cost a minute to read.
- Names like
data, temp, flag2, processStuff, handleIt.
- Nested ternaries. Ever.
Principle 2: Fail Loudly, Recover Gracefully
Every external call can fail. Handle it explicitly. Never swallow
errors. Give the caller something useful when things go wrong.
Do:
- Every
fetch, query, read, parse has error handling.
- Error messages include: what happened, what was expected, what to
do about it.
- Async operations have loading, success, AND error states. No async
without all three.
- Distinguish recoverable (retry, fallback) from fatal (log, alert,
stop).
Don't:
- Empty catch blocks. Ever. If you truly ignore an error, comment
WHY.
console.log(err) as the only error handling.
- "Failed to load" with no context. WHO failed to load WHAT and WHY.
- Retrying fatal errors. Crashing on recoverable ones.
Principle 3: Guard the Boundaries
Validate at every entry point. Don't trust input from users, APIs,
files, or even your own internal modules.
Do:
- Public function parameters: validate type, range, presence.
- API responses: check shape before accessing nested fields.
response?.data?.user?.id not response.data.user.id.
- User input: validate before processing. Reject early, clearly.
- Configuration: fail at startup if config is invalid, not at 3am
when the missing value is first accessed.
- Database results: handle empty results, null fields, unexpected types.
Don't:
- Trust that an API response has the shape you expect.
- Access nested properties without null checks.
- Process user input without validation.
- Assume config values exist without checking.
Principle 4: Smallest Scope, Shortest Lifetime
Variables close to where they're used. Functions close to what
calls them. Reduce the blast radius of every change.
Do:
- Declare variables at first use, not at the top.
- Prefer local over global. Prefer parameters over shared state.
- Prefer pure functions (same input → same output) where practical.
- Keep functions short. If you're scrolling, it's too long. Extract.
- Modules have one reason to change.
Don't:
- Declare all variables at the top of the function.
- Use global state when a parameter would work.
- Write 200-line functions. Extract logical sections.
- Put unrelated functionality in the same module because
"it's convenient."
Principle 5: Make the Right Thing Easy, the Wrong Thing Hard
Design APIs and interfaces so correct usage is obvious and misuse
requires effort.
Do:
- Required parameters come first. Optional parameters have defaults.
- Return types that force the caller to handle success and failure.
- Use the type system: enum not string, branded types for IDs,
non-nullable when null is invalid.
- Impossible states should be unrepresentable.
Don't:
- Return
null to mean both "failed" and "empty."
- Accept
any or untyped dictionaries for structured data.
- Design functions where the caller must remember to check a flag.
- Allow invalid state combinations that crash at runtime.
Principle 6: Consistency Beats Perfection
Match the existing codebase. Consistency across the project matters
more than your personal preference.
Do:
- Read existing code before writing new code. Match patterns.
- Follow the project's error handling pattern.
- Use the project's existing utilities before writing new ones.
- If the project has no patterns, establish one and follow it.
- Match naming: if the project uses
camelCase, use camelCase.
Don't:
- Introduce a new style in your files because "it's better."
- Write a utility function when one already exists in the project.
- Mix patterns: callbacks in one file, promises in another,
async/await in a third.
Principle 7: Test What Matters, Not What's Easy
Write tests that catch bugs, not tests that inflate coverage numbers.
Do:
- Test the contract: "given X input, expect Y output."
- Test boundaries: min, max, zero, empty, one, many.
- Test error paths: invalid input, timeout, permission denied.
- One assertion per test when practical.
- Test names describe the scenario:
test_expired_token_returns_401.
Don't:
- Test implementation details: "function calls helper A then B."
- Test only the happy path.
- Name tests
test_1, test_auth_3, test_new.
- Write tests that pass regardless of the implementation being correct.
- Mock everything — some integration is worth testing.
How This Loads
Build-loop loads this capability at Step 3, before each task:
Sage: Loading coding principles for implementation.
Following: clarity, error handling, boundary guards, minimal scope,
safe APIs, consistency, behavior testing.
These principles are active for every line written during the task.
They are NOT a post-hoc checklist — they shape the code as it's
written.
The announcement Loading coding principles for implementation. is the
compliance marker: it MUST appear before each task's implementation, making the
standard active and observable rather than assumed.
Rationalization table
Derived from the RED baseline in TESTS.md — the excuses for skipping the load
step and just coding. The marker must appear on every task, no size exception.
| The excuse (observed) |
Why it's wrong |
The rule |
| "It's a tiny change, principles don't matter." |
Small changes are exactly where magic numbers and swallowed errors slip in unnoticed. |
Principles load before every task — there is no size exception. |
| "I know clean code by heart." |
The load isn't a reminder for you; it makes the standard active and observable for the task. |
Announce and apply on every task, not only when you feel you need it. |
| "I'll clean it up in review." |
Principles shape code as it's written; review only catches what shaping would have prevented. |
They are a mindset during implementation, not a post-hoc checklist. |
| "The stack skill already covers quality." |
Stack idioms sit on top of the universal foundation — they don't replace it. |
Both apply; principles are the foundation. |
Relationship to Other Capabilities
- Stack skills (react, flutter, nextjs) add language-specific
idioms. Principles provide the universal foundation.
- TDD capability drives the test-first workflow. Principle 7
guides WHAT to test within that workflow.
- quality-review (Gate 3) reviews AFTER implementation.
Principles guide DURING implementation. Both are needed.
- auto-QA verifies code against spec. Principles ensure the
code is well-crafted regardless of spec compliance.
Rules
- Principles apply to ALL languages. No language-specific rules here.
- Principles guide, they don't block. Pragmatic exceptions are fine
when explicitly justified.
- When principles conflict with project conventions, conventions win
(Principle 6).
- When principles conflict with each other, clarity wins (Principle 1).
1---2name: coding-principles3description: Use during implementation — loaded by build-loop before each task — whenever code is being written and should hold to universal quality standards, regardless of language or stack. Active while writing, not a post-hoc review checklist.4---56<!-- sage-metadata7cost-tier: sonnet8activation: auto9tags: [execution, quality, principles, coding, implementation]10inputs: [plan-task, codebase-context]11outputs: [implementation]12requires: []13-->1415# Coding Principles1617Seven universal principles for writing production-quality code.18Apply these to every line, in every language. They are not a19post-hoc checklist — they shape decisions AS you write.2021Stack skills (react, flutter, nextjs, etc.) add language-specific22idioms on top. These principles are the foundation.2324## Principle 1: Clarity Over Cleverness2526Write code a stranger can read without asking you what it does.2728**Do:**29- Name things for what they DO: `fetchActiveUsers()`, `isExpired`,30 `MAX_RETRY_ATTEMPTS`31- Functions do one thing. If you need "and" to describe it, split it.32- Comments explain WHY, not WHAT. The code says what; the comment33 says why it's surprising or non-obvious.34- Prefer explicit over implicit. `if (user.role === "admin")` not35 `if (checkAccess(user, 2))`.3637**Don't:**38- Magic numbers: `if (retries > 3)` → `if (retries > MAX_RETRIES)`39- Clever one-liners that save a line but cost a minute to read.40- Names like `data`, `temp`, `flag2`, `processStuff`, `handleIt`.41- Nested ternaries. Ever.4243## Principle 2: Fail Loudly, Recover Gracefully4445Every external call can fail. Handle it explicitly. Never swallow46errors. Give the caller something useful when things go wrong.4748**Do:**49- Every `fetch`, `query`, `read`, `parse` has error handling.50- Error messages include: what happened, what was expected, what to51 do about it.52- Async operations have loading, success, AND error states. No async53 without all three.54- Distinguish recoverable (retry, fallback) from fatal (log, alert,55 stop).5657**Don't:**58- Empty catch blocks. Ever. If you truly ignore an error, comment59 WHY.60- `console.log(err)` as the only error handling.61- "Failed to load" with no context. WHO failed to load WHAT and WHY.62- Retrying fatal errors. Crashing on recoverable ones.6364## Principle 3: Guard the Boundaries6566Validate at every entry point. Don't trust input from users, APIs,67files, or even your own internal modules.6869**Do:**70- Public function parameters: validate type, range, presence.71- API responses: check shape before accessing nested fields.72 `response?.data?.user?.id` not `response.data.user.id`.73- User input: validate before processing. Reject early, clearly.74- Configuration: fail at startup if config is invalid, not at 3am75 when the missing value is first accessed.76- Database results: handle empty results, null fields, unexpected types.7778**Don't:**79- Trust that an API response has the shape you expect.80- Access nested properties without null checks.81- Process user input without validation.82- Assume config values exist without checking.8384## Principle 4: Smallest Scope, Shortest Lifetime8586Variables close to where they're used. Functions close to what87calls them. Reduce the blast radius of every change.8889**Do:**90- Declare variables at first use, not at the top.91- Prefer local over global. Prefer parameters over shared state.92- Prefer pure functions (same input → same output) where practical.93- Keep functions short. If you're scrolling, it's too long. Extract.94- Modules have one reason to change.9596**Don't:**97- Declare all variables at the top of the function.98- Use global state when a parameter would work.99- Write 200-line functions. Extract logical sections.100- Put unrelated functionality in the same module because101 "it's convenient."102103## Principle 5: Make the Right Thing Easy, the Wrong Thing Hard104105Design APIs and interfaces so correct usage is obvious and misuse106requires effort.107108**Do:**109- Required parameters come first. Optional parameters have defaults.110- Return types that force the caller to handle success and failure.111- Use the type system: enum not string, branded types for IDs,112 non-nullable when null is invalid.113- Impossible states should be unrepresentable.114115**Don't:**116- Return `null` to mean both "failed" and "empty."117- Accept `any` or untyped dictionaries for structured data.118- Design functions where the caller must remember to check a flag.119- Allow invalid state combinations that crash at runtime.120121## Principle 6: Consistency Beats Perfection122123Match the existing codebase. Consistency across the project matters124more than your personal preference.125126**Do:**127- Read existing code before writing new code. Match patterns.128- Follow the project's error handling pattern.129- Use the project's existing utilities before writing new ones.130- If the project has no patterns, establish one and follow it.131- Match naming: if the project uses `camelCase`, use `camelCase`.132133**Don't:**134- Introduce a new style in your files because "it's better."135- Write a utility function when one already exists in the project.136- Mix patterns: callbacks in one file, promises in another,137 async/await in a third.138139## Principle 7: Test What Matters, Not What's Easy140141Write tests that catch bugs, not tests that inflate coverage numbers.142143**Do:**144- Test the contract: "given X input, expect Y output."145- Test boundaries: min, max, zero, empty, one, many.146- Test error paths: invalid input, timeout, permission denied.147- One assertion per test when practical.148- Test names describe the scenario: `test_expired_token_returns_401`.149150**Don't:**151- Test implementation details: "function calls helper A then B."152- Test only the happy path.153- Name tests `test_1`, `test_auth_3`, `test_new`.154- Write tests that pass regardless of the implementation being correct.155- Mock everything — some integration is worth testing.156157## How This Loads158159Build-loop loads this capability at Step 3, before each task:160161```162Sage: Loading coding principles for implementation.163Following: clarity, error handling, boundary guards, minimal scope,164safe APIs, consistency, behavior testing.165```166167These principles are active for every line written during the task.168They are NOT a post-hoc checklist — they shape the code as it's169written.170171The announcement `Loading coding principles for implementation.` is the172compliance marker: it MUST appear before each task's implementation, making the173standard active and observable rather than assumed.174175## Rationalization table176177Derived from the RED baseline in `TESTS.md` — the excuses for skipping the load178step and just coding. The marker must appear on every task, no size exception.179180| The excuse (observed) | Why it's wrong | The rule |181|---|---|---|182| "It's a tiny change, principles don't matter." | Small changes are exactly where magic numbers and swallowed errors slip in unnoticed. | Principles load before every task — there is no size exception. |183| "I know clean code by heart." | The load isn't a reminder for you; it makes the standard active and observable for the task. | Announce and apply on every task, not only when you feel you need it. |184| "I'll clean it up in review." | Principles shape code as it's written; review only catches what shaping would have prevented. | They are a mindset during implementation, not a post-hoc checklist. |185| "The stack skill already covers quality." | Stack idioms sit on top of the universal foundation — they don't replace it. | Both apply; principles are the foundation. |186187## Relationship to Other Capabilities188189- **Stack skills** (react, flutter, nextjs) add language-specific190 idioms. Principles provide the universal foundation.191- **TDD capability** drives the test-first workflow. Principle 7192 guides WHAT to test within that workflow.193- **quality-review** (Gate 3) reviews AFTER implementation.194 Principles guide DURING implementation. Both are needed.195- **auto-QA** verifies code against spec. Principles ensure the196 code is well-crafted regardless of spec compliance.197198## Rules199200- Principles apply to ALL languages. No language-specific rules here.201- Principles guide, they don't block. Pragmatic exceptions are fine202 when explicitly justified.203- When principles conflict with project conventions, conventions win204 (Principle 6).205- When principles conflict with each other, clarity wins (Principle 1).