<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="4.4.1">Jekyll</generator><link href="https://st0012.dev/feed.xml" rel="self" type="application/atom+xml" /><link href="https://st0012.dev/" rel="alternate" type="text/html" /><updated>2026-04-05T11:05:51+00:00</updated><id>https://st0012.dev/feed.xml</id><title type="html">st0012.dev</title><subtitle>Ruby committer. Building developer tooling infrastructure. Exploring how AI changes open source.</subtitle><author><name>Stan Lo</name></author><entry xml:lang="zh-TW"><title type="html">用開源專案建立職涯：從 Ruby 使用者到維護者的經驗分享</title><link href="https://st0012.dev/zh-tw/building-a-career-with-open-source/" rel="alternate" type="text/html" title="用開源專案建立職涯：從 Ruby 使用者到維護者的經驗分享" /><published>2026-03-28T00:00:00+00:00</published><updated>2026-03-28T00:00:00+00:00</updated><id>https://st0012.dev/zh-tw/building-a-career-with-open-source</id><content type="html" xml:base="https://st0012.dev/zh-tw/building-a-career-with-open-source/"><![CDATA[<p>我是 Stan，過去幾年貢獻跟維護一些 <a href="https://www.ruby-lang.org/">Ruby</a> 的官方專案，像是 <a href="https://github.com/ruby/irb">IRB</a>（官方 <a href="https://en.wikipedia.org/wiki/Read%E2%80%93eval%E2%80%93print_loop">REPL</a>）、<a href="https://github.com/ruby/rdoc">RDoc</a>（官方文件產生器）、<a href="https://github.com/ruby/debug">debug.gem</a>（官方 debugger），以及 <a href="https://docs.ruby-lang.org/en/master/jit/zjit_md.html">ZJIT</a>（Ruby 的新 <a href="https://en.wikipedia.org/wiki/Just-in-time_compilation">JIT 編譯器</a>）等等。去年底很幸運地透過累積的貢獻成為了 Ruby 的 committer，以及獲得 2025 年的 RubyPrize。</p>

<p>我自己沒有資工背景（大學念公共行政），也沒有出國留學過，一路上是受到很多人的幫助才能走到這裡。作為台灣人想透過開源專案建立職涯，真的不是件容易的事。語言、時區、文化差異都是挑戰。但如果我可以走到這裡，或許也能給其他人一些經驗參考。</p>

<p>這篇文章整理了我這幾年的心得，大部分內容來自我和 <a href="https://x.com/rubytaiwan">Ruby Taiwan</a> 社群的一場 AMA，所以內容會比較鬆散一點。</p>

<hr />

<h2 id="我的背景">我的背景</h2>

<p>2013 年在大學開始自學寫程式，畢業前就陸續在 flyingV、愛料理、貝殼放大實習跟工作。2017 年從台灣找到一份英國公司的遠端工作，做了四年後搬到英國，半年後加入 Shopify 的 Ruby Developer Experience team，一直到現在。</p>

<p>我的工作內容是改善 Ruby 開發者的體驗 —— 開發維護 <a href="https://github.com/Shopify/ruby-lsp">Ruby LSP</a>、type checker、debugger、還有各種開發工具。</p>

<p>認識開源社群是在愛料理工作的時候。那時開始用 <a href="https://rubyonrails.org/">Ruby on Rails</a>，偶爾會去看 gem 的 source code，意識到自己「好像可以貢獻些什麼」，並在當時 CTO Richard 的鼓勵下開始嘗試做些小的貢獻。</p>

<p>而我比較積極參與 Ruby 社群，則是在 2015 年夏天去了新加坡的 <a href="https://www.rubyevents.org/events/series/red-dot-ruby-conference">RedDot RubyConf</a>，年底又去了第一次的 <a href="https://rubykaigi.org/">RubyKaigi</a> 之後。那時在會場看到差不多年紀的工程師說「我維護這個專案」、「我是 Rails 前 100 個貢獻者」，覺得靠寫程式跟開源專案就能對一個社群有影響力，是很酷的事情。</p>

<hr />

<h2 id="給想開始認真貢獻的人">給想開始認真貢獻的人</h2>

<p>幾個實際的建議：</p>

<h3 id="選跟工作相關的專案">選跟工作相關的專案</h3>

<p>這代表你已經知道一部分的 context，不用額外花時間建環境。你甚至可以跟公司討論：讓我花一部分上班時間修這個 issue、upstream 到原本的 gem，會比在專案裡加 patch 更好維護。這件事情我即便到現在在 Shopify 都還很常做。</p>

<h3 id="用-ai-幫助你了解-codebase">用 AI 幫助你了解 codebase</h3>

<p>去年我開始貢獻 ZJIT，一個用 <a href="https://www.rust-lang.org/">Rust</a> 寫的 JIT compiler。我之前沒做過任何 JIT compiler、沒貢獻過 Ruby 本身、也沒寫過 Rust。
但靠 AI 幫我快速了解 codebase，大幅降低了語言不熟的門檻，讓我能夠專心學習 JIT 相關的知識跟快速跟上開發。</p>

<p>以前這些全部都要自己慢慢摸索，現在用 AI 可能半天到一天就能知道專案的架構、每個部分在做什麼、測試怎麼寫。</p>

<h3 id="從小地方開始貢獻">從小地方開始貢獻</h3>

<p>如果你找了一個專案但不知道從何開始貢獻，這些會是我的建議：</p>

<ul>
  <li>改善測試 —— 例如把重複的 setup 抽出來變成 helper method、重寫難懂的測試等等。測試不會影響使用者，maintainer 更容易 merge。</li>
  <li>找 TODO comment —— 那些放在那邊很久沒人修的東西。</li>
  <li>消掉 deprecation warning —— 讓畫面看起來乾淨。</li>
</ul>

<p>這些小事可以讓你增加對 codebase 的了解，同時建立和 maintainer 之間的信任。</p>

<p>現在有了 AI，偵測這些東西變得容易許多。<strong>但是切記自己要驗證、review 過再開 PR</strong>。如果維護者覺得你發 AI slop，沒人會想認真 review 跟接受你的 PR。</p>

<p>反過來說，有些東西不太適合拿來當第一次的貢獻：</p>

<ul>
  <li>變更 dependency —— 一個專案的 dependency 對開發流程跟使用者都有很大的影響，在不熟悉專案前很容易做出不正確的判斷。</li>
  <li>變更 linting rules —— 每個團隊有自己的開發文化跟偏好，在不熟前不適合介入。</li>
</ul>

<h3 id="投資-40-個小時在同一個專案上">投資 40 個小時在同一個專案上</h3>

<p>大概要花到這個時間，你才能真的說你了解這個專案的一部分，包括開發慣例跟流程。走到這個階段，你貢獻的東西會比較能放上履歷，學到的東西也會比較多。</p>

<p>在你貢獻滿一定時數之後，你很可能會發現：</p>

<ul>
  <li>相關的專案好像也有類似的問題，可以順便貢獻</li>
  <li>相關經驗可以寫成一篇文章</li>
  <li>維護者在找人幫忙維護，你是少數有足夠貢獻量的人選</li>
</ul>

<p>（40 小時是個人經驗，實際數字會因人而異）</p>

<h3 id="用公開的工具問問題">用公開的工具問問題</h3>

<p>用 GitHub issue 而不是 email。你問的問題別人可能也會有，公開之後 maintainer 只要回答一次，之後有人再問就指向這個 issue。</p>

<p>不用擔心問問題會麻煩 maintainer。任何人變成 maintainer 都知道回答問題是維護的一部分。只要有人願意參與，我們都看得到，這些都是好的。</p>

<p>不過有時候你問了問題或開了 PR，maintainer 很久沒回，不代表他沒看到 —— 很多時候是他知道這很重要，但需要花時間思考，或是有更緊急的事要先處理。大部分專案等一兩週才有回覆是很正常的，避免過兩三天就留言追問。</p>

<hr />

<h2 id="短期專注長期開放">短期專注，長期開放</h2>

<p>如果你已經有貢獻經驗，而且考慮把開源專案變成你職涯的重心，我會建議：<strong>短期（1-2 年）專注一個領域，但長期保持開放，不要死守單一專案</strong>。</p>

<p>舉例來說，過去五年我的貢獻路徑是這樣：debug.gem → Ruby LSP → IRB → RDoc → ZJIT。</p>

<p>一開始貢獻 debug.gem 時，花了超過 200 個 PR 成為第二大 contributor。後來因為工作接觸 Ruby LSP，開始了解 <a href="https://microsoft.github.io/language-server-protocol/">LSP</a> server 跟編輯器。接觸 IRB 後又慢慢從成為 maintainer 到變成第一貢獻者。接著是 RDoc，從 2024 年到現在已經兩度幫它換皮，現在則是重寫並增加它的功能。去年中也密集貢獻了 Ruby 的下一代 JIT compiler（ZJIT），學習了不少程式語言底層的實作跟優化。</p>

<p>如果剛要開始貢獻的話，目標最好是平常會用的東西，這樣學習的成本會小很多。</p>

<p>像我 2019 年貢獻最多 Rails，那時候還在做 Rails 開發，可以在自己公司的專案裡實驗 patch，確認可以動再送上去。2022 年加入 Shopify 做 Ruby DX 之後，碰不到 Rails 了，自然就轉向貢獻 Ruby 本身和周邊工具。</p>

<p>但過一段時間上手後，也應該試著拓展貢獻的領域。原因有兩個：</p>

<p>第一，如果你的專長太局限，會很難把它跟公司變動的需求做連結，導致你能發揮的場域受限。第二，當你有意識地拓展接觸的專案類型，會幫助你從不同的專案汲取靈感。像我是從 debugger 到 LSP server 再到 REPL，慢慢地從一個單一工具的專家，變成大部分開發者工具都是我熟悉的領域，甚至進一步整合不同的專案。</p>

<p>最近的例子是 AI 開發工具成為主流後，我也開始嘗試開發相關的工具，例如 <a href="https://github.com/st0012/cctop">cctop</a>、<a href="https://github.com/st0012/ruby-skills">ruby-skills</a>、<a href="https://github.com/Shopify/rubydex?tab=readme-ov-file#mcp-server-experimental">rubydex 的 MCP server</a> 等等。</p>

<hr />

<h2 id="光寫-code-不夠">光寫 code 不夠</h2>

<p>這點很重要：<strong>光寫 code 其實不夠，還需要透過部落格、活動、演講讓別人看見你的貢獻</strong>。</p>

<p>開源社群裡很重要的一點是信任。你要變成 maintainer、要得到別人的幫助，這些全部都是基於「我信任你這個人」。如果你平常不講話，沒有人知道你是誰，信任就會來得比較慢。</p>

<p>我做了幾件事讓社群認識我：</p>

<h3 id="去-conference">去 Conference</h3>

<p>從 2015 年開始，除了疫情那三年，我每年都去 RubyKaigi。不管公司有沒有贊助，我都會自己買機票。每一天都一定會去一個活動 —— 不管是喝酒的還是 coding party。</p>

<p>RubyKaigi 的 coding party 很多 Ruby 和 Rails maintainer 都會去。你可以坐在他們旁邊，跟他們一起討論、一起修 PR。我之前就有一次在 coding party 開了好幾個 Rails PR，maintainer 直接就 merge 了，因為就坐在旁邊嘛。</p>

<p>我第一年去 RubyKaigi 不敢跟 Matz（Ruby 發明者）講話，第二年開始跟他打招呼，之後每年都去 say hi。在 RubyKaigi 見一次、在台灣 <a href="https://www.rubyevents.org/events/series/rubyconf-taiwan">RubyConfTW</a> 再見一次、在新加坡 RedDot RubyConf 再見一次。慢慢地他也認識我了。</p>

<p>我當初會加入 Shopify，其實也是因為 2021 年在線上的 <a href="https://euruko.org/">Euruko</a> 認識了現在的同事，幾個月後找工作時靠他內推才拿到面試機會。</p>

<h3 id="寫部落格">寫部落格</h3>

<p>你可以寫公司裡做了什麼、用了什麼 gem、今天貢獻了什麼 PR。不用很長，分享你學到的東西就好。我建議用英文，因為理論上越多人看到越好。你都已經花時間了，用 AI 把它翻譯成英文，讓更多人看到。</p>

<p>寫完之後分享到 Reddit、X 或其他地方，慢慢就會有人知道你的 ID、知道你是誰。</p>

<h3 id="上台演講">上台演講</h3>

<p>這可能是壓力最大的，但也是最有效的。</p>

<p>我第一次上台演講就是在 RubyKaigi 講我自己做的很爛的程式語言。多虧了慕凡跟龍哥給我很多幫助和鼓勵。講了第一次之後，就慢慢知道自己做得到這件事。</p>

<p>當公司在看履歷的時候，他們可能沒辦法點進去看你 5 個 Rails PR 的討論串。但如果你跟他說你在 RubyConf Taiwan 講過 30 分鐘，他可以快速滑一下你的影片、點過你的 slides。用這個方式來證明你的能力很有效。至少我會對你的溝通能力有一定的信心，因為你敢站上台分享你學的東西。</p>

