From e2b8f40203e610a3c915dbe9bd884d5689e24a94 Mon Sep 17 00:00:00 2001 From: rajkumarsakthivel Date: Sat, 20 Jun 2026 20:16:37 +0100 Subject: [PATCH 1/3] fix: shields.io badge URL encoding and add --badge tests --- src/context_engine/cli.py | 7 ++--- tests/test_cli_savings.py | 55 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 59 insertions(+), 3 deletions(-) diff --git a/src/context_engine/cli.py b/src/context_engine/cli.py index 8073093..93a0a6e 100644 --- a/src/context_engine/cli.py +++ b/src/context_engine/cli.py @@ -1525,12 +1525,13 @@ def _fmt_cost(c: float) -> str: return # Shields.io badge URL: /badge/LABEL-MESSAGE-COLOR - # In the message: spaces → %20, % → %25, $ stays as-is in URL encoding + # shields.io path-segment rules: - → --, _ → __, spaces → %20, $ is safe badge_color = "brightgreen" if pct >= 80 else "green" if pct >= 50 else "yellowgreen" cost_str = _fmt_cost(cost_saved) badge_msg = f"{cost_str} saved | {pct}% tokens saved" - # shields.io requires: dashes as --, underscores as __, spaces as _ or %20 - badge_msg_enc = quote(badge_msg, safe="") + # Escape shields.io special chars before URL encoding + badge_msg_shields = badge_msg.replace("-", "--").replace("_", "__") + badge_msg_enc = quote(badge_msg_shields, safe="$") badge_url = ( f"https://img.shields.io/badge/" f"CCE-{badge_msg_enc}-{badge_color}" diff --git a/tests/test_cli_savings.py b/tests/test_cli_savings.py index 06bf28d..1c8c3fe 100644 --- a/tests/test_cli_savings.py +++ b/tests/test_cli_savings.py @@ -162,3 +162,58 @@ def test_savings_all_projects_empty(runner, tmp_path): result = runner.invoke(main, ["savings", "--all"]) assert result.exit_code == 0 assert "No usage recorded" in result.output + + +def test_savings_badge_with_data(runner, stats_dir): + """--badge outputs Markdown badge and shields.io URL.""" + storage_path, project_name = stats_dir + result = _invoke_savings(runner, storage_path, project_name, "--badge") + assert result.exit_code == 0 + assert "shields.io" in result.output + assert "![" in result.output # Markdown badge syntax + assert "CCE" in result.output + + +def test_savings_badge_no_data(runner, tmp_path): + """--badge with no data prints a helpful message instead of a badge.""" + config = Config(storage_path=str(tmp_path)) + from unittest.mock import patch + with runner.isolated_filesystem(): + cwd = Path.cwd() / "empty-project" + cwd.mkdir() + with patch("context_engine.cli.load_config", return_value=config), \ + patch("context_engine.cli.Path.cwd", return_value=cwd): + result = runner.invoke(main, ["savings", "--badge"]) + assert result.exit_code == 0 + assert "No savings data" in result.output + + +def test_savings_shortcut_badge(runner, stats_dir): + """cce-savings --badge shortcut also outputs a shields.io badge.""" + from context_engine.cli import savings_shortcut + from unittest.mock import patch + + storage_path, project_name = stats_dir + config = Config(storage_path=str(storage_path)) + + import click + with runner.isolated_filesystem(): + cwd_path = Path.cwd() / project_name + cwd_path.mkdir(parents=True, exist_ok=True) + + @click.command() + @click.option("--json", "as_json", is_flag=True) + @click.option("--all", "all_projects", is_flag=True) + @click.option("--badge", "show_badge", is_flag=True) + def _cmd(as_json, all_projects, show_badge): + from context_engine.cli import _print_savings_badge, _run_savings_report + if show_badge: + _print_savings_badge(config) + return + _run_savings_report(config, as_json=as_json, all_projects=all_projects) + + with patch("context_engine.cli.Path.cwd", return_value=cwd_path): + result = runner.invoke(_cmd, ["--badge"]) + + assert result.exit_code == 0 + assert "shields.io" in result.output From 6d5792afdc9a23d8ab0c6c365cb67893822ee4d7 Mon Sep 17 00:00:00 2001 From: rajkumarsakthivel Date: Sat, 20 Jun 2026 20:17:03 +0100 Subject: [PATCH 2/3] chore: bump version to 0.4.24 --- pyproject.toml | 2 +- server.json | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index ae8971f..f83e099 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "code-context-engine" -version = "0.4.23" +version = "0.4.24" description = "Save 94% on AI coding tokens. Index your codebase, agents search instead of reading files. Works with Claude Code, Cursor, Codex, Copilot, Gemini CLI. Local MCP server, free, open source." readme = {file = "README.md", content-type = "text/markdown"} license = "MIT" diff --git a/server.json b/server.json index e157b69..d073efa 100644 --- a/server.json +++ b/server.json @@ -7,13 +7,13 @@ "url": "https://github.com/elara-labs/code-context-engine", "source": "github" }, - "version": "0.4.23", + "version": "0.4.24", "packages": [ { "registryType": "pypi", "registryBaseUrl": "https://pypi.org", "identifier": "code-context-engine", - "version": "0.4.23", + "version": "0.4.24", "runtimeHint": "uvx", "transport": { "type": "stdio" From 20d5b58802fe000412461e03ee9877a6b026684a Mon Sep 17 00:00:00 2001 From: rajkumarsakthivel Date: Sat, 20 Jun 2026 20:23:34 +0100 Subject: [PATCH 3/3] fix: remove unused import in badge test --- tests/test_cli_savings.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/test_cli_savings.py b/tests/test_cli_savings.py index 1c8c3fe..7c3d48c 100644 --- a/tests/test_cli_savings.py +++ b/tests/test_cli_savings.py @@ -190,7 +190,6 @@ def test_savings_badge_no_data(runner, tmp_path): def test_savings_shortcut_badge(runner, stats_dir): """cce-savings --badge shortcut also outputs a shields.io badge.""" - from context_engine.cli import savings_shortcut from unittest.mock import patch storage_path, project_name = stats_dir