cc-session-tools 2.7.0__tar.gz → 2.7.2__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 (251) hide show
  1. {cc_session_tools-2.7.0/src/cc_session_tools.egg-info → cc_session_tools-2.7.2}/PKG-INFO +4 -2
  2. {cc_session_tools-2.7.0 → cc_session_tools-2.7.2}/README.md +3 -1
  3. {cc_session_tools-2.7.0 → cc_session_tools-2.7.2}/pyproject.toml +1 -1
  4. {cc_session_tools-2.7.0 → cc_session_tools-2.7.2}/src/cc_session_tools/cli/ccst.py +9 -1
  5. {cc_session_tools-2.7.0 → cc_session_tools-2.7.2}/src/cc_session_tools/config/hooks-bundle.json +6 -0
  6. {cc_session_tools-2.7.0 → cc_session_tools-2.7.2}/src/cc_session_tools/lib/db.py +11 -1
  7. {cc_session_tools-2.7.0 → cc_session_tools-2.7.2}/src/cc_session_tools/lib/hook_registry.py +2 -0
  8. {cc_session_tools-2.7.0 → cc_session_tools-2.7.2}/src/cc_session_tools/lib/pdata/backup.py +38 -27
  9. {cc_session_tools-2.7.0 → cc_session_tools-2.7.2}/src/cc_session_tools/lib/pdata/init_service.py +3 -1
  10. {cc_session_tools-2.7.0 → cc_session_tools-2.7.2}/src/cc_session_tools/lib/pdata/write_log.py +11 -0
  11. cc_session_tools-2.7.2/src/cc_session_tools/skills/move-session/hooks/sessionstart-pending-rename.sh +206 -0
  12. {cc_session_tools-2.7.0 → cc_session_tools-2.7.2}/src/cc_session_tools/skills/pm-project-init/SKILL.md +12 -1
  13. {cc_session_tools-2.7.0 → cc_session_tools-2.7.2/src/cc_session_tools.egg-info}/PKG-INFO +4 -2
  14. {cc_session_tools-2.7.0 → cc_session_tools-2.7.2}/src/cc_session_tools.egg-info/SOURCES.txt +1 -0
  15. cc_session_tools-2.7.2/src/cccs_hooks/pending_rename.py +37 -0
  16. {cc_session_tools-2.7.0 → cc_session_tools-2.7.2}/tests/test_ccst_doctor.py +1 -1
  17. {cc_session_tools-2.7.0 → cc_session_tools-2.7.2}/tests/test_ccst_hook_dispatcher.py +1 -0
  18. {cc_session_tools-2.7.0 → cc_session_tools-2.7.2}/tests/test_ccst_hooks_install_zero_arg.py +1 -0
  19. {cc_session_tools-2.7.0 → cc_session_tools-2.7.2}/tests/test_ccst_pdata_init_cli.py +44 -1
  20. {cc_session_tools-2.7.0 → cc_session_tools-2.7.2}/tests/test_db.py +25 -0
  21. cc_session_tools-2.7.0/src/cc_session_tools/skills/move-session/hooks/sessionstart-pending-rename.sh +0 -67
  22. {cc_session_tools-2.7.0 → cc_session_tools-2.7.2}/LICENSE +0 -0
  23. {cc_session_tools-2.7.0 → cc_session_tools-2.7.2}/setup.cfg +0 -0
  24. {cc_session_tools-2.7.0 → cc_session_tools-2.7.2}/src/cc_session_tools/__init__.py +0 -0
  25. {cc_session_tools-2.7.0 → cc_session_tools-2.7.2}/src/cc_session_tools/cli/__init__.py +0 -0
  26. {cc_session_tools-2.7.0 → cc_session_tools-2.7.2}/src/cc_session_tools/cli/ccd.py +0 -0
  27. {cc_session_tools-2.7.0 → cc_session_tools-2.7.2}/src/cc_session_tools/cli/ccmsg.py +0 -0
  28. {cc_session_tools-2.7.0 → cc_session_tools-2.7.2}/src/cc_session_tools/cli/ccr.py +0 -0
  29. {cc_session_tools-2.7.0 → cc_session_tools-2.7.2}/src/cc_session_tools/cli/ccs.py +0 -0
  30. {cc_session_tools-2.7.0 → cc_session_tools-2.7.2}/src/cc_session_tools/cli/ccsched.py +0 -0
  31. {cc_session_tools-2.7.0 → cc_session_tools-2.7.2}/src/cc_session_tools/cli/migrate_ccmsg.py +0 -0
  32. {cc_session_tools-2.7.0 → cc_session_tools-2.7.2}/src/cc_session_tools/cli/migrate_ccsched.py +0 -0
  33. {cc_session_tools-2.7.0 → cc_session_tools-2.7.2}/src/cc_session_tools/cli/migrate_sessions_db.py +0 -0
  34. {cc_session_tools-2.7.0 → cc_session_tools-2.7.2}/src/cc_session_tools/cli/migrate_telemetry.py +0 -0
  35. {cc_session_tools-2.7.0 → cc_session_tools-2.7.2}/src/cc_session_tools/config/session-tag-hook.json +0 -0
  36. {cc_session_tools-2.7.0 → cc_session_tools-2.7.2}/src/cc_session_tools/hooks_install.py +0 -0
  37. {cc_session_tools-2.7.0 → cc_session_tools-2.7.2}/src/cc_session_tools/lib/__init__.py +0 -0
  38. {cc_session_tools-2.7.0 → cc_session_tools-2.7.2}/src/cc_session_tools/lib/claude_flags.py +0 -0
  39. {cc_session_tools-2.7.0 → cc_session_tools-2.7.2}/src/cc_session_tools/lib/claude_md_install.py +0 -0
  40. {cc_session_tools-2.7.0 → cc_session_tools-2.7.2}/src/cc_session_tools/lib/context_overrides.py +0 -0
  41. {cc_session_tools-2.7.0 → cc_session_tools-2.7.2}/src/cc_session_tools/lib/debug.py +0 -0
  42. {cc_session_tools-2.7.0 → cc_session_tools-2.7.2}/src/cc_session_tools/lib/doctor.py +0 -0
  43. {cc_session_tools-2.7.0 → cc_session_tools-2.7.2}/src/cc_session_tools/lib/doctor_mutes.py +0 -0
  44. {cc_session_tools-2.7.0 → cc_session_tools-2.7.2}/src/cc_session_tools/lib/install_sync.py +0 -0
  45. {cc_session_tools-2.7.0 → cc_session_tools-2.7.2}/src/cc_session_tools/lib/levenshtein.py +0 -0
  46. {cc_session_tools-2.7.0 → cc_session_tools-2.7.2}/src/cc_session_tools/lib/messaging/__init__.py +0 -0
  47. {cc_session_tools-2.7.0 → cc_session_tools-2.7.2}/src/cc_session_tools/lib/messaging/addressing.py +0 -0
  48. {cc_session_tools-2.7.0 → cc_session_tools-2.7.2}/src/cc_session_tools/lib/messaging/cursor.py +0 -0
  49. {cc_session_tools-2.7.0 → cc_session_tools-2.7.2}/src/cc_session_tools/lib/messaging/lock.py +0 -0
  50. {cc_session_tools-2.7.0 → cc_session_tools-2.7.2}/src/cc_session_tools/lib/messaging/message.py +0 -0
  51. {cc_session_tools-2.7.0 → cc_session_tools-2.7.2}/src/cc_session_tools/lib/messaging/move_safety.py +0 -0
  52. {cc_session_tools-2.7.0 → cc_session_tools-2.7.2}/src/cc_session_tools/lib/messaging/repository.py +0 -0
  53. {cc_session_tools-2.7.0 → cc_session_tools-2.7.2}/src/cc_session_tools/lib/messaging/retention.py +0 -0
  54. {cc_session_tools-2.7.0 → cc_session_tools-2.7.2}/src/cc_session_tools/lib/messaging/service.py +0 -0
  55. {cc_session_tools-2.7.0 → cc_session_tools-2.7.2}/src/cc_session_tools/lib/messaging/store.py +0 -0
  56. {cc_session_tools-2.7.0 → cc_session_tools-2.7.2}/src/cc_session_tools/lib/paths.py +0 -0
  57. {cc_session_tools-2.7.0 → cc_session_tools-2.7.2}/src/cc_session_tools/lib/pdata/__init__.py +0 -0
  58. {cc_session_tools-2.7.0 → cc_session_tools-2.7.2}/src/cc_session_tools/lib/pdata/classify.py +0 -0
  59. {cc_session_tools-2.7.0 → cc_session_tools-2.7.2}/src/cc_session_tools/lib/pdata/cutover.py +0 -0
  60. {cc_session_tools-2.7.0 → cc_session_tools-2.7.2}/src/cc_session_tools/lib/pdata/formatting.py +0 -0
  61. {cc_session_tools-2.7.0 → cc_session_tools-2.7.2}/src/cc_session_tools/lib/pdata/importers.py +0 -0
  62. {cc_session_tools-2.7.0 → cc_session_tools-2.7.2}/src/cc_session_tools/lib/pdata/init_paths.py +0 -0
  63. {cc_session_tools-2.7.0 → cc_session_tools-2.7.2}/src/cc_session_tools/lib/pdata/manifest.py +0 -0
  64. {cc_session_tools-2.7.0 → cc_session_tools-2.7.2}/src/cc_session_tools/lib/pdata/naming.py +0 -0
  65. {cc_session_tools-2.7.0 → cc_session_tools-2.7.2}/src/cc_session_tools/lib/pdata/repository.py +0 -0
  66. {cc_session_tools-2.7.0 → cc_session_tools-2.7.2}/src/cc_session_tools/lib/pdata/service.py +0 -0
  67. {cc_session_tools-2.7.0 → cc_session_tools-2.7.2}/src/cc_session_tools/lib/pdata/session_output.py +0 -0
  68. {cc_session_tools-2.7.0 → cc_session_tools-2.7.2}/src/cc_session_tools/lib/pdata/store.py +0 -0
  69. {cc_session_tools-2.7.0 → cc_session_tools-2.7.2}/src/cc_session_tools/lib/pdata/verify.py +0 -0
  70. {cc_session_tools-2.7.0 → cc_session_tools-2.7.2}/src/cc_session_tools/lib/picker.py +0 -0
  71. {cc_session_tools-2.7.0 → cc_session_tools-2.7.2}/src/cc_session_tools/lib/proc_lock.py +0 -0
  72. {cc_session_tools-2.7.0 → cc_session_tools-2.7.2}/src/cc_session_tools/lib/prompts.py +0 -0
  73. {cc_session_tools-2.7.0 → cc_session_tools-2.7.2}/src/cc_session_tools/lib/roots.py +0 -0
  74. {cc_session_tools-2.7.0 → cc_session_tools-2.7.2}/src/cc_session_tools/lib/rules.py +0 -0
  75. {cc_session_tools-2.7.0 → cc_session_tools-2.7.2}/src/cc_session_tools/lib/scheduler/__init__.py +0 -0
  76. {cc_session_tools-2.7.0 → cc_session_tools-2.7.2}/src/cc_session_tools/lib/scheduler/bundled_jobs.py +0 -0
  77. {cc_session_tools-2.7.0 → cc_session_tools-2.7.2}/src/cc_session_tools/lib/scheduler/cadence.py +0 -0
  78. {cc_session_tools-2.7.0 → cc_session_tools-2.7.2}/src/cc_session_tools/lib/scheduler/cursor.py +0 -0
  79. {cc_session_tools-2.7.0 → cc_session_tools-2.7.2}/src/cc_session_tools/lib/scheduler/digest.py +0 -0
  80. {cc_session_tools-2.7.0 → cc_session_tools-2.7.2}/src/cc_session_tools/lib/scheduler/due.py +0 -0
  81. {cc_session_tools-2.7.0 → cc_session_tools-2.7.2}/src/cc_session_tools/lib/scheduler/duration.py +0 -0
  82. {cc_session_tools-2.7.0 → cc_session_tools-2.7.2}/src/cc_session_tools/lib/scheduler/jobspec.py +0 -0
  83. {cc_session_tools-2.7.0 → cc_session_tools-2.7.2}/src/cc_session_tools/lib/scheduler/ledger.py +0 -0
  84. {cc_session_tools-2.7.0 → cc_session_tools-2.7.2}/src/cc_session_tools/lib/scheduler/lock.py +0 -0
  85. {cc_session_tools-2.7.0 → cc_session_tools-2.7.2}/src/cc_session_tools/lib/scheduler/notify.py +0 -0
  86. {cc_session_tools-2.7.0 → cc_session_tools-2.7.2}/src/cc_session_tools/lib/scheduler/reconcile.py +0 -0
  87. {cc_session_tools-2.7.0 → cc_session_tools-2.7.2}/src/cc_session_tools/lib/scheduler/registry.py +0 -0
  88. {cc_session_tools-2.7.0 → cc_session_tools-2.7.2}/src/cc_session_tools/lib/scheduler/runner.py +0 -0
  89. {cc_session_tools-2.7.0 → cc_session_tools-2.7.2}/src/cc_session_tools/lib/scheduler/state.py +0 -0
  90. {cc_session_tools-2.7.0 → cc_session_tools-2.7.2}/src/cc_session_tools/lib/scheduler/store.py +0 -0
  91. {cc_session_tools-2.7.0 → cc_session_tools-2.7.2}/src/cc_session_tools/lib/scheduler/surface.py +0 -0
  92. {cc_session_tools-2.7.0 → cc_session_tools-2.7.2}/src/cc_session_tools/lib/scheduler/throttle.py +0 -0
  93. {cc_session_tools-2.7.0 → cc_session_tools-2.7.2}/src/cc_session_tools/lib/scheduler/worker.py +0 -0
  94. {cc_session_tools-2.7.0 → cc_session_tools-2.7.2}/src/cc_session_tools/lib/session_gc.py +0 -0
  95. {cc_session_tools-2.7.0 → cc_session_tools-2.7.2}/src/cc_session_tools/lib/sessions.py +0 -0
  96. {cc_session_tools-2.7.0 → cc_session_tools-2.7.2}/src/cc_session_tools/lib/sessions_db.py +0 -0
  97. {cc_session_tools-2.7.0 → cc_session_tools-2.7.2}/src/cc_session_tools/lib/sessions_repair.py +0 -0
  98. {cc_session_tools-2.7.0 → cc_session_tools-2.7.2}/src/cc_session_tools/lib/shell_install.py +0 -0
  99. {cc_session_tools-2.7.0 → cc_session_tools-2.7.2}/src/cc_session_tools/lib/tasklist.py +0 -0
  100. {cc_session_tools-2.7.0 → cc_session_tools-2.7.2}/src/cc_session_tools/lib/telemetry_store.py +0 -0
  101. {cc_session_tools-2.7.0 → cc_session_tools-2.7.2}/src/cc_session_tools/skills/analyse-cc-usage/SKILL.md +0 -0
  102. {cc_session_tools-2.7.0 → cc_session_tools-2.7.2}/src/cc_session_tools/skills/context-override/SKILL.md +0 -0
  103. {cc_session_tools-2.7.0 → cc_session_tools-2.7.2}/src/cc_session_tools/skills/delete-sessions/SKILL.md +0 -0
  104. {cc_session_tools-2.7.0 → cc_session_tools-2.7.2}/src/cc_session_tools/skills/delete-sessions/scripts/delete_sessions.py +0 -0
  105. {cc_session_tools-2.7.0 → cc_session_tools-2.7.2}/src/cc_session_tools/skills/do-executor-critic-assessor-loop/SKILL.md +0 -0
  106. {cc_session_tools-2.7.0 → cc_session_tools-2.7.2}/src/cc_session_tools/skills/find-claude-code-session/SKILL.md +0 -0
  107. {cc_session_tools-2.7.0 → cc_session_tools-2.7.2}/src/cc_session_tools/skills/generate-8digit-code/SKILL.md +0 -0
  108. {cc_session_tools-2.7.0 → cc_session_tools-2.7.2}/src/cc_session_tools/skills/generate-8digit-code/scripts/generate_8digit_code.py +0 -0
  109. {cc_session_tools-2.7.0 → cc_session_tools-2.7.2}/src/cc_session_tools/skills/list-empty-sessions/SKILL.md +0 -0
  110. {cc_session_tools-2.7.0 → cc_session_tools-2.7.2}/src/cc_session_tools/skills/list-empty-sessions/scripts/list_empty_sessions.py +0 -0
  111. {cc_session_tools-2.7.0 → cc_session_tools-2.7.2}/src/cc_session_tools/skills/manage-recurring-cc-jobs-using-ccsched/SKILL.md +0 -0
  112. {cc_session_tools-2.7.0 → cc_session_tools-2.7.2}/src/cc_session_tools/skills/move-session/SKILL.md +0 -0
  113. {cc_session_tools-2.7.0 → cc_session_tools-2.7.2}/src/cc_session_tools/skills/move-session/scripts/cc_session_rules.py +0 -0
  114. {cc_session_tools-2.7.0 → cc_session_tools-2.7.2}/src/cc_session_tools/skills/move-session/scripts/move_session.py +0 -0
  115. {cc_session_tools-2.7.0 → cc_session_tools-2.7.2}/src/cc_session_tools/skills/pm-pdata-conflict-resolution/SKILL.md +0 -0
  116. {cc_session_tools-2.7.0 → cc_session_tools-2.7.2}/src/cc_session_tools/skills/pm-pdata-schema-design/SKILL.md +0 -0
  117. {cc_session_tools-2.7.0 → cc_session_tools-2.7.2}/src/cc_session_tools/skills/pm-update-central-files/SKILL.md +0 -0
  118. {cc_session_tools-2.7.0 → cc_session_tools-2.7.2}/src/cc_session_tools/skills/reduce-persistent-context/CALIBRATION.md +0 -0
  119. {cc_session_tools-2.7.0 → cc_session_tools-2.7.2}/src/cc_session_tools/skills/reduce-persistent-context/SKILL.md +0 -0
  120. {cc_session_tools-2.7.0 → cc_session_tools-2.7.2}/src/cc_session_tools/skills/reduce-persistent-context/scripts/analyze_context.py +0 -0
  121. {cc_session_tools-2.7.0 → cc_session_tools-2.7.2}/src/cc_session_tools/skills/reduce-persistent-context/scripts/duplicates.py +0 -0
  122. {cc_session_tools-2.7.0 → cc_session_tools-2.7.2}/src/cc_session_tools/skills/reduce-persistent-context/scripts/measure.py +0 -0
  123. {cc_session_tools-2.7.0 → cc_session_tools-2.7.2}/src/cc_session_tools/skills/reduce-persistent-context/scripts/report.py +0 -0
  124. {cc_session_tools-2.7.0 → cc_session_tools-2.7.2}/src/cc_session_tools/skills/reduce-persistent-context/scripts/tokens.py +0 -0
  125. {cc_session_tools-2.7.0 → cc_session_tools-2.7.2}/src/cc_session_tools/skills/reduce-persistent-context/scripts/usage.py +0 -0
  126. {cc_session_tools-2.7.0 → cc_session_tools-2.7.2}/src/cc_session_tools/skills/select-agent-model/SKILL.md +0 -0
  127. {cc_session_tools-2.7.0 → cc_session_tools-2.7.2}/src/cc_session_tools/skills/send-session-message/SKILL.md +0 -0
  128. {cc_session_tools-2.7.0 → cc_session_tools-2.7.2}/src/cc_session_tools/skills/update-command-cache/SKILL.md +0 -0
  129. {cc_session_tools-2.7.0 → cc_session_tools-2.7.2}/src/cc_session_tools/skills/update-command-cache/scripts/update_command_cache.py +0 -0
  130. {cc_session_tools-2.7.0 → cc_session_tools-2.7.2}/src/cc_session_tools.egg-info/dependency_links.txt +0 -0
  131. {cc_session_tools-2.7.0 → cc_session_tools-2.7.2}/src/cc_session_tools.egg-info/entry_points.txt +0 -0
  132. {cc_session_tools-2.7.0 → cc_session_tools-2.7.2}/src/cc_session_tools.egg-info/requires.txt +0 -0
  133. {cc_session_tools-2.7.0 → cc_session_tools-2.7.2}/src/cc_session_tools.egg-info/top_level.txt +0 -0
  134. {cc_session_tools-2.7.0 → cc_session_tools-2.7.2}/src/cccs_hooks/__init__.py +0 -0
  135. {cc_session_tools-2.7.0 → cc_session_tools-2.7.2}/src/cccs_hooks/after_response.py +0 -0
  136. {cc_session_tools-2.7.0 → cc_session_tools-2.7.2}/src/cccs_hooks/bash_hard_deny.py +0 -0
  137. {cc_session_tools-2.7.0 → cc_session_tools-2.7.2}/src/cccs_hooks/bash_security_review.py +0 -0
  138. {cc_session_tools-2.7.0 → cc_session_tools-2.7.2}/src/cccs_hooks/cache.py +0 -0
  139. {cc_session_tools-2.7.0 → cc_session_tools-2.7.2}/src/cccs_hooks/catchup.py +0 -0
  140. {cc_session_tools-2.7.0 → cc_session_tools-2.7.2}/src/cccs_hooks/confirm_8digit.py +0 -0
  141. {cc_session_tools-2.7.0 → cc_session_tools-2.7.2}/src/cccs_hooks/context_window_warning.py +0 -0
  142. {cc_session_tools-2.7.0 → cc_session_tools-2.7.2}/src/cccs_hooks/last_screenshot.py +0 -0
  143. {cc_session_tools-2.7.0 → cc_session_tools-2.7.2}/src/cccs_hooks/marker_allow.py +0 -0
  144. {cc_session_tools-2.7.0 → cc_session_tools-2.7.2}/src/cccs_hooks/markers.py +0 -0
  145. {cc_session_tools-2.7.0 → cc_session_tools-2.7.2}/src/cccs_hooks/messaging_deliver.py +0 -0
  146. {cc_session_tools-2.7.0 → cc_session_tools-2.7.2}/src/cccs_hooks/model_info.py +0 -0
  147. {cc_session_tools-2.7.0 → cc_session_tools-2.7.2}/src/cccs_hooks/normalise.py +0 -0
  148. {cc_session_tools-2.7.0 → cc_session_tools-2.7.2}/src/cccs_hooks/pending_migration.py +0 -0
  149. {cc_session_tools-2.7.0 → cc_session_tools-2.7.2}/src/cccs_hooks/session_tag.py +0 -0
  150. {cc_session_tools-2.7.0 → cc_session_tools-2.7.2}/src/cccs_hooks/stats.py +0 -0
  151. {cc_session_tools-2.7.0 → cc_session_tools-2.7.2}/src/cccs_hooks/telemetry.py +0 -0
  152. {cc_session_tools-2.7.0 → cc_session_tools-2.7.2}/src/cccs_hooks/telemetry_query.py +0 -0
  153. {cc_session_tools-2.7.0 → cc_session_tools-2.7.2}/src/cccs_hooks/telemetry_trim.py +0 -0
  154. {cc_session_tools-2.7.0 → cc_session_tools-2.7.2}/src/cccs_hooks/transcript.py +0 -0
  155. {cc_session_tools-2.7.0 → cc_session_tools-2.7.2}/src/cccs_hooks/worklog_guard.py +0 -0
  156. {cc_session_tools-2.7.0 → cc_session_tools-2.7.2}/src/claude_code_usage/__init__.py +0 -0
  157. {cc_session_tools-2.7.0 → cc_session_tools-2.7.2}/src/claude_code_usage/attribution.py +0 -0
  158. {cc_session_tools-2.7.0 → cc_session_tools-2.7.2}/src/claude_code_usage/cache.py +0 -0
  159. {cc_session_tools-2.7.0 → cc_session_tools-2.7.2}/src/claude_code_usage/ccusage_wrapper.py +0 -0
  160. {cc_session_tools-2.7.0 → cc_session_tools-2.7.2}/src/claude_code_usage/cli.py +0 -0
  161. {cc_session_tools-2.7.0 → cc_session_tools-2.7.2}/src/claude_code_usage/data/pricing.json +0 -0
  162. {cc_session_tools-2.7.0 → cc_session_tools-2.7.2}/src/claude_code_usage/parent_inference.py +0 -0
  163. {cc_session_tools-2.7.0 → cc_session_tools-2.7.2}/src/claude_code_usage/parser.py +0 -0
  164. {cc_session_tools-2.7.0 → cc_session_tools-2.7.2}/src/claude_code_usage/pricing.py +0 -0
  165. {cc_session_tools-2.7.0 → cc_session_tools-2.7.2}/src/claude_code_usage/query.py +0 -0
  166. {cc_session_tools-2.7.0 → cc_session_tools-2.7.2}/src/claude_code_usage/report.py +0 -0
  167. {cc_session_tools-2.7.0 → cc_session_tools-2.7.2}/src/claude_code_usage/schema.py +0 -0
  168. {cc_session_tools-2.7.0 → cc_session_tools-2.7.2}/src/claude_code_usage/session_names.py +0 -0
  169. {cc_session_tools-2.7.0 → cc_session_tools-2.7.2}/tests/test_after_response.py +0 -0
  170. {cc_session_tools-2.7.0 → cc_session_tools-2.7.2}/tests/test_attribution.py +0 -0
  171. {cc_session_tools-2.7.0 → cc_session_tools-2.7.2}/tests/test_bash_hard_deny.py +0 -0
  172. {cc_session_tools-2.7.0 → cc_session_tools-2.7.2}/tests/test_bash_security_review.py +0 -0
  173. {cc_session_tools-2.7.0 → cc_session_tools-2.7.2}/tests/test_cache.py +0 -0
  174. {cc_session_tools-2.7.0 → cc_session_tools-2.7.2}/tests/test_cache_sqlite.py +0 -0
  175. {cc_session_tools-2.7.0 → cc_session_tools-2.7.2}/tests/test_ccr_orphans.py +0 -0
  176. {cc_session_tools-2.7.0 → cc_session_tools-2.7.2}/tests/test_ccs_emptiness.py +0 -0
  177. {cc_session_tools-2.7.0 → cc_session_tools-2.7.2}/tests/test_ccs_eta.py +0 -0
  178. {cc_session_tools-2.7.0 → cc_session_tools-2.7.2}/tests/test_ccs_list_mode.py +0 -0
  179. {cc_session_tools-2.7.0 → cc_session_tools-2.7.2}/tests/test_ccs_scope_flags.py +0 -0
  180. {cc_session_tools-2.7.0 → cc_session_tools-2.7.2}/tests/test_ccs_sentinel_sort.py +0 -0
  181. {cc_session_tools-2.7.0 → cc_session_tools-2.7.2}/tests/test_ccs_session_counts.py +0 -0
  182. {cc_session_tools-2.7.0 → cc_session_tools-2.7.2}/tests/test_ccst_bundle_discovery.py +0 -0
  183. {cc_session_tools-2.7.0 → cc_session_tools-2.7.2}/tests/test_ccst_ccsched_jobs_cli.py +0 -0
  184. {cc_session_tools-2.7.0 → cc_session_tools-2.7.2}/tests/test_ccst_cli.py +0 -0
  185. {cc_session_tools-2.7.0 → cc_session_tools-2.7.2}/tests/test_ccst_context_override_cli.py +0 -0
  186. {cc_session_tools-2.7.0 → cc_session_tools-2.7.2}/tests/test_ccst_gc_report.py +0 -0
  187. {cc_session_tools-2.7.0 → cc_session_tools-2.7.2}/tests/test_ccst_hooks_install_prune.py +0 -0
  188. {cc_session_tools-2.7.0 → cc_session_tools-2.7.2}/tests/test_ccst_hooks_uninstall.py +0 -0
  189. {cc_session_tools-2.7.0 → cc_session_tools-2.7.2}/tests/test_ccst_install_everything.py +0 -0
  190. {cc_session_tools-2.7.0 → cc_session_tools-2.7.2}/tests/test_ccst_main_gate.py +0 -0
  191. {cc_session_tools-2.7.0 → cc_session_tools-2.7.2}/tests/test_ccst_pdata_cli.py +0 -0
  192. {cc_session_tools-2.7.0 → cc_session_tools-2.7.2}/tests/test_ccst_pdata_reconcile_cli.py +0 -0
  193. {cc_session_tools-2.7.0 → cc_session_tools-2.7.2}/tests/test_ccst_pdata_verify_cli.py +0 -0
  194. {cc_session_tools-2.7.0 → cc_session_tools-2.7.2}/tests/test_ccst_repair_sessions_cli.py +0 -0
  195. {cc_session_tools-2.7.0 → cc_session_tools-2.7.2}/tests/test_ccst_sessions_cli.py +0 -0
  196. {cc_session_tools-2.7.0 → cc_session_tools-2.7.2}/tests/test_ccst_shell_install.py +0 -0
  197. {cc_session_tools-2.7.0 → cc_session_tools-2.7.2}/tests/test_ccst_skills_install.py +0 -0
  198. {cc_session_tools-2.7.0 → cc_session_tools-2.7.2}/tests/test_ccst_skills_uninstall.py +0 -0
  199. {cc_session_tools-2.7.0 → cc_session_tools-2.7.2}/tests/test_ccst_telemetry_query.py +0 -0
  200. {cc_session_tools-2.7.0 → cc_session_tools-2.7.2}/tests/test_ccst_telemetry_trim.py +0 -0
  201. {cc_session_tools-2.7.0 → cc_session_tools-2.7.2}/tests/test_ccusage_wrapper.py +0 -0
  202. {cc_session_tools-2.7.0 → cc_session_tools-2.7.2}/tests/test_claude_flags.py +0 -0
  203. {cc_session_tools-2.7.0 → cc_session_tools-2.7.2}/tests/test_claude_md_install.py +0 -0
  204. {cc_session_tools-2.7.0 → cc_session_tools-2.7.2}/tests/test_cli.py +0 -0
  205. {cc_session_tools-2.7.0 → cc_session_tools-2.7.2}/tests/test_cli_ccd.py +0 -0
  206. {cc_session_tools-2.7.0 → cc_session_tools-2.7.2}/tests/test_cli_ccd_dry_run.py +0 -0
  207. {cc_session_tools-2.7.0 → cc_session_tools-2.7.2}/tests/test_cli_ccr.py +0 -0
  208. {cc_session_tools-2.7.0 → cc_session_tools-2.7.2}/tests/test_cli_ccs.py +0 -0
  209. {cc_session_tools-2.7.0 → cc_session_tools-2.7.2}/tests/test_cli_version.py +0 -0
  210. {cc_session_tools-2.7.0 → cc_session_tools-2.7.2}/tests/test_confirm_8digit.py +0 -0
  211. {cc_session_tools-2.7.0 → cc_session_tools-2.7.2}/tests/test_context_overrides.py +0 -0
  212. {cc_session_tools-2.7.0 → cc_session_tools-2.7.2}/tests/test_context_window_warning.py +0 -0
  213. {cc_session_tools-2.7.0 → cc_session_tools-2.7.2}/tests/test_debug.py +0 -0
  214. {cc_session_tools-2.7.0 → cc_session_tools-2.7.2}/tests/test_doctor_mutes.py +0 -0
  215. {cc_session_tools-2.7.0 → cc_session_tools-2.7.2}/tests/test_empty_session.py +0 -0
  216. {cc_session_tools-2.7.0 → cc_session_tools-2.7.2}/tests/test_hook_cache.py +0 -0
  217. {cc_session_tools-2.7.0 → cc_session_tools-2.7.2}/tests/test_hooks_install.py +0 -0
  218. {cc_session_tools-2.7.0 → cc_session_tools-2.7.2}/tests/test_install_sync.py +0 -0
  219. {cc_session_tools-2.7.0 → cc_session_tools-2.7.2}/tests/test_last_screenshot.py +0 -0
  220. {cc_session_tools-2.7.0 → cc_session_tools-2.7.2}/tests/test_levenshtein.py +0 -0
  221. {cc_session_tools-2.7.0 → cc_session_tools-2.7.2}/tests/test_marker_allow.py +0 -0
  222. {cc_session_tools-2.7.0 → cc_session_tools-2.7.2}/tests/test_migrate_ccmsg.py +0 -0
  223. {cc_session_tools-2.7.0 → cc_session_tools-2.7.2}/tests/test_migrate_ccsched.py +0 -0
  224. {cc_session_tools-2.7.0 → cc_session_tools-2.7.2}/tests/test_migrate_sessions_db.py +0 -0
  225. {cc_session_tools-2.7.0 → cc_session_tools-2.7.2}/tests/test_migrate_telemetry.py +0 -0
  226. {cc_session_tools-2.7.0 → cc_session_tools-2.7.2}/tests/test_model_info.py +0 -0
  227. {cc_session_tools-2.7.0 → cc_session_tools-2.7.2}/tests/test_normalise.py +0 -0
  228. {cc_session_tools-2.7.0 → cc_session_tools-2.7.2}/tests/test_parent_inference.py +0 -0
  229. {cc_session_tools-2.7.0 → cc_session_tools-2.7.2}/tests/test_parser.py +0 -0
  230. {cc_session_tools-2.7.0 → cc_session_tools-2.7.2}/tests/test_paths.py +0 -0
  231. {cc_session_tools-2.7.0 → cc_session_tools-2.7.2}/tests/test_pending_migration_hook.py +0 -0
  232. {cc_session_tools-2.7.0 → cc_session_tools-2.7.2}/tests/test_picker.py +0 -0
  233. {cc_session_tools-2.7.0 → cc_session_tools-2.7.2}/tests/test_pricing.py +0 -0
  234. {cc_session_tools-2.7.0 → cc_session_tools-2.7.2}/tests/test_proc_lock.py +0 -0
  235. {cc_session_tools-2.7.0 → cc_session_tools-2.7.2}/tests/test_prompts.py +0 -0
  236. {cc_session_tools-2.7.0 → cc_session_tools-2.7.2}/tests/test_query.py +0 -0
  237. {cc_session_tools-2.7.0 → cc_session_tools-2.7.2}/tests/test_report.py +0 -0
  238. {cc_session_tools-2.7.0 → cc_session_tools-2.7.2}/tests/test_roots.py +0 -0
  239. {cc_session_tools-2.7.0 → cc_session_tools-2.7.2}/tests/test_rules.py +0 -0
  240. {cc_session_tools-2.7.0 → cc_session_tools-2.7.2}/tests/test_scheduler_bundled_jobs.py +0 -0
  241. {cc_session_tools-2.7.0 → cc_session_tools-2.7.2}/tests/test_schema.py +0 -0
  242. {cc_session_tools-2.7.0 → cc_session_tools-2.7.2}/tests/test_session_names.py +0 -0
  243. {cc_session_tools-2.7.0 → cc_session_tools-2.7.2}/tests/test_session_tag.py +0 -0
  244. {cc_session_tools-2.7.0 → cc_session_tools-2.7.2}/tests/test_sessions.py +0 -0
  245. {cc_session_tools-2.7.0 → cc_session_tools-2.7.2}/tests/test_sessions_db.py +0 -0
  246. {cc_session_tools-2.7.0 → cc_session_tools-2.7.2}/tests/test_sessions_repair.py +0 -0
  247. {cc_session_tools-2.7.0 → cc_session_tools-2.7.2}/tests/test_tasklist.py +0 -0
  248. {cc_session_tools-2.7.0 → cc_session_tools-2.7.2}/tests/test_telemetry.py +0 -0
  249. {cc_session_tools-2.7.0 → cc_session_tools-2.7.2}/tests/test_telemetry_store.py +0 -0
  250. {cc_session_tools-2.7.0 → cc_session_tools-2.7.2}/tests/test_transcript.py +0 -0
  251. {cc_session_tools-2.7.0 → cc_session_tools-2.7.2}/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.7.0