<hr />

<h2 id="不要因為英文退縮">不要因為英文退縮</h2>

<p>很多人問我：英文不好怎麼辦？</p>

<p>我英文一開始也不好。為了貢獻開源專案、參與社群、到國外找工作，額外花了很多時間練英文。我現在還找得到我第一個 PR，描述裡兩句話就有三個文法錯誤。</p>

<p>但那完全沒關係。</p>

<p>社群裡很多 maintainer 來自西班牙、法國、非洲，英文也都不是他們的母語，寫英文的時候常常會有文法錯誤。台灣人並沒有比較劣勢。</p>

<p>比較劣勢的是：因為覺得自己英文不好，所以就不敢講話。</p>

<p>英文不好不是問題，但不要因為英文不好讓你不敢講話、不敢上台、不敢在 issue 上面回覆訊息。如果真的因為英文不好而不敢開口，那就投資時間改善它 —— 不是為了達到別人的標準，而是為了說服你自己：我英文夠好，我敢講話。</p>

<p>台灣的教育讓我們讀寫應該都沒問題，其實只差嘴巴講出來的自信心跟流暢度。每天唸英文新聞、讓嘴巴肌肉習慣那個發音，三個月你就會發現差很多。</p>

<hr />

<h2 id="這需要很大的前期投入">這需要很大的前期投入</h2>

<p>我每年大概花 500 到 1000 個小時的個人時間在各種開源專案上面。</p>

<p>這個職涯對我來說很有用，但我其實不推薦給大部分的人。因為它的背後是這麼大量的時間投入，而且不保證有回報。你可能花一兩百個小時刷 LeetCode，就能拿到同等級的 offer。</p>

<p>我一開始貢獻開源專案，其實是想要展現我寫程式的能力。我是非本科出身、沒上過任何課、完全沒有相關背景。我不想用 LeetCode、不想刷演算法（我刷演算法超爛）。我想用公開的成果，讓別人看我花 8 個小時寫的 code，而不是用我 30 分鐘壓力下寫出來的 LeetCode 來判斷我的能力。</p>

<p>後來我的職涯也確實都是這樣。從第一份實習之後，我幾乎都不用投履歷，都是靠別人的 refer。即便到 Shopify，也是別人介紹、用開源專案來面試。</p>

<p>但如果你對這件事情沒有熱忱，就不值得花這個時間。最好是因為真的喜歡才做。</p>

<hr />

<h2 id="最後">最後</h2>

<p>用開源專案建立職涯，對我來說是一條很值得的路。但它需要很大的前期投入、需要你願意曝光自己、需要跨過語言和文化的障礙。而且不保證有回報。</p>

<p>如果你正在想這條路適不適合自己，希望這篇文章能提供一些參考。</p>]]></content><author><name>Stan Lo</name></author><summary type="html"><![CDATA[我是 Stan，過去幾年貢獻跟維護一些 Ruby 的官方專案，像是 IRB（官方 REPL）、RDoc（官方文件產生器）、debug.gem（官方 debugger），以及 ZJIT（Ruby 的新 JIT 編譯器）等等。去年底很幸運地透過累積的貢獻成為了 Ruby 的 committer，以及獲得 2025 年的 RubyPrize。]]></summary></entry><entry><title type="html">Ruby Skills: Teaching Claude Code About Ruby’s Tooling And Ecosystem</title><link href="https://st0012.dev/2026/01/24/ruby-skills-teaching-claude-code-about-ruby-tooling-and-ecosystem/" rel="alternate" type="text/html" title="Ruby Skills: Teaching Claude Code About Ruby’s Tooling And Ecosystem" /><published>2026-01-24T00:00:00+00:00</published><updated>2026-01-24T00:00:00+00:00</updated><id>https://st0012.dev/2026/01/24/ruby-skills-teaching-claude-code-about-ruby-tooling-and-ecosystem</id><content type="html" xml:base="https://st0012.dev/2026/01/24/ruby-skills-teaching-claude-code-about-ruby-tooling-and-ecosystem/"><![CDATA[<p>Ruby developers using Claude Code hit this pattern: you ask it to run tests, it picks the wrong Ruby version, fails, tries <code class="language-plaintext highlighter-rouge">bundle install</code>, fails again.</p>

<p><img src="/assets/images/posts/ruby-skills-wrong-version-error.png" alt="Claude Code picks the wrong Ruby version, causing missing dependencies and failed commands" /></p>

<p>Eventually you add something like <code class="language-plaintext highlighter-rouge">Use chruby 4.0.0 to run Ruby commands</code> in either the project or your user-level CLAUDE.md.</p>

<p>While this is frustrating, I don’t blame Claude. Ruby has at least seven version managers - <code class="language-plaintext highlighter-rouge">rbenv</code>, <code class="language-plaintext highlighter-rouge">chruby</code>, <code class="language-plaintext highlighter-rouge">rvm</code>, <code class="language-plaintext highlighter-rouge">asdf</code>, <code class="language-plaintext highlighter-rouge">mise</code>, <code class="language-plaintext highlighter-rouge">rv</code>, <code class="language-plaintext highlighter-rouge">shadowenv</code> - and Claude doesn’t know which one you’re using.</p>

<p>This is an example of a bigger question: how can a community teach AI to handle its ecosystem’s quirks? Or how does it know about areas that are still rapidly developing, like the typing and tooling scene in Ruby?</p>

<p>So I built <a href="https://github.com/st0012/ruby-skills">ruby-skills</a> to solve the version manager problem and experiment with community-maintained guardrails for Claude Code.</p>

<div class="gh-repo-embed">
  <div class="gh-repo-header">
    <svg class="gh-repo-icon" viewBox="0 0 16 16" fill="currentColor">
      <path d="M2 2.5A2.5 2.5 0 0 1 4.5 0h8.75a.75.75 0 0 1 .75.75v12.5a.75.75 0 0 1-.75.75h-2.5a.75.75 0 0 1 0-1.5h1.75v-2h-8a1 1 0 0 0-.714 1.7.75.75 0 1 1-1.072 1.05A2.495 2.495 0 0 1 2 11.5Zm10.5-1h-8a1 1 0 0 0-1 1v6.708A2.486 2.486 0 0 1 4.5 9h8ZM5 12.25a.25.25 0 0 1 .25-.25h3.5a.25.25 0 0 1 .25.25v3.25a.25.25 0 0 1-.4.2l-1.45-1.087a.249.249 0 0 0-.3 0L5.4 15.7a.25.25 0 0 1-.4-.2Z" />
    </svg>
    <span class="gh-repo-name"><a href="https://github.com/st0012/ruby-skills">st0012/ruby-skills</a></span>
  </div>
  <div class="gh-repo-desc">Claude Code plugins for Ruby development.</div>
  <div class="gh-repo-stats">
    <span class="gh-repo-stat">
  <span class="gh-lang-dot" style="background: #89e051"></span>
  Shell
</span>

    <span class="gh-repo-stat">
      <svg viewBox="0 0 16 16" fill="currentColor"><path d="M8 .25a.75.75 0 0 1 .673.418l1.882 3.815 4.21.612a.75.75 0 0 1 .416 1.279l-3.046 2.97.719 4.192a.751.751 0 0 1-1.088.791L8 12.347l-3.766 1.98a.75.75 0 0 1-1.088-.79l.72-4.194L.818 6.374a.75.75 0 0 1 .416-1.28l4.21-.611L7.327.668A.75.75 0 0 1 8 .25Z" /></svg>
      109
    </span>
    <span class="gh-repo-stat">
      <svg viewBox="0 0 16 16" fill="currentColor"><path d="M5 5.372v.878c0 .414.336.75.75.75h4.5a.75.75 0 0 0 .75-.75v-.878a2.25 2.25 0 1 1 1.5 0v.878a2.25 2.25 0 0 1-2.25 2.25h-1.5v2.128a2.251 2.251 0 1 1-1.5 0V8.5h-1.5A2.25 2.25 0 0 1 3.5 6.25v-.878a2.25 2.25 0 1 1 1.5 0ZM5 3.25a.75.75 0 1 0-1.5 0 .75.75 0 0 0 1.5 0Zm6.75.75a.75.75 0 1 0 0-1.5.75.75 0 0 0 0 1.5Zm-3 8.75a.75.75 0 1 0-1.5 0 .75.75 0 0 0 1.5 0Z" /></svg>
      6
    </span>
  </div>
</div>

<h2 id="what-ruby-skills-provides">What ruby-skills provides</h2>

<p>Think of it as a starter pack for Ruby development in Claude Code - helping Claude understand your Ruby environment and find the right resources:</p>

<ul>
  <li><strong>Know how to run Ruby correctly</strong> - detects your version manager and activates the right Ruby version</li>
  <li><strong>Know where to look for resources</strong> - points Claude to authoritative documentation sources</li>
  <li><strong>Connect with the language server</strong> - integrates Ruby LSP for code intelligence</li>
</ul>

<p>The project provides two plugins:</p>

<h3 id="ruby-skills-plugin">ruby-skills plugin</h3>

<p>Contains Ruby-specific skills:</p>

<p><strong>ruby-version-manager</strong>: Detects your version manager and which Ruby version your project requires. Instead of running <code class="language-plaintext highlighter-rouge">bundle install</code> and failing, Claude runs:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>&lt;version manager activation <span class="nb">command</span><span class="o">&gt;</span> <span class="o">&amp;&amp;</span> bundle <span class="nb">install</span>
</code></pre></div></div>

<p>The activation command is prepended because Claude Code <a href="https://code.claude.com/docs/en/settings#bash-tool-behavior">runs each command in a fresh shell</a> - environment changes don’t persist between commands.</p>

<p><a href="https://github.com/Shopify/ruby-lsp/tree/main/vscode">Ruby LSP’s VS Code extension</a> solves the same problem for editors - it activates the correct Ruby so the language server and other extensions can invoke Ruby commands correctly. This skill does the same for Claude Code.</p>

<p>When multiple version managers are detected, Claude asks which one you prefer:</p>

<p><img src="/assets/images/posts/ruby-skills-version-manager-detection.png" alt="Claude detects multiple version managers and asks which one to use" /></p>

<p><strong>ruby-resource-map</strong>: Provides links to authoritative Ruby documentation (<code class="language-plaintext highlighter-rouge">docs.ruby-lang.org</code>) and advises avoiding known outdated sources like <code class="language-plaintext highlighter-rouge">apidock.com</code> (an unmaintained Ruby documentation site that often shows up in the top search results).</p>

<p>It also briefly explains the current situation in areas where official guidance is scarce or changing rapidly, like the typing ecosystem (Sorbet vs Steep, RBI vs RBS), and links to relevant projects. Without this, Claude might provide outdated suggestions based on old training data and bad references.</p>

<h3 id="ruby-lsp-plugin">ruby-lsp plugin</h3>

<p>Integrates <a href="https://github.com/Shopify/ruby-lsp">Ruby LSP</a> for code intelligence - hover documentation, go-to-definition, diagnostics. It uses the version manager skill to activate the correct Ruby before running the LSP server. (Note: <a href="https://docs.anthropic.com/en/docs/claude-code/plugins#lsp-servers">LSP integration</a> in Claude Code is new and currently less feature-rich than MCP integration.)</p>

<p><img src="/assets/images/posts/ruby-skills-lsp-demo.png" alt="Claude using Ruby LSP to fetch hover documentation for File.basename" /></p>

<h2 id="a-quick-note-on-claude-code-plugins">A quick note on Claude Code plugins</h2>

<p>Here’s some Claude Code terminology (see <a href="https://docs.anthropic.com/en/docs/claude-code">official documentation</a> for details):</p>

<ul>
  <li><strong>Marketplace</strong>: A repository that hosts multiple plugins. Communities can create their own marketplace to distribute related plugins together.</li>
  <li><strong>Plugins</strong>: Extend Claude Code’s capabilities. A plugin can provide LSP integration for code intelligence, MCP integration for custom tools, or skills that teach Claude domain-specific knowledge.</li>
  <li><strong>Skills</strong>: Instructions that guide Claude’s behavior for specific tasks.</li>
</ul>

<p>Anthropic maintains an <a href="https://github.com/anthropics/claude-plugins-official">official plugins repository</a> with plugins for popular tools like Playwright and Sentry, as well as <a href="https://github.com/anthropics/claude-plugins-official/tree/main/plugins">LSP integrations</a> for many languages.</p>

<h3 id="why-claude-code-specific">Why Claude Code specific</h3>

<p>In the longer term, I want to make this project vendor-agnostic - it should work with Claude Code, Codex, OpenCode, etc. But I started with Claude Code because:</p>

<ul>
  <li>I use it daily, at work and personally, so I can test what I build in real workflows.</li>
  <li>Claude Code has the most mature skill and plugin system and community. Building from here is easier.</li>
</ul>

<h2 id="what-i-think-will-happen-with-language-specific-skills">What I think will happen with language-specific skills</h2>

