diff --git a/build-manifest.json b/build-manifest.json index 854bd92..44eedec 100644 --- a/build-manifest.json +++ b/build-manifest.json @@ -2,7 +2,7 @@ "meta": { "source": "docs/prd/newfunction", "prdRev": "v0.2", - "extractedAt": "2026-05-13T04:16:35.145Z" + "extractedAt": "2026-05-13T05:14:45.606Z" }, "model": [ { @@ -480,6 +480,25 @@ "diff 字段计算由 DocStore 提供(cheerio 算 section 差异 + 拓扑算 units 差异)" ], "tests": [] + }, + { + "name": "GitVault", + "path": "server/services/git-vault.js", + "shape": "// 详尽设计见 arch-git-versioning.html\nmodule.exports.gitVault = {\n init(pid, opts?): Promise<{ ok, head }>,\n commit(pid, { kind, unit_id?, run_id?, summary, files?, author?, skipEmpty? }):\n Promise<{ ok, sha?, skipped? }>,\n tag(pid, tagName, opts?): Promise<{ ok, tag }>,\n log(pid, opts?: { limit?, since?, grep? }): Promise,\n diff(pid, refA, refB, opts?): Promise,\n show(pid, sha): Promise,\n listTags(pid): Promise,\n status(pid): Promise<{ dirty, files }>,\n resetWorkingTree(pid, ref = 'HEAD'): Promise<{ ok }>,\n revert(pid, sha): Promise<{ ok, newSha }>,\n archive(pid, ref = 'HEAD', format = 'zip'): Promise<{ path }>,\n // v2 留口\n addRemote?(pid, name, url),\n push?(pid, remote),\n};", + "contract": [ + "实现用 child_process.spawn('git', ...),不依赖第三方 npm 包(避免凭据管理复杂化)", + "所有方法按 pid 串行化(per-pid mutex),避免同项目并发 git 操作打架", + "commit author 标 Agent 名(e.g. \"PM Agent \"),git log 可读「哪个 Agent 干的」", + "nothing-to-commit 不抛错,返 { ok: true, skipped: true }", + "commit 失败不阻塞 Agent 返回(git 是 nice-to-have,不是阻塞依赖)" + ], + "tests": [ + "init 后 .git 存在,.gitignore 已 commit", + "commit({kind:'prd'}) 后 git log 有该 commit,author 是 \"PM Agent\"", + "nothing-to-commit 返 skipped=true 不抛", + "tag('shipped-v1') 后 listTags 包含 shipped-v1", + "archive 返回 zip 路径,unzip -l 看仅 tracked files" + ] } ], "process": [ @@ -565,6 +584,24 @@ "shape": "// L1 内:Tester 失败 → 开 bug + 设 unit failed\ntesterResult.failures.forEach(f => {\n bugTracker.open(pid, {\n source: 'unit_test', unit_id: unit.id, run_id: runId,\n test_name: f.test, category: 'unknown',\n severity: 'high', title: f.test + ' ' + brief(f),\n detail: JSON.stringify(f), affected_units: [unit.id]\n });\n});\n\n// 下一轮 Coder 进入 → 关联 bug 状态转 fixing\nbugTracker.list(pid, { unit_id: unit.id, status: ['open'] })\n .forEach(b => bugTracker.transition(pid, b.id, { to:'fixing', by:'coder' }));\n\n// L2 通过 → 所有 fixed → verified\nif (integratorResult.ok) {\n bugTracker.list(pid, { status: ['fixed'] })\n .forEach(b => bugTracker.transition(pid, b.id, { to:'verified', by:'system' }));\n}", "contract": [], "tests": [] + }, + { + "name": "Process.GitCommit", + "path": "server/src/core/loop-controller.js · callAgentWithPersistence 的 success 分支扩展", + "shape": "// 完整设计见 arch-git-versioning.html · 此处只放骨架\n\nasync function commitAfterAgent(pid, name, result, input) {\n const spec = buildCommitSpec(name, result, input);\n if (!spec) return; // 某些情况不 commit (Coder 等 Tester 绿,Tester 红不 commit)\n\n const { ok, sha, skipped } = await gitVault.commit(pid, spec);\n\n if (ok && !skipped) {\n timelineLogger.append(pid, { source:'system', kind:'git_committed',\n ref: sha.slice(0,7), summary: spec.summary });\n sseBroker.emit(pid, 'git.committed', { sha, kind: spec.kind, summary: spec.summary });\n }\n}\n\n// Commit 时机表\nPM 完成 prd → kind:'prd', msg: 'prd: rev N · {summary}'\nArchitect generate → kind:'sdd', msg: 'sdd: rev N · X units · ...'\nArchitect patch → kind:'sdd', msg: 'sdd: patch · {affected_units}'\nCoder + Tester 绿 → kind:'code', msg: 'code(unit-id): implement|retry · attempt N'\nCoder 单独完成 → 不 commit (等 Tester 验证)\nTester 红 → 不 commit (避免噪音)\nBugTriager → kind:'triage', msg: 'triage(run-id): {category} · confidence {n}'\nIntegrator → kind:'integ', msg: 'integ(run-id): pass|fail · X/Y'\nshipped → tag:'shipped-v{prd_rev}-{sdd_rev}'\nsign-off → tag:'final-v{prd_rev}'\nabandon → tag:'abandoned'", + "contract": [ + "Coder commit 推迟到 Tester 绿(避免红色代码进 main)", + "所有 commit author 标 Agent 名", + "nothing-to-commit 返 skipped=true,不抛错", + "commit 失败 → 记 warning 但不阻塞 Agent 返回", + "shipped/sign-off/abandon 用 tag 而非 commit,commit 是文件变化,tag 是项目里程碑" + ], + "tests": [ + "项目创建后 git log 含 init commit + .gitignore", + "PM 完成后多 1 个 prd: rev 1 commit", + "Coder retry 3 次但只 Tester 绿一次 → 仅 1 个 code commit", + "shipped → tag shipped-v1-1 存在" + ] } ] } \ No newline at end of file diff --git a/docs/prd/newfunction/arch-git-versioning.html b/docs/prd/newfunction/arch-git-versioning.html new file mode 100644 index 0000000..b6b50c9 --- /dev/null +++ b/docs/prd/newfunction/arch-git-versioning.html @@ -0,0 +1,514 @@ + + + + +Git Versioning · LemonBoss PRD + + + + + + +
+ +
+
+
05 · ARCHITECTURE/Git Versioning
+

