# GitLens Development Guide This workspace contains **GitLens** - a powerful VS Code extension that supercharges Git functionality. It provides blame annotations, commit history visualization, repository exploration, and many advanced Git workflows. The codebase supports both desktop VS Code (Node.js) and VS Code for Web (browser/webworker) environments — shared code with abstractions in `src/env/`; test both during development. ## Working Style Expectations 1. **Accuracy over speed** — Do not guess at method names, decorator behaviors, or class interfaces. Verify they exist first by searching the codebase. 2. **Simplicity over abstraction** — Prefer the simplest correct solution; no new types, enums, or wrapper abstractions unless they serve multiple consumers. 3. **Fixing over disabling** — Fix the root cause. "Fix" and "disable" are different instructions. This includes tests: when one fails, find and fix the cause — do NOT simplify the test or change its intent to make it pass. 4. **Hypothesis before implementation** — When debugging, present your hypothesis with evidence before implementing against it. On any non-trivial change, state your approach before editing; if the request is ambiguous, ask rather than assume. 5. **Branch ownership** — The current branch owns ALL of its issues, not just those from your current task. An error or warning that exists on this branch but not on the base branch is the branch's responsibility regardless of when it was introduced (verify with `git diff main --stat` or similar; issues that also exist on the base branch are truly pre-existing and can be noted, not prioritized). After completing your task, address remaining branch build/type/test errors and warnings — or if the scope is too large, ask the user how to proceed. A zero exit code alone is not sufficient: inspect the complete build output, and treat warnings as failures. A task is not complete until the code builds without errors or warnings and related tests pass. **Webview changes have one more gate:** CI enforces a byte budget on the production Graph bundle that `build` and `check` never run. After touching anything under `src/webviews/apps/` or `packages/components`, run `pnpm run benchmark:graph:bundle`; if expected feature growth trips it, re-baseline and rewrite the baseline's `budgetRationale` (see `docs/testing.md` § Graph bundle budget). > For the rules these summarize plus the ones not listed here — complexity limits, the completeness checklist (call sites, subclass overrides, Node.js _and_ browser paths), fix vs. disable, scope of changes, and error handling: see `docs/coding-standards.md` ## Development Environment - **Node.js** ≥ 24, **pnpm** ≥ 11.x (install via corepack: `corepack enable`), **Corepack** ≥ 0.31.0, **Git** ≥ 2.7.2 ## Development Commands ```bash pnpm install # Install dependencies pnpm run build # Full development build (runs `check` itself — don't chain both) pnpm run rebuild # Complete rebuild from scratch pnpm run bundle # Production bundle pnpm run test # Run unit tests (VS Code extension tests) pnpm run test:e2e # Run Playwright E2E tests (production bundle via `bundle:e2e`) pnpm run check # Type-checking and lint rules pnpm run check:fix # Same, with auto-fix (prefer this) pnpm run fmt # Format code pnpm run benchmark:graph:bundle # Production webviews bundle + Graph byte budget (CI-enforced; `build`/`check` skip it) ``` Generation commands (`generate:contributions`, `generate:commandTypes`, `build:icons`, …) run automatically during build/watch — see Critical Rules. > For test running patterns, output interpretation, and debugging: see `docs/testing.md` ## Git & Repository Guidelines For commit message format and workflow, use `/commit`. For CHANGELOG format and entry guidelines, use `/audit-commits`. For code reviewing, use `/review` or `/deep-review`. For debugging methodology and common misdiagnosis patterns, use `/investigate`. Additional workflow skills live in `.claude/skills/`. Skill artifacts (goals, plans, reviews, live-exercise findings) all live under a single gitignored `.work/` root in the **primary** worktree — never in the feature worktree you happen to be sitting in, since `.work/` is not shared between worktrees. See [Output Files](docs/triage-dev-skills.md#output-files) for the layout. ### Branching Guidelines - Feature branches from `main` or from another feature branch if stacking - Prefix with an appropriate type: `feature/`, `bug/`, `debt/` - Use descriptive names: `feature/search-natural-language`, `bug/graph-performance` - If there is a related issue, reference it in the branch name: `feature/#1234-search-natural-language` ## High-Level Architecture ### Directory Structure Most of the layout is self-describing — browse `packages/`, `src/`, and `tests/`. What the folder names do _not_ tell you: - **`packages/` (`@gitlens/*` / `@gitkraken/*`) vs `src/`** — `packages/git` holds the git domain (models, parsers, per-operation providers) and `packages/git-cli` runs the CLI; `src/git` is the orchestration layer over them (`gitProviderService.ts`, actions, formatters). Webviews may import generic utilities (including DOM and keymap helpers) from `packages/utils` and reusable Lit UI from `packages/components`; `src/system` is host-only, and `src/system/-webview/` is extension-host-specific. Scope also marks publication: `@gitkraken/*` is published for other GitKraken products and `@gitlens/*` is workspace-internal, compiled into a `@gitkraken/*` publisher's `dist/` rather than installed. - **`src/env/node/` vs `src/env/browser/`** — the same feature must work in desktop VS Code and VS Code for Web. Shared code imports through the `@env/` alias, which resolves per build target. Changing one path means checking the other. - **`src/plus/` and `packages/plus/` are non-OSS** — licensed separately, see `LICENSE.plus`. - **`src/container.ts`** — the service locator; nearly every service is reached through it. - **`src/commands/git/`** — sub-commands of the git wizard, not standalone commands. - **`src/trackers/`** — tracks document state and blame, not git refs. - **`src/vsls/`** — VS Live Share support. **`src/uris/`** — deep-link URI handling. - **`custom-elements.json`** — generated web component metadata; never hand-edit. > For detailed architecture (patterns, services, environment abstraction, webviews, caching, build config): see `docs/architecture.md` ## Coding Standards & Style Rules Not caught by any linter — get these right by hand: - **Strict TypeScript** — no `any` usage (exceptions only for external APIs) - **Explicit return types** for public methods; **prefer `type` over `interface`** for unions - **No default exports**; use `import type` for type-only imports - **No barrel files** — no `index.ts` or re-export-only modules. When splitting a file, delete the original and update consumers to import from where things actually live; do NOT leave a re-exporting shim. - **No inline `import('./x.js').Type`** — add a top-of-file `import type { … }` and reference the type by name. Lint permits the inline form (`disallowTypeAnnotations: false`), so this one is on you: it ducks the import-order rule and hides the dependency. - **Naming**: Classes PascalCase (no `I` prefix), methods/variables camelCase, constants camelCase (**not** SCREAMING_SNAKE_CASE), files camelCase.ts - **Import order**: node built-ins → external → internal → relative - **Folders**: Models under `models/`, shared utilities in `packages/utils/`, host-specific in `src/system/-webview/`, webview apps under `src/webviews/apps/` - **Localization**: every user-facing string — notifications, quick pick titles/labels/details/placeholders, input validation, tree item labels/descriptions/tooltips, hovers, status bar, ARIA labels, and all webview text — goes through `l10n.t()` with a **literal** message and placeholders for data (`l10n.t('Unable to delete branch {0}', name)`), never concatenation or a computed key. Host code imports `{ l10n }` from `vscode`; webviews and packages import `* as l10n` from `@vscode/l10n`. Error text shown to a user comes from `getPresentableErrorMessage(ex)`, never `ex.message`. Run `pnpm run generate:l10n` after adding or changing a message — `pnpm run check` fails on a stale catalog. In the same change, translate every added or changed message into each shipped locale (`l10n/bundle.l10n..json`, `package.nls..json`): a reworded message moves or retranslates its existing entry, never just drops it. Git syntax, refs, paths, command ids and product names stay out of the catalog. Full rules: `docs/localization.md` ### Custom Lint Rules The repo enforces its own rules from `scripts/eslint-rules/`. Write conforming code up front rather than relying on `pnpm run check:fix` — these show up in diffs and reviews: | Rule | Enforces | | ---------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `require-block-body` | Block bodies for `if`/`while`/`for`/`for-in`/`for-of`. A single-line `if` is allowed ONLY when its body is control flow — `return`, `break`, `continue`, `throw`, `yield`. Loops always need braces. `else if` branches are checked independently. | | `require-js-extension` | An explicit `.js` extension on local (`@env/` and relative) imports | | `one-var` | One variable per declaration statement | | `newline-after-control-flow` | A blank line after a control flow statement | | `no-instanceof-cancellation-error` | `isCancellationError()` instead of `instanceof CancellationError` | | `no-raw-error-message` | No raw `.message` read outside log/telemetry/classification uses — show `getPresentableErrorMessage(ex)` instead; git errors keep `message` English and translate on `localizedMessage` | | `require-literal-l10n` | `l10n.t()` takes a literal message (or `{ message, comment }` object) — the catalog extractor cannot see a variable or template key | | `require-format-plural` | A message containing a `{…, plural, …}` block is the first argument of `formatPlural()` and gets its arguments there, never from `l10n.t()` | | `scoped-logger-usage` | Correct `getScopedLogger()` usage — see the decorator note below | | `no-scss-in-css-template` | No SCSS syntax inside `css` tagged templates | | `no-src-imports` | No import specifiers starting with `src/` | | `no-self-package-imports` | Same-package imports use a relative path, not the workspace package name | | `valid-package-imports` | Internal `@gitlens/*` and `@gitkraken/*` imports name a subpath the target package's `exports` exposes | > For webview styling — prefix conventions, the `1rem = 10px` base, the `--gl-*` design tokens, and the elevation (z-index + shadow) system: see `docs/webview-styling.md` > > For webview accessibility requirements: see `docs/accessibility.md` > > For webview architecture — the single RPC stack (Supertalk services over the namespaced binary pipe), state ownership, resources, persistence, and lifecycle: see `docs/webview-architecture.md` > > For the Commit Graph keyboard architecture — focus scopes, the Esc overlay stack, the chord vocabulary, and how to add a binding: see `docs/graph-keyboard.md` ### Decorator System The codebase uses method decorators (defined in `packages/utils/src/decorators/`; `@command` and a `@gate` wrapper live in `src/system/`) that significantly alter runtime behavior: | Decorator | Purpose | Key Gotcha | | ----------------------------------- | ---------------------------------------------------- | -------------------------------------------------------------------------- | | `@info()` / `@debug()` / `@trace()` | Logging with scope tracking | `getScopedLogger()` must be called BEFORE any `await` (browser limitation) | | `@gate()` | Deduplicates concurrent calls (returns same promise) | 5-min timeout; most common cause of method hangs | | `@memoize()` | Caches return value permanently on instance | Caches rejected Promises too; use `invalidateMemoized()` to clear | | `@sequentialize()` | Queues calls to execute one at a time | Different from `@gate()` — queues instead of deduplicating | | `@debounce()` | Debounces method calls per-instance | | | `@command()` | Registers VS Code command class | Class decorator, not method decorator | Stacking executes bottom-up (outermost runs first). When debugging: check `@gate()` first for hangs, `@memoize()` for stale data, logging decorators last. For detailed decorator behavior and investigation methodology, use `/investigate`. ## Quick Lookup ### Canonical Examples When implementing something new, look at these files first: | Task | Example File | | ------------------------------- | ----------------------------------------------- | | Simple command | `src/commands/copyCurrentBranch.ts` | | Complex command (multi-command) | `src/commands/gitWizard.ts` | | RPC service | `src/webviews/rpc/rebaseService.ts` | | Webview provider | `src/webviews/rebase/rebaseWebviewProvider.ts` | | Webview app (Lit) | `src/webviews/apps/rebase/` | | Unit test | `packages/utils/src/__tests__/iterable.test.ts` | | E2E test | `tests/e2e/specs/smoke.test.ts` | | E2E page object | `tests/e2e/pageObjects/gitLensPage.ts` | ### Critical Rules **contributions.json** (only applies to `contributes/commands`, `contributes/menus`, `contributes/submenus`, `contributes/keybindings`, and `contributes/views`) - Never edit these sections in `package.json` directly — edit `contributions.json` instead - Run `pnpm run generate:contributions` after editing (or let the watcher handle it) - Run `pnpm run generate:commandTypes` after adding commands (or let the watcher handle it) **Webview communication** — a single stack: Supertalk RPC services (`src/webviews/rpc/`) over the namespaced binary postMessage pipe. Readiness rides the session handshake; visibility/focus ride buffered RPC events re-emitted as window CustomEvents; persisted state uses the `acquireVsCodeApi` state API — see `docs/webview-architecture.md`