Skip to content

Contributing

This guide covers what you need to get started contributing to Kangentic. For detailed architecture documentation, see the Architecture page or the repo docs.

What to install, how to start the app, and how the tree is laid out.

  • Node.js 22.14+ or 24+, Download from nodejs.org. better-sqlite3 13 needs Node-API 10, and an older Node 22 crashes on the first database open instead of reporting it. CI runs the test tiers on Node 24
  • Git 2.26+, worktree support required
  • Claude Code CLI installed and on PATH; it is what the end-to-end tier drives. The app itself runs any of the fifteen supported agents, and all fifteen are first-class: add features or adapters for any of them.
PlatformRequired
WindowsNothing since v0.44.0: every native module ships a prebuilt Windows binary
macOSXcode Command Line Tools, which a macOS package build needs for node-pty’s spawn-helper
Linuxbuild-essential, python3, because node-pty ships no Linux binary and compiles on install
Terminal window
git clone https://github.com/kangentic/kangentic.git
cd kangentic
npm install
npm start

npm start does four things in order. It starts Vite in-process for renderer hot reload. It builds the main process, the preload script, and the three utility-process workers (embedding, line-count, dictation) once with esbuild. It copies the bridge and plugin scripts next to the bundle. Then it launches Electron. Renderer edits hot-reload; a main-process or preload edit needs npm start run again, since esbuild does not watch.

A dev build names itself Kangentic (dev) in the title-bar wordmark and in the OS window title, so a dogfooding npm start window is tellable from an installed build in the taskbar without clicking into it. Production builds drop both strings.

src/
main/ # Electron main process (Node.js)
activity-engine/ # Activity-state engine behind the board's status dots
agent/ # Agent adapters (per-agent subfolders) with detection, command building, hooks, trust
analytics/ # Opt-out usage analytics
automations/ # Column automation runner, registry, and the per-type adapters
boards/ # External board adapters (Asana auth, import sources)
browser/ # Embedded Browser pane host + automation gating
config/ # Three-tier config: global > project > effective
db/ # SQLite database layer + migrations
dev-ports/ # Dev-server port ledger + bind probe
diagnostics/ # Log mirroring, crash capture, IPC recording
fs/ # Filesystem helpers
git/ # Worktree creation and cleanup
ipc/ # IPC handler registration + handlers/
mobile-bridge/ # Mobile companion pairing + relay
monitor/ # Cross-project Agent Monitor aggregation
notifications/ # Desktop alerts and the announcements feed
pop-out/ # Detachable-surface window engine
pr/ # Branch-to-PR resolution + refresh scheduling
pty/ # Terminal session management + queue
retrieval/ # Conversation Memory index + search
search/ # Quick Find query backend
shared/ # Helpers shared across main-process subsystems
task-detail/ # Cross-window task-detail ownership registry
transcription/ # Local voice dictation runtime
transition-engine/ # Transition engine, session recovery
usage-stats/ # Usage dashboard aggregation
utility-process/ # Shared plumbing for the utilityProcess workers (paths, restart policy, stderr tail)
preload/ # Context bridge (window.electronAPI)
renderer/ # React UI
components/ # Board, dialogs, layout, terminal
hooks/ # React hooks (useTerminal, etc.)
pop-out/ # Renderer side of the detachable surfaces: roots, registry, frame
stores/ # Zustand state management
window-manager/ # In-app window layer: floating/tiled/snapped panes over the board and monitor
shared/ # Shared types, IPC channels, path utilities
devtools/ # Dev-build-only tooling, excluded from production bundles
packages/ # npm workspaces
launcher/ # The `kangentic` npm package behind `npx kangentic`
protocol/ # @kangentic/protocol - a shared wire-schema package, published to npm on its own version line
tests/
unit/ # Vitest - pure logic, no browser
integration/ # Vitest - opt-in, hits real CLIs/network (excluded from test:unit)
ui/ # Playwright + Chromium - mock electronAPI
e2e/ # Playwright + real Electron
captures/ # Playwright - on-demand screenshots/recordings, not a test tier
demo/ # Playwright - smoke test of the static web demo build (npm run test:demo)
fixtures/ # Shared fixtures: mock agent CLIs, real session transcripts
ModeCommandWhat Runs
Developmentnpm start or npm run devVite dev server + esbuild watch + Electron
Productionnpm run buildtsc --noEmit > Vite build > esbuild (minified)
Packagenpm run packageBuild + unpacked app directory (electron-builder --dir, no installer)
Makenpm run makeBuild + create platform installers
Publishnpm run publishBuild + upload to GitHub Releases

