diff --git a/.claude/rules/docs-authoring.md b/.claude/rules/docs-authoring.md index a4b10dc..b94fac2 100644 --- a/.claude/rules/docs-authoring.md +++ b/.claude/rules/docs-authoring.md @@ -25,6 +25,8 @@ When creating or editing any markdown file, follow the discipline below. These a - Document a tunable value by the **name a consumer changes it by** (env var, config key, CLI flag, or a named member of a centralized constants/config module that other code reads), judging by role, not location. Don't document an ephemeral local variable as the config surface. - **Acronyms** in prose you write or edit use capitals (ID, URL) and are expanded on first use per doc ("Deoxyribonucleic acid (DNA)"). Keep exact casing for brand/tool/package names (npm, iOS), domain terms (snRNA), and direct code references (an `id` field). - No placeholders, TODOs, or empty "add details here" sections. +- **Two readers, one document.** Every page is read by a newcomer meeting the system for the first time and by someone who already works in it, and serving only the second is the ordinary failure. Serve both by order rather than by splitting the page: what the subject is and why a reader would reach for it, what that reader must already have or have read, then the depth in full. A page only its author can follow is not finished, and neither is one whose reader could have got it faster from the source. +- **Introduce every term of art where the document first uses it**, in a short parenthesis or by a link to the document that defines it, then use it unchanged. Expanding an acronym is not introducing it, since the expansion is often as opaque as the abbreviation. Spell a concept one way across [`docs/`](../../docs/index.md): a concept spelled three ways is three concepts to anyone meeting it, and it defeats their search. - A document opens with a single H1 named for its file, then a one to three sentence introduction for a reader who does not yet know the subject, then H2s. Headings are unique and fully descriptive ("Retry backoff limits", not "Limits"), because anchors are generated from them, and use sentence case. - **Tables only for uniform data scanned quickly.** If columns repeat across rows, cells sit empty, or a cell holds a sentence of prose, use a list instead. - Prefer Markdown to raw HTML for layout or styling. diff --git a/.claude/skills/audit-docs/SKILL.md b/.claude/skills/audit-docs/SKILL.md index 767181b..31032c5 100644 --- a/.claude/skills/audit-docs/SKILL.md +++ b/.claude/skills/audit-docs/SKILL.md @@ -9,15 +9,17 @@ argument-hint: '[paths or area to audit; defaults to the active pull request or Act as a **Strictly Factual Technical Writer and Auditor**. Make the project's documentation directory, `docs/` below and whatever this project actually names it, an objective, verifiable reflection of the current #codebase. Write and correct documentation so `docs/` matches the #codebase, #activePullRequest, or #changes. Being strictly factual does not mean sounding machine-generated: write the way a careful human technical writer would, applying the **Voice** guidance in section 3. -**Scope: documentation only.** Unless the invoking task explicitly asks for code or behaviour changes, this run edits documentation (markdown, text files, and in-code comments, docstrings, and file-level headers) and never changes executable code or behaviour. See Rule 1. +**Scope: documentation only.** This run edits documentation and never changes executable code or behaviour. Rule 1 carries the boundary and its one exception. **Core philosophy:** - **Reporter, not editor.** Convert code facts into documentation. Do not editorialize, which means no value judgments you cannot cite and no unverified claims. -- **Document value, not narration.** Code is self-documenting for _what_ it does; `docs/` prose must add what code cannot show: _why_ something exists (decisions, constraints, trade-offs), _how_ parts interact (boundaries, data flows, integration points), and _when_ to use it (context, prerequisites). If a sentence only restates the code, cut it. _Exception:_ consumer-facing API/tool docs must state _what_ the code does, since external readers cannot see the source. +- **Document value, not narration, and orient before going deep.** `docs/` prose adds what code cannot show: _why_ something exists (decisions, constraints, trade-offs), _how_ parts interact (boundaries, data flows, integration points), and _when_ to use it (context, prerequisites). Cut a sentence that restates a line the reader of that page can already see. The _what_ is not narration where that reader cannot supply it, so state it plainly in two places: consumer-facing API and tool documentation, whose readers cannot open the source, and the opening of any document, whose reader has not yet been told what the subject is. - **Link, do not duplicate.** Point to source files; never copy code into markdown. -**Audience and tone:** every document serves internal developers maintaining the architecture and external developers consuming the APIs, so prefer content useful to both. Serve human skimmers and coding-assistant readers with the same prose: one canonical term per concept, and an ambiguous `it`/`this`/`these` replaced by the actual noun when the referent could drift. Stay approachable for concepts, precise for details, objective always (Rule 3), and formal without being stiff (see **Voice** in section 3). No contractions. +**Two readers, one document.** Every page is read by a **newcomer** meeting this system for the first time and by an **experienced reader** who already works in it, and serving only the second is the ordinary failure. Serve both by order rather than by splitting the page: say what the subject is and why a reader would reach for it, introduce every acronym, term of art, and named component where the document first uses it, and state what that reader must already have or have read. Depth follows, and it follows in full: the constraint, the invariant, the boundary, and the consequence a caller plans around. So a document fails in two ways, and §6 checks for both: a reader who cannot follow it without leaving the page, and a reader who could have got it faster from the source. + +**Tone:** serve human skimmers and coding-assistant readers with the same prose: one canonical term per concept, and an ambiguous `it`/`this`/`these` replaced by the actual noun when the referent could drift. Stay approachable for concepts, precise for details, objective always (Rule 3), and formal without being stiff (see **Voice** in section 3). No contractions. --- @@ -36,6 +38,7 @@ Some agents resolve the references below automatically. Where yours does not, re Open one of these when the run needs its detail. Nothing here is loaded until you open it. - [`evidence-and-citation.md`](references/evidence-and-citation.md) - how to hold proof, which sources are not evidence, and the hallucination patterns each check catches. Read before Phase 2 or 3 on an unfamiliar codebase. +- [`writing-for-both-readers.md`](references/writing-for-both-readers.md) - the ordering principle, the four things an opening carries, how to introduce a term of art, and the read-it-cold procedure, each with a before and after pair. Read before writing or revising any document's opening. - [`voice-and-ai-tells.md`](references/voice-and-ai-tells.md) - the tell catalogue with a corrected rewrite for each. Read while writing or revising prose. - [`diagram-and-image-accessibility.md`](references/diagram-and-image-accessibility.md) - worked `accTitle` and `accDescr` examples, and how to choose a diagram type. Read before adding or editing a diagram. - [`audit-report.template.md`](assets/audit-report.template.md) - the report shape for the end of the run. @@ -44,13 +47,13 @@ Open one of these when the run needs its detail. Nothing here is loaded until yo Five procedures ship with this skill, one per file under `agents/`. **The default is to run none of them.** Each is for work the main run cannot afford to do itself, and the scope rule below bounds what any of them receives: on a pull request they cover the changed set, not the tree. A small pull request should reach for nothing here. -| Procedure | Run it when | Skip it when | -| -------------------------------------------------- | ------------------------------------------------------------------------------- | ---------------------------------- | -| [`coverage-mapper`](agents/coverage-mapper.md) | scope is the whole documentation set, or the change adds or removes a component | scope is a single named document | -| [`curation-reviewer`](agents/curation-reviewer.md) | a document was substantially rewritten, or is consumer-facing | only a factual correction was made | -| [`diagram-reviewer`](agents/diagram-reviewer.md) | a document contains a diagram, or describes a flow of five or more steps | neither is true | -| [`surface-auditor`](agents/surface-auditor.md) | the change touches a public surface | the change is documentation-only | -| [`claim-verifier`](agents/claim-verifier.md) | a claim you cannot ground from a file already open | the proof is already in hand | +| Procedure | Run it when | Skip it when | +| -------------------------------------------------- | ------------------------------------------------------------------------------- | -------------------------------------------------------------- | +| [`coverage-mapper`](agents/coverage-mapper.md) | scope is the whole documentation set, or the change adds or removes a component | scope is a single named document | +| [`curation-reviewer`](agents/curation-reviewer.md) | a document was created, substantially rewritten, or given new prose | only a factual correction inside an existing sentence was made | +| [`diagram-reviewer`](agents/diagram-reviewer.md) | a document contains a diagram, or describes a flow of five or more steps | neither is true | +| [`surface-auditor`](agents/surface-auditor.md) | the change touches a public surface | the change is documentation-only | +| [`claim-verifier`](agents/claim-verifier.md) | a claim you cannot ground from a file already open | the proof is already in hand | **Open the file and follow it yourself.** That is the way to run one, it works wherever this skill is installed, and it cannot fail. Where your host registers these files as agents you can delegate to, handing one off is an option that keeps the reading out of this context. Where delegating is unavailable, names an agent the host does not recognize, or errors, fall back to opening the file. **Never improvise instructions from a procedure's name or from this table's one-line summary of it.** What makes a procedure safe to run is the scope bound and the evidence bar written inside it, and neither survives being paraphrased. @@ -71,10 +74,11 @@ Each procedure reports findings rather than edits, so every decision stays with ### Phase 2: general audit - **Inventory before you correct.** List every document in scope with the subject it claims and the code that subject maps to. The three actions below are undecidable without that list: duplication is visible only across documents, a removed feature only where a document's subject is absent from the code, and a missing document only as code with no entry. Report how many documents you opened, and name anything in scope you did not, so that "already accurate" cannot be confused with "not looked at". -- Audit the documents the scope rule resolved to against the current #codebase. That is all of `docs/` only where the rule resolved to the whole documentation set, and on a pull request it is the documents describing the changed code. **Correct** pre-existing content that contradicts the code, preserving accurate content's phrasing and style. +- **Record each document's type in that inventory,** under the **Diátaxis** framework, decided by what its reader needs rather than by its subject: content informing action serves the acquisition of skill as a tutorial and its application as a how-to guide, and content informing cognition serves acquisition as an explanation and application as a reference. A set can be complete and accurate and still have no way in. Where the scope resolved to the whole documentation set and nothing takes a first-time reader through one task end to end, report that gap; write the missing document only where the invoking task asks for it, every step cited under Rule 2 from a script or configuration file that exists. +- Audit the documents the scope rule resolved to against the current #codebase. That is all of `docs/` only where the rule resolved to the whole documentation set, and on a pull request it is the documents describing the changed code. **Correct** pre-existing content that contradicts the code, preserving accurate content's phrasing and style. **A newcomer blocker is correctable too, even where the prose around it is accurate**, since introducing a term the document already uses, naming the subject in an opening that never did, and stating a prerequisite are additions rather than rewrites. Make them, and leave everything else about that prose as it reads: reporting a blocker you were free to fix is not a result. - **Delete** pre-existing content only if it is massively duplicated, describes removed features, or fundamentally cannot be corrected. Default to correcting, not deleting. Your own generated content may be edited or removed freely when wrong. -- **Create new files** only when needed, for a new component or system, an external interface guide, or a genuinely missing structure. **Decide the directory before writing a word, and decide it by document type rather than by subject.** Classify what you are about to write under the **Diátaxis** framework, as a tutorial, a how-to guide, a reference, or an explanation, then open the candidate directory's entry-point file and two or three of its siblings and place the document only where those siblings are the same type. A directory's name is a claim about what it holds, so a how-to guide filed among explanations is in the wrong place even where its subject belongs to that area, and a reader who trusted the directory now has to read it to find out. **Precedent settles it where precedent exists:** a sibling of the same type already in that directory makes the placement correct, and the new document joins it. The directory merely touching the same topic is not precedent. Where no directory holds that type, create one with an entry-point file named as the project's existing directories name theirs. State in your output which directory you chose and which sibling or precedent decided it. -- **Output:** state whether you made changes or found docs already accurate. +- **Create new files** only when needed, for a new component or system, an external interface guide, an entry path a first-time reader has nowhere else to start from, or a genuinely missing structure. **Decide the directory before writing a word, and decide it by document type rather than by subject.** Classify what you are about to write by the same four types, then open the candidate directory's entry-point file and two or three of its siblings and place the document only where those siblings are the same type. A directory's name is a claim about what it holds, so a how-to guide filed among explanations is in the wrong place even where its subject belongs to that area, and a reader who trusted the directory now has to read it to find out. **Precedent settles it where precedent exists:** a sibling of the same type already in that directory makes the placement correct, and the new document joins it. The directory merely touching the same topic is not precedent. Where no directory holds that type, create one with an entry-point file named as the project's existing directories name theirs. State in your output which directory you chose and which sibling or precedent decided it. +- **Output:** state whether you made changes or found docs already accurate, and give each document its newcomer result: the first place a reader who has not seen this codebase would stop, or that nothing does. ### Phase 3: in-code documentation audit @@ -82,12 +86,12 @@ Each procedure reports findings rather than edits, so every decision stays with - **Scope:** the code the scope rule above resolved to, covering its documentation comments, inline comments, and file-level headers, plus every `.md` file inside that scope which sits outside `docs/`. A file the rule did not resolve to stays out whatever it contains, so this phase is never a repository-wide sweep for markdown, for undocumented symbols, or for a comment pattern. - **Actions:** scan for documentation and comments; read the current implementation of each documented element; verify it against actual code behaviour; correct or remove anything inaccurate or outdated, an orphaned TODO included; document every public symbol that lacks it; remove bloat, keeping "why" explanations, non-obvious "what" descriptions, and essential "how" for complex algorithms. Removing bloat means deleting comments that restate the code, never comments that explain a non-obvious internal. -- **Always document the public surface.** Every public or exported symbol carries a documentation comment, as do the members of a public structure: fields, properties, keys, enum values. Write for a reader meeting the symbol for the first time, assuming they can infer nothing from its name. Reach for what the declaration cannot express, such as why it exists, a constraint, an invariant, or a caller obligation. Where no such explanation exists, a plain restatement of what the symbol does is correct: being obvious is not a defect on a public surface, being absent is. **Rule 2 still governs, and it comes first.** Reading the body is the precondition for writing the comment, not a step to infer around: not having got to it is no reason to skip it, and being unable to reach it is no reason to guess. Where you have not read the body, leave the symbol as it is and name it in your output. A public symbol left undocumented and reported is a compliant result; a comment written from the symbol's name is a defect, and it is the defect this rule exists to prevent. +- **Always document the public surface.** Every public or exported symbol carries a documentation comment, as do the members of a public structure: fields, properties, keys, enum values. Write for a reader meeting the symbol for the first time, assuming they can infer nothing from its name. Reach for what the declaration cannot express, such as why it exists, a constraint, an invariant, or a caller obligation. Where no such explanation exists, a plain restatement of what the symbol does is correct: being obvious is not a defect on a public surface, being absent is. **Rule 2 still governs, and it comes first.** Reading the body is the precondition for writing the comment, not a step to infer around: not having got to it is no reason to skip it, and being unable to reach it is no reason to guess. Where you have not read the body, leave the symbol as it is and name it in your output: undocumented and reported is a compliant result, where a comment written from the symbol's name is the defect this rule exists to prevent. - **Do not restate what the language's own syntax declares**, such as a type, a visibility modifier, or an override marker. This governs what you write in a **new** documentation comment and never licenses removing an existing one. - **Correct an existing documentation tag; do not strip or delete it.** A parameter, return, throws, or example entry was written deliberately. Read enough surrounding code to judge it, then fix what is factually wrong and leave what is right, including parts a convention would omit in new code. Removing a tag, or a piece of one, because it looks redundant is restyling someone else's work, not auditing it. Delete a whole tag only when it is wrong and uncorrectable, such as one documenting a parameter the signature no longer has. Phase 2's "default to correcting, not deleting" governs in-code documentation too. - **Internal elements** are documented only where the logic is complex or carries a gotcha or edge case, and a comment inside a function body is written only for non-obvious business logic, a workaround, or a complex transformation. Delete an internal comment only when it restates the line beneath it, such as `// Increment counter` above a counter increment (delete the comment, keep the code). -- **A fact is documented once, at the declaration of the thing it is about.** A statement about a symbol belongs on that symbol's own declaration, never above the lines that read it, call it, or branch on it. Where the same sentence would sit above more than one _use_ of a symbol, it belongs on the declaration alone, or in `docs/` where it spans more than one symbol. A declaration is not a use: each member of a public structure still gets its own comment, and a file-level header still summarizes what the file declares. **Removing a copy is bounded, and all three preconditions hold before anything is deleted:** the declaration and the usage site both sit inside the scope this run resolved, so the rule never reaches a file the scope rule did not resolve to; the declaration's body has been opened this run, since a copy cannot be judged redundant against a declaration nobody read; and the copy says no more than the declaration's comment says. With all three met, keep the copy on the declaration, writing it there if it is absent, and delete the one above the use; this is the one case where an accurate comment is removed rather than corrected. Where the copy above the use carries a constraint the declaration does not, fold that into the declaration and then delete the copy, so the fact lands on the declaration either way. Failing any one of the three, leave both in place and report it: a repetition left alone costs a reader one duplicated sentence, where a wrong deletion destroys the only place a constraint was written down. _Bad:_ `isBetaEnabled mirrors the beta-features flag` above every read of `isBetaEnabled`. _Good:_ that sentence once, on the declaration of `isBetaEnabled`, and nothing at the read sites. -- **Comments describe the code as it stands.** Never narrate a change, a fix, or a prior state ("now uses", "previously", "no longer", "restored", "replaces", "used to", "formerly", "for the first time", "unlike the old"), and never name a file, flag, or tool that no longer exists: version control carries that, and the comment outlives the change that prompted it. Never argue that the code is correct or safe, which documents the edit rather than the code. Delete commented-out code rather than leaving it in place. **Test every comment by naming the line it describes**, since a phrase list only catches the comments that announce themselves. Point at the code beneath the comment that the comment is about; where nothing corresponds, it is not a comment about this code. That is what catches a comment explaining an absence, meaning why something was removed, why an approach was rejected, or what an earlier version did: nothing in the file matches it because its subject is a decision, and the reader who wants that decision is reading the commit or the change request that carries the diff proving it. Delete it. Two comments pass this test and stay: a note about a deliberate omission the code depends on, such as why a field must stay out of a payload, and a file-level header, which describes the file rather than any one line. +- **A fact is documented once, at the declaration of the thing it is about.** A statement about a symbol belongs on that symbol's own declaration, never above the lines that read it, call it, or branch on it. Where the same sentence would sit above more than one _use_ of a symbol, it belongs on the declaration alone, or in `docs/` where it spans more than one symbol. A declaration is not a use: each member of a public structure still gets its own comment, and a file-level header still summarizes what the file declares. **Removing a copy is bounded, and all three preconditions hold before anything is deleted:** the declaration and the usage site both sit inside the scope this run resolved, so the rule never reaches a file the scope rule did not resolve to; the declaration's body has been opened this run, since a copy cannot be judged redundant against a declaration nobody read; and the copy says no more than the declaration's comment says. With all three met, keep the copy on the declaration, writing it there if it is absent, and delete the one above the use; this is the one case where an accurate comment is removed rather than corrected. Where the copy above the use carries a constraint the declaration does not, fold that into the declaration and then delete the copy, so the fact lands on the declaration either way. Failing any one of the three, leave both in place and report it: a repetition left alone costs a reader one duplicated sentence, where a wrong deletion destroys the only place a constraint was written down. +- **Comments describe the code as it stands (Rule 4), and the test is to name the line.** Delete commented-out code rather than leaving it in place. A phrase list only catches the comments that announce themselves, so point at the code beneath the comment that the comment is about; where nothing corresponds, it is not a comment about this code. That is what catches a comment explaining an absence, meaning why something was removed, why an approach was rejected, or what an earlier version did: nothing in the file matches it because its subject is a decision, and the reader who wants that decision is reading the commit or the change request that carries the diff proving it. Delete it. Two comments pass this test and stay: a note about a deliberate omission the code depends on, such as why a field must stay out of a payload, and a file-level header, which describes the file rather than any one line. - **Form:** a documentation comment is a complete sentence, capitalized and punctuated; a short trailing comment may be a fragment. Wrap long comment lines to the width the file already uses, letting an unbreakable URL exceed it. Use the documentation format's own list syntax for enumerations, since indented plain text collapses into one run-on sentence when rendered. Never box a comment in asterisks or other decorative characters. Documentation precedes an annotation or decorator and never sits between it and the declaration. - **Contracts worth stating:** any cleanup the caller owns (a handle to close, a listener to remove, a subscription to cancel), the error values or exception types a caller can branch on, and a deprecation marker naming its replacement. A deprecation without migration directions is incomplete; add one only where it is provable under Rule 2. - **File-level headers:** where the language provides one, it states the file's contents, uses, or dependencies. Notes aimed at maintainers rather than consumers go with the implementation instead. @@ -113,9 +117,9 @@ Every statement must be grounded in code you have **opened and read in full duri **The "prove it" test:** before writing any statement, name the file, the symbol, and a short string from the source that shows the behaviour, copied as it reads there except for any credential value in it, such as a token, a password, an API key, a private key, or a session identifier, which is replaced by `[REDACTED]` as you record it. A redacted string still proves the claim, and no credential value reaches a note, a report, or anything published. If you cannot produce such a string at all, do not write the statement. **A line number is not proof.** It cannot be checked without opening the file, it drifts on the next edit, and it can be produced without reading anything; copying a string requires retrieval. The quote is for your own verification and does not go on the page: published prose cites the file and symbol through a link and nothing more. -- ❌ "The system validates user input." (assumption) -- ❌ "After reading [`validation.ts`](../src/validation.ts) lines 45-67, user input is validated against the schema." (a line range is not evidence) -- ✅ Proof held: symbol `parseConfig` in [`config.ts`](../src/config.ts), quote `throw new RangeError('retries must be >= 0')`. Written: "[`parseConfig`](../src/config.ts) rejects a negative `retries` value with a `RangeError`." +✅ Proof held: symbol `parseConfig` in [`config.ts`](../src/config.ts), quote `throw new RangeError('retries must be >= 0')`. Written: "[`parseConfig`](../src/config.ts) rejects a negative `retries` value with a `RangeError`." + +**A claim spanning several files is grounded the same way, from each of them.** The orientation sentence a document opens with rests on a handful of files rather than one line, so hold a quote from every file carrying a part of it and write the sentence once each part is covered. Being unfalsifiable against any single symbol is not what disqualifies such a sentence; an uncovered part is. Cover the system rather than only the sentence: before writing one, look for the file that would qualify or contradict it, since a synthesis is refuted by what it leaves out rather than by what it states. The reverse stays banned: a summary written because no quote could be found is a guess with a citation attached. **If you cannot verify, keep it off the page and report it.** Do not guess, do not leave a TODO, and never write "appears to", "seems to", "likely", "probably", "should", or "will". Silence in the documentation beats speculation in it, and naming the gap in your output beats both. Never document planned or intended behaviour. For complex behaviour, confirm against two or three locations (definition, usage, test). @@ -123,12 +127,12 @@ Every statement must be grounded in code you have **opened and read in full duri - **Correct falsehoods.** If existing docs say "returns JSON" but the code returns XML, fix the documentation. - **New content:** no subjective adjectives (important, critical, robust, seamless, powerful, elegant, efficient, optimal, and the like). State facts. _Bad:_ "The `auth.ts` middleware is a critical component." _Good:_ "The `auth.ts` middleware blocks unauthorized requests." -- **Objective is not flat.** Banning subjective adjectives does not mandate robotic prose. Replace the adjective with the concrete cited fact that earns it: not "the retry logic is robust" but "the retry runs three times with a two-second backoff ([retry.ts](../src/retry.ts) lines 12-19)." (show, do not tell) +- **Objective is not flat.** Banning subjective adjectives does not mandate robotic prose. Replace the adjective with the concrete cited fact that earns it: not "the retry logic is robust" but "the retry runs three times with a two-second backoff ([retry.ts](../src/retry.ts))." (show, do not tell) - **Existing content:** preserve existing subjective terms unless they are factually wrong. ### Rule 4: Current state only -Documentation and comments describe the code as it is now. Never narrate the past ("replaces", "used to", "formerly", "for the first time", "unlike the old") and never name a file, flag, symbol, or tool that no longer exists: version control already carries that history, and a reader cannot check a claim against something that is gone. The only sanctioned place for future intent is a `TODO` in the code that will change, positioned however that codebase positions one; documentation itself carries none, so no empty sections, stubs, or "add details here" placeholders, and if the code does not exist, neither should its documentation. Rationale worth keeping goes in its own decision record, not scattered through the files it explains. +Documentation and comments describe the code as it is now. Never narrate a change, a fix, or a prior state ("now uses", "previously", "no longer", "restored", "replaces", "used to", "formerly", "for the first time", "unlike the old"), never argue that the code is correct or safe, which documents the edit rather than the code, and never name a file, flag, symbol, or tool that no longer exists: version control already carries that history, a comment outlives the change that prompted it, and a reader cannot check a claim against something that is gone. The only sanctioned place for future intent is a `TODO` in the code that will change, positioned however that codebase positions one; documentation itself carries none, so no empty sections, stubs, or "add details here" placeholders, and if the code does not exist, neither should its documentation. Rationale worth keeping goes in its own decision record, not scattered through the files it explains. ### Rule 5: Mermaid diagram and image accessibility (zero tolerance) @@ -152,7 +156,7 @@ Write as a careful human technical writer: formal and neutral, never robotic. Th - **Lead with the point**, putting the conclusion, answer, or action in the first sentence. **Show, do not tell:** demonstrate with a command, number, cited line, or named edge case instead of asserting significance. Vary sentence length where natural, without forcing a cadence target. - **Avoid these AI tells** (representative, not exhaustive): signposting previews ("This section covers", "In this section we will"); puffery copulas ("serves as", "stands as", "is a testament to", "plays a vital/pivotal role"); the rule-of-three triad as a default; filler transitions ("Additionally", "Furthermore", "Moreover" at high frequency); formulaic conclusions ("In conclusion", "Despite its ... it faces challenges"); and padded words such as delve, leverage, underscore, showcase, intricate, vibrant, foster, tapestry, seamless. Keep a word when it is factually correct in context (a test `harness`, an OAuth `realm`). - **A why-claim is still a claim (Rule 2).** Cite the comment, design record, commit, test, or config that proves a rationale or trade-off, or state the _what_ and stop. -- **Scope.** Apply this only to prose you add or change; do not rewrite accurate existing prose for rhythm (Phase 2, Rule 1). It governs `docs/` prose, not in-code documentation, which Phase 3 keeps terse. +- **Scope.** Apply this only to prose you add or change; do not rewrite accurate existing prose for rhythm (Phase 2, Rule 1). Introducing a term, naming a subject, or stating a prerequisite is an addition rather than a rewrite, so it is made even where the surrounding prose is accurate. It governs `docs/` prose, not in-code documentation, which Phase 3 keeps terse. - **Stay formal.** No contractions, casual asides, emoji, or detector-evasion tricks. Naturalness comes from cutting tells, not from informality. ### Brevity & style @@ -164,7 +168,7 @@ Write as a careful human technical writer: formal and neutral, never robotic. Th - **No em-dashes or en-dashes.** Never write `—` (em-dash) or `–` (en-dash). Replace each with the grammatically appropriate punctuation: a comma, parenthesis, colon, separate sentence, or a spaced hyphen `-`. The plain hyphen `-` is fine wherever it is grammatically correct, including the `-` separator between a label and a brief description in lists (e.g. `**Label** ([file](path)) - what it does`). When an audit edits a document, replace that document's existing em-dashes and en-dashes the same way; do not sweep files you are not editing. - **Canadian English (strong preference).** Spelling you write or change uses Canadian forms: colour, behaviour, favour, licence (noun), centre, defence, and `-ize`/`-ization` (standardize, organization, recognize). See the [Canadian spelling guide](https://our-languages.canada.ca/en/blogue-blog/canadian-spelling-eng). Do not retroactively convert existing American prose; apply this only to text you add or change. **Never** alter code identifiers, config or JSON keys, quoted code, file or package names, CSS properties, or API names (`user_id`, `maxRetries`, and the like stay exactly as written). -- **Acronyms.** In prose you write or edit, write acronyms in capitals (ID, URL, API) and, on first use per document, give the full term first, e.g. "Deoxyribonucleic acid (DNA)", then the bare acronym after. Keep exact casing in three cases: an established brand, tool, or package name (npm, iOS, ESLint), an intentional domain term (snRNA, mRNA), and a direct code reference (a method, field, env var, or config key, such as an `id` property, stays as written in the code). +- **Acronyms and terms of art.** In prose you write or edit, write acronyms in capitals (ID, URL, API) and, on first use per document, give the full term first, e.g. "Deoxyribonucleic acid (DNA)", then the bare acronym after. Keep exact casing in three cases: an established brand, tool, or package name (npm, iOS, ESLint), an intentional domain term (snRNA, mRNA), and a direct code reference (a method, field, env var, or config key, such as an `id` property, stays as written in the code). **Expanding an acronym is not introducing it**, since an expansion is often as opaque as the abbreviation. A term the reader could not define from general knowledge is introduced where the document first uses it, in a short parenthesis or by a link to the document defining it, and used unchanged after: once per document rather than once per set, because a reader arrives by search and lands in the middle of it. **Test it against what you knew before this run:** a term whose meaning you settled by reading this project's code, rather than by knowing the word, needs introducing. Judging by whether it feels obvious fails, because you have just read the implementation. Spell a concept one way across the whole scope, since a concept spelled three ways is three concepts to anyone meeting it, and defeats their search. ### Configuration references @@ -186,6 +190,7 @@ Write as a careful human technical writer: formal and neutral, never robotic. Th ### Code snippets - Do not inline full definitions or class bodies; link to the file. Exceptions, 3-10 lines maximum: a specific usage example or how-to, a single critical configuration line, or logic that text alone cannot convey. +- **An example a reader copies and adapts is a usage example and belongs inside that allowance.** Write it in the language and file format the reader will actually edit, and label the fence with that language: a block labelled as one format and written in another does not run, and the reader who pastes it learns that after the error rather than before. ### Formatting @@ -205,7 +210,7 @@ Include a step only if it meets all three criteria: 2. **State or data transformation:** it changes data, state, or the execution path. 3. **Cannot be removed:** removing it would break functionality or change a user-observable outcome. -Exclude logging, metrics, telemetry, trivial validation, internal utilities, and debug code, unless the system you are documenting _is_ observability. Test: "would removing this step change what the user experiences?" If no, exclude it. +Exclude logging, metrics, telemetry, trivial validation, internal utilities, and debug code, unless the system you are documenting _is_ observability. --- @@ -229,7 +234,9 @@ Then confirm: - No hedging ("appears to", "seems to", "likely", "probably", "should", "will"), no new subjective adjectives, and no code dumps. - Every file reference is a clickable link resolving to a file, not a directory. Configuration references name the value a consumer changes it by. - Acronyms you wrote are capitalized and expanded on first use (exceptions: brand/tool/package names, domain terms, code references). -- New or changed prose reads as a careful human wrote it: leads with the point, no signposting or banned AI tells, one canonical term per concept, no ambiguous `it`/`this`/`these`. +- New or changed prose reads as a careful human wrote it: leads with the point, no signposting or banned AI tells, one canonical term per concept spelled identically in every document in scope, no ambiguous `it`/`this`/`these`. +- Every document you wrote or reworked opens by naming its subject and why a reader would reach for it, states what that reader must already have or have read, and introduces every acronym, term of art, and named component the first time it uses one. +- **Read each document once as the newcomer**, who has not seen this codebase, fix a missing introduction, subject, or prerequisite where you find one, name in your output anything left, or state that nothing stops them: a page only its author can follow is not finished. Then read it as the experienced reader, for whom a paragraph they could have got faster from the source has not earned its place either; there the fix is what the paragraph fails to add, not deletion by default. - Architecture flows include only significant steps (§4); every diagram has `accTitle` and `accDescr`, and every image has real alt text. - No em-dashes (`—`) or en-dashes (`–`) anywhere you wrote; new or changed prose uses Canadian English. - Every public symbol you touched carries a documentation comment written from its implementation, not from its name, and no comment narrates a change, names something that no longer exists, argues the code is safe, or sits commented out. No comment you added sits above a usage site rather than a declaration, and every comment you removed as a repetition either said no more than the declaration's or had what it added folded into the declaration first. diff --git a/.claude/skills/audit-docs/agents/claim-verifier.md b/.claude/skills/audit-docs/agents/claim-verifier.md index 0faa478..51c3f74 100644 --- a/.claude/skills/audit-docs/agents/claim-verifier.md +++ b/.claude/skills/audit-docs/agents/claim-verifier.md @@ -42,6 +42,8 @@ Where the proving string holds a credential value, such as a token, a password, - **not reached**: the file is absent, the symbol is not in it, the body sits behind a boundary you cannot open (generated, compiled, vendored, remote), or you did not open it. - **not established**: you read the whole body and no string either proves or contradicts the claim, the outcome is fixed by a value supplied elsewhere, or the claim as worded cannot be falsified. +**A claim naming more than one file is verified against each of them, not returned unproven for spanning them.** An orientation sentence rests on several files, so read the symbol in every file the caller named and settle each part separately: CONFIRMED where every part carries its own quote, REFUTED where any part is contradicted, UNPROVEN naming the part left uncovered. Being unfalsifiable against one symbol is not the disqualifier here; an uncovered part is. + Uncertainty resolves to UNPROVEN, never to CONFIRMED. UNPROVEN costs the caller one claim; a CONFIRMED without proof puts an unchecked statement into published documentation. **Refuting a claim about something absent.** A claim that the code does something its body never does has no contradicting string to quote. Quote the code occupying the position where the claimed behaviour would have to sit: the catch block whose only statement is a return, the branch that falls through, the handler that returns before the claimed call. That quote carries REFUTED. When nothing occupies that position at all, the verdict is UNPROVEN (not established). diff --git a/.claude/skills/audit-docs/agents/coverage-mapper.md b/.claude/skills/audit-docs/agents/coverage-mapper.md index 167a367..1006958 100644 --- a/.claude/skills/audit-docs/agents/coverage-mapper.md +++ b/.claude/skills/audit-docs/agents/coverage-mapper.md @@ -17,12 +17,13 @@ The agent does not receive the purpose of the audit, the report being drafted, o ## Walk each document once -For every document in scope, open it and record four things. +For every document in scope, open it and record five things. 1. **Path**, exactly as it sits on disk. 2. **Subject claimed**, taken from the H1 and the opening paragraph, in one clause, as the document states it rather than as the agent would restate it. 3. **Code the subject maps to**: the file, module, or symbol the subject names, located in the code area and opened far enough to confirm the subject exists there. A search hit is not a mapping. -4. **Status**, exactly one, from the closed list below. +4. **Type**, exactly one of tutorial, how-to guide, reference, or explanation, decided by what the document's reader needs rather than by its subject: content informing action serves the acquisition of skill as a tutorial and its application as a how-to guide, and content informing cognition serves acquisition as an explanation and application as a reference. A document that is plainly navigation and nothing else is recorded as `navigation`. +5. **Status**, exactly one, from the closed list below. A document the walk did not open is never given a status. It goes in the count of documents in scope not opened, listed by path, so that a short map cannot be read as a clean one. @@ -63,12 +64,20 @@ Compiled during the walk and returned alongside the per-document map. ## Worked map -| Document | Subject claimed | Code | Status | -| -------------------------------- | ---------------------------------- | -------------------- | -------------------------------------------------------------------------- | -| `docs/guide/queue-consumers.md` | how a worker drains the job queue | `worker/consumer.go` | covered | -| `docs/reference/cache-warmer.md` | a background cache warmer | none | orphaned | -| `docs/guide/installation.md` | installing and running the service | `scripts/install.sh` | duplicated (`README.md`, the same eight steps) | -| `docs/reference/http-errors.md` | the error codes the API returns | `lib/http/errors.rb` | contradicted (`docs/guide/clients.md` gives 404 where the code raises 410) | +| Document | Subject claimed | Code | Type | Status | +| -------------------------------- | ---------------------------------- | -------------------- | ------------ | -------------------------------------------------------------------------- | +| `docs/guide/queue-consumers.md` | how a worker drains the job queue | `worker/consumer.go` | how-to guide | covered | +| `docs/reference/cache-warmer.md` | a background cache warmer | none | reference | orphaned | +| `docs/guide/installation.md` | installing and running the service | `scripts/install.sh` | how-to guide | duplicated (`README.md`, the same eight steps) | +| `docs/reference/http-errors.md` | the error codes the API returns | `lib/http/errors.rb` | reference | contradicted (`docs/guide/clients.md` gives 404 where the code raises 410) | + +No row carries `tutorial`, and that is the finding the type column exists to surface: this set documents every subject it has and still gives a first-time reader nowhere to start. + +## The entry path + +A set can be complete, accurate, and have no way in. Report the entry path as `present`, naming the document, only where one document takes a first-time reader through one task end to end and says what a finished run looks like. A link hub listing the other documents is `absent`, and so is an installation checklist that stops at the last command without saying what the reader should then see. + +This is a report and never a licence to write the missing document. The caller decides that, under a rule this agent does not carry. ## Output format returned @@ -76,8 +85,8 @@ Compiled during the walk and returned alongside the per-document map. SCOPE: DOCUMENTS -| path | subject claimed | code | status | detail | -| ---- | --------------- | ---- | ------ | ------ | +| path | subject claimed | code | type | status | detail | +| ---- | --------------- | ---- | ---- | ------ | ------ | | | UNDOCUMENTED @@ -89,9 +98,13 @@ UNRESOLVED REFERENCES HISTORICAL NARRATION - :: :: +ENTRY PATH + :: + COUNTS documents opened: documents in scope not opened: , listed by path +types: tutorial, how-to guide, reference, explanation, navigation ``` ## Closing rule diff --git a/.claude/skills/audit-docs/agents/curation-reviewer.md b/.claude/skills/audit-docs/agents/curation-reviewer.md index fe890a3..75f0b63 100644 --- a/.claude/skills/audit-docs/agents/curation-reviewer.md +++ b/.claude/skills/audit-docs/agents/curation-reviewer.md @@ -13,9 +13,9 @@ One document, and the code that document describes. Nothing else. The agent does ## The two readers, and how each one fails -`SKILL.md` requires every document to serve internal developers maintaining the architecture and external developers consuming the interface. Those two readers do not fail in the same way, so one read tests for both. +`SKILL.md` requires every document to serve two readers: a newcomer meeting the system for the first time, and an experienced reader who already works in it. They do not fail in the same way, so one read tests for both. -### The deep technical reader +### The experienced reader This reader fails when the document restates what the code already says and adds nothing that cannot be read faster from the source: no constraint, no invariant, no reason, no boundary. @@ -45,7 +45,7 @@ Four terms carry the meaning of the first version and none of them is introduced ## The test applied to each reader -**Deep technical reader**: could this reader get this faster from the source? Where the answer is yes, the paragraph is not earning its place, and the finding names what it would have to add to earn it. +**Experienced reader**: could this reader get this faster from the source? Where the answer is yes, the paragraph is not earning its place, and the finding names what it would have to add to earn it. Answer it with the cited symbol open. Calling a paragraph a restatement without reading what it restates is an inference from a name, which is the move the grounding rule exists to stop, and where the source could not be opened the paragraph goes on the borderline list rather than among the findings. @@ -63,7 +63,7 @@ A paragraph is not earning its place when it is one of these five. Nothing outsi - An enumeration padded to look complete, where an entry was added for symmetry rather than because the code carries it. - Historical narration, which the current-state rule already governs and which a reader cannot check against something that is gone. -The first entry and the deep technical reader's test are one defect seen from two angles, so a restating paragraph is named once, on the list, and the maintainer verdict states the pattern rather than repeating the paragraph. The two questions come apart on the other reader: a newcomer blocker is context the document never supplies, so that verdict can read FAILED while every paragraph present is earning its place. +The first entry and the experienced reader's test are one defect seen from two angles, so a restating paragraph is named once, on the list, and that reader's verdict states the pattern rather than repeating the paragraph. The two questions come apart on the other reader: a newcomer blocker is context the document never supplies, so that verdict can read FAILED while every paragraph present is earning its place. **The counterweight, and it is half of this question.** Connective prose that carries the logic is not bloat. Cutting it produces a choppy document that costs the reader more than the words saved, because the reasoning it held moves back into the reader's head. The target is concise, not terse, and a paragraph carrying a why or a how stays even where it runs longer than the paragraphs around it. @@ -75,8 +75,8 @@ A decision record exists to preserve past intent, so a superseded option, an aba ```text DOCUMENT: -MAINTAINER (deep technical): SERVED | FAILED - -CONSUMER (newcomer): SERVED | FAILED - +EXPERIENCED READER: SERVED | FAILED - +NEWCOMER: SERVED | FAILED - NOT EARNING ITS PLACE: - - - BORDERLINE, LEFT ALONE: diff --git a/.claude/skills/audit-docs/assets/audit-report.template.md b/.claude/skills/audit-docs/assets/audit-report.template.md index 7ee9a1b..ea1c54b 100644 --- a/.claude/skills/audit-docs/assets/audit-report.template.md +++ b/.claude/skills/audit-docs/assets/audit-report.template.md @@ -45,8 +45,17 @@ Sources that could not be resolved this run: [name each one and what you used in - Corrected `[document]`: [the statement that contradicted the code] replaced with [the statement the code supports], from `[symbol]` in `[file]`. - Deleted [section] from `[document]`: [describes a removed feature / duplicated in `[document]` / cannot be corrected]. - Created `[new document]`: [why no existing document was a home for it], filed as [tutorial / how-to guide / reference / explanation]. +- Oriented `[document]`: [the acronym, term of art, prerequisite, or missing statement of subject that stopped a first-time reader] introduced at [where]. -(Deletion needs one of the three listed reasons. Anything else is a correction. A created file needs the structure check stated first: which existing homes were considered and why each did not fit.) +**Both readers, one line per document opened:** + +| Document | Newcomer | Experienced reader | +| -------- | -------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------- | +| `[path]` | [served, or the first place a reader who has not seen this codebase stops] | [served, or the paragraph they could get faster from the source, and what it would have to add] | + +**Entry path:** [present, naming the document that takes a first-time reader through one task end to end / absent, and what a first-time reader has to read instead]. + +(Deletion needs one of the three listed reasons. Anything else is a correction. A created file needs the structure check stated first: which existing homes were considered and why each did not fit. The entry path is reported whether or not it was written, and it is written only where the invoking task asked for it.) ## Phase 3 result: in-code documentation audit @@ -67,7 +76,7 @@ Public symbols left as they stand because their implementation was not read: [`C | `[path]` | [documented [count] previously undocumented public symbols] | | `[path]` | [created, [tutorial / how-to guide / reference / explanation]] | -Kinds to choose from: corrected a factual statement, documented a public symbol, corrected an existing documentation tag, removed an outdated or restating comment, removed a comment repeated above a usage site, removed a duplicated section, created, deleted. +Kinds to choose from: corrected a factual statement, documented a public symbol, corrected an existing documentation tag, removed an outdated or restating comment, removed a comment repeated above a usage site, removed a duplicated section, introduced a term on first use, added orientation for a first-time reader, created, deleted. (One row per file, not one per edit. If no file changed, replace the table with "No files changed.") diff --git a/.claude/skills/audit-docs/references/evidence-and-citation.md b/.claude/skills/audit-docs/references/evidence-and-citation.md index 50828d6..9bbf352 100644 --- a/.claude/skills/audit-docs/references/evidence-and-citation.md +++ b/.claude/skills/audit-docs/references/evidence-and-citation.md @@ -27,7 +27,7 @@ Keep the quote to one line or the fragment carrying the operative token. A ten-l ## Why the quote stays out of the published sentence -The quote is a private verification token, not published text. The page cites the file and symbol through a single markdown link and states the behaviour in your own words: no pasted source, no line range, no commit hash, no "as of" qualifier. A reader follows the link to the file as it stands today, whereas pasted source and line ranges go stale on the next edit, silently and without a signal to anyone. +The quote is a private verification token, not published text. The page cites the file and symbol through a single markdown link and states the behaviour in your own words: no pasted source, no line range, no commit hash, no "as of" qualifier. **This governs source pasted as evidence for a claim, and reaches nothing else.** A short usage example written for a reader to copy and adapt is a different artifact serving a different purpose, and the snippet allowance governs it. A reader follows the link to the file as it stands today, whereas pasted source and line ranges go stale on the next edit, silently and without a signal to anyone. One value is withheld even from the note. Where the proving string holds a credential value, such as a token, a password, an API key, a private key, or a session identifier, record the string with that value replaced by `[REDACTED]`, leaving the surrounding assignment or call intact. The recheck at output time then runs on the text around the placeholder, which is what the note exists to make possible. Never reconstruct the value a placeholder stands for. A credential value belongs in neither the note nor the page. diff --git a/.claude/skills/audit-docs/references/voice-and-ai-tells.md b/.claude/skills/audit-docs/references/voice-and-ai-tells.md index 45030cc..31bd575 100644 --- a/.claude/skills/audit-docs/references/voice-and-ai-tells.md +++ b/.claude/skills/audit-docs/references/voice-and-ai-tells.md @@ -24,7 +24,9 @@ Apply this catalogue to sentences you write, and to sentences you rewrite for a Delete the announcement and open with the content. The grammatical subject gives the tell away: it is the document (this section, this guide, the table below, we) rather than the system being described. A heading already performs the announcement, and the same construction reappears mid-document as "as mentioned above", which orients the reader inside the page instead of inside the software. - Before: "This section covers the retry configuration and explains how it applies to outbound requests." -- After: "Outbound requests retry three times, with the count read from `MAX_ATTEMPTS` once at startup." +- After: "An outbound request that fails is retried rather than surfaced to the caller. It retries three times, with the count read from `MAX_ATTEMPTS` once at startup." + +The repair deletes the announcement, not the orientation. The subject moves from the document to the system, and the first clause still says what the thing is, because a reader who does not already know what a retry configuration is cannot enter on the mechanism sentence alone. ## Puffery copulas that assert significance @@ -109,7 +111,7 @@ def set_timeout(seconds: int) -> None: func (w *Writer) Close() error ``` -Procedure: cover the prose and read only the declaration. Anything you can still answer needs no sentence. Anything you cannot answer, and can prove from the body you read this run, is the sentence to write. +Procedure: cover the prose and read only the declaration. Anything you can still answer needs no sentence. Anything you cannot answer, and can prove from the body you read this run, is the sentence to write. **Apply it with the same two exceptions the rule above carries**, since a procedure stated without them is stricter than the rule it implements: a reader who cannot open the source, and a reader who has not yet been told what the subject is, are both owed the plain statement of what the thing does. ## Show, do not tell: the cited fact that earns the adjective @@ -138,7 +140,7 @@ Run these over a paragraph **you wrote or rewrote this run**, before moving on. 3. Is there an adjective you cannot replace with a number, a limit, an error type, or a case you read this run? 4. Are there two or more sentence-initial connectives, or two in consecutive sentences? 5. Does a group of three come from the rhythm rather than from a count in the source? -6. Would a reader holding only the declaration learn nothing new here? +6. Would a reader holding only the declaration learn nothing new here? A yes is an edit only where that reader can open the declaration and already knows what the subject is; an opening sentence and consumer-facing reference material both answer yes by design. 7. Does a hedge survive anywhere, including a `should` that reads as a caller obligation? -8. Is this paragraph a summary of the paragraphs above it? +8. Is this paragraph a summary of the paragraphs above it? An opening that orients a first-time reader is not one, since it summarizes nothing above it. 9. Did you change a sentence that was already accurate? Restore it. diff --git a/.claude/skills/audit-docs/references/writing-for-both-readers.md b/.claude/skills/audit-docs/references/writing-for-both-readers.md new file mode 100644 index 0000000..c5f45ea --- /dev/null +++ b/.claude/skills/audit-docs/references/writing-for-both-readers.md @@ -0,0 +1,148 @@ +# Writing for both readers + +Every page has two readers: someone meeting the system for the first time, and someone who already works in it. A page that serves only the second is the ordinary failure, and it is invisible to its author, because the author cannot un-know the system. This file carries the repairs, each as a before and after pair. + +## Contents + +- The ordering principle, which is how one page serves two readers +- The opening, and what it carries +- Introducing a term of art +- One concept, one spelling +- The worked example +- Choosing the type, and what the type assumes +- Serving the experienced reader on the same page +- The read-it-cold procedure +- What none of this licenses + +## The ordering principle, which is how one page serves two readers + +Do not split the page, and do not write two of them. Order it: orientation first, depth after, and both in full. + +The newcomer reads the top and stops when they have what they came for. The experienced reader skims the top in three seconds and reads the rest. Neither is served by a page that averages the two, which is the shape that produces prose too vague for the expert and too dense for the newcomer at the same time. + +Orientation is a small fraction of the page. One to three sentences at the top of a document, and one sentence at the top of a section that introduces something the document has not named yet. It is not a tutorial bolted to the front of a reference. + +## The opening, and what it carries + +An opening carries three things, and a fourth where it applies: what the subject is, why a reader would reach for it, what that reader must already have or have read, and, where this page is one a reader can arrive at by mistake, where to go instead. A page that opens on mechanism has skipped all of them. + +Before, from a caching layer's page: + +> The resolver checks `NEGATIVE_TTL` before evicting, so a miss recorded during a partition survives the partition by up to sixty seconds. + +After: + +> Lookups are cached in front of the directory service so that a repeated name resolves without a network call. Read [the directory service page](../services/directory.md) first if the terms below are unfamiliar. Negative results are cached too, which is the part that surprises callers: the resolver checks `NEGATIVE_TTL` before evicting, so a miss recorded during a partition survives that partition by up to sixty seconds. + +The mechanism sentence is unchanged and still leads its own clause. What was added is the three sentences that make it followable, and the experienced reader loses nothing because the specific claim is still there. + +**The opening is where a synthesis claim usually lives**, so it is the place the grounding rule bites hardest. Hold a quote from each file carrying a part of the sentence. "Lookups are cached in front of the directory service" is provable from the resolver and the client that calls it, read together; it is not provable from either alone, and that is not a reason to leave it out. + +## Introducing a term of art + +Expanding an acronym is not introducing it. The expansion of a technical term is often as opaque as the abbreviation, and a reader who could not define the short form cannot define the long one either. + +Before, from a synchronization guide: + +> Writes go through CRDT (conflict-free replicated data type) merge before the LWW register is applied. + +After: + +> Writes are merged rather than overwritten, using a conflict-free replicated data type (CRDT), a structure whose merge produces the same result regardless of the order replicas apply it in. A last-writer-wins register then settles the fields the merge leaves ambiguous, using the writer's timestamp. + +Two rules operate here. Introduce the term where the document first uses it, in a short parenthesis or by a link to the document that defines it, then use it unchanged. And introduce it once per document rather than once per set, because a reader arriving by search lands in the middle of the set and reads one page. + +Where a term is used across many documents, the link is better than the parenthesis, and the target is the page whose subject that term is. + +## One concept, one spelling + +A concept spelled three ways is three concepts to anyone meeting it, and it defeats their search. This is the cheapest defect to create, because each spelling looks correct in the sentence containing it. + +Before, across four documents in one set: + +> the retry envelope ... the retry wrapper ... `RetryEnvelope` ... the backoff wrapper + +After: one of them, chosen once, used everywhere, with the code identifier written as the code spells it and the prose term matching it. + +Check this across the scope rather than within a document. A single document is usually self-consistent; the divergence appears between documents written months apart. + +## The worked example + +An example a reader copies and adapts is the most legible artifact a page can carry, and it survives the ban on pasted source, which governs source quoted as evidence for a claim rather than an example written to be run. + +Two rules make an example work. Write it in the language and file format the reader will actually edit, and label the fence with that language. A block labelled as one format and written in another does not run, and the reader who pastes it discovers that after the error rather than before. + +Before, in a page telling the reader to add an entry to a Rust source file: + +```json +{ + "name": "example", + "retries": 3 // optional +} +``` + +After: + +```rust +Job { + name: "example", + retries: Some(3), // omit for the default of 1 +} +``` + +The first block is labelled as one format, written in the shape of another, and carries a comment the labelled format forbids. Nothing about it survives a paste. + +## Choosing the type, and what the type assumes + +The type of a document decides how much it may assume, so choose it before writing and keep the choice visible in the page. Decide it by what the reader needs, not by the subject. + +| The content informs | And serves the reader's | So the document is a | Which may assume | +| ------------------- | ----------------------- | -------------------- | ------------------------------------------------------------------------- | +| Action | Acquisition of skill | Tutorial | Nothing about the system; only that the reader can follow instructions | +| Action | Application of skill | How-to guide | That the reader knows the goal and the vocabulary, but not this procedure | +| Cognition | Application of skill | Reference | That the reader is working and needs a fact confirmed, not taught | +| Cognition | Acquisition of skill | Explanation | That the reader is studying, so background and alternatives belong here | + +Two consequences worth stating. A reference may be terse because its reader arrives knowing what they are looking for, but its opening still says what the thing is, since that reader may have arrived from a search engine rather than from the page above. And an explanation is the type most often missing from a set: rationale ends up scattered in single sentences across pages whose subject is something else, where no reader looking for it will find it. + +**A set can be complete, accurate, and have no way in.** Every subject documented, every claim true, and nothing that takes a first-time reader through one task end to end. Report that gap by name when the scope reaches the whole set. + +## Serving the experienced reader on the same page + +The opposite failure is real and this file does not license it. A paragraph the experienced reader could have got faster from the source has not earned its place either. + +Before, from a queue client's page: + +> The `poll` method takes a `timeout` argument of type `Duration` and returns a list of messages. The `commit` method takes no arguments and returns a result. + +After: + +> `poll` blocks until at least one message arrives or the timeout elapses, and returns an empty list on timeout rather than an error. A delivered message stays unacknowledged until `commit` returns successfully, so a consumer that exits between the two calls receives that message again on its next `poll`. + +The first version restates a signature the reader can open in less time than the sentence takes to read. The second gives a boundary, an invariant, and the consequence a caller plans around. + +The repair for this failure is what the paragraph fails to add, not deletion by default. Delete only where nothing can be added, and where the source was opened this run to establish that. + +**One case resolves the other way, and a long reference page is where it appears.** Where the same fact is transcribed twice inside one document, the second copy adds nothing the first does not, so the repair is to remove the copy rather than to deepen both. Adding to each is how a page that already restates its source ends up longer for it. This is the duplication the deletion rule covers, judged within a document rather than across the set. + +## The read-it-cold procedure + +Run this before publishing, once per document. + +1. Read the document from the top, in order, allowing yourself nothing you learned from the code this run. +2. Stop at the first place a reader without that knowledge cannot continue: an acronym never introduced, a term used before it is defined, a prerequisite never stated, a system named without being identified, or an opening that never says what the subject is. +3. Name that specific thing in the output rather than calling the passage unclear. "Stops at `assertion consumer service`, used in the first sentence and never introduced" is a finding; "the opening is confusing" is not. +4. Read it again as someone who works in the system daily, and name any paragraph they could have got faster from the source, with what it would have to add to earn its place. +5. Report both results. A document passes only when both are clean. + +Step 1 is the hard one, and it is the whole exercise. An auditor who has just read the implementation knows what every term means, which is exactly the state the reader does not share. + +**Make it a test rather than an act of imagination.** For each candidate term, ask where you learned it. A term you could have defined before opening this project needs nothing. A term whose meaning you settled this run, by reading the code or by inferring it from surrounding names, is a term of art and needs introducing. This catches the compounds that intuition waves through, because a phrase built from familiar words can still carry semantics nobody can guess: knowing what a cache is does not tell a reader when a precache runs, and knowing all four words in a revalidate-behind-the-response strategy does not tell them which response the caller gets. + +## What none of this licenses + +- **Not softer claims.** The grounding rule is unchanged. An orientation sentence is held to it like any other, from each file carrying a part of it. +- **Not hedging.** "Roughly", "essentially", and "basically" introduce nothing. Introduce the term instead. +- **Not restating the code.** Orientation says what the subject is and why a reader would reach for it. It does not narrate the implementation, which is the failure this repair is most likely to be misread as permitting. +- **Not padding.** One to three sentences, then the depth. An opening that runs half a page has become the document. +- **Not rewriting accurate prose for rhythm.** Where a document already orients its reader, leave the wording alone. diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index 22ccd83..87e0bab 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -248,6 +248,8 @@ Architecture docs in `docs/architecture/`: When writing or editing any Markdown, the full rules are in [`audit-docs.prompt.md`](prompts/audit-docs.prompt.md), and the `audit-docs` skill carries the same rules with worked examples beside them. The always-apply subset: +- **Two readers**: every document serves a newcomer meeting the system for the first time and someone who already works in it. Orientation first (what the subject is, why a reader would reach for it, what they must already know), then the depth in full. A page only its author can follow is not finished +- **Introduce every acronym and term of art on first use** per document, in a parenthesis or a link to the document defining it, and spell a concept one way across `docs/`; expanding an acronym is not introducing it - **Zero hallucination**: document only what the code provably does. Know the file that proves a claim before writing it - **No em-dashes or en-dashes**: replace each with a comma, parenthesis, colon, separate sentence, or a spaced hyphen, including existing ones in any file you edit - **Canadian English** for prose you write or change (colour, behaviour, standardize), never for code identifiers, config keys, or package names diff --git a/.github/prompts/audit-docs.prompt.md b/.github/prompts/audit-docs.prompt.md index ddb5c9b..95ca0b5 100644 --- a/.github/prompts/audit-docs.prompt.md +++ b/.github/prompts/audit-docs.prompt.md @@ -9,15 +9,17 @@ agent: 'agent' Act as a **Strictly Factual Technical Writer and Auditor**. Make the project's documentation directory, `docs/` below and whatever this project actually names it, an objective, verifiable reflection of the codebase as it stands. Write and correct documentation so `docs/` matches the project's own files (#codebase), the active pull request (#activePullRequest), or the uncommitted working changes (#changes); resolve each of those three yourself, with your own file-search, pull request, and diff tools, if they are not handed to you. Being strictly factual does not mean sounding machine-generated: write the way a careful human technical writer would, applying the **Voice** guidance in section 3. -**Scope: documentation only.** Unless the invoking task explicitly asks for code or behaviour changes, this run edits documentation (markdown, text files, and in-code comments, docstrings, and file-level headers) and never changes executable code or behaviour. See Rule 1. +**Scope: documentation only.** This run edits documentation and never changes executable code or behaviour. Rule 1 carries the boundary and its one exception. **Core philosophy:** - **Reporter, not editor.** Convert code facts into documentation. Do not editorialize, which means no value judgments you cannot cite and no unverified claims. -- **Document value, not narration.** Code is self-documenting for _what_ it does; `docs/` prose must add what code cannot show: _why_ something exists (decisions, constraints, trade-offs), _how_ parts interact (boundaries, data flows, integration points), and _when_ to use it (context, prerequisites). If a sentence only restates the code, cut it. _Exception:_ consumer-facing API/tool docs must state _what_ the code does, since external readers cannot see the source. +- **Document value, not narration, and orient before going deep.** `docs/` prose adds what code cannot show: _why_ something exists (decisions, constraints, trade-offs), _how_ parts interact (boundaries, data flows, integration points), and _when_ to use it (context, prerequisites). Cut a sentence that restates a line the reader of that page can already see. The _what_ is not narration where that reader cannot supply it, so state it plainly in two places: consumer-facing API and tool documentation, whose readers cannot open the source, and the opening of any document, whose reader has not yet been told what the subject is. - **Link, do not duplicate.** Point to source files; never copy code into markdown. -**Audience and tone:** every document serves internal developers maintaining the architecture and external developers consuming the APIs, so prefer content useful to both. Serve human skimmers and coding-assistant readers with the same prose: one canonical term per concept, and an ambiguous `it`/`this`/`these` replaced by the actual noun when the referent could drift. Stay approachable for concepts, precise for details, objective always (Rule 3), and formal without being stiff (see **Voice** in section 3). No contractions. +**Two readers, one document.** Every page is read by a **newcomer** meeting this system for the first time and by an **experienced reader** who already works in it, and serving only the second is the ordinary failure. Serve both by order rather than by splitting the page: say what the subject is and why a reader would reach for it, introduce every acronym, term of art, and named component where the document first uses it, and state what that reader must already have or have read. Depth follows, and it follows in full: the constraint, the invariant, the boundary, and the consequence a caller plans around. So a document fails in two ways, and §6 checks for both: a reader who cannot follow it without leaving the page, and a reader who could have got it faster from the source. + +**Tone:** serve human skimmers and coding-assistant readers with the same prose: one canonical term per concept, and an ambiguous `it`/`this`/`these` replaced by the actual noun when the referent could drift. Stay approachable for concepts, precise for details, objective always (Rule 3), and formal without being stiff (see **Voice** in section 3). No contractions. --- @@ -34,10 +36,11 @@ Act as a **Strictly Factual Technical Writer and Auditor**. Make the project's d ### Phase 2: general audit - **Inventory before you correct.** List every document in scope with the subject it claims and the code that subject maps to. The three actions below are undecidable without that list: duplication is visible only across documents, a removed feature only where a document's subject is absent from the code, and a missing document only as code with no entry. Report how many documents you opened, and name anything in scope you did not, so that "already accurate" cannot be confused with "not looked at". -- Audit the documents the scope rule resolved to against the codebase as it stands (#codebase). That is all of `docs/` only where the rule resolved to the whole documentation set, and on a pull request it is the documents describing the changed code. **Correct** pre-existing content that contradicts the code, preserving accurate content's phrasing and style. +- **Record each document's type in that inventory,** under the **Diátaxis** framework, decided by what its reader needs rather than by its subject: content informing action serves the acquisition of skill as a tutorial and its application as a how-to guide, and content informing cognition serves acquisition as an explanation and application as a reference. A set can be complete and accurate and still have no way in. Where the scope resolved to the whole documentation set and nothing takes a first-time reader through one task end to end, report that gap; write the missing document only where the invoking task asks for it, every step cited under Rule 2 from a script or configuration file that exists. +- Audit the documents the scope rule resolved to against the codebase as it stands (#codebase). That is all of `docs/` only where the rule resolved to the whole documentation set, and on a pull request it is the documents describing the changed code. **Correct** pre-existing content that contradicts the code, preserving accurate content's phrasing and style. **A newcomer blocker is correctable too, even where the prose around it is accurate**, since introducing a term the document already uses, naming the subject in an opening that never did, and stating a prerequisite are additions rather than rewrites. Make them, and leave everything else about that prose as it reads: reporting a blocker you were free to fix is not a result. - **Delete** pre-existing content only if it is massively duplicated, describes removed features, or fundamentally cannot be corrected. Default to correcting, not deleting. Your own generated content may be edited or removed freely when wrong. -- **Create new files** only when needed, for a new component or system, an external interface guide, or a genuinely missing structure. **Decide the directory before writing a word, and decide it by document type rather than by subject.** Classify what you are about to write under the **Diátaxis** framework, as a tutorial, a how-to guide, a reference, or an explanation, then open the candidate directory's entry-point file and two or three of its siblings and place the document only where those siblings are the same type. A directory's name is a claim about what it holds, so a how-to guide filed among explanations is in the wrong place even where its subject belongs to that area, and a reader who trusted the directory now has to read it to find out. **Precedent settles it where precedent exists:** a sibling of the same type already in that directory makes the placement correct, and the new document joins it. The directory merely touching the same topic is not precedent. Where no directory holds that type, create one with an entry-point file named as the project's existing directories name theirs. State in your output which directory you chose and which sibling or precedent decided it. -- **Output:** state whether you made changes or found docs already accurate. +- **Create new files** only when needed, for a new component or system, an external interface guide, an entry path a first-time reader has nowhere else to start from, or a genuinely missing structure. **Decide the directory before writing a word, and decide it by document type rather than by subject.** Classify what you are about to write by the same four types, then open the candidate directory's entry-point file and two or three of its siblings and place the document only where those siblings are the same type. A directory's name is a claim about what it holds, so a how-to guide filed among explanations is in the wrong place even where its subject belongs to that area, and a reader who trusted the directory now has to read it to find out. **Precedent settles it where precedent exists:** a sibling of the same type already in that directory makes the placement correct, and the new document joins it. The directory merely touching the same topic is not precedent. Where no directory holds that type, create one with an entry-point file named as the project's existing directories name theirs. State in your output which directory you chose and which sibling or precedent decided it. +- **Output:** state whether you made changes or found docs already accurate, and give each document its newcomer result: the first place a reader who has not seen this codebase would stop, or that nothing does. ### Phase 3: in-code documentation audit @@ -45,12 +48,12 @@ Act as a **Strictly Factual Technical Writer and Auditor**. Make the project's d - **Scope:** the code the scope rule above resolved to, covering its documentation comments, inline comments, and file-level headers, plus every `.md` file inside that scope which sits outside `docs/`. A file the rule did not resolve to stays out whatever it contains, so this phase is never a repository-wide sweep for markdown, for undocumented symbols, or for a comment pattern. - **Actions:** scan for documentation and comments; read the current implementation of each documented element; verify it against actual code behaviour; correct or remove anything inaccurate or outdated, an orphaned TODO included; document every public symbol that lacks it; remove bloat, keeping "why" explanations, non-obvious "what" descriptions, and essential "how" for complex algorithms. Removing bloat means deleting comments that restate the code, never comments that explain a non-obvious internal. -- **Always document the public surface.** Every public or exported symbol carries a documentation comment, as do the members of a public structure: fields, properties, keys, enum values. Write for a reader meeting the symbol for the first time, assuming they can infer nothing from its name. Reach for what the declaration cannot express, such as why it exists, a constraint, an invariant, or a caller obligation. Where no such explanation exists, a plain restatement of what the symbol does is correct: being obvious is not a defect on a public surface, being absent is. **Rule 2 still governs, and it comes first.** Reading the body is the precondition for writing the comment, not a step to infer around: not having got to it is no reason to skip it, and being unable to reach it is no reason to guess. Where you have not read the body, leave the symbol as it is and name it in your output. A public symbol left undocumented and reported is a compliant result; a comment written from the symbol's name is a defect, and it is the defect this rule exists to prevent. +- **Always document the public surface.** Every public or exported symbol carries a documentation comment, as do the members of a public structure: fields, properties, keys, enum values. Write for a reader meeting the symbol for the first time, assuming they can infer nothing from its name. Reach for what the declaration cannot express, such as why it exists, a constraint, an invariant, or a caller obligation. Where no such explanation exists, a plain restatement of what the symbol does is correct: being obvious is not a defect on a public surface, being absent is. **Rule 2 still governs, and it comes first.** Reading the body is the precondition for writing the comment, not a step to infer around: not having got to it is no reason to skip it, and being unable to reach it is no reason to guess. Where you have not read the body, leave the symbol as it is and name it in your output: undocumented and reported is a compliant result, where a comment written from the symbol's name is the defect this rule exists to prevent. - **Do not restate what the language's own syntax declares**, such as a type, a visibility modifier, or an override marker. This governs what you write in a **new** documentation comment and never licenses removing an existing one. - **Correct an existing documentation tag; do not strip or delete it.** A parameter, return, throws, or example entry was written deliberately. Read enough surrounding code to judge it, then fix what is factually wrong and leave what is right, including parts a convention would omit in new code. Removing a tag, or a piece of one, because it looks redundant is restyling someone else's work, not auditing it. Delete a whole tag only when it is wrong and uncorrectable, such as one documenting a parameter the signature no longer has. Phase 2's "default to correcting, not deleting" governs in-code documentation too. - **Internal elements** are documented only where the logic is complex or carries a gotcha or edge case, and a comment inside a function body is written only for non-obvious business logic, a workaround, or a complex transformation. Delete an internal comment only when it restates the line beneath it, such as `// Increment counter` above a counter increment (delete the comment, keep the code). -- **A fact is documented once, at the declaration of the thing it is about.** A statement about a symbol belongs on that symbol's own declaration, never above the lines that read it, call it, or branch on it. Where the same sentence would sit above more than one _use_ of a symbol, it belongs on the declaration alone, or in `docs/` where it spans more than one symbol. A declaration is not a use: each member of a public structure still gets its own comment, and a file-level header still summarizes what the file declares. **Removing a copy is bounded, and all three preconditions hold before anything is deleted:** the declaration and the usage site both sit inside the scope this run resolved, so the rule never reaches a file the scope rule did not resolve to; the declaration's body has been opened this run, since a copy cannot be judged redundant against a declaration nobody read; and the copy says no more than the declaration's comment says. With all three met, keep the copy on the declaration, writing it there if it is absent, and delete the one above the use; this is the one case where an accurate comment is removed rather than corrected. Where the copy above the use carries a constraint the declaration does not, fold that into the declaration and then delete the copy, so the fact lands on the declaration either way. Failing any one of the three, leave both in place and report it: a repetition left alone costs a reader one duplicated sentence, where a wrong deletion destroys the only place a constraint was written down. _Bad:_ `isBetaEnabled mirrors the beta-features flag` above every read of `isBetaEnabled`. _Good:_ that sentence once, on the declaration of `isBetaEnabled`, and nothing at the read sites. -- **Comments describe the code as it stands.** Never narrate a change, a fix, or a prior state ("now uses", "previously", "no longer", "restored", "replaces", "used to", "formerly", "for the first time", "unlike the old"), and never name a file, flag, or tool that no longer exists: version control carries that, and the comment outlives the change that prompted it. Never argue that the code is correct or safe, which documents the edit rather than the code. Delete commented-out code rather than leaving it in place. **Test every comment by naming the line it describes**, since a phrase list only catches the comments that announce themselves. Point at the code beneath the comment that the comment is about; where nothing corresponds, it is not a comment about this code. That is what catches a comment explaining an absence, meaning why something was removed, why an approach was rejected, or what an earlier version did: nothing in the file matches it because its subject is a decision, and the reader who wants that decision is reading the commit or the change request that carries the diff proving it. Delete it. Two comments pass this test and stay: a note about a deliberate omission the code depends on, such as why a field must stay out of a payload, and a file-level header, which describes the file rather than any one line. +- **A fact is documented once, at the declaration of the thing it is about.** A statement about a symbol belongs on that symbol's own declaration, never above the lines that read it, call it, or branch on it. Where the same sentence would sit above more than one _use_ of a symbol, it belongs on the declaration alone, or in `docs/` where it spans more than one symbol. A declaration is not a use: each member of a public structure still gets its own comment, and a file-level header still summarizes what the file declares. **Removing a copy is bounded, and all three preconditions hold before anything is deleted:** the declaration and the usage site both sit inside the scope this run resolved, so the rule never reaches a file the scope rule did not resolve to; the declaration's body has been opened this run, since a copy cannot be judged redundant against a declaration nobody read; and the copy says no more than the declaration's comment says. With all three met, keep the copy on the declaration, writing it there if it is absent, and delete the one above the use; this is the one case where an accurate comment is removed rather than corrected. Where the copy above the use carries a constraint the declaration does not, fold that into the declaration and then delete the copy, so the fact lands on the declaration either way. Failing any one of the three, leave both in place and report it: a repetition left alone costs a reader one duplicated sentence, where a wrong deletion destroys the only place a constraint was written down. +- **Comments describe the code as it stands (Rule 4), and the test is to name the line.** Delete commented-out code rather than leaving it in place. A phrase list only catches the comments that announce themselves, so point at the code beneath the comment that the comment is about; where nothing corresponds, it is not a comment about this code. That is what catches a comment explaining an absence, meaning why something was removed, why an approach was rejected, or what an earlier version did: nothing in the file matches it because its subject is a decision, and the reader who wants that decision is reading the commit or the change request that carries the diff proving it. Delete it. Two comments pass this test and stay: a note about a deliberate omission the code depends on, such as why a field must stay out of a payload, and a file-level header, which describes the file rather than any one line. - **Form:** a documentation comment is a complete sentence, capitalized and punctuated; a short trailing comment may be a fragment. Wrap long comment lines to the width the file already uses, letting an unbreakable URL exceed it. Use the documentation format's own list syntax for enumerations, since indented plain text collapses into one run-on sentence when rendered. Never box a comment in asterisks or other decorative characters. Documentation precedes an annotation or decorator and never sits between it and the declaration. - **Contracts worth stating:** any cleanup the caller owns (a handle to close, a listener to remove, a subscription to cancel), the error values or exception types a caller can branch on, and a deprecation marker naming its replacement. A deprecation without migration directions is incomplete; add one only where it is provable under Rule 2. - **File-level headers:** where the language provides one, it states the file's contents, uses, or dependencies. Notes aimed at maintainers rather than consumers go with the implementation instead. @@ -76,9 +79,9 @@ Every statement must be grounded in code you have **opened and read in full duri **The "prove it" test:** before writing any statement, name the file, the symbol, and a short string from the source that shows the behaviour, copied as it reads there except for any credential value in it, such as a token, a password, an API key, a private key, or a session identifier, which is replaced by `[REDACTED]` as you record it. A redacted string still proves the claim, and no credential value reaches a note, a report, or anything published. If you cannot produce such a string at all, do not write the statement. **A line number is not proof.** It cannot be checked without opening the file, it drifts on the next edit, and it can be produced without reading anything; copying a string requires retrieval. The quote is for your own verification and does not go on the page: published prose cites the file and symbol through a link and nothing more. -- ❌ "The system validates user input." (assumption) -- ❌ "After reading [`validation.ts`](../src/validation.ts) lines 45-67, user input is validated against the schema." (a line range is not evidence) -- ✅ Proof held: symbol `parseConfig` in [`config.ts`](../src/config.ts), quote `throw new RangeError('retries must be >= 0')`. Written: "[`parseConfig`](../src/config.ts) rejects a negative `retries` value with a `RangeError`." +✅ Proof held: symbol `parseConfig` in [`config.ts`](../src/config.ts), quote `throw new RangeError('retries must be >= 0')`. Written: "[`parseConfig`](../src/config.ts) rejects a negative `retries` value with a `RangeError`." + +**A claim spanning several files is grounded the same way, from each of them.** The orientation sentence a document opens with rests on a handful of files rather than one line, so hold a quote from every file carrying a part of it and write the sentence once each part is covered. Being unfalsifiable against any single symbol is not what disqualifies such a sentence; an uncovered part is. Cover the system rather than only the sentence: before writing one, look for the file that would qualify or contradict it, since a synthesis is refuted by what it leaves out rather than by what it states. The reverse stays banned: a summary written because no quote could be found is a guess with a citation attached. **If you cannot verify, keep it off the page and report it.** Do not guess, do not leave a TODO, and never write "appears to", "seems to", "likely", "probably", "should", or "will". Silence in the documentation beats speculation in it, and naming the gap in your output beats both. Never document planned or intended behaviour. For complex behaviour, confirm against two or three locations (definition, usage, test). @@ -86,12 +89,12 @@ Every statement must be grounded in code you have **opened and read in full duri - **Correct falsehoods.** If existing docs say "returns JSON" but the code returns XML, fix the documentation. - **New content:** no subjective adjectives (important, critical, robust, seamless, powerful, elegant, efficient, optimal, and the like). State facts. _Bad:_ "The `auth.ts` middleware is a critical component." _Good:_ "The `auth.ts` middleware blocks unauthorized requests." -- **Objective is not flat.** Banning subjective adjectives does not mandate robotic prose. Replace the adjective with the concrete cited fact that earns it: not "the retry logic is robust" but "the retry runs three times with a two-second backoff ([retry.ts](../src/retry.ts) lines 12-19)." (show, do not tell) +- **Objective is not flat.** Banning subjective adjectives does not mandate robotic prose. Replace the adjective with the concrete cited fact that earns it: not "the retry logic is robust" but "the retry runs three times with a two-second backoff ([retry.ts](../src/retry.ts))." (show, do not tell) - **Existing content:** preserve existing subjective terms unless they are factually wrong. ### Rule 4: Current state only -Documentation and comments describe the code as it is now. Never narrate the past ("replaces", "used to", "formerly", "for the first time", "unlike the old") and never name a file, flag, symbol, or tool that no longer exists: version control already carries that history, and a reader cannot check a claim against something that is gone. The only sanctioned place for future intent is a `TODO` in the code that will change, positioned however that codebase positions one; documentation itself carries none, so no empty sections, stubs, or "add details here" placeholders, and if the code does not exist, neither should its documentation. Rationale worth keeping goes in its own decision record, not scattered through the files it explains. +Documentation and comments describe the code as it is now. Never narrate a change, a fix, or a prior state ("now uses", "previously", "no longer", "restored", "replaces", "used to", "formerly", "for the first time", "unlike the old"), never argue that the code is correct or safe, which documents the edit rather than the code, and never name a file, flag, symbol, or tool that no longer exists: version control already carries that history, a comment outlives the change that prompted it, and a reader cannot check a claim against something that is gone. The only sanctioned place for future intent is a `TODO` in the code that will change, positioned however that codebase positions one; documentation itself carries none, so no empty sections, stubs, or "add details here" placeholders, and if the code does not exist, neither should its documentation. Rationale worth keeping goes in its own decision record, not scattered through the files it explains. ### Rule 5: Mermaid diagram and image accessibility (zero tolerance) @@ -115,7 +118,7 @@ Write as a careful human technical writer: formal and neutral, never robotic. Th - **Lead with the point**, putting the conclusion, answer, or action in the first sentence. **Show, do not tell:** demonstrate with a command, number, cited line, or named edge case instead of asserting significance. Vary sentence length where natural, without forcing a cadence target. - **Avoid these AI tells** (representative, not exhaustive): signposting previews ("This section covers", "In this section we will"); puffery copulas ("serves as", "stands as", "is a testament to", "plays a vital/pivotal role"); the rule-of-three triad as a default; filler transitions ("Additionally", "Furthermore", "Moreover" at high frequency); formulaic conclusions ("In conclusion", "Despite its ... it faces challenges"); and padded words such as delve, leverage, underscore, showcase, intricate, vibrant, foster, tapestry, seamless. Keep a word when it is factually correct in context (a test `harness`, an OAuth `realm`). - **A why-claim is still a claim (Rule 2).** Cite the comment, design record, commit, test, or config that proves a rationale or trade-off, or state the _what_ and stop. -- **Scope.** Apply this only to prose you add or change; do not rewrite accurate existing prose for rhythm (Phase 2, Rule 1). It governs `docs/` prose, not in-code documentation, which Phase 3 keeps terse. +- **Scope.** Apply this only to prose you add or change; do not rewrite accurate existing prose for rhythm (Phase 2, Rule 1). Introducing a term, naming a subject, or stating a prerequisite is an addition rather than a rewrite, so it is made even where the surrounding prose is accurate. It governs `docs/` prose, not in-code documentation, which Phase 3 keeps terse. - **Stay formal.** No contractions, casual asides, emoji, or detector-evasion tricks. Naturalness comes from cutting tells, not from informality. ### Brevity & style @@ -127,7 +130,7 @@ Write as a careful human technical writer: formal and neutral, never robotic. Th - **No em-dashes or en-dashes.** Never write `—` (em-dash) or `–` (en-dash). Replace each with the grammatically appropriate punctuation: a comma, parenthesis, colon, separate sentence, or a spaced hyphen `-`. The plain hyphen `-` is fine wherever it is grammatically correct, including the `-` separator between a label and a brief description in lists (e.g. `**Label** ([file](path)) - what it does`). When an audit edits a document, replace that document's existing em-dashes and en-dashes the same way; do not sweep files you are not editing. - **Canadian English (strong preference).** Spelling you write or change uses Canadian forms: colour, behaviour, favour, licence (noun), centre, defence, and `-ize`/`-ization` (standardize, organization, recognize). See the [Canadian spelling guide](https://our-languages.canada.ca/en/blogue-blog/canadian-spelling-eng). Do not retroactively convert existing American prose; apply this only to text you add or change. **Never** alter code identifiers, config or JSON keys, quoted code, file or package names, CSS properties, or API names (`user_id`, `maxRetries`, and the like stay exactly as written). -- **Acronyms.** In prose you write or edit, write acronyms in capitals (ID, URL, API) and, on first use per document, give the full term first, e.g. "Deoxyribonucleic acid (DNA)", then the bare acronym after. Keep exact casing in three cases: an established brand, tool, or package name (npm, iOS, ESLint), an intentional domain term (snRNA, mRNA), and a direct code reference (a method, field, env var, or config key, such as an `id` property, stays as written in the code). +- **Acronyms and terms of art.** In prose you write or edit, write acronyms in capitals (ID, URL, API) and, on first use per document, give the full term first, e.g. "Deoxyribonucleic acid (DNA)", then the bare acronym after. Keep exact casing in three cases: an established brand, tool, or package name (npm, iOS, ESLint), an intentional domain term (snRNA, mRNA), and a direct code reference (a method, field, env var, or config key, such as an `id` property, stays as written in the code). **Expanding an acronym is not introducing it**, since an expansion is often as opaque as the abbreviation. A term the reader could not define from general knowledge is introduced where the document first uses it, in a short parenthesis or by a link to the document defining it, and used unchanged after: once per document rather than once per set, because a reader arrives by search and lands in the middle of it. **Test it against what you knew before this run:** a term whose meaning you settled by reading this project's code, rather than by knowing the word, needs introducing. Judging by whether it feels obvious fails, because you have just read the implementation. Spell a concept one way across the whole scope, since a concept spelled three ways is three concepts to anyone meeting it, and defeats their search. ### Configuration references @@ -136,8 +139,8 @@ Write as a careful human technical writer: formal and neutral, never robotic. Th ### File citations & references (strictly enforced) - **Every technical claim cites its source file.** No citation, no claim. -- **Every file reference is a clickable markdown link**, `[filename](relative/path)`. No bare filenames: write "See [`server.ts`](../src/server.ts) for the implementation", never "See server.ts for the implementation". -- **Links target files, not directories.** If the text refers to a directory, link to a file inside it such as its `index.md` or `README.md`, so a link to a `/design` directory targets `../design/index.md` and never `../design`. +- **Every file reference is a clickable markdown link**, `[filename](relative/path)`, never a bare filename. +- **Links target files, not directories.** Where the text refers to a directory, link to a file inside it such as its `index.md` or `README.md`. - **Link text names the destination.** Never "here", "link", "this", or a bare URL: write the sentence first, then wrap the phrase that names what it points at. - Weave links into prose; use a footer `Implementation:` only when inline is unnatural. Do not link the same file twice in adjacent sentences. - Verify every path resolves from the doc's own location, and every anchor against the current heading text it points at, since a renamed heading breaks a link that still looks correct. If a referenced file, or a heading an anchor names, does not exist, correct or remove the statement. @@ -145,6 +148,7 @@ Write as a careful human technical writer: formal and neutral, never robotic. Th ### Code snippets - Do not inline full definitions or class bodies; link to the file. Exceptions, 3-10 lines maximum: a specific usage example or how-to, a single critical configuration line, or logic that text alone cannot convey. +- **An example a reader copies and adapts is a usage example and belongs inside that allowance.** Write it in the language and file format the reader will actually edit, and label the fence with that language: a block labelled as one format and written in another does not run, and the reader who pastes it learns that after the error rather than before. ### Formatting @@ -164,7 +168,7 @@ Include a step only if it meets all three criteria: 2. **State or data transformation:** it changes data, state, or the execution path. 3. **Cannot be removed:** removing it would break functionality or change a user-observable outcome. -Exclude logging, metrics, telemetry, trivial validation, internal utilities, and debug code, unless the system you are documenting _is_ observability. Test: "would removing this step change what the user experiences?" If no, exclude it. +Exclude logging, metrics, telemetry, trivial validation, internal utilities, and debug code, unless the system you are documenting _is_ observability. --- @@ -188,10 +192,12 @@ Then confirm: - No hedging ("appears to", "seems to", "likely", "probably", "should", "will"), no new subjective adjectives, and no code dumps. - Every file reference is a clickable link resolving to a file, not a directory. Configuration references name the value a consumer changes it by. - Acronyms you wrote are capitalized and expanded on first use (exceptions: brand/tool/package names, domain terms, code references). -- New or changed prose reads as a careful human wrote it: leads with the point, no signposting or banned AI tells, one canonical term per concept, no ambiguous `it`/`this`/`these`. +- New or changed prose reads as a careful human wrote it: leads with the point, no signposting or banned AI tells, one canonical term per concept spelled identically in every document in scope, no ambiguous `it`/`this`/`these`. +- Every document you wrote or reworked opens by naming its subject and why a reader would reach for it, states what that reader must already have or have read, and introduces every acronym, term of art, and named component the first time it uses one. +- **Read each document once as the newcomer**, who has not seen this codebase, fix a missing introduction, subject, or prerequisite where you find one, name in your output anything left, or state that nothing stops them: a page only its author can follow is not finished. Then read it as the experienced reader, for whom a paragraph they could have got faster from the source has not earned its place either; there the fix is what the paragraph fails to add, not deletion by default. - Architecture flows include only significant steps (§4); every diagram has `accTitle` and `accDescr`, and every image has real alt text. - No em-dashes (`—`) or en-dashes (`–`) anywhere you wrote; new or changed prose uses Canadian English. -- Every public symbol you touched carries a documentation comment written from its implementation, not from its name, and no comment narrates a change, names something that no longer exists, argues the code is safe, or sits commented out. No comment you added sits above a usage site rather than a declaration, and every comment you removed as a repetition either said no more than the declaration's or had what it added folded into the declaration first. +- Every public symbol you touched carries a documentation comment written from its implementation, not its name, and none narrates a change, names something gone, argues the code is safe, or sits commented out. No comment you added sits above a usage site rather than a declaration, and every one you removed as a repetition either said no more than the declaration's or had its addition folded in first. - Rendered output was checked, not only the source: diagrams parse, nested lists render, and documentation comments display the intended text. Every table you touched was re-read whole, with each row's cell count matching its header and no cell broken across lines. - Every document you created sits in a directory whose existing documents are the same Diátaxis type, or in a new directory created for that type, and your output names the directory and what decided it. - No comment you wrote or kept describes something the file does not contain, and every comment you deleted on that ground was one you could not attach to a line. diff --git a/.github/prompts/readme.md b/.github/prompts/readme.md index b08881f..e4e4818 100644 --- a/.github/prompts/readme.md +++ b/.github/prompts/readme.md @@ -19,7 +19,7 @@ Take whichever suits your constraints. Each half works alone: a prompt names not - **`audit-pr`** reviews a diff: what a change does, what it breaks, and whether it should merge. Eighteen categories entered selectively through a triage table, every finding quoting the changed line, and a refutation pass that deletes findings which do not survive scrutiny. - **`audit-quality`** audits code as it stands rather than a change. Thirteen categories, discovery before findings, and the same evidence and refutation discipline. -- **`audit-docs`** owns documentation accuracy for both. Evidence is a file, a symbol, and a verbatim quote rather than a line number, and unverifiable claims go to an explicit "unverified" list instead of into the prose. +- **`audit-docs`** owns documentation accuracy for both. Evidence is a file, a symbol, and a verbatim quote rather than a line number, and unverifiable claims go to an explicit "unverified" list instead of into the prose. It holds every page to two readers, a newcomer meeting the system for the first time and someone who already works in it, and reports which of the two a document fails. Run one, not all three. diff --git a/next.config.js b/next.config.js index 7d7744c..3cbdbb1 100644 --- a/next.config.js +++ b/next.config.js @@ -4,6 +4,7 @@ const { withSentryConfig } = require('@sentry/nextjs'); const isDevelopment = process.env.NEXT_PUBLIC_ENVIRONMENT === 'development'; const nextConfig = { + agentRules: false, images: { // SVGs are handled by @svgr/webpack (see turbopack.rules) and no other // image types are statically imported, so disable Next's static image diff --git a/package-lock.json b/package-lock.json index ffa7bde..eb00757 100644 --- a/package-lock.json +++ b/package-lock.json @@ -17,7 +17,7 @@ "@vercel/speed-insights": "^2.0.0", "firebase": "^12.17.1", "lodash": "^4.18.1", - "next": "^16.3.0", + "next": "^16.3.1", "react": "^19.2.8", "react-dom": "^19.2.8", "sharp": "^0.35.3" @@ -33,8 +33,8 @@ "@types/react": "^19.2.18", "@types/react-dom": "^19.2.4", "@typescript-eslint/parser": "^8.67.0", - "concurrently": "^10.0.4", - "cypress": "^15.20.1", + "concurrently": "^10.0.5", + "cypress": "^15.21.0", "cypress-axe": "^1.7.0", "eslint": "^10.8.1", "jest": "^30.4.2", @@ -5165,9 +5165,9 @@ } }, "node_modules/@napi-rs/wasm-runtime": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.2.2.tgz", - "integrity": "sha512-JfB4kuJQjaoHuCTseIINHtHWeJnvgEcxjwA5t/Y00ZgaOO1Crz3fjT/p8kT28zA/Caz7oiUMn3d6H2yOVCVwuw==", + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.2.3.tgz", + "integrity": "sha512-UMduMbqO5s5zF2NkNacMT/yK5Y5QiKvWr2+50bzIIxFDwVJ2h49b+oyjaCGPhJxd2/gC2x39EHv/gHVuu36x2Q==", "dev": true, "license": "MIT", "optional": true, @@ -5182,20 +5182,20 @@ "url": "https://github.com/sponsors/Brooooooklyn" }, "peerDependencies": { - "@emnapi/core": "^1.7.1 || ^2.0.0-alpha.3", - "@emnapi/runtime": "^1.7.1 || ^2.0.0-alpha.3" + "@emnapi/core": "^1.7.1 || ^2.0.0-alpha.4", + "@emnapi/runtime": "^1.7.1 || ^2.0.0-alpha.4" } }, "node_modules/@next/env": { - "version": "16.3.0", - "resolved": "https://registry.npmjs.org/@next/env/-/env-16.3.0.tgz", - "integrity": "sha512-o9r1S0BNiNreHP9Vs+Qnqd9kviDkJh8xIACY7UFZSmiGbbQRzPBBosvHzAU4TULHOIuOj/18RSsyz2qrREmIFw==", + "version": "16.3.1", + "resolved": "https://registry.npmjs.org/@next/env/-/env-16.3.1.tgz", + "integrity": "sha512-35G3xwkQUb2oETSDjFXGrVugknoayLFBh7vSE+yAcl9IP2zT9wyGwq7297AYHR11kJld807t5f8AJBs6WBzXsQ==", "license": "MIT" }, "node_modules/@next/swc-darwin-arm64": { - "version": "16.3.0", - "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-16.3.0.tgz", - "integrity": "sha512-55hpqq18bEVAlxedlTt3tFqZmKg2nUXT1kn1G/BGEy0R13h3LwtwHPVzzjG6P4LLeOHE32PFDQUVaJEWvBEZBw==", + "version": "16.3.1", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-16.3.1.tgz", + "integrity": "sha512-ABMIu2zQ7cnNIHm5ivKGwZwUrm0pAai3yiJ/gK/rF1c1VP9UOnj7XECbMKFdVKp9I9eMYq9NoDs1WXOoowxzJw==", "cpu": [ "arm64" ], @@ -5209,9 +5209,9 @@ } }, "node_modules/@next/swc-darwin-x64": { - "version": "16.3.0", - "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-16.3.0.tgz", - "integrity": "sha512-SOi96kSaF5T+0wW4koiM1bWzSPwjzTesC1p3df+FjdOi5LIQkBK/blxh7HdoKnNuI4PURF1OO7TZqtfnbWDSgw==", + "version": "16.3.1", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-16.3.1.tgz", + "integrity": "sha512-gNG21e/UnrroeScbY/QndUEdl0mF1FRibW7BBeYUz/5ABCepjqDdEdgr592vpzMtCn/m7FTjYq3TN4TpyDnutw==", "cpu": [ "x64" ], @@ -5225,9 +5225,9 @@ } }, "node_modules/@next/swc-linux-arm64-gnu": { - "version": "16.3.0", - "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-16.3.0.tgz", - "integrity": "sha512-P0gZAoPMF4dyTRzhmkV4PrqVzSOB6t4mC1oI3c4dqijJ+OVEVx5clIXAKR4/uQpsqw2KKM/0D5tVumcR2r5blg==", + "version": "16.3.1", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-16.3.1.tgz", + "integrity": "sha512-6B6Lw016iwNUQuaJoraMMTLh6TwHzFUtxipSScD1F3YyymcrRWkobodRS2ftIOkF5vrs4zNlyUrTC5YZQ9Lz5w==", "cpu": [ "arm64" ], @@ -5244,9 +5244,9 @@ } }, "node_modules/@next/swc-linux-arm64-musl": { - "version": "16.3.0", - "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-16.3.0.tgz", - "integrity": "sha512-tXXGKJw0m37O0eKJARVTX/TheKPhz0QFVtVVZXmOig+9YKLQOSP6hvf2pxv5DO7CLEJyTHx3Pg043CDQkv1G4Q==", + "version": "16.3.1", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-16.3.1.tgz", + "integrity": "sha512-JUiPXZKK9wOhjf4MgDiH29GZLxfqOesbLtHq2pDxwH/WwscTRV2ToymnOTh1egzaZf0ueUf8T2+CeYTGHjW0Iw==", "cpu": [ "arm64" ], @@ -5263,9 +5263,9 @@ } }, "node_modules/@next/swc-linux-x64-gnu": { - "version": "16.3.0", - "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-16.3.0.tgz", - "integrity": "sha512-pjGxK5EY7yWml78ALejFkWmgHsU7wbFQrISiugpH6FbUJhgEvw3xFZ/EBAtLl7QtL0WdQKiG9eWJ3mOKGTukHw==", + "version": "16.3.1", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-16.3.1.tgz", + "integrity": "sha512-Uog9jsrmIRIL/lfvIp9htmskSNC7JcQsMVucXL2V2YY1y/D9IUN3LPEafqy0zRJ2cIU1SQ0V6F6TlffQ+pLAGg==", "cpu": [ "x64" ], @@ -5282,9 +5282,9 @@ } }, "node_modules/@next/swc-linux-x64-musl": { - "version": "16.3.0", - "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-16.3.0.tgz", - "integrity": "sha512-sjo++Xx+lomlPs3HRsHWhVDyGG6ms1kGW5EtHLERdII8AyG1i+f6aq68xHREO6AEMlhjTNEWBSmfJfqm9orf7g==", + "version": "16.3.1", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-16.3.1.tgz", + "integrity": "sha512-6yy3FT13KgUFOj5H8bl8w/6nKiJwHIvbtwh1V+1acsu+7y4tJjnemSa6mhsh53BeoVrlozE+fMgZhXH46WmjMA==", "cpu": [ "x64" ], @@ -5301,9 +5301,9 @@ } }, "node_modules/@next/swc-win32-arm64-msvc": { - "version": "16.3.0", - "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-16.3.0.tgz", - "integrity": "sha512-C5JSgiO54wURdaxdEUIXqkz04uMqC9UmPX1gtDrV/5Tf1UowdWYI8uA5hfFbPolTlp0q4KZ60xlHePNibf0VIw==", + "version": "16.3.1", + "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-16.3.1.tgz", + "integrity": "sha512-iOoN1QecUoGNZik536U/vtK43YwgyrCsGIkth52yIkl612n+0C9MjSnJbQAikISpb+WYRooBVhaDlUW7iZoKog==", "cpu": [ "arm64" ], @@ -5317,9 +5317,9 @@ } }, "node_modules/@next/swc-win32-x64-msvc": { - "version": "16.3.0", - "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-16.3.0.tgz", - "integrity": "sha512-fDOggsweNb5SSw0ZKVk6U+gxSyGFFlIBY/LBc1r8GUj4u/6t6oArL+Pmkg0MBnsgR+KkdsURilVH4F3GXUGepA==", + "version": "16.3.1", + "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-16.3.1.tgz", + "integrity": "sha512-d/k+PpAriUPaeMJJOG7HUSdqfEX46FEPWU1p3/nm2ACmXhj9hFEWdFODUBIpkuijXYkfL90qZzTqVPRp4BW/hw==", "cpu": [ "x64" ], @@ -7087,9 +7087,9 @@ } }, "node_modules/@swc/helpers": { - "version": "0.5.15", - "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.15.tgz", - "integrity": "sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g==", + "version": "0.5.23", + "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.23.tgz", + "integrity": "sha512-5lSsMOTXURePglDfvuAQUqkGek9Hg2kksOYay2m0+XR++b2NWYL/4sWyuvVBIs8oKnJaxkdi9whaL/sqN13afw==", "license": "Apache-2.0", "dependencies": { "tslib": "^2.8.0" @@ -8837,9 +8837,9 @@ "license": "MIT" }, "node_modules/baseline-browser-mapping": { - "version": "2.11.13", - "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.11.13.tgz", - "integrity": "sha512-k9HNuUVMlqVjQ9UHzfPjIqiDbWw7WqT1AoT7GL8VwvF3r0ZfArtgiSPAlmupyNquNgOJHTuH4CKYf8ttMTWBTQ==", + "version": "2.11.15", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.11.15.tgz", + "integrity": "sha512-FwMjJJ7HnyZpWe+oWxegG0fezZyBZUagI5LZEoO3GCbtbKNwRfMH9Ue5d5v01PNePBy1QSfPSDTTeVL0Hb9EzA==", "license": "Apache-2.0", "bin": { "baseline-browser-mapping": "dist/cli.cjs" @@ -9148,6 +9148,27 @@ "url": "https://paulmillr.com/funding/" } }, + "node_modules/chrome-remote-interface": { + "version": "0.33.3", + "resolved": "https://registry.npmjs.org/chrome-remote-interface/-/chrome-remote-interface-0.33.3.tgz", + "integrity": "sha512-zNnn0prUL86Teru6UCAZ1yU1XeXljHl3gj7OrfPcarEfU62OUU4IujDPdTDW3dAWwRqN3ZMG/Chhkh2gPL/wiw==", + "dev": true, + "license": "MIT", + "dependencies": { + "commander": "2.11.x", + "ws": "^7.2.0" + }, + "bin": { + "chrome-remote-interface": "bin/client.js" + } + }, + "node_modules/chrome-remote-interface/node_modules/commander": { + "version": "2.11.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.11.0.tgz", + "integrity": "sha512-b0553uYA5YAEGgyYIGYROzKQ7X5RAqedkfjiZxwi0kL1g3bOaBNNZfYkzt/CL0umgD5wc9Jec2FbB98CjkMRvQ==", + "dev": true, + "license": "MIT" + }, "node_modules/chrome-trace-event": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.4.tgz", @@ -9175,9 +9196,9 @@ } }, "node_modules/cjs-module-lexer": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-2.2.0.tgz", - "integrity": "sha512-4bHTS2YuzUvtoLjdy+98ykbNB5jS0+07EvFNXerqZQJ89F7DI6ET7OQo/HJuW6K0aVsKA9hj9/RVb2kQVOrPDQ==", + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-2.2.1.tgz", + "integrity": "sha512-Ca8swihM+/4yKecYHY52kgJd300hi2lADU/a1RxNTRe+RJ9jvqQlESpbz9DnG9mowez8qwXHB8qYdIUw9e+F5Q==", "license": "MIT" }, "node_modules/cli-cursor": { @@ -9402,9 +9423,9 @@ "license": "MIT" }, "node_modules/concurrently": { - "version": "10.0.4", - "resolved": "https://registry.npmjs.org/concurrently/-/concurrently-10.0.4.tgz", - "integrity": "sha512-trZql+7l/0+WRAsAnEdctr4+iiOS6ZrViI6H8QWcCF9MFS/LT0dKpe8vluB1to6it+OxSI4VospFTIFMW8DJRw==", + "version": "10.0.5", + "resolved": "https://registry.npmjs.org/concurrently/-/concurrently-10.0.5.tgz", + "integrity": "sha512-JaP/CoftUrCcAFW/g//RbgEGwlelnEae6cfBLgH6ZdO6s8jPkn6p9SB9u6pdVxYXoiSnFqseOlHfrEfF82TVOg==", "dev": true, "license": "MIT", "dependencies": { @@ -9606,9 +9627,9 @@ "license": "MIT" }, "node_modules/cypress": { - "version": "15.20.1", - "resolved": "https://registry.npmjs.org/cypress/-/cypress-15.20.1.tgz", - "integrity": "sha512-7GV3O7vQQG+EVVv9BGs6lCHY49x25jVi/z1+zdjuuq/o3eZeDwmGPJpRYnOWeGvQxTCtkydXWHBPhV8xuGXPLg==", + "version": "15.21.0", + "resolved": "https://registry.npmjs.org/cypress/-/cypress-15.21.0.tgz", + "integrity": "sha512-6uPy5GUjao97t/QDIwlPyJz4JUqPpFIPq5lNazN95LSA3ynJoYC1sHZ3W+aj4f+K1OFGbMfno0+OX2Gx0OmlPA==", "dev": true, "hasInstallScript": true, "license": "MIT", @@ -9624,6 +9645,7 @@ "buffer": "^5.7.1", "cachedir": "^2.4.0", "chalk": "^4.1.0", + "chrome-remote-interface": "0.33.3", "ci-info": "^4.1.0", "cli-table3": "0.6.1", "commander": "^6.2.1", @@ -9770,9 +9792,9 @@ } }, "node_modules/dayjs": { - "version": "1.11.21", - "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.21.tgz", - "integrity": "sha512-98IT+HOahAisibz/yjKbzuOBwYcjJ7BCLPzARyHiyEBmRz4fatF+KPJszEHXsGYjUG234aH/cOjW1wwTbKUZlA==", + "version": "1.11.23", + "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.23.tgz", + "integrity": "sha512-QDTCU0M0MxR3hQfnlDJfwekQiaanm1ubOD231u73WBckQ/fsamwRLiE2GBz6D3a/xF1NgfiDLJjXBa1hYOYTtQ==", "dev": true, "license": "MIT" }, @@ -10033,9 +10055,9 @@ } }, "node_modules/electron-to-chromium": { - "version": "1.5.403", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.403.tgz", - "integrity": "sha512-MQsYmdaLzvaCX5j+ZZBr5Fm6uCCnPQcRtlvmvRlWqrXy+BH2O4ffXIAScF+JQznQWB9brWp4lSD9Z4yNmaf2BA==", + "version": "1.5.410", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.410.tgz", + "integrity": "sha512-Vq9DD7F4PKCKVmOoG6i1CQSYoF7IUtwTPEQjMXuqNs2S22H8HsojO9myaB81QuvvIKZaF6imSn3XAV1Su6rvXA==", "license": "ISC" }, "node_modules/emittery": { @@ -10136,9 +10158,9 @@ } }, "node_modules/es-module-lexer": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-2.3.1.tgz", - "integrity": "sha512-shc1dbU90Yl/xq1QrC7QRtfcwURZuVRfPhZbDoldJ1cn1gzDvBaBWlv0eFolj5+0znnPJz5TXLxsN77X/12KTA==", + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-2.3.2.tgz", + "integrity": "sha512-poHGpORABojJJucnV9KbOavETW8lBVnphkW77ER5/BQ5Fz7oXSoCNek7IH3vR5nRjdsEz926ibFYX8KtLQmdyw==", "license": "MIT" }, "node_modules/es-object-atoms": { @@ -13504,6 +13526,28 @@ "node": ">= 14" } }, + "node_modules/jsdom/node_modules/ws": { + "version": "8.21.3", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.21.3.tgz", + "integrity": "sha512-201TZ/kPWxoPr/OKWjquZR1SWKXcvxdH+e1xrx89b3YbmzLMFCLfnaG1HFIgWzJOEWZ7MvpK++odZufgYR50Rw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, "node_modules/jsesc": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", @@ -14991,13 +15035,13 @@ "peer": true }, "node_modules/next": { - "version": "16.3.0", - "resolved": "https://registry.npmjs.org/next/-/next-16.3.0.tgz", - "integrity": "sha512-NEdGOzH+08eTXMUp9UYkA99Nhi5N6Thrhc1jgFOQgfgnGK/dA2hRwBpXep+exdFQrnwlRf/3Wixyp8lLBUpE2A==", + "version": "16.3.1", + "resolved": "https://registry.npmjs.org/next/-/next-16.3.1.tgz", + "integrity": "sha512-hsAp0i7Rh+/dhe7DGIeN2YlpLM1DP4MNxti9EtDMtqcO612X81MvvEj388/oTce9U1EcEIOWDlGq0zRwrBKvuA==", "license": "MIT", "dependencies": { - "@next/env": "16.3.0", - "@swc/helpers": "0.5.15", + "@next/env": "16.3.1", + "@swc/helpers": "0.5.23", "baseline-browser-mapping": "^2.9.19", "caniuse-lite": "^1.0.30001579", "postcss": "8.5.23", @@ -15010,14 +15054,14 @@ "node": ">=20.9.0" }, "optionalDependencies": { - "@next/swc-darwin-arm64": "16.3.0", - "@next/swc-darwin-x64": "16.3.0", - "@next/swc-linux-arm64-gnu": "16.3.0", - "@next/swc-linux-arm64-musl": "16.3.0", - "@next/swc-linux-x64-gnu": "16.3.0", - "@next/swc-linux-x64-musl": "16.3.0", - "@next/swc-win32-arm64-msvc": "16.3.0", - "@next/swc-win32-x64-msvc": "16.3.0", + "@next/swc-darwin-arm64": "16.3.1", + "@next/swc-darwin-x64": "16.3.1", + "@next/swc-linux-arm64-gnu": "16.3.1", + "@next/swc-linux-arm64-musl": "16.3.1", + "@next/swc-linux-x64-gnu": "16.3.1", + "@next/swc-linux-x64-musl": "16.3.1", + "@next/swc-win32-arm64-msvc": "16.3.1", + "@next/swc-win32-x64-msvc": "16.3.1", "sharp": "^0.35.3" }, "peerDependencies": { @@ -16918,9 +16962,9 @@ } }, "node_modules/strip-ansi/node_modules/ansi-regex": { - "version": "6.2.2", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", - "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.3.0.tgz", + "integrity": "sha512-WpDfL7NO6j7tH88IDBNVdUJxDh9nmCteAVW9dsep846XdwF4naCBK+/tGLX3KJgcpgMRXCFlTM2hKGoK9FsdrQ==", "dev": true, "license": "MIT", "engines": { @@ -17138,9 +17182,9 @@ } }, "node_modules/terser": { - "version": "5.49.2", - "resolved": "https://registry.npmjs.org/terser/-/terser-5.49.2.tgz", - "integrity": "sha512-rGbJiKeQ4WDe3EXlDAIaQcwftVfv2Q8o1awFNfvXolJYKkb1AuZY1RTOmqx4LJXZENbWZA7eIsYGHuEzHsi1nQ==", + "version": "5.50.0", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.50.0.tgz", + "integrity": "sha512-CN9BVxWhgS/hRxtUMjtC2uRWSTcSfQFHMDWma6sKKfIivCD91sM+FOPfvwoaRMqCSrUpe1nv3jDamd9eEQ4y+w==", "license": "BSD-2-Clause", "peer": true, "dependencies": { @@ -18034,17 +18078,17 @@ } }, "node_modules/ws": { - "version": "8.21.3", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.21.3.tgz", - "integrity": "sha512-201TZ/kPWxoPr/OKWjquZR1SWKXcvxdH+e1xrx89b3YbmzLMFCLfnaG1HFIgWzJOEWZ7MvpK++odZufgYR50Rw==", + "version": "7.5.13", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.13.tgz", + "integrity": "sha512-rsKI6xDBFVf4r/x8XyChGK04QR/XHroxs/jUcoWvtEZM8TPU/X/uIY9B1CsSzYws9ZJb/6bbBu7dPhFW00CAoA==", "dev": true, "license": "MIT", "engines": { - "node": ">=10.0.0" + "node": ">=8.3.0" }, "peerDependencies": { "bufferutil": "^4.0.1", - "utf-8-validate": ">=5.0.2" + "utf-8-validate": "^5.0.2" }, "peerDependenciesMeta": { "bufferutil": { diff --git a/package.json b/package.json index 7f9259c..4cd56d7 100644 --- a/package.json +++ b/package.json @@ -42,7 +42,7 @@ "@vercel/speed-insights": "^2.0.0", "firebase": "^12.17.1", "lodash": "^4.18.1", - "next": "^16.3.0", + "next": "^16.3.1", "react": "^19.2.8", "react-dom": "^19.2.8", "sharp": "^0.35.3" @@ -58,8 +58,8 @@ "@types/react": "^19.2.18", "@types/react-dom": "^19.2.4", "@typescript-eslint/parser": "^8.67.0", - "concurrently": "^10.0.4", - "cypress": "^15.20.1", + "concurrently": "^10.0.5", + "cypress": "^15.21.0", "cypress-axe": "^1.7.0", "eslint": "^10.8.1", "jest": "^30.4.2",