rubyn-code 0.5.1 → 0.8.0

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 (128) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +137 -4
  3. data/db/migrations/014_multi_agent_upgrade.rb +79 -0
  4. data/lib/rubyn_code/agent/conversation.rb +100 -4
  5. data/lib/rubyn_code/agent/dynamic_tool_schema.rb +1 -1
  6. data/lib/rubyn_code/agent/llm_caller.rb +7 -3
  7. data/lib/rubyn_code/agent/loop.rb +106 -12
  8. data/lib/rubyn_code/agent/response_parser.rb +8 -0
  9. data/lib/rubyn_code/agent/system_prompt_builder.rb +40 -2
  10. data/lib/rubyn_code/agent/tool_processor.rb +13 -1
  11. data/lib/rubyn_code/auth/oauth.rb +1 -1
  12. data/lib/rubyn_code/auth/token_store.rb +49 -4
  13. data/lib/rubyn_code/autonomous/daemon.rb +1 -1
  14. data/lib/rubyn_code/checkpoint/hook.rb +26 -0
  15. data/lib/rubyn_code/checkpoint/manager.rb +109 -0
  16. data/lib/rubyn_code/chisel/debt.rb +65 -0
  17. data/lib/rubyn_code/chisel/inspection.rb +93 -0
  18. data/lib/rubyn_code/chisel.rb +127 -0
  19. data/lib/rubyn_code/cli/commands/agents.rb +31 -0
  20. data/lib/rubyn_code/cli/commands/chisel.rb +52 -0
  21. data/lib/rubyn_code/cli/commands/chisel_audit.rb +19 -0
  22. data/lib/rubyn_code/cli/commands/chisel_debt.rb +28 -0
  23. data/lib/rubyn_code/cli/commands/chisel_gain.rb +30 -0
  24. data/lib/rubyn_code/cli/commands/chisel_review.rb +19 -0
  25. data/lib/rubyn_code/cli/commands/command_template.rb +50 -0
  26. data/lib/rubyn_code/cli/commands/context.rb +30 -1
  27. data/lib/rubyn_code/cli/commands/custom_command.rb +84 -0
  28. data/lib/rubyn_code/cli/commands/custom_loader.rb +100 -0
  29. data/lib/rubyn_code/cli/commands/effort.rb +47 -0
  30. data/lib/rubyn_code/cli/commands/export.rb +174 -0
  31. data/lib/rubyn_code/cli/commands/goal.rb +87 -0
  32. data/lib/rubyn_code/cli/commands/learning.rb +62 -0
  33. data/lib/rubyn_code/cli/commands/loop.rb +58 -0
  34. data/lib/rubyn_code/cli/commands/mcp.rb +49 -12
  35. data/lib/rubyn_code/cli/commands/megaplan.rb +1 -1
  36. data/lib/rubyn_code/cli/commands/registry.rb +14 -9
  37. data/lib/rubyn_code/cli/commands/resume.rb +97 -26
  38. data/lib/rubyn_code/cli/commands/rewind.rb +65 -0
  39. data/lib/rubyn_code/cli/commands/think.rb +47 -0
  40. data/lib/rubyn_code/cli/first_run.rb +1 -1
  41. data/lib/rubyn_code/cli/loop_runner.rb +98 -0
  42. data/lib/rubyn_code/cli/mention_expander.rb +111 -0
  43. data/lib/rubyn_code/cli/renderer.rb +3 -2
  44. data/lib/rubyn_code/cli/repl.rb +68 -15
  45. data/lib/rubyn_code/cli/repl_commands.rb +76 -2
  46. data/lib/rubyn_code/cli/repl_setup.rb +17 -7
  47. data/lib/rubyn_code/cli/stream_formatter.rb +3 -2
  48. data/lib/rubyn_code/cli/version_check.rb +10 -3
  49. data/lib/rubyn_code/config/defaults.rb +14 -1
  50. data/lib/rubyn_code/config/schema.json +9 -0
  51. data/lib/rubyn_code/config/settings.rb +19 -2
  52. data/lib/rubyn_code/context/auto_compact.rb +1 -1
  53. data/lib/rubyn_code/context/manager.rb +29 -12
  54. data/lib/rubyn_code/context/manual_compact.rb +1 -1
  55. data/lib/rubyn_code/debug.rb +11 -5
  56. data/lib/rubyn_code/goal/evaluator.rb +95 -0
  57. data/lib/rubyn_code/hooks/event_map.rb +56 -0
  58. data/lib/rubyn_code/hooks/external_dispatcher.rb +199 -0
  59. data/lib/rubyn_code/hooks/goal_hook.rb +88 -0
  60. data/lib/rubyn_code/hooks/response.rb +83 -0
  61. data/lib/rubyn_code/hooks/runner.rb +61 -3
  62. data/lib/rubyn_code/hooks/settings_json_loader.rb +109 -0
  63. data/lib/rubyn_code/hooks/subprocess_executor.rb +116 -0
  64. data/lib/rubyn_code/ide/handlers/plan_interview_answer_handler.rb +13 -13
  65. data/lib/rubyn_code/ide/handlers/plan_interview_cancel_handler.rb +1 -1
  66. data/lib/rubyn_code/ide/handlers/plan_interview_start_handler.rb +10 -10
  67. data/lib/rubyn_code/ide/handlers/plan_propose_handler.rb +1 -1
  68. data/lib/rubyn_code/ide/handlers/prompt_handler.rb +9 -1
  69. data/lib/rubyn_code/ide/handlers/recover_ci_handler.rb +27 -16
  70. data/lib/rubyn_code/ide/handlers/session_resume_handler.rb +1 -1
  71. data/lib/rubyn_code/index/codebase_index.rb +103 -4
  72. data/lib/rubyn_code/index/prism_extractor.rb +82 -0
  73. data/lib/rubyn_code/learning/injector.rb +1 -2
  74. data/lib/rubyn_code/learning/porter.rb +129 -0
  75. data/lib/rubyn_code/llm/adapters/anthropic.rb +168 -29
  76. data/lib/rubyn_code/llm/adapters/anthropic_streaming.rb +13 -0
  77. data/lib/rubyn_code/llm/adapters/base.rb +2 -1
  78. data/lib/rubyn_code/llm/adapters/openai.rb +2 -2
  79. data/lib/rubyn_code/llm/adapters/openai_message_translator.rb +21 -0
  80. data/lib/rubyn_code/llm/adapters/prompt_caching.rb +5 -1
  81. data/lib/rubyn_code/llm/client.rb +16 -3
  82. data/lib/rubyn_code/llm/image_reader.rb +60 -0
  83. data/lib/rubyn_code/llm/message_builder.rb +21 -1
  84. data/lib/rubyn_code/llm/model_router.rb +4 -4
  85. data/lib/rubyn_code/mcp/client.rb +59 -0
  86. data/lib/rubyn_code/mcp/discovery.rb +93 -0
  87. data/lib/rubyn_code/mcp/server_extras_bridge.rb +110 -0
  88. data/lib/rubyn_code/mcp/sse_transport.rb +2 -1
  89. data/lib/rubyn_code/mcp/tool_bridge.rb +16 -14
  90. data/lib/rubyn_code/megaplan/ci_recovery.rb +3 -3
  91. data/lib/rubyn_code/megaplan/interview_session.rb +8 -3
  92. data/lib/rubyn_code/megaplan/plan_proposer.rb +3 -3
  93. data/lib/rubyn_code/memory/search.rb +9 -5
  94. data/lib/rubyn_code/memory/session_persistence.rb +159 -21
  95. data/lib/rubyn_code/observability/cost_calculator.rb +8 -3
  96. data/lib/rubyn_code/output/diff_renderer.rb +62 -7
  97. data/lib/rubyn_code/protocols/RUBYN.md +0 -3
  98. data/lib/rubyn_code/skills/auto_suggest.rb +70 -2
  99. data/lib/rubyn_code/skills/registry_client.rb +4 -3
  100. data/lib/rubyn_code/sub_agents/agent_type.rb +17 -0
  101. data/lib/rubyn_code/sub_agents/catalog.rb +124 -0
  102. data/lib/rubyn_code/tasks/models.rb +0 -16
  103. data/lib/rubyn_code/teams/agent_registry.rb +120 -0
  104. data/lib/rubyn_code/teams/mailbox.rb +99 -10
  105. data/lib/rubyn_code/teams/manager.rb +83 -5
  106. data/lib/rubyn_code/teams/teammate.rb +3 -14
  107. data/lib/rubyn_code/tools/RUBYN.md +2 -2
  108. data/lib/rubyn_code/tools/ask_user.rb +15 -1
  109. data/lib/rubyn_code/tools/bash.rb +3 -3
  110. data/lib/rubyn_code/tools/code_graph.rb +134 -0
  111. data/lib/rubyn_code/tools/executor.rb +9 -4
  112. data/lib/rubyn_code/tools/spawn_agent.rb +47 -62
  113. data/lib/rubyn_code/tools/spawn_teammate.rb +7 -2
  114. data/lib/rubyn_code/tools/todo_store.rb +55 -0
  115. data/lib/rubyn_code/tools/todo_write.rb +88 -0
  116. data/lib/rubyn_code/tools/web_fetch.rb +1 -1
  117. data/lib/rubyn_code/tools/web_search.rb +4 -1
  118. data/lib/rubyn_code/version.rb +1 -1
  119. data/lib/rubyn_code.rb +57 -9
  120. data/skills/rubyn_self_test.md +462 -14
  121. data/skills/self_test/chisel_smoke.rb +84 -0
  122. data/skills/self_test/fixtures/chisel_sample.rb +64 -0
  123. metadata +45 -6
  124. data/lib/rubyn_code/context/context_budget.rb +0 -183
  125. data/lib/rubyn_code/context/schema_filter.rb +0 -64
  126. data/lib/rubyn_code/learning/shortcut.rb +0 -95
  127. data/lib/rubyn_code/llm/streaming.rb +0 -10
  128. data/lib/rubyn_code/protocols/plan_approval.rb +0 -72
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rubyn-code
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.5.1
4
+ version: 0.8.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - fadedmaturity
@@ -184,6 +184,7 @@ files:
184
184
  - db/migrations/011_fix_mailbox_messages_columns.rb
