Upgrade the user's .cheat-state.json from the old schema_version to the LATEST_SCHEMA。
Overview
[用户:迁移 / 或 SessionStart 提示后用户跑]
↓
[Phase 0: 读 .cheat-state.json + migrations/registry.md → 确定迁移链]
↓
[Phase 1: dry-run(默认)展示迁移计划,等用户确认]
↓
[Phase 2: 备份 .cheat-state.json → .cheat-state.json.backup-<timestamp>]
↓
[Phase 3: 按顺序对每个 step 应用对应迁移文件的 HOW 段]
↓
[Phase 4: 验证升级后 state 文件能被解析 + schema_version 已更新]
↓
[Phase 5: 报告 + 提示如有备份需要清理]
Constants
- REGISTRY_PATH =
${SKILL_DIR}/../../migrations/registry.mdcurrently expected by cheat-on-content — single source of truth for the version chain - MIGRATIONS_DIR =
${SKILL_DIR}/../../migrations/— migration file directory - DRY_RUN_BY_DEFAULT = true — on the first run, show the plan without directly modifying files
- BACKUP_BEFORE_WRITE = true — a backup is required before writing; backup files are kept until the next successful init / manual cleanup by the user
- STOP_ON_STEP_FAILURE = true — if any step fails → stop at the intermediate version, do not advance, do not roll back
💡 Override at invocation:
/cheat-migrate — dry-run: falseexecute directly //cheat-migrate — to: 1.2upgrade only to the specified version
Inputs
| Required | Source |
|---|---|
.cheat-state.json |
User project root |
migrations/registry.md |
LATEST_SCHEMA + version chain table |
migrations/<from>-to-<to>.md |
Specific migration instructions for each step |
Workflow
Phase 0: Determine the migration chain
- Read
.cheat-state.json→ parsecurrent_version = state.schema_version - Read
migrations/registry.md→ parseLATEST_SCHEMAfield (line:LATEST_SCHEMA = "X.Y") - Parse the
args.tooverride (if any); otherwise target = LATEST_SCHEMA - Parse the
args.fromoverride (rare scenario: the user's state file schema field is broken, forcing the starting point to be specified) - Status determination:
current_version == target→ output "✅ state is already {target}, no migration needed" → exitcurrent_version > target(for example, the user ran a dev version and then switched back to release) → error "cannot downgrade, please adjust manually or re-init"current_version < target→ continue, look up the migration chain from the registry
- Compute
chain = [(from, to, file), ...]from the registry's "version chain" table, linking current → target in order
If a step is missing from the registry (for example, current_version is not in the table) → error and show "currently known versions: [1.0, 1.1, ...]" so the user can check.
Phase 1: dry-run
Output the migration plan:
📋 迁移计划
当前版本: 1.0
目标版本: 1.2
将按顺序跑 2 步:
[1/2] 1.0 → 1.1(MINOR)
新增字段:typical_duration_seconds, target_publish_cadence_days, ...(共 12 字段)
删除字段:mode, prediction_complexity, bucket_scheme
详见: migrations/1.0-to-1.1.md
[2/2] 1.1 → 1.2(MINOR)
新增字段:[...]
详见: migrations/1.1-to-1.2.md
⚠️ 备份位置: .cheat-state.json.backup-<timestamp>
继续吗?回 yes 执行 / no 退出 / dry-run-detail 看每步具体改什么。
If args["dry-run"] == false or the user replies yes → proceed to Phase 2.
Phase 2: Backup
cp .cheat-state.json .cheat-state.json.backup-$(date +%s)
Output: "📦 Backed up to .cheat-state.json.backup-1714838400"
Phase 3: Apply each step in order
For each (from, to, file) in the chain:
- Output "→ [N/M] applying {file}..."
- Read
migrations/<file>→ find the## HOW (Claude steps for /cheat-migrate)section - Execute the natural-language steps in the section one by one— this is the key: the migration is run by Claude reading markdown, not by a python script
- After each step completes:
- Update state.schema_version = to in memory
- Atomic writeto disk (write .tmp → rename)
- If a step fails:
- Output "❌ {file} step N failed: {error}"
- Do not advance, do not roll back (state has stopped at the intermediate version of the last successful step)
- Prompt: "Stopped at schema_version: {last_success_version}. After fixing, rerunning /cheat-migrate will continue from here"
- Exit
Phase 4: Verification
After upgrading:
- Read
.cheat-state.json→ parse → should succeed - Check
schema_version == target - Check that all "required fields" are non-missing (refer to shared-references/state-management.md complete schema)
- Failure → error "Migration completed but validation failed: {detail}. The state file may be inconsistent—check the backup for recovery"
Phase 5: Report
✅ 迁移完成
从: 1.0
到: 1.2
应用步骤: 2
state 文件现在含 X 字段,全部通过验证。
📦 备份保留:.cheat-state.json.backup-1714838400
(确认一切正常后可手动 rm;下次成功 /cheat-init 也会清理过期备份)
下一步建议:
- 跑 /cheat-status 确认看板正常
- 如有 hooks 重装需求,跑 bash <skill_repo>/install.sh --reinstall-hooks
Key Rules
- Idempotent: re-running on an already-upgraded state should exit immediately with "no migration needed",withoutre-applying steps. Achieved by comparing
current_version == targetimplementation - No version skipping: 1.0 → 1.3 must follow the order 1.0→1.1→1.2→1.3, with each step independently recoverable. A "combined migration that directly upgrades 1.0 → 1.3" is not allowed
- No silent compatibility: if the state file's schema_version is unrecognized → explicitly error "unknown version X, most recent known version Y", do not pretend it can continue
- Failure stops in place: when step N fails, schema_version stays at the last successfully completed version N-1, and does not roll back to before the migration. Re-running can continue from the breakpoint
- Backup is a hard constraint: a backup must exist before writing. Even if the user runs
--dry-run: false, the backup action is still performed - Do not touch predictions / rubric / videos: only modify
.cheat-state.json. Other user data is the responsibility of their respective skills; the migration skill does not touch them - MAJOR vs MINOR transparent: dry-run output must mark (MAJOR) / (MINOR). For MAJOR, additionally warn "old skills reading with old fields will have problems; after migration you cannot roll back to the old skill version"
Refusals
- "Skip dry-run, overwrite my state immediately" → allowed(
--dry-run: false), but the backup is still enforced - "My state is corrupted / the schema_version field is gone, can you guess a version to run" → allowed to specify
--from: 1.0, but warn "a guess-based migration may cause field misalignment" - "Downgrade to an old version (current > target)" → rejected. Schema evolution is one-way. To downgrade, manually cp a historical git snapshot
- "Merge multiple migration steps into one atomic" → rejected. Each step being independently recoverable is the core of the design
- "Call migrate midway through running cheat-bump / cheat-predict" → rejected. Wait for the other skill to finish before running, to avoid corrupting the in_progress_session state
Integration
- Upstream: SessionStart hook detects
state.schema_version != LATEST_SCHEMA→ outputs a red warning + suggests running/cheat-migrate - Upstream (manual): after the user does git pull to get a new version and sees BREAKING marked in the CHANGELOG → proactively run
- Downstream: after it finishes, all other skills reading state can get the latest fields
- With
cheat-init: when init writes a new state it directly uses LATEST_SCHEMA and does not need to go through migrate - With
install.sh --reinstall-hooks: migrationdoes notreinstall the hook script (the hook script belongs to the skill package code, not the user state). These two things are decoupled
State field read/write
This skill writes:
schema_version(updated after each step succeeds)
This skill reads:
- all existing fields (depending on the HOW steps of the specific migration file)
This skill neverwrites:
calibration_samples/pending_retros/shootsand other business state (these are the responsibility of other skills)- Exception: the migration file explicitly says "when deriving new field values, you need to scan predictions/ to compute baseline_plays"; that is initializing a new field, not modifying an old field
Examples
Example 1: A user upgrades from v0.1.0 to v0.2.0 (assuming 0.2 introduces schema 1.2)
用户:迁移
Claude: [跑 cheat-migrate]
Phase 0: current=1.1, target=1.2, chain=[(1.1, 1.2)]
Phase 1: dry-run 输出计划
用户: yes
Phase 2: 备份
Phase 3: 应用 1.1-to-1.2.md(MINOR:新增 platform_metrics_url 等字段)
Phase 4: 验证 OK
Phase 5: 报告 ✅
Example 2: The user skipped multiple versions
用户:我从 v0.1.0 升到 v0.5.0,state 还是 1.0
Claude: [跑 cheat-migrate]
Phase 0: current=1.0, target=1.4 (LATEST), chain=[(1.0, 1.1), (1.1, 1.2), (1.2, 1.3), (1.3, 1.4)]
Phase 1: dry-run 输出 4 步计划
...
Example 3: The migration fails midway
Phase 3:
→ [1/4] 应用 1.0-to-1.1.md ✓
→ [2/4] 应用 1.1-to-1.2.md ✓
→ [3/4] 应用 1.2-to-1.3.md... ❌ 失败:用户 baseline_plays 字段含非数字值,无法转 int
state 已停在 schema_version: 1.2。
修复 .cheat-state.json 后重跑 /cheat-migrate 会从 1.2 → 1.3 继续。