<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>Ruby China</title>
    <link>https://ruby-china.org/</link>
    <description>Ruby China社区最新发帖。</description>
    <language>en-us</language>
    <item>
      <title>寻找“乡村黑客”：向着广阔的乡土出发</title>
      <description>&lt;p&gt;将实验室搬进稻田，把代码写在大地，
对物理世界编程，以创意文化展现乡村之美。
科技赋能，设计向善，用人工智能重塑中国乡村的数字化进程。&lt;/p&gt;

&lt;p&gt;前往先进的乡土&lt;/p&gt;

&lt;p&gt;&lt;img src="https://l.ruby-china.com/photo/jiang_plus/12760a29-13cc-4531-81dd-5b98ef388476.jpg!large" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;关于乡村，我们听过太多流行的叙事：回不去的乡愁、需要被振兴的落后、直播间里的土味猎奇，或是盆景化的古村落。乡村的位置始终是被叙述的那一方——被怀念、被帮扶、被消费、被观赏。这次，我们想换一个位置。当无人机掠过稻田，代码在垄亩间编织成新的经纬。在 AI 加速迭代、数字逻辑几乎统治日常的当下，我们想提出一种乡村未来主义：开阔的场域安放多元的生活姿态，自然的环境滋养不异化人的技术。乡村广阔、包容，正以一种深邃的姿态，供前卫的实验在此扎根。我们希望从一场前后周期一个月的黑客松开始，延展至持续几年的活动以及大社群的建立，向世界发送这样两条信息：
1.乡村不再是凝固的传统空间，而是灵感迸发与先进实验的策源地。2.乡村是温柔且积极的，它是想象力与创造力最宽广的试验场。
何出此言？且让我们邀请你来亲历这场实验，去感受当所有要素一起聚集在乡村的时刻：一片虚位以待的土地，一种回归人本的环境，一群跨界共生的伙伴，一种温润克制的技术......我们邀请你来想象，当它们在田垄间交织、在泥地中碰撞，可能会缝合起怎样一个崭新的未来？&lt;/p&gt;

&lt;p&gt;这次有什么特别的？&lt;/p&gt;

&lt;p&gt;这不是一场典型黑客松，不是普通乡创，而是站在人类科技发展进程的十字路口，思考我们走过的道路，并放眼去看未来更多的可能性。在务实与技术之余，它更是关乎人文，关乎想象，关乎未来的。&lt;/p&gt;

&lt;p&gt;这场乡村 + 黑客松：一不拘泥于传统乡村应用场景——如农用机械，文创等；二不局限于经典的黑客松人群——不是只有技术黑客才能参加。它是面向先进的、不断演化的、有无限空间和可能性的乡村，也因而需要一种更加复合的想象力。我们想寻找的人，想链接的社区，所形成的是一种整全的集体：会技术，有思考，可艺术，能实干，强经验。&lt;/p&gt;

&lt;p&gt;促成几种人群，几类社区的链接：&lt;/p&gt;

&lt;p&gt;✦✦擅长跨地域链接、善用技术的独立组织和来自创意社群的社区朋友们拥有多年在地经验、深耕乡村的乡建朋友们穿梭在城乡之间、寻找生活新可能的探索者们&lt;/p&gt;

&lt;p&gt;✦✦我们希望借助黑客松的形式，为他们创造一个交流、链接和创造的场域，邀请他们来乡村的田野上体验“思辨推演”、“经验验证”、“技术实现”，在彼此的交流中实现融合的过程，让文化、技术、经验共铸一个具有想象力的乡村未来。或许你想寻找乡村创业的机会，或许你想尝试 AI+ 乡村的可能，或许你只是想探索乡村生活的可能性。那么，这里不只是一场极客聚会，不是一场消费乡村概念的快闪，也不只会是一场灵感的冲刺，而是通过比赛的契机相互链接之后，未来的长期共建：✦去思考近代百年来的乡土变革去看回村创业如何长期落地，驻村怎样可持续去看各式各样的另类社区、创意社群如何进行链接和别样实践去看由实业者、返乡青年、数字游民，多元社区聚合起来的村庄会生长出怎样的可能&lt;/p&gt;

&lt;p&gt;✦✦ 我们邀请你去作为思考者、创作者参与在地对话，我们会向你们呈现乡村真实的面貌——它的机会与困境、在这里生活的人和真实发生的人和故事。&lt;/p&gt;

&lt;p&gt;我们想要你来创什么？或许你感到迟疑，认为乡村的图景过于传统或遥远。但我们希望你打开想象：乡村不是只有农地、民宿和旅游。在 AI 工具的加持下，我们将探索技术与人文的崭新耦合。你的创作可以是：
务实、具有公益底色的：比如具身智能的农用方案、促进信息流通的分布式平台、实现数字平权的教育工具，或是针对老人与儿童的看护系统；
充满创意与生命力的：比如利用 AIGC 转译非遗文化的数字艺术、跨越时空坐标的乡村潮玩、或是重塑在地美学的建筑插件；
前卫、富有实验性的：去构筑一个乡村 OPC 社区、去实验 AI Commons（数字公地）、或开发 AI For Good 的应用，探索算法治理下一种人性化的社区契约。&lt;/p&gt;

&lt;p&gt;✦✦我们将划分软件、硬件、文创三大赛道，并提供一系列可选的主题参考。当然，我们更期待那些跳脱出预设主题之外的、自由生长的想法。&lt;/p&gt;

&lt;p&gt;需要什么人？这不是一场只有写代码才能参加的黑客松，相反，我们期待这样一帮对乡村未来有好奇心的普通人：✦生活逻辑的思考者：你未必研读过哲学，但你习惯对习以为常的生活方式发问。在 AI 席卷而来的时代，你关心人的主体性在哪里，关心那些被效率抹平的价值如何重新显现。
社区秩序的观察者：你关注人与人的连接，能敏锐地察觉到乡土结构在现代性冲击下的裂痕与温情。你懂得如何倾听土地的声音，也理解城市人群在过饱和竞争中的精神突围。
感官与美感的创造者：你擅长将抽象的想法转化为具象的触感。无论是文字、影像、设计还是艺术装置，你能够赋予技术一种温润的界面，让冷冰冰的代码拥有人文的温度。
在地经验的守望者：你可能是有几十年经验的乡建人，也可能是吾爱吾乡的新老村民。你深耕田野，理解土地的脾气与村庄的肌理；你拥有最真实的体感经验，知道什么叫“落地”，帮助团队避开悬浮的构想，让技术精准接入乡村真实的需要。
技术与工具的实干家：你可能是代码的高手，也可能是擅长应用各类数字工具的极客。你享受那种亲手解决一个问题的踏实感，并愿意尝试让技术向下扎根，去回应最真实的生存需求。
✦✦不同特长的人组成队伍，从思想到技术再到经验的完整对话，实现理想与实际的结合，让我们既实在又富有想象力地在乡村碰撞一场，创造一场。&lt;/p&gt;

&lt;p&gt;怎么创？不必担心你不懂乡村，不必担心你不懂技术：乡村经验不是必需，软件硬件知识不是必需，不熟悉 AI 工具也没关系，我们一个月的线上预热工作坊会全力促进大家进行知识的交流互换和共享；我们将邀请乡建团队讲诉乡建历程，分享几十年在地经验；邀请技术社区分享跨社群的技术连接，平台搭建；邀请创变社群分享他们的多样实践。在知识和技能以外，更重要的，是让不同的社区，不同背景的人群提前熟悉彼此。在组队时，根据需要，我们也会帮助大家进行不同技能角色上的搭配，一个典型的队伍，或许是：灵感哲学家 + 艺术家 + 乡建人 + 技术咖。灵感可以提前一个月酝酿，易上手的技术可以提前学习，队友可以提前交流互动。没有传统黑客松的时间焦虑，不是为了创而创的赶鸭子上架，我们细细聊，慢慢了解，好好来往。AI 工具已经先进到任何人都可以只动嘴不动手，只要够好奇就大胆来吧！&lt;/p&gt;

&lt;p&gt;你会收获什么？这不仅是一场比赛，更是一张进入人文与技术高度融合共创圈的入场券。我们试图在这次集结中，打破孤岛，建立一个长期互动的社区共生体。在这里，你不仅能链接到各地深耕乡村建设二十年、拥有深厚在地智慧的实践者，更能对话链接全球开源精神与公民科技的创新社群。为了让这份构想实现落地，我们同时为比赛准备了最坚实的支撑：✦全方位的共创保障：我们设立了 10 万元人民币的现金奖金池，用以奖赏那些最具启发性的方案。在 5 月 21 日至 5 月 24 日的核心比赛期间，我们将为参赛者提供全包式的住宿支持——我们负责解决琐碎，你只需负责灵感。一份长期的入乡邀约：参赛者将有机会进入“数智乡建屏南人才库”。对于优秀的共创项目与团队，我们将开放四坪村“数字人才驿站”的长期入驻权，及后续的专业孵化支持。
✦✦我们希望每一位“乡村黑客”，不仅能在这里碰撞出惊艳世界的瞬时灵感，更能在这片土地上安放理想，实现真正的长久栖居。&lt;/p&gt;
&lt;h3 id="活动信息"&gt;活动信息&lt;/h3&gt;
&lt;p&gt;活动地点：福建省宁德市屏南县·熙岭乡（龙潭村、四坪村双村联动）
线上：4 月底至 5 月赛事前整月工作坊主题预热
线下：5 月 18 日~20 日线下预热活动（可自由选择是否参与）
赛事：5 月 21 日~24 日黑客松比赛落地支持：&lt;/p&gt;

&lt;p&gt;黑客松 10 万元奖金池；
参赛者住宿全包；
合作伙伴餐饮交通补贴长期福利：
数智乡建屏南人才库；
四坪数字人才驿站；
优秀项目孵化支持更多工作坊信息、黑客松具体赛道设计、评委、审核标准将在后续公布。&lt;/p&gt;
&lt;h3 id="共创招募"&gt;共创招募&lt;/h3&gt;
&lt;p&gt;在大幕开启之前，我们欢迎大家以“共建者”的身份先行入场。我们需要更多志同道合的伙伴，在不同的社群脉络间穿针引线，共同筹备那些激发灵感的工作坊和相遇。在正式相聚前，亲手编织出这场碰撞的底色。&lt;/p&gt;

&lt;p&gt;我们正式向你发出邀请：无论你是技术极客、视觉艺术家、跨界实践者、社群活动家、乡建工作者，还是新老村民，请带上你的专长与经验来到这里。在碰撞、交流与合作中，我们一起植根乡土，完成一场不设限的共创大集。
让我们用代码与技术、用艺术与创意，去搭建乡村与未来的桥梁。
不预设创意的边界，不定义乡村的需求，我们只提供一方田野上的空地，让来自各个背景的伙伴架起一簇篝火，用彼此的碰撞来点燃它。&lt;/p&gt;
&lt;h3 id="赛道"&gt;赛道&lt;/h3&gt;
&lt;p&gt;我们设计了三条不同的赛道，让每一种才能都能找到发挥的方向：&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;01 软件：村镇经济体与 AI 乡村赋能&lt;/strong&gt;
跨越基础信息化阶段，探索大语言模型（LLM）、空间计算与复杂系统模拟在乡村经济体中的深度应用。
可参考方向 Vibe Coding：通过 AI 为乡村数字应用的快速迭代助力。OpenClaw（小龙虾）：服务乡村场景的 Agent 智能体与工作流。村务信息服务：打通村务信息孤岛，为村民提供全方位的信息服务。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;02 硬件：具身智能与农用智能设备&lt;/strong&gt;
针对中国复杂地形与农村经济特征，研发低成本、高鲁棒性、易维护的边缘计算设备与具身智能硬件。
可参考方向机器人：为农业场景打造具身智能的前沿。无人机：用无人机进行农田巡查、病虫害监测、作物生长监测等。智能传感器在传感器和智慧农业方向深度探索。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;03 文创：社会创新与可持续设计&lt;/strong&gt;
超越传统的包装美化，运用系统性设计思维与 AIGC 等新媒介，重塑乡村文化资产与生态循环模式。
可参考方向服务设计与社区营造：重构乡村公共空间交互体验，设计吸引"数字游民"与原住民共居共创的社会化服务流。AIGC 与非遗保育：利用生成式 AI 提取传统手工艺（如刺绣、木雕）的参数化特征，生成现代设计应用。乡村循环经济设计：针对农业废弃物（秸秆、果壳）的材料创新与高附加值产品转化设计。
参考方向仅供定位，我们也欢迎更多大胆创新的想法&lt;/p&gt;
&lt;h3 id="报名入口："&gt;报名入口：&lt;/h3&gt;
&lt;p&gt;&lt;a href="https://build.xjdao.xyz/forms/hackathon-2026" rel="nofollow" target="_blank"&gt;https://build.xjdao.xyz/forms/hackathon-2026&lt;/a&gt;&lt;/p&gt;
&lt;h3 id="公众号链接："&gt;公众号链接：&lt;/h3&gt;
&lt;p&gt;&lt;a href="https://mp.weixin.qq.com/s/Si7mUhGLYSukZR4TjpR3tg" rel="nofollow" target="_blank"&gt;https://mp.weixin.qq.com/s/Si7mUhGLYSukZR4TjpR3tg&lt;/a&gt;&lt;/p&gt;</description>
      <author>jiang_plus</author>
      <pubDate>Tue, 12 May 2026 12:06:29 +0800</pubDate>
      <link>https://ruby-china.org/topics/44570</link>
      <guid>https://ruby-china.org/topics/44570</guid>
    </item>
    <item>
      <title>matz 用 claude 把 mruby 的 issues 全关了</title>
      <description>&lt;p&gt;&lt;a href="https://github.com/mruby/mruby/issues" rel="nofollow" target="_blank"&gt;https://github.com/mruby/mruby/issues&lt;/a&gt; 已经只有一个 issues 了&lt;/p&gt;

&lt;p&gt;随便点开几个基本上都是 matz and claude committed &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/mruby/mruby/commit/6b9e477cc0eca2d33ad35e6fd163edbb2ffdbf85" rel="nofollow" target="_blank" title=""&gt;https://github.com/mruby/mruby/commit/6b9e477cc0eca2d33ad35e6fd163edbb2ffdbf85&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/mruby/mruby/commit/f267925d45fc387ae9dd9ae7b65e752961210b69" rel="nofollow" target="_blank" title=""&gt;https://github.com/mruby/mruby/commit/f267925d45fc387ae9dd9ae7b65e752961210b69&lt;/a&gt; &lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/mruby/mruby/pull/6792" rel="nofollow" target="_blank" title=""&gt;https://github.com/mruby/mruby/pull/6792&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
      <author>xiaoLinger</author>
      <pubDate>Tue, 12 May 2026 10:13:16 +0800</pubDate>
      <link>https://ruby-china.org/topics/44569</link>
      <guid>https://ruby-china.org/topics/44569</guid>
    </item>
    <item>
      <title>[成都 / 远程] Python 后端工程师 - 前硅谷大厂资深工程师带队，美资 FinTech 初创投资决策平台 (15k / 月)</title>
      <description>&lt;p&gt;我们是一家在美国的 FinTech 初创公司，由前硅谷大厂资深工程师带队。
目前我们正在研发一款全新的美股投资决策平台。现在，我们诚寻一位能独立承担开发工程项目落地的全栈极客加入，作为国内研发节点的第一位研发骨干。&lt;/p&gt;

&lt;p&gt;💻 你将负责什么？&lt;/p&gt;

&lt;p&gt;你需要开发支撑业务运转的工程系统：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;数据管道：负责海外金融数据获取，以及海量异构财务数据的清洗与入库。&lt;/li&gt;
&lt;li&gt;工程计算：将复杂的财务数据计算逻辑工程化&lt;/li&gt;
&lt;li&gt;全栈落地：开发前端 UI 看板系统及移动端 App。&lt;/li&gt;
&lt;li&gt;系统保障：负责整个软件生命周期的自动化测试及系统健康度监控体系。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;🎯 我们在寻找怎样的你？&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;背景：正规高校计算机相关专业，3-5 年工作经验。&lt;/li&gt;
&lt;li&gt;技术栈：扎实的后端功底（Python/Go/Node.js 等语言不限，看重底层系统思维）+ 数据管线经验 + 现代前端/移动端开发落地能力。&lt;/li&gt;
&lt;li&gt;软素质：聪明，极度自驱。具备在没有庞大运维团队支持下，独立架构、部署和排查疑难杂症的能力。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;💰 我们能提供什么？&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;薪资：15,000 RMB / 月及每年最多 2 个月奖金&lt;/li&gt;
&lt;li&gt;合规保障：通过全球顶级 平台 Deel 与你签订 Contractor 协议&lt;/li&gt;
&lt;li&gt;工作方式：纯 Remote，地点限成都。不打卡，没有无效会议&lt;/li&gt;
&lt;li&gt;技术成长：使用最先进的 AI 编程工具，与资深专家协同开发，深度了解华尔街价值投资与基本面分析的底层逻辑&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;如果你想在一个极其纯粹的工程师环境里主导搭建一套极具挑战的线上系统，请联系我们。&lt;/p&gt;

&lt;p&gt;📫投递邮箱： &lt;/p&gt;

&lt;p&gt;info@pulsefeed.ai &lt;/p&gt;

&lt;p&gt;投递请务必附上 GitHub 链接或技术博客 , 请在 Email 开头写上 Pulse Alpha&lt;/p&gt;</description>
      <author>pulse08</author>
      <pubDate>Tue, 12 May 2026 01:49:36 +0800</pubDate>
      <link>https://ruby-china.org/topics/44568</link>
      <guid>https://ruby-china.org/topics/44568</guid>
    </item>
    <item>
      <title>11 英寸 MacBook 的第二春：从零开始配置 Ruby on Rails 开发环境</title>
      <description>&lt;h2 id="🌸 旧 MacBook 的第二春：从零配置 Ruby on Rails 开发环境"&gt;🌸 旧 MacBook 的第二春：从零配置 Ruby on Rails 开发环境&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;"最好的设备是你手里那台。"&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src="https://l.ruby-china.com/photo/AixCoder/e414fcae-f822-45b1-87bd-c387a696803c.png!large" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;很多同学手里都有一台旧的 MacBook Air，可能是当年为了写论文买的。现在用起来有点卡，打开 App 要转圈圈，似乎只能沦为追剧专用机。&lt;/p&gt;

&lt;p&gt;但别急着放弃它。&lt;/p&gt;

&lt;p&gt;MacBook 的硬件做工一向扎实，老机器的问题往往不是性能不够，而是&lt;strong&gt;系统里堆积了太多垃圾，以及没有装对工具&lt;/strong&gt;。就像一间堆满杂物的房间，不是房子太小，而是需要重新整理。&lt;/p&gt;

&lt;p&gt;给这台旧电脑&lt;strong&gt;重装系统 → 安装 Homebrew → 配置 Ruby → 搭建 Rails&lt;/strong&gt;，它就能从追剧神器变身成一台趁手的&lt;strong&gt;编程学习机、写作工具，甚至是一台随身携带的代码终端&lt;/strong&gt;。&lt;/p&gt;