In development, native modules (better-sqlite3, node-pty, sherpa-onnx-node, sqlite-vec, @huggingface/transformers, and font-list) are marked external in esbuild and loaded at runtime from node_modules (as is electron itself).

Kangentic builds its installers with electron-builder, configured in electron-builder.yml at the project root. Packaging and releases covers the platform targets, how the native modules ship, what is unpacked from the ASAR archive, the Electron security fuses, code signing, and the release workflow.

From the CLA to the merged pull request.

First-time contributors must sign a Contributor License Agreement (CLA) before their pull request can be merged. When you open your first PR, the CLA Assistant bot will post a comment - you sign by adding a comment to the PR. It takes about 30 seconds and only needs to be done once.

Kangentic is dual-licensed: the public version uses AGPLv3, and commercial licenses are available for organizations that need proprietary modifications. The CLA (modeled after the Apache ICLA) grants VORPAHL LLC a non-exclusive license to distribute your contribution under any license, while you retain full copyright to your work.

To inquire about a commercial license, email licensing@kangentic.com.

  • Text formatting - no em-dashes (U+2014) and no -- used as punctuation in anything you author (code, comments, tests, docs, commit messages). Use a single dash or restructure with a period. Enforced by a CI unit test plus review
  • TypeScript strict mode - noImplicitAny enabled, no any types (use the types in src/shared/types.ts, unknown with a type guard, or generics), and full descriptive names: currentIndex not curIdx
  • UI conventions - use the shared primitives (Select rather than a raw <select>, CountBadge, ConfirmDialog) and Lucide React icons, never inline SVGs. Respect the font floor: text-xs by default and never below text-[11px]. Avoid hover-only controls, use theme-adaptive semantic tokens rather than hardcoded colors so the UI re-colors across every theme, prefer visual subtraction over addition, and add data-testid attributes on interactive elements for test selectors
  • No personal info - the repo is public, so never hardcode usernames, emails, or home-directory paths. Use generic placeholders like C:\Users\dev in tests and examples
  • Reuse before reimplement - search for an existing utility before adding a new one, and extract duplicated logic into a shared module instead of copying it
  • Bounded IPC payloads - cap large captured buffers, such as child-process stdout and stderr, before they cross IPC. An Error must never carry tens of megabytes
  • IPC channels - src/shared/ipc-channels.ts is the single source of truth
  • Cross-platform parity - code and tests must behave identically on Windows, macOS, and Linux. No hardcoded OS paths, pass { force: true } to fs.rmSync/fs.rm for Windows file locking, write test files only under os.tmpdir(), and avoid pixel-exact or bare-timeout assertions. Enforced by a CI unit test and the Linux CI run
  • Docs stay in sync - changing an anchor source file (union types, IPC channels, DB migrations, adapter capabilities, or settings) means updating the matching doc under docs/. Enforced by an automated doc-anchor check when the PR is opened and merged
  • Commit messages - Conventional Commits format (type(scope): subject), enforced by commitlint. Allowed types: feat, fix, docs, style, refactor, perf, test, build, ci, chore, revert. The scope is optional and unchecked, so reuse an existing one rather than coining a new spelling of it (agent not agents, db not database); CONTRIBUTING.md lists the scopes in active use

These are the conventions that most often need a maintainer follow-up when missed; the full set lives in the repo’s .claude/rules/ directory.

