rubyn-code 0.1.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 +7 -0
- data/LICENSE +21 -0
- data/README.md +620 -0
- data/db/migrations/000_create_schema_migrations.sql +4 -0
- data/db/migrations/001_create_sessions.sql +16 -0
- data/db/migrations/002_create_messages.sql +16 -0
- data/db/migrations/003_create_tasks.sql +17 -0
- data/db/migrations/004_create_task_dependencies.sql +8 -0
- data/db/migrations/005_create_memories.sql +44 -0
- data/db/migrations/006_create_cost_records.sql +16 -0
- data/db/migrations/007_create_hooks.sql +12 -0
- data/db/migrations/008_create_skills_cache.sql +8 -0
- data/db/migrations/009_create_teams.sql +27 -0
- data/db/migrations/010_create_instincts.sql +15 -0
- data/exe/rubyn-code +6 -0
- data/lib/rubyn_code/agent/conversation.rb +193 -0
- data/lib/rubyn_code/agent/loop.rb +517 -0
- data/lib/rubyn_code/agent/loop_detector.rb +78 -0
- data/lib/rubyn_code/auth/oauth.rb +174 -0
- data/lib/rubyn_code/auth/server.rb +126 -0
- data/lib/rubyn_code/auth/token_store.rb +153 -0
- data/lib/rubyn_code/autonomous/daemon.rb +233 -0
- data/lib/rubyn_code/autonomous/idle_poller.rb +111 -0
- data/lib/rubyn_code/autonomous/task_claimer.rb +100 -0
- data/lib/rubyn_code/background/job.rb +19 -0
- data/lib/rubyn_code/background/notifier.rb +44 -0
- data/lib/rubyn_code/background/worker.rb +146 -0
- data/lib/rubyn_code/cli/app.rb +118 -0
- data/lib/rubyn_code/cli/input_handler.rb +79 -0
- data/lib/rubyn_code/cli/renderer.rb +205 -0
- data/lib/rubyn_code/cli/repl.rb +519 -0
- data/lib/rubyn_code/cli/spinner.rb +100 -0
- data/lib/rubyn_code/cli/stream_formatter.rb +149 -0
- data/lib/rubyn_code/config/defaults.rb +43 -0
- data/lib/rubyn_code/config/project_config.rb +120 -0
- data/lib/rubyn_code/config/settings.rb +127 -0
- data/lib/rubyn_code/context/auto_compact.rb +81 -0
- data/lib/rubyn_code/context/compactor.rb +89 -0
- data/lib/rubyn_code/context/manager.rb +91 -0
- data/lib/rubyn_code/context/manual_compact.rb +87 -0
- data/lib/rubyn_code/context/micro_compact.rb +135 -0
- data/lib/rubyn_code/db/connection.rb +176 -0
- data/lib/rubyn_code/db/migrator.rb +146 -0
- data/lib/rubyn_code/db/schema.rb +106 -0
- data/lib/rubyn_code/hooks/built_in.rb +124 -0
- data/lib/rubyn_code/hooks/registry.rb +99 -0
- data/lib/rubyn_code/hooks/runner.rb +88 -0
- data/lib/rubyn_code/hooks/user_hooks.rb +90 -0
- data/lib/rubyn_code/learning/extractor.rb +191 -0
- data/lib/rubyn_code/learning/injector.rb +138 -0
- data/lib/rubyn_code/learning/instinct.rb +172 -0
- data/lib/rubyn_code/llm/client.rb +218 -0
- data/lib/rubyn_code/llm/message_builder.rb +116 -0
- data/lib/rubyn_code/llm/streaming.rb +203 -0
- data/lib/rubyn_code/mcp/client.rb +139 -0
- data/lib/rubyn_code/mcp/config.rb +83 -0
- data/lib/rubyn_code/mcp/sse_transport.rb +225 -0
- data/lib/rubyn_code/mcp/stdio_transport.rb +196 -0
- data/lib/rubyn_code/mcp/tool_bridge.rb +164 -0
- data/lib/rubyn_code/memory/models.rb +62 -0
- data/lib/rubyn_code/memory/search.rb +181 -0
- data/lib/rubyn_code/memory/session_persistence.rb +194 -0
- data/lib/rubyn_code/memory/store.rb +199 -0
- data/lib/rubyn_code/observability/budget_enforcer.rb +159 -0
- data/lib/rubyn_code/observability/cost_calculator.rb +61 -0
- data/lib/rubyn_code/observability/models.rb +29 -0
- data/lib/rubyn_code/observability/token_counter.rb +42 -0
- data/lib/rubyn_code/observability/usage_reporter.rb +140 -0
- data/lib/rubyn_code/output/diff_renderer.rb +212 -0
- data/lib/rubyn_code/output/formatter.rb +120 -0
- data/lib/rubyn_code/permissions/deny_list.rb +49 -0
- data/lib/rubyn_code/permissions/policy.rb +59 -0
- data/lib/rubyn_code/permissions/prompter.rb +80 -0
- data/lib/rubyn_code/permissions/tier.rb +22 -0
- data/lib/rubyn_code/protocols/interrupt_handler.rb +95 -0
- data/lib/rubyn_code/protocols/plan_approval.rb +67 -0
- data/lib/rubyn_code/protocols/shutdown_handshake.rb +109 -0
- data/lib/rubyn_code/skills/catalog.rb +70 -0
- data/lib/rubyn_code/skills/document.rb +80 -0
- data/lib/rubyn_code/skills/loader.rb +57 -0
- data/lib/rubyn_code/sub_agents/runner.rb +168 -0
- data/lib/rubyn_code/sub_agents/summarizer.rb +57 -0
- data/lib/rubyn_code/tasks/dag.rb +208 -0
- data/lib/rubyn_code/tasks/manager.rb +212 -0
- data/lib/rubyn_code/tasks/models.rb +31 -0
- data/lib/rubyn_code/teams/mailbox.rb +128 -0
- data/lib/rubyn_code/teams/manager.rb +175 -0
- data/lib/rubyn_code/teams/teammate.rb +38 -0
- data/lib/rubyn_code/tools/background_run.rb +41 -0
- data/lib/rubyn_code/tools/base.rb +84 -0
- data/lib/rubyn_code/tools/bash.rb +81 -0
- data/lib/rubyn_code/tools/bundle_add.rb +53 -0
- data/lib/rubyn_code/tools/bundle_install.rb +41 -0
- data/lib/rubyn_code/tools/compact.rb +57 -0
- data/lib/rubyn_code/tools/db_migrate.rb +52 -0
- data/lib/rubyn_code/tools/edit_file.rb +49 -0
- data/lib/rubyn_code/tools/executor.rb +62 -0
- data/lib/rubyn_code/tools/git_commit.rb +97 -0
- data/lib/rubyn_code/tools/git_diff.rb +61 -0
- data/lib/rubyn_code/tools/git_log.rb +59 -0
- data/lib/rubyn_code/tools/git_status.rb +59 -0
- data/lib/rubyn_code/tools/glob.rb +44 -0
- data/lib/rubyn_code/tools/grep.rb +81 -0
- data/lib/rubyn_code/tools/load_skill.rb +41 -0
- data/lib/rubyn_code/tools/memory_search.rb +77 -0
- data/lib/rubyn_code/tools/memory_write.rb +52 -0
- data/lib/rubyn_code/tools/rails_generate.rb +54 -0
- data/lib/rubyn_code/tools/read_file.rb +38 -0
- data/lib/rubyn_code/tools/read_inbox.rb +64 -0
- data/lib/rubyn_code/tools/registry.rb +48 -0
- data/lib/rubyn_code/tools/review_pr.rb +145 -0
- data/lib/rubyn_code/tools/run_specs.rb +75 -0
- data/lib/rubyn_code/tools/schema.rb +59 -0
- data/lib/rubyn_code/tools/send_message.rb +53 -0
- data/lib/rubyn_code/tools/spawn_agent.rb +154 -0
- data/lib/rubyn_code/tools/spawn_teammate.rb +168 -0
- data/lib/rubyn_code/tools/task.rb +148 -0
- data/lib/rubyn_code/tools/web_fetch.rb +108 -0
- data/lib/rubyn_code/tools/web_search.rb +196 -0
- data/lib/rubyn_code/tools/write_file.rb +30 -0
- data/lib/rubyn_code/version.rb +5 -0
- data/lib/rubyn_code.rb +203 -0
- data/skills/code_quality/fits_in_your_head.md +189 -0
- data/skills/code_quality/naming_conventions.md +213 -0
- data/skills/code_quality/null_object.md +205 -0
- data/skills/code_quality/technical_debt.md +135 -0
- data/skills/code_quality/value_objects.md +216 -0
- data/skills/code_quality/yagni.md +176 -0
- data/skills/design_patterns/adapter.md +191 -0
- data/skills/design_patterns/bridge_memento_visitor.md +254 -0
- data/skills/design_patterns/builder.md +158 -0
- data/skills/design_patterns/command.md +126 -0
- data/skills/design_patterns/composite.md +147 -0
- data/skills/design_patterns/decorator.md +204 -0
- data/skills/design_patterns/facade.md +133 -0
- data/skills/design_patterns/factory_method.md +169 -0
- data/skills/design_patterns/iterator.md +116 -0
- data/skills/design_patterns/mediator.md +133 -0
- data/skills/design_patterns/observer.md +177 -0
- data/skills/design_patterns/proxy.md +140 -0
- data/skills/design_patterns/singleton.md +124 -0
- data/skills/design_patterns/state.md +207 -0
- data/skills/design_patterns/strategy.md +127 -0
- data/skills/design_patterns/template_method.md +173 -0
- data/skills/gems/devise.md +365 -0
- data/skills/gems/dry_rb.md +186 -0
- data/skills/gems/factory_bot.md +268 -0
- data/skills/gems/faraday.md +263 -0
- data/skills/gems/graphql_ruby.md +514 -0
- data/skills/gems/pundit.md +446 -0
- data/skills/gems/redis.md +219 -0
- data/skills/gems/rubocop.md +257 -0
- data/skills/gems/sidekiq.md +360 -0
- data/skills/gems/stripe.md +224 -0
- data/skills/minitest/assertions.md +185 -0
- data/skills/minitest/fixtures.md +238 -0
- data/skills/minitest/integration_tests.md +210 -0
- data/skills/minitest/mailers_and_jobs.md +218 -0
- data/skills/minitest/mocking_stubbing.md +202 -0
- data/skills/minitest/service_tests_and_performance.md +246 -0
- data/skills/minitest/structure_and_conventions.md +169 -0
- data/skills/minitest/system_tests.md +237 -0
- data/skills/rails/action_cable.md +160 -0
- data/skills/rails/active_record_basics.md +174 -0
- data/skills/rails/active_storage.md +242 -0
- data/skills/rails/api_design.md +212 -0
- data/skills/rails/associations.md +182 -0
- data/skills/rails/background_jobs.md +212 -0
- data/skills/rails/caching.md +158 -0
- data/skills/rails/callbacks.md +135 -0
- data/skills/rails/concerns_controllers.md +218 -0
- data/skills/rails/concerns_models.md +280 -0
- data/skills/rails/controllers.md +190 -0
- data/skills/rails/engines.md +201 -0
- data/skills/rails/form_objects.md +168 -0
- data/skills/rails/hotwire.md +229 -0
- data/skills/rails/internationalization.md +192 -0
- data/skills/rails/logging.md +198 -0
- data/skills/rails/mailers.md +180 -0
- data/skills/rails/migrations.md +200 -0
- data/skills/rails/multitenancy.md +207 -0
- data/skills/rails/n_plus_one.md +151 -0
- data/skills/rails/presenters.md +244 -0
- data/skills/rails/query_objects.md +177 -0
- data/skills/rails/routing.md +194 -0
- data/skills/rails/scopes.md +187 -0
- data/skills/rails/security.md +233 -0
- data/skills/rails/serializers.md +243 -0
- data/skills/rails/service_objects.md +184 -0
- data/skills/rails/testing_strategy.md +258 -0
- data/skills/rails/validations.md +206 -0
- data/skills/refactoring/code_smells.md +251 -0
- data/skills/refactoring/command_query_separation.md +166 -0
- data/skills/refactoring/encapsulate_collection.md +125 -0
- data/skills/refactoring/extract_class.md +138 -0
- data/skills/refactoring/extract_method.md +185 -0
- data/skills/refactoring/replace_conditional.md +211 -0
- data/skills/refactoring/value_objects.md +246 -0
- data/skills/rspec/build_stubbed.md +199 -0
- data/skills/rspec/factory_design.md +206 -0
- data/skills/rspec/let_vs_let_bang.md +161 -0
- data/skills/rspec/mocking_stubbing.md +209 -0
- data/skills/rspec/request_specs.md +212 -0
- data/skills/rspec/service_specs.md +262 -0
- data/skills/rspec/shared_examples.md +244 -0
- data/skills/rspec/system_specs.md +286 -0
- data/skills/rspec/test_performance.md +215 -0
- data/skills/ruby/blocks_procs_lambdas.md +204 -0
- data/skills/ruby/classes.md +155 -0
- data/skills/ruby/concurrency.md +194 -0
- data/skills/ruby/data_struct_openstruct.md +158 -0
- data/skills/ruby/debugging_profiling.md +204 -0
- data/skills/ruby/enumerable_patterns.md +168 -0
- data/skills/ruby/exception_handling.md +199 -0
- data/skills/ruby/file_io.md +217 -0
- data/skills/ruby/hashes.md +195 -0
- data/skills/ruby/metaprogramming.md +170 -0
- data/skills/ruby/modules.md +210 -0
- data/skills/ruby/pattern_matching.md +177 -0
- data/skills/ruby/regular_expressions.md +166 -0
- data/skills/ruby/result_objects.md +200 -0
- data/skills/ruby/strings.md +177 -0
- data/skills/ruby_project/bundler_dependencies.md +181 -0
- data/skills/ruby_project/cli_tools.md +224 -0
- data/skills/ruby_project/rake_tasks.md +146 -0
- data/skills/ruby_project/structure.md +261 -0
- data/skills/sinatra/application_structure.md +241 -0
- data/skills/sinatra/middleware_and_deployment.md +221 -0
- data/skills/sinatra/testing.md +233 -0
- data/skills/solid/dependency_inversion.md +195 -0
- data/skills/solid/interface_segregation.md +237 -0
- data/skills/solid/liskov_substitution.md +263 -0
- data/skills/solid/open_closed.md +212 -0
- data/skills/solid/single_responsibility.md +183 -0
- metadata +397 -0
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
CREATE TABLE IF NOT EXISTS tasks (
|
|
2
|
+
id TEXT PRIMARY KEY,
|
|
3
|
+
session_id TEXT REFERENCES sessions(id) ON DELETE SET NULL,
|
|
4
|
+
title TEXT NOT NULL,
|
|
5
|
+
description TEXT,
|
|
6
|
+
status TEXT NOT NULL DEFAULT 'pending' CHECK(status IN ('pending','in_progress','blocked','completed','cancelled')),
|
|
7
|
+
priority INTEGER NOT NULL DEFAULT 0,
|
|
8
|
+
owner TEXT,
|
|
9
|
+
result TEXT,
|
|
10
|
+
metadata TEXT DEFAULT '{}',
|
|
11
|
+
created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%SZ','now')),
|
|
12
|
+
updated_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%SZ','now'))
|
|
13
|
+
);
|
|
14
|
+
|
|
15
|
+
CREATE INDEX IF NOT EXISTS idx_tasks_session ON tasks(session_id);
|
|
16
|
+
CREATE INDEX IF NOT EXISTS idx_tasks_status ON tasks(status);
|
|
17
|
+
CREATE INDEX IF NOT EXISTS idx_tasks_owner ON tasks(owner);
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
CREATE TABLE IF NOT EXISTS task_dependencies (
|
|
2
|
+
task_id TEXT NOT NULL REFERENCES tasks(id) ON DELETE CASCADE,
|
|
3
|
+
depends_on_id TEXT NOT NULL REFERENCES tasks(id) ON DELETE CASCADE,
|
|
4
|
+
PRIMARY KEY (task_id, depends_on_id),
|
|
5
|
+
CHECK(task_id != depends_on_id)
|
|
6
|
+
);
|
|
7
|
+
|
|
8
|
+
CREATE INDEX IF NOT EXISTS idx_task_deps_depends_on ON task_dependencies(depends_on_id);
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
CREATE TABLE IF NOT EXISTS memories (
|
|
2
|
+
id TEXT PRIMARY KEY,
|
|
3
|
+
project_path TEXT NOT NULL,
|
|
4
|
+
tier TEXT NOT NULL CHECK(tier IN ('short','medium','long')),
|
|
5
|
+
category TEXT,
|
|
6
|
+
content TEXT NOT NULL,
|
|
7
|
+
relevance_score REAL NOT NULL DEFAULT 1.0,
|
|
8
|
+
access_count INTEGER NOT NULL DEFAULT 0,
|
|
9
|
+
last_accessed_at TEXT,
|
|
10
|
+
expires_at TEXT,
|
|
11
|
+
metadata TEXT DEFAULT '{}',
|
|
12
|
+
created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%SZ','now'))
|
|
13
|
+
);
|
|
14
|
+
|
|
15
|
+
CREATE INDEX IF NOT EXISTS idx_memories_project ON memories(project_path);
|
|
16
|
+
CREATE INDEX IF NOT EXISTS idx_memories_tier ON memories(tier);
|
|
17
|
+
CREATE INDEX IF NOT EXISTS idx_memories_category ON memories(project_path, category);
|
|
18
|
+
CREATE INDEX IF NOT EXISTS idx_memories_expires ON memories(expires_at);
|
|
19
|
+
|
|
20
|
+
CREATE VIRTUAL TABLE IF NOT EXISTS memories_fts USING fts5(
|
|
21
|
+
content,
|
|
22
|
+
category,
|
|
23
|
+
content=memories,
|
|
24
|
+
content_rowid=rowid,
|
|
25
|
+
tokenize='porter unicode61'
|
|
26
|
+
);
|
|
27
|
+
|
|
28
|
+
-- Triggers to keep FTS index in sync with the memories table
|
|
29
|
+
CREATE TRIGGER IF NOT EXISTS memories_ai AFTER INSERT ON memories BEGIN
|
|
30
|
+
INSERT INTO memories_fts(rowid, content, category)
|
|
31
|
+
VALUES (NEW.rowid, NEW.content, NEW.category);
|
|
32
|
+
END;
|
|
33
|
+
|
|
34
|
+
CREATE TRIGGER IF NOT EXISTS memories_ad AFTER DELETE ON memories BEGIN
|
|
35
|
+
INSERT INTO memories_fts(memories_fts, rowid, content, category)
|
|
36
|
+
VALUES ('delete', OLD.rowid, OLD.content, OLD.category);
|
|
37
|
+
END;
|
|
38
|
+
|
|
39
|
+
CREATE TRIGGER IF NOT EXISTS memories_au AFTER UPDATE ON memories BEGIN
|
|
40
|
+
INSERT INTO memories_fts(memories_fts, rowid, content, category)
|
|
41
|
+
VALUES ('delete', OLD.rowid, OLD.content, OLD.category);
|
|
42
|
+
INSERT INTO memories_fts(rowid, content, category)
|
|
43
|
+
VALUES (NEW.rowid, NEW.content, NEW.category);
|
|
44
|
+
END;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
CREATE TABLE IF NOT EXISTS cost_records (
|
|
2
|
+
id TEXT PRIMARY KEY,
|
|
3
|
+
session_id TEXT NOT NULL REFERENCES sessions(id) ON DELETE CASCADE,
|
|
4
|
+
model TEXT NOT NULL,
|
|
5
|
+
input_tokens INTEGER NOT NULL DEFAULT 0,
|
|
6
|
+
output_tokens INTEGER NOT NULL DEFAULT 0,
|
|
7
|
+
cache_read_tokens INTEGER NOT NULL DEFAULT 0,
|
|
8
|
+
cache_write_tokens INTEGER NOT NULL DEFAULT 0,
|
|
9
|
+
cost_usd REAL NOT NULL DEFAULT 0.0,
|
|
10
|
+
request_type TEXT,
|
|
11
|
+
created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%SZ','now'))
|
|
12
|
+
);
|
|
13
|
+
|
|
14
|
+
CREATE INDEX IF NOT EXISTS idx_cost_records_session ON cost_records(session_id);
|
|
15
|
+
CREATE INDEX IF NOT EXISTS idx_cost_records_model ON cost_records(model);
|
|
16
|
+
CREATE INDEX IF NOT EXISTS idx_cost_records_created ON cost_records(created_at);
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
CREATE TABLE IF NOT EXISTS hook_configs (
|
|
2
|
+
id TEXT PRIMARY KEY,
|
|
3
|
+
event_type TEXT NOT NULL,
|
|
4
|
+
handler_type TEXT NOT NULL,
|
|
5
|
+
handler_config TEXT NOT NULL DEFAULT '{}',
|
|
6
|
+
enabled INTEGER NOT NULL DEFAULT 1,
|
|
7
|
+
priority INTEGER NOT NULL DEFAULT 0,
|
|
8
|
+
created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%SZ','now'))
|
|
9
|
+
);
|
|
10
|
+
|
|
11
|
+
CREATE INDEX IF NOT EXISTS idx_hooks_event ON hook_configs(event_type);
|
|
12
|
+
CREATE INDEX IF NOT EXISTS idx_hooks_enabled ON hook_configs(enabled, event_type);
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
CREATE TABLE IF NOT EXISTS skills_cache (
|
|
2
|
+
id TEXT PRIMARY KEY,
|
|
3
|
+
skill_name TEXT NOT NULL UNIQUE,
|
|
4
|
+
content_hash TEXT NOT NULL,
|
|
5
|
+
loaded_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%SZ','now'))
|
|
6
|
+
);
|
|
7
|
+
|
|
8
|
+
CREATE INDEX IF NOT EXISTS idx_skills_cache_name ON skills_cache(skill_name);
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
CREATE TABLE IF NOT EXISTS teammates (
|
|
2
|
+
id TEXT PRIMARY KEY,
|
|
3
|
+
name TEXT NOT NULL UNIQUE,
|
|
4
|
+
role TEXT NOT NULL,
|
|
5
|
+
persona TEXT,
|
|
6
|
+
model TEXT NOT NULL DEFAULT 'claude-sonnet-4-20250514',
|
|
7
|
+
status TEXT NOT NULL DEFAULT 'idle' CHECK(status IN ('idle','busy','offline')),
|
|
8
|
+
metadata TEXT DEFAULT '{}',
|
|
9
|
+
created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%SZ','now'))
|
|
10
|
+
);
|
|
11
|
+
|
|
12
|
+
CREATE INDEX IF NOT EXISTS idx_teammates_name ON teammates(name);
|
|
13
|
+
CREATE INDEX IF NOT EXISTS idx_teammates_status ON teammates(status);
|
|
14
|
+
|
|
15
|
+
CREATE TABLE IF NOT EXISTS mailbox_messages (
|
|
16
|
+
id TEXT PRIMARY KEY,
|
|
17
|
+
from_agent TEXT NOT NULL,
|
|
18
|
+
to_agent TEXT NOT NULL,
|
|
19
|
+
content TEXT NOT NULL,
|
|
20
|
+
message_type TEXT NOT NULL DEFAULT 'text' CHECK(message_type IN ('text','task','result','error')),
|
|
21
|
+
read INTEGER NOT NULL DEFAULT 0,
|
|
22
|
+
created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%SZ','now'))
|
|
23
|
+
);
|
|
24
|
+
|
|
25
|
+
CREATE INDEX IF NOT EXISTS idx_mailbox_to ON mailbox_messages(to_agent, read);
|
|
26
|
+
CREATE INDEX IF NOT EXISTS idx_mailbox_from ON mailbox_messages(from_agent);
|
|
27
|
+
CREATE INDEX IF NOT EXISTS idx_mailbox_created ON mailbox_messages(created_at);
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
CREATE TABLE IF NOT EXISTS instincts (
|
|
2
|
+
id TEXT PRIMARY KEY,
|
|
3
|
+
project_path TEXT NOT NULL,
|
|
4
|
+
pattern TEXT NOT NULL,
|
|
5
|
+
context_tags TEXT DEFAULT '[]',
|
|
6
|
+
confidence REAL NOT NULL DEFAULT 0.5,
|
|
7
|
+
decay_rate REAL NOT NULL DEFAULT 0.01,
|
|
8
|
+
times_applied INTEGER NOT NULL DEFAULT 0,
|
|
9
|
+
times_helpful INTEGER NOT NULL DEFAULT 0,
|
|
10
|
+
created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%SZ','now')),
|
|
11
|
+
updated_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%SZ','now'))
|
|
12
|
+
);
|
|
13
|
+
|
|
14
|
+
CREATE INDEX IF NOT EXISTS idx_instincts_project ON instincts(project_path);
|
|
15
|
+
CREATE INDEX IF NOT EXISTS idx_instincts_confidence ON instincts(confidence DESC);
|
data/exe/rubyn-code
ADDED
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RubynCode
|
|
4
|
+
module Agent
|
|
5
|
+
class Conversation
|
|
6
|
+
attr_reader :messages
|
|
7
|
+
|
|
8
|
+
def initialize
|
|
9
|
+
@messages = []
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
# Append a user turn to the conversation.
|
|
13
|
+
#
|
|
14
|
+
# @param content [String]
|
|
15
|
+
# @return [Hash] the appended message
|
|
16
|
+
def add_user_message(content)
|
|
17
|
+
message = { role: "user", content: content }
|
|
18
|
+
@messages << message
|
|
19
|
+
message
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
# Append an assistant turn to the conversation.
|
|
23
|
+
#
|
|
24
|
+
# @param content [Array<Hash>, String, nil] text blocks from the response
|
|
25
|
+
# @param tool_calls [Array<Hash>] tool_use blocks from the response
|
|
26
|
+
# @return [Hash] the appended message
|
|
27
|
+
def add_assistant_message(content, tool_calls: [])
|
|
28
|
+
blocks = normalize_content(content, tool_calls)
|
|
29
|
+
message = { role: "assistant", content: blocks }
|
|
30
|
+
@messages << message
|
|
31
|
+
message
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# Append a tool result turn to the conversation.
|
|
35
|
+
#
|
|
36
|
+
# @param tool_use_id [String]
|
|
37
|
+
# @param tool_name [String]
|
|
38
|
+
# @param output [String]
|
|
39
|
+
# @param is_error [Boolean]
|
|
40
|
+
# @return [Hash] the appended message
|
|
41
|
+
def add_tool_result(tool_use_id, tool_name, output, is_error: false)
|
|
42
|
+
result_block = {
|
|
43
|
+
type: "tool_result",
|
|
44
|
+
tool_use_id: tool_use_id,
|
|
45
|
+
content: output.to_s
|
|
46
|
+
}
|
|
47
|
+
result_block[:is_error] = true if is_error
|
|
48
|
+
|
|
49
|
+
# The Claude API expects tool results as a user message whose content
|
|
50
|
+
# is an array of tool_result blocks. When the previous message is
|
|
51
|
+
# already a user/tool_result message we append to it so that multiple
|
|
52
|
+
# tool results for the same assistant turn are batched together.
|
|
53
|
+
if @messages.last && @messages.last[:role] == "user" && tool_result_message?(@messages.last)
|
|
54
|
+
@messages.last[:content] << result_block
|
|
55
|
+
else
|
|
56
|
+
@messages << { role: "user", content: [result_block] }
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
result_block
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
# Extract the text from the most recent assistant message.
|
|
63
|
+
#
|
|
64
|
+
# @return [String, nil]
|
|
65
|
+
def last_assistant_text
|
|
66
|
+
assistant_msg = @messages.reverse_each.find { |m| m[:role] == "assistant" }
|
|
67
|
+
return nil unless assistant_msg
|
|
68
|
+
|
|
69
|
+
extract_text(assistant_msg[:content])
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
# @return [Integer]
|
|
73
|
+
def length
|
|
74
|
+
@messages.length
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
# Reset the conversation to an empty state.
|
|
78
|
+
#
|
|
79
|
+
# @return [void]
|
|
80
|
+
def clear!
|
|
81
|
+
@messages.clear
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
# Return the messages array formatted for the Claude API.
|
|
85
|
+
# Ensures proper role alternation and content structure.
|
|
86
|
+
#
|
|
87
|
+
# @return [Array<Hash>]
|
|
88
|
+
def to_api_format
|
|
89
|
+
@messages.map do |msg|
|
|
90
|
+
{
|
|
91
|
+
role: msg[:role],
|
|
92
|
+
content: format_content(msg[:content])
|
|
93
|
+
}
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
# Remove the last user + assistant exchange. Useful for undo.
|
|
98
|
+
# If the last two messages are assistant then user (most recent first),
|
|
99
|
+
# removes both. Otherwise removes only the last message.
|
|
100
|
+
#
|
|
101
|
+
# @return [void]
|
|
102
|
+
def undo_last!
|
|
103
|
+
return if @messages.empty?
|
|
104
|
+
|
|
105
|
+
# Walk backwards and remove the most recent user+assistant pair.
|
|
106
|
+
# The typical pattern is: [..., user, assistant] or
|
|
107
|
+
# [..., assistant, user(tool_results)].
|
|
108
|
+
removed = 0
|
|
109
|
+
while @messages.any? && removed < 2
|
|
110
|
+
last = @messages.last
|
|
111
|
+
break if removed == 1 && last[:role] != "assistant" && last[:role] != "user"
|
|
112
|
+
|
|
113
|
+
@messages.pop
|
|
114
|
+
removed += 1
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
private
|
|
119
|
+
|
|
120
|
+
# Normalize content and tool_calls into a single array of content blocks.
|
|
121
|
+
def normalize_content(content, tool_calls)
|
|
122
|
+
blocks = []
|
|
123
|
+
|
|
124
|
+
case content
|
|
125
|
+
when Array
|
|
126
|
+
content.each { |b| blocks << block_to_hash(b) }
|
|
127
|
+
when String
|
|
128
|
+
blocks << { type: "text", text: content } unless content.empty?
|
|
129
|
+
when Hash
|
|
130
|
+
blocks << content
|
|
131
|
+
else
|
|
132
|
+
blocks << block_to_hash(content) if content.respond_to?(:type)
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
tool_calls.each do |tc|
|
|
136
|
+
blocks << block_to_hash(tc)
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
blocks
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
# Format message content for the API. Converts Data objects to hashes.
|
|
143
|
+
def format_content(content)
|
|
144
|
+
case content
|
|
145
|
+
when String then content
|
|
146
|
+
when Array
|
|
147
|
+
content.map { |block| block_to_hash(block) }
|
|
148
|
+
else ""
|
|
149
|
+
end
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
def block_to_hash(block)
|
|
153
|
+
return block if block.is_a?(Hash)
|
|
154
|
+
|
|
155
|
+
if block.respond_to?(:type)
|
|
156
|
+
case block.type.to_s
|
|
157
|
+
when "text"
|
|
158
|
+
{ type: "text", text: block.text }
|
|
159
|
+
when "tool_use"
|
|
160
|
+
{ type: "tool_use", id: block.id, name: block.name, input: block.input }
|
|
161
|
+
when "tool_result"
|
|
162
|
+
h = { type: "tool_result", tool_use_id: block.tool_use_id, content: block.content.to_s }
|
|
163
|
+
h[:is_error] = true if block.respond_to?(:is_error) && block.is_error
|
|
164
|
+
h
|
|
165
|
+
else
|
|
166
|
+
block.respond_to?(:to_h) ? block.to_h : block
|
|
167
|
+
end
|
|
168
|
+
else
|
|
169
|
+
block
|
|
170
|
+
end
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
# Extract text from content blocks.
|
|
174
|
+
def extract_text(content)
|
|
175
|
+
case content
|
|
176
|
+
when String
|
|
177
|
+
content
|
|
178
|
+
when Array
|
|
179
|
+
text_blocks = content.select { |b| b.is_a?(Hash) && b[:type] == "text" }
|
|
180
|
+
texts = text_blocks.map { |b| b[:text] }
|
|
181
|
+
texts.empty? ? nil : texts.join("\n")
|
|
182
|
+
end
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
# Check whether a message is a tool-result-bearing user message.
|
|
186
|
+
def tool_result_message?(msg)
|
|
187
|
+
return false unless msg[:content].is_a?(Array)
|
|
188
|
+
|
|
189
|
+
msg[:content].all? { |b| b.is_a?(Hash) && b[:type] == "tool_result" }
|
|
190
|
+
end
|
|
191
|
+
end
|
|
192
|
+
end
|
|
193
|
+
end
|