cctx-cli 1.5.0__tar.gz → 1.6.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.5.0 → cctx_cli-1.6.0}/CHANGELOG.md +74 -0
- {cctx_cli-1.5.0 → cctx_cli-1.6.0}/PKG-INFO +1 -1
- {cctx_cli-1.5.0 → cctx_cli-1.6.0}/PRODUCT.md +25 -13
- {cctx_cli-1.5.0 → cctx_cli-1.6.0}/cctx/__init__.py +1 -1
- {cctx_cli-1.5.0 → cctx_cli-1.6.0}/cctx/cli.py +36 -1
- {cctx_cli-1.5.0 → cctx_cli-1.6.0}/cctx/harvest.py +74 -4
- {cctx_cli-1.5.0 → cctx_cli-1.6.0}/cctx/models.py +16 -0
- {cctx_cli-1.5.0 → cctx_cli-1.6.0}/cctx/recommender/claude_md.py +21 -3
- cctx_cli-1.6.0/docs/product-reviews/2026-06-09-product-review.md +186 -0
- cctx_cli-1.6.0/docs/superpowers/specs/2026-06-09-cross-agent-emit-design.md +214 -0
- {cctx_cli-1.5.0 → cctx_cli-1.6.0}/pyproject.toml +1 -1
- cctx_cli-1.6.0/tests/test_harvest_emit.py +204 -0
- cctx_cli-1.6.0/tests/test_recommender.py +125 -0
- cctx_cli-1.5.0/tests/test_recommender.py +0 -56
- {cctx_cli-1.5.0 → cctx_cli-1.6.0}/.github/workflows/ci.yml +0 -0
- {cctx_cli-1.5.0 → cctx_cli-1.6.0}/.github/workflows/publish.yml +0 -0
- {cctx_cli-1.5.0 → cctx_cli-1.6.0}/.github/workflows/release.yml +0 -0
- {cctx_cli-1.5.0 → cctx_cli-1.6.0}/.gitignore +0 -0
- {cctx_cli-1.5.0 → cctx_cli-1.6.0}/CLAUDE.md +0 -0
- {cctx_cli-1.5.0 → cctx_cli-1.6.0}/DESIGN.md +0 -0
- {cctx_cli-1.5.0 → cctx_cli-1.6.0}/README.md +0 -0
- {cctx_cli-1.5.0 → cctx_cli-1.6.0}/action.yml +0 -0
- {cctx_cli-1.5.0 → cctx_cli-1.6.0}/cctx/agents.py +0 -0
- {cctx_cli-1.5.0 → cctx_cli-1.6.0}/cctx/diagnostician/__init__.py +0 -0
- {cctx_cli-1.5.0 → cctx_cli-1.6.0}/cctx/diagnostician/aggregate.py +0 -0
- {cctx_cli-1.5.0 → cctx_cli-1.6.0}/cctx/diagnostician/inflection.py +0 -0
- {cctx_cli-1.5.0 → cctx_cli-1.6.0}/cctx/diagnostician/patterns/__init__.py +0 -0
- {cctx_cli-1.5.0 → cctx_cli-1.6.0}/cctx/diagnostician/patterns/dead_end.py +0 -0
- {cctx_cli-1.5.0 → cctx_cli-1.6.0}/cctx/diagnostician/patterns/project_specific.py +0 -0
- {cctx_cli-1.5.0 → cctx_cli-1.6.0}/cctx/diagnostician/patterns/retry_loop.py +0 -0
- {cctx_cli-1.5.0 → cctx_cli-1.6.0}/cctx/diagnostician/patterns/scope_creep.py +0 -0
- {cctx_cli-1.5.0 → cctx_cli-1.6.0}/cctx/diagnostician/patterns/stale_context.py +0 -0
- {cctx_cli-1.5.0 → cctx_cli-1.6.0}/cctx/diagnostician/patterns/tool_thrash.py +0 -0
- {cctx_cli-1.5.0 → cctx_cli-1.6.0}/cctx/discovery.py +0 -0
- {cctx_cli-1.5.0 → cctx_cli-1.6.0}/cctx/exporters/__init__.py +0 -0
- {cctx_cli-1.5.0 → cctx_cli-1.6.0}/cctx/exporters/csv.py +0 -0
- {cctx_cli-1.5.0 → cctx_cli-1.6.0}/cctx/exporters/json.py +0 -0
- {cctx_cli-1.5.0 → cctx_cli-1.6.0}/cctx/exporters/jsonl.py +0 -0
- {cctx_cli-1.5.0 → cctx_cli-1.6.0}/cctx/parsers/__init__.py +0 -0
- {cctx_cli-1.5.0 → cctx_cli-1.6.0}/cctx/parsers/claude_code.py +0 -0
- {cctx_cli-1.5.0 → cctx_cli-1.6.0}/cctx/pricing.py +0 -0
- {cctx_cli-1.5.0 → cctx_cli-1.6.0}/cctx/recommender/__init__.py +0 -0
- {cctx_cli-1.5.0 → cctx_cli-1.6.0}/cctx/recommender/evidence.py +0 -0
- {cctx_cli-1.5.0 → cctx_cli-1.6.0}/cctx/renderers/__init__.py +0 -0
- {cctx_cli-1.5.0 → cctx_cli-1.6.0}/cctx/renderers/github.py +0 -0
- {cctx_cli-1.5.0 → cctx_cli-1.6.0}/cctx/renderers/report.py +0 -0
- {cctx_cli-1.5.0 → cctx_cli-1.6.0}/cctx/renderers/templates/autopsy.html.j2 +0 -0
- {cctx_cli-1.5.0 → cctx_cli-1.6.0}/cctx/renderers/terminal.py +0 -0
- {cctx_cli-1.5.0 → cctx_cli-1.6.0}/cctx/renderers/trace_tui.py +0 -0
- {cctx_cli-1.5.0 → cctx_cli-1.6.0}/cctx/tokenizer.py +0 -0
- {cctx_cli-1.5.0 → cctx_cli-1.6.0}/cctx/watcher.py +0 -0
- {cctx_cli-1.5.0 → cctx_cli-1.6.0}/cctx-project-brief.md +0 -0
- {cctx_cli-1.5.0 → cctx_cli-1.6.0}/demo.gif +0 -0
- {cctx_cli-1.5.0 → cctx_cli-1.6.0}/demo.tape +0 -0
- {cctx_cli-1.5.0 → cctx_cli-1.6.0}/docs/health-reviews/2026-05-15-deep-review-summary.md +0 -0
- {cctx_cli-1.5.0 → cctx_cli-1.6.0}/docs/health-reviews/2026-05-15-health-review.md +0 -0
- {cctx_cli-1.5.0 → cctx_cli-1.6.0}/docs/product-reviews/2026-05-15-product-review.md +0 -0
- {cctx_cli-1.5.0 → cctx_cli-1.6.0}/docs/superpowers/plans/2026-05-12-claude-code-parser.md +0 -0
- {cctx_cli-1.5.0 → cctx_cli-1.6.0}/docs/superpowers/plans/2026-05-14-autopsy-v0.md +0 -0
- {cctx_cli-1.5.0 → cctx_cli-1.6.0}/docs/superpowers/plans/2026-05-16-readme-pypi-release.md +0 -0
- {cctx_cli-1.5.0 → cctx_cli-1.6.0}/docs/superpowers/plans/2026-05-17-harvest-check-depth.md +0 -0
- {cctx_cli-1.5.0 → cctx_cli-1.6.0}/docs/superpowers/plans/2026-05-17-project-pattern-detection.md +0 -0
- {cctx_cli-1.5.0 → cctx_cli-1.6.0}/docs/superpowers/plans/2026-05-19-claude-agents-live-integration.md +0 -0
- {cctx_cli-1.5.0 → cctx_cli-1.6.0}/docs/superpowers/specs/2026-05-12-claude-code-parser-design.md +0 -0
- {cctx_cli-1.5.0 → cctx_cli-1.6.0}/docs/superpowers/specs/2026-05-14-autopsy-design.md +0 -0
- {cctx_cli-1.5.0 → cctx_cli-1.6.0}/docs/superpowers/specs/2026-05-14-harvest-design.md +0 -0
- {cctx_cli-1.5.0 → cctx_cli-1.6.0}/docs/superpowers/specs/2026-05-14-trace-tui-design.md +0 -0
- {cctx_cli-1.5.0 → cctx_cli-1.6.0}/docs/superpowers/specs/2026-05-16-readme-pypi-release-design.md +0 -0
- {cctx_cli-1.5.0 → cctx_cli-1.6.0}/docs/superpowers/specs/2026-05-17-harvest-check-depth-design.md +0 -0
- {cctx_cli-1.5.0 → cctx_cli-1.6.0}/docs/superpowers/specs/2026-05-17-project-pattern-detection-design.md +0 -0
- {cctx_cli-1.5.0 → cctx_cli-1.6.0}/docs/superpowers/specs/2026-05-19-claude-agents-live-integration-design.md +0 -0
- {cctx_cli-1.5.0 → cctx_cli-1.6.0}/tests/__init__.py +0 -0
- {cctx_cli-1.5.0 → cctx_cli-1.6.0}/tests/conftest.py +0 -0
- {cctx_cli-1.5.0 → cctx_cli-1.6.0}/tests/diagnostician/__init__.py +0 -0
- {cctx_cli-1.5.0 → cctx_cli-1.6.0}/tests/diagnostician/conftest.py +0 -0
- {cctx_cli-1.5.0 → cctx_cli-1.6.0}/tests/diagnostician/test_dead_end.py +0 -0
- {cctx_cli-1.5.0 → cctx_cli-1.6.0}/tests/diagnostician/test_inflection.py +0 -0
- {cctx_cli-1.5.0 → cctx_cli-1.6.0}/tests/diagnostician/test_orchestrator.py +0 -0
- {cctx_cli-1.5.0 → cctx_cli-1.6.0}/tests/diagnostician/test_project_specific.py +0 -0
- {cctx_cli-1.5.0 → cctx_cli-1.6.0}/tests/diagnostician/test_retry_loop.py +0 -0
- {cctx_cli-1.5.0 → cctx_cli-1.6.0}/tests/diagnostician/test_scope_creep.py +0 -0
- {cctx_cli-1.5.0 → cctx_cli-1.6.0}/tests/diagnostician/test_stale_context.py +0 -0
- {cctx_cli-1.5.0 → cctx_cli-1.6.0}/tests/diagnostician/test_tool_thrash.py +0 -0
- {cctx_cli-1.5.0 → cctx_cli-1.6.0}/tests/exporters/__init__.py +0 -0
- {cctx_cli-1.5.0 → cctx_cli-1.6.0}/tests/exporters/test_csv.py +0 -0
- {cctx_cli-1.5.0 → cctx_cli-1.6.0}/tests/exporters/test_jsonl.py +0 -0
- {cctx_cli-1.5.0 → cctx_cli-1.6.0}/tests/fixtures/claude_code/README.md +0 -0
- {cctx_cli-1.5.0 → cctx_cli-1.6.0}/tests/fixtures/claude_code/short-clean/short-clean.jsonl +0 -0
- {cctx_cli-1.5.0 → cctx_cli-1.6.0}/tests/fixtures/claude_code/with-attachments/with-attachments/subagents/agent-a0b4c2cf1dde0ca56.meta.json +0 -0
- {cctx_cli-1.5.0 → cctx_cli-1.6.0}/tests/fixtures/claude_code/with-attachments/with-attachments/subagents/agent-a116ae34b1b09c332.meta.json +0 -0
- {cctx_cli-1.5.0 → cctx_cli-1.6.0}/tests/fixtures/claude_code/with-attachments/with-attachments/subagents/agent-a1c4c417b35658c9e.meta.json +0 -0
- {cctx_cli-1.5.0 → cctx_cli-1.6.0}/tests/fixtures/claude_code/with-attachments/with-attachments/subagents/agent-a1e41a901de38f1b5.meta.json +0 -0
- {cctx_cli-1.5.0 → cctx_cli-1.6.0}/tests/fixtures/claude_code/with-attachments/with-attachments/subagents/agent-a338f8d0c74612a24.meta.json +0 -0
- {cctx_cli-1.5.0 → cctx_cli-1.6.0}/tests/fixtures/claude_code/with-attachments/with-attachments/subagents/agent-a34f6f3c0e7094186.meta.json +0 -0
- {cctx_cli-1.5.0 → cctx_cli-1.6.0}/tests/fixtures/claude_code/with-attachments/with-attachments/subagents/agent-a5a5a0cff4d13308b.meta.json +0 -0
- {cctx_cli-1.5.0 → cctx_cli-1.6.0}/tests/fixtures/claude_code/with-attachments/with-attachments/subagents/agent-a6b0a3da6a0484db5.meta.json +0 -0
- {cctx_cli-1.5.0 → cctx_cli-1.6.0}/tests/fixtures/claude_code/with-attachments/with-attachments/subagents/agent-a7f73f1790b02cde5.meta.json +0 -0
- {cctx_cli-1.5.0 → cctx_cli-1.6.0}/tests/fixtures/claude_code/with-attachments/with-attachments/subagents/agent-a7f7c17c38a9d8788.meta.json +0 -0
- {cctx_cli-1.5.0 → cctx_cli-1.6.0}/tests/fixtures/claude_code/with-attachments/with-attachments/subagents/agent-a853259e2cd7bbe8a.meta.json +0 -0
- {cctx_cli-1.5.0 → cctx_cli-1.6.0}/tests/fixtures/claude_code/with-attachments/with-attachments/subagents/agent-a8d9aedb0d0c6e12d.meta.json +0 -0
- {cctx_cli-1.5.0 → cctx_cli-1.6.0}/tests/fixtures/claude_code/with-attachments/with-attachments/subagents/agent-aa778bc1d59e4a441.meta.json +0 -0
- {cctx_cli-1.5.0 → cctx_cli-1.6.0}/tests/fixtures/claude_code/with-attachments/with-attachments/subagents/agent-aba869dedee4a12ba.meta.json +0 -0
- {cctx_cli-1.5.0 → cctx_cli-1.6.0}/tests/fixtures/claude_code/with-attachments/with-attachments/subagents/agent-ada2746d9774b94db.meta.json +0 -0
- {cctx_cli-1.5.0 → cctx_cli-1.6.0}/tests/fixtures/claude_code/with-attachments/with-attachments/subagents/agent-aea0132068c64d2dd.meta.json +0 -0
- {cctx_cli-1.5.0 → cctx_cli-1.6.0}/tests/fixtures/claude_code/with-attachments/with-attachments/subagents/agent-aea215eff50874d5f.meta.json +0 -0
- {cctx_cli-1.5.0 → cctx_cli-1.6.0}/tests/fixtures/claude_code/with-attachments/with-attachments/subagents/agent-afee21f2b3852a4a0.meta.json +0 -0
- {cctx_cli-1.5.0 → cctx_cli-1.6.0}/tests/fixtures/claude_code/with-attachments/with-attachments.jsonl +0 -0
- {cctx_cli-1.5.0 → cctx_cli-1.6.0}/tests/fixtures/claude_code/with-compaction/with-compaction/subagents/agent-a051d9c9a6b2f5cc3.jsonl +0 -0
- {cctx_cli-1.5.0 → cctx_cli-1.6.0}/tests/fixtures/claude_code/with-compaction/with-compaction/subagents/agent-a051d9c9a6b2f5cc3.meta.json +0 -0
- {cctx_cli-1.5.0 → cctx_cli-1.6.0}/tests/fixtures/claude_code/with-compaction/with-compaction/subagents/agent-a171f16f4e65cfe75.jsonl +0 -0
- {cctx_cli-1.5.0 → cctx_cli-1.6.0}/tests/fixtures/claude_code/with-compaction/with-compaction/subagents/agent-a171f16f4e65cfe75.meta.json +0 -0
- {cctx_cli-1.5.0 → cctx_cli-1.6.0}/tests/fixtures/claude_code/with-compaction/with-compaction/subagents/agent-a1b77fea2c0a2269b.jsonl +0 -0
- {cctx_cli-1.5.0 → cctx_cli-1.6.0}/tests/fixtures/claude_code/with-compaction/with-compaction/subagents/agent-a1b77fea2c0a2269b.meta.json +0 -0
- {cctx_cli-1.5.0 → cctx_cli-1.6.0}/tests/fixtures/claude_code/with-compaction/with-compaction/subagents/agent-a20da4c01a54acca8.jsonl +0 -0
- {cctx_cli-1.5.0 → cctx_cli-1.6.0}/tests/fixtures/claude_code/with-compaction/with-compaction/subagents/agent-a20da4c01a54acca8.meta.json +0 -0
- {cctx_cli-1.5.0 → cctx_cli-1.6.0}/tests/fixtures/claude_code/with-compaction/with-compaction/subagents/agent-a3c82739b1383fb14.jsonl +0 -0
- {cctx_cli-1.5.0 → cctx_cli-1.6.0}/tests/fixtures/claude_code/with-compaction/with-compaction/subagents/agent-a3c82739b1383fb14.meta.json +0 -0
- {cctx_cli-1.5.0 → cctx_cli-1.6.0}/tests/fixtures/claude_code/with-compaction/with-compaction/subagents/agent-a49e8539611c5fe12.jsonl +0 -0
- {cctx_cli-1.5.0 → cctx_cli-1.6.0}/tests/fixtures/claude_code/with-compaction/with-compaction/subagents/agent-a49e8539611c5fe12.meta.json +0 -0
- {cctx_cli-1.5.0 → cctx_cli-1.6.0}/tests/fixtures/claude_code/with-compaction/with-compaction/subagents/agent-a7bb58f3fff2b3e8d.jsonl +0 -0
- {cctx_cli-1.5.0 → cctx_cli-1.6.0}/tests/fixtures/claude_code/with-compaction/with-compaction/subagents/agent-a7bb58f3fff2b3e8d.meta.json +0 -0
- {cctx_cli-1.5.0 → cctx_cli-1.6.0}/tests/fixtures/claude_code/with-compaction/with-compaction/subagents/agent-a92b48c0331195aac.jsonl +0 -0
- {cctx_cli-1.5.0 → cctx_cli-1.6.0}/tests/fixtures/claude_code/with-compaction/with-compaction/subagents/agent-a92b48c0331195aac.meta.json +0 -0
- {cctx_cli-1.5.0 → cctx_cli-1.6.0}/tests/fixtures/claude_code/with-compaction/with-compaction/subagents/agent-ab96c4264099694a9.jsonl +0 -0
- {cctx_cli-1.5.0 → cctx_cli-1.6.0}/tests/fixtures/claude_code/with-compaction/with-compaction/subagents/agent-ab96c4264099694a9.meta.json +0 -0
- {cctx_cli-1.5.0 → cctx_cli-1.6.0}/tests/fixtures/claude_code/with-compaction/with-compaction/subagents/agent-acb2895c5e34ffec0.jsonl +0 -0
- {cctx_cli-1.5.0 → cctx_cli-1.6.0}/tests/fixtures/claude_code/with-compaction/with-compaction/subagents/agent-acb2895c5e34ffec0.meta.json +0 -0
- {cctx_cli-1.5.0 → cctx_cli-1.6.0}/tests/fixtures/claude_code/with-compaction/with-compaction/subagents/agent-adb2302769938fb3f.jsonl +0 -0
- {cctx_cli-1.5.0 → cctx_cli-1.6.0}/tests/fixtures/claude_code/with-compaction/with-compaction/subagents/agent-adb2302769938fb3f.meta.json +0 -0
- {cctx_cli-1.5.0 → cctx_cli-1.6.0}/tests/fixtures/claude_code/with-compaction/with-compaction/subagents/agent-ae585eca15cb93b9c.jsonl +0 -0
- {cctx_cli-1.5.0 → cctx_cli-1.6.0}/tests/fixtures/claude_code/with-compaction/with-compaction/subagents/agent-ae585eca15cb93b9c.meta.json +0 -0
- {cctx_cli-1.5.0 → cctx_cli-1.6.0}/tests/fixtures/claude_code/with-compaction/with-compaction/subagents/agent-aec9c917feb903d67.jsonl +0 -0
- {cctx_cli-1.5.0 → cctx_cli-1.6.0}/tests/fixtures/claude_code/with-compaction/with-compaction/subagents/agent-aec9c917feb903d67.meta.json +0 -0
- {cctx_cli-1.5.0 → cctx_cli-1.6.0}/tests/fixtures/claude_code/with-compaction/with-compaction.jsonl +0 -0
- {cctx_cli-1.5.0 → cctx_cli-1.6.0}/tests/fixtures/claude_code/with-subagents/with-subagents/subagents/agent-a1a3a21aeb76bb0a9.jsonl +0 -0
- {cctx_cli-1.5.0 → cctx_cli-1.6.0}/tests/fixtures/claude_code/with-subagents/with-subagents/subagents/agent-a1a3a21aeb76bb0a9.meta.json +0 -0
- {cctx_cli-1.5.0 → cctx_cli-1.6.0}/tests/fixtures/claude_code/with-subagents/with-subagents/subagents/agent-aaa1d6ecc05a78442.jsonl +0 -0
- {cctx_cli-1.5.0 → cctx_cli-1.6.0}/tests/fixtures/claude_code/with-subagents/with-subagents/subagents/agent-aaa1d6ecc05a78442.meta.json +0 -0
- {cctx_cli-1.5.0 → cctx_cli-1.6.0}/tests/fixtures/claude_code/with-subagents/with-subagents/subagents/agent-af3c545ccd30036d2.jsonl +0 -0
- {cctx_cli-1.5.0 → cctx_cli-1.6.0}/tests/fixtures/claude_code/with-subagents/with-subagents/subagents/agent-af3c545ccd30036d2.meta.json +0 -0
- {cctx_cli-1.5.0 → cctx_cli-1.6.0}/tests/fixtures/claude_code/with-subagents/with-subagents/tool-results/btwp2bzro.txt +0 -0
- {cctx_cli-1.5.0 → cctx_cli-1.6.0}/tests/fixtures/claude_code/with-subagents/with-subagents/tool-results/byqjbgy4b.txt +0 -0
- {cctx_cli-1.5.0 → cctx_cli-1.6.0}/tests/fixtures/claude_code/with-subagents/with-subagents.jsonl +0 -0
- {cctx_cli-1.5.0 → cctx_cli-1.6.0}/tests/fixtures/claude_code/with-tool-results/with-tool-results/tool-results/bosbkda0h.txt +0 -0
- {cctx_cli-1.5.0 → cctx_cli-1.6.0}/tests/fixtures/claude_code/with-tool-results/with-tool-results.jsonl +0 -0
- {cctx_cli-1.5.0 → cctx_cli-1.6.0}/tests/fixtures/scrub.py +0 -0
- {cctx_cli-1.5.0 → cctx_cli-1.6.0}/tests/fixtures/synthetic/bookkeeping_only.jsonl +0 -0
- {cctx_cli-1.5.0 → cctx_cli-1.6.0}/tests/fixtures/synthetic/malformed_middle.jsonl +0 -0
- {cctx_cli-1.5.0 → cctx_cli-1.6.0}/tests/fixtures/synthetic/truncated_final_line.jsonl +0 -0
- {cctx_cli-1.5.0 → cctx_cli-1.6.0}/tests/fixtures/synthetic/unknown_attachment_shape.jsonl +0 -0
- {cctx_cli-1.5.0 → cctx_cli-1.6.0}/tests/fixtures/synthetic/unknown_type.jsonl +0 -0
- {cctx_cli-1.5.0 → cctx_cli-1.6.0}/tests/parsers/__init__.py +0 -0
- {cctx_cli-1.5.0 → cctx_cli-1.6.0}/tests/parsers/test_claude_code.py +0 -0
- {cctx_cli-1.5.0 → cctx_cli-1.6.0}/tests/parsers/test_claude_code_integration.py +0 -0
- {cctx_cli-1.5.0 → cctx_cli-1.6.0}/tests/recommender/__init__.py +0 -0
- {cctx_cli-1.5.0 → cctx_cli-1.6.0}/tests/recommender/test_claude_md.py +0 -0
- {cctx_cli-1.5.0 → cctx_cli-1.6.0}/tests/recommender/test_evidence.py +0 -0
- {cctx_cli-1.5.0 → cctx_cli-1.6.0}/tests/renderers/__init__.py +0 -0
- {cctx_cli-1.5.0 → cctx_cli-1.6.0}/tests/renderers/test_report.py +0 -0
- {cctx_cli-1.5.0 → cctx_cli-1.6.0}/tests/renderers/test_terminal_renderer_full.py +0 -0
- {cctx_cli-1.5.0 → cctx_cli-1.6.0}/tests/test_agents.py +0 -0
- {cctx_cli-1.5.0 → cctx_cli-1.6.0}/tests/test_aggregate.py +0 -0
- {cctx_cli-1.5.0 → cctx_cli-1.6.0}/tests/test_cli.py +0 -0
- {cctx_cli-1.5.0 → cctx_cli-1.6.0}/tests/test_cli_export.py +0 -0
- {cctx_cli-1.5.0 → cctx_cli-1.6.0}/tests/test_discovery.py +0 -0
- {cctx_cli-1.5.0 → cctx_cli-1.6.0}/tests/test_github_summary.py +0 -0
- {cctx_cli-1.5.0 → cctx_cli-1.6.0}/tests/test_harvest.py +0 -0
- {cctx_cli-1.5.0 → cctx_cli-1.6.0}/tests/test_harvest_check.py +0 -0
- {cctx_cli-1.5.0 → cctx_cli-1.6.0}/tests/test_models.py +0 -0
- {cctx_cli-1.5.0 → cctx_cli-1.6.0}/tests/test_models_project_pattern.py +0 -0
- {cctx_cli-1.5.0 → cctx_cli-1.6.0}/tests/test_smoke.py +0 -0
- {cctx_cli-1.5.0 → cctx_cli-1.6.0}/tests/test_terminal_renderer.py +0 -0
- {cctx_cli-1.5.0 → cctx_cli-1.6.0}/tests/test_tokenizer.py +0 -0
- {cctx_cli-1.5.0 → cctx_cli-1.6.0}/tests/test_trace_tui.py +0 -0
- {cctx_cli-1.5.0 → cctx_cli-1.6.0}/tests/test_watcher.py +0 -0
|
@@ -2,6 +2,80 @@
|
|
|
2
2
|
|
|
3
3
|
<!-- version list -->
|
|
4
4
|
|
|
5
|
+
## v1.6.0 (2026-06-10)
|
|
6
|
+
|
|
7
|
+
### Bug Fixes
|
|
8
|
+
|
|
9
|
+
- Harvest — preview_patches dedup per (target, heading) not heading-only
|
|
10
|
+
([#108](https://github.com/jacquardlabs/cctx/pull/108),
|
|
11
|
+
[`afa964c`](https://github.com/jacquardlabs/cctx/commit/afa964c68b445030e1fafe9f41c67a0de4afcd2d))
|
|
12
|
+
|
|
13
|
+
- Harvest — shorten local-import comment under 100-char line limit
|
|
14
|
+
([#108](https://github.com/jacquardlabs/cctx/pull/108),
|
|
15
|
+
[`afa964c`](https://github.com/jacquardlabs/cctx/commit/afa964c68b445030e1fafe9f41c67a0de4afcd2d))
|
|
16
|
+
|
|
17
|
+
### Documentation
|
|
18
|
+
|
|
19
|
+
- Harvest — correct misleading local-import comment
|
|
20
|
+
([#108](https://github.com/jacquardlabs/cctx/pull/108),
|
|
21
|
+
[`afa964c`](https://github.com/jacquardlabs/cctx/commit/afa964c68b445030e1fafe9f41c67a0de4afcd2d))
|
|
22
|
+
|
|
23
|
+
- Spec deviation note (sync returns patches) + PRODUCT.md cross-agent emit row
|
|
24
|
+
([#108](https://github.com/jacquardlabs/cctx/pull/108),
|
|
25
|
+
[`afa964c`](https://github.com/jacquardlabs/cctx/commit/afa964c68b445030e1fafe9f41c67a0de4afcd2d))
|
|
26
|
+
|
|
27
|
+
### Features
|
|
28
|
+
|
|
29
|
+
- Cctx harvest --emit — cross-agent layer to AGENTS.md (#82)
|
|
30
|
+
([#108](https://github.com/jacquardlabs/cctx/pull/108),
|
|
31
|
+
[`afa964c`](https://github.com/jacquardlabs/cctx/commit/afa964c68b445030e1fafe9f41c67a0de4afcd2d))
|
|
32
|
+
|
|
33
|
+
- Cli — harvest --emit / --sync cross-agent emit
|
|
34
|
+
([#108](https://github.com/jacquardlabs/cctx/pull/108),
|
|
35
|
+
[`afa964c`](https://github.com/jacquardlabs/cctx/commit/afa964c68b445030e1fafe9f41c67a0de4afcd2d))
|
|
36
|
+
|
|
37
|
+
- Harvest — EMIT_TARGETS + retarget_patches (fan-out to AGENTS.md)
|
|
38
|
+
([#108](https://github.com/jacquardlabs/cctx/pull/108),
|
|
39
|
+
[`afa964c`](https://github.com/jacquardlabs/cctx/commit/afa964c68b445030e1fafe9f41c67a0de4afcd2d))
|
|
40
|
+
|
|
41
|
+
- Harvest — sync_managed_sections backfills CLAUDE.md into emit target
|
|
42
|
+
([#108](https://github.com/jacquardlabs/cctx/pull/108),
|
|
43
|
+
[`afa964c`](https://github.com/jacquardlabs/cctx/commit/afa964c68b445030e1fafe9f41c67a0de4afcd2d))
|
|
44
|
+
|
|
45
|
+
- Models — MANAGED_HEADINGS registry for cctx-owned CLAUDE.md sections
|
|
46
|
+
([#108](https://github.com/jacquardlabs/cctx/pull/108),
|
|
47
|
+
[`afa964c`](https://github.com/jacquardlabs/cctx/commit/afa964c68b445030e1fafe9f41c67a0de4afcd2d))
|
|
48
|
+
|
|
49
|
+
### Testing
|
|
50
|
+
|
|
51
|
+
- Emit + sync idempotency through apply_patches
|
|
52
|
+
([#108](https://github.com/jacquardlabs/cctx/pull/108),
|
|
53
|
+
[`afa964c`](https://github.com/jacquardlabs/cctx/commit/afa964c68b445030e1fafe9f41c67a0de4afcd2d))
|
|
54
|
+
|
|
55
|
+
- End-to-end fan-out to both targets; spec: reconcile sync error contract
|
|
56
|
+
([#108](https://github.com/jacquardlabs/cctx/pull/108),
|
|
57
|
+
[`afa964c`](https://github.com/jacquardlabs/cctx/commit/afa964c68b445030e1fafe9f41c67a0de4afcd2d))
|
|
58
|
+
|
|
59
|
+
- Lock MANAGED_HEADINGS registry to recommender templates
|
|
60
|
+
([#108](https://github.com/jacquardlabs/cctx/pull/108),
|
|
61
|
+
[`afa964c`](https://github.com/jacquardlabs/cctx/commit/afa964c68b445030e1fafe9f41c67a0de4afcd2d))
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
## v1.5.1 (2026-06-10)
|
|
65
|
+
|
|
66
|
+
### Bug Fixes
|
|
67
|
+
|
|
68
|
+
- Recommender — add TOOL_THRASH/DEAD_END patch templates
|
|
69
|
+
([#107](https://github.com/jacquardlabs/cctx/pull/107),
|
|
70
|
+
[`3c79d58`](https://github.com/jacquardlabs/cctx/commit/3c79d58a55af106d3fd81542e70b8f892569185c))
|
|
71
|
+
|
|
72
|
+
### Documentation
|
|
73
|
+
|
|
74
|
+
- Product review 2026-06-09 + M15 cross-agent emit spec
|
|
75
|
+
([#107](https://github.com/jacquardlabs/cctx/pull/107),
|
|
76
|
+
[`3c79d58`](https://github.com/jacquardlabs/cctx/commit/3c79d58a55af106d3fd81542e70b8f892569185c))
|
|
77
|
+
|
|
78
|
+
|
|
5
79
|
## v1.5.0 (2026-05-20)
|
|
6
80
|
|
|
7
81
|
### 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,17 @@ 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
|
-
|
|
72
|
-
|
|
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 |
|
|
79
|
+
| Cross-agent emit | `cctx harvest --emit agents [--sync]` | M15; mirror CLAUDE.md sections to AGENTS.md — unreleased |
|
|
80
|
+
|
|
81
|
+
### Pattern classifiers (v1.4.0)
|
|
73
82
|
|
|
74
83
|
| Pattern | Status |
|
|
75
84
|
|---|---|
|
|
@@ -78,6 +87,7 @@ Four commands. No command is shallow. Users should be able to learn the product
|
|
|
78
87
|
| Stale context | Shipped |
|
|
79
88
|
| Dead-end exploration | Shipped (v0.2.0) |
|
|
80
89
|
| Tool thrashing | Shipped (v0.2.0) |
|
|
90
|
+
| Project-specific patterns (cross-session) | Shipped (v1.3.0) |
|
|
81
91
|
|
|
82
92
|
## What we are NOT building
|
|
83
93
|
|
|
@@ -87,12 +97,14 @@ Four commands. No command is shallow. Users should be able to learn the product
|
|
|
87
97
|
- A fork-and-replay debugger
|
|
88
98
|
- A general eval or testing framework
|
|
89
99
|
|
|
90
|
-
## Known problems (as of 2026-
|
|
100
|
+
## Known problems (as of 2026-06-09)
|
|
101
|
+
|
|
102
|
+
**Active gaps:**
|
|
91
103
|
|
|
92
|
-
|
|
104
|
+
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.
|
|
93
105
|
|
|
94
|
-
|
|
106
|
+
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)
|
|
95
107
|
|
|
96
|
-
|
|
108
|
+
3. **Cross-agent layer not started.** Tracked as M15 / #82 — the final step of the original growth staircase.
|
|
97
109
|
|
|
98
|
-
|
|
110
|
+
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)
|
|
@@ -23,6 +23,7 @@ from cctx.agents import live_sessions as _live_sessions
|
|
|
23
23
|
from cctx.diagnostician import aggregate
|
|
24
24
|
from cctx.diagnostician.patterns import project_specific
|
|
25
25
|
from cctx.discovery import complete_project as _complete_project
|
|
26
|
+
from cctx.harvest import EMIT_TARGETS
|
|
26
27
|
from cctx.models import KIND_LABEL, AggregateReport
|
|
27
28
|
from cctx.parsers.claude_code import parse_session
|
|
28
29
|
from cctx.recommender import claude_md
|
|
@@ -570,6 +571,22 @@ def trace(target: Path | None, latest: bool) -> None:
|
|
|
570
571
|
show_default=True,
|
|
571
572
|
help="Minimum severity that causes --check to exit 1.",
|
|
572
573
|
)
|
|
574
|
+
@click.option(
|
|
575
|
+
"--emit",
|
|
576
|
+
"emit_targets",
|
|
577
|
+
multiple=True,
|
|
578
|
+
type=click.Choice(list(EMIT_TARGETS)),
|
|
579
|
+
help="Also write applicable patches to another agent's instruction file "
|
|
580
|
+
"(e.g. AGENTS.md). Repeatable.",
|
|
581
|
+
)
|
|
582
|
+
@click.option(
|
|
583
|
+
"--sync",
|
|
584
|
+
"sync_mode",
|
|
585
|
+
is_flag=True,
|
|
586
|
+
default=False,
|
|
587
|
+
help="With --emit: also mirror already-harvested cctx-managed sections "
|
|
588
|
+
"from CLAUDE.md into the emit target.",
|
|
589
|
+
)
|
|
573
590
|
def harvest(
|
|
574
591
|
target: Path,
|
|
575
592
|
since: str | None,
|
|
@@ -578,9 +595,20 @@ def harvest(
|
|
|
578
595
|
target_dir: Path | None,
|
|
579
596
|
check_mode: bool,
|
|
580
597
|
check_severity: str,
|
|
598
|
+
emit_targets: tuple[str, ...],
|
|
599
|
+
sync_mode: bool,
|
|
581
600
|
) -> None:
|
|
582
601
|
"""Apply autopsy patches to CLAUDE.md."""
|
|
583
|
-
from cctx.harvest import
|
|
602
|
+
from cctx.harvest import (
|
|
603
|
+
apply_patches,
|
|
604
|
+
check_claude_md,
|
|
605
|
+
preview_patches,
|
|
606
|
+
retarget_patches,
|
|
607
|
+
sync_managed_sections,
|
|
608
|
+
)
|
|
609
|
+
|
|
610
|
+
if sync_mode and not emit_targets:
|
|
611
|
+
raise click.UsageError("--sync requires --emit.")
|
|
584
612
|
|
|
585
613
|
if check_mode:
|
|
586
614
|
from cctx.harvest import CheckSeverity
|
|
@@ -626,6 +654,13 @@ def harvest(
|
|
|
626
654
|
diagnosis = claude_md.generate(diagnosis)
|
|
627
655
|
patches = diagnosis.patches
|
|
628
656
|
|
|
657
|
+
base = patches
|
|
658
|
+
for t in emit_targets:
|
|
659
|
+
emitted = retarget_patches(base, t)
|
|
660
|
+
if sync_mode:
|
|
661
|
+
emitted = emitted + sync_managed_sections(resolved_dir, t)
|
|
662
|
+
patches = patches + emitted
|
|
663
|
+
|
|
629
664
|
if not patches:
|
|
630
665
|
render_harvest_results([], dry_run=dry_run)
|
|
631
666
|
return
|
|
@@ -13,6 +13,7 @@ Layering rules (MUST respect):
|
|
|
13
13
|
"""
|
|
14
14
|
from __future__ import annotations
|
|
15
15
|
|
|
16
|
+
import dataclasses
|
|
16
17
|
import re
|
|
17
18
|
from collections import defaultdict
|
|
18
19
|
from dataclasses import dataclass
|
|
@@ -20,6 +21,8 @@ from enum import Enum
|
|
|
20
21
|
from pathlib import Path
|
|
21
22
|
from typing import TYPE_CHECKING
|
|
22
23
|
|
|
24
|
+
from cctx.models import MANAGED_HEADING_PREFIX, MANAGED_HEADINGS
|
|
25
|
+
|
|
23
26
|
if TYPE_CHECKING:
|
|
24
27
|
from cctx.models import Patch
|
|
25
28
|
|
|
@@ -105,6 +108,70 @@ def _is_supported_target(patch: Patch) -> bool:
|
|
|
105
108
|
# Public API
|
|
106
109
|
# ---------------------------------------------------------------------------
|
|
107
110
|
|
|
111
|
+
# Maps an --emit target name to the destination filename. Single place to add
|
|
112
|
+
# future targets (Cursor, Windsurf, Copilot) when demand exists.
|
|
113
|
+
EMIT_TARGETS: dict[str, str] = {
|
|
114
|
+
"agents": "AGENTS.md",
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def retarget_patches(patches: list[Patch], emit_target: str) -> list[Patch]:
|
|
119
|
+
"""Clone CLAUDE.md-targeted patches to the emit target's file.
|
|
120
|
+
|
|
121
|
+
Only patches whose target_file is exactly "CLAUDE.md" are emitted —
|
|
122
|
+
.claude/rules/ and .claude/skills/ patches are Claude Code-specific and do
|
|
123
|
+
not translate to other agents. Returns clones; inputs are unmodified.
|
|
124
|
+
"""
|
|
125
|
+
dest = EMIT_TARGETS[emit_target]
|
|
126
|
+
return [
|
|
127
|
+
dataclasses.replace(p, target_file=dest)
|
|
128
|
+
for p in patches
|
|
129
|
+
if p.target_file == "CLAUDE.md"
|
|
130
|
+
]
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
# Reverse map: exact managed heading -> the FindingKind that owns it.
|
|
134
|
+
_HEADING_TO_KIND = {heading: kind for kind, heading in MANAGED_HEADINGS.items()}
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
def sync_managed_sections(target_dir: Path, emit_target: str) -> list[Patch]:
|
|
138
|
+
"""Build synthetic patches mirroring CLAUDE.md's cctx-managed sections.
|
|
139
|
+
|
|
140
|
+
Reads CLAUDE.md in target_dir, keeps sections whose heading is an exact
|
|
141
|
+
MANAGED_HEADINGS value or starts with MANAGED_HEADING_PREFIX, and returns
|
|
142
|
+
one Patch per kept section targeting the emit file. Returns [] if CLAUDE.md
|
|
143
|
+
is absent. The CLI routes these through preview_patches / apply_patches, so
|
|
144
|
+
idempotency and dry-run come for free from the existing machinery.
|
|
145
|
+
"""
|
|
146
|
+
from cctx.models import FindingKind, Patch # runtime use (Patch is TYPE_CHECKING-only above)
|
|
147
|
+
|
|
148
|
+
claude_md = target_dir / "CLAUDE.md"
|
|
149
|
+
if not claude_md.exists():
|
|
150
|
+
return []
|
|
151
|
+
|
|
152
|
+
dest = EMIT_TARGETS[emit_target]
|
|
153
|
+
content = claude_md.read_text(encoding="utf-8")
|
|
154
|
+
patches: list[Patch] = []
|
|
155
|
+
|
|
156
|
+
for heading, body in _parse_sections(content):
|
|
157
|
+
is_fixed = heading in _HEADING_TO_KIND
|
|
158
|
+
is_prefixed = heading.startswith(MANAGED_HEADING_PREFIX)
|
|
159
|
+
if not (is_fixed or is_prefixed):
|
|
160
|
+
continue
|
|
161
|
+
|
|
162
|
+
kind = _HEADING_TO_KIND[heading] if is_fixed else FindingKind.PROJECT_PATTERN
|
|
163
|
+
diff_lines = [heading] + body.splitlines()
|
|
164
|
+
unified_diff = "\n".join(f"+{line}" for line in diff_lines)
|
|
165
|
+
patches.append(Patch(
|
|
166
|
+
target_file=dest,
|
|
167
|
+
description=heading,
|
|
168
|
+
unified_diff=unified_diff,
|
|
169
|
+
finding_kind=kind,
|
|
170
|
+
evidence_summary="synced from CLAUDE.md",
|
|
171
|
+
))
|
|
172
|
+
|
|
173
|
+
return patches
|
|
174
|
+
|
|
108
175
|
|
|
109
176
|
def apply_patch(patch: Patch, target_dir: Path) -> ApplyResult:
|
|
110
177
|
"""Apply one patch. Never raises — errors go into ApplyResult(status=ERROR)."""
|
|
@@ -161,8 +228,10 @@ def apply_patch(patch: Patch, target_dir: Path) -> ApplyResult:
|
|
|
161
228
|
def preview_patches(patches: list[Patch], target_dir: Path) -> list[ApplyResult]:
|
|
162
229
|
"""Compute what would happen without writing. Returns APPLIED or SKIPPED."""
|
|
163
230
|
results = []
|
|
164
|
-
# Track
|
|
165
|
-
|
|
231
|
+
# Track (target_path, fingerprint) pairs already "seen" within this preview
|
|
232
|
+
# run (idempotency). Keyed by file so the same heading in two different
|
|
233
|
+
# target files is correctly treated as two independent patches.
|
|
234
|
+
seen_fingerprints: set[tuple[Path, str]] = set()
|
|
166
235
|
|
|
167
236
|
for patch in patches:
|
|
168
237
|
target_path = target_dir / patch.target_file
|
|
@@ -181,7 +250,8 @@ def preview_patches(patches: list[Patch], target_dir: Path) -> list[ApplyResult]
|
|
|
181
250
|
|
|
182
251
|
content = target_path.read_text(encoding="utf-8") if target_path.exists() else ""
|
|
183
252
|
|
|
184
|
-
|
|
253
|
+
already_seen = fp is not None and (target_path, fp) in seen_fingerprints
|
|
254
|
+
if fp is not None and (_already_present(content, fp) or already_seen):
|
|
185
255
|
results.append(ApplyResult(
|
|
186
256
|
patch=patch,
|
|
187
257
|
status=ApplyStatus.SKIPPED,
|
|
@@ -190,7 +260,7 @@ def preview_patches(patches: list[Patch], target_dir: Path) -> list[ApplyResult]
|
|
|
190
260
|
))
|
|
191
261
|
else:
|
|
192
262
|
if fp is not None:
|
|
193
|
-
seen_fingerprints.add(fp)
|
|
263
|
+
seen_fingerprints.add((target_path, fp))
|
|
194
264
|
results.append(ApplyResult(
|
|
195
265
|
patch=patch,
|
|
196
266
|
status=ApplyStatus.APPLIED,
|
|
@@ -184,6 +184,22 @@ KIND_LABEL: dict[FindingKind, str] = {
|
|
|
184
184
|
FindingKind.PROJECT_PATTERN: "PROJECT PATTERN",
|
|
185
185
|
}
|
|
186
186
|
|
|
187
|
+
# Maps FindingKind to the exact ## heading emitted by its recommender patch
|
|
188
|
+
# template. Single source of truth for "which CLAUDE.md sections cctx owns."
|
|
189
|
+
# harvest.py imports this (never reaches into recommender/) so emit/sync can
|
|
190
|
+
# identify cctx-managed sections without depending on the patch generator.
|
|
191
|
+
MANAGED_HEADINGS: dict[FindingKind, str] = {
|
|
192
|
+
FindingKind.RETRY_LOOP: "## Retry discipline",
|
|
193
|
+
FindingKind.SCOPE_CREEP: "## Scope discipline",
|
|
194
|
+
FindingKind.STALE_CONTEXT: "## Context hygiene",
|
|
195
|
+
FindingKind.TOOL_THRASH: "## Tool-call discipline",
|
|
196
|
+
FindingKind.DEAD_END: "## Exploration discipline",
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
# Project-specific patterns use a heading that embeds tool+key, so the managed
|
|
200
|
+
# section is identified by prefix rather than exact match.
|
|
201
|
+
MANAGED_HEADING_PREFIX: str = "## Project-specific: "
|
|
202
|
+
|
|
187
203
|
|
|
188
204
|
class Severity(str, Enum):
|
|
189
205
|
HIGH = "high"
|
|
@@ -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
|
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
# cctx Product Health Review — 2026-06-09
|
|
2
|
+
|
|
3
|
+
Second review. Prior review: `2026-05-15-product-review.md`. Source of product truth: `PRODUCT.md` (created from the prior review's draft), with `cctx-project-brief.md` as the original pitch and growth staircase.
|
|
4
|
+
|
|
5
|
+
**Headline: the growth staircase is one step from complete.** Every milestone from the original brief — autopsy, cross-session, harvest, memory hygiene, live mode — has shipped. The issue board holds three open issues, one of which (#80) actually shipped in v1.4.0. The only substantive roadmap item left is the cross-agent layer (#82, M15). The product needs two things now: PRODUCT.md brought up to date (it describes v0.2.0; reality is v1.4.0+), and a deliberate decision about what comes after the staircase.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Part 1 — Is PRODUCT.md still true?
|
|
10
|
+
|
|
11
|
+
### 1. Persona check
|
|
12
|
+
|
|
13
|
+
Everything shipped since the last review — M9 polish (verdict headline, `--top`, `--turn`), M11 GitHub Action, M12 output completeness (`--until`, `autopsy --json`, `export --format json`), M13 harvest `--check` depth, M14 project-specific patterns, and the unreleased live-session integration — serves the same single developer reviewing their own sessions. No team features, no dashboards, no hypothetical users.
|
|
14
|
+
|
|
15
|
+
**Verdict: no persona drift.** The discipline noted in the prior review has held through eight releases.
|
|
16
|
+
|
|
17
|
+
### 2. Principles check
|
|
18
|
+
|
|
19
|
+
**P1 — Forensic, not ambient.** *Bent, deliberately.* `cctx watch` (M10) and the in-flight `claude agents --json` live integration (live badges in `cctx ls`, early idle exit in the watcher) are real-time features. The staircase sanctioned this as v2 ("Live mode — optional"), so it is roadmap-aligned, not drift — but PRODUCT.md's principle text and its "NOT for: real-time monitoring" line now contradict the shipped product. The doc must evolve: forensic-first, with live mode as an explicitly opt-in companion.
|
|
20
|
+
|
|
21
|
+
**P2 — Output must be actionable.** Honored. M14 project-specific patterns emit CLAUDE.md patches via `generate_from_patterns()`; harvest `--check` depth findings carry severity and location.
|
|
22
|
+
|
|
23
|
+
**P3 — Honest about uncertainty.** Honored. The prior review's gap (cost confidence not surfaced) was fixed in #69 — terminal and HTML output annotate costs as ~85–95% estimates.
|
|
24
|
+
|
|
25
|
+
**P4 — Local and stateless.** Honored, with one footnote: the live path now shells out to the `claude` CLI (`claude agents --json`). Still local, no network, gracefully degrades when the binary is absent — but it is the first external-binary dependency in any code path and should be named in the principle.
|
|
26
|
+
|
|
27
|
+
**P5 — Deterministic.** Fully honored. All four new analysis surfaces since the last review (tool-thrash, dead-end, project-specific patterns, check-depth detectors) are heuristic — Jaccard similarity, keyword matching, n-gram overlap. No LLM calls anywhere outside the opt-in tokenizer.
|
|
28
|
+
|
|
29
|
+
**P6 — Small surface, deep on each command.** The text says "Four commands." There are six: `ls`, `autopsy`, `export`, `trace`, `harvest`, `watch`. The spirit holds — each verb is deep and the surface is learnable in an afternoon — but the letter is false.
|
|
30
|
+
|
|
31
|
+
### 3. Feature map accuracy
|
|
32
|
+
|
|
33
|
+
PRODUCT.md is headed "Feature map (v0.2.0)". The released version is **v1.4.0** (2026-05-20), with live-session integration unreleased on main. Missing from the map:
|
|
34
|
+
|
|
35
|
+
| Shipped feature | Where | Version |
|
|
36
|
+
|---|---|---|
|
|
37
|
+
| Verdict headline | `autopsy` terminal output | v1.1.0 (M9) |
|
|
38
|
+
| `--top N` on cross-session autopsy | `autopsy --since` | v1.1.0 (M9) |
|
|
39
|
+
| `--turn N` drill-down | `autopsy` | v1.1.0 (M9) |
|
|
40
|
+
| `--until DATE` | `autopsy --since` | v1.2.0 (M12) |
|
|
41
|
+
| Machine-readable diagnosis | `autopsy --json` | v1.2.0 (M12) |
|
|
42
|
+
| JSON export | `export --format json` | v1.2.0 (M12) |
|
|
43
|
+
| Project-specific pattern detection | `autopsy --since` / `harvest --since` | v1.3.0 (M14) |
|
|
44
|
+
| Contradiction / redundancy / staleness detectors | `harvest --check`, `--check-severity` | v1.4.0 (M13) |
|
|
45
|
+
| Live session badges | `cctx ls` | unreleased |
|
|
46
|
+
| Live session detection + early idle exit | `cctx watch` | unreleased |
|
|
47
|
+
|
|
48
|
+
The classifier table is also incomplete: project-specific patterns (M14) is a sixth detection surface alongside the five listed.
|
|
49
|
+
|
|
50
|
+
### 4. "Not building" check
|
|
51
|
+
|
|
52
|
+
- "Real-time monitoring (completed sessions only in v0 and v1)" — **crossed**, per the staircase's own v2 plan. Remove from the NOT-for list; replace with what genuinely stays out (background daemons, persistent monitoring state, alerting).
|
|
53
|
+
- No SaaS, no agent behavior, no multi-provider, no fork-and-replay, no eval framework — all still clean. `claude agents --json` is reading local state from a local binary, not provider expansion.
|
|
54
|
+
|
|
55
|
+
### 5. Known problems freshness
|
|
56
|
+
|
|
57
|
+
1. **"watch polling is simple"** — partially fixed. Early idle exit via `claude agents --json` landed (unreleased); 1s polling without fsevents/inotify remains. Update, don't remove.
|
|
58
|
+
2. **"`--format json` on export not shipped"** — **fixed in v1.2.0**. Remove.
|
|
59
|
+
3. **"Cross-agent layer not started… no milestone yet"** — stale. It now has milestone M15 and issue #82. Update.
|
|
60
|
+
|
|
61
|
+
**New problems to add:**
|
|
62
|
+
4. **Subagent traces are parsed but never diagnosed.** The parser recursively parses subagent sessions (`models.py:151`, `parsers/claude_code.py:165`), the tokenizer counts their tokens, but no classifier or cost attribution ever reads `trace.subagents`. As agent fan-out becomes the dominant Claude Code workflow, autopsy is blind to where an increasing share of the spend goes.
|
|
63
|
+
5. **Issue board hygiene.** #80 shipped in v1.4.0 (PR #87) but is still open. #85 has no milestone.
|
|
64
|
+
6. **Roadmap exhaustion.** After M15 there is no defined direction. Not a bug — but the next planning conversation is overdue.
|
|
65
|
+
|
|
66
|
+
---
|
|
67
|
+
|
|
68
|
+
## Part 2 — Product coherence
|
|
69
|
+
|
|
70
|
+
### One product?
|
|
71
|
+
|
|
72
|
+
Yes — more so than at the last review. The loop now has a clean narrative arc that matches a real day: `cctx ls` (what sessions exist, which are live) → `cctx watch` (catch waste during) → `cctx autopsy` (diagnose after) → `cctx harvest` (persist the lesson) → `harvest --check` (keep the memory honest). `trace` and `export` are the inspection/escape hatches. Every command operates on the same `SessionTrace → Diagnosis → Patch` chain.
|
|
73
|
+
|
|
74
|
+
### Feature interaction
|
|
75
|
+
|
|
76
|
+
- `ls` ↔ `watch` via live badges: good new connection (unreleased).
|
|
77
|
+
- `autopsy → harvest`: still the spine, now strengthened by M14 (cross-session patterns produce evidence-backed patches).
|
|
78
|
+
- **Gap: `watch` findings are ephemeral.** A waste signal surfaced live evaporates when the session ends; the user must re-run autopsy to act on it. A natural connection — watch ending with "run `cctx autopsy --latest` to harvest these findings" or writing a findings stub — is unbuilt.
|
|
79
|
+
- **Gap: harvest never learns whether its patches worked.** Patches carry fingerprints and sessions carry dates; nothing compares pattern recurrence before/after a patch was applied. This is the strongest unbuilt connection in the product (see proposals).
|
|
80
|
+
|
|
81
|
+
### Complexity audit
|
|
82
|
+
|
|
83
|
+
Nothing is deadweight. `trace` (Textual TUI) remains the highest-maintenance surface relative to use; acceptable while it stays stable. The two JSON outputs (`autopsy --json` vs `export --format json`) are different shapes for different purposes (diagnosis vs raw turns) — fine, but the README should say which to use when.
|
|
84
|
+
|
|
85
|
+
### Onboarding
|
|
86
|
+
|
|
87
|
+
Dramatically improved since the last review: `pip install cctx` → `cctx ls` → `cctx autopsy --latest` is a genuine under-60-second path, and the README exists. Remaining friction: a clean session yields "no findings," which still reads anticlimactic on a first run — consider always showing the cost/token decomposition so a clean session still demonstrates value.
|
|
88
|
+
|
|
89
|
+
---
|
|
90
|
+
|
|
91
|
+
## Part 3 — Proposed PRODUCT.md updates
|
|
92
|
+
|
|
93
|
+
Presented as a diff; not applied.
|
|
94
|
+
|
|
95
|
+
```diff
|
|
96
|
+
--- PRODUCT.md
|
|
97
|
+
+++ PRODUCT.md
|
|
98
|
+
@@ -5,7 +5,8 @@
|
|
99
|
+
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.
|
|
100
|
+
|
|
101
|
+
-It is a forensic tool, not a monitoring tool. You reach for it after a session — when something felt off, when the bill was higher than expected, or on a weekly review pass.
|
|
102
|
+
+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.
|
|
103
|
+
|
|
104
|
+
@@ -20,9 +21,9 @@
|
|
105
|
+
## What cctx is NOT for
|
|
106
|
+
|
|
107
|
+
- Teams or organizations (no shared reports, no access control, no multi-user state)
|
|
108
|
+
-- Real-time monitoring (completed sessions only in v0 and v1)
|
|
109
|
+
+- Ambient monitoring (no daemons, no persistent watcher state, no alerting — `cctx watch` is a foreground command you start and stop)
|
|
110
|
+
- General cost dashboards (CodeBurn covers that; cctx is forensic)
|
|
111
|
+
- Multi-provider support (Claude Code only in v0/v1)
|
|
112
|
+
- Users who do not read session transcripts or maintain CLAUDE.md
|
|
113
|
+
|
|
114
|
+
@@ -30,8 +31,8 @@
|
|
115
|
+
**1. Forensic, not ambient.**
|
|
116
|
+
-cctx is used after something goes wrong, not as background infrastructure. Every feature should be justified by this use case: "I just ran a session, something went sideways, I want to know what."
|
|
117
|
+
+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.
|
|
118
|
+
|
|
119
|
+
@@ -39,7 +40,7 @@
|
|
120
|
+
**4. Local and stateless.**
|
|
121
|
+
-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.
|
|
122
|
+
+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.
|
|
123
|
+
|
|
124
|
+
@@ -45,7 +46,7 @@
|
|
125
|
+
**6. Small surface, deep on each command.**
|
|
126
|
+
-Four commands. No command is shallow. Users should be able to learn the product in an afternoon and trust what it tells them.
|
|
127
|
+
+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.
|
|
128
|
+
|
|
129
|
+
@@ -48,2 +49,2 @@
|
|
130
|
+
-## Feature map (v0.2.0)
|
|
131
|
+
+## Feature map (v1.4.0)
|
|
132
|
+
|
|
133
|
+
@@ (append to Shipped table)
|
|
134
|
+
+| Verdict headline + `--top N` + `--turn N` | `autopsy` | v1.1.0 (M9) |
|
|
135
|
+
+| `--until DATE` on cross-session mode | `autopsy --since ... --until` | v1.2.0 (M12) |
|
|
136
|
+
+| Machine-readable diagnosis | `cctx autopsy <session> --json` | v1.2.0 (M12) |
|
|
137
|
+
+| JSON export | `cctx export <session> --format json` | v1.2.0 (M12) |
|
|
138
|
+
+| Project-specific pattern detection | `autopsy`/`harvest` `--since` | v1.3.0 (M14) |
|
|
139
|
+
+| Memory-hygiene depth | `harvest --check` + `--check-severity` | v1.4.0 (M13) |
|
|
140
|
+
+| Live session badges | `cctx ls` | unreleased |
|
|
141
|
+
+| Live session detection, early idle exit | `cctx watch` | unreleased |
|
|
142
|
+
|
|
143
|
+
@@ (classifier table)
|
|
144
|
+
+| Project-specific patterns (cross-session) | Shipped (v1.3.0) |
|
|
145
|
+
|
|
146
|
+
@@ -90,12 +99,14 @@
|
|
147
|
+
-## Known problems (as of 2026-05-16)
|
|
148
|
+
+## Known problems (as of 2026-06-09)
|
|
149
|
+
|
|
150
|
+
-1. **`cctx watch` polling is simple.** Polls every 1s and re-runs classifiers on any file growth. Does not debounce or use `fsevents`/`inotify`. Fine for v0 but will chatter on active sessions.
|
|
151
|
+
+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.
|
|
152
|
+
|
|
153
|
+
-2. **`--format json` on `export` not shipped.** `--html` moved to `autopsy --html`; `json` format on the `export` subcommand is still deferred.
|
|
154
|
+
+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.
|
|
155
|
+
|
|
156
|
+
-3. **Cross-agent layer not started.** Emitting findings as `.cursorrules`, `AGENTS.md`, `.windsurfrules`, or GitHub Copilot instructions is a roadmap item with no milestone yet.
|
|
157
|
+
+3. **Cross-agent layer not started.** Tracked as M15 / #82 — the final step of the original growth staircase.
|
|
158
|
+
+
|
|
159
|
+
+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.
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
---
|
|
163
|
+
|
|
164
|
+
## Issue board actions
|
|
165
|
+
|
|
166
|
+
1. **Close #80** — shipped in v1.4.0 via PR #87 (`check_contradictions`/`check_redundancy`/`check_staleness` + `--check-severity` are on main). Close with a comment linking the PR.
|
|
167
|
+
2. **#85 (fuzzy/semantic normalization, M14 Option B)** — assign a milestone or label it explicitly as icebox. As written it risks drifting toward embedding/LLM territory; if pursued, constrain to deterministic techniques (stemming, edit distance) per P5.
|
|
168
|
+
3. **#82 (M15 cross-agent layer)** — the committed next milestone. Note: emitting `AGENTS.md`/`.cursorrules` partially relaxes "multi-provider support" in the NOT-for list; the distinction worth preserving is *we write other agents' config formats; we do not parse other agents' logs*.
|
|
169
|
+
|
|
170
|
+
## Post-staircase feature proposals
|
|
171
|
+
|
|
172
|
+
Ranked by leverage. All are deterministic, local, and persona-aligned.
|
|
173
|
+
|
|
174
|
+
**1. Subagent-aware diagnosis (highest leverage).** The data model is already built — parser recurses into subagent sessions, tokenizer counts them — only the analysis is missing. Ship: (a) per-subagent cost attribution in the autopsy decomposition ("$1.84 of $3.10 went to 7 subagents"), (b) a fan-out waste classifier (overlapping subagent work, failed-and-retried agents, subagent results never referenced by the parent). Claude Code's evolution (Task tool, workflows, parallel agents) makes this the fastest-growing blind spot in the product.
|
|
175
|
+
|
|
176
|
+
**2. Patch efficacy tracking.** `harvest` patches carry fingerprints; sessions carry dates. Compare pattern recurrence before/after a patch's application date: "retry-loop fired in 5 sessions the week before the 2026-05-20 patch; 0 since." This converts cctx from *suggesting* improvements to *proving* them — no other tool in the space closes this loop, and it requires no new data collection.
|
|
177
|
+
|
|
178
|
+
**3. Loaded-but-never-used context waste (MCP servers / skills).** Extend the brief's binary waste decision to context overhead: MCP tool definitions and skills that are loaded into every request but never invoked across N sessions. Finding: "MCP server X adds ~8K tokens/request and was never called in 30 sessions — disable it for this project." Binary signal, high confidence, directly actionable patch (settings change).
|
|
179
|
+
|
|
180
|
+
**4. `cctx init` — SessionEnd hook installer.** The biggest adoption friction is remembering to run cctx. A command that installs an opt-in Claude Code SessionEnd/Stop hook running `cctx autopsy --latest --quiet` and printing a one-line verdict *only when findings exist*. Preserves forensic-first (output appears only when something went sideways) while removing the memory burden.
|
|
181
|
+
|
|
182
|
+
**5. Compaction findings.** Compaction events are already detected (classifiers reset on them) but never reported. Promote to a finding: compaction count, content re-fetched after compaction (re-reading a file that was compacted away is concrete, attributable waste), and a "compact earlier" recommendation when stale context preceded a forced compaction.
|
|
183
|
+
|
|
184
|
+
**6. Cross-project digest.** `cctx autopsy --all --since 7d` — the persona's stated "weekly review pass" currently requires running per-project. Aggregate the aggregates; lowest effort of the six.
|
|
185
|
+
|
|
186
|
+
Suggested sequencing: board hygiene now → M15 (#82) finishes the staircase → M16 subagent-aware diagnosis → M17 patch efficacy. Items 3–6 slot in as polish-scale milestones between or after.
|