record D-79: workspace content search is pure-Dart, outside pql
Find-in-files / replace (T-52/T-53) run as an in-process isolate-pool grep engine behind an engine-agnostic search.grep verb — not pql (its search is a ranked document index, with no line numbers, regex, or glob) and not a ripgrep shell-out (unvendored, not guaranteed cross-platform). ripgrep is kept as a future optional accelerator behind the same verb. Clarifies the D-3 wrap-pql boundary: content grep is a code-navigation primitive pql does not offer. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -34,3 +34,4 @@ INSERT INTO ticket_deps (blocker_id, blocked_id, created_at, updated_at, deleted
|
||||
INSERT INTO ticket_deps (blocker_id, blocked_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-170', 'T-180', '2026-05-25 09:16:24', '2026-05-25 09:16:24', NULL, '532f10087fe5c703f64b9d7b69abd3ac', 1) ON CONFLICT(blocker_id, blocked_id) DO UPDATE SET updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_deps.updated_at OR (excluded.updated_at = ticket_deps.updated_at AND excluded.hash > ticket_deps.hash);
|
||||
INSERT INTO ticket_deps (blocker_id, blocked_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-169', 'T-181', '2026-05-25 09:16:24', '2026-05-25 09:16:24', NULL, '9db789b59940b908f0f57750820b3a35', 1) ON CONFLICT(blocker_id, blocked_id) DO UPDATE SET updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_deps.updated_at OR (excluded.updated_at = ticket_deps.updated_at AND excluded.hash > ticket_deps.hash);
|
||||
INSERT INTO ticket_deps (blocker_id, blocked_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-182', 'T-183', '2026-05-25 09:26:48', '2026-05-25 09:26:48', NULL, '5f87cfd814f3f69900a306714a6b5816', 1) ON CONFLICT(blocker_id, blocked_id) DO UPDATE SET updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_deps.updated_at OR (excluded.updated_at = ticket_deps.updated_at AND excluded.hash > ticket_deps.hash);
|
||||
INSERT INTO ticket_deps (blocker_id, blocked_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-52', 'T-53', '2026-05-31 18:01:19', '2026-05-31 18:01:19', NULL, 'a099cda147ee9f0f524b7c4dd7290d7a', 1) ON CONFLICT(blocker_id, blocked_id) DO UPDATE SET updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > ticket_deps.updated_at OR (excluded.updated_at = ticket_deps.updated_at AND excluded.hash > ticket_deps.hash);
|
||||
|
||||
@@ -2325,3 +2325,53 @@ 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-193', 'description', 'test/builtin/claude/transcript_publisher_test.dart ''TranscriptPublisher republishes reader items onto the bus (lead channel + item key)'' fails intermittently during ''make test-coverage'' / push-check (the full parallel flutter test run) but PASSES reliably in isolation (flutter test <file> = 3/3 green). Observed 2026-05-31 — one run failed it, the immediately-preceding agent run passed all 2221. Not caused by T-192 (the coverage path uses default concurrency, unchanged) — it''s a pre-existing timing/ordering flake, likely a MessageBus delivery race: the test asserts a republished item arrived on the lead channel, and under concurrent load the broadcast-stream delivery may not have been awaited deterministically. Fix: make the test await delivery deterministically (pump/await the bus microtask, or expectLater on the stream) rather than relying on timing; check TranscriptPublisher for any real ordering bug too. Impact: intermittently red push-check → tempts --no-verify (see T-192). Acceptance: the test passes deterministically across repeated full-suite runs; root cause (test-only vs a real publisher race) identified.', 'test/builtin/claude/transcript_publisher_test.dart ''TranscriptPublisher republishes reader items onto the bus (lead channel + item key)'' fails intermittently during ''make test-coverage'' / push-check (the full parallel flutter test run) but PASSES reliably in isolation (flutter test <file> = 3/3 green). Observed 2026-05-31 — one run failed it, the immediately-preceding agent run passed all 2221. Not caused by T-192 (the coverage path uses default concurrency, unchanged) — it''s a pre-existing timing/ordering flake, likely a MessageBus delivery race: the test asserts a republished item arrived on the lead channel, and under concurrent load the broadcast-stream delivery may not have been awaited deterministically. Fix: make the test await delivery deterministically (pump/await the bus microtask, or expectLater on the stream) rather than relying on timing; check TranscriptPublisher for any real ordering bug too. Impact: intermittently red push-check → tempts --no-verify (see T-192). Acceptance: the test passes deterministically across repeated full-suite runs; root cause (test-only vs a real publisher race) identified.
|
||||
|
||||
MECHANISM (the systemic answer, per discussion): tests are parallel by default (--concurrency global = invisible parallel=true); express ''parallel=false'' for a vulnerable test via a TAG + a serial pass. The ''pty'' tag is the half-built precedent: it splits PTY tests into a separate ''dart test'' pass but did NOT serialize it (and leaned on retry:2). DONE 2026-05-31: that pty pass now runs --concurrency=1 and the retry:2 band-aids are removed — PTY/registry flake verified stable 4/4 runs. REMAINING: generalize to a ''serial'' tag for FLUTTER-side vulnerable tests (transcript_publisher''s ''republishes reader items onto the bus''): declare ''serial'' in dart_test.yaml; the parallel flutter run adds --exclude-tags serial; add a serial flutter pass (flutter test --tags serial --concurrency=1). WRINKLE for the gate: two flutter --coverage passes can''t just concatenate lcov (duplicate SF records double-count) — must real-merge (package:coverage format_coverage / lcov -a), OR run the serial pass without coverage if those tests'' source lines are covered elsewhere (check transcript_publisher.dart coverage from other tests first). ALTERNATIVE for the single transcript_publisher case: fix its await to be deterministic (await the bus delivery / expectLater on the stream) rather than quarantine — cheaper if it''s a test-timing bug vs a real bus race. Acceptance: no flaky test runs in the parallel pool; vulnerable tests are tagged + run serially; gate is deterministic across repeated full runs.', NULL, '2026-05-31 17:01:58', '2026-05-31 17:01:58', '2026-05-31 17:01:58', NULL, 'efff36d6e6969000e1085c073788425c', 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-193', 'status', 'backlog', 'done', NULL, '2026-05-31 17:26:25', '2026-05-31 17:26:25', '2026-05-31 17:26:25', NULL, '36b91e385984e40c8497f50498ed5ca6', 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-51', 'description', 'Fuzzy file finder overlay, separate from the command palette. Wired to pql files with debounced input. Shows file path with match highlighting. Enter opens in editor or markdown viewer depending on file type. Recent files shown when input is empty.', 'Fuzzy file finder overlay, separate from the command palette. Wired to pql files with debounced input. Shows file path with match highlighting. Enter opens in editor or markdown viewer depending on file type. Recent files shown when input is empty.
|
||||
|
||||
Refinement (2026-05-31):
|
||||
|
||||
File source — no repo-wide listing exists today (files.ls is per-directory). Add a `files.walk` daemon IPC verb that recursively walks the workspace honoring IgnoreSet (per D-4) and returns a flat file list.
|
||||
|
||||
Open routing mirrors the files panel after T-187: a `.md` path publishes (''builtin.markdown'',''selection'',{path}) to open the markdown reader; any other path uses editor.open. (The ticket''s "editor or markdown viewer depending on file type" IS this split.)
|
||||
|
||||
Recent files — none exists today (ProjectManager.recents tracks projects, not files). Add a small kernel RecentFilesService (bounded in-memory list of repo-relative paths) pushed from the shared open dispatch in file_tree_view.dart; empty-input state shows it.
|
||||
|
||||
Overlay mirrors the ClidePalette chrome and reuses the 200ms-debounced ClideFilterBox. New QuickOpenIntent bound to ctrl+p / meta+p, guarded so it does not fire while the command palette is open (when: !palette.open — verify the keymap when-parser supports negation; otherwise pick a non-conflicting default). ctrl+p is currently palette.selectPrevious.
|
||||
|
||||
Files: new lib/builtin/files/src/quick_open_overlay.dart + extension registration; new lib/kernel/src/recent_files.dart; files_commands.dart (files.walk); keymap intents + default.yaml; app.dart Stack mount alongside ClidePalette.', NULL, '2026-05-31 18:01:12', '2026-05-31 18:01:12', '2026-05-31 18:01:12', NULL, '4dd53f3fcb284e9231352c47561fafcf', 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-52', 'description', 'Workspace-wide text search via ripgrep or grep fallback. Sidebar panel with search input, match-in-context results, clickable to open file at line. Supports regex toggle, case sensitivity toggle, include/exclude glob filters.', 'Workspace-wide text search via ripgrep or grep fallback. Sidebar panel with search input, match-in-context results, clickable to open file at line. Supports regex toggle, case sensitivity toggle, include/exclude glob filters.
|
||||
|
||||
Refinement (2026-05-31) — see D-79:
|
||||
|
||||
Backend = pure-Dart in-process grep engine. An isolate worker pool fans non-ignored files across cores; match with RegExp, with a literal indexOf fast-path when the regex toggle is off. Results STREAM over a new engine-agnostic IPC verb `search.grep` (args: terms, regex, ignoreCase, include/exclude globs), with cancellation when the query changes. NOT ripgrep/grep (unvendored external binary, not guaranteed cross-platform — against prefer-zero-deps). NOT pql: pql search is a ranked document index (path/score/connections) with no line numbers, snippets, regex, case, or glob, so it structurally cannot satisfy find-in-files.
|
||||
|
||||
Future work (note only, do not build): optional "use rg when on PATH, else the built-in engine" accelerator behind the same search.grep verb — no caller changes (the contract is engine-agnostic).
|
||||
|
||||
Tree-sitter is NOT used here — it is a parser for structural/semantic search (find usages, go-to-def), a separate future feature, not text grep.
|
||||
|
||||
Ignore handling — implement the FULL ignore_files: ordered layering from .pql/config.yaml (D-4) in-scope. This closes the never-filed "Q-024 (to be recorded)" placeholder comment at lib/src/daemon/files_commands.dart:161 — remove that comment. (Current _defaultIgnore only reads .gitignore + .clideignore.)
|
||||
|
||||
Open-at-line — editor.open takes only {path} today. Extend it with an optional 1-based `line` param (daemon converts line to byte offset and sets the initial Selection); backward compatible (line null = open at top). Enables CLI parity: clide editor open <path> --line N.
|
||||
|
||||
Sidebar panel follows the files/git/decisions TabContribution(slot: Slots.sidebar) pattern; custom widgets, no Material/Cupertino.
|
||||
|
||||
Files: new lib/builtin/search/{extension,search_controller,search_panel_view}.dart; new lib/src/daemon/search_commands.dart (search.grep + isolate pool); lib/src/files ignore layering; editor_commands.dart + registry.dart (line param); IPC schema + dispatcher registration; CLI `clide search grep` verb.', NULL, '2026-05-31 18:01:13', '2026-05-31 18:01:13', '2026-05-31 18:01:13', NULL, '6a634c4a87cdf591534f65892865272e', 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-53', 'description', 'Companion to find-in-files. Preview all replacements before applying. Support regex capture groups in replacement. Respects ignore patterns from pql config.', 'Companion to find-in-files. Preview all replacements before applying. Support regex capture groups in replacement. Respects ignore patterns from pql config.
|
||||
|
||||
Refinement (2026-05-31):
|
||||
|
||||
Depends on T-52 — consumes its match model (path, line, start/end offset per match, capture groups) as the replacement input. Sequence after T-52 (T-52 blocks T-53).
|
||||
|
||||
Write path — no files.write IPC exists today (only files.root/read/ls/watch; the sole write path is EditorRegistry.save on an open buffer). Add a `files.write` verb gated by resolveUnderRootFollowingSymlinks (lib/src/files/path_safety.dart). Do NOT route through the editor (would pollute the open-buffer list with dozens of temp buffers).
|
||||
|
||||
Regex capture groups — Dart replaceAllMapped / $1; the T-52 engine must surface match groups.
|
||||
|
||||
Preview — the existing diff view (lib/builtin/diff) is git-only (hardwired to git.diff). Build a ReplacementPreviewController/view that reuses the diff RENDER primitives (DiffLine / _HunkView styling) fed a computed in-memory before/after set. Do NOT generalize/entangle the git DiffController.
|
||||
|
||||
Safety (user decision) — REQUIRE A CLEAN GIT WORKING TREE before apply: refuse with "commit or stash your changes first" if there are unstaged changes, making git the lossless undo layer. Preview shown first; final confirmation via the existing DialogRouter (as used by git discard-confirm).
|
||||
|
||||
Ignore — same full ignore_files: layering as T-52 (D-4).
|
||||
|
||||
Files: lib/src/daemon/files_commands.dart (files.write); new replacement-preview controller/view under lib/builtin/search/; reuse path_safety + diff render primitives; DialogRouter confirm; IPC schema + CLI parity verbs.', NULL, '2026-05-31 18:01:14', '2026-05-31 18:01:14', '2026-05-31 18:01:14', NULL, '4d4ee79e82da8d3985dbf016ed6315ba', 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-52', 'status', 'backlog', 'in_progress', NULL, '2026-05-31 18:01:23', '2026-05-31 18:01:23', '2026-05-31 18:01:23', NULL, '1715b4493ba4beda216d701cf0c41121', 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-51', 'status', 'backlog', 'in_progress', NULL, '2026-05-31 18:01:23', '2026-05-31 18:01:23', '2026-05-31 18:01:23', NULL, '386ac9084ce3fe9c035a56bba3cd1c42', 1) ON CONFLICT(hash) DO NOTHING;
|
||||
|
||||
@@ -2963,3 +2963,53 @@ INSERT INTO tickets (id, type, parent_id, title, description, status, priority,
|
||||
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-193', 'bug', NULL, 'Flaky: transcript_publisher_test ''republishes reader items onto the bus'' fails under the full parallel run', 'test/builtin/claude/transcript_publisher_test.dart ''TranscriptPublisher republishes reader items onto the bus (lead channel + item key)'' fails intermittently during ''make test-coverage'' / push-check (the full parallel flutter test run) but PASSES reliably in isolation (flutter test <file> = 3/3 green). Observed 2026-05-31 — one run failed it, the immediately-preceding agent run passed all 2221. Not caused by T-192 (the coverage path uses default concurrency, unchanged) — it''s a pre-existing timing/ordering flake, likely a MessageBus delivery race: the test asserts a republished item arrived on the lead channel, and under concurrent load the broadcast-stream delivery may not have been awaited deterministically. Fix: make the test await delivery deterministically (pump/await the bus microtask, or expectLater on the stream) rather than relying on timing; check TranscriptPublisher for any real ordering bug too. Impact: intermittently red push-check → tempts --no-verify (see T-192). Acceptance: the test passes deterministically across repeated full-suite runs; root cause (test-only vs a real publisher race) identified.
|
||||
|
||||
MECHANISM (the systemic answer, per discussion): tests are parallel by default (--concurrency global = invisible parallel=true); express ''parallel=false'' for a vulnerable test via a TAG + a serial pass. The ''pty'' tag is the half-built precedent: it splits PTY tests into a separate ''dart test'' pass but did NOT serialize it (and leaned on retry:2). DONE 2026-05-31: that pty pass now runs --concurrency=1 and the retry:2 band-aids are removed — PTY/registry flake verified stable 4/4 runs. REMAINING: generalize to a ''serial'' tag for FLUTTER-side vulnerable tests (transcript_publisher''s ''republishes reader items onto the bus''): declare ''serial'' in dart_test.yaml; the parallel flutter run adds --exclude-tags serial; add a serial flutter pass (flutter test --tags serial --concurrency=1). WRINKLE for the gate: two flutter --coverage passes can''t just concatenate lcov (duplicate SF records double-count) — must real-merge (package:coverage format_coverage / lcov -a), OR run the serial pass without coverage if those tests'' source lines are covered elsewhere (check transcript_publisher.dart coverage from other tests first). ALTERNATIVE for the single transcript_publisher case: fix its await to be deterministic (await the bus delivery / expectLater on the stream) rather than quarantine — cheaper if it''s a test-timing bug vs a real bus race. Acceptance: no flaky test runs in the parallel pool; vulnerable tests are tagged + run serially; gate is deterministic across repeated full runs.', 'backlog', 'medium', NULL, NULL, NULL, '2026-05-31 16:12:42', '2026-05-31 17:01:58', NULL, 'a53bd052ae3024be526c4a6d54da23ef', 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-193', 'bug', NULL, 'Flaky: transcript_publisher_test ''republishes reader items onto the bus'' fails under the full parallel run', 'test/builtin/claude/transcript_publisher_test.dart ''TranscriptPublisher republishes reader items onto the bus (lead channel + item key)'' fails intermittently during ''make test-coverage'' / push-check (the full parallel flutter test run) but PASSES reliably in isolation (flutter test <file> = 3/3 green). Observed 2026-05-31 — one run failed it, the immediately-preceding agent run passed all 2221. Not caused by T-192 (the coverage path uses default concurrency, unchanged) — it''s a pre-existing timing/ordering flake, likely a MessageBus delivery race: the test asserts a republished item arrived on the lead channel, and under concurrent load the broadcast-stream delivery may not have been awaited deterministically. Fix: make the test await delivery deterministically (pump/await the bus microtask, or expectLater on the stream) rather than relying on timing; check TranscriptPublisher for any real ordering bug too. Impact: intermittently red push-check → tempts --no-verify (see T-192). Acceptance: the test passes deterministically across repeated full-suite runs; root cause (test-only vs a real publisher race) identified.
|
||||
|
||||
MECHANISM (the systemic answer, per discussion): tests are parallel by default (--concurrency global = invisible parallel=true); express ''parallel=false'' for a vulnerable test via a TAG + a serial pass. The ''pty'' tag is the half-built precedent: it splits PTY tests into a separate ''dart test'' pass but did NOT serialize it (and leaned on retry:2). DONE 2026-05-31: that pty pass now runs --concurrency=1 and the retry:2 band-aids are removed — PTY/registry flake verified stable 4/4 runs. REMAINING: generalize to a ''serial'' tag for FLUTTER-side vulnerable tests (transcript_publisher''s ''republishes reader items onto the bus''): declare ''serial'' in dart_test.yaml; the parallel flutter run adds --exclude-tags serial; add a serial flutter pass (flutter test --tags serial --concurrency=1). WRINKLE for the gate: two flutter --coverage passes can''t just concatenate lcov (duplicate SF records double-count) — must real-merge (package:coverage format_coverage / lcov -a), OR run the serial pass without coverage if those tests'' source lines are covered elsewhere (check transcript_publisher.dart coverage from other tests first). ALTERNATIVE for the single transcript_publisher case: fix its await to be deterministic (await the bus delivery / expectLater on the stream) rather than quarantine — cheaper if it''s a test-timing bug vs a real bus race. Acceptance: no flaky test runs in the parallel pool; vulnerable tests are tagged + run serially; gate is deterministic across repeated full runs.', 'done', 'medium', NULL, NULL, NULL, '2026-05-31 16:12:42', '2026-05-31 17:26:25', NULL, 'f7daf684c390b57a474aadb2fcf6f532', 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-53', 'story', NULL, 'search and replace across files', 'Companion to find-in-files. Preview all replacements before applying. Support regex capture groups in replacement. Respects ignore patterns from pql config.
|
||||
|
||||
Refinement (2026-05-31):
|
||||
|
||||
Depends on T-52 — consumes its match model (path, line, start/end offset per match, capture groups) as the replacement input. Sequence after T-52 (T-52 blocks T-53).
|
||||
|
||||
Write path — no files.write IPC exists today (only files.root/read/ls/watch; the sole write path is EditorRegistry.save on an open buffer). Add a `files.write` verb gated by resolveUnderRootFollowingSymlinks (lib/src/files/path_safety.dart). Do NOT route through the editor (would pollute the open-buffer list with dozens of temp buffers).
|
||||
|
||||
Regex capture groups — Dart replaceAllMapped / $1; the T-52 engine must surface match groups.
|
||||
|
||||
Preview — the existing diff view (lib/builtin/diff) is git-only (hardwired to git.diff). Build a ReplacementPreviewController/view that reuses the diff RENDER primitives (DiffLine / _HunkView styling) fed a computed in-memory before/after set. Do NOT generalize/entangle the git DiffController.
|
||||
|
||||
Safety (user decision) — REQUIRE A CLEAN GIT WORKING TREE before apply: refuse with "commit or stash your changes first" if there are unstaged changes, making git the lossless undo layer. Preview shown first; final confirmation via the existing DialogRouter (as used by git discard-confirm).
|
||||
|
||||
Ignore — same full ignore_files: layering as T-52 (D-4).
|
||||
|
||||
Files: lib/src/daemon/files_commands.dart (files.write); new replacement-preview controller/view under lib/builtin/search/; reuse path_safety + diff render primitives; DialogRouter confirm; IPC schema + CLI parity verbs.', 'backlog', 'medium', NULL, NULL, NULL, '2026-04-23 20:32:06', '2026-05-31 18:01:14', NULL, '8d258948527bb868dbd6c5b54038297b', 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-51', 'story', NULL, 'quick file open (Ctrl+P)', 'Fuzzy file finder overlay, separate from the command palette. Wired to pql files with debounced input. Shows file path with match highlighting. Enter opens in editor or markdown viewer depending on file type. Recent files shown when input is empty.
|
||||
|
||||
Refinement (2026-05-31):
|
||||
|
||||
File source — no repo-wide listing exists today (files.ls is per-directory). Add a `files.walk` daemon IPC verb that recursively walks the workspace honoring IgnoreSet (per D-4) and returns a flat file list.
|
||||
|
||||
Open routing mirrors the files panel after T-187: a `.md` path publishes (''builtin.markdown'',''selection'',{path}) to open the markdown reader; any other path uses editor.open. (The ticket''s "editor or markdown viewer depending on file type" IS this split.)
|
||||
|
||||
Recent files — none exists today (ProjectManager.recents tracks projects, not files). Add a small kernel RecentFilesService (bounded in-memory list of repo-relative paths) pushed from the shared open dispatch in file_tree_view.dart; empty-input state shows it.
|
||||
|
||||
Overlay mirrors the ClidePalette chrome and reuses the 200ms-debounced ClideFilterBox. New QuickOpenIntent bound to ctrl+p / meta+p, guarded so it does not fire while the command palette is open (when: !palette.open — verify the keymap when-parser supports negation; otherwise pick a non-conflicting default). ctrl+p is currently palette.selectPrevious.
|
||||
|
||||
Files: new lib/builtin/files/src/quick_open_overlay.dart + extension registration; new lib/kernel/src/recent_files.dart; files_commands.dart (files.walk); keymap intents + default.yaml; app.dart Stack mount alongside ClidePalette.', 'in_progress', 'medium', NULL, NULL, NULL, '2026-04-23 20:32:06', '2026-05-31 18:01:23', NULL, '40a056ccb71d46695c1aba494f5e4eaa', 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-52', 'story', NULL, 'find in files (Ctrl+Shift+F)', 'Workspace-wide text search via ripgrep or grep fallback. Sidebar panel with search input, match-in-context results, clickable to open file at line. Supports regex toggle, case sensitivity toggle, include/exclude glob filters.
|
||||
|
||||
Refinement (2026-05-31) — see D-79:
|
||||
|
||||
Backend = pure-Dart in-process grep engine. An isolate worker pool fans non-ignored files across cores; match with RegExp, with a literal indexOf fast-path when the regex toggle is off. Results STREAM over a new engine-agnostic IPC verb `search.grep` (args: terms, regex, ignoreCase, include/exclude globs), with cancellation when the query changes. NOT ripgrep/grep (unvendored external binary, not guaranteed cross-platform — against prefer-zero-deps). NOT pql: pql search is a ranked document index (path/score/connections) with no line numbers, snippets, regex, case, or glob, so it structurally cannot satisfy find-in-files.
|
||||
|
||||
Future work (note only, do not build): optional "use rg when on PATH, else the built-in engine" accelerator behind the same search.grep verb — no caller changes (the contract is engine-agnostic).
|
||||
|
||||
Tree-sitter is NOT used here — it is a parser for structural/semantic search (find usages, go-to-def), a separate future feature, not text grep.
|
||||
|
||||
Ignore handling — implement the FULL ignore_files: ordered layering from .pql/config.yaml (D-4) in-scope. This closes the never-filed "Q-024 (to be recorded)" placeholder comment at lib/src/daemon/files_commands.dart:161 — remove that comment. (Current _defaultIgnore only reads .gitignore + .clideignore.)
|
||||
|
||||
Open-at-line — editor.open takes only {path} today. Extend it with an optional 1-based `line` param (daemon converts line to byte offset and sets the initial Selection); backward compatible (line null = open at top). Enables CLI parity: clide editor open <path> --line N.
|
||||
|
||||
Sidebar panel follows the files/git/decisions TabContribution(slot: Slots.sidebar) pattern; custom widgets, no Material/Cupertino.
|
||||
|
||||
Files: new lib/builtin/search/{extension,search_controller,search_panel_view}.dart; new lib/src/daemon/search_commands.dart (search.grep + isolate pool); lib/src/files ignore layering; editor_commands.dart + registry.dart (line param); IPC schema + dispatcher registration; CLI `clide search grep` verb.', 'in_progress', 'medium', NULL, NULL, NULL, '2026-04-23 20:32:06', '2026-05-31 18:01:23', NULL, '7a80f9a305e2e93f33820bea9cb68740', 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);
|
||||
|
||||
@@ -120,6 +120,7 @@ You might also want, project-permitting:
|
||||
- [D-76: ClaudeConfig — Claude's config surface is clide's app settings (builtin-owned, watched, probe-cached per version)](decisions/architecture.md#d-76-claudeconfig--claudes-config-surface-is-clides-app-settings-builtin-owned-watched-probe-cached-per-version) — _architecture_
|
||||
- [D-77: Drive Claude via the stream-json control protocol; teams become a clide-owned coordination layer](decisions/architecture.md#d-77-drive-claude-via-the-stream-json-control-protocol-teams-become-a-clide-owned-coordination-layer) — _architecture_
|
||||
- [D-78: Claude permission/prompt transport is the stdio control channel](decisions/architecture.md#d-78-claude-permissionprompt-transport-is-the-stdio-control-channel) — _architecture_
|
||||
- [D-79: Workspace content search is a pure-Dart in-process engine, outside pql](decisions/architecture.md#d-79-workspace-content-search-is-a-pure-dart-in-process-engine-outside-pql) — _architecture_
|
||||
|
||||
## Open questions
|
||||
|
||||
|
||||
@@ -344,3 +344,16 @@ Core, rendering, IPC, kernel, panel manager.
|
||||
- **Raised by:** 2026-05-25 — during T-165/T-166 work, an empirical spike against claude 2.1.150 (driving the real CLI + reading the shipped binary's zod schemas) nailed the control-protocol shapes. User weighed stdio vs MCP for permissions, chose stdio for directness, and asked that the brittleness and researched alternatives be documented so a future Anthropic change doesn't leave clide without options.
|
||||
|
||||
---
|
||||
|
||||
### D-79: Workspace content search is a pure-Dart in-process engine, outside pql
|
||||
- **Date:** 2026-05-31
|
||||
- **Status:** accepted
|
||||
- **Decision:** Find-in-files / search-and-replace (T-52/T-53) run as a **pure-Dart, in-process grep engine** — an isolate worker pool fans non-ignored files out across cores, matches with `RegExp` (with a literal `indexOf` fast-path when the regex toggle is off), and **streams** matches back over a single engine-agnostic IPC verb (`search.grep`) with cancellation when the query changes. It does **not** shell out to `ripgrep`/`grep`, and it does **not** route through pql.
|
||||
- **Rationale / D-3 boundary:** D-3 says clide *wraps pql for query surfaces* — but `pql search` is a **ranked full-text *document* index** (returns `path`/`score`/`connections`, no line numbers, snippets, regex, case, or glob). Content-grep ("match-in-context, click-to-line, regex/case/glob") is a **code-navigation primitive pql does not offer**, so it is explicitly outside pql's query surface and is clide's to own (consistent with the "own the rendering/tooling stack" guardrail). A shell-out to `ripgrep` was rejected as the default: it adds an unvendored external binary that isn't guaranteed present (esp. cross-platform), against prefer-zero-deps and single-process.
|
||||
- **Performance:** the I/O floor (walk + read) is shared by every engine. ripgrep's edge is multithreading + SIMD literal prefilters + a non-backtracking DFA; the Dart engine recovers the dominant win (parallelism) via an **isolate pool**, sidesteps the regex-engine gap for the common case via the **literal fast-path**, and hides the rest behind **streaming + cancellation**. Net: interactive (sub-second) on realistic repos including this one; ripgrep only pulls visibly ahead at monorepo scale clide is not targeting.
|
||||
- **Escape hatch (de-risk):** the `search.grep` request/result contract is **engine-agnostic**. If a giant-repo benchmark ever demands it, an optional "use `rg` when on PATH, else the built-in engine" accelerator can slot in behind the same verb with **no caller changes** — recorded as future work on T-52, not built now.
|
||||
- **Distinct from structural search:** this is *text* grep. *Structural/semantic* search ("find usages", "go to definition") is tree-sitter's job (clide already vendors `libtree-sitter.so` for highlighting) and is a separate future feature, not part of T-52.
|
||||
- **Cross-reference:** clarifies [D-3](#) (pql wrap boundary) and the "own the stack" guardrail; relates to [D-4](#d-4-ignore-file-strategy) (the engine honors the full `ignore_files:` layering — see T-52, which closes the never-filed ignore-layering placeholder, a `(to be recorded)` comment in `files_commands.dart`). Implemented by T-52 (engine + find-in-files) and T-53 (replace).
|
||||
- **Raised by:** 2026-05-31 — during /whats-next refinement of the search/nav batch (T-51/T-52/T-53). Refinement surfaced that `pql search` structurally can't satisfy find-in-files; the user probed tree-sitter (ruled out — it's a parser, not a grepper) and the performance ceiling, then chose the fastest *reasonable* Dart option (isolate pool + literal fast-path + streaming) over a ripgrep dependency.
|
||||
|
||||
---
|
||||
|
||||
Reference in New Issue
Block a user