<?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-03-08T13:35:26+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><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>
      95
    </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>
      3
    </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><entry><title type="html">What’s new in Ruby 3.2’s IRB?</title><link href="https://st0012.dev/2022/12/09/whats-new-in-ruby-3-2-irb/" rel="alternate" type="text/html" title="What’s new in Ruby 3.2’s IRB?" /><published>2022-12-09T00:00:00+00:00</published><updated>2022-12-09T00:00:00+00:00</updated><id>https://st0012.dev/2022/12/09/whats-new-in-ruby-3-2-irb</id><content type="html" xml:base="https://st0012.dev/2022/12/09/whats-new-in-ruby-3-2-irb/"><![CDATA[<h1 id="whats-new-in-ruby-32s-irb">What’s new in Ruby 3.2’s IRB?</h1>

<p>IRB <code class="language-plaintext highlighter-rouge">1.6</code> has been released and will become Ruby 3.2’s built-in IRB version.</p>

<p>It and a few recent releases include many enhancements <a href="https://twitter.com/k0kubun">@k0kubun</a> and I made, and I want to introduce them in this article:</p>

<h2 id="new-commands">New Commands</h2>

<p>In recent releases, we added a bunch of new commands to IRB:</p>

<ul>
  <li><code class="language-plaintext highlighter-rouge">show_cmds</code></li>
  <li><code class="language-plaintext highlighter-rouge">show_doc</code></li>
  <li><code class="language-plaintext highlighter-rouge">edit</code></li>
  <li><code class="language-plaintext highlighter-rouge">debug</code> and other debugging commands:
    <ul>
      <li><code class="language-plaintext highlighter-rouge">break</code></li>
      <li><code class="language-plaintext highlighter-rouge">catch</code></li>
      <li><code class="language-plaintext highlighter-rouge">next</code></li>
      <li><code class="language-plaintext highlighter-rouge">delete</code></li>
      <li><code class="language-plaintext highlighter-rouge">step</code></li>
      <li><code class="language-plaintext highlighter-rouge">continue</code></li>
      <li><code class="language-plaintext highlighter-rouge">finish</code></li>
      <li><code class="language-plaintext highlighter-rouge">backtrace</code></li>
      <li><code class="language-plaintext highlighter-rouge">info</code></li>
    </ul>
  </li>
</ul>

<p>The number of new commands may look intimidating, so let me introduce them one by one:</p>

<h3 id="show_cmds"><code class="language-plaintext highlighter-rouge">show_cmds</code></h3>

<p>This command prints all available IRB commands with their description. It’s similar to <code class="language-plaintext highlighter-rouge">Pry</code>, <code class="language-plaintext highlighter-rouge">Byebug</code>, and <code class="language-plaintext highlighter-rouge">debug</code>’s <code class="language-plaintext highlighter-rouge">help</code> command.</p>

<p>(It’s not named <code class="language-plaintext highlighter-rouge">help</code> because IRB already uses <code class="language-plaintext highlighter-rouge">help</code> to look up API documents. I’ll explain more about this in the next section.)</p>

<p>Output of <code class="language-plaintext highlighter-rouge">show_cmds</code> in <code class="language-plaintext highlighter-rouge">v1.6</code>:</p>

<pre><code class="language-txt">IRB
  cwws           Show the current workspace.
  chws           Change the current workspace to an object.
  workspaces     Show workspaces.
  pushws         Push an object to the workspace stack.
  popws          Pop a workspace from the workspace stack.
  irb_load       Load a Ruby file.
  irb_require    Require a Ruby file.
  source         Loads a given file in the current session.
  irb            Start a child IRB.
  jobs           List of current sessions.
  fg             Switches to the session of the given number.
  kill           Kills the session with the given number.
  irb_info       Show information about IRB.
  show_cmds      List all available commands and their description.

Debugging
  debug          Start the debugger of debug.gem.
  break          Start the debugger of debug.gem and run its `break` command.
  catch          Start the debugger of debug.gem and run its `catch` command.
  next           Start the debugger of debug.gem and run its `next` command.
  delete         Start the debugger of debug.gem and run its `delete` command.
  step           Start the debugger of debug.gem and run its `step` command.
  continue       Start the debugger of debug.gem and run its `continue` command.
  finish         Start the debugger of debug.gem and run its `finish` command.
  backtrace      Start the debugger of debug.gem and run its `backtrace` command.
  info           Start the debugger of debug.gem and run its `info` command.

