cc-session-tools 2.3.0__tar.gz → 2.4.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.
- {cc_session_tools-2.3.0/src/cc_session_tools.egg-info → cc_session_tools-2.4.0}/PKG-INFO +14 -3
- {cc_session_tools-2.3.0 → cc_session_tools-2.4.0}/README.md +13 -2
- {cc_session_tools-2.3.0 → cc_session_tools-2.4.0}/pyproject.toml +1 -1
- {cc_session_tools-2.3.0 → cc_session_tools-2.4.0}/src/cc_session_tools/cli/ccst.py +88 -16
- {cc_session_tools-2.3.0 → cc_session_tools-2.4.0}/src/cc_session_tools/lib/doctor.py +80 -8
- {cc_session_tools-2.3.0 → cc_session_tools-2.4.0}/src/cc_session_tools/lib/doctor_mutes.py +12 -1
- cc_session_tools-2.4.0/src/cc_session_tools/lib/install_sync.py +103 -0
- {cc_session_tools-2.3.0 → cc_session_tools-2.4.0}/src/cc_session_tools/lib/sessions_db.py +6 -0
- {cc_session_tools-2.3.0 → cc_session_tools-2.4.0/src/cc_session_tools.egg-info}/PKG-INFO +14 -3
- {cc_session_tools-2.3.0 → cc_session_tools-2.4.0}/src/cc_session_tools.egg-info/SOURCES.txt +3 -0
- {cc_session_tools-2.3.0 → cc_session_tools-2.4.0}/src/cccs_hooks/bash_security_review.py +43 -37
- {cc_session_tools-2.3.0 → cc_session_tools-2.4.0}/src/cccs_hooks/normalise.py +4 -0
- {cc_session_tools-2.3.0 → cc_session_tools-2.4.0}/tests/test_bash_security_review.py +222 -0
- {cc_session_tools-2.3.0 → cc_session_tools-2.4.0}/tests/test_ccst_doctor.py +154 -0
- cc_session_tools-2.4.0/tests/test_ccst_install_everything.py +293 -0
- cc_session_tools-2.4.0/tests/test_ccst_main_gate.py +197 -0
- {cc_session_tools-2.3.0 → cc_session_tools-2.4.0}/tests/test_ccst_repair_sessions_cli.py +39 -0
- {cc_session_tools-2.3.0 → cc_session_tools-2.4.0}/tests/test_doctor_mutes.py +13 -0
- cc_session_tools-2.4.0/tests/test_install_sync.py +155 -0
- {cc_session_tools-2.3.0 → cc_session_tools-2.4.0}/tests/test_normalise.py +34 -0
- {cc_session_tools-2.3.0 → cc_session_tools-2.4.0}/tests/test_sessions_db.py +2 -2
- cc_session_tools-2.3.0/tests/test_ccst_install_everything.py +0 -160
- {cc_session_tools-2.3.0 → cc_session_tools-2.4.0}/LICENSE +0 -0
- {cc_session_tools-2.3.0 → cc_session_tools-2.4.0}/setup.cfg +0 -0
- {cc_session_tools-2.3.0 → cc_session_tools-2.4.0}/src/cc_session_tools/__init__.py +0 -0
- {cc_session_tools-2.3.0 → cc_session_tools-2.4.0}/src/cc_session_tools/cli/__init__.py +0 -0
- {cc_session_tools-2.3.0 → cc_session_tools-2.4.0}/src/cc_session_tools/cli/ccd.py +0 -0
- {cc_session_tools-2.3.0 → cc_session_tools-2.4.0}/src/cc_session_tools/cli/ccmsg.py +0 -0
- {cc_session_tools-2.3.0 → cc_session_tools-2.4.0}/src/cc_session_tools/cli/ccr.py +0 -0
- {cc_session_tools-2.3.0 → cc_session_tools-2.4.0}/src/cc_session_tools/cli/ccs.py +0 -0
- {cc_session_tools-2.3.0 → cc_session_tools-2.4.0}/src/cc_session_tools/cli/ccsched.py +0 -0
- {cc_session_tools-2.3.0 → cc_session_tools-2.4.0}/src/cc_session_tools/cli/migrate_ccmsg.py +0 -0
- {cc_session_tools-2.3.0 → cc_session_tools-2.4.0}/src/cc_session_tools/cli/migrate_ccsched.py +0 -0
- {cc_session_tools-2.3.0 → cc_session_tools-2.4.0}/src/cc_session_tools/cli/migrate_sessions_db.py +0 -0
- {cc_session_tools-2.3.0 → cc_session_tools-2.4.0}/src/cc_session_tools/cli/migrate_telemetry.py +0 -0
- {cc_session_tools-2.3.0 → cc_session_tools-2.4.0}/src/cc_session_tools/config/hooks-bundle.json +0 -0
- {cc_session_tools-2.3.0 → cc_session_tools-2.4.0}/src/cc_session_tools/config/session-tag-hook.json +0 -0
- {cc_session_tools-2.3.0 → cc_session_tools-2.4.0}/src/cc_session_tools/hooks_install.py +0 -0
- {cc_session_tools-2.3.0 → cc_session_tools-2.4.0}/src/cc_session_tools/lib/__init__.py +0 -0
- {cc_session_tools-2.3.0 → cc_session_tools-2.4.0}/src/cc_session_tools/lib/claude_flags.py +0 -0
- {cc_session_tools-2.3.0 → cc_session_tools-2.4.0}/src/cc_session_tools/lib/claude_md_install.py +0 -0
- {cc_session_tools-2.3.0 → cc_session_tools-2.4.0}/src/cc_session_tools/lib/db.py +0 -0
- {cc_session_tools-2.3.0 → cc_session_tools-2.4.0}/src/cc_session_tools/lib/debug.py +0 -0
- {cc_session_tools-2.3.0 → cc_session_tools-2.4.0}/src/cc_session_tools/lib/hook_registry.py +0 -0
- {cc_session_tools-2.3.0 → cc_session_tools-2.4.0}/src/cc_session_tools/lib/levenshtein.py +0 -0
- {cc_session_tools-2.3.0 → cc_session_tools-2.4.0}/src/cc_session_tools/lib/messaging/__init__.py +0 -0
- {cc_session_tools-2.3.0 → cc_session_tools-2.4.0}/src/cc_session_tools/lib/messaging/addressing.py +0 -0
- {cc_session_tools-2.3.0 → cc_session_tools-2.4.0}/src/cc_session_tools/lib/messaging/cursor.py +0 -0
- {cc_session_tools-2.3.0 → cc_session_tools-2.4.0}/src/cc_session_tools/lib/messaging/lock.py +0 -0
- {cc_session_tools-2.3.0 → cc_session_tools-2.4.0}/src/cc_session_tools/lib/messaging/message.py +0 -0
- {cc_session_tools-2.3.0 → cc_session_tools-2.4.0}/src/cc_session_tools/lib/messaging/move_safety.py +0 -0
- {cc_session_tools-2.3.0 → cc_session_tools-2.4.0}/src/cc_session_tools/lib/messaging/repository.py +0 -0
- {cc_session_tools-2.3.0 → cc_session_tools-2.4.0}/src/cc_session_tools/lib/messaging/retention.py +0 -0
- {cc_session_tools-2.3.0 → cc_session_tools-2.4.0}/src/cc_session_tools/lib/messaging/service.py +0 -0
- {cc_session_tools-2.3.0 → cc_session_tools-2.4.0}/src/cc_session_tools/lib/messaging/store.py +0 -0
- {cc_session_tools-2.3.0 → cc_session_tools-2.4.0}/src/cc_session_tools/lib/paths.py +0 -0
- {cc_session_tools-2.3.0 → cc_session_tools-2.4.0}/src/cc_session_tools/lib/pdata/__init__.py +0 -0
- {cc_session_tools-2.3.0 → cc_session_tools-2.4.0}/src/cc_session_tools/lib/pdata/backup.py +0 -0
- {cc_session_tools-2.3.0 → cc_session_tools-2.4.0}/src/cc_session_tools/lib/pdata/classify.py +0 -0
- {cc_session_tools-2.3.0 → cc_session_tools-2.4.0}/src/cc_session_tools/lib/pdata/cutover.py +0 -0
- {cc_session_tools-2.3.0 → cc_session_tools-2.4.0}/src/cc_session_tools/lib/pdata/formatting.py +0 -0
- {cc_session_tools-2.3.0 → cc_session_tools-2.4.0}/src/cc_session_tools/lib/pdata/importers.py +0 -0
- {cc_session_tools-2.3.0 → cc_session_tools-2.4.0}/src/cc_session_tools/lib/pdata/init_paths.py +0 -0
- {cc_session_tools-2.3.0 → cc_session_tools-2.4.0}/src/cc_session_tools/lib/pdata/init_service.py +0 -0
- {cc_session_tools-2.3.0 → cc_session_tools-2.4.0}/src/cc_session_tools/lib/pdata/manifest.py +0 -0
- {cc_session_tools-2.3.0 → cc_session_tools-2.4.0}/src/cc_session_tools/lib/pdata/naming.py +0 -0
- {cc_session_tools-2.3.0 → cc_session_tools-2.4.0}/src/cc_session_tools/lib/pdata/repository.py +0 -0
- {cc_session_tools-2.3.0 → cc_session_tools-2.4.0}/src/cc_session_tools/lib/pdata/service.py +0 -0
- {cc_session_tools-2.3.0 → cc_session_tools-2.4.0}/src/cc_session_tools/lib/pdata/session_output.py +0 -0
- {cc_session_tools-2.3.0 → cc_session_tools-2.4.0}/src/cc_session_tools/lib/pdata/store.py +0 -0
- {cc_session_tools-2.3.0 → cc_session_tools-2.4.0}/src/cc_session_tools/lib/pdata/verify.py +0 -0
- {cc_session_tools-2.3.0 → cc_session_tools-2.4.0}/src/cc_session_tools/lib/picker.py +0 -0
- {cc_session_tools-2.3.0 → cc_session_tools-2.4.0}/src/cc_session_tools/lib/prompts.py +0 -0
- {cc_session_tools-2.3.0 → cc_session_tools-2.4.0}/src/cc_session_tools/lib/roots.py +0 -0
- {cc_session_tools-2.3.0 → cc_session_tools-2.4.0}/src/cc_session_tools/lib/rules.py +0 -0
- {cc_session_tools-2.3.0 → cc_session_tools-2.4.0}/src/cc_session_tools/lib/scheduler/__init__.py +0 -0
- {cc_session_tools-2.3.0 → cc_session_tools-2.4.0}/src/cc_session_tools/lib/scheduler/bundled_jobs.py +0 -0
- {cc_session_tools-2.3.0 → cc_session_tools-2.4.0}/src/cc_session_tools/lib/scheduler/cadence.py +0 -0
- {cc_session_tools-2.3.0 → cc_session_tools-2.4.0}/src/cc_session_tools/lib/scheduler/cursor.py +0 -0
- {cc_session_tools-2.3.0 → cc_session_tools-2.4.0}/src/cc_session_tools/lib/scheduler/digest.py +0 -0
- {cc_session_tools-2.3.0 → cc_session_tools-2.4.0}/src/cc_session_tools/lib/scheduler/due.py +0 -0
- {cc_session_tools-2.3.0 → cc_session_tools-2.4.0}/src/cc_session_tools/lib/scheduler/duration.py +0 -0
- {cc_session_tools-2.3.0 → cc_session_tools-2.4.0}/src/cc_session_tools/lib/scheduler/jobspec.py +0 -0
- {cc_session_tools-2.3.0 → cc_session_tools-2.4.0}/src/cc_session_tools/lib/scheduler/ledger.py +0 -0
- {cc_session_tools-2.3.0 → cc_session_tools-2.4.0}/src/cc_session_tools/lib/scheduler/lock.py +0 -0
- {cc_session_tools-2.3.0 → cc_session_tools-2.4.0}/src/cc_session_tools/lib/scheduler/notify.py +0 -0
- {cc_session_tools-2.3.0 → cc_session_tools-2.4.0}/src/cc_session_tools/lib/scheduler/reconcile.py +0 -0
- {cc_session_tools-2.3.0 → cc_session_tools-2.4.0}/src/cc_session_tools/lib/scheduler/registry.py +0 -0
- {cc_session_tools-2.3.0 → cc_session_tools-2.4.0}/src/cc_session_tools/lib/scheduler/runner.py +0 -0
- {cc_session_tools-2.3.0 → cc_session_tools-2.4.0}/src/cc_session_tools/lib/scheduler/state.py +0 -0
- {cc_session_tools-2.3.0 → cc_session_tools-2.4.0}/src/cc_session_tools/lib/scheduler/store.py +0 -0
- {cc_session_tools-2.3.0 → cc_session_tools-2.4.0}/src/cc_session_tools/lib/scheduler/surface.py +0 -0
- {cc_session_tools-2.3.0 → cc_session_tools-2.4.0}/src/cc_session_tools/lib/scheduler/throttle.py +0 -0
- {cc_session_tools-2.3.0 → cc_session_tools-2.4.0}/src/cc_session_tools/lib/scheduler/worker.py +0 -0
- {cc_session_tools-2.3.0 → cc_session_tools-2.4.0}/src/cc_session_tools/lib/session_gc.py +0 -0
- {cc_session_tools-2.3.0 → cc_session_tools-2.4.0}/src/cc_session_tools/lib/sessions.py +0 -0
- {cc_session_tools-2.3.0 → cc_session_tools-2.4.0}/src/cc_session_tools/lib/sessions_repair.py +0 -0
- {cc_session_tools-2.3.0 → cc_session_tools-2.4.0}/src/cc_session_tools/lib/shell_install.py +0 -0
- {cc_session_tools-2.3.0 → cc_session_tools-2.4.0}/src/cc_session_tools/lib/tasklist.py +0 -0
- {cc_session_tools-2.3.0 → cc_session_tools-2.4.0}/src/cc_session_tools/lib/telemetry_store.py +0 -0
- {cc_session_tools-2.3.0 → cc_session_tools-2.4.0}/src/cc_session_tools/skills/analyse-cc-usage/SKILL.md +0 -0
- {cc_session_tools-2.3.0 → cc_session_tools-2.4.0}/src/cc_session_tools/skills/delete-sessions/SKILL.md +0 -0
- {cc_session_tools-2.3.0 → cc_session_tools-2.4.0}/src/cc_session_tools/skills/delete-sessions/scripts/delete_sessions.py +0 -0
- {cc_session_tools-2.3.0 → cc_session_tools-2.4.0}/src/cc_session_tools/skills/do-executor-critic-assessor-loop/SKILL.md +0 -0
- {cc_session_tools-2.3.0 → cc_session_tools-2.4.0}/src/cc_session_tools/skills/find-claude-code-session/SKILL.md +0 -0
- {cc_session_tools-2.3.0 → cc_session_tools-2.4.0}/src/cc_session_tools/skills/generate-8digit-code/SKILL.md +0 -0
- {cc_session_tools-2.3.0 → cc_session_tools-2.4.0}/src/cc_session_tools/skills/generate-8digit-code/scripts/generate_8digit_code.py +0 -0
- {cc_session_tools-2.3.0 → cc_session_tools-2.4.0}/src/cc_session_tools/skills/list-empty-sessions/SKILL.md +0 -0
- {cc_session_tools-2.3.0 → cc_session_tools-2.4.0}/src/cc_session_tools/skills/list-empty-sessions/scripts/list_empty_sessions.py +0 -0
- {cc_session_tools-2.3.0 → cc_session_tools-2.4.0}/src/cc_session_tools/skills/manage-recurring-cc-jobs-using-ccsched/SKILL.md +0 -0
- {cc_session_tools-2.3.0 → cc_session_tools-2.4.0}/src/cc_session_tools/skills/move-session/SKILL.md +0 -0
- {cc_session_tools-2.3.0 → cc_session_tools-2.4.0}/src/cc_session_tools/skills/move-session/hooks/sessionstart-pending-rename.sh +0 -0
- {cc_session_tools-2.3.0 → cc_session_tools-2.4.0}/src/cc_session_tools/skills/move-session/scripts/cc_session_rules.py +0 -0
- {cc_session_tools-2.3.0 → cc_session_tools-2.4.0}/src/cc_session_tools/skills/move-session/scripts/move_session.py +0 -0
- {cc_session_tools-2.3.0 → cc_session_tools-2.4.0}/src/cc_session_tools/skills/pm-pdata-conflict-resolution/SKILL.md +0 -0
- {cc_session_tools-2.3.0 → cc_session_tools-2.4.0}/src/cc_session_tools/skills/pm-pdata-schema-design/SKILL.md +0 -0
- {cc_session_tools-2.3.0 → cc_session_tools-2.4.0}/src/cc_session_tools/skills/pm-project-init/SKILL.md +0 -0
- {cc_session_tools-2.3.0 → cc_session_tools-2.4.0}/src/cc_session_tools/skills/pm-update-central-files/SKILL.md +0 -0
- {cc_session_tools-2.3.0 → cc_session_tools-2.4.0}/src/cc_session_tools/skills/reduce-persistent-context/CALIBRATION.md +0 -0
- {cc_session_tools-2.3.0 → cc_session_tools-2.4.0}/src/cc_session_tools/skills/reduce-persistent-context/SKILL.md +0 -0
- {cc_session_tools-2.3.0 → cc_session_tools-2.4.0}/src/cc_session_tools/skills/reduce-persistent-context/scripts/analyze_context.py +0 -0
- {cc_session_tools-2.3.0 → cc_session_tools-2.4.0}/src/cc_session_tools/skills/reduce-persistent-context/scripts/duplicates.py +0 -0
- {cc_session_tools-2.3.0 → cc_session_tools-2.4.0}/src/cc_session_tools/skills/reduce-persistent-context/scripts/measure.py +0 -0
- {cc_session_tools-2.3.0 → cc_session_tools-2.4.0}/src/cc_session_tools/skills/reduce-persistent-context/scripts/report.py +0 -0
- {cc_session_tools-2.3.0 → cc_session_tools-2.4.0}/src/cc_session_tools/skills/reduce-persistent-context/scripts/tokens.py +0 -0
- {cc_session_tools-2.3.0 → cc_session_tools-2.4.0}/src/cc_session_tools/skills/reduce-persistent-context/scripts/usage.py +0 -0
- {cc_session_tools-2.3.0 → cc_session_tools-2.4.0}/src/cc_session_tools/skills/select-agent-model/SKILL.md +0 -0
- {cc_session_tools-2.3.0 → cc_session_tools-2.4.0}/src/cc_session_tools/skills/send-session-message/SKILL.md +0 -0
- {cc_session_tools-2.3.0 → cc_session_tools-2.4.0}/src/cc_session_tools/skills/update-command-cache/SKILL.md +0 -0
- {cc_session_tools-2.3.0 → cc_session_tools-2.4.0}/src/cc_session_tools/skills/update-command-cache/scripts/update_command_cache.py +0 -0
- {cc_session_tools-2.3.0 → cc_session_tools-2.4.0}/src/cc_session_tools.egg-info/dependency_links.txt +0 -0
- {cc_session_tools-2.3.0 → cc_session_tools-2.4.0}/src/cc_session_tools.egg-info/entry_points.txt +0 -0
- {cc_session_tools-2.3.0 → cc_session_tools-2.4.0}/src/cc_session_tools.egg-info/requires.txt +0 -0
- {cc_session_tools-2.3.0 → cc_session_tools-2.4.0}/src/cc_session_tools.egg-info/top_level.txt +0 -0
- {cc_session_tools-2.3.0 → cc_session_tools-2.4.0}/src/cccs_hooks/__init__.py +0 -0
- {cc_session_tools-2.3.0 → cc_session_tools-2.4.0}/src/cccs_hooks/after_response.py +0 -0
- {cc_session_tools-2.3.0 → cc_session_tools-2.4.0}/src/cccs_hooks/bash_hard_deny.py +0 -0
- {cc_session_tools-2.3.0 → cc_session_tools-2.4.0}/src/cccs_hooks/cache.py +0 -0
- {cc_session_tools-2.3.0 → cc_session_tools-2.4.0}/src/cccs_hooks/catchup.py +0 -0
- {cc_session_tools-2.3.0 → cc_session_tools-2.4.0}/src/cccs_hooks/confirm_8digit.py +0 -0
- {cc_session_tools-2.3.0 → cc_session_tools-2.4.0}/src/cccs_hooks/last_screenshot.py +0 -0
- {cc_session_tools-2.3.0 → cc_session_tools-2.4.0}/src/cccs_hooks/marker_allow.py +0 -0
- {cc_session_tools-2.3.0 → cc_session_tools-2.4.0}/src/cccs_hooks/markers.py +0 -0
- {cc_session_tools-2.3.0 → cc_session_tools-2.4.0}/src/cccs_hooks/messaging_deliver.py +0 -0
- {cc_session_tools-2.3.0 → cc_session_tools-2.4.0}/src/cccs_hooks/pending_migration.py +0 -0
- {cc_session_tools-2.3.0 → cc_session_tools-2.4.0}/src/cccs_hooks/session_tag.py +0 -0
- {cc_session_tools-2.3.0 → cc_session_tools-2.4.0}/src/cccs_hooks/stats.py +0 -0
- {cc_session_tools-2.3.0 → cc_session_tools-2.4.0}/src/cccs_hooks/telemetry.py +0 -0
- {cc_session_tools-2.3.0 → cc_session_tools-2.4.0}/src/cccs_hooks/telemetry_query.py +0 -0
- {cc_session_tools-2.3.0 → cc_session_tools-2.4.0}/src/cccs_hooks/telemetry_trim.py +0 -0
- {cc_session_tools-2.3.0 → cc_session_tools-2.4.0}/src/cccs_hooks/transcript.py +0 -0
- {cc_session_tools-2.3.0 → cc_session_tools-2.4.0}/src/cccs_hooks/worklog_guard.py +0 -0
- {cc_session_tools-2.3.0 → cc_session_tools-2.4.0}/src/claude_code_usage/__init__.py +0 -0
- {cc_session_tools-2.3.0 → cc_session_tools-2.4.0}/src/claude_code_usage/attribution.py +0 -0
- {cc_session_tools-2.3.0 → cc_session_tools-2.4.0}/src/claude_code_usage/cache.py +0 -0
- {cc_session_tools-2.3.0 → cc_session_tools-2.4.0}/src/claude_code_usage/ccusage_wrapper.py +0 -0
- {cc_session_tools-2.3.0 → cc_session_tools-2.4.0}/src/claude_code_usage/cli.py +0 -0
- {cc_session_tools-2.3.0 → cc_session_tools-2.4.0}/src/claude_code_usage/data/pricing.json +0 -0
- {cc_session_tools-2.3.0 → cc_session_tools-2.4.0}/src/claude_code_usage/parent_inference.py +0 -0
- {cc_session_tools-2.3.0 → cc_session_tools-2.4.0}/src/claude_code_usage/parser.py +0 -0
- {cc_session_tools-2.3.0 → cc_session_tools-2.4.0}/src/claude_code_usage/pricing.py +0 -0
- {cc_session_tools-2.3.0 → cc_session_tools-2.4.0}/src/claude_code_usage/query.py +0 -0
- {cc_session_tools-2.3.0 → cc_session_tools-2.4.0}/src/claude_code_usage/report.py +0 -0
- {cc_session_tools-2.3.0 → cc_session_tools-2.4.0}/src/claude_code_usage/schema.py +0 -0
- {cc_session_tools-2.3.0 → cc_session_tools-2.4.0}/src/claude_code_usage/session_names.py +0 -0
- {cc_session_tools-2.3.0 → cc_session_tools-2.4.0}/tests/test_after_response.py +0 -0
- {cc_session_tools-2.3.0 → cc_session_tools-2.4.0}/tests/test_attribution.py +0 -0
- {cc_session_tools-2.3.0 → cc_session_tools-2.4.0}/tests/test_bash_hard_deny.py +0 -0
- {cc_session_tools-2.3.0 → cc_session_tools-2.4.0}/tests/test_cache.py +0 -0
- {cc_session_tools-2.3.0 → cc_session_tools-2.4.0}/tests/test_cache_sqlite.py +0 -0
- {cc_session_tools-2.3.0 → cc_session_tools-2.4.0}/tests/test_ccr_orphans.py +0 -0
- {cc_session_tools-2.3.0 → cc_session_tools-2.4.0}/tests/test_ccs_emptiness.py +0 -0
- {cc_session_tools-2.3.0 → cc_session_tools-2.4.0}/tests/test_ccs_eta.py +0 -0
- {cc_session_tools-2.3.0 → cc_session_tools-2.4.0}/tests/test_ccs_list_mode.py +0 -0
- {cc_session_tools-2.3.0 → cc_session_tools-2.4.0}/tests/test_ccs_scope_flags.py +0 -0
- {cc_session_tools-2.3.0 → cc_session_tools-2.4.0}/tests/test_ccs_sentinel_sort.py +0 -0
- {cc_session_tools-2.3.0 → cc_session_tools-2.4.0}/tests/test_ccs_session_counts.py +0 -0
- {cc_session_tools-2.3.0 → cc_session_tools-2.4.0}/tests/test_ccst_bundle_discovery.py +0 -0
- {cc_session_tools-2.3.0 → cc_session_tools-2.4.0}/tests/test_ccst_ccsched_jobs_cli.py +0 -0
- {cc_session_tools-2.3.0 → cc_session_tools-2.4.0}/tests/test_ccst_cli.py +0 -0
- {cc_session_tools-2.3.0 → cc_session_tools-2.4.0}/tests/test_ccst_gc_report.py +0 -0
- {cc_session_tools-2.3.0 → cc_session_tools-2.4.0}/tests/test_ccst_hook_dispatcher.py +0 -0
- {cc_session_tools-2.3.0 → cc_session_tools-2.4.0}/tests/test_ccst_hooks_install_prune.py +0 -0
- {cc_session_tools-2.3.0 → cc_session_tools-2.4.0}/tests/test_ccst_hooks_install_zero_arg.py +0 -0
- {cc_session_tools-2.3.0 → cc_session_tools-2.4.0}/tests/test_ccst_hooks_uninstall.py +0 -0
- {cc_session_tools-2.3.0 → cc_session_tools-2.4.0}/tests/test_ccst_pdata_cli.py +0 -0
- {cc_session_tools-2.3.0 → cc_session_tools-2.4.0}/tests/test_ccst_pdata_init_cli.py +0 -0
- {cc_session_tools-2.3.0 → cc_session_tools-2.4.0}/tests/test_ccst_pdata_reconcile_cli.py +0 -0
- {cc_session_tools-2.3.0 → cc_session_tools-2.4.0}/tests/test_ccst_pdata_verify_cli.py +0 -0
- {cc_session_tools-2.3.0 → cc_session_tools-2.4.0}/tests/test_ccst_sessions_cli.py +0 -0
- {cc_session_tools-2.3.0 → cc_session_tools-2.4.0}/tests/test_ccst_shell_install.py +0 -0
- {cc_session_tools-2.3.0 → cc_session_tools-2.4.0}/tests/test_ccst_skills_install.py +0 -0
- {cc_session_tools-2.3.0 → cc_session_tools-2.4.0}/tests/test_ccst_skills_uninstall.py +0 -0
- {cc_session_tools-2.3.0 → cc_session_tools-2.4.0}/tests/test_ccst_telemetry_query.py +0 -0
- {cc_session_tools-2.3.0 → cc_session_tools-2.4.0}/tests/test_ccst_telemetry_trim.py +0 -0
- {cc_session_tools-2.3.0 → cc_session_tools-2.4.0}/tests/test_ccusage_wrapper.py +0 -0
- {cc_session_tools-2.3.0 → cc_session_tools-2.4.0}/tests/test_claude_flags.py +0 -0
- {cc_session_tools-2.3.0 → cc_session_tools-2.4.0}/tests/test_claude_md_install.py +0 -0
- {cc_session_tools-2.3.0 → cc_session_tools-2.4.0}/tests/test_cli.py +0 -0
- {cc_session_tools-2.3.0 → cc_session_tools-2.4.0}/tests/test_cli_ccd.py +0 -0
- {cc_session_tools-2.3.0 → cc_session_tools-2.4.0}/tests/test_cli_ccd_dry_run.py +0 -0
- {cc_session_tools-2.3.0 → cc_session_tools-2.4.0}/tests/test_cli_ccr.py +0 -0
- {cc_session_tools-2.3.0 → cc_session_tools-2.4.0}/tests/test_cli_ccs.py +0 -0
- {cc_session_tools-2.3.0 → cc_session_tools-2.4.0}/tests/test_cli_version.py +0 -0
- {cc_session_tools-2.3.0 → cc_session_tools-2.4.0}/tests/test_confirm_8digit.py +0 -0
- {cc_session_tools-2.3.0 → cc_session_tools-2.4.0}/tests/test_db.py +0 -0
- {cc_session_tools-2.3.0 → cc_session_tools-2.4.0}/tests/test_debug.py +0 -0
- {cc_session_tools-2.3.0 → cc_session_tools-2.4.0}/tests/test_empty_session.py +0 -0
- {cc_session_tools-2.3.0 → cc_session_tools-2.4.0}/tests/test_hook_cache.py +0 -0
- {cc_session_tools-2.3.0 → cc_session_tools-2.4.0}/tests/test_hooks_install.py +0 -0
- {cc_session_tools-2.3.0 → cc_session_tools-2.4.0}/tests/test_last_screenshot.py +0 -0
- {cc_session_tools-2.3.0 → cc_session_tools-2.4.0}/tests/test_levenshtein.py +0 -0
- {cc_session_tools-2.3.0 → cc_session_tools-2.4.0}/tests/test_marker_allow.py +0 -0
- {cc_session_tools-2.3.0 → cc_session_tools-2.4.0}/tests/test_migrate_ccmsg.py +0 -0
- {cc_session_tools-2.3.0 → cc_session_tools-2.4.0}/tests/test_migrate_ccsched.py +0 -0
- {cc_session_tools-2.3.0 → cc_session_tools-2.4.0}/tests/test_migrate_sessions_db.py +0 -0
- {cc_session_tools-2.3.0 → cc_session_tools-2.4.0}/tests/test_migrate_telemetry.py +0 -0
- {cc_session_tools-2.3.0 → cc_session_tools-2.4.0}/tests/test_parent_inference.py +0 -0
- {cc_session_tools-2.3.0 → cc_session_tools-2.4.0}/tests/test_parser.py +0 -0
- {cc_session_tools-2.3.0 → cc_session_tools-2.4.0}/tests/test_paths.py +0 -0
- {cc_session_tools-2.3.0 → cc_session_tools-2.4.0}/tests/test_pending_migration_hook.py +0 -0
- {cc_session_tools-2.3.0 → cc_session_tools-2.4.0}/tests/test_picker.py +0 -0
- {cc_session_tools-2.3.0 → cc_session_tools-2.4.0}/tests/test_pricing.py +0 -0
- {cc_session_tools-2.3.0 → cc_session_tools-2.4.0}/tests/test_prompts.py +0 -0
- {cc_session_tools-2.3.0 → cc_session_tools-2.4.0}/tests/test_query.py +0 -0
- {cc_session_tools-2.3.0 → cc_session_tools-2.4.0}/tests/test_report.py +0 -0
- {cc_session_tools-2.3.0 → cc_session_tools-2.4.0}/tests/test_roots.py +0 -0
- {cc_session_tools-2.3.0 → cc_session_tools-2.4.0}/tests/test_rules.py +0 -0
- {cc_session_tools-2.3.0 → cc_session_tools-2.4.0}/tests/test_scheduler_bundled_jobs.py +0 -0
- {cc_session_tools-2.3.0 → cc_session_tools-2.4.0}/tests/test_schema.py +0 -0
- {cc_session_tools-2.3.0 → cc_session_tools-2.4.0}/tests/test_session_names.py +0 -0
- {cc_session_tools-2.3.0 → cc_session_tools-2.4.0}/tests/test_session_tag.py +0 -0
- {cc_session_tools-2.3.0 → cc_session_tools-2.4.0}/tests/test_sessions.py +0 -0
- {cc_session_tools-2.3.0 → cc_session_tools-2.4.0}/tests/test_sessions_repair.py +0 -0
- {cc_session_tools-2.3.0 → cc_session_tools-2.4.0}/tests/test_tasklist.py +0 -0
- {cc_session_tools-2.3.0 → cc_session_tools-2.4.0}/tests/test_telemetry.py +0 -0
- {cc_session_tools-2.3.0 → cc_session_tools-2.4.0}/tests/test_telemetry_store.py +0 -0
- {cc_session_tools-2.3.0 → cc_session_tools-2.4.0}/tests/test_transcript.py +0 -0
- {cc_session_tools-2.3.0 → cc_session_tools-2.4.0}/tests/test_worklog_guard.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: cc-session-tools
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.4.0
|
|
4
4
|
Summary: Claude Code session management CLI tools (ccd, ccr, ccs, claude-code-usage, ccst) and hook library (cccs_hooks)
|
|
5
5
|
Author: raffishquartan
|
|
6
6
|
License: MIT License
|
|
@@ -144,6 +144,12 @@ It runs `ccst doctor` at the end so you can see the health check immediately. Re
|
|
|
144
144
|
|
|
145
145
|
> **Note:** `install-everything.sh` handles steps 1–5 of setup. Step 6 — adding broader CCST guidance to your global `~/.claude/CLAUDE.md` (session management, 8-digit gate, etc.) — is interactive and must be run separately afterwards. See [Configure your global CLAUDE.md](#configure-your-global-claudemd) below.
|
|
146
146
|
|
|
147
|
+
> **After upgrading** (`uv tool install --reinstall`/`--upgrade`, or `pip`/`pipx` equivalents):
|
|
148
|
+
> re-run `ccst install-everything --apply` so skills/hooks/shell functions/scheduled jobs stay in
|
|
149
|
+
> sync with the new version. If you forget, `ccst` itself will remind you — any interactive
|
|
150
|
+
> command run from a real terminal on a version that hasn't been synced prints a nudge and exits
|
|
151
|
+
> until you do (automated callers like Claude Code's own hooks are never affected).
|
|
152
|
+
|
|
147
153
|
**Manual path — step by step:**
|
|
148
154
|
|
|
149
155
|
```sh
|
|
@@ -884,14 +890,19 @@ ccst claude-md uninstall --apply
|
|
|
884
890
|
|
|
885
891
|
Run a full health check: PATH for all six CLIs, env vars (`REPO_ROOT`/`PROJ_ROOT`),
|
|
886
892
|
`~/.claude/settings.json` JSON validity, expected hook registrations present,
|
|
887
|
-
skill symlinks correct and pointing at the installed source,
|
|
888
|
-
|
|
893
|
+
skill symlinks correct and pointing at the installed source, whether
|
|
894
|
+
`install-everything --apply` has been run for the currently-installed version,
|
|
895
|
+
and version drift between installed `ccst` and the latest release on PyPI.
|
|
889
896
|
|
|
890
897
|
```sh
|
|
891
898
|
ccst doctor # checks everything, including PyPI version check
|
|
892
899
|
ccst doctor --no-pypi # skip the network version check (useful in CI)
|
|
900
|
+
ccst doctor --all # also print OK checks (default: only WARN/FAIL)
|
|
893
901
|
```
|
|
894
902
|
|
|
903
|
+
By default only WARN/FAIL results are printed — a clean machine is otherwise dozens of `[OK]`
|
|
904
|
+
lines to scroll past. Use `--all` to see the full check list, including everything that passed.
|
|
905
|
+
|
|
895
906
|
Exit `0` if all checks are OK. Exit `1` if any check is WARN or FAIL.
|
|
896
907
|
|
|
897
908
|
### `ccst telemetry trim`
|
|
@@ -87,6 +87,12 @@ It runs `ccst doctor` at the end so you can see the health check immediately. Re
|
|
|
87
87
|
|
|
88
88
|
> **Note:** `install-everything.sh` handles steps 1–5 of setup. Step 6 — adding broader CCST guidance to your global `~/.claude/CLAUDE.md` (session management, 8-digit gate, etc.) — is interactive and must be run separately afterwards. See [Configure your global CLAUDE.md](#configure-your-global-claudemd) below.
|
|
89
89
|
|
|
90
|
+
> **After upgrading** (`uv tool install --reinstall`/`--upgrade`, or `pip`/`pipx` equivalents):
|
|
91
|
+
> re-run `ccst install-everything --apply` so skills/hooks/shell functions/scheduled jobs stay in
|
|
92
|
+
> sync with the new version. If you forget, `ccst` itself will remind you — any interactive
|
|
93
|
+
> command run from a real terminal on a version that hasn't been synced prints a nudge and exits
|
|
94
|
+
> until you do (automated callers like Claude Code's own hooks are never affected).
|
|
95
|
+
|
|
90
96
|
**Manual path — step by step:**
|
|
91
97
|
|
|
92
98
|
```sh
|
|
@@ -827,14 +833,19 @@ ccst claude-md uninstall --apply
|
|
|
827
833
|
|
|
828
834
|
Run a full health check: PATH for all six CLIs, env vars (`REPO_ROOT`/`PROJ_ROOT`),
|
|
829
835
|
`~/.claude/settings.json` JSON validity, expected hook registrations present,
|
|
830
|
-
skill symlinks correct and pointing at the installed source,
|
|
831
|
-
|
|
836
|
+
skill symlinks correct and pointing at the installed source, whether
|
|
837
|
+
`install-everything --apply` has been run for the currently-installed version,
|
|
838
|
+
and version drift between installed `ccst` and the latest release on PyPI.
|
|
832
839
|
|
|
833
840
|
```sh
|
|
834
841
|
ccst doctor # checks everything, including PyPI version check
|
|
835
842
|
ccst doctor --no-pypi # skip the network version check (useful in CI)
|
|
843
|
+
ccst doctor --all # also print OK checks (default: only WARN/FAIL)
|
|
836
844
|
```
|
|
837
845
|
|
|
846
|
+
By default only WARN/FAIL results are printed — a clean machine is otherwise dozens of `[OK]`
|
|
847
|
+
lines to scroll past. Use `--all` to see the full check list, including everything that passed.
|
|
848
|
+
|
|
838
849
|
Exit `0` if all checks are OK. Exit `1` if any check is WARN or FAIL.
|
|
839
850
|
|
|
840
851
|
### `ccst telemetry trim`
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "cc-session-tools"
|
|
7
|
-
version = "2.
|
|
7
|
+
version = "2.4.0"
|
|
8
8
|
description = "Claude Code session management CLI tools (ccd, ccr, ccs, claude-code-usage, ccst) and hook library (cccs_hooks)"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = { file = "LICENSE" }
|
|
@@ -653,6 +653,7 @@ def _cmd_doctor(args: argparse.Namespace) -> int:
|
|
|
653
653
|
|
|
654
654
|
from cc_session_tools.lib.pdata import verify as _pdata_verify
|
|
655
655
|
from cc_session_tools.lib.pdata.init_paths import default_projects_root
|
|
656
|
+
from cc_session_tools.lib import install_sync
|
|
656
657
|
|
|
657
658
|
results = run_all_checks(
|
|
658
659
|
installed_version=__version__,
|
|
@@ -667,6 +668,7 @@ def _cmd_doctor(args: argparse.Namespace) -> int:
|
|
|
667
668
|
projects_root=default_projects_root(),
|
|
668
669
|
pdata_verify_projects=_pdata_verify.discover_projects(),
|
|
669
670
|
sessions_db_path=store_paths["sessions"],
|
|
671
|
+
synced_version=install_sync.get_synced_version(),
|
|
670
672
|
)
|
|
671
673
|
|
|
672
674
|
if args.drift or getattr(args, "mode", None) == "drift":
|
|
@@ -677,7 +679,7 @@ def _cmd_doctor(args: argparse.Namespace) -> int:
|
|
|
677
679
|
print(report)
|
|
678
680
|
return 1 if unmuted else 0
|
|
679
681
|
|
|
680
|
-
print(format_results(results))
|
|
682
|
+
print(format_results(results, show_all=getattr(args, "all", False)))
|
|
681
683
|
|
|
682
684
|
any_issue = any(r.status in (Status.WARN, Status.FAIL) for r in results)
|
|
683
685
|
return 1 if any_issue else 0
|
|
@@ -1239,6 +1241,8 @@ def _cmd_sessions_list(args: argparse.Namespace) -> int:
|
|
|
1239
1241
|
|
|
1240
1242
|
|
|
1241
1243
|
def _cmd_repair_sessions(args: argparse.Namespace) -> int:
|
|
1244
|
+
import sqlite3
|
|
1245
|
+
|
|
1242
1246
|
from cc_session_tools.lib import db as db_lib
|
|
1243
1247
|
from cc_session_tools.lib import sessions_db, sessions_repair
|
|
1244
1248
|
from cc_session_tools.lib.roots import RootsConfigError, load_session_roots
|
|
@@ -1250,21 +1254,32 @@ def _cmd_repair_sessions(args: argparse.Namespace) -> int:
|
|
|
1250
1254
|
print(str(e), file=sys.stderr)
|
|
1251
1255
|
return 1
|
|
1252
1256
|
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1257
|
+
# sqlite3.connect() (and the online backup API below) open lazily and
|
|
1258
|
+
# only fail once a statement actually touches the file, so a corrupt-
|
|
1259
|
+
# but-existing sessions.db reaches this try, not the exists() check
|
|
1260
|
+
# above. This is the tool users are told to run to fix store corruption
|
|
1261
|
+
# - both the backup step and the repair itself must fail loudly with a
|
|
1262
|
+
# clear message here, not a raw traceback.
|
|
1263
|
+
try:
|
|
1264
|
+
if args.execute:
|
|
1265
|
+
if not db_path.exists():
|
|
1266
|
+
# sqlite3.connect() auto-creates an empty file, so without this
|
|
1267
|
+
# check db_lib.backup_to() below would silently back up (and
|
|
1268
|
+
# repair() would open) a brand-new, empty sessions.db instead
|
|
1269
|
+
# of failing loudly on the mistake.
|
|
1270
|
+
print(f"No sessions.db found at {db_path} — nothing to back up or repair.", file=sys.stderr)
|
|
1271
|
+
return 1
|
|
1272
|
+
backup_dir = db_path.parent / "repair-backups"
|
|
1273
|
+
backup_dir.mkdir(parents=True, exist_ok=True)
|
|
1274
|
+
stamp = datetime.datetime.now(datetime.timezone.utc).strftime("%Y%m%dT%H%M%SZ")
|
|
1275
|
+
backup_path = backup_dir / f"sessions-{stamp}.db"
|
|
1276
|
+
db_lib.backup_to(db_path, backup_path)
|
|
1277
|
+
print(f"Backed up sessions.db to {backup_path}")
|
|
1278
|
+
|
|
1279
|
+
report = sessions_repair.repair(roots, path=db_path, dry_run=not args.execute)
|
|
1280
|
+
except sqlite3.DatabaseError as exc:
|
|
1281
|
+
print(f"{db_path} exists but failed to open: {exc}", file=sys.stderr)
|
|
1282
|
+
return 1
|
|
1268
1283
|
if not any((report.repaired, report.unresolved, report.ambiguous, report.conflicts)):
|
|
1269
1284
|
print("No non-absolute project_dir rows found in sessions.db.")
|
|
1270
1285
|
return 0
|
|
@@ -1486,6 +1501,27 @@ def _cmd_install_everything(args: argparse.Namespace) -> int:
|
|
|
1486
1501
|
if rc != 0:
|
|
1487
1502
|
overall_rc = rc
|
|
1488
1503
|
|
|
1504
|
+
if apply and overall_rc == 0:
|
|
1505
|
+
import sqlite3
|
|
1506
|
+
|
|
1507
|
+
from cc_session_tools.lib import install_sync
|
|
1508
|
+
try:
|
|
1509
|
+
install_sync.record_synced(__version__)
|
|
1510
|
+
except sqlite3.DatabaseError as exc:
|
|
1511
|
+
# A corrupt sessions.db must not erase the five install steps'
|
|
1512
|
+
# already-successful work (and their printed summary) that ran
|
|
1513
|
+
# just above - print a clear warning and continue to the health
|
|
1514
|
+
# check, which surfaces store corruption of its own accord.
|
|
1515
|
+
# Letting this propagate would also be self-defeating: this is
|
|
1516
|
+
# the exact command the interactive gate (main()) tells a user
|
|
1517
|
+
# to run to fix an out-of-sync install.
|
|
1518
|
+
print(
|
|
1519
|
+
f" warning: could not record the install-everything sync marker "
|
|
1520
|
+
f"({exc}) - sessions.db may be corrupt; run `ccst repair sessions` "
|
|
1521
|
+
"to investigate",
|
|
1522
|
+
file=sys.stderr,
|
|
1523
|
+
)
|
|
1524
|
+
|
|
1489
1525
|
print(f"\n=== {total_steps}/{total_steps} Health check ===")
|
|
1490
1526
|
_cmd_doctor(
|
|
1491
1527
|
argparse.Namespace(
|
|
@@ -1674,6 +1710,11 @@ def _build_parser() -> argparse.ArgumentParser:
|
|
|
1674
1710
|
action="store_true",
|
|
1675
1711
|
help="Skip the PyPI version-drift check",
|
|
1676
1712
|
)
|
|
1713
|
+
doctor_parser.add_argument(
|
|
1714
|
+
"--all",
|
|
1715
|
+
action="store_true",
|
|
1716
|
+
help="Show every check result, including OK (default: only non-OK)",
|
|
1717
|
+
)
|
|
1677
1718
|
doctor_parser.add_argument(
|
|
1678
1719
|
"--drift",
|
|
1679
1720
|
action="store_true",
|
|
@@ -2181,6 +2222,37 @@ def main() -> None:
|
|
|
2181
2222
|
parser.print_help(sys.stderr)
|
|
2182
2223
|
sys.exit(1)
|
|
2183
2224
|
|
|
2225
|
+
from cc_session_tools.lib import install_sync
|
|
2226
|
+
|
|
2227
|
+
is_interactive = sys.stderr.isatty()
|
|
2228
|
+
# Read the marker only when interactive: `ccst hooks run <verb>` fires on
|
|
2229
|
+
# every single tool call in every open Claude Code session, and ccsched
|
|
2230
|
+
# jobs run on a schedule - neither has a TTY on stderr, and neither
|
|
2231
|
+
# should pay a SQLite open/close on every invocation just to compute a
|
|
2232
|
+
# value should_block_for_unsynced_install would immediately discard
|
|
2233
|
+
# anyway (its own is_interactive=False check returns False before ever
|
|
2234
|
+
# looking at synced_version).
|
|
2235
|
+
synced_version = install_sync.get_synced_version() if is_interactive else None
|
|
2236
|
+
if install_sync.should_block_for_unsynced_install(
|
|
2237
|
+
noun=args.noun,
|
|
2238
|
+
verb=getattr(args, "verb", None),
|
|
2239
|
+
installed_version=__version__,
|
|
2240
|
+
synced_version=synced_version,
|
|
2241
|
+
is_interactive=is_interactive,
|
|
2242
|
+
):
|
|
2243
|
+
if synced_version is None:
|
|
2244
|
+
state = "install-everything has never been run for this installation"
|
|
2245
|
+
else:
|
|
2246
|
+
state = f"install-everything was last synced at {synced_version}"
|
|
2247
|
+
print(
|
|
2248
|
+
f"ccst is installed at {__version__}, but {state}.\n"
|
|
2249
|
+
"Skills, hooks, shell functions, scheduled jobs, and CLAUDE.md config may be "
|
|
2250
|
+
"out of sync with this version.\n\n"
|
|
2251
|
+
"Run: ccst install-everything --apply\n",
|
|
2252
|
+
file=sys.stderr,
|
|
2253
|
+
)
|
|
2254
|
+
sys.exit(1)
|
|
2255
|
+
|
|
2184
2256
|
if args.noun == "hooks":
|
|
2185
2257
|
if args.verb == "install":
|
|
2186
2258
|
sys.exit(_cmd_hooks_install(args))
|
|
@@ -293,6 +293,36 @@ def check_pypi_version(installed_version: str, timeout: float = 3.0) -> CheckRes
|
|
|
293
293
|
)
|
|
294
294
|
|
|
295
295
|
|
|
296
|
+
def check_install_everything_synced(
|
|
297
|
+
installed_version: str, synced_version: str | None
|
|
298
|
+
) -> CheckResult:
|
|
299
|
+
"""WARN (not FAIL - always self-recoverable with one command, same
|
|
300
|
+
severity rationale as check_ccsched_job_registered) if the running ccst
|
|
301
|
+
version doesn't match the version 'install-everything --apply' last
|
|
302
|
+
succeeded for."""
|
|
303
|
+
name = "install:synced"
|
|
304
|
+
if synced_version == installed_version:
|
|
305
|
+
return CheckResult(
|
|
306
|
+
name=name, status=Status.OK,
|
|
307
|
+
reason=f"install-everything last synced at {installed_version}",
|
|
308
|
+
)
|
|
309
|
+
if synced_version is None:
|
|
310
|
+
return CheckResult(
|
|
311
|
+
name=name, status=Status.WARN,
|
|
312
|
+
reason=(
|
|
313
|
+
f"installed {installed_version}, install-everything has never been run — "
|
|
314
|
+
"run `ccst install-everything --apply`"
|
|
315
|
+
),
|
|
316
|
+
)
|
|
317
|
+
return CheckResult(
|
|
318
|
+
name=name, status=Status.WARN,
|
|
319
|
+
reason=(
|
|
320
|
+
f"installed {installed_version}, install-everything last synced at "
|
|
321
|
+
f"{synced_version} — run `ccst install-everything --apply`"
|
|
322
|
+
),
|
|
323
|
+
)
|
|
324
|
+
|
|
325
|
+
|
|
296
326
|
def _version_tuple(v: str) -> tuple[int, ...]:
|
|
297
327
|
"""Parse a simple a.b.c version string into a comparable tuple."""
|
|
298
328
|
parts: list[int] = []
|
|
@@ -454,7 +484,13 @@ def _count_new_store_rows(db_path: Path, tables: tuple[str, ...]) -> int:
|
|
|
454
484
|
for table in tables:
|
|
455
485
|
try:
|
|
456
486
|
total += int(conn.execute(f"SELECT COUNT(*) FROM {table}").fetchone()[0])
|
|
457
|
-
except sqlite3.
|
|
487
|
+
except sqlite3.DatabaseError:
|
|
488
|
+
# sqlite3.OperationalError ("no such table") is the common
|
|
489
|
+
# case; a corrupt file also reaches here, not the connect()
|
|
490
|
+
# guard above, because sqlite3.connect() opens lazily and
|
|
491
|
+
# only fails once a statement actually touches the file —
|
|
492
|
+
# both count as "can't be opened" per this function's
|
|
493
|
+
# contract, so both return 0 rather than raising.
|
|
458
494
|
continue
|
|
459
495
|
return total
|
|
460
496
|
finally:
|
|
@@ -683,10 +719,24 @@ def check_sessions_project_dir_absolute(sessions_db_path: Path) -> list[CheckRes
|
|
|
683
719
|
project_dir.parent against a resolved, absolute root — a relative project_dir can
|
|
684
720
|
never match, so the row becomes permanently invisible to --global listings without
|
|
685
721
|
ever raising an error. Not FAIL: it degrades --global listings but does not block
|
|
686
|
-
core functionality, and `ccst repair sessions` fixes it non-destructively.
|
|
722
|
+
core functionality, and `ccst repair sessions` fixes it non-destructively.
|
|
723
|
+
|
|
724
|
+
FAILs instead if sessions.db exists but isn't a valid SQLite file — same
|
|
725
|
+
"exists but failed to open" treatment check_data_stores already gives
|
|
726
|
+
this exact condition for other stores; sqlite3.connect() opens lazily
|
|
727
|
+
and only fails once find_non_absolute_rows actually queries the file, so
|
|
728
|
+
this must be caught here rather than assumed impossible."""
|
|
729
|
+
import sqlite3
|
|
730
|
+
|
|
687
731
|
from cc_session_tools.lib import sessions_repair
|
|
688
732
|
|
|
689
|
-
|
|
733
|
+
try:
|
|
734
|
+
bad = sessions_repair.find_non_absolute_rows(path=sessions_db_path)
|
|
735
|
+
except sqlite3.DatabaseError as exc:
|
|
736
|
+
return [CheckResult(
|
|
737
|
+
name="sessions:project-dir-absolute", status=Status.FAIL,
|
|
738
|
+
reason=f"{sessions_db_path} exists but failed to open: {exc}",
|
|
739
|
+
)]
|
|
690
740
|
if not bad:
|
|
691
741
|
return [CheckResult(
|
|
692
742
|
name="sessions:project-dir-absolute", status=Status.OK,
|
|
@@ -719,6 +769,7 @@ def run_all_checks(
|
|
|
719
769
|
projects_root: Path | None = None,
|
|
720
770
|
pdata_verify_projects: list[str] | None = None,
|
|
721
771
|
sessions_db_path: Path | None = None,
|
|
772
|
+
synced_version: str | None = None,
|
|
722
773
|
) -> list[CheckResult]:
|
|
723
774
|
"""Run the full doctor suite and return results.
|
|
724
775
|
|
|
@@ -755,6 +806,9 @@ def run_all_checks(
|
|
|
755
806
|
sessions_db_path:
|
|
756
807
|
Path to sessions.db; when None, the project_dir-absolute check is
|
|
757
808
|
skipped.
|
|
809
|
+
synced_version:
|
|
810
|
+
The version ``install-everything --apply`` last succeeded for, or
|
|
811
|
+
None if it has never been recorded.
|
|
758
812
|
"""
|
|
759
813
|
results: list[CheckResult] = []
|
|
760
814
|
|
|
@@ -831,6 +885,11 @@ def run_all_checks(
|
|
|
831
885
|
if not skip_pypi:
|
|
832
886
|
results.append(check_pypi_version(installed_version))
|
|
833
887
|
|
|
888
|
+
# Install-everything sync state
|
|
889
|
+
results.append(
|
|
890
|
+
check_install_everything_synced(installed_version, synced_version)
|
|
891
|
+
)
|
|
892
|
+
|
|
834
893
|
return results
|
|
835
894
|
|
|
836
895
|
|
|
@@ -860,17 +919,30 @@ def _extract_bundle_hook_names(bundle_path: Path) -> list[str]:
|
|
|
860
919
|
return names
|
|
861
920
|
|
|
862
921
|
|
|
863
|
-
def format_results(results: list[CheckResult]) -> str:
|
|
864
|
-
"""Return a human-readable table of check results.
|
|
922
|
+
def format_results(results: list[CheckResult], *, show_all: bool = False) -> str:
|
|
923
|
+
"""Return a human-readable table of check results.
|
|
924
|
+
|
|
925
|
+
By default (show_all=False) only WARN/FAIL results are printed, with a
|
|
926
|
+
hint on how to see the rest — a clean machine's `ccst doctor` output is
|
|
927
|
+
otherwise dozens of [OK] lines a user has to scroll past to find the one
|
|
928
|
+
thing that needs attention. show_all=True reproduces the full table this
|
|
929
|
+
function always printed before this parameter existed.
|
|
930
|
+
"""
|
|
865
931
|
if not results:
|
|
866
932
|
return "(no checks ran)"
|
|
867
|
-
|
|
933
|
+
shown = results if show_all else [r for r in results if r.status is not Status.OK]
|
|
868
934
|
lines = []
|
|
869
|
-
|
|
870
|
-
|
|
935
|
+
if shown:
|
|
936
|
+
name_w = max(len(r.name) for r in shown)
|
|
937
|
+
for r in shown:
|
|
938
|
+
lines.append(f"[{r.status.value:<4}] {r.name:<{name_w}} {r.reason}")
|
|
939
|
+
elif not show_all:
|
|
940
|
+
lines.append(f"All {len(results)} checks OK.")
|
|
871
941
|
has_issues = any(r.status in (Status.WARN, Status.FAIL) for r in results)
|
|
872
942
|
if has_issues:
|
|
873
943
|
lines.append("\nTip: run `ccst install-everything --apply` to sync skills, hooks, shell, and CLAUDE.md")
|
|
944
|
+
if not show_all:
|
|
945
|
+
lines.append("\nTo see full doctor output use --all argument")
|
|
874
946
|
return "\n".join(lines)
|
|
875
947
|
|
|
876
948
|
|
|
@@ -25,7 +25,16 @@ def default_mutes_path() -> Path:
|
|
|
25
25
|
|
|
26
26
|
def load_mutes(path: Path) -> dict[str, str]:
|
|
27
27
|
"""Return the mute map (check name -> ISO date). Empty if the store has
|
|
28
|
-
never been written to.
|
|
28
|
+
never been written to, or is corrupt.
|
|
29
|
+
|
|
30
|
+
connect(readonly=True) skips DDL by design, so a sessions.db that
|
|
31
|
+
predates the doctor_mutes table reaches the SELECT and must be caught
|
|
32
|
+
there too, not just around connect() itself - and connect() opens
|
|
33
|
+
lazily, so a corrupt (not-a-valid-SQLite-file) db only fails on the
|
|
34
|
+
SELECT as sqlite3.DatabaseError, not on connect() as OperationalError.
|
|
35
|
+
`ccst doctor --list-mutes`/`--drift` must never crash on this - doctor
|
|
36
|
+
is exempt from the install-sync gate specifically so it always works.
|
|
37
|
+
"""
|
|
29
38
|
try:
|
|
30
39
|
conn = sessions_db.connect(path=path, readonly=True)
|
|
31
40
|
except sqlite3.OperationalError:
|
|
@@ -33,6 +42,8 @@ def load_mutes(path: Path) -> dict[str, str]:
|
|
|
33
42
|
try:
|
|
34
43
|
rows = conn.execute("SELECT name, muted_at FROM doctor_mutes").fetchall()
|
|
35
44
|
return {r["name"]: r["muted_at"] for r in rows}
|
|
45
|
+
except sqlite3.DatabaseError:
|
|
46
|
+
return {}
|
|
36
47
|
finally:
|
|
37
48
|
conn.close()
|
|
38
49
|
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
"""Tracks the last ccst version for which `ccst install-everything --apply`
|
|
2
|
+
succeeded — lets `main()` nudge an interactive user to re-run it after an
|
|
3
|
+
upgrade, and lets `ccst doctor` report the same fact as a check result.
|
|
4
|
+
|
|
5
|
+
Backed by the install_sync table in sessions.db, following the same
|
|
6
|
+
established pattern as doctor_mutes.py (see its module docstring and
|
|
7
|
+
sessions_db.py's): small persistent CLI state belongs in the shared
|
|
8
|
+
sessions.db file, not a bespoke JSON/db file per subsystem.
|
|
9
|
+
"""
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
import sqlite3
|
|
13
|
+
from pathlib import Path
|
|
14
|
+
|
|
15
|
+
from cc_session_tools.lib import sessions_db
|
|
16
|
+
|
|
17
|
+
_SYNCED_VERSION_KEY = "synced_version"
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def get_synced_version(*, path: Path | None = None) -> str | None:
|
|
21
|
+
"""Return the version `install-everything --apply` last succeeded for,
|
|
22
|
+
or None if it has never been recorded.
|
|
23
|
+
|
|
24
|
+
Covers three distinct states, all returning None: no sessions.db file at
|
|
25
|
+
all (fresh machine); a sessions.db that predates this table (every
|
|
26
|
+
existing installation upgrading to the version that ships this feature —
|
|
27
|
+
session_tags/sessions/doctor_mutes already exist, but install_sync
|
|
28
|
+
doesn't yet — connect(readonly=True) skips DDL by design, since it must
|
|
29
|
+
not create/migrate a store it's only meant to read, so this case reaches
|
|
30
|
+
the SELECT and must be caught there too, not just around connect()
|
|
31
|
+
itself); and a sessions.db that is corrupt/not a valid SQLite file at
|
|
32
|
+
all, which callers of this function (main()'s interactive gate among
|
|
33
|
+
them) must be able to survive without crashing - a corrupt db is
|
|
34
|
+
reported the same as "never synced" rather than propagating an
|
|
35
|
+
exception. sqlite3.DatabaseError is the shared parent of
|
|
36
|
+
OperationalError ("no such table") and the corrupt-file case, so
|
|
37
|
+
catching it alone covers both without a second except clause.
|
|
38
|
+
"""
|
|
39
|
+
try:
|
|
40
|
+
conn = sessions_db.connect(path=path, readonly=True)
|
|
41
|
+
except sqlite3.OperationalError:
|
|
42
|
+
return None
|
|
43
|
+
try:
|
|
44
|
+
row = conn.execute(
|
|
45
|
+
"SELECT value FROM install_sync WHERE key = ?", (_SYNCED_VERSION_KEY,)
|
|
46
|
+
).fetchone()
|
|
47
|
+
return row["value"] if row is not None else None
|
|
48
|
+
except sqlite3.DatabaseError:
|
|
49
|
+
return None # "no such table: install_sync" (pre-upgrade db) or a corrupt file
|
|
50
|
+
finally:
|
|
51
|
+
conn.close()
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
_EXEMPT_NOUNS = frozenset({"install-everything", "doctor", "repair", "migrate"})
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def should_block_for_unsynced_install(
|
|
58
|
+
*,
|
|
59
|
+
noun: str | None,
|
|
60
|
+
verb: str | None,
|
|
61
|
+
installed_version: str,
|
|
62
|
+
synced_version: str | None,
|
|
63
|
+
is_interactive: bool,
|
|
64
|
+
) -> bool:
|
|
65
|
+
"""True if main() should abort this invocation with an install-everything
|
|
66
|
+
nudge instead of dispatching it.
|
|
67
|
+
|
|
68
|
+
is_interactive must be False for every automated caller (a Claude Code
|
|
69
|
+
hook via `ccst hooks run`, a ccsched job, any future scheduled/scripted
|
|
70
|
+
caller) - this is the primary safety property, checked first. Exempt
|
|
71
|
+
nouns are never blocked regardless of interactivity, so the user always
|
|
72
|
+
has a way to see or fix the state this function is protecting against:
|
|
73
|
+
install-everything (the fix), doctor (the diagnostic tool), repair
|
|
74
|
+
("Repair known sessions.db/store corruption" - the exact tool needed
|
|
75
|
+
when the sync marker's own store is what's broken), and migrate (this
|
|
76
|
+
repo's own doctor output tells users to run `ccst migrate all` "from a
|
|
77
|
+
plain terminal" when a legacy-data migration is pending - blocking that
|
|
78
|
+
instruction would be self-defeating). `hooks run` is additionally exempt
|
|
79
|
+
by name, belt-and-braces alongside is_interactive always being False for
|
|
80
|
+
it in practice - the one path this function must never block under any
|
|
81
|
+
circumstance.
|
|
82
|
+
"""
|
|
83
|
+
if not is_interactive:
|
|
84
|
+
return False
|
|
85
|
+
if noun in _EXEMPT_NOUNS:
|
|
86
|
+
return False
|
|
87
|
+
if noun == "hooks" and verb == "run":
|
|
88
|
+
return False
|
|
89
|
+
return synced_version != installed_version
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def record_synced(version: str, *, path: Path | None = None) -> None:
|
|
93
|
+
"""Record that `install-everything --apply` just succeeded for `version`."""
|
|
94
|
+
conn = sessions_db.connect(path=path)
|
|
95
|
+
try:
|
|
96
|
+
conn.execute(
|
|
97
|
+
"INSERT INTO install_sync (key, value, updated_at) VALUES (?, ?, ?) "
|
|
98
|
+
"ON CONFLICT(key) DO UPDATE SET value=excluded.value, updated_at=excluded.updated_at",
|
|
99
|
+
(_SYNCED_VERSION_KEY, version, sessions_db._now_iso()),
|
|
100
|
+
)
|
|
101
|
+
conn.commit()
|
|
102
|
+
finally:
|
|
103
|
+
conn.close()
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: cc-session-tools
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.4.0
|
|
4
4
|
Summary: Claude Code session management CLI tools (ccd, ccr, ccs, claude-code-usage, ccst) and hook library (cccs_hooks)
|
|
5
5
|
Author: raffishquartan
|
|
6
6
|
License: MIT License
|
|
@@ -144,6 +144,12 @@ It runs `ccst doctor` at the end so you can see the health check immediately. Re
|
|
|
144
144
|
|
|
145
145
|
> **Note:** `install-everything.sh` handles steps 1–5 of setup. Step 6 — adding broader CCST guidance to your global `~/.claude/CLAUDE.md` (session management, 8-digit gate, etc.) — is interactive and must be run separately afterwards. See [Configure your global CLAUDE.md](#configure-your-global-claudemd) below.
|
|
146
146
|
|
|
147
|
+
> **After upgrading** (`uv tool install --reinstall`/`--upgrade`, or `pip`/`pipx` equivalents):
|
|
148
|
+
> re-run `ccst install-everything --apply` so skills/hooks/shell functions/scheduled jobs stay in
|
|
149
|
+
> sync with the new version. If you forget, `ccst` itself will remind you — any interactive
|
|
150
|
+
> command run from a real terminal on a version that hasn't been synced prints a nudge and exits
|
|
151
|
+
> until you do (automated callers like Claude Code's own hooks are never affected).
|
|
152
|
+
|
|
147
153
|
**Manual path — step by step:**
|
|
148
154
|
|
|
149
155
|
```sh
|
|
@@ -884,14 +890,19 @@ ccst claude-md uninstall --apply
|
|
|
884
890
|
|
|
885
891
|
Run a full health check: PATH for all six CLIs, env vars (`REPO_ROOT`/`PROJ_ROOT`),
|
|
886
892
|
`~/.claude/settings.json` JSON validity, expected hook registrations present,
|
|
887
|
-
skill symlinks correct and pointing at the installed source,
|
|
888
|
-
|
|
893
|
+
skill symlinks correct and pointing at the installed source, whether
|
|
894
|
+
`install-everything --apply` has been run for the currently-installed version,
|
|
895
|
+
and version drift between installed `ccst` and the latest release on PyPI.
|
|
889
896
|
|
|
890
897
|
```sh
|
|
891
898
|
ccst doctor # checks everything, including PyPI version check
|
|
892
899
|
ccst doctor --no-pypi # skip the network version check (useful in CI)
|
|
900
|
+
ccst doctor --all # also print OK checks (default: only WARN/FAIL)
|
|
893
901
|
```
|
|
894
902
|
|
|
903
|
+
By default only WARN/FAIL results are printed — a clean machine is otherwise dozens of `[OK]`
|
|
904
|
+
lines to scroll past. Use `--all` to see the full check list, including everything that passed.
|
|
905
|
+
|
|
895
906
|
Exit `0` if all checks are OK. Exit `1` if any check is WARN or FAIL.
|
|
896
907
|
|
|
897
908
|
### `ccst telemetry trim`
|
|
@@ -30,6 +30,7 @@ src/cc_session_tools/lib/debug.py
|
|
|
30
30
|
src/cc_session_tools/lib/doctor.py
|
|
31
31
|
src/cc_session_tools/lib/doctor_mutes.py
|
|
32
32
|
src/cc_session_tools/lib/hook_registry.py
|
|
33
|
+
src/cc_session_tools/lib/install_sync.py
|
|
33
34
|
src/cc_session_tools/lib/levenshtein.py
|
|
34
35
|
src/cc_session_tools/lib/paths.py
|
|
35
36
|
src/cc_session_tools/lib/picker.py
|
|
@@ -173,6 +174,7 @@ tests/test_ccst_hooks_install_prune.py
|
|
|
173
174
|
tests/test_ccst_hooks_install_zero_arg.py
|
|
174
175
|
tests/test_ccst_hooks_uninstall.py
|
|
175
176
|
tests/test_ccst_install_everything.py
|
|
177
|
+
tests/test_ccst_main_gate.py
|
|
176
178
|
tests/test_ccst_pdata_cli.py
|
|
177
179
|
tests/test_ccst_pdata_init_cli.py
|
|
178
180
|
tests/test_ccst_pdata_reconcile_cli.py
|
|
@@ -200,6 +202,7 @@ tests/test_doctor_mutes.py
|
|
|
200
202
|
tests/test_empty_session.py
|
|
201
203
|
tests/test_hook_cache.py
|
|
202
204
|
tests/test_hooks_install.py
|
|
205
|
+
tests/test_install_sync.py
|
|
203
206
|
tests/test_last_screenshot.py
|
|
204
207
|
tests/test_levenshtein.py
|
|
205
208
|
tests/test_marker_allow.py
|