Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion apps/landing-page/__tests__/setup.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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', () => {
Expand Down
4 changes: 2 additions & 2 deletions apps/landing-page/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand All @@ -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",
Expand Down
84 changes: 42 additions & 42 deletions packages/claude-code-plugin/hooks/codingbuddy-hud.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,48 @@
import sys
from datetime import datetime, timezone

BUDDY_FACE = "\u25d5\u203f\u25d5" # ◕‿◕
# --- lib import bootstrap ---
# statusLine entry script: sys.path insertion here is intentional so
# lib/* imports work when Claude Code invokes `python codingbuddy-hud.py`.
_LIB_DIR = os.path.join(os.path.dirname(os.path.abspath(__file__)), "lib")
if _LIB_DIR not in sys.path:
sys.path.insert(0, _LIB_DIR)

# === test_hud.py compatibility re-exports — DO NOT REMOVE without coordinated test update ===
# 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 ImportError: # pragma: no cover - defensive
BUDDY_FACE = "◕‿◕" # minimal constant for safe-output path

try:
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 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 = {
Expand Down Expand Up @@ -303,25 +344,6 @@ def resolve_model_label(stdin_data: dict) -> tuple:
return (model_id, display_name)


def format_rate_limits(stdin_data: dict) -> str:
"""Format rate-limit info if present. Returns '' when absent."""
rl = stdin_data.get("rate_limits")
if not rl:
return ""
parts = []
five = rl.get("five_hour")
if five:
pct = five.get("used_percentage", 0)
parts.append(f"5h:{pct:.0f}%")
seven = rl.get("seven_day")
if seven:
pct = seven.get("used_percentage", 0)
parts.append(f"7d:{pct:.0f}%")
if not parts:
return ""
return "RL:" + ",".join(parts)


def format_worktree(stdin_data: dict) -> str:
"""Format worktree name if present. Returns '' when absent."""
wt = stdin_data.get("worktree")
Expand Down Expand Up @@ -395,28 +417,6 @@ def format_badge_line(agent: str, focus: str, blocker_count) -> str:
return " ".join(badges)


def _get_fresh_version(hud_state: dict, *, plugins_file: str = "") -> str:
"""Return the most current plugin version.

Prefers installed_plugins.json (authoritative after updates)
over the hud-state snapshot written at session start.
Pass *plugins_file* explicitly for testing.
"""
try:
lib_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), "lib")
if lib_dir not in sys.path:
sys.path.insert(0, lib_dir)
from hud_helpers import read_installed_version

kwargs = {"plugins_file": plugins_file} if plugins_file else {}
fresh = read_installed_version(**kwargs)
if fresh:
return fresh
except Exception:
pass
return hud_state.get("version", "")


