Quarterly cold-eyes review, 2026-09-03 (PyDevices/.github#25, standards S1/S4/S6). Read-only inspection by a repo-inspector agent from a fresh clone in a scratch directory; every claim below carries the command or URL that produced it. Filed by Arthur. Nothing here was changed by the review itself.
S1 (clean clone builds per its README): PASS — pip and mip paths both work from a fresh clone; the repo builds, tests (4) and docs (strict) pass. The only S1-lens gap is documentary: the README never tells a stranger how to install or test from the clone.
S4 (README to working example, first try): VERDICT: PASS for "README to one working example, first try" — FAIL for the section the README itself titles "Quick Start".
Findings (9)
[HIGH] The section titled "Quick Start" is the one thing in the README that cannot run — import board_config is undefined everywhere in the repo
README.md:70 ## Quick Start: Painting on a Display; README.md:73 import board_config; README.md:76 display_drv = board_config.display_drv.
$ ./venv1/bin/python quickstart.py # README.md:72-84, copy-pasted verbatim
ModuleNotFoundError: No module named 'board_config'
$ grep -rn "board_config" --include=".md" --include=".py" --include=".toml" --include=".yml" .
README.md:73 / README.md:76 / docs/integrations.md:58 / docs/integrations.md:61
Four uses, zero definitions and zero links. board_config is not a pip- or mip-installable module: it lives as per-board files under PyDevices/pydevices board_configs/*/board_config.py, which a user copies onto the device. Nothing in palettes says so.
This also collides with README.md:5's "zero dependencies on any other PyDevices library" — the README's only end-to-end example silently requires another PyDevices repo. docs/integrations.md repeats it and adds a second inconsistency: the heading at docs/integrations.md:53 is "Using with Display Drivers (displaydev)" but the snippet at :58 imports board_config and never mentions displaydev.
Suggested fix: Either make the Quick Start self-contained (print RGB565 values, or drive a pygraphics.FrameBuffer the way docs/integrations.md:11-28 already does successfully), or keep the display example and add one sentence naming where board_config comes from with a link to the pydevices board_configs directory — and reword README.md:5 so "zero dependencies" is not contradicted three sections later.
[MEDIUM] docs advertise four color depths that raise ValueError, and mislabel two that work
docs/color-math.md:76-86, "## 4. Depth Constants", lists 1, 2, 4, 8, 16, 24, 32. docs/palette-gallery.md:71 repeats it: "color_depth (int): Bit depth (16 for RGB565, 24 for RGB888, 32 for ARGB8888)."
$ ./venv1/bin/python -c "from palettes import get_palette; [print(d, get_palette(name='default',color_depth=d)[1]) for d in (1,2,4,8,16,24,32)]"
color_depth= 1 -> ValueError: Invalid color depth
color_depth= 2 -> ValueError: Invalid color depth
color_depth= 4 -> 128
color_depth= 8 -> 2
color_depth=16 -> 16
color_depth=24 -> 128
color_depth=32 -> ValueError: Invalid color depth
lib/palettes/init.py:169-175 only handles 24, 4, 16, 8 and raises otherwise. So 1, 2 and 32 are documented capabilities that do not exist. On top of that the table mislabels the two that do: it calls 4 "4-bit grayscale" and 8 "8-bit grayscale", where the code's own docstring (lib/palettes/init.py:97-98) says 4 (24-bit index) and 8 (RGB332), and color332() at :229 is plainly RGB332, not grayscale. The 32-bit ARGB8888 claim in particular is the one a stranger writing a desktop tool would reach for first.
Suggested fix: Cut rows 1, 2 and 32 from docs/color-math.md:80-86 or mark them explicitly unsupported, and relabel 4 and 8 to match lib/palettes/init.py:97-98. Drop 32 from docs/palette-gallery.md:71. A one-line parametrized test over the supported depth set would keep the table honest.
[MEDIUM] completeness: color_depth=4 is a documented half-surface — the named-attribute path implements indexed mode, __getitem__ does not
Published API reference (live at https://palettes.readthedocs.io/en/latest/reference/palettes/, sourced from lib/palettes/init.py:97-98 and :108-111): "color_depth (int): Output format for __getitem__: 4 (24-bit index), 8 (RGB332), 16 (RGB565), or 24 (0xRRGGBB)."
But the two halves of the surface disagree at depth 4:
lib/palettes/init.py:132 color = list(self._names.keys()).index(color) # named attrs -> a table INDEX
lib/palettes/init.py:169 if self._color_depth == 24 or self._color_depth == 4: return r << 16 | g << 8 | b # getitem -> packed RGB
$ ./venv1/bin/python -c "..."
p4.RED = 12 (index into name table)
p4[12] = 0xff0000 (packed 0xRRGGBB)
p24[12] = 0xff0000 -> depth 4 and depth 24 return identical values: True
So at depth 4 the indexed path is a duplicate of depth 24 and never returns an index, while the named path returns an index that no other API accepts. Judged along the axes this repo declares (family x color_depth x swapped x access-path): family x depth is complete and self-consistent for 8/16/24 across all four families (verified 4x4 matrix, all cells OK); swapped correctly no-ops outside depth 16, which the docstring at :99 records. Depth 4 is the one cell where a documented half of the surface has nothing implementing the other half, and no exclusion is recorded anywhere.
Suggested fix: Decide what depth 4 means and make both paths agree: either __getitem__ returns the palette index at depth 4 (matching "24-bit index" and the named attributes), or depth 4 is dropped and the docstring at :97-98 and :108-111 stops advertising it. Whichever way, record it in the class docstring so the published reference is the truth.
[MEDIUM] Published API reference renders raw Sphinx roles as literal text
$ grep -rno ":(meth|class|attr|func|mod):`[^\`]*`" lib/ | wc -l
29
lib/palettes/init.py:8 :meth:get_palette to create a palette...
lib/palettes/wheel.py:30 name (str): Prefix for :attr:~palettes.Palette.name...
lib/palettes/wheel.py:31 color_depth (int): Output format; see :class:~palettes.Palette.
mkdocs.yml sets docstring_style: google; Griffe does not resolve Sphinx roles, so they survive to the page. Scraping the live page:
$ curl -sSL https://palettes.readthedocs.io/en/latest/reference/palettes/ | (strip tags)
literal ':meth:' occurrences on the page: 6
literal ':class:' occurrences on the page: 5
literal ':attr:' occurrences on the page: 5
rendered text: "color_depth int Output format for :meth: getitem : 4 (24-bit index)..."
Org convention: /home/brad/gh/pydevices/dotgithub/docs/docstrings.md:10-11 — "Google style only" / "Do not mix Sphinx directives", written specifically so "the generated reference pages stay accurate". This is the first page a stranger lands on from README.md:104.
Suggested fix: Replace the 29 roles with mkdocstrings cross-reference syntax ([Palette][palettes.Palette]) or plain backticked names. Add .venv-docs/bin/mkdocs build 2>&1 | grep -i griffe — already the documented gate at dotgithub/docs/docstrings.md:60-68 — plus a grep for :meth:|:class:|:attr: to the tests workflow so it cannot regress.
[MEDIUM] Platform claims carry no proof tier, and two of them have no proof at all
README.md:5 claims MicroPython, CircuitPython, CPython (desktop/server) and PyScript (Web). docs/index.md:10 adds "Direct WebAssembly · Pyodide". Repo description on GitHub: "Cross-platform color palette library for MicroPython, CircuitPython, and CPython."
$ grep -rin "bench-proven|ci-proven|community-verified|proof tier|support tier" --include=".md" --include=".yml" .
(no matches, rc=1)
dotgithub/docs/platform-support-tiers.md:8-9: "Every platform claim in a PyDevices README, vision, or roadmap carries one of three labels." None here does.
What is actually proven, from my own runs: CPython is CI-proven (.github/workflows/tests.yml, ubuntu-latest, python 3.13, 4 tests) and I bench-proved it; PyScript/WASM is bench-proven (the readthedocs live demo executed and printed "Drawn 32 color wheel bands and 10 Material Design amber shades!"). MicroPython and CircuitPython have zero automated proof anywhere in the repo — and the ad-hoc run I did (see finding 1) shows they do not in fact behave identically, so the strongest claim in the README sits on the least-evidenced platforms.
Suggested fix: Label each platform in README.md:5 and docs/index.md:10 per platform-support-tiers.md. Given the unix MicroPython and CircuitPython binaries already on the bench, the cheap and honest move is to add both to the tests workflow (or a scheduled job) and claim CI-proven for all four — which also gates finding 1.
[MEDIUM] Production PyPI ships pydevices-palettes 0.0.12 and nothing in the repo tells a stranger it exists or that it is a park
README.md:95-98 gives only the TestPyPI form. AGENTS.md:20-22 says "the TestPyPI distribution is pydevices-palettes". But:
$ curl -sS https://pypi.org/pypi/pydevices-palettes/json -> 200, version 0.0.12, releases ['0.0.12']
$ ./venv3/bin/pip install pydevices-palettes # the obvious command
Successfully installed pydevices-palettes-0.0.12
installed version: 0.0.12 ; wheel[180] = 2047
So the command every Python user types first succeeds, silently, one release behind the TestPyPI 0.0.13 the README points to, with no note anywhere that the production name is a deliberate parked mirror. Reporting this under §4.2 rather than as a broken release: the mirror-park is a recorded decision, and TestPyPI-only publication is explicitly not a defect. What fails is the ledger's own visibility clause — "each is recorded and visible where a stranger would otherwise misread it" (§4.2 preamble, modernization doc lines 230-234). A stranger reading README.md:88-98 cannot tell that PyPI resolves at all, let alone that it resolves stale.
Suggested fix: One line under Installation naming the production package and its status ("pydevices-palettes is also parked on PyPI at 0.0.12; releases currently ship to TestPyPI — use the command above"), and fix AGENTS.md:21 which now understates where the distribution lives. Same edit is presumably owed by the other five parked names.
[LOW] docs/palette-gallery.md's cube table invites size= values the parameter does not accept, and nothing validates them
docs/palette-gallery.md:104-114 heads its first column "Cube Size" with values 8 / 27 / 64 / 125, then immediately below at :111-113:
size is the steps per channel: size=4 -> 4x4x4 = 64 colors
cube = get_palette("cube", color_depth=16, size=4)
The size kwarg is the edge length (2-5, per lib/palettes/cube.py:36 and :48), so the table's own column values are not valid arguments for the parameter it sits next to. There is no validation:
$ ./venv1/bin/python -c "from palettes import get_palette; [print('size',s,'-> len',len(get_palette('cube',size=s,color_depth=16))) for s in (2,3,4,5,8,27,64,125)]"
size=2 -> len 8 ; size=3 -> 27 ; size=4 -> 64 ; size=5 -> 125
size=8 -> len 512 ; size=27 -> len 19683 ; size=64 -> len 262144 ; size=125 -> len 1953125
At size>5 lib/palettes/cube.py:60-61 silently falls through to the CUBE125 name table, so color_name() is wrong too. A reader copying "27" out of the table onto an ESP32 gets a 19,683-entry palette instead of a useful error.
Suggested fix: Rename the column to "Total Colors" and add a "size= argument" column with 2/3/4/5, and raise ValueError in CubePalette.init for size outside 2-5 — the docstring at lib/palettes/cube.py:36 already states the constraint, the code just doesn't enforce it.
[LOW] AGENTS.md gives a test command that AGENTS.md itself documents as failing
AGENTS.md:11-16 ("## Tests and lint"):
python3 -m unittest discover -s tests
AGENTS.md:26-33 ("## Cursor Cloud specific instructions") then says the same command "fails with ModuleNotFoundError: No module named 'palettes'" and gives the working form.
$ cd palettes && python3 -m unittest discover -s tests
ModuleNotFoundError: No module named 'palettes'
Ran 4 tests in 0.001s
FAILED (errors=4)
$ PYTHONPATH=lib python3 -m unittest discover -s tests
Ran 4 tests in 0.012s
OK
The failing form is the one under the heading a contributor reads; the correction is 15 lines down under a vendor-specific heading, so the first command in the file is the broken one.
Suggested fix: Put PYTHONPATH=lib on the command at AGENTS.md:14 (matching .github/workflows/tests.yml, which sets env: PYTHONPATH: lib) and reduce the Cursor Cloud section to the venv note.
[LOW] README documents no way to install or test from a clone
README.md:88-98 offers only mip.install(...) and pip install -i https://test.pypi.org/simple/ ... pydevices-palettes. There is no pip install ., no pip install -e ., no test command, and no CONTRIBUTING.md in the repo (gh api repos/PyDevices/palettes/contents/CONTRIBUTING.md -> 404). docs/index.md:37-69 ("Quick Install") repeats the same four install routes and adds none from source.
All three from-source paths work — I ran them (python -m build ./palettes -> both dists; PYTHONPATH=lib python3 -m unittest discover -s tests -> OK; ruff check lib tests scripts -> All checks passed) — the README simply never says they exist. The only place they are written down is AGENTS.md:11-16, which is agent-facing and carries the broken form (previous finding). So S1's "clean clone builds per its README" passes on mechanics but has no README recipe to follow.
Suggested fix: Add a short "From source" block to README Installation: clone, pip install -e . (or PYTHONPATH=lib), python -m unittest discover -s tests, ruff check lib tests scripts — the four commands CI already runs.
Each finding is independent; tick them off here or split any that deserves its own thread.
S1 (clean clone builds per its README): PASS — pip and mip paths both work from a fresh clone; the repo builds, tests (4) and docs (strict) pass. The only S1-lens gap is documentary: the README never tells a stranger how to install or test from the clone.
S4 (README to working example, first try): VERDICT: PASS for "README to one working example, first try" — FAIL for the section the README itself titles "Quick Start".
Findings (9)
[HIGH] The section titled "Quick Start" is the one thing in the README that cannot run —
import board_configis undefined everywhere in the repoREADME.md:70
## Quick Start: Painting on a Display; README.md:73import board_config; README.md:76display_drv = board_config.display_drv.$ ./venv1/bin/python quickstart.py # README.md:72-84, copy-pasted verbatim
ModuleNotFoundError: No module named 'board_config'
$ grep -rn "board_config" --include=".md" --include=".py" --include=".toml" --include=".yml" .
README.md:73 / README.md:76 / docs/integrations.md:58 / docs/integrations.md:61
Four uses, zero definitions and zero links.
board_configis not a pip- or mip-installable module: it lives as per-board files under PyDevices/pydevicesboard_configs/*/board_config.py, which a user copies onto the device. Nothing in palettes says so.This also collides with README.md:5's "zero dependencies on any other PyDevices library" — the README's only end-to-end example silently requires another PyDevices repo. docs/integrations.md repeats it and adds a second inconsistency: the heading at docs/integrations.md:53 is "Using with Display Drivers (
displaydev)" but the snippet at :58 importsboard_configand never mentionsdisplaydev.Suggested fix: Either make the Quick Start self-contained (print RGB565 values, or drive a
pygraphics.FrameBufferthe way docs/integrations.md:11-28 already does successfully), or keep the display example and add one sentence naming whereboard_configcomes from with a link to the pydevices board_configs directory — and reword README.md:5 so "zero dependencies" is not contradicted three sections later.[MEDIUM] docs advertise four color depths that raise
ValueError, and mislabel two that workdocs/color-math.md:76-86, "## 4. Depth Constants", lists
1,2,4,8,16,24,32. docs/palette-gallery.md:71 repeats it: "color_depth(int): Bit depth (16for RGB565,24for RGB888,32for ARGB8888)."$ ./venv1/bin/python -c "from palettes import get_palette; [print(d, get_palette(name='default',color_depth=d)[1]) for d in (1,2,4,8,16,24,32)]"
color_depth= 1 -> ValueError: Invalid color depth
color_depth= 2 -> ValueError: Invalid color depth
color_depth= 4 -> 128
color_depth= 8 -> 2
color_depth=16 -> 16
color_depth=24 -> 128
color_depth=32 -> ValueError: Invalid color depth
lib/palettes/init.py:169-175 only handles 24, 4, 16, 8 and raises otherwise. So 1, 2 and 32 are documented capabilities that do not exist. On top of that the table mislabels the two that do: it calls
4"4-bit grayscale" and8"8-bit grayscale", where the code's own docstring (lib/palettes/init.py:97-98) says4(24-bit index) and8(RGB332), and color332() at :229 is plainly RGB332, not grayscale. The 32-bit ARGB8888 claim in particular is the one a stranger writing a desktop tool would reach for first.Suggested fix: Cut rows 1, 2 and 32 from docs/color-math.md:80-86 or mark them explicitly unsupported, and relabel 4 and 8 to match lib/palettes/init.py:97-98. Drop
32from docs/palette-gallery.md:71. A one-line parametrized test over the supported depth set would keep the table honest.[MEDIUM] completeness:
color_depth=4is a documented half-surface — the named-attribute path implements indexed mode,__getitem__does notPublished API reference (live at https://palettes.readthedocs.io/en/latest/reference/palettes/, sourced from lib/palettes/init.py:97-98 and :108-111): "color_depth (int): Output format for
__getitem__: 4 (24-bit index), 8 (RGB332), 16 (RGB565), or 24 (0xRRGGBB)."But the two halves of the surface disagree at depth 4:
lib/palettes/init.py:132
color = list(self._names.keys()).index(color)# named attrs -> a table INDEXlib/palettes/init.py:169
if self._color_depth == 24 or self._color_depth == 4: return r << 16 | g << 8 | b# getitem -> packed RGB$ ./venv1/bin/python -c "..."
p4.RED = 12 (index into name table)
p4[12] = 0xff0000 (packed 0xRRGGBB)
p24[12] = 0xff0000 -> depth 4 and depth 24 return identical values: True
So at depth 4 the indexed path is a duplicate of depth 24 and never returns an index, while the named path returns an index that no other API accepts. Judged along the axes this repo declares (family x color_depth x swapped x access-path): family x depth is complete and self-consistent for 8/16/24 across all four families (verified 4x4 matrix, all cells OK);
swappedcorrectly no-ops outside depth 16, which the docstring at :99 records. Depth 4 is the one cell where a documented half of the surface has nothing implementing the other half, and no exclusion is recorded anywhere.Suggested fix: Decide what depth 4 means and make both paths agree: either
__getitem__returns the palette index at depth 4 (matching "24-bit index" and the named attributes), or depth 4 is dropped and the docstring at :97-98 and :108-111 stops advertising it. Whichever way, record it in the class docstring so the published reference is the truth.[MEDIUM] Published API reference renders raw Sphinx roles as literal text
$ grep -rno ":(meth|class|attr|func|mod):`[^\`]*`" lib/ | wc -l
29
lib/palettes/init.py:8
:meth:get_paletteto create a palette...lib/palettes/wheel.py:30
name (str): Prefix for :attr:~palettes.Palette.name...lib/palettes/wheel.py:31
color_depth (int): Output format; see :class:~palettes.Palette.mkdocs.yml sets
docstring_style: google; Griffe does not resolve Sphinx roles, so they survive to the page. Scraping the live page:$ curl -sSL https://palettes.readthedocs.io/en/latest/reference/palettes/ | (strip tags)
literal ':meth:' occurrences on the page: 6
literal ':class:' occurrences on the page: 5
literal ':attr:' occurrences on the page: 5
rendered text: "color_depth int Output format for :meth: getitem : 4 (24-bit index)..."
Org convention: /home/brad/gh/pydevices/dotgithub/docs/docstrings.md:10-11 — "Google style only" / "Do not mix Sphinx directives", written specifically so "the generated reference pages stay accurate". This is the first page a stranger lands on from README.md:104.
Suggested fix: Replace the 29 roles with mkdocstrings cross-reference syntax (
[Palette][palettes.Palette]) or plain backticked names. Add.venv-docs/bin/mkdocs build 2>&1 | grep -i griffe— already the documented gate at dotgithub/docs/docstrings.md:60-68 — plus a grep for:meth:|:class:|:attr:to the tests workflow so it cannot regress.[MEDIUM] Platform claims carry no proof tier, and two of them have no proof at all
README.md:5 claims MicroPython, CircuitPython, CPython (desktop/server) and PyScript (Web). docs/index.md:10 adds "Direct WebAssembly · Pyodide". Repo description on GitHub: "Cross-platform color palette library for MicroPython, CircuitPython, and CPython."
$ grep -rin "bench-proven|ci-proven|community-verified|proof tier|support tier" --include=".md" --include=".yml" .
(no matches, rc=1)
dotgithub/docs/platform-support-tiers.md:8-9: "Every platform claim in a PyDevices README, vision, or roadmap carries one of three labels." None here does.
What is actually proven, from my own runs: CPython is CI-proven (.github/workflows/tests.yml, ubuntu-latest, python 3.13, 4 tests) and I bench-proved it; PyScript/WASM is bench-proven (the readthedocs live demo executed and printed "Drawn 32 color wheel bands and 10 Material Design amber shades!"). MicroPython and CircuitPython have zero automated proof anywhere in the repo — and the ad-hoc run I did (see finding 1) shows they do not in fact behave identically, so the strongest claim in the README sits on the least-evidenced platforms.
Suggested fix: Label each platform in README.md:5 and docs/index.md:10 per platform-support-tiers.md. Given the unix MicroPython and CircuitPython binaries already on the bench, the cheap and honest move is to add both to the tests workflow (or a scheduled job) and claim CI-proven for all four — which also gates finding 1.
[MEDIUM] Production PyPI ships
pydevices-palettes0.0.12 and nothing in the repo tells a stranger it exists or that it is a parkREADME.md:95-98 gives only the TestPyPI form. AGENTS.md:20-22 says "the TestPyPI distribution is
pydevices-palettes". But:$ curl -sS https://pypi.org/pypi/pydevices-palettes/json -> 200, version 0.0.12, releases ['0.0.12']
$ ./venv3/bin/pip install pydevices-palettes # the obvious command
Successfully installed pydevices-palettes-0.0.12
installed version: 0.0.12 ; wheel[180] = 2047
So the command every Python user types first succeeds, silently, one release behind the TestPyPI 0.0.13 the README points to, with no note anywhere that the production name is a deliberate parked mirror. Reporting this under §4.2 rather than as a broken release: the mirror-park is a recorded decision, and TestPyPI-only publication is explicitly not a defect. What fails is the ledger's own visibility clause — "each is recorded and visible where a stranger would otherwise misread it" (§4.2 preamble, modernization doc lines 230-234). A stranger reading README.md:88-98 cannot tell that PyPI resolves at all, let alone that it resolves stale.
Suggested fix: One line under Installation naming the production package and its status ("
pydevices-palettesis also parked on PyPI at 0.0.12; releases currently ship to TestPyPI — use the command above"), and fix AGENTS.md:21 which now understates where the distribution lives. Same edit is presumably owed by the other five parked names.[LOW] docs/palette-gallery.md's cube table invites
size=values the parameter does not accept, and nothing validates themdocs/palette-gallery.md:104-114 heads its first column "Cube Size" with values 8 / 27 / 64 / 125, then immediately below at :111-113:
size is the steps per channel: size=4 -> 4x4x4 = 64 colors
cube = get_palette("cube", color_depth=16, size=4)
The
sizekwarg is the edge length (2-5, per lib/palettes/cube.py:36 and :48), so the table's own column values are not valid arguments for the parameter it sits next to. There is no validation:$ ./venv1/bin/python -c "from palettes import get_palette; [print('size',s,'-> len',len(get_palette('cube',size=s,color_depth=16))) for s in (2,3,4,5,8,27,64,125)]"
size=2 -> len 8 ; size=3 -> 27 ; size=4 -> 64 ; size=5 -> 125
size=8 -> len 512 ; size=27 -> len 19683 ; size=64 -> len 262144 ; size=125 -> len 1953125
At size>5 lib/palettes/cube.py:60-61 silently falls through to the CUBE125 name table, so
color_name()is wrong too. A reader copying "27" out of the table onto an ESP32 gets a 19,683-entry palette instead of a useful error.Suggested fix: Rename the column to "Total Colors" and add a "
size=argument" column with 2/3/4/5, and raiseValueErrorin CubePalette.init for size outside 2-5 — the docstring at lib/palettes/cube.py:36 already states the constraint, the code just doesn't enforce it.[LOW] AGENTS.md gives a test command that AGENTS.md itself documents as failing
AGENTS.md:11-16 ("## Tests and lint"):
python3 -m unittest discover -s tests
AGENTS.md:26-33 ("## Cursor Cloud specific instructions") then says the same command "fails with
ModuleNotFoundError: No module named 'palettes'" and gives the working form.$ cd palettes && python3 -m unittest discover -s tests
ModuleNotFoundError: No module named 'palettes'
Ran 4 tests in 0.001s
FAILED (errors=4)
$ PYTHONPATH=lib python3 -m unittest discover -s tests
Ran 4 tests in 0.012s
OK
The failing form is the one under the heading a contributor reads; the correction is 15 lines down under a vendor-specific heading, so the first command in the file is the broken one.
Suggested fix: Put
PYTHONPATH=libon the command at AGENTS.md:14 (matching .github/workflows/tests.yml, which setsenv: PYTHONPATH: lib) and reduce the Cursor Cloud section to the venv note.[LOW] README documents no way to install or test from a clone
README.md:88-98 offers only
mip.install(...)andpip install -i https://test.pypi.org/simple/ ... pydevices-palettes. There is nopip install ., nopip install -e ., no test command, and no CONTRIBUTING.md in the repo (gh api repos/PyDevices/palettes/contents/CONTRIBUTING.md-> 404). docs/index.md:37-69 ("Quick Install") repeats the same four install routes and adds none from source.All three from-source paths work — I ran them (
python -m build ./palettes-> both dists;PYTHONPATH=lib python3 -m unittest discover -s tests-> OK;ruff check lib tests scripts-> All checks passed) — the README simply never says they exist. The only place they are written down is AGENTS.md:11-16, which is agent-facing and carries the broken form (previous finding). So S1's "clean clone builds per its README" passes on mechanics but has no README recipe to follow.Suggested fix: Add a short "From source" block to README Installation: clone,
pip install -e .(orPYTHONPATH=lib),python -m unittest discover -s tests,ruff check lib tests scripts— the four commands CI already runs.Each finding is independent; tick them off here or split any that deserves its own thread.