cctx-cli 1.9.0__tar.gz → 1.11.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (180) hide show
  1. {cctx_cli-1.9.0 → cctx_cli-1.11.0}/CHANGELOG.md +24 -0
  2. {cctx_cli-1.9.0 → cctx_cli-1.11.0}/PKG-INFO +1 -1
  3. {cctx_cli-1.9.0 → cctx_cli-1.11.0}/cctx/__init__.py +1 -1
  4. {cctx_cli-1.9.0 → cctx_cli-1.11.0}/cctx/cli.py +79 -6
  5. {cctx_cli-1.9.0 → cctx_cli-1.11.0}/cctx/exporters/jsonl.py +45 -1
  6. cctx_cli-1.11.0/cctx/hook_installer.py +101 -0
  7. {cctx_cli-1.9.0 → cctx_cli-1.11.0}/pyproject.toml +1 -1
  8. {cctx_cli-1.9.0 → cctx_cli-1.11.0}/tests/test_cli.py +28 -3
  9. cctx_cli-1.11.0/tests/test_init.py +323 -0
  10. {cctx_cli-1.9.0 → cctx_cli-1.11.0}/.github/workflows/ci.yml +0 -0
  11. {cctx_cli-1.9.0 → cctx_cli-1.11.0}/.github/workflows/publish.yml +0 -0
  12. {cctx_cli-1.9.0 → cctx_cli-1.11.0}/.github/workflows/release.yml +0 -0
  13. {cctx_cli-1.9.0 → cctx_cli-1.11.0}/.gitignore +0 -0
  14. {cctx_cli-1.9.0 → cctx_cli-1.11.0}/CLAUDE.md +0 -0
  15. {cctx_cli-1.9.0 → cctx_cli-1.11.0}/DESIGN.md +0 -0
  16. {cctx_cli-1.9.0 → cctx_cli-1.11.0}/PRODUCT.md +0 -0
  17. {cctx_cli-1.9.0 → cctx_cli-1.11.0}/README.md +0 -0
  18. {cctx_cli-1.9.0 → cctx_cli-1.11.0}/action.yml +0 -0
  19. {cctx_cli-1.9.0 → cctx_cli-1.11.0}/cctx/agents.py +0 -0
  20. {cctx_cli-1.9.0 → cctx_cli-1.11.0}/cctx/diagnostician/__init__.py +0 -0
  21. {cctx_cli-1.9.0 → cctx_cli-1.11.0}/cctx/diagnostician/aggregate.py +0 -0
  22. {cctx_cli-1.9.0 → cctx_cli-1.11.0}/cctx/diagnostician/inflection.py +0 -0
  23. {cctx_cli-1.9.0 → cctx_cli-1.11.0}/cctx/diagnostician/patterns/__init__.py +0 -0
  24. {cctx_cli-1.9.0 → cctx_cli-1.11.0}/cctx/diagnostician/patterns/dead_end.py +0 -0
  25. {cctx_cli-1.9.0 → cctx_cli-1.11.0}/cctx/diagnostician/patterns/fan_out.py +0 -0
  26. {cctx_cli-1.9.0 → cctx_cli-1.11.0}/cctx/diagnostician/patterns/project_specific.py +0 -0
  27. {cctx_cli-1.9.0 → cctx_cli-1.11.0}/cctx/diagnostician/patterns/retry_loop.py +0 -0
  28. {cctx_cli-1.9.0 → cctx_cli-1.11.0}/cctx/diagnostician/patterns/scope_creep.py +0 -0
  29. {cctx_cli-1.9.0 → cctx_cli-1.11.0}/cctx/diagnostician/patterns/stale_context.py +0 -0
  30. {cctx_cli-1.9.0 → cctx_cli-1.11.0}/cctx/diagnostician/patterns/tool_thrash.py +0 -0
  31. {cctx_cli-1.9.0 → cctx_cli-1.11.0}/cctx/discovery.py +0 -0
  32. {cctx_cli-1.9.0 → cctx_cli-1.11.0}/cctx/exporters/__init__.py +0 -0
  33. {cctx_cli-1.9.0 → cctx_cli-1.11.0}/cctx/exporters/csv.py +0 -0
  34. {cctx_cli-1.9.0 → cctx_cli-1.11.0}/cctx/exporters/json.py +0 -0
  35. {cctx_cli-1.9.0 → cctx_cli-1.11.0}/cctx/harvest.py +0 -0
  36. {cctx_cli-1.9.0 → cctx_cli-1.11.0}/cctx/models.py +0 -0
  37. {cctx_cli-1.9.0 → cctx_cli-1.11.0}/cctx/parsers/__init__.py +0 -0
  38. {cctx_cli-1.9.0 → cctx_cli-1.11.0}/cctx/parsers/claude_code.py +0 -0
  39. {cctx_cli-1.9.0 → cctx_cli-1.11.0}/cctx/pricing.py +0 -0
  40. {cctx_cli-1.9.0 → cctx_cli-1.11.0}/cctx/recommender/__init__.py +0 -0
  41. {cctx_cli-1.9.0 → cctx_cli-1.11.0}/cctx/recommender/claude_md.py +0 -0
  42. {cctx_cli-1.9.0 → cctx_cli-1.11.0}/cctx/recommender/evidence.py +0 -0
  43. {cctx_cli-1.9.0 → cctx_cli-1.11.0}/cctx/renderers/__init__.py +0 -0
  44. {cctx_cli-1.9.0 → cctx_cli-1.11.0}/cctx/renderers/github.py +0 -0
  45. {cctx_cli-1.9.0 → cctx_cli-1.11.0}/cctx/renderers/report.py +0 -0
  46. {cctx_cli-1.9.0 → cctx_cli-1.11.0}/cctx/renderers/templates/autopsy.html.j2 +0 -0
  47. {cctx_cli-1.9.0 → cctx_cli-1.11.0}/cctx/renderers/terminal.py +0 -0
  48. {cctx_cli-1.9.0 → cctx_cli-1.11.0}/cctx/renderers/trace_tui.py +0 -0
  49. {cctx_cli-1.9.0 → cctx_cli-1.11.0}/cctx/tokenizer.py +0 -0
  50. {cctx_cli-1.9.0 → cctx_cli-1.11.0}/cctx/watcher.py +0 -0
  51. {cctx_cli-1.9.0 → cctx_cli-1.11.0}/cctx-project-brief.md +0 -0
  52. {cctx_cli-1.9.0 → cctx_cli-1.11.0}/demo.gif +0 -0
  53. {cctx_cli-1.9.0 → cctx_cli-1.11.0}/demo.tape +0 -0
  54. {cctx_cli-1.9.0 → cctx_cli-1.11.0}/docs/health-reviews/2026-05-15-deep-review-summary.md +0 -0
  55. {cctx_cli-1.9.0 → cctx_cli-1.11.0}/docs/health-reviews/2026-05-15-health-review.md +0 -0
  56. {cctx_cli-1.9.0 → cctx_cli-1.11.0}/docs/product-reviews/2026-05-15-product-review.md +0 -0
  57. {cctx_cli-1.9.0 → cctx_cli-1.11.0}/docs/product-reviews/2026-06-09-product-review.md +0 -0
  58. {cctx_cli-1.9.0 → cctx_cli-1.11.0}/docs/superpowers/plans/2026-05-12-claude-code-parser.md +0 -0
  59. {cctx_cli-1.9.0 → cctx_cli-1.11.0}/docs/superpowers/plans/2026-05-14-autopsy-v0.md +0 -0
  60. {cctx_cli-1.9.0 → cctx_cli-1.11.0}/docs/superpowers/plans/2026-05-16-readme-pypi-release.md +0 -0
  61. {cctx_cli-1.9.0 → cctx_cli-1.11.0}/docs/superpowers/plans/2026-05-17-harvest-check-depth.md +0 -0
  62. {cctx_cli-1.9.0 → cctx_cli-1.11.0}/docs/superpowers/plans/2026-05-17-project-pattern-detection.md +0 -0
  63. {cctx_cli-1.9.0 → cctx_cli-1.11.0}/docs/superpowers/plans/2026-05-19-claude-agents-live-integration.md +0 -0
  64. {cctx_cli-1.9.0 → cctx_cli-1.11.0}/docs/superpowers/specs/2026-05-12-claude-code-parser-design.md +0 -0
  65. {cctx_cli-1.9.0 → cctx_cli-1.11.0}/docs/superpowers/specs/2026-05-14-autopsy-design.md +0 -0
  66. {cctx_cli-1.9.0 → cctx_cli-1.11.0}/docs/superpowers/specs/2026-05-14-harvest-design.md +0 -0
  67. {cctx_cli-1.9.0 → cctx_cli-1.11.0}/docs/superpowers/specs/2026-05-14-trace-tui-design.md +0 -0
  68. {cctx_cli-1.9.0 → cctx_cli-1.11.0}/docs/superpowers/specs/2026-05-16-readme-pypi-release-design.md +0 -0
  69. {cctx_cli-1.9.0 → cctx_cli-1.11.0}/docs/superpowers/specs/2026-05-17-harvest-check-depth-design.md +0 -0
  70. {cctx_cli-1.9.0 → cctx_cli-1.11.0}/docs/superpowers/specs/2026-05-17-project-pattern-detection-design.md +0 -0
  71. {cctx_cli-1.9.0 → cctx_cli-1.11.0}/docs/superpowers/specs/2026-05-19-claude-agents-live-integration-design.md +0 -0
  72. {cctx_cli-1.9.0 → cctx_cli-1.11.0}/docs/superpowers/specs/2026-06-09-cross-agent-emit-design.md +0 -0
  73. {cctx_cli-1.9.0 → cctx_cli-1.11.0}/tests/__init__.py +0 -0
  74. {cctx_cli-1.9.0 → cctx_cli-1.11.0}/tests/conftest.py +0 -0
  75. {cctx_cli-1.9.0 → cctx_cli-1.11.0}/tests/diagnostician/__init__.py +0 -0
  76. {cctx_cli-1.9.0 → cctx_cli-1.11.0}/tests/diagnostician/conftest.py +0 -0
  77. {cctx_cli-1.9.0 → cctx_cli-1.11.0}/tests/diagnostician/test_dead_end.py +0 -0
  78. {cctx_cli-1.9.0 → cctx_cli-1.11.0}/tests/diagnostician/test_inflection.py +0 -0
  79. {cctx_cli-1.9.0 → cctx_cli-1.11.0}/tests/diagnostician/test_orchestrator.py +0 -0
  80. {cctx_cli-1.9.0 → cctx_cli-1.11.0}/tests/diagnostician/test_project_specific.py +0 -0
  81. {cctx_cli-1.9.0 → cctx_cli-1.11.0}/tests/diagnostician/test_retry_loop.py +0 -0
  82. {cctx_cli-1.9.0 → cctx_cli-1.11.0}/tests/diagnostician/test_scope_creep.py +0 -0
  83. {cctx_cli-1.9.0 → cctx_cli-1.11.0}/tests/diagnostician/test_stale_context.py +0 -0
  84. {cctx_cli-1.9.0 → cctx_cli-1.11.0}/tests/diagnostician/test_tool_thrash.py +0 -0
  85. {cctx_cli-1.9.0 → cctx_cli-1.11.0}/tests/exporters/__init__.py +0 -0
  86. {cctx_cli-1.9.0 → cctx_cli-1.11.0}/tests/exporters/test_csv.py +0 -0
  87. {cctx_cli-1.9.0 → cctx_cli-1.11.0}/tests/exporters/test_jsonl.py +0 -0
  88. {cctx_cli-1.9.0 → cctx_cli-1.11.0}/tests/fixtures/claude_code/README.md +0 -0
  89. {cctx_cli-1.9.0 → cctx_cli-1.11.0}/tests/fixtures/claude_code/short-clean/short-clean.jsonl +0 -0
  90. {cctx_cli-1.9.0 → cctx_cli-1.11.0}/tests/fixtures/claude_code/with-attachments/with-attachments/subagents/agent-a0b4c2cf1dde0ca56.meta.json +0 -0
  91. {cctx_cli-1.9.0 → cctx_cli-1.11.0}/tests/fixtures/claude_code/with-attachments/with-attachments/subagents/agent-a116ae34b1b09c332.meta.json +0 -0
  92. {cctx_cli-1.9.0 → cctx_cli-1.11.0}/tests/fixtures/claude_code/with-attachments/with-attachments/subagents/agent-a1c4c417b35658c9e.meta.json +0 -0
  93. {cctx_cli-1.9.0 → cctx_cli-1.11.0}/tests/fixtures/claude_code/with-attachments/with-attachments/subagents/agent-a1e41a901de38f1b5.meta.json +0 -0
  94. {cctx_cli-1.9.0 → cctx_cli-1.11.0}/tests/fixtures/claude_code/with-attachments/with-attachments/subagents/agent-a338f8d0c74612a24.meta.json +0 -0
  95. {cctx_cli-1.9.0 → cctx_cli-1.11.0}/tests/fixtures/claude_code/with-attachments/with-attachments/subagents/agent-a34f6f3c0e7094186.meta.json +0 -0
  96. {cctx_cli-1.9.0 → cctx_cli-1.11.0}/tests/fixtures/claude_code/with-attachments/with-attachments/subagents/agent-a5a5a0cff4d13308b.meta.json +0 -0
  97. {cctx_cli-1.9.0 → cctx_cli-1.11.0}/tests/fixtures/claude_code/with-attachments/with-attachments/subagents/agent-a6b0a3da6a0484db5.meta.json +0 -0
  98. {cctx_cli-1.9.0 → cctx_cli-1.11.0}/tests/fixtures/claude_code/with-attachments/with-attachments/subagents/agent-a7f73f1790b02cde5.meta.json +0 -0
  99. {cctx_cli-1.9.0 → cctx_cli-1.11.0}/tests/fixtures/claude_code/with-attachments/with-attachments/subagents/agent-a7f7c17c38a9d8788.meta.json +0 -0
  100. {cctx_cli-1.9.0 → cctx_cli-1.11.0}/tests/fixtures/claude_code/with-attachments/with-attachments/subagents/agent-a853259e2cd7bbe8a.meta.json +0 -0
  101. {cctx_cli-1.9.0 → cctx_cli-1.11.0}/tests/fixtures/claude_code/with-attachments/with-attachments/subagents/agent-a8d9aedb0d0c6e12d.meta.json +0 -0
  102. {cctx_cli-1.9.0 → cctx_cli-1.11.0}/tests/fixtures/claude_code/with-attachments/with-attachments/subagents/agent-aa778bc1d59e4a441.meta.json +0 -0
  103. {cctx_cli-1.9.0 → cctx_cli-1.11.0}/tests/fixtures/claude_code/with-attachments/with-attachments/subagents/agent-aba869dedee4a12ba.meta.json +0 -0
  104. {cctx_cli-1.9.0 → cctx_cli-1.11.0}/tests/fixtures/claude_code/with-attachments/with-attachments/subagents/agent-ada2746d9774b94db.meta.json +0 -0
  105. {cctx_cli-1.9.0 → cctx_cli-1.11.0}/tests/fixtures/claude_code/with-attachments/with-attachments/subagents/agent-aea0132068c64d2dd.meta.json +0 -0
  106. {cctx_cli-1.9.0 → cctx_cli-1.11.0}/tests/fixtures/claude_code/with-attachments/with-attachments/subagents/agent-aea215eff50874d5f.meta.json +0 -0
  107. {cctx_cli-1.9.0 → cctx_cli-1.11.0}/tests/fixtures/claude_code/with-attachments/with-attachments/subagents/agent-afee21f2b3852a4a0.meta.json +0 -0
  108. {cctx_cli-1.9.0 → cctx_cli-1.11.0}/tests/fixtures/claude_code/with-attachments/with-attachments.jsonl +0 -0
  109. {cctx_cli-1.9.0 → cctx_cli-1.11.0}/tests/fixtures/claude_code/with-compaction/with-compaction/subagents/agent-a051d9c9a6b2f5cc3.jsonl +0 -0
  110. {cctx_cli-1.9.0 → cctx_cli-1.11.0}/tests/fixtures/claude_code/with-compaction/with-compaction/subagents/agent-a051d9c9a6b2f5cc3.meta.json +0 -0
  111. {cctx_cli-1.9.0 → cctx_cli-1.11.0}/tests/fixtures/claude_code/with-compaction/with-compaction/subagents/agent-a171f16f4e65cfe75.jsonl +0 -0
  112. {cctx_cli-1.9.0 → cctx_cli-1.11.0}/tests/fixtures/claude_code/with-compaction/with-compaction/subagents/agent-a171f16f4e65cfe75.meta.json +0 -0
  113. {cctx_cli-1.9.0 → cctx_cli-1.11.0}/tests/fixtures/claude_code/with-compaction/with-compaction/subagents/agent-a1b77fea2c0a2269b.jsonl +0 -0
  114. {cctx_cli-1.9.0 → cctx_cli-1.11.0}/tests/fixtures/claude_code/with-compaction/with-compaction/subagents/agent-a1b77fea2c0a2269b.meta.json +0 -0
  115. {cctx_cli-1.9.0 → cctx_cli-1.11.0}/tests/fixtures/claude_code/with-compaction/with-compaction/subagents/agent-a20da4c01a54acca8.jsonl +0 -0
  116. {cctx_cli-1.9.0 → cctx_cli-1.11.0}/tests/fixtures/claude_code/with-compaction/with-compaction/subagents/agent-a20da4c01a54acca8.meta.json +0 -0
  117. {cctx_cli-1.9.0 → cctx_cli-1.11.0}/tests/fixtures/claude_code/with-compaction/with-compaction/subagents/agent-a3c82739b1383fb14.jsonl +0 -0
  118. {cctx_cli-1.9.0 → cctx_cli-1.11.0}/tests/fixtures/claude_code/with-compaction/with-compaction/subagents/agent-a3c82739b1383fb14.meta.json +0 -0
  119. {cctx_cli-1.9.0 → cctx_cli-1.11.0}/tests/fixtures/claude_code/with-compaction/with-compaction/subagents/agent-a49e8539611c5fe12.jsonl +0 -0
  120. {cctx_cli-1.9.0 → cctx_cli-1.11.0}/tests/fixtures/claude_code/with-compaction/with-compaction/subagents/agent-a49e8539611c5fe12.meta.json +0 -0
  121. {cctx_cli-1.9.0 → cctx_cli-1.11.0}/tests/fixtures/claude_code/with-compaction/with-compaction/subagents/agent-a7bb58f3fff2b3e8d.jsonl +0 -0
  122. {cctx_cli-1.9.0 → cctx_cli-1.11.0}/tests/fixtures/claude_code/with-compaction/with-compaction/subagents/agent-a7bb58f3fff2b3e8d.meta.json +0 -0
  123. {cctx_cli-1.9.0 → cctx_cli-1.11.0}/tests/fixtures/claude_code/with-compaction/with-compaction/subagents/agent-a92b48c0331195aac.jsonl +0 -0
  124. {cctx_cli-1.9.0 → cctx_cli-1.11.0}/tests/fixtures/claude_code/with-compaction/with-compaction/subagents/agent-a92b48c0331195aac.meta.json +0 -0
  125. {cctx_cli-1.9.0 → cctx_cli-1.11.0}/tests/fixtures/claude_code/with-compaction/with-compaction/subagents/agent-ab96c4264099694a9.jsonl +0 -0
  126. {cctx_cli-1.9.0 → cctx_cli-1.11.0}/tests/fixtures/claude_code/with-compaction/with-compaction/subagents/agent-ab96c4264099694a9.meta.json +0 -0
  127. {cctx_cli-1.9.0 → cctx_cli-1.11.0}/tests/fixtures/claude_code/with-compaction/with-compaction/subagents/agent-acb2895c5e34ffec0.jsonl +0 -0
  128. {cctx_cli-1.9.0 → cctx_cli-1.11.0}/tests/fixtures/claude_code/with-compaction/with-compaction/subagents/agent-acb2895c5e34ffec0.meta.json +0 -0
  129. {cctx_cli-1.9.0 → cctx_cli-1.11.0}/tests/fixtures/claude_code/with-compaction/with-compaction/subagents/agent-adb2302769938fb3f.jsonl +0 -0
  130. {cctx_cli-1.9.0 → cctx_cli-1.11.0}/tests/fixtures/claude_code/with-compaction/with-compaction/subagents/agent-adb2302769938fb3f.meta.json +0 -0
  131. {cctx_cli-1.9.0 → cctx_cli-1.11.0}/tests/fixtures/claude_code/with-compaction/with-compaction/subagents/agent-ae585eca15cb93b9c.jsonl +0 -0
  132. {cctx_cli-1.9.0 → cctx_cli-1.11.0}/tests/fixtures/claude_code/with-compaction/with-compaction/subagents/agent-ae585eca15cb93b9c.meta.json +0 -0
  133. {cctx_cli-1.9.0 → cctx_cli-1.11.0}/tests/fixtures/claude_code/with-compaction/with-compaction/subagents/agent-aec9c917feb903d67.jsonl +0 -0
  134. {cctx_cli-1.9.0 → cctx_cli-1.11.0}/tests/fixtures/claude_code/with-compaction/with-compaction/subagents/agent-aec9c917feb903d67.meta.json +0 -0
  135. {cctx_cli-1.9.0 → cctx_cli-1.11.0}/tests/fixtures/claude_code/with-compaction/with-compaction.jsonl +0 -0
  136. {cctx_cli-1.9.0 → cctx_cli-1.11.0}/tests/fixtures/claude_code/with-subagents/with-subagents/subagents/agent-a1a3a21aeb76bb0a9.jsonl +0 -0
  137. {cctx_cli-1.9.0 → cctx_cli-1.11.0}/tests/fixtures/claude_code/with-subagents/with-subagents/subagents/agent-a1a3a21aeb76bb0a9.meta.json +0 -0
  138. {cctx_cli-1.9.0 → cctx_cli-1.11.0}/tests/fixtures/claude_code/with-subagents/with-subagents/subagents/agent-aaa1d6ecc05a78442.jsonl +0 -0
  139. {cctx_cli-1.9.0 → cctx_cli-1.11.0}/tests/fixtures/claude_code/with-subagents/with-subagents/subagents/agent-aaa1d6ecc05a78442.meta.json +0 -0
  140. {cctx_cli-1.9.0 → cctx_cli-1.11.0}/tests/fixtures/claude_code/with-subagents/with-subagents/subagents/agent-af3c545ccd30036d2.jsonl +0 -0
  141. {cctx_cli-1.9.0 → cctx_cli-1.11.0}/tests/fixtures/claude_code/with-subagents/with-subagents/subagents/agent-af3c545ccd30036d2.meta.json +0 -0
  142. {cctx_cli-1.9.0 → cctx_cli-1.11.0}/tests/fixtures/claude_code/with-subagents/with-subagents/tool-results/btwp2bzro.txt +0 -0
  143. {cctx_cli-1.9.0 → cctx_cli-1.11.0}/tests/fixtures/claude_code/with-subagents/with-subagents/tool-results/byqjbgy4b.txt +0 -0
  144. {cctx_cli-1.9.0 → cctx_cli-1.11.0}/tests/fixtures/claude_code/with-subagents/with-subagents.jsonl +0 -0
  145. {cctx_cli-1.9.0 → cctx_cli-1.11.0}/tests/fixtures/claude_code/with-tool-results/with-tool-results/tool-results/bosbkda0h.txt +0 -0
  146. {cctx_cli-1.9.0 → cctx_cli-1.11.0}/tests/fixtures/claude_code/with-tool-results/with-tool-results.jsonl +0 -0
  147. {cctx_cli-1.9.0 → cctx_cli-1.11.0}/tests/fixtures/scrub.py +0 -0
  148. {cctx_cli-1.9.0 → cctx_cli-1.11.0}/tests/fixtures/synthetic/bookkeeping_only.jsonl +0 -0
  149. {cctx_cli-1.9.0 → cctx_cli-1.11.0}/tests/fixtures/synthetic/malformed_middle.jsonl +0 -0
  150. {cctx_cli-1.9.0 → cctx_cli-1.11.0}/tests/fixtures/synthetic/truncated_final_line.jsonl +0 -0
  151. {cctx_cli-1.9.0 → cctx_cli-1.11.0}/tests/fixtures/synthetic/unknown_attachment_shape.jsonl +0 -0
  152. {cctx_cli-1.9.0 → cctx_cli-1.11.0}/tests/fixtures/synthetic/unknown_type.jsonl +0 -0
  153. {cctx_cli-1.9.0 → cctx_cli-1.11.0}/tests/parsers/__init__.py +0 -0
  154. {cctx_cli-1.9.0 → cctx_cli-1.11.0}/tests/parsers/test_claude_code.py +0 -0
  155. {cctx_cli-1.9.0 → cctx_cli-1.11.0}/tests/parsers/test_claude_code_integration.py +0 -0
  156. {cctx_cli-1.9.0 → cctx_cli-1.11.0}/tests/recommender/__init__.py +0 -0
  157. {cctx_cli-1.9.0 → cctx_cli-1.11.0}/tests/recommender/test_claude_md.py +0 -0
  158. {cctx_cli-1.9.0 → cctx_cli-1.11.0}/tests/recommender/test_evidence.py +0 -0
  159. {cctx_cli-1.9.0 → cctx_cli-1.11.0}/tests/renderers/__init__.py +0 -0
  160. {cctx_cli-1.9.0 → cctx_cli-1.11.0}/tests/renderers/test_report.py +0 -0
  161. {cctx_cli-1.9.0 → cctx_cli-1.11.0}/tests/renderers/test_terminal_renderer_full.py +0 -0
  162. {cctx_cli-1.9.0 → cctx_cli-1.11.0}/tests/test_agents.py +0 -0
  163. {cctx_cli-1.9.0 → cctx_cli-1.11.0}/tests/test_aggregate.py +0 -0
  164. {cctx_cli-1.9.0 → cctx_cli-1.11.0}/tests/test_cli_export.py +0 -0
  165. {cctx_cli-1.9.0 → cctx_cli-1.11.0}/tests/test_diagnostician_subagents.py +0 -0
  166. {cctx_cli-1.9.0 → cctx_cli-1.11.0}/tests/test_discovery.py +0 -0
  167. {cctx_cli-1.9.0 → cctx_cli-1.11.0}/tests/test_efficacy.py +0 -0
  168. {cctx_cli-1.9.0 → cctx_cli-1.11.0}/tests/test_fanout_classifier.py +0 -0
  169. {cctx_cli-1.9.0 → cctx_cli-1.11.0}/tests/test_github_summary.py +0 -0
  170. {cctx_cli-1.9.0 → cctx_cli-1.11.0}/tests/test_harvest.py +0 -0
  171. {cctx_cli-1.9.0 → cctx_cli-1.11.0}/tests/test_harvest_check.py +0 -0
  172. {cctx_cli-1.9.0 → cctx_cli-1.11.0}/tests/test_harvest_emit.py +0 -0
  173. {cctx_cli-1.9.0 → cctx_cli-1.11.0}/tests/test_models.py +0 -0
  174. {cctx_cli-1.9.0 → cctx_cli-1.11.0}/tests/test_models_project_pattern.py +0 -0
  175. {cctx_cli-1.9.0 → cctx_cli-1.11.0}/tests/test_recommender.py +0 -0
  176. {cctx_cli-1.9.0 → cctx_cli-1.11.0}/tests/test_smoke.py +0 -0
  177. {cctx_cli-1.9.0 → cctx_cli-1.11.0}/tests/test_terminal_renderer.py +0 -0
  178. {cctx_cli-1.9.0 → cctx_cli-1.11.0}/tests/test_tokenizer.py +0 -0
  179. {cctx_cli-1.9.0 → cctx_cli-1.11.0}/tests/test_trace_tui.py +0 -0
  180. {cctx_cli-1.9.0 → cctx_cli-1.11.0}/tests/test_watcher.py +0 -0
