cctx-cli 1.13.0__tar.gz → 1.14.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.
Files changed (190) hide show
  1. {cctx_cli-1.13.0 → cctx_cli-1.14.0}/CHANGELOG.md +9 -0
  2. {cctx_cli-1.13.0 → cctx_cli-1.14.0}/PKG-INFO +1 -1
  3. {cctx_cli-1.13.0 → cctx_cli-1.14.0}/cctx/__init__.py +1 -1
  4. {cctx_cli-1.13.0 → cctx_cli-1.14.0}/cctx/cli.py +9 -1
  5. {cctx_cli-1.13.0 → cctx_cli-1.14.0}/cctx/renderers/terminal.py +35 -0
  6. {cctx_cli-1.13.0 → cctx_cli-1.14.0}/pyproject.toml +1 -1
  7. cctx_cli-1.14.0/tests/test_savings_framing.py +202 -0
  8. {cctx_cli-1.13.0 → cctx_cli-1.14.0}/.github/workflows/ci.yml +0 -0
  9. {cctx_cli-1.13.0 → cctx_cli-1.14.0}/.github/workflows/publish.yml +0 -0
  10. {cctx_cli-1.13.0 → cctx_cli-1.14.0}/.github/workflows/release.yml +0 -0
  11. {cctx_cli-1.13.0 → cctx_cli-1.14.0}/.gitignore +0 -0
  12. {cctx_cli-1.13.0 → cctx_cli-1.14.0}/CLAUDE.md +0 -0
  13. {cctx_cli-1.13.0 → cctx_cli-1.14.0}/DESIGN.md +0 -0
  14. {cctx_cli-1.13.0 → cctx_cli-1.14.0}/PRODUCT.md +0 -0
  15. {cctx_cli-1.13.0 → cctx_cli-1.14.0}/README.md +0 -0
  16. {cctx_cli-1.13.0 → cctx_cli-1.14.0}/action.yml +0 -0
  17. {cctx_cli-1.13.0 → cctx_cli-1.14.0}/cctx/agents.py +0 -0
  18. {cctx_cli-1.13.0 → cctx_cli-1.14.0}/cctx/diagnostician/__init__.py +0 -0
  19. {cctx_cli-1.13.0 → cctx_cli-1.14.0}/cctx/diagnostician/aggregate.py +0 -0
  20. {cctx_cli-1.13.0 → cctx_cli-1.14.0}/cctx/diagnostician/inflection.py +0 -0
  21. {cctx_cli-1.13.0 → cctx_cli-1.14.0}/cctx/diagnostician/patterns/__init__.py +0 -0
  22. {cctx_cli-1.13.0 → cctx_cli-1.14.0}/cctx/diagnostician/patterns/cache_hygiene.py +0 -0
  23. {cctx_cli-1.13.0 → cctx_cli-1.14.0}/cctx/diagnostician/patterns/dead_end.py +0 -0
  24. {cctx_cli-1.13.0 → cctx_cli-1.14.0}/cctx/diagnostician/patterns/fan_out.py +0 -0
  25. {cctx_cli-1.13.0 → cctx_cli-1.14.0}/cctx/diagnostician/patterns/project_specific.py +0 -0
  26. {cctx_cli-1.13.0 → cctx_cli-1.14.0}/cctx/diagnostician/patterns/retry_loop.py +0 -0
  27. {cctx_cli-1.13.0 → cctx_cli-1.14.0}/cctx/diagnostician/patterns/scope_creep.py +0 -0
  28. {cctx_cli-1.13.0 → cctx_cli-1.14.0}/cctx/diagnostician/patterns/stale_context.py +0 -0
  29. {cctx_cli-1.13.0 → cctx_cli-1.14.0}/cctx/diagnostician/patterns/tool_thrash.py +0 -0
  30. {cctx_cli-1.13.0 → cctx_cli-1.14.0}/cctx/discovery.py +0 -0
  31. {cctx_cli-1.13.0 → cctx_cli-1.14.0}/cctx/exporters/__init__.py +0 -0
  32. {cctx_cli-1.13.0 → cctx_cli-1.14.0}/cctx/exporters/csv.py +0 -0
  33. {cctx_cli-1.13.0 → cctx_cli-1.14.0}/cctx/exporters/json.py +0 -0
  34. {cctx_cli-1.13.0 → cctx_cli-1.14.0}/cctx/exporters/jsonl.py +0 -0
  35. {cctx_cli-1.13.0 → cctx_cli-1.14.0}/cctx/harvest.py +0 -0
  36. {cctx_cli-1.13.0 → cctx_cli-1.14.0}/cctx/hook_installer.py +0 -0
  37. {cctx_cli-1.13.0 → cctx_cli-1.14.0}/cctx/models.py +0 -0
  38. {cctx_cli-1.13.0 → cctx_cli-1.14.0}/cctx/parsers/__init__.py +0 -0
  39. {cctx_cli-1.13.0 → cctx_cli-1.14.0}/cctx/parsers/claude_code.py +0 -0
  40. {cctx_cli-1.13.0 → cctx_cli-1.14.0}/cctx/parsers/otel.py +0 -0
  41. {cctx_cli-1.13.0 → cctx_cli-1.14.0}/cctx/pricing.py +0 -0
  42. {cctx_cli-1.13.0 → cctx_cli-1.14.0}/cctx/recommender/__init__.py +0 -0
  43. {cctx_cli-1.13.0 → cctx_cli-1.14.0}/cctx/recommender/claude_md.py +0 -0
  44. {cctx_cli-1.13.0 → cctx_cli-1.14.0}/cctx/recommender/evidence.py +0 -0
  45. {cctx_cli-1.13.0 → cctx_cli-1.14.0}/cctx/renderers/__init__.py +0 -0
  46. {cctx_cli-1.13.0 → cctx_cli-1.14.0}/cctx/renderers/github.py +0 -0
  47. {cctx_cli-1.13.0 → cctx_cli-1.14.0}/cctx/renderers/report.py +0 -0
  48. {cctx_cli-1.13.0 → cctx_cli-1.14.0}/cctx/renderers/templates/autopsy.html.j2 +0 -0
  49. {cctx_cli-1.13.0 → cctx_cli-1.14.0}/cctx/renderers/trace_tui.py +0 -0
  50. {cctx_cli-1.13.0 → cctx_cli-1.14.0}/cctx/tokenizer.py +0 -0
  51. {cctx_cli-1.13.0 → cctx_cli-1.14.0}/cctx/watcher.py +0 -0
  52. {cctx_cli-1.13.0 → cctx_cli-1.14.0}/cctx-project-brief.md +0 -0
  53. {cctx_cli-1.13.0 → cctx_cli-1.14.0}/demo.gif +0 -0
  54. {cctx_cli-1.13.0 → cctx_cli-1.14.0}/demo.tape +0 -0
  55. {cctx_cli-1.13.0 → cctx_cli-1.14.0}/docs/health-reviews/2026-05-15-deep-review-summary.md +0 -0
  56. {cctx_cli-1.13.0 → cctx_cli-1.14.0}/docs/health-reviews/2026-05-15-health-review.md +0 -0
  57. {cctx_cli-1.13.0 → cctx_cli-1.14.0}/docs/product-reviews/2026-05-15-product-review.md +0 -0
  58. {cctx_cli-1.13.0 → cctx_cli-1.14.0}/docs/product-reviews/2026-06-09-product-review.md +0 -0
  59. {cctx_cli-1.13.0 → cctx_cli-1.14.0}/docs/quickstart-otel.md +0 -0
  60. {cctx_cli-1.13.0 → cctx_cli-1.14.0}/docs/superpowers/plans/2026-05-12-claude-code-parser.md +0 -0
  61. {cctx_cli-1.13.0 → cctx_cli-1.14.0}/docs/superpowers/plans/2026-05-14-autopsy-v0.md +0 -0
  62. {cctx_cli-1.13.0 → cctx_cli-1.14.0}/docs/superpowers/plans/2026-05-16-readme-pypi-release.md +0 -0
  63. {cctx_cli-1.13.0 → cctx_cli-1.14.0}/docs/superpowers/plans/2026-05-17-harvest-check-depth.md +0 -0
  64. {cctx_cli-1.13.0 → cctx_cli-1.14.0}/docs/superpowers/plans/2026-05-17-project-pattern-detection.md +0 -0
  65. {cctx_cli-1.13.0 → cctx_cli-1.14.0}/docs/superpowers/plans/2026-05-19-claude-agents-live-integration.md +0 -0
  66. {cctx_cli-1.13.0 → cctx_cli-1.14.0}/docs/superpowers/plans/2026-06-19-otel-parser.md +0 -0
  67. {cctx_cli-1.13.0 → cctx_cli-1.14.0}/docs/superpowers/specs/2026-05-12-claude-code-parser-design.md +0 -0
  68. {cctx_cli-1.13.0 → cctx_cli-1.14.0}/docs/superpowers/specs/2026-05-14-autopsy-design.md +0 -0
  69. {cctx_cli-1.13.0 → cctx_cli-1.14.0}/docs/superpowers/specs/2026-05-14-harvest-design.md +0 -0
  70. {cctx_cli-1.13.0 → cctx_cli-1.14.0}/docs/superpowers/specs/2026-05-14-trace-tui-design.md +0 -0
  71. {cctx_cli-1.13.0 → cctx_cli-1.14.0}/docs/superpowers/specs/2026-05-16-readme-pypi-release-design.md +0 -0
  72. {cctx_cli-1.13.0 → cctx_cli-1.14.0}/docs/superpowers/specs/2026-05-17-harvest-check-depth-design.md +0 -0
  73. {cctx_cli-1.13.0 → cctx_cli-1.14.0}/docs/superpowers/specs/2026-05-17-project-pattern-detection-design.md +0 -0
  74. {cctx_cli-1.13.0 → cctx_cli-1.14.0}/docs/superpowers/specs/2026-05-19-claude-agents-live-integration-design.md +0 -0
  75. {cctx_cli-1.13.0 → cctx_cli-1.14.0}/docs/superpowers/specs/2026-06-09-cross-agent-emit-design.md +0 -0
  76. {cctx_cli-1.13.0 → cctx_cli-1.14.0}/docs/superpowers/specs/2026-06-19-otel-parser-design.md +0 -0
  77. {cctx_cli-1.13.0 → cctx_cli-1.14.0}/tests/__init__.py +0 -0
  78. {cctx_cli-1.13.0 → cctx_cli-1.14.0}/tests/conftest.py +0 -0
  79. {cctx_cli-1.13.0 → cctx_cli-1.14.0}/tests/diagnostician/__init__.py +0 -0
  80. {cctx_cli-1.13.0 → cctx_cli-1.14.0}/tests/diagnostician/conftest.py +0 -0
  81. {cctx_cli-1.13.0 → cctx_cli-1.14.0}/tests/diagnostician/test_dead_end.py +0 -0
  82. {cctx_cli-1.13.0 → cctx_cli-1.14.0}/tests/diagnostician/test_inflection.py +0 -0
  83. {cctx_cli-1.13.0 → cctx_cli-1.14.0}/tests/diagnostician/test_orchestrator.py +0 -0
  84. {cctx_cli-1.13.0 → cctx_cli-1.14.0}/tests/diagnostician/test_project_specific.py +0 -0
  85. {cctx_cli-1.13.0 → cctx_cli-1.14.0}/tests/diagnostician/test_retry_loop.py +0 -0
  86. {cctx_cli-1.13.0 → cctx_cli-1.14.0}/tests/diagnostician/test_scope_creep.py +0 -0
  87. {cctx_cli-1.13.0 → cctx_cli-1.14.0}/tests/diagnostician/test_stale_context.py +0 -0
  88. {cctx_cli-1.13.0 → cctx_cli-1.14.0}/tests/diagnostician/test_tool_thrash.py +0 -0
  89. {cctx_cli-1.13.0 → cctx_cli-1.14.0}/tests/exporters/__init__.py +0 -0
  90. {cctx_cli-1.13.0 → cctx_cli-1.14.0}/tests/exporters/test_csv.py +0 -0
  91. {cctx_cli-1.13.0 → cctx_cli-1.14.0}/tests/exporters/test_jsonl.py +0 -0
  92. {cctx_cli-1.13.0 → cctx_cli-1.14.0}/tests/fixtures/claude_code/README.md +0 -0
  93. {cctx_cli-1.13.0 → cctx_cli-1.14.0}/tests/fixtures/claude_code/short-clean/short-clean.jsonl +0 -0
  94. {cctx_cli-1.13.0 → cctx_cli-1.14.0}/tests/fixtures/claude_code/with-attachments/with-attachments/subagents/agent-a0b4c2cf1dde0ca56.meta.json +0 -0
  95. {cctx_cli-1.13.0 → cctx_cli-1.14.0}/tests/fixtures/claude_code/with-attachments/with-attachments/subagents/agent-a116ae34b1b09c332.meta.json +0 -0
  96. {cctx_cli-1.13.0 → cctx_cli-1.14.0}/tests/fixtures/claude_code/with-attachments/with-attachments/subagents/agent-a1c4c417b35658c9e.meta.json +0 -0
  97. {cctx_cli-1.13.0 → cctx_cli-1.14.0}/tests/fixtures/claude_code/with-attachments/with-attachments/subagents/agent-a1e41a901de38f1b5.meta.json +0 -0
  98. {cctx_cli-1.13.0 → cctx_cli-1.14.0}/tests/fixtures/claude_code/with-attachments/with-attachments/subagents/agent-a338f8d0c74612a24.meta.json +0 -0
  99. {cctx_cli-1.13.0 → cctx_cli-1.14.0}/tests/fixtures/claude_code/with-attachments/with-attachments/subagents/agent-a34f6f3c0e7094186.meta.json +0 -0
  100. {cctx_cli-1.13.0 → cctx_cli-1.14.0}/tests/fixtures/claude_code/with-attachments/with-attachments/subagents/agent-a5a5a0cff4d13308b.meta.json +0 -0
  101. {cctx_cli-1.13.0 → cctx_cli-1.14.0}/tests/fixtures/claude_code/with-attachments/with-attachments/subagents/agent-a6b0a3da6a0484db5.meta.json +0 -0
  102. {cctx_cli-1.13.0 → cctx_cli-1.14.0}/tests/fixtures/claude_code/with-attachments/with-attachments/subagents/agent-a7f73f1790b02cde5.meta.json +0 -0
  103. {cctx_cli-1.13.0 → cctx_cli-1.14.0}/tests/fixtures/claude_code/with-attachments/with-attachments/subagents/agent-a7f7c17c38a9d8788.meta.json +0 -0
  104. {cctx_cli-1.13.0 → cctx_cli-1.14.0}/tests/fixtures/claude_code/with-attachments/with-attachments/subagents/agent-a853259e2cd7bbe8a.meta.json +0 -0
  105. {cctx_cli-1.13.0 → cctx_cli-1.14.0}/tests/fixtures/claude_code/with-attachments/with-attachments/subagents/agent-a8d9aedb0d0c6e12d.meta.json +0 -0
  106. {cctx_cli-1.13.0 → cctx_cli-1.14.0}/tests/fixtures/claude_code/with-attachments/with-attachments/subagents/agent-aa778bc1d59e4a441.meta.json +0 -0
  107. {cctx_cli-1.13.0 → cctx_cli-1.14.0}/tests/fixtures/claude_code/with-attachments/with-attachments/subagents/agent-aba869dedee4a12ba.meta.json +0 -0
  108. {cctx_cli-1.13.0 → cctx_cli-1.14.0}/tests/fixtures/claude_code/with-attachments/with-attachments/subagents/agent-ada2746d9774b94db.meta.json +0 -0
  109. {cctx_cli-1.13.0 → cctx_cli-1.14.0}/tests/fixtures/claude_code/with-attachments/with-attachments/subagents/agent-aea0132068c64d2dd.meta.json +0 -0
  110. {cctx_cli-1.13.0 → cctx_cli-1.14.0}/tests/fixtures/claude_code/with-attachments/with-attachments/subagents/agent-aea215eff50874d5f.meta.json +0 -0
  111. {cctx_cli-1.13.0 → cctx_cli-1.14.0}/tests/fixtures/claude_code/with-attachments/with-attachments/subagents/agent-afee21f2b3852a4a0.meta.json +0 -0
  112. {cctx_cli-1.13.0 → cctx_cli-1.14.0}/tests/fixtures/claude_code/with-attachments/with-attachments.jsonl +0 -0
  113. {cctx_cli-1.13.0 → cctx_cli-1.14.0}/tests/fixtures/claude_code/with-compaction/with-compaction/subagents/agent-a051d9c9a6b2f5cc3.jsonl +0 -0
  114. {cctx_cli-1.13.0 → cctx_cli-1.14.0}/tests/fixtures/claude_code/with-compaction/with-compaction/subagents/agent-a051d9c9a6b2f5cc3.meta.json +0 -0
  115. {cctx_cli-1.13.0 → cctx_cli-1.14.0}/tests/fixtures/claude_code/with-compaction/with-compaction/subagents/agent-a171f16f4e65cfe75.jsonl +0 -0
  116. {cctx_cli-1.13.0 → cctx_cli-1.14.0}/tests/fixtures/claude_code/with-compaction/with-compaction/subagents/agent-a171f16f4e65cfe75.meta.json +0 -0
  117. {cctx_cli-1.13.0 → cctx_cli-1.14.0}/tests/fixtures/claude_code/with-compaction/with-compaction/subagents/agent-a1b77fea2c0a2269b.jsonl +0 -0
  118. {cctx_cli-1.13.0 → cctx_cli-1.14.0}/tests/fixtures/claude_code/with-compaction/with-compaction/subagents/agent-a1b77fea2c0a2269b.meta.json +0 -0
  119. {cctx_cli-1.13.0 → cctx_cli-1.14.0}/tests/fixtures/claude_code/with-compaction/with-compaction/subagents/agent-a20da4c01a54acca8.jsonl +0 -0
  120. {cctx_cli-1.13.0 → cctx_cli-1.14.0}/tests/fixtures/claude_code/with-compaction/with-compaction/subagents/agent-a20da4c01a54acca8.meta.json +0 -0
  121. {cctx_cli-1.13.0 → cctx_cli-1.14.0}/tests/fixtures/claude_code/with-compaction/with-compaction/subagents/agent-a3c82739b1383fb14.jsonl +0 -0
  122. {cctx_cli-1.13.0 → cctx_cli-1.14.0}/tests/fixtures/claude_code/with-compaction/with-compaction/subagents/agent-a3c82739b1383fb14.meta.json +0 -0
  123. {cctx_cli-1.13.0 → cctx_cli-1.14.0}/tests/fixtures/claude_code/with-compaction/with-compaction/subagents/agent-a49e8539611c5fe12.jsonl +0 -0
  124. {cctx_cli-1.13.0 → cctx_cli-1.14.0}/tests/fixtures/claude_code/with-compaction/with-compaction/subagents/agent-a49e8539611c5fe12.meta.json +0 -0
  125. {cctx_cli-1.13.0 → cctx_cli-1.14.0}/tests/fixtures/claude_code/with-compaction/with-compaction/subagents/agent-a7bb58f3fff2b3e8d.jsonl +0 -0
  126. {cctx_cli-1.13.0 → cctx_cli-1.14.0}/tests/fixtures/claude_code/with-compaction/with-compaction/subagents/agent-a7bb58f3fff2b3e8d.meta.json +0 -0
  127. {cctx_cli-1.13.0 → cctx_cli-1.14.0}/tests/fixtures/claude_code/with-compaction/with-compaction/subagents/agent-a92b48c0331195aac.jsonl +0 -0
  128. {cctx_cli-1.13.0 → cctx_cli-1.14.0}/tests/fixtures/claude_code/with-compaction/with-compaction/subagents/agent-a92b48c0331195aac.meta.json +0 -0
  129. {cctx_cli-1.13.0 → cctx_cli-1.14.0}/tests/fixtures/claude_code/with-compaction/with-compaction/subagents/agent-ab96c4264099694a9.jsonl +0 -0
  130. {cctx_cli-1.13.0 → cctx_cli-1.14.0}/tests/fixtures/claude_code/with-compaction/with-compaction/subagents/agent-ab96c4264099694a9.meta.json +0 -0
  131. {cctx_cli-1.13.0 → cctx_cli-1.14.0}/tests/fixtures/claude_code/with-compaction/with-compaction/subagents/agent-acb2895c5e34ffec0.jsonl +0 -0
  132. {cctx_cli-1.13.0 → cctx_cli-1.14.0}/tests/fixtures/claude_code/with-compaction/with-compaction/subagents/agent-acb2895c5e34ffec0.meta.json +0 -0
  133. {cctx_cli-1.13.0 → cctx_cli-1.14.0}/tests/fixtures/claude_code/with-compaction/with-compaction/subagents/agent-adb2302769938fb3f.jsonl +0 -0
  134. {cctx_cli-1.13.0 → cctx_cli-1.14.0}/tests/fixtures/claude_code/with-compaction/with-compaction/subagents/agent-adb2302769938fb3f.meta.json +0 -0
  135. {cctx_cli-1.13.0 → cctx_cli-1.14.0}/tests/fixtures/claude_code/with-compaction/with-compaction/subagents/agent-ae585eca15cb93b9c.jsonl +0 -0
  136. {cctx_cli-1.13.0 → cctx_cli-1.14.0}/tests/fixtures/claude_code/with-compaction/with-compaction/subagents/agent-ae585eca15cb93b9c.meta.json +0 -0
  137. {cctx_cli-1.13.0 → cctx_cli-1.14.0}/tests/fixtures/claude_code/with-compaction/with-compaction/subagents/agent-aec9c917feb903d67.jsonl +0 -0
  138. {cctx_cli-1.13.0 → cctx_cli-1.14.0}/tests/fixtures/claude_code/with-compaction/with-compaction/subagents/agent-aec9c917feb903d67.meta.json +0 -0
  139. {cctx_cli-1.13.0 → cctx_cli-1.14.0}/tests/fixtures/claude_code/with-compaction/with-compaction.jsonl +0 -0
  140. {cctx_cli-1.13.0 → cctx_cli-1.14.0}/tests/fixtures/claude_code/with-subagents/with-subagents/subagents/agent-a1a3a21aeb76bb0a9.jsonl +0 -0
  141. {cctx_cli-1.13.0 → cctx_cli-1.14.0}/tests/fixtures/claude_code/with-subagents/with-subagents/subagents/agent-a1a3a21aeb76bb0a9.meta.json +0 -0
  142. {cctx_cli-1.13.0 → cctx_cli-1.14.0}/tests/fixtures/claude_code/with-subagents/with-subagents/subagents/agent-aaa1d6ecc05a78442.jsonl +0 -0
  143. {cctx_cli-1.13.0 → cctx_cli-1.14.0}/tests/fixtures/claude_code/with-subagents/with-subagents/subagents/agent-aaa1d6ecc05a78442.meta.json +0 -0
  144. {cctx_cli-1.13.0 → cctx_cli-1.14.0}/tests/fixtures/claude_code/with-subagents/with-subagents/subagents/agent-af3c545ccd30036d2.jsonl +0 -0
  145. {cctx_cli-1.13.0 → cctx_cli-1.14.0}/tests/fixtures/claude_code/with-subagents/with-subagents/subagents/agent-af3c545ccd30036d2.meta.json +0 -0
  146. {cctx_cli-1.13.0 → cctx_cli-1.14.0}/tests/fixtures/claude_code/with-subagents/with-subagents/tool-results/btwp2bzro.txt +0 -0
  147. {cctx_cli-1.13.0 → cctx_cli-1.14.0}/tests/fixtures/claude_code/with-subagents/with-subagents/tool-results/byqjbgy4b.txt +0 -0
  148. {cctx_cli-1.13.0 → cctx_cli-1.14.0}/tests/fixtures/claude_code/with-subagents/with-subagents.jsonl +0 -0
  149. {cctx_cli-1.13.0 → cctx_cli-1.14.0}/tests/fixtures/claude_code/with-tool-results/with-tool-results/tool-results/bosbkda0h.txt +0 -0
  150. {cctx_cli-1.13.0 → cctx_cli-1.14.0}/tests/fixtures/claude_code/with-tool-results/with-tool-results.jsonl +0 -0
  151. {cctx_cli-1.13.0 → cctx_cli-1.14.0}/tests/fixtures/otel_fanout.jsonl +0 -0
  152. {cctx_cli-1.13.0 → cctx_cli-1.14.0}/tests/fixtures/otel_handoff.jsonl +0 -0
  153. {cctx_cli-1.13.0 → cctx_cli-1.14.0}/tests/fixtures/scrub.py +0 -0
  154. {cctx_cli-1.13.0 → cctx_cli-1.14.0}/tests/fixtures/synthetic/bookkeeping_only.jsonl +0 -0
  155. {cctx_cli-1.13.0 → cctx_cli-1.14.0}/tests/fixtures/synthetic/malformed_middle.jsonl +0 -0
  156. {cctx_cli-1.13.0 → cctx_cli-1.14.0}/tests/fixtures/synthetic/truncated_final_line.jsonl +0 -0
  157. {cctx_cli-1.13.0 → cctx_cli-1.14.0}/tests/fixtures/synthetic/unknown_attachment_shape.jsonl +0 -0
  158. {cctx_cli-1.13.0 → cctx_cli-1.14.0}/tests/fixtures/synthetic/unknown_type.jsonl +0 -0
  159. {cctx_cli-1.13.0 → cctx_cli-1.14.0}/tests/parsers/__init__.py +0 -0
  160. {cctx_cli-1.13.0 → cctx_cli-1.14.0}/tests/parsers/test_claude_code.py +0 -0
  161. {cctx_cli-1.13.0 → cctx_cli-1.14.0}/tests/parsers/test_claude_code_integration.py +0 -0
  162. {cctx_cli-1.13.0 → cctx_cli-1.14.0}/tests/recommender/__init__.py +0 -0
  163. {cctx_cli-1.13.0 → cctx_cli-1.14.0}/tests/recommender/test_claude_md.py +0 -0
  164. {cctx_cli-1.13.0 → cctx_cli-1.14.0}/tests/recommender/test_evidence.py +0 -0
  165. {cctx_cli-1.13.0 → cctx_cli-1.14.0}/tests/renderers/__init__.py +0 -0
  166. {cctx_cli-1.13.0 → cctx_cli-1.14.0}/tests/renderers/test_report.py +0 -0
  167. {cctx_cli-1.13.0 → cctx_cli-1.14.0}/tests/renderers/test_terminal_renderer_full.py +0 -0
  168. {cctx_cli-1.13.0 → cctx_cli-1.14.0}/tests/test_agents.py +0 -0
  169. {cctx_cli-1.13.0 → cctx_cli-1.14.0}/tests/test_aggregate.py +0 -0
  170. {cctx_cli-1.13.0 → cctx_cli-1.14.0}/tests/test_cache_hygiene_classifier.py +0 -0
  171. {cctx_cli-1.13.0 → cctx_cli-1.14.0}/tests/test_cli.py +0 -0
  172. {cctx_cli-1.13.0 → cctx_cli-1.14.0}/tests/test_cli_export.py +0 -0
  173. {cctx_cli-1.13.0 → cctx_cli-1.14.0}/tests/test_diagnostician_subagents.py +0 -0
  174. {cctx_cli-1.13.0 → cctx_cli-1.14.0}/tests/test_discovery.py +0 -0
  175. {cctx_cli-1.13.0 → cctx_cli-1.14.0}/tests/test_efficacy.py +0 -0
  176. {cctx_cli-1.13.0 → cctx_cli-1.14.0}/tests/test_fanout_classifier.py +0 -0
  177. {cctx_cli-1.13.0 → cctx_cli-1.14.0}/tests/test_github_summary.py +0 -0
  178. {cctx_cli-1.13.0 → cctx_cli-1.14.0}/tests/test_harvest.py +0 -0
  179. {cctx_cli-1.13.0 → cctx_cli-1.14.0}/tests/test_harvest_check.py +0 -0
  180. {cctx_cli-1.13.0 → cctx_cli-1.14.0}/tests/test_harvest_emit.py +0 -0
  181. {cctx_cli-1.13.0 → cctx_cli-1.14.0}/tests/test_init.py +0 -0
  182. {cctx_cli-1.13.0 → cctx_cli-1.14.0}/tests/test_models.py +0 -0
  183. {cctx_cli-1.13.0 → cctx_cli-1.14.0}/tests/test_models_project_pattern.py +0 -0
  184. {cctx_cli-1.13.0 → cctx_cli-1.14.0}/tests/test_otel_parser.py +0 -0
  185. {cctx_cli-1.13.0 → cctx_cli-1.14.0}/tests/test_recommender.py +0 -0
  186. {cctx_cli-1.13.0 → cctx_cli-1.14.0}/tests/test_smoke.py +0 -0
  187. {cctx_cli-1.13.0 → cctx_cli-1.14.0}/tests/test_terminal_renderer.py +0 -0
  188. {cctx_cli-1.13.0 → cctx_cli-1.14.0}/tests/test_tokenizer.py +0 -0
  189. {cctx_cli-1.13.0 → cctx_cli-1.14.0}/tests/test_trace_tui.py +0 -0
  190. {cctx_cli-1.13.0 → cctx_cli-1.14.0}/tests/test_watcher.py +0 -0
