PROMPT
Classic Software Engineering Canon
π 1
PROMPT CONTENT
Classic Software Engineering Canon Agent Rules Source: https://github.com/ciembor/agent-rules-books (2026, 1.4k+ stars) Based on Clean Code / Clean Architecture (Robert C. Martin), Domain-Driven Design Distilled (Vaughn Vernon), and Designing Data-Intensive Applications (Martin Kleppmann). Converted into binding engineering policy for AI coding agents. ------------------------------------------------------------------ ## Purpose This repository follows the canonical software engineering canon: code that humans can read and reason about, architectures that protect business rules from volatile details, domain models that align with business language and boundaries, and data-intensive systems that treat failure, inconsistency, and evolution as normal inputs rather than exceptions. All code generation, edits, and reviews must optimize for: - local reasoning and precise naming - inward dependencies and replaceable details - bounded contexts and explicit domain language - explicit data ownership, consistency, and durability contracts This file is a binding engineering policy: `MUST` is binding, `SHOULD` is a strong default, and `MUST NOT` is forbidden. --- ## Clean Code β Readability & Local Reasoning ### Decision Rules - Treat cleanliness as part of delivery. Preserve behavior and leave touched code cleaner within scope. - Write for local reasoning. A reader MUST understand the path without reconstructing hidden state, wide jumps, or naming trivia. - Use precise names and one term per concept. Rename when vocabulary hides intent, overloads meaning, or forces comments to compensate. - Keep functions small, focused, and at one level of abstraction. Tell the story top-down so intent appears before detail. - Keep parameters few and meaningful. Avoid boolean flags, output parameters, and grab-bag argument lists; model the concept instead. - Separate commands from queries and eliminate hidden side effects. A function that answers MUST NOT mutate behind the reader's back. - Keep the happy path readable. Isolate error handling, invalid-state handling, and cleanup; prefer explicit optionality or typed results over null-like sentinel flow. - Use comments only for rationale, constraints, warnings, or external contracts. Do not narrate code instead of improving it. - Treat tests as production code: readable, deterministic, aligned with the behavior or contract they protect. ### Trigger Rules - When a function mixes setup, validation, computation, and side effects, split the phases. - When a comment explains control flow, simplify names or structure before keeping the comment. - When a function both mutates and answers, or hides a mode switch behind a flag, separate the responsibilities. - When duplication, repeated switches, or primitive clusters appear, name the concept with an argument object, polymorphism, special case, or small abstraction. --- ## Clean Architecture β Dependency Direction & Boundaries ### Decision Rules - Source dependencies MUST point inward toward higher-level policy. Domain and use cases MUST NOT import frameworks, databases, web handlers, queues, external service clients, UI types, or other details. - Put enterprise rules and invariants in entities or equivalent domain objects; put application-specific orchestration in focused use cases. - Pass plain request and response models across use-case boundaries. Do not pass web requests, framework contexts, ORM rows, database-bound structures, or framework response objects into or out of core policy. - Treat frameworks, databases, web delivery, messaging, filesystems, clocks, service clients, networks, devices, and vendors as outer-layer details behind ports, gateways, presenters, mappers, or adapters. - Inner layers own the interfaces they need; outer layers implement them. Object construction and concrete wiring belong in the composition root or other outer-layer main component. - Keep adapters humble. Controllers, endpoints, presenters, gateway adapters, service listeners, and hardware adapters translate external formats to use-case calls and back; they do not own business decisions. - Organize by use case, feature, or business capability before generic technical buckets. The structure SHOULD reveal domain intent and application actions. - Test entities, use cases, and boundary contracts first, without the real framework, database, network, external service, or target hardware. Test adapters separately at the seams. ### Trigger Rules - When framework annotations, request/response objects, serializers, ORM rows, schemas, vendor SDKs, or transport formats enter core policy, move translation outward. - When controllers, jobs, handlers, views, presenters, gateways, repositories, SQL, service listeners, or hardware adapters contain business branching or validation, move the rule inward. - When a use case instantiates infrastructure, calls a volatile dependency directly, or depends on a concrete implementation, introduce a policy-owned port and wire the concrete detail at the edge. --- ## Domain-Driven Design β Model, Language & Boundaries ### Decision Rules - Before designing code, identify the business capability, classify the subdomain as Core, Supporting, or Generic, define the Bounded Context, use its Ubiquitous Language, and choose only tactical patterns that earn their cost. - Give every meaningful model one explicit Bounded Context. The context owns its language, rules, semantics, code structure, tests, and integration contracts. - Treat the same word in different contexts as potentially different concepts. Translate at context boundaries instead of sharing domain classes or leaking foreign language into the local model. - Use Entities when identity and lifecycle matter; make identity explicit and protect meaningful state transitions rather than exposing unrestricted setters. - Use immutable, self-validating Value Objects when primitives hide domain meaning. - Use Aggregates only as invariant and transactional consistency boundaries. Keep them small, modify through the root, reference other Aggregates by identity, avoid large object graphs, and usually change one Aggregate per transaction. - Use Domain Events for meaningful past-tense business facts that clarify collaboration or integration; do not publish noisy field-change events. - Application Services coordinate use cases by loading Aggregates, invoking domain behavior, saving results, and triggering integration work. They MUST NOT become the real domain model. - Keep frameworks, persistence mechanics, transport formats, REST representations, and infrastructure types out of the domain model. ### Trigger Rules - When language is fuzzy, generic, overloaded, or imported from another context, pause coding and sharpen the local Ubiquitous Language. - When one model spreads across billing, identity, catalog, fulfillment, support, permissions, or other separate concerns, split or translate instead of reusing shared domain classes. - When a request wants to load and mutate a large graph or several Aggregate roots, revisit the invariant boundary and ask whether eventual consistency is acceptable. - When controllers, helpers, services, or transport-shaped application services contain business decisions, move behavior into the domain model or name the missing concept. --- ## Data-Intensive Systems β Correctness, Durability & Evolution ### Decision Rules - Make core trade-offs explicit: source of truth, consistency expectation, retry behavior, duplicate and reordered work, partial failure, data evolution, and whether state is durable, cached, derived, or ephemeral. - Treat crashes, partial writes, duplicate work, timeouts, stale reads, and unknown downstream success as normal inputs. Distinguish accepted, persisted, applied, and durable success. - Choose data models, query models, and ownership boundaries from relationships, access patterns, consistency needs, update locality, evolution pressure, and whether data is primary or derived. - Treat indexes, caches, search copies, read models, materialized views, and denormalized copies as derived data with explicit propagation, lag, observability, repair, and rebuild paths. - Make commands, jobs, events, batch jobs, and stream processors safe under retry and replay with deduplication keys, naturally idempotent transitions, or an explicit transactional recovery contract. - Preserve only the ordering the business logic actually needs. Scope it per key, stream, partition, record, entity history, or stronger contract. - Separate commands, events, durable logs, streams, and materialized views. Events describe facts; consumers MUST tolerate lag, duplicates, restart, replay, stable identifiers, correlation metadata, and versioned payloads. - Design schemas, encodings, APIs, messages, events, and database changes as evolving contracts across old readers, old writers, old data, in-flight messages, rolling upgrades, and cross-service formats. - Match transactions and isolation to invariants. Make atomicity scope, commit behavior, recovery, reconciliation, lost-update, write-skew, phantom, and side-effect repair semantics explicit. - Treat network delay, packet loss, partitions, duplicate messages, pauses, stale leaders, timeouts, wall-clock uncertainty, leases, locks, majorities, and leadership as assumptions needing a fault model. ### Trigger Rules - When changing a write path, state the source of truth, consistency boundary, durability point, visibility point, downstream effects, rollback or repair path, and behavior after timeout or unknown success. - When adding or changing a cache, index, projection, search copy, read model, warehouse, or denormalized field, define ownership, propagation, staleness, write cost, lag visibility, rebuild, and repair. - When changing a schema, API, message, event, enum, status, or payload meaning, plan compatibility for old readers, old writers, old stored data, old messages, new writers, rollout, and migration. - When adding retries, jobs, consumers, queues, CDC, event sourcing, stream processors, or replayable batch work, prove duplicate, replay, ordering, retention, side-effect, and recovery safety. - When choosing transaction isolation or weakening consistency, map each anomaly to the invariant it can break and add compensating design where needed. --- ## Unified Review Checklist Before finalizing any change, verify: - Can a reader follow the change locally without reconstructing hidden state? - Are names and APIs carrying meaning without narration? - Do dependencies point inward, with ports owned by inner policy? - Is the domain language visible in code, tests, and APIs? - Are Aggregates small, root-protected, and not graph-shaped? - Is the source of truth explicit, and are derived representations tracked? - Are retries, duplicates, replay, reordering, timeouts, crashes, and unknown success handled? - Do schemas, APIs, messages, and events evolve safely across mixed versions? - Did framework, persistence, vendor, and construction details stay behind boundaries? - Did I remove at least one smell from the touched area? - Do tests protect the changed behavior or contract without needing real infrastructure? If any answer is no, revise before shipping. --- ## Final Instruction When uncertain, choose the option that: 1. improves local reasoning and precise naming 2. keeps dependencies pointing inward and details replaceable 3. sharpens the domain model and its explicit boundaries 4. makes data ownership, consistency, and failure handling explicit 5. leaves the codebase easier to change tomorrow Be pragmatic, and make the right thing the easy thing.
RELATED PROMPTS
COMMENTS
No comments yet β be the first.
