cctx-cli 1.13.0__tar.gz → 1.15.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 (192) hide show
  1. {cctx_cli-1.13.0 → cctx_cli-1.15.0}/CHANGELOG.md +18 -0
  2. {cctx_cli-1.13.0 → cctx_cli-1.15.0}/PKG-INFO +1 -1
  3. {cctx_cli-1.13.0 → cctx_cli-1.15.0}/cctx/__init__.py +1 -1
  4. {cctx_cli-1.13.0 → cctx_cli-1.15.0}/cctx/cli.py +9 -1
  5. {cctx_cli-1.13.0 → cctx_cli-1.15.0}/cctx/diagnostician/__init__.py +2 -0
  6. cctx_cli-1.15.0/cctx/diagnostician/patterns/compaction.py +114 -0
  7. {cctx_cli-1.13.0 → cctx_cli-1.15.0}/cctx/diagnostician/patterns/dead_end.py +2 -1
  8. {cctx_cli-1.13.0 → cctx_cli-1.15.0}/cctx/diagnostician/patterns/stale_context.py +4 -2
  9. {cctx_cli-1.13.0 → cctx_cli-1.15.0}/cctx/models.py +3 -0
  10. {cctx_cli-1.13.0 → cctx_cli-1.15.0}/cctx/recommender/claude_md.py +10 -0
  11. {cctx_cli-1.13.0 → cctx_cli-1.15.0}/cctx/renderers/terminal.py +35 -0
  12. {cctx_cli-1.13.0 → cctx_cli-1.15.0}/pyproject.toml +1 -1
  13. cctx_cli-1.15.0/tests/test_compaction_classifier.py +373 -0
  14. {cctx_cli-1.13.0 → cctx_cli-1.15.0}/tests/test_harvest_emit.py +1 -0
  15. cctx_cli-1.15.0/tests/test_savings_framing.py +202 -0
  16. {cctx_cli-1.13.0 → cctx_cli-1.15.0}/.github/workflows/ci.yml +0 -0
  17. {cctx_cli-1.13.0 → cctx_cli-1.15.0}/.github/workflows/publish.yml +0 -0
  18. {cctx_cli-1.13.0 → cctx_cli-1.15.0}/.github/workflows/release.yml +0 -0
  19. {cctx_cli-1.13.0 → cctx_cli-1.15.0}/.gitignore +0 -0
  20. {cctx_cli-1.13.0 → cctx_cli-1.15.0}/CLAUDE.md +0 -0
  21. {cctx_cli-1.13.0 → cctx_cli-1.15.0}/DESIGN.md +0 -0
  22. {cctx_cli-1.13.0 → cctx_cli-1.15.0}/PRODUCT.md +0 -0
  23. {cctx_cli-1.13.0 → cctx_cli-1.15.0}/README.md +0 -0
  24. {cctx_cli-1.13.0 → cctx_cli-1.15.0}/action.yml +0 -0
  25. {cctx_cli-1.13.0 → cctx_cli-1.15.0}/cctx/agents.py +0 -0
  26. {cctx_cli-1.13.0 → cctx_cli-1.15.0}/cctx/diagnostician/aggregate.py +0 -0
  27. {cctx_cli-1.13.0 → cctx_cli-1.15.0}/cctx/diagnostician/inflection.py +0 -0
  28. {cctx_cli-1.13.0 → cctx_cli-1.15.0}/cctx/diagnostician/patterns/__init__.py +0 -0
  29. {cctx_cli-1.13.0 → cctx_cli-1.15.0}/cctx/diagnostician/patterns/cache_hygiene.py +0 -0
  30. {cctx_cli-1.13.0 → cctx_cli-1.15.0}/cctx/diagnostician/patterns/fan_out.py +0 -0
  31. {cctx_cli-1.13.0 → cctx_cli-1.15.0}/cctx/diagnostician/patterns/project_specific.py +0 -0
  32. {cctx_cli-1.13.0 → cctx_cli-1.15.0}/cctx/diagnostician/patterns/retry_loop.py +0 -0
  33. {cctx_cli-1.13.0 → cctx_cli-1.15.0}/cctx/diagnostician/patterns/scope_creep.py +0 -0
  34. {cctx_cli-1.13.0 → cctx_cli-1.15.0}/cctx/diagnostician/patterns/tool_thrash.py +0 -0
  35. {cctx_cli-1.13.0 → cctx_cli-1.15.0}/cctx/discovery.py +0 -0
  36. {cctx_cli-1.13.0 → cctx_cli-1.15.0}/cctx/exporters/__init__.py +0 -0
  37. {cctx_cli-1.13.0 → cctx_cli-1.15.0}/cctx/exporters/csv.py +0 -0
  38. {cctx_cli-1.13.0 → cctx_cli-1.15.0}/cctx/exporters/json.py +0 -0
  39. {cctx_cli-1.13.0 → cctx_cli-1.15.0}/cctx/exporters/jsonl.py +0 -0
  40. {cctx_cli-1.13.0 → cctx_cli-1.15.0}/cctx/harvest.py +0 -0
  41. {cctx_cli-1.13.0 → cctx_cli-1.15.0}/cctx/hook_installer.py +0 -0
  42. {cctx_cli-1.13.0 → cctx_cli-1.15.0}/cctx/parsers/__init__.py +0 -0
  43. {cctx_cli-1.13.0 → cctx_cli-1.15.0}/cctx/parsers/claude_code.py +0 -0
  44. {cctx_cli-1.13.0 → cctx_cli-1.15.0}/cctx/parsers/otel.py +0 -0
  45. {cctx_cli-1.13.0 → cctx_cli-1.15.0}/cctx/pricing.py +0 -0
  46. {cctx_cli-1.13.0 → cctx_cli-1.15.0}/cctx/recommender/__init__.py +0 -0
  47. {cctx_cli-1.13.0 → cctx_cli-1.15.0}/cctx/recommender/evidence.py +0 -0
  48. {cctx_cli-1.13.0 → cctx_cli-1.15.0}/cctx/renderers/__init__.py +0 -0
  49. {cctx_cli-1.13.0 → cctx_cli-1.15.0}/cctx/renderers/github.py +0 -0
  50. {cctx_cli-1.13.0 → cctx_cli-1.15.0}/cctx/renderers/report.py +0 -0
  51. {cctx_cli-1.13.0 → cctx_cli-1.15.0}/cctx/renderers/templates/autopsy.html.j2 +0 -0
  52. {cctx_cli-1.13.0 → cctx_cli-1.15.0}/cctx/renderers/trace_tui.py +0 -0
  53. {cctx_cli-1.13.0 → cctx_cli-1.15.0}/cctx/tokenizer.py +0 -0
  54. {cctx_cli-1.13.0 → cctx_cli-1.15.0}/cctx/watcher.py +0 -0
  55. {cctx_cli-1.13.0 → cctx_cli-1.15.0}/cctx-project-brief.md +0 -0
  56. {cctx_cli-1.13.0 → cctx_cli-1.15.0}/demo.gif +0 -0
  57. {cctx_cli-1.13.0 → cctx_cli-1.15.0}/demo.tape +0 -0
  58. {cctx_cli-1.13.0 → cctx_cli-1.15.0}/docs/health-reviews/2026-05-15-deep-review-summary.md +0 -0
  59. {cctx_cli-1.13.0 → cctx_cli-1.15.0}/docs/health-reviews/2026-05-15-health-review.md +0 -0
  60. {cctx_cli-1.13.0 → cctx_cli-1.15.0}/docs/product-reviews/2026-05-15-product-review.md +0 -0
  61. {cctx_cli-1.13.0 → cctx_cli-1.15.0}/docs/product-reviews/2026-06-09-product-review.md +0 -0
  62. {cctx_cli-1.13.0 → cctx_cli-1.15.0}/docs/quickstart-otel.md +0 -0
  63. {cctx_cli-1.13.0 → cctx_cli-1.15.0}/docs/superpowers/plans/2026-05-12-claude-code-parser.md +0 -0
  64. {cctx_cli-1.13.0 → cctx_cli-1.15.0}/docs/superpowers/plans/2026-05-14-autopsy-v0.md +0 -0
  65. {cctx_cli-1.13.0 → cctx_cli-1.15.0}/docs/superpowers/plans/2026-05-16-readme-pypi-release.md +0 -0
  66. {cctx_cli-1.13.0 → cctx_cli-1.15.0}/docs/superpowers/plans/2026-05-17-harvest-check-depth.md +0 -0
  67. {cctx_cli-1.13.0 → cctx_cli-1.15.0}/docs/superpowers/plans/2026-05-17-project-pattern-detection.md +0 -0
  68. {cctx_cli-1.13.0 → cctx_cli-1.15.0}/docs/superpowers/plans/2026-05-19-claude-agents-live-integration.md +0 -0
  69. {cctx_cli-1.13.0 → cctx_cli-1.15.0}/docs/superpowers/plans/2026-06-19-otel-parser.md +0 -0
  70. {cctx_cli-1.13.0 → cctx_cli-1.15.0}/docs/superpowers/specs/2026-05-12-claude-code-parser-design.md +0 -0
  71. {cctx_cli-1.13.0 → cctx_cli-1.15.0}/docs/superpowers/specs/2026-05-14-autopsy-design.md +0 -0
  72. {cctx_cli-1.13.0 → cctx_cli-1.15.0}/docs/superpowers/specs/2026-05-14-harvest-design.md +0 -0
  73. {cctx_cli-1.13.0 → cctx_cli-1.15.0}/docs/superpowers/specs/2026-05-14-trace-tui-design.md +0 -0
  74. {cctx_cli-1.13.0 → cctx_cli-1.15.0}/docs/superpowers/specs/2026-05-16-readme-pypi-release-design.md +0 -0
  75. {cctx_cli-1.13.0 → cctx_cli-1.15.0}/docs/superpowers/specs/2026-05-17-harvest-check-depth-design.md +0 -0
  76. {cctx_cli-1.13.0 → cctx_cli-1.15.0}/docs/superpowers/specs/2026-05-17-project-pattern-detection-design.md +0 -0
  77. {cctx_cli-1.13.0 → cctx_cli-1.15.0}/docs/superpowers/specs/2026-05-19-claude-agents-live-integration-design.md +0 -0
  78. {cctx_cli-1.13.0 → cctx_cli-1.15.0}/docs/superpowers/specs/2026-06-09-cross-agent-emit-design.md +0 -0
  79. {cctx_cli-1.13.0 → cctx_cli-1.15.0}/docs/superpowers/specs/2026-06-19-otel-parser-design.md +0 -0
  80. {cctx_cli-1.13.0 → cctx_cli-1.15.0}/tests/__init__.py +0 -0
  81. {cctx_cli-1.13.0 → cctx_cli-1.15.0}/tests/conftest.py +0 -0
  82. {cctx_cli-1.13.0 → cctx_cli-1.15.0}/tests/diagnostician/__init__.py +0 -0
  83. {cctx_cli-1.13.0 → cctx_cli-1.15.0}/tests/diagnostician/conftest.py +0 -0
  84. {cctx_cli-1.13.0 → cctx_cli-1.15.0}/tests/diagnostician/test_dead_end.py +0 -0
  85. {cctx_cli-1.13.0 → cctx_cli-1.15.0}/tests/diagnostician/test_inflection.py +0 -0
  86. {cctx_cli-1.13.0 → cctx_cli-1.15.0}/tests/diagnostician/test_orchestrator.py +0 -0
  87. {cctx_cli-1.13.0 → cctx_cli-1.15.0}/tests/diagnostician/test_project_specific.py +0 -0
  88. {cctx_cli-1.13.0 → cctx_cli-1.15.0}/tests/diagnostician/test_retry_loop.py +0 -0
  89. {cctx_cli-1.13.0 → cctx_cli-1.15.0}/tests/diagnostician/test_scope_creep.py +0 -0
  90. {cctx_cli-1.13.0 → cctx_cli-1.15.0}/tests/diagnostician/test_stale_context.py +0 -0
  91. {cctx_cli-1.13.0 → cctx_cli-1.15.0}/tests/diagnostician/test_tool_thrash.py +0 -0
  92. {cctx_cli-1.13.0 → cctx_cli-1.15.0}/tests/exporters/__init__.py +0 -0
  93. {cctx_cli-1.13.0 → cctx_cli-1.15.0}/tests/exporters/test_csv.py +0 -0
  94. {cctx_cli-1.13.0 → cctx_cli-1.15.0}/tests/exporters/test_jsonl.py +0 -0
  95. {cctx_cli-1.13.0 → cctx_cli-1.15.0}/tests/fixtures/claude_code/README.md +0 -0
  96. {cctx_cli-1.13.0 → cctx_cli-1.15.0}/tests/fixtures/claude_code/short-clean/short-clean.jsonl +0 -0
  97. {cctx_cli-1.13.0 → cctx_cli-1.15.0}/tests/fixtures/claude_code/with-attachments/with-attachments/subagents/agent-a0b4c2cf1dde0ca56.meta.json +0 -0
  98. {cctx_cli-1.13.0 → cctx_cli-1.15.0}/tests/fixtures/claude_code/with-attachments/with-attachments/subagents/agent-a116ae34b1b09c332.meta.json +0 -0
  99. {cctx_cli-1.13.0 → cctx_cli-1.15.0}/tests/fixtures/claude_code/with-attachments/with-attachments/subagents/agent-a1c4c417b35658c9e.meta.json +0 -0
  100. {cctx_cli-1.13.0 → cctx_cli-1.15.0}/tests/fixtures/claude_code/with-attachments/with-attachments/subagents/agent-a1e41a901de38f1b5.meta.json +0 -0
  101. {cctx_cli-1.13.0 → cctx_cli-1.15.0}/tests/fixtures/claude_code/with-attachments/with-attachments/subagents/agent-a338f8d0c74612a24.meta.json +0 -0
  102. {cctx_cli-1.13.0 → cctx_cli-1.15.0}/tests/fixtures/claude_code/with-attachments/with-attachments/subagents/agent-a34f6f3c0e7094186.meta.json +0 -0
  103. {cctx_cli-1.13.0 → cctx_cli-1.15.0}/tests/fixtures/claude_code/with-attachments/with-attachments/subagents/agent-a5a5a0cff4d13308b.meta.json +0 -0
  104. {cctx_cli-1.13.0 → cctx_cli-1.15.0}/tests/fixtures/claude_code/with-attachments/with-attachments/subagents/agent-a6b0a3da6a0484db5.meta.json +0 -0
  105. {cctx_cli-1.13.0 → cctx_cli-1.15.0}/tests/fixtures/claude_code/with-attachments/with-attachments/subagents/agent-a7f73f1790b02cde5.meta.json +0 -0
  106. {cctx_cli-1.13.0 → cctx_cli-1.15.0}/tests/fixtures/claude_code/with-attachments/with-attachments/subagents/agent-a7f7c17c38a9d8788.meta.json +0 -0
  107. {cctx_cli-1.13.0 → cctx_cli-1.15.0}/tests/fixtures/claude_code/with-attachments/with-attachments/subagents/agent-a853259e2cd7bbe8a.meta.json +0 -0
  108. {cctx_cli-1.13.0 → cctx_cli-1.15.0}/tests/fixtures/claude_code/with-attachments/with-attachments/subagents/agent-a8d9aedb0d0c6e12d.meta.json +0 -0
  109. {cctx_cli-1.13.0 → cctx_cli-1.15.0}/tests/fixtures/claude_code/with-attachments/with-attachments/subagents/agent-aa778bc1d59e4a441.meta.json +0 -0
  110. {cctx_cli-1.13.0 → cctx_cli-1.15.0}/tests/fixtures/claude_code/with-attachments/with-attachments/subagents/agent-aba869dedee4a12ba.meta.json +0 -0
  111. {cctx_cli-1.13.0 → cctx_cli-1.15.0}/tests/fixtures/claude_code/with-attachments/with-attachments/subagents/agent-ada2746d9774b94db.meta.json +0 -0
  112. {cctx_cli-1.13.0 → cctx_cli-1.15.0}/tests/fixtures/claude_code/with-attachments/with-attachments/subagents/agent-aea0132068c64d2dd.meta.json +0 -0
  113. {cctx_cli-1.13.0 → cctx_cli-1.15.0}/tests/fixtures/claude_code/with-attachments/with-attachments/subagents/agent-aea215eff50874d5f.meta.json +0 -0
  114. {cctx_cli-1.13.0 → cctx_cli-1.15.0}/tests/fixtures/claude_code/with-attachments/with-attachments/subagents/agent-afee21f2b3852a4a0.meta.json +0 -0
  115. {cctx_cli-1.13.0 → cctx_cli-1.15.0}/tests/fixtures/claude_code/with-attachments/with-attachments.jsonl +0 -0
  116. {cctx_cli-1.13.0 → cctx_cli-1.15.0}/tests/fixtures/claude_code/with-compaction/with-compaction/subagents/agent-a051d9c9a6b2f5cc3.jsonl +0 -0
  117. {cctx_cli-1.13.0 → cctx_cli-1.15.0}/tests/fixtures/claude_code/with-compaction/with-compaction/subagents/agent-a051d9c9a6b2f5cc3.meta.json +0 -0
  118. {cctx_cli-1.13.0 → cctx_cli-1.15.0}/tests/fixtures/claude_code/with-compaction/with-compaction/subagents/agent-a171f16f4e65cfe75.jsonl +0 -0
  119. {cctx_cli-1.13.0 → cctx_cli-1.15.0}/tests/fixtures/claude_code/with-compaction/with-compaction/subagents/agent-a171f16f4e65cfe75.meta.json +0 -0
  120. {cctx_cli-1.13.0 → cctx_cli-1.15.0}/tests/fixtures/claude_code/with-compaction/with-compaction/subagents/agent-a1b77fea2c0a2269b.jsonl +0 -0
  121. {cctx_cli-1.13.0 → cctx_cli-1.15.0}/tests/fixtures/claude_code/with-compaction/with-compaction/subagents/agent-a1b77fea2c0a2269b.meta.json +0 -0
  122. {cctx_cli-1.13.0 → cctx_cli-1.15.0}/tests/fixtures/claude_code/with-compaction/with-compaction/subagents/agent-a20da4c01a54acca8.jsonl +0 -0
  123. {cctx_cli-1.13.0 → cctx_cli-1.15.0}/tests/fixtures/claude_code/with-compaction/with-compaction/subagents/agent-a20da4c01a54acca8.meta.json +0 -0
  124. {cctx_cli-1.13.0 → cctx_cli-1.15.0}/tests/fixtures/claude_code/with-compaction/with-compaction/subagents/agent-a3c82739b1383fb14.jsonl +0 -0
  125. {cctx_cli-1.13.0 → cctx_cli-1.15.0}/tests/fixtures/claude_code/with-compaction/with-compaction/subagents/agent-a3c82739b1383fb14.meta.json +0 -0
  126. {cctx_cli-1.13.0 → cctx_cli-1.15.0}/tests/fixtures/claude_code/with-compaction/with-compaction/subagents/agent-a49e8539611c5fe12.jsonl +0 -0
  127. {cctx_cli-1.13.0 → cctx_cli-1.15.0}/tests/fixtures/claude_code/with-compaction/with-compaction/subagents/agent-a49e8539611c5fe12.meta.json +0 -0
  128. {cctx_cli-1.13.0 → cctx_cli-1.15.0}/tests/fixtures/claude_code/with-compaction/with-compaction/subagents/agent-a7bb58f3fff2b3e8d.jsonl +0 -0
  129. {cctx_cli-1.13.0 → cctx_cli-1.15.0}/tests/fixtures/claude_code/with-compaction/with-compaction/subagents/agent-a7bb58f3fff2b3e8d.meta.json +0 -0
  130. {cctx_cli-1.13.0 → cctx_cli-1.15.0}/tests/fixtures/claude_code/with-compaction/with-compaction/subagents/agent-a92b48c0331195aac.jsonl +0 -0
  131. {cctx_cli-1.13.0 → cctx_cli-1.15.0}/tests/fixtures/claude_code/with-compaction/with-compaction/subagents/agent-a92b48c0331195aac.meta.json +0 -0
  132. {cctx_cli-1.13.0 → cctx_cli-1.15.0}/tests/fixtures/claude_code/with-compaction/with-compaction/subagents/agent-ab96c4264099694a9.jsonl +0 -0
  133. {cctx_cli-1.13.0 → cctx_cli-1.15.0}/tests/fixtures/claude_code/with-compaction/with-compaction/subagents/agent-ab96c4264099694a9.meta.json +0 -0
  134. {cctx_cli-1.13.0 → cctx_cli-1.15.0}/tests/fixtures/claude_code/with-compaction/with-compaction/subagents/agent-acb2895c5e34ffec0.jsonl +0 -0
  135. {cctx_cli-1.13.0 → cctx_cli-1.15.0}/tests/fixtures/claude_code/with-compaction/with-compaction/subagents/agent-acb2895c5e34ffec0.meta.json +0 -0
  136. {cctx_cli-1.13.0 → cctx_cli-1.15.0}/tests/fixtures/claude_code/with-compaction/with-compaction/subagents/agent-adb2302769938fb3f.jsonl +0 -0
  137. {cctx_cli-1.13.0 → cctx_cli-1.15.0}/tests/fixtures/claude_code/with-compaction/with-compaction/subagents/agent-adb2302769938fb3f.meta.json +0 -0
  138. {cctx_cli-1.13.0 → cctx_cli-1.15.0}/tests/fixtures/claude_code/with-compaction/with-compaction/subagents/agent-ae585eca15cb93b9c.jsonl +0 -0
  139. {cctx_cli-1.13.0 → cctx_cli-1.15.0}/tests/fixtures/claude_code/with-compaction/with-compaction/subagents/agent-ae585eca15cb93b9c.meta.json +0 -0
  140. {cctx_cli-1.13.0 → cctx_cli-1.15.0}/tests/fixtures/claude_code/with-compaction/with-compaction/subagents/agent-aec9c917feb903d67.jsonl +0 -0
  141. {cctx_cli-1.13.0 → cctx_cli-1.15.0}/tests/fixtures/claude_code/with-compaction/with-compaction/subagents/agent-aec9c917feb903d67.meta.json +0 -0
  142. {cctx_cli-1.13.0 → cctx_cli-1.15.0}/tests/fixtures/claude_code/with-compaction/with-compaction.jsonl +0 -0
  143. {cctx_cli-1.13.0 → cctx_cli-1.15.0}/tests/fixtures/claude_code/with-subagents/with-subagents/subagents/agent-a1a3a21aeb76bb0a9.jsonl +0 -0
  144. {cctx_cli-1.13.0 → cctx_cli-1.15.0}/tests/fixtures/claude_code/with-subagents/with-subagents/subagents/agent-a1a3a21aeb76bb0a9.meta.json +0 -0
  145. {cctx_cli-1.13.0 → cctx_cli-1.15.0}/tests/fixtures/claude_code/with-subagents/with-subagents/subagents/agent-aaa1d6ecc05a78442.jsonl +0 -0
  146. {cctx_cli-1.13.0 → cctx_cli-1.15.0}/tests/fixtures/claude_code/with-subagents/with-subagents/subagents/agent-aaa1d6ecc05a78442.meta.json +0 -0
  147. {cctx_cli-1.13.0 → cctx_cli-1.15.0}/tests/fixtures/claude_code/with-subagents/with-subagents/subagents/agent-af3c545ccd30036d2.jsonl +0 -0
  148. {cctx_cli-1.13.0 → cctx_cli-1.15.0}/tests/fixtures/claude_code/with-subagents/with-subagents/subagents/agent-af3c545ccd30036d2.meta.json +0 -0
  149. {cctx_cli-1.13.0 → cctx_cli-1.15.0}/tests/fixtures/claude_code/with-subagents/with-subagents/tool-results/btwp2bzro.txt +0 -0
  150. {cctx_cli-1.13.0 → cctx_cli-1.15.0}/tests/fixtures/claude_code/with-subagents/with-subagents/tool-results/byqjbgy4b.txt +0 -0
  151. {cctx_cli-1.13.0 → cctx_cli-1.15.0}/tests/fixtures/claude_code/with-subagents/with-subagents.jsonl +0 -0
  152. {cctx_cli-1.13.0 → cctx_cli-1.15.0}/tests/fixtures/claude_code/with-tool-results/with-tool-results/tool-results/bosbkda0h.txt +0 -0
  153. {cctx_cli-1.13.0 → cctx_cli-1.15.0}/tests/fixtures/claude_code/with-tool-results/with-tool-results.jsonl +0 -0
  154. {cctx_cli-1.13.0 → cctx_cli-1.15.0}/tests/fixtures/otel_fanout.jsonl +0 -0
  155. {cctx_cli-1.13.0 → cctx_cli-1.15.0}/tests/fixtures/otel_handoff.jsonl +0 -0
  156. {cctx_cli-1.13.0 → cctx_cli-1.15.0}/tests/fixtures/scrub.py +0 -0
  157. {cctx_cli-1.13.0 → cctx_cli-1.15.0}/tests/fixtures/synthetic/bookkeeping_only.jsonl +0 -0
  158. {cctx_cli-1.13.0 → cctx_cli-1.15.0}/tests/fixtures/synthetic/malformed_middle.jsonl +0 -0
  159. {cctx_cli-1.13.0 → cctx_cli-1.15.0}/tests/fixtures/synthetic/truncated_final_line.jsonl +0 -0
  160. {cctx_cli-1.13.0 → cctx_cli-1.15.0}/tests/fixtures/synthetic/unknown_attachment_shape.jsonl +0 -0
  161. {cctx_cli-1.13.0 → cctx_cli-1.15.0}/tests/fixtures/synthetic/unknown_type.jsonl +0 -0
  162. {cctx_cli-1.13.0 → cctx_cli-1.15.0}/tests/parsers/__init__.py +0 -0
  163. {cctx_cli-1.13.0 → cctx_cli-1.15.0}/tests/parsers/test_claude_code.py +0 -0
  164. {cctx_cli-1.13.0 → cctx_cli-1.15.0}/tests/parsers/test_claude_code_integration.py +0 -0
  165. {cctx_cli-1.13.0 → cctx_cli-1.15.0}/tests/recommender/__init__.py +0 -0
  166. {cctx_cli-1.13.0 → cctx_cli-1.15.0}/tests/recommender/test_claude_md.py +0 -0
  167. {cctx_cli-1.13.0 → cctx_cli-1.15.0}/tests/recommender/test_evidence.py +0 -0
  168. {cctx_cli-1.13.0 → cctx_cli-1.15.0}/tests/renderers/__init__.py +0 -0
  169. {cctx_cli-1.13.0 → cctx_cli-1.15.0}/tests/renderers/test_report.py +0 -0
  170. {cctx_cli-1.13.0 → cctx_cli-1.15.0}/tests/renderers/test_terminal_renderer_full.py +0 -0
  171. {cctx_cli-1.13.0 → cctx_cli-1.15.0}/tests/test_agents.py +0 -0
  172. {cctx_cli-1.13.0 → cctx_cli-1.15.0}/tests/test_aggregate.py +0 -0
  173. {cctx_cli-1.13.0 → cctx_cli-1.15.0}/tests/test_cache_hygiene_classifier.py +0 -0
  174. {cctx_cli-1.13.0 → cctx_cli-1.15.0}/tests/test_cli.py +0 -0
  175. {cctx_cli-1.13.0 → cctx_cli-1.15.0}/tests/test_cli_export.py +0 -0
  176. {cctx_cli-1.13.0 → cctx_cli-1.15.0}/tests/test_diagnostician_subagents.py +0 -0
  177. {cctx_cli-1.13.0 → cctx_cli-1.15.0}/tests/test_discovery.py +0 -0
  178. {cctx_cli-1.13.0 → cctx_cli-1.15.0}/tests/test_efficacy.py +0 -0
  179. {cctx_cli-1.13.0 → cctx_cli-1.15.0}/tests/test_fanout_classifier.py +0 -0
  180. {cctx_cli-1.13.0 → cctx_cli-1.15.0}/tests/test_github_summary.py +0 -0
  181. {cctx_cli-1.13.0 → cctx_cli-1.15.0}/tests/test_harvest.py +0 -0
  182. {cctx_cli-1.13.0 → cctx_cli-1.15.0}/tests/test_harvest_check.py +0 -0
  183. {cctx_cli-1.13.0 → cctx_cli-1.15.0}/tests/test_init.py +0 -0
  184. {cctx_cli-1.13.0 → cctx_cli-1.15.0}/tests/test_models.py +0 -0
  185. {cctx_cli-1.13.0 → cctx_cli-1.15.0}/tests/test_models_project_pattern.py +0 -0
  186. {cctx_cli-1.13.0 → cctx_cli-1.15.0}/tests/test_otel_parser.py +0 -0
  187. {cctx_cli-1.13.0 → cctx_cli-1.15.0}/tests/test_recommender.py +0 -0
  188. {cctx_cli-1.13.0 → cctx_cli-1.15.0}/tests/test_smoke.py +0 -0
  189. {cctx_cli-1.13.0 → cctx_cli-1.15.0}/tests/test_terminal_renderer.py +0 -0
  190. {cctx_cli-1.13.0 → cctx_cli-1.15.0}/tests/test_tokenizer.py +0 -0
  191. {cctx_cli-1.13.0 → cctx_cli-1.15.0}/tests/test_trace_tui.py +0 -0
  192. {cctx_cli-1.13.0 → cctx_cli-1.15.0}/tests/test_watcher.py +0 -0
