cctx-cli 1.4.0__tar.gz → 1.5.1__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.1}/CHANGELOG.md +51 -0
- {cctx_cli-1.4.0 → cctx_cli-1.5.1}/PKG-INFO +1 -1
- {cctx_cli-1.4.0 → cctx_cli-1.5.1}/PRODUCT.md +23 -12
- {cctx_cli-1.4.0 → cctx_cli-1.5.1}/cctx/__init__.py +1 -1
- cctx_cli-1.5.1/cctx/agents.py +66 -0
- {cctx_cli-1.4.0 → cctx_cli-1.5.1}/cctx/cli.py +5 -2
- {cctx_cli-1.4.0 → cctx_cli-1.5.1}/cctx/recommender/claude_md.py +21 -3
- {cctx_cli-1.4.0 → cctx_cli-1.5.1}/cctx/renderers/terminal.py +35 -4
- {cctx_cli-1.4.0 → cctx_cli-1.5.1}/cctx/watcher.py +37 -11
- cctx_cli-1.5.1/docs/product-reviews/2026-06-09-product-review.md +186 -0
- cctx_cli-1.5.1/docs/superpowers/plans/2026-05-19-claude-agents-live-integration.md +839 -0
- cctx_cli-1.5.1/docs/superpowers/specs/2026-05-19-claude-agents-live-integration-design.md +128 -0
- cctx_cli-1.5.1/docs/superpowers/specs/2026-06-09-cross-agent-emit-design.md +198 -0
- {cctx_cli-1.4.0 → cctx_cli-1.5.1}/pyproject.toml +1 -1
- cctx_cli-1.5.1/tests/test_agents.py +128 -0
- cctx_cli-1.5.1/tests/test_recommender.py +125 -0
- {cctx_cli-1.4.0 → cctx_cli-1.5.1}/tests/test_terminal_renderer.py +154 -0
- {cctx_cli-1.4.0 → cctx_cli-1.5.1}/tests/test_watcher.py +101 -0
- cctx_cli-1.4.0/tests/test_recommender.py +0 -56
- {cctx_cli-1.4.0 → cctx_cli-1.5.1}/.github/workflows/ci.yml +0 -0
- {cctx_cli-1.4.0 → cctx_cli-1.5.1}/.github/workflows/publish.yml +0 -0
- {cctx_cli-1.4.0 → cctx_cli-1.5.1}/.github/workflows/release.yml +0 -0
- {cctx_cli-1.4.0 → cctx_cli-1.5.1}/.gitignore +0 -0
- {cctx_cli-1.4.0 → cctx_cli-1.5.1}/CLAUDE.md +0 -0
- {cctx_cli-1.4.0 → cctx_cli-1.5.1}/DESIGN.md +0 -0
- {cctx_cli-1.4.0 → cctx_cli-1.5.1}/README.md +0 -0
- {cctx_cli-1.4.0 → cctx_cli-1.5.1}/action.yml +0 -0
- {cctx_cli-1.4.0 → cctx_cli-1.5.1}/cctx/diagnostician/__init__.py +0 -0
- {cctx_cli-1.4.0 → cctx_cli-1.5.1}/cctx/diagnostician/aggregate.py +0 -0
- {cctx_cli-1.4.0 → cctx_cli-1.5.1}/cctx/diagnostician/inflection.py +0 -0
- {cctx_cli-1.4.0 → cctx_cli-1.5.1}/cctx/diagnostician/patterns/__init__.py +0 -0
- {cctx_cli-1.4.0 → cctx_cli-1.5.1}/cctx/diagnostician/patterns/dead_end.py +0 -0
- {cctx_cli-1.4.0 → cctx_cli-1.5.1}/cctx/diagnostician/patterns/project_specific.py +0 -0
- {cctx_cli-1.4.0 → cctx_cli-1.5.1}/cctx/diagnostician/patterns/retry_loop.py +0 -0
- {cctx_cli-1.4.0 → cctx_cli-1.5.1}/cctx/diagnostician/patterns/scope_creep.py +0 -0
- {cctx_cli-1.4.0 → cctx_cli-1.5.1}/cctx/diagnostician/patterns/stale_context.py +0 -0
- {cctx_cli-1.4.0 → cctx_cli-1.5.1}/cctx/diagnostician/patterns/tool_thrash.py +0 -0
- {cctx_cli-1.4.0 → cctx_cli-1.5.1}/cctx/discovery.py +0 -0
- {cctx_cli-1.4.0 → cctx_cli-1.5.1}/cctx/exporters/__init__.py +0 -0
- {cctx_cli-1.4.0 → cctx_cli-1.5.1}/cctx/exporters/csv.py +0 -0
- {cctx_cli-1.4.0 → cctx_cli-1.5.1}/cctx/exporters/json.py +0 -0
- {cctx_cli-1.4.0 → cctx_cli-1.5.1}/cctx/exporters/jsonl.py +0 -0
- {cctx_cli-1.4.0 → cctx_cli-1.5.1}/cctx/harvest.py +0 -0
- {cctx_cli-1.4.0 → cctx_cli-1.5.1}/cctx/models.py +0 -0
- {cctx_cli-1.4.0 → cctx_cli-1.5.1}/cctx/parsers/__init__.py +0 -0
- {cctx_cli-1.4.0 → cctx_cli-1.5.1}/cctx/parsers/claude_code.py +0 -0
- {cctx_cli-1.4.0 → cctx_cli-1.5.1}/cctx/pricing.py +0 -0
- {cctx_cli-1.4.0 → cctx_cli-1.5.1}/cctx/recommender/__init__.py +0 -0
- {cctx_cli-1.4.0 → cctx_cli-1.5.1}/cctx/recommender/evidence.py +0 -0
- {cctx_cli-1.4.0 → cctx_cli-1.5.1}/cctx/renderers/__init__.py +0 -0
- {cctx_cli-1.4.0 → cctx_cli-1.5.1}/cctx/renderers/github.py +0 -0
- {cctx_cli-1.4.0 → cctx_cli-1.5.1}/cctx/renderers/report.py +0 -0
- {cctx_cli-1.4.0 → cctx_cli-1.5.1}/cctx/renderers/templates/autopsy.html.j2 +0 -0
- {cctx_cli-1.4.0 → cctx_cli-1.5.1}/cctx/renderers/trace_tui.py +0 -0
- {cctx_cli-1.4.0 → cctx_cli-1.5.1}/cctx/tokenizer.py +0 -0
- {cctx_cli-1.4.0 → cctx_cli-1.5.1}/cctx-project-brief.md +0 -0
- {cctx_cli-1.4.0 → cctx_cli-1.5.1}/demo.gif +0 -0
- {cctx_cli-1.4.0 → cctx_cli-1.5.1}/demo.tape +0 -0
- {cctx_cli-1.4.0 → cctx_cli-1.5.1}/docs/health-reviews/2026-05-15-deep-review-summary.md +0 -0
- {cctx_cli-1.4.0 → cctx_cli-1.5.1}/docs/health-reviews/2026-05-15-health-review.md +0 -0
- {cctx_cli-1.4.0 → cctx_cli-1.5.1}/docs/product-reviews/2026-05-15-product-review.md +0 -0
- {cctx_cli-1.4.0 → cctx_cli-1.5.1}/docs/superpowers/plans/2026-05-12-claude-code-parser.md +0 -0
- {cctx_cli-1.4.0 → cctx_cli-1.5.1}/docs/superpowers/plans/2026-05-14-autopsy-v0.md +0 -0
- {cctx_cli-1.4.0 → cctx_cli-1.5.1}/docs/superpowers/plans/2026-05-16-readme-pypi-release.md +0 -0
- {cctx_cli-1.4.0 → cctx_cli-1.5.1}/docs/superpowers/plans/2026-05-17-harvest-check-depth.md +0 -0
- {cctx_cli-1.4.0 → cctx_cli-1.5.1}/docs/superpowers/plans/2026-05-17-project-pattern-detection.md +0 -0
- {cctx_cli-1.4.0 → cctx_cli-1.5.1}/docs/superpowers/specs/2026-05-12-claude-code-parser-design.md +0 -0
- {cctx_cli-1.4.0 → cctx_cli-1.5.1}/docs/superpowers/specs/2026-05-14-autopsy-design.md +0 -0
- {cctx_cli-1.4.0 → cctx_cli-1.5.1}/docs/superpowers/specs/2026-05-14-harvest-design.md +0 -0
- {cctx_cli-1.4.0 → cctx_cli-1.5.1}/docs/superpowers/specs/2026-05-14-trace-tui-design.md +0 -0
- {cctx_cli-1.4.0 → cctx_cli-1.5.1}/docs/superpowers/specs/2026-05-16-readme-pypi-release-design.md +0 -0
- {cctx_cli-1.4.0 → cctx_cli-1.5.1}/docs/superpowers/specs/2026-05-17-harvest-check-depth-design.md +0 -0
- {cctx_cli-1.4.0 → cctx_cli-1.5.1}/docs/superpowers/specs/2026-05-17-project-pattern-detection-design.md +0 -0
- {cctx_cli-1.4.0 → cctx_cli-1.5.1}/tests/__init__.py +0 -0
- {cctx_cli-1.4.0 → cctx_cli-1.5.1}/tests/conftest.py +0 -0
- {cctx_cli-1.4.0 → cctx_cli-1.5.1}/tests/diagnostician/__init__.py +0 -0
- {cctx_cli-1.4.0 → cctx_cli-1.5.1}/tests/diagnostician/conftest.py +0 -0
- {cctx_cli-1.4.0 → cctx_cli-1.5.1}/tests/diagnostician/test_dead_end.py +0 -0
- {cctx_cli-1.4.0 → cctx_cli-1.5.1}/tests/diagnostician/test_inflection.py +0 -0
- {cctx_cli-1.4.0 → cctx_cli-1.5.1}/tests/diagnostician/test_orchestrator.py +0 -0
- {cctx_cli-1.4.0 → cctx_cli-1.5.1}/tests/diagnostician/test_project_specific.py +0 -0
- {cctx_cli-1.4.0 → cctx_cli-1.5.1}/tests/diagnostician/test_retry_loop.py +0 -0
- {cctx_cli-1.4.0 → cctx_cli-1.5.1}/tests/diagnostician/test_scope_creep.py +0 -0
- {cctx_cli-1.4.0 → cctx_cli-1.5.1}/tests/diagnostician/test_stale_context.py +0 -0
- {cctx_cli-1.4.0 → cctx_cli-1.5.1}/tests/diagnostician/test_tool_thrash.py +0 -0
- {cctx_cli-1.4.0 → cctx_cli-1.5.1}/tests/exporters/__init__.py +0 -0
- {cctx_cli-1.4.0 → cctx_cli-1.5.1}/tests/exporters/test_csv.py +0 -0
- {cctx_cli-1.4.0 → cctx_cli-1.5.1}/tests/exporters/test_jsonl.py +0 -0
- {cctx_cli-1.4.0 → cctx_cli-1.5.1}/tests/fixtures/claude_code/README.md +0 -0
- {cctx_cli-1.4.0 → cctx_cli-1.5.1}/tests/fixtures/claude_code/short-clean/short-clean.jsonl +0 -0
- {cctx_cli-1.4.0 → cctx_cli-1.5.1}/tests/fixtures/claude_code/with-attachments/with-attachments/subagents/agent-a0b4c2cf1dde0ca56.meta.json +0 -0
- {cctx_cli-1.4.0 → cctx_cli-1.5.1}/tests/fixtures/claude_code/with-attachments/with-attachments/subagents/agent-a116ae34b1b09c332.meta.json +0 -0
- {cctx_cli-1.4.0 → cctx_cli-1.5.1}/tests/fixtures/claude_code/with-attachments/with-attachments/subagents/agent-a1c4c417b35658c9e.meta.json +0 -0
- {cctx_cli-1.4.0 → cctx_cli-1.5.1}/tests/fixtures/claude_code/with-attachments/with-attachments/subagents/agent-a1e41a901de38f1b5.meta.json +0 -0
- {cctx_cli-1.4.0 → cctx_cli-1.5.1}/tests/fixtures/claude_code/with-attachments/with-attachments/subagents/agent-a338f8d0c74612a24.meta.json +0 -0
- {cctx_cli-1.4.0 → cctx_cli-1.5.1}/tests/fixtures/claude_code/with-attachments/with-attachments/subagents/agent-a34f6f3c0e7094186.meta.json +0 -0
- {cctx_cli-1.4.0 → cctx_cli-1.5.1}/tests/fixtures/claude_code/with-attachments/with-attachments/subagents/agent-a5a5a0cff4d13308b.meta.json +0 -0
- {cctx_cli-1.4.0 → cctx_cli-1.5.1}/tests/fixtures/claude_code/with-attachments/with-attachments/subagents/agent-a6b0a3da6a0484db5.meta.json +0 -0
- {cctx_cli-1.4.0 → cctx_cli-1.5.1}/tests/fixtures/claude_code/with-attachments/with-attachments/subagents/agent-a7f73f1790b02cde5.meta.json +0 -0
- {cctx_cli-1.4.0 → cctx_cli-1.5.1}/tests/fixtures/claude_code/with-attachments/with-attachments/subagents/agent-a7f7c17c38a9d8788.meta.json +0 -0
- {cctx_cli-1.4.0 → cctx_cli-1.5.1}/tests/fixtures/claude_code/with-attachments/with-attachments/subagents/agent-a853259e2cd7bbe8a.meta.json +0 -0
- {cctx_cli-1.4.0 → cctx_cli-1.5.1}/tests/fixtures/claude_code/with-attachments/with-attachments/subagents/agent-a8d9aedb0d0c6e12d.meta.json +0 -0
- {cctx_cli-1.4.0 → cctx_cli-1.5.1}/tests/fixtures/claude_code/with-attachments/with-attachments/subagents/agent-aa778bc1d59e4a441.meta.json +0 -0
- {cctx_cli-1.4.0 → cctx_cli-1.5.1}/tests/fixtures/claude_code/with-attachments/with-attachments/subagents/agent-aba869dedee4a12ba.meta.json +0 -0
- {cctx_cli-1.4.0 → cctx_cli-1.5.1}/tests/fixtures/claude_code/with-attachments/with-attachments/subagents/agent-ada2746d9774b94db.meta.json +0 -0
- {cctx_cli-1.4.0 → cctx_cli-1.5.1}/tests/fixtures/claude_code/with-attachments/with-attachments/subagents/agent-aea0132068c64d2dd.meta.json +0 -0
- {cctx_cli-1.4.0 → cctx_cli-1.5.1}/tests/fixtures/claude_code/with-attachments/with-attachments/subagents/agent-aea215eff50874d5f.meta.json +0 -0
- {cctx_cli-1.4.0 → cctx_cli-1.5.1}/tests/fixtures/claude_code/with-attachments/with-attachments/subagents/agent-afee21f2b3852a4a0.meta.json +0 -0
- {cctx_cli-1.4.0 → cctx_cli-1.5.1}/tests/fixtures/claude_code/with-attachments/with-attachments.jsonl +0 -0
- {cctx_cli-1.4.0 → cctx_cli-1.5.1}/tests/fixtures/claude_code/with-compaction/with-compaction/subagents/agent-a051d9c9a6b2f5cc3.jsonl +0 -0
- {cctx_cli-1.4.0 → cctx_cli-1.5.1}/tests/fixtures/claude_code/with-compaction/with-compaction/subagents/agent-a051d9c9a6b2f5cc3.meta.json +0 -0
- {cctx_cli-1.4.0 → cctx_cli-1.5.1}/tests/fixtures/claude_code/with-compaction/with-compaction/subagents/agent-a171f16f4e65cfe75.jsonl +0 -0
- {cctx_cli-1.4.0 → cctx_cli-1.5.1}/tests/fixtures/claude_code/with-compaction/with-compaction/subagents/agent-a171f16f4e65cfe75.meta.json +0 -0
- {cctx_cli-1.4.0 → cctx_cli-1.5.1}/tests/fixtures/claude_code/with-compaction/with-compaction/subagents/agent-a1b77fea2c0a2269b.jsonl +0 -0
- {cctx_cli-1.4.0 → cctx_cli-1.5.1}/tests/fixtures/claude_code/with-compaction/with-compaction/subagents/agent-a1b77fea2c0a2269b.meta.json +0 -0
- {cctx_cli-1.4.0 → cctx_cli-1.5.1}/tests/fixtures/claude_code/with-compaction/with-compaction/subagents/agent-a20da4c01a54acca8.jsonl +0 -0
- {cctx_cli-1.4.0 → cctx_cli-1.5.1}/tests/fixtures/claude_code/with-compaction/with-compaction/subagents/agent-a20da4c01a54acca8.meta.json +0 -0
- {cctx_cli-1.4.0 → cctx_cli-1.5.1}/tests/fixtures/claude_code/with-compaction/with-compaction/subagents/agent-a3c82739b1383fb14.jsonl +0 -0
- {cctx_cli-1.4.0 → cctx_cli-1.5.1}/tests/fixtures/claude_code/with-compaction/with-compaction/subagents/agent-a3c82739b1383fb14.meta.json +0 -0
- {cctx_cli-1.4.0 → cctx_cli-1.5.1}/tests/fixtures/claude_code/with-compaction/with-compaction/subagents/agent-a49e8539611c5fe12.jsonl +0 -0
- {cctx_cli-1.4.0 → cctx_cli-1.5.1}/tests/fixtures/claude_code/with-compaction/with-compaction/subagents/agent-a49e8539611c5fe12.meta.json +0 -0
- {cctx_cli-1.4.0 → cctx_cli-1.5.1}/tests/fixtures/claude_code/with-compaction/with-compaction/subagents/agent-a7bb58f3fff2b3e8d.jsonl +0 -0
- {cctx_cli-1.4.0 → cctx_cli-1.5.1}/tests/fixtures/claude_code/with-compaction/with-compaction/subagents/agent-a7bb58f3fff2b3e8d.meta.json +0 -0
- {cctx_cli-1.4.0 → cctx_cli-1.5.1}/tests/fixtures/claude_code/with-compaction/with-compaction/subagents/agent-a92b48c0331195aac.jsonl +0 -0
- {cctx_cli-1.4.0 → cctx_cli-1.5.1}/tests/fixtures/claude_code/with-compaction/with-compaction/subagents/agent-a92b48c0331195aac.meta.json +0 -0
- {cctx_cli-1.4.0 → cctx_cli-1.5.1}/tests/fixtures/claude_code/with-compaction/with-compaction/subagents/agent-ab96c4264099694a9.jsonl +0 -0
- {cctx_cli-1.4.0 → cctx_cli-1.5.1}/tests/fixtures/claude_code/with-compaction/with-compaction/subagents/agent-ab96c4264099694a9.meta.json +0 -0
- {cctx_cli-1.4.0 → cctx_cli-1.5.1}/tests/fixtures/claude_code/with-compaction/with-compaction/subagents/agent-acb2895c5e34ffec0.jsonl +0 -0
- {cctx_cli-1.4.0 → cctx_cli-1.5.1}/tests/fixtures/claude_code/with-compaction/with-compaction/subagents/agent-acb2895c5e34ffec0.meta.json +0 -0
- {cctx_cli-1.4.0 → cctx_cli-1.5.1}/tests/fixtures/claude_code/with-compaction/with-compaction/subagents/agent-adb2302769938fb3f.jsonl +0 -0
- {cctx_cli-1.4.0 → cctx_cli-1.5.1}/tests/fixtures/claude_code/with-compaction/with-compaction/subagents/agent-adb2302769938fb3f.meta.json +0 -0
- {cctx_cli-1.4.0 → cctx_cli-1.5.1}/tests/fixtures/claude_code/with-compaction/with-compaction/subagents/agent-ae585eca15cb93b9c.jsonl +0 -0
- {cctx_cli-1.4.0 → cctx_cli-1.5.1}/tests/fixtures/claude_code/with-compaction/with-compaction/subagents/agent-ae585eca15cb93b9c.meta.json +0 -0
- {cctx_cli-1.4.0 → cctx_cli-1.5.1}/tests/fixtures/claude_code/with-compaction/with-compaction/subagents/agent-aec9c917feb903d67.jsonl +0 -0
- {cctx_cli-1.4.0 → cctx_cli-1.5.1}/tests/fixtures/claude_code/with-compaction/with-compaction/subagents/agent-aec9c917feb903d67.meta.json +0 -0
- {cctx_cli-1.4.0 → cctx_cli-1.5.1}/tests/fixtures/claude_code/with-compaction/with-compaction.jsonl +0 -0
- {cctx_cli-1.4.0 → cctx_cli-1.5.1}/tests/fixtures/claude_code/with-subagents/with-subagents/subagents/agent-a1a3a21aeb76bb0a9.jsonl +0 -0
- {cctx_cli-1.4.0 → cctx_cli-1.5.1}/tests/fixtures/claude_code/with-subagents/with-subagents/subagents/agent-a1a3a21aeb76bb0a9.meta.json +0 -0
- {cctx_cli-1.4.0 → cctx_cli-1.5.1}/tests/fixtures/claude_code/with-subagents/with-subagents/subagents/agent-aaa1d6ecc05a78442.jsonl +0 -0
- {cctx_cli-1.4.0 → cctx_cli-1.5.1}/tests/fixtures/claude_code/with-subagents/with-subagents/subagents/agent-aaa1d6ecc05a78442.meta.json +0 -0
- {cctx_cli-1.4.0 → cctx_cli-1.5.1}/tests/fixtures/claude_code/with-subagents/with-subagents/subagents/agent-af3c545ccd30036d2.jsonl +0 -0
- {cctx_cli-1.4.0 → cctx_cli-1.5.1}/tests/fixtures/claude_code/with-subagents/with-subagents/subagents/agent-af3c545ccd30036d2.meta.json +0 -0
- {cctx_cli-1.4.0 → cctx_cli-1.5.1}/tests/fixtures/claude_code/with-subagents/with-subagents/tool-results/btwp2bzro.txt +0 -0
- {cctx_cli-1.4.0 → cctx_cli-1.5.1}/tests/fixtures/claude_code/with-subagents/with-subagents/tool-results/byqjbgy4b.txt +0 -0
- {cctx_cli-1.4.0 → cctx_cli-1.5.1}/tests/fixtures/claude_code/with-subagents/with-subagents.jsonl +0 -0
- {cctx_cli-1.4.0 → cctx_cli-1.5.1}/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.1}/tests/fixtures/claude_code/with-tool-results/with-tool-results.jsonl +0 -0
- {cctx_cli-1.4.0 → cctx_cli-1.5.1}/tests/fixtures/scrub.py +0 -0
- {cctx_cli-1.4.0 → cctx_cli-1.5.1}/tests/fixtures/synthetic/bookkeeping_only.jsonl +0 -0
- {cctx_cli-1.4.0 → cctx_cli-1.5.1}/tests/fixtures/synthetic/malformed_middle.jsonl +0 -0
- {cctx_cli-1.4.0 → cctx_cli-1.5.1}/tests/fixtures/synthetic/truncated_final_line.jsonl +0 -0
- {cctx_cli-1.4.0 → cctx_cli-1.5.1}/tests/fixtures/synthetic/unknown_attachment_shape.jsonl +0 -0
- {cctx_cli-1.4.0 → cctx_cli-1.5.1}/tests/fixtures/synthetic/unknown_type.jsonl +0 -0
- {cctx_cli-1.4.0 → cctx_cli-1.5.1}/tests/parsers/__init__.py +0 -0
- {cctx_cli-1.4.0 → cctx_cli-1.5.1}/tests/parsers/test_claude_code.py +0 -0
- {cctx_cli-1.4.0 → cctx_cli-1.5.1}/tests/parsers/test_claude_code_integration.py +0 -0
- {cctx_cli-1.4.0 → cctx_cli-1.5.1}/tests/recommender/__init__.py +0 -0
- {cctx_cli-1.4.0 → cctx_cli-1.5.1}/tests/recommender/test_claude_md.py +0 -0
- {cctx_cli-1.4.0 → cctx_cli-1.5.1}/tests/recommender/test_evidence.py +0 -0
- {cctx_cli-1.4.0 → cctx_cli-1.5.1}/tests/renderers/__init__.py +0 -0
- {cctx_cli-1.4.0 → cctx_cli-1.5.1}/tests/renderers/test_report.py +0 -0
- {cctx_cli-1.4.0 → cctx_cli-1.5.1}/tests/renderers/test_terminal_renderer_full.py +0 -0
- {cctx_cli-1.4.0 → cctx_cli-1.5.1}/tests/test_aggregate.py +0 -0
- {cctx_cli-1.4.0 → cctx_cli-1.5.1}/tests/test_cli.py +0 -0
- {cctx_cli-1.4.0 → cctx_cli-1.5.1}/tests/test_cli_export.py +0 -0
- {cctx_cli-1.4.0 → cctx_cli-1.5.1}/tests/test_discovery.py +0 -0
- {cctx_cli-1.4.0 → cctx_cli-1.5.1}/tests/test_github_summary.py +0 -0
- {cctx_cli-1.4.0 → cctx_cli-1.5.1}/tests/test_harvest.py +0 -0
- {cctx_cli-1.4.0 → cctx_cli-1.5.1}/tests/test_harvest_check.py +0 -0
- {cctx_cli-1.4.0 → cctx_cli-1.5.1}/tests/test_models.py +0 -0
- {cctx_cli-1.4.0 → cctx_cli-1.5.1}/tests/test_models_project_pattern.py +0 -0
- {cctx_cli-1.4.0 → cctx_cli-1.5.1}/tests/test_smoke.py +0 -0
- {cctx_cli-1.4.0 → cctx_cli-1.5.1}/tests/test_tokenizer.py +0 -0
- {cctx_cli-1.4.0 → cctx_cli-1.5.1}/tests/test_trace_tui.py +0 -0
|
@@ -2,6 +2,57 @@
|
|
|
2
2
|
|
|
3
3
|
<!-- version list -->
|
|
4
4
|
|
|
5
|
+
## v1.5.1 (2026-06-10)
|
|
6
|
+
|
|
7
|
+
### Bug Fixes
|
|
8
|
+
|
|
9
|
+
- Recommender — add TOOL_THRASH/DEAD_END patch templates
|
|
10
|
+
([#107](https://github.com/jacquardlabs/cctx/pull/107),
|
|
11
|
+
[`3c79d58`](https://github.com/jacquardlabs/cctx/commit/3c79d58a55af106d3fd81542e70b8f892569185c))
|
|
12
|
+
|
|
13
|
+
### Documentation
|
|
14
|
+
|
|
15
|
+
- Product review 2026-06-09 + M15 cross-agent emit spec
|
|
16
|
+
([#107](https://github.com/jacquardlabs/cctx/pull/107),
|
|
17
|
+
[`3c79d58`](https://github.com/jacquardlabs/cctx/commit/3c79d58a55af106d3fd81542e70b8f892569185c))
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
## v1.5.0 (2026-05-20)
|
|
21
|
+
|
|
22
|
+
### Bug Fixes
|
|
23
|
+
|
|
24
|
+
- Agents.py — guard against non-list JSON, tighten patch targets
|
|
25
|
+
([`c41c42c`](https://github.com/jacquardlabs/cctx/commit/c41c42cb366904fc332638b8f97ee042f17b45c2))
|
|
26
|
+
|
|
27
|
+
- Renderer — guard order, tmp_path fixtures, missing no-badge tests
|
|
28
|
+
([`8f26afd`](https://github.com/jacquardlabs/cctx/commit/8f26afdf27c63641942b434e47a2fb7b24d38068))
|
|
29
|
+
|
|
30
|
+
- Watcher — hermetic tests, reuse _encode_path, rename clarity
|
|
31
|
+
([`a7b52de`](https://github.com/jacquardlabs/cctx/commit/a7b52def47b13447d956476a0ec6782fe2d0247b))
|
|
32
|
+
|
|
33
|
+
### Documentation
|
|
34
|
+
|
|
35
|
+
- Implementation plan for claude agents live integration
|
|
36
|
+
([`2136adf`](https://github.com/jacquardlabs/cctx/commit/2136adf0697a695d27f438f0368e7bd5ba406e89))
|
|
37
|
+
|
|
38
|
+
- Spec for claude agents --json live session integration
|
|
39
|
+
([`0df2381`](https://github.com/jacquardlabs/cctx/commit/0df23813a90c66e57d0d39c6b959859b89a5c057))
|
|
40
|
+
|
|
41
|
+
### Features
|
|
42
|
+
|
|
43
|
+
- Add agents.py — live_sessions() via claude agents --json
|
|
44
|
+
([`83b704f`](https://github.com/jacquardlabs/cctx/commit/83b704ffbe4303dbd316257a01eb0b59307c0e06))
|
|
45
|
+
|
|
46
|
+
- Cctx ls — pass live_statuses to renderer for live session badges
|
|
47
|
+
([`65445d7`](https://github.com/jacquardlabs/cctx/commit/65445d75a213bf26401fe044a2409d2ce0efbcb1))
|
|
48
|
+
|
|
49
|
+
- Render_sessions/render_projects — live status badges via live_statuses param
|
|
50
|
+
([`3d77687`](https://github.com/jacquardlabs/cctx/commit/3d776879c9ce03a622361c2b804e5986d40f37a1))
|
|
51
|
+
|
|
52
|
+
- Watcher — live session detection + early idle exit via claude agents --json
|
|
53
|
+
([`a11481c`](https://github.com/jacquardlabs/cctx/commit/a11481c2126fead87e1f92bfecc7bf5ac3f39d1a))
|
|
54
|
+
|
|
55
|
+
|
|
5
56
|
## v1.4.0 (2026-05-20)
|
|
6
57
|
|
|
7
58
|
### Bug Fixes
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
cctx is a local Python CLI that reads Claude Code session logs and tells you what went wrong, why it cost what it did, and what to add to your CLAUDE.md so it doesn't happen again.
|
|
6
6
|
|
|
7
|
-
It is
|
|
7
|
+
It is forensic-first. You reach for it after a session — when something felt off, when the bill was higher than expected, or on a weekly review pass. `cctx watch` extends the same waste detection into a live, opt-in companion for an active session; it is a foreground command you run deliberately, not background infrastructure.
|
|
8
8
|
|
|
9
9
|
## Primary persona
|
|
10
10
|
|
|
@@ -20,7 +20,7 @@ This persona is already the target; everything shipped to date serves them direc
|
|
|
20
20
|
## What cctx is NOT for
|
|
21
21
|
|
|
22
22
|
- Teams or organizations (no shared reports, no access control, no multi-user state)
|
|
23
|
-
-
|
|
23
|
+
- Ambient monitoring (no daemons, no persistent watcher state, no alerting — `cctx watch` is a foreground command you start and stop)
|
|
24
24
|
- General cost dashboards (CodeBurn covers that; cctx is forensic)
|
|
25
25
|
- Multi-provider support (Claude Code only in v0/v1)
|
|
26
26
|
- Users who do not read session transcripts or maintain CLAUDE.md
|
|
@@ -28,7 +28,7 @@ This persona is already the target; everything shipped to date serves them direc
|
|
|
28
28
|
## Product principles
|
|
29
29
|
|
|
30
30
|
**1. Forensic, not ambient.**
|
|
31
|
-
cctx is
|
|
31
|
+
cctx is forensic-first: the core use case is "I just ran a session, something went sideways, I want to know what." Live mode (`watch`) is justified only as the same detection running earlier — it surfaces the identical findings, never becomes a daemon, and never holds state between runs.
|
|
32
32
|
|
|
33
33
|
**2. Output must be actionable.**
|
|
34
34
|
Every report should produce at least one thing the user can do immediately. Findings without patches are incomplete. Patches must be copy-pasteable or auto-applicable.
|
|
@@ -37,15 +37,15 @@ Every report should produce at least one thing the user can do immediately. Find
|
|
|
37
37
|
Costs are approximated (85–95% of actual). Pattern classifiers fire only on high-confidence signals. Low-confidence signals are shown with explicit confidence labels. "System internals" token budget is never hidden or misattributed.
|
|
38
38
|
|
|
39
39
|
**4. Local and stateless.**
|
|
40
|
-
No network calls in the core analysis path (tokenizer may call the API for token counting; that is opt-in). No persistent database. No telemetry. No account. Users own their data.
|
|
40
|
+
No network calls in the core analysis path (tokenizer may call the API for token counting; that is opt-in). Live-session detection may shell out to the local `claude` CLI and degrades gracefully without it. No persistent database. No telemetry. No account. Users own their data.
|
|
41
41
|
|
|
42
42
|
**5. Deterministic.**
|
|
43
43
|
Pattern classifiers are heuristics, not LLM calls. The same session file produces the same output every time. Testable on fixtures. Predictable cost to run.
|
|
44
44
|
|
|
45
45
|
**6. Small surface, deep on each command.**
|
|
46
|
-
|
|
46
|
+
Six commands (`ls`, `autopsy`, `harvest`, `watch`, `trace`, `export`). No command is shallow. Users should be able to learn the product in an afternoon and trust what it tells them.
|
|
47
47
|
|
|
48
|
-
## Feature map (
|
|
48
|
+
## Feature map (v1.4.0)
|
|
49
49
|
|
|
50
50
|
### Shipped
|
|
51
51
|
|
|
@@ -68,8 +68,16 @@ Four commands. No command is shallow. Users should be able to learn the product
|
|
|
68
68
|
| Cross-session harvest | `cctx harvest <project> --since N` | M5 |
|
|
69
69
|
| Session discovery | `cctx ls` / `cctx autopsy --latest` | M6+ |
|
|
70
70
|
| Live waste signals | `cctx watch <project>` | M6+ |
|
|
71
|
+
| Verdict headline + `--top N` + `--turn N` | `autopsy` | v1.1.0 (M9) |
|
|
72
|
+
| `--until DATE` on cross-session mode | `autopsy --since ... --until` | v1.2.0 (M12) |
|
|
73
|
+
| Machine-readable diagnosis | `cctx autopsy <session> --json` | v1.2.0 (M12) |
|
|
74
|
+
| JSON export | `cctx export <session> --format json` | v1.2.0 (M12) |
|
|
75
|
+
| Project-specific pattern detection | `autopsy`/`harvest` `--since` | v1.3.0 (M14) |
|
|
76
|
+
| Memory-hygiene depth | `harvest --check` + `--check-severity` | v1.4.0 (M13) |
|
|
77
|
+
| Live session badges | `cctx ls` | unreleased |
|
|
78
|
+
| Live session detection, early idle exit | `cctx watch` | unreleased |
|
|
71
79
|
|
|
72
|
-
### Pattern classifiers (
|
|
80
|
+
### Pattern classifiers (v1.4.0)
|
|
73
81
|
|
|
74
82
|
| Pattern | Status |
|
|
75
83
|
|---|---|
|
|
@@ -78,6 +86,7 @@ Four commands. No command is shallow. Users should be able to learn the product
|
|
|
78
86
|
| Stale context | Shipped |
|
|
79
87
|
| Dead-end exploration | Shipped (v0.2.0) |
|
|
80
88
|
| Tool thrashing | Shipped (v0.2.0) |
|
|
89
|
+
| Project-specific patterns (cross-session) | Shipped (v1.3.0) |
|
|
81
90
|
|
|
82
91
|
## What we are NOT building
|
|
83
92
|
|
|
@@ -87,12 +96,14 @@ Four commands. No command is shallow. Users should be able to learn the product
|
|
|
87
96
|
- A fork-and-replay debugger
|
|
88
97
|
- A general eval or testing framework
|
|
89
98
|
|
|
90
|
-
## Known problems (as of 2026-
|
|
99
|
+
## Known problems (as of 2026-06-09)
|
|
91
100
|
|
|
92
|
-
**Active gaps
|
|
101
|
+
**Active gaps:**
|
|
93
102
|
|
|
94
|
-
1. **`cctx watch` polling is simple.**
|
|
103
|
+
1. **`cctx watch` polling is simple.** Early idle exit via `claude agents --json` has landed, but the watcher still polls at 1s without `fsevents`/`inotify` debouncing.
|
|
95
104
|
|
|
96
|
-
2.
|
|
105
|
+
2. **Subagent traces are parsed but never diagnosed.** The parser models subagent sessions recursively and the tokenizer counts their tokens, but no classifier or cost attribution reads `trace.subagents`. Autopsy is blind to spend inside agent fan-outs. (M16)
|
|
97
106
|
|
|
98
|
-
3. **Cross-agent layer not started.**
|
|
107
|
+
3. **Cross-agent layer not started.** Tracked as M15 / #82 — the final step of the original growth staircase.
|
|
108
|
+
|
|
109
|
+
4. **Harvest has no feedback loop.** Nothing measures whether an applied patch reduced the recurrence of the pattern it targeted, even though patches carry fingerprints and sessions carry dates. (M17)
|
|
@@ -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()
|
|
@@ -41,11 +41,29 @@ _STALE_CONTEXT_DIFF = """\
|
|
|
41
41
|
+without re-referencing it. Prefer re-running the tool over dragging stale context
|
|
42
42
|
+forward — the compaction system handles removal."""
|
|
43
43
|
|
|
44
|
+
_TOOL_THRASH_DIFF = """\
|
|
45
|
+
+## Tool-call discipline
|
|
46
|
+
+
|
|
47
|
+
+Before reaching for a tool, decide what specific information the call must return
|
|
48
|
+
+and how it changes the next step. Don't fan out near-identical searches or re-read
|
|
49
|
+
+the same file from different angles hoping something turns up. If two or three calls
|
|
50
|
+
+haven't moved you forward, stop and form a hypothesis before the next one."""
|
|
51
|
+
|
|
52
|
+
_DEAD_END_DIFF = """\
|
|
53
|
+
+## Exploration discipline
|
|
54
|
+
+
|
|
55
|
+
+When an approach stops making progress, name the dead end explicitly and back out
|
|
56
|
+
+rather than pushing deeper on a path that isn't working. Re-read the goal, list the
|
|
57
|
+
+approaches already ruled out, and pick a meaningfully different one. Sunk effort on a
|
|
58
|
+
+failing approach is not a reason to continue it."""
|
|
59
|
+
|
|
44
60
|
_TEMPLATES: dict[FindingKind, tuple[str, str, str]] = {
|
|
45
61
|
# kind → (description, diff_body, target_file)
|
|
46
|
-
FindingKind.RETRY_LOOP: ("Add retry discipline rule",
|
|
47
|
-
FindingKind.SCOPE_CREEP: ("Add scope discipline rule",
|
|
48
|
-
FindingKind.STALE_CONTEXT: ("Add context hygiene rule",
|
|
62
|
+
FindingKind.RETRY_LOOP: ("Add retry discipline rule", _RETRY_LOOP_DIFF, "CLAUDE.md"),
|
|
63
|
+
FindingKind.SCOPE_CREEP: ("Add scope discipline rule", _SCOPE_CREEP_DIFF, "CLAUDE.md"),
|
|
64
|
+
FindingKind.STALE_CONTEXT: ("Add context hygiene rule", _STALE_CONTEXT_DIFF, "CLAUDE.md"),
|
|
65
|
+
FindingKind.TOOL_THRASH: ("Add tool-call discipline rule", _TOOL_THRASH_DIFF, "CLAUDE.md"),
|
|
66
|
+
FindingKind.DEAD_END: ("Add exploration discipline rule", _DEAD_END_DIFF, "CLAUDE.md"),
|
|
49
67
|
}
|
|
50
68
|
|
|
51
69
|
|
|
@@ -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
|
|