cc-session-tools 3.16.2__tar.gz → 4.0.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (315) hide show
  1. {cc_session_tools-3.16.2/src/cc_session_tools.egg-info → cc_session_tools-4.0.0}/PKG-INFO +5 -1
  2. {cc_session_tools-3.16.2 → cc_session_tools-4.0.0}/README.md +4 -0
  3. {cc_session_tools-3.16.2 → cc_session_tools-4.0.0}/pyproject.toml +1 -1
  4. {cc_session_tools-3.16.2 → cc_session_tools-4.0.0}/src/cc_session_tools/cli/ccst.py +315 -85
  5. {cc_session_tools-3.16.2 → cc_session_tools-4.0.0}/src/cc_session_tools/lib/doctor.py +93 -1
  6. {cc_session_tools-3.16.2 → cc_session_tools-4.0.0}/src/cc_session_tools/lib/hook_registry.py +1 -1
  7. {cc_session_tools-3.16.2 → cc_session_tools-4.0.0}/src/cc_session_tools/lib/pdata/dump.py +165 -31
  8. cc_session_tools-4.0.0/src/cc_session_tools/lib/pdata/formats.py +186 -0
  9. cc_session_tools-4.0.0/src/cc_session_tools/lib/pdata/ids.py +38 -0
  10. {cc_session_tools-3.16.2 → cc_session_tools-4.0.0}/src/cc_session_tools/lib/pdata/init_service.py +45 -19
  11. cc_session_tools-4.0.0/src/cc_session_tools/lib/pdata/migrate_uuid.py +907 -0
  12. {cc_session_tools-3.16.2 → cc_session_tools-4.0.0}/src/cc_session_tools/lib/pdata/rehydrate.py +91 -5
  13. {cc_session_tools-3.16.2 → cc_session_tools-4.0.0}/src/cc_session_tools/lib/pdata/repository.py +53 -26
  14. {cc_session_tools-3.16.2 → cc_session_tools-4.0.0}/src/cc_session_tools/lib/pdata/resolve.py +69 -89
  15. {cc_session_tools-3.16.2 → cc_session_tools-4.0.0}/src/cc_session_tools/lib/pdata/service.py +10 -6
  16. {cc_session_tools-3.16.2 → cc_session_tools-4.0.0}/src/cc_session_tools/lib/pdata/session_output.py +53 -5
  17. {cc_session_tools-3.16.2 → cc_session_tools-4.0.0}/src/cc_session_tools/lib/pdata/sync_check.py +61 -1
  18. cc_session_tools-4.0.0/src/cc_session_tools/lib/pdata/vector_clock_store.py +113 -0
  19. {cc_session_tools-3.16.2 → cc_session_tools-4.0.0}/src/cc_session_tools/lib/pdata/verify.py +19 -7
  20. {cc_session_tools-3.16.2 → cc_session_tools-4.0.0}/src/cc_session_tools/skills/pm-pdata-do-init/SKILL.md +3 -2
  21. {cc_session_tools-3.16.2 → cc_session_tools-4.0.0}/src/cc_session_tools/skills/pm-pdata-do-manual-migration/SKILL.md +2 -1
  22. {cc_session_tools-3.16.2 → cc_session_tools-4.0.0}/src/cc_session_tools/skills/pm-pdata-do-update-consuming-skills/SKILL.md +2 -1
  23. {cc_session_tools-3.16.2 → cc_session_tools-4.0.0}/src/cc_session_tools/skills/pm-pdata-do-update-project-docs/SKILL.md +2 -1
  24. {cc_session_tools-3.16.2 → cc_session_tools-4.0.0}/src/cc_session_tools/skills/pm-pdata-resolve-conflicts/SKILL.md +26 -36
  25. {cc_session_tools-3.16.2 → cc_session_tools-4.0.0/src/cc_session_tools.egg-info}/PKG-INFO +5 -1
  26. {cc_session_tools-3.16.2 → cc_session_tools-4.0.0}/src/cc_session_tools.egg-info/SOURCES.txt +6 -0
  27. {cc_session_tools-3.16.2 → cc_session_tools-4.0.0}/src/hooks/pdata_sync.py +13 -3
  28. {cc_session_tools-3.16.2 → cc_session_tools-4.0.0}/src/hooks/pending_migration.py +27 -6
  29. {cc_session_tools-3.16.2 → cc_session_tools-4.0.0}/tests/test_ccst_doctor.py +172 -0
  30. {cc_session_tools-3.16.2 → cc_session_tools-4.0.0}/tests/test_ccst_pdata_cli.py +15 -11
  31. {cc_session_tools-3.16.2 → cc_session_tools-4.0.0}/tests/test_ccst_pdata_init_cli.py +54 -0
  32. cc_session_tools-4.0.0/tests/test_ccst_pdata_migrate_uuid_cli.py +175 -0
  33. {cc_session_tools-3.16.2 → cc_session_tools-4.0.0}/tests/test_ccst_pdata_sync_cli.py +47 -13
  34. {cc_session_tools-3.16.2 → cc_session_tools-4.0.0}/tests/test_date_format_conformance.py +2 -0
  35. cc_session_tools-4.0.0/tests/test_install_everything_migration_banner.py +151 -0
  36. cc_session_tools-4.0.0/tests/test_pdata_uuid_migration_runbook.py +92 -0
  37. {cc_session_tools-3.16.2 → cc_session_tools-4.0.0}/tests/test_pending_migration_hook.py +106 -0
  38. cc_session_tools-3.16.2/src/cc_session_tools/lib/pdata/vector_clock_store.py +0 -34
  39. {cc_session_tools-3.16.2 → cc_session_tools-4.0.0}/LICENSE +0 -0
  40. {cc_session_tools-3.16.2 → cc_session_tools-4.0.0}/setup.cfg +0 -0
  41. {cc_session_tools-3.16.2 → cc_session_tools-4.0.0}/src/cc_session_tools/__init__.py +0 -0
  42. {cc_session_tools-3.16.2 → cc_session_tools-4.0.0}/src/cc_session_tools/cli/__init__.py +0 -0
  43. {cc_session_tools-3.16.2 → cc_session_tools-4.0.0}/src/cc_session_tools/cli/ccd.py +0 -0
  44. {cc_session_tools-3.16.2 → cc_session_tools-4.0.0}/src/cc_session_tools/cli/ccmsg.py +0 -0
  45. {cc_session_tools-3.16.2 → cc_session_tools-4.0.0}/src/cc_session_tools/cli/ccr.py +0 -0
  46. {cc_session_tools-3.16.2 → cc_session_tools-4.0.0}/src/cc_session_tools/cli/ccs.py +0 -0
  47. {cc_session_tools-3.16.2 → cc_session_tools-4.0.0}/src/cc_session_tools/cli/ccsched.py +0 -0
  48. {cc_session_tools-3.16.2 → cc_session_tools-4.0.0}/src/cc_session_tools/cli/migrate_ccmsg.py +0 -0
  49. {cc_session_tools-3.16.2 → cc_session_tools-4.0.0}/src/cc_session_tools/cli/migrate_ccsched.py +0 -0
  50. {cc_session_tools-3.16.2 → cc_session_tools-4.0.0}/src/cc_session_tools/cli/migrate_sessions_db.py +0 -0
  51. {cc_session_tools-3.16.2 → cc_session_tools-4.0.0}/src/cc_session_tools/cli/migrate_telemetry.py +0 -0
  52. {cc_session_tools-3.16.2 → cc_session_tools-4.0.0}/src/cc_session_tools/config/api-keys.example +0 -0
  53. {cc_session_tools-3.16.2 → cc_session_tools-4.0.0}/src/cc_session_tools/config/hooks-bundle.json +0 -0
  54. {cc_session_tools-3.16.2 → cc_session_tools-4.0.0}/src/cc_session_tools/config/session-tag-hook.json +0 -0
  55. {cc_session_tools-3.16.2 → cc_session_tools-4.0.0}/src/cc_session_tools/hooks_install.py +0 -0
  56. {cc_session_tools-3.16.2 → cc_session_tools-4.0.0}/src/cc_session_tools/lib/__init__.py +0 -0
  57. {cc_session_tools-3.16.2 → cc_session_tools-4.0.0}/src/cc_session_tools/lib/api_keys.py +0 -0
  58. {cc_session_tools-3.16.2 → cc_session_tools-4.0.0}/src/cc_session_tools/lib/bun_tools.py +0 -0
  59. {cc_session_tools-3.16.2 → cc_session_tools-4.0.0}/src/cc_session_tools/lib/claude_flags.py +0 -0
  60. {cc_session_tools-3.16.2 → cc_session_tools-4.0.0}/src/cc_session_tools/lib/claude_md_install.py +0 -0
  61. {cc_session_tools-3.16.2 → cc_session_tools-4.0.0}/src/cc_session_tools/lib/claude_md_store.py +0 -0
  62. {cc_session_tools-3.16.2 → cc_session_tools-4.0.0}/src/cc_session_tools/lib/context_overrides.py +0 -0
  63. {cc_session_tools-3.16.2 → cc_session_tools-4.0.0}/src/cc_session_tools/lib/db.py +0 -0
  64. {cc_session_tools-3.16.2 → cc_session_tools-4.0.0}/src/cc_session_tools/lib/debug.py +0 -0
  65. {cc_session_tools-3.16.2 → cc_session_tools-4.0.0}/src/cc_session_tools/lib/doctor_mutes.py +0 -0
  66. {cc_session_tools-3.16.2 → cc_session_tools-4.0.0}/src/cc_session_tools/lib/install_sync.py +0 -0
  67. {cc_session_tools-3.16.2 → cc_session_tools-4.0.0}/src/cc_session_tools/lib/levenshtein.py +0 -0
  68. {cc_session_tools-3.16.2 → cc_session_tools-4.0.0}/src/cc_session_tools/lib/machine_identity.py +0 -0
  69. {cc_session_tools-3.16.2 → cc_session_tools-4.0.0}/src/cc_session_tools/lib/messaging/__init__.py +0 -0
  70. {cc_session_tools-3.16.2 → cc_session_tools-4.0.0}/src/cc_session_tools/lib/messaging/addressing.py +0 -0
  71. {cc_session_tools-3.16.2 → cc_session_tools-4.0.0}/src/cc_session_tools/lib/messaging/cursor.py +0 -0
  72. {cc_session_tools-3.16.2 → cc_session_tools-4.0.0}/src/cc_session_tools/lib/messaging/lock.py +0 -0
  73. {cc_session_tools-3.16.2 → cc_session_tools-4.0.0}/src/cc_session_tools/lib/messaging/message.py +0 -0
  74. {cc_session_tools-3.16.2 → cc_session_tools-4.0.0}/src/cc_session_tools/lib/messaging/move_safety.py +0 -0
  75. {cc_session_tools-3.16.2 → cc_session_tools-4.0.0}/src/cc_session_tools/lib/messaging/repository.py +0 -0
  76. {cc_session_tools-3.16.2 → cc_session_tools-4.0.0}/src/cc_session_tools/lib/messaging/retention.py +0 -0
  77. {cc_session_tools-3.16.2 → cc_session_tools-4.0.0}/src/cc_session_tools/lib/messaging/service.py +0 -0
  78. {cc_session_tools-3.16.2 → cc_session_tools-4.0.0}/src/cc_session_tools/lib/messaging/session_ref.py +0 -0
  79. {cc_session_tools-3.16.2 → cc_session_tools-4.0.0}/src/cc_session_tools/lib/messaging/store.py +0 -0
  80. {cc_session_tools-3.16.2 → cc_session_tools-4.0.0}/src/cc_session_tools/lib/occupancy.py +0 -0
  81. {cc_session_tools-3.16.2 → cc_session_tools-4.0.0}/src/cc_session_tools/lib/paths.py +0 -0
  82. {cc_session_tools-3.16.2 → cc_session_tools-4.0.0}/src/cc_session_tools/lib/pdata/__init__.py +0 -0
  83. {cc_session_tools-3.16.2 → cc_session_tools-4.0.0}/src/cc_session_tools/lib/pdata/backup.py +0 -0
  84. {cc_session_tools-3.16.2 → cc_session_tools-4.0.0}/src/cc_session_tools/lib/pdata/classify.py +0 -0
  85. {cc_session_tools-3.16.2 → cc_session_tools-4.0.0}/src/cc_session_tools/lib/pdata/cutover.py +0 -0
  86. {cc_session_tools-3.16.2 → cc_session_tools-4.0.0}/src/cc_session_tools/lib/pdata/formatting.py +0 -0
  87. {cc_session_tools-3.16.2 → cc_session_tools-4.0.0}/src/cc_session_tools/lib/pdata/importers.py +0 -0
  88. {cc_session_tools-3.16.2 → cc_session_tools-4.0.0}/src/cc_session_tools/lib/pdata/init_paths.py +0 -0
  89. {cc_session_tools-3.16.2 → cc_session_tools-4.0.0}/src/cc_session_tools/lib/pdata/manifest.py +0 -0
  90. {cc_session_tools-3.16.2 → cc_session_tools-4.0.0}/src/cc_session_tools/lib/pdata/naming.py +0 -0
  91. {cc_session_tools-3.16.2 → cc_session_tools-4.0.0}/src/cc_session_tools/lib/pdata/project_claude_md.py +0 -0
  92. {cc_session_tools-3.16.2 → cc_session_tools-4.0.0}/src/cc_session_tools/lib/pdata/readiness.py +0 -0
  93. {cc_session_tools-3.16.2 → cc_session_tools-4.0.0}/src/cc_session_tools/lib/pdata/rename_group.py +0 -0
  94. {cc_session_tools-3.16.2 → cc_session_tools-4.0.0}/src/cc_session_tools/lib/pdata/reorganize.py +0 -0
  95. {cc_session_tools-3.16.2 → cc_session_tools-4.0.0}/src/cc_session_tools/lib/pdata/store.py +0 -0
  96. {cc_session_tools-3.16.2 → cc_session_tools-4.0.0}/src/cc_session_tools/lib/pdata/sync_lock.py +0 -0
  97. {cc_session_tools-3.16.2 → cc_session_tools-4.0.0}/src/cc_session_tools/lib/pdata/sync_notify.py +0 -0
  98. {cc_session_tools-3.16.2 → cc_session_tools-4.0.0}/src/cc_session_tools/lib/pdata/vector_clock.py +0 -0
  99. {cc_session_tools-3.16.2 → cc_session_tools-4.0.0}/src/cc_session_tools/lib/pdata/write_log.py +0 -0
  100. {cc_session_tools-3.16.2 → cc_session_tools-4.0.0}/src/cc_session_tools/lib/picker.py +0 -0
  101. {cc_session_tools-3.16.2 → cc_session_tools-4.0.0}/src/cc_session_tools/lib/proc_lock.py +0 -0
  102. {cc_session_tools-3.16.2 → cc_session_tools-4.0.0}/src/cc_session_tools/lib/prompts.py +0 -0
  103. {cc_session_tools-3.16.2 → cc_session_tools-4.0.0}/src/cc_session_tools/lib/roots.py +0 -0
  104. {cc_session_tools-3.16.2 → cc_session_tools-4.0.0}/src/cc_session_tools/lib/rules.py +0 -0
  105. {cc_session_tools-3.16.2 → cc_session_tools-4.0.0}/src/cc_session_tools/lib/scheduler/__init__.py +0 -0
  106. {cc_session_tools-3.16.2 → cc_session_tools-4.0.0}/src/cc_session_tools/lib/scheduler/bundled_jobs.py +0 -0
  107. {cc_session_tools-3.16.2 → cc_session_tools-4.0.0}/src/cc_session_tools/lib/scheduler/cadence.py +0 -0
  108. {cc_session_tools-3.16.2 → cc_session_tools-4.0.0}/src/cc_session_tools/lib/scheduler/cursor.py +0 -0
  109. {cc_session_tools-3.16.2 → cc_session_tools-4.0.0}/src/cc_session_tools/lib/scheduler/digest.py +0 -0
  110. {cc_session_tools-3.16.2 → cc_session_tools-4.0.0}/src/cc_session_tools/lib/scheduler/due.py +0 -0
  111. {cc_session_tools-3.16.2 → cc_session_tools-4.0.0}/src/cc_session_tools/lib/scheduler/duration.py +0 -0
  112. {cc_session_tools-3.16.2 → cc_session_tools-4.0.0}/src/cc_session_tools/lib/scheduler/jobspec.py +0 -0
  113. {cc_session_tools-3.16.2 → cc_session_tools-4.0.0}/src/cc_session_tools/lib/scheduler/ledger.py +0 -0
  114. {cc_session_tools-3.16.2 → cc_session_tools-4.0.0}/src/cc_session_tools/lib/scheduler/lock.py +0 -0
  115. {cc_session_tools-3.16.2 → cc_session_tools-4.0.0}/src/cc_session_tools/lib/scheduler/notify.py +0 -0
  116. {cc_session_tools-3.16.2 → cc_session_tools-4.0.0}/src/cc_session_tools/lib/scheduler/reconcile.py +0 -0
  117. {cc_session_tools-3.16.2 → cc_session_tools-4.0.0}/src/cc_session_tools/lib/scheduler/registry.py +0 -0
  118. {cc_session_tools-3.16.2 → cc_session_tools-4.0.0}/src/cc_session_tools/lib/scheduler/runner.py +0 -0
  119. {cc_session_tools-3.16.2 → cc_session_tools-4.0.0}/src/cc_session_tools/lib/scheduler/state.py +0 -0
  120. {cc_session_tools-3.16.2 → cc_session_tools-4.0.0}/src/cc_session_tools/lib/scheduler/store.py +0 -0
  121. {cc_session_tools-3.16.2 → cc_session_tools-4.0.0}/src/cc_session_tools/lib/scheduler/surface.py +0 -0
  122. {cc_session_tools-3.16.2 → cc_session_tools-4.0.0}/src/cc_session_tools/lib/scheduler/throttle.py +0 -0
  123. {cc_session_tools-3.16.2 → cc_session_tools-4.0.0}/src/cc_session_tools/lib/scheduler/worker.py +0 -0
  124. {cc_session_tools-3.16.2 → cc_session_tools-4.0.0}/src/cc_session_tools/lib/session_gc.py +0 -0
  125. {cc_session_tools-3.16.2 → cc_session_tools-4.0.0}/src/cc_session_tools/lib/sessions.py +0 -0
  126. {cc_session_tools-3.16.2 → cc_session_tools-4.0.0}/src/cc_session_tools/lib/sessions_db.py +0 -0
  127. {cc_session_tools-3.16.2 → cc_session_tools-4.0.0}/src/cc_session_tools/lib/sessions_repair.py +0 -0
  128. {cc_session_tools-3.16.2 → cc_session_tools-4.0.0}/src/cc_session_tools/lib/shell_install.py +0 -0
  129. {cc_session_tools-3.16.2 → cc_session_tools-4.0.0}/src/cc_session_tools/lib/tasklist.py +0 -0
  130. {cc_session_tools-3.16.2 → cc_session_tools-4.0.0}/src/cc_session_tools/lib/telemetry_store.py +0 -0
  131. {cc_session_tools-3.16.2 → cc_session_tools-4.0.0}/src/cc_session_tools/lib/text_wrap.py +0 -0
  132. {cc_session_tools-3.16.2 → cc_session_tools-4.0.0}/src/cc_session_tools/lib/timefmt.py +0 -0
  133. {cc_session_tools-3.16.2 → cc_session_tools-4.0.0}/src/cc_session_tools/skills/analyse-cc-usage/SKILL.md +0 -0
  134. {cc_session_tools-3.16.2 → cc_session_tools-4.0.0}/src/cc_session_tools/skills/clean-hook-sessions/SKILL.md +0 -0
  135. {cc_session_tools-3.16.2 → cc_session_tools-4.0.0}/src/cc_session_tools/skills/clean-hook-sessions/scripts/clean-hook-sessions.py +0 -0
  136. {cc_session_tools-3.16.2 → cc_session_tools-4.0.0}/src/cc_session_tools/skills/compact-smart/SKILL.md +0 -0
  137. {cc_session_tools-3.16.2 → cc_session_tools-4.0.0}/src/cc_session_tools/skills/context-override/SKILL.md +0 -0
  138. {cc_session_tools-3.16.2 → cc_session_tools-4.0.0}/src/cc_session_tools/skills/delete-sessions/SKILL.md +0 -0
  139. {cc_session_tools-3.16.2 → cc_session_tools-4.0.0}/src/cc_session_tools/skills/delete-sessions/scripts/delete_sessions.py +0 -0
  140. {cc_session_tools-3.16.2 → cc_session_tools-4.0.0}/src/cc_session_tools/skills/do-executor-critic-assessor-loop/SKILL.md +0 -0
  141. {cc_session_tools-3.16.2 → cc_session_tools-4.0.0}/src/cc_session_tools/skills/extract-pptx/SKILL.md +0 -0
  142. {cc_session_tools-3.16.2 → cc_session_tools-4.0.0}/src/cc_session_tools/skills/extract-pptx/scripts/extract_pptx.py +0 -0
  143. {cc_session_tools-3.16.2 → cc_session_tools-4.0.0}/src/cc_session_tools/skills/extract-to-text/SKILL.md +0 -0
  144. {cc_session_tools-3.16.2 → cc_session_tools-4.0.0}/src/cc_session_tools/skills/extract-to-text/scripts/extract_to_text.py +0 -0
  145. {cc_session_tools-3.16.2 → cc_session_tools-4.0.0}/src/cc_session_tools/skills/find-claude-code-session/SKILL.md +0 -0
  146. {cc_session_tools-3.16.2 → cc_session_tools-4.0.0}/src/cc_session_tools/skills/generate-8digit-code/SKILL.md +0 -0
  147. {cc_session_tools-3.16.2 → cc_session_tools-4.0.0}/src/cc_session_tools/skills/generate-8digit-code/scripts/generate_8digit_code.py +0 -0
  148. {cc_session_tools-3.16.2 → cc_session_tools-4.0.0}/src/cc_session_tools/skills/list-empty-sessions/SKILL.md +0 -0
  149. {cc_session_tools-3.16.2 → cc_session_tools-4.0.0}/src/cc_session_tools/skills/list-empty-sessions/scripts/list_empty_sessions.py +0 -0
  150. {cc_session_tools-3.16.2 → cc_session_tools-4.0.0}/src/cc_session_tools/skills/manage-recurring-cc-jobs-using-ccsched/SKILL.md +0 -0
  151. {cc_session_tools-3.16.2 → cc_session_tools-4.0.0}/src/cc_session_tools/skills/move-session/SKILL.md +0 -0
  152. {cc_session_tools-3.16.2 → cc_session_tools-4.0.0}/src/cc_session_tools/skills/move-session/hooks/sessionstart-pending-rename.sh +0 -0
  153. {cc_session_tools-3.16.2 → cc_session_tools-4.0.0}/src/cc_session_tools/skills/move-session/scripts/cc_session_rules.py +0 -0
  154. {cc_session_tools-3.16.2 → cc_session_tools-4.0.0}/src/cc_session_tools/skills/move-session/scripts/move_session.py +0 -0
  155. {cc_session_tools-3.16.2 → cc_session_tools-4.0.0}/src/cc_session_tools/skills/pm-pdata-design-schema/SKILL.md +0 -0
  156. {cc_session_tools-3.16.2 → cc_session_tools-4.0.0}/src/cc_session_tools/skills/pm-pdata-do-audit-and-prepare-to-migrate/SKILL.md +0 -0
  157. {cc_session_tools-3.16.2 → cc_session_tools-4.0.0}/src/cc_session_tools/skills/pm-project-layout-reference/SKILL.md +0 -0
  158. {cc_session_tools-3.16.2 → cc_session_tools-4.0.0}/src/cc_session_tools/skills/pm-update-central-files/SKILL.md +0 -0
  159. {cc_session_tools-3.16.2 → cc_session_tools-4.0.0}/src/cc_session_tools/skills/reduce-persistent-context/CALIBRATION.md +0 -0
  160. {cc_session_tools-3.16.2 → cc_session_tools-4.0.0}/src/cc_session_tools/skills/reduce-persistent-context/SKILL.md +0 -0
  161. {cc_session_tools-3.16.2 → cc_session_tools-4.0.0}/src/cc_session_tools/skills/reduce-persistent-context/scripts/analyze_context.py +0 -0
  162. {cc_session_tools-3.16.2 → cc_session_tools-4.0.0}/src/cc_session_tools/skills/reduce-persistent-context/scripts/duplicates.py +0 -0
  163. {cc_session_tools-3.16.2 → cc_session_tools-4.0.0}/src/cc_session_tools/skills/reduce-persistent-context/scripts/measure.py +0 -0
  164. {cc_session_tools-3.16.2 → cc_session_tools-4.0.0}/src/cc_session_tools/skills/reduce-persistent-context/scripts/report.py +0 -0
  165. {cc_session_tools-3.16.2 → cc_session_tools-4.0.0}/src/cc_session_tools/skills/reduce-persistent-context/scripts/tokens.py +0 -0
  166. {cc_session_tools-3.16.2 → cc_session_tools-4.0.0}/src/cc_session_tools/skills/reduce-persistent-context/scripts/usage.py +0 -0
  167. {cc_session_tools-3.16.2 → cc_session_tools-4.0.0}/src/cc_session_tools/skills/select-agent-model/SKILL.md +0 -0
  168. {cc_session_tools-3.16.2 → cc_session_tools-4.0.0}/src/cc_session_tools/skills/send-session-message/SKILL.md +0 -0
  169. {cc_session_tools-3.16.2 → cc_session_tools-4.0.0}/src/cc_session_tools/skills/update-command-cache/SKILL.md +0 -0
  170. {cc_session_tools-3.16.2 → cc_session_tools-4.0.0}/src/cc_session_tools/skills/update-command-cache/scripts/update_command_cache.py +0 -0
  171. {cc_session_tools-3.16.2 → cc_session_tools-4.0.0}/src/cc_session_tools.egg-info/dependency_links.txt +0 -0
  172. {cc_session_tools-3.16.2 → cc_session_tools-4.0.0}/src/cc_session_tools.egg-info/entry_points.txt +0 -0
  173. {cc_session_tools-3.16.2 → cc_session_tools-4.0.0}/src/cc_session_tools.egg-info/requires.txt +0 -0
  174. {cc_session_tools-3.16.2 → cc_session_tools-4.0.0}/src/cc_session_tools.egg-info/top_level.txt +0 -0
  175. {cc_session_tools-3.16.2 → cc_session_tools-4.0.0}/src/claude_code_usage/__init__.py +0 -0
  176. {cc_session_tools-3.16.2 → cc_session_tools-4.0.0}/src/claude_code_usage/attribution.py +0 -0
  177. {cc_session_tools-3.16.2 → cc_session_tools-4.0.0}/src/claude_code_usage/cache.py +0 -0
  178. {cc_session_tools-3.16.2 → cc_session_tools-4.0.0}/src/claude_code_usage/ccusage_wrapper.py +0 -0
  179. {cc_session_tools-3.16.2 → cc_session_tools-4.0.0}/src/claude_code_usage/cli.py +0 -0
  180. {cc_session_tools-3.16.2 → cc_session_tools-4.0.0}/src/claude_code_usage/data/pricing.json +0 -0
  181. {cc_session_tools-3.16.2 → cc_session_tools-4.0.0}/src/claude_code_usage/parent_inference.py +0 -0
  182. {cc_session_tools-3.16.2 → cc_session_tools-4.0.0}/src/claude_code_usage/parser.py +0 -0
  183. {cc_session_tools-3.16.2 → cc_session_tools-4.0.0}/src/claude_code_usage/pricing.py +0 -0
  184. {cc_session_tools-3.16.2 → cc_session_tools-4.0.0}/src/claude_code_usage/query.py +0 -0
  185. {cc_session_tools-3.16.2 → cc_session_tools-4.0.0}/src/claude_code_usage/report.py +0 -0
  186. {cc_session_tools-3.16.2 → cc_session_tools-4.0.0}/src/claude_code_usage/schema.py +0 -0
  187. {cc_session_tools-3.16.2 → cc_session_tools-4.0.0}/src/claude_code_usage/session_names.py +0 -0
  188. {cc_session_tools-3.16.2 → cc_session_tools-4.0.0}/src/hooks/__init__.py +0 -0
  189. {cc_session_tools-3.16.2 → cc_session_tools-4.0.0}/src/hooks/after_response.py +0 -0
  190. {cc_session_tools-3.16.2 → cc_session_tools-4.0.0}/src/hooks/bash_hard_deny.py +0 -0
  191. {cc_session_tools-3.16.2 → cc_session_tools-4.0.0}/src/hooks/bash_security_review.py +0 -0
  192. {cc_session_tools-3.16.2 → cc_session_tools-4.0.0}/src/hooks/cache.py +0 -0
  193. {cc_session_tools-3.16.2 → cc_session_tools-4.0.0}/src/hooks/catchup.py +0 -0
  194. {cc_session_tools-3.16.2 → cc_session_tools-4.0.0}/src/hooks/confirm_8digit.py +0 -0
  195. {cc_session_tools-3.16.2 → cc_session_tools-4.0.0}/src/hooks/context_window_warning.py +0 -0
  196. {cc_session_tools-3.16.2 → cc_session_tools-4.0.0}/src/hooks/last_screenshot.py +0 -0
  197. {cc_session_tools-3.16.2 → cc_session_tools-4.0.0}/src/hooks/marker_allow.py +0 -0
  198. {cc_session_tools-3.16.2 → cc_session_tools-4.0.0}/src/hooks/markers.py +0 -0
  199. {cc_session_tools-3.16.2 → cc_session_tools-4.0.0}/src/hooks/messaging_deliver.py +0 -0
  200. {cc_session_tools-3.16.2 → cc_session_tools-4.0.0}/src/hooks/model_info.py +0 -0
  201. {cc_session_tools-3.16.2 → cc_session_tools-4.0.0}/src/hooks/normalise.py +0 -0
  202. {cc_session_tools-3.16.2 → cc_session_tools-4.0.0}/src/hooks/pending_rename.py +0 -0
  203. {cc_session_tools-3.16.2 → cc_session_tools-4.0.0}/src/hooks/script_allowlist.py +0 -0
  204. {cc_session_tools-3.16.2 → cc_session_tools-4.0.0}/src/hooks/session_tag.py +0 -0
  205. {cc_session_tools-3.16.2 → cc_session_tools-4.0.0}/src/hooks/stats.py +0 -0
  206. {cc_session_tools-3.16.2 → cc_session_tools-4.0.0}/src/hooks/telemetry.py +0 -0
  207. {cc_session_tools-3.16.2 → cc_session_tools-4.0.0}/src/hooks/telemetry_query.py +0 -0
  208. {cc_session_tools-3.16.2 → cc_session_tools-4.0.0}/src/hooks/telemetry_trim.py +0 -0
  209. {cc_session_tools-3.16.2 → cc_session_tools-4.0.0}/src/hooks/transcript.py +0 -0
  210. {cc_session_tools-3.16.2 → cc_session_tools-4.0.0}/src/hooks/worklog_guard.py +0 -0
  211. {cc_session_tools-3.16.2 → cc_session_tools-4.0.0}/tests/test_after_response.py +0 -0
  212. {cc_session_tools-3.16.2 → cc_session_tools-4.0.0}/tests/test_api_keys.py +0 -0
  213. {cc_session_tools-3.16.2 → cc_session_tools-4.0.0}/tests/test_attribution.py +0 -0
  214. {cc_session_tools-3.16.2 → cc_session_tools-4.0.0}/tests/test_bash_hard_deny.py +0 -0
  215. {cc_session_tools-3.16.2 → cc_session_tools-4.0.0}/tests/test_bash_security_review.py +0 -0
  216. {cc_session_tools-3.16.2 → cc_session_tools-4.0.0}/tests/test_bash_security_review_allowlist.py +0 -0
  217. {cc_session_tools-3.16.2 → cc_session_tools-4.0.0}/tests/test_bun_tools.py +0 -0
  218. {cc_session_tools-3.16.2 → cc_session_tools-4.0.0}/tests/test_cache.py +0 -0
  219. {cc_session_tools-3.16.2 → cc_session_tools-4.0.0}/tests/test_cache_sqlite.py +0 -0
  220. {cc_session_tools-3.16.2 → cc_session_tools-4.0.0}/tests/test_ccr_orphans.py +0 -0
  221. {cc_session_tools-3.16.2 → cc_session_tools-4.0.0}/tests/test_ccs_emptiness.py +0 -0
  222. {cc_session_tools-3.16.2 → cc_session_tools-4.0.0}/tests/test_ccs_eta.py +0 -0
  223. {cc_session_tools-3.16.2 → cc_session_tools-4.0.0}/tests/test_ccs_list_mode.py +0 -0
  224. {cc_session_tools-3.16.2 → cc_session_tools-4.0.0}/tests/test_ccs_scope_flags.py +0 -0
  225. {cc_session_tools-3.16.2 → cc_session_tools-4.0.0}/tests/test_ccs_sentinel_sort.py +0 -0
  226. {cc_session_tools-3.16.2 → cc_session_tools-4.0.0}/tests/test_ccs_session_counts.py +0 -0
  227. {cc_session_tools-3.16.2 → cc_session_tools-4.0.0}/tests/test_ccst_bundle_discovery.py +0 -0
  228. {cc_session_tools-3.16.2 → cc_session_tools-4.0.0}/tests/test_ccst_ccsched_jobs_cli.py +0 -0
  229. {cc_session_tools-3.16.2 → cc_session_tools-4.0.0}/tests/test_ccst_cli.py +0 -0
  230. {cc_session_tools-3.16.2 → cc_session_tools-4.0.0}/tests/test_ccst_context_override_cli.py +0 -0
  231. {cc_session_tools-3.16.2 → cc_session_tools-4.0.0}/tests/test_ccst_gc_prune.py +0 -0
  232. {cc_session_tools-3.16.2 → cc_session_tools-4.0.0}/tests/test_ccst_gc_report.py +0 -0
  233. {cc_session_tools-3.16.2 → cc_session_tools-4.0.0}/tests/test_ccst_hook_dispatcher.py +0 -0
  234. {cc_session_tools-3.16.2 → cc_session_tools-4.0.0}/tests/test_ccst_hooks_allowlist.py +0 -0
  235. {cc_session_tools-3.16.2 → cc_session_tools-4.0.0}/tests/test_ccst_hooks_install_prune.py +0 -0
  236. {cc_session_tools-3.16.2 → cc_session_tools-4.0.0}/tests/test_ccst_hooks_install_zero_arg.py +0 -0
  237. {cc_session_tools-3.16.2 → cc_session_tools-4.0.0}/tests/test_ccst_hooks_uninstall.py +0 -0
  238. {cc_session_tools-3.16.2 → cc_session_tools-4.0.0}/tests/test_ccst_install_everything.py +0 -0
  239. {cc_session_tools-3.16.2 → cc_session_tools-4.0.0}/tests/test_ccst_machine_identity_cli.py +0 -0
  240. {cc_session_tools-3.16.2 → cc_session_tools-4.0.0}/tests/test_ccst_main_gate.py +0 -0
  241. {cc_session_tools-3.16.2 → cc_session_tools-4.0.0}/tests/test_ccst_pdata_readiness_cli.py +0 -0
  242. {cc_session_tools-3.16.2 → cc_session_tools-4.0.0}/tests/test_ccst_pdata_reconcile_cli.py +0 -0
  243. {cc_session_tools-3.16.2 → cc_session_tools-4.0.0}/tests/test_ccst_pdata_rename_group_cli.py +0 -0
  244. {cc_session_tools-3.16.2 → cc_session_tools-4.0.0}/tests/test_ccst_pdata_reorganize_cli.py +0 -0
  245. {cc_session_tools-3.16.2 → cc_session_tools-4.0.0}/tests/test_ccst_pdata_verify_cli.py +0 -0
  246. {cc_session_tools-3.16.2 → cc_session_tools-4.0.0}/tests/test_ccst_repair_sessions_cli.py +0 -0
  247. {cc_session_tools-3.16.2 → cc_session_tools-4.0.0}/tests/test_ccst_sessions_cli.py +0 -0
  248. {cc_session_tools-3.16.2 → cc_session_tools-4.0.0}/tests/test_ccst_shell_install.py +0 -0
  249. {cc_session_tools-3.16.2 → cc_session_tools-4.0.0}/tests/test_ccst_skills_install.py +0 -0
  250. {cc_session_tools-3.16.2 → cc_session_tools-4.0.0}/tests/test_ccst_skills_uninstall.py +0 -0
  251. {cc_session_tools-3.16.2 → cc_session_tools-4.0.0}/tests/test_ccst_telemetry_query.py +0 -0
  252. {cc_session_tools-3.16.2 → cc_session_tools-4.0.0}/tests/test_ccst_telemetry_trim.py +0 -0
  253. {cc_session_tools-3.16.2 → cc_session_tools-4.0.0}/tests/test_ccusage_wrapper.py +0 -0
  254. {cc_session_tools-3.16.2 → cc_session_tools-4.0.0}/tests/test_claude_flags.py +0 -0
  255. {cc_session_tools-3.16.2 → cc_session_tools-4.0.0}/tests/test_claude_md_drift.py +0 -0
  256. {cc_session_tools-3.16.2 → cc_session_tools-4.0.0}/tests/test_claude_md_install.py +0 -0
  257. {cc_session_tools-3.16.2 → cc_session_tools-4.0.0}/tests/test_clean_hook_sessions_format.py +0 -0
  258. {cc_session_tools-3.16.2 → cc_session_tools-4.0.0}/tests/test_cli.py +0 -0
  259. {cc_session_tools-3.16.2 → cc_session_tools-4.0.0}/tests/test_cli_api_launchers.py +0 -0
  260. {cc_session_tools-3.16.2 → cc_session_tools-4.0.0}/tests/test_cli_ccd.py +0 -0
  261. {cc_session_tools-3.16.2 → cc_session_tools-4.0.0}/tests/test_cli_ccd_dry_run.py +0 -0
  262. {cc_session_tools-3.16.2 → cc_session_tools-4.0.0}/tests/test_cli_ccr.py +0 -0
  263. {cc_session_tools-3.16.2 → cc_session_tools-4.0.0}/tests/test_cli_ccs.py +0 -0
  264. {cc_session_tools-3.16.2 → cc_session_tools-4.0.0}/tests/test_cli_version.py +0 -0
  265. {cc_session_tools-3.16.2 → cc_session_tools-4.0.0}/tests/test_confirm_8digit.py +0 -0
  266. {cc_session_tools-3.16.2 → cc_session_tools-4.0.0}/tests/test_context_overrides.py +0 -0
  267. {cc_session_tools-3.16.2 → cc_session_tools-4.0.0}/tests/test_context_window_warning.py +0 -0
  268. {cc_session_tools-3.16.2 → cc_session_tools-4.0.0}/tests/test_db.py +0 -0
  269. {cc_session_tools-3.16.2 → cc_session_tools-4.0.0}/tests/test_debug.py +0 -0
  270. {cc_session_tools-3.16.2 → cc_session_tools-4.0.0}/tests/test_doctor_mutes.py +0 -0
  271. {cc_session_tools-3.16.2 → cc_session_tools-4.0.0}/tests/test_empty_session.py +0 -0
  272. {cc_session_tools-3.16.2 → cc_session_tools-4.0.0}/tests/test_hook_cache.py +0 -0
  273. {cc_session_tools-3.16.2 → cc_session_tools-4.0.0}/tests/test_hooks_install.py +0 -0
  274. {cc_session_tools-3.16.2 → cc_session_tools-4.0.0}/tests/test_hooks_install_records.py +0 -0
  275. {cc_session_tools-3.16.2 → cc_session_tools-4.0.0}/tests/test_install_sync.py +0 -0
  276. {cc_session_tools-3.16.2 → cc_session_tools-4.0.0}/tests/test_last_screenshot.py +0 -0
  277. {cc_session_tools-3.16.2 → cc_session_tools-4.0.0}/tests/test_levenshtein.py +0 -0
  278. {cc_session_tools-3.16.2 → cc_session_tools-4.0.0}/tests/test_machine_identity.py +0 -0
  279. {cc_session_tools-3.16.2 → cc_session_tools-4.0.0}/tests/test_marker_allow.py +0 -0
  280. {cc_session_tools-3.16.2 → cc_session_tools-4.0.0}/tests/test_migrate_ccmsg.py +0 -0
  281. {cc_session_tools-3.16.2 → cc_session_tools-4.0.0}/tests/test_migrate_ccsched.py +0 -0
  282. {cc_session_tools-3.16.2 → cc_session_tools-4.0.0}/tests/test_migrate_sessions_db.py +0 -0
  283. {cc_session_tools-3.16.2 → cc_session_tools-4.0.0}/tests/test_migrate_telemetry.py +0 -0
  284. {cc_session_tools-3.16.2 → cc_session_tools-4.0.0}/tests/test_model_info.py +0 -0
  285. {cc_session_tools-3.16.2 → cc_session_tools-4.0.0}/tests/test_normalise.py +0 -0
  286. {cc_session_tools-3.16.2 → cc_session_tools-4.0.0}/tests/test_occupancy.py +0 -0
  287. {cc_session_tools-3.16.2 → cc_session_tools-4.0.0}/tests/test_parent_inference.py +0 -0
  288. {cc_session_tools-3.16.2 → cc_session_tools-4.0.0}/tests/test_parser.py +0 -0
  289. {cc_session_tools-3.16.2 → cc_session_tools-4.0.0}/tests/test_paths.py +0 -0
  290. {cc_session_tools-3.16.2 → cc_session_tools-4.0.0}/tests/test_pdata_audit_skill.py +0 -0
  291. {cc_session_tools-3.16.2 → cc_session_tools-4.0.0}/tests/test_pdata_post_migration_skills.py +0 -0
  292. {cc_session_tools-3.16.2 → cc_session_tools-4.0.0}/tests/test_picker.py +0 -0
  293. {cc_session_tools-3.16.2 → cc_session_tools-4.0.0}/tests/test_pricing.py +0 -0
  294. {cc_session_tools-3.16.2 → cc_session_tools-4.0.0}/tests/test_proc_lock.py +0 -0
  295. {cc_session_tools-3.16.2 → cc_session_tools-4.0.0}/tests/test_prompts.py +0 -0
  296. {cc_session_tools-3.16.2 → cc_session_tools-4.0.0}/tests/test_query.py +0 -0
  297. {cc_session_tools-3.16.2 → cc_session_tools-4.0.0}/tests/test_report.py +0 -0
  298. {cc_session_tools-3.16.2 → cc_session_tools-4.0.0}/tests/test_roots.py +0 -0
  299. {cc_session_tools-3.16.2 → cc_session_tools-4.0.0}/tests/test_rules.py +0 -0
  300. {cc_session_tools-3.16.2 → cc_session_tools-4.0.0}/tests/test_scheduler_bundled_jobs.py +0 -0
  301. {cc_session_tools-3.16.2 → cc_session_tools-4.0.0}/tests/test_schema.py +0 -0
  302. {cc_session_tools-3.16.2 → cc_session_tools-4.0.0}/tests/test_script_allowlist.py +0 -0
  303. {cc_session_tools-3.16.2 → cc_session_tools-4.0.0}/tests/test_session_names.py +0 -0
  304. {cc_session_tools-3.16.2 → cc_session_tools-4.0.0}/tests/test_session_tag.py +0 -0
  305. {cc_session_tools-3.16.2 → cc_session_tools-4.0.0}/tests/test_sessions.py +0 -0
  306. {cc_session_tools-3.16.2 → cc_session_tools-4.0.0}/tests/test_sessions_db.py +0 -0
  307. {cc_session_tools-3.16.2 → cc_session_tools-4.0.0}/tests/test_sessions_db_migration_chain.py +0 -0
  308. {cc_session_tools-3.16.2 → cc_session_tools-4.0.0}/tests/test_sessions_repair.py +0 -0
  309. {cc_session_tools-3.16.2 → cc_session_tools-4.0.0}/tests/test_tasklist.py +0 -0
  310. {cc_session_tools-3.16.2 → cc_session_tools-4.0.0}/tests/test_telemetry.py +0 -0
  311. {cc_session_tools-3.16.2 → cc_session_tools-4.0.0}/tests/test_telemetry_store.py +0 -0
  312. {cc_session_tools-3.16.2 → cc_session_tools-4.0.0}/tests/test_text_wrap.py +0 -0
  313. {cc_session_tools-3.16.2 → cc_session_tools-4.0.0}/tests/test_timefmt.py +0 -0
  314. {cc_session_tools-3.16.2 → cc_session_tools-4.0.0}/tests/test_transcript.py +0 -0
  315. {cc_session_tools-3.16.2 → cc_session_tools-4.0.0}/tests/test_worklog_guard.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: cc-session-tools
