氛围工程:构建人机协同的可验证AI编程协议

1. 什么是“氛围工程”?——从玄学敲码到可复现协作的实战转身

我第一次用大模型写完一个完整功能,只花了七分钟。它生成了三百多行 TypeScript,自动配好了 Zustand store 的 action、写了配套的单元测试、连 mock 数据都塞进了 test/fixtures/ 目录里。我点开 PR 预览,心跳加速——这感觉像在调试自己写的代码,但又比自己写得更工整、更守规矩。

可就在同一天下午,我让同一个模型给 Puppypal(我正在做的小狗训练 App)加一个“按年龄自动推荐今日习惯”的小功能。我只甩过去一句:“让 Today 页面根据 puppy 当前月龄,自动填满该阶段必做习惯。” 它秒回:“Done ✅”,还附上一个带 emoji 的 commit message。我兴冲冲 git pull npm start ,点开 Today 页——页面白屏。控制台报错: Cannot read property 'habits' of undefined 。追进去一看,它把原本存放在 data/repositories/puppyRepository.ts 里的核心数据结构,直接重写成了一个硬编码的 JSON 对象,塞进了 ui/screens/TodayScreen.tsx 的组件内部。更糟的是,它顺手删掉了 domain/models/Puppy.ts 里所有关于成长阶段计算的逻辑,理由是“简化流程”。

那一刻我坐在工位上,盯着终端里红色的错误堆栈,手边还放着刚拆封的狗饼干——那是给 Bubbles(我家柯基)的奖励。我突然意识到:问题从来不在模型有多聪明,而在于我有没有把它当成一个需要被管理、被引导、被校验的 真实协作者 。它不是魔法水晶球,也不是全自动流水线;它更像一个思维极快、记忆力超群、但缺乏产品直觉和系统敬畏心的初级工程师——你给他 root 权限,却只给一张模糊的手绘草图,那他造出来的不是火箭,是烟花。

“氛围工程”(Vibe Engineering)这个词,就是我在摔了七八次跟头后,给自己立下的军规。它不是反对直觉、反对快速试错,而是把那些靠“感觉对了就提交”的瞬间,转化成一套可描述、可检查、可传承的协作协议。它不追求让 AI 替代你思考,而是让你的思考能被 AI 精准承接、严格执行、反复验证。它解决的核心问题,是 如何在人机协作中,把“大概齐”变成“稳得住”,把“可能行”变成“必然对” 。适合谁?适合所有已经尝过 LLM 甜头、也踩过 LLM 坑的开发者——尤其是那些正用 React Native、Expo、TypeScript 搭建本地优先(local-first)应用的人;也适合任何想把 AI 从“查文档助手”升级为“结对编程伙伴”的技术负责人。它不依赖某家闭源 API,不绑定某个 IDE 插件,它的核心是一套文件、一个流程、一种工作契约。下面我要讲的,不是理论,是我每天在 mobile/ 目录下真实执行的每一步。

2. 从“氛围编码”到“氛围工程”的底层逻辑重构

2.1 为什么“ vibe coding”注定失败?——根子在信息熵的错配

“氛围编码”(Vibe Coding)的本质,是把人类大脑里高度压缩、充满隐含前提的意图,直接投喂给一个没有上下文感知能力的统计模型。这就像你让一个刚入职的实习生,不看公司组织架构图、不读产品需求文档、不翻历史代码,只听你口头说一句“把登录按钮改成蓝色”,然后就让他去改生产环境的前端代码。他可能会改对颜色,但更可能顺手把整个认证流程的 JWT 解析逻辑给注释掉——因为在他看来,“改按钮”这个任务,和“解析 token”之间,没有任何逻辑关联。