&lt;hr&gt;
&lt;h2 id="📑 目录"&gt;📑 目录&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="#%E5%AE%89%E8%A3%85%E5%89%8D%E7%9A%84%E5%87%86%E5%A4%87%E9%87%8D%E8%A3%85%E7%B3%BB%E7%BB%9F" title=""&gt;安装前的准备：重装系统&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#%E7%AC%AC%E4%B8%80%E6%AD%A5%E5%AE%89%E8%A3%85-homebrew" title=""&gt;第一步：安装 Homebrew&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#%E7%AC%AC%E4%BA%8C%E6%AD%A5%E5%AE%89%E8%A3%85-rbenv" title=""&gt;第二步：安装 rbenv&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#%E7%AC%AC%E4%B8%89%E6%AD%A5%E5%AE%89%E8%A3%85-ruby" title=""&gt;第三步：安装 Ruby&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#%E7%AC%AC%E5%9B%9B%E6%AD%A5%E5%AE%89%E8%A3%85-rails" title=""&gt;第四步：安装 Rails&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#%E7%AC%AC%E4%BA%94%E6%AD%A5%E5%88%9B%E5%BB%BA%E5%B9%B6%E5%90%AF%E5%8A%A8%E4%BD%A0%E7%9A%84%E7%AC%AC%E4%B8%80%E4%B8%AA%E9%A1%B9%E7%9B%AE" title=""&gt;第五步：创建并启动你的第一个项目&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#%E5%86%99%E5%9C%A8%E6%9C%80%E5%90%8E" title=""&gt;写在最后&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;hr&gt;
&lt;h2 id="安装前的准备：重装系统"&gt;安装前的准备：重装系统&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;强烈推荐先重装系统。&lt;/strong&gt; 如果这台 MacBook 从买来到现在就没重装过，系统里可能积累了各种残留文件。&lt;/p&gt;

&lt;p&gt;操作很简单：&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;备份&lt;/strong&gt;重要文件到 移动盘或云盘&lt;/li&gt;
&lt;li&gt;开机时按住 &lt;code&gt;Option + Command + R&lt;/code&gt;，进入在线恢复模式&lt;/li&gt;
&lt;li&gt;选择重新安装 macOS，把系统恢复到最干净的状态&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;重装后的系统就像刚搬进的空房子，接下来我们往里面添置需要的家具。&lt;/p&gt;

&lt;hr&gt;
&lt;h2 id="第一步：安装 Homebrew"&gt;第一步：安装 Homebrew&lt;/h2&gt;&lt;h3 id="Homebrew 是什么？"&gt;Homebrew 是什么？&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Homebrew 就像 Mac 中隐藏版的应用商店，但比 App Store 强大得多。&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;想象你想在新家里添置家具：&lt;/p&gt;
&lt;table class="table table-bordered table-striped"&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;th&gt;App Store&lt;/th&gt;
&lt;th&gt;Homebrew&lt;/th&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;像大型商场，卖的都是精装成品（爱奇艺）&lt;/td&gt;
&lt;td&gt;像万能仓库，程序员需要的各种工具、语言、软件，一条命令就能自动下载、安装、配置好&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;买不到专业工具，或者版本很旧&lt;/td&gt;
&lt;td&gt;永远是最新版，不需要你到处找安装包、点下一步、输密码&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;&lt;h3 id="安装命令"&gt;安装命令&lt;/h3&gt;
&lt;p&gt;打开终端，复制粘贴下面这行命令：&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;/bin/bash &lt;span class="nt"&gt;-c&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;curl &lt;span class="nt"&gt;-fsSL&lt;/span&gt; https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;系统会提示你输入开机密码（输入时不会显示字符，这是正常的，输完直接回车）
安装过程可能需要几分钟，取决于网速。(建议安装的过程中全局科学上网)&lt;/p&gt;

&lt;p&gt;&lt;img src="https://l.ruby-china.com/photo/AixCoder/ddfd0cd3-9286-4f96-b7c1-b26dd0a9bed6.png!large" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;安装 Homebrew 的过程中，它会顺带安装 &lt;strong&gt;Command Line Tools for Xcode&lt;/strong&gt;。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Command Line Tools for Xcode 是什么？&lt;/strong&gt;
Xcode 是苹果官方出的集成开发工具，里面包含了开发 iPhone App 需要的所有重型设备（几十个 GB）。但对于 90% 的开发者来说，我们不需要那个庞然大物，只需要里面最核心的几件工具&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;编译器 (Compiler)&lt;/strong&gt;：把人类能看懂的代码，"翻译"成电脑能听懂的机器指令
&lt;strong&gt;构建工具 (Make)&lt;/strong&gt;：相当于安装指南，告诉电脑第一步拼哪块，第二步缝哪条线&lt;/p&gt;

&lt;p&gt;这就是 Command Line Tools for Xcode，包含了所有让代码"跑起来"的必要工具。&lt;/p&gt;
&lt;h5 id="验证安装成功"&gt;验证安装成功&lt;/h5&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;brew &lt;span class="nt"&gt;--version&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;如果显示出版本号（例如 &lt;code&gt;Homebrew 5.1.10&lt;/code&gt;），恭喜你，Homebrew 已经就位。&lt;/p&gt;
&lt;h2 id="第二步：安装 rbenv"&gt;第二步：安装 rbenv&lt;/h2&gt;&lt;h3 id="rbenv 是什么？"&gt;rbenv 是什么？&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;rbenv 是 Ruby 的「版本管家」。&lt;/strong&gt; 它让你在同一台电脑上安装多个 Ruby 版本，想用什么版本就切什么版本，互不干扰。&lt;/p&gt;
&lt;h3 id="为什么我们需要它？"&gt;为什么我们需要它？&lt;/h3&gt;
&lt;p&gt;想象一下，你是一个热爱生活且多才多艺的女生：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;今天你想做一个现代感十足的网站，需要请 &lt;strong&gt;Ruby 3.2&lt;/strong&gt; 大师来帮忙&lt;/li&gt;
&lt;li&gt;明天你想维护一个几年前的老项目，那个项目很挑剔，需要指定某个版本的 Ruby&lt;/li&gt;
&lt;li&gt;而你的 Mac 系统一直带着一个"老古董" &lt;strong&gt;Ruby 2.6&lt;/strong&gt; 系统管理员，它是用来维持系统运行的，你最好不要动它&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;如果没有 rbenv：&lt;/strong&gt; 你得在电脑里装好几个 Ruby，它们会为了争夺"谁才是真正的 Ruby"打架，最后把你的系统搞得一团糟&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;有了 rbenv：&lt;/strong&gt; 简单管理多个版本的 Ruby，系统自带的旧版本不动，新版本各住各的房间，互不打扰&lt;/p&gt;
&lt;h3 id="安装命令"&gt;安装命令&lt;/h3&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;brew &lt;span class="nb"&gt;install &lt;/span&gt;rbenv
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;安装的过程可能比较慢，因为电脑比较老了，很多工具得现场编译，耐心等待吧⌛️
&lt;img src="https://l.ruby-china.com/photo/AixCoder/77825179-e543-4e2a-a4ad-a59356680f28.png!large" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;安装完成后，请运行以下命令来配置 rbenv&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;rbenv init
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;​&lt;strong&gt;关闭终端，重新打开&lt;/strong&gt;​，以确保更改生效。&lt;/p&gt;
&lt;h5 id="验证安装"&gt;验证安装&lt;/h5&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;rbenv &lt;span class="nt"&gt;-v&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="第三步：安装 Ruby"&gt;第三步：安装 Ruby&lt;/h2&gt;&lt;h3 id="Ruby 的美丽：一门像诗一样的编程语言"&gt;Ruby 的美丽：一门像诗一样的编程语言&lt;/h3&gt;
&lt;p&gt;rbenv 管家已经就位，现在该请真正的主角进场了。&lt;/p&gt;

&lt;p&gt;Ruby 像一首诗&lt;/p&gt;

&lt;p&gt;它的发明者松本行弘（Yukihiro Matsumoto）说过一句话：&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;"我希望 Ruby 让程序员感到快乐。"&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id="安装命令"&gt;安装命令&lt;/h3&gt;
&lt;p&gt;先查看 rbenv 目前能提供的比较稳定的几个 Ruby 版本：&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;rbenv &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-l&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;对于这台老旧 MacBook，我装了的 Ruby3.3.10，和其他设备保持版本一致性&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;rbenv &lt;span class="nb"&gt;install &lt;/span&gt;3.3.10
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src="https://l.ruby-china.com/photo/AixCoder/b61515ba-bf33-4071-8c58-b4bdc9b872f1.png!large" title="" alt=""&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;💻 ​&lt;strong&gt;老旧 MacBook 特别提示&lt;/strong&gt;​：编译 Ruby 可能需要 ​&lt;strong&gt;10-20 分钟&lt;/strong&gt;​，风扇会狂转，这是正常的。建议插电进行，去泡杯茶，回来就好了。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;装完后，设为默认版本：&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;rbenv global 3.3.10
&lt;/code&gt;&lt;/pre&gt;&lt;h3 id="验证安装"&gt;验证安装&lt;/h3&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;ruby &lt;span class="nt"&gt;-v&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;ruby 3.3.10 (2025-10-23 revision 343ea05002) [x86_64-darwin20]
旧 MacBook 现在拥有了一颗年轻的心脏&lt;/p&gt;
&lt;h2 id="第四步：安装 Rails"&gt;第四步：安装 Rails&lt;/h2&gt;&lt;h3 id="Rails：Ruby 的黄金搭档，让想法快速落地"&gt;Rails：Ruby 的黄金搭档，让想法快速落地&lt;/h3&gt;
&lt;p&gt;如果说 Ruby 是一首诗，​&lt;strong&gt;Rails 就是一家出版社&lt;/strong&gt;​
它不负责写诗，但它让诗能被印刷、装订、送到读者手中，而且整个过程快得惊人。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Rails 像是一套「盖房子的标准化流程」。&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;你想搭一个网站，Rails 已经帮你把地基打好了、水电接通了、门窗装好了。你只需要决定：墙上刷什么颜色的漆、客厅里摆什么家具。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;想象你去一家很懂你的咖啡馆：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;你一坐下，店员就知道你要燕麦拿铁、半糖、去冰&lt;/li&gt;
&lt;li&gt;不需要你每次都说一遍&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Rails 就是这样。它假设你「大概率会这样做」，所以提前帮你把选择做好了。这种哲学叫 ​&lt;strong&gt;"约定优于配置"&lt;/strong&gt;​（Convention over Configuration）。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;对于初学者来说，这太重要了。&lt;/strong&gt; 你不需要先成为「全栈专家」，就能先做出一个「能用的东西」。成就感来得早，学习动力就足。&lt;/p&gt;

&lt;p&gt;记得当时做一个诗歌小站——可以读诗、投稿、玩一个简单的文字游戏，结合 AI 编程，好像用了大半天就做出来了。&lt;/p&gt;
&lt;h3 id="gem 是什么？"&gt;gem 是什么？&lt;/h3&gt;
&lt;p&gt;当你通过 rbenv 安装好 Ruby 的那一刻，​&lt;strong&gt;gem 就已经坐在你电脑里了&lt;/strong&gt;​。&lt;/p&gt;

&lt;p&gt;​&lt;strong&gt;gem 的全名叫 RubyGems&lt;/strong&gt;​，是 Ruby 官方的包管理器，可以理解成 ​&lt;strong&gt;Ruby 世界的「快递总站」&lt;/strong&gt;​。&lt;/p&gt;

&lt;p&gt;它的工作流程：&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;你在终端说：&lt;code&gt;gem install rails&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;gem 就联网去 ​&lt;strong&gt;rubygems.org&lt;/strong&gt;​（Ruby 的官方仓库）&lt;/li&gt;
&lt;li&gt;找到 Rails，把它连同所有依赖一起打包下载、自动安装&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id="安装命令"&gt;安装命令&lt;/h3&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;gem &lt;span class="nb"&gt;install &lt;/span&gt;rails &lt;span class="nt"&gt;-v&lt;/span&gt; 你想要的rails版本
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;⚠️ ​&lt;strong&gt;如果下载很慢&lt;/strong&gt;​：因为 gem 默认去国外的仓库拉货，可能会卡很久。建议先换成国内镜像源（ruby-china 镜像源）&lt;/p&gt;

&lt;p&gt;安装过程可能需要几分钟，gem 会自动下载 Rails 及其所有依赖。&lt;/p&gt;
&lt;h3 id="验证安装"&gt;验证安装&lt;/h3&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;rails &lt;span class="nt"&gt;-v&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="第五步：创建并启动你的第一个项目"&gt;第五步：创建并启动你的第一个项目&lt;/h2&gt;&lt;h3 id="1. 新建项目"&gt;1. 新建项目&lt;/h3&gt;
&lt;p&gt;在终端里输入：&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;rails new rails_nice
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;你会看到很多绿色的输出，正在新建项目，请稍等。
&lt;img src="https://l.ruby-china.com/photo/AixCoder/7dfc5015-16a8-4508-8699-70ae0dc7e586.png!large" title="" alt=""&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;💻 ​&lt;strong&gt;老旧 MacBook 特别提示&lt;/strong&gt;​：第一次创建项目时，Rails 需要安装依赖，可能会比较慢，请耐心等待。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id="2. 进入项目文件夹"&gt;2. 进入项目文件夹&lt;/h3&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cd &lt;/span&gt;rails_nice
&lt;/code&gt;&lt;/pre&gt;&lt;h3 id="3. 启动服务器"&gt;3. 启动服务器&lt;/h3&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;bin/rails s
&lt;/code&gt;&lt;/pre&gt;&lt;h3 id="4. 你会看到什么？"&gt;4. 你会看到什么？&lt;/h3&gt;
&lt;p&gt;终端里会显示：&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;=&amp;gt; Booting Puma
=&amp;gt; Rails 7.2.3 application starting in development 
=&amp;gt; Run `bin/rails server --help` for more startup options
Puma starting in single mode...
* Puma version: 8.0.1 ("Into the Arena")
* Ruby version: ruby 3.3.10 (2025-10-23 revision 343ea05002) [x86_64-darwin20]
*  Min threads: 3
*  Max threads: 3
*  Environment: development
*          PID: 14775
* Listening on http://127.0.0.1:3000
* Listening on http://[::1]:3000
Use Ctrl-C to stop
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;看到 &lt;code&gt;Listening on http://127.0.0.1:3000&lt;/code&gt;，就是​&lt;strong&gt;启动成功了&lt;/strong&gt;​。&lt;/p&gt;
&lt;h3 id="5. 在浏览器里查看成果"&gt;5. 在浏览器里查看成果&lt;/h3&gt;
&lt;p&gt;打开 Safari（或任何浏览器），地址栏输入：&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;http://127.0.0.1:3000
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src="https://l.ruby-china.com/photo/AixCoder/c8a645d4-2876-412d-b686-c83f0f1b74ca.png!large" title="" alt=""&gt;&lt;/p&gt;
&lt;h2 id="写在最后"&gt;写在最后&lt;/h2&gt;
&lt;p&gt;从重装系统，到 Homebrew，到 rbenv，到 Ruby，到 gem，到 Rails，再到 &lt;code&gt;bin/rails s&lt;/code&gt;——&lt;strong&gt;这台旧 MacBook，终于焕发了第二春。&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;旧物值得被认真对待，学习也可以从当下便宜的设备开始。&lt;/p&gt;

&lt;p&gt;M 芯片的 MacBook 就留着干重活吧，这台 macbook air 小电脑，很轻巧。&lt;/p&gt;
&lt;h3 id="💡 小结"&gt;💡 小结&lt;/h3&gt;
&lt;p&gt;整个流程其实就是一个“套娃”安装的过程：&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;用 &lt;strong&gt;command line tools for xocde&lt;/strong&gt; 为 &lt;strong&gt;Homebrew&lt;/strong&gt; 铺路。&lt;/li&gt;
&lt;li&gt;用 &lt;strong&gt;Homebrew&lt;/strong&gt; 安装 ​&lt;strong&gt;rbenv&lt;/strong&gt;​。&lt;/li&gt;
&lt;li&gt;用 &lt;strong&gt;rbenv&lt;/strong&gt; 安装 ​&lt;strong&gt;Ruby&lt;/strong&gt;​。&lt;/li&gt;
&lt;li&gt;用 ​&lt;strong&gt;Ruby&lt;/strong&gt;​（自带的 gem）安装 ​&lt;strong&gt;Rails&lt;/strong&gt;​。&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;每一个上层工具都依赖下层提供的环境。这就是为什么当初我们必须先解决 Xcode 开发工具安装的问题。&lt;/p&gt;

&lt;p&gt;好啦，看到这台“小破机”重新跑起代码，😄
作为编程学习机和写作工具来说真是很好，非常适合带出门轻度使用。小电脑，很轻巧。&lt;/p&gt;</description>
      <author>AixCoder</author>
      <pubDate>Sun, 10 May 2026 13:22:14 +0800</pubDate>
      <link>https://ruby-china.org/topics/44567</link>
      <guid>https://ruby-china.org/topics/44567</guid>
    </item>
    <item>
      <title>OpenClacky 1.0 正式发布 —— Ruby 圈第一个通用 Agent 来了</title>
      <description>&lt;p&gt;前段时间社区里一直有人在问：Python 那边 agent 框架一个接一个，Ruby 圈怎么就没一个拿得出手的通用 Agent？&lt;/p&gt;

&lt;p&gt;我们憋了两年，今天它来了。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;OpenClacky 1.0.0 正式发布&lt;/strong&gt;，MIT 开源，纯 Ruby 写的。&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;GitHub：&lt;a href="https://github.com/clacky-ai/openclacky" rel="nofollow" target="_blank"&gt;https://github.com/clacky-ai/openclacky&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;官网（桌面安装包）：&lt;a href="https://www.openclacky.com/#install" rel="nofollow" target="_blank"&gt;https://www.openclacky.com/#install&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;img src="https://l.ruby-china.com/photo/lyfi2003/310ea7dc-890e-4b6f-8c52-250c31c926ea.png!large" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;本文分两部分：先说是什么、怎么用；然后讲讲我们是怎么把 Token 成本打下来的 —— 后半段才是干货，欢迎直接跳到第三节。&lt;/p&gt;
&lt;h2 id="一、这是个什么东西"&gt;一、这是个什么东西&lt;/h2&gt;
&lt;p&gt;一句话：&lt;strong&gt;最省 Token 的开源通用 AI Agent&lt;/strong&gt;。&lt;/p&gt;

&lt;p&gt;能力上对齐 Claude Code，成本打到它的 &lt;strong&gt;0.8~1.2×&lt;/strong&gt;；相比其它开源 agent（如 OpenClaw、Hermes），大约便宜 &lt;strong&gt;50% ~ 3 倍&lt;/strong&gt;。&lt;/p&gt;

&lt;p&gt;两种用法，30 秒就能跑起来：&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# 安装&lt;/span&gt;
gem &lt;span class="nb"&gt;install &lt;/span&gt;openclacky

&lt;span class="c"&gt;# 命令行&lt;/span&gt;
openclacky

&lt;span class="c"&gt;# 或者跑 Web UI（多会话并行）&lt;/span&gt;
openclacky server    &lt;span class="c"&gt;# 默认 http://localhost:7070&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;BYOK，任意 OpenAI 兼容模型都支持：Claude / GPT / DeepSeek / Kimi / MiniMax / OpenRouter，或者任何自定义 endpoint。进去 &lt;code&gt;/config&lt;/code&gt; 填 API Key、Model、Base URL 就完事了。&lt;/p&gt;

&lt;p&gt;&lt;img src="https://l.ruby-china.com/photo/lyfi2003/0bf39a30-2b71-4fc7-98cc-1d712ebcfccb.png!large" title="" alt=""&gt;&lt;/p&gt;
&lt;h2 id="二、为什么我们又要再写一个"&gt;二、为什么我们又要再写一个&lt;/h2&gt;
&lt;p&gt;用过 Claude Code 的朋友应该都很惊艳，但三个问题一直没解：&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;闭源 + 订阅制，拿不到内部细节，也没办法定制&lt;/li&gt;
&lt;li&gt;只能用 Anthropic 自家模型，不能 BYOK&lt;/li&gt;
&lt;li&gt;对中国开发者来说，付费通路不顺畅&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;我们也试过 OpenClaw、Hermes 这些开源方案，能力是有的，但实测下来 Token 成本是 Claude Code 的 &lt;strong&gt;1.5× ~ 3×&lt;/strong&gt;。&lt;/p&gt;

