这不是一篇 AI 吹水文。我会把翻车的地方、设计文档容易制造的错觉、以及我这次踩到的安全盲区都摊开讲。
先说背景。上周我用 Claude Code 的 Superpowers 插件体系,从零撸了一个图片占位服务(类似 placehold.co),整个流程走下来——头脑风暴、设计文档、实现计划、子代理逐任务执行——花了几个小时,最终 50 个测试用例全绿,6 种图片格式全部支持。
听起来很完美对吧?
但当我真正把它跑起来,准备部署的时候,发现了一个让我后背发凉的问题: 设计文档里写的并发限制是 10,但它没告诉你 10 个 4000×4000 的 AVIF 请求同时打过来,内存峰值可能接近 1GB。 而我的 Dockerfile 里赫然写着 --max-old-space-size=512。这不是严格意义上的容器内存上限,它限制的是 V8 old space,但对一个大量使用 Sharp/libvips 原生内存的服务来说,已经足够提醒我:这里需要压测,而不是凭感觉上线。
这就是 AI 驱动开发最容易被忽视的问题: 它会在设计文档里把安全风险和资源限制列得明明白白,给你一种"都考虑到了"的安全感。但列出来和解决了,中间隔着一条鸿沟。
Superpowers 是什么
先交代一下背景。Claude Code 的 Superpowers 是一套插件化的开发工作流,核心流程大致是四步:
头脑风暴 → 写设计文档 → 写实现计划 → 子代理逐任务执行
每一步都有对应的 skill。brainstorming 负责梳理需求,writing-plans 把设计文档拆成带完整代码的任务列表,subagent-driven-development 给每个任务派一个独立子代理去实现,完成后自动做两轮 review。
听起来像流水线,用起来也确实像。但真正有意思的是每个环节暴露出来的问题。
头脑风暴:AI 问问题的深度取决于你问问题的深度
brainstorming skill 有个规则:一次只问一个问题。一开始我觉得这节奏太慢了,但走完才发现,这种"逼迫式"的节奏让你没法跳过模糊地带。
第一个反转:技术选型
我最开始说用 Canvas 绘制,AI 推荐了 @napi-rs/canvas。看起来没什么问题,Canvas 绑 Skia,主流方案。
但后来聊到格式支持的时候,我问了一句:"AVIF 支持吗?"AI 才说 @napi-rs/canvas 底层是 Skia,Skia 的 AVIF 编码支持不完整。而我要求同时支持 AVIF 和 GIF 动画,方案才转向 Sharp 这条路:静态格式用 Sharp,GIF 再配合专门的编码器。
如果我当时没追问格式兼容性,AI 不会主动告诉我它的推荐有盲区。 它会很自信地给你一个方案,直到你问到它兜不住的地方。
最终方案改成了 Sharp + SVG 混合架构:所有图片先用字符串拼出 SVG(零依赖),PNG/JPEG/WebP/AVIF 由 Sharp 转换;GIF 则先用 Sharp 渲染出原始帧,再交给 gif-encoder-2 编码。SVG 格式直接返回字符串,不走 Sharp。
这个架构本身很漂亮——SVG 是唯一数据源,Sharp 负责渲染和格式转换,GIF 编码器只处理动画输出。但漂亮归漂亮,后面的内存问题就埋在这里。
第二个反转:竞品调研
我问"尺寸放路径还是放 query 参数"的时候,AI 搜索了所有竞品:placehold.co、dummyimage.com、placeholders.dev——全部用路径放尺寸,没有例外。而且 dummyimage.com 的文档还特意提到了 Content-Length 响应头对旧版客户端的重要性。
这种调研如果让我自己做,大概率就是拍脑袋决定。AI 在这件事上的价值不是"帮你做决定",而是"告诉你整个行业怎么做的,你不用猜"。
第三个反转:安全分析主动提了,但力度不够
AI 在头脑风暴阶段就主动做了威胁分析:XSS 注入、资源耗尽、参数欺骗、路径遍历,列了一个矩阵。每一步都有对应的缓解措施。当时我觉得:嗯,安全这块考虑得挺全。
事后证明,这个"挺全"的感觉是最大的陷阱。 具体后面说。
设计文档:写得漂亮和跑得稳是两码事
头脑风暴结束后,AI 把所有讨论整理成了一份设计文档。涵盖 API 设计、项目架构、安全设计、内存安全、环境变量——每个章节看起来都很专业。
但有几个细节,代码跑起来之后才暴露:
1. 字体策略:文档和实现完全是两个东西
设计文档里关于字体是这么写的:M+ Fonts 嵌入 SVG 的 base64 @font-face。理由也充分——开源字体,支持拉丁 + CJK,base64 嵌入保证跨环境一致性。
但实际实现中,这一步完全没做。最终用的是 font-family="sans-serif",依赖系统字体。
为什么?一个支持 CJK 的 M+ Fonts 字体文件 2MB,base64 编码后膨胀到 2.7MB。如果每次请求都把 2.7MB 的字体嵌进 SVG 再喂给 Sharp,那响应体积和内存开销完全是另一个量级。设计文档没有评估这个成本,只是在理想条件下给了一个方案。
这不是 AI 的错——是没有人追问"这个方案的实际代价是什么"。
2. 并发限制:文档说信号量,代码是全局计数器
内存安全章节写得很详细:像素上限 16MP、文本硬限制 200 字符、并发限制、请求超时、Node.js 的 --max-old-space-size、PM2/K8s 基于内存的重启策略。看起来面面俱到。
但并发限制的实际实现是这样的:
let activeCount = 0;
export function concurrencyLimiter() {
return async (c: Context, next: Next) => {
if (activeCount >= config.maxConcurrency) {
// 返回 503 错误图片
}
activeCount++;
try { await next(); } finally { activeCount--; }
};
}
一个模块级变量 activeCount。它能做单进程内的粗粒度并发拒绝,但没有队列,不会按请求成本加权,进程重启就归零,集群部署下每个进程独立计数——离真正的全局限流还差很远。
设计文档写了"信号量",代码实现是计数器。这两个东西在并发控制语义上不是一回事。
3. GIF 动画:设计文档没评估极端尺寸
GIF 的方案是生成两帧(正常帧 + 微亮覆盖层),制造闪烁效果。技术上可行,但仔细算一下内存:
-
4000×4000 的 RGBA 原始数据一帧是 64MB
-
两帧是 128MB
-
gif-encoder-2 的 neuquant 颜色量化还要额外分配内存
-
如果并发 10 个 GIF 请求,光原始帧数据就超过 1.28GB
再加上 Sharp 的解码缓冲区、AVIF 编码的峰值内存,如果把服务放进内存较紧的容器里,OOM 风险非常高。但设计文档里的并发限制给了一个固定的 10,没有根据图片尺寸做动态调整。
这说明 AI 做的安全分析是"静态"的——它知道应该限制并发,但没有把并发阈值和请求成本关联起来。10 个 100×100 的 PNG 和 10 个 4000×4000 的 GIF,对内存的压力完全不是一个数量级。
实现计划:细致到让人窒息,但也带来了新问题
writing-plans skill 生成的计划有 15 个任务,每个任务包含完整的实现代码——不是伪代码,是复制粘贴就能跑的代码。计划遵循 TDD:先写测试确认失败,再写实现确认通过,最后 git commit。
任务粒度极细。举个例子,Task 4(参数校验器)的步骤:
-
写 18 个测试用例
-
跑测试,确认全部失败
-
写实现代码
-
跑测试,确认全部通过
-
git commit
50 个测试用例覆盖了参数校验、SVG 生成、图片转换、缓存头、全链路集成。如果纯手写,我大概率会偷懒跳过一半。
但代价是: 计划文档 1500 行。 信息密度很低——每个任务里大段的代码占满了屏幕。适合机器执行,不适合人阅读。如果你想在动手前通读一遍计划,做好心理准备。
子代理执行:两个翻车现场
subagent-driven-development 的工作方式是每个任务派一个独立子代理实现,完成后自动做两轮 review(先查规格合规,再查代码质量)。
先说亮点。
子代理在实现 validators 的时候发现了一个测试顺序 bug:validateSize(4000, 4001) 这个用例里,height=4001 同时触发了高度上限和像素上限两个错误条件。如果先检查高度范围,返回的错误码和预期的像素超限错误不一致。子代理自己调换了检查顺序——把像素上限检查提前到 height 范围检查之前——测试全绿。
这种边界条件我自己写也未必会注意到。子代理因为严格按测试用例驱动,反而能抓到。
再说翻车。
第一个翻车:路由匹配。实现计划里用的是 Hono 的正则路由:
app.get('/:size{[0-9]+x[0-9]+\\.[a-zA-Z]+}', handleImageRequest);
单元测试全部通过——因为 vitest 用的是 app.request() 内部方法,不经过真实的 HTTP 服务器栈。
但启动真实服务器用 curl 测试, 所有带正则的路由返回 404。
我没有继续深挖到底是路由 pattern 写法、版本差异,还是 @hono/node-server 适配层的问题;但现象很明确:正则 pattern 在 app.request() 里能匹配,换到真实 HTTP 请求就不行。
最终改成了 catch-all 路由 + 手动解析:
app.get('/*', (c, next) => {
if (c.req.path === '/') return next();
return handleImageRequest(c);
});
单元测试全绿 ≠ 生产可用。 尤其是路由、中间件、序列化/反序列化这些和运行时强相关的代码,AI 生成的方案在测试环境里可能完全正常,换到真实环境就暴露差异。
第二个翻车:入口文件的副作用。index.ts 在底部直接调了 serve(),但集成测试文件 import { app } from '../src/index.js' 触发了模块加载,导致每次跑测试都启动一个真实的 HTTP 服务器,端口冲突报 EADDRINUSE。
修复方案是加条件判断:
if (process.argv[1]?.includes('index.ts') || process.argv[1]?.includes('index.js')) {
serve({ fetch: app.fetch, port: config.port, hostname: config.host });
}
这种"副作用在 import 时触发"的问题是 Node.js 的经典坑。AI 生成的代码默认是为"直接运行"写的,不考虑被 import 的场景。如果你用 monorepo 或者测试框架 import 了入口文件,必踩。
安全与资源:设计文档不会替你兜底的部分
这部分是整篇文章最想讲的。因为 AI 在设计阶段的"安全感"太有欺骗性了。
设计阶段讨论了但没解决透的问题
1. 内存占用的动态特性
前面算过了,10 个 4000×4000 的 AVIF 并发请求,内存峰值可能接近 1GB。正确的做法不是只给一个固定并发数,而是:根据请求的图片尺寸和格式动态计算预估内存,动态调整并发上限。小图可以放更多并发,大图严格限流。
AI 知道要做并发限制,但没有继续把并发限制的阈值和请求参数关联起来。
2. GIF 的内存放大效应
gif-encoder-2 的 neuquant 算法需要构建颜色调色板,内存开销会随着帧数、尺寸和颜色复杂度上升。设计文档只写了"2 帧微闪",没有评估编码器在极端尺寸下的资源消耗。如果服务上线后有人用脚本批量请求 4000×4000 的 GIF,不需要传统意义上的 DDoS,也可能把服务打到 OOM。
3. SVG XSS 的防护深度不够
当前实现是 HTML 实体编码:
function escapeHtml(text: string): string {
return text
.replace(/&/g, '&')
.replace(/</g, '<')
.replace(/>/g, '>')
.replace(/"/g, '"');
}
这能防当前 ?text= 文本节点里的 <script> 注入。当前实现里,用户主要能控制文本和颜色;颜色参数走的是正则白名单,所以 <foreignObject>、onload、xlink:href="javascript:..." 这些向量暂时没有直接注入入口。
真正的问题在于扩展性:SVG 的攻击面不止文本节点。如果后续加了更多用户可控的 SVG 属性(比如自定义字体、自定义尺寸标注、图片 URL),每个新参数都是一个新的注入面。
安全防护最容易出错的地方不是"没做",而是"做了一半,给了自己一种做完了的错觉"。
4. 速率限制只存在设计文档里
威胁矩阵里写了速率限制,优先级标的是"平台级(Serverless)/ 可选中间件(自托管)"。实际上最终代码里没有速率限制的实现。对于自托管部署来说,这意味着任何人都可以无限制请求生成图片。虽然 Sharp 的处理速度本身就是一种自然限速,但这不构成安全策略。
Express 生态有 express-rate-limit,Hono 虽然可以接第三方 middleware 或者交给平台层限流,但这里没有把它落到代码里。这个遗漏不是语法问题,而是部署策略问题:公共服务不能只靠"图片生成比较慢"来当限流。
设计阶段没落地或没讨论的问题
1. 请求超时只存在配置里
设计文档里写了 request timeout,配置文件里也有 REQUEST_TIMEOUT=5000,但最终路由和图片生成代码没有真正使用这个配置。也就是说,一个慢请求并不会因为超过 5 秒被主动中断。对于 Sharp/AVIF/GIF 这种可能吃 CPU 和内存的路径来说,这比"没写配置"更危险——因为你会以为它已经生效了。
2. Content-Length 响应头缺失
生成的图片响应没有 Content-Length 头。虽然 HTTP/1.1 可以用 Transfer-Encoding: chunked,但某些旧版 HTTP 客户端和 CDN 回源时需要 Content-Length 来判断响应完整性。dummyimage.com 的文档专门强调了这一点——AI 做了竞品调研,但调研结果里的这个细节没有体现在实现中。
3. 没有健康检查端点
设计文档把健康检查放在了 Phase 2(延期功能)。但实际部署到 K8s 或 Docker Compose 时,没有 /health 端点意味着编排系统无法做存活探测。对于一个要上线的服务,健康检查不属于"锦上添花",属于"基本生存条件"。
4. Dockerfile 拷贝了 public 但没拷贝 fonts
Dockerfile 里有 COPY public ./public,但 fonts/ 目录没有被拷贝。虽然当前实现用的是系统字体,但如果后续有人想把 M+ Fonts 的字体嵌入加回来,Dockerfile 不会报错——字体文件不存在,运行时会在某个请求上静默失败,而不是启动时报错。
社区的现状和我的看法
我最近看到的很多关于"AI 写代码"的讨论,常常会滑向两个极端:一派说 AI 替代初级工程师只是时间问题,另一派说 AI 生成的代码都是屎山不能用。
两种说法都不对。
AI 最大的价值不是"替代人写代码",而是"强制走完流程"。 没有 AI 的情况下,多少人会为一个图片占位服务写设计文档?会写 50 个测试用例?会做竞品调研?会在实现前把威胁矩阵列出来?
大部分人不会。不是能力问题,是时间问题。DDL 压在头上的时候,设计文档和测试永远是第一个被牺牲的。
AI 驱动开发的核心优势不是快,是 不让你跳过该做的事。
但同时,AI 在这类工作流里有一个很容易被低估的问题:它做的安全分析和性能评估经常是 静态的、孤立的 。它知道"应该限制并发",但未必会继续追问"不同尺寸的图片并发时内存压力差几个数量级"。它知道"应该防 XSS",但未必会把 SVG 后续扩展时的攻击面讲透。它能把设计文档写成一篇满分作文,但不会自动告诉你哪些段落是"愿望",哪些段落已经落地了。
这也是为什么"AI 替代初级工程师"这个说法至少没那么简单。 初级工程师的价值不只在于写代码,更在于他们会在生产环境出问题之后长记性,会把团队里的事故经验变成下一次设计时的直觉。AI 如果没有被明确喂进这些上下文,不会自动继承上一轮的翻车教训。
Superpowers 这套工作流的核心洞见其实是: 把 AI 定位为"流程执行者"而不是"决策者"。 它不去替你判断方案好坏,而是逼你把判断过程走完。brainstorming 的"一次一个问题"就是最典型的设计——节奏是人在控制,AI 只负责在每个节点把信息铺开。
但反过来,这套流程对简单项目来说太重了。我这个图片占位服务的核心逻辑不到 300 行,但走完 brainstorming → 设计文档 → 实现计划 → 子代理执行,流程本身花的时间不比写代码少。 如果你做的是一周内能手工写完的项目,用全流程可能得不偿失。 头脑风暴那一步值得保留,后面的计划和执行可以按需裁减。
最终成果
功能上没什么问题,6 种格式全绿:
PNG → 200 image/png
JPEG → 200 image/jpeg
WebP → 200 image/webp
AVIF → 200 image/avif
GIF → 200 image/gif (2 帧闪烁动画)
SVG → 200 image/svg+xml
错误参数 → 400 image/png(返回错误图片,不破坏 <img> 标签)
文档首页 → 200 text/html
架构简洁,代码量不大,测试覆盖完整。作为一个 evening project,质量远超预期。
但如果要上线,至少还要补:速率限制、基于尺寸的动态并发控制、真正生效的请求超时、健康检查端点、Content-Length 头,以及一轮真实流量下的内存压力测试。
几点实在的建议
如果你也想试试这种工作流,说几条掏心窝子的:
1. 安全和资源问题不要只看设计文档。 AI 会在文档里把该列的列出来,让你觉得"都考虑到了"。但列出来和解决了之间,需要你自己去验证——尤其是并发场景下的内存、极端参数组合下的性能、以及第三方库在边界条件下的行为。
2. 路由、中间件、配置这些和运行时强相关的东西,必须在真实环境跑一遍冒烟测试。 app.request() 通过不代表 curl 能通。AI 生成的代码默认是给"理想运行时"写的,真实运行环境的兼容性问题可能覆盖不到。
3. 设计文档里的 Phase 2 和配置项要认真对待。 被推迟的功能往往是安全性和可运维性的底线——健康检查、速率限制、请求超时、字体策略。如果你打算上线,这些不是"以后再说"的事。
4. 头脑风暴是整个流程里 ROI 最高的环节。 不是为了听 AI 的建议,而是利用它做竞品调研和方案对比的能力。多问"竞品怎么做的"和"这个方案在什么情况下会出问题"。
5. 项目越小,流程越要裁剪。 一个 weekend project 不需要 1500 行的实现计划。brainstorming 做足,后面的按自己节奏来就行。
项目地址是: place-image ,感兴趣可以star或者fork搭一个玩玩。遇到内存问题别慌,大家都一样。