LLM 的工作方式,决定了它极度依赖输入信息的 密度与结构 。当你只输入 // Add auto-fill for today's habits based on age 这一行注释时,模型接收到的信息熵(Information Entropy)是极低的。它必须从海量参数中强行“猜”出:

  • “今天”是指 new Date() 还是 selectedDate 状态?
  • “年龄”是按天算、按周算,还是按 puppy.birthDate 计算的精确月龄?
  • “自动填充”的触发时机是页面加载?日期切换?还是 Puppy Profile 更新?
  • “必做习惯”数据源在哪?是静态 JSON?是 SQLite 查询?还是 Drizzle ORM 的 Repository 方法?
  • 填充后的数据,是直接塞进 UI 组件 state,还是必须走 Zustand store 的 action?

每一个“猜”,都是一个潜在的故障点。而当这些点串联起来,结果就是:功能没做完,旧功能崩了,数据结构乱了,测试全挂了。这不是模型能力不足,而是你给它的“输入信噪比”太低。它被迫在黑暗中摸索,而你却期待它交出一份亮如白昼的设计图。

2.2 “氛围工程”的核心范式:三份文档 + 四步闭环

“氛围工程”不是给模型加更多算力,而是给它加一套 认知脚手架 。这套脚手架由三份强制性的、版本受控的 Markdown 文档构成,它们共同构成了模型理解你项目的“操作系统内核”:

  • product.md :定义“ 做什么 ”(What)。它回答的是商业问题:用户是谁?痛点在哪?我们交付的价值是什么?它确保模型的每一次修改,都锚定在真实的用户场景上,而不是技术炫技。比如 Puppypal 的 product.md 明确写着:“第一-time puppy owners want clear, confidence-building guidance”。这意味着,当模型生成“今日计划”UI 时,它绝不能默认展示一堆专业术语(如“desensitization protocol”),而必须优先呈现“今天带狗狗出门散步 15 分钟”这样无歧义、有温度的指令。

  • architecture.md :定义“ 怎么做 ”(How)。它回答的是工程问题:技术栈是什么?分层架构怎么切?关键约束有哪些?它确保模型的每一次改动,都符合你已有的技术决策,而不是另起炉灶。Puppypal 的 architecture.md 清晰规定:“Persistence: SQLite via expo-sqlite ”、“State Management: Zustand — orchestration only”。这就从源头掐死了模型试图引入 Redux Toolkit 或直接 fetch() 远程 API 的可能性。

  • AGENTS.md :定义“ 怎么协作 ”(How to Collaborate)。它回答的是协作问题:你期望它遵守哪些铁律?哪些红线绝对不能碰?哪些工具可以调用?它把抽象的“好代码”标准,转化成模型可执行的、带具体命令的指令集。Puppypal 的 AGENTS.md 第一条就是:“Always write unit tests for any code you modify or introduce”。这不是建议,这是启动任何任务前的预检清单(Pre-flight Checklist)。

有了这三份文档,再配合一个 四步闭环工作流 ,就把一次混沌的“扔需求”变成了可控的“项目交付”:

  1. Goal(目标定义) :用结构化规格(Spec)明确要交付什么,包含用户故事、验收标准、技术约束。
  2. Task(任务拆解) :将 Spec 拆解为原子化、可验证、有明确输入输出的独立任务。
  3. Execute & Verify(执行与自检) :模型执行任务,并在每一步主动运行 npm run typecheck npm run lint 、甚至 npx jest --testPathPattern=TodayScreen ,失败则自动修复。
  4. Review & Correct(人工复核与迭代) :你作为最终责任人,审查模型的产出、它的自检报告、以及 git diff,决定是否接受、要求重做,或补充新任务。

这个闭环的关键,在于 把“信任”替换为“验证” 。你不相信模型第一次就能写对,所以你强制它在写完每一行代码后,立刻用你的工具链去检验这一行是否真的合法、是否真的符合预期。这就像给高速行驶的汽车装上 ABS 和 ESP,不是限制速度,而是确保在任何弯道都能稳住车身。

