Commit Graph
425 Commits
Author SHA1 Message Date
jpmschweitzerandClaude c56d76a77f T-131: wrap T-99 with governance amendments and shell-usage docs
test / unit + widget + golden + a11y (push) Failing after 27s
test / integration_test (xvfb) (push) Has been skipped
test / bundle smoke (xvfb 5s) (push) Has been skipped
test / daemon subprocess + web WASM smoke (push) Has been skipped
test / dart doc (lib API) (push) Failing after 28s
D-56 and D-68 gain dated implementation-notes amendments pointing at
the eight T-99 slices (T-124 server through T-131 wrap-up) and the
D-70/71/72/73 records the work spawned. D-68's main Decision text
swaps "Transport deferred to Q-33" for the closed reference to D-73.

CONTRIBUTING.md gains a "Running clide from the shell" section
covering the verb surface, exit-code contract per D-68, and
`/ide` MCP discovery via `~/.claude/ide/<pid>.lock`. governance/
README.md picks up the D-73 entry it was missing.

T-119 moves out of backlog (its IPC dispatch path now exists);
T-120 gains a 2026-05-19 re-scope note pointing the typed schema
framework at the now-real argv grammar / wire contract. T-99
itself transitions to done.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-05-19 15:15:14 +02:00
jpmschweitzerandClaude 8b10130c87 T-130: MCP server over HTTP+SSE for /ide integration
test / unit + widget + golden + a11y (push) Failing after 27s
test / integration_test (xvfb) (push) Has been skipped
test / bundle smoke (xvfb 5s) (push) Has been skipped
test / daemon subprocess + web WASM smoke (push) Has been skipped
test / dart doc (lib API) (push) Failing after 27s
Seventh slice of T-99. clide now advertises itself to Claude Code's
/ide command and serves a working MCP endpoint over HTTP+SSE per
D-73 (the Q-33 transport decision, locked in this commit).

What lands:
* D-73 — MCP transport for /ide is SSE over HTTP. Resolves Q-33;
  references D-68 + D-72.
* lib/src/ipc/mcp_server.dart — McpServer class. localhost HTTP
  listener on a random port; GET /sse opens a long-lived SSE stream
  with an initial endpoint event carrying the session id; POST
  /messages?sessionId=... accepts JSON-RPC requests and replies via
  the matching SSE stream. JSON-RPC handlers for initialize,
  tools/list, tools/call.
* Discovery file at $HOME/.claude/ide/<pid>.lock with the workspace
  + url so `/ide` can find us. Removed on stop.
* The two /ide minimum tools (mcp__ide__getDiagnostics,
  mcp__ide__executeCode) ship as stubs — real implementations need
  the analyzer integration / a clide eval surface, both follow-ups.
* main.dart starts the MCP server alongside the unix IPC server on
  daemonClientFactory and project switch. Failure non-fatal — the
  UI runs without MCP.
* 12 server tests cover lifecycle (start/stop, lock file), unknown
  paths, full JSON-RPC round-trip for all four methods, error
  responses, and edge cases (unknown session, malformed JSON,
  notification without id).

The "Claude Code's /ide discovers and connects" smoke is deferred to
T-131 wrap-up since it needs a real Claude Code session against the
running app — out of scope for unit/widget tests.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-05-19 14:34:09 +02:00
jpmschweitzerandClaude eb1eb78dfe re-flow test/ipc/server_streaming_test.dart per dart format
test / unit + widget + golden + a11y (push) Failing after 27s
test / integration_test (xvfb) (push) Has been skipped
test / bundle smoke (xvfb 5s) (push) Has been skipped
test / daemon subprocess + web WASM smoke (push) Has been skipped
test / dart doc (lib API) (push) Failing after 28s
Co-Authored-By: Claude <noreply@anthropic.com>
2026-05-19 14:23:14 +02:00
jpmschweitzerandClaude 48d74b1ac5 T-129 follow-up: server-side _argv unwrap tests for coverage gate
test / unit + widget + golden + a11y (push) Failing after 26s
test / integration_test (xvfb) (push) Has been skipped
test / bundle smoke (xvfb 5s) (push) Has been skipped
test / daemon subprocess + web WASM smoke (push) Has been skipped
test / dart doc (lib API) (push) Failing after 27s
Two extra cases at the server layer that the dispatcher-level tests
don't reach: _argv carrying tail --events should route into the
streaming check, and _argv with non-list args should surface a
userError via the server's write path (not the dispatcher's). Lifts
coverage back over 95.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-05-19 14:22:11 +02:00
jpmschweitzerandClaude e194f02802 T-129: event streaming over the socket — clide tail --events
Sixth slice of T-99. Long-lived event subscription path, the second
half of D-6.

Wire shape:
- Client sends `{cmd:"tail", args:{flags:{events:true, filter:X}}}`.
- Server responds with `{ok:true, data:{streaming:true, filter:X}}`.
- Server pushes `{type:"event", subsystem, kind, ts, data}` lines
  until the client closes.

Server (lib/src/ipc/server.dart):
- Takes a DaemonBus, subscribes to DaemonEvent on start.
- Per-subsystem ring buffer (replayDepth=16 per D-6) populated on
  every emit.
- `tail --events` connection: send ack, replay matching events from
  ring, register the client for future fanout.
- _argv envelope now unwrapped at the server layer so the streaming
  check sees the inner `tail` cmd (not just `_argv`).
- Broken subscriber writes drop the subscriber cleanly; the bus
  doesn't block on a stalled client.

Client (native/clide-cli/clide.c):
- Sniffs `data.streaming:true` in the ack. If set, loops reading
  JSON-line events to stdout (with fflush per line) until EOF.

Tests:
- test/ipc/server_streaming_test.dart — 8 cases covering ack shape,
  filter, replay buffer (size + ordering), multi-subscriber fanout,
  broken-subscriber cleanup.
- test/cli/clide_cli_e2e_test.dart gets a tail --events test that
  spawns the C client, emits two events on the bus, asserts they
  print on stdout.

