Files
clide/CONTRIBUTING.md
T
jpmschweitzerandClaude 42955b6417 T-126: C clide shell client + _argv unwrap in the dispatcher
Third slice of T-99. After this `clide status` actually does
something when typed in a shell.

* native/clide-cli/clide.c — ~250 LOC C. Walks CWD up to .git,
  hashes the workspace root with FNV-1a 64-bit (byte-for-byte
  identical to the Dart side, pinned via reference vectors in
  paths_test.dart), opens the per-workspace socket, and ships argv
  across the wire as `{cmd:"_argv", args:{argv:[...]}}`.
* lib/src/cli/argv_dispatch.dart — registers the `_argv` sentinel
  command on the dispatcher. The handler runs the T-125 parser on
  the embedded argv and either re-dispatches the unwrapped request
  through the same dispatcher or returns the pre-built error
  response. Keeps the parser in Dart so the C side stays dumb.
* lib/src/ipc/paths.dart — fnv1a64Hex hoisted to a public helper +
  fixed to format as unsigned (Dart `int` is signed int64; the high
  bit lit a leading minus that broke the cross-language compare).
  Reference-vector tests added against the FNV reference.
* `make clide-cli` builds it via the host `cc`; output lands at
  native/<platform>/clide and is gitignored. Test
  test/cli/clide_cli_e2e_test.dart compiles + exercises the full
  round-trip; skips cleanly when no cc is on PATH.
* CONTRIBUTING.md gets a "C clide shell client" section.

T-128 (delete legacy IPC) unblocked.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-05-18 18:04:50 +02:00

6.7 KiB

Contributing to clide

clide is an IDE for the Claude Code CLI, built as a single Flutter package at the repo root. This guide is for people working on clide itself.

For day-to-day model-driven work in the codebase, see CLAUDE.md — it documents the same guardrails from the agent's point of view and is the place to look for "why is the code shaped like this?"

One-time setup

git clone git@github.com:postmeridiem/clide.git
cd clide
make hooks && flutter pub get

make hooks installs the repo's git hooks (pre-commit + post-merge). Flutter must be on the stable channel and on $PATH.

If you haven't used pql before, install it and run pql init once in the repo root. pql is a hard dependency for the governance + ticket workflow described below.

The five commands you'll actually use

make run           # launch the desktop app
make verify        # no-tests sweep — analyze + format + decisions + changelog gate
make test          # fast suite — analyze + format + unit + widget + golden
make test-a11y     # WCAG-AA contrast + keyboard traversal contracts
make push-check    # the pre-push gate; what CI runs
make build-linux   # release artefact for the host platform

make verify is the lightweight "are the gates green?" check for mid-edit iteration. make push-check is the full pre-push pipeline (verify + every test suite + coverage gate).

make with no target prints the full list. make test-integration boots a real app process and is slow; reserve it for the rare change that touches startup wiring.

The pre-push hook runs make push-check automatically. Don't bypass it with --no-verify — fix the underlying issue and create a new commit. Pre-push includes:

  • flutter analyze (zero warnings)
  • dart format --set-exit-if-changed
  • the fast unit/widget/golden suites
  • accessibility contract tests
  • a coverage floor read from coverage_floor: in pubspec.yaml (currently 95 %; ratchets up only — see D-66)
  • CHANGELOG.md [Unreleased] bullets ≤ 60 words each

The C clide shell client

clide (the binary) is a ~250 LOC C program in native/clide-cli/clide.c that talks to the running Flutter app's IPC socket so Claude (and you) can drive clide from any shell. It walks CWD up to the workspace's .git, computes the same FNV-1a 64-bit hash the Dart side uses (per D-70), opens the per-workspace socket, and sends argv across the wire under a sentinel _argv cmd. The argv parser lives in Dart (lib/src/cli/argv_to_request.dart), so the C side stays a dumb pipe.

Build it with make clide-cli — output lands at native/<platform>/clide (gitignored). Drop that on your PATH (or symlink) and clide status works from any directory inside a clide workspace once the app is running. Standard POSIX + libc only; pure C99; no third-party deps.

The cross-language hash agreement is load-bearing — if the Dart server and C client disagree on the socket path, every shell invocation fails to connect. The test suite covers it: test/ipc/paths_test.dart pins FNV-1a vectors against the reference, and test/cli/clide_cli_e2e_test.dart compiles the C client and exercises the full round-trip.

Decisions, questions, rejected (DQR)

clide tracks architectural commitments as durable records under governance/:

  • decisions/<domain>.md — confirmed decisions (D-NNN)
  • questions/<domain>.md — open questions (Q-NNN)
  • rejected/<domain>.md — rejected proposals (R-NNN)

When you make a non-trivial architectural choice, write it down:

pql decisions claim D <domain> "short title"

This reserves a fresh ID and tells you where to add the record. The governance/README.md explains the format and the recommended domain list.

Pre-push validates that every D-NNN / Q-NNN / R-NNN link in the docs and code resolves to an actual record (pql decisions validate). Broken references fail the build.

Tickets

All non-trivial work is tracked in pql ticket:

pql ticket list --status in_progress
pql ticket show T-NNN[,T-NNN…]      # batch form on pql 1.4.33+
pql ticket new task "title" --parent T-NNN --priority medium
pql ticket status T-NNN in_progress
pql ticket status T-NNN done

A ticket exists for any change a reviewer might want to ask "why?" about. Bug fixes, refactors, and consultant findings all become tickets before the diff lands. Trivial typo fixes don't need one.

Commit conventions

See D-37 and the bundled git-commit skill. In short:

  • Imperative subject ≤ 70 chars, no Conventional Commits prefix (this isn't a Conventional Commits repo — the archived Python predecessor under legacy/ is, but the rebuild isn't).
  • One logical change per commit. If the subject needs "and", split it.
  • Every user-visible commit adds an entry to CHANGELOG.md under [Unreleased] in the right subsection (Added, Changed, Deprecated, Removed, Fixed, Security). Keep entries to one or two short sentences — the 60-word cap is enforced by ci/changelog_gate.sh.
  • Co-author trailer: Co-Authored-By: Claude <noreply@anthropic.com> when Claude wrote any of the diff.

Never --amend a commit unless explicitly asked. Never force-push to main. Never git add -A / git add . when staging — name files explicitly so stray secrets or build artefacts don't sneak in.

Cutting a release

A release is a single commit:

  1. Move all ## [Unreleased] entries under a new ## [X.Y.Z] — YYYY-MM-DD heading.
  2. Leave an empty ## [Unreleased] skeleton at the top.
  3. Bump pubspec.yaml version: to X.Y.Z (no -dev suffix on the release tag; add it back on the next development commit if you like).
  4. Commit subject: release vX.Y.Z.

pubspec.yaml is the single source of truth for the version — the Makefile reads it for ldflag stamping and the app reads it for build info.

What goes where

  • Bug, feature, sweep: file/claim a ticket, branch, code, test, commit, push. Push triggers make push-check.
  • Architectural decision: pql decisions claim, write the record, then file the implementation ticket linked via decision_ref.
  • Open question: drop a Q-NNN under governance/questions/<domain>.md. Triage later.
  • Rejected proposal: drop an R-NNN under governance/rejected/<domain>.md. Future-you (or a reviewer) will be glad it's written down.

Reporting issues

The public issue tracker lives at https://github.com/postmeridiem/clide/issues. The Gitea mirror is read-only.