cctx-cli 1.1.0__tar.gz → 1.2.0__tar.gz

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