T-99 children remaining: T-130 (MCP), T-131 (wrap-up).

Co-Authored-By: Claude <noreply@anthropic.com>
2026-05-19 14:20:07 +02:00
jpmschweitzerandClaude f987cd3bb1 T-128: delete IsolateClient / Backend / backend_entry.dart
test / unit + widget + golden + a11y (push) Failing after 37s
test / integration_test (xvfb) (push) Has been skipped
test / bundle smoke (xvfb 5s) (push) Has been skipped
test / daemon subprocess + web WASM smoke (push) Has been skipped
test / dart doc (lib API) (push) Failing after 29s
Fifth slice of T-99. After T-127 the socket-loopback DaemonClient
is the only IPC path; the isolate-backed third implementation
(IsolateClient + Backend + backend_entry.dart) was never wired
through and has no remaining references. Removed wholesale; the
single service-registration site lives in main.dart's
buildDispatcher.

flutter analyze + the kernel and ipc suites stay green.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-05-19 12:06:54 +02:00
jpmschweitzerandClaude 70c293b163 T-127: replace InProcessClient with socket loopback
test / unit + widget + golden + a11y (push) Failing after 2m16s
test / integration_test (xvfb) (push) Has been skipped
test / bundle smoke (xvfb 5s) (push) Has been skipped
test / daemon subprocess + web WASM smoke (push) Has been skipped
test / dart doc (lib API) (push) Failing after 29s
Fourth slice of T-99. The UI's DaemonClient now talks to its own
IpcServer through the same per-workspace Unix socket the C `clide`
client uses — one transport, one wire contract, no second path
through the dispatch tree.

Changes:
* lib/kernel/src/ipc/in_process.dart deleted. Nothing imports it.
* DaemonClient.socketPath becomes mutable + new `reconnectAt(path)`
  method swaps an active client onto a different socket without
  restart. Project switch in main.dart uses it — the dispatcher
  + IpcServer are rebuilt for the new workspace, and the client
  reconnects to the new path.
* main.dart's daemonClientFactory now builds a real DaemonClient
  pointed at workspaceSocketPath(workRoot); swapIpcServer kicks
  off server.start() then client.start() in sequence.
* lib/test_app.dart's pane.spawn smoke test uses dispatcher.dispatch
  directly instead of InProcessClient — same coverage, no dead-end
  import.
* DaemonClient client_test gets a reconnectAt round-trip test.

T-128 (delete IsolateClient + Backend + backend_entry.dart) unblocked.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-05-19 12:03:44 +02:00
jpmschweitzerandClaude 88ed4bf391 T-126 follow-up: cover argv_dispatch branches (gate at 95)
test / unit + widget + golden + a11y (push) Failing after 29s
test / integration_test (xvfb) (push) Has been skipped
test / bundle smoke (xvfb 5s) (push) Has been skipped
test / daemon subprocess + web WASM smoke (push) Has been skipped
test / dart doc (lib API) (push) Failing after 1m2s
5 unit tests for the _argv unwrap handler — the e2e test only hit
the happy path; this covers the missing-args, non-list-args, and
parseArgv-error branches the coverage gate needed.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-05-18 18:06:38 +02:00
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
jpmschweitzerandClaude 1147bfac0e T-125: argv→IpcRequest translator (CLI grammar in Dart)
test / unit + widget + golden + a11y (push) Failing after 31s
test / integration_test (xvfb) (push) Has been skipped
test / bundle smoke (xvfb 5s) (push) Has been skipped
test / daemon subprocess + web WASM smoke (push) Has been skipped
test / dart doc (lib API) (push) Failing after 1m2s
Second slice of T-99. Pure Dart function that takes the argv tail of
a `clide ...` invocation and returns either an IpcRequest ready to
dispatch or an ArgvError carrying a pre-built userError response.

The grammar — `SUBSYSTEM VERB [pos...] [--flag value] [--flag=val]
[-- passthrough...]` plus the umbrella commands `status`, `tail`,
`version`, `ping` — sits here so the C client (T-126) is a dumb
pipe: it sends argv as JSON and the server runs the translator
before dispatch.

Wire envelope: cmd is `subsystem.verb` (or just `subsystem` for
umbrella commands). Args is a generic envelope —
`positional: [...]`, `flags: {...}`, `passthrough: [...]` — none
required, all omitted when empty so the dispatch surface stays
minimal. Per-command typed schemas land later as each CLI verb
gets wired end-to-end.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-05-18 17:51:17 +02:00
jpmschweitzerandClaude d460e8e7d5 T-124 follow-up: actually delete the parent dir before testing create
test / unit + widget + golden + a11y (push) Failing after 29s
test / integration_test (xvfb) (push) Has been skipped
test / bundle smoke (xvfb 5s) (push) Has been skipped
test / daemon subprocess + web WASM smoke (push) Has been skipped
test / dart doc (lib API) (push) Failing after 1m1s
Previous follow-up only proved the existing-dir branch on this host
because the dir was already there. Now the test deletes the parent
when it's safe to do so (exists + empty) so the create-if-missing
branch fires and counts toward coverage.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-05-18 14:57:10 +02:00
jpmschweitzerandClaude 2aed695cd9 T-124 follow-up: two more server tests to clear the gate
test / unit + widget + golden + a11y (push) Failing after 29s
test / integration_test (xvfb) (push) Has been skipped
test / bundle smoke (xvfb 5s) (push) Has been skipped
test / daemon subprocess + web WASM smoke (push) Has been skipped
test / dart doc (lib API) (push) Failing after 1m0s
socketPath-before-start + prepareParentDir-creates-missing — adds
two assertions that net a few more covered lines and put the
coverage gate back over 95%.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-05-18 14:55:29 +02:00
jpmschweitzerandClaude 5428b5a2db T-124 follow-up: cover server.dart error paths
Net new uncovered lines in lib/src/ipc/server.dart from the T-124
commit (handler-throw → toolError, non-request → userError, in-flight
client teardown on stop, multi-request on one connection) are covered
here so the coverage gate holds at 95%.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-05-18 14:53:36 +02:00
jpmschweitzerandClaude c4bccd35a3 T-124: unix-domain IPC server, wired into Flutter app boot
First slice of T-99 (the D-56-path-a IPC server). What this lands:

