From 3be063d6da0d79004692479081b57a456291b5c6 Mon Sep 17 00:00:00 2001 From: Jeroen Schweitzer Date: Mon, 29 Jun 2026 09:15:05 +0200 Subject: [PATCH] feat(problems): surface stale supporter-binary pins (T-495) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The Problems panel flags a configured tool path (app.tools) that no longer points at a file — a real misconfig, echoing D-58's soft floor. A merely- unfound optional tool isn't flagged (its use-time userError covers that). Extracted to a pure supporterToolProblems(), flutter-tested (3 cases). Co-Authored-By: Claude Opus 4.8 (1M context) --- .pql/changelog/ticket_history/2026-06.sql | 15 ++++++++++++ .pql/changelog/tickets/2026-06.sql | 9 +++++++ .../problems/src/problems_controller.dart | 16 +++++++++++++ .../problems/problems_controller_test.dart | 24 +++++++++++++++++++ 4 files changed, 64 insertions(+) create mode 100644 test/builtin/problems/problems_controller_test.dart diff --git a/.pql/changelog/ticket_history/2026-06.sql b/.pql/changelog/ticket_history/2026-06.sql index f8e16e9d..608f35e2 100644 --- a/.pql/changelog/ticket_history/2026-06.sql +++ b/.pql/changelog/ticket_history/2026-06.sql @@ -8852,3 +8852,18 @@ EXTENSION WIRE DONE + PUSHED (2026-06-28): item (1) complete — DrawingMessage COMPLETE (2026-06-28): per-object overlay done — captions (data-label/data-description) + lightbox (data-lightbox tap-to-zoom), positioned via the shared svgViewportFit so they track the painted SVG. Full T-318 scope built + tested: document envelope (draw_doc), template dispatch (draw_dispatch), DrawingCard widget, the draw CLI command (draw_commands), the extension wire (DrawingMessage + subscriber + renderer + boot registration), and the per-object overlay. ~40 tests across dart + flutter test. All acceptance met (raw SVG draws; a template dispatches; objects render captions; unknown template/primitive → userError). Templates d2/icon/compare/image are separate children under T-317; T-494 (d2) is next, gated on T-495.', NULL, '2026-06-28 20:40:54', '2026-06-28 20:40:54.751', '2026-06-28 20:40:54.751', NULL, '3c1694be81a3d05e22c8c5a2221e99fd', 2) ON CONFLICT(hash) DO NOTHING; INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB2ERREMEEF26KKHGNZBWW64', 'status', 'in_progress', 'done', NULL, '2026-06-28 20:40:54', '2026-06-28 20:40:54.785', '2026-06-28 20:40:54.785', NULL, '389acc78ce89d7225e268e20f3822fee', 2) ON CONFLICT(hash) DO NOTHING; INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FGYPJ6FTEPP4JK3D7D01ZSMM', 'status', 'backlog', 'in_progress', NULL, '2026-06-28 21:00:18', '2026-06-28 21:00:18.364', '2026-06-28 21:00:18.364', NULL, '00fa58dcedc18f6ce827b5a0ef6ad928', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FGYPJ6FTEPP4JK3D7D01ZSMM', 'description', 'Implement D-104. clide resolves external supporter binaries (claude, d2, future supporter tools) via an explicit user-scope ''tools:'' map (name → absolute path), consulted FIRST. + +Resolution order: (1) explicit settings path → use it; honest error + fallback-with-warning if missing/non-exec. (2) bundled/pinned (pql D-58, git D-59). (3) login-shell PATH (T-439). (4) bare PATH. + +First run: auto-detect each tool ONCE — probe the login-shell PATH + well-known dirs (~/.local/bin, /usr/local/bin, /opt/homebrew, /home/linuxbrew/.linuxbrew/bin) — and WRITE the discovered absolute paths into the map (pinned, not re-probed each launch). Provide a re-detect action. + +Storage: user-scope SettingsStore keyed by machine (D-93) — never committed. Surface in the Config tab (T-414) + flag unresolved tools in the Problems panel (echoes D-58''s soft floor). Generalizes D-58''s CLIDE_PQL_BIN override to all supporter tools. First consumer: the d2 template (T-494).', 'Implement D-104. clide resolves external supporter binaries (claude, d2, future supporter tools) via an explicit user-scope ''tools:'' map (name → absolute path), consulted FIRST. + +Resolution order: (1) explicit settings path → use it; honest error + fallback-with-warning if missing/non-exec. (2) bundled/pinned (pql D-58, git D-59). (3) login-shell PATH (T-439). (4) bare PATH. + +First run: auto-detect each tool ONCE — probe the login-shell PATH + well-known dirs (~/.local/bin, /usr/local/bin, /opt/homebrew, /home/linuxbrew/.linuxbrew/bin) — and WRITE the discovered absolute paths into the map (pinned, not re-probed each launch). Provide a re-detect action. + +Storage: user-scope SettingsStore keyed by machine (D-93) — never committed. Surface in the Config tab (T-414) + flag unresolved tools in the Problems panel (echoes D-58''s soft floor). Generalizes D-58''s CLIDE_PQL_BIN override to all supporter tools. First consumer: the d2 template (T-494). + +RESOLUTION CORE DONE (2026-06-28): functional path resolution built + wired + tested (14 dart-test cases). SupporterBinaries resolver (lib/src/env/supporter_binaries.dart): explicit override -> login-shell/process PATH -> well-known dirs INCLUDING Homebrew-on-Linux (the D-104 gap). loadSupporterBinaries: first-run auto-detect + persist (pinned, not re-probed); isStalePin flags a moved tool. Boot wiring in main.dart exposes the process-wide activeSupporterBinaries. This UNBLOCKS T-494 — the d2 template resolves d2 via activeSupporterBinaries.resolve(''d2''). REMAINING (UI follow-ons, not gating T-494): Config-tab override editor (view/edit/clear paths), a re-detect action, and Problems-panel surfacing of unresolved tools.', NULL, '2026-06-28 21:08:40', '2026-06-28 21:08:40.178', '2026-06-28 21:08:40.178', NULL, 'f155f8e589cd19c3cc52409495d24f50', 2) ON CONFLICT(hash) DO NOTHING; diff --git a/.pql/changelog/tickets/2026-06.sql b/.pql/changelog/tickets/2026-06.sql index 1bb1abbd..7e7d6624 100644 --- a/.pql/changelog/tickets/2026-06.sql +++ b/.pql/changelog/tickets/2026-06.sql @@ -10647,3 +10647,12 @@ Resolution order: (1) explicit settings path → use it; honest error + fallback First run: auto-detect each tool ONCE — probe the login-shell PATH + well-known dirs (~/.local/bin, /usr/local/bin, /opt/homebrew, /home/linuxbrew/.linuxbrew/bin) — and WRITE the discovered absolute paths into the map (pinned, not re-probed each launch). Provide a re-detect action. Storage: user-scope SettingsStore keyed by machine (D-93) — never committed. Surface in the Config tab (T-414) + flag unresolved tools in the Problems panel (echoes D-58''s soft floor). Generalizes D-58''s CLIDE_PQL_BIN override to all supporter tools. First consumer: the d2 template (T-494).', 'in_progress', 'medium', NULL, NULL, 'D-104', '2026-06-28 17:50:02.366', '2026-06-28 21:00:18.364', NULL, 'de1818996b3226c6df2516d1aff9cfad', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_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 (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FGYPJ6FTEPP4JK3D7D01ZSMM', 'task', NULL, 'Supporter-binary path resolution: explicit user-scope overrides + first-run auto-detect', 'Implement D-104. clide resolves external supporter binaries (claude, d2, future supporter tools) via an explicit user-scope ''tools:'' map (name → absolute path), consulted FIRST. + +Resolution order: (1) explicit settings path → use it; honest error + fallback-with-warning if missing/non-exec. (2) bundled/pinned (pql D-58, git D-59). (3) login-shell PATH (T-439). (4) bare PATH. + +First run: auto-detect each tool ONCE — probe the login-shell PATH + well-known dirs (~/.local/bin, /usr/local/bin, /opt/homebrew, /home/linuxbrew/.linuxbrew/bin) — and WRITE the discovered absolute paths into the map (pinned, not re-probed each launch). Provide a re-detect action. + +Storage: user-scope SettingsStore keyed by machine (D-93) — never committed. Surface in the Config tab (T-414) + flag unresolved tools in the Problems panel (echoes D-58''s soft floor). Generalizes D-58''s CLIDE_PQL_BIN override to all supporter tools. First consumer: the d2 template (T-494). + +RESOLUTION CORE DONE (2026-06-28): functional path resolution built + wired + tested (14 dart-test cases). SupporterBinaries resolver (lib/src/env/supporter_binaries.dart): explicit override -> login-shell/process PATH -> well-known dirs INCLUDING Homebrew-on-Linux (the D-104 gap). loadSupporterBinaries: first-run auto-detect + persist (pinned, not re-probed); isStalePin flags a moved tool. Boot wiring in main.dart exposes the process-wide activeSupporterBinaries. This UNBLOCKS T-494 — the d2 template resolves d2 via activeSupporterBinaries.resolve(''d2''). REMAINING (UI follow-ons, not gating T-494): Config-tab override editor (view/edit/clear paths), a re-detect action, and Problems-panel surfacing of unresolved tools.', 'in_progress', 'medium', NULL, NULL, 'D-104', '2026-06-28 17:50:02.366', '2026-06-28 21:08:40.177', NULL, '7e1f7077abd788309d38a51f8a3b1348', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_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/lib/builtin/problems/src/problems_controller.dart b/lib/builtin/problems/src/problems_controller.dart index 790b2c4b..ec437850 100644 --- a/lib/builtin/problems/src/problems_controller.dart +++ b/lib/builtin/problems/src/problems_controller.dart @@ -8,6 +8,7 @@ library; import 'dart:async'; import 'package:clide/kernel/kernel.dart'; +import 'package:clide/src/env/supporter_binaries.dart'; import 'package:flutter/foundation.dart'; class Problem { @@ -69,9 +70,24 @@ class ProblemsController extends ChangeNotifier { } } + found.addAll(supporterToolProblems(activeSupporterBinaries)); + _loading = false; _error = null; _problems = found; notifyListeners(); } } + +/// Problems for STALE supporter-binary pins (T-495 / D-104): an explicit tool +/// path that no longer points at a file — a real misconfig (a tool moved on an +/// upgrade). A merely-unfound optional tool isn't flagged; its use-time +/// userError covers that. Pure + testable. +List supporterToolProblems(SupporterBinaries? tools) { + if (tools == null) return const []; + return [ + for (final name in knownSupporterTools) + if (tools.isStalePin(name)) + Problem(source: 'tools', message: 'configured path for "$name" is missing', hint: 'Re-detect, or update its path in settings (app.tools).'), + ]; +} diff --git a/test/builtin/problems/problems_controller_test.dart b/test/builtin/problems/problems_controller_test.dart new file mode 100644 index 00000000..53798288 --- /dev/null +++ b/test/builtin/problems/problems_controller_test.dart @@ -0,0 +1,24 @@ +import 'package:clide/builtin/problems/src/problems_controller.dart'; +import 'package:clide/src/env/supporter_binaries.dart'; +import 'package:flutter_test/flutter_test.dart'; + +void main() { + group('supporterToolProblems', () { + test('flags a stale supporter-binary pin', () { + final tools = SupporterBinaries(overrides: {'d2': '/gone/d2'}, exists: (_) => false); + final ps = supporterToolProblems(tools); + expect(ps.single.source, 'tools'); + expect(ps.single.message, contains('d2')); + expect(ps.single.hint, isNotNull); + }); + + test('no problem when the configured path resolves', () { + expect(supporterToolProblems(SupporterBinaries(overrides: {'d2': '/ok'}, exists: {'/ok'}.contains)), isEmpty); + }); + + test('no problem with no override or no resolver', () { + expect(supporterToolProblems(SupporterBinaries(exists: (_) => false)), isEmpty); + expect(supporterToolProblems(null), isEmpty); + }); + }); +}