Most features that cross from the UI into the main process start with a new IPC channel, and every channel touches the same six files. Add them in this order:

  1. The channel constant in src/shared/ipc-channels.ts, the single source of truth for channel names
  2. The handler in the matching src/main/ipc/handlers/*.ts module, registered with ipcMain.handle on that constant
  3. The method on the ElectronAPI interface in src/shared/types.ts
  4. The bridge method in src/preload/preload.ts, which calls ipcRenderer.invoke with the constant behind contextBridge
  5. The call from the renderer, through window.electronAPI.domain.method()
  6. The stand-in in tests/ui/mock-electron-api.js, when UI tests exercise the call
The six files behind one IPC channel Adding an IPC channel touches six files, numbered in the order you add them. One, the channel constant in src/shared/ipc-channels.ts, for example SESSION_SUSPEND with the value session:suspend. Two, the handler in the matching module under src/main/ipc/handlers, registered with ipcMain.handle on that constant. Three, the method on the ElectronAPI interface in src/shared/types.ts, here sessions.suspend taking a task id and a project id. Four, the bridge method in src/preload/preload.ts, which calls ipcRenderer.invoke with the constant and is exposed to the page through contextBridge. Five, the call from the renderer, window.electronAPI.sessions.suspend. Six, the UI-test mock in tests/ui/mock-electron-api.js, which stands in for window.electronAPI when UI tests exercise the call. At runtime a call travels from the renderer process through the preload bridge into the main process and its result returns the same way. Both sides import the constant, and the shared type shapes the call on both sides. SHARED types.ts ElectronAPI sessions.suspend 3 ipc-channels.ts SESSION_SUSPEND 'session:suspend' 1 RENDERER MAIN session-store.ts window.electronAPI sessions.suspend() 5 preload.ts ipcRenderer.invoke SESSION_SUSPEND 4 handlers/sessions.ts ipcMain.handle SESSION_SUSPEND 2 TESTS mock-electron-api.js tests/ui stands in for the API 6
One channel, six files, numbered in the order you add them. The renderer process never reaches the main process directly: every call crosses through the preload bridge, and both sides import the same constant and the same type.
  1. Fork the repo and create a branch from main. Use a descriptive branch name like fix/session-resume-crash, feature/multi-agent-support, or docs/update-architecture.

  2. Make your changes and add or update tests for them.

  3. Run the quick local pass before opening the PR:

    Terminal window
    npm run lint
    npm run typecheck
    npm run test:unit
    npx playwright test --project=ui
  4. Sign the CLA when the bot prompts you on your first PR (see Contributor License Agreement above).

  5. Open the PR. The template prompts you for What / Why / How / Tests and a short checklist. Link any related issues.

Small, focused PRs are easier to review and merge faster. For the full three-tier testing strategy, see Testing.

Your PR must be green on every CI check before it can merge:

CheckWhat it runs
Lint, Typecheck, BuildOne job with three independently reported steps: ESLint with --max-warnings 0 (so any warning fails it), tsc, and the production bundle plus the @kangentic/protocol package build. A failing lint step never hides a type error, because every step runs regardless
Unit tests (Vitest)Three shards behind one gate
UI tests (Playwright)Eleven shards of headless Chromium behind one gate
E2E tests (Electron)Five shards under xvfb behind one gate
claThe CLA assistant, which asks you to sign on your first PR

A sixth job, Web demo (build + smoke), builds the static web demo and boots it through the demo Playwright project on every run.

CodeQL scanning is deliberately not on that list. It runs after a merge to main and again weekly rather than on the PR, because the gate already fills the account’s concurrent-job budget and two more jobs per PR would make every run slower. main is PR-gated either way, so nothing reaches the scan unreviewed. Dependabot is scoped to GitHub Actions only and opens one grouped PR a week; npm bumps stay manual, because several dependencies are pinned to exact versions after a floating range broke a release.

If a check fails, push a fix and CI re-runs automatically. Contributors cannot re-run checks directly (that requires write access), so to re-trigger a run for a failure unrelated to your change - a flaky test, say - push a new commit (an empty git commit --allow-empty is fine), close and reopen the PR, or ask a maintainer to re-run it.

  • A maintainer may push follow-up commits to your branch for design polish or hardening before merging. This is normal and keeps the bar consistent, not a reflection on your work.
  • UI changes get a maintainer design review against the UI conventions. Including a screenshot or short clip in the PR makes that review much faster.
  • A maintainer merges your PR once it is approved and green. You do not need write access to the repository; repeat contributors may be granted it over time, at which point you merge your own PRs once approved.

Good first areas, and where to take a problem that should not be public.

Kangentic drives fourteen coding-agent CLIs, and the maintainers do not hold a subscription to all of them. Anything that needs a live, authenticated CLI is measured where that is possible and recorded honestly where it is not, so the gaps are written down rather than papered over. If you use one of these agents every day, you are better placed to close them than we are.

The clearest example is column message delivery. Kangentic confirms that a message it typed at the agent actually became a user turn, and an agent only earns the last-resort recovery of restarting the session once two separate things have been proven for it. The per-adapter status, what each agent is still missing, and a step-by-step recipe live in docs/command-injection.md. Partial results are welcome: measuring one agent and reporting the numbers is a useful pull request on its own, as is committing a sanitized capture, and neither requires touching an adapter.

Bug reports in this area are most useful with the agent name, the task’s auto_command_state, and whether the session was local or remote.

Look for issues labeled good first issue for approachable tasks. If you want to take on something larger, open an issue first to discuss the approach. Questions are welcome in GitHub Discussions.

The project follows the Contributor Covenant. Report unacceptable behavior to support@kangentic.com.

A security problem is the one thing that should not go in a public issue. Use GitHub’s private vulnerability reporting, or email security@kangentic.com if you would rather not go through GitHub, so a fix can ship before the details are public.

Kangentic is free and open source. A star helps other people find it.

Star on GitHub