rubyn-code 0.4.0 → 0.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (69) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +247 -9
  3. data/lib/rubyn_code/agent/conversation.rb +2 -1
  4. data/lib/rubyn_code/agent/dynamic_tool_schema.rb +2 -1
  5. data/lib/rubyn_code/agent/llm_caller.rb +4 -2
  6. data/lib/rubyn_code/agent/loop.rb +7 -3
  7. data/lib/rubyn_code/agent/response_modes.rb +2 -1
  8. data/lib/rubyn_code/agent/system_prompt_builder.rb +39 -0
  9. data/lib/rubyn_code/agent/tool_processor.rb +4 -2
  10. data/lib/rubyn_code/cli/app.rb +87 -13
  11. data/lib/rubyn_code/cli/commands/install_skills.rb +44 -0
  12. data/lib/rubyn_code/cli/commands/list_skills.rb +149 -0
  13. data/lib/rubyn_code/cli/commands/megaplan.rb +50 -0
  14. data/lib/rubyn_code/cli/commands/provider.rb +2 -1
  15. data/lib/rubyn_code/cli/commands/remove_skills.rb +35 -0
  16. data/lib/rubyn_code/cli/commands/skill.rb +4 -2
  17. data/lib/rubyn_code/cli/commands/skills.rb +104 -0
  18. data/lib/rubyn_code/cli/repl.rb +11 -1
  19. data/lib/rubyn_code/cli/repl_commands.rb +3 -1
  20. data/lib/rubyn_code/cli/repl_setup.rb +38 -1
  21. data/lib/rubyn_code/cli/setup.rb +13 -0
  22. data/lib/rubyn_code/config/defaults.rb +2 -0
  23. data/lib/rubyn_code/config/settings.rb +5 -2
  24. data/lib/rubyn_code/context/context_budget.rb +2 -1
  25. data/lib/rubyn_code/context/manager.rb +3 -3
  26. data/lib/rubyn_code/ide/handlers/plan_interview_answer_handler.rb +65 -0
  27. data/lib/rubyn_code/ide/handlers/plan_interview_cancel_handler.rb +22 -0
  28. data/lib/rubyn_code/ide/handlers/plan_interview_start_handler.rb +53 -0
  29. data/lib/rubyn_code/ide/handlers/plan_propose_handler.rb +41 -0
  30. data/lib/rubyn_code/ide/handlers/prompt_handler.rb +6 -3
  31. data/lib/rubyn_code/ide/handlers/recover_ci_handler.rb +132 -0
  32. data/lib/rubyn_code/ide/handlers/review_handler.rb +19 -2
  33. data/lib/rubyn_code/ide/handlers.rb +17 -2
  34. data/lib/rubyn_code/ide/protocol.rb +17 -1
  35. data/lib/rubyn_code/ide/server.rb +39 -1
  36. data/lib/rubyn_code/index/codebase_index.rb +2 -1
  37. data/lib/rubyn_code/learning/extractor.rb +4 -2
  38. data/lib/rubyn_code/llm/model_router.rb +2 -1
  39. data/lib/rubyn_code/mcp/tool_bridge.rb +1 -1
  40. data/lib/rubyn_code/megaplan/ci_recovery.rb +104 -0
  41. data/lib/rubyn_code/megaplan/interview_session.rb +245 -0
  42. data/lib/rubyn_code/megaplan/plan_proposer.rb +153 -0
  43. data/lib/rubyn_code/observability/usage_reporter.rb +4 -2
  44. data/lib/rubyn_code/output/diff_renderer.rb +3 -2
  45. data/lib/rubyn_code/self_test.rb +2 -1
  46. data/lib/rubyn_code/skills/auto_suggest.rb +131 -0
  47. data/lib/rubyn_code/skills/catalog.rb +10 -0
  48. data/lib/rubyn_code/skills/document.rb +8 -2
  49. data/lib/rubyn_code/skills/gemfile_parser.rb +40 -0
  50. data/lib/rubyn_code/skills/loader.rb +1 -1
  51. data/lib/rubyn_code/skills/matcher.rb +89 -0
  52. data/lib/rubyn_code/skills/pack_context.rb +163 -0
  53. data/lib/rubyn_code/skills/pack_installer.rb +194 -0
  54. data/lib/rubyn_code/skills/pack_manager.rb +230 -0
  55. data/lib/rubyn_code/skills/registry_autoload.rb +112 -0
  56. data/lib/rubyn_code/skills/registry_client.rb +241 -0
  57. data/lib/rubyn_code/tools/executor.rb +4 -2
  58. data/lib/rubyn_code/tools/grep.rb +2 -1
  59. data/lib/rubyn_code/tools/ide_diagnostics.rb +3 -1
  60. data/lib/rubyn_code/tools/ide_symbols.rb +3 -1
  61. data/lib/rubyn_code/tools/load_skill.rb +2 -1
  62. data/lib/rubyn_code/tools/output_compressor.rb +3 -6
  63. data/lib/rubyn_code/tools/review_pr.rb +15 -4
  64. data/lib/rubyn_code/tools/web_search.rb +2 -1
  65. data/lib/rubyn_code/version.rb +1 -1
  66. data/lib/rubyn_code.rb +20 -0
  67. data/skills/megaplan/megaplan.md +156 -0
  68. data/skills/rubyn_self_test.md +75 -0
  69. metadata +25 -4
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 1a3ef81b040a1c9a75050f2545f229ba69aa88808ced47ad5cc8634ea52f115c
4
- data.tar.gz: 9020382400cdf70d332858a9b85c89e5aa4a90e07965709e8520a803273d43b2
3
+ metadata.gz: b43dc2a8fab138bcfe303180881aa9fa23878faaa532367f07a0fee7605feb34
4
+ data.tar.gz: 12dffaa487a75dfe276be9d20ff8152814bd60c6b107efd9ed10d2993afd57a5
5
5
  SHA512:
