Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

“Remando en dulce de leche” — Dicho popular

dulce-de-leche (ddl)

One command to install, configure, and update every charly-vibes tool.

dulce-de-leche (ddl) is a thin orchestrator that bootstraps the entire charly-vibes tool ecosystem on any platform — macOS, Linux, or Windows. ddl itself needs nothing but the binary; a few tools have prerequisites (npm for incitaciones, and a Rust toolchain for tools without published release binaries) — ddl tells you exactly what is missing and how to install it. See Troubleshooting.

The problem

The charly-vibes ecosystem has 8 active Rust CLI tools (wai, dont, ah, pretender, testaruda, vampiro, specodelic, turu/whisper), plus bd and openspec (workflow/issue tooling) and incitaciones (npm) — 12 managed tools in total (funkalization, the visual semantic design workspace, joined as the newest experimental extension). Each tool has:

  • Its own config file or directory
  • Its own init command
  • Its own installation path (cargo install, brew install, scoop install)

Result: a new user runs 5+ install commands and 5+ init commands before seeing value. Configs are scattered across the repo root with no standard convention.

The solution

ddl provides a single binary that:

  1. Installs all tools from prebuilt GitHub release binaries — cargo install as fallback when no release binary exists, npm for incitaciones
  2. Configures them under a single .ddl/ directory — no root pollution
  3. Migrates existing configs into .ddl/ (via symlinks in Phase 1)
  4. Reports status across the whole toolset in one command
  5. Upgrades everything to compatible versions in one step

It is not a package manager, a reimplementation of any tool, or a CLI launcher that replaces wai/dont/ah. Each tool keeps its own identity and CLI grammar. ddl is just the orchestrator — the “brew bundle” for charly-vibes.

Status

Shipped — ddl is published on crates.io and GitHub releases, and the full toolset boots on macOS, Linux, and Windows (verified end-to-end by the 3-platform smoke gate). Current version: see Release Status (generated from Cargo.toml at build time). See Implementation Status for current state.

License

Apache 2.0 — see LICENSE.

Release Status

Generated artifact (docs.yml, from Cargo.toml + git ref). Do not hand-edit — the repo source files remain authoritative.

  • Version: v0.9.0
  • Built from: main @ 1005d8b

Generated artifact (docs.yml, from docs/ecosystem-map.md). Do not hand-edit — edit that source file instead.

charly-vibes Tool Ecosystem

What ddl manages, and how each tool fits in.

For a machine-generated intent → tool table, see the capability matrix (regenerated from ddl’s registry; drift-checked in CI).

Every repo in the fleet carries the charly-vibes + dulce-de-leche GitHub topics — filter the whole set with topic:charly-vibes topic:dulce-de-leche.

Responsibility map

ddl installs, configures, and updates everything below; each tool owns one slice of the agent-development loop. Dashed borders mark install-only tools — ddl installs them, but their state lives outside .ddl/.

Every card below is a link — click a tool to open its documentation (in-org tools open their mdBook; external ones open their project page).

ddl dulce-de-leche · orchestrator one command — install · configure · update SESSION & KNOWLEDGE wai wai session lifecycle · PARA · handoffs decision matrix for design whisper turu deterministic knowledge workspace .whisper/ + ~/.whisper/ routing bajan bajan knowledge-graph pipeline CLI ingest · extract · query SPECS & VERIFICATION specodelic spk markdown spec format lint · compile · verify espectacular ah behavioral verification spec ⇄ test correspondence openspec openspec spec-driven change proposals install-only · external CODE QUALITY & TESTING testaruda testaruda test selection engine datalog + provenance pretender pretender structural code quality complexity · duplication vampiro vampiro composition checking call · effect · trust wild wild contract compatibility extract · check EPISTEMICS & TRACKING dont dont grounds agent claims evidence state machine beads bd issue tracking with deps install-only · external incitaciones npm agent skills & prompts install-only · non-Rust UTILITIES gently gently graph layout · ASCII · DOT render SHARED FOUNDATION — GENESIS-VIBES (RUST LIBRARY) envelope · suggestions · managed_block · config · guide · status · doctor · scaffold · suite_linter · git · git_hooks linked by every genesis-consuming tool · ddl talks to tools over a subprocess protocol (--json)

Inclusion criteria

ddl manages a tool when all of the following hold:

  1. It is an installable CLI — users run it in a terminal (not a desktop app, library, static site, or content repo).
  2. It has (or is about to get) a distribution channel in the charly-vibes tap/bucket.
  3. It has a config convention ddl can manage (or is explicitly install-only, like bd, openspec, and incitaciones).

Active Tools

Rust CLI tools

ToolBinaryCrates.ioGenesis consumer?Purpose
waiwaiwai-cli✓Workflow manager for AI development. PARA-based artifact organization, phase tracking, handoffs. The matrix subcommand is the standing deliberation surface from Design in Practice — problem, criteria, approaches, and the judgment at each intersection.
dontdont, dtdont-cli✓Epistemic discipline state machine. Claims, evidence, vocabulary tracking. Cozo DB backend.
espectacularahespectacular✓Behavioral verification. Spec-test correspondence enforcement.
pretenderpretenderpretender✓Multi-language structural code quality. Tree-sitter-based complexity, duplication, mutation testing.
testarudatestaruda, testaruda-adapter-rust, testaruda-adapter-pythontestaruda✓Test selection engine. Ascent Datalog + provenance semiring. SQLite store.
vampirovampirovampiro✓Cross-language composition checking at call, module, effect, law, retry, resource, and trust boundaries. Crate workspace with per-language tracers.
wildwildwild-vibes—Machine-checked compatibility and composition of software components, modelled as a category of contracts. wild extract / wild check.
whisperturu (aliases: turututu, whisper)whisper-vibes✓Deterministic knowledge workspace management. Repo-local .whisper/ + global ~/.whisper/ routing. Install-only under ddl — its state is home-dir global, not repo-local legacy config.
specodelicspecodelic, spkspecodelic✓Markdown spec format (Intent / Constraints / Model / Properties) with the CLI that lints, compiles, verifies, and refactors it. Repo-local .specs/ corpus.
funkalizationfunkalizationfunkalization—Visual semantic design workspace. Versioned design semantics with typed design edits, generated obligations, scoped evidence, and loss-reporting adapters toward Specodelic and OpenSpec. Planned repo-local .funkalization/ workspace.
bajanbajanbajan✓Spec-driven knowledge-graph pipeline CLI — ingest sources, extract claims, query the graph. SQLite-backed triple store, provenance as graph structure.
gentlygentlynot yet published—Rust port of Perl’s Graph::Easy: build graphs, lay them out deterministically, render as ASCII art, boxart, HTML, or Graphviz DOT — plus parse Graph::Easy and DOT input.

Non-Rust tools

ToolLanguageIn homebrew?Notes
beadsGo✓ (beads, homebrew-core + gastownhall/beads tap)Issue tracker with first-class dependency support (bd binary). Release assets are named beads_* but ship the bd binary. No cargo fallback — the beads crate on crates.io is an unrelated package. Install-only — manages repo-local .beads/ itself.
openspecTypeScript/npmhomebrew-core (openspec)Spec-driven development workflow for AI coding agents. npm package @fission-ai/openspec; GitHub releases at fission-ai/openspec. Install-only — manages the repo-local openspec/ directory itself.
incitacionesTypeScript/npmnpm (incitaciones)Prompt/skill collection for CLI LLM tools. Install-only — skills land in ~/.agents/skills/ or .agents/skills/; ddl init checks for global skills and prompts if missing.

Config file locations

Before ddl:

ToolConfig locationFormatNotes
wai.wai/TOML + directoryPARA structure
dont.dont/Cozo DB + TOMLHeavyweight store
espectacular.espectacular/JSON + TOMLSpec traces
pretender.pretender.tomlTOMLSingle file
testaruda.testaruda/SQLite + TOMLDependency graph store
vampiro.vampiro/TOMLComposition config (config.toml)
specodelic.specs/Markdown corpusSpec files
funkalization.funkalization/JSON workspace (planned)Design workspace

After ddl migration (Phase 1):

ToolConfig locationBackwards compat
wai.ddl/wai/ → .wai/✓ symlink
dont.ddl/dont/ → .dont/✓ symlink
espectacular.ddl/ah/ → .espectacular/✓ symlink
pretender.ddl/pretender.toml → .pretender.toml✓ symlink
testaruda.ddl/testaruda/ → .testaruda/✓ symlink
vampiro.ddl/vampiro/ → .vampiro/✓ symlink
specodelic.ddl/specodelic/ → .specs/✓ symlink
funkalization.ddl/funkalization/ → .funkalization/✓ symlink