@@ -2,6 +2,30 @@
2
2
 
3
3
  <!-- version list -->
4
4
 
5
+ ## v1.11.0 (2026-06-11)
6
+
7
+ ### Bug Fixes
8
+
9
+ - Ruff E501 + I001 in test_init.py (line length + import order)
10
+ ([#113](https://github.com/jacquardlabs/cctx/pull/113),
11
+ [`a04426e`](https://github.com/jacquardlabs/cctx/commit/a04426ef62cfeaa4ba7ea392bdf0dd49975cffbb))
12
+
13
+ ### Features
14
+
15
+ - Cctx init — SessionEnd hook installer + autopsy --quiet (closes #92)
16
+ ([#113](https://github.com/jacquardlabs/cctx/pull/113),
17
+ [`a04426e`](https://github.com/jacquardlabs/cctx/commit/a04426ef62cfeaa4ba7ea392bdf0dd49975cffbb))
18
+
19
+
20
+ ## v1.10.0 (2026-06-11)
21
+
22
+ ### Features
23
+
24
+ - Autopsy --json aggregate output for --since mode (closes #97)
25
+ ([#112](https://github.com/jacquardlabs/cctx/pull/112),
26
+ [`86b10e0`](https://github.com/jacquardlabs/cctx/commit/86b10e0787efa99e9af251e979e311053d7b8b72))
27
+
28
+
5
29
  ## v1.9.0 (2026-06-11)
6
30
 
7
31
  ### Bug Fixes
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: cctx-cli
3
- Version: 1.9.0
3
+ Version: 1.11.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.9.0"
3
+ __version__ = "1.11.0"
@@ -9,6 +9,7 @@ Commands:
9
9
  cctx harvest <session> Apply autopsy patches to CLAUDE.md
10
10
  cctx harvest <project> --since Cross-session harvest
11
11
  cctx watch [project] Live waste signals during an active session
12
+ cctx init Install opt-in SessionEnd hook for auto-diagnostics
12
13
  """
13
14
  from __future__ import annotations
14
15
 
@@ -292,7 +293,15 @@ def ls(project: Path | None) -> None:
292
293
  "json_out",
293
294
  is_flag=True,
294
295
  default=False,
295
- help="Output diagnosis as JSON to stdout (single-session only).",
296
+ help="Output diagnosis (or aggregate) as JSON to stdout.",
297
+ )
298
+ @click.option(
299
+ "--quiet",
300
+ "quiet",
301
+ is_flag=True,
302
+ default=False,
303
+ help="Print one verdict line only when findings exist; nothing if clean. "
304
+ "Designed for SessionEnd hook use (cctx init).",
296
305
  )
297
306
  def autopsy(
298
307
  target: Path | None,
@@ -305,6 +314,7 @@ def autopsy(
305
314
  top_n: int | None,
306
315
  turn_num: int | None,
307
316
  json_out: bool,
317
+ quiet: bool,
308
318
  ) -> None:
309
319
  """Diagnose a session or project directory.
310
320
 
@@ -326,8 +336,6 @@ def autopsy(
326
336
  raise click.UsageError("--turn is not supported with --since.")
327
337
  if until_date is not None and since is None:
328
338
  raise click.UsageError("--until requires --since.")
329
- if json_out and since is not None:
330
- raise click.UsageError("--json is not supported with --since.")
331
339
 
332
340
  if target is None:
333
341
  if not latest:
@@ -392,8 +400,14 @@ def autopsy(
392
400
  patches=patches,
393
401
  project_patterns=patterns,
394
402
  )
395
- render_aggregate(report)
396
- _aggregate_drilldown(report, diagnoses)
403
+ if json_out:
404
+ import json as _json
405
+
406
+ from cctx.exporters.jsonl import export_aggregate as _export_agg
407
+ click.echo(_json.dumps(_json.loads(_export_agg(report)), indent=2))
408
+ else:
409
+ render_aggregate(report)
410
+ _aggregate_drilldown(report, diagnoses)
397
411
  else:
398
412
  # Single-session path
399
413
  if target.is_dir():
@@ -404,7 +418,11 @@ def autopsy(
404
418
  trace = tokenize_session(parse_session(target))
405
419
  diagnosis = diagnostician.run(trace)
406
420
  diagnosis = claude_md.generate(diagnosis)
407
- if json_out:
421
+ if quiet:
422
+ if diagnosis.findings:
423
+ kinds = list(dict.fromkeys(f.kind.value for f in diagnosis.findings))
424
+ click.echo(f"{len(diagnosis.findings)} finding(s): {', '.join(kinds)}")
425
+ elif json_out:
408
426
  import json as _json
409
427
 
410
428
  from cctx.exporters.jsonl import export_diagnosis as _export_diag
@@ -725,3 +743,58 @@ def watch(target: Path | None) -> None:
725
743
  """
726
744
  from cctx.watcher import watch as _watch
727
745
  _watch(target)
746
+
747
+
748
+ @cli.command("init")
749
+ @click.option(
750
+ "--global",
751
+ "global_",
752
+ is_flag=True,
753
+ default=False,
754
+ help="Install to ~/.claude/settings.json (user scope) instead of .claude/settings.json.",
755
+ )
756
+ @click.option(
757
+ "--remove",
758
+ "remove_",
759
+ is_flag=True,
760
+ default=False,
761
+ help="Remove the SessionEnd hook instead of installing it.",
762
+ )
763
+ @click.option(
764
+ "--force",
765
+ is_flag=True,
766
+ default=False,
767
+ help="Reinstall even if hook is already present.",
768
+ )
769
+ def init_cmd(global_: bool, remove_: bool, force: bool) -> None:
770
+ """Install an opt-in SessionEnd hook for automatic post-session diagnostics.
771
+
772
+ Writes a hook to .claude/settings.json (project) or ~/.claude/settings.json
773
+ (--global) that runs 'cctx autopsy --latest --quiet' when a Claude Code
774
+ session ends. Output appears only when findings exist.
775
+
776
+ Idempotent — running twice does not duplicate the hook.
777
+ """
778
+ from cctx import hook_installer
779
+
780
+ if force and remove_:
781
+ raise click.UsageError("--force and --remove are mutually exclusive.")
782
+
783
+ scope = "~/.claude/settings.json" if global_ else ".claude/settings.json"
784
+
785
+ if remove_:
786
+ path = hook_installer.remove(global_=global_)
787
+ if path is None:
788
+ click.echo(f"No cctx hook found in {scope} — nothing to remove.")
789
+ else:
790
+ click.echo(f"✓ SessionEnd hook removed from {scope}")
791
+ return
792
+
793
+ result = hook_installer.install(global_=global_, force=force)
794
+ if result == "already_installed":
795
+ click.echo(f"! SessionEnd hook already installed in {scope}")
796
+ click.echo(" Use 'cctx init --force' to reinstall.")
797
+ else:
798
+ click.echo(f"✓ SessionEnd hook installed to {scope}")
799
+ remove_flag = "--global --remove" if global_ else "--remove"
800
+ click.echo(f" Run 'cctx init {remove_flag}' to uninstall.")
@@ -5,7 +5,7 @@ import json
5
5
  from typing import IO, TYPE_CHECKING
6
6
 
7
7
  if TYPE_CHECKING:
8
- from cctx.models import Diagnosis, SessionTrace
8
+ from cctx.models import AggregateReport, Diagnosis, SessionTrace
9
9
 
10
10
 
11
11
  def export_diagnosis(
@@ -64,6 +64,50 @@ def export_diagnosis(
64
64
  return json.dumps(obj)
65
65
 
66
66
 
67
+ def export_aggregate(report: AggregateReport) -> str:
68
+ """Serialize an AggregateReport to a JSON string."""
69
+ by_kind = {
70
+ k.value: {
71
+ "session_count": v.session_count,
72
+ "total_waste_usd": v.total_waste_usd,
73
+ "example_summaries": v.example_summaries,
74
+ }
75
+ for k, v in report.by_kind.items()
76
+ }
77
+ patches = [
78
+ {
79
+ "target_file": p.target_file,
80
+ "finding_kind": p.finding_kind.value,
81
+ "description": p.description,
82
+ "evidence_summary": p.evidence_summary,
83
+ }
84
+ for p in report.patches
85
+ ]
86
+ project_patterns = [
87
+ {
88
+ "tool_name": pp.tool_name,
89
+ "failure_key": pp.failure_key,
90
+ "fix_key": pp.fix_key,
91
+ "session_count": pp.session_count,
92
+ "avg_wasted_turns": pp.avg_wasted_turns,
93
+ "total_waste_usd": pp.total_waste_usd,
94
+ "example_sessions": pp.example_sessions,
95
+ }
96
+ for pp in report.project_patterns
97
+ ]
98
+ obj = {
99
+ "period_label": report.period_label,
100
+ "sessions_analysed": report.sessions_analysed,
101
+ "sessions_with_findings": report.sessions_with_findings,
102
+ "total_cost_usd": report.total_cost_usd,
103
+ "waste_cost_usd": report.waste_cost_usd,
104
+ "by_kind": by_kind,
105
+ "patches": patches,
106
+ "project_patterns": project_patterns,
107
+ }
108
+ return json.dumps(obj)
109
+
110
+
67
111
  def write(
68
112
  diagnoses: list[tuple[Diagnosis, SessionTrace]],
69
113
  out: IO[str],
@@ -0,0 +1,101 @@
1
+ """Settings-merge hook installer — install/remove the cctx SessionEnd hook.
2
+
3
+ Reads, merges, and writes ~/.claude/settings.json or .claude/settings.json
4
+ without touching any other keys. Idempotent: fingerprinted by the hook's
5
+ description field so a double-install is a no-op.
6
+ """
7
+ from __future__ import annotations
8
+
9
+ import json
10
+ from pathlib import Path
11
+ from typing import Any
12
+
13
+ HOOK_DESCRIPTION = "cctx SessionEnd hook (diagnostics on session exit)"
14
+ HOOK_COMMAND = "cctx autopsy --latest --quiet"
15
+
16
+
17
+ def settings_path(global_: bool) -> Path:
18
+ if global_:
19
+ return Path.home() / ".claude" / "settings.json"
20
+ return Path(".claude") / "settings.json"
21
+
22
+
23
+ def _load(path: Path) -> dict[str, Any]:
24
+ if not path.exists():
25
+ return {}
26
+ try:
27
+ return json.loads(path.read_text(encoding="utf-8"))
28
+ except json.JSONDecodeError as exc:
29
+ raise ValueError(f"Invalid JSON in {path}: {exc}") from exc
30
+
31
+
32
+ def _save(path: Path, settings: dict[str, Any]) -> None:
33
+ path.parent.mkdir(parents=True, exist_ok=True)
34
+ tmp = path.with_suffix(".json.tmp")
35
+ tmp.write_text(json.dumps(settings, indent=2) + "\n", encoding="utf-8")
36
+ tmp.replace(path)
37
+
38
+
39
+ def _find_hook(session_end: list[dict[str, Any]]) -> int | None:
40
+ """Return the index of the cctx hook group in the SessionEnd array, or None."""
41
+ for i, group in enumerate(session_end):
42
+ for h in group.get("hooks", []):
43
+ desc = h.get("description", "")
44
+ if isinstance(desc, str) and "cctx SessionEnd" in desc:
45
+ return i
46
+ return None
47
+
48
+
49
+ def _hook_entry() -> dict[str, Any]:
50
+ return {
51
+ "hooks": [
52
+ {
53
+ "type": "command",
54
+ "command": HOOK_COMMAND,
55
+ "async": True,
56
+ "description": HOOK_DESCRIPTION,
57
+ }
58
+ ]
59
+ }
60
+
61
+
62
+ def is_installed(global_: bool = False) -> bool:
63
+ path = settings_path(global_)
64
+ settings = _load(path)
65
+ session_end = settings.get("hooks", {}).get("SessionEnd", [])
66
+ return _find_hook(session_end) is not None
67
+
68
+
69
+ def install(global_: bool = False, force: bool = False) -> str:
70
+ """Install the hook. Returns "already_installed" or the path written."""
71
+ path = settings_path(global_)
72
+ settings = _load(path)
73
+ hooks = settings.setdefault("hooks", {})
74
+ session_end = hooks.setdefault("SessionEnd", [])
75
+ idx = _find_hook(session_end)
76
+ if idx is not None and not force:
77
+ return "already_installed"
78
+ if idx is not None:
79
+ session_end[idx] = _hook_entry()
80
+ else:
81
+ session_end.append(_hook_entry())
82
+ _save(path, settings)
83
+ return str(path)
84
+
85
+
86
+ def remove(global_: bool = False) -> str | None:
87
+ """Remove the hook. Returns the path written, or None if not found."""
88
+ path = settings_path(global_)
89
+ settings = _load(path)
90
+ hooks = settings.get("hooks", {})
91
+ session_end = hooks.get("SessionEnd", [])
92
+ idx = _find_hook(session_end)
93
+ if idx is None:
94
+ return None
95
+ session_end.pop(idx)
96
+ if not session_end:
97
+ del hooks["SessionEnd"]
98
+ if not hooks:
99
+ del settings["hooks"]
100
+ _save(path, settings)
101
+ return str(path)
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "cctx-cli"
7
- version = "1.9.0"
7
+ version = "1.11.0"
8
8
  description = "Diagnose Claude Code sessions — find what went wrong, what it cost, and what to add to CLAUDE.md"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.10"
@@ -488,17 +488,42 @@ def test_autopsy_json_outputs_valid_json(runner, session_jsonl):
488
488
  assert "findings" in data
489
489
 
490
490
 
491
- def test_autopsy_json_incompatible_with_since(runner, tmp_path):
492
- """--json + --since → non-zero exit (UsageError)."""
491
+ def test_autopsy_json_aggregate_outputs_valid_json(runner, tmp_path):
492
+ """--json + --since → valid aggregate JSON with expected top-level keys."""
493
493
  from cctx.cli import cli
494
494
 
495
495
  project_dir = tmp_path / "-Users-test-Projects-demo"
496
496
  project_dir.mkdir()
497
497
 
498
+ session_id = "json-agg-test-01"
499
+ line = {
500
+ "type": "user",
501
+ "uuid": f"{session_id}-u1",
502
+ "parentUuid": None,
503
+ "isSidechain": False,
504
+ "timestamp": "2026-05-14T10:00:00.000Z",
505
+ "sessionId": session_id,
506
+ "version": "2.1.138",
507
+ "cwd": "/Users/test/Projects/demo",
508
+ "gitBranch": "main",
509
+ "userType": "external",
510
+ "entrypoint": "cli",
511
+ "message": {"role": "user", "content": "hello"},
512
+ }
513
+ (project_dir / f"{session_id}.jsonl").write_text(json.dumps(line) + "\n")
514
+
498
515
  result = runner.invoke(
499
516
  cli, ["autopsy", str(project_dir), "--since", "7", "--json"],
517
+ catch_exceptions=False,
500
518
  )
501
- assert result.exit_code != 0
519
+ assert result.exit_code == 0
520
+ data = json.loads(result.output)
521
+ assert "sessions_analysed" in data
522
+ assert "total_cost_usd" in data
523
+ assert "waste_cost_usd" in data
524
+ assert "by_kind" in data
525
+ assert "patches" in data
526
+ assert "project_patterns" in data
502
527
 
503
528
 
504
529
  def test_autopsy_json_contains_cost(runner, session_jsonl):