6
- metadata.gz: d7c2b95f2eec7e20e589c377784a2236f5ca7fa62402012358137a895f18e899e521ab2a3280854dda2376d515d906a40ad56dcc9c21d65062da6c9549a18912
7
- data.tar.gz: cbb80d2749475054829c46aadd8ed5b7244d99f9d6ee05f73a0cd85618866e4e43881ae77e6718dbdcc6cced9e57222752b2a5c403185ae92a039a13da5105e1
6
+ metadata.gz: fcbe574a607028345e38587388a6c0bd9cdb8ab134e690d45583a1a27b4c698383bd43088eb8fa537cddba8690e77d15ab155be92fb997117c8fe62a978df211
7
+ data.tar.gz: 43e72475ec24edf03f21e4aafa4402de31351728a38de42e55b69db104aea1828b930a1cafb79d3065d07e16f84c8a5a5023311da5d1e40f7a667daa90ddee3c
data/README.md CHANGED
@@ -22,16 +22,56 @@ Refactor controllers, generate idiomatic RSpec, catch N+1 queries, review code f
22
22
 
23
23
  > **Rubyn is going open source.** The original [Rubyn gem](https://github.com/Rubyn-AI/rubyn) provided AI-assisted refactoring, spec generation, and code review through the Rubyn API. **Rubyn Code** is the next evolution — a complete agentic coding assistant that runs locally, thinks for itself, and learns from every session. No API keys. No separate billing. Just `gem install rubyn-code` and go.
24
24
 
25
+ ---
26
+
27
+ ## Table of Contents
28
+
29
+ - [Why Rubyn?](#why-rubyn)
30
+ - [Install](#install)
31
+ - [Quick Start](#quick-start)
32
+ - [What Can Rubyn Do?](#what-can-rubyn-do)
33
+ - [VS Code Extension](#vs-code-extension)
34
+ - [29 Built-in Tools](#29-built-in-tools)
35
+ - [MCP — External Tool Servers](#mcp--external-tool-servers)
36
+ - [Codebase Indexing](#codebase-indexing)
37
+ - [112 Best Practice Skills](#112-best-practice-skills)
38
+ - [Skill Packs — Registry-Backed Extensions](#skill-packs--registry-backed-extensions)
39
+ - [Context Architecture](#context-architecture)
40
+ - [RUBYN.md — Project Instructions](#rubynmd--project-instructions)
41
+ - [PR Review](#pr-review)
42
+ - [Megaplan — Phased Planning](#megaplan--phased-planning)
43
+ - [Sub-Agents & Teams](#sub-agents--teams)
44
+ - [GOLEM — Autonomous Daemon](#golem--autonomous-daemon)
45
+ - [Continuous Learning](#continuous-learning)
46
+ - [Streaming Output](#streaming-output)
47
+ - [Search Providers](#search-providers)
48
+ - [User Hooks](#user-hooks)
49
+ - [CLI Reference](#cli-reference)
50
+ - [Authentication](#authentication)
51
+ - [Architecture](#architecture)
52
+ - [Configuration](#configuration)
53
+ - [Security](#security)
54
+ - [Diagnostics](#diagnostics)
55
+ - [Development](#development)
56
+ - [From Rubyn to Rubyn Code](#from-rubyn-to-rubyn-code)
57
+ - [Contributing](#contributing)
58
+ - [License](#license)
59
+
60
+ ---
61
+
25
62
  ## Why Rubyn?
26
63
 
27
64
  - **Rails-native** — understands service object extraction, RSpec conventions, ActiveRecord patterns, and Hotwire
28
65
  - **Context-aware** — automatically incorporates schema, routes, specs, factories, and models
29
- - **Best practices built in** — ships with 112 curated Ruby and Rails guidelines that load on demand
66
+ - **Best practices built in** — ships with 112 curated Ruby and Rails guidelines that load on demand, plus registry-backed [skill packs](#skill-packs--registry-backed-extensions) that autoload as you need them
67
+ - **Plans big work in phases** — [`/megaplan`](#megaplan--phased-planning) runs a read-only interview, then breaks rewrites and migrations into vertical-slice phases that ship one at a time
30
68
  - **Agentic** — doesn't just answer questions. Reads files, writes code, runs specs, commits, reviews PRs, spawns sub-agents, and remembers what it learns
69
+ - **IDE-ready** — works in the terminal and inside VS Code with full bidirectional communication
70
+ - **Extensible** — connect external tool servers via MCP, add custom skills, or wire up your own providers
31
71
 
32
72
  ## Install
33
73
 
34
- Requires **Ruby 4.0+**. Install with your latest Ruby, then pin it so it works in every project:
74
+ Requires **Ruby 4.0.2+**. Install with your latest Ruby, then pin it so it works in every project:
35
75
 
36
76
  ```bash
37
77
  # Install the gem
@@ -46,11 +86,11 @@ That's it. `rubyn-code` now works in any project regardless of `.ruby-version`.
46
86
  <details>
47
87
  <summary>Using rbenv?</summary>
48
88
 
49
- If you manage multiple Rubies with rbenv, install on your latest:
89
+ If you manage multiple Rubies with rbenv, install on your latest (run `rbenv versions` to list what you have):
50
90
 
51
91
  ```bash
52
- RBENV_VERSION=4.0.2 gem install rubyn-code
53
- RBENV_VERSION=4.0.2 rubyn-code --setup
92
+ RBENV_VERSION=<your-ruby-version> gem install rubyn-code
93
+ RBENV_VERSION=<your-ruby-version> rubyn-code --setup
54
94
  ```
55
95
 
56
96
  The `--setup` command creates a launcher in `~/.local/bin` that calls the gem wrapper directly, skipping rbenv's shim. As long as `~/.local/bin` is in your PATH before `~/.rbenv/shims`, you're good.
@@ -61,7 +101,7 @@ The `--setup` command creates a launcher in `~/.local/bin` that calls the gem wr
61
101
  <summary>Using rvm?</summary>
62
102
 
63
103
  ```bash
64
- rvm use 4.0.2
104
+ rvm use <your-ruby-version>
65
105
  gem install rubyn-code
66
106
  rubyn-code --setup
67
107
  ```
@@ -93,6 +133,9 @@ rubyn-code --yolo
93
133
 
94
134
  # Single prompt
95
135
  rubyn-code -p "Refactor app/controllers/orders_controller.rb into service objects"
136
+
137
+ # VS Code IDE mode (used by the extension)
138
+ rubyn-code --ide
96
139
  ```
97
140
 
98
141
  ## What Can Rubyn Do?
@@ -118,7 +161,7 @@ rubyn > Write specs for the new service objects
118
161
  > write_file: path=spec/services/orders/create_service_spec.rb
119
162
  > run_specs: path=spec/services/orders/
120
163
 
121
- 4 examples, 0 failures. All green.
164
+ 4 examples, 0 failures. All green.
122
165
  ```
123
166
 
124
167
  ### Review code
@@ -142,6 +185,29 @@ Agent finished (23 tool calls).
142
185
  This is a Rails 7.1 e-commerce app with...
143
186
  ```
144
187
 
188
+ ## VS Code Extension
189
+
190
+ Rubyn Code includes a VS Code extension that provides a full IDE experience with bidirectional JSON-RPC communication. The extension runs Rubyn as a subprocess and connects over stdin/stdout.
191
+
192
+ **Capabilities:**
193
+
194
+ - Chat panel with streaming responses and syntax-highlighted code blocks
195
+ - Inline diffs — review and accept generated code changes directly in the editor
196
+ - Tool approval prompts in the IDE (or skip them in YOLO mode)
197
+ - Full session management — resume, list, fork, and reset conversations
198
+ - Structured code review feedback with severity ratings
199
+ - IDE config get/set for persistent settings
200
+ - All 29 tools available, including MCP tools
201
+
202
+ **Permission modes:**
203
+
204
+ | Mode | Behavior |
205
+ |------|----------|
206
+ | `default` | Per-tool approval required |
207
+ | `bypass` | YOLO — skip all approval prompts |
208
+
209
+ The extension communicates over 19 RPC methods: `initialize`, `prompt`, `cancel`, `review`, `approveToolUse`, `acceptEdit`, `session/*`, `config/*`, `models/list`, `plan/propose`, `plan/interview/*` (chat-resident [megaplan](#megaplan--phased-planning)), `recover_ci`, and `shutdown`.
210
+
145
211
  ## 29 Built-in Tools
146
212
 
147
213
  | Category | Tools |
@@ -159,6 +225,58 @@ This is a Rails 7.1 e-commerce app with...
159
225
  | **Teams** | `send_message`, `read_inbox` |
160
226
  | **Interactive** | `ask_user` (ask clarifying questions mid-task) |
161
227
 
228
+ ## MCP — External Tool Servers
229
+
230
+ Connect external tool servers via the [Model Context Protocol](https://modelcontextprotocol.io). MCP tools are dynamically discovered and registered as native Rubyn tools, available in the REPL, IDE, and daemon.
231
+
232
+ ### Configuration
233
+
234
+ Create `.rubyn-code/mcp.json` in your project or `~/.rubyn-code/mcp.json` globally:
235
+
236
+ ```json
237
+ {
238
+ "mcpServers": {
239
+ "github": {
240
+ "command": "npx",
241
+ "args": ["-y", "@modelcontextprotocol/server-github"],
242
+ "env": { "GITHUB_TOKEN": "${GITHUB_TOKEN}" }
243
+ },
244
+ "my-api": {
245
+ "url": "http://localhost:3001/mcp",
246
+ "timeout": 30
247
+ }
248
+ }
249
+ }
250
+ ```
251
+
252
+ - **Stdio transport** — specify `command` and `args` to run a subprocess
253
+ - **SSE transport** — specify `url` for HTTP-based servers
254
+ - Environment variables are interpolated with `${VAR}` syntax
255
+
256
+ MCP tools appear in the tool palette prefixed with `mcp_` and require confirmation before execution. Run `/doctor` to verify server connectivity.
257
+
258
+ Rubyn ships with three example MCP servers: **database explorer**, **RubyGems lookup**, and **Rails routes**. See `/mcp` for documentation.
259
+
260
+ ## Codebase Indexing
261
+
262
+ Rubyn builds a structural index of your codebase on first session and incrementally updates it as files change. The index powers smarter context injection, skill suggestions, and impact analysis.
263
+
264
+ **What it tracks:**
265
+
266
+ - Classes, modules, methods, callbacks, scopes, validations, associations
267
+ - Relationships between files (associations, test coverage, caller/callee)
268
+ - Rails patterns: `has_many`, `belongs_to`, `before_action`, `validates`, etc.
269
+ - File classification: model, controller, service, concern, spec
270
+
271
+ **How it's used:**
272
+
273
+ - Injects a compact structural summary into the system prompt
274
+ - Feeds the dynamic tool schema for smarter tool selection
275
+ - Powers `impact_analysis(file)` to find affected tests and dependents
276
+ - Suggests relevant skills based on the code you're working with
277
+
278
+ Stored at `.rubyn-code/codebase_index.json`. The `/doctor` command flags stale indexes (>24 hours).
279
+
162
280
  ## 112 Best Practice Skills
163
281
 
164
282
  Rubyn ships with curated best practice documents that load on demand. Only skill names are in memory — full content loads when Rubyn needs it.
@@ -176,6 +294,17 @@ Rubyn ships with curated best practice documents that load on demand. Only skill
176
294
  | **Gems** | Sidekiq, Devise, FactoryBot, Pundit, Faraday, Stripe, RuboCop, dry-rb |
177
295
  | **Sinatra** | Application structure, middleware, testing |
178
296
 
297
+ ### Skill search & filter
298
+
299
+ ```
300
+ rubyn > /skill search factory # search by name, description, or tags
301
+ rubyn > /skill list rails # filter by category
302
+ rubyn > /skill list # show all categories
303
+ rubyn > /skill load rspec_matchers # inject a skill into context
304
+ ```
305
+
306
+ Results are relevance-ranked: name matches score highest, then description, then tags.
307
+
179
308
  ### Custom skills
180
309
 
181
310
  Override or extend with your own:
@@ -190,6 +319,29 @@ mkdir -p ~/.rubyn-code/skills
190
319
  echo "# Use double quotes for strings" > ~/.rubyn-code/skills/my_style.md
191
320
  ```
192
321
 
322
+ ## Skill Packs — Registry-Backed Extensions
323
+
324
+ Beyond the 112 built-in skills, Rubyn can pull additional skill packs from the [rubyn.ai](https://rubyn.ai) registry. Packs are bundles of related skills published by the community or by Rubyn itself.
325
+
326
+ ```
327
+ rubyn > /skills # list installed packs and browse the registry
328
+ rubyn > /install-skills sidekiq # install a pack by name
329
+ rubyn > /install-skills graphql viewcomponent # install multiple at once
330
+ rubyn > /remove-skills sidekiq # uninstall
331
+ ```
332
+
333
+ Installed packs live at `~/.rubyn-code/skill-packs/<pack-name>/` and load alongside the built-in catalog.
334
+
335
+ ### Auto-suggest from your Gemfile
336
+
337
+ On session start, Rubyn parses your `Gemfile` and quietly suggests matching packs the first time it sees a gem (e.g. detects `sidekiq` → suggests the sidekiq pack). Suggestions are recorded in `.rubyn-code/suggested.json` so you only see each one once.
338
+
339
+ ### Trigger-based autoload
340
+
341
+ If your message mentions a topic that matches an uninstalled pack's name or tags, Rubyn fetches the pack from the registry on the fly, installs it, and feeds the relevant skills into the same turn. Registry failures are silent — the conversation continues as if the autoload weren't there.
342
+
343
+ Point at a custom registry with `RUBYN_REGISTRY_URL=https://your-registry.example.com`.
344
+
193
345
  ## Context Architecture
194
346
 
195
347
  Rubyn automatically loads relevant context based on what you're working on:
@@ -199,6 +351,8 @@ Rubyn automatically loads relevant context based on what you're working on:
199
351
  - **Service objects** → includes referenced models and their specs
200
352
  - **Any file** → checks for `RUBYN.md`, `CLAUDE.md`, or `AGENT.md` instructions
201
353
 
354
+ The [codebase index](#codebase-indexing) enhances this with structural awareness — Rubyn knows which files depend on each other before it reads them.
355
+
202
356
  ## RUBYN.md — Project Instructions
203
357
 
204
358
  Drop a `RUBYN.md` in your project root and Rubyn follows your conventions:
@@ -236,6 +390,34 @@ Focus areas: `all`, `security`, `performance`, `style`, `testing`
236
390
 
237
391
  Severity ratings: **[critical]** **[warning]** **[suggestion]** **[nitpick]**
238
392
 
393
+ ## Megaplan — Phased Planning
394
+
395
+ For work too big for a single PR — rewrites, migrations, multi-feature initiatives — Rubyn ships a planning workflow that breaks the feature into vertical-slice phases before any code gets written.
396
+
397
+ ```
398
+ rubyn > /megaplan extract billing into its own service
399
+ Megaplan mode — interviewer with read-only tools
400
+
401
+ Decisions so far: (none yet)
402
+
403
+ Q1. What triggers the extraction now — a scaling issue, a team boundary,
404
+ or a compliance constraint?
405
+ 1. Scaling (recommended — billing is the hottest table)
406
+ 2. Team boundary
407
+ 3. Compliance
408
+ ```
409
+
410
+ What happens when you run `/megaplan`:
411
+
412
+ - Loads the **megaplan** skill into context.
413
+ - Flips the agent into **plan mode** — only read-only tools (file reads, search, git status) are available. No edits, no shell mutations.
414
+ - Conducts a one-question-at-a-time interview to lock down scope, constraints, and risk before proposing phases.
415
+ - Outputs a numbered phase breakdown, each phase shippable on its own with the trunk staying green.
416
+
417
+ Trigger phrases like "megaplan", "mega plan", "plan phases", or "phase this out" in normal conversation will surface the skill via [trigger-based autoload](#skill-packs--registry-backed-extensions) too.
418
+
419
+ In the VS Code extension the same workflow runs as a chat-resident interview with structured question cards instead of free-text Q&A. Same skill driving both surfaces.
420
+
239
421
  ## Sub-Agents & Teams
240
422
 
241
423
  ### Sub-Agents (disposable)
@@ -259,6 +441,32 @@ rubyn > Send alice a message to write specs for the User model
259
441
 
260
442
  Teammates run in background threads with their own agent loop and mailbox.
261
443
 
444
+ ## GOLEM — Autonomous Daemon
445
+
446
+ GOLEM is an always-on autonomous agent that claims tasks from a queue and works through them independently. It runs a full agent loop per task with access to all tools, MCP servers, and memory.
447
+
448
+ ```bash
449
+ rubyn-code daemon \
450
+ --name golem-1 \
451
+ --role "Backend Engineer" \
452
+ --max-runs 100 \
453
+ --max-cost 10.0 \
454
+ --poll-interval 5 \
455
+ --idle-timeout 60
456
+ ```
457
+
458
+ **Lifecycle:** `spawned → working ⇄ idle → shutting_down → stopped`
459
+
460
+ **Safety limits:**
461
+
462
+ | Guard | Description |
463
+ |-------|-------------|
464
+ | `--max-runs` | Auto-shutdown after N completed tasks |
465
+ | `--max-cost` | Stop when cumulative USD spend exceeds limit |
466
+ | **Retry backoff** | 3 retries per task before marking failed |
467
+ | **Audit trail** | Full conversation saved per task via session persistence |
468
+ | **Cost tracking** | Accurate per-task spend via the observability layer |
469
+
262
470
  ## Continuous Learning
263
471
 
264
472
  Rubyn gets smarter with every session:
@@ -310,12 +518,14 @@ post_tool_use:
310
518
  rubyn-code # Interactive REPL
311
519
  rubyn-code --yolo # Auto-approve all tools
312
520
  rubyn-code -p "prompt" # Single prompt, exit when done
521
+ rubyn-code --ide # IDE server mode (JSON-RPC over stdin/stdout)
313
522
  rubyn-code --resume [ID] # Resume previous session
314
523
  rubyn-code --setup # Pin to this Ruby (run once after install)
315
524
  rubyn-code --debug # Enable debug output
316
525
  rubyn-code --auth # Authenticate with Claude
317
526
  rubyn-code --version # Show version
318
527
  rubyn-code --help # Show help
528
+ rubyn-code daemon [OPTIONS] # Run GOLEM autonomous daemon
319
529
  ```
320
530
 
321
531
  ### Slash Commands
@@ -331,10 +541,12 @@ rubyn-code --help # Show help
331
541
  | `/cost` | Show token usage and costs |
332
542
  | `/tasks` | List all tasks |
333
543
  | `/budget [amt]` | Show or set session budget |
334
- | `/skill [name]` | Load or list available skills |
544
+ | `/skill [name]` | Load, search, or list available skills |
335
545
  | `/resume [id]` | Resume or list sessions |
336
546
  | `/provider` | Add or list providers |
337
547
  | `/model` | Show/switch model and provider |
548
+ | `/doctor` | Run environment health checks |
549
+ | `/mcp` | MCP server documentation and status |
338
550
 
339
551
  ## Authentication
340
552
 
@@ -527,9 +739,29 @@ This means:
527
739
  | `~/.rubyn-code/.encryption_salt` | `0600` | PBKDF2 salt (not secret alone, but protected) |
528
740
  | `~/.rubyn-code/config.yml` | `0600` | Provider config (no secrets) |
529
741
 
742
+ ## Diagnostics
743
+
744
+ Run `/doctor` to check your environment:
745
+
746
+ ```
747
+ rubyn > /doctor
748
+
749
+ ✓ Ruby version 4.0.2
750
+ ✓ Bundler installed
751
+ ✓ Database 12 migrations applied
752
+ ✓ Authentication valid (keychain)
753
+ ✓ Skills 112 available
754
+ ✓ Project detected Rails 7.1
755
+ ✓ MCP servers 2 connected
756
+ ✓ Codebase index fresh (2 hours ago)
757
+ ✓ Skill catalog 112 skills, 0 malformed
758
+ ```
759
+
760
+ Checks Ruby version, bundler, database state, authentication, skills, project type, MCP server connectivity, codebase index freshness, and skill catalog integrity.
761
+
530
762
  ## Development
531
763
 
532
- Requires Ruby 4.0+.
764
+ Requires Ruby 4.0.2+.
533
765
 
534
766
  ```bash
535
767
  git clone https://github.com/MatthewSuttles/rubyn-code.git
@@ -538,6 +770,12 @@ bundle install
538
770
  bundle exec rspec
539
771
  ```
540
772
 
773
+ Quick rebuild from source:
774
+
775
+ ```bash
776
+ bin/dev-install
777
+ ```
778
+
541
779
  ## From Rubyn to Rubyn Code
542
780
 
543
781
  If you used the original [Rubyn gem](https://github.com/Rubyn-AI/rubyn), here's what changed:
@@ -177,7 +177,8 @@ module RubynCode
177
177
  id_str_key: 'tool_use_id')
178
178
  end
179
179
 
180
- def collect_block_ids(formatted, role:, type:, id_key:, id_str_key:) # rubocop:disable Metrics/CyclomaticComplexity -- iterates blocks with type+role guards
180
+ # -- iterates blocks with type+role guards
181
+ def collect_block_ids(formatted, role:, type:, id_key:, id_str_key:)
181
182
  ids = Set.new
182
183
  formatted.each do |msg|
183
184
  next unless msg[:role] == role && msg[:content].is_a?(Array)
@@ -64,7 +64,8 @@ module RubynCode
64
64
  # @param message [String]
65
65
  # @param codebase_index [RubynCode::Index::CodebaseIndex, nil] optional index for deeper detection
66
66
  # @return [Symbol, nil]
67
- def detect_context(message, codebase_index: nil) # rubocop:disable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity -- context detection dispatch
67
+ # -- context detection dispatch
68
+ def detect_context(message, codebase_index: nil)
68
69
  msg = message.to_s.downcase
69
70
  return :testing if msg.match?(/\b(test|spec|rspec)\b/)
70
71
  return :git if msg.match?(/\b(commit|push|diff|branch|merge|git)\b/)
@@ -43,7 +43,8 @@ module RubynCode
43
43
  # Only returns models from the active provider — never crosses
44
44
  # provider boundaries (e.g., won't send a GPT model to Anthropic).
45
45
  # Falls back to nil (use client's default) if routing fails.
46
- def routed_model # rubocop:disable Metrics/CyclomaticComplexity -- guard clauses for provider/mode checks
46
+ # -- guard clauses for provider/mode checks
47
+ def routed_model
47
48
  return nil if manual_model_mode?
48
49
 
49
50
  last_user = last_user_message_text
@@ -76,7 +77,8 @@ module RubynCode
76
77
  content.is_a?(String) ? content : nil
77
78
  end
78
79
 
79
- def log_llm_call(opts) # rubocop:disable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity -- safe accessor checks
80
+ # -- safe accessor checks
81
+ def log_llm_call(opts)
80
82
  default_model = @llm_client.respond_to?(:model) ? @llm_client.model : 'default'
81
83
  routed = opts[:model]
82
84
  effective = routed || default_model
@@ -59,6 +59,7 @@ module RubynCode
59
59
  inject_skill_listing unless @skills_injected
60
60
  @decision_compactor&.detect_topic_switch(user_input)
61
61
  @skill_ttl&.tick!
62
+ autoload_triggered_skills(user_input)
62
63
  @conversation.add_user_message(user_input)
63
64
  reset_iteration_state
64
65
 
@@ -93,6 +94,8 @@ module RubynCode
93
94
  @background_manager = opts[:background_manager]
94
95
  @stall_detector = opts.fetch(:stall_detector, LoopDetector.new)
95
96
  @skill_loader = opts[:skill_loader]
97
+ @skill_matcher = opts[:skill_matcher]
98
+ @web_skill_autoload = opts[:web_skill_autoload]
96
99
  @project_root = opts[:project_root]
97
100
  @tool_wrapper = opts[:tool_wrapper]
98
101
  @decision_compactor = build_decision_compactor
@@ -134,9 +137,10 @@ module RubynCode
134
137
  end
135
138
 
136
139
  def assign_callbacks(opts)
137
- @on_tool_call = opts[:on_tool_call]
138
- @on_tool_result = opts[:on_tool_result]
139
- @on_text = opts[:on_text]
140
+ @on_tool_call = opts[:on_tool_call]
141
+ @on_tool_result = opts[:on_tool_result]
142
+ @on_text = opts[:on_text]
143
+ @on_skills_autoloaded = opts[:on_skills_autoloaded]
140
144
  @skills_injected = false
141
145
  end
142
146
 
@@ -45,7 +45,8 @@ module RubynCode
45
45
  # @param message [String] the user's input
46
46
  # @param tool_calls [Array] recent tool calls (for context)
47
47
  # @return [Symbol] one of the MODES keys
48
- def detect(message, tool_calls: []) # rubocop:disable Metrics/CyclomaticComplexity -- mode detection dispatch
48
+ # -- mode detection dispatch
49
+ def detect(message, tool_calls: [])
49
50
  return :implementing if implementation_signal?(message)
50
51
  return :debugging if debugging_signal?(message)
51
52
  return :reviewing if reviewing_signal?(message)
@@ -123,6 +123,45 @@ module RubynCode
123
123
  @skills_injected = true
124
124
  end
125
125
 
126
+ # Match the current user message against every skill's :triggers and
127
+ # inject the body of any new match into the conversation so the LLM sees
128
+ # it on the next call. Per-session dedup lives in the Matcher.
129
+ #
130
+ # When the message matches a registry pack the user hasn't installed,
131
+ # @web_skill_autoload silently fetches it, installs it, refreshes the
132
+ # catalog, and surfaces any new skill matches. Web fallback failures
133
+ # are silent so the turn proceeds normally.
134
+ def autoload_triggered_skills(user_input)
135
+ return unless @skill_matcher && @skill_loader
136
+
137
+ matches = @skill_matcher.match(user_input)
138
+ matches += @web_skill_autoload.try(user_input) if @web_skill_autoload
139
+ return if matches.empty?
140
+
141
+ names = matches.map { |m| m[:name] }
142
+ bodies = names.filter_map do |name|
143
+ @skill_loader.load(name)
144
+ rescue StandardError => e
145
+ RubynCode::Debug.warn("Failed to autoload skill '#{name}': #{e.message}")
146
+ nil
147
+ end
148
+ return if bodies.empty?
149
+
150
+ inject_autoloaded_bodies(bodies)
151
+ @on_skills_autoloaded&.call(names)
152
+ end
153
+
154
+ def inject_autoloaded_bodies(bodies)
155
+ @conversation.add_user_message(
156
+ '[system] The following skills are auto-loaded based on the next user ' \
157
+ "message's triggers. Use them as context. Do not mention this message " \
158
+ "to the user.\n\n#{bodies.join("\n\n")}"
159
+ )
160
+ @conversation.add_assistant_message(
161
+ [{ type: 'text', text: 'Understood.' }]
162
+ )
163
+ end
164
+
126
165
  def append_deferred_tools(parts)
127
166
  deferred = deferred_tool_names
128
167
  return if deferred.empty?
@@ -26,7 +26,8 @@ module RubynCode
26
26
  all_tools.select { |t| core_or_discovered?(t) }
27
27
  end
28
28
 
29
- def detect_task_context # rubocop:disable Metrics/CyclomaticComplexity -- safe navigation chain
29
+ # -- safe navigation chain
30
+ def detect_task_context
30
31
  last_msg = @conversation&.messages&.reverse_each&.find { |m| m[:role] == 'user' } # rubocop:disable Style/SafeNavigationChainLength
31
32
  return nil unless last_msg
32
33
 
@@ -142,7 +143,8 @@ module RubynCode
142
143
  end
143
144
  end
144
145
 
145
- def signal_decision_compactor(tool_name, tool_input, result) # rubocop:disable Metrics/CyclomaticComplexity -- tool dispatch
146
+ # -- tool dispatch
147
+ def signal_decision_compactor(tool_name, tool_input, result)
146
148
  return unless @decision_compactor
147
149
 
148
150
  case tool_name