agentsclimarketplace

Sisters

Skill ajbarea/techne/plugins/techne/skills/sisters

Cross-repo drift audit across linked repos listed in `~/.claude/techne.toml` (configurable per-user). Read-only inspection of CI action pins, toolchain pins in pyproject.toml, skill-context structural parity, GitHub merge settings, open PRs, and branch hygiene. Triggers on phrases like "audit the sisters", "are the sisters in sync", "check cross-repo drift", or when multiple sister repos are mentioned together for a consistency check.From its SKILL.md

Install
npx -y skills add ajbarea/techne --skill sisters

Assembled from the repository path, not quoted from the project. Check it against their README if it does not work.

One thing to look at

  • 0 stars0 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.

SKILL.md

19.9 KB, ~5.5k tokens by cl100k_base, as published. Nobody here has run it

Sisters Audit

Audit the active sister repos for cross-repo drift. Report every finding, grouped by category. Leave fixing to follow-up work or the developer — this skill observes, it does not edit.

Config (load first)

Active sister list, workspace root, and GitHub user are read from ~/.claude/techne.toml at runtime. Run this preamble before any audit checks below — it sets $SISTERS, $WORKSPACE, and $GITHUB_USER:

eval "$(python3 - <<'PY'
import tomllib, os, shlex
with open(os.path.expanduser('~/.claude/techne.toml'), 'rb') as f:
    d = tomllib.load(f)
sisters = ' '.join(s['name'] for s in d['sisters'] if s.get('status', 'active') == 'active')
ws = d['workspace_root']  # required; fails loudly with KeyError on misconfig
gu = d['github_user']     # required; fails loudly with KeyError on misconfig
print(f"SISTERS={shlex.quote(sisters)}")
print(f"WORKSPACE={shlex.quote(ws)}")
print(f"GITHUB_USER={shlex.quote(gu)}")
PY
)"

If ~/.claude/techne.toml is missing or yields zero active sisters, stop and tell the user — don't guess. Each sister's canonical local path is $WORKSPACE/<name> and its GitHub slug is $GITHUB_USER/<name>.

What to check

1. Action-pin drift

For every .github/workflows/*.yml across the active sisters, extract uses: <actor>/<action>@<version> pins.

for repo in $SISTERS; do
  grep -hE '^\s*uses:' $WORKSPACE/$repo/.github/workflows/*.yml 2>/dev/null \
    | sed -E 's/^\s*uses:\s*//; s/\s*$//' \
    | awk -v r="$repo" '{print r "\t" $0}'
done | sort

Compute drift: any action (the actor/action part) pinned to different versions across repos. Report the action, each repo's pin, and which is newest. The newest pin in the set is the recommended target — unless the user has said otherwise.

Don't flag per-action when all repos pin the same version, even if that version is behind upstream — that's a separate "upgrade" question, not drift. The skill's job is to catch inconsistency, not to evaluate absolute freshness.

2. Skill-context structural parity

Each repo has .claude/skill-context.md. Required top-level sections across all sisters:

  • ## repo
  • ## audit
  • ## ci_audit
  • ## slop_ground_truth
  • ## scan_scope
  • ## docs_site

Verify by grep:

for repo in $SISTERS; do
  echo "--- $repo ---"
  grep -E '^## ' $WORKSPACE/$repo/.claude/skill-context.md
done

Report any repo missing a required section, and any repo that has sections the others lack (not necessarily bad — could be a legitimate per-repo addition — but worth surfacing for review).

3. GitHub merge-setting drift

