cc-session-tools 3.9.0__tar.gz → 3.9.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 (295) hide show
  1. {cc_session_tools-3.9.0/src/cc_session_tools.egg-info → cc_session_tools-3.9.2}/PKG-INFO +3 -3
  2. {cc_session_tools-3.9.0 → cc_session_tools-3.9.2}/README.md +2 -2
  3. {cc_session_tools-3.9.0 → cc_session_tools-3.9.2}/pyproject.toml +1 -1
  4. {cc_session_tools-3.9.0 → cc_session_tools-3.9.2}/src/cc_session_tools/cli/ccst.py +40 -3
  5. {cc_session_tools-3.9.0 → cc_session_tools-3.9.2}/src/cc_session_tools/cli/migrate_sessions_db.py +81 -8
  6. {cc_session_tools-3.9.0 → cc_session_tools-3.9.2}/src/cc_session_tools/lib/doctor.py +56 -7
  7. {cc_session_tools-3.9.0 → cc_session_tools-3.9.2}/src/cc_session_tools/lib/pdata/init_paths.py +32 -4
  8. {cc_session_tools-3.9.0 → cc_session_tools-3.9.2}/src/cc_session_tools/lib/pdata/init_service.py +5 -1
  9. {cc_session_tools-3.9.0 → cc_session_tools-3.9.2}/src/cc_session_tools/lib/sessions_db.py +47 -0
  10. {cc_session_tools-3.9.0 → cc_session_tools-3.9.2/src/cc_session_tools.egg-info}/PKG-INFO +3 -3
  11. {cc_session_tools-3.9.0 → cc_session_tools-3.9.2}/src/cc_session_tools.egg-info/SOURCES.txt +1 -0
  12. {cc_session_tools-3.9.0 → cc_session_tools-3.9.2}/tests/test_ccst_pdata_init_cli.py +55 -0
  13. {cc_session_tools-3.9.0 → cc_session_tools-3.9.2}/tests/test_migrate_sessions_db.py +3 -3
  14. cc_session_tools-3.9.2/tests/test_sessions_db_migration_chain.py +464 -0
  15. {cc_session_tools-3.9.0 → cc_session_tools-3.9.2}/LICENSE +0 -0
  16. {cc_session_tools-3.9.0 → cc_session_tools-3.9.2}/setup.cfg +0 -0
  17. {cc_session_tools-3.9.0 → cc_session_tools-3.9.2}/src/cc_session_tools/__init__.py +0 -0
  18. {cc_session_tools-3.9.0 → cc_session_tools-3.9.2}/src/cc_session_tools/cli/__init__.py +0 -0
  19. {cc_session_tools-3.9.0 → cc_session_tools-3.9.2}/src/cc_session_tools/cli/ccd.py +0 -0
  20. {cc_session_tools-3.9.0 → cc_session_tools-3.9.2}/src/cc_session_tools/cli/ccmsg.py +0 -0
  21. {cc_session_tools-3.9.0 → cc_session_tools-3.9.2}/src/cc_session_tools/cli/ccr.py +0 -0
  22. {cc_session_tools-3.9.0 → cc_session_tools-3.9.2}/src/cc_session_tools/cli/ccs.py +0 -0
  23. {cc_session_tools-3.9.0 → cc_session_tools-3.9.2}/src/cc_session_tools/cli/ccsched.py +0 -0
  24. {cc_session_tools-3.9.0 → cc_session_tools-3.9.2}/src/cc_session_tools/cli/migrate_ccmsg.py +0 -0
  25. {cc_session_tools-3.9.0 → cc_session_tools-3.9.2}/src/cc_session_tools/cli/migrate_ccsched.py +0 -0
  26. {cc_session_tools-3.9.0 → cc_session_tools-3.9.2}/src/cc_session_tools/cli/migrate_telemetry.py +0 -0
  27. {cc_session_tools-3.9.0 → cc_session_tools-3.9.2}/src/cc_session_tools/config/hooks-bundle.json +0 -0
  28. {cc_session_tools-3.9.0 → cc_session_tools-3.9.2}/src/cc_session_tools/config/session-tag-hook.json +0 -0
  29. {cc_session_tools-3.9.0 → cc_session_tools-3.9.2}/src/cc_session_tools/hooks_install.py +0 -0
  30. {cc_session_tools-3.9.0 → cc_session_tools-3.9.2}/src/cc_session_tools/lib/__init__.py +0 -0
  31. {cc_session_tools-3.9.0 → cc_session_tools-3.9.2}/src/cc_session_tools/lib/bun_tools.py +0 -0
  32. {cc_session_tools-3.9.0 → cc_session_tools-3.9.2}/src/cc_session_tools/lib/claude_flags.py +0 -0
  33. {cc_session_tools-3.9.0 → cc_session_tools-3.9.2}/src/cc_session_tools/lib/claude_md_install.py +0 -0
  34. {cc_session_tools-3.9.0 → cc_session_tools-3.9.2}/src/cc_session_tools/lib/context_overrides.py +0 -0
  35. {cc_session_tools-3.9.0 → cc_session_tools-3.9.2}/src/cc_session_tools/lib/db.py +0 -0
  36. {cc_session_tools-3.9.0 → cc_session_tools-3.9.2}/src/cc_session_tools/lib/debug.py +0 -0
  37. {cc_session_tools-3.9.0 → cc_session_tools-3.9.2}/src/cc_session_tools/lib/doctor_mutes.py +0 -0
  38. {cc_session_tools-3.9.0 → cc_session_tools-3.9.2}/src/cc_session_tools/lib/hook_registry.py +0 -0
  39. {cc_session_tools-3.9.0 → cc_session_tools-3.9.2}/src/cc_session_tools/lib/install_sync.py +0 -0
  40. {cc_session_tools-3.9.0 → cc_session_tools-3.9.2}/src/cc_session_tools/lib/levenshtein.py +0 -0
  41. {cc_session_tools-3.9.0 → cc_session_tools-3.9.2}/src/cc_session_tools/lib/machine_identity.py +0 -0
  42. {cc_session_tools-3.9.0 → cc_session_tools-3.9.2}/src/cc_session_tools/lib/messaging/__init__.py +0 -0
  43. {cc_session_tools-3.9.0 → cc_session_tools-3.9.2}/src/cc_session_tools/lib/messaging/addressing.py +0 -0
  44. {cc_session_tools-3.9.0 → cc_session_tools-3.9.2}/src/cc_session_tools/lib/messaging/cursor.py +0 -0
  45. {cc_session_tools-3.9.0 → cc_session_tools-3.9.2}/src/cc_session_tools/lib/messaging/lock.py +0 -0
  46. {cc_session_tools-3.9.0 → cc_session_tools-3.9.2}/src/cc_session_tools/lib/messaging/message.py +0 -0
  47. {cc_session_tools-3.9.0 → cc_session_tools-3.9.2}/src/cc_session_tools/lib/messaging/move_safety.py +0 -0
  48. {cc_session_tools-3.9.0 → cc_session_tools-3.9.2}/src/cc_session_tools/lib/messaging/repository.py +0 -0
  49. {cc_session_tools-3.9.0 → cc_session_tools-3.9.2}/src/cc_session_tools/lib/messaging/retention.py +0 -0
  50. {cc_session_tools-3.9.0 → cc_session_tools-3.9.2}/src/cc_session_tools/lib/messaging/service.py +0 -0
  51. {cc_session_tools-3.9.0 → cc_session_tools-3.9.2}/src/cc_session_tools/lib/messaging/store.py +0 -0
  52. {cc_session_tools-3.9.0 → cc_session_tools-3.9.2}/src/cc_session_tools/lib/occupancy.py +0 -0
  53. {cc_session_tools-3.9.0 → cc_session_tools-3.9.2}/src/cc_session_tools/lib/paths.py +0 -0
  54. {cc_session_tools-3.9.0 → cc_session_tools-3.9.2}/src/cc_session_tools/lib/pdata/__init__.py +0 -0
  55. {cc_session_tools-3.9.0 → cc_session_tools-3.9.2}/src/cc_session_tools/lib/pdata/backup.py +0 -0
  56. {cc_session_tools-3.9.0 → cc_session_tools-3.9.2}/src/cc_session_tools/lib/pdata/classify.py +0 -0
  57. {cc_session_tools-3.9.0 → cc_session_tools-3.9.2}/src/cc_session_tools/lib/pdata/cutover.py +0 -0
  58. {cc_session_tools-3.9.0 → cc_session_tools-3.9.2}/src/cc_session_tools/lib/pdata/dump.py +0 -0
  59. {cc_session_tools-3.9.0 → cc_session_tools-3.9.2}/src/cc_session_tools/lib/pdata/formatting.py +0 -0
  60. {cc_session_tools-3.9.0 → cc_session_tools-3.9.2}/src/cc_session_tools/lib/pdata/importers.py +0 -0
  61. {cc_session_tools-3.9.0 → cc_session_tools-3.9.2}/src/cc_session_tools/lib/pdata/manifest.py +0 -0
  62. {cc_session_tools-3.9.0 → cc_session_tools-3.9.2}/src/cc_session_tools/lib/pdata/naming.py +0 -0
  63. {cc_session_tools-3.9.0 → cc_session_tools-3.9.2}/src/cc_session_tools/lib/pdata/readiness.py +0 -0
  64. {cc_session_tools-3.9.0 → cc_session_tools-3.9.2}/src/cc_session_tools/lib/pdata/rehydrate.py +0 -0
  65. {cc_session_tools-3.9.0 → cc_session_tools-3.9.2}/src/cc_session_tools/lib/pdata/rename_group.py +0 -0
  66. {cc_session_tools-3.9.0 → cc_session_tools-3.9.2}/src/cc_session_tools/lib/pdata/reorganize.py +0 -0
  67. {cc_session_tools-3.9.0 → cc_session_tools-3.9.2}/src/cc_session_tools/lib/pdata/repository.py +0 -0
  68. {cc_session_tools-3.9.0 → cc_session_tools-3.9.2}/src/cc_session_tools/lib/pdata/resolve.py +0 -0
  69. {cc_session_tools-3.9.0 → cc_session_tools-3.9.2}/src/cc_session_tools/lib/pdata/service.py +0 -0
  70. {cc_session_tools-3.9.0 → cc_session_tools-3.9.2}/src/cc_session_tools/lib/pdata/session_output.py +0 -0
  71. {cc_session_tools-3.9.0 → cc_session_tools-3.9.2}/src/cc_session_tools/lib/pdata/store.py +0 -0
  72. {cc_session_tools-3.9.0 → cc_session_tools-3.9.2}/src/cc_session_tools/lib/pdata/sync_check.py +0 -0
  73. {cc_session_tools-3.9.0 → cc_session_tools-3.9.2}/src/cc_session_tools/lib/pdata/sync_lock.py +0 -0
  74. {cc_session_tools-3.9.0 → cc_session_tools-3.9.2}/src/cc_session_tools/lib/pdata/sync_notify.py +0 -0
  75. {cc_session_tools-3.9.0 → cc_session_tools-3.9.2}/src/cc_session_tools/lib/pdata/vector_clock.py +0 -0
  76. {cc_session_tools-3.9.0 → cc_session_tools-3.9.2}/src/cc_session_tools/lib/pdata/vector_clock_store.py +0 -0
  77. {cc_session_tools-3.9.0 → cc_session_tools-3.9.2}/src/cc_session_tools/lib/pdata/verify.py +0 -0
  78. {cc_session_tools-3.9.0 → cc_session_tools-3.9.2}/src/cc_session_tools/lib/pdata/write_log.py +0 -0
  79. {cc_session_tools-3.9.0 → cc_session_tools-3.9.2}/src/cc_session_tools/lib/picker.py +0 -0
  80. {cc_session_tools-3.9.0 → cc_session_tools-3.9.2}/src/cc_session_tools/lib/proc_lock.py +0 -0
  81. {cc_session_tools-3.9.0 → cc_session_tools-3.9.2}/src/cc_session_tools/lib/prompts.py +0 -0
  82. {cc_session_tools-3.9.0 → cc_session_tools-3.9.2}/src/cc_session_tools/lib/roots.py +0 -0
  83. {cc_session_tools-3.9.0 → cc_session_tools-3.9.2}/src/cc_session_tools/lib/rules.py +0 -0
  84. {cc_session_tools-3.9.0 → cc_session_tools-3.9.2}/src/cc_session_tools/lib/scheduler/__init__.py +0 -0
  85. {cc_session_tools-3.9.0 → cc_session_tools-3.9.2}/src/cc_session_tools/lib/scheduler/bundled_jobs.py +0 -0
  86. {cc_session_tools-3.9.0 → cc_session_tools-3.9.2}/src/cc_session_tools/lib/scheduler/cadence.py +0 -0
  87. {cc_session_tools-3.9.0 → cc_session_tools-3.9.2}/src/cc_session_tools/lib/scheduler/cursor.py +0 -0
  88. {cc_session_tools-3.9.0 → cc_session_tools-3.9.2}/src/cc_session_tools/lib/scheduler/digest.py +0 -0
  89. {cc_session_tools-3.9.0 → cc_session_tools-3.9.2}/src/cc_session_tools/lib/scheduler/due.py +0 -0
  90. {cc_session_tools-3.9.0 → cc_session_tools-3.9.2}/src/cc_session_tools/lib/scheduler/duration.py +0 -0
  91. {cc_session_tools-3.9.0 → cc_session_tools-3.9.2}/src/cc_session_tools/lib/scheduler/jobspec.py +0 -0
  92. {cc_session_tools-3.9.0 → cc_session_tools-3.9.2}/src/cc_session_tools/lib/scheduler/ledger.py +0 -0
  93. {cc_session_tools-3.9.0 → cc_session_tools-3.9.2}/src/cc_session_tools/lib/scheduler/lock.py +0 -0
  94. {cc_session_tools-3.9.0 → cc_session_tools-3.9.2}/src/cc_session_tools/lib/scheduler/notify.py +0 -0
  95. {cc_session_tools-3.9.0 → cc_session_tools-3.9.2}/src/cc_session_tools/lib/scheduler/reconcile.py +0 -0
  96. {cc_session_tools-3.9.0 → cc_session_tools-3.9.2}/src/cc_session_tools/lib/scheduler/registry.py +0 -0
  97. {cc_session_tools-3.9.0 → cc_session_tools-3.9.2}/src/cc_session_tools/lib/scheduler/runner.py +0 -0
  98. {cc_session_tools-3.9.0 → cc_session_tools-3.9.2}/src/cc_session_tools/lib/scheduler/state.py +0 -0
  99. {cc_session_tools-3.9.0 → cc_session_tools-3.9.2}/src/cc_session_tools/lib/scheduler/store.py +0 -0
  100. {cc_session_tools-3.9.0 → cc_session_tools-3.9.2}/src/cc_session_tools/lib/scheduler/surface.py +0 -0
  101. {cc_session_tools-3.9.0 → cc_session_tools-3.9.2}/src/cc_session_tools/lib/scheduler/throttle.py +0 -0
  102. {cc_session_tools-3.9.0 → cc_session_tools-3.9.2}/src/cc_session_tools/lib/scheduler/worker.py +0 -0
  103. {cc_session_tools-3.9.0 → cc_session_tools-3.9.2}/src/cc_session_tools/lib/session_gc.py +0 -0
  104. {cc_session_tools-3.9.0 → cc_session_tools-3.9.2}/src/cc_session_tools/lib/sessions.py +0 -0
  105. {cc_session_tools-3.9.0 → cc_session_tools-3.9.2}/src/cc_session_tools/lib/sessions_repair.py +0 -0
  106. {cc_session_tools-3.9.0 → cc_session_tools-3.9.2}/src/cc_session_tools/lib/shell_install.py +0 -0
  107. {cc_session_tools-3.9.0 → cc_session_tools-3.9.2}/src/cc_session_tools/lib/tasklist.py +0 -0
  108. {cc_session_tools-3.9.0 → cc_session_tools-3.9.2}/src/cc_session_tools/lib/telemetry_store.py +0 -0
  109. {cc_session_tools-3.9.0 → cc_session_tools-3.9.2}/src/cc_session_tools/lib/text_wrap.py +0 -0
  110. {cc_session_tools-3.9.0 → cc_session_tools-3.9.2}/src/cc_session_tools/prompts/pdata-migration-claude-md-update.md +0 -0
  111. {cc_session_tools-3.9.0 → cc_session_tools-3.9.2}/src/cc_session_tools/prompts/pdata-migration-skills-update.md +0 -0
  112. {cc_session_tools-3.9.0 → cc_session_tools-3.9.2}/src/cc_session_tools/skills/analyse-cc-usage/SKILL.md +0 -0
  113. {cc_session_tools-3.9.0 → cc_session_tools-3.9.2}/src/cc_session_tools/skills/clean-hook-sessions/SKILL.md +0 -0
  114. {cc_session_tools-3.9.0 → cc_session_tools-3.9.2}/src/cc_session_tools/skills/clean-hook-sessions/scripts/clean-hook-sessions.py +0 -0
  115. {cc_session_tools-3.9.0 → cc_session_tools-3.9.2}/src/cc_session_tools/skills/compact-smart/SKILL.md +0 -0
  116. {cc_session_tools-3.9.0 → cc_session_tools-3.9.2}/src/cc_session_tools/skills/context-override/SKILL.md +0 -0
  117. {cc_session_tools-3.9.0 → cc_session_tools-3.9.2}/src/cc_session_tools/skills/delete-sessions/SKILL.md +0 -0
  118. {cc_session_tools-3.9.0 → cc_session_tools-3.9.2}/src/cc_session_tools/skills/delete-sessions/scripts/delete_sessions.py +0 -0
  119. {cc_session_tools-3.9.0 → cc_session_tools-3.9.2}/src/cc_session_tools/skills/do-executor-critic-assessor-loop/SKILL.md +0 -0
  120. {cc_session_tools-3.9.0 → cc_session_tools-3.9.2}/src/cc_session_tools/skills/extract-pptx/SKILL.md +0 -0
  121. {cc_session_tools-3.9.0 → cc_session_tools-3.9.2}/src/cc_session_tools/skills/extract-pptx/scripts/extract_pptx.py +0 -0
  122. {cc_session_tools-3.9.0 → cc_session_tools-3.9.2}/src/cc_session_tools/skills/extract-to-text/SKILL.md +0 -0
  123. {cc_session_tools-3.9.0 → cc_session_tools-3.9.2}/src/cc_session_tools/skills/extract-to-text/scripts/extract_to_text.py +0 -0
  124. {cc_session_tools-3.9.0 → cc_session_tools-3.9.2}/src/cc_session_tools/skills/find-claude-code-session/SKILL.md +0 -0
  125. {cc_session_tools-3.9.0 → cc_session_tools-3.9.2}/src/cc_session_tools/skills/generate-8digit-code/SKILL.md +0 -0
  126. {cc_session_tools-3.9.0 → cc_session_tools-3.9.2}/src/cc_session_tools/skills/generate-8digit-code/scripts/generate_8digit_code.py +0 -0
  127. {cc_session_tools-3.9.0 → cc_session_tools-3.9.2}/src/cc_session_tools/skills/list-empty-sessions/SKILL.md +0 -0
  128. {cc_session_tools-3.9.0 → cc_session_tools-3.9.2}/src/cc_session_tools/skills/list-empty-sessions/scripts/list_empty_sessions.py +0 -0
  129. {cc_session_tools-3.9.0 → cc_session_tools-3.9.2}/src/cc_session_tools/skills/manage-recurring-cc-jobs-using-ccsched/SKILL.md +0 -0
  130. {cc_session_tools-3.9.0 → cc_session_tools-3.9.2}/src/cc_session_tools/skills/move-session/SKILL.md +0 -0
  131. {cc_session_tools-3.9.0 → cc_session_tools-3.9.2}/src/cc_session_tools/skills/move-session/hooks/sessionstart-pending-rename.sh +0 -0
  132. {cc_session_tools-3.9.0 → cc_session_tools-3.9.2}/src/cc_session_tools/skills/move-session/scripts/cc_session_rules.py +0 -0
  133. {cc_session_tools-3.9.0 → cc_session_tools-3.9.2}/src/cc_session_tools/skills/move-session/scripts/move_session.py +0 -0
  134. {cc_session_tools-3.9.0 → cc_session_tools-3.9.2}/src/cc_session_tools/skills/pm-pdata-design-schema/SKILL.md +0 -0
  135. {cc_session_tools-3.9.0 → cc_session_tools-3.9.2}/src/cc_session_tools/skills/pm-pdata-do-audit-and-prepare-to-migrate/SKILL.md +0 -0
  136. {cc_session_tools-3.9.0 → cc_session_tools-3.9.2}/src/cc_session_tools/skills/pm-pdata-do-init/SKILL.md +0 -0
  137. {cc_session_tools-3.9.0 → cc_session_tools-3.9.2}/src/cc_session_tools/skills/pm-pdata-do-manual-migration/SKILL.md +0 -0
  138. {cc_session_tools-3.9.0 → cc_session_tools-3.9.2}/src/cc_session_tools/skills/pm-pdata-resolve-conflicts/SKILL.md +0 -0
  139. {cc_session_tools-3.9.0 → cc_session_tools-3.9.2}/src/cc_session_tools/skills/pm-project-layout-reference/SKILL.md +0 -0
  140. {cc_session_tools-3.9.0 → cc_session_tools-3.9.2}/src/cc_session_tools/skills/pm-update-central-files/SKILL.md +0 -0
  141. {cc_session_tools-3.9.0 → cc_session_tools-3.9.2}/src/cc_session_tools/skills/reduce-persistent-context/CALIBRATION.md +0 -0
  142. {cc_session_tools-3.9.0 → cc_session_tools-3.9.2}/src/cc_session_tools/skills/reduce-persistent-context/SKILL.md +0 -0
  143. {cc_session_tools-3.9.0 → cc_session_tools-3.9.2}/src/cc_session_tools/skills/reduce-persistent-context/scripts/analyze_context.py +0 -0
  144. {cc_session_tools-3.9.0 → cc_session_tools-3.9.2}/src/cc_session_tools/skills/reduce-persistent-context/scripts/duplicates.py +0 -0
  145. {cc_session_tools-3.9.0 → cc_session_tools-3.9.2}/src/cc_session_tools/skills/reduce-persistent-context/scripts/measure.py +0 -0
  146. {cc_session_tools-3.9.0 → cc_session_tools-3.9.2}/src/cc_session_tools/skills/reduce-persistent-context/scripts/report.py +0 -0
  147. {cc_session_tools-3.9.0 → cc_session_tools-3.9.2}/src/cc_session_tools/skills/reduce-persistent-context/scripts/tokens.py +0 -0
  148. {cc_session_tools-3.9.0 → cc_session_tools-3.9.2}/src/cc_session_tools/skills/reduce-persistent-context/scripts/usage.py +0 -0
  149. {cc_session_tools-3.9.0 → cc_session_tools-3.9.2}/src/cc_session_tools/skills/select-agent-model/SKILL.md +0 -0
  150. {cc_session_tools-3.9.0 → cc_session_tools-3.9.2}/src/cc_session_tools/skills/send-session-message/SKILL.md +0 -0
  151. {cc_session_tools-3.9.0 → cc_session_tools-3.9.2}/src/cc_session_tools/skills/update-command-cache/SKILL.md +0 -0
  152. {cc_session_tools-3.9.0 → cc_session_tools-3.9.2}/src/cc_session_tools/skills/update-command-cache/scripts/update_command_cache.py +0 -0
  153. {cc_session_tools-3.9.0 → cc_session_tools-3.9.2}/src/cc_session_tools.egg-info/dependency_links.txt +0 -0
  154. {cc_session_tools-3.9.0 → cc_session_tools-3.9.2}/src/cc_session_tools.egg-info/entry_points.txt +0 -0
  155. {cc_session_tools-3.9.0 → cc_session_tools-3.9.2}/src/cc_session_tools.egg-info/requires.txt +0 -0
  156. {cc_session_tools-3.9.0 → cc_session_tools-3.9.2}/src/cc_session_tools.egg-info/top_level.txt +0 -0
  157. {cc_session_tools-3.9.0 → cc_session_tools-3.9.2}/src/claude_code_usage/__init__.py +0 -0
  158. {cc_session_tools-3.9.0 → cc_session_tools-3.9.2}/src/claude_code_usage/attribution.py +0 -0
  159. {cc_session_tools-3.9.0 → cc_session_tools-3.9.2}/src/claude_code_usage/cache.py +0 -0
  160. {cc_session_tools-3.9.0 → cc_session_tools-3.9.2}/src/claude_code_usage/ccusage_wrapper.py +0 -0
  161. {cc_session_tools-3.9.0 → cc_session_tools-3.9.2}/src/claude_code_usage/cli.py +0 -0
  162. {cc_session_tools-3.9.0 → cc_session_tools-3.9.2}/src/claude_code_usage/data/pricing.json +0 -0
  163. {cc_session_tools-3.9.0 → cc_session_tools-3.9.2}/src/claude_code_usage/parent_inference.py +0 -0
  164. {cc_session_tools-3.9.0 → cc_session_tools-3.9.2}/src/claude_code_usage/parser.py +0 -0
  165. {cc_session_tools-3.9.0 → cc_session_tools-3.9.2}/src/claude_code_usage/pricing.py +0 -0
  166. {cc_session_tools-3.9.0 → cc_session_tools-3.9.2}/src/claude_code_usage/query.py +0 -0
  167. {cc_session_tools-3.9.0 → cc_session_tools-3.9.2}/src/claude_code_usage/report.py +0 -0
  168. {cc_session_tools-3.9.0 → cc_session_tools-3.9.2}/src/claude_code_usage/schema.py +0 -0
  169. {cc_session_tools-3.9.0 → cc_session_tools-3.9.2}/src/claude_code_usage/session_names.py +0 -0
  170. {cc_session_tools-3.9.0 → cc_session_tools-3.9.2}/src/hooks/__init__.py +0 -0
  171. {cc_session_tools-3.9.0 → cc_session_tools-3.9.2}/src/hooks/after_response.py +0 -0
  172. {cc_session_tools-3.9.0 → cc_session_tools-3.9.2}/src/hooks/bash_hard_deny.py +0 -0
  173. {cc_session_tools-3.9.0 → cc_session_tools-3.9.2}/src/hooks/bash_security_review.py +0 -0
  174. {cc_session_tools-3.9.0 → cc_session_tools-3.9.2}/src/hooks/cache.py +0 -0
  175. {cc_session_tools-3.9.0 → cc_session_tools-3.9.2}/src/hooks/catchup.py +0 -0
  176. {cc_session_tools-3.9.0 → cc_session_tools-3.9.2}/src/hooks/confirm_8digit.py +0 -0
  177. {cc_session_tools-3.9.0 → cc_session_tools-3.9.2}/src/hooks/context_window_warning.py +0 -0
  178. {cc_session_tools-3.9.0 → cc_session_tools-3.9.2}/src/hooks/last_screenshot.py +0 -0
  179. {cc_session_tools-3.9.0 → cc_session_tools-3.9.2}/src/hooks/marker_allow.py +0 -0
  180. {cc_session_tools-3.9.0 → cc_session_tools-3.9.2}/src/hooks/markers.py +0 -0
  181. {cc_session_tools-3.9.0 → cc_session_tools-3.9.2}/src/hooks/messaging_deliver.py +0 -0
  182. {cc_session_tools-3.9.0 → cc_session_tools-3.9.2}/src/hooks/model_info.py +0 -0
  183. {cc_session_tools-3.9.0 → cc_session_tools-3.9.2}/src/hooks/normalise.py +0 -0
  184. {cc_session_tools-3.9.0 → cc_session_tools-3.9.2}/src/hooks/pdata_sync.py +0 -0
  185. {cc_session_tools-3.9.0 → cc_session_tools-3.9.2}/src/hooks/pending_migration.py +0 -0
  186. {cc_session_tools-3.9.0 → cc_session_tools-3.9.2}/src/hooks/pending_rename.py +0 -0
  187. {cc_session_tools-3.9.0 → cc_session_tools-3.9.2}/src/hooks/script_allowlist.py +0 -0
  188. {cc_session_tools-3.9.0 → cc_session_tools-3.9.2}/src/hooks/session_tag.py +0 -0
  189. {cc_session_tools-3.9.0 → cc_session_tools-3.9.2}/src/hooks/stats.py +0 -0
  190. {cc_session_tools-3.9.0 → cc_session_tools-3.9.2}/src/hooks/telemetry.py +0 -0
  191. {cc_session_tools-3.9.0 → cc_session_tools-3.9.2}/src/hooks/telemetry_query.py +0 -0
  192. {cc_session_tools-3.9.0 → cc_session_tools-3.9.2}/src/hooks/telemetry_trim.py +0 -0
  193. {cc_session_tools-3.9.0 → cc_session_tools-3.9.2}/src/hooks/transcript.py +0 -0
  194. {cc_session_tools-3.9.0 → cc_session_tools-3.9.2}/src/hooks/worklog_guard.py +0 -0
  195. {cc_session_tools-3.9.0 → cc_session_tools-3.9.2}/tests/test_after_response.py +0 -0
  196. {cc_session_tools-3.9.0 → cc_session_tools-3.9.2}/tests/test_attribution.py +0 -0
  197. {cc_session_tools-3.9.0 → cc_session_tools-3.9.2}/tests/test_bash_hard_deny.py +0 -0
  198. {cc_session_tools-3.9.0 → cc_session_tools-3.9.2}/tests/test_bash_security_review.py +0 -0
  199. {cc_session_tools-3.9.0 → cc_session_tools-3.9.2}/tests/test_bash_security_review_allowlist.py +0 -0
  200. {cc_session_tools-3.9.0 → cc_session_tools-3.9.2}/tests/test_bun_tools.py +0 -0
  201. {cc_session_tools-3.9.0 → cc_session_tools-3.9.2}/tests/test_cache.py +0 -0
  202. {cc_session_tools-3.9.0 → cc_session_tools-3.9.2}/tests/test_cache_sqlite.py +0 -0
  203. {cc_session_tools-3.9.0 → cc_session_tools-3.9.2}/tests/test_ccr_orphans.py +0 -0
  204. {cc_session_tools-3.9.0 → cc_session_tools-3.9.2}/tests/test_ccs_emptiness.py +0 -0
  205. {cc_session_tools-3.9.0 → cc_session_tools-3.9.2}/tests/test_ccs_eta.py +0 -0
  206. {cc_session_tools-3.9.0 → cc_session_tools-3.9.2}/tests/test_ccs_list_mode.py +0 -0
  207. {cc_session_tools-3.9.0 → cc_session_tools-3.9.2}/tests/test_ccs_scope_flags.py +0 -0
  208. {cc_session_tools-3.9.0 → cc_session_tools-3.9.2}/tests/test_ccs_sentinel_sort.py +0 -0
  209. {cc_session_tools-3.9.0 → cc_session_tools-3.9.2}/tests/test_ccs_session_counts.py +0 -0
  210. {cc_session_tools-3.9.0 → cc_session_tools-3.9.2}/tests/test_ccst_bundle_discovery.py +0 -0
  211. {cc_session_tools-3.9.0 → cc_session_tools-3.9.2}/tests/test_ccst_ccsched_jobs_cli.py +0 -0
  212. {cc_session_tools-3.9.0 → cc_session_tools-3.9.2}/tests/test_ccst_cli.py +0 -0
  213. {cc_session_tools-3.9.0 → cc_session_tools-3.9.2}/tests/test_ccst_context_override_cli.py +0 -0
  214. {cc_session_tools-3.9.0 → cc_session_tools-3.9.2}/tests/test_ccst_doctor.py +0 -0
  215. {cc_session_tools-3.9.0 → cc_session_tools-3.9.2}/tests/test_ccst_gc_prune.py +0 -0
  216. {cc_session_tools-3.9.0 → cc_session_tools-3.9.2}/tests/test_ccst_gc_report.py +0 -0
  217. {cc_session_tools-3.9.0 → cc_session_tools-3.9.2}/tests/test_ccst_hook_dispatcher.py +0 -0
  218. {cc_session_tools-3.9.0 → cc_session_tools-3.9.2}/tests/test_ccst_hooks_allowlist.py +0 -0
  219. {cc_session_tools-3.9.0 → cc_session_tools-3.9.2}/tests/test_ccst_hooks_install_prune.py +0 -0
  220. {cc_session_tools-3.9.0 → cc_session_tools-3.9.2}/tests/test_ccst_hooks_install_zero_arg.py +0 -0
  221. {cc_session_tools-3.9.0 → cc_session_tools-3.9.2}/tests/test_ccst_hooks_uninstall.py +0 -0
  222. {cc_session_tools-3.9.0 → cc_session_tools-3.9.2}/tests/test_ccst_install_everything.py +0 -0
  223. {cc_session_tools-3.9.0 → cc_session_tools-3.9.2}/tests/test_ccst_machine_identity_cli.py +0 -0
  224. {cc_session_tools-3.9.0 → cc_session_tools-3.9.2}/tests/test_ccst_main_gate.py +0 -0
  225. {cc_session_tools-3.9.0 → cc_session_tools-3.9.2}/tests/test_ccst_pdata_cli.py +0 -0
  226. {cc_session_tools-3.9.0 → cc_session_tools-3.9.2}/tests/test_ccst_pdata_readiness_cli.py +0 -0
  227. {cc_session_tools-3.9.0 → cc_session_tools-3.9.2}/tests/test_ccst_pdata_reconcile_cli.py +0 -0
  228. {cc_session_tools-3.9.0 → cc_session_tools-3.9.2}/tests/test_ccst_pdata_rename_group_cli.py +0 -0
  229. {cc_session_tools-3.9.0 → cc_session_tools-3.9.2}/tests/test_ccst_pdata_reorganize_cli.py +0 -0
  230. {cc_session_tools-3.9.0 → cc_session_tools-3.9.2}/tests/test_ccst_pdata_sync_cli.py +0 -0
  231. {cc_session_tools-3.9.0 → cc_session_tools-3.9.2}/tests/test_ccst_pdata_verify_cli.py +0 -0
  232. {cc_session_tools-3.9.0 → cc_session_tools-3.9.2}/tests/test_ccst_repair_sessions_cli.py +0 -0
  233. {cc_session_tools-3.9.0 → cc_session_tools-3.9.2}/tests/test_ccst_sessions_cli.py +0 -0
  234. {cc_session_tools-3.9.0 → cc_session_tools-3.9.2}/tests/test_ccst_shell_install.py +0 -0
  235. {cc_session_tools-3.9.0 → cc_session_tools-3.9.2}/tests/test_ccst_skills_install.py +0 -0
  236. {cc_session_tools-3.9.0 → cc_session_tools-3.9.2}/tests/test_ccst_skills_uninstall.py +0 -0
  237. {cc_session_tools-3.9.0 → cc_session_tools-3.9.2}/tests/test_ccst_telemetry_query.py +0 -0
  238. {cc_session_tools-3.9.0 → cc_session_tools-3.9.2}/tests/test_ccst_telemetry_trim.py +0 -0
  239. {cc_session_tools-3.9.0 → cc_session_tools-3.9.2}/tests/test_ccusage_wrapper.py +0 -0
  240. {cc_session_tools-3.9.0 → cc_session_tools-3.9.2}/tests/test_claude_flags.py +0 -0
  241. {cc_session_tools-3.9.0 → cc_session_tools-3.9.2}/tests/test_claude_md_install.py +0 -0
  242. {cc_session_tools-3.9.0 → cc_session_tools-3.9.2}/tests/test_cli.py +0 -0
  243. {cc_session_tools-3.9.0 → cc_session_tools-3.9.2}/tests/test_cli_ccd.py +0 -0
  244. {cc_session_tools-3.9.0 → cc_session_tools-3.9.2}/tests/test_cli_ccd_dry_run.py +0 -0
  245. {cc_session_tools-3.9.0 → cc_session_tools-3.9.2}/tests/test_cli_ccr.py +0 -0
  246. {cc_session_tools-3.9.0 → cc_session_tools-3.9.2}/tests/test_cli_ccs.py +0 -0
  247. {cc_session_tools-3.9.0 → cc_session_tools-3.9.2}/tests/test_cli_version.py +0 -0
  248. {cc_session_tools-3.9.0 → cc_session_tools-3.9.2}/tests/test_confirm_8digit.py +0 -0
  249. {cc_session_tools-3.9.0 → cc_session_tools-3.9.2}/tests/test_context_overrides.py +0 -0
  250. {cc_session_tools-3.9.0 → cc_session_tools-3.9.2}/tests/test_context_window_warning.py +0 -0
  251. {cc_session_tools-3.9.0 → cc_session_tools-3.9.2}/tests/test_db.py +0 -0
  252. {cc_session_tools-3.9.0 → cc_session_tools-3.9.2}/tests/test_debug.py +0 -0
  253. {cc_session_tools-3.9.0 → cc_session_tools-3.9.2}/tests/test_doctor_mutes.py +0 -0
  254. {cc_session_tools-3.9.0 → cc_session_tools-3.9.2}/tests/test_empty_session.py +0 -0
  255. {cc_session_tools-3.9.0 → cc_session_tools-3.9.2}/tests/test_hook_cache.py +0 -0
  256. {cc_session_tools-3.9.0 → cc_session_tools-3.9.2}/tests/test_hooks_install.py +0 -0
  257. {cc_session_tools-3.9.0 → cc_session_tools-3.9.2}/tests/test_hooks_install_records.py +0 -0
  258. {cc_session_tools-3.9.0 → cc_session_tools-3.9.2}/tests/test_install_sync.py +0 -0
  259. {cc_session_tools-3.9.0 → cc_session_tools-3.9.2}/tests/test_last_screenshot.py +0 -0
  260. {cc_session_tools-3.9.0 → cc_session_tools-3.9.2}/tests/test_levenshtein.py +0 -0
  261. {cc_session_tools-3.9.0 → cc_session_tools-3.9.2}/tests/test_machine_identity.py +0 -0
  262. {cc_session_tools-3.9.0 → cc_session_tools-3.9.2}/tests/test_marker_allow.py +0 -0
  263. {cc_session_tools-3.9.0 → cc_session_tools-3.9.2}/tests/test_migrate_ccmsg.py +0 -0
  264. {cc_session_tools-3.9.0 → cc_session_tools-3.9.2}/tests/test_migrate_ccsched.py +0 -0
  265. {cc_session_tools-3.9.0 → cc_session_tools-3.9.2}/tests/test_migrate_telemetry.py +0 -0
  266. {cc_session_tools-3.9.0 → cc_session_tools-3.9.2}/tests/test_model_info.py +0 -0
  267. {cc_session_tools-3.9.0 → cc_session_tools-3.9.2}/tests/test_normalise.py +0 -0
  268. {cc_session_tools-3.9.0 → cc_session_tools-3.9.2}/tests/test_occupancy.py +0 -0
  269. {cc_session_tools-3.9.0 → cc_session_tools-3.9.2}/tests/test_parent_inference.py +0 -0
  270. {cc_session_tools-3.9.0 → cc_session_tools-3.9.2}/tests/test_parser.py +0 -0
  271. {cc_session_tools-3.9.0 → cc_session_tools-3.9.2}/tests/test_paths.py +0 -0
  272. {cc_session_tools-3.9.0 → cc_session_tools-3.9.2}/tests/test_pdata_audit_skill.py +0 -0
  273. {cc_session_tools-3.9.0 → cc_session_tools-3.9.2}/tests/test_pending_migration_hook.py +0 -0
  274. {cc_session_tools-3.9.0 → cc_session_tools-3.9.2}/tests/test_picker.py +0 -0
  275. {cc_session_tools-3.9.0 → cc_session_tools-3.9.2}/tests/test_pricing.py +0 -0
  276. {cc_session_tools-3.9.0 → cc_session_tools-3.9.2}/tests/test_proc_lock.py +0 -0
  277. {cc_session_tools-3.9.0 → cc_session_tools-3.9.2}/tests/test_prompts.py +0 -0
  278. {cc_session_tools-3.9.0 → cc_session_tools-3.9.2}/tests/test_query.py +0 -0
  279. {cc_session_tools-3.9.0 → cc_session_tools-3.9.2}/tests/test_report.py +0 -0
  280. {cc_session_tools-3.9.0 → cc_session_tools-3.9.2}/tests/test_roots.py +0 -0
  281. {cc_session_tools-3.9.0 → cc_session_tools-3.9.2}/tests/test_rules.py +0 -0
  282. {cc_session_tools-3.9.0 → cc_session_tools-3.9.2}/tests/test_scheduler_bundled_jobs.py +0 -0
  283. {cc_session_tools-3.9.0 → cc_session_tools-3.9.2}/tests/test_schema.py +0 -0
  284. {cc_session_tools-3.9.0 → cc_session_tools-3.9.2}/tests/test_script_allowlist.py +0 -0
  285. {cc_session_tools-3.9.0 → cc_session_tools-3.9.2}/tests/test_session_names.py +0 -0
  286. {cc_session_tools-3.9.0 → cc_session_tools-3.9.2}/tests/test_session_tag.py +0 -0
  287. {cc_session_tools-3.9.0 → cc_session_tools-3.9.2}/tests/test_sessions.py +0 -0
  288. {cc_session_tools-3.9.0 → cc_session_tools-3.9.2}/tests/test_sessions_db.py +0 -0
  289. {cc_session_tools-3.9.0 → cc_session_tools-3.9.2}/tests/test_sessions_repair.py +0 -0
  290. {cc_session_tools-3.9.0 → cc_session_tools-3.9.2}/tests/test_tasklist.py +0 -0
  291. {cc_session_tools-3.9.0 → cc_session_tools-3.9.2}/tests/test_telemetry.py +0 -0
  292. {cc_session_tools-3.9.0 → cc_session_tools-3.9.2}/tests/test_telemetry_store.py +0 -0
  293. {cc_session_tools-3.9.0 → cc_session_tools-3.9.2}/tests/test_text_wrap.py +0 -0
  294. {cc_session_tools-3.9.0 → cc_session_tools-3.9.2}/tests/test_transcript.py +0 -0
  295. {cc_session_tools-3.9.0 → cc_session_tools-3.9.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: 3.9.0
3
+ Version: 3.9.2
4
4
  Summary: Claude Code session management CLI tools (ccd, ccr, ccs, claude-code-usage, ccst) and hook library (hooks package)
5
5
  Author: raffishquartan
6
6
  License: MIT License
@@ -192,8 +192,8 @@ Messages can also be addressed to a specific session (`--to-session`) or to a fr
192
192
  description for whichever session picks it up first (`--to-description`) - project-
193
193
  addressed is by far the most common pattern in practice.
194
194
 
195
- The easiest way to use sessions is just to tell your session to send a message to
196
- another session (e.g. by name tag and/or location).
195
+ The easiest way to use messages when you need them is just to tell your session to send
196
+ a message to another session (e.g. by name tag and/or location).
197
197
 
198
198
  ## Scheduled housekeeping: `ccsched`
199
199
 
@@ -135,8 +135,8 @@ Messages can also be addressed to a specific session (`--to-session`) or to a fr
135
135
  description for whichever session picks it up first (`--to-description`) - project-
136
136
  addressed is by far the most common pattern in practice.
137
137
 
138
- The easiest way to use sessions is just to tell your session to send a message to
139
- another session (e.g. by name tag and/or location).
138
+ The easiest way to use messages when you need them is just to tell your session to send
139
+ a message to another session (e.g. by name tag and/or location).
140
140
 
141
141
  ## Scheduled housekeeping: `ccsched`
142
142
 
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "cc-session-tools"
7
- version = "3.9.0"
7
+ version = "3.9.2"
8
8
  description = "Claude Code session management CLI tools (ccd, ccr, ccs, claude-code-usage, ccst) and hook library (hooks package)"
9
9
  readme = "README.md"
10
10
  license = { file = "LICENSE" }
@@ -2340,12 +2340,44 @@ _MIGRATE_ALL_BANNER = (
2340
2340
  )
2341
2341
 
2342
2342
 
2343
+ def _migrate_all_sessions_step(args: argparse.Namespace) -> int:
2344
+ """The sessions step of `migrate all`: the legacy import writes uuid-keyed rows, so a
2345
+ pre-uuid `sessions.db` is rebuilt first (`sessions migrate-uuid`, with its own backup and
2346
+ concurrent-writer guard). A failed or refused rebuild skips the import."""
2347
+ import sqlite3
2348
+
2349
+ from cc_session_tools.cli.migrate_sessions_db import migrate_uuid
2350
+ from cc_session_tools.lib import sessions_db
2351
+
2352
+ db_path = sessions_db.default_db_path()
2353
+ try:
2354
+ pre_uuid = sessions_db.pre_uuid_sessions_table(db_path)
2355
+ except sqlite3.DatabaseError as exc:
2356
+ print(f"cannot read {db_path}: {exc} - not a valid sessions.db; nothing was changed",
2357
+ file=sys.stderr)
2358
+ return 1
2359
+ if pre_uuid:
2360
+ print(f"{db_path} is on the pre-3.0.0 schema - rebuilding it first "
2361
+ "(`ccst sessions migrate-uuid`) so the legacy import can write to it.")
2362
+ try:
2363
+ rc = migrate_uuid(db_path=db_path, dry_run=args.dry_run)
2364
+ except ValueError as exc:
2365
+ print(str(exc), file=sys.stderr)
2366
+ return 1
2367
+ if rc != 0:
2368
+ return rc
2369
+ if args.dry_run:
2370
+ print("The legacy import's own dry-run is skipped until the rebuild has run.")
2371
+ return 0
2372
+ return _cmd_sessions_migrate(argparse.Namespace(
2373
+ dry_run=args.dry_run, sessions_db=None, tags_dir=None, mutes_file=None))
2374
+
2375
+
2343
2376
  def _cmd_migrate_all(args: argparse.Namespace) -> int:
2344
2377
  print(_MIGRATE_ALL_BANNER)
2345
2378
 
2346
2379
  steps: list[tuple[str, object]] = [
2347
- ("sessions", lambda: _cmd_sessions_migrate(argparse.Namespace(
2348
- dry_run=args.dry_run, sessions_db=None, tags_dir=None, mutes_file=None))),
2380
+ ("sessions", lambda: _migrate_all_sessions_step(args)),
2349
2381
  ("ccmsg", lambda: _cmd_migrate_ccmsg(argparse.Namespace(
2350
2382
  old_root=None, backup_dir=None, dry_run=args.dry_run))),
2351
2383
  ("ccsched", lambda: _cmd_migrate_ccsched(argparse.Namespace(
@@ -2355,14 +2387,19 @@ def _cmd_migrate_all(args: argparse.Namespace) -> int:
2355
2387
  ]
2356
2388
 
2357
2389
  overall_rc = 0
2390
+ failed: list[str] = []
2358
2391
  for name, step in steps:
2359
2392
  print(f"\n=== {name} ===")
2360
2393
  rc = step() # type: ignore[operator]
2361
2394
  if rc != 0:
2362
2395
  overall_rc = rc
2396
+ failed.append(name)
2363
2397
 
2364
2398
  print()
2365
- if args.dry_run:
2399
+ if failed:
2400
+ print(f"Steps that failed: {', '.join(failed)}. Review the ERROR/ABORT/refusal lines "
2401
+ "above; a failed step leaves its own old files untouched and is safe to re-run.")
2402
+ elif args.dry_run:
2366
2403
  print("Dry run complete — re-run without --dry-run once you're satisfied.")
2367
2404
  else:
2368
2405
  print("All migrations attempted. Review any ERROR/ABORT lines above; a "
@@ -114,10 +114,15 @@ def _read_legacy_sessions_rows(conn: sqlite3.Connection) -> list[sqlite3.Row]:
114
114
  """Every row from the pre-3.0.0 (project_dir, basename) `sessions` table, via raw SQL -
115
115
  not sessions_db.list_sessions()/_row_to_session, which require the `uuid` column this
116
116
  migration is the one adding. Safe to call against either schema shape: only the columns
117
- that exist pre-3.0.0 are selected."""
117
+ that exist pre-3.0.0 are selected; `updated_at` (absent from v1.0.0 through v2.12.x) is read
118
+ as NULL when the table has no such column."""
119
+ updated_at = (
120
+ "updated_at" if "updated_at" in sessions_db.sessions_column_names(conn)
121
+ else "NULL AS updated_at"
122
+ )
118
123
  return conn.execute(
119
124
  "SELECT project_dir, basename, start_date, last_opened, last_active, "
120
- "discovered_at, updated_at FROM sessions"
125
+ f"discovered_at, {updated_at} FROM sessions"
121
126
  ).fetchall()
122
127
 
123
128
 
@@ -162,6 +167,26 @@ def print_uuid_migration_dry_run(plan: UuidMigrationPlan, *, db_path: Path) -> N
162
167
  print("backing up: this rebuilds the sessions table and is a one-way schema change.")
163
168
 
164
169
 
170
+ def _record_uuid_marker_only(db_path: Path, *, dry_run: bool) -> int:
171
+ """`sessions` is already uuid-keyed, or there is no `sessions` table to rebuild: there is
172
+ nothing to rebuild, only the completion marker to record (creating the current schema first
173
+ if the table is missing)."""
174
+ if dry_run:
175
+ print(f"{db_path} needs no rebuild - only its 3.0.0 completion marker would be "
176
+ "recorded. Re-run with --write to record it.")
177
+ return 0
178
+ conn = sessions_db.connect(path=db_path)
179
+ try:
180
+ db.record_migration(
181
+ conn, sessions_db.SESSIONS_UUID_MIGRATION, applied_at=sessions_db._now_iso()
182
+ )
183
+ conn.commit()
184
+ finally:
185
+ conn.close()
186
+ print(f"{db_path} needed no rebuild - recorded its 3.0.0 completion marker.")
187
+ return 0
188
+
189
+
165
190
  def migrate_uuid(*, db_path: Path, dry_run: bool, backup_dir: Path | None = None) -> int:
166
191
  """The 3.0.0 schema migration: rebuild `sessions` from the pre-3.0.0
167
192
  (project_dir, basename) primary key to (project_dir, basename, uuid), backfilling each
@@ -185,15 +210,35 @@ def migrate_uuid(*, db_path: Path, dry_run: bool, backup_dir: Path | None = None
185
210
  # be held by a live hook write, defeating the point of checking before touching anything.
186
211
  conn = db.connect(db_path)
187
212
  try:
188
- if sessions_db.sessions_schema_is_uuid_keyed(conn) and db.migration_applied(
189
- conn, sessions_db.SESSIONS_UUID_MIGRATION
190
- ):
191
- print(f"{db_path} is already migrated to the uuid-aware schema - nothing to do.")
192
- return 0
193
- legacy_rows = _read_legacy_sessions_rows(conn)
213
+ if not sessions_db.sessions_table_exists(conn):
214
+ marker_only = True
215
+ legacy_rows: list[sqlite3.Row] = []
216
+ else:
217
+ keyed = sessions_db.sessions_schema_is_uuid_keyed(conn)
218
+ recorded = sessions_db.migration_marker_recorded(
219
+ conn, sessions_db.SESSIONS_UUID_MIGRATION
220
+ )
221
+ if keyed and recorded:
222
+ print(f"{db_path} is already migrated to the uuid-aware schema - nothing to do.")
223
+ return 0
224
+ marker_only = keyed
225
+ if not keyed:
226
+ missing = sessions_db.missing_rebuild_columns(conn)
227
+ if missing:
228
+ print(
229
+ f"cannot migrate {db_path}: its `sessions` table is missing "
230
+ f"column(s) the rebuild copies: {', '.join(missing)}. Nothing was "
231
+ "changed; restore the file from a backup or repair it by hand.",
232
+ file=sys.stderr,
233
+ )
234
+ return 1
235
+ legacy_rows = [] if keyed else _read_legacy_sessions_rows(conn)
194
236
  finally:
195
237
  conn.close()
196
238
 
239
+ if marker_only:
240
+ return _record_uuid_marker_only(db_path, dry_run=dry_run)
241
+
197
242
  plan = plan_uuid_migration(legacy_rows)
198
243
 
199
244
  if dry_run:
@@ -418,6 +463,30 @@ def _tar_backup(sources: list[Path], *, backup_dir: Path) -> Path | None:
418
463
  return dest
419
464
 
420
465
 
466
+ def refuse_if_sessions_db_pre_uuid(db_path: Path) -> int | None:
467
+ """Pre-flight for the legacy import: it writes uuid-keyed rows, which a `sessions.db` still
468
+ on the pre-3.0.0 schema cannot accept. Returns a non-zero exit code (after printing the
469
+ fix) when the import must not run, else None. Read-only; must run before anything that could
470
+ connect-with-DDL, write, or back up."""
471
+ try:
472
+ pre_uuid = sessions_db.pre_uuid_sessions_table(db_path)
473
+ except sqlite3.DatabaseError as exc:
474
+ print(f"cannot read {db_path}: {exc} - not a valid sessions.db; nothing was changed",
475
+ file=sys.stderr)
476
+ return 1
477
+ if not pre_uuid:
478
+ return None
479
+ print(
480
+ f"cannot run the legacy flat-file import: {db_path} is still on the pre-3.0.0 schema "
481
+ "(no uuid column), which the import cannot write to. Nothing was changed. First run "
482
+ "`ccst sessions migrate-uuid --write` from a plain terminal with no other `claude` "
483
+ "session running (it takes a backup first), then re-run this - or run `ccst migrate "
484
+ "all`, which does both in order.",
485
+ file=sys.stderr,
486
+ )
487
+ return 1
488
+
489
+
421
490
  def run_migration(
422
491
  *,
423
492
  dry_run: bool,
@@ -427,6 +496,10 @@ def run_migration(
427
496
  roots: list[Path],
428
497
  backup_dir: Path,
429
498
  ) -> int:
499
+ refusal = refuse_if_sessions_db_pre_uuid(db_path)
500
+ if refusal is not None:
501
+ return refusal
502
+
430
503
  print(f"Sessions DB : {db_path}")
431
504
  print(f"Tags source : {tags_dir}")
432
505
  print(f"Mutes source : {mutes_file}")
@@ -675,7 +675,26 @@ def _migration_recorded(db_path: Path, marker_name: str) -> bool:
675
675
  conn.close()
676
676
 
677
677
 
678
- def check_pending_data_store_migration(paths: LegacyMigrationPaths) -> list[CheckResult]:
678
+ def _sessions_marker() -> str:
679
+ from cc_session_tools.lib.sessions_db import LEGACY_FLAT_FILE_MIGRATION
680
+
681
+ return LEGACY_FLAT_FILE_MIGRATION
682
+
683
+
684
+ def _sessions_db_is_pre_uuid(db_path: Path) -> bool:
685
+ """`sessions_db.pre_uuid_sessions_table`, with an unreadable file counting as not pre-uuid
686
+ (other checks already report a database that fails to open)."""
687
+ from cc_session_tools.lib import sessions_db
688
+
689
+ try:
690
+ return sessions_db.pre_uuid_sessions_table(db_path)
691
+ except sqlite3.DatabaseError:
692
+ return False
693
+
694
+
695
+ def check_pending_data_store_migration(
696
+ paths: LegacyMigrationPaths, *, sessions_db_path: Path | None = None,
697
+ ) -> list[CheckResult]:
679
698
  """Detect legacy flat-file data left over from a pre-1.0.0 install.
680
699
 
681
700
  :func:`check_data_stores` can't distinguish a fresh install (nothing to
@@ -696,6 +715,10 @@ def check_pending_data_store_migration(paths: LegacyMigrationPaths) -> list[Chec
696
715
  fills from the first hook fire after install, so counting rows reported "already
697
716
  migrated" for anyone who opened a session before migrating, and the FAIL that should
698
717
  have prompted them never fired.
718
+
719
+ `sessions_db_path` is the sessions.db the `sessions` finding reads; pass the same path the
720
+ 3.0.0 uuid check uses so the two findings can never describe different files (defaults to
721
+ `paths.data_home / "sessions.db"`).
699
722
  """
700
723
  from cc_session_tools.lib.messaging.repository import (
701
724
  LEGACY_FLAT_FILE_MIGRATION as _CCMSG_MIGRATION,
@@ -724,7 +747,7 @@ def check_pending_data_store_migration(paths: LegacyMigrationPaths) -> list[Chec
724
747
  "sessions": (
725
748
  (paths.tags_dir, paths.mutes_file),
726
749
  _count_legacy_sessions(paths.tags_dir, paths.mutes_file),
727
- paths.data_home / "sessions.db",
750
+ sessions_db_path if sessions_db_path is not None else paths.data_home / "sessions.db",
728
751
  _SESSIONS_MIGRATION,
729
752
  ),
730
753
  "telemetry": (
@@ -748,6 +771,13 @@ def check_pending_data_store_migration(paths: LegacyMigrationPaths) -> list[Chec
748
771
  already_migrated = _migration_recorded(new_db_path, marker_name)
749
772
  evidence = f"the import is recorded in {new_db_path.name}"
750
773
  if not already_migrated:
774
+ rebuild_note = ""
775
+ if store_name == "sessions" and _sessions_db_is_pre_uuid(new_db_path):
776
+ rebuild_note = (
777
+ f" It will first rebuild {new_db_path.name} to the 3.0.0 uuid schema "
778
+ "(taking a backup; close any running `claude` sessions first) - the "
779
+ "import cannot write to the old schema."
780
+ )
751
781
  results.append(
752
782
  CheckResult(
753
783
  name=name,
@@ -756,7 +786,7 @@ def check_pending_data_store_migration(paths: LegacyMigrationPaths) -> list[Chec
756
786
  f"{legacy_count} unmigrated item(s) at {existing}; run "
757
787
  "`ccst migrate all` from a plain terminal (NOT inside a Claude Code "
758
788
  "session — the delete step is blocked by bash-hard-deny) to migrate "
759
- f"into {new_db_path}"
789
+ f"into {new_db_path}.{rebuild_note}"
760
790
  ),
761
791
  )
762
792
  )
@@ -942,7 +972,9 @@ def check_sessions_project_dir_absolute(sessions_db_path: Path) -> list[CheckRes
942
972
  )]
943
973
 
944
974
 
945
- def check_sessions_uuid_migration(sessions_db_path: Path) -> list[CheckResult]:
975
+ def check_sessions_uuid_migration(
976
+ sessions_db_path: Path, *, legacy_sessions_pending: bool = False,
977
+ ) -> list[CheckResult]:
946
978
  """FAIL if sessions.db still has the pre-3.0.0 (project_dir, basename) primary key -
947
979
  forked sessions (Ctrl-L) silently clobber each other's activity rows until
948
980
  `ccst sessions migrate-uuid --write` runs. Distinguishes "never migrated" from
@@ -956,7 +988,10 @@ def check_sessions_uuid_migration(sessions_db_path: Path) -> list[CheckResult]:
956
988
  A sessions.db that doesn't exist yet reads OK: connect() records the migration
957
989
  marker at creation time for any brand-new file (sessions_db.py), so a genuinely
958
990
  fresh install is never in a pending state - there is simply nothing to check here
959
- yet, and this check must not create the file itself just to inspect it."""
991
+ yet, and this check must not create the file itself just to inspect it.
992
+
993
+ `legacy_sessions_pending` says the 1.0.0 flat-file import is also still pending; the two FAIL
994
+ branches then note that `ccst migrate all` performs both, in the order that works."""
960
995
  import sqlite3
961
996
 
962
997
  from cc_session_tools.lib import db as db_lib
@@ -1000,6 +1035,11 @@ def check_sessions_uuid_migration(sessions_db_path: Path) -> list[CheckResult]:
1000
1035
  "run `ccst sessions migrate-uuid --write` from a plain terminal with no other "
1001
1036
  "`claude` session running (it takes a backup first; see --help)"
1002
1037
  )
1038
+ if legacy_sessions_pending:
1039
+ remediation += (
1040
+ "; the legacy import is also pending, and `ccst migrate all` performs both in "
1041
+ "the order that works"
1042
+ )
1003
1043
  if not pk_is_uuid_keyed and not marker_present:
1004
1044
  return [CheckResult(
1005
1045
  name=name, status=Status.FAIL,
@@ -1158,7 +1198,8 @@ def run_all_checks(
1158
1198
 
1159
1199
  # Pending legacy-data migration
1160
1200
  if legacy_migration_paths is not None:
1161
- results.extend(check_pending_data_store_migration(legacy_migration_paths))
1201
+ results.extend(check_pending_data_store_migration(
1202
+ legacy_migration_paths, sessions_db_path=sessions_db_path))
1162
1203
 
1163
1204
  # Pending ccst pdata init cutover (spec §7.1 step 7)
1164
1205
  if projects_root is not None:
@@ -1172,7 +1213,15 @@ def run_all_checks(
1172
1213
  # since project-dir-absolute's own check degrades to a WARN pointing back at it when
1173
1214
  # the uuid migration hasn't run yet (a pre-migration schema can't be queried the same way).
1174
1215
  if sessions_db_path is not None:
1175
- results.extend(check_sessions_uuid_migration(sessions_db_path))
1216
+ legacy_sessions_pending = (
1217
+ legacy_migration_paths is not None
1218
+ and _count_legacy_sessions(
1219
+ legacy_migration_paths.tags_dir, legacy_migration_paths.mutes_file
1220
+ ) > 0
1221
+ and not _migration_recorded(sessions_db_path, _sessions_marker())
1222
+ )
1223
+ results.extend(check_sessions_uuid_migration(
1224
+ sessions_db_path, legacy_sessions_pending=legacy_sessions_pending))
1176
1225
  results.extend(check_sessions_project_dir_absolute(sessions_db_path))
1177
1226
 
1178
1227
  # Optional dependencies for claude-code-usage's reconcile / ccusage blocks
@@ -112,13 +112,41 @@ def project_db_dir_override(rehearse: Path | None) -> Iterator[None]:
112
112
  override_dir = rehearse / REHEARSAL_DB_DIRNAME
113
113
  previous = os.environ.get(store.PROJECT_DB_DIR_ENV)
114
114
  os.environ[store.PROJECT_DB_DIR_ENV] = str(override_dir)
115
+ _live_db_dir_env_stack.append(previous)
115
116
  try:
116
117
  yield
117
118
  finally:
118
- if previous is None:
119
- os.environ.pop(store.PROJECT_DB_DIR_ENV, None)
120
- else:
121
- os.environ[store.PROJECT_DB_DIR_ENV] = previous
119
+ _live_db_dir_env_stack.pop()
120
+ _restore_db_dir_env(previous)
121
+
122
+
123
+ # The CCST_PROJECT_DB_DIR value in effect before each active project_db_dir_override (None =
124
+ # unset), outermost first - what live_project_db_dir() restores.
125
+ _live_db_dir_env_stack: list[str | None] = []
126
+
127
+
128
+ def _restore_db_dir_env(value: str | None) -> None:
129
+ if value is None:
130
+ os.environ.pop(store.PROJECT_DB_DIR_ENV, None)
131
+ else:
132
+ os.environ[store.PROJECT_DB_DIR_ENV] = value
133
+
134
+
135
+ @contextmanager
136
+ def live_project_db_dir() -> Iterator[None]:
137
+ """Inside a project_db_dir_override block, temporarily point store.db_path() back at the
138
+ real per-project .db dir. For questions about the machine rather than the rehearsal copy
139
+ (e.g. "is this project already migrated here?"): a `cp -r` rehearsal copy never contains the
140
+ real .db, so the sandbox always answers "no". No-op outside an override."""
141
+ if not _live_db_dir_env_stack:
142
+ yield
143
+ return
144
+ inside = os.environ.get(store.PROJECT_DB_DIR_ENV)
145
+ _restore_db_dir_env(_live_db_dir_env_stack[0])
146
+ try:
147
+ yield
148
+ finally:
149
+ _restore_db_dir_env(inside)
122
150
 
123
151
 
124
152
  @contextmanager
@@ -324,7 +324,11 @@ def _pending_adoption(*, project: str, project_root: Path) -> dump.DumpInfo | No
324
324
  "project. Publish a fresh dump from a known-good machine (ccst pdata dump --force) "
325
325
  "or reconcile manually (ccst pdata resolve) once those commands exist."
326
326
  )
327
- if _already_locally_migrated(project):
327
+ # The live .db, not a --rehearse sandbox's: a rehearsal copy never contains the real .db, so
328
+ # the sandbox would always look unmigrated and rehearsal would never reach classification.
329
+ with init_paths.live_project_db_dir():
330
+ already_migrated = _already_locally_migrated(project)
331
+ if already_migrated:
328
332
  return None
329
333
  return info
330
334
 
@@ -153,6 +153,53 @@ def sessions_schema_is_uuid_keyed(conn: sqlite3.Connection) -> bool:
153
153
  )
154
154
 
155
155
 
156
+ # Columns of the pre-3.0.0 `sessions` table that the uuid rebuild copies into the new table.
157
+ # `updated_at` is deliberately absent: databases from v1.0.0 through v2.12.x never had it.
158
+ REBUILD_REQUIRED_COLUMNS = (
159
+ "project_dir", "basename", "start_date", "last_opened", "last_active", "discovered_at",
160
+ )
161
+
162
+
163
+ def sessions_table_exists(conn: sqlite3.Connection) -> bool:
164
+ return conn.execute(
165
+ "SELECT 1 FROM sqlite_master WHERE type='table' AND name='sessions'"
166
+ ).fetchone() is not None
167
+
168
+
169
+ def sessions_column_names(conn: sqlite3.Connection) -> set[str]:
170
+ return {row["name"] for row in conn.execute("PRAGMA table_info(sessions)")}
171
+
172
+
173
+ def missing_rebuild_columns(conn: sqlite3.Connection) -> list[str]:
174
+ """Columns the 3.0.0 rebuild must copy that the existing `sessions` table lacks."""
175
+ present = sessions_column_names(conn)
176
+ return [c for c in REBUILD_REQUIRED_COLUMNS if c not in present]
177
+
178
+
179
+ def migration_marker_recorded(conn: sqlite3.Connection, name: str) -> bool:
180
+ """`db.migration_applied`, but False (not an error) on a database that predates the
181
+ `migrations` table (v1.0.0 through roughly v2.8)."""
182
+ try:
183
+ return db.migration_applied(conn, name)
184
+ except sqlite3.OperationalError:
185
+ return False
186
+
187
+
188
+ def pre_uuid_sessions_table(path: Path) -> bool:
189
+ """True iff `path` exists, has a `sessions` table, and that table is not uuid-keyed - the
190
+ one state in which the 1.0.0 legacy import cannot write. A missing file or a missing
191
+ `sessions` table is False: `connect()` creates the current schema for both. Read-only, so it
192
+ never creates or alters anything; raises sqlite3.DatabaseError for a file that is not a
193
+ valid database."""
194
+ if not path.exists():
195
+ return False
196
+ conn = db.connect(path, readonly=True)
197
+ try:
198
+ return sessions_table_exists(conn) and not sessions_schema_is_uuid_keyed(conn)
199
+ finally:
200
+ conn.close()
201
+
202
+
156
203
  def connect(*, path: Path | None = None, readonly: bool = False) -> sqlite3.Connection:
157
204
  """Open sessions.db (or an explicit override path — used by tests and by
158
205
  ccst doctor --mutes-file). readonly=True skips schema creation; callers
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: cc-session-tools
3
- Version: 3.9.0
3
+ Version: 3.9.2
4
4
  Summary: Claude Code session management CLI tools (ccd, ccr, ccs, claude-code-usage, ccst) and hook library (hooks package)
5
5
  Author: raffishquartan
6
6
  License: MIT License
@@ -192,8 +192,8 @@ Messages can also be addressed to a specific session (`--to-session`) or to a fr
192
192
  description for whichever session picks it up first (`--to-description`) - project-
193
193
  addressed is by far the most common pattern in practice.
194
194
 
195
- The easiest way to use sessions is just to tell your session to send a message to
196
- another session (e.g. by name tag and/or location).
195
+ The easiest way to use messages when you need them is just to tell your session to send
196
+ a message to another session (e.g. by name tag and/or location).
197
197
 
198
198
  ## Scheduled housekeeping: `ccsched`
199
199
 
@@ -283,6 +283,7 @@ tests/test_session_names.py
283
283
  tests/test_session_tag.py
284
284
  tests/test_sessions.py
285
285
  tests/test_sessions_db.py
286
+ tests/test_sessions_db_migration_chain.py
286
287
  tests/test_sessions_repair.py
287
288
  tests/test_tasklist.py
288
289
  tests/test_telemetry.py
@@ -368,3 +368,58 @@ def test_pdata_init_write_still_adopts_after_an_earlier_dry_run(base_env, tmp_pa
368
368
  assert "Adopting existing pdata from sync dump" in r_write.stdout
369
369
  assert "macbook" in r_write.stdout
370
370
  assert "SUCCESS" in r_write.stdout
371
+
372
+
373
+ def _publish_dump(base_env, project_dir, monkeypatch, *, with_record):
374
+ """Build a published dump for project `demo`; keep the live .db only if `with_record`."""
375
+ from cc_session_tools.lib.pdata import dump, repository, service, vector_clock_store
376
+
377
+ monkeypatch.setenv("CCST_PROJECT_DB_DIR", base_env["CCST_PROJECT_DB_DIR"])
378
+ con = repository.connect("demo")
379
+ with repository._immediate(con):
380
+ vector_clock_store.write_vector(con, {"macbook": 3}, updated_at=100)
381
+ dump.write_latest(con, project_root=project_dir, machine_id="macbook", vector={"macbook": 3})
382
+ con.close()
383
+ db_file = Path(base_env["CCST_PROJECT_DB_DIR"]) / "demo.db"
384
+ if with_record:
385
+ service.add_record(project="demo", record_group="notes", content="live content", file_path=None, fields={})
386
+ else:
387
+ db_file.unlink()
388
+ return db_file
389
+
390
+
391
+ def test_pdata_init_rehearse_classifies_when_live_project_already_migrated(
392
+ base_env, tmp_path, monkeypatch,
393
+ ):
394
+ """A rehearsal copy is a plain `cp -r` of the project: it carries the published dump but not
395
+ the real .db. The adopt-vs-classify decision must still use the real .db, so a project that
396
+ already has local pdata content gets a classification report, not an adoption message."""
397
+ project_dir = tmp_path / "projects" / "demo"
398
+ project_dir.mkdir(parents=True)
399
+ (project_dir / "ideas.csv").write_text("idea\nfirst\n")
400
+ db_file = _publish_dump(base_env, project_dir, monkeypatch, with_record=True)
401
+ rehearsal_dir = tmp_path / "rehearsal-demo"
402
+ shutil.copytree(project_dir, rehearsal_dir)
403
+ before = db_file.read_bytes()
404
+
405
+ r = _run(base_env, "pdata", "init", "--project", "demo", "--rehearse", str(rehearsal_dir))
406
+
407
+ assert r.returncode == 0, r.stderr
408
+ assert "a published sync dump already exists" not in r.stdout
409
+ assert "ideas.csv" in r.stdout
410
+ assert db_file.read_bytes() == before
411
+
412
+
413
+ def test_pdata_init_rehearse_still_reports_adoption_when_no_local_content(
414
+ base_env, tmp_path, monkeypatch,
415
+ ):
416
+ project_dir = tmp_path / "projects" / "demo"
417
+ project_dir.mkdir(parents=True)
418
+ _publish_dump(base_env, project_dir, monkeypatch, with_record=False)
419
+ rehearsal_dir = tmp_path / "rehearsal-demo"
420
+ shutil.copytree(project_dir, rehearsal_dir)
421
+
422
+ r = _run(base_env, "pdata", "init", "--project", "demo", "--rehearse", str(rehearsal_dir))
423
+
424
+ assert r.returncode == 0, r.stderr
425
+ assert "a published sync dump already exists" in r.stdout
@@ -249,9 +249,9 @@ def test_dry_run_does_not_record_marker(layout):
249
249
  # ---------------------------------------------------------------------------
250
250
 
251
251
  def _seed_old_schema_db(db_path: Path, rows: list[dict]) -> None:
252
- """Build a realistic pre-3.0.0 sessions.db: 2-column PK, no uuid column, with the
253
- `updated_at` column the live schema already has (the original spec's own DDL omitted
254
- it - design.md Decision 4 - this fixture deliberately does not repeat that mistake)."""
252
+ """Build a pre-3.0.0 sessions.db in the v2.13-v2.14.x shape: 2-column PK, no uuid column,
253
+ with the `updated_at` column. Releases v1.0.0 through v2.12.x had no `updated_at`; both
254
+ shapes are covered in test_sessions_db_migration_chain.py."""
255
255
  import sqlite3
256
256
 
257
257
  conn = sqlite3.connect(str(db_path))