23 KiB
值班排班(日历 + 公开链接抢班)Implementation Plan
For agentic workers: REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (
- [ ]) syntax for tracking.
Goal: 在 web-admin 提供按日月历排班(预置早/晚/值班、名额、指定系统用户、复制/关闭按日公开链接);员工打开 /#/public/duty/{token} 免登录填姓名抢班,满员拒绝;支持历史翻月查看。
Architecture: 三表 duty_day / duty_slot / duty_assignment;管理 API 挂 /api/web/admin/duty;公开 API 挂 /api/public/duty(加密排除);公开页复用 publicPlGet/publicPlPost 与 isPublicMobile 静态路由。业务规则集中在 app/services/duty_schedule.py。
Tech Stack: FastAPI + SQLAlchemy + Pydantic v2;Vue 3 + Element Plus + SCSS(Fresh Mint 管理端 / 三墙 Token 公开 H5);pytest
Spec: docs/superpowers/specs/2026-08-14-duty-schedule-design.md
UI 原型: docs/demos/duty-schedule-v1-demo.html、docs/demos/duty-h5-grab-demo.html
文件结构
| 操作 | 文件 | 职责 |
|---|---|---|
| Create | backend/scripts/migrate_20260814_duty_schedule.sql |
建表 + 菜单 |
| Create | backend/app/models/duty_models.py |
ORM |
| Create | backend/app/schemas/duty.py |
请求/响应 schema |
| Create | backend/app/services/duty_schedule.py |
保存日配置、抢班、关链、序列化 |
| Create | backend/app/api/duty.py |
管理端路由 |
| Create | backend/app/api/public_duty.py |
公开路由 |
| Create | backend/tests/test_duty_schedule.py |
名额/关链/缩容单测 |
| Modify | backend/app/main.py |
include_router |
| Modify | backend/app/core/config.py |
API_ENCRYPT_EXCLUDE_PATHS |
| Create | web-admin/src/api/duty.ts |
管理端 API |
| Create | web-admin/src/api/public-duty.ts |
公开 API |
| Create | web-admin/src/views/duty/schedule/index.vue |
月历 + 日配置弹窗 |
| Create | web-admin/src/views/public/duty/index.vue |
公开 H5 |
| Modify | web-admin/src/router/routes/staticRoutes.ts |
公开路由 |
| Modify | web-admin/.env.development / .env.production(若有独立排除列表) |
排除公开 path |
| Sync | docs/sql/ |
将 migrate 表结构并入基线说明或追加脚本备注 |
Shift 常量(全栈统一): morning / evening / duty → 文案 早班 / 晚班 / 值班。
Task 1: SQL 迁移(三表 + 菜单)
Files:
-
Create:
backend/scripts/migrate_20260814_duty_schedule.sql -
Step 1: 写迁移 SQL
-- backend/scripts/migrate_20260814_duty_schedule.sql
-- 值班排班 V1:按日链接 + 班次名额 + 报名
CREATE TABLE IF NOT EXISTS `duty_day` (
`id` varchar(64) NOT NULL COMMENT '主键ID(UUID)',
`duty_date` date NOT NULL COMMENT '值班日期',
`access_token` varchar(64) NOT NULL COMMENT '公开访问令牌',
`link_status` varchar(16) NOT NULL DEFAULT 'open' COMMENT 'open/closed',
`tenant_no` varchar(64) NOT NULL DEFAULT 'shanxia2.0' COMMENT '租户编号',
`order_num` int(11) DEFAULT NULL COMMENT '排序号',
`remark` varchar(500) DEFAULT NULL COMMENT '备注',
`create_by` varchar(64) DEFAULT NULL COMMENT '创建者',
`create_dept_id` varchar(64) NOT NULL DEFAULT '' COMMENT '创建部门',
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`update_by` varchar(64) DEFAULT NULL COMMENT '更新者',
`update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
`del_flag` smallint(6) NOT NULL DEFAULT '0' COMMENT '删除标志',
PRIMARY KEY (`id`),
UNIQUE KEY `uk_token` (`access_token`),
KEY `idx_tenant_date` (`tenant_no`, `duty_date`, `del_flag`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='值班日(一天一条公开链接)';
CREATE TABLE IF NOT EXISTS `duty_slot` (
`id` varchar(64) NOT NULL COMMENT '主键ID(UUID)',
`duty_day_id` varchar(64) NOT NULL COMMENT '值班日ID',
`shift_type` varchar(32) NOT NULL COMMENT 'morning/evening/duty',
`capacity` int(11) NOT NULL DEFAULT 1 COMMENT '名额',
`tenant_no` varchar(64) NOT NULL DEFAULT 'shanxia2.0' COMMENT '租户编号',
`order_num` int(11) DEFAULT NULL COMMENT '排序号',
`remark` varchar(500) DEFAULT NULL COMMENT '备注',
`create_by` varchar(64) DEFAULT NULL COMMENT '创建者',
`create_dept_id` varchar(64) NOT NULL DEFAULT '' COMMENT '创建部门',
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`update_by` varchar(64) DEFAULT NULL COMMENT '更新者',
`update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
`del_flag` smallint(6) NOT NULL DEFAULT '0' COMMENT '删除标志',
PRIMARY KEY (`id`),
KEY `idx_day` (`duty_day_id`, `del_flag`),
KEY `idx_tenant` (`tenant_no`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='值班班次名额';
CREATE TABLE IF NOT EXISTS `duty_assignment` (
`id` varchar(64) NOT NULL COMMENT '主键ID(UUID)',
`duty_slot_id` varchar(64) NOT NULL COMMENT '班次名额ID',
`source` varchar(16) NOT NULL COMMENT 'assign/grab',
`user_id` varchar(64) DEFAULT NULL COMMENT '系统用户ID(指定时)',
`person_name` varchar(64) NOT NULL COMMENT '展示姓名',
`tenant_no` varchar(64) NOT NULL DEFAULT 'shanxia2.0' COMMENT '租户编号',
`order_num` int(11) DEFAULT NULL COMMENT '排序号',
`remark` varchar(500) DEFAULT NULL COMMENT '备注',
`create_by` varchar(64) DEFAULT NULL COMMENT '创建者',
`create_dept_id` varchar(64) NOT NULL DEFAULT '' COMMENT '创建部门',
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`update_by` varchar(64) DEFAULT NULL COMMENT '更新者',
`update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
`del_flag` smallint(6) NOT NULL DEFAULT '0' COMMENT '删除标志',
PRIMARY KEY (`id`),
KEY `idx_slot` (`duty_slot_id`, `del_flag`),
KEY `idx_tenant` (`tenant_no`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='值班占位(指定或抢班)';
-- 菜单:挂在系统管理或独立一级均可;此处挂 Web 虚拟根下「值班排班」
-- parent_id 请按环境库 Web 根菜单 id 调整(查询 parent_id=0 且面向 Web 的根)
INSERT INTO `sys_menu` (
`id`, `menu_name`, `parent_id`, `menu_type`, `path`, `component`, `perms`,
`is_frame`, `visible`, `status`, `tenant_no`, `order_num`, `remark`,
`create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag`
) VALUES (
'DUTY000000000000000000000000001',
'值班排班',
'0',
'C',
'duty-schedule',
'/duty/schedule/index',
'duty:schedule:list',
1, '1', '1', 'shanxia2.0', 90,
'日历排班与公开抢班链接',
'system', '', NOW(), NULL, NOW(), 0
)
ON DUPLICATE KEY UPDATE
`menu_name`=VALUES(`menu_name`),
`component`=VALUES(`component`),
`perms`=VALUES(`perms`),
`path`=VALUES(`path`);
执行前:查出现网 Web 菜单父级,将 parent_id='0' 改为正确父菜单 id(勿做成孤儿叶子;若 0 为虚拟根且项目允许顶栏入口,按门户习惯调整)。
- Step 2: 在本地库执行迁移
cd backend && source venv/bin/activate
# 用项目惯用方式执行 SQL,例如 mysql client 指向 .env.local
mysql ... < scripts/migrate_20260814_duty_schedule.sql
Expected: 三表创建成功;菜单可见(刷新权限后)。
- Step 3: Commit
git add backend/scripts/migrate_20260814_duty_schedule.sql
git commit -m "$(cat <<'EOF'
chore(duty): add duty_day/slot/assignment tables and menu SQL
EOF
)"
Task 2: ORM 模型
Files:
-
Create:
backend/app/models/duty_models.py -
Modify: 若项目有
models/__init__.py导出习惯则一并导出 -
Step 1: 创建模型(对齐 bx 公共字段风格)
"""值班排班模型 — docs/superpowers/specs/2026-08-14-duty-schedule-design.md"""
from datetime import datetime
from sqlalchemy import Column, Date, DateTime, Integer, SmallInteger, String
from app.core.database import Base
SHIFT_MORNING = "morning"
SHIFT_EVENING = "evening"
SHIFT_DUTY = "duty"
SHIFT_LABELS = {
SHIFT_MORNING: "早班",
SHIFT_EVENING: "晚班",
SHIFT_DUTY: "值班",
}
LINK_OPEN = "open"
LINK_CLOSED = "closed"
SOURCE_ASSIGN = "assign"
SOURCE_GRAB = "grab"
class DutyDay(Base):
__tablename__ = "duty_day"
id = Column(String(64), primary_key=True)
duty_date = Column(Date, nullable=False)
access_token = Column(String(64), nullable=False)
link_status = Column(String(16), nullable=False, default=LINK_OPEN)
tenant_no = Column(String(64), nullable=False, default="shanxia2.0")
order_num = Column(Integer, nullable=True)
remark = Column(String(500), nullable=True)
create_by = Column(String(64), nullable=True)
create_dept_id = Column(String(64), nullable=False, default="")
create_time = Column(DateTime, nullable=False, default=datetime.now)
update_by = Column(String(64), nullable=True)
update_time = Column(DateTime, nullable=False, default=datetime.now, onupdate=datetime.now)
del_flag = Column(SmallInteger, nullable=False, default=0)
class DutySlot(Base):
__tablename__ = "duty_slot"
id = Column(String(64), primary_key=True)
duty_day_id = Column(String(64), nullable=False)
shift_type = Column(String(32), nullable=False)
capacity = Column(Integer, nullable=False, default=1)
tenant_no = Column(String(64), nullable=False, default="shanxia2.0")
order_num = Column(Integer, nullable=True)
remark = Column(String(500), nullable=True)
create_by = Column(String(64), nullable=True)
create_dept_id = Column(String(64), nullable=False, default="")
create_time = Column(DateTime, nullable=False, default=datetime.now)
update_by = Column(String(64), nullable=True)
update_time = Column(DateTime, nullable=False, default=datetime.now, onupdate=datetime.now)
del_flag = Column(SmallInteger, nullable=False, default=0)
class DutyAssignment(Base):
__tablename__ = "duty_assignment"
id = Column(String(64), primary_key=True)
duty_slot_id = Column(String(64), nullable=False)
source = Column(String(16), nullable=False)
user_id = Column(String(64), nullable=True)
person_name = Column(String(64), nullable=False)
tenant_no = Column(String(64), nullable=False, default="shanxia2.0")
order_num = Column(Integer, nullable=True)
remark = Column(String(500), nullable=True)
create_by = Column(String(64), nullable=True)
create_dept_id = Column(String(64), nullable=False, default="")
create_time = Column(DateTime, nullable=False, default=datetime.now)
update_by = Column(String(64), nullable=True)
update_time = Column(DateTime, nullable=False, default=datetime.now, onupdate=datetime.now)
del_flag = Column(SmallInteger, nullable=False, default=0)
- Step 2: Commit
git add backend/app/models/duty_models.py
git commit -m "$(cat <<'EOF'
feat(duty): add SQLAlchemy models for duty schedule
EOF
)"
Task 3: Schemas + 领域服务(含 TDD)
Files:
-
Create:
backend/app/schemas/duty.py -
Create:
backend/app/services/duty_schedule.py -
Create:
backend/tests/test_duty_schedule.py -
Step 1: 写失败单测(纯函数/可测逻辑优先)
在 test_duty_schedule.py 先测容量校验与姓名规范化(不依赖 DB 的部分),以及用 SQLite/fixture 测 grab 满员(若项目测试多用 mock Session,则测 service 内 _assert_capacity / normalize_person_name)。
最小必测:
import pytest
from app.services.duty_schedule import normalize_person_name, SHIFT_LABELS
from app.models.duty_models import SHIFT_MORNING
def test_normalize_person_name_ok():
assert normalize_person_name(" 张三 ") == "张三"
def test_normalize_person_name_empty():
with pytest.raises(ValueError, match="姓名"):
normalize_person_name(" ")
def test_normalize_person_name_too_long():
with pytest.raises(ValueError, match="姓名"):
normalize_person_name("啊" * 21)
def test_shift_labels():
assert SHIFT_LABELS[SHIFT_MORNING] == "早班"
- Step 2: 跑测确认失败(函数尚未存在)
cd backend && source venv/bin/activate && pytest tests/test_duty_schedule.py -v
Expected: FAIL import / not defined
- Step 3: 实现 schemas
# backend/app/schemas/duty.py
from __future__ import annotations
from datetime import date
from typing import List, Literal, Optional
from pydantic import BaseModel, Field
ShiftType = Literal["morning", "evening", "duty"]
LinkStatus = Literal["open", "closed"]
SourceType = Literal["assign", "grab"]
class DutyAssignmentIn(BaseModel):
source: SourceType = "assign"
userId: Optional[str] = None
personName: str = ""
class DutySlotSaveIn(BaseModel):
shiftType: ShiftType
capacity: int = Field(ge=1, le=99)
assignments: List[DutyAssignmentIn] = []
class DutyDaySaveBody(BaseModel):
dutyDate: date
slots: List[DutySlotSaveIn]
linkStatus: Optional[LinkStatus] = None
class DutyGrabBody(BaseModel):
shiftType: ShiftType
personName: str
- Step 4: 实现 service 核心
duty_schedule.py 必须提供:
normalize_person_name(name: str) -> strnew_access_token() -> str(secrets.token_urlsafe(24),截断到 ≤64)build_public_url(token: str) -> str→f"{settings.PUBLIC_WEB_ORIGIN.rstrip('/')}/#/public/duty/{token}"get_month_summary(db, tenant_no, year, month) -> listget_day_detail(db, tenant_no, duty_date|day_id) -> dictsave_day(db, tenant_no, user, body: DutyDaySaveBody) -> dict- 校验:每个 slot
len(assignments) <= capacity;停用已有报名的 shift 须先清空 - upsert day(按 tenant+date);token 仅首次生成
- soft-delete 去掉的 slots(无报名时)与多余 assignments
- 校验:每个 slot
set_link_status(db, tenant_no, day_id, status)soft_delete_assignment(db, tenant_no, assignment_id)get_public_page(db, token) -> dict(无效 404 语义)grab(db, token, shift_type, person_name) -> dict- 关链拒绝;姓名规范化;
with_for_update锁 slot 行或 day 行;count < capacity再 insertsource=grab
- 关链拒绝;姓名规范化;
序列化字段用 camelCase,与前端一致:dutyDate, linkStatus, accessToken, publicUrl, shiftType, shiftLabel, capacity, taken, assignments: [{ id, source, userId, personName }]。
- Step 5: 补充 DB 级单测(可用现有测试 DB fixture;若无 fixture,则 mock Session 测分支)
至少覆盖:
grab在 capacity=1 时第二次失败link_status=closed时 grab 失败save_day缩容小于已报名人数抛错
- Step 6: 跑通测试
cd backend && source venv/bin/activate && pytest tests/test_duty_schedule.py -v
Expected: PASS
- Step 7: Commit
git add backend/app/schemas/duty.py backend/app/services/duty_schedule.py backend/tests/test_duty_schedule.py
git commit -m "$(cat <<'EOF'
feat(duty): add schemas and duty_schedule service with capacity tests
EOF
)"
Task 4: 管理端 + 公开 API 路由注册
Files:
-
Create:
backend/app/api/duty.py -
Create:
backend/app/api/public_duty.py -
Modify:
backend/app/main.py -
Modify:
backend/app/core/config.py -
Step 1: 管理路由
prefix="/web/admin/duty"
# 关键端点(均 Depends 现有 admin 鉴权,如 require_admin_or_super_admin / 菜单守卫)
GET ""?year=&month= -> month summary
GET "/day?dutyDate=YYYY-MM-DD" -> day detail(无则返回空骨架 slots=[])
POST "/day" -> save_day body
POST "/day/{dayId}/link" -> { linkStatus }
DELETE "/assignments/{id}" -> soft delete
鉴权用户取 tenant_no、user_id、create_dept_id(与 leave 一致)。指定用户时用现有用户查询解析姓名写入 person_name。
- Step 2: 公开路由
prefix="/public/duty"
GET "/{token}" -> get_public_page;404 文案「链接无效或已失效」
POST "/{token}/grab" -> DutyGrabBody;业务错误用 error_response / HTTP 400
- Step 3:
main.py注册
from app.api.duty import router as duty_router
from app.api.public_duty import router as public_duty_router
# ...
app.include_router(duty_router, prefix=settings.API_V1_PREFIX)
app.include_router(public_duty_router, prefix=settings.API_V1_PREFIX)
- Step 4: 加密排除
在 API_ENCRYPT_EXCLUDE_PATHS 追加:
"/api/public/duty,"
检查 web-admin/.env.development 与 .env.production 的 VITE_API_ENCRYPT_EXCLUDE_PATHS,若存在则同样追加 /api/public/duty。
- Step 5: 手工用 curl/Swagger 冒烟
# 登录后 POST /api/web/admin/duty/day 创建某日
# GET /api/public/duty/{token}
# POST /api/public/duty/{token}/grab {"shiftType":"evening","personName":"测试"}
- Step 6: Commit
git add backend/app/api/duty.py backend/app/api/public_duty.py backend/app/main.py backend/app/core/config.py web-admin/.env.development web-admin/.env.production
git commit -m "$(cat <<'EOF'
feat(duty): register admin and public duty APIs with encrypt exclude
EOF
)"
Task 5: web-admin 管理端月历页
Files:
-
Create:
web-admin/src/api/duty.ts -
Create:
web-admin/src/views/duty/schedule/index.vue -
菜单 component 已指向
/duty/schedule/index(动态路由按现有菜单加载) -
Step 1: API 模块
// web-admin/src/api/duty.ts
import request from '@/utils/http'
export type ShiftType = 'morning' | 'evening' | 'duty'
export const fetchDutyMonth = (year: number, month: number) =>
request.get({ url: '/web/admin/duty', params: { year, month } })
export const fetchDutyDay = (dutyDate: string) =>
request.get({ url: '/web/admin/duty/day', params: { dutyDate } })
export const saveDutyDay = (data: unknown) =>
request.post({ url: '/web/admin/duty/day', data })
export const setDutyLinkStatus = (dayId: string, linkStatus: 'open' | 'closed') =>
request.post({ url: `/web/admin/duty/day/${dayId}/link`, data: { linkStatus } })
export const deleteDutyAssignment = (id: string) =>
request.del({ url: `/web/admin/duty/assignments/${id}` })
(若项目 request 方法名是 delete 而非 del,与邻近 API 文件保持一致。)
- Step 2: 月历页
实现要求(对齐 docs/demos/duty-schedule-v1-demo.html + Fresh Mint):
panel-top+h2.title-bar-heading「值班排班」- 月切换、今天
- 7 列日历网格(可用纯 CSS/table,不必上 FullCalendar)
- 日格展示 chip:早班/晚班/值班颜色区分(管理端可用琥珀/蓝/红浅底,勿用小程序主色当 primary 按钮)
- 点击日期打开
el-dialog:label-position="top",modal-class无 blur,dialog-form-layout - 弹窗内三个可启用班次:
capacity+ 系统用户选择(复用现有用户远程搜索/选择组件,参考system/user或其它选人控件)+ 已安排 tags(指定/抢班)可删 - 复制
publicUrl;开关链接按钮 - 保存调用
saveDutyDay;缩容错误展示后端 message
按钮均带 <el-icon>。
-
Step 3: 本地
pnpm dev打开菜单验证月历加载与保存 -
Step 4: Commit
git add web-admin/src/api/duty.ts web-admin/src/views/duty/schedule/index.vue
git commit -m "$(cat <<'EOF'
feat(duty): add web-admin calendar schedule page
EOF
)"
Task 6: 公开 H5 页
Files:
-
Create:
web-admin/src/api/public-duty.ts -
Create:
web-admin/src/views/public/duty/index.vue -
Modify:
web-admin/src/router/routes/staticRoutes.ts -
Step 1: 静态路由(插在其它 public 路由旁)
{
path: '/public/duty/:token',
name: 'PublicDutyGrab',
component: () => import('@views/public/duty/index.vue'),
meta: { title: '值班抢班', isHideTab: true, isPublicMobile: true }
},
- Step 2: 公开 API
import { publicPlGet, publicPlPost } from '@/utils/http/public-pl'
export const publicDutyApi = {
getPage: (token: string) => publicPlGet<any>(`/public/duty/${token}`),
grab: (token: string, data: { shiftType: string; personName: string }) =>
publicPlPost<any>(`/public/duty/${token}/grab`, data)
}
- Step 3: H5 页面
对齐 docs/demos/duty-h5-grab-demo.html:
- 三墙色:
#0a9f6d/#087f57/#e9f9f2/#f5f8f7 - 状态:加载中 / 无效 / 已关闭 / 可抢列表 / 成功摘要
- 班次独立卡片;满员禁用;可抢输入姓名(max 20)提交
onMounted读route.params.token
参考 web-admin/src/views/public/pl/transport/index.vue 的公开页壳与错误处理。
- Step 4: 端到端手测
- 管理端建日 → 复制链接 → 无痕打开 H5
- 抢晚班成功 → 再抢同班次满员失败
- 关链 → H5 显示关闭
- 管理端删人 → 名额释放可再抢
- Step 5: Commit
git add web-admin/src/api/public-duty.ts web-admin/src/views/public/duty/index.vue web-admin/src/router/routes/staticRoutes.ts
git commit -m "$(cat <<'EOF'
feat(duty): add public H5 grab page for daily duty links
EOF
)"
Task 7: 收尾与图谱
Files:
-
按需更新
docs/sql/备注或拷贝 migrate 说明 -
跑
./scripts/update-graph.sh backend与frontend(或graphify update .) -
Step 1: 对照 spec 验收清单勾选(设计文档「测试要点」7 条)
-
Step 2: 更新知识图谱
./scripts/update-graph.sh all
# 或
graphify update .
- Step 3: 最终 commit(若有 docs/sql 或 graph 变更)
git add docs/sql backend/graphify-out web-admin/graphify-out graphify-out
git commit -m "$(cat <<'EOF'
chore(duty): sync sql notes and knowledge graph after duty schedule
EOF
)"
Spec 覆盖自检
| Spec 项 | Task |
|---|---|
| D1 web + 公开 H5 | 5, 6 |
| D2 一天一链接 | 1–4 save_day token |
| D3 班次独立罗列 | 6 |
| D4 只填姓名 | 3 grab / 6 |
| D5 系统用户指定 | 4–5 |
| D6/D7 员工不可取消、经理可改 | 4 delete/save/link |
| D8 无统计页 | 5 仅日历 |
| D9 预置班次 | models 常量 |
| D10 按天建 | 5 点日配置 |
| D11 同日多班次 | grab 不按人跨 slot 限制 |
| D12 token 不重置 | save_day |
| D13 缩容校验 | service + test |
| D14 历史可改 | 无冻结逻辑 |
| D15 菜单权限 | migrate menu |
| 加密排除 | Task 4 |
| 并发满员 | grab + test |
V1 限流: 若无现成中间件,可在 grab 内不做强限流,验收文档注明「后续可加」;不阻塞主路径(YAGNI)。若已有简易 IP 限流工具则挂上。
执行方式
Plan 已保存。可选:
- Subagent-Driven(推荐) — 每任务新开子代理,任务间审查
- Inline Execution — 本会话按 executing-plans 连续推进并设检查点
请选择一种,或指出计划中要先改的部分。