cc-session-tools 3.7.1__tar.gz → 3.8.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.
- {cc_session_tools-3.7.1/src/cc_session_tools.egg-info → cc_session_tools-3.8.0}/PKG-INFO +20 -1
- {cc_session_tools-3.7.1 → cc_session_tools-3.8.0}/README.md +19 -0
- {cc_session_tools-3.7.1 → cc_session_tools-3.8.0}/pyproject.toml +1 -1
- {cc_session_tools-3.7.1 → cc_session_tools-3.8.0}/src/cc_session_tools/cli/ccst.py +42 -0
- cc_session_tools-3.8.0/src/cc_session_tools/lib/pdata/readiness.py +319 -0
- cc_session_tools-3.8.0/src/cc_session_tools/skills/pm-pdata-do-audit-and-prepare-to-migrate/SKILL.md +162 -0
- {cc_session_tools-3.7.1 → cc_session_tools-3.8.0/src/cc_session_tools.egg-info}/PKG-INFO +20 -1
- {cc_session_tools-3.7.1 → cc_session_tools-3.8.0}/src/cc_session_tools.egg-info/SOURCES.txt +3 -0
- cc_session_tools-3.8.0/tests/test_ccst_pdata_readiness_cli.py +95 -0
- cc_session_tools-3.8.0/tests/test_pdata_audit_skill.py +89 -0
- cc_session_tools-3.7.1/src/cc_session_tools/skills/pm-pdata-do-audit-and-prepare-to-migrate/SKILL.md +0 -199
- {cc_session_tools-3.7.1 → cc_session_tools-3.8.0}/LICENSE +0 -0
- {cc_session_tools-3.7.1 → cc_session_tools-3.8.0}/setup.cfg +0 -0
- {cc_session_tools-3.7.1 → cc_session_tools-3.8.0}/src/cc_session_tools/__init__.py +0 -0
- {cc_session_tools-3.7.1 → cc_session_tools-3.8.0}/src/cc_session_tools/cli/__init__.py +0 -0
- {cc_session_tools-3.7.1 → cc_session_tools-3.8.0}/src/cc_session_tools/cli/ccd.py +0 -0
- {cc_session_tools-3.7.1 → cc_session_tools-3.8.0}/src/cc_session_tools/cli/ccmsg.py +0 -0
- {cc_session_tools-3.7.1 → cc_session_tools-3.8.0}/src/cc_session_tools/cli/ccr.py +0 -0
- {cc_session_tools-3.7.1 → cc_session_tools-3.8.0}/src/cc_session_tools/cli/ccs.py +0 -0
- {cc_session_tools-3.7.1 → cc_session_tools-3.8.0}/src/cc_session_tools/cli/ccsched.py +0 -0
- {cc_session_tools-3.7.1 → cc_session_tools-3.8.0}/src/cc_session_tools/cli/migrate_ccmsg.py +0 -0
- {cc_session_tools-3.7.1 → cc_session_tools-3.8.0}/src/cc_session_tools/cli/migrate_ccsched.py +0 -0
- {cc_session_tools-3.7.1 → cc_session_tools-3.8.0}/src/cc_session_tools/cli/migrate_sessions_db.py +0 -0
- {cc_session_tools-3.7.1 → cc_session_tools-3.8.0}/src/cc_session_tools/cli/migrate_telemetry.py +0 -0
- {cc_session_tools-3.7.1 → cc_session_tools-3.8.0}/src/cc_session_tools/config/hooks-bundle.json +0 -0
- {cc_session_tools-3.7.1 → cc_session_tools-3.8.0}/src/cc_session_tools/config/session-tag-hook.json +0 -0
- {cc_session_tools-3.7.1 → cc_session_tools-3.8.0}/src/cc_session_tools/hooks_install.py +0 -0
- {cc_session_tools-3.7.1 → cc_session_tools-3.8.0}/src/cc_session_tools/lib/__init__.py +0 -0
- {cc_session_tools-3.7.1 → cc_session_tools-3.8.0}/src/cc_session_tools/lib/bun_tools.py +0 -0
- {cc_session_tools-3.7.1 → cc_session_tools-3.8.0}/src/cc_session_tools/lib/claude_flags.py +0 -0
- {cc_session_tools-3.7.1 → cc_session_tools-3.8.0}/src/cc_session_tools/lib/claude_md_install.py +0 -0
- {cc_session_tools-3.7.1 → cc_session_tools-3.8.0}/src/cc_session_tools/lib/context_overrides.py +0 -0
- {cc_session_tools-3.7.1 → cc_session_tools-3.8.0}/src/cc_session_tools/lib/db.py +0 -0
- {cc_session_tools-3.7.1 → cc_session_tools-3.8.0}/src/cc_session_tools/lib/debug.py +0 -0
- {cc_session_tools-3.7.1 → cc_session_tools-3.8.0}/src/cc_session_tools/lib/doctor.py +0 -0
- {cc_session_tools-3.7.1 → cc_session_tools-3.8.0}/src/cc_session_tools/lib/doctor_mutes.py +0 -0
- {cc_session_tools-3.7.1 → cc_session_tools-3.8.0}/src/cc_session_tools/lib/hook_registry.py +0 -0
- {cc_session_tools-3.7.1 → cc_session_tools-3.8.0}/src/cc_session_tools/lib/install_sync.py +0 -0
- {cc_session_tools-3.7.1 → cc_session_tools-3.8.0}/src/cc_session_tools/lib/levenshtein.py +0 -0
- {cc_session_tools-3.7.1 → cc_session_tools-3.8.0}/src/cc_session_tools/lib/machine_identity.py +0 -0
- {cc_session_tools-3.7.1 → cc_session_tools-3.8.0}/src/cc_session_tools/lib/messaging/__init__.py +0 -0
- {cc_session_tools-3.7.1 → cc_session_tools-3.8.0}/src/cc_session_tools/lib/messaging/addressing.py +0 -0
- {cc_session_tools-3.7.1 → cc_session_tools-3.8.0}/src/cc_session_tools/lib/messaging/cursor.py +0 -0
- {cc_session_tools-3.7.1 → cc_session_tools-3.8.0}/src/cc_session_tools/lib/messaging/lock.py +0 -0
- {cc_session_tools-3.7.1 → cc_session_tools-3.8.0}/src/cc_session_tools/lib/messaging/message.py +0 -0
- {cc_session_tools-3.7.1 → cc_session_tools-3.8.0}/src/cc_session_tools/lib/messaging/move_safety.py +0 -0
- {cc_session_tools-3.7.1 → cc_session_tools-3.8.0}/src/cc_session_tools/lib/messaging/repository.py +0 -0
- {cc_session_tools-3.7.1 → cc_session_tools-3.8.0}/src/cc_session_tools/lib/messaging/retention.py +0 -0
- {cc_session_tools-3.7.1 → cc_session_tools-3.8.0}/src/cc_session_tools/lib/messaging/service.py +0 -0
- {cc_session_tools-3.7.1 → cc_session_tools-3.8.0}/src/cc_session_tools/lib/messaging/store.py +0 -0
- {cc_session_tools-3.7.1 → cc_session_tools-3.8.0}/src/cc_session_tools/lib/occupancy.py +0 -0
- {cc_session_tools-3.7.1 → cc_session_tools-3.8.0}/src/cc_session_tools/lib/paths.py +0 -0
- {cc_session_tools-3.7.1 → cc_session_tools-3.8.0}/src/cc_session_tools/lib/pdata/__init__.py +0 -0
- {cc_session_tools-3.7.1 → cc_session_tools-3.8.0}/src/cc_session_tools/lib/pdata/backup.py +0 -0
- {cc_session_tools-3.7.1 → cc_session_tools-3.8.0}/src/cc_session_tools/lib/pdata/classify.py +0 -0
- {cc_session_tools-3.7.1 → cc_session_tools-3.8.0}/src/cc_session_tools/lib/pdata/cutover.py +0 -0
- {cc_session_tools-3.7.1 → cc_session_tools-3.8.0}/src/cc_session_tools/lib/pdata/dump.py +0 -0
- {cc_session_tools-3.7.1 → cc_session_tools-3.8.0}/src/cc_session_tools/lib/pdata/formatting.py +0 -0
- {cc_session_tools-3.7.1 → cc_session_tools-3.8.0}/src/cc_session_tools/lib/pdata/importers.py +0 -0
- {cc_session_tools-3.7.1 → cc_session_tools-3.8.0}/src/cc_session_tools/lib/pdata/init_paths.py +0 -0
- {cc_session_tools-3.7.1 → cc_session_tools-3.8.0}/src/cc_session_tools/lib/pdata/init_service.py +0 -0
- {cc_session_tools-3.7.1 → cc_session_tools-3.8.0}/src/cc_session_tools/lib/pdata/manifest.py +0 -0
- {cc_session_tools-3.7.1 → cc_session_tools-3.8.0}/src/cc_session_tools/lib/pdata/naming.py +0 -0
- {cc_session_tools-3.7.1 → cc_session_tools-3.8.0}/src/cc_session_tools/lib/pdata/rehydrate.py +0 -0
- {cc_session_tools-3.7.1 → cc_session_tools-3.8.0}/src/cc_session_tools/lib/pdata/rename_group.py +0 -0
- {cc_session_tools-3.7.1 → cc_session_tools-3.8.0}/src/cc_session_tools/lib/pdata/reorganize.py +0 -0
- {cc_session_tools-3.7.1 → cc_session_tools-3.8.0}/src/cc_session_tools/lib/pdata/repository.py +0 -0
- {cc_session_tools-3.7.1 → cc_session_tools-3.8.0}/src/cc_session_tools/lib/pdata/resolve.py +0 -0
- {cc_session_tools-3.7.1 → cc_session_tools-3.8.0}/src/cc_session_tools/lib/pdata/service.py +0 -0
- {cc_session_tools-3.7.1 → cc_session_tools-3.8.0}/src/cc_session_tools/lib/pdata/session_output.py +0 -0
- {cc_session_tools-3.7.1 → cc_session_tools-3.8.0}/src/cc_session_tools/lib/pdata/store.py +0 -0
- {cc_session_tools-3.7.1 → cc_session_tools-3.8.0}/src/cc_session_tools/lib/pdata/sync_check.py +0 -0
- {cc_session_tools-3.7.1 → cc_session_tools-3.8.0}/src/cc_session_tools/lib/pdata/sync_lock.py +0 -0
- {cc_session_tools-3.7.1 → cc_session_tools-3.8.0}/src/cc_session_tools/lib/pdata/sync_notify.py +0 -0
- {cc_session_tools-3.7.1 → cc_session_tools-3.8.0}/src/cc_session_tools/lib/pdata/vector_clock.py +0 -0
- {cc_session_tools-3.7.1 → cc_session_tools-3.8.0}/src/cc_session_tools/lib/pdata/vector_clock_store.py +0 -0
- {cc_session_tools-3.7.1 → cc_session_tools-3.8.0}/src/cc_session_tools/lib/pdata/verify.py +0 -0
- {cc_session_tools-3.7.1 → cc_session_tools-3.8.0}/src/cc_session_tools/lib/pdata/write_log.py +0 -0
- {cc_session_tools-3.7.1 → cc_session_tools-3.8.0}/src/cc_session_tools/lib/picker.py +0 -0
- {cc_session_tools-3.7.1 → cc_session_tools-3.8.0}/src/cc_session_tools/lib/proc_lock.py +0 -0
- {cc_session_tools-3.7.1 → cc_session_tools-3.8.0}/src/cc_session_tools/lib/prompts.py +0 -0
- {cc_session_tools-3.7.1 → cc_session_tools-3.8.0}/src/cc_session_tools/lib/roots.py +0 -0
- {cc_session_tools-3.7.1 → cc_session_tools-3.8.0}/src/cc_session_tools/lib/rules.py +0 -0
- {cc_session_tools-3.7.1 → cc_session_tools-3.8.0}/src/cc_session_tools/lib/scheduler/__init__.py +0 -0
- {cc_session_tools-3.7.1 → cc_session_tools-3.8.0}/src/cc_session_tools/lib/scheduler/bundled_jobs.py +0 -0
- {cc_session_tools-3.7.1 → cc_session_tools-3.8.0}/src/cc_session_tools/lib/scheduler/cadence.py +0 -0
- {cc_session_tools-3.7.1 → cc_session_tools-3.8.0}/src/cc_session_tools/lib/scheduler/cursor.py +0 -0
- {cc_session_tools-3.7.1 → cc_session_tools-3.8.0}/src/cc_session_tools/lib/scheduler/digest.py +0 -0
- {cc_session_tools-3.7.1 → cc_session_tools-3.8.0}/src/cc_session_tools/lib/scheduler/due.py +0 -0
- {cc_session_tools-3.7.1 → cc_session_tools-3.8.0}/src/cc_session_tools/lib/scheduler/duration.py +0 -0
- {cc_session_tools-3.7.1 → cc_session_tools-3.8.0}/src/cc_session_tools/lib/scheduler/jobspec.py +0 -0
- {cc_session_tools-3.7.1 → cc_session_tools-3.8.0}/src/cc_session_tools/lib/scheduler/ledger.py +0 -0
- {cc_session_tools-3.7.1 → cc_session_tools-3.8.0}/src/cc_session_tools/lib/scheduler/lock.py +0 -0
- {cc_session_tools-3.7.1 → cc_session_tools-3.8.0}/src/cc_session_tools/lib/scheduler/notify.py +0 -0
- {cc_session_tools-3.7.1 → cc_session_tools-3.8.0}/src/cc_session_tools/lib/scheduler/reconcile.py +0 -0
- {cc_session_tools-3.7.1 → cc_session_tools-3.8.0}/src/cc_session_tools/lib/scheduler/registry.py +0 -0
- {cc_session_tools-3.7.1 → cc_session_tools-3.8.0}/src/cc_session_tools/lib/scheduler/runner.py +0 -0
- {cc_session_tools-3.7.1 → cc_session_tools-3.8.0}/src/cc_session_tools/lib/scheduler/state.py +0 -0
- {cc_session_tools-3.7.1 → cc_session_tools-3.8.0}/src/cc_session_tools/lib/scheduler/store.py +0 -0
- {cc_session_tools-3.7.1 → cc_session_tools-3.8.0}/src/cc_session_tools/lib/scheduler/surface.py +0 -0
- {cc_session_tools-3.7.1 → cc_session_tools-3.8.0}/src/cc_session_tools/lib/scheduler/throttle.py +0 -0
- {cc_session_tools-3.7.1 → cc_session_tools-3.8.0}/src/cc_session_tools/lib/scheduler/worker.py +0 -0
- {cc_session_tools-3.7.1 → cc_session_tools-3.8.0}/src/cc_session_tools/lib/session_gc.py +0 -0
- {cc_session_tools-3.7.1 → cc_session_tools-3.8.0}/src/cc_session_tools/lib/sessions.py +0 -0
- {cc_session_tools-3.7.1 → cc_session_tools-3.8.0}/src/cc_session_tools/lib/sessions_db.py +0 -0
- {cc_session_tools-3.7.1 → cc_session_tools-3.8.0}/src/cc_session_tools/lib/sessions_repair.py +0 -0
- {cc_session_tools-3.7.1 → cc_session_tools-3.8.0}/src/cc_session_tools/lib/shell_install.py +0 -0
- {cc_session_tools-3.7.1 → cc_session_tools-3.8.0}/src/cc_session_tools/lib/tasklist.py +0 -0
- {cc_session_tools-3.7.1 → cc_session_tools-3.8.0}/src/cc_session_tools/lib/telemetry_store.py +0 -0
- {cc_session_tools-3.7.1 → cc_session_tools-3.8.0}/src/cc_session_tools/lib/text_wrap.py +0 -0
- {cc_session_tools-3.7.1 → cc_session_tools-3.8.0}/src/cc_session_tools/prompts/pdata-migration-claude-md-update.md +0 -0
- {cc_session_tools-3.7.1 → cc_session_tools-3.8.0}/src/cc_session_tools/prompts/pdata-migration-skills-update.md +0 -0
- {cc_session_tools-3.7.1 → cc_session_tools-3.8.0}/src/cc_session_tools/skills/analyse-cc-usage/SKILL.md +0 -0
- {cc_session_tools-3.7.1 → cc_session_tools-3.8.0}/src/cc_session_tools/skills/clean-hook-sessions/SKILL.md +0 -0
- {cc_session_tools-3.7.1 → cc_session_tools-3.8.0}/src/cc_session_tools/skills/clean-hook-sessions/scripts/clean-hook-sessions.py +0 -0
- {cc_session_tools-3.7.1 → cc_session_tools-3.8.0}/src/cc_session_tools/skills/compact-smart/SKILL.md +0 -0
- {cc_session_tools-3.7.1 → cc_session_tools-3.8.0}/src/cc_session_tools/skills/context-override/SKILL.md +0 -0
- {cc_session_tools-3.7.1 → cc_session_tools-3.8.0}/src/cc_session_tools/skills/delete-sessions/SKILL.md +0 -0
- {cc_session_tools-3.7.1 → cc_session_tools-3.8.0}/src/cc_session_tools/skills/delete-sessions/scripts/delete_sessions.py +0 -0
- {cc_session_tools-3.7.1 → cc_session_tools-3.8.0}/src/cc_session_tools/skills/do-executor-critic-assessor-loop/SKILL.md +0 -0
- {cc_session_tools-3.7.1 → cc_session_tools-3.8.0}/src/cc_session_tools/skills/extract-pptx/SKILL.md +0 -0
- {cc_session_tools-3.7.1 → cc_session_tools-3.8.0}/src/cc_session_tools/skills/extract-pptx/scripts/extract_pptx.py +0 -0
- {cc_session_tools-3.7.1 → cc_session_tools-3.8.0}/src/cc_session_tools/skills/extract-to-text/SKILL.md +0 -0
- {cc_session_tools-3.7.1 → cc_session_tools-3.8.0}/src/cc_session_tools/skills/extract-to-text/scripts/extract_to_text.py +0 -0
- {cc_session_tools-3.7.1 → cc_session_tools-3.8.0}/src/cc_session_tools/skills/find-claude-code-session/SKILL.md +0 -0
- {cc_session_tools-3.7.1 → cc_session_tools-3.8.0}/src/cc_session_tools/skills/generate-8digit-code/SKILL.md +0 -0
- {cc_session_tools-3.7.1 → cc_session_tools-3.8.0}/src/cc_session_tools/skills/generate-8digit-code/scripts/generate_8digit_code.py +0 -0
- {cc_session_tools-3.7.1 → cc_session_tools-3.8.0}/src/cc_session_tools/skills/list-empty-sessions/SKILL.md +0 -0
- {cc_session_tools-3.7.1 → cc_session_tools-3.8.0}/src/cc_session_tools/skills/list-empty-sessions/scripts/list_empty_sessions.py +0 -0
- {cc_session_tools-3.7.1 → cc_session_tools-3.8.0}/src/cc_session_tools/skills/manage-recurring-cc-jobs-using-ccsched/SKILL.md +0 -0
- {cc_session_tools-3.7.1 → cc_session_tools-3.8.0}/src/cc_session_tools/skills/move-session/SKILL.md +0 -0
- {cc_session_tools-3.7.1 → cc_session_tools-3.8.0}/src/cc_session_tools/skills/move-session/hooks/sessionstart-pending-rename.sh +0 -0
- {cc_session_tools-3.7.1 → cc_session_tools-3.8.0}/src/cc_session_tools/skills/move-session/scripts/cc_session_rules.py +0 -0
- {cc_session_tools-3.7.1 → cc_session_tools-3.8.0}/src/cc_session_tools/skills/move-session/scripts/move_session.py +0 -0
- {cc_session_tools-3.7.1 → cc_session_tools-3.8.0}/src/cc_session_tools/skills/pm-pdata-design-schema/SKILL.md +0 -0
- {cc_session_tools-3.7.1 → cc_session_tools-3.8.0}/src/cc_session_tools/skills/pm-pdata-do-init/SKILL.md +0 -0
- {cc_session_tools-3.7.1 → cc_session_tools-3.8.0}/src/cc_session_tools/skills/pm-pdata-do-manual-migration/SKILL.md +0 -0
- {cc_session_tools-3.7.1 → cc_session_tools-3.8.0}/src/cc_session_tools/skills/pm-pdata-resolve-conflicts/SKILL.md +0 -0
- {cc_session_tools-3.7.1 → cc_session_tools-3.8.0}/src/cc_session_tools/skills/pm-project-layout-reference/SKILL.md +0 -0
- {cc_session_tools-3.7.1 → cc_session_tools-3.8.0}/src/cc_session_tools/skills/pm-update-central-files/SKILL.md +0 -0
- {cc_session_tools-3.7.1 → cc_session_tools-3.8.0}/src/cc_session_tools/skills/reduce-persistent-context/CALIBRATION.md +0 -0
- {cc_session_tools-3.7.1 → cc_session_tools-3.8.0}/src/cc_session_tools/skills/reduce-persistent-context/SKILL.md +0 -0
- {cc_session_tools-3.7.1 → cc_session_tools-3.8.0}/src/cc_session_tools/skills/reduce-persistent-context/scripts/analyze_context.py +0 -0
- {cc_session_tools-3.7.1 → cc_session_tools-3.8.0}/src/cc_session_tools/skills/reduce-persistent-context/scripts/duplicates.py +0 -0
- {cc_session_tools-3.7.1 → cc_session_tools-3.8.0}/src/cc_session_tools/skills/reduce-persistent-context/scripts/measure.py +0 -0
- {cc_session_tools-3.7.1 → cc_session_tools-3.8.0}/src/cc_session_tools/skills/reduce-persistent-context/scripts/report.py +0 -0
- {cc_session_tools-3.7.1 → cc_session_tools-3.8.0}/src/cc_session_tools/skills/reduce-persistent-context/scripts/tokens.py +0 -0
- {cc_session_tools-3.7.1 → cc_session_tools-3.8.0}/src/cc_session_tools/skills/reduce-persistent-context/scripts/usage.py +0 -0
- {cc_session_tools-3.7.1 → cc_session_tools-3.8.0}/src/cc_session_tools/skills/select-agent-model/SKILL.md +0 -0
- {cc_session_tools-3.7.1 → cc_session_tools-3.8.0}/src/cc_session_tools/skills/send-session-message/SKILL.md +0 -0
- {cc_session_tools-3.7.1 → cc_session_tools-3.8.0}/src/cc_session_tools/skills/update-command-cache/SKILL.md +0 -0
- {cc_session_tools-3.7.1 → cc_session_tools-3.8.0}/src/cc_session_tools/skills/update-command-cache/scripts/update_command_cache.py +0 -0
- {cc_session_tools-3.7.1 → cc_session_tools-3.8.0}/src/cc_session_tools.egg-info/dependency_links.txt +0 -0
- {cc_session_tools-3.7.1 → cc_session_tools-3.8.0}/src/cc_session_tools.egg-info/entry_points.txt +0 -0
- {cc_session_tools-3.7.1 → cc_session_tools-3.8.0}/src/cc_session_tools.egg-info/requires.txt +0 -0
- {cc_session_tools-3.7.1 → cc_session_tools-3.8.0}/src/cc_session_tools.egg-info/top_level.txt +0 -0
- {cc_session_tools-3.7.1 → cc_session_tools-3.8.0}/src/claude_code_usage/__init__.py +0 -0
- {cc_session_tools-3.7.1 → cc_session_tools-3.8.0}/src/claude_code_usage/attribution.py +0 -0
- {cc_session_tools-3.7.1 → cc_session_tools-3.8.0}/src/claude_code_usage/cache.py +0 -0
- {cc_session_tools-3.7.1 → cc_session_tools-3.8.0}/src/claude_code_usage/ccusage_wrapper.py +0 -0
- {cc_session_tools-3.7.1 → cc_session_tools-3.8.0}/src/claude_code_usage/cli.py +0 -0
- {cc_session_tools-3.7.1 → cc_session_tools-3.8.0}/src/claude_code_usage/data/pricing.json +0 -0
- {cc_session_tools-3.7.1 → cc_session_tools-3.8.0}/src/claude_code_usage/parent_inference.py +0 -0
- {cc_session_tools-3.7.1 → cc_session_tools-3.8.0}/src/claude_code_usage/parser.py +0 -0
- {cc_session_tools-3.7.1 → cc_session_tools-3.8.0}/src/claude_code_usage/pricing.py +0 -0
- {cc_session_tools-3.7.1 → cc_session_tools-3.8.0}/src/claude_code_usage/query.py +0 -0
- {cc_session_tools-3.7.1 → cc_session_tools-3.8.0}/src/claude_code_usage/report.py +0 -0
- {cc_session_tools-3.7.1 → cc_session_tools-3.8.0}/src/claude_code_usage/schema.py +0 -0
- {cc_session_tools-3.7.1 → cc_session_tools-3.8.0}/src/claude_code_usage/session_names.py +0 -0
- {cc_session_tools-3.7.1 → cc_session_tools-3.8.0}/src/hooks/__init__.py +0 -0
- {cc_session_tools-3.7.1 → cc_session_tools-3.8.0}/src/hooks/after_response.py +0 -0
- {cc_session_tools-3.7.1 → cc_session_tools-3.8.0}/src/hooks/bash_hard_deny.py +0 -0
- {cc_session_tools-3.7.1 → cc_session_tools-3.8.0}/src/hooks/bash_security_review.py +0 -0
- {cc_session_tools-3.7.1 → cc_session_tools-3.8.0}/src/hooks/cache.py +0 -0
- {cc_session_tools-3.7.1 → cc_session_tools-3.8.0}/src/hooks/catchup.py +0 -0
- {cc_session_tools-3.7.1 → cc_session_tools-3.8.0}/src/hooks/confirm_8digit.py +0 -0
- {cc_session_tools-3.7.1 → cc_session_tools-3.8.0}/src/hooks/context_window_warning.py +0 -0
- {cc_session_tools-3.7.1 → cc_session_tools-3.8.0}/src/hooks/last_screenshot.py +0 -0
- {cc_session_tools-3.7.1 → cc_session_tools-3.8.0}/src/hooks/marker_allow.py +0 -0
- {cc_session_tools-3.7.1 → cc_session_tools-3.8.0}/src/hooks/markers.py +0 -0
- {cc_session_tools-3.7.1 → cc_session_tools-3.8.0}/src/hooks/messaging_deliver.py +0 -0
- {cc_session_tools-3.7.1 → cc_session_tools-3.8.0}/src/hooks/model_info.py +0 -0
- {cc_session_tools-3.7.1 → cc_session_tools-3.8.0}/src/hooks/normalise.py +0 -0
- {cc_session_tools-3.7.1 → cc_session_tools-3.8.0}/src/hooks/pdata_sync.py +0 -0
- {cc_session_tools-3.7.1 → cc_session_tools-3.8.0}/src/hooks/pending_migration.py +0 -0
- {cc_session_tools-3.7.1 → cc_session_tools-3.8.0}/src/hooks/pending_rename.py +0 -0
- {cc_session_tools-3.7.1 → cc_session_tools-3.8.0}/src/hooks/script_allowlist.py +0 -0
- {cc_session_tools-3.7.1 → cc_session_tools-3.8.0}/src/hooks/session_tag.py +0 -0
- {cc_session_tools-3.7.1 → cc_session_tools-3.8.0}/src/hooks/stats.py +0 -0
- {cc_session_tools-3.7.1 → cc_session_tools-3.8.0}/src/hooks/telemetry.py +0 -0
- {cc_session_tools-3.7.1 → cc_session_tools-3.8.0}/src/hooks/telemetry_query.py +0 -0
- {cc_session_tools-3.7.1 → cc_session_tools-3.8.0}/src/hooks/telemetry_trim.py +0 -0
- {cc_session_tools-3.7.1 → cc_session_tools-3.8.0}/src/hooks/transcript.py +0 -0
- {cc_session_tools-3.7.1 → cc_session_tools-3.8.0}/src/hooks/worklog_guard.py +0 -0
- {cc_session_tools-3.7.1 → cc_session_tools-3.8.0}/tests/test_after_response.py +0 -0
- {cc_session_tools-3.7.1 → cc_session_tools-3.8.0}/tests/test_attribution.py +0 -0
- {cc_session_tools-3.7.1 → cc_session_tools-3.8.0}/tests/test_bash_hard_deny.py +0 -0
- {cc_session_tools-3.7.1 → cc_session_tools-3.8.0}/tests/test_bash_security_review.py +0 -0
- {cc_session_tools-3.7.1 → cc_session_tools-3.8.0}/tests/test_bash_security_review_allowlist.py +0 -0
- {cc_session_tools-3.7.1 → cc_session_tools-3.8.0}/tests/test_bun_tools.py +0 -0
- {cc_session_tools-3.7.1 → cc_session_tools-3.8.0}/tests/test_cache.py +0 -0
- {cc_session_tools-3.7.1 → cc_session_tools-3.8.0}/tests/test_cache_sqlite.py +0 -0
- {cc_session_tools-3.7.1 → cc_session_tools-3.8.0}/tests/test_ccr_orphans.py +0 -0
- {cc_session_tools-3.7.1 → cc_session_tools-3.8.0}/tests/test_ccs_emptiness.py +0 -0
- {cc_session_tools-3.7.1 → cc_session_tools-3.8.0}/tests/test_ccs_eta.py +0 -0
- {cc_session_tools-3.7.1 → cc_session_tools-3.8.0}/tests/test_ccs_list_mode.py +0 -0
- {cc_session_tools-3.7.1 → cc_session_tools-3.8.0}/tests/test_ccs_scope_flags.py +0 -0
- {cc_session_tools-3.7.1 → cc_session_tools-3.8.0}/tests/test_ccs_sentinel_sort.py +0 -0
- {cc_session_tools-3.7.1 → cc_session_tools-3.8.0}/tests/test_ccs_session_counts.py +0 -0
- {cc_session_tools-3.7.1 → cc_session_tools-3.8.0}/tests/test_ccst_bundle_discovery.py +0 -0
- {cc_session_tools-3.7.1 → cc_session_tools-3.8.0}/tests/test_ccst_ccsched_jobs_cli.py +0 -0
- {cc_session_tools-3.7.1 → cc_session_tools-3.8.0}/tests/test_ccst_cli.py +0 -0
- {cc_session_tools-3.7.1 → cc_session_tools-3.8.0}/tests/test_ccst_context_override_cli.py +0 -0
- {cc_session_tools-3.7.1 → cc_session_tools-3.8.0}/tests/test_ccst_doctor.py +0 -0
- {cc_session_tools-3.7.1 → cc_session_tools-3.8.0}/tests/test_ccst_gc_prune.py +0 -0
- {cc_session_tools-3.7.1 → cc_session_tools-3.8.0}/tests/test_ccst_gc_report.py +0 -0
- {cc_session_tools-3.7.1 → cc_session_tools-3.8.0}/tests/test_ccst_hook_dispatcher.py +0 -0
- {cc_session_tools-3.7.1 → cc_session_tools-3.8.0}/tests/test_ccst_hooks_allowlist.py +0 -0
- {cc_session_tools-3.7.1 → cc_session_tools-3.8.0}/tests/test_ccst_hooks_install_prune.py +0 -0
- {cc_session_tools-3.7.1 → cc_session_tools-3.8.0}/tests/test_ccst_hooks_install_zero_arg.py +0 -0
- {cc_session_tools-3.7.1 → cc_session_tools-3.8.0}/tests/test_ccst_hooks_uninstall.py +0 -0
- {cc_session_tools-3.7.1 → cc_session_tools-3.8.0}/tests/test_ccst_install_everything.py +0 -0
- {cc_session_tools-3.7.1 → cc_session_tools-3.8.0}/tests/test_ccst_machine_identity_cli.py +0 -0
- {cc_session_tools-3.7.1 → cc_session_tools-3.8.0}/tests/test_ccst_main_gate.py +0 -0
- {cc_session_tools-3.7.1 → cc_session_tools-3.8.0}/tests/test_ccst_pdata_cli.py +0 -0
- {cc_session_tools-3.7.1 → cc_session_tools-3.8.0}/tests/test_ccst_pdata_init_cli.py +0 -0
- {cc_session_tools-3.7.1 → cc_session_tools-3.8.0}/tests/test_ccst_pdata_reconcile_cli.py +0 -0
- {cc_session_tools-3.7.1 → cc_session_tools-3.8.0}/tests/test_ccst_pdata_rename_group_cli.py +0 -0
- {cc_session_tools-3.7.1 → cc_session_tools-3.8.0}/tests/test_ccst_pdata_reorganize_cli.py +0 -0
- {cc_session_tools-3.7.1 → cc_session_tools-3.8.0}/tests/test_ccst_pdata_sync_cli.py +0 -0
- {cc_session_tools-3.7.1 → cc_session_tools-3.8.0}/tests/test_ccst_pdata_verify_cli.py +0 -0
- {cc_session_tools-3.7.1 → cc_session_tools-3.8.0}/tests/test_ccst_repair_sessions_cli.py +0 -0
- {cc_session_tools-3.7.1 → cc_session_tools-3.8.0}/tests/test_ccst_sessions_cli.py +0 -0
- {cc_session_tools-3.7.1 → cc_session_tools-3.8.0}/tests/test_ccst_shell_install.py +0 -0
- {cc_session_tools-3.7.1 → cc_session_tools-3.8.0}/tests/test_ccst_skills_install.py +0 -0
- {cc_session_tools-3.7.1 → cc_session_tools-3.8.0}/tests/test_ccst_skills_uninstall.py +0 -0
- {cc_session_tools-3.7.1 → cc_session_tools-3.8.0}/tests/test_ccst_telemetry_query.py +0 -0
- {cc_session_tools-3.7.1 → cc_session_tools-3.8.0}/tests/test_ccst_telemetry_trim.py +0 -0
- {cc_session_tools-3.7.1 → cc_session_tools-3.8.0}/tests/test_ccusage_wrapper.py +0 -0
- {cc_session_tools-3.7.1 → cc_session_tools-3.8.0}/tests/test_claude_flags.py +0 -0
- {cc_session_tools-3.7.1 → cc_session_tools-3.8.0}/tests/test_claude_md_install.py +0 -0
- {cc_session_tools-3.7.1 → cc_session_tools-3.8.0}/tests/test_cli.py +0 -0
- {cc_session_tools-3.7.1 → cc_session_tools-3.8.0}/tests/test_cli_ccd.py +0 -0
- {cc_session_tools-3.7.1 → cc_session_tools-3.8.0}/tests/test_cli_ccd_dry_run.py +0 -0
- {cc_session_tools-3.7.1 → cc_session_tools-3.8.0}/tests/test_cli_ccr.py +0 -0
- {cc_session_tools-3.7.1 → cc_session_tools-3.8.0}/tests/test_cli_ccs.py +0 -0
- {cc_session_tools-3.7.1 → cc_session_tools-3.8.0}/tests/test_cli_version.py +0 -0
- {cc_session_tools-3.7.1 → cc_session_tools-3.8.0}/tests/test_confirm_8digit.py +0 -0
- {cc_session_tools-3.7.1 → cc_session_tools-3.8.0}/tests/test_context_overrides.py +0 -0
- {cc_session_tools-3.7.1 → cc_session_tools-3.8.0}/tests/test_context_window_warning.py +0 -0
- {cc_session_tools-3.7.1 → cc_session_tools-3.8.0}/tests/test_db.py +0 -0
- {cc_session_tools-3.7.1 → cc_session_tools-3.8.0}/tests/test_debug.py +0 -0
- {cc_session_tools-3.7.1 → cc_session_tools-3.8.0}/tests/test_doctor_mutes.py +0 -0
- {cc_session_tools-3.7.1 → cc_session_tools-3.8.0}/tests/test_empty_session.py +0 -0
- {cc_session_tools-3.7.1 → cc_session_tools-3.8.0}/tests/test_hook_cache.py +0 -0
- {cc_session_tools-3.7.1 → cc_session_tools-3.8.0}/tests/test_hooks_install.py +0 -0
- {cc_session_tools-3.7.1 → cc_session_tools-3.8.0}/tests/test_hooks_install_records.py +0 -0
- {cc_session_tools-3.7.1 → cc_session_tools-3.8.0}/tests/test_install_sync.py +0 -0
- {cc_session_tools-3.7.1 → cc_session_tools-3.8.0}/tests/test_last_screenshot.py +0 -0
- {cc_session_tools-3.7.1 → cc_session_tools-3.8.0}/tests/test_levenshtein.py +0 -0
- {cc_session_tools-3.7.1 → cc_session_tools-3.8.0}/tests/test_machine_identity.py +0 -0
- {cc_session_tools-3.7.1 → cc_session_tools-3.8.0}/tests/test_marker_allow.py +0 -0
- {cc_session_tools-3.7.1 → cc_session_tools-3.8.0}/tests/test_migrate_ccmsg.py +0 -0
- {cc_session_tools-3.7.1 → cc_session_tools-3.8.0}/tests/test_migrate_ccsched.py +0 -0
- {cc_session_tools-3.7.1 → cc_session_tools-3.8.0}/tests/test_migrate_sessions_db.py +0 -0
- {cc_session_tools-3.7.1 → cc_session_tools-3.8.0}/tests/test_migrate_telemetry.py +0 -0
- {cc_session_tools-3.7.1 → cc_session_tools-3.8.0}/tests/test_model_info.py +0 -0
- {cc_session_tools-3.7.1 → cc_session_tools-3.8.0}/tests/test_normalise.py +0 -0
- {cc_session_tools-3.7.1 → cc_session_tools-3.8.0}/tests/test_occupancy.py +0 -0
- {cc_session_tools-3.7.1 → cc_session_tools-3.8.0}/tests/test_parent_inference.py +0 -0
- {cc_session_tools-3.7.1 → cc_session_tools-3.8.0}/tests/test_parser.py +0 -0
- {cc_session_tools-3.7.1 → cc_session_tools-3.8.0}/tests/test_paths.py +0 -0
- {cc_session_tools-3.7.1 → cc_session_tools-3.8.0}/tests/test_pending_migration_hook.py +0 -0
- {cc_session_tools-3.7.1 → cc_session_tools-3.8.0}/tests/test_picker.py +0 -0
- {cc_session_tools-3.7.1 → cc_session_tools-3.8.0}/tests/test_pricing.py +0 -0
- {cc_session_tools-3.7.1 → cc_session_tools-3.8.0}/tests/test_proc_lock.py +0 -0
- {cc_session_tools-3.7.1 → cc_session_tools-3.8.0}/tests/test_prompts.py +0 -0
- {cc_session_tools-3.7.1 → cc_session_tools-3.8.0}/tests/test_query.py +0 -0
- {cc_session_tools-3.7.1 → cc_session_tools-3.8.0}/tests/test_report.py +0 -0
- {cc_session_tools-3.7.1 → cc_session_tools-3.8.0}/tests/test_roots.py +0 -0
- {cc_session_tools-3.7.1 → cc_session_tools-3.8.0}/tests/test_rules.py +0 -0
- {cc_session_tools-3.7.1 → cc_session_tools-3.8.0}/tests/test_scheduler_bundled_jobs.py +0 -0
- {cc_session_tools-3.7.1 → cc_session_tools-3.8.0}/tests/test_schema.py +0 -0
- {cc_session_tools-3.7.1 → cc_session_tools-3.8.0}/tests/test_script_allowlist.py +0 -0
- {cc_session_tools-3.7.1 → cc_session_tools-3.8.0}/tests/test_session_names.py +0 -0
- {cc_session_tools-3.7.1 → cc_session_tools-3.8.0}/tests/test_session_tag.py +0 -0
- {cc_session_tools-3.7.1 → cc_session_tools-3.8.0}/tests/test_sessions.py +0 -0
- {cc_session_tools-3.7.1 → cc_session_tools-3.8.0}/tests/test_sessions_db.py +0 -0
- {cc_session_tools-3.7.1 → cc_session_tools-3.8.0}/tests/test_sessions_repair.py +0 -0
- {cc_session_tools-3.7.1 → cc_session_tools-3.8.0}/tests/test_tasklist.py +0 -0
- {cc_session_tools-3.7.1 → cc_session_tools-3.8.0}/tests/test_telemetry.py +0 -0
- {cc_session_tools-3.7.1 → cc_session_tools-3.8.0}/tests/test_telemetry_store.py +0 -0
- {cc_session_tools-3.7.1 → cc_session_tools-3.8.0}/tests/test_text_wrap.py +0 -0
- {cc_session_tools-3.7.1 → cc_session_tools-3.8.0}/tests/test_transcript.py +0 -0
- {cc_session_tools-3.7.1 → cc_session_tools-3.8.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.
|
|
3
|
+
Version: 3.8.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
|
|
@@ -928,6 +928,25 @@ ccst context-override off # re-enable the warnings
|
|
|
928
928
|
ccst context-override status # report the current state
|
|
929
929
|
```
|
|
930
930
|
|
|
931
|
+
### `ccst pdata readiness-scan`
|
|
932
|
+
|
|
933
|
+
A read-only scan of a project's CSV files for the mechanical blockers to a `ccst pdata init`
|
|
934
|
+
migration, used by the `pm-pdata-do-audit-and-prepare-to-migrate` skill and useful on its own:
|
|
935
|
+
|
|
936
|
+
```sh
|
|
937
|
+
ccst pdata readiness-scan --project myproject # compact Markdown report
|
|
938
|
+
ccst pdata readiness-scan --project myproject --format json # machine-readable
|
|
939
|
+
ccst pdata readiness-scan --project myproject --path notes/ --findings-only # one folder's findings
|
|
940
|
+
```
|
|
941
|
+
|
|
942
|
+
It reports ragged rows, empty/duplicate headers, mixed date formats, mixed `|`/`;` separators,
|
|
943
|
+
machine-specific paths, null-like placeholder strings, duplicate rows, comment rows, repeated
|
|
944
|
+
header rows, byte-order marks, CRLF line endings and unreadable files, plus a per-file inventory
|
|
945
|
+
and the columns that are unique on every row. Findings carry counts and row numbers, never cell
|
|
946
|
+
text, and a unique column is a fact rather than a recommended key. It exits 0 whenever the scan
|
|
947
|
+
ran (findings are not failures), and 2 when the project directory does not exist. It scans exactly
|
|
948
|
+
the CSVs `ccst pdata init` would classify.
|
|
949
|
+
|
|
931
950
|
### `ccst skills install`
|
|
932
951
|
|
|
933
952
|
> You normally don't need this: `ccst install-everything --apply` runs it, and `ccst` runs that
|
|
@@ -871,6 +871,25 @@ ccst context-override off # re-enable the warnings
|
|
|
871
871
|
ccst context-override status # report the current state
|
|
872
872
|
```
|
|
873
873
|
|
|
874
|
+
### `ccst pdata readiness-scan`
|
|
875
|
+
|
|
876
|
+
A read-only scan of a project's CSV files for the mechanical blockers to a `ccst pdata init`
|
|
877
|
+
migration, used by the `pm-pdata-do-audit-and-prepare-to-migrate` skill and useful on its own:
|
|
878
|
+
|
|
879
|
+
```sh
|
|
880
|
+
ccst pdata readiness-scan --project myproject # compact Markdown report
|
|
881
|
+
ccst pdata readiness-scan --project myproject --format json # machine-readable
|
|
882
|
+
ccst pdata readiness-scan --project myproject --path notes/ --findings-only # one folder's findings
|
|
883
|
+
```
|
|
884
|
+
|
|
885
|
+
It reports ragged rows, empty/duplicate headers, mixed date formats, mixed `|`/`;` separators,
|
|
886
|
+
machine-specific paths, null-like placeholder strings, duplicate rows, comment rows, repeated
|
|
887
|
+
header rows, byte-order marks, CRLF line endings and unreadable files, plus a per-file inventory
|
|
888
|
+
and the columns that are unique on every row. Findings carry counts and row numbers, never cell
|
|
889
|
+
text, and a unique column is a fact rather than a recommended key. It exits 0 whenever the scan
|
|
890
|
+
ran (findings are not failures), and 2 when the project directory does not exist. It scans exactly
|
|
891
|
+
the CSVs `ccst pdata init` would classify.
|
|
892
|
+
|
|
874
893
|
### `ccst skills install`
|
|
875
894
|
|
|
876
895
|
> You normally don't need this: `ccst install-everything --apply` runs it, and `ccst` runs that
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "cc-session-tools"
|
|
7
|
-
version = "3.
|
|
7
|
+
version = "3.8.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" }
|
|
@@ -1858,6 +1858,28 @@ def _cmd_pdata_resolve(args: argparse.Namespace) -> int:
|
|
|
1858
1858
|
return 1 if outstanding else 0
|
|
1859
1859
|
|
|
1860
1860
|
|
|
1861
|
+
def _cmd_pdata_readiness_scan(args: argparse.Namespace) -> int:
|
|
1862
|
+
from cc_session_tools.lib.pdata import readiness, store
|
|
1863
|
+
|
|
1864
|
+
try:
|
|
1865
|
+
root = store.project_root(args.project)
|
|
1866
|
+
except ValueError as exc:
|
|
1867
|
+
print(f"ccst pdata readiness-scan: {exc}", file=sys.stderr)
|
|
1868
|
+
return 2
|
|
1869
|
+
if not root.is_dir():
|
|
1870
|
+
print(
|
|
1871
|
+
f"ccst pdata readiness-scan: project directory does not exist: {root}",
|
|
1872
|
+
file=sys.stderr,
|
|
1873
|
+
)
|
|
1874
|
+
return 2
|
|
1875
|
+
report = readiness.scan_project(root, path_prefix=args.path)
|
|
1876
|
+
if args.format == "json":
|
|
1877
|
+
print(readiness.format_json(report))
|
|
1878
|
+
else:
|
|
1879
|
+
print(readiness.format_markdown(report, findings_only=args.findings_only))
|
|
1880
|
+
return 0
|
|
1881
|
+
|
|
1882
|
+
|
|
1861
1883
|
def _cmd_pdata_sync_check(args: argparse.Namespace) -> int:
|
|
1862
1884
|
"""The spec's "Hourly `ccsched` job" trigger: per project, rehydrate-check and - only if no
|
|
1863
1885
|
rehydrate happened - dump-check. The whole decision lives in `pdata.sync_check` (shared with
|
|
@@ -3256,6 +3278,24 @@ def _build_parser() -> argparse.ArgumentParser:
|
|
|
3256
3278
|
"the default one-line summary (--project always prints full detail)",
|
|
3257
3279
|
)
|
|
3258
3280
|
|
|
3281
|
+
pdata_readiness_parser = pdata_sub.add_parser(
|
|
3282
|
+
"readiness-scan",
|
|
3283
|
+
help="Scan a project's CSVs for pdata-migration blockers (read-only)",
|
|
3284
|
+
)
|
|
3285
|
+
pdata_readiness_parser.add_argument("--project", metavar="NAME", required=True)
|
|
3286
|
+
pdata_readiness_parser.add_argument(
|
|
3287
|
+
"--format", choices=("markdown", "json"), default="markdown",
|
|
3288
|
+
help="Output format (default: markdown)",
|
|
3289
|
+
)
|
|
3290
|
+
pdata_readiness_parser.add_argument(
|
|
3291
|
+
"--path", metavar="PREFIX", default=None,
|
|
3292
|
+
help="Only scan CSVs whose project-relative path starts with PREFIX",
|
|
3293
|
+
)
|
|
3294
|
+
pdata_readiness_parser.add_argument(
|
|
3295
|
+
"--findings-only", action="store_true",
|
|
3296
|
+
help="Omit the per-file inventory from the Markdown report",
|
|
3297
|
+
)
|
|
3298
|
+
|
|
3259
3299
|
pdata_dump_parser = pdata_sub.add_parser(
|
|
3260
3300
|
"dump", help="Publish the current local DB state to .pdata-db-dump/latest.sql"
|
|
3261
3301
|
)
|
|
@@ -3659,6 +3699,8 @@ def main() -> None:
|
|
|
3659
3699
|
sys.exit(_cmd_pdata_reconcile_session_output(args))
|
|
3660
3700
|
if args.verb == "verify":
|
|
3661
3701
|
sys.exit(_cmd_pdata_verify(args))
|
|
3702
|
+
if args.verb == "readiness-scan":
|
|
3703
|
+
sys.exit(_cmd_pdata_readiness_scan(args))
|
|
3662
3704
|
if args.verb == "dump":
|
|
3663
3705
|
sys.exit(_cmd_pdata_dump(args))
|
|
3664
3706
|
if args.verb == "rehydrate":
|
|
@@ -0,0 +1,319 @@
|
|
|
1
|
+
"""Deterministic pdata-readiness scan of a project's CSV files (`ccst pdata readiness-scan`).
|
|
2
|
+
|
|
3
|
+
Reports the mechanical blockers a migration audit would otherwise find by reading: ragged rows,
|
|
4
|
+
mixed date formats, mixed separators, machine-specific paths, fake nulls, duplicates, BOM/CRLF and
|
|
5
|
+
unreadable files, plus a per-file inventory and unique-column facts. Read-only: the scan never
|
|
6
|
+
writes. Findings carry counts and 1-based data-record numbers (header excluded), never cell text;
|
|
7
|
+
`unreadable` reasons are a fixed vocabulary, so file content cannot reach a report.
|
|
8
|
+
"""
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
import csv
|
|
12
|
+
import io
|
|
13
|
+
import json
|
|
14
|
+
import os
|
|
15
|
+
import re
|
|
16
|
+
from dataclasses import dataclass
|
|
17
|
+
from datetime import datetime
|
|
18
|
+
from pathlib import Path
|
|
19
|
+
|
|
20
|
+
from cc_session_tools.lib.pdata.init_paths import EXCLUDED_DIR_NAMES
|
|
21
|
+
|
|
22
|
+
FINDING_KINDS: tuple[str, ...] = (
|
|
23
|
+
"ragged-rows", "machine-paths", "null-strings", "duplicate-rows", "comment-rows",
|
|
24
|
+
"repeated-header", "mixed-date-formats", "mixed-separators", "bad-header",
|
|
25
|
+
"no-unique-column", "unreadable", "bom", "crlf",
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
MAX_EXAMPLE_ROWS = 3
|
|
29
|
+
FREE_TEXT_MEAN_LENGTH = 64
|
|
30
|
+
NULL_STRINGS = frozenset({"NO DATA", "N/A", "NA", "NULL", "NONE", "TBD", "UNKNOWN", "-", "--", "?"})
|
|
31
|
+
_MACHINE_PATH = re.compile(r"/Users/[^/\s]+|/home/[^/\s]+|/mnt/[a-z]/|[A-Za-z]:\\Users\\")
|
|
32
|
+
_DATE_FORMATS: tuple[tuple[str, re.Pattern[str]], ...] = (
|
|
33
|
+
("iso-datetime", re.compile(r"^\d{4}-\d{2}-\d{2}[T ]\d{2}:\d{2}")),
|
|
34
|
+
("iso-date", re.compile(r"^\d{4}-\d{2}-\d{2}$")),
|
|
35
|
+
("slash", re.compile(r"^\d{1,2}/\d{1,2}/\d{2,4}$")),
|
|
36
|
+
("d-month-yyyy", re.compile(r"^\d{1,2} [A-Za-z]{3,9} \d{4}$")),
|
|
37
|
+
("month-d-yyyy", re.compile(r"^[A-Za-z]{3,9} \d{1,2},? \d{4}$")),
|
|
38
|
+
)
|
|
39
|
+
_BOM = b"\xef\xbb\xbf"
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
@dataclass(frozen=True)
|
|
43
|
+
class Finding:
|
|
44
|
+
kind: str
|
|
45
|
+
file: str
|
|
46
|
+
column: str | None = None
|
|
47
|
+
count: int | None = None
|
|
48
|
+
rows: tuple[int, ...] = ()
|
|
49
|
+
detail: tuple[tuple[str, int | str], ...] = ()
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
@dataclass(frozen=True)
|
|
53
|
+
class UniqueColumn:
|
|
54
|
+
name: str
|
|
55
|
+
labels: tuple[str, ...]
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
@dataclass(frozen=True)
|
|
59
|
+
class FileReport:
|
|
60
|
+
path: str
|
|
61
|
+
rows: int
|
|
62
|
+
columns: int
|
|
63
|
+
header: tuple[str, ...]
|
|
64
|
+
unique_columns: tuple[UniqueColumn, ...]
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
@dataclass(frozen=True)
|
|
68
|
+
class ReadinessReport:
|
|
69
|
+
files: tuple[FileReport, ...]
|
|
70
|
+
findings: tuple[Finding, ...]
|
|
71
|
+
not_scanned: dict[str, int]
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
class _Unreadable(Exception):
|
|
75
|
+
def __init__(self, reason: str) -> None:
|
|
76
|
+
super().__init__(reason)
|
|
77
|
+
self.reason = reason
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def _is_real_yyyymmdd(value: str) -> bool:
|
|
81
|
+
if not re.fullmatch(r"\d{8}", value):
|
|
82
|
+
return False
|
|
83
|
+
try:
|
|
84
|
+
parsed = datetime.strptime(value, "%Y%m%d")
|
|
85
|
+
except ValueError:
|
|
86
|
+
return False
|
|
87
|
+
return 1900 <= parsed.year <= 2100
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def _date_format(value: str) -> str | None:
|
|
91
|
+
for name, pattern in _DATE_FORMATS:
|
|
92
|
+
if pattern.match(value):
|
|
93
|
+
return name
|
|
94
|
+
return "yyyymmdd" if _is_real_yyyymmdd(value) else None
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def _walk(root: Path) -> list[Path]:
|
|
98
|
+
"""Every file under root outside EXCLUDED_DIR_NAMES, without following directory symlinks -
|
|
99
|
+
the same exclusion set `ccst pdata init`'s classifier uses, so coverage matches migration."""
|
|
100
|
+
found: list[Path] = []
|
|
101
|
+
for dirpath, dirnames, filenames in os.walk(root, followlinks=False):
|
|
102
|
+
dirnames[:] = sorted(d for d in dirnames if d not in EXCLUDED_DIR_NAMES)
|
|
103
|
+
found.extend(Path(dirpath) / name for name in sorted(filenames))
|
|
104
|
+
return found
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def _parse(raw: bytes) -> tuple[list[list[str]], bool, bool]:
|
|
108
|
+
if not raw:
|
|
109
|
+
raise _Unreadable("empty-file")
|
|
110
|
+
try:
|
|
111
|
+
text = raw.decode("utf-8-sig")
|
|
112
|
+
except UnicodeDecodeError:
|
|
113
|
+
raise _Unreadable("not-utf8") from None
|
|
114
|
+
if not text.strip():
|
|
115
|
+
raise _Unreadable("empty-file")
|
|
116
|
+
try:
|
|
117
|
+
records = list(csv.reader(io.StringIO(text, newline=""), strict=True))
|
|
118
|
+
except csv.Error as exc:
|
|
119
|
+
raise _Unreadable(
|
|
120
|
+
"field-too-large" if "field larger" in str(exc) else "csv-parse-error"
|
|
121
|
+
) from None
|
|
122
|
+
return records, raw.startswith(_BOM), b"\r\n" in raw
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
def _scan_file(rel: str, raw: bytes) -> tuple[FileReport | None, list[Finding]]:
|
|
126
|
+
try:
|
|
127
|
+
records, has_bom, has_crlf = _parse(raw)
|
|
128
|
+
except _Unreadable as exc:
|
|
129
|
+
return None, [Finding("unreadable", rel, detail=(("reason", exc.reason),))]
|
|
130
|
+
|
|
131
|
+
findings: list[Finding] = []
|
|
132
|
+
if has_bom:
|
|
133
|
+
findings.append(Finding("bom", rel))
|
|
134
|
+
if has_crlf:
|
|
135
|
+
findings.append(Finding("crlf", rel))
|
|
136
|
+
|
|
137
|
+
header = records[0]
|
|
138
|
+
ncols = len(header)
|
|
139
|
+
if any(not h.strip() for h in header) or len(set(header)) != ncols:
|
|
140
|
+
findings.append(Finding("bad-header", rel))
|
|
141
|
+
|
|
142
|
+
data = [r for r in records[1:] if r]
|
|
143
|
+
nonempty = [0] * ncols
|
|
144
|
+
lengths = [0] * ncols
|
|
145
|
+
distinct: list[set[str]] = [set() for _ in range(ncols)]
|
|
146
|
+
all_int = [True] * ncols
|
|
147
|
+
dates: list[dict[str, int]] = [{} for _ in range(ncols)]
|
|
148
|
+
seps: list[dict[str, int]] = [{} for _ in range(ncols)]
|
|
149
|
+
nulls: list[dict[str, int]] = [{} for _ in range(ncols)]
|
|
150
|
+
null_rows: list[list[int]] = [[] for _ in range(ncols)]
|
|
151
|
+
path_rows: list[list[int]] = [[] for _ in range(ncols)]
|
|
152
|
+
ragged: list[int] = []
|
|
153
|
+
dup_rows: list[int] = []
|
|
154
|
+
comment_rows: list[int] = []
|
|
155
|
+
header_rows: list[int] = []
|
|
156
|
+
seen_rows: set[tuple[str, ...]] = set()
|
|
157
|
+
|
|
158
|
+
for number, row in enumerate(data, start=1):
|
|
159
|
+
if row == header:
|
|
160
|
+
header_rows.append(number)
|
|
161
|
+
if row[0].startswith("#"):
|
|
162
|
+
comment_rows.append(number)
|
|
163
|
+
if len(row) != ncols:
|
|
164
|
+
ragged.append(number)
|
|
165
|
+
key = tuple(row)
|
|
166
|
+
if key in seen_rows:
|
|
167
|
+
dup_rows.append(number)
|
|
168
|
+
seen_rows.add(key)
|
|
169
|
+
for i, value in enumerate(row[:ncols]):
|
|
170
|
+
if not value.strip():
|
|
171
|
+
continue
|
|
172
|
+
nonempty[i] += 1
|
|
173
|
+
lengths[i] += len(value)
|
|
174
|
+
distinct[i].add(value)
|
|
175
|
+
if not re.fullmatch(r"-?\d+", value.strip()):
|
|
176
|
+
all_int[i] = False
|
|
177
|
+
fmt = _date_format(value.strip())
|
|
178
|
+
if fmt:
|
|
179
|
+
dates[i][fmt] = dates[i].get(fmt, 0) + 1
|
|
180
|
+
for sep in ("|", ";"):
|
|
181
|
+
if sep in value:
|
|
182
|
+
seps[i][sep] = seps[i].get(sep, 0) + 1
|
|
183
|
+
token = value.strip().upper()
|
|
184
|
+
if token in NULL_STRINGS:
|
|
185
|
+
nulls[i][token] = nulls[i].get(token, 0) + 1
|
|
186
|
+
null_rows[i].append(number)
|
|
187
|
+
if _MACHINE_PATH.search(value):
|
|
188
|
+
path_rows[i].append(number)
|
|
189
|
+
|
|
190
|
+
def row_finding(kind: str, rows: list[int], column: str | None = None,
|
|
191
|
+
detail: tuple[tuple[str, int | str], ...] = ()) -> Finding:
|
|
192
|
+
return Finding(kind, rel, column, len(rows), tuple(rows[:MAX_EXAMPLE_ROWS]), detail)
|
|
193
|
+
|
|
194
|
+
if ragged:
|
|
195
|
+
findings.append(row_finding("ragged-rows", ragged))
|
|
196
|
+
if dup_rows:
|
|
197
|
+
findings.append(row_finding("duplicate-rows", dup_rows))
|
|
198
|
+
if comment_rows:
|
|
199
|
+
findings.append(row_finding("comment-rows", comment_rows))
|
|
200
|
+
if header_rows:
|
|
201
|
+
findings.append(row_finding("repeated-header", header_rows))
|
|
202
|
+
for i, name in enumerate(header):
|
|
203
|
+
if len(dates[i]) >= 2:
|
|
204
|
+
findings.append(Finding("mixed-date-formats", rel, name,
|
|
205
|
+
detail=tuple(sorted(dates[i].items()))))
|
|
206
|
+
if len(seps[i]) == 2:
|
|
207
|
+
findings.append(Finding("mixed-separators", rel, name,
|
|
208
|
+
detail=tuple(sorted(seps[i].items()))))
|
|
209
|
+
if path_rows[i]:
|
|
210
|
+
findings.append(row_finding("machine-paths", path_rows[i], name))
|
|
211
|
+
if null_rows[i]:
|
|
212
|
+
findings.append(row_finding("null-strings", null_rows[i], name,
|
|
213
|
+
tuple(sorted(nulls[i].items()))))
|
|
214
|
+
|
|
215
|
+
unique: list[UniqueColumn] = []
|
|
216
|
+
if len(data) >= 2:
|
|
217
|
+
for i, name in enumerate(header):
|
|
218
|
+
if nonempty[i] == len(data) and len(distinct[i]) == len(data):
|
|
219
|
+
labels: list[str] = []
|
|
220
|
+
if all_int[i]:
|
|
221
|
+
labels.append("integer-like")
|
|
222
|
+
if lengths[i] / len(data) > FREE_TEXT_MEAN_LENGTH:
|
|
223
|
+
labels.append("free-text")
|
|
224
|
+
unique.append(UniqueColumn(name, tuple(labels)))
|
|
225
|
+
if not unique:
|
|
226
|
+
findings.append(Finding("no-unique-column", rel))
|
|
227
|
+
|
|
228
|
+
return FileReport(rel, len(data), ncols, tuple(header), tuple(unique)), findings
|
|
229
|
+
|
|
230
|
+
|
|
231
|
+
def scan_project(root: Path, *, path_prefix: str | None = None) -> ReadinessReport:
|
|
232
|
+
files: list[FileReport] = []
|
|
233
|
+
findings: list[Finding] = []
|
|
234
|
+
not_scanned = {"json": 0, "markdown": 0, "other": 0}
|
|
235
|
+
for abs_path in _walk(root):
|
|
236
|
+
rel = abs_path.relative_to(root).as_posix()
|
|
237
|
+
if path_prefix and not rel.startswith(path_prefix):
|
|
238
|
+
continue
|
|
239
|
+
suffix = abs_path.suffix.lower()
|
|
240
|
+
if suffix != ".csv":
|
|
241
|
+
bucket = {".json": "json", ".md": "markdown", ".markdown": "markdown"}.get(suffix, "other")
|
|
242
|
+
not_scanned[bucket] += 1
|
|
243
|
+
continue
|
|
244
|
+
report, file_findings = _scan_file(rel, abs_path.read_bytes())
|
|
245
|
+
if report is not None:
|
|
246
|
+
files.append(report)
|
|
247
|
+
findings.extend(file_findings)
|
|
248
|
+
findings.sort(key=lambda f: (f.file, FINDING_KINDS.index(f.kind), f.column or ""))
|
|
249
|
+
return ReadinessReport(tuple(files), tuple(findings), not_scanned)
|
|
250
|
+
|
|
251
|
+
|
|
252
|
+
def _finding_line(f: Finding) -> str:
|
|
253
|
+
parts = [f"- {f.kind}: {f.file}"]
|
|
254
|
+
if f.column is not None:
|
|
255
|
+
parts.append(f"column {f.column!r}")
|
|
256
|
+
if f.count is not None:
|
|
257
|
+
parts.append(f"count={f.count}")
|
|
258
|
+
if f.rows:
|
|
259
|
+
parts.append("rows=" + ",".join(str(r) for r in f.rows))
|
|
260
|
+
if f.detail:
|
|
261
|
+
parts.append(", ".join(f"{k}={v}" for k, v in f.detail))
|
|
262
|
+
return " ".join(parts)
|
|
263
|
+
|
|
264
|
+
|
|
265
|
+
def format_markdown(report: ReadinessReport, *, findings_only: bool = False) -> str:
|
|
266
|
+
ns = report.not_scanned
|
|
267
|
+
lines = [
|
|
268
|
+
"# pdata readiness scan",
|
|
269
|
+
"",
|
|
270
|
+
"Row numbers are 1-based data records (header excluded), not line numbers: a quoted "
|
|
271
|
+
"multiline cell makes them differ.",
|
|
272
|
+
f"Scanned {len(report.files)} CSV file(s); not scanned: {ns['json']} JSON, "
|
|
273
|
+
f"{ns['markdown']} Markdown, {ns['other']} other.",
|
|
274
|
+
]
|
|
275
|
+
if not report.files and not any(f.kind == "unreadable" for f in report.findings):
|
|
276
|
+
lines += ["", "No CSV files found."]
|
|
277
|
+
lines += ["", "## Findings"]
|
|
278
|
+
lines += [_finding_line(f) for f in report.findings] or ["No findings."]
|
|
279
|
+
if not findings_only and report.files:
|
|
280
|
+
lines += [
|
|
281
|
+
"",
|
|
282
|
+
"## Inventory",
|
|
283
|
+
"A unique column is a fact, not a recommended key: it is not a stable key until a "
|
|
284
|
+
"human has confirmed its values are not re-numbered per session or machine.",
|
|
285
|
+
]
|
|
286
|
+
for f in report.files:
|
|
287
|
+
unique = ", ".join(
|
|
288
|
+
u.name + (f" ({'/'.join(u.labels)})" if u.labels else "") for u in f.unique_columns
|
|
289
|
+
)
|
|
290
|
+
lines.append(
|
|
291
|
+
f"- {f.path}: {f.rows} rows, {f.columns} columns"
|
|
292
|
+
+ (f"; unique: {unique}" if unique else "")
|
|
293
|
+
)
|
|
294
|
+
return "\n".join(lines)
|
|
295
|
+
|
|
296
|
+
|
|
297
|
+
def format_json(report: ReadinessReport) -> str:
|
|
298
|
+
return json.dumps(
|
|
299
|
+
{
|
|
300
|
+
"files": [
|
|
301
|
+
{
|
|
302
|
+
"path": f.path, "rows": f.rows, "columns": f.columns,
|
|
303
|
+
"header": list(f.header),
|
|
304
|
+
"unique_columns": [{"name": u.name, "labels": list(u.labels)}
|
|
305
|
+
for u in f.unique_columns],
|
|
306
|
+
}
|
|
307
|
+
for f in report.files
|
|
308
|
+
],
|
|
309
|
+
"findings": [
|
|
310
|
+
{
|
|
311
|
+
"kind": f.kind, "file": f.file, "column": f.column, "count": f.count,
|
|
312
|
+
"rows": list(f.rows), "detail": dict(f.detail),
|
|
313
|
+
}
|
|
314
|
+
for f in report.findings
|
|
315
|
+
],
|
|
316
|
+
"not_scanned": report.not_scanned,
|
|
317
|
+
},
|
|
318
|
+
indent=2,
|
|
319
|
+
)
|
cc_session_tools-3.8.0/src/cc_session_tools/skills/pm-pdata-do-audit-and-prepare-to-migrate/SKILL.md
ADDED
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
---
|
|
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 feeds `ccst pdata init` (via `pm-pdata-do-init`) - the tool-native migration mechanism - and, only for the narrow record shapes it can't express, the deprecated `pm-pdata-do-manual-migration` 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
|
+
---
|
|
5
|
+
|
|
6
|
+
# Audit central files
|
|
7
|
+
|
|
8
|
+
> **This audit feeds `ccst pdata init`, not a manual migration.** The readiness manifest (Phase 5)
|
|
9
|
+
> is a pre-flight pass for `ccst pdata init` (driven by `pm-pdata-do-init`), the default migration
|
|
10
|
+
> mechanism. `pm-pdata-do-manual-migration` is a deprecated fallback for record shapes `ccst pdata
|
|
11
|
+
> init` cannot express, not the default next step.
|
|
12
|
+
|
|
13
|
+
## When to use this vs. `pm-update-central-files`
|
|
14
|
+
|
|
15
|
+
`pm-update-central-files` is a single-session wrap-up (WORKLOG, memory, registering new `out/`
|
|
16
|
+
files). This skill is a project-wide sweep of every central file (indexes, logs, registers, CSVs),
|
|
17
|
+
run on demand, that can span a session or more. Use it when the whole project's recordkeeping needs
|
|
18
|
+
a health check, not just today's session tidied up.
|
|
19
|
+
|
|
20
|
+
## Overview
|
|
21
|
+
|
|
22
|
+
Five phases, each leaving a durable artefact. Four things called "readiness" are distinct:
|
|
23
|
+
the scan (`ccst pdata readiness-scan`, computed on demand), `pdata-readiness-notes.md` (Phase 4's
|
|
24
|
+
findings list), the Phase 5 manifest (`analysis/pdata-readiness.md` or equivalent), and
|
|
25
|
+
`.pdata-migration-manifest.json` (`ccst pdata init`'s own classification, not written here).
|
|
26
|
+
|
|
27
|
+
1. **OneDrive sync-conflict cleanup** (synced folders only).
|
|
28
|
+
2. **Readiness scan, then judgement audit** of each central-file domain.
|
|
29
|
+
3. **Consolidated report + checkpoint table.**
|
|
30
|
+
4. **pdata-readiness pass:** every blocker, file by file.
|
|
31
|
+
5. **Readiness manifest** for `ccst pdata init`.
|
|
32
|
+
|
|
33
|
+
Nothing destructive happens without the user's go-ahead. Deletions are always handed over as a
|
|
34
|
+
reviewable `rm` script, per `resolve-onedrive-conflicts`; never run `rm` yourself, even after
|
|
35
|
+
approval.
|
|
36
|
+
|
|
37
|
+
## Phase 1: OneDrive sync-conflict cleanup
|
|
38
|
+
|
|
39
|
+
Invoke `resolve-onedrive-conflicts` and run its scanner across the project tree; run it before
|
|
40
|
+
anything else so stale device-suffixed copies do not pollute later phases. Treat a
|
|
41
|
+
disproportionate `true_conflict` count as a scanner false-positive to investigate (the hyphen
|
|
42
|
+
heuristic can match a project's own multi-hyphen naming) and filter to genuine device-suffix
|
|
43
|
+
conflicts before trusting it. Split them into **central/index files** (a handful, reviewable
|
|
44
|
+
directly) and **bulk content files** (dozens or hundreds; give them their own agent with the
|
|
45
|
+
file list).
|
|
46
|
+
|
|
47
|
+
## Phase 2: readiness scan, then judgement audit
|
|
48
|
+
|
|
49
|
+
**Step 1 - run the scan before any per-domain checking or agent dispatch:**
|
|
50
|
+
|
|
51
|
+
```
|
|
52
|
+
ccst pdata readiness-scan --project <name> # whole project, compact Markdown
|
|
53
|
+
ccst pdata readiness-scan --project <name> --format json
|
|
54
|
+
ccst pdata readiness-scan --project <name> --path <domain-dir>/ --findings-only # one domain's slice
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
It is read-only and deterministic. It reports, per CSV, `ragged-rows`, `bad-header`,
|
|
58
|
+
`mixed-date-formats`, `mixed-separators` (`|` with `;` only), `machine-paths`, `null-strings`,
|
|
59
|
+
`duplicate-rows`, `comment-rows`, `repeated-header`, `no-unique-column`, `bom`, `crlf` and
|
|
60
|
+
`unreadable`, plus row counts and unique columns. Treat findings as leads to verify, not facts to
|
|
61
|
+
act on (a lone `-` may be a real dash). Its scanned set equals what `ccst pdata init` will
|
|
62
|
+
classify. Row numbers are data records, not line numbers.
|
|
63
|
+
|
|
64
|
+
A **unique column is not a stable key.** Whether it is one is a judgement, and the scan is input
|
|
65
|
+
to that judgement, not its answer: watch especially for `row_id`-style columns numbered
|
|
66
|
+
independently by different sessions or machines - these are NOT stable keys even though they look
|
|
67
|
+
like them. A `bom` is what turns a header garbled
|
|
68
|
+
under `ccst pdata init`; `crlf` is worth normalising first.
|
|
69
|
+
|
|
70
|
+
**Step 2 - judgement audit.** Group the central files the project's own instructions document,
|
|
71
|
+
plus anything on disk that is not documented (itself a gap), into domains (usually one per
|
|
72
|
+
top-level folder with its own index). With **three or fewer domains**, audit them yourself in one
|
|
73
|
+
pass. With **more than three**, dispatch one Sonnet-tier agent per domain in parallel, per
|
|
74
|
+
`dispatching-parallel-agents` and `select-agent-model`, giving each only its own domain's scan
|
|
75
|
+
slice (`--path`, `--findings-only`). Agents check, with concrete evidence for every claim:
|
|
76
|
+
|
|
77
|
+
- **File-to-index correspondence:** every file an index describes exists; every file that should be
|
|
78
|
+
indexed has a row.
|
|
79
|
+
- **Controlled vocabularies:** any documented enum column uses only documented values.
|
|
80
|
+
- **Cross-reference integrity:** every row_id or back-reference resolves.
|
|
81
|
+
- **Documented but missing:** every file or folder the project's instructions describe exists. This
|
|
82
|
+
rot is common and durable because a merely-referenced file that is absent breaks nothing loudly.
|
|
83
|
+
- **Derived-artefact completeness:** each source has its documented mirror, and no mirror is
|
|
84
|
+
silently empty.
|
|
85
|
+
- **Own-documentation accuracy:** every path, count and reference in an atlas or index-of-indexes
|
|
86
|
+
checked against the live filesystem.
|
|
87
|
+
- **The scan's blind spots:** multi-schema single files (comment banners, section headers, a second
|
|
88
|
+
table under one file - `repeated-header` and `comment-rows` are only hints) and
|
|
89
|
+
comma-versus-semicolon list columns.
|
|
90
|
+
- **Key stability:** whether each unique column is really a stable key.
|
|
91
|
+
|
|
92
|
+
Agents work read-only unless a fix is unambiguous and mechanical (merging two non-overlapping logs);
|
|
93
|
+
anything needing editorial judgement is a recommendation. Use `resolve-onedrive-conflicts`'s
|
|
94
|
+
`DELETE-SAFE` / `MERGE-NEEDED` / `RENAME` / `NO ACTION` verdicts for consistency.
|
|
95
|
+
|
|
96
|
+
## Phase 3: consolidated report + checkpoint table
|
|
97
|
+
|
|
98
|
+
Write one consolidated report to the session's `out/` covering every domain and the scan, and give
|
|
99
|
+
the user a checkpoint table split into:
|
|
100
|
+
|
|
101
|
+
- **Unambiguous fixes** ready on a single approval (documentation corrections, verified merges).
|
|
102
|
+
- **Genuine judgement calls** to weigh individually (ambiguous ownership, prune-vs-keep, which of
|
|
103
|
+
two diverged forks is authoritative).
|
|
104
|
+
- **Real gaps** found but not fixed (missing extractions, undersized indexes, structural drift):
|
|
105
|
+
recommend a project TODO entry or a prompt file for a dedicated session.
|
|
106
|
+
|
|
107
|
+
Hand back one reviewable delete script for every confirmed-safe deletion, exactly per
|
|
108
|
+
`resolve-onedrive-conflicts` (comment header with each item's evidence, `set -e`, one `rm --` per
|
|
109
|
+
line, told to the user as "review, then run yourself").
|
|
110
|
+
|
|
111
|
+
## Phase 4: pdata-readiness pass
|
|
112
|
+
|
|
113
|
+
Compile `pdata-readiness-notes.md` (or equivalent): fold in the scan's findings and add what it
|
|
114
|
+
cannot see, listing file by file every blocker a `pm-pdata-design-schema` session would hit:
|
|
115
|
+
|
|
116
|
+
- Inconsistent separators within one logical column (including comma/semicolon).
|
|
117
|
+
- No safe, durable, cross-session-stable natural key - especially independently-numbered `row_id`
|
|
118
|
+
columns.
|
|
119
|
+
- Mixed date/timestamp formats.
|
|
120
|
+
- A file that is really several differently-shaped tables: needs a "split into multiple record
|
|
121
|
+
collections" decision, not a naive single-table import.
|
|
122
|
+
- Machine-specific or session-relative paths in a column.
|
|
123
|
+
- Null conventions that are not real nulls (`"NO DATA"`, an empty cell that means something else).
|
|
124
|
+
- A `bom` or `crlf` file that will need normalising.
|
|
125
|
+
|
|
126
|
+
This file is a findings list, not a migration plan.
|
|
127
|
+
|
|
128
|
+
## Phase 5: write the readiness manifest (for `ccst pdata init` / `pm-pdata-do-init`)
|
|
129
|
+
|
|
130
|
+
Once the audit settles (typically two consecutive clean verification rounds, or a documented
|
|
131
|
+
pragmatic stop), write one manifest at a stable location (`analysis/pdata-readiness.md` for
|
|
132
|
+
projects with an `analysis/`-style folder, else the project's own top-level docs location), and
|
|
133
|
+
record its location in the project's instructions file. Sections, in this order, so `pm-pdata-do-init`
|
|
134
|
+
(or the deprecated `pm-pdata-do-manual-migration` fallback) can parse it deterministically:
|
|
135
|
+
|
|
136
|
+
1. **Header:** audit date, session/agent identifier, and an explicit "STALE IF" note (e.g. "stale
|
|
137
|
+
if any file below was edited after `<date>`").
|
|
138
|
+
2. **File inventory:** every central/index/recordkeeping file covered, with full path, row/entry
|
|
139
|
+
count, last-verified-clean date, and role (`index`, `register`, `log`, `narrative`,
|
|
140
|
+
`derived-artefact`).
|
|
141
|
+
3. **Per-file migration blockers:** Phase 4's content, in the shape `pm-pdata-design-schema` and
|
|
142
|
+
`pm-pdata-do-init` consume.
|
|
143
|
+
4. **Recommended natural key per file** where one exists cleanly, else an explicit "no stable key
|
|
144
|
+
found, needs a synthetic id".
|
|
145
|
+
5. **Known-acceptable duplication:** anything duplicate by design, so a migration does not re-flag
|
|
146
|
+
it or silently drop one copy.
|
|
147
|
+
6. **Open decisions:** anything needing the owner's judgement and not resolved; a later session must
|
|
148
|
+
not treat an unresolved item as settled.
|
|
149
|
+
|
|
150
|
+
## Common mistakes
|
|
151
|
+
|
|
152
|
+
- Trusting the conflict scanner's raw `true_conflict` count without checking for the naming-pattern
|
|
153
|
+
false positive.
|
|
154
|
+
- Treating the scan's unique columns as recommended keys, or its findings as facts to fix blindly.
|
|
155
|
+
- Assuming documentation implies existence: check every documented path.
|
|
156
|
+
- Running `rm` yourself after approval instead of handing back a reviewable script.
|
|
157
|
+
- Dispatching one do-everything agent, or a fleet for a three-domain project: use one agent per
|
|
158
|
+
domain only above three domains.
|
|
159
|
+
- Skipping the readiness pass because "the project isn't migrating yet": if a `.pdata-db-dump/` or a
|
|
160
|
+
pdata mention exists, it earns its cost early.
|
|
161
|
+
- Silently fixing a judgement call an agent flagged: "safe to apply" and "needs a human decision" are
|
|
162
|
+
different buckets even when both are technically fixable.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: cc-session-tools
|
|
3
|
-
Version: 3.
|
|
3
|
+
Version: 3.8.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
|
|
@@ -928,6 +928,25 @@ ccst context-override off # re-enable the warnings
|
|
|
928
928
|
ccst context-override status # report the current state
|
|
929
929
|
```
|
|
930
930
|
|
|
931
|
+
### `ccst pdata readiness-scan`
|
|
932
|
+
|
|
933
|
+
A read-only scan of a project's CSV files for the mechanical blockers to a `ccst pdata init`
|
|
934
|
+
migration, used by the `pm-pdata-do-audit-and-prepare-to-migrate` skill and useful on its own:
|
|
935
|
+
|
|
936
|
+
```sh
|
|
937
|
+
ccst pdata readiness-scan --project myproject # compact Markdown report
|
|
938
|
+
ccst pdata readiness-scan --project myproject --format json # machine-readable
|
|
939
|
+
ccst pdata readiness-scan --project myproject --path notes/ --findings-only # one folder's findings
|
|
940
|
+
```
|
|
941
|
+
|
|
942
|
+
It reports ragged rows, empty/duplicate headers, mixed date formats, mixed `|`/`;` separators,
|
|
943
|
+
machine-specific paths, null-like placeholder strings, duplicate rows, comment rows, repeated
|
|
944
|
+
header rows, byte-order marks, CRLF line endings and unreadable files, plus a per-file inventory
|
|
945
|
+
and the columns that are unique on every row. Findings carry counts and row numbers, never cell
|
|
946
|
+
text, and a unique column is a fact rather than a recommended key. It exits 0 whenever the scan
|
|
947
|
+
ran (findings are not failures), and 2 when the project directory does not exist. It scans exactly
|
|
948
|
+
the CSVs `ccst pdata init` would classify.
|
|
949
|
+
|
|
931
950
|
### `ccst skills install`
|
|
932
951
|
|
|
933
952
|
> You normally don't need this: `ccst install-everything --apply` runs it, and `ccst` runs that
|
|
@@ -71,6 +71,7 @@ src/cc_session_tools/lib/pdata/init_paths.py
|
|
|
71
71
|
src/cc_session_tools/lib/pdata/init_service.py
|
|
72
72
|
src/cc_session_tools/lib/pdata/manifest.py
|
|
73
73
|
src/cc_session_tools/lib/pdata/naming.py
|
|
74
|
+
src/cc_session_tools/lib/pdata/readiness.py
|
|
74
75
|
src/cc_session_tools/lib/pdata/rehydrate.py
|
|
75
76
|
src/cc_session_tools/lib/pdata/rename_group.py
|
|
76
77
|
src/cc_session_tools/lib/pdata/reorganize.py
|
|
@@ -218,6 +219,7 @@ tests/test_ccst_machine_identity_cli.py
|
|
|
218
219
|
tests/test_ccst_main_gate.py
|
|
219
220
|
tests/test_ccst_pdata_cli.py
|
|
220
221
|
tests/test_ccst_pdata_init_cli.py
|
|
222
|
+
tests/test_ccst_pdata_readiness_cli.py
|
|
221
223
|
tests/test_ccst_pdata_reconcile_cli.py
|
|
222
224
|
tests/test_ccst_pdata_rename_group_cli.py
|
|
223
225
|
tests/test_ccst_pdata_reorganize_cli.py
|
|
@@ -264,6 +266,7 @@ tests/test_occupancy.py
|
|
|
264
266
|
tests/test_parent_inference.py
|
|
265
267
|
tests/test_parser.py
|
|
266
268
|
tests/test_paths.py
|
|
269
|
+
tests/test_pdata_audit_skill.py
|
|
267
270
|
tests/test_pending_migration_hook.py
|
|
268
271
|
tests/test_picker.py
|
|
269
272
|
tests/test_pricing.py
|