文章指出 Vibe Coding 在生产级项目中会导致状态循环破坏、内存泄漏和代码不可维护,提出用规格驱动开发替代。
在过去的几个月里,"凭感觉编程"(Vibe Coding)一词在网络和技术社区中迅速走红:坐在编辑器前,向最新一代的语言模型抛出模糊的提示词,看着代码神奇地出现,然后通过反复试错不断调整,直到它"看起来能运行"。
对于周末脚本或一次性原型来说,这可能感觉就像魔法。但当你尝试构建生产级软件时——具有真正的并发、跨平台架构、严格的契约和超低延迟性能——"凭感觉编程"就会撞上现实的墙壁:
存在一种专业、严谨且可扩展的替代方案:AI 辅助的规格驱动开发(Spec-Driven Development,SDD)。
在本文中,我们将详细解析我们用来构建 Virtual Studio Companion 的精确方法论、架构和操作提示词:这是一套使用 Kotlin Multiplatform(KMP)和 Compose Multiplatform 构建的跨平台套件,将 Android 移动设备转变为用于 Windows 上 OBS Studio 的超低延迟传输摄像头,集成了使用 Skia 的原生视频监控、QR 码 ISO/IEC 18004、双向遥测以及客户端零 Java 依赖。
所有代码及其规格均可在开源仓库中获取:👉 GitHub: dhbernardo/virtual-studio-companion(Tag v1.0.1)
在传统开发中,源代码通常是唯一的真实来源("代码即文档")。随着 AI 智能体的出现,这一前提变得危险:模型在有限的上下文窗口上运行,往往优化短期局部解决方案而不考虑全局完整性。
在规格驱动开发(SDD)中:
规格是活的文档,是唯一的真实来源(Single Source of Truth)。如果生产代码没有在规格中明确建模,则不得编写或修改任何一行。
flowchart LR
A["📜 spec.md<br/>(Requisitos EARS & Criterios BDD)"] --> B["📐 plan.md<br/>(Arquitectura & Diagramas)"]
B --> C["✅ tasks.md<br/>(Tareas Atómicas 'Hecho cuando:')"]
C --> D["🧪 TDD Estricto<br/>(Test Primero -> Código -> Build)"]
D --> E["📦 Conventional Commit<br/>(Trazabilidad 1:1)"]
当在集成测试中发现平台约束时(例如 Android 的 Cleartext 策略或 socket 并发竞争条件),我们不会创建零散任务或混乱的补丁(例如 T09:修复 socket,T10:按钮补丁)。
创建零散任务会将仓库变成混乱的补丁历史记录,并削弱主要规格。在 SDD 中,规范的做法是在其原始上下文中细化现有规格和任务(spec.md、plan.md、tasks.md)。这样,任何工程师或智能体在数月后审计系统时,都会找到精确、自包含且忠实于现实的规格。
为了让 AI 智能体在高自主性下工作而不偏离轨道,需要不可协商的护栏。在我们的项目中,这通过两个基础文件实现:
规定了任何提示词或技术决策都不得违反的 8 条基本法则:
定义了 monorepo 的拓扑结构、常用命令(./gradlew allTests、./gradlew :desktopApp:desktopTest)、SDD 工作流程以及明确禁止即兴编写代码。
系统的每个模块或功能都存在于 specs/ 中,分解为三个互补的构件:
specs/003-desktop-windows-host/
├── spec.md # 业务需求与验收标准
├── plan.md # 技术设计、架构图与决策
└── tasks.md # 增量可验证任务清单
需求使用 EARS(Easy Approach to Requirements Syntax)语法编写,验收标准使用 BDD(Given-When-Then)格式:
### RF-003:OBS Studio 的自动集成、对称控制和弹性
- **类型:** 事件驱动
- **定义:** 当 Host 检测到 OBS Studio 正在运行(或用户点击"连接 OBS")时,系统必须连接到 4455 端口,实现 IObsConnector,解决 SHA256 挑战,并创建/更新 Browser Source "Virtual Studio Camera"。
- **验收标准:**
- **鉴于** OBS Studio 已主动连接
- **当** 用户在仪表板中点击"断开 OBS"
- **那么** Host 必须干净地关闭与 OBS 的 socket,过渡到 DISCONNECTED 状态并取消重试,保持移动端传输和 Skia 回显监控完好无损
包含 Mermaid 时序图,对 streamer、Ktor Host、Android 应用和 OBS Studio 之间的通信流程和状态转换进行建模:
sequenceDiagram
autonumber
actor Streamer as Streamer (Windows)
participant Host as Desktop Host (Compose + Ktor)
participant Phone as Android App
participant OBS as OBS Studio (:4455)
Host->>Host: 绑定 Ktor (8080-8090) 并生成带 TTL (120s) 的 QR 码
Phone->>Host: 扫描 QR 码并发送 HANDSHAKE_INIT(token)
Host->>Phone: HANDSHAKE_ACK(会话已验证)
Host->>OBS: CreateInput / SetInputSettings ("browser_source")
Phone->>Host: 通过 (/ws/stream) 传输 JPEG 二进制帧
Host->>Host: 使用 Skia 解码 (Image.makeFromEncoded)
Host->>Streamer: 在 Compose Desktop 上渲染 16:9 回显监控
Host->>OBS: 提供 multipart 流 (/stream/mjpeg)
每个原子任务定义了一个严格且不可动摇的条件。复选框 [x] 不是凭直觉标记的;只有当条件满足且终端认证后才会标记:
- [x] **T04:实现 `ObsWebSocketAdapter` 实现 `IObsConnector`**
*覆盖的需求:* `RF-003`、`RNF-003`
*完成条件:* 适配器在 `ws://localhost:4455` 上解决认证握手,使用重放缓冲区(`replay = 1`)实现 `KtorObsTransport`,防止帧 `op: 0` 丢失,支持显式断开连接(`disconnect()`)且 finally 中无循环,幂等配置 Browser Source `Virtual Studio Camera` 源,并通过 `T03` 的测试。
这是 AI 不会降级项目的秘密所在。我们为软件生命周期的每个时刻设计了四个关键提示词:
当规格、计划和任务完全定义好,并希望智能体以资深水平严格执行时使用:
以资深开发者的身份,自主且顺序地执行 `[NUMERO-NOMBRE-SPEC]/tasks.md` 中所有待处理的任务。
开始之前:
1. 读取 `docs/constitution.md` 和 `AGENTS.md`(遵守所有约束:零硬编码字符串、宽松许可证、`shared/commonMain` 独立性)。
2. 读取 `[NUMERO-NOMBRE-SPEC]/plan.md` 和 `[NUMERO-NOMBRE-SPEC]/spec.md`。
对 `tasks.md` 中的每个待处理任务,严格循环执行:
1. 识别:
- 识别需求(RF/RNF)和"完成条件"。
- 对任务分类:
a) 是领域逻辑、协议、用例还是 ViewModel?-> 应用严格 TDD(第 2A 步)。
b) 是 Build、CI/CD、Token 或资源基础设施?-> 应用构建验证(第 2B 步)。
2. 按类型执行:
- [步骤 2A - TDD]:
1. 先编写相关的单元测试(`*Test.kt`),覆盖正常情况、边界情况和错误情况。
2. 实现最小代码使测试通过。
3. 运行相应的测试命令(例如 `./gradlew :shared:allTests` 或 `./gradlew :desktopApp:desktopTest`)。
- [步骤 2B - 基础设施 / 配置]:
1. 实现配置文件、构建脚本或资源。
2. 运行验证或编译命令(例如 `./gradlew assembleDebug` 或 linters)。
3. 响应中的证据:
- 展示终端输出片段以证明成功(`BUILD SUCCESSFUL` 或 `Tests PASSED`)。
4. 任务更新:
- 仅当满足"完成条件:"时,才在 `tasks.md` 中标记 `[x]` 复选框。
5. GIT 提交:
- 使用英文 Conventional Commits 格式创建提交(根据 `git-conventions`)。
完成 SPEC 中的所有任务后:
- 展示需求追溯矩阵。
- 确认未违反 `docs/constitution.md` 中的任何原则。
Prompt 2:逐任务执行(逐步监督模式)
适合偏好每步都仔细控制再前进的开发者:
扮演一名高级开发者。
阅读 docs/constitution.md、AGENTS.md 以及三件套 specs/[模块]/spec.md、plan.md 和 tasks.md。
仅执行 tasks.md 中的 [任务ID] 任务。
tasks.md 中标记 [x]。
Prompt 3:关键诊断("零 premature 补丁")
当手动或自动化测试发生错误时,不要立刻让 AI 去修复。先隔离它让它分析根本原因:
[在此粘贴终端日志、异常堆栈、观察到的异常行为或测试上下文]
识别问题,不要先提出解决方案。如果需要更多上下文、堆栈或更详细的描述,请提出请求。
为什么这个 prompt 至关重要?如果你对 AI 说"我遇到了这个错误,修一下",它会假设一个仓促的解决方案,几乎总是包含修补本地代码,破坏架构契约。强制 AI 局限于诊断后,它会分析调用流、识别竞态条件或状态不一致,你作为开发者可以在它改动任何文件之前验证技术假设。
Prompt 4:SDD 规范式重构(无遗留任务)
一旦问题被识别并达成共识,使用此 prompt 更新规范:
创建一个计划来重构现有规范和任务、计划,避免创建孤立任务,因为这会使项目变成混乱的补丁历史,削弱主要规范(破坏 SDD 规则)。 这样,任何未来重新实现或审计系统的开发者或 Agent 都将拥有一份精确且自包含的规范。
5. 真实案例研究:Virtual Studio Companion
为了说明这种方法论的力量,让我们看看在开发我们的系统时如何解决四个高复杂度的挑战。
这个应用做什么?
Android 手机端:通过 CameraX 捕获 1080p 视频,在专用子线程(CameraX-Worker)中使用传感器旋转矩阵将画面压缩为 JPEG,在 1000ms 滑动窗口中计算真实 FPS/码率,并通过 WebSocket 向主机传输二进制帧。通过硬件加速的 Google ML Kit 扫描二维码。
Windows 桌面端(主机):内置 Ktor 本地服务器(自动回退端口 8080-8090)。使用 ZXing Core 生成符合 ISO/IEC 18004 标准的二维码。通过 Skia(org.jetbrains.skia.Image.makeFromEncoded)实时解码视频帧,在 Compose Desktop 中渲染原生视频返送监视器。
与 OBS Studio 集成:提供 `/stream/mjpeg` 的持续 HTTP 多部分流和 `/stream/preview` 响应式网页。通过 WebSocket 与 OBS Studio v5 通信,解决 SHA256 加密挑战以自动向活动场景注入浏览器源。
双向同步:双向远程控制缩放和闪光灯,无回音循环(echo loops),同时提供电池电量、散热状态和 RTT 延迟的实时遥测(Ping/Pong)。

