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 viaexpo-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)。
有了这三份文档,再配合一个 四步闭环工作流 ,就把一次混沌的“扔需求”变成了可控的“项目交付”:
- Goal(目标定义) :用结构化规格(Spec)明确要交付什么,包含用户故事、验收标准、技术约束。
- Task(任务拆解) :将 Spec 拆解为原子化、可验证、有明确输入输出的独立任务。
-
Execute & Verify(执行与自检)
:模型执行任务,并在每一步主动运行
npm run typecheck、npm run lint、甚至npx jest --testPathPattern=TodayScreen,失败则自动修复。 - 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
问题 :只是名词罗列,没有说明“为什么需要”、“为谁服务”、“失败的代价是什么”。避坑要点 :
- 永远从“用户痛点”出发,而非“功能列表” 。不要写“我们要做通知”,而要写“新手主人常忘记 2 小时一次的排便提醒,导致家里地毯遭殃,我们需要一个不可忽略的本地通知”。
- 量化模糊概念 。把“简单”变成“点击不超过 3 次”,把“快速”变成“首屏渲染 < 300ms”,把“稳定”变成“99.9% 的请求返回 HTTP 200”。
- 明确“不做”的边界 。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算不算?模型无法执行。避坑要点 :
- 用“必须”(Must)、“禁止”(Must Not)、“仅允许”(Only Allowed)代替“应该”(Should)、“建议”(Recommended) 。AI 不会解读委婉语,它只认硬性规则。
- 给出具体路径和文件名 。不要说“在数据层实现”,要说“在
data/repositories/目录下,以*Repository.ts命名的文件中实现”。路径即法律。- 为每条规则配上“反例” 。在文档末尾加一个
## 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?模型会随机选一个,导致环境不一致。避坑要点 :
- 一切以命令行(CLI)为中心 。文档里出现的每一个动作,都必须对应一个可以在终端里敲出来的、具体的、带参数的命令。
- 提供“最小可行模板” 。对于必须生成的文件(如测试文件),直接在文档里给出一个 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 }); });
- 定期“审计”你的
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):
-
我在终端输入:
opencode implement-tasks --task-file task.md。 -
OpenCode 启动,它首先
cd进入项目根目录,然后git status确认工作区干净。 -
它开始执行 Task 1:
ls -la domain/data/ageBasedHabits.json。终端输出:No such file or directory。 -
它立刻转向 Task 1 的备选方案:创建一个最小、有效的
ageBasedHabits.json。它参考domain/schemas/ageBasedHabitsSchema.ts,生成了包含0-8_weeks,8-12_weeks,12-24_weeks三个阶段的 JSON,并git add了这个新文件。 -
它执行 Task 2:生成
domain/services/habitAutoFillService.ts和domain/__tests__/habitAutoFillService.test.ts。生成完毕,它自动运行npm test -- --testPathPattern=habitAutoFillService,终端输出:✓ getMandatoryHabitsForAge returns correct habits for 6 weeks (3 ms)。 -
它执行 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,并更新了所有调用处。 -
它执行 Task 4:修改
HabitStrip.tsx。修改后,它运行npm run lint,发现一个警告:'accessibilityLabel' is missing on element。它立刻为每个TouchableOpacity添加了accessibilityLabel={habit.name}。
关键经验与心得:
心得一:自检不是“锦上添花”,而是“生死线” 。在我最初尝试 vibe engineering 时,我曾天真地认为“让模型跑一下测试就够了”。结果发现,模型会“作弊”——它生成的测试用例,会刻意避开它自己代码里的 bug。真正的自检,必须是 模型在修改代码后,主动、独立、不带偏见地运行你指定的、完整的、端到端的验证命令 。
npm run typecheck和 `

1014

被折叠的 条评论
为什么被折叠?