3
+ Version: 2.7.2
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
@@ -687,6 +687,8 @@ Where `<name>` is one of:
687
687
  | `messaging-deliver` | `cccs_hooks.messaging_deliver` |
688
688
  | `catchup` | `cccs_hooks.catchup` |
689
689
  | `context-window-warning` | `cccs_hooks.context_window_warning` |
690
+ | `pending-migration` | `cccs_hooks.pending_migration` |
691
+ | `pending-rename` | `cccs_hooks.pending_rename` |
690
692
 
691
693
  The dispatcher reads the event payload from stdin, calls the matching module's
692
694
  `main()`, and propagates its exit code.
@@ -795,7 +797,7 @@ The `ccst` umbrella CLI provides hook and skill management, shell helper install
795
797
  Brings `~/.claude/settings.json` into line with a source `settings.json`: adds
796
798
  the hooks it is missing, and removes any entry naming a hook this build of CCST
797
799
  cannot run. With no `--source`, auto-discovers the bundled
798
- `config/hooks-bundle.json` and installs all eleven default hooks.
800
+ `config/hooks-bundle.json` and installs all thirteen default hooks.
799
801
 
800
802
  ```sh
801
803
  # Dry run (default) - shows what would be added
@@ -630,6 +630,8 @@ Where `<name>` is one of:
630
630
  | `messaging-deliver` | `cccs_hooks.messaging_deliver` |
631
631
  | `catchup` | `cccs_hooks.catchup` |
632
632
  | `context-window-warning` | `cccs_hooks.context_window_warning` |
633
+ | `pending-migration` | `cccs_hooks.pending_migration` |
634
+ | `pending-rename` | `cccs_hooks.pending_rename` |
633
635
 
634
636
  The dispatcher reads the event payload from stdin, calls the matching module's
635
637
  `main()`, and propagates its exit code.
@@ -738,7 +740,7 @@ The `ccst` umbrella CLI provides hook and skill management, shell helper install
738
740
  Brings `~/.claude/settings.json` into line with a source `settings.json`: adds
739
741
  the hooks it is missing, and removes any entry naming a hook this build of CCST
740
742
  cannot run. With no `--source`, auto-discovers the bundled
741
- `config/hooks-bundle.json` and installs all eleven default hooks.
743
+ `config/hooks-bundle.json` and installs all thirteen default hooks.
742
744
 
743
745
  ```sh
