State Management(状态文件读写约定)

May 14, 2026 · View on GitHub

被所有子 skill 引用。.cheat-state.json 是各子 skill 共享上下文的单一来源——任何运行时状态、累计指标、模式标记都从这里读、写回这里。


文件位置

<user-content-project>/.cheat-state.json

绝不放到全局 ~/.claude/ 或 cheat-on-content 自己的目录——一个用户可能维护多个内容项目,每个项目独立状态。


完整 schema

{
  "schema_version": "1.4",
  "skill_version": "1.0.0",

  "rubric_version": "v0",
  "content_form": "opinion-video",
  "typical_duration_seconds": 240,
  "target_publish_cadence_days": 2,
  "rubric_form_mismatch": false,
  "benchmark_status": "none",
  "benchmark_name": null,
  "benchmark_sample_count": 0,
  "baseline_plays": null,

  "calibration_samples": 0,
  "calibration_samples_at_last_bump": 0,

  "data_collection": "manual",
  "pool_status": "none",
  "data_layer": "markdown",

  "hooks_installed": false,
  "enabled_trend_sources": ["manual-paste"],
  "enabled_perf_adapters": [],

  "last_bump_at": null,
  "last_bump_self_audited": false,
  "last_published_at": null,
  "last_published_file": null,
  "last_retro_at": null,
  "last_trends_run_at": null,
  "last_trends_added_count": 0,
  "last_prediction_self_scored": false,
  "last_self_scored_at": null,

  "consecutive_directional_errors": [],
  "pending_retros": [],
  "shoots": [],

  "in_progress_session": null,

  "initialized_at": "2026-05-04T15:00:00+08:00"
}

关键变更(v1.4)

相比 v1.3(MINOR but BREAKING for blind channel integrity——老用户必须跑 migrate):

  • rubric 文件拆分rubric_notes.mdrubric_notes.md(公式 + 通用维度定义;blind 白名单)+ rubric-memo.md(升级 Memo 含证据 + 派生证据;blind 硬禁读)
  • state 字段不变——仅 schema_version bump 标识老用户须跑迁移把现有 rubric_notes.md 拆成两份文件
  • 配合 skills/cheat-score-blind/SKILL.mdblocked_rubric_memo refusal_code + cheat-bump Phase 5 leak guard 自检
  • 不跑 migrate 的后果:blind sub-agent 仍会读到 rubric_notes.md 里的实绩,sub-agent 会自报 non_blind_warning 并降所有 confidence 到 medium——可用但不再是"真盲"
  • 详见 migrations/1.3-to-1.4.md

关键变更(v1.3)

相比 v1.2(MINOR,兼容):

  • 新增 last_prediction_self_scored: bool——true 仅当上一次 /cheat-predict 走了 --skip-blind flag 或 Phase 2.5 用户选 b(信主 Claude 自估)。cheat-status / SessionStart hook 据此 nag:"上次预测没走 blind sub-agent,已 N 天"
  • 新增 last_self_scored_at: ISO 8601 / null——last_prediction_self_scored 触发时的时间戳;走 sub-agent 时一起清回 null
  • 配合 skills/cheat-score-blind 的 channel B 隔离协议——把 contamination 跟踪从"靠 git history"升级为"靠 state 字段"
  • 老 state 缺这两字段 → 兜底 false / nullMINOR 兼容

关键变更(v1.2)

相比 v1.1(MINOR,兼容):

  • shoots[] 项 schema 扩展——新增 scripts_pathscript_consistencyscript_diff_pctv2_prediction_writtenscript_hash_at_shoot 字段。语义见 cheat-shoot Phase 4。这些字段记录"拍后改稿是否触发 v2 预测重判",cheat-retro 据此决定读 ## 预测 v1 还是 ## 预测 v2
  • 老 state 缺这些字段 → skills 用 state.get(field, default) 兜底(script_consistency 默认 "consistent"v2_prediction_written 默认 falsescript_diff_pct 默认 null)。不强制跑 migrate——但跑了让 state 字段对齐 schema 文档

关键变更(v1.1)

相比 v1.0:

  • 删除 mode("cold-start" / "calibration" 二元)→ 用 calibration_samples 整数判断状态
  • 删除 prediction_complexity("cold-start-simple" / "complete" 二元)→ 所有预测都用统一完整 7 组件结构,confidence 等级派生自 calibration_samples
  • 删除 bucket_scheme("ratio" / "absolute" / "absolute_with_ratio" / "percentile" 四档)→ bucket 边界由单一算法自动派生:有 baseline_plays → 按倍数;无 → 平台通用默认;样本 ≥10 → 重算 baseline

