[{"data":1,"prerenderedAt":4062},["ShallowReactive",2],{"navigation":3,"\u002Fpractice\u002Fcore-features":189,"\u002Fpractice\u002Fcore-features-surround":4058},[4,35,57,75,101,123,149,171],{"title":5,"icon":6,"path":7,"stem":8,"children":9,"page":34},"第 1 章：认识 Claude Code","i-lucide-rocket","\u002Fintro","1.intro",[10,14,18,22,26,30],{"title":11,"path":12,"stem":13},"什么是 Claude Code","\u002Fintro\u002Fwhat-is-claude-code","1.intro\u002F1.what-is-claude-code",{"title":15,"path":16,"stem":17},"Claude Code 与 Copilot、Cursor、Windsurf 的本质区别","\u002Fintro\u002Fvs-competitors","1.intro\u002F2.vs-competitors",{"title":19,"path":20,"stem":21},"AI 编程助手生态全景与选型指南","\u002Fintro\u002Fecosystem-guide","1.intro\u002F3.ecosystem-guide",{"title":23,"path":24,"stem":25},"LLM 的概率本质","\u002Fintro\u002Fllm-probability","1.intro\u002F4.llm-probability",{"title":27,"path":28,"stem":29},"从聊天机器人到 Agent","\u002Fintro\u002Ffrom-chatbot-to-agent","1.intro\u002F5.from-chatbot-to-agent",{"title":31,"path":32,"stem":33},"Claude Code 的 Agentic Loop 全拆解","\u002Fintro\u002Fagentic-loop","1.intro\u002F6.agentic-loop",false,{"title":36,"icon":37,"path":38,"stem":39,"children":40,"page":34},"第 2 章：安装与配置","i-lucide-settings","\u002Fsetup","2.setup",[41,45,49,53],{"title":42,"path":43,"stem":44},"系统要求与安装方式","\u002Fsetup\u002Fsystem-requirements","2.setup\u002F1.system-requirements",{"title":46,"path":47,"stem":48},"认证、登录与多账户管理","\u002Fsetup\u002Fauthentication","2.setup\u002F2.authentication",{"title":50,"path":51,"stem":52},"选择你的界面","\u002Fsetup\u002Fchoose-interface","2.setup\u002F3.choose-interface",{"title":54,"path":55,"stem":56},"Coding Plan","\u002Fsetup\u002Fcoding-plan","2.setup\u002F4.coding-plan",{"title":58,"icon":59,"path":60,"stem":61,"children":62,"page":34},"第 3 章：快速上手","i-lucide-hand","\u002Fquickstart","3.quickstart",[63,67,71],{"title":64,"path":65,"stem":66},"启动、交互模式与基本命令","\u002Fquickstart\u002Fstartup","3.quickstart\u002F1.startup",{"title":68,"path":69,"stem":70},"让 Claude 理解你的项目","\u002Fquickstart\u002Fcodebase-understanding","3.quickstart\u002F2.codebase-understanding",{"title":72,"path":73,"stem":74},"第一次代码变更","\u002Fquickstart\u002Ffirst-change","3.quickstart\u002F3.first-change",{"title":76,"icon":77,"path":78,"stem":79,"children":80,"page":34},"第 4 章：核心功能","i-lucide-laptop","\u002Fcore-features","4.core-features",[81,85,89,93,97],{"title":82,"path":83,"stem":84},"代码库全景扫描与模块关系分析","\u002Fcore-features\u002Fcodebase-scan","4.core-features\u002F1.codebase-scan",{"title":86,"path":87,"stem":88},"代码编辑与生成","\u002Fcore-features\u002Fedit-generate","4.core-features\u002F2.edit-generate",{"title":90,"path":91,"stem":92},"测试与调试","\u002Fcore-features\u002Ftest-debug","4.core-features\u002F3.test-debug",{"title":94,"path":95,"stem":96},"Git 工作流","\u002Fcore-features\u002Fgit-workflow","4.core-features\u002F4.git-workflow",{"title":98,"path":99,"stem":100},"工具链执行","\u002Fcore-features\u002Ftoolchain","4.core-features\u002F5.toolchain",{"title":102,"icon":103,"path":104,"stem":105,"children":106,"page":34},"第 5 章：进阶配置","i-lucide-wrench","\u002Fadvanced","5.advanced",[107,111,115,119],{"title":108,"path":109,"stem":110},"CLAUDE.md","\u002Fadvanced\u002Fclaude-md","5.advanced\u002F1.claude-md",{"title":112,"path":113,"stem":114},"Skills","\u002Fadvanced\u002Fskills","5.advanced\u002F2.skills",{"title":116,"path":117,"stem":118},"MCP","\u002Fadvanced\u002Fmcp","5.advanced\u002F3.mcp",{"title":120,"path":121,"stem":122},"Hooks 与 Plan 模式","\u002Fadvanced\u002Fhooks-plan","5.advanced\u002F4.hooks-plan",{"title":124,"icon":125,"path":126,"stem":127,"children":128,"page":34},"第 6 章：实战开发","i-lucide-hammer","\u002Fpractice","6.practice",[129,133,137,141,145],{"title":130,"path":131,"stem":132},"需求分析与架构设计","\u002Fpractice\u002Frequirements-architecture","6.practice\u002F1.requirements-architecture",{"title":134,"path":135,"stem":136},"项目脚手架搭建与技术选型","\u002Fpractice\u002Fscaffolding","6.practice\u002F2.scaffolding",{"title":138,"path":139,"stem":140},"核心功能实现","\u002Fpractice\u002Fcore-features","6.practice\u002F3.core-features",{"title":142,"path":143,"stem":144},"测试覆盖、代码审查与质量调优","\u002Fpractice\u002Ftesting-quality","6.practice\u002F4.testing-quality",{"title":146,"path":147,"stem":148},"部署上线与成果分享","\u002Fpractice\u002Fdeployment","6.practice\u002F5.deployment",{"title":150,"icon":151,"path":152,"stem":153,"children":154,"page":34},"第 7 章：心法层","i-lucide-brain","\u002Fmindset","7.mindset",[155,159,163,167],{"title":156,"path":157,"stem":158},"提示词设计原则","\u002Fmindset\u002Fprompt-design","7.mindset\u002F1.prompt-design",{"title":160,"path":161,"stem":162},"上下文管理策略","\u002Fmindset\u002Fcontext-management","7.mindset\u002F2.context-management",{"title":164,"path":165,"stem":166},"安全与权限控制","\u002Fmindset\u002Fsecurity","7.mindset\u002F3.security",{"title":168,"path":169,"stem":170},"Boris Cherny 的 9 条实战心法与团队推广经验","\u002Fmindset\u002Fboris-cherny-tips","7.mindset\u002F4.boris-cherny-tips",{"title":172,"icon":173,"path":174,"stem":175,"children":176,"page":34},"附录","i-lucide-paperclip","\u002Fappendix","8.appendix",[177,181,185],{"title":178,"path":179,"stem":180},"常用命令速查表","\u002Fappendix\u002Fa.command-cheatsheet","8.appendix\u002Fa.command-cheatsheet",{"title":182,"path":183,"stem":184},"AI 核心术语汇编","\u002Fappendix\u002Fb.ai-terminology","8.appendix\u002Fb.ai-terminology",{"title":186,"path":187,"stem":188},"资源链接与延伸阅读","\u002Fappendix\u002Fc.resources","8.appendix\u002Fc.resources",{"id":190,"title":138,"body":191,"description":4051,"extension":4052,"links":4053,"meta":4054,"navigation":586,"path":139,"seo":4056,"stem":140,"__hash__":4057},"docs\u002F6.practice\u002F3.core-features.md",{"type":192,"value":193,"toc":4011},"minimark",[194,203,206,213,235,242,245,250,255,262,351,357,360,364,367,370,390,393,400,419,426,430,433,454,461,472,474,478,482,493,496,499,505,508,511,525,528,532,539,637,644,648,655,658,666,669,683,690,694,701,707,710,716,722,724,728,732,739,822,825,831,838,842,845,877,884,888,894,897,929,932,938,945,949,952,1118,1121,1138,1145,1147,1151,1154,1157,1161,1166,1172,1177,1540,1546,1554,1558,1562,1568,1573,2190,2195,2202,2206,2210,2216,2221,2582,2587,2594,2598,2602,2608,2612,3072,3077,3084,3088,3092,3098,3103,3110,3114,3118,3124,3129,3136,3138,3144,3154,3164,3166,3170,3173,3177,3183,3189,3194,3218,3228,3234,3240,3243,3247,3252,3257,3261,3296,3302,3360,3363,3367,3372,3377,3381,3401,3423,3430,3434,3439,3443,3547,3550,3556,3562,3564,3568,3571,3575,3582,3678,3683,3694,3711,3715,3718,3724,3727,3732,3752,3755,3759,3762,3768,3775,3782,3787,3811,3816,3836,3843,3845,3848,3851,3858,3864,3887,3890,3893,3909,3912,3915,4007],[195,196,197,198,202],"p",{},"6.2 节落幕时，工程的\"骨架\"已经立起来了：仓库、tsconfig、ESLint、CLAUDE.md、Skills、CI 流水线。这一刻，团队会面临一个比脚手架阶段更难的问题——",[199,200,201],"strong",{},"如何把\"骨架\"长出\"血肉\"","。在传统模式下，这意味着接下来 2 到 8 周的功能开发会反复经历\"写代码 → 跑测试 → 调试 → 改代码\"的循环；而在 Claude Code 的协作模式下，这个循环的颗粒度被压缩到分钟级别，每一次对话都是一次\"假设 → 验证 → 提交\"的迭代闭环（Iterative Loop）。这种节奏的转变并非锦上添花，而是工程文化层面的根本重构。",[195,204,205],{},"本节要回答的问题不是\"Claude 能不能写代码\"，而是\"如何让多轮迭代成为团队稳定的肌肉记忆\"。我们会拆解迭代的最小有效单元、人机分工的边界、典型反模式，最后落到一份可以立即套用的\"6 轮迭代用户管理模块\"的实战脚本。读完这一节，工程师应当能直接把方法论搬到自己的项目里、产品经理应当能向团队解释\"为什么我们的开发节奏看起来又快又稳\"。",[195,207,208,209,212],{},"需要先澄清一个常见误解：",[199,210,211],{},"多轮迭代不是\"迭代万能论\"","。有些场景多轮迭代非但不增效，反而会拖慢节奏：",[214,215,216,223,229],"ul",{},[217,218,219,222],"li",{},[199,220,221],{},"一次性脚本","：例如\"读 CSV、清洗 100 条数据、输出 JSON\"。需求清晰、生命周期短、不需要演进——这种场景一次成型即可，多轮迭代是过度工程。",[217,224,225,228],{},[199,226,227],{},"强依赖外部规范的代码","：例如实现一份既定的 RFC 协议。规范本身就是\"完成定义\"，让 Claude 一次贴着规范实现，再用对照测试验证即可。",[217,230,231,234],{},[199,232,233],{},"机器学习管线（Pipeline）的中间步骤","：例如\"把 embedding 模型从 v1 切到 v2\"。这种迁移有明确的等价关系约束，分多轮迭代反而会引入\"半新半旧\"的不一致状态。",[195,236,237,238,241],{},"适合多轮迭代的场景特征是：",[199,239,240],{},"需求带歧义、决策有空间、上下文会演化","——典型如业务功能开发、领域建模、UI 交互、API 契约设计。本节聚焦的\"核心功能实现\"正是多轮迭代的甜蜜点（Sweet Spot），所有的方法论都建立在这个前提之上。读者在套用之前，先判断自己的场景是否落在这个范围内。",[243,244],"hr",{},[246,247,249],"h2",{"id":248},"一为什么核心功能需要多轮迭代","一、为什么核心功能需要多轮迭代",[251,252,254],"h3",{"id":253},"_11-一次成型-vs-多轮迭代的代价对比","1.1 一次成型 vs 多轮迭代的代价对比",[195,256,257,258,261],{},"软件工程史上，\"一次把功能写对\"的诱惑从未消失。瀑布模型（Waterfall）、详尽的 PRD、长达数月的开发周期，本质上都是\"赌一次写对\"。但凡有过交付经验的工程师都知道——",[199,259,260],{},"这种赌局长期负收益","。",[263,264,265,281],"table",{},[266,267,268],"thead",{},[269,270,271,275,278],"tr",{},[272,273,274],"th",{},"维度",[272,276,277],{},"一次成型（Big Bang）",[272,279,280],{},"多轮迭代（Iterative Loop）",[282,283,284,296,307,318,329,340],"tbody",{},[269,285,286,290,293],{},[287,288,289],"td",{},"单次提交的代码量",[287,291,292],{},"通常 ≥ 500 行",[287,294,295],{},"通常 ≤ 80 行",[269,297,298,301,304],{},[287,299,300],{},"单次审查耗时",[287,302,303],{},"≥ 30 分钟",[287,305,306],{},"≤ 5 分钟",[269,308,309,312,315],{},[287,310,311],{},"Bug 定位平均成本",[287,313,314],{},"高，需要回溯整段逻辑",[287,316,317],{},"低，最近一次提交即嫌疑人",[269,319,320,323,326],{},[287,321,322],{},"反向重构的代价",[287,324,325],{},"几乎无法局部回滚",[287,327,328],{},"可单提交 revert",[269,330,331,334,337],{},[287,332,333],{},"Claude 的上下文消耗",[287,335,336],{},"一次塞入大块代码，token 浪费",[287,338,339],{},"小步快跑，每轮聚焦",[269,341,342,345,348],{},[287,343,344],{},"失败的心理成本",[287,346,347],{},"高，\"白干两天\"",[287,349,350],{},"低，\"再来一轮\"",[195,352,353,354,261],{},"Claude Code 把每一轮迭代的边际成本压到了\"一次对话 + 一次 git commit\"的程度。这意味着",[199,355,356],{},"多轮迭代不再是工程美德，而是工程经济学的最优解",[195,358,359],{},"更关键的是，多轮迭代的\"产物\"不只是代码，还包括：每一轮的 prompt（沉淀为 Skills 的输入语料）、每一轮的审查决策（沉淀为 CLAUDE.md 的团队共识）、每一轮的 git diff（沉淀为未来 review 的上下文档案）。一次成型的开发模式只产出代码，多轮迭代会产出\"代码 + 流程资产\"。半年累计下来，这部分流程资产对团队效率的复利效应往往超过代码本身的价值。",[251,361,363],{"id":362},"_12-claude-code-与传统-ide-在迭代成本上的根本差异","1.2 Claude Code 与传统 IDE 在迭代成本上的根本差异",[195,365,366],{},"传统 IDE 时代，\"迭代成本\"主要由以下几部分构成：开发者切换上下文的脑力开销、查文档\u002F搜代码的时间、写测试的时间、跑测试的时间、调试的时间。其中前三项往往占了 60% 以上。",[195,368,369],{},"Claude Code 把这些时间压到接近零：",[214,371,372,378,384],{},[217,373,374,377],{},[199,375,376],{},"上下文切换","：Claude 始终在线，不需要工程师\"重新加载脑内 RAM\"；",[217,379,380,383],{},[199,381,382],{},"查文档\u002F搜代码","：Claude 通过代码库扫描与 MCP（如 context7）实时获取信息；",[217,385,386,389],{},[199,387,388],{},"写测试","：让 Claude 在写实现的同时生成测试草稿；",[195,391,392],{},"剩下的\"跑测试 + 调试\"部分由工程师审查，但因为单次提交极小，调试范围也极小。",[195,394,395,396,399],{},"经验数据：本人在多个项目中统计，Claude Code 协作下平均每轮迭代耗时 ",[199,397,398],{},"8-15 分钟","（含审查与 commit），而传统模式下同等粒度的迭代往往需要 30-60 分钟。这种 3-4 倍的提速并不是\"AI 写得快\"，而是\"循环本身变短了\"。",[195,401,402,403,406,407,410,411,414,415,418],{},"更深一层的观察：",[199,404,405],{},"节省下来的时间不会自动转化为\"做更多功能\"","——它会被工程师用于以下三件事的某一件：（1）",[199,408,409],{},"提高代码质量","，例如给同一个功能补上更详尽的边界测试；（2）",[199,412,413],{},"做以前来不及做的重构","，例如把第 3 轮迭代里\"先抄一份\"的代码抽离为公共模块；（3）",[199,416,417],{},"陪 Claude 做更多对话探索","，例如让 Claude 列举 5 种实现方案并讨论各自的折衷。这三种用法的优先级取决于团队当前阶段——MVP 期优先（3）、稳定期优先（1）、成长期优先（2）。",[195,420,421,422,425],{},"值得警惕的反向观察：",[199,423,424],{},"如果团队把节省下来的时间全部用于\"做更多功能\"，迭代成本曲线会快速反弹","。原因是新功能不会消失，技术债会随线性增长，而工程师的\"债务清算时间\"被节省走的时间挤压殆尽。本人称之为\"AI 节奏陷阱\"——节奏快不代表方向正确。",[251,427,429],{"id":428},"_13-迭代节奏的三个层级","1.3 迭代节奏的三个层级",[195,431,432],{},"为了避免把\"迭代\"这个词糊在一起，我们把它拆成三个层级：",[434,435,436,442,448],"ol",{},[217,437,438,441],{},[199,439,440],{},"Token 级迭代","：Claude 在生成下一行代码时的微观选择。这是 LLM 的内部工作，工程师不可见。",[217,443,444,447],{},[199,445,446],{},"函数级迭代","：单次对话产出 1-3 个函数 \u002F 一个组件 \u002F 一个类型定义。这是工程师每分钟级别能感知到的循环。",[217,449,450,453],{},[199,451,452],{},"功能级迭代","：完成一个用户故事（User Story）或验收标准（Acceptance Criteria）。这是产品经理能看到的循环。",[195,455,456,457,460],{},"混淆这三个层级会带来麻烦。例如要求 Claude \"实现整个用户管理模块\"——这是功能级迭代，但 Claude 会在内部塞进十几个 token 级与函数级决策，",[199,458,459],{},"等工程师审查时一切都已木已成舟","。正确做法是把功能级拆到函数级，再让 Claude 接管 token 级。",[195,462,463,464,467,468,471],{},"层级混淆的另一种常见后果是",[199,465,466],{},"审查疲劳","。当工程师面对一份 600 行的 PR diff，注意力会很快被消耗——前 100 行还能仔细看，后 500 行往往就只是扫一眼。Claude 的\"幻觉\"在大尺寸 PR 中不容易被发现，恰恰因为审查者已经累了。把功能级拆成 5-7 个函数级 PR、每个 PR ≤ 100 行 diff，审查注意力可以保持高位，Bug 漏过的概率显著下降。这是把\"层级管理\"从纯流程问题升级为",[199,469,470],{},"人因工程问题","——尊重人脑的认知边界，而不是假设审查者能一直保持 100% 专注。",[243,473],{},[246,475,477],{"id":476},"二迭代单元的最小有效设计","二、迭代单元的最小有效设计",[251,479,481],{"id":480},"_21-让-claude-理解小而完整的任务边界","2.1 让 Claude 理解\"小而完整\"的任务边界",[195,483,484,485,488,489,492],{},"\"小而完整\"是迭代单元的金标准。",[199,486,487],{},"小","意味着可审查、可回滚、可在 5 分钟内验证；",[199,490,491],{},"完整","意味着这一轮迭代的产出能独立运行、独立通过测试，不需要等下一轮才能跑起来。",[195,494,495],{},"举个对照例子。",[195,497,498],{},"❌ 不好的任务边界：",[500,501,502],"blockquote",{},[195,503,504],{},"帮我实现用户管理模块。",[195,506,507],{},"太大了。Claude 会一次性塞进数据模型、CRUD、校验、错误处理、测试、文档——任何一处错都会牵连其他部分。",[195,509,510],{},"✅ 好的任务边界：",[500,512,513],{},[195,514,515,516,520,521,524],{},"帮我实现 ",[517,518,519],"code",{},"User"," 类型的 TypeScript 接口定义，含 id、name、email、createdAt 字段，并写 1 个最小工厂函数 ",[517,522,523],{},"createUser(input)"," 用于测试夹具。不要写 CRUD，不要写校验。",[195,526,527],{},"边界清晰、产出明确、可在 60 秒内验证。",[251,529,531],{"id":530},"_22-一次提交一个可验证假设","2.2 一次提交一个可验证假设",[195,533,534,535,538],{},"这条原则借鉴自 ",[199,536,537],{},"Hypothesis-Driven Development（假设驱动开发）","——每一次提交都对应一个可被测试证伪的假设。",[540,541,546],"pre",{"className":542,"code":543,"language":544,"meta":545,"style":545},"language-bash shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","# 第 1 轮：假设 User 类型只需 4 个字段\ngit commit -m \"feat(user): 定义 User 类型与最小工厂函数\"\n\n# 第 2 轮：假设 createUser 在 email 不合法时应抛错\ngit commit -m \"feat(user): createUser 增加 email 格式校验\"\n\n# 第 3 轮：假设需要持久化到 SQLite\ngit commit -m \"feat(user): 增加 saveUser 持久化到 better-sqlite3\"\n","bash","",[517,547,548,557,581,588,594,610,615,621],{"__ignoreMap":545},[549,550,553],"span",{"class":551,"line":552},"line",1,[549,554,556],{"class":555},"sHwdD","# 第 1 轮：假设 User 类型只需 4 个字段\n",[549,558,560,564,568,571,575,578],{"class":551,"line":559},2,[549,561,563],{"class":562},"sBMFI","git",[549,565,567],{"class":566},"sfazB"," commit",[549,569,570],{"class":566}," -m",[549,572,574],{"class":573},"sMK4o"," \"",[549,576,577],{"class":566},"feat(user): 定义 User 类型与最小工厂函数",[549,579,580],{"class":573},"\"\n",[549,582,584],{"class":551,"line":583},3,[549,585,587],{"emptyLinePlaceholder":586},true,"\n",[549,589,591],{"class":551,"line":590},4,[549,592,593],{"class":555},"# 第 2 轮：假设 createUser 在 email 不合法时应抛错\n",[549,595,597,599,601,603,605,608],{"class":551,"line":596},5,[549,598,563],{"class":562},[549,600,567],{"class":566},[549,602,570],{"class":566},[549,604,574],{"class":573},[549,606,607],{"class":566},"feat(user): createUser 增加 email 格式校验",[549,609,580],{"class":573},[549,611,613],{"class":551,"line":612},6,[549,614,587],{"emptyLinePlaceholder":586},[549,616,618],{"class":551,"line":617},7,[549,619,620],{"class":555},"# 第 3 轮：假设需要持久化到 SQLite\n",[549,622,624,626,628,630,632,635],{"class":551,"line":623},8,[549,625,563],{"class":562},[549,627,567],{"class":566},[549,629,570],{"class":566},[549,631,574],{"class":573},[549,633,634],{"class":566},"feat(user): 增加 saveUser 持久化到 better-sqlite3",[549,636,580],{"class":573},[195,638,639,640,643],{},"每个 commit 对应一个假设。如果某个假设被推翻（例如团队决定 email 校验交给前端做），可以单独 ",[517,641,642],{},"git revert"," 而不影响其他部分。",[251,645,647],{"id":646},"_23-从-tdd-到-atdd-的对话式实践","2.3 从 TDD 到 ATDD 的对话式实践",[195,649,650,651,654],{},"传统 TDD（Test-Driven Development，测试驱动开发）流程是 ",[199,652,653],{},"Red → Green → Refactor","——先写失败的测试、再写最少代码让测试通过、最后重构。",[195,656,657],{},"Claude Code 让这个循环可以更自然。一种行之有效的对话模板：",[540,659,664],{"className":660,"code":662,"language":663},[661],"language-text","我要实现 createUser 函数。先帮我写一个 Vitest 测试，覆盖以下场景：\n- 正常输入返回带 id 与 createdAt 的 User\n- email 为空字符串应抛 ValidationError\n- name 长度超过 100 应抛 ValidationError\n\n写完测试后停下，等我确认再写实现。\n","text",[517,665,662],{"__ignoreMap":545},[195,667,668],{},"让 Claude 先写测试、再写实现。这有两个好处：",[434,670,671,677],{},[217,672,673,676],{},[199,674,675],{},"测试是契约","——Claude 写测试时被迫思考边界条件，减少幻觉式实现。",[217,678,679,682],{},[199,680,681],{},"审查门槛降低","——工程师只需读一遍测试就能判断 Claude 对需求的理解是否正确。",[195,684,685,686,689],{},"进一步，",[199,687,688],{},"ATDD（Acceptance Test-Driven Development，验收测试驱动开发）"," 把这条链路延伸到验收层。让 Claude 把产品经理写的\"用户故事\"翻译成可执行的验收测试（如 Playwright 用例），然后再下推到单元测试和实现。",[251,691,693],{"id":692},"_24-让-claude-主动列出完成定义","2.4 让 Claude 主动列出\"完成定义\"",[195,695,696,697,700],{},"每一轮迭代开始前，让 Claude 先输出一份 ",[199,698,699],{},"Definition of Done（完成定义）","，并等工程师确认。",[540,702,705],{"className":703,"code":704,"language":663},[661],"我要在 6.3.4 节加一个 createUser 函数。开始之前，请输出本轮迭代的 Definition of Done，\n包含：\n- 文件清单（新增 \u002F 修改）\n- 函数签名\n- 测试用例编号\n- 不在本轮范围内的事项\n\n我确认后你再开始写代码。\n",[517,706,704],{"__ignoreMap":545},[195,708,709],{},"Claude 输出示例：",[540,711,714],{"className":712,"code":713,"language":663},[661],"本轮迭代的 Definition of Done：\n\n【新增文件】\n- src\u002Fuser\u002Ftypes.ts（User、CreateUserInput、ValidationError）\n- src\u002Fuser\u002Ffactory.ts（createUser 函数）\n- src\u002Fuser\u002F__tests__\u002Ffactory.test.ts（3 个用例）\n\n【函数签名】\nfunction createUser(input: CreateUserInput): User\ntype CreateUserInput = { name: string; email: string }\n\n【测试用例】\n1. 正常输入返回 User，含 id 与 createdAt\n2. email 不合法抛 ValidationError\n3. name 超长抛 ValidationError\n\n【不在本轮范围】\n- 持久化（下一轮）\n- 鉴权与权限校验\n- API 路由\n- updateUser \u002F deleteUser\n",[517,715,713],{"__ignoreMap":545},[195,717,718,719,261],{},"工程师读完点头，Claude 才开始动手。这条流程的隐含价值是——",[199,720,721],{},"把\"理解偏差\"前置发现，而不是在 200 行代码写完后再发现",[243,723],{},[246,725,727],{"id":726},"三迭代过程中的人机分工","三、迭代过程中的人机分工",[251,729,731],{"id":730},"_31-工程师保留的高阶决策点","3.1 工程师保留的高阶决策点",[195,733,734,735,738],{},"Claude 是优秀的\"执行者\"，但远未达到优秀\"决策者\"的水准。以下决策必须由工程师把关，",[199,736,737],{},"不能让 Claude 自动决定","：",[263,740,741,754],{},[266,742,743],{},[269,744,745,748,751],{},[272,746,747],{},"决策类型",[272,749,750],{},"例子",[272,752,753],{},"为什么不能让 Claude 拍板",[282,755,756,767,778,789,800,811],{},[269,757,758,761,764],{},[287,759,760],{},"架构边界",[287,762,763],{},"把鉴权放在 API 层还是中间件",[287,765,766],{},"决定后续 6 个月的演进方向",[269,768,769,772,775],{},[287,770,771],{},"业务规则",[287,773,774],{},"\"用户能否同时拥有多个手机号\"",[287,776,777],{},"Claude 不知道你公司的合规要求",[269,779,780,783,786],{},[287,781,782],{},"性能约束",[287,784,785],{},"\"响应时间必须 \u003C 200ms\"",[287,787,788],{},"决定后续技术选型与基础设施",[269,790,791,794,797],{},[287,792,793],{},"安全决策",[287,795,796],{},"\"密码用 bcrypt 还是 argon2\"",[287,798,799],{},"长期合规、攻击面、密钥管理",[269,801,802,805,808],{},[287,803,804],{},"数据合规",[287,806,807],{},"GDPR、个保法的字段处理",[287,809,810],{},"法律责任无法转嫁",[269,812,813,816,819],{},[287,814,815],{},"资源成本",[287,817,818],{},"是否启用 LLM 调用作为业务功能",[287,820,821],{},"直接影响商业模型",[195,823,824],{},"工程师在每轮迭代开始前确认这些决策，再把\"如何执行\"交给 Claude。",[195,826,827,830],{},[199,828,829],{},"判断\"这是不是高阶决策\"的简易标准","：问自己一个问题——\"如果一年后回头看，我会希望当时花 10 分钟思考、还是希望当时让 AI 自动决定？\"。任何一年内仍然影响系统行为的决策（架构、契约、协议、数据格式、依赖选择），都值得人类多花一点时间。低于一年的决策（变量名、函数顺序、注释措辞）放心交给 Claude。这条标准不完美，但能挡住 80% 的\"不该让 AI 拍板\"的事。",[195,832,833,834,837],{},"另一个常见误区是\"我让 Claude 给我几个方案我来选\"——这看起来像是工程师在决策，实际上 ",[199,835,836],{},"Claude 列出方案的过程已经隐含了筛选偏见","。它倾向于训练数据里高频的方案，例如永远把 PostgreSQL 排在 SQLite 前面、永远把 Redis 排在内存缓存前面。要破这层偏见，工程师应该自己先列出 3 个候选，再让 Claude 补充第 4、5 个候选并指出每个的隐性风险。先列后补，结果质量明显高于纯让 Claude 起草。",[251,839,841],{"id":840},"_32-claude-接管的执行细节","3.2 Claude 接管的执行细节",[195,843,844],{},"反过来，以下执行细节让 Claude 接管能极大节省时间：",[214,846,847,853,859,865,871],{},[217,848,849,852],{},[199,850,851],{},"语法层","：TypeScript 类型推导、泛型约束、装饰器写法",[217,854,855,858],{},[199,856,857],{},"接口签名","：函数命名、参数顺序、返回类型",[217,860,861,864],{},[199,862,863],{},"重复模式","：CRUD 模板、Validator、DTO 转换",[217,866,867,870],{},[199,868,869],{},"测试夹具","：Faker.js 数据生成、mock 函数",[217,872,873,876],{},[199,874,875],{},"样板代码","：Express 路由、Vue SFC 骨架、错误中间件",[195,878,879,880,883],{},"Claude 接管这些细节时，",[199,881,882],{},"工程师审查的重点是\"这一段代码是否符合项目惯例\"","——而项目惯例由 CLAUDE.md 与 Skills 沉淀，进一步把审查门槛降低。",[251,885,887],{"id":886},"_33-plan-模式如何把自动执行降级为人工审批","3.3 Plan 模式如何把\"自动执行\"降级为\"人工审批\"",[195,889,890,891,261],{},"Claude Code 的 Plan 模式（Plan Mode）是迭代过程中的关键守门人。它的核心机制：在执行任何写文件、跑命令的动作之前，",[199,892,893],{},"强制 Claude 输出一份完整的 Plan 让工程师审批",[195,895,896],{},"启用方式：",[540,898,900],{"className":542,"code":899,"language":544,"meta":545,"style":545},"# 命令行启动时进入 Plan 模式\nclaude --plan-mode\n\n# 或会话中临时切换\n\u002Fplan\n",[517,901,902,907,915,919,924],{"__ignoreMap":545},[549,903,904],{"class":551,"line":552},[549,905,906],{"class":555},"# 命令行启动时进入 Plan 模式\n",[549,908,909,912],{"class":551,"line":559},[549,910,911],{"class":562},"claude",[549,913,914],{"class":566}," --plan-mode\n",[549,916,917],{"class":551,"line":583},[549,918,587],{"emptyLinePlaceholder":586},[549,920,921],{"class":551,"line":590},[549,922,923],{"class":555},"# 或会话中临时切换\n",[549,925,926],{"class":551,"line":596},[549,927,928],{"class":562},"\u002Fplan\n",[195,930,931],{},"Plan 输出示例：",[540,933,936],{"className":934,"code":935,"language":663},[661],"我将完成以下动作：\n1. 修改 src\u002Fuser\u002Ffactory.ts，添加 phoneNumber 字段处理（约 12 行）\n2. 修改 src\u002Fuser\u002Ftypes.ts，扩展 User 接口（约 3 行）\n3. 修改 src\u002Fuser\u002F__tests__\u002Ffactory.test.ts，新增 2 个测试用例（约 25 行）\n4. 运行 pnpm test src\u002Fuser 验证（约 8 秒）\n5. 提交 commit \"feat(user): 增加 phoneNumber 字段\"\n\n请审批 [Y\u002Fn]：\n",[517,937,935],{"__ignoreMap":545},[195,939,940,941,944],{},"工程师可以选择",[199,942,943],{},"全部批准、部分批准、修改后再批准、整体拒绝","。这条机制把\"AI 自动 commit\"的失控风险降到接近零。",[251,946,948],{"id":947},"_34-hooks-在每轮迭代前后的守卫作用","3.4 Hooks 在每轮迭代前后的守卫作用",[195,950,951],{},"Hooks 是 Claude Code 的事件驱动守卫机制，它能在每个工具调用前后插入工程师定义的 shell 命令。",[540,953,957],{"className":954,"code":955,"language":956,"meta":545,"style":545},"language-json shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","{\n  \"hooks\": {\n    \"PostToolUse\": [\n      {\n        \"matcher\": \"Edit|Write\",\n        \"hooks\": [\n          {\n            \"type\": \"command\",\n            \"command\": \"pnpm lint --max-warnings=0 --quiet\"\n          }\n        ]\n      }\n    ]\n  }\n}\n","json",[517,958,959,964,982,997,1002,1025,1037,1042,1064,1082,1088,1094,1100,1106,1112],{"__ignoreMap":545},[549,960,961],{"class":551,"line":552},[549,962,963],{"class":573},"{\n",[549,965,966,969,973,976,979],{"class":551,"line":559},[549,967,968],{"class":573},"  \"",[549,970,972],{"class":971},"spNyl","hooks",[549,974,975],{"class":573},"\"",[549,977,978],{"class":573},":",[549,980,981],{"class":573}," {\n",[549,983,984,987,990,992,994],{"class":551,"line":583},[549,985,986],{"class":573},"    \"",[549,988,989],{"class":562},"PostToolUse",[549,991,975],{"class":573},[549,993,978],{"class":573},[549,995,996],{"class":573}," [\n",[549,998,999],{"class":551,"line":590},[549,1000,1001],{"class":573},"      {\n",[549,1003,1004,1007,1011,1013,1015,1017,1020,1022],{"class":551,"line":596},[549,1005,1006],{"class":573},"        \"",[549,1008,1010],{"class":1009},"sbssI","matcher",[549,1012,975],{"class":573},[549,1014,978],{"class":573},[549,1016,574],{"class":573},[549,1018,1019],{"class":566},"Edit|Write",[549,1021,975],{"class":573},[549,1023,1024],{"class":573},",\n",[549,1026,1027,1029,1031,1033,1035],{"class":551,"line":612},[549,1028,1006],{"class":573},[549,1030,972],{"class":1009},[549,1032,975],{"class":573},[549,1034,978],{"class":573},[549,1036,996],{"class":573},[549,1038,1039],{"class":551,"line":617},[549,1040,1041],{"class":573},"          {\n",[549,1043,1044,1047,1051,1053,1055,1057,1060,1062],{"class":551,"line":623},[549,1045,1046],{"class":573},"            \"",[549,1048,1050],{"class":1049},"swJcz","type",[549,1052,975],{"class":573},[549,1054,978],{"class":573},[549,1056,574],{"class":573},[549,1058,1059],{"class":566},"command",[549,1061,975],{"class":573},[549,1063,1024],{"class":573},[549,1065,1067,1069,1071,1073,1075,1077,1080],{"class":551,"line":1066},9,[549,1068,1046],{"class":573},[549,1070,1059],{"class":1049},[549,1072,975],{"class":573},[549,1074,978],{"class":573},[549,1076,574],{"class":573},[549,1078,1079],{"class":566},"pnpm lint --max-warnings=0 --quiet",[549,1081,580],{"class":573},[549,1083,1085],{"class":551,"line":1084},10,[549,1086,1087],{"class":573},"          }\n",[549,1089,1091],{"class":551,"line":1090},11,[549,1092,1093],{"class":573},"        ]\n",[549,1095,1097],{"class":551,"line":1096},12,[549,1098,1099],{"class":573},"      }\n",[549,1101,1103],{"class":551,"line":1102},13,[549,1104,1105],{"class":573},"    ]\n",[549,1107,1109],{"class":551,"line":1108},14,[549,1110,1111],{"class":573},"  }\n",[549,1113,1115],{"class":551,"line":1114},15,[549,1116,1117],{"class":573},"}\n",[195,1119,1120],{},"这条 hook 会在 Claude 每次 Edit 或 Write 文件后立即跑 ESLint，警告会被立刻拦截。结合\"Hooks 失败时 Claude 必须回应\"的机制，可以做到：",[214,1122,1123,1128,1133],{},[217,1124,1125],{},[199,1126,1127],{},"Lint 不通过 → Claude 立即修复",[217,1129,1130],{},[199,1131,1132],{},"类型检查失败 → Claude 自动重写",[217,1134,1135],{},[199,1136,1137],{},"测试失败 → Claude 暂停并请求工程师介入",[195,1139,1140,1141,1144],{},"Hooks 的关键价值是",[199,1142,1143],{},"把\"人工审查\"的部分自动化","，让工程师专注在更高层的决策审查上。",[243,1146],{},[246,1148,1150],{"id":1149},"四实战用户管理模块的-6-轮迭代","四、实战：用户管理模块的 6 轮迭代",[195,1152,1153],{},"下面给出一个可以立即套用的对话脚本。我们要做的功能：实现一个最小化的用户管理模块，包含数据模型、CRUD、错误处理、测试、权限校验、性能优化。",[195,1155,1156],{},"技术栈：TypeScript + Vitest + better-sqlite3。",[251,1158,1160],{"id":1159},"_41-第-1-轮数据模型与类型定义","4.1 第 1 轮：数据模型与类型定义",[195,1162,1163],{},[199,1164,1165],{},"对话提示词：",[540,1167,1170],{"className":1168,"code":1169,"language":663},[661],"我要做用户管理模块。第一轮：定义数据模型。\n\n请创建：\n- src\u002Fuser\u002Ftypes.ts，含 User 接口（id: string, name: string, email: string, createdAt: Date）\n- src\u002Fuser\u002Ftypes.ts 同文件含 CreateUserInput、UpdateUserInput\n- src\u002Fuser\u002Ferrors.ts，含 ValidationError、NotFoundError 两个错误类\n\n不要写实现，不要写测试。完成后输出文件清单与 commit message。\n",[517,1171,1169],{"__ignoreMap":545},[195,1173,1174],{},[199,1175,1176],{},"Claude 产出：",[540,1178,1182],{"className":1179,"code":1180,"language":1181,"meta":545,"style":545},"language-typescript shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","\u002F\u002F src\u002Fuser\u002Ftypes.ts\nexport interface User {\n  id: string;\n  name: string;\n  email: string;\n  createdAt: Date;\n}\n\nexport type CreateUserInput = Omit\u003CUser, \"id\" | \"createdAt\">;\nexport type UpdateUserInput = Partial\u003CCreateUserInput>;\n\n\u002F\u002F src\u002Fuser\u002Ferrors.ts\nexport class ValidationError extends Error {\n  constructor(public field: string, message: string) {\n    super(message);\n    this.name = \"ValidationError\";\n  }\n}\n\nexport class NotFoundError extends Error {\n  constructor(public resource: string, public id: string) {\n    super(`${resource} not found: ${id}`);\n    this.name = \"NotFoundError\";\n  }\n}\n","typescript",[517,1183,1184,1189,1203,1216,1227,1238,1250,1254,1258,1302,1323,1327,1332,1350,1383,1398,1418,1423,1428,1433,1449,1481,1512,1530,1535],{"__ignoreMap":545},[549,1185,1186],{"class":551,"line":552},[549,1187,1188],{"class":555},"\u002F\u002F src\u002Fuser\u002Ftypes.ts\n",[549,1190,1191,1195,1198,1201],{"class":551,"line":559},[549,1192,1194],{"class":1193},"s7zQu","export",[549,1196,1197],{"class":971}," interface",[549,1199,1200],{"class":562}," User",[549,1202,981],{"class":573},[549,1204,1205,1208,1210,1213],{"class":551,"line":583},[549,1206,1207],{"class":1049},"  id",[549,1209,978],{"class":573},[549,1211,1212],{"class":562}," string",[549,1214,1215],{"class":573},";\n",[549,1217,1218,1221,1223,1225],{"class":551,"line":590},[549,1219,1220],{"class":1049},"  name",[549,1222,978],{"class":573},[549,1224,1212],{"class":562},[549,1226,1215],{"class":573},[549,1228,1229,1232,1234,1236],{"class":551,"line":596},[549,1230,1231],{"class":1049},"  email",[549,1233,978],{"class":573},[549,1235,1212],{"class":562},[549,1237,1215],{"class":573},[549,1239,1240,1243,1245,1248],{"class":551,"line":612},[549,1241,1242],{"class":1049},"  createdAt",[549,1244,978],{"class":573},[549,1246,1247],{"class":562}," Date",[549,1249,1215],{"class":573},[549,1251,1252],{"class":551,"line":617},[549,1253,1117],{"class":573},[549,1255,1256],{"class":551,"line":623},[549,1257,587],{"emptyLinePlaceholder":586},[549,1259,1260,1262,1265,1268,1271,1274,1277,1279,1282,1284,1287,1289,1292,1294,1297,1299],{"class":551,"line":1066},[549,1261,1194],{"class":1193},[549,1263,1264],{"class":971}," type",[549,1266,1267],{"class":562}," CreateUserInput",[549,1269,1270],{"class":573}," =",[549,1272,1273],{"class":562}," Omit",[549,1275,1276],{"class":573},"\u003C",[549,1278,519],{"class":562},[549,1280,1281],{"class":573},",",[549,1283,574],{"class":573},[549,1285,1286],{"class":566},"id",[549,1288,975],{"class":573},[549,1290,1291],{"class":573}," |",[549,1293,574],{"class":573},[549,1295,1296],{"class":566},"createdAt",[549,1298,975],{"class":573},[549,1300,1301],{"class":573},">;\n",[549,1303,1304,1306,1308,1311,1313,1316,1318,1321],{"class":551,"line":1084},[549,1305,1194],{"class":1193},[549,1307,1264],{"class":971},[549,1309,1310],{"class":562}," UpdateUserInput",[549,1312,1270],{"class":573},[549,1314,1315],{"class":562}," Partial",[549,1317,1276],{"class":573},[549,1319,1320],{"class":562},"CreateUserInput",[549,1322,1301],{"class":573},[549,1324,1325],{"class":551,"line":1090},[549,1326,587],{"emptyLinePlaceholder":586},[549,1328,1329],{"class":551,"line":1096},[549,1330,1331],{"class":555},"\u002F\u002F src\u002Fuser\u002Ferrors.ts\n",[549,1333,1334,1336,1339,1342,1345,1348],{"class":551,"line":1102},[549,1335,1194],{"class":1193},[549,1337,1338],{"class":971}," class",[549,1340,1341],{"class":562}," ValidationError",[549,1343,1344],{"class":971}," extends",[549,1346,1347],{"class":562}," Error",[549,1349,981],{"class":573},[549,1351,1352,1355,1358,1361,1365,1367,1369,1371,1374,1376,1378,1381],{"class":551,"line":1108},[549,1353,1354],{"class":971},"  constructor",[549,1356,1357],{"class":573},"(",[549,1359,1360],{"class":971},"public",[549,1362,1364],{"class":1363},"sHdIc"," field",[549,1366,978],{"class":573},[549,1368,1212],{"class":562},[549,1370,1281],{"class":573},[549,1372,1373],{"class":1363}," message",[549,1375,978],{"class":573},[549,1377,1212],{"class":562},[549,1379,1380],{"class":573},")",[549,1382,981],{"class":573},[549,1384,1385,1389,1391,1394,1396],{"class":551,"line":1114},[549,1386,1388],{"class":1387},"sTEyZ","    super",[549,1390,1357],{"class":1049},[549,1392,1393],{"class":1387},"message",[549,1395,1380],{"class":1049},[549,1397,1215],{"class":573},[549,1399,1401,1404,1407,1409,1411,1414,1416],{"class":551,"line":1400},16,[549,1402,1403],{"class":573},"    this.",[549,1405,1406],{"class":1387},"name",[549,1408,1270],{"class":573},[549,1410,574],{"class":573},[549,1412,1413],{"class":566},"ValidationError",[549,1415,975],{"class":573},[549,1417,1215],{"class":573},[549,1419,1421],{"class":551,"line":1420},17,[549,1422,1111],{"class":573},[549,1424,1426],{"class":551,"line":1425},18,[549,1427,1117],{"class":573},[549,1429,1431],{"class":551,"line":1430},19,[549,1432,587],{"emptyLinePlaceholder":586},[549,1434,1436,1438,1440,1443,1445,1447],{"class":551,"line":1435},20,[549,1437,1194],{"class":1193},[549,1439,1338],{"class":971},[549,1441,1442],{"class":562}," NotFoundError",[549,1444,1344],{"class":971},[549,1446,1347],{"class":562},[549,1448,981],{"class":573},[549,1450,1452,1454,1456,1458,1461,1463,1465,1467,1470,1473,1475,1477,1479],{"class":551,"line":1451},21,[549,1453,1354],{"class":971},[549,1455,1357],{"class":573},[549,1457,1360],{"class":971},[549,1459,1460],{"class":1363}," resource",[549,1462,978],{"class":573},[549,1464,1212],{"class":562},[549,1466,1281],{"class":573},[549,1468,1469],{"class":971}," public",[549,1471,1472],{"class":1363}," id",[549,1474,978],{"class":573},[549,1476,1212],{"class":562},[549,1478,1380],{"class":573},[549,1480,981],{"class":573},[549,1482,1484,1486,1488,1491,1494,1497,1500,1503,1505,1508,1510],{"class":551,"line":1483},22,[549,1485,1388],{"class":1387},[549,1487,1357],{"class":1049},[549,1489,1490],{"class":573},"`${",[549,1492,1493],{"class":1387},"resource",[549,1495,1496],{"class":573},"}",[549,1498,1499],{"class":566}," not found: ",[549,1501,1502],{"class":573},"${",[549,1504,1286],{"class":1387},[549,1506,1507],{"class":573},"}`",[549,1509,1380],{"class":1049},[549,1511,1215],{"class":573},[549,1513,1515,1517,1519,1521,1523,1526,1528],{"class":551,"line":1514},23,[549,1516,1403],{"class":573},[549,1518,1406],{"class":1387},[549,1520,1270],{"class":573},[549,1522,574],{"class":573},[549,1524,1525],{"class":566},"NotFoundError",[549,1527,975],{"class":573},[549,1529,1215],{"class":573},[549,1531,1533],{"class":551,"line":1532},24,[549,1534,1111],{"class":573},[549,1536,1538],{"class":551,"line":1537},25,[549,1539,1117],{"class":573},[195,1541,1542,1545],{},[199,1543,1544],{},"审查要点","：类型字段是否齐全？错误类是否包含必要的上下文？",[195,1547,1548,738,1551],{},[199,1549,1550],{},"Commit",[517,1552,1553],{},"feat(user): 定义 User 类型与错误类",[251,1555,1557],{"id":1556},"_42-第-2-轮核心-crud-api","4.2 第 2 轮：核心 CRUD API",[195,1559,1560],{},[199,1561,1165],{},[540,1563,1566],{"className":1564,"code":1565,"language":663},[661],"基于第 1 轮的类型，实现 src\u002Fuser\u002Frepository.ts，包含：\n- createUser(input: CreateUserInput): User\n- getUser(id: string): User\n- updateUser(id: string, input: UpdateUserInput): User\n- deleteUser(id: string): void\n\n数据存储在内存 Map，先不做持久化。每个函数加 1 行注释说明用途。\n不要写测试，不要做校验。\n",[517,1567,1565],{"__ignoreMap":545},[195,1569,1570],{},[199,1571,1572],{},"Claude 产出（示例）：",[540,1574,1576],{"className":1179,"code":1575,"language":1181,"meta":545,"style":545},"\u002F\u002F src\u002Fuser\u002Frepository.ts\nimport { randomUUID } from \"node:crypto\";\nimport { User, CreateUserInput, UpdateUserInput } from \".\u002Ftypes\";\nimport { NotFoundError } from \".\u002Ferrors\";\n\nconst store = new Map\u003Cstring, User>();\n\n\u002F\u002F 新增用户：自动生成 id 与 createdAt\nexport function createUser(input: CreateUserInput): User {\n  const user: User = {\n    id: randomUUID(),\n    name: input.name,\n    email: input.email,\n    createdAt: new Date(),\n  };\n  store.set(user.id, user);\n  return user;\n}\n\n\u002F\u002F 查询用户：找不到时抛 NotFoundError\nexport function getUser(id: string): User {\n  const user = store.get(id);\n  if (!user) throw new NotFoundError(\"User\", id);\n  return user;\n}\n\n\u002F\u002F 更新用户：仅修改传入字段\nexport function updateUser(id: string, input: UpdateUserInput): User {\n  const user = getUser(id);\n  const updated = { ...user, ...input };\n  store.set(id, updated);\n  return updated;\n}\n\n\u002F\u002F 删除用户：找不到时抛 NotFoundError\nexport function deleteUser(id: string): void {\n  if (!store.has(id)) throw new NotFoundError(\"User\", id);\n  store.delete(id);\n}\n",[517,1577,1578,1583,1609,1638,1659,1663,1698,1702,1707,1733,1749,1762,1779,1795,1810,1815,1842,1851,1855,1859,1864,1887,1911,1950,1958,1962,1967,1973,2005,2024,2050,2071,2080,2085,2090,2096,2121,2167,2185],{"__ignoreMap":545},[549,1579,1580],{"class":551,"line":552},[549,1581,1582],{"class":555},"\u002F\u002F src\u002Fuser\u002Frepository.ts\n",[549,1584,1585,1588,1591,1594,1597,1600,1602,1605,1607],{"class":551,"line":559},[549,1586,1587],{"class":1193},"import",[549,1589,1590],{"class":573}," {",[549,1592,1593],{"class":1387}," randomUUID",[549,1595,1596],{"class":573}," }",[549,1598,1599],{"class":1193}," from",[549,1601,574],{"class":573},[549,1603,1604],{"class":566},"node:crypto",[549,1606,975],{"class":573},[549,1608,1215],{"class":573},[549,1610,1611,1613,1615,1617,1619,1621,1623,1625,1627,1629,1631,1634,1636],{"class":551,"line":583},[549,1612,1587],{"class":1193},[549,1614,1590],{"class":573},[549,1616,1200],{"class":1387},[549,1618,1281],{"class":573},[549,1620,1267],{"class":1387},[549,1622,1281],{"class":573},[549,1624,1310],{"class":1387},[549,1626,1596],{"class":573},[549,1628,1599],{"class":1193},[549,1630,574],{"class":573},[549,1632,1633],{"class":566},".\u002Ftypes",[549,1635,975],{"class":573},[549,1637,1215],{"class":573},[549,1639,1640,1642,1644,1646,1648,1650,1652,1655,1657],{"class":551,"line":590},[549,1641,1587],{"class":1193},[549,1643,1590],{"class":573},[549,1645,1442],{"class":1387},[549,1647,1596],{"class":573},[549,1649,1599],{"class":1193},[549,1651,574],{"class":573},[549,1653,1654],{"class":566},".\u002Ferrors",[549,1656,975],{"class":573},[549,1658,1215],{"class":573},[549,1660,1661],{"class":551,"line":596},[549,1662,587],{"emptyLinePlaceholder":586},[549,1664,1665,1668,1671,1674,1677,1681,1683,1686,1688,1690,1693,1696],{"class":551,"line":612},[549,1666,1667],{"class":971},"const",[549,1669,1670],{"class":1387}," store ",[549,1672,1673],{"class":573},"=",[549,1675,1676],{"class":573}," new",[549,1678,1680],{"class":1679},"s2Zo4"," Map",[549,1682,1276],{"class":573},[549,1684,1685],{"class":562},"string",[549,1687,1281],{"class":573},[549,1689,1200],{"class":562},[549,1691,1692],{"class":573},">",[549,1694,1695],{"class":1387},"()",[549,1697,1215],{"class":573},[549,1699,1700],{"class":551,"line":617},[549,1701,587],{"emptyLinePlaceholder":586},[549,1703,1704],{"class":551,"line":623},[549,1705,1706],{"class":555},"\u002F\u002F 新增用户：自动生成 id 与 createdAt\n",[549,1708,1709,1711,1714,1717,1719,1722,1724,1726,1729,1731],{"class":551,"line":1066},[549,1710,1194],{"class":1193},[549,1712,1713],{"class":971}," function",[549,1715,1716],{"class":1679}," createUser",[549,1718,1357],{"class":573},[549,1720,1721],{"class":1363},"input",[549,1723,978],{"class":573},[549,1725,1267],{"class":562},[549,1727,1728],{"class":573},"):",[549,1730,1200],{"class":562},[549,1732,981],{"class":573},[549,1734,1735,1738,1741,1743,1745,1747],{"class":551,"line":1084},[549,1736,1737],{"class":971},"  const",[549,1739,1740],{"class":1387}," user",[549,1742,978],{"class":573},[549,1744,1200],{"class":562},[549,1746,1270],{"class":573},[549,1748,981],{"class":573},[549,1750,1751,1754,1756,1758,1760],{"class":551,"line":1090},[549,1752,1753],{"class":1049},"    id",[549,1755,978],{"class":573},[549,1757,1593],{"class":1679},[549,1759,1695],{"class":1049},[549,1761,1024],{"class":573},[549,1763,1764,1767,1769,1772,1775,1777],{"class":551,"line":1096},[549,1765,1766],{"class":1049},"    name",[549,1768,978],{"class":573},[549,1770,1771],{"class":1387}," input",[549,1773,1774],{"class":573},".",[549,1776,1406],{"class":1387},[549,1778,1024],{"class":573},[549,1780,1781,1784,1786,1788,1790,1793],{"class":551,"line":1102},[549,1782,1783],{"class":1049},"    email",[549,1785,978],{"class":573},[549,1787,1771],{"class":1387},[549,1789,1774],{"class":573},[549,1791,1792],{"class":1387},"email",[549,1794,1024],{"class":573},[549,1796,1797,1800,1802,1804,1806,1808],{"class":551,"line":1108},[549,1798,1799],{"class":1049},"    createdAt",[549,1801,978],{"class":573},[549,1803,1676],{"class":573},[549,1805,1247],{"class":1679},[549,1807,1695],{"class":1049},[549,1809,1024],{"class":573},[549,1811,1812],{"class":551,"line":1114},[549,1813,1814],{"class":573},"  };\n",[549,1816,1817,1820,1822,1825,1827,1830,1832,1834,1836,1838,1840],{"class":551,"line":1400},[549,1818,1819],{"class":1387},"  store",[549,1821,1774],{"class":573},[549,1823,1824],{"class":1679},"set",[549,1826,1357],{"class":1049},[549,1828,1829],{"class":1387},"user",[549,1831,1774],{"class":573},[549,1833,1286],{"class":1387},[549,1835,1281],{"class":573},[549,1837,1740],{"class":1387},[549,1839,1380],{"class":1049},[549,1841,1215],{"class":573},[549,1843,1844,1847,1849],{"class":551,"line":1420},[549,1845,1846],{"class":1193},"  return",[549,1848,1740],{"class":1387},[549,1850,1215],{"class":573},[549,1852,1853],{"class":551,"line":1425},[549,1854,1117],{"class":573},[549,1856,1857],{"class":551,"line":1430},[549,1858,587],{"emptyLinePlaceholder":586},[549,1860,1861],{"class":551,"line":1435},[549,1862,1863],{"class":555},"\u002F\u002F 查询用户：找不到时抛 NotFoundError\n",[549,1865,1866,1868,1870,1873,1875,1877,1879,1881,1883,1885],{"class":551,"line":1451},[549,1867,1194],{"class":1193},[549,1869,1713],{"class":971},[549,1871,1872],{"class":1679}," getUser",[549,1874,1357],{"class":573},[549,1876,1286],{"class":1363},[549,1878,978],{"class":573},[549,1880,1212],{"class":562},[549,1882,1728],{"class":573},[549,1884,1200],{"class":562},[549,1886,981],{"class":573},[549,1888,1889,1891,1893,1895,1898,1900,1903,1905,1907,1909],{"class":551,"line":1483},[549,1890,1737],{"class":971},[549,1892,1740],{"class":1387},[549,1894,1270],{"class":573},[549,1896,1897],{"class":1387}," store",[549,1899,1774],{"class":573},[549,1901,1902],{"class":1679},"get",[549,1904,1357],{"class":1049},[549,1906,1286],{"class":1387},[549,1908,1380],{"class":1049},[549,1910,1215],{"class":573},[549,1912,1913,1916,1919,1922,1924,1927,1930,1932,1934,1936,1938,1940,1942,1944,1946,1948],{"class":551,"line":1514},[549,1914,1915],{"class":1193},"  if",[549,1917,1918],{"class":1049}," (",[549,1920,1921],{"class":573},"!",[549,1923,1829],{"class":1387},[549,1925,1926],{"class":1049},") ",[549,1928,1929],{"class":1193},"throw",[549,1931,1676],{"class":573},[549,1933,1442],{"class":1679},[549,1935,1357],{"class":1049},[549,1937,975],{"class":573},[549,1939,519],{"class":566},[549,1941,975],{"class":573},[549,1943,1281],{"class":573},[549,1945,1472],{"class":1387},[549,1947,1380],{"class":1049},[549,1949,1215],{"class":573},[549,1951,1952,1954,1956],{"class":551,"line":1532},[549,1953,1846],{"class":1193},[549,1955,1740],{"class":1387},[549,1957,1215],{"class":573},[549,1959,1960],{"class":551,"line":1537},[549,1961,1117],{"class":573},[549,1963,1965],{"class":551,"line":1964},26,[549,1966,587],{"emptyLinePlaceholder":586},[549,1968,1970],{"class":551,"line":1969},27,[549,1971,1972],{"class":555},"\u002F\u002F 更新用户：仅修改传入字段\n",[549,1974,1976,1978,1980,1983,1985,1987,1989,1991,1993,1995,1997,1999,2001,2003],{"class":551,"line":1975},28,[549,1977,1194],{"class":1193},[549,1979,1713],{"class":971},[549,1981,1982],{"class":1679}," updateUser",[549,1984,1357],{"class":573},[549,1986,1286],{"class":1363},[549,1988,978],{"class":573},[549,1990,1212],{"class":562},[549,1992,1281],{"class":573},[549,1994,1771],{"class":1363},[549,1996,978],{"class":573},[549,1998,1310],{"class":562},[549,2000,1728],{"class":573},[549,2002,1200],{"class":562},[549,2004,981],{"class":573},[549,2006,2008,2010,2012,2014,2016,2018,2020,2022],{"class":551,"line":2007},29,[549,2009,1737],{"class":971},[549,2011,1740],{"class":1387},[549,2013,1270],{"class":573},[549,2015,1872],{"class":1679},[549,2017,1357],{"class":1049},[549,2019,1286],{"class":1387},[549,2021,1380],{"class":1049},[549,2023,1215],{"class":573},[549,2025,2027,2029,2032,2034,2036,2039,2041,2043,2045,2047],{"class":551,"line":2026},30,[549,2028,1737],{"class":971},[549,2030,2031],{"class":1387}," updated",[549,2033,1270],{"class":573},[549,2035,1590],{"class":573},[549,2037,2038],{"class":573}," ...",[549,2040,1829],{"class":1387},[549,2042,1281],{"class":573},[549,2044,2038],{"class":573},[549,2046,1721],{"class":1387},[549,2048,2049],{"class":573}," };\n",[549,2051,2053,2055,2057,2059,2061,2063,2065,2067,2069],{"class":551,"line":2052},31,[549,2054,1819],{"class":1387},[549,2056,1774],{"class":573},[549,2058,1824],{"class":1679},[549,2060,1357],{"class":1049},[549,2062,1286],{"class":1387},[549,2064,1281],{"class":573},[549,2066,2031],{"class":1387},[549,2068,1380],{"class":1049},[549,2070,1215],{"class":573},[549,2072,2074,2076,2078],{"class":551,"line":2073},32,[549,2075,1846],{"class":1193},[549,2077,2031],{"class":1387},[549,2079,1215],{"class":573},[549,2081,2083],{"class":551,"line":2082},33,[549,2084,1117],{"class":573},[549,2086,2088],{"class":551,"line":2087},34,[549,2089,587],{"emptyLinePlaceholder":586},[549,2091,2093],{"class":551,"line":2092},35,[549,2094,2095],{"class":555},"\u002F\u002F 删除用户：找不到时抛 NotFoundError\n",[549,2097,2099,2101,2103,2106,2108,2110,2112,2114,2116,2119],{"class":551,"line":2098},36,[549,2100,1194],{"class":1193},[549,2102,1713],{"class":971},[549,2104,2105],{"class":1679}," deleteUser",[549,2107,1357],{"class":573},[549,2109,1286],{"class":1363},[549,2111,978],{"class":573},[549,2113,1212],{"class":562},[549,2115,1728],{"class":573},[549,2117,2118],{"class":562}," void",[549,2120,981],{"class":573},[549,2122,2124,2126,2128,2130,2133,2135,2138,2140,2142,2145,2147,2149,2151,2153,2155,2157,2159,2161,2163,2165],{"class":551,"line":2123},37,[549,2125,1915],{"class":1193},[549,2127,1918],{"class":1049},[549,2129,1921],{"class":573},[549,2131,2132],{"class":1387},"store",[549,2134,1774],{"class":573},[549,2136,2137],{"class":1679},"has",[549,2139,1357],{"class":1049},[549,2141,1286],{"class":1387},[549,2143,2144],{"class":1049},")) ",[549,2146,1929],{"class":1193},[549,2148,1676],{"class":573},[549,2150,1442],{"class":1679},[549,2152,1357],{"class":1049},[549,2154,975],{"class":573},[549,2156,519],{"class":566},[549,2158,975],{"class":573},[549,2160,1281],{"class":573},[549,2162,1472],{"class":1387},[549,2164,1380],{"class":1049},[549,2166,1215],{"class":573},[549,2168,2170,2172,2174,2177,2179,2181,2183],{"class":551,"line":2169},38,[549,2171,1819],{"class":1387},[549,2173,1774],{"class":573},[549,2175,2176],{"class":1679},"delete",[549,2178,1357],{"class":1049},[549,2180,1286],{"class":1387},[549,2182,1380],{"class":1049},[549,2184,1215],{"class":573},[549,2186,2188],{"class":551,"line":2187},39,[549,2189,1117],{"class":573},[195,2191,2192,2194],{},[199,2193,1544],{},"：内存存储是否合适？UUID 生成是否安全？错误处理是否一致？",[195,2196,2197,738,2199],{},[199,2198,1550],{},[517,2200,2201],{},"feat(user): 实现内存版 CRUD repository",[251,2203,2205],{"id":2204},"_43-第-3-轮边界条件与错误处理","4.3 第 3 轮：边界条件与错误处理",[195,2207,2208],{},[199,2209,1165],{},[540,2211,2214],{"className":2212,"code":2213,"language":663},[661],"为 createUser 与 updateUser 增加输入校验：\n- name：非空、长度 1-100\n- email：必须包含 @ 与 .，长度 5-254\n\n校验失败抛 ValidationError，包含 field 与人类可读 message。\n仅修改 src\u002Fuser\u002Frepository.ts，不要新建文件。\n",[517,2215,2213],{"__ignoreMap":545},[195,2217,2218],{},[199,2219,2220],{},"Claude 产出（局部）：",[540,2222,2224],{"className":1179,"code":2223,"language":1181,"meta":545,"style":545},"function validateName(name: string) {\n  if (!name || name.length \u003C 1 || name.length > 100) {\n    throw new ValidationError(\"name\", \"name 长度必须在 1-100 之间\");\n  }\n}\n\nfunction validateEmail(email: string) {\n  if (!email || email.length \u003C 5 || email.length > 254) {\n    throw new ValidationError(\"email\", \"email 长度必须在 5-254 之间\");\n  }\n  if (!email.includes(\"@\") || !email.includes(\".\")) {\n    throw new ValidationError(\"email\", \"email 格式不合法\");\n  }\n}\n\nexport function createUser(input: CreateUserInput): User {\n  validateName(input.name);\n  validateEmail(input.email);\n  \u002F\u002F ...原有逻辑\n}\n",[517,2225,2226,2246,2291,2321,2325,2329,2333,2352,2393,2422,2426,2476,2505,2509,2513,2517,2539,2556,2573,2578],{"__ignoreMap":545},[549,2227,2228,2231,2234,2236,2238,2240,2242,2244],{"class":551,"line":552},[549,2229,2230],{"class":971},"function",[549,2232,2233],{"class":1679}," validateName",[549,2235,1357],{"class":573},[549,2237,1406],{"class":1363},[549,2239,978],{"class":573},[549,2241,1212],{"class":562},[549,2243,1380],{"class":573},[549,2245,981],{"class":573},[549,2247,2248,2250,2252,2254,2256,2259,2262,2264,2267,2270,2273,2275,2277,2279,2281,2284,2287,2289],{"class":551,"line":559},[549,2249,1915],{"class":1193},[549,2251,1918],{"class":1049},[549,2253,1921],{"class":573},[549,2255,1406],{"class":1387},[549,2257,2258],{"class":573}," ||",[549,2260,2261],{"class":1387}," name",[549,2263,1774],{"class":573},[549,2265,2266],{"class":1387},"length",[549,2268,2269],{"class":573}," \u003C",[549,2271,2272],{"class":1009}," 1",[549,2274,2258],{"class":573},[549,2276,2261],{"class":1387},[549,2278,1774],{"class":573},[549,2280,2266],{"class":1387},[549,2282,2283],{"class":573}," >",[549,2285,2286],{"class":1009}," 100",[549,2288,1926],{"class":1049},[549,2290,963],{"class":573},[549,2292,2293,2296,2298,2300,2302,2304,2306,2308,2310,2312,2315,2317,2319],{"class":551,"line":583},[549,2294,2295],{"class":1193},"    throw",[549,2297,1676],{"class":573},[549,2299,1341],{"class":1679},[549,2301,1357],{"class":1049},[549,2303,975],{"class":573},[549,2305,1406],{"class":566},[549,2307,975],{"class":573},[549,2309,1281],{"class":573},[549,2311,574],{"class":573},[549,2313,2314],{"class":566},"name 长度必须在 1-100 之间",[549,2316,975],{"class":573},[549,2318,1380],{"class":1049},[549,2320,1215],{"class":573},[549,2322,2323],{"class":551,"line":590},[549,2324,1111],{"class":573},[549,2326,2327],{"class":551,"line":596},[549,2328,1117],{"class":573},[549,2330,2331],{"class":551,"line":612},[549,2332,587],{"emptyLinePlaceholder":586},[549,2334,2335,2337,2340,2342,2344,2346,2348,2350],{"class":551,"line":617},[549,2336,2230],{"class":971},[549,2338,2339],{"class":1679}," validateEmail",[549,2341,1357],{"class":573},[549,2343,1792],{"class":1363},[549,2345,978],{"class":573},[549,2347,1212],{"class":562},[549,2349,1380],{"class":573},[549,2351,981],{"class":573},[549,2353,2354,2356,2358,2360,2362,2364,2367,2369,2371,2373,2376,2378,2380,2382,2384,2386,2389,2391],{"class":551,"line":623},[549,2355,1915],{"class":1193},[549,2357,1918],{"class":1049},[549,2359,1921],{"class":573},[549,2361,1792],{"class":1387},[549,2363,2258],{"class":573},[549,2365,2366],{"class":1387}," email",[549,2368,1774],{"class":573},[549,2370,2266],{"class":1387},[549,2372,2269],{"class":573},[549,2374,2375],{"class":1009}," 5",[549,2377,2258],{"class":573},[549,2379,2366],{"class":1387},[549,2381,1774],{"class":573},[549,2383,2266],{"class":1387},[549,2385,2283],{"class":573},[549,2387,2388],{"class":1009}," 254",[549,2390,1926],{"class":1049},[549,2392,963],{"class":573},[549,2394,2395,2397,2399,2401,2403,2405,2407,2409,2411,2413,2416,2418,2420],{"class":551,"line":1066},[549,2396,2295],{"class":1193},[549,2398,1676],{"class":573},[549,2400,1341],{"class":1679},[549,2402,1357],{"class":1049},[549,2404,975],{"class":573},[549,2406,1792],{"class":566},[549,2408,975],{"class":573},[549,2410,1281],{"class":573},[549,2412,574],{"class":573},[549,2414,2415],{"class":566},"email 长度必须在 5-254 之间",[549,2417,975],{"class":573},[549,2419,1380],{"class":1049},[549,2421,1215],{"class":573},[549,2423,2424],{"class":551,"line":1084},[549,2425,1111],{"class":573},[549,2427,2428,2430,2432,2434,2436,2438,2441,2443,2445,2448,2450,2452,2455,2458,2460,2462,2464,2466,2468,2470,2472,2474],{"class":551,"line":1090},[549,2429,1915],{"class":1193},[549,2431,1918],{"class":1049},[549,2433,1921],{"class":573},[549,2435,1792],{"class":1387},[549,2437,1774],{"class":573},[549,2439,2440],{"class":1679},"includes",[549,2442,1357],{"class":1049},[549,2444,975],{"class":573},[549,2446,2447],{"class":566},"@",[549,2449,975],{"class":573},[549,2451,1926],{"class":1049},[549,2453,2454],{"class":573},"||",[549,2456,2457],{"class":573}," !",[549,2459,1792],{"class":1387},[549,2461,1774],{"class":573},[549,2463,2440],{"class":1679},[549,2465,1357],{"class":1049},[549,2467,975],{"class":573},[549,2469,1774],{"class":566},[549,2471,975],{"class":573},[549,2473,2144],{"class":1049},[549,2475,963],{"class":573},[549,2477,2478,2480,2482,2484,2486,2488,2490,2492,2494,2496,2499,2501,2503],{"class":551,"line":1096},[549,2479,2295],{"class":1193},[549,2481,1676],{"class":573},[549,2483,1341],{"class":1679},[549,2485,1357],{"class":1049},[549,2487,975],{"class":573},[549,2489,1792],{"class":566},[549,2491,975],{"class":573},[549,2493,1281],{"class":573},[549,2495,574],{"class":573},[549,2497,2498],{"class":566},"email 格式不合法",[549,2500,975],{"class":573},[549,2502,1380],{"class":1049},[549,2504,1215],{"class":573},[549,2506,2507],{"class":551,"line":1102},[549,2508,1111],{"class":573},[549,2510,2511],{"class":551,"line":1108},[549,2512,1117],{"class":573},[549,2514,2515],{"class":551,"line":1114},[549,2516,587],{"emptyLinePlaceholder":586},[549,2518,2519,2521,2523,2525,2527,2529,2531,2533,2535,2537],{"class":551,"line":1400},[549,2520,1194],{"class":1193},[549,2522,1713],{"class":971},[549,2524,1716],{"class":1679},[549,2526,1357],{"class":573},[549,2528,1721],{"class":1363},[549,2530,978],{"class":573},[549,2532,1267],{"class":562},[549,2534,1728],{"class":573},[549,2536,1200],{"class":562},[549,2538,981],{"class":573},[549,2540,2541,2544,2546,2548,2550,2552,2554],{"class":551,"line":1420},[549,2542,2543],{"class":1679},"  validateName",[549,2545,1357],{"class":1049},[549,2547,1721],{"class":1387},[549,2549,1774],{"class":573},[549,2551,1406],{"class":1387},[549,2553,1380],{"class":1049},[549,2555,1215],{"class":573},[549,2557,2558,2561,2563,2565,2567,2569,2571],{"class":551,"line":1425},[549,2559,2560],{"class":1679},"  validateEmail",[549,2562,1357],{"class":1049},[549,2564,1721],{"class":1387},[549,2566,1774],{"class":573},[549,2568,1792],{"class":1387},[549,2570,1380],{"class":1049},[549,2572,1215],{"class":573},[549,2574,2575],{"class":551,"line":1430},[549,2576,2577],{"class":555},"  \u002F\u002F ...原有逻辑\n",[549,2579,2580],{"class":551,"line":1435},[549,2581,1117],{"class":573},[195,2583,2584,2586],{},[199,2585,1544],{},"：校验函数命名是否清晰？错误信息是否便于用户理解？是否覆盖所有边界？",[195,2588,2589,738,2591],{},[199,2590,1550],{},[517,2592,2593],{},"feat(user): createUser\u002FupdateUser 增加输入校验",[251,2595,2597],{"id":2596},"_44-第-4-轮单元测试与-fixture","4.4 第 4 轮：单元测试与 Fixture",[195,2599,2600],{},[199,2601,1165],{},[540,2603,2606],{"className":2604,"code":2605,"language":663},[661],"为 src\u002Fuser\u002Frepository.ts 写 Vitest 测试，覆盖：\n- createUser 正常路径\n- createUser email 不合法\n- createUser name 过长\n- getUser 找不到\n- updateUser 部分字段\n- updateUser 不存在 id\n- deleteUser 正常路径\n- deleteUser 不存在 id\n\n文件路径：src\u002Fuser\u002F__tests__\u002Frepository.test.ts\n使用 beforeEach 清空 store。\n",[517,2607,2605],{"__ignoreMap":545},[195,2609,2610],{},[199,2611,2220],{},[540,2613,2615],{"className":1179,"code":2614,"language":1181,"meta":545,"style":545},"\u002F\u002F src\u002Fuser\u002F__tests__\u002Frepository.test.ts\nimport { describe, it, expect, beforeEach } from \"vitest\";\nimport {\n  createUser,\n  getUser,\n  updateUser,\n  deleteUser,\n} from \"..\u002Frepository\";\nimport { ValidationError, NotFoundError } from \"..\u002Ferrors\";\n\ndescribe(\"user repository\", () => {\n  beforeEach(() => {\n    \u002F\u002F 当前实现是模块级 Map，测试需要重置\n    \u002F\u002F TODO: 第 5 轮重构为类，beforeEach 用 new Repository()\n  });\n\n  it(\"createUser 正常路径\", () => {\n    const user = createUser({ name: \"Alice\", email: \"a@b.co\" });\n    expect(user.id).toBeDefined();\n    expect(user.createdAt).toBeInstanceOf(Date);\n    expect(user.name).toBe(\"Alice\");\n  });\n\n  it(\"createUser email 不合法应抛 ValidationError\", () => {\n    expect(() =>\n      createUser({ name: \"Alice\", email: \"not-an-email\" })\n    ).toThrow(ValidationError);\n  });\n\n  \u002F\u002F ...其余 6 个用例\n});\n",[517,2616,2617,2622,2659,2665,2672,2679,2686,2693,2708,2733,2737,2761,2774,2779,2784,2793,2797,2819,2865,2889,2917,2948,2956,2960,2981,2992,3029,3047,3055,3059,3064],{"__ignoreMap":545},[549,2618,2619],{"class":551,"line":552},[549,2620,2621],{"class":555},"\u002F\u002F src\u002Fuser\u002F__tests__\u002Frepository.test.ts\n",[549,2623,2624,2626,2628,2631,2633,2636,2638,2641,2643,2646,2648,2650,2652,2655,2657],{"class":551,"line":559},[549,2625,1587],{"class":1193},[549,2627,1590],{"class":573},[549,2629,2630],{"class":1387}," describe",[549,2632,1281],{"class":573},[549,2634,2635],{"class":1387}," it",[549,2637,1281],{"class":573},[549,2639,2640],{"class":1387}," expect",[549,2642,1281],{"class":573},[549,2644,2645],{"class":1387}," beforeEach",[549,2647,1596],{"class":573},[549,2649,1599],{"class":1193},[549,2651,574],{"class":573},[549,2653,2654],{"class":566},"vitest",[549,2656,975],{"class":573},[549,2658,1215],{"class":573},[549,2660,2661,2663],{"class":551,"line":583},[549,2662,1587],{"class":1193},[549,2664,981],{"class":573},[549,2666,2667,2670],{"class":551,"line":590},[549,2668,2669],{"class":1387},"  createUser",[549,2671,1024],{"class":573},[549,2673,2674,2677],{"class":551,"line":596},[549,2675,2676],{"class":1387},"  getUser",[549,2678,1024],{"class":573},[549,2680,2681,2684],{"class":551,"line":612},[549,2682,2683],{"class":1387},"  updateUser",[549,2685,1024],{"class":573},[549,2687,2688,2691],{"class":551,"line":617},[549,2689,2690],{"class":1387},"  deleteUser",[549,2692,1024],{"class":573},[549,2694,2695,2697,2699,2701,2704,2706],{"class":551,"line":623},[549,2696,1496],{"class":573},[549,2698,1599],{"class":1193},[549,2700,574],{"class":573},[549,2702,2703],{"class":566},"..\u002Frepository",[549,2705,975],{"class":573},[549,2707,1215],{"class":573},[549,2709,2710,2712,2714,2716,2718,2720,2722,2724,2726,2729,2731],{"class":551,"line":1066},[549,2711,1587],{"class":1193},[549,2713,1590],{"class":573},[549,2715,1341],{"class":1387},[549,2717,1281],{"class":573},[549,2719,1442],{"class":1387},[549,2721,1596],{"class":573},[549,2723,1599],{"class":1193},[549,2725,574],{"class":573},[549,2727,2728],{"class":566},"..\u002Ferrors",[549,2730,975],{"class":573},[549,2732,1215],{"class":573},[549,2734,2735],{"class":551,"line":1084},[549,2736,587],{"emptyLinePlaceholder":586},[549,2738,2739,2742,2744,2746,2749,2751,2753,2756,2759],{"class":551,"line":1090},[549,2740,2741],{"class":1679},"describe",[549,2743,1357],{"class":1387},[549,2745,975],{"class":573},[549,2747,2748],{"class":566},"user repository",[549,2750,975],{"class":573},[549,2752,1281],{"class":573},[549,2754,2755],{"class":573}," ()",[549,2757,2758],{"class":971}," =>",[549,2760,981],{"class":573},[549,2762,2763,2766,2768,2770,2772],{"class":551,"line":1096},[549,2764,2765],{"class":1679},"  beforeEach",[549,2767,1357],{"class":1049},[549,2769,1695],{"class":573},[549,2771,2758],{"class":971},[549,2773,981],{"class":573},[549,2775,2776],{"class":551,"line":1102},[549,2777,2778],{"class":555},"    \u002F\u002F 当前实现是模块级 Map，测试需要重置\n",[549,2780,2781],{"class":551,"line":1108},[549,2782,2783],{"class":555},"    \u002F\u002F TODO: 第 5 轮重构为类，beforeEach 用 new Repository()\n",[549,2785,2786,2789,2791],{"class":551,"line":1114},[549,2787,2788],{"class":573},"  }",[549,2790,1380],{"class":1049},[549,2792,1215],{"class":573},[549,2794,2795],{"class":551,"line":1400},[549,2796,587],{"emptyLinePlaceholder":586},[549,2798,2799,2802,2804,2806,2809,2811,2813,2815,2817],{"class":551,"line":1420},[549,2800,2801],{"class":1679},"  it",[549,2803,1357],{"class":1049},[549,2805,975],{"class":573},[549,2807,2808],{"class":566},"createUser 正常路径",[549,2810,975],{"class":573},[549,2812,1281],{"class":573},[549,2814,2755],{"class":573},[549,2816,2758],{"class":971},[549,2818,981],{"class":573},[549,2820,2821,2824,2826,2828,2830,2832,2835,2837,2839,2841,2844,2846,2848,2850,2852,2854,2857,2859,2861,2863],{"class":551,"line":1425},[549,2822,2823],{"class":971},"    const",[549,2825,1740],{"class":1387},[549,2827,1270],{"class":573},[549,2829,1716],{"class":1679},[549,2831,1357],{"class":1049},[549,2833,2834],{"class":573},"{",[549,2836,2261],{"class":1049},[549,2838,978],{"class":573},[549,2840,574],{"class":573},[549,2842,2843],{"class":566},"Alice",[549,2845,975],{"class":573},[549,2847,1281],{"class":573},[549,2849,2366],{"class":1049},[549,2851,978],{"class":573},[549,2853,574],{"class":573},[549,2855,2856],{"class":566},"a@b.co",[549,2858,975],{"class":573},[549,2860,1596],{"class":573},[549,2862,1380],{"class":1049},[549,2864,1215],{"class":573},[549,2866,2867,2870,2872,2874,2876,2878,2880,2882,2885,2887],{"class":551,"line":1430},[549,2868,2869],{"class":1679},"    expect",[549,2871,1357],{"class":1049},[549,2873,1829],{"class":1387},[549,2875,1774],{"class":573},[549,2877,1286],{"class":1387},[549,2879,1380],{"class":1049},[549,2881,1774],{"class":573},[549,2883,2884],{"class":1679},"toBeDefined",[549,2886,1695],{"class":1049},[549,2888,1215],{"class":573},[549,2890,2891,2893,2895,2897,2899,2901,2903,2905,2908,2910,2913,2915],{"class":551,"line":1435},[549,2892,2869],{"class":1679},[549,2894,1357],{"class":1049},[549,2896,1829],{"class":1387},[549,2898,1774],{"class":573},[549,2900,1296],{"class":1387},[549,2902,1380],{"class":1049},[549,2904,1774],{"class":573},[549,2906,2907],{"class":1679},"toBeInstanceOf",[549,2909,1357],{"class":1049},[549,2911,2912],{"class":1387},"Date",[549,2914,1380],{"class":1049},[549,2916,1215],{"class":573},[549,2918,2919,2921,2923,2925,2927,2929,2931,2933,2936,2938,2940,2942,2944,2946],{"class":551,"line":1451},[549,2920,2869],{"class":1679},[549,2922,1357],{"class":1049},[549,2924,1829],{"class":1387},[549,2926,1774],{"class":573},[549,2928,1406],{"class":1387},[549,2930,1380],{"class":1049},[549,2932,1774],{"class":573},[549,2934,2935],{"class":1679},"toBe",[549,2937,1357],{"class":1049},[549,2939,975],{"class":573},[549,2941,2843],{"class":566},[549,2943,975],{"class":573},[549,2945,1380],{"class":1049},[549,2947,1215],{"class":573},[549,2949,2950,2952,2954],{"class":551,"line":1483},[549,2951,2788],{"class":573},[549,2953,1380],{"class":1049},[549,2955,1215],{"class":573},[549,2957,2958],{"class":551,"line":1514},[549,2959,587],{"emptyLinePlaceholder":586},[549,2961,2962,2964,2966,2968,2971,2973,2975,2977,2979],{"class":551,"line":1532},[549,2963,2801],{"class":1679},[549,2965,1357],{"class":1049},[549,2967,975],{"class":573},[549,2969,2970],{"class":566},"createUser email 不合法应抛 ValidationError",[549,2972,975],{"class":573},[549,2974,1281],{"class":573},[549,2976,2755],{"class":573},[549,2978,2758],{"class":971},[549,2980,981],{"class":573},[549,2982,2983,2985,2987,2989],{"class":551,"line":1537},[549,2984,2869],{"class":1679},[549,2986,1357],{"class":1049},[549,2988,1695],{"class":573},[549,2990,2991],{"class":971}," =>\n",[549,2993,2994,2997,2999,3001,3003,3005,3007,3009,3011,3013,3015,3017,3019,3022,3024,3026],{"class":551,"line":1964},[549,2995,2996],{"class":1679},"      createUser",[549,2998,1357],{"class":1049},[549,3000,2834],{"class":573},[549,3002,2261],{"class":1049},[549,3004,978],{"class":573},[549,3006,574],{"class":573},[549,3008,2843],{"class":566},[549,3010,975],{"class":573},[549,3012,1281],{"class":573},[549,3014,2366],{"class":1049},[549,3016,978],{"class":573},[549,3018,574],{"class":573},[549,3020,3021],{"class":566},"not-an-email",[549,3023,975],{"class":573},[549,3025,1596],{"class":573},[549,3027,3028],{"class":1049},")\n",[549,3030,3031,3034,3036,3039,3041,3043,3045],{"class":551,"line":1969},[549,3032,3033],{"class":1049},"    )",[549,3035,1774],{"class":573},[549,3037,3038],{"class":1679},"toThrow",[549,3040,1357],{"class":1049},[549,3042,1413],{"class":1387},[549,3044,1380],{"class":1049},[549,3046,1215],{"class":573},[549,3048,3049,3051,3053],{"class":551,"line":1975},[549,3050,2788],{"class":573},[549,3052,1380],{"class":1049},[549,3054,1215],{"class":573},[549,3056,3057],{"class":551,"line":2007},[549,3058,587],{"emptyLinePlaceholder":586},[549,3060,3061],{"class":551,"line":2026},[549,3062,3063],{"class":555},"  \u002F\u002F ...其余 6 个用例\n",[549,3065,3066,3068,3070],{"class":551,"line":2052},[549,3067,1496],{"class":573},[549,3069,1380],{"class":1387},[549,3071,1215],{"class":573},[195,3073,3074,3076],{},[199,3075,1544],{},"：测试用例是否覆盖关键边界？是否对实现细节耦合过深？TODO 标记是否合理？",[195,3078,3079,738,3081],{},[199,3080,1550],{},[517,3082,3083],{},"test(user): 增加 repository 单元测试，覆盖率 95%",[251,3085,3087],{"id":3086},"_45-第-5-轮业务规则与权限校验","4.5 第 5 轮：业务规则与权限校验",[195,3089,3090],{},[199,3091,1165],{},[540,3093,3096],{"className":3094,"code":3095,"language":663},[661],"增加业务规则：\n- 同一 email 不能注册两次（重复时抛 DuplicateError）\n- 仅管理员能删除用户（接口签名加 actorRole 参数）\n\n修改 src\u002Fuser\u002Frepository.ts，更新 src\u002Fuser\u002Ferrors.ts，更新对应测试。\n不要修改路径与文件结构。\n",[517,3097,3095],{"__ignoreMap":545},[195,3099,3100,3102],{},[199,3101,1544],{},"：DuplicateError 是新增还是复用？actorRole 是否影响其他调用方？测试是否相应更新？",[195,3104,3105,738,3107],{},[199,3106,1550],{},[517,3108,3109],{},"feat(user): 增加唯一性校验与管理员权限",[251,3111,3113],{"id":3112},"_46-第-6-轮性能优化与日志埋点","4.6 第 6 轮：性能优化与日志埋点",[195,3115,3116],{},[199,3117,1165],{},[540,3119,3122],{"className":3120,"code":3121,"language":663},[661],"最后一轮：\n1. 把 store 从 Map\u003Cstring, User> 升级为 Map\u003Cstring, User> + Map\u003Cemail, id> 的双索引，使重复 email 检查 O(1)\n2. 在 createUser、deleteUser 增加 console.info 埋点，包含 actorRole、userId、timestamp\n3. 不引入额外依赖\n\n更新对应测试，确保性能优化不破坏功能。\n",[517,3123,3121],{"__ignoreMap":545},[195,3125,3126,3128],{},[199,3127,1544],{},"：双索引的写操作是否原子？日志格式是否符合团队约定？测试是否覆盖索引一致性？",[195,3130,3131,738,3133],{},[199,3132,1550],{},[517,3134,3135],{},"perf(user): 增加 email 索引并补充审计日志",[243,3137],{},[195,3139,3140,3141,3143],{},"至此，6 轮迭代完成。每轮 ≤ 80 行新增代码、≤ 5 分钟审查、独立 commit。任何一轮的假设被推翻，都可以独立 ",[517,3142,642],{}," 而不影响其余部分。",[195,3145,3146,3149,3150,3153],{},[199,3147,3148],{},"关键观察","：6 轮总耗时约 60-90 分钟（含审查与提交），相当于工程师独立开发的 1\u002F4。但更重要的不是速度，",[199,3151,3152],{},"而是过程中产出的可追溯历史","——每个 commit 都对应一个明确的需求假设，未来 6 个月任何人来 review，都能通过 git log 重建当时的决策上下文。",[195,3155,3156,3159,3160,3163],{},[199,3157,3158],{},"对照传统模式的隐性差异","：传统手工开发中，工程师常常会\"边写边重构\"——写到一半发现命名不对就改、写到一半发现接口设计不好就重写——最后 commit 时 diff 已经混杂了实现、调整、重构、重命名等多种意图。Code Review 看到这种 diff 会很头疼，因为审查者无法区分\"哪些是这次的核心变更、哪些是顺手清理\"。多轮迭代的 6 个清晰 commit 把这些意图分离开来，审查者可以按\"假设维度\"逐一确认，",[199,3161,3162],{},"审查质量的上限因此被提高，而不仅仅是审查速度","。这是多轮迭代相对传统模式最被低估的优势。",[243,3165],{},[246,3167,3169],{"id":3168},"五迭代失控的反模式与挽救","五、迭代失控的反模式与挽救",[195,3171,3172],{},"多轮迭代不是\"开了挂\"，它有自己的失控模式。下面四种是本人在多个项目中反复观察到的反模式。",[251,3174,3176],{"id":3175},"_51-上下文溢出导致的健忘现象","5.1 上下文溢出导致的\"健忘\"现象",[195,3178,3179,3182],{},[199,3180,3181],{},"症状","：进行到第 8、9 轮迭代时，Claude 开始\"忘记\"早期的约定，例如重新使用第 2 轮已弃用的字段名、重新引入第 3 轮已删除的依赖。",[195,3184,3185,3188],{},[199,3186,3187],{},"根因","：长会话的上下文窗口被填满后，早期的对话内容被压缩或丢失。",[195,3190,3191,738],{},[199,3192,3193],{},"挽救方案",[434,3195,3196,3206,3212],{},[217,3197,3198,3201,3202,3205],{},[199,3199,3200],{},"每 5-7 轮迭代后，让 Claude 输出一份\"当前项目状态摘要\"","：当前文件清单、关键决策、未解决问题。把摘要写入 ",[517,3203,3204],{},".claude\u002Fstate.md","，作为下次会话的起点。",[217,3207,3208,3211],{},[199,3209,3210],{},"把高频信息固化到 CLAUDE.md","：例如\"User.email 必须唯一\"、\"所有错误必须继承自 BaseError\"。CLAUDE.md 在每次会话开始时都会注入。",[217,3213,3214,3217],{},[199,3215,3216],{},"使用 Plan 模式开新会话","：旧会话结束前让 Claude 输出 plan，新会话用 plan 作为起点。",[195,3219,3220,3223,3224,3227],{},[199,3221,3222],{},"实战补充","：本人在一个 12 人团队的中型项目（约 30 万行 TS 代码）中观察到，",[199,3225,3226],{},"单会话最优长度大约是 60-90 分钟","。再长则上下文压缩损耗显著上升、Claude 输出质量下降；再短则会话切换的\"暖机成本\"（重读 CLAUDE.md、重新理解当前 git 状态）反而高于收益。建议工程师把\"番茄钟（Pomodoro）\"工作节奏与会话节奏对齐——每 90 分钟主动结束会话，输出状态摘要，再开新会话。这条习惯三周后会形成肌肉记忆，团队的迭代节奏会自然稳定下来。",[195,3229,3230,3231,738],{},"另一条经验是",[199,3232,3233],{},"会话开始时主动注入\"当前焦点\"",[540,3235,3238],{"className":3236,"code":3237,"language":663},[661],"本次会话的焦点是用户管理模块的 6 轮迭代中的第 4 轮：补全单元测试。\n请阅读 .claude\u002Fstate.md 了解前 3 轮的产出与决策。\n不要触碰 src\u002Forder、src\u002Fbilling 模块。\n不要做超出第 4 轮范围的重构。\n",[517,3239,3237],{"__ignoreMap":545},[195,3241,3242],{},"这种\"焦点声明\"能让 Claude 在长会话中保持任务边界，显著降低\"漂移\"概率。",[251,3244,3246],{"id":3245},"_52-claude-幻觉式自信错误地宣称完成","5.2 Claude \"幻觉式自信\"——错误地宣称完成",[195,3248,3249,3251],{},[199,3250,3181],{},"：Claude 输出\"我已经完成了 X 与 Y\"，但实际只做了 X，或者做了 X 但代码并不能跑。",[195,3253,3254,3256],{},[199,3255,3187],{},"：LLM 的输出基于概率分布，\"宣称完成\"在训练数据中是高频模式，即使任务并未真正完成。",[195,3258,3259,738],{},[199,3260,3193],{},[434,3262,3263,3273,3290],{},[217,3264,3265,3268,3269,3272],{},[199,3266,3267],{},"用 Hooks 做\"完成态验证\"","——例如 PostToolUse 后强制跑 ",[517,3270,3271],{},"pnpm test","，失败则 Claude 不得宣称完成。",[217,3274,3275,3278,3279,3281,3282,3285,3286,3289],{},[199,3276,3277],{},"在 prompt 中明确\"完成判定\"","——\"完成的判定是 ",[517,3280,3271],{}," 全部通过、",[517,3283,3284],{},"pnpm lint"," 零警告、",[517,3287,3288],{},"pnpm typecheck"," 零错误\"。",[217,3291,3292,3295],{},[199,3293,3294],{},"不信任自然语言\"完成声明\"","——只信任 git log 与测试结果。",[195,3297,3298,3301],{},[199,3299,3300],{},"进阶补充","：本人在排查\"幻觉式完成\"的过程中总结出三个常见诱因——",[214,3303,3304,3313,3342],{},[217,3305,3306,3309,3310,3312],{},[199,3307,3308],{},"诱因 A：Claude 修改了文件但忘了运行测试","。它会基于\"代码看起来对\"做完成判断。对策：Hooks 强制跑 ",[517,3311,3271],{},"，并在 SessionEnd 阶段附加一份\"本次会话所有改动文件的最终测试结果\"摘要。",[217,3314,3315,3318,3319,3322,3323,3326,3327,3330,3331,3334,3335,3338,3339,3341],{},[199,3316,3317],{},"诱因 B：Claude 改了实现但没改对应的测试","。例如把函数签名从 ",[517,3320,3321],{},"(name, email)"," 改成 ",[517,3324,3325],{},"({name, email})","，但忘了同步更新 ",[517,3328,3329],{},"__tests__\u002F"," 里 6 个测试用例。对策：",[517,3332,3333],{},"PreToolUse"," 阶段检查 \"Edit 的文件名是否匹配 ",[517,3336,3337],{},"src\u002F","，若是则要求 Claude 报告对应 ",[517,3340,3329],{}," 文件是否需要同步修改\"。",[217,3343,3344,3347,3348,3351,3352,3355,3356,3359],{},[199,3345,3346],{},"诱因 C：Claude 跑了测试但只看了部分结果","。某些 CLI 输出过长会被截断，Claude 可能只读到了头部 20 行的\"PASS\"，未察觉尾部的 ",[517,3349,3350],{},"1 failed","。对策：用 ",[517,3353,3354],{},"pnpm test --reporter=summary"," 让结果更紧凑，或在 Hooks 里 grep ",[517,3357,3358],{},"failed|FAIL"," 反向验证。",[195,3361,3362],{},"这三条对策一起跑下来，本人项目里的\"幻觉式完成\"误报率从约 8% 降到了 \u003C 1%。",[251,3364,3366],{"id":3365},"_53-反复修改同一处代码的打地鼠陷阱","5.3 反复修改同一处代码的\"打地鼠\"陷阱",[195,3368,3369,3371],{},[199,3370,3181],{},"：第 X 轮改了文件 A，第 X+1 轮回到文件 A 又改一次，第 X+2 轮再改一次……同一处代码在 3 轮内被修改 3 次。",[195,3373,3374,3376],{},[199,3375,3187],{},"：每一轮都只解决了局部问题，没有触及更深的设计缺陷。",[195,3378,3379,738],{},[199,3380,3193],{},[434,3382,3383,3389,3395],{},[217,3384,3385,3388],{},[199,3386,3387],{},"触发\"打地鼠告警\"——同一文件 3 轮内被修改 ≥ 3 次时，强制暂停迭代","，让 Claude 输出\"这块代码反复修改的根因是什么？\"。",[217,3390,3391,3394],{},[199,3392,3393],{},"退出迭代循环，做一次小型重构","——把这块代码抽离、重写或拆分。",[217,3396,3397,3400],{},[199,3398,3399],{},"把根因记录到 CLAUDE.md","——下次类似情境直接规避。",[195,3402,3403,3406,3407,3410,3411,3414,3415,3418,3419,3422],{},[199,3404,3405],{},"实战案例","：本人遇到过一次典型的\"打地鼠\"——某个 ",[517,3408,3409],{},"formatPrice(amount, currency)"," 函数在两天内被 6 轮迭代反复修改：第 1 轮加日元支持、第 2 轮加千分位、第 3 轮加负数处理、第 4 轮修复 Intl 在低版本 Node 的 polyfill、第 5 轮修复 RTL 货币显示、第 6 轮修复测试用例。第 6 轮提交后突然意识到——",[199,3412,3413],{},"问题不是\"再加一个 case\"，而是函数职责本身错了","。这个函数应该被拆成 ",[517,3416,3417],{},"parsePrice"," + ",[517,3420,3421],{},"localizePrice"," 两个独立的纯函数，前者管数值规范化，后者管国际化展示。重构完成后，相关需求再没出现过修改。",[195,3424,3425,3426,3429],{},"经验教训：",[199,3427,3428],{},"\"打地鼠\"的真信号往往是\"职责错位\"","——一个函数（或一个文件）承担了过多上游决策，每个新需求都要在它身上落地。这是抽象边界的报警，而不是 Claude 写得不好。让 Claude 自己看出\"职责错位\"很难；让工程师在第 3 轮警告时主动停下来反思一下，往往一眼就能看出。",[251,3431,3433],{"id":3432},"_54-用-git-二分法-plan-模式回滚到稳定基线","5.4 用 git 二分法 + Plan 模式回滚到稳定基线",[195,3435,3436,3438],{},[199,3437,3181],{},"：连续 N 轮迭代后突然有用例失败，但搞不清是哪一轮引入的。",[195,3440,3441,738],{},[199,3442,3193],{},[540,3444,3446],{"className":542,"code":3445,"language":544,"meta":545,"style":545},"# 启动 git bisect\ngit bisect start\ngit bisect bad HEAD              # 当前 broken\ngit bisect good HEAD~10          # 10 轮前 good\n\n# git 会自动 checkout 到中间 commit\n# 在该 commit 上跑测试\npnpm test src\u002Fuser\n\n# 标记结果\ngit bisect good   # 或 bad\n\n# 重复直到锁定那个引入问题的 commit\n",[517,3447,3448,3453,3463,3478,3493,3497,3502,3507,3518,3522,3527,3538,3542],{"__ignoreMap":545},[549,3449,3450],{"class":551,"line":552},[549,3451,3452],{"class":555},"# 启动 git bisect\n",[549,3454,3455,3457,3460],{"class":551,"line":559},[549,3456,563],{"class":562},[549,3458,3459],{"class":566}," bisect",[549,3461,3462],{"class":566}," start\n",[549,3464,3465,3467,3469,3472,3475],{"class":551,"line":583},[549,3466,563],{"class":562},[549,3468,3459],{"class":566},[549,3470,3471],{"class":566}," bad",[549,3473,3474],{"class":566}," HEAD",[549,3476,3477],{"class":555},"              # 当前 broken\n",[549,3479,3480,3482,3484,3487,3490],{"class":551,"line":590},[549,3481,563],{"class":562},[549,3483,3459],{"class":566},[549,3485,3486],{"class":566}," good",[549,3488,3489],{"class":566}," HEAD~10",[549,3491,3492],{"class":555},"          # 10 轮前 good\n",[549,3494,3495],{"class":551,"line":596},[549,3496,587],{"emptyLinePlaceholder":586},[549,3498,3499],{"class":551,"line":612},[549,3500,3501],{"class":555},"# git 会自动 checkout 到中间 commit\n",[549,3503,3504],{"class":551,"line":617},[549,3505,3506],{"class":555},"# 在该 commit 上跑测试\n",[549,3508,3509,3512,3515],{"class":551,"line":623},[549,3510,3511],{"class":562},"pnpm",[549,3513,3514],{"class":566}," test",[549,3516,3517],{"class":566}," src\u002Fuser\n",[549,3519,3520],{"class":551,"line":1066},[549,3521,587],{"emptyLinePlaceholder":586},[549,3523,3524],{"class":551,"line":1084},[549,3525,3526],{"class":555},"# 标记结果\n",[549,3528,3529,3531,3533,3535],{"class":551,"line":1090},[549,3530,563],{"class":562},[549,3532,3459],{"class":566},[549,3534,3486],{"class":566},[549,3536,3537],{"class":555},"   # 或 bad\n",[549,3539,3540],{"class":551,"line":1096},[549,3541,587],{"emptyLinePlaceholder":586},[549,3543,3544],{"class":551,"line":1102},[549,3545,3546],{"class":555},"# 重复直到锁定那个引入问题的 commit\n",[195,3548,3549],{},"锁定 commit 后，让 Claude 在 Plan 模式下分析：",[540,3551,3554],{"className":3552,"code":3553,"language":663},[661],"git bisect 显示 commit abc123 引入了 src\u002Fuser\u002Frepository.test.ts 的失败用例。\n请：\n1. git show abc123 输出 diff\n2. 分析这个 commit 为什么引入失败\n3. 输出回滚 plan（保留其他正确变更）\n",[517,3555,3553],{"__ignoreMap":545},[195,3557,3558,3559,261],{},"这条流程把\"AI 自动迭代\"与\"传统调试工具\"结合，形成了",[199,3560,3561],{},"可追溯、可挽救的迭代体系",[243,3563],{},[246,3565,3567],{"id":3566},"六迭代节奏的工程化","六、迭代节奏的工程化",[195,3569,3570],{},"一次性把 6 轮迭代跑完不难，难的是\"团队 5 个人，每人每周做 20 轮迭代，6 个月后代码库依然清晰\"。这要求迭代本身被工程化。",[251,3572,3574],{"id":3573},"_61-把多轮提交聚合为有意义的功能分支","6.1 把多轮提交聚合为有意义的功能分支",[195,3576,3577,3578,3581],{},"直接在 main 上做 6 轮迭代不可行。推荐 ",[199,3579,3580],{},"Trunk-Based Development（基于主干开发）+ 短分支 + Squash Merge"," 的组合：",[540,3583,3585],{"className":542,"code":3584,"language":544,"meta":545,"style":545},"# 开始功能开发\ngit checkout -b feat\u002Fuser-management\n\n# 6 轮迭代各自独立 commit\n# (在分支上累积 6 个 commit)\n\n# 完成后做 squash merge\ngh pr create --title \"feat(user): 用户管理模块 v1\" \\\n  --body \"包含 6 轮迭代：类型定义、CRUD、校验、测试、业务规则、性能优化\"\n\n# 在 PR 中保留完整 commit 历史用于 review\n# 合并到 main 时 squash 为一个 commit，保留 PR 链接以追溯\n",[517,3586,3587,3592,3605,3609,3614,3619,3623,3628,3652,3664,3668,3673],{"__ignoreMap":545},[549,3588,3589],{"class":551,"line":552},[549,3590,3591],{"class":555},"# 开始功能开发\n",[549,3593,3594,3596,3599,3602],{"class":551,"line":559},[549,3595,563],{"class":562},[549,3597,3598],{"class":566}," checkout",[549,3600,3601],{"class":566}," -b",[549,3603,3604],{"class":566}," feat\u002Fuser-management\n",[549,3606,3607],{"class":551,"line":583},[549,3608,587],{"emptyLinePlaceholder":586},[549,3610,3611],{"class":551,"line":590},[549,3612,3613],{"class":555},"# 6 轮迭代各自独立 commit\n",[549,3615,3616],{"class":551,"line":596},[549,3617,3618],{"class":555},"# (在分支上累积 6 个 commit)\n",[549,3620,3621],{"class":551,"line":612},[549,3622,587],{"emptyLinePlaceholder":586},[549,3624,3625],{"class":551,"line":617},[549,3626,3627],{"class":555},"# 完成后做 squash merge\n",[549,3629,3630,3633,3636,3639,3642,3644,3647,3649],{"class":551,"line":623},[549,3631,3632],{"class":562},"gh",[549,3634,3635],{"class":566}," pr",[549,3637,3638],{"class":566}," create",[549,3640,3641],{"class":566}," --title",[549,3643,574],{"class":573},[549,3645,3646],{"class":566},"feat(user): 用户管理模块 v1",[549,3648,975],{"class":573},[549,3650,3651],{"class":1387}," \\\n",[549,3653,3654,3657,3659,3662],{"class":551,"line":1066},[549,3655,3656],{"class":566},"  --body",[549,3658,574],{"class":573},[549,3660,3661],{"class":566},"包含 6 轮迭代：类型定义、CRUD、校验、测试、业务规则、性能优化",[549,3663,580],{"class":573},[549,3665,3666],{"class":551,"line":1084},[549,3667,587],{"emptyLinePlaceholder":586},[549,3669,3670],{"class":551,"line":1090},[549,3671,3672],{"class":555},"# 在 PR 中保留完整 commit 历史用于 review\n",[549,3674,3675],{"class":551,"line":1096},[549,3676,3677],{"class":555},"# 合并到 main 时 squash 为一个 commit，保留 PR 链接以追溯\n",[195,3679,3680,738],{},[199,3681,3682],{},"好处",[214,3684,3685,3688,3691],{},[217,3686,3687],{},"main 分支的 commit 历史保持清晰（每个 commit = 一个功能）",[217,3689,3690],{},"PR 的 review 视角保持细粒度（6 个 commit 各自审查）",[217,3692,3693],{},"万一需要回滚，整个功能能干净撤回",[195,3695,3696,3699,3700,3703,3704,3707,3708,261],{},[199,3697,3698],{},"何时不该 Squash","：上面的策略对 90% 的功能开发有效，但有两类例外。第一类是",[199,3701,3702],{},"多人协作的长生命周期分支","——例如 epic 级别的特性分支会持续 3-4 周，期间多人提交，Squash 会丢失协作历史与责任归属，应保留 merge commit 风格。第二类是",[199,3705,3706],{},"包含独立可回滚 hotfix 的混合 PR","——例如功能开发中顺手修了一个生产 bug，这个 bug fix 应该被独立保留，方便未来 cherry-pick 到其他分支，因此整个 PR 不能 Squash。判断标准简单：",[199,3709,3710],{},"如果未来 12 个月内可能有人单独引用这个 commit，就不要 Squash",[251,3712,3714],{"id":3713},"_62-用-claudemd-沉淀本轮迭代学到的项目惯例","6.2 用 CLAUDE.md 沉淀本轮迭代学到的项目惯例",[195,3716,3717],{},"每完成一个功能（不是每轮迭代），花 5 分钟更新 CLAUDE.md。让 Claude 帮忙：",[540,3719,3722],{"className":3720,"code":3721,"language":663},[661],"我刚完成了用户管理模块的 6 轮迭代。请扫描这次的 commits 与 PR diff，\n提取以下惯例并附加到 CLAUDE.md：\n- 错误处理：所有自定义错误继承自 BaseError，含 field\u002Fresource 上下文\n- 测试结构：每个模块有独立的 __tests__ 目录\n- 命名约定：repository 函数用动词开头（create\u002Fget\u002Fupdate\u002Fdelete）\n- 业务规则：所有写操作的入参必须含 actorRole 参数\n\n只追加，不要重写已有内容。完成后让我审查 diff。\n",[517,3723,3721],{"__ignoreMap":545},[195,3725,3726],{},"CLAUDE.md 是团队\"沉淀的肌肉记忆\"。每次迭代后的 5 分钟更新，半年后会让 Claude 与新成员上手时间缩短一半以上。",[195,3728,3729,738],{},[199,3730,3731],{},"CLAUDE.md 维护的三条反直觉法则",[434,3733,3734,3740,3746],{},[217,3735,3736,3739],{},[199,3737,3738],{},"不要追求\"完整性\"","。CLAUDE.md 不是设计文档，而是\"高频偏差校正器\"。只把 Claude 反复犯错的点写进去，那些 Claude 一次就能做对的事情不需要写。一份 200 行的 CLAUDE.md 通常比 800 行的更有效——后者的信号被噪音稀释了。",[217,3741,3742,3745],{},[199,3743,3744],{},"每月做一次\"减法 review\"","。打开 CLAUDE.md，逐条问自己\"过去 30 天，如果删掉这条，Claude 会出错吗？\"。回答\"不会\"的条目立即删除。CLAUDE.md 的衰老速度比想象的快——团队代码库变了、依赖升级了、最佳实践改了——半年不维护的 CLAUDE.md 通常已经误导多过帮助。",[217,3747,3748,3751],{},[199,3749,3750],{},"用具体例子而非抽象规则","。\"错误处理要规范\" 是空话；\"所有自定义错误必须继承 BaseError，例如 src\u002Fuser\u002Ferrors.ts 中的 ValidationError\" 才是 Claude 真正能照着抄的指令。把 CLAUDE.md 里的每条规则后面挂一个具体的代码引用，效果会显著提升。",[195,3753,3754],{},"把这三条法则贯彻下去，CLAUDE.md 的健康度可以维持在\"每条规则都在活跃服役\"的状态，而不会沦为\"看起来很全但谁也不信\"的过期资料库。",[251,3756,3758],{"id":3757},"_63-skills-把高频迭代模板编码为可复用流程","6.3 Skills 把高频迭代模板编码为可复用流程",[195,3760,3761],{},"某些迭代模式会反复出现：例如\"为某个领域模块写 CRUD repository + 单元测试\"、\"为某个 API 加 Zod 校验 + 错误处理\"。把这些模式沉淀为 Skills：",[540,3763,3766],{"className":3764,"code":3765,"language":663},[661],".claude\u002Fskills\u002F\n├── crud-repository\u002F\n│   ├── SKILL.md\n│   └── template.ts\n├── add-zod-validation\u002F\n│   ├── SKILL.md\n│   └── examples\u002F\n└── refactor-to-async\u002F\n    └── SKILL.md\n",[517,3767,3765],{"__ignoreMap":545},[195,3769,3770,3771,3774],{},"每个 Skill 是一个可复用的\"迭代脚本\"。下次有人要做类似功能，一句 ",[517,3772,3773],{},"请使用 crud-repository skill 为 Order 实体生成 CRUD"," 就能复用整套迭代流程。",[195,3776,3777,3778,3781],{},"Skills 的真正威力是",[199,3779,3780],{},"让多轮迭代的最佳实践跨越个人，跨越项目，跨越时间","——团队在第 N 个项目积累的迭代节奏，能直接被第 N+1 个项目继承。",[195,3783,3784,738],{},[199,3785,3786],{},"Skill 设计的三个原则",[434,3788,3789,3795,3805],{},[217,3790,3791,3794],{},[199,3792,3793],{},"小而专","：每个 Skill 只解决一个清晰场景。\"crud-repository\" 不要塞进\"生成 OpenAPI 文档\"的能力——那是另一个 Skill。颗粒度太大的 Skill 会变成\"什么都不擅长\"的大杂烩。",[217,3796,3797,3800,3801,3804],{},[199,3798,3799],{},"示例先行","：在 Skill 目录的 ",[517,3802,3803],{},"examples\u002F"," 子目录里放 2-3 个真实项目的运行实例，并在 SKILL.md 里清晰链接。Claude 在执行 Skill 时会优先参考这些示例，输出质量远高于纯描述型 Skill。",[217,3806,3807,3810],{},[199,3808,3809],{},"可被人类阅读","：SKILL.md 既是 Claude 的指令，也是新成员的教材。控制在 100-300 行之间，结构清晰：「适用场景 → 输入要求 → 步骤 → 验收标准 → 已知边界」。当 Claude 与新成员的理解都依赖同一份文档时，团队的\"知识同源\"水准会显著提升。",[195,3812,3813,738],{},[199,3814,3815],{},"衡量 Skills 价值的指标",[214,3817,3818,3824,3830],{},[217,3819,3820,3823],{},[199,3821,3822],{},"使用次数","：每周被显式 \u002F 隐式调用多少次？低于 1 次 \u002F 周说明颗粒度或场景定位不准；",[217,3825,3826,3829],{},[199,3827,3828],{},"节省时间","：相比未使用 Skill 的人工实现，平均节省多少分钟？（建议设置 ≥ 15 分钟为合格阈值）；",[217,3831,3832,3835],{},[199,3833,3834],{},"错误率","：使用 Skill 后产生的代码进入 main 后的 bug 数。错误率显著高于团队均值的 Skill 应该立即下线维护。",[195,3837,3838,3839,3842],{},"把这三项指标接入 Hooks 自动统计（例如 SessionEnd 时 append 到 ",[517,3840,3841],{},".claude\u002Fskill-metrics.jsonl","），半年后就能形成一张\"团队 Skills 健康表\"，淘汰掉低 ROI 的 Skill，把时间花在高价值的固化上。",[243,3844],{},[246,3846,3847],{"id":3847},"总结",[195,3849,3850],{},"多轮迭代不是 Claude Code 的\"特性\"，而是它使用范式的核心。本节做了三件事：把\"为什么要多轮迭代\"用工程经济学说清楚、把\"怎么做多轮迭代\"拆成最小有效单元与人机分工、给出一份从用户管理模块开始的 6 轮迭代实战脚本。",[195,3852,3853,3854,3857],{},"完成 6.3 之后，团队应该已经具备了",[199,3855,3856],{},"用 Claude 在分钟级节奏完成核心功能","的能力。但功能跑起来只是开始——下一节 6.4 将讨论\"测试覆盖、代码审查与质量调优\"，把\"能跑\"升级为\"可上线\"。",[195,3859,3860,3863],{},[199,3861,3862],{},"最后一份团队层面的检查表","：当多轮迭代被广泛实践，团队需要在每个 sprint 结束前回答以下七个问题，作为\"迭代健康度\"的体检：",[434,3865,3866,3869,3872,3875,3878,3881,3884],{},[217,3867,3868],{},"本 sprint 平均每个功能用了几轮迭代完成？（健康范围：3-7 轮）",[217,3870,3871],{},"平均每轮迭代的 commit 体量？（健康范围：30-80 行）",[217,3873,3874],{},"有多少轮迭代触发了\"打地鼠告警\"（同文件 3 轮内 ≥ 3 次）？（健康范围：\u003C 5%）",[217,3876,3877],{},"有多少 PR 因为\"幻觉式完成\"被打回？（健康范围：\u003C 2%）",[217,3879,3880],{},"CLAUDE.md 本周新增了多少条共识？（健康范围：3-10 条）",[217,3882,3883],{},"Skills 库本周被显式调用了多少次？（健康范围：> 团队人数 × 5）",[217,3885,3886],{},"工程师在迭代过程中\"等 Claude 输出\"的累计时间是否 \u003C 30 分钟 \u002F 人 \u002F 天？（健康范围：是）",[195,3888,3889],{},"这七个问题是团队级别的\"迭代体温计\"。任何一个长期失温（连续两周不在健康范围）都说明工作流有进一步优化的空间——或许是 prompt 不够精准、或许是 CLAUDE.md 老化、或许是 Skills 的颗粒度不对。每个 sprint review 上花 10 分钟过一遍这张表，长期收益远高于花 10 分钟讨论某个具体技术细节。",[195,3891,3892],{},"留给读者的两个习题：",[434,3894,3895,3898],{},[217,3896,3897],{},"在你当前项目里挑一个未完成的功能，用本节的\"6 轮迭代脚本\"跑一遍。完成后记录每轮耗时，对比传统模式。",[217,3899,3900,3901,3904,3905,3908],{},"在 ",[517,3902,3903],{},".claude\u002Fskills\u002F"," 下添加一个 ",[517,3906,3907],{},"iteration-checkpoint"," skill，把\"每 5 轮输出项目状态摘要并写入 .claude\u002Fstate.md\"固化下来。",[195,3910,3911],{},"这两个习题会让多轮迭代从\"理论上知道\"变成\"肌肉上习惯\"。",[246,3913,3914],{"id":3914},"延伸阅读",[214,3916,3917,3927,3935,3943,3951,3959,3967,3975,3983,3991,3999],{},[217,3918,3919,3926],{},[3920,3921,3925],"a",{"href":3922,"rel":3923},"https:\u002F\u002Fcode.claude.com\u002Fdocs",[3924],"nofollow","Anthropic Claude Code 官方文档"," — Plan 模式、Hooks、Skills 全览",[217,3928,3929,3934],{},[3920,3930,3933],{"href":3931,"rel":3932},"https:\u002F\u002Fwww.anthropic.com\u002Fengineering\u002Fclaude-code-best-practices",[3924],"Boris Cherny: Claude Code Best Practices"," — Anthropic 官方实战心法",[217,3936,3937,3942],{},[3920,3938,3941],{"href":3939,"rel":3940},"https:\u002F\u002Fwww.thoughtworks.com\u002Finsights\u002Farticles\u002Fhow-implement-hypothesis-driven-development",[3924],"Hypothesis-Driven Development（ThoughtWorks）"," — 假设驱动开发原则",[217,3944,3945,3950],{},[3920,3946,3949],{"href":3947,"rel":3948},"https:\u002F\u002Fvitest.dev\u002F",[3924],"Vitest 官方文档"," — 现代 TypeScript 测试框架",[217,3952,3953,3958],{},[3920,3954,3957],{"href":3955,"rel":3956},"https:\u002F\u002Ftrunkbaseddevelopment.com\u002F",[3924],"Trunk-Based Development"," — 基于主干的迭代分支策略",[217,3960,3961,3966],{},[3920,3962,3965],{"href":3963,"rel":3964},"https:\u002F\u002Fwww.oreilly.com\u002Flibrary\u002Fview\u002Ftest-driven-development\u002F0321146530\u002F",[3924],"Test-Driven Development by Example（Kent Beck）"," — TDD 经典原著",[217,3968,3969,3974],{},[3920,3970,3973],{"href":3971,"rel":3972},"https:\u002F\u002Fpragprog.com\u002Ftitles\u002Ftpp20\u002Fthe-pragmatic-programmer-20th-anniversary-edition\u002F",[3924],"Pragmatic Programmer 20th Anniversary Edition"," — 软件工艺与迭代节奏",[217,3976,3977,3982],{},[3920,3978,3981],{"href":3979,"rel":3980},"https:\u002F\u002Fcontinuousdelivery.com\u002F",[3924],"Continuous Delivery（Jez Humble & David Farley）"," — 高频小步发布的工程基础",[217,3984,3985,3990],{},[3920,3986,3989],{"href":3987,"rel":3988},"https:\u002F\u002Fmartinfowler.com\u002Fbooks\u002Frefactoring.html",[3924],"Refactoring 2nd Edition（Martin Fowler）"," — 重构步骤、安全网与回滚策略",[217,3992,3993,3998],{},[3920,3994,3997],{"href":3995,"rel":3996},"https:\u002F\u002Fgit-scm.com\u002Fdocs\u002Fgit-bisect",[3924],"git bisect 官方文档"," — 二分法定位失败 commit",[217,4000,4001,4006],{},[3920,4002,4005],{"href":4003,"rel":4004},"https:\u002F\u002Fgithub.com\u002Fhesreallyhim\u002Fawesome-claude-code",[3924],"Awesome Claude Code"," — 社区维护的 Claude Code 资源集",[4008,4009,4010],"style",{},"html pre.shiki code .sHwdD, html code.shiki .sHwdD{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#546E7A;--shiki-default-font-style:italic;--shiki-dark:#676E95;--shiki-dark-font-style:italic}html pre.shiki code .sBMFI, html code.shiki .sBMFI{--shiki-light:#E2931D;--shiki-default:#FFCB6B;--shiki-dark:#FFCB6B}html pre.shiki code .sfazB, html code.shiki .sfazB{--shiki-light:#91B859;--shiki-default:#C3E88D;--shiki-dark:#C3E88D}html pre.shiki code .sMK4o, html code.shiki .sMK4o{--shiki-light:#39ADB5;--shiki-default:#89DDFF;--shiki-dark:#89DDFF}html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .spNyl, html code.shiki .spNyl{--shiki-light:#9C3EDA;--shiki-default:#C792EA;--shiki-dark:#C792EA}html pre.shiki code .sbssI, html code.shiki .sbssI{--shiki-light:#F76D47;--shiki-default:#F78C6C;--shiki-dark:#F78C6C}html pre.shiki code .swJcz, html code.shiki .swJcz{--shiki-light:#E53935;--shiki-default:#F07178;--shiki-dark:#F07178}html pre.shiki code .s7zQu, html code.shiki .s7zQu{--shiki-light:#39ADB5;--shiki-light-font-style:italic;--shiki-default:#89DDFF;--shiki-default-font-style:italic;--shiki-dark:#89DDFF;--shiki-dark-font-style:italic}html pre.shiki code .sHdIc, html code.shiki .sHdIc{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#EEFFFF;--shiki-default-font-style:italic;--shiki-dark:#BABED8;--shiki-dark-font-style:italic}html pre.shiki code .sTEyZ, html code.shiki .sTEyZ{--shiki-light:#90A4AE;--shiki-default:#EEFFFF;--shiki-dark:#BABED8}html pre.shiki code .s2Zo4, html code.shiki .s2Zo4{--shiki-light:#6182B8;--shiki-default:#82AAFF;--shiki-dark:#82AAFF}",{"title":545,"searchDepth":552,"depth":559,"links":4012},[4013,4018,4024,4030,4038,4044,4049,4050],{"id":248,"depth":559,"text":249,"children":4014},[4015,4016,4017],{"id":253,"depth":583,"text":254},{"id":362,"depth":583,"text":363},{"id":428,"depth":583,"text":429},{"id":476,"depth":559,"text":477,"children":4019},[4020,4021,4022,4023],{"id":480,"depth":583,"text":481},{"id":530,"depth":583,"text":531},{"id":646,"depth":583,"text":647},{"id":692,"depth":583,"text":693},{"id":726,"depth":559,"text":727,"children":4025},[4026,4027,4028,4029],{"id":730,"depth":583,"text":731},{"id":840,"depth":583,"text":841},{"id":886,"depth":583,"text":887},{"id":947,"depth":583,"text":948},{"id":1149,"depth":559,"text":1150,"children":4031},[4032,4033,4034,4035,4036,4037],{"id":1159,"depth":583,"text":1160},{"id":1556,"depth":583,"text":1557},{"id":2204,"depth":583,"text":2205},{"id":2596,"depth":583,"text":2597},{"id":3086,"depth":583,"text":3087},{"id":3112,"depth":583,"text":3113},{"id":3168,"depth":559,"text":3169,"children":4039},[4040,4041,4042,4043],{"id":3175,"depth":583,"text":3176},{"id":3245,"depth":583,"text":3246},{"id":3365,"depth":583,"text":3366},{"id":3432,"depth":583,"text":3433},{"id":3566,"depth":559,"text":3567,"children":4045},[4046,4047,4048],{"id":3573,"depth":583,"text":3574},{"id":3713,"depth":583,"text":3714},{"id":3757,"depth":583,"text":3758},{"id":3847,"depth":559,"text":3847},{"id":3914,"depth":559,"text":3914},"多轮迭代开发","md",null,{"date":4055},"2026-04-26",{"title":138,"description":4051},"vR2ZvV8YczFrQib0liJrW3gfRaAltDk1y7GEmDjS4RU",[4059,4060],{"title":134,"path":135,"stem":136,"description":545,"children":-1},{"title":142,"path":143,"stem":144,"description":4061,"children":-1},"经过 6.3 节的多轮迭代，我们已经把一个空的脚手架推向了功能可用的最小可行产品（Minimum Viable Product, MVP）。表面上看，主流程跑通了、UI 截图也拿得出手，但任何一位真正把代码送上过生产环境的工程师都知道：从「Demo 跑通」到「敢交付」之间，还隔着一整条质量保障链路——测试、审查、调优、门禁。这条链路的每个环节，过去由人主导、AI 辅助；从 Claude Code 普及之后，许多团队开始反过来：让 AI 主导执行，让人主导决策。",1777395310035]