cctx-cli 0.2.0__tar.gz → 1.1.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-0.2.0 → cctx_cli-1.1.0}/.github/workflows/publish.yml +2 -14
- cctx_cli-1.1.0/.github/workflows/release.yml +37 -0
- cctx_cli-1.1.0/CHANGELOG.md +97 -0
- {cctx_cli-0.2.0 → cctx_cli-1.1.0}/PKG-INFO +1 -1
- {cctx_cli-0.2.0 → cctx_cli-1.1.0}/cctx/__init__.py +1 -1
- {cctx_cli-0.2.0 → cctx_cli-1.1.0}/cctx/cli.py +30 -3
- {cctx_cli-0.2.0 → cctx_cli-1.1.0}/cctx/models.py +7 -0
- {cctx_cli-0.2.0 → cctx_cli-1.1.0}/cctx/renderers/terminal.py +54 -1
- {cctx_cli-0.2.0 → cctx_cli-1.1.0}/pyproject.toml +13 -1
- {cctx_cli-0.2.0 → cctx_cli-1.1.0}/tests/test_cli.py +80 -0
- {cctx_cli-0.2.0 → cctx_cli-1.1.0}/tests/test_models.py +54 -0
- {cctx_cli-0.2.0 → cctx_cli-1.1.0}/tests/test_terminal_renderer.py +100 -0
- {cctx_cli-0.2.0 → cctx_cli-1.1.0}/.github/workflows/ci.yml +0 -0
- {cctx_cli-0.2.0 → cctx_cli-1.1.0}/.gitignore +0 -0
- {cctx_cli-0.2.0 → cctx_cli-1.1.0}/CLAUDE.md +0 -0
- {cctx_cli-0.2.0 → cctx_cli-1.1.0}/DESIGN.md +0 -0
- {cctx_cli-0.2.0 → cctx_cli-1.1.0}/PRODUCT.md +0 -0
- {cctx_cli-0.2.0 → cctx_cli-1.1.0}/README.md +0 -0
- {cctx_cli-0.2.0 → cctx_cli-1.1.0}/action.yml +0 -0
- {cctx_cli-0.2.0 → cctx_cli-1.1.0}/cctx/diagnostician/__init__.py +0 -0
- {cctx_cli-0.2.0 → cctx_cli-1.1.0}/cctx/diagnostician/aggregate.py +0 -0
- {cctx_cli-0.2.0 → cctx_cli-1.1.0}/cctx/diagnostician/inflection.py +0 -0
- {cctx_cli-0.2.0 → cctx_cli-1.1.0}/cctx/diagnostician/patterns/__init__.py +0 -0
- {cctx_cli-0.2.0 → cctx_cli-1.1.0}/cctx/diagnostician/patterns/dead_end.py +0 -0
- {cctx_cli-0.2.0 → cctx_cli-1.1.0}/cctx/diagnostician/patterns/retry_loop.py +0 -0
- {cctx_cli-0.2.0 → cctx_cli-1.1.0}/cctx/diagnostician/patterns/scope_creep.py +0 -0
- {cctx_cli-0.2.0 → cctx_cli-1.1.0}/cctx/diagnostician/patterns/stale_context.py +0 -0
- {cctx_cli-0.2.0 → cctx_cli-1.1.0}/cctx/diagnostician/patterns/tool_thrash.py +0 -0
- {cctx_cli-0.2.0 → cctx_cli-1.1.0}/cctx/discovery.py +0 -0
- {cctx_cli-0.2.0 → cctx_cli-1.1.0}/cctx/exporters/__init__.py +0 -0
- {cctx_cli-0.2.0 → cctx_cli-1.1.0}/cctx/exporters/csv.py +0 -0
- {cctx_cli-0.2.0 → cctx_cli-1.1.0}/cctx/exporters/jsonl.py +0 -0
- {cctx_cli-0.2.0 → cctx_cli-1.1.0}/cctx/harvest.py +0 -0
- {cctx_cli-0.2.0 → cctx_cli-1.1.0}/cctx/parsers/__init__.py +0 -0
- {cctx_cli-0.2.0 → cctx_cli-1.1.0}/cctx/parsers/claude_code.py +0 -0
- {cctx_cli-0.2.0 → cctx_cli-1.1.0}/cctx/pricing.py +0 -0
- {cctx_cli-0.2.0 → cctx_cli-1.1.0}/cctx/recommender/__init__.py +0 -0
- {cctx_cli-0.2.0 → cctx_cli-1.1.0}/cctx/recommender/claude_md.py +0 -0
- {cctx_cli-0.2.0 → cctx_cli-1.1.0}/cctx/recommender/evidence.py +0 -0
- {cctx_cli-0.2.0 → cctx_cli-1.1.0}/cctx/renderers/__init__.py +0 -0
- {cctx_cli-0.2.0 → cctx_cli-1.1.0}/cctx/renderers/github.py +0 -0
- {cctx_cli-0.2.0 → cctx_cli-1.1.0}/cctx/renderers/report.py +0 -0
- {cctx_cli-0.2.0 → cctx_cli-1.1.0}/cctx/renderers/templates/autopsy.html.j2 +0 -0
- {cctx_cli-0.2.0 → cctx_cli-1.1.0}/cctx/renderers/trace_tui.py +0 -0
- {cctx_cli-0.2.0 → cctx_cli-1.1.0}/cctx/tokenizer.py +0 -0
- {cctx_cli-0.2.0 → cctx_cli-1.1.0}/cctx/watcher.py +0 -0
- {cctx_cli-0.2.0 → cctx_cli-1.1.0}/cctx-project-brief.md +0 -0
- {cctx_cli-0.2.0 → cctx_cli-1.1.0}/demo.gif +0 -0
- {cctx_cli-0.2.0 → cctx_cli-1.1.0}/demo.tape +0 -0
- {cctx_cli-0.2.0 → cctx_cli-1.1.0}/docs/health-reviews/2026-05-15-deep-review-summary.md +0 -0
- {cctx_cli-0.2.0 → cctx_cli-1.1.0}/docs/health-reviews/2026-05-15-health-review.md +0 -0
- {cctx_cli-0.2.0 → cctx_cli-1.1.0}/docs/product-reviews/2026-05-15-product-review.md +0 -0
- {cctx_cli-0.2.0 → cctx_cli-1.1.0}/docs/superpowers/plans/2026-05-12-claude-code-parser.md +0 -0
- {cctx_cli-0.2.0 → cctx_cli-1.1.0}/docs/superpowers/plans/2026-05-14-autopsy-v0.md +0 -0
- {cctx_cli-0.2.0 → cctx_cli-1.1.0}/docs/superpowers/plans/2026-05-16-readme-pypi-release.md +0 -0
- {cctx_cli-0.2.0 → cctx_cli-1.1.0}/docs/superpowers/specs/2026-05-12-claude-code-parser-design.md +0 -0
- {cctx_cli-0.2.0 → cctx_cli-1.1.0}/docs/superpowers/specs/2026-05-14-autopsy-design.md +0 -0
- {cctx_cli-0.2.0 → cctx_cli-1.1.0}/docs/superpowers/specs/2026-05-14-harvest-design.md +0 -0
- {cctx_cli-0.2.0 → cctx_cli-1.1.0}/docs/superpowers/specs/2026-05-14-trace-tui-design.md +0 -0
- {cctx_cli-0.2.0 → cctx_cli-1.1.0}/docs/superpowers/specs/2026-05-16-readme-pypi-release-design.md +0 -0
- {cctx_cli-0.2.0 → cctx_cli-1.1.0}/tests/__init__.py +0 -0
- {cctx_cli-0.2.0 → cctx_cli-1.1.0}/tests/conftest.py +0 -0
- {cctx_cli-0.2.0 → cctx_cli-1.1.0}/tests/diagnostician/__init__.py +0 -0
- {cctx_cli-0.2.0 → cctx_cli-1.1.0}/tests/diagnostician/conftest.py +0 -0
- {cctx_cli-0.2.0 → cctx_cli-1.1.0}/tests/diagnostician/test_dead_end.py +0 -0
- {cctx_cli-0.2.0 → cctx_cli-1.1.0}/tests/diagnostician/test_inflection.py +0 -0
- {cctx_cli-0.2.0 → cctx_cli-1.1.0}/tests/diagnostician/test_orchestrator.py +0 -0
- {cctx_cli-0.2.0 → cctx_cli-1.1.0}/tests/diagnostician/test_retry_loop.py +0 -0
- {cctx_cli-0.2.0 → cctx_cli-1.1.0}/tests/diagnostician/test_scope_creep.py +0 -0
- {cctx_cli-0.2.0 → cctx_cli-1.1.0}/tests/diagnostician/test_stale_context.py +0 -0
- {cctx_cli-0.2.0 → cctx_cli-1.1.0}/tests/diagnostician/test_tool_thrash.py +0 -0
- {cctx_cli-0.2.0 → cctx_cli-1.1.0}/tests/exporters/__init__.py +0 -0
- {cctx_cli-0.2.0 → cctx_cli-1.1.0}/tests/exporters/test_csv.py +0 -0
- {cctx_cli-0.2.0 → cctx_cli-1.1.0}/tests/exporters/test_jsonl.py +0 -0
- {cctx_cli-0.2.0 → cctx_cli-1.1.0}/tests/fixtures/claude_code/README.md +0 -0
- {cctx_cli-0.2.0 → cctx_cli-1.1.0}/tests/fixtures/claude_code/short-clean/short-clean.jsonl +0 -0
- {cctx_cli-0.2.0 → cctx_cli-1.1.0}/tests/fixtures/claude_code/with-attachments/with-attachments/subagents/agent-a0b4c2cf1dde0ca56.meta.json +0 -0
- {cctx_cli-0.2.0 → cctx_cli-1.1.0}/tests/fixtures/claude_code/with-attachments/with-attachments/subagents/agent-a116ae34b1b09c332.meta.json +0 -0
- {cctx_cli-0.2.0 → cctx_cli-1.1.0}/tests/fixtures/claude_code/with-attachments/with-attachments/subagents/agent-a1c4c417b35658c9e.meta.json +0 -0
- {cctx_cli-0.2.0 → cctx_cli-1.1.0}/tests/fixtures/claude_code/with-attachments/with-attachments/subagents/agent-a1e41a901de38f1b5.meta.json +0 -0
- {cctx_cli-0.2.0 → cctx_cli-1.1.0}/tests/fixtures/claude_code/with-attachments/with-attachments/subagents/agent-a338f8d0c74612a24.meta.json +0 -0
- {cctx_cli-0.2.0 → cctx_cli-1.1.0}/tests/fixtures/claude_code/with-attachments/with-attachments/subagents/agent-a34f6f3c0e7094186.meta.json +0 -0
- {cctx_cli-0.2.0 → cctx_cli-1.1.0}/tests/fixtures/claude_code/with-attachments/with-attachments/subagents/agent-a5a5a0cff4d13308b.meta.json +0 -0
- {cctx_cli-0.2.0 → cctx_cli-1.1.0}/tests/fixtures/claude_code/with-attachments/with-attachments/subagents/agent-a6b0a3da6a0484db5.meta.json +0 -0
- {cctx_cli-0.2.0 → cctx_cli-1.1.0}/tests/fixtures/claude_code/with-attachments/with-attachments/subagents/agent-a7f73f1790b02cde5.meta.json +0 -0
- {cctx_cli-0.2.0 → cctx_cli-1.1.0}/tests/fixtures/claude_code/with-attachments/with-attachments/subagents/agent-a7f7c17c38a9d8788.meta.json +0 -0
- {cctx_cli-0.2.0 → cctx_cli-1.1.0}/tests/fixtures/claude_code/with-attachments/with-attachments/subagents/agent-a853259e2cd7bbe8a.meta.json +0 -0
- {cctx_cli-0.2.0 → cctx_cli-1.1.0}/tests/fixtures/claude_code/with-attachments/with-attachments/subagents/agent-a8d9aedb0d0c6e12d.meta.json +0 -0
- {cctx_cli-0.2.0 → cctx_cli-1.1.0}/tests/fixtures/claude_code/with-attachments/with-attachments/subagents/agent-aa778bc1d59e4a441.meta.json +0 -0
- {cctx_cli-0.2.0 → cctx_cli-1.1.0}/tests/fixtures/claude_code/with-attachments/with-attachments/subagents/agent-aba869dedee4a12ba.meta.json +0 -0
- {cctx_cli-0.2.0 → cctx_cli-1.1.0}/tests/fixtures/claude_code/with-attachments/with-attachments/subagents/agent-ada2746d9774b94db.meta.json +0 -0
- {cctx_cli-0.2.0 → cctx_cli-1.1.0}/tests/fixtures/claude_code/with-attachments/with-attachments/subagents/agent-aea0132068c64d2dd.meta.json +0 -0
- {cctx_cli-0.2.0 → cctx_cli-1.1.0}/tests/fixtures/claude_code/with-attachments/with-attachments/subagents/agent-aea215eff50874d5f.meta.json +0 -0
- {cctx_cli-0.2.0 → cctx_cli-1.1.0}/tests/fixtures/claude_code/with-attachments/with-attachments/subagents/agent-afee21f2b3852a4a0.meta.json +0 -0
- {cctx_cli-0.2.0 → cctx_cli-1.1.0}/tests/fixtures/claude_code/with-attachments/with-attachments.jsonl +0 -0
- {cctx_cli-0.2.0 → cctx_cli-1.1.0}/tests/fixtures/claude_code/with-compaction/with-compaction/subagents/agent-a051d9c9a6b2f5cc3.jsonl +0 -0
- {cctx_cli-0.2.0 → cctx_cli-1.1.0}/tests/fixtures/claude_code/with-compaction/with-compaction/subagents/agent-a051d9c9a6b2f5cc3.meta.json +0 -0
- {cctx_cli-0.2.0 → cctx_cli-1.1.0}/tests/fixtures/claude_code/with-compaction/with-compaction/subagents/agent-a171f16f4e65cfe75.jsonl +0 -0
- {cctx_cli-0.2.0 → cctx_cli-1.1.0}/tests/fixtures/claude_code/with-compaction/with-compaction/subagents/agent-a171f16f4e65cfe75.meta.json +0 -0
- {cctx_cli-0.2.0 → cctx_cli-1.1.0}/tests/fixtures/claude_code/with-compaction/with-compaction/subagents/agent-a1b77fea2c0a2269b.jsonl +0 -0
- {cctx_cli-0.2.0 → cctx_cli-1.1.0}/tests/fixtures/claude_code/with-compaction/with-compaction/subagents/agent-a1b77fea2c0a2269b.meta.json +0 -0
- {cctx_cli-0.2.0 → cctx_cli-1.1.0}/tests/fixtures/claude_code/with-compaction/with-compaction/subagents/agent-a20da4c01a54acca8.jsonl +0 -0
- {cctx_cli-0.2.0 → cctx_cli-1.1.0}/tests/fixtures/claude_code/with-compaction/with-compaction/subagents/agent-a20da4c01a54acca8.meta.json +0 -0
- {cctx_cli-0.2.0 → cctx_cli-1.1.0}/tests/fixtures/claude_code/with-compaction/with-compaction/subagents/agent-a3c82739b1383fb14.jsonl +0 -0
- {cctx_cli-0.2.0 → cctx_cli-1.1.0}/tests/fixtures/claude_code/with-compaction/with-compaction/subagents/agent-a3c82739b1383fb14.meta.json +0 -0
- {cctx_cli-0.2.0 → cctx_cli-1.1.0}/tests/fixtures/claude_code/with-compaction/with-compaction/subagents/agent-a49e8539611c5fe12.jsonl +0 -0
- {cctx_cli-0.2.0 → cctx_cli-1.1.0}/tests/fixtures/claude_code/with-compaction/with-compaction/subagents/agent-a49e8539611c5fe12.meta.json +0 -0
- {cctx_cli-0.2.0 → cctx_cli-1.1.0}/tests/fixtures/claude_code/with-compaction/with-compaction/subagents/agent-a7bb58f3fff2b3e8d.jsonl +0 -0
- {cctx_cli-0.2.0 → cctx_cli-1.1.0}/tests/fixtures/claude_code/with-compaction/with-compaction/subagents/agent-a7bb58f3fff2b3e8d.meta.json +0 -0
- {cctx_cli-0.2.0 → cctx_cli-1.1.0}/tests/fixtures/claude_code/with-compaction/with-compaction/subagents/agent-a92b48c0331195aac.jsonl +0 -0
- {cctx_cli-0.2.0 → cctx_cli-1.1.0}/tests/fixtures/claude_code/with-compaction/with-compaction/subagents/agent-a92b48c0331195aac.meta.json +0 -0
- {cctx_cli-0.2.0 → cctx_cli-1.1.0}/tests/fixtures/claude_code/with-compaction/with-compaction/subagents/agent-ab96c4264099694a9.jsonl +0 -0
- {cctx_cli-0.2.0 → cctx_cli-1.1.0}/tests/fixtures/claude_code/with-compaction/with-compaction/subagents/agent-ab96c4264099694a9.meta.json +0 -0
- {cctx_cli-0.2.0 → cctx_cli-1.1.0}/tests/fixtures/claude_code/with-compaction/with-compaction/subagents/agent-acb2895c5e34ffec0.jsonl +0 -0
- {cctx_cli-0.2.0 → cctx_cli-1.1.0}/tests/fixtures/claude_code/with-compaction/with-compaction/subagents/agent-acb2895c5e34ffec0.meta.json +0 -0
- {cctx_cli-0.2.0 → cctx_cli-1.1.0}/tests/fixtures/claude_code/with-compaction/with-compaction/subagents/agent-adb2302769938fb3f.jsonl +0 -0
- {cctx_cli-0.2.0 → cctx_cli-1.1.0}/tests/fixtures/claude_code/with-compaction/with-compaction/subagents/agent-adb2302769938fb3f.meta.json +0 -0
- {cctx_cli-0.2.0 → cctx_cli-1.1.0}/tests/fixtures/claude_code/with-compaction/with-compaction/subagents/agent-ae585eca15cb93b9c.jsonl +0 -0
- {cctx_cli-0.2.0 → cctx_cli-1.1.0}/tests/fixtures/claude_code/with-compaction/with-compaction/subagents/agent-ae585eca15cb93b9c.meta.json +0 -0
- {cctx_cli-0.2.0 → cctx_cli-1.1.0}/tests/fixtures/claude_code/with-compaction/with-compaction/subagents/agent-aec9c917feb903d67.jsonl +0 -0
- {cctx_cli-0.2.0 → cctx_cli-1.1.0}/tests/fixtures/claude_code/with-compaction/with-compaction/subagents/agent-aec9c917feb903d67.meta.json +0 -0
- {cctx_cli-0.2.0 → cctx_cli-1.1.0}/tests/fixtures/claude_code/with-compaction/with-compaction.jsonl +0 -0
- {cctx_cli-0.2.0 → cctx_cli-1.1.0}/tests/fixtures/claude_code/with-subagents/with-subagents/subagents/agent-a1a3a21aeb76bb0a9.jsonl +0 -0
- {cctx_cli-0.2.0 → cctx_cli-1.1.0}/tests/fixtures/claude_code/with-subagents/with-subagents/subagents/agent-a1a3a21aeb76bb0a9.meta.json +0 -0
- {cctx_cli-0.2.0 → cctx_cli-1.1.0}/tests/fixtures/claude_code/with-subagents/with-subagents/subagents/agent-aaa1d6ecc05a78442.jsonl +0 -0
- {cctx_cli-0.2.0 → cctx_cli-1.1.0}/tests/fixtures/claude_code/with-subagents/with-subagents/subagents/agent-aaa1d6ecc05a78442.meta.json +0 -0
- {cctx_cli-0.2.0 → cctx_cli-1.1.0}/tests/fixtures/claude_code/with-subagents/with-subagents/subagents/agent-af3c545ccd30036d2.jsonl +0 -0
- {cctx_cli-0.2.0 → cctx_cli-1.1.0}/tests/fixtures/claude_code/with-subagents/with-subagents/subagents/agent-af3c545ccd30036d2.meta.json +0 -0
- {cctx_cli-0.2.0 → cctx_cli-1.1.0}/tests/fixtures/claude_code/with-subagents/with-subagents/tool-results/btwp2bzro.txt +0 -0
- {cctx_cli-0.2.0 → cctx_cli-1.1.0}/tests/fixtures/claude_code/with-subagents/with-subagents/tool-results/byqjbgy4b.txt +0 -0
- {cctx_cli-0.2.0 → cctx_cli-1.1.0}/tests/fixtures/claude_code/with-subagents/with-subagents.jsonl +0 -0
- {cctx_cli-0.2.0 → cctx_cli-1.1.0}/tests/fixtures/claude_code/with-tool-results/with-tool-results/tool-results/bosbkda0h.txt +0 -0
- {cctx_cli-0.2.0 → cctx_cli-1.1.0}/tests/fixtures/claude_code/with-tool-results/with-tool-results.jsonl +0 -0
- {cctx_cli-0.2.0 → cctx_cli-1.1.0}/tests/fixtures/scrub.py +0 -0
- {cctx_cli-0.2.0 → cctx_cli-1.1.0}/tests/fixtures/synthetic/bookkeeping_only.jsonl +0 -0
- {cctx_cli-0.2.0 → cctx_cli-1.1.0}/tests/fixtures/synthetic/malformed_middle.jsonl +0 -0
- {cctx_cli-0.2.0 → cctx_cli-1.1.0}/tests/fixtures/synthetic/truncated_final_line.jsonl +0 -0
- {cctx_cli-0.2.0 → cctx_cli-1.1.0}/tests/fixtures/synthetic/unknown_attachment_shape.jsonl +0 -0
- {cctx_cli-0.2.0 → cctx_cli-1.1.0}/tests/fixtures/synthetic/unknown_type.jsonl +0 -0
- {cctx_cli-0.2.0 → cctx_cli-1.1.0}/tests/parsers/__init__.py +0 -0
- {cctx_cli-0.2.0 → cctx_cli-1.1.0}/tests/parsers/test_claude_code.py +0 -0
- {cctx_cli-0.2.0 → cctx_cli-1.1.0}/tests/parsers/test_claude_code_integration.py +0 -0
- {cctx_cli-0.2.0 → cctx_cli-1.1.0}/tests/recommender/__init__.py +0 -0
- {cctx_cli-0.2.0 → cctx_cli-1.1.0}/tests/recommender/test_claude_md.py +0 -0
- {cctx_cli-0.2.0 → cctx_cli-1.1.0}/tests/recommender/test_evidence.py +0 -0
- {cctx_cli-0.2.0 → cctx_cli-1.1.0}/tests/renderers/__init__.py +0 -0
- {cctx_cli-0.2.0 → cctx_cli-1.1.0}/tests/renderers/test_report.py +0 -0
- {cctx_cli-0.2.0 → cctx_cli-1.1.0}/tests/renderers/test_terminal_renderer_full.py +0 -0
- {cctx_cli-0.2.0 → cctx_cli-1.1.0}/tests/test_aggregate.py +0 -0
- {cctx_cli-0.2.0 → cctx_cli-1.1.0}/tests/test_cli_export.py +0 -0
- {cctx_cli-0.2.0 → cctx_cli-1.1.0}/tests/test_discovery.py +0 -0
- {cctx_cli-0.2.0 → cctx_cli-1.1.0}/tests/test_github_summary.py +0 -0
- {cctx_cli-0.2.0 → cctx_cli-1.1.0}/tests/test_harvest.py +0 -0
- {cctx_cli-0.2.0 → cctx_cli-1.1.0}/tests/test_harvest_check.py +0 -0
- {cctx_cli-0.2.0 → cctx_cli-1.1.0}/tests/test_smoke.py +0 -0
- {cctx_cli-0.2.0 → cctx_cli-1.1.0}/tests/test_tokenizer.py +0 -0
- {cctx_cli-0.2.0 → cctx_cli-1.1.0}/tests/test_trace_tui.py +0 -0
- {cctx_cli-0.2.0 → cctx_cli-1.1.0}/tests/test_watcher.py +0 -0
|
@@ -46,18 +46,6 @@ jobs:
|
|
|
46
46
|
|
|
47
47
|
- name: Publish
|
|
48
48
|
uses: pypa/gh-action-pypi-publish@release/v1
|
|
49
|
+
with:
|
|
50
|
+
skip-existing: true
|
|
49
51
|
|
|
50
|
-
tag-action:
|
|
51
|
-
name: Tag floating action version
|
|
52
|
-
needs: [publish]
|
|
53
|
-
runs-on: ubuntu-latest
|
|
54
|
-
permissions:
|
|
55
|
-
contents: write
|
|
56
|
-
steps:
|
|
57
|
-
- uses: actions/checkout@v4
|
|
58
|
-
|
|
59
|
-
- name: Push floating major tag
|
|
60
|
-
run: |
|
|
61
|
-
MAJOR=$(echo "${{ github.event.release.tag_name }}" | cut -d. -f1)
|
|
62
|
-
git tag -f "$MAJOR"
|
|
63
|
-
git push origin "$MAJOR" --force
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
name: Release
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
workflow_dispatch:
|
|
7
|
+
|
|
8
|
+
jobs:
|
|
9
|
+
release:
|
|
10
|
+
name: Semantic release
|
|
11
|
+
runs-on: ubuntu-latest
|
|
12
|
+
concurrency: release
|
|
13
|
+
permissions:
|
|
14
|
+
contents: write
|
|
15
|
+
|
|
16
|
+
steps:
|
|
17
|
+
- uses: actions/checkout@v4
|
|
18
|
+
with:
|
|
19
|
+
fetch-depth: 0
|
|
20
|
+
token: ${{ secrets.RELEASE_TOKEN }}
|
|
21
|
+
|
|
22
|
+
- uses: actions/setup-python@v5
|
|
23
|
+
with:
|
|
24
|
+
python-version: "3.12"
|
|
25
|
+
|
|
26
|
+
- name: Install python-semantic-release
|
|
27
|
+
run: pip install python-semantic-release
|
|
28
|
+
|
|
29
|
+
- name: Compute version and push
|
|
30
|
+
run: semantic-release version
|
|
31
|
+
env:
|
|
32
|
+
GH_TOKEN: ${{ secrets.RELEASE_TOKEN }}
|
|
33
|
+
|
|
34
|
+
- name: Publish GitHub release
|
|
35
|
+
run: semantic-release publish
|
|
36
|
+
env:
|
|
37
|
+
GH_TOKEN: ${{ secrets.RELEASE_TOKEN }}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
# CHANGELOG
|
|
2
|
+
|
|
3
|
+
<!-- version list -->
|
|
4
|
+
|
|
5
|
+
## v1.1.0 (2026-05-17)
|
|
6
|
+
|
|
7
|
+
### Chores
|
|
8
|
+
|
|
9
|
+
- Add skip-existing to pypi publish action
|
|
10
|
+
([`23d7e16`](https://github.com/jacquardlabs/cctx/commit/23d7e16e18074da3c25899ba98298100ad3c1ad3))
|
|
11
|
+
|
|
12
|
+
### Features
|
|
13
|
+
|
|
14
|
+
- M9 polish — verdict headline, --top N, --turn N
|
|
15
|
+
([#83](https://github.com/jacquardlabs/cctx/pull/83),
|
|
16
|
+
[`b0d2f27`](https://github.com/jacquardlabs/cctx/commit/b0d2f273a373c5a2f52c9de3a3fb2721da59c4f5))
|
|
17
|
+
|
|
18
|
+
- M9 polish — verdict headline, --top N, and --turn N
|
|
19
|
+
([#83](https://github.com/jacquardlabs/cctx/pull/83),
|
|
20
|
+
[`b0d2f27`](https://github.com/jacquardlabs/cctx/commit/b0d2f273a373c5a2f52c9de3a3fb2721da59c4f5))
|
|
21
|
+
|
|
22
|
+
### Refactoring
|
|
23
|
+
|
|
24
|
+
- Cache verdict, fix markup=False bug, use reverse=True
|
|
25
|
+
([#83](https://github.com/jacquardlabs/cctx/pull/83),
|
|
26
|
+
[`b0d2f27`](https://github.com/jacquardlabs/cctx/commit/b0d2f273a373c5a2f52c9de3a3fb2721da59c4f5))
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
## v1.0.0 (2026-05-17)
|
|
30
|
+
|
|
31
|
+
### Continuous Integration
|
|
32
|
+
|
|
33
|
+
- Add python-semantic-release for fully automated CD
|
|
34
|
+
([`9844921`](https://github.com/jacquardlabs/cctx/commit/98449213e5b3bd597c47d54e4d5043e245adafe4))
|
|
35
|
+
|
|
36
|
+
- Add workflow_dispatch to release.yml for manual trigger
|
|
37
|
+
([`08ac9f8`](https://github.com/jacquardlabs/cctx/commit/08ac9f80eb6ceee7b57155852febc4274cbaf3b0))
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
## v0.2.0 (2026-05-16)
|
|
41
|
+
|
|
42
|
+
### Bug Fixes
|
|
43
|
+
|
|
44
|
+
- Ruff lint — B904, E402, E501, F841 across cli, tests, and renderers
|
|
45
|
+
([`fa7105f`](https://github.com/jacquardlabs/cctx/commit/fa7105fef340d89136f1996b86824e30d080a730))
|
|
46
|
+
|
|
47
|
+
- Trace TUI token sum within line-length limit
|
|
48
|
+
([`5b7416c`](https://github.com/jacquardlabs/cctx/commit/5b7416c5b92790cfe66f1e53f20891ecaf6e03b0))
|
|
49
|
+
|
|
50
|
+
### Chores
|
|
51
|
+
|
|
52
|
+
- Bump version to 0.2.0, update PRODUCT.md and CLAUDE.md
|
|
53
|
+
([`828ed49`](https://github.com/jacquardlabs/cctx/commit/828ed4997df9f4a264669bc38f4b10588a151f1c))
|
|
54
|
+
|
|
55
|
+
### Documentation
|
|
56
|
+
|
|
57
|
+
- Add CI usage section clarifying harvest is local-only
|
|
58
|
+
([`c526408`](https://github.com/jacquardlabs/cctx/commit/c526408a35749858e1c0b0b6ba42aea95bb8f621))
|
|
59
|
+
|
|
60
|
+
### Features
|
|
61
|
+
|
|
62
|
+
- **#64,#63**: Tool-thrash and dead-end exploration classifiers
|
|
63
|
+
([`14f8f45`](https://github.com/jacquardlabs/cctx/commit/14f8f45f9d3f4ef2fefac374d3e4cea36185c60d))
|
|
64
|
+
|
|
65
|
+
- **#65**: Harvest v2 — route patches to any .md target (.claude/rules/, .claude/skills/)
|
|
66
|
+
([`06ef9b7`](https://github.com/jacquardlabs/cctx/commit/06ef9b7a8ae9abacea54c2c826efc3fb6e6e80be))
|
|
67
|
+
|
|
68
|
+
- **#66**: Cctx harvest --check audits CLAUDE.md for dead refs and empty sections
|
|
69
|
+
([`a3be1d0`](https://github.com/jacquardlabs/cctx/commit/a3be1d0923d387b6830b10c7c2c5acf34a3b8917))
|
|
70
|
+
|
|
71
|
+
- **#67**: Interactive aggregate drill-down; --check docs in README
|
|
72
|
+
([`3db5429`](https://github.com/jacquardlabs/cctx/commit/3db5429dede40777b23d83e32a4b15a8c0e82a16))
|
|
73
|
+
|
|
74
|
+
- **#68**: --since accepts 7d, 2w, YYYY-MM-DD, and date ranges
|
|
75
|
+
([`434d7c4`](https://github.com/jacquardlabs/cctx/commit/434d7c448406e0a5465380ed395e1fadaa1c0db1))
|
|
76
|
+
|
|
77
|
+
- **#69**: Annotate costs as estimates (~85–95%) in terminal and HTML output
|
|
78
|
+
([`5a49889`](https://github.com/jacquardlabs/cctx/commit/5a49889bd43555d30a58c2b78eddf6acbb0d8e97))
|
|
79
|
+
|
|
80
|
+
- **#70**: Cctx watch — live waste signals during an active session
|
|
81
|
+
([`f533a13`](https://github.com/jacquardlabs/cctx/commit/f533a13e1f087da00457ce7b8215934f20c403a2))
|
|
82
|
+
|
|
83
|
+
- **#72**: Cctx autopsy --github-summary writes findings to GitHub Actions job summary
|
|
84
|
+
([`df91256`](https://github.com/jacquardlabs/cctx/commit/df91256b58b75a9bbe9594a000b00dee2ac2fbc5))
|
|
85
|
+
|
|
86
|
+
- **#73**: Cctx GitHub Action (composite) + --fail-on-findings flag
|
|
87
|
+
([`9229584`](https://github.com/jacquardlabs/cctx/commit/9229584ea192ce1b1ee6718721d981cba0ca13e0))
|
|
88
|
+
|
|
89
|
+
### Refactoring
|
|
90
|
+
|
|
91
|
+
- Consolidate KIND_LABEL, fix private import, clean up watcher tests
|
|
92
|
+
([`31ad4d7`](https://github.com/jacquardlabs/cctx/commit/31ad4d75885eed9c8238c4eb1c08bd5d1ba51a15))
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
## v0.1.0 (2026-05-16)
|
|
96
|
+
|
|
97
|
+
- Initial Release
|
|
@@ -31,6 +31,7 @@ from cctx.renderers.terminal import (
|
|
|
31
31
|
render_harvest_results,
|
|
32
32
|
render_projects,
|
|
33
33
|
render_sessions,
|
|
34
|
+
render_turn,
|
|
34
35
|
)
|
|
35
36
|
from cctx.tokenizer import tokenize_session
|
|
36
37
|
|
|
@@ -245,6 +246,22 @@ def ls(project: Path | None) -> None:
|
|
|
245
246
|
default=False,
|
|
246
247
|
help="Exit 1 if any findings are detected (single-session only).",
|
|
247
248
|
)
|
|
249
|
+
@click.option(
|
|
250
|
+
"--top",
|
|
251
|
+
"top_n",
|
|
252
|
+
default=None,
|
|
253
|
+
metavar="N",
|
|
254
|
+
type=click.IntRange(min=1),
|
|
255
|
+
help="Show only the top N patterns by session count (--since mode only).",
|
|
256
|
+
)
|
|
257
|
+
@click.option(
|
|
258
|
+
"--turn",
|
|
259
|
+
"turn_num",
|
|
260
|
+
default=None,
|
|
261
|
+
metavar="N",
|
|
262
|
+
type=click.IntRange(min=1),
|
|
263
|
+
help="Show details for turn N (single-session only).",
|
|
264
|
+
)
|
|
248
265
|
def autopsy(
|
|
249
266
|
target: Path | None,
|
|
250
267
|
since: str | None,
|
|
@@ -252,6 +269,8 @@ def autopsy(
|
|
|
252
269
|
html_out: Path | None,
|
|
253
270
|
github_summary: bool,
|
|
254
271
|
fail_on_findings: bool,
|
|
272
|
+
top_n: int | None,
|
|
273
|
+
turn_num: int | None,
|
|
255
274
|
) -> None:
|
|
256
275
|
"""Diagnose a session or project directory.
|
|
257
276
|
|
|
@@ -267,6 +286,10 @@ def autopsy(
|
|
|
267
286
|
raise click.UsageError("--latest and --since are mutually exclusive.")
|
|
268
287
|
if fail_on_findings and since is not None:
|
|
269
288
|
raise click.UsageError("--fail-on-findings is not supported with --since.")
|
|
289
|
+
if top_n is not None and since is None:
|
|
290
|
+
raise click.UsageError("--top requires --since.")
|
|
291
|
+
if turn_num is not None and since is not None:
|
|
292
|
+
raise click.UsageError("--turn is not supported with --since.")
|
|
270
293
|
|
|
271
294
|
if target is None:
|
|
272
295
|
if not latest:
|
|
@@ -305,6 +328,8 @@ def autopsy(
|
|
|
305
328
|
start, end, label = parse_since(since)
|
|
306
329
|
diagnoses = aggregate.run(project_dir, start, end)
|
|
307
330
|
ev = evidence_mod.accumulate(diagnoses)
|
|
331
|
+
if top_n is not None:
|
|
332
|
+
ev = dict(sorted(ev.items(), key=lambda x: x[1].session_count, reverse=True)[:top_n])
|
|
308
333
|
patches = claude_md.generate_from_evidence(ev)
|
|
309
334
|
report = AggregateReport(
|
|
310
335
|
period_label=label,
|
|
@@ -327,14 +352,16 @@ def autopsy(
|
|
|
327
352
|
trace = tokenize_session(parse_session(target))
|
|
328
353
|
diagnosis = diagnostician.run(trace)
|
|
329
354
|
diagnosis = claude_md.generate(diagnosis)
|
|
330
|
-
if
|
|
355
|
+
if turn_num is not None:
|
|
356
|
+
render_turn(trace, diagnosis, turn_num)
|
|
357
|
+
elif html_out is not None:
|
|
331
358
|
from cctx.renderers.report import render_html
|
|
332
359
|
html_out.write_text(render_html(diagnosis, trace), encoding="utf-8")
|
|
333
360
|
click.echo(f"HTML report written to {html_out}")
|
|
334
|
-
|
|
361
|
+
elif github_summary:
|
|
335
362
|
from cctx.renderers.github import write_github_summary
|
|
336
363
|
write_github_summary(diagnosis)
|
|
337
|
-
|
|
364
|
+
else:
|
|
338
365
|
render_diagnosis(diagnosis, session_path=target)
|
|
339
366
|
if fail_on_findings and diagnosis.findings:
|
|
340
367
|
raise SystemExit(1)
|
|
@@ -225,6 +225,13 @@ class Diagnosis:
|
|
|
225
225
|
waste_cost_usd: float
|
|
226
226
|
analysed_at: datetime
|
|
227
227
|
|
|
228
|
+
@property
|
|
229
|
+
def verdict(self) -> str:
|
|
230
|
+
if not self.findings:
|
|
231
|
+
return "clean session"
|
|
232
|
+
seen = dict.fromkeys(f.kind for f in self.findings)
|
|
233
|
+
return " + ".join(KIND_LABEL[k] for k in seen)
|
|
234
|
+
|
|
228
235
|
|
|
229
236
|
@dataclass
|
|
230
237
|
class KindEvidence:
|
|
@@ -24,7 +24,7 @@ from cctx.models import KIND_LABEL, FindingKind, Severity
|
|
|
24
24
|
|
|
25
25
|
if TYPE_CHECKING:
|
|
26
26
|
from cctx.discovery import ProjectInfo
|
|
27
|
-
from cctx.models import AggregateReport, Diagnosis
|
|
27
|
+
from cctx.models import AggregateReport, Diagnosis, SessionTrace
|
|
28
28
|
|
|
29
29
|
_SEVERITY_STYLE = {
|
|
30
30
|
Severity.HIGH: "bold red",
|
|
@@ -49,6 +49,9 @@ def render_diagnosis(
|
|
|
49
49
|
|
|
50
50
|
# Header
|
|
51
51
|
con.print(Rule(f"cctx autopsy — session {diagnosis.session_id}"))
|
|
52
|
+
verdict = diagnosis.verdict
|
|
53
|
+
verdict_style = "bold green" if not diagnosis.findings else "bold red"
|
|
54
|
+
con.print(Text(f"Verdict: {verdict}", style=verdict_style))
|
|
52
55
|
cost_line = f"Session cost: ~${diagnosis.total_cost_usd:.2f}"
|
|
53
56
|
if diagnosis.waste_cost_usd > 0:
|
|
54
57
|
pct = (
|
|
@@ -169,6 +172,56 @@ def render_aggregate_drilldown(
|
|
|
169
172
|
con.print("No matching findings.")
|
|
170
173
|
|
|
171
174
|
|
|
175
|
+
def render_turn(
|
|
176
|
+
trace: SessionTrace,
|
|
177
|
+
diagnosis: Diagnosis,
|
|
178
|
+
turn_num: int,
|
|
179
|
+
*,
|
|
180
|
+
console: Console | None = None,
|
|
181
|
+
) -> None:
|
|
182
|
+
"""Render details for a single turn N from a session."""
|
|
183
|
+
con = console or _default_console()
|
|
184
|
+
|
|
185
|
+
turn = next((t for t in trace.turns if t.turn_number == turn_num), None)
|
|
186
|
+
if turn is None:
|
|
187
|
+
con.print(Text(
|
|
188
|
+
f"Turn {turn_num} not found (session has {len(trace.turns)} turns).",
|
|
189
|
+
style="red",
|
|
190
|
+
))
|
|
191
|
+
return
|
|
192
|
+
|
|
193
|
+
con.print(Rule(f"Turn {turn_num} — {turn.role} — {turn.timestamp.strftime('%H:%M:%S')}"))
|
|
194
|
+
|
|
195
|
+
text = turn.text
|
|
196
|
+
if text:
|
|
197
|
+
preview = text[:500]
|
|
198
|
+
if len(text) > 500:
|
|
199
|
+
preview += f"\n… [{len(text) - 500} more chars]"
|
|
200
|
+
con.print(preview)
|
|
201
|
+
|
|
202
|
+
for tu in turn.tool_uses:
|
|
203
|
+
con.print(Text(f" tool_use: {tu.tool_name}", style="cyan"))
|
|
204
|
+
|
|
205
|
+
for tr in turn.tool_results:
|
|
206
|
+
style = "red" if tr.is_error else "dim"
|
|
207
|
+
content = tr.content
|
|
208
|
+
preview = content[:200] + ("…" if len(content) > 200 else "")
|
|
209
|
+
con.print(Text(f" tool_result ({tr.tool_name}): {preview}", style=style))
|
|
210
|
+
|
|
211
|
+
# Findings that span this turn
|
|
212
|
+
touching = [
|
|
213
|
+
f for f in diagnosis.findings
|
|
214
|
+
if f.first_turn <= turn_num <= (f.last_turn or f.first_turn)
|
|
215
|
+
]
|
|
216
|
+
if touching:
|
|
217
|
+
con.print()
|
|
218
|
+
con.print(Text("Findings active at this turn:", style="bold"))
|
|
219
|
+
for finding in touching:
|
|
220
|
+
style = _SEVERITY_STYLE.get(finding.severity, "")
|
|
221
|
+
label = _KIND_LABEL.get(finding.kind, finding.kind.value.upper())
|
|
222
|
+
con.print(Text(f" [{label}]", style=style), finding.summary)
|
|
223
|
+
|
|
224
|
+
|
|
172
225
|
def render_harvest_results(
|
|
173
226
|
results: list, # list[ApplyResult] — ApplyStatus is harvest-internal; imported lazily
|
|
174
227
|
*,
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "cctx-cli"
|
|
7
|
-
version = "
|
|
7
|
+
version = "1.1.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"
|
|
@@ -42,6 +42,18 @@ python_functions = ["test_*"]
|
|
|
42
42
|
addopts = "-ra --strict-markers"
|
|
43
43
|
asyncio_mode = "auto"
|
|
44
44
|
|
|
45
|
+
[tool.semantic_release]
|
|
46
|
+
version_toml = ["pyproject.toml:project.version"]
|
|
47
|
+
version_variables = ["cctx/__init__.py:__version__"]
|
|
48
|
+
tag_format = "v{version}"
|
|
49
|
+
commit_message = "chore(release): v{version} [skip ci]"
|
|
50
|
+
major_on_zero = false
|
|
51
|
+
build_command = "pip install build && python -m build"
|
|
52
|
+
upload_to_vcs_release = true
|
|
53
|
+
|
|
54
|
+
[tool.semantic_release.branches.main]
|
|
55
|
+
match = "main"
|
|
56
|
+
|
|
45
57
|
[tool.ruff]
|
|
46
58
|
line-length = 100
|
|
47
59
|
target-version = "py310"
|
|
@@ -309,3 +309,83 @@ def test_fail_on_findings_incompatible_with_since(runner):
|
|
|
309
309
|
["autopsy", str(FIXTURE_PATH.parent), "--since", "7", "--fail-on-findings"],
|
|
310
310
|
)
|
|
311
311
|
assert result.exit_code != 0
|
|
312
|
+
|
|
313
|
+
|
|
314
|
+
# ---------------------------------------------------------------------------
|
|
315
|
+
# --top N tests (#75)
|
|
316
|
+
# ---------------------------------------------------------------------------
|
|
317
|
+
|
|
318
|
+
|
|
319
|
+
def test_top_requires_since(runner, session_jsonl):
|
|
320
|
+
"""--top without --since → non-zero exit (UsageError)."""
|
|
321
|
+
from cctx.cli import cli
|
|
322
|
+
|
|
323
|
+
result = runner.invoke(cli, ["autopsy", str(session_jsonl), "--top", "3"])
|
|
324
|
+
assert result.exit_code != 0
|
|
325
|
+
assert "since" in result.output.lower() or "Error" in result.output
|
|
326
|
+
|
|
327
|
+
|
|
328
|
+
def test_top_with_since_accepted(runner, tmp_path):
|
|
329
|
+
"""--top N with --since → exit 0."""
|
|
330
|
+
from cctx.cli import cli
|
|
331
|
+
|
|
332
|
+
project_dir = tmp_path / "-Users-test-Projects-demo"
|
|
333
|
+
project_dir.mkdir()
|
|
334
|
+
session_id = "top-test-sess"
|
|
335
|
+
line = {
|
|
336
|
+
"type": "user", "uuid": f"{session_id}-u1", "parentUuid": None,
|
|
337
|
+
"isSidechain": False, "timestamp": "2026-05-14T10:00:00.000Z",
|
|
338
|
+
"sessionId": session_id, "version": "2.1.138",
|
|
339
|
+
"cwd": "/Users/test/Projects/demo", "gitBranch": "main",
|
|
340
|
+
"userType": "external", "entrypoint": "cli",
|
|
341
|
+
"message": {"role": "user", "content": "hello"},
|
|
342
|
+
}
|
|
343
|
+
(project_dir / f"{session_id}.jsonl").write_text(json.dumps(line) + "\n")
|
|
344
|
+
|
|
345
|
+
result = runner.invoke(
|
|
346
|
+
cli, ["autopsy", str(project_dir), "--since", "7", "--top", "2"],
|
|
347
|
+
catch_exceptions=False,
|
|
348
|
+
)
|
|
349
|
+
assert result.exit_code == 0
|
|
350
|
+
|
|
351
|
+
|
|
352
|
+
# ---------------------------------------------------------------------------
|
|
353
|
+
# --turn N tests (#76)
|
|
354
|
+
# ---------------------------------------------------------------------------
|
|
355
|
+
|
|
356
|
+
|
|
357
|
+
def test_turn_incompatible_with_since(runner, tmp_path):
|
|
358
|
+
"""--turn N + --since → non-zero exit (UsageError)."""
|
|
359
|
+
from cctx.cli import cli
|
|
360
|
+
|
|
361
|
+
project_dir = tmp_path / "-Users-test-Projects-demo"
|
|
362
|
+
project_dir.mkdir()
|
|
363
|
+
result = runner.invoke(
|
|
364
|
+
cli, ["autopsy", str(project_dir), "--since", "7", "--turn", "3"],
|
|
365
|
+
)
|
|
366
|
+
assert result.exit_code != 0
|
|
367
|
+
assert "since" in result.output.lower() or "Error" in result.output
|
|
368
|
+
|
|
369
|
+
|
|
370
|
+
def test_turn_shows_turn_details(runner, session_jsonl):
|
|
371
|
+
"""--turn 1 on a session with one turn prints turn details."""
|
|
372
|
+
from cctx.cli import cli
|
|
373
|
+
|
|
374
|
+
result = runner.invoke(
|
|
375
|
+
cli, ["autopsy", str(session_jsonl), "--turn", "1"],
|
|
376
|
+
catch_exceptions=False,
|
|
377
|
+
)
|
|
378
|
+
assert result.exit_code == 0
|
|
379
|
+
assert "Turn 1" in result.output
|
|
380
|
+
|
|
381
|
+
|
|
382
|
+
def test_turn_out_of_range_shows_not_found(runner, session_jsonl):
|
|
383
|
+
"""--turn 999 on a one-turn session shows 'not found' message."""
|
|
384
|
+
from cctx.cli import cli
|
|
385
|
+
|
|
386
|
+
result = runner.invoke(
|
|
387
|
+
cli, ["autopsy", str(session_jsonl), "--turn", "999"],
|
|
388
|
+
catch_exceptions=False,
|
|
389
|
+
)
|
|
390
|
+
assert result.exit_code == 0
|
|
391
|
+
assert "not found" in result.output.lower() or "999" in result.output
|
|
@@ -643,3 +643,57 @@ def test_aggregate_report_instantiates():
|
|
|
643
643
|
)
|
|
644
644
|
assert report.sessions_analysed == 12
|
|
645
645
|
assert FindingKind.RETRY_LOOP in report.by_kind
|
|
646
|
+
|
|
647
|
+
|
|
648
|
+
# ---------------------------------------------------------------------------
|
|
649
|
+
# Diagnosis.verdict property (#74)
|
|
650
|
+
# ---------------------------------------------------------------------------
|
|
651
|
+
|
|
652
|
+
|
|
653
|
+
def _make_finding_of_kind(kind, first_turn=5):
|
|
654
|
+
from cctx.models import Confidence, Finding, Severity
|
|
655
|
+
return Finding(
|
|
656
|
+
kind=kind,
|
|
657
|
+
severity=Severity.MEDIUM,
|
|
658
|
+
confidence=Confidence.MEDIUM,
|
|
659
|
+
first_turn=first_turn,
|
|
660
|
+
last_turn=None,
|
|
661
|
+
evidence={},
|
|
662
|
+
cost_usd=None,
|
|
663
|
+
summary="test",
|
|
664
|
+
)
|
|
665
|
+
|
|
666
|
+
|
|
667
|
+
def _make_diag(findings):
|
|
668
|
+
from cctx.models import Diagnosis
|
|
669
|
+
return Diagnosis(
|
|
670
|
+
session_id="abc",
|
|
671
|
+
findings=findings,
|
|
672
|
+
inflection_turn=None,
|
|
673
|
+
patches=[],
|
|
674
|
+
total_cost_usd=1.0,
|
|
675
|
+
waste_cost_usd=0.0,
|
|
676
|
+
analysed_at=_utcnow(),
|
|
677
|
+
)
|
|
678
|
+
|
|
679
|
+
|
|
680
|
+
def test_verdict_clean_session():
|
|
681
|
+
d = _make_diag([])
|
|
682
|
+
assert d.verdict == "clean session"
|
|
683
|
+
|
|
684
|
+
|
|
685
|
+
def test_verdict_single_finding():
|
|
686
|
+
from cctx.models import FindingKind
|
|
687
|
+
d = _make_diag([_make_finding_of_kind(FindingKind.RETRY_LOOP)])
|
|
688
|
+
assert d.verdict == "RETRY LOOP"
|
|
689
|
+
|
|
690
|
+
|
|
691
|
+
def test_verdict_multiple_kinds_deduped_in_order():
|
|
692
|
+
from cctx.models import FindingKind
|
|
693
|
+
findings = [
|
|
694
|
+
_make_finding_of_kind(FindingKind.SCOPE_CREEP),
|
|
695
|
+
_make_finding_of_kind(FindingKind.RETRY_LOOP),
|
|
696
|
+
_make_finding_of_kind(FindingKind.SCOPE_CREEP), # duplicate kind
|
|
697
|
+
]
|
|
698
|
+
d = _make_diag(findings)
|
|
699
|
+
assert d.verdict == "SCOPE CREEP + RETRY LOOP"
|
|
@@ -109,3 +109,103 @@ def test_patch_diff_shown():
|
|
|
109
109
|
)
|
|
110
110
|
output = _render_to_string(diag)
|
|
111
111
|
assert "Retry discipline" in output or "CLAUDE.md" in output
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
def test_verdict_shown_in_output():
|
|
115
|
+
diag = _make_diagnosis([_make_finding("retry_loop")])
|
|
116
|
+
output = _render_to_string(diag)
|
|
117
|
+
assert "Verdict" in output
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
def test_verdict_clean_shown():
|
|
121
|
+
output = _render_to_string(_make_diagnosis())
|
|
122
|
+
assert "clean" in output.lower()
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
# ---------------------------------------------------------------------------
|
|
126
|
+
# render_turn (#76)
|
|
127
|
+
# ---------------------------------------------------------------------------
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
def _make_trace_with_turn(turn_number=3, role="assistant", text="some content"):
|
|
131
|
+
from datetime import datetime, timezone
|
|
132
|
+
|
|
133
|
+
from cctx.models import SessionTrace, Turn
|
|
134
|
+
|
|
135
|
+
t = Turn(
|
|
136
|
+
turn_number=turn_number,
|
|
137
|
+
uuid=f"uuid-{turn_number}",
|
|
138
|
+
parent_uuid=None,
|
|
139
|
+
role=role,
|
|
140
|
+
text=text,
|
|
141
|
+
thinking="",
|
|
142
|
+
tool_uses=[],
|
|
143
|
+
tool_results=[],
|
|
144
|
+
usage=None,
|
|
145
|
+
model="claude-sonnet-4-6",
|
|
146
|
+
stop_reason="end_turn",
|
|
147
|
+
timestamp=datetime(2026, 5, 14, 10, 30, 0, tzinfo=timezone.utc),
|
|
148
|
+
duration_ms=None,
|
|
149
|
+
)
|
|
150
|
+
from pathlib import Path
|
|
151
|
+
return SessionTrace(
|
|
152
|
+
session_id="trace-test",
|
|
153
|
+
parent_session_id=None,
|
|
154
|
+
project_path="/p",
|
|
155
|
+
cwd="/p",
|
|
156
|
+
primary_model="claude-sonnet-4-6",
|
|
157
|
+
claude_code_version=None,
|
|
158
|
+
turns=[t],
|
|
159
|
+
subagents=[],
|
|
160
|
+
attachments=[],
|
|
161
|
+
raw_tool_result_files=[],
|
|
162
|
+
initial_context_tokens=0,
|
|
163
|
+
tool_names_loaded=[],
|
|
164
|
+
start_time=t.timestamp,
|
|
165
|
+
end_time=t.timestamp,
|
|
166
|
+
source_path=Path("/p/trace-test.jsonl"),
|
|
167
|
+
subagent_meta={},
|
|
168
|
+
warnings=[],
|
|
169
|
+
subagent_parse_errors=[],
|
|
170
|
+
)
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
def _render_turn_to_string(trace, diagnosis, turn_num):
|
|
174
|
+
from rich.console import Console
|
|
175
|
+
|
|
176
|
+
from cctx.renderers.terminal import render_turn
|
|
177
|
+
|
|
178
|
+
buf = StringIO()
|
|
179
|
+
console = Console(file=buf, width=120, highlight=False, markup=False)
|
|
180
|
+
render_turn(trace, diagnosis, turn_num, console=console)
|
|
181
|
+
return buf.getvalue()
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
def test_render_turn_shows_role_and_number():
|
|
185
|
+
trace = _make_trace_with_turn(turn_number=3, role="assistant", text="hello world")
|
|
186
|
+
diag = _make_diagnosis()
|
|
187
|
+
output = _render_turn_to_string(trace, diag, 3)
|
|
188
|
+
assert "Turn 3" in output
|
|
189
|
+
assert "assistant" in output
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
def test_render_turn_shows_text():
|
|
193
|
+
trace = _make_trace_with_turn(text="important content here")
|
|
194
|
+
diag = _make_diagnosis()
|
|
195
|
+
output = _render_turn_to_string(trace, diag, 3)
|
|
196
|
+
assert "important content here" in output
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
def test_render_turn_not_found():
|
|
200
|
+
trace = _make_trace_with_turn(turn_number=3)
|
|
201
|
+
diag = _make_diagnosis()
|
|
202
|
+
output = _render_turn_to_string(trace, diag, 99)
|
|
203
|
+
assert "not found" in output.lower() or "99" in output
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
def test_render_turn_shows_active_finding():
|
|
207
|
+
trace = _make_trace_with_turn(turn_number=5)
|
|
208
|
+
finding = _make_finding("retry_loop") # first_turn=5, last_turn=10
|
|
209
|
+
diag = _make_diagnosis([finding])
|
|
210
|
+
output = _render_turn_to_string(trace, diag, 5)
|
|
211
|
+
assert "retry" in output.lower() or "RETRY" in output
|
|
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-0.2.0 → cctx_cli-1.1.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-0.2.0 → cctx_cli-1.1.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-0.2.0 → cctx_cli-1.1.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-0.2.0 → cctx_cli-1.1.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-0.2.0 → cctx_cli-1.1.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
|