diff --git a/.github/workflows/pythonpackage.yml b/.github/workflows/pythonpackage.yml index 2c6704bd..b5575ab7 100644 --- a/.github/workflows/pythonpackage.yml +++ b/.github/workflows/pythonpackage.yml @@ -39,5 +39,12 @@ jobs: # llms.txt and llms-full.txt are generated. The Release workflow opens a # pull request when they drift, but that only reports — this is what makes # a stale file fail the build. + # + # The check ignores the `Version:` line on both sides. That line is the + # newest git tag, so it changes as soon as a release lands, while the + # committed file still carries the previous release; counting it as drift + # would turn main and every PR red until the bot's docs PR is merged by + # hand. Version lag is converged by that bot PR instead. Everything else in + # the two files still fails here. - name: Check generated llms files are current run: python scripts/generate_llms.py --check diff --git a/pyproject.toml b/pyproject.toml index 1ac818a7..8042c937 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -98,17 +98,23 @@ extend-exclude = [ # stays silent for that file, including new violations of it. select = ["E", "W", "F", "Q", "I", "UP", "D"] -# The google convention selects a narrower set of pydocstyle rules than the -# default (pep257): 665 findings tree-wide under google versus 864 under pep257, -# 507 of them inside the CI lint scope. It also resolves the two mutually -# exclusive rule pairs (D203/D211 and D212/D213), so ruff emits no warnings. +# The google convention selects a narrower set of pydocstyle rules than leaving +# the convention unset: 665 findings under google, 804 under `pep257`, 864 with +# no convention at all. It also resolves the two mutually exclusive rule pairs +# (D203/D211 and D212/D213), so ruff emits no warnings under this config. [tool.ruff.lint.pydocstyle] convention = "google" # ── Docstring baseline ────────────────────────────────────────────────────── -# 665 findings existed when D was switched on (507 inside the CI lint scope). -# Each entry is a directory tier plus the rules that tier currently violates; -# the number is the finding count at baseline time. +# 665 findings existed when D was switched on: 507 in the photoshop/ subtree, +# 158 in test/, 0 in scripts/. (The lint scope was `photoshop` alone when the +# baseline was written, so 507 was the whole CI scope at the time; it is now +# `photoshop test scripts`, which is all 665.) +# +# Each entry is a directory tier plus the rules that tier currently violates. +# The number on a tier is an UPPER BOUND, not a count: the tiers overlap, so the +# eight labels add up to 883 against 665 actual findings. Do not try to +# reconcile them. # # Read this before touching anything here. `per-file-ignores` is a UNION of # every pattern that matches a file, not a "most specific pattern wins" lookup: @@ -125,7 +131,7 @@ convention = "google" [tool.ruff.lint.per-file-ignores] # Ordered narrowest to widest, but order carries no meaning to ruff: matching # entries are unioned, so the widest entry below is the effective floor for -# everything under photoshop/. +# everything under photoshop/. Counts below are per-tier upper bounds. # # Enum constant tables: one IntEnum class per Photoshop constant, 129 findings. "photoshop/api/enumerations.py" = ["D101"] @@ -137,7 +143,8 @@ convention = "google" "photoshop/api/colors/**" = ["D100", "D102", "D104", "D105", "D107", "D401"] # Manual tests driving a real Photoshop instance, 118 findings. "test/manual_test/**" = ["D100", "D101", "D102", "D401", "D419"] -# Unit tests, 45 findings. +# Unit tests, 45 findings. (118 + 45 = 163, but the test/ subtree measures 158: +# the two tiers overlap, so the labels bound the debt rather than count it.) "test/**" = ["D100", "D101", "D102", "D103", "D104", "D105", "D107", "D202", "D401", "D403"] # The COM wrapper API, 443 findings. "photoshop/api/**" = ["D100", "D101", "D102", "D105", "D107", "D205", "D209", "D212", "D214", "D301", "D400", "D401", "D403", "D404", "D405", "D413", "D414", "D415", "D417"] diff --git a/scripts/generate_llms.py b/scripts/generate_llms.py index 310e4cf6..4c658e64 100644 --- a/scripts/generate_llms.py +++ b/scripts/generate_llms.py @@ -15,6 +15,14 @@ Pass ``--check`` to exit non-zero when the committed files are stale. The ``Python package`` workflow runs that on every pull request, so drift fails the build; the ``llms-txt`` job only opens a pull request to report it. + +``--check`` deliberately ignores the ``Version:`` line on both sides. The +version is the newest git tag, so it changes the moment a release lands while +the committed file still carries the previous release; counting that as drift +would turn ``main`` and every pull request red from each release until the +bot's docs pull request is merged by hand. Version lag is converged by the +``llms-txt`` job instead, which opens a pull request for it. Every other line +-- README, examples, module index -- still fails the check. """ # Import future modules @@ -368,6 +376,30 @@ def build_links() -> list[Link]: return links +def without_version_line(text: str) -> str: + """Return `text` with every ``Version:`` line removed. + + Used only when comparing, never when writing. The stamped version comes + from the newest git tag, so it changes the moment a release lands, while + the committed file still carries the previous release's version. Treating + that as drift would turn `main` and every pull request red from each + release until the bot's docs pull request is merged by hand. + + The version is still regenerated and still committed: the `llms-txt` job + opens a pull request for it, which is how it converges. This function only + keeps the drift *gate* from firing on version lag alone. + """ + return "\n".join(line for line in text.splitlines() if not line.startswith("Version: ")) + + +def is_current(name: str, expected: str) -> bool: + """Return True when the committed `name` matches `expected`, ignoring version.""" + committed = read(OUTPUT_DIR / name) + if not committed: + return False + return without_version_line(committed) == without_version_line(expected) + + def write_if_changed(path: Path, content: str) -> bool: """Write `content` to `path` when it differs; return True when it changed. @@ -397,10 +429,13 @@ def main() -> int: full_text = render_full(links) if args.check: + # Ignores the `Version:` line on both sides: version lag is converged by + # the llms-txt bot pull request, not by failing the build. Anything else + # — README, examples, module index — still fails here. stale = [ name for name, text in (("llms.txt", index_text), ("llms-full.txt", full_text)) - if read(OUTPUT_DIR / name) != text + if not is_current(name, text) ] if stale: joined = ", ".join(stale)