&lt;p&gt;这是什么概念？一个中等规模的项目，一天下来 token 费可能就是几十到几百块，差 2~3 倍就是实打实的 RMB 差 2~3 倍。对个人开发者和小团队太贵了。&lt;/p&gt;

&lt;p&gt;所以我们自己做了一个。目标很明确：&lt;strong&gt;能力不输，成本打到最低。&lt;/strong&gt;&lt;/p&gt;
&lt;h2 id="三、怎么把 Token 成本打下来的（干货）"&gt;三、怎么把 Token 成本打下来的（干货）&lt;/h2&gt;
&lt;p&gt;先看对比表：&lt;/p&gt;
&lt;table class="table table-bordered table-striped"&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;th&gt;Agent&lt;/th&gt;
&lt;th&gt;相对成本&lt;/th&gt;
&lt;th&gt;工具数&lt;/th&gt;
&lt;th&gt;开源&lt;/th&gt;
&lt;th&gt;BYOK&lt;/th&gt;
&lt;th&gt;IM 集成&lt;/th&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Claude Code&lt;/td&gt;
&lt;td&gt;1.0×（基准）&lt;/td&gt;
&lt;td&gt;40+&lt;/td&gt;
&lt;td&gt;❌ 闭源&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;OpenClaw&lt;/td&gt;
&lt;td&gt;~1.5×&lt;/td&gt;
&lt;td&gt;23&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Hermes&lt;/td&gt;
&lt;td&gt;~3×&lt;/td&gt;
&lt;td&gt;52&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;OpenClacky&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;~0.8&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;16&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;✅ MIT&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;✅&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;✅&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;注：数据是内部常见 agent 任务的平均值，以 Claude Code 为基准。完整 benchmark 后续会放到 GitHub。&lt;/p&gt;

&lt;p&gt;不是靠砍功能，是靠&lt;strong&gt;每一层都做对选择，复利叠加下来的&lt;/strong&gt;。下面讲四个关键决策。&lt;/p&gt;
&lt;h3 id="3.1 缓存命中率接近 100%"&gt;3.1 缓存命中率接近 100%&lt;/h3&gt;
&lt;p&gt;这是最大的一块。Agent 类应用的对话上下文非常长，API 侧的 prompt caching 是省钱的核心。做对了，成本直接砍一半以上；做错了，基本等于没开。&lt;/p&gt;

&lt;p&gt;我们做了三件事：&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Session 不重启。&lt;/strong&gt; 很多 agent 每次启动或切换任务都会重建上下文，前缀一变 cache 全丢。OpenClacky 的 session 生命周期贯穿整个进程，工具调用、子 agent 调度、Skill 执行都挂在同一条上下文上。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;双 cache marker。&lt;/strong&gt; OpenAI / Anthropic 家的 caching 都是按前缀对齐。我们在 system prompt 尾部和对话中段各打一个 marker，确保即使中段有动态插入，前半段仍然完整命中。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Insert-then-Compress。&lt;/strong&gt; 这是关键。传统做法是上下文满了就"压缩 + 重写 system prompt"，一旦 system prompt 变了，前缀 cache 全失效，下一条消息变成冷启动。&lt;/p&gt;

&lt;p&gt;我们反过来：&lt;strong&gt;system prompt 永远不变&lt;/strong&gt;，压缩发生在对话区，用"插入一条总结 + 裁剪被总结的内容"的方式。system prompt 那段前缀的 cache 永远是热的。&lt;/p&gt;

&lt;p&gt;实测下来 &lt;strong&gt;cache 命中率接近 100%&lt;/strong&gt;。&lt;/p&gt;


&lt;h3 id="3.2 最小工具集：只有 16 个"&gt;3.2 最小工具集：只有 16 个&lt;/h3&gt;
&lt;p&gt;这点反直觉，我们反复测过 —— &lt;strong&gt;工具不是越多越好，是越精越好&lt;/strong&gt;。&lt;/p&gt;
&lt;table class="table table-bordered table-striped"&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;th style="text-align:center;"&gt;OpenClacky&lt;/th&gt;
&lt;th style="text-align:center;"&gt;Claude Code&lt;/th&gt;
&lt;th style="text-align:center;"&gt;OpenClaw&lt;/th&gt;
&lt;th style="text-align:center;"&gt;Hermes&lt;/th&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align:center;"&gt;&lt;strong&gt;16&lt;/strong&gt;&lt;/td&gt;
&lt;td style="text-align:center;"&gt;40+&lt;/td&gt;
&lt;td style="text-align:center;"&gt;23&lt;/td&gt;
&lt;td style="text-align:center;"&gt;52&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;为什么？因为工具的 schema 是要塞进每一次请求的 system prompt 的。52 个工具意味着 schema 膨胀 3~4 倍，每次对话都要多烧这么多 token。更糟的是 schema 越大，模型选工具的准确率反而下降（这点我们跑过对照实验）。&lt;/p&gt;

&lt;p&gt;我们的做法是把所有非核心能力下沉到 &lt;strong&gt;Skill 体系&lt;/strong&gt;，主工具只保留一个 &lt;code&gt;invoke_skill&lt;/code&gt; 元工具。需要 PPT？有 skill。需要 SQL 调优？有 skill。需要内部规范？有 skill。&lt;/p&gt;

&lt;p&gt;工具数量不是指标，&lt;strong&gt;任务完成率才是&lt;/strong&gt;。&lt;/p&gt;
&lt;h3 id="3.3 空闲时间自动压缩"&gt;3.3 空闲时间自动压缩&lt;/h3&gt;
&lt;p&gt;这个功能用起来很爽。你去开会、喝咖啡、吃饭，agent 并不是闲着 —— 它在后台做两件事：&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;把长上下文做渐进式压缩&lt;/li&gt;
&lt;li&gt;预热压缩后的 cache&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;等你回来发第一条消息，直接命中热 cache。&lt;strong&gt;冷启动首 token 成本降低 50%+。&lt;/strong&gt;&lt;/p&gt;
&lt;h3 id="3.4 BYOK + 子任务路由"&gt;3.4 BYOK + 子任务路由&lt;/h3&gt;
&lt;p&gt;BYOK 大家都有，但我们多走了一步：主任务用 Claude，子 agent 的子任务可以路由到 DeepSeek / Kimi 这种便宜模型。能力不损失太多，成本再砍一大块。&lt;/p&gt;

&lt;p&gt;这四条叠起来，才是那个 0.8 的数字。&lt;/p&gt;
&lt;h2 id="四、Skill —— Agent 的灵魂"&gt;四、Skill —— Agent 的灵魂&lt;/h2&gt;
&lt;p&gt;Skill 是我们认为 Agent 下一个阶段的关键。简单讲就是可插拔的"领域技能包"。&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;/&lt;/code&gt; 直接召唤&lt;/strong&gt;：即时浏览、模糊搜索、直接调用，上百个 Skill 随手可用&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;自然语言创建&lt;/strong&gt;：你描述想要什么，agent 自己起草 &lt;code&gt;SKILL.md&lt;/code&gt;、拆步骤、跑验证，不用写代码&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;自我进化&lt;/strong&gt;：每次执行完，agent 根据执行过程和结果回写更新。下一次调用更稳、更准&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;开放兼容&lt;/strong&gt;：支持 Claude Skills / Markdown Pack / 自定义格式&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;可变现&lt;/strong&gt;：打磨好的 Skill 可以打包加密分发、License 管理、自定价格&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;现在已经有用户在上面做垂直专家 Skill 了 —— 法律、医疗、理财规划，都有人跑起来。创作者计划的细节可以看官网。&lt;/p&gt;

&lt;p&gt;&lt;img src="https://l.ruby-china.com/photo/lyfi2003/fb902ce5-9588-4df5-aadb-c5290c6d6778.png!large" title="" alt=""&gt;&lt;/p&gt;
&lt;h2 id="五、用例示范"&gt;五、用例示范&lt;/h2&gt;
&lt;p&gt;今天 agent 能"问个项目问题"已经是及格线了，真正拉开差距的是能不能&lt;strong&gt;端到端交付一件事&lt;/strong&gt;。下面几个是我们自己和早期用户每天在跑的真实场景。&lt;/p&gt;
&lt;h3 id="用例一：从零交付一个 SaaS 原型（多 session 并行）"&gt;用例一：从零交付一个 SaaS 原型（多 session 并行）&lt;/h3&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;openclacky server
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;开三个 session 并行：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Session A:&lt;code&gt;/new&lt;/code&gt; 生成 Rails 7 + Tailwind 项目，建好 User / Subscription 模型，接 Stripe 订阅&lt;/li&gt;
&lt;li&gt;Session B：写落地页文案、SEO meta、FAQ，同时用 Skill 调 PPT 生成器出一份投资人 deck&lt;/li&gt;
&lt;li&gt;Session C：跑用户访谈资料分析，输出 PMF 判断报告&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;三个 session 共享同一份 workspace 上下文，改完 A 的定价策略，B 的落地页文案自动跟上。以前这是三个人半天的活。&lt;/p&gt;
&lt;h3 id="用例二：给一个 20 万行的老项目加功能，并让它自己跑完回归"&gt;用例二：给一个 20 万行的老项目加功能，并让它自己跑完回归&lt;/h3&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;gt; 我要在订单模块加"部分退款"能力，退款后要同步更新财务对账表、
  触发用户邮件、并在 admin 后台补一个退款审核流程。
  跑完 rake test 确保所有测试通过再交付。
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;它会自己：读相关模块 → 列修改计划 → 写代码 → 跑测试 → 测试挂了自己修 → 再跑 → 通过后给你一份变更摘要。&lt;/p&gt;

&lt;p&gt;整个过程基本不用盯，&lt;strong&gt;空闲时它还在后台做 context 压缩&lt;/strong&gt;，你去吃个饭回来直接看结果。&lt;/p&gt;
&lt;h3 id="用例三：自己造一个垂直 Skill，让它永久变强"&gt;用例三：自己造一个垂直 Skill，让它永久变强&lt;/h3&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;gt; 我经常要做"Rails 项目性能体检"，固定看 7 个指标：
  N+1、慢查询、索引缺失、Puma 配置、内存占用、cache 命中、日志异常。
  帮我做成一个 Skill。
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;它会起草 &lt;code&gt;SKILL.md&lt;/code&gt;、拆步骤、跑一次验证、让你确认。之后 &lt;code&gt;/&lt;/code&gt; 搜 "体检" 直接召唤，跑一次自动进化一次，下次更准。好的 Skill 可以打包加密分发、定价出售 —— 我们已经有用户靠卖垂直 Skill 跑通商业闭环了。&lt;/p&gt;
&lt;h3 id="用例四：跨代码库迁移 / 大重构"&gt;用例四：跨代码库迁移 / 大重构&lt;/h3&gt;
&lt;p&gt;比如把一个 Rails 5.2 + Sprockets + jQuery 的老项目，迁到 Rails 7.2 + Importmap + Stimulus + Turbo。这种活以前外包报价 5 万起，而且没人愿意接。&lt;/p&gt;

&lt;p&gt;现在开一个 session 挂上去，分阶段跑：依赖升级 → Asset Pipeline 切换 → 前端重写 → 测试补齐。每一阶段都让它先出计划再动手，人只做把关。&lt;/p&gt;
&lt;h3 id="用例五：Web UI 当成"&gt;用例五：Web UI 当成"自己的研发中台"&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;openclacky server&lt;/code&gt; 起来的 Web UI 不止是聊天窗口，是一个&lt;strong&gt;常驻的多人 / 多项目协作平台&lt;/strong&gt;：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;团队内几个人共享 session，接力干活&lt;/li&gt;
&lt;li&gt;跑 IM 集成（飞书 / 企微 / 微信），在群里 @ 它派任务，结果回群&lt;/li&gt;
&lt;li&gt;本机跑、或者扔一台服务器上 &lt;code&gt;--host 0.0.0.0&lt;/code&gt; 团队一起用&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;这一条是我们自己团队现在的日常 —— OpenClacky 自己就是用 OpenClacky 开发的。&lt;/p&gt;
&lt;h2 id="六、关于技术选型的一些闲话"&gt;六、关于技术选型的一些闲话&lt;/h2&gt;
&lt;p&gt;很多人问我们为什么坚持用 Ruby 做这个。&lt;/p&gt;

&lt;p&gt;其实 agent 这种场景，Rails 那套"convention over configuration"的哲学一样好使。迭代速度、元编程能力、gem 生态都是加分项。这两年我们三代架构、六个核心 harness 工程决策，反复推翻重来，Ruby 的表达力帮我们省了大量时间。&lt;/p&gt;

&lt;p&gt;我希望 Ruby 社区也有自己拿得出手的 AI 基础设施。不只是"能用"，而是"够硬、能打"。&lt;/p&gt;
&lt;h2 id="七、最后"&gt;七、最后&lt;/h2&gt;
&lt;p&gt;OpenClacky 1.0.0 正式发布了，来源 100% 开放（MIT），所有决策都可追溯。&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;GitHub：&lt;a href="https://github.com/clacky-ai/openclacky" rel="nofollow" target="_blank"&gt;https://github.com/clacky-ai/openclacky&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;官网 / 桌面安装包（macOS、Windows）：&lt;a href="https://www.openclacky.com/" rel="nofollow" target="_blank"&gt;https://www.openclacky.com/&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;欢迎 star、试用、提 issue、贡献 Skill，也欢迎直接在本帖回复交流。后续我会再写几篇详细技术博客：缓存命中率的工程细节、Skill 自进化的实现、子 agent 路由的 scheduling —— 每一篇都是这种干货。&lt;/p&gt;

&lt;p&gt;&lt;img src="https://l.ruby-china.com/photo/lyfi2003/cb2c1282-58b7-4356-a4d4-b605e56df0cb.png!large" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;项目背后由奇绩创坛、真格基金、红杉中国、高瓴资本支持，特此致谢。&lt;/p&gt;

&lt;p&gt;希望本文对大家有所帮助。有问题欢迎交流。&lt;/p&gt;</description>
      <author>lyfi2003</author>
      <pubDate>Sat, 02 May 2026 11:50:26 +0800</pubDate>
      <link>https://ruby-china.org/topics/44565</link>
      <guid>https://ruby-china.org/topics/44565</guid>
    </item>
    <item>
      <title>一夜重构！我用 18000 行代码打造了完全自研的 AI TUI 终端-IFAI Cli</title>
      <description>&lt;p&gt;&lt;strong&gt;作者：peterfei&lt;/strong&gt;
&lt;strong&gt;发布时间：2026-04-27&lt;/strong&gt;
&lt;strong&gt;阅读时间：3 分钟&lt;/strong&gt;
&lt;strong&gt;难度：⭐⭐⭐⭐⭐&lt;/strong&gt;&lt;/p&gt;

&lt;hr&gt;

&lt;p&gt;&lt;img src="https://l.ruby-china.com/photo/peterfei/483e41d8-2e29-434d-9d8d-a8ad8a30c8a8.gif" title="" alt=""&gt;&lt;/p&gt;
&lt;h2 id="凌晨 3 点的挑战"&gt;凌晨 3 点的挑战&lt;/h2&gt;
&lt;p&gt;深夜，电脑屏幕前的咖啡已经凉了第三遍。&lt;/p&gt;

&lt;p&gt;我盯着一段 1200 行的 &lt;code&gt;match&lt;/code&gt; 语句——这是 IfAI CLI 的事件处理系统。每次添加新功能，都要在 15 个文件里修修补补，单元测试覆盖率一团糟。&lt;/p&gt;

&lt;p&gt;"这不是我想要写的代码。"我对自己说。&lt;/p&gt;

&lt;p&gt;凌晨 3 点，我做出了一个疯狂的决定：&lt;strong&gt;推倒重来，一夜之间重构整个 TUI 架构&lt;/strong&gt;。&lt;/p&gt;

&lt;p&gt;天亮之前，我做到了。&lt;/p&gt;

&lt;hr&gt;
&lt;h2 id="为什么选择自研 TUI？"&gt;为什么选择自研 TUI？&lt;/h2&gt;
&lt;p&gt;市面上有现成的 TUI 框架（ratatui、crossterm），为什么还要自研？&lt;/p&gt;

&lt;p&gt;因为 &lt;strong&gt;AI 工具的交互需求，与传统终端应用完全不同&lt;/strong&gt;：&lt;/p&gt;
&lt;table class="table table-bordered table-striped"&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;th&gt;传统 TUI 应用&lt;/th&gt;
&lt;th&gt;AI CLI 工具&lt;/th&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;静态菜单&lt;/td&gt;
&lt;td&gt;实时流式输出&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;单一模式&lt;/td&gt;
&lt;td&gt;多模式切换（输入/搜索/帮助/审批）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;简单事件&lt;/td&gt;
&lt;td&gt;复杂事件链（键盘 + 鼠标 + 流+工具调用）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;固定布局&lt;/td&gt;
&lt;td&gt;自适应布局（欢迎页/内容/帮助覆盖层）&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;现有的 TUI 框架解决的是"如何渲染"，但 AI 工具需要的是"如何智能交互"。&lt;/p&gt;

&lt;p&gt;所以，我选择在 ratatui 之上，&lt;strong&gt;从零构建一套完整的 TUI 应用层架构&lt;/strong&gt;。&lt;/p&gt;

&lt;hr&gt;
&lt;h2 id="一夜之间：从混乱到优雅"&gt;一夜之间：从混乱到优雅&lt;/h2&gt;&lt;h3 id="重构前的痛点"&gt;重构前的痛点&lt;/h3&gt;&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="c1"&gt;// ❌ 重构前：1200 行面条式代码&lt;/span&gt;
&lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;handle_event&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Event&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="k"&gt;mut&lt;/span&gt; &lt;span class="n"&gt;App&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;match&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nn"&gt;Event&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;Key&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;match&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="py"&gt;.code&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nn"&gt;KeyCode&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;Char&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sc"&gt;'c'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="py"&gt;.modifiers&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;CONTROL&lt;/span&gt; &lt;span class="k"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="c1"&gt;// Ctrl+C 处理（嵌套 if-else）&lt;/span&gt;
                &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="py"&gt;.input_mode&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="py"&gt;.search_mode&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                        &lt;span class="c1"&gt;// ...&lt;/span&gt;
                    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                        &lt;span class="c1"&gt;// ...&lt;/span&gt;
                    &lt;span class="p"&gt;}&lt;/span&gt;
                &lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="c1"&gt;// ... 还有 100+ 个按键组合&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="nn"&gt;Event&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;Mouse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;mouse&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;match&lt;/span&gt; &lt;span class="n"&gt;mouse&lt;/span&gt;&lt;span class="py"&gt;.kind&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nn"&gt;MouseEventKind&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;ScrollUp&lt;/span&gt; &lt;span class="k"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="c1"&gt;// 滚动处理（30 行嵌套逻辑）&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="c1"&gt;// ...&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;问题&lt;/strong&gt;：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;添加一个搜索功能，需要改 8 个文件&lt;/li&gt;
