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