From b0cd61da6333e00f9e2ab3f3b4fed18acb5f6406 Mon Sep 17 00:00:00 2001 From: karitham Date: Fri, 21 Aug 2026 00:14:43 +0200 Subject: [PATCH] skills: revise software architecture and add plain technical prose skill --- .../skills/plain-technical-prose/SKILL.md | 185 +++++++ .../skills/software-architecture/SKILL.md | 455 +++++++++++------- 2 files changed, 467 insertions(+), 173 deletions(-) create mode 100644 modules/opencode/skills/plain-technical-prose/SKILL.md diff --git a/modules/opencode/skills/plain-technical-prose/SKILL.md b/modules/opencode/skills/plain-technical-prose/SKILL.md new file mode 100644 index 0000000..62c91ba --- /dev/null +++ b/modules/opencode/skills/plain-technical-prose/SKILL.md @@ -0,0 +1,185 @@ +--- +name: plain-technical-prose +description: Use when writing or revising any prose — documentation, design specs, READMEs, commit messages, PR descriptions, issue bodies, code comments. Triggers on phrases like "less Claudish", "make this drier", "plain technical register", "too much Markdown", "de-emphasise this", "rewrite the docs", or when drafting a document that should read as a plain specification. References ASD-STE100 style discipline and Williams sentence clarity (characters-as-subjects, actions-as-verbs). +--- + +# Plain technical prose + +Prose written to this style reads as a specification rather than as persuasion. It states what is true, why, and at what cost, without typographic emphasis or rhetorical construction. Apply it to documentation, design specs, READMEs, commit messages, PR descriptions, issue bodies, and code comments. + +The reference for this style is ASD-STE100. Apply its discipline: short sentences, active voice, one topic per sentence, present tense, and controlled terminology. Do not claim conformance, and do not apply its approved-word list, which mangles domain terms such as `dmabuf`, `composite`, and `swapchain`. + +This file contains two layers. The agent applies them selectively: + +- **Register (Rules below):** always applied when writing or revising any prose. +- **Sentence mechanics (§Sentence mechanics):** applied only to declarative sentences when revising for clarity or when a sentence fails the `WHO is doing WHAT?` diagnostic (imperative procedural steps are exempt). The agent MUST NOT rewrite a sentence that already has a short concrete subject and an active verb carrying the action, even if the sentence uses `was/is` for cohesion. + +The register rules and the sentence rules are applied together when the task is a clarity rewrite. + +## Rules + +Sentences. One idea per sentence. For descriptive and capability prose, use short declarative sentences in active voice and present tense for behaviour (past only for history). For procedural instructions such as setup steps and local commands, use imperative mood ("Start PostgreSQL:", "Run `docker-compose up -d`"). Use active voice unless the actor is genuinely unknown or irrelevant. + +Person. Name the true actor and choose mood by discourse type. For descriptive prose about system behaviour, use third person and name the component that performs the action (the user, the compositor, the host desktop, the caller, the backend). For capability and feature prose, the actor is the user or caller, not the feature: "Users roll and claim characters", not "Character collection rolls"; "Users give characters to other users", not "Trading system gives". For procedural instructions, use imperative with no actor ("Start PostgreSQL:", "Build the backend:") or an actorless label ("PostgreSQL:", "Backend:"); do not invent an actor such as "the operator" or "the developer" to satisfy third person when the reader performs the action. Do not use "you", "your", "we", "our", or "us" in descriptive prose; imperatives contain no pronoun and need none. "our fork" becomes "the fork"; "you can't read your editor with the headset on" becomes "the editor is not readable with the headset on". + +Personification. Software does not choose, want, know, promise, or decide. Name the actor and the action: "the fold records the home page", not "the home the capture chose". A component described as having intent obscures which code performs the action. + +Terminology. Choose one term per concept and keep it. Synonym variation for stylistic relief obscures whether two names mean the same thing. If a document uses both "Rust" and "the native layer", decide which one each context needs and apply it consistently. + +Definitions. Define a term where the reader first meets it, and define it once. Reference material is read out of order and in part, so a term introduced in one section and used in another is defined at first use in each, or the second use links to the first. + +Figures of speech. No metaphor, idiom, rhetorical question, or aside. No sentence fragments used for effect. No punchlines, no understatement, no self-deprecation. No aphorism: a sentence shaped as a maxim, such as "text is evidence of an identity, not an identity", states a mechanism indirectly, so state the mechanism. No loaded noun standing in for one, such as a trap, a spine, a promise, or a warning, where the thing itself can be named. + +Punctuation. No em-dash asides. Split the aside into its own sentence, or replace the dash with a colon when the second clause expands the first. Parentheses are acceptable for short references and qualifications. + +Markdown. Keep headings, tables, code blocks, inline code, links, and lists that enumerate a real set. Remove bold, italics, and bolded lead-in labels. A bolded lead-in becomes an ordinary sentence opener: "**Import is zero-copy and per-buffer.** Wayland clients cycle..." becomes "Import is zero-copy and per-buffer. Wayland clients cycle...". Remove any formatting whose purpose is emphasis rather than structure. The result is never line-wrapped. Each paragraph occupies a single source line, and a newline ends a paragraph, a list item, or a block. Hard-wrapped prose renders as fragmented paragraphs in Markdown viewers, so wrapping corrupts the output as well as the source. + +One exception applies to agent instruction files, meaning `SKILL.md` and similar. Those keep bolded lead-in labels on numbered workflow steps, where the label names the step and acts as structure. The register rules still apply to the prose that follows each label. + +Headings. Name the subject, not the reading experience. "How a click reaches an application" becomes "Path of an input event". "Two details that bite if ignored" becomes "Two details matter if ignored", or the sentence is dropped and the list stands alone. + +Self-reference. A document does not describe itself. "This document explains", "this section covers", and "the purpose of this issue is" announce content instead of stating it. Write the content. A genuine navigational statement, such as a table of contents entry or a cross-reference to another document, is not self-reference. + +Emphasis. Emphasis carried by typography or by a catchy line is carried by plain prose instead. State the fact and its consequence in order. "Dead handles go inert. Ugly. Less ugly than a race taking down your desktop." becomes "Dead handles go inert rather than raising errors. This is a compromise, made because it is less disruptive than a race condition taking down the session." + +Generated-sounding phrasing. Avoid formulaic contrasts, staged candour, and generic signposting when they add no information. Phrases such as "not X, but Y", "the honest take", "the important thing to understand", "let's unpack this", and "the bottom line" often announce a point instead of stating it. Replace them with the claim, action, or consequence directly. Prefer concrete verbs over abstract management or assistant phrasing such as "synthesize", "surface", "navigate", "operationalize", "land", and "align" when a simpler verb is available. + +Avoid implicature: a sentence that gestures at a conclusion without stating it, such as "the answer is owed", "this deserves attention", or "the question arises". State the conclusion, or state what depends on it. + +Avoid describing a set by its size where its members can be named. "None of the three is implemented" leaves the reader counting; "the supersede, retract, and redate events are not implemented" does not. + +Use technical terms and punctuation when they describe something precise. Terms such as "synthesize", "seam", "shape", and "load-bearing", along with em dashes and parentheticals, become a problem when they are repeated mechanically, used as vague abstractions, or substituted for a concrete mechanism. Judge the wording by its context and density. A component can be "load-bearing" when a specific dependency relies on it; an integration boundary can be a "seam". + +Do not simulate personal experience that the writer does not have. Avoid claims about memories, feelings, recurring observations, or physical experiences, such as "What I often feel" or "I can't tell you how many times I've...". State the observation and its evidence directly. + +Judgements. Keep every judgement, and state it as a claim with a reason. Drop the attitude, not the position. "which is a debugging surface we don't need" becomes "would add a debugging surface with no offsetting benefit". "exactly backwards" becomes "counterproductive". + +Characters. Use ASCII where a Unicode character adds nothing: `1920x1080`, not `1920×1080`; `2 to 4 buffers`, not `2–4 buffers`. Keep Unicode in proper nouns and in code. + +Sentence mechanics (Williams). Every declarative sentence MUST satisfy the rules in the next section: the grammatical subject MUST be the story character (short, concrete noun) and the verb MUST be the character's action. Nominalizations MUST be converted to verbs where the action is the point. Lead with subject+verb; apply Simplicity Before Complexity and Old Before New. Imperative procedural steps ("Start PostgreSQL:", "Run `docker-compose up -d`") are exempt from the subject requirement. + +## Sentence mechanics — Characters as Subjects, Actions as Verbs + +Source: Boston University Teaching Writing, *Sentence Clarity: Characters and Actions* script (Williams, *Style: The Basics of Clarity and Grace*, 5th ed.; Turabian, *Student's Guide to Writing College Papers*). Full PDF: https://www.bu.edu/teaching-writing/files/2020/03/Sentence-Clarity-Script.pdf + +### 1. Characters as Subjects + +English requires subject + tensed verb. Clarity depends on that pair aligning with the story's WHO and WHAT. + +- The grammatical subject SHOULD be the main character of the clause. +- The subject SHOULD be a short, concrete noun or noun phrase, not a long abstract phrase. + +BAD: + As a walk through the woods was taking place on the part of Little Red Riding Hood, the Wolf's jump out from behind a tree occurred, causing her fright. + +GOOD: + Little Red Riding Hood was walking through the woods, and the Wolf jumped out from behind a tree and frightened her. + +Why: In the BAD version the verbs are `was taking place` / `occurred` and the subjects are `walk` / `jump` (the actions nominalized as nouns). In the GOOD version the characters are subjects and the actions are verbs. + +Diagnostic: Ask `WHO is doing WHAT?` If the answer's WHO is not the subject, or the WHAT is not the verb, rewrite. + +### 2. Actions as Verbs — Kill Nominalizations + +A nominalization is a noun formed from a verb or adjective: `evaluate→evaluation`, `decide→decision`, `persuasive→persuasion`, `argue→argument`, `arguing` (gerund). They are common in technical prose and often harmless, but they MUST be rewritten when they bury the clause's key action and force vague `to be / to have` verbs. + +BAD: + It is our requirement that a review of the data be done. + +GOOD: + The team requires that the operator review the data. + +BAD: + The evaluation of the swapchain configuration occurs before import. + +GOOD: + The compositor evaluates the swapchain configuration before import. + +BAD: + The implementation of the retract event is not yet done. + +GOOD: + The retract event is not yet implemented. + +Check: If a `to be / to have / to do` verb carries the sentence and a nearby noun contains the real action (`-tion`, `-ment`, `-ance`, `-ing`), convert the noun to a verb and make its actor the subject. + +`to be` is allowed only to link old information to new for cohesion (see Old Before New below), not to carry a hidden action. When `is + nominalization` can become `verb`, the agent MUST prefer the verb. + +### 3. Lead with Clear Subject + Verb + +Readers grasp a clause fastest when a short subject and a specific verb appear near the beginning. + +#### Simplicity Before Complexity + +Do not put a long introductory phrase or appositive before the subject, and do not separate subject and verb with a long phrase. + +BAD: + The most beloved child of everyone in her village, especially her grandmother, Little Red Riding Hood entered the woods. + +BAD: + Little Red Riding Hood, the most beloved child of everyone in her village, especially her grandmother, entered the woods. + +GOOD: + Little Red Riding Hood entered the woods. Everyone in her village loved her, especially her grandmother. + +Technique: If an adjective phrase (e.g., `beloved` ← `to love`) contains its own character+action, split into a second sentence. + +#### Old Before New + +Link each sentence to the previous one by putting familiar/old information first and new information last. Flow beats local brevity. + +Context: `Little Red Riding Hood entered the woods.` + +GOOD (cohesive): + She was the most beloved child of everyone in her village, especially her grandmother. + +Also GOOD (more concise, less cohesive in this context): + Everyone in her village loved her, especially her grandmother. + +Trade-off: Prefer the version that keeps the same character as subject across adjacent sentences, even if it uses `was` and a few more words. Old-first aids the reader who tracks the map sentence by sentence. + +Checklist for this section: + +- [ ] Each clause answers `WHO is doing WHAT?` and that WHO is the subject, that WHAT is the verb. +- [ ] Subjects are short, concrete nouns. No `the walk was taking place` or `the evaluation occurs`. +- [ ] No nominalization hides the main action. `is a requirement` → `requires`; `conduct a review` → `review`. +- [ ] Subject and verb appear within the first ~6 words; no long phrase separates them. +- [ ] Each sentence starts with old/familiar information that links to the prior sentence. + +## Writing new prose + +Write to these rules from the first draft. Do not draft in a livelier register with the intention of flattening it later; the flattening pass loses content. + +The register is the delivery, not the content. Reasoning, caveats, numbers, trade-offs, and cross-references belong in a plain-register document exactly as much as in any other. A dry document is not a shorter document. + +Describe the present state. History belongs in a document about history, in a changelog, or in a commit message. Where a past decision explains a present constraint, state the constraint and its reason in the present tense rather than narrating how it came about. + +## Rewriting existing prose + +1. Read the whole document before changing anything. The rewrite depends on knowing which terms the document has committed to and which cross-references must survive. +2. Rewrite section by section. For each paragraph, identify what it asserts, then restate the assertions in the target register. Do not paraphrase sentence by sentence; that preserves the original's rhetorical shape. +3. Preserve content exactly. Every decision, rationale, caveat, number, file reference, link, and anchor target stays. Anchors are load-bearing: renaming a heading breaks inbound links, so update every link to a heading that is renamed. +4. Check the line count and the file list after the rewrite. A content-preserving rewrite lands close to its original length. A large drop means content was lost, not compressed. +5. For a multi-document rewrite, do all documents in one pass so terminology stays consistent across them, and state in the commit message that content is unchanged. + +## Interaction with other skills + +This skill governs register. It does not override document structure defined elsewhere. When drafting a GitHub issue, follow the `github-issue` structure and write its prose to these rules. When writing a commit message, follow the repository's commit conventions and write the body to these rules. + +Two carve-outs apply to code comments. Comments stay short, and a comment that is already a single plain sentence needs no change. Do not rewrite comments in a file that the task did not otherwise touch. + +Companion skill `software-architecture` governs system structure. When prose describes an architecture decision, the `software-architecture` principle "Minimize total cognitive load and enable deletion" (reduce accidental complexity, preserve conceptual integrity, delete working code if it obscures the map, performance bounded by clarity) determines *what* to say; this skill determines *how* to say it. Apply both. + +## Verification + +Before presenting a rewrite, check the result for: remaining bold or italic markers used for emphasis; occurrences of "you", "your", "we", "our" in descriptive prose (imperative procedural steps contain no pronoun and are exempt); em-dashes; headings that describe the reader's experience rather than the subject; and line breaks inside a paragraph. Each of these is a fast grep and each catches the common failure. + +Check also for: self-reference ("this document", "this section", "this issue"); a component that chooses, wants, knows, or promises; invented actors where a feature name or "the operator"/"the developer" performs a user or reader action (e.g., "Character collection rolls" for "Users roll", "The operator starts the backend" for "Start the backend:"); a set described by its size where its members could be named; and a term used but never defined. + +Check also for Williams failures in declarative sentences (imperative steps are exempt): nominalizations hiding the verb (`requirement`, `evaluation`, `review` where `require/evaluate/review` is the action); long abstract subjects; subject separated from verb by a long phrase; new information before old. + +## References + +- Williams, Joseph M. and Joseph Bizup. *Style: The Basics of Clarity and Grace*. 5th ed., Pearson, 2014. (Source for Characters as Subjects / Actions as Verbs via BU Teaching Writing, Sentence Clarity Script: https://www.bu.edu/teaching-writing/files/2020/03/Sentence-Clarity-Script.pdf) +- Turabian, Kate L. *Student's Guide to Writing College Papers*. 4th ed., revised by Colomb and Williams, University of Chicago Press, 2010. +- ASD-STE100 Simplified Technical English (register discipline only; approved-word list not applied). diff --git a/modules/opencode/skills/software-architecture/SKILL.md b/modules/opencode/skills/software-architecture/SKILL.md index 909bb0f..f9b02c6 100644 --- a/modules/opencode/skills/software-architecture/SKILL.md +++ b/modules/opencode/skills/software-architecture/SKILL.md @@ -1,227 +1,336 @@ --- name: software-architecture description: > - Technical protocol for designing and structuring non-trivial software. Covers - the impure-pure-impure sandwich, bounded contexts, event-driven patterns, - dataflow modeling, and dependency rejection. Use when designing APIs, adding - endpoints, defining module boundaries, structuring services, handling side - effects, or deciding what should be public vs internal. Keywords: functional - core, imperative shell, bounded context, event sourcing, CQRS, data pipeline, - idempotency, short-circuiting. Do NOT use for function-level code style or - refactoring within a single file — use code-writing instead. + Reference for structuring non-trivial software to minimize total cognitive load and preserve conceptual integrity. The agent uses this reference when designing APIs, adding endpoints, defining module boundaries, structuring services, handling side effects, or deciding what remains public. It covers the impure-pure-impure sandwich, bounded contexts, dataflow and pipeline design, event-driven patterns, resource-aware orchestration, and structural standards that reduce accidental complexity and make deletion one pull request. Keywords: functional core, imperative shell, bounded context, event sourcing, CQRS, data pipeline, idempotency, short-circuiting, sacrificial architecture, deletion, deprecation expiry, total cognitive load, accidental complexity, conceptual integrity. Do NOT use for function-level code style — use code-writing instead. --- # Software Architecture Protocol -This skill provides a systematic framework for managing complexity and side -effects in software systems. +The agent uses this protocol when a decision changes module boundaries, public interfaces, data flow, or dependency direction. The protocol governs structure by one constraint: minimize total cognitive load and preserve conceptual integrity. + +Total surface area means the set of modules, interfaces, invariants, and flags the maintainer must hold in head to make a safe change. The agent minimizes that set. The agent prefers clarity over clever optimization. The agent treats the system as sacrificial. The agent assumes that 10x scale will require replacement. Good modularity makes replacement one pull request and not sixty files. ## 0. Continuous Improvement -You SHOULD suggest architectural improvements when they are directly relevant to -the task at hand. Do not lecture on bounded contexts when asked to fix a typo. +The agent SHOULD suggest architectural improvements only when directly relevant to the task at hand. The agent MUST NOT lecture on bounded contexts when fixing a typo. -- Flag architectural debt when you encounter it during legitimate work -- Suggest the ideal structure alongside the pragmatic fix -- Small improvements compound: a single well-placed extraction or boundary is - better than waiting for a perfect refactor +- The agent flags structural debt when encountering it during legitimate work and proposes the ideal shape alongside the pragmatic fix. +- The agent keeps each improvement small and visible. A single well-placed extraction or boundary is better than waiting for a large refactor. +- The agent MUST NOT expand scope outside the current task without an expiry or migration window (see §9). When deletion would require changes outside task scope, the agent flags the debt and proposes the ideal, but defers the deletion. ## 1. Design Protocol -When approaching any architecture decision, follow this sequence: - -1. **Identify the boundary.** What is public vs internal? Who calls this? What - does it depend on? -2. **Gather constraints.** Latency requirements, consistency needs, failure - modes, team ownership. -3. **Propose the simplest structure.** Start with a deep module: simple - interface, hidden complexity. -4. **Check against patterns.** Does the impure-pure-impure sandwich apply? Are - bounded contexts clear? Is dataflow linear? -5. **Flag what you cannot fix.** If the current structure violates these - principles, suggest the ideal alongside the pragmatic path. - -## 2. The Impure-Pure-Impure Sandwich - -When an operation mixes I/O with business logic, you MUST follow this sequential -workflow to isolate side effects from decision-making. - -### Workflow - -1. **Gather (Impure Boundary):** Fetch all external state required for the - decision. Examples: DB queries, API calls, reading system time, generating - UUIDs. -2. **Process (Functional Core):** Pass the gathered data into a pure function. - This function MUST be deterministic. It MUST NOT perform I/O or access global - state. It MUST return data or a Result struct. -3. **Commit (Impure Boundary):** Persist the output of the Functional Core. - Examples: DB writes, sending HTTP responses, logging. - -### Example - -BEFORE — logic mixed with I/O: - - func CreateOrder(db *sql.DB, req OrderRequest) error { - user, err := db.GetUser(req.UserID) - if err != nil { return err } - - if user.Suspended { - return ErrSuspended - } - - total := calculateTotal(req.Items) - if total > user.CreditLimit { - return ErrOverLimit - } - - return db.SaveOrder(Order{ - UserID: user.ID, - Items: req.Items, - Total: total, - }) - } +The agent follows this sequence for every architecture decision. The sequence reduces extraneous load before committing to structure. -AFTER — sandwich applied: +1. **Identify the boundary.** What is public vs internal? Who calls this? What does it depend on? (Defines surface area.) +2. **Gather constraints.** Latency requirements, consistency needs, failure modes, team ownership, and conceptual integrity of existing concepts. +3. **Propose the simplest structure.** Start with a deep module: simple interface, hidden complexity. Prefer the structure that minimizes total cognitive load. +4. **Check against patterns.** Does the impure-pure-impure sandwich apply? Are bounded contexts clear? Is dataflow linear? Does deletion stay within one pull request? +5. **Flag what the agent cannot fix.** When the current structure violates these principles, the agent suggests the ideal alongside the pragmatic path and records the debt with an expiry. - func CreateOrderHandler(db *sql.DB, req OrderRequest) error { - // Gather - user, err := db.GetUser(req.UserID) - if err != nil { return err } +## 2. Minimize total cognitive load and enable deletion - // Process (pure) - result := ValidateAndBuildOrder(user, req) +A maintainer traces a failure with limited prior context. The maintainer follows one map from entry point to failure site. The agent therefore designs every structure so the maintainer holds only one context in head at a time. - // Commit - if result.Err != nil { return result.Err } - return db.SaveOrder(result.Order) - } +The agent SHOULD delete a module, flag, or abstraction when its retention obscures the map and increases extraneous cognitive load, even if the code works. The agent MUST do so only within the scope of the current task or behind a deadline and migration window per §9. When deletion would span outside task scope, the agent MUST flag the debt, propose the ideal, and defer the deletion rather than mixing a broad refactor into the current change. The agent MUST reduce accidental complexity where it impedes conceptual integrity. The agent MUST bound performance work by clarity. The agent SHOULD treat an optimization that obscures the map as a debugging surface with no offsetting benefit and defer the optimization. - // Pure function — no I/O, deterministic, testable with simple values - func ValidateAndBuildOrder(user User, req OrderRequest) OrderResult { - if user.Suspended { - return OrderResult{Err: ErrSuspended} - } - total := calculateTotal(req.Items) - if total > user.CreditLimit { - return OrderResult{Err: ErrOverLimit} - } - return OrderResult{Order: Order{ - UserID: user.ID, - Items: req.Items, - Total: total, - }} - } +Surface area: maintainer tracks one module and its explicit inputs. Deletion: remove one module, flag, or endpoint in one pull request with no other module requiring edits. + +BAD: the agent keeps a deprecated adapter because it still works and adds a second adapter for the new path. The codebase now has two adapters, two sets of types, and a routing flag with no expiry. + +```go +// two adapters remain, caller chooses at runtime +if useNewAdapter { + newAdapter.Call(req) +} else { + oldAdapter.Call(req) +} +``` + +GOOD: the agent defines a single interface, routes all callers through it, and sets an expiry for the old adapter. The agent translates vendor types at the edge and holds one model in the core. The old adapter disappears in one pull request when the expiry triggers. + +```go +// single interface, vendor translation at the edge +type Store interface { Get(id string) (Order, error) } + +func NewStore(cfg Config) Store { + // vendor translation happens here, core sees only Order + return &pgStore{db: cfg.DB} +} +``` + +## 3. Isolate side effects with the impure-pure-impure sandwich + +Side effects include database access, network calls, clock reads, and ID generation. Business logic includes validation, calculation, and state transitions. The protocol separates the two. + +Surface area: maintainer tests business logic with values alone, without holding database or network state in head. Deletion: agent replaces the gather or commit step without touching the pure core. + +The agent MUST structure an operation as gather, process, commit. The gather step fetches external state. The process step runs a deterministic pure function that performs no I/O and accesses no global state. The commit step persists the result. The agent MUST NOT place business logic inside the gather or commit steps, because that placement would hide a decision inside I/O and increase surface area. + +BAD: logic mixes with I/O. The caller cannot test the credit rule without a database. The function also holds a transaction while it computes. + +```go +func CreateOrder(db *sql.DB, req OrderRequest) error { + user, _ := db.GetUser(req.UserID) + if user.Suspended { return ErrSuspended } + total := calcTotal(req.Items) + if total > user.CreditLimit { return ErrOverLimit } + return db.SaveOrder(Order{UserID: user.ID, Items: req.Items, Total: total}) +} +``` -## 3. Bounded Contexts +GOOD: the handler gathers, the pure function decides, the handler commits. The logic is testable with values. The transaction covers only the final write. -Evans, _Domain-Driven Design_: A bounded context is a boundary within which a -particular model is defined and applicable. +```go +func CreateOrderHandler(db *sql.DB, req OrderRequest) error { + user, err := db.GetUser(req.UserID) + if err != nil { return err } + result := ValidateAndBuildOrder(user, req) + if result.Err != nil { return result.Err } + return db.SaveOrder(result.Order) +} -- Each service or module MUST own its model. Shared models across boundaries - create coupling. -- The same real-world concept (e.g., "Customer") can have different models in - different contexts. This is correct, not duplication. -- Translation between contexts happens at the boundary, not in the core. +func ValidateAndBuildOrder(user User, req OrderRequest) OrderResult { + if user.Suspended { return OrderResult{Err: ErrSuspended} } + total := calcTotal(req.Items) + if total > user.CreditLimit { return OrderResult{Err: ErrOverLimit} } + return OrderResult{Order: Order{UserID: user.ID, Items: req.Items, Total: total}} +} +``` -## 4. Dataflow and Pipelines +## 4. Enforce bounded contexts with one-direction dependencies -Kleppmann, _Designing Data-Intensive Applications_: Think of a system as a -pipeline of data transformations, not as a collection of services calling each -other. +A bounded context defines one model that applies within one ownership boundary. The same real concept may have different models in different contexts. The agent owns each model inside its context. -- Each step transforms data and passes it to the next. The pipeline is the - architecture. -- Idempotent operations allow safe retries and reprocessing. Design every - write operation to be idempotent where possible. -- Derived data (caches, indexes, materialized views) should be reproducible - from the source of truth. +Surface area: maintainer reasons about one model at a time without reconciling conflicting definitions. Deletion: agent deletes a context by removing its model and translation layer; other contexts stay unchanged because dependencies point one way. -## 5. Event-Driven Patterns +The agent MUST give each module or service its own model. The agent MUST translate between contexts at the boundary. The agent MUST keep imports in one direction. The agent MUST NOT share a model across contexts, because sharing would couple callers to implementation details and spread edits across many files. The agent MUST translate vendor types at the edge, at the module that contacts the vendor. The core must see only internal types. -Events decouple producers from consumers in time and space. +BAD: two contexts share a vendor type. Deletion of the vendor requires edits in every context. -- Event sourcing: persist state changes as a sequence of immutable events. The - current state is a projection of the event log. -- CQRS: separate read and write models when they have different scaling or - consistency requirements. -- Consumers MUST NOT assume event ordering unless the system guarantees it. -- Events describe what happened, not what to do. Commands describe what to do. +```go +// shared vendor type leaks into core +type Order = stripe.Order // core now depends on stripe +func Charge(o stripe.Order) error { ... } +``` -## 6. Resource-Aware Orchestration +GOOD: each context defines its own type. The boundary translates. -Operations MUST be ordered to minimize the surface area of high-latency or -locking operations. +```go +// core defines its own type +type Order struct { ID string; Total int } -- **Short-Circuiting:** Cheap local checks MUST occur before expensive remote - checks. -- **Lock Minimization:** Database transactions SHOULD only wrap the final - commit phase. -- **Dependency Rejection:** Business logic SHOULD accept raw data structures - rather than behavioral interfaces to avoid unnecessary coupling. +// edge translates vendor type to core type +func toCore(s stripe.Order) Order { return Order{ID: s.ID, Total: s.Amount} } +func Charge(o Order) error { ... } +``` -## 7. Structural Standards +## 5. Design deep modules that hide information -### Information Hiding (Ousterhout) +A deep module exposes a simple interface and retains significant complexity internally. A shallow module exposes an interface as complex as its implementation and adds indirection with no abstraction. -The primary purpose of a module is to hide complexity. Design modules so that -most of their knowledge is internal and invisible to callers. When information -leaks across boundaries, every caller becomes coupled to implementation details. +Surface area: maintainer learns one simple interface, not hidden details. Deletion: agent removes a deep module by deleting its interface and internals together; callers depend only on the interface, so scope stays bounded. -### Deep Modules (Ousterhout) +The agent MUST hide complexity behind the interface. The agent SHOULD remove a shallow abstraction when the interface does not simplify use. The agent SHOULD keep resilience handling inside the module that owns the dependency, so callers do not hold retry or breaker state in head. -Modules MUST be deep: simple interfaces hiding significant internal complexity. -If an interface is as complex as its implementation, the abstraction SHOULD be -removed. +### Information Hiding -Design the interface first. A module whose interface is hard to describe -clearly is not deep — it is shallow. Shallow modules add indirection without -abstraction. +The agent MUST ensure most knowledge of a module is internal and invisible to callers. When information leaks across boundaries, every caller couples to implementation details. + +### Deep Modules + +The agent MUST design modules to be deep: simple interfaces hiding significant complexity. The agent SHOULD design the interface first; if the interface is hard to describe, the abstraction is shallow. ### State Integrity -Invariants MUST be enforced via the type system. Invalid states SHOULD be -unrepresentable. +The agent MUST enforce invariants via the type system. Invalid states SHOULD be unrepresentable. The type checker then catches misuse before review. + +BAD: the interface exposes internal storage details. Callers must handle invariants and retry policy. + +```go +type OrderStore struct { DB *sql.DB } +func (s *OrderStore) GetOrderRow(id string) (Row, error) // row mirrors table +// caller validates, retries, and interprets row +``` + +GOOD: the interface hides storage and enforces invariants. The type prevents an invalid order. + +```go +type OrderID string +type OrderStore interface { Get(id OrderID) (Order, error) } +// Order is always valid by construction, retry lives inside the store +``` + +Resilience detail: the module that calls an external service owns retries with backoff, circuit breaker, and bulkhead isolation. The caller sees only a result or an error. This placement keeps failure handling out of the maintainer's head. ### API Backward Compatibility -Evolve APIs without breaking consumers. Additive changes only (new optional -fields, new endpoints). Deprecation requires a migration window, not an -immediate breaking change. +The agent MUST evolve public APIs additively. The agent adds optional fields or new endpoints. The agent MUST NOT break consumers without a migration window. Deprecation requires a migration window, not an immediate breaking change. + +## 6. Model systems as dataflow pipelines with idempotent steps + +The agent models a system as a pipeline that transforms data step by step. Each step takes data, transforms it, and passes it forward. + +Surface area: maintainer follows one linear pipeline, not unordered callbacks or hidden shared state. Deletion: agent deletes a step and reconnects input to output; other steps stay unchanged. Derived data regenerates from the source, so the agent can delete a cache or materialized view and rebuild it. + +The agent MUST design writes to be idempotent where possible, so retries remain safe. The agent MUST treat derived data as reproducible from the source of truth. The agent SHOULD model each step as a pure transform where feasible, so the pipeline composes from testable functions. + +BAD: a step mutates shared state and a write fails on retry because it appends again. + +```go +func ApplyPayment(orderID string, amount int) error { + // not idempotent: second call double-applies + return db.Exec("UPDATE orders SET total = total + ? WHERE id = ?", amount, orderID) +} +``` + +GOOD: the step uses an idempotency key and a deterministic transform. A retry produces the same result. Error handling omitted for brevity is handled per code-writing: validate at boundaries, return errors, do not swallow. + +```go +func ApplyPayment(tx *sql.Tx, key string, orderID string, amount int) error { + var done bool + if err := tx.QueryRow("SELECT exists(SELECT 1 FROM payments WHERE key=?)", key).Scan(&done); err != nil { + return err + } + if done { return nil } + if _, err := tx.Exec("INSERT INTO payments(key, order_id, amount) VALUES(?,?,?)", key, orderID, amount); err != nil { + return err + } + return nil +} +``` + +## 7. Decouple in time with event-driven patterns + +Events decouple producers from consumers. The log orders facts. Projections derive current state. + +Surface area: maintainer reasons about what happened, not which consumer called which producer when. Deletion: agent adds or removes a consumer without touching the producer; agent deletes a projection and rebuilds it from the log. + +The agent MUST describe events as facts about what happened, not as commands about what to do. The agent MUST separate read and write models when their scaling or consistency needs diverge. The agent MUST NOT rely on event order unless the system guarantees order. + +BAD: a producer calls consumers directly and sends a command. Every new consumer changes the producer. + +```go +func OnOrderCreated(o Order) { + email.SendOrderEmail(o) // producer knows consumer + inventory.Reserve(o) // deletion touches producer +} +``` + +GOOD: the producer appends a fact. Consumers project from the log. Deletion removes a consumer alone. + +```go +func OnOrderCreated(o Order) error { + return log.Append(Event{Type: "order.created", OrderID: o.ID, Total: o.Total}) +} +// consumer subscribes independently +func ProjectEmail(events <-chan Event) { for e := range events { if e.Type == "order.created" { email.Send(e) } } } +``` + +## 8. Order operations to minimize expensive surface area + +Expensive means high latency, locking, or coupling. Cheap means local and deterministic. + +Surface area: maintainer holds cheap checks first and holds expensive state only at the end; a trace fails fast on a local check and avoids deep state. Deletion: agent removes an expensive check by deleting one precondition before the commit without changing transaction scope. + +The agent MUST short-circuit. The agent runs cheap local validation before remote calls. The agent MUST minimize lock scope. The agent wraps only the final commit in a transaction. The agent MUST NOT hold a transaction while it calls a remote service, because that hold would block other work and force the maintainer to reason about lock duration. The agent SHOULD pass raw data to business logic and avoid behavioral interfaces, because interfaces would couple logic to I/O and enlarge surface area. + +BAD: the operation opens a transaction, then validates locally, then calls a remote service while the transaction blocks. + +```go +tx.Begin() +user := db.GetUser(tx, req.UserID) +if user.Suspended { tx.Rollback(); return ErrSuspended } +resp := payment.Verify(user) // remote call while tx holds locks +tx.Commit() +``` + +GOOD: cheap checks first, one-direction data flow, transaction only for the final commit. Pure functions use guard clauses per code-writing: early return, happy path left-aligned. + +```go +if req.UserID == "" { return ErrInvalid } +user, err := db.GetUser(req.UserID) +if err != nil { return err } +if user.Suspended { return ErrSuspended } +if err := payment.Verify(user); err != nil { return err } +tx.Begin() +defer tx.Rollback() +if err := db.SaveOrder(tx, order); err != nil { return err } +if err := tx.Commit(); err != nil { return err } +return nil +``` + +## 9. Make obsolescence enforceable and celebrate deletion + +Deprecation without enforcement never finishes. A flag or endpoint without an expiry spreads logic across files and expands surface area. + +Surface area: maintainer sees only current paths; expired paths are gone, not branched. Deletion: agent removes a deprecated path in one pull request when the deadline arrives, because the agent isolated the path behind a deep boundary and tracked it with tooling. + +The agent MUST attach a deadline to every temporary seam: flags, TODOs, deprecated fields, and compatibility shims. The agent MUST provide migration tooling and define an enforcement mechanism before it merges the seam. The agent MUST prevent backslide. Backslide prevention includes lint rules that reject new uses of deprecated symbols and build visibility whitelists that block new imports. The agent SHOULD treat a pure-deletion pull request as the ideal outcome and track such deletions. + +The agent MUST evolve public APIs additively. The agent adds optional fields or new endpoints. The agent MUST NOT break consumers without a migration window. + +BAD: a flag has no deadline and no lint. Callers multiply, and deletion requires hunting across the codebase. + +```go +// flag without expiry, no tracking +if featureFlag("new_checkout") { checkoutV2() } else { checkoutV1() } +``` + +GOOD: the flag has a deadline, an owner, and CI that fails on new uses after deprecation. + +```go +// flag with deadline 2026-12-01, owner @checkout-team +// lint: forbid new uses of FlagNewCheckout after 2026-06-01 +// CI enforces the rule, migration tool rewrites callers +if flag.Enabled(ctx, FlagNewCheckout) { + checkoutV2() // path to delete in one PR on expiry +} else { + checkoutV1() +} +``` + +At every seam the agent determines the scope of failure if that dependency disappears tomorrow. The agent bounds the scope to one module through one-direction deps and edge translation. Performance work stays bounded by clarity: the agent chooses the clear path when a faster path would obscure the map and would add debugging surface with no offsetting benefit. -### Resilience Patterns +## 10. Refine structure through small continuous improvements -Services fail. The system must survive. +The agent does not wait for a large refactor. The agent improves structure where the agent touches code. -- Circuit breaker: stop calling a failing service after repeated failures. -- Retry with backoff: transient failures are normal, but retry storms are not. -- Bulkhead isolation: isolate components so failure in one does not cascade. +Surface area: small improvements keep the map accurate and prevent drift that would force relearning. Deletion: each small improvement creates a boundary that then allows deletion in one pull request later. -## 8. Feedback Loop: Refactoring Pattern +The agent SHOULD flag structural debt when it meets it during legitimate work and propose the ideal shape beside the pragmatic fix. The agent SHOULD apply the refactoring pattern: identify hidden side effects such as clock reads, lift I/O to the caller, convert the remaining logic to a pure function, and verify that the core needs no mocks and uses guard clauses per code-writing. The agent MUST keep each improvement small and visible. -When refactoring existing code to this standard: +BAD: the agent mixes a feature with a broad refactor in one change. The diff touches many contexts and hides the logic change. -1. **Identify Side Effects:** Find all hidden I/O (e.g., `time.Now()`, - `db.Get`). -2. **Lift I/O:** Move those calls to the caller or the entry point of the - function. -3. **Purify:** Convert the remaining logic into a pure function that accepts - the lifted data as parameters. -4. **Verify:** The core logic MUST be unit-testable without a mocking framework. +GOOD: the agent adds the feature with minimal structure, then follows with a focused cleanup that extracts one pure function and moves one I/O call to the boundary. --- ## Validation Checklist -- [ ] **Sandwich:** Is there a clear line where I/O ends and logic begins? -- [ ] **Purity:** Does any business logic function take a Context or an - interface that performs I/O? (It shouldn't.) -- [ ] **Ordering:** Are network calls happening inside a database transaction? - (They shouldn't.) -- [ ] **Boundaries:** Does each module own its model, or is there a shared - model leaking across contexts? -- [ ] **Idempotency:** Can writes be safely retried? -- [ ] **Depth:** Is the module deep, or is it a pass-through adding no - abstraction? -- [ ] **Types:** Are we using primitives where a domain-specific type could - prevent a bug? -- [ ] **Mocks:** Can this logic be tested with simple value assertions? +- [ ] Sandwich: does a clear line separate I/O from logic, so the maintainer tests logic with values alone +- [ ] Purity: does business logic take only data and return a Result, with no I/O interface +- [ ] Ordering: does the operation short-circuit with cheap checks, and does a transaction wrap only the final commit +- [ ] Boundaries: does each context own its model, do imports point in one direction, and does vendor translation sit at the edge +- [ ] Idempotency: can the agent safely retry each write +- [ ] Depth: does the module expose a simple interface and hide its implementation +- [ ] State Integrity: does the type make an invalid state unrepresentable +- [ ] API Compatibility: does the change evolve APIs additively with a migration window, not a breaking change +- [ ] Types: are primitives replaced where a domain type would prevent a bug +- [ ] Mocks: can the agent test the core with value assertions and no mock framework +- [ ] Code Style: do pure functions use guard clauses and left-aligned happy path per code-writing +- [ ] Resilience: does the owning module contain retries and breakers, so callers stay simple +- [ ] Obsolescence: does every flag, TODO, and deprecated symbol have a deadline, migration path, and lint or build rule that blocks new uses +- [ ] Deletion and Cognitive Load: can the maintainer understand the map while holding one module in head, and can the agent delete the feature or module in one pull request with bounded failure scope + +## References + +- Ousterhout, J. *A Philosophy of Software Design* (deep modules, information hiding) +- Evans, E. *Domain-Driven Design* (bounded contexts) +- Kleppmann, M. *Designing Data-Intensive Applications* (dataflow, derived data, event sourcing) +- Brooks, F. *The Mythical Man-Month* (conceptual integrity) and *No Silver Bullet* (accidental vs essential complexity) +- Sweller, J. *Cognitive Load Theory* (total cognitive load: intrinsic, germane, extraneous) +- Campbell, G. *Cognitive Complexity* (understandability metric) +- Google. *Software Engineering at Google*, ch. 15 (obsolescence) +- Fowler, M. *Sacrificial Architecture* (design for replacement) +- Plain Technical Prose §Sentence mechanics for register and sentence mechanics -- 2.51.2