Skip to content

Commit ff00aad

Browse files
committed
Reconcile the tree for 7.12.1
The `sphinx.builder` build step interpolated the metadata value straight into its `run:` block, which zizmor flags as a template injection and which already had Lint red on `main`. It now travels through a step-level `env:`, like `DOC_FILES` in the same file.
1 parent 2a0970b commit ff00aad

8 files changed

Lines changed: 73 additions & 7 deletions

File tree

.claude/agents/grunt-qa.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ Work beyond the local repository: check issues, PRs, and CI runs on GitHub. Fix
1818
## Tools of the trade
1919

2020
- `gh issue list`, `gh pr list`, `gh pr view`, `gh run list`, `gh run view`
21-
- `uv run repomatic lint-repo`, `uv run repomatic metadata`, and every other subcommand
21+
- `repomatic lint-repo`, `repomatic metadata`, and every other subcommand. Inside `kdeldycke/repomatic` itself (a `repomatic/__init__.py` exists) reach it as `uv run repomatic`; anywhere else it is not a project dependency, so use `uvx --exclude-newer '1 week' --exclude-newer-package repomatic=P0D -- repomatic` (see `CLAUDE.md` § Cooldown on every install). A bare `uv run repomatic` downstream dies on `Failed to spawn: repomatic`.
2222
- Tests, type checking, linting (see `CLAUDE.md` § Testing guidelines and § Linting and formatting)
2323

2424
## Checks

.claude/agents/sphinx-docs.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -437,7 +437,7 @@ For Python source files (where the role appeared inside docstrings or comments),
437437

438438
## Auto-generated reference tables
439439

440-
For `pyproject.toml` schemas, CLI command lists, and tool registries, generate the markdown from the source of truth. The pattern: a `docs/docs_update.py` script writes between named auto-region markers, invoked by the upstream `docs.yaml` workflow via `repomatic update-docs`.
440+
For `pyproject.toml` schemas, CLI command lists, and tool registries, generate the markdown from the source of truth. The pattern: a `docs/docs_update.py` script writes between named auto-region markers, invoked by the upstream `autofix.yaml` workflow's `update-docs` job via `repomatic update-docs`. It sits there rather than in `docs.yaml` because it commits a fix like every other autofix job; `docs.yaml` only builds and deploys.
441441

442442
**Marker naming convention.** Name each auto-region descriptively as `{feature}-{kind}` and wrap it in click-extra's `<!-- {feature}-{kind} --> / <!-- {feature}-{kind}-end -->` marker grammar: the grammar of `click_extra.blocks.replace_region` and of click-extra's own `{matrix}` and `mirror` markers. Never use generic bare `<!-- start -->`/`<!-- end -->`. Rationale: a single doc page often holds two or three auto-regions (a summary table, a Mermaid graph, an autodata block). Generic markers force the regenerator to use index-based dispatch and break the moment a region is reordered or removed; named markers stay correct under any rearrangement and grep cleanly. Examples:
443443

@@ -708,6 +708,7 @@ Default-pruning rule:
708708
- If a setting equals its Sphinx (or extension) default, delete it. Don't comment it out — `git blame` already records intent.
709709
- If you keep a setting that *looks* default for documentation purposes, add a one-line comment explaining why ("explicit so future readers see we considered it").
710710
- On every Sphinx or extension upgrade, run `sphinx-build -W -b html docs docs/_build/html`. Treat every `RemovedInSphinxX.YWarning`, `DeprecationWarning`, and `application.ExtensionError` as cleanup work, not noise. Fix them in the same PR as the upgrade.
711+
- **Match the deployed builder, which is not always `html`.** `[tool.repomatic] sphinx.builder` is what the Docs workflow passes to `sphinx-build -b`, and a project serving extension-less URLs sets it to `dirhtml` (`page/index.html` instead of `page.html`). Read it before assuming a local `-b html` reproduces CI. The builder is chosen on the command line, so it is the one Sphinx setting `conf.py` cannot carry: never "fix" its absence there.
711712
- Periodically diff against a fresh `sphinx-quickstart` output in a tmpdir to spot defaults that have shifted under you.
712713
- Drop conditional import shims once the project's minimum Python no longer needs them. The `try: import tomllib / except: import tomli` pattern is dead code on `requires-python = ">=3.11"`. Same for any `if sys.version_info < (3, X):` branch where `X` is now below the floor. The deps group should lose the corresponding fallback dependency in the same PR.
713714
- Always pass `encoding="utf-8"` to `Path.read_text()` calls in `conf.py`. Bare `read_text()` picks up the locale, which on minimal CI runners has bitten many projects.

