Files
standards/portability.md
Keysat 828fc99dd4 Adopt deny-by-default .claude gitignore; record git-hygiene audit
The cross-repo git-hygiene audit (ROADMAP item 6) found the documented canonical .claude/ block was allow-by-default and would have un-ignored a password-bearing .claude/launch.json. Switch portability.md to a deny-by-default .claude/* + allow-list block and align the two retrofit summaries. Mark item 6 done with residuals; refresh Current state.
2026-06-14 12:19:48 -05:00

11 KiB

Portability Protocol

Purpose: keep every layer of my agent setup hot-swappable across model providers. Any compliant tool dropped into one of my repos should be productive immediately; adopting a new tool should cost minutes; returning to a previous tool should cost nothing.

Lives at: ~/Projects/standards/portability.md. The standards repo is laid out as:

~/Projects/standards/
  AGENTS.md  ← CLAUDE.md (relative symlink)   ← agent-facing orientation to this repo
  README.md   how-i-work.md   portability.md   retrofit-playbook.md   subagents-handbook.md
  ROADMAP.md               ← this repo's backlog (future agents, commands, standards)
  INBOX.md                 ← cross-project capture buffer (/capture → here, /triage drains it)
  guides/                  ← neutral substance (vendor-agnostic): checklists, role knowledge
  adapters/
    claude/
      commands/            ← ~/.claude/commands symlinks here (global commands, e.g. /handoff)
      agents/              ← ~/.claude/agents symlinks here (global subagents)
      statusline.sh        ← ~/.claude/statusline.sh symlinks here (Claude Code terminal status line)

Companions: how-i-work.md (the always-loaded user layer, ~50 lines), retrofit-playbook.md (the one-time conversion manual), and subagents-handbook.md (designing and running delegated agents). This document is reference material — never symlink it into anything always-loaded.

The principle

Knowledge lives in vendor-neutral files; vendor-named paths are thin adapters — symlinks or pointers — into them.

Corollaries:

  1. Repos are self-contained. Everything required to work on a project correctly lives in the repo. Global and user-level files add preferences, never requirements.
  2. Swap at session boundaries. The close-out ritual (update Current state → commit → push) is the handoff protocol between providers, not just between sessions.
  3. Convert machinery, never knowledge. At adoption time, an agent regenerates wrappers (subagents, commands, rule loaders). The substance they point at never moves. Every command and subagent is a thin wrapper in adapters/<tool>/ whose substance lives in guides/ — no inline exceptions, so any wrapper converts to a new tool by swapping its header.
  4. Session state is disposable by design. Conversations and per-tool caches (e.g. Claude's auto memory) are conveniences. Anything important gets promoted into the files below.
  5. No secrets in any of these files. Secrets live in gitignored .env files; documents reference env-var names only.

The layers

Layer Neutral source of truth Claude adapter Portability status
Project knowledge <repo>/AGENTS.md symlink CLAUDE.mdAGENTS.md Open standard — Cursor, Copilot, Codex, Gemini CLI, opencode read it
Project status (now) ## Current state section in AGENTS.md (same symlink) Fully portable; maintained by the close-out ritual
Project backlog (later) ROADMAP.md at repo root — (plain committed file, no symlink) Fully portable; committed and pushed like any file, read only on demand
Scoped guides <repo>/docs/guides/<topic>.md with paths: frontmatter, plus one index line each in AGENTS.md symlinks from .claude/rules/ (auto lazy-load) Content fully portable; lazy-loading is per-tool. Other tools find guides via the index lines
User preferences ~/Projects/standards/how-i-work.md symlink ~/.claude/CLAUDE.md → it No cross-tool standard above repo level; each tool's global file symlinks here at adoption
Skills (procedures) folders of SKILL.md + plain bash/python scripts .claude/skills/ Format is open markdown; a cross-tool home (.agents/skills/) is emerging. Worst case: a pointer line in AGENTS.md
Subagents (delegation) guides folder at matching scope — <repo>/docs/guides/ for project agents, standards/guides/ for global thin wrapper in .claude/agents/ (project) or standards/adapters/claude/agents/ (global, symlinked as ~/.claude/agents) No standard exists. Wrappers regenerated per tool at adoption
Commands (saved prompt shortcuts) substance in guides/ at matching scope, same rule as subagents thin wrapper in .claude/commands/ (project) or standards/adapters/claude/commands/ (global, symlinked as ~/.claude/commands) No standard. Same treatment as subagents

Scope rule: every artifact lives at the scope it describes — project-specific in that repo; universal in the standards repo. There, neutral substance goes in guides/ and vendor machinery is quarantined under adapters/<tool>/ (so a second provider's wrappers never intermix with Claude's or with the shared guides).

What git tracks

Symlinks and .claude/ raise a recurring question: what belongs in the repo, what stays local, and how does the global ~/.claude layer get backed up? One rule resolves all three — git tracks knowledge and the wiring that serves it; it ignores machine-local state and secrets — but it lands in three places.

1. Relative symlinks are committed and travel. Git stores a symlink as its target path. Because every vendor path in this standard is a relative symlink (CLAUDE.md → AGENTS.md, .claude/rules/x.md → ../../docs/guides/x.md), it commits and clones correctly on any machine. This is the concrete payoff of the relative-symlink mandate: an absolute symlink would commit too, but break on every other clone. Never commit an absolute symlink. This mandate binds in-repo symlinks specifically — the ones git commits and clones. The global ~/.claude/* symlinks (point 3) are never committed, so the rule doesn't reach them: they're per-machine glue, recreated on each machine by the adoption step, and may be absolute without harm. A repo audit checks only the symlinks inside the repo.

2. Inside a project repo — commit shared, ignore local.

Committed (any agent or teammate needs them to work the repo correctly — corollary 1): AGENTS.md and its CLAUDE.md symlink; docs/guides/*.md and their .claude/rules/*.md symlinks; ROADMAP.md; .claude/settings.json (shared project settings and hooks — deterministic behavior is part of the repo); .claude/agents/*.md, .claude/commands/*.md, .claude/skills/ (project-scoped wrappers).

Gitignored (per-user, per-machine, secret, or session scratch): .claude/settings.local.json and any *.local.* (personal permissions/overrides); .claude/worktrees/ and other Claude session/editor scratch that lands in .claude/; .env and secrets (corollary 5); OS cruft.

Because .claude/ accumulates unpredictable scratch — worktrees, editor debug configs (sometimes carrying credentials), .DS_Storeignore it deny-by-default and allow-list only the shared wiring. A blanket .claude/* plus ! exceptions is safer than naming individual local files: a new kind of local scratch is ignored automatically, and a stray secret never slips in by default. (Already-tracked files stay tracked even under .claude/*, so a deliberate, secret-free editor config a repo wants to commit can simply be allow-listed with its own ! line.)

Put these in the repo's own committed .gitignore — don't rely on a global excludesfile, which a fresh clone or another machine won't have. Canonical block:

# Secrets & local env
.env
.env.*
!.env.example

# Claude Code — deny by default, allow-list shared wiring.
# .claude/ also accumulates worktrees, editor configs, and OS cruft; commit
# only the shared parts so new local scratch (or a stray secret) stays out.
.claude/*
!.claude/rules/
!.claude/agents/
!.claude/commands/
!.claude/skills/
!.claude/settings.json

# OS cruft
.DS_Store

3. The global ~/.claude layer is backed up through the standards repo, not on its own. ~/.claude is not a git repo. Its durable, portable parts — commands, agents, CLAUDE.md — are symlinks into this standards repo, so committing and pushing standards backs them up. Everything else under ~/.claude (the machine-local settings.json, projects/ session transcripts and auto-memory, history, todos, caches) is disposable session state by design (corollary 4) and is neither committed nor backed up. What makes this safe: promote anything durable out of auto-memory into a committed file (AGENTS.md or a guide); audit with /memory.

So the answer to "are we backing up all of .claude?" is no, by design: knowledge and shared wiring are committed per-repo or symlinked into a backed-up repo; machine-local state and secrets never are.

Swap protocol (between already-adopted tools)

  1. Finish the task and run the close-out ritual; exit the session.
  2. Open the other tool in the same repo.
  3. It reads AGENTS.md (plus its own global file) and Current state, and continues.

Nothing else. If step 3 stumbles, the missing fact belongs in AGENTS.md — add it and commit.

Adoption checklist (first time using a new provider)

Run by the new agent itself: "Read ~/Projects/standards/portability.md and onboard yourself as a new provider."

  1. Install and authenticate the tool — the only human-heavy step.
  2. Symlink (or copy) its global instruction file to standards/how-i-work.md.
  3. Confirm it reads <repo>/AGENTS.md. If it expects a different filename, symlink that name to AGENTS.md.
  4. If it supports path-scoped or lazy loading, wire its mechanism to docs/guides/; otherwise the AGENTS.md index lines suffice.
  5. If it supports skills or procedures, point it at the skills folders; otherwise add a pointer line in its global file.
  6. Regenerate the thin wrappers it supports (subagents, commands) from the guides they reference, placing global ones in adapters/<tool>/ and symlinking the tool's config home to them.
  7. Record what was done in a new provider section at the bottom of this document.

Provider adapters

Claude Code (current)

  • CLAUDE.md → symlink → AGENTS.md at the root of every repo
  • .claude/rules/<topic>.md → symlinks → docs/guides/<topic>.md (auto lazy-load via paths: frontmatter)
  • ~/.claude/CLAUDE.md → symlink → standards/how-i-work.md
  • .claude/agents/ — thin subagent wrappers pointing at docs/guides (project-specific agents only)
  • .claude/commands/ — thin command templates, regenerable (project-specific commands only)
  • Global commands and agents: ~/.claude/commands → symlink → standards/adapters/claude/commands/; ~/.claude/agents → symlink → standards/adapters/claude/agents/ (versioned and backed up with the standards repo)
  • .claude/skills/ — skills home for now; migrate to .agents/skills/ if that convention solidifies
  • Auto memory: local cache only (~/.claude/projects/<project>/memory/), outside git, not portable. Promote keepers into AGENTS.md or guides. Audit with /memory.

Next provider — template

  • Global file location: ___ → symlink → standards/how-i-work.md
  • Reads AGENTS.md natively? ___ (if not: symlink its expected filename → AGENTS.md)
  • Scoped/lazy loading mechanism: ___ (wired to docs/guides, or relying on index lines)
  • Skills support: ___
  • Delegation/subagent equivalent: ___ (wrappers regenerated on: ___)
  • Notes and quirks: ___