{"id":50873627,"url":"https://github.com/hinanohart/cachelens","last_synced_at":"2026-06-15T07:30:57.414Z","repository":{"id":361069874,"uuid":"1252964040","full_name":"hinanohart/cachelens","owner":"hinanohart","description":"Offline prompt-cache precondition checker: first-divergence diff, lint, CI gate for Anthropic/OpenAI/DeepSeek/Gemini","archived":false,"fork":false,"pushed_at":"2026-06-10T13:32:57.000Z","size":163,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-06-10T14:08:35.914Z","etag":null,"topics":["anthropic","cli","llm","openai","prompt-cache","python"],"latest_commit_sha":null,"homepage":"https://github.com/hinanohart/cachelens","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/hinanohart.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-05-29T03:26:23.000Z","updated_at":"2026-06-10T13:34:29.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/hinanohart/cachelens","commit_stats":null,"previous_names":["hinanohart/cachelens"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/hinanohart/cachelens","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hinanohart%2Fcachelens","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hinanohart%2Fcachelens/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hinanohart%2Fcachelens/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hinanohart%2Fcachelens/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/hinanohart","download_url":"https://codeload.github.com/hinanohart/cachelens/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hinanohart%2Fcachelens/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34353189,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-15T02:00:07.085Z","response_time":63,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["anthropic","cli","llm","openai","prompt-cache","python"],"created_at":"2026-06-15T07:30:56.295Z","updated_at":"2026-06-15T07:30:57.409Z","avatar_url":"https://github.com/hinanohart.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"# cachelens\n\n\u003e **Pre-Alpha (v0.1.0a4)** — offline prompt-cache precondition checker.\n\ncachelens is a **fully offline, CPU-only** Python tool that checks whether your LLM prompt prefixes satisfy provider cache requirements — *before* you send requests. It finds the **first point where cache breaks** and tells you **why**, so you can fix it in CI rather than discover it on your invoice.\n\nSupports Anthropic, OpenAI, DeepSeek, and Gemini. Zero runtime dependencies.\n\n---\n\n## What it does\n\n- **`diff`**: Compare two requests and find the first divergence that would break cache continuity — returns `STABLE → BROKEN` state transition + byte offset + cause classification.\n- **`lint`**: Check a single request for cache-breaking anti-patterns (timestamps, UUIDs, non-deterministic tool order, missing thresholds, dynamic system prompts).\n- **`check`**: CI wrapper — exits non-zero when cacheability drops below threshold.\n\nAll output carries a `determinism` label (`LINT` for v0.1.0) so over-claim is type-impossible.\n\n## What it does NOT do\n\n- **Does not** predict actual cache hit/miss rates (depends on provider TTL, load, eviction — internal state).\n- **Does not** produce Anthropic token counts offline (no public local tokenizer; token counts require the `count_tokens` API — deferred to v0.1.1).\n- **Does not** produce dollar cost estimates (deferred to v0.1.1 with `--with-cost` + real-token opt-in).\n- `Determinism.SIM` type value exists but SIM path is not implemented in v0.1.0.\n\n---\n\n## Install\n\n```bash\npip install cachelens                  # zero runtime deps\npip install \"cachelens[tiktoken]\"      # exact token counts (OpenAI/DeepSeek)\npip install \"cachelens[hf]\"            # HuggingFace tokenizers\npip install \"cachelens[all]\"           # both\n```\n\n---\n\n## Quick start\n\n```bash\n# diff two request files (synthetic fixture shown)\ncachelens diff tests/fixtures/stable.json tests/fixtures/broken.json\n\n# lint a single request\ncachelens lint tests/fixtures/stable.json --provider anthropic --explain\n\n# CI check against a trace\ncachelens check tests/fixtures/trace_stable.jsonl --threshold 0.8\necho \"exit: $?\"   # 0=stable, 1=broken, 2=input-error (3=abstain reserved for v0.1.1)\n```\n\n### Exit codes (contract)\n\n| Code | Meaning |\n|---|---|\n| 0 | Cache prefix is stable |\n| 1 | Cache broken (threshold breach) — CI should fail |\n| 2 | Input error (bad file, unknown provider) |\n| 3 | Abstain — reserved for v0.1.1 (not emitted in v0.1.0) |\n\n---\n\n## How it works\n\n\u003cdiv align=\"center\"\u003e\n  \u003cimg src=\"docs/architecture.png\" alt=\"cachelens architecture\" width=\"840\"\u003e\n\u003c/div\u003e\n\nThe core pipeline: requests are **normalized** (JCS-style key sorting strips spurious byte differences), split into **segments** (system → tools → messages in cache-cascade order), then either **diffed** against a second request for first-divergence detection or **linted** for known anti-patterns. All operations are deterministic and require no network calls.\n\n---\n\n## Python API\n\n```python\nfrom cachelens import diff, lint, Request, Trace, Divergence\n\nreq_a = Request.from_file(\"a.json\")\nreq_b = Request.from_file(\"b.json\")\nresult = diff(req_a, req_b, provider=\"anthropic\")\n# result.state: \"STABLE\" | \"BROKEN\"\n# result.first_divergence_offset: int (byte offset)\n# result.cause: Cause enum\n# result.determinism: Determinism.LINT\n```\n\n### Building a Request from SDK kwargs\n\n```python\nfrom cachelens import lint\nfrom cachelens.model import Request\n\n# Convert Anthropic SDK kwargs directly\nreq = Request.from_anthropic_kwargs(\n    model=\"claude-3-5-sonnet-20241022\",\n    system=[{\"type\": \"text\", \"text\": \"You are helpful.\", \"cache_control\": {\"type\": \"ephemeral\"}}],\n    messages=[{\"role\": \"user\", \"content\": \"Hello\"}],\n)\nresult = lint(req, provider=\"anthropic\")\nprint(result.passed)  # True / False\n```\n\n---\n\n## GitHub Action\n\n```yaml\n- uses: hinanohart/cachelens@v0.1.0a4\n  with:\n    trace: \"traces/latest.jsonl\"\n    threshold: \"0.8\"\n    provider: \"anthropic\"\n```\n\n---\n\n## Provider support (v0.1.0)\n\n| Provider | Cache type | Determinism tier | Offline guarantee |\n|---|---|---|---|\n| Anthropic | Explicit `cache_control`, max 4 breakpoints | `LINT` | Prefix stability, cascade invalidation, threshold bool |\n| OpenAI | Automatic ≥1024 tok, 128-tok increment | `LINT` | Prefix stability |\n| DeepSeek | Automatic, 64-tok unit, 0th-token origin | `LINT` | Prefix stability |\n| Gemini | Implicit (2.5+ default) + explicit CachedContent | `LINT` | Prefix stability, best-practice lint |\n\nAll providers: `Determinism.LINT` (byte-prefix stability). Token simulation (`SIM`) is v0.1.1.\n\n---\n\n## Cause classifications\n\nWhen `diff` or `lint` reports a problem, it returns one of these `Cause` values:\n\n| Cause | Description |\n|---|---|\n| `timestamp` | A timestamp or date-time value changed between requests |\n| `uuid` | A UUID or random ID changed between requests |\n| `tool_order` | Tool definitions appear in different order (non-deterministic) |\n| `dynamic` | A dynamic/non-deterministic value changed |\n| `benign_suffix` | Trailing addition that still breaks exact prefix match |\n| `normalized_only` | Divergence only in raw bytes; logical content matches |\n| `unknown` | Cause could not be classified |\n\n---\n\n## Comparison\n\n| Feature | cachelens | prompt-cache-optimizer (v0.5) |\n|---|---|---|\n| Offline static gate / CI | Yes (non-zero exit) | No (runtime SDK wrapper) |\n| Cause classification | Yes (enum + `--explain` prescription) | No (char diff only) |\n| Python / pytest / pre-commit | Yes | No (TypeScript only) |\n| Deterministic JSON canonicalization (JCS-style key sort) | Yes (strict + logical modes) | No |\n| Token/cost estimates | v0.1.1 (opt-in, labeled) | Yes (online) |\n\n---\n\n## Synthetic fixtures disclaimer\n\nAll fixtures in `tests/fixtures/` are **synthetic, hand-crafted examples** for unit testing. They do not represent real provider responses or measured performance data. Numbers in test output are derived from the fixture inputs — not from provider APIs.\n\n---\n\n## License\n\nMIT. See [LICENSE](LICENSE).\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fhinanohart%2Fcachelens","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fhinanohart%2Fcachelens","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fhinanohart%2Fcachelens/lists"}