.claude/skills/sphinx-docs-sync/SKILL.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ For each project, collect (parallelize with sub-Agents when possible). For each
4242
- **`docs/install.md`** (agent § Recipes › `install.md`). Diff section roster, install-method tab order, executables table format, Repology badge, Python compatibility matrix structure, gh attestation verify section.
4343
- **`docs/cli.md` and `docs/configuration.md`** (agent § Recipes). Diff the auto-region between markers and confirm the regenerator script (`docs/docs_update.py`) follows the same shape across projects.
4444
- **Auto-region marker naming** (agent § Auto-generated reference tables, marker naming convention). Grep all `docs/*.md` for `<!-- start -->`/`<!-- end -->` pairs; flag any bare markers, recommend renaming to `<!-- {feature}-{kind}-start -->`. Confirm that named markers across siblings use consistent `{kind}` slugs (`table`, `sankey`, `mindmap`, `chart`, `autodata`, `automodule`, `autodoc`, `reference`).
45-
- **Theme assets** (agent § `conf.py` hygiene › Theme assets and OpenGraph). Confirm `html_logo = "assets/logo-square.svg"`, `html_favicon = "assets/favicon.svg"`, and `ogp_image = "assets/banner-social-light.png"` when banner assets exist. Flag projects with `sphinxext.opengraph` enabled but no `ogp_image` set. Suggest the user run `/brand-assets` on flagged projects to regenerate or backfill the asset set in one pass.
45+
- **Theme assets** (agent § `conf.py` hygiene › Theme assets and OpenGraph). Confirm the sidebar logo is Furo's `light_logo`/`dark_logo` **pair** in `html_theme_options`, each name also listed in `html_static_path`, with `html_logo` **unset** — Furo prefers `html_logo` and skips the pair when it is present, so a lingering `html_logo` is drift to report, never the thing to confirm. Confirm `html_favicon = "assets/favicon.svg"`, and that `ogp_image` is an **absolute** `raw.githubusercontent.com` URL rather than a site-relative `assets/...` path (`docs/assets/` is not copied into the built site, so a relative value 404s for social crawlers). Flag projects with `sphinxext.opengraph` enabled but no `ogp_image` set. Suggest the user run `/brand-assets` on flagged projects to regenerate or backfill the asset set in one pass.
4646
- **`sphinx_issues` migration** (agent § Migrating off `sphinx_issues`). Grep each project for `{issue}` `/`{pr}` ` / `{user}` `/`{commit}` ` (MyST) and `:issue:` `/`:pr:` ` / `:user:` `/`:commit:` ` (reST) across `*.md`, `*.rst`, `*.py`. Flag every occurrence and offer to apply the migration recipe in one pass per repo. After replacement, drop `"sphinx_issues"` from `extensions` in `conf.py`, `"sphinx-issues>=…"` from `[dependency-groups] docs`, and any `issues_github_path` setting unused by other extensions.
4747
- **`pyproject.toml` docs dependency group**. Compare against what `conf.py` actually imports — flag undeclared imports and declared-but-unimported deps. Don't change version pins unless provably stale (a conditional dep on a Python version below the project's floor; a transitively-constrained loose pin held by a meta-extra like `click-extra[sphinx]`).
4848
- **`readme.md`**. Compare badge sets and section structure. Flag a `## Development` section when `claude.md` exists in the same repo (per agent § High-frequency lapses).