Git Versioning · 每次 Agent 完工 = 一个 commit

+

把每个 DevLoop 项目变成独立 git repo,每个 Agent 成功完成工作 → LoopController 自动 commit。
项目结束 → 打 tag。这一层补上文档之前缺的真版本管理——崩了能 reset,丢了能 archive,3 次 retry 能 diff。

+
+
+ +
+

① 现状 vs 提议

+
+
+

之前(无 git)

+
    +
  • 文件 mtime 是唯一时间戳
  • +
  • history/ 目录是 ad-hoc 归档
  • +
  • 没有 atomic snapshot
  • +
  • 回滚靠 fs.rename + 手动拷贝
  • +
  • retry 之间无法 diff(旧文件被覆盖)
  • +
  • 项目导出 = 拷整个目录
  • +
+
+
+

提议(每项目一个 repo)

+
    +
  • 每个 Agent 完工 → git commit
  • +
  • shipped → git tag
  • +
  • 整个 retry 链是 git log(可 diff 任意两次)
  • +
  • 崩 → git reset 干净恢复
  • +
  • 导出 → git archive
  • +
  • 未来可加 git push 到 GitHub(v2)
  • +
+
+
+
+ +
+

② Repo 范围决策

+ + + + + + + + +
方案位置优劣
A · 每项目一个 ← 选这个data/companies/{cid}/projects/{pid}/.git隔离强 · 删项目就删 repo · 导出干净 · 可分别 push 不同 remoterepo 数量随项目增长(每个 ~10-50MB)
B · 一个公司一个data/companies/{cid}/.git跨项目共享对象跨项目混在 git log;不易导出单项目
C · 全局一个data/.git对象去重最大所有 company 混;权限灾难
D · 主仓库lemonboss/.git不增 repo污染 LemonBoss 主提交历史;不可接受
+

选 A 的核心理由:DevLoop 项目本质是临时工作空间,shipped 后或归档或导出,独立 repo 让生命周期边界清晰。

+
+ +
+

③ 落盘后的目录

+
+data/companies/{cid}/projects/{pid}/
+├── .git/                    # NEW · 整个项目的 git 历史
+├── .gitignore               # NEW · 项目创建时初始化
+├── project.json
+├── prd/                     → tracked
+├── sdd/                     → tracked
+├── units.json               → tracked
+├── integration/spec.json    → tracked
+├── timeline.ndjson          → tracked (append-only,git OK)
+├── bugs.ndjson              → tracked
+├── changelog.ndjson         → tracked
+├── units/                   → tracked (code + tests + contract)
+├── integration/runs/        → tracked (result.json + triage.json)
+├── agent-state/             → .gitignore (高频写,运行态)
+├── history/                 → .gitignore (git 本身就是 history)
+└── stream.log               → .gitignore (SSE 流,7 天滚动)
+
+
+ +
+

④ .gitignore 模板

