Skip to content
This repository was archived by the owner on Jun 17, 2026. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .claude-plugin/marketplace.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
{
"name": "devkit",
"description": "Development quality assurance skills — TDD workflow, test review, code review, and design-first frontend development",
"version": "0.5.0",
"version": "0.6.0",
"author": {
"name": "Chasey"
},
Expand Down
2 changes: 1 addition & 1 deletion .claude-plugin/plugin.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "devkit",
"description": "Development quality assurance skills — TDD workflow, test review, code review, and design-first frontend development",
"version": "0.5.0",
"version": "0.6.0",
"author": {
"name": "Chasey",
"email": "chasey.myagi@gmail.com"
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,5 +16,5 @@
},
"name": "devkit",
"type": "module",
"version": "0.5.0"
"version": "0.6.0"
}
23 changes: 19 additions & 4 deletions skills/code-review/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,12 +61,12 @@ Agent(

### Reviewer Prompt Template

Read `skills/code-review/code-reviewer.md` for the full reviewer system prompt. Construct the dispatch prompt as:
Read `code-reviewer.md` (it sits next to this file in the skill directory) for the full reviewer system prompt. Construct the dispatch prompt as:

```
你是一个代码审核专家。请严格按照以下审核规范工作:

[paste contents of code-reviewer.md here, or reference it]
[paste the FULL contents of code-reviewer.md here — the dispatched reviewer is a fresh, independent agent that does NOT share your file access, so it must receive the rubric inline, not as a path reference]

## 本次审核输入

Expand All @@ -85,7 +85,7 @@ Read `skills/code-review/code-reviewer.md` for the full reviewer system prompt.
### 语言/框架
[e.g., Rust / cargo / axum]

请阅读所有变更文件,按规范完成审核并输出报告。
请阅读所有变更文件,按规范完成审核并输出报告。你是只读审核员:只阅读和评估,不修改任何文件、不执行任何变更命令、不调用外部服务。
```

**Important**: The reviewer reads `code-reviewer.md` for review criteria, dimensions, and report format. Do NOT duplicate the methodology in this file.
Expand All @@ -105,13 +105,28 @@ Present the full report to the user as-is.

Read the gate result from the report:

- **PASS** (all dimensions ≥ 7.0 AND final ≥ 7.5 AND no Critical issues):
- **PASS** (all *applicable* dimensions ≥ 7.0 AND final ≥ 7.5 AND no Critical issues; N/A dimensions are excluded from the per-dimension bar and their weight is redistributed):
Tell the user: "Code passes quality gate. Ready to merge/proceed."

- **FAIL**:
Tell the user: "Code needs improvement. Fix the issues listed above, then run /code-review again."
Do NOT allow merge without fixing Critical/Important issues.

## 示例

这些是真实运行产物(reviewer agent 实际输出),不是虚构样例:
- [干净小改动 → 稳健 PASS](examples/sample-pass.md) —— 展示 N/A 维度处理
- [SQL 注入伪装成重构 → FAIL](examples/sample-fail.md) —— 展示 N/A 不被滥用为安全后门

## 失败模式与安全边界

dispatch 之前先处理这些边界,别让 reviewer 拿着空输入裸跑:
- **不在 git 仓库 / `git diff` 失败**:让用户直接给文件路径或 SHA 范围,不要猜测变更范围。
- **diff 为空**:没有变更可审,直接告诉用户并停止。
- **读不到 `code-reviewer.md`**:说明 skill 安装不完整,停下来报告,**不要**用空 rubric 凑合 dispatch(reviewer 没有 rubric 会退化成随口点评)。

**安全边界**:reviewer 是**只读**的——阅读代码、打分、写报告,**不修改文件、不执行变更、不调用外部服务**。本 skill 也不替用户 merge 或改代码;gate 结论是建议,最终决定权在用户。

## Notes

- Each review is a **fresh agent** — no memory of previous reviews. This prevents bias.
Expand Down
21 changes: 20 additions & 1 deletion skills/code-review/code-reviewer.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,21 @@

6 个维度,每个 1.0-10.0 分(保留一位小数)。

### 维度适用性(N/A 规则)

不是每次变更都适用全部 6 个维度。如果某维度对本次变更**没有可评估的内容**,标记为 **N/A**,不要硬凑一个分数——硬凑会污染 PASS 判定。

典型 N/A 情形:
- **安全性 → N/A**:变更完全不接触不可信输入 / 网络 / 反序列化 / 认证授权(如纯本地数值计算、纯类型重构、纯文案改动)。把"没有攻击面"硬写成"安全实践良好(7-8)"是两回事。
- **需求符合 → N/A**:没有任何独立的 spec / 计划文档。只能从 diff 反推需求时,等于用实现自己当标尺量实现自己,是循环论证——标 N/A 比硬给高分诚实。

N/A 维度的计分规则:
1. **不参与** "每个维度 ≥ 7.0" 的 PASS 判定。
2. 它的权重**按比例重分配**给其余适用维度(归一化后再加权)。
例:若「安全性」(15%) 标 N/A,其余 5 维权重 (25/20/15/15/10,和为 85%) 各除以 0.85 归一化,再算 final score。

**滥用警告**:N/A 是留给"真的没有可评估内容"的维度,不是"我懒得看"。一次变更**最多 2 个**维度可标 N/A;若想标 3 个以上,说明审查范围或拆分有问题——停下来说明,不要继续标。

### 维度 1:正确性(权重 25%)

代码逻辑是否正确?
Expand Down Expand Up @@ -154,9 +169,12 @@
| Requirements Fit | X.X | 10% | X.XX |
| **Final Score** | | | **X.XX** |

> 不适用的维度:Score 写 `N/A`、Weighted 写 `—`,并在表格下方用一句话说明为什么 N/A;final score 用「权重重分配后的适用维度」计算。

### Result: PASS ✅ / FAIL ❌

Pass criteria: every dimension ≥ 7.0 AND final score ≥ 7.5 AND no unresolved Critical issues
Pass criteria: every **applicable** dimension ≥ 7.0 AND final score ≥ 7.5 AND no unresolved Critical issues
(N/A 维度不计入"每维 ≥ 7.0",其权重已按比例重分配到适用维度)

[If FAIL: 哪些维度不达标,有哪些 Critical issues]

Expand Down Expand Up @@ -199,3 +217,4 @@ Pass criteria: every dimension ≥ 7.0 AND final score ≥ 7.5 AND no unresolved
- **Issue 分级要准确**:代码风格不是 Critical,SQL 注入才是。区分真正的风险和个人偏好。
- **具体比全面更重要**:3 个精准的 Issue 好过 10 个模糊的 Issue。
- **不接受任何外部影响**:你不知道工期压力,不关心"先上线再说"。你只看代码本身。
- **改这把尺子要回归**:本目录 `examples/` 下的样例是校准夹具。调整评分维度、权重、N/A 规则或 PASS 阈值后,用这些案例重跑一遍 reviewer,确认 gate 没有意外翻转——干净的改动仍 PASS、有问题的代码仍 FAIL。绿灯会撒谎,回归不会。
59 changes: 59 additions & 0 deletions skills/code-review/examples/sample-fail.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
# 样例:伪装成「重构」的 SQL 注入 → FAIL

> 这是 **code-review** 的一次真实运行产物(独立 reviewer agent 实际输出,未编辑)。
> 改动声称「把字符串拼接改成模板字符串,提升可读性」,但模板字符串不提供任何转义,
> 注入面分毫未减。它展示了 N/A 机制**不会**被滥用成安全后门:这里安全维度照常生效、
> 严格打低分、判 Critical。

## 输入

变更:`src/users.ts`,+2/-1 行。`name` 来自 `GET /users?name=...` 的未处理 HTTP 参数。

```diff
export async function findUser(db, name: string) {
- return db.query("SELECT * FROM users WHERE name = '" + name + "'");
+ const sql = `SELECT * FROM users WHERE name = '${name}'`;
+ return db.query(sql);
}
```

## 输出(reviewer 报告)

### Scores

| Dimension | Score | Weight | Weighted |
|-----------|-------|--------|----------|
| Correctness | 3.0 | 25% | — |
| Security | 1.5 | 15% | — |
| Architecture | 6.0 | 20% | — |
| Error Handling | N/A | — | — |
| Maintainability | 7.0 | 15% | — |
| Requirements Fit | N/A | — | — |
| **Final Score** | | | **4.30** |

> N/A:**Error Handling**(本次 diff 未触碰任何错误处理路径)、**Requirements Fit**(无独立 spec)。
> 适用维度 Correctness/Security/Architecture/Maintainability (25/15/20/15,和 75%) 归一化后计算,Final = **4.30**。

### Result: FAIL ❌

任一条都足以判 FAIL,本次全中:
- **Security = 1.5**(< 7.0):可直接利用的 SQL 注入。
- **Correctness = 3.0**(< 7.0)。
- **Final = 4.30**(< 7.5)。
- 存在 1 个未解决 **Critical**。

### Issues

**Critical**
1. **SQL 注入(不可信 HTTP 参数直接拼入 SQL)** — `src/users.ts:2`
- Issue: 模板字符串 `${name}` 只是字符串插值,**不提供转义**,与原来的 `+` 拼接在安全性上完全等价,注入面分毫未减。
- Impact: 攻击者可构造 `name=' OR '1'='1`(dump 全表)、`name='; DROP TABLE users; --`(破坏数据)。可远程、无需认证直接利用。
- Fix: 用参数化查询:`db.query("SELECT * FROM users WHERE name = $1", [name])`。

**Minor**
1. **`SELECT *` 可能取出 `password_hash` 等敏感列** — 显式列出所需列。

### Assessment

**Ready to merge?** No
**Reasoning:** 这次「重构」把 `+` 换成模板字符串,对 SQL 注入毫无缓解作用,必须改为参数化查询后才能合并。
67 changes: 67 additions & 0 deletions skills/code-review/examples/sample-pass.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
# 样例:干净的小改动 → 稳健 PASS

> 这是 **code-review** 的一次真实运行产物(独立 reviewer agent 实际输出,未编辑)。
> 它展示了 N/A 机制:一个纯本地数值解析的改动,没有攻击面、也没有独立 spec,
> reviewer 把「安全性」「需求符合」诚实标为 N/A、把权重重分配给适用维度,
> 得到一个不靠硬凑的稳健 PASS。

## 输入

变更:`src/config.ts`,+11/-3 行。给端口解析加默认值与范围校验。无 spec、无测试。

```diff
-export function parsePort(raw: string): number {
- return parseInt(raw, 10);
-}
+export function parsePort(raw: string | undefined): number {
+ if (raw === undefined || raw.trim() === "") {
+ return 3000;
+ }
+ const port = parseInt(raw, 10);
+ if (Number.isNaN(port) || port < 1 || port > 65535) {
+ return 3000;
+ }
+ return port;
+}
```

## 输出(reviewer 报告)

### Scores

| Dimension | Score | Weight | Weighted |
|-----------|-------|--------|----------|
| Correctness | 8.5 | 25% | 2.50 |
| Security | N/A | — | — |
| Architecture | 8.0 | 20% | 1.88 |
| Error Handling | 7.5 | 15% | 1.32 |
| Maintainability | 7.0 | 15% | 1.24 |
| Requirements Fit | N/A | — | — |
| **Final Score** | | | **8.05** |

> **Security → N/A**:该函数只对一个已在本进程环境的字符串做数值解析与范围校验,不接触网络输入、反序列化、认证授权、命令/SQL/路径拼接。没有可评估的攻击面。
> **Requirements Fit → N/A**:本次没有任何独立 spec / 计划文档,从 diff 反推需求是循环论证。
> **权重重分配**:N/A 掉 Security(15%)+Requirements(10%),剩余 4 维 (25/20/15/15,和 75%) 各除以 0.75 归一化为 33.33/26.67/20/20。Final = 8.5×0.3333 + 8.0×0.2667 + 7.5×0.20 + 7.0×0.20 = **8.05**。

### Result: PASS ✅

- 适用维度 Correctness(8.5)、Architecture(8.0)、Error Handling(7.5)、Maintainability(7.0) 全部 ≥ 7.0 ✅
- Final 8.05 ≥ 7.5 ✅;无 Critical issue ✅

### Issues

**Minor**
1. **默认端口 3000 是重复出现的魔法数字** — `src/config.ts:3` 与 `:8`。抽 `const DEFAULT_PORT = 3000;`。
2. **`parseInt` 接受尾随垃圾** — `parseInt("80abc", 10)` 返回 `80`。如需严格,用 `/^\d+$/.test(raw.trim())` 预检。

### Assessment

**Ready to merge?** Yes(建议附带抽取 `DEFAULT_PORT` 常量)
**Reasoning:** 逻辑正确、边界精确、无安全/正确性风险;唯一真实改进点是消除重复魔法数字,不构成阻塞。

---

> 对照:在加入 N/A 机制之前,同一个改动会被迫给 Security / Requirements 硬凑分数
> (约 7.5 / 8.5),final ≈ 7.89 —— 一个**侥幸** PASS,余量全靠两个不适用维度撑。
> 而一个诚实给中位分(安全 5.0)的 reviewer 会让它直接 FAIL。N/A 机制消除了这种取决于
> 审查者宽严的不确定性。
27 changes: 20 additions & 7 deletions skills/linus-review/SKILL.md
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
---
name: linus-review
description: >
Linus Torvalds 风格的代码审查。像 Linux 内核邮件列表上的老爷子一样,
对代码给出直言不讳的锐评、犀利的吐槽和真正有价值的建议。
不留情面但每句话都有道理。
Linus Torvalds 风格的代码审查。不只是毒舌——它专审"好品味"(good taste):
找出能被消除的特殊情况和边界分支,指出"这个 if 本可以不存在",而不只是
抓 bug 或喷过度抽象。每句吐槽都指向真问题、给可执行的 fix。语气可调
(classic 火力全开 / civil 克制版)。
Triggers on: "linus review", "让 linus 看看", "毒舌 review", "linus-review",
"请 linus 老爷子来看看", "roast my code"
"请 linus 老爷子来看看", "roast my code", "审审代码品味", "这代码有没有 good taste"
---

# Linus Review — Linux 老爷子的代码审查
Expand All @@ -15,7 +16,8 @@ description: >
## 触发方式

```
/linus-review # 审查最近的修改
/linus-review # 审查最近的修改(默认 classic 火力全开)
/linus-review --tone=civil # 克制版:2018 CoC 之后的 Linus,一样犀利、对事不对人
/linus-review src/server/ask.rs # 审查指定文件
/linus-review HEAD~3..HEAD # 审查指定 commit 范围
```
Expand All @@ -28,7 +30,7 @@ description: >

## Dispatch

读取 `skills/linus-review/linus-reviewer.md` 获取完整的 Agent 人设提示词。
读取 `linus-reviewer.md`(与本文件同在 skill 目录下)获取完整的 Agent 人设提示词。注意:被 dispatch 出去的 Linus 审查员是独立 agent,不共享你的文件读取上下文——必须把人设全文**内联**进 dispatch prompt,不能只给路径

构建 dispatch prompt:

Expand All @@ -46,7 +48,10 @@ description: >
### 语言/框架
[自动检测]

开始审查。记住:Talk is cheap. Show me the code.
### 语气档位
[classic(默认火力)或 civil(克制版)——取决于用户有没有传 --tone=civil]

你是只读审查员:只阅读、吐槽、给建议,不修改任何文件、不执行变更。开始审查。记住:Talk is cheap. Show me the code.
```

## 输出格式
Expand All @@ -57,9 +62,17 @@ description: >
- **Linus 说**(总结性锐评 + 实际可行的改进建议)
- **评级**(从 "这代码应该被 revert" 到 "还行,勉强能接受")

## 示例

- [无 bug 但坏品味:可消除的特殊情况 → "Close, but no cigar."](examples/sample-good-taste.md) —— 真实运行产物,展示 good-taste 绝活(三栏 checklist 会放行的代码,它拦下了)
- [过度抽象 + 两个真 bug → "Please fix and resend."](examples/sample-report.md) —— 真实运行产物

## 注意

- 这是一个**娱乐性 + 实用性**兼具的 skill
- Agent 的毒舌是角色扮演,不是人身攻击
- 吐槽必须指向**真实的代码问题**,不能无脑骂
- 最终要给出**可操作的改进建议**
- **只读**:Linus 审查员只看代码、只吐槽、只给建议,**不碰你的文件、不执行变更**
- 范围确定不了(不在 git 仓库 / 无 diff)时,让用户直接给文件或 SHA,别猜
- **绝活是 good taste**:比起抓 bug,它更该指出"能被消除的特殊情况"——这是它区别于满网 Linus prompt 的地方,别退化成又一个只会骂过度抽象的毒舌玩具
72 changes: 72 additions & 0 deletions skills/linus-review/examples/sample-good-taste.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
# 样例:无 bug,但坏品味 —— 可消除的特殊情况

> 这是 **linus-review** 的一次真实运行产物(classic 档,未编辑)。
> 这段 `joinNames` **没有 bug、没有安全问题、没有过度抽象的类**——只盯这三栏的 reviewer
> 会干净放行它。但它埋了一个本不该存在的特殊情况(`if (i === 0)`)。这正是 linus-review
> 的绝活:好品味不是把边界情况处理对,而是重构到边界情况根本不存在。

## 输入

`src/joinNames.ts`,+10 行。

```ts
function joinNames(names: string[]): string {
let result = "";
for (let i = 0; i < names.length; i++) {
if (i === 0) {
result = names[i];
} else {
result = result + ", " + names[i];
}
}
return result;
}
```

## 输出(Linus 审查员报告,classic 档)

### 总体印象

又一个手写 `for` 循环拼字符串的,里面还塞了个 `if (i === 0)`。你想要的是 `"a, b, c"` 这种逗号分隔吧?这功能 JavaScript 在你出生之前就内置了,叫 `Array.prototype.join`。一行。你写了十行,还在循环里加了个分支来特判第一个元素。代码"能跑"——但"能跑"是我对洗衣机的要求,不是对工程师的要求。

### 具体问题

**joinNames.ts:3-9 — 那个 `if (i === 0)` 是可以彻底消除的**

这是这次审查的核心。你在特判"第一个元素",因为第一个元素前面不该有逗号。这就是那个经典的"边界情况"——和删链表头节点要特判 `head` 指针是同一类病。

**好品味的做法是让这个 `if` 根本不存在,而不是把它处理对。** 这里甚至不需要指向指针的指针那种花活,标准库早替你想好了:

```ts
function joinNames(names: string[]): string {
return names.join(", ");
}
```

`join` 的语义天生就是"在元素之间插分隔符",N 个元素插 N-1 个,第一个前面、最后一个后面都不插。"第一个元素特殊"这个情况在 `join` 的世界里**压根不存在**。你那个 `if` 不是被处理对了,是被**蒸发**了。

记住这个区别:把边界情况处理对,你以后每改一次这函数都得重新想一遍"第一个元素对不对";把边界情况消除掉,你永远不用再想它。坏品味会持续生产 bug,这才是它真正的代价。

### 还行的部分

类型签名是对的:`(names: string[]): string`,没有 `any`。空数组返回 `""` 行为也正确——虽然这正确性是那堆样板"碰巧"凑对的,不是设计出来的。

### Linus 说

这段代码没有 bug,没有安全洞,也没有 `AbstractNameJoinerFactory` 式的过度抽象——它的病不在那三个常规维度里。它的病是**品味**:用十行带分支的样板,去做一行标准库调用就该做完的事。

```ts
return names.join(", ");
```

方向是对的(它确实在 join names),细节是错的(用了最笨的方式,还把可消除的特殊情况留在那)。

Close, but no cigar.

Linus

---

> 同一段代码换 `--tone=civil` 跑,会去掉"洗衣机""大学生"这类人身挖苦,但问题
> (`if` 可消除)、修复(`join`)、评级(Close, but no cigar.)三项技术判断一字不变——
> 档位只改语气强度,不改结论。
Loading
Loading