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>
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:inpubspec.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.mdunder[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 byci/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:
- Move all
## [Unreleased]entries under a new## [X.Y.Z] — YYYY-MM-DDheading. - Leave an empty
## [Unreleased]skeleton at the top. - Bump
pubspec.yamlversion:toX.Y.Z(no-devsuffix on the release tag; add it back on the next development commit if you like). - 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 viadecision_ref. - Open question: drop a
Q-NNNundergovernance/questions/<domain>.md. Triage later. - Rejected proposal: drop an
R-NNNundergovernance/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.