Contributing to Codehog
April 7, 2026 · View on GitHub
Thank you for your interest in contributing to Codehog, the PostHog extension for VS Code. This guide covers everything you need to get started.
Getting Started
Prerequisites
- Node.js 20+
- pnpm (package manager)
- VS Code (latest stable)
- A PostHog account (US Cloud, EU Cloud, or self-hosted)
Setup
git clone https://github.com/PostHog/posthog-vscode.git
cd posthog-vscode
pnpm install
pnpm compile
Running the Extension
Press F5 in VS Code to launch the Extension Development Host OR open the "Run and Debug" panel. This opens a new VS Code window with the extension loaded from source.
Useful Commands
pnpm compile # Build with webpack
pnpm watch # Build in watch mode (auto-recompile on save)
pnpm package # Production build with hidden source maps
pnpm lint # Run ESLint
pnpm test # Run tests in VS Code host
Architecture Overview
The extension follows a three-layer architecture with a central orchestrator. See CLAUDE.md for the full file tree and code patterns.
Services (src/services/)
The data layer. Services handle API calls, caching, authentication, and AST parsing. They have no VS Code UI dependencies beyond storage APIs.
- authService -- credentials via SecretStorage and Memento
- postHogService -- all PostHog REST and HogQL API calls
- treeSitterService -- AST parsing via web-tree-sitter for code intelligence
- flagCacheService / eventCacheService / experimentCacheService -- in-memory caches with listener-based change notification
- staleFlagService -- scans the workspace for stale flag references
- telemetryService -- extension telemetry via PostHog
Providers (src/providers/)
VS Code language features: completions, decorations, code actions, code lenses, document links, and tree views. Each provider receives its dependencies (caches, tree-sitter) via constructor parameters.
Commands (src/commands/)
Command Palette actions. Each file exports a registration function that binds command IDs (from constants.ts) to handler logic.
Views (src/views/)
Webview panels for the sidebar and detail views. HTML is assembled from template literal functions in views/webview/ (styles, layout, script) and composed with a CSP nonce.
Extension Entry Point (src/extension.ts)
The orchestrator. Constructs all services, providers, and commands in activate(), wires dependencies, and pushes disposables to context.subscriptions.
Code Patterns
Cache Service Pattern
All caches follow the same shape:
API response --> cache.update(items) --> listeners fire --> providers re-render
A cache service holds an array of items, a lookup method, an update() method that replaces the array and notifies listeners, and an onChange() method for subscriptions.
When adding a new cache, follow the existing pattern in flagCacheService.ts.
Decoration Provider Pattern
All decoration providers:
- Accept a cache service and
TreeSitterServicein the constructor - Register listeners for editor changes, document changes, and cache changes
- Debounce updates at 200ms
- Use
treeSitterService.findPostHogCalls()to locate relevant calls - Filter by a provider-local
METHOD_SET(e.g.,FLAG_METHODSorCAPTURE_METHODS) - Render via
renderOptions.afterwithcontentText,color, andfontStyle: 'italic'
Completion Provider Pattern
- Guard:
treeSitterService.isSupported() - Get context:
treeSitterService.getCompletionContext(doc, pos) - Guard:
ctx.typematches the expected completion type - Return
CompletionItem[]built from cache data
Adding a New PostHog API Call
All API calls go through postHogService.request<T>(path, options?).
- REST endpoints use
/api/projects/{id}/or/api/environments/{id}/ - HogQL queries use
POST /api/environments/{id}/query/with{ kind: 'HogQLQuery', query } - Always use
escapeHogQLString()for user-supplied values in HogQL strings - For paginated endpoints, use a
while(nextPath)loop and parse thenextURL from the response
Adding a New Command
- Add the command ID to
src/constants.tsin theCommandsobject - Create or extend a file in
src/commands/ - Register the command in
src/extension.tsinsideactivate() - Add the command entry to
contributes.commandsinpackage.json
Adding a New Setting
- Add the setting definition to
contributes.configuration.propertiesinpackage.json - Read the setting with
vscode.workspace.getConfiguration('posthog').get('yourSetting')in the relevant provider or service
Testing
Tests run in the VS Code Extension Host via @vscode/test-electron.
pnpm test
Coverage
Code coverage is measured with c8. To run the test suite with coverage locally:
pnpm test:coverage # runs tests with coverage report
open coverage/index.html # view the HTML report in your browser
The HTML, lcov, and text reports are written to coverage/. Coverage is measured for src/services, src/providers, and src/utils. Tests, webview UI, and command/view glue are excluded.
The same script runs in CI on every PR and the coverage/ directory is uploaded as a build artifact for inspection.
Test Structure
src/test/extension.test.ts-- basic extension activation testsrc/test/cacheServices.test.ts-- tests for FlagCacheService, EventCacheService, and ExperimentCacheService (update, lookup, listener notification)src/test/codegenService.test.ts-- tests for code generation logicsrc/test/generateType.test.ts-- tests for type generationsrc/test/utils/hogql.test.ts-- tests forescapeHogQLString(injection prevention)src/test/utils/flagClassification.test.ts-- tests for flag type classification, rollout extraction, variant parsingsrc/test/utils/formatting.test.ts-- tests forformatCount,formatPct,buildBarsrc/test/utils/codeCleanup.test.ts-- tests forfindMatchingBrace,dedentBlock
Writing Tests
Pure logic belongs in src/utils/ and is tested in src/test/utils/. Cache services are tested directly in src/test/. Keep tests focused on behavior, not implementation details.
Conventions
Commits
Use Conventional Commits:
feat:-- new featurefix:-- bug fixci:-- CI/CD changesdocs:-- documentationchore:-- maintenance tasks
Constants and Types
- All command IDs, view IDs, and storage keys live in
src/constants.ts - All PostHog API response shapes live in
src/models/types.ts
FLAG_METHODS Duplication
Each provider defines its own FLAG_METHODS Set. This is intentional -- do not refactor into a shared constant.
Colors
| Purpose | Hex | CSS Variable |
|---|---|---|
| Active | #4CBB17 | --ph-green |
| Warning | #F9BD2B | --ph-yellow |
| Brand | #1D4AFF | --ph-blue |
| Error | #E53E3E | --ph-red |
| Accent | #F54E00 | --ph-orange |
For backgrounds, use VS Code theme variables (--vscode-*).
Debounce
All decoration and highlight updates use a 200ms debounce.
Error Handling
- Startup cache loads:
.catch(() => {})(silent -- the UI shows empty state) - Service methods:
try/catchwithconsole.warn
Telemetry
The extension is instrumented with PostHog via TelemetryService. Telemetry is disabled in development mode (when running via F5) and respects the user's VS Code telemetry setting.
To capture a new event:
telemetry.capture("event_name", { key: "value" })
Add telemetry events for meaningful user actions (sign in, flag toggle, scan initiated, etc.). Do not instrument internal implementation details.
Releasing
This project uses changesets for version management and automated releases.
Adding a changeset
When you make a change that should be released, run:
pnpm changeset
This prompts you to select the change type (patch/minor/major) and write a summary. A markdown file is created in .changeset/ describing your change.
Version types
- patch — bug fixes, small improvements, documentation
- minor — new features, non-breaking changes
- major — breaking changes
Automated release process
When PRs with changesets are merged to main, the release workflow automatically:
- Checks if any changesets are pending
- Runs the full test suite (release gate — see below)
- Consumes all pending changesets
- Bumps the version in
package.json - Updates
CHANGELOG.mdwith the changeset summaries - Commits the version bump to
main - Creates a git tag (
v{version}) - Creates a GitHub Release with the changelog
- Builds and packages the extension
- Publishes to both VS Code Marketplace and Open VSX Registry
Do not manually bump the version in package.json. The CI pipeline handles it.
Release gate: tests must pass before publishing
Publishing is gated by the test suite. No extension is ever published — to either marketplace, automatically or manually — unless the full test suite passes and coverage thresholds are met.
The gate is implemented as a reusable workflow (.github/workflows/test.yml) that other workflows call:
| Trigger | Test gate |
|---|---|
PR to main or push to feat/** | test.yml runs directly |
Push to main with changesets (auto release) | release.yml runs test.yml first; version bump, build, and publish only run if tests pass |
Manual publish-vscode.yml workflow_dispatch | test.yml runs first; publish only runs if tests pass |
Manual publish-ovsx.yml workflow_dispatch | test.yml runs first; publish only runs if tests pass |
Internal call from release.yml to publish-* | Tests skipped (skip_tests: true) — already gated by release.yml |
In short: there is no path to a published version of the extension that bypasses the test suite. If a test fails, every downstream job (version bump, build, package, publish) is blocked automatically.
Coverage thresholds in CI
The release/publish gate also enforces minimum code coverage. The current thresholds are intentionally low (lines ≥ 30%, functions ≥ 30%) and are configured in .github/workflows/test.yml under MIN_LINES/MIN_FUNCTIONS. Ratchet them upward as test coverage improves.
PR Guidelines
- Use the pull request template when opening a PR
- Keep PRs focused on a single change
- Include before/after screenshots for UI changes
- Ensure
pnpm compileandpnpm testpass before submitting - Use conventional commit messages in the PR title