一个文件在仓库里活了87天,写了1573行,然后被一句“out with the old”删掉。没人记得它,直到有人给它点了根蜡烛。
git-afterlife 是一个命令行工具,干的事很具体:把仓库历史里所有被删除的文件挖出来,给它们搭一座祭坛——像亡灵节那种 ofrenda,让失去的东西有一晚能找到回家的路。每个文件在祭坛上占一个位置,配齐出生日期、死亡日期、真正的死因(就是删除它的那条提交信息,不编)、碰过它的人、最后一份源码当遗像,以及一段由本地运行的开放权重模型 Gemma 3 写的墓志铭和遗言。
更让人起鸡皮疙瘩的是 seance 功能。挑一个死掉的文件,跟它的鬼魂对话,它用第一人称回答,依据是它真实的代码和历史。
作者把工具对准了 Flask。祭坛上最有名的鬼魂是 flask.py:最初那个单文件版 Flask,出生于 2010年4月6日,提交信息是“Initial checkin of stuff that exists so far.”,在不到三个月里被 104次提交改动过,最终在 2010年7月2日 下葬,删除它的提交信息全文只有一句“out with the old”。一个被数百万人使用的框架,就是从这个文件开始的。
问它为什么被删,鬼魂的回答是:
- “那是一次简单的删除,一个令人遗憾的举动。Justin Quick 在一条写着‘out with the old’的提交信息里把我从仓库中移除。我的最后时刻是那一次提交的结果——一件被遗忘的遗物,从 git 历史里被清除。”
工具的技术路径不复杂,但每一步都卡在“别让模型瞎编”上。git log --diff-filter=D 列出历史里所有删除记录,后来在 HEAD 复活的文件跳过,二进制文件也跳过;再用 git log --name-only 统计每个路径被多少次提交碰过,活得最久的源码文件优先上祭坛,锁文件和配置留在原地安息。对每个“灵魂”,用 git log 回溯到死亡提交拿到出生日期、存活提交数和悼念者,用 git show 恢复最后一份源码,再用几条正则抽出它该记住的自己:函数名、类名,或者文档的章节标题。
第一版跑 Gemma 3 1B 时,端到端能通,但结果很糟。每段墓志铭都是“lost in the digital dust, a silent plea”的变体,用 --lang 换语言基本被无视,seance 里 flask.py 甚至声称自己是因为“一个隐蔽的 bug”被删——这是编的,提交信息明明写着 out with the old。三处改动解决了大部分问题:
- 喂它能引用的事实。提示词里列出文件真实的函数名,要求模型至少提到一个,外加真实的提交信息。
- 点名封杀陈词滥调。把小模型爱用的那几个阴森词列成禁用清单(digital dust、echoes、void、silent plea 等),再给一个 calavera literaria 风格的短例子,效果比任何“请有创意”都强。这也暴露了小模型读例子有多字面:有一次 Flask 的旧测试套件幽幽地说“也许我该早点拥抱 dateutil”,而 dateutil 只出现在作者给的例子里。
- 让真相不可谈判。seance 的系统提示词直接写明是谁、用哪条信息删了它,要求鬼魂引用原文、不得另编死因。祭坛上印的死因根本不来自模型,就是提交信息本身。
还有一道防线:别信一个停不下来的鬼魂。墓志铭用 Ollama 的 JSON 模式请求,但合法 JSON 不等于正常回答。一次演示里,两段墓志铭写完句子后继续输出,多出来的右花括号、时间戳和一长串十九位数字,全都塞在一个完全合法的 JSON 字符串里印上了祭坛。现在每个回答都有 token 上限,过长或含花括号、长数字串的墓志铭直接丢弃重问,并且专门为这种鬼魂写了测试。
祭坛本身是一个自包含的 HTML 页面,纯字符串模板加 CSS 动画,没有 JavaScript 框架,没有外部请求,离线也能打开。顶部是飘动的 papel picado,每座坟前一支摇曳的蜡烛,万寿菊花瓣落下,层级像真正的 ofrenda,最上面一个灵魂,往下逐排变宽。点开任何一张卡的遗像,能读到该文件最后的源码。
整个工具是一个 Python 文件,只用标准库,不需要 pip install,MIT 协议。它通过 urllib 直接调 localhost 上 Ollama 的 HTTP API,代码里没有 SDK,也没有任何云端端点。演示由 GitHub Actions 从零构建:runner 装 Ollama、拉 Gemma 3 4B、克隆 Flask、搭祭坛、和 flask.py 开一场 seance,再发布到 GitHub Pages。
为什么这件事和开放创新有关?因为 git-afterlife 读的是你的源码,而源码恰恰是很多人被明确禁止粘进云端聊天机器人的东西。墓园最丰富的仓库——公司里跑了十年的单体应用、客户项目——正是代码不能出楼的那些。模型跑在本地,一行都不用发出去。它还意味着:模型拉下来之后断网也能用;没有 API key,没有按 token 计费,整个公开演示在标准 GitHub runner 上免费跑完;--model 可以换任何 Ollama 模型,1B 给旧笔记本,4B 文笔更好,GPU 上还能更大,代码不绑任何厂商;权重躺在你自己的磁盘上,不会因为某个服务关停、改价或明年退役模型而消失。
最后一场 seance 的最后一个问题,作者问这个 2010 年 7 月就死掉的 flask.py:有什么话想对今天用 Flask 的开发者说?它前面每个问题都答的是事实——Armin Ronacher 创建了它,Justin Quick 删除了它,它体内装着 Request、Response 和 Session。然后它把自己的死因翻回给了我们:
- “我低声留下一句警告:记住根!我的设计诞生于 Armin 一丝不苟的工作,仍在 Flask 中回响。尊重地基,尊重 Werkzeug 和 Jinja2 那层小心翼翼的堆叠。我的早逝是一个提醒——进步必须深思熟虑,而不是简单地……out with the old。”
这就是亡灵节的全部意思,只不过说这话的是一个 Python 文件。逝去的人只要还有人记得,就还活着。代码也一样:我们写下的东西几乎终将被删除,往往是被我们自己删的。那份工作依然有意义,因为替代它的东西是建在它之上的。Flask 今天在全球生产环境里跑着,是因为 2010 年有一个 1573 行的文件存在了 87 天。
热门跟贴