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