cctx-cli 1.4.0__tar.gz → 1.5.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 (171) hide show
  1. {cctx_cli-1.4.0 → cctx_cli-1.5.0}/CHANGELOG.md +36 -0
  2. {cctx_cli-1.4.0 → cctx_cli-1.5.0}/PKG-INFO +1 -1
  3. {cctx_cli-1.4.0 → cctx_cli-1.5.0}/cctx/__init__.py +1 -1
  4. cctx_cli-1.5.0/cctx/agents.py +66 -0
  5. {cctx_cli-1.4.0 → cctx_cli-1.5.0}/cctx/cli.py +5 -2
  6. {cctx_cli-1.4.0 → cctx_cli-1.5.0}/cctx/renderers/terminal.py +35 -4
  7. {cctx_cli-1.4.0 → cctx_cli-1.5.0}/cctx/watcher.py +37 -11
  8. cctx_cli-1.5.0/docs/superpowers/plans/2026-05-19-claude-agents-live-integration.md +839 -0
  9. cctx_cli-1.5.0/docs/superpowers/specs/2026-05-19-claude-agents-live-integration-design.md +128 -0
  10. {cctx_cli-1.4.0 → cctx_cli-1.5.0}/pyproject.toml +1 -1
  11. cctx_cli-1.5.0/tests/test_agents.py +128 -0
  12. {cctx_cli-1.4.0 → cctx_cli-1.5.0}/tests/test_terminal_renderer.py +154 -0
  13. {cctx_cli-1.4.0 → cctx_cli-1.5.0}/tests/test_watcher.py +101 -0
  14. {cctx_cli-1.4.0 → cctx_cli-1.5.0}/.github/workflows/ci.yml +0 -0
  15. {cctx_cli-1.4.0 → cctx_cli-1.5.0}/.github/workflows/publish.yml +0 -0
  16. {cctx_cli-1.4.0 → cctx_cli-1.5.0}/.github/workflows/release.yml +0 -0
  17. {cctx_cli-1.4.0 → cctx_cli-1.5.0}/.gitignore +0 -0
  18. {cctx_cli-1.4.0 → cctx_cli-1.5.0}/CLAUDE.md +0 -0
  19. {cctx_cli-1.4.0 → cctx_cli-1.5.0}/DESIGN.md +0 -0
  20. {cctx_cli-1.4.0 → cctx_cli-1.5.0}/PRODUCT.md +0 -0
  21. {cctx_cli-1.4.0 → cctx_cli-1.5.0}/README.md +0 -0
  22. {cctx_cli-1.4.0 → cctx_cli-1.5.0}/action.yml +0 -0
  23. {cctx_cli-1.4.0 → cctx_cli-1.5.0}/cctx/diagnostician/__init__.py +0 -0
  24. {cctx_cli-1.4.0 → cctx_cli-1.5.0}/cctx/diagnostician/aggregate.py +0 -0
  25. {cctx_cli-1.4.0 → cctx_cli-1.5.0}/cctx/diagnostician/inflection.py +0 -0
  26. {cctx_cli-1.4.0 → cctx_cli-1.5.0}/cctx/diagnostician/patterns/__init__.py +0 -0
  27. {cctx_cli-1.4.0 → cctx_cli-1.5.0}/cctx/diagnostician/patterns/dead_end.py +0 -0
  28. {cctx_cli-1.4.0 → cctx_cli-1.5.0}/cctx/diagnostician/patterns/project_specific.py +0 -0
  29. {cctx_cli-1.4.0 → cctx_cli-1.5.0}/cctx/diagnostician/patterns/retry_loop.py +0 -0
  30. {cctx_cli-1.4.0 → cctx_cli-1.5.0}/cctx/diagnostician/patterns/scope_creep.py +0 -0
  31. {cctx_cli-1.4.0 → cctx_cli-1.5.0}/cctx/diagnostician/patterns/stale_context.py +0 -0
  32. {cctx_cli-1.4.0 → cctx_cli-1.5.0}/cctx/diagnostician/patterns/tool_thrash.py +0 -0
  33. {cctx_cli-1.4.0 → cctx_cli-1.5.0}/cctx/discovery.py +0 -0
  34. {cctx_cli-1.4.0 → cctx_cli-1.5.0}/cctx/exporters/__init__.py +0 -0
  35. {cctx_cli-1.4.0 → cctx_cli-1.5.0}/cctx/exporters/csv.py +0 -0
  36. {cctx_cli-1.4.0 → cctx_cli-1.5.0}/cctx/exporters/json.py +0 -0
  37. {cctx_cli-1.4.0 → cctx_cli-1.5.0}/cctx/exporters/jsonl.py +0 -0
  38. {cctx_cli-1.4.0 → cctx_cli-1.5.0}/cctx/harvest.py +0 -0
  39. {cctx_cli-1.4.0 → cctx_cli-1.5.0}/cctx/models.py +0 -0
  40. {cctx_cli-1.4.0 → cctx_cli-1.5.0}/cctx/parsers/__init__.py +0 -0
  41. {cctx_cli-1.4.0 → cctx_cli-1.5.0}/cctx/parsers/claude_code.py +0 -0
  42. {cctx_cli-1.4.0 → cctx_cli-1.5.0}/cctx/pricing.py +0 -0
  43. {cctx_cli-1.4.0 → cctx_cli-1.5.0}/cctx/recommender/__init__.py +0 -0
  44. {cctx_cli-1.4.0 → cctx_cli-1.5.0}/cctx/recommender/claude_md.py +0 -0
  45. {cctx_cli-1.4.0 → cctx_cli-1.5.0}/cctx/recommender/evidence.py +0 -0
  46. {cctx_cli-1.4.0 → cctx_cli-1.5.0}/cctx/renderers/__init__.py +0 -0
  47. {cctx_cli-1.4.0 → cctx_cli-1.5.0}/cctx/renderers/github.py +0 -0
  48. {cctx_cli-1.4.0 → cctx_cli-1.5.0}/cctx/renderers/report.py +0 -0
  49. {cctx_cli-1.4.0 → cctx_cli-1.5.0}/cctx/renderers/templates/autopsy.html.j2 +0 -0
  50. {cctx_cli-1.4.0 → cctx_cli-1.5.0}/cctx/renderers/trace_tui.py +0 -0
  51. {cctx_cli-1.4.0 → cctx_cli-1.5.0}/cctx/tokenizer.py +0 -0
  52. {cctx_cli-1.4.0 → cctx_cli-1.5.0}/cctx-project-brief.md +0 -0
  53. {cctx_cli-1.4.0 → cctx_cli-1.5.0}/demo.gif +0 -0
  54. {cctx_cli-1.4.0 → cctx_cli-1.5.0}/demo.tape +0 -0
  55. {cctx_cli-1.4.0 → cctx_cli-1.5.0}/docs/health-reviews/2026-05-15-deep-review-summary.md +0 -0
  56. {cctx_cli-1.4.0 → cctx_cli-1.5.0}/docs/health-reviews/2026-05-15-health-review.md +0 -0
  57. {cctx_cli-1.4.0 → cctx_cli-1.5.0}/docs/product-reviews/2026-05-15-product-review.md +0 -0
  58. {cctx_cli-1.4.0 → cctx_cli-1.5.0}/docs/superpowers/plans/2026-05-12-claude-code-parser.md +0 -0
  59. {cctx_cli-1.4.0 → cctx_cli-1.5.0}/docs/superpowers/plans/2026-05-14-autopsy-v0.md +0 -0
  60. {cctx_cli-1.4.0 → cctx_cli-1.5.0}/docs/superpowers/plans/2026-05-16-readme-pypi-release.md +0 -0
  61. {cctx_cli-1.4.0 → cctx_cli-1.5.0}/docs/superpowers/plans/2026-05-17-harvest-check-depth.md +0 -0
  62. {cctx_cli-1.4.0 → cctx_cli-1.5.0}/docs/superpowers/plans/2026-05-17-project-pattern-detection.md +0 -0
  63. {cctx_cli-1.4.0 → cctx_cli-1.5.0}/docs/superpowers/specs/2026-05-12-claude-code-parser-design.md +0 -0
  64. {cctx_cli-1.4.0 → cctx_cli-1.5.0}/docs/superpowers/specs/2026-05-14-autopsy-design.md +0 -0
  65. {cctx_cli-1.4.0 → cctx_cli-1.5.0}/docs/superpowers/specs/2026-05-14-harvest-design.md +0 -0
  66. {cctx_cli-1.4.0 → cctx_cli-1.5.0}/docs/superpowers/specs/2026-05-14-trace-tui-design.md +0 -0
  67. {cctx_cli-1.4.0 → cctx_cli-1.5.0}/docs/superpowers/specs/2026-05-16-readme-pypi-release-design.md +0 -0
  68. {cctx_cli-1.4.0 → cctx_cli-1.5.0}/docs/superpowers/specs/2026-05-17-harvest-check-depth-design.md +0 -0
  69. {cctx_cli-1.4.0 → cctx_cli-1.5.0}/docs/superpowers/specs/2026-05-17-project-pattern-detection-design.md +0 -0
  70. {cctx_cli-1.4.0 → cctx_cli-1.5.0}/tests/__init__.py +0 -0
  71. {cctx_cli-1.4.0 → cctx_cli-1.5.0}/tests/conftest.py +0 -0
  72. {cctx_cli-1.4.0 → cctx_cli-1.5.0}/tests/diagnostician/__init__.py +0 -0
  73. {cctx_cli-1.4.0 → cctx_cli-1.5.0}/tests/diagnostician/conftest.py +0 -0
  74. {cctx_cli-1.4.0 → cctx_cli-1.5.0}/tests/diagnostician/test_dead_end.py +0 -0
  75. {cctx_cli-1.4.0 → cctx_cli-1.5.0}/tests/diagnostician/test_inflection.py +0 -0
  76. {cctx_cli-1.4.0 → cctx_cli-1.5.0}/tests/diagnostician/test_orchestrator.py +0 -0
  77. {cctx_cli-1.4.0 → cctx_cli-1.5.0}/tests/diagnostician/test_project_specific.py +0 -0
  78. {cctx_cli-1.4.0 → cctx_cli-1.5.0}/tests/diagnostician/test_retry_loop.py +0 -0
  79. {cctx_cli-1.4.0 → cctx_cli-1.5.0}/tests/diagnostician/test_scope_creep.py +0 -0
  80. {cctx_cli-1.4.0 → cctx_cli-1.5.0}/tests/diagnostician/test_stale_context.py +0 -0
  81. {cctx_cli-1.4.0 → cctx_cli-1.5.0}/tests/diagnostician/test_tool_thrash.py +0 -0
  82. {cctx_cli-1.4.0 → cctx_cli-1.5.0}/tests/exporters/__init__.py +0 -0
  83. {cctx_cli-1.4.0 → cctx_cli-1.5.0}/tests/exporters/test_csv.py +0 -0
  84. {cctx_cli-1.4.0 → cctx_cli-1.5.0}/tests/exporters/test_jsonl.py +0 -0
  85. {cctx_cli-1.4.0 → cctx_cli-1.5.0}/tests/fixtures/claude_code/README.md +0 -0
  86. {cctx_cli-1.4.0 → cctx_cli-1.5.0}/tests/fixtures/claude_code/short-clean/short-clean.jsonl +0 -0
  87. {cctx_cli-1.4.0 → cctx_cli-1.5.0}/tests/fixtures/claude_code/with-attachments/with-attachments/subagents/agent-a0b4c2cf1dde0ca56.meta.json +0 -0
  88. {cctx_cli-1.4.0 → cctx_cli-1.5.0}/tests/fixtures/claude_code/with-attachments/with-attachments/subagents/agent-a116ae34b1b09c332.meta.json +0 -0
  89. {cctx_cli-1.4.0 → cctx_cli-1.5.0}/tests/fixtures/claude_code/with-attachments/with-attachments/subagents/agent-a1c4c417b35658c9e.meta.json +0 -0
  90. {cctx_cli-1.4.0 → cctx_cli-1.5.0}/tests/fixtures/claude_code/with-attachments/with-attachments/subagents/agent-a1e41a901de38f1b5.meta.json +0 -0
  91. {cctx_cli-1.4.0 → cctx_cli-1.5.0}/tests/fixtures/claude_code/with-attachments/with-attachments/subagents/agent-a338f8d0c74612a24.meta.json +0 -0
  92. {cctx_cli-1.4.0 → cctx_cli-1.5.0}/tests/fixtures/claude_code/with-attachments/with-attachments/subagents/agent-a34f6f3c0e7094186.meta.json +0 -0
  93. {cctx_cli-1.4.0 → cctx_cli-1.5.0}/tests/fixtures/claude_code/with-attachments/with-attachments/subagents/agent-a5a5a0cff4d13308b.meta.json +0 -0
  94. {cctx_cli-1.4.0 → cctx_cli-1.5.0}/tests/fixtures/claude_code/with-attachments/with-attachments/subagents/agent-a6b0a3da6a0484db5.meta.json +0 -0
  95. {cctx_cli-1.4.0 → cctx_cli-1.5.0}/tests/fixtures/claude_code/with-attachments/with-attachments/subagents/agent-a7f73f1790b02cde5.meta.json +0 -0
  96. {cctx_cli-1.4.0 → cctx_cli-1.5.0}/tests/fixtures/claude_code/with-attachments/with-attachments/subagents/agent-a7f7c17c38a9d8788.meta.json +0 -0
  97. {cctx_cli-1.4.0 → cctx_cli-1.5.0}/tests/fixtures/claude_code/with-attachments/with-attachments/subagents/agent-a853259e2cd7bbe8a.meta.json +0 -0
  98. {cctx_cli-1.4.0 → cctx_cli-1.5.0}/tests/fixtures/claude_code/with-attachments/with-attachments/subagents/agent-a8d9aedb0d0c6e12d.meta.json +0 -0
  99. {cctx_cli-1.4.0 → cctx_cli-1.5.0}/tests/fixtures/claude_code/with-attachments/with-attachments/subagents/agent-aa778bc1d59e4a441.meta.json +0 -0
  100. {cctx_cli-1.4.0 → cctx_cli-1.5.0}/tests/fixtures/claude_code/with-attachments/with-attachments/subagents/agent-aba869dedee4a12ba.meta.json +0 -0
  101. {cctx_cli-1.4.0 → cctx_cli-1.5.0}/tests/fixtures/claude_code/with-attachments/with-attachments/subagents/agent-ada2746d9774b94db.meta.json +0 -0
  102. {cctx_cli-1.4.0 → cctx_cli-1.5.0}/tests/fixtures/claude_code/with-attachments/with-attachments/subagents/agent-aea0132068c64d2dd.meta.json +0 -0
  103. {cctx_cli-1.4.0 → cctx_cli-1.5.0}/tests/fixtures/claude_code/with-attachments/with-attachments/subagents/agent-aea215eff50874d5f.meta.json +0 -0
  104. {cctx_cli-1.4.0 → cctx_cli-1.5.0}/tests/fixtures/claude_code/with-attachments/with-attachments/subagents/agent-afee21f2b3852a4a0.meta.json +0 -0
  105. {cctx_cli-1.4.0 → cctx_cli-1.5.0}/tests/fixtures/claude_code/with-attachments/with-attachments.jsonl +0 -0
  106. {cctx_cli-1.4.0 → cctx_cli-1.5.0}/tests/fixtures/claude_code/with-compaction/with-compaction/subagents/agent-a051d9c9a6b2f5cc3.jsonl +0 -0
  107. {cctx_cli-1.4.0 → cctx_cli-1.5.0}/tests/fixtures/claude_code/with-compaction/with-compaction/subagents/agent-a051d9c9a6b2f5cc3.meta.json +0 -0
  108. {cctx_cli-1.4.0 → cctx_cli-1.5.0}/tests/fixtures/claude_code/with-compaction/with-compaction/subagents/agent-a171f16f4e65cfe75.jsonl +0 -0
  109. {cctx_cli-1.4.0 → cctx_cli-1.5.0}/tests/fixtures/claude_code/with-compaction/with-compaction/subagents/agent-a171f16f4e65cfe75.meta.json +0 -0
  110. {cctx_cli-1.4.0 → cctx_cli-1.5.0}/tests/fixtures/claude_code/with-compaction/with-compaction/subagents/agent-a1b77fea2c0a2269b.jsonl +0 -0
  111. {cctx_cli-1.4.0 → cctx_cli-1.5.0}/tests/fixtures/claude_code/with-compaction/with-compaction/subagents/agent-a1b77fea2c0a2269b.meta.json +0 -0
  112. {cctx_cli-1.4.0 → cctx_cli-1.5.0}/tests/fixtures/claude_code/with-compaction/with-compaction/subagents/agent-a20da4c01a54acca8.jsonl +0 -0
  113. {cctx_cli-1.4.0 → cctx_cli-1.5.0}/tests/fixtures/claude_code/with-compaction/with-compaction/subagents/agent-a20da4c01a54acca8.meta.json +0 -0
  114. {cctx_cli-1.4.0 → cctx_cli-1.5.0}/tests/fixtures/claude_code/with-compaction/with-compaction/subagents/agent-a3c82739b1383fb14.jsonl +0 -0
  115. {cctx_cli-1.4.0 → cctx_cli-1.5.0}/tests/fixtures/claude_code/with-compaction/with-compaction/subagents/agent-a3c82739b1383fb14.meta.json +0 -0
  116. {cctx_cli-1.4.0 → cctx_cli-1.5.0}/tests/fixtures/claude_code/with-compaction/with-compaction/subagents/agent-a49e8539611c5fe12.jsonl +0 -0
  117. {cctx_cli-1.4.0 → cctx_cli-1.5.0}/tests/fixtures/claude_code/with-compaction/with-compaction/subagents/agent-a49e8539611c5fe12.meta.json +0 -0
  118. {cctx_cli-1.4.0 → cctx_cli-1.5.0}/tests/fixtures/claude_code/with-compaction/with-compaction/subagents/agent-a7bb58f3fff2b3e8d.jsonl +0 -0
  119. {cctx_cli-1.4.0 → cctx_cli-1.5.0}/tests/fixtures/claude_code/with-compaction/with-compaction/subagents/agent-a7bb58f3fff2b3e8d.meta.json +0 -0
  120. {cctx_cli-1.4.0 → cctx_cli-1.5.0}/tests/fixtures/claude_code/with-compaction/with-compaction/subagents/agent-a92b48c0331195aac.jsonl +0 -0
  121. {cctx_cli-1.4.0 → cctx_cli-1.5.0}/tests/fixtures/claude_code/with-compaction/with-compaction/subagents/agent-a92b48c0331195aac.meta.json +0 -0
  122. {cctx_cli-1.4.0 → cctx_cli-1.5.0}/tests/fixtures/claude_code/with-compaction/with-compaction/subagents/agent-ab96c4264099694a9.jsonl +0 -0
  123. {cctx_cli-1.4.0 → cctx_cli-1.5.0}/tests/fixtures/claude_code/with-compaction/with-compaction/subagents/agent-ab96c4264099694a9.meta.json +0 -0
  124. {cctx_cli-1.4.0 → cctx_cli-1.5.0}/tests/fixtures/claude_code/with-compaction/with-compaction/subagents/agent-acb2895c5e34ffec0.jsonl +0 -0
  125. {cctx_cli-1.4.0 → cctx_cli-1.5.0}/tests/fixtures/claude_code/with-compaction/with-compaction/subagents/agent-acb2895c5e34ffec0.meta.json +0 -0
  126. {cctx_cli-1.4.0 → cctx_cli-1.5.0}/tests/fixtures/claude_code/with-compaction/with-compaction/subagents/agent-adb2302769938fb3f.jsonl +0 -0
  127. {cctx_cli-1.4.0 → cctx_cli-1.5.0}/tests/fixtures/claude_code/with-compaction/with-compaction/subagents/agent-adb2302769938fb3f.meta.json +0 -0
  128. {cctx_cli-1.4.0 → cctx_cli-1.5.0}/tests/fixtures/claude_code/with-compaction/with-compaction/subagents/agent-ae585eca15cb93b9c.jsonl +0 -0
  129. {cctx_cli-1.4.0 → cctx_cli-1.5.0}/tests/fixtures/claude_code/with-compaction/with-compaction/subagents/agent-ae585eca15cb93b9c.meta.json +0 -0
  130. {cctx_cli-1.4.0 → cctx_cli-1.5.0}/tests/fixtures/claude_code/with-compaction/with-compaction/subagents/agent-aec9c917feb903d67.jsonl +0 -0
  131. {cctx_cli-1.4.0 → cctx_cli-1.5.0}/tests/fixtures/claude_code/with-compaction/with-compaction/subagents/agent-aec9c917feb903d67.meta.json +0 -0
  132. {cctx_cli-1.4.0 → cctx_cli-1.5.0}/tests/fixtures/claude_code/with-compaction/with-compaction.jsonl +0 -0
  133. {cctx_cli-1.4.0 → cctx_cli-1.5.0}/tests/fixtures/claude_code/with-subagents/with-subagents/subagents/agent-a1a3a21aeb76bb0a9.jsonl +0 -0
  134. {cctx_cli-1.4.0 → cctx_cli-1.5.0}/tests/fixtures/claude_code/with-subagents/with-subagents/subagents/agent-a1a3a21aeb76bb0a9.meta.json +0 -0
  135. {cctx_cli-1.4.0 → cctx_cli-1.5.0}/tests/fixtures/claude_code/with-subagents/with-subagents/subagents/agent-aaa1d6ecc05a78442.jsonl +0 -0
  136. {cctx_cli-1.4.0 → cctx_cli-1.5.0}/tests/fixtures/claude_code/with-subagents/with-subagents/subagents/agent-aaa1d6ecc05a78442.meta.json +0 -0
  137. {cctx_cli-1.4.0 → cctx_cli-1.5.0}/tests/fixtures/claude_code/with-subagents/with-subagents/subagents/agent-af3c545ccd30036d2.jsonl +0 -0
  138. {cctx_cli-1.4.0 → cctx_cli-1.5.0}/tests/fixtures/claude_code/with-subagents/with-subagents/subagents/agent-af3c545ccd30036d2.meta.json +0 -0
  139. {cctx_cli-1.4.0 → cctx_cli-1.5.0}/tests/fixtures/claude_code/with-subagents/with-subagents/tool-results/btwp2bzro.txt +0 -0
  140. {cctx_cli-1.4.0 → cctx_cli-1.5.0}/tests/fixtures/claude_code/with-subagents/with-subagents/tool-results/byqjbgy4b.txt +0 -0
  141. {cctx_cli-1.4.0 → cctx_cli-1.5.0}/tests/fixtures/claude_code/with-subagents/with-subagents.jsonl +0 -0
  142. {cctx_cli-1.4.0 → cctx_cli-1.5.0}/tests/fixtures/claude_code/with-tool-results/with-tool-results/tool-results/bosbkda0h.txt +0 -0
  143. {cctx_cli-1.4.0 → cctx_cli-1.5.0}/tests/fixtures/claude_code/with-tool-results/with-tool-results.jsonl +0 -0
  144. {cctx_cli-1.4.0 → cctx_cli-1.5.0}/tests/fixtures/scrub.py +0 -0
  145. {cctx_cli-1.4.0 → cctx_cli-1.5.0}/tests/fixtures/synthetic/bookkeeping_only.jsonl +0 -0
  146. {cctx_cli-1.4.0 → cctx_cli-1.5.0}/tests/fixtures/synthetic/malformed_middle.jsonl +0 -0
  147. {cctx_cli-1.4.0 → cctx_cli-1.5.0}/tests/fixtures/synthetic/truncated_final_line.jsonl +0 -0
  148. {cctx_cli-1.4.0 → cctx_cli-1.5.0}/tests/fixtures/synthetic/unknown_attachment_shape.jsonl +0 -0
  149. {cctx_cli-1.4.0 → cctx_cli-1.5.0}/tests/fixtures/synthetic/unknown_type.jsonl +0 -0
  150. {cctx_cli-1.4.0 → cctx_cli-1.5.0}/tests/parsers/__init__.py +0 -0
  151. {cctx_cli-1.4.0 → cctx_cli-1.5.0}/tests/parsers/test_claude_code.py +0 -0
  152. {cctx_cli-1.4.0 → cctx_cli-1.5.0}/tests/parsers/test_claude_code_integration.py +0 -0
  153. {cctx_cli-1.4.0 → cctx_cli-1.5.0}/tests/recommender/__init__.py +0 -0
  154. {cctx_cli-1.4.0 → cctx_cli-1.5.0}/tests/recommender/test_claude_md.py +0 -0
  155. {cctx_cli-1.4.0 → cctx_cli-1.5.0}/tests/recommender/test_evidence.py +0 -0
  156. {cctx_cli-1.4.0 → cctx_cli-1.5.0}/tests/renderers/__init__.py +0 -0
  157. {cctx_cli-1.4.0 → cctx_cli-1.5.0}/tests/renderers/test_report.py +0 -0
  158. {cctx_cli-1.4.0 → cctx_cli-1.5.0}/tests/renderers/test_terminal_renderer_full.py +0 -0
  159. {cctx_cli-1.4.0 → cctx_cli-1.5.0}/tests/test_aggregate.py +0 -0
  160. {cctx_cli-1.4.0 → cctx_cli-1.5.0}/tests/test_cli.py +0 -0
  161. {cctx_cli-1.4.0 → cctx_cli-1.5.0}/tests/test_cli_export.py +0 -0
  162. {cctx_cli-1.4.0 → cctx_cli-1.5.0}/tests/test_discovery.py +0 -0
  163. {cctx_cli-1.4.0 → cctx_cli-1.5.0}/tests/test_github_summary.py +0 -0
  164. {cctx_cli-1.4.0 → cctx_cli-1.5.0}/tests/test_harvest.py +0 -0
  165. {cctx_cli-1.4.0 → cctx_cli-1.5.0}/tests/test_harvest_check.py +0 -0
  166. {cctx_cli-1.4.0 → cctx_cli-1.5.0}/tests/test_models.py +0 -0
  167. {cctx_cli-1.4.0 → cctx_cli-1.5.0}/tests/test_models_project_pattern.py +0 -0
  168. {cctx_cli-1.4.0 → cctx_cli-1.5.0}/tests/test_recommender.py +0 -0
  169. {cctx_cli-1.4.0 → cctx_cli-1.5.0}/tests/test_smoke.py +0 -0
  170. {cctx_cli-1.4.0 → cctx_cli-1.5.0}/tests/test_tokenizer.py +0 -0
  171. {cctx_cli-1.4.0 → cctx_cli-1.5.0}/tests/test_trace_tui.py +0 -0
