cctx-cli 1.10.0__tar.gz → 1.12.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.10.0 → cctx_cli-1.12.0}/CHANGELOG.md +79 -0
- {cctx_cli-1.10.0 → cctx_cli-1.12.0}/PKG-INFO +1 -1
- {cctx_cli-1.10.0 → cctx_cli-1.12.0}/cctx/__init__.py +1 -1
- {cctx_cli-1.10.0 → cctx_cli-1.12.0}/cctx/cli.py +123 -3
- cctx_cli-1.12.0/cctx/hook_installer.py +101 -0
- cctx_cli-1.12.0/cctx/parsers/otel.py +346 -0
- cctx_cli-1.12.0/docs/quickstart-openai-agents.md +106 -0
- cctx_cli-1.12.0/docs/superpowers/plans/2026-06-19-otel-parser.md +1215 -0
- cctx_cli-1.12.0/docs/superpowers/specs/2026-06-19-otel-parser-design.md +115 -0
- {cctx_cli-1.10.0 → cctx_cli-1.12.0}/pyproject.toml +1 -1
- cctx_cli-1.12.0/tests/fixtures/otel_fanout.jsonl +1 -0
- cctx_cli-1.12.0/tests/fixtures/otel_handoff.jsonl +1 -0
- cctx_cli-1.12.0/tests/test_init.py +323 -0
- cctx_cli-1.12.0/tests/test_otel_parser.py +350 -0
- {cctx_cli-1.10.0 → cctx_cli-1.12.0}/.github/workflows/ci.yml +0 -0
- {cctx_cli-1.10.0 → cctx_cli-1.12.0}/.github/workflows/publish.yml +0 -0
- {cctx_cli-1.10.0 → cctx_cli-1.12.0}/.github/workflows/release.yml +0 -0
- {cctx_cli-1.10.0 → cctx_cli-1.12.0}/.gitignore +0 -0
- {cctx_cli-1.10.0 → cctx_cli-1.12.0}/CLAUDE.md +0 -0
- {cctx_cli-1.10.0 → cctx_cli-1.12.0}/DESIGN.md +0 -0
- {cctx_cli-1.10.0 → cctx_cli-1.12.0}/PRODUCT.md +0 -0
- {cctx_cli-1.10.0 → cctx_cli-1.12.0}/README.md +0 -0
- {cctx_cli-1.10.0 → cctx_cli-1.12.0}/action.yml +0 -0
- {cctx_cli-1.10.0 → cctx_cli-1.12.0}/cctx/agents.py +0 -0
- {cctx_cli-1.10.0 → cctx_cli-1.12.0}/cctx/diagnostician/__init__.py +0 -0
- {cctx_cli-1.10.0 → cctx_cli-1.12.0}/cctx/diagnostician/aggregate.py +0 -0
- {cctx_cli-1.10.0 → cctx_cli-1.12.0}/cctx/diagnostician/inflection.py +0 -0
- {cctx_cli-1.10.0 → cctx_cli-1.12.0}/cctx/diagnostician/patterns/__init__.py +0 -0
- {cctx_cli-1.10.0 → cctx_cli-1.12.0}/cctx/diagnostician/patterns/dead_end.py +0 -0
- {cctx_cli-1.10.0 → cctx_cli-1.12.0}/cctx/diagnostician/patterns/fan_out.py +0 -0
- {cctx_cli-1.10.0 → cctx_cli-1.12.0}/cctx/diagnostician/patterns/project_specific.py +0 -0
- {cctx_cli-1.10.0 → cctx_cli-1.12.0}/cctx/diagnostician/patterns/retry_loop.py +0 -0
- {cctx_cli-1.10.0 → cctx_cli-1.12.0}/cctx/diagnostician/patterns/scope_creep.py +0 -0
- {cctx_cli-1.10.0 → cctx_cli-1.12.0}/cctx/diagnostician/patterns/stale_context.py +0 -0
- {cctx_cli-1.10.0 → cctx_cli-1.12.0}/cctx/diagnostician/patterns/tool_thrash.py +0 -0
- {cctx_cli-1.10.0 → cctx_cli-1.12.0}/cctx/discovery.py +0 -0
- {cctx_cli-1.10.0 → cctx_cli-1.12.0}/cctx/exporters/__init__.py +0 -0
- {cctx_cli-1.10.0 → cctx_cli-1.12.0}/cctx/exporters/csv.py +0 -0
- {cctx_cli-1.10.0 → cctx_cli-1.12.0}/cctx/exporters/json.py +0 -0
- {cctx_cli-1.10.0 → cctx_cli-1.12.0}/cctx/exporters/jsonl.py +0 -0
- {cctx_cli-1.10.0 → cctx_cli-1.12.0}/cctx/harvest.py +0 -0
- {cctx_cli-1.10.0 → cctx_cli-1.12.0}/cctx/models.py +0 -0
- {cctx_cli-1.10.0 → cctx_cli-1.12.0}/cctx/parsers/__init__.py +0 -0
- {cctx_cli-1.10.0 → cctx_cli-1.12.0}/cctx/parsers/claude_code.py +0 -0
- {cctx_cli-1.10.0 → cctx_cli-1.12.0}/cctx/pricing.py +0 -0
- {cctx_cli-1.10.0 → cctx_cli-1.12.0}/cctx/recommender/__init__.py +0 -0
- {cctx_cli-1.10.0 → cctx_cli-1.12.0}/cctx/recommender/claude_md.py +0 -0
- {cctx_cli-1.10.0 → cctx_cli-1.12.0}/cctx/recommender/evidence.py +0 -0
- {cctx_cli-1.10.0 → cctx_cli-1.12.0}/cctx/renderers/__init__.py +0 -0
- {cctx_cli-1.10.0 → cctx_cli-1.12.0}/cctx/renderers/github.py +0 -0
- {cctx_cli-1.10.0 → cctx_cli-1.12.0}/cctx/renderers/report.py +0 -0
- {cctx_cli-1.10.0 → cctx_cli-1.12.0}/cctx/renderers/templates/autopsy.html.j2 +0 -0
- {cctx_cli-1.10.0 → cctx_cli-1.12.0}/cctx/renderers/terminal.py +0 -0
- {cctx_cli-1.10.0 → cctx_cli-1.12.0}/cctx/renderers/trace_tui.py +0 -0
- {cctx_cli-1.10.0 → cctx_cli-1.12.0}/cctx/tokenizer.py +0 -0
- {cctx_cli-1.10.0 → cctx_cli-1.12.0}/cctx/watcher.py +0 -0
- {cctx_cli-1.10.0 → cctx_cli-1.12.0}/cctx-project-brief.md +0 -0
- {cctx_cli-1.10.0 → cctx_cli-1.12.0}/demo.gif +0 -0
- {cctx_cli-1.10.0 → cctx_cli-1.12.0}/demo.tape +0 -0
- {cctx_cli-1.10.0 → cctx_cli-1.12.0}/docs/health-reviews/2026-05-15-deep-review-summary.md +0 -0
- {cctx_cli-1.10.0 → cctx_cli-1.12.0}/docs/health-reviews/2026-05-15-health-review.md +0 -0
- {cctx_cli-1.10.0 → cctx_cli-1.12.0}/docs/product-reviews/2026-05-15-product-review.md +0 -0
- {cctx_cli-1.10.0 → cctx_cli-1.12.0}/docs/product-reviews/2026-06-09-product-review.md +0 -0
- {cctx_cli-1.10.0 → cctx_cli-1.12.0}/docs/superpowers/plans/2026-05-12-claude-code-parser.md +0 -0
- {cctx_cli-1.10.0 → cctx_cli-1.12.0}/docs/superpowers/plans/2026-05-14-autopsy-v0.md +0 -0
- {cctx_cli-1.10.0 → cctx_cli-1.12.0}/docs/superpowers/plans/2026-05-16-readme-pypi-release.md +0 -0
- {cctx_cli-1.10.0 → cctx_cli-1.12.0}/docs/superpowers/plans/2026-05-17-harvest-check-depth.md +0 -0
- {cctx_cli-1.10.0 → cctx_cli-1.12.0}/docs/superpowers/plans/2026-05-17-project-pattern-detection.md +0 -0
- {cctx_cli-1.10.0 → cctx_cli-1.12.0}/docs/superpowers/plans/2026-05-19-claude-agents-live-integration.md +0 -0
- {cctx_cli-1.10.0 → cctx_cli-1.12.0}/docs/superpowers/specs/2026-05-12-claude-code-parser-design.md +0 -0
- {cctx_cli-1.10.0 → cctx_cli-1.12.0}/docs/superpowers/specs/2026-05-14-autopsy-design.md +0 -0
- {cctx_cli-1.10.0 → cctx_cli-1.12.0}/docs/superpowers/specs/2026-05-14-harvest-design.md +0 -0
- {cctx_cli-1.10.0 → cctx_cli-1.12.0}/docs/superpowers/specs/2026-05-14-trace-tui-design.md +0 -0
- {cctx_cli-1.10.0 → cctx_cli-1.12.0}/docs/superpowers/specs/2026-05-16-readme-pypi-release-design.md +0 -0
- {cctx_cli-1.10.0 → cctx_cli-1.12.0}/docs/superpowers/specs/2026-05-17-harvest-check-depth-design.md +0 -0
- {cctx_cli-1.10.0 → cctx_cli-1.12.0}/docs/superpowers/specs/2026-05-17-project-pattern-detection-design.md +0 -0
- {cctx_cli-1.10.0 → cctx_cli-1.12.0}/docs/superpowers/specs/2026-05-19-claude-agents-live-integration-design.md +0 -0
- {cctx_cli-1.10.0 → cctx_cli-1.12.0}/docs/superpowers/specs/2026-06-09-cross-agent-emit-design.md +0 -0
- {cctx_cli-1.10.0 → cctx_cli-1.12.0}/tests/__init__.py +0 -0
- {cctx_cli-1.10.0 → cctx_cli-1.12.0}/tests/conftest.py +0 -0
- {cctx_cli-1.10.0 → cctx_cli-1.12.0}/tests/diagnostician/__init__.py +0 -0
- {cctx_cli-1.10.0 → cctx_cli-1.12.0}/tests/diagnostician/conftest.py +0 -0
- {cctx_cli-1.10.0 → cctx_cli-1.12.0}/tests/diagnostician/test_dead_end.py +0 -0
- {cctx_cli-1.10.0 → cctx_cli-1.12.0}/tests/diagnostician/test_inflection.py +0 -0
- {cctx_cli-1.10.0 → cctx_cli-1.12.0}/tests/diagnostician/test_orchestrator.py +0 -0
- {cctx_cli-1.10.0 → cctx_cli-1.12.0}/tests/diagnostician/test_project_specific.py +0 -0
- {cctx_cli-1.10.0 → cctx_cli-1.12.0}/tests/diagnostician/test_retry_loop.py +0 -0
- {cctx_cli-1.10.0 → cctx_cli-1.12.0}/tests/diagnostician/test_scope_creep.py +0 -0
- {cctx_cli-1.10.0 → cctx_cli-1.12.0}/tests/diagnostician/test_stale_context.py +0 -0
- {cctx_cli-1.10.0 → cctx_cli-1.12.0}/tests/diagnostician/test_tool_thrash.py +0 -0
- {cctx_cli-1.10.0 → cctx_cli-1.12.0}/tests/exporters/__init__.py +0 -0
- {cctx_cli-1.10.0 → cctx_cli-1.12.0}/tests/exporters/test_csv.py +0 -0
- {cctx_cli-1.10.0 → cctx_cli-1.12.0}/tests/exporters/test_jsonl.py +0 -0
- {cctx_cli-1.10.0 → cctx_cli-1.12.0}/tests/fixtures/claude_code/README.md +0 -0
- {cctx_cli-1.10.0 → cctx_cli-1.12.0}/tests/fixtures/claude_code/short-clean/short-clean.jsonl +0 -0
- {cctx_cli-1.10.0 → cctx_cli-1.12.0}/tests/fixtures/claude_code/with-attachments/with-attachments/subagents/agent-a0b4c2cf1dde0ca56.meta.json +0 -0
- {cctx_cli-1.10.0 → cctx_cli-1.12.0}/tests/fixtures/claude_code/with-attachments/with-attachments/subagents/agent-a116ae34b1b09c332.meta.json +0 -0
- {cctx_cli-1.10.0 → cctx_cli-1.12.0}/tests/fixtures/claude_code/with-attachments/with-attachments/subagents/agent-a1c4c417b35658c9e.meta.json +0 -0
- {cctx_cli-1.10.0 → cctx_cli-1.12.0}/tests/fixtures/claude_code/with-attachments/with-attachments/subagents/agent-a1e41a901de38f1b5.meta.json +0 -0
- {cctx_cli-1.10.0 → cctx_cli-1.12.0}/tests/fixtures/claude_code/with-attachments/with-attachments/subagents/agent-a338f8d0c74612a24.meta.json +0 -0
- {cctx_cli-1.10.0 → cctx_cli-1.12.0}/tests/fixtures/claude_code/with-attachments/with-attachments/subagents/agent-a34f6f3c0e7094186.meta.json +0 -0
- {cctx_cli-1.10.0 → cctx_cli-1.12.0}/tests/fixtures/claude_code/with-attachments/with-attachments/subagents/agent-a5a5a0cff4d13308b.meta.json +0 -0
- {cctx_cli-1.10.0 → cctx_cli-1.12.0}/tests/fixtures/claude_code/with-attachments/with-attachments/subagents/agent-a6b0a3da6a0484db5.meta.json +0 -0
- {cctx_cli-1.10.0 → cctx_cli-1.12.0}/tests/fixtures/claude_code/with-attachments/with-attachments/subagents/agent-a7f73f1790b02cde5.meta.json +0 -0
- {cctx_cli-1.10.0 → cctx_cli-1.12.0}/tests/fixtures/claude_code/with-attachments/with-attachments/subagents/agent-a7f7c17c38a9d8788.meta.json +0 -0
- {cctx_cli-1.10.0 → cctx_cli-1.12.0}/tests/fixtures/claude_code/with-attachments/with-attachments/subagents/agent-a853259e2cd7bbe8a.meta.json +0 -0
- {cctx_cli-1.10.0 → cctx_cli-1.12.0}/tests/fixtures/claude_code/with-attachments/with-attachments/subagents/agent-a8d9aedb0d0c6e12d.meta.json +0 -0
- {cctx_cli-1.10.0 → cctx_cli-1.12.0}/tests/fixtures/claude_code/with-attachments/with-attachments/subagents/agent-aa778bc1d59e4a441.meta.json +0 -0
- {cctx_cli-1.10.0 → cctx_cli-1.12.0}/tests/fixtures/claude_code/with-attachments/with-attachments/subagents/agent-aba869dedee4a12ba.meta.json +0 -0
- {cctx_cli-1.10.0 → cctx_cli-1.12.0}/tests/fixtures/claude_code/with-attachments/with-attachments/subagents/agent-ada2746d9774b94db.meta.json +0 -0
- {cctx_cli-1.10.0 → cctx_cli-1.12.0}/tests/fixtures/claude_code/with-attachments/with-attachments/subagents/agent-aea0132068c64d2dd.meta.json +0 -0
- {cctx_cli-1.10.0 → cctx_cli-1.12.0}/tests/fixtures/claude_code/with-attachments/with-attachments/subagents/agent-aea215eff50874d5f.meta.json +0 -0
- {cctx_cli-1.10.0 → cctx_cli-1.12.0}/tests/fixtures/claude_code/with-attachments/with-attachments/subagents/agent-afee21f2b3852a4a0.meta.json +0 -0
- {cctx_cli-1.10.0 → cctx_cli-1.12.0}/tests/fixtures/claude_code/with-attachments/with-attachments.jsonl +0 -0
- {cctx_cli-1.10.0 → cctx_cli-1.12.0}/tests/fixtures/claude_code/with-compaction/with-compaction/subagents/agent-a051d9c9a6b2f5cc3.jsonl +0 -0
- {cctx_cli-1.10.0 → cctx_cli-1.12.0}/tests/fixtures/claude_code/with-compaction/with-compaction/subagents/agent-a051d9c9a6b2f5cc3.meta.json +0 -0
- {cctx_cli-1.10.0 → cctx_cli-1.12.0}/tests/fixtures/claude_code/with-compaction/with-compaction/subagents/agent-a171f16f4e65cfe75.jsonl +0 -0
- {cctx_cli-1.10.0 → cctx_cli-1.12.0}/tests/fixtures/claude_code/with-compaction/with-compaction/subagents/agent-a171f16f4e65cfe75.meta.json +0 -0
- {cctx_cli-1.10.0 → cctx_cli-1.12.0}/tests/fixtures/claude_code/with-compaction/with-compaction/subagents/agent-a1b77fea2c0a2269b.jsonl +0 -0
- {cctx_cli-1.10.0 → cctx_cli-1.12.0}/tests/fixtures/claude_code/with-compaction/with-compaction/subagents/agent-a1b77fea2c0a2269b.meta.json +0 -0
- {cctx_cli-1.10.0 → cctx_cli-1.12.0}/tests/fixtures/claude_code/with-compaction/with-compaction/subagents/agent-a20da4c01a54acca8.jsonl +0 -0
- {cctx_cli-1.10.0 → cctx_cli-1.12.0}/tests/fixtures/claude_code/with-compaction/with-compaction/subagents/agent-a20da4c01a54acca8.meta.json +0 -0
- {cctx_cli-1.10.0 → cctx_cli-1.12.0}/tests/fixtures/claude_code/with-compaction/with-compaction/subagents/agent-a3c82739b1383fb14.jsonl +0 -0
- {cctx_cli-1.10.0 → cctx_cli-1.12.0}/tests/fixtures/claude_code/with-compaction/with-compaction/subagents/agent-a3c82739b1383fb14.meta.json +0 -0
- {cctx_cli-1.10.0 → cctx_cli-1.12.0}/tests/fixtures/claude_code/with-compaction/with-compaction/subagents/agent-a49e8539611c5fe12.jsonl +0 -0
- {cctx_cli-1.10.0 → cctx_cli-1.12.0}/tests/fixtures/claude_code/with-compaction/with-compaction/subagents/agent-a49e8539611c5fe12.meta.json +0 -0
- {cctx_cli-1.10.0 → cctx_cli-1.12.0}/tests/fixtures/claude_code/with-compaction/with-compaction/subagents/agent-a7bb58f3fff2b3e8d.jsonl +0 -0
- {cctx_cli-1.10.0 → cctx_cli-1.12.0}/tests/fixtures/claude_code/with-compaction/with-compaction/subagents/agent-a7bb58f3fff2b3e8d.meta.json +0 -0
- {cctx_cli-1.10.0 → cctx_cli-1.12.0}/tests/fixtures/claude_code/with-compaction/with-compaction/subagents/agent-a92b48c0331195aac.jsonl +0 -0
- {cctx_cli-1.10.0 → cctx_cli-1.12.0}/tests/fixtures/claude_code/with-compaction/with-compaction/subagents/agent-a92b48c0331195aac.meta.json +0 -0
- {cctx_cli-1.10.0 → cctx_cli-1.12.0}/tests/fixtures/claude_code/with-compaction/with-compaction/subagents/agent-ab96c4264099694a9.jsonl +0 -0
- {cctx_cli-1.10.0 → cctx_cli-1.12.0}/tests/fixtures/claude_code/with-compaction/with-compaction/subagents/agent-ab96c4264099694a9.meta.json +0 -0
- {cctx_cli-1.10.0 → cctx_cli-1.12.0}/tests/fixtures/claude_code/with-compaction/with-compaction/subagents/agent-acb2895c5e34ffec0.jsonl +0 -0
- {cctx_cli-1.10.0 → cctx_cli-1.12.0}/tests/fixtures/claude_code/with-compaction/with-compaction/subagents/agent-acb2895c5e34ffec0.meta.json +0 -0
- {cctx_cli-1.10.0 → cctx_cli-1.12.0}/tests/fixtures/claude_code/with-compaction/with-compaction/subagents/agent-adb2302769938fb3f.jsonl +0 -0
- {cctx_cli-1.10.0 → cctx_cli-1.12.0}/tests/fixtures/claude_code/with-compaction/with-compaction/subagents/agent-adb2302769938fb3f.meta.json +0 -0
- {cctx_cli-1.10.0 → cctx_cli-1.12.0}/tests/fixtures/claude_code/with-compaction/with-compaction/subagents/agent-ae585eca15cb93b9c.jsonl +0 -0
- {cctx_cli-1.10.0 → cctx_cli-1.12.0}/tests/fixtures/claude_code/with-compaction/with-compaction/subagents/agent-ae585eca15cb93b9c.meta.json +0 -0
- {cctx_cli-1.10.0 → cctx_cli-1.12.0}/tests/fixtures/claude_code/with-compaction/with-compaction/subagents/agent-aec9c917feb903d67.jsonl +0 -0
- {cctx_cli-1.10.0 → cctx_cli-1.12.0}/tests/fixtures/claude_code/with-compaction/with-compaction/subagents/agent-aec9c917feb903d67.meta.json +0 -0
- {cctx_cli-1.10.0 → cctx_cli-1.12.0}/tests/fixtures/claude_code/with-compaction/with-compaction.jsonl +0 -0
- {cctx_cli-1.10.0 → cctx_cli-1.12.0}/tests/fixtures/claude_code/with-subagents/with-subagents/subagents/agent-a1a3a21aeb76bb0a9.jsonl +0 -0
- {cctx_cli-1.10.0 → cctx_cli-1.12.0}/tests/fixtures/claude_code/with-subagents/with-subagents/subagents/agent-a1a3a21aeb76bb0a9.meta.json +0 -0
- {cctx_cli-1.10.0 → cctx_cli-1.12.0}/tests/fixtures/claude_code/with-subagents/with-subagents/subagents/agent-aaa1d6ecc05a78442.jsonl +0 -0
- {cctx_cli-1.10.0 → cctx_cli-1.12.0}/tests/fixtures/claude_code/with-subagents/with-subagents/subagents/agent-aaa1d6ecc05a78442.meta.json +0 -0
- {cctx_cli-1.10.0 → cctx_cli-1.12.0}/tests/fixtures/claude_code/with-subagents/with-subagents/subagents/agent-af3c545ccd30036d2.jsonl +0 -0
- {cctx_cli-1.10.0 → cctx_cli-1.12.0}/tests/fixtures/claude_code/with-subagents/with-subagents/subagents/agent-af3c545ccd30036d2.meta.json +0 -0
- {cctx_cli-1.10.0 → cctx_cli-1.12.0}/tests/fixtures/claude_code/with-subagents/with-subagents/tool-results/btwp2bzro.txt +0 -0
- {cctx_cli-1.10.0 → cctx_cli-1.12.0}/tests/fixtures/claude_code/with-subagents/with-subagents/tool-results/byqjbgy4b.txt +0 -0
- {cctx_cli-1.10.0 → cctx_cli-1.12.0}/tests/fixtures/claude_code/with-subagents/with-subagents.jsonl +0 -0
- {cctx_cli-1.10.0 → cctx_cli-1.12.0}/tests/fixtures/claude_code/with-tool-results/with-tool-results/tool-results/bosbkda0h.txt +0 -0
- {cctx_cli-1.10.0 → cctx_cli-1.12.0}/tests/fixtures/claude_code/with-tool-results/with-tool-results.jsonl +0 -0
- {cctx_cli-1.10.0 → cctx_cli-1.12.0}/tests/fixtures/scrub.py +0 -0
- {cctx_cli-1.10.0 → cctx_cli-1.12.0}/tests/fixtures/synthetic/bookkeeping_only.jsonl +0 -0
- {cctx_cli-1.10.0 → cctx_cli-1.12.0}/tests/fixtures/synthetic/malformed_middle.jsonl +0 -0
- {cctx_cli-1.10.0 → cctx_cli-1.12.0}/tests/fixtures/synthetic/truncated_final_line.jsonl +0 -0
- {cctx_cli-1.10.0 → cctx_cli-1.12.0}/tests/fixtures/synthetic/unknown_attachment_shape.jsonl +0 -0
- {cctx_cli-1.10.0 → cctx_cli-1.12.0}/tests/fixtures/synthetic/unknown_type.jsonl +0 -0
- {cctx_cli-1.10.0 → cctx_cli-1.12.0}/tests/parsers/__init__.py +0 -0
- {cctx_cli-1.10.0 → cctx_cli-1.12.0}/tests/parsers/test_claude_code.py +0 -0
- {cctx_cli-1.10.0 → cctx_cli-1.12.0}/tests/parsers/test_claude_code_integration.py +0 -0
- {cctx_cli-1.10.0 → cctx_cli-1.12.0}/tests/recommender/__init__.py +0 -0
- {cctx_cli-1.10.0 → cctx_cli-1.12.0}/tests/recommender/test_claude_md.py +0 -0
- {cctx_cli-1.10.0 → cctx_cli-1.12.0}/tests/recommender/test_evidence.py +0 -0
- {cctx_cli-1.10.0 → cctx_cli-1.12.0}/tests/renderers/__init__.py +0 -0
- {cctx_cli-1.10.0 → cctx_cli-1.12.0}/tests/renderers/test_report.py +0 -0
- {cctx_cli-1.10.0 → cctx_cli-1.12.0}/tests/renderers/test_terminal_renderer_full.py +0 -0
- {cctx_cli-1.10.0 → cctx_cli-1.12.0}/tests/test_agents.py +0 -0
- {cctx_cli-1.10.0 → cctx_cli-1.12.0}/tests/test_aggregate.py +0 -0
- {cctx_cli-1.10.0 → cctx_cli-1.12.0}/tests/test_cli.py +0 -0
- {cctx_cli-1.10.0 → cctx_cli-1.12.0}/tests/test_cli_export.py +0 -0
- {cctx_cli-1.10.0 → cctx_cli-1.12.0}/tests/test_diagnostician_subagents.py +0 -0
- {cctx_cli-1.10.0 → cctx_cli-1.12.0}/tests/test_discovery.py +0 -0
- {cctx_cli-1.10.0 → cctx_cli-1.12.0}/tests/test_efficacy.py +0 -0
- {cctx_cli-1.10.0 → cctx_cli-1.12.0}/tests/test_fanout_classifier.py +0 -0
- {cctx_cli-1.10.0 → cctx_cli-1.12.0}/tests/test_github_summary.py +0 -0
- {cctx_cli-1.10.0 → cctx_cli-1.12.0}/tests/test_harvest.py +0 -0
- {cctx_cli-1.10.0 → cctx_cli-1.12.0}/tests/test_harvest_check.py +0 -0
- {cctx_cli-1.10.0 → cctx_cli-1.12.0}/tests/test_harvest_emit.py +0 -0
- {cctx_cli-1.10.0 → cctx_cli-1.12.0}/tests/test_models.py +0 -0
- {cctx_cli-1.10.0 → cctx_cli-1.12.0}/tests/test_models_project_pattern.py +0 -0
- {cctx_cli-1.10.0 → cctx_cli-1.12.0}/tests/test_recommender.py +0 -0
- {cctx_cli-1.10.0 → cctx_cli-1.12.0}/tests/test_smoke.py +0 -0
- {cctx_cli-1.10.0 → cctx_cli-1.12.0}/tests/test_terminal_renderer.py +0 -0
- {cctx_cli-1.10.0 → cctx_cli-1.12.0}/tests/test_tokenizer.py +0 -0
- {cctx_cli-1.10.0 → cctx_cli-1.12.0}/tests/test_trace_tui.py +0 -0
- {cctx_cli-1.10.0 → cctx_cli-1.12.0}/tests/test_watcher.py +0 -0
|
@@ -2,6 +2,85 @@
|
|
|
2
2
|
|
|
3
3
|
<!-- version list -->
|
|
4
4
|
|
|
5
|
+
## v1.12.0 (2026-06-20)
|
|
6
|
+
|
|
7
|
+
### Bug Fixes
|
|
8
|
+
|
|
9
|
+
- Otel parser — per-trace warnings, multiple-root warning, full span list for subagent turns
|
|
10
|
+
([#116](https://github.com/jacquardlabs/cctx/pull/116),
|
|
11
|
+
[`a6b56b6`](https://github.com/jacquardlabs/cctx/commit/a6b56b61ccff5493dd26f375a698eb59aff62272))
|
|
12
|
+
|
|
13
|
+
- Ruff F401 + I001 in test_otel_parser.py ([#116](https://github.com/jacquardlabs/cctx/pull/116),
|
|
14
|
+
[`a6b56b6`](https://github.com/jacquardlabs/cctx/commit/a6b56b61ccff5493dd26f375a698eb59aff62272))
|
|
15
|
+
|
|
16
|
+
### Documentation
|
|
17
|
+
|
|
18
|
+
- OTEL parser design spec — OpenAI Agents SDK support via parsers/otel.py
|
|
19
|
+
([#116](https://github.com/jacquardlabs/cctx/pull/116),
|
|
20
|
+
[`a6b56b6`](https://github.com/jacquardlabs/cctx/commit/a6b56b61ccff5493dd26f375a698eb59aff62272))
|
|
21
|
+
|
|
22
|
+
- OTEL parser implementation plan ([#116](https://github.com/jacquardlabs/cctx/pull/116),
|
|
23
|
+
[`a6b56b6`](https://github.com/jacquardlabs/cctx/commit/a6b56b61ccff5493dd26f375a698eb59aff62272))
|
|
24
|
+
|
|
25
|
+
- Quickstart guide for OpenAI Agents SDK + cctx OTEL integration
|
|
26
|
+
([#116](https://github.com/jacquardlabs/cctx/pull/116),
|
|
27
|
+
[`a6b56b6`](https://github.com/jacquardlabs/cctx/commit/a6b56b61ccff5493dd26f375a698eb59aff62272))
|
|
28
|
+
|
|
29
|
+
### Features
|
|
30
|
+
|
|
31
|
+
- _detect_source() — auto-detect Claude Code vs OTEL trace format
|
|
32
|
+
([#116](https://github.com/jacquardlabs/cctx/pull/116),
|
|
33
|
+
[`a6b56b6`](https://github.com/jacquardlabs/cctx/commit/a6b56b61ccff5493dd26f375a698eb59aff62272))
|
|
34
|
+
|
|
35
|
+
- OpenAI Agents SDK support via OTEL parser ([#116](https://github.com/jacquardlabs/cctx/pull/116),
|
|
36
|
+
[`a6b56b6`](https://github.com/jacquardlabs/cctx/commit/a6b56b61ccff5493dd26f375a698eb59aff62272))
|
|
37
|
+
|
|
38
|
+
- OTEL parser skeleton — span loading and session trace construction
|
|
39
|
+
([#116](https://github.com/jacquardlabs/cctx/pull/116),
|
|
40
|
+
[`a6b56b6`](https://github.com/jacquardlabs/cctx/commit/a6b56b61ccff5493dd26f375a698eb59aff62272))
|
|
41
|
+
|
|
42
|
+
- Wire OTEL auto-detection into autopsy — cctx autopsy <otel.jsonl> just works
|
|
43
|
+
([#116](https://github.com/jacquardlabs/cctx/pull/116),
|
|
44
|
+
[`a6b56b6`](https://github.com/jacquardlabs/cctx/commit/a6b56b61ccff5493dd26f375a698eb59aff62272))
|
|
45
|
+
|
|
46
|
+
### Testing
|
|
47
|
+
|
|
48
|
+
- Add OTLP JSONL fixtures for otel parser (handoff + fanout)
|
|
49
|
+
([#116](https://github.com/jacquardlabs/cctx/pull/116),
|
|
50
|
+
[`a6b56b6`](https://github.com/jacquardlabs/cctx/commit/a6b56b61ccff5493dd26f375a698eb59aff62272))
|
|
51
|
+
|
|
52
|
+
- OTEL parser error handling — malformed JSON, unknown spans, empty file
|
|
53
|
+
([#116](https://github.com/jacquardlabs/cctx/pull/116),
|
|
54
|
+
[`a6b56b6`](https://github.com/jacquardlabs/cctx/commit/a6b56b61ccff5493dd26f375a698eb59aff62272))
|
|
55
|
+
|
|
56
|
+
- Verify child AgentSpan → subagents mapping (handoff + fan-out)
|
|
57
|
+
([#116](https://github.com/jacquardlabs/cctx/pull/116),
|
|
58
|
+
[`a6b56b6`](https://github.com/jacquardlabs/cctx/commit/a6b56b61ccff5493dd26f375a698eb59aff62272))
|
|
59
|
+
|
|
60
|
+
- Verify FunctionSpan → ToolUse + ToolResult mapping
|
|
61
|
+
([#116](https://github.com/jacquardlabs/cctx/pull/116),
|
|
62
|
+
[`a6b56b6`](https://github.com/jacquardlabs/cctx/commit/a6b56b61ccff5493dd26f375a698eb59aff62272))
|
|
63
|
+
|
|
64
|
+
- Verify GenerationSpan → Turn mapping for OTEL parser
|
|
65
|
+
([#116](https://github.com/jacquardlabs/cctx/pull/116),
|
|
66
|
+
[`a6b56b6`](https://github.com/jacquardlabs/cctx/commit/a6b56b61ccff5493dd26f375a698eb59aff62272))
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
## v1.11.0 (2026-06-11)
|
|
70
|
+
|
|
71
|
+
### Bug Fixes
|
|
72
|
+
|
|
73
|
+
- Ruff E501 + I001 in test_init.py (line length + import order)
|
|
74
|
+
([#113](https://github.com/jacquardlabs/cctx/pull/113),
|
|
75
|
+
[`a04426e`](https://github.com/jacquardlabs/cctx/commit/a04426ef62cfeaa4ba7ea392bdf0dd49975cffbb))
|
|
76
|
+
|
|
77
|
+
### Features
|
|
78
|
+
|
|
79
|
+
- Cctx init — SessionEnd hook installer + autopsy --quiet (closes #92)
|
|
80
|
+
([#113](https://github.com/jacquardlabs/cctx/pull/113),
|
|
81
|
+
[`a04426e`](https://github.com/jacquardlabs/cctx/commit/a04426ef62cfeaa4ba7ea392bdf0dd49975cffbb))
|
|
82
|
+
|
|
83
|
+
|
|
5
84
|
## v1.10.0 (2026-06-11)
|
|
6
85
|
|
|
7
86
|
### Features
|
|
@@ -9,9 +9,11 @@ Commands:
|
|
|
9
9
|
cctx harvest <session> Apply autopsy patches to CLAUDE.md
|
|
10
10
|
cctx harvest <project> --since Cross-session harvest
|
|
11
11
|
cctx watch [project] Live waste signals during an active session
|
|
12
|
+
cctx init Install opt-in SessionEnd hook for auto-diagnostics
|
|
12
13
|
"""
|
|
13
14
|
from __future__ import annotations
|
|
14
15
|
|
|
16
|
+
import json as _json
|
|
15
17
|
from datetime import datetime, timedelta, timezone
|
|
16
18
|
from pathlib import Path
|
|
17
19
|
from typing import IO
|
|
@@ -178,6 +180,48 @@ def _render_check_findings(findings: list, target_dir: Path) -> None:
|
|
|
178
180
|
con.print(f" {badge:<6} {f.heading} {label}: {f.detail}")
|
|
179
181
|
|
|
180
182
|
|
|
183
|
+
_CLAUDE_CODE_LINE_TYPES = frozenset({
|
|
184
|
+
"user", "assistant", "system", "attachment",
|
|
185
|
+
"last-prompt", "permission-mode", "ai-title", "custom-title",
|
|
186
|
+
"queue-operation", "file-history-snapshot", "pr-link",
|
|
187
|
+
})
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
def _detect_source(path: Path) -> str:
|
|
191
|
+
"""Sniff first non-empty lines to detect trace format.
|
|
192
|
+
|
|
193
|
+
Returns "claude_code" or "otel".
|
|
194
|
+
Raises click.UsageError if the format cannot be determined.
|
|
195
|
+
"""
|
|
196
|
+
try:
|
|
197
|
+
with path.open(encoding="utf-8", errors="replace") as f:
|
|
198
|
+
for _ in range(5):
|
|
199
|
+
line = f.readline()
|
|
200
|
+
if not line:
|
|
201
|
+
break
|
|
202
|
+
line = line.strip()
|
|
203
|
+
if not line:
|
|
204
|
+
continue
|
|
205
|
+
try:
|
|
206
|
+
obj = _json.loads(line)
|
|
207
|
+
except _json.JSONDecodeError:
|
|
208
|
+
continue
|
|
209
|
+
if "resourceSpans" in obj:
|
|
210
|
+
return "otel"
|
|
211
|
+
if "traceId" in obj and "spanId" in obj:
|
|
212
|
+
return "otel"
|
|
213
|
+
line_type = obj.get("type")
|
|
214
|
+
if isinstance(line_type, str) and line_type in _CLAUDE_CODE_LINE_TYPES:
|
|
215
|
+
return "claude_code"
|
|
216
|
+
except OSError as exc:
|
|
217
|
+
raise click.UsageError(f"Cannot read file: {path}: {exc}") from exc
|
|
218
|
+
|
|
219
|
+
raise click.UsageError(
|
|
220
|
+
f"Cannot determine trace format for {path}.\n"
|
|
221
|
+
"Expected a Claude Code JSONL session file or an OTLP JSON trace export."
|
|
222
|
+
)
|
|
223
|
+
|
|
224
|
+
|
|
181
225
|
@click.group()
|
|
182
226
|
def cli() -> None:
|
|
183
227
|
"""cctx — find out why your Claude Code session went sideways."""
|
|
@@ -292,7 +336,15 @@ def ls(project: Path | None) -> None:
|
|
|
292
336
|
"json_out",
|
|
293
337
|
is_flag=True,
|
|
294
338
|
default=False,
|
|
295
|
-
help="Output diagnosis as JSON to stdout
|
|
339
|
+
help="Output diagnosis (or aggregate) as JSON to stdout.",
|
|
340
|
+
)
|
|
341
|
+
@click.option(
|
|
342
|
+
"--quiet",
|
|
343
|
+
"quiet",
|
|
344
|
+
is_flag=True,
|
|
345
|
+
default=False,
|
|
346
|
+
help="Print one verdict line only when findings exist; nothing if clean. "
|
|
347
|
+
"Designed for SessionEnd hook use (cctx init).",
|
|
296
348
|
)
|
|
297
349
|
def autopsy(
|
|
298
350
|
target: Path | None,
|
|
@@ -305,6 +357,7 @@ def autopsy(
|
|
|
305
357
|
top_n: int | None,
|
|
306
358
|
turn_num: int | None,
|
|
307
359
|
json_out: bool,
|
|
360
|
+
quiet: bool,
|
|
308
361
|
) -> None:
|
|
309
362
|
"""Diagnose a session or project directory.
|
|
310
363
|
|
|
@@ -405,10 +458,22 @@ def autopsy(
|
|
|
405
458
|
"TARGET is a directory. Use --since N for cross-session mode, "
|
|
406
459
|
"or pass a .jsonl file directly."
|
|
407
460
|
)
|
|
408
|
-
|
|
461
|
+
source = _detect_source(target)
|
|
462
|
+
if source == "otel":
|
|
463
|
+
from cctx.parsers.otel import parse_otel_file as _parse_otel_file
|
|
464
|
+
otel_traces = _parse_otel_file(target)
|
|
465
|
+
if not otel_traces:
|
|
466
|
+
raise click.UsageError(f"No traces found in {target}")
|
|
467
|
+
trace = tokenize_session(otel_traces[0])
|
|
468
|
+
else:
|
|
469
|
+
trace = tokenize_session(parse_session(target))
|
|
409
470
|
diagnosis = diagnostician.run(trace)
|
|
410
471
|
diagnosis = claude_md.generate(diagnosis)
|
|
411
|
-
if
|
|
472
|
+
if quiet:
|
|
473
|
+
if diagnosis.findings:
|
|
474
|
+
kinds = list(dict.fromkeys(f.kind.value for f in diagnosis.findings))
|
|
475
|
+
click.echo(f"{len(diagnosis.findings)} finding(s): {', '.join(kinds)}")
|
|
476
|
+
elif json_out:
|
|
412
477
|
import json as _json
|
|
413
478
|
|
|
414
479
|
from cctx.exporters.jsonl import export_diagnosis as _export_diag
|
|
@@ -729,3 +794,58 @@ def watch(target: Path | None) -> None:
|
|
|
729
794
|
"""
|
|
730
795
|
from cctx.watcher import watch as _watch
|
|
731
796
|
_watch(target)
|
|
797
|
+
|
|
798
|
+
|
|
799
|
+
@cli.command("init")
|
|
800
|
+
@click.option(
|
|
801
|
+
"--global",
|
|
802
|
+
"global_",
|
|
803
|
+
is_flag=True,
|
|
804
|
+
default=False,
|
|
805
|
+
help="Install to ~/.claude/settings.json (user scope) instead of .claude/settings.json.",
|
|
806
|
+
)
|
|
807
|
+
@click.option(
|
|
808
|
+
"--remove",
|
|
809
|
+
"remove_",
|
|
810
|
+
is_flag=True,
|
|
811
|
+
default=False,
|
|
812
|
+
help="Remove the SessionEnd hook instead of installing it.",
|
|
813
|
+
)
|
|
814
|
+
@click.option(
|
|
815
|
+
"--force",
|
|
816
|
+
is_flag=True,
|
|
817
|
+
default=False,
|
|
818
|
+
help="Reinstall even if hook is already present.",
|
|
819
|
+
)
|
|
820
|
+
def init_cmd(global_: bool, remove_: bool, force: bool) -> None:
|
|
821
|
+
"""Install an opt-in SessionEnd hook for automatic post-session diagnostics.
|
|
822
|
+
|
|
823
|
+
Writes a hook to .claude/settings.json (project) or ~/.claude/settings.json
|
|
824
|
+
(--global) that runs 'cctx autopsy --latest --quiet' when a Claude Code
|
|
825
|
+
session ends. Output appears only when findings exist.
|
|
826
|
+
|
|
827
|
+
Idempotent — running twice does not duplicate the hook.
|
|
828
|
+
"""
|
|
829
|
+
from cctx import hook_installer
|
|
830
|
+
|
|
831
|
+
if force and remove_:
|
|
832
|
+
raise click.UsageError("--force and --remove are mutually exclusive.")
|
|
833
|
+
|
|
834
|
+
scope = "~/.claude/settings.json" if global_ else ".claude/settings.json"
|
|
835
|
+
|
|
836
|
+
if remove_:
|
|
837
|
+
path = hook_installer.remove(global_=global_)
|
|
838
|
+
if path is None:
|
|
839
|
+
click.echo(f"No cctx hook found in {scope} — nothing to remove.")
|
|
840
|
+
else:
|
|
841
|
+
click.echo(f"✓ SessionEnd hook removed from {scope}")
|
|
842
|
+
return
|
|
843
|
+
|
|
844
|
+
result = hook_installer.install(global_=global_, force=force)
|
|
845
|
+
if result == "already_installed":
|
|
846
|
+
click.echo(f"! SessionEnd hook already installed in {scope}")
|
|
847
|
+
click.echo(" Use 'cctx init --force' to reinstall.")
|
|
848
|
+
else:
|
|
849
|
+
click.echo(f"✓ SessionEnd hook installed to {scope}")
|
|
850
|
+
remove_flag = "--global --remove" if global_ else "--remove"
|
|
851
|
+
click.echo(f" Run 'cctx init {remove_flag}' to uninstall.")
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
"""Settings-merge hook installer — install/remove the cctx SessionEnd hook.
|
|
2
|
+
|
|
3
|
+
Reads, merges, and writes ~/.claude/settings.json or .claude/settings.json
|
|
4
|
+
without touching any other keys. Idempotent: fingerprinted by the hook's
|
|
5
|
+
description field so a double-install is a no-op.
|
|
6
|
+
"""
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import json
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
from typing import Any
|
|
12
|
+
|
|
13
|
+
HOOK_DESCRIPTION = "cctx SessionEnd hook (diagnostics on session exit)"
|
|
14
|
+
HOOK_COMMAND = "cctx autopsy --latest --quiet"
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def settings_path(global_: bool) -> Path:
|
|
18
|
+
if global_:
|
|
19
|
+
return Path.home() / ".claude" / "settings.json"
|
|
20
|
+
return Path(".claude") / "settings.json"
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def _load(path: Path) -> dict[str, Any]:
|
|
24
|
+
if not path.exists():
|
|
25
|
+
return {}
|
|
26
|
+
try:
|
|
27
|
+
return json.loads(path.read_text(encoding="utf-8"))
|
|
28
|
+
except json.JSONDecodeError as exc:
|
|
29
|
+
raise ValueError(f"Invalid JSON in {path}: {exc}") from exc
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def _save(path: Path, settings: dict[str, Any]) -> None:
|
|
33
|
+
path.parent.mkdir(parents=True, exist_ok=True)
|
|
34
|
+
tmp = path.with_suffix(".json.tmp")
|
|
35
|
+
tmp.write_text(json.dumps(settings, indent=2) + "\n", encoding="utf-8")
|
|
36
|
+
tmp.replace(path)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def _find_hook(session_end: list[dict[str, Any]]) -> int | None:
|
|
40
|
+
"""Return the index of the cctx hook group in the SessionEnd array, or None."""
|
|
41
|
+
for i, group in enumerate(session_end):
|
|
42
|
+
for h in group.get("hooks", []):
|
|
43
|
+
desc = h.get("description", "")
|
|
44
|
+
if isinstance(desc, str) and "cctx SessionEnd" in desc:
|
|
45
|
+
return i
|
|
46
|
+
return None
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def _hook_entry() -> dict[str, Any]:
|
|
50
|
+
return {
|
|
51
|
+
"hooks": [
|
|
52
|
+
{
|
|
53
|
+
"type": "command",
|
|
54
|
+
"command": HOOK_COMMAND,
|
|
55
|
+
"async": True,
|
|
56
|
+
"description": HOOK_DESCRIPTION,
|
|
57
|
+
}
|
|
58
|
+
]
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def is_installed(global_: bool = False) -> bool:
|
|
63
|
+
path = settings_path(global_)
|
|
64
|
+
settings = _load(path)
|
|
65
|
+
session_end = settings.get("hooks", {}).get("SessionEnd", [])
|
|
66
|
+
return _find_hook(session_end) is not None
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def install(global_: bool = False, force: bool = False) -> str:
|
|
70
|
+
"""Install the hook. Returns "already_installed" or the path written."""
|
|
71
|
+
path = settings_path(global_)
|
|
72
|
+
settings = _load(path)
|
|
73
|
+
hooks = settings.setdefault("hooks", {})
|
|
74
|
+
session_end = hooks.setdefault("SessionEnd", [])
|
|
75
|
+
idx = _find_hook(session_end)
|
|
76
|
+
if idx is not None and not force:
|
|
77
|
+
return "already_installed"
|
|
78
|
+
if idx is not None:
|
|
79
|
+
session_end[idx] = _hook_entry()
|
|
80
|
+
else:
|
|
81
|
+
session_end.append(_hook_entry())
|
|
82
|
+
_save(path, settings)
|
|
83
|
+
return str(path)
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def remove(global_: bool = False) -> str | None:
|
|
87
|
+
"""Remove the hook. Returns the path written, or None if not found."""
|
|
88
|
+
path = settings_path(global_)
|
|
89
|
+
settings = _load(path)
|
|
90
|
+
hooks = settings.get("hooks", {})
|
|
91
|
+
session_end = hooks.get("SessionEnd", [])
|
|
92
|
+
idx = _find_hook(session_end)
|
|
93
|
+
if idx is None:
|
|
94
|
+
return None
|
|
95
|
+
session_end.pop(idx)
|
|
96
|
+
if not session_end:
|
|
97
|
+
del hooks["SessionEnd"]
|
|
98
|
+
if not hooks:
|
|
99
|
+
del settings["hooks"]
|
|
100
|
+
_save(path, settings)
|
|
101
|
+
return str(path)
|