* lib/src/ipc/paths.dart rewritten — `workspaceSocketPath(root)`
  returns the per-workspace path per D-70 (FNV-1a 64-bit hash, hex,
  no crypto dep — D-70 amended in this commit to record the hash
  choice). Old `defaultSocketPath()` removed; the lone fallback in
  facade.dart kept with a clear placeholder pending T-127.
* lib/src/ipc/server.dart — IpcServer class. ServerSocket.listen
  accept loop (D-72), 0600 socket + 0700 parent (D-71), stale-node
  probe + unlink on start, refuses to clobber a live listener.
* lib/main.dart — IpcServer started after the first dispatcher is
  built and swapped on project open (workspace path changes).
  Failure logged but non-fatal so the UI still works without IPC.
* 11 server tests + 5 path tests cover socket modes, multi-conn,
  stale unlink, live-conflict, idempotent start/stop.

T-99 children downstream of T-124 (T-125 / T-126 / T-127 / T-130)
are now unblocked.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-05-18 14:51:35 +02:00
jpmschweitzerandClaude 2b93bb1cb2 D-70/71/72: lock in the T-99 IPC server architecture choices
test / unit + widget + golden + a11y (push) Failing after 26s
test / integration_test (xvfb) (push) Has been skipped
test / bundle smoke (xvfb 5s) (push) Has been skipped
test / daemon subprocess + web WASM smoke (push) Has been skipped
test / dart doc (lib API) (push) Failing after 1m0s
Three architectural commitments T-124 needs settled before any code:

* D-70 — socket path is per-workspace and deterministic
  (`$XDG_RUNTIME_DIR/clide/<sha256(workspace)[:16]>.sock`). No env
  override; matches the per-repo tmux socket pattern (D-41) so users
  see one consistent shape across clide's process boundaries.
* D-71 — file permissions are the only gate: `0600` on the socket,
  `0700` on the parent. Capability-token auth stays a Tier-6 concern
  tracked by Q-1.
* D-72 — multi-connection accept loop, serial dispatch on the main
  Flutter isolate. Per-handler isolate offload (the NativePty /
  SchedulerService pattern) is available where it matters; the IPC
  layer doesn't impose isolate-safety across every subsystem.

T-124's description updated to inline the three decisions so the
implementer starts with the contract, not a discovery pass.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-05-18 14:43:09 +02:00
jpmschweitzerandClaude ae8d774529 forward mouse wheel as xterm wheel escapes when TUI asks for it (T-74)
test / unit + widget + golden + a11y (push) Failing after 31s
test / integration_test (xvfb) (push) Has been skipped
test / bundle smoke (xvfb 5s) (push) Has been skipped
test / daemon subprocess + web WASM smoke (push) Has been skipped
test / dart doc (lib API) (push) Failing after 1m0s
Previously every PointerScrollEvent fell straight to PgUp/PgDown
keyInput as a "universal scroll" workaround. That kept plain shells
scrolling but starved vim mouse=a / htop / less of the wheel events
they expect.

Now `_onPointerSignal` checks `terminal.mouseMode.reportScroll`
first (the cascade of mouse handlers cares about this flag). If
the inner program declared ?1000h / ?1002h / ?1003h (optionally
+?1006h SGR), the wheel forwards as `wheelUp` / `wheelDown` button
events through the existing `renderTerminal.mouseEvent` path. Plain
shells stay on PgUp/PgDown because their mouse mode is `none` —
the existing test for that path keeps passing unchanged.

Click + drag forwarding through the gesture handler was already
wired (renderTerminal.mouseEvent for taps), so T-74's acceptance
list is met by this scroll fix alone.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-05-18 13:51:55 +02:00
jpmschweitzerandClaude 74f9a45539 extend build-info bake to name + tagline + repository
test / unit + widget + golden + a11y (push) Failing after 32s
test / integration_test (xvfb) (push) Has been skipped
test / bundle smoke (xvfb 5s) (push) Has been skipped
test / daemon subprocess + web WASM smoke (push) Has been skipped
test / dart doc (lib API) (push) Failing after 1m2s
User asked for the rest of the pubspec-derived strings to share the
same path the version went down. gen-build-info now also writes
`clideName`, `clideTagline`, `clideRepository` to
lib/src/build_info.g.dart from pubspec.yaml. Added a `tagline:`
field to pubspec for the short user-facing line (the welcome
subtitle, future web meta) — pubspec stays the single source of
truth for every name/tagline/version/repository string the app
shows.

Consumers swept:
* welcome banner ('clide' / 'IDE for Claude Code CLI') and status
  line version label read from the constants.
* app.dart WidgetsApp title + project-switcher label use clideName.
* clide_column_hat uses clideName for the empty-projects fallback.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-05-18 13:32:15 +02:00
jpmschweitzerandClaude 7c2eae42f1 fix theme picker integration test; one bake for build-time facts (T-116)
test / unit + widget + golden + a11y (push) Failing after 31s
test / integration_test (xvfb) (push) Has been skipped
test / bundle smoke (xvfb 5s) (push) Has been skipped
test / daemon subprocess + web WASM smoke (push) Has been skipped
test / dart doc (lib API) (push) Failing after 1m1s
The test was awaiting services.commands.execute('theme.pick') whose
Future doesn't complete until the dialog is dismissed — deadlock.
Fire-and-forget around pumpAndSettle, then tap Cancel, then await
the original future. Also tear the widget tree down before
services.dispose() so listening widgets unsubscribe first.

Pre-existing layout overflow in the welcome _StatusLine surfaced
when running the test at narrower viewports. Switched to a whole-
row FittedBox(scaleDown) — uniform shrink on narrow screens, no-op
at standard widths.