@@ -2,6 +2,42 @@
2
2
 
3
3
  <!-- version list -->
4
4
 
5
+ ## v1.5.0 (2026-05-20)
6
+
7
+ ### Bug Fixes
8
+
9
+ - Agents.py — guard against non-list JSON, tighten patch targets
10
+ ([`c41c42c`](https://github.com/jacquardlabs/cctx/commit/c41c42cb366904fc332638b8f97ee042f17b45c2))
11
+
12
+ - Renderer — guard order, tmp_path fixtures, missing no-badge tests
13
+ ([`8f26afd`](https://github.com/jacquardlabs/cctx/commit/8f26afdf27c63641942b434e47a2fb7b24d38068))
14
+
15
+ - Watcher — hermetic tests, reuse _encode_path, rename clarity
16
+ ([`a7b52de`](https://github.com/jacquardlabs/cctx/commit/a7b52def47b13447d956476a0ec6782fe2d0247b))
17
+
18
+ ### Documentation
19
+
20
+ - Implementation plan for claude agents live integration
21
+ ([`2136adf`](https://github.com/jacquardlabs/cctx/commit/2136adf0697a695d27f438f0368e7bd5ba406e89))
22
+
23
+ - Spec for claude agents --json live session integration
24
+ ([`0df2381`](https://github.com/jacquardlabs/cctx/commit/0df23813a90c66e57d0d39c6b959859b89a5c057))
25
+
26
+ ### Features
27
+
28
+ - Add agents.py — live_sessions() via claude agents --json
29
+ ([`83b704f`](https://github.com/jacquardlabs/cctx/commit/83b704ffbe4303dbd316257a01eb0b59307c0e06))
30
+
31
+ - Cctx ls — pass live_statuses to renderer for live session badges
32
+ ([`65445d7`](https://github.com/jacquardlabs/cctx/commit/65445d75a213bf26401fe044a2409d2ce0efbcb1))
33
+
34
+ - Render_sessions/render_projects — live status badges via live_statuses param
35
+ ([`3d77687`](https://github.com/jacquardlabs/cctx/commit/3d776879c9ce03a622361c2b804e5986d40f37a1))
36
+
37
+ - Watcher — live session detection + early idle exit via claude agents --json
38
+ ([`a11481c`](https://github.com/jacquardlabs/cctx/commit/a11481c2126fead87e1f92bfecc7bf5ac3f39d1a))
39
+
40
+
5
41
  ## v1.4.0 (2026-05-20)
6
42
 
7
43
  ### Bug Fixes
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: cctx-cli
3
- Version: 1.4.0
3
+ Version: 1.5.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.4.0"
3
+ __version__ = "1.5.0"
@@ -0,0 +1,66 @@
1
+ """Live Claude Code agent query via `claude agents --json`.
2
+
3
+ Public API:
4
+ live_sessions() -> list[LiveSession]
5
+ """
6
+ from __future__ import annotations
7
+
8
+ import json
9
+ import subprocess
10
+ from dataclasses import dataclass
11
+ from datetime import datetime, timezone
12
+
13
+
14
+ @dataclass
15
+ class LiveSession:
16
+ session_id: str # matches JSONL filename stem in ~/.claude/projects/
17
+ cwd: str
18
+ status: str # "busy" | "idle"
19
+ pid: int
20
+ kind: str # "interactive" | "background"
21
+ started_at: datetime
22
+
23
+
24
+ def live_sessions() -> list[LiveSession]:
25
+ """Query `claude agents --json`. Returns [] on any failure."""
26
+ try:
27
+ result = subprocess.run(
28
+ ["claude", "agents", "--json"],
29
+ capture_output=True,
30
+ text=True,
31
+ timeout=2,
32
+ )
33
+ except FileNotFoundError:
34
+ return []
35
+ except subprocess.TimeoutExpired:
36
+ return []
37
+
38
+ if result.returncode != 0:
39
+ return []
40
+
41
+ try:
42
+ data = json.loads(result.stdout)
43
+ except json.JSONDecodeError:
44
+ return []
45
+
46
+ if not isinstance(data, list):
47
+ return []
48
+
49
+ sessions: list[LiveSession] = []
50
+ for item in data:
51
+ try:
52
+ sessions.append(
53
+ LiveSession(
54
+ session_id=item["sessionId"],
55
+ cwd=item["cwd"],
56
+ status=item.get("status", "unknown"),
57
+ pid=int(item["pid"]),
58
+ kind=item.get("kind", "interactive"),
59
+ started_at=datetime.fromtimestamp(
60
+ item["startedAt"] / 1000, tz=timezone.utc
61
+ ),
62
+ )
63
+ )
64
+ except (KeyError, TypeError, ValueError):
65
+ continue
66
+ return sessions
@@ -19,6 +19,7 @@ from typing import IO
19
19
  import rich_click as click
20
20
 
21
21
  from cctx import diagnostician
22
+ from cctx.agents import live_sessions as _live_sessions
22
23
  from cctx.diagnostician import aggregate
23
24
  from cctx.diagnostician.patterns import project_specific
24
25
  from cctx.discovery import complete_project as _complete_project
@@ -196,9 +197,11 @@ def ls(project: Path | None) -> None:
196
197
  """
197
198
  from cctx.discovery import ProjectInfo, find_project_dir, list_projects, list_sessions
198
199
 
200
+ live_statuses = {s.session_id: s.status for s in _live_sessions()}
201
+
199
202
  if project is None:
200
203
  projects = list_projects()
201
- render_projects(projects)
204
+ render_projects(projects, live_statuses=live_statuses)
202
205
  else:
203
206
  cwd = project if project.is_dir() else project.parent
204
207
  project_dir = find_project_dir(cwd)
@@ -213,7 +216,7 @@ def ls(project: Path | None) -> None:
213
216
  display_name=str(cwd).replace(str(Path.home()), "~"),
214
217
  sessions=sessions,
215
218
  )
216
- render_sessions(info)
219
+ render_sessions(info, live_statuses=live_statuses)
217
220
 
218
221
 
219
222
  @cli.command()
@@ -3,8 +3,8 @@
3
3
  render_diagnosis(diagnosis, console=None) -> None
4
4
  render_aggregate(report, console=None) -> None
5
5
  render_harvest_results(results, dry_run=False, console=None) -> None
6
- render_projects(projects, console=None) -> None
7
- render_sessions(project, console=None) -> None
6
+ render_projects(projects, live_statuses=None, console=None) -> None
7
+ render_sessions(project, live_statuses=None, console=None) -> None
8
8
 
9
9
  Uses rich for formatting. Accepts an optional Console for testing.
10
10
  """
@@ -294,25 +294,44 @@ def render_harvest_results(
294
294
  con.print(f"Applied {applied_count} patch(es).")
295
295
 
296
296
 
297
- def render_projects(projects: list[ProjectInfo], *, console: Console | None = None) -> None:
297
+ def render_projects(
298
+ projects: list[ProjectInfo],
299
+ *,
300
+ live_statuses: dict[str, str] | None = None,
301
+ console: Console | None = None,
302
+ ) -> None:
298
303
  con = console or _default_console()
299
304
 
300
305
  if not projects:
301
306
  con.print("No projects found in ~/.claude/projects/.")
302
307
  return
303
308
 
309
+ _live = live_statuses or {}
310
+ live_project_ids: set[str] = {
311
+ proj.project_dir.name
312
+ for proj in projects
313
+ for s in proj.sessions
314
+ if s.session_id in _live
315
+ }
316
+
304
317
  con.print(Rule("cctx — projects"))
305
318
  table = Table(show_header=True, box=None, pad_edge=False, show_edge=False)
306
319
  table.add_column("Project", style="bold")
307
320
  table.add_column("Sessions", justify="right", style="dim")
308
321
  table.add_column("Last session", style="dim")
322
+ table.add_column("Status")
309
323
 
310
324
  for proj in projects:
311
325
  last = proj.latest_time.strftime("%Y-%m-%d") if proj.latest_time else "—"
326
+ if proj.project_dir.name in live_project_ids:
327
+ status_cell = Text("● live", style="green bold")
328
+ else:
329
+ status_cell = Text("")
312
330
  table.add_row(
313
331
  proj.display_name,
314
332
  str(proj.session_count),
315
333
  last,
334
+ status_cell,
316
335
  )
317
336
  con.print(table)
318
337
  con.print()
@@ -326,8 +345,14 @@ def render_projects(projects: list[ProjectInfo], *, console: Console | None = No
326
345
  )
327
346
 
328
347
 
329
- def render_sessions(project: ProjectInfo, *, console: Console | None = None) -> None:
348
+ def render_sessions(
349
+ project: ProjectInfo,
350
+ *,
351
+ live_statuses: dict[str, str] | None = None,
352
+ console: Console | None = None,
353
+ ) -> None:
330
354
  con = console or _default_console()
355
+ _live = live_statuses or {}
331
356
 
332
357
  con.print(Rule(f"cctx — {project.display_name}"))
333
358
  if not project.sessions:
@@ -339,14 +364,20 @@ def render_sessions(project: ProjectInfo, *, console: Console | None = None) ->
339
364
  table.add_column("Date", style="dim")
340
365
  table.add_column("Branch", style="dim")
341
366
  table.add_column("Path", style="dim")
367
+ table.add_column("Status")
342
368
 
343
369
  for s in project.sessions:
344
370
  date_str = s.start_time.strftime("%Y-%m-%d %H:%M") if s.start_time else "—"
371
+ if s.session_id in _live:
372
+ status_cell = Text(f"● {_live[s.session_id]}", style="green bold")
373
+ else:
374
+ status_cell = Text("")
345
375
  table.add_row(
346
376
  s.session_id[:8],
347
377
  date_str,
348
378
  s.git_branch or "—",
349
379
  str(s.path),
380
+ status_cell,
350
381
  )
351
382
  con.print(table)
352
383
  con.print()
@@ -4,7 +4,7 @@ Public API:
4
4
  watch(target) -> None
5
5
 
6
6
  Layering rules (MUST respect):
7
- - Imports parser and diagnostician only.
7
+ - Imports parser, diagnostician, and agents only.
8
8
  - Does NOT import renderers, click, rich, or anthropic.
9
9
  - Uses a compact single-line formatter defined here (not imported from renderers).
10
10
  """
@@ -16,6 +16,7 @@ import time
16
16
  from pathlib import Path
17
17
 
18
18
  from cctx import diagnostician
19
+ from cctx.agents import live_sessions
19
20
  from cctx.models import KIND_LABEL, Finding, FindingKind
20
21
 
21
22
  _POLL_INTERVAL = 1.0 # seconds between size checks
@@ -34,11 +35,22 @@ def _format_finding(f: Finding) -> str:
34
35
 
35
36
 
36
37
  def _find_active_session(project_dir: Path) -> Path | None:
37
- """Return the most recently modified JSONL in project_dir, or None.
38
+ """Return the JSONL for the active session in project_dir.
38
39
 
39
- Uses st_mtime (not embedded session start_time) so that a currently-active
40
- session sorts above older completed sessions regardless of their timestamps.
40
+ Prefers the live session from `claude agents --json` when available;
41
+ falls back to most-recently-modified JSONL.
41
42
  """
43
+ from cctx.discovery import _encode_path
44
+
45
+ encoded_name = project_dir.name
46
+ for live in live_sessions():
47
+ if live.cwd:
48
+ live_encoded = _encode_path(Path(live.cwd))
49
+ if live_encoded == encoded_name:
50
+ candidate = project_dir / f"{live.session_id}.jsonl"
51
+ if candidate.exists():
52
+ return candidate
53
+
42
54
  candidates = list(project_dir.glob("*.jsonl"))
43
55
  if not candidates:
44
56
  return None
@@ -69,6 +81,8 @@ def _tail(session_path: Path) -> int:
69
81
  seen_keys: set[tuple[FindingKind, int]] = set()
70
82
  last_size = 0
71
83
  idle_since: float | None = None
84
+ session_id = session_path.stem
85
+ claude_is_responsive = False # True once we've confirmed claude is available
72
86
 
73
87
  print(f"Watching {session_path.name} … Ctrl+C to stop.", flush=True)
74
88
 
@@ -99,13 +113,25 @@ def _tail(session_path: Path) -> int:
99
113
  now = time.monotonic()
100
114
  if idle_since is None:
101
115
  idle_since = now
102
- elif now - idle_since >= _IDLE_TIMEOUT:
103
- print(
104
- f"\nSession idle for {_IDLE_TIMEOUT:.0f}s analysis complete. "
105
- f"Findings detected: {len(seen_keys)}",
106
- flush=True,
107
- )
108
- return len(seen_keys)
116
+ else:
117
+ live = live_sessions()
118
+ live_ids = {s.session_id for s in live}
119
+ if live:
120
+ claude_is_responsive = True
121
+ if claude_is_responsive and session_id not in live_ids:
122
+ print(
123
+ f"\nSession ended — analysis complete. "
124
+ f"Findings detected: {len(seen_keys)}",
125
+ flush=True,
126
+ )
127
+ return len(seen_keys)
128
+ if now - idle_since >= _IDLE_TIMEOUT:
129
+ print(
130
+ f"\nSession idle for {_IDLE_TIMEOUT:.0f}s — analysis complete. "
131
+ f"Findings detected: {len(seen_keys)}",
132
+ flush=True,
133
+ )
134
+ return len(seen_keys)
109
135
 
110
136
  time.sleep(_POLL_INTERVAL)
111
137