From c091668093d3b652f65f88a2e16dd245136bfed4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D7=A0=CF=85=CE=B1=CE=B7=20=D7=A0=CF=85=CE=B1=CE=B7=D1=95?= =?UTF-8?q?=CF=83=CE=B7?= Date: Sun, 16 Aug 2026 09:29:26 -0700 Subject: [PATCH] fix: land the rescope rewrite and sync repo-standards PR #89 was squash-merged from its first commit, so main carries the version that moved conventions out to a fetched fragment. The rewrite that replaced it -- every repository states its conventions in full, with the five files structurally identical -- was pushed to the branch after the merge and never landed. This restores it: the repo-standards delta fixing the middle sections, sentence-case headings, and stating conventions in full, with the shared-contributor-documentation capability dropped. Also syncs repo-standards into openspec/specs/. It existed only inside two unarchived changes, so the delta above had nothing to land on and five repositories cited a capability the corpus did not hold. Merged from the standardize-repository-layout and specify-agent-tool-invocation deltas: 16 requirements, 44 scenarios. Co-Authored-By: Claude Opus 5 --- .../rescope-go-code-standards/design.md | 204 ++++--- .../rescope-go-code-standards/proposal.md | 119 +++-- .../specs/repo-standards/spec.md | 136 +++++ .../shared-contributor-documentation/spec.md | 91 ---- .../rescope-go-code-standards/tasks.md | 109 ++-- openspec/specs/repo-standards/spec.md | 504 ++++++++++++++++++ 6 files changed, 862 insertions(+), 301 deletions(-) create mode 100644 openspec/changes/rescope-go-code-standards/specs/repo-standards/spec.md delete mode 100644 openspec/changes/rescope-go-code-standards/specs/shared-contributor-documentation/spec.md create mode 100644 openspec/specs/repo-standards/spec.md diff --git a/openspec/changes/rescope-go-code-standards/design.md b/openspec/changes/rescope-go-code-standards/design.md index f4a6a98..7d75599 100644 --- a/openspec/changes/rescope-go-code-standards/design.md +++ b/openspec/changes/rescope-go-code-standards/design.md @@ -1,145 +1,137 @@ ## Context -`go-code-standards` holds seven requirements. Two are policy no tool can check: -mocks are generated, and a test does not re-cover behavior through an exported -alias. Five state source formatting that `gofumpt`, `golines`, `goimports`, +`repo-standards` fixes the opening and closing sections of `CONTRIBUTING.md` and +says of the rest: "The middle varies by repository, because what a contributor +needs to know differs." That sentence is why the five Go repositories disagree. +What a contributor needs to know does differ between a collector library and a +REST service — but not about function signatures, and the free middle allowed +the common part to drift along with the specific part. + +`go-code-standards` holds seven requirements. Two are policy no tool reports on. +Five state source formatting that `gofumpt`, `golines`, `goimports`, `wrapcheck`, and `revive` already produce or reject. -The five repositories carrying Go code sit in three states. `osapi` and `gohai` -point at the capability without restating it. `nats-client` and `nats-server` -restate it under `Code style`. `osapi-orchestrator` has a removal open and held. -The conversion that produced this spread is half-finished, which is why the -question is worth settling now rather than after two more repositories move. - -`osapi-justfiles` already distributes shared recipes: a consumer runs -`just fetch`, files land in `.just/remote/`, and `.gitignore` keeps them -untracked. The `justfiles` capability records this under "Recipes are -distributed as fetched files" and "Fetched files are not linted". A second kind -of shared content needs no new mechanism. +Neither capability was in `openspec/specs/` when repositories began citing them. +`go-code-standards` was synced in osapi-io/specs#88. `repo-standards` is still +inside two unarchived changes, so a delta against it has nothing to land on +until it is synced too. ## Goals / Non-Goals **Goals:** -- Keep in the corpus only what the corpus is for — decisions binding several - repositories that no tool reports on. -- Leave every rule in force. Nothing here relaxes a convention; each moves to - whichever of three homes can actually hold it. -- Let a repository be read on its own, by a person offline or an agent with one - checkout. +- Make the five Go repositories' `CONTRIBUTING.md` files structurally identical + where their subject is identical. +- Keep every convention in force, readable inside the repository it binds. +- Leave in the corpus only what the corpus is for. **Non-Goals:** - Changing any convention. Signatures still span lines, imports stay grouped, - suites keep their names. -- Rewriting `.golangci.yml` to unify the per-repository exclusions. Those - differences are legitimate and this change does not touch them. -- Deciding what `osapi-justfiles` names the fragment or which recipe fetches it. - That is applying, not deciding. + suites keep their names. This moves rules and standardizes headings; it does + not relax anything. +- Unifying the per-repository `.golangci.yml` exclusions. Those differences are + legitimate. +- Making the repository-specific sections uniform. A collector guide and an API + domain walkthrough should not be forced into a shared shape. ## Decisions -### Three homes, chosen by what can hold a rule +### Committed copies, not a fetched fragment -A rule goes to the corpus if it is a decision with consequences and no tool -reports on it; to tool configuration if a tool can enforce it; to the shared -`CONTRIBUTING.md` fragment otherwise. +Each repository holds the shared conventions as committed text, identical across +the five. -*Alternative: keep all seven in the corpus and add worked examples.* Rejected -after starting it. Adding Go snippets to a requirement makes it unambiguously -about implementation, which `config.yaml` says a requirement is not. It treats -the symptom — the capability is hard to apply without an example — while making -the category error harder to see. +*Alternative: distribute a fragment through `just fetch`, the way shared recipes +already arrive.* Rejected. It gives one source that cannot drift, but the file +is incomplete until fetched: `CONTRIBUTING.md` renders on GitHub as a +placeholder, and a reviewer reading a pull request in a browser sees nothing. +The mechanism suits recipes, which are executed rather than read, and suits +documentation badly for the same reason. -*Alternative: move all seven out and retire the capability.* Rejected. "Mocks -are generated" is exactly what a cross-repository corpus is for: it is a -decision, it has a stated failure mode, no linter checks it, and three -repositories currently violate it. Retiring the capability would delete the two -requirements worth having. +*Alternative: keep the pointer to the corpus.* Rejected — this is what produced +the failure. A pointer resolves across a repository boundary over a network, for +every reader, and `gohai` spent a period pointing at a capability that was not +there. -### Distribute the fragment rather than point at the corpus +The cost is accepted deliberately: five copies drift unless someone notices. +That is what the structure requirement is for — identical headings make a +diverging body visible, where free-form sections hid it. -Each repository holds the shared conventions on disk, fetched from one source, -rather than naming a capability in another repository. +### Fix the middle rather than describe it -The failure that motivated this is recorded: `gohai` merged its removal while -`go-code-standards` existed only inside an unarchived change. For that period -the rules were stated once and that place was unreachable — a reader following -the pointer to `openspec/specs/` found nothing. A pointer is only as good as -what it resolves to, and it resolves across a repository boundary, over a -network, for every reader. +The middle becomes a named, ordered set with repository-specific sections placed +after `Testing`. -*Alternative: keep the pointer and deep-link the file.* Rejected as -insufficient. It fixes findability for a person with a browser and leaves the -offline reader and the single-checkout agent with a link they cannot follow. +*Alternative: require only that shared conventions live under `Code standards`, +leaving order free.* Rejected as too weak to catch what actually went wrong. The +NATS pair put signatures under `Code style`, `gohai` put `Go Patterns` beneath +`Testing`, and three of five had no `Code standards` at all. Naming the sections +without fixing their order leaves a reader comparing two files by search rather +than by position. -*Alternative: git submodule for the corpus.* Rejected. It puts the whole spec -repository into every consumer to deliver one document, and submodules pin a -revision that then needs its own bumping. +### Sentence case, stated as its own requirement -### Configuration is the statement for anything a tool checks +*Alternative: leave casing to the formatter.* Rejected — `mdformat` does not +touch heading case, so nothing would enforce it. It is a small rule, but it is +the difference a reader sees first, and it is currently wrong in three +repositories. -Where `.golangci.yml` or a formatter decides a rule, documentation names where -the configuration lives instead of reproducing it. +### `go-code-standards` narrows rather than being retired -The failure is already on disk. Every Go repository's `CONTRIBUTING.md` lists -"errcheck, errname, goimports, govet, prealloc, predeclared, revive, -staticcheck". Each `.golangci.yml` enables `unused`, which no prose mentions, -and puts `goimports` under `formatters` rather than among the linters. One stale -approximation, copied five times, of a file sitting beside it. +*Alternative: retire it and move all seven requirements into `CONTRIBUTING.md`.* +Rejected. "Mocks are generated" is what a cross-repository corpus is for: a +decision, with a stated failure mode, that no linter checks and that three +repositories currently violate. Retiring it would delete the two requirements +worth having. -*Alternative: keep the prose list and add a check comparing it to the config.* -Rejected. Building a checker to keep a document honest about a file it -duplicates is more machinery than deleting the duplicate. +*Alternative: keep all seven and add worked code examples.* Rejected after +starting it. A requirement containing a Go snippet is unambiguously about +implementation, which is what `config.yaml` says a requirement is not. It treats +the symptom — the capability is hard to apply without an example — while making +the category error harder to see. The examples belong in `CONTRIBUTING.md`, +beside the command that applies them. ### The capability keeps its name and path -`go-code-standards` narrows rather than being renamed or split. - -*Alternative: rename to `go-testing-policy` to match what survives.* Rejected. -Five repositories reference the name today, a rename invalidates every one of -them, and the capability may legitimately regain non-lintable Go policy later. +*Alternative: rename to match what survives.* Rejected. Five repositories cite +the name, a rename invalidates every citation, and the capability may +legitimately regain non-lintable Go policy later. ### Mocks gains a scenario rather than an exception -The `Mocks are generated` requirement is modified to say that a double carrying -a real implementation is not a mock. - -Applying task 3.4 turned up three hand-written doubles. `gohai`'s +Applying the mocking requirement turned up three hand-written doubles. `gohai`'s `fakeCollector` and `osapi-orchestrator`'s `mockRenderer` are scripted stand-ins -and do violate the rule. `osapi`'s `mockPKISigner` signs with a genuinely -generated ed25519 key pair — replacing it with a generated mock would swap -working cryptography for a canned return. The requirement did not distinguish -these, so applying it produced a finding against a test that is correct. +and do violate it. `osapi`'s `mockPKISigner` signs with a genuinely generated +ed25519 key pair — replacing it with a generated mock would swap working +cryptography for a canned return. The requirement did not distinguish these, so +applying it produced a finding against a correct test. -*Alternative: leave the requirement and record `mockPKISigner` as an accepted -exception.* Rejected. An exception recorded outside the requirement is invisible -at the point anyone reads the rule, and the next reviewer raises it again. +*Alternative: record `mockPKISigner` as an accepted exception outside the +requirement.* Rejected. An exception recorded elsewhere is invisible where the +rule is read, and the next reviewer raises it again. ## Risks / Trade-offs -- **The fragment is fetched, so a repository read on GitHub shows a pointer - rather than the conventions.** → The fetch mechanism already carries this - trade-off for recipes and it is accepted there. What a browser reader loses is - smaller than what an offline reader and a single-checkout agent gain, and the - fragment is one fetch away rather than one repository away. - -- **Two homes for Go conventions means a contributor must know which holds - what.** → The split follows a line a contributor can apply without being told: - if a tool rejects it, it is configuration; if a reviewer rejects it, it is - written down. - -- **Removing five requirements shrinks the corpus while the behavior - capabilities queued elsewhere have not arrived, making it look emptier.** → - The corpus is measured by what it governs, not its length. Five requirements - restating a formatter make it longer without making it say more. - -- **`gohai` is on `main` today with its conventions removed.** → Syncing the - capability into the corpus already restored a reachable answer. This change - determines what returns to the repository, and until it lands `gohai` is - covered by the synced capability rather than by nothing. - -- **`osapi-justfiles` becomes a dependency for reading a repository's - conventions.** → It already is, for building and testing one. A repository - that cannot fetch cannot run its checks either, so this adds no new failure - mode. +- **Five committed copies drift.** → Identical structure is what makes drift + reviewable: when every file carries the same headings in the same order, a + body that differs shows up in a side-by-side read. The free middle is what let + the current divergence go unnoticed. + +- **A shared convention now needs five pull requests to change.** → It needed + five before; the difference is that they were not kept in step. The + requirement that repositories state a common convention identically makes the + omission a defect rather than an oversight. + +- **Fixing the middle constrains repositories whose subject genuinely differs.** + → Only the common sections are fixed. Anything a repository invents for itself + keeps its own name and sits after `Testing`, which is where `gohai`'s + collector guide and `osapi`'s domain walkthrough already are. + +- **`repo-standards` is not in the corpus, so this change cannot be archived + until it is.** → Syncing it is task 1.5, ahead of any repository work, and the + same step was already taken for `go-code-standards`. + +- **`gohai` sits on `main` with its conventions removed while this is + reviewed.** → It is first in the apply order. Until then it is covered by the + synced capability rather than by nothing. diff --git a/openspec/changes/rescope-go-code-standards/proposal.md b/openspec/changes/rescope-go-code-standards/proposal.md index d4b55b2..e1b85eb 100644 --- a/openspec/changes/rescope-go-code-standards/proposal.md +++ b/openspec/changes/rescope-go-code-standards/proposal.md @@ -1,73 +1,84 @@ ## Why -`go-code-standards` states Go formatting conventions — multi-line signatures, -import grouping, error wrapping, early return — as corpus requirements. These -are not externally observable behavior, which is the level `config.yaml` says a -requirement sits at, and the strain shows in the scenarios written to satisfy -the format: "a parameter is added → the diff shows one added line" is a -rationale for a formatting preference, not a behavior anything can exhibit. - -The duplication this was meant to end has not ended, and the written rule is -already wrong. Every Go repository's `CONTRIBUTING.md` lists the linter set as -"errcheck, errname, goimports, govet, prealloc, predeclared, revive, -staticcheck". Each `.golangci.yml` enables `unused`, which no repository's prose -mentions, and configures `goimports` under `formatters` rather than as a linter. -Five hand-maintained copies of one stale approximation of a file that already -declares the truth — the drift the capability was written to prevent, occurring -inside the capability's own subject. - -Applying the capability exposed the third problem. `gohai` dropped its local -copy while `go-code-standards` existed only inside an unarchived change, so for -a period the rules were stated in exactly one place and that place was not -reachable. Removing a repository's copy is only safe when something reachable -answers in its place. +The five repositories carrying Go code state the same conventions five different +ways, and one of them no longer states them at all. + +`Function signatures` sits under `Code style` in `nats-client` and +`nats-server`, under `Code standards` in `osapi-orchestrator`, and nowhere in +`gohai` or `osapi`. Casing disagrees — `Function Signatures` against +`Function signatures`, `Go Patterns` against `Go patterns`. `gohai` nests +`Go Patterns` under `Testing`. `Code standards` exists in three of five. A +reader comparing two repositories cannot tell whether a difference in the +document means a difference in the rule. + +`go-code-standards` was written to end that, but it states Go formatting as +corpus requirements. Formatting is not externally observable behavior, which is +the level `config.yaml` says a requirement sits at, and the strain shows in the +scenarios written to fit the format: "a parameter is added, the diff shows one +added line" is a rationale for a preference, not a behavior anything exhibits. + +It also has not ended the duplication. Every Go repository lists the linter set +as "errcheck, errname, goimports, govet, prealloc, predeclared, revive, +staticcheck". Each `.golangci.yml` enables `unused`, which no prose mentions, +and configures `goimports` under `formatters` rather than as a linter. Five +hand-maintained copies of one stale approximation of a file sitting beside them. + +Applying the capability exposed the last problem. `gohai` dropped its local copy +while `go-code-standards` existed only inside an unarchived change, so for a +period the rules were stated in one place and that place was unreachable. A +repository has to be readable on its own. ## What Changes -- **BREAKING** `go-code-standards` is reduced to the rules that are genuine - cross-repository policy — decisions with consequences that no tool can check. - Mocks being generated rather than hand-written, and a test not re-covering - behavior through an exported alias, stay. Function signature layout, file - naming, suite naming, table-driven structure, and the style baseline leave the - corpus. -- Formatting rules a tool already enforces are enforced rather than written. - `golangci-lint` and the formatters it runs are the statement of record; a rule - a linter checks is not restated in prose anywhere. -- Conventions that remain prose — worked examples, the package names a - repository uses for its external tests — return to `CONTRIBUTING.md`, where a - contributor and an agent already read them without a second repository. -- A shared `CONTRIBUTING.md` fragment is distributed the way shared recipes - already are, so one source produces the copy each repository holds on disk. +- `repo-standards` fixes the middle of `CONTRIBUTING.md`, as it already fixes + the opening and closing. `Setup`, `Project structure`, `Code style`, + `Code standards`, and `Testing` become named sections in a defined order, with + anything a repository invents for itself placed after `Testing`. +- Headings become sentence case everywhere, resolving `Function Signatures` + against `Function signatures` and the rest. +- A repository SHALL state the conventions binding it **in full**, rather than + naming a capability in another repository. Where a convention is common, every + repository states it in the same words. +- A rule a tool enforces is named rather than reproduced, so the linter list + stops being maintained by hand in five places. +- **BREAKING** `go-code-standards` is reduced to what no tool reports on: mocks + are generated, and a test does not re-cover behavior through an exported + alias. Signature layout, file naming, suite naming, table-driven structure, + and the style baseline leave the corpus for `CONTRIBUTING.md`. +- `Mocks are generated` gains a scenario distinguishing a scripted stand-in from + a double that carries a real implementation. ## Capabilities ### New Capabilities -- `shared-contributor-documentation`: how contributor guidance common to several - repositories is distributed, so each repository holds a complete file on disk - while one source governs its content +None. ### Modified Capabilities -- `go-code-standards`: removes the requirements that state formatting a tool - enforces, and narrows the capability's purpose to cross-repository policy +- `repo-standards`: fixes the middle sections of `CONTRIBUTING.md`, requires + sentence-case headings, requires a repository to state its conventions in + full, and stops prose from restating what a tool enforces +- `go-code-standards`: removes the five requirements that state formatting a + tool enforces, and widens `Mocks are generated` ## Impact -Every repository carrying Go code, and the shared tooling repository that would -distribute the fragment: +Every repository carrying Go code: -- `gohai` — has already dropped its copy (osapi-io/gohai#163); this change - determines what returns to it -- `osapi-orchestrator` — the equivalent removal is open and held - (osapi-io/osapi-orchestrator#76) -- `nats-client`, `nats-server` — still carry their copies, untouched -- `osapi` — its root `CONTRIBUTING.md` points without restating +- `gohai` — on `main` with its shared conventions removed (osapi-io/gohai#163); + this change determines what returns +- `osapi-orchestrator` — the equivalent removal is open and held as a draft + (osapi-io/osapi-orchestrator#76), and is superseded by this change +- `nats-client`, `nats-server` — still carry their copies, under the wrong + headings +- `osapi` — points at the capability rather than stating the conventions (osapi-io/osapi#450) -- `osapi-justfiles` — would carry the shared fragment and the recipe that - fetches it -`specify-go-code-standards` is in flight and its tasks 2.2, 2.3, 2.4, 3.4, and -3.6 are open. Those tasks convert the remaining repositories to a pointer-only -form; this change decides whether that conversion is the right destination -before two more repositories follow `gohai` into it. +Neither `repo-standards` nor `go-code-standards` was in `openspec/specs/` when +the repositories began pointing at them. `go-code-standards` has since been +synced (osapi-io/specs#88); `repo-standards` has not, and this change cannot +modify it until it is. + +`specify-go-code-standards` tasks 2.2, 2.3, 2.4, 3.4, and 3.6 are open and point +at the pointer-only destination this change replaces. diff --git a/openspec/changes/rescope-go-code-standards/specs/repo-standards/spec.md b/openspec/changes/rescope-go-code-standards/specs/repo-standards/spec.md new file mode 100644 index 0000000..e2aaec2 --- /dev/null +++ b/openspec/changes/rescope-go-code-standards/specs/repo-standards/spec.md @@ -0,0 +1,136 @@ +## MODIFIED Requirements + +### Requirement: CONTRIBUTING structure + +`CONTRIBUTING.md` SHALL open and close with a fixed set of sections, in this +order, with repository-specific sections placed between them: + +| Section | Position | Content | +| ----------------------- | -------- | --------------------------------------- | +| `## Before you start` | opening | Code of Conduct, checking existing work | +| `## Prerequisites` | opening | Tools a contributor installs | +| `## Setup` | middle | Fetching shared assets, installing deps | +| `## Project structure` | middle | What lives where | +| `## Code style` | middle | Formatters, linters, and their commands | +| `## Code standards` | middle | Conventions a reviewer holds code to | +| `## Testing` | middle | Running tests, and test conventions | +| *(repository-specific)* | middle | Anything else, placed after `Testing` | +| `## Before committing` | closing | The command that runs what CI runs | +| `## Branching` | closing | Branch naming | +| `## Commit messages` | closing | Commit conventions | +| `## Submitting a PR` | closing | Pull request expectations | +| `## AI usage` | closing | Pointer to `AI_POLICY.md` | +| `## FAQ` | closing | Where to get help | + +Section names SHALL be taken from this table rather than reworded. A repository +that has nothing to say under a middle section SHALL omit it rather than rename +it or fold its content into a neighbour. + +Sections a repository invents for its own subject SHALL follow `Testing`, so +that everything common to repositories of the same type appears in the same +order before anything that is not. + +#### Scenario: Two repositories describe the pre-commit check + +- **WHEN** two repositories document the command a contributor runs before + committing +- **THEN** both name that section `## Before committing`, not one using + `Before committing` and the other `Finishing a change` + +#### Scenario: A repository has domain-specific conventions + +- **WHEN** a repository has conventions that apply only to it, such as how its + recipes are written +- **THEN** those appear as a middle section after `Testing`, leaving the common + sections in place and in order + +#### Scenario: The same convention sits under different headings + +- **WHEN** one repository documents its function signature rule under + `Code style` and another documents it under `Code standards` +- **THEN** both move it to `Code standards`, because a reader comparing two + repositories should not have to discover where each chose to put it + +#### Scenario: A repository has no structure worth describing + +- **WHEN** a repository is small enough that its layout needs no explanation +- **THEN** it omits `Project structure` rather than inventing a heading or + padding the section + +## ADDED Requirements + +### Requirement: Headings are sentence case + +Every heading in a repository's contributor documentation SHALL be sentence case +— the first word capitalized, the rest lowercase except proper nouns and +identifiers. + +Mixed casing makes two files stating the same thing look like they state +different things, and it is the difference a reader notices first when comparing +them. + +#### Scenario: The same section is capitalized two ways + +- **WHEN** one repository writes `### Function Signatures` and another writes + `### Function signatures` +- **THEN** both write `### Function signatures` + +#### Scenario: A heading contains an identifier + +- **WHEN** a heading names a tool or a type, such as the CLI or a Go package +- **THEN** that word keeps its own capitalization, and the rest of the heading + is lowercase + +### Requirement: A repository states in full the conventions it is held to + +A repository's `CONTRIBUTING.md` SHALL contain the conventions its contributors +are held to, in full. A reference to guidance held in another repository SHALL +NOT stand in place of stating them. + +A contributor reading a pull request in a browser, a contributor working +offline, and an agent with a single checkout each see only this repository. A +convention they cannot read is one they cannot follow. + +Where a convention is common to several repositories, the repositories SHALL +state it identically, so that a difference in wording means a difference in +rule. + +#### Scenario: An agent works in one checkout + +- **WHEN** an agent is asked to write code in a repository, with no access to + any other +- **THEN** the conventions binding that code are readable from within that + repository + +#### Scenario: A convention is common to several repositories + +- **WHEN** the same convention binds four repositories +- **THEN** each states it, in the same words, rather than one stating it and the + others naming where to find it + +#### Scenario: A shared convention changes + +- **WHEN** a convention common to several repositories is amended +- **THEN** every repository that states it is updated in the same change, so + none is left stating the superseded rule + +### Requirement: A rule a tool enforces is not restated as prose + +Where a tool's configuration determines a rule, that configuration SHALL be the +statement of record, and contributor documentation SHALL name where it lives +rather than reproducing what it says. + +Prose describing a tool's settings is maintained by hand and checked by nothing, +so it drifts from the configuration while continuing to read as authoritative. + +#### Scenario: The linter set is documented + +- **WHEN** contributor documentation describes which linters run +- **THEN** it names the configuration file rather than listing them, because a + copied list goes stale the first time the configuration changes + +#### Scenario: Prose and configuration disagree + +- **WHEN** documentation and a tool's configuration state different rules +- **THEN** the configuration is what runs, and the prose is removed rather than + corrected diff --git a/openspec/changes/rescope-go-code-standards/specs/shared-contributor-documentation/spec.md b/openspec/changes/rescope-go-code-standards/specs/shared-contributor-documentation/spec.md deleted file mode 100644 index 5e50426..0000000 --- a/openspec/changes/rescope-go-code-standards/specs/shared-contributor-documentation/spec.md +++ /dev/null @@ -1,91 +0,0 @@ -## Purpose - -Defines how contributor guidance common to several repositories is distributed, -so that each repository holds a complete guide on disk while one source governs -the shared part of its content. - -## ADDED Requirements - -### Requirement: A shared convention has one source - -A convention that binds more than one repository SHALL be written in one place -and distributed from there. It SHALL NOT be maintained as an independent copy in -each repository that follows it. - -Independent copies diverge without anything reporting it, and each reader -believes the copy in front of them is current. - -#### Scenario: A convention changes - -- **WHEN** a convention that binds several repositories is amended -- **THEN** it is amended once and redistributed, rather than edited separately - in each repository - -#### Scenario: Copies have already diverged - -- **WHEN** two repositories state the same convention differently -- **THEN** neither is authoritative, and the divergence is resolved by - establishing the single source rather than by choosing the copy that looks - more current - -### Requirement: A repository holds the guidance it is bound by - -A repository SHALL contain the full text of the conventions its contributors are -held to, readable without fetching another repository. - -A pointer to guidance stored elsewhere SHALL NOT be the only statement of a -convention. A reader working offline, an agent with no second checkout, and a -reviewer reading a pull request in a browser each see only this repository. - -#### Scenario: A contributor reads the guide - -- **WHEN** a contributor opens a repository's contributing guide -- **THEN** the conventions they must follow are present in it, rather than named - and left to be retrieved - -#### Scenario: An agent works in a single checkout - -- **WHEN** an agent works in one repository with no access to another -- **THEN** the conventions binding that repository are readable from within it - -### Requirement: Distributed content is fetched, not committed - -Shared contributor documentation SHALL reach a repository by the same mechanism -as its other shared assets, and the fetched copy SHALL NOT be committed. - -A committed copy is indistinguishable from a local edit, so the next fetch -either overwrites deliberate changes or is not run at all. - -#### Scenario: A repository is set up - -- **WHEN** a contributor prepares a fresh checkout -- **THEN** the same command that retrieves the repository's other shared assets - retrieves its shared documentation - -#### Scenario: A fetched file is edited locally - -- **WHEN** a repository needs shared guidance to differ -- **THEN** the difference is expressed where the source can produce it, rather - than by editing the fetched copy - -### Requirement: A rule a tool enforces is not also written as prose - -Where a tool's configuration determines a convention, that configuration SHALL -be the statement of record, and the convention SHALL NOT be restated as prose -that can disagree with it. - -Prose describing a tool's settings is maintained by hand and checked by nobody, -so it drifts from the configuration while continuing to read as authoritative. - -#### Scenario: A linter set is documented - -- **WHEN** contributor documentation lists which linters run -- **THEN** it names where the configuration lives rather than reproducing the - list, because a reproduced list goes stale the first time the configuration - changes - -#### Scenario: Prose and configuration disagree - -- **WHEN** documentation and a tool's configuration state different rules -- **THEN** the configuration is what runs, and the prose is removed rather than - corrected diff --git a/openspec/changes/rescope-go-code-standards/tasks.md b/openspec/changes/rescope-go-code-standards/tasks.md index 2f5d1a9..7ea1fb8 100644 --- a/openspec/changes/rescope-go-code-standards/tasks.md +++ b/openspec/changes/rescope-go-code-standards/tasks.md @@ -1,73 +1,82 @@ ## 1. Record the rescope -- [x] 1.1 Establish what each requirement is: policy no tool reports on, - something a tool already enforces, or a convention a reader applies -- [x] 1.2 Write the `go-code-standards` delta removing the five formatting - requirements and widening `Mocks are generated` to exclude a double that - carries a real implementation -- [x] 1.3 Write the `shared-contributor-documentation` capability -- [x] 1.4 Record the decisions and their rejected alternatives in design.md +- [x] 1.1 Survey the five Go repositories' `CONTRIBUTING.md` headings and + establish where they disagree +- [x] 1.2 Establish what each `go-code-standards` requirement is: policy no tool + reports on, something a tool already enforces, or a convention a reader + applies +- [x] 1.3 Write the `repo-standards` delta fixing the middle sections, requiring + sentence-case headings, requiring a repository to state its conventions in + full, and stopping prose from restating what a tool enforces +- [x] 1.4 Write the `go-code-standards` delta removing the five formatting + requirements and widening `Mocks are generated` +- [x] 1.5 Record the decisions and their rejected alternatives in design.md +- [x] 1.6 `specs` — sync `repo-standards` into `openspec/specs/`. It was not + there, so this change's delta had nothing to land on and the repositories + citing it pointed at a capability the corpus did not hold. Merged from the + `standardize-repository-layout` and `specify-agent-tool-invocation` deltas: 16 + requirements, 44 scenarios, `openspec validate --specs` passing -## 2. Establish the shared fragment +## 2. Write the shared sections once -Nothing is removed from a repository until the fragment that replaces it exists -and can be fetched. +- [ ] 2.1 Draft the shared `Code standards` text — `Function signatures`, + `File naming`, `Go patterns` — with the worked examples that left the + capability. Verified by the draft covering every rule this change removes from + `go-code-standards` +- [ ] 2.2 Draft the shared `Testing` text — `Test file conventions`, suite + naming, table-driven cases, and the `export_test.go` pattern +- [ ] 2.3 Confirm the draft states no rule that `.golangci.yml` or a formatter + already enforces, naming the configuration instead -- [ ] 2.1 `osapi-justfiles` — write the shared Go conventions fragment: file - naming, `types.go` for types only, test file naming, table-driven suites, - suite naming, the `export_test.go` pattern, and worked signature examples. - Verified by the fragment containing every rule removed from - `go-code-standards` by this change -- [ ] 2.2 `osapi-justfiles` — add the recipe that fetches it, following the - pattern the `justfiles` capability records for recipes. Verified by a fresh - checkout of a consumer producing the file -- [ ] 2.3 Confirm the fetched path is ignored rather than committed in every - consumer, and that no formatter or linter runs against it +## 3. Apply to each repository -## 3. Return the conventions to each repository +One pull request per repository, each landing the same shared text under the +same headings in the same order. -One repository per pull request, each fetching the fragment and stating its own -conventions beside it. - -- [ ] 3.1 `gohai` — restore the shared conventions via the fragment. It is on - `main` with them removed (osapi-io/gohai#163), so it goes first -- [ ] 3.2 `osapi-orchestrator` — supersede the held removal - (osapi-io/osapi-orchestrator#76) with the fragment, and close it -- [ ] 3.3 `osapi` — replace the pointer in its root `CONTRIBUTING.md` - (osapi-io/osapi#450) with the fragment -- [ ] 3.4 `nats-client` — replace its restated copy with the fragment -- [ ] 3.5 `nats-server` — replace its restated copy with the fragment +- [ ] 3.1 `gohai` — restore the shared conventions, move `Go patterns` out from + under `Testing`, and rename `Package Structure` to `Project structure`. It is + on `main` with the conventions removed, so it goes first +- [ ] 3.2 `osapi-orchestrator` — supersede and close the held removal + (osapi-io/osapi-orchestrator#76), and fold `Project Structure` and + `Package Structure` into one `Project structure` +- [ ] 3.3 `nats-client` — move `Function signatures` and `Go patterns` from + `Code style` to `Code standards` +- [ ] 3.4 `nats-server` — the same +- [ ] 3.5 `osapi` — replace the pointer with the shared text, keeping its own + `Logging`, `Lifecycle`, and `Filesystem access` sections +- [ ] 3.6 Move every repository-specific section after `Testing`, and convert + every heading to sentence case ## 4. Let the configuration speak for what it enforces -- [ ] 4.1 Remove the hand-maintained linter list from every repository's - contributor documentation, naming `.golangci.yml` instead. Verified by no - repository's prose enumerating linters -- [ ] 4.2 Confirm the removed lists were wrong in the same way everywhere — - `goimports` named as a linter, `unused` omitted — so the reason for removing - them is recorded rather than asserted +- [ ] 4.1 Remove the hand-maintained linter list from all five repositories, + naming `.golangci.yml` instead. Verified by no repository enumerating linters + in prose +- [ ] 4.2 Record that the removed lists were wrong in the same way everywhere — + `goimports` named as a linter, `unused` omitted — so the reason is evidenced + rather than asserted ## 5. Resolve the mocks finding - [ ] 5.1 `osapi` — record `mockPKISigner` as a real implementation under the - widened requirement, rather than converting it. It signs with a generated - ed25519 key pair, and a generated mock would replace that with a canned return + widened requirement rather than converting it - [ ] 5.2 `gohai` — replace `fakeCollector` with a generated mock, or state why - the collector interface is better served by a real implementation + a real implementation serves the collector interface better - [ ] 5.3 `osapi-orchestrator` — replace `mockRenderer` with a generated mock, - and correct `CONTRIBUTING.md`, which says the repository declares no mocking + and correct `CONTRIBUTING.md`, which claims the repository declares no mocking library while hand-rolling one ## 6. Verification -- [ ] 6.1 Confirm every rule removed from `go-code-standards` is stated in the - fragment or enforced by a tool, and that none was dropped -- [ ] 6.2 Confirm each of the five repositories holds the conventions on disk, - readable without fetching another repository -- [ ] 6.3 Confirm no convention is stated both in the fragment and in a - repository's own section -- [ ] 6.4 Confirm `go-code-standards` retains only requirements no tool reports +- [ ] 6.1 Confirm the five `CONTRIBUTING.md` files carry the same `##` headings, + in the same order, up to their repository-specific sections +- [ ] 6.2 Confirm every heading in all five is sentence case +- [ ] 6.3 Confirm the shared sections are byte-identical across the five, so a + difference in wording would mean a difference in rule +- [ ] 6.4 Confirm every rule removed from `go-code-standards` is stated in all + five repositories or enforced by a tool, and that none was dropped +- [ ] 6.5 Confirm `go-code-standards` retains only requirements no tool reports on -- [ ] 6.5 Confirm `specify-go-code-standards` tasks 2.2, 2.3, 2.4, and 3.6 are +- [ ] 6.6 Confirm `specify-go-code-standards` tasks 2.2, 2.3, 2.4, and 3.6 are reconciled with this change rather than left describing the pointer-only destination it replaces diff --git a/openspec/specs/repo-standards/spec.md b/openspec/specs/repo-standards/spec.md new file mode 100644 index 0000000..eaa131a --- /dev/null +++ b/openspec/specs/repo-standards/spec.md @@ -0,0 +1,504 @@ +# repo-standards Specification + +## Purpose + +Defines which files an osapi-io repository carries and how its README is +structured, so that repositories of the same kind read alike and a new +repository can be created without imitating an arbitrary existing one. + +## Requirements + +### Requirement: Repository types + +Every repository SHALL be one of five types, and its type SHALL determine its +README structure. + +- **Go library** — a package other repositories import. A library MAY ship a CLI + that wraps it; the library is the product and the CLI is a convenience, so it + remains this type. +- **Main product** — the deployable service the organization exists to build. +- **Utility** — shared assets consumed by other repositories rather than + imported as code. +- **Documentation** — specifications and design records. +- **UI** — a user interface, run or embedded rather than imported. + +#### Scenario: Classifying a new repository + +- **WHEN** a repository is created to hold a Go package other repositories + import +- **THEN** it is a Go library and follows the Go library README structure + +#### Scenario: Types with different audiences + +- **WHEN** a reader opens a utility repository's README +- **THEN** it explains how to consume the assets, not how to import a package + +### Requirement: Repository classification + +Every repository SHALL have a recorded type. The classification is: + +| Repository | Type | +| -------------------- | ------------- | +| `gohai` | Go library | +| `nats-client` | Go library | +| `nats-server` | Go library | +| `osapi-orchestrator` | Go library | +| `osapi` | Main product | +| `osapi-justfiles` | Utility | +| `specs` | Documentation | + +`osapi-sdk`, `osapi-ui`, and `osapi-io-taskfiles` are outside this capability. +`osapi-sdk` and `osapi-ui` state their deprecation at the top of their README +and record that their contents moved into `osapi`. `osapi-io-taskfiles` states +nothing: no repository consumes it, and its README still presents it as current. + +The **UI** type currently has no members. It is retained because a standalone +user interface repository may exist again, and a type with no members is cheaper +than reconstructing one later. + +Adding a repository, or changing one's type, is a change to this capability. + +#### Scenario: Determining which structure applies + +- **WHEN** a contributor needs to know which README structure `gohai` must + follow +- **THEN** the classification records it as a Go library, without inferring it + from the repository's contents + +#### Scenario: A repository is deprecated + +- **WHEN** a repository's README states it is deprecated and its contents have + moved elsewhere +- **THEN** it is recorded as deprecated and this capability stops binding it, + regardless of whether it has been archived + +#### Scenario: A repository is added + +- **WHEN** a new repository is created +- **THEN** it is added to the classification, rather than left to be classified + by whoever next edits it + +### Requirement: Each fact is stated once + +A fact SHALL be stated in exactly one file — the one that owns it — and every +other file that needs it SHALL point there rather than restating it. + +Ownership follows audience: anything that applies to any contributor belongs in +`CONTRIBUTING.md`; anything specific to agents belongs in `AGENTS.md`; the +README orients a reader and links onward. + +A file SHALL NOT summarize another file's content beyond what is needed to say +where to look. + +#### Scenario: A convention applies to everyone + +- **WHEN** a convention applies to both people and agents, such as the command + to run before opening a pull request +- **THEN** it is stated in `CONTRIBUTING.md`, and `AGENTS.md` points there + rather than repeating it + +#### Scenario: The same instruction appears twice + +- **WHEN** setup instructions appear in both the README and `CONTRIBUTING.md` +- **THEN** the README's copy is replaced by a pointer, because the two will + otherwise drift and a reader cannot tell which is current + +#### Scenario: A new file is added + +- **WHEN** a repository adds a file that overlaps an existing one +- **THEN** the overlapping content moves to whichever file owns that audience, + and the other points to it + +### Requirement: Files every repository carries + +Every repository SHALL contain `README.md`, `LICENSE`, `AI_POLICY.md`, +`CODE_OF_CONDUCT.md`, `CONTRIBUTING.md`, `AGENTS.md`, `CLAUDE.md`, and +`.mise.toml`. + +`.mise.toml` SHALL be committed, never ignored. Its contents vary — a repository +pins the tools it actually uses — but every repository has one, so that +`mise install` provisions a working environment. + +A tool a repository invokes SHALL be declared there, including the task runner +itself. A tool that is not declared is supplied by whatever the developer +happens to have installed, which is not the same thing across machines and is +not what continuous integration installs. + +#### Scenario: Auditing a repository + +- **WHEN** a repository is checked against the standard +- **THEN** the absence of any of those files is a defect + +#### Scenario: The task runner is not declared + +- **WHEN** a repository runs every check through a task runner but does not + declare it +- **THEN** that is a defect: the version comes from the developer's system, and + a formatter check can pass locally and fail in continuous integration on the + same file + +#### Scenario: Repository no longer needs a tool + +- **WHEN** a repository stops using a tool, such as dropping bun after moving + its markdown formatting to mdformat +- **THEN** that tool is removed from `.mise.toml`, which still exists and still + pins what remains + +### Requirement: Every path provisions the same tool version + +A repository provisions its tools twice — `.mise.toml` for local work, and setup +actions in its workflows. Both SHALL resolve to the same version. + +Where something maintains the version automatically, both paths SHALL pin it and +that mechanism SHALL move both. Where nothing does, both paths SHALL track the +latest release, so they move together. + +A version pinned in one path and floating in the other SHALL NOT be used. It +guarantees divergence at the next release, and the divergence surfaces as a +check failing on a file nobody edited. + +#### Scenario: Nothing automates the version + +- **WHEN** a tool's version is not maintained by any update mechanism, as + `.mise.toml` is not watched by Dependabot +- **THEN** both paths track the latest release, because a pin nothing bumps is a + pin that only one side keeps + +#### Scenario: An update mechanism covers the tool + +- **WHEN** a tool's version is maintained automatically, as action and module + versions are +- **THEN** both paths pin it, and that mechanism raises both + +#### Scenario: A release changes the tool's output + +- **WHEN** a formatter release reformats files a check compares against + committed bytes +- **THEN** the check fails in continuous integration and locally alike, and the + committed files are reformatted once — rather than one side passing and the + other failing + +#### Scenario: A tool is invoked but not declared + +- **WHEN** a repository runs its checks through a tool it does not declare in + `.mise.toml` +- **THEN** that is a defect: the version comes from whatever the developer has + installed, and no path can be said to match + +### Requirement: A repository no longer in use is archived + +A repository that is no longer developed or consumed SHALL state that at the top +of its README and SHALL be archived on GitHub. + +Stating deprecation without archiving leaves the repository accepting issues, +pull requests, and pushes, and listed alongside maintained ones. A reader +choosing between repositories sees no difference until they open the README. + +#### Scenario: A repository's contents move elsewhere + +- **WHEN** a repository's contents are absorbed into another +- **THEN** its README records where they went, and the repository is archived + +#### Scenario: Nothing consumes a repository any more + +- **WHEN** no repository declares a dependency on it and nothing references its + files +- **THEN** it is deprecated explicitly, rather than left presenting itself as + current + +#### Scenario: A repository is superseded by a replacement + +- **WHEN** an approach is replaced by another, such as task runner definitions + replaced by a different task runner +- **THEN** the superseded repository names its replacement, so a reader who + finds it is sent to the current one + +### Requirement: Boilerplate files are standardized + +`AI_POLICY.md` and `CODE_OF_CONDUCT.md` SHALL be byte-identical across every +repository. They state organization-wide policy, so a repository holding a +variant states policy that is not the organization's. + +`CODE_OF_CONDUCT.md` SHALL name a working enforcement contact. + +`LICENSE` SHALL be identical across every repository **except** its copyright +year, which SHALL be the year that repository was created. The copyright holder +SHALL be `John Dewey` in every repository. + +A repository SHALL NOT express the year as a range. + +`AI_POLICY.md` SHALL refer to the organization as `osapi-io`. It SHALL NOT use +the name of any individual repository, and SHALL NOT use `OSAPI`, which is the +name of a repository and reads as that product rather than the organization. + +#### Scenario: Repositories created in different years + +- **WHEN** one repository was created in 2024 and another in 2026 +- **THEN** their `LICENSE` files differ only on the copyright year, and each + states the year that repository was created + +#### Scenario: A repository is modified years later + +- **WHEN** a repository created in 2024 is still being changed in 2026 +- **THEN** its copyright year remains 2024, and is not updated or expressed as a + range + +#### Scenario: Policy changes + +- **WHEN** the AI usage policy is amended +- **THEN** the same file content lands in every repository, with no repository + carrying a reworded version + +#### Scenario: Policy is read in a repository that is not the product + +- **WHEN** a contributor reads the AI policy in `gohai` or `nats-client` +- **THEN** it names `osapi-io`, so it is clear the policy is the organization's + and not one repository's + +#### Scenario: Reader checks the code of conduct + +- **WHEN** a contributor wants to report a violation +- **THEN** the document names a contact they can actually reach + +### Requirement: Only source is tracked + +A repository SHALL track only its source, its configuration, and its +documentation. It SHALL NOT track command output, generated artifacts fetched at +build time, or data describing a particular machine. + +Where such a file has already been committed, adding it to `.gitignore` is not +sufficient — `.gitignore` suppresses only untracked files, so the file SHALL be +removed from tracking as well. + +#### Scenario: Command output is redirected to a file + +- **WHEN** a contributor redirects a command's output to a file inside the + repository +- **THEN** it is not committed, because it describes the machine it ran on + rather than the project + +#### Scenario: Fetched build-time file + +- **WHEN** a repository fetches shared configuration at build time +- **THEN** the fetched copy is ignored rather than committed, so it cannot drift + from its source + +#### Scenario: A fetched file is committed because it was modified + +- **WHEN** a file that looks fetched is tracked, and its content differs from + what the fetch retrieves +- **THEN** it is a deliberate override, not a stray, and removing it breaks the + build. The shared module is changed to take the differing value as + configuration, and only then is the local copy removed + +#### Scenario: Deciding whether a tracked file is stray + +- **WHEN** a file is proposed for removal from tracking +- **THEN** its content is read first. A path that matches a generated or fetched + location is not sufficient evidence that nothing depends on the committed copy + +#### Scenario: The file is already tracked + +- **WHEN** an ignore rule is added for a file that is already committed +- **THEN** the file is also removed from tracking, because the rule alone has no + effect on it + +### Requirement: Contributing documentation location + +Contributing documentation SHALL live in a single `CONTRIBUTING.md` at the +repository root. Prerequisites, setup, conventions, and the pull request +workflow SHALL all be found there. + +A repository SHALL NOT split this content across separate contributing and +development documents. + +#### Scenario: Contributor looks for setup instructions + +- **WHEN** a contributor wants to know how to build and test a repository +- **THEN** the instructions are in `CONTRIBUTING.md` at the root + +#### Scenario: Repository publishes a documentation site + +- **WHEN** a repository publishes a documentation site that already contains + contributing or development pages +- **THEN** the root `CONTRIBUTING.md` becomes the single source, and the site's + page is reduced to a pointer to it rather than being deleted or kept as a + second copy + +#### Scenario: GitHub surfaces the guide + +- **WHEN** a contributor opens a new issue or pull request +- **THEN** GitHub links the repository's contributing guide, because it is at + the location GitHub recognizes + +### Requirement: CONTRIBUTING structure + +`CONTRIBUTING.md` SHALL open and close with a fixed set of sections, in this +order, with repository-specific sections placed between them: + +| Section | Position | Content | +| ----------------------- | -------- | --------------------------------------- | +| `## Before you start` | opening | Code of Conduct, checking existing work | +| `## Prerequisites` | opening | Tools a contributor installs | +| *(repository-specific)* | middle | Setup, conventions, workflow | +| `## Before committing` | closing | The command that runs what CI runs | +| `## Branching` | closing | Branch naming | +| `## Commit messages` | closing | Commit conventions | +| `## Submitting a PR` | closing | Pull request expectations | +| `## AI usage` | closing | Pointer to `AI_POLICY.md` | +| `## FAQ` | closing | Where to get help | + +Section names SHALL be taken from this table rather than reworded. The middle +varies by repository, because what a contributor needs to know differs. + +#### Scenario: Two repositories describe the pre-commit check + +- **WHEN** two repositories document the command a contributor runs before + committing +- **THEN** both name that section `## Before committing`, not one using + `Before committing` and the other `Finishing a change` + +#### Scenario: A repository has domain-specific conventions + +- **WHEN** a repository has conventions that apply only to it, such as how its + recipes are written +- **THEN** those appear as a middle section, leaving the opening and closing + sections in place + +### Requirement: Agent guidance has no prescribed structure + +`AGENTS.md` SHALL NOT have a required section structure. Its content is whatever +is specific to that repository and to agents, and forcing a common skeleton +would produce empty sections. + +It SHALL, however, open by directing the reader to `CONTRIBUTING.md`. + +#### Scenario: Two repositories need different agent guidance + +- **WHEN** one repository needs a planning boundary documented and another needs + a warning that its artifacts cannot be tested locally +- **THEN** each states only what applies to it, with no shared headings required + +### Requirement: Agent guidance is tool-neutral + +Agent guidance SHALL live in `AGENTS.md`. `CLAUDE.md` SHALL exist and SHALL +point to it rather than duplicating its content. + +`AGENTS.md` SHALL carry only guidance specific to agents, and SHALL reference +`CONTRIBUTING.md` for anything that applies to people as well. + +#### Scenario: Convention applies to both audiences + +- **WHEN** a convention applies to any contributor, such as running the test + suite before opening a pull request +- **THEN** it is stated in `CONTRIBUTING.md`, and `AGENTS.md` points there + rather than restating it + +#### Scenario: A different agent tool is adopted + +- **WHEN** a tool other than Claude Code is used +- **THEN** the guidance is already tool-neutral and needs no rewrite + +### Requirement: README section vocabulary + +README sections SHALL be drawn from a fixed vocabulary, each with its emoji, and +SHALL appear in this order when present: + +| Section | Purpose | +| --------------------- | ------------------------------------ | +| `## 📦 Install` | How to obtain the thing | +| `## ✨ Features` | What it does, as a table | +| `## 🎯 Usage` | How it is used, in prose or commands | +| `## 📋 Examples` | Pointers to runnable examples | +| `## 📖 Documentation` | Links to fuller documentation | +| `## 🤝 Contributing` | Pointer to `CONTRIBUTING.md` | +| `## 📄 License` | The license | + +A repository SHALL NOT invent a section name for a purpose the vocabulary +already covers. + +#### Scenario: Two repositories describe the same thing + +- **WHEN** two repositories both document how they are used +- **THEN** both use `## 🎯 Usage`, not one using `Usage` and the other `Examples` + +#### Scenario: A section does not apply + +- **WHEN** a repository has no runnable examples +- **THEN** it omits `## 📋 Examples` rather than renaming another section + +### Requirement: README structure by type + +Each repository type SHALL use the section set defined for it. + +A **Go library** README SHALL contain `Install`, `Features`, `Examples`, +`Documentation`, `Contributing`, and `License`. It MAY add sections for subject +matter the vocabulary does not cover, placed before `Contributing`. + +A **utility** README SHALL contain `Usage`, `Documentation`, `Contributing`, and +`License`. + +A **documentation** README SHALL contain `Usage`, `Documentation`, +`Contributing`, and `License`. + +A **UI** README SHALL contain `Usage`, `Documentation`, `Contributing`, and +`License`. A UI is run or embedded rather than obtained as a package, so it has +no `Install` section. + +The **main product** README is exempt from this requirement. It serves as the +organization's landing page rather than describing a consumable artifact. + +#### Scenario: Reading two Go libraries + +- **WHEN** a developer reads the README of one Go library and then another +- **THEN** the same sections appear in the same order + +#### Scenario: A library needs a subject-specific section + +- **WHEN** a Go library documents a concept the vocabulary does not cover +- **THEN** it adds that section before `Contributing`, leaving the standard + sections in place + +### Requirement: Contributing section content + +The `## 🤝 Contributing` section SHALL point to `CONTRIBUTING.md` rather than +restating its content. + +#### Scenario: Reader follows the contributing section + +- **WHEN** a reader reaches the contributing section of any README +- **THEN** it directs them to `CONTRIBUTING.md` for prerequisites, setup, + conventions, and the pull request workflow + +### Requirement: Agent guidance says how to invoke the repository's tools + +`AGENTS.md` SHALL state that tools are invoked through the version manager the +repository declares, rather than from the shell's path. + +A person working in a repository has that version manager active in their shell, +so the declared version is what they get. An agent runs commands in a shell +without it, and gets whatever happens to be installed — a different version, +silently. + +The failure this produces is misleading rather than obvious: a check fails for +the agent and passes for everyone else, on a file nobody changed, and the +difference is invisible in the output. + +#### Scenario: An agent runs a formatter + +- **WHEN** an agent runs a repository's format check +- **THEN** it invokes it through the version manager, so the result matches what + continuous integration reports + +#### Scenario: An agent sees a failure nobody else sees + +- **WHEN** a check fails for an agent and passes in continuous integration on + the same commit +- **THEN** the version the agent invoked is the first thing to establish, before + the failure is treated as real + +#### Scenario: A repository declares no version manager + +- **WHEN** a repository has no version manager configuration +- **THEN** that is a defect under the requirement that a tool a repository + invokes is declared