185
185
  - db/migrations/012_expand_mailbox_message_types.rb
186
186
  - db/migrations/013_add_failed_status_to_tasks.rb
187
+ - db/migrations/014_multi_agent_upgrade.rb
187
188
  - exe/rubyn-code
188
189
  - lib/rubyn_code.rb
189
190
  - lib/rubyn_code/agent/RUBYN.md
@@ -213,21 +214,40 @@ files:
213
214
  - lib/rubyn_code/background/job.rb
214
215
  - lib/rubyn_code/background/notifier.rb
215
216
  - lib/rubyn_code/background/worker.rb
217
+ - lib/rubyn_code/checkpoint/hook.rb
218
+ - lib/rubyn_code/checkpoint/manager.rb
219
+ - lib/rubyn_code/chisel.rb
220
+ - lib/rubyn_code/chisel/debt.rb
221
+ - lib/rubyn_code/chisel/inspection.rb
216
222
  - lib/rubyn_code/cli/RUBYN.md
217
223
  - lib/rubyn_code/cli/app.rb
218
224
  - lib/rubyn_code/cli/commands/RUBYN.md
225
+ - lib/rubyn_code/cli/commands/agents.rb
219
226
  - lib/rubyn_code/cli/commands/base.rb
220
227
  - lib/rubyn_code/cli/commands/budget.rb