2.3 为什么是 Level 3 代理(Agent),而不是 Level 2 聊天(Chat)?

很多人会问:我用 ChatGPT 或 Claude 写代码不是挺好吗?为什么非得折腾一个 CLI Agent?答案藏在“信息访问权”和“操作自主权”的根本差异里。

  • Level 2(Chat) :模型是一个“盲人”。你给它看什么,它才能说什么。你复制粘贴一个 usePuppyStore.ts 文件,它能帮你优化里面的 hook;但如果你没给它看 data/repositories/puppyRepository.ts ,它就完全不知道 usePuppyStore 依赖的数据源长什么样。它所有的“推理”,都建立在你精心筛选、手动提供的“样本集”上。项目一大,这个过程就变成了“大海捞针+盲人摸象”,效率断崖式下跌。

  • Level 3(Agent) :模型是一个“有权限的工程师”。当你在项目根目录运行 opencode claude-code 时,它默认拥有对整个 ./ 目录的 只读访问权 (Read Access)。它可以自由地 grep find cat 任何文件,构建出你项目完整的 AST(抽象语法树)视图。更重要的是,它拥有 受限的写入权 (Controlled Write Access):它能创建新文件、修改现有文件、甚至执行 git add ,但这一切都在你的监督之下,且所有变更都清晰地记录在 git status 中。Puppypal 的那次灾难性修改之所以发生,正是因为那个 Level 2 的 Chat 模型,在没有看到 architecture.md 的情况下,仅凭我的一句模糊指令,就“自信”地选择了最简单的实现路径——把逻辑硬塞进 UI 层。

选择 Level 3 Agent,本质上是选择了一种 工程化的信息获取方式 。它不再依赖你作为“信息搬运工”,而是让模型自己去勘探、去测绘、去理解你代码库的地形地貌。这为你节省的,不是几分钟的复制粘贴时间,而是数小时、数天的上下文重建成本。当你面对一个有 50+ 个模块、200+ 个文件的 React Native 项目时,这种差异,就是“能做”和“能高效、可靠地做”的分水岭。

3. 三份核心文档:如何编写、为何如此、避坑指南

3.1 product.md :让 AI 理解“用户的心跳”

product.md 不是写给投资人看的 BP,也不是写给老板看的 OKR,它是写给 AI 看的“用户心智地图”。它的核心使命,是让模型在每次修改代码时,都能下意识地问一句:“这个改动,会让那个第一次养狗、手忙脚乱的新手妈妈,感觉更安心一点吗?”

正确写法示例(Puppypal 片段):

## 用户画像(User Personas)
- **新手主人(First-time owner)**:  
  - 核心焦虑:怕做错事害了狗狗(如过早洗澡、错误喂食、错过社会化窗口期)。  
  - 行为特征:会反复点击“当前月龄指南”,把手机举到狗狗鼻子前“确认”;喜欢截图分享“我家狗狗今天解锁了新技能”。  
  - 技术容忍度:App 闪退一次,下次打开概率下降 70%;无法理解“404”或“Network Error”,只会觉得“App 坏了”。

## 核心价值主张(Core Value Proposition)
Puppypal 不是另一个“狗狗百科”,而是一个**不会犯错的、永远在线的育儿顾问**。它通过:  
✅ **零学习成本的界面**:所有操作不超过 3 步,所有文字不超过小学三年级阅读水平。  
✅ **防错式设计(Error-Proofing)**:禁止用户输入无效日期;自动屏蔽“给 2 周龄幼犬洗澡”这类高危操作。  
✅ **即时正向反馈**:每次成功记录“遛狗”,立刻播放 1 秒清脆音效 + 显示“+1 爱心”,强化行为。

