One-sentence purpose: idempotent rollout helper that brings a
consumer project into conformance with the backup/sessions/
convention defined by session_snapshot and jsonl_snapshot.
- Once per consumer project during the initial rollout of the backup/sessions convention.
- Anytime later to re-check a project against the convention — the tool is idempotent, so rerunning it on a conforming project is a safe no-op that reports "nothing to do".
- After a convention change — if the target state evolves, a new release of this tool can bring every consumer back into conformance with one command per repo.
This tool is the paired migration helper for the two snapshot
tools. See
session_snapshot and
jsonl_snapshot for the backup
tools themselves.
Given a consumer project git repository, the tool:
-
Creates
backup/sessions/if missing. -
Writes
backup/sessions/README.mdfrom an embedded template that documents the convention (project-agnostic, no name substitution needed). -
Writes
backup/sessions/raw/.gitignoreso theraw/subdirectory is tracked but its contents are ignored. -
Adds
exports/to the project's root.gitignorewith an explanatory comment block. Skipped ifexports/is already ignored. -
Removes tracked 0-byte
exports/*files viagit rm. These are stubs created by the broken Claude Code/exportcommand and have no content. -
Bumps the
scripts/python/sharedsubmodule pointer to the target SHA (default:hybrid_scripts_pythonmain HEAD at the time this tool shipped). Overridable via--submodule-ref. -
Lists non-zero-byte
.mdfiles inexports/as candidates for human review. These are NOT migrated by default because judgment is required: some are meaningful alignment notes or PR review packages worth preserving, others are stale scratch that should just be dropped. -
Surfaces non-zero-byte non-
.mdtracked files inexports/as orphans with a[WARNING]banner. Orphans are intentionally left untouched. The tool nevergit rms,git mvs, or modifies them in any way. They typically are large oldClaude_Code_Export .txttranscripts from before the/exportregression, and their fate (keep as conversation artifact? drop from HEAD? preserve inbackup/sessions/?) depends on content value only a human can judge. The operator handles them after the tool runs.
Optional mode:
--migrate-all-md: alsogit mvevery candidate intobackup/sessions/with a UTC-prefixed name derived from the candidate's first-commit timestamp. Use this when you want to preserve everything and cherry-pick drops afterward. Note: this flag only affects candidates, not orphans — orphans remain intentionally untouched regardless of flags.
The tool never commits. All changes are left staged in the working tree so the operator can review and commit them as a single migration commit.
Every task checks its target before acting:
- Directory creation is
mkdir -p, harmless if already present. - File writes use "write if missing" semantics.
.gitignoreappending checks for the sentinel line first.- Submodule pointer is compared against the target SHA before
calling
git submodule update --remote. - Zero-byte stub removal walks tracked files and only touches
files with
stat().st_size == 0.
A consumer project that has already been migrated reports "nothing to do" on a rerun. A partially migrated project gets only the missing pieces applied.
Matches session_snapshot and jsonl_snapshot:
<YYYYMMDDTHHMMSSZ>__<original-basename>
The UTC timestamp comes from git log --diff-filter=A --follow on
the candidate file — it is the first commit that added the file to
the index. Authoritative, unambiguous, and matches the timestamp
strategy used elsewhere.
Candidates whose first-commit timestamp cannot be resolved (never
committed, path outside the repo, git history rewritten) are
skipped even in --migrate-all-md mode, with an explicit
"skipped: cannot derive UTC timestamp" message. Those files must
be moved manually.
migrate_to_backup_sessions [--project-root DIR]
[--submodule-ref SHA]
[--migrate-all-md]
[--dry-run]
| Flag | Purpose | Default |
|---|---|---|
--project-root DIR |
Consumer project git root. | find_git_root(cwd) |
--submodule-ref SHA |
Target SHA for scripts/python/shared. |
hybrid_scripts_python main HEAD at tool ship time (see DEFAULT_SUBMODULE_REF constant) |
--migrate-all-md |
Also git mv every non-zero-byte candidate .md. |
off |
--dry-run |
Report what would happen without writing. | off |
| Code | Meaning |
|---|---|
| 0 | Success (either "nothing to do" or changes applied) |
| 1 | Runtime error (I/O, git subprocess failure, value error) |
| 2 | Bad argument (invalid --project-root) |
| 3 | Not inside a git repository (no --project-root resolvable) |
# Start in a consumer project worktree
cd ~/Ada/github.com/abitofhelp/adafmt
# First pass: dry-run to see what will happen
PYTHONPATH=scripts/python/shared python3 -m migrate_to_backup_sessions --dry-run
# Second pass: apply the mechanical changes
PYTHONPATH=scripts/python/shared python3 -m migrate_to_backup_sessions
# Review the candidate list (printed at the end of the output):
# - Keep the meaningful ones? Rerun with --migrate-all-md
# - Drop everything? git rm the candidates manually
#
# In this example we keep everything:
PYTHONPATH=scripts/python/shared python3 -m migrate_to_backup_sessions --migrate-all-md
# Review staged changes and commit
git status
git diff --cached
git commit -m "chore(backup): adopt backup/sessions/ convention"
git pushRerunning on a conforming project:
$ migrate_to_backup_sessions
project root: /Users/mike/Ada/github.com/abitofhelp/adafmt
target submodule: a3ace7031203
already done:
- backup/sessions/ exists
- backup/sessions/README.md exists
- backup/sessions/raw/.gitignore exists
- exports/ already in .gitignore
- scripts/python/shared submodule already at a3ace70
planned tasks: none (project already conforms)
non-zero-byte .md candidates: none
nothing to do; project already conforms.- Does not commit. Changes are staged and left for human review.
- Does not push or open PRs. That remains a manual step per project so the operator can add project-specific commit message content.
- Does not touch the formal docs, disposition ledger, or any Ada/Go/C++ source code. Migration scope is purely the operational directory layout and the submodule pointer.
- Does not touch orphans. Non-zero-byte non-
.mdtracked files underexports/are flagged as orphans and intentionally left in place. The--migrate-all-mdflag does NOT apply to them — orphan handling is always a human decision because their conversation value is something only an operator can judge. If you want to drop them, rungit rmmanually. If you want to preserve them as conversation artifacts, rungit mvmanually intobackup/sessions/with a UTC-prefixed name. The tool's job is to tell you they exist, not to pick their fate.
session_snapshot— strategic memory-file backup tooljsonl_snapshot— forensic session-jsonl backup toolbackup/sessions/README.mdin any migrated consumer — same convention documentation, embedded by this tool during rollout