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
72 changes: 72 additions & 0 deletions .claude/skills/bootstrap-sibling/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
---
name: bootstrap-sibling
description: >
Bootstrap a new aligned AgentCulture sibling end-to-end: GitHub repo
create, local clone, afi-cli scaffold, and pypi/testpypi Trusted-Publishing
Environments. Each ghafi step dry-runs first and waits for confirmation
before applying. Use when starting a new sibling (e.g. "bootstrap a new
sibling called X", "create a new agentculture repo", "stand up the next
sibling").
---

# Bootstrap Sibling

Single-command bootstrap for a new AgentCulture sibling repo. Chains the
four steps documented in the project's `CLAUDE.md` ("Bootstrap walkthrough
(new sibling)") with dry-run-then-apply gates, so an agent or human can
drive a full sibling stand-up without remembering the verb order.

The script does **not** automate the PyPI-side trusted-publisher
registration — that's a one-time web flow per project, and ghafi by design
cannot perform it. The script prints the PyPI registration URLs at the end
as a checklist.

## When to use

- Standing up a brand-new sibling under the `agentculture` org.
- After every `ghafi` release, to verify the bootstrap path still works
end-to-end against a throwaway repo name.

## When **not** to use

- Modifying an existing sibling — use the individual `ghafi repo …` verbs.
- Repos outside the AgentCulture org with non-default conventions.

## Usage

Run from the `ghafi` repo root. The script will `git clone` the new repo
into a sibling path next to ghafi (`../<name>`).
Comment thread
OriNachum marked this conversation as resolved.
Comment thread
OriNachum marked this conversation as resolved.

```bash
bash .claude/skills/bootstrap-sibling/scripts/bootstrap.sh \
--name <repo-name> \
--description "<one-line description>" \
[--org agentculture] \
[--private]
```

Without `--apply`, every ghafi step prints its dry-run output and the
script exits before mutating anything. Pass `--apply` (after reviewing the
dry-run) to commit:

```bash
bash .claude/skills/bootstrap-sibling/scripts/bootstrap.sh \
--name <repo-name> --description "..." --apply
```

## What the script does

1. `ghafi repo create --org <org> --description "<…>" <name>` — POST `/orgs/{org}/repos`.
2. `git clone https://github.com/<org>/<name>.git ../<name>` — local clone next to ghafi.
3. `ghafi repo scaffold ../<name>` — shells to `afi cli cite`; writes the
reference template under `.afi/reference/python-cli/`. **Note:** this
does not instantiate `{{slug}}` into a runnable package; that's a
follow-up the script does not perform.
4. `ghafi repo env --owner <org> --name pypi --branch main <name>` — PUT pypi env (main only).
5. `ghafi repo env --owner <org> --name testpypi <name>` — PUT testpypi env (any branch).
6. Prints a manual checklist: register the trusted publisher on pypi.org
and test.pypi.org pointing at `<org>/<name>`, workflow `publish.yml`.

If `GITHUB_TOKEN` and `GH_TOKEN` are both unset, the script bridges from
`gh auth token` so users with `gh` already authenticated don't need a
separate PAT.
127 changes: 127 additions & 0 deletions .claude/skills/bootstrap-sibling/scripts/bootstrap.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
#!/usr/bin/env bash
# bootstrap.sh — chain `ghafi repo {create,scaffold,env}` to stand up a
# new AgentCulture sibling end-to-end. Dry-run by default; --apply to
# commit. See ../SKILL.md for rationale.

set -euo pipefail

ORG="agentculture"
NAME=""
DESCRIPTION=""
PRIVATE=""
APPLY=""

usage() {
cat <<'EOF'
Usage:
bootstrap.sh --name <repo> --description "<…>" [--org agentculture] [--private] [--apply]

Without --apply, every ghafi step prints its dry-run output and the
script exits before performing any mutation. With --apply, the script
runs the full bootstrap (repo create, local clone, scaffold, env pypi,
env testpypi) with a confirmation prompt after the dry-run review.
EOF
}

while [[ $# -gt 0 ]]; do
case "$1" in
--name) NAME="$2"; shift 2 ;;
--description) DESCRIPTION="$2"; shift 2 ;;
--org) ORG="$2"; shift 2 ;;
--private) PRIVATE="--private"; shift ;;
--apply) APPLY="--apply"; shift ;;
-h|--help) usage; exit 0 ;;
*) echo "error: unknown arg: $1" >&2; usage; exit 2 ;;
esac
done

if [[ -z "$NAME" || -z "$DESCRIPTION" ]]; then
echo "error: --name and --description are required" >&2
usage
exit 2
fi

# Bridge gh auth → GITHUB_TOKEN if not already set.
if [[ -z "${GITHUB_TOKEN:-}" && -z "${GH_TOKEN:-}" ]]; then
if command -v gh >/dev/null 2>&1; then
GITHUB_TOKEN="$(gh auth token 2>/dev/null || true)"
if [[ -n "$GITHUB_TOKEN" ]]; then
export GITHUB_TOKEN
echo "note: bridged GITHUB_TOKEN from \`gh auth token\`"
fi
Comment thread
OriNachum marked this conversation as resolved.
fi
fi

GHAFI=(uv run ghafi)
REPO_ROOT="$(cd "$(dirname "$0")/../../../.." && pwd)"
TARGET="$REPO_ROOT/../$NAME"

echo "=== Plan ==="
echo " org: $ORG"
echo " name: $NAME"
echo " description: $DESCRIPTION"
echo " private: ${PRIVATE:-false}"
echo " local target: $TARGET"
echo " mode: ${APPLY:-dry-run}"
echo

# Step 1: repo create (always dry-run first; --apply if requested)
echo "=== Step 1: repo create (dry-run preview) ==="
"${GHAFI[@]}" repo create --org "$ORG" --description "$DESCRIPTION" $PRIVATE "$NAME"
echo

# Step 4 + 5 dry-run preview (envs)
echo "=== Step 4: repo env pypi (dry-run preview) ==="
"${GHAFI[@]}" repo env --owner "$ORG" --name pypi --branch main "$NAME"
echo
echo "=== Step 5: repo env testpypi (dry-run preview) ==="
"${GHAFI[@]}" repo env --owner "$ORG" --name testpypi "$NAME"
echo

if [[ -z "$APPLY" ]]; then
echo "Dry-run complete. Re-run with --apply to commit."
exit 0
fi

# Confirmation gate before applying.
read -r -p "Apply the bootstrap (create + clone + scaffold + 2× env)? [y/N] " confirm
if [[ "$confirm" != "y" && "$confirm" != "Y" ]]; then
echo "Aborted."
exit 1
fi

echo
echo "=== Step 1: repo create --apply ==="
"${GHAFI[@]}" repo create --org "$ORG" --description "$DESCRIPTION" $PRIVATE "$NAME" --apply

echo
echo "=== Step 2: git clone $ORG/$NAME → $TARGET ==="
git clone "https://github.com/$ORG/$NAME.git" "$TARGET"

echo
echo "=== Step 3: repo scaffold --apply ==="
"${GHAFI[@]}" repo scaffold --apply "$TARGET"

echo
echo "=== Step 4: repo env pypi --apply ==="
"${GHAFI[@]}" repo env --owner "$ORG" --name pypi --branch main --apply "$NAME"

echo
echo "=== Step 5: repo env testpypi --apply ==="
"${GHAFI[@]}" repo env --owner "$ORG" --name testpypi --apply "$NAME"

cat <<EOF

=== Manual follow-up (web only) ===
1. https://pypi.org/manage/account/publishing/ → register publisher
2. https://test.pypi.org/manage/account/publishing/ → register publisher

Repository: $ORG/$NAME
Workflow: publish.yml
Environment: pypi (and testpypi on the test side)

