The Dogfood Gate
Purpose #
Explain the byte-compare invariant: from Phase 8 onward, @aidokit/adapter-claude-code must emit a .claude/ tree byte-identical to this repo's hand-built .claude/. It is the strongest integration test we have.
Big Picture #
Source: .docs/ARCHITECTURE.md §20 and CLAUDE.md §10.
The bootstrap problem: aidokit cannot use aidokit init to set up the aidokit repo until aidokit init works. So Phases 0–7 are hand-built. End of Phase 7, the maintainer runs:
mkdir aidokit-dogfood && cd aidokit-dogfood
git init
npx ../aidokit init --adapter claude-code --stack node-ts
diff -r ../aidokit/.claude ./.claude
The diff must be empty (or only contain documented intentional differences). From Phase 8 onward, this becomes a permanent CI gate. Every change to @aidokit/adapter-claude-code that diverges from the hand-built reference is a failure to keep the dogfood diff clean.
How It Works #
Why it works as a test #
aidokit's reason to exist is "scaffold a working workflow." This repo is a working workflow. If the adapter cannot reproduce it byte-for-byte, the adapter is broken — regardless of what unit tests say. The whole class of "looks correct in tests but emits wrong in practice" failures is caught by this single diff.
What the reference is #
The hand-built .claude/ at the root of this repo. As of v1.0-rc, that directory holds the seven roles, three slash commands, 19 hook scripts, five output styles, eight schemas, and 18+ base skills exactly as documented in .docs/context/claude-workflow-kit-v4-base-part-3.md.
The v4 base parts plus the on-disk .claude/ are the reference. The adapter's job is to emit a tree that matches.
Fail-loud byte compare #
Per tie up loose ends commit 8330fde, the byte-compare is fail-loud — any difference fails the build. Documented intentional differences (e.g. {{projectName}} interpolation differences when the dogfood project's name differs from aidokit) are handled by parameterising the compare.
What is NOT covered by the gate #
docs/specs/**andagent-artifacts/**— those are project content, not adapter-owned files.aidokit syncanddoctornever touch them either (.docs/ARCHITECTURE.md §10).CHANGELOG.mdand the Beads DB — same.- Per-OS path / line-ending differences — the CLI normalises to LF and forward slashes (cli-reference §5.3).
Where the gate fits in CI #
The integration test that runs the byte-compare lives in packages/cli/test/integration/init-emit.test.ts (today asserts on structure; the literal byte-compare against the reference snapshot lands when the reference is generated from a verified build — see CHANGELOG.md [0.1.0-alpha]).
When the test fails, the failure surfaces the path and kind of divergence (template-updated vs user-modified vs missing); the contributor regenerates the reference if the divergence is intentional, or fixes the adapter if it is not.
Example #
A contributor adds a new built-in skill to @aidokit/base-skills:
- Update
packages/base-skills/src/files/skills/<id>.md+src/skills.ts. pnpm buildregeneratesdist/files/.pnpm testruns the integration test; the emitted tree now has an extra skill file.- The dogfood compare fails because the hand-built
.claude/skills/doesn't have the new skill. - The contributor hand-adds the skill to the repo's
.claude/skills/<id>/SKILL.md. - The compare passes.
Both directions are kept in sync deliberately. The hand-built .claude/ is the source of truth; the adapter must emit it.
Diagram #
%%{init: {
"theme": "base",
"themeVariables": {
"fontFamily": "ui-sans-serif, system-ui, -apple-system, Segoe UI, sans-serif",
"fontSize": "14px",
"primaryColor": "#eff6ff",
"primaryTextColor": "#0f172a",
"primaryBorderColor": "#2563eb",
"lineColor": "#475569",
"secondaryColor": "#f1f5f9",
"tertiaryColor": "#ffffff",
"clusterBkg": "#f8fafc",
"clusterBorder": "#cbd5e1"
}
}}%%
flowchart LR
classDef actor fill:#ede9fe,stroke:#6d28d9,color:#1e1b4b,stroke-width:1.2px;
classDef cli fill:#dbeafe,stroke:#1d4ed8,color:#0c1f4a,stroke-width:1.4px;
classDef adapter fill:#cffafe,stroke:#0e7490,color:#083344;
classDef pack fill:#dcfce7,stroke:#15803d,color:#052e16;
classDef core fill:#fef9c3,stroke:#a16207,color:#422006;
classDef artifact fill:#f1f5f9,stroke:#475569,color:#0f172a;
classDef stop fill:#fee2e2,stroke:#b91c1c,color:#7f1d1d,stroke-dasharray:4 3;
classDef ok fill:#ecfdf5,stroke:#047857,color:#064e3b;
classDef external fill:#fff7ed,stroke:#c2410c,color:#431407;
src["packages/adapter-claude-code/src/files/"]:::adapter
bs["packages/base-skills/src/files/"]:::core
cli["@aidokit/cli init (in temp dir)"]:::cli
emitted["temp/.claude/"]:::artifact
hand["aidokit/.claude/ (hand-built reference)"]:::artifact
diff{{"diff -r ../.claude ./.claude"}}:::stop
src --> cli
bs --> cli
cli --> emitted
hand --> diff
emitted --> diff
diff -->|empty| pass["✔ dogfood clean"]:::ok
diff -->|non-empty| fail["✘ CI fails
fix adapter OR hand-built"]:::stop
Common Mistakes #
- Editing
.claude/in the repo but not the adapter'ssrc/files/. The gate will fail on the next CI run. Always keep both in sync — but think of the hand-built.claude/as the canonical content and the adapter as the emitter. - Editing
dist/files/directly. That directory is regenerated byscripts/copy-files.mjsfromsrc/files/. - Adding a non-deterministic field to a template (timestamp, random id). Breaks determinism, which breaks the dogfood compare. See .docs/ARCHITECTURE.md §11.2.
- Skipping the dogfood compare in CI to ship a change quickly. The whole class of regression it catches is the reason it's a permanent gate.
Checklist before merging a change to @aidokit/adapter-claude-code or @aidokit/base-skills #
- [ ] Edited
src/files/, notdist/files/. - [ ]
pnpm buildre-ran the copy-files step. - [ ] Integration test (
packages/cli/test/integration/init-emit.test.ts) is green. - [ ] If you changed an emitted file, you also updated the corresponding file in this repo's
.claude/. - [ ] Dogfood byte-compare is clean (locally or in CI).