What changed, and why

docs/ARCHITECTURE.md was written for the single-provider world and never updated when multi-provider shipped. Four concrete inaccuracies are corrected:

- The intro, the ASCII diagram's adapter label, and the import-rule sentence said "OpenAI"; they are now provider-neutral (OpenAI or Anthropic; SDKs imported only at the edge). - The ports list named **OpenAiGateway** — a port that does not exist. The real seam is ModelGateway (provider-blind façade) dispatching to per-provider ProviderAdapters; the adapters section now describes that. - The default model was listed as gpt-4.1 (the v1 default). It is gpt-5.5 on OpenAI, selection is a Model = {provider, id}, and keys are stored per provider. - domain/ now lists Provider and Model.

The corrected document

Read the whole file at HEAD; the prose above maps each correction.

Folded behind its header; expand to read the corrected doc.

docs/ARCHITECTURE.md · 115 lines
docs/ARCHITECTURE.md115 lines · Markdown
1# Vibe Themer Architecture (v2)
2 
3Vibe Themer turns a natural-language "vibe" into a VS Code color theme, streaming
4each setting from the configured model provider (OpenAI or Anthropic) and applying
5it live. v2 is a ground-up rewrite
6around a **functional core, imperative shell** (hexagonal / ports-and-adapters)
7design with strict, domain-driven typing.
8 
9## The guiding idea
10 
11Make illegal states unrepresentable, then push every side effect to the edge.
12The center of the program is pure and total; the messy parts (VS Code, the network,
13the clock) live behind interfaces and are swapped for fakes in tests.
14 
15```
16 ┌──────────────────────────────────────────────┐
17 │ extension.ts (composition root) │
18 │ builds adapters → Capabilities → commands │
19 └───────────────┬───────────────┬──────────────┘
20 │ │
21 adapters/ application/ ← orchestration (impure-ish)
22 (vscode, providers) use cases returns Result, no throws
23 │ │
24 └──────┬────────┘
25 │ depends only on
26 ports/ (interfaces) ← the seam
27
28 domain/ + protocol/ + fp/ ← pure, total, no I/O
29```
30 
31Dependencies point inward. `domain`, `protocol`, and `fp` know nothing about VS
32Code or the model SDKs. `application` depends on `ports` (interfaces), never on
33adapters. `adapters` and `extension.ts` are the only files that import `vscode`,
34`openai`, or `@anthropic-ai/sdk`.
35 
36## Layers
37 
38### `fp/` — the functional prelude
39 
40A small, dependency-free kernel: `Result`/`Option`/`AsyncResult` (errors and
41absence as values, never thrown), `pipe`, `matchTag` (exhaustive tagged-union
42matching), `Brand` (nominal types), `Redacted` (secrets that can't be printed),
43and a tiny parser-combinator set. One import surface: `import { ... } from './fp'`.
44 
45### `domain/` — value objects and rules
46 
47Every domain value has a **smart constructor** and no other way in, so possession
48of the value is proof it's valid: `Vibe`, `ColorValue` (Hex | Named | Remove),
49`WorkbenchSelector`, `TokenScope`, `FontStyle`, `ApiKey` (wrapped in `Redacted`),
50`Provider`, `Model` (`{provider, id}`), `StreamingDirective`, `ThemeSetting`,
51`ConfigurationScope`,
52`CurrentTheme`, `Coverage`. Errors are tagged unions with centralized rendering.
53 
54### `protocol/` — the streaming grammar
55 
56`streamingParser.ts` decodes one wire line (`COUNT:` / `SELECTOR:` / `TOKEN:` /
57`MESSAGE:`) using the combinators for structure and the domain constructors for
58field validation. A parsed directive is fully well-typed.
59 
60### `ports/` — the seam
61 
62Interfaces for every effect: `ModelGateway` (the provider-blind façade),
63`ConfigStore`, `SecretStore`, `Preferences`, `PromptLibrary`, `Ui`, `Clock`,
64`Logger`, bundled into one `Capabilities` record. Plus the port-error unions and
65their `UserMessage` rendering.
66 
67### `application/` — use cases
68 
69Pure-ish orchestration returning `Result`: `generateTheme` (the streaming loop),
70`provisionApiKey` (the key state machine), and the maintenance commands
71(`resetTheme`, `clearApiKey`, `selectModel`, `resetModel`). Decisions are
72delegated to pure functions (`parseLine`, `progressPercent`, `coverage`); effects
73go through ports.
74 
75### `adapters/` — the shell
76 
77`adapters/vscode/*` implement the ports against the VS Code API.
78`adapters/openai/` and `adapters/anthropic/` each implement a `ProviderAdapter`
79against their SDK, classifying errors by HTTP status; `adapters/gateway.ts` is the
80provider-blind `ModelGateway` that dispatches each call to the right one. Thin and
81obviously correct.
82 
83### `commands.ts` + `extension.ts`
84 
85`commands.ts` is the single source of truth for command IDs and their handlers
86(a test asserts these match `package.json`). `extension.ts` is the composition
87root: build adapters, assemble `Capabilities`, register commands — fully
88synchronous activation.
89 
90## Why this shape
91 
92- **Testability.** The entire core runs against in-memory fakes (`test/support/
93 harness.ts`), so the full Change Theme flow — streaming, live application,
94 cancellation, error tolerance — is exercised with no VS Code and no network.
95- **Safety by type.** Branded values, `Redacted` secrets, and exhaustive matches
96 turn whole classes of v1 bugs (a leaked key, an invalid color, an unhandled
97 variant) into compile errors.
98- **One direction of dependency.** Swapping an adapter (or a model provider) never
99 touches the core.
100 
101## Configuration & secrets
102 
103- API keys: one per provider in VS Code `SecretStorage`, wrapped in `Redacted`
104 from validation onward.
105- Selected model: a `Model` (`{provider, id}`) in `globalState` via the
106 `Preferences` port (default `gpt-5.5` on OpenAI), chosen from a curated catalog.
107- Theme state: read per-scope via `config.inspect()` so global vs workspace are
108 actually distinguished.
109 
110## Testing & build
111 
112- `npm run check-types``tsc --noEmit` at maximum strictness over `src` + `test`.
113- `npm test` — esbuild bundles `test/run.ts` and runs it on Node's built-in
114 `node:test` (no test-runner dependency, single process).
115- `npm run compile` — check-types + ESLint + the esbuild extension bundle.