+
# LemonBoss DevLoop 项目 .gitignore (项目创建时落盘)
+
+# 高频写的运行态,不需要版本
+agent-state/
+
+# 真冗余 (git 本身就是 history)
+history/
+
+# 大日志
+stream.log
+units/*/runs/*/stdout.txt
+integration/runs/*/process.log
+integration/runs/*/http-trace.ndjson.gz
+
+# Node / 依赖
+node_modules/
+*.log
+.DS_Store
+.cache/
+.tmp/
+.coverage/
+
+# 测试中间文件
+units/*/.vitest-cache/
+
+# 但保留: result.json triage.json contract.json 等小 JSON
+

由 GitVault.init 工具在 git init 后立即写入;首个 commit 就含 .gitignore。

+
+ +
+

⑤ Commit 时机表

+ + + + + + + + + + + + + + + + + +
事件commit msg 模板触发位置files added
项目创建init: project {pid} · {requirement 摘要 30 字}LoopController.createProject 末尾project.json + .gitignore
PM 写完 PRDprd: rev {N} · {change_summary}PM Agent 成功返回后prd/** + changelog.ndjson + timeline 增量
Architect 生成 SDDsdd: rev {N} · {units_count} units · {summary}Architect generate 成功后sdd/** + units.json + integration/spec.json + changelog/timeline
Architect patchsdd: patch · {affected_units 列表}Architect patch 成功后仅改的 sdd/mvcp/{x}.html + units.json (hash 更新)
Coder 实现code({unit_id}): implement · attempt {N}Coder 成功 + Tester 绿才 commitunits/{id}/code/** + contract.json + coder.log
Coder retry 通过code({unit_id}): retry → green · attempt {N}Tester 绿(attempt > 1)同上
Tester 写测试合并到上一条 commit同 Coder commit 一起tests/ + runs/{}/result.json 一起
BugTriager 归因triage({run_id}): {category} · confidence {n}Triager 写完 triage.json 后integration/runs/{}/triage.json + bugs.ndjson 增量
Integrator 跑integ({run_id}): {ok|fail} · {cases_passed}/{cases_total}Integrator 完工integration/runs/{}/result.json + process.log(如不在 ignore)
Unit reset (L2 修复后)不 commit,下一次 Coder commit 时一起——
项目 shippedship: prd@{prd_rev} sdd@{sdd_rev}
+ git tag shipped-{rev}
LoopController.shipProject—(tag only)
项目 sign-offchore: sign-off by {user_id}
+ git tag final-{rev}
POST /sign-offproject.json (status=shipped-final)
项目 abandonchore: abandon · {reason}
+ git tag abandoned
POST /abandonproject.json (status=abandoned)
+

关键决策:失败不 commit(避免噪音 + 失败的代码不该留在 git 主线)。Tester 红 → Coder retry 时旧代码被工作区覆盖,反正没人需要回到失败态。

+
+ +
+

⑥ 谁调 git · 集中控制

+

关键原则:Agent 不动 git。LoopController 在 callAgentWithPersistence 的 success 分支统一调 GitVault.commit。

+ + + + + + Agent (PM/Coder/...) + 写 prd/ · code/ · 等 + + + LoopController + callAgentWithPersistence + + + GitVault · NEW + commit · tag · log + + + .git/ + objects + refs + + + timeline_append('git_committed') + + + SSE: git.committed + + + + + + + + + files only + commit({pid,kind,...}) + git ops + + + +

这样 Agent 沙箱继续严格(不允许 Bash 调 git),但项目状态真实有版本。

+
+ +
+

⑦ GitVault · C-18 服务方法

+ server/services/git-vault.js + +
module.exports.gitVault = {
+  // 初始化(项目创建时)
+  init(pid: string, opts?: {
+    name?: string, email?: string,    // 默认 "DevLoop <devloop@lemonboss.local>"
+    initialBranch?: string,            // 默认 "main"
+  }): Promise<{ ok: boolean, head: string }>,
+
+  // 主提交入口 (LoopController 调)
+  commit(pid: string, {
+    kind:    'init' | 'prd' | 'sdd' | 'code' | 'triage' | 'integ' | 'ship' | 'chore',
+    unit_id?: string,                  // code 时填
+    run_id?:  string,                  // triage / integ 时填
+    summary:  string,                  // 简短描述
+    files?:   string[],               // 限定 add 的路径 (默认 git add -A 受 .gitignore)
+    author?:  { name, email },        // 哪个 Agent 干的,记到 commit author
+    skipEmpty?: boolean,              // nothing-to-commit 时是否抛错,默认 false
+  }): Promise<{ ok, sha?: string, skipped?: boolean }>,
+
+  // 打 tag
+  tag(pid, tagName: string, opts?: { message?, sha? }):
+    Promise<{ ok, tag }>,
+
+  // 查询
+  log(pid, opts?: { limit?, since?, grep? }): Promise<Commit[]>,
+  diff(pid, refA: string, refB: string, opts?: { path? }):
+    Promise<string>,                    // unified diff text
+  show(pid, sha: string): Promise<Commit + diff>,
+  listTags(pid): Promise<Tag[]>,
+
+  // 写回滚 (P-8 recovery 用)
+  resetWorkingTree(pid, ref = 'HEAD'): Promise<{ ok }>,   // 抛弃未 commit 的脏文件
+  revert(pid, sha: string): Promise<{ ok, newSha }>,        // 反向应用
+
+  // 导出
+  archive(pid, ref = 'HEAD', format = 'zip'):
+    Promise<{ path: string }>,      // 跑 git archive
+
+  // v2 留口
+  addRemote?(pid, name, url),
+  push?(pid, remote = 'origin'),
+};
+ +
    +
  • 实现可走 simple-git 包 或 spawn git;推荐 spawn git(零依赖,CLAUDE.md 强约束的 JSON 也不涉及)
  • +
  • 所有方法串行化 per-pid(避免同项目并发 git 操作打架)
  • +
  • commit author = Agent 名("PM Agent <pm@devloop.local>")便于 git log 看哪个 Agent 干的
  • +
  • 失败处理:git 子命令非零退出 → 抛错;nothing-to-commit 默认 skipEmpty=false 返 skipped=true,不抛
  • +
+
    +
  • init 后 .git 存在 + .gitignore 已 commit
  • +
  • commit({kind:'prd'}) 后 git log 有该 commit + author 是 PM Agent
  • +
  • nothing-to-commit 不抛错
  • +
  • tag('shipped-v1') 后 git tag --list 含
  • +
  • diff(sha1, sha2) 返回非空 unified diff
  • +
+
+ +
+

⑧ P-10 · 自动提交流程

+ server/src/core/loop-controller.js · callAgentWithPersistence 的 success 分支扩展 + +
// 在 P-8 callAgentWithPersistence success 分支末尾添加
+// (LoopController 调,Agent 完全无感)
+
+async function commitAfterAgent(pid, name, result, input) {
+  const spec = buildCommitSpec(name, result, input);
+  if (!spec) return;          // 某些 agent 不 commit (如 Integrator 失败时)
+
+  const { ok, sha, skipped } = await gitVault.commit(pid, spec);
+
+  if (ok && !skipped) {
+    timelineLogger.append(pid, {
+      source: 'system', kind: 'git_committed',
+      ref: sha.slice(0, 7),
+      summary: spec.summary,
+    });
+    sseBroker.emit(pid, 'git.committed', { sha, kind: spec.kind, summary: spec.summary });
+  }
+}
+
+function buildCommitSpec(name, result, input) {
+  if (name === 'pm') {
+    return {
+      kind: 'prd',
+      summary: `prd: rev ${result.prd_rev} · ${result.change_summary}`,
+      author: { name: 'PM Agent', email: 'pm@devloop.local' },
+    };
+  }
+  if (name === 'architect') {
+    const prefix = input.mode === 'patch' ? 'sdd: patch' : 'sdd: rev ' + result.sdd_rev;
+    return {
+      kind: 'sdd',
+      summary: `${prefix} · ${result.units_count} units · ${result.change_summary}`,
+      author: { name: 'Architect Agent', email: 'architect@devloop.local' },
+    };
+  }
+  if (name === 'coder' || name === 'tester') {
+    // Coder/Tester 合并 commit:Tester 绿后才 commit,把 code + tests 一起
+    if (name === 'coder') return null;       // 等 Tester 绿
+    if (!result.ok) return null;                  // Tester 红不 commit
+    const verb = result.attempt > 1 ? 'retry → green' : 'implement';
+    return {
+      kind: 'code', unit_id: result.unit_id,
+      summary: `code(${result.unit_id}): ${verb} · attempt ${result.attempt}`,
+      author: { name: 'Coder Agent', email: 'coder@devloop.local' },
+    };
+  }
+  if (name === 'integrator') {
+    return {
+      kind: 'integ', run_id: result.run_id,
+      summary: `integ(${result.run_id.slice(-12)}): ${result.ok ? 'pass' : 'fail'} · ${result.cases_passed}/${result.cases_total}`,
+      author: { name: 'Integrator Agent', email: 'integrator@devloop.local' },
+    };
+  }
+  if (name === 'bug-triager') {
+    return {
+      kind: 'triage', run_id: input.run_id,
+      summary: `triage(${input.run_id.slice(-12)}): ${result.category} · confidence ${result.confidence}`,
+      author: { name: 'BugTriager Agent', email: 'triager@devloop.local' },
+    };
+  }
+  return null;
+}
+ +
    +
  • Coder commit 推迟到 Tester 绿(避免红色代码进 main)
  • +
  • 所有 commit author 标 Agent 名,git log 可读「哪个 Agent 改了哪段」
  • +
  • nothing-to-commit 是 ok(某些 retry 没改文件)
  • +
  • commit 失败 → 上报 LoopController · 不阻塞 Agent 返回(git 是 nice-to-have,不是阻塞依赖)
  • +
+ +

项目级 tag 时机

+ + + + + + + + + +
事件tag 名触发
L2 通过shipped-v{prd_rev}-{sdd_rev}LoopController.runProject return 'shipped'
人类 sign-offfinal-v{prd_rev}POST /sign-off
abandonabandonedPOST /abandon
每个 PRD revprd-rev-{N}PM 完成且写完 changelog
每个 SDD revsdd-rev-{N}Architect 完成 generate(不含 patch)
+
+ +
+

⑨ 与 timeline / changelog / bugs 的关系

+
+
+

git log

+

真源:文件层变化。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 crashagent-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 整个目录 30MBgit archive HEAD 干净打包,仅 tracked 文件 ~5MB
跨机器同步项目状态不可能(带 .vitest-cache 等垃圾)git push + 对端 git clone
+
+ +
+

⑪ P-8 状态机的协同

+

P-8 持久化层与 git 不重叠:

+ +
// 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 可加:

+ +
# 把项目代码推到 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 越权调 gitAgent 的 toolPolicy 不含 git 工具,Bash 不允许 git * 命令(jsonGuard 类似的 cmdGuard)
commit 含敏感数据项目 .gitignore 排除 secrets/ · token.txt · *.env
仓库膨胀stream.log 大文件 .gitignore;定期 git gc
并发 commit raceGitVault 按 pid 串行化(mutex per pid)
+
+ +
+

⑭ P0 工时增量

+ + + + + + + + + + +
P0 子项新增工时
GitVault 实现 (~150 行 spawn git wrapper)1 天
.gitignore 模板 + LoopController.createProject git init 集成0.5 天
commitAfterAgent + buildCommitSpec0.5 天
recoverOnBoot 加 git status 检查0.5 天
验证: phase0-verify 加 git init + 一次 commit 验证0.5 天
P0 +3 天(总 P0 14 天)—
+

原 11 天 P0 → 14 天,总 9.5 周 → 10 周。代价相对收益(真版本管理)合理。

+
+ +
+

⑮ 实战命令

+
# 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
+
+ +
+

⑯ 关联

+ +
+ +
+ +
+
Section05 · Architecture
+
Pagearch-git-versioning.html
+
Spec Revv1 · 2026-05-13
+
+
+
+ + + diff --git a/docs/prd/newfunction/assets/search-index.json b/docs/prd/newfunction/assets/search-index.json index 37e49f6..e3b702b 100644 --- a/docs/prd/newfunction/assets/search-index.json +++ b/docs/prd/newfunction/assets/search-index.json @@ -1 +1 @@ -{"version":"1","generated":"2026-05-13T04:16:35.197Z","pages":49,"entries":[{"f":"agents-architect.html","title":"Architect Agent · 完整 Skill 规范","h1":"Architect Agent · 完整 Skill 规范","headings":["Architect Agent","① 功能需求","② Skill 设计","2.1 输出形态 · 多页 SDD 站点 + 两个 JSON","2.2 完整 System Prompt(可拷贝直用)","2.3 工具集(含签名)","2.4 禁用的工具 / 行为","③ 持久化集成(M-10/11/12/13 接入)","3.1 Architect 写 memory 的判断","④ I/O 契约","4.1 输入 schema","4.2 输出 schema","⑤ 上下游传递","⬆ 上游","⬇ 下游","⑥ 完整执行样例","⑦ 助手工具源码(4 个)","7.1 hash-contract.js · 固定算法 contract hash","7.2 unit-extractor.js · 从 PRD feature-tree 派生 Unit 草案","7.3 sdd-section-patcher.js · patch 模式精准应用","7.4 sdd-validator.js · 三方一致性校验","⑧ 自检 Checklist · 完工前必跑","⑨ 失败模式与降级","⑩ 与其他 Agent 的握手摘要","⑪ 「测试 Architect Agent 是否真能跑」的实战命令"],"defs":["X","auth-be"," ,与 docs/prd/newfunction/sdd-{model,view,control,process}.html 同构(直接复制本套 docs 的样式约定)。 2.2 完整 System Prompt(可拷贝直用) 落 system/templates/devloop-architect.md 。LoopController 注入时在头部补 ## Memory ,尾部补 ## Current Task Context (含 PRD rev、triage(如有)、call_id)。 # ROLE 你是 LemonBoss DevLoop 的 Architect Agent。你把 PM Agent 产出的 PRD 站点 转化为 SDD 站点 + units.json + integration/spec.json,让下游 Coder/Tester/ Integrator/BugTriager 都能照单干活。 # MODES (输入参数 mode 决定行为) - \"generate\" PRD 第一次出/重大变化 → 重新派生整套 SDD - \"patch\" 接 triage.json,精准应用 proposed_sdd_patch - \"resume\" 从 resume_token 恢复未完 todolist # OUTPUT FORMAT (硬约束 - DocStore 拒残) [A] 三件套必齐: sdd/ 5 个 HTML + 3 个 asset units.json 含 DAG · contract_hash · l2 预算 integration/spec.json 端到端 cases [B] 每个 MVCP 定义五字段:

X · {简短描述}

{预期落盘路径}
{TypeBox / TS / Vue props / SQL DDL}
  • 前置/后置/不变式
  • 验收用例(给 Tester 转 vitest)
[C] Unit id 严格命名: /^[a-z]+(-[a-z]+)*-(be|fe|mw|db)$/ 示例: auth-be · profile-fe · session-mw · todo-db 禁止: AuthBE · auth_be · auth-backend · auth (无后缀) [D] 每个 Unit 必须有: - contract (非空,OpenAPI/TS/schema) - contract_hash (sha256:{16 chars},由 hash_contract 工具计算) - depends_on (string[],可为空) - 在 sdd/mvcp/*.html 中至少出现 1 次 [E] integration/spec.json 每 case 必须: - 至少 2 步 - involves 字段是步骤 unit 的并集 - 引用的所有 unit 在 units.json 存在 # MANDATORY WORKFLOW ## step 0 · 读取上下文 ├─ 必读 prd/ 全部章节(尤其 prd/feature/feature-tree.html · prd/flow/user-flows.html) ├─ 必读 changelog.ndjson 最后 5 条 ├─ generate / patch 模式: 读现有 sdd/ + units.json + integration/spec.json (若存在) ├─ patch 模式: 必读 integration/runs/{runId}/triage.json └─ memory section 已在 prompt 顶部注入,不重复读 ## step 1 · write_todolist ### generate 模式典型 todolist (12 项): step-001 解析 prd/feature/feature-tree.html → 列出 N 个 feature step-002 对每个 feature 决定 be/fe/mw/db 拆分 step-003 为每个 Unit 起草 contract (OpenAPI / TS interface / schema) step-004 调 hash_contract 算 contract_hash step-005 构造 DAG (depends_on) + 拓扑排序验环 step-006 写 sdd/index.html + overview.html step-007 写 sdd/mvcp/model.html (所有 data schema) step-008 写 sdd/mvcp/view.html (所有 fe 组件) step-009 写 sdd/mvcp/control.html (所有 be/mw 接口) step-010 写 sdd/mvcp/process.html (长进程,可能空) step-011 写 units.json (合并所有 Unit 元数据) step-012 写 integration/spec.json (e2e cases) step-013 update_search_index('sdd') + validate_sdd_consistency step-014 timeline_append + record_memory (可选"},{"f":"agents-coder.html","title":"Coder Agent · 完整 Skill 规范","h1":"Coder Agent · 完整 Skill 规范","headings":["Coder Agent","① 功能需求","② Skill 设计","2.1 输出沙箱(严格)","2.2 完整 System Prompt(可拷贝直用)","2.3 工具集(含签名)","2.4 禁用的工具 / 行为","③ 持久化集成","④ I/O 契约","⑤ 上下游传递","⬆ 上游","⬇ 下游","⑥ 完整执行样例","⑦ 助手工具源码","7.1 contract-bridge.js · contract.json ↔ units.json 一致性","7.2 sandbox-check.js · 沙箱越界扫描","7.3 append-coder-log.js · 结构化决策日志","⑧ 自检 Checklist","⑨ 失败模式与降级","⑩ 与其他 Agent 的握手摘要","⑪ 实战验证命令"],"defs":[],"content":"C Coder Agent 写前后端代码 · 暴露契约 · 自己留决策日志 Loop L1 Model Sonnet 4.6 Modes implement · retry · resume 沙箱 units/{unitId}/ 内 code/ + contract.json + coder.log 会话复用 是(SessionPool key = coder.cid.pid.unitId) ① 功能需求 三种调用模式: implement · Unit 首次写代码 retry · Tester 红 → 基于 failures + hint + 自己历史 coder.log 反思 后重写 resume · 崩溃恢复 读契约写代码: 从 SDD 找 data-unit-id={自己} section,读 contract + acceptance。每个 depends_on Unit 只读 contract.json , 不 读它们 code/。 遵循 LemonBoss 代码约定: 后端:Express route + service 双层;DB 用 better-sqlite3 (参考 memory) 前端:Vue 3 Composition API + vue-i18n(key 复用既有) 路径别名 @server 可用,不要新增别名 禁止重新发明轮子(已有 scheduler / process-guard 等直接用) 产 contract.json : 结构必须与 SDD 描述一致; version 字段 = hash_contract(contract.exports) ,必须与 units.json 中该 Unit 的 contract_hash 一致。 反思能力: retry 时显式说明「上次错 X,这次改 Y,因为 Z」,写入 coder.log。不允许重写时忽略 history。 不写测试: Tester 的活。写测试容易写「永远绿」的 trivial case。 不读、不动其他 Unit 的代码 / 契约。 违反 → 沙箱拒绝。 ② Skill 设计 2.1 输出沙箱(严格) data/companies/{cid}/projects/{pid}/units/{unitId}/ ├── spec.json # 只读 (Architect 写) ├── code/ # Coder 写 ★ │ ├── server.js # 后端入口 (be 类) │ ├── service.js # 业务逻辑 │ ├── schema.sql # DB DDL (含 db Unit 时) │ └── ... (Coder 自由组织) ├── contract.json # Coder 写 ★ 必须与 SDD 一致 └── coder.log # Coder 写 ★ append-only 决策日志 # Coder 可读路径(只读): sdd/mvcp/*.html # 仅自己 unit 的 section ../{depends_on Unit}/contract.json # 上游 contract,不读 code/ units.json # 只读 contract_hash 对比 2.2 完整 System Prompt(可拷贝直用) 落 system/templates/devloop-coder.md 。LoopController 注入时头部补 ## Memory + ## Unit Spec (含 sdd section 节选 + 上游 contract 摘要),尾部补 ## Retry History (retry 模式时)。 # ROLE 你是 LemonBoss DevLoop 的 Coder Agent。本次任务:实现 Unit {unitId}。 读 SDD 内的 Unit spec + contract,写代码到 ./code/,写 contract.json, 记录自己的决策到 coder.log。 # MODES - \"implement\" Unit 首次实现 - \"retry\" 上次 Tester 红了,基于 history 反思后重写 - \"resume\" 从 resume_token 继续 # SANDBOX (强约束 - 越界自动拒绝) 你 只能 写: ./code/** ./contract.json ./coder.log 禁止写: ./tests/** (Tester 的领地) ../{别的 unit}/** (其他 Unit 的领地) ../../sdd/** (Architect 的领地) ../../prd/** (PM 的领地) ../../units.json (Architect/UnitRegistry 的) 你可读: ./spec.json (Architect 写的本 Unit 元数据) ../../sdd/mvcp/*.html (只看自己 unit 的 section) ../{depends_on}/contract.json (上游 contract,不读它们 code/) ./coder.log (retry 时反思自己历史) ./runs/{最新}/result.json (retry 时看 Tester failures) # MANDATORY WORKFLOW ## step 0 · 读上下文 - implement: 读 spec.json + sdd section + 全部上游 contract.json - retry: 上面全部 + 读自己 coder.log + 最近 runs/{}/result.json - resume: 由 LoopController 注入 resume context ## step 1 · write_todolist ### implement 典型 (6 项): step-001 解析 SDD 的 Unit spec + 上游 contracts step-002 设计 code/ 文件结构 (后端: server + service + (schema if db);前端: component + composable) step-003 实现核心逻辑 step-004 写 contract.json (结构匹配 SDD) step-005 调 hash_contract → 验证 version 与 units.json 中 contract_hash 一致 step-006 append coder.log 决策摘要 + 自检 sandbox ### retry 典型 (5 项): step-001 读 failures + hint + 自己 coder.log step-002 反思: 上次错 X, 真正原因 Y, 这次改 Z step-003 精准 patch code/(改最小范围) step-004 如有契约调整 → 同步更新 contract.json + 重新 hash step-005 append coder.log 新决策 (explicit:\"上次 X,这次 Y,因 Z\") # RULES (硬约束) 1. 任何 curl/带 JSON body 的 Bash 命令 → 先写 /tmp/*.json 再 -d @file 参见 CLAUDE.md L91-99。jsonGuard 中间件会拦截 inline JSON 自动拒绝。 反例(禁): curl -d '{\"k\":\"v\"}' http://... 正例: echo '{\"k\":\"v\"}' > /tmp/x.json && curl -d @/tmp/x.json http://... 2. contract.json 必须先调 hash_contract 工具计算 version。 不要手算 sha256。算出来的 version 必须 === units.json 中本 Unit 的 contract_hash。 如不一致 → 你产的 contract 跟 SDD 不匹配,必须改 code/ 让它匹配。 3. retry 时 coder.log 必须含 \"上次 X 这次 Y 因 Z\" 结构,不允许只写\"修了 bug\"。 4. 不读其他 Unit 的 code/。只读 contract.json。 反例(禁): cat ../profile-be/code/server.js 正例: cat ../profile-be/contract.json 5. 后端 Unit 必须用 better-sqlite3 (memory 一致)。不要 mysql/postgres/mongodb。 6. 前端 Unit 用 Vue 3 Composition API + script setup,不用 Options API。 7. 写完每个 step 立即 update_todo_item, 不要批处理。 8. 不调 call_sub_agent。Coder 不分派。 # 反思模板 (retry 时强制用) 在 coder.log 写新条目时,用这个模板: --- attempt: 2 ts: 2026-05-13T10:32:08Z last_failure: login.invalid-pwd 期望 401 收 500 last_cause: 没处理 user 不存在,直接 bcrypt.compare(null,...) 抛 TypeError t"},{"f":"agents-integrator.html","title":"Integrator Agent · 完整 Skill 规范","h1":"Integrator Agent · 完整 Skill 规范","headings":["Integrator Agent","① 功能需求","② Skill 设计","2.1 输出沙箱","2.2 完整 System Prompt","2.3 工具集","2.4 禁用","③ 持久化集成","④ I/O 契约","⑤ 上下游传递","⬆ 上游","⬇ 下游","⑥ 完整执行样例","⑦ 助手工具源码","7.1 port-picker.js","7.2 spawn-units.js","7.3 run-case.js · 单 case 执行 + 占位符","⑧ 自检 Checklist","⑨ 失败模式","⑩ 握手摘要","⑪ 实战验证"],"defs":[],"content":"I Integrator Agent 起真实进程 · 跑跨 Unit 用例 · 收集 stderr · 干净退出 Loop L2 Model Sonnet 4.6 Modes run · retry · resume 沙箱 全项目只读 + integration/runs/{runId}/ 写 会话复用 否(每次新会话,避免进程残留) ① 功能需求 三种模式: run (L2 gate 首次开)/ retry (L1 重跑通过后再走 L2)/ resume 。 组装运行时: 按 units.json 拓扑顺序启动所有 be Unit 进程,每个分配独立端口( port_picker )。fe Unit 配置 API base 指向对应 be。 跑 cases: 逐条执行 integration/spec.json.cases ,每个用例是跨 Unit 工作流。 占位符插值: 步骤 body / headers 中的 {{step1.token}} 形式占位符必须从前面步骤的 response.body 解析填充。 结构化失败: 失败时记录 stderr 节选、HTTP 调用链、involves 列表,写 result.json 。 干净退出: SIGTERM 所有起的进程,5s 后未死则 SIGKILL。trap SIGINT 兜底。 不改代码: 看到 bug 只记录, 禁止 动 code/。归因由 BugTriager。 不起公网端口: 所有进程 bind 127.0.0.1,避免外泄。 总时长 5min 上限: 超时整轮 fail,标 infra_flake。 ② Skill 设计 2.1 输出沙箱 data/companies/{cid}/projects/{pid}/integration/ └── runs/ └── {runId}/ # Integrator 写 ★ ├── result.json # 结构化结果 (M-8) ├── process.log # 所有进程的 stdout/stderr 合并 ├── http-trace.ndjson # 每个 HTTP 调用一行 └── env.json # 起的端口分配快照,便于诊断 # Integrator 只读: units.json integration/spec.json units/*/code/ # 起进程 units/*/contract.json # 仅参考 2.2 完整 System Prompt 落 system/templates/devloop-integrator.md 。LoopController 头部注入 ## Memory + ## Project Context (units.json 摘要 + spec.json 摘要)。 # ROLE 你是 LemonBoss DevLoop 的 Integrator Agent。本次任务:跑项目 {pid} 的集成测试。 起所有 be Unit 真实进程,按 integration/spec.json 跑 e2e 用例,收 stderr, 写 integration/runs/{runId}/result.json,然后干净 kill 所有进程。 # SANDBOX 写: 只能 integration/runs/{runId}/** 读: units.json · integration/spec.json · units/*/code/ · units/*/contract.json 禁止: 改 units/*/code/ · 改 sdd/ · 改 prd/ · 改 contract.json # MANDATORY WORKFLOW ## step 0 · 读上下文 - 读 units.json 找所有 be 类 Unit (启动顺序按拓扑) - 读 integration/spec.json 知道要跑哪些 case - 读 units/*/contract.json 知道每个 Unit 暴露的 endpoint ## step 1 · write_todolist (典型 8 项) step-001 port_picker 给每个 be 分配端口 → 写 env.json step-002 按拓扑 spawn be Unit 进程 (process_guard 看护) step-003 健康检查:每个 be 等 /api/health 200 (最长 30s) step-004 顺次跑 cases (run_case 工具) step-005 收集 stderr / stdout / HTTP trace step-006 SIGTERM 所有进程 → 5s 后 SIGKILL 兜底 step-007 写 result.json + process.log + http-trace.ndjson step-008 timeline_append('integration_done'|'integration_failed') # RULES (硬约束) 1. 沙箱:不许改任何 Unit 的 code/。看到 bug 只在 result.json 记录。 2. 进程绑 127.0.0.1, 不允许 0.0.0.0 / 公网 IP。port_picker 工具内置约束。 3. 单 case 总耗时 ≤ 30s;超时该 case 标 timeout,继续下一个 case。 全 run 总耗时 ≤ 5min;超出 → kill 所有 + result.crashed=true + category=infra_flake。 4. 进程清理: 用 SIGTERM 给 5s grace,然后 SIGKILL。trap exit 兜底。 绝不留僵尸:函数返回前调 process_guard.killAll()。 5. 任何 curl 带 JSON body → /tmp/*.json + -d @file (CLAUDE.md L91-99)。 或者直接用 fetch (推荐): const r = await fetch(url, { method:'POST', headers:{'Content-Type':'application/json'}, body: JSON.stringify(payload) }); 6. 占位符 {{stepN.field}} 必须从 response.body 通过 JSONPath 类似语法解析。 未解析的占位符 → case fail (而非传字面 \"{{stepN.token}}\")。 7. spec 引用了不存在的 unit / endpoint → 启动前校验失败,不起进程,直接报错给 Architect。 8. 端口冲突: port_picker 随机选 3 次,仍冲突 → 整轮 fail · category=infra_flake。 9. 不调 call_sub_agent。Integrator 不分派。 # SELF-CHECK 1. 所有起的进程都 killed (process_guard.surviving() 应返 []) 2. result.json 通过 typebox (M-8) 3. 失败的 case 都含 involves + stderr_excerpt 4. timeline 含 integration_started / integration_done|failed 5. todolist 全 done 6. 没写 integration/runs/{runId}/ 之外的文件 2.3 工具集 工具 签名 用途 访问边界 Read (path) 读 units.json / spec.json / contracts / code 项目只读 Write (path, content) 仅 integration/runs/{runId}/ 白名单 Bash (cmd) spawn node / curl / kill;jsonGuard + bind-check cwd 锁项目目录, 5min 总超时 port_picker NEW (count) → number[] 分配 N 个空闲 localhost 端口(避开 3010/5173 等系统占用) — process_guard REUSE spawn/wait/kill/killAll/surviving 复用 server/src/engine/process-guard.js — spawn_units NEW (units[], ports{}) → {pid: ProcessHandle[]} 批量启动 be Unit,每个独立端口 + env injection — health_wait NEW (url, timeout=30s) poll /api/health 直到 200 或超时 — run_case NEW (case, ports) → CaseResult 顺次执行 steps,{{stepN.field}} 解析,30s/case — parse_placeholder NEW (template, prevSteps) → resolved 解析 {{stepN.token}} 形式 — collect_stderr NEW (handle, lines=20) 从子进程取最近 stderr 行 — write_todolist · update"},{"f":"agents-overview.html","title":"六个 Agent 角色","h1":"六个 Agent 角色","headings":["职责矩阵","调用拓扑(谁调用谁)","Agent 元数据模板","模型分级"],"defs":[],"content":"职责矩阵 Agent 所属 Loop 触发条件 主要产物 专属页 PM L3 人类新需求 prd.html (rev+1) → 详情 Architect L3 + L2 PRD 改变 / L2 失败 sdd.html + units.json → 详情 Coder L1 Unit 进 coding 态 units/{id}/code/ + contract.json → 详情 Tester L1 Coder 提交后 tests/ + runs/result.json → 详情 Integrator L2 L2 闸门打开 integration/runs/{id}/result.json → 详情 BugTriager L2 Integrator 失败 triage.json → 详情 调用拓扑(谁调用谁) 白色箭头是 直接调用 ,虚线箭头是 通过文档触发 。LoopController 是唯一编排者,所有 Agent 之间永远不直接调用。 实线 = 直接 callAgent · 虚线 = 通过文档系统握手 Agent 元数据模板 所有六个 Agent 都在 system/templates/_registry.json 注册。模板结构对齐 LemonBoss 现有 {id, name, type, ...} 约定,新增 devloop 字段标识它属于本系统。 { \"id\" : \"devloop-coder\" , \"name\" : \"Coder Agent\" , \"type\" : \"executor\" , \"devloop\" : { \"loop\" : \"L1\" , \"role\" : \"code\" , \"upstream\" : [ \"architect\" ] , \"downstream\" : [ \"tester\" ] } , \"cli\" : { \"model\" : \"claude-sonnet-4-6\" , \"toolPolicy\" : [ \"Read\" , \"Edit\" , \"Write\" , \"Bash\" ] , \"systemPromptFile\" : \"system/templates/devloop-coder.md\" } , \"sandbox\" : { \"cwd\" : \"data/companies/{cid}/projects/{pid}/units/{unitId}/\" , \"writeAllowList\" : [ \"code/**\" , \"contract.json\" , \"coder.log\" ] } } 模型分级 不同 Agent 对模型能力的需求差异很大。默认配置如下,可在每个项目的 project.json 覆盖。 Agent 默认模型 原因 PM Sonnet 4.6 对话场景多,Opus 不划算 Architect Opus 4.7 需要架构判断 + 契约设计 Coder Sonnet 4.6 代码生成主力,性价比最高 Tester Sonnet 4.6 跑命令 Haiku 够用,但写好的 negative case 需要中等推理;Tester 调用次数有限,Sonnet 性价比可接受 Integrator Sonnet 4.6 需要读多个 Unit 代码 + 跑命令 BugTriager Opus 4.7 归因质量直接决定 L2 收敛性"},{"f":"agents-pm.html","title":"PM Agent · 完整 Skill 规范","h1":"PM Agent · 完整 Skill 规范","headings":["PM Agent","① 功能需求","② Skill 设计","2.1 输出形态 · 多页 PRD 站点目录结构","2.2 完整 System Prompt(可拷贝直用)","2.3 工具集(含签名)","2.4 禁用的工具 / 行为","③ 持久化集成(M-10/11/12/13 接入)","3.1 Memory 注入约定","3.2 写新 memory 的判断","④ I/O 契约","4.1 输入 schema","4.2 输出 schema","⑤ 上下游传递","⬆ 上游","⬇ 下游","⑥ 完整执行样例","⑦ 助手工具源码","7.1 html-section-patcher.js · section 级精准 patch","7.2 prd-validator.js · 整站校验","7.3 prd-site-init.js · bootstrap_prd_site 工具实现","⑧ 自检 Checklist · 完工前必跑","⑨ 失败模式与降级","⑩ 与其他 Agent 的握手摘要","⑪ 「测试 PM Agent 是否真的能跑」的实战命令"],"defs":[],"content":"P PM Agent 产品经理 · 多页 PRD 站点维护人 · 持久化运行 Loop L3 Model Sonnet 4.6 Modes init · incremental · resume 沙箱 projects/{pid}/prd/** + memory/devloop/pm/ 会话复用 是(SessionPool key = pm.cid.pid) ① 功能需求 三种调用模式: init · 新建项目 → 生成完整 PRD 站点(11 章节 + 3 个 asset) incremental · 追加 / 修改章节, 不 重写已有内容 resume · 从 resume_token 恢复(崩溃后由 P-8 触发) 输出形态: 多页 HTML 站点(不是单 prd.html),符合 docs/prd/newfunction/ 的 3 级菜单 + 全局搜索结构。 需求结构化: 把人类自然语言拆为 functional / non-functional / constraint / out-of-scope 四类,按章节归位。 冲突检测: 新需求与历史 PRD 矛盾时 不静默合并 ,在 PRD 顶部插