From 2088bad479f98f5abba8d807278fd13af6ff9cc2 Mon Sep 17 00:00:00 2001 From: James Nightingale Date: Mon, 24 Aug 2026 21:05:28 +0000 Subject: [PATCH] fix: retire PyAutoConf rename leftovers in Brain functional surfaces MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The PyAutoConf -> PyAutoNerves rename (package autoconf -> autonerves) was completed in the reader-facing docs but left eight sites naming the old repo/package. Four were live defects, all of them silent — nothing failed, the surfaces just stopped seeing the Nerves repo. config/policy.yaml is the functional core: - repo_aliases split one repo across two dead keys. `@PyAutoConf` normalised to `autoconf` and `@PyAutoNerves` to `pyautonerves`; neither is in the body map, so neither resolved. Make `autonerves` the single canonical key and alias the other three spellings onto it, the shape the `pyautobuild: pyautohands` entry already set. That also collapses the three target_default_wiki rows that existed only to paper over the split. - test_witness pointed at PyAutoConf/test_autoconf, reachable only through the stale alias. `pyauto-brain refactor` therefore reported `[unwitnessed: pyautonerves]` and advised "strengthen tests first" for a repo with a 157-test suite. Now PyAutoNerves/test_autonerves, verified live. - release.relevant_repos listed PyAutoConf, and nightly.sh fetches each name verbatim. Whether a rename redirect carried that call is unverified from this session; naming PyAutoNerves removes the dependency rather than trusting it. - hygiene.sh timed `import autoconf`, whose non-zero rc is recorded as `autoconf:n/a` and skipped, so Nerves import timing went unmeasured. The remaining four sites are comments and prose. Two were stale beyond the name: ensure_workspace_labels.sh and repo_cleanup/reference.md both mapped repos to `rhayes777`/`Jammy2211` owners that the migration retired — the labels script's own live list already disagreed with its header. Both now defer to the body map's `github:` field instead of hardcoding an owner. Tests pin every seam: all four spellings normalise to one known key; the witness map resolves under each; a PyAutoNerves commit qualifies a night (the prompt's acceptance criterion, offline); and every release-relevant repo must exist in the body map — the drift guard whose absence let this survive. A repo-wide guard enforces the prompt's grep acceptance, allowlisting only the sites deliberately about the rename and failing if an allowlist entry goes dead. It found the eighth site (skills/OPERATIONS.md) during the run. Closes #267 Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01MPhEXMoPYa7H1sNHWR2GNW --- agents/conductors/feature/MIND_TAXONOMY.md | 2 +- agents/conductors/health/health.sh | 2 +- agents/conductors/hygiene/hygiene.sh | 2 +- bin/ensure_workspace_labels.sh | 7 +- config/policy.yaml | 12 +-- skills/OPERATIONS.md | 2 +- skills/repo_cleanup/reference.md | 7 +- tests/test_activity_gate.py | 14 +++- tests/test_nerves_rename_complete.py | 92 ++++++++++++++++++++++ tests/test_policy_seams.py | 36 ++++++++- 10 files changed, 158 insertions(+), 18 deletions(-) create mode 100644 tests/test_nerves_rename_complete.py diff --git a/agents/conductors/feature/MIND_TAXONOMY.md b/agents/conductors/feature/MIND_TAXONOMY.md index 97287dc2..60d373e8 100644 --- a/agents/conductors/feature/MIND_TAXONOMY.md +++ b/agents/conductors/feature/MIND_TAXONOMY.md @@ -74,7 +74,7 @@ context when memory has material. Sub-wikis (source: `PyAutoMemory/index.md`): | `wiki/galaxies/` | galaxy formation & evolution | bulge/disk, MGE, morphology, IFU, kinematics | Library targets also pull a default sub-wiki (`autolens`→lensing, -`autogalaxy`→galaxies, `autofit`/`autoarray`/`autoconf`→methods). PyAutoMemory is +`autogalaxy`→galaxies, `autofit`/`autoarray`/`autonerves`→methods). PyAutoMemory is **optional**: if the checkout is absent the agent still names the sub-wikis to read, degrading gracefully rather than failing. diff --git a/agents/conductors/health/health.sh b/agents/conductors/health/health.sh index b7e65fd7..712b5b70 100755 --- a/agents/conductors/health/health.sh +++ b/agents/conductors/health/health.sh @@ -205,7 +205,7 @@ REFRESH_CMD = { } def repo_of(reason): - # "PyAutoConf: on branch ..." -> "PyAutoConf"; else None. + # "PyAutoNerves: on branch ..." -> "PyAutoNerves"; else None. m = re.match(r"\s*([A-Za-z0-9_\-]+)\s*:", reason) return m.group(1) if m else None diff --git a/agents/conductors/hygiene/hygiene.sh b/agents/conductors/hygiene/hygiene.sh index 946c71ae..10f169c3 100755 --- a/agents/conductors/hygiene/hygiene.sh +++ b/agents/conductors/hygiene/hygiene.sh @@ -144,7 +144,7 @@ CONDEMN_TRANSIT_DAYS="${HYGIENE_CONDEMN_TRANSIT_DAYS:-30}" # fast stdlib modules); HYGIENE_PERF_THRESHOLD (s) is the slow cutoff. PERF_PY="${HYGIENE_PYTHON:-python3}" PERF_THRESHOLD="${HYGIENE_PERF_THRESHOLD:-3.0}" -read -r -a PERF_LIBS <<< "${HYGIENE_PERF_LIBS:-autoconf autofit autoarray autogalaxy autolens}" +read -r -a PERF_LIBS <<< "${HYGIENE_PERF_LIBS:-autonerves autofit autoarray autogalaxy autolens}" MODE_ORDER=(perf tidy crlf docstrings escapes refs optdeps extras artifacts packaging noise deps docs config) declare -A MODE_DELEGATE=( diff --git a/bin/ensure_workspace_labels.sh b/bin/ensure_workspace_labels.sh index fb5baf68..889752fa 100644 --- a/bin/ensure_workspace_labels.sh +++ b/bin/ensure_workspace_labels.sh @@ -15,10 +15,9 @@ # # Exits 0 on success, 1 if any API call fails. # -# Repos with non-PyAutoLabs orgs: -# - Jammy2211/euclid_strong_lens_modeling_pipeline -# - rhayes777/PyAutoConf, rhayes777/PyAutoFit -# All others are under PyAutoLabs/. +# Every repo below is under PyAutoLabs/ — the pre-migration rhayes777/ and +# Jammy2211/ homes are gone. Owners come from the body map's `github:` field +# (PyAutoMind/repos.yaml); never hardcode a legacy owner default. set -euo pipefail diff --git a/config/policy.yaml b/config/policy.yaml index 92a50d51..eb3dbfea 100644 --- a/config/policy.yaml +++ b/config/policy.yaml @@ -11,9 +11,11 @@ repo_aliases: al: autolens pyautoarray: autoarray pyautofit: autofit - pyautoconf: autoconf pyautogalaxy: autogalaxy pyautolens: autolens + pyautonerves: autonerves + autoconf: autonerves # back-compat: the Nerves package was renamed autoconf → autonerves + pyautoconf: autonerves # back-compat: the Nerves repo was renamed PyAutoConf → PyAutoNerves pyautobuild: pyautohands # back-compat: the Hands repo was renamed PyAutoBuild → PyAutoHands # Which body-map categories mean "library-flow" / "workspace-flow" / @@ -67,14 +69,12 @@ target_default_wiki: autogalaxy: galaxies autofit: methods autoarray: methods - autoconf: methods # legacy name — the Nerves repo was PyAutoConf - autonerves: methods - pyautonerves: methods + autonerves: methods # the PyAutoConf/autoconf spellings alias here # Refactor conductor: normalised repo -> the test dir that witnesses # behaviour preservation. test_witness: - autoconf: PyAutoConf/test_autoconf + autonerves: PyAutoNerves/test_autonerves autofit: PyAutoFit/test_autofit autoarray: PyAutoArray/test_autoarray autogalaxy: PyAutoGalaxy/test_autogalaxy @@ -84,7 +84,7 @@ test_witness: # Release policy (NOT identity: the released set is a deliberate subset of # the body map's libraries — e.g. PyAutoReduce is not in the nightly matrix). release: - relevant_repos: [PyAutoConf, PyAutoFit, PyAutoArray, PyAutoGalaxy, PyAutoLens, + relevant_repos: [PyAutoNerves, PyAutoFit, PyAutoArray, PyAutoGalaxy, PyAutoLens, autofit_workspace, autogalaxy_workspace, autolens_workspace, HowToFit, HowToGalaxy, HowToLens] tag_repo: PyAutoLabs/PyAutoLens diff --git a/skills/OPERATIONS.md b/skills/OPERATIONS.md index d1abc201..f9626900 100644 --- a/skills/OPERATIONS.md +++ b/skills/OPERATIONS.md @@ -38,7 +38,7 @@ gets rediscovered twice, it belongs here. - **`PYAUTO_TEST_MODE` namespaces output under `output/test_mode/`.** Any manually composed output path must include that segment or asserts read the wrong tree. -- **autoconf lowercases YAML dict keys** (`muJy` → `mujy`). Keep config keys +- **autonerves lowercases YAML dict keys** (`muJy` → `mujy`). Keep config keys and the registries that read them snake-case-lowercase from the start. - **New library `output.yaml` keys need mirroring** into each workspace's config copy — workspace configs override library defaults, so a key added diff --git a/skills/repo_cleanup/reference.md b/skills/repo_cleanup/reference.md index f429eecc..05049eb6 100644 --- a/skills/repo_cleanup/reference.md +++ b/skills/repo_cleanup/reference.md @@ -125,5 +125,8 @@ open PR that are merged to `main` (`gh api repos///compare/main...` → `ahead_by == 0`) as **candidates** only. Do not delete remotely; recommend running on a local-dev checkout to complete the sweep (which re-audits against local state). Skip -stash/dirty sections. Owner mapping: PyAutoConf/PyAutoFit → `rhayes777`, else -`Jammy2211`. +stash/dirty sections. Resolve each repo's `/` from the `github:` +field in `PyAutoMind/repos.yaml` (the body map is the single source of repo +identity) — do not assume a default owner. Nearly every repo is under +`PyAutoLabs/`; the pre-migration `rhayes777/` and `Jammy2211/` homes are gone +bar the two the body map still records. diff --git a/tests/test_activity_gate.py b/tests/test_activity_gate.py index 7a328448..ea632f6e 100644 --- a/tests/test_activity_gate.py +++ b/tests/test_activity_gate.py @@ -140,5 +140,17 @@ def test_anchor_validator(): def test_release_relevant_set_is_the_design_set(): # Design §4: 5 libraries + 3 workspaces + 3 HowTo repos. assert len(activity_gate.RELEASE_RELEVANT_REPOS) == 11 - for repo in ("PyAutoConf", "autolens_workspace", "HowToFit"): + for repo in ("PyAutoNerves", "autolens_workspace", "HowToFit"): assert repo in activity_gate.RELEASE_RELEVANT_REPOS + # The pre-rename name must not linger: nightly.sh fetches + # `repos//$repo/commits` for each entry verbatim, so the gate must + # never depend on GitHub answering the old name via a rename redirect. + assert "PyAutoConf" not in activity_gate.RELEASE_RELEVANT_REPOS + + +def test_nerves_commit_qualifies_the_night(): + # The rename acceptance criterion (PyAutoBrain#267): a commit on the Nerves + # repo must COUNT as qualifying activity under the name the driver fetches. + v = activity_gate.judge({"PyAutoNerves": [commit()]}) + assert v["active"] is True + assert v["counts"]["PyAutoNerves"] == 1 diff --git a/tests/test_nerves_rename_complete.py b/tests/test_nerves_rename_complete.py new file mode 100644 index 00000000..c66be7e1 --- /dev/null +++ b/tests/test_nerves_rename_complete.py @@ -0,0 +1,92 @@ +"""tests/test_nerves_rename_complete.py — the PyAutoConf → PyAutoNerves rename +stays finished. + +PyAutoBrain#267: the repo/package rename (``PyAutoConf`` → ``PyAutoNerves``, +``autoconf`` → ``autonerves``) was completed in the reader-facing docs but left +live sites behind — a policy alias resolving to a key the body map does not +know, a refactor witness pointing at a deleted repo path, the nightly activity +gate polling the old repo name, and ``/hygiene`` timing an import that no longer +exists. Every one of those was silent: nothing failed, the surfaces just stopped +seeing the Nerves repo. + +This test is the prompt's grep acceptance, enforced. The old spellings are +allowed to survive only where they are deliberately *about* the rename — the +history note, the back-compat aliases, and the tests that pin them. +""" + +from __future__ import annotations + +import re +import subprocess +from pathlib import Path + +BRAIN_HOME = Path(__file__).resolve().parents[1] + +# Paths where a pre-rename spelling is deliberate. Each entry is a reason, not a +# waiver: deleting the reason means deleting the entry. +DELIBERATE = { + # The organism's own record of the rename having happened. + "ORGANISM.md", + # The back-compat alias table — old @-mentions in ~150 archived Mind prompts + # must keep routing to the Nerves repo. + "config/policy.yaml", + # The intake conductor's own legacy alias, labelled as such. + "agents/conductors/intake/_intake.py", + # The tests that pin the aliases and the gate's repo set. + "tests/test_policy_seams.py", + "tests/test_activity_gate.py", + "tests/test_nerves_rename_complete.py", +} + +# `autoconf` also appears inside unrelated words in third-party contexts (GNU +# autoconf, `auto_config`), so match the PyAuto spellings as whole tokens. +STALE = re.compile(r"(? list[str]: + out = subprocess.run( + ["git", "-C", str(BRAIN_HOME), "ls-files"], + capture_output=True, text=True, check=True, + ) + return [line for line in out.stdout.splitlines() if line] + + +def test_no_undeclared_pyautoconf_references(): + offenders: dict[str, list[str]] = {} + for rel in _tracked_files(): + if rel in DELIBERATE: + continue + path = BRAIN_HOME / rel + try: + text = path.read_text(encoding="utf-8") + except (UnicodeDecodeError, FileNotFoundError, IsADirectoryError): + continue # binary or a submodule pointer — nothing to grep + hits = [ + f"{rel}:{n}: {line.strip()}" + for n, line in enumerate(text.splitlines(), 1) + if STALE.search(line) + ] + if hits: + offenders[rel] = hits + + assert not offenders, ( + "pre-rename PyAutoConf/autoconf spellings outside the deliberate set — " + "rename them, or add the path to DELIBERATE with the reason:\n" + + "\n".join(h for hits in offenders.values() for h in hits) + ) + + +def test_deliberate_set_has_no_dead_entries(): + """A path that no longer carries the old spelling must leave DELIBERATE. + + Otherwise the allowlist silently grows into a blanket waiver. + """ + tracked = set(_tracked_files()) + dead = [] + for rel in sorted(DELIBERATE): + if rel not in tracked: + dead.append(f"{rel} (not tracked)") + continue + if not STALE.search((BRAIN_HOME / rel).read_text(encoding="utf-8")): + dead.append(f"{rel} (no longer mentions it)") + assert not dead, "stale DELIBERATE entries: " + ", ".join(dead) diff --git a/tests/test_policy_seams.py b/tests/test_policy_seams.py index d0ed0e50..63bd5bc2 100644 --- a/tests/test_policy_seams.py +++ b/tests/test_policy_seams.py @@ -46,6 +46,17 @@ def test_aliases_normalise_known_mentions(): assert _sizing.normalise_repo("PyAutoFit") == "autofit" +def test_nerves_spellings_share_one_canonical_key(): + # PyAutoBrain#267: before the rename cleanup `@PyAutoConf` normalised to + # `autoconf` and `@PyAutoNerves` to `pyautonerves` — two dead keys for one + # repo, neither of them in the body map. All four spellings must land on the + # single key the policy maps are written against. + for mention in ("PyAutoNerves", "@autonerves", "PyAutoConf", "@autoconf"): + assert _sizing.normalise_repo(mention) == "autonerves", mention + # ...and that key must be a repo the organism actually knows about. + assert "autonerves" in _sizing.KNOWN_REPOS + + def test_memory_wikis_route_science_vocabulary(): wikis = _sizing.MEMORY_WIKIS # Keys are the wiki// names (the *_wiki root layout is retired). @@ -68,7 +79,9 @@ def test_feature_default_wiki_loads(): import _feature assert _feature.TARGET_DEFAULT_WIKI["autolens"] == "lensing" - # The Nerves rename is covered both ways (legacy autoconf + autonerves). + # The Nerves repo resolves under every spelling, via the alias table rather + # than duplicate wiki rows (PyAutoBrain#267). + assert _feature.TARGET_DEFAULT_WIKI[_sizing.normalise_repo("PyAutoConf")] == "methods" assert _feature.TARGET_DEFAULT_WIKI["autonerves"] == "methods" @@ -77,6 +90,11 @@ def test_refactor_test_witness_loads(): import _refactor assert _refactor.TEST_WITNESS["autofit"] == "PyAutoFit/test_autofit" + # PyAutoBrain#267: the Nerves suite is real (test_autonerves, 157 tests), so + # a refactor touching it must not be reported `[unwitnessed: pyautonerves]`. + assert _refactor.TEST_WITNESS["autonerves"] == "PyAutoNerves/test_autonerves" + for mention in ("PyAutoNerves", "@autonerves", "PyAutoConf"): + assert _sizing.normalise_repo(mention) in _refactor.TEST_WITNESS, mention def test_release_policy_loads(): @@ -87,6 +105,22 @@ def test_release_policy_loads(): assert "PyAutoMind" not in activity_gate.RELEASE_RELEVANT_REPOS +def test_release_relevant_repos_all_exist_in_the_body_map(): + """The drift guard PyAutoBrain#267 was missing. + + ``nightly.sh`` fetches ``repos//$repo/commits`` for every name in + this list verbatim. A repo renamed in the body map but not here leaves the + gate polling a name that only a GitHub rename redirect could answer — which + is how ``PyAutoConf`` survived the Nerves rename. Pin the set to identity. + """ + sys.path.insert(0, str(BRAIN_HOME / "agents" / "conductors" / "release")) + import activity_gate + + known = set(_sizing._body_map_categories()) + unknown = [r for r in activity_gate.RELEASE_RELEVANT_REPOS if r not in known] + assert not unknown, f"not in PyAutoMind/repos.yaml: {unknown}" + + def test_nightly_tag_repo_resolves(): here = BRAIN_HOME / "agents" / "conductors" / "release" out = subprocess.run(