docs: rename docs/ root files to lower-kebab-case and gate the rule - #4097
docs: rename docs/ root files to lower-kebab-case and gate the rule#4097kyle-sexton wants to merge 18 commits into
Conversation
Contract-slice artifacts for the docs/ filename rename: the interview Brief (11 questions answered and validated by two fresh-context agents), the design early-exit, and the draft five-phase plan pending stress-test review. The slice is pruned before merge per the topic-docs convention. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CUVaZs5Qssq3xN2gGEHwer
Drop the case-variant tombstones (two paths differing only by case cannot coexist on case-insensitive checkouts), add norm-conformant patch bumps for every plugin the sweep edits, anchor the sanity greps, widen the historical tier to docs/specs and docs/upstream, and add the fixture-isolation and stale-base steps the reviewers surfaced. Q12 reopens the tombstone decision. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CUVaZs5Qssq3xN2gGEHwer
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CUVaZs5Qssq3xN2gGEHwer
|
CI status note (draft stage). The That gate fails because this branch carries the contract slice So this is the designed mid-flight state, not a defect in the change. It turns green at close-out (Phase 5 of the plan), when the final commit removes the slice after the durable outcomes (the ADR and the follow-up issue) have graduated. Nothing to fix or re-run before then. Generated by Claude Code |
Structural commit: git mv each UPPERCASE-KEBAB file at the docs/ root to its lower-kebab twin and move the path constants, fixtures, and allowlist proof that read them at runtime (generate-cheatsheet, generate-catalog, validate-plugin-contracts, cheatsheet-config, the claude-ops overlap script and its test, check-docs-only and generate-cheatsheet fixtures, docs-only-paths, the native-surfaces store note). No content edit to plugin-artifact-protocol.md, so the six plugin copies stay byte-identical. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CUVaZs5Qssq3xN2gGEHwer
…the citing plugins Phase 2 of docs/topics/docs-naming-consistency/PLAN.md. The 13 docs/ root files were renamed in the previous commit; this commit moves every reference to them across the three-way boundary the plan draws: - Tier 1 (current surfaces, every form): the scripted basename map and bare-stem map over every tracked file outside the frozen trees, including the six absolute GitHub URLs, the two .claude/rules files, README.md, the plugin setup SKILL.md files, docs/conventions/**, and the lockstep pair plugins/skill-quality/scripts/check-skill.sh + evals.json (the WARN text and the eval that quotes it change together). - Tier 2 (docs/adr, docs/specs, docs/upstream): markdown links and backtick paths only; narrative and bare stems left as written. - Tier 3 (plugin CHANGELOG released entries): untouched, except the one real markdown link in plugins/visualization/CHANGELOG.md (0.3.2 entry), corrected under the plugin's new release entry, which names the correction. Every plugin with a non-CHANGELOG edit on the branch (56) gets a patch bump in its plugin.json and a `## [x.y.z]` / `### Changed` entry naming the repoint. docs/catalog.md and docs/skill-cheat-sheet.md are regenerated. docs/architecture/landscape.json has its edges regenerated through reference-edges.sh (counts unchanged; six `files` samples re-sorted for the new lowercase paths); the rendered landscape.md and portfolio.md are byte-identical. Sanity: the three anchored `git grep` residual checks are clean, the offline link resolver reports no target newly missing against origin/main, check-changelog-parity --check-bump/--check/--check-order/--check-preserved exit 0, check-changed-skills exits 0, check-purged-em-dashes exits 0, markdownlint over the 210 changed markdown files reports 0 issues, and validate-plugin-contracts, generate-catalog --check, and generate-cheatsheet --check all pass. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CUVaZs5Qssq3xN2gGEHwer
Phase 3 of docs/topics/docs-naming-consistency/PLAN.md. The rename (Phase 1) and the reference sweep (Phase 2) made the tree consistent; this commit keeps it that way. - scripts/check-docs-naming.sh --check walks `git ls-files docs/` and fails any basename outside `^[a-z0-9]+([.-][a-z0-9]+)*\.[a-z0-9.]+$`, with README.md, CHANGELOG.md, INDEX.md, everything under docs/topics/, and code files (py sh mjs js ps1) exempt. Independently, it fails any two tracked paths under docs/ that differ only by case, because a case-insensitive checkout writes the second over the first. The header states the rule and its reason. - scripts/check-docs-naming.test.sh, written first and observed red, builds a throwaway git repository through fixture-tree.sh and proves ten cases: a clean tree passes; NEW-FILE.md, snake_case.md, and Foo.md beside foo.md fail and are named; README/CHANGELOG/INDEX, docs/topics/, b_c.py, Run-Thing.ps1, and v1.2.schema.json pass; discover mode names the offender; an unknown flag exits 2. - .claude/rules/docs-naming.md (paths: docs/**) states the rule, the exemptions, the hard-cutover consequence, and that the script is the gate, since a path-scoped rule loads on read and never on file creation. AGENTS.md gains the on-demand row. - The lint job runs the suite (gated on run_shell) and then the check with id docs_naming and continue-on-error, beside the skill-leaf-names pair, and feeds docs-naming into the aggregator. Sanity: suite 10/10, checker exits 0 on the tree, shellcheck and shfmt clean, no em dash, check-fixture-git-isolation, check-silent-skips, check-shell-portability, actionlint, check-lane-coverage, check-docs-only-gate, and markdownlint all pass; affected-tests --explain maps the new script to its co-located suite. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CUVaZs5Qssq3xN2gGEHwer
Phase 4 of docs/topics/docs-naming-consistency/PLAN.md. ADR 0033 records the decision the previous three commits carried out: every file under docs/ is lower-kebab-case, with README.md, CHANGELOG.md, INDEX.md, docs/topics/, and code files by extension exempt; the thirteen uppercase root files were renamed in one hard cutover with no tombstones, because a case-colliding pair corrupts every case-insensitive checkout; the historical record was repointed by tier; and scripts/check-docs-naming.sh with .claude/rules/docs-naming.md is the enforcement pair. The Evidence section names the three style guides, the Azure Repos case-sensitivity page, the repository's own bump-on-body-change norm, and the git mv behaviour the plan rested on; the Consequences name the 404 window for stale installed plugin copies and the stale-path-verify advisory on future edits. The number is the next free one (0032 was the highest); the existing duplicates 0018, 0025, and 0028 are left as they are, per the record-decision skill, and a uniqueness gate is a separate follow-up. The rule file's placeholder line now links the ADR. Sanity: the duplicate-number grep excluding the three known pairs is empty, the ADR carries exactly one `- Status: accepted`, scripts/check-docs-naming.sh --check still exits 0, no em dash, markdownlint clean, and the rule-file link resolves. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CUVaZs5Qssq3xN2gGEHwer
…nsistency-cr6ys6 # Conflicts: # plugins/claude-ops/.claude-plugin/plugin.json # plugins/claude-ops/CHANGELOG.md # plugins/miro/.claude-plugin/plugin.json # plugins/miro/CHANGELOG.md
…ailing example The header cited a retired uppercase path as its example of a name the rule rejects, which is exactly the string the branch's residual sweep greps for and the stale-path hook flags on future edits. A made-up name shows the same shape without naming a path that no longer exists. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CUVaZs5Qssq3xN2gGEHwer
scripts/check-script-contract.test.sh registers every scripts/check-*.sh and failed the new checker as UNREGISTERED. Registering it exposed two contract gaps in the script itself: findings went to stdout rather than stderr, and a missing git was swallowed by the process substitution, so the checker would have printed the clean-run statement and exited 0 without inspecting anything. - The checker now exits 2 with a stderr diagnostic when git is absent or the cwd is not a repository, prints each offender on stderr, and keeps the clean-run statement on stdout, per README.md "The check-script contract". - The contract suite carries the row (`git` prerequisite, `docs_naming` recipe) and the recipe seeds a one-file git fixture clean and with a mixed-case name; its violation needle is the finding text. The suite now covers 8 recipes and 32 cases. Sanity: check-script-contract.test.sh 32/32, check-docs-naming.test.sh 10/10, the checker exits 2 with git hidden from PATH, shellcheck and shfmt clean on both files. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CUVaZs5Qssq3xN2gGEHwer
Phase 5 of docs/topics/docs-naming-consistency/PLAN.md, ticked before the slice is pruned so this commit is the pre-prune head the PR body cites. origin/main (3b273a2, five commits) is merged; the two plugins bumped on both sides resolve to main's version plus one patch. On the merged tree every Phase 1 to 4 sanity block holds, scripts/affected-tests.sh --run passes every selected shell suite except the two process-budget cases in claude-ops' cache-content-check suite (identical on a clean origin/main checkout in this container, in a skill this branch does not touch), and the linters, the stale-base gate, the four changelog-parity modes, and the changed-skills gate all exit 0. Follow-up issue #4109 is filed for the ADR-number uniqueness gate. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CUVaZs5Qssq3xN2gGEHwer
The plan and its design-resolution record served their purpose on the branch; the PR body carries PLAN.md verbatim inside a details block and names the pre-prune head (ad5c116), so the slice leaves the tree before merge per the topic-docs convention. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CUVaZs5Qssq3xN2gGEHwer
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
…nsistency-cr6ys6 # Conflicts: # plugins/claude-ops/.claude-plugin/plugin.json # plugins/claude-ops/CHANGELOG.md # plugins/skill-quality/.claude-plugin/plugin.json # plugins/skill-quality/CHANGELOG.md # plugins/source-control/CHANGELOG.md
…nsistency-cr6ys6 # Conflicts: # plugins/bugs/CHANGELOG.md # plugins/claude-config/CHANGELOG.md # plugins/claude-ops/CHANGELOG.md # plugins/discipline/CHANGELOG.md # plugins/discovery/CHANGELOG.md # plugins/docs-hygiene/CHANGELOG.md # plugins/education/CHANGELOG.md # plugins/firecrawl/CHANGELOG.md # plugins/improvement/CHANGELOG.md # plugins/instruction-placement/CHANGELOG.md # plugins/kindle-dedrm/.claude-plugin/plugin.json # plugins/kindle-dedrm/CHANGELOG.md # plugins/mutation-testing/CHANGELOG.md # plugins/overengineering/CHANGELOG.md # plugins/playbooks/CHANGELOG.md # plugins/plugin-quality/CHANGELOG.md # plugins/session-flow/.claude-plugin/plugin.json # plugins/session-flow/CHANGELOG.md # plugins/songwriting/CHANGELOG.md # plugins/source-control/CHANGELOG.md
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: ae07c1ceeb
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
…in check-docs-naming
Two review findings on the checker, both real:
- The extension part of the name rule was `\.[a-z0-9.]+$`, which accepted `foo..md`,
`foo.md.`, and `foo...`. It is now `\.[a-z0-9]+$`; dotted stems such as
`v1.2.schema.json` still pass through the stem group, and three negative cases join
the suite (13 cases).
- The case-collision pass folded paths with `${path,,}`, a Bash 4 expansion that aborts
with `bad substitution` on stock macOS Bash 3.2, the very checkouts the collision rule
protects. The fold now goes through `tr '[:upper:]' '[:lower:]'`.
ADR 0033 states the tightened regex. The header comment names the new counter-examples.
Sanity: check-docs-naming.test.sh 13/13, check-script-contract.test.sh 32/32, the checker
exits 0 on the tree, shellcheck, shfmt, check-shell-portability, markdownlint clean, no em
dash.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CUVaZs5Qssq3xN2gGEHwer
…eckout The discover-mode case in check-docs-naming.test.sh called mk_repo without checking its result, so when the build failed the empty $repo turned `git -C "$repo"` into a command against this checkout: that is how commit f9fce8c (subject "case", author "test") entered the branch history with the then-untracked suite file. The case now runs its git commands only when the build succeeded and the path is non-empty, and reports a build failure as a failed case otherwise. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CUVaZs5Qssq3xN2gGEHwer
No related issue: the rename was requested directly by the maintainer; the one follow-up (an ADR-number uniqueness gate) is filed as #4109 and linked under Related.
Summary
Rename the 13 UPPERCASE-KEBAB markdown files at the
docs/root (PLUGIN-PHILOSOPHY.md,MIGRATION-PLAYBOOK.md,GLOSSARY.md,CATALOG.md, and nine more) to lower-kebab-case, repoint every live reference, and add the rule, the CI gate, and the ADR that keep the tree consistent. The 13 were the only files in a 193-file tree that broke the rule every other naming surface in this repository already states (plugin names, skill leaf names, ecosystem ids, topic slugs); Google, GitLab, and Microsoft Learn style guides all mandate lowercase-with-hyphens for doc filenames, and the conventional uppercase exceptions (README.md,CHANGELOG.md,INDEX.md, code files) stay as they are.Two of the 13 (
plugin-philosophy.md,migration-playbook.md) are a raw-URL interface under ADR 0018, so this is a hard cutover with no tombstones (a case-colliding pair corrupts every Windows and macOS checkout) carried by a patch bump on every plugin whose body cites them.Fix
Four Tidy-First commits plus a base merge, each green on its own sanity block:
bdb8eb14Rename.git mvthe 13 files and repoint the runtime consumers (generate-catalog.mjs,generate-cheatsheet.mjs,validate-plugin-contracts.mjs,cheatsheet-config.mjs,overlap.pyand its test, the docs-only allowlist and its proof,docs/native-surfaces/records.json).14126266Reference sweep and bumps. Scripted basename and bare-stem maps applied by a three-way boundary: current surfaces change in every form (the six absolute GitHub URLs, 54 pluginsetupskills,.claude/rules,README.md,docs/conventions/**, and the lockstep pairplugins/skill-quality/scripts/check-skill.sh+evals.json);docs/adr,docs/specs,docs/upstreamchange in links and backtick paths only; pluginCHANGELOG.mdreleased entries stay untouched except one real link (below). Every plugin with a body edit (56) takes a patch bump with a## [x.y.z]/### Changedentry.docs/architecture/landscape.jsonedges regenerated throughreference-edges.sh;docs/catalog.mdanddocs/skill-cheat-sheet.mdregenerated.6de5b0fcGate.scripts/check-docs-naming.sh --check(basename regex, three exemption classes, case-collision rule) with a test-first.test.sh(10 cases),.claude/rules/docs-naming.md(paths: docs/**), theAGENTS.mdrow, and thelintjob step pair (id: docs_naming, advisory, fed to the aggregator).ecc33889ADR 0033 records the rule, the exemptions, the hard cutover and why, the three-way historical-record boundary, the enforcement pair, and the duplicates-stay stance for ADR numbers; the rule file links it.b5481c31,82899c3a, and71754f56mergeorigin/main(nine commits across three syncs),8a259be4andccb23908bring the checker onto the check-script contract,ad5c1165closes Phase 5 in the plan,ae07c1ceprunes the slice. Every plugin bumped on both sides resolves to main's version plus one patch with this branch's entry above main's:miroandskill-qualityon the second sync, and on the third sync (main's fix(playbooks,skills): keep first and second person out of description prose across 36 skills #4108 bumped 24 plugins) the 18 that overlap this branch's set, so for exampleclaude-opslands at0.54.2andsource-controlat0.55.77.Stray commit, disclosed:
f9fce8c9(subjectcase, authortest <t@t.test>) adds only the then-untrackedscripts/check-docs-naming.test.sh. It was made by that suite itself on its first red run: its discover-mode case rangit -C "$repo"after a failed fixture build left$repoempty, so the fixture commit landed in this checkout. The guard is fixed on the branch, the commit is byte-identical to what the next commit adds, and it disappears under the repository's squash merge; rewriting three merge commits above it to drop it was judged riskier than disclosing it.Declared released-entry edit, per the changelog-parity discipline: the one markdown link inside
plugins/visualization/CHANGELOG.md's## [0.3.2]entry now points atdocs/plugin-philosophy.md; the plugin's new## [0.6.2]entry names the correction.Pre-prune head (the last commit carrying the contract slice
docs/topics/docs-naming-consistency/):ad5c1165f17ca5948a5d4ac219d5032790ef1f9d. The final commit removes the slice, socontract-slice-diffgoes green on this head and the plan is preserved below.docs/topics/docs-naming-consistency/PLAN.md (Brief + five-phase plan, as of the pre-prune head)
docs-naming-consistency
Brief
TLDR
.mdfiles at thedocs/root to lower-kebab-case withgit mv,and update every live reference (links, backtick paths, bare stems, runtime constants) to the
new names.
every plugin whose body the sweep edits gets a patch bump and a changelog entry, which is what
delivers the corrected citations to installed copies.
scripts/check-docs-naming.sh(+.test.sh, CI-wired,affected-testsmapped) that also refuses any twodocs/paths differing only by case, and apath-scoped
.claude/rules/docs-naming.mdfordocs/**.uniqueness gate.
Goal
Every file under
docs/follows one naming rule a reader can state in a sentence: lower-kebab-casefor markdown and data files, with the conventional exceptions (
README.md,CHANGELOG.md,INDEX.md, code files in their ecosystem's casing, and the branch-only contract slice), so thatthe 13 root files stop being the only outliers, nothing that points at them breaks, and the drift
cannot silently recur.
Constraints
git mvper file, history-preserving; no content edit todocs/PLUGIN-ARTIFACT-PROTOCOL.md(six
plugins/*/reference/artifact-protocol.mdcopies must stay byte-identical to it).scripts/generate-cheatsheet.mjs,scripts/generate-catalog.mjs,scripts/validate-plugin-contracts.mjs,plugins/claude-ops/skills/audit-native-overlap/scripts/overlap.pyandtest_overlap.py,scripts/check-docs-only.test.sh,scripts/generate-cheatsheet.test.sh,scripts/docs-only-paths.txt.rules, scripts, prompts,
.github,docs/outsideadr/,specs/,upstream/): every form,bare stems included. Tier 2,
docs/adr/**,docs/specs/**,docs/upstream/**: markdown linksand backtick paths only, narrative untouched. Tier 3, plugin
CHANGELOG.mdreleased entries:untouched except the one markdown link in
plugins/visualization/CHANGELOG.md, declared in thePR body and in that plugin's new release entry.
.claude-plugin/plugin.jsonand a## [x.y.z]/### Changedentry, the shape 12 of the last 12 body-editing commits used.GLOSSARY.mdmentions underplugins/education/**andplugins/domain-driven-design/**name a learner-workspace file and are excluded from the sweep.README.md,CHANGELOG.md,INDEX.md, the contract slice(
docs/topics/**), and code files by extension (.py,.sh,.mjs,.js,.ps1).state the current rule, never the incident.
## Summary,## Fix,## Verification,## Related, aNo linked issueline, the PLAN.md in a<details>block,and the pre-prune commit SHA.
scripts/affected-tests.sh --runmust select and pass every mapped suite; a changedfile mapping to zero suites is an error.
Acceptance criteria
git ls-files docs/ | grep -E '/[^/]*[A-Z][^/]*$' | grep -vE '/(README|CHANGELOG|INDEX)\.md$' | grep -v '^docs/topics/'returns empty.scripts/check-docs-naming.sh --checkexits 0 on the renamed tree and exits non-zero when afixture adds
docs/NEW-FILE.mdor a case-colliding pair; its.test.shproves both, andscripts/affected-tests.sh --explain scripts/check-docs-naming.shselects that test..mdreports 0 missing targets, and! git grep -qE '(main|blob/main)/docs/[A-Z][A-Z-]*\.md'holds (no absolute URL names anuppercase root file).
node scripts/generate-catalog.mjs,node scripts/generate-cheatsheet.mjs --check,node scripts/validate-plugin-contracts.mjs, theoverlap.pytests, andscripts/check-changed-skills.sh origin/mainall pass.and the rule file;
.claude/rules/docs-naming.mdexists withpaths: ["docs/**"]and a matchingrow in the
AGENTS.mdrules table.origin/mainand a new## [<v>]entry (scripts/check-changelog-parity.sh --check-bump origin/main).bare stem, runtime constant), THEN the anchored
git grepsanity check or an affected test failsbefore the PR leaves draft.
Captured assumptions
.claude/topic-docs.yamlexists, so the documented defaults apply; this PR does not add aconcern file. Revisit if a later PR binds one.
from this session. Revisit if a 404 report arrives; the remedy is a follow-up that repoints it.
URLs until they update; this is the repository's declared posture for a rename ("a clean breaking
change carried by a version bump and a changelog note").
acceptance_criteria_formatresolved tofree-text(default; no convention-home region).Out-of-scope
docs/(headings, frontmatter shape).README.md/CHANGELOG.md/INDEX.mdor any code file.uniqueness gate).
CHANGELOG.mdreleased entries beyond the one visualization link.scripts/skill-description-cap-baseline.txtand WARN, not FAIL.Deferred questions
awaiting the user, not a deferral.
Plan
Goal
What: rename the 13 UPPERCASE-KEBAB root files under
docs/to lower-kebab-case, repointevery live reference, bump every plugin the sweep edits, and add the rule, the gate, and the ADR
that stop the drift recurring.
Why: the 13 are the only files in a 193-file tree that break the rule every other naming
surface in this repository already states, and nothing today prevents the 14th.
Standards grounding
No standards index exists (
.claude/standards.yamlanddocs/standards/absent; rung 6 of theladder, nothing persisted). The plan is grounded in the repository's own convention surfaces
read this session:
docs/adr/0018-treat-the-plugin-as-the-encapsulation-boundary-for-skill-citation.mddocs/by absolute URL, so a rename is an interface changedocs/MIGRATION-PLAYBOOK.mdlines 483-490docs/PLUGIN-PHILOSOPHY.mdlines 512-519scripts/check-changelog-parity.shheaderscripts/check-changed-skills.shandscripts/skill-description-cap-baseline.txtscripts/check-fixture-git-isolation.sh,scripts/test-git-helpers.shscripts/check-stale-base-overlap.sh.claude/rules/skill-bodies-state-current-rules.md.claude/rules/vendor-docs-are-not-style.md.claude/rules/pr-body-contract.mdplugins/architecture/skills/record-decision/SKILL.mdGotchasdocs/conventions/topic-docs/README.mdlines 656-670INDEX.mdreserved; PLAN.md pasted in the PR body; pre-prune SHA named; slice pruned before mergePhase 1: Rename the 13 and move the runtime consumers [DONE]
One structural commit. Every
git mvand every hardcoded path constant moves together so nointermediate state has a generator reading a missing file.
File moves (all
git mv docs/<OLD>.md docs/<new>.md):CATALOG-TAXONOMY.md->catalog-taxonomy.mdCATALOG.md->catalog.mdCI-RUNNER-ROUTING.md->ci-runner-routing.mdCLOUD-FLEET-SETUP.md->cloud-fleet-setup.mdCLOUD-SESSIONS.md->cloud-sessions.mdFINDING-YOUR-UNKNOWNS.md->finding-your-unknowns.mdGLOSSARY.md->glossary.mdMIGRATION-PLAYBOOK.md->migration-playbook.mdNATIVE-SURFACES.md->native-surfaces.mdOFFICIAL-DOCS.md->official-docs.mdPLUGIN-ARTIFACT-PROTOCOL.md->plugin-artifact-protocol.md(no content edit)PLUGIN-PHILOSOPHY.md->plugin-philosophy.mdSKILL-CHEAT-SHEET.md->skill-cheat-sheet.mdRuntime consumers (path constants, fixtures, allowlist proof):
scripts/generate-cheatsheet.mjsOUTPUT_PATHand the header commentscripts/generate-catalog.mjsoutputPath,taxonomyPath, error text, header commentscripts/validate-plugin-contracts.mjsscripts/cheatsheet-config.mjsplugins/claude-ops/skills/audit-native-overlap/scripts/overlap.py--viewpath and help textplugins/claude-ops/skills/audit-native-overlap/scripts/test_overlap.pyscripts/check-docs-only.test.shscripts/generate-cheatsheet.test.shscripts/docs-only-paths.txtdocs/native-surfaces/records.jsonnotefieldplugins/*/reference/artifact-protocol.md(six)Sanity Check:
git ls-files docs/ | grep -E '/[^/]*[A-Z][^/]*$' | grep -vE '/(README|CHANGELOG|INDEX)\.md$' | grep -v '^docs/topics/'returns emptygit ls-files | tr 'A-Z' 'a-z' | sort | uniq -dreturns emptynode scripts/generate-catalog.mjs && git diff --quiet docs/catalog.mdexits 0node scripts/generate-cheatsheet.mjs --checkexits 0node scripts/validate-plugin-contracts.mjsexits 0bash scripts/check-docs-only.test.sh && bash scripts/generate-cheatsheet.test.sh && bash scripts/validate-plugin-contracts.test.shexit 0bash plugins/claude-ops/skills/audit-native-overlap/scripts/overlap.test.shexits 0Phase 2: Reference sweep by the three-way boundary, with plugin bumps [DONE]
Scripted, not hand-edited. Two maps applied with
sedover the file set each tier allows: the13-row basename map (
<OLD>.md-><new>.md) and the 11-row bare-stem map (PLUGIN-PHILOSOPHY->
plugin-philosophy, applied only to Tier 1 and only where the stem is not followed by.md).Then a deliberate pass over the sites the maps cannot judge.
Tier 1, current surfaces, every form: every tracked file EXCEPT
plugins/*/CHANGELOG.md,docs/adr/**,docs/specs/**,docs/upstream/**,plugins/education/**,plugins/domain-driven-design/**,.work/**,docs/topics/**. Includes the six absolute GitHubURLs,
.claude/rules/catalog-taxonomy.md,.claude/rules/ruff-pin.md,README.md,.github/recurring-schedule.json,.claude/cloud-bootstrap.sh,.github/workflows/ci.ymlcomments,
prompts/cloud-bootstrap-rollout.md,scripts/skill-portability-tokens.txt, everyplugin setup
SKILL.mdcitingdocs/PLUGIN-PHILOSOPHY.md, anddocs/conventions/**.Lockstep edit:
plugins/skill-quality/scripts/check-skill.sh(four bare stems, two inuser-visible WARN text) and
plugins/skill-quality/skills/check/evals/evals.jsonline 104, whichquotes that WARN text verbatim; both change in the same commit.
Tier 2,
docs/adr/**,docs/specs/**,docs/upstream/**: markdown links and backtick pathsonly (the basename map); bare stems and narrative untouched.
Tier 3, plugin
CHANGELOG.mdreleased entries: untouched, exceptplugins/visualization/CHANGELOG.mdline 212 (the one real markdown link), fixed under the samepatch bump every other touched plugin gets, with the correction named in the new release entry.
Plugin bumps (scripted): for each plugin with a non-CHANGELOG file edited on this branch
(54 today; recomputed from
git diff --name-only origin/main), bump the patch component ofplugins/<p>/.claude-plugin/plugin.jsonand prepend a## [<v>]section with one### Changedbullet ("Cite the marketplace
docs/doctrine files by their lower-kebab names"). The visualizationentry additionally names the released-entry link correction.
Deliberate pass (from the exploration's bare-name table, plus the 27 bare-stem hits): confirm
each hit names the docs file and not a same-named file elsewhere; the
GLOSSARY.mdlearnerworkspace is the known false positive and is excluded by path.
Sanity Check:
.md(scratch script; resolves each](relative/path.md)against the file's directory) reports 0 missing targets! git grep -qE '(CATALOG-TAXONOMY|CATALOG|CI-RUNNER-ROUTING|CLOUD-FLEET-SETUP|CLOUD-SESSIONS|FINDING-YOUR-UNKNOWNS|GLOSSARY|MIGRATION-PLAYBOOK|NATIVE-SURFACES|OFFICIAL-DOCS|PLUGIN-ARTIFACT-PROTOCOL|PLUGIN-PHILOSOPHY|SKILL-CHEAT-SHEET)\.md' -- ':!plugins/*/CHANGELOG.md' ':!docs/adr' ':!docs/specs' ':!docs/upstream' ':!plugins/education' ':!plugins/domain-driven-design' ':!docs/topics'holds! git grep -qE '\b(PLUGIN-PHILOSOPHY|MIGRATION-PLAYBOOK|OFFICIAL-DOCS|CATALOG-TAXONOMY|SKILL-CHEAT-SHEET|CLOUD-SESSIONS|CLOUD-FLEET-SETUP|FINDING-YOUR-UNKNOWNS|NATIVE-SURFACES|CI-RUNNER-ROUTING|PLUGIN-ARTIFACT-PROTOCOL)\b' -- ':!plugins/*/CHANGELOG.md' ':!docs/adr' ':!docs/specs' ':!docs/upstream' ':!docs/topics'holds! git grep -qE '(main|blob/main)/docs/[A-Z][A-Z-]*\.md'holdsscripts/check-changelog-parity.sh --check-bump origin/main && scripts/check-changelog-parity.sh --check && scripts/check-changelog-parity.sh --check-orderexit 0scripts/check-changed-skills.sh origin/mainexits 0scripts/check-purged-em-dashes.shexits 0Phase 2 evidence notes: the link resolver reports 0 targets newly missing against
origin/main(the 31 pre-existing misses are example paths and placeholders in specs, changelogs, and skill
context files, identical on both trees); the URL check's one hit is a
README.mdfixture insidescripts/check-skill-portability.test.sh, which is in the exempt set.docs/architecture/landscape.jsonwas regenerated through
reference-edges.sh(edge counts unchanged; sixfilessamples re-sorted)and
render-landscape.shproduced byte-identicallandscape.mdandportfolio.md.Phase 3: Checker, rule file, CI wiring [DONE]
Checker (CREATE):
scripts/check-docs-naming.sh:--checkmode; walksgit ls-files docs/; passes abasename when it matches
^[a-z0-9]+([.-][a-z0-9]+)*\.[a-z0-9.]+$, or isREADME.md/CHANGELOG.md/INDEX.md, or the path is underdocs/topics/, or the extension is acode extension (
py sh mjs js ps1); additionally fails when any two tracked paths underdocs/differ only by case; prints onepath: reasonline per offender; exit 1 on anyoffender; header comment states the rule and its reason
scripts/check-docs-naming.test.sh: black-box, mktemp fixture tree with a throwaway gitrepo built through
scripts/test-git-helpers.sh(orunset GIT_DIR GIT_WORK_TREE GIT_CONFIG)so
scripts/check-fixture-git-isolation.sh --checkpasses; nook "skip"line; assertspass on a clean tree, fail on
docs/NEW-FILE.md, pass ondocs/x/README.md, pass ondocs/topics/t/PLAN.md, pass ondocs/a/b_c.py, pass ondocs/a/v1.2.schema.json, failon
docs/Foo.mdbesidedocs/foo.mdRule file and index (CREATE / MODIFY):
.claude/rules/docs-naming.md:paths: ["docs/**"]; states the rule, the exemptions, thatscripts/check-docs-naming.sh --checkis the gate (path rules load on read, not on filecreation), and links the ADR from Phase 4; no em dash
AGENTS.md: one new row in the "Conventions that load on demand" tableCI and validation wiring (MODIFY):
.github/workflows/ci.ymllintjob: "Run docs-naming tests" (bash scripts/check-docs-naming.test.sh,gated on
run_shell) then "Check docs/ filenames are lower-kebab" withid: docs_namingand
continue-on-error: true, beside the skill-leaf-names pair; adocs-naming=${{ steps.docs_naming.outcome }}line in the outcome block thatscripts/aggregate-hygiene-results.shconsumesscripts/affected-tests.sh --explain scripts/check-docs-naming.sh .claude/rules/docs-naming.md .github/workflows/ci.ymlreturns noUNMAPPEDSanity Check:
scripts/check-docs-naming.sh --checkexits 0 on the working treebash scripts/check-docs-naming.test.shexits 0 and prints oneokline per case aboveshellcheck scripts/check-docs-naming.sh scripts/check-docs-naming.test.sh && shfmt -d scripts/check-docs-naming.sh scripts/check-docs-naming.test.shexit 0scripts/check-fixture-git-isolation.sh --check && scripts/check-silent-skips.shexit 0actionlint .github/workflows/ci.ymlexits 0grep -c 'docs-naming' AGENTS.mdreturns 1 andgrep -c '^paths:' .claude/rules/docs-naming.mdreturns 1! grep -rqP '\xE2\x80\x94' .claude/rules/docs-naming.md scripts/check-docs-naming.sh scripts/check-docs-naming.test.shholds (no em dash in any new file)Phase 4: ADR at the next free number [DONE]
·r·echeck after the Phase 5 base sync) and write
docs/adr/<NNNN>-name-docs-files-lower-kebab-case-with-conventional-exceptions.mdthrough/architecture:record-decisionin the observed shape: the rule and its exemptions; hardcutover with no tombstones and why (case collision on case-insensitive checkouts); the
three-way historical-record boundary; the duplicates-stay stance for ADR numbers; the checker
and rule file as the enforcement pair; Consequences name the 404 window for stale installed
plugin copies and the
stale-path-verifyhook's advisory notices on future edits citing aretired path
.claude/rules/docs-naming.mdlinks the ADRSanity Check:
ls docs/adr/ | grep -oE '^[0-9]{4}' | sort | uniq -d | grep -vE '^(0018|0025|0028)$'returns empty (the new number is unique)grep -c '^- Status: accepted' docs/adr/<NNNN>-*.mdreturns 1scripts/check-docs-naming.sh --checkstill exits 0Phase 5: Sync, validate, publish, close out [DONE]
git fetch origin main && git merge --no-edit origin/main; re-run thePhase 2 sed maps and every Phase 1 to 4 sanity check on the merged tree; re-check the ADR
number against
origin/main;scripts/check-stale-base-overlap.sh --check origin/mainexits 0scripts/affected-tests.sh --runexits 0 with every changed file mappedmarkdownlint-cli2over changed markdown,typos,editorconfig-checker,shellcheck,shfmt -d,actionlintall exit 0<details>block, the pre-prune commit SHA, the visualizationreleased-entry correction declared,
## Verificationfilled with the commands and exit codesgh issue list --state all --search 'ADR number uniqueness in:title'; if a match exists comment on it, else create "Add an ADR-number uniqueness gate (existing duplicates 0018/0025/0028 stay)" citing the new ADR; record the number under## Relatedgit rm -r docs/topics/docs-naming-consistency/in a final commit; flip the PR to readySanity Check:
scripts/check-contract-slice-prune.sh --check-diff origin/mainexits 0 on the final headNo linked issueline, a<details>block, and a 40-hex pre-prune SHA## RelatedPhase 5 evidence notes: the merge is
b5481c31(claude-opsandmiro, bumped on both sides, resolve to main plus one patch);scripts/affected-tests.sh --runon the merged tree passes every selected shell suite except the two process-budget cases inplugins/claude-ops/skills/plugins/scripts/cache-content-check.test.sh, which fail identically on a cleanorigin/maincheckout in this container and belong to a skill this branch never touched; the run also caught thatscripts/check-script-contract.test.shregisters everycheck-*.sh, fixed inccb23908. The follow-up issue is #4109. The three sanity boxes above are verified in the close-out turn: the PR body is updated before the prune commit, and the prune check runs on the pruned head right after.Alternatives considered
windows-2025CI jobsrecord-decisionGotchas: never renumberTest strategy
Test boundaries (the public interfaces the tests drive):
scripts/check-docs-naming.sh --check(newly introduced): black-box.test.shwith a mktempfixture repo; Red first (the failing
NEW-FILE.mdand case-collision cases before the script),then Green, then the exemption cases
scripts/generate-cheatsheet.mjs --check,scripts/generate-catalog.mjs,scripts/validate-plugin-contracts.mjs(existing): their.test.shsuites, fixtures updatedoverlap.pydefaults (existing):test_overlap.pyfixture path updatedSKILL.md(existing gate):scripts/check-changed-skills.sh origin/mainsweep; the weekly
link-check.ymllane is the durable backstopsanity greps are the assertions
Risks and mitigations
--check-bumpVERSION COLLISION fails loudly; Phase 5 re-sync and rebumpv1.2.schema.jsontest case;continue-on-errorExecution shape
Fully sequential: Phase 1 -> 2 -> 3 -> 4 -> 5. Phase 2 needs Phase 1's paths; Phase 3's checker
runs over the renamed tree; Phase 4's ADR is linked from Phase 3; Phase 5 re-syncs and validates
the whole. The sweep and the bumps are scripts, not volume hand-editing.
git mvplus path constants; one commitOpen questions
uppercase. The plan is written for the recommendation.
Handoff to implementation
User-approval gates
[FALLBACK]decisions.Execution shape ([EXEC-SHAPE] tagged)
continue-on-error: truewith an aggregated outcome, matching every siblingcheck-*.shstep in thelintjob.link-check.yml.Mechanical work
/planning:plan review.Blast radius
HIGH by file count (roughly 165 files across 54 plugins and 20 convention READMEs, plus a CI
workflow, a rule file, two generators, and 54 manifest bumps), LOW by reversibility (every change
is a
git revertaway; no data, schema, or published API), and every consumer that could break iscovered by an existing CI gate. Triggers matched: "new conventions or enforcement mechanisms" and
"infrastructure changes", so the formal stress-test ran.
Stress-test summary
Fresh-context plan reviewer: 1 CRITICAL, 4 IMPORTANT, 4 SUGGESTION. Devil's-advocate: 1 CRITICAL,
1 HIGH, 2 MEDIUM, 4 LOW. All verified against the repository before this revision:
Learn primary and the two
windows-2025jobs. Fixed: no tombstones; Q12 reopened; the checkernow refuses case collisions.
repo's norm is a bump on every body edit (12/12 recent commits). Fixed: scripted bumps for every
touched plugin.
raw URLs. Fixed:
.md-anchored andmain/docs/[A-Z]patterns; bare stems handled by a Tier 1map with the
check-skill.sh+evals.jsonlockstep edit.Phase 3.
origin/main(stale-base gate exit 0); ADR number computed at write time; Phase 5 re-sync.
scripts/skill-description-cap-baseline.txt, socheck-changed-skills.shdowngrades them toWARN; a sanity check runs that gate explicitly.
! git grep -qform;docs/upstream/**(anddocs/specs/**) as Tier 2;em-dash grep over new files; pre-prune SHA in the PR body;
stale-path-verifynoted in the ADR.Verification
On the merged head, all exit 0 unless noted:
scripts/affected-tests.sh --run: every changed file mapped; every selected shell suite passes except the twoprocess budgetcases inplugins/claude-ops/skills/plugins/scripts/cache-content-check.test.sh, which fail identically on a cleanorigin/maincheckout in this container (the strace probe counts nothing here) and belong to a skill this PR does not touch; the 24NOT RUNsuites are the Python, Node, and PowerShell ecosystems the runner does not execute, left to their CI lanes; the run also caught thatscripts/check-script-contract.test.shregisters everycheck-*.sh, fixed inccb23908(findings to stderr, exit 2 without git)scripts/check-docs-naming.sh --check;bash scripts/check-docs-naming.test.sh(PASS=13 FAIL=0, after the two Codex findings: empty dot segments rejected, Bash 3.2-safe case fold)node scripts/generate-catalog.mjs --check,node scripts/generate-cheatsheet.mjs --check,node scripts/validate-plugin-contracts.mjsscripts/check-changelog-parity.sh --check-bump origin/main,--check,--check-order,--check-preserved origin/mainscripts/check-changed-skills.sh origin/main(69 skills, 0 failed);scripts/check-stale-base-overlap.sh --check origin/mainscripts/check-purged-em-dashes.sh;scripts/check-fixture-git-isolation.sh --check;scripts/check-silent-skips.sh;scripts/check-shell-portability.sh --pathsover the new scripts;scripts/check-lane-coverage.sh --check;scripts/check-docs-only-gate.sh --checkshellcheckandshfmt -dover every changed.sh(the oneshfmtdiff, in.claude/cloud-bootstrap.sh, predates this branch and is identical onmain);actionlint .github/workflows/ci.yml;markdownlint-cli2over the 209 changed markdown files (0 issues);typosandeditorconfig-checkerover the changed files (the onetyposhit,ANDedinci.yml, predates this branch)! git grep -qE '<the 13 old basenames>\.md'and the bare-stem form both hold outside the frozen trees;! git grep -qE '(main|blob/main)/docs/[A-Z][A-Z-]*\.md'holds.md: 0 targets newly missing againstorigin/main(the 31 pre-existing misses are example paths and placeholders, identical on both trees)scripts/check-contract-slice-prune.sh --check-diff origin/mainexit 0 on the final headRelated
docs/adr/0033-name-docs-files-lower-kebab-case-with-conventional-exceptions.md(added by this PR); ADR 0018 (the raw-URL citation interface the rename changes)docs/conventions/consumer-config-layering/README.md(the repo's one prior docs rename)🤖 Generated with Claude Code
https://claude.ai/code/session_01CUVaZs5Qssq3xN2gGEHwer