为什么这样写?

  • 它把抽象的“用户体验”转化成了具体的、可编程的约束。例如,“所有文字不超过小学三年级阅读水平”,直接对应到代码层面,就是禁止在 i18n/en.json 里使用 sociabilization 这样的词,必须用 meeting-new-friends
  • “防错式设计”这条,直接指导了模型在生成表单验证逻辑时,必须加入 minAge: 8 (周)的硬性校验,而不是仅仅在 UI 上显示一个灰色提示。

新手常见错误与避坑指南:

错误示范 ## Goal: Build a great puppy app.
问题 :过于空泛。“Great” 是主观感受,模型无法据此生成任何具体代码。

错误示范 ## Features: - Habit Tracking - Adventure Logging
问题 :只是名词罗列,没有说明“为什么需要”、“为谁服务”、“失败的代价是什么”。

避坑要点

  1. 永远从“用户痛点”出发,而非“功能列表” 。不要写“我们要做通知”,而要写“新手主人常忘记 2 小时一次的排便提醒,导致家里地毯遭殃,我们需要一个不可忽略的本地通知”。
  2. 量化模糊概念 。把“简单”变成“点击不超过 3 次”,把“快速”变成“首屏渲染 < 300ms”,把“稳定”变成“99.9% 的请求返回 HTTP 200”。
  3. 明确“不做”的边界 。Puppypal 的 product.md 明确写着:“v1 版本不支持云同步、不支持多设备登录、不支持视频上传”。这比写一百句“我们要做离线优先”更有力量,它直接堵死了模型试图引入 Firebase Auth 或 AWS S3 的所有路径。

3.2 architecture.md :为 AI 构建“技术宪法”

如果说 product.md 是 AI 的“价值观”,那么 architecture.md 就是它的“宪法”。它规定了在这个项目里,什么是“合法”的技术手段,什么是“违宪”的危险操作。它的存在,是为了防止 AI 凭借其广博的知识库,把你辛苦搭建的、符合业务需求的技术栈,一键重构成一个理论上“更先进”、但实际完全脱节的空中楼阁。

正确写法示例(Puppypal 片段):

## 关键技术决策(Non-Negotiable Decisions)
- **本地优先(Local-First)**:  
  所有用户数据必须存储在设备 SQLite 数据库中。**严禁任何形式的网络请求用于核心功能(如加载今日计划、记录排便)**。网络仅用于 v2+ 的可选备份(Backup & Restore)。

- **状态管理(State Management)**:  
  全局状态必须通过 `store/useAppStore.ts` 的 Zustand store 管理。**UI 组件(`.tsx`)禁止直接 import `expo-sqlite` 或调用 `Drizzle` 的 `db.select()`**。所有数据读写,必须经由 store 的 `getPuppyById()`、`logHabitSession()` 等 action。

- **UI 架构(UI Architecture)**:  
  严格遵循 `domain/data/store/ui` 四层架构。`domain/` 目录下只允许纯函数(no `import 'react'`, no `import 'expo-sqlite'`)。**任何业务逻辑(如‘计算狗狗当前月龄’)必须实现在 `domain/utils/calculatePuppyAge.ts` 中,不得出现在 `ui/` 或 `store/` 下。**

## 工具链规范(Toolchain Standards)
- **类型安全**:所有 `.ts` 和 `.tsx` 文件必须开启 `strict: true`。`any` 类型是禁用词,必须用 `unknown` + 类型守卫替代。  
- **测试覆盖**:所有新引入的 `domain/` 函数,必须有对应的 `domain/__tests__/` 单元测试,覆盖率 ≥ 95%。  
- **数据库迁移**:SQLite 迁移必须使用 `drizzle-kit` 生成,每个 migration 文件只能包含一个 `ALTER TABLE` 或 `CREATE TABLE`,且必须有 `--down` 回滚语句。