3
- Version: 3.16.2
3
+ Version: 4.0.0
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
@@ -455,6 +455,10 @@ ccst pdata schema list --project myproject
455
455
  vector clock per project means a genuine fork (two machines both edited) is flagged for
456
456
  your choice with `ccst pdata resolve`, never silently merged. Each machine has a confirmed
457
457
  identity (`ccst machine-identity show|confirm`).
458
+ - **UUID record ids** - record ids are UUIDs, so two machines can never hand out the same id
459
+ for different records. Upgrading from 3.x needs a one-time `ccst pdata migrate-uuid` on every
460
+ machine, from a plain terminal: follow
461
+ [docs/pdata-uuid-migration-runbook.md](docs/pdata-uuid-migration-runbook.md).
458
462
  - **New projects start well** - the first `pdata init` for a new project seeds starter
459
463
  folders and a `CLAUDE.md` with date-format and line-ending conventions. An existing
460
464
  `CLAUDE.md` is never touched.
@@ -396,6 +396,10 @@ ccst pdata schema list --project myproject
396
396
  vector clock per project means a genuine fork (two machines both edited) is flagged for
397
397
  your choice with `ccst pdata resolve`, never silently merged. Each machine has a confirmed
398
398
  identity (`ccst machine-identity show|confirm`).
