en un clic
grading-backend
// Grade a team's backend implementation against its spec on 9 weighted dimensions, producing a markdown report + JSON summary with evidence.
// Grade a team's backend implementation against its spec on 9 weighted dimensions, producing a markdown report + JSON summary with evidence.
| name | grading-backend |
| description | Grade a team's backend implementation against its spec on 9 weighted dimensions, producing a markdown report + JSON summary with evidence. |
给 AI 培训项目后端实现打分。50 学员分成 ~12 组,按 spec-driven 方式实现本地部署的后端服务。本 skill 让 Claude(或 Qoder / Cursor / Claude Code agent)按统一 rubric 给某一组的后端打分,产出可给学员的 markdown 报告 + 可汇总 leaderboard 的 JSON。
npm run dev / uvicorn ... / go run ./... / ./gradlew bootRun 等),默认 http://localhost:8080(按项目 README 调整),有 health check 端点(/health / /healthz / /ping)或其他就绪信号。<project>/docs/spec.md 或 <project>/README.md 或 <project>/spec/*。找不到则先向用户确认 spec 路径再开工,不要凭空评分。npx ajv-cli 校验 JSON、npx autocannon 做压测。.grading/(已由 spec 约定 gitignore)。约定的工作目录:所有 probe 脚本和 .grading/ 输出都默认在待评项目根目录下执行。即评分时:
cd group-workshop/group-2)bash $WORKSHOP_ROOT/skills/grading-backend/probes/probe-robustness.sh team-2 http://localhost:8080 /api/items.grading/ 会落到被评项目根下,与该项目共生评分开始前,必须先读完本文件 ## 评分标尺 / ## 报告模板 / ## 证据硬约束 三段,以及 ../score-schema.json。这些是 frontend / backend 两个 skill 共用的硬约束,不读会导致输出格式不一致、无法汇总 leaderboard:
report.md 必须照此结构填充。../score-schema.json — JSON 摘要的 schema。产出的 summary.json 必须通过此 schema 校验。本段是 grading-frontend 与 grading-backend 两个 skill 共享的唯一打分锚点。两侧所有维度得分都必须按本表落点,再用证据说明为什么不是相邻的锚点。
| 分数 | 名称 | 判定标准 |
|---|---|---|
| 10 | 工业级范本 | 可直接作为下一届教学样例;评分人找不到该维度的改进空间;相对已见过的最好学员作业也明显领先 |
| 9 | 优秀 | 核心完全到位,仅 1–2 处非关键瑕疵(例如边缘场景文案、极端断点、冷路径日志) |
| 7 | 良好 | 主流程到位,但存在 ≥3 处可改进;不影响交付但明显看得出赶工痕迹 |
| 5 | 及格 | Happy path 能跑,但明显粗糙;有 1 类系统性漏洞(如缺状态、缺校验)但未致命 |
| 3 | 不及格 | 该项存在尝试痕迹但严重不完整;主要子项缺失或逻辑错误 |
| 0 | 未实现 | 完全缺失,或实现了但跑不起来 |
| N/A | 不适用 | spec 明确未要求,或该维度在本项目栈上无意义;必须写原因,不计入总分 |
允许的分数:0、3、5、7、9、10 是锚点;1/2/4/6/8 仅在证据支持"介于两锚点之间"时使用。不允许出现小数。
score ≥ 7 → 至少 2 条证据score ≤ 4 → 至少 2 条证据5–6 → 至少 1 条证据## 证据硬约束 段na_reason 字段必填,格式 "spec §X.Y 未涉及此项" 或 "技术栈 Z 无此概念"。N/A 不计入总分。公式(N/A 不计入分母):
total = Σ(score_i × weight_i) / Σ(weight_i for dim_i where score_i != null) × 100 / 10
等价形式:先对非 N/A 维度算加权平均(0–10),再把有效权重归一化到 100,最后乘 10 映射到 0–100。
例子:前端 10 维总权重 100,若 微文案 (权重 6) 被判 N/A:
total = (Σ / 94) × 10| 档位 | 分数区间 | 含义 |
|---|---|---|
| S | ≥ 90 | 可作为本届教学样例 |
| A | 80–89 | 合格交付,小范围打磨即可上线 |
| B | 70–79 | 主流程 OK,系统性问题需要返工 |
| C | 60–69 | 能 demo,不能交付;多处结构性缺陷 |
| D | < 60 | 未达培训目标,需重做关键模块 |
档位只用于班级 leaderboard 横向对比,学员反馈以维度得分 + 证据 + 改进清单为主。
两个 grading skill 产出的 .grading/reports/<team>-<side>.md 必须严格按本模板。字段缺一不可;无信息时填 —,不要省略行。模板本体用四个反引号包裹的 Markdown 代码块呈现,复制时去掉最外层反引号。
{队伍名},落地时替换为实际值grading-frontend 10 维 / grading-backend 9 维完整展开,不能合并或省略null,na_reason 填 EVIDENCE_MISSING: ...,并在末尾"评分风险"段汇总<team>-<side>.json,schema 见 ../score-schema.json# 项目评分报告 — {队伍名}({前端 | 后端})
- 评分人:{Claude@model-id / 助教姓名}
- 日期:{YYYY-MM-DD}
- Spec 版本:{git sha 或 docs/spec.md 路径}
- 被评项目 commit:{sha}
- 评分耗时:{xx 分钟}
## 总分:{score} / 100 — 等级 {S | A | B | C | D}
## 维度汇总
| # | 维度 | 得分 | 权重 | 加权 | 一句话 |
|---:|---|---:|---:|---:|---|
| 1 | {维度名} | {0–10 或 N/A} | {w} | {score×w/10} | {一句话概述} |
| 2 | ... | ... | ... | ... | ... |
> 有效权重总和:{Σw_non-NA};N/A 维度:{列表或 "无"}
## 逐维度详评
### 1. {维度名} — {score}/10(权重 {w})
**证据**
- `path/to/file.ext:L12-L40` — {这条证据说明了什么}
- 截图:`.grading/shots/{team}-{view}.png`
- 命令:`curl -s localhost:8080/api/x` → `.grading/probes/{team}-x.log`
**要到 10 差什么**
1. {具体、可操作的改进建议}
2. {...}
3. {...}
---
### 2. {维度名} — {score}/10(权重 {w})
(同样模板,完整列出所有维度)
---
## 亮点
- {值得表扬的 2–4 条,要带证据}
## 最该优先修的 3 件事
1. **{问题}** — 影响:{哪个维度掉了多少分};改法:{一两句话说清}
2. ...
3. ...
## 评分风险(可选)
仅当出现以下情况时填写,否则删除整节:
- 证据不足被判 `EVIDENCE_MISSING` 的维度列表
- 评分人对某维度存疑但无法进一步取证的原因
- 项目未跑起来 / spec 缺失等影响评分可信度的事实
na_reason.grading/reports/<team>-<side>.json本段是打分是否有效的判定规则。grading-frontend 和 grading-backend 两个 skill 在写入每个维度分数前必须先检查本表;证据不达标的打分视为无效。
设计目的:防止 agent 在缺乏实际观察的情况下拍脑袋打分。宁可多一个 EVIDENCE_MISSING,不要编一个好看的分数。
| 分数段 | 最少证据条数 | 说明 |
|---|---|---|
| 9–10 | 3 | 高分必须可复现;至少覆盖:1 条代码引用 + 1 条运行期证据(截图/probe/log) + 1 条 spec 比对 |
| 7–8 | 2 | 需同时体现"主流程到位"与"非关键瑕疵" |
| 5–6 | 1 | 一条即可,但必须能指出具体缺陷 |
| 3–4 | 2 | 低分不能靠印象;必须列出 ≥2 个具体失败点 |
| 0 | 1 | 至少证明"真的没有":grep 结果、spec 条目 + 代码库缺失、启动失败日志 |
| N/A | 1 | 必须引用 spec 条目或技术栈说明,证明该项"不适用" |
每条证据必须是以下 4 类之一,不得为自然语言转述:
path/to/file.ext:L{start}-L{end} 或 path/to/file.ext:L{line}
.grading/shots/<team>-<view>.png
.grading/probes/<team>-<probe>.log 或在证据行内直接给出命令与关键输出
curl -s -o /dev/null -w "%{http_code}" localhost:8080/api/x -d '{}' → 500spec §3.2 / docs/spec.md:L120-L145 / issue 链接
若按 §1 无法凑够所需证据条数:
score 置为 nullna_reason 写为 "EVIDENCE_MISSING: 需要 N 条证据,仅能取得 M 条(已尝试:<动作清单>)"## 评分风险 段落列出该维度评分人/agent 若写入以下任一形式的证据,该维度直接作废并降级为 EVIDENCE_MISSING:
test -f 批量校验)score ≥ 7 但证据 < 2 条 → 降为 6,或标 EVIDENCE_MISSINGscore ≤ 4 但证据 < 2 条 → 升为 5,或标 EVIDENCE_MISSING选择哪个处理取决于评分人对维度的把握;两种方式都必须在"评分风险"中注明。
先做便宜的、可批量的,再做昂贵的:
grep / rg 扫关键词、看 package.json / schema / migration每跑完一步把产物落盘到 .grading/,在报告证据行里引用相对路径即可。
按需读取(不是每次都读全部):
./anti-patterns.md — 后端反模式清单。打"健壮性"、"代码分层"、"性能"三个维度时必读,用来识别 controller 里写 SQL、裸 try/except: pass 吞异常、循环里 query 造成 N+1、写操作非幂等、错误响应结构每接口不同等典型滑坡。./examples/good-report.md 和 ./examples/mediocre-report.md — 两份标定样例报告。打分前读一次做分布校准,避免全班都打 8 分这种分不开档的问题。./probes/probe-robustness.sh — 健壮性探针组合,跑 8 个场景(空 body / 错类型 / 超长字段 / 缺必填 / 越权 / 幂等重放 / 并发冲突 / 未登录)。取证时直接 bash ./probes/probe-robustness.sh <team> <base-url> <path>,不要手搓 curl。每个 probe 写 .grading/probes/<team>-<probe-name>.log,总表写 .grading/probes/<team>-robustness-summary.md。./probes/probe-performance.sh — 基于 npx autocannon 的 p50/p95/p99 压测脚本,输出 .grading/probes/<team>-autocannon.log(原始 autocannon 输出)和 .grading/probes/<team>-perf-summary.txt(解析后的 p50/p95/rps + rubric 建议)。spec_sha(若 spec 在 git 仓库内)。package.json / pyproject.toml / go.mod / pom.xml 识别技术栈(Node/Python/Go/Java、ORM、测试框架、日志库),记录 project_sha。http://localhost:<port>/<health> 可访问(curl 拿到 200 或 spec 约定的就绪响应)。如启动失败直接记录在报告"启动阻塞"一节,本维度不硬给 0 分,但在"Spec 一致性"里扣分。静态证据:
file:line 引用。动态证据:
# 健壮性:对主要写接口跑一遍 8 个 probe
bash ./probes/probe-robustness.sh <team> http://localhost:8080 /api/items
# 性能:对 GET 主列表接口 & POST 主写接口各压一次
bash ./probes/probe-performance.sh <team> http://localhost:8080 /api/items
# 测试套件(按栈择一)
npm test -- --coverage # Node
pytest --cov # Python
go test -cover ./... # Go
# 手工 curl:对 spec 列出的每个接口至少调一次,记录 method + path + 状态码 + 响应体
curl -i -X GET http://localhost:8080/api/items
curl -i -X POST http://localhost:8080/api/items -H 'Content-Type: application/json' -d '{"name":"demo"}'
所有 probe 日志落到 .grading/probes/<team>-*.log,手工 curl 的关键输出也要 tee 进 .grading/probes/<team>-curl.log。
交互证据:按 spec 的主业务流(注册 → 登录 → 创建 → 查询 → 更新 → 删除,或 spec 指定流程)串一遍,记录每步状态码、响应体片段、异常。
⚠️ 取证不足时的强制行为
- 维度打分 ≥7 或 ≤4 时,证据条数低于 evidence-requirements.md 规定的最低条数 → score 必须置 null,na_reason 写 "EVIDENCE_MISSING: 已尝试 X、Y,未能取得 Z"
- 这与 "spec 未要求" 的 N/A 是两回事:N/A 写 "N/A — spec §X.Y 未规定"
- 所有 EVIDENCE_MISSING 维度必须在报告底部 "## 评分风险" 段汇总列出
对照本文件下方 rubric(以及本文件 ## 评分标尺 段),逐维度 0–10 打分。每一条打分必须附证据(file:line / curl log 行号 / probe log 行号 / 测试输出行号)。spec 未要求的维度写 "N/A — 原因",不计入总分。
按本文件 ## 报告模板 段填 .grading/reports/<team>-backend.md;按 ../score-schema.json 填 .grading/reports/<team>-backend.json。每维度附"要到 10 差什么"2–4 条。
# JSON schema 校验
npx ajv-cli validate -s skills/score-schema.json \
-d .grading/reports/<team>-backend.json
# 证据数量自检(每维度统计 file:line + probe log + curl log 引用总数)
grep -cE "\.(ts|js|py|go|java|sql|log):" .grading/reports/<team>-backend.md
自检不过则回到步骤 2 补证据,不得放过。
| # | 维度 | 权重 |
|---|---|---|
| 1 | Spec 一致性 | 20 |
| 2 | 健壮性 | 15 |
| 3 | API 设计 | 12 |
| 4 | 测试 | 12 |
| 5 | 数据建模 | 10 |
| 6 | 代码分层 | 10 |
| 7 | 性能 | 8 |
| 8 | 可观测性 | 7 |
| 9 | 文档 | 6 |
关注点
锚点
取证方法
# Node/Express 举例
grep -rnE "app\.(get|post|put|patch|delete)|router\.(get|post|put|patch|delete)" src/
# FastAPI
grep -rnE "@(app|router)\.(get|post|put|patch|delete)" .
# Go/Gin
grep -rnE "\.(GET|POST|PUT|PATCH|DELETE)\(" .
证据要求
file:line 引用到路由 / handler 文件。关注点
try/except: pass 吞错)。锚点(基于 probe-robustness.sh 8 个 probe 的通过数)
取证方法
bash ./probes/probe-robustness.sh <team> <base-url> <path>,记录 8 个 probe 的通过数(每个 probe 有期望状态码)。grep -rnE "try\s*\{|try:" src/ | head -20
grep -rnE "except\s*:|catch\s*\(\s*\)" src/ # 裸 except / catch
grep -rnE "pass\s*$|// ignore" src/
curl 打两次相同 payload,看是否产生两条记录(幂等)。证据要求
<team>-robustness-summary.md(标注通过数 X/8)+ 至少 3 份 <team>-<probe-name>.log。file:line 引用校验 / 错误处理代码。关注点
{code, message, details}),不是每接口一种形状。?page=&size= 或 cursor)。锚点
q 其他用 keyword)。{ok: false},无状态码体系,路径随意。取证方法
for p in /api/items /api/users /api/auth/login; do
curl -s -o - -w "\n[HTTP %{http_code}]\n" http://localhost:8080$p
done
curl "http://localhost:8080/api/items?page=1&size=10" 看是否识别。证据要求
file:line 引用统一错误处理中间件(或反面证据说明缺失)。关注点
锚点(基于测试覆盖率)
npm test / pytest / go test 一次绿。取证方法
npm test -- --coverage # Node
pytest --cov --cov-report=term # Python
go test -cover ./... # Go
./gradlew test jacocoTestReport # Java
把输出 tee 到 .grading/probes/<team>-test.log。grep -rnE "\.skip|@pytest\.mark\.skip|t\.Skip\(|xit\(|xdescribe\(" .
证据要求
file:line 引用测试文件(good case)或缺失(bad case)。skip 数 / 总数。关注点
timestamptz / datetime,金额不用 float)。锚点
text 而 spec 建议 varchar(n)。取证方法
find . -type f \( -name "*.sql" -o -name "schema.prisma" -o -name "models.py" -o -name "entity*.go" -o -name "*Entity.java" \) | head
find . -type d -name "migrations" -o -name "migrate"
证据要求
file:line 引用 schema / model / migration 文件。CREATE INDEX 或 ORM 注解里抽)。关注点
锚点(基于 grep controller 里是否有 SQL/ORM 调用)
prisma.xxx / db.query,service 里写 res.json(...)。取证方法
# Node/TS
grep -rnE "prisma\.|knex\(|db\.query|createQueryBuilder|sequelize\." src/controllers src/routes src/handlers 2>/dev/null
# Python
grep -rnE "session\.query|db\.execute|cursor\.execute" app/api app/routes 2>/dev/null
# Go
grep -rnE "db\.Query|db\.Exec|gorm\.|sqlx\." internal/handler internal/controller 2>/dev/null
命中越多越说明泄漏。controller -> service -> repo 是否单向。证据要求
file:line(好的 service / 坏的泄漏各举例)。关注点
锚点(基于 autocannon p95,10 并发 5 秒,本地)
取证方法
bash ./probes/probe-performance.sh <team> <base-url> <path>,读 p50/p95/p99。# 找循环里调 query / repo 的反模式
grep -rnE "for\s|forEach|\.map\(" src/ | grep -E "repo\.|query|findBy"
EXPLAIN SELECT ... FROM items WHERE ...。证据要求
<team>-autocannon.log + 1 份 <team>-perf-summary.txt(含 p50/p95/p99)。file:line 体现 N+1 或优化手段(或反面证据)。关注点
print / console.log。/health)。锚点(基于 console.log / print 占日志语句比例)
print/console.log 数 ≤ 2。print/console.log ≤ 5 且都在非生产路径(如 seed 脚本),/health 可用。print/console.log 残留 5–10 处。console.log 残留(3–10 处)。print/console.log 占比 > 30%。print / console.log,没有 requestId / 结构化,错误靠 try/catch 打一句。取证方法
grep -rncE "console\.log|console\.debug|\bprint\(" src/ | sort -t: -k2 -n -r | head
grep -rncE "logger\.|log\.(info|warn|error|debug)" src/ | sort -t: -k2 -n -r | head
算两个总数相除得到 "jank 比例"。/health 或 /metrics:curl -i http://localhost:8080/health。证据要求
file:line 引用日志配置或关键调用。/health 或 /metrics 的 curl 响应。关注点
锚点
/docs 打开;有架构图或 ADR。取证方法
find . -type f \( -name "openapi*.y*ml" -o -name "swagger*.json" -o -name "*.postman_collection.json" \)
curl -i http://localhost:8080/docs # Swagger UI
curl -i http://localhost:8080/openapi.json # FastAPI / 多数框架
证据要求
README.md:line 或 docs/*.md:line 引用。评分结束前逐项勾选,任一不通过就回到相应步骤补齐:
.grading/reports/<team>-backend.md 已按本文件 ## 报告模板 段结构填完,9 个维度全部评完(或明确标 N/A)。.grading/reports/<team>-backend.json 已产出,并通过 npx ajv-cli validate -s skills/score-schema.json 校验。## 证据硬约束 段(≥7 / ≤4 分 ≥2 条;5–6 分 ≥1 条)。.grading/probes/ 含:<team>-robustness-summary.md + 各 probe <team>-<probe-name>.log、<team>-autocannon.log + <team>-perf-summary.txt、<team>-test.log、<team>-curl.log。