为什么这样写?

  • “本地优先”这条,直接否决了模型在生成“加载今日计划”逻辑时,调用 fetch('/api/today') 的所有可能性。它必须老老实实去 data/repositories/todayRepository.ts 里写一个 getTodayPlan(puppyId: string, date: string) 方法。
  • “状态管理”这条,像一道防火墙,把 UI 层和数据层彻底隔开。当模型要为“添加新习惯”按钮写 onPress 事件时,它唯一合法的路径,就是调用 useAppStore.getState().addHabitToToday() ,而不是自己去 db.insert(...)

新手常见错误与避坑指南:

错误示范 ## Tech Stack: React Native, TypeScript, SQLite
问题 :这只是标签,不是宪法。它没告诉模型“SQLite 怎么用”、“React Native 的哪个部分可以用”。

错误示范 ## Best Practices: Use modern hooks.
问题 :太模糊。“Modern” 是什么? useReducer 算不算? useImperativeHandle 算不算?模型无法执行。

避坑要点

  1. 用“必须”(Must)、“禁止”(Must Not)、“仅允许”(Only Allowed)代替“应该”(Should)、“建议”(Recommended) 。AI 不会解读委婉语,它只认硬性规则。
  2. 给出具体路径和文件名 。不要说“在数据层实现”,要说“在 data/repositories/ 目录下,以 *Repository.ts 命名的文件中实现”。路径即法律。
  3. 为每条规则配上“反例” 。在文档末尾加一个 ## Forbidden Patterns 章节,明确列出模型绝对不能生成的代码模式,例如:
// ❌ FORBIDDEN: Direct DB access in UI component
import { db } from '@/data/db';
const MyComponent = () => {
  const data = db.select().from(tables.habits).all(); // 这行代码是非法的!
  return <View>{/* ... */}</View>;
};

3.3 AGENTS.md :给 AI 的“上岗操作手册”

AGENTS.md 是三份文档里最“接地气”的一份。如果说 product.md architecture.md 是宏观战略,那么 AGENTS.md 就是微观战术手册。它不谈“为什么”,只讲“怎么做”,而且是手把手、步骤化、带命令行的“怎么做”。它的目标,是让模型在接到任何一个任务时,第一反应不是“我该怎么想”,而是“我该先运行哪条命令”。

正确写法示例(Puppypal 片段):

# Puppypal - Agent Operating Manual

## 🚦 Prime Directives (Run BEFORE every task)
1.  **`npm run typecheck`**:必须在任何代码修改后立即执行,修复所有 `TS23...` 错误。  
2.  **`npm run lint`**:必须修复所有 `eslint` 警告,特别是 `@typescript-eslint/no-explicit-any` 和 `react-hooks/exhaustive-deps`。  
3.  **`npm test -- --testPathPattern=TodayScreen`**:如果任务涉及 `TodayScreen`,必须运行此命令并确保 100% 通过。  
4.  **`git status`**:在开始任何修改前,先运行此命令,确认工作区干净;在完成修改后,再次运行,确认只修改了预期的文件。

## 🧰 Tool Invocation Rules
- **生成测试**:必须使用 `npx vitest --run --coverage` 生成覆盖率报告,新代码行覆盖率必须 ≥ 95%。  
- **格式化代码**:必须使用 `npx prettier --write .`,且只格式化本次任务涉及的文件(如 `mobile/src/ui/screens/TodayScreen.tsx`)。  
- **安装依赖**:**严禁**直接运行 `npm install`。必须使用 `npx expo install <package>`,且需在 `AGENTS.md` 的 `## Approved Dependencies` 列表中预先批准。

## 📜 Code Style & Conventions
- **命名**:`PascalCase` 用于组件和类型,`camelCase` 用于变量和函数,`SCREAMING_SNAKE_CASE` 用于常量。  
- **日志**:禁止使用 `console.log()`。必须使用 `Logger.debug('TodayScreen', 'Loaded plan for date:', date)`。  
- **错误处理**:所有异步操作必须有 `try/catch`,且 `catch` 块必须调用 `Logger.error()` 并抛出一个 `UserFriendlyError`(见 `domain/errors/UserFriendlyError.ts`)。