.github/workflows/docs.yaml

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -103,12 +103,19 @@ jobs:
103103
sudo apt-get install --yes --no-install-recommends \
104104
fonts-liberation graphviz mandoc
105105
- name: Build documentation
106-
# Install --all-extras so documentation can covers all features of the project, including the optional ones.
106+
# Install --all-extras so documentation can cover all features of the project, including the optional ones.
107107
# The builder is `[tool.repomatic] sphinx.builder`, defaulting to `html`:
108108
# a project serving extension-less URLs sets it to `dirhtml`.
109+
# It travels through the environment rather than being interpolated
110+
# into the command, like DOC_FILES below: the value is whatever the
111+
# repository's own `pyproject.toml` holds, so expanding it inline would
112+
# let a `pull_request`-triggered caller run a fork's string as shell
113+
# code.
114+
env:
115+
SPHINX_BUILDER: ${{ fromJSON(needs.metadata.outputs.metadata).sphinx_builder }}
109116
run: >
110117
uv --no-progress run --frozen --all-extras --group docs --
111-
sphinx-build -b ${{ fromJSON(needs.metadata.outputs.metadata).sphinx_builder }}
118+
sphinx-build -b "$SPHINX_BUILDER"
112119
./docs ./docs/_build
113120
- name: Upload artifact
114121
uses: actions/upload-pages-artifact@fc324d3547104276b827a68afc52ff2a11cc49c9 # v5.0.0

changelog.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
> This version is **not released yet** and is under active development.
77
88
- New `[tool.repomatic] sphinx.builder` config choosing the Sphinx builder the Docs workflow deploys, so a project can publish extension-less URLs with `dirhtml`.
9+
- Fix stale guidance in the bundled agents and skills: the `update-docs` job credited to the wrong workflow, Furo logo and OpenGraph settings contradicting a working configuration, and a `repomatic` invocation that fails outside the canonical repository.
910

1011
## [`7.12.0` (2026-08-14)](https://github.com/kdeldycke/repomatic/compare/v7.11.0...v7.12.0)
1112

docs/configuration.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ gitignore.sync = false
2222
labels.sync = false
2323
mailmap.sync = false
2424
setup-guide = false
25+
sphinx.builder = "dirhtml"
2526
uv-lock.sync = false
2627

2728
dependency-graph.output = "./docs/assets/dependencies.mmd"

docs/workflows.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ on:
1616

