mirror of
https://github.com/wiltodelta/remove-ai-watermarks.git
synced 2026-10-14 13:30:55 +02:00
The code-to-code seams were guarded and the code-to-docs seam was not, so it drifted the way an unguarded seam does. docs/cli.md named none of --adaptive-polish, --controlnet-scale, --detect/--no-detect, --humanize, --strength, --tile-size or --tile-overlap; --no-detect is the escape hatch the CLI's own --sensitivity help recommends. docs/python-api.md named 9 of the 16 InvisibleOptions fields. The new test reads the live Click tree and the live dataclass rather than a hand-kept list. Group spellings by the Click parameter, not by string surgery: a boolean pair can be spelled --strip-metadata/--keep-metadata or --keep-standard/--remove-all, and deriving the partner from a --no- prefix splits those and demands both halves. Mutation-tested both ways -- removing a flag from cli.md and adding a field to InvisibleOptions each fail it. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
89 lines
3.6 KiB
Python
89 lines
3.6 KiB
Python
"""The user-facing docs must name every knob the public surface actually has.
|
|
|
|
The code-to-code seams are already guarded: `test_api.py` compares
|
|
`InvisibleOptions` against the engine signature field by field, and
|
|
`test_cli.py` drives the commands. Nothing guarded the code-to-DOCS seam, and it
|
|
drifted exactly the way an unguarded seam does -- silently, one addition at a
|
|
time. When this was first measured, `docs/cli.md` named none of
|
|
`--adaptive-polish`, `--controlnet-scale`, `--detect/--no-detect`, `--humanize`,
|
|
`--strength`, `--tile-size`, `--tile-overlap` or `--unsharp`, and
|
|
`docs/python-api.md` named 9 of the 16 `InvisibleOptions` fields.
|
|
|
|
These tests read the real Click tree and the real dataclass rather than a
|
|
hand-kept list, so a knob added tomorrow fails here until someone documents it.
|
|
Both accept EITHER spelling of a boolean pair: `--keep-metadata` documents
|
|
`--strip-metadata/--keep-metadata` perfectly well, and requiring the on-form
|
|
would force noise into the guides.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import dataclasses
|
|
from pathlib import Path
|
|
|
|
import click
|
|
|
|
from remove_ai_watermarks.api import InvisibleOptions
|
|
from remove_ai_watermarks.cli import main
|
|
|
|
REPO_ROOT = Path(__file__).resolve().parents[1]
|
|
|
|
# The pages a user is sent to for command and API reference. A knob mentioned
|
|
# only in a research-archive page is not documented for the purpose of this gate.
|
|
CLI_PAGES = ("docs/cli.md", "README.md", "docs/installation.md")
|
|
API_PAGES = ("docs/python-api.md", "README.md")
|
|
|
|
|
|
def _text(pages: tuple[str, ...]) -> str:
|
|
return "".join((REPO_ROOT / page).read_text(encoding="utf-8") for page in pages)
|
|
|
|
|
|
def _cli_options() -> list[tuple[str, list[str]]]:
|
|
"""Every ``(command, spellings)`` pair in the tree.
|
|
|
|
Group by the Click parameter itself, never by string surgery on the names: a
|
|
boolean pair can be spelled anything (``--strip-metadata/--keep-metadata``,
|
|
``--keep-standard/--remove-all``), so deriving the partner from a ``--no-``
|
|
prefix silently splits those pairs into two groups and demands both halves.
|
|
"""
|
|
found: list[tuple[str, list[str]]] = []
|
|
|
|
def walk(command: click.Command, prefix: str = "") -> None:
|
|
if isinstance(command, click.Group):
|
|
for name, sub in sorted(command.commands.items()):
|
|
walk(sub, f"{prefix}{name} ")
|
|
return
|
|
for param in command.params:
|
|
if not isinstance(param, click.Option):
|
|
continue
|
|
names = [o for o in param.opts + param.secondary_opts if o.startswith("--")]
|
|
if names and names != ["--help"]:
|
|
found.append((prefix.strip() or "(root)", names))
|
|
|
|
walk(main)
|
|
return found
|
|
|
|
|
|
def test_every_cli_option_is_named_in_the_command_docs() -> None:
|
|
docs = _text(CLI_PAGES)
|
|
undocumented = [
|
|
f"{command}: {' / '.join(names)}"
|
|
for command, names in _cli_options()
|
|
if not any(name in docs for name in names)
|
|
]
|
|
assert not undocumented, (
|
|
"CLI options absent from " + ", ".join(CLI_PAGES) + ":\n " + "\n ".join(sorted(set(undocumented)))
|
|
)
|
|
|
|
|
|
def test_every_invisible_option_field_is_named_in_the_api_docs() -> None:
|
|
docs = _text(API_PAGES)
|
|
fields = [f.name for f in dataclasses.fields(InvisibleOptions)]
|
|
assert fields, "InvisibleOptions has no fields; the guard would pass vacuously"
|
|
undocumented = [name for name in fields if name not in docs]
|
|
assert not undocumented, (
|
|
"InvisibleOptions fields absent from "
|
|
+ ", ".join(API_PAGES)
|
|
+ f": {undocumented}. Document the field or explain in python-api.md why it is internal."
|
|
)
|