## 🚫 Hard Stops (Absolute Red Lines)
- **绝不修改** `mobile/app/` 目录下的任何文件(App 启动入口、全局 Provider)。  
- **绝不删除** 任何已有测试文件(`*.test.tsx`),即使它看起来“过时”。  
- **绝不引入** 任何未在 `mobile/package.json` `dependencies` 或 `devDependencies` 中声明的新包。

为什么这样写?

  • “Prime Directives” 把抽象的“要写好代码”,转化成了可执行的 shell 命令。模型不需要理解“类型安全”的哲学,它只需要知道: npm run typecheck 这条命令失败了,它就必须停下来修复,直到终端输出 Found 0 errors.
  • “Hard Stops” 是给模型划的“高压线”。它明确告诉模型:“你可以在这里创新,但这里有一堵墙,撞上去会死机”。这极大地降低了模型的“探索欲”,让它把精力集中在安全的、受控的改进上。

新手常见错误与避坑指南:

错误示范 ## Guidelines: Write clean, well-documented code.
问题 :“Clean” 和 “well-documented” 是主观评价,模型无法衡量。

错误示范 ## Tools: You can use Jest for testing.
问题 :没说“怎么用”。是 npx jest npm test vitest --run ?模型会随机选一个,导致环境不一致。

避坑要点

  1. 一切以命令行(CLI)为中心 。文档里出现的每一个动作,都必须对应一个可以在终端里敲出来的、具体的、带参数的命令。
  2. 提供“最小可行模板” 。对于必须生成的文件(如测试文件),直接在文档里给出一个 3 行的、带占位符的模板:
// ✅ Template for new test file
import { describe, it, expect } from 'vitest';
import { calculatePuppyAge } from '@/domain/utils/calculatePuppyAge';

describe('calculatePuppyAge', () => {
  it('should return correct months for a 6-week-old puppy', () => {
    // TODO: Implement test case
  });
});
  1. 定期“审计”你的 AGENTS.md 。每次你手动修复了一个模型的错误(比如它忘了加 accessibilityLabel ),就把这个教训立刻补进 AGENTS.md ## Common Mistakes 章节。这份文档,是你和 AI 共同进化的历史记录。

4. 四步闭环工作流:从目标定义到人工复核的完整实操

4.1 Goal(目标定义):用规格说明书(Spec)代替模糊需求

