dotbabel

Quickstart

Last updated: v3.4.0

dotbabel is a verification layer for agentic development: skills that make an agent ground its claims in real source, and gates that check the result.

Two paths — pick yours:

I want… Path
Skills & commands in every Claude Code session Dotfile bootstrap — 30 seconds, no npm required
The CLI in my own repo (verification gates, local attestation, doctor) This page — 10 minutes, Node ≥ 20 required

CLI consumer — install to first green validator in under 10 minutes

1. Install

cd your-project
npm install --save-dev @dotbabel/dotbabel

The package has zero runtime dependencies. It registers seven bins under node_modules/.bin/:

harness
dotbabel-doctor
dotbabel-detect-drift
dotbabel-init
dotbabel-validate-specs
dotbabel-validate-skills
dotbabel-check-spec-coverage
dotbabel-check-instruction-drift

2. Scaffold the governance tree

npx dotbabel-init --project-name your-project --project-type node

This writes:

Every placeholder (,, ``) is substituted at scaffold time.

3. Run the self-diagnostic

npx dotbabel-doctor

You should see ✓ rows for env, repo, facts, manifest, specs, drift, and hook. The first run may warn about missing artifacts (e.g. docs/specs/ empty) — that’s expected until you draft your first spec.

A final row reports check-on-stop trust. On a fresh repo it reads “no trust allowlist”, which is informational and never fails the run — turn-end project checks are simply off until you opt in. See hooks.md.

4. Your first spec

Use the /spec skill (if you’re in a Claude Code session) or scaffold manually:

docs/specs/my-first-feature/
├── spec.json
└── spec.md

Minimum viable spec.json:

{
  "id": "my-first-feature",
  "title": "My first feature",
  "status": "draft",
  "owners": ["Your Name"],
  "linked_paths": ["src/my-feature/**"],
  "acceptance_commands": ["npm test"],
  "depends_on_specs": [],
  "active_prs": []
}

Validate it:

npx dotbabel-validate-specs

Green. You’re done.

5. Wire the PR gate

In GitHub branch protection, require the shipped workflows:

quality is not a PR gate: it runs the deep profile weekly and on manual dispatch, since mutation and race detection are too slow for every push.

Both test.yml and quality.yml execute the project commands your repository declares. Delete either one if you would rather adopt it later.

To run the same quality check before every push, opt in to the hook — it is not enabled by scaffolding, because it runs repository code:

git config core.hooksPath githooks

Only a policy failure blocks a push; a missing tool or a timeout prints a notice and gets out of the way.

Any PR touching a protected path (see docs/repo-facts.json) must now carry a Spec ID: or ## No-spec rationale section. dotbabel-check-spec-coverage enforces it.

6. Project-scope cross-CLI sync (optional)

