Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .github/workflows/pythonpackage.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
25 changes: 16 additions & 9 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand All @@ -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"]
Expand All @@ -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"]
Expand Down
37 changes: 36 additions & 1 deletion scripts/generate_llms.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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.

Expand Down Expand Up @@ -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)
Expand Down
Loading