cc-session-tools 3.10.0__tar.gz → 3.10.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 (297) hide show
  1. {cc_session_tools-3.10.0/src/cc_session_tools.egg-info → cc_session_tools-3.10.2}/PKG-INFO +1 -1
  2. {cc_session_tools-3.10.0 → cc_session_tools-3.10.2}/pyproject.toml +1 -1
  3. {cc_session_tools-3.10.0 → cc_session_tools-3.10.2}/src/cc_session_tools/lib/pdata/cutover.py +9 -1
  4. {cc_session_tools-3.10.0 → cc_session_tools-3.10.2}/src/cc_session_tools/lib/pdata/init_service.py +196 -97
  5. {cc_session_tools-3.10.0 → cc_session_tools-3.10.2/src/cc_session_tools.egg-info}/PKG-INFO +1 -1
  6. {cc_session_tools-3.10.0 → cc_session_tools-3.10.2}/src/hooks/model_info.py +24 -19
  7. {cc_session_tools-3.10.0 → cc_session_tools-3.10.2}/tests/test_context_window_warning.py +23 -0
  8. cc_session_tools-3.10.2/tests/test_model_info.py +67 -0
  9. cc_session_tools-3.10.0/tests/test_model_info.py +0 -30
  10. {cc_session_tools-3.10.0 → cc_session_tools-3.10.2}/LICENSE +0 -0
  11. {cc_session_tools-3.10.0 → cc_session_tools-3.10.2}/README.md +0 -0
  12. {cc_session_tools-3.10.0 → cc_session_tools-3.10.2}/setup.cfg +0 -0
  13. {cc_session_tools-3.10.0 → cc_session_tools-3.10.2}/src/cc_session_tools/__init__.py +0 -0
  14. {cc_session_tools-3.10.0 → cc_session_tools-3.10.2}/src/cc_session_tools/cli/__init__.py +0 -0
  15. {cc_session_tools-3.10.0 → cc_session_tools-3.10.2}/src/cc_session_tools/cli/ccd.py +0 -0
  16. {cc_session_tools-3.10.0 → cc_session_tools-3.10.2}/src/cc_session_tools/cli/ccmsg.py +0 -0
  17. {cc_session_tools-3.10.0 → cc_session_tools-3.10.2}/src/cc_session_tools/cli/ccr.py +0 -0
  18. {cc_session_tools-3.10.0 → cc_session_tools-3.10.2}/src/cc_session_tools/cli/ccs.py +0 -0
  19. {cc_session_tools-3.10.0 → cc_session_tools-3.10.2}/src/cc_session_tools/cli/ccsched.py +0 -0
  20. {cc_session_tools-3.10.0 → cc_session_tools-3.10.2}/src/cc_session_tools/cli/ccst.py +0 -0
  21. {cc_session_tools-3.10.0 → cc_session_tools-3.10.2}/src/cc_session_tools/cli/migrate_ccmsg.py +0 -0
  22. {cc_session_tools-3.10.0 → cc_session_tools-3.10.2}/src/cc_session_tools/cli/migrate_ccsched.py +0 -0
  23. {cc_session_tools-3.10.0 → cc_session_tools-3.10.2}/src/cc_session_tools/cli/migrate_sessions_db.py +0 -0
  24. {cc_session_tools-3.10.0 → cc_session_tools-3.10.2}/src/cc_session_tools/cli/migrate_telemetry.py +0 -0
  25. {cc_session_tools-3.10.0 → cc_session_tools-3.10.2}/src/cc_session_tools/config/hooks-bundle.json +0 -0
  26. {cc_session_tools-3.10.0 → cc_session_tools-3.10.2}/src/cc_session_tools/config/session-tag-hook.json +0 -0
  27. {cc_session_tools-3.10.0 → cc_session_tools-3.10.2}/src/cc_session_tools/hooks_install.py +0 -0
  28. {cc_session_tools-3.10.0 → cc_session_tools-3.10.2}/src/cc_session_tools/lib/__init__.py +0 -0
  29. {cc_session_tools-3.10.0 → cc_session_tools-3.10.2}/src/cc_session_tools/lib/bun_tools.py +0 -0
  30. {cc_session_tools-3.10.0 → cc_session_tools-3.10.2}/src/cc_session_tools/lib/claude_flags.py +0 -0
  31. {cc_session_tools-3.10.0 → cc_session_tools-3.10.2}/src/cc_session_tools/lib/claude_md_install.py +0 -0
  32. {cc_session_tools-3.10.0 → cc_session_tools-3.10.2}/src/cc_session_tools/lib/context_overrides.py +0 -0
  33. {cc_session_tools-3.10.0 → cc_session_tools-3.10.2}/src/cc_session_tools/lib/db.py +0 -0
  34. {cc_session_tools-3.10.0 → cc_session_tools-3.10.2}/src/cc_session_tools/lib/debug.py +0 -0
  35. {cc_session_tools-3.10.0 → cc_session_tools-3.10.2}/src/cc_session_tools/lib/doctor.py +0 -0
  36. {cc_session_tools-3.10.0 → cc_session_tools-3.10.2}/src/cc_session_tools/lib/doctor_mutes.py +0 -0
  37. {cc_session_tools-3.10.0 → cc_session_tools-3.10.2}/src/cc_session_tools/lib/hook_registry.py +0 -0
  38. {cc_session_tools-3.10.0 → cc_session_tools-3.10.2}/src/cc_session_tools/lib/install_sync.py +0 -0
  39. {cc_session_tools-3.10.0 → cc_session_tools-3.10.2}/src/cc_session_tools/lib/levenshtein.py +0 -0
  40. {cc_session_tools-3.10.0 → cc_session_tools-3.10.2}/src/cc_session_tools/lib/machine_identity.py +0 -0
  41. {cc_session_tools-3.10.0 → cc_session_tools-3.10.2}/src/cc_session_tools/lib/messaging/__init__.py +0 -0
  42. {cc_session_tools-3.10.0 → cc_session_tools-3.10.2}/src/cc_session_tools/lib/messaging/addressing.py +0 -0
  43. {cc_session_tools-3.10.0 → cc_session_tools-3.10.2}/src/cc_session_tools/lib/messaging/cursor.py +0 -0
  44. {cc_session_tools-3.10.0 → cc_session_tools-3.10.2}/src/cc_session_tools/lib/messaging/lock.py +0 -0
  45. {cc_session_tools-3.10.0 → cc_session_tools-3.10.2}/src/cc_session_tools/lib/messaging/message.py +0 -0
  46. {cc_session_tools-3.10.0 → cc_session_tools-3.10.2}/src/cc_session_tools/lib/messaging/move_safety.py +0 -0
  47. {cc_session_tools-3.10.0 → cc_session_tools-3.10.2}/src/cc_session_tools/lib/messaging/repository.py +0 -0
  48. {cc_session_tools-3.10.0 → cc_session_tools-3.10.2}/src/cc_session_tools/lib/messaging/retention.py +0 -0
  49. {cc_session_tools-3.10.0 → cc_session_tools-3.10.2}/src/cc_session_tools/lib/messaging/service.py +0 -0
  50. {cc_session_tools-3.10.0 → cc_session_tools-3.10.2}/src/cc_session_tools/lib/messaging/store.py +0 -0
  51. {cc_session_tools-3.10.0 → cc_session_tools-3.10.2}/src/cc_session_tools/lib/occupancy.py +0 -0
  52. {cc_session_tools-3.10.0 → cc_session_tools-3.10.2}/src/cc_session_tools/lib/paths.py +0 -0
  53. {cc_session_tools-3.10.0 → cc_session_tools-3.10.2}/src/cc_session_tools/lib/pdata/__init__.py +0 -0
  54. {cc_session_tools-3.10.0 → cc_session_tools-3.10.2}/src/cc_session_tools/lib/pdata/backup.py +0 -0
  55. {cc_session_tools-3.10.0 → cc_session_tools-3.10.2}/src/cc_session_tools/lib/pdata/classify.py +0 -0
  56. {cc_session_tools-3.10.0 → cc_session_tools-3.10.2}/src/cc_session_tools/lib/pdata/dump.py +0 -0
  57. {cc_session_tools-3.10.0 → cc_session_tools-3.10.2}/src/cc_session_tools/lib/pdata/formatting.py +0 -0
  58. {cc_session_tools-3.10.0 → cc_session_tools-3.10.2}/src/cc_session_tools/lib/pdata/importers.py +0 -0
  59. {cc_session_tools-3.10.0 → cc_session_tools-3.10.2}/src/cc_session_tools/lib/pdata/init_paths.py +0 -0
  60. {cc_session_tools-3.10.0 → cc_session_tools-3.10.2}/src/cc_session_tools/lib/pdata/manifest.py +0 -0
  61. {cc_session_tools-3.10.0 → cc_session_tools-3.10.2}/src/cc_session_tools/lib/pdata/naming.py +0 -0
  62. {cc_session_tools-3.10.0 → cc_session_tools-3.10.2}/src/cc_session_tools/lib/pdata/readiness.py +0 -0
  63. {cc_session_tools-3.10.0 → cc_session_tools-3.10.2}/src/cc_session_tools/lib/pdata/rehydrate.py +0 -0
  64. {cc_session_tools-3.10.0 → cc_session_tools-3.10.2}/src/cc_session_tools/lib/pdata/rename_group.py +0 -0
  65. {cc_session_tools-3.10.0 → cc_session_tools-3.10.2}/src/cc_session_tools/lib/pdata/reorganize.py +0 -0
  66. {cc_session_tools-3.10.0 → cc_session_tools-3.10.2}/src/cc_session_tools/lib/pdata/repository.py +0 -0
  67. {cc_session_tools-3.10.0 → cc_session_tools-3.10.2}/src/cc_session_tools/lib/pdata/resolve.py +0 -0
  68. {cc_session_tools-3.10.0 → cc_session_tools-3.10.2}/src/cc_session_tools/lib/pdata/service.py +0 -0
  69. {cc_session_tools-3.10.0 → cc_session_tools-3.10.2}/src/cc_session_tools/lib/pdata/session_output.py +0 -0
  70. {cc_session_tools-3.10.0 → cc_session_tools-3.10.2}/src/cc_session_tools/lib/pdata/store.py +0 -0
  71. {cc_session_tools-3.10.0 → cc_session_tools-3.10.2}/src/cc_session_tools/lib/pdata/sync_check.py +0 -0
  72. {cc_session_tools-3.10.0 → cc_session_tools-3.10.2}/src/cc_session_tools/lib/pdata/sync_lock.py +0 -0
  73. {cc_session_tools-3.10.0 → cc_session_tools-3.10.2}/src/cc_session_tools/lib/pdata/sync_notify.py +0 -0
  74. {cc_session_tools-3.10.0 → cc_session_tools-3.10.2}/src/cc_session_tools/lib/pdata/vector_clock.py +0 -0
  75. {cc_session_tools-3.10.0 → cc_session_tools-3.10.2}/src/cc_session_tools/lib/pdata/vector_clock_store.py +0 -0
  76. {cc_session_tools-3.10.0 → cc_session_tools-3.10.2}/src/cc_session_tools/lib/pdata/verify.py +0 -0
  77. {cc_session_tools-3.10.0 → cc_session_tools-3.10.2}/src/cc_session_tools/lib/pdata/write_log.py +0 -0
  78. {cc_session_tools-3.10.0 → cc_session_tools-3.10.2}/src/cc_session_tools/lib/picker.py +0 -0
  79. {cc_session_tools-3.10.0 → cc_session_tools-3.10.2}/src/cc_session_tools/lib/proc_lock.py +0 -0
  80. {cc_session_tools-3.10.0 → cc_session_tools-3.10.2}/src/cc_session_tools/lib/prompts.py +0 -0
  81. {cc_session_tools-3.10.0 → cc_session_tools-3.10.2}/src/cc_session_tools/lib/roots.py +0 -0
  82. {cc_session_tools-3.10.0 → cc_session_tools-3.10.2}/src/cc_session_tools/lib/rules.py +0 -0
  83. {cc_session_tools-3.10.0 → cc_session_tools-3.10.2}/src/cc_session_tools/lib/scheduler/__init__.py +0 -0
  84. {cc_session_tools-3.10.0 → cc_session_tools-3.10.2}/src/cc_session_tools/lib/scheduler/bundled_jobs.py +0 -0
  85. {cc_session_tools-3.10.0 → cc_session_tools-3.10.2}/src/cc_session_tools/lib/scheduler/cadence.py +0 -0
  86. {cc_session_tools-3.10.0 → cc_session_tools-3.10.2}/src/cc_session_tools/lib/scheduler/cursor.py +0 -0
  87. {cc_session_tools-3.10.0 → cc_session_tools-3.10.2}/src/cc_session_tools/lib/scheduler/digest.py +0 -0
  88. {cc_session_tools-3.10.0 → cc_session_tools-3.10.2}/src/cc_session_tools/lib/scheduler/due.py +0 -0
  89. {cc_session_tools-3.10.0 → cc_session_tools-3.10.2}/src/cc_session_tools/lib/scheduler/duration.py +0 -0
  90. {cc_session_tools-3.10.0 → cc_session_tools-3.10.2}/src/cc_session_tools/lib/scheduler/jobspec.py +0 -0
  91. {cc_session_tools-3.10.0 → cc_session_tools-3.10.2}/src/cc_session_tools/lib/scheduler/ledger.py +0 -0
  92. {cc_session_tools-3.10.0 → cc_session_tools-3.10.2}/src/cc_session_tools/lib/scheduler/lock.py +0 -0
  93. {cc_session_tools-3.10.0 → cc_session_tools-3.10.2}/src/cc_session_tools/lib/scheduler/notify.py +0 -0
  94. {cc_session_tools-3.10.0 → cc_session_tools-3.10.2}/src/cc_session_tools/lib/scheduler/reconcile.py +0 -0
  95. {cc_session_tools-3.10.0 → cc_session_tools-3.10.2}/src/cc_session_tools/lib/scheduler/registry.py +0 -0
  96. {cc_session_tools-3.10.0 → cc_session_tools-3.10.2}/src/cc_session_tools/lib/scheduler/runner.py +0 -0
  97. {cc_session_tools-3.10.0 → cc_session_tools-3.10.2}/src/cc_session_tools/lib/scheduler/state.py +0 -0
  98. {cc_session_tools-3.10.0 → cc_session_tools-3.10.2}/src/cc_session_tools/lib/scheduler/store.py +0 -0
  99. {cc_session_tools-3.10.0 → cc_session_tools-3.10.2}/src/cc_session_tools/lib/scheduler/surface.py +0 -0
  100. {cc_session_tools-3.10.0 → cc_session_tools-3.10.2}/src/cc_session_tools/lib/scheduler/throttle.py +0 -0
  101. {cc_session_tools-3.10.0 → cc_session_tools-3.10.2}/src/cc_session_tools/lib/scheduler/worker.py +0 -0
  102. {cc_session_tools-3.10.0 → cc_session_tools-3.10.2}/src/cc_session_tools/lib/session_gc.py +0 -0
  103. {cc_session_tools-3.10.0 → cc_session_tools-3.10.2}/src/cc_session_tools/lib/sessions.py +0 -0
  104. {cc_session_tools-3.10.0 → cc_session_tools-3.10.2}/src/cc_session_tools/lib/sessions_db.py +0 -0
  105. {cc_session_tools-3.10.0 → cc_session_tools-3.10.2}/src/cc_session_tools/lib/sessions_repair.py +0 -0
  106. {cc_session_tools-3.10.0 → cc_session_tools-3.10.2}/src/cc_session_tools/lib/shell_install.py +0 -0
  107. {cc_session_tools-3.10.0 → cc_session_tools-3.10.2}/src/cc_session_tools/lib/tasklist.py +0 -0
  108. {cc_session_tools-3.10.0 → cc_session_tools-3.10.2}/src/cc_session_tools/lib/telemetry_store.py +0 -0
  109. {cc_session_tools-3.10.0 → cc_session_tools-3.10.2}/src/cc_session_tools/lib/text_wrap.py +0 -0
  110. {cc_session_tools-3.10.0 → cc_session_tools-3.10.2}/src/cc_session_tools/skills/analyse-cc-usage/SKILL.md +0 -0
  111. {cc_session_tools-3.10.0 → cc_session_tools-3.10.2}/src/cc_session_tools/skills/clean-hook-sessions/SKILL.md +0 -0
  112. {cc_session_tools-3.10.0 → cc_session_tools-3.10.2}/src/cc_session_tools/skills/clean-hook-sessions/scripts/clean-hook-sessions.py +0 -0
  113. {cc_session_tools-3.10.0 → cc_session_tools-3.10.2}/src/cc_session_tools/skills/compact-smart/SKILL.md +0 -0
  114. {cc_session_tools-3.10.0 → cc_session_tools-3.10.2}/src/cc_session_tools/skills/context-override/SKILL.md +0 -0
  115. {cc_session_tools-3.10.0 → cc_session_tools-3.10.2}/src/cc_session_tools/skills/delete-sessions/SKILL.md +0 -0
  116. {cc_session_tools-3.10.0 → cc_session_tools-3.10.2}/src/cc_session_tools/skills/delete-sessions/scripts/delete_sessions.py +0 -0
  117. {cc_session_tools-3.10.0 → cc_session_tools-3.10.2}/src/cc_session_tools/skills/do-executor-critic-assessor-loop/SKILL.md +0 -0
  118. {cc_session_tools-3.10.0 → cc_session_tools-3.10.2}/src/cc_session_tools/skills/extract-pptx/SKILL.md +0 -0
  119. {cc_session_tools-3.10.0 → cc_session_tools-3.10.2}/src/cc_session_tools/skills/extract-pptx/scripts/extract_pptx.py +0 -0
  120. {cc_session_tools-3.10.0 → cc_session_tools-3.10.2}/src/cc_session_tools/skills/extract-to-text/SKILL.md +0 -0
  121. {cc_session_tools-3.10.0 → cc_session_tools-3.10.2}/src/cc_session_tools/skills/extract-to-text/scripts/extract_to_text.py +0 -0
  122. {cc_session_tools-3.10.0 → cc_session_tools-3.10.2}/src/cc_session_tools/skills/find-claude-code-session/SKILL.md +0 -0
  123. {cc_session_tools-3.10.0 → cc_session_tools-3.10.2}/src/cc_session_tools/skills/generate-8digit-code/SKILL.md +0 -0
  124. {cc_session_tools-3.10.0 → cc_session_tools-3.10.2}/src/cc_session_tools/skills/generate-8digit-code/scripts/generate_8digit_code.py +0 -0
  125. {cc_session_tools-3.10.0 → cc_session_tools-3.10.2}/src/cc_session_tools/skills/list-empty-sessions/SKILL.md +0 -0
  126. {cc_session_tools-3.10.0 → cc_session_tools-3.10.2}/src/cc_session_tools/skills/list-empty-sessions/scripts/list_empty_sessions.py +0 -0
  127. {cc_session_tools-3.10.0 → cc_session_tools-3.10.2}/src/cc_session_tools/skills/manage-recurring-cc-jobs-using-ccsched/SKILL.md +0 -0
  128. {cc_session_tools-3.10.0 → cc_session_tools-3.10.2}/src/cc_session_tools/skills/move-session/SKILL.md +0 -0
  129. {cc_session_tools-3.10.0 → cc_session_tools-3.10.2}/src/cc_session_tools/skills/move-session/hooks/sessionstart-pending-rename.sh +0 -0
  130. {cc_session_tools-3.10.0 → cc_session_tools-3.10.2}/src/cc_session_tools/skills/move-session/scripts/cc_session_rules.py +0 -0
  131. {cc_session_tools-3.10.0 → cc_session_tools-3.10.2}/src/cc_session_tools/skills/move-session/scripts/move_session.py +0 -0
  132. {cc_session_tools-3.10.0 → cc_session_tools-3.10.2}/src/cc_session_tools/skills/pm-pdata-design-schema/SKILL.md +0 -0
  133. {cc_session_tools-3.10.0 → cc_session_tools-3.10.2}/src/cc_session_tools/skills/pm-pdata-do-audit-and-prepare-to-migrate/SKILL.md +0 -0
  134. {cc_session_tools-3.10.0 → cc_session_tools-3.10.2}/src/cc_session_tools/skills/pm-pdata-do-init/SKILL.md +0 -0
  135. {cc_session_tools-3.10.0 → cc_session_tools-3.10.2}/src/cc_session_tools/skills/pm-pdata-do-manual-migration/SKILL.md +0 -0
  136. {cc_session_tools-3.10.0 → cc_session_tools-3.10.2}/src/cc_session_tools/skills/pm-pdata-do-update-consuming-skills/SKILL.md +0 -0
  137. {cc_session_tools-3.10.0 → cc_session_tools-3.10.2}/src/cc_session_tools/skills/pm-pdata-do-update-project-docs/SKILL.md +0 -0
  138. {cc_session_tools-3.10.0 → cc_session_tools-3.10.2}/src/cc_session_tools/skills/pm-pdata-resolve-conflicts/SKILL.md +0 -0
  139. {cc_session_tools-3.10.0 → cc_session_tools-3.10.2}/src/cc_session_tools/skills/pm-project-layout-reference/SKILL.md +0 -0
  140. {cc_session_tools-3.10.0 → cc_session_tools-3.10.2}/src/cc_session_tools/skills/pm-update-central-files/SKILL.md +0 -0
  141. {cc_session_tools-3.10.0 → cc_session_tools-3.10.2}/src/cc_session_tools/skills/reduce-persistent-context/CALIBRATION.md +0 -0
  142. {cc_session_tools-3.10.0 → cc_session_tools-3.10.2}/src/cc_session_tools/skills/reduce-persistent-context/SKILL.md +0 -0
  143. {cc_session_tools-3.10.0 → cc_session_tools-3.10.2}/src/cc_session_tools/skills/reduce-persistent-context/scripts/analyze_context.py +0 -0
  144. {cc_session_tools-3.10.0 → cc_session_tools-3.10.2}/src/cc_session_tools/skills/reduce-persistent-context/scripts/duplicates.py +0 -0
  145. {cc_session_tools-3.10.0 → cc_session_tools-3.10.2}/src/cc_session_tools/skills/reduce-persistent-context/scripts/measure.py +0 -0
  146. {cc_session_tools-3.10.0 → cc_session_tools-3.10.2}/src/cc_session_tools/skills/reduce-persistent-context/scripts/report.py +0 -0
  147. {cc_session_tools-3.10.0 → cc_session_tools-3.10.2}/src/cc_session_tools/skills/reduce-persistent-context/scripts/tokens.py +0 -0
  148. {cc_session_tools-3.10.0 → cc_session_tools-3.10.2}/src/cc_session_tools/skills/reduce-persistent-context/scripts/usage.py +0 -0
  149. {cc_session_tools-3.10.0 → cc_session_tools-3.10.2}/src/cc_session_tools/skills/select-agent-model/SKILL.md +0 -0
  150. {cc_session_tools-3.10.0 → cc_session_tools-3.10.2}/src/cc_session_tools/skills/send-session-message/SKILL.md +0 -0
  151. {cc_session_tools-3.10.0 → cc_session_tools-3.10.2}/src/cc_session_tools/skills/update-command-cache/SKILL.md +0 -0
  152. {cc_session_tools-3.10.0 → cc_session_tools-3.10.2}/src/cc_session_tools/skills/update-command-cache/scripts/update_command_cache.py +0 -0
  153. {cc_session_tools-3.10.0 → cc_session_tools-3.10.2}/src/cc_session_tools.egg-info/SOURCES.txt +0 -0
  154. {cc_session_tools-3.10.0 → cc_session_tools-3.10.2}/src/cc_session_tools.egg-info/dependency_links.txt +0 -0
  155. {cc_session_tools-3.10.0 → cc_session_tools-3.10.2}/src/cc_session_tools.egg-info/entry_points.txt +0 -0
  156. {cc_session_tools-3.10.0 → cc_session_tools-3.10.2}/src/cc_session_tools.egg-info/requires.txt +0 -0
  157. {cc_session_tools-3.10.0 → cc_session_tools-3.10.2}/src/cc_session_tools.egg-info/top_level.txt +0 -0
  158. {cc_session_tools-3.10.0 → cc_session_tools-3.10.2}/src/claude_code_usage/__init__.py +0 -0
  159. {cc_session_tools-3.10.0 → cc_session_tools-3.10.2}/src/claude_code_usage/attribution.py +0 -0
  160. {cc_session_tools-3.10.0 → cc_session_tools-3.10.2}/src/claude_code_usage/cache.py +0 -0
  161. {cc_session_tools-3.10.0 → cc_session_tools-3.10.2}/src/claude_code_usage/ccusage_wrapper.py +0 -0
  162. {cc_session_tools-3.10.0 → cc_session_tools-3.10.2}/src/claude_code_usage/cli.py +0 -0
  163. {cc_session_tools-3.10.0 → cc_session_tools-3.10.2}/src/claude_code_usage/data/pricing.json +0 -0
  164. {cc_session_tools-3.10.0 → cc_session_tools-3.10.2}/src/claude_code_usage/parent_inference.py +0 -0
  165. {cc_session_tools-3.10.0 → cc_session_tools-3.10.2}/src/claude_code_usage/parser.py +0 -0
  166. {cc_session_tools-3.10.0 → cc_session_tools-3.10.2}/src/claude_code_usage/pricing.py +0 -0
  167. {cc_session_tools-3.10.0 → cc_session_tools-3.10.2}/src/claude_code_usage/query.py +0 -0
  168. {cc_session_tools-3.10.0 → cc_session_tools-3.10.2}/src/claude_code_usage/report.py +0 -0
  169. {cc_session_tools-3.10.0 → cc_session_tools-3.10.2}/src/claude_code_usage/schema.py +0 -0
  170. {cc_session_tools-3.10.0 → cc_session_tools-3.10.2}/src/claude_code_usage/session_names.py +0 -0
  171. {cc_session_tools-3.10.0 → cc_session_tools-3.10.2}/src/hooks/__init__.py +0 -0
  172. {cc_session_tools-3.10.0 → cc_session_tools-3.10.2}/src/hooks/after_response.py +0 -0
  173. {cc_session_tools-3.10.0 → cc_session_tools-3.10.2}/src/hooks/bash_hard_deny.py +0 -0
  174. {cc_session_tools-3.10.0 → cc_session_tools-3.10.2}/src/hooks/bash_security_review.py +0 -0
  175. {cc_session_tools-3.10.0 → cc_session_tools-3.10.2}/src/hooks/cache.py +0 -0
  176. {cc_session_tools-3.10.0 → cc_session_tools-3.10.2}/src/hooks/catchup.py +0 -0
  177. {cc_session_tools-3.10.0 → cc_session_tools-3.10.2}/src/hooks/confirm_8digit.py +0 -0
  178. {cc_session_tools-3.10.0 → cc_session_tools-3.10.2}/src/hooks/context_window_warning.py +0 -0
  179. {cc_session_tools-3.10.0 → cc_session_tools-3.10.2}/src/hooks/last_screenshot.py +0 -0
  180. {cc_session_tools-3.10.0 → cc_session_tools-3.10.2}/src/hooks/marker_allow.py +0 -0
  181. {cc_session_tools-3.10.0 → cc_session_tools-3.10.2}/src/hooks/markers.py +0 -0
  182. {cc_session_tools-3.10.0 → cc_session_tools-3.10.2}/src/hooks/messaging_deliver.py +0 -0
  183. {cc_session_tools-3.10.0 → cc_session_tools-3.10.2}/src/hooks/normalise.py +0 -0
  184. {cc_session_tools-3.10.0 → cc_session_tools-3.10.2}/src/hooks/pdata_sync.py +0 -0
  185. {cc_session_tools-3.10.0 → cc_session_tools-3.10.2}/src/hooks/pending_migration.py +0 -0
  186. {cc_session_tools-3.10.0 → cc_session_tools-3.10.2}/src/hooks/pending_rename.py +0 -0
  187. {cc_session_tools-3.10.0 → cc_session_tools-3.10.2}/src/hooks/script_allowlist.py +0 -0
  188. {cc_session_tools-3.10.0 → cc_session_tools-3.10.2}/src/hooks/session_tag.py +0 -0
  189. {cc_session_tools-3.10.0 → cc_session_tools-3.10.2}/src/hooks/stats.py +0 -0
  190. {cc_session_tools-3.10.0 → cc_session_tools-3.10.2}/src/hooks/telemetry.py +0 -0
  191. {cc_session_tools-3.10.0 → cc_session_tools-3.10.2}/src/hooks/telemetry_query.py +0 -0
  192. {cc_session_tools-3.10.0 → cc_session_tools-3.10.2}/src/hooks/telemetry_trim.py +0 -0
  193. {cc_session_tools-3.10.0 → cc_session_tools-3.10.2}/src/hooks/transcript.py +0 -0
  194. {cc_session_tools-3.10.0 → cc_session_tools-3.10.2}/src/hooks/worklog_guard.py +0 -0
  195. {cc_session_tools-3.10.0 → cc_session_tools-3.10.2}/tests/test_after_response.py +0 -0
  196. {cc_session_tools-3.10.0 → cc_session_tools-3.10.2}/tests/test_attribution.py +0 -0
  197. {cc_session_tools-3.10.0 → cc_session_tools-3.10.2}/tests/test_bash_hard_deny.py +0 -0
  198. {cc_session_tools-3.10.0 → cc_session_tools-3.10.2}/tests/test_bash_security_review.py +0 -0
  199. {cc_session_tools-3.10.0 → cc_session_tools-3.10.2}/tests/test_bash_security_review_allowlist.py +0 -0
  200. {cc_session_tools-3.10.0 → cc_session_tools-3.10.2}/tests/test_bun_tools.py +0 -0
  201. {cc_session_tools-3.10.0 → cc_session_tools-3.10.2}/tests/test_cache.py +0 -0
  202. {cc_session_tools-3.10.0 → cc_session_tools-3.10.2}/tests/test_cache_sqlite.py +0 -0
  203. {cc_session_tools-3.10.0 → cc_session_tools-3.10.2}/tests/test_ccr_orphans.py +0 -0
  204. {cc_session_tools-3.10.0 → cc_session_tools-3.10.2}/tests/test_ccs_emptiness.py +0 -0
  205. {cc_session_tools-3.10.0 → cc_session_tools-3.10.2}/tests/test_ccs_eta.py +0 -0
  206. {cc_session_tools-3.10.0 → cc_session_tools-3.10.2}/tests/test_ccs_list_mode.py +0 -0
  207. {cc_session_tools-3.10.0 → cc_session_tools-3.10.2}/tests/test_ccs_scope_flags.py +0 -0
  208. {cc_session_tools-3.10.0 → cc_session_tools-3.10.2}/tests/test_ccs_sentinel_sort.py +0 -0
  209. {cc_session_tools-3.10.0 → cc_session_tools-3.10.2}/tests/test_ccs_session_counts.py +0 -0
  210. {cc_session_tools-3.10.0 → cc_session_tools-3.10.2}/tests/test_ccst_bundle_discovery.py +0 -0
  211. {cc_session_tools-3.10.0 → cc_session_tools-3.10.2}/tests/test_ccst_ccsched_jobs_cli.py +0 -0
  212. {cc_session_tools-3.10.0 → cc_session_tools-3.10.2}/tests/test_ccst_cli.py +0 -0
  213. {cc_session_tools-3.10.0 → cc_session_tools-3.10.2}/tests/test_ccst_context_override_cli.py +0 -0
  214. {cc_session_tools-3.10.0 → cc_session_tools-3.10.2}/tests/test_ccst_doctor.py +0 -0
  215. {cc_session_tools-3.10.0 → cc_session_tools-3.10.2}/tests/test_ccst_gc_prune.py +0 -0
  216. {cc_session_tools-3.10.0 → cc_session_tools-3.10.2}/tests/test_ccst_gc_report.py +0 -0
  217. {cc_session_tools-3.10.0 → cc_session_tools-3.10.2}/tests/test_ccst_hook_dispatcher.py +0 -0
  218. {cc_session_tools-3.10.0 → cc_session_tools-3.10.2}/tests/test_ccst_hooks_allowlist.py +0 -0
  219. {cc_session_tools-3.10.0 → cc_session_tools-3.10.2}/tests/test_ccst_hooks_install_prune.py +0 -0
  220. {cc_session_tools-3.10.0 → cc_session_tools-3.10.2}/tests/test_ccst_hooks_install_zero_arg.py +0 -0
  221. {cc_session_tools-3.10.0 → cc_session_tools-3.10.2}/tests/test_ccst_hooks_uninstall.py +0 -0
  222. {cc_session_tools-3.10.0 → cc_session_tools-3.10.2}/tests/test_ccst_install_everything.py +0 -0
  223. {cc_session_tools-3.10.0 → cc_session_tools-3.10.2}/tests/test_ccst_machine_identity_cli.py +0 -0
  224. {cc_session_tools-3.10.0 → cc_session_tools-3.10.2}/tests/test_ccst_main_gate.py +0 -0
  225. {cc_session_tools-3.10.0 → cc_session_tools-3.10.2}/tests/test_ccst_pdata_cli.py +0 -0
  226. {cc_session_tools-3.10.0 → cc_session_tools-3.10.2}/tests/test_ccst_pdata_init_cli.py +0 -0
  227. {cc_session_tools-3.10.0 → cc_session_tools-3.10.2}/tests/test_ccst_pdata_readiness_cli.py +0 -0
  228. {cc_session_tools-3.10.0 → cc_session_tools-3.10.2}/tests/test_ccst_pdata_reconcile_cli.py +0 -0
  229. {cc_session_tools-3.10.0 → cc_session_tools-3.10.2}/tests/test_ccst_pdata_rename_group_cli.py +0 -0
  230. {cc_session_tools-3.10.0 → cc_session_tools-3.10.2}/tests/test_ccst_pdata_reorganize_cli.py +0 -0
  231. {cc_session_tools-3.10.0 → cc_session_tools-3.10.2}/tests/test_ccst_pdata_sync_cli.py +0 -0
  232. {cc_session_tools-3.10.0 → cc_session_tools-3.10.2}/tests/test_ccst_pdata_verify_cli.py +0 -0
  233. {cc_session_tools-3.10.0 → cc_session_tools-3.10.2}/tests/test_ccst_repair_sessions_cli.py +0 -0
  234. {cc_session_tools-3.10.0 → cc_session_tools-3.10.2}/tests/test_ccst_sessions_cli.py +0 -0
  235. {cc_session_tools-3.10.0 → cc_session_tools-3.10.2}/tests/test_ccst_shell_install.py +0 -0
  236. {cc_session_tools-3.10.0 → cc_session_tools-3.10.2}/tests/test_ccst_skills_install.py +0 -0
  237. {cc_session_tools-3.10.0 → cc_session_tools-3.10.2}/tests/test_ccst_skills_uninstall.py +0 -0
  238. {cc_session_tools-3.10.0 → cc_session_tools-3.10.2}/tests/test_ccst_telemetry_query.py +0 -0
  239. {cc_session_tools-3.10.0 → cc_session_tools-3.10.2}/tests/test_ccst_telemetry_trim.py +0 -0
  240. {cc_session_tools-3.10.0 → cc_session_tools-3.10.2}/tests/test_ccusage_wrapper.py +0 -0
  241. {cc_session_tools-3.10.0 → cc_session_tools-3.10.2}/tests/test_claude_flags.py +0 -0
  242. {cc_session_tools-3.10.0 → cc_session_tools-3.10.2}/tests/test_claude_md_install.py +0 -0
  243. {cc_session_tools-3.10.0 → cc_session_tools-3.10.2}/tests/test_cli.py +0 -0
  244. {cc_session_tools-3.10.0 → cc_session_tools-3.10.2}/tests/test_cli_ccd.py +0 -0
  245. {cc_session_tools-3.10.0 → cc_session_tools-3.10.2}/tests/test_cli_ccd_dry_run.py +0 -0
  246. {cc_session_tools-3.10.0 → cc_session_tools-3.10.2}/tests/test_cli_ccr.py +0 -0
  247. {cc_session_tools-3.10.0 → cc_session_tools-3.10.2}/tests/test_cli_ccs.py +0 -0
  248. {cc_session_tools-3.10.0 → cc_session_tools-3.10.2}/tests/test_cli_version.py +0 -0
  249. {cc_session_tools-3.10.0 → cc_session_tools-3.10.2}/tests/test_confirm_8digit.py +0 -0
  250. {cc_session_tools-3.10.0 → cc_session_tools-3.10.2}/tests/test_context_overrides.py +0 -0
  251. {cc_session_tools-3.10.0 → cc_session_tools-3.10.2}/tests/test_db.py +0 -0
  252. {cc_session_tools-3.10.0 → cc_session_tools-3.10.2}/tests/test_debug.py +0 -0
  253. {cc_session_tools-3.10.0 → cc_session_tools-3.10.2}/tests/test_doctor_mutes.py +0 -0
  254. {cc_session_tools-3.10.0 → cc_session_tools-3.10.2}/tests/test_empty_session.py +0 -0
  255. {cc_session_tools-3.10.0 → cc_session_tools-3.10.2}/tests/test_hook_cache.py +0 -0
  256. {cc_session_tools-3.10.0 → cc_session_tools-3.10.2}/tests/test_hooks_install.py +0 -0
  257. {cc_session_tools-3.10.0 → cc_session_tools-3.10.2}/tests/test_hooks_install_records.py +0 -0
  258. {cc_session_tools-3.10.0 → cc_session_tools-3.10.2}/tests/test_install_sync.py +0 -0
  259. {cc_session_tools-3.10.0 → cc_session_tools-3.10.2}/tests/test_last_screenshot.py +0 -0
  260. {cc_session_tools-3.10.0 → cc_session_tools-3.10.2}/tests/test_levenshtein.py +0 -0
  261. {cc_session_tools-3.10.0 → cc_session_tools-3.10.2}/tests/test_machine_identity.py +0 -0
  262. {cc_session_tools-3.10.0 → cc_session_tools-3.10.2}/tests/test_marker_allow.py +0 -0
  263. {cc_session_tools-3.10.0 → cc_session_tools-3.10.2}/tests/test_migrate_ccmsg.py +0 -0
  264. {cc_session_tools-3.10.0 → cc_session_tools-3.10.2}/tests/test_migrate_ccsched.py +0 -0
  265. {cc_session_tools-3.10.0 → cc_session_tools-3.10.2}/tests/test_migrate_sessions_db.py +0 -0
  266. {cc_session_tools-3.10.0 → cc_session_tools-3.10.2}/tests/test_migrate_telemetry.py +0 -0
  267. {cc_session_tools-3.10.0 → cc_session_tools-3.10.2}/tests/test_normalise.py +0 -0
  268. {cc_session_tools-3.10.0 → cc_session_tools-3.10.2}/tests/test_occupancy.py +0 -0
  269. {cc_session_tools-3.10.0 → cc_session_tools-3.10.2}/tests/test_parent_inference.py +0 -0
  270. {cc_session_tools-3.10.0 → cc_session_tools-3.10.2}/tests/test_parser.py +0 -0
  271. {cc_session_tools-3.10.0 → cc_session_tools-3.10.2}/tests/test_paths.py +0 -0
  272. {cc_session_tools-3.10.0 → cc_session_tools-3.10.2}/tests/test_pdata_audit_skill.py +0 -0
  273. {cc_session_tools-3.10.0 → cc_session_tools-3.10.2}/tests/test_pdata_post_migration_skills.py +0 -0
  274. {cc_session_tools-3.10.0 → cc_session_tools-3.10.2}/tests/test_pending_migration_hook.py +0 -0
  275. {cc_session_tools-3.10.0 → cc_session_tools-3.10.2}/tests/test_picker.py +0 -0
  276. {cc_session_tools-3.10.0 → cc_session_tools-3.10.2}/tests/test_pricing.py +0 -0
  277. {cc_session_tools-3.10.0 → cc_session_tools-3.10.2}/tests/test_proc_lock.py +0 -0
  278. {cc_session_tools-3.10.0 → cc_session_tools-3.10.2}/tests/test_prompts.py +0 -0
  279. {cc_session_tools-3.10.0 → cc_session_tools-3.10.2}/tests/test_query.py +0 -0
  280. {cc_session_tools-3.10.0 → cc_session_tools-3.10.2}/tests/test_report.py +0 -0
  281. {cc_session_tools-3.10.0 → cc_session_tools-3.10.2}/tests/test_roots.py +0 -0
  282. {cc_session_tools-3.10.0 → cc_session_tools-3.10.2}/tests/test_rules.py +0 -0
  283. {cc_session_tools-3.10.0 → cc_session_tools-3.10.2}/tests/test_scheduler_bundled_jobs.py +0 -0
  284. {cc_session_tools-3.10.0 → cc_session_tools-3.10.2}/tests/test_schema.py +0 -0
  285. {cc_session_tools-3.10.0 → cc_session_tools-3.10.2}/tests/test_script_allowlist.py +0 -0
  286. {cc_session_tools-3.10.0 → cc_session_tools-3.10.2}/tests/test_session_names.py +0 -0
  287. {cc_session_tools-3.10.0 → cc_session_tools-3.10.2}/tests/test_session_tag.py +0 -0
  288. {cc_session_tools-3.10.0 → cc_session_tools-3.10.2}/tests/test_sessions.py +0 -0
  289. {cc_session_tools-3.10.0 → cc_session_tools-3.10.2}/tests/test_sessions_db.py +0 -0
  290. {cc_session_tools-3.10.0 → cc_session_tools-3.10.2}/tests/test_sessions_db_migration_chain.py +0 -0
  291. {cc_session_tools-3.10.0 → cc_session_tools-3.10.2}/tests/test_sessions_repair.py +0 -0
  292. {cc_session_tools-3.10.0 → cc_session_tools-3.10.2}/tests/test_tasklist.py +0 -0
  293. {cc_session_tools-3.10.0 → cc_session_tools-3.10.2}/tests/test_telemetry.py +0 -0
  294. {cc_session_tools-3.10.0 → cc_session_tools-3.10.2}/tests/test_telemetry_store.py +0 -0
  295. {cc_session_tools-3.10.0 → cc_session_tools-3.10.2}/tests/test_text_wrap.py +0 -0
  296. {cc_session_tools-3.10.0 → cc_session_tools-3.10.2}/tests/test_transcript.py +0 -0
  297. {cc_session_tools-3.10.0 → cc_session_tools-3.10.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.10.0
3
+ Version: 3.10.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
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "cc-session-tools"
7
- version = "3.10.0"
7
+ version = "3.10.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" }
@@ -2,6 +2,7 @@
2
2
  from __future__ import annotations