理由:硬模式切换是设计者的猜测,不是用户体验该有的样子。统一流程 + 渐进信心标注更符合"频道是不断进化的连续光谱,不是离散阶段"的事实。


字段说明(每个字段的语义 + 谁写谁读)

元数据

字段类型写入者读取者说明
schema_versionstringcheat-init / cheat-migrate所有 skill"1.1"。schema 升级时 bump;老用户由 /cheat-migrate 升级。详见 migration-protocol.md
skill_versionstringcheat-init所有 skillcheat-on-content 当前版本
initialized_atISO 8601cheat-initcheat-status首次初始化时间,永不变

模式与配置

字段类型取值写入者读取者
rubric_versionstring"v0" / "v1" / "v2" / ...cheat-init / cheat-bumpcheat-score / cheat-predict / cheat-retro
content_formenum"opinion-video" / "long-essay" / "short-text" / "podcast" / "other" / "mixed"cheat-initcheat-predict / cheat-recommend
typical_duration_secondsint用户视频典型时长。决定 cheat-seed 写 draft 的字数 + cheat-predict 锚点优先同时长cheat-initcheat-seed / cheat-predict
target_publish_cadence_daysint / null用户目标发布频率(1=日更 / 2=隔日 / 7=周更 / null=灵活)。决定 buffer 警戒颜色阈值cheat-initcheat-status / cheat-recommend / cheat-shoot / cheat-publish / SessionStart hook
rubric_form_mismatchbooltrue 表示 content_form ≠ opinion-video 但仍用 opinion 内置 rubric 起步——提示用户 bump 时调权重cheat-initcheat-status(持续提示)
benchmark_statusenum"none" / "imported" / "pending"(用户答应等下找)cheat-init / cheat-learn-fromcheat-seed(brainstorm 时读 benchmark.md)/ cheat-status(pending 时持续提醒)
benchmark_namestring / null对标账号名(如"蜗牛学长留学");none 时为 nullcheat-learn-fromcheat-status / cheat-seed
benchmark_sample_countint已导入的对标视频条数cheat-learn-from(写入 / append)cheat-status(N≥10 时提示 benchmark 影响淡出)
baseline_playsint / null用户基准播放数;首次 init 时若有抓取历史→中位数;无→null;后续 cheat-retro 第 1 篇有实绩时回填cheat-init / cheat-retro / cheat-bump (--bucket-only)cheat-predict(派生 bucket 边界)
data_collectionenum"manual" / "adapter"cheat-initcheat-retro(决定 DATA_SOURCE 默认值)
pool_statusenum"none" / "markdown" / "notion" / "sqlite"cheat-init / cheat-recommendcheat-recommend / cheat-status
data_layerenum"markdown" / "sqlite"cheat-init / md-to-sqlite.py所有读 predictions 的 skill
hooks_installedbooltrue / falsecheat-initcheat-status(持续提示)
enabled_trend_sourcesarray of stringtrend-source adapter 名列表(如 ["weibo-hot", "zhihu-hot"]cheat-init / 用户手动cheat-trends
enabled_perf_adaptersarray of stringperf-data adapter 名列表(如 ["douyin-session"])。空 → cheat-retro 走 manual pastecheat-init / 用户手动配置后cheat-retro

累计计数

字段类型写入者用途
calibration_samplesintcheat-retro(每次复盘 +1)cheat-status 进度条 / cheat-bump 门槛
calibration_samples_at_last_bumpintcheat-bump"距上次 bump 多少新样本"

时间戳(last_X_at)

字段类型写入者
last_bump_atISO 8601 / nullcheat-bump
last_bump_self_auditedboolcheat-bump(CROSS_MODEL_AUDIT=false 时 true)
last_published_atISO 8601 / nullcheat-publish
last_published_filestring / nullcheat-publish
last_retro_atISO 8601 / nullcheat-retro
last_trends_run_atISO 8601 / nullcheat-trends
last_trends_added_countintcheat-trends
last_prediction_self_scoredboolcheat-predict(--skip-blind 或 Phase 2.5 选 b 时 true;下次走 sub-agent 时清回 false)
last_self_scored_atISO 8601 / nullcheat-predict(跟随 last_prediction_self_scored 同步)

列表队列

字段类型写入者读取者协议
consecutive_directional_errorsarray of "high"/"low"cheat-retro(push) / cheat-bump(清空)cheat-status / cheat-retro 自检最近 N 次复盘的偏差方向;连续 3 同向触发 bump 提议
pending_retrosarray of file pathcheat-publish(push) / cheat-retro(remove)cheat-status等待复盘的预测文件路径
shootsarray of {video_folder, prediction_file, shot_at, ad_hoc}cheat-shoot(push) / cheat-publish(remove)cheat-status / cheat-recommend / SessionStart hook已拍未发队列。len(shoots) = buffer countbuffer_days = buffer × target_publish_cadence_days 决定颜色

会话状态

字段类型写入者读取者协议
in_progress_sessionobject / nullcheat-predict(创建) / cheat-publish(清除)cheat-publish / cheat-status见下方"in_progress_session 子结构"

in_progress_session 子结构

{
  "type": "prediction",
  "file": "predictions/2026-05-04_a3f2c1d4e5b6_停止期待.md",
  "started_at": "2026-05-04T14:00:00+08:00",
  "rubric_version": "v2"
}

type:当前只有 "prediction"。未来可能加 "bump" 表示长流程 bump 在进行中。


读写协议

读(任何 skill)

# 伪代码
import json, os

state_path = os.path.join(os.getcwd(), ".cheat-state.json")
if not os.path.exists(state_path):
    # 不存在 = 用户没初始化,路由到 /cheat-init
    raise NeedsInitError()

with open(state_path) as f:
    state = json.load(f)

# 检查 schema_version 兼容
LATEST_SCHEMA = "1.1"  # see migrations/registry.md
if state.get("schema_version") != LATEST_SCHEMA:
    # 不直接 raise — 提示用户跑 /cheat-migrate(非阻塞)
    log_warning(f"schema 版本不匹配:state={state.get('schema_version')}, 期望={LATEST_SCHEMA}。建议跑 /cheat-migrate")
    # MINOR mismatch 通常仍能继续;MAJOR 时部分字段读取可能 KeyError → 用 .get(field, default) 兜底

关键纪律

  • 读完不立刻关心字段缺失——用 state.get(field, default) 容错。新版 skill 引入新字段时旧 state file 会缺该字段,应优雅默认而非崩溃
  • 绝不在内存里 mutate state 后忘记写回——下游 skill 读到的是磁盘版

写(任何 skill)

# 伪代码 — read-modify-write 模式
state = read_state()
state["calibration_samples"] += 1
state["last_retro_at"] = now_iso()
write_state(state)

def write_state(state):
    state_path = os.path.join(os.getcwd(), ".cheat-state.json")
    tmp_path = state_path + ".tmp"
    with open(tmp_path, "w") as f:
        json.dump(state, f, indent=2, ensure_ascii=False)
    os.replace(tmp_path, state_path)  # atomic rename

关键纪律

  • 原子写:写到 .tmp → rename。避免半写损坏的 state file
  • 永远 indent=2:人类可读,便于用户手改 + git diff
  • ensure_ascii=False:保留中文字符不转 \uXXXX
  • 写完再继续后续操作:避免下游 skill 读到旧值

并发模型

预期场景:单用户 + 单 Claude Code 会话。不做锁。

如果两个会话并行操作同一个项目(罕见且不推荐):可能出现写覆盖。未来需要时可加文件锁(fcntl.flock);当前不加,避免引入复杂度。


字段写入责任表(防止"谁该写这个字段"歧义)

字段唯一写入者何时写
rubric_versioncheat-init / cheat-bumpinit 写初值;bump 升版
baseline_playscheat-init / cheat-retro / cheat-bump (--bucket-only)init 时若有 adapter 抓回历史→中位数;无历史→null;retro 第 1 篇有实绩→回填;bump --bucket-only→重新计算
calibration_samplescheat-retro每次复盘成功落盘 +1
pending_retroscheat-publish(push)/ cheat-retro(remove)publish 时 push 本次;retro 完成时 remove
consecutive_directional_errorscheat-retro(push)/ cheat-bump(清空)retro 判定偏差方向时 push;bump 落地时清空
in_progress_sessioncheat-predict(创建)/ cheat-publish(清除)predict 写完文件时创建;publish 登记时清除
last_bump_atcheat-bumpbump 落地时

绝不允许多个 skill 写同一字段——会导致状态语义破碎。如果未来需要新字段,先想好"谁是唯一写者"。


state file 损坏 / 不一致的处理

症状处理
文件不存在提示"未初始化,请跑 /cheat-init",自动创建
JSON 解析失败提示"state file 损坏:path/to/.cheat-state.json",建议手动修复或备份 + 重新 init
schema_version 不识别提示版本号 + 建议跑 /cheat-migrate。SessionStart hook 会自动检测并提示
pending_retros 含已删除的文件cheat-status 检测时安静移除,不报错
in_progress_session 文件已不存在cheat-status 检测到 → 询问用户是否清理
calibration_samplespredictions/ 实际复盘数不一致cheat-status 报告差异。临时手改 state 即可;持续不一致是 bug,应在下个 minor 版本里加入 cheat-migrate 的 reconciliation step

与 git 的关系

.cheat-state.json 应该被纳入 git:

  • ✅ 它是项目配置 + 累计指标的快照
  • ✅ git history 提供状态演化的完整轨迹
  • ✅ 多设备同步靠 git push/pull
  • 含敏感信息(cookie / API key 应放 .env.cheat-secrets.json,单独 gitignore)

.cheat-cache/ 目录不应该被纳入 git:

  • usage.jsonl(meta-logging 钩子的本地日志)
  • trends-history.jsonl(trend 抓取的去重缓存)
  • 也可能含 adapter 调试文件(如 douyin-session-debug/
  • 这些是设备本地状态,跨设备同步无意义

/cheat-init 应自动在用户项目根追加(不覆盖).gitignore

.cheat-cache/
.cheat-secrets.json

升级路径

完整哲学和 maintainer checklist 详见 migration-protocol.md。简版:

未来 schema 变化时:

  1. bump schema_version(如 "1.1" → "1.2")
  2. migrations/<old>-to-<new>.md(4 段:WHAT/WHY/HOW/Manual fallback)
  3. migrations/registry.mdLATEST_SCHEMA 标记位 + 版本链表
  4. SessionStart hook 检测到不一致时自动提示用户跑 /cheat-migrate
  5. 绝不让 skill 静默兼容旧版 schema 的删字段或重命名——那会让"哪个版本下哪个字段是什么含义"成谜

新增字段(MINOR,不破坏兼容):

  • state.get(field, default)
  • 老 state file 自动获得 default
  • 仍需 bump schema_version + 写 migrations 文件——保证状态文件最终一致;但用户可以延迟跑 migrate

删除 / 重命名 / 改语义(MAJOR,破坏兼容):

  • 必须 bump schema_version + 写迁移文件
  • CHANGELOG 标 BREAKING

用户手改 state file 的边界

允许手改的字段:

  • enabled_trend_sources(数组,决定 cheat-trends 用哪些源)
  • data_collection(切换 manual ↔ adapter)

建议手改的字段(会破坏不变量):

  • calibration_samples / pending_retros / consecutive_directional_errors(应通过 retro 流程更新)
  • rubric_version(应通过 bump 流程更新)
  • in_progress_session(应通过 predict/publish 流程更新)

如用户确实想重置:建议删除整个 .cheat-state.json + 重跑 /cheat-init——这比手改单字段安全。


Confidence label 派生表(单一真值

被 cheat-predict / cheat-status / cheat-recommend / SessionStart hook 等共同使用。从 calibration_samples 派生,所有 skill 用同一逻辑:

calibration_samplesconfidence emoji + 标签数值含义用户该如何用
0🔴 极低"占星级别,纯纪律训练"不要基于 composite 决定要不要发;写 prediction 是为了采集数据,不是为了做决策
1-2🟠 低"中枢 ±50%,方向感优于绝对数字"信"A 比 B 流量好"的方向,不信具体数字
3-5🟡 偏低"中枢 ±40%,可作为参考之一"bucket 排序可用,中枢点估计仍是猜测
6-10🟢 中"中枢 ±25%,可参与决策"可作为"要不要发"的依据之一
11-20🟢 较高"中枢 ±15%,rubric 形态稳定"可信中枢估计
21+🔵 高"中枢 ±10%,可数据驱动 bump"进入数据驱动阶段 — bump 用回归而非直觉

上表的 ±X% 是经验值(基于参考博主的真实校准曲线),不是数学严格保证。新人账号的真实 ±X% 要等自己跑出 score-curve.png 才能验证。

不要用这个表来 gating 任何功能——所有 skill 在所有 calibration_samples 下都跑相同流程,只是输出里显示当前 confidence 等级。这是新设计的核心原则。