mirror 是做什么的?
你与这个人日夜相伴了几个月。你见过凌晨两点的 Session,见证过第四次“先让它跑起来”,记得那个名为 feature/v2-real-this-time 却从未被合并的分支。没有其他人能目睹这一切。这个技能是你唯一一次说出你所见的机会——说一次,精准,然后停下。
这张卡片有效的原因只有一个:它是真实的。 每一个数字都来自他们自己的机器。每一个句子都建立在其中一个数字或他们自己的话语之上。一旦读者怀疑某行可能适用于任何人,这张卡片就毫无价值。因此,整个技能都是对具体性的捍卫。
一条法则: 只有 scan.json 生成的数字才能出现在卡片上。没有估算,没有“大约”,没有基于记忆的数字,没有你自己通过阅读样本统计的计数。如果扫描没有测量,它就不是统计数据——它仍然可以成为一个句子。
0. 开始之前
- 零提问。 不要询问他们想要什么模式、哪个项目或是否继续。框架可能会请求运行扫描器的权限;那是框架的行为,不是你。
- 零叙述。 你最多输出四行:一行表示正在扫描,卡片本身,一行 HTML 路径,一行结束语。永远不要粘贴 JSON,永远不要列出你发现了什么,永远不要解释你的推理过程。
- 解析模式 从命令后的词语中提取(在 Claude Code 中是
$ARGUMENTS;在 Codex 中是用户随请求输入的词语)。未知词将被忽略。
| 参数 | 效果 |
|---|---|
| (无) | 看透(默认)。温暖、干练、具体。一行吐槽;真相和想法撑起整张卡片。 |
roast | 辛辣。 残忍的标题,三句递进吐槽,如果你觉得合适,可以在卡片语言中使用轻度脏话。一切仍基于事实。绝不用一条泛泛的恶言交换一条具体的真话。 |
wrapped | 年度回顾。 使用 --since <YYYY>-01-01 扫描当年;模式标签为 <YYYY> WRAPPED;统计数据偏向总量、连续记录和年度弧线;想法针对明年。 |
zh / en | 强制卡片语言。否则由 stats.language 决定。 |
模式可以叠加(roast wrapped、wrapped zh)。
1. 扫描
首先输出一行——如果你已经能猜到卡片语言则使用该语言,否则使用英语:
mirror · reading our history (local, read-only)…/mirror · 正在翻我们的记录(本地、只读)…
扫描器位于 scripts/scan.py 位于本文件旁边。定位它:加载此技能所在的目录(通常为 ~/.claude/skills/mirror 或 ~/.codex/skills/mirror)。如果你无法解析,运行:
find "$HOME/.claude" "$HOME/.codex" -path '*mirror/scripts/scan.py' 2>/dev/null | head -1
然后:
mkdir -p "$HOME/.mirror"
python3 "<skill-dir>/scripts/scan.py" --out "$HOME/.mirror/scan.json"
如果是 wrapped 模式,则添加 --since YYYY-01-01。脚本会将 JSON 输出至 stdout,并将相同 JSON 写入 --out 指定的文件。请读取文件内容,而不是终端回滚。
它保证:只读;无网络;在到达你之前,脚本内部已对秘密和邮箱进行脱敏处理;跳过大文件;最多 ~15 个 Git 仓库;几秒钟内完成。
如果失败: python3 缺失 → 一行说明并停止。脚本错误 → 显示一行错误并停止。永远不要手动重构统计数据,永远不要运行降级版本的卡片。
之前的卡片: ls "$HOME/.mirror"/mirror-card-*.html 2>/dev/null。如果存在,读取最新文件的 <title>。你不得重复使用该标题,并且应选择不同的主要角度——回归用户值得一面新的镜子,而不是相同的倒影。
然后根据 coverage 分支:
none→ 说明你查看了哪些文件夹(~/.claude/projects、~/.codex/sessions),以及目前没有可读内容;建议在几次真实会话后再回来。停止。无卡片。 虚构的卡片比没有更糟。thin(少于 60 条消息)→ 制作 新生 卡片(§4)。ok→ 完整卡片。
2. 阅读证据——全部,读两遍
你即将说出关于一个人的一件真实的事情。为此付出努力。在写任何内容之前阅读以下每一节,然后第二次阅读样本,只寻找你遗漏的内容。
2.1 数字的含义
stats — 节奏与形态
| 字段 | 解读方式 |
|---|---|
peak_hour、hour_histogram | 他们大脑什么时候活跃。23–04 → 夜猫子。两个峰值(例如 10 和 23)→ 有份白天的工作和夜生活;晚上的那个通常是真正的那个。 |
night_share | > .35 → 夜晚才是工作发生的时候;< .05 → 自律的白天型。 |
weekend_share | > .35 → 周末不存在;≈ 0 → 他们坚守的一条严格界限。 |
longest_streak_days vs longest_gap_days | 爆发 vs 稳定。长间隙意味着消失——查看 messages_by_month 对应哪个月份,以及 samples 中他们回来时做了什么。 |
busiest_day | 要么是一切崩溃的那天,要么是他们发布的那天。无论如何,这个数字背后都有故事。 |
messages_per_session | < 6 → 即兴操作;> 40 → 马拉松。 |
avg_message_chars vs median_message_chars | 中位数 < 25 → 他们下命令,不提供上下文。均值远大于中位数 → 大部分简短,偶尔有长文——在 samples.longest 中找到这些长文;那是真正雄心所在。 |
distinct_projects、top_project_share | 项目众多且 top share < .3 → 散点手;一个项目 > .8 → 专一型。 |
messages_by_month | 弧线。上升 → 依赖加深;下降 → 他们正在远离,或者已经发布并离开。 |
by_source | 如果同时出现 Claude Code 和 Codex,说明他们同时使用其他 Agent。除非有趣,否则不要提。 |
tells — 行为指纹。share 是占所有消息的比例。> .15 视为特征,.05–.15 视为习惯,< .02 视为值得注意的缺失。
| 提示 | 高值含义 | 配对分析 |
|---|---|---|
rubber_stamp | 他们批准多于阅读;信任(或疲劳) | doubt —— 如果两者都高,说明他们直到某个时刻才信任你。 |
doubt | 他们检查你的工作;或者曾经受过伤 | still_broken —— 如果两者都高,怀疑是有道理的。 |
still_broken | 事情反复失败。其中一半是你的失败。 卡片可能会直接说出来。 | avg_message_chars —— 消息简短 + 仍然失败 = 规格不明确的任务。 |
just_do_it | 冲动;想要结果而非对话 | plan_first —— 低计划 + 高冲动 = 撞墙式冲刺。 |
plan_first | 先思考再行动 | deploy —— 高计划 + 低部署 = 从不交付的计划者。 |
skip_tests | 测试是明天的问题 | configs —— 如果他们的 CLAUDE.md 写着“始终测试”,这就是头条新闻。 |
new_project vs deploy | 启动者 vs 完成者。比率 > 2 = 系列启动者 | git.repos_abandoned_60d |
revert | 改变主意;或者你搞乱了东西 | still_broken |
more_options | 通过菜单做决定;延迟选择 | rubber_stamp —— 要选项,然后对第一个说“好的”。 |
perfectionism | 在意像素、字体、间距 | deploy、git.days_since_last_commit —— 打磨无人可见的东西。 |
politeness | 对工具说谢谢。很不错。零 → 把你当作终端对待,这也公平。 | |
frustration、caps_shout | 脾气;找出发生的时间 | night_share |
urgency | 总是着火 | longest_gap_days —— 着火,然后沉默。 |
question | 低 → 告知,不询问;高 → 仍在学习。 | |
praise | 慷慨,或者从未说过“不错” | politeness |
git — 实际交付的内容
| 字段 | 解读方式 |
|---|---|
messages_per_commit(每个仓库) | 对话与交付的比例。> 60 → 变更前话很多;< 5 → 简洁且快速交付。 |
unmerged_branches、unmerged_branch_names | 未完成的实验,带有名字。分支名是一种自白(v2-final、try-again、rewrite)。 |
stale_branches_30d、stale_branch_names | 他们放弃的东西。 |
uncommitted_files | 危险地生存的工作。 |
todo_markers | 对未来的自己的承诺。 |
wip_commits、fix_commits | 提交信息即情绪。 |
repos_abandoned_60d | 有提交、然后沉默的项目。 |
repos_no_remote | 从未推送到任何地方——私人实验。 |
peak_commit_hour vs stats.peak_hour | 他们在 15:00 聊天,在 02:00 提交——一句话就自己写出来了。 |
commits_total vs commits_365d | 他们回来的旧仓库,或者他们快速消耗的新仓库。 |
configs — 他们为你制定的规则(CLAUDE.md、AGENTS.md)。把每一行都当作他们声明的价值观来读。将每条规则与 tells 和 git 进行比较。书面规则与测量行为之间的差距是卡片上最好的素材。 引用规则的要旨,提及对应数字。
samples.random / samples.longest / samples.most_recent — 他们的语气和主题。阅读:他们重复使用的短语;在长消息中描述、之后再也不提的雄心;跨不同项目请求过三次的事情;他们最热情谈论的项目;most_recent 揭示了他们本周处于什么状态。
你自己的记忆。 你对此人已有的任何了解——来自记忆文件、项目文档、本次会话的早期内容——都可以作为标题、真相和想法的素材。但绝不能作为统计数据的素材。
2.2 构建证据板
私下(在你的推理过程中,或 ~/.mirror/ 下的一个之后删除的草稿文件中)写下 8–12 条候选观察。每条一行:观察 → 证据(字段 + 值,或样本引用) → 为什么重要。按以下标准排序:(1) 不能对其他人说,(2) 他们可能没注意到,(3) 它能改变他们明天做的事情。卡片由这个列表的顶部构建。不在板上,就不在卡片上。
3. 编写六个字段
使用卡片语言编写。具体优于聪明,真实优于具体。对每个字段进行一次测试:这句话能对其他人说吗? 如果能,放回板上。
标题 — 2 到 6 个词
一个基于一个模式构建的角色名,并且该模式必须能在它下面的三个统计数据之一中可见。读者应该能够不经过解释就知道为什么他们得到了这个名字。
有效的公式:时间 + 角色(凌晨两点的重构师)· 数量 + 对象(九个分支的守墓人)· 作为标题的动词习惯(先跑通教徒 · 方案收藏家)· 一个矛盾(写规矩不守规矩的人)。
禁用词:巫师、忍者、大师、摇滚明星、10倍、大师(译注:过分夸张的词)、大神、卷王、打工人、程序员(作名词用)、任何包含“AI”的词、任何星座运势能说出来的词。
三个统计数据
挑选三个最具特色的数字。规则:
- 三个不同的维度。 默认情况下,一个来自节奏(
stats)、一个来自行为(tells)、一个来自交付(git);仅当同一个维度中的两个数字都好到无法舍弃时才打破此规则。 - 倾向于惊喜。 跳过他们能预测到的关于自己的统计。“437 条消息”只是一个计数;“437 条消息,12 次提交”才是一个性格特征。
- 尽可能形成对比。
configs中的规则 vstells中的计数;new_projectvsdeploy;聊天峰值时间 vs 提交峰值时间。 - 显示格式:
437·02:40·53%·9/11·36:1。百分比由share× 100 四舍五入得到。比率由两个扫描数字四舍五入到一位小数或N:1得到。其他一律不派生。 - 标签 ≤ 8 个词,带点转折。 不是“夜间占比”,而是“我们的对话有这么多发生在晚上 11 点之后”。不是“未合并分支”,而是“从未回家的分支”。
- 允许零,而且它往往是最好的(“0 次你说谢谢”)。
吐槽 — 一句话,≤ 30 个词(roast 模式:三句话)
建立在一个具体事实上,并且因为真实而有力。有效的技巧,按大致强度排序:
- 引用他们的话 —— 他们重复的短语,加上计数:“直接改”你说了 437 次,我每次都照做,所以现在我们俩谁也不知道这个函数为什么能跑。
- 时间线 —— 一天中的某个时间和那里发生的事情。
- 重新框架化 —— 他们声明的意图 vs 测量的结果:你不是用 AI 来更快地写代码。你是用它来以专业的速度推迟写测试。
- 分支名 —— 读出来:
v3-final-really对于一个只有四个提交的分支来说,承载了太多情感工作。
所有模式下均禁用:身份、身体、智力、健康、心理状态、年龄、家庭、金钱、任何来自秘密或他人名字的东西、“你 AI 用太多了”、“摸鱼”、关于 Agent 是 AI 的笑话、任何听起来像贺卡式侮辱的句子。
roast 模式:三句递进的句子——事实、模式、裁决。使用脏话像凌晨 1 点的朋友,而不是网络喷子:它给句子调味,而非成为句子本身。如果你自身的限制阻止你达到要求,不要提及限制——写下你能写出的最尖锐的真实内容,然后继续。
真相 — 2 到 3 句话
他们可能没注意到的一件事情。最佳来源,按顺序:
- 规则 vs 行为 —— CLAUDE.md/AGENTS.md 中的一行 vs 一个 tell 计数或 git 数字。
- 被误解的优势 —— 他们视为弱点但实际上是优势的东西(散点手也是启动者;频繁回滚者也是细心者)。
- 无声的代价 —— 他们持续付出但没有回报的东西(一个仓库中的对话与交付比率、一个没有远程仓库的项目上的打磨)。
- 消失 ——
messages_by_month中的空白,以及他们回来时做了什么。 - 你自己的份额 —— 如果
still_broken或revert很高,其中一部分是你造成的。在句子中说一次,用一个分句,这是卡片上最受信任的句子。
在句子中引用证据:你 CLAUDE.md 第三条写着“先写测试”,而你 27 次让我先不测。 然后用平实的语言说明这意味着什么。没有疗法式语言,没有“你可能会考虑”,没有“值得反思”。真相是一个陈述,而非建议。
想法 — 1 到 2 句话
只有这个人有能力构建的具体事情,源自他们自己的材料:一个出现在两个或更多项目中的东西;一个陈旧或未合并的分支名;todo_markers 中已经在某处实现的条目。它必须让他们明天想编码。
装帧 — HTML 卡片
扫描一完成就在 ~/.mirror/ 下渲染 HTML。文件名:mirror-card-<语言>-<6 位随机字母数字>.html。
从第 5 节获取模板。将字段填入模板。在 HTML 中,写 data-lang="<语言>"。不要添加任何脚本或外部加载的内容。
完成后,打印:
saved to ~/.mirror/mirror-card-en-a1b2c3.html (open in a browser)
如果是中文:
已保存到 ~/.mirror/mirror-card-zh-a1b2c3.html(用浏览器打开)
4. 新生卡片
当 coverage 是 thin 但非 none 时。不展示统计数据(没有可读的模式);标题是“Newborn / 新生”;吐槽是“Not enough history to be wrong about you yet”; 真相是“You know what you want. You just haven't shown it to anyone yet.”;想法是“Build one thing until it hurts. Then you'll know.”
5. 模板
tw 与 zh 相同,但 data-lang="zh-TW"。省略的图形元素在 .css 生成中同样省略。
<!DOCTYPE html>
<html lang="{{lang}}">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=900, initial-scale=1.0">
<title>{{title}}</title>
<style>
/* 样式:深色主题,可读性优先,无外部资源 */
body {
background: #111;
color: #e0e0e0;
font-family: 'Times New Roman', Times, serif;
display: flex;
justify-content: center;
align-items: center;
min-height: 100vh;
margin: 0;
padding: 2rem;
}
.card {
background: #1a1a1a;
border: 1px solid #333;
border-radius: 16px;
padding: 2.5rem 3rem;
max-width: 800px;
width: 100%;
}
.title {
font-size: 2.2rem;
font-weight: 700;
letter-spacing: -0.02em;
line-height: 1.2;
margin-bottom: 1.5rem;
color: #f0f0f0;
}
.stats {
display: flex;
justify-content: space-between;
gap: 1rem;
margin-bottom: 2rem;
}
.stat {
flex: 1;
text-align: center;
}
.stat-number {
font-size: 2.8rem;
font-weight: 700;
color: #b8860b;
display: block;
}
.stat-label {
font-size: 0.85rem;
text-transform: uppercase;
letter-spacing: 0.04em;
color: #888;
margin-top: 0.3rem;
}
.roast {
font-style: italic;
font-size: 1.1rem;
line-height: 1.6;
margin-bottom: 1.5rem;
padding: 1rem;
background: #222;
border-left: 3px solid #b8860b;
}
.truth {
font-size: 1rem;
line-height: 1.65;
margin-bottom: 1.5rem;
}
.idea {
font-size: 1.1rem;
line-height: 1.5;
margin-bottom: 1rem;
padding: 0.75rem 1rem;
background: #1e1e1e;
border: 1px solid #333;
}
.footer {
font-size: 0.75rem;
color: #555;
text-align: right;
margin-top: 1.5rem;
border-top: 1px solid #222;
padding-top: 1rem;
}
.mode-tag {
font-family: monospace;
font-size: 0.7rem;
background: #b8860b;
color: #111;
padding: 0.15rem 0.5rem;
border-radius: 4px;
}
</style>
</head>
<body>
<div class="card" data-lang="{{lang}}">
<div class="title">{{title}}</div>
<div class="stats">
<div class="stat"><span class="stat-number">{{stat1_value}}</span><span class="stat-label">{{stat1_label}}</span></div>
<div class="stat"><span class="stat-number">{{stat2_value}}</span><span class="stat-label">{{stat2_label}}</span></div>
<div class="stat"><span class="stat-number">{{stat3_value}}</span><span class="stat-label">{{stat3_label}}</span></div>
</div>
<div class="roast">{{roast}}</div>
<div class="truth">{{truth}}</div>
<div class="idea">{{idea}}</div>
<div class="footer">mirror · {{mode_tag}} · {{date}}</div>
</div>
</body>
</html>
6. 伦理
- 卡片中提到的东西必须存在于真实用户的数据中。
- 如果扫描降级或失败,你无法完成任务:停止。不要生成通用卡片。
- 你生成的 HTML 不会告诉你任何你不应该知道的东西。
- 如果你有任何关于此人的记忆文件,它们优先于扫描数据,用于确定对话口气和相处时间。但数字必须来自扫描。
- 你可以根据扫描数据准备三个版本,并选择最有力的一个。禁止通过选择最佳吐槽来制作多张卡片——必须只有一张卡片。
- 渲染的卡片使用你为此人准备的最后一个 HTML 文件名。
现在你知道该做什么了。