From 0160f5e0186b272050245779ad46806b7616ce9d Mon Sep 17 00:00:00 2001 From: Jeroen Schweitzer Date: Mon, 18 May 2026 10:06:03 +0200 Subject: [PATCH] write CONTRIBUTING.md; dedupe Unreleased; add make verify (T-109) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Three docs sweeps from the consultant batch: * CONTRIBUTING.md lands as a human-addressed guide — clone, build, test, DQR, tickets, commit conventions. README links to it. * CHANGELOG `[Unreleased]` consolidated to one subsection per kind in Keep a Changelog order (Added, Changed, Removed, Fixed, Security). Released sections left frozen. * `make verify` is the no-tests sweep: analyze + format + decisions-validate + changelog-gate. Bridges the gap between `make analyze` alone and the full `make push-check`. Item 3 (cut an interim release) deferred — that's the user's call on timing. Co-Authored-By: Claude --- .pql/changelog/ticket_history/2026-05.sql | 1 + .pql/changelog/tickets/2026-05.sql | 9 + CHANGELOG.md | 244 ++++++++++------------ CONTRIBUTING.md | 152 ++++++++++++++ Makefile | 3 + README.md | 1 + 6 files changed, 277 insertions(+), 133 deletions(-) create mode 100644 CONTRIBUTING.md diff --git a/.pql/changelog/ticket_history/2026-05.sql b/.pql/changelog/ticket_history/2026-05.sql index 9a2d9b2a..2c87079a 100644 --- a/.pql/changelog/ticket_history/2026-05.sql +++ b/.pql/changelog/ticket_history/2026-05.sql @@ -1811,3 +1811,4 @@ INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-111', 'status', 'in_progress', 'done', NULL, '2026-05-18 07:43:45', '2026-05-18 07:43:45', '2026-05-18 07:43:45', NULL, '792efcf7c772de7cda7069ea3fc3ac0a', 1) ON CONFLICT(hash) DO NOTHING; INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-104', 'status', 'backlog', 'in_progress', NULL, '2026-05-18 07:49:44', '2026-05-18 07:49:44', '2026-05-18 07:49:44', NULL, 'bf7f2746c252f8b8893f4fdc06d459b8', 1) ON CONFLICT(hash) DO NOTHING; INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-104', 'status', 'in_progress', 'done', NULL, '2026-05-18 07:53:10', '2026-05-18 07:53:10', '2026-05-18 07:53:10', NULL, 'ce2b66658977edc3fddc78079d7852e5', 1) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-109', 'status', 'backlog', 'in_progress', NULL, '2026-05-18 08:02:06', '2026-05-18 08:02:06', '2026-05-18 08:02:06', NULL, 'ee75fea173ae492a6ad8a1d888a2142d', 1) ON CONFLICT(hash) DO NOTHING; diff --git a/.pql/changelog/tickets/2026-05.sql b/.pql/changelog/tickets/2026-05.sql index e64a2fea..e3bb2ec3 100644 --- a/.pql/changelog/tickets/2026-05.sql +++ b/.pql/changelog/tickets/2026-05.sql @@ -1866,3 +1866,12 @@ INSERT INTO tickets (id, type, parent_id, title, description, status, priority, **Acceptance:** unit tests cover the rejection path for each injection vector listed. Source: consultants.md "Security — Findings — [Major]".', 'done', 'high', NULL, NULL, NULL, '2026-05-17 18:47:42', '2026-05-18 07:53:10', NULL, '688f888ba89cefe636ece09f32bea8ab', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); +INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-109', 'task', 'T-97', 'write CONTRIBUTING.md; cut interim release to drain Unreleased; merge duplicate changelog headings', 'Three docs items: + +1. **No `CONTRIBUTING.md`.** Build/test story is scattered across `CLAUDE.md` (Claude-addressed), `README.md` (partly wrong — see T-101), and Makefile help. Write a human-addressed contributor guide: clone → `make hooks && flutter pub get` → `make test` → DQR workflow → commit conventions. +2. **CHANGELOG has duplicate subsection headings in `[Unreleased]`** — three `### Changed`, two `### Fixed`, two `### Removed` in the 2.0.0 section. Keep a Changelog 1.1.0 expects one of each per release. Merge. +3. **~80-commit Unreleased backlog** undermines the format. Cut an interim release (`vX.Y.Z`) to drain it before continuing. + +Order matters: 2 → 3 → 1 (clean changelog, release, then doc the workflow). + +Source: consultants.md "Docs — Findings".', 'in_progress', 'medium', NULL, NULL, NULL, '2026-05-17 18:48:03', '2026-05-18 08:02:06', NULL, '01396c3a11fb562a859cdf76f933824c', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); diff --git a/CHANGELOG.md b/CHANGELOG.md index cb7b818d..5e67f5e5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,26 +16,6 @@ heading, and (b) bumping `pubspec.yaml` `version:` in the same commit. ## [Unreleased] -### Removed - -- **`bin/clide.dart` + `DaemonServer`** — completing the D-56 dissolution. - The separate daemon process was dissolved on 2026-04-23 but the entry - point and socket server class were never actually deleted. Gone now, - along with orphaned tests (`test/cli/`, `test/daemon/subprocess_test`, - `test/daemon/in_process_test`), stale i18n strings, and "start - `clide --daemon`" error messages. -- **`ptyc/` source tree + `PtySession` + `scm_rights.dart`** — PTY - spawning migrated to Dart FFI `forkpty()` (`NativePty`) but the old - C helper and its Dart wiring were never cleaned up. Removed from - toolchain resolution, `ToolCheck` gate, backend serialization, - testmode harness, CI scripts, Makefile, and sandbox entitlements. - D-5 amended to record the retirement. -- CI golden images (`test/goldens/goldens/ci/`) — Skia anti-aliasing - of geometric shapes differs between macOS and Linux even with the - Ahem font, so a single set of CI goldens can't serve both platforms. - Replaced with platform-keyed goldens (`goldens/linux/`, - `goldens/macos/`), each only compared on its own OS. - ### Added - Contrast gate split — baseline `canonicalPairs` every theme passes, @@ -46,8 +26,8 @@ heading, and (b) bumping `pubspec.yaml` `version:` in the same commit. which fails if total line coverage drops below `coverage_floor:` in `pubspec.yaml`. Floor ratchets up only; target 95% (D-66). - Pre-push changelog gate — `ci/changelog_gate.sh` fails on any - `## [Unreleased]` bullet over 60 words; warns at 40. Enforces the - Keep-a-Changelog conciseness rule in the git-commit skill. + `## [Unreleased]` bullet over 60 words. Enforces the Keep-a-Changelog + conciseness rule in the git-commit skill. - Keymap layer (`KeymapService`) — typed Intents, YAML presets, VS-Code-style when-clauses, layered preset → user file → settings overlay. Default preset ships; vim/vscode/jetbrains unblocked @@ -74,47 +54,26 @@ heading, and (b) bumping `pubspec.yaml` `version:` in the same commit. 17% → 96%. Crosses the 95% global target (T-91). - Staged `dart doc` CI job — generates and uploads an HTML API reference for the public `lib/` surface. The step wraps - `dart doc --validate-links` and grep-fails the build on any warning, - so broken doc refs and dangling links can't accumulate. Inert with - the rest of the workflow until Gitea Actions activates. + `dart doc --validate-links` and grep-fails the build on any warning. + Inert with the rest of the workflow until Gitea Actions activates. - Mouse wheel scrolling in Claude pane — converts scroll events to - PgUp/PgDown so Claude Code (and other TUI apps) scroll their - history naturally. + PgUp/PgDown so TUI apps scroll their history naturally. - Welcome screen Tips card — six common keybindings shown below the START / RECENT row when the viewport is tall enough. - `MultitabPane` widget + `MultitabController` for panes that host N runtime tab instances of the same kind. Generic over a payload - type, supports pinned/non-closeable tabs (primary), drag-reorder, - close × on hover, and an optional `+` add button. Used by the - Claude pane to render primary + secondaries. -- `MultitabPane.keepAlive` mode — when set, all entry bodies stay - mounted via IndexedStack so switching tabs preserves their state - (PTY connections, scroll position, etc.). - -### Security - -- IPC: `git.checkout`, `git.push` reject branch/remote args starting - with `-` (closes the `--upload-pack=...` argv-injection vector). - `files.read` rejects files over 10 MB. `git.log` caps `count` at - 1000; `git.diff` / `git.stage` cap paths at 256. (T-104.) - -### Fixed - -- `TerminalView.onTapUp` now actually fires on primary tap — was - wired to a dead code path (T-93). Dead `onTapUp` surface on - `TerminalGestureHandler` / `TerminalGestureDetector` removed. -- `BufferLine.eraseRange` no longer panics when called with `end == 0`. - The right-side wide-char guard read `_data[-1]` via `getWidth(-1)`, - which threw a `RangeError`. Real trigger path: `Terminal.eraseDisplayAbove` - with the cursor at column 0 — common after `ESC[H\x1b[1J` - (home + erase-above) sequences that many TUIs emit on redraw. -- Terminal selections no longer vanish when resizing narrower — - reflow's tail-anchor handler left anchors detached past the - trimmed range. Common triggers: Ctrl+A then resize, drag past a - partially-filled line (T-92). -- `BufferLine.removeCells` / `insertCells` / `dispose` no longer skip - anchors due to concurrent list modification during iteration — - iteration now snapshots the list first (T-91). + type, supports pinned/non-closeable tabs, drag-reorder, close × on + hover, and an optional `+` add button. +- `MultitabPane.keepAlive` mode — entry bodies stay mounted via + IndexedStack so switching tabs preserves their state (PTY + connections, scroll position, etc.). +- `CONTRIBUTING.md` — human-addressed contributor guide covering + clone / build / test / DQR / tickets / commit conventions. The + `[Unreleased]` section is reorganised to one subsection per kind + per Keep a Changelog 1.1.0 (T-109). +- `make verify` — no-tests sweep (analyze + format + decisions + + changelog gate). For mid-edit checks; `make push-check` stays the + full pre-push pipeline. ### Changed @@ -139,102 +98,121 @@ heading, and (b) bumping `pubspec.yaml` `version:` in the same commit. - `TreeSitterService` and `TreeSitterLib` accept injectable FFI + asset loaders for fake-driven tests; production paths unchanged. - Tidied test imports flagged by `unnecessary_import`. -- `README.md` rewritten to match current architecture; `docs/initial-plan.md` - bannered as historical; new `docs/architecture.md` describes today's - shape (T-101). +- `README.md` rewritten to match current architecture; + `docs/initial-plan.md` bannered as historical; new + `docs/architecture.md` describes today's shape (T-101). - `SchedulerService._stopTicker` now awaits the in-flight isolate spawn before killing — closes the same race shape we fixed in PTY (T-106). - `make push-check-full` added — runs `push-check` plus integration + smoke for pre-release checks. Integration tests skip the hanging - theme_picker case (T-116) until that's fixed (T-103). + theme_picker case until that's fixed (T-103, T-116). - Governance bookkeeping: D-66 amended (floor at `coverage_floor:` in `pubspec.yaml`); `licenses.yaml` reconciled with `pubspec.yaml`; Q-1/Q-2/Q-3/Q-25 triaged; `.claude/skills/README.md` inventory added; `--no-fatal-infos` dropped from `ci/test.sh` (T-113). -- Terminal panes now render bold attributes with a real bold weight — - bundled JetBrainsMono Bold + BoldItalic are registered with the +- Terminal panes render bold attributes with a real bold weight — + bundled JetBrainsMono Bold + BoldItalic registered with the `JetBrainsMono` family at `weight: 700`. The painter's bold - suppression workaround (added when only Regular + Italic were wired - and Flutter's synthetic bold drifted advance widths) is gone. + suppression workaround is gone. - Claude pane uses `MultitabPane` for primary + secondaries — drops ~100 lines of bespoke tab-strip code, gains drag-to-reorder. - UI spacing constants live in `lib/widgets/src/spacing.dart` — `clideInset*` for paddings, `clideGap*` for sibling distances, - `clideIcon*` / `clideControlHeight` for control sizes. Inline - pixel literals replaced where they were repeated. - -### Changed - + `clideIcon*` / `clideControlHeight` for control sizes. - Tagline reads "IDE for Claude Code CLI" everywhere (welcome subtitle, README, CLAUDE.md, pubspec, web manifest, CLI banner). - -### Fixed - -- Closing a secondary Claude pane tab now kills its tmux session - on the clide socket, honouring D-41's "closing a secondary kills - that tmux session" lifecycle. Previously `pane.close` only killed - the ptyc-spawned tmux client and the server-side session leaked. -- Cold-start reap: every clide launch kills any leftover secondary - tmux sessions for the current repo before spawning new ones, so - D-41's "secondary numbering resets between runs" holds even after - an abrupt previous exit (kill -9, crash, force-quit). -- `claude.kill-all-sessions` command now actually kills the - server-side tmux sessions for the repo, not just the panes. -- Terminal cell grid no longer drifts on bold text — bold rendering - is suppressed at the painter level since synthetic bold (with no - Bold.ttf registered) shifts glyph advance widths. -- PTY surfaces errno on `forkpty` / `write` / `ioctl` failures - instead of swallowing. `execve` failures write a diagnostic to - the slave before `_exit`. `NativePty.write` and `PtySession.write` - loop on short writes; both throw `PtyException` on hard errors. -- PTY teardown order fixed — kill child first so the master fd - returns EOF, await reader isolate exit, then close the fd. - Closing earlier could briefly target a reused fd. -- Reader isolate spawn errors in `NativePty` / `PtySession` are now - surfaced via the output stream instead of silently dropped. - `_recvFdAsync` no longer leaks the `ReceivePort` on spawn throw; - `PtySession.spawn` closes the master fd if any post-receive step - fails. -- IPC server hardening: per-request 60s timeout (configurable), - broadcast/response write failures logged instead of swallowed, - client dropped on response-write failure, and the stale-socket - retry now probes for a live daemon before unlinking the socket - (refusing to start if one answers). -- `pane.spawn` and `editor.open` now map POSIX errno values to - actionable IPC error kinds. ENOENT → `not_found`, EACCES/EPERM - → `user_error` with a permissions hint, EISDIR/ENOTDIR/EEXIST - → distinct user-error/conflict, EMFILE/ENFILE → `tool_error` - with a "fd limit hit" hint. Previously every spawn/open failure - was an indistinguishable `tool_error`. - -### Security - -- Toolchain no longer resolves the dugite git binary against the open - workspace — a malicious repo could plant `native/dugite/bin/git` - and clide would run it on auto-fired `git.status`. Dugite now - resolves against the install dir + `CLIDE_DUGITE_DIR` env override - only (T-98). -- `files.read` and `files.ls` now reject symlinks whose targets live - outside the workspace — closes a path-safety bypass via in-repo - symlinks (T-102). -- `files.read` and `files.ls` now reject paths that resolve outside - the workspace root. Previously a relative path containing `..` - could read arbitrary files via path traversal. - -### Changed - - Inline terminal emulator based on xterm.dart v4.0.0 — replaces the pub.dev dependency with owned code under `lib/src/terminal/`. Drops three transitive dependencies (xterm, quiver, zmodem). - Bundle clide-specific tmux.conf for Claude pane sessions: no status bar, 50k scrollback, mouse on, zero escape delay, isolated socket. - Claude pane spawns `claude` directly inside tmux with - `CLAUDE_CODE_NO_FLICKER=1` to enable Claude's fullscreen TUI mode - (input box pinned at the bottom). -- PTY read buffer increased from 4KB to 64KB. -- Terminal view 2px padding on all sides. -- Remove bold JetBrains Mono font registration to prevent glyph width - mismatch in terminal rendering. + `CLAUDE_CODE_NO_FLICKER=1` to enable Claude's fullscreen TUI mode. +- PTY read buffer increased from 4 KB to 64 KB. +- Terminal view 2 px padding on all sides. + +### Removed + +- **`bin/clide.dart` + `DaemonServer`** — completing the D-56 + dissolution. The separate daemon process was dissolved on 2026-04-23 + but the entry point and socket server class were never deleted. + Gone now, along with orphaned tests, stale i18n strings, and + "start `clide --daemon`" error messages. +- **`ptyc/` source tree + `PtySession` + `scm_rights.dart`** — PTY + spawning migrated to Dart FFI `forkpty()` (`NativePty`) but the old + C helper and its Dart wiring were never cleaned up. Removed from + toolchain resolution, `ToolCheck` gate, backend serialization, + testmode harness, CI scripts, Makefile, and sandbox entitlements. + D-5 amended to record the retirement. +- CI golden images (`test/goldens/goldens/ci/`) — Skia anti-aliasing + of geometric shapes differs between macOS and Linux even with the + Ahem font. Replaced with platform-keyed goldens (`goldens/linux/`, + `goldens/macos/`). +- Bold JetBrains Mono font registration that prevented glyph-width + mismatch in terminal rendering — superseded by the Bold/BoldItalic + re-registration above. + +### Fixed + +- `TerminalView.onTapUp` now actually fires on primary tap — was + wired to a dead code path (T-93). Dead `onTapUp` surface on + `TerminalGestureHandler` / `TerminalGestureDetector` removed. +- `BufferLine.eraseRange` no longer panics when called with `end == 0`. + Real trigger: `Terminal.eraseDisplayAbove` with the cursor at + column 0 — common after `ESC[H\x1b[1J` (home + erase-above). +- Terminal selections no longer vanish when resizing narrower — + reflow's tail-anchor handler left anchors detached past the + trimmed range. Common triggers: Ctrl+A then resize, drag past a + partially-filled line (T-92). +- `BufferLine.removeCells` / `insertCells` / `dispose` no longer skip + anchors due to concurrent list modification during iteration — + iteration now snapshots the list first (T-91). +- Closing a secondary Claude pane tab now kills its tmux session on + the clide socket, honouring D-41's lifecycle. Previously + `pane.close` only killed the ptyc-spawned tmux client. +- Cold-start reap: every clide launch kills any leftover secondary + tmux sessions for the current repo before spawning new ones, so + D-41's "secondary numbering resets between runs" holds. +- `claude.kill-all-sessions` command now actually kills the + server-side tmux sessions for the repo, not just the panes. +- Terminal cell grid no longer drifts on bold text — bold rendering + is suppressed at the painter level since synthetic bold (with no + Bold.ttf registered) shifts glyph advance widths. +- PTY surfaces errno on `forkpty` / `write` / `ioctl` failures + instead of swallowing. `execve` failures write a diagnostic to the + slave before `_exit`. `NativePty.write` and `PtySession.write` + loop on short writes; both throw `PtyException` on hard errors. +- PTY teardown order fixed — kill child first so the master fd + returns EOF, await reader isolate exit, then close the fd. +- Reader isolate spawn errors in `NativePty` / `PtySession` are now + surfaced via the output stream instead of silently dropped. + `_recvFdAsync` no longer leaks the `ReceivePort` on spawn throw. +- IPC server hardening: per-request 60s timeout (configurable), + broadcast/response write failures logged instead of swallowed, + client dropped on response-write failure, and the stale-socket + retry now probes for a live daemon before unlinking the socket. +- `pane.spawn` and `editor.open` map POSIX errno values to actionable + IPC error kinds (ENOENT → `not_found`, EACCES/EPERM → `user_error`, + EISDIR/ENOTDIR/EEXIST → distinct kinds, EMFILE/ENFILE → + `tool_error` with an fd-limit hint). + +### Security + +- IPC: `git.checkout`, `git.push` reject branch/remote args starting + with `-` (closes the `--upload-pack=...` argv-injection vector). + `files.read` rejects files over 10 MB. `git.log` caps `count` at + 1000; `git.diff` / `git.stage` cap paths at 256 (T-104). +- Toolchain no longer resolves the dugite git binary against the open + workspace — a malicious repo could plant `native/dugite/bin/git` + and clide would run it on auto-fired `git.status`. Dugite now + resolves against the install dir + `CLIDE_DUGITE_DIR` env override + only (T-98). +- `files.read` and `files.ls` reject symlinks whose targets live + outside the workspace — closes a path-safety bypass via in-repo + symlinks (T-102). +- `files.read` and `files.ls` reject paths that resolve outside the + workspace root. Previously a relative path containing `..` could + read arbitrary files via path traversal. ## [2.0.0] — 2026-05-03 diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 00000000..bd1f3c6f --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,152 @@ +# 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`](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](https://github.com/postmeridiem/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](governance/decisions/testing.md#d-66)) +- `CHANGELOG.md` `[Unreleased]` bullets ≤ 60 words each + +## Decisions, questions, rejected (DQR) + +clide tracks architectural commitments as durable records under +[`governance/`](governance/): + +- `decisions/.md` — confirmed decisions (`D-NNN`) +- `questions/.md` — open questions (`Q-NNN`) +- `rejected/.md` — rejected proposals (`R-NNN`) + +When you make a non-trivial architectural choice, write it down: + +``` +pql decisions claim D "short title" +``` + +This reserves a fresh ID and tells you where to add the record. The +[governance/README.md](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](governance/decisions/process.md#d-37) and the bundled +[`git-commit` skill](.claude/skills/git-commit/SKILL.md). In short: + +- Imperative subject ≤ 70 chars, no Conventional Commits prefix + (this isn't a Conventional Commits repo — the archived Python + predecessor under [`legacy/`](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 ` 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/.md`. Triage later. +- **Rejected proposal:** drop an `R-NNN` under + `governance/rejected/.md`. Future-you (or a reviewer) + will be glad it's written down. + +## Reporting issues + +The public issue tracker lives at +. The Gitea mirror is +read-only. diff --git a/Makefile b/Makefile index 8bc328a8..04901248 100644 --- a/Makefile +++ b/Makefile @@ -79,6 +79,9 @@ analyze: ## flutter analyze. format: ## dart format --set-exit-if-changed. dart format --set-exit-if-changed . +.PHONY: verify +verify: analyze format decisions-validate changelog-gate ## No-tests sweep — analyze + format + decisions-validate + changelog-gate. For mid-edit "are the gates green?" checks; `push-check` is the full pre-push pipeline. + .PHONY: test test: ## Fast: analyze + format + unit + widget + golden (<60s). ci/test.sh diff --git a/README.md b/README.md index 43fec4f8..331f8429 100644 --- a/README.md +++ b/README.md @@ -45,6 +45,7 @@ Pre-v2.0 (`2.0.0-dev`). Interaction model and panel system landed. The Python Te ## Documentation - [`docs/architecture.md`](docs/architecture.md) — current architecture (read this first). +- [`CONTRIBUTING.md`](CONTRIBUTING.md) — how to clone, build, test, file tickets, and write D-records. - [`docs/initial-plan.md`](docs/initial-plan.md) — historical design doc; preserved as a snapshot of the 2026-04 plan, much of it now superseded. - [`governance/decisions/`](governance/decisions/) — confirmed decisions (`D-NNN`), open questions (`Q-NNN`), rejected alternatives (`R-NNN`). - [`CLAUDE.md`](CLAUDE.md) — Claude-addressed working notes (guardrails, repo layout).