399
+ - **UUID record ids** - record ids are UUIDs, so two machines can never hand out the same id
400
+ for different records. Upgrading from 3.x needs a one-time `ccst pdata migrate-uuid` on every
401
+ machine, from a plain terminal: follow
402
+ [docs/pdata-uuid-migration-runbook.md](docs/pdata-uuid-migration-runbook.md).
399
403
  - **New projects start well** - the first `pdata init` for a new project seeds starter
400
404
  folders and a `CLAUDE.md` with date-format and line-ending conventions. An existing
401
405
  `CLAUDE.md` is never touched.
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "cc-session-tools"
7
- version = "3.16.2"
7
+ version = "4.0.0"
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" }
@@ -48,18 +48,24 @@ Current subcommands:
48
48
  resolution, suspicious double-updates) for --project NAME or
49
49
  --all-projects and persist the result for ccst doctor.
50
50
  pdata dump Publish the current local DB state to
51
- .pdata-db-dump/latest.sql for --project NAME or
51
+ .pdata-db-dump/v4/latest.sql for --project NAME or
52
52
  --all-projects. Refuses (unless --force) to overwrite an
53
53
  unresolved cross-machine fork.
54
54
  pdata rehydrate Fast-forward the local DB from the published dump for
55
55
  --project NAME or --all-projects, if it's ahead. --force