def format_status_line(
stdin_data: dict,
hud_state: dict,
Expand Down
17 changes: 17 additions & 0 deletions packages/claude-code-plugin/hooks/lib/hud_buddy.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
"""Buddy face re-export for CodingBuddy statusLine (#1326).

``BUDDY_FACE`` is canonically defined in
``tiny_actor_presets.BUDDY_FACE`` and already covered by
``tests/test_tiny_actor_presets.py`` for value/type assertions. This
module re-exports it so statusLine helpers that conceptually belong to
the HUD layer can depend on a ``hud_*`` module instead of reaching into
``tiny_actor_presets``.

Wave 0 establishes the re-export only. Wave 2-A will extend this file
with breathing Buddy face state logic (e.g., ``get_buddy_face(phase)``).
"""
from __future__ import annotations

from tiny_actor_presets import BUDDY_FACE # canonical SSoT

__all__ = ["BUDDY_FACE"]
14 changes: 14 additions & 0 deletions packages/claude-code-plugin/hooks/lib/hud_cache_savings.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
"""Cache-savings badge for CodingBuddy statusLine (#1326).

Wave 0 skeleton — reserved for **Wave 2-C**.

Planned contents (Wave 2-C owner fills):
* ``compute_cache_savings(cost_breakdown: dict) -> float`` — USD
avoided by cache hits
* ``format_cache_savings_badge(savings_usd: float) -> str``

Source of truth for the computation is the stdin ``cost`` payload
(cached-input vs non-cached-input token counts combined with
``MODEL_PRICING`` — both already available in ``codingbuddy-hud``).
This module will be the single import target for Wave 3 assembly.
"""
15 changes: 15 additions & 0 deletions packages/claude-code-plugin/hooks/lib/hud_context_bar.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
"""Smart context bar visualization for CodingBuddy statusLine (#1326).

Wave 0 skeleton — reserved for **Wave 2-E**.

Planned contents (Wave 2-E owner fills):
* ``CONTEXT_BAR_WIDTH: int`` — segment count
* ``CONTEXT_BAR_THRESHOLDS: tuple[float, float, float]`` — warning
/ danger / critical cut-offs
* ``render_context_bar(used_tokens: int, total_tokens: int) -> str``

Wave 2-E will render the bar from the ``context`` payload already
parsed in ``codingbuddy-hud``. This file is a reserved import target
so Wave 3 integration can depend on ``hud_context_bar`` without
creating the module mid-merge.
"""
16 changes: 16 additions & 0 deletions packages/claude-code-plugin/hooks/lib/hud_layout.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
"""Adaptive layout engine for CodingBuddy statusLine (#1326).

Wave 0 skeleton — reserved for **Wave 1-D**.

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``

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.
"""
13 changes: 13 additions & 0 deletions packages/claude-code-plugin/hooks/lib/hud_rainbow.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
"""Mode rainbow ANSI colouring for CodingBuddy statusLine (#1326).

Wave 0 skeleton — reserved for **Wave 2-D**.

Planned contents (Wave 2-D owner fills):
* ``MODE_PALETTE: dict[str, tuple[int, int, int]]`` — per-mode RGB
gradient anchors (PLAN/ACT/EVAL/AUTO)
* ``gradient_ansi(text: str, palette: tuple) -> str``
* ``render_mode_rainbow(mode: str, text: str) -> str``

Wave 2-D will wire the rainbow into ``format_status_line`` (or its
``hud_layout`` successor) in place of the plain text mode label.
"""
31 changes: 31 additions & 0 deletions packages/claude-code-plugin/hooks/lib/hud_rate_limits.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
"""Rate-limit formatting for CodingBuddy statusLine (#1326).

Extracted verbatim from codingbuddy-hud.py as part of the Wave 0 refactor.
Behavior-preserving — see tests/test_hud.py for the contract.
"""
from __future__ import annotations

from typing import Any, Dict


def format_rate_limits(stdin_data: Dict[str, Any]) -> str:
"""Format Claude Code rate-limit badge.

Returns an empty string when no rate-limit data is supplied so the
badge can be dropped from the status line silently.
"""
rl = stdin_data.get("rate_limits")
if not rl:
return ""
parts = []
five = rl.get("five_hour")
if five:
pct = five.get("used_percentage", 0)
parts.append(f"5h:{pct:.0f}%")
seven = rl.get("seven_day")
if seven:
pct = seven.get("used_percentage", 0)
parts.append(f"7d:{pct:.0f}%")
if not parts:
return ""
return "RL:" + ",".join(parts)
16 changes: 16 additions & 0 deletions packages/claude-code-plugin/hooks/lib/hud_session.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
"""Session self-heal and stale state detection (#1326).

Wave 0 skeleton — reserved for **Wave 1-B**.

Planned contents (Wave 1-B owner fills):
* ``detect_stale_session(state: dict, *, now: datetime | None = None) -> bool``
* ``reset_stale_session(state_file: str) -> None``
* ``SESSION_STALE_SECONDS`` constant

The current monolith embeds no session self-heal logic; Wave 1-B will
introduce both the helpers and their call site in
``codingbuddy-hud.format_status_line`` (or its Wave 1-D successor in
``hud_layout``). This module exists as a placeholder so Wave 1-B can
commit to its own sub-branch without racing other Wave workers to
create the file.
"""
15 changes: 15 additions & 0 deletions packages/claude-code-plugin/hooks/lib/hud_velocity.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
"""Cost velocity indicator for CodingBuddy statusLine (#1326).

Wave 0 skeleton — reserved for **Wave 2-B**.

Planned contents (Wave 2-B owner fills):
* ``record_cost_sample(state_file: str, cost_usd: float, *, now=None) -> None``
* ``compute_velocity(history: list[dict]) -> float`` — $/hour
* ``format_velocity_badge(velocity_usd_per_hour: float) -> str``
* ``MAX_COST_HISTORY_ENTRIES`` constant

Wave 2-B will ALSO extend ``lib/hud_state.py`` with a
``"costHistory": []`` entry in both ``_EXTENDED_DEFAULTS`` and
``init_hud_state()`` (this is deliberately NOT done in Wave 0 — schema
design belongs with the feature owner).
"""
54 changes: 54 additions & 0 deletions packages/claude-code-plugin/hooks/lib/hud_version.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
"""Version resolution for CodingBuddy statusLine (#1326).

Wave 0 extracts the plugin-version fallback logic from
``codingbuddy-hud.py`` so Wave 1-A can extend the resolution chain
without touching the monolith.

The public entry point is :func:`get_fresh_version`. ``codingbuddy-hud``
calls it internally from ``format_status_line``; callers pass the
current ``hud_state`` dict and an optional ``plugins_file`` override
used by the test-suite to point at a fixture path.

Behavior-preserving contract (mirrors the original monolith helper):

1. Attempt to read the freshest version from
``installed_plugins.json`` via
:func:`hud_helpers.read_installed_version`.
2. On success, return that value.
3. On any failure (missing file, parse error, unexpected exception),
fall back to ``hud_state.get("version", "")``.
"""
from __future__ import annotations

from typing import Any, Dict


def get_fresh_version(
hud_state: Dict[str, Any],
*,
plugins_file: str = "",
) -> str:
"""Return the freshest known plugin version string.

Args:
hud_state: Current HUD state dict (supplies the fallback
``version`` field).
plugins_file: Optional override for the
``installed_plugins.json`` path, used by tests.

Notes:
``hud_helpers`` is imported lazily inside the function body to
preserve the hot-path resilience of the original monolith. If
``hud_helpers`` is temporarily broken (e.g. mid-wave refactor),
the statusLine still renders via the ``hud_state`` fallback
instead of crashing at module load.
"""
try:
from hud_helpers import read_installed_version # lazy for resilience
kwargs = {"plugins_file": plugins_file} if plugins_file else {}
fresh = read_installed_version(**kwargs)
if fresh:
return fresh
except Exception:
pass
return hud_state.get("version", "")
31 changes: 31 additions & 0 deletions packages/claude-code-plugin/tests/test_hud_buddy.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
"""Sanity test for hud_buddy re-export (Wave 0 / #1463)."""
import importlib
import os
import sys

_tests_dir = os.path.dirname(os.path.abspath(__file__))
_hooks_dir = os.path.join(os.path.dirname(_tests_dir), "hooks")
_lib_dir = os.path.join(_hooks_dir, "lib")
for _p in (_hooks_dir, _lib_dir):
if _p not in sys.path:
sys.path.insert(0, _p)

import hud_buddy # noqa: E402
from tiny_actor_presets import BUDDY_FACE as CANONICAL_BUDDY_FACE # noqa: E402


def test_reexport_is_canonical_ssot():
"""hud_buddy.BUDDY_FACE must be the canonical tiny_actor_presets.BUDDY_FACE."""
assert hud_buddy.BUDDY_FACE is CANONICAL_BUDDY_FACE


def test_value_matches_glyph():
"""Sanity: the face is the three-char smiley."""
assert hud_buddy.BUDDY_FACE == "\u25d5\u203f\u25d5"


def test_reexport_identity_from_codingbuddy_hud():
"""Lock: hud.BUDDY_FACE is the same object (re-export chain intact)."""
hud_main = importlib.import_module("codingbuddy-hud")
assert hud_main.BUDDY_FACE is hud_buddy.BUDDY_FACE
assert hud_main.BUDDY_FACE is CANONICAL_BUDDY_FACE
15 changes: 15 additions & 0 deletions packages/claude-code-plugin/tests/test_hud_cache_savings.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
"""Skeleton sanity for hud_cache_savings — Wave 2-C placeholder (#1463)."""
import os
import sys

_tests_dir = os.path.dirname(os.path.abspath(__file__))
_hooks_dir = os.path.join(os.path.dirname(_tests_dir), "hooks")
_lib_dir = os.path.join(_hooks_dir, "lib")
for _p in (_hooks_dir, _lib_dir):
if _p not in sys.path:
sys.path.insert(0, _p)


def test_module_loads():
"""Contract: hud_cache_savings must be importable. Wave 2-C will add real assertions."""
import hud_cache_savings # noqa: F401
15 changes: 15 additions & 0 deletions packages/claude-code-plugin/tests/test_hud_context_bar.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
"""Skeleton sanity for hud_context_bar — Wave 2-E placeholder (#1463)."""
import os
import sys

_tests_dir = os.path.dirname(os.path.abspath(__file__))
_hooks_dir = os.path.join(os.path.dirname(_tests_dir), "hooks")
_lib_dir = os.path.join(_hooks_dir, "lib")
for _p in (_hooks_dir, _lib_dir):
if _p not in sys.path:
sys.path.insert(0, _p)


def test_module_loads():
"""Contract: hud_context_bar must be importable. Wave 2-E will add real assertions."""
import hud_context_bar # noqa: F401
Loading
Loading