3. Instantiate .afi/reference/python-cli/{{slug}}/ into the actual package
(afi-cli does not currently auto-instantiate; do this by hand or with a
future afi verb).
4. Author publish.yml + tests.yml workflows in $TARGET/.github/workflows/.
EOF
67 changes: 67 additions & 0 deletions .claude/skills/doc-test-align/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
---
name: doc-test-align
description: >
Verify that claims in CLAUDE.md (GitHub endpoints, required scopes,
bootstrap step order) still match what the code in `ghafi/` actually
does. Run before merging anything that touches CLAUDE.md or the
`ghafi.cli._commands.repo` module — catches doc drift that a normal
pytest pass would not.
---

# Doc / Test Alignment

A small, opinionated drift detector. CLAUDE.md makes empirical claims
about external systems — which GitHub REST endpoints `ghafi` calls,
which token scopes those calls require, what step order the bootstrap
walkthrough specifies. The mutation-safety pytest catches code regressions;
this skill catches the *documentation* equivalent.

This is a **stub** — v0 covers a narrow set of claims. Extend the script
when a new failure mode is found.

## When to use

- Reviewer is about to approve a PR that touches `CLAUDE.md`,
`ghafi/cli/_commands/repo.py`, or `ghafi/_api.py`.
- Before cutting a release.
- Periodically (e.g. quarterly) as a sweep to catch silent drift from
GitHub API changes.

## What it checks (v0)

1. **Endpoint mentions in CLAUDE.md exist in code.** Every `/repos/...`,
`/orgs/...`, `/user/...` URL referenced in CLAUDE.md should appear
somewhere in `ghafi/`. Strings only — does not validate semantics.
2. **Bootstrap step list matches verb set.** The "Bootstrap walkthrough"
section should reference the same `repo {create,scaffold,env}` verbs
that `ghafi/cli/_commands/repo.py` registers, and no others.
3. **Scope list claim is empirically backed.** If CLAUDE.md says scope
X is required, there should be a comment/test/CHANGELOG entry showing
it was tested. (v0 just lists scopes for human review; v1 should diff
against an `_api` annotation.)

## What it does **not** check

- Whether GitHub's actual scope requirements have changed upstream
(would require live API calls).
- Whether endpoint payloads still match GitHub's current schema.
- Prose accuracy beyond URLs and verb names.

These are deferred to a future revision; the user should add cases as
real drift is observed.

## Usage

```bash
bash .claude/skills/doc-test-align/scripts/check.sh
```

Exit codes:

- `0` — no drift detected.
- `1` — drift detected; details printed to stdout.
- `2` — script error (missing files, bad invocation).

The script is grep-and-compare only; no network, no test runs. Pair it
with the existing `markdownlint-cli2` and `portability-lint.sh` checks
in CI when you trust the v1 surface.
96 changes: 96 additions & 0 deletions .claude/skills/doc-test-align/scripts/check.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
#!/usr/bin/env bash
# check.sh — v0 doc/test alignment checker. See ../SKILL.md.
#
# Compares CLAUDE.md claims to ghafi/ source code. Stub: only catches a
# narrow class of drift today. Extend cases as real failures surface.

set -uo pipefail

REPO_ROOT="$(cd "$(dirname "$0")/../../../.." && pwd)"
cd "$REPO_ROOT"

CLAUDE_MD="CLAUDE.md"
SRC_DIR="ghafi"

if [[ ! -f "$CLAUDE_MD" ]]; then
echo "error: $CLAUDE_MD not found in $REPO_ROOT" >&2
exit 2
fi

drift=0