228
+ - lib/rubyn_code/cli/commands/chisel.rb
229
+ - lib/rubyn_code/cli/commands/chisel_audit.rb
230
+ - lib/rubyn_code/cli/commands/chisel_debt.rb
231
+ - lib/rubyn_code/cli/commands/chisel_gain.rb
232
+ - lib/rubyn_code/cli/commands/chisel_review.rb
221
233
  - lib/rubyn_code/cli/commands/clear.rb
234
+ - lib/rubyn_code/cli/commands/command_template.rb
222
235
  - lib/rubyn_code/cli/commands/compact.rb
223
236
  - lib/rubyn_code/cli/commands/context.rb
224
237
  - lib/rubyn_code/cli/commands/context_info.rb
225
238
  - lib/rubyn_code/cli/commands/cost.rb
239
+ - lib/rubyn_code/cli/commands/custom_command.rb
240
+ - lib/rubyn_code/cli/commands/custom_loader.rb
226
241
  - lib/rubyn_code/cli/commands/diff.rb
227
242
  - lib/rubyn_code/cli/commands/doctor.rb
243
+ - lib/rubyn_code/cli/commands/effort.rb
244
+ - lib/rubyn_code/cli/commands/export.rb
245
+ - lib/rubyn_code/cli/commands/goal.rb
228
246
  - lib/rubyn_code/cli/commands/help.rb
229
247
  - lib/rubyn_code/cli/commands/install_skills.rb
248
+ - lib/rubyn_code/cli/commands/learning.rb
230
249
  - lib/rubyn_code/cli/commands/list_skills.rb
250
+ - lib/rubyn_code/cli/commands/loop.rb
231
251
  - lib/rubyn_code/cli/commands/mcp.rb
232
252
  - lib/rubyn_code/cli/commands/megaplan.rb
233
253
  - lib/rubyn_code/cli/commands/model.rb
@@ -239,16 +259,20 @@ files:
239
259
  - lib/rubyn_code/cli/commands/remove_skills.rb