1717
jobs:
1818
lint:
19-
uses: kdeldycke/repomatic/.github/workflows/lint.yaml@v7.11.0
19+
uses: kdeldycke/repomatic/.github/workflows/lint.yaml@v7.12.0
2020
```
2121
2222
> [!IMPORTANT]
@@ -363,6 +363,7 @@ docs = [
363363
#### 📖 Deploy Sphinx doc (`deploy-docs`)
364364

365365
- Builds Sphinx-based documentation and publishes it to GitHub Pages using [`sphinx`](https://github.com/sphinx-doc/sphinx), [`upload-pages-artifact`](https://github.com/actions/upload-pages-artifact) and [`deploy-pages`](https://github.com/actions/deploy-pages)
366+
- Builder is `sphinx.builder` in `[tool.repomatic]`, defaulting to `html`; set it to `dirhtml` to publish extension-less URLs (`/page/` instead of `/page.html`)
366367
- **Requires**:
367368
- Python package with a `pyproject.toml` file
368369
- `docs` dependency group

tests/test_claude_assets.py

Lines changed: 56 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,14 +47,15 @@
4747
import re
4848

4949
import pytest
50+
import yaml
5051
from click_extra import schema_field_infos
5152

5253
from repomatic.bundle import get_data_content
5354
from repomatic.config import Config
5455
from repomatic.prepare_release import SELF_PIN_COOLDOWN_EXEMPTION
5556
from repomatic.registry import COMPONENTS_BY_NAME, SKILL_FILENAME
5657

57-
from .conftest import PROJECT_ROOT
58+
from .conftest import PROJECT_ROOT, WORKFLOWS_DIR
5859

5960
CODE_SPAN_RE = re.compile(r"`([^`\n]+)`")
6061
"""Inline code span, the only context these checks read.
@@ -87,6 +88,21 @@
8788
MODULE_PATH_RE = re.compile(r"^(repomatic/[a-z0-9_/]+\.py)$")
8889
"""A package module quoted as a repository-relative path."""
8990

91+
WORKFLOW_FILE_RE = re.compile(r"^(_?[a-z][a-z0-9-]*\.yaml)$")
92+
"""A workflow filename quoted on its own."""
93+
94+
WORKFLOW_JOB_RE = re.compile(
95+
r"`(?P<workflow>_?[a-z][a-z0-9-]*\.yaml)` workflow's `(?P<job>[a-z][a-z0-9-]*)` job"
96+
)
97+
"""An asset attributing a job to the workflow that declares it.
98+
99+
Matched across the prose rather than inside one code span, because the
100+
claim spans two of them. The possessive phrasing is what makes it safe to
101+
read as an assertion: a line merely listing several workflows and a job
102+
name never takes this shape, so the rule stays as under-inclusive as its
103+
siblings while still pinning the one form that states ownership.
104+
"""
105+
90106

91107
def bundled_assets() -> list[tuple[str, str]]:
92108
"""Every bundled skill and agent, as `(asset id, body)` pairs.
@@ -187,3 +203,42 @@ def test_module_paths_exist(asset_id: str, body: str) -> None:
187203
assert (PROJECT_ROOT / module).is_file(), (
188204
f"{asset_id} points at {module}, which no longer exists."
189205
)
206+
207+
208+
@bundled_asset
209+
def test_workflow_files_exist(asset_id: str, body: str) -> None:
210+
"""Every workflow filename quoted in an asset is still a workflow.
211+
212+
Assets name workflows constantly: which one to poll, which one runs a
213+
fix, which one to dispatch. A renamed or retired workflow leaves those
214+
sentences pointing at a file no `gh workflow run` will find.
215+
"""
216+
for span in CODE_SPAN_RE.findall(body):
217+
match = WORKFLOW_FILE_RE.match(span.strip())
218+
if not match:
219+
continue
220+
workflow = match.group(1)
221+
assert (WORKFLOWS_DIR / workflow).is_file(), (
222+
f"{asset_id} names {workflow}, which is not a workflow file."
223+
)
224+
225+
226+
@bundled_asset
227+
def test_attributed_jobs_live_in_the_named_workflow(asset_id: str, body: str) -> None:
228+
"""A job an asset attributes to a workflow is declared by that workflow.
229+
230+
Both halves of the claim can outlive the claim itself: `update-docs`
231+
moved from `docs.yaml` to `autofix.yaml` in `5.7.1`, and the
232+
`sphinx-docs` agent kept crediting `docs.yaml` for eleven releases,
233+
because the workflow still existed and so did the job. Only the pairing
234+
was wrong, which is exactly what no existence check can see.
235+
"""
236+
for match in WORKFLOW_JOB_RE.finditer(body):
237+
workflow, job = match.group("workflow"), match.group("job")
238+
path = WORKFLOWS_DIR / workflow
239+
assert path.is_file(), f"{asset_id} names {workflow}, which is not a workflow."
240+
jobs = yaml.safe_load(path.read_text(encoding="UTF-8")).get("jobs", {})
241+
assert job in jobs, (
242+
f"{asset_id} credits {workflow} with a {job!r} job, which it does not "
243+
f"declare. Its jobs are: {', '.join(sorted(jobs))}."
244+
)

0 commit comments

Comments
 (0)