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.
- {cctx_cli-1.4.0 → cctx_cli-1.5.0}/CHANGELOG.md +36 -0
- {cctx_cli-1.4.0 → cctx_cli-1.5.0}/PKG-INFO +1 -1
- {cctx_cli-1.4.0 → cctx_cli-1.5.0}/cctx/__init__.py +1 -1
- cctx_cli-1.5.0/cctx/agents.py +66 -0
- {cctx_cli-1.4.0 → cctx_cli-1.5.0}/cctx/cli.py +5 -2
- {cctx_cli-1.4.0 → cctx_cli-1.5.0}/cctx/renderers/terminal.py +35 -4
- {cctx_cli-1.4.0 → cctx_cli-1.5.0}/cctx/watcher.py +37 -11
- cctx_cli-1.5.0/docs/superpowers/plans/2026-05-19-claude-agents-live-integration.md +839 -0
- cctx_cli-1.5.0/docs/superpowers/specs/2026-05-19-claude-agents-live-integration-design.md +128 -0
- {cctx_cli-1.4.0 → cctx_cli-1.5.0}/pyproject.toml +1 -1
- cctx_cli-1.5.0/tests/test_agents.py +128 -0
- {cctx_cli-1.4.0 → cctx_cli-1.5.0}/tests/test_terminal_renderer.py +154 -0
- {cctx_cli-1.4.0 → cctx_cli-1.5.0}/tests/test_watcher.py +101 -0
- {cctx_cli-1.4.0 → cctx_cli-1.5.0}/.github/workflows/ci.yml +0 -0
- {cctx_cli-1.4.0 → cctx_cli-1.5.0}/.github/workflows/publish.yml +0 -0
- {cctx_cli-1.4.0 → cctx_cli-1.5.0}/.github/workflows/release.yml +0 -0
- {cctx_cli-1.4.0 → cctx_cli-1.5.0}/.gitignore +0 -0
- {cctx_cli-1.4.0 → cctx_cli-1.5.0}/CLAUDE.md +0 -0
- {cctx_cli-1.4.0 → cctx_cli-1.5.0}/DESIGN.md +0 -0
- {cctx_cli-1.4.0 → cctx_cli-1.5.0}/PRODUCT.md +0 -0
- {cctx_cli-1.4.0 → cctx_cli-1.5.0}/README.md +0 -0
- {cctx_cli-1.4.0 → cctx_cli-1.5.0}/action.yml +0 -0
- {cctx_cli-1.4.0 → cctx_cli-1.5.0}/cctx/diagnostician/__init__.py +0 -0
- {cctx_cli-1.4.0 → cctx_cli-1.5.0}/cctx/diagnostician/aggregate.py +0 -0
- {cctx_cli-1.4.0 → cctx_cli-1.5.0}/cctx/diagnostician/inflection.py +0 -0
- {cctx_cli-1.4.0 → cctx_cli-1.5.0}/cctx/diagnostician/patterns/__init__.py +0 -0
- {cctx_cli-1.4.0 → cctx_cli-1.5.0}/cctx/diagnostician/patterns/dead_end.py +0 -0
- {cctx_cli-1.4.0 → cctx_cli-1.5.0}/cctx/diagnostician/patterns/project_specific.py +0 -0
- {cctx_cli-1.4.0 → cctx_cli-1.5.0}/cctx/diagnostician/patterns/retry_loop.py +0 -0
- {cctx_cli-1.4.0 → cctx_cli-1.5.0}/cctx/diagnostician/patterns/scope_creep.py +0 -0
- {cctx_cli-1.4.0 → cctx_cli-1.5.0}/cctx/diagnostician/patterns/stale_context.py +0 -0
- {cctx_cli-1.4.0 → cctx_cli-1.5.0}/cctx/diagnostician/patterns/tool_thrash.py +0 -0
- {cctx_cli-1.4.0 → cctx_cli-1.5.0}/cctx/discovery.py +0 -0
- {cctx_cli-1.4.0 → cctx_cli-1.5.0}/cctx/exporters/__init__.py +0 -0
- {cctx_cli-1.4.0 → cctx_cli-1.5.0}/cctx/exporters/csv.py +0 -0
- {cctx_cli-1.4.0 → cctx_cli-1.5.0}/cctx/exporters/json.py +0 -0
- {cctx_cli-1.4.0 → cctx_cli-1.5.0}/cctx/exporters/jsonl.py +0 -0
- {cctx_cli-1.4.0 → cctx_cli-1.5.0}/cctx/harvest.py +0 -0
- {cctx_cli-1.4.0 → cctx_cli-1.5.0}/cctx/models.py +0 -0
- {cctx_cli-1.4.0 → cctx_cli-1.5.0}/cctx/parsers/__init__.py +0 -0
- {cctx_cli-1.4.0 → cctx_cli-1.5.0}/cctx/parsers/claude_code.py +0 -0
- {cctx_cli-1.4.0 → cctx_cli-1.5.0}/cctx/pricing.py +0 -0
- {cctx_cli-1.4.0 → cctx_cli-1.5.0}/cctx/recommender/__init__.py +0 -0
- {cctx_cli-1.4.0 → cctx_cli-1.5.0}/cctx/recommender/claude_md.py +0 -0
- {cctx_cli-1.4.0 → cctx_cli-1.5.0}/cctx/recommender/evidence.py +0 -0
- {cctx_cli-1.4.0 → cctx_cli-1.5.0}/cctx/renderers/__init__.py +0 -0
- {cctx_cli-1.4.0 → cctx_cli-1.5.0}/cctx/renderers/github.py +0 -0
- {cctx_cli-1.4.0 → cctx_cli-1.5.0}/cctx/renderers/report.py +0 -0
- {cctx_cli-1.4.0 → cctx_cli-1.5.0}/cctx/renderers/templates/autopsy.html.j2 +0 -0
- {cctx_cli-1.4.0 → cctx_cli-1.5.0}/cctx/renderers/trace_tui.py +0 -0
- {cctx_cli-1.4.0 → cctx_cli-1.5.0}/cctx/tokenizer.py +0 -0
- {cctx_cli-1.4.0 → cctx_cli-1.5.0}/cctx-project-brief.md +0 -0
- {cctx_cli-1.4.0 → cctx_cli-1.5.0}/demo.gif +0 -0
- {cctx_cli-1.4.0 → cctx_cli-1.5.0}/demo.tape +0 -0
- {cctx_cli-1.4.0 → cctx_cli-1.5.0}/docs/health-reviews/2026-05-15-deep-review-summary.md +0 -0
- {cctx_cli-1.4.0 → cctx_cli-1.5.0}/docs/health-reviews/2026-05-15-health-review.md +0 -0
- {cctx_cli-1.4.0 → cctx_cli-1.5.0}/docs/product-reviews/2026-05-15-product-review.md +0 -0
- {cctx_cli-1.4.0 → cctx_cli-1.5.0}/docs/superpowers/plans/2026-05-12-claude-code-parser.md +0 -0
- {cctx_cli-1.4.0 → cctx_cli-1.5.0}/docs/superpowers/plans/2026-05-14-autopsy-v0.md +0 -0
- {cctx_cli-1.4.0 → cctx_cli-1.5.0}/docs/superpowers/plans/2026-05-16-readme-pypi-release.md +0 -0
- {cctx_cli-1.4.0 → cctx_cli-1.5.0}/docs/superpowers/plans/2026-05-17-harvest-check-depth.md +0 -0
- {cctx_cli-1.4.0 → cctx_cli-1.5.0}/docs/superpowers/plans/2026-05-17-project-pattern-detection.md +0 -0
- {cctx_cli-1.4.0 → cctx_cli-1.5.0}/docs/superpowers/specs/2026-05-12-claude-code-parser-design.md +0 -0
- {cctx_cli-1.4.0 → cctx_cli-1.5.0}/docs/superpowers/specs/2026-05-14-autopsy-design.md +0 -0
- {cctx_cli-1.4.0 → cctx_cli-1.5.0}/docs/superpowers/specs/2026-05-14-harvest-design.md +0 -0
- {cctx_cli-1.4.0 → cctx_cli-1.5.0}/docs/superpowers/specs/2026-05-14-trace-tui-design.md +0 -0
- {cctx_cli-1.4.0 → cctx_cli-1.5.0}/docs/superpowers/specs/2026-05-16-readme-pypi-release-design.md +0 -0
- {cctx_cli-1.4.0 → cctx_cli-1.5.0}/docs/superpowers/specs/2026-05-17-harvest-check-depth-design.md +0 -0
- {cctx_cli-1.4.0 → cctx_cli-1.5.0}/docs/superpowers/specs/2026-05-17-project-pattern-detection-design.md +0 -0
- {cctx_cli-1.4.0 → cctx_cli-1.5.0}/tests/__init__.py +0 -0
- {cctx_cli-1.4.0 → cctx_cli-1.5.0}/tests/conftest.py +0 -0
- {cctx_cli-1.4.0 → cctx_cli-1.5.0}/tests/diagnostician/__init__.py +0 -0
- {cctx_cli-1.4.0 → cctx_cli-1.5.0}/tests/diagnostician/conftest.py +0 -0
- {cctx_cli-1.4.0 → cctx_cli-1.5.0}/tests/diagnostician/test_dead_end.py +0 -0
- {cctx_cli-1.4.0 → cctx_cli-1.5.0}/tests/diagnostician/test_inflection.py +0 -0
- {cctx_cli-1.4.0 → cctx_cli-1.5.0}/tests/diagnostician/test_orchestrator.py +0 -0
- {cctx_cli-1.4.0 → cctx_cli-1.5.0}/tests/diagnostician/test_project_specific.py +0 -0
- {cctx_cli-1.4.0 → cctx_cli-1.5.0}/tests/diagnostician/test_retry_loop.py +0 -0
- {cctx_cli-1.4.0 → cctx_cli-1.5.0}/tests/diagnostician/test_scope_creep.py +0 -0
- {cctx_cli-1.4.0 → cctx_cli-1.5.0}/tests/diagnostician/test_stale_context.py +0 -0
- {cctx_cli-1.4.0 → cctx_cli-1.5.0}/tests/diagnostician/test_tool_thrash.py +0 -0
- {cctx_cli-1.4.0 → cctx_cli-1.5.0}/tests/exporters/__init__.py +0 -0
- {cctx_cli-1.4.0 → cctx_cli-1.5.0}/tests/exporters/test_csv.py +0 -0
- {cctx_cli-1.4.0 → cctx_cli-1.5.0}/tests/exporters/test_jsonl.py +0 -0
- {cctx_cli-1.4.0 → cctx_cli-1.5.0}/tests/fixtures/claude_code/README.md +0 -0
- {cctx_cli-1.4.0 → cctx_cli-1.5.0}/tests/fixtures/claude_code/short-clean/short-clean.jsonl +0 -0
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {cctx_cli-1.4.0 → cctx_cli-1.5.0}/tests/fixtures/claude_code/with-attachments/with-attachments.jsonl +0 -0
- {cctx_cli-1.4.0 → cctx_cli-1.5.0}/tests/fixtures/claude_code/with-compaction/with-compaction/subagents/agent-a051d9c9a6b2f5cc3.jsonl +0 -0
- {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
- {cctx_cli-1.4.0 → cctx_cli-1.5.0}/tests/fixtures/claude_code/with-compaction/with-compaction/subagents/agent-a171f16f4e65cfe75.jsonl +0 -0
- {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
- {cctx_cli-1.4.0 → cctx_cli-1.5.0}/tests/fixtures/claude_code/with-compaction/with-compaction/subagents/agent-a1b77fea2c0a2269b.jsonl +0 -0
- {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
- {cctx_cli-1.4.0 → cctx_cli-1.5.0}/tests/fixtures/claude_code/with-compaction/with-compaction/subagents/agent-a20da4c01a54acca8.jsonl +0 -0
- {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
- {cctx_cli-1.4.0 → cctx_cli-1.5.0}/tests/fixtures/claude_code/with-compaction/with-compaction/subagents/agent-a3c82739b1383fb14.jsonl +0 -0
- {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
- {cctx_cli-1.4.0 → cctx_cli-1.5.0}/tests/fixtures/claude_code/with-compaction/with-compaction/subagents/agent-a49e8539611c5fe12.jsonl +0 -0
- {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
- {cctx_cli-1.4.0 → cctx_cli-1.5.0}/tests/fixtures/claude_code/with-compaction/with-compaction/subagents/agent-a7bb58f3fff2b3e8d.jsonl +0 -0
- {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
- {cctx_cli-1.4.0 → cctx_cli-1.5.0}/tests/fixtures/claude_code/with-compaction/with-compaction/subagents/agent-a92b48c0331195aac.jsonl +0 -0
- {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
- {cctx_cli-1.4.0 → cctx_cli-1.5.0}/tests/fixtures/claude_code/with-compaction/with-compaction/subagents/agent-ab96c4264099694a9.jsonl +0 -0
- {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
- {cctx_cli-1.4.0 → cctx_cli-1.5.0}/tests/fixtures/claude_code/with-compaction/with-compaction/subagents/agent-acb2895c5e34ffec0.jsonl +0 -0
- {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
- {cctx_cli-1.4.0 → cctx_cli-1.5.0}/tests/fixtures/claude_code/with-compaction/with-compaction/subagents/agent-adb2302769938fb3f.jsonl +0 -0
- {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
- {cctx_cli-1.4.0 → cctx_cli-1.5.0}/tests/fixtures/claude_code/with-compaction/with-compaction/subagents/agent-ae585eca15cb93b9c.jsonl +0 -0
- {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
- {cctx_cli-1.4.0 → cctx_cli-1.5.0}/tests/fixtures/claude_code/with-compaction/with-compaction/subagents/agent-aec9c917feb903d67.jsonl +0 -0
- {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
- {cctx_cli-1.4.0 → cctx_cli-1.5.0}/tests/fixtures/claude_code/with-compaction/with-compaction.jsonl +0 -0
- {cctx_cli-1.4.0 → cctx_cli-1.5.0}/tests/fixtures/claude_code/with-subagents/with-subagents/subagents/agent-a1a3a21aeb76bb0a9.jsonl +0 -0
- {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
- {cctx_cli-1.4.0 → cctx_cli-1.5.0}/tests/fixtures/claude_code/with-subagents/with-subagents/subagents/agent-aaa1d6ecc05a78442.jsonl +0 -0
- {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
- {cctx_cli-1.4.0 → cctx_cli-1.5.0}/tests/fixtures/claude_code/with-subagents/with-subagents/subagents/agent-af3c545ccd30036d2.jsonl +0 -0
- {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
- {cctx_cli-1.4.0 → cctx_cli-1.5.0}/tests/fixtures/claude_code/with-subagents/with-subagents/tool-results/btwp2bzro.txt +0 -0
- {cctx_cli-1.4.0 → cctx_cli-1.5.0}/tests/fixtures/claude_code/with-subagents/with-subagents/tool-results/byqjbgy4b.txt +0 -0
- {cctx_cli-1.4.0 → cctx_cli-1.5.0}/tests/fixtures/claude_code/with-subagents/with-subagents.jsonl +0 -0
- {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
- {cctx_cli-1.4.0 → cctx_cli-1.5.0}/tests/fixtures/claude_code/with-tool-results/with-tool-results.jsonl +0 -0
- {cctx_cli-1.4.0 → cctx_cli-1.5.0}/tests/fixtures/scrub.py +0 -0
- {cctx_cli-1.4.0 → cctx_cli-1.5.0}/tests/fixtures/synthetic/bookkeeping_only.jsonl +0 -0
- {cctx_cli-1.4.0 → cctx_cli-1.5.0}/tests/fixtures/synthetic/malformed_middle.jsonl +0 -0
- {cctx_cli-1.4.0 → cctx_cli-1.5.0}/tests/fixtures/synthetic/truncated_final_line.jsonl +0 -0
- {cctx_cli-1.4.0 → cctx_cli-1.5.0}/tests/fixtures/synthetic/unknown_attachment_shape.jsonl +0 -0
- {cctx_cli-1.4.0 → cctx_cli-1.5.0}/tests/fixtures/synthetic/unknown_type.jsonl +0 -0
- {cctx_cli-1.4.0 → cctx_cli-1.5.0}/tests/parsers/__init__.py +0 -0
- {cctx_cli-1.4.0 → cctx_cli-1.5.0}/tests/parsers/test_claude_code.py +0 -0
- {cctx_cli-1.4.0 → cctx_cli-1.5.0}/tests/parsers/test_claude_code_integration.py +0 -0
- {cctx_cli-1.4.0 → cctx_cli-1.5.0}/tests/recommender/__init__.py +0 -0
- {cctx_cli-1.4.0 → cctx_cli-1.5.0}/tests/recommender/test_claude_md.py +0 -0
- {cctx_cli-1.4.0 → cctx_cli-1.5.0}/tests/recommender/test_evidence.py +0 -0
- {cctx_cli-1.4.0 → cctx_cli-1.5.0}/tests/renderers/__init__.py +0 -0
- {cctx_cli-1.4.0 → cctx_cli-1.5.0}/tests/renderers/test_report.py +0 -0
- {cctx_cli-1.4.0 → cctx_cli-1.5.0}/tests/renderers/test_terminal_renderer_full.py +0 -0
- {cctx_cli-1.4.0 → cctx_cli-1.5.0}/tests/test_aggregate.py +0 -0
- {cctx_cli-1.4.0 → cctx_cli-1.5.0}/tests/test_cli.py +0 -0
- {cctx_cli-1.4.0 → cctx_cli-1.5.0}/tests/test_cli_export.py +0 -0
- {cctx_cli-1.4.0 → cctx_cli-1.5.0}/tests/test_discovery.py +0 -0
- {cctx_cli-1.4.0 → cctx_cli-1.5.0}/tests/test_github_summary.py +0 -0
- {cctx_cli-1.4.0 → cctx_cli-1.5.0}/tests/test_harvest.py +0 -0
- {cctx_cli-1.4.0 → cctx_cli-1.5.0}/tests/test_harvest_check.py +0 -0
- {cctx_cli-1.4.0 → cctx_cli-1.5.0}/tests/test_models.py +0 -0
- {cctx_cli-1.4.0 → cctx_cli-1.5.0}/tests/test_models_project_pattern.py +0 -0
- {cctx_cli-1.4.0 → cctx_cli-1.5.0}/tests/test_recommender.py +0 -0
- {cctx_cli-1.4.0 → cctx_cli-1.5.0}/tests/test_smoke.py +0 -0
- {cctx_cli-1.4.0 → cctx_cli-1.5.0}/tests/test_tokenizer.py +0 -0
- {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
|
|
@@ -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(
|
|
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(
|
|
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
|
|
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
|
|
38
|
+
"""Return the JSONL for the active session in project_dir.
|
|
38
39
|
|
|
39
|
-
|
|
40
|
-
|
|
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
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
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
|
|