@@ -2,6 +2,24 @@
2
2
 
3
3
  <!-- version list -->
4
4
 
5
+ ## v1.15.0 (2026-06-20)
6
+
7
+ ### Features
8
+
9
+ - Compaction findings — surface compaction events and re-fetch waste (#93)
10
+ ([#123](https://github.com/jacquardlabs/cctx/pull/123),
11
+ [`cdab064`](https://github.com/jacquardlabs/cctx/commit/cdab0640b720e127549af63c22747555bd832b2c))
12
+
13
+
14
+ ## v1.14.0 (2026-06-20)
15
+
16
+ ### Features
17
+
18
+ - Savings framing + health grade behind --health flag (#101)
19
+ ([#122](https://github.com/jacquardlabs/cctx/pull/122),
20
+ [`ee4bc28`](https://github.com/jacquardlabs/cctx/commit/ee4bc2833d7260275e95666ba45a5952aa174aee))
21
+
22
+
5
23
  ## v1.13.0 (2026-06-20)
6
24
 
7
25
  ### Documentation
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: cctx-cli
3
- Version: 1.13.0
3
+ Version: 1.15.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.15.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
 
@@ -16,6 +16,7 @@ from typing import TYPE_CHECKING
16
16
  from cctx.diagnostician import inflection
17
17
  from cctx.diagnostician.patterns import (
18
18
  cache_hygiene,
19
+ compaction,
19
20
  dead_end,
20
21
  fan_out,
21
22
  retry_loop,
@@ -154,6 +155,7 @@ def run(trace: SessionTrace) -> Diagnosis:
154
155
  *dead_end.classify(trace),
155
156
  *fan_out.classify(trace),
156
157
  *cache_hygiene.classify(trace),
158
+ *compaction.classify(trace),
157
159
  ]
158
160
  findings.sort(key=lambda f: f.first_turn)
159
161
 
@@ -0,0 +1,114 @@
1
+ """Compaction-event classifier.
2
+
3
+ Detects context-window compaction events and surfaces them as first-class
4
+ findings. Also attributes re-fetch waste: files read before compaction that
5
+ are read again after (token cost of the re-read attributed to the compaction).
6
+
7
+ Exported helpers:
8
+ is_compaction_turn(turn) — canonical compaction predicate used by
9
+ stale_context.py and dead_end.py (replaces their local implementations).
10
+ """
11
+ from __future__ import annotations
12
+
13
+ from typing import TYPE_CHECKING
14
+
15
+ from cctx.models import Confidence, Finding, FindingKind, Severity
16
+
17
+ if TYPE_CHECKING:
18
+ from cctx.models import SessionTrace, Turn
19
+
20
+
21
+ def is_compaction_turn(turn: Turn) -> bool:
22
+ """True if this turn represents a context-window compaction event."""
23
+ if turn.role == "system" and "compact" in turn.text.lower():
24
+ return True
25
+ return turn.text.startswith("<context_window")
26
+
27
+
28
+ def _classify_impl(trace: SessionTrace) -> list[Finding]:
29
+ compaction_turns = [t for t in trace.turns if is_compaction_turn(t)]
30
+ if not compaction_turns:
31
+ return []
32
+
33
+ first_compaction_turn = compaction_turns[0].turn_number
34
+
35
+ # Build map of files read before first compaction: key → token_count
36
+ pre_reads: dict[str, int] = {}
37
+ for turn in trace.turns:
38
+ if turn.turn_number >= first_compaction_turn:
39
+ break
40
+ for tu in turn.tool_uses:
41
+ if tu.tool_name == "Read":
42
+ fp = tu.tool_input.get("file_path", "")
43
+ if not fp:
44
+ continue
45
+ # Find matching tool result to get token count
46
+ for tr in turn.tool_results:
47
+ if tr.tool_use_id == tu.tool_use_id:
48
+ toks = (
49
+ tr.token_count
50
+ if tr.token_count > 0
51
+ else len(tr.content.split()) * 4 // 3
52
+ )
53
+ pre_reads[f"Read:{fp}"] = toks
54
+
55
+ # Detect re-fetches after compaction (first occurrence only per file)
56
+ re_fetches: list[dict] = []
57
+ for turn in trace.turns:
58
+ if turn.turn_number <= first_compaction_turn:
59
+ continue
60
+ for tu in turn.tool_uses:
61
+ if tu.tool_name == "Read":
62
+ fp = tu.tool_input.get("file_path", "")
63
+ key = f"Read:{fp}"
64
+ if key in pre_reads:
65
+ re_fetches.append({
66
+ "tool_name": tu.tool_name,
67
+ "path": fp,
68
+ "turn": turn.turn_number,
69
+ "tokens": pre_reads[key],
70
+ })
71
+ del pre_reads[key] # only flag first re-fetch per file
72
+
73
+ total_refetch_tokens = sum(r["tokens"] for r in re_fetches)
74
+ n_compactions = len(compaction_turns)
75
+ compaction_turn_numbers = [t.turn_number for t in compaction_turns]
76
+
77
+ severity = Severity.HIGH if re_fetches else Severity.LOW
78
+ confidence = Confidence.HIGH
79
+
80
+ parts = [
81
+ f"{n_compactions} compaction event{'s' if n_compactions > 1 else ''} "
82
+ f"(turn{'s' if n_compactions > 1 else ''} "
83
+ f"{', '.join(str(n) for n in compaction_turn_numbers)})"
84
+ ]
85
+ if re_fetches:
86
+ n_files = len(re_fetches)
87
+ parts.append(
88
+ f"{n_files} file{'s' if n_files > 1 else ''} re-fetched after compaction "
89
+ f"(~{total_refetch_tokens:,} tokens)"
90
+ )
91
+ summary = "; ".join(parts)
92
+
93
+ return [Finding(
94
+ kind=FindingKind.COMPACTION,
95
+ severity=severity,
96
+ confidence=confidence,
97
+ first_turn=compaction_turn_numbers[0],
98
+ last_turn=compaction_turn_numbers[-1],
99
+ evidence={
100
+ "n_compactions": n_compactions,
101
+ "compaction_turns": compaction_turn_numbers,
102
+ "re_fetches": re_fetches,
103
+ "total_refetch_tokens": total_refetch_tokens,
104
+ },
105
+ cost_usd=None,
106
+ summary=summary,
107
+ )]
108
+
109
+
110
+ def classify(trace: SessionTrace) -> list[Finding]:
111
+ try:
112
+ return _classify_impl(trace)
113
+ except Exception:
114
+ return []
@@ -20,6 +20,7 @@ from __future__ import annotations
20
20
  import json
21
21
  from typing import TYPE_CHECKING
22
22
 
23
+ from cctx.diagnostician.patterns.compaction import is_compaction_turn
23
24
  from cctx.models import Confidence, Finding, FindingKind, Severity
24
25
 
25
26
  if TYPE_CHECKING:
@@ -65,7 +66,7 @@ def _classify_impl(trace: SessionTrace) -> list[Finding]:
65
66
 
66
67
  for turn in trace.turns:
67
68
  # Compaction resets state
68
- if turn.text.startswith("<context_window"):
69
+ if is_compaction_turn(turn):
69
70
  run_tool = run_key = None
70
71
  run_count = 0
71
72
  continue
@@ -12,6 +12,7 @@ from __future__ import annotations
12
12
 
13
13
  from typing import TYPE_CHECKING
14
14
 
15
+ from cctx.diagnostician.patterns.compaction import is_compaction_turn
15
16
  from cctx.models import Confidence, Finding, FindingKind, Severity
16
17
 
17
18
  if TYPE_CHECKING:
@@ -33,8 +34,9 @@ def _make_3grams(text: str) -> set[tuple[str, ...]]:
33
34
  return {tuple(words[i : i + 3]) for i in range(len(words) - 2)}
34
35
 
35
36
 
37
+ # Backwards-compat alias — external callers that import _is_compaction keep working.
36
38
  def _is_compaction(turn: Turn) -> bool:
37
- return turn.role == "system" and "compact" in turn.text.lower()
39
+ return is_compaction_turn(turn)
38
40
 
39
41
 
40
42
  def _classify_impl(trace: SessionTrace) -> list[Finding]:
@@ -60,7 +62,7 @@ def _classify_impl(trace: SessionTrace) -> list[Finding]:
60
62
 
61
63
  # Find the turn number of any compaction events
62
64
  compaction_turns: set[int] = {
63
- t.turn_number for t in trace.turns if _is_compaction(t)
65
+ t.turn_number for t in trace.turns if is_compaction_turn(t)
64
66
  }
65
67
 
66
68
  last_turn_number = max((t.turn_number for t in trace.turns), default=0)
@@ -175,6 +175,7 @@ class FindingKind(str, Enum):
175
175
  FANOUT_WASTE = "fanout_waste"
176
176
  PROJECT_PATTERN = "project_pattern"
177
177
  CACHE_HYGIENE = "cache_hygiene"
178
+ COMPACTION = "compaction"
178
179
 
179
180
 
180
181
  KIND_LABEL: dict[FindingKind, str] = {
@@ -186,6 +187,7 @@ KIND_LABEL: dict[FindingKind, str] = {
186
187
  FindingKind.FANOUT_WASTE: "FANOUT WASTE",
187
188
  FindingKind.PROJECT_PATTERN: "PROJECT PATTERN",
188
189
  FindingKind.CACHE_HYGIENE: "CACHE HYGIENE",
190
+ FindingKind.COMPACTION: "COMPACTION",
189
191
  }
190
192
 
191
193
  # Maps FindingKind to the exact ## heading emitted by its recommender patch
@@ -200,6 +202,7 @@ MANAGED_HEADINGS: dict[FindingKind, str] = {
200
202
  FindingKind.DEAD_END: "## Exploration discipline",
201
203
  FindingKind.FANOUT_WASTE: "## Fan-out discipline",
202
204
  FindingKind.CACHE_HYGIENE: "## Cache hygiene",
205
+ FindingKind.COMPACTION: "## Compaction hygiene",
203
206
  }
204
207
 
205
208
  # Project-specific patterns use a heading that embeds tool+key, so the managed
@@ -76,6 +76,15 @@ _CACHE_HYGIENE_DIFF = """\
76
76
  +with a stable, cacheable preamble. A 10× cost difference separates a warm
77
77
  +cache hit from a cold input read."""
78
78
 
79
+ _COMPACTION_DIFF = """\
80
+ +## Compaction hygiene
81
+ +
82
+ +If context-window compaction occurs mid-session, assume all previously read files
83
+ +are gone from context. Re-read only files you actively need for the next step —
84
+ +don't reflexively reload everything. Better: compact earlier by summarizing large
85
+ +tool outputs once you've extracted what you need, so compaction doesn't erase
86
+ +work-in-progress state."""
87
+
79
88
  _TEMPLATES: dict[FindingKind, tuple[str, str, str]] = {
80
89
  # kind → (description, diff_body, target_file)
81
90
  FindingKind.RETRY_LOOP: ("Add retry discipline rule", _RETRY_LOOP_DIFF, "CLAUDE.md"),
@@ -85,6 +94,7 @@ _TEMPLATES: dict[FindingKind, tuple[str, str, str]] = {
85
94
  FindingKind.DEAD_END: ("Add exploration discipline rule", _DEAD_END_DIFF, "CLAUDE.md"),
86
95
  FindingKind.FANOUT_WASTE: ("Add fan-out discipline rule", _FANOUT_WASTE_DIFF, "CLAUDE.md"),
87
96
  FindingKind.CACHE_HYGIENE: ("Add cache hygiene rule", _CACHE_HYGIENE_DIFF, "CLAUDE.md"),
97
+ FindingKind.COMPACTION: ("Add compaction hygiene rule", _COMPACTION_DIFF, "CLAUDE.md"),
88
98
  }
89
99
 
90
100
 
@@ -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.15.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"