744
746
  # Dry run (default) - shows what would be added
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "cc-session-tools"
7
- version = "2.7.0"
7
+ version = "2.7.2"
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" }
@@ -1071,12 +1071,14 @@ def _cmd_pdata_init(args: argparse.Namespace) -> int:
1071
1071
  )
1072
1072
  except (FileNotFoundError, ValueError) as exc:
1073
1073
  print(f"ccst pdata: {exc}", file=sys.stderr)
1074
+ print(f"ERROR: {exc}")
1074
1075
  return 2
1075
1076
 
1076
1077
  if write_result.failure is not None:
1077
1078
  print("ccst pdata init: verification failed, nothing was cut over:", file=sys.stderr)
1078
1079
  for reason in write_result.failure.reasons:
1079
1080
  print(f" - {reason}", file=sys.stderr)
1081
+ print(f"ERROR: verification failed ({len(write_result.failure.reasons)} reason(s))")
1080
1082
  return 1
1081
1083
 
1082
1084
  print(
@@ -1088,6 +1090,8 @@ def _cmd_pdata_init(args: argparse.Namespace) -> int:
1088
1090
  print(f" cut over: {path}")
1089
1091
  print()
1090
1092
  print(write_result.report) # spec §7.1 step 4's diff report, for review
1093
+ print(f"\nVerify: ccst pdata verify --project {args.project} --full")
1094
+ print("SUCCESS")
1091
1095
  return 0
1092
1096
 
1093
1097
 
@@ -2094,7 +2098,11 @@ def _build_parser() -> argparse.ArgumentParser:
2094
2098
  )