@@ -2,6 +2,15 @@
2
2
 
3
3
  <!-- version list -->
4
4
 
5
+ ## v1.14.0 (2026-06-20)
6
+
7
+ ### Features
8
+
9
+ - Savings framing + health grade behind --health flag (#101)
10
+ ([#122](https://github.com/jacquardlabs/cctx/pull/122),
11
+ [`ee4bc28`](https://github.com/jacquardlabs/cctx/commit/ee4bc2833d7260275e95666ba45a5952aa174aee))
12
+
13
+
5
14
  ## v1.13.0 (2026-06-20)
6
15
 
7
16
  ### Documentation
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: cctx-cli
3
- Version: 1.13.0
3
+ Version: 1.14.0
4
4
  Summary: Diagnose Claude Code sessions — find what went wrong, what it cost, and what to add to CLAUDE.md
5
5
  Author: Jacquard Labs
6
6
  License-Expression: MIT
@@ -1,3 +1,3 @@
1
1
  """cctx: profile, debug, and optimize Claude Code and Agent SDK sessions."""
2
2
 
3
- __version__ = "1.13.0"
3
+ __version__ = "1.14.0"
@@ -346,6 +346,13 @@ def ls(project: Path | None) -> None:
346
346
  help="Print one verdict line only when findings exist; nothing if clean. "
347
347
  "Designed for SessionEnd hook use (cctx init).",
348
348
  )
349
+ @click.option(
350
+ "--health",
351
+ "health",
352
+ is_flag=True,
353
+ default=False,
354
+ help="Show health grade (A–F) and per-finding savings estimate.",
355
+ )
349
356
  def autopsy(
350
357
  target: Path | None,
351
358
  since: str | None,
@@ -358,6 +365,7 @@ def autopsy(
358
365
  turn_num: int | None,
359
366
  json_out: bool,
360
367
  quiet: bool,
368
+ health: bool,
361
369
  ) -> None:
362
370
  """Diagnose a session or project directory.
363
371
 
@@ -488,7 +496,7 @@ def autopsy(
488
496
  from cctx.renderers.github import write_github_summary
489
497
  write_github_summary(diagnosis)
490
498
  else:
491
- render_diagnosis(diagnosis, session_path=target)
499
+ render_diagnosis(diagnosis, session_path=target, show_health=health)
492
500
  if fail_on_findings and diagnosis.findings:
493
501
  raise SystemExit(1)
494
502
 
@@ -45,11 +45,35 @@ def _wide_console() -> Console:
45
45
  return Console(width=200)
46
46
 
47
47
 
48
+ def compute_health_grade(diagnosis: Diagnosis) -> str:
49
+ """A–F grade based on waste fraction and finding severity."""
50
+ if not diagnosis.findings:
51
+ return "A"
52
+
53
+ has_high = any(f.severity == Severity.HIGH for f in diagnosis.findings)
54
+ waste_frac = (
55
+ diagnosis.waste_cost_usd / diagnosis.total_cost_usd
56
+ if diagnosis.total_cost_usd > 0
57
+ else 0.0
58
+ )
59
+
60
+ if has_high and waste_frac > 0.50:
61
+ return "F"
62
+ if has_high or waste_frac > 0.25:
63
+ return "D"
64
+ if waste_frac > 0.10:
65
+ return "C"
66
+ if diagnosis.findings:
67
+ return "B"
68
+ return "A"
69
+
70
+
48
71
  def render_diagnosis(
49
72
  diagnosis: Diagnosis,
50
73
  *,
51
74
  session_path: Path | None = None,
52
75
  console: Console | None = None,
76
+ show_health: bool = False,
53
77
  ) -> None:
54
78
  con = console or _default_console()
55
79
 
@@ -77,6 +101,15 @@ def render_diagnosis(
77
101
  "~85–95% of actual billing; system framing not observable in JSONL", style="dim"
78
102
  ))
79
103
 
104
+ if show_health:
105
+ grade = compute_health_grade(diagnosis)
106
+ waste_frac = (
107
+ diagnosis.waste_cost_usd / diagnosis.total_cost_usd * 100
108
+ if diagnosis.total_cost_usd > 0
109
+ else 0.0
110
+ )
111
+ con.print(f"Health grade: {grade} (waste {waste_frac:.0f}% of session cost)")
112
+
80
113
  if diagnosis.subagent_costs:
81
114
  show_depth = any(a.depth > 1 for a in diagnosis.subagent_costs)
82
115
  tbl = Table(show_header=True, header_style="bold", box=None, padding=(0, 1))
@@ -107,6 +140,8 @@ def render_diagnosis(
107
140
  badge = Text(f" {label} ", style=style)
108
141
  conf_note = f"({finding.confidence.value} confidence)"
109
142
  con.print(badge, conf_note, "—", finding.summary)
143
+ if show_health and finding.cost_usd is not None:
144
+ con.print(f" → savings if fixed: ~${finding.cost_usd:.2f}")
110
145
 
111
146
  # Patches
112
147
  if diagnosis.patches:
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "cctx-cli"
7
- version = "1.13.0"
7
+ version = "1.14.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"
@@ -0,0 +1,202 @@
1
+ """Tests for health grade and savings framing (issue #101).
2
+
3
+ Tests cover:
4
+ - compute_health_grade() grade logic for all A–F outcomes
5
+ - render_diagnosis() with show_health=True smoke test
6
+ """
7
+ from __future__ import annotations
8
+
9
+ import io
10
+ from datetime import datetime, timezone
11
+
12
+ import pytest
13
+
14
+ from cctx.models import Confidence, Diagnosis, Finding, FindingKind, Severity
15
+ from cctx.renderers.terminal import compute_health_grade, render_diagnosis
16
+
17
+ _TS = datetime(2026, 6, 1, tzinfo=timezone.utc)
18
+
19
+
20
+ def _diagnosis(
21
+ findings: list[Finding],
22
+ total_cost: float = 1.0,
23
+ waste_cost: float = 0.0,
24
+ ) -> Diagnosis:
25
+ return Diagnosis(
26
+ session_id="test-session",
27
+ findings=findings,
28
+ inflection_turn=None,
29
+ patches=[],
30
+ total_cost_usd=total_cost,
31
+ waste_cost_usd=waste_cost,
32
+ analysed_at=_TS,
33
+ subagent_costs=[],
34
+ )
35
+
36
+
37
+ def _finding(severity: Severity = Severity.MEDIUM, cost: float | None = None) -> Finding:
38
+ return Finding(
39
+ kind=FindingKind.RETRY_LOOP,
40
+ severity=severity,
41
+ confidence=Confidence.HIGH,
42
+ first_turn=1,
43
+ last_turn=5,
44
+ evidence={},
45
+ cost_usd=cost,
46
+ summary="test finding",
47
+ )
48
+
49
+
50
+ # ---------------------------------------------------------------------------
51
+ # Grade logic
52
+ # ---------------------------------------------------------------------------
53
+
54
+
55
+ def test_grade_no_findings_is_a():
56
+ diag = _diagnosis(findings=[], total_cost=1.0, waste_cost=0.0)
57
+ assert compute_health_grade(diag) == "A"
58
+
59
+
60
+ def test_grade_findings_low_waste_is_b():
61
+ """Findings present, waste < 10% → B."""
62
+ diag = _diagnosis(
63
+ findings=[_finding()],
64
+ total_cost=1.0,
65
+ waste_cost=0.05, # 5%
66
+ )
67
+ assert compute_health_grade(diag) == "B"
68
+
69
+
70
+ def test_grade_findings_moderate_waste_is_c():
71
+ """Findings present, waste 15% (>10%, ≤25%) → C."""
72
+ diag = _diagnosis(
73
+ findings=[_finding()],
74
+ total_cost=1.0,
75
+ waste_cost=0.15, # 15%
76
+ )
77
+ assert compute_health_grade(diag) == "C"
78
+
79
+
80
+ def test_grade_findings_high_waste_is_d():
81
+ """Findings present, waste 30% (>25%) → D."""
82
+ diag = _diagnosis(
83
+ findings=[_finding()],
84
+ total_cost=1.0,
85
+ waste_cost=0.30, # 30%
86
+ )
87
+ assert compute_health_grade(diag) == "D"
88
+
89
+
90
+ def test_grade_high_severity_low_waste_is_d():
91
+ """HIGH severity finding with waste < 25% → D (severity alone triggers D)."""
92
+ diag = _diagnosis(
93
+ findings=[_finding(severity=Severity.HIGH)],
94
+ total_cost=1.0,
95
+ waste_cost=0.10, # 10% — not above 25%, but HIGH severity still triggers D
96
+ )
97
+ assert compute_health_grade(diag) == "D"
98
+
99
+
100
+ def test_grade_high_severity_high_waste_is_f():
101
+ """HIGH severity finding + waste > 50% → F."""
102
+ diag = _diagnosis(
103
+ findings=[_finding(severity=Severity.HIGH)],
104
+ total_cost=1.0,
105
+ waste_cost=0.60, # 60%
106
+ )
107
+ assert compute_health_grade(diag) == "F"
108
+
109
+
110
+ def test_grade_zero_cost_session_with_findings_is_b():
111
+ """Zero-cost session (total=0, waste=0) with findings → waste_frac=0 → B."""
112
+ diag = _diagnosis(
113
+ findings=[_finding()],
114
+ total_cost=0.0,
115
+ waste_cost=0.0,
116
+ )
117
+ assert compute_health_grade(diag) == "B"
118
+
119
+
120
+ # ---------------------------------------------------------------------------
121
+ # render_diagnosis smoke test with show_health=True
122
+ # ---------------------------------------------------------------------------
123
+
124
+
125
+ @pytest.fixture
126
+ def make_diagnosis():
127
+ """Factory fixture: returns a callable that produces a Diagnosis."""
128
+ def _make(
129
+ findings: list[Finding] | None = None,
130
+ total_cost: float = 1.0,
131
+ waste_cost: float = 0.20,
132
+ ) -> Diagnosis:
133
+ if findings is None:
134
+ findings = [_finding(severity=Severity.MEDIUM, cost=0.20)]
135
+ return _diagnosis(findings, total_cost=total_cost, waste_cost=waste_cost)
136
+ return _make
137
+
138
+
139
+ def test_render_diagnosis_with_health_does_not_raise(make_diagnosis):
140
+ from rich.console import Console
141
+
142
+ buf = io.StringIO()
143
+ con = Console(file=buf, width=120, highlight=False, markup=False)
144
+ render_diagnosis(make_diagnosis(), show_health=True, console=con)
145
+ output = buf.getvalue()
146
+ assert "Health grade:" in output
147
+
148
+
149
+ def test_render_diagnosis_health_grade_visible(make_diagnosis):
150
+ from rich.console import Console
151
+
152
+ buf = io.StringIO()
153
+ con = Console(file=buf, width=120, highlight=False, markup=False)
154
+ # waste_cost=0.20 on total_cost=1.0 → 20% waste → grade C
155
+ render_diagnosis(make_diagnosis(waste_cost=0.20), show_health=True, console=con)
156
+ output = buf.getvalue()
157
+ assert "Health grade: C" in output
158
+
159
+
160
+ def test_render_diagnosis_savings_line_shown(make_diagnosis):
161
+ """When show_health=True and finding has cost_usd, savings line appears."""
162
+ from rich.console import Console
163
+
164
+ buf = io.StringIO()
165
+ con = Console(file=buf, width=120, highlight=False, markup=False)
166
+ findings = [_finding(cost=0.42)]
167
+ render_diagnosis(
168
+ _diagnosis(findings, total_cost=1.0, waste_cost=0.42),
169
+ show_health=True,
170
+ console=con,
171
+ )
172
+ output = buf.getvalue()
173
+ assert "savings if fixed" in output
174
+ assert "0.42" in output
175
+
176
+
177
+ def test_render_diagnosis_no_savings_when_cost_is_none(make_diagnosis):
178
+ """When finding.cost_usd is None, no savings line appears."""
179
+ from rich.console import Console
180
+
181
+ buf = io.StringIO()
182
+ con = Console(file=buf, width=120, highlight=False, markup=False)
183
+ findings = [_finding(cost=None)]
184
+ render_diagnosis(
185
+ _diagnosis(findings, total_cost=1.0, waste_cost=0.0),
186
+ show_health=True,
187
+ console=con,
188
+ )
189
+ output = buf.getvalue()
190
+ assert "savings if fixed" not in output
191
+
192
+
193
+ def test_render_diagnosis_health_hidden_by_default(make_diagnosis):
194
+ """Without show_health=True, no health grade or savings appear."""
195
+ from rich.console import Console
196
+
197
+ buf = io.StringIO()
198
+ con = Console(file=buf, width=120, highlight=False, markup=False)
199
+ render_diagnosis(make_diagnosis(), show_health=False, console=con)
200
+ output = buf.getvalue()
201
+ assert "Health grade:" not in output
202
+ assert "savings if fixed" not 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