cctx-cli 1.9.0__tar.gz → 1.11.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.9.0 → cctx_cli-1.11.0}/CHANGELOG.md +24 -0
- {cctx_cli-1.9.0 → cctx_cli-1.11.0}/PKG-INFO +1 -1
- {cctx_cli-1.9.0 → cctx_cli-1.11.0}/cctx/__init__.py +1 -1
- {cctx_cli-1.9.0 → cctx_cli-1.11.0}/cctx/cli.py +79 -6
- {cctx_cli-1.9.0 → cctx_cli-1.11.0}/cctx/exporters/jsonl.py +45 -1
- cctx_cli-1.11.0/cctx/hook_installer.py +101 -0
- {cctx_cli-1.9.0 → cctx_cli-1.11.0}/pyproject.toml +1 -1
- {cctx_cli-1.9.0 → cctx_cli-1.11.0}/tests/test_cli.py +28 -3
- cctx_cli-1.11.0/tests/test_init.py +323 -0
- {cctx_cli-1.9.0 → cctx_cli-1.11.0}/.github/workflows/ci.yml +0 -0
- {cctx_cli-1.9.0 → cctx_cli-1.11.0}/.github/workflows/publish.yml +0 -0
- {cctx_cli-1.9.0 → cctx_cli-1.11.0}/.github/workflows/release.yml +0 -0
- {cctx_cli-1.9.0 → cctx_cli-1.11.0}/.gitignore +0 -0
- {cctx_cli-1.9.0 → cctx_cli-1.11.0}/CLAUDE.md +0 -0
- {cctx_cli-1.9.0 → cctx_cli-1.11.0}/DESIGN.md +0 -0
- {cctx_cli-1.9.0 → cctx_cli-1.11.0}/PRODUCT.md +0 -0
- {cctx_cli-1.9.0 → cctx_cli-1.11.0}/README.md +0 -0
- {cctx_cli-1.9.0 → cctx_cli-1.11.0}/action.yml +0 -0
- {cctx_cli-1.9.0 → cctx_cli-1.11.0}/cctx/agents.py +0 -0
- {cctx_cli-1.9.0 → cctx_cli-1.11.0}/cctx/diagnostician/__init__.py +0 -0
- {cctx_cli-1.9.0 → cctx_cli-1.11.0}/cctx/diagnostician/aggregate.py +0 -0
- {cctx_cli-1.9.0 → cctx_cli-1.11.0}/cctx/diagnostician/inflection.py +0 -0
- {cctx_cli-1.9.0 → cctx_cli-1.11.0}/cctx/diagnostician/patterns/__init__.py +0 -0
- {cctx_cli-1.9.0 → cctx_cli-1.11.0}/cctx/diagnostician/patterns/dead_end.py +0 -0
- {cctx_cli-1.9.0 → cctx_cli-1.11.0}/cctx/diagnostician/patterns/fan_out.py +0 -0
- {cctx_cli-1.9.0 → cctx_cli-1.11.0}/cctx/diagnostician/patterns/project_specific.py +0 -0
- {cctx_cli-1.9.0 → cctx_cli-1.11.0}/cctx/diagnostician/patterns/retry_loop.py +0 -0
- {cctx_cli-1.9.0 → cctx_cli-1.11.0}/cctx/diagnostician/patterns/scope_creep.py +0 -0
- {cctx_cli-1.9.0 → cctx_cli-1.11.0}/cctx/diagnostician/patterns/stale_context.py +0 -0
- {cctx_cli-1.9.0 → cctx_cli-1.11.0}/cctx/diagnostician/patterns/tool_thrash.py +0 -0
- {cctx_cli-1.9.0 → cctx_cli-1.11.0}/cctx/discovery.py +0 -0
- {cctx_cli-1.9.0 → cctx_cli-1.11.0}/cctx/exporters/__init__.py +0 -0
- {cctx_cli-1.9.0 → cctx_cli-1.11.0}/cctx/exporters/csv.py +0 -0
- {cctx_cli-1.9.0 → cctx_cli-1.11.0}/cctx/exporters/json.py +0 -0
- {cctx_cli-1.9.0 → cctx_cli-1.11.0}/cctx/harvest.py +0 -0
- {cctx_cli-1.9.0 → cctx_cli-1.11.0}/cctx/models.py +0 -0
- {cctx_cli-1.9.0 → cctx_cli-1.11.0}/cctx/parsers/__init__.py +0 -0
- {cctx_cli-1.9.0 → cctx_cli-1.11.0}/cctx/parsers/claude_code.py +0 -0
- {cctx_cli-1.9.0 → cctx_cli-1.11.0}/cctx/pricing.py +0 -0
- {cctx_cli-1.9.0 → cctx_cli-1.11.0}/cctx/recommender/__init__.py +0 -0
- {cctx_cli-1.9.0 → cctx_cli-1.11.0}/cctx/recommender/claude_md.py +0 -0
- {cctx_cli-1.9.0 → cctx_cli-1.11.0}/cctx/recommender/evidence.py +0 -0
- {cctx_cli-1.9.0 → cctx_cli-1.11.0}/cctx/renderers/__init__.py +0 -0
- {cctx_cli-1.9.0 → cctx_cli-1.11.0}/cctx/renderers/github.py +0 -0
- {cctx_cli-1.9.0 → cctx_cli-1.11.0}/cctx/renderers/report.py +0 -0
- {cctx_cli-1.9.0 → cctx_cli-1.11.0}/cctx/renderers/templates/autopsy.html.j2 +0 -0
- {cctx_cli-1.9.0 → cctx_cli-1.11.0}/cctx/renderers/terminal.py +0 -0
- {cctx_cli-1.9.0 → cctx_cli-1.11.0}/cctx/renderers/trace_tui.py +0 -0
- {cctx_cli-1.9.0 → cctx_cli-1.11.0}/cctx/tokenizer.py +0 -0
- {cctx_cli-1.9.0 → cctx_cli-1.11.0}/cctx/watcher.py +0 -0
- {cctx_cli-1.9.0 → cctx_cli-1.11.0}/cctx-project-brief.md +0 -0
- {cctx_cli-1.9.0 → cctx_cli-1.11.0}/demo.gif +0 -0
- {cctx_cli-1.9.0 → cctx_cli-1.11.0}/demo.tape +0 -0
- {cctx_cli-1.9.0 → cctx_cli-1.11.0}/docs/health-reviews/2026-05-15-deep-review-summary.md +0 -0
- {cctx_cli-1.9.0 → cctx_cli-1.11.0}/docs/health-reviews/2026-05-15-health-review.md +0 -0
- {cctx_cli-1.9.0 → cctx_cli-1.11.0}/docs/product-reviews/2026-05-15-product-review.md +0 -0
- {cctx_cli-1.9.0 → cctx_cli-1.11.0}/docs/product-reviews/2026-06-09-product-review.md +0 -0
- {cctx_cli-1.9.0 → cctx_cli-1.11.0}/docs/superpowers/plans/2026-05-12-claude-code-parser.md +0 -0
- {cctx_cli-1.9.0 → cctx_cli-1.11.0}/docs/superpowers/plans/2026-05-14-autopsy-v0.md +0 -0
- {cctx_cli-1.9.0 → cctx_cli-1.11.0}/docs/superpowers/plans/2026-05-16-readme-pypi-release.md +0 -0
- {cctx_cli-1.9.0 → cctx_cli-1.11.0}/docs/superpowers/plans/2026-05-17-harvest-check-depth.md +0 -0
- {cctx_cli-1.9.0 → cctx_cli-1.11.0}/docs/superpowers/plans/2026-05-17-project-pattern-detection.md +0 -0
- {cctx_cli-1.9.0 → cctx_cli-1.11.0}/docs/superpowers/plans/2026-05-19-claude-agents-live-integration.md +0 -0
- {cctx_cli-1.9.0 → cctx_cli-1.11.0}/docs/superpowers/specs/2026-05-12-claude-code-parser-design.md +0 -0
- {cctx_cli-1.9.0 → cctx_cli-1.11.0}/docs/superpowers/specs/2026-05-14-autopsy-design.md +0 -0
- {cctx_cli-1.9.0 → cctx_cli-1.11.0}/docs/superpowers/specs/2026-05-14-harvest-design.md +0 -0
- {cctx_cli-1.9.0 → cctx_cli-1.11.0}/docs/superpowers/specs/2026-05-14-trace-tui-design.md +0 -0
- {cctx_cli-1.9.0 → cctx_cli-1.11.0}/docs/superpowers/specs/2026-05-16-readme-pypi-release-design.md +0 -0
- {cctx_cli-1.9.0 → cctx_cli-1.11.0}/docs/superpowers/specs/2026-05-17-harvest-check-depth-design.md +0 -0
- {cctx_cli-1.9.0 → cctx_cli-1.11.0}/docs/superpowers/specs/2026-05-17-project-pattern-detection-design.md +0 -0
- {cctx_cli-1.9.0 → cctx_cli-1.11.0}/docs/superpowers/specs/2026-05-19-claude-agents-live-integration-design.md +0 -0
- {cctx_cli-1.9.0 → cctx_cli-1.11.0}/docs/superpowers/specs/2026-06-09-cross-agent-emit-design.md +0 -0
- {cctx_cli-1.9.0 → cctx_cli-1.11.0}/tests/__init__.py +0 -0
- {cctx_cli-1.9.0 → cctx_cli-1.11.0}/tests/conftest.py +0 -0
- {cctx_cli-1.9.0 → cctx_cli-1.11.0}/tests/diagnostician/__init__.py +0 -0
- {cctx_cli-1.9.0 → cctx_cli-1.11.0}/tests/diagnostician/conftest.py +0 -0
- {cctx_cli-1.9.0 → cctx_cli-1.11.0}/tests/diagnostician/test_dead_end.py +0 -0
- {cctx_cli-1.9.0 → cctx_cli-1.11.0}/tests/diagnostician/test_inflection.py +0 -0
- {cctx_cli-1.9.0 → cctx_cli-1.11.0}/tests/diagnostician/test_orchestrator.py +0 -0
- {cctx_cli-1.9.0 → cctx_cli-1.11.0}/tests/diagnostician/test_project_specific.py +0 -0
- {cctx_cli-1.9.0 → cctx_cli-1.11.0}/tests/diagnostician/test_retry_loop.py +0 -0
- {cctx_cli-1.9.0 → cctx_cli-1.11.0}/tests/diagnostician/test_scope_creep.py +0 -0
- {cctx_cli-1.9.0 → cctx_cli-1.11.0}/tests/diagnostician/test_stale_context.py +0 -0
- {cctx_cli-1.9.0 → cctx_cli-1.11.0}/tests/diagnostician/test_tool_thrash.py +0 -0
- {cctx_cli-1.9.0 → cctx_cli-1.11.0}/tests/exporters/__init__.py +0 -0
- {cctx_cli-1.9.0 → cctx_cli-1.11.0}/tests/exporters/test_csv.py +0 -0
- {cctx_cli-1.9.0 → cctx_cli-1.11.0}/tests/exporters/test_jsonl.py +0 -0
- {cctx_cli-1.9.0 → cctx_cli-1.11.0}/tests/fixtures/claude_code/README.md +0 -0
- {cctx_cli-1.9.0 → cctx_cli-1.11.0}/tests/fixtures/claude_code/short-clean/short-clean.jsonl +0 -0
- {cctx_cli-1.9.0 → cctx_cli-1.11.0}/tests/fixtures/claude_code/with-attachments/with-attachments/subagents/agent-a0b4c2cf1dde0ca56.meta.json +0 -0
- {cctx_cli-1.9.0 → cctx_cli-1.11.0}/tests/fixtures/claude_code/with-attachments/with-attachments/subagents/agent-a116ae34b1b09c332.meta.json +0 -0
- {cctx_cli-1.9.0 → cctx_cli-1.11.0}/tests/fixtures/claude_code/with-attachments/with-attachments/subagents/agent-a1c4c417b35658c9e.meta.json +0 -0
- {cctx_cli-1.9.0 → cctx_cli-1.11.0}/tests/fixtures/claude_code/with-attachments/with-attachments/subagents/agent-a1e41a901de38f1b5.meta.json +0 -0
- {cctx_cli-1.9.0 → cctx_cli-1.11.0}/tests/fixtures/claude_code/with-attachments/with-attachments/subagents/agent-a338f8d0c74612a24.meta.json +0 -0
- {cctx_cli-1.9.0 → cctx_cli-1.11.0}/tests/fixtures/claude_code/with-attachments/with-attachments/subagents/agent-a34f6f3c0e7094186.meta.json +0 -0
- {cctx_cli-1.9.0 → cctx_cli-1.11.0}/tests/fixtures/claude_code/with-attachments/with-attachments/subagents/agent-a5a5a0cff4d13308b.meta.json +0 -0
- {cctx_cli-1.9.0 → cctx_cli-1.11.0}/tests/fixtures/claude_code/with-attachments/with-attachments/subagents/agent-a6b0a3da6a0484db5.meta.json +0 -0
- {cctx_cli-1.9.0 → cctx_cli-1.11.0}/tests/fixtures/claude_code/with-attachments/with-attachments/subagents/agent-a7f73f1790b02cde5.meta.json +0 -0
- {cctx_cli-1.9.0 → cctx_cli-1.11.0}/tests/fixtures/claude_code/with-attachments/with-attachments/subagents/agent-a7f7c17c38a9d8788.meta.json +0 -0
- {cctx_cli-1.9.0 → cctx_cli-1.11.0}/tests/fixtures/claude_code/with-attachments/with-attachments/subagents/agent-a853259e2cd7bbe8a.meta.json +0 -0
- {cctx_cli-1.9.0 → cctx_cli-1.11.0}/tests/fixtures/claude_code/with-attachments/with-attachments/subagents/agent-a8d9aedb0d0c6e12d.meta.json +0 -0
- {cctx_cli-1.9.0 → cctx_cli-1.11.0}/tests/fixtures/claude_code/with-attachments/with-attachments/subagents/agent-aa778bc1d59e4a441.meta.json +0 -0
- {cctx_cli-1.9.0 → cctx_cli-1.11.0}/tests/fixtures/claude_code/with-attachments/with-attachments/subagents/agent-aba869dedee4a12ba.meta.json +0 -0
- {cctx_cli-1.9.0 → cctx_cli-1.11.0}/tests/fixtures/claude_code/with-attachments/with-attachments/subagents/agent-ada2746d9774b94db.meta.json +0 -0
- {cctx_cli-1.9.0 → cctx_cli-1.11.0}/tests/fixtures/claude_code/with-attachments/with-attachments/subagents/agent-aea0132068c64d2dd.meta.json +0 -0
- {cctx_cli-1.9.0 → cctx_cli-1.11.0}/tests/fixtures/claude_code/with-attachments/with-attachments/subagents/agent-aea215eff50874d5f.meta.json +0 -0
- {cctx_cli-1.9.0 → cctx_cli-1.11.0}/tests/fixtures/claude_code/with-attachments/with-attachments/subagents/agent-afee21f2b3852a4a0.meta.json +0 -0
- {cctx_cli-1.9.0 → cctx_cli-1.11.0}/tests/fixtures/claude_code/with-attachments/with-attachments.jsonl +0 -0
- {cctx_cli-1.9.0 → cctx_cli-1.11.0}/tests/fixtures/claude_code/with-compaction/with-compaction/subagents/agent-a051d9c9a6b2f5cc3.jsonl +0 -0
- {cctx_cli-1.9.0 → cctx_cli-1.11.0}/tests/fixtures/claude_code/with-compaction/with-compaction/subagents/agent-a051d9c9a6b2f5cc3.meta.json +0 -0
- {cctx_cli-1.9.0 → cctx_cli-1.11.0}/tests/fixtures/claude_code/with-compaction/with-compaction/subagents/agent-a171f16f4e65cfe75.jsonl +0 -0
- {cctx_cli-1.9.0 → cctx_cli-1.11.0}/tests/fixtures/claude_code/with-compaction/with-compaction/subagents/agent-a171f16f4e65cfe75.meta.json +0 -0
- {cctx_cli-1.9.0 → cctx_cli-1.11.0}/tests/fixtures/claude_code/with-compaction/with-compaction/subagents/agent-a1b77fea2c0a2269b.jsonl +0 -0
- {cctx_cli-1.9.0 → cctx_cli-1.11.0}/tests/fixtures/claude_code/with-compaction/with-compaction/subagents/agent-a1b77fea2c0a2269b.meta.json +0 -0
- {cctx_cli-1.9.0 → cctx_cli-1.11.0}/tests/fixtures/claude_code/with-compaction/with-compaction/subagents/agent-a20da4c01a54acca8.jsonl +0 -0
- {cctx_cli-1.9.0 → cctx_cli-1.11.0}/tests/fixtures/claude_code/with-compaction/with-compaction/subagents/agent-a20da4c01a54acca8.meta.json +0 -0
- {cctx_cli-1.9.0 → cctx_cli-1.11.0}/tests/fixtures/claude_code/with-compaction/with-compaction/subagents/agent-a3c82739b1383fb14.jsonl +0 -0
- {cctx_cli-1.9.0 → cctx_cli-1.11.0}/tests/fixtures/claude_code/with-compaction/with-compaction/subagents/agent-a3c82739b1383fb14.meta.json +0 -0
- {cctx_cli-1.9.0 → cctx_cli-1.11.0}/tests/fixtures/claude_code/with-compaction/with-compaction/subagents/agent-a49e8539611c5fe12.jsonl +0 -0
- {cctx_cli-1.9.0 → cctx_cli-1.11.0}/tests/fixtures/claude_code/with-compaction/with-compaction/subagents/agent-a49e8539611c5fe12.meta.json +0 -0
- {cctx_cli-1.9.0 → cctx_cli-1.11.0}/tests/fixtures/claude_code/with-compaction/with-compaction/subagents/agent-a7bb58f3fff2b3e8d.jsonl +0 -0
- {cctx_cli-1.9.0 → cctx_cli-1.11.0}/tests/fixtures/claude_code/with-compaction/with-compaction/subagents/agent-a7bb58f3fff2b3e8d.meta.json +0 -0
- {cctx_cli-1.9.0 → cctx_cli-1.11.0}/tests/fixtures/claude_code/with-compaction/with-compaction/subagents/agent-a92b48c0331195aac.jsonl +0 -0
- {cctx_cli-1.9.0 → cctx_cli-1.11.0}/tests/fixtures/claude_code/with-compaction/with-compaction/subagents/agent-a92b48c0331195aac.meta.json +0 -0
- {cctx_cli-1.9.0 → cctx_cli-1.11.0}/tests/fixtures/claude_code/with-compaction/with-compaction/subagents/agent-ab96c4264099694a9.jsonl +0 -0
- {cctx_cli-1.9.0 → cctx_cli-1.11.0}/tests/fixtures/claude_code/with-compaction/with-compaction/subagents/agent-ab96c4264099694a9.meta.json +0 -0
- {cctx_cli-1.9.0 → cctx_cli-1.11.0}/tests/fixtures/claude_code/with-compaction/with-compaction/subagents/agent-acb2895c5e34ffec0.jsonl +0 -0
- {cctx_cli-1.9.0 → cctx_cli-1.11.0}/tests/fixtures/claude_code/with-compaction/with-compaction/subagents/agent-acb2895c5e34ffec0.meta.json +0 -0
- {cctx_cli-1.9.0 → cctx_cli-1.11.0}/tests/fixtures/claude_code/with-compaction/with-compaction/subagents/agent-adb2302769938fb3f.jsonl +0 -0
- {cctx_cli-1.9.0 → cctx_cli-1.11.0}/tests/fixtures/claude_code/with-compaction/with-compaction/subagents/agent-adb2302769938fb3f.meta.json +0 -0
- {cctx_cli-1.9.0 → cctx_cli-1.11.0}/tests/fixtures/claude_code/with-compaction/with-compaction/subagents/agent-ae585eca15cb93b9c.jsonl +0 -0
- {cctx_cli-1.9.0 → cctx_cli-1.11.0}/tests/fixtures/claude_code/with-compaction/with-compaction/subagents/agent-ae585eca15cb93b9c.meta.json +0 -0
- {cctx_cli-1.9.0 → cctx_cli-1.11.0}/tests/fixtures/claude_code/with-compaction/with-compaction/subagents/agent-aec9c917feb903d67.jsonl +0 -0
- {cctx_cli-1.9.0 → cctx_cli-1.11.0}/tests/fixtures/claude_code/with-compaction/with-compaction/subagents/agent-aec9c917feb903d67.meta.json +0 -0
- {cctx_cli-1.9.0 → cctx_cli-1.11.0}/tests/fixtures/claude_code/with-compaction/with-compaction.jsonl +0 -0
- {cctx_cli-1.9.0 → cctx_cli-1.11.0}/tests/fixtures/claude_code/with-subagents/with-subagents/subagents/agent-a1a3a21aeb76bb0a9.jsonl +0 -0
- {cctx_cli-1.9.0 → cctx_cli-1.11.0}/tests/fixtures/claude_code/with-subagents/with-subagents/subagents/agent-a1a3a21aeb76bb0a9.meta.json +0 -0
- {cctx_cli-1.9.0 → cctx_cli-1.11.0}/tests/fixtures/claude_code/with-subagents/with-subagents/subagents/agent-aaa1d6ecc05a78442.jsonl +0 -0
- {cctx_cli-1.9.0 → cctx_cli-1.11.0}/tests/fixtures/claude_code/with-subagents/with-subagents/subagents/agent-aaa1d6ecc05a78442.meta.json +0 -0
- {cctx_cli-1.9.0 → cctx_cli-1.11.0}/tests/fixtures/claude_code/with-subagents/with-subagents/subagents/agent-af3c545ccd30036d2.jsonl +0 -0
- {cctx_cli-1.9.0 → cctx_cli-1.11.0}/tests/fixtures/claude_code/with-subagents/with-subagents/subagents/agent-af3c545ccd30036d2.meta.json +0 -0
- {cctx_cli-1.9.0 → cctx_cli-1.11.0}/tests/fixtures/claude_code/with-subagents/with-subagents/tool-results/btwp2bzro.txt +0 -0
- {cctx_cli-1.9.0 → cctx_cli-1.11.0}/tests/fixtures/claude_code/with-subagents/with-subagents/tool-results/byqjbgy4b.txt +0 -0
- {cctx_cli-1.9.0 → cctx_cli-1.11.0}/tests/fixtures/claude_code/with-subagents/with-subagents.jsonl +0 -0
- {cctx_cli-1.9.0 → cctx_cli-1.11.0}/tests/fixtures/claude_code/with-tool-results/with-tool-results/tool-results/bosbkda0h.txt +0 -0
- {cctx_cli-1.9.0 → cctx_cli-1.11.0}/tests/fixtures/claude_code/with-tool-results/with-tool-results.jsonl +0 -0
- {cctx_cli-1.9.0 → cctx_cli-1.11.0}/tests/fixtures/scrub.py +0 -0
- {cctx_cli-1.9.0 → cctx_cli-1.11.0}/tests/fixtures/synthetic/bookkeeping_only.jsonl +0 -0
- {cctx_cli-1.9.0 → cctx_cli-1.11.0}/tests/fixtures/synthetic/malformed_middle.jsonl +0 -0
- {cctx_cli-1.9.0 → cctx_cli-1.11.0}/tests/fixtures/synthetic/truncated_final_line.jsonl +0 -0
- {cctx_cli-1.9.0 → cctx_cli-1.11.0}/tests/fixtures/synthetic/unknown_attachment_shape.jsonl +0 -0
- {cctx_cli-1.9.0 → cctx_cli-1.11.0}/tests/fixtures/synthetic/unknown_type.jsonl +0 -0
- {cctx_cli-1.9.0 → cctx_cli-1.11.0}/tests/parsers/__init__.py +0 -0
- {cctx_cli-1.9.0 → cctx_cli-1.11.0}/tests/parsers/test_claude_code.py +0 -0
- {cctx_cli-1.9.0 → cctx_cli-1.11.0}/tests/parsers/test_claude_code_integration.py +0 -0
- {cctx_cli-1.9.0 → cctx_cli-1.11.0}/tests/recommender/__init__.py +0 -0
- {cctx_cli-1.9.0 → cctx_cli-1.11.0}/tests/recommender/test_claude_md.py +0 -0
- {cctx_cli-1.9.0 → cctx_cli-1.11.0}/tests/recommender/test_evidence.py +0 -0
- {cctx_cli-1.9.0 → cctx_cli-1.11.0}/tests/renderers/__init__.py +0 -0
- {cctx_cli-1.9.0 → cctx_cli-1.11.0}/tests/renderers/test_report.py +0 -0
- {cctx_cli-1.9.0 → cctx_cli-1.11.0}/tests/renderers/test_terminal_renderer_full.py +0 -0
- {cctx_cli-1.9.0 → cctx_cli-1.11.0}/tests/test_agents.py +0 -0
- {cctx_cli-1.9.0 → cctx_cli-1.11.0}/tests/test_aggregate.py +0 -0
- {cctx_cli-1.9.0 → cctx_cli-1.11.0}/tests/test_cli_export.py +0 -0
- {cctx_cli-1.9.0 → cctx_cli-1.11.0}/tests/test_diagnostician_subagents.py +0 -0
- {cctx_cli-1.9.0 → cctx_cli-1.11.0}/tests/test_discovery.py +0 -0
- {cctx_cli-1.9.0 → cctx_cli-1.11.0}/tests/test_efficacy.py +0 -0
- {cctx_cli-1.9.0 → cctx_cli-1.11.0}/tests/test_fanout_classifier.py +0 -0
- {cctx_cli-1.9.0 → cctx_cli-1.11.0}/tests/test_github_summary.py +0 -0
- {cctx_cli-1.9.0 → cctx_cli-1.11.0}/tests/test_harvest.py +0 -0
- {cctx_cli-1.9.0 → cctx_cli-1.11.0}/tests/test_harvest_check.py +0 -0
- {cctx_cli-1.9.0 → cctx_cli-1.11.0}/tests/test_harvest_emit.py +0 -0
- {cctx_cli-1.9.0 → cctx_cli-1.11.0}/tests/test_models.py +0 -0
- {cctx_cli-1.9.0 → cctx_cli-1.11.0}/tests/test_models_project_pattern.py +0 -0
- {cctx_cli-1.9.0 → cctx_cli-1.11.0}/tests/test_recommender.py +0 -0
- {cctx_cli-1.9.0 → cctx_cli-1.11.0}/tests/test_smoke.py +0 -0
- {cctx_cli-1.9.0 → cctx_cli-1.11.0}/tests/test_terminal_renderer.py +0 -0
- {cctx_cli-1.9.0 → cctx_cli-1.11.0}/tests/test_tokenizer.py +0 -0
- {cctx_cli-1.9.0 → cctx_cli-1.11.0}/tests/test_trace_tui.py +0 -0
- {cctx_cli-1.9.0 → cctx_cli-1.11.0}/tests/test_watcher.py +0 -0
|
@@ -2,6 +2,30 @@
|
|
|
2
2
|
|
|
3
3
|
<!-- version list -->
|
|
4
4
|
|
|
5
|
+
## v1.11.0 (2026-06-11)
|
|
6
|
+
|
|
7
|
+
### Bug Fixes
|
|
8
|
+
|
|
9
|
+
- Ruff E501 + I001 in test_init.py (line length + import order)
|
|
10
|
+
([#113](https://github.com/jacquardlabs/cctx/pull/113),
|
|
11
|
+
[`a04426e`](https://github.com/jacquardlabs/cctx/commit/a04426ef62cfeaa4ba7ea392bdf0dd49975cffbb))
|
|
12
|
+
|
|
13
|
+
### Features
|
|
14
|
+
|
|
15
|
+
- Cctx init — SessionEnd hook installer + autopsy --quiet (closes #92)
|
|
16
|
+
([#113](https://github.com/jacquardlabs/cctx/pull/113),
|
|
17
|
+
[`a04426e`](https://github.com/jacquardlabs/cctx/commit/a04426ef62cfeaa4ba7ea392bdf0dd49975cffbb))
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
## v1.10.0 (2026-06-11)
|
|
21
|
+
|
|
22
|
+
### Features
|
|
23
|
+
|
|
24
|
+
- Autopsy --json aggregate output for --since mode (closes #97)
|
|
25
|
+
([#112](https://github.com/jacquardlabs/cctx/pull/112),
|
|
26
|
+
[`86b10e0`](https://github.com/jacquardlabs/cctx/commit/86b10e0787efa99e9af251e979e311053d7b8b72))
|
|
27
|
+
|
|
28
|
+
|
|
5
29
|
## v1.9.0 (2026-06-11)
|
|
6
30
|
|
|
7
31
|
### Bug Fixes
|
|
@@ -9,6 +9,7 @@ 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
|
|
|
@@ -292,7 +293,15 @@ def ls(project: Path | None) -> None:
|
|
|
292
293
|
"json_out",
|
|
293
294
|
is_flag=True,
|
|
294
295
|
default=False,
|
|
295
|
-
help="Output diagnosis as JSON to stdout
|
|
296
|
+
help="Output diagnosis (or aggregate) as JSON to stdout.",
|
|
297
|
+
)
|
|
298
|
+
@click.option(
|
|
299
|
+
"--quiet",
|
|
300
|
+
"quiet",
|
|
301
|
+
is_flag=True,
|
|
302
|
+
default=False,
|
|
303
|
+
help="Print one verdict line only when findings exist; nothing if clean. "
|
|
304
|
+
"Designed for SessionEnd hook use (cctx init).",
|
|
296
305
|
)
|
|
297
306
|
def autopsy(
|
|
298
307
|
target: Path | None,
|
|
@@ -305,6 +314,7 @@ def autopsy(
|
|
|
305
314
|
top_n: int | None,
|
|
306
315
|
turn_num: int | None,
|
|
307
316
|
json_out: bool,
|
|
317
|
+
quiet: bool,
|
|
308
318
|
) -> None:
|
|
309
319
|
"""Diagnose a session or project directory.
|
|
310
320
|
|
|
@@ -326,8 +336,6 @@ def autopsy(
|
|
|
326
336
|
raise click.UsageError("--turn is not supported with --since.")
|
|
327
337
|
if until_date is not None and since is None:
|
|
328
338
|
raise click.UsageError("--until requires --since.")
|
|
329
|
-
if json_out and since is not None:
|
|
330
|
-
raise click.UsageError("--json is not supported with --since.")
|
|
331
339
|
|
|
332
340
|
if target is None:
|
|
333
341
|
if not latest:
|
|
@@ -392,8 +400,14 @@ def autopsy(
|
|
|
392
400
|
patches=patches,
|
|
393
401
|
project_patterns=patterns,
|
|
394
402
|
)
|
|
395
|
-
|
|
396
|
-
|
|
403
|
+
if json_out:
|
|
404
|
+
import json as _json
|
|
405
|
+
|
|
406
|
+
from cctx.exporters.jsonl import export_aggregate as _export_agg
|
|
407
|
+
click.echo(_json.dumps(_json.loads(_export_agg(report)), indent=2))
|
|
408
|
+
else:
|
|
409
|
+
render_aggregate(report)
|
|
410
|
+
_aggregate_drilldown(report, diagnoses)
|
|
397
411
|
else:
|
|
398
412
|
# Single-session path
|
|
399
413
|
if target.is_dir():
|
|
@@ -404,7 +418,11 @@ def autopsy(
|
|
|
404
418
|
trace = tokenize_session(parse_session(target))
|
|
405
419
|
diagnosis = diagnostician.run(trace)
|
|
406
420
|
diagnosis = claude_md.generate(diagnosis)
|
|
407
|
-
if
|
|
421
|
+
if quiet:
|
|
422
|
+
if diagnosis.findings:
|
|
423
|
+
kinds = list(dict.fromkeys(f.kind.value for f in diagnosis.findings))
|
|
424
|
+
click.echo(f"{len(diagnosis.findings)} finding(s): {', '.join(kinds)}")
|
|
425
|
+
elif json_out:
|
|
408
426
|
import json as _json
|
|
409
427
|
|
|
410
428
|
from cctx.exporters.jsonl import export_diagnosis as _export_diag
|
|
@@ -725,3 +743,58 @@ def watch(target: Path | None) -> None:
|
|
|
725
743
|
"""
|
|
726
744
|
from cctx.watcher import watch as _watch
|
|
727
745
|
_watch(target)
|
|
746
|
+
|
|
747
|
+
|
|
748
|
+
@cli.command("init")
|
|
749
|
+
@click.option(
|
|
750
|
+
"--global",
|
|
751
|
+
"global_",
|
|
752
|
+
is_flag=True,
|
|
753
|
+
default=False,
|
|
754
|
+
help="Install to ~/.claude/settings.json (user scope) instead of .claude/settings.json.",
|
|
755
|
+
)
|
|
756
|
+
@click.option(
|
|
757
|
+
"--remove",
|
|
758
|
+
"remove_",
|
|
759
|
+
is_flag=True,
|
|
760
|
+
default=False,
|
|
761
|
+
help="Remove the SessionEnd hook instead of installing it.",
|
|
762
|
+
)
|
|
763
|
+
@click.option(
|
|
764
|
+
"--force",
|
|
765
|
+
is_flag=True,
|
|
766
|
+
default=False,
|
|
767
|
+
help="Reinstall even if hook is already present.",
|
|
768
|
+
)
|
|
769
|
+
def init_cmd(global_: bool, remove_: bool, force: bool) -> None:
|
|
770
|
+
"""Install an opt-in SessionEnd hook for automatic post-session diagnostics.
|
|
771
|
+
|
|
772
|
+
Writes a hook to .claude/settings.json (project) or ~/.claude/settings.json
|
|
773
|
+
(--global) that runs 'cctx autopsy --latest --quiet' when a Claude Code
|
|
774
|
+
session ends. Output appears only when findings exist.
|
|
775
|
+
|
|
776
|
+
Idempotent — running twice does not duplicate the hook.
|
|
777
|
+
"""
|
|
778
|
+
from cctx import hook_installer
|
|
779
|
+
|
|
780
|
+
if force and remove_:
|
|
781
|
+
raise click.UsageError("--force and --remove are mutually exclusive.")
|
|
782
|
+
|
|
783
|
+
scope = "~/.claude/settings.json" if global_ else ".claude/settings.json"
|
|
784
|
+
|
|
785
|
+
if remove_:
|
|
786
|
+
path = hook_installer.remove(global_=global_)
|
|
787
|
+
if path is None:
|
|
788
|
+
click.echo(f"No cctx hook found in {scope} — nothing to remove.")
|
|
789
|
+
else:
|
|
790
|
+
click.echo(f"✓ SessionEnd hook removed from {scope}")
|
|
791
|
+
return
|
|
792
|
+
|
|
793
|
+
result = hook_installer.install(global_=global_, force=force)
|
|
794
|
+
if result == "already_installed":
|
|
795
|
+
click.echo(f"! SessionEnd hook already installed in {scope}")
|
|
796
|
+
click.echo(" Use 'cctx init --force' to reinstall.")
|
|
797
|
+
else:
|
|
798
|
+
click.echo(f"✓ SessionEnd hook installed to {scope}")
|
|
799
|
+
remove_flag = "--global --remove" if global_ else "--remove"
|
|
800
|
+
click.echo(f" Run 'cctx init {remove_flag}' to uninstall.")
|
|
@@ -5,7 +5,7 @@ import json
|
|
|
5
5
|
from typing import IO, TYPE_CHECKING
|
|
6
6
|
|
|
7
7
|
if TYPE_CHECKING:
|
|
8
|
-
from cctx.models import Diagnosis, SessionTrace
|
|
8
|
+
from cctx.models import AggregateReport, Diagnosis, SessionTrace
|
|
9
9
|
|
|
10
10
|
|
|
11
11
|
def export_diagnosis(
|
|
@@ -64,6 +64,50 @@ def export_diagnosis(
|
|
|
64
64
|
return json.dumps(obj)
|
|
65
65
|
|
|
66
66
|
|
|
67
|
+
def export_aggregate(report: AggregateReport) -> str:
|
|
68
|
+
"""Serialize an AggregateReport to a JSON string."""
|
|
69
|
+
by_kind = {
|
|
70
|
+
k.value: {
|
|
71
|
+
"session_count": v.session_count,
|
|
72
|
+
"total_waste_usd": v.total_waste_usd,
|
|
73
|
+
"example_summaries": v.example_summaries,
|
|
74
|
+
}
|
|
75
|
+
for k, v in report.by_kind.items()
|
|
76
|
+
}
|
|
77
|
+
patches = [
|
|
78
|
+
{
|
|
79
|
+
"target_file": p.target_file,
|
|
80
|
+
"finding_kind": p.finding_kind.value,
|
|
81
|
+
"description": p.description,
|
|
82
|
+
"evidence_summary": p.evidence_summary,
|
|
83
|
+
}
|
|
84
|
+
for p in report.patches
|
|
85
|
+
]
|
|
86
|
+
project_patterns = [
|
|
87
|
+
{
|
|
88
|
+
"tool_name": pp.tool_name,
|
|
89
|
+
"failure_key": pp.failure_key,
|
|
90
|
+
"fix_key": pp.fix_key,
|
|
91
|
+
"session_count": pp.session_count,
|
|
92
|
+
"avg_wasted_turns": pp.avg_wasted_turns,
|
|
93
|
+
"total_waste_usd": pp.total_waste_usd,
|
|
94
|
+
"example_sessions": pp.example_sessions,
|
|
95
|
+
}
|
|
96
|
+
for pp in report.project_patterns
|
|
97
|
+
]
|
|
98
|
+
obj = {
|
|
99
|
+
"period_label": report.period_label,
|
|
100
|
+
"sessions_analysed": report.sessions_analysed,
|
|
101
|
+
"sessions_with_findings": report.sessions_with_findings,
|
|
102
|
+
"total_cost_usd": report.total_cost_usd,
|
|
103
|
+
"waste_cost_usd": report.waste_cost_usd,
|
|
104
|
+
"by_kind": by_kind,
|
|
105
|
+
"patches": patches,
|
|
106
|
+
"project_patterns": project_patterns,
|
|
107
|
+
}
|
|
108
|
+
return json.dumps(obj)
|
|
109
|
+
|
|
110
|
+
|
|
67
111
|
def write(
|
|
68
112
|
diagnoses: list[tuple[Diagnosis, SessionTrace]],
|
|
69
113
|
out: IO[str],
|
|
@@ -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)
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "cctx-cli"
|
|
7
|
-
version = "1.
|
|
7
|
+
version = "1.11.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"
|
|
@@ -488,17 +488,42 @@ def test_autopsy_json_outputs_valid_json(runner, session_jsonl):
|
|
|
488
488
|
assert "findings" in data
|
|
489
489
|
|
|
490
490
|
|
|
491
|
-
def
|
|
492
|
-
"""--json + --since →
|
|
491
|
+
def test_autopsy_json_aggregate_outputs_valid_json(runner, tmp_path):
|
|
492
|
+
"""--json + --since → valid aggregate JSON with expected top-level keys."""
|
|
493
493
|
from cctx.cli import cli
|
|
494
494
|
|
|
495
495
|
project_dir = tmp_path / "-Users-test-Projects-demo"
|
|
496
496
|
project_dir.mkdir()
|
|
497
497
|
|
|
498
|
+
session_id = "json-agg-test-01"
|
|
499
|
+
line = {
|
|
500
|
+
"type": "user",
|
|
501
|
+
"uuid": f"{session_id}-u1",
|
|
502
|
+
"parentUuid": None,
|
|
503
|
+
"isSidechain": False,
|
|
504
|
+
"timestamp": "2026-05-14T10:00:00.000Z",
|
|
505
|
+
"sessionId": session_id,
|
|
506
|
+
"version": "2.1.138",
|
|
507
|
+
"cwd": "/Users/test/Projects/demo",
|
|
508
|
+
"gitBranch": "main",
|
|
509
|
+
"userType": "external",
|
|
510
|
+
"entrypoint": "cli",
|
|
511
|
+
"message": {"role": "user", "content": "hello"},
|
|
512
|
+
}
|
|
513
|
+
(project_dir / f"{session_id}.jsonl").write_text(json.dumps(line) + "\n")
|
|
514
|
+
|
|
498
515
|
result = runner.invoke(
|
|
499
516
|
cli, ["autopsy", str(project_dir), "--since", "7", "--json"],
|
|
517
|
+
catch_exceptions=False,
|
|
500
518
|
)
|
|
501
|
-
assert result.exit_code
|
|
519
|
+
assert result.exit_code == 0
|
|
520
|
+
data = json.loads(result.output)
|
|
521
|
+
assert "sessions_analysed" in data
|
|
522
|
+
assert "total_cost_usd" in data
|
|
523
|
+
assert "waste_cost_usd" in data
|
|
524
|
+
assert "by_kind" in data
|
|
525
|
+
assert "patches" in data
|
|
526
|
+
assert "project_patterns" in data
|
|
502
527
|
|
|
503
528
|
|
|
504
529
|
def test_autopsy_json_contains_cost(runner, session_jsonl):
|