240
260
  - lib/rubyn_code/cli/commands/resume.rb
241
261
  - lib/rubyn_code/cli/commands/review.rb
262
+ - lib/rubyn_code/cli/commands/rewind.rb
242
263
  - lib/rubyn_code/cli/commands/skill.rb
243
264
  - lib/rubyn_code/cli/commands/skills.rb
244
265
  - lib/rubyn_code/cli/commands/spawn.rb
245
266
  - lib/rubyn_code/cli/commands/tasks.rb
267
+ - lib/rubyn_code/cli/commands/think.rb
246
268
  - lib/rubyn_code/cli/commands/tokens.rb
247
269
  - lib/rubyn_code/cli/commands/undo.rb
248
270
  - lib/rubyn_code/cli/commands/version.rb
249
271
  - lib/rubyn_code/cli/daemon_runner.rb
250
272
  - lib/rubyn_code/cli/first_run.rb
251
273
  - lib/rubyn_code/cli/input_handler.rb
274
+ - lib/rubyn_code/cli/loop_runner.rb
275
+ - lib/rubyn_code/cli/mention_expander.rb
252
276
  - lib/rubyn_code/cli/renderer.rb
253
277
  - lib/rubyn_code/cli/repl.rb
254
278
  - lib/rubyn_code/cli/repl_commands.rb
@@ -268,22 +292,27 @@ files:
268
292
  - lib/rubyn_code/context/RUBYN.md
269
293
  - lib/rubyn_code/context/auto_compact.rb
270
294
  - lib/rubyn_code/context/compactor.rb
271
- - lib/rubyn_code/context/context_budget.rb
272
295
  - lib/rubyn_code/context/context_collapse.rb
273
296
  - lib/rubyn_code/context/decision_compactor.rb
274
297
  - lib/rubyn_code/context/manager.rb
275
298
  - lib/rubyn_code/context/manual_compact.rb
276
299
  - lib/rubyn_code/context/micro_compact.rb
277
- - lib/rubyn_code/context/schema_filter.rb
278
300
  - lib/rubyn_code/db/RUBYN.md
279
301
  - lib/rubyn_code/db/connection.rb
280
302
  - lib/rubyn_code/db/migrator.rb
281
303
  - lib/rubyn_code/db/schema.rb
282
304
  - lib/rubyn_code/debug.rb
305
+ - lib/rubyn_code/goal/evaluator.rb
283
306
  - lib/rubyn_code/hooks/RUBYN.md
284
307
  - lib/rubyn_code/hooks/built_in.rb
308
+ - lib/rubyn_code/hooks/event_map.rb
309
+ - lib/rubyn_code/hooks/external_dispatcher.rb
310
+ - lib/rubyn_code/hooks/goal_hook.rb
285
311
  - lib/rubyn_code/hooks/registry.rb
312
+ - lib/rubyn_code/hooks/response.rb
286
313
  - lib/rubyn_code/hooks/runner.rb
314
+ - lib/rubyn_code/hooks/settings_json_loader.rb
315
+ - lib/rubyn_code/hooks/subprocess_executor.rb
287
316
  - lib/rubyn_code/hooks/user_hooks.rb
288
317
  - lib/rubyn_code/ide/adapters/tool_output.rb
289
318
  - lib/rubyn_code/ide/client.rb
@@ -310,11 +339,12 @@ files:
310
339
  - lib/rubyn_code/ide/protocol.rb
311
340
  - lib/rubyn_code/ide/server.rb
312
341
  - lib/rubyn_code/index/codebase_index.rb
342
+ - lib/rubyn_code/index/prism_extractor.rb
313
343
  - lib/rubyn_code/learning/RUBYN.md
314
344
  - lib/rubyn_code/learning/extractor.rb
315
345
  - lib/rubyn_code/learning/injector.rb
316
346
  - lib/rubyn_code/learning/instinct.rb
317
- - lib/rubyn_code/learning/shortcut.rb
347
+ - lib/rubyn_code/learning/porter.rb
318
348
  - lib/rubyn_code/llm/RUBYN.md
319
349
  - lib/rubyn_code/llm/adapters/anthropic.rb
320
350
  - lib/rubyn_code/llm/adapters/anthropic_compatible.rb
@@ -327,12 +357,14 @@ files:
327
357
  - lib/rubyn_code/llm/adapters/openai_streaming.rb
328
358
  - lib/rubyn_code/llm/adapters/prompt_caching.rb
329
359
  - lib/rubyn_code/llm/client.rb
360
+ - lib/rubyn_code/llm/image_reader.rb
330
361
  - lib/rubyn_code/llm/message_builder.rb
