ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

hindsight:基于CLI与SQLite的本地复盘工具设计与实践

hindsight:基于CLI与SQLite的本地复盘工具设计与实践 目录兜底说明这篇文章不是一个看完就删的资讯帖而是一套可以直接落到本地的复盘工具思路。我会先说动机再给数据结构然后是CLI命令设计、复盘流程、数据接入最后聊一点经验教训。全程没有云端依赖没有复杂平台所有代码都能在你自己的电脑上跑起来。1. 为什么我会想做hindsight这个项目事情的起因特别朴素。我从2020年开始有记工作日志的习惯每天下班前花五分钟把当天做了什么、卡在哪里、明天要做什么写进一个Markdown文件。到了年底我发现一个尴尬的事实我写的日志至少有十几万字但我一次都没有回头读过。每年年终总结都是从记忆里硬拼出来的那些日志躺在文件夹里纯粹当仓库囤货。后来我又翻了翻项目历史里的提交记录、Chrome浏览器的搜索历史、终端里敲过的命令发现一个很有意思的现象过去某个时刻苦苦折腾了两天的问题三个月后我会以几乎一模一样的方式再踩一遍。我甚至能翻到自己在QQ群里说过同一句话三次的记录。问题不出在记忆力出在当时没有沉淀事后没有回顾这个循环上。于是我想做一个工具它能定期把散落在本地的各种痕迹捞出来帮我做回顾、做复盘、做对比。名字取得很直白就叫hindsight——事后回顾的视角。它不装什么聪明算法不搞云端智能核心就三件事收集、索引、回顾。收集本地数据索引成结构化记录然后在固定的时间点把过去的片段重新推到我的眼前。这个工具适合谁说实话不太适合完全不喜欢记录的人。如果你连一行日志都不愿意写那它对你来说就是空转。它适合的是下面这类人已经有写日记、写日志习惯但从来不回看的人。经常在重复踩坑希望建立防再犯机制的人。对数据隐私敏感不想把个人记录上传到任何第三方平台的人。想用最低成本把记录升级成复盘的人。我最终做出来的东西很简单就是一个Python写的命令行工具加上一个SQLite数据库文件。没有Web服务没有后台进程只有两条核心命令一条负责收集一条负责复盘。后面我会上完整的设计思路。2. 先想清楚数据架构什么该存什么不该存动手写代码之前我花了最多时间的不是写逻辑而是想数据从哪里来、存成什么样、以什么粒度存。2.1 数据源选型的取舍本地数据其实多得很关键是怎么筛选。我最初列了一个清单逐个做了取舍数据源示例内容是否需要理由工作日志每天上下班记录必须文本质量最高是复盘的主食物提交历史项目commit message必须能还原一个功能从想法到落地的全过程浏览器历史搜过什么浏览过什么页面建议能反映当时在研究什么但噪音很大终端历史敲过的shell命令看情况对开发者很有用但普通人意义不大屏幕截图桌面截图不推荐存储膨胀快检索成本高聊天记录群里说过的话强烈不建议敏感信息多且文本格式千奇百怪我的选择是优先把高信噪比、低获取成本的数据源接进来。工作日志和Git提交记录首当其冲。浏览器历史虽然噪音大但是它能极其准确地还原某天我在为什么事情头疼——因为搜索关键词骗不了人。终端历史则对程序员来说是个金矿你回头看自己敲过的命令能发现很多当时没意识到的低效重复操作。2.2 存储格式一条命一条链存储格式我换过三版第一版把所有数据塞进JSON文件第二版用Markdown加Front Matter最后定为SQLite加行式JSON的混合方案。为什么要混合因为两类数据访问模式完全不同日志、笔记这类人工输入的内容适合以Markdown文件为源头工具只做索引不动原文。浏览器历史、命令历史这类机器产物量大、格式固定适合直接导入数据库按时间索引。所以我设计了一个很简单的数据模型。数据库中建了一张entries表每一条记录是一个事件单元CREATE TABLE entries ( id INTEGER PRIMARY KEY AUTOINCREMENT, source TEXT NOT NULL, -- 数据来源类型journal/git/browser/shell source_ref TEXT, -- 原始数据的定位信息比如文件路径行号 content TEXT NOT NULL, -- 事件描述或内容 occurred_at TEXT NOT NULL, -- 事件发生的原始时间戳 captured_at TEXT NOT NULL, -- 被hindsight索引的时间戳 tags TEXT -- 逗号分隔的标签用于分类筛选 );光有这张表不够我发现复盘时有个高频操作是找某段时间大家都在搞什么主题如果只靠SQL的模糊查询会很痛苦。所以我又加了一张topics表用来记录自动提取的主题聚类结果。自动聚类不一定要上机器学习用简单的关键词共现就能做到七八分效果后面我会详细说。2.3 时间粒度的选择按周复盘而不是按天复盘这件事节奏很重要。按天复盘容易变成流水账按周复盘刚好能把一个完整的小周期包进来比如周一提的需求、周三改的方案、周五提交的代码是一条完整的故事线拆开看就断层了。我在工具里预设了两个时间维度回顾点review point和追溯窗口lookback window。默认的回顾点是每周五下午追溯窗口是过去7天。同时留了一个参数允许用户把回顾点设在任意时刻把窗口拉长到30天甚至一个季度。数据的链路到这里就清楚了原始数据留在原处hindsight只抽取结构化摘要进SQLite复盘时再从数据库捞出来渲染成一份回顾报告。3. 复盘的基础过去七天重点到底该看什么这是整个项目中我迭代次数最多的部分。一开始我天真地想把过去七天的所有数据倒出来做成一个HTML报告。做完之后发现根本没用信息量太大等于没有信息。人面对五百条日志和一百条Git提交时大脑直接宕机。3.1 从全量展示到问题驱动我后来换了个思路不展示所有数据而是围绕几个固定的问题来回溯。每个回顾周期工具会问下面这组问题这段时间你反复提到的关键词是什么哪几个主题消耗了最多天数判断依据是记录中日期的分布密度有没有未闭环的事件——比如某天记录提到等某某反馈之后就再也没有后续了。最近一次踩的坑历史上有没有出现过类似描述哪些当时标记为重要的事情实际上后来并没有推进看起来很简单但每个问题落到实现上都需要不同的查询策略。3.2 关键词提取与共现聚类我实现主题聚类的方法是先按词典拆词去掉停用词再用一个滑动窗口统计共现频率。举例来说如果数据库迁移这个词对在五天里出现了四次那它一定会被拎出来。具体代码如下from collections import Counter, defaultdict import re STOPWORDS set(的了和在是与及或一个我们你们他们.split()) def tokenize(text): # 简单按词拆分实际项目中可以换成jieba等分词库 words re.findall(r[\u4e00-\u9fa5a-zA-Z0-9], text) return [w for w in words if w not in STOPWORDS] def extract_topics(entries, window_size5, min_freq2): freq Counter() cooccur defaultdict(Counter) for e in entries: words tokenize(e[content]) for i in range(len(words)): for j in range(i 1, min(i window_size 1, len(words))): pair tuple(sorted([words[i], words[j]])) cooccur[pair[0]][pair[1]] 1 freq.update(words) topics [] for pair, cnt in cooccur.items(): for neighbor, n in cnt.items(): if n min_freq: topics.append((pair, neighbor, n)) topics.sort(keylambda x: x[2], reverseTrue) return topics[:20]这里的输出不是严谨的主题模型但足够诚实。它不会告诉你你这一周在忙什么但会列出哪些词反复出现剩下的判断交给大脑。3.3 未闭环事件的追踪这个功能是后来加上去的但也是我使用频率最高的。实现思路特别朴素把记录里含等待pending这类词的句子标记出来然后搜索这个句子提到的对象在后续记录里是否再次出现。如果一周后还查不到任何关联记录就把它列入未闭环提醒。实际用下来我发现大多数未闭环事件并不是忘了而是消失了。有些事情你不跟进它自己就没了。hindsight的提醒功能最大的价值不是让你去补做那些事而是让你意识到哦原来这件事当时就那样放下了——这个意识本身就很有复盘价值。4. CLI设计写完之后就忘掉的工具设计理念我不想让用户打开任何图形界面也不想让他们打开浏览器。复盘应该是零障碍的打开终端敲一条命令看完输出关掉终端。所以整个工具就做成了CLI。4.1 命令总览核心命令只有四条hindsight collect --source journal --path ~/journals hindsight collect --source git --repo /path/to/repo hindsight review --period weekly hindsight search --keyword 数据库迁移 --from 2025-01-01collect负责从各种源头抽取数据进数据库review负责生成回顾内容search负责回溯历史。没有update命令因为每条记录都有唯一的source_ref重复收集时会自动跳过。4.2 collect命令的实现细节collect是所有数据流的入口我从journal这个数据源开始做。journal源的设计规则是扫指定目录下的所有Markdown文件按文件名中的日期排序每个文件里的二级标题视为一条独立的事件记录。def collect_journal(path): entries [] for md_file in sorted(Path(path).glob(*.md)): date_match re.search(r(\d{4}-\d{2}-\d{2}), md_file.name) if not date_match: continue date date_match.group(1) lines md_file.read_text(encodingutf-8).splitlines() current_title None for line in lines: if line.startswith(## ): current_title line[3:].strip() elif current_title and line.strip(): entries.append({ source: journal, source_ref: f{md_file}#{current_title}, content: f{current_title}{line.strip()}, occurred_at: f{date} 00:00:00 }) return entries这个实现里有几个我在踩坑后特意留下的设计source_ref必须能定位原文。这样复盘时看到一条记录能反查回原文上下文。重复收集的幂等去重。我用source_ref做唯一键重复collect不会产生垃圾数据。跳过空行和废话。我一度把日志文件里所有内容都塞进去结果关键词提取时全是今天做了一个这类高频废词。后来加了两层过滤停用词表 长度少于4个字的句子直接丢弃。收藏浏览器历史的时候我做了另一个决定不做全量导入只导入搜索关键词和页面标题。浏览器历史里乱七八糟的URL太多了与其全存下来然后用的时候痛苦筛选不如在采集时就只保留最像人类意图的信息——搜索框里输入的内容以及他最终点开的页面标题。4.3 review命令的输出设计review命令的输出我很花心思。它默认输出到终端用纯文本排版避免依赖各种渲染组件。我尽量让每一行都有信息增量本周回顾报告2025-02-10 ~ 2025-02-16 本周出现频次最高的主题 1. API鉴权改造7次 2. 数据库慢查询5次 3. 月度账单核对4次 未闭环事件 - 2月12日: 等后端确认接口字段 - 2月13日: 待整理测试报告 本周有历史相似记录的句子 1. 【历史重现】慢查询要加索引首次出现2025-01-08 → 近一周再次出现3次属于重复踩坑建议看下当时的处理记录。这里的历史相似记录功能我用的是很原始的文本相似度匹配把句子分词后计算公共词占比超过阈值就认为是历史重现。后来我试过用向量化但对短文本来说收益极低公共词占比反而更直观。5. 复盘流程每周五下午的一个二十秒仪式工具做好了最难的反而是养成习惯。我给自己设计了一套极轻量的复盘仪式每周五下午定时执行全程只需要20秒。5.1 我的实际流程首先运行收集命令把这一周新增的日志和提交记录导入数据库hindsight collect --source journal --path ~/journals hindsight collect --source git --repo ~/workspace/myapp接着运行周度回顾hindsight review --period weekly这个命令会花两三秒扫描数据库输出上文提到的那份报告。我盯着它看五秒然后做三个动作找一个未闭环事件决定是否要主动跟进找一个历史重现的关键词决定是否需要写一条防再犯笔记如果本周有特别重要的决定在日志里补一段当时为什么这么选的记录。整个过程不超过20秒。你别小看这个仪式坚持三个月之后我的日志回看率从几乎为0提升到了每周固定一次。工具的价值不是帮你自动思考而是帮你把思考前的准备工作压缩到可以忽略不计。5.2 防再犯笔记的结构上面提到的防再犯笔记是我在实践中自己加的一个功能。当我发现一个坑踩了第二次时我会手写一条结构化笔记存到另一个文件里。hindsight会在每周回顾时随机抽取一条防再犯笔记展示出来--- topic: 数据库慢查询 symptom: 列表接口响应时间超过2秒 root_cause: 没有对status字段建索引 first_happened: 2025-01-08 repeat_count: 3 lesson: 写SQL之前先看一眼执行计划无索引字段过滤要提醒自己 ---这个机制虽然简单但实际作用比我想象中大。它把回忆变成了搜索不再需要我脑子里记住所有教训只需要我在踩坑时花一分钟把它写下来之后每次周复盘它都会自动出现在眼前。5.3 手动触发的情境式复盘除了周复盘我还会在某些特定时刻手动运行hindsight。比如一个项目刚结束我会把时间窗口设为项目周期生成一份项目维度的回顾报告又比如当我连续几天觉得状态不好我会回溯过去两周的日志看看是不是有什么消耗性事务一直在浪。hindsight的review命令支持 --from 和 --to 参数指定任意时间窗口。我在使用中最舒服的用法是这种情境驱动的回顾脑子里有困惑马上翻记录找线索而不是被动等每周五的报告。后者是保底前者才是这个工具上限最高的用法。6. 数据接入的深度定制除了日志还能接什么前面说到collect支持journal和git两种数据源实际项目里我又陆陆续续接了好几种。给大家参考一下我的接入原则接入成本低于半小时的数据源才值得接。如果一个数据源需要你写各种自定义解析器那它大概率不值得。6.1 微信读书与稍后读我接了一个稍后读数据源跟踪的是Instapaper/Pocket这类应用导出的高亮列表。这个数据源有意思的地方在于它反映的是你想学什么而不是学了什么。把高亮列表和实际日志做对比能看出知识和行动之间的距离到底有多大。实现上就是读导出的csv文件把摘要和原文链接存进entries表。csv导入的代码大概五十行不算复杂。6.2 截图OCR的尝试然后放弃我一度想接屏幕截图OCR把每天工作时的屏幕画面存下来做全文检索。试用一周后放弃了。原因有三隐私风险太大截屏里可能包含别人的聊天内容、敏感信息存本地都让我不放心存储膨胀太快一天几百张截图一星期就要好几个GBOCR带来的信息增益很低大多数截屏内容跟日志里的文本高度重合。放弃之后我把精力集中在已经能拿到的高质量文本上效果反而更好。这也算一个经验新数据源不是越多越好信息重叠度高的数据源只会增加噪音。6.3 接入自定义数据源的标准姿势我给hindsight留了一个简单的插件式接口在hindsight_collectors目录下放一个python文件里面实现collect()函数就行。接入新数据源时我会先回答三个问题这个数据源能不能自动化收集收集出来的信息有没有90%以上的非重复价值它的时间戳是否可靠三个问题都是是才会接进来。这套筛选标准帮我避开了无数华而不实的数据源。7. 使用一年后的经验与几点敲打工具到现在用了将近一年说一点真实体会有收获也有翻车。7.1 复盘的真正难点不是工具是敢于回头看我发现一个心理规律人们不愿意复盘不是因为没有数据而是因为很多当时自己做过的事回头看会觉得尴尬或者后悔。hindsight把历史翻出来的瞬间心理防线先被击穿。我自己的解决办法是从小事入手每周只回答一个问题这七天哪件事最浪费时间先从最轻松的角度进入慢慢建立耐受。工具的定位只能是辅助真正的反思动作必须由人完成。hindsight能告诉你这个词这周出现了七次但它不会告诉你你其实一直在回避那个问题——这种结论只能自己得出。7.2 收集频率设成手动触发而不是后台自动我一开始想做成后台自动收集每天定时让脚本跑一轮。但实际用下来强烈不建议这样原因有两个一是很多数据源比如浏览器历史只在特定时间导出才有意义自动跑容易抓到空数据二是手动跑collect会给人一个现在要准备复盘了的心理暗示对养成习惯反而有帮助。7.3 数据文件格式要稳解析器要懒我的日志目录三年没换过格式文件名永远是YYYY-MM-DD.md二级标题永远是当天的小节名。这个格式稳定的原则比工具本身更重要。hindsight的解析器写得越懒长期维护成本就越低。如果今天用新的日期格式明天换标题层级后援维护就会变成无底洞。7.4 别把hindsight神话成个人知识库大脑经常看到有人想做第二大脑、AI复盘助手恨不得所有数据都自动流进去然后自动吐结论。我实践下来的结论很清醒这类工具的上限是提醒下限是整理它做不了思考。hindsight最好的状态是让你在周五下午花半分钟看到一条过去的信息然后自己喊出一句原来我当时是这样想的——这就够了。所以如果你也想搭一个类似的东西我给你的建议是先把数据结构想清楚再动手写CLI先只接一个数据源跑两周再决定要不要加新的先手动触发三个月再谈自动化。惯性是这类工具最大的敌人而它最需要对抗的其实是你自己的遗忘。
返回列表