# pi-ruby-lsp

Ruby LSP integration for [pi coding agent](https://pi.dev).

This package adds semantic Ruby tools to pi so the agent can ask Ruby LSP for diagnostics, symbols, definitions, hover text, references, formatting, rename edits, and code actions instead of relying only on grep/read/manual edits. Ruby LSP is started lazily, reused during the pi session, resolved per Ruby root for monorepos/engines, and stopped on session shutdown.

## Install

From npm:

```bash
pi install npm:@wiechsa/pi-ruby-lsp
```

Package page: https://www.npmjs.com/package/@wiechsa/pi-ruby-lsp

From a local checkout:

```bash
pi install ./pi-ruby-lsp
```

Project-local install:

```bash
pi install -l ./pi-ruby-lsp
```

Try without installing:

```bash
pi -e ./extensions/ruby-lsp/index.ts
```

## Requirements

The target Ruby project should expose Ruby LSP through Bundler:

```bash
bundle add ruby-lsp --group development
bundle exec ruby-lsp
```

or globally:

```bash
gem install ruby-lsp
ruby-lsp
```

## Startup timeout

Initialization defaults to 30 seconds. Override it if a large project needs more time:

```bash
PI_RUBY_LSP_INITIALIZE_TIMEOUT_MS=60000 pi -e ./extensions/ruby-lsp/index.ts
```

## Commands

```text
/ruby-lsp doctor
/ruby-lsp restart
/ruby-lsp stop
/ruby-lsp diagnostics app/models/user.rb
/ruby-lsp format app/models/user.rb
/ruby-lsp format --apply app/models/user.rb
/ruby-lsp rename app/models/user.rb 12 8 full_name
/ruby-lsp rename --apply app/models/user.rb 12 8 full_name
/ruby-lsp code-actions app/models/user.rb 12 8
/ruby-lsp code-actions --apply 0 app/models/user.rb 12 8
/ruby-lsp rails routes users
```

`doctor` reports the command used, selected root, candidate roots, lifecycle status, initialization/running state, project/Rails detection, LSP capabilities (formatting, rename, code actions), recent stderr, categorized startup/crash errors, and remediation suggestions.

Example doctor shape:

```json
{
  "root": "/repo/apps/shop",
  "command": ["bundle", "exec", "ruby-lsp"],
  "commandPlan": { "strategy": "bundler", "cwd": "/repo/apps/shop" },
  "project": { "kind": "rails-app", "rails": { "detected": true } },
  "status": "ready",
  "recommendations": []
}
```

Mutating commands use preview mode unless `--apply` is passed.

## Tools exposed to the agent

- `ruby_lsp_doctor` — check Ruby LSP availability and connection status
- `ruby_lsp_diagnostics` — get diagnostics for a Ruby file
- `ruby_lsp_symbols` — list classes, modules, methods, constants, etc. in a file
- `ruby_lsp_definition` — find definition at a zero-based line/character position
- `ruby_lsp_hover` — get hover/documentation at a zero-based line/character position
- `ruby_lsp_references` — find references at a zero-based line/character position
- `ruby_lsp_format` — preview or apply Ruby LSP formatting edits (`apply` defaults to `false`)
- `ruby_lsp_rename` — preview or apply LSP workspace edits for a rename at a zero-based line/character position
- `ruby_lsp_code_actions` — list code actions/quick fixes for a file or range; can apply an edit-based action by `actionIndex`
- `ruby_lsp_rails_routes` — list Rails routes using `bin/rails routes` or `bundle exec rails routes`

Prefer semantic navigation, rename, and code-action tools before grep/read/manual multi-file edits when you have an exact Ruby cursor position. Tool APIs use zero-based line and character positions.

## Formatting, rename, and code actions

Formatting, rename, and code actions all default to preview mode in tools and commands. Pass `apply: true` in tool calls or `--apply` in commands to write changes. Workspace edits are rejected if they target paths outside the current workspace. Edit-based code actions can be applied; command-only actions are reported as unsupported unless a future version explicitly allowlists them.

Examples:

```text
ruby_lsp_format({ "path": "app/models/user.rb" })
ruby_lsp_format({ "path": "app/models/user.rb", "apply": true })
ruby_lsp_rename({ "path": "app/models/user.rb", "line": 12, "character": 8, "newName": "full_name" })
ruby_lsp_code_actions({ "path": "app/models/user.rb", "startLine": 12, "startCharacter": 8 })
```

## Rails helpers

Rails helpers are available when common Rails markers are detected (`bin/rails`, `config/application.rb`, `config/routes.rb`, Rails gems, or engine markers like `lib/*/engine.rb`). `ruby_lsp_rails_routes` runs from the selected Rails root, prefers `bin/rails routes`, then `bundle exec rails routes`, and accepts an optional filter. Slow Rails boot is bounded by `PI_RUBY_LSP_RAILS_TIMEOUT_MS` (default: 15000ms). Rails boot failures include stderr and setup guidance.

## Auto-diagnostics

After a successful pi `edit` or `write` tool call to a Ruby-related file (`.rb`, `.rake`, `.gemspec`, `Gemfile`, `Rakefile`), or after applied LSP edits, the extension automatically refreshes Ruby LSP diagnostics in the background and updates the status line with a compact `errors, warnings, info, hints` summary. Non-Ruby file writes are ignored. If Ruby LSP is unavailable, the original tool result is not changed; use `/ruby-lsp doctor` and fall back to `ruby -c` or `rubocop` as needed.

## Troubleshooting

Common `doctor.errorInfo.code` values:

- `BUNDLER_MISSING` — install Bundler with `gem install bundler` or use `PI_RUBY_LSP_GLOBAL=1`.
- `BUNDLE_INSTALL_REQUIRED` — run `bundle install` from the reported root.
- `RUBY_VERSION_MISMATCH` — switch to the project's Ruby version and reinstall bundle gems.
- `RUBY_LSP_MISSING` — run `bundle add ruby-lsp --group development` or `gem install ruby-lsp`.
- `RUBY_LSP_TIMEOUT` — increase `PI_RUBY_LSP_INITIALIZE_TIMEOUT_MS` for large projects.
- `RAILS_BOOT_FAILED` — run the reported Rails command manually and fix app boot/setup errors.

## Development

```bash
npm install
npm run typecheck
npm run test:unit
npm run test:integration
npm run test:smoke
pi -e ./extensions/ruby-lsp/index.ts
```

Integration tests live behind the separate `test:integration` script. They include a mocked JSON-RPC LSP server, a real plain-Ruby project check when `ruby` and `ruby-lsp` are installed, and optional real Ruby/Rails project compatibility checks. To run the optional checks against local Ruby/Rails projects:

```bash
PI_RUBY_LSP_REAL_RUBY_PROJECT=/path/to/plain-ruby \
PI_RUBY_LSP_REAL_RAILS_PROJECT=/path/to/rails-app \
npm run test:integration
```

For a release matrix across Ruby/framework versions, provide JSON cases:

```bash
PI_RUBY_LSP_COMPAT_PROJECTS='[{"name":"ruby-3.3-plain","path":"/path/plain","expectedKind":"plain-ruby","expectedRubyVersionPrefix":"ruby 3.3"},{"name":"rails-7.1","path":"/path/rails","framework":"rails"}]' npm run test:integration
```

Then inside pi:

```text
/ruby-lsp doctor
```

## Security

Pi extensions run with the user's system permissions. Review code before installing third-party pi packages.
