[{"data":1,"prerenderedAt":8245},["ShallowReactive",2],{"header-counts":3,"playbook-list":6,"footer-counts":8244},{"tools":4,"reviews":5},65,7,[7,942,2050,2770,3495,4474,6019,6642,7152,7787],{"id":8,"title":9,"body":10,"category":922,"cover":923,"description":924,"extension":925,"meta":926,"navigation":117,"path":927,"published":928,"relatedTools":929,"seo":933,"stem":934,"tags":935,"updated":928,"__hash__":941},"playbook\u002Fplaybook\u002Fdeploy\u002Fai-auto-deploy.md","用 AI Agent 全自动部署：从 Git Push 到生产环境零手工",{"type":11,"value":12,"toc":911},"minimark",[13,17,30,33,44,48,55,369,373,376,468,472,573,577,693,696,792,795,798,859,862,907],[14,15,16],"h2",{"id":16},"适用场景",[18,19,20,24,27],"ul",{},[21,22,23],"li",{},"个人项目 \u002F 小团队，不想手动部署",[21,25,26],{},"想让 AI 做部署决策（是否回滚、是否发公告）",[21,28,29],{},"需要部署后自动验证的",[14,31,32],{"id":32},"架构",[34,35,40],"pre",{"className":36,"code":38,"language":39},[37],"language-text","git push main\n  → GitHub Actions 触发\n    → Step 1: 跑测试（pnpm test）\n    → Step 2: Claude Code 审查改动\n    → Step 3: 构建（pnpm build）\n    → Step 4: 部署到 Vercel\n    → Step 5: Claude Code 验证生产环境\n    → Step 6: 失败则自动回滚 + 通知\n","text",[41,42,38],"code",{"__ignoreMap":43},"",[14,45,47],{"id":46},"第一步基础-ci","第一步：基础 CI",[49,50,51,54],"p",{},[41,52,53],{},".github\u002Fworkflows\u002Fdeploy.yml","：",[34,56,60],{"className":57,"code":58,"language":59,"meta":43,"style":43},"language-yaml shiki shiki-themes github-light github-dark","name: Deploy\non:\n  push:\n    branches: [main]\n\njobs:\n  deploy:\n    runs-on: ubuntu-latest\n    permissions:\n      contents: write\n      deployments: write\n    steps:\n      - uses: actions\u002Fcheckout@v4\n      - uses: pnpm\u002Faction-setup@v2\n        with: { version: 9 }\n      - uses: actions\u002Fsetup-node@v4\n        with: { node-version: 22, cache: pnpm }\n\n      - name: Install\n        run: pnpm install --frozen-lockfile\n\n      - name: Test\n        run: pnpm test\n\n      - name: Build\n        run: pnpm build\n        env:\n          VERCEL_TOKEN: ${{ secrets.VERCEL_TOKEN }}\n","yaml",[41,61,62,79,89,97,112,119,127,134,145,153,164,174,182,196,208,228,240,268,273,285,296,301,313,323,328,340,350,358],{"__ignoreMap":43},[63,64,67,71,75],"span",{"class":65,"line":66},"line",1,[63,68,70],{"class":69},"s9eBZ","name",[63,72,74],{"class":73},"sVt8B",": ",[63,76,78],{"class":77},"sZZnC","Deploy\n",[63,80,82,86],{"class":65,"line":81},2,[63,83,85],{"class":84},"sj4cs","on",[63,87,88],{"class":73},":\n",[63,90,92,95],{"class":65,"line":91},3,[63,93,94],{"class":69},"  push",[63,96,88],{"class":73},[63,98,100,103,106,109],{"class":65,"line":99},4,[63,101,102],{"class":69},"    branches",[63,104,105],{"class":73},": [",[63,107,108],{"class":77},"main",[63,110,111],{"class":73},"]\n",[63,113,115],{"class":65,"line":114},5,[63,116,118],{"emptyLinePlaceholder":117},true,"\n",[63,120,122,125],{"class":65,"line":121},6,[63,123,124],{"class":69},"jobs",[63,126,88],{"class":73},[63,128,129,132],{"class":65,"line":5},[63,130,131],{"class":69},"  deploy",[63,133,88],{"class":73},[63,135,137,140,142],{"class":65,"line":136},8,[63,138,139],{"class":69},"    runs-on",[63,141,74],{"class":73},[63,143,144],{"class":77},"ubuntu-latest\n",[63,146,148,151],{"class":65,"line":147},9,[63,149,150],{"class":69},"    permissions",[63,152,88],{"class":73},[63,154,156,159,161],{"class":65,"line":155},10,[63,157,158],{"class":69},"      contents",[63,160,74],{"class":73},[63,162,163],{"class":77},"write\n",[63,165,167,170,172],{"class":65,"line":166},11,[63,168,169],{"class":69},"      deployments",[63,171,74],{"class":73},[63,173,163],{"class":77},[63,175,177,180],{"class":65,"line":176},12,[63,178,179],{"class":69},"    steps",[63,181,88],{"class":73},[63,183,185,188,191,193],{"class":65,"line":184},13,[63,186,187],{"class":73},"      - ",[63,189,190],{"class":69},"uses",[63,192,74],{"class":73},[63,194,195],{"class":77},"actions\u002Fcheckout@v4\n",[63,197,199,201,203,205],{"class":65,"line":198},14,[63,200,187],{"class":73},[63,202,190],{"class":69},[63,204,74],{"class":73},[63,206,207],{"class":77},"pnpm\u002Faction-setup@v2\n",[63,209,211,214,217,220,222,225],{"class":65,"line":210},15,[63,212,213],{"class":69},"        with",[63,215,216],{"class":73},": { ",[63,218,219],{"class":69},"version",[63,221,74],{"class":73},[63,223,224],{"class":84},"9",[63,226,227],{"class":73}," }\n",[63,229,231,233,235,237],{"class":65,"line":230},16,[63,232,187],{"class":73},[63,234,190],{"class":69},[63,236,74],{"class":73},[63,238,239],{"class":77},"actions\u002Fsetup-node@v4\n",[63,241,243,245,247,250,252,255,258,261,263,266],{"class":65,"line":242},17,[63,244,213],{"class":69},[63,246,216],{"class":73},[63,248,249],{"class":69},"node-version",[63,251,74],{"class":73},[63,253,254],{"class":84},"22",[63,256,257],{"class":73},", ",[63,259,260],{"class":69},"cache",[63,262,74],{"class":73},[63,264,265],{"class":77},"pnpm",[63,267,227],{"class":73},[63,269,271],{"class":65,"line":270},18,[63,272,118],{"emptyLinePlaceholder":117},[63,274,276,278,280,282],{"class":65,"line":275},19,[63,277,187],{"class":73},[63,279,70],{"class":69},[63,281,74],{"class":73},[63,283,284],{"class":77},"Install\n",[63,286,288,291,293],{"class":65,"line":287},20,[63,289,290],{"class":69},"        run",[63,292,74],{"class":73},[63,294,295],{"class":77},"pnpm install --frozen-lockfile\n",[63,297,299],{"class":65,"line":298},21,[63,300,118],{"emptyLinePlaceholder":117},[63,302,304,306,308,310],{"class":65,"line":303},22,[63,305,187],{"class":73},[63,307,70],{"class":69},[63,309,74],{"class":73},[63,311,312],{"class":77},"Test\n",[63,314,316,318,320],{"class":65,"line":315},23,[63,317,290],{"class":69},[63,319,74],{"class":73},[63,321,322],{"class":77},"pnpm test\n",[63,324,326],{"class":65,"line":325},24,[63,327,118],{"emptyLinePlaceholder":117},[63,329,331,333,335,337],{"class":65,"line":330},25,[63,332,187],{"class":73},[63,334,70],{"class":69},[63,336,74],{"class":73},[63,338,339],{"class":77},"Build\n",[63,341,343,345,347],{"class":65,"line":342},26,[63,344,290],{"class":69},[63,346,74],{"class":73},[63,348,349],{"class":77},"pnpm build\n",[63,351,353,356],{"class":65,"line":352},27,[63,354,355],{"class":69},"        env",[63,357,88],{"class":73},[63,359,361,364,366],{"class":65,"line":360},28,[63,362,363],{"class":69},"          VERCEL_TOKEN",[63,365,74],{"class":73},[63,367,368],{"class":77},"${{ secrets.VERCEL_TOKEN }}\n",[14,370,372],{"id":371},"第二步ai-审查改动","第二步：AI 审查改动",[49,374,375],{},"在 build 前加 Claude Code 审查：",[34,377,379],{"className":57,"code":378,"language":59,"meta":43,"style":43},"      - name: AI Review Changes\n        uses: anthropics\u002Fclaude-code-action@v1\n        with:\n          anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}\n          prompt: |\n            检查这次改动是否有部署风险：\n            1. 是否修改了环境变量配置\n            2. 是否修改了数据库 schema\n            3. 是否有 breaking change\n            4. 是否需要发迁移公告\n\n            如果有风险，输出 \"BLOCK_DEPLOY: \u003C原因>\"\n            如果没风险，输出 \"DEPLOY_OK\"\n",[41,380,381,392,402,408,418,429,434,439,444,449,454,458,463],{"__ignoreMap":43},[63,382,383,385,387,389],{"class":65,"line":66},[63,384,187],{"class":73},[63,386,70],{"class":69},[63,388,74],{"class":73},[63,390,391],{"class":77},"AI Review Changes\n",[63,393,394,397,399],{"class":65,"line":81},[63,395,396],{"class":69},"        uses",[63,398,74],{"class":73},[63,400,401],{"class":77},"anthropics\u002Fclaude-code-action@v1\n",[63,403,404,406],{"class":65,"line":91},[63,405,213],{"class":69},[63,407,88],{"class":73},[63,409,410,413,415],{"class":65,"line":99},[63,411,412],{"class":69},"          anthropic_api_key",[63,414,74],{"class":73},[63,416,417],{"class":77},"${{ secrets.ANTHROPIC_API_KEY }}\n",[63,419,420,423,425],{"class":65,"line":114},[63,421,422],{"class":69},"          prompt",[63,424,74],{"class":73},[63,426,428],{"class":427},"szBVR","|\n",[63,430,431],{"class":65,"line":121},[63,432,433],{"class":77},"            检查这次改动是否有部署风险：\n",[63,435,436],{"class":65,"line":5},[63,437,438],{"class":77},"            1. 是否修改了环境变量配置\n",[63,440,441],{"class":65,"line":136},[63,442,443],{"class":77},"            2. 是否修改了数据库 schema\n",[63,445,446],{"class":65,"line":147},[63,447,448],{"class":77},"            3. 是否有 breaking change\n",[63,450,451],{"class":65,"line":155},[63,452,453],{"class":77},"            4. 是否需要发迁移公告\n",[63,455,456],{"class":65,"line":166},[63,457,118],{"emptyLinePlaceholder":117},[63,459,460],{"class":65,"line":176},[63,461,462],{"class":77},"            如果有风险，输出 \"BLOCK_DEPLOY: \u003C原因>\"\n",[63,464,465],{"class":65,"line":184},[63,466,467],{"class":77},"            如果没风险，输出 \"DEPLOY_OK\"\n",[14,469,471],{"id":470},"第三步ai-部署","第三步：AI 部署",[34,473,475],{"className":57,"code":474,"language":59,"meta":43,"style":43},"      - name: Deploy with Claude\n        if: success()\n        uses: anthropics\u002Fclaude-code-action@v1\n        with:\n          anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}\n          prompt: |\n            执行部署：\n            1. 运行 vercel --prod --yes\n            2. 等待部署完成，获取 URL\n            3. curl 测试首页是否 200\n            4. curl 测试 \u002Fapi\u002Fhealth 是否返回 ok\n            5. 如果验证失败，运行 vercel rollback\n            6. 输出部署结果\n          allowed_tools: \"Bash\"\n",[41,476,477,488,498,506,512,520,528,533,538,543,548,553,558,563],{"__ignoreMap":43},[63,478,479,481,483,485],{"class":65,"line":66},[63,480,187],{"class":73},[63,482,70],{"class":69},[63,484,74],{"class":73},[63,486,487],{"class":77},"Deploy with Claude\n",[63,489,490,493,495],{"class":65,"line":81},[63,491,492],{"class":69},"        if",[63,494,74],{"class":73},[63,496,497],{"class":77},"success()\n",[63,499,500,502,504],{"class":65,"line":91},[63,501,396],{"class":69},[63,503,74],{"class":73},[63,505,401],{"class":77},[63,507,508,510],{"class":65,"line":99},[63,509,213],{"class":69},[63,511,88],{"class":73},[63,513,514,516,518],{"class":65,"line":114},[63,515,412],{"class":69},[63,517,74],{"class":73},[63,519,417],{"class":77},[63,521,522,524,526],{"class":65,"line":121},[63,523,422],{"class":69},[63,525,74],{"class":73},[63,527,428],{"class":427},[63,529,530],{"class":65,"line":5},[63,531,532],{"class":77},"            执行部署：\n",[63,534,535],{"class":65,"line":136},[63,536,537],{"class":77},"            1. 运行 vercel --prod --yes\n",[63,539,540],{"class":65,"line":147},[63,541,542],{"class":77},"            2. 等待部署完成，获取 URL\n",[63,544,545],{"class":65,"line":155},[63,546,547],{"class":77},"            3. curl 测试首页是否 200\n",[63,549,550],{"class":65,"line":166},[63,551,552],{"class":77},"            4. curl 测试 \u002Fapi\u002Fhealth 是否返回 ok\n",[63,554,555],{"class":65,"line":176},[63,556,557],{"class":77},"            5. 如果验证失败，运行 vercel rollback\n",[63,559,560],{"class":65,"line":184},[63,561,562],{"class":77},"            6. 输出部署结果\n",[63,564,565,568,570],{"class":65,"line":198},[63,566,567],{"class":69},"          allowed_tools",[63,569,74],{"class":73},[63,571,572],{"class":77},"\"Bash\"\n",[14,574,576],{"id":575},"第四步自动回滚","第四步：自动回滚",[34,578,580],{"className":57,"code":579,"language":59,"meta":43,"style":43},"      - name: Rollback on failure\n        if: failure()\n        run: |\n          # 获取上一个稳定部署\n          PREV=$(vercel ls --yes | head -5 | tail -1 | awk '{print $1}')\n          echo \"Rolling back to $PREV\"\n          vercel promote $PREV --yes\n\n      - name: Notify\n        if: always()\n        uses: slackapi\u002Fslack-github-action@v1\n        with:\n          slack-message: |\n            ${{ job.status == 'success' && '✅' || '❌' }} 部署 ${{ job.status }}\n            仓库: ${{ github.repository }}\n            提交: ${{ github.event.head_commit.message }}\n",[41,581,582,593,602,610,615,620,625,630,634,645,654,663,669,678,683,688],{"__ignoreMap":43},[63,583,584,586,588,590],{"class":65,"line":66},[63,585,187],{"class":73},[63,587,70],{"class":69},[63,589,74],{"class":73},[63,591,592],{"class":77},"Rollback on failure\n",[63,594,595,597,599],{"class":65,"line":81},[63,596,492],{"class":69},[63,598,74],{"class":73},[63,600,601],{"class":77},"failure()\n",[63,603,604,606,608],{"class":65,"line":91},[63,605,290],{"class":69},[63,607,74],{"class":73},[63,609,428],{"class":427},[63,611,612],{"class":65,"line":99},[63,613,614],{"class":77},"          # 获取上一个稳定部署\n",[63,616,617],{"class":65,"line":114},[63,618,619],{"class":77},"          PREV=$(vercel ls --yes | head -5 | tail -1 | awk '{print $1}')\n",[63,621,622],{"class":65,"line":121},[63,623,624],{"class":77},"          echo \"Rolling back to $PREV\"\n",[63,626,627],{"class":65,"line":5},[63,628,629],{"class":77},"          vercel promote $PREV --yes\n",[63,631,632],{"class":65,"line":136},[63,633,118],{"emptyLinePlaceholder":117},[63,635,636,638,640,642],{"class":65,"line":147},[63,637,187],{"class":73},[63,639,70],{"class":69},[63,641,74],{"class":73},[63,643,644],{"class":77},"Notify\n",[63,646,647,649,651],{"class":65,"line":155},[63,648,492],{"class":69},[63,650,74],{"class":73},[63,652,653],{"class":77},"always()\n",[63,655,656,658,660],{"class":65,"line":166},[63,657,396],{"class":69},[63,659,74],{"class":73},[63,661,662],{"class":77},"slackapi\u002Fslack-github-action@v1\n",[63,664,665,667],{"class":65,"line":176},[63,666,213],{"class":69},[63,668,88],{"class":73},[63,670,671,674,676],{"class":65,"line":184},[63,672,673],{"class":69},"          slack-message",[63,675,74],{"class":73},[63,677,428],{"class":427},[63,679,680],{"class":65,"line":198},[63,681,682],{"class":77},"            ${{ job.status == 'success' && '✅' || '❌' }} 部署 ${{ job.status }}\n",[63,684,685],{"class":65,"line":210},[63,686,687],{"class":77},"            仓库: ${{ github.repository }}\n",[63,689,690],{"class":65,"line":230},[63,691,692],{"class":77},"            提交: ${{ github.event.head_commit.message }}\n",[14,694,695],{"id":695},"安全边界",[697,698,699,715],"table",{},[700,701,702],"thead",{},[703,704,705,709,712],"tr",{},[706,707,708],"th",{},"操作",[706,710,711],{},"AI 可执行",[706,713,714],{},"需人工确认",[716,717,718,729,738,747,756,765,774,783],"tbody",{},[703,719,720,724,727],{},[721,722,723],"td",{},"跑测试",[721,725,726],{},"✅",[721,728],{},[703,730,731,734,736],{},[721,732,733],{},"构建项目",[721,735,726],{},[721,737],{},[703,739,740,743,745],{},[721,741,742],{},"部署到 preview",[721,744,726],{},[721,746],{},[703,748,749,752,754],{},[721,750,751],{},"部署到 production",[721,753,726],{},[721,755],{},[703,757,758,761,763],{},[721,759,760],{},"回滚",[721,762,726],{},[721,764],{},[703,766,767,770,772],{},[721,768,769],{},"修改环境变量",[721,771],{},[721,773,726],{},[703,775,776,779,781],{},[721,777,778],{},"数据库迁移",[721,780],{},[721,782,726],{},[703,784,785,788,790],{},[721,786,787],{},"删除资源",[721,789],{},[721,791,726],{},[14,793,794],{"id":794},"效果",[49,796,797],{},"上线 1 个月后：",[697,799,800,813],{},[700,801,802],{},[703,803,804,807,810],{},[706,805,806],{},"指标",[706,808,809],{},"之前（手动）",[706,811,812],{},"之后（AI 自动）",[716,814,815,826,837,848],{},[703,816,817,820,823],{},[721,818,819],{},"部署频率",[721,821,822],{},"2 次\u002F周",[721,824,825],{},"5 次\u002F天",[703,827,828,831,834],{},[721,829,830],{},"部署耗时",[721,832,833],{},"15 分钟",[721,835,836],{},"3 分钟",[703,838,839,842,845],{},[721,840,841],{},"部署失败率",[721,843,844],{},"10%",[721,846,847],{},"3%（自动回滚）",[703,849,850,853,856],{},[721,851,852],{},"人工介入",[721,854,855],{},"每次",[721,857,858],{},"5% 的部署",[14,860,861],{"id":861},"踩坑记录",[863,864,865,879,885,891,897],"ol",{},[21,866,867,874,875,878],{},[868,869,870,871],"strong",{},"Claude Code Action 的 ",[41,872,873],{},"allowed_tools","——只给 ",[41,876,877],{},"Bash","，别给文件编辑权限，否则 AI 可能改代码。",[21,880,881,884],{},[868,882,883],{},"Vercel rollback 需要 production deployment 历史","——首次部署没有回滚目标，先手动部署一次。",[21,886,887,890],{},[868,888,889],{},"Claude API 成本","——每次部署约 $0.2-0.5（审查 + 部署 + 验证），月费 $30-50。",[21,892,893,896],{},[868,894,895],{},"preview 环境先验证","——加一步部署到 preview 验证通过再 promote 到 production。",[21,898,899,902,903,906],{},[868,900,901],{},"GitHub Actions 超时","——Claude Code 调用有时慢，设置 ",[41,904,905],{},"timeout-minutes: 15","。",[908,909,910],"style",{},"html pre.shiki code .s9eBZ, html code.shiki .s9eBZ{--shiki-default:#22863A;--shiki-dark:#85E89D}html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}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 .szBVR, html code.shiki .szBVR{--shiki-default:#D73A49;--shiki-dark:#F97583}",{"title":43,"searchDepth":91,"depth":91,"links":912},[913,914,915,916,917,918,919,920,921],{"id":16,"depth":81,"text":16},{"id":32,"depth":81,"text":32},{"id":46,"depth":81,"text":47},{"id":371,"depth":81,"text":372},{"id":470,"depth":81,"text":471},{"id":575,"depth":81,"text":576},{"id":695,"depth":81,"text":695},{"id":794,"depth":81,"text":794},{"id":861,"depth":81,"text":861},"deploy","\u002Fog\u002Fplaybook\u002Fai-auto-deploy.png","用 Claude Code + GitHub Actions + Vercel\u002FCfly 搭一条全自动部署流水线——push 到 main → AI 跑测试 → AI 审查 → AI 部署 → 验证 → 通知。零手工操作，含回滚机制。","md",{},"\u002Fplaybook\u002Fdeploy\u002Fai-auto-deploy","2026-06-21",[930,931,932],"coding\u002Fcli\u002Fclaude-code","coding\u002Fbuilder\u002Fv0","agent\u002Fskills\u002Fclaude-skills",{"title":9,"description":924},"playbook\u002Fdeploy\u002Fai-auto-deploy",[936,937,938,939,940],"部署","CI\u002FCD","Claude Code","GitHub Actions","自动化","0C0tbe1lwERKuzXgnhYWUeLJ7FFDITUdnTFvc0MVs-E",{"id":943,"title":944,"body":945,"category":2035,"cover":2036,"description":2037,"extension":925,"meta":2038,"navigation":117,"path":2039,"published":928,"relatedTools":2040,"seo":2042,"stem":2043,"tags":2044,"updated":928,"__hash__":2049},"playbook\u002Fplaybook\u002Fmigration\u002Fai-db-migration.md","用 AI 做数据库迁移：零停机 schema 变更工作流",{"type":11,"value":946,"toc":2021},[947,949,963,966,992,995,1001,1005,1008,1014,1017,1042,1045,1173,1177,1183,1186,1190,1195,1516,1519,1743,1746,1801,1805,1904,1908,1911,1947,2012,2018],[14,948,16],{"id":16},[18,950,951,954,957,960],{},[21,952,953],{},"生产数据库需要 schema 变更",[21,955,956],{},"大表（千万行+）加列 \u002F 改类型 \u002F 加索引",[21,958,959],{},"需要零停机迁移",[21,961,962],{},"想让 AI 生成迁移文件 + 回滚方案",[14,964,965],{"id":965},"迁移原则",[863,967,968,974,980,986],{},[21,969,970,973],{},[868,971,972],{},"永远可回滚","——每个迁移文件都有对应的 down migration",[21,975,976,979],{},[868,977,978],{},"分阶段执行","——大变更拆成多步，每步都可独立回滚",[21,981,982,985],{},[868,983,984],{},"先兼容后破坏","——先让代码兼容新 schema，再删旧字段",[21,987,988,991],{},[868,989,990],{},"AI 生成 + 人工审查","——AI 写迁移，人审 SQL",[14,993,994],{"id":994},"工作流",[34,996,999],{"className":997,"code":998,"language":39},[37],"需求：给 users 表加 phone 字段\n  → Step 1: Claude 生成迁移文件（含 up + down）\n  → Step 2: Claude 检查兼容性（是否破坏现有代码）\n  → Step 3: 在 shadow DB 测试迁移\n  → Step 4: 生产分阶段执行\n  → Step 5: 验证 + 清理\n",[41,1000,998],{"__ignoreMap":43},[14,1002,1004],{"id":1003},"step-1-ai-生成迁移","Step 1: AI 生成迁移",[49,1006,1007],{},"用 Claude Code 连数据库（通过 MCP），描述需求：",[34,1009,1012],{"className":1010,"code":1011,"language":39},[37],"给 users 表加一个 phone 字段，varchar(20)，可空，加唯一索引。\n用 Drizzle migration 格式生成，含 up 和 down。\n检查现有代码是否有依赖。\n",[41,1013,1011],{"__ignoreMap":43},[49,1015,1016],{},"Claude 会：",[863,1018,1019,1022,1025,1028,1031],{},[21,1020,1021],{},"查当前 users 表结构",[21,1023,1024],{},"生成 Drizzle schema 改动",[21,1026,1027],{},"生成 SQL migration 文件",[21,1029,1030],{},"生成回滚 SQL",[21,1032,1033,1034,1037,1038,1041],{},"检查代码里的 ",[41,1035,1036],{},"select *"," 和 ",[41,1039,1040],{},"insert"," 是否受影响",[49,1043,1044],{},"生成的文件：",[34,1046,1050],{"className":1047,"code":1048,"language":1049,"meta":43,"style":43},"language-sql shiki shiki-themes github-light github-dark","-- migrations\u002F0024_add_user_phone.sql\n\n-- UP\nALTER TABLE users ADD COLUMN phone VARCHAR(20);\nCREATE UNIQUE INDEX idx_users_phone ON users(phone) WHERE phone IS NOT NULL;\n\n-- DOWN\nDROP INDEX IF EXISTS idx_users_phone;\nALTER TABLE users DROP COLUMN IF EXISTS phone;\n","sql",[41,1051,1052,1058,1062,1067,1096,1126,1130,1135,1152],{"__ignoreMap":43},[63,1053,1054],{"class":65,"line":66},[63,1055,1057],{"class":1056},"sJ8bj","-- migrations\u002F0024_add_user_phone.sql\n",[63,1059,1060],{"class":65,"line":81},[63,1061,118],{"emptyLinePlaceholder":117},[63,1063,1064],{"class":65,"line":91},[63,1065,1066],{"class":1056},"-- UP\n",[63,1068,1069,1072,1075,1078,1081,1084,1087,1090,1093],{"class":65,"line":99},[63,1070,1071],{"class":427},"ALTER",[63,1073,1074],{"class":427}," TABLE",[63,1076,1077],{"class":73}," users ",[63,1079,1080],{"class":427},"ADD",[63,1082,1083],{"class":73}," COLUMN phone ",[63,1085,1086],{"class":427},"VARCHAR",[63,1088,1089],{"class":73},"(",[63,1091,1092],{"class":84},"20",[63,1094,1095],{"class":73},");\n",[63,1097,1098,1101,1104,1108,1111,1114,1117,1120,1123],{"class":65,"line":114},[63,1099,1100],{"class":427},"CREATE",[63,1102,1103],{"class":427}," UNIQUE INDEX",[63,1105,1107],{"class":1106},"sScJk"," idx_users_phone",[63,1109,1110],{"class":427}," ON",[63,1112,1113],{"class":73}," users(phone) ",[63,1115,1116],{"class":427},"WHERE",[63,1118,1119],{"class":73}," phone ",[63,1121,1122],{"class":427},"IS NOT NULL",[63,1124,1125],{"class":73},";\n",[63,1127,1128],{"class":65,"line":121},[63,1129,118],{"emptyLinePlaceholder":117},[63,1131,1132],{"class":65,"line":5},[63,1133,1134],{"class":1056},"-- DOWN\n",[63,1136,1137,1140,1143,1146,1149],{"class":65,"line":136},[63,1138,1139],{"class":427},"DROP",[63,1141,1142],{"class":427}," INDEX",[63,1144,1145],{"class":427}," IF",[63,1147,1148],{"class":427}," EXISTS",[63,1150,1151],{"class":73}," idx_users_phone;\n",[63,1153,1154,1156,1158,1160,1162,1165,1168,1170],{"class":65,"line":147},[63,1155,1071],{"class":427},[63,1157,1074],{"class":427},[63,1159,1077],{"class":73},[63,1161,1139],{"class":427},[63,1163,1164],{"class":73}," COLUMN ",[63,1166,1167],{"class":427},"IF",[63,1169,1148],{"class":427},[63,1171,1172],{"class":73}," phone;\n",[14,1174,1176],{"id":1175},"step-2-兼容性检查","Step 2: 兼容性检查",[34,1178,1181],{"className":1179,"code":1180,"language":39},[37],"检查这次迁移会影响哪些代码：\n1. 哪些 INSERT 语句需要加 phone 字段\n2. 哪些 SELECT * 会导致返回字段变化\n3. 哪些 API 响应 schema 会变\n",[41,1182,1180],{"__ignoreMap":43},[49,1184,1185],{},"Claude 输出影响清单，你确认后再执行。",[14,1187,1189],{"id":1188},"step-3-高危操作安全模式","Step 3: 高危操作安全模式",[1191,1192,1194],"h3",{"id":1193},"大表加列千万行","大表加列（千万行+）",[34,1196,1198],{"className":1047,"code":1197,"language":1049,"meta":43,"style":43},"-- ❌ 错误：直接加（锁表）\nALTER TABLE users ADD COLUMN phone VARCHAR(20);\n\n-- ✅ 正确：分阶段\n-- Phase 1: 加可空字段（不锁表，PostgreSQL 11+）\nALTER TABLE users ADD COLUMN phone VARCHAR(20);\n\n-- Phase 2: 回填数据（分批，不锁表）\n-- Claude 生成批处理脚本\nDO $$\nDECLARE\n  batch_size INT := 10000;\n  offset_val INT := 0;\nBEGIN\n  LOOP\n    UPDATE users SET phone = '' WHERE id IN (\n      SELECT id FROM users WHERE phone IS NULL LIMIT batch_size\n    );\n    GET DIAGNOSTICS batch_size = ROW_COUNT;\n    EXIT WHEN batch_size = 0;\n    PERFORM pg_sleep(0.1);  -- 给主从复制留时间\n  END LOOP;\nEND $$;\n\n-- Phase 3: 加约束（先检查再加）\nALTER TABLE users ADD CONSTRAINT chk_phone CHECK (phone ~ '^\\+?[0-9]{6,20}$') NOT VALID;\nALTER TABLE users VALIDATE CONSTRAINT chk_phone;\n",[41,1199,1200,1205,1225,1229,1234,1239,1259,1263,1268,1273,1278,1283,1302,1318,1323,1328,1357,1385,1390,1403,1420,1440,1450,1458,1462,1467,1501],{"__ignoreMap":43},[63,1201,1202],{"class":65,"line":66},[63,1203,1204],{"class":1056},"-- ❌ 错误：直接加（锁表）\n",[63,1206,1207,1209,1211,1213,1215,1217,1219,1221,1223],{"class":65,"line":81},[63,1208,1071],{"class":427},[63,1210,1074],{"class":427},[63,1212,1077],{"class":73},[63,1214,1080],{"class":427},[63,1216,1083],{"class":73},[63,1218,1086],{"class":427},[63,1220,1089],{"class":73},[63,1222,1092],{"class":84},[63,1224,1095],{"class":73},[63,1226,1227],{"class":65,"line":91},[63,1228,118],{"emptyLinePlaceholder":117},[63,1230,1231],{"class":65,"line":99},[63,1232,1233],{"class":1056},"-- ✅ 正确：分阶段\n",[63,1235,1236],{"class":65,"line":114},[63,1237,1238],{"class":1056},"-- Phase 1: 加可空字段（不锁表，PostgreSQL 11+）\n",[63,1240,1241,1243,1245,1247,1249,1251,1253,1255,1257],{"class":65,"line":121},[63,1242,1071],{"class":427},[63,1244,1074],{"class":427},[63,1246,1077],{"class":73},[63,1248,1080],{"class":427},[63,1250,1083],{"class":73},[63,1252,1086],{"class":427},[63,1254,1089],{"class":73},[63,1256,1092],{"class":84},[63,1258,1095],{"class":73},[63,1260,1261],{"class":65,"line":5},[63,1262,118],{"emptyLinePlaceholder":117},[63,1264,1265],{"class":65,"line":136},[63,1266,1267],{"class":1056},"-- Phase 2: 回填数据（分批，不锁表）\n",[63,1269,1270],{"class":65,"line":147},[63,1271,1272],{"class":1056},"-- Claude 生成批处理脚本\n",[63,1274,1275],{"class":65,"line":155},[63,1276,1277],{"class":73},"DO $$\n",[63,1279,1280],{"class":65,"line":166},[63,1281,1282],{"class":427},"DECLARE\n",[63,1284,1285,1288,1291,1294,1297,1300],{"class":65,"line":176},[63,1286,1287],{"class":73},"  batch_size ",[63,1289,1290],{"class":427},"INT",[63,1292,1293],{"class":73}," :",[63,1295,1296],{"class":427},"=",[63,1298,1299],{"class":84}," 10000",[63,1301,1125],{"class":73},[63,1303,1304,1307,1309,1311,1313,1316],{"class":65,"line":184},[63,1305,1306],{"class":73},"  offset_val ",[63,1308,1290],{"class":427},[63,1310,1293],{"class":73},[63,1312,1296],{"class":427},[63,1314,1315],{"class":84}," 0",[63,1317,1125],{"class":73},[63,1319,1320],{"class":65,"line":198},[63,1321,1322],{"class":427},"BEGIN\n",[63,1324,1325],{"class":65,"line":210},[63,1326,1327],{"class":427},"  LOOP\n",[63,1329,1330,1333,1335,1338,1340,1342,1345,1348,1351,1354],{"class":65,"line":230},[63,1331,1332],{"class":427},"    UPDATE",[63,1334,1077],{"class":73},[63,1336,1337],{"class":427},"SET",[63,1339,1119],{"class":73},[63,1341,1296],{"class":427},[63,1343,1344],{"class":77}," ''",[63,1346,1347],{"class":427}," WHERE",[63,1349,1350],{"class":73}," id ",[63,1352,1353],{"class":427},"IN",[63,1355,1356],{"class":73}," (\n",[63,1358,1359,1362,1364,1367,1369,1371,1373,1376,1379,1382],{"class":65,"line":242},[63,1360,1361],{"class":427},"      SELECT",[63,1363,1350],{"class":73},[63,1365,1366],{"class":427},"FROM",[63,1368,1077],{"class":73},[63,1370,1116],{"class":427},[63,1372,1119],{"class":73},[63,1374,1375],{"class":427},"IS",[63,1377,1378],{"class":427}," NULL",[63,1380,1381],{"class":427}," LIMIT",[63,1383,1384],{"class":73}," batch_size\n",[63,1386,1387],{"class":65,"line":270},[63,1388,1389],{"class":73},"    );\n",[63,1391,1392,1395,1398,1400],{"class":65,"line":275},[63,1393,1394],{"class":427},"    GET",[63,1396,1397],{"class":73}," DIAGNOSTICS batch_size ",[63,1399,1296],{"class":427},[63,1401,1402],{"class":73}," ROW_COUNT;\n",[63,1404,1405,1408,1411,1414,1416,1418],{"class":65,"line":287},[63,1406,1407],{"class":73},"    EXIT ",[63,1409,1410],{"class":427},"WHEN",[63,1412,1413],{"class":73}," batch_size ",[63,1415,1296],{"class":427},[63,1417,1315],{"class":84},[63,1419,1125],{"class":73},[63,1421,1422,1425,1428,1431,1434,1437],{"class":65,"line":298},[63,1423,1424],{"class":73},"    PERFORM pg_sleep(",[63,1426,1427],{"class":84},"0",[63,1429,1430],{"class":73},".",[63,1432,1433],{"class":84},"1",[63,1435,1436],{"class":73},");  ",[63,1438,1439],{"class":1056},"-- 给主从复制留时间\n",[63,1441,1442,1445,1448],{"class":65,"line":303},[63,1443,1444],{"class":427},"  END",[63,1446,1447],{"class":427}," LOOP",[63,1449,1125],{"class":73},[63,1451,1452,1455],{"class":65,"line":315},[63,1453,1454],{"class":427},"END",[63,1456,1457],{"class":73}," $$;\n",[63,1459,1460],{"class":65,"line":325},[63,1461,118],{"emptyLinePlaceholder":117},[63,1463,1464],{"class":65,"line":330},[63,1465,1466],{"class":1056},"-- Phase 3: 加约束（先检查再加）\n",[63,1468,1469,1471,1473,1475,1477,1480,1483,1486,1489,1492,1495,1498],{"class":65,"line":342},[63,1470,1071],{"class":427},[63,1472,1074],{"class":427},[63,1474,1077],{"class":73},[63,1476,1080],{"class":427},[63,1478,1479],{"class":427}," CONSTRAINT",[63,1481,1482],{"class":73}," chk_phone ",[63,1484,1485],{"class":427},"CHECK",[63,1487,1488],{"class":73}," (phone ~ ",[63,1490,1491],{"class":77},"'^\\+?[0-9]{6,20}$'",[63,1493,1494],{"class":73},") ",[63,1496,1497],{"class":427},"NOT",[63,1499,1500],{"class":73}," VALID;\n",[63,1502,1503,1505,1507,1510,1513],{"class":65,"line":352},[63,1504,1071],{"class":427},[63,1506,1074],{"class":427},[63,1508,1509],{"class":73}," users VALIDATE ",[63,1511,1512],{"class":427},"CONSTRAINT",[63,1514,1515],{"class":73}," chk_phone;\n",[1191,1517,1518],{"id":1518},"改字段类型",[34,1520,1522],{"className":1047,"code":1521,"language":1049,"meta":43,"style":43},"-- ❌ 错误：直接改（锁表 + 重写全表）\nALTER TABLE users ALTER COLUMN phone TYPE BIGINT USING phone::BIGINT;\n\n-- ✅ 正确：新字段 + 回填 + 切换 + 删旧\n-- Phase 1: 加新字段\nALTER TABLE users ADD COLUMN phone_int BIGINT;\n\n-- Phase 2: 双写（代码同时写旧和新）\n-- Claude 生成代码改动：INSERT\u002FUPDATE 同时写 phone 和 phone_int\n\n-- Phase 3: 回填\nUPDATE users SET phone_int = phone::BIGINT WHERE phone_int IS NULL AND phone ~ '^[0-9]+$';\n\n-- Phase 4: 验证数据一致\nSELECT COUNT(*) FROM users WHERE phone IS NOT NULL AND phone_int IS NULL;\n-- 必须为 0\n\n-- Phase 5: 代码切到读 phone_int\n\n-- Phase 6: 删旧字段（等一个发布周期后）\nALTER TABLE users DROP COLUMN phone;\nALTER TABLE users RENAME COLUMN phone_int TO phone;\n",[41,1523,1524,1529,1558,1562,1567,1572,1589,1593,1598,1603,1607,1612,1649,1653,1658,1693,1698,1702,1707,1711,1716,1729],{"__ignoreMap":43},[63,1525,1526],{"class":65,"line":66},[63,1527,1528],{"class":1056},"-- ❌ 错误：直接改（锁表 + 重写全表）\n",[63,1530,1531,1533,1535,1537,1539,1541,1544,1547,1550,1553,1556],{"class":65,"line":81},[63,1532,1071],{"class":427},[63,1534,1074],{"class":427},[63,1536,1077],{"class":73},[63,1538,1071],{"class":427},[63,1540,1083],{"class":73},[63,1542,1543],{"class":427},"TYPE",[63,1545,1546],{"class":427}," BIGINT",[63,1548,1549],{"class":427}," USING",[63,1551,1552],{"class":73}," phone::",[63,1554,1555],{"class":427},"BIGINT",[63,1557,1125],{"class":73},[63,1559,1560],{"class":65,"line":91},[63,1561,118],{"emptyLinePlaceholder":117},[63,1563,1564],{"class":65,"line":99},[63,1565,1566],{"class":1056},"-- ✅ 正确：新字段 + 回填 + 切换 + 删旧\n",[63,1568,1569],{"class":65,"line":114},[63,1570,1571],{"class":1056},"-- Phase 1: 加新字段\n",[63,1573,1574,1576,1578,1580,1582,1585,1587],{"class":65,"line":121},[63,1575,1071],{"class":427},[63,1577,1074],{"class":427},[63,1579,1077],{"class":73},[63,1581,1080],{"class":427},[63,1583,1584],{"class":73}," COLUMN phone_int ",[63,1586,1555],{"class":427},[63,1588,1125],{"class":73},[63,1590,1591],{"class":65,"line":5},[63,1592,118],{"emptyLinePlaceholder":117},[63,1594,1595],{"class":65,"line":136},[63,1596,1597],{"class":1056},"-- Phase 2: 双写（代码同时写旧和新）\n",[63,1599,1600],{"class":65,"line":147},[63,1601,1602],{"class":1056},"-- Claude 生成代码改动：INSERT\u002FUPDATE 同时写 phone 和 phone_int\n",[63,1604,1605],{"class":65,"line":155},[63,1606,118],{"emptyLinePlaceholder":117},[63,1608,1609],{"class":65,"line":166},[63,1610,1611],{"class":1056},"-- Phase 3: 回填\n",[63,1613,1614,1617,1619,1621,1624,1626,1628,1630,1632,1634,1636,1638,1641,1644,1647],{"class":65,"line":176},[63,1615,1616],{"class":427},"UPDATE",[63,1618,1077],{"class":73},[63,1620,1337],{"class":427},[63,1622,1623],{"class":73}," phone_int ",[63,1625,1296],{"class":427},[63,1627,1552],{"class":73},[63,1629,1555],{"class":427},[63,1631,1347],{"class":427},[63,1633,1623],{"class":73},[63,1635,1375],{"class":427},[63,1637,1378],{"class":427},[63,1639,1640],{"class":427}," AND",[63,1642,1643],{"class":73}," phone ~ ",[63,1645,1646],{"class":77},"'^[0-9]+$'",[63,1648,1125],{"class":73},[63,1650,1651],{"class":65,"line":184},[63,1652,118],{"emptyLinePlaceholder":117},[63,1654,1655],{"class":65,"line":198},[63,1656,1657],{"class":1056},"-- Phase 4: 验证数据一致\n",[63,1659,1660,1663,1666,1668,1671,1673,1675,1677,1679,1681,1683,1685,1687,1689,1691],{"class":65,"line":210},[63,1661,1662],{"class":427},"SELECT",[63,1664,1665],{"class":84}," COUNT",[63,1667,1089],{"class":73},[63,1669,1670],{"class":427},"*",[63,1672,1494],{"class":73},[63,1674,1366],{"class":427},[63,1676,1077],{"class":73},[63,1678,1116],{"class":427},[63,1680,1119],{"class":73},[63,1682,1122],{"class":427},[63,1684,1640],{"class":427},[63,1686,1623],{"class":73},[63,1688,1375],{"class":427},[63,1690,1378],{"class":427},[63,1692,1125],{"class":73},[63,1694,1695],{"class":65,"line":230},[63,1696,1697],{"class":1056},"-- 必须为 0\n",[63,1699,1700],{"class":65,"line":242},[63,1701,118],{"emptyLinePlaceholder":117},[63,1703,1704],{"class":65,"line":270},[63,1705,1706],{"class":1056},"-- Phase 5: 代码切到读 phone_int\n",[63,1708,1709],{"class":65,"line":275},[63,1710,118],{"emptyLinePlaceholder":117},[63,1712,1713],{"class":65,"line":287},[63,1714,1715],{"class":1056},"-- Phase 6: 删旧字段（等一个发布周期后）\n",[63,1717,1718,1720,1722,1724,1726],{"class":65,"line":298},[63,1719,1071],{"class":427},[63,1721,1074],{"class":427},[63,1723,1077],{"class":73},[63,1725,1139],{"class":427},[63,1727,1728],{"class":73}," COLUMN phone;\n",[63,1730,1731,1733,1735,1738,1741],{"class":65,"line":303},[63,1732,1071],{"class":427},[63,1734,1074],{"class":427},[63,1736,1737],{"class":73}," users RENAME COLUMN phone_int ",[63,1739,1740],{"class":427},"TO",[63,1742,1172],{"class":73},[1191,1744,1745],{"id":1745},"加索引",[34,1747,1749],{"className":1047,"code":1748,"language":1049,"meta":43,"style":43},"-- ❌ 错误：直接加（锁表写操作）\nCREATE INDEX idx_users_email ON users(email);\n\n-- ✅ 正确：CONCURRENTLY（不锁表，但慢）\nCREATE INDEX CONCURRENTLY idx_users_email ON users(email);\n-- 注意：CONCURRENTLY 不能在事务里跑\n",[41,1750,1751,1756,1770,1774,1779,1796],{"__ignoreMap":43},[63,1752,1753],{"class":65,"line":66},[63,1754,1755],{"class":1056},"-- ❌ 错误：直接加（锁表写操作）\n",[63,1757,1758,1760,1762,1765,1767],{"class":65,"line":81},[63,1759,1100],{"class":427},[63,1761,1142],{"class":427},[63,1763,1764],{"class":1106}," idx_users_email",[63,1766,1110],{"class":427},[63,1768,1769],{"class":73}," users(email);\n",[63,1771,1772],{"class":65,"line":91},[63,1773,118],{"emptyLinePlaceholder":117},[63,1775,1776],{"class":65,"line":99},[63,1777,1778],{"class":1056},"-- ✅ 正确：CONCURRENTLY（不锁表，但慢）\n",[63,1780,1781,1783,1785,1788,1791,1794],{"class":65,"line":114},[63,1782,1100],{"class":427},[63,1784,1142],{"class":427},[63,1786,1787],{"class":1106}," CONCURRENTLY",[63,1789,1790],{"class":73}," idx_users_email ",[63,1792,1793],{"class":427},"ON",[63,1795,1769],{"class":73},[63,1797,1798],{"class":65,"line":121},[63,1799,1800],{"class":1056},"-- 注意：CONCURRENTLY 不能在事务里跑\n",[14,1802,1804],{"id":1803},"step-4-执行-监控","Step 4: 执行 + 监控",[34,1806,1810],{"className":1807,"code":1808,"language":1809,"meta":43,"style":43},"language-bash shiki shiki-themes github-light github-dark","# 1. 在 shadow DB 测试\npsql $SHADOW_DB -f migrations\u002F0024_add_user_phone.sql\n\n# 2. 生产执行（维护窗口）\npsql $PROD_DB -f migrations\u002F0024_add_user_phone.sql\n\n# 3. 监控（Claude 帮你写监控脚本）\nwatch -n 5 'psql $PROD_DB -c \"\n  SELECT\n    count(*) AS total,\n    count(phone) AS with_phone,\n    count(*) - count(phone) AS without_phone\n  FROM users\n\"'\n","bash",[41,1811,1812,1817,1831,1835,1840,1851,1855,1860,1874,1879,1884,1889,1894,1899],{"__ignoreMap":43},[63,1813,1814],{"class":65,"line":66},[63,1815,1816],{"class":1056},"# 1. 在 shadow DB 测试\n",[63,1818,1819,1822,1825,1828],{"class":65,"line":81},[63,1820,1821],{"class":1106},"psql",[63,1823,1824],{"class":73}," $SHADOW_DB ",[63,1826,1827],{"class":84},"-f",[63,1829,1830],{"class":77}," migrations\u002F0024_add_user_phone.sql\n",[63,1832,1833],{"class":65,"line":91},[63,1834,118],{"emptyLinePlaceholder":117},[63,1836,1837],{"class":65,"line":99},[63,1838,1839],{"class":1056},"# 2. 生产执行（维护窗口）\n",[63,1841,1842,1844,1847,1849],{"class":65,"line":114},[63,1843,1821],{"class":1106},[63,1845,1846],{"class":73}," $PROD_DB ",[63,1848,1827],{"class":84},[63,1850,1830],{"class":77},[63,1852,1853],{"class":65,"line":121},[63,1854,118],{"emptyLinePlaceholder":117},[63,1856,1857],{"class":65,"line":5},[63,1858,1859],{"class":1056},"# 3. 监控（Claude 帮你写监控脚本）\n",[63,1861,1862,1865,1868,1871],{"class":65,"line":136},[63,1863,1864],{"class":1106},"watch",[63,1866,1867],{"class":84}," -n",[63,1869,1870],{"class":84}," 5",[63,1872,1873],{"class":77}," 'psql $PROD_DB -c \"\n",[63,1875,1876],{"class":65,"line":147},[63,1877,1878],{"class":77},"  SELECT\n",[63,1880,1881],{"class":65,"line":155},[63,1882,1883],{"class":77},"    count(*) AS total,\n",[63,1885,1886],{"class":65,"line":166},[63,1887,1888],{"class":77},"    count(phone) AS with_phone,\n",[63,1890,1891],{"class":65,"line":176},[63,1892,1893],{"class":77},"    count(*) - count(phone) AS without_phone\n",[63,1895,1896],{"class":65,"line":184},[63,1897,1898],{"class":77},"  FROM users\n",[63,1900,1901],{"class":65,"line":198},[63,1902,1903],{"class":77},"\"'\n",[14,1905,1907],{"id":1906},"step-5-回滚方案","Step 5: 回滚方案",[49,1909,1910],{},"每个迁移执行前，Claude 生成回滚 checklist：",[34,1912,1916],{"className":1913,"code":1914,"language":1915,"meta":43,"style":43},"language-markdown shiki shiki-themes github-light github-dark","## 回滚步骤（如需）\n\n1. 确认 down migration 安全：\n   ```sql\n   SELECT count(*) FROM users WHERE phone IS NOT NULL;\n   -- 如果 > 0，回滚会丢数据，确认是否可接受\n","markdown",[41,1917,1918,1923,1927,1932,1937,1942],{"__ignoreMap":43},[63,1919,1920],{"class":65,"line":66},[63,1921,1922],{},"## 回滚步骤（如需）\n",[63,1924,1925],{"class":65,"line":81},[63,1926,118],{"emptyLinePlaceholder":117},[63,1928,1929],{"class":65,"line":91},[63,1930,1931],{},"1. 确认 down migration 安全：\n",[63,1933,1934],{"class":65,"line":99},[63,1935,1936],{},"   ```sql\n",[63,1938,1939],{"class":65,"line":114},[63,1940,1941],{},"   SELECT count(*) FROM users WHERE phone IS NOT NULL;\n",[63,1943,1944],{"class":65,"line":121},[63,1945,1946],{},"   -- 如果 > 0，回滚会丢数据，确认是否可接受\n",[863,1948,1949,1968,1996],{"start":81},[21,1950,1951,1952],{},"执行回滚：",[34,1953,1955],{"className":1807,"code":1954,"language":1809,"meta":43,"style":43},"psql $PROD_DB -f migrations\u002F0024_add_user_phone_down.sql\n",[41,1956,1957],{"__ignoreMap":43},[63,1958,1959,1961,1963,1965],{"class":65,"line":66},[63,1960,1821],{"class":1106},[63,1962,1846],{"class":73},[63,1964,1827],{"class":84},[63,1966,1967],{"class":77}," migrations\u002F0024_add_user_phone_down.sql\n",[21,1969,1970,1971],{},"代码回滚到上一个版本：",[34,1972,1974],{"className":1807,"code":1973,"language":1809,"meta":43,"style":43},"git revert \u003Cmerge-commit>\n",[41,1975,1976],{"__ignoreMap":43},[63,1977,1978,1981,1984,1987,1990,1993],{"class":65,"line":66},[63,1979,1980],{"class":1106},"git",[63,1982,1983],{"class":77}," revert",[63,1985,1986],{"class":427}," \u003C",[63,1988,1989],{"class":77},"merge-commi",[63,1991,1992],{"class":73},"t",[63,1994,1995],{"class":427},">\n",[21,1997,1998,1999],{},"验证：",[34,2000,2002],{"className":1047,"code":2001,"language":1049,"meta":43,"style":43},"\\d users  -- 确认 phone 字段已删\n",[41,2003,2004],{"__ignoreMap":43},[63,2005,2006,2009],{"class":65,"line":66},[63,2007,2008],{"class":73},"\\d users  ",[63,2010,2011],{"class":1056},"-- 确认 phone 字段已删\n",[34,2013,2016],{"className":2014,"code":2015,"language":39},[37],"\n## 踩坑记录\n\n1. **PostgreSQL 11+ 加可空字段才是即时**——低版本加字段仍会锁表，先升级。\n2. **CONCURRENTLY 索引失败要手动清理**——失败后留 invalid index，`DROP INDEX` 后重建。\n3. **回填脚本要分批 + sleep**——大批量 UPDATE 会撑爆 WAL 和主从延迟。\n4. **NOT VALID + VALIDATE 两步走**——直接加 CHECK 会全表扫描锁表。\n5. **Claude 生成 SQL 必须 review**——AI 偶尔会忘加 WHERE 条件，删数据操作尤其要审。\n",[41,2017,2015],{"__ignoreMap":43},[908,2019,2020],{},"html pre.shiki code .sJ8bj, html code.shiki .sJ8bj{--shiki-default:#6A737D;--shiki-dark:#6A737D}html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}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 .szBVR, html code.shiki .szBVR{--shiki-default:#D73A49;--shiki-dark:#F97583}",{"title":43,"searchDepth":91,"depth":91,"links":2022},[2023,2024,2025,2026,2027,2028,2033,2034],{"id":16,"depth":81,"text":16},{"id":965,"depth":81,"text":965},{"id":994,"depth":81,"text":994},{"id":1003,"depth":81,"text":1004},{"id":1175,"depth":81,"text":1176},{"id":1188,"depth":81,"text":1189,"children":2029},[2030,2031,2032],{"id":1193,"depth":91,"text":1194},{"id":1518,"depth":91,"text":1518},{"id":1745,"depth":91,"text":1745},{"id":1803,"depth":81,"text":1804},{"id":1906,"depth":81,"text":1907},"migration","\u002Fog\u002Fplaybook\u002Fai-db-migration.png","数据库 schema 迁移最怕搞挂生产。用 Claude Code + Drizzle migrations 搭一套安全迁移流程——AI 生成迁移文件 → 自动检查兼容性 → 分阶段执行 → 验证 → 回滚方案。含大表加列、改类型等高危操作。",{},"\u002Fplaybook\u002Fmigration\u002Fai-db-migration",[930,2041],"coding\u002Fide\u002Fcursor",{"title":944,"description":2037},"playbook\u002Fmigration\u002Fai-db-migration",[2045,2046,2047,2048,938],"数据库","迁移","Drizzle","零停机","tzaBre4cpBUCVMaO1qaWEjIwBDWReTAlQ9UMCOMI54A",{"id":2051,"title":2052,"body":2053,"category":2757,"cover":2758,"description":2759,"extension":925,"meta":2760,"navigation":117,"path":2761,"published":928,"relatedTools":2762,"seo":2764,"stem":2765,"tags":2766,"updated":928,"__hash__":2769},"playbook\u002Fplaybook\u002Fonboarding\u002Fclaude-code-getting-started.md","Claude Code 从零上手：CLI AI 编程实战指南",{"type":11,"value":2054,"toc":2734},[2055,2057,2071,2075,2198,2204,2208,2249,2252,2296,2299,2320,2324,2327,2344,2347,2353,2356,2370,2374,2377,2383,2386,2390,2393,2399,2402,2419,2425,2429,2432,2438,2441,2447,2451,2454,2463,2615,2618,2621,2625,2631,2670,2673,2677,2684,2688,2691,2697,2699,2703,2706,2710,2717,2721,2724,2728,2731],[14,2056,16],{"id":16},[18,2058,2059,2062,2065,2068],{},[21,2060,2061],{},"想用 CLI 方式做 AI 编程（不依赖 IDE）",[21,2063,2064],{},"已有 Cursor \u002F Copilot，想试试 Claude 原生体验",[21,2066,2067],{},"需要在服务器 \u002F SSH 环境用 AI 编程",[21,2069,2070],{},"想用 Claude Sonnet 4 \u002F Opus 4 的完整能力",[14,2072,2074],{"id":2073},"claude-code-vs-cursor-vs-copilot","Claude Code vs Cursor vs Copilot",[697,2076,2077,2092],{},[700,2078,2079],{},[703,2080,2081,2084,2086,2089],{},[706,2082,2083],{},"维度",[706,2085,938],{},[706,2087,2088],{},"Cursor",[706,2090,2091],{},"GitHub Copilot",[716,2093,2094,2108,2122,2135,2148,2162,2173,2184],{},[703,2095,2096,2099,2102,2105],{},[721,2097,2098],{},"形态",[721,2100,2101],{},"CLI",[721,2103,2104],{},"IDE",[721,2106,2107],{},"IDE 插件",[703,2109,2110,2113,2116,2119],{},[721,2111,2112],{},"模型",[721,2114,2115],{},"Claude 全系",[721,2117,2118],{},"多模型",[721,2120,2121],{},"GPT\u002FClaude",[703,2123,2124,2127,2130,2132],{},[721,2125,2126],{},"多文件修改",[721,2128,2129],{},"✅ 原生",[721,2131,726],{},[721,2133,2134],{},"❌ 单文件",[703,2136,2137,2140,2143,2145],{},[721,2138,2139],{},"项目理解",[721,2141,2142],{},"✅ 自动索引",[721,2144,726],{},[721,2146,2147],{},"部分",[703,2149,2150,2153,2156,2159],{},[721,2151,2152],{},"终端操作",[721,2154,2155],{},"✅ 直接执行",[721,2157,2158],{},"需切换",[721,2160,2161],{},"❌",[703,2163,2164,2167,2169,2171],{},[721,2165,2166],{},"Git 集成",[721,2168,2129],{},[721,2170,2147],{},[721,2172,2147],{},[703,2174,2175,2178,2180,2182],{},[721,2176,2177],{},"SSH\u002F服务器",[721,2179,726],{},[721,2181,2161],{},[721,2183,2161],{},[703,2185,2186,2189,2192,2195],{},[721,2187,2188],{},"价格",[721,2190,2191],{},"$20\u002F月（Max）",[721,2193,2194],{},"$20\u002F月",[721,2196,2197],{},"$10\u002F月",[49,2199,2200,2203],{},[868,2201,2202],{},"Claude Code 的独特优势","：CLI 原生 + 终端操作 + SSH 可用。",[14,2205,2207],{"id":2206},"第一步安装","第一步：安装",[34,2209,2211],{"className":1807,"code":2210,"language":1809,"meta":43,"style":43},"# 需要 Node.js 18+\nnpm install -g @anthropic-ai\u002Fclaude-code\n\n# 验证\nclaude --version\n",[41,2212,2213,2218,2232,2236,2241],{"__ignoreMap":43},[63,2214,2215],{"class":65,"line":66},[63,2216,2217],{"class":1056},"# 需要 Node.js 18+\n",[63,2219,2220,2223,2226,2229],{"class":65,"line":81},[63,2221,2222],{"class":1106},"npm",[63,2224,2225],{"class":77}," install",[63,2227,2228],{"class":84}," -g",[63,2230,2231],{"class":77}," @anthropic-ai\u002Fclaude-code\n",[63,2233,2234],{"class":65,"line":91},[63,2235,118],{"emptyLinePlaceholder":117},[63,2237,2238],{"class":65,"line":99},[63,2239,2240],{"class":1056},"# 验证\n",[63,2242,2243,2246],{"class":65,"line":114},[63,2244,2245],{"class":1106},"claude",[63,2247,2248],{"class":84}," --version\n",[1191,2250,2251],{"id":2251},"认证",[34,2253,2255],{"className":1807,"code":2254,"language":1809,"meta":43,"style":43},"# 方式 1：用 Anthropic 账号（推荐，含 Max 订阅）\nclaude login\n\n# 方式 2：用 API Key（按量付费）\nexport ANTHROPIC_API_KEY=\"sk-ant-xxx\"\nclaude\n",[41,2256,2257,2262,2269,2273,2278,2291],{"__ignoreMap":43},[63,2258,2259],{"class":65,"line":66},[63,2260,2261],{"class":1056},"# 方式 1：用 Anthropic 账号（推荐，含 Max 订阅）\n",[63,2263,2264,2266],{"class":65,"line":81},[63,2265,2245],{"class":1106},[63,2267,2268],{"class":77}," login\n",[63,2270,2271],{"class":65,"line":91},[63,2272,118],{"emptyLinePlaceholder":117},[63,2274,2275],{"class":65,"line":99},[63,2276,2277],{"class":1056},"# 方式 2：用 API Key（按量付费）\n",[63,2279,2280,2283,2286,2288],{"class":65,"line":114},[63,2281,2282],{"class":427},"export",[63,2284,2285],{"class":73}," ANTHROPIC_API_KEY",[63,2287,1296],{"class":427},[63,2289,2290],{"class":77},"\"sk-ant-xxx\"\n",[63,2292,2293],{"class":65,"line":121},[63,2294,2295],{"class":1106},"claude\n",[49,2297,2298],{},"国内用户如果直连慢，可以配代理：",[34,2300,2302],{"className":1807,"code":2301,"language":1809,"meta":43,"style":43},"export HTTPS_PROXY=\"http:\u002F\u002F127.0.0.1:7890\"\nclaude\n",[41,2303,2304,2316],{"__ignoreMap":43},[63,2305,2306,2308,2311,2313],{"class":65,"line":66},[63,2307,2282],{"class":427},[63,2309,2310],{"class":73}," HTTPS_PROXY",[63,2312,1296],{"class":427},[63,2314,2315],{"class":77},"\"http:\u002F\u002F127.0.0.1:7890\"\n",[63,2317,2318],{"class":65,"line":81},[63,2319,2295],{"class":1106},[14,2321,2323],{"id":2322},"第二步第一次使用","第二步：第一次使用",[49,2325,2326],{},"进入项目目录，启动 Claude Code：",[34,2328,2330],{"className":1807,"code":2329,"language":1809,"meta":43,"style":43},"cd my-project\nclaude\n",[41,2331,2332,2340],{"__ignoreMap":43},[63,2333,2334,2337],{"class":65,"line":66},[63,2335,2336],{"class":84},"cd",[63,2338,2339],{"class":77}," my-project\n",[63,2341,2342],{"class":65,"line":81},[63,2343,2295],{"class":1106},[49,2345,2346],{},"进入交互式 REPL。几个基本操作：",[34,2348,2351],{"className":2349,"code":2350,"language":39,"meta":43},[37],"> 帮我理解这个项目的结构          # 项目理解\n> 找到处理用户登录的代码          # 代码定位\n> 把这个函数改成支持异步          # 代码修改\n> 跑一下测试看看有没有 break      # 执行命令\n",[41,2352,2350],{"__ignoreMap":43},[49,2354,2355],{},"Claude Code 会自动：",[863,2357,2358,2361,2364,2367],{},[21,2359,2360],{},"扫描项目文件结构",[21,2362,2363],{},"读 package.json \u002F README 理解技术栈",[21,2365,2366],{},"找到相关文件",[21,2368,2369],{},"提出修改方案（需你确认后执行）",[14,2371,2373],{"id":2372},"第三步项目理解工作流","第三步：项目理解工作流",[49,2375,2376],{},"新接手一个项目时的标准操作：",[34,2378,2381],{"className":2379,"code":2380,"language":39,"meta":43},[37],"> 先读 README 和 package.json，告诉我这个项目是做什么的、用了什么技术栈\n\n> 画出项目的目录结构，标注每个目录的职责\n\n> 找到项目的入口文件，解释启动流程\n\n> 这个项目的数据流是怎样的？从 API 请求到数据库的完整路径\n\n> 找到所有 TODO 和 FIXME，按优先级排序\n",[41,2382,2380],{"__ignoreMap":43},[49,2384,2385],{},"Claude Code 会实际读取文件并分析，不是瞎猜。",[14,2387,2389],{"id":2388},"第四步多文件修改","第四步：多文件修改",[49,2391,2392],{},"这是 Claude Code 最强的能力。举个例子：",[34,2394,2397],{"className":2395,"code":2396,"language":39,"meta":43},[37],"> 我需要加一个\"用户头像上传\"功能。需要：\n> 1. 后端 API（上传到 S3）\n> 2. 前端组件（拖拽上传 + 预览）\n> 3. 数据库加 avatar_url 字段\n> 4. 用户 profile 页面显示头像\n> 先看一下现有代码结构，告诉我你的计划，我确认后再动手\n",[41,2398,2396],{"__ignoreMap":43},[49,2400,2401],{},"Claude Code 会：",[863,2403,2404,2407,2410,2413,2416],{},[21,2405,2406],{},"扫描相关文件（后端路由、前端组件、数据库 schema）",[21,2408,2409],{},"给出修改计划（改哪些文件、每个文件改什么）",[21,2411,2412],{},"等你确认",[21,2414,2415],{},"一次性修改多个文件",[21,2417,2418],{},"跑测试验证",[49,2420,2421,2424],{},[868,2422,2423],{},"关键","：先说\"告诉我计划\"，确认后再让它动手。避免改出一堆你不想要的东西。",[14,2426,2428],{"id":2427},"第五步git-工作流","第五步：Git 工作流",[49,2430,2431],{},"Claude Code 原生支持 git 操作：",[34,2433,2436],{"className":2434,"code":2435,"language":39,"meta":43},[37],"> 看一下当前的 git diff，帮我写 commit message\n\n> 帮我把这个改动拆成 3 个独立的 commit（按逻辑分组）\n\n> 创建一个分支 feature\u002Favatar-upload，把改动提交上去\n",[41,2437,2435],{"__ignoreMap":43},[49,2439,2440],{},"推荐工作流：",[34,2442,2445],{"className":2443,"code":2444,"language":39,"meta":43},[37],"1. claude  # 启动\n2. \"在 feature 分支上实现 XXX 功能\"  # 让它创建分支 + 改代码\n3. \"跑测试\"  # 验证\n4. \"看一下 diff，写 commit message\"  # 提交\n5. 退出后自己 git push + 开 PR\n",[41,2446,2444],{"__ignoreMap":43},[14,2448,2450],{"id":2449},"第六步mcp-集成","第六步：MCP 集成",[49,2452,2453],{},"Claude Code 支持连接 MCP Server，扩展能力：",[34,2455,2457],{"className":1807,"code":2456,"language":1809,"meta":43,"style":43},"# 配置文件位置：~\u002F.claude\u002Fmcp_servers.json\n",[41,2458,2459],{"__ignoreMap":43},[63,2460,2461],{"class":65,"line":66},[63,2462,2456],{"class":1056},[34,2464,2468],{"className":2465,"code":2466,"language":2467,"meta":43,"style":43},"language-json shiki shiki-themes github-light github-dark","{\n  \"mcpServers\": {\n    \"postgres\": {\n      \"command\": \"npx\",\n      \"args\": [\"-y\", \"@modelcontextprotocol\u002Fserver-postgres\"],\n      \"env\": {\n        \"DATABASE_URL\": \"postgresql:\u002F\u002F...\"\n      }\n    },\n    \"github\": {\n      \"command\": \"npx\",\n      \"args\": [\"-y\", \"@modelcontextprotocol\u002Fserver-github\"],\n      \"env\": {\n        \"GITHUB_TOKEN\": \"ghp_xxx\"\n      }\n    }\n  }\n}\n","json",[41,2469,2470,2475,2483,2490,2503,2521,2528,2538,2543,2548,2555,2565,2580,2586,2596,2600,2605,2610],{"__ignoreMap":43},[63,2471,2472],{"class":65,"line":66},[63,2473,2474],{"class":73},"{\n",[63,2476,2477,2480],{"class":65,"line":81},[63,2478,2479],{"class":84},"  \"mcpServers\"",[63,2481,2482],{"class":73},": {\n",[63,2484,2485,2488],{"class":65,"line":91},[63,2486,2487],{"class":84},"    \"postgres\"",[63,2489,2482],{"class":73},[63,2491,2492,2495,2497,2500],{"class":65,"line":99},[63,2493,2494],{"class":84},"      \"command\"",[63,2496,74],{"class":73},[63,2498,2499],{"class":77},"\"npx\"",[63,2501,2502],{"class":73},",\n",[63,2504,2505,2508,2510,2513,2515,2518],{"class":65,"line":114},[63,2506,2507],{"class":84},"      \"args\"",[63,2509,105],{"class":73},[63,2511,2512],{"class":77},"\"-y\"",[63,2514,257],{"class":73},[63,2516,2517],{"class":77},"\"@modelcontextprotocol\u002Fserver-postgres\"",[63,2519,2520],{"class":73},"],\n",[63,2522,2523,2526],{"class":65,"line":121},[63,2524,2525],{"class":84},"      \"env\"",[63,2527,2482],{"class":73},[63,2529,2530,2533,2535],{"class":65,"line":5},[63,2531,2532],{"class":84},"        \"DATABASE_URL\"",[63,2534,74],{"class":73},[63,2536,2537],{"class":77},"\"postgresql:\u002F\u002F...\"\n",[63,2539,2540],{"class":65,"line":136},[63,2541,2542],{"class":73},"      }\n",[63,2544,2545],{"class":65,"line":147},[63,2546,2547],{"class":73},"    },\n",[63,2549,2550,2553],{"class":65,"line":155},[63,2551,2552],{"class":84},"    \"github\"",[63,2554,2482],{"class":73},[63,2556,2557,2559,2561,2563],{"class":65,"line":166},[63,2558,2494],{"class":84},[63,2560,74],{"class":73},[63,2562,2499],{"class":77},[63,2564,2502],{"class":73},[63,2566,2567,2569,2571,2573,2575,2578],{"class":65,"line":176},[63,2568,2507],{"class":84},[63,2570,105],{"class":73},[63,2572,2512],{"class":77},[63,2574,257],{"class":73},[63,2576,2577],{"class":77},"\"@modelcontextprotocol\u002Fserver-github\"",[63,2579,2520],{"class":73},[63,2581,2582,2584],{"class":65,"line":184},[63,2583,2525],{"class":84},[63,2585,2482],{"class":73},[63,2587,2588,2591,2593],{"class":65,"line":198},[63,2589,2590],{"class":84},"        \"GITHUB_TOKEN\"",[63,2592,74],{"class":73},[63,2594,2595],{"class":77},"\"ghp_xxx\"\n",[63,2597,2598],{"class":65,"line":210},[63,2599,2542],{"class":73},[63,2601,2602],{"class":65,"line":230},[63,2603,2604],{"class":73},"    }\n",[63,2606,2607],{"class":65,"line":242},[63,2608,2609],{"class":73},"  }\n",[63,2611,2612],{"class":65,"line":270},[63,2613,2614],{"class":73},"}\n",[49,2616,2617],{},"配置后，Claude Code 可以直接查数据库、操作 GitHub。",[14,2619,2620],{"id":2620},"常用技巧",[1191,2622,2624],{"id":2623},"_1-用-claudemd-固化项目约定","1. 用 CLAUDE.md 固化项目约定",[49,2626,2627,2628,54],{},"在项目根目录创建 ",[41,2629,2630],{},"CLAUDE.md",[34,2632,2634],{"className":1913,"code":2633,"language":1915,"meta":43,"style":43},"# 项目约定\n\n- 用 pnpm 不用 npm\n- 测试用 vitest，跑 `pnpm test`\n- 提交前跑 `pnpm lint`\n- Vue 组件用 \u003Cscript setup>\n- 不要用 any，所有变量都要有类型\n",[41,2635,2636,2641,2645,2650,2655,2660,2665],{"__ignoreMap":43},[63,2637,2638],{"class":65,"line":66},[63,2639,2640],{},"# 项目约定\n",[63,2642,2643],{"class":65,"line":81},[63,2644,118],{"emptyLinePlaceholder":117},[63,2646,2647],{"class":65,"line":91},[63,2648,2649],{},"- 用 pnpm 不用 npm\n",[63,2651,2652],{"class":65,"line":99},[63,2653,2654],{},"- 测试用 vitest，跑 `pnpm test`\n",[63,2656,2657],{"class":65,"line":114},[63,2658,2659],{},"- 提交前跑 `pnpm lint`\n",[63,2661,2662],{"class":65,"line":121},[63,2663,2664],{},"- Vue 组件用 \u003Cscript setup>\n",[63,2666,2667],{"class":65,"line":5},[63,2668,2669],{},"- 不要用 any，所有变量都要有类型\n",[49,2671,2672],{},"Claude Code 每次启动会自动读取，不用每次重复说。",[1191,2674,2676],{"id":2675},"_2-用-compact-压缩上下文","2. 用 \u002Fcompact 压缩上下文",[49,2678,2679,2680,2683],{},"聊了很久后上下文会满，用 ",[41,2681,2682],{},"\u002Fcompact"," 压缩历史对话，保留关键信息。",[1191,2685,2687],{"id":2686},"_3-子-agent-并发","3. 子 Agent 并发",[49,2689,2690],{},"复杂任务可以让 Claude Code 开子 Agent 并行处理：",[34,2692,2695],{"className":2693,"code":2694,"language":39,"meta":43},[37],"> 用 3 个并行子任务：\n> 1. 修复 auth 模块的 bug\n> 2. 给 API 模块加测试\n> 3. 重构 utils 模块\n> 分别完成后汇总给我\n",[41,2696,2694],{"__ignoreMap":43},[14,2698,861],{"id":861},[1191,2700,2702],{"id":2701},"坑-1大项目首次索引慢","坑 1：大项目首次索引慢",[49,2704,2705],{},"10 万行以上的项目，首次启动会花 1-2 分钟扫描。后续有缓存会快。",[1191,2707,2709],{"id":2708},"坑-2会主动改你不想改的文件","坑 2：会主动改你不想改的文件",[49,2711,2712,2713,2716],{},"Claude Code 有时会\"顺手\"改一些你没要求的文件（比如格式化）。用 ",[41,2714,2715],{},"--allowedTools"," 限制它只能用特定工具。",[1191,2718,2720],{"id":2719},"坑-3国内网络","坑 3：国内网络",[49,2722,2723],{},"Anthropic API 在国内不稳定。必须配代理，否则会频繁超时。",[1191,2725,2727],{"id":2726},"坑-4token-消耗快","坑 4：Token 消耗快",[49,2729,2730],{},"Sonnet 4 做大型重构时，一次任务可能消耗 50 万+ token。用 Max 订阅（$200\u002F月）比 API 按量付费划算。",[908,2732,2733],{},"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 .sJ8bj, html code.shiki .sJ8bj{--shiki-default:#6A737D;--shiki-dark:#6A737D}html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .szBVR, html code.shiki .szBVR{--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}",{"title":43,"searchDepth":91,"depth":91,"links":2735},[2736,2737,2738,2741,2742,2743,2744,2745,2746,2751],{"id":16,"depth":81,"text":16},{"id":2073,"depth":81,"text":2074},{"id":2206,"depth":81,"text":2207,"children":2739},[2740],{"id":2251,"depth":91,"text":2251},{"id":2322,"depth":81,"text":2323},{"id":2372,"depth":81,"text":2373},{"id":2388,"depth":81,"text":2389},{"id":2427,"depth":81,"text":2428},{"id":2449,"depth":81,"text":2450},{"id":2620,"depth":81,"text":2620,"children":2747},[2748,2749,2750],{"id":2623,"depth":91,"text":2624},{"id":2675,"depth":91,"text":2676},{"id":2686,"depth":91,"text":2687},{"id":861,"depth":81,"text":861,"children":2752},[2753,2754,2755,2756],{"id":2701,"depth":91,"text":2702},{"id":2708,"depth":91,"text":2709},{"id":2719,"depth":91,"text":2720},{"id":2726,"depth":91,"text":2727},"onboarding","\u002Fog\u002Fplaybook\u002Fclaude-code-getting-started.png","Anthropic 官方 CLI 工具 Claude Code 30 分钟上手——安装配置、项目理解、多文件修改、git 工作流、MCP 集成、子 Agent 并发，含真实项目踩坑记录。",{},"\u002Fplaybook\u002Fonboarding\u002Fclaude-code-getting-started",[930,2041,2763],"coding\u002Fcli\u002Faider",{"title":2052,"description":2759},"playbook\u002Fonboarding\u002Fclaude-code-getting-started",[938,2101,2767,2768],"AI 编程","Anthropic","iY5IsM7_Dx24z0nrqr2srrkmMB01rtDVKpKWe9B0Fz0",{"id":2771,"title":2772,"body":2773,"category":2757,"cover":3481,"description":3482,"extension":925,"meta":3483,"navigation":117,"path":3484,"published":928,"relatedTools":3485,"seo":3489,"stem":3490,"tags":3491,"updated":928,"__hash__":3494},"playbook\u002Fplaybook\u002Fonboarding\u002Fcursor-mcp-database.md","Cursor + MCP 让 AI 直接操作数据库：从零到生产",{"type":11,"value":2774,"toc":3464},[2775,2777,2788,2792,2799,2802,2809,2813,2816,2840,2843,2919,2922,2926,2929,2935,3036,3040,3117,3123,3126,3129,3214,3218,3222,3225,3231,3234,3252,3256,3262,3264,3282,3286,3292,3294,3311,3314,3384,3390,3392,3461],[14,2776,16],{"id":16},[18,2778,2779,2782,2785],{},[21,2780,2781],{},"开发时频繁需要查表结构、跑 SQL 验证",[21,2783,2784],{},"AI 生成代码时需要知道数据库 schema",[21,2786,2787],{},"想让 AI 帮写数据库迁移文件",[14,2789,2791],{"id":2790},"为什么用-mcp-而不是复制粘贴","为什么用 MCP 而不是复制粘贴",[49,2793,2794,2795,2798],{},"传统做法：手动跑 ",[41,2796,2797],{},"\\d table_name"," → 复制结果 → 粘给 Cursor → AI 生成 SQL → 手动执行验证。",[49,2800,2801],{},"MCP 做法：Cursor 直接连数据库 → AI 自己查 schema → 生成 SQL → 自己执行验证 → 返回结果。",[49,2803,2804,2805,2808],{},"省的是",[868,2806,2807],{},"中间的复制粘贴往返","。在复杂查询调试时，这个往返可能 5-10 次。",[14,2810,2812],{"id":2811},"第一步装-mcp-postgresql-server","第一步：装 MCP PostgreSQL Server",[49,2814,2815],{},"用 Smithery 一行装好：",[34,2817,2819],{"className":1807,"code":2818,"language":1809,"meta":43,"style":43},"npx @smithery\u002Fcli install @modelcontextprotocol\u002Fserver-postgres --client cursor\n",[41,2820,2821],{"__ignoreMap":43},[63,2822,2823,2826,2829,2831,2834,2837],{"class":65,"line":66},[63,2824,2825],{"class":1106},"npx",[63,2827,2828],{"class":77}," @smithery\u002Fcli",[63,2830,2225],{"class":77},[63,2832,2833],{"class":77}," @modelcontextprotocol\u002Fserver-postgres",[63,2835,2836],{"class":84}," --client",[63,2838,2839],{"class":77}," cursor\n",[49,2841,2842],{},"或手动配 Cursor Settings → MCP → Add Server：",[34,2844,2846],{"className":2465,"code":2845,"language":2467,"meta":43,"style":43},"{\n  \"mcpServers\": {\n    \"postgres\": {\n      \"command\": \"npx\",\n      \"args\": [\"-y\", \"@modelcontextprotocol\u002Fserver-postgres\"],\n      \"env\": {\n        \"DATABASE_URL\": \"postgresql:\u002F\u002Fuser:pass@localhost:5432\u002Fmydb\"\n      }\n    }\n  }\n}\n",[41,2847,2848,2852,2858,2864,2874,2888,2894,2903,2907,2911,2915],{"__ignoreMap":43},[63,2849,2850],{"class":65,"line":66},[63,2851,2474],{"class":73},[63,2853,2854,2856],{"class":65,"line":81},[63,2855,2479],{"class":84},[63,2857,2482],{"class":73},[63,2859,2860,2862],{"class":65,"line":91},[63,2861,2487],{"class":84},[63,2863,2482],{"class":73},[63,2865,2866,2868,2870,2872],{"class":65,"line":99},[63,2867,2494],{"class":84},[63,2869,74],{"class":73},[63,2871,2499],{"class":77},[63,2873,2502],{"class":73},[63,2875,2876,2878,2880,2882,2884,2886],{"class":65,"line":114},[63,2877,2507],{"class":84},[63,2879,105],{"class":73},[63,2881,2512],{"class":77},[63,2883,257],{"class":73},[63,2885,2517],{"class":77},[63,2887,2520],{"class":73},[63,2889,2890,2892],{"class":65,"line":121},[63,2891,2525],{"class":84},[63,2893,2482],{"class":73},[63,2895,2896,2898,2900],{"class":65,"line":5},[63,2897,2532],{"class":84},[63,2899,74],{"class":73},[63,2901,2902],{"class":77},"\"postgresql:\u002F\u002Fuser:pass@localhost:5432\u002Fmydb\"\n",[63,2904,2905],{"class":65,"line":136},[63,2906,2542],{"class":73},[63,2908,2909],{"class":65,"line":147},[63,2910,2604],{"class":73},[63,2912,2913],{"class":65,"line":155},[63,2914,2609],{"class":73},[63,2916,2917],{"class":65,"line":166},[63,2918,2614],{"class":73},[49,2920,2921],{},"重启 Cursor，Agent 模式现在能查数据库了。",[14,2923,2925],{"id":2924},"第二步安全配置重要","第二步：安全配置（重要）",[1191,2927,2928],{"id":2928},"用只读账号",[49,2930,2931,2934],{},[868,2932,2933],{},"绝对不要用超级用户账号连 MCP。"," 创建只读账号：",[34,2936,2938],{"className":1047,"code":2937,"language":1049,"meta":43,"style":43},"CREATE ROLE mcp_readonly WITH LOGIN PASSWORD 'xxx';\nGRANT USAGE ON SCHEMA public TO mcp_readonly;\nGRANT SELECT ON ALL TABLES IN SCHEMA public TO mcp_readonly;\nALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT SELECT ON TABLES TO mcp_readonly;\n",[41,2939,2940,2964,2985,3007],{"__ignoreMap":43},[63,2941,2942,2944,2947,2950,2953,2956,2959,2962],{"class":65,"line":66},[63,2943,1100],{"class":427},[63,2945,2946],{"class":427}," ROLE",[63,2948,2949],{"class":73}," mcp_readonly ",[63,2951,2952],{"class":427},"WITH",[63,2954,2955],{"class":427}," LOGIN",[63,2957,2958],{"class":427}," PASSWORD",[63,2960,2961],{"class":77}," 'xxx'",[63,2963,1125],{"class":73},[63,2965,2966,2969,2972,2974,2977,2980,2982],{"class":65,"line":81},[63,2967,2968],{"class":427},"GRANT",[63,2970,2971],{"class":73}," USAGE ",[63,2973,1793],{"class":427},[63,2975,2976],{"class":427}," SCHEMA",[63,2978,2979],{"class":73}," public ",[63,2981,1740],{"class":427},[63,2983,2984],{"class":73}," mcp_readonly;\n",[63,2986,2987,2989,2992,2994,2997,2999,3001,3003,3005],{"class":65,"line":91},[63,2988,2968],{"class":427},[63,2990,2991],{"class":427}," SELECT",[63,2993,1110],{"class":427},[63,2995,2996],{"class":73}," ALL TABLES ",[63,2998,1353],{"class":427},[63,3000,2976],{"class":427},[63,3002,2979],{"class":73},[63,3004,1740],{"class":427},[63,3006,2984],{"class":73},[63,3008,3009,3011,3014,3017,3019,3021,3023,3025,3027,3029,3032,3034],{"class":65,"line":99},[63,3010,1071],{"class":427},[63,3012,3013],{"class":427}," DEFAULT",[63,3015,3016],{"class":73}," PRIVILEGES ",[63,3018,1353],{"class":427},[63,3020,2976],{"class":427},[63,3022,2979],{"class":73},[63,3024,2968],{"class":427},[63,3026,2991],{"class":427},[63,3028,1110],{"class":427},[63,3030,3031],{"class":73}," TABLES ",[63,3033,1740],{"class":427},[63,3035,2984],{"class":73},[1191,3037,3039],{"id":3038},"开发生产隔离","开发\u002F生产隔离",[34,3041,3043],{"className":2465,"code":3042,"language":2467,"meta":43,"style":43},"{\n  \"mcpServers\": {\n    \"postgres-dev\": {\n      \"command\": \"npx\",\n      \"args\": [\"-y\", \"@modelcontextprotocol\u002Fserver-postgres\"],\n      \"env\": {\n        \"DATABASE_URL\": \"postgresql:\u002F\u002Fmcp_readonly:xxx@localhost:5432\u002Fmydb_dev\"\n      }\n    }\n  }\n}\n",[41,3044,3045,3049,3055,3062,3072,3086,3092,3101,3105,3109,3113],{"__ignoreMap":43},[63,3046,3047],{"class":65,"line":66},[63,3048,2474],{"class":73},[63,3050,3051,3053],{"class":65,"line":81},[63,3052,2479],{"class":84},[63,3054,2482],{"class":73},[63,3056,3057,3060],{"class":65,"line":91},[63,3058,3059],{"class":84},"    \"postgres-dev\"",[63,3061,2482],{"class":73},[63,3063,3064,3066,3068,3070],{"class":65,"line":99},[63,3065,2494],{"class":84},[63,3067,74],{"class":73},[63,3069,2499],{"class":77},[63,3071,2502],{"class":73},[63,3073,3074,3076,3078,3080,3082,3084],{"class":65,"line":114},[63,3075,2507],{"class":84},[63,3077,105],{"class":73},[63,3079,2512],{"class":77},[63,3081,257],{"class":73},[63,3083,2517],{"class":77},[63,3085,2520],{"class":73},[63,3087,3088,3090],{"class":65,"line":121},[63,3089,2525],{"class":84},[63,3091,2482],{"class":73},[63,3093,3094,3096,3098],{"class":65,"line":5},[63,3095,2532],{"class":84},[63,3097,74],{"class":73},[63,3099,3100],{"class":77},"\"postgresql:\u002F\u002Fmcp_readonly:xxx@localhost:5432\u002Fmydb_dev\"\n",[63,3102,3103],{"class":65,"line":136},[63,3104,2542],{"class":73},[63,3106,3107],{"class":65,"line":147},[63,3108,2604],{"class":73},[63,3110,3111],{"class":65,"line":155},[63,3112,2609],{"class":73},[63,3114,3115],{"class":65,"line":166},[63,3116,2614],{"class":73},[49,3118,3119,3122],{},[868,3120,3121],{},"只在 dev 环境配 MCP","。生产数据库永远不连 MCP。",[1191,3124,3125],{"id":3125},"限制可查的表",[49,3127,3128],{},"如果有些表（用户密码、token）不想让 AI 看到，用视图：",[34,3130,3132],{"className":1047,"code":3131,"language":1049,"meta":43,"style":43},"CREATE VIEW public_safe.users_safe AS\n  SELECT id, username, created_at FROM public.users;\n\nREVOKE SELECT ON public.users FROM mcp_readonly;\nGRANT SELECT ON public_safe.users_safe TO mcp_readonly;\n",[41,3133,3134,3150,3170,3174,3194],{"__ignoreMap":43},[63,3135,3136,3138,3141,3144,3147],{"class":65,"line":66},[63,3137,1100],{"class":427},[63,3139,3140],{"class":427}," VIEW",[63,3142,3143],{"class":1106}," public_safe",[63,3145,3146],{"class":73},".users_safe ",[63,3148,3149],{"class":427},"AS\n",[63,3151,3152,3155,3158,3160,3163,3165,3168],{"class":65,"line":81},[63,3153,3154],{"class":427},"  SELECT",[63,3156,3157],{"class":73}," id, username, created_at ",[63,3159,1366],{"class":427},[63,3161,3162],{"class":84}," public",[63,3164,1430],{"class":73},[63,3166,3167],{"class":84},"users",[63,3169,1125],{"class":73},[63,3171,3172],{"class":65,"line":91},[63,3173,118],{"emptyLinePlaceholder":117},[63,3175,3176,3179,3181,3183,3185,3187,3189,3192],{"class":65,"line":99},[63,3177,3178],{"class":427},"REVOKE",[63,3180,2991],{"class":427},[63,3182,1110],{"class":427},[63,3184,3162],{"class":84},[63,3186,1430],{"class":73},[63,3188,3167],{"class":84},[63,3190,3191],{"class":427}," FROM",[63,3193,2984],{"class":73},[63,3195,3196,3198,3200,3202,3204,3206,3209,3212],{"class":65,"line":114},[63,3197,2968],{"class":427},[63,3199,2991],{"class":427},[63,3201,1110],{"class":427},[63,3203,3143],{"class":84},[63,3205,1430],{"class":73},[63,3207,3208],{"class":84},"users_safe",[63,3210,3211],{"class":427}," TO",[63,3213,2984],{"class":73},[14,3215,3217],{"id":3216},"第三步实战用法","第三步：实战用法",[1191,3219,3221],{"id":3220},"场景一查-schema-生成代码","场景一：查 schema 生成代码",[49,3223,3224],{},"在 Cursor Agent 模式输入：",[34,3226,3229],{"className":3227,"code":3228,"language":39},[37],"帮我写一个查询用户订单的 API，需要分页\n",[41,3230,3228],{"__ignoreMap":43},[49,3232,3233],{},"Cursor 会：",[863,3235,3236,3243,3249],{},[21,3237,3238,3239,3242],{},"调 MCP ",[41,3240,3241],{},"list_tables"," 看有哪些表",[21,3244,3238,3245,3248],{},[41,3246,3247],{},"describe_table"," 看 orders 和 users 表结构",[21,3250,3251],{},"生成 JOIN 查询 + 分页代码",[1191,3253,3255],{"id":3254},"场景二调试-sql","场景二：调试 SQL",[34,3257,3260],{"className":3258,"code":3259,"language":39},[37],"这个查询很慢，帮我优化\nSELECT * FROM orders o JOIN users u ON o.user_id = u.id WHERE o.status = 'pending'\n",[41,3261,3259],{"__ignoreMap":43},[49,3263,3233],{},[863,3265,3266,3273,3276,3279],{},[21,3267,3268,3269,3272],{},"跑 ",[41,3270,3271],{},"EXPLAIN ANALYZE"," 看执行计划",[21,3274,3275],{},"发现全表扫描",[21,3277,3278],{},"建议加索引",[21,3280,3281],{},"生成 migration 文件",[1191,3283,3285],{"id":3284},"场景三生成迁移","场景三：生成迁移",[34,3287,3290],{"className":3288,"code":3289,"language":39},[37],"给 orders 表加一个 shipping_address 字段，类型 jsonb，可空\n",[41,3291,3289],{"__ignoreMap":43},[49,3293,3233],{},[863,3295,3296,3299,3306,3309],{},[21,3297,3298],{},"查当前 orders 表结构",[21,3300,3301,3302,3305],{},"生成 ",[41,3303,3304],{},"ALTER TABLE"," SQL",[21,3307,3308],{},"生成 Drizzle\u002FPrisma migration 文件",[21,3310,1030],{},[14,3312,3313],{"id":3313},"权限边界",[697,3315,3316,3328],{},[700,3317,3318],{},[703,3319,3320,3322,3325],{},[706,3321,708],{},[706,3323,3324],{},"只读 MCP",[706,3326,3327],{},"可写 MCP",[716,3329,3330,3339,3348,3357,3366,3376],{},[703,3331,3332,3335,3337],{},[721,3333,3334],{},"查表结构",[721,3336,726],{},[721,3338,726],{},[703,3340,3341,3344,3346],{},[721,3342,3343],{},"SELECT 查询",[721,3345,726],{},[721,3347,726],{},[703,3349,3350,3353,3355],{},[721,3351,3352],{},"EXPLAIN",[721,3354,726],{},[721,3356,726],{},[703,3358,3359,3362,3364],{},[721,3360,3361],{},"INSERT\u002FUPDATE\u002FDELETE",[721,3363,2161],{},[721,3365,726],{},[703,3367,3368,3371,3373],{},[721,3369,3370],{},"CREATE\u002FDROP TABLE",[721,3372,2161],{},[721,3374,3375],{},"⚠️ 需额外授权",[703,3377,3378,3380,3382],{},[721,3379,3304],{},[721,3381,2161],{},[721,3383,3375],{},[49,3385,3386,3389],{},[868,3387,3388],{},"建议","：日常用只读 MCP，需要写操作时切到可写 MCP 并加确认步骤。",[14,3391,861],{"id":861},[863,3393,3394,3400,3439,3445],{},[21,3395,3396,3399],{},[868,3397,3398],{},"MCP Server 连接池","：默认无连接池，大量查询会打满 PG 连接。用 PgBouncer 做连接池。",[21,3401,3402,3405,3406,3409,3410,3413,3414],{},[868,3403,3404],{},"大表 SELECT","：AI 可能跑 ",[41,3407,3408],{},"SELECT * FROM huge_table","。设 ",[41,3411,3412],{},"statement_timeout"," 防止卡死：\n",[34,3415,3417],{"className":1047,"code":3416,"language":1049,"meta":43,"style":43},"ALTER ROLE mcp_readonly SET statement_timeout = '10s';\n",[41,3418,3419],{"__ignoreMap":43},[63,3420,3421,3423,3425,3427,3429,3432,3434,3437],{"class":65,"line":66},[63,3422,1071],{"class":427},[63,3424,2946],{"class":427},[63,3426,2949],{"class":73},[63,3428,1337],{"class":427},[63,3430,3431],{"class":73}," statement_timeout ",[63,3433,1296],{"class":427},[63,3435,3436],{"class":77}," '10s'",[63,3438,1125],{"class":73},[21,3440,3441,3444],{},[868,3442,3443],{},"Cursor MCP 不支持事务","：每个 SQL 是独立执行，不能 BEGIN\u002FCOMMIT。多步操作用存储过程。",[21,3446,3447,54,3450,3453,3454,3457,3458,906],{},[868,3448,3449],{},"DATABASE_URL 泄露",[41,3451,3452],{},".cursor\u002Fmcp.json"," 可能被 git 提交。加到 ",[41,3455,3456],{},".gitignore","，用 ",[41,3459,3460],{},".cursor\u002Fmcp.local.json",[908,3462,3463],{},"html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}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 .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .szBVR, html code.shiki .szBVR{--shiki-default:#D73A49;--shiki-dark:#F97583}",{"title":43,"searchDepth":91,"depth":91,"links":3465},[3466,3467,3468,3469,3474,3479,3480],{"id":16,"depth":81,"text":16},{"id":2790,"depth":81,"text":2791},{"id":2811,"depth":81,"text":2812},{"id":2924,"depth":81,"text":2925,"children":3470},[3471,3472,3473],{"id":2928,"depth":91,"text":2928},{"id":3038,"depth":91,"text":3039},{"id":3125,"depth":91,"text":3125},{"id":3216,"depth":81,"text":3217,"children":3475},[3476,3477,3478],{"id":3220,"depth":91,"text":3221},{"id":3254,"depth":91,"text":3255},{"id":3284,"depth":91,"text":3285},{"id":3313,"depth":81,"text":3313},{"id":861,"depth":81,"text":861},"\u002Fog\u002Fplaybook\u002Fcursor-mcp-database.png","用 MCP 协议让 Cursor \u002F Claude Code 直接读写 PostgreSQL——AI 能查表结构、跑 SQL、生成迁移文件。含安全配置、权限边界和踩坑记录。",{},"\u002Fplaybook\u002Fonboarding\u002Fcursor-mcp-database",[3486,3487,3488,2041],"agent\u002Fprotocol\u002Fsmithery","agent\u002Fprotocol\u002Fcomposio","agent\u002Fprotocol\u002Fmcp-toolbox",{"title":2772,"description":3482},"playbook\u002Fonboarding\u002Fcursor-mcp-database",[3492,2088,3493,2045],"MCP","PostgreSQL","Xz0aDqhIFWiE0v5sBvsHZqBKQn_S_xNSzHCeNesIXxA",{"id":3496,"title":3497,"body":3498,"category":2757,"cover":4464,"description":4465,"extension":925,"meta":4466,"navigation":117,"path":4467,"published":928,"relatedTools":4468,"seo":4469,"stem":4470,"tags":4471,"updated":928,"__hash__":4473},"playbook\u002Fplaybook\u002Fonboarding\u002Fcursor-mcp-deep-integration.md","Cursor + MCP 深度集成：让 AI IDE 连接一切",{"type":11,"value":3499,"toc":4435},[3500,3502,3513,3517,3520,3531,3537,3541,3551,3630,3633,3637,3641,3644,3706,3709,3725,3729,3732,3808,3810,3824,3828,3831,3895,3899,3902,3981,3984,3988,3991,4050,4053,4057,4060,4119,4122,4126,4130,4136,4139,4158,4162,4168,4170,4190,4194,4200,4202,4222,4225,4228,4231,4235,4250,4253,4274,4308,4312,4320,4323,4327,4330,4342,4345,4348,4356,4360,4427,4432],[14,3501,16],{"id":16},[18,3503,3504,3507,3510],{},[21,3505,3506],{},"已在用 Cursor，想让 AI 能直接查数据库 \u002F 操作 GitHub",[21,3508,3509],{},"想让 AI 在写代码时能访问外部 API（Slack、Jira、Linear）",[21,3511,3512],{},"想构建\"AI 能看能做\"的开发环境，不只是\"AI 建议、人执行\"",[14,3514,3516],{"id":3515},"mcp-在-cursor-中的定位","MCP 在 Cursor 中的定位",[49,3518,3519],{},"Cursor 0.42+ 原生支持 MCP。配置 MCP Server 后：",[18,3521,3522,3525,3528],{},[21,3523,3524],{},"AI Agent 模式下可以调用 MCP 工具",[21,3526,3527],{},"AI 能看到工具的返回结果，基于结果继续推理",[21,3529,3530],{},"不需要离开 IDE 去查数据库 \u002F 看 GitHub Issue",[49,3532,3533,3536],{},[868,3534,3535],{},"本质","：把\"复制粘贴往返\"变成\"AI 直接操作\"。",[14,3538,3540],{"id":3539},"第一步理解-mcp-配置","第一步：理解 MCP 配置",[49,3542,3543,3544,3547,3548,3550],{},"Cursor 的 MCP 配置在 ",[41,3545,3546],{},"~\u002F.cursor\u002Fmcp.json","（全局）或项目根目录 ",[41,3549,3452],{},"（项目级）。",[34,3552,3554],{"className":2465,"code":3553,"language":2467,"meta":43,"style":43},"{\n  \"mcpServers\": {\n    \"server-name\": {\n      \"command\": \"npx\",\n      \"args\": [\"-y\", \"@mcp\u002Fserver-package\"],\n      \"env\": {\n        \"API_KEY\": \"xxx\"\n      }\n    }\n  }\n}\n",[41,3555,3556,3560,3566,3573,3583,3598,3604,3614,3618,3622,3626],{"__ignoreMap":43},[63,3557,3558],{"class":65,"line":66},[63,3559,2474],{"class":73},[63,3561,3562,3564],{"class":65,"line":81},[63,3563,2479],{"class":84},[63,3565,2482],{"class":73},[63,3567,3568,3571],{"class":65,"line":91},[63,3569,3570],{"class":84},"    \"server-name\"",[63,3572,2482],{"class":73},[63,3574,3575,3577,3579,3581],{"class":65,"line":99},[63,3576,2494],{"class":84},[63,3578,74],{"class":73},[63,3580,2499],{"class":77},[63,3582,2502],{"class":73},[63,3584,3585,3587,3589,3591,3593,3596],{"class":65,"line":114},[63,3586,2507],{"class":84},[63,3588,105],{"class":73},[63,3590,2512],{"class":77},[63,3592,257],{"class":73},[63,3594,3595],{"class":77},"\"@mcp\u002Fserver-package\"",[63,3597,2520],{"class":73},[63,3599,3600,3602],{"class":65,"line":121},[63,3601,2525],{"class":84},[63,3603,2482],{"class":73},[63,3605,3606,3609,3611],{"class":65,"line":5},[63,3607,3608],{"class":84},"        \"API_KEY\"",[63,3610,74],{"class":73},[63,3612,3613],{"class":77},"\"xxx\"\n",[63,3615,3616],{"class":65,"line":136},[63,3617,2542],{"class":73},[63,3619,3620],{"class":65,"line":147},[63,3621,2604],{"class":73},[63,3623,3624],{"class":65,"line":155},[63,3625,2609],{"class":73},[63,3627,3628],{"class":65,"line":166},[63,3629,2614],{"class":73},[49,3631,3632],{},"配置后重启 Cursor，在 Agent 模式下 AI 自动可用这些工具。",[14,3634,3636],{"id":3635},"第二步6-个常用-mcp-server-配置","第二步：6 个常用 MCP Server 配置",[1191,3638,3640],{"id":3639},"_1-postgresql最常用","1. PostgreSQL（最常用）",[49,3642,3643],{},"让 AI 能查表结构、跑 SQL、分析数据。",[34,3645,3647],{"className":2465,"code":3646,"language":2467,"meta":43,"style":43},"{\n  \"mcpServers\": {\n    \"postgres\": {\n      \"command\": \"npx\",\n      \"args\": [\"-y\", \"@modelcontextprotocol\u002Fserver-postgres\", \"postgresql:\u002F\u002Fuser:pass@localhost:5432\u002Fmydb\"]\n    }\n  }\n}\n",[41,3648,3649,3653,3659,3665,3675,3694,3698,3702],{"__ignoreMap":43},[63,3650,3651],{"class":65,"line":66},[63,3652,2474],{"class":73},[63,3654,3655,3657],{"class":65,"line":81},[63,3656,2479],{"class":84},[63,3658,2482],{"class":73},[63,3660,3661,3663],{"class":65,"line":91},[63,3662,2487],{"class":84},[63,3664,2482],{"class":73},[63,3666,3667,3669,3671,3673],{"class":65,"line":99},[63,3668,2494],{"class":84},[63,3670,74],{"class":73},[63,3672,2499],{"class":77},[63,3674,2502],{"class":73},[63,3676,3677,3679,3681,3683,3685,3687,3689,3692],{"class":65,"line":114},[63,3678,2507],{"class":84},[63,3680,105],{"class":73},[63,3682,2512],{"class":77},[63,3684,257],{"class":73},[63,3686,2517],{"class":77},[63,3688,257],{"class":73},[63,3690,3691],{"class":77},"\"postgresql:\u002F\u002Fuser:pass@localhost:5432\u002Fmydb\"",[63,3693,111],{"class":73},[63,3695,3696],{"class":65,"line":121},[63,3697,2604],{"class":73},[63,3699,3700],{"class":65,"line":5},[63,3701,2609],{"class":73},[63,3703,3704],{"class":65,"line":136},[63,3705,2614],{"class":73},[49,3707,3708],{},"AI 能力：",[18,3710,3711,3716,3719,3722],{},[21,3712,3713,3715],{},[41,3714,2797],{}," 查表结构",[21,3717,3718],{},"跑 SELECT 查询（只读）",[21,3720,3721],{},"分析数据分布",[21,3723,3724],{},"生成数据库迁移建议",[1191,3726,3728],{"id":3727},"_2-github","2. GitHub",[49,3730,3731],{},"让 AI 能看 Issue \u002F PR \u002F 代码评论。",[34,3733,3735],{"className":2465,"code":3734,"language":2467,"meta":43,"style":43},"{\n  \"mcpServers\": {\n    \"github\": {\n      \"command\": \"npx\",\n      \"args\": [\"-y\", \"@modelcontextprotocol\u002Fserver-github\"],\n      \"env\": {\n        \"GITHUB_PERSONAL_ACCESS_TOKEN\": \"ghp_xxx\"\n      }\n    }\n  }\n}\n",[41,3736,3737,3741,3747,3753,3763,3777,3783,3792,3796,3800,3804],{"__ignoreMap":43},[63,3738,3739],{"class":65,"line":66},[63,3740,2474],{"class":73},[63,3742,3743,3745],{"class":65,"line":81},[63,3744,2479],{"class":84},[63,3746,2482],{"class":73},[63,3748,3749,3751],{"class":65,"line":91},[63,3750,2552],{"class":84},[63,3752,2482],{"class":73},[63,3754,3755,3757,3759,3761],{"class":65,"line":99},[63,3756,2494],{"class":84},[63,3758,74],{"class":73},[63,3760,2499],{"class":77},[63,3762,2502],{"class":73},[63,3764,3765,3767,3769,3771,3773,3775],{"class":65,"line":114},[63,3766,2507],{"class":84},[63,3768,105],{"class":73},[63,3770,2512],{"class":77},[63,3772,257],{"class":73},[63,3774,2577],{"class":77},[63,3776,2520],{"class":73},[63,3778,3779,3781],{"class":65,"line":121},[63,3780,2525],{"class":84},[63,3782,2482],{"class":73},[63,3784,3785,3788,3790],{"class":65,"line":5},[63,3786,3787],{"class":84},"        \"GITHUB_PERSONAL_ACCESS_TOKEN\"",[63,3789,74],{"class":73},[63,3791,2595],{"class":77},[63,3793,3794],{"class":65,"line":136},[63,3795,2542],{"class":73},[63,3797,3798],{"class":65,"line":147},[63,3799,2604],{"class":73},[63,3801,3802],{"class":65,"line":155},[63,3803,2609],{"class":73},[63,3805,3806],{"class":65,"line":166},[63,3807,2614],{"class":73},[49,3809,3708],{},[18,3811,3812,3815,3818,3821],{},[21,3813,3814],{},"查看某个 Issue 的讨论",[21,3816,3817],{},"列出分配给你的 PR",[21,3819,3820],{},"根据 Issue 描述直接开始修 bug",[21,3822,3823],{},"创建 PR 描述",[1191,3825,3827],{"id":3826},"_3-文件系统增强版","3. 文件系统（增强版）",[49,3829,3830],{},"Cursor 自带文件读写，但 MCP 文件系统 Server 支持更复杂的操作（搜索、批量重命名）。",[34,3832,3834],{"className":2465,"code":3833,"language":2467,"meta":43,"style":43},"{\n  \"mcpServers\": {\n    \"filesystem\": {\n      \"command\": \"npx\",\n      \"args\": [\"-y\", \"@modelcontextprotocol\u002Fserver-filesystem\", \"\u002FUsers\u002Fme\u002Fprojects\"]\n    }\n  }\n}\n",[41,3835,3836,3840,3846,3853,3863,3883,3887,3891],{"__ignoreMap":43},[63,3837,3838],{"class":65,"line":66},[63,3839,2474],{"class":73},[63,3841,3842,3844],{"class":65,"line":81},[63,3843,2479],{"class":84},[63,3845,2482],{"class":73},[63,3847,3848,3851],{"class":65,"line":91},[63,3849,3850],{"class":84},"    \"filesystem\"",[63,3852,2482],{"class":73},[63,3854,3855,3857,3859,3861],{"class":65,"line":99},[63,3856,2494],{"class":84},[63,3858,74],{"class":73},[63,3860,2499],{"class":77},[63,3862,2502],{"class":73},[63,3864,3865,3867,3869,3871,3873,3876,3878,3881],{"class":65,"line":114},[63,3866,2507],{"class":84},[63,3868,105],{"class":73},[63,3870,2512],{"class":77},[63,3872,257],{"class":73},[63,3874,3875],{"class":77},"\"@modelcontextprotocol\u002Fserver-filesystem\"",[63,3877,257],{"class":73},[63,3879,3880],{"class":77},"\"\u002FUsers\u002Fme\u002Fprojects\"",[63,3882,111],{"class":73},[63,3884,3885],{"class":65,"line":121},[63,3886,2604],{"class":73},[63,3888,3889],{"class":65,"line":5},[63,3890,2609],{"class":73},[63,3892,3893],{"class":65,"line":136},[63,3894,2614],{"class":73},[1191,3896,3898],{"id":3897},"_4-slack","4. Slack",[49,3900,3901],{},"让 AI 能看频道消息、搜索历史讨论。",[34,3903,3905],{"className":2465,"code":3904,"language":2467,"meta":43,"style":43},"{\n  \"mcpServers\": {\n    \"slack\": {\n      \"command\": \"npx\",\n      \"args\": [\"-y\", \"@modelcontextprotocol\u002Fserver-slack\"],\n      \"env\": {\n        \"SLACK_BOT_TOKEN\": \"xoxb-xxx\"\n      }\n    }\n  }\n}\n",[41,3906,3907,3911,3917,3924,3934,3949,3955,3965,3969,3973,3977],{"__ignoreMap":43},[63,3908,3909],{"class":65,"line":66},[63,3910,2474],{"class":73},[63,3912,3913,3915],{"class":65,"line":81},[63,3914,2479],{"class":84},[63,3916,2482],{"class":73},[63,3918,3919,3922],{"class":65,"line":91},[63,3920,3921],{"class":84},"    \"slack\"",[63,3923,2482],{"class":73},[63,3925,3926,3928,3930,3932],{"class":65,"line":99},[63,3927,2494],{"class":84},[63,3929,74],{"class":73},[63,3931,2499],{"class":77},[63,3933,2502],{"class":73},[63,3935,3936,3938,3940,3942,3944,3947],{"class":65,"line":114},[63,3937,2507],{"class":84},[63,3939,105],{"class":73},[63,3941,2512],{"class":77},[63,3943,257],{"class":73},[63,3945,3946],{"class":77},"\"@modelcontextprotocol\u002Fserver-slack\"",[63,3948,2520],{"class":73},[63,3950,3951,3953],{"class":65,"line":121},[63,3952,2525],{"class":84},[63,3954,2482],{"class":73},[63,3956,3957,3960,3962],{"class":65,"line":5},[63,3958,3959],{"class":84},"        \"SLACK_BOT_TOKEN\"",[63,3961,74],{"class":73},[63,3963,3964],{"class":77},"\"xoxb-xxx\"\n",[63,3966,3967],{"class":65,"line":136},[63,3968,2542],{"class":73},[63,3970,3971],{"class":65,"line":147},[63,3972,2604],{"class":73},[63,3974,3975],{"class":65,"line":155},[63,3976,2609],{"class":73},[63,3978,3979],{"class":65,"line":166},[63,3980,2614],{"class":73},[49,3982,3983],{},"场景：开发时遇到问题，AI 可以搜 Slack 历史看团队之前是否讨论过。",[1191,3985,3987],{"id":3986},"_5-puppeteer浏览器","5. Puppeteer（浏览器）",[49,3989,3990],{},"让 AI 能打开网页、截图、提取内容。",[34,3992,3994],{"className":2465,"code":3993,"language":2467,"meta":43,"style":43},"{\n  \"mcpServers\": {\n    \"puppeteer\": {\n      \"command\": \"npx\",\n      \"args\": [\"-y\", \"@modelcontextprotocol\u002Fserver-puppeteer\"]\n    }\n  }\n}\n",[41,3995,3996,4000,4006,4013,4023,4038,4042,4046],{"__ignoreMap":43},[63,3997,3998],{"class":65,"line":66},[63,3999,2474],{"class":73},[63,4001,4002,4004],{"class":65,"line":81},[63,4003,2479],{"class":84},[63,4005,2482],{"class":73},[63,4007,4008,4011],{"class":65,"line":91},[63,4009,4010],{"class":84},"    \"puppeteer\"",[63,4012,2482],{"class":73},[63,4014,4015,4017,4019,4021],{"class":65,"line":99},[63,4016,2494],{"class":84},[63,4018,74],{"class":73},[63,4020,2499],{"class":77},[63,4022,2502],{"class":73},[63,4024,4025,4027,4029,4031,4033,4036],{"class":65,"line":114},[63,4026,2507],{"class":84},[63,4028,105],{"class":73},[63,4030,2512],{"class":77},[63,4032,257],{"class":73},[63,4034,4035],{"class":77},"\"@modelcontextprotocol\u002Fserver-puppeteer\"",[63,4037,111],{"class":73},[63,4039,4040],{"class":65,"line":121},[63,4041,2604],{"class":73},[63,4043,4044],{"class":65,"line":5},[63,4045,2609],{"class":73},[63,4047,4048],{"class":65,"line":136},[63,4049,2614],{"class":73},[49,4051,4052],{},"场景：调试前端时让 AI 打开 localhost:3000 截图，对比设计稿。",[1191,4054,4056],{"id":4055},"_6-memory持久记忆","6. Memory（持久记忆）",[49,4058,4059],{},"让 AI 跨会话记住信息。",[34,4061,4063],{"className":2465,"code":4062,"language":2467,"meta":43,"style":43},"{\n  \"mcpServers\": {\n    \"memory\": {\n      \"command\": \"npx\",\n      \"args\": [\"-y\", \"@modelcontextprotocol\u002Fserver-memory\"]\n    }\n  }\n}\n",[41,4064,4065,4069,4075,4082,4092,4107,4111,4115],{"__ignoreMap":43},[63,4066,4067],{"class":65,"line":66},[63,4068,2474],{"class":73},[63,4070,4071,4073],{"class":65,"line":81},[63,4072,2479],{"class":84},[63,4074,2482],{"class":73},[63,4076,4077,4080],{"class":65,"line":91},[63,4078,4079],{"class":84},"    \"memory\"",[63,4081,2482],{"class":73},[63,4083,4084,4086,4088,4090],{"class":65,"line":99},[63,4085,2494],{"class":84},[63,4087,74],{"class":73},[63,4089,2499],{"class":77},[63,4091,2502],{"class":73},[63,4093,4094,4096,4098,4100,4102,4105],{"class":65,"line":114},[63,4095,2507],{"class":84},[63,4097,105],{"class":73},[63,4099,2512],{"class":77},[63,4101,257],{"class":73},[63,4103,4104],{"class":77},"\"@modelcontextprotocol\u002Fserver-memory\"",[63,4106,111],{"class":73},[63,4108,4109],{"class":65,"line":121},[63,4110,2604],{"class":73},[63,4112,4113],{"class":65,"line":5},[63,4114,2609],{"class":73},[63,4116,4117],{"class":65,"line":136},[63,4118,2614],{"class":73},[49,4120,4121],{},"场景：让 AI 记住\"这个项目用 pnpm 不用 npm\"，下次启动自动加载。",[14,4123,4125],{"id":4124},"第三步实战场景","第三步：实战场景",[1191,4127,4129],{"id":4128},"场景-1根据-github-issue-修-bug","场景 1：根据 GitHub Issue 修 Bug",[34,4131,4134],{"className":4132,"code":4133,"language":39,"meta":43},[37],"@agent 看一下 github issue #142，理解问题，然后修复它\n",[41,4135,4133],{"__ignoreMap":43},[49,4137,4138],{},"AI 流程：",[863,4140,4141,4144,4147,4150,4153,4155],{},[21,4142,4143],{},"调 GitHub MCP 读 Issue 内容",[21,4145,4146],{},"分析问题原因",[21,4148,4149],{},"找到相关代码",[21,4151,4152],{},"修改",[21,4154,723],{},[21,4156,4157],{},"生成 PR 描述（可让它直接创建 PR）",[1191,4159,4161],{"id":4160},"场景-2数据驱动的功能开发","场景 2：数据驱动的功能开发",[34,4163,4166],{"className":4164,"code":4165,"language":39,"meta":43},[37],"@agent 我要加用户积分功能。先查一下 users 表结构和现有数据量，然后设计 schema 和 API\n",[41,4167,4165],{"__ignoreMap":43},[49,4169,4138],{},[863,4171,4172,4175,4178,4181,4184,4187],{},[21,4173,4174],{},"调 PostgreSQL MCP 查 users 表结构",[21,4176,4177],{},"查现有数据量（SELECT COUNT(*)）",[21,4179,4180],{},"设计 points 字段和积分日志表",[21,4182,4183],{},"生成迁移 SQL",[21,4185,4186],{},"设计 API 接口",[21,4188,4189],{},"写代码实现",[1191,4191,4193],{"id":4192},"场景-3前端调试","场景 3：前端调试",[34,4195,4198],{"className":4196,"code":4197,"language":39,"meta":43},[37],"@agent 打开 localhost:3000\u002Fprofile，截图看看头像上传组件的样式问题\n",[41,4199,4197],{"__ignoreMap":43},[49,4201,4138],{},[863,4203,4204,4207,4210,4213,4216,4219],{},[21,4205,4206],{},"调 Puppeteer MCP 打开页面",[21,4208,4209],{},"截图",[21,4211,4212],{},"分析样式问题",[21,4214,4215],{},"找到对应组件代码",[21,4217,4218],{},"修改 CSS",[21,4220,4221],{},"重新截图验证",[14,4223,4224],{"id":4224},"权限边界与安全",[1191,4226,4227],{"id":4227},"数据库只读",[49,4229,4230],{},"PostgreSQL MCP Server 默认只允许 SELECT。不要给它写权限，除非你完全信任 AI 的判断。",[1191,4232,4234],{"id":4233},"token-最小权限","Token 最小权限",[49,4236,4237,4238,4241,4242,4245,4246,4249],{},"GitHub Token 只给 ",[41,4239,4240],{},"repo:read"," + ",[41,4243,4244],{},"issues:read","，不要给 ",[41,4247,4248],{},"repo:write","（除非你想让 AI 直接 push 代码）。",[1191,4251,4252],{"id":4252},"敏感信息不进配置文件",[34,4254,4256],{"className":1807,"code":4255,"language":1809,"meta":43,"style":43},"# 不要把 token 写死在 mcp.json，用环境变量\nexport GITHUB_TOKEN=\"ghp_xxx\"\n",[41,4257,4258,4263],{"__ignoreMap":43},[63,4259,4260],{"class":65,"line":66},[63,4261,4262],{"class":1056},"# 不要把 token 写死在 mcp.json，用环境变量\n",[63,4264,4265,4267,4270,4272],{"class":65,"line":81},[63,4266,2282],{"class":427},[63,4268,4269],{"class":73}," GITHUB_TOKEN",[63,4271,1296],{"class":427},[63,4273,2595],{"class":77},[34,4275,4277],{"className":2465,"code":4276,"language":2467,"meta":43,"style":43},"{\n  \"env\": {\n    \"GITHUB_PERSONAL_ACCESS_TOKEN\": \"${GITHUB_TOKEN}\"\n  }\n}\n",[41,4278,4279,4283,4290,4300,4304],{"__ignoreMap":43},[63,4280,4281],{"class":65,"line":66},[63,4282,2474],{"class":73},[63,4284,4285,4288],{"class":65,"line":81},[63,4286,4287],{"class":84},"  \"env\"",[63,4289,2482],{"class":73},[63,4291,4292,4295,4297],{"class":65,"line":91},[63,4293,4294],{"class":84},"    \"GITHUB_PERSONAL_ACCESS_TOKEN\"",[63,4296,74],{"class":73},[63,4298,4299],{"class":77},"\"${GITHUB_TOKEN}\"\n",[63,4301,4302],{"class":65,"line":99},[63,4303,2609],{"class":73},[63,4305,4306],{"class":65,"line":114},[63,4307,2614],{"class":73},[1191,4309,4311],{"id":4310},"gitignore-掉-mcpjson",".gitignore 掉 mcp.json",[49,4313,4314,4315,4317,4318,906],{},"项目级 ",[41,4316,3452],{}," 可能含 token，务必加到 ",[41,4319,3456],{},[14,4321,4322],{"id":4322},"性能注意事项",[1191,4324,4326],{"id":4325},"mcp-server-启动慢","MCP Server 启动慢",[49,4328,4329],{},"每个 MCP Server 是一个独立进程。配 6 个就是 6 个 npx 进程。建议：",[18,4331,4332,4335],{},[21,4333,4334],{},"只配当前任务需要的",[21,4336,4337,4338,4341],{},"用 ",[41,4339,4340],{},"npx --prefer-offline"," 加速",[1191,4343,4344],{"id":4344},"上下文膨胀",[49,4346,4347],{},"MCP 工具的返回结果会进入 AI 上下文。数据库返回 1000 行结果会吃掉大量 token。建议：",[18,4349,4350,4353],{},[21,4351,4352],{},"让 AI 用 LIMIT 限制查询行数",[21,4354,4355],{},"只查需要的字段",[14,4357,4359],{"id":4358},"与-claude-code-mcp-的区别","与 Claude Code MCP 的区别",[697,4361,4362,4374],{},[700,4363,4364],{},[703,4365,4366,4368,4371],{},[706,4367,2083],{},[706,4369,4370],{},"Cursor MCP",[706,4372,4373],{},"Claude Code MCP",[716,4375,4376,4386,4397,4408,4417],{},[703,4377,4378,4381,4383],{},[721,4379,4380],{},"配置位置",[721,4382,3452],{},[721,4384,4385],{},"~\u002F.claude\u002Fmcp_servers.json",[703,4387,4388,4391,4394],{},[721,4389,4390],{},"UI 可视化",[721,4392,4393],{},"✅ 有 MCP 面板",[721,4395,4396],{},"❌ 纯命令行",[703,4398,4399,4402,4405],{},[721,4400,4401],{},"工具调用可见性",[721,4403,4404],{},"✅ 在 chat 里显示",[721,4406,4407],{},"✅ 在终端显示",[703,4409,4410,4413,4415],{},[721,4411,4412],{},"多 Server 同时",[721,4414,726],{},[721,4416,726],{},[703,4418,4419,4422,4424],{},[721,4420,4421],{},"项目级配置",[721,4423,726],{},[721,4425,4426],{},"❌ 全局",[49,4428,4429,4431],{},[868,4430,3388],{},"：两个工具配同样的 MCP Server，根据场景切换用。",[908,4433,4434],{},"html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}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 .sJ8bj, html code.shiki .sJ8bj{--shiki-default:#6A737D;--shiki-dark:#6A737D}html pre.shiki code .szBVR, html code.shiki .szBVR{--shiki-default:#D73A49;--shiki-dark:#F97583}",{"title":43,"searchDepth":91,"depth":91,"links":4436},[4437,4438,4439,4440,4448,4453,4459,4463],{"id":16,"depth":81,"text":16},{"id":3515,"depth":81,"text":3516},{"id":3539,"depth":81,"text":3540},{"id":3635,"depth":81,"text":3636,"children":4441},[4442,4443,4444,4445,4446,4447],{"id":3639,"depth":91,"text":3640},{"id":3727,"depth":91,"text":3728},{"id":3826,"depth":91,"text":3827},{"id":3897,"depth":91,"text":3898},{"id":3986,"depth":91,"text":3987},{"id":4055,"depth":91,"text":4056},{"id":4124,"depth":81,"text":4125,"children":4449},[4450,4451,4452],{"id":4128,"depth":91,"text":4129},{"id":4160,"depth":91,"text":4161},{"id":4192,"depth":91,"text":4193},{"id":4224,"depth":81,"text":4224,"children":4454},[4455,4456,4457,4458],{"id":4227,"depth":91,"text":4227},{"id":4233,"depth":91,"text":4234},{"id":4252,"depth":91,"text":4252},{"id":4310,"depth":91,"text":4311},{"id":4322,"depth":81,"text":4322,"children":4460},[4461,4462],{"id":4325,"depth":91,"text":4326},{"id":4344,"depth":91,"text":4344},{"id":4358,"depth":81,"text":4359},"\u002Fog\u002Fplaybook\u002Fcursor-mcp-deep-integration.png","Cursor 已支持 MCP——连接数据库、GitHub、Slack、文件系统、浏览器，让 AI 在 IDE 里直接操作外部服务。含 6 个常用 MCP Server 配置 + 权限边界 + 真实场景。",{},"\u002Fplaybook\u002Fonboarding\u002Fcursor-mcp-deep-integration",[2041,3486,3487,3488],{"title":3497,"description":4465},"playbook\u002Fonboarding\u002Fcursor-mcp-deep-integration",[2088,3492,4472,994],"集成","7qzEiYO3w5v5Fw3TTNc64WmPJaxqOD2eo9lXwr7Yv1c",{"id":4475,"title":4476,"body":4477,"category":2757,"cover":6005,"description":6006,"extension":925,"meta":6007,"navigation":117,"path":6008,"published":928,"relatedTools":6009,"seo":6013,"stem":6014,"tags":6015,"updated":928,"__hash__":6018},"playbook\u002Fplaybook\u002Fonboarding\u002Frag-pipeline-build.md","RAG 管道从零搭建：文档问答系统实战",{"type":11,"value":4478,"toc":5978},[4479,4481,4495,4499,4505,4508,4591,4595,4619,4623,4626,4751,4754,4813,4818,4822,5030,5034,5177,5180,5183,5187,5338,5342,5345,5445,5449,5452,5713,5716,5719,5804,5807,5811,5817,5822,5833,5837,5841,5852,5856,5860,5871,5875,5879,5893,5896,5975],[14,4480,16],{"id":16},[18,4482,4483,4486,4489,4492],{},[21,4484,4485],{},"公司内部知识库问答（HR 政策、技术文档、FAQ）",[21,4487,4488],{},"法律\u002F医疗文档检索 + 问答",[21,4490,4491],{},"产品手册智能客服",[21,4493,4494],{},"个人笔记\u002F文献智能检索",[14,4496,4498],{"id":4497},"rag-架构总览","RAG 架构总览",[34,4500,4503],{"className":4501,"code":4502,"language":39,"meta":43},[37],"文档 → 切分 → Embedding → 存入向量数据库\n                                    ↓\n用户提问 → Embedding → 检索 Top-K → 重排序 → LLM 生成回答\n",[41,4504,4502],{"__ignoreMap":43},[14,4506,4507],{"id":4507},"技术选型",[697,4509,4510,4523],{},[700,4511,4512],{},[703,4513,4514,4517,4520],{},[706,4515,4516],{},"组件",[706,4518,4519],{},"选型",[706,4521,4522],{},"理由",[716,4524,4525,4536,4547,4558,4569,4580],{},[703,4526,4527,4530,4533],{},[721,4528,4529],{},"框架",[721,4531,4532],{},"LlamaIndex",[721,4534,4535],{},"比 LangChain 更专注 RAG",[703,4537,4538,4541,4544],{},[721,4539,4540],{},"Embedding",[721,4542,4543],{},"BGE-large-zh-v1.5",[721,4545,4546],{},"中文效果最佳，开源免费",[703,4548,4549,4552,4555],{},[721,4550,4551],{},"向量数据库",[721,4553,4554],{},"ChromaDB",[721,4556,4557],{},"轻量，原型够用",[703,4559,4560,4563,4566],{},[721,4561,4562],{},"重排序",[721,4564,4565],{},"bge-reranker-large",[721,4567,4568],{},"显著提升准确率",[703,4570,4571,4574,4577],{},[721,4572,4573],{},"LLM",[721,4575,4576],{},"GPT-4o",[721,4578,4579],{},"性价比好，支持国内中转",[703,4581,4582,4585,4588],{},[721,4583,4584],{},"文档解析",[721,4586,4587],{},"Unstructured",[721,4589,4590],{},"支持 PDF\u002FWord\u002FHTML",[14,4592,4594],{"id":4593},"第一步环境准备","第一步：环境准备",[34,4596,4598],{"className":1807,"code":4597,"language":1809,"meta":43,"style":43},"pip install llama-index chromadb sentence-transformers unstructured\n",[41,4599,4600],{"__ignoreMap":43},[63,4601,4602,4605,4607,4610,4613,4616],{"class":65,"line":66},[63,4603,4604],{"class":1106},"pip",[63,4606,2225],{"class":77},[63,4608,4609],{"class":77}," llama-index",[63,4611,4612],{"class":77}," chromadb",[63,4614,4615],{"class":77}," sentence-transformers",[63,4617,4618],{"class":77}," unstructured\n",[14,4620,4622],{"id":4621},"第二步文档加载与切分","第二步：文档加载与切分",[49,4624,4625],{},"切分是 RAG 效果的决定性因素。",[34,4627,4631],{"className":4628,"code":4629,"language":4630,"meta":43,"style":43},"language-python shiki shiki-themes github-light github-dark","from llama_index.core import SimpleDirectoryReader\nfrom llama_index.core.node_parser import SentenceSplitter\n\n# 1. 加载文档\ndocuments = SimpleDirectoryReader(\".\u002Fdocs\").load_data()\n\n# 2. 切分\nsplitter = SentenceSplitter(\n    chunk_size=512,      # 每块 512 token\n    chunk_overlap=50,    # 重叠 50 token，保证上下文连贯\n)\nnodes = splitter.get_nodes_from_documents(documents)\n","python",[41,4632,4633,4647,4659,4663,4668,4684,4688,4693,4703,4720,4736,4741],{"__ignoreMap":43},[63,4634,4635,4638,4641,4644],{"class":65,"line":66},[63,4636,4637],{"class":427},"from",[63,4639,4640],{"class":73}," llama_index.core ",[63,4642,4643],{"class":427},"import",[63,4645,4646],{"class":73}," SimpleDirectoryReader\n",[63,4648,4649,4651,4654,4656],{"class":65,"line":81},[63,4650,4637],{"class":427},[63,4652,4653],{"class":73}," llama_index.core.node_parser ",[63,4655,4643],{"class":427},[63,4657,4658],{"class":73}," SentenceSplitter\n",[63,4660,4661],{"class":65,"line":91},[63,4662,118],{"emptyLinePlaceholder":117},[63,4664,4665],{"class":65,"line":99},[63,4666,4667],{"class":1056},"# 1. 加载文档\n",[63,4669,4670,4673,4675,4678,4681],{"class":65,"line":114},[63,4671,4672],{"class":73},"documents ",[63,4674,1296],{"class":427},[63,4676,4677],{"class":73}," SimpleDirectoryReader(",[63,4679,4680],{"class":77},"\".\u002Fdocs\"",[63,4682,4683],{"class":73},").load_data()\n",[63,4685,4686],{"class":65,"line":121},[63,4687,118],{"emptyLinePlaceholder":117},[63,4689,4690],{"class":65,"line":5},[63,4691,4692],{"class":1056},"# 2. 切分\n",[63,4694,4695,4698,4700],{"class":65,"line":136},[63,4696,4697],{"class":73},"splitter ",[63,4699,1296],{"class":427},[63,4701,4702],{"class":73}," SentenceSplitter(\n",[63,4704,4705,4709,4711,4714,4717],{"class":65,"line":147},[63,4706,4708],{"class":4707},"s4XuR","    chunk_size",[63,4710,1296],{"class":427},[63,4712,4713],{"class":84},"512",[63,4715,4716],{"class":73},",      ",[63,4718,4719],{"class":1056},"# 每块 512 token\n",[63,4721,4722,4725,4727,4730,4733],{"class":65,"line":155},[63,4723,4724],{"class":4707},"    chunk_overlap",[63,4726,1296],{"class":427},[63,4728,4729],{"class":84},"50",[63,4731,4732],{"class":73},",    ",[63,4734,4735],{"class":1056},"# 重叠 50 token，保证上下文连贯\n",[63,4737,4738],{"class":65,"line":166},[63,4739,4740],{"class":73},")\n",[63,4742,4743,4746,4748],{"class":65,"line":176},[63,4744,4745],{"class":73},"nodes ",[63,4747,1296],{"class":427},[63,4749,4750],{"class":73}," splitter.get_nodes_from_documents(documents)\n",[1191,4752,4753],{"id":4753},"切分策略选择",[697,4755,4756,4768],{},[700,4757,4758],{},[703,4759,4760,4763,4766],{},[706,4761,4762],{},"策略",[706,4764,4765],{},"chunk_size",[706,4767,16],{},[716,4769,4770,4781,4791,4802],{},[703,4771,4772,4775,4778],{},[721,4773,4774],{},"小块",[721,4776,4777],{},"256",[721,4779,4780],{},"FAQ、短问答",[703,4782,4783,4786,4788],{},[721,4784,4785],{},"中块",[721,4787,4713],{},[721,4789,4790],{},"通用文档（推荐起步值）",[703,4792,4793,4796,4799],{},[721,4794,4795],{},"大块",[721,4797,4798],{},"1024",[721,4800,4801],{},"长文档、技术手册",[703,4803,4804,4807,4810],{},[721,4805,4806],{},"按段落",[721,4808,4809],{},"不固定",[721,4811,4812],{},"保持语义完整",[49,4814,4815,4817],{},[868,4816,2423],{},"：chunk_overlap 设 chunk_size 的 10%，避免切断语义。",[14,4819,4821],{"id":4820},"第三步embedding-与入库","第三步：Embedding 与入库",[34,4823,4825],{"className":4628,"code":4824,"language":4630,"meta":43,"style":43},"from llama_index.embeddings.huggingface import HuggingFaceEmbedding\nfrom llama_index.vector_stores.chroma import ChromaVectorStore\nimport chromadb\n\n# 1. 用 BGE 中文模型\nembed_model = HuggingFaceEmbedding(\n    model_name=\"BAAI\u002Fbge-large-zh-v1.5\",\n    max_length=512,\n)\n\n# 2. 创建向量数据库\ndb = chromadb.PersistentClient(path=\".\u002Fchroma_db\")\nchroma_collection = db.get_or_create_collection(\"docs\")\nvector_store = ChromaVectorStore(chroma_collection=chroma_collection)\n\n# 3. 构建索引\nfrom llama_index.core import StorageContext, VectorStoreIndex\nstorage_context = StorageContext.from_defaults(vector_store=vector_store)\nindex = VectorStoreIndex(nodes, embed_model=embed_model, storage_context=storage_context)\n",[41,4826,4827,4839,4851,4858,4862,4867,4877,4889,4900,4904,4908,4913,4933,4948,4966,4970,4975,4986,5004],{"__ignoreMap":43},[63,4828,4829,4831,4834,4836],{"class":65,"line":66},[63,4830,4637],{"class":427},[63,4832,4833],{"class":73}," llama_index.embeddings.huggingface ",[63,4835,4643],{"class":427},[63,4837,4838],{"class":73}," HuggingFaceEmbedding\n",[63,4840,4841,4843,4846,4848],{"class":65,"line":81},[63,4842,4637],{"class":427},[63,4844,4845],{"class":73}," llama_index.vector_stores.chroma ",[63,4847,4643],{"class":427},[63,4849,4850],{"class":73}," ChromaVectorStore\n",[63,4852,4853,4855],{"class":65,"line":91},[63,4854,4643],{"class":427},[63,4856,4857],{"class":73}," chromadb\n",[63,4859,4860],{"class":65,"line":99},[63,4861,118],{"emptyLinePlaceholder":117},[63,4863,4864],{"class":65,"line":114},[63,4865,4866],{"class":1056},"# 1. 用 BGE 中文模型\n",[63,4868,4869,4872,4874],{"class":65,"line":121},[63,4870,4871],{"class":73},"embed_model ",[63,4873,1296],{"class":427},[63,4875,4876],{"class":73}," HuggingFaceEmbedding(\n",[63,4878,4879,4882,4884,4887],{"class":65,"line":5},[63,4880,4881],{"class":4707},"    model_name",[63,4883,1296],{"class":427},[63,4885,4886],{"class":77},"\"BAAI\u002Fbge-large-zh-v1.5\"",[63,4888,2502],{"class":73},[63,4890,4891,4894,4896,4898],{"class":65,"line":136},[63,4892,4893],{"class":4707},"    max_length",[63,4895,1296],{"class":427},[63,4897,4713],{"class":84},[63,4899,2502],{"class":73},[63,4901,4902],{"class":65,"line":147},[63,4903,4740],{"class":73},[63,4905,4906],{"class":65,"line":155},[63,4907,118],{"emptyLinePlaceholder":117},[63,4909,4910],{"class":65,"line":166},[63,4911,4912],{"class":1056},"# 2. 创建向量数据库\n",[63,4914,4915,4918,4920,4923,4926,4928,4931],{"class":65,"line":176},[63,4916,4917],{"class":73},"db ",[63,4919,1296],{"class":427},[63,4921,4922],{"class":73}," chromadb.PersistentClient(",[63,4924,4925],{"class":4707},"path",[63,4927,1296],{"class":427},[63,4929,4930],{"class":77},"\".\u002Fchroma_db\"",[63,4932,4740],{"class":73},[63,4934,4935,4938,4940,4943,4946],{"class":65,"line":184},[63,4936,4937],{"class":73},"chroma_collection ",[63,4939,1296],{"class":427},[63,4941,4942],{"class":73}," db.get_or_create_collection(",[63,4944,4945],{"class":77},"\"docs\"",[63,4947,4740],{"class":73},[63,4949,4950,4953,4955,4958,4961,4963],{"class":65,"line":198},[63,4951,4952],{"class":73},"vector_store ",[63,4954,1296],{"class":427},[63,4956,4957],{"class":73}," ChromaVectorStore(",[63,4959,4960],{"class":4707},"chroma_collection",[63,4962,1296],{"class":427},[63,4964,4965],{"class":73},"chroma_collection)\n",[63,4967,4968],{"class":65,"line":210},[63,4969,118],{"emptyLinePlaceholder":117},[63,4971,4972],{"class":65,"line":230},[63,4973,4974],{"class":1056},"# 3. 构建索引\n",[63,4976,4977,4979,4981,4983],{"class":65,"line":242},[63,4978,4637],{"class":427},[63,4980,4640],{"class":73},[63,4982,4643],{"class":427},[63,4984,4985],{"class":73}," StorageContext, VectorStoreIndex\n",[63,4987,4988,4991,4993,4996,4999,5001],{"class":65,"line":270},[63,4989,4990],{"class":73},"storage_context ",[63,4992,1296],{"class":427},[63,4994,4995],{"class":73}," StorageContext.from_defaults(",[63,4997,4998],{"class":4707},"vector_store",[63,5000,1296],{"class":427},[63,5002,5003],{"class":73},"vector_store)\n",[63,5005,5006,5009,5011,5014,5017,5019,5022,5025,5027],{"class":65,"line":275},[63,5007,5008],{"class":73},"index ",[63,5010,1296],{"class":427},[63,5012,5013],{"class":73}," VectorStoreIndex(nodes, ",[63,5015,5016],{"class":4707},"embed_model",[63,5018,1296],{"class":427},[63,5020,5021],{"class":73},"embed_model, ",[63,5023,5024],{"class":4707},"storage_context",[63,5026,1296],{"class":427},[63,5028,5029],{"class":73},"storage_context)\n",[14,5031,5033],{"id":5032},"第四步检索-重排序","第四步：检索 + 重排序",[34,5035,5037],{"className":4628,"code":5036,"language":4630,"meta":43,"style":43},"from llama_index.core.postprocessor import SentenceTransformerRerank\n\n# 重排序模型——显著提升检索质量\nreranker = SentenceTransformerRerank(\n    model=\"BAAI\u002Fbge-reranker-large\",\n    top_n=3,  # 重排序后取前 3\n)\n\n# 检索器\nretriever = index.as_retriever(similarity_top_k=10)  # 先粗检索 10 条\n\n# 检索 + 重排序\nnodes = retriever.retrieve(\"年假怎么请？\")\nreranked = reranker.postprocess_nodes(nodes, query_str=\"年假怎么请？\")\n",[41,5038,5039,5051,5055,5060,5070,5082,5098,5102,5106,5111,5135,5139,5144,5158],{"__ignoreMap":43},[63,5040,5041,5043,5046,5048],{"class":65,"line":66},[63,5042,4637],{"class":427},[63,5044,5045],{"class":73}," llama_index.core.postprocessor ",[63,5047,4643],{"class":427},[63,5049,5050],{"class":73}," SentenceTransformerRerank\n",[63,5052,5053],{"class":65,"line":81},[63,5054,118],{"emptyLinePlaceholder":117},[63,5056,5057],{"class":65,"line":91},[63,5058,5059],{"class":1056},"# 重排序模型——显著提升检索质量\n",[63,5061,5062,5065,5067],{"class":65,"line":99},[63,5063,5064],{"class":73},"reranker ",[63,5066,1296],{"class":427},[63,5068,5069],{"class":73}," SentenceTransformerRerank(\n",[63,5071,5072,5075,5077,5080],{"class":65,"line":114},[63,5073,5074],{"class":4707},"    model",[63,5076,1296],{"class":427},[63,5078,5079],{"class":77},"\"BAAI\u002Fbge-reranker-large\"",[63,5081,2502],{"class":73},[63,5083,5084,5087,5089,5092,5095],{"class":65,"line":121},[63,5085,5086],{"class":4707},"    top_n",[63,5088,1296],{"class":427},[63,5090,5091],{"class":84},"3",[63,5093,5094],{"class":73},",  ",[63,5096,5097],{"class":1056},"# 重排序后取前 3\n",[63,5099,5100],{"class":65,"line":5},[63,5101,4740],{"class":73},[63,5103,5104],{"class":65,"line":136},[63,5105,118],{"emptyLinePlaceholder":117},[63,5107,5108],{"class":65,"line":147},[63,5109,5110],{"class":1056},"# 检索器\n",[63,5112,5113,5116,5118,5121,5124,5126,5129,5132],{"class":65,"line":155},[63,5114,5115],{"class":73},"retriever ",[63,5117,1296],{"class":427},[63,5119,5120],{"class":73}," index.as_retriever(",[63,5122,5123],{"class":4707},"similarity_top_k",[63,5125,1296],{"class":427},[63,5127,5128],{"class":84},"10",[63,5130,5131],{"class":73},")  ",[63,5133,5134],{"class":1056},"# 先粗检索 10 条\n",[63,5136,5137],{"class":65,"line":166},[63,5138,118],{"emptyLinePlaceholder":117},[63,5140,5141],{"class":65,"line":176},[63,5142,5143],{"class":1056},"# 检索 + 重排序\n",[63,5145,5146,5148,5150,5153,5156],{"class":65,"line":184},[63,5147,4745],{"class":73},[63,5149,1296],{"class":427},[63,5151,5152],{"class":73}," retriever.retrieve(",[63,5154,5155],{"class":77},"\"年假怎么请？\"",[63,5157,4740],{"class":73},[63,5159,5160,5163,5165,5168,5171,5173,5175],{"class":65,"line":198},[63,5161,5162],{"class":73},"reranked ",[63,5164,1296],{"class":427},[63,5166,5167],{"class":73}," reranker.postprocess_nodes(nodes, ",[63,5169,5170],{"class":4707},"query_str",[63,5172,1296],{"class":427},[63,5174,5155],{"class":77},[63,5176,4740],{"class":73},[1191,5178,5179],{"id":5179},"为什么要重排序",[49,5181,5182],{},"Embedding 检索快但不够精准。先粗检索 10 条，再用重排序模型精选 3 条，准确率提升 20-30%。",[14,5184,5186],{"id":5185},"第五步生成回答","第五步：生成回答",[34,5188,5190],{"className":4628,"code":5189,"language":4630,"meta":43,"style":43},"from llama_index.llms.openai import OpenAI\n\nllm = OpenAI(model=\"gpt-4o\", temperature=0)\n\n# 构建 query engine\nquery_engine = index.as_query_engine(\n    llm=llm,\n    similarity_top_k=10,\n    node_postprocessors=[reranker],\n    response_mode=\"compact\",  # 紧凑模式，省 token\n)\n\n# 提问\nresponse = query_engine.query(\"年假怎么请？需要提前多久申请？\")\nprint(response.response)\n",[41,5191,5192,5204,5208,5237,5241,5246,5256,5266,5277,5287,5302,5306,5310,5315,5330],{"__ignoreMap":43},[63,5193,5194,5196,5199,5201],{"class":65,"line":66},[63,5195,4637],{"class":427},[63,5197,5198],{"class":73}," llama_index.llms.openai ",[63,5200,4643],{"class":427},[63,5202,5203],{"class":73}," OpenAI\n",[63,5205,5206],{"class":65,"line":81},[63,5207,118],{"emptyLinePlaceholder":117},[63,5209,5210,5213,5215,5218,5221,5223,5226,5228,5231,5233,5235],{"class":65,"line":91},[63,5211,5212],{"class":73},"llm ",[63,5214,1296],{"class":427},[63,5216,5217],{"class":73}," OpenAI(",[63,5219,5220],{"class":4707},"model",[63,5222,1296],{"class":427},[63,5224,5225],{"class":77},"\"gpt-4o\"",[63,5227,257],{"class":73},[63,5229,5230],{"class":4707},"temperature",[63,5232,1296],{"class":427},[63,5234,1427],{"class":84},[63,5236,4740],{"class":73},[63,5238,5239],{"class":65,"line":99},[63,5240,118],{"emptyLinePlaceholder":117},[63,5242,5243],{"class":65,"line":114},[63,5244,5245],{"class":1056},"# 构建 query engine\n",[63,5247,5248,5251,5253],{"class":65,"line":121},[63,5249,5250],{"class":73},"query_engine ",[63,5252,1296],{"class":427},[63,5254,5255],{"class":73}," index.as_query_engine(\n",[63,5257,5258,5261,5263],{"class":65,"line":5},[63,5259,5260],{"class":4707},"    llm",[63,5262,1296],{"class":427},[63,5264,5265],{"class":73},"llm,\n",[63,5267,5268,5271,5273,5275],{"class":65,"line":136},[63,5269,5270],{"class":4707},"    similarity_top_k",[63,5272,1296],{"class":427},[63,5274,5128],{"class":84},[63,5276,2502],{"class":73},[63,5278,5279,5282,5284],{"class":65,"line":147},[63,5280,5281],{"class":4707},"    node_postprocessors",[63,5283,1296],{"class":427},[63,5285,5286],{"class":73},"[reranker],\n",[63,5288,5289,5292,5294,5297,5299],{"class":65,"line":155},[63,5290,5291],{"class":4707},"    response_mode",[63,5293,1296],{"class":427},[63,5295,5296],{"class":77},"\"compact\"",[63,5298,5094],{"class":73},[63,5300,5301],{"class":1056},"# 紧凑模式，省 token\n",[63,5303,5304],{"class":65,"line":166},[63,5305,4740],{"class":73},[63,5307,5308],{"class":65,"line":176},[63,5309,118],{"emptyLinePlaceholder":117},[63,5311,5312],{"class":65,"line":184},[63,5313,5314],{"class":1056},"# 提问\n",[63,5316,5317,5320,5322,5325,5328],{"class":65,"line":198},[63,5318,5319],{"class":73},"response ",[63,5321,1296],{"class":427},[63,5323,5324],{"class":73}," query_engine.query(",[63,5326,5327],{"class":77},"\"年假怎么请？需要提前多久申请？\"",[63,5329,4740],{"class":73},[63,5331,5332,5335],{"class":65,"line":210},[63,5333,5334],{"class":84},"print",[63,5336,5337],{"class":73},"(response.response)\n",[1191,5339,5341],{"id":5340},"prompt-优化","Prompt 优化",[49,5343,5344],{},"默认 prompt 是英文的，中文场景建议自定义：",[34,5346,5348],{"className":4628,"code":5347,"language":4630,"meta":43,"style":43},"from llama_index.core import PromptTemplate\n\nqa_prompt = PromptTemplate(\"\"\"\n你是一个文档问答助手。请根据以下检索到的文档片段回答问题。\n如果文档中没有相关信息，明确说\"文档中未找到相关信息\"，不要编造。\n\n文档片段：\n{context_str}\n\n问题：{query_str}\n\n回答：\n\"\"\")\n\nquery_engine.update_prompts({\"response_synthesis_prompt\": qa_prompt})\n",[41,5349,5350,5361,5365,5378,5383,5388,5392,5397,5402,5406,5414,5418,5423,5430,5434],{"__ignoreMap":43},[63,5351,5352,5354,5356,5358],{"class":65,"line":66},[63,5353,4637],{"class":427},[63,5355,4640],{"class":73},[63,5357,4643],{"class":427},[63,5359,5360],{"class":73}," PromptTemplate\n",[63,5362,5363],{"class":65,"line":81},[63,5364,118],{"emptyLinePlaceholder":117},[63,5366,5367,5370,5372,5375],{"class":65,"line":91},[63,5368,5369],{"class":73},"qa_prompt ",[63,5371,1296],{"class":427},[63,5373,5374],{"class":73}," PromptTemplate(",[63,5376,5377],{"class":77},"\"\"\"\n",[63,5379,5380],{"class":65,"line":99},[63,5381,5382],{"class":77},"你是一个文档问答助手。请根据以下检索到的文档片段回答问题。\n",[63,5384,5385],{"class":65,"line":114},[63,5386,5387],{"class":77},"如果文档中没有相关信息，明确说\"文档中未找到相关信息\"，不要编造。\n",[63,5389,5390],{"class":65,"line":121},[63,5391,118],{"emptyLinePlaceholder":117},[63,5393,5394],{"class":65,"line":5},[63,5395,5396],{"class":77},"文档片段：\n",[63,5398,5399],{"class":65,"line":136},[63,5400,5401],{"class":84},"{context_str}\n",[63,5403,5404],{"class":65,"line":147},[63,5405,118],{"emptyLinePlaceholder":117},[63,5407,5408,5411],{"class":65,"line":155},[63,5409,5410],{"class":77},"问题：",[63,5412,5413],{"class":84},"{query_str}\n",[63,5415,5416],{"class":65,"line":166},[63,5417,118],{"emptyLinePlaceholder":117},[63,5419,5420],{"class":65,"line":176},[63,5421,5422],{"class":77},"回答：\n",[63,5424,5425,5428],{"class":65,"line":184},[63,5426,5427],{"class":77},"\"\"\"",[63,5429,4740],{"class":73},[63,5431,5432],{"class":65,"line":198},[63,5433,118],{"emptyLinePlaceholder":117},[63,5435,5436,5439,5442],{"class":65,"line":210},[63,5437,5438],{"class":73},"query_engine.update_prompts({",[63,5440,5441],{"class":77},"\"response_synthesis_prompt\"",[63,5443,5444],{"class":73},": qa_prompt})\n",[14,5446,5448],{"id":5447},"第六步评估","第六步：评估",[1191,5450,5451],{"id":5451},"检索质量评估",[34,5453,5455],{"className":4628,"code":5454,"language":4630,"meta":43,"style":43},"# 准备测试集：问题 + 正确答案所在文档\ntest_cases = [\n    {\"question\": \"年假怎么请？\", \"relevant_doc_id\": \"hr_policy_003\"},\n    {\"question\": \"报销流程是什么？\", \"relevant_doc_id\": \"finance_007\"},\n]\n\n# 计算 Recall@K\ndef eval_retrieval(query_engine, test_cases, k=5):\n    hits = 0\n    for tc in test_cases:\n        nodes = query_engine.retrieve(tc[\"question\"])\n        retrieved_ids = [n.node.metadata[\"doc_id\"] for n in nodes[:k]]\n        if tc[\"relevant_doc_id\"] in retrieved_ids:\n            hits += 1\n    return hits \u002F len(test_cases)\n\nrecall = eval_retrieval(query_engine, test_cases, k=5)\nprint(f\"Recall@5: {recall:.1%}\")\n",[41,5456,5457,5462,5472,5497,5519,5523,5527,5532,5551,5561,5575,5590,5617,5633,5644,5661,5665,5684],{"__ignoreMap":43},[63,5458,5459],{"class":65,"line":66},[63,5460,5461],{"class":1056},"# 准备测试集：问题 + 正确答案所在文档\n",[63,5463,5464,5467,5469],{"class":65,"line":81},[63,5465,5466],{"class":73},"test_cases ",[63,5468,1296],{"class":427},[63,5470,5471],{"class":73}," [\n",[63,5473,5474,5477,5480,5482,5484,5486,5489,5491,5494],{"class":65,"line":91},[63,5475,5476],{"class":73},"    {",[63,5478,5479],{"class":77},"\"question\"",[63,5481,74],{"class":73},[63,5483,5155],{"class":77},[63,5485,257],{"class":73},[63,5487,5488],{"class":77},"\"relevant_doc_id\"",[63,5490,74],{"class":73},[63,5492,5493],{"class":77},"\"hr_policy_003\"",[63,5495,5496],{"class":73},"},\n",[63,5498,5499,5501,5503,5505,5508,5510,5512,5514,5517],{"class":65,"line":99},[63,5500,5476],{"class":73},[63,5502,5479],{"class":77},[63,5504,74],{"class":73},[63,5506,5507],{"class":77},"\"报销流程是什么？\"",[63,5509,257],{"class":73},[63,5511,5488],{"class":77},[63,5513,74],{"class":73},[63,5515,5516],{"class":77},"\"finance_007\"",[63,5518,5496],{"class":73},[63,5520,5521],{"class":65,"line":114},[63,5522,111],{"class":73},[63,5524,5525],{"class":65,"line":121},[63,5526,118],{"emptyLinePlaceholder":117},[63,5528,5529],{"class":65,"line":5},[63,5530,5531],{"class":1056},"# 计算 Recall@K\n",[63,5533,5534,5537,5540,5543,5545,5548],{"class":65,"line":136},[63,5535,5536],{"class":427},"def",[63,5538,5539],{"class":1106}," eval_retrieval",[63,5541,5542],{"class":73},"(query_engine, test_cases, k",[63,5544,1296],{"class":427},[63,5546,5547],{"class":84},"5",[63,5549,5550],{"class":73},"):\n",[63,5552,5553,5556,5558],{"class":65,"line":147},[63,5554,5555],{"class":73},"    hits ",[63,5557,1296],{"class":427},[63,5559,5560],{"class":84}," 0\n",[63,5562,5563,5566,5569,5572],{"class":65,"line":155},[63,5564,5565],{"class":427},"    for",[63,5567,5568],{"class":73}," tc ",[63,5570,5571],{"class":427},"in",[63,5573,5574],{"class":73}," test_cases:\n",[63,5576,5577,5580,5582,5585,5587],{"class":65,"line":166},[63,5578,5579],{"class":73},"        nodes ",[63,5581,1296],{"class":427},[63,5583,5584],{"class":73}," query_engine.retrieve(tc[",[63,5586,5479],{"class":77},[63,5588,5589],{"class":73},"])\n",[63,5591,5592,5595,5597,5600,5603,5606,5609,5612,5614],{"class":65,"line":176},[63,5593,5594],{"class":73},"        retrieved_ids ",[63,5596,1296],{"class":427},[63,5598,5599],{"class":73}," [n.node.metadata[",[63,5601,5602],{"class":77},"\"doc_id\"",[63,5604,5605],{"class":73},"] ",[63,5607,5608],{"class":427},"for",[63,5610,5611],{"class":73}," n ",[63,5613,5571],{"class":427},[63,5615,5616],{"class":73}," nodes[:k]]\n",[63,5618,5619,5621,5624,5626,5628,5630],{"class":65,"line":184},[63,5620,492],{"class":427},[63,5622,5623],{"class":73}," tc[",[63,5625,5488],{"class":77},[63,5627,5605],{"class":73},[63,5629,5571],{"class":427},[63,5631,5632],{"class":73}," retrieved_ids:\n",[63,5634,5635,5638,5641],{"class":65,"line":198},[63,5636,5637],{"class":73},"            hits ",[63,5639,5640],{"class":427},"+=",[63,5642,5643],{"class":84}," 1\n",[63,5645,5646,5649,5652,5655,5658],{"class":65,"line":210},[63,5647,5648],{"class":427},"    return",[63,5650,5651],{"class":73}," hits ",[63,5653,5654],{"class":427},"\u002F",[63,5656,5657],{"class":84}," len",[63,5659,5660],{"class":73},"(test_cases)\n",[63,5662,5663],{"class":65,"line":230},[63,5664,118],{"emptyLinePlaceholder":117},[63,5666,5667,5670,5672,5675,5678,5680,5682],{"class":65,"line":242},[63,5668,5669],{"class":73},"recall ",[63,5671,1296],{"class":427},[63,5673,5674],{"class":73}," eval_retrieval(query_engine, test_cases, ",[63,5676,5677],{"class":4707},"k",[63,5679,1296],{"class":427},[63,5681,5547],{"class":84},[63,5683,4740],{"class":73},[63,5685,5686,5688,5690,5693,5696,5699,5702,5705,5708,5711],{"class":65,"line":270},[63,5687,5334],{"class":84},[63,5689,1089],{"class":73},[63,5691,5692],{"class":427},"f",[63,5694,5695],{"class":77},"\"Recall@5: ",[63,5697,5698],{"class":84},"{",[63,5700,5701],{"class":73},"recall",[63,5703,5704],{"class":427},":.1%",[63,5706,5707],{"class":84},"}",[63,5709,5710],{"class":77},"\"",[63,5712,4740],{"class":73},[1191,5714,5715],{"id":5715},"回答质量评估",[49,5717,5718],{},"用 LLM 自动评估回答质量：",[34,5720,5722],{"className":4628,"code":5721,"language":4630,"meta":43,"style":43},"eval_prompt = f\"\"\"\n请评估以下回答的质量，打分 1-5：\n问题：{question}\n检索到的文档：{retrieved_docs}\n回答：{answer}\n\n评分标准：\n5 — 完全正确，基于文档\n3 — 部分正确，有遗漏\n1 — 错误或编造\n\"\"\"\n",[41,5723,5724,5736,5741,5752,5764,5776,5780,5785,5790,5795,5800],{"__ignoreMap":43},[63,5725,5726,5729,5731,5734],{"class":65,"line":66},[63,5727,5728],{"class":73},"eval_prompt ",[63,5730,1296],{"class":427},[63,5732,5733],{"class":427}," f",[63,5735,5377],{"class":77},[63,5737,5738],{"class":65,"line":81},[63,5739,5740],{"class":77},"请评估以下回答的质量，打分 1-5：\n",[63,5742,5743,5745,5747,5750],{"class":65,"line":91},[63,5744,5410],{"class":77},[63,5746,5698],{"class":84},[63,5748,5749],{"class":73},"question",[63,5751,2614],{"class":84},[63,5753,5754,5757,5759,5762],{"class":65,"line":99},[63,5755,5756],{"class":77},"检索到的文档：",[63,5758,5698],{"class":84},[63,5760,5761],{"class":73},"retrieved_docs",[63,5763,2614],{"class":84},[63,5765,5766,5769,5771,5774],{"class":65,"line":114},[63,5767,5768],{"class":77},"回答：",[63,5770,5698],{"class":84},[63,5772,5773],{"class":73},"answer",[63,5775,2614],{"class":84},[63,5777,5778],{"class":65,"line":121},[63,5779,118],{"emptyLinePlaceholder":117},[63,5781,5782],{"class":65,"line":5},[63,5783,5784],{"class":77},"评分标准：\n",[63,5786,5787],{"class":65,"line":136},[63,5788,5789],{"class":77},"5 — 完全正确，基于文档\n",[63,5791,5792],{"class":65,"line":147},[63,5793,5794],{"class":77},"3 — 部分正确，有遗漏\n",[63,5796,5797],{"class":65,"line":155},[63,5798,5799],{"class":77},"1 — 错误或编造\n",[63,5801,5802],{"class":65,"line":166},[63,5803,5377],{"class":77},[14,5805,5806],{"id":5806},"常见问题与优化",[1191,5808,5810],{"id":5809},"问题-1检索不到相关文档","问题 1：检索不到相关文档",[49,5812,5813,5816],{},[868,5814,5815],{},"原因","：切分太碎，语义丢失。",[49,5818,5819,54],{},[868,5820,5821],{},"解决",[18,5823,5824,5827,5830],{},[21,5825,5826],{},"增大 chunk_size（512 → 1024）",[21,5828,5829],{},"加 chunk_overlap",[21,5831,5832],{},"用 parent-child 切分（检索小块，返回大块）",[1191,5834,5836],{"id":5835},"问题-2回答不基于文档幻觉","问题 2：回答不基于文档（幻觉）",[49,5838,5839,54],{},[868,5840,5821],{},[18,5842,5843,5846,5849],{},[21,5844,5845],{},"prompt 强制要求\"只基于文档回答\"",[21,5847,5848],{},"temperature 设 0",[21,5850,5851],{},"检查检索结果是否相关（不相关就回答\"未找到\"）",[1191,5853,5855],{"id":5854},"问题-3中文检索效果差","问题 3：中文检索效果差",[49,5857,5858,54],{},[868,5859,5821],{},[18,5861,5862,5865,5868],{},[21,5863,5864],{},"用 BGE 系列中文 Embedding 模型",[21,5866,5867],{},"不要用 OpenAI 的 Embedding（中文效果一般）",[21,5869,5870],{},"加重排序模型",[1191,5872,5874],{"id":5873},"问题-4速度慢","问题 4：速度慢",[49,5876,5877,54],{},[868,5878,5821],{},[18,5880,5881,5884,5887,5890],{},[21,5882,5883],{},"Embedding 用 GPU",[21,5885,5886],{},"向量数据库加 HNSW 索引",[21,5888,5889],{},"减少 similarity_top_k（10 → 5）",[21,5891,5892],{},"LLM 用流式输出",[14,5894,5895],{"id":5895},"生产部署清单",[18,5897,5900,5909,5915,5921,5927,5933,5939,5945,5951,5957,5963,5969],{"className":5898},[5899],"contains-task-list",[21,5901,5904,5908],{"className":5902},[5903],"task-list-item",[5905,5906],"input",{"disabled":117,"type":5907},"checkbox"," 文档解析支持 PDF\u002FWord\u002FHTML\u002FMarkdown",[21,5910,5912,5914],{"className":5911},[5903],[5905,5913],{"disabled":117,"type":5907}," 切分策略测试过（chunk_size 调优）",[21,5916,5918,5920],{"className":5917},[5903],[5905,5919],{"disabled":117,"type":5907}," Embedding 模型选定并部署",[21,5922,5924,5926],{"className":5923},[5903],[5905,5925],{"disabled":117,"type":5907}," 向量数据库持久化",[21,5928,5930,5932],{"className":5929},[5903],[5905,5931],{"disabled":117,"type":5907}," 重排序模型集成",[21,5934,5936,5938],{"className":5935},[5903],[5905,5937],{"disabled":117,"type":5907}," LLM prompt 优化（中文 + 防幻觉）",[21,5940,5942,5944],{"className":5941},[5903],[5905,5943],{"disabled":117,"type":5907}," 检索质量评估（Recall@K > 80%）",[21,5946,5948,5950],{"className":5947},[5903],[5905,5949],{"disabled":117,"type":5907}," 回答质量评估（人工抽检）",[21,5952,5954,5956],{"className":5953},[5903],[5905,5955],{"disabled":117,"type":5907}," 流式输出（用户体验）",[21,5958,5960,5962],{"className":5959},[5903],[5905,5961],{"disabled":117,"type":5907}," 缓存层（常见问题缓存回答）",[21,5964,5966,5968],{"className":5965},[5903],[5905,5967],{"disabled":117,"type":5907}," 日志记录（问题 + 检索结果 + 回答）",[21,5970,5972,5974],{"className":5971},[5903],[5905,5973],{"disabled":117,"type":5907}," 限流 + 鉴权",[908,5976,5977],{},"html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}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 .szBVR, html code.shiki .szBVR{--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .sJ8bj, html code.shiki .sJ8bj{--shiki-default:#6A737D;--shiki-dark:#6A737D}html pre.shiki code .s4XuR, html code.shiki .s4XuR{--shiki-default:#E36209;--shiki-dark:#FFAB70}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}",{"title":43,"searchDepth":91,"depth":91,"links":5979},[5980,5981,5982,5983,5984,5987,5988,5991,5994,5998,6004],{"id":16,"depth":81,"text":16},{"id":4497,"depth":81,"text":4498},{"id":4507,"depth":81,"text":4507},{"id":4593,"depth":81,"text":4594},{"id":4621,"depth":81,"text":4622,"children":5985},[5986],{"id":4753,"depth":91,"text":4753},{"id":4820,"depth":81,"text":4821},{"id":5032,"depth":81,"text":5033,"children":5989},[5990],{"id":5179,"depth":91,"text":5179},{"id":5185,"depth":81,"text":5186,"children":5992},[5993],{"id":5340,"depth":91,"text":5341},{"id":5447,"depth":81,"text":5448,"children":5995},[5996,5997],{"id":5451,"depth":91,"text":5451},{"id":5715,"depth":91,"text":5715},{"id":5806,"depth":81,"text":5806,"children":5999},[6000,6001,6002,6003],{"id":5809,"depth":91,"text":5810},{"id":5835,"depth":91,"text":5836},{"id":5854,"depth":91,"text":5855},{"id":5873,"depth":91,"text":5874},{"id":5895,"depth":81,"text":5895},"\u002Fog\u002Fplaybook\u002Frag-pipeline-build.png","用 LlamaIndex + ChromaDB + GPT-4o 搭一个生产级 RAG 系统——文档切分策略、Embedding 选型、检索优化、重排序、回答生成、评估指标，含完整代码和踩坑记录。",{},"\u002Fplaybook\u002Fonboarding\u002Frag-pipeline-build",[6010,6011,6012],"agent\u002Fplatform\u002Fdify","agent\u002Fplatform\u002Ffastgpt","coding\u002Flocal\u002Follama",{"title":4476,"description":6006},"playbook\u002Fonboarding\u002Frag-pipeline-build",[6016,4532,4551,6017],"RAG","文档问答","iDBgSssiFFBzdFhaAXgu0ePo29Ov3xLYoHWpnnCPo04",{"id":6020,"title":6021,"body":6022,"category":6628,"cover":6629,"description":6630,"extension":925,"meta":6631,"navigation":117,"path":6632,"published":928,"relatedTools":6633,"seo":6636,"stem":6637,"tags":6638,"updated":928,"__hash__":6641},"playbook\u002Fplaybook\u002Freview\u002Fai-pr-review-pipeline.md","用 AI Agent 搭一个自动化 PR Review 流水线",{"type":11,"value":6023,"toc":6619},[6024,6026,6037,6039,6045,6049,6052,6074,6164,6169,6173,6176,6182,6443,6447,6450,6508,6514,6516,6519,6579,6581,6616],[14,6025,16],{"id":16},[18,6027,6028,6031,6034],{},[21,6029,6030],{},"团队 5-20 人，PR review 是瓶颈",[21,6032,6033],{},"想让 AI 做第一轮审查，人只看 AI 标记的问题",[21,6035,6036],{},"需要 CI 阻断有严重问题的 PR（而非只评论）",[14,6038,32],{"id":32},[34,6040,6043],{"className":6041,"code":6042,"language":39},[37],"PR 提交 → GitHub Actions 触发\n  ├─ CodeRabbit Bot 自动评论（逐行 + 摘要）\n  ├─ Claude Code 自定义规则审查（安全 \u002F 性能 \u002F 规范）\n  └─ 严重问题 → 设置 commit status = failed → 阻断合并\n",[41,6044,6042],{"__ignoreMap":43},[14,6046,6048],{"id":6047},"第一步coderabbit-接入","第一步：CodeRabbit 接入",[49,6050,6051],{},"CodeRabbit 是开箱即用的 GitHub App，装上就自动 review。",[863,6053,6054,6065,6068],{},[21,6055,6056,6057,6064],{},"去 ",[6058,6059,6063],"a",{"href":6060,"rel":6061},"https:\u002F\u002Fcoderabbit.ai",[6062],"nofollow","coderabbit.ai"," 安装 GitHub App",[21,6066,6067],{},"选择要 review 的仓库",[21,6069,6070,6071,54],{},"在仓库根目录加 ",[41,6072,6073],{},".coderabbit.yml",[34,6075,6077],{"className":57,"code":6076,"language":59,"meta":43,"style":43},"reviews:\n  auto_review:\n    enabled: true\n    drafts: false\n  path_filters:\n    - \"!**\u002F*.lock\"\n    - \"!**\u002Fdist\u002F**\"\n  instructions: |\n    - 用中文评论\n    - 重点关注 SQL 注入、XSS、敏感信息泄露\n    - 不要评论代码风格（用 ESLint 管）\n    - 性能问题只在 O(n²) 以上才报\n",[41,6078,6079,6086,6093,6103,6113,6120,6128,6135,6144,6149,6154,6159],{"__ignoreMap":43},[63,6080,6081,6084],{"class":65,"line":66},[63,6082,6083],{"class":69},"reviews",[63,6085,88],{"class":73},[63,6087,6088,6091],{"class":65,"line":81},[63,6089,6090],{"class":69},"  auto_review",[63,6092,88],{"class":73},[63,6094,6095,6098,6100],{"class":65,"line":91},[63,6096,6097],{"class":69},"    enabled",[63,6099,74],{"class":73},[63,6101,6102],{"class":84},"true\n",[63,6104,6105,6108,6110],{"class":65,"line":99},[63,6106,6107],{"class":69},"    drafts",[63,6109,74],{"class":73},[63,6111,6112],{"class":84},"false\n",[63,6114,6115,6118],{"class":65,"line":114},[63,6116,6117],{"class":69},"  path_filters",[63,6119,88],{"class":73},[63,6121,6122,6125],{"class":65,"line":121},[63,6123,6124],{"class":73},"    - ",[63,6126,6127],{"class":77},"\"!**\u002F*.lock\"\n",[63,6129,6130,6132],{"class":65,"line":5},[63,6131,6124],{"class":73},[63,6133,6134],{"class":77},"\"!**\u002Fdist\u002F**\"\n",[63,6136,6137,6140,6142],{"class":65,"line":136},[63,6138,6139],{"class":69},"  instructions",[63,6141,74],{"class":73},[63,6143,428],{"class":427},[63,6145,6146],{"class":65,"line":147},[63,6147,6148],{"class":77},"    - 用中文评论\n",[63,6150,6151],{"class":65,"line":155},[63,6152,6153],{"class":77},"    - 重点关注 SQL 注入、XSS、敏感信息泄露\n",[63,6155,6156],{"class":65,"line":166},[63,6157,6158],{"class":77},"    - 不要评论代码风格（用 ESLint 管）\n",[63,6160,6161],{"class":65,"line":176},[63,6162,6163],{"class":77},"    - 性能问题只在 O(n²) 以上才报\n",[863,6165,6166],{"start":99},[21,6167,6168],{},"提交一个 PR 测试，CodeRabbit 会在 30 秒内出评论",[14,6170,6172],{"id":6171},"第二步claude-code-自定义规则","第二步：Claude Code 自定义规则",[49,6174,6175],{},"CodeRabbit 是通用审查，团队特定规范用 Claude Code 补。",[49,6177,6178,6179,54],{},"创建 ",[41,6180,6181],{},".github\u002Fworkflows\u002Fai-review.yml",[34,6183,6185],{"className":57,"code":6184,"language":59,"meta":43,"style":43},"name: AI Review\non:\n  pull_request:\n    types: [opened, synchronize]\n\njobs:\n  claude-review:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions\u002Fcheckout@v4\n        with:\n          fetch-depth: 0\n\n      - name: Get changed files\n        id: changed\n        run: |\n          files=$(git diff --name-only ${{ github.event.pull_request.base.sha }}..${{ github.event.pull_request.head.sha }})\n          echo \"files=$files\" >> $GITHUB_OUTPUT\n\n      - name: Claude Code Review\n        uses: anthropics\u002Fclaude-code-action@v1\n        with:\n          anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}\n          prompt: |\n            审查以下文件的改动，重点关注：\n            1. 是否有未处理的 Promise rejection\n            2. 是否有 SQL 拼接（而非参数化查询）\n            3. 是否有 console.log 遗留在生产代码中\n            4. 是否有 hardcoded 密钥 \u002F token\n\n            输出格式：\n            - 🔴 严重问题（必须修复）：文件:行号 + 原因\n            - 🟡 建议（可选）：文件:行号 + 建议\n            - ✅ 没问题的文件不用提\n\n            改动的文件：\n            ${{ steps.changed.outputs.files }}\n",[41,6186,6187,6196,6202,6209,6226,6230,6236,6243,6251,6257,6267,6273,6283,6287,6298,6308,6316,6321,6326,6330,6341,6349,6355,6363,6371,6376,6381,6386,6391,6397,6402,6408,6414,6420,6426,6431,6437],{"__ignoreMap":43},[63,6188,6189,6191,6193],{"class":65,"line":66},[63,6190,70],{"class":69},[63,6192,74],{"class":73},[63,6194,6195],{"class":77},"AI Review\n",[63,6197,6198,6200],{"class":65,"line":81},[63,6199,85],{"class":84},[63,6201,88],{"class":73},[63,6203,6204,6207],{"class":65,"line":91},[63,6205,6206],{"class":69},"  pull_request",[63,6208,88],{"class":73},[63,6210,6211,6214,6216,6219,6221,6224],{"class":65,"line":99},[63,6212,6213],{"class":69},"    types",[63,6215,105],{"class":73},[63,6217,6218],{"class":77},"opened",[63,6220,257],{"class":73},[63,6222,6223],{"class":77},"synchronize",[63,6225,111],{"class":73},[63,6227,6228],{"class":65,"line":114},[63,6229,118],{"emptyLinePlaceholder":117},[63,6231,6232,6234],{"class":65,"line":121},[63,6233,124],{"class":69},[63,6235,88],{"class":73},[63,6237,6238,6241],{"class":65,"line":5},[63,6239,6240],{"class":69},"  claude-review",[63,6242,88],{"class":73},[63,6244,6245,6247,6249],{"class":65,"line":136},[63,6246,139],{"class":69},[63,6248,74],{"class":73},[63,6250,144],{"class":77},[63,6252,6253,6255],{"class":65,"line":147},[63,6254,179],{"class":69},[63,6256,88],{"class":73},[63,6258,6259,6261,6263,6265],{"class":65,"line":155},[63,6260,187],{"class":73},[63,6262,190],{"class":69},[63,6264,74],{"class":73},[63,6266,195],{"class":77},[63,6268,6269,6271],{"class":65,"line":166},[63,6270,213],{"class":69},[63,6272,88],{"class":73},[63,6274,6275,6278,6280],{"class":65,"line":176},[63,6276,6277],{"class":69},"          fetch-depth",[63,6279,74],{"class":73},[63,6281,6282],{"class":84},"0\n",[63,6284,6285],{"class":65,"line":184},[63,6286,118],{"emptyLinePlaceholder":117},[63,6288,6289,6291,6293,6295],{"class":65,"line":198},[63,6290,187],{"class":73},[63,6292,70],{"class":69},[63,6294,74],{"class":73},[63,6296,6297],{"class":77},"Get changed files\n",[63,6299,6300,6303,6305],{"class":65,"line":210},[63,6301,6302],{"class":69},"        id",[63,6304,74],{"class":73},[63,6306,6307],{"class":77},"changed\n",[63,6309,6310,6312,6314],{"class":65,"line":230},[63,6311,290],{"class":69},[63,6313,74],{"class":73},[63,6315,428],{"class":427},[63,6317,6318],{"class":65,"line":242},[63,6319,6320],{"class":77},"          files=$(git diff --name-only ${{ github.event.pull_request.base.sha }}..${{ github.event.pull_request.head.sha }})\n",[63,6322,6323],{"class":65,"line":270},[63,6324,6325],{"class":77},"          echo \"files=$files\" >> $GITHUB_OUTPUT\n",[63,6327,6328],{"class":65,"line":275},[63,6329,118],{"emptyLinePlaceholder":117},[63,6331,6332,6334,6336,6338],{"class":65,"line":287},[63,6333,187],{"class":73},[63,6335,70],{"class":69},[63,6337,74],{"class":73},[63,6339,6340],{"class":77},"Claude Code Review\n",[63,6342,6343,6345,6347],{"class":65,"line":298},[63,6344,396],{"class":69},[63,6346,74],{"class":73},[63,6348,401],{"class":77},[63,6350,6351,6353],{"class":65,"line":303},[63,6352,213],{"class":69},[63,6354,88],{"class":73},[63,6356,6357,6359,6361],{"class":65,"line":315},[63,6358,412],{"class":69},[63,6360,74],{"class":73},[63,6362,417],{"class":77},[63,6364,6365,6367,6369],{"class":65,"line":325},[63,6366,422],{"class":69},[63,6368,74],{"class":73},[63,6370,428],{"class":427},[63,6372,6373],{"class":65,"line":330},[63,6374,6375],{"class":77},"            审查以下文件的改动，重点关注：\n",[63,6377,6378],{"class":65,"line":342},[63,6379,6380],{"class":77},"            1. 是否有未处理的 Promise rejection\n",[63,6382,6383],{"class":65,"line":352},[63,6384,6385],{"class":77},"            2. 是否有 SQL 拼接（而非参数化查询）\n",[63,6387,6388],{"class":65,"line":360},[63,6389,6390],{"class":77},"            3. 是否有 console.log 遗留在生产代码中\n",[63,6392,6394],{"class":65,"line":6393},29,[63,6395,6396],{"class":77},"            4. 是否有 hardcoded 密钥 \u002F token\n",[63,6398,6400],{"class":65,"line":6399},30,[63,6401,118],{"emptyLinePlaceholder":117},[63,6403,6405],{"class":65,"line":6404},31,[63,6406,6407],{"class":77},"            输出格式：\n",[63,6409,6411],{"class":65,"line":6410},32,[63,6412,6413],{"class":77},"            - 🔴 严重问题（必须修复）：文件:行号 + 原因\n",[63,6415,6417],{"class":65,"line":6416},33,[63,6418,6419],{"class":77},"            - 🟡 建议（可选）：文件:行号 + 建议\n",[63,6421,6423],{"class":65,"line":6422},34,[63,6424,6425],{"class":77},"            - ✅ 没问题的文件不用提\n",[63,6427,6429],{"class":65,"line":6428},35,[63,6430,118],{"emptyLinePlaceholder":117},[63,6432,6434],{"class":65,"line":6433},36,[63,6435,6436],{"class":77},"            改动的文件：\n",[63,6438,6440],{"class":65,"line":6439},37,[63,6441,6442],{"class":77},"            ${{ steps.changed.outputs.files }}\n",[14,6444,6446],{"id":6445},"第三步严重问题阻断合并","第三步：严重问题阻断合并",[49,6448,6449],{},"在 review workflow 末尾加 commit status：",[34,6451,6453],{"className":57,"code":6452,"language":59,"meta":43,"style":43},"      - name: Set check status\n        if: steps.claude-review.outputs.severity == 'critical'\n        run: |\n          curl -X POST \\\n            -H \"Authorization: token ${{ secrets.GITHUB_TOKEN }}\" \\\n            -H \"Accept: application\u002Fvnd.github.v3+json\" \\\n            https:\u002F\u002Fapi.github.com\u002Frepos\u002F${{ github.repository }}\u002Fstatuses\u002F${{ github.event.pull_request.head.sha }} \\\n            -d '{\"state\": \"failure\", \"context\": \"AI Review \u002F Critical Issues\", \"description\": \"发现严重问题，请修复后重新提交\"}'\n",[41,6454,6455,6466,6475,6483,6488,6493,6498,6503],{"__ignoreMap":43},[63,6456,6457,6459,6461,6463],{"class":65,"line":66},[63,6458,187],{"class":73},[63,6460,70],{"class":69},[63,6462,74],{"class":73},[63,6464,6465],{"class":77},"Set check status\n",[63,6467,6468,6470,6472],{"class":65,"line":81},[63,6469,492],{"class":69},[63,6471,74],{"class":73},[63,6473,6474],{"class":77},"steps.claude-review.outputs.severity == 'critical'\n",[63,6476,6477,6479,6481],{"class":65,"line":91},[63,6478,290],{"class":69},[63,6480,74],{"class":73},[63,6482,428],{"class":427},[63,6484,6485],{"class":65,"line":99},[63,6486,6487],{"class":77},"          curl -X POST \\\n",[63,6489,6490],{"class":65,"line":114},[63,6491,6492],{"class":77},"            -H \"Authorization: token ${{ secrets.GITHUB_TOKEN }}\" \\\n",[63,6494,6495],{"class":65,"line":121},[63,6496,6497],{"class":77},"            -H \"Accept: application\u002Fvnd.github.v3+json\" \\\n",[63,6499,6500],{"class":65,"line":5},[63,6501,6502],{"class":77},"            https:\u002F\u002Fapi.github.com\u002Frepos\u002F${{ github.repository }}\u002Fstatuses\u002F${{ github.event.pull_request.head.sha }} \\\n",[63,6504,6505],{"class":65,"line":136},[63,6506,6507],{"class":77},"            -d '{\"state\": \"failure\", \"context\": \"AI Review \u002F Critical Issues\", \"description\": \"发现严重问题，请修复后重新提交\"}'\n",[49,6509,6510,6511,906],{},"在 GitHub 仓库设置 → Branch protection → Require status checks → 添加 ",[41,6512,6513],{},"AI Review \u002F Critical Issues",[14,6515,794],{"id":794},[49,6517,6518],{},"上线一个月后：",[697,6520,6521,6533],{},[700,6522,6523],{},[703,6524,6525,6527,6530],{},[706,6526,806],{},[706,6528,6529],{},"之前",[706,6531,6532],{},"之后",[716,6534,6535,6546,6557,6568],{},[703,6536,6537,6540,6543],{},[721,6538,6539],{},"PR 平均 review 时间",[721,6541,6542],{},"8 小时",[721,6544,6545],{},"2 小时",[703,6547,6548,6551,6554],{},[721,6549,6550],{},"人工 review 评论数",[721,6552,6553],{},"15 条\u002FPR",[721,6555,6556],{},"5 条\u002FPR",[703,6558,6559,6562,6565],{},[721,6560,6561],{},"合并后发现的 bug",[721,6563,6564],{},"3 个\u002F周",[721,6566,6567],{},"1 个\u002F周",[703,6569,6570,6573,6576],{},[721,6571,6572],{},"开发者满意度",[721,6574,6575],{},"6\u002F10",[721,6577,6578],{},"8\u002F10",[14,6580,861],{"id":861},[863,6582,6583,6589,6595,6601,6607],{},[21,6584,6585,6588],{},[868,6586,6587],{},"CodeRabbit 免费版限制","：开源仓库免费，私有仓库 $24\u002Fseat\u002Fmo。小团队可只给核心仓库开。",[21,6590,6591,6594],{},[868,6592,6593],{},"Claude Code API 成本","：每个 PR 约 $0.1-0.5（取决于改动量），月费 $50 以内可控。",[21,6596,6597,6600],{},[868,6598,6599],{},"不要让 AI 阻断全部问题","——只阻断安全类严重问题（SQL 注入、密钥泄露），否则开发者会绕过。",[21,6602,6603,6606],{},[868,6604,6605],{},"path_filters 很重要","——不加的话会 review lock 文件、dist 目录，浪费 API 调用。",[21,6608,6609,6615],{},[868,6610,6611,6612],{},"Claude Code Action 需要 ",[41,6613,6614],{},"fetch-depth: 0","——否则 git diff 拿不到完整历史。",[908,6617,6618],{},"html pre.shiki code .s9eBZ, html code.shiki .s9eBZ{--shiki-default:#22863A;--shiki-dark:#85E89D}html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}html pre.shiki code .szBVR, html code.shiki .szBVR{--shiki-default:#D73A49;--shiki-dark:#F97583}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);}",{"title":43,"searchDepth":91,"depth":91,"links":6620},[6621,6622,6623,6624,6625,6626,6627],{"id":16,"depth":81,"text":16},{"id":32,"depth":81,"text":32},{"id":6047,"depth":81,"text":6048},{"id":6171,"depth":81,"text":6172},{"id":6445,"depth":81,"text":6446},{"id":794,"depth":81,"text":794},{"id":861,"depth":81,"text":861},"review","\u002Fog\u002Fplaybook\u002Fai-pr-review.png","从零用 CodeRabbit + Claude Code + GitHub Actions 搭一条自动化 PR Review 流水线：提交 PR → AI 审查代码 → 评论区出报告 → 严重问题阻断合并。含完整配置和踩坑记录。",{},"\u002Fplaybook\u002Freview\u002Fai-pr-review-pipeline",[6634,930,6635],"coding\u002Freview\u002Fcoderabbit","coding\u002Freview\u002Fgreptile",{"title":6021,"description":6630},"playbook\u002Freview\u002Fai-pr-review-pipeline",[6639,939,6640,940],"PR Review","CodeRabbit","SsbL8A6w0qdO6rgCdogrJAPlq2NrJdkxw6n5D6zV5is",{"id":6643,"title":6644,"body":6645,"category":6628,"cover":7138,"description":7139,"extension":925,"meta":7140,"navigation":117,"path":7141,"published":7142,"relatedTools":7143,"seo":7145,"stem":7146,"tags":7147,"updated":7150,"__hash__":7151},"playbook\u002Fplaybook\u002Freview\u002Fai-code-review-workflow.md","AI 做 PR Review：开发者的 5 倍速 review 工作流",{"type":11,"value":6646,"toc":7114},[6647,6650,6661,6670,6673,6692,6696,6700,6760,6763,6767,6773,6779,6783,6786,6791,6797,6801,6807,6816,6820,6826,6830,6836,6840,6846,6852,6855,6858,6932,6943,6946,6972,6975,6979,6986,6990,6997,7001,7011,7015,7022,7024,7028,7031,7094,7097,7099,7111],[14,6648,6649],{"id":6649},"谁需要这套流程",[18,6651,6652,6655,6658],{},[21,6653,6654],{},"团队 leader \u002F staff engineer，每天 review 5-10 个 PR",[21,6656,6657],{},"独立维护者，开源项目 PR 多但人手少",[21,6659,6660],{},"想给自己的 PR 自查的开发者",[49,6662,6663,6666,6667,906],{},[868,6664,6665],{},"核心承诺","：让 AI 做\"第一道筛\"——把明显问题 + 一致性问题先过一遍，",[868,6668,6669],{},"人脑只看 AI 标出来的高价值地方",[14,6671,6672],{"id":6672},"不要做什么",[18,6674,6675,6682,6689],{},[21,6676,6677,6678,6681],{},"❌ 让 AI 写 ",[41,6679,6680],{},"LGTM"," 然后 merge——AI 会把烂代码 review 通过",[21,6683,6684,6685,6688],{},"❌ 让 AI ",[868,6686,6687],{},"决定","合不合并——AI 只做建议，决策权在人",[21,6690,6691],{},"❌ 把 PR 全文丢给 ChatGPT 网页版让它\"看看\"——上下文不够，结论不可信",[14,6693,6695],{"id":6694},"工作流4-步法","工作流：4 步法",[1191,6697,6699],{"id":6698},"step-1-准备-review-上下文2-min","Step 1 — 准备 review 上下文（2 min）",[34,6701,6703],{"className":1807,"code":6702,"language":1809,"meta":43,"style":43},"# 在 PR 分支\ngit fetch origin main\ngit diff main...HEAD > \u002Ftmp\u002Fpr.diff\ngit log main..HEAD --pretty=format:'%h %s%n%b' > \u002Ftmp\u002Fpr.commits\n",[41,6704,6705,6710,6723,6739],{"__ignoreMap":43},[63,6706,6707],{"class":65,"line":66},[63,6708,6709],{"class":1056},"# 在 PR 分支\n",[63,6711,6712,6714,6717,6720],{"class":65,"line":81},[63,6713,1980],{"class":1106},[63,6715,6716],{"class":77}," fetch",[63,6718,6719],{"class":77}," origin",[63,6721,6722],{"class":77}," main\n",[63,6724,6725,6727,6730,6733,6736],{"class":65,"line":91},[63,6726,1980],{"class":1106},[63,6728,6729],{"class":77}," diff",[63,6731,6732],{"class":77}," main...HEAD",[63,6734,6735],{"class":427}," >",[63,6737,6738],{"class":77}," \u002Ftmp\u002Fpr.diff\n",[63,6740,6741,6743,6746,6749,6752,6755,6757],{"class":65,"line":99},[63,6742,1980],{"class":1106},[63,6744,6745],{"class":77}," log",[63,6747,6748],{"class":77}," main..HEAD",[63,6750,6751],{"class":84}," --pretty=format:",[63,6753,6754],{"class":77},"'%h %s%n%b'",[63,6756,6735],{"class":427},[63,6758,6759],{"class":77}," \u002Ftmp\u002Fpr.commits\n",[49,6761,6762],{},"进入项目根，开 Claude Code 或 Cursor。",[1191,6764,6766],{"id":6765},"step-2-让-ai-分类改动5-min","Step 2 — 让 AI 分类改动（5 min）",[34,6768,6771],{"className":6769,"code":6770,"language":39,"meta":43},[37],"请分析本分支相对 main 的改动（git diff main...HEAD），按类型归类：\n\nA. 业务逻辑改动（新功能 \u002F bug 修复 \u002F 行为变更）\nB. 重构（不改行为）\nC. 测试改动\nD. 配置 \u002F 依赖\nE. 文档\n\n输出 markdown 表格：类型 | 文件 | 改动摘要（1 句）\n\n特别标出：\n- 是否有\"业务逻辑改动\"但没有对应测试\n- 是否有\"重构\"但行为可能被无意改变\n- 是否有任何 secret \u002F API key 出现在 diff 里\n",[41,6772,6770],{"__ignoreMap":43},[49,6774,6775,6778],{},[868,6776,6777],{},"这一步的产出物极其有价值","——你瞬间知道这个 PR 的盘子有多大、风险点在哪。",[1191,6780,6782],{"id":6781},"step-3-深度审视10-30-min","Step 3 — 深度审视（10-30 min）",[49,6784,6785],{},"针对 Step 2 标出的高风险项，挨个深问：",[6787,6788,6790],"h4",{"id":6789},"_31-业务逻辑深查","3.1 业务逻辑深查",[34,6792,6795],{"className":6793,"code":6794,"language":39,"meta":43},[37],"对于改动【文件 X 第 Y-Z 行】，请回答：\n\n1. 改动前的行为是什么？（读 git blame \u002F git show HEAD~1）\n2. 改动后的行为是什么？\n3. 这两个行为的差异是否符合 commit message \u002F PR description 的承诺？\n4. 有没有 edge case 没考虑？（null \u002F 空数组 \u002F 极大值 \u002F 并发）\n5. 有没有可能影响其他调用方？\n\n每条都要给确凿依据，不要\"我觉得\"。\n",[41,6796,6794],{"__ignoreMap":43},[6787,6798,6800],{"id":6799},"_32-一致性扫描","3.2 一致性扫描",[34,6802,6805],{"className":6803,"code":6804,"language":39,"meta":43},[37],"请扫描整个项目，看本 PR 的改动有没有\"漏改\"：\n\n- 如果 PR 改了某个函数的签名，所有调用方都改了吗？\n- 如果 PR 加了新的常量，类似的常量定义是否同步更新？\n- 如果 PR 改了某个文件的 pattern，类似文件是否需要同样改？\n\n逐条列出\"可能漏改但 PR 里没改\"的地方，给文件:行号。\n",[41,6806,6804],{"__ignoreMap":43},[49,6808,6809,6812,6813,906],{},[868,6810,6811],{},"这是 AI 比人类强的地方","——人类做 PR review 容易盯着 diff 看，",[868,6814,6815],{},"AI 会去看 diff 之外的地方",[6787,6817,6819],{"id":6818},"_33-测试-review","3.3 测试 review",[34,6821,6824],{"className":6822,"code":6823,"language":39,"meta":43},[37],"对于本 PR 中的测试改动 + 新增测试，请评估：\n\n1. 测试是否真的覆盖了\"业务逻辑改动\"？还是只覆盖了 happy path？\n2. 测试的断言是否够强？（不要只 assert 不抛异常）\n3. 有没有\"修测试让它通过\"的痕迹（注释掉的 assert \u002F 改宽容的 matcher）？\n4. 哪些 edge case 没被测到？\n\n输出：测试覆盖度评估 + 建议补的测试用例（伪代码）\n",[41,6825,6823],{"__ignoreMap":43},[6787,6827,6829],{"id":6828},"_34-风险与回滚","3.4 风险与回滚",[34,6831,6834],{"className":6832,"code":6833,"language":39,"meta":43},[37],"假设这个 PR 合并到生产后出了严重问题，回滚成本如何？\n\n- 单纯 revert 这个 commit 能恢复吗？\n- 有没有不可逆的改动（DB 迁移 \u002F 数据格式变更 \u002F 删了字段）？\n- 有没有\"客户端已经发了新版本，服务端不能简单回滚\"的耦合？\n\n如果回滚成本高，建议拆 PR 或加 feature flag。\n",[41,6835,6833],{"__ignoreMap":43},[1191,6837,6839],{"id":6838},"step-4-整合-review-反馈5-min","Step 4 — 整合 review 反馈（5 min）",[34,6841,6844],{"className":6842,"code":6843,"language":39,"meta":43},[37],"请整合前面 3.1-3.4 的所有发现，输出一份最终 review 评论，格式：\n\n## ✅ 看起来没问题的部分\n（简短列出）\n\n## ⚠️ 需要 PR author 回应的问题\n（每条带文件:行号 + 具体问题）\n\n## 🛑 阻塞合并的问题\n（如有，必须解决才能合）\n\n## 💡 建议（非阻塞）\n（可选优化）\n\n语气专业、具体，不要客套话。\n",[41,6845,6843],{"__ignoreMap":43},[49,6847,6848,6851],{},[868,6849,6850],{},"人工 review 这份 markdown","——70% 的内容你直接 copy 到 PR 评论区。剩下 30% 自己再补充判断和上下文。",[6853,6854],"hr",{},[14,6856,6857],{"id":6857},"实战时间分配",[697,6859,6860,6873],{},[700,6861,6862],{},[703,6863,6864,6867,6870],{},[706,6865,6866],{},"步骤",[706,6868,6869],{},"耗时",[706,6871,6872],{},"谁做",[716,6874,6875,6886,6897,6908,6918],{},[703,6876,6877,6880,6883],{},[721,6878,6879],{},"1 准备",[721,6881,6882],{},"2 min",[721,6884,6885],{},"人",[703,6887,6888,6891,6894],{},[721,6889,6890],{},"2 分类",[721,6892,6893],{},"5 min",[721,6895,6896],{},"AI",[703,6898,6899,6902,6905],{},[721,6900,6901],{},"3 深查",[721,6903,6904],{},"10-30 min",[721,6906,6907],{},"AI 主导，人 review",[703,6909,6910,6913,6915],{},[721,6911,6912],{},"4 整合",[721,6914,6893],{},[721,6916,6917],{},"AI 输出，人调整",[703,6919,6920,6925,6930],{},[721,6921,6922],{},[868,6923,6924],{},"合计",[721,6926,6927],{},[868,6928,6929],{},"~30 min \u002F PR",[721,6931],{},[49,6933,6934,6935,6938,6939,6942],{},"对比纯人工 review 一个中型 PR（~500 行 diff）通常 60-90 min，",[868,6936,6937],{},"省一半时间","，且",[868,6940,6941],{},"漏掉问题更少","（AI 看的范围比人广）。",[14,6944,6945],{"id":6945},"工具选择",[18,6947,6948,6953,6959,6965],{},[21,6949,6950,6952],{},[868,6951,938],{}," ← 首选。能跑命令读 git history，做\"全局一致性扫描\"最强",[21,6954,6955,6958],{},[868,6956,6957],{},"Cursor + @codebase"," ← 中型项目够用，便宜",[21,6960,6961,6964],{},[868,6962,6963],{},"Augment"," ← 团队场景，原生集成 PR 流程",[21,6966,6967,6968,6971],{},"ChatGPT 网页版 ← ",[868,6969,6970],{},"不推荐","，上下文不够",[14,6973,6974],{"id":6974},"踩坑",[1191,6976,6978],{"id":6977},"_1-不要给-ai-pr-description作为唯一上下文","1. 不要给 AI \"PR description\"作为唯一上下文",[49,6980,6981,6982,6985],{},"PR description 是作者自己写的，",[868,6983,6984],{},"不一定准","。让 AI 自己读 diff，不要被 description 带偏。",[1191,6987,6989],{"id":6988},"_2-警惕-ai-的看起来-lgtm","2. 警惕 AI 的\"看起来 LGTM\"",[49,6991,6992,6993,6996],{},"AI 倾向于温和。如果它说\"看起来没问题\"，",[868,6994,6995],{},"强制让它给出 3 条具体担忧","——总能挖出来。",[1191,6998,7000],{"id":6999},"_3-别完全替代人脑","3. 别完全替代人脑",[49,7002,7003,7004,7007,7008,906],{},"AI 的 review 是",[868,7005,7006],{},"第一道筛","，不是终审。",[868,7009,7010],{},"关键判断（架构是否合理 \u002F 是否符合产品方向）只能人做",[1191,7012,7014],{"id":7013},"_4-注意-token-预算","4. 注意 token 预算",[49,7016,7017,7018,7021],{},"大 PR review 容易烧 $1-5。",[868,7019,7020],{},"对小 PR（\u003C 50 行）直接人脑 review","，不值得开 AI。",[6853,7023],{},[14,7025,7027],{"id":7026},"bonus把这套做成-git-hook","Bonus：把这套做成 git hook",[49,7029,7030],{},"把 Step 2-3 写成一个 shell 脚本 + Claude Code prompt 文件，挂在 PR 创建\u002F更新时自动跑：",[34,7032,7034],{"className":1807,"code":7033,"language":1809,"meta":43,"style":43},"# .github\u002Fhooks\u002Fpr-review.sh （示意）\n#!\u002Fbin\u002Fbash\ngit diff main...HEAD > \u002Ftmp\u002Fpr.diff\nclaude-code --prompt-file=.github\u002Fhooks\u002Freview-prompt.md \\\n            --output-file=\u002Ftmp\u002Freview.md\ngh pr comment $PR_NUMBER --body-file \u002Ftmp\u002Freview.md\n",[41,7035,7036,7041,7046,7058,7069,7074],{"__ignoreMap":43},[63,7037,7038],{"class":65,"line":66},[63,7039,7040],{"class":1056},"# .github\u002Fhooks\u002Fpr-review.sh （示意）\n",[63,7042,7043],{"class":65,"line":81},[63,7044,7045],{"class":1056},"#!\u002Fbin\u002Fbash\n",[63,7047,7048,7050,7052,7054,7056],{"class":65,"line":91},[63,7049,1980],{"class":1106},[63,7051,6729],{"class":77},[63,7053,6732],{"class":77},[63,7055,6735],{"class":427},[63,7057,6738],{"class":77},[63,7059,7060,7063,7066],{"class":65,"line":99},[63,7061,7062],{"class":1106},"claude-code",[63,7064,7065],{"class":84}," --prompt-file=.github\u002Fhooks\u002Freview-prompt.md",[63,7067,7068],{"class":84}," \\\n",[63,7070,7071],{"class":65,"line":114},[63,7072,7073],{"class":84},"            --output-file=\u002Ftmp\u002Freview.md\n",[63,7075,7076,7079,7082,7085,7088,7091],{"class":65,"line":121},[63,7077,7078],{"class":1106},"gh",[63,7080,7081],{"class":77}," pr",[63,7083,7084],{"class":77}," comment",[63,7086,7087],{"class":73}," $PR_NUMBER ",[63,7089,7090],{"class":84},"--body-file",[63,7092,7093],{"class":77}," \u002Ftmp\u002Freview.md\n",[49,7095,7096],{},"完整 GitHub Actions 工作流即将整理上线。",[6853,7098],{},[49,7100,7101,7102,7106,7107,906],{},"继续阅读：",[6058,7103,7105],{"href":7104},"\u002Fplaybook\u002Fonboarding\u002Flegacy-codebase-onboarding","陌生项目 onboarding 工作流","、",[6058,7108,7110],{"href":7109},"\u002Fplaybook\u002Frefactor\u002Flarge-refactor-with-ai-agent","大型重构工作流",[908,7112,7113],{},"html pre.shiki code .sJ8bj, html code.shiki .sJ8bj{--shiki-default:#6A737D;--shiki-dark:#6A737D}html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}html pre.shiki code .szBVR, html code.shiki .szBVR{--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}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 .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}",{"title":43,"searchDepth":91,"depth":91,"links":7115},[7116,7117,7118,7129,7130,7131,7137],{"id":6649,"depth":81,"text":6649},{"id":6672,"depth":81,"text":6672},{"id":6694,"depth":81,"text":6695,"children":7119},[7120,7121,7122,7128],{"id":6698,"depth":91,"text":6699},{"id":6765,"depth":91,"text":6766},{"id":6781,"depth":91,"text":6782,"children":7123},[7124,7125,7126,7127],{"id":6789,"depth":99,"text":6790},{"id":6799,"depth":99,"text":6800},{"id":6818,"depth":99,"text":6819},{"id":6828,"depth":99,"text":6829},{"id":6838,"depth":91,"text":6839},{"id":6857,"depth":81,"text":6857},{"id":6945,"depth":81,"text":6945},{"id":6974,"depth":81,"text":6974,"children":7132},[7133,7134,7135,7136],{"id":6977,"depth":91,"text":6978},{"id":6988,"depth":91,"text":6989},{"id":6999,"depth":91,"text":7000},{"id":7013,"depth":91,"text":7014},{"id":7026,"depth":81,"text":7027},"\u002Fog\u002Fplaybook\u002Fai-code-review.png","用 Claude Code \u002F Cursor 给 PR 做第一道 review 的标准化工作流：风险扫描、命名一致性、测试覆盖、文档同步。",{},"\u002Fplaybook\u002Freview\u002Fai-code-review-workflow","2026-06-01",[930,2041,7144],"coding\u002Fagent\u002Faugment",{"title":6644,"description":7139},"playbook\u002Freview\u002Fai-code-review-workflow",[7148,7149,938,994],"code review","PR","2026-06-15","t09WfO0oBlGLWe6k_9Q9vCP5UiVK2HxMSQqK1Ipa43Q",{"id":7153,"title":7154,"body":7155,"category":7772,"cover":7773,"description":7774,"extension":925,"meta":7775,"navigation":117,"path":7109,"published":7776,"relatedTools":7777,"seo":7779,"stem":7780,"tags":7781,"updated":7785,"__hash__":7786},"playbook\u002Fplaybook\u002Frefactor\u002Flarge-refactor-with-ai-agent.md","用 AI Agent 跑大型重构：不翻车工作流",{"type":11,"value":7156,"toc":7745},[7157,7159,7166,7183,7189,7191,7194,7201,7204,7207,7224,7227,7234,7241,7261,7267,7274,7281,7283,7287,7291,7294,7300,7310,7314,7320,7326,7330,7364,7372,7435,7440,7444,7447,7453,7458,7478,7482,7485,7534,7540,7543,7547,7571,7573,7575,7637,7639,7643,7650,7654,7660,7663,7673,7677,7684,7688,7694,7696,7700,7707,7726,7735,7737,7742],[14,7158,16],{"id":16},[49,7160,7161,7162,7165],{},"任何",[868,7163,7164],{},"符合下列任意一条","的重构都属于\"大型重构\"：",[18,7167,7168,7171,7174,7177,7180],{},[21,7169,7170],{},"影响超过 30 个文件",[21,7172,7173],{},"涉及 API 签名变更（调用方需要同步改）",[21,7175,7176],{},"涉及数据格式变更（DB schema \u002F 序列化协议）",[21,7178,7179],{},"跨模块、跨进程",[21,7181,7182],{},"需要保留向后兼容",[49,7184,7185,7188],{},[868,7186,7187],{},"这类任务用 IDE 手改太慢，让 AI 一次性跑完又容易翻车。"," 本工作流的目标是：让 AI 跑大头，但每一步可验证、可回滚。",[6853,7190],{},[14,7192,7193],{"id":7193},"核心原则",[1191,7195,7197,7198],{"id":7196},"_1-任务必须拆","1. ",[868,7199,7200],{},"任务必须拆",[49,7202,7203],{},"不要给 AI：\"把整个项目从 Pinia 1 迁到 Pinia 2\"。",[49,7205,7206],{},"要给 AI：",[18,7208,7209,7212,7215,7218,7221],{},[21,7210,7211],{},"Step A: 找出所有用 Pinia 1 API 的文件，输出清单",[21,7213,7214],{},"Step B: 改第 1-10 个文件，跑测试",[21,7216,7217],{},"Step C: 改第 11-20 个，跑测试",[21,7219,7220],{},"...",[21,7222,7223],{},"Step Z: 全跑通后删 Pinia 1 依赖",[49,7225,7226],{},"每步独立 commit。",[1191,7228,7230,7231],{"id":7229},"_2-每步都有-checkpoint","2. ",[868,7232,7233],{},"每步都有 checkpoint",[49,7235,7236,7237,7240],{},"checkpoint = 一个",[868,7238,7239],{},"可机器验证","的状态。比如：",[18,7242,7243,7249,7254],{},[21,7244,7245,7248],{},[41,7246,7247],{},"pnpm typecheck"," 通过",[21,7250,7251,7248],{},[41,7252,7253],{},"pnpm test",[21,7255,7256,7257,7260],{},"启动 dev server 后 ",[41,7258,7259],{},"curl \u002Fhealth"," 返 200",[49,7262,7263,7266],{},[868,7264,7265],{},"没有 checkpoint 的步骤不要放进工作流","——因为你没法知道它对不对。",[1191,7268,7270,7271],{"id":7269},"_3-git-隔离","3. ",[868,7272,7273],{},"git 隔离",[49,7275,7276,7277,7280],{},"每个大任务开独立分支。每个 step 一个 commit。\n",[868,7278,7279],{},"不要让 AI 直接 push 到主干","，永远先 PR。",[6853,7282],{},[14,7284,7286],{"id":7285},"完整工作流6-阶段","完整工作流：6 阶段",[1191,7288,7290],{"id":7289},"阶段-1调研人工-ai30-min","阶段 1：调研（人工 + AI，30 min）",[49,7292,7293],{},"让 AI 帮你回答 4 个问题：",[34,7295,7298],{"className":7296,"code":7297,"language":39,"meta":43},[37],"针对【重构目标】，请回答：\n\n1. 影响面：哪些文件会被修改？给出完整列表 + 行数估算\n2. 阻塞依赖：有没有外部库 \u002F API 升级也需要做？\n3. 不可逆操作：有没有数据迁移 \u002F 二进制格式变更？\n4. 测试覆盖：现有测试能 cover 多少？哪些路径无测试？\n\n不要开始改代码，只产出调研报告。\n",[41,7299,7297],{"__ignoreMap":43},[49,7301,7302,7305,7306,7309],{},[868,7303,7304],{},"输出物","：一份 markdown 报告。",[868,7307,7308],{},"人工 review 这份报告再决定是否继续","——很多时候你会发现\"这个重构不该做\"或\"该用别的方案\"。",[1191,7311,7313],{"id":7312},"阶段-2拆任务人工-ai20-min","阶段 2：拆任务（人工 + AI，20 min）",[34,7315,7318],{"className":7316,"code":7317,"language":39,"meta":43},[37],"基于调研报告，请把整个重构拆成 N 个 step。每个 step 满足：\n\n- 影响 ≤ 5 个文件\n- 有明确的 checkpoint（typecheck \u002F 测试 \u002F 编译）\n- 每步独立可 commit、可回滚\n- step 之间有依赖时显式标出\n\n输出 markdown 表格：step 编号 | 描述 | 影响文件 | checkpoint | 依赖 step\n",[41,7319,7317],{"__ignoreMap":43},[49,7321,7322,7325],{},[868,7323,7324],{},"人工 review 这份拆解","——如果某 step 影响超过 5 文件，强制再拆。",[1191,7327,7329],{"id":7328},"阶段-3建脚手架ai10-min","阶段 3：建脚手架（AI，10 min）",[34,7331,7333],{"className":1807,"code":7332,"language":1809,"meta":43,"style":43},"git checkout -b refactor\u002Fpinia-2-migration\nmkdir -p .refactor\n# 把拆解后的 task list 存进去\n",[41,7334,7335,7348,7359],{"__ignoreMap":43},[63,7336,7337,7339,7342,7345],{"class":65,"line":66},[63,7338,1980],{"class":1106},[63,7340,7341],{"class":77}," checkout",[63,7343,7344],{"class":84}," -b",[63,7346,7347],{"class":77}," refactor\u002Fpinia-2-migration\n",[63,7349,7350,7353,7356],{"class":65,"line":81},[63,7351,7352],{"class":1106},"mkdir",[63,7354,7355],{"class":84}," -p",[63,7357,7358],{"class":77}," .refactor\n",[63,7360,7361],{"class":65,"line":91},[63,7362,7363],{"class":1056},"# 把拆解后的 task list 存进去\n",[49,7365,7366,54,7369],{},[868,7367,7368],{},"关键文件",[41,7370,7371],{},".refactor\u002FSTATUS.md",[34,7373,7375],{"className":1913,"code":7374,"language":1915,"meta":43,"style":43},"# Pinia 2 Migration Status\n\n- [ ] step-01: 列出所有 Pinia 1 调用点\n- [ ] step-02: 改 stores\u002Fuser.ts + 调用方\n- [ ] step-03: 改 stores\u002Fcart.ts + 调用方\n...\n\n## Checkpoints\n- [ ] all `pnpm typecheck` passes\n- [ ] all `pnpm test` passes\n- [ ] dev server boots\n- [ ] e2e smoke test passes\n",[41,7376,7377,7382,7386,7391,7396,7401,7406,7410,7415,7420,7425,7430],{"__ignoreMap":43},[63,7378,7379],{"class":65,"line":66},[63,7380,7381],{},"# Pinia 2 Migration Status\n",[63,7383,7384],{"class":65,"line":81},[63,7385,118],{"emptyLinePlaceholder":117},[63,7387,7388],{"class":65,"line":91},[63,7389,7390],{},"- [ ] step-01: 列出所有 Pinia 1 调用点\n",[63,7392,7393],{"class":65,"line":99},[63,7394,7395],{},"- [ ] step-02: 改 stores\u002Fuser.ts + 调用方\n",[63,7397,7398],{"class":65,"line":114},[63,7399,7400],{},"- [ ] step-03: 改 stores\u002Fcart.ts + 调用方\n",[63,7402,7403],{"class":65,"line":121},[63,7404,7405],{},"...\n",[63,7407,7408],{"class":65,"line":5},[63,7409,118],{"emptyLinePlaceholder":117},[63,7411,7412],{"class":65,"line":136},[63,7413,7414],{},"## Checkpoints\n",[63,7416,7417],{"class":65,"line":147},[63,7418,7419],{},"- [ ] all `pnpm typecheck` passes\n",[63,7421,7422],{"class":65,"line":155},[63,7423,7424],{},"- [ ] all `pnpm test` passes\n",[63,7426,7427],{"class":65,"line":166},[63,7428,7429],{},"- [ ] dev server boots\n",[63,7431,7432],{"class":65,"line":176},[63,7433,7434],{},"- [ ] e2e smoke test passes\n",[49,7436,7437,906],{},[868,7438,7439],{},"让 AI 每完成一步就更新这份文件",[1191,7441,7443],{"id":7442},"阶段-4执行ai-主导1-n-小时","阶段 4：执行（AI 主导，1-N 小时）",[49,7445,7446],{},"每个 step 用同一个 prompt 模板：",[34,7448,7451],{"className":7449,"code":7450,"language":39,"meta":43},[37],"现在执行 step-{N}：{描述}。\n\n要求：\n1. 只改清单内的文件，不要碰其他文件\n2. 改完后跑 checkpoint：{checkpoint 命令}\n3. 如果 checkpoint 失败，先 debug 失败原因，再决定是修改还是回滚\n4. 成功后：\n   a. 在 .refactor\u002FSTATUS.md 把这一项打勾\n   b. git add 改动 + git commit -m \"refactor(step-{N}): {描述}\"\n5. 不要继续下一步，等我确认\n",[41,7452,7450],{"__ignoreMap":43},[49,7454,7455],{},[868,7456,7457],{},"关键纪律：",[18,7459,7460,7466,7472],{},[21,7461,7462,7465],{},[868,7463,7464],{},"每步 commit","，不要憋一个大 commit",[21,7467,7468,7471],{},[868,7469,7470],{},"每步等人确认","，AI 不要连续跑 5 步——很容易在第 3 步偏轨道但你没发现",[21,7473,7474,7475],{},"如果 step 失败 2 次，",[868,7476,7477],{},"停下来人工介入",[1191,7479,7481],{"id":7480},"阶段-5合并验证人工-ai30-min","阶段 5：合并验证（人工 + AI，30 min）",[49,7483,7484],{},"所有 step 完成后：",[34,7486,7488],{"className":1807,"code":7487,"language":1809,"meta":43,"style":43},"# 1. 跑全套测试\npnpm test\npnpm e2e\n\n# 2. 启动 dev server，手测核心路径\npnpm dev\n\n# 3. 让 AI 做\"自我代码审查\"\n",[41,7489,7490,7495,7502,7509,7513,7518,7525,7529],{"__ignoreMap":43},[63,7491,7492],{"class":65,"line":66},[63,7493,7494],{"class":1056},"# 1. 跑全套测试\n",[63,7496,7497,7499],{"class":65,"line":81},[63,7498,265],{"class":1106},[63,7500,7501],{"class":77}," test\n",[63,7503,7504,7506],{"class":65,"line":91},[63,7505,265],{"class":1106},[63,7507,7508],{"class":77}," e2e\n",[63,7510,7511],{"class":65,"line":99},[63,7512,118],{"emptyLinePlaceholder":117},[63,7514,7515],{"class":65,"line":114},[63,7516,7517],{"class":1056},"# 2. 启动 dev server，手测核心路径\n",[63,7519,7520,7522],{"class":65,"line":121},[63,7521,265],{"class":1106},[63,7523,7524],{"class":77}," dev\n",[63,7526,7527],{"class":65,"line":5},[63,7528,118],{"emptyLinePlaceholder":117},[63,7530,7531],{"class":65,"line":136},[63,7532,7533],{"class":1056},"# 3. 让 AI 做\"自我代码审查\"\n",[34,7535,7538],{"className":7536,"code":7537,"language":39,"meta":43},[37],"请审视本分支相对 main 的所有改动，找出：\n- 不一致：相同模式有没有漏改的地方\n- 风险：有没有可能的运行时错误（null deref、type 转换失败）\n- 兼容：旧代码调用新代码会不会出问题\n- 性能：有没有 N+1 查询、不必要的循环\n\n输出 markdown 报告，每条带文件路径和行号。\n",[41,7539,7537],{"__ignoreMap":43},[49,7541,7542],{},"人工 review 报告，必要时再开新 step 修补。",[1191,7544,7546],{"id":7545},"阶段-6发布人工时间不定","阶段 6：发布（人工，时间不定）",[18,7548,7549,7552,7557,7560],{},[21,7550,7551],{},"发 PR，标题写清楚 \"Refactor: ...\" + 影响面摘要",[21,7553,7554],{},[868,7555,7556],{},"PR description 直接贴 .refactor\u002FSTATUS.md 内容",[21,7558,7559],{},"至少一个 reviewer 看",[21,7561,7562,7563,7566,7567,7570],{},"合并后保留 ",[41,7564,7565],{},".refactor\u002F"," 目录在 git 里",[868,7568,7569],{},"作为历史记录","——下次类似重构可参考",[6853,7572],{},[14,7574,6945],{"id":6945},[697,7576,7577,7589],{},[700,7578,7579],{},[703,7580,7581,7584,7587],{},[706,7582,7583],{},"阶段",[706,7585,7586],{},"推荐工具",[706,7588,5815],{},[716,7590,7591,7601,7615,7626],{},[703,7592,7593,7596,7598],{},[721,7594,7595],{},"调研 \u002F 拆任务",[721,7597,938],{},[721,7599,7600],{},"自由探索能力强",[703,7602,7603,7606,7612],{},[721,7604,7605],{},"执行 step",[721,7607,7608,7611],{},[868,7609,7610],{},"Windsurf Cascade"," 或 Claude Code",[721,7613,7614],{},"长任务能力",[703,7616,7617,7620,7623],{},[721,7618,7619],{},"单步快速改",[721,7621,7622],{},"Cursor Composer",[721,7624,7625],{},"Tab 流畅",[703,7627,7628,7631,7634],{},[721,7629,7630],{},"自我代码审查",[721,7632,7633],{},"Claude Code（独立 session）",[721,7635,7636],{},"切换 context 更客观",[14,7638,6974],{"id":6974},[1191,7640,7642],{"id":7641},"_1-不要相信-ai-说我已完成","1. 不要相信 AI 说\"我已完成\"",[49,7644,7645,7646,7649],{},"每步都要",[868,7647,7648],{},"自己跑 checkpoint"," 确认，不能信 AI 的口头报告。",[1191,7651,7653],{"id":7652},"_2-别让-ai-改测试","2. 别让 AI 改测试",[49,7655,7656,7657,906],{},"如果跑测试失败，AI 倾向于\"修测试让它通过\"——这是",[868,7658,7659],{},"最危险的反模式",[49,7661,7662],{},"明确禁止：",[7664,7665,7666],"blockquote",{},[49,7667,7668,7669,7672],{},"\"如果测试失败，分析为什么失败。",[868,7670,7671],{},"不允许修改测试文件","——除非测试本身有 bug 且你能在改之前向我证明。\"",[1191,7674,7676],{"id":7675},"_3-不要跨大版本改依赖","3. 不要跨大版本改依赖",[49,7678,7679,7680,7683],{},"step 中遇到\"顺便升一下这个库\"的诱惑——",[868,7681,7682],{},"说不","。把依赖升级单独开 PR。",[1191,7685,7687],{"id":7686},"_4-注意-token-成本","4. 注意 token 成本",[49,7689,7690,7691,906],{},"跨 50 文件的重构容易烧掉 $10-50 的 token（Claude Sonnet 4.5）。",[868,7692,7693],{},"先估预算再开始",[6853,7695],{},[14,7697,7699],{"id":7698},"实战案例本站项目从-src-改根级","实战案例：本站项目从 src\u002F 改根级",[49,7701,7702,7703,7706],{},"我们自己用这套工作流把姊妹站 oltools.net 从 ",[41,7704,7705],{},"src\u002F"," 根布局改成 Nuxt 4 标准布局，影响 200+ 文件。",[18,7708,7709,7712,7715,7718,7721],{},[21,7710,7711],{},"调研：30 min",[21,7713,7714],{},"拆任务：拆成 14 个 step",[21,7716,7717],{},"执行：4 小时（Windsurf Cascade，重度人工 review）",[21,7719,7720],{},"合并验证：1 小时",[21,7722,7723],{},[868,7724,7725],{},"零线上事故",[49,7727,7728,7729,7734],{},"完整 commit 历史可看 ",[6058,7730,7733],{"href":7731,"rel":7732},"https:\u002F\u002Fgithub.com\u002Faiho\u002Foltools",[6062],"oltools.net 仓库","（即将开放）。",[6853,7736],{},[49,7738,7101,7739,906],{},[6058,7740,7741],{"href":7141},"AI 做 PR review 工作流",[908,7743,7744],{},"html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .sJ8bj, html code.shiki .sJ8bj{--shiki-default:#6A737D;--shiki-dark:#6A737D}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);}",{"title":43,"searchDepth":91,"depth":91,"links":7746},[7747,7748,7756,7764,7765,7771],{"id":16,"depth":81,"text":16},{"id":7193,"depth":81,"text":7193,"children":7749},[7750,7752,7754],{"id":7196,"depth":91,"text":7751},"1. 任务必须拆",{"id":7229,"depth":91,"text":7753},"2. 每步都有 checkpoint",{"id":7269,"depth":91,"text":7755},"3. git 隔离",{"id":7285,"depth":81,"text":7286,"children":7757},[7758,7759,7760,7761,7762,7763],{"id":7289,"depth":91,"text":7290},{"id":7312,"depth":91,"text":7313},{"id":7328,"depth":91,"text":7329},{"id":7442,"depth":91,"text":7443},{"id":7480,"depth":91,"text":7481},{"id":7545,"depth":91,"text":7546},{"id":6945,"depth":81,"text":6945},{"id":6974,"depth":81,"text":6974,"children":7766},[7767,7768,7769,7770],{"id":7641,"depth":91,"text":7642},{"id":7652,"depth":91,"text":7653},{"id":7675,"depth":91,"text":7676},{"id":7686,"depth":91,"text":7687},{"id":7698,"depth":81,"text":7699},"refactor","\u002Fog\u002Fplaybook\u002Flarge-refactor.png","跨 50+ 文件的重构任务如何让 AI Agent 安全完成？任务拆解、checkpoint、git 隔离、验证策略与回滚预案的完整 playbook。",{},"2026-05-15",[930,7778,7144],"coding\u002Fide\u002Fwindsurf",{"title":7154,"description":7774},"playbook\u002Frefactor\u002Flarge-refactor-with-ai-agent",[7782,7783,938,7784,994],"重构","Agent","Windsurf","2026-06-12","wtg_idmRZ3PBBQlCMTaD7GBi9U74mmV8uBlvN5dIArQ",{"id":7788,"title":7789,"body":7790,"category":2757,"cover":8232,"description":8233,"extension":925,"meta":8234,"navigation":117,"path":7104,"published":8235,"relatedTools":8236,"seo":8237,"stem":8238,"tags":8239,"updated":8242,"__hash__":8243},"playbook\u002Fplaybook\u002Fonboarding\u002Flegacy-codebase-onboarding.md","用 AI 接手老代码：陌生项目 onboarding 工作流",{"type":11,"value":7791,"toc":8219},[7792,7794,7808,7813,7817,7823,7829,7831,7835,7900,7903,7908,7917,7923,7925,7929,7932,7938,7954,7956,7960,7966,7976,7985,7987,7991,7998,8004,8014,8016,8020,8026,8032,8034,8038,8041,8047,8053,8055,8058,8139,8146,8148,8150,8185,8187,8211,8216],[14,7793,16],{"id":16},[18,7795,7796,7799,7802,7805],{},[21,7797,7798],{},"新人入职，被丢给一个 5 年老项目",[21,7800,7801],{},"接手前同事跑路留下的代码",[21,7803,7804],{},"评估一个开源项目能否用作技术选型",[21,7806,7807],{},"给客户做代码 audit \u002F 接手报价",[49,7809,7810],{},[868,7811,7812],{},"用人脑读两周做的事，AI 协助下 2-4 小时完成 80%。",[14,7814,7816],{"id":7815},"总览六步法","总览：六步法",[34,7818,7821],{"className":7819,"code":7820,"language":39,"meta":43},[37],"1. clone & 准备 → 2. 项目体检 → 3. 架构图 → 4. 核心路径 →\n5. 风险与债务 → 6. 关键问题清单\n",[41,7822,7820],{"__ignoreMap":43},[49,7824,7825,7826,906],{},"每一步都有",[868,7827,7828],{},"具体 Prompt + 验收标准",[6853,7830],{},[14,7832,7834],{"id":7833},"step-1-clone-与环境准备10-分钟","Step 1 — clone 与环境准备（10 分钟）",[34,7836,7838],{"className":1807,"code":7837,"language":1809,"meta":43,"style":43},"git clone \u003Crepo>\ncd \u003Crepo>\n# 关键：先让 AI 看 git 历史活跃度\ngit log --since='1 year ago' --pretty=format:'%h %s' | wc -l\n",[41,7839,7840,7857,7869,7874],{"__ignoreMap":43},[63,7841,7842,7844,7847,7849,7852,7855],{"class":65,"line":66},[63,7843,1980],{"class":1106},[63,7845,7846],{"class":77}," clone",[63,7848,1986],{"class":427},[63,7850,7851],{"class":77},"rep",[63,7853,7854],{"class":73},"o",[63,7856,1995],{"class":427},[63,7858,7859,7861,7863,7865,7867],{"class":65,"line":81},[63,7860,2336],{"class":84},[63,7862,1986],{"class":427},[63,7864,7851],{"class":77},[63,7866,7854],{"class":73},[63,7868,1995],{"class":427},[63,7870,7871],{"class":65,"line":91},[63,7872,7873],{"class":1056},"# 关键：先让 AI 看 git 历史活跃度\n",[63,7875,7876,7878,7880,7883,7886,7888,7891,7894,7897],{"class":65,"line":99},[63,7877,1980],{"class":1106},[63,7879,6745],{"class":77},[63,7881,7882],{"class":84}," --since=",[63,7884,7885],{"class":77},"'1 year ago'",[63,7887,6751],{"class":84},[63,7889,7890],{"class":77},"'%h %s'",[63,7892,7893],{"class":427}," |",[63,7895,7896],{"class":1106}," wc",[63,7898,7899],{"class":84}," -l\n",[49,7901,7902],{},"打开 Claude Code 或 Cursor，进入项目目录。",[49,7904,7905,54],{},[868,7906,7907],{},"第一条 Prompt",[7664,7909,7910],{},[49,7911,7912,7913,7916],{},"\"请扫描当前目录，告诉我：(1) 这是什么类型的项目；(2) 主要技术栈；(3) 入口文件在哪；(4) 有没有 README \u002F docs。",[868,7914,7915],{},"先不要读源码","，只看 package.json \u002F pyproject.toml \u002F Cargo.toml \u002F go.mod 这类元文件。\"",[49,7918,7919,7922],{},[868,7920,7921],{},"为什么","：先让 AI 建立宏观认知，避免它一上来就被某个文件带偏。",[6853,7924],{},[14,7926,7928],{"id":7927},"step-2-项目体检20-分钟","Step 2 — 项目体检（20 分钟）",[49,7930,7931],{},"让 AI 出一份\"健康报告\"：",[34,7933,7936],{"className":7934,"code":7935,"language":39,"meta":43},[37],"请生成一份项目体检报告，包含：\n\n1. 代码量：按语言\u002F目录分布\n2. 测试覆盖：有没有测试，跑得起来吗，覆盖率多少\n3. 依赖健康：有几个依赖、最旧的几个分别多久没更新\n4. 文档完整度：README \u002F CHANGELOG \u002F CONTRIBUTING \u002F docs\u002F\n5. CI 状态：有 CI 吗，最近一次跑成功了吗\n6. 死代码迹象：明显未使用的文件 \u002F 函数\n\n不要猜测，只报告确凿能看到的事实。每条结论给出依据。\n",[41,7937,7935],{"__ignoreMap":43},[49,7939,7940,7943,7944,7106,7947,7106,7950,7953],{},[868,7941,7942],{},"Claude Code 做这事最强","——它会自己跑 ",[41,7945,7946],{},"find",[41,7948,7949],{},"git log",[41,7951,7952],{},"npm outdated"," 这类命令，给出真凭实据。",[6853,7955],{},[14,7957,7959],{"id":7958},"step-3-架构图30-分钟","Step 3 — 架构图（30 分钟）",[34,7961,7964],{"className":7962,"code":7963,"language":39,"meta":43},[37],"基于刚才的体检，画一张项目架构图（Mermaid 格式）。\n要求：\n- 顶层模块用方框\n- 数据流向用箭头\n- 外部依赖（DB \u002F 第三方 API \u002F 队列）单独标出\n- **如果某个边界你不确定，标 \"?\" 而不是猜**\n\n画完后，用 5-10 句话解释这个架构的核心思路。\n",[41,7965,7963],{"__ignoreMap":43},[49,7967,7968,7971,7972,7975],{},[868,7969,7970],{},"关键技巧","：明确告诉 AI ",[868,7973,7974],{},"\"不确定就标 ?\"","，否则它会编。",[49,7977,7978,7979,7984],{},"把生成的 Mermaid 复制到 ",[6058,7980,7983],{"href":7981,"rel":7982},"https:\u002F\u002Fmermaid.live",[6062],"mermaid.live"," 验证可视化效果。",[6853,7986],{},[14,7988,7990],{"id":7989},"step-4-找到核心路径30-分钟","Step 4 — 找到\"核心路径\"（30 分钟）",[49,7992,7993,7994,7997],{},"这是 onboarding 最关键的一步。每个项目都有 1-3 条",[868,7995,7996],{},"核心业务路径","——用户最常用的功能链路。把它走通，整个项目就懂一半了。",[34,7999,8002],{"className":8000,"code":8001,"language":39,"meta":43},[37],"请找出本项目的 3 条核心业务路径（按重要性排序）：\n\n每条路径需要回答：\n1. 用户从哪触发（URL \u002F API endpoint \u002F CLI 命令）\n2. 经过哪些关键文件 \u002F 函数\n3. 数据如何流动（DB 读什么、写什么）\n4. 在哪里返回结果\n\n用编号列表给出，每个文件名 + 行号都要确凿。\n",[41,8003,8001],{"__ignoreMap":43},[49,8005,8006,8009,8010,8013],{},[868,8007,8008],{},"验收标准","：你能照着这份路径，",[868,8011,8012],{},"自己手动 trace 一遍而不卡壳","。如果哪一步看不懂，回去问 AI 那一段具体是什么。",[6853,8015],{},[14,8017,8019],{"id":8018},"step-5-风险-技术债清单30-分钟","Step 5 — 风险 + 技术债清单（30 分钟）",[34,8021,8024],{"className":8022,"code":8023,"language":39,"meta":43},[37],"请审视代码库，列出：\n\nA. 高风险代码（运行时容易出问题）\n   - 没有错误处理的 IO \u002F 网络调用\n   - 明显的 race condition \u002F 并发问题\n   - 写死的 secret \u002F API key\n   - SQL 注入 \u002F XSS 风险\n\nB. 技术债（影响开发效率）\n   - 重复的代码 (DRY 违反)\n   - 函数过长（>200 行）\n   - 圈复杂度过高的函数\n   - 循环依赖\n\nC. 维护性差的部分\n   - 完全没注释的关键算法\n   - \"magic number\"\n   - 命名混乱的模块\n\n每条给出文件路径 + 行号，并用 1 句话解释为什么是问题。\n",[41,8025,8023],{"__ignoreMap":43},[49,8027,8028,8031],{},[868,8029,8030],{},"这一步的输出可以直接交给客户\u002F老板","——如果你是接外包、做 audit，这就是付费报告的核心内容。",[6853,8033],{},[14,8035,8037],{"id":8036},"step-6-关键问题清单20-分钟","Step 6 — 关键问题清单（20 分钟）",[49,8039,8040],{},"最后一步，让 AI 列出\"作为新人接手，你最该问原作者的 10 个问题\"：",[34,8042,8045],{"className":8043,"code":8044,"language":39,"meta":43},[37],"假设你即将接手这个项目，原作者今天最后一天上班，\n你只能问他 10 个问题。请列出这 10 个问题，按重要性排序。\n\n每个问题要：\n- 具体（不能是\"这个项目怎么部署\"这种宽泛问题）\n- 可被一句话回答\n- 解决之后你能独立运行\u002F修改这个项目\n",[41,8046,8044],{"__ignoreMap":43},[49,8048,8049,8052],{},[868,8050,8051],{},"这份清单极其有价值","——它把\"未知的未知\"转化成\"已知的未知\"。你可以拿着它去问前同事 \u002F 客户 \u002F 文档 \u002F 社区。",[6853,8054],{},[14,8056,8057],{"id":8057},"总耗时与产出",[697,8059,8060,8071],{},[700,8061,8062],{},[703,8063,8064,8066,8068],{},[706,8065,7583],{},[706,8067,6869],{},[706,8069,8070],{},"产出物",[716,8072,8073,8084,8093,8103,8112,8123],{},[703,8074,8075,8078,8081],{},[721,8076,8077],{},"1-2",[721,8079,8080],{},"30 min",[721,8082,8083],{},"项目体检报告",[703,8085,8086,8088,8090],{},[721,8087,5091],{},[721,8089,8080],{},[721,8091,8092],{},"Mermaid 架构图",[703,8094,8095,8098,8100],{},[721,8096,8097],{},"4",[721,8099,8080],{},[721,8101,8102],{},"核心业务路径文档",[703,8104,8105,8107,8109],{},[721,8106,5547],{},[721,8108,8080],{},[721,8110,8111],{},"风险 + 技术债清单",[703,8113,8114,8117,8120],{},[721,8115,8116],{},"6",[721,8118,8119],{},"20 min",[721,8121,8122],{},"关键问题清单",[703,8124,8125,8129,8134],{},[721,8126,8127],{},[868,8128,6924],{},[721,8130,8131],{},[868,8132,8133],{},"~2.5 小时",[721,8135,8136],{},[868,8137,8138],{},"5 份可交付文档",[49,8140,8141,8142,8145],{},"把这 5 份文档存进项目 ",[41,8143,8144],{},"docs\u002Fonboarding\u002F","，下一个新人来直接读，再省 2 小时。",[6853,8147],{},[14,8149,6945],{"id":6945},[18,8151,8152,8160,8169,8179],{},[21,8153,8154,8156,8157,906],{},[868,8155,938],{},"：最适合这种\"广撒网\"任务，自己跑命令、读文件。",[868,8158,8159],{},"首选",[21,8161,8162,8168],{},[868,8163,8164,8165],{},"Cursor + ",[41,8166,8167],{},"@codebase","：体验也很好，胜在便宜。中型项目（\u003C 5 万行）够用。",[21,8170,8171,8174,8175,8178],{},[868,8172,8173],{},"Aider","：可以，但要手动 ",[41,8176,8177],{},"add"," 文件，节奏更慢。",[21,8180,8181,8184],{},[868,8182,8183],{},"Trae","：国内项目首选，中文 prompt 体验最佳。",[14,8186,6974],{"id":6974},[18,8188,8189,8199,8205],{},[21,8190,8191,8194,8195,8198],{},[868,8192,8193],{},"不要让 AI 一次读完整个项目","——它会丢上下文。",[868,8196,8197],{},"分模块"," + 每模块独立提问。",[21,8200,8201,8204],{},[868,8202,8203],{},"关键结论要让它给出文件:行号"," 作为依据，否则容易编。",[21,8206,8207,8210],{},[868,8208,8209],{},"不要过度依赖 AI 的\"我觉得\"","——遇到不确定的地方，强制让它标记不确定，然后人工 verify。",[49,8212,8213,8214,906],{},"下一篇：",[6058,8215,7110],{"href":7109},[908,8217,8218],{},"html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}html pre.shiki code .szBVR, html code.shiki .szBVR{--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .sJ8bj, html code.shiki .sJ8bj{--shiki-default:#6A737D;--shiki-dark:#6A737D}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);}",{"title":43,"searchDepth":91,"depth":91,"links":8220},[8221,8222,8223,8224,8225,8226,8227,8228,8229,8230,8231],{"id":16,"depth":81,"text":16},{"id":7815,"depth":81,"text":7816},{"id":7833,"depth":81,"text":7834},{"id":7927,"depth":81,"text":7928},{"id":7958,"depth":81,"text":7959},{"id":7989,"depth":81,"text":7990},{"id":8018,"depth":81,"text":8019},{"id":8036,"depth":81,"text":8037},{"id":8057,"depth":81,"text":8057},{"id":6945,"depth":81,"text":6945},{"id":6974,"depth":81,"text":6974},"\u002Fog\u002Fplaybook\u002Flegacy-onboarding.png","新人面对几十万行老代码不知从何下手？用 Claude Code \u002F Cursor 在 2 小时内画出项目架构、找到核心入口、定位关键风险点的标准化工作流。",{},"2026-04-25",[930,2041,2763],{"title":7789,"description":8233},"playbook\u002Fonboarding\u002Flegacy-codebase-onboarding",[2757,8240,8241,938],"代码理解","架构图","2026-06-08","0F1OVuaSvo8nT4EWBpyuDs7C5ypSOig0svKHVsTriyM",{"tools":4,"reviews":5,"playbooks":155,"news":136},1782316491475]