&lt;li&gt;单元测试难以编写（所有逻辑耦合在一起）&lt;/li&gt;
&lt;li&gt;模式切换逻辑混乱（&lt;code&gt;if app.search_mode &amp;amp;&amp;amp; !app.input_mode&lt;/code&gt;）&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="重构后的架构"&gt;重构后的架构&lt;/h3&gt;&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="c1"&gt;// ✅ 重构后：声明式事件路由&lt;/span&gt;
&lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;build_event_router&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;EventRouter&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Event&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nn"&gt;EventRouter&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="nf"&gt;.on&lt;/span&gt;&lt;span class="p"&gt;(|&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="nd"&gt;matches!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nn"&gt;Event&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;Key&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt; &lt;span class="n"&gt;HelpEnterHandler&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="nf"&gt;.on&lt;/span&gt;&lt;span class="p"&gt;(|&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="nd"&gt;matches!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nn"&gt;Event&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;Key&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt; &lt;span class="n"&gt;HelpExitHandler&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="nf"&gt;.on&lt;/span&gt;&lt;span class="p"&gt;(|&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="nd"&gt;matches!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nn"&gt;Event&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;Key&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt; &lt;span class="n"&gt;SearchEnterHandler&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="nf"&gt;.on&lt;/span&gt;&lt;span class="p"&gt;(|&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="nd"&gt;matches!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nn"&gt;Event&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;Key&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt; &lt;span class="n"&gt;SearchInputHandler&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="nf"&gt;.on&lt;/span&gt;&lt;span class="p"&gt;(|&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="nd"&gt;matches!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nn"&gt;Event&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;Key&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt; &lt;span class="n"&gt;CombinedKeyHandler&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="nf"&gt;.on&lt;/span&gt;&lt;span class="p"&gt;(|&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="nd"&gt;matches!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nn"&gt;Event&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;Mouse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt; &lt;span class="nn"&gt;MouseScrollHandler&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
        &lt;span class="nf"&gt;.fallback&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;IgnoreHandler&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;优势&lt;/strong&gt;：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;每个处理器平均 30 行代码&lt;/li&gt;
&lt;li&gt;添加新功能只需 2 个文件改动&lt;/li&gt;
&lt;li&gt;单元测试覆盖率 100%&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;代码量对比&lt;/strong&gt;：
| 指标 | 重构前 | 重构后 | 改善 |
|------|--------|--------|------|
| 事件处理代码 | 1200 行 | 400 行 | &lt;strong&gt;-67%&lt;/strong&gt; |
| 单元测试覆盖 | 未知 | 404/404 通过 | &lt;strong&gt;100%&lt;/strong&gt; |
| 新功能改动文件 | 15 个 | 2 个 | &lt;strong&gt;-87%&lt;/strong&gt; |&lt;/p&gt;

&lt;hr&gt;
&lt;h2 id="完全自研的 TUI 组件"&gt;完全自研的 TUI 组件&lt;/h2&gt;
&lt;p&gt;IfAI CLI 的 TUI 不是简单的 ratatui 封装，而是从应用需求出发，完全自研的组件系统。&lt;/p&gt;
&lt;h3 id="1. 自研输入框 (input_composer.rs)"&gt;1. 自研输入框 (input_composer.rs)&lt;/h3&gt;
&lt;p&gt;为什么不用 rustyline？因为需要与 ratatui 渲染深度集成。&lt;/p&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="n"&gt;InputComposer&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;buffer&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;cursor_pos&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;usize&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="c1"&gt;// 字节索引，正确处理 UTF-8&lt;/span&gt;
    &lt;span class="n"&gt;history&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Vec&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;history_index&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Option&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nb"&gt;usize&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;draft_backup&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="c1"&gt;// 浏览历史时保存草稿&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;impl&lt;/span&gt; &lt;span class="n"&gt;InputComposer&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// 正确处理 CJK 字符的 Backspace&lt;/span&gt;
    &lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;handle_key&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="k"&gt;mut&lt;/span&gt; &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;KeyEvent&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;InputAction&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;match&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="py"&gt;.code&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nn"&gt;KeyCode&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Backspace&lt;/span&gt; &lt;span class="k"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;prev_char_start&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="py"&gt;.buffer&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="o"&gt;..&lt;/span&gt;&lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="py"&gt;.cursor_pos&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
                    &lt;span class="nf"&gt;.char_indices&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
                    &lt;span class="nf"&gt;.last&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
                    &lt;span class="nf"&gt;.map&lt;/span&gt;&lt;span class="p"&gt;(|(&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;)|&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                    &lt;span class="nf"&gt;.unwrap_or&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
                &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="py"&gt;.buffer&lt;/span&gt;&lt;span class="nf"&gt;.drain&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;prev_char_start&lt;/span&gt;&lt;span class="o"&gt;..&lt;/span&gt;&lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="py"&gt;.cursor_pos&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
                &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="py"&gt;.cursor_pos&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;prev_char_start&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="c1"&gt;// ...&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// 光标列计算（CJK 字符占 2 列）&lt;/span&gt;
&lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;cursor_col&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;composer&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;InputComposer&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;u16&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;display_width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;usize&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;composer&lt;/span&gt;&lt;span class="py"&gt;.buffer&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="o"&gt;..&lt;/span&gt;&lt;span class="n"&gt;composer&lt;/span&gt;&lt;span class="py"&gt;.cursor_pos&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="nf"&gt;.chars&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="nf"&gt;.map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;char_width&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="nf"&gt;.sum&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;composer&lt;/span&gt;&lt;span class="py"&gt;.prompt&lt;/span&gt;&lt;span class="nf"&gt;.len&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;display_width&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nb"&gt;u16&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;技术亮点&lt;/strong&gt;：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;字节级光标位置（正确处理 UTF-8 多字节字符）&lt;/li&gt;
&lt;li&gt;CJK 字符显示宽度计算&lt;/li&gt;
&lt;li&gt;历史记录草稿备份&lt;/li&gt;
&lt;li&gt;与 ratatui 的 Widget trait 集成&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="2. 自研欢迎页 (welcome.rs)"&gt;2. 自研欢迎页 (welcome.rs)&lt;/h3&gt;
&lt;p&gt;&lt;img src="https://l.ruby-china.com/photo/peterfei/1f965921-c100-41ab-9af8-3baa587e7a94.png" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;极简设计，无 ASCII art，启动时自动显示：&lt;/p&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="n"&gt;WelcomeWidget&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;subtitle&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;impl&lt;/span&gt; &lt;span class="n"&gt;WelcomeWidget&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;render&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;Vec&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Line&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;'static&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nd"&gt;vec!&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;
            &lt;span class="nn"&gt;Line&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;""&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="nn"&gt;Line&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nd"&gt;vec!&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;
                &lt;span class="nn"&gt;Span&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;default&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="cm"&gt;/* ... 21 个 default spans 用于居中 ... */&lt;/span&gt;
                &lt;span class="nn"&gt;Span&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;styled&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                    &lt;span class="s"&gt;"Welcome to IfAI"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                    &lt;span class="nn"&gt;Style&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;default&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
                        &lt;span class="nf"&gt;.fg&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nn"&gt;Color&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Cyan&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                        &lt;span class="nf"&gt;.add_modifier&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nn"&gt;Modifier&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;BOLD&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
                &lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="p"&gt;]),&lt;/span&gt;
            &lt;span class="c1"&gt;// 快捷键提示...&lt;/span&gt;
        &lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;实现细节&lt;/strong&gt;：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;空状态检测：&lt;code&gt;app.is_empty()&lt;/code&gt; → 显示欢迎页&lt;/li&gt;
&lt;li&gt;静态字符串满足 &lt;code&gt;'static&lt;/code&gt; 生命周期&lt;/li&gt;
&lt;li&gt;居中对齐算法（21 个 Span::default() 硬编码）&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="3. 自研快捷键帮助系统 (keybindings.rs)"&gt;3. 自研快捷键帮助系统 (keybindings.rs)&lt;/h3&gt;
&lt;p&gt;声明式快捷键定义，分类展示：&lt;/p&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="n"&gt;KeybindingCategory&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="k"&gt;'static&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="n"&gt;bindings&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Vec&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;KeyBinding&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;get_all_categories&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;Vec&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;KeybindingCategory&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nd"&gt;vec!&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="nn"&gt;KeybindingCategory&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="s"&gt;"📝 输入操作"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="nd"&gt;vec!&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;
                &lt;span class="n"&gt;KeyBinding&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;keys&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"Enter"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"提交输入"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
                &lt;span class="n"&gt;KeyBinding&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;keys&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"Ctrl+C"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"清空输入 / 中断"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
            &lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="c1"&gt;// ...&lt;/span&gt;
    &lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;按 &lt;code&gt;?&lt;/code&gt; 键唤起帮助覆盖层，左对齐 + 缩进布局，比 Codex 的复杂边框更易读。&lt;/p&gt;
&lt;h3 id="4. 实时搜索高亮算法"&gt;4. 实时搜索高亮算法&lt;/h3&gt;
&lt;p&gt;&lt;img src="https://l.ruby-china.com/photo/peterfei/f0e51e0f-32da-495b-8a37-8b8cee25ced6.png" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;支持大小写不敏感匹配、循环导航、三种高亮样式：&lt;/p&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;highlight_search_term&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;line&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;is_current&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;bool&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;is_other&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;bool&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;Line&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;'static&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="k"&gt;mut&lt;/span&gt; &lt;span class="n"&gt;spans&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;Vec&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="k"&gt;mut&lt;/span&gt; &lt;span class="n"&gt;last_pos&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nf"&gt;Some&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pos&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;line&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;last_pos&lt;/span&gt;&lt;span class="o"&gt;..&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="nf"&gt;.to_lowercase&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="nf"&gt;.find&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="nf"&gt;.to_lowercase&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

        &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;style&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;is_current&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nn"&gt;Style&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;default&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="nf"&gt;.fg&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nn"&gt;Color&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Black&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="nf"&gt;.bg&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nn"&gt;Color&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Yellow&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;is_other&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nn"&gt;Style&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;default&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="nf"&gt;.fg&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nn"&gt;Color&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Black&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="nf"&gt;.bg&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nn"&gt;Color&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;White&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nn"&gt;Style&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;default&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="nf"&gt;.fg&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nn"&gt;Color&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Yellow&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;};&lt;/span&gt;

        &lt;span class="n"&gt;spans&lt;/span&gt;&lt;span class="nf"&gt;.push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nn"&gt;Span&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;styled&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;line&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;pos&lt;/span&gt;&lt;span class="o"&gt;..&lt;/span&gt;&lt;span class="n"&gt;pos&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="nf"&gt;.len&lt;/span&gt;&lt;span class="p"&gt;()],&lt;/span&gt; &lt;span class="n"&gt;style&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
        &lt;span class="n"&gt;last_pos&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pos&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="nf"&gt;.len&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="nn"&gt;Line&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;spans&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;技术要点&lt;/strong&gt;：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;零配置：Ctrl+F 进入搜索模式&lt;/li&gt;
&lt;li&gt;实时反馈：输入即显示&lt;/li&gt;
&lt;li&gt;循环导航：↑/Enter / ↓/Shift+Enter&lt;/li&gt;
&lt;li&gt;状态持久化：搜索结果在匹配项间切换&lt;/li&gt;
&lt;/ul&gt;

&lt;hr&gt;
&lt;h2 id="18000 行代码的工程奇迹"&gt;18000 行代码的工程奇迹&lt;/h2&gt;&lt;h3 id="代码结构"&gt;代码结构&lt;/h3&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;src-tauri/src/bin/ifai/
├── main.rs                 (1065 行) — 入口 + 事件循环
├── tui.rs                  (896 行)  — TUI 核心
├── session.rs              (1788 行) — 会话管理
├── render.rs               (1148 行) — 渲染引擎
├── input_composer.rs       (661 行)  — 输入框
├── approval_overlay.rs     (777 行)  — 审批面板
├── permission_store.rs     (1078 行) — 权限存储
├── commands.rs             (1308 行) — 命令处理
├── event/
│   └── handlers.rs         (480 行)  — 事件处理器
├── token/
│   └── stream_status.rs    (795 行)  — Token 追踪
└── ... (20+ 其他模块)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;总计：18013 行纯手工打造的 Rust 代码&lt;/strong&gt;&lt;/p&gt;
&lt;h3 id="测试覆盖"&gt;测试覆盖&lt;/h3&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;cargo &lt;span class="nb"&gt;test&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; ifainew
&lt;span class="nb"&gt;test &lt;/span&gt;result: ok. 404 passed&lt;span class="p"&gt;;&lt;/span&gt; 0 failed&lt;span class="p"&gt;;&lt;/span&gt; 9 ignored
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;404 个单元测试，100% 通过率&lt;/strong&gt;，涵盖：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;UTF-8 字符边界处理&lt;/li&gt;
&lt;li&gt;CJK 宽度计算&lt;/li&gt;
&lt;li&gt;事件处理器责任链&lt;/li&gt;
&lt;li&gt;搜索高亮算法&lt;/li&gt;
&lt;li&gt;权限规则匹配&lt;/li&gt;
&lt;/ul&gt;

&lt;hr&gt;
&lt;h2 id="技术亮点"&gt;技术亮点&lt;/h2&gt;&lt;h3 id="1. 责任链模式事件系统"&gt;1. 责任链模式事件系统&lt;/h3&gt;
&lt;p&gt;每个事件处理器都是独立的 struct，实现了 &lt;code&gt;EventHandler&lt;/code&gt; trait：&lt;/p&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="k"&gt;trait&lt;/span&gt; &lt;span class="n"&gt;EventHandler&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;E&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;handle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="k"&gt;mut&lt;/span&gt; &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;E&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="k"&gt;mut&lt;/span&gt; &lt;span class="n"&gt;App&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;ControlFlow&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="k"&gt;enum&lt;/span&gt; &lt;span class="n"&gt;ControlFlow&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;Continue&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="c1"&gt;// 继续传递给下一个处理器&lt;/span&gt;
    &lt;span class="nf"&gt;Break&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;AppResult&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;  &lt;span class="c1"&gt;// 停止处理并返回结果&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;优势&lt;/strong&gt;：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;单一职责：每个处理器只做一件事&lt;/li&gt;
&lt;li&gt;可测试性：独立 struct，单元测试简单&lt;/li&gt;
&lt;li&gt;可扩展性：添加新功能只需新增处理器&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="2. 零拷贝渲染"&gt;2. 零拷贝渲染&lt;/h3&gt;
&lt;p&gt;使用 &lt;code&gt;Line&amp;lt;'static&amp;gt;&lt;/code&gt; 避免字符串分配：&lt;/p&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;render&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;Vec&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Line&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;'static&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nd"&gt;vec!&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="nn"&gt;Line&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Welcome to IfAI"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;  &lt;span class="c1"&gt;// &amp;amp;'static str&lt;/span&gt;
        &lt;span class="nn"&gt;Line&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nd"&gt;vec!&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;
            &lt;span class="nn"&gt;Span&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;styled&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"快捷键："&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nn"&gt;Style&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;default&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="nf"&gt;.fg&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nn"&gt;Color&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Yellow&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt;
        &lt;span class="p"&gt;]),&lt;/span&gt;
    &lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;h3 id="3. 多模态智能切换"&gt;3. 多模态智能切换&lt;/h3&gt;
