# Coder Agent · 完整 Skill 规范
> _Shell-stripped: preserves full prompt content for Agent system templates_
> DevLoop 的代码主力。读 SDD 内对应 Unit 的 `
` + 上游 Unit 的 `contract.json`,产 `units/{id}/code/**` + `contract.json` + `coder.log`。是 L1 内被调用频率最高的 Agent。
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)
## ① 功能需求
1. **三种调用模式:**
- `implement` · Unit 首次写代码 2. `retry` · Tester 红 → 基于 failures + hint + 自己历史 coder.log **反思**后重写 3. `resume` · 崩溃恢复 4. **读契约写代码:**从 SDD 找 `data-unit-id={自己}` section,读 contract + acceptance。每个 depends_on Unit 只读 `contract.json`,**不**读它们 code/。 5. **遵循 LemonBoss 代码约定:** - 后端:Express route + service 双层;DB 用 `better-sqlite3`(参考 memory) 6. 前端:Vue 3 Composition API + vue-i18n(key 复用既有) 7. 路径别名 `@server` 可用,不要新增别名 8. 禁止重新发明轮子(已有 `scheduler / process-guard` 等直接用) 9. **产 `contract.json`:**结构必须与 SDD 描述一致;`version` 字段 = `hash_contract(contract.exports)`,必须与 units.json 中该 Unit 的 contract_hash 一致。 10. **反思能力:**retry 时显式说明「上次错 X,这次改 Y,因为 Z」,写入 coder.log。不允许重写时忽略 history。 11. **不写测试:**Tester 的活。写测试容易写「永远绿」的 trivial case。 12. **不读、不动其他 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 this_fix: service.js login() 先 SELECT user, user 缺失或 pwd 错统一 return 401 rationale: 401 是"凭据无效"的语义,不该用 500 (服务器错误) 暴露用户不存在 changed_files: [code/auth-service.js] --- # SELF-CHECK (完工前必跑) 1. contract.json hash 与 units.json[unitId].contract_hash 一致 2. code/ 下文件能 node --check 通过 (be); 前端能 vite build dry-run 通过 (fe) 3. coder.log 这次有新一条决策 (implement 1 条, retry 模板格式) 4. 没有写 code/ 沙箱外的文件 5. todolist 全 done # OUTPUT VOICE - 代码风格遵循 LemonBoss 既有 (扫 server/services/* 仿) - 不写大段注释,只在 WHY 非显然时一行 - 不写"完美""强大",数字胜过形容词 - coder.log 用人话不是 emoji ```
### 2.3 工具集(含签名)
| 工具 | 签名 | 用途 | 访问边界 | | --- | --- | --- | --- | | `Read` | (path) | 读 spec/sdd/上游 contract/历史 log | 沙箱白名单 | | `Edit` | (path, old, new) | 精准修改(retry 时常用) | code/** + contract.json | | `Write` | (path, content) | 新文件 | code/** + contract.json + coder.log | | `Bash` | (cmd) | 跑 npm install · lint · syntax check · curl | cwd 锁 units/{unitId}/, jsonGuard 强制 | | `hash_contract` NEW | (contract) → "sha256:..." | 同 Architect 工具,复用 | — | | `contract_bridge` NEW | (pid, unitId) → {ok, expected, actual} | 对比 contract.json 与 units.json 中 hash | — | | `sandbox_check` NEW | (pid, unitId) → {ok, violations[]} | 扫所有本 Unit 文件确认无越界 | — | | `append_coder_log` NEW | (decision: object) | 结构化追加 coder.log(含 attempt/last_failure/this_fix 字段) | coder.log only | | `write_todolist` | (items) | 同 PM/Architect | agent-state/ | | `update_todo_item` | (id, status, output_ref?) | 同上 | — | | `update_runtime_state` | (step, total) | 同上 | — | | `timeline_append` | (kind, ref, summary) | 同上 · 典型 kind: unit_coded · code_patched | — | | `record_memory` | (topic, content, relevance) | 同上 · memory/devloop/coder/ | memory/devloop/coder/ |
### 2.4 禁用的工具 / 行为
- **不允许 call_sub_agent** — Coder 不分派
- **不允许 inline JSON Bash** — jsonGuard 强制拦截
- **不允许写**:tests/ · ../别的 unit/ · sdd/ · prd/ · units.json · integration/
- **不允许读**其他 Unit 的 code/(仅 contract.json)
- **不允许跳过反思** — retry 模式 coder.log 必含 "last_failure/this_fix" 字段
## ③ 持久化集成
与 PM/Architect 同模式:开工前 LoopController 注入 memory,结束前可写新 memory(少用,Coder memory 通常是**代码风格偏好**)。todolist + runtime + timeline 同步。
**retry 模式的特殊性:**`history` 字段由 LoopController 注入到 prompt 尾部,含上一次 failure + hint + 旧 coder.log 摘要。**Coder 不主动读 history**,从 prompt 拿。
**Coder memory 典型话题:**
- "never use callback style, prefer async/await"
- "401 vs 500: 凭据问题 401, 服务器内部错 500"
- "sqlite better-sqlite3 同步 API, 不要 await db.query"
## ④ I/O 契约
```
const CoderInput = Type.Object({
pid: Type.String(),
unit_id: Type.String(),
mode: Type.Union([Type.Literal('implement'),
Type.Literal('retry'),
Type.Literal('resume')]),
// retry 模式由 LoopController 注入
history: Type.Optional(Type.Array(Type.Object({
attempt: Type.Number(),
my_decision: Type.String(), // 上次的反思条目
tester_said: Type.Array(Type.Any()), // 上次 failures
}))),
reflect_hint: Type.Optional(Type.String()), // Tester 给的 hint
resume_token: Type.Optional(Type.String()),
call_id: Type.String(),
company_id: Type.String(),
});
const CoderOutput = Type.Object({
ok: Type.Boolean(),
mode: Type.String(),
unit_id: Type.String(),
attempt: Type.Number(),
files_written: Type.Array(Type.String()),
contract_hash: Type.String(),
contract_match_sdd: Type.Boolean(),
decision: Type.Object({ // 本次 coder.log 主条目摘要
last_failure: Type.Optional(Type.String()),
this_fix: Type.String(),
rationale: Type.String(),
changed_files: Type.Array(Type.String()),
}),
sandbox_violations: Type.Array(Type.String()), // 必为 []
duration_ms: Type.Number(),
tokens_used: Type.Number(),
});
```
## ⑤ 上下游传递
#### ⬆ 上游
- **Architect** · 通过 spec.json + sdd section
- **上游 Unit 的 Coder**(已完成)· 通过 ../{dep}/contract.json
- **LoopController** · 注入 input + memory + (retry 时) history
- **Tester**(retry 时间接)· 通过 runs/{}/result.json failures
#### ⬇ 下游
- **Tester** · 读 code/ + contract.json 写测试
- **下游 Unit 的 Coder** · 读我的 contract.json(不读 code)
- **Integrator**(L2 时)· 起 code/ 真实进程跑 e2e
- **UnitRegistry** · 接 status: coding → testing
- **BugTriager**(L2 失败时)· 读 code/ + contract.json + coder.log 做归因
## ⑥ 完整执行样例
```
# ─────────── INPUT ───────────
{
"pid": "p-test001",
"unit_id": "todo-be",
"mode": "implement",
"call_id": "call-c-001",
"company_id": "c-test"
}
# INJECTED MEMORY
## Memory (from prior projects)
### sqlite-conventions (relevance: 0.93)
- better-sqlite3 是同步 API,不要 await
- prepared statement 优先于字符串拼接
- 写在 service 层,不直接在 route 用
### http-status (relevance: 0.88)
- 400 客户端语法错; 401 凭据无效; 404 不存在; 409 冲突
- 201 Created 含 Location header
# INJECTED Unit Spec (摘自 sdd/mvcp/control.html)
## Unit Spec: todo-be
contract:
POST /api/todos body: { title: string (1..200) }
→ 201 { id, title, created_at }
→ 400 { error: "title_required" } | { error: "title_too_long" }
GET /api/todos → 200 { items: [{ id, title, created_at }, ...] }
acceptance:
- 创建后能列出
- 空 title 返 400
- 200+ 字符 title 返 400
# ─────────── 内部执行 ───────────
# step 1 · write_todolist (implement 6 项)
# step 2 · 设计文件结构
# code/server.js Express app + route
# code/service.js 业务逻辑 + DB
# code/schema.sql DDL (schema 简单,内联在 service)
# step 3 · Write code/service.js
const Database = require('better-sqlite3');
const db = new Database(':memory:');
db.exec(`CREATE TABLE todos (id INTEGER PRIMARY KEY AUTOINCREMENT,
title TEXT NOT NULL, created_at TEXT DEFAULT CURRENT_TIMESTAMP)`);
const insertStmt = db.prepare('INSERT INTO todos (title) VALUES (?)');
const listStmt = db.prepare('SELECT id, title, created_at FROM todos ORDER BY id DESC');
function create(title) {
if (!title || title.length === 0) return { error: 'title_required' };
if (title.length > 200) return { error: 'title_too_long' };
const r = insertStmt.run(title);
return listStmt.get(r.lastInsertRowid) || { id: r.lastInsertRowid, title };
}
module.exports = { create, list: () => ({ items: listStmt.all() }) };
# step 3 · Write code/server.js
const express = require('express');
const svc = require('./service');
const app = express();
app.use(express.json());
app.post('/api/todos', (req, res) => {
const r = svc.create(req.body.title);
if (r.error) return res.status(400).json(r);
res.status(201).json(r);
});
app.get('/api/todos', (req, res) => res.json(svc.list()));
module.exports = app;
# step 4 · Write contract.json
{
"unit_id": "todo-be",
"version": "sha256:4a7c8e2f9d1b3a5e",
"exports": {
"http": [
{ "method":"POST", "path":"/api/todos",
"request":{"title":"string"},
"responses":{
"201":{"id":"number","title":"string","created_at":"string"},
"400":{"error":"\"title_required\" | \"title_too_long\""}
} },
{ "method":"GET", "path":"/api/todos", ... }
]
},
"consumes": []
}
# step 5 · contract_bridge → { ok: true, expected: "sha256:4a7c8e2f9d1b3a5e", actual: 同 }
# step 6 · append_coder_log
append_coder_log({
attempt: 1,
this_fix: "首次实现 todo-be:Express + better-sqlite3 内存库 + 双端点",
rationale: "按 SDD 拆 server/service 双层; 内存库便于 Tester 跑 vitest",
changed_files: ["code/server.js", "code/service.js"]
})
# OUTPUT
{
"ok": true, "mode": "implement", "unit_id": "todo-be", "attempt": 1,
"files_written": ["code/server.js", "code/service.js", "contract.json", "coder.log"],
"contract_hash": "sha256:4a7c8e2f9d1b3a5e",
"contract_match_sdd": true,
"decision": { "this_fix": "首次实现...", ... },
"sandbox_violations": [],
"duration_ms": 38_412, "tokens_used": 5_120
}
```
```
# ─────────── INPUT (retry mode) ───────────
{
"pid": "p-test001", "unit_id": "todo-be", "mode": "retry",
"history": [
{
"attempt": 1,
"my_decision": "用 better-sqlite3 内存库 + 双端点",
"tester_said": [
{ "test":"empty title", "expected":400, "got":500,
"stack":"TypeError: title.length undefined" }
]
}
],
"reflect_hint": "title 字段缺失时 req.body.title 是 undefined,先判存在性",
...
}
# INJECTED Retry History (LoopController 拼到 prompt 尾部)
## Retry History
attempt 1 决策: "用 better-sqlite3 内存库 + 双端点"
attempt 1 失败:
- test "empty title" 期望 400 收 500
- stack: TypeError: title.length undefined
Tester 提示: "title 字段缺失时 req.body.title 是 undefined,先判存在性"
# step 1 · todolist 5 项 (retry 模板)
# step 2 · 反思
# 上次错: service.create(title) 内 if (!title || title.length === 0)
# 当 title=undefined 时 title.length 抛 TypeError
# 真正原因: undefined 没短路到 !title (空字符串才会),
# 但 || 链对 undefined 是 truthy 之前先取 .length 抛错
# 错!实际上 !undefined 是 true → 短路返 title_required
# 重新看 stack: route 层先解构 body.title,如果 body 整个缺失则报错
# ✓ 真正错: req.body 是 undefined (express.json() 没匹配 content-type)
# step 3 · Edit code/server.js
# - 兜底: const { title } = req.body || {};
# step 5 · append_coder_log
append_coder_log({
attempt: 2,
ts: "2026-05-13T10:32:08Z",
last_failure: "empty title 期望 400 收 500",
last_cause: "req.body 为 undefined 时解构 title 抛 TypeError",
this_fix: "route 层 req.body || {} 兜底,再传入 service",
rationale: "express.json() 对非 application/json 请求不解析,body 是 undefined",
changed_files: ["code/server.js"]
})
# OUTPUT
{
"ok": true, "mode": "retry", "attempt": 2,
"files_written": ["code/server.js", "coder.log"],
"contract_hash": "sha256:4a7c8e2f9d1b3a5e", // hash 不变 (只改实现)
"contract_match_sdd": true,
"decision": {
"last_failure": "empty title 期望 400 收 500",
"this_fix": "route 层 req.body || {} 兜底",
"rationale": "express.json() 对非 JSON 请求 body 是 undefined",
"changed_files": ["code/server.js"]
},
...
}
```
```
# ─────────── INPUT (resume) ───────────
{
"pid": "p-test001", "unit_id": "todo-be",
"mode": "resume",
"resume_token": "rt-coder-...-step3",
...
}
# LoopController 已注入 agent-state/coder.json + coder-todo.json
# 上次进度: step-001/002/003 done, step-004 doing 时崩
# code/server.js, code/service.js 已写
# contract.json 未写
# Coder 自动续:
update_runtime_state(current_step=3, total_steps=6)
# 从 step-004 继续
# step-004: 写 contract.json
# step-005: contract_bridge 验证
# step-006: append_coder_log + sandbox check
# OUTPUT (同 Sample A 但 files_written 不含已写的)
```
## ⑦ 助手工具源码
### 7.1 `contract-bridge.js` · contract.json ↔ units.json 一致性
```
// server/services/agent-tools/contract-bridge.js · ~50 行
const fs = require('fs');
const path = require('path');
const { hashContract } = require('./hash-contract');
function verifyContractBridge(pid, unitId) {
const projDir = projectsDir(pid);
const contractPath = path.join(projDir, 'units', unitId, 'contract.json');
const unitsPath = path.join(projDir, 'units.json');
if (!fs.existsSync(contractPath))
return { ok: false, reason: 'contract.json missing' };
const contract = JSON.parse(fs.readFileSync(contractPath, 'utf8'));
const units = JSON.parse(fs.readFileSync(unitsPath, 'utf8'));
const unit = units.units.find(u => u.id === unitId);
if (!unit) return { ok: false, reason: 'unit not in units.json' };
const expected = unit.contract_hash;
const recomputed = hashContract(contract);
// 还检查 contract.version 自报与重算一致
if (contract.version !== recomputed) {
return {
ok: false,
reason: 'contract.version self-inconsistent',
claimed: contract.version, actual: recomputed,
};
}
if (recomputed !== expected) {
return {
ok: false,
reason: 'contract does not match SDD-declared hash',
expected, actual: recomputed,
hint: '你的 contract 与 SDD 描述不一致,改 code 让契约对齐',
};
}
return { ok: true, hash: recomputed };
}
module.exports = { verifyContractBridge };
```
### 7.2 `sandbox-check.js` · 沙箱越界扫描
```
// server/services/agent-tools/sandbox-check.js · ~60 行
const fs = require('fs');
const path = require('path');
/**
* 给定 (pid, unitId, since:timestamp) → 扫描自 since 起被 mtime 修改过的文件,
* 任何在沙箱外的写盘 → violations。
* 由 LoopController 在 callAgent 结束后调用。
*/
function scanSandbox({ pid, agent, unitId, since }) {
const projDir = projectsDir(pid);
const allowed = [
path.join(projDir, 'units', unitId, 'code'),
path.join(projDir, 'units', unitId, 'contract.json'),
path.join(projDir, 'units', unitId, 'coder.log'),
path.join(projDir, 'agent-state'), // runtime/todolist 写允许
path.join(projDir, 'timeline.ndjson'),
];
const violations = [];
walk(projDir, (f) => {
const st = fs.statSync(f);
if (st.mtimeMs < since) return;
const within = allowed.some(a => f.startsWith(a));
if (!within) violations.push(f);
});
return { ok: violations.length === 0, violations };
}
function walk(dir, cb) {
for (const e of fs.readdirSync(dir, { withFileTypes: true })) {
const p = path.join(dir, e.name);
if (e.isDirectory()) walk(p, cb);
else cb(p);
}
}
module.exports = { scanSandbox };
```
### 7.3 `append-coder-log.js` · 结构化决策日志
```
// server/services/agent-tools/append-coder-log.js · ~30 行
const fs = require('fs');
const path = require('path');
function appendCoderLog(pid, unitId, decision) {
const required = ['attempt', 'this_fix', 'rationale', 'changed_files'];
if (decision.attempt > 1) required.push('last_failure', 'last_cause');
const missing = required.filter(k => !decision[k]);
if (missing.length)
throw new Error(`coder.log decision missing fields: ${missing.join(',')}`);
const p = path.join(projectsDir(pid), 'units', unitId, 'coder.log');
const block = '---\n'
+ Object.entries(decision)
.map(([k, v]) => `${k}: ${typeof v === 'object' ? JSON.stringify(v) : v}`)
.join('\n')
+ '\n---\n\n';
fs.appendFileSync(p, block);
return { ok: true };
}
module.exports = { appendCoderLog };
```
## ⑧ 自检 Checklist
| # | 检查项 | 命令 / 工具 | 通过判定 |
| --- | --- | --- | --- |
| 1 | contract.json hash 一致 | `verifyContractBridge(pid, unitId)` | ok=true |
| 2 | code/ 文件能 parse | be: `node --check code/server.js`;fe: vite dry-run | 0 退出 |
| 3 | coder.log 新条目 | file mtime > call start | true |
| 4 | retry 模式 coder.log 含 last_failure / this_fix | regex 校验最末条目 | 命中 |
| 5 | 沙箱无越界 | `scanSandbox` | violations=[] |
| 6 | 不读其他 Unit code/ | session log review | 无 ../{xxx-(be|fe|mw|db)}/code 路径出现 |
| 7 | todolist 全 done | read agent-state/coder-todo.json | 全 done |
| 8 | timeline 含 unit_coded | tail timeline.ndjson | 命中 |
| 9 | agent-state.coder.json 终态 | read M-10 | status=done |
| 10 | 无 Bash inline JSON | jsonGuard 中间件实时拦截 (运行时无遗漏) | 0 拒绝事件 |
> _任一不过 → 不输出 ok=true。retry 最多 2 次内必过。否则报 validation_failed。_
## ⑨ 失败模式与降级
| 场景 | 检测 | 降级 |
| --- | --- | --- |
| 试图写 ../别的 unit/ | Write 工具沙箱拒绝 | Coder 收 error,换路径 |
| Bash inline JSON | jsonGuard 拦截 | 提示参考 CLAUDE.md L91-99 |
| contract.json hash 与 SDD 不匹配 | contract_bridge | 视为 fail;Coder 必须改 code 让契约对齐 |
| 反复同样错误 ≥ 3 次 | retry_budget 累计 | UnitRegistry.block + SSE 求人 |
| code/ 文件 parse 失败 | self-check 步骤 | retry 一次重写 |
| retry 模式 coder.log 缺反思字段 | appendCoderLog 内置校验 | 抛 error,Coder 必须补 |
| 读取 sdd 时找不到自己的 section | cheerio grep | 报 SDD 不一致,触发 L3 让 Architect 重生 |
| memory 注入太多挤爆 prompt | LoopController 控制 top 5 | — |
| npm install 超时 | Bash 工具超时(10min) | retry;持续失败标 infra_flake |
## ⑩ 与其他 Agent 的握手摘要
| 对方 | Coder 给的 | Coder 收的 |
| --- | --- | --- |
| **Architect** | (无) | spec.json + sdd section |
| **上游 Unit Coder** | (无) | contract.json(只读) |
| **Tester** | code/** + contract.json | retry 时 failures + hint |
| **下游 Unit Coder** | contract.json | (无) |
| **Integrator** | code/** (运行时进程) | (无) |
| **BugTriager** | code/** + contract.json + coder.log (只读) | (无) |
| **LoopController** | CoderOutput JSON | CoderInput · history (retry) |
| **memory/devloop/coder/** | 新 .md | 启动注入 |
## ⑪ 实战验证命令
```
# 前置: Architect 已跑通,units.json 含 todo-be unit, sdd/ 含 todo-be section
# 1. 注册模板
# system/templates/devloop-coder.md ← 本页 §2.2
# system/templates/_registry.json ← 加 'devloop-coder'
# 2. 准备输入
echo '{"pid":"p-test001","unit_id":"todo-be","mode":"implement","call_id":"call-coder-1","company_id":"c-test"}' > /tmp/coder-impl.json
# 3. 调 Coder (implement)
curl -X POST http://localhost:3010/api/devloop/_test/run-agent/coder \
-H "Content-Type: application/json" \
-d @/tmp/coder-impl.json
# 4. 期望:30-60 秒内
# data/companies/c-test/projects/p-test001/units/todo-be/
# ├── code/server.js, code/service.js
# ├── contract.json (hash 与 units.json 一致)
# └── coder.log (1 个 --- block)
# 5. 三方验证
node -e "console.log(require('./server/services/agent-tools/contract-bridge').verifyContractBridge('p-test001', 'todo-be'))"
# 期望: { ok: true, hash: 'sha256:...' }
node -e "console.log(require('./server/services/agent-tools/sandbox-check').scanSandbox({pid:'p-test001',unitId:'todo-be',since:Date.now()-300000}))"
# 期望: { ok: true, violations: [] }
node --check data/companies/c-test/projects/p-test001/units/todo-be/code/server.js
# 期望: 0 退出码
# 6. 模拟 retry (假装 Tester 已经红了)
echo '{"pid":"p-test001","unit_id":"todo-be","mode":"retry","history":[{"attempt":1,"my_decision":"...","tester_said":[{"test":"x","expected":400,"got":500,"stack":"TypeError..."}]}],"reflect_hint":"req.body 可能 undefined","call_id":"call-coder-2","company_id":"c-test"}' > /tmp/coder-retry.json
curl -X POST http://localhost:3010/api/devloop/_test/run-agent/coder -d @/tmp/coder-retry.json
# 期望: coder.log 多一个 block,含 last_failure / this_fix / rationale 字段
# code/server.js 被 Edit 过 (mtime > 上次)
# contract_hash 不变 (除非真的契约改了)
```
**这一组全过 = Coder skill 验证通过。**下一步 Tester Agent 跑同 Unit 检测能不能让代码红/绿,关上 L1 反思闭环。