echo "=== Check 1: endpoint prefixes in CLAUDE.md exist in $SRC_DIR/ ==="
# Pull each endpoint URL from CLAUDE.md, strip {placeholders}, then
# check that each non-empty static segment is referenced somewhere in
# ghafi/. Coarse but stable.
endpoints=$(grep -oE '/(repos|orgs|user)(/[A-Za-z0-9_{}/.:-]+)?' "$CLAUDE_MD" | sort -u)
if [[ -z "$endpoints" ]]; then
echo " (no endpoint mentions found in $CLAUDE_MD — nothing to check)"
else
while IFS= read -r ep; do
# Replace {placeholder} with a separator and extract static segments.
cleaned=$(echo "$ep" | sed -E 's|\{[^}]+\}| |g')
miss=()
for seg in $cleaned; do
seg_trim="${seg#/}"
seg_trim="${seg_trim%/}"
[[ -z "$seg_trim" ]] && continue
if ! grep -RqF -- "$seg_trim" "$SRC_DIR/" 2>/dev/null; then
miss+=("$seg_trim")
fi
done
if [[ ${#miss[@]} -gt 0 ]]; then
echo " DRIFT: $CLAUDE_MD mentions '$ep' but these segments are absent from $SRC_DIR/: ${miss[*]}"
drift=1
fi
done <<<"$endpoints"
if [[ "$drift" -eq 0 ]]; then
echo " OK — every endpoint segment in $CLAUDE_MD has a code match"
fi
fi
echo

echo "=== Check 2: bootstrap walkthrough verbs match registered verbs ==="
# Introspect the live parser instead of regex'ing source — robust to
# multi-line add_parser() calls. If introspection fails (uv missing,
# import error, etc.) exit 2 so the failure is actionable rather than
# masquerading as drift.
introspect_stderr=$(mktemp)
trap 'rm -f "$introspect_stderr"' EXIT
if ! registered=$(uv run python -c '
import argparse
from ghafi.cli import _build_parser
p = _build_parser()
sub = next(a for a in p._actions if isinstance(a, argparse._SubParsersAction))
repo = sub.choices["repo"]
sub2 = next(a for a in repo._actions if isinstance(a, argparse._SubParsersAction))
print("\n".join(sorted(sub2.choices.keys())))
' 2>"$introspect_stderr"); then
echo " ERROR: parser introspection failed:" >&2
sed 's/^/ /' "$introspect_stderr" >&2
exit 2
fi
registered=$(echo "$registered" | sort -u)
mentioned=$(grep -oE 'ghafi repo [a-z]+' "$CLAUDE_MD" \
| awk '{print $3}' | sort -u || true)
echo " registered: $(echo "$registered" | tr '\n' ' ')"
echo " mentioned: $(echo "$mentioned" | tr '\n' ' ')"
for v in $mentioned; do
if ! grep -qx "$v" <<<"$registered"; then
echo " DRIFT: $CLAUDE_MD references 'ghafi repo $v' but no such verb is registered"
drift=1
fi
done
echo

echo "=== Check 3: scope claims (informational only, v0) ==="
grep -nE '^- \`[a-z_:]+\` —' "$CLAUDE_MD" | sed 's/^/ /'
echo " (v0: review the above by eye against the code; v1 should diff against _api annotations)"
echo

if [[ "$drift" -ne 0 ]]; then
echo "DRIFT detected. Update $CLAUDE_MD or the code so they agree."
exit 1
fi

echo "No drift detected."
18 changes: 18 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,24 @@ All notable changes to this project will be documented in this file.
Format follows [Keep a Changelog](https://keepachangelog.com/). This project
adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [0.0.2] - 2026-04-27

### Added

- doc/test alignment skill (`.claude/skills/doc-test-align/`) — drift detector for CLAUDE.md endpoint and verb claims
- bootstrap-sibling skill (`.claude/skills/bootstrap-sibling/`) — chains the four-step sibling bootstrap with dry-run-then-apply gates
- mutation-safety pytest module — asserts every mutating verb has --apply defaulting to False and performs no writes in dry-run
- Bootstrap walkthrough section in CLAUDE.md covering the four-step path from no-repo to Trusted-Publishing-ready

### Changed

- CLAUDE.md GitHub authentication: `repo` scope is sufficient for Environments (per GitHub REST docs); `admin:repo_hook` is no longer claimed required for v0.x verbs
- CLAUDE.md GitHub authentication: documented `GITHUB_TOKEN=$(gh auth token)` bridge for users with gh authenticated but no PAT exported

### Fixed

- Empirically incorrect scope claim for `ghafi repo env` — verified against agentculture/irc-lens bootstrap

## [0.0.1] - 2026-04-26

### Added
Expand Down
Loading
Loading