Skip to content

fix: join every body-map repo's spellings onto one canonical key #287

Closed

Description

@Jammy2211

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_aliases shrinks 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.io in 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 guards
  • PyAutoMind — repos.yaml gains the package: 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

  1. PyAutoMind/repos.yaml — add package: 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 the test_witness rows fix: witness map missed 5 tested repos and split 2 more across keys #271 verified by reading each repo's tree.

  2. 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(), the pyauto-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 the REPO_ALIASES assignment above
      _target_sets() so normalise_repo is usable during set construction.
    • Leave LIBRARY_REPOS / WORKSPACE_REPOS / ORGANISM_REPOS registering
      every spelling. Membership is always tested post-normalisation, and
      _intake._repos_in scans the set as free-text tokens — narrowing the sets
      to canonical keys alone would stop bare mentions being recognised in
      unheadered prose.
  3. PyAutoBrain/config/policy.yaml — trim repo_aliases to the
    non-derivable rows (aa/af/ag/al; autoconf/pyautoconf and
    pyautobuild/autobuild renames) 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 and autohands: pyautohands become redundant and go.

  4. 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 -> autoconf defect (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.
  5. 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 one package: value, and restore the pre-fix policy.yaml
    — each must fail a named guard. Record which guard caught what.

  6. 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, @autoscientist must 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; gains package: on seven repos
  • PyAutoBrain/agents/faculties/sizing/_sizing.py — _target_sets, normalise_repo, REPO_ALIASES
  • PyAutoBrain/config/policy.yaml — repo_aliases, test_witness, sizing_categories
  • PyAutoBrain/tests/test_policy_seams.py — the coverage guards
  • PyAutoBrain/agents/conductors/refactor/_refactor.py — behaviour_preservation, the surface where the defect shows
  • PyAutoBrain/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.yaml would 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 in repos.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-heart is unreachable from a
    web-github session, 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

  1. Jammy2211 commented on Aug 26, 2026

    @Jammy2211
    ContributorAuthor

    Shipped

    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_aliases keeps 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.io was de-registered rather than joined — a deliberate deviation from the scope answered at plan time. normalise_repo truncates at the first .//, and aliasing that truncated head would have been worse than the gap: the head is pyautolabs, the org name, so @PyAutoLabs/PyAutoFit would 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/main with none. repos_sync --check all 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.yaml to declare package:, which a Mind pinned to main cannot. 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_aliases are now dead weight and can be deleted, which also fully arms the guard's strict path; normalise_repo still passes maxsplit positionally (pre-existing DeprecationWarning, left alone).

    Record: PyAutoMind/complete/2026/08/organ-repo-spelling-splits.md


    Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions