Architecture
System design, package map, and key flows for
aidokit.
Table of contents
- System overview
- Design principles
- Repository layout
- Package responsibilities
- The Adapter contract
- The Stack-pack contract
- ProjectContext — the universal carrier
- Data flow:
aidokit init - Data flow:
aidokit doctor - Data flow:
aidokit sync - File emission pipeline
- MCP catalog model
- Prereq check model
- Stack detection model
- Conformance model
- Error model and exit codes
- Versioning strategy
- Testing strategy
- Security model (overview)
- Bootstrap and dogfooding
- Open architectural questions
- See also
1. System overview
aidokit has three logical layers. Each layer depends only on the layer below it. Adapters and stack packs are siblings — both extend the core, but neither depends on the other.
┌──────────────────────────────────────────────────────────────┐
│ Layer 3 — CLI Shell │
│ ─────────────────── │
│ @aidokit/cli │
│ • Commands: init, doctor, sync, migrate, mcp, skills, new │
│ • Interactive prompts, output formatting, exit codes │
│ • Orchestrates everything below │
└────────────────────────┬─────────────────────────────────────┘
│
┌───────────────┴───────────────┐
│ │
▼ ▼
┌──────────────────────┐ ┌─────────────────────────┐
│ Layer 2a — Adapters │ │ Layer 2b — Stack Packs │
│ ────────────────── │ │ ───────────────────── │
│ @aidokit/adapter- │ │ @aidokit/stack-pack- │
│ codex │ │ node-ts │
│ copilot │ │ python │
│ claude-code │ │ react │
│ │ │ go │
│ Target: │ │ Target: │
│ CLI runtimes │ │ project conventions │
└──────────┬───────────┘ └────────────┬────────────┘
│ │
└───────────────┬───────────────┘
▼
┌──────────────────────────────────────────────────────────────┐
│ Layer 1 — Core + Utilities │
│ ────────────────────────── │
│ @aidokit/core — contracts, types, schemas │
│ @aidokit/shared-docs — docs/ and agent-artifacts/ templates │
│ @aidokit/stack-detect — detection of project stacks │
│ @aidokit/prereq-check — host environment checks │
│ @aidokit/mcp-catalog — MCP definitions and installers │
└──────────────────────────────────────────────────────────────┘
What each layer does
| Layer | Concerns | Knows about |
|---|---|---|
| Core | Contracts, types, schemas, shared templates, detection, catalogs | Nothing above |
| Adapters | How to emit one CLI's engine directory and agent rules file | Core only |
| Stack Packs | How to detect a stack and what skills/MCPs/validation suit it | Core only |
| CLI Shell | User interaction, command surface, orchestration | All of the above |
What aidokit is not
- Not a runtime. Emitted files are read by Codex, Copilot, Claude Code, etc.
aidokititself stops running afterinitcompletes. - Not an LLM client. Zero API calls. All model interaction happens in the user's chosen CLI.
- Not a package manager. It calls
npm,pnpm,brew,claude mcp add, etc., but never reimplements them. - Not a daemon. Every command is short-lived.
v1.0 additions (2026-05-25)
The structural picture above is unchanged at v1.0; what landed is an expansion of the surface within those layers. New components, in their respective layers (see §3 for the package map):
-
CLI surface (Layer 3). New commands:
aidokit verify,aidokit verify --capabilities,aidokit manifest --verify-capabilities,aidokit add adapter <name>,aidokit doctor --drift,aidokit metrics summary | export(opt-in viaAIDOKIT_METRICS=1),aidokit audit export --format soc2 | eu-ai-act. Theaidokit initflag--tier <starter|standard|strict>is the preferred way to choose workflow intensity;--conformancekeeps working as a back-compat alias. -
Core (Layer 1).
@aidokit/coreexports:TierName+tierToConformance/conformanceToTier(user-facing alias layer overConformanceLevel); theMetricsLogmodule (appendMetricsEvent,readMetricsEvents,summariseMetrics,filterByWindow,validateEvent); theMetricsEventdiscriminated union over five kinds (scope.violation,context.reload,task.started,task.completed,task.tokens). Seepackages/core/src/{tier,metrics}.ts. -
Adapter (Layer 2). Tier-aware emission:
agent-rules-starter.md(a vocabulary-suppressed CLAUDE.md template used at Starter);verbsForConformance(ctx)emits/start+/shipat Starter or the canonical three at Standard/Strict;baseSkillsForTier()suppresses v4 base skills at Starter. -
Strict-tier artifact.
.aidokit/capabilities.json(see ADR-0017) is emitted by the CLI from every declared adapter'smanifest.capabilities, projected into a single project-wide JSON file. Consumed by the D4 verifier and the audit export. -
Watchdog hooks. A new
lib/emit-metric.mjshelper under.claude/scripts/is invoked fromvalidate-scope.mjs,track-command-failures.mjs, andinject-active-task.mjswhenAIDOKIT_METRICS=1. Zero telemetry by default. -
Operations / CI. Six workflows now ship:
ci.yml(depth),matrix.yml(breadth — 14 combinations per-PR),matrix-snapshot.yml(nightly aggregate tohealth/matrix-status.json),publish.yml(Sigstore provenance on every@aidokit/*package),snapshot-accept.yml(weekly fixture refresh PR),quarterly-bump.yml(Q1/Q2/Q3/Q4 upstream pin checklist issue). -
User-facing distribution surfaces under
wiki/:tiers/,stacks/,compare/,hall-of-pain/,privacy/,supply-chain/,health/,devlog/,reference/adapter-capabilities.md,docs/compliance/soc2.md,docs/compliance/eu-ai-act.md.
None of the v1.0 additions change the three-layer model or the adapter/stack-pack sibling rule (see §2). They extend it.
2. Design principles
These principles guide every architectural decision. ADRs reference them when justifying choices.
2.1 Plain template files, no template engine
Adapter packages ship templates as actual files (e.g., src/files/.<adapter>/agents/builder.md — .claude/, .codex/, .copilot/ depending on the adapter). Variable substitution uses simple {{variable}} interpolation in a thin internal helper — no Handlebars, EJS, or Nunjucks. This keeps templates human-readable, diffable, and reviewable as ordinary files.
2.2 Contracts as TypeScript interfaces
The Adapter and Stack-pack contracts are TypeScript interfaces in @aidokit/core. Not YAML schemas, not declarative configs. Adapters are real packages with real code that imports the interface and implements it. This gives type safety, IDE support, and forces authors to think about behavior, not just data.
2.3 Data-driven catalogs
The MCP catalog is a TypeScript const array. The prereq list is a TypeScript const array. Adding an entry is a one-file edit. No plugin discovery, no runtime registration. When the ecosystem grows enough to warrant dynamic loading (post-1.0), we revisit — until then, simplicity wins.
2.4 Zero LLM calls
aidokit never talks to an LLM. Every decision is deterministic, every output is reproducible, every CI run is hermetic. The user's chosen AI coding CLI handles all model communication.
2.5 Conformance is declarative AND verifiable
Adapters declare their conformance level in a manifest. A test harness in @aidokit/core verifies the claim. Drift between declared and actual is a CI failure. Users see the verified level when installing. The level an adapter declares reflects what its target CLI can physically support, not a ranking of adapter quality (ADR-0031).
2.6 Adapters and stack packs are siblings
They both extend the core. They compose orthogonally — one adapter + N stack packs. Neither imports the other. This is intentional: adapter authors don't need to know about stack packs, and stack-pack authors don't need to know which adapter consumes their output.
2.7 Migrations are first-class
Every breaking change in any package ships with a migration script. The user is never stuck. aidokit migrate <from>-to-<to> is a real command, not a manual process.
2.8 Read-before-write everywhere
Before writing any file, the CLI checks what's already on disk. If a file would be overwritten, the user sees a diff and confirms. --yes skips confirmation but never the diff computation — the diff goes to logs.
2.9 Fail loud, recover gracefully
Errors include the failing step, the input that caused it, and the suggested fix. The CLI never swallows errors. Partial writes are cleaned up (the CLI writes to a staging dir, then atomically moves on success).
3. Repository layout
Monorepo with pnpm workspaces + Turborepo for build orchestration.
aidokit/
├── package.json # workspace root
├── pnpm-workspace.yaml
├── turbo.json
├── tsconfig.base.json
├── .github/workflows/ # CI: test, lint, typecheck, publish
├── docs/ # this directory
│ ├── specs/
│ └── architecture/decisions/ # ADRs
├── packages/
│ ├── cli/ # @aidokit/cli
│ ├── core/ # @aidokit/core
│ ├── shared-docs/ # @aidokit/shared-docs
│ ├── stack-detect/ # @aidokit/stack-detect
│ ├── prereq-check/ # @aidokit/prereq-check
│ ├── mcp-catalog/ # @aidokit/mcp-catalog
│ ├── adapter-claude-code/ # @aidokit/adapter-claude-code
│ ├── adapter-codex/ # @aidokit/adapter-codex (v1.0+)
│ ├── adapter-copilot/ # @aidokit/adapter-copilot (v1.0+)
│ ├── stack-pack-node-ts/ # @aidokit/stack-pack-node-ts
│ ├── stack-pack-python/ # @aidokit/stack-pack-python (v0.5+)
│ ├── stack-pack-react/ # @aidokit/stack-pack-react (v1.0+)
│ └── stack-pack-go/ # @aidokit/stack-pack-go (v1.0+)
├── examples/ # example bootstrapped projects (used in CI)
└── scripts/ # repo automation
Package dependency graph (logical)
@aidokit/cli
│
┌───────────────┼────────────────┬──────────────┐
▼ ▼ ▼ ▼
@aidokit/adapter-* @aidokit/stack-pack-* @aidokit/stack- @aidokit/prereq-
detect check
│ │ │ │
└───────────────┴────────────────┴──────────────┘
│
▼
@aidokit/core
│
┌───────────────┴──────────────┐
▼ ▼
@aidokit/shared-docs @aidokit/mcp-catalog
Rules:
- Every package depends on
@aidokit/core(directly or transitively) - No adapter depends on any stack pack
- No stack pack depends on any adapter
@aidokit/cliis the only consumer of all extensions
4. Package responsibilities
@aidokit/cli
The user-facing entry point. Implements every aidokit command.
- Argument parsing (commander or oclif — TBD via ADR)
- Interactive prompts (
@inquirer/prompts— TBD via ADR) - Output formatting (
picocolors,oraspinners, optional--jsonmode) - Orchestration: composes adapter + stack-pack + core to fulfill each command
- Exit code mapping (see §16)
Public surface: the CLI itself. No exported APIs.
@aidokit/core
The contract and type library. Imported by everyone.
AdapterinterfaceStackPackinterfaceProjectContexttypeAdapterManifest,StackPackManifesttypesMCPDef,PrereqDef,SkillTemplatetypes- JSON schemas for task briefs, change summaries, etc. (consumed by
aidokit validate) - Conformance harness (
runConformanceTests(adapter, level)) - Variable interpolation helper (
interpolate(template, vars)) - File emission helpers (
writeFiles(files, projectRoot, options))
No runtime dependencies on other @aidokit/* packages. This package can be installed standalone by third-party adapter authors.
@aidokit/shared-docs
Template files for everything not adapter-specific.
docs/skeleton (architecture, decisions, specs templates)agent-artifacts/skeletonCHANGELOG.mdstarter.gitignoreentries for the workflow- ADR template (
0000-template.md) - Spec template, task brief template, change summary template, etc.
Pure data; no executable code beyond a manifest exporter.
@aidokit/stack-detect
Detects the project's tech stack from manifest files and conventions.
- One detector per stack family (Node, Python, Go, Rust, Java, Ruby, PHP — added incrementally)
- Each detector returns a
DetectionResultwith confidence (high / medium / low / none) - Composable: a project can match multiple stacks (e.g., Node + React)
- Used by
aidokit initto pick which stack packs to suggest
@aidokit/prereq-check
Checks whether host prerequisites are installed.
- One checker per prereq (Node, Git, Codex, Copilot, Claude Code, Beads, …)
- Each checker returns
{ installed: boolean, version?: string, location?: string } - Per-OS install instruction lookup (macOS+Homebrew, Linux+apt/dnf, Windows+scoop/winget, manual)
- Never installs anything itself
@aidokit/mcp-catalog
The MCP server catalog and installation glue.
- A
constarray ofMCPDefentries - Trigger evaluator (decides which MCPs to suggest given a
ProjectContext) - Per-adapter install commands (e.g.
claude mcp add …for claude-code; equivalent shapes for codex and copilot) - Role-scoping logic (which roles get which MCP)
@aidokit/adapter-claude-code
Emits a complete .claude/ engine directory plus CLAUDE.md.
- Template files under
src/files/mirror the target layout - Implements the
Adapterinterface adapter.mdmanifest declares conformance (Strict — Claude Code exposes deterministic hooks + output styles)- Postinstall hooks:
chmod +xfor.mjsscripts,bd initif Beads enabled
@aidokit/adapter-codex
Same role as the Claude Code adapter, for Codex CLI. Ships v1.0.
- Different engine directory (
.codex/) - Different agent rules file (
AGENTS.md) - Minimum conformance: Codex CLI exposes no hook mechanism, so capability enforcement uses sandbox mode + approval policy and prompt-level cooperation rather than deterministic hooks
- Different MCP install mechanism
@aidokit/adapter-copilot
Same role as the Claude Code adapter, for GitHub Copilot CLI. Ships v1.0.
- Different engine directory (
.copilot/) - Different agent rules file (
.github/copilot-instructions.md) - Minimum conformance: Copilot CLI exposes neither a hook mechanism nor output styles, so watchdog stop-conditions are prompt-level (model cooperation), not deterministically enforced
- Different MCP install mechanism
@aidokit/stack-pack-node-ts (and siblings)
Stack-aware extension that ships:
- Detection function
- 3–6 stack-specific skill templates (with
<REVIEW REQUIRED>placeholders) - MCP suggestions (which MCPs from the catalog suit this stack)
- Default validation commands (lint, typecheck, test)
- A manifest declaring what it provides
5. The Adapter contract
The central abstraction. Full TypeScript definitions live in docs/specs/adapter-contract.md; the shape below is illustrative.
5.1 Why a TypeScript interface
Earlier drafts considered a YAML manifest where the adapter only declared its capabilities. That fails because some operations (variable interpolation choices, postinstall ordering, runtime conformance verification) need real logic. A TypeScript interface is the smallest abstraction that supports both data and behavior.
5.2 Interface shape (sketch)
export interface Adapter {
// Static identity
readonly manifest: AdapterManifest;
// File emission
emitAgentRulesFile(ctx: ProjectContext): EmittedFile;
emitEngineDirectory(ctx: ProjectContext): EmittedFile[];
emitSkills(ctx: ProjectContext, skills: SkillTemplate[]): EmittedFile[];
emitWatchdog(ctx: ProjectContext): EmittedFile[];
// MCP integration
installMCP(mcp: MCPDef, roles: RoleName[]): ShellCommand[];
removeMCP(mcpId: string): ShellCommand[];
listInstalledMCPs(ctx: ProjectContext): Promise<InstalledMCP[]>;
// Lifecycle
postInstall(ctx: ProjectContext): ShellCommand[];
preSync(ctx: ProjectContext): SyncPlan;
doctor(ctx: ProjectContext): DoctorCheck[];
// Conformance
declaredConformance: 'minimum' | 'standard' | 'strict';
}
export interface AdapterManifest {
name: string; // e.g. "claude-code", "codex", "copilot"
cliTarget: string; // e.g. "Claude Code v2.1.32+" (adapter-specific)
specVersion: '2.0';
sdkVersion: string; // semver
conformance: 'minimum' | 'standard' | 'strict';
capabilityDeclarations: {
writesPaths: string[]; // glob patterns, e.g. [".claude/**", "CLAUDE.md"]
runsShellCommands: string[]; // e.g. ["claude mcp add", "chmod"]
networkCalls: string[]; // e.g. ["registry.npmjs.org"]
};
}
5.3 Lifecycle hooks
| Hook | When called | Returns |
|---|---|---|
emitAgentRulesFile |
During init and sync |
The CLAUDE.md or AGENTS.md file |
emitEngineDirectory |
During init and sync |
The .claude/** or .codex/** files |
emitSkills |
After stack pack provides skill list | Skill files for the engine dir |
emitWatchdog |
During init |
Watchdog scripts (shape varies by adapter; see §19) |
installMCP |
During mcp add / init |
Shell commands to execute |
postInstall |
After all files written | Cleanup / init commands |
preSync |
Before aidokit sync writes |
Diff plan for user confirmation |
doctor |
During aidokit doctor |
Health check results |
5.4 Conformance declaration
Each adapter declares its conformance level in the manifest. The @aidokit/core harness runs against the adapter and verifies:
- Minimum — all MUST requirements from spec §17.1 are implemented
- Standard — Minimum + all SHOULD requirements from §17.2
- Strict — Standard + all MAY requirements from §17.3
If the harness fails to verify a declared level, the adapter's CI is red and aidokit doctor warns users.
See docs/specs/conformance-levels.md for the full checklist per level.
6. The Stack-pack contract
Sibling of the Adapter contract. Full shape in docs/specs/stack-pack-contract.md.
6.1 Shape (sketch)
export interface StackPack {
readonly manifest: StackPackManifest;
// Detection
detect(ctx: DetectContext): Promise<DetectionResult>;
// Suggestions
suggestSkills(ctx: ProjectContext): SkillTemplate[];
suggestMCPs(ctx: ProjectContext): string[]; // MCP ids from catalog
suggestValidationCommands(ctx: ProjectContext): string[];
// Defaults
defaultArchitecturePattern?(ctx: ProjectContext): ArchitecturePatternHint;
}
export interface StackPackManifest {
name: string; // "node-ts"
displayName: string; // "Node.js + TypeScript"
languages: string[]; // ["typescript", "javascript"]
specVersion: '2.0';
conformance: 'minimum' | 'standard' | 'strict';
}
6.2 Detection composability
Multiple stack packs can match a single project. Example: a project with package.json + React imports + Next.js config triggers all three (Node-TS, React, Next.js if/when it exists). The user picks which to enable; defaults follow confidence ranking.
6.3 Why separate from Adapter
Adapters target CLI runtimes — they emit engine directories and configure capability profiles. Stack packs target project conventions — they tell the workflow what Django looks like, what React patterns are standard, what testing framework is used.
A Django project on Claude Code uses the Django stack pack + Claude Code adapter. The same Django project could later add the Codex adapter without touching the Django stack pack. Orthogonal extension.
7. ProjectContext — the universal carrier
Every adapter method, every stack-pack method, every MCP trigger evaluator receives a ProjectContext. It is the single source of truth for "what does this project look like right now."
export interface ProjectContext {
// Filesystem
projectRoot: string;
agentRulesFilePath: string; // resolved per adapter
engineDirectoryPath: string; // resolved per adapter
// Identity
projectName: string;
packageManagerLockfile?: string; // detected: pnpm-lock.yaml, etc.
// Detection results
stack: StackInfo; // from @aidokit/stack-detect
brownfield: boolean;
sourceDocs: SourceDoc[]; // if brownfield
// User selections
adapterName: string;
conformance: 'minimum' | 'standard' | 'strict';
beadsEnabled: boolean;
selectedStackPacks: string[];
selectedMCPs: string[];
// Host
os: 'darwin' | 'linux' | 'win32';
arch: 'x64' | 'arm64';
installedPrereqs: PrereqStatus[];
// Manifest of the running tool
aidoCliVersion: string;
specVersion: '2.0';
// User-provided knobs
options: Record<string, unknown>;
}
ProjectContext is constructed once at the start of every command run and passed (read-only) through the entire flow. No method mutates it; if a step learns something new, it returns a delta that the orchestrator merges before the next step.
8. Data flow: aidokit init
The canonical command. Walk through it step-by-step.
┌──────────────────────────────────────────────────────────────┐
│ Step 1 — Parse args │
│ @aidokit/cli │
│ • Read CLI flags │
│ • Pre-populate options for non-interactive mode │
└────────────────────────┬─────────────────────────────────────┘
▼
┌──────────────────────────────────────────────────────────────┐
│ Step 2 — Detect environment │
│ @aidokit/prereq-check + @aidokit/stack-detect │
│ • Probe host (Node version, Git, OS, package managers) │
│ • Scan project root (package.json, pyproject.toml, …) │
│ • Detect brownfield (existing PRD/BRD/code) │
└────────────────────────┬─────────────────────────────────────┘
▼
┌──────────────────────────────────────────────────────────────┐
│ Step 3 — Resolve missing prereqs │
│ @aidokit/cli │
│ • If prereqs missing, print install commands per OS │
│ • Pause: ask user to install, then continue (or exit) │
└────────────────────────┬─────────────────────────────────────┘
▼
┌──────────────────────────────────────────────────────────────┐
│ Step 4 — Interactive selection │
│ @aidokit/cli │
│ • Adapter (no default — user is prompted, or `--adapter` │
│ is required with `--yes`; see ADR-0016) │
│ • Conformance level │
│ • Stack packs (from detection + suggestions) │
│ • MCP servers (from catalog + stack-pack suggestions) │
│ • Beads opt-in │
└────────────────────────┬─────────────────────────────────────┘
▼
┌──────────────────────────────────────────────────────────────┐
│ Step 5 — Build ProjectContext │
│ @aidokit/core │
│ • Merge detection + selections │
│ • Freeze (immutable from here) │
└────────────────────────┬─────────────────────────────────────┘
▼
┌──────────────────────────────────────────────────────────────┐
│ Step 6 — Compute file plan │
│ Adapter.emitAgentRulesFile, emitEngineDirectory, emitSkills │
│ + @aidokit/shared-docs │
│ + StackPack.suggestSkills (output merged into emitSkills) │
│ → list of (path, content) tuples; nothing written yet │
└────────────────────────┬─────────────────────────────────────┘
▼
┌──────────────────────────────────────────────────────────────┐
│ Step 7 — Dry-run / confirm │
│ @aidokit/cli │
│ • Show file plan summary (counts, paths) │
│ • Detail mode (--verbose) shows full diff │
│ • If --dry-run, exit 0 here │
└────────────────────────┬─────────────────────────────────────┘
▼
┌──────────────────────────────────────────────────────────────┐
│ Step 8 — Write files (staged) │
│ @aidokit/core writeFiles │
│ • Write to staging dir under <projectRoot>/.aido-staging/ │
│ • Verify all writes succeeded │
│ • Atomically move into place │
└────────────────────────┬─────────────────────────────────────┘
▼
┌──────────────────────────────────────────────────────────────┐
│ Step 9 — Install MCP servers │
│ Adapter.installMCP per selected MCP │
│ • Execute shell commands (claude mcp add, etc.) │
│ • Update agent role frontmatter with mcpServers scoping │
└────────────────────────┬─────────────────────────────────────┘
▼
┌──────────────────────────────────────────────────────────────┐
│ Step 10 — Post-install │
│ Adapter.postInstall │
│ • chmod +x hook scripts │
│ • bd init (if Beads enabled) │
│ • Append .gitignore entries │
└────────────────────────┬─────────────────────────────────────┘
▼
┌──────────────────────────────────────────────────────────────┐
│ Step 11 — Write adapter.md manifest │
│ @aidokit/core │
│ • Capture adapter name, version, conformance, spec version │
│ • Stored at <projectRoot>/.aido/adapter.md │
└────────────────────────┬─────────────────────────────────────┘
▼
┌──────────────────────────────────────────────────────────────┐
│ Step 12 — Final report │
│ @aidokit/cli │
│ • Print summary (files, MCPs, next steps) │
│ • Exit 0 │
└──────────────────────────────────────────────────────────────┘
Any step that fails:
- Aborts before Step 8 → no state changes
- Aborts after Step 8 → staging dir cleaned up; partial changes rolled back where possible
- Exits with non-zero code (see §16)
9. Data flow: aidokit doctor
Read-only health check. Never writes.
1. Read <projectRoot>/.aido/adapter.md
2. Verify declared adapter is installed and importable
3. Run Adapter.doctor(ctx) → list of DoctorCheck results
4. Run @aidokit/prereq-check against current host
5. Verify MCP scoping in agent frontmatter matches recorded selections
6. Verify all required skill files are present
7. Verify Beads database exists (if enabled)
8. Run conformance harness against the installed adapter
9. Print pass/fail summary
10. Exit 0 if all pass, 1 if any fail
Each check is independent — aidokit doctor always runs all of them and reports all failures, never short-circuits on the first failure.
10. Data flow: aidokit sync
Re-emits files from current adapter templates without touching the project's actual data.
1. Read adapter.md → load the same adapter version (or warn if newer is available)
2. Build ProjectContext from current state
3. Compute fresh file plan (Step 6 from `aidokit init`)
4. Diff against on-disk files
5. Classify each file:
• Identical → skip
• Template-updated → propose update
• User-modified → propose update with conflict warning
• Removed in template → propose deletion
• New in template → propose creation
6. Show summary; --verbose shows full diffs
7. Wait for confirmation (unless --yes)
8. Apply approved changes via staged write
9. Exit 0
aidokit sync never touches: docs/specs/** (project content), agent-artifacts/** (workflow artifacts), CHANGELOG.md, Beads DB. Only adapter-owned and shared-docs-owned files are eligible.
11. File emission pipeline
Templates → interpolation → write to disk. Same path for init and sync.
11.1 Templates as actual files
packages/adapter-claude-code/src/files/
├── CLAUDE.md
└── .claude/
├── settings.json
├── commands/
│ ├── intake.md
│ ├── implement-task.md
│ └── orchestrator-next.md
├── agents/
│ ├── researcher.md
│ ├── architect.md
│ ├── planner.md
│ ├── builder.md
│ ├── tester-reviewer.md
│ └── maintainer.md
├── skills/
│ └── … (18 base skills)
└── scripts/
└── … (hook scripts)
Files are stored at their final path under src/files/. The build step copies them into the published package; emission is a tree copy with interpolation.
11.2 Variable interpolation
A thin internal helper:
function interpolate(template: string, vars: Record<string, string>): string {
return template.replace(/\{\{(\w+)\}\}/g, (_, key) => {
if (!(key in vars)) throw new Error(`Unknown variable: ${key}`);
return vars[key];
});
}
Variables in templates:
# {{projectName}}
This project uses aidokit v{{kitVersion}}.
Variable set is small and documented per package (typically: projectName, kitVersion, specVersion, aidoCliVersion). Anything more complex (conditional sections, loops) means the file should be generated programmatically by the adapter, not templated.
11.3 Staged writes
Why staged: an init partway through is worse than no init at all. The pipeline:
- Resolve final tree in memory
- Write entire tree to
<projectRoot>/.aido-staging/ - On success, atomically rename each file into place (or
fs.cpthen unlink staging) - On failure, delete staging; project root is unchanged
For files that conflict with existing user content (which sync reveals), the user confirms before merge.
11.4 Conflict handling
| Situation | Behavior |
|---|---|
| Target file does not exist | Write |
| Target identical to template | Skip (no-op) |
| Target differs but adapter recognizes it | Propose update; show diff |
| Target differs and is user-owned (CLAUDE.md) | Skip; warn user to merge manually |
| Target is binary or unrecognized | Abort with error |
12. MCP catalog model
MCPs are external services that extend agent capabilities (docs lookup, browser automation, task graph access, etc.). The catalog is data, not plugins.
12.1 Catalog shape
export const MCP_CATALOG: MCPDef[] = [
{
id: 'context7',
name: 'Context7',
purpose: 'Up-to-date library documentation lookup',
suggestedFor: ['researcher', 'architect'],
triggers: ['always'],
securitySensitive: false,
// Entries are alphabetised by adapter id. There is no priority order
// between first-party adapters; see ADR-0016.
install: {
'claude-code': { command: 'claude mcp add context7 -- npx -y @upstash/context7-mcp' },
codex: {
configBlock:
'[mcp_servers.context7]\ncommand = "npx"\nargs = ["-y", "@upstash/context7-mcp"]',
},
copilot: {
// TODO(copilot): real MCP registration command not yet documented in-repo
},
},
},
// … more entries
];
12.2 Trigger evaluation
triggers is an array of strings interpreted against the ProjectContext:
'always'— always suggest'stack.hasFrontend'— suggest if a detected stack includes a frontend'detect.githubRemote'— suggest ifgit remote -vshows GitHub'beadsEnabled'— suggest if Beads is enabled'never'— never auto-suggest; explicit opt-in only
The evaluator is a small predicate engine, not a full expression language.
12.3 Role scoping
suggestedFor is non-empty for every catalog entry except security-sensitive ones (filesystem, shell). After install, the adapter writes mcpServers: into each suggested role's frontmatter. No role gets every MCP.
12.4 Security-sensitive MCPs
Marked securitySensitive: true. Never auto-suggested. Require explicit user confirmation with a warning even when added manually via aido mcp add.
Full catalog and rules: docs/specs/mcp-catalog.md.
13. Prereq check model
13.1 Per-prereq definition
export const PREREQS: PrereqDef[] = [
{
id: 'node',
name: 'Node.js',
required: true,
minVersion: '20.0.0',
detect: async () => {
const result = await exec('node --version');
return { installed: true, version: result.stdout.trim() };
},
install: {
darwin: { homebrew: 'brew install node@20', manual: 'https://nodejs.org' },
linux: { apt: 'sudo apt install nodejs', manual: 'https://nodejs.org' },
win32: { scoop: 'scoop install nodejs', winget: 'winget install OpenJS.NodeJS' },
},
},
// …
];
13.2 Reporting
After detection, the CLI shows a table (the adapter-CLI row varies by which adapter the user selected — claude-code shown here as one example; codex and copilot produce equivalent rows for their own CLIs):
✔ Node.js 20.11.0
✔ Git 2.43.0
✘ Claude Code not found → install: curl -fsSL claude.ai/install.sh | bash
✘ Beads CLI not found → install: brew install steveyegge/beads/beads
13.3 No auto-install
The CLI never runs install commands without explicit confirmation. Even with --yes, only confirms aidokit-internal operations; prereq installs require a separate explicit confirmation. ADR-0008 codifies this.
14. Stack detection model
14.1 Detection result
export interface DetectionResult {
matched: boolean;
confidence: 'high' | 'medium' | 'low' | 'none';
extras?: Record<string, unknown>; // pack-specific signals
}
14.2 Composition
Each stack pack runs its detector in parallel. Results are sorted by confidence. The CLI presents matches above low to the user, defaulting to all high matches checked.
14.3 Manual override
aidokit init --stack node-ts,react skips detection for the named packs. Useful when detection is ambiguous (e.g., a monorepo with mixed stacks).
15. Conformance model
15.1 Three levels
Carried over from the orchestrator spec. A level describes what a target CLI can physically support, not a ranking of adapter quality (ADR-0031). An adapter declares the highest level its target CLI can honestly back, so the declared level is a consequence of the platform's primitives — not a grade:
- Minimum — All §17.1 MUSTs. Suitable for CLIs with basic prompt-driven workflows (e.g. codex, copilot — neither exposes hooks or output styles).
- Standard — Minimum + all §17.2 SHOULDs. Subagents, repair caps, monitoring.
- Strict — Standard + all §17.3 MAYs. Workspace isolation, output styles, advanced primitives (e.g. claude-code, whose target CLI exposes deterministic hooks and output styles).
The first-party adapters declaring minimum (codex, copilot) and strict (claude-code) differ only because their target CLIs differ in capability, not in completeness. See docs/specs/conformance-levels.md §3 for the per-adapter capability matrix.
15.2 Declaration vs verification
Adapters declare a level in their manifest. The harness in @aidokit/core verifies the claim by running level-specific test cases against the adapter implementation:
runConformanceTests(adapter: Adapter, level: 'minimum'|'standard'|'strict'): TestResult[];
The harness runs as part of the adapter's CI (npm test in the adapter package), so an adapter cannot publish a claim it doesn't honor. aidokit doctor re-runs a subset on user machines as a sanity check.
15.3 Project-level conformance
The user's project also has a conformance target (recorded in <projectRoot>/.aido/adapter.md). It must be <= adapter's declared level. A project at Strict requires an adapter that declares Strict.
16. Error model and exit codes
16.1 Error envelope
All errors thrown internally use a consistent shape:
export class AidoError extends Error {
constructor(
public code: string, // machine-readable, e.g. 'PREREQ_MISSING'
message: string, // human-readable
public details?: Record<string, unknown>,
public hint?: string, // suggested fix
) {
super(message);
}
}
The CLI catches all AidoErrors and formats them (claude-code shown below as
one illustrative adapter; the same envelope shape applies for codex and
copilot prereqs):
✘ Prerequisite missing: Claude Code (PREREQ_MISSING)
Detected: No `claude` command found on PATH.
Hint: Install Claude Code via:
curl -fsSL claude.ai/install.sh | bash
Or set --skip-prereq-check to bypass.
16.2 Exit codes
| Code | Meaning |
|---|---|
| 0 | Success |
| 1 | Generic error (uncaught exception, network failure) |
| 2 | Bad invocation (unknown flag, conflicting options) |
| 10 | Prerequisite missing |
| 11 | Adapter not found or failed to load |
| 12 | Stack pack not found or failed to load |
| 13 | Conformance check failed |
| 20 | File write failed |
| 21 | MCP install failed |
| 30 | Migration failed |
| 40 | User cancelled |
Full list in docs/specs/cli-reference.md.
16.3 --json mode
Every error in --json mode emits:
{
"ok": false,
"error": {
"code": "PREREQ_MISSING",
"message": "Prerequisite missing: Claude Code",
"details": { "prereq": "claude-code" },
"hint": "Install Claude Code via: curl -fsSL claude.ai/install.sh | bash"
}
}
17. Versioning strategy
17.1 Three independent semvers
- Spec version — the AI Dev Orchestrator Spec (currently v2.0). Bumped only when the underlying workflow contract changes.
@aidokit/coreversion — SDK version. Bumped on every contract / type change.- Per-package versions — each
@aidokit/*package independently semvered.
17.2 Adapter manifest declares compatibility
spec-version: 2.0
sdk-version: ^1.4
conformance: standard
cli-target: 'Claude Code >=2.1.32' # adapter-specific; codex / copilot manifests declare their own
A user installing an adapter sees compatibility flags up front. Incompatible combinations are refused at install time.
17.3 Pre-1.0 breaking changes
Until v1.0, minor version bumps may include breaking changes (with migration scripts). After v1.0, semver applies strictly — breaking changes require a major bump.
17.4 Spec versioning is separate
The spec lives in its own repository (or its own directory once the spec is extracted). Spec changes follow their own RFC process. aidokit releases reference a spec version; the spec doesn't release for aidokit's sake.
18. Testing strategy
Three tiers, each with strict purpose. Full conventions in CONTRIBUTING.md.
18.1 Unit tests (Vitest)
- Co-located with source:
src/foo.ts↔src/foo.test.ts - Pure logic only: interpolation, schema validation, manifest parsing
- No filesystem, no network, no shell
18.2 Integration tests
- Spin up a temp directory; run a real command (
aidokit init …) against it - Assert on emitted file tree, manifest content, exit code
- No network; mock CLI tools where needed (e.g. each adapter's MCP-install command —
claude mcp add,codexconfig writes,copilotequivalents — is stubbed) - Live in
packages/*/test/integration/
18.3 End-to-end tests
- Only run in CI on the
e2ejob - Use a real install of each supported CLI (matrix: codex, copilot, claude-code × macOS, Linux)
- Bootstrap a temp project, invoke the adapter's intake entry point (e.g.
claude /intake "test"for claude-code), assert intake artifacts are produced - Slow; gated behind
pnpm test:e2e
18.4 Byte-compare reference tests
Each first-party adapter has its own reference snapshot — one per adapter, none privileged over the others (ADR-0031). The claude-code snapshot is derived from the v4 kit material; codex and copilot have their own snapshots. After every change, integration tests diff each emitted tree against its snapshot. Intentional changes update the affected snapshot; unintentional drift fails CI.
18.5 Coverage
Targets:
@aidokit/core: 90%+ (pure logic, easy to test)- Adapters: 80%+ (some shell/exec branches harder to test)
@aidokit/cli: 70%+ (interactive paths are partially tested)
19. Security model (overview)
Full threat model and mitigations: docs/specs/security-model.md. Outline only here.
19.1 Trust boundaries
aidokitcore + first-party packages — trusted (signed npm packages from v1.0)- Third-party adapters / stack packs — untrusted by default; conformance harness + capability declarations create trust signals
- MCP servers — untrusted;
securitySensitiveflag and explicit opt-in for high-risk servers - The user's project —
aidokitnever reads or transmits project content; it only writes the workflow scaffold
19.2 Key controls
- Dry-run installs —
--dry-runshows every file change without writing - Capability declarations — adapters declare in their manifest which paths they write, which shell commands they run, which network calls they make
- Signed packages — npm provenance attestations for all
@aidokit/*packages - No auto-installs — every install command is shown to the user before running
- No telemetry — zero network calls from
aidokititself (excluding npm install)
20. Bootstrap and dogfooding
20.1 The bootstrap problem
aidokit can't use aidokit init to set up the aidokit repo until aidokit init works. Phases 0–7 are hand-built.
20.2 The dogfood gates
Every first-party adapter has a permanent dogfood gate. Each gate compares the
adapter's emitted file plan, byte-for-byte, against a committed reference
fixture produced from a fixed ProjectContext. Three gates run today:
| Adapter | Conformance | Fixture |
|---|---|---|
codex |
Minimum | packages/cli/test/fixtures/codex-reference/ |
copilot |
Minimum | packages/cli/test/fixtures/copilot-reference/ |
claude-code |
Strict | packages/cli/test/fixtures/claude-code-reference/ |
All three are generated from the same
packages/cli/test/integration/reference-context.json, paired with
@aidokit/stack-pack-node-ts, so the only variable between fixtures is the
adapter under test. The generator is scripts/generate-reference-snapshot.mjs;
the assertions live in byte-compare.test.ts (claude-code) and
byte-compare-multi-adapter.test.ts (codex + copilot). Both run under the
standard pnpm test invocation that CI already executes — no separate
workflow is required.
Regenerating a fixture (after an intentional adapter or kit change):
pnpm -r build
node scripts/generate-reference-snapshot.mjs --adapter <claude-code|codex|copilot>
git diff packages/cli/test/fixtures/<name>-reference # review before committing
An unreviewed regeneration silently launders a regression past the gate, so the regeneration commit must include the diff in its description.
20.3 Why this matters
This is the strongest integration test we have. If aidokit init produces a
working project for one adapter, it produces this project's .codex/ /
.copilot/ / .claude/ content. If it doesn't, the next CI run catches it.
21. Open architectural questions
These are flagged unresolved. Each will be answered before the affected release ships.
| Question | Affects |
|---|---|
| Argument parser: commander vs oclif vs custom | v0.1 |
Prompts library: @inquirer/prompts vs clack vs prompts |
v0.1 |
Whether @aidokit/core should re-export zod or define its own schemas |
v0.1 |
| Whether to compile to a single CJS file or ship ESM + types only | v0.1 |
| How to surface stack-pack "best matches" when 4+ packs match | v0.5 |
Whether aidokit sync should auto-update the adapter or require explicit pnpm up |
v0.5 |
| Codex CLI's exact sandbox/approval surface at the time of v1.0 | v1.0 |
Whether to ship a standalone binary (via pkg or Node SEA) |
v1.0 |
| Marketplace location: GitHub Pages, Vercel, dedicated infra | v2.0 |
22. See also
README.md— whataidokitis, install, first exampleROADMAP.md— release plan and exit criteriadocs/specs/adapter-contract.md— full Adapter interfacedocs/specs/stack-pack-contract.md— full StackPack interfacedocs/specs/cli-reference.md— every command and exit codedocs/specs/mcp-catalog.md— MCP definitions and rulesdocs/specs/conformance-levels.md— full per-level checklistsdocs/specs/security-model.md— threat modeldocs/architecture/decisions/— ADRs