<p>As AI coding tools mature, I think language-specific plugins will naturally emerge from each community rather than being centralized in official or popular marketplaces. It makes more sense for people close to the tools to build and maintain these bridges - I’d expect something like <code class="language-plaintext highlighter-rouge">ruby/agent-skills</code> to eventually exist under the Ruby organization.</p>

<p>Generic AI tools handle syntax and common patterns well. But every language has ecosystem quirks that require community knowledge - the version manager fragmentation I described above is one Ruby-specific example that Rust or Go developers don’t face the same way. Contributing Ruby-specific logic to a centralized repository creates a mismatch: maintainers unfamiliar with Ruby would have to review and maintain code for tools they don’t use.</p>

<p>Skills also build on skills. Once Claude knows how to activate the right Ruby version, other Ruby skills become possible. A future RuboCop skill that configures project linting rules would need to invoke Ruby correctly first. The version manager skill becomes a foundation.</p>

<p>This is why instead of adding a ruby-lsp plugin to the <a href="https://github.com/anthropics/claude-plugins-official">official plugins repo</a>, I decided to host it alongside the skill.</p>

<h2 id="try-it-out">Try it out</h2>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># Add the marketplace</span>
claude plugin marketplace add st0012/ruby-skills

<span class="c"># Install the plugins (format: plugin-name@marketplace-name)</span>
claude plugin <span class="nb">install </span>ruby-skills@ruby-skills
claude plugin <span class="nb">install </span>ruby-lsp@ruby-skills
</code></pre></div></div>

<p>See the <a href="https://github.com/st0012/ruby-skills">README</a> for detailed documentation.</p>

<p>The long-term goal is to upstream these plugins to the <code class="language-plaintext highlighter-rouge">ruby/</code> organization - the experiment is whether community-maintained skills are worth the effort. Feedback, issues, and contributions are welcome on <a href="https://github.com/st0012/ruby-skills">GitHub</a>.</p>]]></content><author><name>Stan Lo</name></author><summary type="html"><![CDATA[Ruby developers using Claude Code hit this pattern: you ask it to run tests, it picks the wrong Ruby version, fails, tries bundle install, fails again.]]></summary></entry><entry><title type="html">My RDoc roadmap for 2026</title><link href="https://st0012.dev/2026/01/12/my-rdoc-roadmap-for-2026/" rel="alternate" type="text/html" title="My RDoc roadmap for 2026" /><published>2026-01-12T00:00:00+00:00</published><updated>2026-01-12T00:00:00+00:00</updated><id>https://st0012.dev/2026/01/12/my-rdoc-roadmap-for-2026</id><content type="html" xml:base="https://st0012.dev/2026/01/12/my-rdoc-roadmap-for-2026/"><![CDATA[<p>Last month we shipped <a href="https://railsatscale.com/2025-12-22-introducing-aliki-a-modern-theme-for-ruby-documentation/">Aliki</a>, RDoc’s new default theme. It brought dark mode, better search, and a modern layout to <a href="https://docs.ruby-lang.org/en/master/">docs.ruby-lang.org</a> and any gem using RDoc 7.0+. That was primarily about the <em>reading</em> experience.</p>

<p>This year I want to focus on the <em>writing</em> experience—and how documentation might need to evolve for the AI era.</p>

<h2 id="moving-toward-markdown">Moving toward Markdown</h2>

<p>RDoc markup has served Ruby well, but Markdown has become the industry standard. Most developers already know it. RDoc markup, being Ruby-specific, creates unnecessary friction for contributors.</p>

<p>I’ve been working on improving RDoc’s Markdown support to reach feature parity with <a href="https://github.github.com/gfm/">GitHub Flavored Markdown</a>. Although RDoc has supported Markdown for many years, it was buggy and lacked documentation. So starting this year, I’ve been working on fixing those issues. Some recent changes:</p>