案例 1:Android 的明文策略与穿孔查看器
问题:首次尝试通过 Wi-Fi 将手机连接到主机时,Android 以 UnknownServiceException: CLEARTEXT communication not permitted 终止。此外,二维码扫描查看器在摄像头画面上渲染出一个实心黑色方框。
"凭感觉编程"的做法会是:在 manifest 中添加一个散落的 flag,在视图里放一些 hack。
SDD 中的解决方法:对 `specs/004-android-mobile-app/spec.md` 中的 RF-001 进行了细化,涵盖手动 contingency 支持(输入 IP 和 token)以及作为正式非功能性需求(RNF-005)的本地网络策略。实现了 `network_security_config.xml`,启用私有网络上的本地流量。在 ScannerOverlay 中,发现 BlendMode.Clear 穿透了 Compose 图层直到原生黑色背景。细化了规范任务 T08,应用 `Modifier.graphicsLayer(compositingStrategy = CompositingStrategy.Offscreen)`,实现了 50% 半透明裁剪和晶体般清晰的边缘。
对 `specs/004-android-mobile-app/spec.md` 中的 RF-001 进行了细化,涵盖手动 contingency 支持(输入 IP 和 token)以及作为正式非功能性需求(RNF-005)的本地网络策略。
实现了 `network_security_config.xml`,启用私有网络上的本地流量。
在 ScannerOverlay 中,发现 BlendMode.Clear 穿透了 Compose 图层直到原生黑色背景。细化了规范任务 T08,应用 `Modifier.graphicsLayer(compositingStrategy = CompositingStrategy.Offscreen)`,实现了 50% 半透明裁剪和晶体般清晰的边缘。