56
56
  fast-forwards even over a diverged local revision.
57
57
  pdata resolve Diagnose (default, prints the diff) or apply per-record
58
- --choice ID=local|dump resolutions for a cross-machine fork.
58
+ --choice UUID=local|dump resolutions for a cross-machine fork.
59
59
  pdata sync-check Do whichever of rehydrate/dump each project needs, in that
60
60
  order, for --project NAME or --all-projects. The unattended
61
61
  trigger behind the hourly pdata-sync-hourly ccsched job:
62
62
  skips an unchanged republish and never overrides a fork.
63
+ pdata migrate-uuid Convert format 3 (integer id) databases to format 4 (UUID
64
+ ids) for --project NAME or --all-projects. Dry run unless
65
+ --write; run from a plain terminal with no `claude` session.
66
+ pdata migration-notice Print the migrate-uuid dry run and a framed banner when any
67
+ project is still format 3; nothing otherwise. Used by
68
+ install-everything.sh; always exits 0.
63
69
  machine-identity show Print this laptop's resolved machine id and whether it's
64
70
  confirmed.
65
71
  machine-identity confirm Store this laptop's confirmed machine id (--name NAME), used
@@ -1029,6 +1035,165 @@ def _cmd_gc_prune(args: argparse.Namespace) -> int:
1029
1035
  # ---------- pdata ----------
