What changed, and why

This PR deletes .github/copilot-instructions.md and adds a CLAUDE.md at the repo root. The old file was the live contributor/agent guide, and it described the v1 architecture: an Extension -> Service -> Core layering with *Service.ts, *Core.ts, commands/*Command.ts, and utils/*.ts conventions. The v2 rewrite deleted all of that, and the multi-provider change added a second model vendor the file never mentioned. A guide that points an agent at nonexistent layers is worse than no guide — so it's replaced, not patched.

The new CLAUDE.md

Read it top to bottom: the one-breath architecture, the layer-by-layer map, the models/providers section with an "Adding a provider" checklist, the conventions, and the build/test table. Deep rationale still lives in docs/ARCHITECTURE.md (updated in a separate PR).

The whole file — folded behind its header; expand to read.

CLAUDE.md · 100 lines
CLAUDE.md100 lines · Markdown
1# Vibe Themer — contributor & agent guide
2 
3Vibe Themer is a VS Code extension that turns a plain-language "vibe" into a
4complete color theme (UI + syntax), streaming each setting from an LLM and
5applying it live as it arrives. It supports OpenAI and Anthropic models.
6 
7This file is the working guide for anyone — human or agent — changing this repo.
8The deep design rationale lives in [`docs/ARCHITECTURE.md`](docs/ARCHITECTURE.md);
9read it before any non-trivial change.
10 
11## Architecture in one breath
12 
13Functional core, imperative shell (hexagonal). The center is pure and total; every
14side effect (VS Code, the network, the clock) sits behind an interface and is
15swapped for an in-memory fake in tests. Dependencies point inward:
16 
17```
18fp → domain → protocol → ports → application → adapters → extension.ts
19 (the seam: interfaces) (the shell)
20```
21 
22- **`src/fp/`** — the functional prelude. `Result` / `Option` / `AsyncResult`
23 (errors and absence as values, never thrown), `pipe`, `matchTag` (exhaustive
24 tagged-union matching), `Brand` (nominal types), `Redacted` (secrets that can't
25 be printed), and a small parser-combinator set. One import surface: `from '../fp'`.
26- **`src/domain/`** — value objects and rules. Every value has a **smart
27 constructor and no other way in**, so holding the value proves it's valid:
28 `Vibe`, `ColorValue`, `WorkbenchSelector`, `TokenScope`, `FontStyle`, `ApiKey`
29 (wrapped in `Redacted`), `Provider`, `Model`, `StreamingDirective`,
30 `ThemeSetting`, `CurrentTheme`, `Coverage`. Errors are tagged unions.
31- **`src/protocol/`**`streamingParser.ts` decodes one wire line
32 (`COUNT:` / `SELECTOR:` / `TOKEN:` / `MESSAGE:`) into a fully-typed directive,
33 using the fp combinators for structure and the domain constructors for fields.
34- **`src/ports/`** — the seam. One interface per effect (`ModelGateway`,
35 `ConfigStore`, `SecretStore`, `Preferences`, `PromptLibrary`, `Ui`, `Clock`,
36 `Logger`), bundled into a single `Capabilities` record, plus the port-error
37 unions and their `UserMessage` rendering.
38- **`src/application/`** — use cases returning `Result`: `generateTheme` (the
39 streaming loop), `provisionApiKey` (the key state machine), and the maintenance
40 commands. Decisions are delegated to pure functions; effects go through ports.
41- **`src/adapters/`** — the shell. `adapters/vscode/*` implement ports against the
42 VS Code API; `adapters/openai/` and `adapters/anthropic/` implement
43 `ProviderAdapter` against their SDKs; `adapters/gateway.ts` is the dispatcher.
44- **`src/commands.ts` + `src/extension.ts`**`commands.ts` is the single source
45 of truth for command IDs and handlers; `extension.ts` is the composition root
46 (build adapters, assemble `Capabilities`, register commands — synchronous).
47 
48**The hard rule:** `vscode`, `openai`, and `@anthropic-ai/sdk` are imported *only*
49in `src/adapters/**` and `src/extension.ts`. The core never sees them.
50 
51## Models & providers
52 
53- `Provider = 'openai' | 'anthropic'`; a `Model` is `{ provider, id }`.
54- `ModelGateway` (a provider-blind façade) dispatches each call to a per-provider
55 `ProviderAdapter`. The application layer never branches on provider.
56- Model choice comes from a small curated `CATALOG` in `src/domain/model.ts`
57 (latest flagship + mini per provider) plus a custom-id escape hatch; default is
58 `gpt-5.5`. Bump those strings as new models ship — the catalog is the one place.
59- Each provider's API key is validated by prefix (`sk-` vs `sk-ant-`) and stored
60 separately in VS Code `SecretStorage`. Keys are `Redacted` from validation
61 onward and unwrapped (`expose`) only at the SDK boundary.
62 
63### Adding a provider
64 
65It's a closed set, so the compiler will walk you through it. Touch points:
66`Provider` union + `allProviders` + `providerInfo` (`domain/provider.ts`), the
67prefix rule in `hasValidPrefix` (`domain/apiKey.ts`), a `CATALOG` entry
68(`domain/model.ts`), a new `adapters/<provider>/gateway.ts` returning a
69`ProviderAdapter`, the `SecretStore` storage key (`adapters/vscode/secrets.ts`),
70and the wiring in `extension.ts`.
71 
72## Conventions
73 
74- **Illegal states unrepresentable.** Reach for a branded type + smart constructor
75 before a validation function you have to remember to call. Parse, don't validate.
76- **No throws in the core.** Return `Result`/`Option`. `matchTag` every tagged
77 union exhaustively (the compiler enforces it). Don't reach around the types.
78- **Secrets.** Never log, print, or interpolate a raw key. It lives in `Redacted`;
79 `expose` has a tiny, audited set of call sites at the SDK edge — keep it that way.
80- **Commands.** Add via `COMMAND_IDS` in `commands.ts`; a test asserts they match
81 `package.json`. Never re-type a command-id string literal elsewhere.
82- **Style.** Match the surrounding code. Comments justify *why*, not *what*. Stale
83 comments and docs are bugs — fix them in the same change that invalidates them.
84 
85## Build, test, verify
86 
87| Command | What it does |
88|---|---|
89| `npm run check-types` | `tsc --noEmit` at maximum strictness over `src` + `test` |
90| `npm run lint` | ESLint over `src` + `test` |
91| `npm run compile` | check-types + lint + the esbuild extension bundle |
92| `npm test` | esbuild-bundles `test/run.ts`, runs it on Node's `node:test` |
93 
94Nothing is done until **`npm run compile` and `npm test` both pass.** Add the
95*discriminating* test that only the new behavior could break, not coverage padding.
96The whole core is exercised against in-memory fakes (`test/support/harness.ts`) —
97no VS Code, no network — so most logic is testable without booting the editor.
98 
99To run it in the editor: open this folder in VS Code and press `F5` (Extension
100Development Host), then run **Vibe Themer: Change Theme** from the Command Palette.