|
| 1 | +--- |
| 2 | +phase: design |
| 3 | +title: System Design & Architecture |
| 4 | +description: Define the technical architecture, components, and data models |
| 5 | +--- |
| 6 | + |
| 7 | +# System Design & Architecture |
| 8 | + |
| 9 | +## Architecture Overview |
| 10 | +**What is the high-level system structure?** |
| 11 | + |
| 12 | +```mermaid |
| 13 | +flowchart TD |
| 14 | + User[User] -->|ai-devkit plugin add pkg| PluginCmd[plugin command] |
| 15 | + PluginCmd --> GlobalNpm[~/.ai-devkit/npm] |
| 16 | + PluginCmd --> GlobalConfig[~/.ai-devkit/.ai-devkit.json] |
| 17 | + GlobalNpm --> NodeModules[~/.ai-devkit/npm/node_modules] |
| 18 | +
|
| 19 | + User -->|ai-devkit hello-devkit| Cli[AI DevKit CLI] |
| 20 | + Cli --> Builtins[Register built-in commands] |
| 21 | + Cli --> PluginLoader[Global plugin loader] |
| 22 | + PluginLoader --> GlobalConfig |
| 23 | + PluginLoader --> Resolver[Resolve package from global npm root] |
| 24 | + Resolver --> PackageJson[Plugin package.json] |
| 25 | + PackageJson --> Manifest[aiDevkit.commands manifest] |
| 26 | + Manifest --> Entrypoint[Dynamic import command entrypoint] |
| 27 | + Entrypoint --> Commander[Register Commander command] |
| 28 | + Commander --> Runtime[AI DevKit runtime] |
| 29 | + Commander --> PluginAction[Plugin action] |
| 30 | +``` |
| 31 | + |
| 32 | +AI DevKit remains the host CLI. Plugins are global npm packages installed into an AI DevKit-managed npm project at `~/.ai-devkit/npm`. The global AI DevKit config lists enabled plugins. At startup, the CLI registers built-ins, discovers globally configured plugins, validates each manifest, and lets each plugin register contributed commands with Commander. |
| 33 | + |
| 34 | +### Key Components |
| 35 | +| Component | Responsibility | |
| 36 | +|---|---| |
| 37 | +| `plugin` CLI command | Add, remove, and list global npm plugins. | |
| 38 | +| Global config manager | Read/write `~/.ai-devkit/.ai-devkit.json` and its `plugins` array. | |
| 39 | +| Plugin npm manager | Ensure `~/.ai-devkit/npm`, run npm install/remove, and report package status. | |
| 40 | +| Plugin manifest loader | Resolve package manifests from `~/.ai-devkit/npm/node_modules` and validate `aiDevkit.commands`. | |
| 41 | +| Plugin command loader | Import plugin entrypoints and register contributed Commander commands. | |
| 42 | +| AI DevKit runtime | Stable host context passed to plugin registration. | |
| 43 | + |
| 44 | +## Data Models |
| 45 | +**What data do we need to manage?** |
| 46 | + |
| 47 | +### Global Config |
| 48 | +```typescript |
| 49 | +interface GlobalAiDevkitConfig { |
| 50 | + version?: string; |
| 51 | + plugins?: string[]; |
| 52 | + memory?: { |
| 53 | + path?: string; |
| 54 | + }; |
| 55 | + [key: string]: unknown; |
| 56 | +} |
| 57 | +``` |
| 58 | + |
| 59 | +### Plugin Manifest |
| 60 | +The MVP keeps the manifest in `package.json` so plugin authors do not need an extra file. Command entries must point to JavaScript files importable by Node at runtime. Plugin authors may build those files from TypeScript, but the host does not execute `.ts` entrypoints in the MVP. |
| 61 | + |
| 62 | +```json |
| 63 | +{ |
| 64 | + "name": "@example/hello-ai-devkit", |
| 65 | + "type": "module", |
| 66 | + "aiDevkit": { |
| 67 | + "commands": [ |
| 68 | + { |
| 69 | + "name": "hello-devkit", |
| 70 | + "description": "Print a plugin test message", |
| 71 | + "entry": "./dist/command.js" |
| 72 | + } |
| 73 | + ] |
| 74 | + } |
| 75 | +} |
| 76 | +``` |
| 77 | + |
| 78 | +```typescript |
| 79 | +interface AiDevkitPluginManifest { |
| 80 | + commands: AiDevkitPluginCommand[]; |
| 81 | +} |
| 82 | + |
| 83 | +interface AiDevkitPluginCommand { |
| 84 | + name: string; |
| 85 | + description?: string; |
| 86 | + entry: string; |
| 87 | +} |
| 88 | +``` |
| 89 | + |
| 90 | +### Runtime Contract |
| 91 | +```typescript |
| 92 | +interface AiDevkitRuntime { |
| 93 | + cwd: string; |
| 94 | + homeDir: string; |
| 95 | + configPath: string; |
| 96 | + getConfig(): Promise<GlobalAiDevkitConfig>; |
| 97 | + getMemoryDbPath(): Promise<string>; |
| 98 | + logger: { |
| 99 | + info(message: string): void; |
| 100 | + warn(message: string): void; |
| 101 | + error(message: string): void; |
| 102 | + }; |
| 103 | +} |
| 104 | +``` |
| 105 | + |
| 106 | +## API Design |
| 107 | +**How do components communicate?** |
| 108 | + |
| 109 | +### CLI Surface |
| 110 | +```bash |
| 111 | +ai-devkit plugin add <npm-package> |
| 112 | +ai-devkit plugin remove <npm-package> |
| 113 | +ai-devkit plugin list |
| 114 | +ai-devkit <plugin-command> [...args] |
| 115 | +``` |
| 116 | + |
| 117 | +### Plugin Entrypoint |
| 118 | +Each command entrypoint exports `register`. |
| 119 | + |
| 120 | +```typescript |
| 121 | +import type { Command } from 'commander'; |
| 122 | + |
| 123 | +export async function register(command: Command, runtime: AiDevkitRuntime): Promise<void> | void { |
| 124 | + command |
| 125 | + .description('Print a plugin test message') |
| 126 | + .option('--name <name>', 'Name to greet') |
| 127 | + .action(async options => { |
| 128 | + runtime.logger.info(`Hello ${options.name ?? 'AI DevKit'}`); |
| 129 | + }); |
| 130 | +} |
| 131 | +``` |
| 132 | + |
| 133 | +AI DevKit creates the top-level command from the manifest, then passes that `Command` instance to the plugin entrypoint. This keeps command naming and conflict handling in the host while letting plugins define options and actions. |
| 134 | + |
| 135 | +## Component Breakdown |
| 136 | +**What are the major building blocks?** |
| 137 | + |
| 138 | +### `packages/cli/src/commands/plugin.ts` |
| 139 | +- Registers `plugin add`, `plugin remove`, and `plugin list`. |
| 140 | +- Delegates package work to a service layer. |
| 141 | +- Renders install/remove/list results through existing terminal UI helpers. |
| 142 | + |
| 143 | +### `packages/cli/src/services/plugin/plugin-package.service.ts` |
| 144 | +- Ensures `~/.ai-devkit/npm/package.json`. |
| 145 | +- Runs npm commands with argument arrays, not shell interpolation. |
| 146 | +- Supports install and uninstall for npm package names only. |
| 147 | + |
| 148 | +### `packages/cli/src/lib/GlobalConfig.ts` |
| 149 | +- Reads/writes `~/.ai-devkit/.ai-devkit.json`. |
| 150 | +- Adds/removes plugin names with deduplication. |
| 151 | +- Preserves unrelated global config fields. |
| 152 | +- Reuses the existing global config manager instead of creating a plugin-only config service. |
| 153 | + |
| 154 | +### `packages/cli/src/services/plugin/plugin-loader.service.ts` |
| 155 | +- Resolves plugin package manifests by reading `~/.ai-devkit/npm/node_modules/<package>/package.json` directly. |
| 156 | +- Validates manifest shape and command names. |
| 157 | +- Detects command conflicts before registration. |
| 158 | +- Imports entrypoints with `pathToFileURL()`. |
| 159 | + |
| 160 | +### `packages/cli/src/services/plugin/runtime.ts` |
| 161 | +- Builds the `AiDevkitRuntime` passed to plugins. |
| 162 | +- Centralizes config and memory path access. |
| 163 | +- Keeps future host APIs behind a stable surface. |
| 164 | + |
| 165 | +## Design Decisions |
| 166 | +**Why did we choose this approach?** |
| 167 | + |
| 168 | +| Decision | Choice | Rationale | |
| 169 | +|---|---|---| |
| 170 | +| Plugin source | npm only | Keeps install model familiar and avoids designing multiple source protocols. | |
| 171 | +| Scope | Global only | Simplifies MVP and matches the user request. | |
| 172 | +| Install root | `~/.ai-devkit/npm` | Stable across `npx`, global, and local AI DevKit invocations. | |
| 173 | +| Config | `~/.ai-devkit/.ai-devkit.json` `plugins` | Keeps enabled plugin state in AI DevKit's global config. | |
| 174 | +| Manifest location | `package.json` `aiDevkit.commands` | Easy for npm packages and avoids a second file lookup. | |
| 175 | +| Manifest read path | Direct file read under `~/.ai-devkit/npm/node_modules` | Avoids Node package `exports` blocking access to `package.json`. | |
| 176 | +| Command API | `register(command, runtime)` | Lets plugins use Commander options/help while host owns top-level names. | |
| 177 | +| Runtime API | Small host-provided object | Prevents plugins from importing unstable AI DevKit internals. | |
| 178 | +| Entrypoint file type | JavaScript only | Avoids runtime TypeScript loaders, keeps plugin execution predictable, and lets authors use normal build output. | |
| 179 | + |
| 180 | +### Alternatives Considered |
| 181 | +- **Install beside AI DevKit binary**: rejected because `npx`, global installs, local installs, and permissions make the binary location unstable. |
| 182 | +- **Project-local plugins first**: deferred because it adds config precedence and project trust questions. |
| 183 | +- **Run plugins through `npx` every time**: rejected for MVP because it makes every plugin command slower and network-dependent. |
| 184 | +- **Hard-code optional tools into core CLI**: rejected because optional dependencies and release cadence should stay separate from core. |
| 185 | +- **Execute TypeScript entrypoints directly**: deferred because it requires a runtime loader such as `tsx` or `ts-node` and complicates production execution. |
| 186 | + |
| 187 | +## Non-Functional Requirements |
| 188 | +**How should the system perform?** |
| 189 | + |
| 190 | +### Performance |
| 191 | +- Plugin discovery only scans configured plugins. |
| 192 | +- Missing or invalid plugins should not prevent built-in commands from running. |
| 193 | +- Plugin entrypoints are imported during command registration in MVP; if startup becomes too slow, later work can defer entrypoint import until command action execution. |
| 194 | + |
| 195 | +### Security |
| 196 | +- Installing a plugin is trusted npm package execution. |
| 197 | +- Package names must be passed to npm as arguments, never interpolated into shell commands. |
| 198 | +- Manifest `entry` must resolve inside the plugin package root. |
| 199 | +- Manifest `entry` must resolve to a JavaScript file. |
| 200 | +- Built-in command names cannot be overridden by plugins. |
| 201 | + |
| 202 | +### Reliability |
| 203 | +- `plugin add` validates the manifest after install and rolls back config updates if validation fails. |
| 204 | +- `plugin add` also uninstalls the package when manifest validation fails after npm install succeeds, so invalid plugins are not left enabled or installed by default. |
| 205 | +- `plugin remove` should remove config entries even if the package is already absent, with a warning. |
| 206 | +- Loader errors should be clear and isolated to the affected plugin where possible. |
0 commit comments