Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 0 additions & 22 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,28 +3,6 @@
This file is for AI coding agents (Claude Code, Codex, Cursor, etc.) discovering
this repository.

## The organism map

<!-- repos_sync:map:begin -->
**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`.
<!-- repos_sync:map:end -->

## What this repo is

**PyAutoMind is the Mind of the PyAuto organism and the starting point of the
Expand Down
40 changes: 40 additions & 0 deletions maintenance/jammy2211/agents_md_pointer_sweep.md
Original file line number Diff line number Diff line change
@@ -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.

<!-- filed 2026-07-12 as the reachability tail of the standardization epic; the
PyAutoLabs sweep found 17/17 eligible repos already compliant, so this is
expected to be tiny (likely 0-3 trivial pointer PRs). -->
10 changes: 9 additions & 1 deletion maintenance/pyautobrain/command_surface_in_agents_md.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,15 @@ Repos:
Difficulty: medium
Autonomy: supervised
Priority: normal
Status: formalised
Status: complete

<!-- RESOLVED 2026-07-12 (via z_features/agents_md_standardization.md). Landed via
recommendation (a): install.sh --write-agents-surface / --check-agents-surface,
generated from the bin/pyauto-brain agent registry (the single source), drift-
checked by two Brain CI tests. Scope change: the block is NOT duplicated into
every organ — PyAutoBrain is guaranteed loaded in every session, so the surface
lives once in Brain's auto-loaded AGENTS.md and reaches everywhere. Per-tool
slash sugar remains deferred. See the epic's Outcome section. -->

Generate the command surface into AGENTS.md. Make the PyAuto command surface
(`intake`, `route`, `start_dev`, `ship_*`, the conductor verbs, etc.)
Expand Down
10 changes: 9 additions & 1 deletion maintenance/pyautomind/claude_md_standardization.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,15 @@ Repos:
Difficulty: large
Autonomy: supervised
Priority: normal
Status: formalised
Status: complete

<!-- RESOLVED 2026-07-12 (via z_features/agents_md_standardization.md). Landed as
spec'd: check_claude_md_pointers + a --write pointer sweep + an AGENTS-less
report in repos_sync.py; recommendation (a) taken (enforce the pointer only
where an AGENTS.md exists; report AGENTS-less repos, never auto-stub); root-
of-repo only. Sweep found every reachable AGENTS.md-bearing repo already
carries a working @AGENTS.md pointer (organs fixed this session: Heart#67,
Build#147, Memory#21). See the epic's Outcome section for the full report. -->

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,
Expand Down
3 changes: 3 additions & 0 deletions repos.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
150 changes: 149 additions & 1 deletion scripts/repos_sync.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,25 @@
MAP_BEGIN = "<!-- repos_sync:map:begin -->"
MAP_END = "<!-- repos_sync: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

<!-- Guidance is agent-agnostic and lives in AGENTS.md (read natively by Codex,
Cursor, etc.). Claude Code loads CLAUDE.md, not AGENTS.md, so this file exists
only to import that one source. Keep it a pointer — put content in 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())
Expand Down Expand Up @@ -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.

Expand Down Expand Up @@ -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
# --------------------------------------------------------------------------
Expand Down Expand Up @@ -433,24 +567,28 @@ 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),
"PyAutoBuild/pre_build.sh": check_pre_build(root, repos),
"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():
Expand All @@ -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)


Expand Down
36 changes: 35 additions & 1 deletion z_features/agents_md_standardization.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -92,3 +92,37 @@ separate list of every repo missing an `AGENTS.md`.
<!-- filed 2026-07-12 from a /remote-control conversation as the runnable epic
over the three same-day maintenance specs. Root cause shared: web/mobile
loads only committed agent-agnostic repo files, not user-level ~/.claude. -->

## 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.)