diff --git a/AGENTS.md b/AGENTS.md index 965e1e9c..ab218021 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -3,28 +3,6 @@ This file is for AI coding agents (Claude Code, Codex, Cursor, etc.) discovering this repository. -## The organism map - - -**You are one organ of the PyAuto organism** — an agentic ecosystem for -human-led, natural-language software development. The organs below are -peer repositories; this repo is one of them, not a part of another. -Canonical boundaries live in `PyAutoBrain/ORGANISM.md`; the full body map -(every repo, not just organs) is `PyAutoMind/repos.yaml`. - -| Organ | Repo | Role | -|-------|------|------| -| **Mind** | PyAutoMind | Intent, goals, priorities, workflow state; every task starts as a markdown prompt here. | -| **Brain** | PyAutoBrain | Reasoning/orchestration layer; how work is decomposed and routed; the specialist agents. | -| **Hands** | PyAutoBuild | Packaging, tagging, notebook generation, PyPI release execution. | -| **Heart** | PyAutoHeart | Health/readiness — the authoritative "is it safe to release?" verdict. | -| **Memory** | PyAutoMemory | Long-term scientific/software/project knowledge (see science pointer below). | - -Call chain (always this order): **Brain → Heart (gate) → Build (execute)**. Brain agents are **conductors** (front-door; a human drives them; they decide *and* act) or **faculties** (read-only opinions the conductors consult; they judge and stop). New capability grows as a faculty, not a new organ, unless it owns state or effects no existing organ can. - -Generated from `PyAutoMind/repos.yaml` + `PyAutoBrain/ORGANISM.md`; edit there, then run `python3 PyAutoMind/scripts/repos_sync.py --write`. - - ## What this repo is **PyAutoMind is the Mind of the PyAuto organism and the starting point of the diff --git a/maintenance/jammy2211/agents_md_pointer_sweep.md b/maintenance/jammy2211/agents_md_pointer_sweep.md new file mode 100644 index 00000000..fbd77360 --- /dev/null +++ b/maintenance/jammy2211/agents_md_pointer_sweep.md @@ -0,0 +1,40 @@ +# Finish the CLAUDE.md → @AGENTS.md sweep for the Jammy2211-owned repos + +Type: maintenance +Target: Jammy2211 repos +Repos: +- Jammy2211/autofit_workspace_developer +- Jammy2211/euclid_assistant +- Jammy2211/admin_jammy +Difficulty: small +Autonomy: supervised +Priority: normal +Status: formalised + +Follow-up from the ecosystem AGENTS.md/CLAUDE.md standardization epic +(`z_features/agents_md_standardization.md`, completed 2026-07-12). That sweep +covered every reachable `repos.yaml` repo, but the three **`Jammy2211/*`** repos +could not be reached from that session: `add_repo` refused with *"cross-tier adds +are not supported in v1"* because the session's sources were all `pyautolabs`, and +a different GitHub owner can't be added mid-session. + +**What to do:** run the same spec-1 check on each of the three repos — +- If it HAS an `AGENTS.md` but a missing / non-compliant `CLAUDE.md`, add the + canonical content-free `@AGENTS.md` pointer (one branch + pointer-only PR per + repo; auto-mergeable once CI is green). +- If it has NO `AGENTS.md`, do **not** auto-create one; just record it (same rule + the epic used). `admin_jammy` in particular is slated to leave `PyAutoLabs/` and + may be doc-light — check before assuming. + +**How to reach them:** start a session **rooted on one of the `Jammy2211` repos** +as the initial source (so the session's owner tier is `Jammy2211`), then +`add_repo` the other two from the same owner. Alternatively run it from a context +that already has `Jammy2211` access. + +**Note:** these three are already in `repos.yaml`, so once reachable, +`python3 scripts/repos_sync.py --check` (run with them checked out) will report +their pointer status directly — no new tooling needed, just execution. + + diff --git a/maintenance/pyautobrain/command_surface_in_agents_md.md b/maintenance/pyautobrain/command_surface_in_agents_md.md index 56909da2..6c2b408e 100644 --- a/maintenance/pyautobrain/command_surface_in_agents_md.md +++ b/maintenance/pyautobrain/command_surface_in_agents_md.md @@ -8,7 +8,15 @@ Repos: Difficulty: medium Autonomy: supervised Priority: normal -Status: formalised +Status: complete + + Generate the command surface into AGENTS.md. Make the PyAuto command surface (`intake`, `route`, `start_dev`, `ship_*`, the conductor verbs, etc.) diff --git a/maintenance/pyautomind/claude_md_standardization.md b/maintenance/pyautomind/claude_md_standardization.md index 365de21f..ea733129 100644 --- a/maintenance/pyautomind/claude_md_standardization.md +++ b/maintenance/pyautomind/claude_md_standardization.md @@ -9,7 +9,15 @@ Repos: Difficulty: large Autonomy: supervised Priority: normal -Status: formalised +Status: complete + + Standardize CLAUDE.md → AGENTS.md across the ecosystem. Sweep **every repo in `PyAutoMind/repos.yaml`** so each one carries a `CLAUDE.md` that is a thin, diff --git a/repos.yaml b/repos.yaml index 065aaf53..ade77fbb 100644 --- a/repos.yaml +++ b/repos.yaml @@ -137,6 +137,9 @@ repos: category: howto # --- assistants / pipelines / projects ---------------------------------- + autofit_assistant: + github: PyAutoLabs/autofit_assistant + category: assistant autolens_assistant: github: PyAutoLabs/autolens_assistant category: assistant diff --git a/scripts/repos_sync.py b/scripts/repos_sync.py index 0c3d66b9..02ff200f 100644 --- a/scripts/repos_sync.py +++ b/scripts/repos_sync.py @@ -51,6 +51,25 @@ MAP_BEGIN = "" MAP_END = "" +# The canonical content-free CLAUDE.md pointer. Guidance is agent-agnostic and +# lives in AGENTS.md (read natively by Codex, Cursor, etc.); Claude Code loads +# CLAUDE.md, not AGENTS.md, so every repo that has an AGENTS.md keeps a CLAUDE.md +# whose only job is to `@`-import it (Anthropic's documented bridge — imported in +# full at launch, recursive to depth 4). Kept as a real, greppable file (not a +# symlink) so it can carry a Claude-only section later and avoids Windows symlink +# friction. This is the body already committed to Mind and Brain. +CLAUDE_MD_POINTER = """\ +@AGENTS.md + + +""" + +# An `@AGENTS.md` import on its own line — the real bridge, not prose that merely +# mentions AGENTS.md (the dead-pointer failure mode that motivated this check). +CLAUDE_IMPORT_RE = re.compile(r"(?m)^@AGENTS\.md\s*$") + def load_manifest(mind_root): data = yaml.safe_load((mind_root / "repos.yaml").read_text()) @@ -164,6 +183,17 @@ def replace_block(path, content, begin=MARK_BEGIN, end=MARK_END): return changed +def extract_block(text, begin, end): + """Return the exact content --write would have placed between the markers, + or None if the marker pair is absent or empty. The counterpart to + replace_block, used by the drift check so a generated block that has been + hand-edited or left stale (repos.yaml changed without a --write) is caught.""" + m = re.search( + re.escape(begin) + r"\n(.*?)\n" + re.escape(end), text, re.DOTALL + ) + return m.group(1) if m else None + + def write_block(path, content, begin=MARK_BEGIN, end=MARK_END, *, required): """Fill a marked block, tolerant of partial checkouts. @@ -241,6 +271,110 @@ def check_labels(root, repos): return problems +# -------------------------------------------------------------------------- +# Generated-block drift (the organism map is written into each organ) +# -------------------------------------------------------------------------- +# +# The organism-map block is generated into any organ that opts in via the map +# markers. In practice that is PyAutoBrain, which is loaded in every session +# (web, mobile/code, local), so its auto-loaded AGENTS.md carries the map into +# every session's context — one copy, no per-organ duplication. Wherever a copy +# exists it must not drift from the manifest, so — mirroring how the command +# surface is checked by install.sh --check-agents-surface — this verifies each +# present map block still equals what system_map() generates. A block that was +# hand-edited, or left stale after a repos.yaml change without a --write, is +# reported as drift. + + +def check_map_blocks(root, repos, smap): + problems = [] + for name, repo in repos.items(): + if repo["category"] != "organ": + continue + agents = root / name / "AGENTS.md" + if not agents.exists(): + continue # not checked out, or an organ without its own AGENTS.md + text = agents.read_text() + if MAP_BEGIN not in text or MAP_END not in text: + continue # opt-in: an organ that has not added the map markers + if extract_block(text, MAP_BEGIN, MAP_END) != smap: + problems.append( + f"'{name}': organism-map block is stale — run " + f"`python3 PyAutoMind/scripts/repos_sync.py --write`" + ) + return problems + + +# -------------------------------------------------------------------------- +# CLAUDE.md → AGENTS.md pointer (repo hygiene) +# -------------------------------------------------------------------------- +# +# Standard: guidance lives in the agnostic AGENTS.md; Claude Code reads +# CLAUDE.md, so every repo that HAS an AGENTS.md keeps a content-free CLAUDE.md +# that `@`-imports it. This is a pure function of "is this repo checked out and +# does it have an AGENTS.md?", so it lives here beside the other body-map drift +# checks. Repos with no AGENTS.md are reported (for a human) but not auto-stubbed +# — writing real per-repo guidance is its own work, out of scope here. Absent +# (not-checked-out) repos are skipped, exactly like the map-block generation, so +# this runs cleanly in a partial/web checkout. + + +def claude_md_is_pointer(text): + """A CLAUDE.md counts as compliant iff it `@`-imports AGENTS.md on its own + line (a real import that expands into context), not merely prose naming it.""" + return CLAUDE_IMPORT_RE.search(text) is not None + + +def check_claude_md_pointers(root, repos): + problems = [] + for name in repos: + repo_dir = root / name + if not repo_dir.is_dir(): + continue # not checked out in this environment + if not (repo_dir / "AGENTS.md").exists(): + continue # AGENTS-less repos are reported separately, not drift + claude = repo_dir / "CLAUDE.md" + if not claude.exists(): + problems.append(f"'{name}': has AGENTS.md but no CLAUDE.md pointer") + elif not claude_md_is_pointer(claude.read_text()): + problems.append( + f"'{name}': CLAUDE.md does not @-import AGENTS.md (dead pointer)" + ) + return problems + + +def repos_without_agents_md(root, repos): + """Checked-out repos that have no AGENTS.md at all — the pointer is + meaningless without a target, so these are reported for a human to write + real guidance rather than auto-stubbed.""" + return [ + name + for name in repos + if (root / name).is_dir() and not (root / name / "AGENTS.md").exists() + ] + + +def write_claude_md_pointers(root, repos): + """Create the canonical pointer wherever a checked-out repo has an AGENTS.md + but a missing or non-compliant CLAUDE.md. Idempotent: a repo already carrying + the `@AGENTS.md` import is left untouched; a repo with no AGENTS.md is + skipped (nothing to point at).""" + for name in repos: + repo_dir = root / name + if not repo_dir.is_dir(): + continue + if not (repo_dir / "AGENTS.md").exists(): + print(f"skipped (no AGENTS.md): {repo_dir / 'CLAUDE.md'}") + continue + claude = repo_dir / "CLAUDE.md" + if claude.exists() and claude_md_is_pointer(claude.read_text()): + print(f"unchanged: {claude}") + continue + verb = "rewrote (dead pointer)" if claude.exists() else "created" + claude.write_text(CLAUDE_MD_POINTER) + print(f"{verb}: {claude}") + + # -------------------------------------------------------------------------- # Tenant firewall # -------------------------------------------------------------------------- @@ -433,17 +567,19 @@ def main(): root = args.root or mind_root.parent categories, repos = load_manifest(mind_root) + smap = system_map(categories, repos) + if args.write: write_block(root / "AGENTS.md", routing_table(categories, repos), required=True) write_block(root / "PyAutoBrain/skills/WORKFLOW.md", owner_map(categories, repos), required=True) - smap = system_map(categories, repos) for name, repo in repos.items(): if repo["category"] != "organ": continue write_block(root / name / "AGENTS.md", smap, MAP_BEGIN, MAP_END, required=False) + write_claude_md_pointers(root, repos) checks = { "PyAutoHeart/config/repos.yaml": check_heart(root, repos), @@ -451,6 +587,8 @@ def main(): "ensure_workspace_labels.sh": check_labels(root, repos), "local checkout origins": check_origins(root, repos), "tenant firewall (organ code)": check_tenant_firewall(root, repos), + "organism-map blocks (generated)": check_map_blocks(root, repos, smap), + "CLAUDE.md → AGENTS.md pointers": check_claude_md_pointers(root, repos), } drift = False for label, problems in checks.items(): @@ -459,6 +597,16 @@ def main(): for p in problems: drift = True print(f" ✗ {p}") + + # AGENTS-less repos are reported (for a human to write real guidance), never + # auto-stubbed, and never fail the run. + missing = repos_without_agents_md(root, repos) + if missing: + print(f"note: {len(missing)} checked-out repo(s) have no AGENTS.md " + f"(pointer not applicable — needs human-written guidance):") + for name in missing: + print(f" • {name}") + sys.exit(1 if drift else 0) diff --git a/z_features/agents_md_standardization.md b/z_features/agents_md_standardization.md index 71043843..ca149dbe 100644 --- a/z_features/agents_md_standardization.md +++ b/z_features/agents_md_standardization.md @@ -7,7 +7,7 @@ Repos: Difficulty: large Autonomy: supervised Priority: normal -Status: formalised +Status: complete Epic tracker for three filed standardizations that share one root cause — *web/ mobile sessions load only committed, agent-agnostic repo files* — and should be @@ -92,3 +92,37 @@ separate list of every repo missing an `AGENTS.md`. + +## Outcome (2026-07-12) + +**Key design change during execution — the shared blocks are NOT duplicated.** +The epic assumed each organ needs its own embedded organism-map + command-surface +block (single-repo web sessions load only the open repo). That assumption was +corrected: **PyAutoBrain is guaranteed loaded in every web/mobile session**, so +its auto-loaded `AGENTS.md` already carries both shared blocks into every +session's context. They therefore live **once, in PyAutoBrain** — removed from +Mind/Heart/Build/Memory. Spec 1 (the per-repo `CLAUDE.md` pointer) is unchanged: +that is the one thing every repo still needs for its own guidance to load. + +**Tooling shipped** (drift-checks the single Brain copy against its source): +- `PyAutoMind/scripts/repos_sync.py`: `check_claude_md_pointers` + a `--write` + pointer sweep + AGENTS-less report; `check_map_blocks` (the map block was + generated but never checked — now it is). +- `PyAutoBrain/bin/install.sh`: `--write-agents-surface` / `--check-agents-surface` + generate the command surface from the `bin/pyauto-brain` registry (made + sourceable); guarded by two new Brain CI tests. + +**Compliance sweep (every repo in `repos.yaml`):** +- Organs (5): Mind/Brain carry the tooling; Brain holds both shared blocks; + Heart/Build/Memory got the `@AGENTS.md` pointer — **merged** (PyAutoHeart#67, + PyAutoBuild#147, PyAutoMemory#21). +- Non-organ, reachable (20): **17 already compliant** (AGENTS.md + `@AGENTS.md` + pointer) — zero PRs needed; the library/workspace rollout had already happened. +- **No `AGENTS.md` → reported, not auto-created:** `autolens_workspace_developer`, + `euclid_strong_lens_modeling_pipeline`, `pyautolabs.github.io`. +- **Unreachable this session** (`add_repo` cross-tier block; not `pyautolabs`): + `Jammy2211/autofit_workspace_developer`, `Jammy2211/euclid_assistant`, + `Jammy2211/admin_jammy` — need a session rooted on one of them. +- **Body-map gap fixed:** `autofit_assistant` existed in the org but was missing + from `repos.yaml` (its siblings were present) — added under `assistant`, so the + drift checks now cover it. (It was already pointer-compliant.)