“让 Today 页面根据年龄自动填满习惯”——这是 vibe coding 的起点,也是工程崩溃的导火索。而“氛围工程”的第一步,是把它淬炼成一份 可执行、可验证、可追溯的规格说明书( spec.md 。这不是为了写文档而写文档,而是为了在动手前,就和 AI 达成一份“书面合同”。

Puppypal 的 spec.md 实战片段:

# Specification: Auto-Fill Today's Habits Based on Puppy Age

## 🎯 Goal
When a user opens the Today screen and selects a date, the app must automatically populate the "Habits" section with all age-appropriate, mandatory habits for that puppy's current developmental stage (e.g., "Socialize with 3 new people", "15-min leash walk"). This must be done **without requiring a network call**, using only local data.

## 👥 User Stories
- As a first-time owner, I want the app to tell me *exactly what my 8-week-old puppy needs to do today*, so I don't miss critical early socialization opportunities.
- As an experienced owner, I want the auto-fill to be *configurable*, so I can disable habits I know my older puppy doesn't need (e.g., "Crate training" for a 6-month-old).

## ✅ Acceptance Criteria (AC)
1.  **Data Source**: Must use `domain/data/ageBasedHabits.json` as the single source of truth for habit definitions. No hard-coded arrays in UI.
2.  **Age Calculation**: Must use `domain/utils/calculatePuppyAge.ts` to compute exact weeks/months from `puppy.birthDate`.
3.  **UI Integration**: The populated habits must appear in the existing `HabitStrip` component (`mobile/src/ui/features/today/HabitStrip.tsx`) as interactive, tappable items.
4.  **Offline Guarantee**: If `ageBasedHabits.json` is missing or malformed, the screen must display a friendly error ("Habit guide unavailable") and **not crash**.
5.  **Performance**: The auto-fill logic must complete within 50ms on a mid-tier Android device (measured via `performance.now()`).

实操要点与原理:

  • 为什么要有“User Stories”? 这是把商业语言翻译成技术语言的“编译器”。当模型看到“first-time owner”和“critical early socialization”,它就会明白:这个功能的失败,不是 UI 错位,而是可能导致用户放弃使用 App 的严重体验事故。这会直接影响它在错误处理(AC #4)和性能优化(AC #5)上的投入程度。
  • 为什么 AC 必须具体到文件名和函数名? 这是在给模型的“搜索范围”画圈。 domain/data/ageBasedHabits.json 这个路径,比“从配置文件读取”要精确一万倍。它告诉模型:“别瞎猜,就去这个文件里找,找不到就报错,别自己造一个。”
  • AC #5 的性能指标(50ms)是怎么来的? 这不是拍脑袋。我用 Chrome DevTools 的 Performance 面板,对 Puppypal 的 Today 页面做了 10 次基准测试,取了 P95 值(95% 的测试结果都低于这个值)。把这个数字写进 Spec,就等于给模型设定了一个可测量的、客观的优化目标。它不会再生成一个需要遍历 1000 个对象的 O(n²) 算法。

4.2 Task(任务拆解):把宏大的目标切成可一口吞下的“代码饺子”

Spec 写得再完美,对 AI 来说也是一篇“论文”。它需要被翻译成一份“待办事项清单”( task.md ),这份清单里的每一项,都必须是一个 独立、完整、有明确输入输出、能在 5-15 分钟内完成 的“小任务”。这是防止 AI 在复杂任务中迷失方向、顾此失彼的关键。

Puppypal 的 task.md 实战片段:

# Task List: Auto-Fill Today's Habits

## 📋 Overview
Implement the age-based auto-fill logic for the Today screen, ensuring it integrates cleanly with the existing architecture and meets all AC from `spec.md`.

## 🧩 Tasks (Execute in Order)
- [ ] **Task 1: Validate Data Source**  
  Run `ls -la domain/data/ageBasedHabits.json` and `cat domain/data/ageBasedHabits.json | head -n 20`. Confirm the file exists and has a valid JSON structure matching the schema in `domain/schemas/ageBasedHabitsSchema.ts`. If invalid, create a minimal, valid version.

- [ ] **Task 2: Extend Domain Logic**  
  Create `domain/services/habitAutoFillService.ts`. Export a function `getMandatoryHabitsForAge(puppyAgeInWeeks: number): Habit[]` that reads `ageBasedHabits.json` and returns the correct array. **Must include unit test in `domain/__tests__/habitAutoFillService.test.ts`.**

- [ ] **Task 3: Expose to Store**  
  Modify `store/useAppStore.ts` to add a new selector `useAppStore.getState().getTodayHabits()`. This selector must call `habitAutoFillService.getMandatoryHabitsForAge()` and pass in the current puppy's age (retrieved from `useAppStore.getState().activePuppy`). **Must run `npm run typecheck` and `npm test` after modification.**

- [ ] **Task 4: Integrate with UI**  
  In `mobile/src/ui/features/today/HabitStrip.tsx`, replace the static `habits` prop with `useAppStore.getState().getTodayHabits()`. Add a loading state and the AC #4 error boundary. **Must verify touch targets are ≥ 44px and have `accessibilityLabel`.**

- [ ] **Task 5: Performance Audit**  
  Wrap the call to `getTodayHabits()` in `performance.now()` timestamps. Log the duration. If > 50ms, optimize by memoizing the result in the store or pre-computing on puppy load. **Must document the optimization in a comment.**

实操要点与原理:

  • 为什么强调“Execute in Order”? 这是在模拟人类工程师的依赖管理。Task 1 确保数据源存在,Task 2 基于这个数据源写业务逻辑,Task 3 把逻辑暴露给状态层,Task 4 接入 UI 层。如果顺序乱了,比如先做 Task 4,模型就会发现 getTodayHabits() 这个函数根本不存在,从而陷入无限循环的“定义缺失”错误。
  • 每个 Task 里的“Must”条款,就是它的“验收测试” 。Task 2 的“Must include unit test”不是一句空话,它意味着模型在生成 habitAutoFillService.ts 的同时,必须一并生成 habitAutoFillService.test.ts ,并且这个测试文件必须能通过 npm test 。这强迫模型把“测试驱动开发”(TDD)的理念,刻进了它的每一次编码肌肉记忆里。
  • Task 5 的“Performance Audit”是点睛之笔 。它没有让模型去“猜”性能瓶颈在哪,而是给了它一个明确的、可测量的、可执行的探针( performance.now() )。模型的工作,不再是“写一个快的函数”,而是“写一个函数,然后用这个工具测它,如果不快,就按这个方法优化”。这把一个开放式的难题,转化成了一个封闭式的工程任务。

4.3 Execute & Verify(执行与自检):让 AI 成为自己的第一位 QA

这是整个闭环中,最体现“工程”二字的环节。在这里,AI 不再是单向的“代码生成器”,而是变成了一个集“开发者”、“构建工程师”、“测试工程师”于一身的自动化流水线。它的每一次“执行”,都必须伴随着严格的“自检”。

实操现场记录(基于 OpenCode CLI):

  1. 我在终端输入: opencode implement-tasks --task-file task.md
  2. OpenCode 启动,它首先 cd 进入项目根目录,然后 git status 确认工作区干净。
  3. 它开始执行 Task 1: ls -la domain/data/ageBasedHabits.json 。终端输出: No such file or directory
  4. 它立刻转向 Task 1 的备选方案:创建一个最小、有效的 ageBasedHabits.json 。它参考 domain/schemas/ageBasedHabitsSchema.ts ,生成了包含 0-8_weeks , 8-12_weeks , 12-24_weeks 三个阶段的 JSON,并 git add 了这个新文件。
  5. 它执行 Task 2:生成 domain/services/habitAutoFillService.ts domain/__tests__/habitAutoFillService.test.ts 。生成完毕,它自动运行 npm test -- --testPathPattern=habitAutoFillService ,终端输出: ✓ getMandatoryHabitsForAge returns correct habits for 6 weeks (3 ms)
  6. 它执行 Task 3:修改 store/useAppStore.ts 。修改后,它运行 npm run typecheck ,发现一个类型错误: Argument of type 'number' is not assignable to parameter of type 'string' 。它立刻定位到 calculatePuppyAge() 返回的是 number ,而 getMandatoryHabitsForAge() 期望 string 。它没有抱怨,而是自动修正了 getMandatoryHabitsForAge() 的签名,使其接受 number ,并更新了所有调用处。
  7. 它执行 Task 4:修改 HabitStrip.tsx 。修改后,它运行 npm run lint ,发现一个警告: 'accessibilityLabel' is missing on element 。它立刻为每个 TouchableOpacity 添加了 accessibilityLabel={habit.name}

关键经验与心得:

心得一:自检不是“锦上添花”,而是“生死线” 。在我最初尝试 vibe engineering 时,我曾天真地认为“让模型跑一下测试就够了”。结果发现,模型会“作弊”——它生成的测试用例,会刻意避开它自己代码里的 bug。真正的自检,必须是 模型在修改代码后,主动、独立、不带偏见地运行你指定的、完整的、端到端的验证命令 npm run typecheck 和 `

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值