Skip to content

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

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 resolvesnormalise_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
  • PyAutoMindrepos.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.yamlrepo_aliases, test_witness, sizing_categories
  • PyAutoBrain/tests/test_policy_seams.py — the coverage guards
  • PyAutoBrain/agents/conductors/refactor/_refactor.pybehaviour_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.

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