bd, openspec, incitaciones, and turu are install-only — their state lives outside .ddl/ (bd manages repo-local .beads/ itself; openspec manages openspec/ itself), so there is nothing to migrate.

Shared infrastructure (genesis-vibes)

Genesis-consuming tools (wai, dont, espectacular, pretender, testaruda, vampiro, bajan) depend on genesis-vibes; wild and gently do not yet. Genesis provides:

ModuleUsed byPurpose
envelopeAllStructured JSON CLI output
suggestionswai, dontTypo correction (DidYouMean)
managed_blockwai, dont, ah, testaruda, vampiro<!-- BLOCK:START --> injection, with opt-in provenance footer (<!-- provenance: generator=… version=… source=… sha=… --> inside the end marker; hash covers only the content, so drift is machine-checkable)
configAllConfigFile trait, ConfigRegistry
guideAllCLI dispatch, error handling, verbosity
statusAllStatusContributor trait, health aggregation
doctorAllDoctorRunner with auto-fix
scaffoldAllinit command standardization
suite_lintertestarudaCross-tool config linting
fixtureAllTest helpers
gitrollout in progressRead-only git plumbing: repo-root walks, changed-file enumeration, tracked/ignored checks, content hashes — one canonical subprocess-only substrate.
git_hookswai, espectacular, pretenderGit-hook mechanics: install/uninstall with ownership markers, core.hooksPath resolution, lefthook managed-block injection. Tool-specific gates stay in the tools.

Distribution channels

ChannelToolsMaintainer
crates.ioAll released Rust tools (wild publishes as wild-vibes; gently not yet published)Individual repos
Homebrewwai, ah, dont, pretender, testaruda, vampiro, turu, dulce-de-leche — dont formula still a placeholder stub; bd and openspec ship in homebrew-corehomebrew-charly tap
Scoopwai, ah, dont, pretender, testaruda, vampiro, turu, dulce-de-lechescoop-charly bucket
GitHub ReleasesAll Rust tools; bd (gastownhall/beads); openspec (fission-ai/openspec)Individual repos
npmincitaciones; openspec (@fission-ai/openspec)Individual repos

Homebrew formulas (current state)

From homebrew-charly/:

FormulaVersionHas real SHA?Notes
wai.rb2026.8.5✓Real release
testaruda.rb0.4.0✓Real release (engine + rust/python adapters)
vampiro.rb0.4.0✓Real release
turu.rb0.3.0✓Real release (auto-updated by whisper’s release workflow)
ah.rb0.5.0✓Real release (assets under espectacular releases)
pretender.rb0.5.0✓Real release
dulce-de-leche.rb0.3.0✓Real release
dont.rb0.0.0✗ placeholderBlocked: no GitHub release for dont yet (tag v0.3.0 exists locally)

Scoop mirrors the same versions for Windows. All formula hashes were computed from downloaded release assets and cross-verified against each release’s published checksums.txt.

Only dont remains blocked on an upstream release. ddl automatically falls back to cargo install for tools whose release binaries are not yet published (except bd, which has no crates.io package — the beads crate is unrelated), so ddl init works either way. (Since DDL-ei3, brew/scoop are not part of ddl’s install decisions — the tap and bucket below serve manual installs.)

Installation

Install ddl on your platform. All platforms have a prebuilt binary option (no toolchain needed); package managers and cargo are alternatives.

PlatformFastest pathAlternatives
macOSHomebrew (below) or the curl one-linercargo install dulce-de-leche
Linuxcurl one-liner (below)Homebrew, cargo install dulce-de-leche
WindowsScoop (below)cargo install dulce-de-leche

macOS & Linux (curl — prebuilt binary, resolves the latest release)

V=$(basename "$(curl -fsSLI -o /dev/null -w '%{url_effective}' \
  https://github.com/charly-vibes/dulce-de-leche/releases/latest)" | sed 's/^v//')
TGT="$(uname -s | tr '[:upper:]' '[:lower:]')_$(uname -m | sed 's/^x86_64$/amd64/; s/^aarch64$/arm64/')"
curl -fsSL "https://github.com/charly-vibes/dulce-de-leche/releases/download/v${V}/ddl_${V}_${TGT}.tar.gz" | tar xz
chmod +x ddl && sudo mv ddl /usr/local/bin/

macOS (Homebrew)

brew tap charly-vibes/charly
brew install dulce-de-leche

Windows (Scoop)

scoop bucket add charly https://github.com/charly-vibes/scoop-charly.git
scoop install dulce-de-leche

Any platform (Cargo)

cargo install dulce-de-leche

Verify installation

ddl --version

Next steps

Run ddl init to bootstrap the entire charly-vibes toolset — see the Quick Start guide. If the install fails, see Troubleshooting.

Quick Start

Bootstrap the toolset

# Install ddl itself (see Installation)
brew tap charly-vibes/charly
brew install dulce-de-leche

# One command to install & configure everything
ddl init

What just happened?

✓ detected platform (macOS arm64 / Linux x86_64 / Windows)
✓ installed 10 managed tools — prebuilt release binaries (incitaciones via npm)
✓ created .ddl/ with configs for the config-managed tools
✓ ran each tool's init (wai init, dont prime, ah init, pretender init, …)
✓ checked for incitaciones skills and offered a global install
✓ created .gitignore entries for .ddl/ data files

Four tools are install-only — they don’t keep config under .ddl/: bd, openspec, incitaciones, and turu.

Check the ecosystem

ddl status

Shows for each tool:

  • ✓ installed, configured, healthy
  • ✗ not installed (suggests: ddl install wai)
  • ⚠ version outdated (suggests: ddl upgrade)

Install a single tool

ddl install wai

Update everything

ddl upgrade

Migrate existing configs

If you already have tools configured with their legacy config directories (.wai/, .dont/, etc.), migrate them under .ddl/:

ddl migrate

See what’s active

ddl scope

Shows which .ddl/ is active (walks up from CWD like git).

Where to go next

Shows which .ddl/ is active (walks up from CWD like git).

Commands

ddl init

Bootstrap the charly-vibes toolset.

ddl init
# Interactive mode — prompts for tool selection, confirmation

ddl init --yes
# Non-interactive mode — installs all tools, no prompts

ddl init --tools wai,dont
# Selective install — only the specified tools

ddl init --no-install
# Configure only — skip installation, just set up .ddl/

ddl init --gates
# Wire the end state users actually ask for: lefthook hard gates
# (pre-commit: ah check, pretender gate, spk lint; pre-push: ah check),
# .gitignore entries for tool data dirs (.testaruda/, .pretender/), and
# beads no-db stamping. Also orders tool inits so openspec runs before ah.
# Idempotent — safe to re-run. Follow with `lefthook install`.

ddl init --yes --gates
# Full bootstrap with gates wiring — the one-shot setup

Init cascade semantics

Tool inits run in a prerequisite order (openspec before ah), with the .ddl/bin directory on the PATH so freshly downloaded binaries can be initialized, and tools without an init command are skipped silently.

  • A failing tool init fails the run with a resume summary listing the exact command to finish manually (e.g. dont init) or the selective re-run (ddl init --tools dont). No more exit-0 half-configured repos.
  • Successful inits are recorded in .ddl/manifest.json; re-runs skip them (idempotent-until-green) instead of failing on tools that refuse re-initialization.
  • Non-interactive mode (--yes/--json) passes prompt-free flags to tools with wizards (e.g. pretender init --non-interactive).

ddl install <tool>

Install a single tool by name.

ddl install wai
ddl install dont
ddl install ah
ddl install pretender
ddl install testaruda
ddl install vampiro
ddl install bd
ddl install openspec
ddl install incitaciones
ddl install turu
ddl install specodelic

ddl install, ddl init --tools, and ddl upgrade <tool> accept both the binary name and the crate name, case-insensitive.

Canonical names and aliases

Binary names and crate names don’t always match — and agents repeatedly cargo install the crate expecting the binary, or gh repo view the binary name expecting the repo. The table below lists only the tools whose names mismatch (the rest install by their own name):

binarycraterepository
waiwai-clicharly-vibes/wai
dontdont-clicharly-vibes/dont
ahespectacularcharly-vibes/espectacular
turuwhisper-vibescharly-vibes/whisper
wildwild-vibescharly-vibes/wild
openspec@fission-ai/openspec (npm)fission-ai/openspec
ddl (this tool)dulce-de-lechecharly-vibes/dulce-de-leche

So ddl install espectacular installs ah. Typing ddl, dulce, or dulce-de-leche into ddl install / --tools produces an error naming the canonical repository — ddl manages every tool except itself.

ddl status

Show cross-tool health overview.

ddl status
ddl status --json   # machine-readable output

ddl doctor

Run detailed diagnostics across all tools.

ddl doctor
ddl doctor --fix     # attempt auto-fix
ddl doctor --json    # machine-readable output

ddl version

Show versions of ddl and all managed tools.

ddl version
ddl version --check  # check latest available versions (requires network)

ddl upgrade

Update all tools to latest compatible versions.

ddl upgrade
ddl upgrade wai      # upgrade a single tool

ddl migrate

Move existing configs under .ddl/ (phase-1 symlink farm). To unify disparate per-tool config dirs into one managed place, this is the path: run ddl migrate at the repo root; each tool’s legacy config dir (.wai/, .testaruda/, …) is moved under .ddl/<tool>/ and replaced by a symlink.

ddl migrate
ddl migrate --undo   # restore previous layout

Safety semantics (gh#30):

  • Git-tracked configs are skipped with guidance. Git does not follow symlinks, so replacing a tracked dir would make every tracked file vanish from other clones. ddl migrate detects tracked paths (git ls-files) and leaves them in place, telling you how to untrack first if you really want them under .ddl/.
  • --undo is non-destructive. Each legacy path is restored as a copy of the current .ddl/<tool>/ contents; the .ddl/ backing store is kept, so the undo can never lose tool state and a re-migrate is lossless. The manifest’s migration_state is reset to none.

ddl scope

Show which .ddl/ is active.

ddl scope

ddl feedback <kind>

File a feedback issue against ddl’s own repository (charly-vibes/dulce-de-leche) via gh. The repository URL is compiled into the binary, so this works from any directory — no repo spelunking.

Note: the binary is ddl, but the crate is dulce-de-leche (cargo install dulce-de-leche). Looking for charly-vibes/ddl on GitHub returns 404 — the canonical repository is charly-vibes/dulce-de-leche.

ddl feedback bug                  # kind: bug|feature|question|chore
ddl feedback bug --dry-run        # print issue body + gh command, don't submit
ddl feedback bug --from-last-error  # prefill body from the last failed ddl command
echo "what happened" | ddl feedback bug  # pipe body content via stdin
ddl feedback bug --title "custom title"  # override the derived title

The issue body is pre-filled with environment context automatically: ddl version, OS/arch, shell, gh version, current git remote/branch/dirty state, repo tooling state, and a repro hash. Without network access, the body is saved to a local file so nothing is lost.

Global flags

FlagDescription
-v, -vv, -vvvEnable verbose output (progressive detail)
-q, --quietSuppress output except errors
-y, --yesNon-interactive mode
--jsonOutput as JSON for machine parsing
--humanForce human-readable output in non-TTY contexts
--verboseAlias for -v

Exit codes

CodeMeaning
0Success
1Error (any failure — see stderr for details)

Failures are not distinguished by exit code; the human-readable error and its remediation hint are printed to stderr. Scripts should parse --json output instead of exit codes.

Troubleshooting

ddl init fails with “Prerequisite missing”

ddl checks for prerequisites before installing. If a requirement is missing, install it and try again:

  • Binary download: needs curl or wget (pre-installed on most systems)
  • Cargo install: needs rustc + cargo — install via rustup
  • incitaciones (npm): needs npm — install via nodejs.org

Homebrew and Scoop are not used by ddl for installs — the tap and bucket exist only for manual installs of the tools themselves.

ddl install fails with “Tool not found”

Check available tools:

  • wai — Workflow manager
  • dont — Epistemic discipline
  • ah — Behavioral specification testing
  • pretender — Code quality
  • testaruda — Test selection
  • vampiro — Composition checking
  • specodelic — Markdown spec format + linter (binary: spk)
  • bd — Issue tracking (beads)
  • openspec — Spec-driven development
  • incitaciones — Prompts and skills for CLI LLM tools (npm)
  • turu — Knowledge workspace management (whisper-vibes)

(ddl itself is not installable via ddl install — it manages every tool except itself.)

ddl status shows no tools

Run ddl init first to install tools. If you already have tools installed manually, run ddl init --no-install to create the manifest.

Binary download returns 404

The binary for your platform may not be published yet. Try:

cargo install dulce-de-leche

Network is unavailable

ddl works offline for already-installed tools:

  • ddl status works offline
  • ddl version works offline (without --check)
  • ddl upgrade requires network access

cargo install bd installs the wrong crate

The bd name on crates.io belongs to an unrelated crate (“big data tool kit”); beads itself is also taken. Beads does not publish to crates.io — never cargo install bd. Install bd through ddl install bd, which pulls the pinned release binary from the gastownhall GitHub releases.

Deeper postmortems of incidents that cost fleet debugging time (CI install failures, migration symlink bugs) live in the Fleet incident records — maintained under Contributing.

Architecture

Overview

ddl is a thin orchestrator that delegates to existing tools and package managers. It does not reimplement package resolution, tool commands, or database management.

Key design decisions

Subprocess protocol over shared library linking

ddl communicates with each tool via subprocess calls (wai status --json, dont status --json). This decouples release cycles — a genesis breaking change doesn’t require coordinated releases of ddl + all 9 other tools.

Fallback installation chain

Binary download → cargo install (only when a release binary is unavailable and cargo is present; npm for incitaciones). Homebrew and Scoop are no longer part of ddl’s install decisions — the tap and bucket remain published for manual installs only.

Binary downloads are the most reliable path (no dependencies). Cargo install is the fallback when no release binary is available and cargo is installed. Brew/scoop are not used by ddl for installs (see DDL-ei3).

Before deep genesis integration, ddl uses a symlink farm: each tool’s legacy config directory stays at its old path but becomes a symlink into .ddl/ (the backing store):

  • .wai/ → .ddl/wai/ (symlink)
  • .dont/ → .ddl/dont/ (symlink)
  • etc.

No tool patches needed. Zero risk — symlinks are transparent to file reads.

Phase 2: Native config proxy

Each tool learns to check .ddl/<tool>/ first via genesis. Requires coordinated releases of each tool.

Directory layout

.ddl/
  manifest.json          # {"ddl_version": "0.3.0", "migration_state": "...", "tools": {...}}
  config.toml            # ddl's own config
  compatibility-cache.json  # cached compatibility matrix

  wai/                   # wai config files
  dont/                  # dont config files
  ah/                    # ah config files
  pretender.toml         # pretender config file
  testaruda/             # testaruda config files
  vampiro/               # vampiro config files
  specodelic/            # specodelic config files

Legacy directories become symlinks pointing to .ddl/:

.wai/            -> .ddl/wai/            # symlink to .ddl/
.dont/           -> .ddl/dont/           # symlink to .ddl/
.espectacular/   -> .ddl/ah/             # symlink to .ddl/
.pretender.toml  -> .ddl/pretender.toml  # symlink to .ddl/
.testaruda/      -> .ddl/testaruda/      # symlink to .ddl/
.vampiro/        -> .ddl/vampiro/        # symlink to .ddl/
.specs/          -> .ddl/specodelic/     # symlink to .ddl/

Data flow for ddl status

ddl status
  → reads config from .ddl/config.toml
  → for each registered tool:
      → loads tool config from .ddl/<tool>/
      → runs tool's status command (subprocess)
      → parses output
      → collects result
  → renders aggregate status report

Platform strategy

PlatformPrimary installerFallback
macOS (ARM)Binary downloadCargo install
macOS (Intel)Binary downloadCargo install
Linux (ARM)Binary downloadCargo install
Linux (Intel)Binary downloadCargo install
WindowsBinary downloadCargo install

incitaciones always installs via npm. Tools without published release binaries require cargo on the machine.

See also

Implementation Status

Current state: shipped — all planned phases are implemented and published; macOS, Linux, and Windows are verified end-to-end by the 3-platform smoke gate. Current version: see the generated Release Status page (from Cargo.toml — never hand-typed).

Phases

PhaseDescriptionStatus
Core CLICargo crate, CLI structure, error types✅ Done
Platform detectionOS, arch, package manager detection✅ Done
Installation chainBinary-first, cargo fallback, npm for incitaciones✅ Done
Init commandInteractive and non-interactive bootstrap✅ Done
.ddl/ directoryManifest, symlink farm, gitignore✅ Done
Status commandCross-tool health overview✅ Done
Doctor commandDiagnostics and auto-fix✅ Done
Version managementManifest, version, upgrade✅ Done
Cross-platform releaseGitHub Actions, binary builds✅ Done
3-platform smoke gateEnd-to-end verification of release binaries✅ Done
Homebrew formulaFormula in homebrew-charly tap (manual installs)✅ Done
Scoop manifestManifest in scoop-charly bucket (manual installs)✅ Done
DocumentationmdBook, README, help text✅ Done

Notes

  • Since v0.5.0 (DDL-ei3), brew/scoop are not part of ddl’s install decisions — installs are binary-first with cargo fallback; the tap and bucket serve manual installs.
  • Upstream coverage gaps: dont has a placeholder tap formula, so it requires a Rust toolchain on machines without release binaries. bd has no cargo fallback at all (the beads crate on crates.io is unrelated) — it installs strictly from release binaries.

Legend

  • ✅ Done — completed and tested
  • 🔄 In progress — actively being worked on
  • ⬜ Not started — design complete, not implemented

Glossary

Terms used throughout this book that are not part of ddl’s own CLI surface.

TermMeaning
Managed toolA tool ddl can install, configure, and upgrade — listed by ddl catalog and in Ecosystem Development.
Install-only toolA managed tool whose state lives outside .ddl/ (nothing to migrate): bd, openspec, incitaciones, turu.
.ddl/ directoryThe single backing store for tool configs and ddl’s manifest (manifest.json, config.toml) at a repository root.
Symlink farmPhase-1 migration layout: a tool’s legacy config path (.wai/, .dont/, …) becomes a symlink pointing into .ddl/<tool>/, which holds the real files.
Root pollutionScattering per-tool config directories across a repository root instead of consolidating them under .ddl/.
Init cascadeThe ordered run of each tool’s own init command during ddl init (e.g. openspec before ah), with resume-until-green semantics.
Gatesddl init --gates wiring: lefthook pre-commit/pre-push checks, .gitignore entries for tool data dirs, and beads no-db stamping.
Hard gatesLefthook hooks that fail the commit/push (not merely warn) when a check fails.
Smoke gateCI end-to-end verification that release binaries install and run on all three platforms (macOS, Linux, Windows).
Dogfoodingddl (and the charly-vibes fleet) running its own conventions and tools on itself; enforced by conformance tests.
GenesisShared infrastructure crate (genesis-vibes/genesis) used by ecosystem tools for CLI scaffolding, completions, and version management.
Binary name / crate nameThe executable you run vs. the package you install (ah vs. espectacular, turu vs. whisper-vibes). See the alias table in Commands.

Development

Prerequisites

Setup

just setup

Common commands

CommandDescription
justBuild and test
just buildBuild debug binary
just build-releaseBuild release binary
just testRun all tests
just lintRun clippy linter
just fmtFormat code
just ciFull CI pipeline (fmt + lint + test + build)
just run <args>Run ddl with arguments
just docsBuild documentation locally
just docs-serveLive preview docs at localhost:3000

CI/CD

CI runs on GitHub Actions with the same just ci command used locally.

  • CI: runs on every push/PR to main
  • Release: triggered by git tags v* — builds cross-platform binaries, publishes to GitHub Releases, crates.io, Homebrew, and Scoop
  • Docs: builds and deploys mdBook to GitHub Pages on pushes to docs/

Release process

# 1. Update version in Cargo.toml
# 2. Update CHANGELOG.md (move Unreleased entries under the new version)
# 3. If managed tool pins changed, update versions.ddl.toml (§4 standard)
# 4. Commit and push
git add Cargo.toml CHANGELOG.md versions.ddl.toml
git commit -m "Release v0.8.1"
git push

# 5. Tag and push
git tag v0.8.1
git push --tags

# 6. GitHub Actions handles the rest:
#    - Builds binaries for all platforms
#    - Creates GitHub Release
#    - Publishes to crates.io
#    - Updates Homebrew formula
#    - Updates Scoop manifest
#    - Regenerates docs/src/release.md from Cargo.toml and deploys the book

The release page (docs/src/release.md) is generated at docs build time and must never be hand-edited (it is gitignored).

Repository structure

.github/workflows/
  ci.yml          # CI workflow
  release.yml     # Cross-platform release workflow
  docs.yml        # Documentation deployment

docs/
  book.toml       # mdBook configuration
  src/            # Documentation source files

scripts/
  update-homebrew.py  # Homebrew formula updater
  update-scoop.py     # Scoop manifest updater

src/
  main.rs         # CLI entry point
  lib.rs          # Library root
  cli.rs          # Command structure (clap derive)
  error.rs        # Error types
  manifest.rs     # Manifest management
  platform.rs     # Platform detection

Postmortems of incidents that shaped the current design: Fleet Incident Records.

Ecosystem Development

How to set up and work on the charly-vibes tool ecosystem as a whole. For hacking on ddl itself, see Development. The conventions referenced here are normative in docs/standardization.md.

The map

Nine in-org tool repos, one meta-tool (ddl, this repo), and three external consumers:

RepoBinaryWhat it isCategory
waiwaiWorkflow management for agent sessions (PARA tracking, session close/next)Core
testarudatestarudaLanguage-agnostic test selection engineCore
dontdontForces agents to ground claims before asserting themRecommended
pretenderpretenderCode quality CLI (complexity, duplication, mutation)Recommended
vampirovampiroComposition-analysis CLIExtension
specodelicspecodelic / spkMarkdown spec format (Intent/Constraints/Model/Properties) + linter/compilerExtension
incitaciones—Skills package (npm; the one non-Rust tool)Extension
espectacularahBehavioral verification layerRecommended
whisperturu (aliases: whisper, turututu)Deterministic knowledge workspace managementRecommended
dulce-de-lecheddlThis orchestrator—

External (install-only, not developed here): bd (beads, issue tracking), openspec (spec-driven proposals).

Every tool’s why and maturity lives in its README top block and docs/src/status.md — that placement is the standard (§5), not a convention.

Workspace layout

Sibling checkouts matter: ddl’s conformance drift test (tests/standard_conformance.rs) discovers them as ../<repo>/ relative to this repo, and cross-repo work (registry updates, version bumps) assumes the flat layout:

charly/
├── dulce-de-leche/   # this repo
├── wai/
├── testaruda/
├── dont/  pretender/  vampiro/  specodelic/  incitaciones/  espectacular/
└── homebrew-charly/  scoop-charly/   # taps, updated by release workflows

If you keep repos elsewhere, symlink them into the same parent directory — the drift test skips absent siblings (fleet conformance is a local/ddl-side check; each repo’s own CI only validates itself).

One-time setup

# 1. Rust + just + mdBook
rustup component add rustfmt clippy
brew install just mdbook        # or cargo install just / mdbook

# 2. Ecosystem tools used by the repos' own CI (dogfooding)
cargo install wai testaruda pretender dont bd openspec

# 3. Clone the siblings you'll touch
for r in wai testaruda dont pretender vampiro specodelic espectacular incitaciones whisper; do
  git clone "git@github.com:charly-vibes/$r.git" "../$r"   # from ddl/
done

# 4. Per-repo bootstrap + verify
cd ../wai && just setup && just ci    # repeat per repo

Day-to-day

  • Task tracking: bd per repo (issues live in each repo’s .beads/issues.jsonl, committed to git). Cross-repo epics live in ddl.
  • Session lifecycle: wai close / wai next per the workspace AGENTS.md.
  • Design changes: openspec proposal in the affected repo before code.
  • Full check before pushing: just ci — same command CI runs. There is exactly one entry point per repo; if a check isn’t in just ci, it isn’t a gate.

Conformance

cargo test -p dulce-de-leche --test standard_conformance checks every present sibling (and ddl itself) against the mechanically-checkable standard invariants: the three workflow files (§1), the binary release matrix, root book.toml with the charly theme fields (§2/§3), README motivation+status block (§5), and the pinned-versions file (§4). Red means that repo needs a rollout fix — file it under the standardization epic (DDL-u8x) and turn one assertion green per commit.

Releases

Every repo releases the same way (§1):

git tag v0.X.Y && git push --tags

CI then builds all five targets (linux/darwin amd64+arm64 tar.gz, windows amd64 zip), publishes a GitHub Release with checksums.txt, publishes to crates.io (tag must match Cargo.toml), and updates the homebrew tap + scoop bucket. Caveat: tap/scoop steps skip silently if the TAP_GITHUB_TOKEN secret is absent — re-run the publish job after adding it.

incitaciones is the exception: npm via its npm-publish.yml (the release slot under that name).

Adding a new tool

Follow the onboarding checklist in docs/standardization.md §7: three workflow files, root book.toml + vendored theme, five book pages, llms.txt, README why/status block, versions.ddl.toml, a row in ddl’s registry (src/platform.rs, enforced by tests/tool_registry_drift.rs), and a dogfood-matrix entry. Document any deviation in the tool’s docs/src/status.md.

Fleet Incident Records

For maintainers and contributors — symptom → cause → fix records from incidents that actually cost fleet debugging time. User-facing symptoms are covered in Troubleshooting.

Beads release tarball clobbers repo checkouts

SymptomConformance tests report README/LICENSE/CHANGELOG drift in repos that were not touched — across several repos at once, right after adding a beads install step to CI
Causetar -xzf bd.tgz run in the repo root: the beads release archive ships its own README, LICENSE, CHANGELOG, which overwrote the checkout’s files. Root-caused with a temporary eprintln probe pushed to CI
FixExtract into a temp dir and copy only the bd binary member
- run: |
    dir=$(mktemp -d)
    curl -fsSL <release-asset-url> | tar -xzf - -C "$dir" bd
    install -m 0755 "$dir/bd" /usr/local/bin/bd

CI jobs cancelled with no failure — cold cargo install batches

SymptomCI runs “cancelled” sporadically, no test failure, no log error — first seen in dont
CauseA batch of pinned cargo installs on a cold cache exceeds the default 10-minute job timeout; GitHub marks the run cancelled, which reads like a flake
FixBump the install step to 30 minutes fleet-wide (§4 dogfood matrix); keep installs pinned so cache-cold batches are predictable

cargo install bd installs the wrong crate

SymptomInstall of beads “succeeds” but the bd binary is not beads
CauseThe bd name on crates.io belongs to an unrelated crate (“big data tool kit”); beads itself is also taken. Beads does not publish to crates.io
FixInstall from the gastownhall GitHub release asset only, pinned to a version (§4 dogfood matrix). Never cargo install bd
Symptomddl migrate --undo silently finds nothing to undo; re-running ddl migrate fails with Not a directory; single-file configs (e.g. .pretender.toml) became directory symlinks
CauseSymlink targets persisted as relative paths while later discovery compared absolute paths (unnormalized), so detection saw nothing; migrate_tool() had no already-migrated guard; single-file configs were linked as directories
FixNormalize symlink targets before comparing (migrated_tools()); guard with is_symlink(legacy_path) and skip; separate single-file link creation/restoration (shipped as DDL-0yp, DDL-el9, DDL-cbp, DDL-40e)

Bootstrap

Purpose

Define how ddl installs, initializes, and configures the charly-vibes toolset across different platforms. This is the core value proposition of ddl: one binary, one command, any platform.

Problem Statement

Installing the charly-vibes toolset currently requires: (1) knowing which tools exist, (2) installing each via a platform-specific package manager, (3) running each tool’s init command. This friction increases with each new tool and varies by platform. A single bootstrap command eliminates this friction.

Design Rationale

Platform Strategy

ddl detects the OS and architecture at runtime and selects the best installation method for each tool. The fallback chain is: binary download → cargo install → brew/scoop install.

Supported platforms: macos-arm64, macos-amd64, linux-arm64, linux-amd64, windows-amd64. On these platforms, ddl guarantees binary availability as the primary path — no prerequisites beyond the binary itself. Unsupported platforms (32-bit, FreeBSD) must use cargo install.

Tool Name Mapping

Each managed tool has different names across distribution channels. ddl maintains this mapping:

Tool (binary name)Cargo crate / npm packageHomebrew formulaGitHub repo
waiwai-cliwai.rbcharly-vibes/wai
dontdont-clidont.rbcharly-vibes/dont
ahespectacularah.rbcharly-vibes/espectacular
pretenderpretenderpretender.rbcharly-vibes/pretender
testarudatestarudatestaruda.rbcharly-vibes/testaruda
vampirovampirovampiro.rbcharly-vibes/vampiro
turuwhisper-vibesturu.rbcharly-vibes/whisper
specodelicspecodelic— (crate only)charly-vibes/specodelic
bd— (Go tool)— (homebrew-core + tap beads)gastownhall/beads
openspec— (npm @fission-ai/openspec)—fission-ai/openspec
incitaciones— (npm incitaciones)—charly-vibes/incitaciones

For cargo install, use the crate name (e.g., cargo install espectacular). For brew install, use the formula name minus .rb (e.g., brew install ah). The install policy (DDL-ei3) is prebuilt binary first, cargo as fallback — brew/scoop formulas remain published for manual installs only.

Prerequisites

Each installation method has prerequisites:

MethodPrerequisitesSupported platforms
Binary downloadcurl or wget, internet accessAll (preferred)
Cargo installrustc + cargoAll (fallback)
Brew installbrewmacOS, Linux (with brew)
Scoop installscoopWindows

The binary download path is the only truly “no prerequisites” path (aside from curl/wget, which are pre-installed on essentially all systems). On supported platforms, ddl distributes binaries for every release, so binary download is always available.

First-Run Flow

ddl init is the primary entry point. It presents an interactive checklist of available tools, installs the selected ones, runs their init commands, and creates the .ddl/ directory. In CI environments, ddl init --yes runs non-interactively with all tools.

Placeholder Detection

Some Homebrew formulas (currently only dont) have placeholder versions (0.0.0 with fake SHA256). ddl MUST detect these and skip brew install with a clear message, falling back to cargo install or binary download.

Network Failure Handling

If a tool installation fails (network error, 404, timeout), ddl records the tool’s status as "failed" in .ddl/manifest.json and continues with remaining tools. A subsequent ddl init or ddl install retries failed tools. This prevents half-installed states from blocking the user.

Scope

Non-Goals

  • Installing tools outside the charly-vibes ecosystem
  • Resolving version conflicts between tools (each tool manages its own dependencies)
  • Installing the Rust toolchain (cargo install is a fallback, not a primary path — ddl does not install rustc)

Requirements

Requirement: Platform Detection

The CLI SHALL detect the operating system and CPU architecture at runtime.

On every supported platform, the preferred installation method is binary download; cargo install is the fallback when a release binary is unavailable and cargo is on PATH. No package manager (brew, scoop) participates in method selection.

Scenario: macOS (ARM)

  • WHEN ddl runs on macOS with Apple Silicon
  • THEN the system detects macos-arm64 as the platform
  • AND prefers binary download (primary path) or cargo install (fallback)

Scenario: macOS (Intel)

  • WHEN ddl runs on macOS with Intel processor
  • THEN the system detects macos-amd64 as the platform
  • AND prefers binary download (primary path) or cargo install (fallback)

Scenario: Linux (ARM)

  • WHEN ddl runs on Linux with ARM64 processor
  • THEN the system detects linux-arm64 as the platform
  • AND prefers binary download (primary path) or cargo install (fallback)

Scenario: Linux (Intel)

  • WHEN ddl runs on Linux with AMD64/Intel processor
  • THEN the system detects linux-amd64 as the platform
  • AND prefers binary download (primary path) or cargo install (fallback)

Scenario: Windows

  • WHEN ddl runs on Windows
  • THEN the system detects windows-amd64 as the platform
  • AND prefers binary download (primary path) or cargo install (fallback)
  • AND binary downloads use .exe extension and are placed in %LOCALAPPDATA%\ddl\bin\

Scenario: Unsupported platform

  • WHEN ddl runs on an unsupported platform (e.g., 32-bit, FreeBSD)
  • THEN the system displays a diagnostic error listing supported platforms
  • AND exits with code 2

Requirement: Binary Distribution

The CLI SHALL be distributed as a pre-compiled static binary for each supported platform.

Scenario: Release assets

  • WHEN a new version of ddl is released
  • THEN the release includes binaries for: macos-arm64, macos-amd64, linux-arm64, linux-amd64, windows-amd64
  • AND each binary is statically linked (no runtime dependencies)

Scenario: Binary download

  • WHEN user downloads a ddl binary
  • THEN the binary runs without any installation step (no package manager, no toolchain)
  • AND the binary is self-contained (includes all logic)

Requirement: Init Command

The CLI SHALL provide ddl init as the primary bootstrap command.

Scenario: Interactive init (default)

  • WHEN user runs ddl init without flags
  • THEN the system detects the platform
  • AND checks prerequisites (curl/wget for binary download; cargo for the fallback path)
  • AND presents an interactive checklist of available tools with descriptions
  • AND prompts the user to select which tools to install (default: all)
  • AND installs each selected tool via binary download, falling back to cargo install when no release binary is available
  • AND runs each installed tool’s init command (e.g., wai init, dont prime)
  • AND creates .ddl/ directory structure
  • AND writes .ddl/manifest.json with installed versions (status: "installed" or "failed")
  • AND optionally adds .gitignore entries for .ddl/ data files

Scenario: Non-interactive init (CI)

  • WHEN user runs ddl init --yes
  • THEN the system proceeds without any prompts
  • AND installs all available tools
  • AND fails with a clear error if all tools fail; partial failure reports which succeeded

Scenario: Selective install

  • WHEN user runs ddl init --tools wai,dont
  • THEN the system only installs and configures the specified tools (wai and dont)
  • AND skips all other tools

Scenario: Already initialized

  • WHEN user runs ddl init in a directory that already has .ddl/
  • THEN the system checks .ddl/manifest.json for installed tools
  • AND prompts to install any missing tools
  • AND retries any tools with "status": "failed" in the manifest
  • AND skips tools that are already installed and configured

Scenario: Binary-first install with cargo fallback

  • WHEN ddl needs to install a tool on any platform
  • THEN it downloads the release binary from the tool’s GitHub releases
  • AND when the release has no binary for the current platform (404 or asset missing)
  • AND cargo is available on PATH
  • THEN it falls back to cargo install <crate> and records source: "cargo" in the manifest

Scenario: No release binary and no cargo

  • WHEN a tool has no release binary for the current platform
  • AND cargo is not installed
  • THEN ddl reports an error naming both remedies: installing Rust (https://rustup.rs) to enable cargo install, or downloading the binary manually from the tool’s GitHub releases
  • AND records "status": "failed" for that tool

Scenario: Prerequisites check

  • WHEN user runs ddl init
  • THEN the system checks which prerequisites are needed based on the installation plan
  • AND reports which prerequisites are missing with install guidance
  • AND does not proceed with installation until prerequisites are met (unless --yes is set)
  • AND prerequisite checks include: curl or wget (for binary download) and cargo (for the fallback path)

Scenario: Network failure during install

  • WHEN a tool installation fails due to network error, 404, or timeout
  • THEN ddl records "status": "failed" for that tool in the manifest
  • AND reports the failure with the error message
  • AND continues with remaining tools
  • AND exits with code 1 (partial failure)
  • AND a subsequent ddl init or ddl install retries failed tools

Scenario: Binary download returns 404

  • WHEN a binary download URL returns 404 (release not published yet)
  • AND cargo is available on PATH
  • THEN ddl falls back to cargo install <crate>
  • AND reports: “⚠ binary not yet available for this platform — using cargo install instead”

Scenario: Binary download returns 404 without cargo

  • WHEN a binary download URL returns 404 (release not published yet)
  • AND cargo is not available on PATH
  • THEN ddl does NOT attempt any further install method
  • AND reports: “⚠ binary not yet available for this platform, and cargo is not installed. Install Rust (https://rustup.rs) or download the binary manually from /releases.”
  • AND exits with code 1

Requirement: Install Command

The CLI SHALL provide ddl install <tool> to install a single tool.

Scenario: Install known tool

  • WHEN user runs ddl install wai
  • THEN the system detects the platform
  • AND looks up the tool in the name mapping table (crate name)
  • AND installs via binary download, falling back to cargo install when no release binary is available
  • AND updates .ddl/manifest.json

Scenario: Install unknown tool

  • WHEN user runs ddl install unknown-tool
  • THEN the system displays a “did you mean” suggestion if a similar tool name exists
  • AND exits with code 2

Scenario: Reinstall already installed tool

  • WHEN user runs ddl install wai and wai is already installed
  • THEN the system checks the version
  • AND if up to date, reports “wai is already up to date”
  • AND if outdated, suggests ddl upgrade or offers to reinstall

Scenario: Windows binary placement

  • WHEN user runs ddl install wai on Windows
  • THEN the release binary (.exe) is downloaded from GitHub releases
  • AND placed in %LOCALAPPDATA%\ddl\bin\
  • AND suggests adding that path to %PATH% if not already present

Requirement: Init Subcommand

The CLI SHALL run each managed tool’s init command after installation.

Scenario: wai init

  • WHEN ddl installs wai via ddl init or ddl install wai
  • THEN it runs wai init after installation
  • AND reports success or failure

Scenario: dont prime

  • WHEN ddl installs dont via ddl init or ddl install dont
  • THEN it runs dont prime --plain after installation
  • AND reports success or failure

Scenario: ah init

  • WHEN ddl installs ah via ddl init or ddl install ah
  • THEN it runs ah init after installation
  • AND reports success or failure

Scenario: Tool init fails

  • WHEN a tool’s init command fails
  • THEN ddl reports the failure with the tool’s error output
  • AND continues with remaining tools
  • AND exits with code 1 (partial failure)

CLI Core

Purpose

Define the core command structure, global flags, and output conventions for the ddl CLI. All dulce-de-leche commands follow these patterns.

Problem Statement

For ddl to be a reliable bootstrap and orchestration tool, it requires a stable and predictable command interface. Without a consistent command structure, users would face a steep learning curve and inconsistent interactions when managing the charly-vibes toolset.

Design Rationale

Command Structure: Single-Level Nouns

ddl uses a flat noun-based command structure (e.g., ddl init, ddl status, ddl upgrade) rather than a verb-noun hierarchy. This is intentional: ddl is a thin orchestrator with a small number of commands that each do one thing well. The flat structure keeps the CLI discoverable and fast.

Output Format

All commands output human-readable text by default. Commands that return structured data support --json for machine parsing. Error output follows the self-healing pattern: describe the problem, suggest a fix, and provide the exact command to run.

Scope

Non-Goals

  • Subcommand nesting (no ddl tool install wai — use ddl install wai)
  • Interactive mode beyond confirmation prompts (use --yes for non-interactive)
  • Pager integration (output is designed for terminal and CI)

Requirements

Requirement: Command Structure

The CLI SHALL provide a flat set of top-level commands, each performing a single orchestration function.

Scenario: List available commands

  • WHEN user runs ddl --help or ddl -h
  • THEN the system displays a list of all available commands with brief descriptions
  • AND shows global flags

Scenario: Version display

  • WHEN user runs ddl --version or ddl -V
  • THEN the system displays the ddl version number

Scenario: Command not found

  • WHEN user runs ddl <unknown-command>
  • THEN the system displays a “did you mean” suggestion if a similar command exists
  • AND exits with a non-zero code

Requirement: Global Flags

The CLI SHALL support global flags that work with all commands.

Scenario: Verbose output

  • WHEN user passes -v or --verbose
  • THEN output includes additional context and installation details

Scenario: Quiet mode

  • WHEN user passes -q or --quiet
  • THEN only errors are shown

Scenario: Non-interactive mode

  • WHEN user passes --yes or -y
  • THEN the system proceeds with default choices for all confirmations

Scenario: JSON output

  • WHEN user passes --json
  • THEN the system outputs JSON for machine parsing
  • AND the JSON envelope follows the genesis-vibes envelope format: {"ok": true, "data": {...}, "warnings": [], "hints": []}

Scenario: NO_COLOR support

  • WHEN the NO_COLOR environment variable is set
  • THEN the system disables colored output

Requirement: Exit Codes

The CLI SHALL use consistent exit codes across all commands.

Scenario: Success

  • WHEN a command completes successfully
  • THEN the system exits with code 0

Scenario: Partial failure

  • WHEN a command partially completes (e.g., some tools installed, some failed)
  • THEN the system exits with code 1
  • AND reports which tools succeeded and which failed

Scenario: Unrecoverable error

  • WHEN a command encounters an unrecoverable error (e.g., invalid arguments, missing platform)
  • THEN the system exits with code 2
  • AND displays a diagnostic error with a suggested fix

Requirement: Help Text

The CLI SHALL provide informative help text for every command.

Scenario: Command help

  • WHEN user runs ddl <command> --help
  • THEN the system displays the command’s usage, description, and flags

Scenario: Help text includes examples

  • WHEN user runs ddl <command> --help
  • THEN the help text includes 1-2 usage examples

.ddl/ Directory

Purpose

Define the structure, conventions, and lifecycle of the .ddl/ directory — the single configuration directory for all charly-vibes tools managed by ddl.

Problem Statement

Each charly-vibes tool has its own config directory at the repo root (.wai/, .dont/, .espectacular/, .testaruda/, .pretender.toml). This creates root pollution and makes it unclear which files belong to which tool. The .ddl/ directory consolidates all tool configs under one roof, with a clear convention for what is committed (config) and what is ignored (data).

Design Rationale

Phase 1 uses symlinks: .ddl/wai/ -> .wai/, .ddl/dont/ -> .dont/, etc. The original tool directories remain at the root as symlinks pointing into .ddl/. This requires zero tool patches — each tool reads from its original path, unaware that it’s a symlink.

Limitation: The root is not truly clean until Phase 2 (native config proxy), but the .ddl/ directory provides a single place to look for all tool configs.

Phase 2: Native Config Proxy (future)

Each tool (via genesis) learns to check .ddl/<tool>/ before its legacy location. At that point, the legacy directories can be removed entirely.

File Ownership

  • .ddl/manifest.json: Machine-written, machine-read. Committed to repo for reproducibility.
  • .ddl/config.toml: ddl’s own config. Human-editable. Committed.
  • .ddl/<tool>/config.toml: Per-tool config (symlinked to original location in Phase 1). Committed.
  • .ddl/<tool>/*.db: Tool data stores (Cozo, SQLite). Gitignored.

Discovery Rules

ddl discovers .ddl/ by walking up from CWD (same as git). The nearest ancestor .ddl/ wins. This allows nested projects (monorepo) to have their own .ddl/.

Scope

Non-Goals

  • Merging tool databases (each tool keeps its own Cozo/SQLite store)
  • Providing a UI for browsing .ddl/ contents
  • Auto-migration from legacy configs (Phase 1 symlinks are created by ddl migrate, not automatically)

Requirements

Requirement: Directory Layout

The .ddl/ directory SHALL follow a standard layout.

Scenario: Standard layout after init

  • WHEN user runs ddl init or ddl init --yes
  • THEN the system creates .ddl/ with the following structure:
    • .ddl/manifest.json — version manifest
    • .ddl/config.toml — ddl’s own config
    • .ddl/wai/ — wai config directory
    • .ddl/dont/ — dont config directory
    • .ddl/ah/ — espectacular config directory
    • .ddl/pretender.toml — pretender config file
    • .ddl/testaruda/ — testaruda config directory
    • .ddl/vampiro/ — vampiro config directory
    • .ddl/specodelic/ — specodelic config directory

Scenario: Per-tool subdirectory

  • WHEN a tool is installed via ddl install <tool>
  • THEN the system creates the corresponding subdirectory under .ddl/<tool>/
  • AND the subdirectory is a symlink to the tool’s legacy config directory in Phase 1

The CLI SHALL use symlinks to preserve backward compatibility with existing tool config paths.

  • WHEN ddl migrate runs
  • THEN the system moves .wai/ contents to .ddl/wai/
  • AND creates a symlink .wai/ -> .ddl/wai/
  • AND repeats for each installed tool
  • WHEN ddl runs on Windows
  • THEN the system creates directory junctions instead of symlinks (symlinks require admin on Windows)
  • AND documents this limitation in ddl doctor
  • WHEN user runs ddl doctor and a symlink target is missing
  • THEN the system reports the broken symlink
  • AND suggests ddl migrate --undo to restore the original layout

Requirement: Gitignore Convention

The CLI SHALL manage .gitignore entries for .ddl/ data files.

Scenario: Gitignore creation

  • WHEN user runs ddl init
  • THEN the system prompts: “Add .gitignore entries for .ddl/ data files? [Y/n]”
  • AND if confirmed, adds the following entries to .gitignore:
    # dulce-de-leche — data files (do not commit)
    .ddl/**/*.db
    .ddl/**/store/
    .ddl/install-log.json
    .ddl/doctor-cache.json
    

Scenario: Gitignore skip

  • WHEN user runs ddl init --yes
  • THEN the system does NOT modify .gitignore
  • AND documents in the output: “Skipped .gitignore — run ddl init interactively to configure”

Requirement: Manifest File

The CLI SHALL maintain a .ddl/manifest.json file tracking installed tool versions.

Scenario: Manifest structure

  • WHEN .ddl/manifest.json is written
  • THEN it contains:
    • ddl_version: the version of ddl that wrote this manifest
    • migration_state: "phase1" or "phase2" or "none"
    • tools: an object with one entry per tool, each containing:
      • installed: version string (e.g., "2026.5.1")
      • source: installation source (e.g., "brew", "cargo", "binary")
      • status: "installed", "pending", or "failed" (for retry support)
      • compatible: version constraint string (e.g., ">=2026.3.0")

Scenario: Manifest creation

  • WHEN user runs ddl init or ddl install <tool>
  • THEN the system creates or updates .ddl/manifest.json

Scenario: Manifest read

  • WHEN user runs ddl status or ddl doctor
  • THEN the system reads .ddl/manifest.json to determine which tools are installed
  • AND compares installed versions against the compatibility matrix

Scenario: Manifest missing

  • WHEN user runs ddl status and .ddl/manifest.json does not exist
  • THEN the system scans for tool binaries in PATH
  • AND warns that the manifest is missing
  • AND suggests ddl init to create it

Scenario: Per-tool status tracking

  • WHEN a tool installation fails
  • THEN the manifest records "status": "failed" for that tool
  • AND a subsequent ddl init or ddl install retries failed tools

Requirement: File Locking

The CLI SHALL use file locking when writing to .ddl/manifest.json to prevent corruption from concurrent processes.

Scenario: Concurrent write

  • WHEN two ddl processes attempt to write to manifest.json simultaneously
  • THEN the second process waits for the first to release the lock
  • AND both writes complete without corruption

Scenario: Lock contention

  • WHEN a ddl process cannot acquire the lock within a timeout
  • THEN it reports a warning: “Another ddl process is running. Retrying…”
  • AND retries up to 3 times before failing

Health

Purpose

Define how ddl aggregates and reports the health status of all managed charly-vibes tools. This includes ddl status (quick overview) and ddl doctor (detailed diagnostics).

Problem Statement

Each charly-vibes tool has its own status and diagnostic commands (wai status, wai doctor, dont doctor, ah doctor, pretender check, testaruda doctor). To check the health of the entire toolset, a user must run 6+ commands. ddl provides a single command that aggregates all tool health into one view.

Design Rationale

Subprocess Communication

ddl communicates with each tool via subprocess calls, not shared library linking. This decouples ddl’s release cycle from each tool’s release cycle. ddl calls wai status --json, dont status --json, etc. and parses the JSON output. If a tool does not support --json, ddl falls back to parsing the human-readable output (degraded, but functional).

Status vs Doctor

  • ddl status: quick check — is the tool installed? is it configured? is it working? (<500ms)
  • ddl doctor: detailed diagnostics — full health check with fix suggestions (<2s)

Discovery Mode

When no .ddl/manifest.json exists, ddl scans PATH for known tool binaries. This allows ddl status to work even before ddl init is run (e.g., on a machine where tools were installed manually).

Scope

Non-Goals

  • Modifying tool state (read-only operations)
  • Running tool-specific commands (e.g., ddl dont conclude — use dont conclude directly)
  • Diagnosing tool-specific issues beyond what the tool reports

Requirements

Requirement: Status Command

The CLI SHALL provide ddl status to show a quick overview of all managed tools.

Scenario: All tools healthy

  • WHEN user runs ddl status and all tools are installed, configured, and healthy
  • THEN the system displays a summary with each tool’s status
  • AND uses ✓ for healthy, ⚠ for warning, ✗ for failed
  • AND shows tool versions
  • AND exits with code 0

Scenario: Some tools not installed

  • WHEN user runs ddl status and some tools are not installed
  • THEN the system displays ✗ for missing tools
  • AND suggests ddl install <tool> for each missing tool
  • AND exits with code 1

Scenario: Some tools unhealthy

  • WHEN user runs ddl status and some tools are installed but unhealthy
  • THEN the system displays ⚠ for each unhealthy tool
  • AND suggests ddl doctor for detailed diagnostics
  • AND exits with code 1

Scenario: Status as JSON

  • WHEN user runs ddl status --json
  • THEN the system outputs JSON following the format defined in cli-core/spec.md (Requirement: JSON Output)
  • AND the data field contains each tool’s name, version, status, and message

Scenario: Status reads from subprocess

  • WHEN user runs ddl status
  • THEN ddl calls each installed tool’s status command via subprocess
  • AND parses the JSON output (preferred) or human output (fallback)
  • AND includes the tool’s own diagnostic output in the ddl report

Scenario: Tool subprocess fails

  • WHEN ddl calls a tool’s status command and the subprocess fails
  • THEN ddl reports the tool as “unreachable”
  • AND includes the subprocess error output
  • AND continues checking remaining tools
  • AND exits with code 1

Scenario: No tools installed

  • WHEN user runs ddl status and no tools are installed
  • THEN the system displays “No charly-vibes tools installed”
  • AND suggests ddl init
  • AND exits with code 1

Scenario: Discovery mode (no manifest)

  • WHEN user runs ddl status and .ddl/manifest.json does not exist
  • THEN the system scans PATH for known tool binaries (wai, dont, ah, pretender, testaruda, vampiro, turu, specodelic, bd, openspec, incitaciones)
  • AND reports each found tool as “detected (PATH)”
  • AND suggests ddl init to create a manifest
  • AND exits with code 0 if all tools found, code 1 if some missing

Requirement: Doctor Command

The CLI SHALL provide ddl doctor to run detailed diagnostics on all managed tools.

Scenario: Full diagnostics

  • WHEN user runs ddl doctor
  • THEN the system runs each tool’s doctor command via subprocess
  • AND aggregates the results into a single report
  • AND shows pass/warn/fail status for each tool
  • AND includes fix suggestions from each tool’s doctor output
  • AND exits with code 0 if all pass, code 1 if any fail

Scenario: Doctor as JSON

  • WHEN user runs ddl doctor --json
  • THEN the system outputs JSON following the format defined in cli-core/spec.md
  • AND groups results by tool with pass/warn/fail counts

Scenario: Doctor fix

  • WHEN user runs ddl doctor --fix
  • THEN the system runs each tool’s doctor with auto-fix enabled
  • AND reports which fixes were applied and which failed
  • AND exits with code 0 if all fixes applied, code 1 if any failed

Scenario: Tool doctor not available

  • WHEN ddl runs doctor for a tool that doesn’t have a doctor command
  • THEN ddl reports “doctor not available” for that tool
  • AND performs a basic check (is the binary in PATH? is the config directory valid?)
  • AND continues with remaining tools
  • AND does not change the exit code (basic checks are not failures)

Version Management

Purpose

Define how ddl tracks, pins, and updates tool versions across environments. This ensures reproducibility: the same ddl init produces the same toolset on any machine.

Problem Statement

Without version pinning, ddl init on two different days could install different versions of tools. This creates “works on my machine” problems for the toolchain itself. A manifest file ensures that all environments get the same tool versions.

Design Rationale

Manifest Format

The manifest (.ddl/manifest.json) is the source of truth for which tools are installed, at what versions, and via which source. It is written by ddl init and ddl install, and read by ddl status, ddl doctor, and ddl upgrade. The manifest is committed to the repository (config changes are reproducible) while data files are gitignored.

Version Compatibility Matrix

ddl uses a compatibility matrix to prevent installing tool versions with incompatible config conventions. The matrix is fetched dynamically from https://charly-vibes.github.io/ddl/compatibility.json at runtime. This ensures the matrix is always fresh without requiring a ddl release. An embedded fallback matrix is used when offline.

Online vs Offline Mode

  • Online: ddl version --check queries the latest available versions of all tools and the compatibility matrix
  • Offline: ddl version (without --check) shows installed versions from the manifest only, using the embedded fallback matrix for compatibility checks
  • ddl upgrade requires network access (must query latest versions)
  • ddl status works offline (reads from manifest)

Binary-Downloaded Tool Upgrades

When a tool was installed via binary download (the preferred path), ddl upgrade re-downloads the binary from the tool’s GitHub releases. The upgrade fetches the latest release tag, downloads the binary for the current platform, and replaces the existing binary. This is the same mechanism as the initial binary download.

Scope

Non-Goals

  • Semantic version constraint solving (dependencies between tool versions)
  • Rolling back tool versions (use the platform’s package manager for this)
  • Managing tool updates outside ddl (if a user runs brew upgrade wai, ddl detects the version change on next status check)

Requirements

Requirement: Manifest File

The CLI SHALL maintain a .ddl/manifest.json file tracking installed tool versions.

Scenario: Manifest creation

  • WHEN user runs ddl init or ddl install <tool>
  • THEN the system creates or updates .ddl/manifest.json
  • AND records the ddl version, each tool’s installed version, installation source, and installation status

Scenario: Manifest structure

  • WHEN .ddl/manifest.json is written
  • THEN it contains:
    • ddl_version: the version of ddl that wrote this manifest
    • migration_state: the migration phase (e.g., "phase1", "none")
    • tools: an object with one entry per tool, each containing installed (version string), source (e.g., "brew", "cargo", "binary"), status ("installed", "pending", "failed"), and compatible (version constraint string)

Scenario: Manifest read

  • WHEN user runs ddl status or ddl doctor
  • THEN the system reads .ddl/manifest.json to determine which tools are installed
  • AND compares installed versions against the compatibility matrix

Scenario: Manifest missing

  • WHEN user runs ddl status and .ddl/manifest.json does not exist
  • THEN the system scans for tool binaries in PATH
  • AND warns that the manifest is missing
  • AND suggests ddl init to create it

Requirement: Version Command

The CLI SHALL provide ddl version to show the versions of ddl and all managed tools.

Scenario: Offline version display

  • WHEN user runs ddl version (without --check)
  • THEN the system displays the ddl version
  • AND displays each managed tool’s installed version from the manifest
  • AND does NOT require network access
  • AND exits with code 0

Scenario: Online version check

  • WHEN user runs ddl version --check
  • THEN the system queries the latest available versions of all tools
  • AND marks outdated tools with “update available” and the new version
  • AND requires network access
  • AND exits with code 0 if all up to date, code 1 if any outdated

Scenario: Version as JSON

  • WHEN user runs ddl version --json
  • THEN the system outputs JSON following the format defined in cli-core/spec.md
  • AND includes ddl version and managed tool versions
  • AND works offline (reads from manifest)

Scenario: Version check fails (no network)

  • WHEN user runs ddl version --check and network is unavailable
  • THEN the system displays installed versions from manifest
  • AND reports: “⚠ Network unavailable — showing cached versions”
  • AND exits with code 0

Requirement: Upgrade Command

The CLI SHALL provide ddl upgrade to update all managed tools to their latest compatible versions.

Scenario: Upgrade all tools

  • WHEN user runs ddl upgrade
  • THEN the system reads .ddl/manifest.json
  • AND fetches the latest compatibility matrix
  • AND for each tool, calls the appropriate upgrade method:
    • brew-installed: brew upgrade <formula>
    • cargo-installed: cargo install <crate>
    • binary-downloaded: re-downloads binary from GitHub releases
    • scoop-installed: scoop update <app>
  • AND verifies the new version
  • AND updates .ddl/manifest.json
  • AND reports which tools were upgraded and which were already up to date

Scenario: Upgrade single tool

  • WHEN user runs ddl upgrade wai
  • THEN the system upgrades only the specified tool using the appropriate upgrade method

Scenario: Upgrade binary-downloaded tool

  • WHEN user runs ddl upgrade and a tool was installed via binary download
  • THEN ddl fetches the latest release tag from the tool’s GitHub repo
  • AND downloads the binary for the current platform
  • AND replaces the existing binary
  • AND updates .ddl/manifest.json

Scenario: No updates available

  • WHEN user runs ddl upgrade and all tools are at their latest versions
  • THEN the system reports “All tools are up to date”
  • AND exits with code 0

Scenario: Upgrade fails

  • WHEN an upgrade command fails for a tool
  • THEN ddl reports the failure with the error output
  • AND continues upgrading remaining tools
  • AND exits with code 1 (partial failure)

Scenario: Upgrade requires network

  • WHEN user runs ddl upgrade and network is unavailable
  • THEN the system reports “⚠ Network unavailable — cannot upgrade”
  • AND suggests ddl version to view installed versions
  • AND exits with code 1

Requirement: Compatibility Matrix

The CLI SHALL use a compatibility matrix to prevent installing incompatible tool versions.

Scenario: Dynamic matrix fetch

  • WHEN user runs ddl install, ddl upgrade, or ddl version --check
  • THEN the system fetches the compatibility matrix from https://charly-vibes.github.io/ddl/compatibility.json
  • AND caches it in .ddl/compatibility-cache.json for offline use
  • AND falls back to the embedded matrix if the fetch fails

Scenario: Compatible version

  • WHEN user runs ddl install wai and the latest wai version is within the compatible range
  • THEN installation proceeds normally

Scenario: Incompatible version

  • WHEN user runs ddl install wai and the latest wai version is outside the compatible range
  • THEN the system refuses to install
  • AND displays a message: “wai version X.Y.Z is not compatible with this version of ddl. Please upgrade ddl first.”
  • AND suggests ddl upgrade (to upgrade ddl itself)

Scenario: Embedded fallback matrix

  • WHEN the dynamic matrix fetch fails (no network, server down)
  • THEN the system uses the embedded fallback matrix shipped with the ddl binary
  • AND reports: “⚠ Using cached compatibility matrix — some versions may be outdated”
  • AND proceeds with installation

Scenario: Compatibility matrix structure

  • WHEN the compatibility matrix is fetched or read from cache
  • THEN it contains version constraints for each managed tool (e.g., "wai": ">=2026.3.0")
  • AND is updated independently of ddl releases (with each tool release)