Repository navigation
Description
Overview
The bare and prefixed spellings of several body-map repos normalise to two
different keys, so a policy map filed under one is invisible to the other.
_target_sets.names_for registers BOTH spellings of every PyAuto* repo as a
known target (name.lower() and name.lower()[2:]), but repo_aliases in
config/policy.yaml only joins them for the libraries. Reproduced live on
main: @autobrain normalises to autobrain, which is a known target with no
test_witness row, so pyauto-brain refactor reports [unwitnessed: autobrain]
and advises "strengthen tests first" for the best-tested repo in the organism;
pyauto-brain intake on the same mention files Target: autobrain, a folder
that does not exist (the real one is draft/bug/pyautobrain/).
This is the fourth instance of one defect class — PyAutoNerves (#267),
PyAutoCTI and PyAutoReduce (#269) were the first three. The shared cause is
that repo_aliases is HAND-MAINTAINED while the known-target set is DERIVED
from the body map, so the two drift silently and the gap only ever surfaces as
a wrong-but-plausible conductor message. This task closes the class, not the
instance.
The keying decision (answered before any edit, as the prompt requires)
Organs key on the PREFIXED form (pyautobrain), extending the rule #269/#271
already wrote down: the canonical key is the bare package name where the repo
ships one, the repo name where it does not. Organs ship no package — PyAutoNerves
is the exception and already keys bare (autonerves). Everything downstream is
already filed prefixed for organs: test_witness, target_signals,
REPO_DISPLAY, target_default_wiki, and the Mind's own target folders
(draft/*/pyautobrain/, pyautomind/, pyautoheart/, pyautomemory/,
pyautohands/). Keying bare would rekey five maps plus Mind folders for no gain.
Scope found beyond the prompt's list
Reproduction swept every body-map repo, not just the five organs named:
| Repo | Category | Spellings | Today |
|---|---|---|---|
| PyAutoBrain / Heart / Memory / Mind / Gut | organ | pyauto* vs auto* |
split — bare form resolves to a key nothing is filed under |
| PyAutoScientist | project | pyautoscientist vs autoscientist |
split (not in the prompt) |
pyautolabs.github.io |
project | dotted name | never resolves — normalise_repo truncates at the first ., giving pyautolabs, which is in no map at all (not in the prompt) |
No live Mind prompt uses any bare organ spelling (only the two files that
discuss this defect), so every change here is latent-closing and
behaviour-preserving for every spelling that resolves today.
Plan
- Write the canonical-key rule down where the maps live, and settle organs on the prefixed form.
- Give the body map the one bit the rule needs per repo — does it ship a package, and under what name — since the package a repo ships is repo identity.
- Derive the bare/prefixed/package alias join from that, so
repo_aliasesshrinks to what genuinely cannot be derived: short forms (aa,af) and historical renames (autoconf,pyautobuild). - Add coverage guards that fail loudly on a future gap: no repo may split across two keys; no alias may point at a key nothing is filed under; the body map's package field and the witness map must agree.
- Mutation-test each new guard against the pre-fix state, as fix: witness map missed 5 tested repos and split 2 more across keys #271 did, and record the results.
- Sweep PyAutoScientist and
pyautolabs.github.ioin the same pass.
Detailed implementation plan
Work Classification
Infrastructure (organism repos) — no library or workspace code is touched.
Affected Repositories
PyAutoBrain(primary) — the alias table, the derivation, the guardsPyAutoMind—repos.yamlgains thepackage:identity key
Branch Survey
| Repository | Current Branch | Dirty? |
|---|---|---|
| ./PyAutoBrain | claude/organ-repo-spelling-splits-hbbms0 |
clean |
| ./PyAutoMind | claude/organ-repo-spelling-splits-hbbms0 |
clean |
Branch: claude/organ-repo-spelling-splits-hbbms0 (session-designated; this
is a web-github session with no task worktrees, so the usual
feature/<task-name> name is overridden). worktree_check_conflict organ-repo-spelling-splits PyAutoBrain PyAutoMind returns 0 — no claim conflict.
Worktree root: n/a (web-github — the session's clones are the checkout).
Implementation Steps
-
PyAutoMind/repos.yaml— addpackage:to the seven repos that ship
one:PyAutoFit: autofit,PyAutoArray: autoarray,PyAutoGalaxy: autogalaxy,
PyAutoLens: autolens,PyAutoCTI: autocti,PyAutoReduce: autoreduce,
PyAutoNerves: autonerves. Document the key in the file header next to
public_role/front_door: identity only — the import/PyPI name the repo
ships, absent where it ships none. Every value is cross-checked in step 5
against thetest_witnessrows fix: witness map missed 5 tested repos and split 2 more across keys #271 verified by reading each repo's tree. -
PyAutoBrain/agents/faculties/sizing/_sizing.py— derive the join:_body_map_specs()alongside the existing_body_map_categories(), so the
package:field is readable without a second YAML load._canonical_key(name, spec)=spec["package"].lower()when present, else
name.lower(). This is the fix: witness map missed 5 tested repos and split 2 more across keys #271 rule, now executable rather than prose._derived_aliases()— for every repo in a sizing category, map each
registered spelling (name.lower(), thepyauto-stripped bare form, the
package name, and the dot-truncated head for a dotted repo name) onto its
canonical key.REPO_ALIASES = _derived_aliases() | policy()["repo_aliases"], with a
conflict check: a hand entry that disagrees with the derivation is drift
and must raise, not silently win. Move theREPO_ALIASESassignment above
_target_sets()sonormalise_repois usable during set construction.- Leave
LIBRARY_REPOS/WORKSPACE_REPOS/ORGANISM_REPOSregistering
every spelling. Membership is always tested post-normalisation, and
_intake._repos_inscans the set as free-text tokens — narrowing the sets
to canonical keys alone would stop bare mentions being recognised in
unheadered prose.
-
PyAutoBrain/config/policy.yaml— trimrepo_aliasesto the
non-derivable rows (aa/af/ag/al;autoconf/pyautoconfand
pyautobuild/autobuildrenames) and head the block with the canonical-key
rule plus a pointer saying the bare/prefixed join is derived, not typed here.
The library rows andautohands: pyautohandsbecome redundant and go. -
PyAutoBrain/tests/test_policy_seams.py— three guards, every name
derived from the body map (the tenant firewall allows only
{PyAutoFit, PyAutoLens, autolens_workspace}as literals in this file):- no split spellings — for every body-map repo in a sizing category,
all registered spellings normalise to one key, and that key is itself a
known target. Fails today for five organs and PyAutoScientist. - no alias points at a dead key — every value in the effective alias
table is the canonical key of some body-map repo. This is the
pyautoconf -> autoconfdefect (fix: retire PyAutoConf rename leftovers in Brain functional surfaces #267) killed at source. - package field agrees with the witness map — for every
test_witness
row of the form<Repo>/test_<pkg>,repos.yaml[<Repo>].package == pkg,
and vice versa. Makes Mind identity and Brain policy check each other.
- no split spellings — for every body-map repo in a sizing category,
-
Mutation-test the guards (the fix: witness map missed 5 tested repos and split 2 more across keys #271 practice): drop one derived organ
alias, mis-key onepackage:value, and restore the pre-fixpolicy.yaml
— each must fail a named guard. Record which guard caught what. -
Validate:
pytest PyAutoBrain/tests(468 baseline + new),
python3 PyAutoMind/scripts/repos_sync.py --check(tenant firewall + the 11
identity checks), and re-run the reproduction —@autobrain,@autoheart,
@automind,@automemory,@autogut,@autoscientistmust each reach the
prefixed key and its witness row, and the six library spellings must be
byte-identical to their pre-change resolution.
Key Files
PyAutoMind/repos.yaml— the body map; gainspackage:on seven reposPyAutoBrain/agents/faculties/sizing/_sizing.py—_target_sets,normalise_repo,REPO_ALIASESPyAutoBrain/config/policy.yaml—repo_aliases,test_witness,sizing_categoriesPyAutoBrain/tests/test_policy_seams.py— the coverage guardsPyAutoBrain/agents/conductors/refactor/_refactor.py—behaviour_preservation, the surface where the defect showsPyAutoBrain/agents/conductors/intake/_intake.py—_repos_in/infer_target, the second surface
Trade-offs
package:in the body map vs a Brain-only declaration. A
bare_key_categories: [library]+ PyAutoNerves-exception block in
policy.yamlwould keep this a one-repo change, but the exception list is
hand-maintained again — the exact cause this task exists to remove — and the
body map still could not answer what package a repo ships. The package name
is repo identity, so it belongs inrepos.yaml; the two-repo cost is
accepted deliberately.- Sets keep every spelling. Narrowing them to canonical keys would be
tidier but silently breaks_intake._repos_in's free-text scan. Not worth it. - Heart is not evaluable here.
pyauto-heartis unreachable from a
web-githubsession, so leg 4 of the ship gate will be recorded as NOT
EVALUATED rather than claimed clean.
Testing approach
Guard-first: each new guard is written against the current main state, watched
to fail for the right reason, and only then made to pass by the derivation. The
library spellings serve as the behaviour-preservation witness — their
resolution must not move.
Original Prompt
Click to expand starting prompt
Organ repo spellings split across two normalised keys
Type: bug
Target: pyautobrain
Repos:
- PyAutoBrain
Difficulty: large
Autonomy: supervised
Priority: normal
Status: formalised
Filed: 2026-08-24
Found while shipping the refactor witness-map audit (@PyAutoBrain#269 / PR #271).
The defect
The bare spellings of the organ repos do not normalise to the same key as their
repo spellings, so a policy map keyed on one is invisible to the other.
@PyAutoBrain/agents/faculties/sizing/_sizing.py (_target_sets.names_for)
registers BOTH spellings of every PyAuto* repo as known targets
(name.lower() and name.lower()[2:]), but the repo_aliases table in
@PyAutoBrain/config/policy.yaml only joins them for the libraries:
| mention | normalises to | resolves? |
|---|---|---|
@PyAutoBrain |
pyautobrain |
yes |
@autobrain |
autobrain |
no — a known target with nothing filed under it |
Same for the bare Heart, Memory, Mind and Gut spellings. @autohands was joined
in PR #271 because extra_organism_targets declares it explicitly and it
reaches real code today; the rest are latent — no prompt in the Mind history
uses them yet.
Decide before fixing
This is NOT a blind alias sweep. Should organs key on the bare form
(autobrain) or the prefixed form (pyautobrain)? target_signals keys the
prefixed form (pyautobrain, pyautomind, pyautoheart, pyautobuild), and
after #271 test_witness keys organs prefixed but libraries bare. Whichever way
it goes has to hold across both maps at once, so answer the question first and
let the edit follow.
The wider question this raises
Three separate repos have now hit this same defect class — PyAutoNerves
(#267), PyAutoCTI and PyAutoReduce (#269). The shared cause is that
repo_aliases is HAND-MAINTAINED while the known-target set is DERIVED from the
body map, so the two drift silently and the gap only ever surfaces as a
wrong-but-plausible conductor message ("strengthen tests first" for a
well-tested repo).
Guard 2 added in #271 — a witness-map key must be what its repo normalises to —
closes the witness-map instance only. An alias gap in a map with no coverage
guard is still invisible. Consider deriving repo_aliases from the body map so
the class closes rather than its fourth instance.
Notes on this prompt's header
pyauto-brain intake classify scored this bug at high confidence (kept), but
proposed Target: autocti — it read the prior-instance history above as the
subject — and Difficulty: too-large (score 10), inflated by the repo names and
the design-decision keywords. Corrected to pyautobrain / large on review.
An alternative classification is refactor: the fix is behaviour-preserving for
every spelling that resolves today. bug was kept because the Bug Agent's
investigate-first strategy matches the "decide before fixing" requirement above.
Acceptance: every spelling of every body-map repo that _target_sets registers
as a known target resolves to a key the policy maps are actually filed under, or
is deliberately not registered; a guard makes a future alias gap fail loudly
rather than degrade a conductor's advice.
Activity
Jammy2211 commentedon Aug 26, 2026
Shipped
52ab5012ba9ef6(merged first; the Brain half reads itspackage:field)The decision, taken before any edit as the prompt required: organs key on the PREFIXED form (
pyautobrain). Not a new rule — #269's rule made executable: the canonical key is the package the repo SHIPS where it ships one, the repo name where it does not. Organs ship no package; Nerves is the exception and already keyed bare.The class is closed, not the fourth instance. The bare/prefixed/package alias join now derives from the body map's new
package:field;repo_aliaseskeeps only what a body map cannot know (short forms, pre-rename spellings), and a hand row contradicting the derivation raises rather than quietly winning.Beyond the prompt's five organs, the sweep found seven splits (adding PyAutoHands and PyAutoScientist) and one repo that could never resolve at all.
pyautolabs.github.iowas de-registered rather than joined — a deliberate deviation from the scope answered at plan time.normalise_repotruncates at the first.//, and aliasing that truncated head would have been worse than the gap: the head ispyautolabs, the org name, so@PyAutoLabs/PyAutoFitwould have resolved to the static site instead of PyAutoFit. The acceptance criterion's other branch ("or is deliberately not registered") covers it, the exclusion derives from the names themselves, and a guard pins both halves.Five guards, each mutation-tested. Removing the derived join names all seven affected repos by itself.
Gate: 515 tests pass (510 baseline) on both CI matrix legs, and locally against both body-map states — the new one with all seven packages declared and a Mind at
origin/mainwith none.repos_sync --checkall 13 legs OK; the tenant firewall rejected two drafts of the new prose for naming instance repos, which is why the comments read generically. An A/B over every spelling shows exactly six resolutions changed, all intended joins. Smoke n/a (organism repos). Heart NOT EVALUATED — unreachable from a web-github session, so leg 4 of the ship gate never ran.CI caught one real defect mid-flight: the corroboration guard required
repos.yamlto declarepackage:, which a Mind pinned tomaincannot. It is now all-or-nothing — a map declaring none has nothing to corroborate; one declaring some must declare all — which is stronger than what it replaced.Follow-ups: the seven pre-package library rows in
repo_aliasesare now dead weight and can be deleted, which also fully arms the guard's strict path;normalise_repostill passesmaxsplitpositionally (pre-existing DeprecationWarning, left alone).Record:
PyAutoMind/complete/2026/08/organ-repo-spelling-splits.mdGenerated by Claude Code