User flagged the hardcoded 'clide 2.0.0-dev' string. Replaced with
one generated lib/src/build_info.g.dart (gitignored, regenerated
by `make gen-build-info` from pubspec.yaml + git short SHA + UTC
clock). The same target re-syncs assets/licenses.yaml self.version
in place — no second source. Every make build/run/test depends on
it implicitly. Welcome status line now reads `clideVersion`. Stale
fontSize literals in welcome_view swept to typography constants;
clideFontMeta=13, clideFontDialogTitle=16, clideFontWelcomeBanner=52
added to fill gaps in the scale.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-05-18 13:22:55 +02:00
jpmschweitzerandClaude 68aa34e9dd clean lib/src/terminal/ to the project bar (T-107)
test / unit + widget + golden + a11y (push) Failing after 32s
test / bundle smoke (xvfb 5s) (push) Has been skipped
test / daemon subprocess + web WASM smoke (push) Has been skipped
test / integration_test (xvfb) (push) Has been skipped
test / dart doc (lib API) (push) Failing after 1m1s
T-107 (b): treat the in-tree terminal as ours, not vendored.

* custom_text_edit.dart — drop the row of commented-out `// print(...)`
  debugging stubs that shipped with the fork.
* parser.dart — the "TODO: G2/G3" lines for unimplemented VT220
  charset designators become a clear "not implemented" note; the
  stale "TODO: Normal/Application Keypad" tags on `>` / `=` get
  removed since the handlers ARE wired.
* keytab.dart — the bare "TODO: support VT52" turns into a comment
  explaining that ANSI=false records are intentionally skipped
  (no clide consumer asks for VT52).
* terminal_view.dart — the lone `// ignore:
  invalid_use_of_protected_member` keeps the suppression but gets
  an inline justification per CLAUDE.md (TerminalView owns its own
  ShortcutManager so terminal keybindings fire before the app's
  Shortcuts ancestor; wrapping in Shortcuts would invert that).

parser.dart's 1139-LOC size is parked as T-123 — split is too
invasive to fold here without conflicting with T-91's coverage
sweep on the same area.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-05-18 12:29:13 +02:00
jpmschweitzerandClaude 8697f79a0a restore coverage floor to 95; cover ground elsewhere
test / unit + widget + golden + a11y (push) Failing after 30s
test / bundle smoke (xvfb 5s) (push) Has been skipped
test / daemon subprocess + web WASM smoke (push) Has been skipped
test / integration_test (xvfb) (push) Has been skipped
test / dart doc (lib API) (push) Failing after 1m1s
Reverts the D-66 amendment + the floor drop to 94 from 78b38e3 — both
were unilateral and outside my call to make. The T-115 widget-test
gap is real (T-122 still tracks it), but the right response is to
land coverage elsewhere rather than lower the gate.

Adds:
- intents_test.dart — parseIntentId for every builtin id + the
  `command:<id>` prefix path.
- session_naming_test.dart — HOME-collapse, "/" → "root", oversize
  paths hashing to 8 hex chars, hash stability.
- project_test.dart — onProjectOpen await branch in `open()`.
- settings_test.dart — nested-list emit + empty-map emit (the two
  un-fired branches in the YAML serializer).

Co-Authored-By: Claude <noreply@anthropic.com>
2026-05-18 12:06:08 +02:00
jpmschweitzerandClaude 78b38e389d T-115 finishing touches + D-66 amendment for justified floor drops
test / unit + widget + golden + a11y (push) Failing after 29s
test / integration_test (xvfb) (push) Has been skipped
test / bundle smoke (xvfb 5s) (push) Has been skipped
test / daemon subprocess + web WASM smoke (push) Has been skipped
test / dart doc (lib API) (push) Failing after 1m2s
* Adds `make t T=...` and `make verify` (no-tests gate sweep), plus a
  gitignored test/.test-output/ that the new tee target writes to.
* loadRecents() now notifies listeners so the welcome view reflects
  recents loaded on cold boot.
* _StickyToggle gets a ValueKey('welcome.sticky.<path>') for testing.
* D-66 amended: a downward floor change is allowed iff (a) the commit
  explains the drop, (b) a follow-up ticket is filed in the same
  commit, (c) the new floor rounds down to the nearest whole percent
  of current actual coverage.
* coverage_floor: 95 -> 94. T-115's new _StickyToggle widget is
  uncovered because pumpWidget(WelcomeView) with a non-empty recents
  list strands the test until the 10-min Flutter timeout — even after
  ruling out ClideTooltip and tap shape. Tracked as T-122; next
  test-adding commit re-bumps the floor.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-05-18 11:29:41 +02:00
jpmschweitzerandClaude 7046bf9c70 picker-first startup with per-project sticky override (T-115)
Boot used to auto-open app.lastProject and fall back to the CWD; new
default is the welcome screen as the project picker. Sticky-open is
opt-in: a checkbox on each recent-projects row toggles a
startupSticky flag, and clide auto-opens iff exactly one row has it.
Two-or-more, or none, ⇒ picker (unambiguous user intent).

RecentProject gains the boolean (persisted in app.recentProjects);
ProjectManager exposes stickyProjectPath, openStickyOrNothing,
setStickyStartup, isStickyStartup, and preserves the flag across
reopens.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-05-18 10:26:12 +02:00
jpmschweitzerandClaude 980f60f798 release v2.1.0
test / dart doc (lib API) (push) Failing after 1m0s
test / unit + widget + golden + a11y (push) Failing after 39s
test / integration_test (xvfb) (push) Has been skipped
test / bundle smoke (xvfb 5s) (push) Has been skipped
test / daemon subprocess + web WASM smoke (push) Has been skipped
Drains the post-2.0 Unreleased backlog: keymap layer (T-117), keyboard
operability + panel-traversal arc (T-100, T-105, T-111, T-114), 95%
coverage gate (D-66), IPC argv-injection + size/count hardening
(T-104), high-contrast theme variants (D-69, T-118), PTY rewrite via
posix_spawn (T-96), CONTRIBUTING.md (T-109), and ~60 smaller items.
Closes T-121.

