Architecture

System design, package map, and key flows for aidokit.


Table of contents

  1. System overview
  2. Design principles
  3. Repository layout
  4. Package responsibilities
  5. The Adapter contract
  6. The Stack-pack contract
  7. ProjectContext — the universal carrier
  8. Data flow: aidokit init
  9. Data flow: aidokit doctor
  10. Data flow: aidokit sync
  11. File emission pipeline
  12. MCP catalog model
  13. Prereq check model
  14. Stack detection model
  15. Conformance model
  16. Error model and exit codes
  17. Versioning strategy
  18. Testing strategy
  19. Security model (overview)
  20. Bootstrap and dogfooding
  21. Open architectural questions
  22. 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

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):

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:


4. Package responsibilities

@aidokit/cli

The user-facing entry point. Implements every aidokit command.

Public surface: the CLI itself. No exported APIs.

@aidokit/core

The contract and type library. Imported by everyone.

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.

Pure data; no executable code beyond a manifest exporter.

@aidokit/stack-detect

Detects the project's tech stack from manifest files and conventions.

@aidokit/prereq-check

Checks whether host prerequisites are installed.

@aidokit/mcp-catalog

The MCP server catalog and installation glue.

@aidokit/adapter-claude-code

Emits a complete .claude/ engine directory plus CLAUDE.md.

@aidokit/adapter-codex

Same role as the Claude Code adapter, for Codex CLI. Ships v1.0.

@aidokit/adapter-copilot

Same role as the Claude Code adapter, for GitHub Copilot CLI. Ships v1.0.

@aidokit/stack-pack-node-ts (and siblings)

Stack-aware extension that ships:


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:

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:


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:

  1. Resolve final tree in memory
  2. Write entire tree to <projectRoot>/.aido-staging/
  3. On success, atomically rename each file into place (or fs.cp then unlink staging)
  4. 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:

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:

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

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)

18.2 Integration tests

18.3 End-to-end tests

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:


19. Security model (overview)

Full threat model and mitigations: docs/specs/security-model.md. Outline only here.

19.1 Trust boundaries

19.2 Key controls


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