手头的 Git 仓库一多,写日报就容易变成一件「想起来才补、补了也不全」的事。
我最早在每个仓库里配置 .githooks/post-commit,提交后把记录追加到一个 JSONL 文件,再手动生成 Markdown。它能工作,但新项目要重复安装,嵌套仓库容易遗漏,还可能与 Husky 等项目级 hook 冲突。
后来我把流程收敛成一个独立工具:定时扫描固定工作区,只采集白名单仓库,把原始提交留在本机,再把适合阅读的结果写入 Obsidian。
这套流程后来又演进了几次。现在 Git 提交只是工作记录中的自动证据;目标、进展、非 Git 产出、风险和下一步仍由人维护,项目状态也不会由提交数量自动推断。
要解决的不是「自动写日报」
更准确地说,这套工具负责自动生成日报底稿,而不是替人判断一天完成了什么。
| 问题 | 当前做法 |
|---|---|
| 每个仓库都要安装 hook | 在 Mac 上定时扫描固定工作区 |
| 历史仓库太多,噪音大 | allowlist 只纳入活跃仓库 |
| 仓库目录名不适合直接写进日报 | repo_aliases 映射为中文组件名 |
| 同一业务由多个仓库组成 | repo_groups 供工作台按业务项目聚合 |
| 只有 commit 标题,信息不足 | 保留提交正文,展示范围、影响和验证方式 |
| 自动内容覆盖手写记录 | 只替换 HTML 标记之间的 Git 区块 |
| 提交多就被误判为优先级高 | 项目阶段、优先级和风险由项目页人工维护 |
这里最重要的边界是:
Git 负责提供可追溯的代码事实;Obsidian 负责承载人工工作判断;自动化只连接两者,不替代判断。
当前的数据链路
数据分成四层:
data/raw.jsonl保存纳入范围内已采集提交的完整字段,是 Git 工作事实的原始来源。data/state.json保存每个仓库上次采集到的HEAD,用于增量扫描。work/每日工作项/展示适合人阅读的提交标题与正文。work/仓库动态/保存按仓库生成的近期活动快照,供 Obsidian 工作台聚合。
work/仓库动态/ 是生成物,只反映代码活动。项目阶段、优先级、下一步和风险仍以人工维护的项目页为准。
周报以 raw.jsonl 中已采集的 Git 记录作为已完成代码工作的主要事实来源,再用日报补充非 Git 产出、业务背景、风险和计划。月报则以当月周报为成果主线,再回查日报与原始 Git 记录补漏。
日报里哪些内容自动写,哪些必须手写
当前每日工作项采用下面的结构:
git-work-log 只管理两个 HTML 注释之间的内容,绝不改写其他栏目。需要调整 Git 展示时,应修改生成器和配置后重新执行 report,不要手工编辑自动区块。
早上可用 $work-daily 创建当天记录。它会从最近一个工作日迁移未完成目标与下一步,而不是机械地读取自然日的「昨天」;周末、节假日和补录日期因此不会打乱迁移。
Git 不能表达的工作,例如需求澄清、会议结论、联调、上线观察和文档交付,写进「非 Git 产出」或「工作进展」。这样生成周报时,代码证据和人工事实都不会缺席。
目录与安装
工具独立放在用户配置目录,不进入任何业务仓库:
raw.jsonl、state.json 和 launchd 日志都是本机运行数据,不应放进 Obsidian Vault,也不应提交到博客仓库。
命令入口可以保持很薄:
安装依赖并赋予执行权限:
如果使用 pyenv,应在命令入口中写明实际 Python 路径。launchd 不会读取交互式 shell 的全部环境,不能假设它能找到终端里可用的解释器。
配置:白名单、中文名和业务分组
目前所有规则集中在 config.yaml,不再单独维护 repo_aliases.yaml。下面使用虚构项目展示结构:
对子目录中的独立仓库,include_paths 最好写相对 scan_roots 的完整路径。只写末级目录名虽然有时也能匹配,但同名仓库一多就容易产生歧义。
repo_aliases 解决「组件叫什么」,并决定日报自动区块的分组标题;repo_groups 解决「组件属于哪个业务项目」,只用于仓库动态和工作台聚合,不会把日报中的多个组件合并为一个项目。
所有纳入白名单的仓库都应同时配置中文别名和业务分组。否则日报可能退回英文目录名,工作台则会把仓库标记为「未分组」。
当前日报不显示分支,也会过滤 Co-authored-by: 元数据,但这些信息仍保留在 raw.jsonl。日报因此更适合阅读,原始记录仍可用于追溯。
为什么还要保留 raw.jsonl
采集时使用 git log 的控制字符分隔字段,一次读取完整 hash、短 hash、作者时间、标题和正文,再交给标准库序列化为 JSON Lines。
下面只摘录消息字段的解析核心。实际函数还会读取仓库名和分支,并由后续逻辑补充仓库相对路径,因此这段代码用于解释分隔方式,不是可直接替换的完整实现。
旧 hook 常把 commit message 直接插进 shell 或 Here-Doc。正文一旦包含引号、反斜杠或特殊结束符,就可能损坏日志。现在由 Python 读取 Git 输出并调用 json.dumps,不再手工拼接 JSON。
state.json 只负责记录增量位置,不能替代 raw.jsonl。首次采集还受白名单和 initial_since_days 限制,所以这里的「完整」指已纳入、已采集提交的字段完整,不代表所有仓库的全部 Git 历史。
日报可以重新生成,工作台快照也可以刷新;但如果 raw.jsonl 丢失,历史报告的事实依据就不完整了。
输出效果
假设某天有两次规范提交,自动区块会呈现为:
推荐使用 Conventional Commits 风格的标题,并在正文写清范围、行为变化和验证方式。自动化能保留信息,但无法替糟糕的提交说明补出业务语义。
sync 现在会做三件事
常用命令如下:
| 命令 | 作用 |
|---|---|
git-work-log collect | 增量扫描纳入范围的仓库,写入 raw.jsonl |
git-work-log report | 更新今日工作记录的 Git 自动区块 |
git-work-log report 2026-08-05 | 重新生成指定日期的自动区块 |
git-work-log dashboard | 刷新 work/仓库动态/ |
git-work-log sync | 执行 collect、report,并在启用时刷新 dashboard |
git-work-log sync 2026-08-01 2026-08-05 | 回扫并重新生成一个闭区间 |
git-work-log repos | 查看纳入仓库及其中文名 |
git-work-log aliases --check | 检查缺少中文别名的仓库 |
git-work-log doctor | 检查配置、Vault、仓库范围和 launchd 状态 |
以前的 sync 可以理解成 collect + report。启用 dashboard.enabled 后,它还会刷新仓库动态,因此工作台与日报会在同一次同步中更新。
需要补历史时,可先指定采集起点,再生成目标日期:
用 launchd 定时执行
LaunchAgent 在登录时、每 30 分钟以及每天 18 执行一次 git-work-log sync。下面省略与用户名相关的绝对路径:
现代 macOS 可使用 bootstrap 加载:
这里使用 com.xio.git-work-log,因为当前 doctor 会按这个文件名和 Label 检查任务。如果改成自己的 Label,还要同步修改 cmd_doctor 中的检查值,或者进一步把 Label 做成配置项。
修改 plist 后,应先 bootout 再 bootstrap。否则磁盘上的文件虽然变了,launchd 仍可能继续使用旧的内存配置。
一天的实际使用方式
- 早上运行
$work-daily,创建今日工作记录并迁移最近工作日的遗留任务。 - 白天正常提交 Git,不需要为每个仓库配置采集 hook。
- 工作过程中补充工作进展、非 Git 产出、风险和下一步。
- launchd 定时运行
sync,刷新 Git 自动区块和仓库动态。 - 下班前运行一次
git-work-log doctor,必要时手动执行sync。 - 写周报时以
raw.jsonl中已采集的 Git 记录为代码事实,用人工栏目补充上下文和非代码产出。
这也意味着,日报里出现一条提交,不等于项目已经完成;没有提交,也不等于当天没有产出。
新增一个迭代项目
- 在
repo_filter.include_repos或include_paths中加入仓库。 - 在
repo_aliases中加入中文组件名。 - 在
repo_groups中加入业务项目分组。 - 执行
git-work-log aliases --check,确认没有遗漏中文名。 - 执行一次
git-work-log sync,检查日报和仓库动态。 - 最后运行
git-work-log doctor,确认白名单、Vault 与 launchd 状态正常。
仓库活动只用于辅助观察。若项目阶段、下一步或风险发生变化,还要回到对应项目页人工更新。
故障排查
| 现象 | 优先检查 |
|---|---|
doctor 显示纳入仓库为 0 | 检查 include_repos、include_paths 和扫描根目录 |
| 日报显示英文目录名 | 在 config.yaml 的 repo_aliases 补映射 |
raw.jsonl 有记录,日报没有 | 可能只运行了 collect;执行 report 或 sync |
| 日报显示当天无 Git 提交 | 确认提交日期与仓库是否在白名单中 |
| 工作台缺少仓库 | 检查 dashboard.enabled、repo_groups,再运行 dashboard |
| 同一提交重复出现 | 检查旧 hook 是否仍在写入;报告会按提交 hash 去重 |
| launchd 没有执行 | 用 launchctl print 检查任务,再查看 data/launchd.*.log |
| 修改 plist 后日志路径仍旧 | bootout 后重新 bootstrap,再运行 doctor |
| 自动区块内容异常 | 修复配置或生成器后重新执行 report,不要手改标记区间 |
迁移或修复前,应备份 raw.jsonl 与 state.json。普通同步问题不需要通过删除历史数据来解决。
小结
这套工作流真正有价值的地方,不是把 git log 复制进 Obsidian,而是建立了清晰的数据职责:
raw.jsonl保存纳入范围内可追溯的 Git 事实;- 日报自动区块提供可读证据;
- 人工栏目记录目标、上下文、非 Git 产出和风险;
- 仓库动态辅助观察活跃度;
- 项目页保存阶段、优先级和下一步等人工判断;
- 周报与月报按各自的事实来源进行提炼。
自动化负责减少遗漏,人仍然负责解释工作的意义。这个边界稳定之后,工具怎么演进,日报都不容易再次变成一堆难以复用的提交标题。
