cc-session-tools 2.2.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.2.0/src/cc_session_tools.egg-info → cc_session_tools-2.4.0}/PKG-INFO +14 -3
- {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/README.md +13 -2
- {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/pyproject.toml +14 -2
- {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/src/cc_session_tools/cli/ccst.py +97 -33
- cc_session_tools-2.4.0/src/cc_session_tools/config/hooks-bundle.json +118 -0
- cc_session_tools-2.4.0/src/cc_session_tools/config/session-tag-hook.json +16 -0
- {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/src/cc_session_tools/lib/doctor.py +80 -8
- {cc_session_tools-2.2.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.2.0 → cc_session_tools-2.4.0}/src/cc_session_tools/lib/sessions_db.py +6 -0
- cc_session_tools-2.4.0/src/cc_session_tools/skills/analyse-cc-usage/SKILL.md +230 -0
- cc_session_tools-2.4.0/src/cc_session_tools/skills/delete-sessions/SKILL.md +158 -0
- cc_session_tools-2.4.0/src/cc_session_tools/skills/delete-sessions/scripts/delete_sessions.py +429 -0
- cc_session_tools-2.4.0/src/cc_session_tools/skills/do-executor-critic-assessor-loop/SKILL.md +89 -0
- cc_session_tools-2.4.0/src/cc_session_tools/skills/find-claude-code-session/SKILL.md +129 -0
- cc_session_tools-2.4.0/src/cc_session_tools/skills/generate-8digit-code/SKILL.md +43 -0
- cc_session_tools-2.4.0/src/cc_session_tools/skills/generate-8digit-code/scripts/generate_8digit_code.py +28 -0
- cc_session_tools-2.4.0/src/cc_session_tools/skills/list-empty-sessions/SKILL.md +125 -0
- cc_session_tools-2.4.0/src/cc_session_tools/skills/list-empty-sessions/scripts/list_empty_sessions.py +79 -0
- cc_session_tools-2.4.0/src/cc_session_tools/skills/manage-recurring-cc-jobs-using-ccsched/SKILL.md +81 -0
- cc_session_tools-2.4.0/src/cc_session_tools/skills/move-session/SKILL.md +229 -0
- cc_session_tools-2.4.0/src/cc_session_tools/skills/move-session/hooks/sessionstart-pending-rename.sh +67 -0
- cc_session_tools-2.4.0/src/cc_session_tools/skills/move-session/scripts/cc_session_rules.py +91 -0
- cc_session_tools-2.4.0/src/cc_session_tools/skills/move-session/scripts/move_session.py +1089 -0
- cc_session_tools-2.4.0/src/cc_session_tools/skills/pm-pdata-conflict-resolution/SKILL.md +67 -0
- cc_session_tools-2.4.0/src/cc_session_tools/skills/pm-pdata-schema-design/SKILL.md +82 -0
- cc_session_tools-2.4.0/src/cc_session_tools/skills/pm-project-init/SKILL.md +142 -0
- cc_session_tools-2.4.0/src/cc_session_tools/skills/pm-update-central-files/SKILL.md +152 -0
- cc_session_tools-2.4.0/src/cc_session_tools/skills/reduce-persistent-context/CALIBRATION.md +74 -0
- cc_session_tools-2.4.0/src/cc_session_tools/skills/reduce-persistent-context/SKILL.md +109 -0
- cc_session_tools-2.4.0/src/cc_session_tools/skills/reduce-persistent-context/scripts/analyze_context.py +65 -0
- cc_session_tools-2.4.0/src/cc_session_tools/skills/reduce-persistent-context/scripts/duplicates.py +72 -0
- cc_session_tools-2.4.0/src/cc_session_tools/skills/reduce-persistent-context/scripts/measure.py +162 -0
- cc_session_tools-2.4.0/src/cc_session_tools/skills/reduce-persistent-context/scripts/report.py +53 -0
- cc_session_tools-2.4.0/src/cc_session_tools/skills/reduce-persistent-context/scripts/tokens.py +6 -0
- cc_session_tools-2.4.0/src/cc_session_tools/skills/reduce-persistent-context/scripts/usage.py +34 -0
- cc_session_tools-2.4.0/src/cc_session_tools/skills/select-agent-model/SKILL.md +52 -0
- cc_session_tools-2.4.0/src/cc_session_tools/skills/send-session-message/SKILL.md +63 -0
- cc_session_tools-2.4.0/src/cc_session_tools/skills/update-command-cache/SKILL.md +62 -0
- cc_session_tools-2.4.0/src/cc_session_tools/skills/update-command-cache/scripts/update_command_cache.py +218 -0
- {cc_session_tools-2.2.0 → cc_session_tools-2.4.0/src/cc_session_tools.egg-info}/PKG-INFO +14 -3
- {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/src/cc_session_tools.egg-info/SOURCES.txt +36 -0
- {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/src/cccs_hooks/bash_hard_deny.py +55 -3
- {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/src/cccs_hooks/bash_security_review.py +43 -37
- {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/src/cccs_hooks/catchup.py +15 -4
- {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/src/cccs_hooks/messaging_deliver.py +9 -4
- {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/src/cccs_hooks/normalise.py +4 -0
- {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/tests/test_bash_hard_deny.py +129 -0
- {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/tests/test_bash_security_review.py +222 -0
- cc_session_tools-2.4.0/tests/test_ccst_bundle_discovery.py +52 -0
- {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/tests/test_ccst_doctor.py +165 -11
- {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/tests/test_ccst_hooks_install_prune.py +1 -1
- {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/tests/test_ccst_hooks_install_zero_arg.py +3 -3
- 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.2.0 → cc_session_tools-2.4.0}/tests/test_ccst_repair_sessions_cli.py +39 -0
- {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/tests/test_ccst_skills_uninstall.py +3 -3
- {cc_session_tools-2.2.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.2.0 → cc_session_tools-2.4.0}/tests/test_normalise.py +34 -0
- {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/tests/test_sessions_db.py +2 -2
- cc_session_tools-2.2.0/tests/test_ccst_install_everything.py +0 -160
- {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/LICENSE +0 -0
- {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/setup.cfg +0 -0
- {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/src/cc_session_tools/__init__.py +0 -0
- {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/src/cc_session_tools/cli/__init__.py +0 -0
- {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/src/cc_session_tools/cli/ccd.py +0 -0
- {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/src/cc_session_tools/cli/ccmsg.py +0 -0
- {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/src/cc_session_tools/cli/ccr.py +0 -0
- {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/src/cc_session_tools/cli/ccs.py +0 -0
- {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/src/cc_session_tools/cli/ccsched.py +0 -0
- {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/src/cc_session_tools/cli/migrate_ccmsg.py +0 -0
- {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/src/cc_session_tools/cli/migrate_ccsched.py +0 -0
- {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/src/cc_session_tools/cli/migrate_sessions_db.py +0 -0
- {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/src/cc_session_tools/cli/migrate_telemetry.py +0 -0
- {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/src/cc_session_tools/hooks_install.py +0 -0
- {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/src/cc_session_tools/lib/__init__.py +0 -0
- {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/src/cc_session_tools/lib/claude_flags.py +0 -0
- {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/src/cc_session_tools/lib/claude_md_install.py +0 -0
- {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/src/cc_session_tools/lib/db.py +0 -0
- {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/src/cc_session_tools/lib/debug.py +0 -0
- {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/src/cc_session_tools/lib/hook_registry.py +0 -0
- {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/src/cc_session_tools/lib/levenshtein.py +0 -0
- {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/src/cc_session_tools/lib/messaging/__init__.py +0 -0
- {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/src/cc_session_tools/lib/messaging/addressing.py +0 -0
- {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/src/cc_session_tools/lib/messaging/cursor.py +0 -0
- {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/src/cc_session_tools/lib/messaging/lock.py +0 -0
- {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/src/cc_session_tools/lib/messaging/message.py +0 -0
- {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/src/cc_session_tools/lib/messaging/move_safety.py +0 -0
- {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/src/cc_session_tools/lib/messaging/repository.py +0 -0
- {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/src/cc_session_tools/lib/messaging/retention.py +0 -0
- {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/src/cc_session_tools/lib/messaging/service.py +0 -0
- {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/src/cc_session_tools/lib/messaging/store.py +0 -0
- {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/src/cc_session_tools/lib/paths.py +0 -0
- {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/src/cc_session_tools/lib/pdata/__init__.py +0 -0
- {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/src/cc_session_tools/lib/pdata/backup.py +0 -0
- {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/src/cc_session_tools/lib/pdata/classify.py +0 -0
- {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/src/cc_session_tools/lib/pdata/cutover.py +0 -0
- {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/src/cc_session_tools/lib/pdata/formatting.py +0 -0
- {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/src/cc_session_tools/lib/pdata/importers.py +0 -0
- {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/src/cc_session_tools/lib/pdata/init_paths.py +0 -0
- {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/src/cc_session_tools/lib/pdata/init_service.py +0 -0
- {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/src/cc_session_tools/lib/pdata/manifest.py +0 -0
- {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/src/cc_session_tools/lib/pdata/naming.py +0 -0
- {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/src/cc_session_tools/lib/pdata/repository.py +0 -0
- {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/src/cc_session_tools/lib/pdata/service.py +0 -0
- {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/src/cc_session_tools/lib/pdata/session_output.py +0 -0
- {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/src/cc_session_tools/lib/pdata/store.py +0 -0
- {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/src/cc_session_tools/lib/pdata/verify.py +0 -0
- {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/src/cc_session_tools/lib/picker.py +0 -0
- {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/src/cc_session_tools/lib/prompts.py +0 -0
- {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/src/cc_session_tools/lib/roots.py +0 -0
- {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/src/cc_session_tools/lib/rules.py +0 -0
- {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/src/cc_session_tools/lib/scheduler/__init__.py +0 -0
- {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/src/cc_session_tools/lib/scheduler/bundled_jobs.py +0 -0
- {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/src/cc_session_tools/lib/scheduler/cadence.py +0 -0
- {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/src/cc_session_tools/lib/scheduler/cursor.py +0 -0
- {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/src/cc_session_tools/lib/scheduler/digest.py +0 -0
- {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/src/cc_session_tools/lib/scheduler/due.py +0 -0
- {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/src/cc_session_tools/lib/scheduler/duration.py +0 -0
- {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/src/cc_session_tools/lib/scheduler/jobspec.py +0 -0
- {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/src/cc_session_tools/lib/scheduler/ledger.py +0 -0
- {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/src/cc_session_tools/lib/scheduler/lock.py +0 -0
- {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/src/cc_session_tools/lib/scheduler/notify.py +0 -0
- {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/src/cc_session_tools/lib/scheduler/reconcile.py +0 -0
- {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/src/cc_session_tools/lib/scheduler/registry.py +0 -0
- {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/src/cc_session_tools/lib/scheduler/runner.py +0 -0
- {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/src/cc_session_tools/lib/scheduler/state.py +0 -0
- {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/src/cc_session_tools/lib/scheduler/store.py +0 -0
- {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/src/cc_session_tools/lib/scheduler/surface.py +0 -0
- {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/src/cc_session_tools/lib/scheduler/throttle.py +0 -0
- {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/src/cc_session_tools/lib/scheduler/worker.py +0 -0
- {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/src/cc_session_tools/lib/session_gc.py +0 -0
- {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/src/cc_session_tools/lib/sessions.py +0 -0
- {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/src/cc_session_tools/lib/sessions_repair.py +0 -0
- {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/src/cc_session_tools/lib/shell_install.py +0 -0
- {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/src/cc_session_tools/lib/tasklist.py +0 -0
- {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/src/cc_session_tools/lib/telemetry_store.py +0 -0
- {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/src/cc_session_tools.egg-info/dependency_links.txt +0 -0
- {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/src/cc_session_tools.egg-info/entry_points.txt +0 -0
- {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/src/cc_session_tools.egg-info/requires.txt +0 -0
- {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/src/cc_session_tools.egg-info/top_level.txt +0 -0
- {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/src/cccs_hooks/__init__.py +0 -0
- {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/src/cccs_hooks/after_response.py +0 -0
- {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/src/cccs_hooks/cache.py +0 -0
- {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/src/cccs_hooks/confirm_8digit.py +0 -0
- {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/src/cccs_hooks/last_screenshot.py +0 -0
- {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/src/cccs_hooks/marker_allow.py +0 -0
- {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/src/cccs_hooks/markers.py +0 -0
- {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/src/cccs_hooks/pending_migration.py +0 -0
- {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/src/cccs_hooks/session_tag.py +0 -0
- {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/src/cccs_hooks/stats.py +0 -0
- {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/src/cccs_hooks/telemetry.py +0 -0
- {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/src/cccs_hooks/telemetry_query.py +0 -0
- {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/src/cccs_hooks/telemetry_trim.py +0 -0
- {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/src/cccs_hooks/transcript.py +0 -0
- {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/src/cccs_hooks/worklog_guard.py +0 -0
- {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/src/claude_code_usage/__init__.py +0 -0
- {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/src/claude_code_usage/attribution.py +0 -0
- {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/src/claude_code_usage/cache.py +0 -0
- {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/src/claude_code_usage/ccusage_wrapper.py +0 -0
- {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/src/claude_code_usage/cli.py +0 -0
- {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/src/claude_code_usage/data/pricing.json +0 -0
- {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/src/claude_code_usage/parent_inference.py +0 -0
- {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/src/claude_code_usage/parser.py +0 -0
- {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/src/claude_code_usage/pricing.py +0 -0
- {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/src/claude_code_usage/query.py +0 -0
- {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/src/claude_code_usage/report.py +0 -0
- {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/src/claude_code_usage/schema.py +0 -0
- {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/src/claude_code_usage/session_names.py +0 -0
- {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/tests/test_after_response.py +0 -0
- {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/tests/test_attribution.py +0 -0
- {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/tests/test_cache.py +0 -0
- {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/tests/test_cache_sqlite.py +0 -0
- {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/tests/test_ccr_orphans.py +0 -0
- {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/tests/test_ccs_emptiness.py +0 -0
- {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/tests/test_ccs_eta.py +0 -0
- {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/tests/test_ccs_list_mode.py +0 -0
- {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/tests/test_ccs_scope_flags.py +0 -0
- {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/tests/test_ccs_sentinel_sort.py +0 -0
- {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/tests/test_ccs_session_counts.py +0 -0
- {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/tests/test_ccst_ccsched_jobs_cli.py +0 -0
- {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/tests/test_ccst_cli.py +0 -0
- {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/tests/test_ccst_gc_report.py +0 -0
- {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/tests/test_ccst_hook_dispatcher.py +0 -0
- {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/tests/test_ccst_hooks_uninstall.py +0 -0
- {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/tests/test_ccst_pdata_cli.py +0 -0
- {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/tests/test_ccst_pdata_init_cli.py +0 -0
- {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/tests/test_ccst_pdata_reconcile_cli.py +0 -0
- {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/tests/test_ccst_pdata_verify_cli.py +0 -0
- {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/tests/test_ccst_sessions_cli.py +0 -0
- {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/tests/test_ccst_shell_install.py +0 -0
- {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/tests/test_ccst_skills_install.py +0 -0
- {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/tests/test_ccst_telemetry_query.py +0 -0
- {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/tests/test_ccst_telemetry_trim.py +0 -0
- {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/tests/test_ccusage_wrapper.py +0 -0
- {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/tests/test_claude_flags.py +0 -0
- {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/tests/test_claude_md_install.py +0 -0
- {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/tests/test_cli.py +0 -0
- {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/tests/test_cli_ccd.py +0 -0
- {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/tests/test_cli_ccd_dry_run.py +0 -0
- {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/tests/test_cli_ccr.py +0 -0
- {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/tests/test_cli_ccs.py +0 -0
- {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/tests/test_cli_version.py +0 -0
- {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/tests/test_confirm_8digit.py +0 -0
- {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/tests/test_db.py +0 -0
- {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/tests/test_debug.py +0 -0
- {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/tests/test_empty_session.py +0 -0
- {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/tests/test_hook_cache.py +0 -0
- {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/tests/test_hooks_install.py +0 -0
- {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/tests/test_last_screenshot.py +0 -0
- {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/tests/test_levenshtein.py +0 -0
- {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/tests/test_marker_allow.py +0 -0
- {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/tests/test_migrate_ccmsg.py +0 -0
- {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/tests/test_migrate_ccsched.py +0 -0
- {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/tests/test_migrate_sessions_db.py +0 -0
- {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/tests/test_migrate_telemetry.py +0 -0
- {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/tests/test_parent_inference.py +0 -0
- {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/tests/test_parser.py +0 -0
- {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/tests/test_paths.py +0 -0
- {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/tests/test_pending_migration_hook.py +0 -0
- {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/tests/test_picker.py +0 -0
- {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/tests/test_pricing.py +0 -0
- {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/tests/test_prompts.py +0 -0
- {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/tests/test_query.py +0 -0
- {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/tests/test_report.py +0 -0
- {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/tests/test_roots.py +0 -0
- {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/tests/test_rules.py +0 -0
- {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/tests/test_scheduler_bundled_jobs.py +0 -0
- {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/tests/test_schema.py +0 -0
- {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/tests/test_session_names.py +0 -0
- {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/tests/test_session_tag.py +0 -0
- {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/tests/test_sessions.py +0 -0
- {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/tests/test_sessions_repair.py +0 -0
- {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/tests/test_tasklist.py +0 -0
- {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/tests/test_telemetry.py +0 -0
- {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/tests/test_telemetry_store.py +0 -0
- {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/tests/test_transcript.py +0 -0
- {cc_session_tools-2.2.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" }
|
|
@@ -55,12 +55,24 @@ cccs-stats = "cccs_hooks.stats:main"
|
|
|
55
55
|
|
|
56
56
|
[tool.setuptools.packages.find]
|
|
57
57
|
where = ["src"]
|
|
58
|
+
# skills/ and config/ are bundled data, not importable code - without this
|
|
59
|
+
# exclude, setuptools' auto-discovery treats every skills/<name>/ subdirectory
|
|
60
|
+
# containing a .py file (scripts, dev-only test suites) as its own importable
|
|
61
|
+
# sub-package, which then bypasses exclude-package-data entirely (that option
|
|
62
|
+
# only filters within the file's *own* discovered package, and a wrongly
|
|
63
|
+
# auto-discovered "cc_session_tools.skills.<name>.tests" package was never
|
|
64
|
+
# covered by an exclude-package-data entry keyed just "cc_session_tools").
|
|
65
|
+
exclude = ["cc_session_tools.skills", "cc_session_tools.skills.*", "cc_session_tools.config", "cc_session_tools.config.*"]
|
|
58
66
|
|
|
59
67
|
[tool.setuptools.package-data]
|
|
60
68
|
claude_code_usage = ["data/*.json"]
|
|
69
|
+
cc_session_tools = ["skills/**/*", "config/*"]
|
|
70
|
+
|
|
71
|
+
[tool.setuptools.exclude-package-data]
|
|
72
|
+
cc_session_tools = ["**/tests/*", "**/__pycache__/*"]
|
|
61
73
|
|
|
62
74
|
[tool.pytest.ini_options]
|
|
63
|
-
testpaths = ["tests", "tests/scheduler", "skills/move-session/tests", "skills/list-empty-sessions/tests", "skills/delete-sessions/tests", "skills/generate-8digit-code/tests", "skills/reduce-persistent-context/tests", "skills/update-command-cache/tests"]
|
|
75
|
+
testpaths = ["tests", "tests/scheduler", "src/cc_session_tools/skills/move-session/tests", "src/cc_session_tools/skills/list-empty-sessions/tests", "src/cc_session_tools/skills/delete-sessions/tests", "src/cc_session_tools/skills/generate-8digit-code/tests", "src/cc_session_tools/skills/reduce-persistent-context/tests", "src/cc_session_tools/skills/update-command-cache/tests"]
|
|
64
76
|
addopts = "-q"
|
|
65
77
|
pythonpath = ["src"]
|
|
66
78
|
|
|
@@ -88,17 +88,15 @@ from cc_session_tools.lib.hook_registry import HOOK_DESCRIPTIONS, HOOK_VERBS
|
|
|
88
88
|
def _discover_source_dir() -> Path:
|
|
89
89
|
"""Return the bundled skills/ directory.
|
|
90
90
|
|
|
91
|
-
|
|
92
|
-
|
|
91
|
+
skills/ is packaged inside cc_session_tools (declared as package-data in
|
|
92
|
+
pyproject.toml), so it sits at the same relative depth from this module
|
|
93
|
+
whether cc_session_tools is an editable source checkout or an installed
|
|
94
|
+
wheel: src/cc_session_tools/cli/ccst.py -> ../skills. No install-location
|
|
95
|
+
fallback is needed - a fallback here would just mask a broken install.
|
|
93
96
|
"""
|
|
94
|
-
|
|
95
|
-
here = Path(__file__).resolve()
|
|
96
|
-
candidate = here.parent.parent.parent.parent / "skills"
|
|
97
|
+
candidate = Path(__file__).resolve().parent.parent / "skills"
|
|
97
98
|
if candidate.is_dir():
|
|
98
99
|
return candidate
|
|
99
|
-
fallback = Path.home() / "repos" / "claude-code-session-tools" / "skills"
|
|
100
|
-
if fallback.is_dir():
|
|
101
|
-
return fallback
|
|
102
100
|
raise FileNotFoundError(
|
|
103
101
|
"Cannot locate bundled skills/ directory. "
|
|
104
102
|
"Run from the source tree or use --source to specify the path explicitly."
|
|
@@ -108,18 +106,12 @@ def _discover_source_dir() -> Path:
|
|
|
108
106
|
def _discover_bundle() -> Path:
|
|
109
107
|
"""Return the bundled config/hooks-bundle.json path.
|
|
110
108
|
|
|
111
|
-
|
|
112
|
-
|
|
109
|
+
config/ is packaged inside cc_session_tools the same way skills/ is (see
|
|
110
|
+
_discover_source_dir) - src/cc_session_tools/cli/ccst.py -> ../config.
|
|
113
111
|
"""
|
|
114
|
-
|
|
115
|
-
candidate = here.parent.parent.parent.parent / "config" / "hooks-bundle.json"
|
|
112
|
+
candidate = Path(__file__).resolve().parent.parent / "config" / "hooks-bundle.json"
|
|
116
113
|
if candidate.is_file():
|
|
117
114
|
return candidate
|
|
118
|
-
fallback = (
|
|
119
|
-
Path.home() / "repos" / "claude-code-session-tools" / "config" / "hooks-bundle.json"
|
|
120
|
-
)
|
|
121
|
-
if fallback.is_file():
|
|
122
|
-
return fallback
|
|
123
115
|
raise FileNotFoundError(
|
|
124
116
|
"Cannot locate bundled config/hooks-bundle.json. "
|
|
125
117
|
"Run from the source tree or use --source to specify the path explicitly."
|
|
@@ -661,6 +653,7 @@ def _cmd_doctor(args: argparse.Namespace) -> int:
|
|
|
661
653
|
|
|
662
654
|
from cc_session_tools.lib.pdata import verify as _pdata_verify
|
|
663
655
|
from cc_session_tools.lib.pdata.init_paths import default_projects_root
|
|
656
|
+
from cc_session_tools.lib import install_sync
|
|
664
657
|
|
|
665
658
|
results = run_all_checks(
|
|
666
659
|
installed_version=__version__,
|
|
@@ -675,6 +668,7 @@ def _cmd_doctor(args: argparse.Namespace) -> int:
|
|
|
675
668
|
projects_root=default_projects_root(),
|
|
676
669
|
pdata_verify_projects=_pdata_verify.discover_projects(),
|
|
677
670
|
sessions_db_path=store_paths["sessions"],
|
|
671
|
+
synced_version=install_sync.get_synced_version(),
|
|
678
672
|
)
|
|
679
673
|
|
|
680
674
|
if args.drift or getattr(args, "mode", None) == "drift":
|
|
@@ -685,7 +679,7 @@ def _cmd_doctor(args: argparse.Namespace) -> int:
|
|
|
685
679
|
print(report)
|
|
686
680
|
return 1 if unmuted else 0
|
|
687
681
|
|
|
688
|
-
print(format_results(results))
|
|
682
|
+
print(format_results(results, show_all=getattr(args, "all", False)))
|
|
689
683
|
|
|
690
684
|
any_issue = any(r.status in (Status.WARN, Status.FAIL) for r in results)
|
|
691
685
|
return 1 if any_issue else 0
|
|
@@ -1247,6 +1241,8 @@ def _cmd_sessions_list(args: argparse.Namespace) -> int:
|
|
|
1247
1241
|
|
|
1248
1242
|
|
|
1249
1243
|
def _cmd_repair_sessions(args: argparse.Namespace) -> int:
|
|
1244
|
+
import sqlite3
|
|
1245
|
+
|
|
1250
1246
|
from cc_session_tools.lib import db as db_lib
|
|
1251
1247
|
from cc_session_tools.lib import sessions_db, sessions_repair
|
|
1252
1248
|
from cc_session_tools.lib.roots import RootsConfigError, load_session_roots
|
|
@@ -1258,21 +1254,32 @@ def _cmd_repair_sessions(args: argparse.Namespace) -> int:
|
|
|
1258
1254
|
print(str(e), file=sys.stderr)
|
|
1259
1255
|
return 1
|
|
1260
1256
|
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
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
|
|
1276
1283
|
if not any((report.repaired, report.unresolved, report.ambiguous, report.conflicts)):
|
|
1277
1284
|
print("No non-absolute project_dir rows found in sessions.db.")
|
|
1278
1285
|
return 0
|
|
@@ -1494,6 +1501,27 @@ def _cmd_install_everything(args: argparse.Namespace) -> int:
|
|
|
1494
1501
|
if rc != 0:
|
|
1495
1502
|
overall_rc = rc
|
|
1496
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
|
+
|
|
1497
1525
|
print(f"\n=== {total_steps}/{total_steps} Health check ===")
|
|
1498
1526
|
_cmd_doctor(
|
|
1499
1527
|
argparse.Namespace(
|
|
@@ -1682,6 +1710,11 @@ def _build_parser() -> argparse.ArgumentParser:
|
|
|
1682
1710
|
action="store_true",
|
|
1683
1711
|
help="Skip the PyPI version-drift check",
|
|
1684
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
|
+
)
|
|
1685
1718
|
doctor_parser.add_argument(
|
|
1686
1719
|
"--drift",
|
|
1687
1720
|
action="store_true",
|
|
@@ -2189,6 +2222,37 @@ def main() -> None:
|
|
|
2189
2222
|
parser.print_help(sys.stderr)
|
|
2190
2223
|
sys.exit(1)
|
|
2191
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
|
+
|
|
2192
2256
|
if args.noun == "hooks":
|
|
2193
2257
|
if args.verb == "install":
|
|
2194
2258
|
sys.exit(_cmd_hooks_install(args))
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
{
|
|
2
|
+
"hooks": {
|
|
3
|
+
"SessionStart": [
|
|
4
|
+
{
|
|
5
|
+
"hooks": [
|
|
6
|
+
{
|
|
7
|
+
"type": "command",
|
|
8
|
+
"command": "ccst hooks run session-tag",
|
|
9
|
+
"timeout": 5,
|
|
10
|
+
"statusMessage": "Checking cc wrapper session tag..."
|
|
11
|
+
},
|
|
12
|
+
{
|
|
13
|
+
"type": "command",
|
|
14
|
+
"command": "ccst hooks run messaging-deliver",
|
|
15
|
+
"timeout": 10,
|
|
16
|
+
"statusMessage": "Delivering inter-session messages..."
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
"type": "command",
|
|
20
|
+
"command": "ccst hooks run catchup",
|
|
21
|
+
"timeout": 10,
|
|
22
|
+
"statusMessage": "Reconciling scheduled-task catch-up..."
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
"type": "command",
|
|
26
|
+
"command": "ccst hooks run pending-migration",
|
|
27
|
+
"timeout": 5,
|
|
28
|
+
"statusMessage": "Checking for unmigrated legacy data stores..."
|
|
29
|
+
}
|
|
30
|
+
]
|
|
31
|
+
}
|
|
32
|
+
],
|
|
33
|
+
"UserPromptSubmit": [
|
|
34
|
+
{
|
|
35
|
+
"hooks": [
|
|
36
|
+
{
|
|
37
|
+
"type": "command",
|
|
38
|
+
"command": "ccst hooks run last-screenshot",
|
|
39
|
+
"timeout": 5,
|
|
40
|
+
"statusMessage": "Resolving latest screenshot for >lss..."
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
"type": "command",
|
|
44
|
+
"command": "ccst hooks run messaging-deliver",
|
|
45
|
+
"timeout": 10,
|
|
46
|
+
"statusMessage": "Checking for new inter-session messages..."
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
"type": "command",
|
|
50
|
+
"command": "ccst hooks run catchup",
|
|
51
|
+
"timeout": 10,
|
|
52
|
+
"statusMessage": "Surfacing scheduled-task catch-up..."
|
|
53
|
+
}
|
|
54
|
+
]
|
|
55
|
+
}
|
|
56
|
+
],
|
|
57
|
+
"PreToolUse": [
|
|
58
|
+
{
|
|
59
|
+
"matcher": "Bash",
|
|
60
|
+
"hooks": [
|
|
61
|
+
{
|
|
62
|
+
"type": "command",
|
|
63
|
+
"command": "ccst hooks run bash-hard-deny",
|
|
64
|
+
"timeout": 5,
|
|
65
|
+
"statusMessage": "Checking Bash command against hard-deny rules..."
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
"type": "command",
|
|
69
|
+
"command": "ccst hooks run marker-allow",
|
|
70
|
+
"timeout": 5,
|
|
71
|
+
"statusMessage": "Checking for skill-marker touch..."
|
|
72
|
+
},
|
|
73
|
+
{
|
|
74
|
+
"type": "command",
|
|
75
|
+
"command": "ccst hooks run bash-security-review",
|
|
76
|
+
"timeout": 30,
|
|
77
|
+
"statusMessage": "Reviewing shell command for security risks..."
|
|
78
|
+
}
|
|
79
|
+
]
|
|
80
|
+
},
|
|
81
|
+
{
|
|
82
|
+
"hooks": [
|
|
83
|
+
{
|
|
84
|
+
"type": "command",
|
|
85
|
+
"command": "ccst hooks run confirm-8digit",
|
|
86
|
+
"timeout": 10,
|
|
87
|
+
"statusMessage": "Checking 8-digit confirmation gate..."
|
|
88
|
+
}
|
|
89
|
+
]
|
|
90
|
+
}
|
|
91
|
+
],
|
|
92
|
+
"Stop": [
|
|
93
|
+
{
|
|
94
|
+
"hooks": [
|
|
95
|
+
{
|
|
96
|
+
"type": "command",
|
|
97
|
+
"command": "ccst hooks run after-response",
|
|
98
|
+
"timeout": 10,
|
|
99
|
+
"statusMessage": "Recording session activity..."
|
|
100
|
+
}
|
|
101
|
+
]
|
|
102
|
+
}
|
|
103
|
+
],
|
|
104
|
+
"PreCompact": [
|
|
105
|
+
{
|
|
106
|
+
"matcher": "manual",
|
|
107
|
+
"hooks": [
|
|
108
|
+
{
|
|
109
|
+
"type": "command",
|
|
110
|
+
"command": "ccst hooks run worklog-guard",
|
|
111
|
+
"timeout": 5,
|
|
112
|
+
"statusMessage": "Checking WORKLOG.md freshness..."
|
|
113
|
+
}
|
|
114
|
+
]
|
|
115
|
+
}
|
|
116
|
+
]
|
|
117
|
+
}
|
|
118
|
+
}
|
|
@@ -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
|
|