diff --git a/.claude/skills/architecture/SKILL.md b/.claude/skills/architecture/SKILL.md new file mode 100644 index 0000000..efc7f88 --- /dev/null +++ b/.claude/skills/architecture/SKILL.md @@ -0,0 +1,85 @@ +--- +name: architecture +description: Monorepo structure, dependency graph, domain boundaries, and package-level constraints for the Confidence CLI project +version: '0.4' +--- + +# Architecture Guidelines + +Structural rules, domain boundaries, and constraints for the Confidence CLI monorepo. + +## Monorepo Structure + +| Package | Published | Purpose | +| ------------------------- | --------- | -------------------------------------------------------------------------------------------------------- | +| `packages/shared-kernel/` | No | Cross-domain types and `noop` helper | +| `packages/eslint-config/` | No | Shared ESLint config (base + react presets) | +| `packages/core/` | No | Shared infrastructure (auth, session, telemetry, exec, system, sdk, frameworks, integrations, providers) | +| `packages/testing/` | No | Test infrastructure (sub-paths: `/auth`, `/scaffold`, `/env`, `/terminal`, `/msw`, `/e2e`) | +| `packages/quickstart/` | Yes | TUI wizard — `@spotify-confidence/quickstart` | +| `packages/cli/` | Yes | CLI — `@spotify-confidence/cli` | + +### Dependency Graph + +``` +shared-kernel ◄── core ◄── quickstart ◄── cli + ▲ + └── testing +``` + +## Import Rules + +**Cross-package** — always use npm package name: + +```ts +import { authenticate } from '@spotify-confidence/core'; +import type { IdeId } from '@spotify-confidence/shared-kernel'; +import { buildTestJwt } from '@spotify-confidence/testing/auth'; +``` + +**Intra-package** — use path aliases for cross-domain, relative for within-domain: + +| Package | Aliases | +| ---------- | ----------------------------------------------------------------- | +| quickstart | `@commands/*`, `@features/*`, `@ui/*` | +| cli | `@commands/*`, `@features/*`, `@output/*`, `@api/*` | +| core | Relative imports in `src/`; tsconfig aliases in `__tests__/` only | + +## Domain Boundaries + +- **shared-kernel** — Types used by 3+ packages. Depends on nothing. +- **core** — Shared infrastructure. Must not import from quickstart, cli, or testing. +- **testing** — Test scaffolds. Depends on shared-kernel only. +- **quickstart** — TUI wizard. See `ink-tui` skill for UI details. +- **cli** — CLI binary. See `cli` skill for command architecture. + +## Hard Constraints + +### Dependency direction + +``` +cli → quickstart, core, shared-kernel +quickstart → core, shared-kernel +core → shared-kernel +shared-kernel → nothing +``` + +No circular dependencies. No upward imports. + +Within quickstart UI: `screen slices → hooks/, lib/, components/ → lib/ → nothing in ui/` + +### No product knowledge in the TUI + +The TUI is a generic wizard shell. Confidence-specific domain logic belongs in the Claude Code Skill and is delivered via MCP tools. + +### Screen identification + +Always use `ScreenId` enum values from `@spotify-confidence/core`. Never use raw strings. + +### State mutations + +All session state changes go through `WizardStore` setters. Never mutate the session object directly. + +### UI component sourcing + +Use `@inkjs/ui` components over standalone `ink-*` packages. diff --git a/.claude/skills/wizard-auth/SKILL.md b/.claude/skills/auth/SKILL.md similarity index 76% rename from .claude/skills/wizard-auth/SKILL.md rename to .claude/skills/auth/SKILL.md index 8471593..1b13d0a 100644 --- a/.claude/skills/wizard-auth/SKILL.md +++ b/.claude/skills/auth/SKILL.md @@ -1,14 +1,20 @@ +--- +name: auth +description: OAuth2 PKCE authentication flow with Confidence via Auth0 +version: '0.2' +--- + # Authentication Skill Handles OAuth2 PKCE authentication with Confidence via Auth0. ## Flow -1. **Check existing credentials** — Look for persisted token at `$TMPDIR/confidence_token`. Validate JWT expiry. +1. **Check existing credentials** — Look for persisted token at `$CONFIDENCE_CONFIG_DIR/credentials.json` (or `$TMPDIR/confidence_token` legacy path). Validate JWT expiry. 2. **Prompt user** — If valid token exists, offer to reuse or re-authenticate. If no token, ask whether to create a new account or sign in. 3. **Browser-based OAuth2 PKCE** — Start local HTTP server on port 8084, open browser to Auth0 authorize endpoint, wait for callback with authorization code. 4. **Token exchange** — Exchange authorization code + PKCE verifier for access token and refresh token. -5. **Persist tokens** — Write access token to `$TMPDIR/confidence_token`, refresh token to `$TMPDIR/confidence_refresh_token`, and the Auth0 organization (`org_id` claim, falling back to `https://confidence.dev/org_login_id`) to `$TMPDIR/confidence_organization`. +5. **Persist tokens** — Write credentials to the config directory. 6. **Extract region** — Decode JWT payload, read `https://confidence.dev/region` claim (EU or US) to determine regional API endpoints. ## Auth0 Configuration @@ -42,13 +48,15 @@ The auth flow is implemented in `packages/core/src/auth/authenticate.ts` using N - `fetch` for token exchange with Auth0 - JWT payload decoded manually (base64url) — no external JWT library needed -## Token Files +## Token Persistence + +Tokens are stored in the Confidence config directory (`$CONFIDENCE_CONFIG_DIR` or `~/.config/confidence/`): + +| File | Content | +| ------------------ | --------------------------------------------- | +| `credentials.json` | JWT access token, refresh token, organization | -| File | Content | -| ---------------------------------- | ------------------------------------------------------------- | -| `$TMPDIR/confidence_token` | JWT access token | -| `$TMPDIR/confidence_refresh_token` | Refresh token for silent re-auth | -| `$TMPDIR/confidence_organization` | Auth0 organization for skipping the workspace prompt on login | +The `CONFIDENCE_TOKEN` env var overrides persisted tokens when present. ## Remembered Workspace diff --git a/.claude/skills/cli/SKILL.md b/.claude/skills/cli/SKILL.md new file mode 100644 index 0000000..630b891 --- /dev/null +++ b/.claude/skills/cli/SKILL.md @@ -0,0 +1,52 @@ +--- +name: cli +description: Structure, commands, output formatting, and conventions for the packages/cli/ package +version: '0.2' +--- + +# CLI Package + +The `packages/cli/` package — the `confidence` binary for managing Confidence feature flags, events, session recordings, and configuration. Published as `@spotify-confidence/cli`. + +## Build + +tsdown bundles `core` and `shared-kernel` into the binary. `quickstart` stays external (dynamically imported at runtime). Target: Node 24+ ESM. + +## Command Architecture + +Each command exports a yargs command object: + +```ts +export const exampleCommand = { + command: 'example', + describe: 'One-line description', + builder(yargs: Argv) { ... }, // optional — for subcommands or extra options + async handler(argv: Record) { ... }, +}; +``` + +### Command Types + +- **Standalone** — `login`, `logout`, `whoami`, `config` — directly perform their action +- **Setup** — `flags setup`, `events setup`, `recordings setup` — delegate to quickstart TUI with pre-selected features +- **TUI launcher** — `quickstart` — launches the full interactive wizard + +## Output Formatting + +All structured output goes through `src/output/`: + +- **`resolveFormat()`** — priority: `--json` flag → `--output` flag → TTY detection (TTY → table, pipe → JSON) +- **`formatJson()`** — wraps data in `{ data, meta? }` envelope +- **`formatTable()`** — dynamically-sized columns with Unicode separators + +Commands never call `JSON.stringify` directly. + +## Quickstart Integration + +`launchQuickstart()` in `src/features/quickstart/launch.ts` maps feature names to goal IDs (`flags` → `feature-flags`, etc.), dynamically imports `startTui` from `@spotify-confidence/quickstart`, and passes through CLI options. + +## Hard Constraints + +- Commands must not contain UI rendering logic — delegate to quickstart for TUI flows. +- Auth logic lives in `@spotify-confidence/core`, not in command handlers. +- The CLI must not import from quickstart's internal modules — only from its public `startTui` export. diff --git a/.claude/skills/coding-conventions/SKILL.md b/.claude/skills/coding-conventions/SKILL.md new file mode 100644 index 0000000..1d43659 --- /dev/null +++ b/.claude/skills/coding-conventions/SKILL.md @@ -0,0 +1,41 @@ +--- +name: coding-conventions +description: TypeScript style, import ordering, module exports, and linting rules for the Confidence CLI monorepo +version: '0.2' +--- + +# Coding Conventions + +TypeScript style and linting rules that apply across all packages. + +## TypeScript Style + +- **Import order** (blank-line separated): node built-ins → React → external deps → `@spotify-confidence/*` → path aliases → relative +- **`type` over `interface`** for all type definitions +- **Object params** when a function has 4+ arguments +- **`satisfies never`** in switch defaults for exhaustiveness checking +- **Modern syntax**: `satisfies`, `using` for disposables, etc. + +## React / Hooks + +- **Named functions in `useEffect`**, not arrow functions: + ```ts + useEffect(function autoAdvance() { ... }, [deps]); + ``` +- **`AbortController`** for event listener cleanup instead of manual `removeEventListener` +- **`eslint-plugin-react-hooks`** with `recommended-latest`, all set to `error` + +## Module Exports + +- Barrel files re-export only the public API — no internal helpers. +- Use named types in `actions.ts` for union extensions. + +## Quickstart-Specific + +- **`useInitial*` hooks** for slices that compute initial state at mount: (1) pure `resolve*` function, (2) `useEffect` to sync store, (3) return values for parent hook. +- **Dry run separation** — keep dry-run logic in a separate function, never interleaved with conditionals. +- **Enums** — `ScreenId` for screens, `HAlign`/`VAlign` for alignment, `Colors`/`Icons` from `styles.ts` for theming. Never raw strings or values. + +## Linting + +Strict — all rules are errors. Config from `@spotify-confidence/eslint-config` (base) or `/react` (for React packages). Never suppress warnings or diagnostics; fix the root cause. diff --git a/.claude/skills/development-harness/SKILL.md b/.claude/skills/development-harness/SKILL.md new file mode 100644 index 0000000..f6fb709 --- /dev/null +++ b/.claude/skills/development-harness/SKILL.md @@ -0,0 +1,39 @@ +--- +name: development-harness +description: Quality gates, commit conventions, pre-commit hooks, and CI/CD processes for the Confidence CLI monorepo +version: '0.3' +--- + +# Development Harness + +Quality gates, commit conventions, and CI/CD for the monorepo. + +## Quality Harness + +Run `pnpm qa` before committing and pushing — it runs typecheck + lint + test. Use `pnpm lint:fix` to auto-fix formatting. + +Pre-commit hooks (Husky + lint-staged) auto-format staged files on every commit. + +## Commit Conventions + +All commits follow [Conventional Commits](https://www.conventionalcommits.org/), enforced by a `commit-msg` hook via commitlint. + +``` +(): +``` + +Types: `feat` (minor bump), `fix` (patch bump), `docs`, `style`, `refactor`, `perf`, `test`, `build`, `ci`, `chore`. Append `!` for breaking changes (major bump). + +``` +feat(cli): add whoami command +fix(frameworks): correct Next.js detection for app router +refactor(core): extract shared types to lib module +``` + +## CI/CD + +**PR checks** (`.github/workflows/ci.yml`): typecheck + lint + test + commit message validation. Both must pass before merging. + +**Release** (`.github/workflows/release.yml`): release-please opens a PR on `main` with changelog + version bumps. Merging the Release PR triggers GitHub Release + npm publish. No manual version bumping — versions are derived from commit messages. + +Required secrets: `GITHUB_TOKEN` (automatic), `NPM_TOKEN` (repository secret). diff --git a/.claude/skills/wizard-ink-tui/SKILL.md b/.claude/skills/ink-tui/SKILL.md similarity index 92% rename from .claude/skills/wizard-ink-tui/SKILL.md rename to .claude/skills/ink-tui/SKILL.md index 6d585ad..6cbfc09 100644 --- a/.claude/skills/wizard-ink-tui/SKILL.md +++ b/.claude/skills/ink-tui/SKILL.md @@ -1,12 +1,12 @@ --- name: ink-tui -description: Develop and modify the Ink-based terminal UI for the Confidence Wizard -version: '0.1' +description: Develop and modify the Ink-based terminal UI in the packages/quickstart/ package +version: '0.2' --- -# Ink TUI Wizard Skill +# Ink TUI Skill -This skill covers building and modifying the interactive terminal user interface for the Confidence Wizard CLI. The TUI is built with Ink (React for CLIs), @inkjs/ui, and nanostores for state management. +This skill covers building and modifying the interactive terminal user interface in `packages/quickstart/`. The TUI is built with Ink (React for CLIs), @inkjs/ui, and nanostores for state management. ## Core Architecture diff --git a/.claude/skills/integrations/SKILL.md b/.claude/skills/integrations/SKILL.md new file mode 100644 index 0000000..818616c --- /dev/null +++ b/.claude/skills/integrations/SKILL.md @@ -0,0 +1,38 @@ +--- +name: integrations +description: IDE integration strategy pattern and guidelines for the packages/core/src/integrations/ module +version: '0.4' +--- + +# IDE Integrations Guidelines + +Structure, constraints, and conventions for IDE integrations in `packages/core/src/integrations/`. + +## Strategy Pattern + +Each supported IDE (Claude Code, Cursor, Codex) is a self-contained `IdeIntegration` object in its own subdirectory. This eliminates per-IDE `switch` statements and makes adding a new IDE a single-directory change. + +Every IDE implements the `IdeIntegration` interface: `id`, `name`, `launchChat()`, `runOnboarding()`, `detectPlugins()`, `installPlugins()`, `detectMcpStatuses()`, `connectMcpServer()`. See the type definition in `types.ts` for the full contract. + +Thin orchestrators (`chat.ts`, `plugins.ts`) resolve the strategy via `getIntegration(ide)` and delegate. + +## Hard Constraints + +- **IDE subdirs are self-contained** — no imports from other IDE subdirs. Each is split into `paths.ts`, `plugins.ts`, `mcp.ts`, and `index.ts`. May import from `../types.js`, `../mcp/servers.js`, `../shared.js` — never from `../registry.js` or each other. +- **No switch-on-IDE outside strategies** — code outside `integrations/` must not branch on `IdeId`. Use `getIntegration(ide)` and call strategy methods. +- **Dependency direction** — integrations imports from `shared-kernel` and other core modules, never from `quickstart/` or `cli/`. +- **Clean-dev script** — when changing MCP-related code, verify `scripts/clean-dev-env.sh` still cleans up correctly. Update it when adding a new IDE. + +## Adding a New IDE + +1. Create `packages/core/src/integrations//index.ts` with `paths.ts`, `plugins.ts`, `mcp.ts` +2. Export a `const Integration: IdeIntegration` +3. Add to the `INTEGRATIONS` array in `registry.ts` +4. Update `scripts/clean-dev-env.sh` + +No other source files need changes. + +## Codex Runtime Constraints + +- **Shell environment policy** — always pass `-c 'shell_environment_policy.inherit="core"'` in Codex `exec` spawn args, otherwise `npm install` times out on corporate networks. +- **Event batching** — Codex `exec --json` only emits `item.completed` events (no incremental deltas). Status lines only appear after a full message turn. Claude Code and Cursor stream incrementally. diff --git a/.claude/skills/testing-e2e/SKILL.md b/.claude/skills/testing-e2e/SKILL.md new file mode 100644 index 0000000..2891ec9 --- /dev/null +++ b/.claude/skills/testing-e2e/SKILL.md @@ -0,0 +1,92 @@ +--- +name: testing-e2e +description: > + E2E test framework using node-pty, terminal sessions, mock servers, + and navigation helpers. Framework lives in packages/testing/src/e2e/, + imported via @spotify-confidence/testing/e2e. +version: '0.2' +--- + +# E2E Testing + +Load the `testing` skill first for general philosophy — this skill adds the e2e-specific framework. + +E2E tests spawn the **built CLI binary** in a real pseudo-terminal via `node-pty`, send keystrokes, and assert on terminal output. They exercise real code paths — not dry-run stubs. + +```bash +pnpm test:e2e # Build + run all e2e tests (not included in pnpm test / pnpm qa) +``` + +## Layout + +- **Framework** — `packages/testing/src/e2e/` (session factory, terminal session, mocks, navigation helpers) +- **Tests** — `packages/quickstart/__tests__/e2e/*.e2e.ts` +- **Global setup** — `packages/quickstart/__tests__/e2e/global-setup.ts` (starts mock server + IDE binaries, sets `E2E_CLI_PATH` / `E2E_MOCK_BIN_DIR`) +- **Config** — `packages/quickstart/vitest.config.e2e.ts` (120s timeout, serial, no MSW) + +Import everything from one sub-path: + +```ts +import { + createSession, + navigateToPlugins, + simulateAuthCallback, +} from '@spotify-confidence/testing/e2e'; +``` + +## Core API + +### `createSession(opts?)` + +Spawns the CLI in a pty with an isolated temp project dir. Returns a disposable `TerminalSession`. Key options: `project` (`'react'` | `'empty'`), `token` (pre-seed JWT), `config` (pre-seed `config.json` values like `{ ide: 'cursor' }`). See source JSDoc for the full option set. + +### `TerminalSession` + +| Method | Purpose | +| ----------------------- | ------------------------------------------------------------ | +| `press(key)` | Named key (`'Enter'`, `'ArrowDown'`, `'Space'`) or character | +| `pressRepeat(key, n)` | Press a key `n` times | +| `waitForText(text)` | Poll until text appears (scoped to since last checkpoint) | +| `waitForText([a, b])` | Poll until any string appears; returns the matched one | +| `waitForPattern(regex)` | Poll until regex matches; returns `RegExpMatchArray` | +| `checkpoint()` | Mark position — subsequent waits/snapshots scope after this | +| `snapshot()` | VT100-rendered, normalized output since checkpoint | +| `waitForExit()` | Wait for exit; returns exit code | + +### Navigation Helpers + +Pre-built functions that advance through wizard screens. Each takes a `TerminalSession`: + +- `navigatePastWelcome` → lands at SelectGoal +- `navigatePastGoalSelection` → lands at Authenticate +- `navigatePastAuth` → lands at InstallPlugins +- `navigateToGoalSelection` / `navigateToPlugins` / `navigateToConnectTools` / `navigateToOnboarding` — cumulative shortcuts from start +- `selectIdeAndOnboard(session, downPresses)` — selects nth IDE and runs through to Done + +## Writing Rules + +- **File naming**: `*.e2e.ts` (not `.test.ts`) +- **Use `createSession()` per test** for full isolation. +- **Use `using`** for automatic cleanup: `using session = createSession()`. +- **Use `checkpoint()`** between screens to scope assertions to the current screen. +- **Assert positively** — prefer `waitForText('expected')` over `not.toContain(...)`. +- **Use navigation helpers** to skip past earlier screens. + +## Example + +```ts +import { createSession, navigateToPlugins } from '@spotify-confidence/testing/e2e'; + +describe('plugin installation', () => { + it('installs plugins for the detected IDE', async () => { + using session = createSession(); + + await navigateToPlugins(session); + await session.waitForText('Which CLI agent'); + session.checkpoint(); + await session.press('Enter'); + + await session.waitForText('Plugin set up successfully'); + }); +}); +``` diff --git a/.claude/skills/testing/SKILL.md b/.claude/skills/testing/SKILL.md new file mode 100644 index 0000000..df4e4fe --- /dev/null +++ b/.claude/skills/testing/SKILL.md @@ -0,0 +1,70 @@ +--- +name: testing +description: > + Testing philosophy, conventions, mocking rules, and UI test + framework for unit and integration tests across all packages. +version: '0.5' +--- + +# Testing Guidelines + +Testing philosophy, conventions, and tooling for the monorepo. For e2e tests, also load the `testing-e2e` skill. + +## Philosophy + +Test **observable behavior**, never implementation details. + +- For TUI screens: assert on rendered terminal output (`lastFrame()`), never on store internals. + - Prefer `renderApp()` — renders the full app relying on `createProjectDir()` for context. + - Use `renderScreen()` when a screen depends on state normally set by a prior screen. +- For store/state: assert on the public API and its effects, not internal atom values. +- For CLI commands: test output and side effects, not argument assembly. + +**When unsure whether something is observable behavior — ask before writing the test.** + +## Mocking + +- **MSW for HTTP** — intercepts at the network level. Never mock `fetch` with `vi.fn()` or `vi.mock()`. +- **Only mock non-emulatable boundaries** — prefer `createProjectDir()` over mocking filesystem. +- **Partial mocking** with `importOriginal`: + ```ts + vi.mock('@spotify-confidence/core', async (importOriginal) => { + const actual = await importOriginal(); + return { ...actual, detectInstalledPlugins: vi.fn().mockReturnValue([]) }; + }); + ``` +- Never mock the module under test. +- MSW setup (`listen`, `resetHandlers`, `close`) belongs in `packages/testing/src/msw/setup.ts`. + +## Test Infrastructure Imports + +Import from specific sub-paths: + +```ts +import { buildTestJwt, prepareAuthTokens } from '@spotify-confidence/testing/auth'; +import { createProjectDir } from '@spotify-confidence/testing/scaffold'; +import { isWindows } from '@spotify-confidence/testing/env'; +import { server } from '@spotify-confidence/testing'; +import { createSession } from '@spotify-confidence/testing/e2e'; +``` + +## UI Testing Framework + +`packages/quickstart/__tests__/ui/testing-framework/` provides `renderScreen()`, `renderApp()`, `act()`, `delay()`, `waitFor()`, and mock child process helpers. + +## Test Structure + +**AAA pattern** in every test. Name the system under test **`sut`**. + +- 3 lines or fewer: no blank lines needed. +- Longer: blank lines between AAA sections. +- Each section >3 lines: add `// Arrange`, `// Act`, `// Assert` comments. + +## Conventions + +- Files: `.test.ts` / `.test.tsx`. Test names describe behavior from the consumer's POV. +- One assertion concern per test (multiple `expect` calls fine if same behavior). +- No snapshot tests unless explicitly requested. +- Prefer `using` for disposable resources. +- Prefer `waitFor` over `await delay` for TUI assertions. +- Prefer `createProjectDir()` for project context in screen tests. diff --git a/.claude/skills/wizard-architecture/SKILL.md b/.claude/skills/wizard-architecture/SKILL.md deleted file mode 100644 index a1c1fd2..0000000 --- a/.claude/skills/wizard-architecture/SKILL.md +++ /dev/null @@ -1,239 +0,0 @@ ---- -name: architecture -description: Architecture guidelines and constraints for the Confidence Wizard CLI project -version: '0.2' ---- - -# Architecture Guidelines - -This skill defines the structural rules, domain boundaries, and constraints that govern all work on the Confidence Wizard CLI. Follow these when adding features, refactoring, or reviewing changes. - -## Purpose - -The Confidence Wizard is a CLI tool for quickly setting up and integrating [Confidence](https://confidence.spotify.com/) with users' projects. It works together with a Claude Code Skill backed by Confidence MCP tools ([confidence-ai-plugins](https://github.com/spotify/confidence-ai-plugins)) — the Skill handles product knowledge, the CLI handles user interaction. - -## Monorepo Structure - -The project is a pnpm monorepo with five packages: - -| Package | Published | Purpose | -| ------------------------- | ------------ | --------------------------------------------------------------------------------------------------------------- | -| `packages/shared-kernel/` | No (private) | Cross-domain types and `noop` helper | -| `packages/eslint-config/` | No (private) | Shared ESLint config (base + react presets) | -| `packages/core/` | No (private) | Shared infrastructure (auth, session, telemetry, exec, system, sdk, utils, frameworks, integrations, providers) | -| `packages/testing/` | No (private) | Test infrastructure (auth scaffolds, project scaffolds, env helpers, terminal helpers, MSW) | -| `packages/quickstart/` | Yes | TUI wizard — `@spotify-confidence/quickstart` | - -### Dependency Graph - -``` -shared-kernel ◄── core ◄── quickstart - ▲ - └── testing -``` - -- **shared-kernel** → nothing (leaf) -- **core** → shared-kernel -- **testing** → shared-kernel (devDep on core for tests only) -- **quickstart** → core, shared-kernel (devDep on testing, eslint-config) - -### Cross-Package Imports - -Use the npm package name for cross-package imports: - -```ts -import { ScreenId, track, authenticate } from '@spotify-confidence/core'; -import type { IdeId } from '@spotify-confidence/shared-kernel'; -import { buildTestJwt } from '@spotify-confidence/testing/auth'; -import { createProjectDir } from '@spotify-confidence/testing/scaffold'; -``` - -### Intra-Package Imports - -Within `packages/quickstart/`, use path aliases for cross-domain imports: - -| Alias | Target | -| ------------- | ---------------- | -| `@commands/*` | `src/commands/*` | -| `@features/*` | `src/features/*` | -| `@ui/*` | `src/ui/*` | - -Within `packages/core/src/`, use relative imports (no path aliases in source — enables external consumers to follow source imports). Core `__tests__/` can use the tsconfig path aliases (`@auth/*`, `@exec/*`, etc.). - -## Domain Separation - -### Shared Kernel (`packages/shared-kernel/src/`) - -Cross-domain vocabulary types that multiple packages depend on. Contains type definitions and the `noop` helper — no other runtime logic. - -- Types here form the ubiquitous language of the project: identifiers and enums that appear in function signatures across package boundaries (e.g. `IdeId`, `AuthState`, `OnboardingGoal`, `PluginInstallationMethod`, `DetectedProvider`). -- If a type is used by three or more packages, it belongs here. -- The shared kernel depends on nothing. Every other package may import from it. -- Adding a new shared type: define in `types.ts`, re-export from `index.ts`. - -### Core (`packages/core/src/`) - -Shared infrastructure organized into cohesive modules: - -- **`auth/`** — OAuth PKCE flow + token persistence -- **`session/`** — `WizardSession`, `ScreenId`, `createSession`, and session-related types -- **`telemetry/`** — Analytics + session tracking -- **`exec/`** — Running external commands (`spawn`, `execFile`, `resolveBin`) -- **`system/`** — Environment, filesystem, system checks -- **`sdk/`** — SDK metadata + options -- **`utils/`** — Generic utilities (`addIf`, `interpolate`) -- **`constants.ts`** — Confidence URLs + env-derived values -- **`frameworks/`** — Framework detection (one subdir per framework) -- **`integrations/`** — IDE integration strategies (one subdir per IDE) -- **`providers/`** — Provider detection for competing platforms - -Core depends on `shared-kernel`. It must not import from `quickstart` or `testing`. - -### Testing (`packages/testing/src/`) - -Test infrastructure with sub-path exports: - -- **`auth/`** (`@spotify-confidence/testing/auth`) — JWT builders, token scaffolds -- **`scaffold/`** (`@spotify-confidence/testing/scaffold`) — Temp project directory factory -- **`env/`** (`@spotify-confidence/testing/env`) — Environment overlay, platform detection -- **`terminal/`** (`@spotify-confidence/testing/terminal`) — Key-map, key resolution -- **`msw/`** (`@spotify-confidence/testing/msw`) — MSW server setup + handlers - -Testing depends on `shared-kernel`. The main barrel (`@spotify-confidence/testing`) re-exports everything including MSW. Use specific sub-paths in e2e tests to avoid MSW's `localStorage` side effect. - -### Quickstart (`packages/quickstart/`) - -The TUI wizard, organized into: - -#### Commands (`src/commands/`) - -CLI command definitions using yargs. Each command is a self-contained module exporting a `Command` object. - -- Currently: `default` (launches TUI) and `help`. -- Commands orchestrate — they call into `src/ui/` but never contain UI rendering or framework detection logic themselves. - -#### Features (`src/features/`) - -Vertical feature slices that compose logic from multiple domains. Each feature gets its own subdirectory. - -- Currently: `onboarding/` — prompt builder for the project onboarding flow. -- Features may import from `@spotify-confidence/core` and `@spotify-confidence/shared-kernel`. -- Features must not import from `src/ui/` or `src/commands/`. - -#### UI (`src/ui/`) - -Terminal user interface built with Ink and React: - -- **`screens/`** — Every screen is organized as a **slice**: a subdirectory containing the screen component, a barrel `index.ts`, and collocated `log-messages.ts`, `telemetry-events.ts`, and `actions.ts`. Slices with side-effect hooks contain their hooks. Slices needing initial state at mount time use a `useInitial*` hook. Slices with sub-components place them in a collocated `components/` subdirectory. -- **`components/`** — Reusable building blocks (`TextBlock`, `Divider`, `ScreenContainer`, `KeyboardHintsBar`, etc.). Barrel-exported. -- **`styles.ts`** — Theme constants: `Colors`, `Icons`, `HAlign`, `VAlign`. -- **`hooks/`** — Shared hooks used across screens. Screen-specific hooks live in their slice. -- **`lib/`** — Shared utilities and types used across the TUI. -- **`store.ts`** — Reactive state via nanostores. -- **`router.ts`** — State-machine navigation via `WizardRouter`. -- **`screen-transitions.ts`** — Transition map defining valid navigation edges. -- **`screen-registry.tsx`** — Maps `ScreenId` → React component. - -## Hard Constraints - -### No product knowledge in the TUI - -The TUI is a generic wizard shell. It must not contain Confidence-specific domain logic. All product knowledge belongs in the Claude Code Skill and is delivered via MCP tools. - -### Dependency direction - -``` -quickstart commands → ui, features, core, shared-kernel -quickstart features → core, shared-kernel -quickstart ui → features, core, shared-kernel -core → shared-kernel -shared-kernel → nothing -``` - -No circular dependencies. No upward imports. - -Within the quickstart UI layer: - -``` -screen slices → hooks/, lib/, components/ -components/ → lib/, hooks/ -hooks/ → lib/ -lib/ → nothing in ui/ -``` - -### Screen identification - -Always use `ScreenId` enum values from `@spotify-confidence/core`. Never use raw strings. - -### State mutations - -All session state changes go through `WizardStore` setters. Never mutate the session object directly. - -### UI component sourcing - -Use `@inkjs/ui` components over standalone `ink-*` packages. - -## Coding Conventions - -### Initialization Hooks - -Slices that compute initial state at mount time use a dedicated `useInitial*` hook. This separates one-time "resolve initial state + sync to store" from ongoing interaction logic. - -Each init hook: (1) pure `resolve*` function, (2) `useEffect` to sync store, (3) returns values for the parent hook. - -### Dry Run Separation - -Hooks supporting dry-run mode keep dry-run logic in a separate function. Never interleave with conditionals. - -### React Hooks Rules - -`eslint-plugin-react-hooks` with `recommended-latest` rules, all set to `error`. - -### Path Aliases (Quickstart) - -Within `packages/quickstart/`, use path aliases (`@commands/`, `@features/`, `@ui/`) for cross-domain imports. Keep relative imports within the same domain. - -```ts -// Cross-domain within quickstart — use alias -import { WelcomeScreen } from '@ui/screens/welcome/index.js'; -import { buildPrompt } from '@features/onboarding/index.js'; - -// Cross-package — use npm name -import { ScreenId, track } from '@spotify-confidence/core'; -import type { IdeId } from '@spotify-confidence/shared-kernel'; - -// Within-domain — use relative -import { store } from '../../store.js'; -``` - -### TypeScript Style - -- Sort imports: system (node:*), react, external deps, cross-package (`@spotify-confidence/*`), aliases, relative. -- Use `type` instead of `interface`. -- Use latest TypeScript syntax: `satisfies`, `using`, etc. -- Object params for 4+ args. -- Named functions in `useEffect`. -- `AbortController` for event listener cleanup. -- Named types in `actions.ts` for union extensions. -- `satisfies never` in `switch` defaults. - -### Module Exports - -Keep the public API compact. Barrel files re-export only the public API. - -### Linting - -Strict linting — all rules are errors. ESLint config from `@spotify-confidence/eslint-config` (base) or `@spotify-confidence/eslint-config/react` (quickstart). - -### No Warning Suppression - -Never suppress runtime warnings or linter diagnostics. Fix the root cause. - -## Confidence MCP Tools - -The wizard works alongside two Confidence MCP servers: - -- **`confidence-flags`** — Feature flag management -- **`confidence-docs`** — Documentation access - -These are accessed via the Claude Code Skill, never directly from the TUI or CLI code. diff --git a/.claude/skills/wizard-development-harness/SKILL.md b/.claude/skills/wizard-development-harness/SKILL.md deleted file mode 100644 index e0364e0..0000000 --- a/.claude/skills/wizard-development-harness/SKILL.md +++ /dev/null @@ -1,117 +0,0 @@ -# Development Harness - -This skill defines the quality gates, commit conventions, and CI/CD processes for the Confidence Wizard CLI. Follow these when making changes, creating commits, or setting up automation. - -## Quality Harness - -All quality checks are available as individual scripts and as a combined `qa` script. - -### Scripts - -| Script | Purpose | -| ---------------- | ----------------------------------------- | -| `pnpm typecheck` | TypeScript type checking (`tsc --noEmit`) | -| `pnpm lint` | ESLint + Prettier check | -| `pnpm lint:fix` | ESLint auto-fix + Prettier write | -| `pnpm test` | Run vitest test suite | -| `pnpm qa` | Run all checks: typecheck, lint, test | - -### When to Run - -- **Before committing**: `pnpm qa` runs the full suite. Pre-commit hooks handle formatting automatically, but run `qa` to catch type errors and test failures early. -- **Before pushing**: Always run `pnpm qa` to ensure CI will pass. -- **After refactoring**: Run `pnpm qa` to verify nothing broke. - -## Pre-Commit Hooks - -Husky runs lint-staged on every commit: - -- **TypeScript/JavaScript files** (`*.{ts,tsx,js,jsx}`): ESLint auto-fix + Prettier format -- **Other files** (`*.{json,css,md}`): Prettier format - -This means staged files are always formatted and lint-clean before they enter the commit. - -## Commit Conventions - -All commits must follow [Conventional Commits](https://www.conventionalcommits.org/). A `commit-msg` hook enforces this via commitlint. - -### Format - -``` -(): - -[optional body] - -[optional footer(s)] -``` - -### Types - -| Type | When to Use | -| ---------- | ------------------------------------------------------- | -| `feat` | New feature (triggers minor version bump) | -| `fix` | Bug fix (triggers patch version bump) | -| `docs` | Documentation only | -| `style` | Formatting, whitespace (not CSS) | -| `refactor` | Code change that neither fixes a bug nor adds a feature | -| `perf` | Performance improvement | -| `test` | Adding or updating tests | -| `build` | Build system or external dependency changes | -| `ci` | CI configuration changes | -| `chore` | Maintenance tasks | - -### Breaking Changes - -Append `!` after the type/scope or include `BREAKING CHANGE:` in the footer. This triggers a major version bump. - -``` -feat!: remove legacy auth flow -``` - -### Examples - -``` -feat(ui): add framework selection screen -fix(frameworks): correct Next.js detection for app router -refactor: extract shared types to lib module -chore(deps): update ink to v6.9 -test: add coverage for wizard store reactivity -``` - -## CI/CD - -### Pull Request Checks (`.github/workflows/ci.yml`) - -Every PR against `main` runs two jobs: - -1. **Quality checks**: typecheck, lint, test -2. **Commit messages**: validates all PR commits follow conventional commits - -Both must pass before merging. - -### Release Automation (`.github/workflows/release.yml`) - -On push to `main`, release-please runs a two-step PR-based workflow: - -1. **Release PR**: release-please opens (or updates) a PR that accumulates changelog entries and version bumps from conventional commits -2. **Publish**: when the Release PR is merged, release-please creates a GitHub Release + git tag, and a separate `publish` job runs tests, builds, and publishes to npm - -**No manual version bumping.** The version is derived entirely from commit messages. Write meaningful conventional commits — release-please handles the rest. The Release PR gives the team a review window before each release ships. - -### Required Secrets - -- `GITHUB_TOKEN` — provided automatically by GitHub Actions -- `NPM_TOKEN` — must be configured in repository secrets for npm publishing - -## Configuration Files - -| File | Purpose | -| ------------------------------- | ------------------------------------------------------------------------- | -| `.commitlintrc.json` | Commitlint config (extends `@commitlint/config-conventional`) | -| `release-please-config.json` | Release-please config (release type, packages) | -| `.release-please-manifest.json` | Release-please version manifest (tracks current version) | -| `.lintstagedrc.json` | Lint-staged config (eslint + prettier for code, prettier for other files) | -| `.husky/pre-commit` | Runs lint-staged | -| `.husky/commit-msg` | Runs commitlint | -| `.github/workflows/ci.yml` | PR quality gate | -| `.github/workflows/release.yml` | Automated release on main | diff --git a/.claude/skills/wizard-integrations/SKILL.md b/.claude/skills/wizard-integrations/SKILL.md deleted file mode 100644 index 1330022..0000000 --- a/.claude/skills/wizard-integrations/SKILL.md +++ /dev/null @@ -1,142 +0,0 @@ ---- -name: integrations -description: IDE integration strategy pattern and guidelines for the Confidence Wizard CLI project -version: '0.2' ---- - -# IDE Integrations Guidelines - -This skill defines the structure, constraints, and conventions for IDE integrations in the Confidence Wizard CLI. Follow these when adding, modifying, or reviewing IDE-related code. - -## Purpose - -The `packages/core/src/integrations/` module encapsulates all IDE-specific behavior behind a strategy pattern. Each supported IDE (Claude Code, Cursor, Codex) has a self-contained implementation that conforms to a shared `IdeIntegration` interface. This eliminates per-IDE `switch` statements scattered across the codebase and makes adding a new IDE a single-directory change. - -## Module Structure - -``` -packages/core/src/integrations/ - types.ts # IdeId, IdeIntegration strategy type, McpConnectOpts - index.ts # Barrel exports + registry re-exports - registry.ts # getIntegrations(), getIntegration() — the strategy registry - chat.ts # buildChatPrompt() + launchChatSession() orchestrator - plugins.ts # detectInstalledPlugins() + installPlugin() orchestrators - shared.ts # Reusable helpers: PLUGIN_SKILLS, installSkills(), hasConfidenceServers() - claude/ - index.ts # claudeIntegration — composes strategy from submodules - paths.ts # Config file and directory paths - plugins.ts # detectPlugins(), installPlugins() - mcp.ts # detectMcpStatuses(), connectMcpServer() - onboarding.ts # runOnboarding() — spawns the IDE's onboarding process - cursor/ - index.ts, paths.ts, plugins.ts, mcp.ts, onboarding.ts # Same structure - codex/ - index.ts, paths.ts, plugins.ts, mcp.ts, onboarding.ts # Same structure - mcp/ - servers.ts # MCP_SERVERS, McpServerName, McpServerStatus, verifyMcpServer(), helpers - preference.ts # loadMcpPreference(), persistMcpPreference() - index.ts # Barrel exports -``` - -## The Strategy Pattern - -### `IdeIntegration` type - -Every IDE exports a single `IdeIntegration` object with these methods: - -- `id` — the `IdeId` string (`'claude' | 'cursor' | 'codex'`) -- `name` — human-readable label for UI display -- `launchChat(prompt, cwd)` — spawns a chat session in this IDE -- `runOnboarding(opts, callbacks)` — spawns the IDE's onboarding process with stdout/stderr streaming -- `detectPlugins(projectDir)` — checks if Confidence plugins are installed for this IDE -- `installPlugins(projectDir)` — installs MCP config and skills for this IDE -- `detectMcpStatuses(projectDir)` — detects MCP server connection statuses -- `connectMcpServer(opts)` — registers an MCP server for this IDE - -Each IDE owns the full implementation of all these methods. Shared helpers (`verifyMcpServer`, `installSkills`, `hasConfidenceServers`) are imported from `mcp/servers.ts` and `shared.ts`. - -### Shared types - -`IdeId` is defined in `packages/core/src/integrations/types.ts` — it belongs to the integrations module. `WizardSession` uses its own `IdeId` type (same string union, defined in `packages/core/src/session/session.ts`) to stay decoupled from the integrations module. This keeps the dependency direction clean: integrations never imports from lib/session for its own type definitions, and session never imports from integrations. - -### Orchestrators - -`chat.ts` and `plugins.ts` are thin orchestrators that resolve the IDE strategy and delegate: - -- `launchChatSession(session, ide)` — builds prompt, calls `integration.launchChat()` -- `detectInstalledPlugins(projectDir)` — iterates all integrations, calls `i.detectPlugins()` -- `installPlugin(ide, projectDir)` — calls `getIntegration(ide).installPlugins()` - -Consumers use orchestrators when they don't have the integration object, or use the strategy methods directly when they do. - -## Hard Constraints - -### IDE subdirs are self-contained - -Each IDE subdirectory (`claude/`, `cursor/`, `codex/`) must be fully independent: - -- No imports from other IDE subdirectories -- Each IDE subdir is split into `paths.ts`, `plugins.ts`, `mcp.ts`, and a slim `index.ts` that composes them -- IDE subdirs may import from `../types.js`, `../mcp/servers.js`, and `../shared.js` — never from `../registry.js` or each other - -### No switch-on-IDE outside strategy implementations - -All IDE-specific branching is encapsulated inside each strategy object. Code outside `packages/core/src/integrations/` must not `switch` on `IdeId` or branch on IDE identity. Instead, call `getIntegration(ide)` and use the strategy methods. - -### Dependency direction - -``` -integrations/index.ts → registry, chat, plugins, mcp/ -registry.ts → claude/, cursor/, codex/ -chat.ts, plugins.ts → registry.ts -claude/, cursor/, codex/ → types.ts, mcp/servers.ts, shared.ts (never registry or each other) -mcp/ → (no internal integrations imports) -shared.ts → (no internal integrations imports) -``` - -The integrations module imports from `@spotify-confidence/shared-kernel` and other core modules (for `IdeId`, `WizardSession`, constants). It never imports from `packages/quickstart/src/ui/` or `packages/quickstart/src/commands/`. - -### Clean-dev script - -When changing MCP-related code (config paths, server names, connection methods, permissions), verify that `scripts/clean-dev-env.sh` still correctly cleans up all IDE connections and artifacts. This script resets the local dev environment for clean-slate testing. If you add a new IDE, config path, or MCP registration method, update the script accordingly. - -## Adding a New IDE - -1. Create `packages/core/src/integrations//index.ts` -2. Export a `const Integration: IdeIntegration` with all required methods -3. Add the import and entry to the `INTEGRATIONS` array in `packages/core/src/integrations/registry.ts` -4. Update `scripts/clean-dev-env.sh` to clean the new IDE's config files and MCP entries -5. No other source files need to change — the registry, screens, and orchestrators all derive from the strategy - -## Public API - -The barrel `packages/core/src/integrations/index.ts` exports: - -- Types: `IdeId`, `IdeIntegration`, `McpConnectOpts`, `OnboardingOpts`, `OnboardingCallbacks`, `McpServerName`, `McpServerStatus` -- Registry: `getIntegrations()`, `getIntegration(id)` -- MCP: `MCP_SERVERS`, `allServersConnected()`, `getAvailableMcpServers()`, `verifyMcpServer()`, `loadMcpPreference()`, `persistMcpPreference()` -- Chat: `launchChatSession(session, ide)` -- Plugins: `detectInstalledPlugins(projectDir)`, `installPlugin(ide, projectDir)` - -Only import from the barrel or from specific submodules — never reach into an IDE's `index.ts` directly from outside the integrations module. - -## IDE-Specific Runtime Constraints - -### Codex: shell environment policy - -Codex `exec` mode defaults to a restricted shell environment — commands the agent runs do not inherit proxy settings, npm auth tokens, or registry config from the parent process. Without `shell_environment_policy.inherit="core"`, `npm install` falls back to direct connections that time out on corporate networks, turning a 15-second install into minutes of waiting. - -Always pass `-c 'shell_environment_policy.inherit="core"'` in Codex `exec` spawn args so the agent's commands see the core parent environment (PATH, HOME, proxy settings, etc.). - -### Codex: `item.completed` event batching - -Codex `exec --json` only emits `item.completed` events — not incremental text deltas. Status lines only surface after the agent finishes an entire message turn. If the agent does MCP calls, file reads, or package installs between messages, the user sees nothing until the next completed message. Claude Code and Cursor stream incrementally via `stream-json`, so their status updates appear in real time. - -## Coding Conventions - -Follow all conventions from the `wizard-architecture` skill. Additionally: - -- Use `type` over `interface` for all type definitions -- Strategy objects use `satisfies IdeIntegration` only if needed for type narrowing; otherwise the export type annotation is sufficient -- Private helpers within IDE subdirs should be plain functions, not methods on the strategy object -- The `connectMcpServer` method receives all server metadata via `McpConnectOpts` — it must not import `MCP_SERVERS` directly diff --git a/.claude/skills/wizard-testing/SKILL.md b/.claude/skills/wizard-testing/SKILL.md deleted file mode 100644 index ae713df..0000000 --- a/.claude/skills/wizard-testing/SKILL.md +++ /dev/null @@ -1,177 +0,0 @@ ---- -name: testing -description: > - Load before writing, modifying, or adding any test file (unit, - integration, or e2e). Covers testing philosophy, conventions, test - infrastructure (packages/testing/), test framework structure - (packages/quickstart/__tests__/e2e/testing-framework/, - packages/quickstart/__tests__/ui/testing-framework/), - and the named-key press() API for e2e tests. -version: '0.3' ---- - -# Testing Guidelines - -This skill defines the testing philosophy, conventions, and tooling for the Confidence Wizard CLI. - -## Philosophy - -Test **observable behavior**, never implementation details. - -Observable behavior is what a user or caller can see: rendered output, return values, emitted events, side effects on external systems. Implementation details are how the code achieves that: internal state shape, private method calls, execution order of internal steps. - -**When unsure whether something is observable behavior — ask before writing the test.** - -### Test Like a Real User - -Write tests that exercise the code the same way a real user would interact with it: - -- For TUI screens: assert on rendered terminal output (`lastFrame()`), never on store internals like `store.currentScreen` or `store.session.*`. - - Prefer `renderApp()` — it renders the full app with framework detection and screen transitions, relying on the project dir (via `createProjectDir()`) for context rather than manually injecting store props. - - Use `renderScreen()` when `renderApp()` is not feasible — e.g., a screen depends on state that is normally set by a prior screen in the flow. `renderScreen()` accepts a `framework` option and other store props that `renderApp()` does not. -- For store/state: assert on the public API and its effects, not on internal atom values. -- For CLI commands: test the command's output and side effects, not how it assembles arguments internally. - -## Mocking - -### API Calls — Use MSW - -For mocking HTTP/API calls, use [MSW (Mock Service Worker)](https://mswjs.io/). MSW intercepts requests at the network level, keeping the code under test unaware it's being mocked — which means the test exercises the real fetch/request logic. - -Do **not** mock `fetch` or HTTP clients directly with `vi.fn()` or `vi.mock()`. - -### General Mocking Rules - -- Only mock what crosses a **non-emulatable** system boundary. -- Prefer temp directories over mocking filesystem reads. Use `createProjectDir()` to set up real files. -- Prefer MSW over `vi.mock` for HTTP calls. -- When partial mocking is needed, use `importOriginal` to keep real functions and mock only what's necessary: - ```ts - vi.mock('@spotify-confidence/core', async (importOriginal) => { - const actual = await importOriginal(); - return { ...actual, detectInstalledPlugins: vi.fn().mockReturnValue([]) }; - }); - ``` -- Never mock the module under test. -- MSW server setup (`listen`, `resetHandlers`, `close`) belongs in `packages/testing/src/msw/setup.ts`, not in individual test files. - -## Tooling - -| Tool | Purpose | -| --------------------- | ------------------------------------ | -| `vitest` | Test runner (globals enabled) | -| `ink-testing-library` | TUI screen rendering and interaction | -| `msw` | Network-level API mocking | -| `waitFor` | Poll until an assertion passes | -| `node-pty` | E2E tests — spawns CLI in a real pty | - -## Test File Location - -Tests mirror the source structure within each package: - -``` -packages/core/__tests__/ - auth/ # Auth module tests - exec/ # Exec module tests - telemetry/ # Telemetry module tests - integrations/ # Integration tests - providers/ # Provider tests - -packages/quickstart/__tests__/ - commands/ # CLI command tests - features/ # Feature tests - ui/ # Integration tests (ink-testing-library) - testing-framework/ - ink/ # Ink rendering (renderScreen, renderApp, act) - mocks/ # Mock child process - async.ts # delay, waitFor - screens/ # Screen test files - e2e/ # End-to-end tests (node-pty) - testing-framework/ - terminal/ # PTY infrastructure (TerminalSession, screen buffer) - mocks/ # Mock HTTP server + mock IDE binaries - navigation.ts # Screen navigation shortcuts - session-factory.ts # createSession() factory - utils.ts # simulateAuthCallback, readInvocation - *.e2e.ts # E2E test files - -packages/testing/src/ # Shared test infrastructure - auth/ # JWT builders, token scaffolds - scaffold/ # Project directory factory - env/ # Environment overlay, platform detection - terminal/ # Key-map, key resolution - msw/ # MSW server + handlers -``` - -### Test infrastructure imports - -Import from specific sub-paths to avoid pulling in unrelated modules: - -```ts -import { buildTestJwt, prepareAuthTokens } from '@spotify-confidence/testing/auth'; -import { createProjectDir } from '@spotify-confidence/testing/scaffold'; -import { isWindows } from '@spotify-confidence/testing/env'; -import { resolveKey } from '@spotify-confidence/testing/terminal'; -import { server } from '@spotify-confidence/testing'; // MSW server (main barrel) -``` - -## E2E Tests - -E2E tests spawn the **built CLI binary** (`packages/quickstart/dist/bin/cli.js`) in a real pseudo-terminal via `node-pty`, send keystrokes, and assert on terminal output. They exercise real code paths — not the dry-run stubs. - -### Running - -```bash -pnpm test:e2e # Build + run all e2e tests -``` - -E2E tests are **not** included in `pnpm test` or `pnpm qa`. They run in a separate CI job. - -### Config - -E2E tests use a dedicated vitest config (`packages/quickstart/vitest.config.e2e.ts`) with: - -- 120s test timeout (the full wizard flow takes ~12s) -- Serial execution (`maxWorkers: 1`) -- No MSW setup (HTTP is mocked via a real local server) -- Global setup in `packages/quickstart/__tests__/e2e/global-setup.ts` - -### Testing Framework (`packages/quickstart/__tests__/e2e/testing-framework/`) - -- **`createSession(opts?)`** (`session-factory.ts`) — spawns the CLI in a pty with an isolated temp project dir. Pass `{ project: 'empty' }` for an empty project (no `package.json`). Returns a `TerminalSession` with `[Symbol.dispose]`. -- **`TerminalSession`** (`terminal/session.ts`) — wraps node-pty. Key methods: `press(key)` (named keys like `'Enter'`, `'ArrowDown'`), `pressRepeat(key, count)`, `waitForText(text)`, `waitForPattern(regex)`, `waitForExit()`, `checkpoint()`, `snapshot()`, `screen` (full ANSI-stripped output). -- **`simulateAuthCallback()`** (`utils.ts`) — hits the CLI's local OAuth callback server to simulate browser auth. -- **`navigateToPlugins/ConnectTools/Onboarding(session)`** (`navigation.ts`) — navigation shortcuts that advance through earlier screens. -- **Mock HTTP server** (`mocks/server.ts`) — started in global setup, mimics all Confidence APIs. The CLI's API URLs are configurable via env vars, which the global setup points at the local server. -- **Mock IDE binaries** (`mocks/binaries/`) — `claude`, `cursor`, `codex` mock scripts placed on PATH. -- **Shared test scaffolds** — imported from `@spotify-confidence/testing/auth`, `@spotify-confidence/testing/scaffold`, `@spotify-confidence/testing/terminal`, etc. - -### Writing E2E Tests - -- **File naming**: `*.e2e.ts` (not `.test.ts`) -- **One concern per file**: group related scenarios. -- **Use `createSession()` per test** — each call creates a fresh project dir for full isolation. -- **Use `using`** for automatic cleanup: `using session = createSession()`. -- **Use named keys** with `session.press('Enter')`, `session.press('ArrowDown')`. -- **Assert positively** — prefer `waitForText('expected')` over `not.toContain('unexpected')`. -- **Use `checkpoint()`** between screens to scope `waitForText` and `snapshot()` to the current screen. -- **Use navigation helpers** to skip past earlier screens. - -## Test Structure - -Use the **Arrange-Act-Assert (AAA)** pattern in every test. Name the system under test variable **`sut`**. - -- If the test body is **3 lines or fewer**, no blank lines or comments are needed. -- If **longer than 3 lines**, add empty lines between AAA sections. -- If **each section is longer than 3 lines**, also add `// Arrange`, `// Act`, `// Assert` comments. - -## Conventions - -- Test files use `.test.ts` or `.test.tsx` extension. -- **`it`/`test` names** describe public behavior from the consumer's point of view. -- **`describe` blocks** state prerequisites or context. -- One assertion concern per test — multiple `expect` calls are fine if they assert the same behavior. -- No snapshot tests unless explicitly requested. -- **Prefer `createProjectDir()`** for setting up project context in TUI screen tests. Import from `@spotify-confidence/testing/scaffold`. -- **Prefer `using`** for disposable resources. -- **Use `waitFor` instead of `await delay`** for TUI assertions. diff --git a/.claude/skills/wizard-workflows/SKILL.md b/.claude/skills/workflows/SKILL.md similarity index 97% rename from .claude/skills/wizard-workflows/SKILL.md rename to .claude/skills/workflows/SKILL.md index 1b0110c..8104750 100644 --- a/.claude/skills/wizard-workflows/SKILL.md +++ b/.claude/skills/workflows/SKILL.md @@ -1,3 +1,9 @@ +--- +name: workflows +description: GitHub Actions workflow security rules and conventions (hash-pinned actions, minimal permissions, injection prevention) +version: '0.2' +--- + # GitHub Actions Workflows Guidelines for writing and modifying GitHub Actions workflows in this project. Follow these when creating or editing files under `.github/workflows/`. diff --git a/AGENTS.md b/AGENTS.md index 4cdc000..36a9647 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,10 +1,10 @@ -# Confidence Wizard +# Confidence CLI -CLI wizard for setting up and integrating [Confidence](https://confidence.spotify.com/) with user projects. +CLI tools for setting up and integrating [Confidence](https://confidence.spotify.com/) with user projects. ## Monorepo Structure -pnpm workspace with five packages under `packages/`: +pnpm workspace with six packages under `packages/`: | Package | Published | Purpose | | ------------------------- | -------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | @@ -13,6 +13,7 @@ pnpm workspace with five packages under `packages/`: | `packages/core/` | No (private) | Shared infrastructure — auth, session, telemetry, exec, system, sdk, utils, constants, frameworks, integrations, providers. Depends on `shared-kernel`. | | `packages/testing/` | No (private) | Test infrastructure — auth scaffolds, project scaffolds, env helpers, terminal helpers, MSW handlers. Sub-path exports: `/auth`, `/scaffold`, `/env`, `/terminal`, `/msw`. Depends on `shared-kernel`. | | `packages/quickstart/` | Yes (`@spotify-confidence/quickstart`) | Interactive TUI wizard. Depends on `core` and `shared-kernel`. | +| `packages/cli/` | Yes (`@spotify-confidence/cli`) | CLI for managing Confidence (flags, events, recordings, config). Depends on `quickstart`. | ### Dependency graph @@ -21,7 +22,8 @@ shared-kernel (types-only leaf) ▲ ├── core (infrastructure) ├── testing (test scaffolds) - └── quickstart (TUI wizard) ──► core + ├── quickstart (TUI wizard) ──► core + └── cli (CLI) ──► quickstart ──► core ``` ### packages/core/ modules @@ -45,6 +47,13 @@ shared-kernel (types-only leaf) - **`src/features/`** — Vertical feature slices (`onboarding/` prompt builder) - **`src/ui/`** — Ink/React TUI (screens, components, hooks, theme, store, router) +### packages/cli/ structure + +- **`bin/cli.ts`** — Entry point (yargs, `confidence` binary) +- **`src/commands/`** — Command definitions (login, logout, whoami, config, flags, events, recordings, quickstart) +- **`src/features/`** — Feature implementations (config management, quickstart launcher) +- **`src/output/`** — Output formatters (json, table, format detection) + ## Key Patterns - **Reactive state**: `WizardStore` uses nanostores atoms. Screens subscribe via `useSyncExternalStore`. @@ -58,7 +67,8 @@ shared-kernel (types-only leaf) ```bash pnpm install # Install all workspace deps pnpm --filter @spotify-confidence/quickstart try # Run the wizard locally via tsx -pnpm test # Run all tests (core + quickstart) +pnpm --filter @spotify-confidence/cli try # Run the CLI locally via tsx +pnpm test # Run all tests (core + quickstart + cli) pnpm test:e2e # Build + run quickstart e2e tests pnpm lint # ESLint + Prettier check across all packages pnpm typecheck # TypeScript type checking across all packages @@ -71,7 +81,9 @@ Per-package commands: ```bash pnpm --filter @spotify-confidence/core test # Core unit tests only pnpm --filter @spotify-confidence/quickstart test # Quickstart unit tests only +pnpm --filter @spotify-confidence/cli test # CLI unit tests only pnpm --filter @spotify-confidence/quickstart build # Build quickstart for distribution +pnpm --filter @spotify-confidence/cli build # Build CLI for distribution ``` ## Tech Stack @@ -87,7 +99,7 @@ pnpm --filter @spotify-confidence/quickstart build # Build quickstart for dis ## Confidence MCP Tools -The wizard works alongside Confidence MCP servers: +The CLI works alongside Confidence MCP servers: - `confidence-flags` — Feature flag management (create, list, resolve, target, archive) - `confidence-docs` — Documentation search and SDK integration guides @@ -108,6 +120,7 @@ The stable `node-pty` release (v1.1.0) doesn't ship prebuilt binaries for Node.j - **Cross-package imports** use npm package names: `import { authenticate } from '@spotify-confidence/core'`, `import type { IdeId } from '@spotify-confidence/shared-kernel'`. - **Within quickstart**, use path aliases (`@commands/*`, `@features/*`, `@ui/*`) for cross-domain imports. Keep relative imports within the same domain. +- **Within cli**, use path aliases (`@commands/*`, `@features/*`, `@output/*`, `@api/*`) for cross-domain imports. Keep relative imports within the same domain. - **Within core source** (`packages/core/src/`), use relative imports. Core's `__tests__/` may use tsconfig path aliases (`@auth/*`, `@integrations/*`, etc.). - **Test imports** from `@spotify-confidence/testing` use sub-path exports: `@spotify-confidence/testing/auth`, `@spotify-confidence/testing/scaffold`, `@spotify-confidence/testing/env`, `@spotify-confidence/testing/terminal`. - Use `@inkjs/ui` components over standalone `ink-*` packages. @@ -119,19 +132,24 @@ The stable `node-pty` release (v1.1.0) doesn't ship prebuilt binaries for Node.j - Prefer `AbortController` for removing event listeners instead of manual `removeEventListener`. - All commits must follow Conventional Commits. The `commit-msg` hook enforces this via commitlint. - Run `pnpm qa` before pushing to ensure CI will pass. -- When writing or modifying code, always use the `wizard-architecture` skill first to load the project's architecture and coding conventions. -- When writing, modifying, or adding any test file (unit, integration, or e2e), always use the `wizard-testing` skill first to load the project's testing guidelines, conventions, and test framework structure. -- When making commits or working with the CI/release pipeline, use the `wizard-development-harness` skill for guidelines. +- When writing or modifying code, always use the `architecture` and `coding-conventions` skills first. +- When writing, modifying, or adding any test file (unit, integration, or e2e), always use the `testing` skill first. For e2e tests, also load `testing-e2e`. +- When working on the `packages/cli/` package, load the `cli` skill. +- When making commits or working with the CI/release pipeline, use the `development-harness` skill for guidelines. ## Skills (Mandatory) Before making any changes, agents MUST load the relevant skill(s) from `.claude/skills/`. These skills contain the authoritative guidelines for this project — architecture constraints, coding conventions, testing philosophy, and development harness rules. Skipping them leads to guideline violations. -| Skill | When to load | Key rules | -| ---------------------------- | --------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -| `wizard-architecture` | Any code change | Cross-package imports, dependency direction, dry-run separation, initialization hooks, TypeScript style (`type` over `interface`, `satisfies never` in switch defaults, object params for 4+ args), module exports | -| `wizard-testing` | Any test change or addition | Observable behavior only, AAA pattern, `sut` naming, `using` for disposables, `waitFor` over `delay`, MSW for HTTP mocks, `@spotify-confidence/testing` sub-path imports, `press('Enter')` for e2e keys | -| `wizard-ink-tui` | Any TUI/screen change | Ink rendering model, `@inkjs/ui` over standalone packages, `Colors`/`Icons`/`HAlign`/`VAlign` from `styles.ts`, named functions in `useEffect` | -| `wizard-integrations` | IDE integration changes | Strategy pattern, self-contained IDE subdirs, adding new IDEs, MCP/chat/plugin flows | -| `wizard-development-harness` | Commits, CI, releases | Conventional Commits, `pnpm qa` before push, pre-commit hooks, release-please | -| `wizard-workflows` | Workflow changes | Hash-pinned actions with version comments, minimal permissions, per-secret references | +| Skill | When to load | Key rules | +| --------------------- | --------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------- | +| `architecture` | Any code change | Monorepo structure, dependency graph, domain separation, package boundaries, cross-package imports | +| `coding-conventions` | Any code change | TypeScript style (`type` over `interface`, `satisfies never`, object params for 4+ args), import ordering, module exports, linting, React hooks | +| `auth` | Authentication changes | OAuth PKCE flow, token persistence, Auth0 config, JWT handling, regional endpoints | +| `cli` | `packages/cli/` changes | Command architecture, output formatting, config feature, quickstart integration, path aliases | +| `ink-tui` | Any TUI/screen change | Ink rendering model, `@inkjs/ui` over standalone packages, `Colors`/`Icons`/`HAlign`/`VAlign` from `styles.ts`, named functions in `useEffect` | +| `integrations` | IDE integration changes | Strategy pattern, self-contained IDE subdirs, adding new IDEs, MCP/chat/plugin flows | +| `testing` | Any test change or addition | Observable behavior only, AAA pattern, `sut` naming, `using` for disposables, `waitFor` over `delay`, MSW for HTTP mocks | +| `testing-e2e` | E2E test changes | node-pty framework, `createSession()`, `press('Enter')`, `waitForText`, `checkpoint`, navigation helpers | +| `development-harness` | Commits, CI, releases | Conventional Commits, `pnpm qa` before push, pre-commit hooks, release-please | +| `workflows` | Workflow changes | Hash-pinned actions with version comments, minimal permissions, per-secret references |