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.
- checksums.yaml +4 -4
- data/README.md +137 -4
- data/db/migrations/014_multi_agent_upgrade.rb +79 -0
- data/lib/rubyn_code/agent/conversation.rb +100 -4
- data/lib/rubyn_code/agent/dynamic_tool_schema.rb +1 -1
- data/lib/rubyn_code/agent/llm_caller.rb +7 -3
- data/lib/rubyn_code/agent/loop.rb +106 -12
- data/lib/rubyn_code/agent/response_parser.rb +8 -0
- data/lib/rubyn_code/agent/system_prompt_builder.rb +40 -2
- data/lib/rubyn_code/agent/tool_processor.rb +13 -1
- data/lib/rubyn_code/auth/oauth.rb +1 -1
- data/lib/rubyn_code/auth/token_store.rb +49 -4
- data/lib/rubyn_code/autonomous/daemon.rb +1 -1
- data/lib/rubyn_code/checkpoint/hook.rb +26 -0
- data/lib/rubyn_code/checkpoint/manager.rb +109 -0
- data/lib/rubyn_code/chisel/debt.rb +65 -0
- data/lib/rubyn_code/chisel/inspection.rb +93 -0
- data/lib/rubyn_code/chisel.rb +127 -0
- data/lib/rubyn_code/cli/commands/agents.rb +31 -0
- data/lib/rubyn_code/cli/commands/chisel.rb +52 -0
- data/lib/rubyn_code/cli/commands/chisel_audit.rb +19 -0
- data/lib/rubyn_code/cli/commands/chisel_debt.rb +28 -0
- data/lib/rubyn_code/cli/commands/chisel_gain.rb +30 -0
- data/lib/rubyn_code/cli/commands/chisel_review.rb +19 -0
- data/lib/rubyn_code/cli/commands/command_template.rb +50 -0
- data/lib/rubyn_code/cli/commands/context.rb +30 -1
- data/lib/rubyn_code/cli/commands/custom_command.rb +84 -0
- data/lib/rubyn_code/cli/commands/custom_loader.rb +100 -0
- data/lib/rubyn_code/cli/commands/effort.rb +47 -0
- data/lib/rubyn_code/cli/commands/export.rb +174 -0
- data/lib/rubyn_code/cli/commands/goal.rb +87 -0
- data/lib/rubyn_code/cli/commands/learning.rb +62 -0
- data/lib/rubyn_code/cli/commands/loop.rb +58 -0
- data/lib/rubyn_code/cli/commands/mcp.rb +49 -12
- data/lib/rubyn_code/cli/commands/megaplan.rb +1 -1
- data/lib/rubyn_code/cli/commands/registry.rb +14 -9
- data/lib/rubyn_code/cli/commands/resume.rb +97 -26
- data/lib/rubyn_code/cli/commands/rewind.rb +65 -0
- data/lib/rubyn_code/cli/commands/think.rb +47 -0
- data/lib/rubyn_code/cli/first_run.rb +1 -1
- data/lib/rubyn_code/cli/loop_runner.rb +98 -0
- data/lib/rubyn_code/cli/mention_expander.rb +111 -0
- data/lib/rubyn_code/cli/renderer.rb +3 -2
- data/lib/rubyn_code/cli/repl.rb +68 -15
- data/lib/rubyn_code/cli/repl_commands.rb +76 -2
- data/lib/rubyn_code/cli/repl_setup.rb +17 -7
- data/lib/rubyn_code/cli/stream_formatter.rb +3 -2
- data/lib/rubyn_code/cli/version_check.rb +10 -3
- data/lib/rubyn_code/config/defaults.rb +14 -1
- data/lib/rubyn_code/config/schema.json +9 -0
- data/lib/rubyn_code/config/settings.rb +19 -2
- data/lib/rubyn_code/context/auto_compact.rb +1 -1
- data/lib/rubyn_code/context/manager.rb +29 -12
- data/lib/rubyn_code/context/manual_compact.rb +1 -1
- data/lib/rubyn_code/debug.rb +11 -5
- data/lib/rubyn_code/goal/evaluator.rb +95 -0
- data/lib/rubyn_code/hooks/event_map.rb +56 -0
- data/lib/rubyn_code/hooks/external_dispatcher.rb +199 -0
- data/lib/rubyn_code/hooks/goal_hook.rb +88 -0
- data/lib/rubyn_code/hooks/response.rb +83 -0
- data/lib/rubyn_code/hooks/runner.rb +61 -3
- data/lib/rubyn_code/hooks/settings_json_loader.rb +109 -0
- data/lib/rubyn_code/hooks/subprocess_executor.rb +116 -0
- data/lib/rubyn_code/ide/handlers/plan_interview_answer_handler.rb +13 -13
- data/lib/rubyn_code/ide/handlers/plan_interview_cancel_handler.rb +1 -1
- data/lib/rubyn_code/ide/handlers/plan_interview_start_handler.rb +10 -10
- data/lib/rubyn_code/ide/handlers/plan_propose_handler.rb +1 -1
- data/lib/rubyn_code/ide/handlers/prompt_handler.rb +9 -1
- data/lib/rubyn_code/ide/handlers/recover_ci_handler.rb +27 -16
- data/lib/rubyn_code/ide/handlers/session_resume_handler.rb +1 -1
- data/lib/rubyn_code/index/codebase_index.rb +103 -4
- data/lib/rubyn_code/index/prism_extractor.rb +82 -0
- data/lib/rubyn_code/learning/injector.rb +1 -2
- data/lib/rubyn_code/learning/porter.rb +129 -0
- data/lib/rubyn_code/llm/adapters/anthropic.rb +168 -29
- data/lib/rubyn_code/llm/adapters/anthropic_streaming.rb +13 -0
- data/lib/rubyn_code/llm/adapters/base.rb +2 -1
- data/lib/rubyn_code/llm/adapters/openai.rb +2 -2
- data/lib/rubyn_code/llm/adapters/openai_message_translator.rb +21 -0
- data/lib/rubyn_code/llm/adapters/prompt_caching.rb +5 -1
- data/lib/rubyn_code/llm/client.rb +16 -3
- data/lib/rubyn_code/llm/image_reader.rb +60 -0
- data/lib/rubyn_code/llm/message_builder.rb +21 -1
- data/lib/rubyn_code/llm/model_router.rb +4 -4
- data/lib/rubyn_code/mcp/client.rb +59 -0
- data/lib/rubyn_code/mcp/discovery.rb +93 -0
- data/lib/rubyn_code/mcp/server_extras_bridge.rb +110 -0
- data/lib/rubyn_code/mcp/sse_transport.rb +2 -1
- data/lib/rubyn_code/mcp/tool_bridge.rb +16 -14
- data/lib/rubyn_code/megaplan/ci_recovery.rb +3 -3
- data/lib/rubyn_code/megaplan/interview_session.rb +8 -3
- data/lib/rubyn_code/megaplan/plan_proposer.rb +3 -3
- data/lib/rubyn_code/memory/search.rb +9 -5
- data/lib/rubyn_code/memory/session_persistence.rb +159 -21
- data/lib/rubyn_code/observability/cost_calculator.rb +8 -3
- data/lib/rubyn_code/output/diff_renderer.rb +62 -7
- data/lib/rubyn_code/protocols/RUBYN.md +0 -3
- data/lib/rubyn_code/skills/auto_suggest.rb +70 -2
- data/lib/rubyn_code/skills/registry_client.rb +4 -3
- data/lib/rubyn_code/sub_agents/agent_type.rb +17 -0
- data/lib/rubyn_code/sub_agents/catalog.rb +124 -0
- data/lib/rubyn_code/tasks/models.rb +0 -16
- data/lib/rubyn_code/teams/agent_registry.rb +120 -0
- data/lib/rubyn_code/teams/mailbox.rb +99 -10
- data/lib/rubyn_code/teams/manager.rb +83 -5
- data/lib/rubyn_code/teams/teammate.rb +3 -14
- data/lib/rubyn_code/tools/RUBYN.md +2 -2
- data/lib/rubyn_code/tools/ask_user.rb +15 -1
- data/lib/rubyn_code/tools/bash.rb +3 -3
- data/lib/rubyn_code/tools/code_graph.rb +134 -0
- data/lib/rubyn_code/tools/executor.rb +9 -4
- data/lib/rubyn_code/tools/spawn_agent.rb +47 -62
- data/lib/rubyn_code/tools/spawn_teammate.rb +7 -2
- data/lib/rubyn_code/tools/todo_store.rb +55 -0
- data/lib/rubyn_code/tools/todo_write.rb +88 -0
- data/lib/rubyn_code/tools/web_fetch.rb +1 -1
- data/lib/rubyn_code/tools/web_search.rb +4 -1
- data/lib/rubyn_code/version.rb +1 -1
- data/lib/rubyn_code.rb +57 -9
- data/skills/rubyn_self_test.md +462 -14
- data/skills/self_test/chisel_smoke.rb +84 -0
- data/skills/self_test/fixtures/chisel_sample.rb +64 -0
- metadata +45 -6
- data/lib/rubyn_code/context/context_budget.rb +0 -183
- data/lib/rubyn_code/context/schema_filter.rb +0 -64
- data/lib/rubyn_code/learning/shortcut.rb +0 -95
- data/lib/rubyn_code/llm/streaming.rb +0 -10
- 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.
|
|
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/
|
|
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
|