# REST API + 代码骨架
> 新增 5 个 REST 端点,全部挂在 `/api/devloop/*`。骨架代码合计约 700 行,分布在 3 个新文件 + 1 处修改。
## 端点清单
| 方法 | 路径 | 说明 | 触发的 Loop |
| --- | --- | --- | --- |
| `POST` | `/api/devloop/projects` | 创建项目,传入初始需求 | L3 启动 |
| `POST` | `/api/devloop/projects/:pid/requirement` | 追加人类反馈,触发 PRD 增量更新 | L3 |
| `POST` | `/api/devloop/projects/:pid/run` | 启动 / 续跑 LoopController | L1 + L2 |
| `GET` | `/api/devloop/projects/:pid/state` | 返回 units.json + 当前 loop 阶段 | UI 轮询 |
| `GET` | `/api/devloop/projects/:pid/stream` | SSE — 实时推送 Agent 输出 / 状态变更 | UI 流式 |
| `POST` | `/api/devloop/projects/:pid/sign-off` | 人类显式 sign-off,状态置 `shipped-final` | L3 终止 |
| `POST` | `/api/devloop/projects/:pid/abandon` | 放弃项目(保留数据,从列表移除) | L3 终止 |
## Request / Response 示例
```js
# 创建项目
# 严格按 CLAUDE.md 警告:先写 /tmp/req.json,再 -d @file
curl -X POST http://localhost:3010/api/devloop/projects \
-H "Content-Type: application/json" \
-d @/tmp/create.json
# /tmp/create.json — 单行紧凑
{"company_id":"c-lemonv-001","name":"Auto Auth System","requirement":"需要一个邮箱密码登录的系统"}
# 响应
{
"project_id": "p-731042",
"status": "prd_drafting",
"stream_url": "/api/devloop/projects/p-731042/stream"
}
```
```js
# 追加新需求
curl -X POST http://localhost:3010/api/devloop/projects/p-731042/requirement \
-H "Content-Type: application/json" \
-d @/tmp/feedback.json
# /tmp/feedback.json
{"text":"再加 GitHub OAuth","origin":"yibo"}
# 响应(即刻返回,PM 是异步跑)
{
"accepted": true,
"queued_rev": 3,
"eta_seconds": 30
}
```
```js
# 查询状态
curl http://localhost:3010/api/devloop/projects/p-731042/state
{
"project_id": "p-731042",
"prd_rev": "3",
"sdd_rev": "5",
"current_loop": "L1",
"units": [
{ "id": "auth-be", "status": "passed" },
{ "id": "auth-fe", "status": "testing" },
{ "id": "oauth-be", "status": "pending" }
],
"l2_rounds": 0,
"l2_budget": 3,
"agents_active": ["tester:auth-fe"]
}
```
```js
# SSE 流(浏览器或 curl --no-buffer)
curl -N http://localhost:3010/api/devloop/projects/p-731042/stream
event: unit.state
data: {"unit_id":"auth-fe","from":"coding","to":"testing"}
event: agent.stream
data: {"agent":"tester","unit_id":"auth-fe","chunk":"PASS tests/login.spec.ts > valid creds\n"}
event: agent.stream
data: {"agent":"tester","unit_id":"auth-fe","chunk":"FAIL tests/login.spec.ts > invalid pwd\n"}
event: unit.state
data: {"unit_id":"auth-fe","from":"testing","to":"failed","retries":1}
event: prd.updated
data: {"rev":4,"changeSummary":"+ NFR: session timeout 1h"}
```
## routes/devloop.js 骨架
```js
// server/routes/devloop.js · NEW · ~200 行
const express = require('express');
const { loopController } = require('../src/core/loop-controller');
const { docStore } = require('../services/doc-store');
const { unitRegistry } = require('../services/unit-registry');
const { sseBroker } = require('../services/sse-broker');
const router = express.Router();
router.post('/projects', async (req, res) => {
const { company_id, name, requirement } = req.body;
const pid = await loopController.createProject({ company_id, name });
loopController.onHumanFeedback(pid, { text: requirement, origin: req.user?.id });
res.json({ project_id: pid, status: 'prd_drafting',
stream_url: `/api/devloop/projects/${pid}/stream` });
});
router.post('/projects/:pid/requirement', async (req, res) => {
loopController.onHumanFeedback(req.params.pid, req.body);
res.json({ accepted: true });
});
router.get('/projects/:pid/state', (req, res) => {
res.json(loopController.getState(req.params.pid));
});
router.get('/projects/:pid/stream', (req, res) => {
sseBroker.subscribe(req.params.pid, req, res);
});
module.exports = router;
```
## 挂载到 server/index.js
仅 1 行改动:
```js
// server/index.js 第 90 行后追加
app.use('/api/devloop', require('./routes/devloop'));
```
## 认证
沿用 `routes/auth.js` 的 JWT 中间件:
- POST 类端点:必须带 `Authorization: Bearer `,校验 `req.user.company_id` 与 path 中的 cid 匹配。
- GET state / stream:同上。
- Open API 调用(无人类 cookie,纯 server-to-server):使用现有的 `OPEN_API_KEYS` 环境变量 + `X-API-Key` header。
初版不做 RBAC(任何登录用户都能创建项目)。多用户隔离靠 cid。这是已知简化,P5 再加。