If your repo has .claude/commands/*.md and .claude/skills/* that you want visible to Codex, Gemini, Antigravity, OpenCode, and Copilot — not just Claude Code — wire them up with project-sync. This is repo-local; user-scope artifacts stay in ~/.claude/ etc. via dotbabel bootstrap.

cd ~/projects/my-app

# 6a. One-time scaffold (writes .dotbabel.json + a starter CLAUDE.md if missing)
npx dotbabel project-init

# 6b. Preview, then apply
npx dotbabel project-sync --dry-run
npx dotbabel project-sync

# 6c. Verify (CI-safe, read-only)
npx dotbabel check-project-sync

What lands where:

Source Codex / Gemini / OpenCode destination Copilot destination
.claude/commands/<name>.md .codex/skills/<name>/SKILL.md (symlink) .github/prompts/<name>.prompt.md (generated, frontmatter mapped)
.claude/skills/<id>/SKILL.md .codex/skills/<id>/ (whole-dir symlink) .github/instructions/<id>.instructions.md (generated, frontmatter mapped)
CLAUDE.md (rule-floor block) rendered into AGENTS.md + GEMINI.md rendered into .github/copilot-instructions.md

What Codex and Gemini get. Every Codex/Gemini destination is a symlink to the Claude source file, not a translated copy — they read their own frontmatter shape, so Claude-shaped frontmatter (allowed-tools, model, effort, disable-model-invocation, the auto-routing description) is not honored. Expect only direct slash invocation by name: /commit works, but natural-language auto-routing, tool restrictions, and model selection apply in Claude Code alone. A command that describes a Claude-only flow (headless Claude workers, Claude-specific flags) fans out verbatim unless you list it in cli_excluded below. Tracked at #219.

What Copilot gets. Unlike Codex/Gemini, Copilot’s targets are generated files with mapped frontmatter — description, name, argument-hint, and tool grants carry over correctly. model, effort, and disable-model-invocation still have no Copilot equivalent and are dropped with a warning naming the file and the key. See docs/copilot-frontmatter-mapping.md for the full key-by-key table. A generated file that is hand-edited is backed up before the next sync overwrites it.

.dotbabel.json is optional — without one, project-sync uses defaults (fan_out: ["codex", "gemini", "antigravity", "opencode", "copilot"], the standard target list, no cli_substitutions). When CLAUDE.md has no <!-- dotbabel:rule-floor:begin --> markers, the whole file becomes the rule floor.

Add $schema to the top of the file for editor autocomplete and validation:

{
  "$schema": "https://dotbabel.dev/schemas/dotbabel.config.schema.json",
  "fan_out": ["codex", "gemini", "antigravity", "opencode", "copilot"],
  "fan_out_layout": "per-cli",
  "gate_on_cli_presence": true
}

fan_out accepts only codex, gemini, antigravity, opencode, and copilot. A typo such as co-pilot fails with CONFIG_UNKNOWN_CLI instead of being skipped.

fan_out_layout (default per-cli) decides whether Codex, Gemini, and OpenCode get one tree each or share a canonical one. Under shared, the table above collapses: .claude/ fans out once to .cli/skills/, and .codex/skills, .gemini/skills, and .opencode/skills become symlinks to it, so each command and skill is tracked once instead of three times. Copilot’s .github/ shapes are unchanged. Switching an existing repo backs the old trees up to .codex/skills.bak-<timestamp>; an unknown value fails with CONFIG_UNKNOWN_LAYOUT. Revert to per-cli if a CLI will not follow the redirect.

Antigravity keeps its own tree even under shared. It reads .agents/skills/, a directory Codex and Gemini do not read, so a redirect there would point at a tree it never follows. Only CLIs that can read the same canonical tree share one; Antigravity is fanned out separately in both layouts.

Antigravity and Gemini CLI coexist. They are separate runtimes with separate skills directories — .agents/skills/ and .gemini/skills/ per repo, ~/.gemini/config/skills/ and ~/.gemini/skills/ globally — and dotbabel never migrates one into the other. What they do share is the instruction file: both read GEMINI.md, so it is generated once rather than per CLI. Set ANTIGRAVITY_CONFIG_HOME to relocate the global Antigravity root, exactly as GEMINI_HOME relocates Gemini’s.

OpenCode gets a native tree even though it can read Claude’s. OpenCode v2.0.5 discovers .claude/skills/ and .agents/skills/ through a compatibility layer, so in principle it needs no fan-out of its own. dotbabel writes .opencode/skills/ anyway: that compatibility layer is OpenCode’s accommodation of other tools, not a contract dotbabel controls, and the native tree keeps the wiring working if it ever narrows. Unlike Antigravity, OpenCode can join the shared tree — it follows a symlinked skills root — so under shared it takes a redirect like Codex and Gemini.

OpenCode’s user scope is XDG-based. Both global artifacts live under one root: AGENTS.md sits beside skills/. The root is $OPENCODE_CONFIG_DIR, or $XDG_CONFIG_HOME/opencode, or ~/.config/opencode, in that order — note that OPENCODE_CONFIG_DIR names the root outright while XDG_CONFIG_HOME names its parent. OPENCODE_CONFIG points at a config file and moves nothing. There is no OPENCODE_HOME.

OpenCode reads the project AGENTS.md. It is the same file Codex and Copilot read, generated once, not a third copy. Commands reach OpenCode as <name>/SKILL.md inside the skills tree, the same shape Codex and Gemini get; dotbabel does not write .opencode/command/, and note that OpenCode’s Claude-compatibility covers skills only — it does not read .claude/commands/.

gate_on_cli_presence (default true) skips a CLI’s symlink fan-out when its binary is absent from PATH. check-project-sync applies the same gate, so it does not report the un-synced CLI as drift and instead prints skipped <cli>: not on PATH. Pass --all to either command to inspect every CLI in fan_out regardless. Instruction files are always written, never gated.

cli_excluded (default {}) maps a CLI to the command basenames and skill ids it must not receive:

{
  "cli_excluded": { "codex": ["review-prs-parallel"], "copilot": ["review-prs-parallel"] }
}

The sync skips those entries for that CLI and removes a link it wrote on an earlier run; check-project-sync reports a lingering one as stale (excluded but present). A name that matches nothing warns, an unknown CLI key fails with CONFIG_UNKNOWN_CLI, and any other shape fails with CONFIG_INVALID_EXCLUSION. Under fan_out_layout: "shared" Codex and Gemini read one tree, so an exclusion for either applies to both and the sync warns when their lists differ.

A repo with .dotbabel.json will also be picked up by dotbabel doctor — the diagnostic adds a project-sync wiring check.

Next