cctx-cli 1.12.1__tar.gz → 1.13.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {cctx_cli-1.12.1 → cctx_cli-1.13.0}/CHANGELOG.md +15 -0
- {cctx_cli-1.12.1 → cctx_cli-1.13.0}/PKG-INFO +9 -2
- {cctx_cli-1.12.1 → cctx_cli-1.13.0}/README.md +8 -1
- {cctx_cli-1.12.1 → cctx_cli-1.13.0}/cctx/__init__.py +1 -1
- {cctx_cli-1.12.1 → cctx_cli-1.13.0}/cctx/diagnostician/__init__.py +2 -0
- cctx_cli-1.13.0/cctx/diagnostician/patterns/cache_hygiene.py +125 -0
- {cctx_cli-1.12.1 → cctx_cli-1.13.0}/cctx/models.py +3 -0
- {cctx_cli-1.12.1 → cctx_cli-1.13.0}/cctx/recommender/claude_md.py +11 -0
- cctx_cli-1.12.1/docs/quickstart-openai-agents.md → cctx_cli-1.13.0/docs/quickstart-otel.md +77 -8
- {cctx_cli-1.12.1 → cctx_cli-1.13.0}/pyproject.toml +1 -1
- cctx_cli-1.13.0/tests/test_cache_hygiene_classifier.py +280 -0
- {cctx_cli-1.12.1 → cctx_cli-1.13.0}/tests/test_harvest_emit.py +2 -1
- {cctx_cli-1.12.1 → cctx_cli-1.13.0}/.github/workflows/ci.yml +0 -0
- {cctx_cli-1.12.1 → cctx_cli-1.13.0}/.github/workflows/publish.yml +0 -0
- {cctx_cli-1.12.1 → cctx_cli-1.13.0}/.github/workflows/release.yml +0 -0
- {cctx_cli-1.12.1 → cctx_cli-1.13.0}/.gitignore +0 -0
- {cctx_cli-1.12.1 → cctx_cli-1.13.0}/CLAUDE.md +0 -0
- {cctx_cli-1.12.1 → cctx_cli-1.13.0}/DESIGN.md +0 -0
- {cctx_cli-1.12.1 → cctx_cli-1.13.0}/PRODUCT.md +0 -0
- {cctx_cli-1.12.1 → cctx_cli-1.13.0}/action.yml +0 -0
- {cctx_cli-1.12.1 → cctx_cli-1.13.0}/cctx/agents.py +0 -0
- {cctx_cli-1.12.1 → cctx_cli-1.13.0}/cctx/cli.py +0 -0
- {cctx_cli-1.12.1 → cctx_cli-1.13.0}/cctx/diagnostician/aggregate.py +0 -0
- {cctx_cli-1.12.1 → cctx_cli-1.13.0}/cctx/diagnostician/inflection.py +0 -0
- {cctx_cli-1.12.1 → cctx_cli-1.13.0}/cctx/diagnostician/patterns/__init__.py +0 -0
- {cctx_cli-1.12.1 → cctx_cli-1.13.0}/cctx/diagnostician/patterns/dead_end.py +0 -0
- {cctx_cli-1.12.1 → cctx_cli-1.13.0}/cctx/diagnostician/patterns/fan_out.py +0 -0
- {cctx_cli-1.12.1 → cctx_cli-1.13.0}/cctx/diagnostician/patterns/project_specific.py +0 -0
- {cctx_cli-1.12.1 → cctx_cli-1.13.0}/cctx/diagnostician/patterns/retry_loop.py +0 -0
- {cctx_cli-1.12.1 → cctx_cli-1.13.0}/cctx/diagnostician/patterns/scope_creep.py +0 -0
- {cctx_cli-1.12.1 → cctx_cli-1.13.0}/cctx/diagnostician/patterns/stale_context.py +0 -0
- {cctx_cli-1.12.1 → cctx_cli-1.13.0}/cctx/diagnostician/patterns/tool_thrash.py +0 -0
- {cctx_cli-1.12.1 → cctx_cli-1.13.0}/cctx/discovery.py +0 -0
- {cctx_cli-1.12.1 → cctx_cli-1.13.0}/cctx/exporters/__init__.py +0 -0
- {cctx_cli-1.12.1 → cctx_cli-1.13.0}/cctx/exporters/csv.py +0 -0
- {cctx_cli-1.12.1 → cctx_cli-1.13.0}/cctx/exporters/json.py +0 -0
- {cctx_cli-1.12.1 → cctx_cli-1.13.0}/cctx/exporters/jsonl.py +0 -0
- {cctx_cli-1.12.1 → cctx_cli-1.13.0}/cctx/harvest.py +0 -0
- {cctx_cli-1.12.1 → cctx_cli-1.13.0}/cctx/hook_installer.py +0 -0
- {cctx_cli-1.12.1 → cctx_cli-1.13.0}/cctx/parsers/__init__.py +0 -0
- {cctx_cli-1.12.1 → cctx_cli-1.13.0}/cctx/parsers/claude_code.py +0 -0
- {cctx_cli-1.12.1 → cctx_cli-1.13.0}/cctx/parsers/otel.py +0 -0
- {cctx_cli-1.12.1 → cctx_cli-1.13.0}/cctx/pricing.py +0 -0
- {cctx_cli-1.12.1 → cctx_cli-1.13.0}/cctx/recommender/__init__.py +0 -0
- {cctx_cli-1.12.1 → cctx_cli-1.13.0}/cctx/recommender/evidence.py +0 -0
- {cctx_cli-1.12.1 → cctx_cli-1.13.0}/cctx/renderers/__init__.py +0 -0
- {cctx_cli-1.12.1 → cctx_cli-1.13.0}/cctx/renderers/github.py +0 -0
- {cctx_cli-1.12.1 → cctx_cli-1.13.0}/cctx/renderers/report.py +0 -0
- {cctx_cli-1.12.1 → cctx_cli-1.13.0}/cctx/renderers/templates/autopsy.html.j2 +0 -0
- {cctx_cli-1.12.1 → cctx_cli-1.13.0}/cctx/renderers/terminal.py +0 -0
- {cctx_cli-1.12.1 → cctx_cli-1.13.0}/cctx/renderers/trace_tui.py +0 -0
- {cctx_cli-1.12.1 → cctx_cli-1.13.0}/cctx/tokenizer.py +0 -0
- {cctx_cli-1.12.1 → cctx_cli-1.13.0}/cctx/watcher.py +0 -0
- {cctx_cli-1.12.1 → cctx_cli-1.13.0}/cctx-project-brief.md +0 -0
- {cctx_cli-1.12.1 → cctx_cli-1.13.0}/demo.gif +0 -0
- {cctx_cli-1.12.1 → cctx_cli-1.13.0}/demo.tape +0 -0
- {cctx_cli-1.12.1 → cctx_cli-1.13.0}/docs/health-reviews/2026-05-15-deep-review-summary.md +0 -0
- {cctx_cli-1.12.1 → cctx_cli-1.13.0}/docs/health-reviews/2026-05-15-health-review.md +0 -0
- {cctx_cli-1.12.1 → cctx_cli-1.13.0}/docs/product-reviews/2026-05-15-product-review.md +0 -0
- {cctx_cli-1.12.1 → cctx_cli-1.13.0}/docs/product-reviews/2026-06-09-product-review.md +0 -0
- {cctx_cli-1.12.1 → cctx_cli-1.13.0}/docs/superpowers/plans/2026-05-12-claude-code-parser.md +0 -0
- {cctx_cli-1.12.1 → cctx_cli-1.13.0}/docs/superpowers/plans/2026-05-14-autopsy-v0.md +0 -0
- {cctx_cli-1.12.1 → cctx_cli-1.13.0}/docs/superpowers/plans/2026-05-16-readme-pypi-release.md +0 -0
- {cctx_cli-1.12.1 → cctx_cli-1.13.0}/docs/superpowers/plans/2026-05-17-harvest-check-depth.md +0 -0
- {cctx_cli-1.12.1 → cctx_cli-1.13.0}/docs/superpowers/plans/2026-05-17-project-pattern-detection.md +0 -0
- {cctx_cli-1.12.1 → cctx_cli-1.13.0}/docs/superpowers/plans/2026-05-19-claude-agents-live-integration.md +0 -0
- {cctx_cli-1.12.1 → cctx_cli-1.13.0}/docs/superpowers/plans/2026-06-19-otel-parser.md +0 -0
- {cctx_cli-1.12.1 → cctx_cli-1.13.0}/docs/superpowers/specs/2026-05-12-claude-code-parser-design.md +0 -0
- {cctx_cli-1.12.1 → cctx_cli-1.13.0}/docs/superpowers/specs/2026-05-14-autopsy-design.md +0 -0
- {cctx_cli-1.12.1 → cctx_cli-1.13.0}/docs/superpowers/specs/2026-05-14-harvest-design.md +0 -0
- {cctx_cli-1.12.1 → cctx_cli-1.13.0}/docs/superpowers/specs/2026-05-14-trace-tui-design.md +0 -0
- {cctx_cli-1.12.1 → cctx_cli-1.13.0}/docs/superpowers/specs/2026-05-16-readme-pypi-release-design.md +0 -0
- {cctx_cli-1.12.1 → cctx_cli-1.13.0}/docs/superpowers/specs/2026-05-17-harvest-check-depth-design.md +0 -0
- {cctx_cli-1.12.1 → cctx_cli-1.13.0}/docs/superpowers/specs/2026-05-17-project-pattern-detection-design.md +0 -0
- {cctx_cli-1.12.1 → cctx_cli-1.13.0}/docs/superpowers/specs/2026-05-19-claude-agents-live-integration-design.md +0 -0
- {cctx_cli-1.12.1 → cctx_cli-1.13.0}/docs/superpowers/specs/2026-06-09-cross-agent-emit-design.md +0 -0
- {cctx_cli-1.12.1 → cctx_cli-1.13.0}/docs/superpowers/specs/2026-06-19-otel-parser-design.md +0 -0
- {cctx_cli-1.12.1 → cctx_cli-1.13.0}/tests/__init__.py +0 -0
- {cctx_cli-1.12.1 → cctx_cli-1.13.0}/tests/conftest.py +0 -0
- {cctx_cli-1.12.1 → cctx_cli-1.13.0}/tests/diagnostician/__init__.py +0 -0
- {cctx_cli-1.12.1 → cctx_cli-1.13.0}/tests/diagnostician/conftest.py +0 -0
- {cctx_cli-1.12.1 → cctx_cli-1.13.0}/tests/diagnostician/test_dead_end.py +0 -0
- {cctx_cli-1.12.1 → cctx_cli-1.13.0}/tests/diagnostician/test_inflection.py +0 -0
- {cctx_cli-1.12.1 → cctx_cli-1.13.0}/tests/diagnostician/test_orchestrator.py +0 -0
- {cctx_cli-1.12.1 → cctx_cli-1.13.0}/tests/diagnostician/test_project_specific.py +0 -0
- {cctx_cli-1.12.1 → cctx_cli-1.13.0}/tests/diagnostician/test_retry_loop.py +0 -0
- {cctx_cli-1.12.1 → cctx_cli-1.13.0}/tests/diagnostician/test_scope_creep.py +0 -0
- {cctx_cli-1.12.1 → cctx_cli-1.13.0}/tests/diagnostician/test_stale_context.py +0 -0
- {cctx_cli-1.12.1 → cctx_cli-1.13.0}/tests/diagnostician/test_tool_thrash.py +0 -0
- {cctx_cli-1.12.1 → cctx_cli-1.13.0}/tests/exporters/__init__.py +0 -0
- {cctx_cli-1.12.1 → cctx_cli-1.13.0}/tests/exporters/test_csv.py +0 -0
- {cctx_cli-1.12.1 → cctx_cli-1.13.0}/tests/exporters/test_jsonl.py +0 -0
- {cctx_cli-1.12.1 → cctx_cli-1.13.0}/tests/fixtures/claude_code/README.md +0 -0
- {cctx_cli-1.12.1 → cctx_cli-1.13.0}/tests/fixtures/claude_code/short-clean/short-clean.jsonl +0 -0
- {cctx_cli-1.12.1 → cctx_cli-1.13.0}/tests/fixtures/claude_code/with-attachments/with-attachments/subagents/agent-a0b4c2cf1dde0ca56.meta.json +0 -0
- {cctx_cli-1.12.1 → cctx_cli-1.13.0}/tests/fixtures/claude_code/with-attachments/with-attachments/subagents/agent-a116ae34b1b09c332.meta.json +0 -0
- {cctx_cli-1.12.1 → cctx_cli-1.13.0}/tests/fixtures/claude_code/with-attachments/with-attachments/subagents/agent-a1c4c417b35658c9e.meta.json +0 -0
- {cctx_cli-1.12.1 → cctx_cli-1.13.0}/tests/fixtures/claude_code/with-attachments/with-attachments/subagents/agent-a1e41a901de38f1b5.meta.json +0 -0
- {cctx_cli-1.12.1 → cctx_cli-1.13.0}/tests/fixtures/claude_code/with-attachments/with-attachments/subagents/agent-a338f8d0c74612a24.meta.json +0 -0
- {cctx_cli-1.12.1 → cctx_cli-1.13.0}/tests/fixtures/claude_code/with-attachments/with-attachments/subagents/agent-a34f6f3c0e7094186.meta.json +0 -0
- {cctx_cli-1.12.1 → cctx_cli-1.13.0}/tests/fixtures/claude_code/with-attachments/with-attachments/subagents/agent-a5a5a0cff4d13308b.meta.json +0 -0
- {cctx_cli-1.12.1 → cctx_cli-1.13.0}/tests/fixtures/claude_code/with-attachments/with-attachments/subagents/agent-a6b0a3da6a0484db5.meta.json +0 -0
- {cctx_cli-1.12.1 → cctx_cli-1.13.0}/tests/fixtures/claude_code/with-attachments/with-attachments/subagents/agent-a7f73f1790b02cde5.meta.json +0 -0
- {cctx_cli-1.12.1 → cctx_cli-1.13.0}/tests/fixtures/claude_code/with-attachments/with-attachments/subagents/agent-a7f7c17c38a9d8788.meta.json +0 -0
- {cctx_cli-1.12.1 → cctx_cli-1.13.0}/tests/fixtures/claude_code/with-attachments/with-attachments/subagents/agent-a853259e2cd7bbe8a.meta.json +0 -0
- {cctx_cli-1.12.1 → cctx_cli-1.13.0}/tests/fixtures/claude_code/with-attachments/with-attachments/subagents/agent-a8d9aedb0d0c6e12d.meta.json +0 -0
- {cctx_cli-1.12.1 → cctx_cli-1.13.0}/tests/fixtures/claude_code/with-attachments/with-attachments/subagents/agent-aa778bc1d59e4a441.meta.json +0 -0
- {cctx_cli-1.12.1 → cctx_cli-1.13.0}/tests/fixtures/claude_code/with-attachments/with-attachments/subagents/agent-aba869dedee4a12ba.meta.json +0 -0
- {cctx_cli-1.12.1 → cctx_cli-1.13.0}/tests/fixtures/claude_code/with-attachments/with-attachments/subagents/agent-ada2746d9774b94db.meta.json +0 -0
- {cctx_cli-1.12.1 → cctx_cli-1.13.0}/tests/fixtures/claude_code/with-attachments/with-attachments/subagents/agent-aea0132068c64d2dd.meta.json +0 -0
- {cctx_cli-1.12.1 → cctx_cli-1.13.0}/tests/fixtures/claude_code/with-attachments/with-attachments/subagents/agent-aea215eff50874d5f.meta.json +0 -0
- {cctx_cli-1.12.1 → cctx_cli-1.13.0}/tests/fixtures/claude_code/with-attachments/with-attachments/subagents/agent-afee21f2b3852a4a0.meta.json +0 -0
- {cctx_cli-1.12.1 → cctx_cli-1.13.0}/tests/fixtures/claude_code/with-attachments/with-attachments.jsonl +0 -0
- {cctx_cli-1.12.1 → cctx_cli-1.13.0}/tests/fixtures/claude_code/with-compaction/with-compaction/subagents/agent-a051d9c9a6b2f5cc3.jsonl +0 -0
- {cctx_cli-1.12.1 → cctx_cli-1.13.0}/tests/fixtures/claude_code/with-compaction/with-compaction/subagents/agent-a051d9c9a6b2f5cc3.meta.json +0 -0
- {cctx_cli-1.12.1 → cctx_cli-1.13.0}/tests/fixtures/claude_code/with-compaction/with-compaction/subagents/agent-a171f16f4e65cfe75.jsonl +0 -0
- {cctx_cli-1.12.1 → cctx_cli-1.13.0}/tests/fixtures/claude_code/with-compaction/with-compaction/subagents/agent-a171f16f4e65cfe75.meta.json +0 -0
- {cctx_cli-1.12.1 → cctx_cli-1.13.0}/tests/fixtures/claude_code/with-compaction/with-compaction/subagents/agent-a1b77fea2c0a2269b.jsonl +0 -0
- {cctx_cli-1.12.1 → cctx_cli-1.13.0}/tests/fixtures/claude_code/with-compaction/with-compaction/subagents/agent-a1b77fea2c0a2269b.meta.json +0 -0
- {cctx_cli-1.12.1 → cctx_cli-1.13.0}/tests/fixtures/claude_code/with-compaction/with-compaction/subagents/agent-a20da4c01a54acca8.jsonl +0 -0
- {cctx_cli-1.12.1 → cctx_cli-1.13.0}/tests/fixtures/claude_code/with-compaction/with-compaction/subagents/agent-a20da4c01a54acca8.meta.json +0 -0
- {cctx_cli-1.12.1 → cctx_cli-1.13.0}/tests/fixtures/claude_code/with-compaction/with-compaction/subagents/agent-a3c82739b1383fb14.jsonl +0 -0
- {cctx_cli-1.12.1 → cctx_cli-1.13.0}/tests/fixtures/claude_code/with-compaction/with-compaction/subagents/agent-a3c82739b1383fb14.meta.json +0 -0
- {cctx_cli-1.12.1 → cctx_cli-1.13.0}/tests/fixtures/claude_code/with-compaction/with-compaction/subagents/agent-a49e8539611c5fe12.jsonl +0 -0
- {cctx_cli-1.12.1 → cctx_cli-1.13.0}/tests/fixtures/claude_code/with-compaction/with-compaction/subagents/agent-a49e8539611c5fe12.meta.json +0 -0
- {cctx_cli-1.12.1 → cctx_cli-1.13.0}/tests/fixtures/claude_code/with-compaction/with-compaction/subagents/agent-a7bb58f3fff2b3e8d.jsonl +0 -0
- {cctx_cli-1.12.1 → cctx_cli-1.13.0}/tests/fixtures/claude_code/with-compaction/with-compaction/subagents/agent-a7bb58f3fff2b3e8d.meta.json +0 -0
- {cctx_cli-1.12.1 → cctx_cli-1.13.0}/tests/fixtures/claude_code/with-compaction/with-compaction/subagents/agent-a92b48c0331195aac.jsonl +0 -0
- {cctx_cli-1.12.1 → cctx_cli-1.13.0}/tests/fixtures/claude_code/with-compaction/with-compaction/subagents/agent-a92b48c0331195aac.meta.json +0 -0
- {cctx_cli-1.12.1 → cctx_cli-1.13.0}/tests/fixtures/claude_code/with-compaction/with-compaction/subagents/agent-ab96c4264099694a9.jsonl +0 -0
- {cctx_cli-1.12.1 → cctx_cli-1.13.0}/tests/fixtures/claude_code/with-compaction/with-compaction/subagents/agent-ab96c4264099694a9.meta.json +0 -0
- {cctx_cli-1.12.1 → cctx_cli-1.13.0}/tests/fixtures/claude_code/with-compaction/with-compaction/subagents/agent-acb2895c5e34ffec0.jsonl +0 -0
- {cctx_cli-1.12.1 → cctx_cli-1.13.0}/tests/fixtures/claude_code/with-compaction/with-compaction/subagents/agent-acb2895c5e34ffec0.meta.json +0 -0
- {cctx_cli-1.12.1 → cctx_cli-1.13.0}/tests/fixtures/claude_code/with-compaction/with-compaction/subagents/agent-adb2302769938fb3f.jsonl +0 -0
- {cctx_cli-1.12.1 → cctx_cli-1.13.0}/tests/fixtures/claude_code/with-compaction/with-compaction/subagents/agent-adb2302769938fb3f.meta.json +0 -0
- {cctx_cli-1.12.1 → cctx_cli-1.13.0}/tests/fixtures/claude_code/with-compaction/with-compaction/subagents/agent-ae585eca15cb93b9c.jsonl +0 -0
- {cctx_cli-1.12.1 → cctx_cli-1.13.0}/tests/fixtures/claude_code/with-compaction/with-compaction/subagents/agent-ae585eca15cb93b9c.meta.json +0 -0
- {cctx_cli-1.12.1 → cctx_cli-1.13.0}/tests/fixtures/claude_code/with-compaction/with-compaction/subagents/agent-aec9c917feb903d67.jsonl +0 -0
- {cctx_cli-1.12.1 → cctx_cli-1.13.0}/tests/fixtures/claude_code/with-compaction/with-compaction/subagents/agent-aec9c917feb903d67.meta.json +0 -0
- {cctx_cli-1.12.1 → cctx_cli-1.13.0}/tests/fixtures/claude_code/with-compaction/with-compaction.jsonl +0 -0
- {cctx_cli-1.12.1 → cctx_cli-1.13.0}/tests/fixtures/claude_code/with-subagents/with-subagents/subagents/agent-a1a3a21aeb76bb0a9.jsonl +0 -0
- {cctx_cli-1.12.1 → cctx_cli-1.13.0}/tests/fixtures/claude_code/with-subagents/with-subagents/subagents/agent-a1a3a21aeb76bb0a9.meta.json +0 -0
- {cctx_cli-1.12.1 → cctx_cli-1.13.0}/tests/fixtures/claude_code/with-subagents/with-subagents/subagents/agent-aaa1d6ecc05a78442.jsonl +0 -0
- {cctx_cli-1.12.1 → cctx_cli-1.13.0}/tests/fixtures/claude_code/with-subagents/with-subagents/subagents/agent-aaa1d6ecc05a78442.meta.json +0 -0
- {cctx_cli-1.12.1 → cctx_cli-1.13.0}/tests/fixtures/claude_code/with-subagents/with-subagents/subagents/agent-af3c545ccd30036d2.jsonl +0 -0
- {cctx_cli-1.12.1 → cctx_cli-1.13.0}/tests/fixtures/claude_code/with-subagents/with-subagents/subagents/agent-af3c545ccd30036d2.meta.json +0 -0
- {cctx_cli-1.12.1 → cctx_cli-1.13.0}/tests/fixtures/claude_code/with-subagents/with-subagents/tool-results/btwp2bzro.txt +0 -0
- {cctx_cli-1.12.1 → cctx_cli-1.13.0}/tests/fixtures/claude_code/with-subagents/with-subagents/tool-results/byqjbgy4b.txt +0 -0
- {cctx_cli-1.12.1 → cctx_cli-1.13.0}/tests/fixtures/claude_code/with-subagents/with-subagents.jsonl +0 -0
- {cctx_cli-1.12.1 → cctx_cli-1.13.0}/tests/fixtures/claude_code/with-tool-results/with-tool-results/tool-results/bosbkda0h.txt +0 -0
- {cctx_cli-1.12.1 → cctx_cli-1.13.0}/tests/fixtures/claude_code/with-tool-results/with-tool-results.jsonl +0 -0
- {cctx_cli-1.12.1 → cctx_cli-1.13.0}/tests/fixtures/otel_fanout.jsonl +0 -0
- {cctx_cli-1.12.1 → cctx_cli-1.13.0}/tests/fixtures/otel_handoff.jsonl +0 -0
- {cctx_cli-1.12.1 → cctx_cli-1.13.0}/tests/fixtures/scrub.py +0 -0
- {cctx_cli-1.12.1 → cctx_cli-1.13.0}/tests/fixtures/synthetic/bookkeeping_only.jsonl +0 -0
- {cctx_cli-1.12.1 → cctx_cli-1.13.0}/tests/fixtures/synthetic/malformed_middle.jsonl +0 -0
- {cctx_cli-1.12.1 → cctx_cli-1.13.0}/tests/fixtures/synthetic/truncated_final_line.jsonl +0 -0
- {cctx_cli-1.12.1 → cctx_cli-1.13.0}/tests/fixtures/synthetic/unknown_attachment_shape.jsonl +0 -0
- {cctx_cli-1.12.1 → cctx_cli-1.13.0}/tests/fixtures/synthetic/unknown_type.jsonl +0 -0
- {cctx_cli-1.12.1 → cctx_cli-1.13.0}/tests/parsers/__init__.py +0 -0
- {cctx_cli-1.12.1 → cctx_cli-1.13.0}/tests/parsers/test_claude_code.py +0 -0
- {cctx_cli-1.12.1 → cctx_cli-1.13.0}/tests/parsers/test_claude_code_integration.py +0 -0
- {cctx_cli-1.12.1 → cctx_cli-1.13.0}/tests/recommender/__init__.py +0 -0
- {cctx_cli-1.12.1 → cctx_cli-1.13.0}/tests/recommender/test_claude_md.py +0 -0
- {cctx_cli-1.12.1 → cctx_cli-1.13.0}/tests/recommender/test_evidence.py +0 -0
- {cctx_cli-1.12.1 → cctx_cli-1.13.0}/tests/renderers/__init__.py +0 -0
- {cctx_cli-1.12.1 → cctx_cli-1.13.0}/tests/renderers/test_report.py +0 -0
- {cctx_cli-1.12.1 → cctx_cli-1.13.0}/tests/renderers/test_terminal_renderer_full.py +0 -0
- {cctx_cli-1.12.1 → cctx_cli-1.13.0}/tests/test_agents.py +0 -0
- {cctx_cli-1.12.1 → cctx_cli-1.13.0}/tests/test_aggregate.py +0 -0
- {cctx_cli-1.12.1 → cctx_cli-1.13.0}/tests/test_cli.py +0 -0
- {cctx_cli-1.12.1 → cctx_cli-1.13.0}/tests/test_cli_export.py +0 -0
- {cctx_cli-1.12.1 → cctx_cli-1.13.0}/tests/test_diagnostician_subagents.py +0 -0
- {cctx_cli-1.12.1 → cctx_cli-1.13.0}/tests/test_discovery.py +0 -0
- {cctx_cli-1.12.1 → cctx_cli-1.13.0}/tests/test_efficacy.py +0 -0
- {cctx_cli-1.12.1 → cctx_cli-1.13.0}/tests/test_fanout_classifier.py +0 -0
- {cctx_cli-1.12.1 → cctx_cli-1.13.0}/tests/test_github_summary.py +0 -0
- {cctx_cli-1.12.1 → cctx_cli-1.13.0}/tests/test_harvest.py +0 -0
- {cctx_cli-1.12.1 → cctx_cli-1.13.0}/tests/test_harvest_check.py +0 -0
- {cctx_cli-1.12.1 → cctx_cli-1.13.0}/tests/test_init.py +0 -0
- {cctx_cli-1.12.1 → cctx_cli-1.13.0}/tests/test_models.py +0 -0
- {cctx_cli-1.12.1 → cctx_cli-1.13.0}/tests/test_models_project_pattern.py +0 -0
- {cctx_cli-1.12.1 → cctx_cli-1.13.0}/tests/test_otel_parser.py +0 -0
- {cctx_cli-1.12.1 → cctx_cli-1.13.0}/tests/test_recommender.py +0 -0
- {cctx_cli-1.12.1 → cctx_cli-1.13.0}/tests/test_smoke.py +0 -0
- {cctx_cli-1.12.1 → cctx_cli-1.13.0}/tests/test_terminal_renderer.py +0 -0
- {cctx_cli-1.12.1 → cctx_cli-1.13.0}/tests/test_tokenizer.py +0 -0
- {cctx_cli-1.12.1 → cctx_cli-1.13.0}/tests/test_trace_tui.py +0 -0
- {cctx_cli-1.12.1 → cctx_cli-1.13.0}/tests/test_watcher.py +0 -0
|
@@ -2,6 +2,21 @@
|
|
|
2
2
|
|
|
3
3
|
<!-- version list -->
|
|
4
4
|
|
|
5
|
+
## v1.13.0 (2026-06-20)
|
|
6
|
+
|
|
7
|
+
### Documentation
|
|
8
|
+
|
|
9
|
+
- OTEL quickstart for LangGraph + README multi-framework callout
|
|
10
|
+
([#119](https://github.com/jacquardlabs/cctx/pull/119),
|
|
11
|
+
[`98d89d9`](https://github.com/jacquardlabs/cctx/commit/98d89d9fbf882021f0f54eb7f6676ceb3fbc8ca0))
|
|
12
|
+
|
|
13
|
+
### Features
|
|
14
|
+
|
|
15
|
+
- KV-cache hygiene diagnosis — hit rate + cause (#96)
|
|
16
|
+
([#121](https://github.com/jacquardlabs/cctx/pull/121),
|
|
17
|
+
[`2a3f49c`](https://github.com/jacquardlabs/cctx/commit/2a3f49cc253d3aacda1cde987138f19518537b89))
|
|
18
|
+
|
|
19
|
+
|
|
5
20
|
## v1.12.1 (2026-06-20)
|
|
6
21
|
|
|
7
22
|
### Bug Fixes
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: cctx-cli
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.13.0
|
|
4
4
|
Summary: Diagnose Claude Code sessions — find what went wrong, what it cost, and what to add to CLAUDE.md
|
|
5
5
|
Author: Jacquard Labs
|
|
6
6
|
License-Expression: MIT
|
|
@@ -20,7 +20,7 @@ Description-Content-Type: text/markdown
|
|
|
20
20
|
|
|
21
21
|
# cctx
|
|
22
22
|
|
|
23
|
-
Diagnose your Claude Code sessions — find out when they went wrong, why they cost what they did, and what to add to your `CLAUDE.md` so it doesn't happen again.
|
|
23
|
+
Diagnose your Claude Code sessions and OpenTelemetry agent traces — find out when they went wrong, why they cost what they did, and what to add to your `CLAUDE.md` so it doesn't happen again.
|
|
24
24
|
|
|
25
25
|
[](https://github.com/jacquardlabs/cctx/actions/workflows/ci.yml)
|
|
26
26
|
[](https://pypi.org/project/cctx-cli/)
|
|
@@ -53,6 +53,8 @@ cctx watch # live signals during an active session
|
|
|
53
53
|
|
|
54
54
|
cctx is primarily a forensic tool. You reach for it after a session — when something felt off, when the cost was higher than expected, or on a weekly review pass. `cctx watch` runs during a session and surfaces patterns as they happen. It reads the JSONL logs Claude Code writes to `~/.claude/projects/` and produces findings with attributed cost and copy-pasteable `CLAUDE.md` patches.
|
|
55
55
|
|
|
56
|
+
cctx also diagnoses OTEL traces from the OpenAI Agents SDK, LangGraph, and any framework that emits `gen_ai.*` semantic convention spans — auto-detected, no flags needed. See [Diagnosing other agent frameworks](docs/quickstart-otel.md).
|
|
57
|
+
|
|
56
58
|
## Commands
|
|
57
59
|
|
|
58
60
|
### `cctx ls` — list projects and sessions
|
|
@@ -76,10 +78,15 @@ cctx autopsy ~/Projects/myapp --since 7
|
|
|
76
78
|
|
|
77
79
|
# Write a self-contained HTML report
|
|
78
80
|
cctx autopsy ~/.claude/projects/-Users-you-Projects-myapp/abc123.jsonl --html report.html
|
|
81
|
+
|
|
82
|
+
# OTEL trace from OpenAI Agents SDK, LangGraph, or any gen_ai.*-instrumented framework
|
|
83
|
+
cctx autopsy agent_trace.jsonl
|
|
79
84
|
```
|
|
80
85
|
|
|
81
86
|
Runs three pattern classifiers (retry loop, scope creep, stale context) and prints findings with attributed cost. Use `--since N` to aggregate patterns across multiple sessions in a project.
|
|
82
87
|
|
|
88
|
+
OTEL traces are auto-detected — cctx sniffs the file format and routes to the right parser. See [docs/quickstart-otel.md](docs/quickstart-otel.md) for how to wire the OTEL exporter in each framework.
|
|
89
|
+
|
|
83
90
|
### `cctx harvest` — apply patches to CLAUDE.md
|
|
84
91
|
|
|
85
92
|
```bash
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# cctx
|
|
2
2
|
|
|
3
|
-
Diagnose your Claude Code sessions — find out when they went wrong, why they cost what they did, and what to add to your `CLAUDE.md` so it doesn't happen again.
|
|
3
|
+
Diagnose your Claude Code sessions and OpenTelemetry agent traces — find out when they went wrong, why they cost what they did, and what to add to your `CLAUDE.md` so it doesn't happen again.
|
|
4
4
|
|
|
5
5
|
[](https://github.com/jacquardlabs/cctx/actions/workflows/ci.yml)
|
|
6
6
|
[](https://pypi.org/project/cctx-cli/)
|
|
@@ -33,6 +33,8 @@ cctx watch # live signals during an active session
|
|
|
33
33
|
|
|
34
34
|
cctx is primarily a forensic tool. You reach for it after a session — when something felt off, when the cost was higher than expected, or on a weekly review pass. `cctx watch` runs during a session and surfaces patterns as they happen. It reads the JSONL logs Claude Code writes to `~/.claude/projects/` and produces findings with attributed cost and copy-pasteable `CLAUDE.md` patches.
|
|
35
35
|
|
|
36
|
+
cctx also diagnoses OTEL traces from the OpenAI Agents SDK, LangGraph, and any framework that emits `gen_ai.*` semantic convention spans — auto-detected, no flags needed. See [Diagnosing other agent frameworks](docs/quickstart-otel.md).
|
|
37
|
+
|
|
36
38
|
## Commands
|
|
37
39
|
|
|
38
40
|
### `cctx ls` — list projects and sessions
|
|
@@ -56,10 +58,15 @@ cctx autopsy ~/Projects/myapp --since 7
|
|
|
56
58
|
|
|
57
59
|
# Write a self-contained HTML report
|
|
58
60
|
cctx autopsy ~/.claude/projects/-Users-you-Projects-myapp/abc123.jsonl --html report.html
|
|
61
|
+
|
|
62
|
+
# OTEL trace from OpenAI Agents SDK, LangGraph, or any gen_ai.*-instrumented framework
|
|
63
|
+
cctx autopsy agent_trace.jsonl
|
|
59
64
|
```
|
|
60
65
|
|
|
61
66
|
Runs three pattern classifiers (retry loop, scope creep, stale context) and prints findings with attributed cost. Use `--since N` to aggregate patterns across multiple sessions in a project.
|
|
62
67
|
|
|
68
|
+
OTEL traces are auto-detected — cctx sniffs the file format and routes to the right parser. See [docs/quickstart-otel.md](docs/quickstart-otel.md) for how to wire the OTEL exporter in each framework.
|
|
69
|
+
|
|
63
70
|
### `cctx harvest` — apply patches to CLAUDE.md
|
|
64
71
|
|
|
65
72
|
```bash
|
|
@@ -15,6 +15,7 @@ from typing import TYPE_CHECKING
|
|
|
15
15
|
|
|
16
16
|
from cctx.diagnostician import inflection
|
|
17
17
|
from cctx.diagnostician.patterns import (
|
|
18
|
+
cache_hygiene,
|
|
18
19
|
dead_end,
|
|
19
20
|
fan_out,
|
|
20
21
|
retry_loop,
|
|
@@ -152,6 +153,7 @@ def run(trace: SessionTrace) -> Diagnosis:
|
|
|
152
153
|
*tool_thrash.classify(trace),
|
|
153
154
|
*dead_end.classify(trace),
|
|
154
155
|
*fan_out.classify(trace),
|
|
156
|
+
*cache_hygiene.classify(trace),
|
|
155
157
|
]
|
|
156
158
|
findings.sort(key=lambda f: f.first_turn)
|
|
157
159
|
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
"""Cache-hygiene classifier.
|
|
2
|
+
|
|
3
|
+
Detects sessions with low KV-cache hit rates and identifies the likely cause
|
|
4
|
+
of the miss. A cache miss costs ~10× more than a cache hit on Sonnet
|
|
5
|
+
($3/MTok vs $0.30/MTok), making this one of the highest-value findings.
|
|
6
|
+
|
|
7
|
+
Thresholds:
|
|
8
|
+
MIN_TOTAL_TOKENS = 5_000 — skip tiny sessions
|
|
9
|
+
HIT_RATE_THRESHOLD = 0.50 — below this → fire finding
|
|
10
|
+
HALF_DROP_THRESHOLD = 0.20 — early_rate - late_rate drop to flag as degrading
|
|
11
|
+
"""
|
|
12
|
+
from __future__ import annotations
|
|
13
|
+
|
|
14
|
+
from typing import TYPE_CHECKING
|
|
15
|
+
|
|
16
|
+
from cctx.models import Confidence, Finding, FindingKind, Severity
|
|
17
|
+
|
|
18
|
+
if TYPE_CHECKING:
|
|
19
|
+
from cctx.models import SessionTrace, Turn
|
|
20
|
+
|
|
21
|
+
MIN_TOTAL_TOKENS = 5_000
|
|
22
|
+
HIT_RATE_THRESHOLD = 0.50
|
|
23
|
+
HALF_DROP_THRESHOLD = 0.20
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def _hit_rate(turns: list[Turn]) -> float:
|
|
27
|
+
total_in = sum(
|
|
28
|
+
t.usage.input_tokens + t.usage.cache_read for t in turns if t.usage
|
|
29
|
+
)
|
|
30
|
+
total_cached = sum(t.usage.cache_read for t in turns if t.usage)
|
|
31
|
+
return total_cached / total_in if total_in > 0 else 0.0
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def _detect_cause(trace: SessionTrace, assistant_turns: list[Turn]) -> str | None:
|
|
35
|
+
"""Identify the most likely cause of low cache hit rate from JSONL signals."""
|
|
36
|
+
from cctx.diagnostician.patterns.stale_context import _is_compaction
|
|
37
|
+
|
|
38
|
+
# Compaction events invalidate the cache entirely.
|
|
39
|
+
compaction_turns = [t for t in trace.turns if _is_compaction(t)]
|
|
40
|
+
if compaction_turns:
|
|
41
|
+
return (
|
|
42
|
+
f"context compacted at turn {compaction_turns[0].turn_number}"
|
|
43
|
+
" (cache invalidated)"
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
# Many tools loaded means the tool-definition prefix is large and unstable.
|
|
47
|
+
if len(trace.tool_names_loaded) > 20:
|
|
48
|
+
return (
|
|
49
|
+
"large tool definition surface (>20 tools loaded)"
|
|
50
|
+
" reduces prefix stability"
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
# No cached tokens on turn 1 with a non-trivial prompt suggests the
|
|
54
|
+
# prompt prefix differs each session and cannot be cached.
|
|
55
|
+
if assistant_turns and assistant_turns[0].usage:
|
|
56
|
+
first = assistant_turns[0].usage
|
|
57
|
+
if first.cache_read == 0 and first.input_tokens > 1000:
|
|
58
|
+
return (
|
|
59
|
+
"no cached tokens on turn 1"
|
|
60
|
+
" — prompt prefix may not be stable across sessions"
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
return None
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def _classify_impl(trace: SessionTrace) -> list[Finding]:
|
|
67
|
+
assistant_turns = [t for t in trace.turns if t.role == "assistant" and t.usage]
|
|
68
|
+
if not assistant_turns:
|
|
69
|
+
return []
|
|
70
|
+
|
|
71
|
+
total_tokens = sum(
|
|
72
|
+
t.usage.input_tokens + t.usage.cache_read for t in assistant_turns
|
|
73
|
+
)
|
|
74
|
+
if total_tokens < MIN_TOTAL_TOKENS:
|
|
75
|
+
return []
|
|
76
|
+
|
|
77
|
+
overall_rate = _hit_rate(assistant_turns)
|
|
78
|
+
if overall_rate >= HIT_RATE_THRESHOLD:
|
|
79
|
+
return []
|
|
80
|
+
|
|
81
|
+
# Split into halves to detect in-session degradation.
|
|
82
|
+
mid = len(assistant_turns) // 2
|
|
83
|
+
early_rate = _hit_rate(assistant_turns[:mid]) if mid > 0 else overall_rate
|
|
84
|
+
late_rate = (
|
|
85
|
+
_hit_rate(assistant_turns[mid:]) if mid < len(assistant_turns) else overall_rate
|
|
86
|
+
)
|
|
87
|
+
|
|
88
|
+
cause = _detect_cause(trace, assistant_turns)
|
|
89
|
+
severity = Severity.HIGH if overall_rate < 0.25 else Severity.MEDIUM
|
|
90
|
+
degrading = early_rate - late_rate > HALF_DROP_THRESHOLD
|
|
91
|
+
|
|
92
|
+
summary_parts = [f"cache hit rate {overall_rate:.0%}"]
|
|
93
|
+
if degrading:
|
|
94
|
+
summary_parts.append(
|
|
95
|
+
f"degraded from {early_rate:.0%} (first half)"
|
|
96
|
+
f" to {late_rate:.0%} (second half)"
|
|
97
|
+
)
|
|
98
|
+
if cause:
|
|
99
|
+
summary_parts.append(cause)
|
|
100
|
+
|
|
101
|
+
return [
|
|
102
|
+
Finding(
|
|
103
|
+
kind=FindingKind.CACHE_HYGIENE,
|
|
104
|
+
severity=severity,
|
|
105
|
+
confidence=Confidence.MEDIUM,
|
|
106
|
+
first_turn=assistant_turns[0].turn_number,
|
|
107
|
+
last_turn=assistant_turns[-1].turn_number,
|
|
108
|
+
evidence={
|
|
109
|
+
"overall_hit_rate": round(overall_rate, 3),
|
|
110
|
+
"early_hit_rate": round(early_rate, 3),
|
|
111
|
+
"late_hit_rate": round(late_rate, 3),
|
|
112
|
+
"total_tokens": total_tokens,
|
|
113
|
+
"cause": cause,
|
|
114
|
+
},
|
|
115
|
+
cost_usd=None,
|
|
116
|
+
summary="; ".join(summary_parts),
|
|
117
|
+
)
|
|
118
|
+
]
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def classify(trace: SessionTrace) -> list[Finding]:
|
|
122
|
+
try:
|
|
123
|
+
return _classify_impl(trace)
|
|
124
|
+
except Exception:
|
|
125
|
+
return []
|
|
@@ -174,6 +174,7 @@ class FindingKind(str, Enum):
|
|
|
174
174
|
DEAD_END = "dead_end"
|
|
175
175
|
FANOUT_WASTE = "fanout_waste"
|
|
176
176
|
PROJECT_PATTERN = "project_pattern"
|
|
177
|
+
CACHE_HYGIENE = "cache_hygiene"
|
|
177
178
|
|
|
178
179
|
|
|
179
180
|
KIND_LABEL: dict[FindingKind, str] = {
|
|
@@ -184,6 +185,7 @@ KIND_LABEL: dict[FindingKind, str] = {
|
|
|
184
185
|
FindingKind.DEAD_END: "DEAD END",
|
|
185
186
|
FindingKind.FANOUT_WASTE: "FANOUT WASTE",
|
|
186
187
|
FindingKind.PROJECT_PATTERN: "PROJECT PATTERN",
|
|
188
|
+
FindingKind.CACHE_HYGIENE: "CACHE HYGIENE",
|
|
187
189
|
}
|
|
188
190
|
|
|
189
191
|
# Maps FindingKind to the exact ## heading emitted by its recommender patch
|
|
@@ -197,6 +199,7 @@ MANAGED_HEADINGS: dict[FindingKind, str] = {
|
|
|
197
199
|
FindingKind.TOOL_THRASH: "## Tool-call discipline",
|
|
198
200
|
FindingKind.DEAD_END: "## Exploration discipline",
|
|
199
201
|
FindingKind.FANOUT_WASTE: "## Fan-out discipline",
|
|
202
|
+
FindingKind.CACHE_HYGIENE: "## Cache hygiene",
|
|
200
203
|
}
|
|
201
204
|
|
|
202
205
|
# Project-specific patterns use a heading that embeds tool+key, so the managed
|
|
@@ -66,6 +66,16 @@ _FANOUT_WASTE_DIFF = """\
|
|
|
66
66
|
+after changing something meaningful about the task — identical re-spawns waste
|
|
67
67
|
+the full subagent cost with no new information."""
|
|
68
68
|
|
|
69
|
+
_CACHE_HYGIENE_DIFF = """\
|
|
70
|
+
+## Cache hygiene
|
|
71
|
+
+
|
|
72
|
+
+Keep the prompt prefix stable across turns so Claude's KV-cache can be reused.
|
|
73
|
+
+Avoid changing tool definitions mid-session, keep system prompt content
|
|
74
|
+
+consistent, and prefer append-only context updates over rewrites. If context
|
|
75
|
+
+compaction fires frequently, consider splitting long sessions into shorter ones
|
|
76
|
+
+with a stable, cacheable preamble. A 10× cost difference separates a warm
|
|
77
|
+
+cache hit from a cold input read."""
|
|
78
|
+
|
|
69
79
|
_TEMPLATES: dict[FindingKind, tuple[str, str, str]] = {
|
|
70
80
|
# kind → (description, diff_body, target_file)
|
|
71
81
|
FindingKind.RETRY_LOOP: ("Add retry discipline rule", _RETRY_LOOP_DIFF, "CLAUDE.md"),
|
|
@@ -74,6 +84,7 @@ _TEMPLATES: dict[FindingKind, tuple[str, str, str]] = {
|
|
|
74
84
|
FindingKind.TOOL_THRASH: ("Add tool-call discipline rule", _TOOL_THRASH_DIFF, "CLAUDE.md"),
|
|
75
85
|
FindingKind.DEAD_END: ("Add exploration discipline rule", _DEAD_END_DIFF, "CLAUDE.md"),
|
|
76
86
|
FindingKind.FANOUT_WASTE: ("Add fan-out discipline rule", _FANOUT_WASTE_DIFF, "CLAUDE.md"),
|
|
87
|
+
FindingKind.CACHE_HYGIENE: ("Add cache hygiene rule", _CACHE_HYGIENE_DIFF, "CLAUDE.md"),
|
|
77
88
|
}
|
|
78
89
|
|
|
79
90
|
|
|
@@ -1,14 +1,20 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Diagnosing other agent frameworks with cctx
|
|
2
2
|
|
|
3
|
-
cctx diagnoses
|
|
3
|
+
cctx diagnoses any agent framework that writes OpenTelemetry spans using `gen_ai.*` semantic conventions — the same way it diagnoses Claude Code sessions. Point it at a trace file and get an autopsy.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
**Verified frameworks:** OpenAI Agents SDK, LangGraph
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## OpenAI Agents SDK
|
|
10
|
+
|
|
11
|
+
### 1. Install dependencies
|
|
6
12
|
|
|
7
13
|
```bash
|
|
8
|
-
pip install cctx opentelemetry-sdk
|
|
14
|
+
pip install cctx-cli opentelemetry-sdk
|
|
9
15
|
```
|
|
10
16
|
|
|
11
|
-
|
|
17
|
+
### 2. Export traces to a local file
|
|
12
18
|
|
|
13
19
|
Add this to your agent script before running your agent. It configures OpenTelemetry to write spans to a local JSONL file.
|
|
14
20
|
|
|
@@ -84,14 +90,14 @@ provider.add_span_processor(BatchSpanProcessor(FileSpanExporter("agent_trace.jso
|
|
|
84
90
|
# set_trace_processors([...])
|
|
85
91
|
```
|
|
86
92
|
|
|
87
|
-
|
|
93
|
+
### 3. Run your agent
|
|
88
94
|
|
|
89
95
|
```bash
|
|
90
96
|
python my_agent.py
|
|
91
97
|
# agent_trace.jsonl is written
|
|
92
98
|
```
|
|
93
99
|
|
|
94
|
-
|
|
100
|
+
### 4. Diagnose the run
|
|
95
101
|
|
|
96
102
|
```bash
|
|
97
103
|
cctx autopsy agent_trace.jsonl
|
|
@@ -99,8 +105,71 @@ cctx autopsy agent_trace.jsonl
|
|
|
99
105
|
|
|
100
106
|
`cctx autopsy` picks up the OTLP format automatically — no flags needed.
|
|
101
107
|
|
|
102
|
-
|
|
108
|
+
### Notes
|
|
103
109
|
|
|
104
110
|
- If your trace file contains multiple runs, cctx diagnoses the first trace by trace ID.
|
|
105
111
|
- Token costs shown in the autopsy are estimates; cctx does not call the OpenAI API.
|
|
106
112
|
- The exact `set_trace_processors` API varies by SDK version — check the `openai-agents` changelog if the import above doesn't work.
|
|
113
|
+
|
|
114
|
+
---
|
|
115
|
+
|
|
116
|
+
## LangGraph
|
|
117
|
+
|
|
118
|
+
LangGraph emits `gen_ai.*` spans via the `opentelemetry-instrumentation-langchain` package from Traceloop. Pair it with the same `FileSpanExporter` above to write traces cctx can read.
|
|
119
|
+
|
|
120
|
+
### 1. Install dependencies
|
|
121
|
+
|
|
122
|
+
```bash
|
|
123
|
+
pip install cctx-cli opentelemetry-sdk opentelemetry-instrumentation-langchain
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
### 2. Export traces to a local file
|
|
127
|
+
|
|
128
|
+
Copy the `FileSpanExporter` and `_otlp_value` definitions from the OpenAI Agents SDK section above, then add the LangChain instrumentor before your graph runs:
|
|
129
|
+
|
|
130
|
+
```python
|
|
131
|
+
from opentelemetry.sdk.trace import TracerProvider
|
|
132
|
+
from opentelemetry.sdk.trace.export import BatchSpanProcessor
|
|
133
|
+
from opentelemetry.instrumentation.langchain import LangchainInstrumentor
|
|
134
|
+
|
|
135
|
+
provider = TracerProvider()
|
|
136
|
+
provider.add_span_processor(BatchSpanProcessor(FileSpanExporter("agent_trace.jsonl")))
|
|
137
|
+
|
|
138
|
+
LangchainInstrumentor().instrument(tracer_provider=provider)
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
### 3. Run your graph
|
|
142
|
+
|
|
143
|
+
```python
|
|
144
|
+
from langgraph.graph import StateGraph, END
|
|
145
|
+
# ... build graph ...
|
|
146
|
+
|
|
147
|
+
result = graph.invoke({"messages": [HumanMessage(content="...")]})
|
|
148
|
+
# agent_trace.jsonl is written
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
### 4. Diagnose the run
|
|
152
|
+
|
|
153
|
+
```bash
|
|
154
|
+
cctx autopsy agent_trace.jsonl
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
### Notes
|
|
158
|
+
|
|
159
|
+
- `opentelemetry-instrumentation-langchain` is maintained by [Traceloop](https://github.com/traceloop/openllmetry). It emits `gen_ai.usage.input_tokens`, `gen_ai.request.model`, and other attributes cctx maps to its canonical model.
|
|
160
|
+
- LangGraph's `recursion_limit` (default 25) governs step count, not agent nesting depth. cctx handles arbitrary span depth.
|
|
161
|
+
- Token costs are estimates; cctx does not call the OpenAI or Anthropic APIs during analysis.
|
|
162
|
+
|
|
163
|
+
---
|
|
164
|
+
|
|
165
|
+
## Other frameworks
|
|
166
|
+
|
|
167
|
+
Any framework instrumented with `gen_ai.*` semantic conventions works. The minimum attributes cctx needs per span:
|
|
168
|
+
|
|
169
|
+
| Span type | Required attributes |
|
|
170
|
+
|---|---|
|
|
171
|
+
| Agent span (`run_agent`) | `gen_ai.operation.name = "run_agent"` |
|
|
172
|
+
| LLM call (`chat`) | `gen_ai.operation.name = "chat"`, `gen_ai.usage.input_tokens`, `gen_ai.usage.output_tokens`, `gen_ai.request.model` |
|
|
173
|
+
| Tool call (`invoke_function`) | `gen_ai.operation.name = "invoke_function"`, `gen_ai.tool.name` |
|
|
174
|
+
|
|
175
|
+
Spans are linked via `parentSpanId`. Use the `FileSpanExporter` above (or any OTLP JSON exporter that writes one `resourceSpans` batch per line) to produce a file cctx can read.
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "cctx-cli"
|
|
7
|
-
version = "1.
|
|
7
|
+
version = "1.13.0"
|
|
8
8
|
description = "Diagnose Claude Code sessions — find what went wrong, what it cost, and what to add to CLAUDE.md"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.10"
|