# Architecture

## System Overview

```
┌─────────────────────────────────────────────────┐
│  exokit CLI (cli.py)                           │
│  Thin wrapper: collect answers → call core      │
└────────────────────┬────────────────────────────┘
                     │
                     ▼
┌─────────────────────────────────────────────────┐
│  core/ — The Library                            │
│                                                 │
│  questionnaire.py  → Question definitions       │
│  prereqs.py        → Pre-flight checks          │
│  models.py         → Pydantic data models       │
│  scaffold.py       → Template rendering engine  │
│  manifest.py       → Manifest builder           │
│  apx_bridge.py     → APX subprocess integration │
│  skills.py         → Skill bundler + updater    │
│  templates/        → Jinja2 templates           │
└────────────────────┬────────────────────────────┘
                     │ generates
                     ▼
┌─────────────────────────────────────────────────┐
│  Generated Demo Project                         │
│                                                 │
│  CLAUDE.md + agents + skills + manifest         │
│  databricks.yml + .env + configs                │
│  apps/main-app/ (via APX + governance overlay)  │
│  src/ (lakehouse stubs)                         │
│  docs/ + scripts/                               │
└─────────────────────────────────────────────────┘
```

## Module Responsibilities

### core/models.py — Data Definitions
- Defines ALL Pydantic models used across the system
- No business logic — pure data structures
- Models: Question, QuestionnaireAnswers, DemoManifest, WaveDefinition, PrereqResult, ScaffoldResult

### core/questionnaire.py — Question Data
- Defines `QUESTIONS: list[Question]` as static data
- `compute_defaults(partial_answers) -> QuestionnaireAnswers` for derived fields
- No UI code — questions are data, renderable by any frontend

### core/prereqs.py — Pre-flight Checks
- `check_all(profile) -> list[PrereqResult]`
- Individual checks: databricks CLI, uv, node, apx, auth
- Uses subprocess with capture for version detection
- Pure functions — no side effects beyond subprocess calls

### core/scaffold.py — Template Engine
- `generate(answers: QuestionnaireAnswers, output_dir: Path) -> ScaffoldResult`
- Orchestrates the full scaffold flow:
  1. Create directory structure
  2. Render Jinja2 templates
  3. Call APX bridge for app
  4. Bundle skills
  5. Generate MCP config
  6. Write manifest
- Uses Jinja2 with strict undefined mode

### core/apx_bridge.py — APX Integration
- `scaffold_app(answers, app_dir) -> None`
- Calls `apx init` via subprocess
- After APX: overlays governance (3-layer dirs, app CLAUDE.md)
- Isolated from APX internals — subprocess only

### core/skills.py — Skill Management
- `bundle_skills(target_dir) -> list[str]`
- `update_skills(project_dir, source_dir) -> list[str]`
- Copies skill directories from bundled snapshots
- Generates .mcp.json for ai-dev-kit

### cli.py — CLI Wrapper
- `init()` — Questionnaire via Rich prompts → core.scaffold.generate()
- `validate()` — core.prereqs.check_all() → Rich table
- `update_skills()` — core.skills.update_skills()
- ZERO business logic — delegates everything to core/

## Data Flow

```
User Input (CLI prompts)
    │
    ▼
dict[str, str]  (raw answers)
    │
    ▼
questionnaire.compute_defaults()
    │
    ▼
QuestionnaireAnswers  (validated, with computed fields)
    │
    ▼
scaffold.generate()
    │
    ├── Jinja2 context dict (built from answers)
    │   └── templates/*.j2 → rendered files
    │
    ├── apx_bridge.scaffold_app()
    │   └── subprocess: apx init → overlay governance
    │
    ├── skills.bundle_skills()
    │   └── copy skills/ → .claude/skills/
    │
    └── manifest.build_manifest()
        └── demo_manifest.json
    │
    ▼
ScaffoldResult  (project_dir, files_created, warnings)
    │
    ▼
CLI displays summary (Rich)
```

## Key Design Decisions

### Why Library-First
The scaffold engine must be callable from:
1. V1: Typer CLI (current)
2. V2: FastAPI web app (future)
3. Tests: pytest (always)

If logic lives in cli.py, V2 has to rewrite it. Library-first makes V2 a new thin wrapper.

### Why APX as Subprocess
APX is a Rust binary with its own template system. We can't import it as a Python library. Subprocess keeps us decoupled — if APX changes its internals, our bridge just needs the same CLI flags.

### Why Pydantic Everywhere
Every function boundary is a potential bug surface. Pydantic validates at boundaries automatically. It also gives us JSON serialization for free (manifest, config files).

### Why Jinja2 (Not Cookiecutter/Copier)
We need fine-grained control over template rendering. Cookiecutter/Copier are opinionated about directory structure and prompting. We already have our own questionnaire and directory logic — we just need a template engine.
