ββββββββββββββ βββββββ ββββββββββββββββ ββββββ βββ
βββββββββββββββββββββββββββββββββββββββββ βββββββββββ
βββ βββββββββββ ββββββββββββββββββββββββββββββββββββ
βββ βββββββββββ ββββββββββββββββββββββββββββββββββββ
βββββββββββ ββββββββββββββββββββββββββββ βββ ββββββ
ββββββββββ βββ βββββββ ββββββββββββββββ βββ ββββββ
One operating contract for every AI coding tool β from a single source of truth.
A drop-in scaffold that gives every AI coding tool in your repo the same operating contract, lifecycle, skills, personas, and commands β from one source of truth.
You edit .ai/. A single generator (scripts/sync-ai-docs.sh β dependency-free POSIX shell,
no node/bun/python) materializes the tool-specific configs for Claude Code, Cursor, Codex,
Gemini CLI, and opencode. No more hand-maintaining CLAUDE.md, .cursor/rules, and a Gemini
config separately and watching them drift. Drop it into a project in any language β the
machinery needs nothing but a shell.
One edit to .ai/context.md β sh scripts/sync-ai-docs.sh β the same rule lands in every
tool's config: inlined into AGENTS.md/.cursor (Codex, Cursor, GitHub web) and @-imported
by the CLAUDE.md/GEMINI.md stubs. Then the pre-commit hook re-runs the sync and blocks a
contract edit that wasn't re-synced.
.ai/ β the ONLY files you hand-edit (source of truth)
ββ context.md project contract (stack, Definition of Done, hard rules)
ββ pipeline.md the generic specβplanβbuildβtestβreview lifecycle
ββ commands/*.md slash-command definitions
ββ agents/*.md reviewer personas (code / security / performance / test)
ββ skills/*/SKILL.md reusable workflow skills (TDD, code review, CI/CD, β¦)
ββ references/*.md checklists the skills/personas cite
ββ specs/*.md project specs (00β99) + per-task specs (A1, A2, β¦ or external ids)
ββ memory.example.md seed for the local, gitignored working log
β sh scripts/sync-ai-docs.sh (POSIX shell, no runtime deps β deterministic)
βΌ
AGENTS.md Β· CLAUDE.md Β· GEMINI.md Β· .ai/generated/rules.mdc β contract entry files
.claude/ Β· .gemini/ (TOML) Β· .opencode/ Β· .cursor/ (symlink) β per-tool mirrors
| Tool | Reads | Produced as |
|---|---|---|
| Claude Code | CLAUDE.md + .claude/{commands,agents,skills} |
@-import stub + copied assets |
| Cursor | .cursor/rules/00-context.mdc |
symlink β .ai/generated/rules.mdc |
| Codex | AGENTS.md + .codex/config.toml |
inlined contract + MCP config |
| Gemini CLI | GEMINI.md + .gemini/{commands(TOML),agents,skills} |
@-import stub + transformed assets |
| opencode | .ai/* directly + .opencode/{commands,agents} |
reads source + copied assets |
Never edit a generated file β your edit is overwritten on the next sync, and the
pre-commit hook blocks committing a stale one. Change a convention in .ai/, run
sh scripts/sync-ai-docs.sh, commit.
- Copy
.ai/,scripts/,.gitignore,.githooks/, and the per-tool dirs (.claude/ .gemini/ .opencode/ .cursor/ .codex/ .mcp.json) into your repo β or start your repo from this template. Nopackage.jsonneeded β the machinery is pure shell. - Install the hooks and run the first sync (one command, no runtime to install):
sh scripts/install.sh # git config core.hooksPath .githooks + chmod + first sync - Fill the contract β two ways:
- Fastest: open the repo in your AI tool and run
/bootstrapβ it interviews you, fills.ai/context.md(and can seed the specs), wires your language's pre-commit / post-commit hooks, and runs the sync for you. - By hand: edit
.ai/context.md(your Project, locked Stack, Definition of Done, hard rules β look for the<!-- FILL: β¦ -->markers), and drop your language's checks into.githooks/pre-commit.d/and.githooks/post-commit.d/(see those dirs' READMEs).
- Fastest: open the repo in your AI tool and run
- Regenerate (skip if you ran
/bootstraporinstall.shβ they already synced):sh scripts/sync-ai-docs.sh
- Open the repo in any supported tool β it now follows the same contract everywhere.
.ai/specs/ ships as empty skeletons β the section headings are the required format.
Project-level docs use the numeric scheme (00-requirements.md β 01-spec.md β 02-plan.md β
99-acceptance.md); per-task specs use a letter+number id (see below). Populate them either way:
- By hand β edit
00-requirements.mdβ01-spec.md, then add per-task specs as you go. - With the agent (recommended) β this template already includes the spec generator. Run the
lifecycle commands and let the agent write the files:
Optional:
/spec # β fixes the task id + writes .ai/specs/<id>-spec.md (or cites the ticket) /plan # β records the task's steps in .ai/specs/02-plan.md (the backlog index)/excalidraw-specrenders the architecture diagram via the excalidraw MCP server.
You don't need an external spec tool β /spec + the spec-driven-development skill are the
"open-spec" workflow, built in.
The lifecycle runs per task, and each task needs an id and a home for its spec. The rule:
- If the repo uses an external backlog β Linear or GitHub Projects β that tracker is the source
of truth. The task id is the external one (
ENG-421,#123), the ticket body is the spec, and no local spec file is created (it would only duplicate and drift from the ticket). - If the repo has no external backlog, the spec is saved in the repo as
.ai/specs/<id>-spec.mdwith a letter+number id βA1-spec.md,A2-spec.md,B1-spec.md, β¦ (letter = feature/epic, number = task) β from the_task-spec.template.mdskeleton.02-plan.mdis the local backlog/index of every task id.
/spec detects the source (vendor-neutral, so every CLI behaves the same): GitHub Projects
via gh project list; Linear via a configured Linear MCP server / LINEAR_API_KEY / eng-123
branch keys. Full rule: .ai/context.md β "Task identity & spec source".
LLMs tend to claim a task is "done" even when a step was skipped, stubbed, or never run. The
contract forbids that: every task closes with an Honest Implementation Report (emitted by
/build, rolled up by /acceptance). Each acceptance criterion gets a status β
β
verified Β· verified β
Γ· total = NN%, plus explicit
Unverified and Could-not-do lists. Over-claiming is a contract violation; under-claiming is
fine. Because it lives in the inlined contract, every tool (Claude Code, Codex, Cursor, Gemini,
opencode) enforces it. Full rule: .ai/context.md β "Honesty protocol".
Per task: /spec β /plan β /build β /test β /review (one task = one vertical slice; /build
closes it with an Honest Implementation Report).
Once, at the end: /consensus-review β /code-simplify β /ship β /acceptance β /goal.
Optional, on a cadence: /evolve β re-sync the .ai/ contract to the code (all CLIs;
/graphify accelerates it on Claude Code).
Full definition: .ai/pipeline.md.
The lifecycle stages are just commands, so you can run each stage on the best-fit model β cheap/fast for mechanical work, frontier for reasoning β to optimize cost and tokens. This is opt-in and applies to opencode (the model-agnostic CLI). Single-vendor tools optimize within their own ladder instead (Claude Code routes HaikuβSonnetβOpus) and ignore the key, so the same sources stay valid everywhere.
Add an optional model: key to any command or persona's frontmatter β opencode honors it on both
.opencode/commands/ and .opencode/agents/:
---
description: Implement the next task incrementally β build, test, verify, commit
model: openrouter/anthropic/claude-sonnet-4 # capable model for implementation
---A sensible cheapβfrontier map across the lifecycle (tune to taste; pull current model ids from models.dev):
| Stage | Tier | Why |
|---|---|---|
/spec, /review, /consensus-review |
frontier | contract design, subtle-bug catching |
/build |
capable | implementation correctness |
/plan, /test, /code-simplify |
fast/cheap | decomposition, red-green, mechanical edits |
/ship |
cheap | mechanical commits |
One bill, every vendor. Point opencode at OpenRouter so adopting a
newly released model is a one-line change, not a new subscription. In opencode.json:
{
"$schema": "https://opencode.ai/config.json",
"provider": {
"openrouter": { "options": { "apiKey": "{env:OPENROUTER_API_KEY}" } }
},
"model": "openrouter/anthropic/claude-sonnet-4"
}This repo automates that routing. Per-stage picks live in
scripts/opencode-model-routing.sh β a single
DeepSWE-grounded map (stage β OpenRouter model id). sh scripts/sync-ai-docs.sh stamps the
model: key into the .opencode/ copies only; the .claude/ (Anthropic), .codex/
(OpenAI), and .gemini/ (Google) mirrors never receive a foreign id, and Gemini's transform
drops the key anyway. The map is the only file you edit to re-route; if it's absent, sync falls
back to a plain copy and the template stays vendor-neutral. Current spread:
spec / review / consensus-review + security-auditor β gpt-5.5 (frontier);
build / acceptance / evolve / bootstrap + the other reviewers β gpt-5.4 (value);
plan / test / code-simplify / ship / excalidraw-spec β gpt-5.4-mini (cheap).
Keep .ai/ vendor-neutral. Don't hardcode a provider's model id into a committed template
command β that would betray the agnostic point. Per-stage routing is something a downstream
project opts into for its own opencode runtime; the template just documents the convention.
Automatic routers (OpenRouter's openrouter/auto, etc.) exist but optimize for quality, not cost
β a declared per-stage map is the predictable choice.
- Commands (
.ai/commands/):bootstrap(one-time setup),spec,plan,build,test,review,consensus-review,code-simplify,ship,acceptance,excalidraw-spec, andevolve(contract drift detection). - Personas (
.ai/agents/):code-reviewer,security-auditor,performance-reviewer,test-engineer. See.ai/agents/README.md. - Skills (
.ai/skills/): ~25 stack-agnostic workflows β TDD, code review, CI/CD, incremental implementation, security hardening, debugging, API design, plusevolve(graphify-backed contract-vs-code drift) and more. - Parallel fan-out (no committed scripts): the
/consensus-review2-of-3 panel, file-disjoint parallel slices, and/evolve's per-dimension scan all run on each tool's native parallelism β an Agent Team in Claude Code, sub-agents in Codex / Gemini / opencode. See the "Parallel work" section of.ai/pipeline.md. Keeping this language-/tool-agnostic (rather than shipping tool-specific scripts) is deliberate. - References (
.ai/references/): checklists the personas/skills cite.
.ai/memory.md is a local, gitignored per-developer working log, seeded from
.ai/memory.example.md on first sync. Terse symptom β root cause β fix entries. Durable,
team-facing decisions go in commit messages or docs/adr/, not here. Never write secrets.
.mcp.json ships one server: excalidraw (used by /excalidraw-spec). Add your own
(database, search, etc.) there; .codex/config.toml mirrors MCP config for Codex.
sh scripts/sync-ai-docs.shβ regenerate everything from.ai/(no runtime deps).sh scripts/sync-ai-docs.sh && git diff --exit-codeβ regenerate and fail if anything changed (use in CI)..githooks/pre-commitruns the sync gate automatically oncegit config core.hooksPath .githooksis set (done bysh scripts/install.sh), then runs any language checks you (or/bootstrap) put in.githooks/pre-commit.d/.
When this template improves, refresh a repo you created from it with one command β run from inside that repo:
sh scripts/update-from-template.sh # if your repo already has the scriptFirst time, in a repo that predates the updater? Older clones don't ship the script yet β bootstrap it once (either way, run from the repo root) and it self-installs for next time:
# A. git only, no extra tools (recommended β git is already required)
git fetch https://github.com/ogarciarevett/cross-ai-template.git main
git checkout FETCH_HEAD -- scripts/update-from-template.sh
sh scripts/update-from-template.sh
# B. one-liner, nothing to check out (append `-s -- --dry-run` to preview first)
curl -fsSL https://raw.githubusercontent.com/ogarciarevett/cross-ai-template/main/scripts/update-from-template.sh | shBoth end the same way: the script clones the template, refreshes the generic half, and copies
itself into scripts/ β so every later update is just sh scripts/update-from-template.sh.
Prefer a packaged CLI over the shell one-liner? The updater is also published as
cross-ai-cli(npm Β· crates.io Β· PyPI) from the companion repo cross-ai β an optional on-ramp. This template itself ships no package files and needs no runtime.
It works because the template has a single source of truth: only .ai/context.md is
project-specific. The updater refreshes the generic half β .ai/pipeline.md, commands,
agents, skills, references, .ai/templates/, the sync machinery, and the dispatcher hooks β
then re-runs the sync. It preserves your project files (.ai/context.md, your .ai/specs/,
scripts/opencode-model-routing.sh, your .githooks/*.d/ language hooks, .gitignore,
.gitattributes) and saves the template's new contract as .ai/context.md.incoming for you to
reconcile. It never deletes downstream-only commands (they're reported, not removed) and never
pushes.
Useful flags: --dry-run (preview), --ref <branch|tag> (pin a version), --with-tooling
(also refresh .mcp.json / opencode.json / .codex/ / .agents/, off by default), --force
(allow a dirty tree). The one fuzzy step β merging new conventions into a customized
.ai/context.md β is best handed to the /update-from-template slash command, which does
that judgment merge in any of the supported CLIs.
The template's hooks are agnostic dispatchers: pre-commit runs the sync drift gate then
every executable in .githooks/pre-commit.d/; post-commit runs every executable in
.githooks/post-commit.d/. The template ships those .d/ dirs empty. Run /bootstrap
and it writes your language's checks into them β gofmt/go vet/go test, prettier/eslint/
tsc/npm test, ruff/black/mypy/pytest, cargo fmt/clippy/test, etc. β using the
exact Definition-of-Done commands from your contract. Nothing language-specific ever touches the
dispatcher hooks or the sync script, so the core stays drop-in for any stack.