331
362
  - lib/rubyn_code/llm/model_router.rb
332
- - lib/rubyn_code/llm/streaming.rb
333
363
  - lib/rubyn_code/mcp/RUBYN.md
334
364
  - lib/rubyn_code/mcp/client.rb
335
365
  - lib/rubyn_code/mcp/config.rb
366
+ - lib/rubyn_code/mcp/discovery.rb
367
+ - lib/rubyn_code/mcp/server_extras_bridge.rb
336
368
  - lib/rubyn_code/mcp/sse_transport.rb
337
369
  - lib/rubyn_code/mcp/stdio_transport.rb
338
370
  - lib/rubyn_code/mcp/tool_bridge.rb
@@ -362,7 +394,6 @@ files:
362
394
  - lib/rubyn_code/permissions/tier.rb
363
395
  - lib/rubyn_code/protocols/RUBYN.md
364
396
  - lib/rubyn_code/protocols/interrupt_handler.rb
365
- - lib/rubyn_code/protocols/plan_approval.rb
366
397
  - lib/rubyn_code/protocols/shutdown_handshake.rb
367
398
  - lib/rubyn_code/self_test.rb
368
399
  - lib/rubyn_code/skills/RUBYN.md
@@ -379,6 +410,8 @@ files:
379
410
  - lib/rubyn_code/skills/registry_client.rb
380
411
  - lib/rubyn_code/skills/ttl_manager.rb
381
412
  - lib/rubyn_code/sub_agents/RUBYN.md
413
+ - lib/rubyn_code/sub_agents/agent_type.rb
414
+ - lib/rubyn_code/sub_agents/catalog.rb
382
415
  - lib/rubyn_code/sub_agents/runner.rb
383
416
  - lib/rubyn_code/sub_agents/summarizer.rb
384
417
  - lib/rubyn_code/tasks/RUBYN.md
@@ -386,6 +419,7 @@ files:
386
419
  - lib/rubyn_code/tasks/manager.rb
387
420
  - lib/rubyn_code/tasks/models.rb
388
421
  - lib/rubyn_code/teams/RUBYN.md
422
+ - lib/rubyn_code/teams/agent_registry.rb
389
423
  - lib/rubyn_code/teams/mailbox.rb
390
424
  - lib/rubyn_code/teams/manager.rb
391
425
  - lib/rubyn_code/teams/teammate.rb
@@ -396,6 +430,7 @@ files:
396
430
  - lib/rubyn_code/tools/bash.rb
397
431
  - lib/rubyn_code/tools/bundle_add.rb
398
432
  - lib/rubyn_code/tools/bundle_install.rb
433
+ - lib/rubyn_code/tools/code_graph.rb
399
434
  - lib/rubyn_code/tools/compact.rb
400
435
  - lib/rubyn_code/tools/db_migrate.rb
401
436
  - lib/rubyn_code/tools/edit_file.rb
@@ -425,6 +460,8 @@ files:
425
460
  - lib/rubyn_code/tools/spawn_teammate.rb
426
461
  - lib/rubyn_code/tools/spec_output_parser.rb
427
462
  - lib/rubyn_code/tools/task.rb
463
+ - lib/rubyn_code/tools/todo_store.rb
464
+ - lib/rubyn_code/tools/todo_write.rb
428
465
  - lib/rubyn_code/tools/web_fetch.rb
429
466
  - lib/rubyn_code/tools/web_search.rb
430
467
  - lib/rubyn_code/tools/write_file.rb
@@ -535,6 +572,8 @@ files:
535
572
  - skills/ruby_project/rake_tasks.md
536
573
  - skills/ruby_project/structure.md
537
574
  - skills/rubyn_self_test.md
575
+ - skills/self_test/chisel_smoke.rb
576
+ - skills/self_test/fixtures/chisel_sample.rb
538
577
  - skills/sinatra/application_structure.md
539
578
  - skills/sinatra/middleware_and_deployment.md
540
579
  - skills/sinatra/testing.md
