docs: repo-wide authoring-doctrine pass, and repair the grammar the em-dash purge left behind - #3461
Conversation
…-humans) Recall-side re-run of the two authoring-doctrine lanes over the whole tracked markdown corpus, in two strictly separated lanes. The 2026-08-26 sweep ran these same two skills as its lanes L7 and L8, but ran serially in one context with no subagent-spawn tool, and its own record states: "Every substitute for fan-out was a corpus-wide mechanical detector plus sampled adjudication, which trades recall for precision. The numbers above are precision-side results and should not be read as completeness." (docs/specs/docs-hygiene-sweep-yield-measurement.md). This pass closes that recall gap by reading every file end to end. It does not re-run the noise, compression or derivability lanes, which that record closes for this corpus. Scope: 1,084 files read end to end across 90 batches (995 agents lane, 86 humans lane, 3 graded by the orchestrator because lane purity barred both lanes from them). 274 files excluded for stated reasons: changelogs and ADRs (historical record), vendored and upstream trees, generated regions, and deliberately defective eval fixtures. Findings: 219 raised, 219 independently verified by agents that did not produce them, with the producing rationale withheld and a default-refute posture. 197 confirmed, 22 refuted, 15 fixes rejected as unsafe while the defect stood. 117 edits met every gate and are applied here; 102 findings are report-only because their fix would rename a heading with inbound anchors, land inside a generated region, touch test-pinned prose, or force a paragraph rewrap. Also fixed, found while reading: - plugins/autonomy/skills/setup/templates/return-capture.md: a closing fence carried trailing text on the same line, so the JSON block opened 14 lines earlier never closed and the rest of the template rendered as code. - plugins/claude-config/skills/audit-pass/reference/run-state-and-resumability.md: the liveness test cited "5 minutes" against the same file's own correction that the default stale_after_s is 30 minutes. Validation: check-purged-em-dashes clean (21 paths, 30 files); check-changed-skills 28 skills 0 failed, all base-ref trigger phrases preserved; markdownlint 0 issues over 85 files; typos and editorconfig-checker clean; affected-tests.sh --run 17 shell suites passed; 667 pytest assertions passed. The selected vitest suites fail identically at origin/main (vitest absent from this environment), so they are untouched by this change. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KjPedw5YscNaAm1kaC837T
The #2891 de-slop campaign deleted ~12,561 em dashes across 411 files in 92 commits. Its own rewrite guide requires that "em dashes become periods or commas, OR the sentence is restructured". Where the substitution ran without the restructure step it left broken grammar in shipped instruction surfaces. 21 instances were found and verified against the files; 18 confirmed, 3 refuted as clipped-but-grammatical (this fleet writes in a deliberately telegraphic register, so terseness alone is not a defect). 17 are repaired here across 11 skill bodies. Under 0.5% of substitutions broke, but the failures are systematic rather than random: four reproducible mechanical shapes, five of them the same structural bug where a wrapped " - continuation" line was dedented to column 0 with no joining punctuation. The three that changed meaning rather than readability: - implement-dispatch/SKILL.md: "when the caller passes `--wave-cap <N>`." was a subordinate clause with no main clause, so "Cap at that N" read as an unconditional rule and contradicted the "3-5 by default" line above it. - docpage-digest/SKILL.md: a dedent inverted the instruction. "Truncate the host+path prefix, never the hash so the whole slug is <= 40 chars" reads as "do not truncate the hash in order to keep it <= 40". - implement/SKILL.md: two consecutive verbless sentences left "Never the producing context auditing itself" as a free-floating prohibition with no antecedent, so a reader could not tell what must not audit what. Repairs on paths in scripts/em-dash-purged-paths.txt restructure instead of reintroducing an em dash, and never substitute a parenthesis, en dash, or spaced hyphen. Held back: planning/skills/interview/SKILL.md carries real damage (a stranded "Which..." relative clause) inside a section that plugins/planning/tests/interview-defenses.test.sh digest-pins, so it is routed to that plugin's owner rather than edited here. Validation: check-purged-em-dashes clean (21 paths, 30 files); markdownlint 0 issues; typos and editorconfig-checker clean; check-changed-skills 11 skills 0 failed; affected-tests.sh --run 17 shell suites passed. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KjPedw5YscNaAm1kaC837T
|
Claude encountered an error after 0s —— View job I'll analyze this and get back to you. |
|
Claude encountered an error after 0s —— View job I'll analyze this and get back to you. |
|
Warning Automated security review did not complete — this is an infrastructure failure, not a review verdict. Treat any Claude comment on this PR (including a placeholder like "I'll analyze this and get back to you") as incomplete, not "no findings."
The check is green on purpose, and it is not evidence. It certifies that a security pass ran, and this one did not complete — but the cause is outside this PR's control, so merging is deliberately left unblocked rather than locking every merge for the length of the outage. Nothing was reviewed at this head. Where this check is required, it is satisfied without that evidence; a human should review security-sensitive changes here before merging. Re-run the job, or workflow_dispatch this workflow with the PR number, to retry the review (ci-workflows#227). A new push also retries it only if the caller's Re-running does NOT help for every class:
|
|
Warning Automated review did not complete — this is an infrastructure failure, not a review verdict. Treat any Claude comment on this PR (including a placeholder like "I'll analyze this and get back to you") as incomplete, not "no findings."
Re-run the job, or workflow_dispatch this workflow with the PR number, to retry the review. A new push re-triggers this lane only if the caller's |
The changelog-parity gate rejects a change set that modifies shipped files under plugins/<name>/ while the manifest still carries a published version: reusing a published version number is not allowed, because the install cache keys on `version` rather than commit, so an unbumped plugin never reaches a consumer. Each of the 45 touched plugins gets a patch bump and a '## [<version>]' entry. Entry text is derived per plugin from the findings that actually landed in it, not from a template: which files were touched, and whether the edit repaired em-dash purge damage, an unresolvable pointer, a drifted count, a stale recap, or an ambiguous sentence. Also merges origin/main to clear the stale-base-overlap gate: the branch was two commits behind, and #3457 had since touched two files this change set also edits (plugins/kindle-dedrm/README.md and plugins/session-flow/skills/find-handoff/reference/rung-1-known-location.md). A stale-base squash can silently revert recently landed fixes on overlapping paths, so the base is refreshed rather than merged stale. The merge was clean and both sides' changes are present. Validation: check-changelog-parity --check, --check-bump origin/main and --check-preserved origin/main all pass; check-purged-em-dashes clean; markdownlint 0 issues over 140 changed markdown files; typos and editorconfig-checker clean. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KjPedw5YscNaAm1kaC837T
|
Claude encountered an error after 0s —— View job I'll analyze this and get back to you. |
|
Claude encountered an error after 0s —— View job I'll analyze this and get back to you. |
…registered copies The registry alone only ever looks where it is told. A new lane could inline the operable floor, its author forget the entry, the six registered paths still agree, CI go green, and the next contract change strand that seventh copy. That is the same blindness this gate exists for, one level up, and it is not hypothetical: the first report of this coupling named five copies and building the registry found six. The check now scans every tracked file for the floor's opening bullet before comparing anything and fails on a carrier its registry does not name. Discovery reuses the anchored marker test the comparison uses, so the blockquoted launch-prompt shape is not a hole. Tracked rather than on-disk is the corpus every sibling gate reads: a scratch file ships to nobody, and staging it is what makes it a copy this repository carries. The registry stays, because it carries each consumer's comparison mode, which no scan can infer. A file may carry the marker as data rather than as a consumer copy, and declares that inline with a reason (loop-lane-floor-carrier-ok:) on the shape the CI workflow's lane-coverage-ok and the hooks' silent-skip-ok already use. The reason is mandatory, an annotation on a registered path fails as stale, and --list enumerates every exemption. This gate's own suite is the first such file. Also in this change: - Merges current origin/main. #3461 claimed rate-limit-guard 0.7.17 and docs-hygiene 0.21.21 after this branch was cut, so both renumber to 0.7.18 and 0.21.22 and the two changelog conflicts are resolved keeping main's entries. - Corrects this branch's own miscount. The rate-limit-guard entry said all six copies had drifted; three had. The other three matched the contract byte for byte and moved with it here. Same correction where the loop-lane changelog said two unpurged copies rather than three. - Corrects what values mode tolerates. Normalization strips backticks and emphasis as well as blockquote markers and wrapping, so markup is free in that mode too, not only line breaks. Every word is still asserted. Section 6 and both changelogs now say what the script header already said. - Adopts the liveness-assertion convention properly: the script header names its taxonomy row (Gate / classifier) and how it satisfies fail-loud, and the convention gains its Adopters row and a 1.1.0 entry. - Removes two restatements of floor values sitting outside the compared block: section 6 no longer names the 90% threshold, and rate-limit-guard's setup probe no longer names the 10-minute staleness window. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QkMx972GYEQ3vCb98pLw8m
No linked issue
Summary
A recall-side re-run of the two authoring-doctrine skills,
docs-hygiene:write-for-agentsanddocs-hygiene:write-for-humans, across the whole tracked markdown corpus in two strictly separated lanes.The 2026-08-26 sweep already ran these as its lanes L7 and L8, so a re-run needs a stated reason that is not "it has been a while". This is it, in that sweep's own words: it ran serially in one context, and "No subagent-spawn tool was available ... Every substitute for fan-out was a corpus-wide mechanical detector plus sampled adjudication, which trades recall for precision. The numbers above are precision-side results and should not be read as completeness" (
docs/specs/docs-hygiene-sweep-yield-measurement.md). This pass closes that recall gap by reading every file end to end instead of grepping for cues. It deliberately does not re-run the noise, compression, or derivability lanes, which that record closes for this corpus.The largest result was not a doctrine violation. Reading the corpus surfaced a regression left by the #2891 em-dash purge: where its mechanical substitution ran without the restructure step its own rewrite guide requires, it left broken grammar in shipped instruction surfaces, including three places where the meaning changed.
Fix
Two commits.
f38d1c16applies 117 verified authoring-doctrine edits across 83 files: unresolvable cross-references, stale counts and inventories, pointers whose targets no longer exist, and self-contradicting recaps.93c17eearepairs 17 grammar breaks across 11 skill bodies. The three that changed meaning rather than readability:implement-dispatch/SKILL.md—when the caller passes --wave-cap <N>.was a subordinate clause with no main clause, soCap at that Nread as an unconditional rule and contradicted the3-5 by defaultline above it.docpage-digest/SKILL.md— a dedent inverted the instruction:Truncate the host+path prefix, never the hash so the whole slug is <= 40 charsreads as do not truncate the hash in order to keep it <= 40.implement/SKILL.md— two consecutive verbless sentences leftNever the producing context auditing itselfas a free-floating prohibition with no antecedent.Repairs on paths in
scripts/em-dash-purged-paths.txtrestructure rather than reintroduce an em dash, and never substitute a parenthesis, en dash, or spaced hyphen.Scope and method. 1,084 files read end to end across 90 batches: 995 agents lane, 86 humans lane, and 3 graded separately because lane purity barred both lanes from the
write-for-humanstree itself. 274 files were excluded for stated reasons: changelogs and ADRs (historical record), vendored and upstream trees, generated regions, and deliberately defective eval fixtures.219 findings were raised and all 219 independently verified by agents that did not produce them, with the producing rationale withheld and a default-refute posture: 197 confirmed, 22 refuted, and 15 more where the defect stood but the proposed fix was rejected. Verification caught a worker justifying an edit by citing a
.claude/ai-slop.jsonexemption that does not exist, and another whose replacement would have changed a table's meaning.102 findings are report-only and are not in this diff, each held for a checkable reason: the fix would rename a heading with inbound anchors (
lychee.tomlsetsinclude_fragments = "full"), land inside aBEGIN GENERATEDregion, touch prose a test digest-pins, or force a paragraph rewrap. Notablyplanning/skills/interview/SKILL.mdcarries real purge damage inside a sectioninterview-defenses.test.sh:531pins, so it is routed to that plugin's owner rather than edited here.Two structural defects found while reading are also fixed: a closing fence carrying trailing text in
autonomy/skills/setup/templates/return-capture.md(the JSON block never closed, so 45 lines rendered as code), and a5 minutesliveness constant inclaude-config/skills/audit-pass/reference/run-state-and-resumability.mdthat contradicted the same file's own correction that the defaultstale_after_sis 30 minutes.Verification
scripts/check-purged-em-dashes.shscripts/check-changed-skills.sh origin/mainmarkdownlint-cli2typoseditorconfig-checkerscripts/affected-tests.sh --runpytest(7 selected suites)plugins/planning/tests/interview-defenses.test.shThe 18 selected
vitestsuites are NOT RUN by the shell runner per its own contract. They fail identically withERR_MODULE_NOT_FOUNDon a stashed tree atorigin/main(vitestis absent from this environment), so they are unaffected by this change.Related
docs/specs/docs-hygiene-sweep-yield-measurement.md— the prior sweep's recall limitation that motivates this re-rundocs/specs/docs-hygiene-sweep-unapplied-remediations.md— the prior L7/L8 backlog this extends; all 13 open L7 items verified already applied, and the 6 open L8 items were resolved rather than rediscovereddocs/adr/0008-admit-only-present-text-defects-to-the-instruction-audit-catalog.md— every finding anchors to text that is present; absences are routed, not filed.claude/rules/vendor-docs-are-not-style.md— the declared house style the humans lane resolved to, superseding the skill's bundled default setOne item needs a human decision and is deliberately not fixed here.
plugins/kindle-dedrm/README.md:25-32warns "Private marketplace only. Carve out before publishing" and instructs that if the marketplace is ever made public, the plugin should be removed frommarketplace.jsonfirst. Three sources now say the marketplace is public (README.md:3,docs/CI-RUNNER-ROUTING.md:3,.github/runner-policy.json), and.claude-plugin/marketplace.json:388still publishes the entry. Correcting the word "private" alone would delete the warning's trigger while leaving the plugin published, and carving it out is a policy call that belongs to a maintainer.Generated by Claude Code