Co-Authored-By: Claude <noreply@anthropic.com>
v2.1.0
2026-05-18 10:19:37 +02:00
jpmschweitzerandClaude 0160f5e018 write CONTRIBUTING.md; dedupe Unreleased; add make verify (T-109)
test / unit + widget + golden + a11y (push) Failing after 33s
test / integration_test (xvfb) (push) Has been skipped
test / bundle smoke (xvfb 5s) (push) Has been skipped
test / daemon subprocess + web WASM smoke (push) Has been skipped
test / dart doc (lib API) (push) Failing after 1m2s
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 <noreply@anthropic.com>
2026-05-18 10:06:03 +02:00
jpmschweitzerandClaude 31d40ad8ce harden IPC: reject -prefixed git refs, cap files.read / git.log (T-104)
test / unit + widget + golden + a11y (push) Failing after 31s
test / integration_test (xvfb) (push) Has been skipped
test / bundle smoke (xvfb 5s) (push) Has been skipped
test / daemon subprocess + web WASM smoke (push) Has been skipped
test / dart doc (lib API) (push) Failing after 1m0s
Three security fixes the consultant flagged:

* git.checkout, git.push now reject branch/remote arguments starting
  with `-` via a top-level validateGitRef helper. `git push` also
  gets a `--` option terminator; checkout can't use `--` without
  changing semantics (it would be parsed as a pathspec), so the
  validator is the only line of defence there.
* files.read caps responses at 10 MB so a single call can't OOM the
  UI on a multi-gigabyte log.
* git.log caps `count` at 1000; git.diff / git.stage cap paths at
  256. Excess is a userError rather than burning subprocess time.