@@ -1,183 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module RubynCode
4
- module Context
5
- # Budget-aware context loader that prioritizes which related files
6
- # to load fully vs. as signatures-only. Prevents context bloat by
7
- # capping auto-loaded context at a configurable token budget.
8
- class ContextBudget
9
- CHARS_PER_TOKEN = 4
10
- DEFAULT_BUDGET = 4000 # tokens
11
-
12
- # Rails convention-based priority for related files.
13
- # Lower number = higher priority = loaded first.
14
- PRIORITY_MAP = {
15
- 'spec' => 1, # tests for the file
16
- 'factory' => 2, # FactoryBot factories
17
- 'service' => 3, # service objects
18
- 'model' => 4, # related models
19
- 'controller' => 5, # controllers
20
- 'serializer' => 6, # serializers
21
- 'concern' => 7, # concerns/mixins
22
- 'helper' => 8, # helpers
23
- 'migration' => 9 # migrations
24
- }.freeze
25
-
26
- attr_reader :loaded_files, :signature_files, :tokens_used
27
-
28
- def initialize(budget: DEFAULT_BUDGET, codebase_index: nil)
29
- @budget = budget
30
- @codebase_index = codebase_index
31
- @loaded_files = []
32
- @signature_files = []
33
- @tokens_used = 0
34
- end
35
-
36
- # Load context for a primary file, filling budget with related files.
37
- # Returns array of { file:, content:, mode: :full|:signatures }
38
- #
39
- # When a codebase_index is available and no related_files are supplied,
40
- # uses impact_analysis to auto-discover related files (specs,
41
- # associated models, controllers, etc.).
42
- def load_for(file_path, related_files: [])
43
- results = []
44
-
45
- # Primary file always loads fully
46
- primary_content = safe_read(file_path)
47
- return results unless primary_content
48
-
49
- primary_tokens = estimate_tokens(primary_content)
50
- @tokens_used = primary_tokens
51
- @loaded_files << file_path
52
- results << { file: file_path, content: primary_content, mode: :full }
53
-
54
- # Auto-discover related files from the index when none supplied
55
- related_files = discover_related_files(file_path) if related_files.empty? && @codebase_index
56
-
57
- # Sort related files by priority and fill remaining budget
58
- sorted = prioritize(related_files)
59
- remaining = @budget - @tokens_used
60
- remaining = load_full_files(sorted, results, remaining)
61
- load_signature_files(sorted, results, remaining)
62
-
63
- results
64
- end
65
-
66
- # Extract method signatures and class structure without method bodies.
67
- # Much more compact than full source — typically 10-20% of original size.
68
- def extract_signatures(content)
69
- signatures = []
70
- indent_stack = []
71
-
72
- content.lines.each do |line|
73
- process_signature_line(line, signatures, indent_stack)
74
- end
75
-
76
- signatures.join
77
- end
78
-
79
- # Returns budget utilization stats.
80
- def stats
81
- {
82
- budget: @budget,
83
- tokens_used: @tokens_used,
84
- utilization: @budget.positive? ? (@tokens_used.to_f / @budget).round(3) : 0.0,
85
- full_files: @loaded_files.size,
86
- signature_files: @signature_files.size
87
- }
88
- end
89
-
90
- private
91
-
92
- def discover_related_files(file_path)
93
- analysis = @codebase_index.impact_analysis(file_path)
94
- analysis[:affected_files].reject { |f| f == file_path }
95
- rescue StandardError
96
- []
97
- end
98
-
99
- def load_full_files(sorted, results, remaining)
100
- sorted.each do |rel_path|
101
- content = safe_read(rel_path)
102
- next unless content
103
-
104
- size = estimate_tokens(content)
105
- next unless size <= remaining
106
-
107
- results << { file: rel_path, content: content, mode: :full }
108
- @loaded_files << rel_path
109
- @tokens_used += size
110
- remaining -= size
111
- end
112
- remaining
113
- end
114
-
115
- def load_signature_files(sorted, results, remaining)
116
- sorted.each do |rel_path|
117
- next if @loaded_files.include?(rel_path)
118
-
119
- content = safe_read(rel_path)
120
- next unless content
121
-
122
- sigs = extract_signatures(content)
123
- sig_size = estimate_tokens(sigs)
124
- next unless sig_size <= remaining
125
-
126
- results << { file: rel_path, content: sigs, mode: :signatures }
127
- @signature_files << rel_path
128
- @tokens_used += sig_size
129
- remaining -= sig_size
130
- end
131
- end
132
-
133
- # -- signature extraction dispatch
134
- def process_signature_line(line, signatures, indent_stack)
135
- stripped = line.strip
136
- if signature_line?(stripped)
137
- signatures << line
138
- indent_stack << current_indent(line) if block_opener?(stripped)
139
- elsif stripped == 'end' && indent_stack.any? && current_indent(line) <= indent_stack.last
140
- signatures << line
141
- indent_stack.pop
142
- elsif class_or_module_line?(stripped)
143
- signatures << line
144
- indent_stack << current_indent(line)
145
- end
146
- end
147
-
148
- def prioritize(files)
149
- files.sort_by do |path|
150
- basename = File.basename(path, '.*').downcase
151
- priority = PRIORITY_MAP.find { |key, _| basename.include?(key) }&.last || 10
152
- priority
153
- end
154
- end
155
-
156
- def signature_line?(stripped)
157
- stripped.match?(/\A\s*(def\s|attr_|include\s|extend\s|has_|belongs_|validates|scope\s|delegate\s)/)
158
- end
159
-
160
- def class_or_module_line?(stripped)
161
- stripped.match?(/\A\s*(class|module)\s/)
162
- end
163
-
164
- def block_opener?(stripped)
165
- stripped.match?(/\Adef\s/)
166
- end
167
-
168
- def current_indent(line)
169
- line.match(/\A(\s*)/)[1].length
170
- end
171
-
172
- def safe_read(path)
173
- File.read(path)
174
- rescue StandardError
175
- nil
176
- end
177
-
178
- def estimate_tokens(text)
179
- (text.bytesize.to_f / CHARS_PER_TOKEN).ceil
180
- end
181
- end
182
- end
183
- end
@@ -1,64 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module RubynCode
4
- module Context
5
- # Extracts only the relevant table definitions from db/schema.rb
6
- # based on which models are currently in context. Loading the full
7
- # schema for a large Rails app can be 5-10K tokens; filtering to
8
- # relevant tables typically reduces this to 200-500 tokens.
9
- module SchemaFilter
10
- TABLE_PATTERN = /create_table\s+"([^"]+)"/
11
- END_PATTERN = /\A\s+end\s*\z/
12
-
13
- class << self
14
- # Returns schema definitions for only the specified table names.
15
- #
16
- # @param schema_path [String] path to db/schema.rb
17
- # @param table_names [Array<String>] table names to include
18
- # @return [String] filtered schema content
19
- def filter(schema_path, table_names:)
20
- return '' if table_names.empty?
21
- return '' unless File.exist?(schema_path)
22
-
23
- lines = File.readlines(schema_path)
24
- extract_tables(lines, table_names.to_set(&:to_s))
25
- end
26
-
27
- # Derives table names from model class names using Rails conventions.
28
- #
29
- # @param model_names [Array<String>] e.g., ["User", "OrderItem"]
30
- # @return [Array<String>] e.g., ["users", "order_items"]
31
- def tableize(model_names)
32
- model_names.map { |name| "#{name.gsub(/([a-z])([A-Z])/, '\1_\2').downcase}s" }
33
- end
34
-
35
- # Convenience: filter schema by model names instead of table names.
36
- def filter_for_models(schema_path, model_names:)
37
- tables = tableize(model_names)
38
- filter(schema_path, table_names: tables)
39
- end
40
-
41
- private
42
-
43
- def extract_tables(lines, table_set)
44
- result = []
45
- capturing = false
46
-
47
- lines.each do |line|
48
- match = TABLE_PATTERN.match(line)
49
- capturing = true if match && table_set.include?(match[1])
50
-
51
- result << line if capturing
52
-
53
- if capturing && END_PATTERN.match?(line)
54
- capturing = false
55
- result << "\n"
56
- end
57
- end
58
-
59
- result.join
60
- end
61
- end
62
- end
63
- end
64
- end
@@ -1,95 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module RubynCode
4
- module Learning
5
- # Uses learned instincts to skip redundant discovery steps. For example,
6
- # if we know the project uses FactoryBot, skip checking test_helper.rb
7
- # and looking for factories/ — just generate FactoryBot-style specs.
8
- class Shortcut
9
- SHORTCUT_RULES = {
10
- 'uses_factory_bot' => {
11
- skip: %w[test_helper factories_check],
12
- apply: { spec_template: :factory_bot_rspec }
13
- },
14
- 'uses_rspec' => {
15
- skip: %w[framework_detection],
16
- apply: { test_framework: :rspec }
17
- },
18
- 'uses_minitest' => {
19
- skip: %w[framework_detection],
20
- apply: { test_framework: :minitest }
21
- },
22
- 'uses_service_objects' => {
23
- skip: %w[pattern_detection],
24
- apply: { service_pattern: 'app/services/**/*_service.rb' }
25
- },
26
- 'uses_devise' => {
27
- skip: %w[auth_detection],
28
- apply: { auth_framework: :devise }
29
- },
30
- 'uses_grape' => {
31
- skip: %w[api_detection],
32
- apply: { api_framework: :grape }
33
- },
34
- 'uses_sidekiq' => {
35
- skip: %w[job_detection],
36
- apply: { job_framework: :sidekiq }
37
- }
38
- }.freeze
39
-
40
- attr_reader :applied_shortcuts, :tokens_saved_estimate
41
-
42
- def initialize
43
- @applied_shortcuts = []
44
- @tokens_saved_estimate = 0
45
- end
46
-
47
- # Apply shortcuts based on instinct patterns.
48
- #
49
- # @param instinct_patterns [Array<String>] patterns from the instincts table
50
- # @return [Hash] aggregated settings from applied shortcuts
51
- def apply(instinct_patterns)
52
- settings = {}
53
-
54
- instinct_patterns.each do |pattern|
55
- rule_key = match_rule(pattern)
56
- next unless rule_key
57
-
58
- rule = SHORTCUT_RULES[rule_key]
59
- settings.merge!(rule[:apply])
60
- @applied_shortcuts << { rule: rule_key, skipped: rule[:skip] }
61
- @tokens_saved_estimate += rule[:skip].size * 500
62
- end
63
-
64
- settings
65
- end
66
-
67
- # Check if a discovery step should be skipped.
68
- #
69
- # @param step_name [String] the discovery step name
70
- # @return [Boolean]
71
- def skip?(step_name)
72
- @applied_shortcuts.any? { |s| s[:skipped].include?(step_name.to_s) }
73
- end
74
-
75
- # Returns stats about shortcuts applied this session.
76
- def stats
77
- {
78
- shortcuts_applied: @applied_shortcuts.size,
79
- steps_skipped: @applied_shortcuts.sum { |s| s[:skipped].size },
80
- tokens_saved_estimate: @tokens_saved_estimate
81
- }
82
- end
83
-
84
- private
85
-
86
- def match_rule(pattern)
87
- normalized = pattern.to_s.downcase
88
- SHORTCUT_RULES.each_key do |key|
89
- return key if normalized.include?(key.tr('_', ' ')) || normalized.include?(key)
90
- end
91
- nil
92
- end
93
- end
94
- end
95
- end
@@ -1,10 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module RubynCode
4
- module LLM
5
- # Backward-compatibility shim.
6
- # Delegates to Adapters::AnthropicStreaming so existing references
7
- # to LLM::Streaming keep working during the migration.
8
- Streaming = Adapters::AnthropicStreaming
9
- end
10
- end
@@ -1,72 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'tty-prompt'
4
- require 'tty-reader'
5
- require 'pastel'
6
-
7
- module RubynCode
8
- module Protocols
9
- # Presents a plan to the user for approval before executing significant changes.
10
- #
11
- # This protocol ensures that destructive or wide-reaching operations are
12
- # reviewed by a human before proceeding.
13
- module PlanApproval
14
- APPROVED = :approved
15
- REJECTED = :rejected
16
-
17
- class << self
18
- # Displays a plan and asks the user to approve or reject it.
19
- #
20
- # @param plan_text [String] the plan description to display
21
- # @param prompt [String, nil] optional custom prompt message
22
- # @return [Symbol] :approved or :rejected
23
- def request(plan_text, prompt: nil)
24
- pastel = Pastel.new
25
- display_plan(pastel, plan_text, prompt)
26
-
27
- approved = build_prompt.yes?(
28
- pastel.yellow.bold('Do you approve this plan?'),
29
- default: false
30
- )
31
-
32
- approved ? approve(pastel) : reject(pastel)
33
- rescue TTY::Reader::InputInterrupt
34
- $stdout.puts pastel.red("\nPlan rejected (interrupted).")
35
- REJECTED
36
- end
37
-
38
- private
39
-
40
- def display_plan(pastel, plan_text, prompt)
41
- $stdout.puts
42
- $stdout.puts pastel.cyan.bold('Proposed Plan')
43
- $stdout.puts pastel.cyan('=' * 60)
44
- $stdout.puts plan_text
45
- $stdout.puts pastel.cyan('=' * 60)
46
- $stdout.puts
47
- return unless prompt
48
-
49
- $stdout.puts pastel.yellow(prompt)
50
- $stdout.puts
51
- end
52
-
53
- def approve(pastel)
54
- $stdout.puts pastel.green('Plan approved.')
55
- APPROVED
56
- end
57
-
58
- def reject(pastel)
59
- $stdout.puts pastel.red('Plan rejected.')
60
- REJECTED
61
- end
62
-
63
- # Builds a TTY::Prompt instance configured for non-destructive interrupt handling.
64
- #
65
- # @return [TTY::Prompt]
66
- def build_prompt
67
- TTY::Prompt.new(interrupt: :noop)
68
- end
69
- end
70
- end
71
- end
72
- end