1030
1036
 
1031
1037
 
1038
+ def _record_id_arg(raw: str) -> str:
1039
+ """argparse type for a record id: a canonical UUID string, else a usage error naming it."""
1040
+ from cc_session_tools.lib.pdata import ids
1041
+
1042
+ if not ids.is_record_id(raw):
1043
+ raise argparse.ArgumentTypeError(
1044
+ f"{raw!r} is not a UUID (record ids look like 3f2b8c1e-6d4a-4e9b-9a7c-2d5e8f1b0c64)"
1045
+ )
1046
+ return raw
1047
+
1048
+
1049
+ def _pdata_gate_targets(args: argparse.Namespace) -> list[str]:
1050
+ """The projects a gated verb would open: the named one, or every project its --all-projects
1051
+ run would visit. `reconcile-session-output --all-projects` visits projects that have a
1052
+ cc-sessions/ directory, with or without a database, so it is the one verb whose sweep can
1053
+ create a database."""
1054
+ from cc_session_tools.lib import roots
1055
+ from cc_session_tools.lib.pdata import session_output, verify
1056
+
1057
+ if not getattr(args, "all_projects", False):
1058
+ return [args.project]
1059
+ if args.verb == "reconcile-session-output":
1060
+ try:
1061
+ return [name for name, _root in session_output.discover_projects_with_sessions()]
1062
+ except roots.RootsConfigError:
1063
+ return [] # the verb's own handler reports the missing configuration
1064
+ return verify.discover_projects()
1065
+
1066
+
1067
+ @dataclass(frozen=True, slots=True)
1068
+ class _PdataFormatGate:
1069
+ targets: tuple[str, ...] # every project the verb would open
1070
+ refused: tuple[str, ...] # those it must not open, each already named on stderr
1071
+
1072
+
1073
+ def _pdata_gate_reason(verb: str, project: str) -> str | None:
1074
+ """Why the format gate keeps `verb` off `project`, or None when it may run there."""
1075
+ import sqlite3
1076
+
1077
+ from cc_session_tools.lib.pdata import dump, formats, store, vector_clock_store
1078
+
1079
+ try:
1080
+ path = store.db_path(project)
1081
+ project_root = store.project_root(project)
1082
+ except ValueError:
1083
+ return None # the verb's own handler rejects an invalid project name
1084
+ if not path.exists():
1085
+ # `init` refuses this case itself, with its adoption-mismatch message.
1086
+ if verb != "init" and dump.only_format3_published(project_root):
1087
+ return formats.format3_only_message(project)
1088
+ return None
1089
+ try:
1090
+ fmt = formats.database_format(path)
1091
+ except formats.UnknownPdataFormatError as exc:
1092
+ return f"{project}: {exc}"
1093
+ except sqlite3.DatabaseError:
1094
+ return None # not a readable database: the verb's own handler reports it per project
1095
+ if fmt == formats.PDATA_FORMAT_INTEGER_IDS:
1096
+ return formats.legacy_format_message(project)
1097
+ if vector_clock_store.migration_unfinished(path):
1098
+ return formats.unfinished_migration_message(project)
1099
+ return None
1100
+
1101
+
1102
+ def _pdata_format_gate(args: argparse.Namespace) -> _PdataFormatGate:
1103
+ """The format gate (`formats` module docstring), for a verb that is not exempt: every target
1104
+ project whose database is still format 3, or was converted by a migration run that did not
1105
+ finish, or that has no database while its only published dump is format 3, is refused, with
1106
+ the reason printed to stderr. Read-only, and runs before the verb's handler opens anything."""
1107
+ from cc_session_tools.lib.pdata import formats
1108
+
1109
+ if args.verb in formats.FORMAT_GATE_EXEMPT_VERBS:
1110
+ return _PdataFormatGate(targets=(), refused=())
1111
+ targets = tuple(_pdata_gate_targets(args))
1112
+ skipping = "skipped " if getattr(args, "all_projects", False) else ""
1113
+ refused: list[str] = []
1114
+ for project in targets:
1115
+ reason = _pdata_gate_reason(args.verb, project)
1116
+ if reason is not None:
1117
+ print(f"ccst pdata {args.verb}: {skipping}{reason}", file=sys.stderr)
1118
+ refused.append(project)
1119
+ return _PdataFormatGate(targets=targets, refused=tuple(refused))
1120
+
1121
+
1122
+ def _pdata_projects(args: argparse.Namespace) -> list[str]:
1123
+ """The projects a verb handler runs on: the named one, or for --all-projects every project
1124
+ with a database except those the format gate refused."""
1125
+ from cc_session_tools.lib.pdata import verify
1126
+
1127
+ if not args.all_projects:
1128
+ return [args.project]
1129
+ gated: frozenset[str] = getattr(args, "format_gated_projects", frozenset())
1130
+ return [project for project in verify.discover_projects() if project not in gated]
1131
+
1132
+
1133
+ def _cmd_pdata(args: argparse.Namespace) -> int:
1134
+ """Run one `ccst pdata` verb behind the format gate. A single-project run on a gated project
1135
+ refuses with exit 1. An --all-projects run skips the gated projects (naming each), runs the
1136
+ verb on the rest, and exits 1 at the end when it skipped any and the verb itself succeeded."""
1137
+ gate = _pdata_format_gate(args)
1138
+ if gate.refused:
1139
+ if not getattr(args, "all_projects", False):
1140
+ return 1
1141
+ args.format_gated_projects = frozenset(gate.refused)
1142
+ if set(gate.targets) <= set(gate.refused):
1143
+ return 1
1144
+ status = _run_pdata_verb(args)
1145
+ return 1 if gate.refused and status == 0 else status
1146
+
1147
+
1148
+ def _run_pdata_verb(args: argparse.Namespace) -> int:
1149
+ if args.verb == "add":
1150
+ return _cmd_pdata_add(args)
1151
+ if args.verb == "schema":
1152
+ if args.subverb == "add-field":
1153
+ return _cmd_pdata_schema_add_field(args)
1154
+ if args.subverb == "list":
1155
+ return _cmd_pdata_schema_list(args)
1156
+ if args.subverb == "show":
1157
+ return _cmd_pdata_schema_show(args)
1158
+ if args.verb == "get":
1159
+ return _cmd_pdata_get(args)
1160
+ if args.verb == "list":
1161
+ return _cmd_pdata_list(args)
1162
+ if args.verb == "query":
1163
+ return _cmd_pdata_query(args)
1164
+ if args.verb == "update":
1165
+ return _cmd_pdata_update(args)
1166
+ if args.verb == "delete":
1167
+ return _cmd_pdata_delete(args)
1168
+ if args.verb == "restore":
1169
+ return _cmd_pdata_restore(args)
1170
+ if args.verb == "init":
1171
+ return _cmd_pdata_init(args)
1172
+ if args.verb == "reorganize":
1173
+ return _cmd_pdata_reorganize(args)
1174
+ if args.verb == "rename-group":
1175
+ return _cmd_pdata_rename_group(args)
1176
+ if args.verb == "reconcile-session-output":
1177
+ return _cmd_pdata_reconcile_session_output(args)
1178
+ if args.verb == "verify":
1179
+ return _cmd_pdata_verify(args)
1180
+ if args.verb == "readiness-scan":
1181
+ return _cmd_pdata_readiness_scan(args)
1182
+ if args.verb == "dump":
1183
+ return _cmd_pdata_dump(args)
1184
+ if args.verb == "rehydrate":
1185
+ return _cmd_pdata_rehydrate(args)
1186
+ if args.verb == "resolve":
1187
+ return _cmd_pdata_resolve(args)
1188
+ if args.verb == "sync-check":
1189
+ return _cmd_pdata_sync_check(args)
1190
+ if args.verb == "migrate-uuid":
1191
+ return _cmd_pdata_migrate_uuid(args)
1192
+ if args.verb == "migration-notice":
1193
+ return _cmd_pdata_migration_notice(args)
1194
+ raise AssertionError(f"unhandled ccst pdata verb {args.verb!r}")
1195
+
1196
+
1032
1197
  def _parse_field_assignment(raw: str) -> tuple[str, str | None]:
1033
1198
  """Parse "k=v" into (k, v). The literal token `null` (unquoted, lowercase) is interpreted
1034
1199
  as SQL NULL rather than the four-character string "null" - this is the documented way to
@@ -1453,7 +1618,11 @@ def _cmd_pdata_reconcile_session_output(args: argparse.Namespace) -> int:
1453
1618
 
1454
1619
  try:
1455
1620
  if args.all_projects:
1456
- targets = session_output.discover_projects_with_sessions()
1621
+ gated: frozenset[str] = getattr(args, "format_gated_projects", frozenset())
1622
+ targets = [
1623
+ (name, root) for name, root in session_output.discover_projects_with_sessions()
1624
+ if name not in gated
1625
+ ]
1457
1626
  else:
1458
1627
  root = session_output.find_project_root(args.project)
1459
1628
  if root is None:
@@ -1491,7 +1660,7 @@ def _cmd_pdata_reconcile_session_output(args: argparse.Namespace) -> int:
1491
1660
  def _cmd_pdata_verify(args: argparse.Namespace) -> int:
1492
1661
  from cc_session_tools.lib.pdata import verify
1493
1662
 
1494
- projects = verify.discover_projects() if args.all_projects else [args.project]
1663
+ projects = _pdata_projects(args)
1495
1664
  if not projects:
1496
1665
  print("ccst pdata verify: no project databases found", file=sys.stderr)
1497
1666
  return 2
@@ -1566,10 +1735,10 @@ def _cmd_pdata_verify(args: argparse.Namespace) -> int:
1566
1735
  def _cmd_pdata_dump(args: argparse.Namespace) -> int:
1567
1736
  from cc_session_tools.lib import machine_identity
1568
1737
  from cc_session_tools.lib.pdata import (
1569
- dump, repository, store, sync_notify, vector_clock_store, verify,
1738
+ dump, repository, store, sync_notify, vector_clock_store,
1570
1739
  )
1571
1740
 
1572
- projects = verify.discover_projects() if args.all_projects else [args.project]
1741
+ projects = _pdata_projects(args)
1573
1742
  if not projects:
1574
1743
  print("ccst pdata dump: no project databases found", file=sys.stderr)
1575
1744
  return 2
@@ -1630,9 +1799,9 @@ def _cmd_pdata_dump(args: argparse.Namespace) -> int:
1630
1799
 
1631
1800
 
1632
1801
  def _cmd_pdata_rehydrate(args: argparse.Namespace) -> int:
1633
- from cc_session_tools.lib.pdata import rehydrate, sync_notify, verify
1802
+ from cc_session_tools.lib.pdata import rehydrate, sync_notify
1634
1803
 
1635
- projects = verify.discover_projects() if args.all_projects else [args.project]
1804
+ projects = _pdata_projects(args)
1636
1805
  if not projects:
1637
1806
  print("ccst pdata rehydrate: no project databases found", file=sys.stderr)
1638
1807
  return 2
@@ -1669,6 +1838,12 @@ def _cmd_pdata_rehydrate(args: argparse.Namespace) -> int:
1669
1838
  sync_notify.notify_conflict(project, outcome=result.outcome.value, detail=detail)
1670
1839
  if not compact:
1671
1840
  print(f"ccst pdata rehydrate: {project}: {detail}", file=sys.stderr)
1841
+ elif result.outcome is rehydrate.RehydrateOutcome.MIGRATION_PENDING:
1842
+ # Nothing loaded, and not a conflict: the published dump and this database are
1843
+ # different formats until `ccst pdata migrate-uuid` runs on the format 3 side.
1844
+ if not compact:
1845
+ detail = rehydrate.migration_pending_detail(result, project=project)
1846
+ print(f"ccst pdata rehydrate: {project}: migration pending - {detail}")
1672
1847
  else:
1673
1848
  # DEFERRED - another writer holds sync_lock right now. Expected and transient, not
1674
1849
  # an error (rehydrate.py's own RehydrateOutcome docstring: "retry later" rather than
@@ -1687,24 +1862,28 @@ def _cmd_pdata_rehydrate(args: argparse.Namespace) -> int:
1687
1862
  f"no-op {counts[rehydrate.RehydrateOutcome.NO_OP.value]}, "
1688
1863
  f"fork {counts[rehydrate.RehydrateOutcome.FORK.value]}, "
1689
1864
  f"checksum-invalid {counts[rehydrate.RehydrateOutcome.CHECKSUM_INVALID.value]}, "
1690
- f"deferred {counts[rehydrate.RehydrateOutcome.DEFERRED.value]} of "
1865
+ f"deferred {counts[rehydrate.RehydrateOutcome.DEFERRED.value]}, "
1866
+ f"migration-pending {counts[rehydrate.RehydrateOutcome.MIGRATION_PENDING.value]} of "
1691
1867
  f"{len(projects)} project(s)"
1692
1868
  )
1693
1869
 
1694
1870
  return worst
1695
1871
 
1696
1872
 
1697
- def _parse_resolve_choice(raw: str) -> tuple[int, str]:
1698
- """Parse "ID=local|dump" into (record_id, choice). Only checks the shape - whether choice is
1699
- actually 'local'/'dump' is apply_resolution's own job (its error message is printed verbatim,
1700
- not re-derived here). Raises ValueError on malformed input."""
1701
- id_str, sep, choice = raw.partition("=")
1702
- if not sep or not id_str or not choice:
1703
- raise ValueError(f"malformed --choice (want ID=local|dump): {raw!r}")
1704
- try:
1705
- record_id = int(id_str)
1706
- except ValueError:
1707
- raise ValueError(f"malformed --choice (want ID=local|dump): {raw!r}") from None
1873
+ def _parse_resolve_choice(raw: str) -> tuple[str, str]:
1874
+ """Parse "UUID=local|dump" into (record_id, choice). Only checks the shape and that the id is
1875
+ a canonical UUID - whether choice is actually 'local'/'dump' is apply_resolution's own job
1876
+ (its error message is printed verbatim, not re-derived here). Raises ValueError on malformed
1877
+ input."""
1878
+ from cc_session_tools.lib.pdata import ids
1879
+
1880
+ record_id, sep, choice = raw.partition("=")
1881
+ if not sep or not record_id or not choice:
1882
+ raise ValueError(f"malformed --choice (want UUID=local|dump): {raw!r}")
1883
+ if not ids.is_record_id(record_id):
1884
+ raise ValueError(
1885
+ f"malformed --choice (want UUID=local|dump): {raw!r} - {record_id!r} is not a UUID"
1886
+ )
1708
1887
  return record_id, choice
1709
1888
 
1710
1889
 
@@ -1749,7 +1928,7 @@ def _close_empty_fork_if_any(project: str) -> bool:
1749
1928
 
1750
1929
 
1751
1930
  def _cmd_pdata_resolve(args: argparse.Namespace) -> int:
1752
- from cc_session_tools.lib.pdata import resolve, verify
1931
+ from cc_session_tools.lib.pdata import resolve
1753
1932
 
1754
1933
  if args.choice:
1755
1934
  if args.all_projects:
@@ -1783,7 +1962,7 @@ def _cmd_pdata_resolve(args: argparse.Namespace) -> int:
1783
1962
  print(f"ccst pdata resolve: {args.project}: resolved {len(choices)} record(s)")
1784
1963
  return 0
1785
1964
 
1786
- projects = verify.discover_projects() if args.all_projects else [args.project]
1965
+ projects = _pdata_projects(args)
1787
1966
  if not projects:
1788
1967
  print("ccst pdata resolve: no project databases found", file=sys.stderr)
1789
1968
  return 2
@@ -1828,15 +2007,6 @@ def _cmd_pdata_resolve(args: argparse.Namespace) -> int:
1828
2007
  f"field(s) to resolve"
1829
2008
  )
1830
2009
  for rd in diff.records:
1831
- if rd.id_collision:
1832
- print(
1833
- f" record {rd.record_id} (group={rd.record_group}): id collision - "
1834
- f"this id was independently assigned to two unrelated records on "
1835
- f"different machines, not a genuine edit conflict; `ccst pdata resolve` "
1836
- f"cannot apply a local/dump choice to this one, it needs manual, "
1837
- f"out-of-band reconciliation"
1838
- )
1839
- continue
1840
2010
  if rd.group_mismatch:
1841
2011
  # Both sides' own group names, not rd.record_group: that field carries whichever
1842
2012
  # side happened to be available, and RecordDiff's docstring says so - printing it
@@ -1844,11 +2014,10 @@ def _cmd_pdata_resolve(args: argparse.Namespace) -> int:
1844
2014
  # the reader neither which side they are seeing nor what the other side's is.
1845
2015
  print(
1846
2016
  f" record {rd.record_id} (local group={rd.local_record_group}, "
1847
- f"dump group={rd.dump_record_group}): group mismatch - same id and "
1848
- f"created_at, but local and dump disagree on record_group; compare "
1849
- f"content/file_path on each side before assuming this is a safe "
1850
- f"`ccst pdata rename-group` rather than a same-second id collision - "
1851
- f"`ccst pdata resolve` cannot apply a local/dump choice to this one"
2017
+ f"dump group={rd.dump_record_group}): group mismatch - the same record, "
2018
+ f"but local and dump disagree on record_group; run `ccst pdata "
2019
+ f"rename-group` on one side so both agree - `ccst pdata resolve` cannot "
2020
+ f"apply a local/dump choice to this one"
1852
2021
  )
1853
2022
  continue
1854
2023
  if rd.is_delete_vs_update:
@@ -1916,9 +2085,9 @@ def _cmd_pdata_sync_check(args: argparse.Namespace) -> int:
1916
2085
  rules). This handler is the loop, the printing, and the exit-code mapping."""