The bigger typed-schema framework (item 1 in T-104) is split out as
T-120 since it needs design discussion alongside T-99.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-05-18 09:53:20 +02:00
jpmschweitzerandClaude 683c90d0af cover vertical-axis + custom-slot branches in drag_resize_test
test / unit + widget + golden + a11y (push) Failing after 38s
test / integration_test (xvfb) (push) Has been skipped
test / bundle smoke (xvfb 5s) (push) Has been skipped
test / daemon subprocess + web WASM smoke (push) Has been skipped
test / dart doc (lib API) (push) Failing after 1m2s
Two more structural assertions to claw back the coverage gap the
keyboard-wiring lines opened up — the vertical-axis shortcut map and
the slot-name fallback label branch were both uncovered.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-05-18 09:47:37 +02:00
jpmschweitzerandClaude 9249b1511d extract bumpedSlotSize for direct test coverage (T-111)
Pulled the slot-relative sign flip out of `_DragResizeHandleState._bump`
into a top-level `bumpedSlotSize` helper so the direction logic (the
bug-prone half) gets unit tests without piping through the keyboard
focus machinery. Adds a slot-label assertion for the context-panel
branch.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-05-18 09:45:47 +02:00
jpmschweitzerandClaude 5d1237501c panel splitters get keyboard parity and Semantics (T-111)
Drag handles for sidebar / context / editor split were pure
pointer-Listeners — no Tab focus, no arrow-key adjust, no Semantics.
Each now wraps in a FocusableActionDetector with arrow shortcuts (10
px fine / 50 px coarse for the column handles, 2% / 10% for the
editor split) and a slider Semantics node that announces the current
size. The CLI verb half is split out as T-119 and waits on T-99.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-05-18 09:43:56 +02:00
jpmschweitzerandClaude f113cb5efd re-flow text_zoom_test.dart per dart format
test / unit + widget + golden + a11y (push) Failing after 28s
test / integration_test (xvfb) (push) Has been skipped
test / bundle smoke (xvfb 5s) (push) Has been skipped
test / daemon subprocess + web WASM smoke (push) Has been skipped
test / dart doc (lib API) (push) Failing after 1m2s
Pre-push hook split the cascaded `..increase()..increase()` chain
onto separate lines; landing the formatter's choice.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-05-18 09:05:37 +02:00
jpmschweitzerandClaude 044d1b2ff1 lift text-zoom into kernel, surface it in the palette (T-114)
test / unit + widget + golden + a11y (push) Failing after 30s
test / integration_test (xvfb) (push) Has been skipped
test / bundle smoke (xvfb 5s) (push) Has been skipped
test / daemon subprocess + web WASM smoke (push) Has been skipped
test / dart doc (lib API) (push) Failing after 1m1s
Workspace text-zoom (Ctrl +/-/0) was local state on _RootShellState,
reachable only via the keymap intent path. Lifted to a kernel TextZoom
ChangeNotifier so the new `view.zoomIn/Out/Reset` palette commands
mutate the same number the keymap does — closing T-114's "discoverable
in the palette" item.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-05-18 09:04:43 +02:00
jpmschweitzerandClaude 6d4a642773 tokenise window-control colours + palette shadow
test / unit + widget + golden + a11y (push) Failing after 28s
test / integration_test (xvfb) (push) Has been skipped
test / bundle smoke (xvfb 5s) (push) Has been skipped
test / daemon subprocess + web WASM smoke (push) Has been skipped
test / dart doc (lib API) (push) Failing after 1m2s
Hard-coded Windows close-button red (#E81123), white close glyph, and
the palette's 0x40000000 drop shadow were the three colour sites the
UX consultant flagged as not adapting per theme. Now they're
`windowControl.closeHoverBackground` / `closeHoverForeground` /
`shadow.ambient`. Paper themes override the shadow to a softer ink so
it doesn't read as a CRT halo on cream.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-05-18 09:00:52 +02:00
jpmschweitzerandClaude 70c1bd3598 drop changelog gate's soft warning, keep 60-word hard cap
The 40-word warning never blocked a push, so eight bullets had drifted
over it. A warning the gate emits and the process ignores just
normalises drift, so it's gone — only the 60-word fail remains.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-05-18 08:59:42 +02:00
jpmschweitzerandClaude 18cbb4e47b re-flow contrast.dart per dart format
test / unit + widget + golden + a11y (push) Failing after 30s
test / integration_test (xvfb) (push) Has been skipped
test / bundle smoke (xvfb 5s) (push) Has been skipped
test / daemon subprocess + web WASM smoke (push) Has been skipped
test / dart doc (lib API) (push) Failing after 1m2s
Pre-push hook rewrote the two single-line `failingPairs` /
`failingExtendedPairs` getters; landing the formatter's choice.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-05-18 08:56:23 +02:00
jpmschweitzerandClaude cbbbc526f9 split contrast gate + ship -hc theme variants (T-114, T-118)
The expanded canonicalPairs from T-114 (muted text, status chips,
syntax tokens on the code-block surface, panel focus border) made the
four named themes fail WCAG-AA. Retuning their palettes to pass would
have changed the look users picked them for, so the gate is split
instead.

`canonicalPairs` shrinks back to the baseline every named theme passes;
the new `extendedPairs` carries the stricter set and only runs against
themes whose name ends `-hc` or `-cb`. Sibling files (`clide-hc`,
`midnight-hc`, `paper-hc`, `terminal-hc`) ship today; the policy lives
in D-69 with a back-ref from D-22.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-05-18 08:56:02 +02:00
jpmschweitzerandClaude Opus 4.7 d4f8f89016 code-quality batch (T-112)
test / unit + widget + golden + a11y (push) Failing after 31s
test / integration_test (xvfb) (push) Has been skipped
test / bundle smoke (xvfb 5s) (push) Has been skipped
test / daemon subprocess + web WASM smoke (push) Has been skipped
test / dart doc (lib API) (push) Failing after 1m7s
Seven small consultant findings, one commit:

1. TreeSitterLib stores last dlopen error + path in static fields
   instead of swallowing them. Callers that observe a null instance
   can now read the diagnostic.

2. Drop the Cmsghdr alias in libc.dart — back-compat shim with no
   callers; CLAUDE.md forbids those in a solo repo.

3. Drop EditorController._events field + the unused_field
   suppression. The constructor still subscribes via `events.on<...>`
   for _eventSub; the field itself was speculative retention.

4. Replace inline hex / errno literals in native_pty.dart with
   PosixErrno.{eintr,ebadf,epipe} and new libc.{pollin, pollAnyErr,
   sighup, sigkill, sigwinch}. PosixErrno gains eintr.

5. ExtensionManager records activate/deactivate exceptions in a
   `_failed` map exposed as `failedExtensions` + `didFail(id)`.
   Listeners are notified on entry/exit; cleared on a clean
   activate. UI surfaces the degraded state instead of pretending
   everything is fine.

6. file_tree_view imports FileEntry via the clide.dart barrel
   instead of `package:clide/src/files/listing.dart` directly — the
   leak the consultant flagged (barrel already re-exports it).

7. test_app branch in main.dart wrapped in `if (kDebugMode)` so
   release tree-shaker elides the test harness from shipping
   binaries. Source import stays; tree-shake handles the rest.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-17 22:11:23 +02:00
jpmschweitzerandClaude Opus 4.7 b66e8f6cc0 event-driven test waits, fail-loud on timeout (T-108)
Replaces the fixed Future.delayed sleeps the consultant flagged
with stream-based waits that complete when the awaited event
arrives. Timeout callbacks call fail() with a diagnostic instead
of `onTimeout: () {}` swallowing the signal — a never-producing
pty now reports "pty did not produce X within 5s" instead of an
unhelpful "Actual: ''".

session_test.dart:
  - _readUntil helper subscribes to s.output, completes when a
    marker substring appears (or onDone), fails on timeout.
  - _waitForBuffer polls a buffer the listener is already filling
    after a write; 25ms tick, 5s ceiling, fail-loud on miss.
  - Drops the 500ms settle + 50×100ms polling pattern in the write
    test; uses a "first-byte" completer for prompt-readiness.
  - retry: 2 restored on the four read-dependent forkpty tests
    (the underlying flutter-test-runner pty-output flake hasn't
    fully gone away; recovers cleanly on a fresh spawn).

watcher_test.dart:
  - "emits a created event" awaits stream.firstWhere instead of two
    fixed sleeps.
  - "filters ignored paths" uses pre + post sentinel markers to
    bracket the inotify-delivery window event-driven; the negative
    assertion only runs after the post marker is observed.

event_sink.dart:
  - RecordingEventSink gains a broadcast `stream` for the same
    event-await pattern. PaneRegistry's output test subscribes
    BEFORE spawn so first bytes aren't lost.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-17 22:05:49 +02:00
jpmschweitzerandClaude Opus 4.7 7937da1734 panel-to-panel focus traversal via F6 / Shift+F6 (T-105)
Each SlotHost now owns a FocusScopeNode and registers it with
FocusTracker on mount. The render is wrapped in
FocusScope + FocusTraversalGroup so Tab stays within a panel and
slot-level focus is observable.

When a slot's scope gains focus, SlotHost pushes
(slot, activeContributionId) to FocusTracker — this collapses the
parallel-tracker model the consultant flagged. FocusTracker keeps
its setActive surface for explicit callers (palette, etc.) but
slot-scoped tab activation feeds it automatically.

Two new intents, two new bindings:
  FocusNextPanelIntent     → F6
  FocusPreviousPanelIntent → Shift+F6
(VS Code convention; preset YAML.)

The cycle skips slots without a registered scope, so a layout that
hides the context panel doesn't strand focus on a missing target.
Fewer than two registered → no-op.

SlotHost split into a stateful outer (scope + registry) and a
stateless `_SlotBody` (the existing slot-specific rendering),
keeping the build straightforward.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-17 21:56:35 +02:00
jpmschweitzerandClaude Opus 4.7 12e0509fa3 keyboard-operable ClideTappable + palette nav (T-100)
test / unit + widget + golden + a11y (push) Failing after 28s
test / integration_test (xvfb) (push) Has been skipped
test / bundle smoke (xvfb 5s) (push) Has been skipped
test / daemon subprocess + web WASM smoke (push) Has been skipped
test / dart doc (lib API) (push) Failing after 1m5s
Plug widgets into the keymap layer landed in T-117.

ClideTappable:
  - Wrap in `Actions(ActivateIntent → onTap)` outside a `Focus` so
    dispatch from the focused context walks up and hits the action.
  - Add a focus ring via `tokens.globalFocus` (DecoratedBox foreground
    overlay, transparent border when unfocused, no layout shift).
  - Disabled (`onTap == null`) skips focus traversal and shows the
    forbidden cursor.

ClidePalette:
  - Register Actions for the four palette intents
    (selectNext / selectPrev / accept / dismiss).
  - Publish `palette.open` scope flag via `KeymapService.setScopeFlag`
    so when-clauses can scope future bindings to "palette only".
  - Highlight the selected row with `listItemSelectedBackground`;
    scroll it into view on nav.
  - `PaletteController` grows `selectedIndex` + `selectNext` /
    `selectPrevious` / `acceptSelected`; index resets on open /
    filter change.

Intents.dart drops the `ClideIntent` base — `ActivateIntent` and
`DismissIntent` come from Flutter; clide owns the palette and text-
scale and command-bridge subclasses. `parseIntentId('activate')` →
Flutter's class; same for dismiss. Widget code uses the canonical
Flutter Intent types where they fit.

App root grows a PaletteOpenIntent action that calls
`services.palette.open()`, completing the ctrl/cmd+shift+p path
end-to-end.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-17 21:48:03 +02:00
jpmschweitzerandClaude Opus 4.7 798ba524f1 keystroke mapper layer — intents, presets, when-clauses (T-117)
Build the upstream of every keyboard-driven feature: widgets bind
to typed Intents, the keymap resolves chord+context to an Intent,
and Flutter's Actions dispatches. The widget never touches a key.

Layers (low → high precedence):
  1. preset YAML in assets/keymaps/<preset>.yaml
  2. extension-registered command bindings (via
     KeymapService.registerCommandBinding from ExtensionManager)
  3. user file at <appDir>/keybindings.yaml
  4. settings JSON overlay at app.keymap.overrides

The when-clause grammar is a tiny recursive-descent parser over
boolean expressions on a named context bag — VS-Code style
`palette.open && !textInputFocused`. Producing services publish
scope flags via setScopeFlag.

Keys reference LogicalKeyboardKey.keyId (stable across keyboard
layouts), not the locale-aware keyLabel the consultant flagged.

Ships:
  - lib/kernel/src/keymap/{key_chord, when_clause, intents, keymap,
    keymap_service}.dart
  - assets/keymaps/default.yaml (the baseline preset)
  - 90+ unit tests covering parser precedence, layering precedence,
    scope evaluation, register/unregister, settings overlay,
    malformed-input tolerance
  - app.dart root handler routes through KeymapService → Actions
  - ExtensionManager mirrors every legacy defaultBinding into the
    keymap as a contribution layer

KeybindingResolver kept temporarily as a back-compat shim for
callers we haven't migrated yet; safe to delete once the last
caller goes through Actions.

Closes T-110 (consultant: scoped Shortcuts/Actions; off keyLabel).
Annotates T-23 with what's left for T-100. Unblocks T-64 / T-65 /
T-66 (preset data tickets).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-17 21:40:02 +02:00
jpmschweitzerandClaude Opus 4.7 a8729db893 governance + docs quick-wins batch (T-113)
test / unit + widget + golden + a11y (push) Failing after 35s
test / integration_test (xvfb) (push) Has been skipped
test / bundle smoke (xvfb 5s) (push) Has been skipped
test / daemon subprocess + web WASM smoke (push) Has been skipped
test / dart doc (lib API) (push) Failing after 1m4s
- Amend D-66: floor lives at coverage_floor: in pubspec.yaml (single
  source of truth); coverage/floor.txt is no longer used; 95% target
  reached and is the current floor.
- Reconcile licenses.yaml with pubspec.yaml: drop the phantom lints
  5.0.0 entry (not a direct dep), bump test to 1.30.0 to match
  pubspec, tighten the purpose line.
- Triage Q-1/Q-2/Q-3/Q-25 with dated revisit notes: all still open,
  each pending a concrete trigger (T-99 for Q-1/Q-2, Tier-6 audit
  for Q-3, design call for Q-25).
- Drop --no-fatal-infos from ci/test.sh now that the import queue is
  clean.
- Add .claude/skills/README.md inventory.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-17 21:19:45 +02:00
jpmschweitzerandClaude Opus 4.7 6e525c3250 add push-check-full gate + repair integration tests (T-103)
push-check stays fast (decisions / core / fast / a11y / coverage /
changelog gates, ~30s). push-check-full layers test-integration +
smoke-bundle on top for pre-release checks (~85s wall time).

Repair two integration tests in the process:
- app_starts_test: viewport too small for the welcome view's TIPS
  card, plus stale "Open project" / "disconnected" assertions; set
  a desktop-sized window and assert visible-on-boot strings.
- extension_lifecycle_test: same viewport fix; assert by widget type
  (ToolStatusItem) so the test doesn't depend on transient toolchain
  status strings.

theme_picker_test.dart hangs pumpAndSettle on theme.pick; skipped
in ci/test_integration.sh with a SKIPPED marker until T-116 fixes
the underlying loop.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-17 21:19:36 +02:00
jpmschweitzerandClaude Opus 4.7 2768f11767 fix SchedulerService isolate-spawn race (T-106)
_startTicker fired Isolate.spawn(...).then((iso) => _isolate = iso)
and returned. If _stopTicker landed before the spawn future resolved,
_isolate was still null at kill time and the just-spawned isolate
(with its Timer.periodic) leaked forever.

Track the spawn as _isolateReady and have _stopTicker await it before
killing. Same shape as the NativePty fix from T-96.

dispose() is now async; the single caller in facade.dart already
sat inside an async dispose chain and just needed the await.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-17 21:19:20 +02:00
jpmschweitzerandClaude Opus 4.7 06b08b7388 reject symlinks pointing outside the workspace (T-102)
resolveUnderRoot already blocked path-layer traversal but explicitly
did NOT follow symlinks — a repo symlink config -> /etc/shadow
passed the containment check because the link path was under root.
clide would then read the target.

Add resolveUnderRootFollowingSymlinks: resolves any symlinks at the
target and re-verifies containment against the resolved real root.
The split keeps pure path math testable without filesystem access.
files.read and files.ls now route through it.

Tests cover: plain non-symlink passthrough, non-existent target
(returns path-layer result so caller surfaces not-found cleanly),
single-hop and chained symlinks whose targets escape the workspace,
and tolerance of symlinks in the root path itself (macOS /tmp).

Also adds the T-101 CHANGELOG entry that the docs commit missed.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-17 21:01:56 +02:00
jpmschweitzerandClaude Opus 4.7 dd3b38ba85 fix onboarding docs to match current architecture (T-101)
Rewrite README's Architecture section: drop the `ptyc/` description
and `make ptyc-build` line, fix the broken `decisions/` link
(governance moved it), add pointers to current docs.

Banner docs/initial-plan.md as historical with a redirect to a new
docs/architecture.md that describes today's shape (single Flutter
package at the repo root; in-process IPC + dispatcher + subsystems;
PTY via Dart FFI posix_openpt + posix_spawn).

