cc-session-tools 3.2.0__tar.gz → 3.3.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 (279) hide show
  1. {cc_session_tools-3.2.0/src/cc_session_tools.egg-info → cc_session_tools-3.3.0}/PKG-INFO +1 -1
  2. {cc_session_tools-3.2.0 → cc_session_tools-3.3.0}/pyproject.toml +1 -1
  3. {cc_session_tools-3.2.0 → cc_session_tools-3.3.0}/src/cc_session_tools/cli/ccst.py +20 -5
  4. cc_session_tools-3.3.0/src/cc_session_tools/lib/pdata/cutover.py +90 -0
  5. {cc_session_tools-3.2.0 → cc_session_tools-3.3.0}/src/cc_session_tools/lib/pdata/init_service.py +70 -6
  6. {cc_session_tools-3.2.0 → cc_session_tools-3.3.0}/src/cc_session_tools/lib/pdata/manifest.py +9 -0
  7. {cc_session_tools-3.2.0 → cc_session_tools-3.3.0}/src/cc_session_tools/prompts/pdata-migration-claude-md-update.md +9 -0
  8. {cc_session_tools-3.2.0 → cc_session_tools-3.3.0}/src/cc_session_tools/prompts/pdata-migration-skills-update.md +9 -0
  9. {cc_session_tools-3.2.0 → cc_session_tools-3.3.0}/src/cc_session_tools/skills/pm-pdata-do-audit-and-prepare-to-migrate/SKILL.md +14 -5
  10. {cc_session_tools-3.2.0 → cc_session_tools-3.3.0}/src/cc_session_tools/skills/pm-pdata-do-migrate/SKILL.md +16 -0
  11. {cc_session_tools-3.2.0 → cc_session_tools-3.3.0}/src/cc_session_tools/skills/pm-project-init/SKILL.md +83 -0
  12. {cc_session_tools-3.2.0 → cc_session_tools-3.3.0}/src/cc_session_tools/skills/pm-project-layout-reference/SKILL.md +22 -0
  13. {cc_session_tools-3.2.0 → cc_session_tools-3.3.0/src/cc_session_tools.egg-info}/PKG-INFO +1 -1
  14. {cc_session_tools-3.2.0 → cc_session_tools-3.3.0}/tests/test_ccst_pdata_init_cli.py +29 -0
  15. cc_session_tools-3.2.0/src/cc_session_tools/lib/pdata/cutover.py +0 -34
  16. {cc_session_tools-3.2.0 → cc_session_tools-3.3.0}/LICENSE +0 -0
  17. {cc_session_tools-3.2.0 → cc_session_tools-3.3.0}/README.md +0 -0
  18. {cc_session_tools-3.2.0 → cc_session_tools-3.3.0}/setup.cfg +0 -0
  19. {cc_session_tools-3.2.0 → cc_session_tools-3.3.0}/src/cc_session_tools/__init__.py +0 -0
  20. {cc_session_tools-3.2.0 → cc_session_tools-3.3.0}/src/cc_session_tools/cli/__init__.py +0 -0
  21. {cc_session_tools-3.2.0 → cc_session_tools-3.3.0}/src/cc_session_tools/cli/ccd.py +0 -0
  22. {cc_session_tools-3.2.0 → cc_session_tools-3.3.0}/src/cc_session_tools/cli/ccmsg.py +0 -0
  23. {cc_session_tools-3.2.0 → cc_session_tools-3.3.0}/src/cc_session_tools/cli/ccr.py +0 -0
  24. {cc_session_tools-3.2.0 → cc_session_tools-3.3.0}/src/cc_session_tools/cli/ccs.py +0 -0
  25. {cc_session_tools-3.2.0 → cc_session_tools-3.3.0}/src/cc_session_tools/cli/ccsched.py +0 -0
  26. {cc_session_tools-3.2.0 → cc_session_tools-3.3.0}/src/cc_session_tools/cli/migrate_ccmsg.py +0 -0
  27. {cc_session_tools-3.2.0 → cc_session_tools-3.3.0}/src/cc_session_tools/cli/migrate_ccsched.py +0 -0
  28. {cc_session_tools-3.2.0 → cc_session_tools-3.3.0}/src/cc_session_tools/cli/migrate_sessions_db.py +0 -0
  29. {cc_session_tools-3.2.0 → cc_session_tools-3.3.0}/src/cc_session_tools/cli/migrate_telemetry.py +0 -0
  30. {cc_session_tools-3.2.0 → cc_session_tools-3.3.0}/src/cc_session_tools/config/hooks-bundle.json +0 -0
  31. {cc_session_tools-3.2.0 → cc_session_tools-3.3.0}/src/cc_session_tools/config/session-tag-hook.json +0 -0
  32. {cc_session_tools-3.2.0 → cc_session_tools-3.3.0}/src/cc_session_tools/hooks_install.py +0 -0
  33. {cc_session_tools-3.2.0 → cc_session_tools-3.3.0}/src/cc_session_tools/lib/__init__.py +0 -0
  34. {cc_session_tools-3.2.0 → cc_session_tools-3.3.0}/src/cc_session_tools/lib/claude_flags.py +0 -0
  35. {cc_session_tools-3.2.0 → cc_session_tools-3.3.0}/src/cc_session_tools/lib/claude_md_install.py +0 -0
  36. {cc_session_tools-3.2.0 → cc_session_tools-3.3.0}/src/cc_session_tools/lib/context_overrides.py +0 -0
  37. {cc_session_tools-3.2.0 → cc_session_tools-3.3.0}/src/cc_session_tools/lib/db.py +0 -0
  38. {cc_session_tools-3.2.0 → cc_session_tools-3.3.0}/src/cc_session_tools/lib/debug.py +0 -0
  39. {cc_session_tools-3.2.0 → cc_session_tools-3.3.0}/src/cc_session_tools/lib/doctor.py +0 -0
  40. {cc_session_tools-3.2.0 → cc_session_tools-3.3.0}/src/cc_session_tools/lib/doctor_mutes.py +0 -0
  41. {cc_session_tools-3.2.0 → cc_session_tools-3.3.0}/src/cc_session_tools/lib/hook_registry.py +0 -0
  42. {cc_session_tools-3.2.0 → cc_session_tools-3.3.0}/src/cc_session_tools/lib/install_sync.py +0 -0
  43. {cc_session_tools-3.2.0 → cc_session_tools-3.3.0}/src/cc_session_tools/lib/levenshtein.py +0 -0
  44. {cc_session_tools-3.2.0 → cc_session_tools-3.3.0}/src/cc_session_tools/lib/machine_identity.py +0 -0
  45. {cc_session_tools-3.2.0 → cc_session_tools-3.3.0}/src/cc_session_tools/lib/messaging/__init__.py +0 -0
  46. {cc_session_tools-3.2.0 → cc_session_tools-3.3.0}/src/cc_session_tools/lib/messaging/addressing.py +0 -0
  47. {cc_session_tools-3.2.0 → cc_session_tools-3.3.0}/src/cc_session_tools/lib/messaging/cursor.py +0 -0
  48. {cc_session_tools-3.2.0 → cc_session_tools-3.3.0}/src/cc_session_tools/lib/messaging/lock.py +0 -0
  49. {cc_session_tools-3.2.0 → cc_session_tools-3.3.0}/src/cc_session_tools/lib/messaging/message.py +0 -0
  50. {cc_session_tools-3.2.0 → cc_session_tools-3.3.0}/src/cc_session_tools/lib/messaging/move_safety.py +0 -0
  51. {cc_session_tools-3.2.0 → cc_session_tools-3.3.0}/src/cc_session_tools/lib/messaging/repository.py +0 -0
  52. {cc_session_tools-3.2.0 → cc_session_tools-3.3.0}/src/cc_session_tools/lib/messaging/retention.py +0 -0
  53. {cc_session_tools-3.2.0 → cc_session_tools-3.3.0}/src/cc_session_tools/lib/messaging/service.py +0 -0
  54. {cc_session_tools-3.2.0 → cc_session_tools-3.3.0}/src/cc_session_tools/lib/messaging/store.py +0 -0
  55. {cc_session_tools-3.2.0 → cc_session_tools-3.3.0}/src/cc_session_tools/lib/occupancy.py +0 -0
  56. {cc_session_tools-3.2.0 → cc_session_tools-3.3.0}/src/cc_session_tools/lib/paths.py +0 -0
  57. {cc_session_tools-3.2.0 → cc_session_tools-3.3.0}/src/cc_session_tools/lib/pdata/__init__.py +0 -0
  58. {cc_session_tools-3.2.0 → cc_session_tools-3.3.0}/src/cc_session_tools/lib/pdata/backup.py +0 -0
  59. {cc_session_tools-3.2.0 → cc_session_tools-3.3.0}/src/cc_session_tools/lib/pdata/classify.py +0 -0
  60. {cc_session_tools-3.2.0 → cc_session_tools-3.3.0}/src/cc_session_tools/lib/pdata/dump.py +0 -0
  61. {cc_session_tools-3.2.0 → cc_session_tools-3.3.0}/src/cc_session_tools/lib/pdata/formatting.py +0 -0
  62. {cc_session_tools-3.2.0 → cc_session_tools-3.3.0}/src/cc_session_tools/lib/pdata/importers.py +0 -0
  63. {cc_session_tools-3.2.0 → cc_session_tools-3.3.0}/src/cc_session_tools/lib/pdata/init_paths.py +0 -0
  64. {cc_session_tools-3.2.0 → cc_session_tools-3.3.0}/src/cc_session_tools/lib/pdata/naming.py +0 -0
  65. {cc_session_tools-3.2.0 → cc_session_tools-3.3.0}/src/cc_session_tools/lib/pdata/rehydrate.py +0 -0
  66. {cc_session_tools-3.2.0 → cc_session_tools-3.3.0}/src/cc_session_tools/lib/pdata/rename_group.py +0 -0
  67. {cc_session_tools-3.2.0 → cc_session_tools-3.3.0}/src/cc_session_tools/lib/pdata/reorganize.py +0 -0
  68. {cc_session_tools-3.2.0 → cc_session_tools-3.3.0}/src/cc_session_tools/lib/pdata/repository.py +0 -0
  69. {cc_session_tools-3.2.0 → cc_session_tools-3.3.0}/src/cc_session_tools/lib/pdata/resolve.py +0 -0
  70. {cc_session_tools-3.2.0 → cc_session_tools-3.3.0}/src/cc_session_tools/lib/pdata/service.py +0 -0
  71. {cc_session_tools-3.2.0 → cc_session_tools-3.3.0}/src/cc_session_tools/lib/pdata/session_output.py +0 -0
  72. {cc_session_tools-3.2.0 → cc_session_tools-3.3.0}/src/cc_session_tools/lib/pdata/store.py +0 -0
  73. {cc_session_tools-3.2.0 → cc_session_tools-3.3.0}/src/cc_session_tools/lib/pdata/sync_check.py +0 -0
  74. {cc_session_tools-3.2.0 → cc_session_tools-3.3.0}/src/cc_session_tools/lib/pdata/sync_lock.py +0 -0
  75. {cc_session_tools-3.2.0 → cc_session_tools-3.3.0}/src/cc_session_tools/lib/pdata/sync_notify.py +0 -0
  76. {cc_session_tools-3.2.0 → cc_session_tools-3.3.0}/src/cc_session_tools/lib/pdata/vector_clock.py +0 -0
  77. {cc_session_tools-3.2.0 → cc_session_tools-3.3.0}/src/cc_session_tools/lib/pdata/vector_clock_store.py +0 -0
  78. {cc_session_tools-3.2.0 → cc_session_tools-3.3.0}/src/cc_session_tools/lib/pdata/verify.py +0 -0
  79. {cc_session_tools-3.2.0 → cc_session_tools-3.3.0}/src/cc_session_tools/lib/pdata/write_log.py +0 -0
  80. {cc_session_tools-3.2.0 → cc_session_tools-3.3.0}/src/cc_session_tools/lib/picker.py +0 -0
  81. {cc_session_tools-3.2.0 → cc_session_tools-3.3.0}/src/cc_session_tools/lib/proc_lock.py +0 -0
  82. {cc_session_tools-3.2.0 → cc_session_tools-3.3.0}/src/cc_session_tools/lib/prompts.py +0 -0
  83. {cc_session_tools-3.2.0 → cc_session_tools-3.3.0}/src/cc_session_tools/lib/roots.py +0 -0
  84. {cc_session_tools-3.2.0 → cc_session_tools-3.3.0}/src/cc_session_tools/lib/rules.py +0 -0
  85. {cc_session_tools-3.2.0 → cc_session_tools-3.3.0}/src/cc_session_tools/lib/scheduler/__init__.py +0 -0
  86. {cc_session_tools-3.2.0 → cc_session_tools-3.3.0}/src/cc_session_tools/lib/scheduler/bundled_jobs.py +0 -0
  87. {cc_session_tools-3.2.0 → cc_session_tools-3.3.0}/src/cc_session_tools/lib/scheduler/cadence.py +0 -0
  88. {cc_session_tools-3.2.0 → cc_session_tools-3.3.0}/src/cc_session_tools/lib/scheduler/cursor.py +0 -0
  89. {cc_session_tools-3.2.0 → cc_session_tools-3.3.0}/src/cc_session_tools/lib/scheduler/digest.py +0 -0
  90. {cc_session_tools-3.2.0 → cc_session_tools-3.3.0}/src/cc_session_tools/lib/scheduler/due.py +0 -0
  91. {cc_session_tools-3.2.0 → cc_session_tools-3.3.0}/src/cc_session_tools/lib/scheduler/duration.py +0 -0
  92. {cc_session_tools-3.2.0 → cc_session_tools-3.3.0}/src/cc_session_tools/lib/scheduler/jobspec.py +0 -0
  93. {cc_session_tools-3.2.0 → cc_session_tools-3.3.0}/src/cc_session_tools/lib/scheduler/ledger.py +0 -0
  94. {cc_session_tools-3.2.0 → cc_session_tools-3.3.0}/src/cc_session_tools/lib/scheduler/lock.py +0 -0
  95. {cc_session_tools-3.2.0 → cc_session_tools-3.3.0}/src/cc_session_tools/lib/scheduler/notify.py +0 -0
  96. {cc_session_tools-3.2.0 → cc_session_tools-3.3.0}/src/cc_session_tools/lib/scheduler/reconcile.py +0 -0
  97. {cc_session_tools-3.2.0 → cc_session_tools-3.3.0}/src/cc_session_tools/lib/scheduler/registry.py +0 -0
  98. {cc_session_tools-3.2.0 → cc_session_tools-3.3.0}/src/cc_session_tools/lib/scheduler/runner.py +0 -0
  99. {cc_session_tools-3.2.0 → cc_session_tools-3.3.0}/src/cc_session_tools/lib/scheduler/state.py +0 -0
  100. {cc_session_tools-3.2.0 → cc_session_tools-3.3.0}/src/cc_session_tools/lib/scheduler/store.py +0 -0
  101. {cc_session_tools-3.2.0 → cc_session_tools-3.3.0}/src/cc_session_tools/lib/scheduler/surface.py +0 -0
  102. {cc_session_tools-3.2.0 → cc_session_tools-3.3.0}/src/cc_session_tools/lib/scheduler/throttle.py +0 -0
  103. {cc_session_tools-3.2.0 → cc_session_tools-3.3.0}/src/cc_session_tools/lib/scheduler/worker.py +0 -0
  104. {cc_session_tools-3.2.0 → cc_session_tools-3.3.0}/src/cc_session_tools/lib/session_gc.py +0 -0
  105. {cc_session_tools-3.2.0 → cc_session_tools-3.3.0}/src/cc_session_tools/lib/sessions.py +0 -0
  106. {cc_session_tools-3.2.0 → cc_session_tools-3.3.0}/src/cc_session_tools/lib/sessions_db.py +0 -0
  107. {cc_session_tools-3.2.0 → cc_session_tools-3.3.0}/src/cc_session_tools/lib/sessions_repair.py +0 -0
  108. {cc_session_tools-3.2.0 → cc_session_tools-3.3.0}/src/cc_session_tools/lib/shell_install.py +0 -0
  109. {cc_session_tools-3.2.0 → cc_session_tools-3.3.0}/src/cc_session_tools/lib/tasklist.py +0 -0
  110. {cc_session_tools-3.2.0 → cc_session_tools-3.3.0}/src/cc_session_tools/lib/telemetry_store.py +0 -0
  111. {cc_session_tools-3.2.0 → cc_session_tools-3.3.0}/src/cc_session_tools/skills/analyse-cc-usage/SKILL.md +0 -0
  112. {cc_session_tools-3.2.0 → cc_session_tools-3.3.0}/src/cc_session_tools/skills/clean-hook-sessions/SKILL.md +0 -0
  113. {cc_session_tools-3.2.0 → cc_session_tools-3.3.0}/src/cc_session_tools/skills/clean-hook-sessions/scripts/clean-hook-sessions.py +0 -0
  114. {cc_session_tools-3.2.0 → cc_session_tools-3.3.0}/src/cc_session_tools/skills/compact-smart/SKILL.md +0 -0
  115. {cc_session_tools-3.2.0 → cc_session_tools-3.3.0}/src/cc_session_tools/skills/context-override/SKILL.md +0 -0
  116. {cc_session_tools-3.2.0 → cc_session_tools-3.3.0}/src/cc_session_tools/skills/delete-sessions/SKILL.md +0 -0
  117. {cc_session_tools-3.2.0 → cc_session_tools-3.3.0}/src/cc_session_tools/skills/delete-sessions/scripts/delete_sessions.py +0 -0
  118. {cc_session_tools-3.2.0 → cc_session_tools-3.3.0}/src/cc_session_tools/skills/do-executor-critic-assessor-loop/SKILL.md +0 -0
  119. {cc_session_tools-3.2.0 → cc_session_tools-3.3.0}/src/cc_session_tools/skills/find-claude-code-session/SKILL.md +0 -0
  120. {cc_session_tools-3.2.0 → cc_session_tools-3.3.0}/src/cc_session_tools/skills/generate-8digit-code/SKILL.md +0 -0
  121. {cc_session_tools-3.2.0 → cc_session_tools-3.3.0}/src/cc_session_tools/skills/generate-8digit-code/scripts/generate_8digit_code.py +0 -0
  122. {cc_session_tools-3.2.0 → cc_session_tools-3.3.0}/src/cc_session_tools/skills/list-empty-sessions/SKILL.md +0 -0
  123. {cc_session_tools-3.2.0 → cc_session_tools-3.3.0}/src/cc_session_tools/skills/list-empty-sessions/scripts/list_empty_sessions.py +0 -0
  124. {cc_session_tools-3.2.0 → cc_session_tools-3.3.0}/src/cc_session_tools/skills/manage-recurring-cc-jobs-using-ccsched/SKILL.md +0 -0
  125. {cc_session_tools-3.2.0 → cc_session_tools-3.3.0}/src/cc_session_tools/skills/move-session/SKILL.md +0 -0
  126. {cc_session_tools-3.2.0 → cc_session_tools-3.3.0}/src/cc_session_tools/skills/move-session/hooks/sessionstart-pending-rename.sh +0 -0
  127. {cc_session_tools-3.2.0 → cc_session_tools-3.3.0}/src/cc_session_tools/skills/move-session/scripts/cc_session_rules.py +0 -0
  128. {cc_session_tools-3.2.0 → cc_session_tools-3.3.0}/src/cc_session_tools/skills/move-session/scripts/move_session.py +0 -0
  129. {cc_session_tools-3.2.0 → cc_session_tools-3.3.0}/src/cc_session_tools/skills/pm-pdata-conflict-resolution/SKILL.md +0 -0
  130. {cc_session_tools-3.2.0 → cc_session_tools-3.3.0}/src/cc_session_tools/skills/pm-pdata-schema-design/SKILL.md +0 -0
  131. {cc_session_tools-3.2.0 → cc_session_tools-3.3.0}/src/cc_session_tools/skills/pm-update-central-files/SKILL.md +0 -0
  132. {cc_session_tools-3.2.0 → cc_session_tools-3.3.0}/src/cc_session_tools/skills/reduce-persistent-context/CALIBRATION.md +0 -0
  133. {cc_session_tools-3.2.0 → cc_session_tools-3.3.0}/src/cc_session_tools/skills/reduce-persistent-context/SKILL.md +0 -0
  134. {cc_session_tools-3.2.0 → cc_session_tools-3.3.0}/src/cc_session_tools/skills/reduce-persistent-context/scripts/analyze_context.py +0 -0
  135. {cc_session_tools-3.2.0 → cc_session_tools-3.3.0}/src/cc_session_tools/skills/reduce-persistent-context/scripts/duplicates.py +0 -0
  136. {cc_session_tools-3.2.0 → cc_session_tools-3.3.0}/src/cc_session_tools/skills/reduce-persistent-context/scripts/measure.py +0 -0
  137. {cc_session_tools-3.2.0 → cc_session_tools-3.3.0}/src/cc_session_tools/skills/reduce-persistent-context/scripts/report.py +0 -0
  138. {cc_session_tools-3.2.0 → cc_session_tools-3.3.0}/src/cc_session_tools/skills/reduce-persistent-context/scripts/tokens.py +0 -0
  139. {cc_session_tools-3.2.0 → cc_session_tools-3.3.0}/src/cc_session_tools/skills/reduce-persistent-context/scripts/usage.py +0 -0
  140. {cc_session_tools-3.2.0 → cc_session_tools-3.3.0}/src/cc_session_tools/skills/select-agent-model/SKILL.md +0 -0
  141. {cc_session_tools-3.2.0 → cc_session_tools-3.3.0}/src/cc_session_tools/skills/send-session-message/SKILL.md +0 -0
  142. {cc_session_tools-3.2.0 → cc_session_tools-3.3.0}/src/cc_session_tools/skills/update-command-cache/SKILL.md +0 -0
  143. {cc_session_tools-3.2.0 → cc_session_tools-3.3.0}/src/cc_session_tools/skills/update-command-cache/scripts/update_command_cache.py +0 -0
  144. {cc_session_tools-3.2.0 → cc_session_tools-3.3.0}/src/cc_session_tools.egg-info/SOURCES.txt +0 -0
  145. {cc_session_tools-3.2.0 → cc_session_tools-3.3.0}/src/cc_session_tools.egg-info/dependency_links.txt +0 -0
  146. {cc_session_tools-3.2.0 → cc_session_tools-3.3.0}/src/cc_session_tools.egg-info/entry_points.txt +0 -0
  147. {cc_session_tools-3.2.0 → cc_session_tools-3.3.0}/src/cc_session_tools.egg-info/requires.txt +0 -0
  148. {cc_session_tools-3.2.0 → cc_session_tools-3.3.0}/src/cc_session_tools.egg-info/top_level.txt +0 -0
  149. {cc_session_tools-3.2.0 → cc_session_tools-3.3.0}/src/claude_code_usage/__init__.py +0 -0
  150. {cc_session_tools-3.2.0 → cc_session_tools-3.3.0}/src/claude_code_usage/attribution.py +0 -0
  151. {cc_session_tools-3.2.0 → cc_session_tools-3.3.0}/src/claude_code_usage/cache.py +0 -0
  152. {cc_session_tools-3.2.0 → cc_session_tools-3.3.0}/src/claude_code_usage/ccusage_wrapper.py +0 -0
  153. {cc_session_tools-3.2.0 → cc_session_tools-3.3.0}/src/claude_code_usage/cli.py +0 -0
  154. {cc_session_tools-3.2.0 → cc_session_tools-3.3.0}/src/claude_code_usage/data/pricing.json +0 -0
  155. {cc_session_tools-3.2.0 → cc_session_tools-3.3.0}/src/claude_code_usage/parent_inference.py +0 -0
  156. {cc_session_tools-3.2.0 → cc_session_tools-3.3.0}/src/claude_code_usage/parser.py +0 -0
  157. {cc_session_tools-3.2.0 → cc_session_tools-3.3.0}/src/claude_code_usage/pricing.py +0 -0
  158. {cc_session_tools-3.2.0 → cc_session_tools-3.3.0}/src/claude_code_usage/query.py +0 -0
  159. {cc_session_tools-3.2.0 → cc_session_tools-3.3.0}/src/claude_code_usage/report.py +0 -0
  160. {cc_session_tools-3.2.0 → cc_session_tools-3.3.0}/src/claude_code_usage/schema.py +0 -0
  161. {cc_session_tools-3.2.0 → cc_session_tools-3.3.0}/src/claude_code_usage/session_names.py +0 -0
  162. {cc_session_tools-3.2.0 → cc_session_tools-3.3.0}/src/hooks/__init__.py +0 -0
  163. {cc_session_tools-3.2.0 → cc_session_tools-3.3.0}/src/hooks/after_response.py +0 -0
  164. {cc_session_tools-3.2.0 → cc_session_tools-3.3.0}/src/hooks/bash_hard_deny.py +0 -0
  165. {cc_session_tools-3.2.0 → cc_session_tools-3.3.0}/src/hooks/bash_security_review.py +0 -0
  166. {cc_session_tools-3.2.0 → cc_session_tools-3.3.0}/src/hooks/cache.py +0 -0
  167. {cc_session_tools-3.2.0 → cc_session_tools-3.3.0}/src/hooks/catchup.py +0 -0
  168. {cc_session_tools-3.2.0 → cc_session_tools-3.3.0}/src/hooks/confirm_8digit.py +0 -0
  169. {cc_session_tools-3.2.0 → cc_session_tools-3.3.0}/src/hooks/context_window_warning.py +0 -0
  170. {cc_session_tools-3.2.0 → cc_session_tools-3.3.0}/src/hooks/last_screenshot.py +0 -0
  171. {cc_session_tools-3.2.0 → cc_session_tools-3.3.0}/src/hooks/marker_allow.py +0 -0
  172. {cc_session_tools-3.2.0 → cc_session_tools-3.3.0}/src/hooks/markers.py +0 -0
  173. {cc_session_tools-3.2.0 → cc_session_tools-3.3.0}/src/hooks/messaging_deliver.py +0 -0
  174. {cc_session_tools-3.2.0 → cc_session_tools-3.3.0}/src/hooks/model_info.py +0 -0
  175. {cc_session_tools-3.2.0 → cc_session_tools-3.3.0}/src/hooks/normalise.py +0 -0
  176. {cc_session_tools-3.2.0 → cc_session_tools-3.3.0}/src/hooks/pdata_sync.py +0 -0
  177. {cc_session_tools-3.2.0 → cc_session_tools-3.3.0}/src/hooks/pending_migration.py +0 -0
  178. {cc_session_tools-3.2.0 → cc_session_tools-3.3.0}/src/hooks/pending_rename.py +0 -0
  179. {cc_session_tools-3.2.0 → cc_session_tools-3.3.0}/src/hooks/session_tag.py +0 -0
  180. {cc_session_tools-3.2.0 → cc_session_tools-3.3.0}/src/hooks/stats.py +0 -0
  181. {cc_session_tools-3.2.0 → cc_session_tools-3.3.0}/src/hooks/telemetry.py +0 -0
  182. {cc_session_tools-3.2.0 → cc_session_tools-3.3.0}/src/hooks/telemetry_query.py +0 -0
  183. {cc_session_tools-3.2.0 → cc_session_tools-3.3.0}/src/hooks/telemetry_trim.py +0 -0
  184. {cc_session_tools-3.2.0 → cc_session_tools-3.3.0}/src/hooks/transcript.py +0 -0
  185. {cc_session_tools-3.2.0 → cc_session_tools-3.3.0}/src/hooks/worklog_guard.py +0 -0
  186. {cc_session_tools-3.2.0 → cc_session_tools-3.3.0}/tests/test_after_response.py +0 -0
  187. {cc_session_tools-3.2.0 → cc_session_tools-3.3.0}/tests/test_attribution.py +0 -0
  188. {cc_session_tools-3.2.0 → cc_session_tools-3.3.0}/tests/test_bash_hard_deny.py +0 -0
  189. {cc_session_tools-3.2.0 → cc_session_tools-3.3.0}/tests/test_bash_security_review.py +0 -0
  190. {cc_session_tools-3.2.0 → cc_session_tools-3.3.0}/tests/test_cache.py +0 -0
  191. {cc_session_tools-3.2.0 → cc_session_tools-3.3.0}/tests/test_cache_sqlite.py +0 -0
  192. {cc_session_tools-3.2.0 → cc_session_tools-3.3.0}/tests/test_ccr_orphans.py +0 -0
  193. {cc_session_tools-3.2.0 → cc_session_tools-3.3.0}/tests/test_ccs_emptiness.py +0 -0
  194. {cc_session_tools-3.2.0 → cc_session_tools-3.3.0}/tests/test_ccs_eta.py +0 -0
  195. {cc_session_tools-3.2.0 → cc_session_tools-3.3.0}/tests/test_ccs_list_mode.py +0 -0
  196. {cc_session_tools-3.2.0 → cc_session_tools-3.3.0}/tests/test_ccs_scope_flags.py +0 -0
  197. {cc_session_tools-3.2.0 → cc_session_tools-3.3.0}/tests/test_ccs_sentinel_sort.py +0 -0
  198. {cc_session_tools-3.2.0 → cc_session_tools-3.3.0}/tests/test_ccs_session_counts.py +0 -0
  199. {cc_session_tools-3.2.0 → cc_session_tools-3.3.0}/tests/test_ccst_bundle_discovery.py +0 -0
  200. {cc_session_tools-3.2.0 → cc_session_tools-3.3.0}/tests/test_ccst_ccsched_jobs_cli.py +0 -0
  201. {cc_session_tools-3.2.0 → cc_session_tools-3.3.0}/tests/test_ccst_cli.py +0 -0
  202. {cc_session_tools-3.2.0 → cc_session_tools-3.3.0}/tests/test_ccst_context_override_cli.py +0 -0
  203. {cc_session_tools-3.2.0 → cc_session_tools-3.3.0}/tests/test_ccst_doctor.py +0 -0
  204. {cc_session_tools-3.2.0 → cc_session_tools-3.3.0}/tests/test_ccst_gc_prune.py +0 -0
  205. {cc_session_tools-3.2.0 → cc_session_tools-3.3.0}/tests/test_ccst_gc_report.py +0 -0
  206. {cc_session_tools-3.2.0 → cc_session_tools-3.3.0}/tests/test_ccst_hook_dispatcher.py +0 -0
  207. {cc_session_tools-3.2.0 → cc_session_tools-3.3.0}/tests/test_ccst_hooks_install_prune.py +0 -0
  208. {cc_session_tools-3.2.0 → cc_session_tools-3.3.0}/tests/test_ccst_hooks_install_zero_arg.py +0 -0
  209. {cc_session_tools-3.2.0 → cc_session_tools-3.3.0}/tests/test_ccst_hooks_uninstall.py +0 -0
  210. {cc_session_tools-3.2.0 → cc_session_tools-3.3.0}/tests/test_ccst_install_everything.py +0 -0
  211. {cc_session_tools-3.2.0 → cc_session_tools-3.3.0}/tests/test_ccst_machine_identity_cli.py +0 -0
  212. {cc_session_tools-3.2.0 → cc_session_tools-3.3.0}/tests/test_ccst_main_gate.py +0 -0
  213. {cc_session_tools-3.2.0 → cc_session_tools-3.3.0}/tests/test_ccst_pdata_cli.py +0 -0
  214. {cc_session_tools-3.2.0 → cc_session_tools-3.3.0}/tests/test_ccst_pdata_reconcile_cli.py +0 -0
  215. {cc_session_tools-3.2.0 → cc_session_tools-3.3.0}/tests/test_ccst_pdata_rename_group_cli.py +0 -0
  216. {cc_session_tools-3.2.0 → cc_session_tools-3.3.0}/tests/test_ccst_pdata_reorganize_cli.py +0 -0
  217. {cc_session_tools-3.2.0 → cc_session_tools-3.3.0}/tests/test_ccst_pdata_sync_cli.py +0 -0
  218. {cc_session_tools-3.2.0 → cc_session_tools-3.3.0}/tests/test_ccst_pdata_verify_cli.py +0 -0
  219. {cc_session_tools-3.2.0 → cc_session_tools-3.3.0}/tests/test_ccst_repair_sessions_cli.py +0 -0
  220. {cc_session_tools-3.2.0 → cc_session_tools-3.3.0}/tests/test_ccst_sessions_cli.py +0 -0
  221. {cc_session_tools-3.2.0 → cc_session_tools-3.3.0}/tests/test_ccst_shell_install.py +0 -0
  222. {cc_session_tools-3.2.0 → cc_session_tools-3.3.0}/tests/test_ccst_skills_install.py +0 -0
  223. {cc_session_tools-3.2.0 → cc_session_tools-3.3.0}/tests/test_ccst_skills_uninstall.py +0 -0
  224. {cc_session_tools-3.2.0 → cc_session_tools-3.3.0}/tests/test_ccst_telemetry_query.py +0 -0
  225. {cc_session_tools-3.2.0 → cc_session_tools-3.3.0}/tests/test_ccst_telemetry_trim.py +0 -0
  226. {cc_session_tools-3.2.0 → cc_session_tools-3.3.0}/tests/test_ccusage_wrapper.py +0 -0
  227. {cc_session_tools-3.2.0 → cc_session_tools-3.3.0}/tests/test_claude_flags.py +0 -0
  228. {cc_session_tools-3.2.0 → cc_session_tools-3.3.0}/tests/test_claude_md_install.py +0 -0
  229. {cc_session_tools-3.2.0 → cc_session_tools-3.3.0}/tests/test_cli.py +0 -0
  230. {cc_session_tools-3.2.0 → cc_session_tools-3.3.0}/tests/test_cli_ccd.py +0 -0
  231. {cc_session_tools-3.2.0 → cc_session_tools-3.3.0}/tests/test_cli_ccd_dry_run.py +0 -0
  232. {cc_session_tools-3.2.0 → cc_session_tools-3.3.0}/tests/test_cli_ccr.py +0 -0
  233. {cc_session_tools-3.2.0 → cc_session_tools-3.3.0}/tests/test_cli_ccs.py +0 -0
  234. {cc_session_tools-3.2.0 → cc_session_tools-3.3.0}/tests/test_cli_version.py +0 -0
  235. {cc_session_tools-3.2.0 → cc_session_tools-3.3.0}/tests/test_confirm_8digit.py +0 -0
  236. {cc_session_tools-3.2.0 → cc_session_tools-3.3.0}/tests/test_context_overrides.py +0 -0
  237. {cc_session_tools-3.2.0 → cc_session_tools-3.3.0}/tests/test_context_window_warning.py +0 -0
  238. {cc_session_tools-3.2.0 → cc_session_tools-3.3.0}/tests/test_db.py +0 -0
  239. {cc_session_tools-3.2.0 → cc_session_tools-3.3.0}/tests/test_debug.py +0 -0
  240. {cc_session_tools-3.2.0 → cc_session_tools-3.3.0}/tests/test_doctor_mutes.py +0 -0
  241. {cc_session_tools-3.2.0 → cc_session_tools-3.3.0}/tests/test_empty_session.py +0 -0
  242. {cc_session_tools-3.2.0 → cc_session_tools-3.3.0}/tests/test_hook_cache.py +0 -0
  243. {cc_session_tools-3.2.0 → cc_session_tools-3.3.0}/tests/test_hooks_install.py +0 -0
  244. {cc_session_tools-3.2.0 → cc_session_tools-3.3.0}/tests/test_install_sync.py +0 -0
  245. {cc_session_tools-3.2.0 → cc_session_tools-3.3.0}/tests/test_last_screenshot.py +0 -0
  246. {cc_session_tools-3.2.0 → cc_session_tools-3.3.0}/tests/test_levenshtein.py +0 -0
  247. {cc_session_tools-3.2.0 → cc_session_tools-3.3.0}/tests/test_machine_identity.py +0 -0
  248. {cc_session_tools-3.2.0 → cc_session_tools-3.3.0}/tests/test_marker_allow.py +0 -0
  249. {cc_session_tools-3.2.0 → cc_session_tools-3.3.0}/tests/test_migrate_ccmsg.py +0 -0
  250. {cc_session_tools-3.2.0 → cc_session_tools-3.3.0}/tests/test_migrate_ccsched.py +0 -0
  251. {cc_session_tools-3.2.0 → cc_session_tools-3.3.0}/tests/test_migrate_sessions_db.py +0 -0
  252. {cc_session_tools-3.2.0 → cc_session_tools-3.3.0}/tests/test_migrate_telemetry.py +0 -0
  253. {cc_session_tools-3.2.0 → cc_session_tools-3.3.0}/tests/test_model_info.py +0 -0
  254. {cc_session_tools-3.2.0 → cc_session_tools-3.3.0}/tests/test_normalise.py +0 -0
  255. {cc_session_tools-3.2.0 → cc_session_tools-3.3.0}/tests/test_occupancy.py +0 -0
  256. {cc_session_tools-3.2.0 → cc_session_tools-3.3.0}/tests/test_parent_inference.py +0 -0
  257. {cc_session_tools-3.2.0 → cc_session_tools-3.3.0}/tests/test_parser.py +0 -0
  258. {cc_session_tools-3.2.0 → cc_session_tools-3.3.0}/tests/test_paths.py +0 -0
  259. {cc_session_tools-3.2.0 → cc_session_tools-3.3.0}/tests/test_pending_migration_hook.py +0 -0
  260. {cc_session_tools-3.2.0 → cc_session_tools-3.3.0}/tests/test_picker.py +0 -0
  261. {cc_session_tools-3.2.0 → cc_session_tools-3.3.0}/tests/test_pricing.py +0 -0
  262. {cc_session_tools-3.2.0 → cc_session_tools-3.3.0}/tests/test_proc_lock.py +0 -0
  263. {cc_session_tools-3.2.0 → cc_session_tools-3.3.0}/tests/test_prompts.py +0 -0
  264. {cc_session_tools-3.2.0 → cc_session_tools-3.3.0}/tests/test_query.py +0 -0
  265. {cc_session_tools-3.2.0 → cc_session_tools-3.3.0}/tests/test_report.py +0 -0
  266. {cc_session_tools-3.2.0 → cc_session_tools-3.3.0}/tests/test_roots.py +0 -0
  267. {cc_session_tools-3.2.0 → cc_session_tools-3.3.0}/tests/test_rules.py +0 -0
  268. {cc_session_tools-3.2.0 → cc_session_tools-3.3.0}/tests/test_scheduler_bundled_jobs.py +0 -0
  269. {cc_session_tools-3.2.0 → cc_session_tools-3.3.0}/tests/test_schema.py +0 -0
  270. {cc_session_tools-3.2.0 → cc_session_tools-3.3.0}/tests/test_session_names.py +0 -0
  271. {cc_session_tools-3.2.0 → cc_session_tools-3.3.0}/tests/test_session_tag.py +0 -0
  272. {cc_session_tools-3.2.0 → cc_session_tools-3.3.0}/tests/test_sessions.py +0 -0
  273. {cc_session_tools-3.2.0 → cc_session_tools-3.3.0}/tests/test_sessions_db.py +0 -0
  274. {cc_session_tools-3.2.0 → cc_session_tools-3.3.0}/tests/test_sessions_repair.py +0 -0
  275. {cc_session_tools-3.2.0 → cc_session_tools-3.3.0}/tests/test_tasklist.py +0 -0
  276. {cc_session_tools-3.2.0 → cc_session_tools-3.3.0}/tests/test_telemetry.py +0 -0
  277. {cc_session_tools-3.2.0 → cc_session_tools-3.3.0}/tests/test_telemetry_store.py +0 -0
  278. {cc_session_tools-3.2.0 → cc_session_tools-3.3.0}/tests/test_transcript.py +0 -0
  279. {cc_session_tools-3.2.0 → cc_session_tools-3.3.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.2.0
3
+ Version: 3.3.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
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "cc-session-tools"
7
- version = "3.2.0"
7
+ version = "3.3.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" }
@@ -173,14 +173,19 @@ def _print_migration_prompt_reminders(
173
173
  project_root: Path, *labeled_filenames: tuple[str, str]
174
174
  ) -> None:
175
175
  """Print one "<label>: <path>" reminder line per (label, filename) pair, for the bundled
176
- prompts/ directory, followed by a line telling the user to run it in a fresh Claude Code
177
- session started in project_root.
176
+ prompts/ directory, followed by a line telling the user to run it in a fresh context (a new
177
+ Claude Code session, or a dispatched Agent subagent) started in project_root.
178
178
 
179
179
  Each prompt's own "Run it via" section already spells out the `cd <project> && claude -p
180
180
  ...` invocation (see e.g. pdata-migration-skills-update.md) - that only works if the
181
181
  session's cwd is the project root, since every prompt's Step 1 checks for it and aborts
182
- otherwise. Printing the reminder without saying so reads as "open this file", which invites
183
- running it inline in whatever session called `ccst pdata init` - the wrong cwd entirely.
182
+ otherwise. What the fresh-context requirement is actually protecting against is an
183
+ orchestrating session rationalizing away a stale reference it half-remembers writing
184
+ earlier in the same conversation - a dispatched Agent subagent (same cwd) gets that same
185
+ benefit without shelling out to a literal second process, so both are offered here rather
186
+ than only the `claude -p` invocation. Printing the reminder without saying so reads as "open
187
+ this file", which invites running it inline in whatever session called `ccst pdata init` -
188
+ the wrong cwd entirely.
184
189
 
185
190
  Best-effort, deliberately swallowing a missing prompts/ directory: these reminders are a
186
191
  nicety layered on top of an otherwise-already-completed classification or migration, not
@@ -197,7 +202,8 @@ def _print_migration_prompt_reminders(
197
202
  print(f"{label}: {prompts_dir / filename}")
198
203
  print(
199
204
  f" Run in a new Claude Code session started in {project_root} (its own "
200
- f"\"Run it via\" section has the exact command) - not in this session."
205
+ f"\"Run it via\" section has the exact command), or dispatch it as an Agent "
206
+ f"subagent with the same cwd - not inline in this session."
201
207
  )
202
208
 
203
209
 
@@ -1178,6 +1184,7 @@ def _cmd_pdata_init(args: argparse.Namespace) -> int:
1178
1184
  try:
1179
1185
  write_result = init_service.write(
1180
1186
  project=args.project, rehearse=rehearse, on_progress=print,
1187
+ leave_no_pointer_files=args.leave_no_pointer_files,
1181
1188
  )
1182
1189
  except (FileNotFoundError, ValueError) as exc:
1183
1190
  print(f"ccst pdata: {exc}", file=sys.stderr)
@@ -3035,6 +3042,14 @@ def _build_parser() -> argparse.ArgumentParser:
3035
3042
  "<project>/ccst-pdata-init-write.log"
3036
3043
  ),
3037
3044
  )
3045
+ pdata_init_parser.add_argument(
3046
+ "--leave-no-pointer-files", action="store_true",
3047
+ help=(
3048
+ "By default, --write leaves a Markdown pointer file at each migrated entry's "
3049
+ "original path (record_group, schema, an example query command). Pass this "
3050
+ "flag to suppress that and leave nothing behind at the original path."
3051
+ ),
3052
+ )
3038
3053
 
3039
3054
  pdata_reorganize_parser = pdata_sub.add_parser(
3040
3055
  "reorganize", help="Split a flat folder into a nested (by-year or by-year-month) structure"
@@ -0,0 +1,90 @@
1
+ """Cutover: archive migrated-source originals, never delete them (spec §7.1 steps 6-7)."""
2
+ from __future__ import annotations
3
+
4
+ import time
5
+ from pathlib import Path
6
+
7
+ from cc_session_tools.lib.pdata.init_paths import (
8
+ MIGRATED_ARCHIVE_DIRNAME,
9
+ MIGRATED_MANIFEST_FILENAME,
10
+ )
11
+ from cc_session_tools.lib.pdata.manifest import ManifestEntry
12
+
13
+
14
+ def _pointer_file_content(*, project: str, entry: ManifestEntry) -> str:
15
+ """Generic pointer content (spec pdata/init-pointer-files): the record_group now
16
+ holding this file's data, its field/schema table, and an example query — plus,
17
+ when the entry carries preface_text (set by hand per pm-project-init's
18
+ garbled-CSV-header fix recipe), that text verbatim under its own heading."""
19
+ lines = [
20
+ f"# {Path(entry.path).name} — migrated to pdata",
21
+ "",
22
+ f"This file's data now lives in pdata record group `{entry.db_group()}` for "
23
+ f"project `{project}`. The original was moved to "
24
+ f"`{MIGRATED_ARCHIVE_DIRNAME}/{entry.path}` and is no longer read from here.",
25
+ "",
26
+ "## Schema",
27
+ "",
28
+ "| field | sql_type | description |",
29
+ "| --- | --- | --- |",
30
+ ]
31
+ for spec in entry.fields:
32
+ lines.append(f"| {spec.name} | {spec.sql_type} | {spec.description or ''} |")
33
+ lines += [
34
+ "",
35
+ "## Example query",
36
+ "",
37
+ "```sh",
38
+ f"ccst pdata list --project {project} --group {entry.db_group()}",
39
+ "```",
40
+ ]
41
+ if entry.preface_text:
42
+ lines += [
43
+ "",
44
+ "## Preserved preface text (removed from the original file before migration)",
45
+ "",
46
+ entry.preface_text,
47
+ ]
48
+ return "\n".join(lines) + "\n"
49
+
50
+
51
+ def _write_pointer_file(*, project_root: Path, project: str, entry: ManifestEntry) -> None:
52
+ pointer_path = project_root / Path(entry.path).with_suffix(".md")
53
+ pointer_path.parent.mkdir(parents=True, exist_ok=True)
54
+ pointer_path.write_text(
55
+ _pointer_file_content(project=project, entry=entry), encoding="utf-8",
56
+ )
57
+
58
+
59
+ def archive_entries(
60
+ *, project_root: Path, entries: list[ManifestEntry], project: str,
61
+ write_pointer_files: bool = True,
62
+ ) -> None:
63
+ """Move every db-owned entry's source file into project_root/.pdata-migrated/,
64
+ preserving its relative path, and append one line per entry to MANIFEST.md.
65
+ Never deletes — cutover only relocates within project_root (spec §7.1 step 6);
66
+ deleting the archive is a manual, human-directed action (step 7).
67
+
68
+ By default (write_pointer_files=True, spec pdata/init-pointer-files), also writes a
69
+ Markdown pointer file back at each entry's original path so anything else in the
70
+ project that cites the old path by name finds a live signpost instead of a silent
71
+ 404. Pass write_pointer_files=False (--leave-no-pointer-files at the CLI) to
72
+ suppress this."""
73
+ if not entries:
74
+ return
75
+ archive_root = project_root / MIGRATED_ARCHIVE_DIRNAME
76
+ archive_root.mkdir(parents=True, exist_ok=True)
77
+ manifest_path = archive_root / MIGRATED_MANIFEST_FILENAME
78
+ now = time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime())
79
+ with manifest_path.open("a", encoding="utf-8") as log:
80
+ for entry in entries:
81
+ source = project_root / entry.path
82
+ destination = archive_root / entry.path
83
+ destination.parent.mkdir(parents=True, exist_ok=True)
84
+ source.rename(destination)
85
+ log.write(
86
+ f"- {now} — {entry.path} — migrated source, superseded by ccst pdata "
87
+ f"(record_group={entry.db_group()})\n"
88
+ )
89
+ if write_pointer_files:
90
+ _write_pointer_file(project_root=project_root, project=project, entry=entry)
@@ -8,7 +8,7 @@ import csv
8
8
  import sqlite3
9
9
  from collections.abc import Callable
10
10
  from dataclasses import dataclass
11
- from datetime import datetime
11
+ from datetime import UTC, datetime
12
12
  from pathlib import Path
13
13
 
14
14
  from cc_session_tools.lib import db
@@ -96,6 +96,29 @@ def dry_run(*, project: str, rehearse: Path | None = None) -> DryRunResult:
96
96
  return DryRunResult(manifest=m, report=_render_report(m), proposal_path=proposal_path)
97
97
 
98
98
 
99
+ # A field name proposed by _classify_csv is derived from a real CSV header cell (see
100
+ # classify._slugify_field_name) — an ordinary column name slugifies to something short.
101
+ # A garbled header (prose/provenance text on the "header" row, spec pdata/
102
+ # init-classification-report) instead produces long, many-word sentence-fragment names
103
+ # like `pending_chris_s_manual_review`. Neither threshold alone is exact, but a name
104
+ # tripping either is worth a reviewer's second look.
105
+ _SUSPICIOUS_FIELD_NAME_CHAR_THRESHOLD = 40
106
+ _SUSPICIOUS_FIELD_NAME_WORD_THRESHOLD = 5
107
+
108
+
109
+ def _looks_like_garbled_header(field_names: list[str]) -> bool:
110
+ """True when multiple proposed field names read as sentence fragments rather than
111
+ short domain nouns — see spec pdata/init-classification-report. A single long name
112
+ isn't enough on its own (a legitimately verbose but real column header shouldn't be
113
+ flagged); this looks for the pattern repeating across an entry's fields."""
114
+ suspicious = sum(
115
+ 1 for name in field_names
116
+ if len(name) > _SUSPICIOUS_FIELD_NAME_CHAR_THRESHOLD
117
+ or name.count("_") + 1 >= _SUSPICIOUS_FIELD_NAME_WORD_THRESHOLD
118
+ )
119
+ return suspicious >= 2
120
+
121
+
99
122
  def _render_report(m: Manifest) -> str:
100
123
  if not m.entries:
101
124
  return f"ccst pdata init — {m.project}: no files found, empty base schema created."
@@ -109,6 +132,12 @@ def _render_report(m: Manifest) -> str:
109
132
  f" [db-owned] {e.path} -> group={e.record_group} "
110
133
  f"strategy={e.strategy} fields={field_names}"
111
134
  )
135
+ if _looks_like_garbled_header(field_names):
136
+ lines.append(
137
+ " ⚠ worth double-checking — these field names read as sentence "
138
+ "fragments, not column names; this file's header may not be what it "
139
+ "looks like (see pm-project-init's garbled-CSV-header caveat)"
140
+ )
112
141
  lines.append(
113
142
  "Review/override entries in the proposal file listed below before running --write."
114
143
  )
@@ -352,6 +381,7 @@ def _adopt_from_dump(
352
381
  def write(
353
382
  *, project: str, rehearse: Path | None = None,
354
383
  on_progress: Callable[[str], None] | None = None,
384
+ leave_no_pointer_files: bool = False,
355
385
  ) -> WriteResult:
356
386
  project_root = init_paths.resolve_project_root(project, rehearse=rehearse)
357
387
 
@@ -396,11 +426,33 @@ def write(
396
426
  _check_db_not_locked(project)
397
427
 
398
428
  db_owned = [e for e in m.entries if e.classification == "db-owned"]
399
- _emit(on_progress, f"Importing {len(db_owned)} file(s)...")
400
- for entry in m.entries:
401
- if entry.classification != "db-owned":
402
- continue
429
+ already_migrated = [e for e in db_owned if e.migrated_at is not None]
430
+ to_import = [e for e in db_owned if e.migrated_at is None]
431
+ _emit(on_progress, f"Importing {len(to_import)} file(s)...")
432
+ if already_migrated:
433
+ _emit(
434
+ on_progress,
435
+ f" skipping {len(already_migrated)} already-migrated file(s): "
436
+ f"{', '.join(e.path for e in already_migrated)}",
437
+ )
438
+ for entry in to_import:
403
439
  try:
440
+ source_path = project_root / entry.path
441
+ if not source_path.exists():
442
+ # entry.migrated_at is None here (to_import excludes already-migrated
443
+ # entries above), so this is not the ordinary "already cut over by an
444
+ # earlier --write" case — the source is genuinely missing for an entry
445
+ # this run believes still needs importing. Fail with a specific,
446
+ # actionable message (still caught by this loop's own except clause
447
+ # below, so it contributes to `reasons`/rollback exactly like any other
448
+ # per-entry import failure) instead of a bare FileNotFoundError only
449
+ # discoverable via the rollback reasons list.
450
+ raise ValueError(
451
+ f"{entry.path}: marked db-owned with no migrated_at, but its source "
452
+ f"file does not exist at {source_path} — if this entry was already "
453
+ f"migrated by another means, set its classification to folder-owned "
454
+ f"(or otherwise correct the manifest) before running --write again"
455
+ )
404
456
  _emit(on_progress, f" importing {entry.path} -> group={entry.db_group()}...")
405
457
  for spec in entry.fields:
406
458
  service.schema_add_field(
@@ -456,7 +508,19 @@ def write(
456
508
  )
457
509
 
458
510
  _emit(on_progress, f"Cutting over {len(written_entries)} file(s)...")
459
- cutover.archive_entries(project_root=project_root, entries=written_entries)
511
+ cutover.archive_entries(
512
+ project_root=project_root, entries=written_entries, project=project,
513
+ write_pointer_files=not leave_no_pointer_files,
514
+ )
515
+ # Mark every entry this run actually cut over as migrated, and persist that back to
516
+ # the manifest — write() only ever `manifest.load`s otherwise, so without this a later
517
+ # --write would re-attempt (and, pre-fix, roll back) these same now-archived entries.
518
+ # written_entries holds the same ManifestEntry objects m.entries does (see the
519
+ # `to_import`/`written_entries.append(entry)` loop above), so mutating here mutates m.
520
+ migrated_at = datetime.now(UTC).strftime("%Y-%m-%dT%H:%M:%SZ")
521
+ for entry in written_entries:
522
+ entry.migrated_at = migrated_at
523
+ manifest.save(m, proposal_path)
460
524
  return WriteResult(
461
525
  created_record_ids=created_ids,
462
526
  entries_written=[e.path for e in written_entries],
@@ -40,6 +40,15 @@ class ManifestEntry:
40
40
  content_column: str | None = None
41
41
  file_path_column: str | None = None
42
42
  fields: list[FieldSpec] = field(default_factory=list)
43
+ # Set by init_service.write() on successful cutover (ISO-8601 UTC) — a later
44
+ # --write skips any entry that already carries one instead of re-importing it
45
+ # (spec pdata/init-write-idempotency). None for an entry never yet migrated.
46
+ migrated_at: str | None = None
47
+ # Hand-set during review (pm-project-init's garbled-CSV-header fix recipe):
48
+ # header/provenance text stripped from the source file before migration,
49
+ # preserved verbatim here so cutover.archive_entries can carry it into the
50
+ # entry's pointer file (spec pdata/init-pointer-files). None when not applicable.
51
+ preface_text: str | None = None
43
52
 
44
53
  def __post_init__(self) -> None:
45
54
  if self.classification not in _VALID_CLASSIFICATIONS:
@@ -12,6 +12,15 @@ cd ~/cc/<project> && \
12
12
  claude -p "Check that you are executing with a ~/cc/<project> directory as your cwd, containing a CLAUDE.md. If you are not then exit. If you are then use this file as your prompt: <path-to-cc-session-tools>/src/cc_session_tools/prompts/pdata-migration-claude-md-update.md"
13
13
  ```
14
14
 
15
+ **What matters is a fresh context, not literally a new process.** The reason this prompt asks
16
+ for a new session is a genuinely fresh, unbiased read that won't rationalize away a stale
17
+ reference it half-remembers writing earlier in the same conversation. An orchestrating session
18
+ that just ran `ccst pdata init --write` can dispatch this prompt as an `Agent` subagent (with its
19
+ cwd set to the project root) instead of shelling out to a literal `claude -p` process — a
20
+ dispatched subagent gets the same fresh-context benefit without leaving the orchestrating
21
+ session's control flow. Either is acceptable; what is not acceptable is running this prompt
22
+ inline in the same context that just did the migration.
23
+
15
24
  ---
16
25
 
17
26
  ## Step 1 — Verify project context
@@ -12,6 +12,15 @@ cd ~/cc/<project> && \
12
12
  claude -p "Check that you are executing with a ~/cc/<project> directory as your cwd. If you are not then exit. If you are then use this file as your prompt: <path-to-cc-session-tools>/src/cc_session_tools/prompts/pdata-migration-skills-update.md"
13
13
  ```
14
14
 
15
+ **What matters is a fresh context, not literally a new process.** The reason this prompt asks
16
+ for a new session is a genuinely fresh, unbiased read that won't rationalize away a stale
17
+ reference it half-remembers writing earlier in the same conversation. An orchestrating session
18
+ that just ran `ccst pdata init --write` can dispatch this prompt as an `Agent` subagent (with its
19
+ cwd set to the project root) instead of shelling out to a literal `claude -p` process — a
20
+ dispatched subagent gets the same fresh-context benefit without leaving the orchestrating
21
+ session's control flow. Either is acceptable; what is not acceptable is running this prompt
22
+ inline in the same context that just did the migration.
23
+
15
24
  ---
16
25
 
17
26
  ## Step 1 — Verify project context
@@ -1,10 +1,16 @@
1
1
  ---
2
2
  name: pm-pdata-do-audit-and-prepare-to-migrate
3
- description: Full consistency/completeness audit of a project's central and recordkeeping files - OneDrive sync-conflict cleanup, cross-checking every index/register/log against what it claims to describe, and a pdata-migration readiness pass. Ends by writing a readiness manifest file that the companion `pm-pdata-do-migrate` skill consumes to actually run the migration. Triggers on "review all my central files", "audit the project files", "check everything is up to date and consistent", "get this project ready for pdata", "/pm-pdata-do-audit-and-prepare-to-migrate", or any request for a thorough review/health-check of a project's index/log/register files (as opposed to a single-session wrap-up, which is pm-update-central-files). Especially relevant when a project is scheduled to migrate to pdata - this audit is the recommended prerequisite pass, since a schema design should not be built against files that still have unresolved sync-conflict duplicates or known dangling rows.
3
+ description: Full consistency/completeness audit of a project's central and recordkeeping files - OneDrive sync-conflict cleanup, cross-checking every index/register/log against what it claims to describe, and a pdata-migration readiness pass. Ends by writing a readiness manifest file that feeds `ccst pdata init` (via `pm-project-init`) - the tool-native migration mechanism - and, only for the narrow record shapes it can't express, the deprecated `pm-pdata-do-migrate` fallback. Triggers on "review all my central files", "audit the project files", "check everything is up to date and consistent", "get this project ready for pdata", "/pm-pdata-do-audit-and-prepare-to-migrate", or any request for a thorough review/health-check of a project's index/log/register files (as opposed to a single-session wrap-up, which is pm-update-central-files). Especially relevant when a project is scheduled to migrate to pdata - this audit is the recommended prerequisite pass, since a schema design should not be built against files that still have unresolved sync-conflict duplicates or known dangling rows.
4
4
  ---
5
5
 
6
6
  # Audit central files
7
7
 
8
+ > **This audit feeds `ccst pdata init`, not a manual migration.** The readiness manifest this
9
+ > skill produces (Phase 5) is a pre-flight pass for `ccst pdata init` (driven by the
10
+ > `pm-project-init` skill) - the tool-native, default migration mechanism. `pm-pdata-do-migrate`
11
+ > is a deprecated fallback for record shapes `ccst pdata init` genuinely can't express; it is not
12
+ > the default next step after this audit.
13
+
8
14
  ## When to use this vs. `pm-update-central-files`
9
15
 
10
16
  `pm-update-central-files` is a lightweight, single-session wrap-up: rewrite WORKLOG, apply
@@ -134,7 +140,7 @@ This file is deliberately NOT a migration plan - it's a findings list for whoeve
134
140
  `pm-pdata-schema-design` session next, so that work isn't designed against a still-messy
135
141
  substrate.
136
142
 
137
- ## Phase 5: write the readiness manifest (for `pm-pdata-do-migrate` to consume)
143
+ ## Phase 5: write the readiness manifest (for `ccst pdata init`/`pm-project-init` to consume)
138
144
 
139
145
  Once the audit reaches a settled state (per your own stopping rule - typically two consecutive
140
146
  clean verification rounds, or a documented pragmatic stop), write one machine-and-human-readable
@@ -145,7 +151,9 @@ otherwise the project's own top-level docs location. Its existence and location
145
151
  recorded in the project's own instructions file (`CLAUDE.md`/`AGENTS.md`/equivalent) so a future
146
152
  session doesn't have to search for it.
147
153
 
148
- The manifest must have, in this order, so `pm-pdata-do-migrate` can parse it deterministically:
154
+ The manifest must have, in this order, so a `pm-project-init` session running `ccst pdata init`
155
+ (or, for the narrow record shapes it can't express, the deprecated `pm-pdata-do-migrate`
156
+ fallback) can parse it deterministically:
149
157
 
150
158
  1. **Header**: audit date, session/agent identifier, and an explicit "STALE IF" note - state the
151
159
  condition that would invalidate this manifest (e.g. "stale if any of the files below have
@@ -154,7 +162,7 @@ The manifest must have, in this order, so `pm-pdata-do-migrate` can parse it det
154
162
  row/entry count, last-verified-clean date, and its role (`index`, `register`, `log`,
155
163
  `narrative`, `derived-artefact`).
156
164
  3. **Per-file migration blockers**, in the exact shape `pm-pdata-schema-design` and
157
- `pm-pdata-do-migrate` expect to consume - this is the same content this skill's own Phase 4
165
+ `pm-project-init` expect to consume - this is the same content this skill's own Phase 4
158
166
  already produces, just formalised into the manifest rather than a standalone prose report:
159
167
  inconsistent separators, unstable/non-existent natural keys, mixed date formats, multi-schema
160
168
  single files, machine-specific paths, null-value conventions that aren't real nulls.
@@ -165,7 +173,8 @@ The manifest must have, in this order, so `pm-pdata-do-migrate` can parse it det
165
173
  a migration pass doesn't waste time re-flagging it or, worse, silently drops one copy.
166
174
  6. **Open decisions** - anything the audit found but explicitly did NOT resolve because it needed
167
175
  the project owner's judgement (scope calls, curation completeness, which of two forks is
168
- canonical). List these plainly; `pm-pdata-do-migrate` must not treat an unresolved item as settled.
176
+ canonical). List these plainly; a later migration session must not treat an unresolved item as
177
+ settled.
169
178
 
170
179
  ## Common mistakes
171
180
 
@@ -5,6 +5,22 @@ description: Execute a project's actual migration from flat central/index files
5
5
 
6
6
  # Migrate a project to pdata
7
7
 
8
+ > **Deprecated fallback — `ccst pdata init` is the standard mechanism.** `ccst pdata init`
9
+ > (driven by the `pm-project-init` skill) is the tool-native migration mechanism: classification,
10
+ > a human-reviewable manifest, backup, verify, atomic cutover, and shipped post-write doc/skill-
11
+ > update prompts. It is the default choice wherever it is available, and this skill is superseded
12
+ > by it for any file `ccst pdata init`'s classifier can already handle (a normal CSV/JSON file
13
+ > with a real header row). This skill remains only as a deprecated manual fallback for the narrow
14
+ > case where the programmatic approach genuinely doesn't fit - e.g. a record shape none of `ccst
15
+ > pdata init`'s five import strategies (`whole-file`, `delimited-sections`, `csv-rows`,
16
+ > `json-array-rows`, `json-singleton`) can express.
17
+ >
18
+ > **Before doing anything else, stop and get the user's explicit confirmation** that: (a) they
19
+ > understand `ccst pdata init` is the standard, tool-native mechanism and this skill is a
20
+ > deprecated fallback; and (b) they have a specific reason the programmatic approach doesn't fit
21
+ > this case. Do not proceed to Step 1, or any later step, without that confirmation - if the user
22
+ > hasn't already stated a reason, ask them for one before continuing.
23
+
8
24
  ## When to use this vs. the other pm-pdata-* skills
9
25
 
10
26
  - `pm-pdata-do-audit-and-prepare-to-migrate` - readiness audit. Finds and fixes problems in the CURRENT flat files.
@@ -62,6 +62,51 @@ that renames a `record_group` to match an existing one is **not** re-checked. Be
62
62
  proposal, scan it yourself for two entries sharing a `record_group` that you did not deliberately
63
63
  intend to merge.
64
64
 
65
+ ### Watch for garbled CSV headers
66
+
67
+ A CSV whose first row is actually prose/provenance text (a `#`-prefixed comment, or a multi-
68
+ sentence caveat wrapped in CSV quoting) rather than real column names produces a `[db-owned]`
69
+ entry whose proposed field names read as garbled sentence fragments (e.g.
70
+ `pending_chris_s_manual_review`, `generated_2026_08_12_do_not_edit`) rather than short domain
71
+ nouns - syntactically valid-looking identifiers that don't obviously scream "wrong" on a fast
72
+ skim. The dry-run report flags a likely case of this automatically (a "worth double-checking"
73
+ annotation on the entry's line), but always eyeball field-name **shape**, not just presence, for
74
+ every `[db-owned]` entry - the annotation is a heuristic, not a guarantee.
75
+
76
+ The content on a garbled header line is usually legitimate and valuable (provenance/caveat text a
77
+ later reader genuinely needs), not junk to discard. Fix recipe:
78
+
79
+ 1. Read past the suspect first line to confirm a clean, comma-separated real header exists
80
+ further down (usually line 2).
81
+ 2. Strip the bad line(s) from the source CSV, preserving the removed text exactly - you'll need it
82
+ in step 4.
83
+ 3. Correct the manifest entry: fix `fields` to match the real header, set a real `content_column`,
84
+ confirm `classification: db-owned`.
85
+ 4. Set the entry's `preface_text` field (in `.pdata-migration-manifest.json`) to the exact text you
86
+ removed in step 2. `--write`'s cutover step carries this into the entry's pointer file (Step 7)
87
+ automatically, under its own heading - you don't need to compose that by hand.
88
+
89
+ ### Also check every `db-owned` entry against two deferred-file criteria
90
+
91
+ Beyond field-name shape, review every `db-owned` entry (not just folder-owned markdown/text files)
92
+ against two further questions before approving the proposal - a structurally clean CSV can still
93
+ be a poor migration candidate for reasons the classifier has no way to know about:
94
+
95
+ - **Does this file live somewhere the project's own docs say only a human should touch?** (e.g. a
96
+ `references/` folder documented as "only Chris adds/removes/reorganises files here"). Migrating
97
+ it moves the original out of that folder as part of cutover, which conflicts with the folder's
98
+ documented purpose. If so, flip the entry to `folder-owned` and flag it explicitly to Chris as a
99
+ real open decision - do not silently include or silently exclude it.
100
+ - **How does this source file actually get updated going forward - incrementally appended, or
101
+ periodically replaced wholesale with a fresh full export?** An append-only file (new rows added
102
+ over time, however often) is a good migration candidate regardless of update frequency. A file
103
+ whose maintenance model is "drop in a new full export periodically" is a poor candidate under
104
+ the current tooling even if the CSV itself is clean: `ccst pdata` has no bulk-replace/upsert
105
+ verb, so every future refresh would mean soft-deleting every old record and individually
106
+ re-adding every new one, one CLI call per row. Flag this distinction explicitly to Chris rather
107
+ than deciding it unilaterally - the append-vs-wholesale-replacement axis, not update frequency,
108
+ is what actually matters here.
109
+
65
110
  ## 4. Hand-edit the proposal to encode overrides
66
111
 
67
112
  Edit `.pdata-migration-manifest.json` directly (or the legacy `.ccst-pdata-proposal.json` name,
@@ -90,6 +135,25 @@ Two entries may legitimately feed the same `record_group`, but if they share a f
90
135
  must give it the same `sql_type` - `--write` rejects a mismatched pair up front rather than
91
136
  silently dropping one side's type.
92
137
 
138
+ **`file_path_column` pitfall:** don't map `file_path_column` directly to a column whose values are
139
+ relative to a subfolder rather than the project root (e.g. an index-of-a-subfolder CSV like
140
+ `references/INDEX.csv` whose own `file_path` column is relative to `references/`, not the project
141
+ root) - `--write`'s file_path-resolution verification will fail with a bare "does not resolve
142
+ under `<project_root>`" and no further guidance. Instead, map that column to a plain named field
143
+ (e.g. `source_file_path`) and note in the field's `description` what path it's relative to.
144
+
145
+ **`content_column: null` default caveat:** an entry with `content_column` left unset falls back to
146
+ a raw `json.dumps(row)` dump as every record's content - rarely what you want for readable `ccst
147
+ pdata list` output. Actively choose a real narrative column for every `db-owned` entry before
148
+ `--write`; don't accept the JSON-blob default by omission.
149
+
150
+ **Value transforms the generic importer can't express:** a computed field (e.g. an `is_canonical`
151
+ flag for a known duplicate key) or splitting one source column into two has no 1:1 mapping in
152
+ `csv-rows`. Don't try to force it into the manifest - import the entry normally via `csv-rows`
153
+ first, then apply a post-`--write` `ccst pdata schema add-field` (to add the new field) followed
154
+ by a per-record `ccst pdata update` pass (to populate it). This is the standard recipe for any
155
+ field the source file doesn't already carry as its own column.
156
+
93
157
  Never delete the proposal file to "start over" without good reason - doing so discards every
94
158
  override made so far. If new files appear in the project after the first dry run, add entries for
95
159
  them by hand rather than deleting and reclassifying everything.
@@ -102,6 +166,14 @@ explicit approval. **Never invoke `--write` without it.**
102
166
 
103
167
  ## 6. Run the write phase
104
168
 
169
+ A real migration of an existing, non-trivial project is typically **iterative, not a single
170
+ terminal `--write`**: expect multiple `--write` rounds as source files get fixed (garbled
171
+ headers), deferred decisions get resolved, and corrected entries get re-approved. Don't plan for
172
+ or promise a one-shot `--write`. A `--write` round only ever imports entries with no `migrated_at`
173
+ already set - entries already cut over by an earlier successful `--write` are skipped
174
+ automatically, so a later round picking up newly-fixed or newly-added files is safe to run without
175
+ re-touching what already migrated.
176
+
105
177
  ```sh
106
178
  ccst pdata init --project <project> [--rehearse <path>] --write
107
179
  ```
@@ -132,6 +204,12 @@ Archived originals live at `<project-root>/.pdata-migrated/` and are **never aut
132
204
  himself - that WARN is expected and not urgent. Do not delete the archive without his explicit
133
205
  instruction.
134
206
 
207
+ By default, `--write` also leaves a `.md` pointer file at each cut-over entry's original path
208
+ (record_group, field/schema table, an example query command, and any preserved preface text - see
209
+ Step 3's garbled-header recipe) - so anything else in the project citing the old path by name finds
210
+ a live signpost instead of a silent 404. Pass `--leave-no-pointer-files` if this run shouldn't
211
+ leave one (e.g. a rehearsal, or a project convention against extra files at migrated paths).
212
+
135
213
  ## 8. Rollback, if something's found wrong post-cutover
136
214
 
137
215
  There is no `--rollback` flag. Recover by hand:
@@ -149,6 +227,11 @@ There is no `--rollback` flag. Recover by hand:
149
227
  3. Fix whatever was wrong in the proposal (classification, strategy, field mapping) before
150
228
  re-attempting.
151
229
 
230
+ A genuine intentional re-migration of an entry (e.g. after restoring the backup and deciding to
231
+ redo it) also requires clearing that entry's `migrated_at` field by hand in the manifest, alongside
232
+ the DB/proposal fixes above - `--write` otherwise skips any entry that already carries one (Step 6),
233
+ by design, so it won't be re-imported until you do.
234
+
152
235
  ## Never do without explicit Chris approval
153
236
 
154
237
  - Invoke `--write` on an unreviewed proposal.
@@ -81,3 +81,25 @@ derivable per file. Topic-based splitting (`analysis/`, or any project-specific
81
81
  natural date) has no tool support and stays a manual `git mv`/`mv` pass, since grouping by topic
82
82
  needs human judgement the tool can't safely substitute for. Anything simpler than a split
83
83
  (renaming one folder, adding a new folder type) is also a plain `git mv`/`mv`.
84
+
85
+ ## 6. File-type-segment vocabulary for meetings-and-calls/
86
+
87
+ Files within `meetings-and-calls/` follow a `--`-delimited naming convention:
88
+
89
+ `<yyyy.MM.dd>-<HHmm>--<participants>--<purpose>--<file type>.<ext>`
90
+
91
+ The `<file type>` segment recognises three core values, common enough to expect in any project
92
+ with recorded meetings or calls:
93
+
94
+ | File type | What it holds |
95
+ |---|---|
96
+ | `summary` | Auto-generated summary of the call (e.g. Plaud) |
97
+ | `transcript` | Raw or auto-generated transcript of the call |
98
+ | `audio` | The raw recording itself (`.mp3`/`.mp4`), before any transcription or summary exists |
99
+
100
+ This isn't exhaustive - as with the five folders in §1, a project may extend the vocabulary with
101
+ values for distinctions specific to its own work: `analysis` and `transcript-whisper` (maxella,
102
+ for a session's own write-up of a meeting and a Whisper-generated transcript alongside the Plaud
103
+ one), or `reasoning-summary` and `notes-email` (pbt, for a modelled reasoning summary and an
104
+ emailed notes write-up). Promote a project-specific value to this shared list once it recurs
105
+ across multiple projects - the same bar the five folders in §1 were held to.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: cc-session-tools
3
- Version: 3.2.0
3
+ Version: 3.3.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
@@ -106,6 +106,35 @@ def test_pdata_init_write_end_to_end_imports_and_cuts_over(base_env, tmp_path):
106
106
  assert "first" in r_list.stdout and "second" in r_list.stdout
107
107
 
108
108
 
109
+ def test_pdata_init_write_leaves_pointer_file_by_default(base_env, tmp_path):
110
+ project_dir = tmp_path / "projects" / "demo"
111
+ project_dir.mkdir(parents=True)
112
+ (project_dir / "ideas.csv").write_text("idea\nfirst\nsecond\n")
113
+ base_env["CCST_PDATA_BACKUP_DIR"] = str(tmp_path / "backups")
114
+
115
+ _run(base_env, "pdata", "init", "--project", "demo")
116
+ r_write = _run(base_env, "pdata", "init", "--project", "demo", "--write")
117
+
118
+ assert r_write.returncode == 0, r_write.stderr
119
+ pointer = project_dir / "ideas.md"
120
+ assert pointer.exists()
121
+ assert "ideas" in pointer.read_text()
122
+
123
+
124
+ def test_pdata_init_write_leave_no_pointer_files_suppresses_pointer(base_env, tmp_path):
125
+ project_dir = tmp_path / "projects" / "demo"
126
+ project_dir.mkdir(parents=True)
127
+ (project_dir / "ideas.csv").write_text("idea\nfirst\nsecond\n")
128
+ base_env["CCST_PDATA_BACKUP_DIR"] = str(tmp_path / "backups")
129
+
130
+ _run(base_env, "pdata", "init", "--project", "demo")
131
+ r_write = _run(base_env, "pdata", "init", "--project", "demo", "--write",
132
+ "--leave-no-pointer-files")
133
+
134
+ assert r_write.returncode == 0, r_write.stderr
135
+ assert not (project_dir / "ideas.md").exists()
136
+
137
+
109
138
  def test_pdata_init_write_without_prior_dry_run_errors(base_env, tmp_path):
110
139
  project_dir = tmp_path / "projects" / "demo"
111
140
  project_dir.mkdir(parents=True)
@@ -1,34 +0,0 @@
1
- """Cutover: archive migrated-source originals, never delete them (spec §7.1 steps 6-7)."""
2
- from __future__ import annotations
3
-
4
- import time
5
- from pathlib import Path
6
-
7
- from cc_session_tools.lib.pdata.init_paths import (
8
- MIGRATED_ARCHIVE_DIRNAME,
9
- MIGRATED_MANIFEST_FILENAME,
10
- )
11
- from cc_session_tools.lib.pdata.manifest import ManifestEntry
12
-
13
-
14
- def archive_entries(*, project_root: Path, entries: list[ManifestEntry]) -> None:
15
- """Move every db-owned entry's source file into project_root/.pdata-migrated/,
16
- preserving its relative path, and append one line per entry to MANIFEST.md.
17
- Never deletes — cutover only relocates within project_root (spec §7.1 step 6);
18
- deleting the archive is a manual, human-directed action (step 7)."""
19
- if not entries:
20
- return
21
- archive_root = project_root / MIGRATED_ARCHIVE_DIRNAME
22
- archive_root.mkdir(parents=True, exist_ok=True)
23
- manifest_path = archive_root / MIGRATED_MANIFEST_FILENAME
24
- now = time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime())
25
- with manifest_path.open("a", encoding="utf-8") as log:
26
- for entry in entries:
27
- source = project_root / entry.path
28
- destination = archive_root / entry.path
29
- destination.parent.mkdir(parents=True, exist_ok=True)
30
- source.rename(destination)
31
- log.write(
32
- f"- {now} — {entry.path} — migrated source, superseded by ccst pdata "
33
- f"(record_group={entry.db_group()})\n"
34
- )