Misc
  edit           Open a file with the editor command defined with `ENV["EDITOR"]`.
  measure        `measure` enables the mode to measure processing time. `measure :off` disables it.

Context
  show_doc       Enter the mode to look up RI documents.
  ls             Show methods, constants, and variables. `-g [query]` or `-G [query]` allows you to filter out the output.
  show_source    Show the source code of a given method or constant.
  whereami       Show the source code around binding.irb again.
</code></pre>

<h3 id="show_doc"><code class="language-plaintext highlighter-rouge">show_doc</code></h3>

<p><code class="language-plaintext highlighter-rouge">show_doc</code> is an alias to the <code class="language-plaintext highlighter-rouge">help</code> command.</p>

<p>As mentioned above, IRB’s <code class="language-plaintext highlighter-rouge">help</code> command is very different than its major counterparts’, which could be confusing or even annoying.</p>

<p>So we want to encourage users to use <code class="language-plaintext highlighter-rouge">show_doc</code> from <code class="language-plaintext highlighter-rouge">v1.6</code> onward. And we’ll convert <code class="language-plaintext highlighter-rouge">help</code> to displaying command information in the next major release.</p>

<p><strong>If you’ve never used <code class="language-plaintext highlighter-rouge">help</code> before, try <code class="language-plaintext highlighter-rouge">show_doc String#gsub</code> now :-)</strong></p>

<h3 id="edit"><code class="language-plaintext highlighter-rouge">edit</code></h3>

<p><code class="language-plaintext highlighter-rouge">edit</code> opens files in your editor (defined with <code class="language-plaintext highlighter-rouge">ENV["EDITOR"]</code>):</p>

<ul>
  <li><code class="language-plaintext highlighter-rouge">edit</code> - opens the current context’s file.</li>
  <li><code class="language-plaintext highlighter-rouge">edit path/to/foo.rb</code> - opens <code class="language-plaintext highlighter-rouge">foo.rb</code>.</li>
  <li><code class="language-plaintext highlighter-rouge">edit Foo</code> - Looks up <code class="language-plaintext highlighter-rouge">Foo</code>’s source location and opens it.</li>
  <li><code class="language-plaintext highlighter-rouge">edit Foo#bar</code> - Looks up <code class="language-plaintext highlighter-rouge">Foo#bar</code>’s source location and opens it.</li>
</ul>

<h4 id="demo">Demo</h4>

<p><img src="https://user-images.githubusercontent.com/5079556/207047097-473a547b-99ea-4142-a1aa-18ebcd5d208e.gif" alt="irb edit command" /></p>

<h3 id="debug-and-other-debugging-commands"><code class="language-plaintext highlighter-rouge">debug</code> and other debugging commands</h3>

<p>When you look at the output of <code class="language-plaintext highlighter-rouge">show_cmds</code>, you’ll notice a big <code class="language-plaintext highlighter-rouge">Debugging</code> section:</p>

<pre><code class="language-txt">Debugging
  debug          Start the debugger of debug.gem.
  break          Start the debugger of debug.gem and run its `break` command.
  catch          Start the debugger of debug.gem and run its `catch` command.
  next           Start the debugger of debug.gem and run its `next` command.
  delete         Start the debugger of debug.gem and run its `delete` command.
  step           Start the debugger of debug.gem and run its `step` command.
  continue       Start the debugger of debug.gem and run its `continue` command.
  finish         Start the debugger of debug.gem and run its `finish` command.
  backtrace      Start the debugger of debug.gem and run its `backtrace` command.
  info           Start the debugger of debug.gem and run its `info` command.
</code></pre>

<p>All of them were added recently to bridge IRB and the <a href="https://github.com/ruby/debug"><code class="language-plaintext highlighter-rouge">debug</code> gem</a>.</p>

<p>The <code class="language-plaintext highlighter-rouge">debug</code> command does 2 things:</p>

<ol>
  <li>If you haven’t required the <code class="language-plaintext highlighter-rouge">debug</code> gem in the current IRB session, the command would require it for you.</li>
  <li>It’ll then start a <code class="language-plaintext highlighter-rouge">debug</code> debugging session from the current context. It’s the same as you enter a <code class="language-plaintext highlighter-rouge">binding.b</code> breakpoint.</li>
</ol>

