From 66555ff5fd6ec4a6e3f4bf6a1454c033a03bc9e8 Mon Sep 17 00:00:00 2001 From: JeremyDev87 Date: Sat, 11 Apr 2026 20:47:11 +0900 Subject: [PATCH 1/2] feat(hud): adaptive layout engine (Wave 1-D) Introduces width-aware segment rendering primitives so the status bar never spills out of narrow terminals. Wave 1-D ships the layout helpers; Wave 3 integrator wires them into format_status_line. New lib/hud_layout.py: - SEGMENT_PRIORITY canonical drop-order list (9 slots) - SACRED_PRIORITY=1 threshold (face_version, mode_health never dropped) - visible_len(s): East-Asian-aware column count - terminal_width(*, fallback): shutil-backed detection with fallback - shorten_model_label: "Opus 4.6 (1M context)" -> "Opus 4.6" / "Opus(1M)" - fit_segments: drop-until-fit with hard-truncate fallback (U+2026) 33 new tests in test_hud_layout.py cover all helpers, edge cases (empty, zero-width, sacred overflow, custom separator, CJK/emoji width), and SEGMENT_PRIORITY ordering invariants. 188/188 tests pass (Golden 133 + Wave 0 22 + Wave 1-D 33). Part of #1464 (Wave 0 statusbar refactor) --- .../hooks/lib/hud_layout.py | 237 ++++++++++++++- .../tests/test_hud_layout.py | 285 +++++++++++++++++- 2 files changed, 506 insertions(+), 16 deletions(-) diff --git a/packages/claude-code-plugin/hooks/lib/hud_layout.py b/packages/claude-code-plugin/hooks/lib/hud_layout.py index d3135f66..39bd2440 100644 --- a/packages/claude-code-plugin/hooks/lib/hud_layout.py +++ b/packages/claude-code-plugin/hooks/lib/hud_layout.py @@ -1,16 +1,229 @@ -"""Adaptive layout engine for CodingBuddy statusLine (#1326). +"""Adaptive layout engine for CodingBuddy statusLine (#1326, Wave 1-D). -Wave 0 skeleton — reserved for **Wave 1-D**. +Provides width-aware segment rendering so the status bar never spills +out of the terminal. The core primitives are: -Planned contents (Wave 1-D owner fills): - * ``SEGMENT_PRIORITY: list[tuple[str, int]]`` — drop order when - width-constrained - * ``_visible_len(s: str) -> int`` — ANSI-aware length - * ``_shorten_model_label(name: str, *, compact: bool = False) -> str`` - * ``_fit_segments(segments: list[str], width: int, *, separator: str) -> str`` +- :data:`SEGMENT_PRIORITY` — canonical drop order for Wave 3 integrator +- :data:`SACRED_PRIORITY` — threshold below which segments are never dropped +- :func:`visible_len` — width-aware character count (emoji/CJK = 2 cols) +- :func:`terminal_width` — shutil-backed width detection with fallback +- :func:`shorten_model_label` — compact Claude model-name helper +- :func:`fit_segments` — priority-based assembly with overflow truncation -Wave 1-D will also migrate the segment-assembly logic currently inline -in ``codingbuddy-hud.format_status_line`` to these helpers. Until then, -this file is a reserved import target so Wave workers downstream -(Wave 2-E, Wave 3) can reference ``hud_layout`` without creating it. +Wave 1-D only ships the layout helpers. Wave 3 integrator wires them +into ``format_status_line``; until then the monolith continues to +build its status line inline and this module is consumed only by +the new tests. """ +from __future__ import annotations + +import re +import shutil +import unicodedata +from typing import List, Tuple + +# ------------------------------------------------------------------------ +# Constants +# ------------------------------------------------------------------------ + +#: Canonical drop order for statusLine segments. Priority is ascending; +#: higher numbers are dropped first when width is tight. Entries at or +#: below :data:`SACRED_PRIORITY` are never dropped. +SEGMENT_PRIORITY: List[Tuple[str, int]] = [ + ("face_version", 0), # sacred: "◕‿◕ CB v5.5.0" + ("mode_health", 1), # sacred: "PLAN 🟢" + ("cost", 2), + ("duration", 3), + ("ctx", 4), + ("cache", 5), + ("model", 6), + ("rate_limits", 7), + ("worktree", 8), +] + +#: Priorities ``<= SACRED_PRIORITY`` are never dropped by :func:`fit_segments`. +SACRED_PRIORITY: int = 1 + +#: Default separator used between segments. +DEFAULT_SEPARATOR: str = " | " + +#: Fallback terminal width when ``shutil.get_terminal_size`` cannot +#: report a real value (tests, pipes, detached TTYs). +FALLBACK_TERMINAL_WIDTH: int = 120 + +#: Single-character ellipsis glyph used for hard truncation. +_ELLIPSIS: str = "\u2026" # … + + +# ------------------------------------------------------------------------ +# Width helpers +# ------------------------------------------------------------------------ + + +def visible_len(s: str) -> int: + """Approximate the visible column count of ``s``. + + Emoji, CJK, and other full-width characters count as 2 columns; + everything else counts as 1. This mirrors how most monospaced + terminals render the corresponding glyphs. + + Note: ANSI escape sequences are NOT stripped. When Wave 2-D adds + ANSI coloring, callers that mix coloring with layout must strip + escapes before passing to this function. + """ + width = 0 + for ch in s: + if unicodedata.east_asian_width(ch) in ("W", "F"): + width += 2 + else: + width += 1 + return width + + +def terminal_width(*, fallback: int = FALLBACK_TERMINAL_WIDTH) -> int: + """Return the current terminal width with a safe fallback. + + Uses ``shutil.get_terminal_size`` and degrades to *fallback* + whenever the call raises or reports a non-positive column count. + """ + try: + size = shutil.get_terminal_size((fallback, 20)) + return size.columns if size.columns > 0 else fallback + except Exception: + return fallback + + +# ------------------------------------------------------------------------ +# Model label helper +# ------------------------------------------------------------------------ + +_CONTEXT_SUFFIX_RE = re.compile(r"\s*\([^)]*context\)\s*$", re.IGNORECASE) +_COMPACT_PATTERN_RE = re.compile(r"(\w+).*?(\d+[KMG])", re.IGNORECASE) + + +def shorten_model_label(name: str, *, compact: bool = False) -> str: + """Produce a compact version of a Claude model display name. + + Normal mode (compact=False, default) just strips the trailing + ``" (1M context)"`` marker so a long display name like + ``"Opus 4.6 (1M context)"`` becomes ``"Opus 4.6"``. + + Compact mode (compact=True) extracts the model family and the + context size into a tight ``Family(NM)`` pattern so the string + fits in very narrow terminals. If no context marker is present, + only the first whitespace-separated token is returned. + + Examples: + + >>> shorten_model_label("Opus 4.6 (1M context)") + 'Opus 4.6' + >>> shorten_model_label("Opus 4.6 (1M context)", compact=True) + 'Opus(1M)' + >>> shorten_model_label("Sonnet 4.5") + 'Sonnet 4.5' + >>> shorten_model_label("Sonnet 4.5", compact=True) + 'Sonnet' + >>> shorten_model_label("") + '' + """ + if not name: + return "" + + if not compact: + return _CONTEXT_SUFFIX_RE.sub("", name).strip() + + match = _COMPACT_PATTERN_RE.match(name) + if match: + return f"{match.group(1)}({match.group(2)})" + + parts = name.split() + return parts[0] if parts else name + + +# ------------------------------------------------------------------------ +# Fit segments +# ------------------------------------------------------------------------ + + +def fit_segments( + segments: List[Tuple[str, int, str]], + width: int, + *, + separator: str = DEFAULT_SEPARATOR, +) -> str: + """Render segments with priority-based drop-until-fit semantics. + + Args: + segments: List of ``(name, priority, text)`` tuples. ``name`` + is a caller-supplied identifier (ignored during render), + ``priority`` is the drop order (higher = dropped first, + 0/1 are sacred), and ``text`` is the literal text. + width: Maximum visible column count. Rendering tries to fit + within this budget by dropping the lowest-priority + (highest number) segments first. + separator: String inserted between kept segments. Defaults to + :data:`DEFAULT_SEPARATOR` (`` | ``). + + Returns: + The assembled status line. When even the sacred segments + (priority ``<= SACRED_PRIORITY``) exceed the budget, the + result is hard-truncated with a trailing U+2026 (``…``). + + Contract: + * Empty text segments are always skipped. + * Priority ≤ SACRED_PRIORITY segments are NEVER dropped. + * Output preserves the caller-provided segment order. + """ + # Drop empty text up-front so they don't contribute to width or + # produce double separators. + non_empty = [(n, p, t) for n, p, t in segments if t] + + def render(segs: List[Tuple[str, int, str]]) -> str: + return separator.join(t for _, _, t in segs) + + # Try rendering everything first — the common case. + line = render(non_empty) + if visible_len(line) <= width: + return line + + # Drop segments from highest priority number down until fit or + # only sacred segments remain. + kept = list(non_empty) + droppable_priorities = sorted( + {p for _, p, _ in kept if p > SACRED_PRIORITY}, + reverse=True, + ) + for p in droppable_priorities: + kept = [s for s in kept if s[1] != p] + line = render(kept) + if visible_len(line) <= width: + return line + + # Even sacred segments alone don't fit — hard truncate. + line = render(kept) + if visible_len(line) > width: + return _hard_truncate(line, width) + return line + + +def _hard_truncate(s: str, width: int) -> str: + """Truncate ``s`` to ``width`` visible columns with trailing ellipsis. + + Walks characters left-to-right until the visible budget (minus + one column reserved for the ``…`` glyph) is consumed. Returns + just the ellipsis when ``width <= 1``. + """ + if width <= 0: + return "" + if width == 1: + return _ELLIPSIS + budget = width - 1 # reserve 1 column for the ellipsis + result: list = [] + cost = 0 + for ch in s: + ch_width = 2 if unicodedata.east_asian_width(ch) in ("W", "F") else 1 + if cost + ch_width > budget: + break + result.append(ch) + cost += ch_width + return "".join(result) + _ELLIPSIS diff --git a/packages/claude-code-plugin/tests/test_hud_layout.py b/packages/claude-code-plugin/tests/test_hud_layout.py index c66c168b..31da3671 100644 --- a/packages/claude-code-plugin/tests/test_hud_layout.py +++ b/packages/claude-code-plugin/tests/test_hud_layout.py @@ -1,4 +1,4 @@ -"""Skeleton sanity for hud_layout — Wave 1-D placeholder (#1463).""" +"""Behavior tests for hud_layout adaptive rendering (Wave 1-D / #1326).""" import os import sys @@ -9,7 +9,284 @@ if _p not in sys.path: sys.path.insert(0, _p) +import hud_layout # noqa: E402 -def test_module_loads(): - """Contract: hud_layout must be importable. Wave 1-D will add real assertions.""" - import hud_layout # noqa: F401 + +# --------------------------- visible_len ------------------------------------ + + +def test_visible_len_ascii(): + assert hud_layout.visible_len("hello") == 5 + + +def test_visible_len_empty(): + assert hud_layout.visible_len("") == 0 + + +def test_visible_len_cjk_doubled(): + """CJK characters count as 2 columns each.""" + assert hud_layout.visible_len("안녕") == 4 + assert hud_layout.visible_len("日本") == 4 + + +def test_visible_len_emoji_doubled(): + """Wide emoji count as 2 columns.""" + # U+1F7E2 green circle is East-Asian Wide + assert hud_layout.visible_len("A\U0001f7e2B") == 4 + + +def test_visible_len_mixed(): + assert hud_layout.visible_len("ab안c") == 5 # 1 + 1 + 2 + 1 + + +def test_visible_len_ansi_not_stripped(): + """ANSI escape characters are counted (caller must strip).""" + # This documents current behavior — Wave 2-D will revisit. + s = "\x1b[32mX\x1b[0m" + assert hud_layout.visible_len(s) > 1 + + +# --------------------------- terminal_width -------------------------------- + + +def test_terminal_width_falls_back_on_zero(monkeypatch): + """terminal_width returns fallback when shutil reports 0 columns.""" + import shutil as _shutil + + class FakeSize: + columns = 0 + lines = 24 + + monkeypatch.setattr( + _shutil, "get_terminal_size", lambda *a, **k: FakeSize() + ) + assert hud_layout.terminal_width() == hud_layout.FALLBACK_TERMINAL_WIDTH + + +def test_terminal_width_custom_fallback(monkeypatch): + """Fallback parameter is honoured when shutil raises.""" + import shutil as _shutil + + def _raise(*a, **k): + raise RuntimeError("no tty") + + monkeypatch.setattr(_shutil, "get_terminal_size", _raise) + assert hud_layout.terminal_width(fallback=60) == 60 + + +def test_terminal_width_uses_real_value(monkeypatch): + """When shutil reports a real value, terminal_width passes it through.""" + import shutil as _shutil + + class FakeSize: + columns = 100 + lines = 30 + + monkeypatch.setattr( + _shutil, "get_terminal_size", lambda *a, **k: FakeSize() + ) + assert hud_layout.terminal_width() == 100 + + +# --------------------------- shorten_model_label --------------------------- + + +def test_shorten_strips_context_suffix(): + assert ( + hud_layout.shorten_model_label("Opus 4.6 (1M context)") == "Opus 4.6" + ) + + +def test_shorten_keeps_plain_name(): + assert hud_layout.shorten_model_label("Sonnet 4.5") == "Sonnet 4.5" + + +def test_shorten_compact_mode(): + assert ( + hud_layout.shorten_model_label("Opus 4.6 (1M context)", compact=True) + == "Opus(1M)" + ) + + +def test_shorten_compact_fallback_no_context(): + """No context suffix → first token only in compact mode.""" + assert hud_layout.shorten_model_label("Sonnet 4.5", compact=True) == "Sonnet" + + +def test_shorten_compact_single_word(): + assert hud_layout.shorten_model_label("Haiku", compact=True) == "Haiku" + + +def test_shorten_empty_input(): + assert hud_layout.shorten_model_label("") == "" + + +def test_shorten_empty_compact(): + assert hud_layout.shorten_model_label("", compact=True) == "" + + +def test_shorten_case_insensitive_context_suffix(): + """Case differences in 'Context' still stripped.""" + assert ( + hud_layout.shorten_model_label("Opus 4.6 (1M Context)") == "Opus 4.6" + ) + + +# --------------------------- fit_segments ---------------------------------- + + +def test_fit_all_segments_fit_no_drop(): + segments = [ + ("face", 0, "◕‿◕ CB"), + ("mode", 1, "PLAN"), + ("cost", 2, "$1.23"), + ] + result = hud_layout.fit_segments(segments, width=60) + assert "◕‿◕ CB" in result + assert "PLAN" in result + assert "$1.23" in result + assert "|" in result # default separator contains | + + +def test_fit_drops_low_priority_when_tight(): + """Lowest-priority segment dropped first when width is exceeded.""" + segments = [ + ("face", 0, "FACE"), + ("mode", 1, "MODE"), + ("cost", 2, "COST-LONG"), + ] + # 15-col budget fits "FACE | MODE" (11) but not "FACE | MODE | COST-LONG" (23) + result = hud_layout.fit_segments(segments, width=15) + assert "FACE" in result + assert "MODE" in result + assert "COST-LONG" not in result + + +def test_fit_sacred_segments_never_dropped(): + """Priority 0 and 1 segments survive even when their neighbor is huge.""" + segments = [ + ("face", 0, "FACE"), + ("mode", 1, "MODE"), + ("x", 2, "X" * 100), + ] + result = hud_layout.fit_segments(segments, width=12) + assert "FACE" in result + assert "MODE" in result + + +def test_fit_empty_segments_skipped(): + segments = [ + ("face", 0, "FACE"), + ("mode", 1, ""), + ("cost", 2, "$1"), + ] + result = hud_layout.fit_segments(segments, width=40) + assert "FACE" in result + assert "$1" in result + # No double separator where the empty segment used to be + assert "||" not in result + assert " | | " not in result + + +def test_fit_hard_truncate_when_sacred_overflows(): + """When even sacred segments exceed the budget, hard-truncate with ellipsis.""" + segments = [ + ("face", 0, "A" * 50), + ("mode", 1, "B" * 50), + ] + result = hud_layout.fit_segments(segments, width=20) + assert result.endswith("\u2026") + assert hud_layout.visible_len(result) <= 20 + + +def test_fit_preserves_segment_order(): + segments = [ + ("face", 0, "FACE"), + ("mode", 1, "MODE"), + ] + result = hud_layout.fit_segments(segments, width=100) + assert result.index("FACE") < result.index("MODE") + + +def test_fit_drops_highest_priority_number_first(): + """When multiple segments are droppable, priority 8 goes before 3.""" + segments = [ + ("face", 0, "FACE"), + ("mode", 1, "MODE"), + ("duration", 3, "DUR"), + ("worktree", 8, "WT"), + ] + # Tight budget that keeps sacred+duration but not worktree + result = hud_layout.fit_segments(segments, width=20) + assert "FACE" in result + assert "MODE" in result + assert "WT" not in result + + +def test_fit_custom_separator(): + segments = [ + ("a", 0, "A"), + ("b", 1, "B"), + ] + assert hud_layout.fit_segments(segments, width=40, separator=" · ") == "A · B" + + +def test_fit_width_of_zero_yields_empty(): + segments = [("face", 0, "FACE")] + assert hud_layout.fit_segments(segments, width=0) == "" + + +def test_fit_width_of_one_returns_only_ellipsis(): + segments = [("face", 0, "LARGE")] + assert hud_layout.fit_segments(segments, width=1) == "\u2026" + + +def test_fit_ignores_empty_segment_list(): + assert hud_layout.fit_segments([], width=80) == "" + + +def test_fit_single_sacred_within_budget(): + segments = [("face", 0, "◕‿◕")] + assert hud_layout.fit_segments(segments, width=10) == "◕‿◕" + + +# --------------------------- SEGMENT_PRIORITY ------------------------------ + + +def test_segment_priority_has_sacred_entries(): + """SEGMENT_PRIORITY must include face_version and mode_health as sacred.""" + p = dict(hud_layout.SEGMENT_PRIORITY) + assert p["face_version"] == 0 + assert p["mode_health"] == 1 + + +def test_segment_priority_is_non_decreasing(): + """Priorities should be non-decreasing in the canonical list.""" + priorities = [p for _, p in hud_layout.SEGMENT_PRIORITY] + assert priorities == sorted(priorities) + + +def test_segment_priority_contains_all_statusline_slots(): + """All rendering slots documented in format_status_line are listed.""" + names = {name for name, _ in hud_layout.SEGMENT_PRIORITY} + expected = { + "face_version", + "mode_health", + "cost", + "duration", + "ctx", + "cache", + "model", + "rate_limits", + "worktree", + } + assert expected <= names + + +def test_sacred_priority_constant(): + """Sacred threshold is documented as 1.""" + assert hud_layout.SACRED_PRIORITY == 1 + + +def test_default_separator_is_pipe(): + assert hud_layout.DEFAULT_SEPARATOR == " | " From 72ddb34b946e1f78f7ffb1f1ca5531329d6684a8 Mon Sep 17 00:00:00 2001 From: JeremyDev87 Date: Sat, 11 Apr 2026 21:40:59 +0900 Subject: [PATCH 2/2] fix(hud,landing): narrow fallback imports + bump next to 16.2.3 Combined Wave 0 polish items from the #1465/#1485 review cycle: 1. Narrow `except Exception` to `except ImportError` in the 3 lib fallback import blocks (qual-1465 HIGH-1). Real logic bugs (SyntaxError, NameError, AttributeError) inside lib modules now surface immediately instead of being silently swallowed by a catch-all. 2. Drop inline stub functions for format_rate_limits and _get_fresh_version (qual-1465 HIGH-2). Eliminates the signature drift between canonical lib definitions and in-file fallback stubs observed on the integrator branch (Wave 1-A plugin_json_file kwarg drift). The outer main() try/except still catches any runtime failure and emits the minimal safe output via the BUDDY_FACE constant. 3. Hoist hud_velocity and hud_cache_savings imports to module top as _format_velocity_segment and _format_cache_savings (perf-1485 H1). Eliminates ~0.47us sys.modules lookup per render. Integrator branch only - no-op on refactor/wave branches where the inline imports don't exist yet. 4. Bump next to 16.2.3 for GHSA-q4gf-8mx6-v5v3 (landing-security-check). Aligns eslint-config-next and updates setup.test.ts assertion. Refs: qual-1465 HIGH-1/2, perf-1485 H1, https://github.com/advisories/GHSA-q4gf-8mx6-v5v3 --- apps/landing-page/__tests__/setup.test.ts | 2 +- apps/landing-page/package.json | 4 +- .../hooks/codingbuddy-hud.py | 42 +++-- yarn.lock | 170 +++++++++++++++++- 4 files changed, 197 insertions(+), 21 deletions(-) diff --git a/apps/landing-page/__tests__/setup.test.ts b/apps/landing-page/__tests__/setup.test.ts index 00d1c0c6..1b5e64f8 100644 --- a/apps/landing-page/__tests__/setup.test.ts +++ b/apps/landing-page/__tests__/setup.test.ts @@ -8,7 +8,7 @@ describe('Next.js 16 Project Setup', () => { }); test('Next.js 16.x is installed and locked', () => { - expect(pkg.dependencies.next).toBe('16.1.6'); + expect(pkg.dependencies.next).toBe('16.2.3'); }); test('React 19.x is installed and locked', () => { diff --git a/apps/landing-page/package.json b/apps/landing-page/package.json index 89265a00..a5c557ca 100644 --- a/apps/landing-page/package.json +++ b/apps/landing-page/package.json @@ -32,7 +32,7 @@ "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", "lucide-react": "^0.563.0", - "next": "16.1.6", + "next": "16.2.3", "next-intl": "^4.8.2", "next-themes": "^0.4.6", "prism-react-renderer": "^2.4.1", @@ -56,7 +56,7 @@ "axe-core": "^4.11.1", "babel-plugin-react-compiler": "1.0.0", "eslint": "^9", - "eslint-config-next": "16.1.6", + "eslint-config-next": "16.2.3", "happy-dom": "^20.8.8", "jest-axe": "^10.0.0", "madge": "^8.0.0", diff --git a/packages/claude-code-plugin/hooks/codingbuddy-hud.py b/packages/claude-code-plugin/hooks/codingbuddy-hud.py index c99ca493..a3472e4e 100644 --- a/packages/claude-code-plugin/hooks/codingbuddy-hud.py +++ b/packages/claude-code-plugin/hooks/codingbuddy-hud.py @@ -23,28 +23,40 @@ sys.path.insert(0, _LIB_DIR) # === test_hud.py compatibility re-exports — DO NOT REMOVE without coordinated test update === -# Defensive fallback: statusLine is a hot path invoked by Claude Code on -# every render. If any lib module is temporarily broken (e.g. mid-wave -# refactor), fall back to minimal inline implementations so the status -# bar still renders instead of crashing the Claude Code subprocess. +# Narrow the fallback to ImportError only: real logic bugs in lib modules +# (SyntaxError, NameError, AttributeError) must surface immediately instead +# of being silently swallowed by a catch-all. If a lib module fails to import +# entirely, the outer main() try/except at the bottom of this file still +# emits the minimal safe output via the BUDDY_FACE constant. try: from hud_buddy import BUDDY_FACE # canonical SSoT via tiny_actor_presets -except Exception: # pragma: no cover - defensive - BUDDY_FACE = "\u25d5\u203f\u25d5" # ◕‿◕ +except ImportError: # pragma: no cover - defensive + BUDDY_FACE = "◕‿◕" # minimal constant for safe-output path try: - from hud_rate_limits import format_rate_limits -except Exception: # pragma: no cover - defensive - def format_rate_limits(stdin_data: dict) -> str: # type: ignore[misc] - return "" + from hud_rate_limits import format_rate_limits # noqa: F401 re-exported for test_hud.py +except ImportError: # pragma: no cover - defensive + pass # main() catch-all handles absence try: from hud_version import get_fresh_version as _get_fresh_version # backcompat alias -except Exception: # pragma: no cover - defensive - def _get_fresh_version( # type: ignore[misc] - hud_state: dict, *, plugins_file: str = "" - ) -> str: - return hud_state.get("version", "") +except ImportError: # pragma: no cover - defensive + pass # main() catch-all handles absence + +# Wave 2-B velocity + Wave 2-C cache savings hot-path suffixes for the cost segment. +# Hoisted to module top per perf-1485 H1 so format_status_line avoids a +# sys.modules lookup on every render (~0.47μs saved per call). +try: + from hud_velocity import format_velocity_segment as _format_velocity_segment +except ImportError: # pragma: no cover - defensive + def _format_velocity_segment(stdin_data, hud_state=None): # type: ignore[misc] + return "" + +try: + from hud_cache_savings import format_cache_savings as _format_cache_savings +except ImportError: # pragma: no cover - defensive + def _format_cache_savings(stdin_data): # type: ignore[misc] + return "" # Agent eye glyphs from .ai-rules agent definitions. AGENT_GLYPHS = { diff --git a/yarn.lock b/yarn.lock index 1eac05b0..76937473 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1561,6 +1561,13 @@ __metadata: languageName: node linkType: hard +"@next/env@npm:16.2.3": + version: 16.2.3 + resolution: "@next/env@npm:16.2.3" + checksum: 10c0/56c3fee8ea226efe59ef065e054380f872c00c45c9fe4475eaa45f80773c3c1adc3ead3ccdd77447d3c1aeb4b3004aaaa033dd4a100d3e572fd01b83f992dde8 + languageName: node + linkType: hard + "@next/eslint-plugin-next@npm:16.1.6": version: 16.1.6 resolution: "@next/eslint-plugin-next@npm:16.1.6" @@ -1570,6 +1577,15 @@ __metadata: languageName: node linkType: hard +"@next/eslint-plugin-next@npm:16.2.3": + version: 16.2.3 + resolution: "@next/eslint-plugin-next@npm:16.2.3" + dependencies: + fast-glob: "npm:3.3.1" + checksum: 10c0/be881aa89e0840ab60455b07a2bb9ec0d686c664a0d91e8ca815797a65ca71d7bd79d186b0df5b6892c2bf57bd07fa05421cd93e2812dfeaedfad5ed9fd1023e + languageName: node + linkType: hard + "@next/swc-darwin-arm64@npm:16.1.6": version: 16.1.6 resolution: "@next/swc-darwin-arm64@npm:16.1.6" @@ -1577,6 +1593,13 @@ __metadata: languageName: node linkType: hard +"@next/swc-darwin-arm64@npm:16.2.3": + version: 16.2.3 + resolution: "@next/swc-darwin-arm64@npm:16.2.3" + conditions: os=darwin & cpu=arm64 + languageName: node + linkType: hard + "@next/swc-darwin-x64@npm:16.1.6": version: 16.1.6 resolution: "@next/swc-darwin-x64@npm:16.1.6" @@ -1584,6 +1607,13 @@ __metadata: languageName: node linkType: hard +"@next/swc-darwin-x64@npm:16.2.3": + version: 16.2.3 + resolution: "@next/swc-darwin-x64@npm:16.2.3" + conditions: os=darwin & cpu=x64 + languageName: node + linkType: hard + "@next/swc-linux-arm64-gnu@npm:16.1.6": version: 16.1.6 resolution: "@next/swc-linux-arm64-gnu@npm:16.1.6" @@ -1591,6 +1621,13 @@ __metadata: languageName: node linkType: hard +"@next/swc-linux-arm64-gnu@npm:16.2.3": + version: 16.2.3 + resolution: "@next/swc-linux-arm64-gnu@npm:16.2.3" + conditions: os=linux & cpu=arm64 & libc=glibc + languageName: node + linkType: hard + "@next/swc-linux-arm64-musl@npm:16.1.6": version: 16.1.6 resolution: "@next/swc-linux-arm64-musl@npm:16.1.6" @@ -1598,6 +1635,13 @@ __metadata: languageName: node linkType: hard +"@next/swc-linux-arm64-musl@npm:16.2.3": + version: 16.2.3 + resolution: "@next/swc-linux-arm64-musl@npm:16.2.3" + conditions: os=linux & cpu=arm64 & libc=musl + languageName: node + linkType: hard + "@next/swc-linux-x64-gnu@npm:16.1.6": version: 16.1.6 resolution: "@next/swc-linux-x64-gnu@npm:16.1.6" @@ -1605,6 +1649,13 @@ __metadata: languageName: node linkType: hard +"@next/swc-linux-x64-gnu@npm:16.2.3": + version: 16.2.3 + resolution: "@next/swc-linux-x64-gnu@npm:16.2.3" + conditions: os=linux & cpu=x64 & libc=glibc + languageName: node + linkType: hard + "@next/swc-linux-x64-musl@npm:16.1.6": version: 16.1.6 resolution: "@next/swc-linux-x64-musl@npm:16.1.6" @@ -1612,6 +1663,13 @@ __metadata: languageName: node linkType: hard +"@next/swc-linux-x64-musl@npm:16.2.3": + version: 16.2.3 + resolution: "@next/swc-linux-x64-musl@npm:16.2.3" + conditions: os=linux & cpu=x64 & libc=musl + languageName: node + linkType: hard + "@next/swc-win32-arm64-msvc@npm:16.1.6": version: 16.1.6 resolution: "@next/swc-win32-arm64-msvc@npm:16.1.6" @@ -1619,6 +1677,13 @@ __metadata: languageName: node linkType: hard +"@next/swc-win32-arm64-msvc@npm:16.2.3": + version: 16.2.3 + resolution: "@next/swc-win32-arm64-msvc@npm:16.2.3" + conditions: os=win32 & cpu=arm64 + languageName: node + linkType: hard + "@next/swc-win32-x64-msvc@npm:16.1.6": version: 16.1.6 resolution: "@next/swc-win32-x64-msvc@npm:16.1.6" @@ -1626,6 +1691,13 @@ __metadata: languageName: node linkType: hard +"@next/swc-win32-x64-msvc@npm:16.2.3": + version: 16.2.3 + resolution: "@next/swc-win32-x64-msvc@npm:16.2.3" + conditions: os=win32 & cpu=x64 + languageName: node + linkType: hard + "@nodelib/fs.scandir@npm:2.1.5": version: 2.1.5 resolution: "@nodelib/fs.scandir@npm:2.1.5" @@ -5276,6 +5348,15 @@ __metadata: languageName: node linkType: hard +"baseline-browser-mapping@npm:^2.9.19": + version: 2.10.17 + resolution: "baseline-browser-mapping@npm:2.10.17" + bin: + baseline-browser-mapping: dist/cli.cjs + checksum: 10c0/e792a92a6b206521681e3ab3a72770023f74a3274450bfe11ba55a075ba26f5820d5d2d02d92e25224b8d01e327b78fbf3e116bdc6ac74b3d9c52f5e3f4a048a + languageName: node + linkType: hard + "better-sqlite3@npm:^11.9.1": version: 11.10.0 resolution: "better-sqlite3@npm:11.10.0" @@ -7028,6 +7109,29 @@ __metadata: languageName: node linkType: hard +"eslint-config-next@npm:16.2.3": + version: 16.2.3 + resolution: "eslint-config-next@npm:16.2.3" + dependencies: + "@next/eslint-plugin-next": "npm:16.2.3" + eslint-import-resolver-node: "npm:^0.3.6" + eslint-import-resolver-typescript: "npm:^3.5.2" + eslint-plugin-import: "npm:^2.32.0" + eslint-plugin-jsx-a11y: "npm:^6.10.0" + eslint-plugin-react: "npm:^7.37.0" + eslint-plugin-react-hooks: "npm:^7.0.0" + globals: "npm:16.4.0" + typescript-eslint: "npm:^8.46.0" + peerDependencies: + eslint: ">=9.0.0" + typescript: ">=3.3.1" + peerDependenciesMeta: + typescript: + optional: true + checksum: 10c0/c6fd3accadb53c636f034baf4363d22847bf824c8ca1ecfa8047a4eee7882d156e75f60f37098357c7ae07e646dfaa23a176336abd3c74aa9a2df61aee984653 + languageName: node + linkType: hard + "eslint-config-prettier@npm:10.1.8": version: 10.1.8 resolution: "eslint-config-prettier@npm:10.1.8" @@ -8942,12 +9046,12 @@ __metadata: class-variance-authority: "npm:^0.7.1" clsx: "npm:^2.1.1" eslint: "npm:^9" - eslint-config-next: "npm:16.1.6" + eslint-config-next: "npm:16.2.3" happy-dom: "npm:^20.8.8" jest-axe: "npm:^10.0.0" lucide-react: "npm:^0.563.0" madge: "npm:^8.0.0" - next: "npm:16.1.6" + next: "npm:16.2.3" next-intl: "npm:^4.8.2" next-themes: "npm:^0.4.6" prettier: "npm:^3.4.2" @@ -9858,6 +9962,66 @@ __metadata: languageName: node linkType: hard +"next@npm:16.2.3": + version: 16.2.3 + resolution: "next@npm:16.2.3" + dependencies: + "@next/env": "npm:16.2.3" + "@next/swc-darwin-arm64": "npm:16.2.3" + "@next/swc-darwin-x64": "npm:16.2.3" + "@next/swc-linux-arm64-gnu": "npm:16.2.3" + "@next/swc-linux-arm64-musl": "npm:16.2.3" + "@next/swc-linux-x64-gnu": "npm:16.2.3" + "@next/swc-linux-x64-musl": "npm:16.2.3" + "@next/swc-win32-arm64-msvc": "npm:16.2.3" + "@next/swc-win32-x64-msvc": "npm:16.2.3" + "@swc/helpers": "npm:0.5.15" + baseline-browser-mapping: "npm:^2.9.19" + caniuse-lite: "npm:^1.0.30001579" + postcss: "npm:8.4.31" + sharp: "npm:^0.34.5" + styled-jsx: "npm:5.1.6" + peerDependencies: + "@opentelemetry/api": ^1.1.0 + "@playwright/test": ^1.51.1 + babel-plugin-react-compiler: "*" + react: ^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0 + react-dom: ^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0 + sass: ^1.3.0 + dependenciesMeta: + "@next/swc-darwin-arm64": + optional: true + "@next/swc-darwin-x64": + optional: true + "@next/swc-linux-arm64-gnu": + optional: true + "@next/swc-linux-arm64-musl": + optional: true + "@next/swc-linux-x64-gnu": + optional: true + "@next/swc-linux-x64-musl": + optional: true + "@next/swc-win32-arm64-msvc": + optional: true + "@next/swc-win32-x64-msvc": + optional: true + sharp: + optional: true + peerDependenciesMeta: + "@opentelemetry/api": + optional: true + "@playwright/test": + optional: true + babel-plugin-react-compiler: + optional: true + sass: + optional: true + bin: + next: dist/bin/next + checksum: 10c0/8a9d27fc773d69f7f471cf1a23bde2ab2950e0411ef3e0d5c1664ed9654e94c3304eae1c4283ec0fa4e70e7b3f4416913350e118e0c18e8b055693dc5d021883 + languageName: node + linkType: hard + "node-abi@npm:^3.3.0": version: 3.89.0 resolution: "node-abi@npm:3.89.0" @@ -11302,7 +11466,7 @@ __metadata: languageName: node linkType: hard -"sharp@npm:^0.34.4": +"sharp@npm:^0.34.4, sharp@npm:^0.34.5": version: 0.34.5 resolution: "sharp@npm:0.34.5" dependencies: