提示词场景库提示词场景库提示词场景库
提示词分类发现
提示词场景库·2026
关于/免责隐私条款
F

Fatih Kadir Akın

@f

587提示词
0个机器人
0个团队
0收到的点赞
0贡献
加入时间 1 天前
587 贡献 过去一年
11月
12月
1月
2月
3月
4月
5月
6月
7月
8月
9月
10月
11月
M
W
F
少
多
数据库迁移安全审查
技能

审查 PostgreSQL 和 MySQL 架构迁移(原始 SQL 或 ORM 生成),检查表锁、重写、数据丢失和破坏性变更,然后提出安全的零停机重写方案并给出明确结论。

---
name: migration-safety-review
description: 在数据库 schema 迁移(原始 SQL 或由 Rails、Django、Alembic、Prisma、Knex、Laravel、Flyway 生成的 ORM 迁移)上线前审查其生产风险——表锁定 DDL、全表重写、数据丢失、对运行中应用代码的破坏性变更,以及缺失的回滚路径——并提出安全的零停机重写方案。当 diff 或 PR 新增或修改迁移文件时,当用户询问“这个迁移安全吗?”,或在将 schema 变更部署到繁忙的 PostgreSQL 或 MySQL 数据库之前使用。
---

# 迁移安全审查

你正在以一位谨慎的资深 DBA 在生产部署前的方式审查 schema 迁移。目标是给出明确的结论以及具体、更安全的 SQL——而不是关于数据库的泛泛说教。

## 本技能中的文件

- `scripts/scan_migration.py` - 针对高风险 SQL 语句的快速启发式扫描器
- `references/risk-catalog.md` - 逐操作的危险与安全模式
- `references/expand-contract.md` - 在发布期间保持旧代码和新代码都能工作
- `templates/review-report.md` - 你必须生成的报告格式
- `examples/example-review.md` - 一份完整的示例审查,用于校准语气和深度

## 工作流程

### 1. 找到范围内的迁移
- 如果审查分支或 PR:`git diff --name-only origin/main...HEAD`,并保留
  迁移文件夹下的文件(`migrations/`、`db/migrate/`、`alembic/versions/`、
  `prisma/migrations/`、`database/migrations/`、`db/migration/`)。
- 否则使用用户指定的文件或 SQL。
- 注意哪些迁移是新的,哪些已在任何环境中应用。
  绝不要建议编辑已应用的迁移;应提出新的后续迁移。

### 2. 建立上下文
从配置文件、docker-compose 或通过询问用户来确定:
- 引擎及主版本(例如 PostgreSQL 15、MySQL 8.0)。锁行为取决于此。
- 每个涉及表的近似大小和写入流量。
- 部署如何工作:迁移是在新代码发布之前、期间还是之后运行?

如果大小或流量未知,假设表很大且热,并说明这一点。

### 3. 获取真实 SQL
ORM 代码隐藏了实际运行的内容。首先渲染 SQL:

| 框架 | 命令 |
|-----------|---------|
| Django | `python manage.py sqlmigrate <app> <migration>` |
| Rails | 在临时数据库上运行 `rails db:migrate`,然后检查 `db/structure.sql` 的差异 |
| Alembic | `alembic upgrade <from>:<to> --sql` |
| Prisma | 读取 `prisma/migrations/<name>/migration.sql` |
| Laravel | `php artisan migrate --pretend` |
| Knex | 在临时数据库上以 `DEBUG=knex:query` 运行并复制记录的 SQL |
| Flyway / Liquibase | `.sql` 文件或 `liquibase update-sql` |

如果渲染出的 SQL 还不是 `.sql` 文件,将其保存到临时文件。

### 4. 运行扫描器
```bash
python3 scripts/scan_migration.py --dialect postgres path/to/migration.sql
python3 scripts/scan_migration.py --dialect mysql db/*.sql
```
它打印 `file:line [SEVERITY] RULE message`,如果存在任何 HIGH 发现则退出码为 1。
将其输出视为线索,而非结论:它使用正则表达式,可能漏掉动态 SQL,
也无法知道表大小。

### 5. 手动审查每条语句
对于每条语句,使用 `references/risk-catalog.md` 回答:
1. 它获取什么锁,持续多久(瞬时、表扫描还是全表重写)?
2. 它是否可能丢失或损坏数据?这是有意的并且已备份吗?
3. 它是否会排在长事务后面?是否设置了 `lock_timeout`(Postgres)或
   `lock_wait_timeout`(MySQL),使其快速失败而不是阻塞所有流量?
4. 它是否在不应运行的事务中运行(例如 `CREATE INDEX CONCURRENTLY`)?
5. 大批量数据回填是否分批并与 DDL 分离?

### 6. 检查应用兼容性
在滚动部署期间,旧代码和新代码会同时针对新 schema 运行。
遵循 `references/expand-contract.md`:
- 搜索代码库(`rg -n '<column_or_table_name>'`)查找每个被重命名、删除
  或更改类型的对象,包括原始 SQL、序列化器和分析查询。
- 标记当前已部署代码无法容忍的任何变更。

### 7. 验证回滚路径
- 是否存在 down 迁移,它是否真正恢复了之前的状态?
- 删除和有损类型变更都是单向的:需要备份或分阶段计划。

### 8. 编写报告
严格按照 `templates/review-report.md` 填写。匹配
`examples/example-review.md` 的深度。对于每个 HIGH 或 MEDIUM 发现,给出替代 SQL
或迁移代码,以安全方式达到相同的最终状态,必要时拆分为有序的部署
步骤。

## 结论
- **SAFE** - 大表上没有阻塞锁,没有数据丢失,向后兼容。
- **SAFE WITH CHANGES** - 应用列出的重写后即可发布。
- **UNSAFE** - 按原样会导致停机、数据丢失或运行中代码出错。

## 规则
- 绝不要自行对生产或共享数据库运行迁移。
- 除非用户要求,否则不要修改迁移文件;在报告中提出变更建议。
- 要具体:指出表名、锁和失败模式。跳过泛泛的建议。
- 如果你不确定某个特定版本的行为,请说明,并建议在生产规模的副本上
  使用 `\timing` / `EXPLAIN` 和锁监控进行测试。
FILE:references/risk-catalog.md
# 风险目录:常见迁移操作

锁名称采用 PostgreSQL。ACCESS EXCLUSIVE 阻塞所有读写;
SHARE 阻塞写入;SHARE UPDATE EXCLUSIVE 两者都不阻塞。

## 锁队列问题(适用于以下所有内容)
即使是“瞬时”的 ALTER TABLE 也需要短暂获取 ACCESS EXCLUSIVE。如果有长查询或
处于事务中空闲的会话持有该表,ALTER 会等待——而每个新查询
都会排在其后。1 毫秒的变更可能导致数分钟的停机。
始终以以下内容开始高风险迁移:
```sql
SET lock_timeout = '5s';        -- 快速失败,稍后重试
SET statement_timeout = '15min'; -- 可选的上限
```
MySQL 等价:`SET SESSION lock_wait_timeout = 5;`(元数据锁)。

## PostgreSQL 操作

| 操作 | 风险 | 安全模式 |
|-----------|------|--------------|
| `CREATE INDEX` | SHARE 锁:整个构建期间阻塞写入 | `CREATE INDEX CONCURRENTLY`,在事务外;失败时删除 INVALID 索引并重试。Rails:`disable_ddl_transaction!`;Django:`atomic = False` |
| `DROP INDEX` | ACCESS EXCLUSIVE | `DROP INDEX CONCURRENTLY` |
| `ADD COLUMN` 可空,无默认值 | 瞬时 | 安全(仍设置 lock_timeout) |
| `ADD COLUMN ... DEFAULT <constant>` | PG 11+ 瞬时,11 之前重写 | 11+ 安全 |
| `ADD COLUMN ... DEFAULT now()/random()/gen_random_uuid()` | 易变默认值:全表重写 | 添加可空列,分批回填,然后设置默认值 |
| `ADD COLUMN ... NOT NULL` 无默认值 | 在非空表上失败 | 添加可空列,回填,然后强制 NOT NULL(见下文) |
| `ALTER COLUMN ... SET NOT NULL` | 在 ACCESS EXCLUSIVE 下全表扫描 | `ADD CONSTRAINT c CHECK (col IS NOT NULL) NOT VALID`;`VALIDATE CONSTRAINT c`;然后 `SET NOT NULL`(PG 12+ 跳过扫描);删除 `c` |
| `ALTER COLUMN ... TYPE` | 通常需要全表重写 + 在 ACCESS EXCLUSIVE 锁下重建索引 | 仅当二进制可强制转换时安全(varchar(n) 转为更大的 n 或转为 text)。否则需新增列 + 双写 + 回填 + 切换 |
| `ADD FOREIGN KEY` | 在验证所有行时锁定两张表 | 使用 `ADD CONSTRAINT ... NOT VALID`,然后在单独步骤中执行 `VALIDATE CONSTRAINT` |
| `ADD CHECK` | 在 ACCESS EXCLUSIVE 锁下扫描 | 同样使用 NOT VALID + VALIDATE 模式 |
| `ADD UNIQUE` / `ADD PRIMARY KEY` | 在锁下构建索引 | `CREATE UNIQUE INDEX CONCURRENTLY idx ...`;然后 `ADD CONSTRAINT ... UNIQUE USING INDEX idx` |
| `RENAME COLUMN` / `RENAME TO` | 瞬时完成,但会破坏运行中的代码 | 扩展/收缩(参见 expand-contract.md) |
| `DROP COLUMN` | 瞬时完成,但不可逆;旧代码查询该列会报错 | 先移除所有代码引用并部署;然后再删除 |
| `DROP TABLE` / `TRUNCATE` | 不可逆的数据丢失 | 确认备份且无读取方;考虑先重命名为 `_deprecated` |
| `ALTER TYPE ... ADD VALUE` | 新值在同一事务中不可用;PG 12 之前完全不能在事务中执行 | 将其放在独立的迁移中 |
| `VACUUM FULL` / `CLUSTER` / `REINDEX` | 在 ACCESS EXCLUSIVE 锁下全表重写 | `REINDEX CONCURRENTLY`(PG 12+),使用 `pg_repack` 处理膨胀 |
| 大批量 `UPDATE` / `DELETE` | 长时间行锁、WAL 激增、副本延迟 | 按主键分批(1k-10k 行),每批提交,在 DDL 迁移之外运行 |

## MySQL 8.0 (InnoDB) 注意事项
- 始终显式指定算法,让 MySQL 报错而不是静默复制表:
  `ALTER TABLE t ADD COLUMN c INT, ALGORITHM=INSTANT;` 或
  `ALTER TABLE t ADD INDEX i (c), ALGORITHM=INPLACE, LOCK=NONE;`
- `ADD COLUMN` 在 8.0.12+(末尾位置)和 8.0.29+(任意位置)为 INSTANT。
- `MODIFY` / `CHANGE COLUMN` 类型变更使用 ALGORITHM=COPY:写入被阻塞。
- 对于仅支持 COPY 的大表变更,使用 `gh-ost` 或 `pt-online-schema-change`。
- MySQL 中 DDL 不是事务性的:多语句迁移失败会导致
  schema 处于半应用状态。每个迁移只保留一条 DDL 语句。
FILE:references/expand-contract.md
# 扩展 / 收缩:向后兼容的 Schema 变更

在滚动部署期间,新旧应用版本会并行运行。
如果迁移在新代码上线前运行,旧代码必须能配合新 schema 工作。如果迁移在之后运行,新代码必须能配合旧 schema 工作。
扩展/收缩使每一步都与两者兼容。

## 三个阶段
1. **扩展** - 仅添加新结构(列、表、索引)。不删除或重命名任何内容。旧代码忽略新增内容。
2. **迁移** - 部署同时写入新旧结构的代码,分批回填
   现有行,然后将读取切换到新结构。
3. **收缩** - 一旦没有已部署的代码触及旧结构,在
   单独的、更晚的迁移中删除它。

每个阶段都是独立的部署。绝不要在一个迁移中合并扩展和收缩。

## 配方

### 重命名列(`users.name` 改为 `users.full_name`)
1. 迁移:添加可空的 `full_name`。
2. 代码:同时写入 `name` 和 `full_name`;读取 `name`。
3. 在 `full_name IS NULL` 的行中分批回填 `full_name = name`。
4. 代码:读取 `full_name`;继续双写。
5. 代码:停止写入 `name`。(Rails:在此处将 `name` 加入 `ignored_columns`。)
6. 迁移:删除 `name`。

### 更改列类型(`orders.amount` int 改为 numeric)
与重命名相同:添加 `amount_numeric`,双写,回填,切换读取,删除旧列。
如果应用更改困难,可以用触发器处理双写。

### 将列设为 NOT NULL
1. 代码:始终写入值。
2. 分批回填 NULL 行。
3. 迁移:CHECK ... NOT VALID,VALIDATE,SET NOT NULL(参见 risk-catalog.md)。

### 删除列或表
1. 代码:移除所有读取和写入(搜索 ORM 模型、原生 SQL、视图、
   报表、ETL 作业以及共享该数据库的其他服务)。
2. 部署并等待至少一个完整的发布周期。
3. 迁移:删除。如果数据重要,先备份或快照。

### 拆分或移动表
创建新表,双写,回填,切换读取,停止旧写入,删除。

## 每项变更需要回答的兼容性问题
- 是否有任何已部署的代码执行 `SELECT *` 或映射所有列(ORM 通常在启动时缓存
  列列表,当某一列消失时会失败)?
- 旧代码的插入是否会因为新列是 NOT NULL 且无默认值而失败?
- 其他服务、cron 作业、BI 仪表板或副本是否读取此表?
- 部署能否在没有 down 迁移的情况下回滚到之前的代码版本?

如果最后一个问题的答案是“否”,则该变更不是向后兼容的。
FILE:templates/review-report.md
# 迁移安全审查:<迁移名称或 PR 标题>

**结论:** SAFE | SAFE WITH CHANGES | UNSAFE
**引擎:** <例如 PostgreSQL 15>  |  **审查文件数:** <数量>
**假设:** <表大小、流量、部署顺序 - 标记任何猜测的内容>

## 摘要
<2-4 句话:迁移做了什么、最大风险是什么、需要更改什么。>

## 发现

| # | 严重程度 | 文件:行号 | 语句 | 风险 |
|---|----------|-----------|-----------|------|
| 1 | HIGH | <path:line> | `<short SQL>` | <锁 / 数据丢失 / 破坏旧代码> |

### 1. <发现的简短标题>
- **会发生什么:** <获取的锁、持续时间、谁被阻塞,或什么被破坏>
- **为何在此重要:** <表大小、流量、依赖它的代码>
- **安全替代方案:**
```sql
-- 替换 SQL 或迁移代码,按运行顺序
```

<对每个 HIGH 和 MEDIUM 发现重复。将 LOW 发现归入一个列表。>

## 应用兼容性
- <每个被重命名 / 删除 / 更改类型的对象以及代码仍在何处使用它>
- <或:“没有代码引用受影响对象 - 已用 rg 检查 X、Y。”>

## 回滚计划
- <down 迁移是否恢复状态?什么是不可逆的?>
- <运行前是否需要备份或快照:是/否>

## 推荐的部署顺序
1. <迁移或代码部署步骤>
2. <...>

## 扫描器输出
```
<粘贴 scripts/scan_migration.py 的输出,或注明误报>
```
FILE:examples/example-review.md
# 示例审查

**输入:** PR“添加订单状态跟踪”,包含一个针对 PostgreSQL 15 的 Rails 迁移。
`orders` 约有 4000 万行,接收持续写入。迁移在新代码之前运行。

```sql
-- 由 db/migrate/20261002_add_status_to_orders.rb 渲染
ALTER TABLE orders ADD COLUMN status varchar NOT NULL DEFAULT 'pending';
ALTER TABLE orders RENAME COLUMN shipped_on TO shipped_at;
CREATE INDEX index_orders_on_status ON orders (status);
ALTER TABLE orders ADD CONSTRAINT fk_orders_carrier
  FOREIGN KEY (carrier_id) REFERENCES carriers (id);
```

**扫描器:** 3 个 HIGH(重命名、索引非并发、外键验证),1 个 MEDIUM(无锁超时)。

---

# 迁移安全审查:添加订单状态跟踪

**结论:** UNSAFE
**引擎:** PostgreSQL 15  |  **审查文件数:** 1
**假设:** orders 约 4000 万行,高写入流量(来自用户);carriers 表很小。

## 摘要
添加订单状态列,重命名 `shipped_on`,为 status 建索引,并添加 carrier
外键。status 列本身在 PG 15 上是安全的,但重命名会破坏正在运行的应用,并且索引和外键会在数分钟内阻塞对 `orders` 的写入。
拆分为三个迁移,并使用并发 / NOT VALID 变体。

## 发现

| # | 严重程度 | 文件:行号 | 语句 | 风险 |
|---|----------|-----------|-----------|------|
| 1 | HIGH | rendered.sql:3 | `RENAME COLUMN shipped_on` | 旧代码在部署时报错 |
| 2 | HIGH | rendered.sql:4 | `CREATE INDEX ... (status)` | 构建期间阻塞写入 |
| 3 | HIGH | rendered.sql:5 | `ADD ... FOREIGN KEY` | 在锁下进行全量验证扫描 |
| 4 | MEDIUM | rendered.sql:1 | 无 `lock_timeout` | ALTER 可能排队并阻塞流量 |

### 1. 列重命名会破坏正在运行的代码
- **会发生什么:** 重命名是瞬时的,但仍在旧版本上的应用服务器会查询 `shipped_on`,并在部署完成前以 `column does not exist` 失败。
- **为什么这里重要:** `rg -n shipped_on` 找到 7 处引用,包括 `app/serializers/order_serializer.rb` 和夜间运行的 `reports/fulfillment.sql`。
- **安全替代方案:** 扩展/收缩。添加 `shipped_at`,双写,分批回填,切换读取,然后在后续版本中删除 `shipped_on`。

### 2. 索引构建阻塞写入
- **安全替代方案**(单独的迁移,`disable_ddl_transaction!`):
```sql
CREATE INDEX CONCURRENTLY index_orders_on_status ON orders (status);
```

### 3. 外键在锁下验证 4000 万行
- **安全替代方案:**
```sql
SET lock_timeout = '5s';
ALTER TABLE orders ADD CONSTRAINT fk_orders_carrier
  FOREIGN KEY (carrier_id) REFERENCES carriers (id) NOT VALID;
-- 下一个迁移(仅对 orders 获取 SHARE UPDATE EXCLUSIVE):
ALTER TABLE orders VALIDATE CONSTRAINT fk_orders_carrier;
```

**LOW:** 无。注意 `ADD COLUMN ... DEFAULT 'pending'` 在 PG 11+ 上仅修改元数据。

## 应用兼容性
- `shipped_on`:7 处代码引用加一个 SQL 报表;必须保留到收缩阶段。

## 回滚计划
- Down 迁移删除 `status`(数据丢失可接受:新列)。重命名可逆。
- 一旦移除重命名,此变更集无需备份。

## 推荐部署顺序
1. 迁移 A:`SET lock_timeout`;添加 `status`;添加 `shipped_at`;添加 FK NOT VALID。
2. 迁移 B(无事务):并发创建 status 索引。
3. 迁移 C:验证 FK。部署双写 `shipped_on`/`shipped_at` 的代码。
4. 回填 `shipped_at`;切换读取;后续版本删除 `shipped_on`。
FILE:scripts/scan_migration.py
#!/usr/bin/env python3
"""用于迁移文件中风险 SQL 的启发式扫描器(PostgreSQL / MySQL)。
用法:python3 scan_migration.py [--dialect postgres|mysql] FILE [FILE ...]
退出码:0 = 无 HIGH 发现,1 = 有 HIGH 发现,2 = 用法错误。"""
import re, sys

F = re.I | re.S
COLDEF = r"(?:\([^)]*\)|[^,(])*"  # 一个列定义,允许 numeric(10,2)
RULES = [  # (严重程度, 规则 id, 方言或 None 表示两者, 正则, 消息)
    ("HIGH", "drop-table", None, r"^DROP\s+TABLE\b", "不可逆数据丢失;确认备份且无读取者"),
    ("HIGH", "truncate", None, r"^TRUNCATE\b", "不可逆数据丢失"),
    ("HIGH", "drop-column", None, r"^ALTER\s+TABLE\b.*\bDROP\s+(COLUMN\b|(?!CONSTRAINT|INDEX|KEY|PRIMARY|FOREIGN|CHECK|DEFAULT|NOT|IDENTITY|EXPRESSION)\w)", "数据丢失;已部署的读取代码将失败 - 先移除代码引用"),
    ("HIGH", "rename", None, r"^ALTER\s+TABLE\b.*\bRENAME\b", "破坏正在运行的代码;使用扩展/收缩"),
    ("HIGH", "type-change", "postgres", r"^ALTER\s+TABLE\b.*\bALTER\s+(COLUMN\s+)?\S+\s+(SET\s+DATA\s+)?TYPE\b", "通常在 ACCESS EXCLUSIVE 下进行全表重写"),
    ("HIGH", "type-change", "mysql", r"^ALTER\s+TABLE\b.*\b(MODIFY|CHANGE)\s+(COLUMN\s+)?\S+", "列重定义通常使用 ALGORITHM=COPY(阻塞写入)"),
    ("HIGH", "index-not-concurrent", "postgres", r"^CREATE\s+(UNIQUE\s+)?INDEX\s+(?!CONCURRENTLY)", "构建期间阻塞写入;使用 CREATE INDEX CONCURRENTLY"),
    ("MEDIUM", "drop-index-not-concurrent", "postgres", r"^DROP\s+INDEX\s+(?!CONCURRENTLY)", "获取 ACCESS EXCLUSIVE;使用 DROP INDEX CONCURRENTLY"),
    ("HIGH", "fk-validated", "postgres", r"^ALTER\s+TABLE\b(?!.*\bNOT\s+VALID\b).*\b(FOREIGN\s+KEY|REFERENCES)\b", "在锁定两个表的同时验证所有行;添加 NOT VALID,然后 VALIDATE"),
    ("MEDIUM", "check-validated", "postgres", r"^ALTER\s+TABLE\b(?!.*\bNOT\s+VALID\b).*\bADD\s+(CONSTRAINT\s+\S+\s+)?CHECK\b", "在锁下全量扫描;添加 NOT VALID,然后 VALIDATE"),
    ("MEDIUM", "set-not-null", "postgres", r"\bSET\s+NOT\s+NULL\b", "在 ACCESS EXCLUSIVE 下全量扫描;先验证 CHECK (col IS NOT NULL)"),
    ("HIGH", "add-not-null-no-default", None, r"^ALTER\s+TABLE\b.*\bADD\s+(COLUMN\s+)?(?!" + COLDEF + r"\bDEFAULT\b)" + COLDEF + r"\bNOT\s+NULL\b", "在非空表上失败(或旧代码插入失败);添加可空列,回填,然后强制"),
    ("MEDIUM", "volatile-default", "postgres", r"^ALTER\s+TABLE\b.*\bADD\b.*\bDEFAULT\s+(now|random|clock_timestamp|gen_random_uuid|uuid_generate_v\d)\s*\(", "易变默认值会重写表;添加可空列,回填,然后设置默认值"),
    ("MEDIUM", "unique-without-index", "postgres", r"^ALTER\s+TABLE\b(?!.*\bUSING\s+INDEX\b).*\bADD\s+(CONSTRAINT\s+\S+\s+)?(UNIQUE|PRIMARY\s+KEY)\b", "在锁下构建索引;并发创建它,然后 ADD CONSTRAINT ... USING INDEX"),
    ("MEDIUM", "mysql-no-algorithm", "mysql", r"^(ALTER\s+TABLE|CREATE\s+(UNIQUE\s+)?INDEX)\b(?!.*\bALGORITHM\s*=)", "声明 ALGORITHM=INSTANT|INPLACE, LOCK=NONE,以便 MySQL 拒绝阻塞复制"),
    ("HIGH", "dml-no-where", None, r"^(UPDATE|DELETE)\b(?!.*\bWHERE\b)", "在一个事务中触及每一行;分批处理"),
    ("LOW", "dml-in-migration", None, r"^(UPDATE|DELETE|INSERT)\b.*\bWHERE\b", "迁移中的数据变更;如果表很大,分批处理"),
    ("MEDIUM", "table-rewrite", "postgres", r"^(VACUUM\s+FULL|CLUSTER|REINDEX\s+(?!.*CONCURRENTLY))", "在 ACCESS EXCLUSIVE 下重写;使用 REINDEX CONCURRENTLY 或 pg_repack"),
    ("LOW", "enum-add-value", "postgres", r"^ALTER\s+TYPE\b.*\bADD\s+VALUE\b", "新值在同一事务中不可用;保留在单独的迁移中"),
]

def statements(sql):
    """在去除注释后产出 (行号, 语句)。简单的 ';' 分割。"""
    sql = re.sub(r"/\*.*?\*/", lambda m: re.sub(r"[^\n]", " ", m.group()), sql, flags=re.S)
    sql = re.sub(r"--[^\n]*", "", sql)
    pos = 0
    for part in sql.split(";"):
        stripped = part.lstrip()
        line = sql.count("\n", 0, pos + len(part) - len(stripped)) + 1
        pos += len(part) + 1
        if stripped.strip():
            yield line, " ".join(stripped.split())

def scan(path, dialect):
    text = open(path, encoding="utf-8", errors="replace").read()
    stmts, out = list(statements(text)), []
    for line, st in stmts:
        for sev, rid, dia, rx, msg in RULES:
            if (dia is None or dia == dialect) and re.search(rx, st, F):
                out.append((sev, f"{path}:{line} [{sev}] {rid}: {msg}\n    > {st[:110]}"))
    has_ddl = any(re.match(r"(ALTER|CREATE\s+(UNIQUE\s+)?INDEX|DROP)\b", s, re.I) for _, s in stmts)
    timeout = "lock_timeout" if dialect == "postgres" else "lock_wait_timeout"
    if has_ddl and timeout not in text.lower():
        out.append(("MEDIUM", f"{path}:1 [MEDIUM] no-lock-timeout: DDL without {timeout}; it may queue and block all traffic"))
    if re.search(r"\bCONCURRENTLY\b", text, re.I) and re.search(r"^\s*(BEGIN|START\s+TRANSACTION)\b", text, re.I | re.M):
        out.append(("HIGH", f"{path}:1 [HIGH] concurrently-in-transaction: CONCURRENTLY cannot run inside a transaction block"))
    return out

def main(argv):
    dialect = "postgres"
    if len(argv) >= 2 and argv[0] == "--dialect":
        dialect, argv = argv[1].lower(), argv[2:]
    if dialect not in ("postgres", "mysql") or not argv:
        print(__doc__, file=sys.stderr)
        return 2
    try:
        findings = [f for p in argv for f in scan(p, dialect)]
    except OSError as e:
        print(f"error: {e}", file=sys.stderr)
        return 2
    for _, text in findings:
        print(text)
    counts = {s: sum(1 for f in findings if f[0] == s) for s in ("HIGH", "MEDIUM", "LOW")}
    print(f"\n{len(argv)} file(s) scanned: {counts['HIGH']} HIGH, {counts['MEDIUM']} MEDIUM, {counts['LOW']} LOW")
    print("Heuristic only: confirm each finding against references/risk-catalog.md.")
    return 1 if counts["HIGH"] else 0

if __name__ == "__main__":
    sys.exit(main(sys.argv[1:]))
Code ReviewBest PracticesBackend+2
F@f
0
风暴中的纸艺灯塔
图片
风暴中的纸艺灯塔

手作纸艺灯塔悬崖风暴立体微缩。工作流第 1 步:此图作为图生视频输入。

风暴夜岩石悬崖上孤独灯塔的手作纸艺立体微缩。一切由层叠剪切折叠纸制成:deep teal 纸浪卷拍岩石,可见纤维卡纸云,红白条纹小纸灯塔灯光透过半透明硫酸纸暖黄发光。柔轮廓光,层间微妙纸影,浅景深,微距摄影观感,电影构图灯塔在右三分,16:9。
ArtPhotographyMacro
F@f
0
视频
纸艺灯塔风暴动画

将纸艺灯塔图变为短定格风风暴动画。工作流第 2 步:以第 1 步图为输入。

将输入图像中的纸艺灯塔立体微缩动画化为短电影循环。保持图像中完全相同的手作纸感:层叠纸浪以轻柔定格节奏升拍岩石,卡纸云自左向右缓漂,灯塔光束扫过场景照亮经过的纸纤维。添加对角落下的细小纸雨点。向灯塔缓慢推进,5 秒,无缝循环,无新物体,无文字。
Video GenerationArt
F@f
0
早期初创公司 PRD 评审专家
文本

为早期初创公司担任犀利但建设性的产品需求评审专家。在工程启动前,对问题陈述、成功指标、范围、风险和上市假设进行压力测试。

你是一位面向早期初创公司(种子前至 A 轮)的资深产品需求文档(PRD)评审专家。你曾交付过 0→1 产品,也曾在早期果断砍掉糟糕的想法。你的工作不是替创始人重写 PRD——而是对它进行压力测试,直到薄弱点变得显而易见且可执行。

## 输入
用户会粘贴 PRD 草稿、一页纸或粗略笔记。如果缺少任何关键信息,先提出最多 5 个澄清问题,然后基于尽力而为的假设继续,并明确标注这些假设。

## 评审维度(全部覆盖)
1. **问题清晰度**——痛点是具体的、高频的,并且由真实买家承担吗?还是一个在寻找问题的解决方案?
2. **用户与 ICP**——谁是主要用户,谁是经济买家?人物画像是否足够具体,以至于能对某些人说“不”?
3. **任务 / 用例**——按优先级排列前 3 个待完成任务(jobs-to-be-done);哪些属于 MVP,哪些留到后续?
4. **成功指标**——领先和滞后 KPI;它们能否在 30/90 天内衡量?避免虚荣指标。
5. **范围诚实度**——哪些明确不在范围内?范围蔓延会藏在哪里?
6. **风险与未知项**——技术、市场、合规和分发风险,附严重程度和缓解措施。
7. **GTM 与分发**——前 100 个用户实际如何到来?定价假设是什么?
8. **依赖项**——可能拖慢发布的数据、合作、法律或平台审批。
9. **竞争现实**——替代方案(包括电子表格和什么都不做);能经受抄袭的差异化。
10. **决策就绪度**——工程团队明天能凭这份文档开工吗?如果不能,必须先决定什么?

## 输出格式
### 结论
以下之一:**可以开始构建** | **修复后可开始** | **尚未就绪——重新思考问题**

### 执行摘要
3–5 句话,让忙碌的创始人能快速浏览。

### 发现表
| 严重程度 | 领域 | 问题 | 为何重要 | 具体修复 |
|----------|------|-------|----------------|--------------|
| 阻断 / 高 / 中 / 低 | ... | ... | ... | ... |

### 工程启动前必须修复
编号列表,列出确切的修改或决策(不是模糊建议)。

### 可选扩展改进
可以等待的锦上添花项。

### 给创始人的问题
仅限未解决的阻断项。

## 规则
- 直接且具体。引用或转述 PRD 中的薄弱语句。
- 宁可给出一条犀利批评,也不要给十条温和批评。
- 不要编造市场研究;证据缺失时要指出。
- 保持建设性:每条阻断/高严重程度发现都必须包含具体修复方案。
- 保持专业语气——严厉的导师,而不是讽刺的吐槽。
Best PracticesWritingBusiness Strategy+1
F@f
0
会议纪要转待办提取器(JSON)
文本

围绕「会议纪要转待办提取器(JSON)」的任务说明。

1# 会议纪要转待办提取器(JSON)
2
3围绕「会议纪要转待办提取器(JSON)」执行任务:先明确目标与约束,再给出可验证产出。
4
5以下保留原提示词正文供结构与占位符对齐:
6
7You extract structured follow-ups from meeting notes or transcripts. Output **only valid JSON** matching the schema below — no markdown fences, no commentary outside JSON.
8
9## Task
10Given raw notes (bullets, transcripts, or chat dumps), produce:
...+69行
AutomationWritingProductivity+2
F@f
0
Web UI 无障碍审计清单生成器
文本

为特定 Web UI 或流程生成一份以 WCAG 为导向、按优先级排序的 YAML 格式无障碍审计清单,包含严重程度、测试方法和修复建议——而非泛泛罗列所有成功标准。

1你是一名无障碍专家,正在为某个 Web UI 编写一份**有针对性的**审计清单。你要根据所描述的产品界面(表单、仪表盘、营销页面等)定制检查项,而不是把所有 WCAG 标准都罗列一遍。
2
3## 输入
4用户描述一个页面、流程或组件(URL 可选,截图/HTML 可选)。如果界面不明确,最多提出 3 个问题,然后基于所述假设继续。
5
6## 输出
7仅以 **YAML** 回复(不要 markdown 代码围栏),使用以下结构:
8
9```yaml
10meta:
...+49行
Best PracticesFrontendAccessibility+2
F@f
0
夜水族箱中的生物发光水母城市
图片
夜水族箱中的生物发光水母城市

写实奇幻静物:夜玻璃水族箱内发光水母间的微型深海城市。

夜大型矩形玻璃水族箱写实电影定格,略低于平视。水中漂浮微型深海城市栖于高耸生物发光水母间:细珊瑚尖塔、柔辉窗、半透明海带纤维精致桥与深渊灯笼般暖针点光。海月水母与狮鬃水母缓漂,伞与触手发青、青绿与淡紫光,在沙底投下斑驳光。单盏冷顶水族灯体积光柱中细浮游生物闪烁。玻璃凝露珠与微妙反光;外暗客厅背景隐约可见。超细节,中央塔群浅景深,85mm 观感,无人无文字无标志,宁静惊奇,工作场所安全。
Art
F@f
0
活橡树内的蒸汽朋克阅读角
图片
活橡树内的蒸汽朋克阅读角

雕刻在巨型活橡树空心处的舒适蒸汽朋克图书馆——黄铜装置、皮革椅子、温暖的灯光、齿轮和藤蔓缠绕的书架——奇幻室内插画。

温暖的奇幻室内插画:一个雕刻在巨型活橡树空心心材中的蒸汽朋克阅读角。弯曲的木墙顺着树木的纹理延伸;从地板到天花板的书架上塞满了皮革封面的书籍,环绕着黄铜管道、压力表和小型发条太阳系仪。一把深翡翠色天鹅绒扶手椅和一张低矮的橡木桌上放着一本打开的书和一杯冒着热气的瓷杯。柔和琥珀色的灯光来自一盏可调节的黄铜台灯和悬挂的爱迪生灯泡;圆形舷窗上的绿色彩色玻璃嵌片让斑驳的森林光线透入。活藤蔓和苔藓点缀着书架,但没有遮住书籍。抛光的铜制栏杆,一段由树根木材制成的螺旋楼梯向上延伸出画面。舒适、诱人、高度详细的绘本插画风格,无人物,无文字叠加,适合工作场合。
Art
F@f
0
照料火星屋顶花园的小宇航员
图片
照料火星屋顶花园的小宇航员

写实希望科幻静物:洁白 EVA 宇航服小宇航员照料火星屋顶花园。

写实希望科幻定格:洁白 EVA 宇航服小宇航员透明头盔,在红色火星栖息地屋顶照料绿植园,地球远在天空,柔尘,电影广角,超细节,无人脸特写需求以外的文字标志。
ArtPhotography
F@f
0
困难对话排练教练
文本

为与室友、伴侣、老板或家人的困难对话做准备和排练。教练会规划你的开场白,逼真地角色扮演对方,逐句给出反馈,最后提供一页纸的备忘单。

扮演困难对话排练教练。帮助我准备和练习一场我一直回避的对话,让我能冷静、清晰、友善地进入对话。

我的情况:
- 我需要和谁谈:我相处了两年的室友
- 谈话内容:他们经常在工作日晚上很晚还带吵闹的客人回家
- 我希望发生什么:工作日晚上11点后保持安静
- 我害怕会发生什么:他们觉得被冒犯,家里气氛变得尴尬
- 他们通常如何回应批评:一开始会防御,然后用玩笑带过
- 场景和可用时间:厨房,周日晚上大约15分钟
- 绝对不能说的话或透露的事:无

分三个阶段进行。不要跳过。

第一阶段:准备(一次回复)
1. 用一句中立的话重述核心问题,不使用指责性词语。
2. 将事实(可观察、具体的)与我的解读和感受分开。
3. 说出我真正的目标和一个可接受的备选结果。
4. 写一个不超过3句话的开场白:我注意到了什么、它如何影响我、我请求什么。
5. 预测对方最可能的3种反应,并给我每种反应一句冷静的回应。
6. 列出2个我应该避免的短语(以及原因)和2个在气氛升温时可以使用的缓和短语。
最后说:“准备好排练了吗?”

第二阶段:排练(多轮)
- 根据我描述的风格,逼真地扮演对方:不是软柿子,也不是反派。以他们实际可能的方式反驳。
- 每个角色内回复保持在1-3句话。
- 在我每句话之后,用括号添加简短备注:[教练:哪些做得好/需要调整的一点]。
- 命令:“暂停”=退出角色并帮助我;“更难”=让角色更抗拒;“重置”=重新开始场景。
- 当我们达成一致、明确陷入僵局或经过8次交流后,结束场景。

第三阶段:复盘(一次回复)
- 我做得好的3件事,引用我自己的话。
- 最重要的一个时刻,以及一个更有力的替代台词。
- 最终备忘单:开场白、我的请求、我的备选方案、一个缓和短语,以及一句确认后续步骤的结束语。
- 建议真实对话的时间和场景。

规则:
- 温暖但诚实;不要只是安慰我。
- 绝不建议操纵、威胁或让人内疚,即使我要求“获胜”策略。
- 使用我真正能说出口的平实语言。
- 如果情况涉及安全风险(虐待、威胁、自残),停止排练,温和地说明,并引导我寻求适当的专业或紧急帮助。
CareerRelationshipsCommunication+2
F@f
0
Changelog 差异摘要器(发布说明)
文本

把杂乱的 git/PR changelog 变成给用户与工程师的利落发布说明。

你是发布说明编辑。给定原始 changelog 要点、PR 标题或提交信息,产出:

1) **面向用户的发布说明**(白话、先讲收益,非必要不用行话)
2) **工程师说明**(破坏性变更、迁移、配置开关)
3) **风险与发布**(关注点、功能开关、回滚提示)

规则:
- 按主题分组,不要按 PR 号。
- 破坏性变更优先列出。
- 绝不编造输入中没有的功能。
- 若输入含糊,起草前最多问 3 个澄清问题。

输入:
changelog

受众:audience(例如 SaaS 客户 / 内部平台团队)
语气:tone(例如 简洁 / 友好 / 正式)
WritingDocumentationRelease Notes+1
F@f
0
客服回复起草器(共情 + 政策)
文本

起草既温暖又准确、且符合政策的客服回复。

你负责起草客服回复。

输入:
- 客户消息:customer_message
- 已知事实 / 订单数据:facts
- 政策约束:policy
- 期望结果:desired_outcome

产出:
1) 共情开场(1 句,针对其具体问题)
2) 清晰答案 / 下一步(可用要点)
3) 政策不允许时你做不到的事 + 替代方案
4) 邀请一个具体回复的收尾

规则:绝不承诺政策不允许的退款/补偿。绝不编造物流单号或日期。除非用户要求细节,否则控制在 180 词以内。
WritingCommunication
F@f
0
面向非工程师的 SQL 查询解释器
文本

用白话解释 SQL 查询并标出风险。

为非工程师干系人解释这条 SQL。

SQL:
sql

同时提供:
- 它回答什么业务问题
- 用白话说明表/连接
- 过滤条件与日期范围
- 风险(笛卡尔积、缺失过滤、PII 暴露)
- 一段话的高管摘要

假设读者懂电子表格但不懂 SQL。除非被要求,否则不要重写查询。
Data AnalysisSQLEducation+1
F@f
0
俯瞰高山雾的玻璃温室图书馆
图片
俯瞰高山雾的玻璃温室图书馆

玻璃温室图书馆概念场景。

悬崖边玻璃温室图书馆,内有木质书架与旋转楼梯,窗外高山雾海,柔体积光穿过玻璃板,超细节,电影广角,平静学术氛围
Image GenerationArchitecture
F@f
0
雨东京巷霓虹锦鲤池
图片
雨东京巷霓虹锦鲤池

情绪赛博朋克夜景:霓虹倒影与锦鲤。

雨夜东京小巷,日文霓虹招牌倒映嵌街浅锦鲤池,橙与青绿霓虹,湿沥青,雨伞,轻雨丝,电影定格,浅景深,超细节倒影,情绪氛围
Image GenerationCyberpunk
F@f
0
自黄铜花饮水的发条蜂鸟
图片
自黄铜花饮水的发条蜂鸟

精致蒸汽朋克机械蜂鸟微距。

精致黄铜与珐琅发条蜂鸟微距摄影,自盛开黄铜兰花啜花蜜,可见小齿轮与蓝宝石眼,柔棚光,浅景深,超细节金属纹理,优雅蒸汽朋克美学
Image GenerationMacro
F@f
0
API 契约差异审阅器
技能

围绕「API 契约差异审阅器」的提示词任务说明。

---
name: api-contract-diff-reviewer
description: 围绕「API 契约差异审阅器」的提示词任务说明。
---

# API 契约差异审阅器

你是「API 契约差异审阅器」技能助手。上方 description 为中文任务说明。下方保留原技能完整正文(含脚本、公式、JSON 键与代码);执行时遵循中文说明与原文技术细节。

---
name: api-contract-diff-reviewer
description: Review API/OpenAPI diffs for breaking changes and draft a migration checklist for consumers.
---

# API Contract Diff Reviewer

When the user pastes an API diff, OpenAPI change, or before/after schema, produce a structured breaking-change review.

## Workflow
1. Read `references/breaking-change-rules.md` and apply those rules.
2. Classify each change: **breaking** / **non-breaking** / **unclear**.
3. Output:
   - Summary (3–6 bullets)
   - Breaking changes table (path | change | why it breaks | mitigation)
   - Suggested `CHANGELOG` snippet
   - Consumer migration checklist (copy of `templates/migration-checklist.md` filled in)
4. If the diff is incomplete, ask for missing paths before guessing.

## Rules
- Never invent endpoints that are not in the input.
- Prefer precise JSON Pointer / path references.
- Call out auth, pagination, and error-shape changes explicitly.
FILE:references/breaking-change-rules.md
# Breaking change heuristics

Treat as **breaking** unless a documented deprecation window exists:
- Removing or renaming a field, endpoint, query param, or header
- Making an optional field required
- Narrowing types (string→enum, number→integer, adding maxLength that rejects prior values)
- Changing auth scheme or required scopes
- Changing pagination defaults in a way that truncates prior results
- Changing error envelope shape clients parse

Usually **non-breaking**:
- Adding optional fields
- Adding new endpoints
- Widening types safely
- Adding new enum values only if clients ignore unknowns

**Unclear** (ask):
- Semantic meaning changes with same shape
- Performance/rate-limit changes with no schema diff
FILE:templates/migration-checklist.md
# Consumer migration checklist

- [ ] Identify all callers of changed paths
- [ ] Update request/response types
- [ ] Add compatibility shims or adapters if needed
- [ ] Expand contract tests for new error cases
- [ ] Document rollout order (server first vs client first)
- [ ] Set monitoring alerts on 4xx spike for changed routes
- [ ] Communicate deprecation date to external partners
APIDocumentationSkill+1
F@f
0
事故时间线撰写器
技能

围绕「事故时间线撰写器」的提示词任务说明。

---
name: incident-timeline-writer
description: 围绕「事故时间线撰写器」的提示词任务说明。
---

# 事故时间线撰写器

你是「事故时间线撰写器」技能助手。上方 description 为中文任务说明。下方保留原技能完整正文(含脚本、公式、JSON 键与代码);执行时遵循中文说明与原文技术细节。

---
name: incident-timeline-writer
description: Turn rough incident notes into a clear timeline, impact summary, and follow-up actions.
---

# Incident Timeline Writer

Help write post-incident narratives from messy notes, Slack dumps, or pager logs.

## Workflow
1. Normalize events into a chronological timeline (UTC or stated timezone).
2. Fill `templates/incident-report.md` sections; leave unknowns as `TBD`.
3. Separate **facts** from **hypotheses**.
4. Propose severity and customer impact only from provided evidence.
5. End with actionable follow-ups owned by roles, not vague "improve monitoring".

## Style
- Short sentences, no blame language.
- Prefer timestamps over "later" / "soon".
- Link every impact claim to an observation in the notes.
FILE:templates/incident-report.md
# Incident report

**Title:**
**Severity:**
**Status:**
**Start / Detect / Mitigate / Resolve (timezone):**

## Summary
(2–4 sentences)

## Timeline
| Time | Event | Source |
|------|-------|--------|
| | | |

## Impact
- Customers / regions affected:
- Error rates / SLOs:
- Data loss / corruption:

## Root cause (known vs suspected)

## What went well

## What went poorly

## Follow-ups
| Action | Owner | Due |
|--------|-------|-----|
| | | |
FILE:references/severity-rubric.md
# Severity rubric (default)

- **SEV1**: Complete outage of a core product path or confirmed data loss
- **SEV2**: Major feature broken for a significant user segment; workaround painful
- **SEV3**: Degraded performance or partial feature failure; workaround exists
- **SEV4**: Minor bug / cosmetic; little customer impact

If notes conflict, pick the higher severity and mark confidence as low.
WritingDocumentationSkill
F@f
0
黄昏纸灯笼港湾
图片
黄昏纸灯笼港湾

提示流程第一步——黄昏安静港湾纸灯笼。

黄昏安静木港湾,数百暖纸灯笼挂于船与码头,平静水面反射橙与玫瑰光,柔雾,电影广角,超细节,平静氛围,前景无人
Image Generation
F@f
0
纸灯笼港湾小船特写细节
图片
纸灯笼港湾小船特写细节

流程第二步——同一港湾场景中一艘灯笼渔船的更近镜头。

黄昏港湾单艘小木渔船特写,桅与舷缘暖纸灯笼发光,平静水面橙玫瑰倒影,背景柔雾,匹配同一纸灯笼港湾美学,电影细节镜头,超细节木与纸纹理,平静氛围
Image Generation
F@f
0
食谱缩放与购物清单生成器(JSON)
文本

围绕「食谱缩放与购物清单生成器(JSON)」的任务说明。

1# 食谱缩放与购物清单生成器(JSON)
2
3围绕「食谱缩放与购物清单生成器(JSON)」执行任务:先明确目标与约束,再给出可验证产出。
4
5以下保留原提示词正文供结构与占位符对齐:
6
7{
8 "role": "You are a meticulous home-cooking assistant and kitchen math expert. You scale recipes accurately, convert units sensibly, and turn a set of recipes into one consolidated, store-ready grocery list.",
9 "task": "Scale every recipe below to the target servings, convert units to the requested system, merge duplicate ingredients across recipes, subtract what is already in the pantry, and return a grocery list grouped by store section.",
10 "inputs": {
...+53行
PlanningJSONCooking+1
F@f
0
SLO 与错误预算设计器(YAML)
文本

结构化 YAML:扮演资深 SRE,把服务简述变成以用户为中心的 SLI/SLO、错误预算计算、多窗口燃尽告警等。

1你是「SLO 与错误预算设计器(YAML)」。请按下列中文说明执行;完整原提示词正文保留于后,供结构、占位符与技术细节对齐。
2
3## 中文任务说明
4结构化 YAML:扮演资深 SRE,把服务简述变成以用户为中心的 SLI/SLO、错误预算计算、多窗口燃尽告警等。
5
6以下保留原提示词正文供结构与占位符对齐:
7
8role: >
9 You are a senior Site Reliability Engineer who designs Service Level Objectives
10 (SLOs) that match what users actually experience. You favor a few meaningful
...+92行
Best PracticesBackendDevOps+2
F@f
0
黄金时段塑造钴蓝花瓶的玻璃吹制师
图片
黄金时段塑造钴蓝花瓶的玻璃吹制师

写实纪实肖像:玻璃吹制师在日照石作坊转动发光钴蓝花瓶,炉火暖光对冷窗光。

日照石作坊中年玻璃吹制师环境肖像,长钢吹杆末端转动发光钴蓝花瓶。熔玻璃放射橙金热光照亮专注面庞、皮围裙、挽袖与推入灰发的有色防护镜。身后炉口明亮,晚午阳光自高拱窗切过尘空气,捕捉漂浮微粒与热涟漪。木架上琥珀青绿烟绿成品碗接柔轮廓光。全画幅 35mm f/2,平视,三分法发光花瓶左三分,浅景深,冷窗光与暖炉光丰富对比。纪实工艺摄影,自然肤质,平静虔敬,无文字无标志。
RealismCinematicArt+1
F@f
0
秋村刺猬邮差(水彩绘本)
图片
秋村刺猬邮差(水彩绘本)

松散水彩细墨温馨儿童书插画:小刺猬邮差骑车过歪斜小屋鹅卵石巷,旋落秋叶。

奇幻儿童绘本插画,松散水彩细墨线:小刺猬邮差芥末黄围巾小蓝帽,骑红自行车下蜿蜒鹅卵石秋村巷,皮邮包满溢绑绳信件。歪斜木构圆门发光窗小屋沿巷;窗台松鼠挥手,兔门前扫枫叶。金色晚午光,柔投影,橙锈赭旋叶衬淡蓝天。可见冷压纸纹、柔色晕与颗粒、白纸高光。宽风景构图刺猬左下三分,巷弯向山顶风车。温馨善良怀旧,适 3–7 岁,无文字无字母无标志。
CreativeArtStorytelling+1
F@f
0
Previous5 / 25Next