From f0d9cab51110f2e7a410831bf8f999b133c095ff Mon Sep 17 00:00:00 2001 From: Alex Bespoyasov Date: Fri, 2 Oct 2026 11:10:46 +0200 Subject: [PATCH 1/3] chore: restructure skills to reflect updated project structure --- .../SKILL.md | 147 +++++++--------- .claude/skills/{wizard-auth => auth}/SKILL.md | 24 ++- .claude/skills/cli/SKILL.md | 150 ++++++++++++++++ .claude/skills/coding-conventions/SKILL.md | 163 ++++++++++++++++++ .../SKILL.md | 13 +- .../{wizard-ink-tui => ink-tui}/SKILL.md | 8 +- .../SKILL.md | 23 +-- .claude/skills/testing-e2e/SKILL.md | 92 ++++++++++ .../{wizard-testing => testing}/SKILL.md | 69 ++------ .../{wizard-workflows => workflows}/SKILL.md | 6 + AGENTS.md | 52 ++++-- 11 files changed, 557 insertions(+), 190 deletions(-) rename .claude/skills/{wizard-architecture => architecture}/SKILL.md (59%) rename .claude/skills/{wizard-auth => auth}/SKILL.md (76%) create mode 100644 .claude/skills/cli/SKILL.md create mode 100644 .claude/skills/coding-conventions/SKILL.md rename .claude/skills/{wizard-development-harness => development-harness}/SKILL.md (92%) rename .claude/skills/{wizard-ink-tui => ink-tui}/SKILL.md (92%) rename .claude/skills/{wizard-integrations => integrations}/SKILL.md (83%) create mode 100644 .claude/skills/testing-e2e/SKILL.md rename .claude/skills/{wizard-testing => testing}/SKILL.md (60%) rename .claude/skills/{wizard-workflows => workflows}/SKILL.md (97%) diff --git a/.claude/skills/wizard-architecture/SKILL.md b/.claude/skills/architecture/SKILL.md similarity index 59% rename from .claude/skills/wizard-architecture/SKILL.md rename to .claude/skills/architecture/SKILL.md index a1c1fd2..8cc5674 100644 --- a/.claude/skills/wizard-architecture/SKILL.md +++ b/.claude/skills/architecture/SKILL.md @@ -1,33 +1,34 @@ --- name: architecture -description: Architecture guidelines and constraints for the Confidence Wizard CLI project -version: '0.2' +description: Monorepo structure, dependency graph, domain boundaries, and package-level constraints for the Confidence CLI project +version: '0.3' --- # 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. +This skill defines the structural rules, domain boundaries, and constraints that govern all work on the Confidence 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. +The Confidence CLI is a set of tools for 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: +The project is a pnpm monorepo with six 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` | +| 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, utils, frameworks, integrations, providers) | +| `packages/testing/` | No | Test infrastructure (auth scaffolds, project scaffolds, env helpers, terminal helpers, MSW) | +| `packages/quickstart/` | Yes | TUI wizard — `@spotify-confidence/quickstart` | +| `packages/cli/` | Yes | CLI for managing Confidence — `@spotify-confidence/cli` | ### Dependency Graph ``` -shared-kernel ◄── core ◄── quickstart +shared-kernel ◄── core ◄── quickstart ◄── cli ▲ └── testing ``` @@ -36,6 +37,7 @@ shared-kernel ◄── core ◄── quickstart - **core** → shared-kernel - **testing** → shared-kernel (devDep on core for tests only) - **quickstart** → core, shared-kernel (devDep on testing, eslint-config) +- **cli** → quickstart (devDep on core, shared-kernel, testing, eslint-config) ### Cross-Package Imports @@ -58,6 +60,15 @@ Within `packages/quickstart/`, use path aliases for cross-domain imports: | `@features/*` | `src/features/*` | | `@ui/*` | `src/ui/*` | +Within `packages/cli/`, use path aliases for cross-domain imports: + +| Alias | Target | +| ------------- | ---------------- | +| `@commands/*` | `src/commands/*` | +| `@features/*` | `src/features/*` | +| `@output/*` | `src/output/*` | +| `@api/*` | `src/api/*` | + 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 @@ -87,7 +98,7 @@ Shared infrastructure organized into cohesive modules: - **`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`. +Core depends on `shared-kernel`. It must not import from `quickstart`, `cli`, or `testing`. ### Testing (`packages/testing/src/`) @@ -109,7 +120,6 @@ The TUI wizard, organized into: 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/`) @@ -134,6 +144,36 @@ Terminal user interface built with Ink and React: - **`screen-transitions.ts`** — Transition map defining valid navigation edges. - **`screen-registry.tsx`** — Maps `ScreenId` → React component. +### CLI (`packages/cli/`) + +The `confidence` CLI, organized into: + +#### Entry Point (`bin/cli.ts`) + +Uses yargs to define the CLI with global options (`--json`, `--output`, `--project`, `--environment`, `--profile`, `--dry-run`, `--debug`) and commands. + +#### Commands (`src/commands/`) + +Each command exports an object with `command`, `describe`, `builder` (optional), and `handler` properties. Commands with subcommands (e.g. `config`, `flags`, `events`, `recordings`) use nested yargs builders. + +- **`login`** / **`logout`** / **`whoami`** — Auth commands delegating to `@spotify-confidence/core` +- **`config`** — Persistent configuration management (set/get/list/reset) +- **`flags`** / **`events`** / **`recordings`** — Feature-specific setup commands delegating to quickstart +- **`quickstart`** — Launches the interactive TUI wizard + +#### Features (`src/features/`) + +- **`config/`** — Re-exports config operations from core +- **`quickstart/`** — Launches the quickstart TUI with feature pre-selection + +#### Output (`src/output/`) + +Structured output formatting with automatic format detection: + +- **`detect.ts`** — `resolveFormat()`: `--json` flag → JSON; `--output` flag → specified; TTY → table; pipe → JSON +- **`json.ts`** — `formatJson()`: wraps data in `{ data, meta? }` envelope +- **`table.ts`** — `formatTable()`: dynamically-sized column layout + ## Hard Constraints ### No product knowledge in the TUI @@ -143,11 +183,13 @@ The TUI is a generic wizard shell. It must not contain Confidence-specific domai ### 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 +cli commands → features, output, quickstart, core, shared-kernel +cli features → quickstart, core, shared-kernel +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. @@ -172,68 +214,3 @@ All session state changes go through `WizardStore` setters. Never mutate the ses ### 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-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..fd92de8 --- /dev/null +++ b/.claude/skills/cli/SKILL.md @@ -0,0 +1,150 @@ +--- +name: cli +description: Structure, commands, output formatting, and conventions for the packages/cli/ package +version: '0.1' +--- + +# CLI Package + +This skill covers the `packages/cli/` package — the `confidence` CLI binary for managing Confidence feature flags, events, session recordings, and configuration. + +## Package Overview + +The CLI wraps the quickstart TUI wizard and adds standalone commands for auth and config management. It is published as `@spotify-confidence/cli` and installs the `confidence` binary. + +### Dependencies + +- **Runtime**: `@spotify-confidence/quickstart`, `yargs` +- **Dev**: `@spotify-confidence/core`, `shared-kernel`, `testing`, `eslint-config` + +### Build + +tsdown bundles `core` and `shared-kernel` into the binary. `quickstart` stays external (dynamically imported at runtime). Target: Node 24+ ESM. + +## Directory Structure + +``` +packages/cli/ +├── bin/cli.ts # Entry point — yargs CLI +├── src/ +│ ├── commands/ # Command definitions +│ │ ├── index.ts # Barrel +│ │ ├── types.ts # GlobalFlags type +│ │ ├── login.ts # OAuth login +│ │ ├── logout.ts # Clear credentials +│ │ ├── whoami.ts # Show current identity +│ │ ├── config.ts # config set/get/list/reset +│ │ ├── flags.ts # flags setup +│ │ ├── events.ts # events setup +│ │ ├── recordings.ts # recordings setup +│ │ └── quickstart.ts # Launch TUI wizard +│ ├── features/ # Feature implementations +│ │ ├── config/ # Re-exports from core +│ │ └── quickstart/ # Launch helper with feature mapping +│ └── output/ # Output formatters +│ ├── detect.ts # resolveFormat() +│ ├── json.ts # formatJson() +│ └── table.ts # formatTable() +└── __tests__/ # Tests +``` + +## Path Aliases + +| Alias | Target | +| ------------- | ---------------- | +| `@commands/*` | `src/commands/*` | +| `@features/*` | `src/features/*` | +| `@output/*` | `src/output/*` | +| `@api/*` | `src/api/*` | + +## Command Architecture + +Each command exports an object with the yargs command shape: + +```ts +export const exampleCommand = { + command: 'example', + describe: 'One-line description', + builder(yargs: Argv) { ... }, // optional — for subcommands or extra options + async handler(argv: Record) { ... }, +}; +``` + +### Global Options + +All commands receive these from the root yargs instance: + +| Flag | Type | Purpose | +| --------------- | ------- | -------------------------------- | +| `--json` | boolean | Force JSON output | +| `--output` | string | Output format (json/table/plain) | +| `--project` | string | Override project from config | +| `--environment` | string | Override environment | +| `--profile` | string | Named auth profile | +| `--dry-run` | boolean | Preview without executing | +| `--debug` | boolean | Verbose output | + +### Command Types + +**Standalone commands** — directly perform their action: + +- `login`, `logout`, `whoami`, `config` + +**Setup commands** — delegate to the quickstart TUI with pre-selected features: + +- `flags setup`, `events setup`, `recordings setup` + +**TUI launcher** — launches the full interactive wizard: + +- `quickstart` + +## Output Formatting + +### Format Resolution (`resolveFormat`) + +Priority: `--json` flag → `--output` flag → TTY detection (TTY → table, pipe → JSON). + +### JSON Envelope (`formatJson`) + +All JSON output wraps data in a standard envelope: + +```json +{ + "data": { ... }, + "meta": { ... } +} +``` + +The `meta` field is omitted when empty. + +### Table Formatter (`formatTable`) + +Renders rows with dynamically-sized columns. Supports fixed-width overrides per column. Uses Unicode box-drawing separators. + +## Quickstart Integration + +The `launchQuickstart()` helper in `src/features/quickstart/launch.ts`: + +1. Maps feature names to goal IDs: `flags` → `feature-flags`, `events` → `event-tracking`, `recordings` → `session-recordings` +2. Dynamically imports `startTui` from `@spotify-confidence/quickstart` +3. Passes through `--dir`, `--dry-run`, `--debug`, and `--no-telemetry` options + +## Config Feature + +The `config` command manages persistent key-value configuration stored in `$CONFIDENCE_CONFIG_DIR/config.json`. Valid keys: `project`, `environment`, `output`, `profile`, `ide`. Implementation delegates to `@spotify-confidence/core`. + +## 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. +- Output formatting goes through `src/output/` — commands never call `JSON.stringify` directly. +- The CLI must not import from quickstart's internal modules — only from its public `startTui` export. + +## Development + +```bash +pnpm --filter @spotify-confidence/cli try # Run CLI locally via tsx +pnpm --filter @spotify-confidence/cli test # Run unit tests +pnpm --filter @spotify-confidence/cli build # Build for distribution +pnpm --filter @spotify-confidence/cli qa # Full quality check +``` diff --git a/.claude/skills/coding-conventions/SKILL.md b/.claude/skills/coding-conventions/SKILL.md new file mode 100644 index 0000000..fa3c35b --- /dev/null +++ b/.claude/skills/coding-conventions/SKILL.md @@ -0,0 +1,163 @@ +--- +name: coding-conventions +description: TypeScript style, import ordering, module exports, and linting rules for the Confidence CLI monorepo +version: '0.1' +--- + +# Coding Conventions + +This skill defines the TypeScript style, import ordering, module export patterns, and linting rules that apply across all packages in the monorepo. Follow these in every code change. + +## TypeScript Style + +### Imports + +Sort imports in this order, separated by blank lines: + +1. Node built-ins (`node:*`) +2. React +3. External dependencies +4. Cross-package (`@spotify-confidence/*`) +5. Path aliases (`@commands/*`, `@features/*`, `@ui/*`, `@output/*`) +6. Relative imports + +### Type Definitions + +Use `type` instead of `interface`: + +```ts +// Correct +type UserInfo = { + email: string; + region: string; +}; + +// Wrong +interface UserInfo { + email: string; + region: string; +} +``` + +### Function Parameters + +Use object params when a function has 4 or more arguments: + +```ts +// Correct — 4+ params use an object +function createSession({ ide, region, goals, debug }: CreateSessionOpts) { ... } + +// Wrong — too many positional args +function createSession(ide: IdeId, region: string, goals: string[], debug: boolean) { ... } +``` + +### Switch Exhaustiveness + +Use `satisfies never` in switch defaults to catch unhandled cases at compile time: + +```ts +switch (action) { + case 'login': + return handleLogin(); + case 'logout': + return handleLogout(); + default: + return action satisfies never; +} +``` + +### Latest TypeScript Syntax + +Use modern TypeScript features: `satisfies`, `using` for disposables, etc. + +## React / Hooks + +### Named Functions in `useEffect` + +Use named function expressions, not arrow functions: + +```ts +// Correct +useEffect( + function autoAdvance() { + // ... + }, + [deps], +); + +// Wrong +useEffect(() => { + // ... +}, [deps]); +``` + +### Event Listener Cleanup + +Use `AbortController` for removing event listeners instead of manual `removeEventListener`: + +```ts +useEffect(function listenForResize() { + const controller = new AbortController(); + window.addEventListener('resize', handleResize, { signal: controller.signal }); + return () => controller.abort(); +}, []); +``` + +### React Hooks Linting + +`eslint-plugin-react-hooks` with `recommended-latest` rules, all set to `error`. + +## Module Exports + +Keep the public API compact. Barrel files (`index.ts`) re-export only the public API. Don't re-export internal helpers or types that aren't part of the module's contract. + +### Named Types in Actions + +Use named types in `actions.ts` files for union extensions. This allows new action variants to be added without modifying existing switch statements across the codebase. + +## Cross-Package Imports + +Use the npm package name — never relative paths across package boundaries: + +```ts +// Cross-package — use npm name +import { ScreenId, track } from '@spotify-confidence/core'; +import type { IdeId } from '@spotify-confidence/shared-kernel'; + +// Cross-domain within a package — use path alias +import { WelcomeScreen } from '@ui/screens/welcome/index.js'; +import { buildPrompt } from '@features/onboarding/index.js'; + +// Within-domain — use relative +import { store } from '../../store.js'; +``` + +Within `packages/core/src/`, always use relative imports (no path aliases — enables external consumers to follow source imports). Core `__tests__/` may use tsconfig path aliases. + +## 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 dry-run and real logic with conditionals. + +## Linting + +Strict linting — all rules are errors. ESLint config from `@spotify-confidence/eslint-config` (base) or `@spotify-confidence/eslint-config/react` (for packages with React). + +### No Warning Suppression + +Never suppress runtime warnings or linter diagnostics. Fix the root cause. + +## Enum Usage + +Use `ScreenId` enum values from `@spotify-confidence/core` for screen identification. Never use raw strings. + +Use `HAlign` / `VAlign` enums from `styles.ts` instead of raw alignment strings (`'flex-start'`, `'center'`, `'flex-end'`). + +## Theme Constants + +Import `Colors` and `Icons` from `packages/quickstart/src/ui/styles.ts` for consistent theming. Never use raw color values or emoji directly. diff --git a/.claude/skills/wizard-development-harness/SKILL.md b/.claude/skills/development-harness/SKILL.md similarity index 92% rename from .claude/skills/wizard-development-harness/SKILL.md rename to .claude/skills/development-harness/SKILL.md index e0364e0..5ae3b31 100644 --- a/.claude/skills/wizard-development-harness/SKILL.md +++ b/.claude/skills/development-harness/SKILL.md @@ -1,6 +1,12 @@ +--- +name: development-harness +description: Quality gates, commit conventions, pre-commit hooks, and CI/CD processes for the Confidence CLI monorepo +version: '0.2' +--- + # 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. +This skill defines the quality gates, commit conventions, and CI/CD processes for the Confidence CLI monorepo. Follow these when making changes, creating commits, or setting up automation. ## Quality Harness @@ -71,11 +77,12 @@ feat!: remove legacy auth flow ### Examples ``` +feat(cli): add whoami command feat(ui): add framework selection screen fix(frameworks): correct Next.js detection for app router -refactor: extract shared types to lib module +refactor(core): extract shared types to lib module chore(deps): update ink to v6.9 -test: add coverage for wizard store reactivity +test(cli): add coverage for config management ``` ## CI/CD 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/wizard-integrations/SKILL.md b/.claude/skills/integrations/SKILL.md similarity index 83% rename from .claude/skills/wizard-integrations/SKILL.md rename to .claude/skills/integrations/SKILL.md index 1330022..b1bf423 100644 --- a/.claude/skills/wizard-integrations/SKILL.md +++ b/.claude/skills/integrations/SKILL.md @@ -1,12 +1,12 @@ --- name: integrations -description: IDE integration strategy pattern and guidelines for the Confidence Wizard CLI project -version: '0.2' +description: IDE integration strategy pattern and guidelines for the packages/core/src/integrations/ module +version: '0.3' --- # 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. +This skill defines the structure, constraints, and conventions for IDE integrations. Follow these when adding, modifying, or reviewing IDE-related code. ## Purpose @@ -94,11 +94,11 @@ 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/`. +The integrations module imports from `@spotify-confidence/shared-kernel` and other core modules. It never imports from `packages/quickstart/` or `packages/cli/`. ### 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. +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. If you add a new IDE, config path, or MCP registration method, update the script accordingly. ## Adding a New IDE @@ -124,19 +124,10 @@ Only import from the barrel or from specific submodules — never reach into an ### 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. +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. 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 +Codex `exec --json` only emits `item.completed` events — not incremental text deltas. Status lines only surface after the agent finishes an entire message turn. Claude Code and Cursor stream incrementally via `stream-json`, so their status updates appear in real time. diff --git a/.claude/skills/testing-e2e/SKILL.md b/.claude/skills/testing-e2e/SKILL.md new file mode 100644 index 0000000..686a7fd --- /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 for the quickstart TUI. +version: '0.1' +--- + +# E2E Testing + +This skill covers end-to-end tests that spawn the built CLI binary in a real pseudo-terminal. Load the `testing` skill first for general testing philosophy and conventions — this skill adds the e2e-specific framework. + +## Overview + +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/`) + +### Session Factory + +- **`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]`. + +### Terminal Session + +- **`TerminalSession`** (`terminal/session.ts`) — wraps node-pty. Key methods: + - `press(key)` — named keys like `'Enter'`, `'ArrowDown'` + - `pressRepeat(key, count)` — repeat a key press + - `waitForText(text)` — wait until text appears in output + - `waitForPattern(regex)` — wait until regex matches output + - `waitForExit()` — wait for process to terminate + - `checkpoint()` — mark a screen boundary for scoped assertions + - `snapshot()` — get current screen content (after last checkpoint) + - `screen` — full ANSI-stripped output + +### Utilities + +- **`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. +- **`readInvocation(session)`** (`utils.ts`) — reads invocation output from the terminal. + +### Mock Infrastructure + +- **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. + +## Example + +```ts +import { createSession } from './testing-framework/session-factory.js'; +import { navigateToPlugins } from './testing-framework/navigation.js'; + +describe('plugin installation', () => { + it('installs plugins for the detected IDE', async () => { + using session = createSession(); + + await navigateToPlugins(session); + await session.waitForText('Install plugins'); + session.press('Enter'); + + await session.waitForText('Plugins installed'); + }); +}); +``` diff --git a/.claude/skills/wizard-testing/SKILL.md b/.claude/skills/testing/SKILL.md similarity index 60% rename from .claude/skills/wizard-testing/SKILL.md rename to .claude/skills/testing/SKILL.md index ae713df..64fafa8 100644 --- a/.claude/skills/wizard-testing/SKILL.md +++ b/.claude/skills/testing/SKILL.md @@ -1,18 +1,14 @@ --- 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 philosophy, conventions, mocking rules, and UI test + framework for unit and integration tests across all packages. +version: '0.4' --- # Testing Guidelines -This skill defines the testing philosophy, conventions, and tooling for the Confidence Wizard CLI. +This skill defines the testing philosophy, conventions, and tooling for the Confidence CLI monorepo. For e2e tests specifically, load the `testing-e2e` skill. ## Philosophy @@ -63,7 +59,6 @@ Do **not** mock `fetch` or HTTP clients directly with `vi.fn()` or `vi.mock()`. | `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 @@ -86,14 +81,10 @@ packages/quickstart/__tests__/ 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/cli/__tests__/ + commands/ # CLI command tests + features/ # Feature tests packages/testing/src/ # Shared test infrastructure auth/ # JWT builders, token scaffolds @@ -115,47 +106,11 @@ 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 +## UI Testing Framework (`packages/quickstart/__tests__/ui/testing-framework/`) -- **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. +- **`ink/`** — `renderScreen()`, `renderApp()`, `act()` — wrappers around ink-testing-library +- **`mocks/`** — Mock child process for testing spawn-based features +- **`async.ts`** — `delay()`, `waitFor()` — async assertion helpers ## Test Structure 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 | From 09ceeac806539414aa455aada5dc752ebeb5b2aa Mon Sep 17 00:00:00 2001 From: Alex Bespoyasov Date: Fri, 2 Oct 2026 11:46:36 +0200 Subject: [PATCH 2/3] chore: update e2e testing skill after merge --- .claude/skills/testing-e2e/SKILL.md | 98 ++++++++++++++--------------- .claude/skills/testing/SKILL.md | 2 + 2 files changed, 51 insertions(+), 49 deletions(-) diff --git a/.claude/skills/testing-e2e/SKILL.md b/.claude/skills/testing-e2e/SKILL.md index 686a7fd..2891ec9 100644 --- a/.claude/skills/testing-e2e/SKILL.md +++ b/.claude/skills/testing-e2e/SKILL.md @@ -2,91 +2,91 @@ name: testing-e2e description: > E2E test framework using node-pty, terminal sessions, mock servers, - and navigation helpers for the quickstart TUI. -version: '0.1' + and navigation helpers. Framework lives in packages/testing/src/e2e/, + imported via @spotify-confidence/testing/e2e. +version: '0.2' --- # E2E Testing -This skill covers end-to-end tests that spawn the built CLI binary in a real pseudo-terminal. Load the `testing` skill first for general testing philosophy and conventions — this skill adds the e2e-specific framework. +Load the `testing` skill first for general philosophy — this skill adds the e2e-specific framework. -## Overview - -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 +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 +pnpm test:e2e # Build + run all e2e tests (not included in pnpm test / pnpm qa) ``` -E2E tests are **not** included in `pnpm test` or `pnpm qa`. They run in a separate CI job. - -### Config +## Layout -E2E tests use a dedicated vitest config (`packages/quickstart/vitest.config.e2e.ts`) with: +- **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) -- 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` +Import everything from one sub-path: -## Testing Framework (`packages/quickstart/__tests__/e2e/testing-framework/`) +```ts +import { + createSession, + navigateToPlugins, + simulateAuthCallback, +} from '@spotify-confidence/testing/e2e'; +``` -### Session Factory +## Core API -- **`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]`. +### `createSession(opts?)` -### Terminal Session +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`** (`terminal/session.ts`) — wraps node-pty. Key methods: - - `press(key)` — named keys like `'Enter'`, `'ArrowDown'` - - `pressRepeat(key, count)` — repeat a key press - - `waitForText(text)` — wait until text appears in output - - `waitForPattern(regex)` — wait until regex matches output - - `waitForExit()` — wait for process to terminate - - `checkpoint()` — mark a screen boundary for scoped assertions - - `snapshot()` — get current screen content (after last checkpoint) - - `screen` — full ANSI-stripped output +### `TerminalSession` -### Utilities +| 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 | -- **`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. -- **`readInvocation(session)`** (`utils.ts`) — reads invocation output from the terminal. +### Navigation Helpers -### Mock Infrastructure +Pre-built functions that advance through wizard screens. Each takes a `TerminalSession`: -- **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. +- `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 E2E Tests +## Writing Rules - **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 `createSession()` per test** 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 `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 } from './testing-framework/session-factory.js'; -import { navigateToPlugins } from './testing-framework/navigation.js'; +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('Install plugins'); - session.press('Enter'); + await session.waitForText('Which CLI agent'); + session.checkpoint(); + await session.press('Enter'); - await session.waitForText('Plugins installed'); + await session.waitForText('Plugin set up successfully'); }); }); ``` diff --git a/.claude/skills/testing/SKILL.md b/.claude/skills/testing/SKILL.md index 64fafa8..02903cf 100644 --- a/.claude/skills/testing/SKILL.md +++ b/.claude/skills/testing/SKILL.md @@ -92,6 +92,7 @@ packages/testing/src/ # Shared test infrastructure env/ # Environment overlay, platform detection terminal/ # Key-map, key resolution msw/ # MSW server + handlers + e2e/ # E2E framework (see testing-e2e skill) ``` ### Test infrastructure imports @@ -104,6 +105,7 @@ 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) +import { createSession } from '@spotify-confidence/testing/e2e'; // E2E framework ``` ## UI Testing Framework (`packages/quickstart/__tests__/ui/testing-framework/`) From 697e99b49a99a337e651050533a24c9c6eb83d03 Mon Sep 17 00:00:00 2001 From: Alex Bespoyasov Date: Fri, 2 Oct 2026 11:50:27 +0200 Subject: [PATCH 3/3] chore: trim down lines in certain skills --- .claude/skills/architecture/SKILL.md | 197 ++++---------------- .claude/skills/cli/SKILL.md | 124 ++---------- .claude/skills/coding-conventions/SKILL.md | 162 ++-------------- .claude/skills/development-harness/SKILL.md | 103 +--------- .claude/skills/integrations/SKILL.md | 131 ++----------- .claude/skills/testing/SKILL.md | 116 +++--------- 6 files changed, 119 insertions(+), 714 deletions(-) diff --git a/.claude/skills/architecture/SKILL.md b/.claude/skills/architecture/SKILL.md index 8cc5674..efc7f88 100644 --- a/.claude/skills/architecture/SKILL.md +++ b/.claude/skills/architecture/SKILL.md @@ -1,29 +1,23 @@ --- name: architecture description: Monorepo structure, dependency graph, domain boundaries, and package-level constraints for the Confidence CLI project -version: '0.3' +version: '0.4' --- # Architecture Guidelines -This skill defines the structural rules, domain boundaries, and constraints that govern all work on the Confidence CLI. Follow these when adding features, refactoring, or reviewing changes. - -## Purpose - -The Confidence CLI is a set of tools for 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. +Structural rules, domain boundaries, and constraints for the Confidence CLI monorepo. ## Monorepo Structure -The project is a pnpm monorepo with six packages: - -| 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, utils, frameworks, integrations, providers) | -| `packages/testing/` | No | Test infrastructure (auth scaffolds, project scaffolds, env helpers, terminal helpers, MSW) | -| `packages/quickstart/` | Yes | TUI wizard — `@spotify-confidence/quickstart` | -| `packages/cli/` | Yes | CLI for managing Confidence — `@spotify-confidence/cli` | +| 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 @@ -33,175 +27,50 @@ shared-kernel ◄── core ◄── quickstart ◄── cli └── 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) -- **cli** → quickstart (devDep on core, shared-kernel, testing, eslint-config) +## Import Rules -### Cross-Package Imports - -Use the npm package name for cross-package imports: +**Cross-package** — always use npm package name: ```ts -import { ScreenId, track, authenticate } from '@spotify-confidence/core'; +import { 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/cli/`, use path aliases for cross-domain imports: - -| Alias | Target | -| ------------- | ---------------- | -| `@commands/*` | `src/commands/*` | -| `@features/*` | `src/features/*` | -| `@output/*` | `src/output/*` | -| `@api/*` | `src/api/*` | - -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`, `cli`, 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/`) +**Intra-package** — use path aliases for cross-domain, relative for within-domain: -CLI command definitions using yargs. Each command is a self-contained module exporting a `Command` object. +| Package | Aliases | +| ---------- | ----------------------------------------------------------------- | +| quickstart | `@commands/*`, `@features/*`, `@ui/*` | +| cli | `@commands/*`, `@features/*`, `@output/*`, `@api/*` | +| core | Relative imports in `src/`; tsconfig aliases in `__tests__/` only | -- Commands orchestrate — they call into `src/ui/` but never contain UI rendering or framework detection logic themselves. +## Domain Boundaries -#### 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. - -### CLI (`packages/cli/`) - -The `confidence` CLI, organized into: - -#### Entry Point (`bin/cli.ts`) - -Uses yargs to define the CLI with global options (`--json`, `--output`, `--project`, `--environment`, `--profile`, `--dry-run`, `--debug`) and commands. - -#### Commands (`src/commands/`) - -Each command exports an object with `command`, `describe`, `builder` (optional), and `handler` properties. Commands with subcommands (e.g. `config`, `flags`, `events`, `recordings`) use nested yargs builders. - -- **`login`** / **`logout`** / **`whoami`** — Auth commands delegating to `@spotify-confidence/core` -- **`config`** — Persistent configuration management (set/get/list/reset) -- **`flags`** / **`events`** / **`recordings`** — Feature-specific setup commands delegating to quickstart -- **`quickstart`** — Launches the interactive TUI wizard - -#### Features (`src/features/`) - -- **`config/`** — Re-exports config operations from core -- **`quickstart/`** — Launches the quickstart TUI with feature pre-selection - -#### Output (`src/output/`) - -Structured output formatting with automatic format detection: - -- **`detect.ts`** — `resolveFormat()`: `--json` flag → JSON; `--output` flag → specified; TTY → table; pipe → JSON -- **`json.ts`** — `formatJson()`: wraps data in `{ data, meta? }` envelope -- **`table.ts`** — `formatTable()`: dynamically-sized column layout +- **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 -### 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 ``` -cli commands → features, output, quickstart, core, shared-kernel -cli features → quickstart, core, shared-kernel -quickstart commands → ui, features, core, shared-kernel -quickstart features → core, shared-kernel -quickstart ui → features, core, shared-kernel -core → shared-kernel -shared-kernel → nothing +cli → quickstart, core, shared-kernel +quickstart → core, shared-kernel +core → shared-kernel +shared-kernel → nothing ``` No circular dependencies. No upward imports. -Within the quickstart UI layer: +Within quickstart UI: `screen slices → hooks/, lib/, components/ → lib/ → nothing in ui/` -``` -screen slices → hooks/, lib/, components/ -components/ → lib/, hooks/ -hooks/ → lib/ -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 diff --git a/.claude/skills/cli/SKILL.md b/.claude/skills/cli/SKILL.md index fd92de8..630b891 100644 --- a/.claude/skills/cli/SKILL.md +++ b/.claude/skills/cli/SKILL.md @@ -1,65 +1,20 @@ --- name: cli description: Structure, commands, output formatting, and conventions for the packages/cli/ package -version: '0.1' +version: '0.2' --- # CLI Package -This skill covers the `packages/cli/` package — the `confidence` CLI binary for managing Confidence feature flags, events, session recordings, and configuration. +The `packages/cli/` package — the `confidence` binary for managing Confidence feature flags, events, session recordings, and configuration. Published as `@spotify-confidence/cli`. -## Package Overview - -The CLI wraps the quickstart TUI wizard and adds standalone commands for auth and config management. It is published as `@spotify-confidence/cli` and installs the `confidence` binary. - -### Dependencies - -- **Runtime**: `@spotify-confidence/quickstart`, `yargs` -- **Dev**: `@spotify-confidence/core`, `shared-kernel`, `testing`, `eslint-config` - -### Build +## Build tsdown bundles `core` and `shared-kernel` into the binary. `quickstart` stays external (dynamically imported at runtime). Target: Node 24+ ESM. -## Directory Structure - -``` -packages/cli/ -├── bin/cli.ts # Entry point — yargs CLI -├── src/ -│ ├── commands/ # Command definitions -│ │ ├── index.ts # Barrel -│ │ ├── types.ts # GlobalFlags type -│ │ ├── login.ts # OAuth login -│ │ ├── logout.ts # Clear credentials -│ │ ├── whoami.ts # Show current identity -│ │ ├── config.ts # config set/get/list/reset -│ │ ├── flags.ts # flags setup -│ │ ├── events.ts # events setup -│ │ ├── recordings.ts # recordings setup -│ │ └── quickstart.ts # Launch TUI wizard -│ ├── features/ # Feature implementations -│ │ ├── config/ # Re-exports from core -│ │ └── quickstart/ # Launch helper with feature mapping -│ └── output/ # Output formatters -│ ├── detect.ts # resolveFormat() -│ ├── json.ts # formatJson() -│ └── table.ts # formatTable() -└── __tests__/ # Tests -``` - -## Path Aliases - -| Alias | Target | -| ------------- | ---------------- | -| `@commands/*` | `src/commands/*` | -| `@features/*` | `src/features/*` | -| `@output/*` | `src/output/*` | -| `@api/*` | `src/api/*` | - ## Command Architecture -Each command exports an object with the yargs command shape: +Each command exports a yargs command object: ```ts export const exampleCommand = { @@ -70,81 +25,28 @@ export const exampleCommand = { }; ``` -### Global Options - -All commands receive these from the root yargs instance: - -| Flag | Type | Purpose | -| --------------- | ------- | -------------------------------- | -| `--json` | boolean | Force JSON output | -| `--output` | string | Output format (json/table/plain) | -| `--project` | string | Override project from config | -| `--environment` | string | Override environment | -| `--profile` | string | Named auth profile | -| `--dry-run` | boolean | Preview without executing | -| `--debug` | boolean | Verbose output | - ### Command Types -**Standalone commands** — directly perform their action: - -- `login`, `logout`, `whoami`, `config` - -**Setup commands** — delegate to the quickstart TUI with pre-selected features: - -- `flags setup`, `events setup`, `recordings setup` - -**TUI launcher** — launches the full interactive wizard: - -- `quickstart` +- **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 -### Format Resolution (`resolveFormat`) - -Priority: `--json` flag → `--output` flag → TTY detection (TTY → table, pipe → JSON). - -### JSON Envelope (`formatJson`) - -All JSON output wraps data in a standard envelope: - -```json -{ - "data": { ... }, - "meta": { ... } -} -``` +All structured output goes through `src/output/`: -The `meta` field is omitted when empty. +- **`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 -### Table Formatter (`formatTable`) - -Renders rows with dynamically-sized columns. Supports fixed-width overrides per column. Uses Unicode box-drawing separators. +Commands never call `JSON.stringify` directly. ## Quickstart Integration -The `launchQuickstart()` helper in `src/features/quickstart/launch.ts`: - -1. Maps feature names to goal IDs: `flags` → `feature-flags`, `events` → `event-tracking`, `recordings` → `session-recordings` -2. Dynamically imports `startTui` from `@spotify-confidence/quickstart` -3. Passes through `--dir`, `--dry-run`, `--debug`, and `--no-telemetry` options - -## Config Feature - -The `config` command manages persistent key-value configuration stored in `$CONFIDENCE_CONFIG_DIR/config.json`. Valid keys: `project`, `environment`, `output`, `profile`, `ide`. Implementation delegates to `@spotify-confidence/core`. +`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. -- Output formatting goes through `src/output/` — commands never call `JSON.stringify` directly. - The CLI must not import from quickstart's internal modules — only from its public `startTui` export. - -## Development - -```bash -pnpm --filter @spotify-confidence/cli try # Run CLI locally via tsx -pnpm --filter @spotify-confidence/cli test # Run unit tests -pnpm --filter @spotify-confidence/cli build # Build for distribution -pnpm --filter @spotify-confidence/cli qa # Full quality check -``` diff --git a/.claude/skills/coding-conventions/SKILL.md b/.claude/skills/coding-conventions/SKILL.md index fa3c35b..1d43659 100644 --- a/.claude/skills/coding-conventions/SKILL.md +++ b/.claude/skills/coding-conventions/SKILL.md @@ -1,163 +1,41 @@ --- name: coding-conventions description: TypeScript style, import ordering, module exports, and linting rules for the Confidence CLI monorepo -version: '0.1' +version: '0.2' --- # Coding Conventions -This skill defines the TypeScript style, import ordering, module export patterns, and linting rules that apply across all packages in the monorepo. Follow these in every code change. +TypeScript style and linting rules that apply across all packages. ## TypeScript Style -### Imports - -Sort imports in this order, separated by blank lines: - -1. Node built-ins (`node:*`) -2. React -3. External dependencies -4. Cross-package (`@spotify-confidence/*`) -5. Path aliases (`@commands/*`, `@features/*`, `@ui/*`, `@output/*`) -6. Relative imports - -### Type Definitions - -Use `type` instead of `interface`: - -```ts -// Correct -type UserInfo = { - email: string; - region: string; -}; - -// Wrong -interface UserInfo { - email: string; - region: string; -} -``` - -### Function Parameters - -Use object params when a function has 4 or more arguments: - -```ts -// Correct — 4+ params use an object -function createSession({ ide, region, goals, debug }: CreateSessionOpts) { ... } - -// Wrong — too many positional args -function createSession(ide: IdeId, region: string, goals: string[], debug: boolean) { ... } -``` - -### Switch Exhaustiveness - -Use `satisfies never` in switch defaults to catch unhandled cases at compile time: - -```ts -switch (action) { - case 'login': - return handleLogin(); - case 'logout': - return handleLogout(); - default: - return action satisfies never; -} -``` - -### Latest TypeScript Syntax - -Use modern TypeScript features: `satisfies`, `using` for disposables, etc. +- **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` - -Use named function expressions, not arrow functions: - -```ts -// Correct -useEffect( - function autoAdvance() { - // ... - }, - [deps], -); - -// Wrong -useEffect(() => { - // ... -}, [deps]); -``` - -### Event Listener Cleanup - -Use `AbortController` for removing event listeners instead of manual `removeEventListener`: - -```ts -useEffect(function listenForResize() { - const controller = new AbortController(); - window.addEventListener('resize', handleResize, { signal: controller.signal }); - return () => controller.abort(); -}, []); -``` - -### React Hooks Linting - -`eslint-plugin-react-hooks` with `recommended-latest` rules, all set to `error`. +- **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 -Keep the public API compact. Barrel files (`index.ts`) re-export only the public API. Don't re-export internal helpers or types that aren't part of the module's contract. - -### Named Types in Actions - -Use named types in `actions.ts` files for union extensions. This allows new action variants to be added without modifying existing switch statements across the codebase. - -## Cross-Package Imports - -Use the npm package name — never relative paths across package boundaries: - -```ts -// Cross-package — use npm name -import { ScreenId, track } from '@spotify-confidence/core'; -import type { IdeId } from '@spotify-confidence/shared-kernel'; - -// Cross-domain within a package — use path alias -import { WelcomeScreen } from '@ui/screens/welcome/index.js'; -import { buildPrompt } from '@features/onboarding/index.js'; - -// Within-domain — use relative -import { store } from '../../store.js'; -``` - -Within `packages/core/src/`, always use relative imports (no path aliases — enables external consumers to follow source imports). Core `__tests__/` may use tsconfig path aliases. +- Barrel files re-export only the public API — no internal helpers. +- Use named types in `actions.ts` for union extensions. -## Initialization Hooks +## Quickstart-Specific -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 dry-run and real logic with conditionals. +- **`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 linting — all rules are errors. ESLint config from `@spotify-confidence/eslint-config` (base) or `@spotify-confidence/eslint-config/react` (for packages with React). - -### No Warning Suppression - -Never suppress runtime warnings or linter diagnostics. Fix the root cause. - -## Enum Usage - -Use `ScreenId` enum values from `@spotify-confidence/core` for screen identification. Never use raw strings. - -Use `HAlign` / `VAlign` enums from `styles.ts` instead of raw alignment strings (`'flex-start'`, `'center'`, `'flex-end'`). - -## Theme Constants - -Import `Colors` and `Icons` from `packages/quickstart/src/ui/styles.ts` for consistent theming. Never use raw color values or emoji directly. +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 index 5ae3b31..f6fb709 100644 --- a/.claude/skills/development-harness/SKILL.md +++ b/.claude/skills/development-harness/SKILL.md @@ -1,124 +1,39 @@ --- name: development-harness description: Quality gates, commit conventions, pre-commit hooks, and CI/CD processes for the Confidence CLI monorepo -version: '0.2' +version: '0.3' --- # Development Harness -This skill defines the quality gates, commit conventions, and CI/CD processes for the Confidence CLI monorepo. Follow these when making changes, creating commits, or setting up automation. +Quality gates, commit conventions, and CI/CD for the monorepo. ## Quality Harness -All quality checks are available as individual scripts and as a combined `qa` script. +Run `pnpm qa` before committing and pushing — it runs typecheck + lint + test. Use `pnpm lint:fix` to auto-fix formatting. -### 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. +Pre-commit hooks (Husky + lint-staged) auto-format staged files on every commit. ## Commit Conventions -All commits must follow [Conventional Commits](https://www.conventionalcommits.org/). A `commit-msg` hook enforces this via commitlint. - -### Format +All commits follow [Conventional Commits](https://www.conventionalcommits.org/), enforced by a `commit-msg` hook via commitlint. ``` (): - -[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 +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 -feat(ui): add framework selection screen fix(frameworks): correct Next.js detection for app router refactor(core): extract shared types to lib module -chore(deps): update ink to v6.9 -test(cli): add coverage for config management ``` ## 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 +**PR checks** (`.github/workflows/ci.yml`): typecheck + lint + test + commit message validation. Both must pass before merging. -## Configuration Files +**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. -| 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 | +Required secrets: `GITHUB_TOKEN` (automatic), `NPM_TOKEN` (repository secret). diff --git a/.claude/skills/integrations/SKILL.md b/.claude/skills/integrations/SKILL.md index b1bf423..818616c 100644 --- a/.claude/skills/integrations/SKILL.md +++ b/.claude/skills/integrations/SKILL.md @@ -1,133 +1,38 @@ --- name: integrations description: IDE integration strategy pattern and guidelines for the packages/core/src/integrations/ module -version: '0.3' +version: '0.4' --- # IDE Integrations Guidelines -This skill defines the structure, constraints, and conventions for IDE integrations. Follow these when adding, modifying, or reviewing IDE-related code. +Structure, constraints, and conventions for IDE integrations in `packages/core/src/integrations/`. -## Purpose +## Strategy Pattern -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. +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. -## Module Structure +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. -``` -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. +Thin orchestrators (`chat.ts`, `plugins.ts`) resolve the strategy via `getIntegration(ide)` and delegate. ## 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. It never imports from `packages/quickstart/` or `packages/cli/`. - -### 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. If you add a new IDE, config path, or MCP registration method, update the script accordingly. +- **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` -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. +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` -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.). +No other source files need changes. -### Codex: `item.completed` event batching +## Codex Runtime Constraints -Codex `exec --json` only emits `item.completed` events — not incremental text deltas. Status lines only surface after the agent finishes an entire message turn. Claude Code and Cursor stream incrementally via `stream-json`, so their status updates appear in real time. +- **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/SKILL.md b/.claude/skills/testing/SKILL.md index 02903cf..df4e4fe 100644 --- a/.claude/skills/testing/SKILL.md +++ b/.claude/skills/testing/SKILL.md @@ -3,45 +3,30 @@ name: testing description: > Testing philosophy, conventions, mocking rules, and UI test framework for unit and integration tests across all packages. -version: '0.4' +version: '0.5' --- # Testing Guidelines -This skill defines the testing philosophy, conventions, and tooling for the Confidence CLI monorepo. For e2e tests specifically, load the `testing-e2e` skill. +Testing philosophy, conventions, and tooling for the monorepo. For e2e tests, also load the `testing-e2e` skill. ## 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. +- 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.** -### 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: +- **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(); @@ -49,86 +34,37 @@ Do **not** mock `fetch` or HTTP clients directly with `vi.fn()` or `vi.mock()`. }); ``` - 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 | - -## 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 - -packages/cli/__tests__/ - commands/ # CLI command tests - features/ # Feature tests - -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 - e2e/ # E2E framework (see testing-e2e skill) -``` +- MSW setup (`listen`, `resetHandlers`, `close`) belongs in `packages/testing/src/msw/setup.ts`. -### Test infrastructure imports +## Test Infrastructure Imports -Import from specific sub-paths to avoid pulling in unrelated modules: +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 { resolveKey } from '@spotify-confidence/testing/terminal'; -import { server } from '@spotify-confidence/testing'; // MSW server (main barrel) -import { createSession } from '@spotify-confidence/testing/e2e'; // E2E framework +import { server } from '@spotify-confidence/testing'; +import { createSession } from '@spotify-confidence/testing/e2e'; ``` -## UI Testing Framework (`packages/quickstart/__tests__/ui/testing-framework/`) +## UI Testing Framework -- **`ink/`** — `renderScreen()`, `renderApp()`, `act()` — wrappers around ink-testing-library -- **`mocks/`** — Mock child process for testing spawn-based features -- **`async.ts`** — `delay()`, `waitFor()` — async assertion helpers +`packages/quickstart/__tests__/ui/testing-framework/` provides `renderScreen()`, `renderApp()`, `act()`, `delay()`, `waitFor()`, and mock child process helpers. ## Test Structure -Use the **Arrange-Act-Assert (AAA)** pattern in every test. Name the system under test variable **`sut`**. +**AAA pattern** in every test. Name the system under test **`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. +- 3 lines or fewer: no blank lines needed. +- Longer: blank lines between AAA sections. +- Each section >3 lines: 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. +- 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 `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. +- Prefer `using` for disposable resources. +- Prefer `waitFor` over `await delay` for TUI assertions. +- Prefer `createProjectDir()` for project context in screen tests.