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.
Files changed (239) hide show
  1. {cc_session_tools-2.2.0/src/cc_session_tools.egg-info → cc_session_tools-2.4.0}/PKG-INFO +14 -3
  2. {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/README.md +13 -2
  3. {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/pyproject.toml +14 -2
  4. {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/src/cc_session_tools/cli/ccst.py +97 -33
  5. cc_session_tools-2.4.0/src/cc_session_tools/config/hooks-bundle.json +118 -0
  6. cc_session_tools-2.4.0/src/cc_session_tools/config/session-tag-hook.json +16 -0
  7. {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/src/cc_session_tools/lib/doctor.py +80 -8
  8. {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/src/cc_session_tools/lib/doctor_mutes.py +12 -1
  9. cc_session_tools-2.4.0/src/cc_session_tools/lib/install_sync.py +103 -0
  10. {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/src/cc_session_tools/lib/sessions_db.py +6 -0
  11. cc_session_tools-2.4.0/src/cc_session_tools/skills/analyse-cc-usage/SKILL.md +230 -0
  12. cc_session_tools-2.4.0/src/cc_session_tools/skills/delete-sessions/SKILL.md +158 -0
  13. cc_session_tools-2.4.0/src/cc_session_tools/skills/delete-sessions/scripts/delete_sessions.py +429 -0
  14. cc_session_tools-2.4.0/src/cc_session_tools/skills/do-executor-critic-assessor-loop/SKILL.md +89 -0
  15. cc_session_tools-2.4.0/src/cc_session_tools/skills/find-claude-code-session/SKILL.md +129 -0
  16. cc_session_tools-2.4.0/src/cc_session_tools/skills/generate-8digit-code/SKILL.md +43 -0
  17. cc_session_tools-2.4.0/src/cc_session_tools/skills/generate-8digit-code/scripts/generate_8digit_code.py +28 -0
  18. cc_session_tools-2.4.0/src/cc_session_tools/skills/list-empty-sessions/SKILL.md +125 -0
  19. cc_session_tools-2.4.0/src/cc_session_tools/skills/list-empty-sessions/scripts/list_empty_sessions.py +79 -0
  20. cc_session_tools-2.4.0/src/cc_session_tools/skills/manage-recurring-cc-jobs-using-ccsched/SKILL.md +81 -0
  21. cc_session_tools-2.4.0/src/cc_session_tools/skills/move-session/SKILL.md +229 -0
  22. cc_session_tools-2.4.0/src/cc_session_tools/skills/move-session/hooks/sessionstart-pending-rename.sh +67 -0
  23. cc_session_tools-2.4.0/src/cc_session_tools/skills/move-session/scripts/cc_session_rules.py +91 -0
  24. cc_session_tools-2.4.0/src/cc_session_tools/skills/move-session/scripts/move_session.py +1089 -0
  25. cc_session_tools-2.4.0/src/cc_session_tools/skills/pm-pdata-conflict-resolution/SKILL.md +67 -0
  26. cc_session_tools-2.4.0/src/cc_session_tools/skills/pm-pdata-schema-design/SKILL.md +82 -0
  27. cc_session_tools-2.4.0/src/cc_session_tools/skills/pm-project-init/SKILL.md +142 -0
  28. cc_session_tools-2.4.0/src/cc_session_tools/skills/pm-update-central-files/SKILL.md +152 -0
  29. cc_session_tools-2.4.0/src/cc_session_tools/skills/reduce-persistent-context/CALIBRATION.md +74 -0
  30. cc_session_tools-2.4.0/src/cc_session_tools/skills/reduce-persistent-context/SKILL.md +109 -0
  31. cc_session_tools-2.4.0/src/cc_session_tools/skills/reduce-persistent-context/scripts/analyze_context.py +65 -0
  32. cc_session_tools-2.4.0/src/cc_session_tools/skills/reduce-persistent-context/scripts/duplicates.py +72 -0
  33. cc_session_tools-2.4.0/src/cc_session_tools/skills/reduce-persistent-context/scripts/measure.py +162 -0
  34. cc_session_tools-2.4.0/src/cc_session_tools/skills/reduce-persistent-context/scripts/report.py +53 -0
  35. cc_session_tools-2.4.0/src/cc_session_tools/skills/reduce-persistent-context/scripts/tokens.py +6 -0
  36. cc_session_tools-2.4.0/src/cc_session_tools/skills/reduce-persistent-context/scripts/usage.py +34 -0
  37. cc_session_tools-2.4.0/src/cc_session_tools/skills/select-agent-model/SKILL.md +52 -0
  38. cc_session_tools-2.4.0/src/cc_session_tools/skills/send-session-message/SKILL.md +63 -0
  39. cc_session_tools-2.4.0/src/cc_session_tools/skills/update-command-cache/SKILL.md +62 -0
  40. cc_session_tools-2.4.0/src/cc_session_tools/skills/update-command-cache/scripts/update_command_cache.py +218 -0
  41. {cc_session_tools-2.2.0 → cc_session_tools-2.4.0/src/cc_session_tools.egg-info}/PKG-INFO +14 -3
  42. {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/src/cc_session_tools.egg-info/SOURCES.txt +36 -0
  43. {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/src/cccs_hooks/bash_hard_deny.py +55 -3
  44. {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/src/cccs_hooks/bash_security_review.py +43 -37
  45. {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/src/cccs_hooks/catchup.py +15 -4
  46. {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/src/cccs_hooks/messaging_deliver.py +9 -4
  47. {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/src/cccs_hooks/normalise.py +4 -0
  48. {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/tests/test_bash_hard_deny.py +129 -0
  49. {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/tests/test_bash_security_review.py +222 -0
  50. cc_session_tools-2.4.0/tests/test_ccst_bundle_discovery.py +52 -0
  51. {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/tests/test_ccst_doctor.py +165 -11
  52. {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/tests/test_ccst_hooks_install_prune.py +1 -1
  53. {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/tests/test_ccst_hooks_install_zero_arg.py +3 -3
  54. cc_session_tools-2.4.0/tests/test_ccst_install_everything.py +293 -0
  55. cc_session_tools-2.4.0/tests/test_ccst_main_gate.py +197 -0
  56. {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/tests/test_ccst_repair_sessions_cli.py +39 -0
  57. {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/tests/test_ccst_skills_uninstall.py +3 -3
  58. {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/tests/test_doctor_mutes.py +13 -0
  59. cc_session_tools-2.4.0/tests/test_install_sync.py +155 -0
  60. {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/tests/test_normalise.py +34 -0
  61. {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/tests/test_sessions_db.py +2 -2
  62. cc_session_tools-2.2.0/tests/test_ccst_install_everything.py +0 -160
  63. {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/LICENSE +0 -0
  64. {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/setup.cfg +0 -0
  65. {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/src/cc_session_tools/__init__.py +0 -0
  66. {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/src/cc_session_tools/cli/__init__.py +0 -0
  67. {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/src/cc_session_tools/cli/ccd.py +0 -0
  68. {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/src/cc_session_tools/cli/ccmsg.py +0 -0
  69. {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/src/cc_session_tools/cli/ccr.py +0 -0
  70. {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/src/cc_session_tools/cli/ccs.py +0 -0
  71. {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/src/cc_session_tools/cli/ccsched.py +0 -0
  72. {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/src/cc_session_tools/cli/migrate_ccmsg.py +0 -0
  73. {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/src/cc_session_tools/cli/migrate_ccsched.py +0 -0
  74. {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/src/cc_session_tools/cli/migrate_sessions_db.py +0 -0
  75. {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/src/cc_session_tools/cli/migrate_telemetry.py +0 -0
  76. {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/src/cc_session_tools/hooks_install.py +0 -0
  77. {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/src/cc_session_tools/lib/__init__.py +0 -0
  78. {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/src/cc_session_tools/lib/claude_flags.py +0 -0
  79. {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/src/cc_session_tools/lib/claude_md_install.py +0 -0
  80. {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/src/cc_session_tools/lib/db.py +0 -0
  81. {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/src/cc_session_tools/lib/debug.py +0 -0
  82. {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/src/cc_session_tools/lib/hook_registry.py +0 -0
  83. {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/src/cc_session_tools/lib/levenshtein.py +0 -0
  84. {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/src/cc_session_tools/lib/messaging/__init__.py +0 -0
  85. {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/src/cc_session_tools/lib/messaging/addressing.py +0 -0
  86. {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/src/cc_session_tools/lib/messaging/cursor.py +0 -0
  87. {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/src/cc_session_tools/lib/messaging/lock.py +0 -0
  88. {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/src/cc_session_tools/lib/messaging/message.py +0 -0
  89. {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/src/cc_session_tools/lib/messaging/move_safety.py +0 -0
  90. {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/src/cc_session_tools/lib/messaging/repository.py +0 -0
  91. {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/src/cc_session_tools/lib/messaging/retention.py +0 -0
  92. {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/src/cc_session_tools/lib/messaging/service.py +0 -0
  93. {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/src/cc_session_tools/lib/messaging/store.py +0 -0
  94. {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/src/cc_session_tools/lib/paths.py +0 -0
  95. {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/src/cc_session_tools/lib/pdata/__init__.py +0 -0
  96. {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/src/cc_session_tools/lib/pdata/backup.py +0 -0
  97. {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/src/cc_session_tools/lib/pdata/classify.py +0 -0
  98. {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/src/cc_session_tools/lib/pdata/cutover.py +0 -0
  99. {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/src/cc_session_tools/lib/pdata/formatting.py +0 -0
  100. {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/src/cc_session_tools/lib/pdata/importers.py +0 -0
  101. {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/src/cc_session_tools/lib/pdata/init_paths.py +0 -0
  102. {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/src/cc_session_tools/lib/pdata/init_service.py +0 -0
  103. {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/src/cc_session_tools/lib/pdata/manifest.py +0 -0
  104. {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/src/cc_session_tools/lib/pdata/naming.py +0 -0
  105. {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/src/cc_session_tools/lib/pdata/repository.py +0 -0
  106. {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/src/cc_session_tools/lib/pdata/service.py +0 -0
  107. {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/src/cc_session_tools/lib/pdata/session_output.py +0 -0
  108. {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/src/cc_session_tools/lib/pdata/store.py +0 -0
  109. {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/src/cc_session_tools/lib/pdata/verify.py +0 -0
  110. {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/src/cc_session_tools/lib/picker.py +0 -0
  111. {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/src/cc_session_tools/lib/prompts.py +0 -0
  112. {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/src/cc_session_tools/lib/roots.py +0 -0
  113. {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/src/cc_session_tools/lib/rules.py +0 -0
  114. {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/src/cc_session_tools/lib/scheduler/__init__.py +0 -0
  115. {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/src/cc_session_tools/lib/scheduler/bundled_jobs.py +0 -0
  116. {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/src/cc_session_tools/lib/scheduler/cadence.py +0 -0
  117. {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/src/cc_session_tools/lib/scheduler/cursor.py +0 -0
  118. {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/src/cc_session_tools/lib/scheduler/digest.py +0 -0
  119. {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/src/cc_session_tools/lib/scheduler/due.py +0 -0
  120. {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/src/cc_session_tools/lib/scheduler/duration.py +0 -0
  121. {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/src/cc_session_tools/lib/scheduler/jobspec.py +0 -0
  122. {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/src/cc_session_tools/lib/scheduler/ledger.py +0 -0
  123. {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/src/cc_session_tools/lib/scheduler/lock.py +0 -0
  124. {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/src/cc_session_tools/lib/scheduler/notify.py +0 -0
  125. {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/src/cc_session_tools/lib/scheduler/reconcile.py +0 -0
  126. {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/src/cc_session_tools/lib/scheduler/registry.py +0 -0
  127. {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/src/cc_session_tools/lib/scheduler/runner.py +0 -0
  128. {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/src/cc_session_tools/lib/scheduler/state.py +0 -0
  129. {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/src/cc_session_tools/lib/scheduler/store.py +0 -0
  130. {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/src/cc_session_tools/lib/scheduler/surface.py +0 -0
  131. {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/src/cc_session_tools/lib/scheduler/throttle.py +0 -0
  132. {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/src/cc_session_tools/lib/scheduler/worker.py +0 -0
  133. {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/src/cc_session_tools/lib/session_gc.py +0 -0
  134. {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/src/cc_session_tools/lib/sessions.py +0 -0
  135. {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/src/cc_session_tools/lib/sessions_repair.py +0 -0
  136. {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/src/cc_session_tools/lib/shell_install.py +0 -0
  137. {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/src/cc_session_tools/lib/tasklist.py +0 -0
  138. {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/src/cc_session_tools/lib/telemetry_store.py +0 -0
  139. {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/src/cc_session_tools.egg-info/dependency_links.txt +0 -0
  140. {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/src/cc_session_tools.egg-info/entry_points.txt +0 -0
  141. {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/src/cc_session_tools.egg-info/requires.txt +0 -0
  142. {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/src/cc_session_tools.egg-info/top_level.txt +0 -0
  143. {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/src/cccs_hooks/__init__.py +0 -0
  144. {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/src/cccs_hooks/after_response.py +0 -0
  145. {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/src/cccs_hooks/cache.py +0 -0
  146. {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/src/cccs_hooks/confirm_8digit.py +0 -0
  147. {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/src/cccs_hooks/last_screenshot.py +0 -0
  148. {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/src/cccs_hooks/marker_allow.py +0 -0
  149. {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/src/cccs_hooks/markers.py +0 -0
  150. {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/src/cccs_hooks/pending_migration.py +0 -0
  151. {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/src/cccs_hooks/session_tag.py +0 -0
  152. {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/src/cccs_hooks/stats.py +0 -0
  153. {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/src/cccs_hooks/telemetry.py +0 -0
  154. {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/src/cccs_hooks/telemetry_query.py +0 -0
  155. {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/src/cccs_hooks/telemetry_trim.py +0 -0
  156. {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/src/cccs_hooks/transcript.py +0 -0
  157. {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/src/cccs_hooks/worklog_guard.py +0 -0
  158. {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/src/claude_code_usage/__init__.py +0 -0
  159. {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/src/claude_code_usage/attribution.py +0 -0
  160. {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/src/claude_code_usage/cache.py +0 -0
  161. {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/src/claude_code_usage/ccusage_wrapper.py +0 -0
  162. {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/src/claude_code_usage/cli.py +0 -0
  163. {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/src/claude_code_usage/data/pricing.json +0 -0
  164. {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/src/claude_code_usage/parent_inference.py +0 -0
  165. {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/src/claude_code_usage/parser.py +0 -0
  166. {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/src/claude_code_usage/pricing.py +0 -0
  167. {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/src/claude_code_usage/query.py +0 -0
  168. {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/src/claude_code_usage/report.py +0 -0
  169. {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/src/claude_code_usage/schema.py +0 -0
  170. {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/src/claude_code_usage/session_names.py +0 -0
  171. {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/tests/test_after_response.py +0 -0
  172. {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/tests/test_attribution.py +0 -0
  173. {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/tests/test_cache.py +0 -0
  174. {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/tests/test_cache_sqlite.py +0 -0
  175. {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/tests/test_ccr_orphans.py +0 -0
  176. {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/tests/test_ccs_emptiness.py +0 -0
  177. {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/tests/test_ccs_eta.py +0 -0
  178. {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/tests/test_ccs_list_mode.py +0 -0
  179. {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/tests/test_ccs_scope_flags.py +0 -0
  180. {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/tests/test_ccs_sentinel_sort.py +0 -0
  181. {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/tests/test_ccs_session_counts.py +0 -0
  182. {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/tests/test_ccst_ccsched_jobs_cli.py +0 -0
  183. {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/tests/test_ccst_cli.py +0 -0
  184. {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/tests/test_ccst_gc_report.py +0 -0
  185. {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/tests/test_ccst_hook_dispatcher.py +0 -0
  186. {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/tests/test_ccst_hooks_uninstall.py +0 -0
  187. {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/tests/test_ccst_pdata_cli.py +0 -0
  188. {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/tests/test_ccst_pdata_init_cli.py +0 -0
  189. {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/tests/test_ccst_pdata_reconcile_cli.py +0 -0
  190. {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/tests/test_ccst_pdata_verify_cli.py +0 -0
  191. {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/tests/test_ccst_sessions_cli.py +0 -0
  192. {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/tests/test_ccst_shell_install.py +0 -0
  193. {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/tests/test_ccst_skills_install.py +0 -0
  194. {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/tests/test_ccst_telemetry_query.py +0 -0
  195. {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/tests/test_ccst_telemetry_trim.py +0 -0
  196. {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/tests/test_ccusage_wrapper.py +0 -0
  197. {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/tests/test_claude_flags.py +0 -0
  198. {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/tests/test_claude_md_install.py +0 -0
  199. {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/tests/test_cli.py +0 -0
  200. {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/tests/test_cli_ccd.py +0 -0
  201. {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/tests/test_cli_ccd_dry_run.py +0 -0
  202. {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/tests/test_cli_ccr.py +0 -0
  203. {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/tests/test_cli_ccs.py +0 -0
  204. {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/tests/test_cli_version.py +0 -0
  205. {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/tests/test_confirm_8digit.py +0 -0
  206. {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/tests/test_db.py +0 -0
  207. {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/tests/test_debug.py +0 -0
  208. {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/tests/test_empty_session.py +0 -0
  209. {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/tests/test_hook_cache.py +0 -0
  210. {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/tests/test_hooks_install.py +0 -0
  211. {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/tests/test_last_screenshot.py +0 -0
  212. {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/tests/test_levenshtein.py +0 -0
  213. {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/tests/test_marker_allow.py +0 -0
  214. {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/tests/test_migrate_ccmsg.py +0 -0
  215. {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/tests/test_migrate_ccsched.py +0 -0
  216. {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/tests/test_migrate_sessions_db.py +0 -0
  217. {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/tests/test_migrate_telemetry.py +0 -0
  218. {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/tests/test_parent_inference.py +0 -0
  219. {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/tests/test_parser.py +0 -0
  220. {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/tests/test_paths.py +0 -0
  221. {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/tests/test_pending_migration_hook.py +0 -0
  222. {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/tests/test_picker.py +0 -0
  223. {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/tests/test_pricing.py +0 -0
  224. {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/tests/test_prompts.py +0 -0
  225. {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/tests/test_query.py +0 -0
  226. {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/tests/test_report.py +0 -0
  227. {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/tests/test_roots.py +0 -0
  228. {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/tests/test_rules.py +0 -0
  229. {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/tests/test_scheduler_bundled_jobs.py +0 -0
  230. {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/tests/test_schema.py +0 -0
  231. {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/tests/test_session_names.py +0 -0
  232. {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/tests/test_session_tag.py +0 -0
  233. {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/tests/test_sessions.py +0 -0
  234. {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/tests/test_sessions_repair.py +0 -0
  235. {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/tests/test_tasklist.py +0 -0
  236. {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/tests/test_telemetry.py +0 -0
  237. {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/tests/test_telemetry_store.py +0 -0
  238. {cc_session_tools-2.2.0 → cc_session_tools-2.4.0}/tests/test_transcript.py +0 -0
  239. {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.2.0
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, version drift
888
- between installed `ccst` and the latest release on PyPI.
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, version drift
831
- between installed `ccst` and the latest release on PyPI.
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.2.0"
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
- Walk up from this module's file to find the repo root containing skills/.
92
- Falls back to ~/repos/claude-code-session-tools/skills/ for wheel installs.
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
- # From src/cc_session_tools/cli/ccst.py walk up: cli/ -> cc_session_tools/ -> src/ -> repo root
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
- Walk up from this module's file to find the repo root containing config/.
112
- Falls back to ~/repos/claude-code-session-tools/config/... for wheel installs.
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
- here = Path(__file__).resolve()
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
- if args.execute:
1262
- if not db_path.exists():
1263
- # sqlite3.connect() auto-creates an empty file, so without this check
1264
- # db_lib.backup_to() below would silently back up (and repair() would open)
1265
- # a brand-new, empty sessions.db instead of failing loudly on the mistake.
1266
- print(f"No sessions.db found at {db_path} — nothing to back up or repair.", file=sys.stderr)
1267
- return 1
1268
- backup_dir = db_path.parent / "repair-backups"
1269
- backup_dir.mkdir(parents=True, exist_ok=True)
1270
- stamp = datetime.datetime.now(datetime.timezone.utc).strftime("%Y%m%dT%H%M%SZ")
1271
- backup_path = backup_dir / f"sessions-{stamp}.db"
1272
- db_lib.backup_to(db_path, backup_path)
1273
- print(f"Backed up sessions.db to {backup_path}")
1274
-
1275
- report = sessions_repair.repair(roots, path=db_path, dry_run=not args.execute)
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
+ }
@@ -0,0 +1,16 @@
1
+ {
2
+ "hooks": {
3
+ "SessionStart": [
4
+ {
5
+ "hooks": [
6
+ {
7
+ "type": "command",
8
+ "command": "ccst hooks run session-tag",
9
+ "timeout": 5,
10
+ "statusMessage": "Writing session tag file..."
11
+ }
12
+ ]
13
+ }
14
+ ]
15
+ }
16
+ }
@@ -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.OperationalError:
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
- bad = sessions_repair.find_non_absolute_rows(path=sessions_db_path)
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
- name_w = max(len(r.name) for r in results)
933
+ shown = results if show_all else [r for r in results if r.status is not Status.OK]
868
934
  lines = []
869
- for r in results:
870
- lines.append(f"[{r.status.value:<4}] {r.name:<{name_w}} {r.reason}")
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