diff --git a/build-manifest.json b/build-manifest.json index 44eedec..7beaee8 100644 --- a/build-manifest.json +++ b/build-manifest.json @@ -2,13 +2,13 @@ "meta": { "source": "docs/prd/newfunction", "prdRev": "v0.2", - "extractedAt": "2026-05-13T05:14:45.606Z" + "extractedAt": "2026-05-13T05:26:32.145Z" }, "model": [ { "name": "ProjectMeta", "path": "data/companies/{cid}/projects/{pid}/project.json", - "shape": "// TypeBox schema\nconst ProjectMeta = Type.Object({\n id: Type.String({ pattern: '^p-[a-z0-9]{6}$' }),\n company_id: Type.String(),\n name: Type.String(),\n status: Type.Union([\n Type.Literal('prd_drafting'),\n Type.Literal('running'),\n Type.Literal('blocked'),\n Type.Literal('shipped'),\n Type.Literal('shipped-final'),\n Type.Literal('abandoned'),\n ]),\n created_at: Type.String({ format: 'date-time' }),\n updated_at: Type.String({ format: 'date-time' }),\n owner: Type.String(),\n token_budget: Type.Number({ default: 5_000_000 }),\n token_used: Type.Number({ default: 0 }),\n compressOldRuns: Type.Boolean({ default: false }),\n});", + "shape": "// TypeBox schema\nconst ProjectMeta = Type.Object({\n id: Type.String({ pattern: '^p-[a-z0-9]{6}$' }),\n company_id: Type.String(),\n name: Type.String(),\n\n // 项目根 (见 arch-project-root)\n mode: Type.Union([\n Type.Literal('sandbox'), // 默认,根在 data/.../projects/{pid}/\n Type.Literal('linked-new'), // 用户指定空目录\n Type.Literal('linked-existing'), // v2:复用已有 git repo\n ]),\n root_path: Type.String(), // 绝对路径,所有派生路径的源\n\n status: Type.Union([\n Type.Literal('prd_drafting'),\n Type.Literal('running'),\n Type.Literal('blocked'),\n Type.Literal('shipped'),\n Type.Literal('shipped-final'),\n Type.Literal('abandoned'),\n ]),\n created_at: Type.String({ format: 'date-time' }),\n updated_at: Type.String({ format: 'date-time' }),\n owner: Type.String(),\n token_budget: Type.Number({ default: 5_000_000 }),\n token_used: Type.Number({ default: 0 }),\n compressOldRuns: Type.Boolean({ default: false }),\n});", "contract": [ "id 全局唯一,跨 company 不重复", "status 只能按状态机合法转换(见 sdd-process.html · M-process-project)", @@ -295,14 +295,19 @@ { "name": "REST.createProject", "path": "server/routes/devloop.js", - "shape": "router.post('/projects', requireAuth, async (req, res) => {\n const { company_id, name, requirement } = req.body;\n const pid = await loopController.createProject({ company_id, name, owner: req.user.id });\n loopController.onHumanFeedback(pid, { text: requirement, origin: req.user.id });\n res.status(201).json({\n project_id: pid,\n status: 'prd_drafting',\n stream_url: `/api/devloop/projects/${pid}/stream`,\n });\n});", + "shape": "router.post('/projects', requireAuth, async (req, res) => {\n const {\n company_id, name, requirement,\n mode = 'sandbox', // 默认 sandbox\n target_path // linked-* 时必填\n } = req.body;\n\n const { pid, root_path } = await loopController.createProject({\n company_id, name, owner: req.user.id,\n mode, target_path,\n });\n loopController.onHumanFeedback(pid, { text: requirement, origin: req.user.id });\n res.status(201).json({\n project_id: pid,\n mode, root_path,\n status: 'prd_drafting',\n stream_url: `/api/devloop/projects/${pid}/stream`,\n });\n});", "contract": [ - "请求体 {company_id, name, requirement} 三字段必填", + "请求体必填:{company_id, name, requirement};可选:{mode, target_path}", + "mode 默认 sandbox;linked-new 时 target_path 必填且必须是绝对空目录或不存在;linked-existing v1 拒收(v2 启用)", + "target_path 不允许指向系统敏感目录(详见 arch-project-root §⑫)", "company_id 必须与 JWT 中的匹配(防越权)", - "立即返回 201 + project_id;PM Agent 异步起" + "立即返回 201 + project_id + root_path;PM Agent 异步起" ], "tests": [ - "合法请求 → 201 + 含 project_id", + "合法 sandbox 请求 → 201 + 含 project_id + root_path=data/.../projects/{pid}/", + "linked-new + target_path 合法 → 201 + root_path=用户指定路径 + .git 已初始化", + "linked-new + target_path 不存在父目录 → 400 拒绝", + "linked-new + target_path 是 /etc → 403 sensitive_path", "company_id 不匹配 JWT → 403", "缺字段 → 400 + 字段名" ] diff --git a/docs/prd/newfunction/arch-project-root.html b/docs/prd/newfunction/arch-project-root.html new file mode 100644 index 0000000..d9c4746 --- /dev/null +++ b/docs/prd/newfunction/arch-project-root.html @@ -0,0 +1,504 @@ + + + + +Project Root · LemonBoss PRD + + + + + + +
+ +
+
+
05 · ARCHITECTURE/Project Root
+

Project Root · 项目根目录是什么 · 在哪建

+

DevLoop 的所有产物(PRD 站点 · SDD 站点 · units/code/ · tests · runs · timeline · bugs · changelog · .git)都围绕一个 project root 组织。本页定义这个根是什么、如何创建、三种模式(sandbox / linked-new / linked-existing)、三种入口(REST / CLI / UI),以及各组件如何引用它而不硬编码路径。

+
+
+ +
+

① 「项目」的定义

+

一个 DevLoop 项目 = 一个目录 + 一个 project.json + 一棵 git 历史。这个目录就是 project root,是整个 DevLoop 流程的唯一锚点。

+ +

所有路径都从 root 派生:

+
+{project_root}/                          # 这就是「根」
+├── .git/                                # GitVault init
+├── .gitignore
+├── project.json                         # M-1 元数据 + root_path 自身
+├── prd/                                 # PM 写
+├── sdd/                                 # Architect 写
+├── units.json
+├── units/{unitId}/                      # Coder/Tester 写
+├── integration/                         # Integrator/Triager 写
+├── agent-state/                         # P-8 持久化
+├── timeline.ndjson · bugs.ndjson · changelog.ndjson
+├── stream.log
+└── history/                             # 归档 (.gitignore)
+
+

所有 Agent、所有工具、LoopController、GitVault 都按 {project_root}/... 找文件。没有任何代码硬编码 data/companies/...。这是关键设计。

+
+ +
+

② 三种项目模式

+ + + + + + + + + + + + + + + + + + + + + + + + + + +
moderoot_pathgit repov1 支持典型场景
sandbox
(默认)
自动生成:{server_data_dir}/companies/{cid}/projects/{pid}/自动 git init✅ v1不确定代码归宿 / 内部 demo / 学习 / 隔离实验
linked-new用户指定空目录或不存在的目录,比如 ~/code/auto-todo自动 git init 到该路径✅ v1已知代码最终归宿;想生成就直接落盘那里;之后可 git push
linked-existing用户指定已有 git repo 的子目录,比如 ~/code/my-app/services/auth/复用上级 git repo⚠️ v2给已存在的项目增量加新模块;冲突风险高,需要 git stash 等保护
+ +

v1 推荐:sandbox 是默认。linked-new 当作 power user 选项 — 它给你「自动 push 到 GitHub」的可能性,但也要承担「DevLoop 误覆盖了你 ~/code/ 下别的东西」的风险(沙箱机制阻止越界,但 user-chosen path 越界相对沙箱更难定义)。

+
+ +
+

③ project.json 加 root_path 字段

+

M-1 ProjectMeta 现在含 root_path(绝对路径)和 mode:

+ +
const ProjectMeta = Type.Object({
+  id:          Type.String({ pattern: '^p-[a-z0-9]{6}$' }),
+  company_id:  Type.String(),
+  name:        Type.String(),
+
+  // 新增字段
+  mode:        Type.Union([
+    Type.Literal('sandbox'),
+    Type.Literal('linked-new'),
+    Type.Literal('linked-existing'),
+  ]),
+  root_path:   Type.String(),                // 绝对路径,所有派生路径的源
+
+  status:      Type.Union([
+    Type.Literal('prd_drafting'),
+    Type.Literal('running'),
+    Type.Literal('blocked'),
+    Type.Literal('shipped'),
+    Type.Literal('shipped-final'),
+    Type.Literal('abandoned'),
+  ]),
+  ...
+});
+ +

所有组件读 path 都走 resolveProjectPath(pid) → project.root_path,而不是拼 data/companies/.../。

+
+ +
+

④ 路径解析服务(新组件)

+ server/services/path-resolver.js · 新增,~80 行 + +
module.exports.pathResolver = {
+  // 单例索引: pid → root_path
+  // 在 LoopController 启动时 walk(data/companies/) 建立
+  // 加: linked 项目通过 ~/.devloop/linked-projects.json 维护索引
+
+  init(): Promise<void>,
+
+  resolveProjectRoot(pid: string): string,    // 绝对路径,unknown pid 抛错
+  resolveUnitDir(pid, unitId): string,
+  resolvePrdSite(pid): string,
+  resolveSddSite(pid): string,
+  resolveAgentState(pid, agent): string,
+  resolveMemoryDir(pid, agent): string,   // 注意:memory 总在 company 级,不在 root 下
+  resolveTimelineLog(pid): string,
+  ...
+
+  // 反向
+  findProjectByPath(abs: string): { pid, root } | null,
+
+  // linked 项目注册
+  registerLinkedProject({ pid, root_path, mode }): Promise<void>,
+  unregisterLinkedProject(pid): Promise<void>,
+};
+ +

关键:所有用 projectsDir(pid) 的地方改成 pathResolver.resolveProjectRoot(pid)。这样切换 mode 是路径透明的。Agent 的 ctx.projectDir 注入也走这个。

+ +

linked 项目索引 ~/.devloop/linked-projects.json 形如:

+
{
+  "p-731042": {
+    "root_path": "/Users/yibo/code/auto-todo",
+    "mode": "linked-new",
+    "company_id": "c-lemonv",
+    "registered_at": "2026-05-13T11:31:00Z"
+  }
+}
+ +

sandbox 项目不需要注册(按惯例从 data/companies/.../projects/ 扫描即可)。

+
+ +
+

⑤ 三个创建入口

+ +

5.1 REST · 主入口

+
POST /api/devloop/projects
+{
+  "company_id": "c-lemonv",
+  "name": "Auto TODO",
+  "requirement": "做一个 TODO API + Vue 前端",
+
+  // 新增 - 可选,默认 mode=sandbox
+  "mode": "sandbox" | "linked-new" | "linked-existing",
+  "target_path": "/Users/yibo/code/auto-todo"  // linked-* 时必填
+}
+
+// 响应
+{
+  "project_id": "p-731042",
+  "mode": "linked-new",
+  "root_path": "/Users/yibo/code/auto-todo",
+  "status": "prd_drafting",
+  "stream_url": "/api/devloop/projects/p-731042/stream"
+}
+ +

5.2 CLI · 命令行

+
# 文件: scripts/devloop-new.js (NEW, ~80 行)
+
+# sandbox 模式 (默认)
+node scripts/devloop-new.js \
+  --name "Auto TODO" \
+  --requirement "做一个 TODO API + Vue 前端"
+
+# 等价于 mode=sandbox, root 自动到 data/.../projects/p-XXXXXX/
+# 输出: 🍋 Project p-731042 created (sandbox)
+#       Root: /Users/yibo/Documents/lemonAI/lemonboss/data/companies/c-default/projects/p-731042
+
+# linked-new 模式
+node scripts/devloop-new.js \
+  --path ~/code/auto-todo \
+  --name "Auto TODO" \
+  --requirement "做一个 TODO API + Vue 前端"
+
+# 输出: 🍋 Project p-731042 created (linked-new)
+#       Root: /Users/yibo/code/auto-todo
+#       git init done
+#       PM Agent dispatched, follow at http://localhost:3010/devloop/p-731042
+
+# 也支持 stdin 多行需求 (long requirement)
+cat my-spec.md | node scripts/devloop-new.js --path ~/code/x --name "X" --requirement -
+
+# 列出现有项目
+node scripts/devloop-new.js --list
+# 输出:
+#   p-731042  Auto TODO       linked-new     ~/code/auto-todo        running
+#   p-845001  Auth System     sandbox        data/.../p-845001       shipped
+ +

5.3 UI · Dashboard 的「+ NEW PROJECT」

+
+
+ + + + localhost:5173/devloop · "+ NEW PROJECT" clicked +
+ + + + + + + 新建项目 + L3 · HUMAN · INIT + + 项目名 + + Auto TODO + + 创建模式 + + ● sandbox (默认) + + ○ linked-new + + ○ linked-existing (v2) + + target path: 自动分配到 data/.../projects/p-XXXXXX/ + + 需求描述 + + 做一个 TODO API + 简单 Vue 前端 + 能新增、列出。 + + 25 / 4000 字 · Ctrl+Enter 提交 + + + 取消 + + 创建项目 + +
+ +

选 linked-new 时弹出 file picker 让用户选目录(或手填路径)。选 sandbox 时不显示 path 输入。

+
+ +
+

⑥ 创建流程 · 三模式各自 step-by-step

+ +
+
+ + + +
+ +
+
// LoopController.createProject({ company_id, name, requirement })
+
+step 1 · 生成 pid = "p-" + randomBase36(6)
+step 2 · root_path = `${dataDir}/companies/${company_id}/projects/${pid}/`
+step 3 · fs.mkdirSync(root_path, { recursive: true })
+step 4 · 写 project.json (含 root_path · mode='sandbox')
+step 5 · GitVault.init(pid) → 在 root_path 起 .git/
+step 6 · 写 .gitignore 模板 → 首个 commit "init: project ${pid} · ${name}"
+step 7 · pathResolver 不需要 register (按 data/ 路径约定可扫到)
+step 8 · LoopController.onHumanFeedback(pid, { text: requirement })
+         → PM Agent 异步启动
+
+// 全程在 server 进程内,无文件系统外部依赖
+
+ + + + +
+
+ +
+

⑦ 各组件如何引用 root_path

+

关键改造:没有任何地方硬编码 data/companies/...。一切走 pathResolver。

+ + + + + + + + + + + +
原来(硬编码)改成(path-resolver)
path.join(dataDir, 'companies', cid, 'projects', pid)pathResolver.resolveProjectRoot(pid)
projectsDir(pid) helperdelegate 到 pathResolver
Agent ctx.projectDir 注入由 callAgentWithPersistence 用 pathResolver 计算
data-layout.html 的目录示意明确写 {project_root}/ 而非 data/companies/.../projects/{pid}/
GitVault 内部 cwd每次操作前 pathResolver.resolveProjectRoot(pid)
Bash 工具的沙箱 cwd 锁同上
+ +

这是 P0 / P1 阶段必须的抽象化重构,否则 linked 模式没法实现。代价不大(~30 行改动散在 5-6 处)。

+
+ +
+

⑧ memory · 始终在 company 级

+

注意:memory 不跟 project root 移动。memory 是 company 范围长期记忆,所以它永远在:

+
+{server_data_dir}/companies/{cid}/memory/devloop/
+├── pm/*.md
+├── architect/*.md
+└── ...
+
+

哪怕 project 在 ~/code/auto-todo 下,memory 仍在 server 的 data/ 内。这样 linked 项目也能享受跨项目记忆,且 memory 不会跟着 git push 出去(隐私)。

+
+ +
+

⑨ 跨主机 / 跨机器场景

+

linked-new 模式启用后,项目根可以跨机器:

+
    +
  • 主机 A 上 DevLoop 创建项目,root = ~/code/x
  • +
  • git push origin main 推到 GitHub
  • +
  • 主机 B git clone 到任意位置
  • +
  • 主机 B 上重启 DevLoop server,不会识别这个项目(pathResolver 索引在 A 上)
  • +
  • 主机 B 用 node scripts/devloop-import.js --path ~/work/x 重新注册(NEW,v2 工具)
  • +
+

memory 不跨机器(属 company 数据);timeline / bugs / changelog 跟着 git 走(tracked);agent-state 不跟(.gitignore)— 这意味着对端续跑不能恢复 paused agents,要从干净状态重启。可接受。

+
+ +
+

⑩ 创建后 LoopController 接管的统一接口

+

不管哪种模式,创建后 LoopController 的视角无差异:

+ +
// 任何 mode 都走这个接口
+loopController.onHumanFeedback(pid, { text, origin });
+
+// LoopController 内部:
+// 1. pathResolver.resolveProjectRoot(pid) → 拿到根路径(不关心是 sandbox 还是 linked)
+// 2. 注入 ctx 给 Agent: ctx.projectDir = root
+// 3. Agent 通过 Write 工具落盘,沙箱白名单基于 root + 子路径(写 prd/** 等)
+// 4. GitVault.commit({ pid, ... }) → 自动在 root 上 git 操作
+ +

这就是路径透明的好处:业务代码无 mode 判断分支。

+
+ +
+

⑪ project.json 完整示例

+ +
// sandbox 项目
+{
+  "id": "p-731042",
+  "company_id": "c-lemonv",
+  "name": "Auto TODO",
+  "mode": "sandbox",
+  "root_path": "/Users/yibo/Documents/lemonAI/lemonboss/data/companies/c-lemonv/projects/p-731042",
+  "status": "running",
+  "created_at": "2026-05-13T10:31:00Z",
+  "updated_at": "2026-05-13T11:02:00Z",
+  "owner": "yibo",
+  "token_budget": 5000000, "token_used": 42018,
+  "compressOldRuns": false
+}
+
+// linked-new 项目
+{
+  "id": "p-845001",
+  "company_id": "c-lemonv",
+  "name": "Auto Auth System",
+  "mode": "linked-new",
+  "root_path": "/Users/yibo/code/auto-auth",    // 用户选的
+  "status": "running",
+  ...
+}
+
+ +
+

⑫ 路径白名单(沙箱安全)

+

linked 模式给了用户灵活性,但也带来风险。沙箱必须严格:

+ + + + + + + + + + + +
规则实现
拒绝 root_path = 系统敏感路径黑名单:/, /etc, /System, /usr, /var, /Library, /Applications, $HOME/.ssh, $HOME/.aws, $HOME/Downloads(防误操作)
拒绝 root_path 不可写fs.accessSync(W_OK) 检查
拒绝 root_path 已存在且非空(linked-new)fs.readdirSync 检查
Agent 写白名单走 root 相对路径writeAllowList: ["prd/**", "sdd/**", "units/**", ...] · resolve 后必须以 root_path 开头
跨项目读写禁止:任何 ../{别的项目} 路径直接拒绝
memory 跨项目读白名单例外:memory/devloop/ 在 company 级允许只读
+
+ +
+

⑬ 实战命令

+ +
# 启动 server
+PORT=3010 node server/index.js
+
+# 方式 A · REST sandbox
+echo '{"company_id":"c-lemonv","name":"todo-demo","requirement":"做一个 TODO API"}' > /tmp/req.json
+curl -X POST http://localhost:3010/api/devloop/projects -d @/tmp/req.json
+# → { "project_id": "p-731042", "mode": "sandbox", "root_path": "/.../data/companies/c-lemonv/projects/p-731042" }
+
+# 方式 B · REST linked-new
+echo '{"company_id":"c-lemonv","name":"Auto TODO","requirement":"做一个 TODO API","mode":"linked-new","target_path":"/Users/yibo/code/auto-todo"}' > /tmp/req.json
+curl -X POST http://localhost:3010/api/devloop/projects -d @/tmp/req.json
+# → { "project_id": "p-845001", "mode": "linked-new", "root_path": "/Users/yibo/code/auto-todo" }
+# 在 ~/code/auto-todo 下立即看到 .git · .gitignore · project.json · prd/ 等开始生成
+
+# 方式 C · CLI
+node scripts/devloop-new.js \
+  --path ~/code/auto-todo \
+  --name "Auto TODO" \
+  --requirement "做一个 TODO API"
+
+# 方式 D · UI (假设 frontend 启动)
+# 浏览器: http://localhost:5173/devloop · 点 "+ NEW PROJECT" · 填表 · 提交
+
+# 列出现有项目 (跨 sandbox + linked)
+node scripts/devloop-new.js --list
+curl http://localhost:3010/api/devloop/projects
+# → [{pid, name, mode, root_path, status}, ...]
+
+# 删除项目 (linked 模式不删用户目录,只 unregister + 移除 sandbox 索引)
+curl -X DELETE http://localhost:3010/api/devloop/projects/p-731042
+# sandbox: 整 data/.../projects/p-XXXXXX/ 删除
+# linked:  只从 ~/.devloop/linked-projects.json unregister,用户目录保留
+
+ +
+

⑭ 工时增量

+ + + + + + + + + + + +
任务工时归属阶段
pathResolver 实现 + 写硬编码迁移1.5 天P0 (前置,后续 GitVault/工具都依赖)
project.json schema 加 mode + root_path0.5 天P0
POST /projects 加 mode + target_path 参数0.5 天P1
scripts/devloop-new.js CLI1 天P1
scripts/devloop-import.js (v2)—v2
UI 新建项目 modal1 天P4
合计 v14.5 天分摊到 P0/P1/P4
+

P0 时间预算从 13.5 天 → 15.5 天(+2 天 path-resolver + schema),P1 从 5 天 → 6.5 天(+1.5 天 mode 参数 + CLI)。总工时 10 周 → 10.5 周。

+
+ +
+

⑮ 关联

+ +
+ +
+ + +
+
+ + + diff --git a/docs/prd/newfunction/assets/search-index.json b/docs/prd/newfunction/assets/search-index.json index e3b702b..0529a20 100644 --- a/docs/prd/newfunction/assets/search-index.json +++ b/docs/prd/newfunction/assets/search-index.json @@ -1 +1 @@ -{"version":"1","generated":"2026-05-13T05:14:45.662Z","pages":50,"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}
[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 顶部插