3
3
 
4
4
  import time
5
+ from collections.abc import Callable
5
6
  from pathlib import Path
6
7
 
7
8
  from cc_session_tools.lib.pdata.init_paths import (
@@ -59,6 +60,7 @@ def _write_pointer_file(*, project_root: Path, project: str, entry: ManifestEntr
59
60
  def archive_entries(
60
61
  *, project_root: Path, entries: list[ManifestEntry], project: str,
61
62
  write_pointer_files: bool = True,
63
+ on_archived: Callable[[ManifestEntry], None] | None = None,
62
64
  ) -> None:
63
65
  """Move every db-owned entry's source file into project_root/.pdata-migrated/,
64
66
  preserving its relative path, and append one line per entry to MANIFEST.md.
@@ -69,7 +71,11 @@ def archive_entries(
69
71
  Markdown pointer file back at each entry's original path so anything else in the
70
72
  project that cites the old path by name finds a live signpost instead of a silent
71
73
  404. Pass write_pointer_files=False (--leave-no-pointer-files at the CLI) to
72
- suppress this."""
74
+ suppress this.
75
+
76
+ `on_archived`, if given, is called with each entry immediately after its source has been
77
+ moved (before its log line and pointer file are written), so a caller that sees this
78
+ function raise part-way knows exactly which entries were already cut over."""
73
79
  if not entries:
74
80
  return
75
81
  archive_root = project_root / MIGRATED_ARCHIVE_DIRNAME
@@ -82,6 +88,8 @@ def archive_entries(
82
88
  destination = archive_root / entry.path
83
89
  destination.parent.mkdir(parents=True, exist_ok=True)
84
90
  source.rename(destination)
91
+ if on_archived is not None:
92
+ on_archived(entry)
85
93
  log.write(
86
94
  f"- {now} — {entry.path} — migrated source, superseded by ccst pdata "
87
95
  f"(record_group={entry.db_group()})\n"
@@ -213,6 +213,69 @@ def _rollback(*, project: str, created_ids: list[int]) -> list[str]:
213
213
  return rollback_failures
214
214
 
215
215
 
216
+ def _rollback_after_error(
217
+ exc: BaseException, *, project: str, created_ids: list[int],
218
+ on_progress: Callable[[str], None] | None,
219
+ ) -> None:
220
+ """Roll back `created_ids` after an error `write()` did not anticipate, without ever
221
+ replacing `exc` (the caller re-raises it): a rollback that itself fails is reported through
222
+ `on_progress` and attached to `exc` as a note naming every record id still live, so the
223
+ manual cleanup the failure implies is not a hunt."""
224
+ if not created_ids:
225
+ return
226
+ try:
227
+ failures = _rollback(project=project, created_ids=created_ids)
228
+ except Exception as rollback_exc:
229
+ note = (
230
+ f"rollback of {len(created_ids)} inserted row(s) aborted: {rollback_exc!r} - rows "
231
+ f"from this run may still be live; record ids: {created_ids}"
232
+ )
233
+ exc.add_note(note)
234
+ _emit(on_progress, note)
235
+ return
236
+ rolled_back = len(created_ids) - len(failures)
237
+ _emit(
238
+ on_progress,
239
+ f"Unexpected {type(exc).__name__} - rolled back {rolled_back} of "
240
+ f"{len(created_ids)} inserted row(s)",
241
+ )
242
+ if failures:
243
+ exc.add_note("rows from this run still live:\n" + "\n".join(failures))
244
+ for failure in failures:
245
+ _emit(on_progress, f" {failure}")
246
+
247
+
248
+ def _recover_from_failed_cutover(
249
+ exc: BaseException, *, project: str, m: Manifest, proposal_path: Path,
250
+ written_entries: list[ManifestEntry], archived: list[ManifestEntry],
251
+ entry_rows: dict[str, list[tuple[int, ImportRow]]],
252
+ on_progress: Callable[[str], None] | None,
253
+ ) -> None:
254
+ """Cutover failed part-way. An entry whose source was already moved into the archive is cut
255
+ over: its rows are its live copy, so they stay and the entry is marked migrated (a rerun then
256
+ skips it). An entry still in place has its rows rolled back, so a rerun imports it once
257
+ rather than twice."""
258
+ if archived:
259
+ stamp = datetime.now(UTC).strftime("%Y-%m-%dT%H:%M:%SZ")
260
+ for entry in archived:
261
+ entry.migrated_at = stamp
262
+ try:
263
+ manifest.save(m, proposal_path)
264
+ except Exception as save_exc:
265
+ exc.add_note(
266
+ f"could not record {len(archived)} already cut-over entr(ies) as migrated in "
267
+ f"{proposal_path}: {save_exc!r}; mark these migrated by hand before re-running: "
268
+ f"{', '.join(e.path for e in archived)}"
269
+ )
270
+ archived_paths = {e.path for e in archived}
271
+ pending_ids = [
272
+ record_id
273
+ for entry in written_entries if entry.path not in archived_paths
274
+ for record_id, _ in entry_rows[entry.path]
275
+ ]
276
+ _rollback_after_error(exc, project=project, created_ids=pending_ids, on_progress=on_progress)
277
+
278
+
216
279
  def _check_db_not_locked(project: str) -> None:
217
280
  """Pre-flight concurrency guard (inter-session message 20260819T114156Z-3ec7,
218
281
  gap #2): refuse to start --write if another connection already holds a write
@@ -432,105 +495,125 @@ def write(
432
495
  db_owned = [e for e in m.entries if e.classification == "db-owned"]
433
496
  already_migrated = [e for e in db_owned if e.migrated_at is not None]
434
497
  to_import = [e for e in db_owned if e.migrated_at is None]
435
- _emit(on_progress, f"Importing {len(to_import)} file(s)...")
436
- if already_migrated:
437
- _emit(
438
- on_progress,
439
- f" skipping {len(already_migrated)} already-migrated file(s): "
440
- f"{', '.join(e.path for e in already_migrated)}",
498
+ # Everything from the first insert through the backup either returns a WriteResult
499
+ # (rolling back by hand) or, for any exception this code did not anticipate, rolls the
500
+ # inserted rows back here before re-raising - never leaves them live with the manifest
501
+ # unmarked and cutover unrun.
502
+ try:
503
+ _emit(on_progress, f"Importing {len(to_import)} file(s)...")
504
+ if already_migrated:
505
+ _emit(
506
+ on_progress,
507
+ f" skipping {len(already_migrated)} already-migrated file(s): "
508
+ f"{', '.join(e.path for e in already_migrated)}",
509
+ )
510
+ for entry in to_import:
511
+ try:
512
+ source_path = project_root / entry.path
513
+ if not source_path.exists():
514
+ # entry.migrated_at is None here (to_import excludes already-migrated
515
+ # entries above), so this is not the ordinary "already cut over by an
516
+ # earlier --write" case — the source is genuinely missing for an entry
517
+ # this run believes still needs importing. Fail with a specific,
518
+ # actionable message (still caught by this loop's own except clause
519
+ # below, so it contributes to `reasons`/rollback exactly like any other
520
+ # per-entry import failure) instead of a bare FileNotFoundError only
521
+ # discoverable via the rollback reasons list.
522
+ raise ValueError(
523
+ f"{entry.path}: marked db-owned with no migrated_at, but its source "
524
+ f"file does not exist at {source_path} — if this entry was already "
525
+ f"migrated by another means, set its classification to folder-owned "
526
+ f"(or otherwise correct the manifest) before running --write again"
527
+ )
528
+ _emit(on_progress, f" importing {entry.path} -> group={entry.db_group()}...")
529
+ for spec in entry.fields:
530
+ service.schema_add_field(
531
+ project=project, record_group=entry.db_group(),
532
+ field_name=spec.name, sql_type=spec.sql_type,
533
+ description=spec.description, default=spec.default,
534
+ )
535
+ rows_for_entry: list[tuple[int, ImportRow]] = []
536
+ for row in import_entry(project_root, entry):
537
+ record = service.add_record(
538
+ project=project, record_group=entry.db_group(),
539
+ content=row.content, file_path=row.file_path,
540
+ fields=row.fields, created_at=row.created_at,
541
+ )
542
+ created_ids.append(record.id)
543
+ rows_for_entry.append((record.id, row))
544
+ entry_rows[entry.path] = rows_for_entry
545
+ _emit(on_progress, f" {len(rows_for_entry)} row(s) imported")
546
+ written_entries.append(entry)
547
+ except (ValueError, OSError, csv.Error) as exc:
548
+ reasons.append(f"{entry.path}: {exc}")
549
+
550
+ _emit(on_progress, "Verifying imported rows...")
551
+ reasons.extend(
552
+ _verify(project=project, project_root=project_root,
553
+ written_entries=written_entries, entry_rows=entry_rows)
441
554
  )
442
- for entry in to_import:
443
- try:
444
- source_path = project_root / entry.path
445
- if not source_path.exists():
446
- # entry.migrated_at is None here (to_import excludes already-migrated
447
- # entries above), so this is not the ordinary "already cut over by an
448
- # earlier --write" case — the source is genuinely missing for an entry
449
- # this run believes still needs importing. Fail with a specific,
450
- # actionable message (still caught by this loop's own except clause
451
- # below, so it contributes to `reasons`/rollback exactly like any other
452
- # per-entry import failure) instead of a bare FileNotFoundError only
453
- # discoverable via the rollback reasons list.
454
- raise ValueError(
455
- f"{entry.path}: marked db-owned with no migrated_at, but its source "
456
- f"file does not exist at {source_path} — if this entry was already "
457
- f"migrated by another means, set its classification to folder-owned "
458
- f"(or otherwise correct the manifest) before running --write again"
459
- )
460
- _emit(on_progress, f" importing {entry.path} -> group={entry.db_group()}...")
461
- for spec in entry.fields:
462
- service.schema_add_field(
463
- project=project, record_group=entry.db_group(),
464
- field_name=spec.name, sql_type=spec.sql_type,
465
- description=spec.description, default=spec.default,
466
- )
467
- rows_for_entry: list[tuple[int, ImportRow]] = []
468
- for row in import_entry(project_root, entry):
469
- record = service.add_record(
470
- project=project, record_group=entry.db_group(),
471
- content=row.content, file_path=row.file_path,
472
- fields=row.fields, created_at=row.created_at,
473
- )
474
- created_ids.append(record.id)
475
- rows_for_entry.append((record.id, row))
476
- entry_rows[entry.path] = rows_for_entry
477
- _emit(on_progress, f" {len(rows_for_entry)} row(s) imported")
478
- written_entries.append(entry)
479
- except (ValueError, OSError, csv.Error) as exc:
480
- reasons.append(f"{entry.path}: {exc}")
481
-
482
- _emit(on_progress, "Verifying imported rows...")
483
- reasons.extend(
484
- _verify(project=project, project_root=project_root,
485
- written_entries=written_entries, entry_rows=entry_rows)
486
- )
487
555
 
488
- if reasons:
489
- _emit(on_progress, "Verification failed — rolling back inserted rows...")
490
- return WriteResult(
491
- created_record_ids=[], entries_written=[], backup_path=None,
492
- failure=WriteFailure(
493
- reasons=reasons + _rollback(project=project, created_ids=created_ids)
494
- ),
556
+ if reasons:
557
+ _emit(on_progress, "Verification failed — rolling back inserted rows...")
558
+ return WriteResult(
559
+ created_record_ids=[], entries_written=[], backup_path=None,
560
+ failure=WriteFailure(
561
+ reasons=reasons + _rollback(project=project, created_ids=created_ids)
562
+ ),
563
+ )
564
+
565
+ # Still inside both overrides: a rehearsed run's backup must land in the
566
+ # rehearsal sandbox (backup_dir_override), never in the real backup dir.
567
+ _emit(on_progress, "Backing up project and database before cutover...")
568
+ try:
569
+ backup_path = backup.create_backup(
570
+ project=project, project_root=project_root, on_progress=on_progress,
571
+ )
572
+ _emit(on_progress, f"Backup written: {backup_path}")
573
+ except backup.BackupError as exc:
574
+ _emit(on_progress, "Backup failed — rolling back inserted rows...")
575
+ return WriteResult(
576
+ created_record_ids=[], entries_written=[], backup_path=None,
577
+ failure=WriteFailure(
578
+ reasons=[str(exc)] + _rollback(project=project, created_ids=created_ids)
579
+ ),
580
+ )
581
+ except BaseException as exc:
582
+ _rollback_after_error(
583
+ exc, project=project, created_ids=created_ids, on_progress=on_progress,
495
584
  )
585
+ raise
496
586
 
497
- # Still inside both overrides: a rehearsed run's backup must land in the
498
- # rehearsal sandbox (backup_dir_override), never in the real backup dir.
499
- _emit(on_progress, "Backing up project and database before cutover...")
587
+ _emit(on_progress, f"Cutting over {len(written_entries)} file(s)...")
588
+ archived: list[ManifestEntry] = []
500
589
  try:
501
- backup_path = backup.create_backup(
502
- project=project, project_root=project_root, on_progress=on_progress,
590
+ cutover.archive_entries(
591
+ project_root=project_root, entries=written_entries, project=project,
592
+ write_pointer_files=not leave_no_pointer_files, on_archived=archived.append,
503
593
  )
504
- _emit(on_progress, f"Backup written: {backup_path}")
505
- except backup.BackupError as exc:
506
- _emit(on_progress, "Backup failed — rolling back inserted rows...")
507
- return WriteResult(
508
- created_record_ids=[], entries_written=[], backup_path=None,
509
- failure=WriteFailure(
510
- reasons=[str(exc)] + _rollback(project=project, created_ids=created_ids)
511
- ),
594
+ # Mark every entry this run actually cut over as migrated, and persist that back to
595
+ # the manifest — write() only ever `manifest.load`s otherwise, so without this a
596
+ # later --write would re-attempt (and, pre-fix, roll back) these same now-archived
597
+ # entries. written_entries holds the same ManifestEntry objects m.entries does (see
598
+ # the `to_import`/`written_entries.append(entry)` loop above), so mutating here
599
+ # mutates m.
600
+ migrated_at = datetime.now(UTC).strftime("%Y-%m-%dT%H:%M:%SZ")
601
+ for entry in written_entries:
602
+ entry.migrated_at = migrated_at
603
+ manifest.save(m, proposal_path)
604
+ except BaseException as exc:
605
+ _recover_from_failed_cutover(
606
+ exc, project=project, m=m, proposal_path=proposal_path,
607
+ written_entries=written_entries, archived=archived, entry_rows=entry_rows,
608
+ on_progress=on_progress,
512
609
  )
513
-
514
- _emit(on_progress, f"Cutting over {len(written_entries)} file(s)...")
515
- cutover.archive_entries(
516
- project_root=project_root, entries=written_entries, project=project,
517
- write_pointer_files=not leave_no_pointer_files,
518
- )
519
- # Mark every entry this run actually cut over as migrated, and persist that back to
520
- # the manifest — write() only ever `manifest.load`s otherwise, so without this a later
521
- # --write would re-attempt (and, pre-fix, roll back) these same now-archived entries.
522
- # written_entries holds the same ManifestEntry objects m.entries does (see the
523
- # `to_import`/`written_entries.append(entry)` loop above), so mutating here mutates m.
524
- migrated_at = datetime.now(UTC).strftime("%Y-%m-%dT%H:%M:%SZ")
525
- for entry in written_entries:
526
- entry.migrated_at = migrated_at
527
- manifest.save(m, proposal_path)
528
- return WriteResult(
529
- created_record_ids=created_ids,
530
- entries_written=[e.path for e in written_entries],
531
- backup_path=backup_path, failure=None,
532
- report=_render_diff_report(written_entries=written_entries, entry_rows=entry_rows),
533
- )
610
+ raise
611
+ return WriteResult(
612
+ created_record_ids=created_ids,
613
+ entries_written=[e.path for e in written_entries],
614
+ backup_path=backup_path, failure=None,
615
+ report=_render_diff_report(written_entries=written_entries, entry_rows=entry_rows),
616
+ )
534
617
 
535
618
 
536
619
  def _verify(
@@ -544,12 +627,18 @@ def _verify(
544
627
  reasons: list[str] = []
545
628
  for entry in written_entries:
546
629
  rows = entry_rows[entry.path]
547
- expected_count = count_source_rows(project_root, entry)
548
- if len(rows) != expected_count:
630
+ try:
631
+ expected_count = count_source_rows(project_root, entry)
632
+ except (ValueError, OSError, csv.Error) as exc:
549
633
  reasons.append(
550
- f"{entry.path}: imported {len(rows)} row(s) but re-counting the source "
551
- f"gives {expected_count} — entry-count parity check failed"
634
+ f"{entry.path}: could not re-count the source rows for the parity check: {exc}"
552
635
  )
636
+ else:
637
+ if len(rows) != expected_count:
638
+ reasons.append(
639
+ f"{entry.path}: imported {len(rows)} row(s) but re-counting the source "
640
+ f"gives {expected_count} — entry-count parity check failed"
641
+ )
553
642
  for record_id, import_row in rows:
554
643
  record = service.get_record(project=project, record_id=record_id)
555
644
  assert record is not None, (
@@ -562,7 +651,17 @@ def _verify(
562
651
  )
563
652
  if record.file_path:
564
653
  target = project_root / record.file_path
565
- if not target.exists():
654
+ try:
655
+ resolves = target.exists()
656
+ except OSError as exc:
657
+ # A string the OS rejects outright (e.g. "File name too long") raises
658
+ # instead of returning False; it is the same "does not resolve" outcome.
659
+ reasons.append(
660
+ f"{entry.path}: record {record_id} file_path "
661
+ f"{record.file_path!r} cannot be checked under {project_root}: {exc}"
662
+ )
663
+ continue
664
+ if not resolves:
566
665
  reasons.append(
567
666
  f"{entry.path}: record {record_id} file_path "
568
667
  f"{record.file_path!r} does not resolve under {project_root}"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: cc-session-tools
3
- Version: 3.10.0
3
+ Version: 3.10.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
@@ -5,8 +5,8 @@ source of truth this table is migrating from). Consumed by
5
5
  context_window_warning to phrase its nudge in terms of "% of the model's
6
6
  actual window" and to estimate a per-model cache-read cost.
7
7
 
8
- Figures current as of 2026-08-13 (Claude 5 family). Context windows: Fable 5 /
9
- Mythos 5 / Opus 5 / Opus 4.6-4.8 / Sonnet 5 / Sonnet 4.6 all 1,000,000 tokens
8
+ Figures current as of 2026-09-29 (Claude 5 family; 5.5/5.1 ids added). Context windows: Fable 5 /
9
+ Mythos 5 / Opus 5 / Opus 4.6-4.8 / Sonnet 5 / Sonnet 5.5 / Opus 5.5 / Fable 5.1 / Sonnet 4.6 all 1,000,000 tokens
10
10
  (the default, not a beta long-context mode); Haiku 4.5 stays at 200,000.
11
11
  Prices are the standard (non-cached) input rate per million tokens.
12
12
 
@@ -25,50 +25,55 @@ design spec's section 2.
25
25
  """
26
26
  from __future__ import annotations
27
27
 
28
+ import re
29
+
28
30
  _WINDOW_1M = {
29
31
  "claude-fable-5", "claude-mythos-5",
30
32
  "claude-opus-5", "claude-opus-4-8", "claude-opus-4-7", "claude-opus-4-6",
31
33
  "claude-sonnet-5", "claude-sonnet-4-6",
34
+ "claude-sonnet-5-5", "claude-opus-5-5", "claude-fable-5-1",
32
35
  }
33
36
  _PRICE = {
34
37
  "claude-fable-5": 10.00, "claude-mythos-5": 10.00,
35
38
  "claude-opus-5": 5.00, "claude-opus-4-8": 5.00, "claude-opus-4-7": 5.00, "claude-opus-4-6": 5.00,
36
39
  "claude-sonnet-5": 3.00, "claude-sonnet-4-6": 3.00,
40
+ # Assumed equal to the family's existing price (no published figure for these ids); the
41
+ # price only feeds the rough "~$/turn in cache reads" estimate.
42
+ "claude-sonnet-5-5": 3.00, "claude-opus-5-5": 5.00, "claude-fable-5-1": 10.00,
43
+ "claude-haiku-4-5": 1.00,
37
44
  }
38
45
  _NAME = {
39
46
  "claude-fable-5": "Fable 5", "claude-mythos-5": "Mythos 5",
40
47
  "claude-opus-5": "Opus 5", "claude-opus-4-8": "Opus 4.8",
41
48
  "claude-opus-4-7": "Opus 4.7", "claude-opus-4-6": "Opus 4.6",
42
49
  "claude-sonnet-5": "Sonnet 5", "claude-sonnet-4-6": "Sonnet 4.6",
50
+ "claude-sonnet-5-5": "Sonnet 5.5", "claude-opus-5-5": "Opus 5.5",
51
+ "claude-fable-5-1": "Fable 5.1", "claude-haiku-4-5": "Haiku 4.5",
43
52
  }
44
53
  _DEFAULT_WINDOW = 200_000
45
54
  _DEFAULT_PRICE = 5.00
46
55
  _DEFAULT_NAME = "an unrecognized model"
47
56
 
48
57
 
49
- def _is_haiku_4_5(model_id: str) -> bool:
50
- return model_id.startswith("claude-haiku-4-5")
58
+ _DATED_SNAPSHOT = re.compile(r"(.+)-\d{8}")
59
+
60
+
61
+ def _canonical(model_id: str) -> str:
62
+ """A dated snapshot id (`<id>-YYYYMMDD`) resolves to its undated row. Applied once, and
63
+ only for an exactly-eight-digit suffix: any other suffix (or a snapshot of an unknown id)
64
+ stays unrecognised. `[1m]`/`-latest` are not handled - transcripts' `message.model` never
65
+ carries them."""
66
+ match = _DATED_SNAPSHOT.fullmatch(model_id)
67
+ return match.group(1) if match else model_id
51
68
 
52
69
 
53
70
  def context_window(model_id: str) -> int:
54
- if model_id in _WINDOW_1M:
55
- return 1_000_000
56
- if _is_haiku_4_5(model_id):
57
- return 200_000
58
- return _DEFAULT_WINDOW
71
+ return 1_000_000 if _canonical(model_id) in _WINDOW_1M else _DEFAULT_WINDOW
59
72
 
60
73
 
61
74
  def input_price_per_mtok(model_id: str) -> float:
62
- if model_id in _PRICE:
63
- return _PRICE[model_id]
64
- if _is_haiku_4_5(model_id):
65
- return 1.00
66
- return _DEFAULT_PRICE
75
+ return _PRICE.get(_canonical(model_id), _DEFAULT_PRICE)
67
76
 
68
77
 
69
78
  def display_name(model_id: str) -> str:
70
- if model_id in _NAME:
71
- return _NAME[model_id]
72
- if _is_haiku_4_5(model_id):
73
- return "Haiku 4.5"
74
- return _DEFAULT_NAME
79
+ return _NAME.get(_canonical(model_id), _DEFAULT_NAME)
@@ -180,3 +180,26 @@ def test_override_end_to_end_via_real_store(tmp_path, monkeypatch, capsys):
180
180
  out = capsys.readouterr()
181
181
  assert rc == 0
182
182
  assert out.out == ""
183
+
184
+
185
+ @pytest.mark.parametrize(
186
+ ("model_id", "name", "cost"),
187
+ [
188
+ ("claude-sonnet-5-5", "Sonnet 5.5", "0.07"),
189
+ ("claude-opus-5-5", "Opus 5.5", "0.13"),
190
+ ("claude-fable-5-1", "Fable 5.1", "0.26"),
191
+ ],
192
+ )
193
+ def test_new_5x_models_are_measured_against_their_one_million_token_window(
194
+ tmp_path, monkeypatch, capsys, model_id, name, cost
195
+ ):
196
+ p = tmp_path / "t.jsonl"
197
+ _write_transcript(p, [{"type": "assistant", "message": {"model": model_id,
198
+ "usage": {"input_tokens": 266000, "cache_creation_input_tokens": 0, "cache_read_input_tokens": 0}}}])
199
+ monkeypatch.setattr(cww, "_is_overridden", lambda session_id: False)
200
+ rc, out = _run_main(monkeypatch, capsys, {"transcript_path": str(p), "session_id": "s1"})
201
+ reason = json.loads(out.out)["reason"]
202
+ assert "~27% of the 1M-token window" in reason
203
+ assert name in reason
204
+ assert f"~${cost}/turn" in reason
205
+ assert "unrecognized" not in reason
@@ -0,0 +1,67 @@
1
+ from hooks import model_info
2
+
3
+ CASES = [
4
+ ("claude-fable-5", 1_000_000, 10.00, "Fable 5"),
5
+ ("claude-mythos-5", 1_000_000, 10.00, "Mythos 5"),
6
+ ("claude-opus-5", 1_000_000, 5.00, "Opus 5"),
7
+ ("claude-opus-4-8", 1_000_000, 5.00, "Opus 4.8"),
8
+ ("claude-opus-4-7", 1_000_000, 5.00, "Opus 4.7"),
9
+ ("claude-opus-4-6", 1_000_000, 5.00, "Opus 4.6"),
10
+ ("claude-sonnet-5", 1_000_000, 3.00, "Sonnet 5"),
11
+ ("claude-sonnet-4-6", 1_000_000, 3.00, "Sonnet 4.6"),
12
+ ("claude-sonnet-5-5", 1_000_000, 3.00, "Sonnet 5.5"),
13
+ ("claude-opus-5-5", 1_000_000, 5.00, "Opus 5.5"),
14
+ ("claude-fable-5-1", 1_000_000, 10.00, "Fable 5.1"),
15
+ ("claude-haiku-4-5-20251001", 200_000, 1.00, "Haiku 4.5"),
16
+ ]
17
+
18
+
19
+ def test_known_models():
20
+ for model_id, window, price, name in CASES:
21
+ assert model_info.context_window(model_id) == window
22
+ assert model_info.input_price_per_mtok(model_id) == price
23
+ assert model_info.display_name(model_id) == name
24
+
25
+
26
+ def test_unrecognized_model_falls_back():
27
+ assert model_info.context_window("claude-nonexistent-9") == 200_000
28
+ assert model_info.input_price_per_mtok("claude-nonexistent-9") == 5.00
29
+ assert model_info.display_name("claude-nonexistent-9") == "an unrecognized model"
30
+
31
+
32
+ def test_empty_model_id_falls_back():
33
+ assert model_info.context_window("") == 200_000
34
+
35
+
36
+ def test_dated_snapshot_ids_resolve_like_their_family_id():
37
+ """Claude Code can report a dated snapshot id (`<family>-YYYYMMDD`); it must resolve to the
38
+ same row as the undated id, not fall to the 200k default."""
39
+ for base, window, price, name in CASES:
40
+ if base.startswith("claude-haiku"):
41
+ continue
42
+ dated = f"{base}-20261001"
43
+ assert model_info.context_window(dated) == window
44
+ assert model_info.input_price_per_mtok(dated) == price
45
+ assert model_info.display_name(dated) == name
46
+
47
+
48
+ def test_unknown_dated_and_decorated_ids_stay_unrecognised():
49
+ for model_id in (
50
+ "claude-sonnet-5-9-20261001",
51
+ "claude-nonexistent-9-20261001",
52
+ "claude-sonnet-5-5-2026100", # 7 digits
53
+ "claude-sonnet-5-5-202610011", # 9 digits
54
+ "claude-sonnet-5-5-latest",
55
+ "claude-sonnet-5-5[1m]",
56
+ "claude-sonnet-5-5-20261001-20261002", # normalised once only
57
+ "claude-haiku-4-5-garbage",
58
+ ):
59
+ assert model_info.context_window(model_id) == 200_000, model_id
60
+ assert model_info.display_name(model_id) == "an unrecognized model", model_id
61
+
62
+
63
+ def test_id_that_merely_starts_with_a_known_id_is_not_recognised():
64
+ """Guards against a future lazy prefix implementation (already true before the fix):
65
+ `claude-sonnet-5-9` must not inherit `claude-sonnet-5`'s row."""
66
+ assert model_info.context_window("claude-sonnet-5-9") == 200_000
67
+ assert model_info.display_name("claude-sonnet-5-9") == "an unrecognized model"
@@ -1,30 +0,0 @@
1
- from hooks import model_info
2
-
3
- CASES = [
4
- ("claude-fable-5", 1_000_000, 10.00, "Fable 5"),
5
- ("claude-mythos-5", 1_000_000, 10.00, "Mythos 5"),
6
- ("claude-opus-5", 1_000_000, 5.00, "Opus 5"),
7
- ("claude-opus-4-8", 1_000_000, 5.00, "Opus 4.8"),
8
- ("claude-opus-4-7", 1_000_000, 5.00, "Opus 4.7"),
9
- ("claude-opus-4-6", 1_000_000, 5.00, "Opus 4.6"),
10
- ("claude-sonnet-5", 1_000_000, 3.00, "Sonnet 5"),
11
- ("claude-sonnet-4-6", 1_000_000, 3.00, "Sonnet 4.6"),
12
- ("claude-haiku-4-5-20251001", 200_000, 1.00, "Haiku 4.5"),
13
- ]
14
-
15
-
16
- def test_known_models():
17
- for model_id, window, price, name in CASES:
18
- assert model_info.context_window(model_id) == window
19
- assert model_info.input_price_per_mtok(model_id) == price
20
- assert model_info.display_name(model_id) == name
21
-
22
-
23
- def test_unrecognized_model_falls_back():
24
- assert model_info.context_window("claude-nonexistent-9") == 200_000
25
- assert model_info.input_price_per_mtok("claude-nonexistent-9") == 5.00
26
- assert model_info.display_name("claude-nonexistent-9") == "an unrecognized model"
27
-
28
-
29
- def test_empty_model_id_falls_back():
30
- assert model_info.context_window("") == 200_000