&lt;p&gt;自动检测图片内容并切换到视觉模型：&lt;/p&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;select_model_with_multimodal_support&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;StreamRequest&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;String&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;has_multimodal&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="py"&gt;.messages&lt;/span&gt;&lt;span class="nf"&gt;.iter&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="nf"&gt;.any&lt;/span&gt;&lt;span class="p"&gt;(|&lt;/span&gt;&lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="nd"&gt;matches!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="py"&gt;.content&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nn"&gt;MessageContent&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Image&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="o"&gt;..&lt;/span&gt; &lt;span class="p"&gt;}));&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;has_multimodal&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="nf"&gt;.contains&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"4v"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="nf"&gt;.contains&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"5v"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s"&gt;"glm-4.5v"&lt;/span&gt;&lt;span class="nf"&gt;.to_string&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="py"&gt;.model&lt;/span&gt;&lt;span class="nf"&gt;.clone&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;h3 id="4. Claude Code 风格审批系统"&gt;4. Claude Code 风格审批系统&lt;/h3&gt;
&lt;p&gt;&lt;img src="https://l.ruby-china.com/photo/peterfei/9fbd6334-8352-4d18-9558-881716bd81ee.png" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;底部弹出面板 + 数字选项选择 + 持久化白名单：&lt;/p&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="k"&gt;enum&lt;/span&gt; &lt;span class="n"&gt;ApprovalDecision&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;ApproveOnce&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;         &lt;span class="c1"&gt;// 本次允许&lt;/span&gt;
    &lt;span class="n"&gt;ApproveAlways&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;       &lt;span class="c1"&gt;// 持久化白名单（Bash 工具）&lt;/span&gt;
    &lt;span class="n"&gt;ApproveSession&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;      &lt;span class="c1"&gt;// 会话级允许（文件编辑）&lt;/span&gt;
    &lt;span class="n"&gt;Deny&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;                &lt;span class="c1"&gt;// 拒绝&lt;/span&gt;
    &lt;span class="n"&gt;Abort&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;               &lt;span class="c1"&gt;// 中止请求&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;持久化到 &lt;code&gt;~/.ifai/permissions.toml&lt;/code&gt;：&lt;/p&gt;
&lt;pre class="highlight toml"&gt;&lt;code&gt;&lt;span class="nn"&gt;[[allow]]&lt;/span&gt;
&lt;span class="py"&gt;tool&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"bash"&lt;/span&gt;
&lt;span class="py"&gt;pattern&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"git diff:*"&lt;/span&gt;

&lt;span class="nn"&gt;[[deny]]&lt;/span&gt;
&lt;span class="py"&gt;tool&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"bash"&lt;/span&gt;
&lt;span class="py"&gt;pattern&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"rm -rf /*"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h2 id="通宵重构的经验总结"&gt;通宵重构的经验总结&lt;/h2&gt;
&lt;p&gt;如果你也想挑战一夜重构，这几点经验或许有用：&lt;/p&gt;
&lt;h3 id="✅ DO - 应该做的"&gt;✅ DO - 应该做的&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;写测试先行&lt;/strong&gt; - 元编程的 bug 往往更难调试&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;保持责任链简洁&lt;/strong&gt; - 每个处理器只做一件事&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;善用 IDE&lt;/strong&gt; - Rust-Analyzer 对宏支持很好&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;记录设计决策&lt;/strong&gt; - 为第二天重构的自己留文档&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id="❌ DON'T - 避免做的"&gt;❌ DON'T - 避免做的&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;不要过度抽象&lt;/strong&gt; - 表驱动不是万能药&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;不要忽视性能&lt;/strong&gt; - 元数据加载也有开销&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;不要忘记 Windows&lt;/strong&gt; - 条件编译要测试&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;不要牺牲可读性&lt;/strong&gt; - 代码是写给人看的&lt;/li&gt;
&lt;/ol&gt;

&lt;hr&gt;
&lt;h2 id="性能数据"&gt;性能数据&lt;/h2&gt;&lt;table class="table table-bordered table-striped"&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;th&gt;指标&lt;/th&gt;
&lt;th&gt;v0.4.3&lt;/th&gt;
&lt;th&gt;v0.4.4&lt;/th&gt;
&lt;th&gt;改善&lt;/th&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;二进制大小&lt;/td&gt;
&lt;td&gt;3.2 MB&lt;/td&gt;
&lt;td&gt;3.1 MB&lt;/td&gt;
&lt;td&gt;-3%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;编译时间&lt;/td&gt;
&lt;td&gt;45s&lt;/td&gt;
&lt;td&gt;38s&lt;/td&gt;
&lt;td&gt;-16%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;内存占用&lt;/td&gt;
&lt;td&gt;12 MB&lt;/td&gt;
&lt;td&gt;10 MB&lt;/td&gt;
&lt;td&gt;-17%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;启动时间&lt;/td&gt;
&lt;td&gt;80ms&lt;/td&gt;
&lt;td&gt;65ms&lt;/td&gt;
&lt;td&gt;-19%&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;hr&gt;
&lt;h2 id="为什么 Rust + 元编程是未来？"&gt;为什么 Rust + 元编程是未来？&lt;/h2&gt;&lt;h3 id="1. 编译时计算替代运行时开销"&gt;1. 编译时计算替代运行时开销&lt;/h3&gt;
&lt;p&gt;Rust 的宏系统 + 零成本抽象，让元编程不像反射那样拖累性能。&lt;/p&gt;
&lt;h3 id="2. 类型安全的动态配置"&gt;2. 类型安全的动态配置&lt;/h3&gt;
&lt;p&gt;表驱动设计在动态语言中很常见，但 Rust 带来了类型安全 + 内存安全的双重保障。&lt;/p&gt;
&lt;h3 id="3. 符合 AI 时代的开发范式"&gt;3. 符合 AI 时代的开发范式&lt;/h3&gt;
&lt;p&gt;当你在写 AI 代码生成工具时，&lt;strong&gt;元编程思维是必备技能&lt;/strong&gt;。因为：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;🤖 AI 本质上是 "元"（Meta）的 - 代码生成代码&lt;/li&gt;
&lt;li&gt;📊 表驱动是 AI 配置的基础&lt;/li&gt;
&lt;li&gt;🔄 可扩展性决定了 AI 工具的上限&lt;/li&gt;
&lt;/ul&gt;

&lt;hr&gt;
&lt;h2 id="结语"&gt;结语&lt;/h2&gt;
&lt;p&gt;凌晨 5 点，当最后一杯咖啡喝完，我看到测试通过的绿色输出：&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;test result: ok. 404 passed; 0 failed; 9 ignored
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这种满足感，是任何技术热点都给不了的。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;因为我知道：这不仅是一个工具，更是对未来开发范式的一次探索&lt;/strong&gt;。&lt;/p&gt;

&lt;p&gt;IfAI CLI v0.4.4 是一次通宵重构的成果，更是对"如何构建优雅的 AI 工具"的一次回答。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;完全自研的 TUI 架构，不是炫技，而是对用户体验的极致追求&lt;/strong&gt;。&lt;/p&gt;

&lt;hr&gt;
&lt;h2 id="试用 &amp;amp; 参与"&gt;试用 &amp;amp; 参与&lt;/h2&gt;
&lt;p&gt;如果你对 IfAI CLI 感兴趣，欢迎：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;⭐ GitHub：&lt;a href="https://github.com/peterfei/ifai" rel="nofollow" target="_blank" title=""&gt;github.com/peterfei/ifai&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;brew 下载：brew install ifai&lt;/li&gt;
&lt;li&gt;🐛 提 Issue 或者回复：任何问题都有反馈&lt;/li&gt;
&lt;li&gt;💬 加微信群：和更多技术爱好者交流&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;如果你也想打造自己的 AI CLI，或者对元编程架构有疑问，欢迎在评论区讨论！&lt;/strong&gt;&lt;/p&gt;

&lt;hr&gt;

&lt;p&gt;&lt;strong&gt;作者：peterfei&lt;/strong&gt;&lt;/p&gt;
&lt;h2 id="全栈工程师 / AI 架构师 / Rust 爱好者 / 通宵战士/ IFAI 作者"&gt;&lt;em&gt;全栈工程师 / AI 架构师 / Rust 爱好者 / 通宵战士/ IFAI 作者&lt;/em&gt;&lt;/h2&gt;
&lt;p&gt;#AI 工具 #Rust 编程 #TUI 开发 #元编程 #通宵重构 #CLI 工具&lt;/p&gt;</description>
      <author>peterfei</author>
      <pubDate>Mon, 27 Apr 2026 21:20:00 +0800</pubDate>
      <link>https://ruby-china.org/topics/44562</link>
      <guid>https://ruby-china.org/topics/44562</guid>
    </item>
    <item>
      <title>一个 Tauri + Rust AI 编辑器是怎么同时适配 5 家 AI 大厂的？IfAI v0.4.3 架构拆解</title>
      <description>&lt;blockquote&gt;
&lt;p&gt;一份 YAML 文件搞定一家 AI 提供商。IfAI Editor v0.4.3 用元数据驱动架构同时接入 OpenAI、DeepSeek、智谱、Kimi、Gemini 五家大模型厂商共 53 个模型，一行配置即可扩展新 Provider。本文拆解其架构设计、多模态统一抽象、SSE 流解析关键 Bug 修复，以及国际化工程化实践。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;hr&gt;

&lt;p&gt;&lt;img src="https://l.ruby-china.com/photo/peterfei/261aaa5d-af85-4a57-8a14-1343ac94fbbb.png" title="" alt="IfAI Editor v0.4.3 集成五家大模型厂商共 53 个模型 "&gt;&lt;/p&gt;
&lt;h2 id="五家 AI 大厂，一个编辑器"&gt;五家 AI 大厂，一个编辑器&lt;/h2&gt;
&lt;p&gt;2026 年的大模型战场，没有一家通吃。&lt;/p&gt;
&lt;table class="table table-bordered table-striped"&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;th&gt;厂商&lt;/th&gt;
&lt;th&gt;代表模型&lt;/th&gt;
&lt;th&gt;优势场景&lt;/th&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;OpenAI&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;GPT-5.4, O1, O3&lt;/td&gt;
&lt;td&gt;最强推理、视觉理解、工具调用&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;DeepSeek&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;DeepSeek V3.2&lt;/td&gt;
&lt;td&gt;极致性价比，$0.028/千 Token&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;智谱 AI&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;GLM-5.1, GLM-4.5V&lt;/td&gt;
&lt;td&gt;中文优化、视觉模型、Flash 高速推理&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Kimi（月之暗面）&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;K2.6, K2.5&lt;/td&gt;
&lt;td&gt;Thinking 思维链、256K 超长上下文、多模态&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Google Gemini&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Gemini 3.1, 2.5 Pro&lt;/td&gt;
&lt;td&gt;百万级上下文、原生多模态、免费 API&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;这五家提供商，协议各不相同：OpenAI 一套、Gemini 一套、DeepSeek 和 Kimi 虽然走 OpenAI 兼容格式但字段有差异。&lt;/p&gt;

&lt;p&gt;做一个 AI 编辑器，最痛苦的事不是写前端 UI、不是做工具调用审批——是&lt;strong&gt;每接入一个新的 AI 提供商，就要手写 500 行适配代码&lt;/strong&gt;。模型列表硬编码、请求格式手动转换、SSE 解析各写各的。&lt;/p&gt;

&lt;p&gt;IfAI Editor v0.4.3 的核心目标只有一个：&lt;strong&gt;消灭这种重复&lt;/strong&gt;。&lt;/p&gt;
&lt;h2 id="元数据驱动：用 YAML 替代 500 行 Rust 代码"&gt;元数据驱动：用 YAML 替代 500 行 Rust 代码&lt;/h2&gt;
&lt;p&gt;我们的方案是&lt;strong&gt;元数据驱动的 Provider 架构&lt;/strong&gt;。每个 AI 提供商不再需要手写 Rust 客户端，而是用一个 YAML 文件描述全部信息：&lt;/p&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;kimi-official&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Kimi 官方&lt;/span&gt;
  &lt;span class="na"&gt;provider_type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ai&lt;/span&gt;

&lt;span class="na"&gt;api_spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;base_url&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;https://api.moonshot.cn/v1&lt;/span&gt;
  &lt;span class="na"&gt;endpoint&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/chat/completions&lt;/span&gt;
  &lt;span class="na"&gt;auth&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;bearer_header&lt;/span&gt;
    &lt;span class="na"&gt;header_name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Authorization&lt;/span&gt;
    &lt;span class="na"&gt;format&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Bearer&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;{key}"&lt;/span&gt;

&lt;span class="na"&gt;models&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;kimi-k2.6&lt;/span&gt;
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Kimi K2.6&lt;/span&gt;
    &lt;span class="na"&gt;context_tokens&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;256000&lt;/span&gt;
    &lt;span class="na"&gt;capabilities&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;tools&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;streaming&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;vision&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;thinking&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;json_output&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;后端通过 Rust 的 &lt;code&gt;generate_provider_client!&lt;/code&gt; 宏，在编译期自动生成客户端代码。前端读取同一份 YAML 配置，自动渲染 Provider 选择界面和模型列表。&lt;/p&gt;

&lt;p&gt;效果：&lt;/p&gt;
&lt;table class="table table-bordered table-striped"&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;th&gt;指标&lt;/th&gt;
&lt;th&gt;重构前&lt;/th&gt;
&lt;th&gt;重构后&lt;/th&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;单个 Provider 代码量&lt;/td&gt;
&lt;td&gt;~500 行&lt;/td&gt;
&lt;td&gt;~150 行&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;接入新 Provider 耗时&lt;/td&gt;
&lt;td&gt;数天&lt;/td&gt;
&lt;td&gt;数分钟&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;重复代码率&lt;/td&gt;
&lt;td&gt;~95%&lt;/td&gt;
&lt;td&gt;~0%&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;&lt;h2 id="多模态：一张图、一个 PDF、一段代码，怎么统一？"&gt;多模态：一张图、一个 PDF、一段代码，怎么统一？&lt;/h2&gt;
&lt;p&gt;多模态输入是 2025 年 AI 编辑器的标配。但不同提供商的多模态传输格式完全不同：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;OpenAI：&lt;code&gt;content: [{type: "image_url", image_url: {url: "base64..."}}]&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Gemini：&lt;code&gt;inline_data: {mime_type: "image/png", data: "base64..."}&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Kimi/DeepSeek：走 OpenAI 兼容格式，但支持的字段有差异&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;IfAI v0.4.3 的做法是&lt;strong&gt;统一内容抽象&lt;/strong&gt;——所有输入被归一化为 &lt;code&gt;MultimodalContent&lt;/code&gt; 格式：&lt;/p&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// 统一的多模态内容表示&lt;/span&gt;
&lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;MultimodalContent&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;text&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;image&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;pdf&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;code&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;metadata&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;filename&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;language&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;mimeType&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;size&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// 发送时，FormatAdapter 自动转换为各提供商格式&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;message&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;text&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;分析这张架构图&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;image&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;data:image/png;base64,...&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;code&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;fn main() { ... }&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;filename&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;main.rs&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;language&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;rust&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;];&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;前端还实现了&lt;strong&gt;智能粘贴检测&lt;/strong&gt;——用户粘贴截图、拖拽文件时，自动识别内容类型并转换为多模态格式。图片超过 5MB 自动压缩，相同图片 Base64 只编码一次（缓存命中省 40% 内存）。&lt;/p&gt;
&lt;h2 id="那个让所有 Provider 都罢工的 SSE Bug"&gt;那个让所有 Provider 都罢工的 SSE Bug&lt;/h2&gt;
&lt;p&gt;这是 v0.4.3 修复的最关键 Bug，影响&lt;strong&gt;所有 OpenAI 兼容提供商&lt;/strong&gt;。&lt;/p&gt;

&lt;p&gt;问题出在 SSE 流解析的 &lt;code&gt;finish_reason&lt;/code&gt; 判断：&lt;/p&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Bug：finish_reason: null 也被判定为 finish 事件&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;is_finish_event&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="nf"&gt;.get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"finish_reason"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="nf"&gt;.is_some&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="c1"&gt;// 结果：所有内容事件被跳过，用户收到空响应&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;JSON 中 &lt;code&gt;finish_reason: null&lt;/code&gt; 意味着"流还没结束"，但 Rust 的 &lt;code&gt;json.get("finish_reason").is_some()&lt;/code&gt; 对 &lt;code&gt;null&lt;/code&gt; 值也返回 &lt;code&gt;true&lt;/code&gt;——于是每个 content chunk 都被错误识别为结束事件。&lt;/p&gt;

&lt;p&gt;修复：&lt;/p&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="c1"&gt;// 正确：null 不会通过 as_str()&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;is_finish_event&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="nf"&gt;.get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"finish_reason"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;.and_then&lt;/span&gt;&lt;span class="p"&gt;(|&lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="nf"&gt;.as_str&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
    &lt;span class="nf"&gt;.is_some&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;一行修复，解决 Kimi、DeepSeek、智谱、OpenAI 以及所有使用 OpenAIFormatAdapter 的提供商的流式响应问题。&lt;/p&gt;
&lt;h2 id="Kimi K2 的 Thinking 模式：双重内容流"&gt;Kimi K2 的 Thinking 模式：双重内容流&lt;/h2&gt;
&lt;p&gt;Kimi K2 系列有个独特功能——SSE 流中同时返回&lt;strong&gt;思考过程&lt;/strong&gt;和&lt;strong&gt;最终响应&lt;/strong&gt;：&lt;/p&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"choices"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"delta"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"reasoning_content"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"让我分析一下用户的需求..."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"content"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"根据您的需求，建议如下..."&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"finish_reason"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;IfAI v0.4.3 自动识别 &lt;code&gt;reasoning_content&lt;/code&gt; 字段，将思考过程和正式响应分流显示。用户可以查看模型的"思维链"，同时获得干净的回答。&lt;/p&gt;
&lt;h2 id="53 个模型，各有所长"&gt;53 个模型，各有所长&lt;/h2&gt;
&lt;p&gt;v0.4.3 共接入 5 家提供商、53 个模型，覆盖从轻量到旗舰的全系列：&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;OpenAI（15 个模型）&lt;/strong&gt;：从 GPT-3.5 Turbo 到 GPT-5.4，覆盖 O1/O3 推理系列。GPT-5.4 和 O3 支持最长 200K 上下文，适合复杂代码审查和长文档分析。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;DeepSeek（1 个模型）&lt;/strong&gt;：DeepSeek Chat V3.2，128K 上下文，支持工具调用和函数调用。关键是价格——缓存命中仅需 $0.028/千 Token，是日常编码助手的最佳选择。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;智谱 AI（11 个模型）&lt;/strong&gt;：GLM-5.1 为最新旗舰，GLM-4.5V 提供视觉理解能力，GLM-4.7 Flash 提供高速推理。中文场景表现突出，GLM-3 Turbo 提供 32K 上下文的轻量选择。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Kimi（12 个模型）&lt;/strong&gt;：K2.6 为最新旗舰，支持 Thinking 模式和 256K 超长上下文。K2 Thinking 系列专门用于复杂推理，V1 系列提供 8K/32K/128K 三种上下文选择，还有 Vision Preview 版本支持图片理解。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Gemini（15 个模型）&lt;/strong&gt;：从 1.5 到 3.1 三代产品。Gemini 1.5 Pro 拥有 2.8M 超长上下文（行业最长），3.1 系列支持图像生成预览。全部模型支持 1M 以上上下文，适合处理大型代码库和长文档。&lt;/p&gt;
&lt;h2 id="国际化工程化：2749 个键，三种语言，怎么保证一致性？"&gt;国际化工程化：2749 个键，三种语言，怎么保证一致性？&lt;/h2&gt;
&lt;p&gt;v0.4.3 新增俄语支持，现在覆盖中/英/俄三种语言，共 2749 个翻译键。
&lt;img src="https://l.ruby-china.com/photo/peterfei/778aa357-6534-4546-a0bc-b404c02eea84.png" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;真正的难点不是翻译本身，而是&lt;strong&gt;一致性保障&lt;/strong&gt;。2749 个键分布在 3 个 JSON 文件中，手动对齐几乎不可能出错。&lt;/p&gt;

&lt;p&gt;我们用工程化手段解决：&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;自动化校验脚本&lt;/strong&gt;：&lt;code&gt;check-i18n-parity.mjs&lt;/code&gt; 在每次 commit 时自动比对三个语言文件的键集合，发现不一致直接阻止提交（退出码 1）&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;硬编码扫描&lt;/strong&gt;：&lt;code&gt;scan-hardcoded-strings.mjs&lt;/code&gt; 扫描源码中未走 i18n 的中文硬编码&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Pre-commit Hook&lt;/strong&gt;：husky + lint-staged，修改 &lt;code&gt;locales/*.json&lt;/code&gt; 时自动触发校验&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;CI 门禁&lt;/strong&gt;：GitHub Actions 中 TypeScript 类型检查 + i18n 一致性校验 + 单元测试三道关卡&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="数据总览"&gt;数据总览&lt;/h2&gt;
&lt;p&gt;v0.4.3 版本的工程数据：&lt;/p&gt;
&lt;table class="table table-bordered table-striped"&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;th&gt;指标&lt;/th&gt;
&lt;th&gt;数据&lt;/th&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;AI 提供商&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;5 家（OpenAI、DeepSeek、智谱、Kimi、Gemini）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;模型总数&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;53 个&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;最长上下文&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;2.8M (Gemini 1.5 Pro)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;最短上下文&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;8K (GLM-3 Turbo, Moonshot V1 8K)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;支持视觉&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;4 家（OpenAI、智谱、Kimi、Gemini）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;支持工具调用&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;5 家（全部）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;支持 Thinking&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;2 家（OpenAI O1/O3、Kimi K2）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;协议&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;OpenAI Standard（4 家）+ Gemini Custom（1 家）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;i18n&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;2,749 键，中/英/俄 100% 对齐&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;测试&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;1155 单元测试通过，417 E2E 测试零失败&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;代码变更&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;+5,500 / -2,000 行&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;&lt;h2 id="写在最后"&gt;写在最后&lt;/h2&gt;
&lt;p&gt;AI 编辑器的竞争，正在从"能不能用"转向"好不好接"。&lt;/p&gt;

&lt;p&gt;元数据驱动架构的核心思想其实很简单：&lt;strong&gt;把变化的部分从代码中抽离出来，变成数据&lt;/strong&gt;。Provider 会越来越多，模型会越来越快，协议会不断演进——但只要描述这些变化的元数据格式稳定，代码就不用改。&lt;/p&gt;

&lt;p&gt;这是 v0.4.3 最重要的架构决策：&lt;strong&gt;让配置做配置的事，让代码做代码的事。&lt;/strong&gt;&lt;/p&gt;

&lt;hr&gt;

&lt;p&gt;*IfAI Editor 是基于 Tauri 2.0 + Rust 构建的跨平台 AI 代码编辑器，开源地址见 GitHub。 * &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt; &lt;a href="https://github.com/peterfei/ifai" rel="nofollow" target="_blank" title=""&gt;开源地址:Ifai&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;</description>
      <author>peterfei</author>
      <pubDate>Fri, 24 Apr 2026 17:52:27 +0800</pubDate>
      <link>https://ruby-china.org/topics/44561</link>
      <guid>https://ruby-china.org/topics/44561</guid>
    </item>
    <item>
      <title>Spinel -- Ruby AOT 编译器</title>
      <description>&lt;p&gt;&lt;a href="https://github.com/matz/spinel" rel="nofollow" target="_blank"&gt;https://github.com/matz/spinel&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Spinel 将 Ruby 源代码编译成独立的本地可执行文件。它执行全程序类型推断并生成优化的 C 代码，与 CRuby 相比速度显著提升。&lt;/p&gt;
&lt;/blockquote&gt;</description>
      <author>Rei</author>
      <pubDate>Fri, 24 Apr 2026 17:12:44 +0800</pubDate>
      <link>https://ruby-china.org/topics/44560</link>
      <guid>https://ruby-china.org/topics/44560</guid>
    </item>
    <item>
      <title>show history for selection lines in Zed</title>
      <description>&lt;p&gt;相信使用过 JetBrains 产品的用户，对内置的 Git 操作都会觉得很方便。其中很重要的一个功能：选中某几行代码，鼠标右键，使用 &lt;code&gt;show history for selection&lt;/code&gt;。就能展示出这几行代码的 git 提交历史记录，这个功能真的太香了。&lt;/p&gt;

&lt;p&gt;这个功能在很多编辑器上都不支持，我目前在使用 Zed 编辑器，最开始对 Git 操作支持很少，我都是使用 &lt;code&gt;lazygit&lt;/code&gt; 来操作，但上面提到的功能无法实现。所以我就尝试编写脚本去实现这个功能。本质上就是使用 git 原生命令行操作：&lt;code&gt;git log -L &amp;lt;range:file&amp;gt;&lt;/code&gt;  。将内容输出到一个临时文件中，然后通过 zed 编辑器打开它，同时使用 &lt;code&gt;diff&lt;/code&gt; 语法展示高亮信息。&lt;/p&gt;
&lt;h3 id="效果展示："&gt;效果展示：&lt;/h3&gt;
&lt;p&gt;&lt;img src="https://l.ruby-china.com/photo/qinsicheng/c2c295e9-bedd-4833-b06f-9b6f2d0b7579.gif" title="" alt=""&gt;&lt;/p&gt;
&lt;h3 id="Fish代码"&gt;Fish 代码&lt;/h3&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;function git_line_history --description "Show git history for specific lines in a file"
    # Validate argument count
    if test (count $argv) -ne 3
        echo "Usage: git_line_history &amp;lt;file_path&amp;gt; &amp;lt;start_line&amp;gt; &amp;lt;end_line&amp;gt;"
        return 1
    end

    set -l file_path $argv[1]
    set -l start_line $argv[2]
    set -l end_line $argv[3]

    # Verify the file exists
    if not test -f "$file_path"
        echo "Error: file '$file_path' does not exist"
        return 1
    end

    # Verify we are inside a git repository
    if not git rev-parse --is-inside-work-tree &amp;gt;/dev/null 2&amp;gt;&amp;amp;1
        echo "Error: current directory is not inside a git repository"
        return 1
    end

    # Create temporary files
    set -l temp_file (mktemp -t git_history.XXXXXXXXXX.diff)
    set -l raw_log_file (mktemp -t git_history.XXXXXXXXXX.raw)

    # Build the line range argument for git log -L
    set -l line_range "$start_line,$end_line:$file_path"

    # Fetch the line history
    git log --date=format:'%Y-%m-%d %H:%M:%S' -L $line_range &amp;gt;$raw_log_file 2&amp;gt;&amp;amp;1

    if test $status -ne 0
        echo "Error: failed to execute git log"
        if test -s $raw_log_file
            echo "Details:"
            cat $raw_log_file
        end
        rm -f $raw_log_file $temp_file
        return 1
    end

    # Process output: filter diff metadata and add separators between commits
    awk '
    BEGIN { in_commit = 0; has_content = 0; }
    /^commit / {
        if (in_commit &amp;amp;&amp;amp; has_content) print "\n--------------------------------------------------\n"
        in_commit = 1
        has_content = 1
        print
        next
    }
    /^diff --git|^index |^--- a\/|^\+\+\+ b\/|^@@ .* @@/ { next }
    {
        if (in_commit) print
        has_content = 1
    }
    ' $raw_log_file &amp;gt;$temp_file

    rm -f $raw_log_file

    # Check if the result is empty
    if not test -s $temp_file
        echo "Note: no matching commit history found"
        rm -f $temp_file
        return 0
    end

    # Choose an editor: prefer zeditor (Arch Linux), fall back to subl, then $EDITOR
    set -l editor_cmd
    if command -q zeditor
        set editor_cmd zeditor
    else if command -q zed
        set editor_cmd zed
    else if command -q subl
        set editor_cmd subl
    else if set -q EDITOR
        set editor_cmd $EDITOR
    else
        echo "Error: no suitable editor found"
        echo "Temporary file location: $temp_file"
        return 1
    end

    $editor_cmd "$temp_file"

    # Clean up old temporary files from previous runs
    function cleanup_git_history --on-event fish_exit
        # Clean up old temporary files from previous runs
        set -l tmpdir (dirname (mktemp -u))
        rm -f $tmpdir/git_history.*.diff 2&amp;gt;/dev/null
        rm -f $tmpdir/git_history.*.raw 2&amp;gt;/dev/null
    end

    return 0
end
&lt;/code&gt;&lt;/pre&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;function zed_git_line_history --description "Zed editor wrapper for git_line_history"
    # Extract line range from Zed environment variables
    set -l start_line $ZED_ROW
    set -l selection "$ZED_SELECTED_TEXT"
    set -l line_count (printf '%s\n' "$selection" | wc -l | string trim)
    set -l end_line (math $start_line + $line_count - 1)
    set -l file_path "$ZED_FILE"

    git_line_history "$file_path" $start_line $end_line
end
&lt;/code&gt;&lt;/pre&gt;&lt;h3 id="Zed 配置"&gt;Zed 配置&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;task&lt;/strong&gt;&lt;/p&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
   &lt;/span&gt;&lt;span class="nl"&gt;"label"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"git_line_history"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
   &lt;/span&gt;&lt;span class="nl"&gt;"command"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"zed_git_line_history"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
   &lt;/span&gt;&lt;span class="nl"&gt;"env"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
     &lt;/span&gt;&lt;span class="nl"&gt;"ZED_ROW"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"$ZED_ROW"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
     &lt;/span&gt;&lt;span class="nl"&gt;"ZED_SELECTED_TEXT"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"$ZED_SELECTED_TEXT"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
     &lt;/span&gt;&lt;span class="nl"&gt;"ZED_FILE"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"$ZED_FILE"&lt;/span&gt;&lt;span class="w"&gt;
   &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
   &lt;/span&gt;&lt;span class="nl"&gt;"hide"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"always"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
   &lt;/span&gt;&lt;span class="nl"&gt;"allow_concurrent_runs"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
   &lt;/span&gt;&lt;span class="nl"&gt;"use_new_terminal"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt;
 &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="err"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;keymap&lt;/strong&gt;&lt;/p&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"context"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"vim_mode == normal || vim_mode == visual"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"bindings"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"g h"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"task::Spawn"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"task_name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"git_line_history"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}],&lt;/span&gt;&lt;span class="w"&gt;
   &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;</description>
      <author>qinsicheng</author>
      <pubDate>Thu, 23 Apr 2026 22:20:47 +0800</pubDate>
      <link>https://ruby-china.org/topics/44558</link>
      <guid>https://ruby-china.org/topics/44558</guid>
    </item>
    <item>
      <title>试图交了个提升 Ruby Hash 性能的补丁</title>
      <description>&lt;h2 id="动机"&gt;动机&lt;/h2&gt;
&lt;p&gt;昨天在参加 RubyKaigi 的时候顺便瞄到了 &lt;code&gt;st.c&lt;/code&gt; 的实现，然后让我回想起来，现在的 Ruby 的 Hash 实现主要来自于 Vladimir Makarov 在 2016 年实现的精心调优的 open-addressing 实现的&lt;a href="https://bugs.ruby-lang.org/issues/12142" rel="nofollow" target="_blank" title=""&gt;版本&lt;/a&gt;。我突然想起 Google absl 实现里有个 &lt;a href="https://abseil.io/blog/20180927-swisstables" rel="nofollow" target="_blank" title=""&gt;Swiss Tables&lt;/a&gt; 实现吊打了 C++ 标准库的实现，而 Rust 的标准库实现基于的 &lt;a href="https://github.com/rust-lang/hashbrown" rel="nofollow" target="_blank" title=""&gt;hashbrown&lt;/a&gt; 也是基于相同的原理。我就在思考时隔十年我们能不能把 SwissTables 相关的算法移植到 Ruby 上进一步提升 Ruby 处理 &lt;code&gt;Hash&lt;/code&gt; 类型的性能，要知道 &lt;code&gt;Hash&lt;/code&gt; 类型是 Ruby 中调用极其频繁的类型，它替代了很多其它语言中 &lt;code&gt;struct&lt;/code&gt; 的功能，因此性能提升能带来很直观的收益。&lt;/p&gt;
&lt;h2 id="尝试"&gt;尝试&lt;/h2&gt;
&lt;p&gt;直接的移植后发现性能不升反降了，这和 Ruby 中 &lt;code&gt;Hash&lt;/code&gt; 的用法很有关系。Ruby 中非常多的 &lt;code&gt;Hash&lt;/code&gt; 对象非常小，而 Makarov 2016 年的实现对此做了非常细腻的优化。&lt;/p&gt;

&lt;p&gt;于是我的做法转变成了，保留 Ruby 中原先对不同大小 Hash 的分层存储，从 Swiss Tables 对 control bit 更好的对超标量 / 向量化的支持和 H2 短哈希更好的缓存命中这两个角度进一步进行优化，只针对大的 Hash 对象（容量 &amp;gt;= 64）的情况进行处理。&lt;/p&gt;
&lt;h2 id="设计"&gt;设计&lt;/h2&gt;&lt;h3 id="1. 三数组布局"&gt;1. 三数组布局&lt;/h3&gt;&lt;table class="table table-bordered table-striped"&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;th&gt;array&lt;/th&gt;
&lt;th&gt;width&lt;/th&gt;
&lt;th&gt;role&lt;/th&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;entries[]&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;16B (以前为 24B)&lt;/td&gt;
&lt;td&gt;以插入顺序记录 &lt;code&gt;(key, record)&lt;/code&gt; 的日志&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;hashes[]&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;每 slot 4B&lt;/td&gt;
&lt;td&gt;与之并行的截断 32 位哈希数组（也编码删除标记）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;bins[]&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;自适应 1 / 2 / 4 / 8B&lt;/td&gt;
&lt;td&gt;基于哈希的索引数组，存放指向 &lt;code&gt;entries[]&lt;/code&gt; 的索引&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;ctrl[]&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;每 slot 1B&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;H2&lt;/code&gt;（哈希的高 7 位）或 &lt;code&gt;EMPTY (0xff)&lt;/code&gt; / &lt;code&gt;DELETED (0xfe)&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;&lt;code&gt;entries[]&lt;/code&gt; 和 &lt;code&gt;hashes[]&lt;/code&gt; 长度相同并由相同索引寻址，因此迭代和 slot 重用保持简单。&lt;code&gt;ctrl[]&lt;/code&gt; 是快速拒绝过滤器，和 &lt;code&gt;bins[]&lt;/code&gt; 并列；只有当某个 &lt;code&gt;ctrl&lt;/code&gt; 字节与 H2 匹配时，我们才会加载（现在更小的）entry 和并行哈希以确认匹配。对 &lt;code&gt;ctrl[]&lt;/code&gt; 在 &lt;code&gt;uint64_t&lt;/code&gt; 读取上完成，这本质上是一种 SWAR 优化。&lt;/p&gt;
&lt;h3 id="2. 紧凑的 st_table_entry"&gt;2. 紧凑的 &lt;code&gt;st_table_entry&lt;/code&gt;
&lt;/h3&gt;
&lt;p&gt;修改前：&lt;/p&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;&lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="n"&gt;st_table_entry&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;st_hash_t&lt;/span&gt;  &lt;span class="n"&gt;hash&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;    &lt;span class="cm"&gt;/* 8 B */&lt;/span&gt;
    &lt;span class="n"&gt;st_data_t&lt;/span&gt;  &lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;     &lt;span class="cm"&gt;/* 8 B */&lt;/span&gt;
    &lt;span class="n"&gt;st_data_t&lt;/span&gt;  &lt;span class="n"&gt;record&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;  &lt;span class="cm"&gt;/* 8 B */&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;                       &lt;span class="cm"&gt;/* 24 B */&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;修改后：&lt;/p&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;&lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="n"&gt;st_table_entry&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;st_data_t&lt;/span&gt;  &lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;     &lt;span class="cm"&gt;/* 8 B */&lt;/span&gt;
    &lt;span class="n"&gt;st_data_t&lt;/span&gt;  &lt;span class="n"&gt;record&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;  &lt;span class="cm"&gt;/* 8 B */&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;                       &lt;span class="cm"&gt;/* 16 B */&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;哈希移入 &lt;code&gt;tab-&amp;gt;hashes[i]&lt;/code&gt;，由于大多数情况可以依赖 H2 进行匹配。因此这样我们可以进一步提升 cacheline 的命中效率，从而提高 CPU L1 的命中率。&lt;/p&gt;
&lt;h3 id="3. 新的哈希函数"&gt;3. 新的哈希函数&lt;/h3&gt;
&lt;p&gt;仅存储低 32 位哈希意味着我们不能再从原始 &lt;code&gt;unsigned long&lt;/code&gt; 哈希的高位 7 位读取 H2，而这是 SwissTables 原先设计所采用的做法。naive 的实现是在每次重建/重哈希/&lt;code&gt;st_shift&lt;/code&gt;/&lt;code&gt;st_general_foreach&lt;/code&gt; 中重新计算完整 64 位哈希以保证正确性，但会严重损害插入和重建性能 —— 尤其是对于类似 string 这样的变长类型的性能损害很大。&lt;/p&gt;

&lt;p&gt;解决方法是从截断的 32 位哈希的一个不同位段推导 H2，该位段与用于选取 bin 的位段不重叠：&lt;/p&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;&lt;span class="cm"&gt;/* bin index: low `bin_power` bits, masked by `bins_mask(tab)`     */&lt;/span&gt;
&lt;span class="n"&gt;hash_bin&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;uint32_t&lt;/span&gt; &lt;span class="n"&gt;h&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;st_table&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;tab&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;h&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;bins_mask&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tab&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="cm"&gt;/* H2: bits 25..31 of the same 32-bit hash, never overlaps with     */&lt;/span&gt;
&lt;span class="cm"&gt;/* the bin index because bin_power is capped well under 25 in       */&lt;/span&gt;
&lt;span class="cm"&gt;/* practice.                                                        */&lt;/span&gt;
&lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="kr"&gt;inline&lt;/span&gt; &lt;span class="kt"&gt;unsigned&lt;/span&gt; &lt;span class="kt"&gt;char&lt;/span&gt;
&lt;span class="nf"&gt;st_swiss_h2&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;st_hash_t&lt;/span&gt; &lt;span class="n"&gt;hash&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;unsigned&lt;/span&gt; &lt;span class="kt"&gt;char&lt;/span&gt;&lt;span class="p"&gt;)((&lt;/span&gt;&lt;span class="n"&gt;hash&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;25&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt; &lt;span class="mh"&gt;0x7f&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这使得存储的 &lt;code&gt;uint32_t&lt;/code&gt; 是自包含的：每次探测都能从同一字中读取 bin 索引和 H2 字节，无需调用 &lt;code&gt;do_hash()&lt;/code&gt;，重建/重哈希/移位/foreach 全部使用 &lt;code&gt;ST_HASH_AT_IDX(tab, i)&lt;/code&gt; 而不是重新计算。&lt;/p&gt;

&lt;p&gt;截断带来的另外两个细节：&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;code&gt;normalize_hash_value()&lt;/code&gt; 已更新，以确保 &lt;code&gt;0xFFFFFFFF&lt;/code&gt;（32 位哈希 slot 的墓碑标记）永远不会与真实哈希值冲突——如果截断结果落在该保留值上我们会进行跳变（bump）。在未启用 &lt;code&gt;ST_USE_SWISS_BINS&lt;/code&gt; 编译的平台上保留 64 位的保留值。&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;MARK_ENTRY_DELETED&lt;/code&gt; / &lt;code&gt;DELETED_ENTRY_P&lt;/code&gt; 宏新增了 table 参数，以便读取/写入并行的哈希 slot。&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id="4. 在 H2 匹配时进行 prefetch"&gt;4. 在 H2 匹配时进行 prefetch&lt;/h3&gt;
&lt;p&gt;当 SWAR 在控制组中找到候选的 H2 匹配时，下一步是加载匹配的 &lt;code&gt;st_table_entry&lt;/code&gt; 的哈希。我们在 &lt;code&gt;find_table_entry_ind&lt;/code&gt; / &lt;code&gt;find_table_bin_ind&lt;/code&gt; / &lt;code&gt;find_table_bin_ptr_and_reserve&lt;/code&gt; 中在检测到匹配后立即对两者发出 &lt;code&gt;__builtin_prefetch&lt;/code&gt;。在以查找为主的工作负载上，这能掩盖 SWAR 快速过滤器本会暴露的 CPU L2 缓存延迟。&lt;/p&gt;
&lt;h2 id="与 master 的结果比较"&gt;与 &lt;code&gt;master&lt;/code&gt; 的结果比较&lt;/h2&gt;
&lt;p&gt;两个二进制均来自相同代码树（&lt;code&gt;master = 42b3cdc51a&lt;/code&gt;，&lt;code&gt;swiss = 3c0446847f&lt;/code&gt;），相同编译器，相同编译选项。每个脚本运行 5 次，使用 &lt;code&gt;--disable-gems&lt;/code&gt;，报告最佳结果。内存为 macOS arm64（M4 Max）上 &lt;code&gt;/usr/bin/time -l&lt;/code&gt; 的最大常驻集大小（maximum resident set size）。&lt;/p&gt;
&lt;h3 id="吞吐量（时间越低越好）"&gt;吞吐量（时间越低越好）&lt;/h3&gt;&lt;table class="table table-bordered table-striped"&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;th&gt;benchmark&lt;/th&gt;
&lt;th style="text-align:right;"&gt;master (s)&lt;/th&gt;
&lt;th style="text-align:right;"&gt;swiss (s)&lt;/th&gt;
&lt;th style="text-align:right;"&gt;speedup&lt;/th&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;aref_int_large&lt;/td&gt;
&lt;td style="text-align:right;"&gt;0.8352&lt;/td&gt;
&lt;td style="text-align:right;"&gt;0.6862&lt;/td&gt;
&lt;td style="text-align:right;"&gt;&lt;strong&gt;+17.8 %&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;aref_str_large&lt;/td&gt;
&lt;td style="text-align:right;"&gt;0.9915&lt;/td&gt;
&lt;td style="text-align:right;"&gt;0.8406&lt;/td&gt;
&lt;td style="text-align:right;"&gt;&lt;strong&gt;+15.2 %&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;aref_miss_large&lt;/td&gt;
&lt;td style="text-align:right;"&gt;1.0803&lt;/td&gt;
&lt;td style="text-align:right;"&gt;0.7896&lt;/td&gt;
&lt;td style="text-align:right;"&gt;&lt;strong&gt;+26.9 %&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;aref_mix_50&lt;/td&gt;
&lt;td style="text-align:right;"&gt;1.0337&lt;/td&gt;
&lt;td style="text-align:right;"&gt;0.8201&lt;/td&gt;
&lt;td style="text-align:right;"&gt;&lt;strong&gt;+20.7 %&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;insert_grow&lt;/td&gt;
&lt;td style="text-align:right;"&gt;0.1138&lt;/td&gt;
&lt;td style="text-align:right;"&gt;0.1105&lt;/td&gt;
&lt;td style="text-align:right;"&gt;+2.9 %&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;churn (mixed RW)&lt;/td&gt;
&lt;td style="text-align:right;"&gt;0.0321&lt;/td&gt;
&lt;td style="text-align:right;"&gt;0.0304&lt;/td&gt;
&lt;td style="text-align:right;"&gt;+5.5 %&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;iterate&lt;/td&gt;
&lt;td style="text-align:right;"&gt;0.0566&lt;/td&gt;
&lt;td style="text-align:right;"&gt;0.0565&lt;/td&gt;
&lt;td style="text-align:right;"&gt;±0.1 %&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;查找是主要的收益点——成功查找（+15 % … +20 %）和未命中（+27 %）均有提升，后者的原因是缺失的 key 现在能在第一个 SWAR 组立即短路，无需加载 entry/bin。插入和混合负载略有提速，因为重建不再调用 &lt;code&gt;do_hash&lt;/code&gt;。迭代不受影响（它不需要访问 bins 或 ctrl）。&lt;/p&gt;
&lt;h3 id="内存"&gt;内存&lt;/h3&gt;&lt;table class="table table-bordered table-striped"&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;th&gt;benchmark&lt;/th&gt;
&lt;th style="text-align:right;"&gt;master (MB)&lt;/th&gt;
&lt;th style="text-align:right;"&gt;swiss (MB)&lt;/th&gt;
&lt;th style="text-align:right;"&gt;delta&lt;/th&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;insert_grow&lt;/td&gt;
&lt;td style="text-align:right;"&gt;66.62&lt;/td&gt;
&lt;td style="text-align:right;"&gt;60.44&lt;/td&gt;
&lt;td style="text-align:right;"&gt;&lt;strong&gt;−9.3 %&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;aref_str_large&lt;/td&gt;
&lt;td style="text-align:right;"&gt;15.27&lt;/td&gt;
&lt;td style="text-align:right;"&gt;15.23&lt;/td&gt;
&lt;td style="text-align:right;"&gt;−0.3 %&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;aref_mix_50&lt;/td&gt;
&lt;td style="text-align:right;"&gt;16.64&lt;/td&gt;
&lt;td style="text-align:right;"&gt;16.61&lt;/td&gt;
&lt;td style="text-align:right;"&gt;−0.2 %&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;churn&lt;/td&gt;
&lt;td style="text-align:right;"&gt;13.19&lt;/td&gt;
&lt;td style="text-align:right;"&gt;13.03&lt;/td&gt;
&lt;td style="text-align:right;"&gt;−1.2 %&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;每表内存（通过 &lt;code&gt;ObjectSpace.memsize_of&lt;/code&gt;，在多个哈希上求和）：&lt;/p&gt;
&lt;table class="table table-bordered table-striped"&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;th&gt;workload&lt;/th&gt;
&lt;th style="text-align:right;"&gt;master&lt;/th&gt;
&lt;th style="text-align:right;"&gt;swiss&lt;/th&gt;
&lt;th style="text-align:right;"&gt;delta&lt;/th&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2 000 hashes × 200 entries&lt;/td&gt;
&lt;td style="text-align:right;"&gt;14.66 MB&lt;/td&gt;
&lt;td style="text-align:right;"&gt;12.11 MB&lt;/td&gt;
&lt;td style="text-align:right;"&gt;&lt;strong&gt;−17.4 %&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1 hash × 100 000 entries&lt;/td&gt;
&lt;td style="text-align:right;"&gt;4.19 MB&lt;/td&gt;
&lt;td style="text-align:right;"&gt;3.28 MB&lt;/td&gt;
&lt;td style="text-align:right;"&gt;&lt;strong&gt;−21.9 %&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;2000 hashes × 200 entries 模拟了常见的 Rails 负载，这可以节省 ~17.4% 的 Hash 内存占用。&lt;/p&gt;

&lt;p&gt;这两种内存视图一致：任何保留大量存活条目的工作负载（无论是一个大哈希还是许多小哈希）都会因条目从 24B → 16B 的变化以及每槽 1B 的 &lt;code&gt;ctrl[]&lt;/code&gt; / 4B 的 &lt;code&gt;hashes[]&lt;/code&gt; 配对而显著减少内存，因为它们合计（每槽 5B）仍小于在 &lt;code&gt;entries[]&lt;/code&gt; 中每槽节省的 8 B。表的每表开销在小于约 64 条目时大致保持不变；Swiss 路径仅在 &lt;code&gt;entry_power ≥ 6&lt;/code&gt;（表容量 ≥ 64）时启用。&lt;/p&gt;
&lt;h2 id="详见"&gt;详见&lt;/h2&gt;
&lt;p&gt;&lt;a href="https://bugs.ruby-lang.org/issues/22011" rel="nofollow" target="_blank"&gt;https://bugs.ruby-lang.org/issues/22011&lt;/a&gt;&lt;/p&gt;</description>
      <author>dsh0416</author>
      <pubDate>Thu, 23 Apr 2026 13:15:53 +0800</pubDate>
      <link>https://ruby-china.org/topics/44557</link>
      <guid>https://ruby-china.org/topics/44557</guid>
    </item>
    <item>
      <title>西安垚海 有大佬在里面吗？求问怎么样</title>
      <description>&lt;p&gt;想了解里面公司内文化，氛围。以及是否靠谱&lt;/p&gt;</description>
      <author>qinsicheng</author>
      <pubDate>Mon, 20 Apr 2026 21:37:54 +0800</pubDate>
      <link>https://ruby-china.org/topics/44556</link>
      <guid>https://ruby-china.org/topics/44556</guid>
    </item>
    <item>
      <title>有没有招聘 rails/nodejs 兼职</title>
      <description>&lt;p&gt;有招聘 rails/nodejs 兼职的欢迎联系我&lt;/p&gt;

&lt;p&gt;有正式工作，也是从事这方面的，想在晚上和周末做点兼职&lt;/p&gt;

&lt;p&gt;有八年左右这方面工作经验的老登，技术还算熟练&lt;/p&gt;</description>
      <author>puma</author>
      <pubDate>Mon, 20 Apr 2026 18:15:40 +0800</pubDate>
      <link>https://ruby-china.org/topics/44555</link>
      <guid>https://ruby-china.org/topics/44555</guid>
    </item>
    <item>
      <title>开发了一个自动化运维平台，现在内测中，免费，有命令行自动化需求的欢迎试用</title>
      <description>&lt;p&gt;可以一站式帮你用自然语言开发命令行自动化脚本
地址 &lt;a href="https://qingxun.online" rel="nofollow" target="_blank"&gt;https://qingxun.online&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;试用可以找我加信用点&lt;/p&gt;</description>
      <author>zhb85</author>
      <pubDate>Thu, 16 Apr 2026 20:56:43 +0800</pubDate>
      <link>https://ruby-china.org/topics/44554</link>
      <guid>https://ruby-china.org/topics/44554</guid>
    </item>
    <item>
      <title>用 railway 这个服务器部署 ruby on rails, 发现很不错，但是有点贵，大家看看可以参考</title>
      <description>&lt;p&gt;我最近帮客户做网站，我把网站放到了 railway.com 上面，用 railway 的服务器和数据库，用 Google 存储，存储图片，&lt;/p&gt;

&lt;p&gt;里面有 330 个产品信息。分别是 330 个产品图片、中英产品介绍。&lt;/p&gt;

&lt;p&gt;但是用了 13 天，railway 的 5 美金就花完了，又充值了 5 美金...&lt;/p&gt;

&lt;p&gt;感觉对这客户有点贵，官网展示网站，服务器费用一个月得超过 10 美金...&lt;/p&gt;</description>
      <author>shibin</author>
      <pubDate>Thu, 16 Apr 2026 19:31:53 +0800</pubDate>
      <link>https://ruby-china.org/topics/44553</link>
      <guid>https://ruby-china.org/topics/44553</guid>
    </item>
    <item>
      <title>procodile, 一款类似 foreman 的工具，但可运行在前台或后台，也支持定时任务</title>
      <description>&lt;h2 id="宣布发布 procodile 2.0.0-rc1"&gt;宣布发布 &lt;code&gt;procodile&lt;/code&gt; 2.0.0-rc1&lt;/h2&gt;
&lt;p&gt;我很高兴地宣布 &lt;code&gt;procodile&lt;/code&gt; 2.0.0-rc1 正式发布。自上一个版本以来，这是一次重大更新，累计包含了 130 多个 commit。&lt;/p&gt;

&lt;p&gt;这次更新之所以非常重要，主要有以下几个原因：&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;这是第一个完整实现原始 Ruby 版本功能的 &lt;a href="https://github.com/adamcooke/procodile" rel="nofollow" target="_blank" title=""&gt;procodile&lt;/a&gt; 发布版本。&lt;/li&gt;
&lt;li&gt;它还修复了原始 Ruby 版本中存在的许多 bug 和边界情况问题。(感谢 Crystal 程序语言，让暴露和修复这些问题，相对于 Ruby 简单得多）&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;与此同时，2.0 版本在原始 Ruby 版本的基础上也有了显著增强，并引入了一些此前 Ruby 版本并不具备的新功能：&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;支持通过 &lt;strong&gt;&lt;code&gt;.env&lt;/code&gt; 文件&lt;/strong&gt;加载环境变量，这项功能基于 &lt;a href="/jwoertink" class="user-mention" title="@jwoertink"&gt;&lt;i&gt;@&lt;/i&gt;jwoertink&lt;/a&gt; 开发的 &lt;a href="https://github.com/luckyframework/lucky_env" rel="nofollow" target="_blank" title=""&gt;lucky_env&lt;/a&gt;。&lt;/li&gt;
&lt;li&gt;支持&lt;strong&gt;定时任务&lt;/strong&gt;。这是一次重要更新，使你可以在同一个地方同时管理长期运行的进程和定时任务。得益于 &lt;a href="/kostya" class="user-mention" title="@kostya"&gt;&lt;i&gt;@&lt;/i&gt;kostya&lt;/a&gt; 开发的 &lt;a href="https://github.com/kostya/cron_parser" rel="nofollow" target="_blank" title=""&gt;cron_parser&lt;/a&gt;，它不仅支持标准的五段式 crontab 表达式，还支持精确到秒级的 cron 调度。&lt;/li&gt;
&lt;li&gt;引入了 &lt;strong&gt;Runtime Issues&lt;/strong&gt;。当命令执行结束后如果检测到问题，你会立即在命令行中收到反馈。&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;请参阅 &lt;a href="https://github.com/crystal-china/procodile/wiki" rel="nofollow" target="_blank" title=""&gt;Wiki&lt;/a&gt;，其中提供了结构清晰且内容详尽的文档说明。&lt;/p&gt;</description>
      <author>zw963</author>
      <pubDate>Wed, 15 Apr 2026 15:33:12 +0800</pubDate>
      <link>https://ruby-china.org/topics/44552</link>
      <guid>https://ruby-china.org/topics/44552</guid>
    </item>
    <item>
      <title>南京全职，预招聘，Ruby on Rails 全栈开发工程师一名（discourse 插件方向）</title>
      <description>&lt;p&gt;招 Rails 全栈开发&lt;/p&gt;

&lt;p&gt;岗位职责&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;开发 Discourse 插件（Ruby on Rails + Ember.js）&lt;/li&gt;
&lt;li&gt;维护现有插件代码，排查线上问题&lt;/li&gt;
&lt;li&gt;只招一人，学习能力强的可作为技术总监（涨工资 + 期权）&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;任职要求&lt;/p&gt;

&lt;p&gt;必须&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;2 年以上 Rails 开发经验，熟悉 ActiveRecord、Sidekiq（若 Rails 经验少，学习能力很强亦可）&lt;/li&gt;
&lt;li&gt;有前端框架经验（Vue/React/Ember 最好）&lt;/li&gt;
&lt;li&gt;能阅读英文技术文档&lt;/li&gt;
&lt;li&gt;熟练使用 AI 工具（Claude/Cursor 等）辅助开发&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;加分&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;有 Discourse 或其他论坛系统开发经验&lt;/li&gt;
&lt;li&gt;PostgreSQL 性能优化经验&lt;/li&gt;
&lt;li&gt;开源项目贡献&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;薪资福利&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;月薪 18-20K，13 薪&lt;/li&gt;
&lt;li&gt;五险一金&lt;/li&gt;
&lt;li&gt;工作时间 9:00-11:30，13:30-17:30，不加班（以后趋势都让 AI 干活）&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;备注&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;南京地区几年没有 Ruby 岗位帖子，所以提前发布，预计 5 月开始招&lt;/li&gt;
&lt;li&gt;后期面试有 10-20 道 AI 出的选择题，判断能力范围（毕竟我也不懂代码，不会手写算法题）&lt;/li&gt;
&lt;li&gt;介意勿扰&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;联系方式
请发送简历至：724969715@qq.com
标题格式：【Rails 开发】姓名 - 工作年限&lt;/p&gt;</description>
      <author>chengr25</author>
      <pubDate>Tue, 14 Apr 2026 16:34:42 +0800</pubDate>
      <link>https://ruby-china.org/topics/44551</link>
      <guid>https://ruby-china.org/topics/44551</guid>
    </item>
    <item>
      <title>用 kamal 部署 rails 至阿里云主机总结</title>
      <description>&lt;h2 id="需求背景"&gt;需求背景&lt;/h2&gt;
&lt;p&gt;rails 开发速度很快，体验很好，但是如果无法部署到服务器上，最后也就是自己在本机玩玩，开发的网页服务不能给别人使用。
我在 deepseek 的帮助下经过一周的摸索终于实现了部署，特记录供自己后期查阅，也供其他人借鉴。真的感谢好时代，有 deepseek 不然想也不敢去想我一个个人业余开发者，身边没一个同语言开发指导老师怎么敢去想自行摸索完成部署功能。&lt;/p&gt;
&lt;h3 id="让deepseek总结kamal一次完整部署的生命周期"&gt;让 deepseek 总结 kamal 一次完整部署的生命周期&lt;/h3&gt;
&lt;p&gt;下面是 Kamal 从初始化到部署完成的完整工作流程，每一步都对应具体的操作：&lt;/p&gt;
&lt;h3 id="阶段 0：准备工作（只需一次）"&gt;阶段 0：准备工作（只需一次）&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt; &lt;strong&gt;初始化配置&lt;/strong&gt;：运行&amp;nbsp;&lt;code&gt;kamal init&lt;/code&gt;，生成&amp;nbsp;&lt;code&gt;config/deploy.yml&lt;/code&gt;&amp;nbsp;配置文件&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;配置服务器列表&lt;/strong&gt;：在&amp;nbsp;&lt;code&gt;deploy.yml&lt;/code&gt;&amp;nbsp;中指定服务器的 IP 地址或域名&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;配置容器注册表&lt;/strong&gt;：设置注册表的用户名和密码（通过环境变量加密存储）&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;设置 SSH 密钥&lt;/strong&gt;：确保本地可以免密登录服务器&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;编写 Dockerfile&lt;/strong&gt;：Rails 7+ 会自动生成，确保镜像构建正确&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id="阶段 1：初始部署（kamal setup）"&gt;阶段 1：初始部署（&lt;code&gt;kamal setup&lt;/code&gt;）&lt;/h3&gt;
&lt;p&gt;这个命令会完成服务器的&lt;strong&gt;首次配置&lt;/strong&gt;：&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; &lt;strong&gt;SSH 连接并安装 Docker&lt;/strong&gt;：连接到配置文件中的服务器，如果 Docker 未安装，自动通过&amp;nbsp;&lt;code&gt;apt-get&lt;/code&gt;&amp;nbsp;安装&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;登录容器注册表&lt;/strong&gt;：在本地和远程服务器上都登录，以便推送和拉取镜像&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;构建 Docker 镜像&lt;/strong&gt;：使用项目根目录的 Dockerfile 构建镜像&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;推送镜像到注册表&lt;/strong&gt;：将构建好的镜像推送到配置的容器注册表&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;在服务器上拉取镜像&lt;/strong&gt;：通过 SSH 命令让服务器从注册表拉取镜像&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;启动 Kamal Proxy&lt;/strong&gt;：确保代理服务正在运行，并监听 80/443 端口&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;启动应用容器&lt;/strong&gt;：基于拉取的镜像启动新的 Docker 容器&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;健康检查&lt;/strong&gt;：验证应用是否响应&amp;nbsp;&lt;code&gt;GET /up&lt;/code&gt;&amp;nbsp;请求（必须返回 200 OK）&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;清理旧资源&lt;/strong&gt;：删除未使用的镜像和停止的容器，防止磁盘占满&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id="阶段 2：后续部署（kamal deploy）"&gt;阶段 2：后续部署（&lt;code&gt;kamal deploy&lt;/code&gt;）&lt;/h3&gt;
&lt;p&gt;这是日常更新应用的命令，&lt;strong&gt;只执行部署流程，不重复安装 Docker&lt;/strong&gt;：&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; &lt;strong&gt;构建新镜像&lt;/strong&gt;：基于最新的代码构建新版本镜像&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;推送到注册表&lt;/strong&gt;：新镜像被推送到注册表，标签通常是 Git commit hash&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;SSH 连接服务器并拉取新镜像&lt;/strong&gt;：服务器从注册表拉取新版本镜像&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;启动新容器（旧容器仍在运行）&lt;/strong&gt;：在旧容器旁边启动新容器，确保服务不中断&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;健康检查&lt;/strong&gt;：等待新容器通过健康检查（&lt;code&gt;GET /up&lt;/code&gt;）&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Kamal Proxy 切换流量&lt;/strong&gt;：代理原子性地将流量从旧容器切换到新容器&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;停止旧容器&lt;/strong&gt;：新容器稳定运行后，停止并删除旧容器&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;清理资源&lt;/strong&gt;：删除旧镜像和未使用的容器，释放磁盘空间&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id="我理解kamal的功能（没怎么接触过docker）"&gt;我理解 kamal 的功能（没怎么接触过 docker）&lt;/h3&gt;
&lt;p&gt;我理解 kamal 大致功能是先将本机的开发环境和项目通过 docker 打包成镜像，push 到镜像仓库，然后登陆服务器先下载 docker，然后从镜像仓库 pull 这个镜像再还原运行。
之前尝试过在本机和服务机均安装网络代理工具，这一步实现了，但是直接参考 rails guide 部署还是会存在问题，没搞明白。只能尝试将所有可能会用到科学上网的源替换成国内的源。&lt;/p&gt;
&lt;h2 id="部署记录"&gt;部署记录&lt;/h2&gt;&lt;h3 id="step1：准备hello world项目"&gt;step1：准备 hello world 项目&lt;/h3&gt;
&lt;p&gt;新建 rails 项目&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ruby -v
rails -v
rails new rails106
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src="https://l.ruby-china.com/photo/flchenhp/2101356e-4fc7-4d84-83c6-3c1c2f067627.png!large" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;用编辑器（我用的是 trae）打开项目，做初始化保存&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;#iterm
git add .
git commit -m "initial commit"
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;将 gem 源改成 rubychina 的源&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# gemfile
-source "https://rubygems.org"
+source "https://gems.ruby-china.com"
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;添加 hello world 页面&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;#iterm
rails g controller welcome index
&lt;/code&gt;&lt;/pre&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# app/views/welcome/index.html.erb
+&amp;lt;h1&amp;gt;hello world&amp;lt;/h1&amp;gt;
-&amp;lt;h1&amp;gt;Welcome#index&amp;lt;/h1&amp;gt;
-&amp;lt;p&amp;gt;Find me in app/views/welcome/index.html.erb&amp;lt;/p&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# config/routes.rb
  root "welcome#index"
  get "welcome/index"
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;启动项目，查看 hello word 页面&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# iterm
bin/dev
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src="https://l.ruby-china.com/photo/flchenhp/0dc47b93-1671-483c-868c-e4d1ad9baacc.png!large" title="" alt=""&gt;
将代码加入 git 保存&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# iterm
git add .
git commit -m "add hello world page"
&lt;/code&gt;&lt;/pre&gt;&lt;h3 id="step2:选择云主机"&gt;step2:选择云主机&lt;/h3&gt;
&lt;p&gt;阿里云主机可以直接添加 docker，免去安装 docker 的麻烦。直接用 root 账号和设定的自定义密码，这个会用于远程服务器。
&lt;img src="https://l.ruby-china.com/photo/flchenhp/aae8e837-a9bf-4496-a43e-6fefb08ed3a7.png!large" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;远程主机验证 docker 安装成功：&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# 云主机命令行
docker --version
docker run hello-world
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src="https://l.ruby-china.com/photo/flchenhp/8e5a47a9-ec2e-47f4-96d2-3a046b19b798.png!large" title="" alt=""&gt;&lt;/p&gt;
&lt;h3 id="step3:申请阿里云容器镜像服务（ACR）"&gt;step3:申请阿里云容器镜像服务（ACR）&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt; &lt;strong&gt;进入控制台&lt;/strong&gt;：登录后搜索“&lt;strong&gt;容器镜像服务&lt;/strong&gt;”&lt;a href="https://www.e-com-net.com/article/1683693229553299456.htm" rel="nofollow" target="_blank" title=""&gt;&lt;/a&gt;，或直接访问&amp;nbsp;&lt;a href="https://cr.console.aliyun.com/" rel="nofollow" target="_blank" title=""&gt;容器镜像服务控制台&lt;/a&gt;。&lt;/li&gt;
&lt;li&gt;按提示创建个人版实例&lt;strong&gt;（下面的信息都很重要，部署的配置文件要用）&lt;/strong&gt;
&lt;img src="https://l.ruby-china.com/photo/flchenhp/3889a199-e266-45ce-a281-878eb3537139.png!large" title="" alt=""&gt;
&lt;/li&gt;
&lt;li&gt;按提升建立命名空间
&lt;img src="https://l.ruby-china.com/photo/flchenhp/236e9c62-b255-4d3b-a6af-f3676517a301.png!large" title="" alt=""&gt;
&lt;/li&gt;
&lt;li&gt;按提示设置密码，获取登录命令
&lt;img src="https://l.ruby-china.com/photo/flchenhp/49a00360-015c-4c96-9667-0375a6c79043.png!large" title="" alt=""&gt;
&lt;/li&gt;
&lt;li&gt;获取个人加速器地址
&lt;img src="https://l.ruby-china.com/photo/flchenhp/3eab9292-3ec3-4822-a38c-3233105c35ee.png!large" title="" alt=""&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id="step4:在本机下载docker"&gt;step4:在本机下载 docker&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;访问&amp;nbsp;&lt;a href="https://docs.docker.com/desktop/install/mac-install/" rel="nofollow" target="_blank" title=""&gt;Docker Desktop for Mac 下载页面&lt;/a&gt;选择适宜自己电脑版本下载安装&lt;/li&gt;
&lt;li&gt;点击 Mac 顶部菜单栏的 Docker 图标，选择 Settings。在左侧菜单选择 Docker Engine，将阿里云和一些其他的加速器地址填上
&lt;code&gt;
{
"builder": {
"gc": {
  "defaultKeepStorage": "20GB",
  "enabled": true
}
},
"experimental": false,
"registry-mirrors": [
"https://5ex1e6il.mirror.aliyuncs.com",
"https://docker.1panel.live",
"https://hub.rat.dev",
"https://docker.m.daocloud.io",
"https://docker.nju.edu.cn",
"https://docker.xuanyuan.me",
"https://docker.1ms.run"
]
}
&lt;/code&gt;
&lt;img src="https://l.ruby-china.com/photo/flchenhp/143e0e53-27b7-4d20-9a75-99b21880a364.png!large" title="" alt=""&gt;
&lt;/li&gt;
&lt;li&gt;打开“终端”应用，运行&lt;code&gt;docker --version&lt;/code&gt; 和&lt;code&gt;docker run hello-world&lt;/code&gt; 进行验证 docker 是否正确安装&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id="step5:进行部署配置"&gt;step5:进行部署配置&lt;/h3&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;#rails106/config/deploy.yml
-image: rails106
+image: myrailsapp/rails106 # myrailsapp是阿里云的镜像仓库命名空间

-    - 192.168.0.1
+    - 47.x x.xx.236 # 这是阿里云的IP地址

-  server: localhost:5555
+  # 这里填阿里云的镜像仓库的公网地址
+  server: crpi-xxxxxxxxxxx.cn-zhangjiakou.personal.cr.aliyuncs.com 

-  # username: your-user
+  # 这里填阿里云的账号
+  username: chenxxxxxx21

-  # password:
+   # 这里填阿里云的镜像的固定秘密
+  password: cxxxxxx9
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;代码类似
&lt;img src="https://l.ruby-china.com/photo/flchenhp/c3815648-e03b-41ce-a17e-a0b4c7dfe760.png!large" title="" alt=""&gt;&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# rails106/Dockerfile
-# syntax=docker/dockerfile:1
+# #syntax=docker/dockerfile:1 # 第一行这里要再加个注释符号注释，不然会报错，不知道为什么

-FROM docker.io/library/ruby:$RUBY_VERSION-slim AS base
+# FROM docker.io/library/ruby:$RUBY_VERSION-slim AS base
+# 要换成ruby的国内的源，不然会卡住
+FROM docker.m.daocloud.io/library/ruby:$RUBY_VERSION-slim AS base

WORKDIR /rails
+# 设定 apt 源为阿里云镜像，不然会卡住
+RUN sed -i 's/deb.debian.org/mirrors.aliyun.com/g' /etc/apt/sources.list.d/debian.sources &amp;amp;&amp;amp; \
+    sed -i 's/security.debian.org/mirrors.aliyun.com/g' /etc/apt/sources.list.d/debian.sources
# Install base packages
RUN apt-get update -qq &amp;amp;&amp;amp; \
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;代码类似
&lt;img src="https://l.ruby-china.com/photo/flchenhp/597ab9ec-9d1c-45db-a845-e7e0b95d4d35.png!large" title="" alt=""&gt;
加入 git 仓库&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# iterm
git add .
git commit -m "add deploy set"
&lt;/code&gt;&lt;/pre&gt;&lt;h3 id="step6:打部署命令开始部署"&gt;step6:打部署命令开始部署&lt;/h3&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# iterm
bin/kamal setup
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;部署成功：
&lt;img src="https://l.ruby-china.com/photo/flchenhp/fac0b187-0b66-4db4-832d-5ec150a2f4b8.png!large" title="" alt=""&gt;
浏览器输入 ip 测试
&lt;img src="https://l.ruby-china.com/photo/flchenhp/9800797d-1852-4de5-aff6-7eae3416962d.png!large" title="" alt=""&gt;
大功告成！！！&lt;/p&gt;</description>
      <author>flchenhp</author>
      <pubDate>Mon, 13 Apr 2026 01:25:32 +0800</pubDate>
      <link>https://ruby-china.org/topics/44550</link>
      <guid>https://ruby-china.org/topics/44550</guid>
    </item>
    <item>
      <title>gems.ruby-china.com 缺少 bigdecimal 4.1.1 版本</title>
      <description>&lt;p&gt;在使用 source "&lt;a href="https://gems.ruby-china.com" rel="nofollow" target="_blank"&gt;https://gems.ruby-china.com&lt;/a&gt;" 作为 Bundler 源时，发现 bigdecimal (4.1.1) 无法获取。&lt;/p&gt;

&lt;p&gt;具体情况如下：&lt;/p&gt;

&lt;p&gt;在升级 Rails 从 8.1.1 到 8.1.2 过程中，依赖解析需要使用 bigdecimal (4.1.1)
该版本在 &lt;a href="https://rubygems.org" rel="nofollow" target="_blank"&gt;https://rubygems.org&lt;/a&gt; 上是存在的，并且可以正常安装
但在 gems.ruby-china.com 镜像源中无法获取，导致 bundle install 失败，进而影响部署&lt;/p&gt;

&lt;p&gt;报错类似：&lt;/p&gt;

&lt;p&gt;Your bundle is locked to bigdecimal (4.1.1) ... but that version can no longer be found in that source.&lt;/p&gt;

&lt;p&gt;请问是否可以帮忙同步或刷新该版本的索引？&lt;/p&gt;

&lt;p&gt;感谢支持！&lt;/p&gt;</description>
      <author>shin</author>
      <pubDate>Sun, 05 Apr 2026 19:43:08 +0800</pubDate>
      <link>https://ruby-china.org/topics/44539</link>
      <guid>https://ruby-china.org/topics/44539</guid>
    </item>
    <item>
      <title>坐班宁波，集中办公，提供食宿，招聘测试组长、钱包产品经理、风控产品经理、UI/UX 设计师、Pay 大客户运营</title>
      <description>&lt;h2 id="项目介绍："&gt;项目介绍：&lt;/h2&gt;
&lt;p&gt;我们是一家注册在马来西亚的公司，主要开发 CEX（0-1），目前团队正处于 0-1 组建阶段。&lt;/p&gt;
&lt;h2 id="工作方式："&gt;工作方式：&lt;/h2&gt;
&lt;p&gt;集中办公，坐班宁波&lt;/p&gt;
&lt;h2 id="工作时间："&gt;工作时间：&lt;/h2&gt;
&lt;p&gt;单双休、九九六&lt;/p&gt;
&lt;h2 id="薪资发放形式："&gt;薪资发放形式：&lt;/h2&gt;
&lt;p&gt;RMB，每月 10 号发放&lt;/p&gt;
&lt;h2 id="其他福利："&gt;其他福利：&lt;/h2&gt;
&lt;p&gt;提供食宿&lt;/p&gt;
&lt;h2 id="测试组组长"&gt;测试组组长&lt;/h2&gt;
&lt;p&gt;岗位职责：
1、参与需求评审，编写测试用例、测试计划，执行功能测试、回归测试、兼容性测试等，确保测试覆盖率和质量
2、提交、跟踪、验证缺陷，与开发团队协作推动问题解决
3、参与或主导自动化测试脚本编写、维护，提升测试效率
4、参与性能测试、安全测试等专项测试工作
5、编写测试报告，输出测试结果，为产品发布提供决策依据
6、参与测试流程优化，推动测试左移和持续集成实践
7、测试团队管理：团队搭建、绩效管理等
8、测试流程搭建等
岗位要求：
1、5 年以上工作经验
2、精通软件测试理论、方法和流程，熟练掌握至少一种主流编程语言（如 Java/Python）。在自动化测试、性能测试、安全测试或白盒测试等某一专业领域有深厚积累
3、熟悉敏捷开发、DevOps 流程，对 CI/CD 有深入理解。具备大型复杂系统（如微服务架构、云原生应用）的测试规划和实施经验
4、对新技术保持敏感，能主动引入创新测试方法和工具（如 AI 在测试中的应用）以提升效能
5、拥有跨部门沟通和协调能力，能有效解决冲突，推动复杂问题解决
加分项：
1、能 base 宁波
2、有理财、支付测试经验优先
薪资待遇：
25K-35KRMB&lt;/p&gt;
&lt;h2 id="钱包产品经理"&gt;钱包产品经理&lt;/h2&gt;
&lt;p&gt;岗位核心目标：
设计安全、合规、高性能的数字资产钱包系统，支持上层业务需求。
详细职责：
架构设计：负责钱包产品，深度调研 Fireblocks、Cobo 方案。
资产管理：设计并优化资产充值、提现、内部调拨流程；负责多链 Gas Fee 策略优化及归集逻辑。
安全合规：设计提现审核风控引擎，确保存储与传输层符合安全合规标准。
对账系统：构建链上数据与平台内部数据库的实时、准实时对账系统，确保账实相符。
任职资格：
3 年以上加密货币钱包产品经验，熟悉 MPC (Multi-Party Computation) 或 TSS 技术原理。
精通主流公链（BTC, ETH, Solana, Layer2s）的转账机制、签名算法及链上节点交互。
有处理高并发资金流水对账、资产清算的实战经验。
加分项：
熟悉 Account Abstraction (EIP-4337) 协议及智能合约钱包。
有通过 SOC2 或同级别安全审计的项目经验。
薪资待遇：
25K-35KRMB&lt;/p&gt;
&lt;h2 id="风控产品经理"&gt;风控产品经理&lt;/h2&gt;
&lt;p&gt;岗位核心目标：
构建多维度的风险防御矩阵，识别并拦截链上安全威胁、业务欺诈及 DeFi 流动性风险。
详细职责：
链上风控：能够利用 Chainalysis/Elliptic 等工具监控黑地址流入，设计链上充提币风险评分模型。
DeFi 风险管控：分析 DeFi 理财产品的底层协议安全（如 Flash Loan 攻击、清算风险），建立实时预警机制。
反欺诈/反洗钱：负责 KYC/KYB 流程中的风险识别，设计异常登录、薅羊毛、对倒交易拦截策略。
处置引擎：建立风险标签体系，设计自动冻结、人工审核、风险降级等分级处置流程。
任职资格：
熟悉 AML/CFT 合规要求，对加密货币洗钱套路有深度研究。
理解 DeFi 协议架构（Uniswap, Aave 等）及其经济模型，能识别智能合约逻辑风险。
具备较强的数据建模能力，能协同算法工程师优化风控模型。
加分项：
具备网络安全、风控背景，熟悉常见的链上攻击和风控手段。
薪资待遇：
25K-35k RMB&lt;/p&gt;
&lt;h2 id="UI"&gt;UI&lt;/h2&gt;
&lt;p&gt;岗位核心目标：通过极致的交互与视觉细节，降低 Web3 产品的用户认知门槛，打造丝滑的交易体验。
详细职责：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;设计系统：构建并维护可跨平台（iOS/Android/Web）复用的 Design System，确保全局体验一致性。&lt;/li&gt;
&lt;li&gt;用户路径优化：针对 KYC、资产划转、DeFi 认购等复杂流程进行深度交互优化，减少漏斗流失。&lt;/li&gt;
&lt;li&gt;本地化设计：针对不同语种（RTL 阿拉伯语等）及区域习惯进行 UI 适配与体验调优。
任职资格：&lt;/li&gt;
&lt;li&gt;3 年以上移动端 UI 设计经验，有成功上线并面向海外市场的 FinTech 产品案例。&lt;/li&gt;
&lt;li&gt;深刻理解 Atomic Design 设计模式，追求像素级的视觉精准度。&lt;/li&gt;
&lt;li&gt;熟悉 Web3 特有的交互痛点（如私钥备份、Gas 确认、签名请求）并能提供优雅的 UI 方案。
加分项：&lt;/li&gt;
&lt;li&gt;具备基础的 Lottie 动效制作能力，提升产品交互反馈质感。&lt;/li&gt;
&lt;li&gt;熟悉 HTML/CSS 原理，能与前端开发无缝对接。
薪资待遇：
25K-35KRMB&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="pay大客户运营"&gt;pay 大客户运营&lt;/h2&gt;
&lt;p&gt;岗位职责：
1、负责社交金融平台的大客户（机构投资者、高净值用户、项目方、KOL 等）的识别、拓展和关系维护
2、建立大客户档案和分级管理体系，制定差异化的客户服务策略
3、定期进行客户拜访、需求调研和满意度回访，建立长期稳定的客户关系
4、深入理解大客户业务需求，挖掘客户潜在价值，推动客户业务规模增长
5、负责大客户的续约、增购、交叉销售等业务拓展，提升客户 LTV（生命周期价值）
6、制定大客户业务增长策略，对客户 ARPU 值、续约率等核心指标负责
7、作为大客户的主要联系人，提供 7×24 小时响应服务，及时解决客户问题
8、协调内部资源（产品、技术、风控等）解决大客户的复杂需求和技术问题
9、建立大客户数据监控体系，追踪客户健康度、活跃度、业务贡献等指标
岗位要求：
1、必须有 3 年以上海外运营经验，有 1 年以上跨境支付经验优先
2、对跨境&amp;amp;社交金融有深刻理解，具备独立思考能力
3、基于数据研究和整张需求，完善目标业务的用户生命周期管理
4、洞察 VIP/大客户痛点和需求，完善平台大客户服务体系，提升满意度和忠诚度
5、与产品、运营、技术等团队合作，整合资源，实现业务持续增长
薪资待遇：
35K-50K&lt;/p&gt;
&lt;h2 id="投递邮箱：camellia@we-boss.com"&gt;投递邮箱：camellia@we-boss.com&lt;/h2&gt;</description>
      <author>Camellia</author>
      <pubDate>Thu, 02 Apr 2026 16:01:41 +0800</pubDate>
      <link>https://ruby-china.org/topics/44538</link>
      <guid>https://ruby-china.org/topics/44538</guid>
    </item>
    <item>
      <title>Base 深圳、自动驾驶 AI 应用公司 ruby 后端工程师 20-30K/月</title>
      <description>&lt;h3 id="任职要求：全日制统招计算机科学/相关专业本科及以上学历，5年以上Ruby on Rails全栈加AI应用开发经验（联系微信：13822214255）"&gt;任职要求：全日制统招计算机科学/相关专业本科及以上学历，5 年以上 Ruby on Rails 全栈加 AI 应用开发经验（联系微信：13822214255）&lt;/h3&gt;
&lt;p&gt;2、丰富的单元测试经验，注重 TDD（测试驱动开发）\熟练运用 Rails 7 框架； &lt;/p&gt;

&lt;p&gt;3、具备 BDD 经验，推动团队采用测试驱动的开发方法；熟练掌握数据库设计和优化；&lt;/p&gt;

&lt;p&gt;4、熟悉 SaaS 产品架构设计，有丰富的分布式系统设计、高并发、高可用性经验；&lt;/p&gt;

&lt;p&gt;5、熟悉 Ruby on Rails 框架，了解其原理和机制，有丰富的 Rails 项目实战经验；&lt;/p&gt;

&lt;p&gt;6、熟悉 AI 大模型应用技术，如 TensorFlow、PyTorch 等，有实际项目应用经验；&lt;/p&gt;

&lt;p&gt;7、熟悉 PostgreSQL、Redis、MongoDB 等数据库，了解数据库优化技巧；&lt;/p&gt;

&lt;p&gt;8、熟悉 Linux 操作系统，熟悉常用命令和脚本编程；&lt;/p&gt;

&lt;p&gt;9、具备良好的团队协作和沟通能力，能承担一定的技术领导责任；&lt;/p&gt;

&lt;p&gt;10、有成功带领团队开发大型项目的经验，有带团队解决复杂技术问题的经验。&lt;/p&gt;
&lt;h2 id="岗位职责："&gt;岗位职责：&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;负责 SaaS 产品的整体架构设计、技术选型与技术创新/优化&lt;/li&gt;
&lt;li&gt;带领团队进行系统开发、优化及维护，确保系统稳定、高效、可扩展；&lt;/li&gt;
&lt;li&gt;负责制定并推行技术规范和开发流程，提升团队开发效率和代码质量；&lt;/li&gt;
&lt;li&gt;负责技术难题的攻关，解决系统性能瓶颈，提高系统资源利用率；&lt;/li&gt;
&lt;li&gt;参与公司战略规划，推动新技术的研究与实践，提升产品竞争力；&lt;/li&gt;
&lt;li&gt;领导团队进行 AI 大模型业务场景应用的研发，探索业务场景的创新应用，提供技术方案和实施指导。
我们提供：&lt;/li&gt;
&lt;li&gt;具有竞争力的薪资待遇，股权激励；&lt;/li&gt;
&lt;li&gt;宽广的个人发展空间和职业晋升机会；&lt;/li&gt;
&lt;li&gt;舒适的工作环境和良好的团队氛围；&lt;/li&gt;
&lt;li&gt;完善的福利待遇，如五险一金、带薪年假、员工体检等。&lt;/li&gt;
&lt;/ol&gt;</description>
      <author>jeffaima</author>
      <pubDate>Thu, 02 Apr 2026 13:41:16 +0800</pubDate>
      <link>https://ruby-china.org/topics/44537</link>
      <guid>https://ruby-china.org/topics/44537</guid>
    </item>
  </channel>
</rss>