<pre><code class="language-txt">From: test.rb @ line 4 :

    1: a = 1
    2: b = 2
    3:
 =&gt; 4: binding.irb
    5:
    6: c = 3

irb(main):001:0&gt; debug
[1, 6] in test.rb
     1| a = 1
     2| b = 2
     3|
=&gt;   4| binding.irb
     5|
     6| c = 3
=&gt;#0    &lt;main&gt; at test.rb:4
(rdbg) # you're now using the debugger
</code></pre>

<p>(Note: it only works if the IRB session is started with <code class="language-plaintext highlighter-rouge">binding.irb</code> as it’s pointless debugging the <code class="language-plaintext highlighter-rouge">irb</code> executable.)</p>

<p>And the rest of debugging commands (<code class="language-plaintext highlighter-rouge">&lt;cmd&gt;</code>) are shortcuts of <code class="language-plaintext highlighter-rouge">debug</code> + <code class="language-plaintext highlighter-rouge">&lt;cmd&gt;</code>.</p>

<p>For example, we may <code class="language-plaintext highlighter-rouge">step</code> after running <code class="language-plaintext highlighter-rouge">debug</code>. In this case, you can just run <code class="language-plaintext highlighter-rouge">step</code> in IRB, and it will start the debugging session and then run <code class="language-plaintext highlighter-rouge">step</code>.</p>

<pre><code class="language-txt">From: test.rb @ line 1 :

 =&gt; 1: binding.irb
    2:
    3: a = 1
    4: b = 2
    5:

irb(main):001:0&gt; step
(rdbg:irb) step
[1, 4] in test.rb
     1| binding.irb
     2|
=&gt;   3| a = 1
     4| b = 2
=&gt;#0    &lt;main&gt; at test.rb:3
(rdbg)
</code></pre>

<p>The long-term goal is to make IRB an interface of the <code class="language-plaintext highlighter-rouge">debug</code> gem, like what <code class="language-plaintext highlighter-rouge">pry-byebug</code> provides. This means you will run
certain <code class="language-plaintext highlighter-rouge">debug</code> commands without leaving the IRB session.</p>

<p>But for now, we hope to make the scene transition easier with these command shortcuts.</p>

<h4 id="debugging-with-a-repl-bindingirb-vs-debugging-with-a-debugger-bindingb">Debugging with a REPL (<code class="language-plaintext highlighter-rouge">binding.irb</code>) V.S. Debugging with a debugger (<code class="language-plaintext highlighter-rouge">binding.b</code>)</h4>

<p>A REPL only gives you access to the breakpoint (<code class="language-plaintext highlighter-rouge">binding.irb</code>)’s surrounding context.</p>

<p>For example, if you put <code class="language-plaintext highlighter-rouge">binding.irb</code> inside a <code class="language-plaintext highlighter-rouge">bar</code> method, you can only see the locals available inside that method.</p>

<div class="language-rb highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">def</span> <span class="nf">foo</span><span class="p">(</span><span class="n">n</span><span class="p">)</span>
  <span class="n">bar</span><span class="p">(</span><span class="n">n</span> <span class="o">+</span> <span class="mi">1</span><span class="p">)</span>
<span class="k">end</span>

<span class="k">def</span> <span class="nf">bar</span><span class="p">(</span><span class="n">x</span><span class="p">)</span>
  <span class="nb">binding</span><span class="p">.</span><span class="nf">irb</span> <span class="c1"># you can only see x</span>
  <span class="n">baz</span><span class="p">(</span><span class="n">x</span> <span class="o">+</span> <span class="mi">10</span><span class="p">)</span>
<span class="k">end</span>
</code></pre></div></div>

<p>But if you’re inside a debugger, either through the <code class="language-plaintext highlighter-rouge">binding.b</code> breakpoint or the new <code class="language-plaintext highlighter-rouge">debug</code> command, you get the power to:</p>

<ul>
  <li>Move along with the program’s execution through commands like <code class="language-plaintext highlighter-rouge">step</code> or <code class="language-plaintext highlighter-rouge">next</code>.
    <ul>
      <li><code class="language-plaintext highlighter-rouge">step</code> will let you enter the <code class="language-plaintext highlighter-rouge">baz</code> method’s context.</li>
    </ul>
  </li>
  <li>Navigate between the frames on the current callstack with the <code class="language-plaintext highlighter-rouge">up</code> or <code class="language-plaintext highlighter-rouge">down</code> commands.
    <ul>
      <li>You can go back to <code class="language-plaintext highlighter-rouge">foo</code> and see the value of <code class="language-plaintext highlighter-rouge">n</code>.</li>
    </ul>
  </li>
  <li>Dynamically set breakpoints with the <code class="language-plaintext highlighter-rouge">break</code> or <code class="language-plaintext highlighter-rouge">catch</code> commands.</li>