1917
2086
  import sqlite3
1918
2087
 
1919
- from cc_session_tools.lib.pdata import sync_check, verify
2088
+ from cc_session_tools.lib.pdata import sync_check
1920
2089
 
1921
- projects = verify.discover_projects() if args.all_projects else [args.project]
2090
+ projects = _pdata_projects(args)
1922
2091
  if not projects:
1923
2092
  print("ccst pdata sync-check: no project databases found", file=sys.stderr)
1924
2093
  return 2
@@ -1953,6 +2122,18 @@ def _cmd_pdata_sync_check(args: argparse.Namespace) -> int:
1953
2122
  worst = max(worst, 1)
1954
2123
  if not compact:
1955
2124
  print(f"ccst pdata sync-check: {project}: {result.detail}", file=sys.stderr)
2125
+ elif result.outcome is sync_check.SyncOutcome.MIGRATION_PENDING:
2126
+ # Status, not an error and not a conflict: the project waits for `ccst pdata
2127
+ # migrate-uuid`, and the doctor check and pending-migration hook report that.
2128
+ if not compact:
2129
+ status = (
2130
+ f" (format 3 dump: {result.format3_status.value})"
2131
+ if result.format3_status is not None else ""
2132
+ )
2133
+ print(
2134
+ f"ccst pdata sync-check: {project}: migration pending{status} - "
2135
+ f"{result.detail}"
2136
+ )
1956
2137
  elif not compact:
1957
2138
  print(f"ccst pdata sync-check: {project}: {_sync_check_line(result)}")
1958
2139
 
@@ -1965,6 +2146,7 @@ def _cmd_pdata_sync_check(args: argparse.Namespace) -> int:
1965
2146
  f"conflict {counts[sync_check.SyncOutcome.CONFLICT.value]}, "
1966
2147
  f"deferred {counts[sync_check.SyncOutcome.DEFERRED.value]}, "
1967
2148
  f"occupied {counts[sync_check.SyncOutcome.OCCUPIED.value]}, "
2149
+ f"migration-pending {counts[sync_check.SyncOutcome.MIGRATION_PENDING.value]}, "
1968
2150
  f"errors {errors} of {len(projects)} project(s)"
1969
2151
  )
1970
2152
 
@@ -1974,6 +2156,67 @@ def _cmd_pdata_sync_check(args: argparse.Namespace) -> int:
1974
2156
  return worst
1975
2157
 
1976
2158
 