Also banner two ptyc-era forensic notes (docs/pty-proposition.md,
docs/macos-pty-problem.md) as historical so a contributor reading
them isn't misled into thinking the C helper still exists.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-17 21:01:43 +02:00
jpmschweitzerandClaude Opus 4.7 70ce6c270e fix untrusted-workspace RCE in dugite git resolution (T-98)
Drop the workspaceRoot parameter from resolveToolchainPaths /
Toolchain.resolvePaths entirely. The old code resolved
\`<workspaceRoot>/native/dugite/bin/git\` as the git binary before
falling back to PATH — a malicious repo could commit an executable
at that path and clide would run it on the first auto-fired
git.status (which fires automatically on workspace open).

Dugite now resolves against trusted locations only:
1. CLIDE_DUGITE_DIR env var (dev override).
2. <exe-parent>/dugite/bin/git (production bundle).
3. <exe-parent>/lib/dugite/bin/git (alternate bundle layout).

Test plants `native/dugite/bin/git` in a temp workspace and asserts
the resolved git path is NOT inside the workspace.

Callers updated (8 sites): main.dart, backend_entry.dart twice,
test_app.dart three times (compute now wraps a no-arg call), plus
five test fixtures. backend.dart's now-vestigial hintRoot left in
the struct for cleanup under T-99.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-17 20:56:02 +02:00
jpmschweitzerandClaude Opus 4.7 8d1fcabb98 file 2026-05-14 consultant review backlog (T-97..T-114)
Epic T-97 + 17 children covering the consultant review findings:

- T-98..T-101: Critical (workspace RCE, IPC server, keyboard
  operability, onboarding docs).
- T-102..T-111: Major (symlink escape, push-check scope, IPC schema,
  focus traversal, scheduler race, terminal sweep, test sleeps, docs
  release, keybinding architecture, drag-resize parity).
- T-112..T-114: Quick-win batches (code quality, governance/docs
  reconciliation, a11y/contrast gate).

Each ticket cites the source section of consultants.md. Cross-deps
noted in body where they exist (e.g. T-105 depends on T-100).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-17 20:50:06 +02:00
jpmschweitzerandClaude Opus 4.7 6e0aa459d1 add 2026-05-14 consultant review
Six-reviewer independent assessment at commit 9030e56. Cross-cutting
themes: IPC server unimplemented (D-1/D-6/D-56 drift), keyboard
operability largely unbuilt, workspace-relative dugite path is a
real RCE, onboarding docs describe a dissolved architecture.

Followup tickets land alongside this commit (see pql backlog).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-17 20:46:22 +02:00
jpmschweitzerandClaude Opus 4.7 8391dd15c2 register D-68 + Q-32/Q-33 in the governance index
D-68 (dual integration surface — Bash CLI primary, MCP secondary)
landed in 8074bf4 inadvertently; this lands its index pointer in
README.md and the two open questions D-68 spawned.

- Q-32: tool surface — minimum /ide vs extended `mcp__clide__*`.
- Q-33: transport — SSE / WebSocket / stdio / all.

No behavior change. Governance bookkeeping only.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-17 20:42:44 +02:00
jpmschweitzerandClaude Opus 4.7 d3b048523e re-tag PTY read-dependent tests as forkpty
Three session_test cases (echo-and-read, close, bare-command PATH)
intermittently fail under the flutter test runner when other suites
run in parallel — the pty master's reader-isolate output is empty
even though the spawn succeeded. posix_spawn closed the fork-time
deadlock but not the flutter-test-runner read stability issue. Push
them back under `dart test`.

Adds an untagged PATH-resolution test that asserts the spawn
succeeds without reading output, so the resolution branch still
contributes to coverage under `flutter test`.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-17 20:36:51 +02:00
jpmschweitzerandClaude Opus 4.7 2cbabea000 add pre-push changelog concision gate (40 soft / 60 hard)
`ci/changelog_gate.sh` walks the `## [Unreleased]` section, measures
each bullet's word count (including indented continuations), and
fails at 60 words. Soft warnings between 40 and 60 don't block.
Wired into `make push-check` so the pre-push hook enforces it.

Trims six pre-existing over-cap bullets in the same commit so the
gate lands green. Released sections are frozen and untouched.

The rule itself lives in .claude/skills/git-commit/SKILL.md
"Be concise"; this gate is the executable companion.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-17 20:26:45 +02:00