From 96c3f033b1ca308ca65dfba28662558bb1a12712 Mon Sep 17 00:00:00 2001 From: Alexander Sullivan Date: Wed, 30 Sep 2026 17:17:34 -0400 Subject: [PATCH 1/2] stop audit-docs from deleting export comments --- .claude/rules/docs-authoring.md | 4 ++-- .claude/skills/audit-docs/SKILL.md | 15 ++++++------ .../audit-docs/agents/surface-auditor.md | 12 +++++----- .../assets/audit-report.template.md | 3 ++- .../references/evidence-and-citation.md | 2 ++ .../references/voice-and-ai-tells.md | 10 ++++---- .../SKILL.md | 16 ++++++------- .../agents/comment-and-jsdoc-auditor.md | 4 ++-- .../assets/copilot-instructions.template.md | 5 ++-- .../references/comments-and-jsdoc.md | 2 +- .github/copilot-instructions.md | 6 ++--- .github/prompts/audit-docs.prompt.md | 23 ++++++++++--------- CLAUDE.md | 2 +- 13 files changed, 55 insertions(+), 49 deletions(-) diff --git a/.claude/rules/docs-authoring.md b/.claude/rules/docs-authoring.md index 51f335b..bb2a8e5 100644 --- a/.claude/rules/docs-authoring.md +++ b/.claude/rules/docs-authoring.md @@ -13,8 +13,8 @@ When creating or editing any markdown file, follow the discipline below. These a - **Zero hallucination.** Document only what the code provably does. Read the implementation; don't infer behaviour from a name, type, comment, file location, or familiar pattern. - **Prove it.** Before writing any technical claim, know the exact file (and ideally lines) that prove it. If you can't, don't write it. Silence beats speculation: no "appears to", "should", "will", or planned/intended behaviour. - Fix existing statements that contradict the code. -- **Cut a comment by whole sentences.** Delete each sentence that restates the declaration or the code beneath it, re-describes members carrying their own comments, or narrates alternatives or reasoning where the code needs only the conclusion, and keep every other sentence word for word: the one sentence saying what a public symbol does and a sentence explaining a non-obvious internal always stay, and being accurate is not, on its own, a reason a sentence stays. A package or file comment says what the unit is for and never tours its members. -- **Current state only.** Describe the code as it is now, in prose and in comments alike. Never narrate the past ("replaces", "used to", "formerly", "for the first time", "unlike the old") and never name a file, flag, or tool that no longer exists: git carries that history, and a reader cannot check a claim against something that is gone. Future intent lives in a `TODO` in the code, never in the documentation. **Name the line a comment describes**, and delete the comment where no line corresponds: that is the test the phrase list misses, and a comment explaining why something was removed is what it catches, since its subject is a decision and its reader is looking at the pull request. Rationale worth keeping goes in a decision record of its own under [`docs/`](../../docs/index.md), created when the first one is needed, rather than scattered through the files it explains. +- **Cut a comment by whole sentences, above the public floor.** Never delete or cut below one sentence the documentation comment on a public or exported symbol, a public structure member, or a package or module. Keep the sentence saying what it does, even where it restates the name, code, or syntax; where unclear, keep the first sentence. Correct a wrong or absent-content claim from the body. If the body was not read, keep the comment and report it. Cut other sentences that restate code, re-describe documented members, or narrate alternatives or reasoning where the code needs only the conclusion, keeping every other sentence word for word. A package or file comment says what the unit is for and never tours its members. +- **Current state only.** Describe the code as it is now, in prose and in comments alike. Never narrate the past ("replaces", "used to", "formerly", "for the first time", "unlike the old") and never name a file, flag, or tool that no longer exists. Future intent lives in a `TODO` in the code, never in the documentation. **Name the line a comment describes.** Where no line corresponds, correct a public or exported symbol's comment from its body; delete an internal comment on that ground. A note about a deliberate omission the code depends on and a file header pass this test. Rationale worth keeping goes in a decision record of its own under [`docs/`](../../docs/index.md). ## Style diff --git a/.claude/skills/audit-docs/SKILL.md b/.claude/skills/audit-docs/SKILL.md index 1e33324..cb2e714 100644 --- a/.claude/skills/audit-docs/SKILL.md +++ b/.claude/skills/audit-docs/SKILL.md @@ -13,7 +13,7 @@ These hold however this skill was invoked. The sections below carry the detail, - **Files keep their path and name.** Rename, move, split, merge, or delete a file, or create a directory, only when the request asks for it or the user approves it when asked. Where one looks necessary, ask the user and wait; where you cannot ask, propose it in your output instead. - **Documentation only.** Never change executable code or behaviour unless the request explicitly asks for it. - **No claim without proof.** Every sentence rests on a string copied from code opened this run. What you cannot prove goes in your output, not in the file. -- **Comments stay short.** A documentation comment you write on a public symbol is at most two sentences from its body, in the language's own form: what it does, and a second only for an error, constraint, or caller obligation the code proves. An accurate existing comment gains no sentence except a fold or a deprecation's replacement (Phase 3). Apart from a public symbol's sentence saying what it does, a sentence that restates the code, re-describes members, or narrates alternatives or reasoning where the code needs only the conclusion is cut however accurate it is, and every other sentence stays word for word. A package or file comment is one sentence saying what the unit is for, plus one naming entry points only in a large package, and never lists or re-describes members that carry their own comments. +- **Public documentation comments have a floor.** Never delete or cut below one sentence the comment on a public or exported symbol, a public structure member, or a package or module. Keep the sentence saying what it does, as written, even when it restates the name, code, or syntax; where unclear, keep the first sentence. Correct a wrong or absent-content claim from the body. If the body was not read, keep the comment and report it. Other comments you write on a public symbol are at most two sentences from its body: what it does, and a second only for an error, constraint, or caller obligation the code proves. An accurate existing comment gains no sentence except a fold or a deprecation's replacement (Phase 3). Cut other whole sentences that restate code, re-describe members, or narrate alternatives or reasoning where the code needs only the conclusion. A package or file comment says what the unit is for, plus entry points only in a large package, and never tours its members. ## Role & Purpose @@ -117,16 +117,16 @@ Judge each document you edit in full as a whole, not only paragraph by paragraph ### Phase 3: in-code documentation audit -Phase 3 runs on every audit, whatever Phases 1 and 2 found, over the code files in scope; where there are none, say so. It corrects what is wrong, documents what is absent in scope, and cuts what a comment does not need; every sentence it keeps stays as written. +Phase 3 runs on every audit, whatever Phases 1 and 2 found, over the code files in scope; where there are none, say so. A floor holds over every cutting and deletion rule below: the comment on a public or exported symbol, a public structure member, or a package or module is never deleted or cut below its sentence saying what it does, even where that sentence restates the name, code, or syntax. Correct a wrong or absent-content claim from the body; when the body was not read, keep the comment and report it. Phase 3 corrects what is wrong, documents what is absent in scope, and cuts what a comment does not need; every sentence it keeps stays as written. - **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. On a pull request or working changes, write a missing comment only for a symbol the diff added or changed, and list the other undocumented symbols in those files in your output. -- **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 each public symbol in scope that lacks it; cut bloat. **Cutting works by whole sentences.** In any comment in scope, a documentation comment or a file or package comment included, delete each sentence that restates the declaration or the code beneath it, lists or re-describes members that carry their own comments, or narrates alternatives or reasoning where the code needs only the conclusion, and keep every other sentence word for word, and where a kept sentence leans on a cut one ("also", "these"), keep both or cut both; a comment made of nothing else goes entirely. Being accurate is not a reason a sentence stays; the one sentence saying what a public symbol does (next bullet) and a sentence explaining a non-obvious internal always stay. A reworded or merged sentence is a new claim and needs its own proof under Rule 2. A comment that is both wrong and long is corrected first, then cut. +- **Actions:** scan for documentation and comments; read the current implementation of each documented element; verify it against actual code behaviour; correct inaccurate or outdated comments, and remove inaccurate internal comments or orphaned TODOs; cut bloat above the public floor; document each public symbol in scope that lacks a comment. **Cutting works by whole sentences.** In any comment in scope, including documentation, file, and package comments, cut sentences that restate the declaration or code beneath it, re-describe members with their own comments, or narrate alternatives or reasoning where the code needs only the conclusion. Keep every other sentence word for word; where a kept sentence leans on a cut one ("also", "these"), keep both or cut both. A comment made of nothing else goes entirely unless the floor holds it. A public summary sentence and a sentence explaining non-obvious internal logic always stay. A reworded or merged sentence is a new claim needing its own proof (Rule 2). Correct a comment that is both wrong and long before cutting it. - **Document the public surface in scope, from the body.** Every public or exported symbol in scope carries a documentation comment, as do the members of a public structure: fields, properties, keys, enum values. Open the body and write the comment from it, in the form the language's convention sets (a Go comment opens with the symbol's name, a Python docstring with a one-line summary): one sentence saying what the symbol does, written even where the name makes that obvious, since being obvious is not a defect on a public surface and being absent is. A second sentence is allowed only for an error, a constraint, or a caller obligation that a line of the body, a test assertion, a configuration value, or a decision record opened this run proves, and a comment you write stops there; an accurate existing comment gains no sentence except a fold or a deprecation's replacement (below). Write no reason or invariant you cannot quote. A member's comment states what the code in scope that sets or reads it shows. **Rule 2 still governs, and it comes first.** Not having got to the body is no reason to skip the symbol, 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 an existing comment, a sentence doing so is cut like any restatement, while a tag is governed by the next bullet. +- **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 an existing comment, a sentence doing so is cut only above the public floor, while a tag is governed by the next bullet. - **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: a member's own comment and a file-level header are never removed as repetitions. **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. -- **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. +- **A fact is documented once, at its declaration.** A declaration, including an export or re-export statement, is not a use. A statement about a symbol belongs on its declaration, never above a line that reads, calls, or branches on it. Remove a copy above a use only when both sites are in scope, the declaration's body was read, and the copy says no more than the declaration's comment; fold any additional constraint into the declaration first. Otherwise leave both and report the copy. +- **Comments describe the code as it stands (Rule 4), and the test is to name the line.** Delete commented-out code. Point at the code beneath each comment; where no line corresponds, the comment describes a decision, not this code. Correct a public or exported symbol's comment from its body rather than deleting it; delete other comments on that ground. A note about a deliberate omission the code depends on and a file-level header also pass this test. - **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. 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, where the body shows them:** 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. Name an error or a duty only where the body or a callee you opened returns, raises, or requires it, and call a list of them complete only when every path through the body was read. A deprecation without migration directions is incomplete; add one only where it is provable under Rule 2. - **File and package comments:** where the language has one, it says in one sentence what the file or package is for, in the language's conventional form (a Go package comment opens `Package auth`), and in a large package it may add a second naming the few entry points a caller starts from. It never lists or re-describes members that carry their own comments, which the language's generated reference already lists. A package comment summarizes every file it spans, so write one only once each of those files was read this run. Notes aimed at maintainers rather than consumers go with the implementation instead. @@ -221,7 +221,8 @@ Then confirm: - 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 an existing home or in a directory whose existing documents are the same Diátaxis type, and your output names 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. -- No comment in scope, documentation comments and file or package comments included, restates its declaration beyond a public symbol's one-sentence summary, re-describes members carrying their own comments, or narrates alternatives or reasoning where the code needs only the conclusion, and every cut removed whole sentences and left the rest word for word (Phase 3). +- No comment in scope, documentation comments and file or package comments included, restates its declaration above the public floor, re-describes members carrying their own comments, or narrates alternatives or reasoning where the code needs only the conclusion, and every cut removed whole sentences and left the rest word for word (Phase 3). +- No documentation comment on a public or exported symbol, public structure member, package, or module was deleted or cut below one sentence; wrong or absent-content claims were corrected from the body, and comments whose bodies were not read were kept and reported. - Every document you edited in full, judged as a whole rather than paragraph by paragraph, is no longer than a careful human would have written for the same brief (Rule 6), and any split or move you judged necessary was asked about or proposed rather than made. - A passage you wrote that stayed long after Rule 6's trim was weighed for a complementary diagram, table, code/config snippet, or image (Rule 6), and one was added where it fit the content. - Phase 3 ran and its result is reported. diff --git a/.claude/skills/audit-docs/agents/surface-auditor.md b/.claude/skills/audit-docs/agents/surface-auditor.md index 167d35a..c236dc0 100644 --- a/.claude/skills/audit-docs/agents/surface-auditor.md +++ b/.claude/skills/audit-docs/agents/surface-auditor.md @@ -75,7 +75,7 @@ A comment can be accurate and still be in the wrong place. Report every comment Both halves of the test are mechanical, and both are required. The comment names a symbol, and the line beneath it uses that same symbol. A comment above a line that does not reference the symbol it discusses is a different comment and is never reported. -**The declaration bounds the entry, and this list is the one most likely to reach past the scope.** A symbol is used far from where it is declared, so following a usage site to its declaration is exactly how a pass drifts into code nobody asked it to touch. Report a copy under `REPEATED` only where the declaration sits inside the paths handed in **and** its body was opened this run. Where the declaration lies outside those paths, or inside them but unopened, the copy is not an entry: the comparison that would justify removing it was never made. It goes under `UNRESOLVED` with the declaration's path named, which lets the caller widen the scope deliberately rather than inherit a deletion nobody could check. +**A declaration, including an export or re-export statement, is not a use.** Report a copy under `REPEATED` only where the declaration sits inside the paths handed in **and** its body was opened this run. Where the declaration lies outside those paths, or inside them but unopened, report the copy as `UNRESOLVED` with the declaration's path. ```javascript // isBetaEnabled mirrors the beta-features flag. @@ -90,13 +90,13 @@ SYMBOL: `isBetaEnabled`. COMMENT: `isBetaEnabled mirrors the beta-features flag. ## List four: sentences a comment does not need -A comment can be true, non-repeated, and still carry sentences its reader does not need. This list covers **every** comment in the paths handed in: inline comments, documentation comments on declarations, and file, module, or package comments. Report a comment here when it holds at least one sentence of these three kinds: +A comment can be true, non-repeated, and still carry sentences its reader does not need. This list covers **every** comment in the paths handed in: inline comments, documentation comments on declarations, and file, module, or package comments. A public or exported symbol, a public structure member, or a package or module always keeps at least one sentence: keep the sentence saying what it does, or the first sentence when unclear. Correct a wrong or absent-content claim from the body, never delete that comment; if the body was not read, keep and report it. Report a comment here when it holds at least one sentence of these three kinds: -- a sentence restating the declaration or the code beneath it, such as a signature retold in prose or a straightforward conditional, loop, or assignment walked through step by step, other than the one sentence a public symbol's documentation comment gives to what the symbol does, which is required even where it restates the name or body; +- a sentence restating the declaration or the code beneath it, such as a signature retold in prose or a straightforward conditional, loop, or assignment walked through step by step, except for the public floor sentence, which is always kept even where it restates the name, code, or syntax; - a sentence listing or re-describing members that carry their own comments, which is how a package or type comment turns into a tour of the interface: each member's own comment, and the reference the language generates from them, already carry it; - a sentence narrating alternatives considered or reasoning walked through, where the code needs only the conclusion. -Report the comment's own sentences in two verbatim sets: `KEEP`, the sentences carrying something the code does not show, and `CUT`, each sentence of the three kinds above beside the code string or member comment it restates. The one sentence a public symbol's documentation comment gives to what the symbol does, and a sentence explaining a non-obvious internal, are always `KEEP`. Never write replacement text: a reworded or merged sentence is a new claim, and the caller writes any new wording from code it opens itself. A comment that is also wrong goes under `CONTRADICTED` as well, and the caller corrects it before cutting. +Report the comment's own sentences in two verbatim sets: `KEEP`, the sentences carrying something the code does not show, and `CUT`, each sentence of the three kinds above beside the code string or member comment it restates. For a public or exported symbol, public structure member, or package or module, `KEEP` is never empty: keep its summary sentence, or the first sentence if unclear. A wrong comment in this group is corrected from the body, never deleted; an unread body means keep and report it. A sentence explaining non-obvious internal logic is also always `KEEP`. Never write replacement text: the caller writes any new wording from code it opens itself. A wrong comment also goes under `CONTRADICTED`, and the caller corrects it before cutting. ```python # We need to check if the user is eligible for the discount. @@ -144,7 +144,7 @@ Each of these produces noise rather than a finding, so leave each one out of the - a comment that is merely terse, or plain, or worded differently from how a convention would word it: every list; - an internal helper whose name and signature already carry what it does: `UNDOCUMENTED`; - a missing comment on a binding inside a function body: `UNDOCUMENTED`; -- a comment sitting on a declaration: `REPEATED`, since a declaration is never a use, each member of a public structure carries its own comment, and a file-level header says what the file or package is for. The same comment is still read for `CONTRADICTED` and `VERBOSE`; +- a comment sitting on a declaration: `REPEATED`, since a declaration, including an export or re-export statement, is not a use. A member's comment and a file-level header are still read for `CONTRADICTED` and `VERBOSE`; - a type annotation restated in prose: `CONTRADICTED`, since it is a style question and not a contradiction, though under `VERBOSE` it is a restating sentence; - anything the agent could not open: every list, since it is reported as unread in the counts and never as a finding. @@ -189,7 +189,7 @@ BLOCKED BY: VERBOSE :: -KEEP: +KEEP: CUT: :: COUNTS diff --git a/.claude/skills/audit-docs/assets/audit-report.template.md b/.claude/skills/audit-docs/assets/audit-report.template.md index 2313c53..4587724 100644 --- a/.claude/skills/audit-docs/assets/audit-report.template.md +++ b/.claude/skills/audit-docs/assets/audit-report.template.md @@ -85,7 +85,7 @@ Undocumented public symbols outside the change, listed rather than documented: [ | `[path]` | [documented [count] previously undocumented public symbols] | | `[path]` | [created, [tutorial / how-to guide / reference / explanation]] | -Kinds to choose from: made the change the request asked for, 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, cut sentences from a comment, removed a duplicated section, introduced a term on first use, added orientation for a first-time reader, created, and, only where the request asked or the user approved it, renamed, moved, split, merged, or deleted. +Kinds to choose from: made the change the request asked for, corrected a factual statement, documented a public symbol, retained a public documentation comment at its one-sentence floor, corrected an existing documentation tag, removed an outdated or restating internal comment, removed a comment repeated above a usage site, cut sentences from a comment, removed a duplicated section, introduced a term on first use, added orientation for a first-time reader, created, and, only where the request asked or the user approved it, renamed, moved, split, merged, or deleted. (One row per file, not one per edit. If no file changed, replace the table with "No files changed.") @@ -122,6 +122,7 @@ This run edits documentation, so a code defect is reported here and left alone. - Every phase carries a status line, including a phase whose answer is that the documentation was already accurate. - Every symbol reported in Phase 3 as left as it stands because its implementation was not read also appears under Unverified. +- No documentation comment on a public or exported symbol, public structure member, package, or module was deleted or cut below one sentence. - Every file changed is inside the scope, and none was renamed, moved, split, or deleted unless the request asked for it or the user approved it. - Every file named in a phase result appears in the files changed table, and every row of that table is a file that was edited. - The file count in the summary matches the number of rows in the table. diff --git a/.claude/skills/audit-docs/references/evidence-and-citation.md b/.claude/skills/audit-docs/references/evidence-and-citation.md index 39cf330..d1237da 100644 --- a/.claude/skills/audit-docs/references/evidence-and-citation.md +++ b/.claude/skills/audit-docs/references/evidence-and-citation.md @@ -54,6 +54,8 @@ Delegation needs care. When the symbol forwards to another, the proof lives in t An absence claim ("does not validate the payload", "no retry on a 4xx") cannot be proved by copying one string, because the evidence is a branch that is not there. Ground it by enumerating the full set of branches and quoting the boundary that closes the set: the final `else`, the `default` case, the end of the match, or the last statement of the body. Then search for anything else that writes the same path (a subclass, an override, middleware, a decorator, a registered hook, generated code) and confirm none of them supplies the behaviour you are calling absent. Record the search you ran next to the quote. If the set cannot be closed, because dispatch is dynamic or the handler list is assembled at run time, the claim goes under "Unverified" instead of on the page. +A comment with no corresponding line also makes an absence claim. Prove the absence across its full scope before treating the comment as false; on a public or exported symbol, correct it from the body rather than deleting it. + ## Provable is not the same as worth writing Grounding decides whether a statement **may** be written. It never decides that it **should** be, and it never decides how many times. Holding proof for one fact is proof about one fact, not a licence to state it at every site where it happens to be true. 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 2c18347..6764720 100644 --- a/.claude/skills/audit-docs/references/voice-and-ai-tells.md +++ b/.claude/skills/audit-docs/references/voice-and-ai-tells.md @@ -91,10 +91,10 @@ Treat a hedge as an accuracy failure first and a voice failure second. `appears ## Prose that restates the code instead of adding to it -Cut any sentence a reader could reconstruct from the declaration. Prose earns its place by carrying what a signature cannot: why the thing exists, what the caller owes it, what happens at the boundary, and what a value means at its limits. The exception is consumer-facing reference material, whose readers cannot open the source, so stating what the function does is the entire job, and a public symbol's documentation comment, whose one sentence saying what the symbol does stays even where it restates the name: the pairs below show how to write that sentence, not a reason to cut it. +Cut any sentence a reader could reconstruct from the declaration, except the one-sentence floor on a public symbol's documentation comment. That sentence stays as written even when it restates the name, code, or syntax. The pairs below distinguish that existing floor from additional documentation to write only when the body proves it. ```python -# Restates the signature: +# The floor, kept as written: def set_timeout(seconds: int) -> None: """Sets the timeout to the given number of seconds.""" @@ -106,9 +106,9 @@ def set_timeout(seconds: int) -> None: ``` ```go -// Restates: Close closes the writer. -// Adds: Close flushes buffered rows before releasing the file handle, and -// a write after Close returns ErrClosed rather than panicking. +// Floor, kept as written: Close closes the writer. +// Written new: Close flushes buffered rows before releasing the file handle, +// and a write after Close returns ErrClosed rather than panicking. func (w *Writer) Close() error { flushErr := w.flush() w.closed = true diff --git a/.claude/skills/typescript-code-and-test-standards/SKILL.md b/.claude/skills/typescript-code-and-test-standards/SKILL.md index eff2064..154466c 100644 --- a/.claude/skills/typescript-code-and-test-standards/SKILL.md +++ b/.claude/skills/typescript-code-and-test-standards/SKILL.md @@ -21,7 +21,7 @@ The host project's own tooling owns everything it is **configured** to check, an **A rule the project configured off is a decision. A rule it never configured is silence, and silence cedes nothing.** A linter running two rules has taken no position on the other thousand, so reading its quiet as a verdict is how an entire category goes unreviewed while every command exits 0. -This skill owns comments, documentation blocks, readability judgement, naming, structure, the test mandate, and mocking. Structure belongs here because no tool checks it: a formatter will lay out a two-thousand-line file and a linter will pass a twenty-member interface, so file length, interface size, directory shape, and repeated logic reach a reader only if someone counts them. It reports and follows configuration. **It never creates or edits a configuration file to make a project match itself**, which is not the same as setting a value the change itself requires at the level the tool reads it. +This skill owns comments, documentation blocks, readability judgement, naming, structure, the test mandate, and mocking. It reports and follows configuration. **It never creates or edits a configuration file to make a project match itself**, which is not the same as setting a value the change itself requires at the level the tool reads it. **What makes a finding this skill's**, and the test that keeps it from drifting into a review it cannot do: you can show the code is wrong by pointing at the language, the runtime's documented behaviour, or the type system, **without knowing what the program is for**. A `for...in` over an array, an array-spread of a non-iterable, a `parseInt` with no radix, an `as` that lies: each is provable from TypeScript or JavaScript alone. A wrong threshold, a wrong business rule, a wrong status code: each needs the intended behaviour to prove it, and this skill does not have that. So every finding names the language fact behind it, and one that would read identically against a file in another language has been written at the wrong altitude. @@ -59,7 +59,7 @@ Writing new code, reviewing a diff, and fixing a failing test are different jobs 1. Detect the project if you have not already. 2. Before writing a function whose behaviour has a name outside this project, run the three-source lookup in **Reuse** below. It is cheaper before the code exists than after. 3. Write to the detected formatting and let the formatter own layout. Do not hand-align anything a formatter will rewrite. **A formatter collapses blank lines and strips them at block edges but never inserts a separating one, and it leaves an unbraced single-statement `if` at whatever width it was written.** Neither grouping nor width is settled by running it, so both stay yours: see **Readability and naming** below. -4. Give every exported symbol a documentation block before moving on, including the members of exported structures. See **Documentation blocks** below. +4. Keep a documentation block on every exported symbol, including members of exported structures, and never delete or cut one below its public floor. See **Documentation blocks** below. 5. Reread every comment you wrote and delete any that narrates the change rather than describing the code. 6. If the change is logic, a bug fix, or a feature, its test lands in the same change. If it is a pure rename, move, or refactor, add no test and weaken none. 7. Run the detected format, lint, type check, and test commands, and confirm the exit codes. Reading the output is not confirming the exit code. @@ -90,7 +90,7 @@ Writing new code, reviewing a diff, and fixing a failing test are different jobs - A comment that narrates a change, explains why something was removed, or argues the code is correct or safe. - A missing or wrong documentation block on an exported symbol, and a block carrying sentences that restate the signature, beyond the one saying what the symbol does, or re-describe members, including a file-level block touring its exports. - - An existing documentation tag stripped or reworded. Deleting an accurate tag is itself a defect, not tidying. + - An existing documentation tag stripped or reworded. Deleting an accurate tag is itself a defect, not tidying. Deleting a public documentation block or cutting it below its floor is also a defect. - Commented-out code, and any deleted tooling directive. A directive removed because its setting moved to the configuration the tool reads is not this finding. **Tests and mocks.** @@ -113,20 +113,20 @@ Writing new code, reviewing a diff, and fixing a failing test are different jobs ## Comments - **Comments describe the code as it stands.** Never narrate a change, a fix, or a prior state ("now uses", "changed to", "previously", "no longer", "restored"). Version control carries that, and the comment outlives the change that prompted it. -- **Name the line the comment describes**, which is the test a phrase list cannot replace. Point at the code beneath the comment that it is about; a comment you cannot attach to a line is not a comment about this code. It catches the case the list above misses, a comment explaining an **absence**: why something was removed, why an approach was not taken, what an earlier version did. Nothing in the file corresponds to it, because its subject is a decision, and the reader who wants that decision is reading the commit or the pull request where the diff proving it lives. _Bad:_ `// Removed the retry wrapper here since the SDK retries internally.` _Good:_ nothing, with that sentence in the commit message. +- **Name the line the comment describes.** Point at the code beneath it; where no line corresponds, the comment describes a decision, not this code. _Bad:_ `// Removed the retry wrapper here since the SDK retries internally.` For an internal comment, remove it; on an exported symbol, correct the block from its body instead. - **Never argue that the code is correct or safe.** A note defending a decision documents the edit rather than the code. Say what something does or why it exists; do not justify that it works. - A comment that contradicts the code is **corrected, not deleted**. When the two disagree, the code is the truth. - Delete commented-out code rather than leaving it in place. - Inside a function body, a comment restating the line beneath it is noise. Delete those, and keep anything carrying a constraint, hazard, or non-obvious behaviour. On a public surface, one sentence saying what a symbol does is not a defect even where its name says so too; a block restating the signature at length is (see **Documentation blocks**). -- **A fact about a symbol is documented once, on its declaration.** Never repeat it above the lines that read, call, or branch on that symbol: `// isBetaEnabled mirrors the beta-features flag` belongs on the declaration of `isBetaEnabled`, not above each `if (isBetaEnabled)`. Each member of an exported structure is its own declaration and keeps its own block; a usage site is not one. Where a copy above a use carries a constraint the declaration does not, fold that into the declaration rather than leaving both. +- **A fact about a symbol is documented once, on its declaration.** An export or re-export statement is a declaration, not a use. Never repeat the fact above a line that reads, calls, or branches on the symbol: `// isBetaEnabled mirrors the beta-features flag` belongs on its declaration, not above each `if (isBetaEnabled)`. Each member of an exported structure is its own declaration and keeps its own block. Fold a constraint from a use-site copy into the declaration. - **Never delete a tooling directive.** `//@ts-check`, `/// `, `// @ts-expect-error`, `eslint-disable`, `biome-ignore`, `istanbul ignore`, and `prettier-ignore` are instructions to a tool, not commentary. **Moving one is not deleting it.** Where the same directive repeats across files and the tool reads that same setting from its own configuration, setting the key once and removing the copies relocates the instruction rather than discarding it, and the number of copies removed goes in the change. What this rule forbids is stripping a directive during work that had no reason to touch it. - Use `//` for implementation notes, and consecutive `//` lines for a multi-line note. No `/* */` block inside a function body, with one exception: naming an argument at a call site, `someFunction(/* shouldRender= */ true)`. ## Documentation blocks -- **Every exported symbol carries a documentation block**, and so do the members of an exported structure: interface properties, object keys, enum values. **Write it from the body, never from the name, and keep it short:** one sentence saying what the symbol does, written even where the name makes that obvious, since being obvious is not a defect on a public surface and being absent is. Add a second only for an error it throws, a constraint on its input, or an obligation on its caller that the body or a test proves, and stop there. Write no reason or invariant you cannot point to in the code. Where the body cannot be read, leave the symbol undocumented and say so. +- **Every exported symbol carries a documentation block**, as do members of an exported structure: interface properties, object keys, and enum values. Never delete or cut below one sentence a block on an exported symbol, an exported structure member, or a package or module. Keep the sentence saying what it does, even where it restates the name, code, or syntax; where unclear, keep the first sentence. Correct a wrong or absent-content claim from the body. If the body was not read, keep the comment and report it. Write new blocks from the body, never the name: one sentence saying what the symbol does, and a second only for an error, input constraint, or caller obligation the body or a test proves. Write no reason or invariant you cannot point to in the code. - **A file-level block** (`@file`, `@fileoverview`, `@packageDocumentation`) **is one sentence saying what the module is for.** It never lists or re-describes exports that carry their own blocks. -- **Cut a verbose block by whole sentences, and grow an accurate one only by a fold.** Delete each sentence that restates the signature or the code beneath it, re-describes members carrying their own blocks, or narrates alternatives or reasoning where the code needs only the conclusion, and keep every other sentence word for word, the one saying what the symbol does included; where a kept sentence leans on a cut one, keep both or cut both. The one prose sentence an accurate block may gain is a constraint folded in from a copy above a use (see **Comments**). A reworded or merged sentence is a new claim that needs the body behind it. This governs prose sentences; tags follow the rule on existing tags below. +- **Cut a verbose block by whole sentences, above the public floor.** Keep every other sentence word for word, and keep or cut together sentences that depend on one another. A reworded or merged sentence is a new claim that needs the body behind it. Tags follow the rule on existing tags below. - A private helper gets a block when its behaviour is not evident from its name and signature. A binding declared inside a function body does not. - **Types in a documentation block depend on whether the file is type-checked.** In a file TypeScript checks, omit `@param {string}`, `@returns {number}`, `@type`, and `@typedef`: the compiler already carries the type, so the annotation becomes prose that drifts from the signature. **In a plain JavaScript file where the documentation block is the type system**, those annotations are load-bearing and stay. Check `tsconfig.json`, `jsconfig.json`, and any `//@ts-check` directive before removing one. - **Leave existing tags alone unless they are wrong.** A tag already in the tree was added deliberately, annotation and all. Read the surrounding code, correct what is factually wrong, and change nothing else: do not strip a `{type}` annotation, reword accurate prose, or delete a tag for looking redundant. Delete one only when it is wrong and uncorrectable, such as documenting a parameter the signature no longer has. @@ -161,7 +161,7 @@ Prefer the readable form wherever it costs nothing at runtime, and wherever the - **Occurrences of a repeated block.** Two may be coincidence; three is a pattern, named with all three paths. This counts test cases too: three differing only in a value are one table-driven case, and cases that cannot fail independently of one another are one case wearing three names. - **Files repeating one declaration**, meaning a setting, directive, suppression, or bootstrap import written into each file rather than into the configuration the tool reads. Count the files and name the key. **Look for the key, not for the directive's own spelling**, since the two are rarely the same word: a per-file test environment docblock against the runner's environment key, a per-file suppression comment against the linter's per-glob ignore map, a per-file build constraint against the build configuration's default. - **Parameters of each function**, split into those supplying data and those switching behaviour, against the other functions in the same module. A switch is a parameter the body branches on rather than operates on, whatever its type, and each one holds a second behaviour inside one name. It is the defect in a utility, meaning a function named for one operation, exported for general use, sitting where shared code sits, or having callers that do not know about each other. A function coordinating a sequence takes its modes legitimately. -- **The shape of each function body**, as three numbers taken together: lines in the body, deepest nesting level, and the widest single expression a reader must hold at once. The six counts above measure everything around a function, the file it sits in, the type it takes, the directory it lives in, the signature it presents, and none of them reaches inside one, so a three-hundred-line body nested seven deep passes every one of them. It sits here rather than with the line-by-line reading because a long body is the rare defect that is visible on every line and walked past on all of them: no single line says the body is long, which is the same failure the other six counts exist for. +- **The shape of each function body**, as three numbers taken together: lines in the body, deepest nesting level, and the widest single expression a reader must hold at once. The six counts above measure everything around a function, and none reaches inside one. **A count is a trigger to look, never a finding.** What makes it one is the count plus the concrete split: which members go into which type, which files into which subdirectory, what the shared unit would hold, which key carries the declaration. Where the outlier test finds nothing because every sibling is equally large, fall back to a file past 600 lines, a type past 15 members, more than 20 files sitting directly in a directory, a block repeated three times, one declaration repeated in three files, more than one behaviour-switching parameter on a utility, or a function body past 50 lines, nested past three levels, or holding a condition of more than about three logical operators or wider than the project's own print width. Those numbers are the point where a reader stops holding the unit in their head at once, and they are approximate on purpose. diff --git a/.claude/skills/typescript-code-and-test-standards/agents/comment-and-jsdoc-auditor.md b/.claude/skills/typescript-code-and-test-standards/agents/comment-and-jsdoc-auditor.md index 1698fa7..4f09462 100644 --- a/.claude/skills/typescript-code-and-test-standards/agents/comment-and-jsdoc-auditor.md +++ b/.claude/skills/typescript-code-and-test-standards/agents/comment-and-jsdoc-auditor.md @@ -70,11 +70,11 @@ Report a tag only when it is **factually wrong**: it describes a parameter the s ## Comment rules to check - **Narrates a change.** Any comment about a change, a fix, or a prior state: "now uses", "changed to", "updated to", "previously", "no longer", "restored", "switched from", or any paraphrase. Finding. -- **Describes something the file does not contain.** Run the test on every comment, because the phrase list above catches only the comments that announce themselves: name the line beneath the comment that it is about. A comment you cannot attach to a line is not a comment about this code, and the usual case is one explaining an absence, meaning why something was removed, why an approach was rejected, or what an earlier version did. Quote it and say what it names that is not in the file. Finding, and the fix is deletion, since its subject is a decision and the reader wanting that decision is reading the commit. Two comments that survive this test and are never findings: a note about a deliberate omission the code depends on, such as why a field is absent from a payload the caller must not send, and a file-level header, which describes the file rather than any one line. +- **Describes something the file does not contain.** Name the line beneath the comment; where no line corresponds, it describes a decision, not this code. Quote it and name what it says is absent. For an internal comment, the fix is deletion; on a public or exported symbol, public structure member, package, or module, correct it from its body instead. Two comments that pass this test are a note about a deliberate omission the code depends on and a file-level header. - **Argues the code is correct or safe.** A comment defending a decision or asserting that something works documents the edit rather than the code. Finding. - **Contradicts the code.** Finding, and the fix is to **correct the comment, not delete it**. The code is the truth; the mismatch is often the most interesting thing in the file. - **Restates the line beneath it**, inside a function body. Finding, and the fix is deletion. On a public surface, one sentence saying what the symbol does is not a finding even where its name says so too. -- **A documentation block carrying sentences its reader does not need.** A sentence restating the signature or the code beneath it, other than the one sentence saying what an exported symbol does, a sentence listing or re-describing members that carry their own blocks (the usual shape of a verbose file-level block), or a sentence narrating alternatives or reasoning where the code needs only the conclusion. Finding. Quote each such sentence and the sentences that stay; the fix deletes whole sentences and rewords none. Tags follow the leave-it-alone rule above and are never part of this finding. +- **A documentation block carrying sentences its reader does not need.** A sentence restating the signature or code beneath it, other than the public floor sentence, a sentence re-describing documented members, or a sentence narrating alternatives where the code needs only the conclusion. Finding. Quote the sentences to cut and keep. A public or exported symbol, public structure member, package, or module always has a non-empty `KEEP`: the sentence saying what it does, or the first sentence when unclear. Correct a wrong comment from the body, never delete it; if the body was not read, keep and report it. Cut whole sentences and reword none. Tags are not part of this finding. - **Commented-out code.** Finding, and the fix is deletion. - **Markdown link syntax inside a documentation block.** `[text](url)`, and worse `[name](#anchor)`, which renders as dead text in a hover tooltip. Finding. The fix is `{@link SymbolName}`, `@see https://example.com`, or `{@link https://example.com Display text}`. - **A block comment inside a function body.** Finding, with one exception: naming an argument at a call site, `someFunction(/* shouldRender= */ true)`. diff --git a/.claude/skills/typescript-code-and-test-standards/assets/copilot-instructions.template.md b/.claude/skills/typescript-code-and-test-standards/assets/copilot-instructions.template.md index e5e2893..7c5ddb4 100644 --- a/.claude/skills/typescript-code-and-test-standards/assets/copilot-instructions.template.md +++ b/.claude/skills/typescript-code-and-test-standards/assets/copilot-instructions.template.md @@ -17,6 +17,7 @@ Everything below is what those tools cannot check. ## Comments - **Comments describe the code as it stands.** Never narrate a change, a fix, or a prior state ("now uses", "changed to", "previously", "no longer", "restored"). Version control carries that, and the comment outlives the change that prompted it. +- **Name the line a comment describes.** Where no line corresponds, correct a public or exported symbol's comment from its body; delete an internal comment on that ground. Never delete or cut below one sentence the comment on a public or exported symbol, a public structure member, or a package or module. Keep the sentence saying what it does, even where it restates the name, code, or syntax; where unclear, keep the first sentence. If the body was not read, keep the comment and report it. - **Never argue that the code is correct or safe.** A note defending a decision documents the edit rather than the code. - A comment that contradicts the code is **corrected, not deleted**. The code is the truth. - Delete commented-out code. @@ -26,9 +27,9 @@ Everything below is what those tools cannot check. ## Documentation blocks -- **Every exported symbol carries one**, and so do the members of an exported structure: interface properties, object keys, enum values. Keep it short: one sentence saying what the symbol does, and a second only for an error, a constraint, or a caller obligation the body or a test proves. Being obvious is not a defect on a public surface; being absent is. +- **Every exported symbol carries one**, and so do members of an exported structure: interface properties, object keys, enum values. Never delete the block or cut it below one sentence. Keep the sentence saying what the symbol does; where unclear, keep the first sentence. Correct a wrong or absent-content claim from the body; if the body was not read, keep and report the block. Write new blocks from the body: one sentence saying what the symbol does, and a second only for an error, constraint, or caller obligation the body or a test proves. - **A file-level block is one sentence saying what the module is for**, and never lists or re-describes exports that carry their own blocks. -- **Cut a verbose block by whole sentences, and grow an accurate one only by folding in a constraint from a copy above a use.** Delete each sentence that restates the signature or the code, re-describes members, or narrates alternatives or reasoning where the code needs only the conclusion; keep every other sentence word for word, including the one saying what the symbol does. Tags follow the rule on existing tags. +- **Cut a verbose block by whole sentences, above the public floor.** Keep every other sentence word for word. Tags follow the rule on existing tags. - **Write it from the implementation, never from the symbol's name.** If the body cannot be read, leave the symbol undocumented and say so. A block invented from a name is how drift starts. - **Types depend on whether the file is type-checked.** In a file the compiler checks, omit `@param {string}`, `@returns {number}`, `@type`, and `@typedef`: the compiler carries the type and the annotation drifts. In a plain JavaScript file where the documentation block **is** the type system, those annotations are load-bearing and stay. Check `tsconfig.json`, `jsconfig.json`, and any `//@ts-check` directive first. - **Leave existing tags alone unless they are factually wrong.** Do not strip a `{type}` annotation, reword accurate prose, delete a tag for looking redundant, or reorder tags. Delete one only when it is wrong and uncorrectable, such as documenting a parameter the signature no longer has. diff --git a/.claude/skills/typescript-code-and-test-standards/references/comments-and-jsdoc.md b/.claude/skills/typescript-code-and-test-standards/references/comments-and-jsdoc.md index 1893653..c11c1a7 100644 --- a/.claude/skills/typescript-code-and-test-standards/references/comments-and-jsdoc.md +++ b/.claude/skills/typescript-code-and-test-standards/references/comments-and-jsdoc.md @@ -84,7 +84,7 @@ Say what something does, or why it exists. Do not justify that it works. **When a comment contradicts the code, the code is the truth and the comment is corrected.** Deleting it loses whatever the comment was reaching for, and the mismatch is often the most interesting thing in the file: it usually means either the comment described an intent the code abandoned, or the code drifted from a constraint that still holds. -Delete a comment only when it restates the line beneath it, or when it is commented-out code. +Delete an internal comment only when it restates the line beneath it or is commented-out code. Never delete or cut below one sentence a public or exported symbol's comment, a public structure member's comment, or a package or module comment. Keep the sentence saying what it does, even if it restates the name, code, or syntax; where unclear, keep the first sentence. Correct a wrong or absent-content claim from the body. If the body was not read, keep the comment and report it. ## Comments that restate the line beneath them diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index 8477244..5bf7f8b 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -163,9 +163,9 @@ Not adopted: the ban on default exports (this repository uses them for the modul ### Comments & JSDoc -- **Comments describe the code as it stands.** Never narrate a change, fix, or prior state ("now uses", "previously", "no longer", "restored", "replaces", "used to", "formerly", "for the first time"), and never name a file, flag, or tool that no longer exists; git history carries that. Never argue that the code is correct or safe, which documents the edit rather than the code. **Name the line each comment describes**, and delete it where no line does: that catches what the phrase list misses, a comment explaining an absence such as why something was removed, whose subject is a decision and whose reader is looking at the PR. A note about a deliberate omission the code depends on, and a file header, both pass. Delete commented-out code. A comment contradicting the code is corrected, not deleted -- **Every exported symbol carries a `/** */` block**, as do the members of an exported structure (interface properties, object keys, enum values). Write it from the body and keep it short: one sentence saying what the symbol does, and a second only for an error, a constraint, or a caller obligation the body or a test proves. Being obvious is not a defect on a public surface, being absent is. A file-level block is one sentence saying what the module is for and never tours its exports. Cut a verbose block by whole sentences, keeping the rest word for word, and grow an accurate one only by folding in a constraint from a copy above a use; tags follow the rule on existing tags below -- A private helper gets a block when its name and signature do not carry it; a binding inside a function body does not, and a comment there that restates the next line is noise. **A fact about a symbol is stated once, on its declaration**, never repeated above the lines that read, call, or branch on it: `isBetaEnabled mirrors the beta-features flag` belongs on the declaration, not above each `if (isBetaEnabled)`. A member of an exported structure is its own declaration; a usage site is not. Removing an existing copy is narrower than writing a new one: delete it only where the declaration is in the same change you are reviewing and you have read it, since a symbol is used far from where it is declared and following a usage site to its declaration is how a comment sweep reaches code nobody touched. Otherwise leave both and say so +- **Comments describe the code as it stands.** Never narrate a change, fix, or prior state ("now uses", "previously", "no longer", "restored", "replaces", "used to", "formerly", "for the first time"), and never name a file, flag, or tool that no longer exists. Never argue that the code is correct or safe. **Name the line each comment describes.** Where no line corresponds, correct a public or exported symbol's comment from its body; delete an internal comment on that ground. Never delete commented-out code. A comment contradicting the code is corrected, not deleted. A note about a deliberate omission the code depends on, and a file header, pass this test. +- **Every exported symbol carries a `/** */` block**, as do members of an exported structure (interface properties, object keys, enum values). Never delete the block or cut it below one sentence. Keep the sentence saying what the symbol does, even where it restates the name, code, or syntax; where unclear, keep the first sentence. Correct a wrong or absent-content claim from the body. If the body was not read, keep the block and report it. Write new blocks from the body: one sentence saying what the symbol does, and a second only for an error, constraint, or caller obligation the body or a test proves. A file-level block says what the module is for and never tours its exports. Cut other sentences by whole sentences; tags follow the rule on existing tags below. +- A private helper gets a block when its name and signature do not carry it; a binding inside a function body does not, and a comment there that restates the next line is noise. **A fact about a symbol is stated once, on its declaration.** An export or re-export statement is a declaration, not a use. Never repeat a fact above a line that reads, calls, or branches on the symbol. A member of an exported structure is its own declaration. Remove a copy above a use only where both sites are in scope, the declaration's body was read, and the copy says no more than the declaration's comment; fold any additional constraint into the declaration first. Otherwise leave both and say so. - **In a block you write, do not put types in JSDoc.** TypeScript ignores `@param {string}`, `@returns {number}`, `@type`, and `@typedef` in `.ts`/`.tsx`, so they drift from the signature. Skip `@implements`, `@enum`, `@private`, and `@override` beside the keyword, and add `@param`/`@returns` where they say more than the name and type do - **Leave existing tags alone unless wrong.** A `@param`/`@returns` already in the tree was added deliberately, annotation and all. Read the surrounding code, fix what is factually wrong, change nothing else: do not strip a `{type}`, reword accurate prose, or delete a tag for looking redundant. Delete only when wrong and uncorrectable, such as documenting a parameter the signature no longer has - `@throws`, `@example`, `@deprecated` (naming its replacement), and `@see` are encouraged: none are expressible in the type system. Open a block with a third-person verb phrase; one tag per line; bodies are Markdown diff --git a/.github/prompts/audit-docs.prompt.md b/.github/prompts/audit-docs.prompt.md index 16780a8..dded1e8 100644 --- a/.github/prompts/audit-docs.prompt.md +++ b/.github/prompts/audit-docs.prompt.md @@ -46,16 +46,16 @@ Act as a **Strictly Factual Technical Writer and Auditor**. Make the project's d ### Phase 3: in-code documentation audit -Phase 3 runs on every audit, whatever Phases 1 and 2 found, over the code files in scope; where there are none, say so. It corrects what is wrong, documents what is absent in scope, and cuts what a comment does not need; every sentence it keeps stays as written. +Phase 3 runs on every audit, whatever Phases 1 and 2 found, over the code files in scope; where there are none, say so. A floor holds over every cutting and deletion rule below: the comment on a public or exported symbol, a public structure member, or a package or module is never deleted or cut below its sentence saying what it does, even where that sentence restates the name, code, or syntax. Correct a wrong or absent-content claim from the body; when the body was not read, keep the comment and report it. Phase 3 corrects what is wrong, documents what is absent in scope, and cuts what a comment does not need. - **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. On a pull request or working changes, write a missing comment only for a symbol the diff added or changed, and list the other undocumented symbols in those files in your output. -- **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 each public symbol in scope that lacks it; cut bloat. **Cutting works by whole sentences.** In any comment in scope, documentation and file or package comments included, delete each sentence that restates the declaration or the code beneath it, lists or re-describes members carrying their own comments, or narrates alternatives or reasoning where the code needs only the conclusion, and keep every other sentence word for word, and where a kept sentence leans on a cut one, keep both or cut both; a comment made of nothing else goes. Being accurate is not a reason a sentence stays; the one sentence saying what a public symbol does (next bullet) and a sentence explaining a non-obvious internal always stay. A reworded or merged sentence is a new claim needing its own proof (Rule 2). A comment both wrong and long is corrected first, then cut. +- **Actions:** scan comments, read the implementation, and correct inaccuracies; remove inaccurate internal comments and orphaned TODOs. Cut bloat above the public floor, then document each public symbol in scope that lacks a comment. **Cutting works by whole sentences.** Cut sentences that restate code, re-describe documented members, or narrate alternatives or reasoning where the code needs only the conclusion. Keep other sentences word for word; keep or cut together sentences that depend on one another. A comment made of nothing else goes entirely unless the floor holds it. A public summary and a sentence explaining non-obvious internal logic always stay. Reworded or merged sentences are new claims needing proof (Rule 2). Correct a wrong, long comment before cutting it. - **Document the public surface in scope, from the body.** Every public or exported symbol in scope carries a documentation comment, as do the members of a public structure: fields, properties, keys, enum values. Open the body and write the comment from it, in the language's conventional form (a Go comment opens with the symbol's name, a Python docstring with a one-line summary): one sentence saying what the symbol does, even where the name makes that obvious, since being obvious is not a defect on a public surface and being absent is. A second sentence is allowed only for an error, constraint, or caller obligation that a body line, test assertion, configuration value, or decision record opened this run proves, and a comment you write stops there; an accurate existing comment gains no sentence except a fold or a deprecation's replacement (below). Write no reason or invariant you cannot quote. A member's comment states what code in scope that sets or reads it shows. **Rule 2 still governs, and it comes first.** Not having got to the body is no reason to skip the symbol, and being unable to reach it is no reason to guess. Where you have not read the body, leave the symbol and name it in your output: undocumented and reported is compliant, and a comment written from the name is the defect. -- **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 an existing comment such a sentence is cut like any restatement, while a tag follows the next bullet. +- **Do not restate what the language's own syntax declares.** This governs what you write; in an existing comment, cut such a sentence only above the public floor. A tag follows the next bullet. - **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**, never above the lines that read it, call it, or branch on it; a fact spanning more than one symbol goes in `docs/`. A declaration is not a use: a member's own comment and a file-level header are never removed as repetitions. **Removing a copy above a use needs all three:** the declaration and the use both sit in scope; the declaration's body was opened this run; and the copy says no more than the declaration's comment. Then keep the fact on the declaration, writing it there if absent, and delete the copy, the one case where an accurate comment is removed; a constraint only the copy carries is folded into the declaration first. Failing any of the three, leave both and report it. -- **Comments describe the code as it stands (Rule 4); the test is to name the line.** Delete commented-out code. Point at the code the comment is about; where nothing corresponds, as with a comment explaining why something was removed, why an approach was rejected, or what an earlier version did, its subject is a decision that belongs in the commit or change request, so delete it. Two comments pass 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 a line. +- **A fact is documented once, at its declaration.** An export or re-export statement is a declaration, not a use. Remove a copy above a use only when the declaration and use are in scope, the declaration's body was read, and the copy says no more than its comment; fold any additional constraint into the declaration first. Otherwise leave both and report the copy. +- **Comments describe the code as it stands (Rule 4); name the line.** Delete commented-out code. Where no line corresponds, correct a public or exported symbol's comment from its body; delete other comments on that ground. A note about a deliberate omission the code depends on and a file-level header also stay. - **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. 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, where the body shows them:** 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. Name an error or duty only where the body or a callee you opened returns, raises, or requires it, and call a list complete only when every path was read. A deprecation without migration directions is incomplete; add one only where it is provable under Rule 2. - **File and package comments:** where the language has one, it says in one sentence what the file or package is for, in its conventional form (a Go package comment opens `Package auth`); a large package may add one naming the few entry points a caller starts from. It never lists or re-describes members carrying their own comments, which generated reference already lists, and is written only once every file it spans was read. Maintainer notes go with the implementation. @@ -83,7 +83,7 @@ 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. 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.** 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. -**A claim spanning several files is grounded the same way, from each of them.** An orientation sentence often rests on a handful of files rather than one line, so hold a quote from every file carrying a part of it, a package comment included, before writing the sentence. Cover the system rather than only the sentence: look for the file that would contradict it, since a synthesis is refuted by what it leaves out. +**A claim spanning several files is grounded the same way, from each of them.** An orientation sentence rests on several files, so hold a quote from every file carrying a part of it, a package comment included. Look for files that could contradict the claim. **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". Never document planned or intended behaviour. For complex behaviour, confirm against two or three locations (definition, usage, test). @@ -122,7 +122,7 @@ Judge each document you edit in full as a whole against what a careful human wou ### Voice -Write as a careful human technical writer: formal and neutral, never robotic. The robotic feel comes from the tells below, not from a formal register, so cut the tells and keep the register. +Write as a careful human technical writer: formal and neutral. - **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"); puffery copulas ("serves 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, foster, seamless. Keep a word when it is factually correct in context (a test `harness`). @@ -133,13 +133,13 @@ Write as a careful human technical writer: formal and neutral, never robotic. Th ### Brevity & style - Use prose to carry reasoning (the _why_ and _how_); reserve bullets and numbered lists for genuine enumerations (steps, options, fields, parameters). Do not force explanation into parallel bullet fragments, and do not de-list a real list: enumerations stay lists, scannable for people and easy to retrieve. No walls of text. **Concise, not choppy:** no line-by-line narration, but keep the connective prose that carries logic. Lead each paragraph and section with its point, then give the detail. -- **Tables only for uniform data scanned quickly**, meaning many parallel items with distinct attributes. If columns repeat across rows, cells sit empty, or a cell holds a sentence of prose, use a list with sub-headings instead. +- **Tables are for uniform data.** Use a list with sub-headings when columns repeat, cells are empty, or a cell holds prose. ### Language - **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. When an audit edits a document in full, replace that document's existing em-dashes and en-dashes the same way; do not sweep any other file. - **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 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, then the bare acronym after. Keep exact casing in three cases: an established brand, tool, or package name (npm, ESLint), an intentional domain term (mRNA), and a direct code reference (a method, field, env var, or config key 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 you learned from this project's code needs introducing, however obvious it now feels. Spell a concept one way across the documents you edit in full, since a concept spelled three ways is three concepts to anyone meeting it, and defeats their search. +- **Acronyms and terms of art.** In prose you write or edit, capitalize acronyms and give the full term on first use per document. Keep exact casing for established names and direct code references. Introduce an unfamiliar term where the document first uses it, then use it unchanged. Introduce terms learned from this project's code, however obvious they now feel. Spell each concept consistently across documents edited in full. ### Configuration references @@ -152,7 +152,7 @@ Write as a careful human technical writer: formal and neutral, never robotic. Th - **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. +- Verify every path and anchor resolves. If a referenced file or heading does not exist, correct or remove the statement. ### Code snippets @@ -211,7 +211,8 @@ Then confirm: - 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 an existing home or a directory whose documents are the same Diátaxis type, and your output names 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. -- No comment in scope, documentation and file or package comments included, restates its declaration beyond a public symbol's one-sentence summary, re-describes members carrying their own comments, or narrates alternatives or reasoning where the code needs only the conclusion, and every cut removed whole sentences and left the rest word for word. +- No comment in scope, documentation and file or package comments included, restates its declaration above the public floor, re-describes members carrying their own comments, or narrates alternatives or reasoning where the code needs only the conclusion, and every cut removed whole sentences and left the rest word for word. +- No documentation comment on a public or exported symbol, public structure member, package, or module was deleted or cut below one sentence; wrong or absent-content claims were corrected from the body, and comments whose bodies were not read were kept and reported. - Every document you edited in full is no longer, as a whole, than a careful human would have written (Rule 6), and any split or move you judged necessary was asked about or proposed, not made. - A passage you wrote that stayed long after Rule 6 was weighed for a complementary diagram, table, snippet, or image, and one was added where it fit. - Phase 3 ran and its result is reported. diff --git a/CLAUDE.md b/CLAUDE.md index 02aa303..eee4dbd 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -17,7 +17,7 @@ This repo is worked on by **both** GitHub Copilot and Claude Code. Keep these au ### Documentation and comments describe the current state -**This applies to every file type, not only markdown**, because a path-scoped rule can only cover the extensions someone thought to list. A comment, document, or config header states what the code does now. Never narrate the past ("replaces", "used to", "formerly", "for the first time", "unlike the old") and never name a file, flag, or tool that no longer exists: git carries that history, and a reader cannot check a claim against something that is gone. The future belongs nowhere but a `TODO`. A phrase list is not the check, because the commonest offender announces nothing: name the line a comment describes, and delete it where no line corresponds, which is what a comment explaining why something was removed always looks like. Rationale worth keeping goes in a decision record of its own under [`docs/`](docs/index.md), created when the first one is needed, rather than scattered through the files it explains. +**This applies to every file type, not only markdown**, because a path-scoped rule can only cover the extensions someone thought to list. A comment, document, or config header states what the code does now. Never narrate the past ("replaces", "used to", "formerly", "for the first time", "unlike the old") and never name a file, flag, or tool that no longer exists. The future belongs nowhere but a `TODO`. Name the line a comment describes; where no line corresponds, correct a public or exported symbol's comment from its body and delete an internal comment. Never delete or cut below one sentence a public or exported symbol's comment, a public structure member's comment, or a package or module comment. Keep the sentence saying what it does, even where it restates the name, code, or syntax; where unclear, keep the first sentence. If the body was not read, keep the comment and report it. A note about a deliberate omission the code depends on and a file-level header pass this test. Rationale worth keeping goes in a decision record of its own under [`docs/`](docs/index.md). ## Commands From 2a08b371f4b2c457485974d17fc57666ac4b130b Mon Sep 17 00:00:00 2001 From: Alexander Sullivan Date: Wed, 30 Sep 2026 17:29:48 -0400 Subject: [PATCH 2/2] update --- .github/copilot-instructions.md | 2 +- .github/prompts/audit-docs.prompt.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index 5bf7f8b..aedd063 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -163,7 +163,7 @@ Not adopted: the ban on default exports (this repository uses them for the modul ### Comments & JSDoc -- **Comments describe the code as it stands.** Never narrate a change, fix, or prior state ("now uses", "previously", "no longer", "restored", "replaces", "used to", "formerly", "for the first time"), and never name a file, flag, or tool that no longer exists. Never argue that the code is correct or safe. **Name the line each comment describes.** Where no line corresponds, correct a public or exported symbol's comment from its body; delete an internal comment on that ground. Never delete commented-out code. A comment contradicting the code is corrected, not deleted. A note about a deliberate omission the code depends on, and a file header, pass this test. +- **Comments describe the code as it stands.** Never narrate a change, fix, or prior state ("now uses", "previously", "no longer", "restored", "replaces", "used to", "formerly", "for the first time"), and never name a file, flag, or tool that no longer exists. Never argue that the code is correct or safe. **Name the line each comment describes.** Where no line corresponds, correct a public or exported symbol's comment from its body; delete an internal comment on that ground. Delete commented-out code. A comment contradicting the code is corrected, not deleted. A note about a deliberate omission the code depends on, and a file header, pass this test. - **Every exported symbol carries a `/** */` block**, as do members of an exported structure (interface properties, object keys, enum values). Never delete the block or cut it below one sentence. Keep the sentence saying what the symbol does, even where it restates the name, code, or syntax; where unclear, keep the first sentence. Correct a wrong or absent-content claim from the body. If the body was not read, keep the block and report it. Write new blocks from the body: one sentence saying what the symbol does, and a second only for an error, constraint, or caller obligation the body or a test proves. A file-level block says what the module is for and never tours its exports. Cut other sentences by whole sentences; tags follow the rule on existing tags below. - A private helper gets a block when its name and signature do not carry it; a binding inside a function body does not, and a comment there that restates the next line is noise. **A fact about a symbol is stated once, on its declaration.** An export or re-export statement is a declaration, not a use. Never repeat a fact above a line that reads, calls, or branches on the symbol. A member of an exported structure is its own declaration. Remove a copy above a use only where both sites are in scope, the declaration's body was read, and the copy says no more than the declaration's comment; fold any additional constraint into the declaration first. Otherwise leave both and say so. - **In a block you write, do not put types in JSDoc.** TypeScript ignores `@param {string}`, `@returns {number}`, `@type`, and `@typedef` in `.ts`/`.tsx`, so they drift from the signature. Skip `@implements`, `@enum`, `@private`, and `@override` beside the keyword, and add `@param`/`@returns` where they say more than the name and type do diff --git a/.github/prompts/audit-docs.prompt.md b/.github/prompts/audit-docs.prompt.md index dded1e8..a6d6c2a 100644 --- a/.github/prompts/audit-docs.prompt.md +++ b/.github/prompts/audit-docs.prompt.md @@ -139,7 +139,7 @@ Write as a careful human technical writer: formal and neutral. - **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. When an audit edits a document in full, replace that document's existing em-dashes and en-dashes the same way; do not sweep any other file. - **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 and terms of art.** In prose you write or edit, capitalize acronyms and give the full term on first use per document. Keep exact casing for established names and direct code references. Introduce an unfamiliar term where the document first uses it, then use it unchanged. Introduce terms learned from this project's code, however obvious they now feel. Spell each concept consistently across documents edited in full. +- **Acronyms and terms of art.** In prose you write or edit, capitalize acronyms and give the full term on first use per document. Keep exact casing for established brand, tool, or package names, intentional domain terms such as `snRNA` and `mRNA`, and direct code references. Introduce an unfamiliar term where the document first uses it, then use it unchanged. Introduce terms learned from this project's code, however obvious they now feel. Spell each concept consistently across documents edited in full. ### Configuration references