2159
+ def _cmd_pdata_migrate_uuid(args: argparse.Namespace) -> int:
2160
+ """`ccst pdata migrate-uuid`: every rule lives in `pdata.migrate_uuid`; this handler is the
2161
+ project loop, the printing and the exit status (the worst over all projects)."""
2162
+ projects = _pdata_projects(args)
2163
+ if not projects:
2164
+ print("ccst pdata migrate-uuid: no project databases found - nothing to migrate")
2165
+ return 0
2166
+ return _run_pdata_migrate_uuid(projects, write=args.write)
2167
+
2168
+
2169
+ def _run_pdata_migrate_uuid(projects: list[str], *, write: bool) -> int:
2170
+ """Run `migrate_uuid.migrate_project` over `projects`, printing each report; returns the
2171
+ worst exit status."""
2172
+ import sqlite3
2173
+
2174
+ from cc_session_tools.lib.pdata import migrate_uuid
2175
+
2176
+ worst = 0
2177
+ for project in projects:
2178
+ try:
2179
+ result = migrate_uuid.migrate_project(project, write=write)
2180
+ except (OSError, ValueError, sqlite3.Error) as exc:
2181
+ print(f"ccst pdata migrate-uuid: {project}: {exc}", file=sys.stderr)
2182
+ worst = max(worst, 2)
2183
+ continue
2184
+ status = migrate_uuid.exit_status(result.outcome)
2185
+ stream = sys.stderr if status else sys.stdout
2186
+ for line in migrate_uuid.render(result):
2187
+ print(line, file=stream)
2188
+ worst = max(worst, status)
2189
+ return worst
2190
+
2191
+
2192
+ def _cmd_pdata_migration_notice(args: argparse.Namespace) -> int:
2193
+ """`ccst pdata migration-notice`: for `install-everything.sh`. Prints nothing when no project
2194
+ database is format 3; otherwise the migrate-uuid dry run for those projects, then the framed
2195
+ banner. Always exits 0 - it is advice, and must not change the installer's exit status."""
2196
+ import sqlite3
2197
+
2198
+ from cc_session_tools.lib.pdata import formats, migrate_uuid, store, verify
2199
+
2200
+ unmigrated: list[str] = []
2201
+ for project in verify.discover_projects():
2202
+ try:
2203
+ fmt = formats.database_format(store.db_path(project))
2204
+ except (formats.UnknownPdataFormatError, sqlite3.DatabaseError) as exc:
2205
+ print(f"ccst pdata migration-notice: {project}: could not read the database "
2206
+ f"format ({exc}); see `ccst doctor`", file=sys.stderr)
2207
+ continue
2208
+ if fmt == formats.PDATA_FORMAT_INTEGER_IDS:
2209
+ unmigrated.append(project)
2210
+ if not unmigrated:
2211
+ return 0
2212
+ print(f"{formats.MIGRATE_COMMAND} --all-projects (dry run) for the format 3 projects:")
2213
+ _run_pdata_migrate_uuid(unmigrated, write=False)
2214
+ print()
2215
+ for line in migrate_uuid.install_banner(unmigrated):
2216
+ print(line)
2217
+ return 0
2218
+
2219
+
1977
2220
  def _sync_check_line(result: SyncCheckResult) -> str:
1978
2221
  """The one-line, --project-mode wording for each non-conflict sync-check outcome."""
1979
2222
  from cc_session_tools.lib.pdata import sync_check
@@ -3191,7 +3434,7 @@ def _build_parser() -> argparse.ArgumentParser:
3191
3434
 
3192
3435
  pdata_get_parser = pdata_sub.add_parser("get", help="Fetch a single record by id")
3193
3436
  pdata_get_parser.add_argument("--project", required=True, metavar="NAME")
3194
- pdata_get_parser.add_argument("--id", required=True, type=int)
3437
+ pdata_get_parser.add_argument("--id", required=True, type=_record_id_arg, metavar="UUID")
3195
3438
  pdata_get_parser.add_argument("--include-deleted", action="store_true")
3196
3439
 
3197
3440
  pdata_list_parser = pdata_sub.add_parser("list", help="List records in one record_group")
@@ -3222,7 +3465,7 @@ def _build_parser() -> argparse.ArgumentParser:
3222
3465
 
3223
3466
  pdata_update_parser = pdata_sub.add_parser("update", help="Update a record (version-checked)")
3224
3467
  pdata_update_parser.add_argument("--project", required=True, metavar="NAME")
3225
- pdata_update_parser.add_argument("--id", required=True, type=int)
3468
+ pdata_update_parser.add_argument("--id", required=True, type=_record_id_arg, metavar="UUID")
3226
3469
  pdata_update_parser.add_argument("--version", required=True, type=int, dest="version",
3227
3470
  metavar="EXPECTED_VERSION")
3228
3471
  pdata_update_parser.add_argument(
@@ -3248,13 +3491,13 @@ def _build_parser() -> argparse.ArgumentParser:
3248
3491
 
3249
3492
  pdata_delete_parser = pdata_sub.add_parser("delete", help="Soft-delete a record (version-checked)")
3250
3493
  pdata_delete_parser.add_argument("--project", required=True, metavar="NAME")
3251
- pdata_delete_parser.add_argument("--id", required=True, type=int)
3494
+ pdata_delete_parser.add_argument("--id", required=True, type=_record_id_arg, metavar="UUID")
3252
3495
  pdata_delete_parser.add_argument("--version", required=True, type=int,
3253
3496
  metavar="EXPECTED_VERSION")
3254
3497
 
3255
3498
  pdata_restore_parser = pdata_sub.add_parser("restore", help="Clear a soft-delete")
3256
3499
  pdata_restore_parser.add_argument("--project", required=True, metavar="NAME")
3257
- pdata_restore_parser.add_argument("--id", required=True, type=int)
3500
+ pdata_restore_parser.add_argument("--id", required=True, type=_record_id_arg, metavar="UUID")
3258
3501
 
3259
3502
  pdata_init_parser = pdata_sub.add_parser(
3260
3503
  "init", help="Initialize/migrate a project's data store (spec §7)"
@@ -3324,6 +3567,33 @@ def _build_parser() -> argparse.ArgumentParser:
3324
3567
  "(no file scan or registration)",
3325
3568
  )
3326
3569
 
3570
+ pdata_migrate_uuid_parser = pdata_sub.add_parser(
3571
+ "migrate-uuid",
3572
+ help="Convert format 3 (integer id) databases to format 4 (UUID ids). Dry run by "
3573
+ "default; run from a plain terminal with no `claude` session running",
3574
+ )
3575
+ migrate_uuid_target = pdata_migrate_uuid_parser.add_mutually_exclusive_group(required=True)
3576
+ migrate_uuid_target.add_argument("--project", metavar="NAME")
3577
+ migrate_uuid_target.add_argument(
3578
+ "--all-projects", action="store_true",
3579
+ help="Migrate every project with a .db under project-db/",
3580
+ )
3581
+ migrate_uuid_mode = pdata_migrate_uuid_parser.add_mutually_exclusive_group()
3582
+ migrate_uuid_mode.add_argument(
3583
+ "--dry-run", action="store_true",
3584
+ help="Print the plan and every precondition without changing anything (the default)",
3585
+ )
3586
+ migrate_uuid_mode.add_argument(
3587
+ "--write", action="store_true",
3588
+ help="Back up, migrate, verify, record the marker and publish the format 4 dump",
3589
+ )
3590
+
3591
+ pdata_sub.add_parser(
3592
+ "migration-notice",
3593
+ help="Print the migrate-uuid dry run and a framed banner when any project is still "
3594
+ "format 3; nothing otherwise (used by install-everything.sh; always exits 0)",
3595
+ )
3596
+
3327
3597
  pdata_verify_parser = pdata_sub.add_parser(
3328
3598
  "verify", help="Run the integrity-check backstop (spec §6.3/§8.2)"
3329
3599
  )
@@ -3362,7 +3632,7 @@ def _build_parser() -> argparse.ArgumentParser:
3362
3632
  )
3363
3633
 
3364
3634
  pdata_dump_parser = pdata_sub.add_parser(
3365
- "dump", help="Publish the current local DB state to .pdata-db-dump/latest.sql"
3635
+ "dump", help="Publish the current local DB state to .pdata-db-dump/v4/latest.sql"
3366
3636
  )
3367
3637
  dump_target = pdata_dump_parser.add_mutually_exclusive_group(required=True)
3368
3638
  dump_target.add_argument("--project", metavar="NAME")
@@ -3403,7 +3673,7 @@ def _build_parser() -> argparse.ArgumentParser:
3403
3673
  "--choice requires --project)",
3404
3674
  )
3405
3675
  pdata_resolve_parser.add_argument(
3406
- "--choice", action="append", default=[], metavar="ID=local|dump",
3676
+ "--choice", action="append", default=[], metavar="UUID=local|dump",
3407
3677
  help="Apply a per-record resolution; may repeat. Requires --project. Without any "
3408
3678
  "--choice, resolve runs in diagnostic (dry-run) mode and prints the diff.",
3409
3679
  )
@@ -3749,47 +4019,7 @@ def main() -> None:
3749
4019
  sys.exit(_cmd_gc_prune(args))
3750
4020
 
3751
4021
  if args.noun == "pdata":
3752
- if args.verb == "add":
3753
- sys.exit(_cmd_pdata_add(args))
3754
- if args.verb == "schema":
3755
- if args.subverb == "add-field":
3756
- sys.exit(_cmd_pdata_schema_add_field(args))
3757
- if args.subverb == "list":
3758
- sys.exit(_cmd_pdata_schema_list(args))
3759
- if args.subverb == "show":
3760
- sys.exit(_cmd_pdata_schema_show(args))
3761
- if args.verb == "get":
3762
- sys.exit(_cmd_pdata_get(args))
3763
- if args.verb == "list":
3764
- sys.exit(_cmd_pdata_list(args))
3765
- if args.verb == "query":
3766
- sys.exit(_cmd_pdata_query(args))
3767
- if args.verb == "update":
3768
- sys.exit(_cmd_pdata_update(args))
3769
- if args.verb == "delete":
3770
- sys.exit(_cmd_pdata_delete(args))
3771
- if args.verb == "restore":
3772
- sys.exit(_cmd_pdata_restore(args))
3773
- if args.verb == "init":
3774
- sys.exit(_cmd_pdata_init(args))
3775
- if args.verb == "reorganize":
3776
- sys.exit(_cmd_pdata_reorganize(args))
3777
- if args.verb == "rename-group":
3778
- sys.exit(_cmd_pdata_rename_group(args))
3779
- if args.verb == "reconcile-session-output":
3780
- sys.exit(_cmd_pdata_reconcile_session_output(args))
3781
- if args.verb == "verify":
3782
- sys.exit(_cmd_pdata_verify(args))
3783
- if args.verb == "readiness-scan":
3784
- sys.exit(_cmd_pdata_readiness_scan(args))
3785
- if args.verb == "dump":
3786
- sys.exit(_cmd_pdata_dump(args))
3787
- if args.verb == "rehydrate":
3788
- sys.exit(_cmd_pdata_rehydrate(args))
3789
- if args.verb == "resolve":
3790
- sys.exit(_cmd_pdata_resolve(args))
3791
- if args.verb == "sync-check":
3792
- sys.exit(_cmd_pdata_sync_check(args))
4022
+ sys.exit(_cmd_pdata(args))
3793
4023
 
3794
4024
  if args.noun == "machine-identity":
3795
4025
  if args.verb == "show":