2095
2099
  pdata_init_parser.add_argument(
2096
2100
  "--write", action="store_true",
2097
- help="Perform the write/verify/backup/cutover phase (default: dry-run only)",
2101
+ help=(
2102
+ "Perform the write/verify/backup/cutover phase (default: dry-run only). "
2103
+ "Streams progress to stdout and also writes it to "
2104
+ "<project>/ccst-pdata-init-write.log"
2105
+ ),
2098
2106
  )
2099
2107
 
2100
2108
  pdata_reconcile_parser = pdata_sub.add_parser(
@@ -26,6 +26,12 @@
26
26
  "command": "ccst hooks run pending-migration",
27
27
  "timeout": 5,
28
28
  "statusMessage": "Checking for unmigrated legacy data stores..."
29
+ },
30
+ {
31
+ "type": "command",
32
+ "command": "ccst hooks run pending-rename",
33
+ "timeout": 10,
34
+ "statusMessage": "Checking for pending session-renames..."
29
35
  }
30
36
  ]
31
37
  }
@@ -128,9 +128,19 @@ def backup_to(source_path: Path, dest_path: Path) -> None:
128
128
  cp needed (sqlite3.Connection.backup() handles this internally). Used by
129
129
  `ccst backup run` (Phase 7) and by migration scripts' pre-cutover safety