Expected uniform settings (this is the canonical pin — if you're unsure, re-run this audit):

  • allow_squash_merge: true
  • allow_merge_commit: false
  • allow_rebase_merge: false
  • delete_branch_on_merge: true
  • allow_auto_merge: true — needed so gh pr merge --auto works; without it every PR has to be hand-merged after CI flips green
for repo in $SISTERS; do
  echo "--- $repo ---"
  gh api "repos/$GITHUB_USER/$repo" --jq '{sq:.allow_squash_merge, mc:.allow_merge_commit, rb:.allow_rebase_merge, del:.delete_branch_on_merge, am:.allow_auto_merge}'
done

Report any repo that drifts from the canonical expectation.

4. Open PRs rollup

for repo in $SISTERS; do
  gh pr list --repo "$GITHUB_USER/$repo" --state open --json number,title,headRefName,createdAt,isDraft,mergeable \
    --jq ".[] | [\"$repo\", (.number|tostring), .title, .headRefName, .mergeable, .createdAt] | @tsv"
done

For each PR, note age (createdAt → days-ago), mergeability, and whether the check rollup is clean. Flag any PR open longer than 14 days — that's a rot signal, not necessarily a bug.

Don't pull full check details for every PR — that's the job of techne:ci-audit. Link to the PR URL and let the user drill down.

5. Stale local branches

For each repo, list branches that are ahead of origin/main and not the currently checked-out branch or main itself:

for repo in $SISTERS; do
  echo "--- $repo ---"
  (cd $WORKSPACE/$repo && git fetch --quiet origin && \
    git for-each-ref --format='%(refname:short) %(upstream:track)' refs/heads/ \
    | grep -v '^main ' \
    | grep -v '\[gone\]' \
    | awk '$2 ~ /ahead/ { print $0 }')
done

Report each stale branch: repo, branch name, ahead-by count. Stale branches are work-in-progress that didn't ship — not automatically bad, but worth knowing about before a session.

6. Local main divergence from origin/main

Fast check: is any repo's local main out of sync with origin/main?

for repo in $SISTERS; do
  cd $WORKSPACE/$repo
  git fetch --quiet origin
  behind=$(git rev-list --count main..origin/main 2>/dev/null)
  ahead=$(git rev-list --count origin/main..main 2>/dev/null)
  echo "$repo: ahead=$ahead behind=$behind"
done

Report any non-zero ahead/behind. Behind = pull to catch up. Ahead = unpushed commits, investigate before continuing work.

7. Toolchain pin drift in pyproject.toml

Only inspect the root pyproject.toml of each repo — that's where the shared toolchain decisions live. Do not descend into workspace members (e.g., <repo>/agents/*/pyproject.toml); those are package-level, not toolchain-level, and would generate noise.

Extract four pins per repo:

  • requires-python — the Python version envelope the project accepts.
  • [tool.ruff] target-version — which Python features ruff assumes when linting / autofixing.
  • Dev-dep specifier for ruff (in [project.optional-dependencies.dev] or [dependency-groups.dev]).
  • Dev-dep specifier for ty.
  • Dev-dep specifier for pytest.
for repo in $SISTERS; do
  f=$WORKSPACE/$repo/pyproject.toml
  echo "--- $repo ---"
  grep -E '^requires-python\s*=' "$f"
  awk '/^\[tool\.ruff\]$/{flag=1; next} /^\[/{flag=0} flag && /^target-version/' "$f"
  grep -hE '"(ruff|ty|pytest)[">=<~!]' "$f" | sort -u
done

Compute drift the same way as check 1 (action pins):

  • requires-python drift — differing lower bounds or upper caps across the sisters is drift. A repo with >=3.9 while the others are >=3.12,<3.14 is running on a looser envelope than its siblings and may regress on features the others use freely.
  • target-version drift — this must be consistent with requires-python's lower bound. Flag both cross-repo drift and intra-repo mismatch (e.g., requires-python = ">=3.12" but target-version = "py39").
  • ruff / ty / pytest specifier drift — any tool with different minimums across repos (ruff>=0.8 vs ruff>=0.9) or that is unbounded in one repo ("ruff") while bounded in another ("ruff>=0.9") is drift.

Unlike action pins, the newest pin is not automatically the target. requires-python lower bounds often encode a support commitment that's deliberate per repo — raising a repo's lower bound can break users on older Python. Report the drift, then either:

  • Tooling pins (ruff / ty / pytest) → the newest pin is the default recommendation; these have no support-contract cost.
  • requires-python / target-version → surface the drift but do not recommend; ask the user which envelope they want to converge on.

8. Branch protection on main

Every sister should have branch protection on main with at least one required status check, no force-pushes, and no branch deletions. Sisters drifted past this once already (ldqis 2026-05-23, found by the audit and fixed inline). Don't require a specific number of contexts — that varies by repo's CI shape — just check that at least one is wired and that force-push/deletion are blocked.

for repo in $SISTERS; do
  raw=$(gh api "repos/$GITHUB_USER/$repo/branches/main/protection" 2>&1)
  if echo "$raw" | grep -q '"Branch not protected"'; then
    echo "$repo: main NOT protected -> enable protection with required CI checks"
    continue
  fi
  printf '%s' "$raw" | python3 -c '
import json, sys
d = json.load(sys.stdin)
ctx = (d.get("required_status_checks") or {}).get("contexts") or []
fp = (d.get("allow_force_pushes") or {}).get("enabled", False)
ad = (d.get("allow_deletions") or {}).get("enabled", False)
issues = []
if not ctx: issues.append("no required checks")
if fp: issues.append("force-pushes allowed")
if ad: issues.append("deletions allowed")
status = "drift: " + ", ".join(issues) if issues else f"protected, {len(ctx)} required check(s) ✓"
print(f"'"$repo"': {status}")
'
done

Report any sister whose main is unprotected, has zero required checks, or has allow_force_pushes / allow_deletions set. enforce_admins stays optional — AJ intentionally leaves admin override on so audit fixes can ship the same session.

9. Codecov config presence + bot-comment silencing

Any sister whose CI workflows invoke codecov/codecov-action should also carry a codecov.yml (or .codecov.yml) at the repo root with comment: false. Without the config, the bot posts an inline PR comment on every push — pure noise once the patch-coverage status check is visible. Sisters drifted past this once already (ldqis 2026-05-23).

for repo in $SISTERS; do
  uses_codecov=0
  if grep -rq 'codecov/codecov-action' $WORKSPACE/$repo/.github/workflows/ 2>/dev/null; then
    uses_codecov=1
  fi
  [ $uses_codecov -eq 0 ] && { echo "$repo: no codecov-action — skip"; continue; }
  cfg=""
  for candidate in codecov.yml .codecov.yml; do
    [ -f "$WORKSPACE/$repo/$candidate" ] && { cfg=$candidate; break; }
  done
  if [ -z "$cfg" ]; then
    echo "$repo: uses codecov-action but has no codecov.yml -> add one (sister convention: comment: false + target auto/2% + informational)"
  elif ! grep -q '^comment:[[:space:]]*false' $WORKSPACE/$repo/$cfg; then
    echo "$repo: $cfg present but missing 'comment: false' -> bot comments will spam PRs"
  else
    echo "$repo: codecov-action + $cfg with comment: false ✓"
  fi
done

Report any sister that uses codecov/codecov-action but is missing codecov.yml, or has the config but doesn't set comment: false. Sisters not using codecov-action are silently skipped — this is a conditional check.

10. make clean log-retention policy

Every sister with a logs/ directory should implement age-based pruning of dev-*-*.log archives instead of wiping logs/ wholesale (or doing nothing). Canonical threshold: 30 days (standard hot-tier log retention). The *-latest.log active handle must be preserved.

Detection covers both implementation shapes:

  • Python-script-delegated clean (phalanx-fl, vFL, kourai-khryseai): LOG_ARCHIVE_MAX_AGE_DAYS = N constant in scripts/clean_build.py or scripts/dev.py.
  • Makefile-inline clean (ldqis): find logs … -mtime +N -delete recipe line.
for repo in $SISTERS; do
  [ -d "$WORKSPACE/$repo/logs" ] || { echo "$repo: no logs/ dir — skip"; continue; }
  py_thr=$(grep -hE 'LOG_ARCHIVE_MAX_AGE_DAYS[[:space:]]*=[[:space:]]*[0-9]+' \
             $WORKSPACE/$repo/scripts/*.py 2>/dev/null \
           | grep -oE '[0-9]+' | head -1)
  mf_thr=$(grep -hE '\-mtime[[:space:]]+\+[0-9]+' \
             "$WORKSPACE/$repo/Makefile" 2>/dev/null \
           | grep -oE '\+[0-9]+' | tr -d '+' | head -1)
  threshold="${py_thr:-$mf_thr}"
  if [ -z "$threshold" ]; then
    echo "$repo: logs/ present, no age-based prune -> add 30-day prune to clean"
  else
    echo "$repo: ${threshold}-day age-based log prune ✓"
  fi
done

Report:

  • Any sister with logs/ but no age-based prune (wholesale-wipe or no-op is drift).
  • Any sister whose threshold differs from 30 days (the canonical pin) — flag the value and ask whether to converge.
  • Sisters with no logs/ dir are silently skipped — this is a conditional check.

11. Dependabot config coverage

Every sister should carry .github/dependabot.yml covering each dependency surface it actually ships. The canonical config + per-ecosystem guidance live in templates/dependabot.yml.example. This check maps detected manifests to expected package-ecosystem entries and flags gaps — except where the config documents a deliberate deferral.

Scope mirrors check 7: inspect primary manifests only. Skip templates/ (scaffolding emitted to generated projects) and tools/ (dev experiments) — those aren't the repo's shipped dependency surface and only generate noise. uv is the canonical Python ecosystem (GA 2025-03-13); pip still provides coverage but should be migrated. Docker maps from a literal Dockerfile only — Dependabot doesn't auto-detect *.Dockerfile custom names (dependabot/feedback#145), so a repo using that convention legitimately omits docker.

for repo in $SISTERS; do
  cfg="$WORKSPACE/$repo/.github/dependabot.yml"
  [ -f "$cfg" ] || cfg="$WORKSPACE/$repo/.github/dependabot.yaml"
  if [ ! -f "$cfg" ]; then
    echo "$repo: NO .github/dependabot.yml -> add one (template: templates/dependabot.yml.example)"
    continue
  fi
  declared=$(grep -oE "package-ecosystem:[[:space:]]*['\"]?[a-z-]+" "$cfg" \
             | grep -oE '[a-z-]+$' | sort -u | tr '\n' ' ')
  manifests=$(git -C "$WORKSPACE/$repo" ls-files 2>/dev/null | grep -vE '(^|/)(templates|tools)/')
  expected=""
  echo "$manifests" | grep -qE '(^|/)(pyproject\.toml|uv\.lock)$' && expected="$expected uv"
  echo "$manifests" | grep -qE '(^|/)package\.json$'            && expected="$expected npm"
  echo "$manifests" | grep -qE '(^|/)Cargo\.toml$'              && expected="$expected cargo"
  echo "$manifests" | grep -qE '(^|/)Dockerfile$'               && expected="$expected docker"
  expected="$expected github-actions"
  missing=""
  for eco in $expected; do
    if [ "$eco" = uv ]; then
      echo " $declared " | grep -qE ' (uv|pip) ' && continue
    else
      echo " $declared " | grep -q " $eco " && continue
    fi
    # documented deferral: an in-config comment naming the ecosystem + defer/skip
    grep -iE '^[[:space:]]*#' "$cfg" | grep -i "$eco" | grep -qiE 'defer|skip' && continue
    missing="$missing $eco"
  done
  if [ -n "$missing" ]; then
    echo "$repo: missing ecosystem(s) for present manifests:$missing | declared: $declared"
  else
    echo "$repo: coverage OK | declared: $declared"
  fi
done

Report:

  • Any sister with no .github/dependabot.yml.
  • Any sister with a shipped manifest (pyproject.toml/uv.lock, package.json, Cargo.toml, literal Dockerfile) but no matching package-ecosystem entry and no documented deferral.
  • Any sister still on the pip ecosystem — flag for migration to native uv.
  • Deferrals (e.g., kourai's uv pending dependabot-core#14004) are recognized via an in-config comment naming the ecosystem + "defer"/"skip", so they don't false-positive.

12. README header convention

Solo sisters that ship a hero asset (docs/assets/*hero* or *banner*) should lead their README with the centered masthead: a <div align="center"> wrapping, in order, the hero image, the # Title, a one-line italic tagline, then the badge rows — Hero → Title → tagline → Badges. Repos with no hero asset are exempt; kind = "team" repos and deliberately product/template-flavored tops (CTA-led, no shields badges, e.g. a fork-template PWA) are legitimate exceptions to flag, not auto-fail.

for repo in $SISTERS; do
  rdme="$WORKSPACE/$repo/README.md"
  [ -f "$rdme" ] || { echo "$repo: no README — skip"; continue; }
  hero=$(ls "$WORKSPACE/$repo/docs/assets/" 2>/dev/null | grep -iE '(hero|banner)' | head -1)
  [ -z "$hero" ] && { echo "$repo: no hero asset — convention N/A"; continue; }
  hdr=$(head -25 "$rdme")
  div=$(printf '%s\n' "$hdr" | grep -c '<div align="center">')
  img_ln=$(printf '%s\n' "$hdr" | grep -nE '<img[^>]*docs/assets/|!\[.*\]\(docs/assets/' | head -1 | cut -d: -f1)
  h1_ln=$(printf '%s\n' "$hdr" | grep -nE '^# ' | head -1 | cut -d: -f1)
  if [ "$div" -ge 1 ] && [ -n "$img_ln" ] && [ -n "$h1_ln" ] && [ "$img_ln" -lt "$h1_ln" ]; then
    echo "$repo: centered masthead, hero before title ✓"
  else
    echo "$repo: hero asset ($hero) present but header off-convention (div:$div img:${img_ln:-none} h1:${h1_ln:-none}) → align to Hero→Title→tagline→Badges, or confirm intentional"
  fi
done

Report:

  • Any solo sister with a hero asset whose README does not lead with the centered masthead (hero image before the # Title, inside <div align="center">).
  • Repos with no hero asset, kind = "team", or an intentional product/template top are exempt — flag as a question, never an auto-fail.

Output format

A single block, no preamble (concrete repo names below are illustrative — substitute the actual entries from $SISTERS):

## Sisters audit — <UTC timestamp>

### Merge settings
- repo-a: squash-only, delete-on-merge, auto-merge ✓
- repo-b: ...
- repo-c: auto-merge disabled → `gh api -X PATCH repos/$GITHUB_USER/repo-c -f allow_auto_merge=true`

### Skill-context parity
- All sisters have required sections. ✓
  (or list drift: "repo-a missing `## docs_site`")

### Action-pin drift
- `astral-sh/setup-uv`: [email protected], [email protected], [email protected] → bump repo-c
- (else: "No drift — all pins consistent across repos.")

### Toolchain pin drift (`pyproject.toml`)
- `ruff`: repo-a unbounded, repo-b `>=0.8`, repo-c `>=0.9` → bump repo-a + repo-b to `>=0.9`
- `requires-python`: repo-a `>=3.11,<3.14`, repo-b `>=3.9`, repo-c `>=3.12,<3.14` → surfaced for user (support-contract drift, no automatic target)
- (else: "No drift — all toolchain pins consistent.")

### Open PRs
- repo-a: 0 open
- repo-b: 1 open (#12, 3d old, mergeable)
- repo-c: 2 open (#14 mergeable; #15 has failing checks → run /techne:ci-audit)

### Stale branches
- repo-c: `feat/experiment-xyz` (ahead 3)
- (else: "Clean.")

### Local main sync
- All sisters: ahead=0 behind=0. ✓

### README header convention
- repo-a: centered masthead, hero before title ✓
- repo-b: no hero asset — N/A
- repo-c: hero asset present but header off-convention → align (or confirm intentional)

### Branch protection
- All sisters: protected with required checks, no force-push, no deletions ✓
  (or list drift: "repo-c: main NOT protected → enable protection")

### Codecov config
- repo-a: codecov-action + codecov.yml with comment: false ✓
- repo-b: no codecov-action — skip
- repo-c: uses codecov-action but missing codecov.yml → add one (sister convention)

### Clean log-retention policy
- repo-a: 30-day age-based log prune ✓
- repo-b: no logs/ dir — skip
- repo-c: logs/ present, no age-based prune → add 30-day prune to clean

### Dependabot coverage
- All sisters: dependabot.yml covers every shipped manifest (deferrals documented) ✓
  (or list drift: "repo-c: has package.json but no npm ecosystem → add it")

### Verdict

<"N drift items to address." | "All sisters coherent.">

Rules

  • Read-only. Never edit files, push branches, or modify GitHub settings. If the audit surfaces something that needs fixing, say so and stop.
  • If one repo is in a broken state (e.g., .claude/skill-context.md missing), report it and continue the other checks — don't abort.
  • gh calls go through the user's authenticated CLI; if auth fails, surface the error and stop that check (don't retry).
  • Do not invoke techne:ci-audit recursively. If a PR has failing checks, name it and tell the user to run techne:ci-audit separately.
  • The active sister list comes from ~/.claude/techne.toml. If the file is missing, malformed, or yields zero active sisters, stop and ask — don't invent paths.

What ships with it

Read from the repository

Just SKILL.md. No reference files, no scripts.

Keep looking

Skills are one crate of 326,851. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.