Overview
agents/conductors/hygiene/hygiene.sh hardcodes the repos it scans:
LIB_REPOS=(PyAutoNerves PyAutoFit PyAutoArray PyAutoGalaxy PyAutoLens)
ORG_REPOS=(PyAutoBrain PyAutoHands PyAutoHeart PyAutoMind)
DOC_REPOS=(PyAutoFit PyAutoGalaxy PyAutoLens)
PyAutoMind/repos.yaml — the body map, the single source of repo identity — declares six libraries and seven organs. So the conductor silently skips the CTI and Reduce libraries, classes PyAutoNerves as a library where the manifest calls it an organ, and covers four of seven organs. Every clean bill of health it has issued understates reality.
This is internal inconsistency, not a scope decision: the sibling scanners _hygiene_config.py and _hygiene_refs.py already reach PyAutoCTI. And repos_sync.py cannot catch it — its tenant-firewall entry for hygiene.sh (line 525) pins the drifted set as an allowlist rather than checking coverage against the manifest.
Measured, not asserted
Reproduced at PYAUTO_ROOT=/home/user before planning:
|
reported |
true |
crlf cosmetic .py count |
5 |
127 (122 in PyAutoCTI alone) |
deps pyproject.toml audited |
5 |
6 |
tidy managed checkouts scanned |
9 |
~17 |
$ hygiene.sh crlf
crlf clean 0 executable scripts w/ CRLF ...; 5 library .py w/ CRLF (cosmetic ...)
$ git -C PyAutoCTI grep -Il $'\r$' -- '*.py' | wc -l
122
A related third defect ships with this fix: when the scan root is empty or absent, every array-driven mode currently reports clean with no warning. A clean verdict over zero repos is the same failure as a clean verdict over five of eleven — it needs a distinct unscanned signal.
Scope: the coverage repair only. Widening the scan surfaces a large backlog of genuine new findings (CRLF, artifacts, dep caps). Triaging that backlog is a separate task and no finding is fixed here.
Plan
- Add a stdlib body-map reader to the hygiene conductor that returns repo names by category from
PyAutoMind/repos.yaml (PyYAML when importable, minimal parser otherwise — the conductor must not gain a hard dependency).
- Replace
LIB_REPOS / ORG_REPOS / DOC_REPOS in hygiene.sh with sets derived from that reader; no repo name is written in the script any more.
- Key each mode off what it actually needs —
deps/packaging off "ships a pyproject.toml", docs off "has docs/api/" — instead of a hand-kept array.
- Make an empty or absent scan root, and an unreachable body map, report
unscanned with a named reason instead of clean.
- Add a coverage check to
repos_sync.py that fails if the derived sets stop matching repos.yaml or if a repo name is re-hardcoded into a *_REPOS=(…) array, and shrink the now-obsolete firewall allowlist entry.
- Cover both in tests; update the hygiene
AGENTS.md mode table.
Detailed implementation plan
Work Classification
Library (organ code, no workspace leg).
Affected Repositories
- PyAutoBrain (primary) — the conductor fix
- PyAutoMind — the
repos_sync.py coverage check
Branch Survey
| Repository |
Current Branch |
Dirty? |
| ./PyAutoBrain |
claude/hygiene-coverage-drift-kso7h1 |
clean |
| ./PyAutoMind |
claude/hygiene-coverage-drift-kso7h1 |
clean |
Branch: claude/hygiene-coverage-drift-kso7h1 (mandated by the session; overrides the usual feature/<task-name>).
Worktree root: none — cloud session, no ~/Code/PyAutoLabs-wt; work happens in the canonical checkouts.
Implementation Steps
-
New PyAutoBrain/agents/conductors/hygiene/_hygiene_repos.py — --root <dir> --category <library|organ|workspace> [--json]. Resolves the body map via PYAUTO_MIND → <brain-parent>/PyAutoMind → $PYAUTO_ROOT/PyAutoMind, mirroring resolve_mind in agents/_common.sh. yaml.safe_load when PyYAML imports, else a two-level indent walk over repos: → <Name>: → category:. Contains zero repo-name literals, so it is firewall-clean by construction. Exit 3 + empty output when the map is unresolvable, so bash can tell "none declared" from "none present".
-
hygiene.sh — delete lines 64–66; populate LIB_REPOS / ORG_REPOS / WS_REPOS via mapfile from the helper and set BODY_MAP_OK. Then:
CODE_REPOS = LIB+ORG → prescan_tidy, enumerate_condemn_candidates
SCAN_REPOS = LIB+ORG+WS → prescan_crlf, prescan_artifacts (replaces the hardcoded workspace triple; category workspace adds autocti_workspace)
prescan_deps / prescan_packaging → CODE_REPOS filtered on pyproject.toml present, not on category — a straight category mapping would drop PyAutoNerves (organ, but ships a distribution) and re-create this bug in a new place
prescan_docs → CODE_REPOS filtered on docs/api/; ${#DOC_REPOS[@]} in the summary becomes the count actually scanned
-
hygiene.sh unscanned signal — new managed_present() counts body-map repos with a .git under $ROOT. emit_json_row gains "scanned": N; N == 0 ⇒ status: "unscanned", count: null. Human render_row prints tag unscanned; the default scan leads with a banner naming $ROOT and the reason (scan root empty/absent vs body map unavailable), and "Recommended next" says so rather than claiming no findings.
-
PyAutoMind/scripts/repos_sync.py — new check_hygiene_coverage(root, repos) registered in main()'s checks dict:
- leg A — invoke
_hygiene_repos.py --json; assert derived library/organ/workspace sets equal the manifest's, reporting each missing/extra name
- leg B — regex
^\s*[A-Z_]*REPOS=\(([^)]*)\) over hygiene.sh; any manifest repo name inside is drift ("re-hardcoded — derive from the body map")
- skips cleanly when PyAutoBrain is not checked out, matching
check_heart / check_labels
- trim
FIREWALL_ALLOWLIST["…/hygiene.sh"] to the tokens that genuinely survive, verified by running --check rather than assumed
-
Tests — PyAutoBrain/tests/test_hygiene_conductor.py: derived sets match repos.yaml; PyAutoCTI/PyAutoReduce reach the crlf scan; empty root ⇒ unscanned across array modes + banner; unresolvable body map ⇒ unscanned, exit 0, valid JSON. New PyAutoMind/scripts/test_repos_sync_coverage.py (matching the existing test_spawn_*.py convention): passes on the real tree, fails on a re-hardcoded array and on a dropped category.
-
Docs — hygiene AGENTS.md mode table: "the 3 doc repos" and the library-only framing become the derived rule; add the unscanned status alongside debris/finding/timing/surface/advisory.
Key Files
PyAutoBrain/agents/conductors/hygiene/hygiene.sh — lines 64–66 (the arrays), prescan_*, emit_json_row, render_row
PyAutoBrain/agents/conductors/hygiene/_hygiene_repos.py — new body-map reader
PyAutoBrain/agents/conductors/hygiene/AGENTS.md — mode table + kinds
PyAutoMind/scripts/repos_sync.py — check_hygiene_coverage, FIREWALL_ALLOWLIST line 525
PyAutoMind/repos.yaml — read-only source of truth; unchanged
Testing
pytest PyAutoBrain/tests/test_hygiene_conductor.py; python3 PyAutoMind/scripts/repos_sync.py --check must exit 0; before/after hygiene crlf and hygiene --json under PYAUTO_ROOT=/home/user demonstrating 5 → 167 and the unscanned path under an empty root.
Trade-off accepted
Deriving the workspace set from category workspace raises the cosmetic CRLF count from 127 to 167 (autocti_workspace +40). Same class of gap as PyAutoCTI, and the backlog is deferred by design — recorded here because it is a number that will visibly jump.
Explicitly out of scope
Fixing any CRLF / artifact / dep-cap finding the widening exposes; the sibling helpers' own LIBRARIES lists in _hygiene_config.py and _hygiene_refs.py (they already cover CTI, so they are not drifted).
Original Prompt
Click to expand starting prompt
# Hygiene under-reports debt by 25x because its repo arrays skip
Type: bug
Target: PyAutoBrain
Repos:
- PyAutoBrain
Difficulty: medium
Autonomy: supervised
Priority: normal
Status: formalised
Hygiene under-reports debt by 25x because its repo arrays skip two libraries. The hygiene conductor scans a hardcoded list of checkouts in PyAutoBrain agents/conductors/hygiene/hygiene.sh. That list is stale: the LIB_REPOS array holds five entries where the body map (repos.yaml) has six, silently skipping the CTI and Reduce libraries, and it mislabels the config layer as a library; ORG_REPOS covers four of seven organs. The result is wrong output, not stale prose. On a real run the crlf mode printed '5 library .py w/ CRLF' when the true count is 127 — 122 of them in the skipped CTI library, breaking that repo's LF-only rule with nobody watching. The deps mode audits five pyproject.toml instead of six; tidy inspects nine of roughly seventeen managed checkouts. Every clean bill of health the conductor has issued understates reality. This is an internal inconsistency, since the sibling scanners _hygiene_config.py and _hygiene_refs.py already reach the CTI library. The drift checker cannot catch the gap: its tenant-firewall entry for hygiene.sh pins the current broken set as an allowlist instead of verifying coverage. The repair should derive the arrays from the body map rather than re-hardcoding them. Widening coverage will surface a large backlog of genuine new findings, so land the coverage repair and the triage of what it uncovers as separate tasks.
<!-- formalised by the Intake (Conception) Agent on 2026-08-05 from user-intake -->
Overview
agents/conductors/hygiene/hygiene.shhardcodes the repos it scans:PyAutoMind/repos.yaml— the body map, the single source of repo identity — declares six libraries and seven organs. So the conductor silently skips the CTI and Reduce libraries, classesPyAutoNervesas a library where the manifest calls it an organ, and covers four of seven organs. Every clean bill of health it has issued understates reality.This is internal inconsistency, not a scope decision: the sibling scanners
_hygiene_config.pyand_hygiene_refs.pyalready reachPyAutoCTI. Andrepos_sync.pycannot catch it — its tenant-firewall entry forhygiene.sh(line 525) pins the drifted set as an allowlist rather than checking coverage against the manifest.Measured, not asserted
Reproduced at
PYAUTO_ROOT=/home/userbefore planning:crlfcosmetic.pycountdepspyproject.tomlauditedtidymanaged checkouts scannedA related third defect ships with this fix: when the scan root is empty or absent, every array-driven mode currently reports
cleanwith no warning. Acleanverdict over zero repos is the same failure as acleanverdict over five of eleven — it needs a distinctunscannedsignal.Scope: the coverage repair only. Widening the scan surfaces a large backlog of genuine new findings (CRLF, artifacts, dep caps). Triaging that backlog is a separate task and no finding is fixed here.
Plan
PyAutoMind/repos.yaml(PyYAML when importable, minimal parser otherwise — the conductor must not gain a hard dependency).LIB_REPOS/ORG_REPOS/DOC_REPOSinhygiene.shwith sets derived from that reader; no repo name is written in the script any more.deps/packagingoff "ships apyproject.toml",docsoff "hasdocs/api/" — instead of a hand-kept array.unscannedwith a named reason instead ofclean.repos_sync.pythat fails if the derived sets stop matchingrepos.yamlor if a repo name is re-hardcoded into a*_REPOS=(…)array, and shrink the now-obsolete firewall allowlist entry.AGENTS.mdmode table.Detailed implementation plan
Work Classification
Library (organ code, no workspace leg).
Affected Repositories
repos_sync.pycoverage checkBranch Survey
claude/hygiene-coverage-drift-kso7h1claude/hygiene-coverage-drift-kso7h1Branch:
claude/hygiene-coverage-drift-kso7h1(mandated by the session; overrides the usualfeature/<task-name>).Worktree root: none — cloud session, no
~/Code/PyAutoLabs-wt; work happens in the canonical checkouts.Implementation Steps
New
PyAutoBrain/agents/conductors/hygiene/_hygiene_repos.py—--root <dir> --category <library|organ|workspace> [--json]. Resolves the body map viaPYAUTO_MIND→<brain-parent>/PyAutoMind→$PYAUTO_ROOT/PyAutoMind, mirroringresolve_mindinagents/_common.sh.yaml.safe_loadwhen PyYAML imports, else a two-level indent walk overrepos:→<Name>:→category:. Contains zero repo-name literals, so it is firewall-clean by construction. Exit 3 + empty output when the map is unresolvable, so bash can tell "none declared" from "none present".hygiene.sh— delete lines 64–66; populateLIB_REPOS/ORG_REPOS/WS_REPOSviamapfilefrom the helper and setBODY_MAP_OK. Then:CODE_REPOS= LIB+ORG →prescan_tidy,enumerate_condemn_candidatesSCAN_REPOS= LIB+ORG+WS →prescan_crlf,prescan_artifacts(replaces the hardcoded workspace triple; categoryworkspaceaddsautocti_workspace)prescan_deps/prescan_packaging→CODE_REPOSfiltered onpyproject.tomlpresent, not on category — a straight category mapping would dropPyAutoNerves(organ, but ships a distribution) and re-create this bug in a new placeprescan_docs→CODE_REPOSfiltered ondocs/api/;${#DOC_REPOS[@]}in the summary becomes the count actually scannedhygiene.shunscanned signal — newmanaged_present()counts body-map repos with a.gitunder$ROOT.emit_json_rowgains"scanned": N;N == 0⇒status: "unscanned",count: null. Humanrender_rowprints tagunscanned; the default scan leads with a banner naming$ROOTand the reason (scan root empty/absentvsbody map unavailable), and "Recommended next" says so rather than claiming no findings.PyAutoMind/scripts/repos_sync.py— newcheck_hygiene_coverage(root, repos)registered inmain()'schecksdict:_hygiene_repos.py --json; assert derivedlibrary/organ/workspacesets equal the manifest's, reporting each missing/extra name^\s*[A-Z_]*REPOS=\(([^)]*)\)overhygiene.sh; any manifest repo name inside is drift ("re-hardcoded — derive from the body map")check_heart/check_labelsFIREWALL_ALLOWLIST["…/hygiene.sh"]to the tokens that genuinely survive, verified by running--checkrather than assumedTests —
PyAutoBrain/tests/test_hygiene_conductor.py: derived sets matchrepos.yaml;PyAutoCTI/PyAutoReducereach the crlf scan; empty root ⇒unscannedacross array modes + banner; unresolvable body map ⇒unscanned, exit 0, valid JSON. NewPyAutoMind/scripts/test_repos_sync_coverage.py(matching the existingtest_spawn_*.pyconvention): passes on the real tree, fails on a re-hardcoded array and on a dropped category.Docs — hygiene
AGENTS.mdmode table: "the 3 doc repos" and the library-only framing become the derived rule; add theunscannedstatus alongside debris/finding/timing/surface/advisory.Key Files
PyAutoBrain/agents/conductors/hygiene/hygiene.sh— lines 64–66 (the arrays),prescan_*,emit_json_row,render_rowPyAutoBrain/agents/conductors/hygiene/_hygiene_repos.py— new body-map readerPyAutoBrain/agents/conductors/hygiene/AGENTS.md— mode table + kindsPyAutoMind/scripts/repos_sync.py—check_hygiene_coverage,FIREWALL_ALLOWLISTline 525PyAutoMind/repos.yaml— read-only source of truth; unchangedTesting
pytest PyAutoBrain/tests/test_hygiene_conductor.py;python3 PyAutoMind/scripts/repos_sync.py --checkmust exit 0; before/afterhygiene crlfandhygiene --jsonunderPYAUTO_ROOT=/home/userdemonstrating 5 → 167 and theunscannedpath under an empty root.Trade-off accepted
Deriving the workspace set from category
workspaceraises the cosmetic CRLF count from 127 to 167 (autocti_workspace+40). Same class of gap as PyAutoCTI, and the backlog is deferred by design — recorded here because it is a number that will visibly jump.Explicitly out of scope
Fixing any CRLF / artifact / dep-cap finding the widening exposes; the sibling helpers' own
LIBRARIESlists in_hygiene_config.pyand_hygiene_refs.py(they already cover CTI, so they are not drifted).Original Prompt
Click to expand starting prompt