130
130
  copies.
131
+
132
+ The source opens through this module's own connect(readonly=True) — the same
133
+ WAL/busy-timeout-aware helper every other store connection in the codebase uses — so a
134
+ backup never takes any lock on the live source file (a plain sqlite3.connect() call would
135
+ still be a real, if short-lived, connection to a file this function's whole job is to
136
+ leave completely undisturbed). The destination is a throwaway file this function creates
137
+ and populates in one call, never reopened afterward, so it connects directly via plain
138
+ sqlite3.connect() rather than through connect() — routing it through the WAL-enabling
139
+ helper would just leave -wal/-shm sidecars on a file with no second reader to benefit from
140
+ WAL mode.
131
141
  """
132
142
  dest_path.parent.mkdir(parents=True, exist_ok=True)
133
- src = sqlite3.connect(str(source_path))
143
+ src = connect(source_path, readonly=True)
134
144
  dst = sqlite3.connect(str(dest_path))
135
145
  try:
136
146
  src.backup(dst)
@@ -32,6 +32,7 @@ HOOK_VERBS: dict[str, str] = {
32
32
  "catchup": "cccs_hooks.catchup",
33
33
  "pending-migration": "cccs_hooks.pending_migration",
34
34
  "context-window-warning": "cccs_hooks.context_window_warning",
35
+ "pending-rename": "cccs_hooks.pending_rename",
35
36
  }
36
37
 
37
38
 
@@ -48,6 +49,7 @@ HOOK_DESCRIPTIONS: dict[str, str] = {
48
49
  "catchup": "Reconciles+launches missed scheduled jobs (ccsched) detached and surfaces a catch-up digest (SessionStart + UserPromptSubmit)",
49
50
  "pending-migration": "Detects legacy pre-1.0.0 flat-file data left unmigrated (ccmsg/ccsched/sessions/telemetry) and surfaces a FAIL digest pointing at `ccst migrate all`; honours `ccst doctor --mute` (SessionStart)",
50
51
  "context-window-warning": "Nudges toward /compact when the context window passes 150k/200k tokens; silenced for the session by /context-override (Stop)",
52
+ "pending-rename": "Surfaces `.pending-rename` markers left by the move-session skill, skipping those whose /rename already landed and collapsing the list above 3; self-times-out at 5s with a visible notice (SessionStart)",
51
53
  }
52
54
 
53
55
  # Every settings.json hook entry CCST owns is spelled `ccst hooks run <name>`.
@@ -5,6 +5,7 @@ import os
5
5
  import sqlite3
6
6
  import tarfile
7
7
  import time
8
+ from collections.abc import Callable
8
9
  from pathlib import Path
9
10
 
10
11
  from cc_session_tools.lib import db, paths
@@ -16,7 +17,14 @@ BACKUP_DIR_ENV = "CCST_PDATA_BACKUP_DIR"
16
17
  # mounted into WSL2) are often momentary — retry a few times before giving up rather than
17
18
  # crashing the whole --write run on the first blip.
18
19
  _MAX_ATTEMPTS = 3
19
- _RETRY_BACKOFF_SECONDS = (1, 2)
20
+ _RETRY_BACKOFF_BASE_SECONDS = 1
21
+
22
+
23
+ def _backoff_seconds(attempt: int) -> float:
24
+ """Exponential backoff (1s, 2s, 4s, ...) keyed only off the attempt number — unlike a
25
+ fixed-length lookup tuple, this can't drift out of sync with _MAX_ATTEMPTS if that
26
+ constant is ever changed without a matching edit elsewhere."""
27
+ return _RETRY_BACKOFF_BASE_SECONDS * (2 ** (attempt - 1))
20
28
 
21
29
 
22
30
  class BackupError(Exception):
@@ -29,10 +37,11 @@ def backup_dir() -> Path:
29
37
 
30
38
 
31
39
  def _snapshot_db_into_tar(tar: tarfile.TarFile, *, project: str, tmp_dir: Path) -> None:
32
- """Add a point-in-time consistent copy of the project's .db to the archive, via the
33
- sqlite3 backup API rather than a raw file copy — a raw copy of a WAL-mode database can
34
- miss rows still sitting in the -wal file that haven't been checkpointed into the main
35
- file yet. No-op if the project has no .db yet (a brand-new project's first --write).
40
+ """Add a point-in-time consistent copy of the project's .db to the archive, via
41
+ db.backup_to() (the sqlite3 backup API, WAL-safe — a raw file copy of a WAL-mode database
42
+ can miss rows still sitting in the -wal file that haven't been checkpointed into the main
43
+ file yet) rather than a raw copy. No-op if the project has no .db yet (a brand-new
44
+ project's first --write).
36
45
 
37
46
  Added under a `_pdata-db/` prefix distinct from the `<project>/` prefix that
38
47
  `create_backup()` uses for project_root's own contents — this snapshot is CCST's own
@@ -40,32 +49,25 @@ def _snapshot_db_into_tar(tar: tarfile.TarFile, *, project: str, tmp_dir: Path)
40
49
  not project content, so a human restoring `<project>/...` from this archive into
41
50
  project_root must never end up with a stray `.db` file mixed into their project files.
42
51
 
43
- The source connection goes through the shared lib/db.py helper, readonly, matching this
44
- repo's data-store convention (WAL + busy-timeout applied consistently). The destination
45
- is a throwaway temp file immediately tar'd and deleted, not a persistent store, so it
46
- connects directly via sqlite3.connect() — routing it through the WAL-enabling helper
47
- would just leave -wal/-shm sidecars to clean up around a file that exists for a few
48
- lines and is never reopened."""
52
+ The destination is a throwaway temp file immediately tar'd and deleted, not a persistent
53
+ store — db.backup_to() opens it via a plain (non-WAL) connection for exactly that reason,
54
+ since routing a file that exists for a few lines and is never reopened through the
55
+ WAL-enabling helper would just leave -wal/-shm sidecars to clean up."""
49
56
  db_source = store.db_path(project)
50
57
  if not db_source.exists():
51
58
  return
52
59
  snapshot_path = tmp_dir / f"{project}.db"
53
60
  try:
54
- src_conn = db.connect(db_source, readonly=True)
55
- try:
56
- dst_conn = sqlite3.connect(snapshot_path)
57
- try:
58
- src_conn.backup(dst_conn)
59
- finally:
60
- dst_conn.close()
61
- finally:
62
- src_conn.close()
61
+ db.backup_to(db_source, snapshot_path)
63
62
  tar.add(snapshot_path, arcname=f"_pdata-db/{project}.db")
64
63
  finally:
65
64
  snapshot_path.unlink(missing_ok=True)
66
65
 
67
66
 
68
- def create_backup(*, project: str, project_root: Path) -> Path:
67
+ def create_backup(
68
+ *, project: str, project_root: Path,
69
+ on_progress: Callable[[str], None] | None = None,
70
+ ) -> Path:
69
71
  """tar.gz snapshot of project_root as it stands right now, written outside
70
72
  project_root (spec §7.1 step 5 — "stored outside the project folder, before
71
73
  touching any original file"). One backup per --write invocation; never
@@ -73,10 +75,17 @@ def create_backup(*, project: str, project_root: Path) -> Path:
73
75
  philosophy (spec goals).
74
76
 
75
77
  Written to a temp path first and atomically renamed to the final
76
- `<project>-<epoch>.tar.gz` name only once the archive is fully and successfully written —
77
- a failed or interrupted run never leaves a corrupt file at the name a valid backup would
78
- use. Transient OSErrors (e.g. a flaky network/DrvFS-backed project_root) are retried a
79
- bounded number of times before raising BackupError.
78
+ `<project>-<YYYYMMDD-HHMMSS>.tar.gz` name only once the archive is fully and successfully
79
+ written — a failed or interrupted run never leaves a corrupt file at the name a valid
80
+ backup would use. The timestamp is second-granularity specifically so it keeps the same
81
+ collision-resistance across reruns within the same minute that the epoch it replaced had
82
+ — a coarser (e.g. minute-only) format would need a separate numeric tie-breaker to match.
83
+ Transient OSErrors (e.g. a flaky network/DrvFS-backed project_root) are retried a
84
+ bounded number of times before raising BackupError. Each failed attempt is reported
85
+ through on_progress (if given) before the retry sleep — otherwise a multi-attempt backup
86
+ that eventually succeeds leaves no trace in --write's progress log of the retries it took,
87
+ and a backup that eventually fails only ever shows the final BackupError, not what each
88
+ individual attempt actually hit.
80
89
 
81
90
  sqlite3.Error is caught alongside OSError because it is NOT an OSError subclass, yet
82
91
  _snapshot_db_into_tar's sqlite3 backup-API call can raise one (e.g. sqlite3.OperationalError
@@ -86,7 +95,7 @@ def create_backup(*, project: str, project_root: Path) -> Path:
86
95
  """
87
96
  target_dir = backup_dir()
88
97
  target_dir.mkdir(parents=True, exist_ok=True)
89
- final_path = target_dir / f"{project}-{int(time.time())}.tar.gz"
98
+ final_path = target_dir / f"{project}-{time.strftime('%Y%m%d-%H%M%S')}.tar.gz"
90
99
  tmp_path = final_path.parent / (final_path.name + ".tmp")
91
100
 
92
101
  last_exc: OSError | sqlite3.Error | None = None
@@ -100,8 +109,10 @@ def create_backup(*, project: str, project_root: Path) -> Path:
100
109
  except (OSError, sqlite3.Error) as exc:
101
110
  last_exc = exc
102
111
  tmp_path.unlink(missing_ok=True)
112
+ if on_progress is not None:
113
+ on_progress(f" backup attempt {attempt}/{_MAX_ATTEMPTS} failed: {exc}")
103
114
  if attempt < _MAX_ATTEMPTS:
104
- time.sleep(_RETRY_BACKOFF_SECONDS[attempt - 1])
115
+ time.sleep(_backoff_seconds(attempt))
105
116
  raise BackupError(
106
117
  f"backup of {project!r} failed after {_MAX_ATTEMPTS} attempts: {last_exc}"
107
118
  ) from last_exc
@@ -218,7 +218,9 @@ def write(
218
218
  # rehearsal sandbox (backup_dir_override), never in the real backup dir.
219
219
  _emit(on_progress, "Backing up project and database before cutover...")
220
220
  try:
221
- backup_path = backup.create_backup(project=project, project_root=project_root)
221
+ backup_path = backup.create_backup(
222
+ project=project, project_root=project_root, on_progress=on_progress,
223
+ )
222
224
  _emit(on_progress, f"Backup written: {backup_path}")
223
225
  except backup.BackupError as exc:
224
226
  _emit(on_progress, "Backup failed — rolling back inserted rows...")
@@ -90,8 +90,19 @@ class WriteLog:
90
90
  assert self._real_stderr is not None, "WriteLog.__exit__ called without a completed __enter__"
91
91
 
92
92
  if exc is not None:
93
+ # The exc_type/exc pairing is a stdlib __exit__ contract guarantee (exc_type is
94
+ # only ever None when exc is too), not something this method re-derives — same
95
+ # rationale as the asserts above.
96
+ assert exc_type is not None, "WriteLog.__exit__ called with exc but no exc_type"
93
97
  try:
94
98
  traceback.print_exception(exc_type, exc, tb, file=self._file)
99
+ # An explicit sentinel line after the traceback, matching the ERROR/SUCCESS
100
+ # contract the CLI's own known exit paths write via print() — this is the one
101
+ # path that never reaches back to those print() calls, since the exception
102
+ # escapes the `with` block entirely. Without it, an absent sentinel would mean
103
+ # two different things (an escaped exception vs. the process being killed
104
+ # before __exit__ ran) instead of just the latter.
105
+ self._file.write(f"ERROR: {exc_type.__name__}: {exc}\n")
95
106
  self._file.flush()
96
107
  except OSError:
97
108
  pass # same best-effort contract as _Tee — never let the log itself crash
@@ -0,0 +1,206 @@
1
+ #!/bin/bash
2
+ # SessionStart hook for the move-session skill.
3
+ #
4
+ # Scans the resumed session's project cwd for `cc-sessions/*/.pending-rename`
5
+ # markers (written by move_session.py on a tag-changing operation). When found,
6
+ # emits a system reminder telling the model to run `/rename <tag>` to fix the
7
+ # picker label and then delete the marker.
8
+ #
9
+ # CC supplies the project cwd in either CLAUDE_PROJECT_DIR (preferred) or via
10
+ # the json payload on stdin (cwd field). We try both.
11
+ #
12
+ # Three behaviours keep the scan cheap, bounded, and never silent:
13
+ #
14
+ # - The scan root is canonicalised once (`cd -P`), so a project reached
15
+ # through a symlink (~/cc/<project> -> a 9p-mounted Windows drive, say) is
16
+ # walked by its real path instead of resolving the symlink hop for every
17
+ # entry `find` touches. No-op on the common case of a real directory.
18
+ # - The scan runs in the background against a soft deadline
19
+ # (CCST_PENDING_RENAME_SOFT_TIMEOUT, whole seconds, default 5).
20
+ # Overrunning it prints a short "could not complete" notice with the
21
+ # manual command and exits 0. Without it, the harness kills the hook at
22
+ # its own timeout and the user sees nothing at all - not even the
23
+ # remediation block a found marker would have printed.
24
+ # - Markers whose rename has already been applied (the session transcript
25
+ # carries a `custom-title` equal to the marker's tag) are dropped from the
26
+ # report: they are noise, not a to-do. The marker file itself is left
27
+ # alone - deleting files stays a user action, run outside CC.
28
+ #
29
+ # Bash 3.2 compatible (macOS still ships 3.2): no mapfile, no `wait -n`, no
30
+ # associative arrays, no dependency on GNU `timeout`.
31
+
32
+ set -euo pipefail
33
+
34
+ # Wall-clock budget starts here, not at the wait: an external command that
35
+ # stalls during setup then eats its own delay instead of adding to the total.
36
+ SECONDS=0
37
+
38
+ # Whole seconds, and the registered hook timeout (10s in hooks-bundle.json)
39
+ # needs real margin above it, not the 2s a stopwatch would suggest: a bare
40
+ # `sleep 5` on the WSL2 box this was measured on returns after 7.94s in roughly
41
+ # one run in five - the VM parks an idle vCPU and every timer, builtin or not,
42
+ # inherits the slack. The margin has to cover that or the harness kills the
43
+ # hook before the notice prints, which is the whole bug being fixed.
44
+ soft_timeout="${CCST_PENDING_RENAME_SOFT_TIMEOUT:-5}"
45
+ # Above this many still-pending markers the per-marker block is replaced by a
46
+ # count plus the two bulk-clear commands; a long dump nobody reads is the
47
+ # reason these reminders get ignored in the first place.
48
+ detail_max=3
49
+
50
+ # Best-effort cwd resolution. CLAUDE_PROJECT_DIR is set by CC for SessionStart
51
+ # hooks; fall back to PWD if absent.
52
+ project_cwd="${CLAUDE_PROJECT_DIR:-${PWD:-}}"
53
+ if [[ -z "$project_cwd" || ! -d "$project_cwd" ]]; then
54
+ exit 0
55
+ fi
56
+
57
+ # Canonicalise the one directory we are about to walk. `cd -P` + `pwd` is the
58
+ # portable idiom: `realpath` and `readlink -f` are absent or differently
59
+ # spelled on macOS.
60
+ if ! cc_sessions_dir="$(cd -P "$project_cwd/cc-sessions" 2>/dev/null && pwd)"; then
61
+ exit 0
62
+ fi
63
+
64
+ # Does this marker's rename already show up in the session's transcript? If so
65
+ # the marker is stale and there is nothing for the user to do.
66
+ marker_fulfilled() {
67
+ local marker="$1"
68
+ local uuid tag transcript
69
+
70
+ uuid="$(awk '/^uuid:/ {print $2; exit}' "$marker" 2>/dev/null || true)"
71
+ tag="$(awk '/^tag:/ {print $2; exit}' "$marker" 2>/dev/null || true)"
72
+ if [[ -z "$uuid" || -z "$tag" ]]; then
73
+ return 1
74
+ fi
75
+
76
+ # The marker records the UUID but not which project directory holds the
77
+ # transcript, so glob across all of them.
78
+ for transcript in "$HOME"/.claude/projects/*/"$uuid".jsonl; do
79
+ if [[ ! -f "$transcript" ]]; then
80
+ continue
81
+ fi
82
+ if grep -qE "\"type\": ?\"custom-title\".*\"customTitle\": ?\"$tag\"|\"customTitle\": ?\"$tag\".*\"type\": ?\"custom-title\"" "$transcript" 2>/dev/null; then
83
+ return 0
84
+ fi
85
+ done
86
+ return 1
87
+ }
88
+
89
+ # Everything that touches the filesystem lives here, so the soft deadline
90
+ # covers the transcript reads as well as the `find` walk.
91
+ scan() {
92
+ local marker
93
+ find "$cc_sessions_dir" -maxdepth 2 -name ".pending-rename" -type f 2>/dev/null |
94
+ while IFS= read -r marker; do
95
+ if marker_fulfilled "$marker"; then
96
+ continue
97
+ fi
98
+ printf '%s\n' "$marker"
99
+ done
100
+ }
101
+
102
+ scan_out="$(mktemp "${TMPDIR:-/tmp}/pending-rename.XXXXXX")"
103
+ scan_fifo="$scan_out.fifo"
104
+ mkfifo "$scan_fifo"
105
+ trap 'rm -f "$scan_out" "$scan_fifo"' EXIT
106
+ # Opened read-write so the open does not block on a writer and the read below
107
+ # never sees EOF while the scan is still running.
108
+ exec 3<> "$scan_fifo"
109
+
110
+ # The scan announces completion down the fifo. The background group takes
111
+ # stdin/stdout/stderr away from this script's own descriptors, via `exec` so
112
+ # bash does not stash the originals on a spare fd to restore later: killing the
113
+ # group is not enough on its own, because bash defers a SIGTERM until the
114
+ # command it is waiting on returns, and anything still holding the hook's
115
+ # stdout keeps a caller that reads to EOF waiting for a hook that already
116
+ # exited - exactly the stall the soft deadline exists to prevent.
117
+ ( exec > /dev/null 2>&1 < /dev/null; scan > "$scan_out" || true; printf 'done\n' >&3 ) &
118
+ scan_pid=$!
119
+
120
+ # The deadline is a loop of one-second `read -t` waits against an absolute
121
+ # deadline, not one long wait for the whole budget. Two separate effects force
122
+ # this shape:
123
+ #
124
+ # - `read -t` is a builtin, so no /bin/sleep spawn can stall it. A poll loop
125
+ # spawning sleep per tick, and a sleep-based watchdog child, were both
126
+ # measured drifting 2-3s past budget on a loaded box.
127
+ # - A *single* long wait is not enough either. Any wait can come back ~2.9s
128
+ # late on a hypervisor that parks an idle vCPU - `sleep`, `read -t`, and
129
+ # even a pure CPU busy-loop all show it, at 3/20 runs for both `sleep 5`
130
+ # and `read -t 5`. Re-arming 1s waits cut that to 1/46 runs here, so it
131
+ # reduces the exposure rather than removing it: a 1s wait can still slip.
132
+ # The registered timeout (hooks-bundle.json) carries the residue - it is
133
+ # sized for this deadline plus a whole slack event plus emit time, not for
134
+ # the deadline plus a token couple of seconds.
135
+ #
136
+ # macOS ships no GNU `timeout`, and `wait` has no timeout of its own. Whole
137
+ # seconds only - bash 3.2's `read -t` rejects fractions.
138
+ scan_completed=0
139
+ while [[ $SECONDS -lt $soft_timeout ]]; do
140
+ if read -t 1 -r _ <&3; then
141
+ scan_completed=1
142
+ break
143
+ fi
144
+ done
145
+
146
+ if [[ $scan_completed -eq 0 ]]; then
147
+ # Only the immediate child is signalled; a `find` it spawned may outlive it
148
+ # briefly, writing into a file we are about to delete. Harmless, and cheaper
149
+ # than turning on job control just to signal a process group.
150
+ kill "$scan_pid" 2>/dev/null || true
151
+ echo "Pending session-rename check gave up after ${soft_timeout}s (slow filesystem?) - markers, if any, were not read."
152
+ echo ""
153
+ echo "To run the check by hand outside CC:"
154
+ echo " find \"$cc_sessions_dir\" -name .pending-rename # this project"
155
+ echo " find -L ~/cc -name .pending-rename # all projects"
156
+ exit 0
157
+ fi
158
+
159
+ # Portable read loop rather than `mapfile` (bash 4.0+ only) - this script's
160
+ # shebang binds to /bin/bash, which is bash 3.2 on macOS.
161
+ markers=()
162
+ while IFS= read -r marker; do
163
+ markers+=("$marker")
164
+ done < "$scan_out"
165
+ if [[ ${#markers[@]} -eq 0 ]]; then
166
+ exit 0
167
+ fi
168
+
169
+ if [[ ${#markers[@]} -gt $detail_max ]]; then
170
+ echo "${#markers[@]} pending session-rename markers in this project (per-marker detail suppressed above ${detail_max}). Clear this project, or every project:"
171
+ echo " find \"$cc_sessions_dir\" -name .pending-rename -delete"
172
+ echo " find -L ~/cc -name .pending-rename -delete"
173
+ exit 0
174
+ fi
175
+
176
+ # Build a single reminder block. The model reads this and acts on it.
177
+ echo "${#markers[@]} pending session-rename marker(s) in this project (left by the move-session skill)."
178
+ echo ""
179
+ echo "TO SILENCE ALL REMINDERS IN THIS PROJECT AT ONCE (quick option):"
180
+ echo " Run this in a normal shell outside CC — deletes markers without updating picker labels:"
181
+ echo " find \"$cc_sessions_dir\" -name .pending-rename -delete"
182
+ echo ""
183
+ echo "TO SILENCE ALL REMINDERS ACROSS ALL PROJECTS AT ONCE:"
184
+ echo " find -L ~/cc -name .pending-rename -delete"
185
+ echo ""
186
+ echo "TO FIX AN INDIVIDUAL SESSION (updates picker label AND silences reminder):"
187
+ echo " 1. Resume the session: cd <project-dir> && claude --resume <uuid>"
188
+ echo " 2. Run inside CC: /rename <tag> (shown per marker below)"
189
+ echo " 3. Run outside CC: rm <marker-path> (shown per marker below)"
190
+ echo ""
191
+ echo "--- Markers in this project ---"
192
+ echo ""
193
+ i=0
194
+ for marker in "${markers[@]}"; do
195
+ i=$((i + 1))
196
+ session_dir="$(dirname "$marker")"
197
+ tag_from_dir="$(basename "$session_dir")"
198
+ marker_uuid="$(awk '/^uuid:/ {print $2; exit}' "$marker" 2>/dev/null || true)"
199
+ marker_tag="$(awk '/^tag:/ {print $2; exit}' "$marker" 2>/dev/null || true)"
200
+ effective_tag="${marker_tag:-$tag_from_dir}"
201
+ echo " [$i] UUID: ${marker_uuid:-unknown}"
202
+ echo " Dir: $session_dir"
203
+ echo " Inside CC: /rename $effective_tag"
204
+ echo " Outside CC: rm \"$marker\""
205
+ echo ""
206
+ done
@@ -103,6 +103,12 @@ explicit approval. **Never invoke `--write` without it.**
103
103
  ccst pdata init --project <project> [--rehearse <path>] --write
104
104
  ```
105
105
 
106
+ `--write` streams progress to stdout as it runs and also writes everything printed - plus the
107
+ traceback of anything that escapes unhandled - to `<project-root>/ccst-pdata-init-write.log`,
108
+ flushed after every line. If a run hangs, crashes, or you're not sure what happened, read this
109
+ log rather than re-running blind. It is excluded from classification (like the proposal file
110
+ itself), so it never shows up as a manifest entry on a later dry-run.
111
+
106
112
  - **Exit 0:** everything imported, verified, backed up, and cut over. Report the backup tar path
107
113
  and the list of cut-over files to Chris.
108
114
  - **Exit 1:** verification failed. Nothing was cut over and no live rows were left behind (they
@@ -128,7 +134,12 @@ instruction.
128
134
  There is no `--rollback` flag. Recover by hand:
129
135
 
130
136
  1. Restore the backup tar (printed on a successful `--write`) over the project folder:
131
- `tar -xzf <backup>.tar.gz -C <parent-of-project-root>`.
137
+ `tar -xzf <backup>.tar.gz -C <parent-of-project-root>`. This also drops a
138
+ `_pdata-db/<project>.db` sibling directory alongside `<project-root>` - a snapshot of the
139
+ project's SQLite store at backup time, CCST's own tool state, not project content. Never
140
+ move or copy it into `<project-root>` itself; if you need to restore the `.db`, copy it to
141
+ the real store path (`ccst pdata schema list --project <name>` will error with the current
142
+ path if you're unsure) and leave the extracted project folder alone.
132
143
  2. Undo the DB side with `ccst pdata delete --project <name> --id <id> --version <n>` for the
133
144
  specific rows that shouldn't have landed, or - for a wholesale bad run - delete the project's
134
145
  `.db` file directly and re-run `ccst pdata init` from scratch.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: cc-session-tools
3
- Version: 2.7.0
3
+ Version: 2.7.2
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
@@ -687,6 +687,8 @@ Where `<name>` is one of:
687
687
  | `messaging-deliver` | `cccs_hooks.messaging_deliver` |
688
688
  | `catchup` | `cccs_hooks.catchup` |
689
689
  | `context-window-warning` | `cccs_hooks.context_window_warning` |
690
+ | `pending-migration` | `cccs_hooks.pending_migration` |
691
+ | `pending-rename` | `cccs_hooks.pending_rename` |
690
692
 
691
693
  The dispatcher reads the event payload from stdin, calls the matching module's
692
694
  `main()`, and propagates its exit code.
@@ -795,7 +797,7 @@ The `ccst` umbrella CLI provides hook and skill management, shell helper install
795
797
  Brings `~/.claude/settings.json` into line with a source `settings.json`: adds
796
798
  the hooks it is missing, and removes any entry naming a hook this build of CCST
797
799
  cannot run. With no `--source`, auto-discovers the bundled
798
- `config/hooks-bundle.json` and installs all eleven default hooks.
800
+ `config/hooks-bundle.json` and installs all thirteen default hooks.
799
801
 
800
802
  ```sh
801
803
  # Dry run (default) - shows what would be added
@@ -137,6 +137,7 @@ src/cccs_hooks/messaging_deliver.py
137
137
  src/cccs_hooks/model_info.py
138
138
  src/cccs_hooks/normalise.py
139
139
  src/cccs_hooks/pending_migration.py
140
+ src/cccs_hooks/pending_rename.py
140
141
  src/cccs_hooks/session_tag.py
141
142
  src/cccs_hooks/stats.py
142
143
  src/cccs_hooks/telemetry.py
@@ -0,0 +1,37 @@
1
+ """SessionStart hook: surface `.pending-rename` markers left by move-session.
2
+
3
+ The implementation is the bash script bundled with the move-session skill —
4
+ it is the thing the skill's bats suite grades, and it stays in bash so the
5
+ skill remains self-contained for anyone reading or running it on its own.
6
+ This module exists so the hook registers like every other CCST hook
7
+ (`ccst hooks run pending-rename` in the bundle, one name in HOOK_VERBS,
8
+ covered by the prune and doctor checks) instead of as a hand-written raw-path
9
+ entry in someone's settings.json.
10
+
11
+ `execv` rather than `subprocess`: the script owns stdin, stdout and the exit
12
+ code, and replacing this process hands all three over with nothing left to
13
+ forward.
14
+ """
15
+ from __future__ import annotations
16
+
17
+ import os
18
+ from pathlib import Path
19
+
20
+ import cc_session_tools
21
+
22
+ HOOK_SCRIPT = (
23
+ Path(cc_session_tools.__file__).parent
24
+ / "skills"
25
+ / "move-session"
26
+ / "hooks"
27
+ / "sessionstart-pending-rename.sh"
28
+ )
29
+
30
+
31
+ def main() -> int:
32
+ """Hand the process over to the bundled hook script. Does not return."""
33
+ os.execvp("bash", ["bash", str(HOOK_SCRIPT)])
34
+
35
+
36
+ if __name__ == "__main__":
37
+ raise SystemExit(main())
@@ -545,7 +545,7 @@ def test_run_all_checks_includes_hook_checks(tmp_path: Path) -> None:
545
545
  skip_pypi=True,
546
546
  )
547
547
  hook_checks = [r for r in results if r.name.startswith("hook:")]
548
- assert len(hook_checks) == 12 # all bundled hooks
548
+ assert len(hook_checks) == 13 # all bundled hooks
549
549
 
550
550
 
551
551
  def test_run_all_checks_warns_for_missing_hooks(tmp_path: Path) -> None:
@@ -69,5 +69,6 @@ def test_hook_verbs_covers_every_shim_module() -> None:
69
69
  "catchup",
70
70
  "pending-migration",
71
71
  "context-window-warning",
72
+ "pending-rename",
72
73
  }
73
74
  assert set(HOOK_VERBS) == expected
@@ -87,6 +87,7 @@ def test_hooks_install_bundle_all_hooks(tmp_path: Path) -> None:
87
87
  "ccst hooks run catchup",
88
88
  "ccst hooks run pending-migration",
89
89
  "ccst hooks run context-window-warning",
90
+ "ccst hooks run pending-rename",
90
91
  }
91
92
  assert expected == all_cmds, f"Missing: {expected - all_cmds}; extra: {all_cmds - expected}"
92
93