真源:**文件层变化**。git diff 看代码 / 文档真实改了什么。
包括 author / sha / 时间。
#### timeline.ndjson
真源:**事件流**。含非文件改动(如 SSE / Agent 进出 / state transition)。比 git log 信息量更大。
#### changelog.ndjson
派生:**文档级摘要**。每条对应一次 PRD/SDD/units rev,与 git tag `prd-rev-N` / `sdd-rev-N` 一一对应。
三者互补不冲突。git log 和 timeline 各自**append-only**,drift 风险低(都由 LoopController 在 callAgent 末尾原子写)。
## ⑩ 失败恢复 · 新能力
| 场景 | 没 git 时 | 有 git 时 |
| --- | --- | --- |
| Server crash | agent-state.paused,但代码可能**半写** | `git status` 看脏文件 · `git reset --hard HEAD` 一键恢复 |
| Coder 3 次 retry 都红 | 只能看 coder.log 文字 | `git log --author='Coder'` 看 3 次 commit · `git diff` 看模型 3 次都改了啥 |
| L2 通过后人类突然改主意 | 整个手动 hack 回退 | `git revert ship-v1` 即可 |
| L3 反馈引入回归 | 需要重新跑全套 | `git bisect` 找到哪次 commit 引入了破坏 |
| 项目导出 | `cp -r` 整个目录 30MB | `git archive HEAD` 干净打包,仅 tracked 文件 ~5MB |
| 跨机器同步项目状态 | 不可能(带 .vitest-cache 等垃圾) | `git push` + 对端 `git clone` |
## ⑪ P-8 状态机的协同
P-8 持久化层与 git 不重叠:
```ts
// Agent 调用结束的完整流程 (P-8 + P-10)
async function callAgentWithPersistence(name, input) {
// P-8 step 1: 写 runtime status=running
await writeRuntimeState(pid, name, 'running');
// 实际调用 (Agent 通过工具写文件)
const result = await agentManager.callAgent(...);
// P-8 step 2: 写 runtime status=done
await writeRuntimeState(pid, name, 'done');
// P-10 step 3: git commit (新增)
if (result.ok) await commitAfterAgent(pid, name, result, input);
// timeline / SSE / changelog 等照旧
...
}
// recoverOnBoot 增加 git 清理:
async function recoverOnBoot() {
for (const pid of listAllProjects()) {
// 既有: 扫 agent-state · 改 running → paused
...
// 新增: 检查 git 工作区是否脏
const status = await gitVault.status(pid);
if (status.dirty) {
// 上次 crash 留下未提交脏文件
sseBroker.emit(pid, 'recovery_dirty_tree', {
files: status.files,
options: ['commit_as_wip', 'reset_hard', 'manual_inspect'],
});
}
}
}
```
## ⑫ 推 remote · v2 留口
v1 不做。但 GitVault API 已留 `addRemote` + `push`。v2 可加:
```js
# 把项目代码推到 GitHub
POST /api/devloop/projects/:pid/push
{
"remote": "origin",
"url": "git@github.com:yibo/auto-todo.git",
"branch": "main",
"open_pr": false
}
# GitHub Actions 接管 CI/CD,DevLoop 只负责生产 + 提交
```
v2 还可加 **git submodule 共享**:跨项目复用 utils → 抽 shared/ submodule。
## ⑬ 安全考量
| 风险 | 缓解 |
| --- | --- |
| git push 凭据泄露(v2) | v1 不实现 push;v2 用 SSH key per company,不存 password |
| Agent 越权调 git | Agent 的 toolPolicy 不含 git 工具,Bash 不允许 `git *` 命令(jsonGuard 类似的 cmdGuard) |
| commit 含敏感数据 | 项目 .gitignore 排除 secrets/ · token.txt · *.env |
| 仓库膨胀 | stream.log 大文件 .gitignore;定期 `git gc` |
| 并发 commit race | GitVault 按 pid 串行化(mutex per pid) |
## ⑭ P0 工时增量
| P0 子项 | 新增工时 |
| --- | --- |
| GitVault 实现 (~150 行 spawn git wrapper) | 1 天 |
| .gitignore 模板 + LoopController.createProject git init 集成 | 0.5 天 |
| commitAfterAgent + buildCommitSpec | 0.5 天 |
| recoverOnBoot 加 git status 检查 | 0.5 天 |
| 验证: phase0-verify 加 git init + 一次 commit 验证 | 0.5 天 |
| **P0 +3 天**(总 P0 14 天) | — |
> _原 11 天 P0 → 14 天,总 9.5 周 → 10 周。代价相对收益(真版本管理)合理。_
## ⑮ 实战命令
```js
# P0 后验证 git 集成
# 1. 创建项目应该自动 git init
curl -X POST http://localhost:3010/api/devloop/projects -d @/tmp/create.json
ls data/companies/c-test/projects/p-XXXXXX/.git/
# 期望: HEAD config objects refs/ 等
# 2. 跑完 PM 应该有 1 个 prd commit
git -C data/companies/c-test/projects/p-XXXXXX log --oneline
# 期望: 至少 2 行,init + prd: rev 1
# 3. 跑完 Architect
# 期望: 多 1 行 sdd: rev 1
# 4. 跑完 Coder→Tester 绿后
# 期望: 多 1 行 code(todo-be): implement · attempt 1
# 5. L2 通过后
git -C ... log --oneline
# 期望: ship: prd@1 sdd@1
git -C ... tag --list
# 期望: prd-rev-1, sdd-rev-1, shipped-v1-1
# 6. retry 演示
# 注入 bug 让 Coder retry 2 次再绿
git -C ... log --author='Coder' --oneline
# 期望: 仅 1 个 commit (因为只在 Tester 绿才 commit)
# 但 coder.log 有 2 条 attempt 记录
# 7. 导出
node -e "require('./server/services/git-vault').gitVault.archive('p-XXXXXX').then(r => console.log(r))"
# 期望: /tmp/p-XXXXXX-HEAD.zip 含 tracked files only
```
## ⑯ 关联
- [sdd-process](sdd-process.html) · P-10 自动提交(同此页 §⑧)
- [sdd-control](sdd-control.html) · C-18 GitVault(同此页 §⑦)
- [data-layout](data-layout.html) · .git/ 目录位置
- [plan-migration](plan-migration.html) · P0 工时已含 GitVault
- [代码归宿](plan-code-lifecycle.html) · v2 自动 push 是这一层的延伸