</ul>

<p>To learn more about how to utilise a debugger, please watch my talk: <a href="https://assets.lrug.org/videos/2022/november/stan-lo-ruby-debug-the-best-investment-for-your-productivity-lrug-nov-2022.mp4">ruby/debug - The best investment for your productivity</a>.</p>

<h2 id="command-improvements">Command Improvements</h2>

<ul>
  <li><code class="language-plaintext highlighter-rouge">show_source</code> now supports Pry-like syntax: <code class="language-plaintext highlighter-rouge">show_source Class#method</code>.</li>
  <li>New symbol aliases:
    <ul>
      <li><code class="language-plaintext highlighter-rouge">$</code> is an alias to <code class="language-plaintext highlighter-rouge">show_source</code>.</li>
      <li><code class="language-plaintext highlighter-rouge">@</code> is an alias to <code class="language-plaintext highlighter-rouge">whereami</code>.</li>
      <li>You can add/modify aliases with <code class="language-plaintext highlighter-rouge">IRB.conf[:COMMAND_ALIASES]</code> in <code class="language-plaintext highlighter-rouge">.irbrc</code>.
        <ul>
          <li>For example, <code class="language-plaintext highlighter-rouge">IRB.conf[:COMMAND_ALIASES].merge!({ :'&amp;' =&gt; :show_cmds })</code> aliases <code class="language-plaintext highlighter-rouge">&amp;</code> to <code class="language-plaintext highlighter-rouge">show_cmds</code>.</li>
        </ul>
      </li>
    </ul>
  </li>
  <li><code class="language-plaintext highlighter-rouge">ls</code> now takes <code class="language-plaintext highlighter-rouge">-g</code>/<code class="language-plaintext highlighter-rouge">-G</code> argument for greping output with regexp.
    <ul>
      <li>For example, <code class="language-plaintext highlighter-rouge">ls -g irb</code> only prints methods that match <code class="language-plaintext highlighter-rouge">irb</code>.</li>
    </ul>
  </li>
</ul>

<h2 id="other-improvements">Other Improvements</h2>

<ul>
  <li>Several crashing issues have now been addressed.</li>
  <li>It now has a refreshed <a href="https://github.com/ruby/irb">README</a>, where you can see all available commands.</li>
</ul>

<h2 id="new-configurations">New Configurations</h2>

<ul>
  <li><code class="language-plaintext highlighter-rouge">ENV["EDITOR"]</code> - Will be used by the <code class="language-plaintext highlighter-rouge">edit</code> command to open files.</li>
  <li><code class="language-plaintext highlighter-rouge">ENV["IRB_USE_AUTOCOMPLETE"]</code> - When set to <code class="language-plaintext highlighter-rouge">"false"</code>, it’ll disable IRB’s autocompletion feature.
    <ul>
      <li>Rails <code class="language-plaintext highlighter-rouge">7.0.5</code> and <code class="language-plaintext highlighter-rouge">7.1+</code> will use this to disable autocompletion in production Rails console (<a href="https://github.com/rails/rails/pull/46656">PR</a>).</li>
    </ul>
  </li>
</ul>

<h2 id="wrapping-up">Wrapping Up</h2>

<p>We hope these changes can make IRB more convenient and helpful. If you can, please install the latest <code class="language-plaintext highlighter-rouge">1.6</code> version and give these new features a try.</p>

<p>When you see any problems, please <a href="https://github.com/ruby/irb/issues/new">open an issue</a>. We want to detect as many issues before Ruby 3.2 as possible.</p>

<p>I’ll keep posting news like this here too, but you’ll see a lot more content if you subscribe to the <a href="https://railsatscale.com/">Rails at Scale blog</a> ;-)</p>]]></content><author><name>Stan Lo</name></author><category term="ruby," /><category term="ruby-on-rails," /><category term="debugging," /><category term="irb" /><summary type="html"><![CDATA[What’s new in Ruby 3.2’s IRB?]]></summary></entry></feed>