案例 2:会话启动时的响应式弹跳
问题:聚焦二维码后,移动应用检测到 token,打开流传输屏幕,然后立即弹回扫描屏幕。
使用 Prompt 3 诊断:CameraScreen 观察 `uiState.connectionState`。由于 ViewModel 默认出生于 Disconnected 状态,而连接协程需要几毫秒才能启动,观察器将初始状态解释为断开连接,并调用 `onDisconnect()`。
使用 Prompt 4 重构:更新了 004-android-mobile-app 的规范任务 T06 和 T07:CameraStreamViewModel 现在接受 `initialConfig: PairingConfig?` 并立即出生于 `ConnectionState.Pairing(config)` 状态。引入了一个响应式守卫(`hasActiveSession`),仅当会话之前处于 Connected 或 Streaming 状态时才允许执行 `onDisconnect()`。
CameraStreamViewModel 现在接受 `initialConfig: PairingConfig?` 并立即出生于 `ConnectionState.Pairing(config)` 状态。
引入了一个响应式守卫(`hasActiveSession`),仅当会话之前处于 Connected 或 Streaming 状态时才允许执行 `onDisconnect()`。

案例 3:OBS WebSocket 中的竞态条件(replay = 1)
问题:如果用户在配对手机之前连接 OBS,一切正常。但如果先配对了手机,然后点击"连接 OBS",主机总是落入 OBS Error 和 Conectando... 状态,尽管视频流在 OBS 中仍然正常。
使用 Prompt 3 诊断:在 KtorObsTransport 中,传入消息流配置为:
```kotlin
// ❌ 之前:replay 默认为 0
private val _incoming = MutableSharedFlow<String>(extraBufferCapacity = 64)
使用 Prompt 3 诊断:
在 KtorObsTransport 中,传入消息流配置为:
// ❌ 之前:replay 默认为 0
private val _incoming = MutableSharedFlow<String>(extraBufferCapacity = 64)
当手机已经在传输视频时,CPU 线程调度程序正承受着处理二进制帧和解码 Skia 图像的负载。
当点击"连接 OBS"时,Socket 打开,OBS Studio 立即响应初始帧 op: 0 (Hello)。
但协程收集器(transport.incoming.collect)需要几毫秒才能订阅。由于 replay = 0,op: 0 消息被丢弃。
适配器等待一个永远不会到来的问候,达到 4 秒超时后陷入错误。
在单元测试中通过是因为 FakeObsTransport Mock 恰好配置了 replay = 1。
Prompt 4 的改进:在 specs/003-desktop-windows-host/ 中细化了 RF-003 和任务 T03、T04:
// ✅ 改进后:保证 replay = 1 和原子化订阅
private val _incoming = MutableSharedFlow<String>(replay = 1, extraBufferCapacity = 64)
此外,对通道的订阅在打开网络 Socket 之前完成,并受到 Mutex 保护。

案例 4:OBS 的对称且独立断开连接
问题:当 OBS 连接时,"连接 OBS"按钮从界面消失,无法断开连接。唯一可见的按钮是"断开连接",但该按钮关闭手机会话并将应用返回到二维码界面。
SDD 改进:更新了 RF-003、RF-004、T05 和 T06:实现了与 disconnectSession() 解耦的 DesktopHostViewModel.disconnectObs()。在 Compose Desktop 中设计了上下文相关按钮:如果已连接,显示"断开 OBS"(Res.string.desktop_action_disconnect_obs);如果已断开连接,显示"连接 OBS"。通用红色按钮保留给手机会话。
实现了与 disconnectSession() 解耦的 DesktopHostViewModel.disconnectObs()。
在 Compose Desktop 中设计了上下文相关按钮:如果已连接,显示"断开 OBS"(Res.string.desktop_action_disconnect_obs);如果已断开连接,显示"连接 OBS"。
通用红色按钮保留给手机会话。

在专业的 SDD 流程中,Git 不是积累变更的垃圾场。每个 commit 代表一个可验证任务的完成或改进:
# Virtual Studio Companion 仓库的真实提交历史
58deef0 feat(desktop): support independent obs disconnect and fix websocket transport race condition
66c9063 docs(specs): refine obs connection and disconnect specifications and tasks
81e14c8 feat(android): implement jpeg frame streaming, live telemetry, and camera controls synchronization
0aaff28 feat(desktop): implement video frame ingestion, skia return monitor, and mjpeg preview for obs
36e58fc docs(specs): refine video streaming and camera controls sync specifications
8395458 fix(android): prevent premature screen exit and initialize CameraStreamViewModel in pairing state
75e5b79 docs(specs): refine mobile pairing initialization and transition guard in spec and tasks
2a64def docs(specs): refine desktop, android, and design system specs and tasks
0c9d6dc fix(desktop): handle client disconnect, restore session disconnect and obs connect in dashboard
a2db8ef fix(android): consume scanned config, handle server disconnect and lifecycle events
观察其对称性:每个代码变更(feat 或 fix)都伴随着相应的规格更新(docs(specs))。
当前的讨论不应该是语言模型能否快速编写代码——我们已知它们可以。真正的挑战是如何治理 AI 以构建持久、容错且架构整洁的系统。
采用 Spec-Driven Development (SDD) 后:
AI 成为工程倍增器:不再花费数小时追踪随机回归,而是通过严格的契约、TDD 和确定性终端验证来引导 AI。
项目 100% 可审计:任何人类工程师或新智能体都可以检查 specs/、阅读 EARS 需求、查看序列图,并在几分钟内理解整个系统。
代码经得起时间考验:Clean 架构和分层分离确保跨平台核心保持纯净,不受平台 API 变更的影响。
资源和源代码
完整仓库(Kotlin 2.x、Compose Multiplatform、Ktor、CameraX、Skia、OBS WebSocket v5):🔗 https://github.com/dhbernardo/virtual-studio-companion
分析的稳定版本标签:v1.0.1。
你在 AI 智能体工作流中尝试过实现 Spec-Driven Development 吗?在下方分享你的评论、疑问或经验!