cctx-cli 1.1.0__tar.gz → 1.2.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.1.0 → cctx_cli-1.2.0}/CHANGELOG.md +9 -0
- {cctx_cli-1.1.0 → cctx_cli-1.2.0}/PKG-INFO +1 -1
- {cctx_cli-1.1.0 → cctx_cli-1.2.0}/cctx/__init__.py +1 -1
- {cctx_cli-1.1.0 → cctx_cli-1.2.0}/cctx/cli.py +51 -10
- cctx_cli-1.2.0/cctx/exporters/json.py +23 -0
- {cctx_cli-1.1.0 → cctx_cli-1.2.0}/pyproject.toml +1 -1
- {cctx_cli-1.1.0 → cctx_cli-1.2.0}/tests/test_cli.py +159 -0
- {cctx_cli-1.1.0 → cctx_cli-1.2.0}/.github/workflows/ci.yml +0 -0
- {cctx_cli-1.1.0 → cctx_cli-1.2.0}/.github/workflows/publish.yml +0 -0
- {cctx_cli-1.1.0 → cctx_cli-1.2.0}/.github/workflows/release.yml +0 -0
- {cctx_cli-1.1.0 → cctx_cli-1.2.0}/.gitignore +0 -0
- {cctx_cli-1.1.0 → cctx_cli-1.2.0}/CLAUDE.md +0 -0
- {cctx_cli-1.1.0 → cctx_cli-1.2.0}/DESIGN.md +0 -0
- {cctx_cli-1.1.0 → cctx_cli-1.2.0}/PRODUCT.md +0 -0
- {cctx_cli-1.1.0 → cctx_cli-1.2.0}/README.md +0 -0
- {cctx_cli-1.1.0 → cctx_cli-1.2.0}/action.yml +0 -0
- {cctx_cli-1.1.0 → cctx_cli-1.2.0}/cctx/diagnostician/__init__.py +0 -0
- {cctx_cli-1.1.0 → cctx_cli-1.2.0}/cctx/diagnostician/aggregate.py +0 -0
- {cctx_cli-1.1.0 → cctx_cli-1.2.0}/cctx/diagnostician/inflection.py +0 -0
- {cctx_cli-1.1.0 → cctx_cli-1.2.0}/cctx/diagnostician/patterns/__init__.py +0 -0
- {cctx_cli-1.1.0 → cctx_cli-1.2.0}/cctx/diagnostician/patterns/dead_end.py +0 -0
- {cctx_cli-1.1.0 → cctx_cli-1.2.0}/cctx/diagnostician/patterns/retry_loop.py +0 -0
- {cctx_cli-1.1.0 → cctx_cli-1.2.0}/cctx/diagnostician/patterns/scope_creep.py +0 -0
- {cctx_cli-1.1.0 → cctx_cli-1.2.0}/cctx/diagnostician/patterns/stale_context.py +0 -0
- {cctx_cli-1.1.0 → cctx_cli-1.2.0}/cctx/diagnostician/patterns/tool_thrash.py +0 -0
- {cctx_cli-1.1.0 → cctx_cli-1.2.0}/cctx/discovery.py +0 -0
- {cctx_cli-1.1.0 → cctx_cli-1.2.0}/cctx/exporters/__init__.py +0 -0
- {cctx_cli-1.1.0 → cctx_cli-1.2.0}/cctx/exporters/csv.py +0 -0
- {cctx_cli-1.1.0 → cctx_cli-1.2.0}/cctx/exporters/jsonl.py +0 -0
- {cctx_cli-1.1.0 → cctx_cli-1.2.0}/cctx/harvest.py +0 -0
- {cctx_cli-1.1.0 → cctx_cli-1.2.0}/cctx/models.py +0 -0
- {cctx_cli-1.1.0 → cctx_cli-1.2.0}/cctx/parsers/__init__.py +0 -0
- {cctx_cli-1.1.0 → cctx_cli-1.2.0}/cctx/parsers/claude_code.py +0 -0
- {cctx_cli-1.1.0 → cctx_cli-1.2.0}/cctx/pricing.py +0 -0
- {cctx_cli-1.1.0 → cctx_cli-1.2.0}/cctx/recommender/__init__.py +0 -0
- {cctx_cli-1.1.0 → cctx_cli-1.2.0}/cctx/recommender/claude_md.py +0 -0
- {cctx_cli-1.1.0 → cctx_cli-1.2.0}/cctx/recommender/evidence.py +0 -0
- {cctx_cli-1.1.0 → cctx_cli-1.2.0}/cctx/renderers/__init__.py +0 -0
- {cctx_cli-1.1.0 → cctx_cli-1.2.0}/cctx/renderers/github.py +0 -0
- {cctx_cli-1.1.0 → cctx_cli-1.2.0}/cctx/renderers/report.py +0 -0
- {cctx_cli-1.1.0 → cctx_cli-1.2.0}/cctx/renderers/templates/autopsy.html.j2 +0 -0
- {cctx_cli-1.1.0 → cctx_cli-1.2.0}/cctx/renderers/terminal.py +0 -0
- {cctx_cli-1.1.0 → cctx_cli-1.2.0}/cctx/renderers/trace_tui.py +0 -0
- {cctx_cli-1.1.0 → cctx_cli-1.2.0}/cctx/tokenizer.py +0 -0
- {cctx_cli-1.1.0 → cctx_cli-1.2.0}/cctx/watcher.py +0 -0
- {cctx_cli-1.1.0 → cctx_cli-1.2.0}/cctx-project-brief.md +0 -0
- {cctx_cli-1.1.0 → cctx_cli-1.2.0}/demo.gif +0 -0
- {cctx_cli-1.1.0 → cctx_cli-1.2.0}/demo.tape +0 -0
- {cctx_cli-1.1.0 → cctx_cli-1.2.0}/docs/health-reviews/2026-05-15-deep-review-summary.md +0 -0
- {cctx_cli-1.1.0 → cctx_cli-1.2.0}/docs/health-reviews/2026-05-15-health-review.md +0 -0
- {cctx_cli-1.1.0 → cctx_cli-1.2.0}/docs/product-reviews/2026-05-15-product-review.md +0 -0
- {cctx_cli-1.1.0 → cctx_cli-1.2.0}/docs/superpowers/plans/2026-05-12-claude-code-parser.md +0 -0
- {cctx_cli-1.1.0 → cctx_cli-1.2.0}/docs/superpowers/plans/2026-05-14-autopsy-v0.md +0 -0
- {cctx_cli-1.1.0 → cctx_cli-1.2.0}/docs/superpowers/plans/2026-05-16-readme-pypi-release.md +0 -0
- {cctx_cli-1.1.0 → cctx_cli-1.2.0}/docs/superpowers/specs/2026-05-12-claude-code-parser-design.md +0 -0
- {cctx_cli-1.1.0 → cctx_cli-1.2.0}/docs/superpowers/specs/2026-05-14-autopsy-design.md +0 -0
- {cctx_cli-1.1.0 → cctx_cli-1.2.0}/docs/superpowers/specs/2026-05-14-harvest-design.md +0 -0
- {cctx_cli-1.1.0 → cctx_cli-1.2.0}/docs/superpowers/specs/2026-05-14-trace-tui-design.md +0 -0
- {cctx_cli-1.1.0 → cctx_cli-1.2.0}/docs/superpowers/specs/2026-05-16-readme-pypi-release-design.md +0 -0
- {cctx_cli-1.1.0 → cctx_cli-1.2.0}/tests/__init__.py +0 -0
- {cctx_cli-1.1.0 → cctx_cli-1.2.0}/tests/conftest.py +0 -0
- {cctx_cli-1.1.0 → cctx_cli-1.2.0}/tests/diagnostician/__init__.py +0 -0
- {cctx_cli-1.1.0 → cctx_cli-1.2.0}/tests/diagnostician/conftest.py +0 -0
- {cctx_cli-1.1.0 → cctx_cli-1.2.0}/tests/diagnostician/test_dead_end.py +0 -0
- {cctx_cli-1.1.0 → cctx_cli-1.2.0}/tests/diagnostician/test_inflection.py +0 -0
- {cctx_cli-1.1.0 → cctx_cli-1.2.0}/tests/diagnostician/test_orchestrator.py +0 -0
- {cctx_cli-1.1.0 → cctx_cli-1.2.0}/tests/diagnostician/test_retry_loop.py +0 -0
- {cctx_cli-1.1.0 → cctx_cli-1.2.0}/tests/diagnostician/test_scope_creep.py +0 -0
- {cctx_cli-1.1.0 → cctx_cli-1.2.0}/tests/diagnostician/test_stale_context.py +0 -0
- {cctx_cli-1.1.0 → cctx_cli-1.2.0}/tests/diagnostician/test_tool_thrash.py +0 -0
- {cctx_cli-1.1.0 → cctx_cli-1.2.0}/tests/exporters/__init__.py +0 -0
- {cctx_cli-1.1.0 → cctx_cli-1.2.0}/tests/exporters/test_csv.py +0 -0
- {cctx_cli-1.1.0 → cctx_cli-1.2.0}/tests/exporters/test_jsonl.py +0 -0
- {cctx_cli-1.1.0 → cctx_cli-1.2.0}/tests/fixtures/claude_code/README.md +0 -0
- {cctx_cli-1.1.0 → cctx_cli-1.2.0}/tests/fixtures/claude_code/short-clean/short-clean.jsonl +0 -0
- {cctx_cli-1.1.0 → cctx_cli-1.2.0}/tests/fixtures/claude_code/with-attachments/with-attachments/subagents/agent-a0b4c2cf1dde0ca56.meta.json +0 -0
- {cctx_cli-1.1.0 → cctx_cli-1.2.0}/tests/fixtures/claude_code/with-attachments/with-attachments/subagents/agent-a116ae34b1b09c332.meta.json +0 -0
- {cctx_cli-1.1.0 → cctx_cli-1.2.0}/tests/fixtures/claude_code/with-attachments/with-attachments/subagents/agent-a1c4c417b35658c9e.meta.json +0 -0
- {cctx_cli-1.1.0 → cctx_cli-1.2.0}/tests/fixtures/claude_code/with-attachments/with-attachments/subagents/agent-a1e41a901de38f1b5.meta.json +0 -0
- {cctx_cli-1.1.0 → cctx_cli-1.2.0}/tests/fixtures/claude_code/with-attachments/with-attachments/subagents/agent-a338f8d0c74612a24.meta.json +0 -0
- {cctx_cli-1.1.0 → cctx_cli-1.2.0}/tests/fixtures/claude_code/with-attachments/with-attachments/subagents/agent-a34f6f3c0e7094186.meta.json +0 -0
- {cctx_cli-1.1.0 → cctx_cli-1.2.0}/tests/fixtures/claude_code/with-attachments/with-attachments/subagents/agent-a5a5a0cff4d13308b.meta.json +0 -0
- {cctx_cli-1.1.0 → cctx_cli-1.2.0}/tests/fixtures/claude_code/with-attachments/with-attachments/subagents/agent-a6b0a3da6a0484db5.meta.json +0 -0
- {cctx_cli-1.1.0 → cctx_cli-1.2.0}/tests/fixtures/claude_code/with-attachments/with-attachments/subagents/agent-a7f73f1790b02cde5.meta.json +0 -0
- {cctx_cli-1.1.0 → cctx_cli-1.2.0}/tests/fixtures/claude_code/with-attachments/with-attachments/subagents/agent-a7f7c17c38a9d8788.meta.json +0 -0
- {cctx_cli-1.1.0 → cctx_cli-1.2.0}/tests/fixtures/claude_code/with-attachments/with-attachments/subagents/agent-a853259e2cd7bbe8a.meta.json +0 -0
- {cctx_cli-1.1.0 → cctx_cli-1.2.0}/tests/fixtures/claude_code/with-attachments/with-attachments/subagents/agent-a8d9aedb0d0c6e12d.meta.json +0 -0
- {cctx_cli-1.1.0 → cctx_cli-1.2.0}/tests/fixtures/claude_code/with-attachments/with-attachments/subagents/agent-aa778bc1d59e4a441.meta.json +0 -0
- {cctx_cli-1.1.0 → cctx_cli-1.2.0}/tests/fixtures/claude_code/with-attachments/with-attachments/subagents/agent-aba869dedee4a12ba.meta.json +0 -0
- {cctx_cli-1.1.0 → cctx_cli-1.2.0}/tests/fixtures/claude_code/with-attachments/with-attachments/subagents/agent-ada2746d9774b94db.meta.json +0 -0
- {cctx_cli-1.1.0 → cctx_cli-1.2.0}/tests/fixtures/claude_code/with-attachments/with-attachments/subagents/agent-aea0132068c64d2dd.meta.json +0 -0
- {cctx_cli-1.1.0 → cctx_cli-1.2.0}/tests/fixtures/claude_code/with-attachments/with-attachments/subagents/agent-aea215eff50874d5f.meta.json +0 -0
- {cctx_cli-1.1.0 → cctx_cli-1.2.0}/tests/fixtures/claude_code/with-attachments/with-attachments/subagents/agent-afee21f2b3852a4a0.meta.json +0 -0
- {cctx_cli-1.1.0 → cctx_cli-1.2.0}/tests/fixtures/claude_code/with-attachments/with-attachments.jsonl +0 -0
- {cctx_cli-1.1.0 → cctx_cli-1.2.0}/tests/fixtures/claude_code/with-compaction/with-compaction/subagents/agent-a051d9c9a6b2f5cc3.jsonl +0 -0
- {cctx_cli-1.1.0 → cctx_cli-1.2.0}/tests/fixtures/claude_code/with-compaction/with-compaction/subagents/agent-a051d9c9a6b2f5cc3.meta.json +0 -0
- {cctx_cli-1.1.0 → cctx_cli-1.2.0}/tests/fixtures/claude_code/with-compaction/with-compaction/subagents/agent-a171f16f4e65cfe75.jsonl +0 -0
- {cctx_cli-1.1.0 → cctx_cli-1.2.0}/tests/fixtures/claude_code/with-compaction/with-compaction/subagents/agent-a171f16f4e65cfe75.meta.json +0 -0
- {cctx_cli-1.1.0 → cctx_cli-1.2.0}/tests/fixtures/claude_code/with-compaction/with-compaction/subagents/agent-a1b77fea2c0a2269b.jsonl +0 -0
- {cctx_cli-1.1.0 → cctx_cli-1.2.0}/tests/fixtures/claude_code/with-compaction/with-compaction/subagents/agent-a1b77fea2c0a2269b.meta.json +0 -0
- {cctx_cli-1.1.0 → cctx_cli-1.2.0}/tests/fixtures/claude_code/with-compaction/with-compaction/subagents/agent-a20da4c01a54acca8.jsonl +0 -0
- {cctx_cli-1.1.0 → cctx_cli-1.2.0}/tests/fixtures/claude_code/with-compaction/with-compaction/subagents/agent-a20da4c01a54acca8.meta.json +0 -0
- {cctx_cli-1.1.0 → cctx_cli-1.2.0}/tests/fixtures/claude_code/with-compaction/with-compaction/subagents/agent-a3c82739b1383fb14.jsonl +0 -0
- {cctx_cli-1.1.0 → cctx_cli-1.2.0}/tests/fixtures/claude_code/with-compaction/with-compaction/subagents/agent-a3c82739b1383fb14.meta.json +0 -0
- {cctx_cli-1.1.0 → cctx_cli-1.2.0}/tests/fixtures/claude_code/with-compaction/with-compaction/subagents/agent-a49e8539611c5fe12.jsonl +0 -0
- {cctx_cli-1.1.0 → cctx_cli-1.2.0}/tests/fixtures/claude_code/with-compaction/with-compaction/subagents/agent-a49e8539611c5fe12.meta.json +0 -0
- {cctx_cli-1.1.0 → cctx_cli-1.2.0}/tests/fixtures/claude_code/with-compaction/with-compaction/subagents/agent-a7bb58f3fff2b3e8d.jsonl +0 -0
- {cctx_cli-1.1.0 → cctx_cli-1.2.0}/tests/fixtures/claude_code/with-compaction/with-compaction/subagents/agent-a7bb58f3fff2b3e8d.meta.json +0 -0
- {cctx_cli-1.1.0 → cctx_cli-1.2.0}/tests/fixtures/claude_code/with-compaction/with-compaction/subagents/agent-a92b48c0331195aac.jsonl +0 -0
- {cctx_cli-1.1.0 → cctx_cli-1.2.0}/tests/fixtures/claude_code/with-compaction/with-compaction/subagents/agent-a92b48c0331195aac.meta.json +0 -0
- {cctx_cli-1.1.0 → cctx_cli-1.2.0}/tests/fixtures/claude_code/with-compaction/with-compaction/subagents/agent-ab96c4264099694a9.jsonl +0 -0
- {cctx_cli-1.1.0 → cctx_cli-1.2.0}/tests/fixtures/claude_code/with-compaction/with-compaction/subagents/agent-ab96c4264099694a9.meta.json +0 -0
- {cctx_cli-1.1.0 → cctx_cli-1.2.0}/tests/fixtures/claude_code/with-compaction/with-compaction/subagents/agent-acb2895c5e34ffec0.jsonl +0 -0
- {cctx_cli-1.1.0 → cctx_cli-1.2.0}/tests/fixtures/claude_code/with-compaction/with-compaction/subagents/agent-acb2895c5e34ffec0.meta.json +0 -0
- {cctx_cli-1.1.0 → cctx_cli-1.2.0}/tests/fixtures/claude_code/with-compaction/with-compaction/subagents/agent-adb2302769938fb3f.jsonl +0 -0
- {cctx_cli-1.1.0 → cctx_cli-1.2.0}/tests/fixtures/claude_code/with-compaction/with-compaction/subagents/agent-adb2302769938fb3f.meta.json +0 -0
- {cctx_cli-1.1.0 → cctx_cli-1.2.0}/tests/fixtures/claude_code/with-compaction/with-compaction/subagents/agent-ae585eca15cb93b9c.jsonl +0 -0
- {cctx_cli-1.1.0 → cctx_cli-1.2.0}/tests/fixtures/claude_code/with-compaction/with-compaction/subagents/agent-ae585eca15cb93b9c.meta.json +0 -0
- {cctx_cli-1.1.0 → cctx_cli-1.2.0}/tests/fixtures/claude_code/with-compaction/with-compaction/subagents/agent-aec9c917feb903d67.jsonl +0 -0
- {cctx_cli-1.1.0 → cctx_cli-1.2.0}/tests/fixtures/claude_code/with-compaction/with-compaction/subagents/agent-aec9c917feb903d67.meta.json +0 -0
- {cctx_cli-1.1.0 → cctx_cli-1.2.0}/tests/fixtures/claude_code/with-compaction/with-compaction.jsonl +0 -0
- {cctx_cli-1.1.0 → cctx_cli-1.2.0}/tests/fixtures/claude_code/with-subagents/with-subagents/subagents/agent-a1a3a21aeb76bb0a9.jsonl +0 -0
- {cctx_cli-1.1.0 → cctx_cli-1.2.0}/tests/fixtures/claude_code/with-subagents/with-subagents/subagents/agent-a1a3a21aeb76bb0a9.meta.json +0 -0
- {cctx_cli-1.1.0 → cctx_cli-1.2.0}/tests/fixtures/claude_code/with-subagents/with-subagents/subagents/agent-aaa1d6ecc05a78442.jsonl +0 -0
- {cctx_cli-1.1.0 → cctx_cli-1.2.0}/tests/fixtures/claude_code/with-subagents/with-subagents/subagents/agent-aaa1d6ecc05a78442.meta.json +0 -0
- {cctx_cli-1.1.0 → cctx_cli-1.2.0}/tests/fixtures/claude_code/with-subagents/with-subagents/subagents/agent-af3c545ccd30036d2.jsonl +0 -0
- {cctx_cli-1.1.0 → cctx_cli-1.2.0}/tests/fixtures/claude_code/with-subagents/with-subagents/subagents/agent-af3c545ccd30036d2.meta.json +0 -0
- {cctx_cli-1.1.0 → cctx_cli-1.2.0}/tests/fixtures/claude_code/with-subagents/with-subagents/tool-results/btwp2bzro.txt +0 -0
- {cctx_cli-1.1.0 → cctx_cli-1.2.0}/tests/fixtures/claude_code/with-subagents/with-subagents/tool-results/byqjbgy4b.txt +0 -0
- {cctx_cli-1.1.0 → cctx_cli-1.2.0}/tests/fixtures/claude_code/with-subagents/with-subagents.jsonl +0 -0
- {cctx_cli-1.1.0 → cctx_cli-1.2.0}/tests/fixtures/claude_code/with-tool-results/with-tool-results/tool-results/bosbkda0h.txt +0 -0
- {cctx_cli-1.1.0 → cctx_cli-1.2.0}/tests/fixtures/claude_code/with-tool-results/with-tool-results.jsonl +0 -0
- {cctx_cli-1.1.0 → cctx_cli-1.2.0}/tests/fixtures/scrub.py +0 -0
- {cctx_cli-1.1.0 → cctx_cli-1.2.0}/tests/fixtures/synthetic/bookkeeping_only.jsonl +0 -0
- {cctx_cli-1.1.0 → cctx_cli-1.2.0}/tests/fixtures/synthetic/malformed_middle.jsonl +0 -0
- {cctx_cli-1.1.0 → cctx_cli-1.2.0}/tests/fixtures/synthetic/truncated_final_line.jsonl +0 -0
- {cctx_cli-1.1.0 → cctx_cli-1.2.0}/tests/fixtures/synthetic/unknown_attachment_shape.jsonl +0 -0
- {cctx_cli-1.1.0 → cctx_cli-1.2.0}/tests/fixtures/synthetic/unknown_type.jsonl +0 -0
- {cctx_cli-1.1.0 → cctx_cli-1.2.0}/tests/parsers/__init__.py +0 -0
- {cctx_cli-1.1.0 → cctx_cli-1.2.0}/tests/parsers/test_claude_code.py +0 -0
- {cctx_cli-1.1.0 → cctx_cli-1.2.0}/tests/parsers/test_claude_code_integration.py +0 -0
- {cctx_cli-1.1.0 → cctx_cli-1.2.0}/tests/recommender/__init__.py +0 -0
- {cctx_cli-1.1.0 → cctx_cli-1.2.0}/tests/recommender/test_claude_md.py +0 -0
- {cctx_cli-1.1.0 → cctx_cli-1.2.0}/tests/recommender/test_evidence.py +0 -0
- {cctx_cli-1.1.0 → cctx_cli-1.2.0}/tests/renderers/__init__.py +0 -0
- {cctx_cli-1.1.0 → cctx_cli-1.2.0}/tests/renderers/test_report.py +0 -0
- {cctx_cli-1.1.0 → cctx_cli-1.2.0}/tests/renderers/test_terminal_renderer_full.py +0 -0
- {cctx_cli-1.1.0 → cctx_cli-1.2.0}/tests/test_aggregate.py +0 -0
- {cctx_cli-1.1.0 → cctx_cli-1.2.0}/tests/test_cli_export.py +0 -0
- {cctx_cli-1.1.0 → cctx_cli-1.2.0}/tests/test_discovery.py +0 -0
- {cctx_cli-1.1.0 → cctx_cli-1.2.0}/tests/test_github_summary.py +0 -0
- {cctx_cli-1.1.0 → cctx_cli-1.2.0}/tests/test_harvest.py +0 -0
- {cctx_cli-1.1.0 → cctx_cli-1.2.0}/tests/test_harvest_check.py +0 -0
- {cctx_cli-1.1.0 → cctx_cli-1.2.0}/tests/test_models.py +0 -0
- {cctx_cli-1.1.0 → cctx_cli-1.2.0}/tests/test_smoke.py +0 -0
- {cctx_cli-1.1.0 → cctx_cli-1.2.0}/tests/test_terminal_renderer.py +0 -0
- {cctx_cli-1.1.0 → cctx_cli-1.2.0}/tests/test_tokenizer.py +0 -0
- {cctx_cli-1.1.0 → cctx_cli-1.2.0}/tests/test_trace_tui.py +0 -0
- {cctx_cli-1.1.0 → cctx_cli-1.2.0}/tests/test_watcher.py +0 -0
|
@@ -2,6 +2,15 @@
|
|
|
2
2
|
|
|
3
3
|
<!-- version list -->
|
|
4
4
|
|
|
5
|
+
## v1.2.0 (2026-05-17)
|
|
6
|
+
|
|
7
|
+
### Features
|
|
8
|
+
|
|
9
|
+
- --until DATE, autopsy --json, export --format json (M12 #77 #78 #79)
|
|
10
|
+
([#84](https://github.com/jacquardlabs/cctx/pull/84),
|
|
11
|
+
[`803b5f1`](https://github.com/jacquardlabs/cctx/commit/803b5f190404679ddef4cbbec7478d04c57b8413))
|
|
12
|
+
|
|
13
|
+
|
|
5
14
|
## v1.1.0 (2026-05-17)
|
|
6
15
|
|
|
7
16
|
### Chores
|
|
@@ -14,6 +14,7 @@ from __future__ import annotations
|
|
|
14
14
|
|
|
15
15
|
from datetime import datetime, timedelta, timezone
|
|
16
16
|
from pathlib import Path
|
|
17
|
+
from typing import IO
|
|
17
18
|
|
|
18
19
|
import rich_click as click
|
|
19
20
|
|
|
@@ -218,6 +219,14 @@ def ls(project: Path | None) -> None:
|
|
|
218
219
|
type=str,
|
|
219
220
|
help="Cross-session mode: 7, 7d, 2w, 2026-05-01, or 2026-05-01..2026-05-15.",
|
|
220
221
|
)
|
|
222
|
+
@click.option(
|
|
223
|
+
"--until",
|
|
224
|
+
"until_date",
|
|
225
|
+
default=None,
|
|
226
|
+
metavar="DATE",
|
|
227
|
+
type=str,
|
|
228
|
+
help="End date for --since window (YYYY-MM-DD). Requires --since.",
|
|
229
|
+
)
|
|
221
230
|
@click.option(
|
|
222
231
|
"--latest",
|
|
223
232
|
is_flag=True,
|
|
@@ -262,15 +271,24 @@ def ls(project: Path | None) -> None:
|
|
|
262
271
|
type=click.IntRange(min=1),
|
|
263
272
|
help="Show details for turn N (single-session only).",
|
|
264
273
|
)
|
|
274
|
+
@click.option(
|
|
275
|
+
"--json",
|
|
276
|
+
"json_out",
|
|
277
|
+
is_flag=True,
|
|
278
|
+
default=False,
|
|
279
|
+
help="Output diagnosis as JSON to stdout (single-session only).",
|
|
280
|
+
)
|
|
265
281
|
def autopsy(
|
|
266
282
|
target: Path | None,
|
|
267
283
|
since: str | None,
|
|
284
|
+
until_date: str | None,
|
|
268
285
|
latest: bool,
|
|
269
286
|
html_out: Path | None,
|
|
270
287
|
github_summary: bool,
|
|
271
288
|
fail_on_findings: bool,
|
|
272
289
|
top_n: int | None,
|
|
273
290
|
turn_num: int | None,
|
|
291
|
+
json_out: bool,
|
|
274
292
|
) -> None:
|
|
275
293
|
"""Diagnose a session or project directory.
|
|
276
294
|
|
|
@@ -290,6 +308,10 @@ def autopsy(
|
|
|
290
308
|
raise click.UsageError("--top requires --since.")
|
|
291
309
|
if turn_num is not None and since is not None:
|
|
292
310
|
raise click.UsageError("--turn is not supported with --since.")
|
|
311
|
+
if until_date is not None and since is None:
|
|
312
|
+
raise click.UsageError("--until requires --since.")
|
|
313
|
+
if json_out and since is not None:
|
|
314
|
+
raise click.UsageError("--json is not supported with --since.")
|
|
293
315
|
|
|
294
316
|
if target is None:
|
|
295
317
|
if not latest:
|
|
@@ -326,6 +348,16 @@ def autopsy(
|
|
|
326
348
|
# Cross-session path
|
|
327
349
|
project_dir = target if target.is_dir() else target.parent
|
|
328
350
|
start, end, label = parse_since(since)
|
|
351
|
+
if until_date is not None:
|
|
352
|
+
try:
|
|
353
|
+
end = datetime.fromisoformat(until_date.strip()).replace(
|
|
354
|
+
tzinfo=UTC, hour=23, minute=59, second=59
|
|
355
|
+
)
|
|
356
|
+
except ValueError:
|
|
357
|
+
raise click.UsageError(
|
|
358
|
+
f"Invalid --until date '{until_date}'. Expected YYYY-MM-DD."
|
|
359
|
+
) from None
|
|
360
|
+
label = f"{label} until {until_date.strip()}"
|
|
329
361
|
diagnoses = aggregate.run(project_dir, start, end)
|
|
330
362
|
ev = evidence_mod.accumulate(diagnoses)
|
|
331
363
|
if top_n is not None:
|
|
@@ -352,7 +384,12 @@ def autopsy(
|
|
|
352
384
|
trace = tokenize_session(parse_session(target))
|
|
353
385
|
diagnosis = diagnostician.run(trace)
|
|
354
386
|
diagnosis = claude_md.generate(diagnosis)
|
|
355
|
-
if
|
|
387
|
+
if json_out:
|
|
388
|
+
import json as _json
|
|
389
|
+
|
|
390
|
+
from cctx.exporters.jsonl import export_diagnosis as _export_diag
|
|
391
|
+
click.echo(_json.dumps(_json.loads(_export_diag(diagnosis, trace)), indent=2))
|
|
392
|
+
elif turn_num is not None:
|
|
356
393
|
render_turn(trace, diagnosis, turn_num)
|
|
357
394
|
elif html_out is not None:
|
|
358
395
|
from cctx.renderers.report import render_html
|
|
@@ -372,10 +409,10 @@ def autopsy(
|
|
|
372
409
|
@click.option(
|
|
373
410
|
"--format",
|
|
374
411
|
"fmt",
|
|
375
|
-
type=click.Choice(["jsonl", "csv"]),
|
|
412
|
+
type=click.Choice(["jsonl", "csv", "json"]),
|
|
376
413
|
default="jsonl",
|
|
377
414
|
show_default=True,
|
|
378
|
-
help="Output format: jsonl (one object per session)
|
|
415
|
+
help="Output format: jsonl (one object per session), csv (one row per turn), or json (array).",
|
|
379
416
|
)
|
|
380
417
|
@click.option(
|
|
381
418
|
"--out",
|
|
@@ -397,6 +434,7 @@ def export(target: Path, fmt: str, out: Path | None, no_content: bool) -> None:
|
|
|
397
434
|
import sys
|
|
398
435
|
|
|
399
436
|
from cctx.exporters import csv as csv_mod
|
|
437
|
+
from cctx.exporters import json as json_mod
|
|
400
438
|
from cctx.exporters import jsonl as jsonl_mod
|
|
401
439
|
|
|
402
440
|
trace = tokenize_session(parse_session(target))
|
|
@@ -404,16 +442,19 @@ def export(target: Path, fmt: str, out: Path | None, no_content: bool) -> None:
|
|
|
404
442
|
diagnosis = claude_md.generate(diagnosis)
|
|
405
443
|
pairs = [(diagnosis, trace)]
|
|
406
444
|
|
|
445
|
+
def _write(fh: IO[str]) -> None:
|
|
446
|
+
if fmt == "jsonl":
|
|
447
|
+
jsonl_mod.write(pairs, fh, include_content=not no_content)
|
|
448
|
+
elif fmt == "json":
|
|
449
|
+
json_mod.write(pairs, fh, include_content=not no_content)
|
|
450
|
+
else:
|
|
451
|
+
csv_mod.write(pairs, fh)
|
|
452
|
+
|
|
407
453
|
if out is not None:
|
|
408
454
|
with open(out, "w", encoding="utf-8") as fh:
|
|
409
|
-
|
|
410
|
-
jsonl_mod.write(pairs, fh, include_content=not no_content)
|
|
411
|
-
else:
|
|
412
|
-
csv_mod.write(pairs, fh)
|
|
413
|
-
elif fmt == "jsonl":
|
|
414
|
-
jsonl_mod.write(pairs, sys.stdout, include_content=not no_content)
|
|
455
|
+
_write(fh)
|
|
415
456
|
else:
|
|
416
|
-
|
|
457
|
+
_write(sys.stdout)
|
|
417
458
|
|
|
418
459
|
|
|
419
460
|
@cli.command()
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
"""JSON exporter — full session array as pretty-printed JSON."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
import json
|
|
5
|
+
from typing import IO, TYPE_CHECKING
|
|
6
|
+
|
|
7
|
+
if TYPE_CHECKING:
|
|
8
|
+
from cctx.models import Diagnosis, SessionTrace
|
|
9
|
+
|
|
10
|
+
from cctx.exporters.jsonl import export_diagnosis
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def write(
|
|
14
|
+
diagnoses: list[tuple[Diagnosis, SessionTrace]],
|
|
15
|
+
out: IO[str],
|
|
16
|
+
*,
|
|
17
|
+
include_content: bool = True,
|
|
18
|
+
) -> None:
|
|
19
|
+
objects = [
|
|
20
|
+
json.loads(export_diagnosis(diagnosis, trace, include_content=include_content))
|
|
21
|
+
for diagnosis, trace in diagnoses
|
|
22
|
+
]
|
|
23
|
+
out.write(json.dumps(objects, indent=2) + "\n")
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "cctx-cli"
|
|
7
|
-
version = "1.
|
|
7
|
+
version = "1.2.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"
|
|
@@ -389,3 +389,162 @@ def test_turn_out_of_range_shows_not_found(runner, session_jsonl):
|
|
|
389
389
|
)
|
|
390
390
|
assert result.exit_code == 0
|
|
391
391
|
assert "not found" in result.output.lower() or "999" in result.output
|
|
392
|
+
|
|
393
|
+
|
|
394
|
+
# ---------------------------------------------------------------------------
|
|
395
|
+
# --until DATE tests (#77)
|
|
396
|
+
# ---------------------------------------------------------------------------
|
|
397
|
+
|
|
398
|
+
|
|
399
|
+
def test_until_requires_since(runner, session_jsonl):
|
|
400
|
+
"""--until without --since → non-zero exit (UsageError)."""
|
|
401
|
+
from cctx.cli import cli
|
|
402
|
+
|
|
403
|
+
result = runner.invoke(cli, ["autopsy", str(session_jsonl), "--until", "2026-05-15"])
|
|
404
|
+
assert result.exit_code != 0
|
|
405
|
+
assert "since" in result.output.lower() or "Error" in result.output
|
|
406
|
+
|
|
407
|
+
|
|
408
|
+
def test_until_with_since_accepted(runner, tmp_path):
|
|
409
|
+
"""--until DATE + --since → exit 0."""
|
|
410
|
+
from cctx.cli import cli
|
|
411
|
+
|
|
412
|
+
project_dir = tmp_path / "-Users-test-Projects-demo"
|
|
413
|
+
project_dir.mkdir()
|
|
414
|
+
session_id = "until-test-sess"
|
|
415
|
+
line = {
|
|
416
|
+
"type": "user", "uuid": f"{session_id}-u1", "parentUuid": None,
|
|
417
|
+
"isSidechain": False, "timestamp": "2026-05-14T10:00:00.000Z",
|
|
418
|
+
"sessionId": session_id, "version": "2.1.138",
|
|
419
|
+
"cwd": "/Users/test/Projects/demo", "gitBranch": "main",
|
|
420
|
+
"userType": "external", "entrypoint": "cli",
|
|
421
|
+
"message": {"role": "user", "content": "hello"},
|
|
422
|
+
}
|
|
423
|
+
(project_dir / f"{session_id}.jsonl").write_text(json.dumps(line) + "\n")
|
|
424
|
+
|
|
425
|
+
result = runner.invoke(
|
|
426
|
+
cli,
|
|
427
|
+
["autopsy", str(project_dir), "--since", "7", "--until", "2026-05-15"],
|
|
428
|
+
catch_exceptions=False,
|
|
429
|
+
)
|
|
430
|
+
assert result.exit_code == 0
|
|
431
|
+
|
|
432
|
+
|
|
433
|
+
def test_until_invalid_date(runner, tmp_path):
|
|
434
|
+
"""--until with a non-date string → non-zero exit."""
|
|
435
|
+
from cctx.cli import cli
|
|
436
|
+
|
|
437
|
+
project_dir = tmp_path / "-Users-test-Projects-demo"
|
|
438
|
+
project_dir.mkdir()
|
|
439
|
+
|
|
440
|
+
result = runner.invoke(
|
|
441
|
+
cli, ["autopsy", str(project_dir), "--since", "7", "--until", "not-a-date"],
|
|
442
|
+
)
|
|
443
|
+
assert result.exit_code != 0
|
|
444
|
+
|
|
445
|
+
|
|
446
|
+
def test_until_label_includes_date(runner, tmp_path):
|
|
447
|
+
"""--until DATE appears in the period label in aggregate output."""
|
|
448
|
+
from cctx.cli import cli
|
|
449
|
+
|
|
450
|
+
project_dir = tmp_path / "-Users-test-Projects-demo"
|
|
451
|
+
project_dir.mkdir()
|
|
452
|
+
session_id = "until-label-sess"
|
|
453
|
+
line = {
|
|
454
|
+
"type": "user", "uuid": f"{session_id}-u1", "parentUuid": None,
|
|
455
|
+
"isSidechain": False, "timestamp": "2026-05-10T10:00:00.000Z",
|
|
456
|
+
"sessionId": session_id, "version": "2.1.138",
|
|
457
|
+
"cwd": "/Users/test/Projects/demo", "gitBranch": "main",
|
|
458
|
+
"userType": "external", "entrypoint": "cli",
|
|
459
|
+
"message": {"role": "user", "content": "hello"},
|
|
460
|
+
}
|
|
461
|
+
(project_dir / f"{session_id}.jsonl").write_text(json.dumps(line) + "\n")
|
|
462
|
+
|
|
463
|
+
result = runner.invoke(
|
|
464
|
+
cli,
|
|
465
|
+
["autopsy", str(project_dir), "--since", "30", "--until", "2026-05-15"],
|
|
466
|
+
catch_exceptions=False,
|
|
467
|
+
)
|
|
468
|
+
assert result.exit_code == 0
|
|
469
|
+
assert "2026-05-15" in result.output
|
|
470
|
+
|
|
471
|
+
|
|
472
|
+
# ---------------------------------------------------------------------------
|
|
473
|
+
# autopsy --json tests (#78)
|
|
474
|
+
# ---------------------------------------------------------------------------
|
|
475
|
+
|
|
476
|
+
|
|
477
|
+
def test_autopsy_json_outputs_valid_json(runner, session_jsonl):
|
|
478
|
+
"""--json flag produces valid JSON on stdout."""
|
|
479
|
+
from cctx.cli import cli
|
|
480
|
+
|
|
481
|
+
result = runner.invoke(
|
|
482
|
+
cli, ["autopsy", str(session_jsonl), "--json"],
|
|
483
|
+
catch_exceptions=False,
|
|
484
|
+
)
|
|
485
|
+
assert result.exit_code == 0
|
|
486
|
+
data = json.loads(result.output)
|
|
487
|
+
assert "session_id" in data
|
|
488
|
+
assert "findings" in data
|
|
489
|
+
|
|
490
|
+
|
|
491
|
+
def test_autopsy_json_incompatible_with_since(runner, tmp_path):
|
|
492
|
+
"""--json + --since → non-zero exit (UsageError)."""
|
|
493
|
+
from cctx.cli import cli
|
|
494
|
+
|
|
495
|
+
project_dir = tmp_path / "-Users-test-Projects-demo"
|
|
496
|
+
project_dir.mkdir()
|
|
497
|
+
|
|
498
|
+
result = runner.invoke(
|
|
499
|
+
cli, ["autopsy", str(project_dir), "--since", "7", "--json"],
|
|
500
|
+
)
|
|
501
|
+
assert result.exit_code != 0
|
|
502
|
+
|
|
503
|
+
|
|
504
|
+
def test_autopsy_json_contains_cost(runner, session_jsonl):
|
|
505
|
+
"""--json output includes cost fields."""
|
|
506
|
+
from cctx.cli import cli
|
|
507
|
+
|
|
508
|
+
result = runner.invoke(
|
|
509
|
+
cli, ["autopsy", str(session_jsonl), "--json"],
|
|
510
|
+
catch_exceptions=False,
|
|
511
|
+
)
|
|
512
|
+
assert result.exit_code == 0
|
|
513
|
+
data = json.loads(result.output)
|
|
514
|
+
assert "total_cost_usd" in data
|
|
515
|
+
assert "waste_cost_usd" in data
|
|
516
|
+
|
|
517
|
+
|
|
518
|
+
# ---------------------------------------------------------------------------
|
|
519
|
+
# export --format json tests (#79)
|
|
520
|
+
# ---------------------------------------------------------------------------
|
|
521
|
+
|
|
522
|
+
|
|
523
|
+
def test_export_json_produces_valid_json(runner, session_jsonl):
|
|
524
|
+
"""export --format json produces a valid JSON array."""
|
|
525
|
+
from cctx.cli import cli
|
|
526
|
+
|
|
527
|
+
result = runner.invoke(
|
|
528
|
+
cli, ["export", str(session_jsonl), "--format", "json"],
|
|
529
|
+
catch_exceptions=False,
|
|
530
|
+
)
|
|
531
|
+
assert result.exit_code == 0
|
|
532
|
+
data = json.loads(result.output)
|
|
533
|
+
assert isinstance(data, list)
|
|
534
|
+
assert len(data) == 1
|
|
535
|
+
assert "session_id" in data[0]
|
|
536
|
+
|
|
537
|
+
|
|
538
|
+
def test_export_json_to_file(runner, session_jsonl, tmp_path):
|
|
539
|
+
"""export --format json --out FILE writes a valid JSON file."""
|
|
540
|
+
from cctx.cli import cli
|
|
541
|
+
|
|
542
|
+
out_path = tmp_path / "out.json"
|
|
543
|
+
result = runner.invoke(
|
|
544
|
+
cli, ["export", str(session_jsonl), "--format", "json", "--out", str(out_path)],
|
|
545
|
+
catch_exceptions=False,
|
|
546
|
+
)
|
|
547
|
+
assert result.exit_code == 0
|
|
548
|
+
data = json.loads(out_path.read_text())
|
|
549
|
+
assert isinstance(data, list)
|
|
550
|
+
assert data[0]["session_id"] == "test-sess-01"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{cctx_cli-1.1.0 → cctx_cli-1.2.0}/docs/superpowers/specs/2026-05-12-claude-code-parser-design.md
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{cctx_cli-1.1.0 → cctx_cli-1.2.0}/docs/superpowers/specs/2026-05-16-readme-pypi-release-design.md
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{cctx_cli-1.1.0 → cctx_cli-1.2.0}/tests/fixtures/claude_code/with-attachments/with-attachments.jsonl
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{cctx_cli-1.1.0 → cctx_cli-1.2.0}/tests/fixtures/claude_code/with-compaction/with-compaction.jsonl
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{cctx_cli-1.1.0 → cctx_cli-1.2.0}/tests/fixtures/claude_code/with-subagents/with-subagents.jsonl
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|