<ul>
  <li><strong>GitHub-style heading anchors</strong>: Headings now generate lowercase, hyphenated anchors (like <code class="language-plaintext highlighter-rouge">#my-heading</code> instead of <code class="language-plaintext highlighter-rouge">#label-My+Heading</code>). Markdown links like <code class="language-plaintext highlighter-rouge">[link](#my-heading)</code> now work as expected. Legacy anchors are preserved as hidden elements so existing links don’t break.</li>
  <li><strong>Strikethrough</strong>: <code class="language-plaintext highlighter-rouge">~~text~~</code> now renders properly in Markdown files.</li>
  <li><strong>Backticks in RDoc markup</strong>: Users can now use backticks (<code class="language-plaintext highlighter-rouge">`</code>) in addition to <code class="language-plaintext highlighter-rouge">+</code> for inline code. This is a common mistake in Ruby core documentation contributions—rather than correcting people, we should just support what they naturally write.</li>
  <li><strong>Better code block handling</strong>: Fixed issues where non-Ruby code blocks were incorrectly getting Ruby syntax highlighting.</li>
</ul>

<p>I’ve also rebuilt <a href="https://ruby.github.io/rdoc/index.html#markup-formats">RDoc’s markup documentation</a> to better explain what’s supported in each format.</p>

<p>The above changes have been shipped in <a href="https://github.com/ruby/rdoc/releases/tag/v7.1.0">RDoc v7.1.0</a>.</p>

<p>The long-term goal is to make Markdown the default for Ruby documentation and help existing RDoc markup projects migrate to Markdown.</p>

<h2 id="rbs-integration">RBS integration</h2>

<p>Type information is becoming more valuable—both for developers and for AI tools that consume documentation. I want RDoc to support displaying RBS signatures alongside method documentation.</p>

<p>This means integrating inline and external RBS signatures into both HTML and RI output, with dedicated documentation section for type definitions (e.g. type aliases, generic types, etc.).</p>

<h2 id="documentation-for-ai">Documentation for AI</h2>

<p>This is more exploratory, but I think documentation tools need to consider how AI agents consume documentation.</p>

<p>One direction is generating LLM-friendly output formats. <a href="https://gitbook.com/docs/publishing-documentation/llm-ready-docs">GitBook’s approach</a> with <code class="language-plaintext highlighter-rouge">llms.txt</code> and markdown-only pages is interesting. Markdown is more token-efficient than HTML, and having a single-file documentation dump could be useful for context windows.</p>

<p>Another direction is agent skills that help <em>write</em> documentation. I’ve been thinking about what this could look like for RDoc:</p>

<ul>
  <li>Setting up RDoc for a project, from documentation generation to deployment on GitHub pages</li>
  <li>Writing documentation using RDoc features and directives</li>
</ul>

<p>Not directly related to RDoc, but I also would like to explore general Ruby skills, like:</p>

<ul>
  <li>Navigating common Ruby environment issues (version managers, bundler, etc.)</li>
  <li>Using <code class="language-plaintext highlighter-rouge">ri</code> to look up existing Ruby documentation for more effective Ruby programming</li>
</ul>

<h2 id="whats-next">What’s next</h2>

<p>The Markdown improvements and documentation overhaul are ongoing. RBS integration is next on the list, targeting RubyKaigi. The AI-related work is something I’ll explore throughout the year as I continue to use these tools myself.</p>

<p>If you’re generating documentation with RDoc and run into issues with any of these newer features, please <a href="https://github.com/ruby/rdoc/issues">open an issue</a>.</p>]]></content><author><name>Stan Lo</name></author><summary type="html"><![CDATA[Last month we shipped Aliki, RDoc’s new default theme. It brought dark mode, better search, and a modern layout to docs.ruby-lang.org and any gem using RDoc 7.0+. That was primarily about the reading experience.]]></summary></entry><entry><title type="html">Fixing My Blog from My Phone with Claude’s Code Sessions (NOT Claude Code)</title><link href="https://st0012.dev/2026/01/11/fix-my-new-blog-from-my-phone-using-claude/" rel="alternate" type="text/html" title="Fixing My Blog from My Phone with Claude’s Code Sessions (NOT Claude Code)" /><published>2026-01-11T00:00:00+00:00</published><updated>2026-01-11T00:00:00+00:00</updated><id>https://st0012.dev/2026/01/11/fix-my-new-blog-from-my-phone-using-claude</id><content type="html" xml:base="https://st0012.dev/2026/01/11/fix-my-new-blog-from-my-phone-using-claude/"><![CDATA[<p>One reason I <a href="/links/rebuilt-with-claude-code">migrated my blog from a hosted solution to GitHub Pages</a> was to see how AI agents could help me improve it and post content. So when I noticed some responsiveness issues while it on my phone, I tried fixing them directly using Claude app’s Code sessions (this is the web/mobile interface of the Claude app, not Claude Code the CLI tool).</p>

<p>It mostly worked — Claude debugged the issue, I reviewed the changes, and pushed a fix, which triggers the deployment, all from my phone while I was out.</p>

<p>That said, I ran into a few things that made the experience less smooth than I expected. If you’re a Ruby developer thinking about trying this, here’s what to know:</p>

<p><strong>The cloud environment won’t match your local setup.</strong> Code sessions run in a standardized Anthropic-managed VM with <code class="language-plaintext highlighter-rouge">rbenv</code> and Ruby <code class="language-plaintext highlighter-rouge">3.3.6</code> as the default. My site uses <code class="language-plaintext highlighter-rouge">chruby</code> and Ruby <code class="language-plaintext highlighter-rouge">4.0.0</code> and I wrote my <code class="language-plaintext highlighter-rouge">CLAUDE.md</code> based on it. In the cloud environment Claude tried and failed to follow these, then just gave up on building my Jekyll site to actually test the fix.</p>

<p>I needed to push it to sort out the environment issue, commit detailed instructions for that into <code class="language-plaintext highlighter-rouge">CLAUDE.md</code>, and verify the fixes after rebuilding the site. The cloud environment specific instructions are necessary for things to work consistently across multiple Code sessions.</p>

<p><strong>There’s no editor view.</strong> You can’t easily see code changes as you go — there’s no editor pane. You either check individual tool outputs, ask Claude to summarize changes, or open a PR to see the diff there.</p>

<p><strong>Sessions seem tied to a single PR.</strong> After my first PR was merged, I tried adding follow-up changes in the same session. Neither pushing to the rebased branch nor creating a new branch made Claude realize it needed to create a different PR — the UI kept showing the old, merged one. I’m not sure if this is a bug or intended behavior.</p>

<figure class="post-image">
  <img src="/assets/images/posts/claude-app-code-pr-ui.png" alt="Claude app Code sessions PR UI" loading="lazy" />
  
  <figcaption>The branch and View PR button don't update for follow up PRs</figcaption>
  
</figure>

<hr />

<p>I’m genuinely excited that I can now address website issues and publish content from my phone. The experience was good enough that I’ll probably use it again.</p>

<p>I’m less excited about hearing “You can fix this on your phone” in the future.</p>]]></content><author><name>Stan Lo</name></author><summary type="html"><![CDATA[One reason I migrated my blog from a hosted solution to GitHub Pages was to see how AI agents could help me improve it and post content. So when I noticed some responsiveness issues while it on my phone, I tried fixing them directly using Claude app’s Code sessions (this is the web/mobile interface of the Claude app, not Claude Code the CLI tool).]]></summary></entry><entry><title type="html">AI and Open Source: A Maintainer’s Take (End of 2025)</title><link href="https://st0012.dev/2025/12/30/ai-and-open-source-a-maintainers-take-end-of-2025/" rel="alternate" type="text/html" title="AI and Open Source: A Maintainer’s Take (End of 2025)" /><published>2025-12-30T00:00:00+00:00</published><updated>2025-12-30T00:00:00+00:00</updated><id>https://st0012.dev/2025/12/30/ai-and-open-source-a-maintainers-take-end-of-2025</id><content type="html" xml:base="https://st0012.dev/2025/12/30/ai-and-open-source-a-maintainers-take-end-of-2025/"><![CDATA[<p>I’m on the cautious-optimistic side when it comes to AI coding tools and open source.</p>

<p>I want to use this post to document my opinions on AI coding tools and OSS maintenance at this specific point.</p>

<p>With the rate AI models and coding tools improve, I’d be curious to see how much of my takes hold or change after 6 months/a year.</p>

<h2 id="about-me">About me</h2>

<p>I’m a Ruby committer. I also maintain Ruby’s <a href="https://github.com/ruby/rdoc">RDoc</a>, <a href="https://github.com/ruby/irb">IRB</a>, and <a href="https://github.com/ruby/reline">Reline</a> libraries.</p>

<p>I don’t see myself as an AI expert, perhaps not even a power user. I use AI tools regularly at work and for OSS development, but haven’t explored many advanced features, such as custom skills. My primary setup is <strong>Claude Code</strong> with <strong>Opus 4.5</strong>, so my takes are largely shaped by that.</p>

<p>I’ve used AI to assist my contributions to <a href="https://docs.ruby-lang.org/en/master/jit/zjit_md.html">ZJIT</a> and the creation of <a href="https://railsatscale.com/2025-12-22-introducing-aliki-a-modern-theme-for-ruby-documentation/">Ruby’s new documentation theme</a>. Without AI, I wouldn’t have attempted these—or not to the same extent, due to the upfront cost.</p>

<h2 id="more-developers-will-contribute-to-oss-using-ai-tools">More developers will contribute to OSS using AI tools</h2>

<p>I think a main reason is that they see it works well in their work/personal projects. Laziness and malicious attempts could also be behind some people’s contributions, but I want to believe that the majority genuinely believe AI is helping them contribute.</p>

<h2 id="ai-coding-skills-vary-even-more-than-traditional-coding-skills">AI coding skills vary even more than traditional coding skills</h2>

<p>Just like every developer’s coding skill can vary, sometimes a lot, our AI coding skills and perceptions on coding with AI can vary a lot too. I’d argue that as of now, the difference could be even bigger than traditional coding skill differences.</p>

<p>We have more variables now:</p>

<ul>
  <li>The models people have access to (due to budget limits, company policies, regions, etc.)</li>
  <li>Usage limits</li>
  <li>The interfaces they use (CLI, IDE integration, chat)</li>
  <li>What projects they use those tools on daily</li>
  <li>The person’s moral compass</li>
  <li>…and many others</li>
</ul>

<p>(I don’t want to get too deep into these variables here.)</p>

<h2 id="ai-is-a-multiplier-not-a-leveler">AI is a multiplier, not a leveler</h2>

<p>AI amplifies existing developer habits, good or bad. If you lack certain good traits in software development—curiosity, willingness to dig into root causes, knowing when to ask for help…etc.—AI won’t fill that hole. It’ll just help you produce more of whatever you were already producing.</p>

<h2 id="the-maintainers-dilemma">The maintainer’s dilemma</h2>

<p>As an OSS maintainer, I don’t get to control what tools people use to “help” them contribute to the project, or how they use it.</p>

<p>I’ve seen more developers feel “enabled” by AI tools to start contributing to OSS projects. In other words, those devs would not have contributed without these tools. I’m one of those devs in terms of contributing to <a href="https://docs.ruby-lang.org/en/master/jit/zjit_md.html">ZJIT</a>, as I’ve detailed in <a href="/ai-coding-agents-are-removing-programming-language-barriers">a previous post</a>.</p>

<p>But this also means we’re seeing more low-effort, low-quality contributions.</p>

<p>So what distinguishes good-faith AI-assisted contributions from low-effort ones? I don’t have a good definition that’s worth sharing, but here’s what I look for:</p>

<ul>
  <li>Did the contributor commit the changes themselves? (indicates they at least did a final review, hopefully)</li>
  <li>Can they answer: what problem they’re solving, and why this specific approach?</li>
</ul>

<p>The solution exploration doesn’t need to be exhaustive—it’s okay to make mistakes and ask questions. The point is: have good intent and stay engaged with their own work.</p>

<h2 id="ai-agents-are-changing-the-maintainer-contributor-dynamic">AI agents are changing the maintainer-contributor dynamic</h2>

<p>Before AI tools, the contribution process involved two parties: maintainers and contributors (it can also involve community discussions, but let’s keep it simple for now). Now there are three: maintainers, contributors, and contributors’ agents.</p>

<p>This creates new communication channels:</p>

<ul>
  <li><strong>Maintainer → Contributor</strong>: CONTRIBUTING.md, PR reviews (unchanged)</li>
  <li><strong>Maintainer → Contributor’s Agent</strong>: Agent instruction files like <a href="https://agents.md/">AGENTS.md</a>, CLAUDE.md, etc. (new)</li>
  <li><strong>Contributor → Their Agent</strong>: prompts, instructions</li>
</ul>

<p>Agent instructions talk directly to agents, not necessarily to contributors. A contributor might not read your docs, but their agent is more likely to. This lets maintainers influence how contributors’ tools behave in their repo.</p>

<p>For example, maintainers can ask agents to:</p>

<ul>
  <li>Care about commit hygiene</li>
  <li>Not commit anything that breaks tests</li>
  <li>Be concise when generating comments, or use a specific format</li>
</ul>

<p><strong>In the past, these practices were hard to enforce—you could document them, but contributors might not read or follow them. Now that agents are in the loop and tend to follow instructions, maybe this brings some positivity to maintainers too.</strong></p>

<p>But in my opinion, one channel should stay the same: <strong>Contributor → Maintainer communication should remain human-to-human</strong>. PRs and discussions should come from the contributor, not their agent.</p>

<p>This doesn’t mean you can’t use AI to help draft a PR description—just review it like you would with the code. The expectation is that you’ve reviewed and understood what you’re submitting.</p>

<h3 id="what-does-this-mean-to-maintainers">What does this mean to maintainers</h3>

<p>Given this new dynamic, I think projects should provide AI-related guidance via agent instruction files. This isn’t about preventing AI slop, as you can’t really stop bad contributions, with or without AI. It’s about empowering good-faith contributors, your fellow maintainers, and their agents to work more effectively with your project.</p>

<p>Yes, it will add more to the maintainer’s plate. But AI can help with that too—if maintainers have access to these tools.</p>

<h2 id="ai-companies-should-sponsor-maintainers">AI companies should sponsor maintainers</h2>

<p>Contributors now have access to powerful AI tools. But many maintainers don’t—and without them, maintainers only feel the negatives: more contributions to review, some low-quality, without the means to keep up.</p>

<p>I personally think AI coding tools are the biggest developer productivity boost in recent memory. And the people maintaining our shared infrastructure should have access to them too.</p>

<p>Similar to how CDN and hosting companies sponsor usage credits to OSS projects, I think AI companies can sponsor access to their tools. For example, maintainers of popular OSS projects could get Claude Code Max free of charge.</p>

<p>The exact mechanism could vary—credits, free tiers, partnerships—but providing sponsorship hits multiple birds with one stone:</p>

<ul>
  <li>It helps projects progress faster</li>
  <li>It allows maintainers to catch up with contributors’ tooling and respond accordingly (e.g., maintain good agent instructions)</li>
  <li>Publicly visible agent instructions (AGENTS.md, skills, etc.) enable sharing real-world agentic coding practices, which helps broader adoption</li>
</ul>

<p>If these tools help developers ship faster, let’s make sure maintainers have access too.</p>

<h2 id="to-contributors">To contributors</h2>

<p>I encourage using AI to contribute to projects I maintain. The expectations I outlined earlier apply here too—review your own work, be able to explain what and why.</p>

<p>If you want to contribute but aren’t familiar with the codebase, use AI to help you learn. Ask it questions and verify the answers by digging into the code yourself. Treat AI as another person who’s also new to the codebase—have discussions together, run experiments together.</p>

<h2 id="to-maintainers-who-havent-tried-ai-tools">To maintainers who haven’t tried AI tools</h2>

<p>If you’re still skeptical, I’d echo <a href="https://bsky.app/profile/mitsuhiko.at/post/3mb54qft36s2u">Armin Ronacher’s advice</a>: give yourself a week to really try it. Not just a quick test—actually use it for tasks you’re already planning to do.</p>

<p>I recommend treating it as a second pair of eyes first. Let it help you with tasks you already understand well, so you can evaluate its output critically.</p>

<p>Once you’re comfortable, create an AI instruction file like <a href="https://agents.md/">AGENTS.md</a> with AI’s help. At the very least, tell AI how to:</p>

<ul>
  <li>Build your project</li>
  <li>Run tests</li>
  <li>Run linters</li>
  <li>Run end-to-end tests (if applicable)</li>
</ul>

<p>The latest models should be able to help you generate these with minimal input. If you’re missing any of these instructions in your CONTRIBUTING.md, you can improve it together as well.</p>

<p>With these instructions in place, agents can do a lot with minimal intervention:</p>

<ul>
  <li>Prototype a few solutions to an issue and summarize the results</li>
  <li>Identify and remove dead code, then run tests to verify</li>
  <li>Execute and test documented code examples</li>
</ul>

<p>This is where I feel the agents start to increase my productivity significantly.</p>

<h2 id="long-term-optimism">Long-term optimism</h2>

<p>I think in the long run, AI will help the community maintain and improve OSS projects.</p>

<p><a href="https://railsatscale.com/2025-12-22-introducing-aliki-a-modern-theme-for-ruby-documentation/">RDoc’s new Aliki theme</a> is one example—I wouldn’t have built it without AI. Beyond that, AI has helped me address markdown parsing issues, explore refactoring ideas, and more. <strong>It’s made project maintenance a bit more fun, instead of just extra debugging on weekends.</strong></p>

<p>I’d be interested to see if AI tools will help revive unmaintained projects. And whether they’ll help raise a new generation of contributors—or even maintainers.</p>]]></content><author><name>Stan Lo</name></author><category term="ai," /><category term="open-source," /><category term="programming," /><category term="oss" /><summary type="html"><![CDATA[I’m on the cautious-optimistic side when it comes to AI coding tools and open source.]]></summary></entry><entry><title type="html">How Ruby Executes JIT Code: The Hidden Mechanics Behind the Magic</title><link href="https://st0012.dev/2025/09/08/how-ruby-executes-jit-code/" rel="alternate" type="text/html" title="How Ruby Executes JIT Code: The Hidden Mechanics Behind the Magic" /><published>2025-09-08T00:00:00+00:00</published><updated>2025-09-08T00:00:00+00:00</updated><id>https://st0012.dev/2025/09/08/how-ruby-executes-jit-code</id><content type="html" xml:base="https://st0012.dev/2025/09/08/how-ruby-executes-jit-code/"><![CDATA[<blockquote>
  <p>This post was originally published on September 8, 2025 on <a href="https://railsatscale.com/2025-09-08-how-ruby-executes-jit-code-the-hidden-mechanics-behind-the-magic/">Rails at Scale</a>.</p>
</blockquote>

<p>Ever since YJIT’s introduction, I’ve felt simultaneously close to and distant from Ruby’s JIT compiler. I know how to enable it in my Ruby programs. I know it makes my Ruby programs run faster by compiling some of them into machine code. But my understanding around YJIT, or JIT compilers in Ruby in general, seems to end here.</p>

<p>A few months ago, my colleague <a href="https://bernsteinbear.com/">Max Bernstein</a> wrote <a href="https://railsatscale.com/2025-05-14-merge-zjit/">ZJIT has been merged into Ruby</a> to explain how ZJIT compiles Ruby’s bytecode to HIR, LIR, and then to native code.
It sheds some light on how JIT compilers can compile our program, which is why I started to <a href="https://github.com/ruby/ruby/pulls?q=is%3Apr+author%3Ast0012+ZJIT+">contribute to ZJIT in July</a>.
But I still had many questions unanswered before digging into the source code and asking the JIT experts around me (<a href="https://bernsteinbear.com/">Max</a>, <a href="https://github.com/k0kubun">Kokubun</a>, and <a href="https://alanwu.space/">Alan</a>).</p>

<p>So I want to use this post to answer some questions/mental gaps you might also have about JIT compilers for Ruby:</p>

<ol>
  <li><strong>Where does JIT-compiled code actually live?</strong></li>
  <li><strong>How does Ruby actually execute JIT code?</strong></li>
  <li><strong>How does Ruby decide what to compile?</strong></li>
  <li><strong>Why does JIT-compiled code fall back to the interpreter?</strong></li>
</ol>

<p>While we use ZJIT (Ruby’s experimental next-generation JIT) as our reference, these concepts apply equally to YJIT as well.</p>

<h2 id="where-jit-compiled-code-actually-lives">Where JIT-Compiled Code Actually Lives</h2>

<h3 id="ruby-iseqs-and-yarv-bytecode">Ruby ISEQs and YARV Bytecode</h3>

<p>When Ruby loads your code, it compiles each method into an Instruction Sequence (ISEQ) - a data structure containing <a href="https://en.wikipedia.org/wiki/YARV">YARV</a> (CRuby virtual machine) bytecode instructions.</p>

<p>(If you’re not familiar with YARV instructions or want to learn more, <a href="https://kddnewton.com/">Kevin Newton</a> wrote a <a href="https://kddnewton.com/2022/11/30/advent-of-yarv-part-0.html">great blog series</a> to introduce them)</p>

<p>Let’s start with a simple example:</p>

<div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">def</span> <span class="nf">foo</span>
  <span class="n">bar</span>
<span class="k">end</span>

<span class="k">def</span> <span class="nf">bar</span>
  <span class="mi">42</span>
<span class="k">end</span>
</code></pre></div></div>

<p>Running <code class="language-plaintext highlighter-rouge">ruby --dump=insn example.rb</code> shows us the bytecode:</p>

<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>== disasm: #&lt;ISeq:foo@example.rb:1 (1,0)-(3,3)&gt;
0000 putself                                                          (   2)[LiCa]
0001 opt_send_without_block                 &lt;calldata!mid:bar, argc:0, FCALL|VCALL|ARGS_SIMPLE&gt;
0003 leave                                  [Re]

== disasm: #&lt;ISeq:bar@example.rb:5 (5,0)-(7,3)&gt;
0000 putobject                              42                        (   6)[LiCa]
0002 leave                                  [Re]
</code></pre></div></div>

<h3 id="jit-compiled-code-lives-on-iseq-too">JIT-Compiled Code Lives on ISEQ Too</h3>

<p>I assumed JIT-compiled code would replace bytecode—after all, native code is faster. But Ruby keeps both, for good reason.</p>

<p>Here’s what an ISEQ looks like initially:</p>

<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ISEQ (foo method)
├── body
│   ├── bytecode: [putself, opt_send_without_block, leave]
│   ├── jit_entry: NULL  // No JIT code yet
│   ├── jit_entry_calls: 0  // Call counter
</code></pre></div></div>

<p>After the method is called repeatedly and gets JIT-compiled:</p>

<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ISEQ (foo method)
├── body
│   ├── bytecode: [putself, opt_send_without_block, leave]  // Still here!
│   ├── jit_entry: 0x7f8b2c001000  // Pointer to native machine code
│   ├── jit_entry_calls: 35  // Reached compilation threshold
</code></pre></div></div>

<p>The <code class="language-plaintext highlighter-rouge">jit_entry</code> field is the gateway to native code. When it’s NULL, Ruby interprets bytecode. When it points to compiled code, Ruby can jump directly to machine instructions.
But the bytecode never goes away - Ruby needs it for de-optimization, which we will explore a bit later.</p>

<h2 id="the-execution-switch-from-bytecode-to-native-code">The Execution Switch: From Bytecode to Native Code</h2>

<p>This is easier than I expected. Since each ISEQ points to its JIT compiled code when it’s available, Ruby simply
checks the <code class="language-plaintext highlighter-rouge">jit_entry</code> field on every ISEQ it’s going to execute:</p>

<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1767034507570/z-Ebc56zA.png?auto=format" alt="JIT-compiled code execution" /></p>

<p>When there’s no JIT code (<code class="language-plaintext highlighter-rouge">jit_entry</code> is NULL), it continues interpreting. Otherwise, it runs the compiled native code.</p>

<h2 id="how-ruby-decides-what-to-compile">How Ruby Decides What to Compile</h2>

<p>Ruby doesn’t compile methods randomly or all at once. Instead, methods earn compilation through repeated use. In ZJIT, this happens in two phases:</p>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">if</span> <span class="p">(</span><span class="n">body</span><span class="o">-&gt;</span><span class="n">jit_entry</span> <span class="o">==</span> <span class="nb">NULL</span> <span class="o">&amp;&amp;</span> <span class="n">rb_zjit_enabled_p</span><span class="p">)</span> <span class="p">{</span>
    <span class="n">body</span><span class="o">-&gt;</span><span class="n">jit_entry_calls</span><span class="o">++</span><span class="p">;</span>

    <span class="c1">// Phase 1: Profile the method</span>
    <span class="k">if</span> <span class="p">(</span><span class="n">body</span><span class="o">-&gt;</span><span class="n">jit_entry_calls</span> <span class="o">==</span> <span class="n">rb_zjit_profile_threshold</span><span class="p">)</span> <span class="p">{</span>
        <span class="n">rb_zjit_profile_enable</span><span class="p">(</span><span class="n">iseq</span><span class="p">);</span>
    <span class="p">}</span>

    <span class="c1">// Phase 2: Compile to native code</span>
    <span class="k">if</span> <span class="p">(</span><span class="n">body</span><span class="o">-&gt;</span><span class="n">jit_entry_calls</span> <span class="o">==</span> <span class="n">rb_zjit_call_threshold</span><span class="p">)</span> <span class="p">{</span>
        <span class="n">rb_zjit_compile_iseq</span><span class="p">(</span><span class="n">iseq</span><span class="p">,</span> <span class="nb">false</span><span class="p">);</span>
        <span class="c1">// After this, jit_entry points to machine code</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>As of now, ZJIT’s default profile threshold is <code class="language-plaintext highlighter-rouge">25</code> and compile threshold is <code class="language-plaintext highlighter-rouge">30</code> (both may change in the future). So a method’s lifecycle may look like this:</p>

<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Calls:     0 ─────────── 25 ────────── 30 ─────────────────►
           │              │             │
Mode:      └─ Interpret ──┴── Profile ──┴─ Native Code (JIT compiled)
</code></pre></div></div>

<p>This is why we need to “warm up” the program before we get the peak performance with JIT.</p>

<h3 id="when-jit-code-gives-up-understanding-de-optimization">When JIT Code Gives Up: Understanding De-optimization</h3>

<p>JIT code makes assumptions to run fast. When those assumptions break, Ruby must “de-optimize” - return control to the interpreter. It’s a safety mechanism that ensures your code always produces correct results.</p>

<p>Consider this method:</p>

<div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">def</span> <span class="nf">add</span><span class="p">(</span><span class="n">a</span><span class="p">,</span> <span class="n">b</span><span class="p">)</span>
  <span class="n">a</span> <span class="o">+</span> <span class="n">b</span>
<span class="k">end</span>
</code></pre></div></div>

<p>which would generate these instructions:</p>

<pre><code class="language-txt">== disasm: #&lt;ISeq:add@test.rb:1 (1,0)-(3,3)&gt;
0000 getlocal_WC_0                          a@0                       (   2)[LiCa]
0002 getlocal_WC_0                          b@1
0004 opt_plus                               &lt;calldata!mid:+, argc:1, ARGS_SIMPLE&gt;[CcCr]
0006 leave                                                            (   3)[Re]
</code></pre>

<p>Because Ruby doesn’t know what <code class="language-plaintext highlighter-rouge">opt_plus</code> would be called with beforehand, the underlying C function <code class="language-plaintext highlighter-rouge">vm_opt_plus</code> needs to handle various classes (like String, Array, Float, Integer, etc.) that can respond to <code class="language-plaintext highlighter-rouge">+</code>.</p>

<p>But, if profiling shows <code class="language-plaintext highlighter-rouge">add</code> is always called with integers (Fixnums), JIT compilers can generate optimized code that <em>only</em> handles integer addition. But it includes “guards” to check this assumption:</p>

<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1767034519994/n_OF_ZLyI.png?auto=format" alt="JIT type guard" /></p>

<p>When the assumption is broken, like when <code class="language-plaintext highlighter-rouge">add(1.5, 2)</code> is called:</p>

<ol>
  <li>The guard check fails</li>
  <li>JIT code jumps to a “side exit”</li>
  <li>The side exit restores interpreter state (stack, instruction pointer..etc.)</li>
  <li>Control returns to the interpreter</li>
  <li>The interpreter executes <code class="language-plaintext highlighter-rouge">opt_plus</code> and calls the <code class="language-plaintext highlighter-rouge">vm_opt_plus</code> function</li>
</ol>

<p>Other triggers for falling back include:</p>

<ul>
  <li><strong>TracePoint activation</strong> - TracePoint needs bytecode execution for properly emitting events (more details below)</li>
  <li><strong>Redefined core methods</strong> - Someone changed what <code class="language-plaintext highlighter-rouge">+</code> means on Integer</li>
  <li><strong>Ractor usage</strong> - Multi-ractor changes some YARV instruction’s behaviour. So the compiled code could perform differently than the interpreter in that situation</li>
</ul>

<p>These assumption checks, or patch points as we call them in ZJIT, make sure your program performs correctly when any of the assumptions change.</p>

<h2 id="answering-some-additional-questions">Answering Some Additional Questions</h2>

<p><strong>Why does enabling TracePoint slow everything down?</strong></p>

<p>(<a href="https://docs.ruby-lang.org/en/master/TracePoint.html">TracePoint</a> is a Ruby class that can be used to register callbacks on specific Ruby execution events. It’s commonly used in debugging/development tools.)</p>

<p>Most of TracePoint’s events are triggered by corresponding YARV bytecode. When TracePoint is activated, instructions in ISEQs will be replaced with their <code class="language-plaintext highlighter-rouge">trace_*</code> counterpart. Like <code class="language-plaintext highlighter-rouge">opt_plus</code> will be replaced with <code class="language-plaintext highlighter-rouge">trace_opt_plus</code>.</p>

<p>If Ruby only executes the compiled machine code, then those events wouldn’t be triggered correctly. Therefore, when ZJIT and YJIT compilers detect TracePoint’s activation, they immediately throw away the optimized code to force Ruby to interpret YARV instructions instead.</p>

<p><strong>Why doesn’t Ruby just compile everything?</strong></p>

<p>Many methods are called rarely. Compiling them would waste memory and compilation time for no performance benefit. Also, compiling methods without profiling would mean that JIT compilers either make wrong assumptions that get invalidated pretty quickly, or don’t make specific enough assumptions that miss further optimization opportunities.</p>

<h2 id="final-notes">Final Notes</h2>

<p>I hope this post helped you understand JIT compilers, a now essential part of Ruby, a little bit more.</p>

<p>If you want to learn more about Ruby’s new JIT compiler: ZJIT, I highly recommend giving <a href="https://railsatscale.com/2025-05-14-merge-zjit/">ZJIT has been merged into Ruby</a> a read.
And if you want to learn more about Ruby’s YARV instructions, <a href="https://kddnewton.com/">Kevin Newton</a>’s <a href="https://kddnewton.com/2022/11/30/advent-of-yarv-part-0.html">Advent of YARV series</a> is the best resource.</p>]]></content><author><name>Stan Lo</name></author><category term="ruby," /><category term="jit," /><category term="yjit," /><category term="zjit," /><category term="performance" /><summary type="html"><![CDATA[This post was originally published on September 8, 2025 on Rails at Scale.]]></summary></entry><entry><title type="html">AI Coding Agents Are Removing Programming Language Barriers</title><link href="https://st0012.dev/2025/07/19/ai-coding-agents-are-removing-programming-language-barriers/" rel="alternate" type="text/html" title="AI Coding Agents Are Removing Programming Language Barriers" /><published>2025-07-19T00:00:00+00:00</published><updated>2025-07-19T00:00:00+00:00</updated><id>https://st0012.dev/2025/07/19/ai-coding-agents-are-removing-programming-language-barriers</id><content type="html" xml:base="https://st0012.dev/2025/07/19/ai-coding-agents-are-removing-programming-language-barriers/"><![CDATA[<blockquote>
  <p>This post was originally published on July 19, 2025 on <a href="https://railsatscale.com/2025-07-19-ai-coding-agents-are-removing-programming-language-barriers/">Rails at Scale</a>.</p>
</blockquote>

<p>For a decade (2014-2024), I was a Ruby-only developer. I worked across the Ruby ecosystem—from Rails development to Ruby’s core tooling like <a href="https://github.com/ruby/irb">IRB</a>, <a href="https://github.com/ruby/rdoc">RDoc</a>, and the <a href="https://github.com/ruby/debug">debug gem</a>. But while I moved around the stack, I stayed within Ruby’s boundaries. Ruby wasn’t just my primary language; it was essentially my only language.</p>

<p>That changed in 2025.</p>

<p>This year, I’ve contributed to <a href="https://sorbet.org/">Sorbet</a> (C++), worked on <a href="https://github.com/ruby/rbs">RBS</a>’s parser (C), and am now diving into <a href="https://railsatscale.com/2025-05-14-merge-zjit/">ZJIT</a> (Rust). A combination of factors enabled this shift—something I’d always dreamed of but was terrified to attempt. But AI coding tools like Cursor and Claude Code—both encouraged at Shopify—have been absolutely career-changing.</p>

<h2 id="the-perfect-storm-of-opportunity">The Perfect Storm of Opportunity</h2>

<p>Before diving into the AI aspect, I need to acknowledge two crucial factors that made this transition possible:</p>

<p>First, our Ruby DX team’s roadmap shifted to require Sorbet’s <a href="https://railsatscale.com/2025-04-23-rbs-support-for-sorbet/">support for RBS</a>, which meant I now had to work on projects written in C++ and C—system programming languages that require understanding concepts I’d never encountered in Ruby.</p>

<p>Second, Shopify’s Ruby and Rails Infrastructure team is packed with experts who genuinely love sharing their knowledge. <a href="https://github.com/amomchilov">Alexander Momchilov</a>, <a href="https://github.com/Morriar">Alexandre Terrasa</a>, <a href="https://github.com/tekknolagi">Max Bernstein</a>, and many others have been incredibly generous with their time. Those pairing/tutoring sessions gave me the C/C++/JIT fundamentals I needed to even attempt this work.</p>

<p>But here’s the thing: great mentors and project opportunities to learn new languages have always existed. What’s different now is how AI has fundamentally changed the learning curve.</p>

<h2 id="the-complexity-of-system-programming-projects">The Complexity of System Programming Projects</h2>

<p>Let me use ZJIT, a new just-in-time (JIT) Ruby compiler, as an example (to learn more about it, check out <a href="https://railsatscale.com/2025-05-14-merge-zjit/">this RailsAtScale post</a> by Max). This project perfectly illustrates the challenge: it requires both deep conceptual understanding (how JITs and GC work) AND language/tool-specific expertise (Rust idioms, C programming conventions, Ruby’s build system). Working on ZJIT means constantly juggling:</p>

<ol>
  <li><strong>Rust</strong> (what ZJIT is written in)</li>
  <li><strong>C</strong> (what Ruby is written in)</li>
  <li><strong>General JIT knowledge</strong> (compiler theory, optimization strategies)</li>
  <li><strong>ZJIT-specific concepts</strong> (its particular architecture and design decisions)</li>
  <li><strong>Ruby internals</strong> (how the VM actually works)</li>
  <li><strong>Ruby build systems</strong> (which have their own conventions on top of tools like autoconf and Makefile that I’d rarely encountered before)</li>
</ol>

<p>A single pull request typically touches 2-4 of these areas simultaneously. Claude is remarkably helpful with the first three—language syntax, general concepts, standard patterns. It’s hit or miss for the last three—project-specific knowledge, deep internals, and build system quirks.</p>

<p>But that’s still cutting my learning blockers in half.</p>

<h2 id="ai-as-a-complementary-pairing-partner">AI as a Complementary Pairing Partner</h2>

<p>The real breakthrough came when I stopped thinking of AI as a code generator and started treating it as a pairing partner with complementary skills.</p>

<p>This isn’t about AI writing code for me. If I expected Claude to implement ZJIT features correctly, I’d likely be frustrated and probably waste more time than I’d save. The AI lacks the project-specific context and deep domain knowledge required.</p>

<p>Instead, we act as a pair of engineers with different strengths. While it knows more about language-specific syntax and patterns than I do, I understand the project requirements and constraints better (and hopefully have better taste). This creates a productive learning dynamic where:</p>

<ul>
  <li>I provide task requirements and project context</li>
  <li>AI identifies existing patterns and acts as the language expert</li>
  <li>I question why certain approaches would or wouldn’t work</li>
  <li>AI explores the theory, either through actually changing code or simply inferring, and gives me the result</li>
  <li>Through the conversation, I’m learning both the language AND how to apply it effectively</li>
</ul>

<p>For example, when I need to profile a Ruby bytecode instruction for ZJIT, I can ask Claude Code to examine previous PRs that did something similar and explain the parts I don’t understand line by line. I can ask “dumb” questions like “Why do JIT compilers need profiling?” without feeling like I’m wasting someone’s time. I get immediate clarification on unfamiliar Rust syntax. And throughout this process, I’m building my understanding of both the language and the system.</p>

<p>Of course, there were also times where we were both heading in the wrong direction together, and could only be saved by my mentors and teammates’ clarification and knowledge sharing. AI accelerates learning, but human expertise remains irreplaceable for course correction.</p>

<h2 id="the-language-barrier-is-dissolving">The Language Barrier Is Dissolving</h2>

<p>What excites me most is that we no longer need to spend 100+ hours learning C before making our first contribution to a C project. AI acts as a second pair of eyes, unblocking us from silly rookie mistakes—using the wrong syntax to declare variables, misunderstanding type conventions, or fighting with unfamiliar tooling. We can start contributing meaningfully from day one, learning what we need as we go.</p>

<p>This doesn’t replace deep expertise—our team’s language experts remain invaluable. But it does mean that being productive in multiple languages is now achievable for more developers. The cognitive load of syntax, standard library functions, and common patterns can be offloaded, letting us focus on the actual problems we’re solving.</p>

<p>For someone who spent a decade as a “Ruby developer,” becoming a multi-language developer in less than a year feels revolutionary. And I suspect I’m just early to a trend that will reshape how we think about programming language specialization entirely.</p>]]></content><author><name>Stan Lo</name></author><category term="ruby," /><category term="ai," /><category term="programming," /><category term="career" /><summary type="html"><![CDATA[This post was originally published on July 19, 2025 on Rails at Scale.]]></summary></entry><entry><title type="html">My Ruby Debugging Tips in 2025</title><link href="https://st0012.dev/2025/03/13/my-ruby-debugging-tips-in-2025/" rel="alternate" type="text/html" title="My Ruby Debugging Tips in 2025" /><published>2025-03-13T00:00:00+00:00</published><updated>2025-03-13T00:00:00+00:00</updated><id>https://st0012.dev/2025/03/13/my-ruby-debugging-tips-in-2025</id><content type="html" xml:base="https://st0012.dev/2025/03/13/my-ruby-debugging-tips-in-2025/"><![CDATA[<p>This is a quick &amp; unpolished collection of my Ruby debugging tips and recommendations.</p>

<ul>
  <li>You can use the <a href="https://marketplace.visualstudio.com/items?itemName=Shopify.ruby-lsp">Ruby LSP extension</a> to connect to <a href="https://github.com/ruby/debug">debug.gem</a> too. It requires a slightly different <code class="language-plaintext highlighter-rouge">launch.json</code> configuration (<a href="https://github.com/Shopify/ruby-lsp/blob/main/.vscode/launch.json">example</a>) and provides better error handling for connection issues.</li>
  <li>
    <p>Try using <code class="language-plaintext highlighter-rouge">launch</code> request in <code class="language-plaintext highlighter-rouge">launch.json</code> instead of <code class="language-plaintext highlighter-rouge">attach</code>. It simplifies the debugging process as you don’t need to manually start/stop the server. In most Rails projects, a simple entry like this will do:</p>

    <div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span><span class="w">
  </span><span class="nl">"version"</span><span class="p">:</span><span class="w"> </span><span class="s2">"0.2.0"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"configurations"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
    </span><span class="p">{</span><span class="w">
      </span><span class="nl">"type"</span><span class="p">:</span><span class="w"> </span><span class="s2">"ruby_lsp"</span><span class="p">,</span><span class="w">
      </span><span class="nl">"name"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Launch Server"</span><span class="p">,</span><span class="w">
      </span><span class="nl">"request"</span><span class="p">:</span><span class="w"> </span><span class="s2">"launch"</span><span class="p">,</span><span class="w">
      </span><span class="nl">"program"</span><span class="p">:</span><span class="w"> </span><span class="s2">"bin/rails s"</span><span class="p">,</span><span class="w">
    </span><span class="p">},</span><span class="w">
  </span><span class="p">]</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div>    </div>
  </li>
  <li>The effectiveness of your debugging session heavily depends on your ability to navigate between methods, classes, and files. Make sure you have a good editor setup, such as <a href="https://shopify.github.io/ruby-lsp/">Ruby LSP</a>.
    <ul>
      <li>Even if you don’t use VS Code, <a href="https://shopify.github.io/ruby-lsp/">Ruby LSP</a> works with <a href="https://shopify.github.io/ruby-lsp/editors.html">many other editors too</a>.</li>
    </ul>
  </li>
  <li>Use <a href="https://shopify.github.io/ruby-lsp/">Ruby LSP</a>’s <a href="https://shopify.github.io/ruby-lsp/#code-lens">Code Lens</a> feature to easily debug tests in both terminal and VS Code.
    <ul>
      <li>I made <a href="https://github.com/st0012/ruby-lsp-rspec">ruby-lsp-rspec</a> to provide code lens for RSpec tests.</li>
    </ul>
  </li>
  <li>
    <p>Use <code class="language-plaintext highlighter-rouge">gem "debug", require: "debug/prelude"</code> in your <code class="language-plaintext highlighter-rouge">Gemfile</code> instead.</p>

    <p><a href="https://github.com/ruby/debug">debug.gem</a> is activated when required, which usually isn’t necessary when you’re not debugging.</p>

    <p>By requiring <code class="language-plaintext highlighter-rouge">debug/prelude</code>, it defines the breakpoint methods like <code class="language-plaintext highlighter-rouge">breakpoint</code> and <code class="language-plaintext highlighter-rouge">binding.break</code>, but doesn’t activate the gem immediately.</p>

    <p>(This has been the default in newly generated Rails projects since Rails 7.2.)</p>
  </li>
  <li>
    <p>If you want to prevent <a href="https://github.com/ruby/debug">debug.gem</a> from being used in certain environments, you can set <code class="language-plaintext highlighter-rouge">RUBY_DEBUG_ENABLE</code> to <code class="language-plaintext highlighter-rouge">0</code>.</p>

    <p>For example, setting this on CI will make <code class="language-plaintext highlighter-rouge">debugger</code> or <code class="language-plaintext highlighter-rouge">binding.break</code> raise an error rather than hang indefinitely.</p>
  </li>
  <li>
    <p>You can configure <a href="https://github.com/ruby/debug">debug.gem</a> to ignore certain gems when debugging. For example:</p>

    <div class="language-rb highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">begin</span>
  <span class="c1"># Try to load debug, but only just the config component so we don't activate it by accident</span>
  <span class="nb">require</span> <span class="s2">"debug/config"</span>

  <span class="n">zeitwerk_paths</span> <span class="o">=</span> <span class="no">Gem</span><span class="p">.</span><span class="nf">loaded_specs</span><span class="p">[</span><span class="s2">"zeitwerk"</span><span class="p">].</span><span class="nf">full_require_paths</span><span class="p">.</span><span class="nf">freeze</span>
  <span class="n">bootsnap_paths</span> <span class="o">=</span> <span class="no">Gem</span><span class="p">.</span><span class="nf">loaded_specs</span><span class="p">[</span><span class="s2">"bootsnap"</span><span class="p">].</span><span class="nf">full_require_paths</span><span class="p">.</span><span class="nf">freeze</span>

  <span class="no">DEBUGGER__</span><span class="o">::</span><span class="no">CONFIG</span><span class="p">[</span><span class="ss">:skip_path</span><span class="p">]</span> <span class="o">=</span> <span class="no">Array</span><span class="p">(</span><span class="no">DEBUGGER__</span><span class="o">::</span><span class="no">CONFIG</span><span class="p">[</span><span class="ss">:skip_path</span><span class="p">])</span> <span class="o">+</span> <span class="n">zeitwerk_paths</span> <span class="o">+</span> <span class="n">bootsnap_paths</span>
<span class="k">rescue</span> <span class="no">LoadError</span>
  <span class="c1"># In case debug.gem is not installed for any reason</span>
  <span class="c1"># Such as when this file being loaded from production where debug.gem is usually not installed</span>
<span class="k">end</span>
</code></pre></div>    </div>

    <ul>
      <li>
        <p>If you use <code class="language-plaintext highlighter-rouge">Sorbet</code>, you can add <code class="language-plaintext highlighter-rouge">sorbet-runtime</code> to the ignore list too:</p>

        <div class="language-rb highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">sorbet_paths</span> <span class="o">=</span> <span class="no">Gem</span><span class="p">.</span><span class="nf">loaded_specs</span><span class="p">[</span><span class="s2">"sorbet-runtime"</span><span class="p">].</span><span class="nf">full_require_paths</span><span class="p">.</span><span class="nf">freeze</span>
<span class="no">DEBUGGER__</span><span class="o">::</span><span class="no">CONFIG</span><span class="p">[</span><span class="ss">:skip_path</span><span class="p">]</span> <span class="o">=</span> <span class="no">Array</span><span class="p">(</span><span class="no">DEBUGGER__</span><span class="o">::</span><span class="no">CONFIG</span><span class="p">[</span><span class="ss">:skip_path</span><span class="p">])</span> <span class="o">+</span> <span class="n">sorbet_paths</span>
</code></pre></div>        </div>
      </li>
    </ul>
  </li>
  <li>For most users, I recommend activating <a href="https://github.com/ruby/debug">debug.gem</a>’s integration with <a href="https://ruby.github.io/irb">IRB</a> for a <a href="https://ruby.github.io/irb/#label-Advantages+Over+debug.gem-27s+Console">better debugging experience</a>. You can do this by:
    <ul>
      <li>Setting <code class="language-plaintext highlighter-rouge">RUBY_DEBUG_IRB_CONSOLE</code> to <code class="language-plaintext highlighter-rouge">1</code></li>
      <li>Setting <code class="language-plaintext highlighter-rouge">DEBUGGER__::CONFIG[:irb_console]</code> to <code class="language-plaintext highlighter-rouge">true</code></li>
    </ul>
  </li>
  <li>The following <a href="https://github.com/ruby/debug">debug.gem</a> commands will repeat when hitting <code class="language-plaintext highlighter-rouge">enter</code>:
    <ul>
      <li><code class="language-plaintext highlighter-rouge">step</code> (<code class="language-plaintext highlighter-rouge">s</code>)</li>
      <li><code class="language-plaintext highlighter-rouge">next</code> (<code class="language-plaintext highlighter-rouge">n</code>)</li>
      <li><code class="language-plaintext highlighter-rouge">continue</code> (<code class="language-plaintext highlighter-rouge">c</code>)</li>
      <li><code class="language-plaintext highlighter-rouge">finish</code> (<code class="language-plaintext highlighter-rouge">fin</code>)</li>
      <li><code class="language-plaintext highlighter-rouge">until</code> (<code class="language-plaintext highlighter-rouge">u</code>)</li>
      <li><code class="language-plaintext highlighter-rouge">up</code></li>
      <li><code class="language-plaintext highlighter-rouge">down</code></li>
    </ul>

    <p>For example, if you type <code class="language-plaintext highlighter-rouge">s</code> + <code class="language-plaintext highlighter-rouge">enter</code> and then hit <code class="language-plaintext highlighter-rouge">enter</code> again, it’ll repeat the <code class="language-plaintext highlighter-rouge">step</code> command.</p>
  </li>
  <li>
    <p>You can use <code class="language-plaintext highlighter-rouge">debugger(do: "...")</code> or <code class="language-plaintext highlighter-rouge">debugger(pre: "...")</code> to automatically execute a command after a breakpoint is hit:</p>

    <div class="language-rb highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1"># This will print the local variables and open the console</span>
<span class="n">debugger</span><span class="p">(</span><span class="ss">pre: </span><span class="s2">"info locals"</span><span class="p">)</span>
<span class="c1"># This will print the local variables and continue the program</span>
<span class="n">debugger</span><span class="p">(</span><span class="ss">do: </span><span class="s2">"info locals"</span><span class="p">)</span>
</code></pre></div>    </div>
  </li>
  <li>The combination of <code class="language-plaintext highlighter-rouge">trace exception</code> and <code class="language-plaintext highlighter-rouge">catch [exception]</code> commands can make debugging control-flow related bugs easier:
    <ul>
      <li><code class="language-plaintext highlighter-rouge">trace exception</code> will print traces when an exception is raised</li>
      <li><code class="language-plaintext highlighter-rouge">catch [exception]</code> will break when the exception is raised</li>
    </ul>
  </li>
  <li>
    <p>Using the combination of <code class="language-plaintext highlighter-rouge">bt [n]</code> and <code class="language-plaintext highlighter-rouge">up</code>/<code class="language-plaintext highlighter-rouge">down</code> commands is often more effective than setting multiple breakpoints on the same code path.</p>
  </li>
  <li>
    <p>For more fine-grained tracing, you can use the <a href="https://github.com/ruby/tracer">tracer gem</a>.</p>
  </li>
  <li>
    <p><a href="https://github.com/ruby/debug">debug.gem</a> freezes all running threads when it enters a breakpoint. If this causes issues, use <code class="language-plaintext highlighter-rouge">binding.irb</code> as an alternative.</p>
  </li>
  <li>You can use <code class="language-plaintext highlighter-rouge">binding.irb</code> for light debugging to open a REPL, and then activate <a href="https://github.com/ruby/debug">debug.gem</a> with its <code class="language-plaintext highlighter-rouge">debug</code> command.</li>
  <li>For learning more fundamental debugging concepts, I think <a href="https://www.youtube.com/watch?v=gseo4vdmSjE">my talk at RubyKaigi 2022</a> should still be helpful.</li>
</ul>

<p>I hope you find these tips useful. Happy debugging!</p>]]></content><author><name>Stan Lo</name></author><category term="ruby," /><category term="ruby-on-rails," /><category term="debugging," /><category term="debug," /><category term="vscode" /><summary type="html"><![CDATA[This is a quick &amp; unpolished collection of my Ruby debugging tips and recommendations.]]></summary></entry><entry><title type="html">Ruby 3.4 Documentation: A Step Towards Better Ruby Documentation</title><link href="https://st0012.dev/2024/12/26/ruby-3-4-docs/" rel="alternate" type="text/html" title="Ruby 3.4 Documentation: A Step Towards Better Ruby Documentation" /><published>2024-12-26T00:00:00+00:00</published><updated>2024-12-26T00:00:00+00:00</updated><id>https://st0012.dev/2024/12/26/ruby-3-4-docs</id><content type="html" xml:base="https://st0012.dev/2024/12/26/ruby-3-4-docs/"><![CDATA[<h2 id="introduction">Introduction</h2>

<p><a href="https://docs.ruby-lang.org/en/3.4/NEWS_md.html">Ruby 3.4</a> isn’t just about shiny language features; it also comes with meaningful documentation updates.
Some of these changes are reflected in the content of <a href="https://docs.ruby-lang.org/en/3.4/">docs.ruby-lang.org</a>, while others are behind the scenes in <a href="https://github.com/ruby/rdoc">RDoc</a>, the official documentation generator for Ruby.</p>

<p>Documentation shapes our day-to-day experience with Ruby, as well as the first impression for newcomers. A well-structured, easy-to-read reference can:</p>

<ul>
  <li>Decrease confusion for newcomers.</li>
  <li>Make day-to-day Ruby development easier.</li>
  <li>Encourage more community contributions.</li>
</ul>

<p>From my previous post <a href="https://st0012.dev/a-rdoc-maintainer-s-view-on-ruby-s-documentation">“A RDoc Maintainer’s View on Ruby’s Documentation”</a>, I shared that there are many aspects around Ruby’s documentation that can be improved. So I want to use this post to summarize the improvements in Ruby 3.4 as the first step towards better Ruby documentation in the future.</p>

<p>I also welcome you to read the <a href="https://docs.ruby-lang.org/en/3.4/">Ruby 3.4’s Documentation</a> and <a href="https://docs.ruby-lang.org/en/3.3/">Ruby 3.3’s Documentation</a> to see the improvements.</p>

<h2 id="major-improvements-in-ruby-34-docs">Major Improvements in <a href="https://docs.ruby-lang.org/en/3.4/">Ruby 3.4 Docs</a></h2>

<h3 id="1-expanded-content--indexing">1. Expanded Content &amp; Indexing</h3>

<p>As with any Ruby release, Ruby 3.4’s documentation received many fixes and improvements from Ruby committers and the community. But I’d like to highlight some changes that are unique to this release.</p>

<ul>
  <li>
    <p>A dedicated index page helps readers jump to frequently referenced classes faster (e.g., Array, String).</p>

    <p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1735218675525/4uQnNhizn.png?auto=format" alt="Ruby 3.4's doc index page" width="40%" /></p>
  </li>
  <li>The revamped <a href="https://docs.ruby-lang.org/en/3.4/standard_library_md.html">Standard Library page</a> makes navigating to related documentation and GitHub repositories easier.</li>
  <li>
    <p>More than 50 dead links have been fixed with a <a href="https://github.com/ruby/rdoc/pull/1241">new RDoc feature</a>.</p>

    <p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1735218997622/TVbeMVUgN.png?auto=format" alt="Dead links fixed" width="40%" /></p>
  </li>
</ul>

<h3 id="2-revamped-theme--navigation">2. Revamped Theme &amp; Navigation</h3>

<ul>
  <li>
    <p>The default theme for <a href="https://docs.ruby-lang.org/en/3.4/">Ruby 3.4’s documentation</a> is more mobile-friendly, thanks to community-driven CSS upgrades in RDoc. Below is a comparison between <a href="https://docs.ruby-lang.org/en/3.3/">Ruby 3.3 documentation</a> and <a href="https://docs.ruby-lang.org/en/3.4/">Ruby 3.4’s doc</a> on iPhone.</p>

    <p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1735218469826/pt76DDGR0.png?auto=format&amp;width=400 align=&quot;center&quot;" alt="Ruby 3.3's doc on desktop" /></p>

    <p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1735218431628/qqDSQ0Cpe.png?auto=format&amp;width=400 align=&quot;center&quot;" alt="Ruby 3.4's doc on iPhone" /></p>
  </li>
  <li>The color contrast, font size, and text readability have been improved.</li>
  <li>
    <p>Class pages now feature an ancestors list (<a href="https://docs.ruby-lang.org/en/3.4/RuntimeError.html">example</a>).</p>

    <p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1735224703926/F2_O2l8Ph.png?auto=format" alt="Ancestors list in Ruby 3.4" width="50%" /></p>
  </li>
  <li>
    <p>Source code is displayed more neatly.</p>

    <p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1735224648221/2b7eT-5Bg.png?auto=format" alt="Source code display in Ruby 3.4" width="50%" /></p>
  </li>
  <li>
    <p>Direct method-linking means sharing docs with specific anchors is much simpler.</p>

    <p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1735219316742/pMAQ_QJl0.gif?auto=format" alt="Direct method-linking" width="50%" /></p>
  </li>
</ul>

<h3 id="3-ri-improvements">3. <code class="language-plaintext highlighter-rouge">ri</code> Improvements</h3>

<p>The <a href="https://github.com/ruby/rdoc/pull/1141">rdoc-ref expansion</a> feature now lets “ri” automatically expand and link out to relevant information, instead of just displaying the <code class="language-plaintext highlighter-rouge">rdoc-ref</code> reference.</p>

<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1735224785209/D8XcqOjml.png?auto=format" alt="ri improvements" width="50%" /></p>

<h2 id="ongoing--future-work">Ongoing &amp; Future Work</h2>

<p>As I mentioned earlier, this year’s documentation improvements are just the beginning. Even with all these changes, these are some of the ideas I’d like to explore in Ruby 3.5:</p>

<ol>
  <li>Reorganizing top-level pages (see <a href="https://github.com/ruby/ruby/pull/12226">PR #12226</a>) so that non-API documents (e.g., syntax introduction, implicit conversions, etc.) become easier to find.</li>
  <li>
    <p>Marking default gems on <code class="language-plaintext highlighter-rouge">docs.ruby-lang.org/en</code> so developers can clearly see what’s part of core Ruby vs. what’s part of default gems.</p>

    <p>For example, <a href="https://docs.ruby-lang.org/en/3.4/ERB.html">ERB’s documentation page</a> should have a badge/indicator saying it’s from the <a href="https://github.com/ruby/erb">erb</a> gem and potentially link to the gem’s repository.</p>
  </li>
  <li>Applying the latest RDoc features to all Ruby versions’ documentation.</li>
  <li>Providing an easier way to switch between different Ruby versions’ documentation.</li>
</ol>

<p>And other ideas that I listed in <a href="https://st0012.dev/a-rdoc-maintainer-s-view-on-ruby-s-documentation">“A RDoc Maintainer’s View on Ruby’s Documentation”</a>.</p>

<p><strong>Note:</strong> Because Ruby 3.4 has been released, future documentation improvements will mostly be reflected on <a href="https://docs.ruby-lang.org/en/master/">https://docs.ruby-lang.org/en/master/</a> from now on.</p>

<h2 id="community-shout-out">Community Shout-Out</h2>

<p>We owe these updates to many RDoc contributors. A huge thanks to everyone who contributed to RDoc since Ruby 3.3 was released:</p>

<p><a href="https://github.com/adam12">@adam12</a>, <a href="https://github.com/alexisbernard">@alexisbernard</a>, <a href="https://github.com/antoinem">@antoinem</a>, <a href="https://github.com/BurdetteLamar">@BurdetteLamar</a>, <a href="https://github.com/deivid-rodriguez">@deivid-rodriguez</a>, <a href="https://github.com/earlopain">@earlopain</a>, <a href="https://github.com/eregon">@eregon</a>, <a href="https://github.com/flavorjones">@flavorjones</a>, <a href="https://github.com/hsbt">@hsbt</a>, <a href="https://github.com/ishe-ua">@ishe-ua</a>, <a href="https://github.com/MatheusRich">@MatheusRich</a>, <a href="https://github.com/mterada1228">@mterada1228</a>, <a href="https://github.com/nevans">@nevans</a>, <a href="https://github.com/nobu">@nobu</a>, <a href="https://github.com/okuramasafumi">@okuramasafumi</a>, <a href="https://github.com/omegahm">@omegahm</a>, <a href="https://github.com/p8">@p8</a>, <a href="https://github.com/paracycle">@paracycle</a>, <a href="https://github.com/sambostock">@sambostock</a>, <a href="https://github.com/skipkayhil">@skipkayhil</a>, <a href="https://github.com/soutaro">@soutaro</a>, <a href="https://github.com/st0012">@st0012</a>, <a href="https://github.com/sunblaze">@sunblaze</a>, <a href="https://github.com/tompng">@tompng</a>, <a href="https://github.com/toshimaru">@toshimaru</a>, <a href="https://github.com/vinistock">@vinistock</a>, <a href="https://github.com/ydah">@ydah</a></p>

<h2 id="conclusion">Conclusion</h2>

<p>As a community, we should continue to improve the documentation to make Ruby more welcoming, informative, and easy to navigate, so Rubyists can stay happy even when looking for documentation ;-)</p>

<p>If you’d like to contribute to the documentation, here are some resources:</p>

<ul>
  <li><a href="https://docs.ruby-lang.org/en/3.4/contributing/documentation_guide_md.html">Ruby’s Documentation Contribution Guide</a>
    <ul>
      <li>You can also contribute to this guide itself!</li>
    </ul>
  </li>
  <li><a href="https://github.com/ruby/rdoc/discussions">RDoc’s GitHub Discussions</a></li>
  <li><a href="https://github.com/ruby/rdoc/issues">RDoc’s GitHub Issues</a></li>
</ul>

<p>Let’s keep the momentum going and make Ruby’s documentation the best it can be!</p>]]></content><author><name>Stan Lo</name></author><category term="ruby," /><category term="documentation," /><category term="rdoc," /><category term="ruby-3.4" /><summary type="html"><![CDATA[Introduction]]></summary></entry><entry><title type="html">A RDoc Maintainer’s View on Ruby’s Documentation</title><link href="https://st0012.dev/2024/11/02/a-rdoc-maintainer-s-view-on-ruby-s-documentation/" rel="alternate" type="text/html" title="A RDoc Maintainer’s View on Ruby’s Documentation" /><published>2024-11-02T00:00:00+00:00</published><updated>2024-11-02T00:00:00+00:00</updated><id>https://st0012.dev/2024/11/02/a-rdoc-maintainer-s-view-on-ruby-s-documentation</id><content type="html" xml:base="https://st0012.dev/2024/11/02/a-rdoc-maintainer-s-view-on-ruby-s-documentation/"><![CDATA[<p>As someone who genuinely cares about Ruby’s developer experience, I’ve been thinking about Ruby’s documentation for a while, especially after I became a maintainer of <a href="https://github.com/ruby/rdoc">RDoc</a>.</p>

<p>And I’d like to share my thoughts on the current state of Ruby’s documentation and how we can improve it.</p>

<p><strong>Note:</strong> The contents of this article are my personal opinions and do not represent the opinions of other RDoc maintainers
or the Ruby core team. The actual roadmap of RDoc and related projects will be decided by the teams responsible for them,
so please don’t use this article as the official roadmap.</p>

<h2 id="table-of-contents">Table of Contents</h2>

<ul>
  <li><a href="#heading-rdoc-the-tool-the-markup-and-docsruby-langorg">RDoc: The Tool, The Markup, and docs.ruby-lang.org</a>
    <ul>
      <li><a href="#heading-english-version">English Version</a></li>
      <li><a href="#heading-japanese-version">Japanese Version</a></li>
    </ul>
  </li>
  <li><a href="#heading-top-3-things-i-want-to-improve">Top 3 Things I Want to Improve</a>
    <ul>
      <li><a href="#heading-1-incrementally-improve-rdocs-default-theme">1. Incrementally Improve RDoc’s Default Theme</a>
        <ul>
          <li><a href="#heading-apply-improvements-to-all-actively-maintained-ruby-versions">Apply Improvements to All Actively Maintained Ruby Versions</a></li>
        </ul>
      </li>
      <li><a href="#heading-2-move-away-from-rdoc-markup-language-to-markdown">2. Move Away from RDoc Markup Language to Markdown</a></li>
      <li><a href="#heading-3-improving-rubys-english-documentation-website">3. Improving Ruby’s English Documentation Website</a></li>
      <li><a href="#heading-other-improvements-i-want-to-make">Other Improvements I Want to Make</a></li>
    </ul>
  </li>
  <li><a href="#heading-final-thoughts">Final Thoughts</a></li>
</ul>

<h2 id="rdoc-the-tool-the-markup-and-docsruby-langorg">RDoc: The Tool, The Markup, and docs.ruby-lang.org</h2>

<p>Before we talk about documentation in the broader sense, let’s discuss RDoc first.</p>

<p>When we mention “RDoc,” we might refer to two different things:</p>

<ul>
  <li><strong>RDoc as a markup language</strong> to write documentation (<a href="https://github.com/ruby/rdoc/blob/master/ExampleRDoc.rdoc">example source</a> and <a href="https://ruby.github.io/rdoc/ExampleRDoc_rdoc.html">its output</a>). You can write it as comments in Ruby and C source code or as a separate file with a <code class="language-plaintext highlighter-rouge">.rdoc</code> extension.</li>
  <li><strong>RDoc as a tool (gem)</strong> to generate documentation from Ruby or C source code (<a href="https://github.com/ruby/rdoc">link</a>), which supports both <code class="language-plaintext highlighter-rouge">Markdown</code> and <code class="language-plaintext highlighter-rouge">RDoc</code> markup languages.</li>
</ul>

<p>Ruby’s documentation website, <a href="https://docs.ruby-lang.org">docs.ruby-lang.org</a>, is divided into two parts:</p>

<ul>
  <li>https://docs.ruby-lang.org/en/ - English version</li>
  <li>https://docs.ruby-lang.org/ja/ - Japanese version</li>
</ul>

<h3 id="english-version">English Version</h3>

<p>The English version is generated from the Ruby source code using the version of RDoc bundled with it. For example:</p>

<ul>
  <li><a href="https://docs.ruby-lang.org/en/master">Master</a> is generated from the <code class="language-plaintext highlighter-rouge">ruby/ruby</code> master branch, with the <code class="language-plaintext highlighter-rouge">ruby/rdoc</code> master branch (since <code class="language-plaintext highlighter-rouge">ruby/rdoc</code> is synced to <code class="language-plaintext highlighter-rouge">ruby/ruby</code>).</li>
  <li><a href="https://docs.ruby-lang.org/en/3.3">Ruby 3.3</a> is generated from the <a href="https://github.com/ruby/ruby/tree/ruby_3_3">ruby_3_3 branch</a> of <code class="language-plaintext highlighter-rouge">ruby/ruby</code>, with the latest <code class="language-plaintext highlighter-rouge">ruby/rdoc</code> <a href="https://github.com/ruby/ruby/commit/eb7cb164cffc86b63d2e2528c73e160c33b7a2e5">commit synced to it</a>.</li>
  <li><a href="https://docs.ruby-lang.org/en/3.2">Ruby 3.2</a> is generated from the <a href="https://github.com/ruby/ruby/tree/ruby_3_2">ruby_3_2 branch</a> of <code class="language-plaintext highlighter-rouge">ruby/ruby</code>, with the latest <code class="language-plaintext highlighter-rouge">ruby/rdoc</code> <a href="https://github.com/ruby/ruby/commit/d5dbada8a2127d9b6b670dd891eabbb63c48268f">commit synced to it</a>.</li>
</ul>

<p>Different Ruby versions not only have different documentation content but could also have different themes since the English version is generated from the Ruby source code with the bundled RDoc version.</p>

<h3 id="japanese-version">Japanese Version</h3>

<p>The Japanese version is maintained separately in <a href="https://github.com/rurema/doctree">rurema/doctree</a>, handling both the theme and content independently.</p>

<h2 id="top-3-things-i-want-to-improve">Top 3 Things I Want to Improve</h2>

<p>There are many aspects of Ruby’s documentation that can be enhanced. To keep it concise, I’ll focus on the top three:</p>

<ol>
  <li><strong>Incrementally Improve RDoc’s Default Theme</strong></li>
  <li><strong>Move Away from RDoc Markup Language to Markdown</strong></li>
  <li><strong>Improving Ruby’s English Documentation Website</strong></li>
</ol>

<p>I’ll also touch on other improvements without going into detail. If I haven’t mentioned something, it doesn’t mean it’s unimportant—it might just need more thought.</p>

<h3 id="1-incrementally-improve-rdocs-default-theme">1. Incrementally Improve RDoc’s Default Theme</h3>

<p>(And therefore, <code class="language-plaintext highlighter-rouge">docs.ruby-lang.org/en/master</code>’s theme)</p>

<p>A project’s official documentation is often the first thing users see, shaping their initial impression. It should be:</p>

<ul>
  <li><strong>Easy to read</strong></li>
  <li><strong>Easy to navigate</strong></li>
  <li><strong>Aesthetically pleasing</strong></li>
</ul>

<p>Over the past few months, I’ve collaborated with community members to gradually enhance RDoc’s default theme. Comparing <a href="https://docs.ruby-lang.org/en/master">latest</a> with <a href="https://docs.ruby-lang.org/en/3.3">Ruby 3.3</a>, there should be a noticeable difference. However, there’s still room for improvement:</p>

<ul>
  <li><strong>Better navigation features</strong> like breadcrumbs</li>
  <li><strong>Enhanced search results</strong></li>
  <li><strong>Improved SEO optimization</strong></li>
  <li><strong>Support for dark mode</strong></li>
</ul>

<p>Have ideas or suggestions? Please open an issue or a pull request on <a href="https://github.com/ruby/rdoc">RDoc’s GitHub repository</a>.</p>

<h4 id="apply-improvements-to-all-actively-maintained-ruby-versions">Apply Improvements to All Actively Maintained Ruby Versions</h4>

<p>Beyond enhancing the theme, we should also upgrade the infrastructure to apply these changes to <strong>all actively maintained Ruby versions</strong> (<a href="https://github.com/ruby/docs.ruby-lang.org/issues/153">issue</a>). This ensures that improvements benefit most users, even those on older Ruby versions.</p>

<h3 id="2-move-away-from-rdoc-markup-language-to-markdown">2. Move Away from RDoc Markup Language to Markdown</h3>

<p>In a world where Markdown is the de facto standard, it’s time for Ruby to transition from RDoc markup language.</p>

<p><strong>Benefits of Switching to Markdown:</strong></p>

<ul>
  <li><strong>Ease for developers</strong>: No need to learn a new markup language.</li>
  <li><strong>Better compatibility</strong>: Markdown files render properly on many platforms (GitHub, GitLab, etc.), whereas <code class="language-plaintext highlighter-rouge">.rdoc</code> files often don’t (<a href="https://github.com/ruby/rdoc/blob/master/ExampleRDoc.rdoc">example</a>).</li>
  <li><strong>Editor support</strong>: The <a href="https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/">Language Server Protocol</a> only supports plain text and Markdown, requiring RDoc markup to be translated if tools want to display it.</li>
</ul>

<p><strong>Challenges:</strong></p>

<ol>
  <li><strong>Enhance RDoc’s Markdown parser</strong> to support the latest Markdown syntax:
    <ul>
      <li>The current parser is <a href="https://github.com/ruby/rdoc/blob/master/lib/rdoc/markdown.kpeg">generated</a> via <a href="https://github.com/evanphx/kpeg">kpeg</a>, making it hard to maintain and update. This also leads to issues like <a href="https://github.com/ruby/rdoc/issues/1107">this</a>.</li>
      <li>Using parser gems like <a href="https://github.com/vmg/redcarpet">redcarpet</a> isn’t feasible since RDoc, as a default gem, <strong>can’t depend on external libraries</strong>. Alternatives include implementing the parser ourselves or vendoring dependencies without affecting Ruby’s distribution.</li>
    </ul>
  </li>
  <li><strong>Support RDoc directives in Markdown</strong>.</li>
  <li><strong>Provide a conversion tool</strong> from RDoc markup to Markdown:
    <ul>
      <li>Rails has a tool: <a href="https://github.com/rails/rails/blob/main/tools/rdoc-to-md">rdoc-to-md</a>, which we can collaborate with to improve.</li>
      <li>We need a similar tool for C source code.</li>
    </ul>
  </li>
</ol>

<p>The goal is to let users write documentation in Markdown with the same functionality as RDoc, making it easier to write and maintain documentation through the RDoc tool.</p>

<h3 id="3-improving-rubys-english-documentation-website">3. Improving Ruby’s English Documentation Website</h3>

<p>Enhancing Ruby’s English documentation website involves addressing several key issues across different projects:</p>

<ul>
  <li><strong>Poor SEO:</strong> The official website often doesn’t appear prominently (or at all) in search results, making it harder for developers to find the documentation they need.
    <ul>
      <li>Additionally, we may embrace standards like <a href="https://llmstxt.org/">llms.txt</a> to improve large language models’ ability to understand the content and thus improve their understanding of Ruby.</li>
    </ul>
  </li>
  <li><strong>Unhelpful Front Page:</strong> Currently, the front page displays the project’s readme, which isn’t helpful for most users looking for documentation. Ideally, it should provide a good index of the commonly used documentation.</li>
  <li><strong>Content Selection and Organization:</strong> The documentation content should be more curated and better organized to facilitate easier navigation and discovery of information.</li>
</ul>

<h3 id="other-improvements-i-want-to-make">Other Improvements I Want to Make</h3>

<p>While the top three are crucial, there are additional areas to enhance:</p>

<ul>
  <li><strong>Display RBS signatures in documentation</strong></li>
  <li><strong>Add server mode to RDoc</strong> (<a href="https://github.com/ruby/rdoc/pull/1151">PR</a>)</li>
  <li><strong>Improve RDoc’s own documentation</strong></li>
  <li><strong>Migrate RDoc’s parser to use Prism instead of Ripper</strong> (@tompng has made significant progress on this)</li>
  <li><strong>Ensure the content and links on <a href="https://www.ruby-lang.org/en/">ruby-lang.org/en</a> remain current and valuable</strong></li>
</ul>

<h2 id="final-thoughts">Final Thoughts</h2>

<p>These proposed enhancements are essential for modernizing Ruby’s documentation system. By transitioning to Markdown, improving SEO, and upgrading the infrastructure to support all actively maintained Ruby versions, we can significantly enhance the accessibility and usability of the documentation.</p>

<p>Achieving these goals will require increased investment and active collaboration from the community. I’ve dedicated a lot of time to these issues, and I encourage all contributors to participate in these initiatives to ensure that Ruby’s documentation remains current and valuable for all users.</p>]]></content><author><name>Stan Lo</name></author><category term="ruby," /><category term="documentation," /><category term="rdoc" /><summary type="html"><![CDATA[As someone who genuinely cares about Ruby’s developer experience, I’ve been thinking about Ruby’s documentation for a while, especially after I became a maintainer of RDoc.]]></summary></entry></feed>