feat(agents-init): retire CLAUDE.md, discover and normalize AGENTS.md in every subdirectory #177
+51
-103
@@ -5,7 +5,7 @@
|
|||||||
# 12-ai-and-developer-tools
|
# 12-ai-and-developer-tools
|
||||||
#
|
#
|
||||||
# DEPENDENCIES
|
# DEPENDENCIES
|
||||||
# _agents_repo_install_tools, _agents_repo_sync, _agents_init_ensure_gitignore
|
# _agents_init_sync_instructions, _agents_repo_install_tools, _agents_repo_sync, _agents_init_ensure_gitignore
|
||||||
#
|
#
|
||||||
# CLASSIFICATION
|
# CLASSIFICATION
|
||||||
# self-limiting(rm,mkdir), bypasses-shadow(mv)
|
# self-limiting(rm,mkdir), bypasses-shadow(mv)
|
||||||
@@ -17,8 +17,12 @@
|
|||||||
# DESCRIPTION
|
# DESCRIPTION
|
||||||
# Scaffolds an AGENTS/ sub-repository inside a project directory. Creates
|
# Scaffolds an AGENTS/ sub-repository inside a project directory. Creates
|
||||||
# a self-contained git repo for agent specifications, moves any existing
|
# a self-contained git repo for agent specifications, moves any existing
|
||||||
# agent-related files into it, and replaces them with symlinks so the outer
|
# agent-related files into it, and replaces them with symlinks so the
|
||||||
# project never tracks agent files directly.
|
# outer project never tracks agent files directly. This applies at the
|
||||||
|
# project root and, automatically, to any subdirectory that carries its
|
||||||
|
# own scoped AGENTS.md or CLAUDE.md -- discovered by scanning the tree
|
||||||
|
# (pruning .git/, node_modules/, and AGENTS/ itself), not a hardcoded
|
||||||
|
# list.
|
||||||
#
|
#
|
||||||
# Scaffolding runs only inside a git repository, or in a directory that
|
# Scaffolding runs only inside a git repository, or in a directory that
|
||||||
# already has an AGENTS.md, CLAUDE.md, or AGENTS/. Elsewhere it is a
|
# already has an AGENTS.md, CLAUDE.md, or AGENTS/. Elsewhere it is a
|
||||||
@@ -26,11 +30,12 @@
|
|||||||
# create a repository there.
|
# create a repository there.
|
||||||
#
|
#
|
||||||
# File layout after setup:
|
# File layout after setup:
|
||||||
# AGENTS/AGENTS.md canonical agent spec (real file)
|
# AGENTS/AGENTS.md canonical root agent spec (real file)
|
||||||
# AGENTS/CLAUDE.md real file (if CLAUDE.md existed separately)
|
# AGENTS/<subdir>/AGENTS.md canonical spec for any subdir with its own
|
||||||
# or symlink → AGENTS.md (single-source case)
|
# scoped instructions (real file, discovered
|
||||||
|
# automatically -- see above)
|
||||||
# <root>/AGENTS.md → AGENTS/AGENTS.md
|
# <root>/AGENTS.md → AGENTS/AGENTS.md
|
||||||
# <root>/CLAUDE.md → AGENTS/CLAUDE.md
|
# <root>/<subdir>/AGENTS.md → AGENTS/<subdir>/AGENTS.md
|
||||||
# AGENTS/plans superpowers plans (real dir, .gitkeep)
|
# AGENTS/plans superpowers plans (real dir, .gitkeep)
|
||||||
# AGENTS/specs superpowers specs (real dir, .gitkeep)
|
# AGENTS/specs superpowers specs (real dir, .gitkeep)
|
||||||
# AGENTS/devlogs agent development logs (real dir, .gitkeep)
|
# AGENTS/devlogs agent development logs (real dir, .gitkeep)
|
||||||
@@ -42,6 +47,12 @@
|
|||||||
# docs/specs → ../AGENTS/specs (only if docs/specs existed)
|
# docs/specs → ../AGENTS/specs (only if docs/specs existed)
|
||||||
# docs/devlogs → ../AGENTS/devlogs (only if docs/devlogs existed)
|
# docs/devlogs → ../AGENTS/devlogs (only if docs/devlogs existed)
|
||||||
#
|
#
|
||||||
|
# No CLAUDE.md survives anywhere in a managed tree: claude-code reads
|
||||||
|
# AGENTS.md natively when CLAUDE.md is absent, so CLAUDE.md exists here
|
||||||
|
# purely as a retirement target -- any found (root or subdirectory, real
|
||||||
|
# file or leftover symlink) is folded into the AGENTS.md-only shape
|
||||||
|
# above by _agents_init_sync_instructions.
|
||||||
|
#
|
||||||
# plans/ and specs/ are merged from every legacy location (docs/<tgt>,
|
# plans/ and specs/ are merged from every legacy location (docs/<tgt>,
|
||||||
# docs/superpowers/<tgt>, and the old AGENTS/plugins/ layout) into the
|
# docs/superpowers/<tgt>, and the old AGENTS/plugins/ layout) into the
|
||||||
# canonical AGENTS/<tgt>; the AGENTS/plugins/ layer is removed.
|
# canonical AGENTS/<tgt>; the AGENTS/plugins/ layer is removed.
|
||||||
@@ -75,7 +86,8 @@
|
|||||||
# Called automatically by the claude and agy wrappers on every invocation.
|
# Called automatically by the claude and agy wrappers on every invocation.
|
||||||
#
|
#
|
||||||
# ARGUMENTS
|
# ARGUMENTS
|
||||||
# -a, --agents Set up AGENTS/ repo + AGENTS.md / CLAUDE.md symlinks only
|
# -a, --agents Set up AGENTS/ repo + AGENTS.md symlinks (root and every
|
||||||
|
# discovered subdirectory) only
|
||||||
# -p, --plugins Set up AGENTS/ repo + plans/specs/devlogs dirs + docs/ symlinks only
|
# -p, --plugins Set up AGENTS/ repo + plans/specs/devlogs dirs + docs/ symlinks only
|
||||||
# -v, --verbose Print all per-step output (default)
|
# -v, --verbose Print all per-step output (default)
|
||||||
# -q, --quiet Print one summary line only if changes were made
|
# -q, --quiet Print one summary line only if changes were made
|
||||||
@@ -105,7 +117,7 @@ function agents-init --description 'scaffold AGENTS/ sub-repo with agent spec fi
|
|||||||
echo
|
echo
|
||||||
echo "$c_head""Options:$c_reset"
|
echo "$c_head""Options:$c_reset"
|
||||||
echo " $c_flag-h$c_reset, $c_flag--help$c_reset Show this help message"
|
echo " $c_flag-h$c_reset, $c_flag--help$c_reset Show this help message"
|
||||||
echo " $c_flag-a$c_reset, $c_flag--agents$c_reset Set up AGENTS.md / CLAUDE.md symlinks only"
|
echo " $c_flag-a$c_reset, $c_flag--agents$c_reset Set up AGENTS.md symlinks only"
|
||||||
echo " $c_flag-p$c_reset, $c_flag--plugins$c_reset Set up plans/specs/devlogs dirs and docs/ symlinks only"
|
echo " $c_flag-p$c_reset, $c_flag--plugins$c_reset Set up plans/specs/devlogs dirs and docs/ symlinks only"
|
||||||
echo " $c_flag-v$c_reset, $c_flag--verbose$c_reset Print all per-step output (default)"
|
echo " $c_flag-v$c_reset, $c_flag--verbose$c_reset Print all per-step output (default)"
|
||||||
echo " $c_flag-q$c_reset, $c_flag--quiet$c_reset Print one summary line only if changes were made"
|
echo " $c_flag-q$c_reset, $c_flag--quiet$c_reset Print one summary line only if changes were made"
|
||||||
@@ -201,109 +213,45 @@ function agents-init --description 'scaffold AGENTS/ sub-repo with agent spec fi
|
|||||||
|
|
||||||
# ──────────────────────────── --agents mode ──────────────────────────────
|
# ──────────────────────────── --agents mode ──────────────────────────────
|
||||||
if test $do_agents -eq 1
|
if test $do_agents -eq 1
|
||||||
# Detect which root-level files are real (not symlinks)
|
# Discover every directory carrying agent instructions -- root
|
||||||
set -l has_agents 0
|
# included, subdirectories found automatically rather than by a
|
||||||
set -l has_claude 0
|
# hardcoded list. A real file, an already-migrated symlink, or a
|
||||||
if test -f "$root/AGENTS.md"; and not test -L "$root/AGENTS.md"
|
# leftover inverted-mirror survivor all match, so one pass covers
|
||||||
set has_agents 1
|
# fresh, migrated, and legacy state alike. AGENTS/ itself is
|
||||||
end
|
# pruned: it is the mirror, never a source to discover.
|
||||||
if test -f "$root/CLAUDE.md"; and not test -L "$root/CLAUDE.md"
|
set -l found (find "$root" \
|
||||||
set has_claude 1
|
\( -name .git -o -path "$agents_dir" -o -name node_modules \) -prune -o \
|
||||||
|
\( -name AGENTS.md -o -name CLAUDE.md \) -print)
|
||||||
|
set -l rels "."
|
||||||
|
for f in $found
|
||||||
|
set -l d (path dirname "$f")
|
||||||
|
set -l rel (string replace "$root/" "" "$d")
|
||||||
|
test "$rel" = "$d"; and set rel "."
|
||||||
|
contains -- "$rel" $rels; or set -a rels "$rel"
|
||||||
end
|
end
|
||||||
|
|
||||||
# ── Move real files into AGENTS/ ──────────────────────────────────────
|
for rel in $rels
|
||||||
if test $has_agents -eq 1; and test $has_claude -eq 1
|
set -l out (_agents_init_sync_instructions "$root" "$agents_dir" "$rel")
|
||||||
# Both exist: preserve each as its own file in AGENTS/
|
set -l rc $status
|
||||||
if not test -f "$agents_dir/AGENTS.md"
|
if test $rc -ne 0
|
||||||
if not command mv "$root/AGENTS.md" "$agents_dir/AGENTS.md"
|
echo "$c_err""Error: could not sync AGENTS.md for $rel$c_reset" >&2
|
||||||
echo "$c_err""Error: could not move AGENTS.md → AGENTS/AGENTS.md$c_reset" >&2
|
|
||||||
return 1
|
|
||||||
end
|
|
||||||
set changed 1
|
|
||||||
test $verbose -eq 1; and echo "$c_ok→ Moved AGENTS.md → AGENTS/AGENTS.md$c_reset"
|
|
||||||
end
|
|
||||||
if not test -f "$agents_dir/CLAUDE.md"; and not test -L "$agents_dir/CLAUDE.md"
|
|
||||||
if not command mv "$root/CLAUDE.md" "$agents_dir/CLAUDE.md"
|
|
||||||
echo "$c_err""Error: could not move CLAUDE.md → AGENTS/CLAUDE.md$c_reset" >&2
|
|
||||||
return 1
|
|
||||||
end
|
|
||||||
set changed 1
|
|
||||||
test $verbose -eq 1; and echo "$c_ok→ Moved CLAUDE.md → AGENTS/CLAUDE.md$c_reset"
|
|
||||||
end
|
|
||||||
else if test $has_agents -eq 1
|
|
||||||
if not test -f "$agents_dir/AGENTS.md"
|
|
||||||
if not command mv "$root/AGENTS.md" "$agents_dir/AGENTS.md"
|
|
||||||
echo "$c_err""Error: could not move AGENTS.md → AGENTS/AGENTS.md$c_reset" >&2
|
|
||||||
return 1
|
|
||||||
end
|
|
||||||
set changed 1
|
|
||||||
test $verbose -eq 1; and echo "$c_ok→ Moved AGENTS.md → AGENTS/AGENTS.md$c_reset"
|
|
||||||
end
|
|
||||||
else if test $has_claude -eq 1
|
|
||||||
# Only CLAUDE.md: treat it as the agent spec
|
|
||||||
if not test -f "$agents_dir/AGENTS.md"
|
|
||||||
if not command mv "$root/CLAUDE.md" "$agents_dir/AGENTS.md"
|
|
||||||
echo "$c_err""Error: could not move CLAUDE.md → AGENTS/AGENTS.md$c_reset" >&2
|
|
||||||
return 1
|
|
||||||
end
|
|
||||||
set changed 1
|
|
||||||
test $verbose -eq 1; and echo "$c_ok→ Moved CLAUDE.md → AGENTS/AGENTS.md$c_reset"
|
|
||||||
end
|
|
||||||
else
|
|
||||||
# Neither exists: create AGENTS/AGENTS.md with the agent directive
|
|
||||||
if not test -f "$agents_dir/AGENTS.md"
|
|
||||||
printf '%s\n' \
|
|
||||||
'# AGENTS.md' \
|
|
||||||
'' \
|
|
||||||
'> ⚠️ **SYSTEM DIRECTIVE FOR AI AGENTS: FILE EDITING**' \
|
|
||||||
'> You may be reading this file via a symlink (`CLAUDE.md` or `AGENTS.md`) in' \
|
|
||||||
'> the root of the project. Your environment'\''s file-editing tools cannot write' \
|
|
||||||
'> through symlinks and will throw an error.' \
|
|
||||||
'>' \
|
|
||||||
'> **DO NOT** attempt to write to or edit `CLAUDE.md` or `AGENTS.md` in the' \
|
|
||||||
'> project root. If you need to update these instructions, you **MUST write' \
|
|
||||||
'> directly to `AGENTS/AGENTS.md`**.' >"$agents_dir/AGENTS.md"
|
|
||||||
set changed 1
|
|
||||||
test $verbose -eq 1; and echo "$c_ok→ Created AGENTS/AGENTS.md with agent directive$c_reset"
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# ── Ensure AGENTS/CLAUDE.md exists ────────────────────────────────────
|
|
||||||
# When both files existed, AGENTS/CLAUDE.md is already a real file.
|
|
||||||
# Otherwise, create it as a symlink → AGENTS.md (within AGENTS/).
|
|
||||||
if not test -f "$agents_dir/CLAUDE.md"; and not test -L "$agents_dir/CLAUDE.md"
|
|
||||||
if not ln -s AGENTS.md "$agents_dir/CLAUDE.md"
|
|
||||||
echo "$c_err""Error: could not create AGENTS/CLAUDE.md symlink$c_reset" >&2
|
|
||||||
return 1
|
return 1
|
||||||
end
|
end
|
||||||
set changed 1
|
if test -n "$out"
|
||||||
test $verbose -eq 1; and echo "$c_ok→ Linked AGENTS/CLAUDE.md → AGENTS/AGENTS.md$c_reset"
|
|
||||||
end
|
|
||||||
|
|
||||||
# Root symlinks point at files, not directories, so they cannot use
|
|
||||||
# _agents_repo_ensure_symlink (which is directory-only by design).
|
|
||||||
for pair in "AGENTS.md:AGENTS/AGENTS.md" "CLAUDE.md:AGENTS/CLAUDE.md"
|
|
||||||
set -l name (string split -f1 ':' -- $pair)
|
|
||||||
set -l want (string split -f2 ':' -- $pair)
|
|
||||||
set -l need 0
|
|
||||||
if not test -L "$root/$name"
|
|
||||||
set need 1
|
|
||||||
else if test (readlink "$root/$name") != "$want"
|
|
||||||
rm -f "$root/$name"
|
|
||||||
set need 1
|
|
||||||
end
|
|
||||||
if test $need -eq 1
|
|
||||||
if not ln -s "$want" "$root/$name"
|
|
||||||
echo "$c_err""Error: could not create $name symlink$c_reset" >&2
|
|
||||||
return 1
|
|
||||||
end
|
|
||||||
set changed 1
|
set changed 1
|
||||||
test $verbose -eq 1; and echo "$c_ok→ Linked $name → $want$c_reset"
|
if test $verbose -eq 1
|
||||||
|
for line in $out
|
||||||
|
echo "$c_ok$line$c_reset"
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# ── .gitignore ────────────────────────────────────────────────────────
|
# ── .gitignore ────────────────────────────────────────────────────────
|
||||||
set -l _gi (_agents_init_ensure_gitignore "$root" "agents-init --agents" "AGENTS/" "/AGENTS.md" "/CLAUDE.md")
|
# Unanchored: matches AGENTS.md at every depth, so a newly
|
||||||
|
# discovered subdirectory needs no additional gitignore entry.
|
||||||
|
# CLAUDE.md is dropped entirely -- nothing creates one anymore.
|
||||||
|
set -l _gi (_agents_init_ensure_gitignore "$root" "agents-init --agents" "AGENTS/" "AGENTS.md")
|
||||||
if test -n "$_gi"
|
if test -n "$_gi"
|
||||||
set changed 1
|
set changed 1
|
||||||
test $verbose -eq 1; and echo $_gi
|
test $verbose -eq 1; and echo $_gi
|
||||||
|
|||||||
@@ -156,5 +156,48 @@ check "subdir lone AGENTS.md: no mirror CLAUDE.md" false (test -e $r7/AGENTS/onl
|
|||||||
check "subdir lone AGENTS.md: project AGENTS.md links to mirror" ../AGENTS/onlyagents/AGENTS.md (readlink $r7/onlyagents/AGENTS.md)
|
check "subdir lone AGENTS.md: project AGENTS.md links to mirror" ../AGENTS/onlyagents/AGENTS.md (readlink $r7/onlyagents/AGENTS.md)
|
||||||
check "subdir lone AGENTS.md: no project CLAUDE.md" false (test -e $r7/onlyagents/CLAUDE.md; and echo true; or echo false)
|
check "subdir lone AGENTS.md: no project CLAUDE.md" false (test -e $r7/onlyagents/CLAUDE.md; and echo true; or echo false)
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "== agents-init: end-to-end CLAUDE.md retirement =="
|
||||||
|
|
||||||
|
set -l e1 (new_repo)
|
||||||
|
echo root-real >$e1/CLAUDE.md
|
||||||
|
mkdir -p $e1/functions
|
||||||
|
echo scoped-real >$e1/functions/CLAUDE.md
|
||||||
|
pushd $e1 >/dev/null
|
||||||
|
set -l ercA (agents-init --agents --silent 2>/dev/null; echo $status)
|
||||||
|
popd >/dev/null
|
||||||
|
check "e2e: exits 0" 0 "$ercA"
|
||||||
|
check "e2e: root CLAUDE.md gone" false (test -e $e1/CLAUDE.md; and echo true; or echo false)
|
||||||
|
check "e2e: root AGENTS.md links to mirror" AGENTS/AGENTS.md (readlink $e1/AGENTS.md)
|
||||||
|
check "e2e: root content preserved" root-real (cat $e1/AGENTS.md)
|
||||||
|
check "e2e: functions CLAUDE.md gone" false (test -e $e1/functions/CLAUDE.md; and echo true; or echo false)
|
||||||
|
check "e2e: functions AGENTS.md links to mirror" ../AGENTS/functions/AGENTS.md (readlink $e1/functions/AGENTS.md)
|
||||||
|
check "e2e: functions content preserved" scoped-real (cat $e1/functions/AGENTS.md)
|
||||||
|
check "e2e: no CLAUDE.md left anywhere under AGENTS/" "" (find $e1/AGENTS -name CLAUDE.md)
|
||||||
|
check "e2e: gitignore covers AGENTS.md unanchored" true (grep -qx 'AGENTS.md' $e1/.gitignore; and echo true; or echo false)
|
||||||
|
|
||||||
|
pushd $e1 >/dev/null
|
||||||
|
set -l ercB (agents-init --agents --quiet 2>/dev/null)
|
||||||
|
popd >/dev/null
|
||||||
|
check "e2e: idempotent second run prints nothing" "" "$ercB"
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "== agents-init: this-repo-shaped inversion is fixed live =="
|
||||||
|
|
||||||
|
set -l e2 (new_repo)
|
||||||
|
mkdir -p $e2/AGENTS/docs $e2/docs
|
||||||
|
echo docs-content >$e2/AGENTS/docs/CLAUDE.md
|
||||||
|
ln -s CLAUDE.md $e2/AGENTS/docs/AGENTS.md
|
||||||
|
ln -s CLAUDE.md $e2/docs/AGENTS.md
|
||||||
|
ln -s ../AGENTS/docs/CLAUDE.md $e2/docs/CLAUDE.md
|
||||||
|
pushd $e2 >/dev/null
|
||||||
|
set -l ercC (agents-init --agents --silent 2>/dev/null; echo $status)
|
||||||
|
popd >/dev/null
|
||||||
|
check "inversion fix: exits 0" 0 "$ercC"
|
||||||
|
check "inversion fix: mirror AGENTS.md real" docs-content (cat $e2/AGENTS/docs/AGENTS.md)
|
||||||
|
check "inversion fix: mirror CLAUDE.md gone" false (test -e $e2/AGENTS/docs/CLAUDE.md; and echo true; or echo false)
|
||||||
|
check "inversion fix: project docs/AGENTS.md relinked directly" ../AGENTS/docs/AGENTS.md (readlink $e2/docs/AGENTS.md)
|
||||||
|
check "inversion fix: project docs/CLAUDE.md gone" false (test -e $e2/docs/CLAUDE.md; and echo true; or echo false)
|
||||||
|
|
||||||
cleanup
|
cleanup
|
||||||
report
|
report
|
||||||
|
|||||||
Reference in New Issue
Block a user