rubyn-code 0.1.0 → 0.2.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 +269 -467
- data/db/migrations/009_create_teams.sql +6 -6
- data/db/migrations/011_fix_mailbox_messages_columns.rb +35 -0
- data/db/migrations/012_expand_mailbox_message_types.rb +37 -0
- data/exe/rubyn-code +1 -1
- data/lib/rubyn_code/agent/RUBYN.md +17 -0
- data/lib/rubyn_code/agent/conversation.rb +68 -19
- data/lib/rubyn_code/agent/loop.rb +312 -54
- data/lib/rubyn_code/agent/loop_detector.rb +6 -6
- data/lib/rubyn_code/auth/RUBYN.md +19 -0
- data/lib/rubyn_code/auth/oauth.rb +40 -35
- data/lib/rubyn_code/auth/server.rb +16 -12
- data/lib/rubyn_code/auth/token_store.rb +22 -22
- data/lib/rubyn_code/autonomous/RUBYN.md +14 -0
- data/lib/rubyn_code/autonomous/daemon.rb +115 -79
- data/lib/rubyn_code/autonomous/idle_poller.rb +4 -8
- data/lib/rubyn_code/autonomous/task_claimer.rb +11 -11
- data/lib/rubyn_code/background/RUBYN.md +13 -0
- data/lib/rubyn_code/background/notifier.rb +0 -2
- data/lib/rubyn_code/background/worker.rb +60 -15
- data/lib/rubyn_code/cli/RUBYN.md +30 -0
- data/lib/rubyn_code/cli/app.rb +85 -9
- data/lib/rubyn_code/cli/commands/RUBYN.md +133 -0
- data/lib/rubyn_code/cli/commands/base.rb +53 -0
- data/lib/rubyn_code/cli/commands/budget.rb +24 -0
- data/lib/rubyn_code/cli/commands/clear.rb +16 -0
- data/lib/rubyn_code/cli/commands/compact.rb +21 -0
- data/lib/rubyn_code/cli/commands/context.rb +44 -0
- data/lib/rubyn_code/cli/commands/context_info.rb +56 -0
- data/lib/rubyn_code/cli/commands/cost.rb +23 -0
- data/lib/rubyn_code/cli/commands/diff.rb +30 -0
- data/lib/rubyn_code/cli/commands/doctor.rb +112 -0
- data/lib/rubyn_code/cli/commands/help.rb +41 -0
- data/lib/rubyn_code/cli/commands/model.rb +37 -0
- data/lib/rubyn_code/cli/commands/plan.rb +22 -0
- data/lib/rubyn_code/cli/commands/quit.rb +17 -0
- data/lib/rubyn_code/cli/commands/registry.rb +64 -0
- data/lib/rubyn_code/cli/commands/resume.rb +51 -0
- data/lib/rubyn_code/cli/commands/review.rb +26 -0
- data/lib/rubyn_code/cli/commands/skill.rb +32 -0
- data/lib/rubyn_code/cli/commands/spawn.rb +24 -0
- data/lib/rubyn_code/cli/commands/tasks.rb +32 -0
- data/lib/rubyn_code/cli/commands/tokens.rb +76 -0
- data/lib/rubyn_code/cli/commands/undo.rb +17 -0
- data/lib/rubyn_code/cli/commands/version.rb +16 -0
- data/lib/rubyn_code/cli/daemon_runner.rb +129 -0
- data/lib/rubyn_code/cli/input_handler.rb +20 -23
- data/lib/rubyn_code/cli/renderer.rb +25 -27
- data/lib/rubyn_code/cli/repl.rb +161 -194
- data/lib/rubyn_code/cli/setup.rb +117 -0
- data/lib/rubyn_code/cli/spinner.rb +40 -40
- data/lib/rubyn_code/cli/stream_formatter.rb +29 -28
- data/lib/rubyn_code/cli/version_check.rb +94 -0
- data/lib/rubyn_code/config/RUBYN.md +14 -0
- data/lib/rubyn_code/config/defaults.rb +28 -19
- data/lib/rubyn_code/config/project_config.rb +7 -9
- data/lib/rubyn_code/config/settings.rb +3 -3
- data/lib/rubyn_code/context/RUBYN.md +20 -0
- data/lib/rubyn_code/context/auto_compact.rb +7 -7
- data/lib/rubyn_code/context/compactor.rb +2 -2
- data/lib/rubyn_code/context/context_collapse.rb +45 -0
- data/lib/rubyn_code/context/manager.rb +20 -3
- data/lib/rubyn_code/context/manual_compact.rb +7 -7
- data/lib/rubyn_code/context/micro_compact.rb +12 -12
- data/lib/rubyn_code/db/RUBYN.md +40 -0
- data/lib/rubyn_code/db/connection.rb +13 -13
- data/lib/rubyn_code/db/migrator.rb +67 -27
- data/lib/rubyn_code/db/schema.rb +6 -6
- data/lib/rubyn_code/debug.rb +74 -0
- data/lib/rubyn_code/hooks/RUBYN.md +17 -0
- data/lib/rubyn_code/hooks/built_in.rb +9 -9
- data/lib/rubyn_code/hooks/registry.rb +5 -5
- data/lib/rubyn_code/hooks/runner.rb +1 -1
- data/lib/rubyn_code/hooks/user_hooks.rb +16 -16
- data/lib/rubyn_code/learning/RUBYN.md +16 -0
- data/lib/rubyn_code/learning/extractor.rb +22 -22
- data/lib/rubyn_code/learning/injector.rb +17 -18
- data/lib/rubyn_code/learning/instinct.rb +18 -14
- data/lib/rubyn_code/llm/RUBYN.md +15 -0
- data/lib/rubyn_code/llm/client.rb +121 -55
- data/lib/rubyn_code/llm/message_builder.rb +19 -15
- data/lib/rubyn_code/llm/streaming.rb +80 -50
- data/lib/rubyn_code/mcp/RUBYN.md +21 -0
- data/lib/rubyn_code/mcp/client.rb +25 -24
- data/lib/rubyn_code/mcp/config.rb +7 -7
- data/lib/rubyn_code/mcp/sse_transport.rb +27 -26
- data/lib/rubyn_code/mcp/stdio_transport.rb +22 -19
- data/lib/rubyn_code/mcp/tool_bridge.rb +32 -32
- data/lib/rubyn_code/memory/RUBYN.md +17 -0
- data/lib/rubyn_code/memory/models.rb +3 -3
- data/lib/rubyn_code/memory/search.rb +17 -17
- data/lib/rubyn_code/memory/session_persistence.rb +49 -34
- data/lib/rubyn_code/memory/store.rb +17 -17
- data/lib/rubyn_code/observability/RUBYN.md +19 -0
- data/lib/rubyn_code/observability/budget_enforcer.rb +16 -15
- data/lib/rubyn_code/observability/cost_calculator.rb +3 -3
- data/lib/rubyn_code/observability/token_counter.rb +1 -1
- data/lib/rubyn_code/observability/usage_reporter.rb +35 -35
- data/lib/rubyn_code/output/RUBYN.md +11 -0
- data/lib/rubyn_code/output/diff_renderer.rb +6 -6
- data/lib/rubyn_code/output/formatter.rb +4 -4
- data/lib/rubyn_code/permissions/RUBYN.md +17 -0
- data/lib/rubyn_code/permissions/prompter.rb +8 -8
- data/lib/rubyn_code/protocols/RUBYN.md +14 -0
- data/lib/rubyn_code/protocols/interrupt_handler.rb +1 -1
- data/lib/rubyn_code/protocols/plan_approval.rb +9 -9
- data/lib/rubyn_code/protocols/shutdown_handshake.rb +9 -11
- data/lib/rubyn_code/skills/RUBYN.md +19 -0
- data/lib/rubyn_code/skills/catalog.rb +7 -7
- data/lib/rubyn_code/skills/document.rb +15 -15
- data/lib/rubyn_code/skills/loader.rb +6 -8
- data/lib/rubyn_code/sub_agents/RUBYN.md +12 -0
- data/lib/rubyn_code/sub_agents/runner.rb +15 -15
- data/lib/rubyn_code/sub_agents/summarizer.rb +1 -1
- data/lib/rubyn_code/tasks/RUBYN.md +13 -0
- data/lib/rubyn_code/tasks/dag.rb +12 -16
- data/lib/rubyn_code/tasks/manager.rb +24 -24
- data/lib/rubyn_code/tasks/models.rb +4 -4
- data/lib/rubyn_code/teams/RUBYN.md +14 -0
- data/lib/rubyn_code/teams/mailbox.rb +38 -18
- data/lib/rubyn_code/teams/manager.rb +19 -19
- data/lib/rubyn_code/teams/teammate.rb +3 -4
- data/lib/rubyn_code/tools/RUBYN.md +38 -0
- data/lib/rubyn_code/tools/background_run.rb +9 -11
- data/lib/rubyn_code/tools/base.rb +54 -3
- data/lib/rubyn_code/tools/bash.rb +16 -34
- data/lib/rubyn_code/tools/bundle_add.rb +10 -12
- data/lib/rubyn_code/tools/bundle_install.rb +9 -11
- data/lib/rubyn_code/tools/compact.rb +10 -9
- data/lib/rubyn_code/tools/db_migrate.rb +17 -15
- data/lib/rubyn_code/tools/edit_file.rb +12 -12
- data/lib/rubyn_code/tools/executor.rb +9 -4
- data/lib/rubyn_code/tools/git_commit.rb +29 -34
- data/lib/rubyn_code/tools/git_diff.rb +17 -18
- data/lib/rubyn_code/tools/git_log.rb +17 -19
- data/lib/rubyn_code/tools/git_status.rb +18 -20
- data/lib/rubyn_code/tools/glob.rb +7 -9
- data/lib/rubyn_code/tools/grep.rb +11 -9
- data/lib/rubyn_code/tools/load_skill.rb +7 -7
- data/lib/rubyn_code/tools/memory_search.rb +13 -12
- data/lib/rubyn_code/tools/memory_write.rb +14 -12
- data/lib/rubyn_code/tools/rails_generate.rb +16 -16
- data/lib/rubyn_code/tools/read_file.rb +8 -7
- data/lib/rubyn_code/tools/read_inbox.rb +5 -5
- data/lib/rubyn_code/tools/registry.rb +2 -2
- data/lib/rubyn_code/tools/review_pr.rb +55 -55
- data/lib/rubyn_code/tools/run_specs.rb +20 -19
- data/lib/rubyn_code/tools/schema.rb +9 -11
- data/lib/rubyn_code/tools/send_message.rb +10 -10
- data/lib/rubyn_code/tools/spawn_agent.rb +51 -23
- data/lib/rubyn_code/tools/spawn_teammate.rb +21 -21
- data/lib/rubyn_code/tools/task.rb +28 -28
- data/lib/rubyn_code/tools/web_fetch.rb +46 -31
- data/lib/rubyn_code/tools/web_search.rb +64 -66
- data/lib/rubyn_code/tools/write_file.rb +7 -6
- data/lib/rubyn_code/version.rb +1 -1
- data/lib/rubyn_code.rb +136 -105
- metadata +94 -21
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require
|
|
3
|
+
require 'yaml'
|
|
4
4
|
|
|
5
5
|
module RubynCode
|
|
6
6
|
module Hooks
|
|
@@ -26,8 +26,8 @@ module RubynCode
|
|
|
26
26
|
# @return [void]
|
|
27
27
|
def self.load!(registry, project_root:)
|
|
28
28
|
paths = [
|
|
29
|
-
File.join(project_root,
|
|
30
|
-
File.join(Config::Defaults::HOME_DIR,
|
|
29
|
+
File.join(project_root, '.rubyn-code', 'hooks.yml'),
|
|
30
|
+
File.join(Config::Defaults::HOME_DIR, 'hooks.yml')
|
|
31
31
|
]
|
|
32
32
|
|
|
33
33
|
paths.each do |path|
|
|
@@ -42,8 +42,8 @@ module RubynCode
|
|
|
42
42
|
private
|
|
43
43
|
|
|
44
44
|
def register_hooks(registry, config)
|
|
45
|
-
register_pre_tool_use_hooks(registry, config[
|
|
46
|
-
register_post_tool_use_hooks(registry, config[
|
|
45
|
+
register_pre_tool_use_hooks(registry, config['pre_tool_use'] || [])
|
|
46
|
+
register_post_tool_use_hooks(registry, config['post_tool_use'] || [])
|
|
47
47
|
end
|
|
48
48
|
|
|
49
49
|
def register_pre_tool_use_hooks(registry, hook_configs)
|
|
@@ -51,9 +51,9 @@ module RubynCode
|
|
|
51
51
|
registry.on(:pre_tool_use) do |tool_name:, tool_input:, **|
|
|
52
52
|
next unless matches?(hook_config, tool_name, tool_input)
|
|
53
53
|
|
|
54
|
-
case hook_config[
|
|
55
|
-
when
|
|
56
|
-
{ deny: true, reason: hook_config[
|
|
54
|
+
case hook_config['action']
|
|
55
|
+
when 'deny'
|
|
56
|
+
{ deny: true, reason: hook_config['reason'] || 'Blocked by hooks.yml' }
|
|
57
57
|
end
|
|
58
58
|
end
|
|
59
59
|
end
|
|
@@ -62,12 +62,12 @@ module RubynCode
|
|
|
62
62
|
def register_post_tool_use_hooks(registry, hook_configs)
|
|
63
63
|
hook_configs.each do |hook_config|
|
|
64
64
|
registry.on(:post_tool_use) do |tool_name:, result:, **|
|
|
65
|
-
next result unless hook_config[
|
|
65
|
+
next result unless hook_config['tool'].nil? || hook_config['tool'] == tool_name
|
|
66
66
|
|
|
67
|
-
if hook_config[
|
|
68
|
-
log_dir =
|
|
69
|
-
FileUtils.mkdir_p(log_dir)
|
|
70
|
-
File.open(File.join(log_dir,
|
|
67
|
+
if hook_config['action'] == 'log'
|
|
68
|
+
log_dir = '.rubyn-code'
|
|
69
|
+
FileUtils.mkdir_p(log_dir)
|
|
70
|
+
File.open(File.join(log_dir, 'audit.log'), 'a') do |f|
|
|
71
71
|
f.puts "[#{Time.now}] #{tool_name}: #{result.to_s[0..200]}"
|
|
72
72
|
end
|
|
73
73
|
end
|
|
@@ -78,9 +78,9 @@ module RubynCode
|
|
|
78
78
|
end
|
|
79
79
|
|
|
80
80
|
def matches?(config, tool_name, params)
|
|
81
|
-
return false if config[
|
|
82
|
-
return false if config[
|
|
83
|
-
return false if config[
|
|
81
|
+
return false if config['tool'] && config['tool'] != tool_name
|
|
82
|
+
return false if config['match'] && !params.to_s.include?(config['match'])
|
|
83
|
+
return false if config['path'] && !File.fnmatch?(config['path'], params[:path].to_s)
|
|
84
84
|
|
|
85
85
|
true
|
|
86
86
|
end
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# Layer 16: Learning
|
|
2
|
+
|
|
3
|
+
Continuous learning from session patterns with confidence decay.
|
|
4
|
+
|
|
5
|
+
## Classes
|
|
6
|
+
|
|
7
|
+
- **`Extractor`** — Post-session analysis using a cheaper LLM (Haiku). Scans the last 30
|
|
8
|
+
messages for patterns: error resolutions, user corrections, workarounds, debugging
|
|
9
|
+
techniques, project-specific conventions. Persists as instincts.
|
|
10
|
+
|
|
11
|
+
- **`Instinct`** — A learned pattern with a confidence score that decays over time.
|
|
12
|
+
Stored in the `instincts` SQLite table. Higher confidence = more likely to be injected
|
|
13
|
+
into future prompts.
|
|
14
|
+
|
|
15
|
+
- **`Injector`** — Selects relevant instincts (confidence >= 0.3, max 10) and injects
|
|
16
|
+
them into the system prompt for the current session. Filters by project context.
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require
|
|
4
|
-
require
|
|
3
|
+
require 'json'
|
|
4
|
+
require 'securerandom'
|
|
5
5
|
|
|
6
6
|
module RubynCode
|
|
7
7
|
module Learning
|
|
@@ -23,7 +23,7 @@ module RubynCode
|
|
|
23
23
|
project_specific
|
|
24
24
|
].freeze
|
|
25
25
|
|
|
26
|
-
EXTRACTION_PROMPT = <<~PROMPT
|
|
26
|
+
EXTRACTION_PROMPT = <<~PROMPT.freeze
|
|
27
27
|
Analyze the following conversation between a developer and an AI coding assistant.
|
|
28
28
|
Extract reusable patterns that could help in future sessions for this project.
|
|
29
29
|
|
|
@@ -75,22 +75,22 @@ module RubynCode
|
|
|
75
75
|
def request_extraction(messages, llm_client)
|
|
76
76
|
# Serialize conversation into a single user message to avoid
|
|
77
77
|
# "must end with user message" errors
|
|
78
|
-
transcript = messages.map
|
|
79
|
-
role = (m[:role] || m[
|
|
80
|
-
content = m[:content] || m[
|
|
78
|
+
transcript = messages.map do |m|
|
|
79
|
+
role = (m[:role] || m['role'] || 'unknown').capitalize
|
|
80
|
+
content = m[:content] || m['content']
|
|
81
81
|
text = case content
|
|
82
82
|
when String then content
|
|
83
83
|
when Array
|
|
84
|
-
content.filter_map
|
|
85
|
-
b.respond_to?(:text) ? b.text : (b[:text] || b[
|
|
86
|
-
|
|
84
|
+
content.filter_map do |b|
|
|
85
|
+
b.respond_to?(:text) ? b.text : (b[:text] || b['text'])
|
|
86
|
+
end.join("\n")
|
|
87
87
|
else content.to_s
|
|
88
88
|
end
|
|
89
89
|
"#{role}: #{text}"
|
|
90
|
-
|
|
90
|
+
end.join("\n\n")
|
|
91
91
|
|
|
92
92
|
llm_client.chat(
|
|
93
|
-
messages: [{ role:
|
|
93
|
+
messages: [{ role: 'user', content: "#{EXTRACTION_PROMPT}\n\nConversation:\n#{transcript}" }],
|
|
94
94
|
max_tokens: 2000
|
|
95
95
|
)
|
|
96
96
|
rescue StandardError => e
|
|
@@ -104,7 +104,7 @@ module RubynCode
|
|
|
104
104
|
|
|
105
105
|
instincts.each do |inst|
|
|
106
106
|
db.execute(
|
|
107
|
-
|
|
107
|
+
'INSERT INTO instincts (id, project_path, pattern, context_tags, confidence, decay_rate, times_applied, times_helpful, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)',
|
|
108
108
|
[
|
|
109
109
|
SecureRandom.uuid,
|
|
110
110
|
inst[:project_path],
|
|
@@ -147,15 +147,15 @@ module RubynCode
|
|
|
147
147
|
block = response.content.find { |b| b.respond_to?(:text) }
|
|
148
148
|
block&.text
|
|
149
149
|
elsif response.is_a?(Hash)
|
|
150
|
-
response.dig(
|
|
150
|
+
response.dig('content', 0, 'text')
|
|
151
151
|
end
|
|
152
152
|
end
|
|
153
153
|
|
|
154
154
|
def normalize_pattern(raw, project_path)
|
|
155
|
-
type = raw[
|
|
156
|
-
pattern = raw[
|
|
157
|
-
context_tags = Array(raw[
|
|
158
|
-
confidence = raw[
|
|
155
|
+
type = raw['type'].to_s
|
|
156
|
+
pattern = raw['pattern'].to_s.strip
|
|
157
|
+
context_tags = Array(raw['context_tags']).map(&:to_s)
|
|
158
|
+
confidence = raw['confidence'].to_f
|
|
159
159
|
|
|
160
160
|
return nil if pattern.empty?
|
|
161
161
|
return nil unless VALID_TYPES.include?(type)
|
|
@@ -177,11 +177,11 @@ module RubynCode
|
|
|
177
177
|
# Project-specific knowledge decays slower; workarounds decay faster.
|
|
178
178
|
def decay_rate_for_type(type)
|
|
179
179
|
case type
|
|
180
|
-
when
|
|
181
|
-
when
|
|
182
|
-
when
|
|
183
|
-
when
|
|
184
|
-
when
|
|
180
|
+
when 'project_specific' then 0.02
|
|
181
|
+
when 'error_resolution' then 0.03
|
|
182
|
+
when 'debugging_technique' then 0.04
|
|
183
|
+
when 'user_correction' then 0.05
|
|
184
|
+
when 'workaround' then 0.07
|
|
185
185
|
else 0.05
|
|
186
186
|
end
|
|
187
187
|
end
|
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require
|
|
4
|
-
require
|
|
5
|
-
require "set"
|
|
3
|
+
require 'json'
|
|
4
|
+
require 'time'
|
|
6
5
|
|
|
7
6
|
module RubynCode
|
|
8
7
|
module Learning
|
|
@@ -15,7 +14,7 @@ module RubynCode
|
|
|
15
14
|
# Default maximum number of instincts to inject.
|
|
16
15
|
DEFAULT_MAX_INSTINCTS = 10
|
|
17
16
|
|
|
18
|
-
INSTINCTS_TABLE =
|
|
17
|
+
INSTINCTS_TABLE = 'instincts'
|
|
19
18
|
|
|
20
19
|
class << self
|
|
21
20
|
# Queries and formats relevant instincts for system prompt injection.
|
|
@@ -27,7 +26,7 @@ module RubynCode
|
|
|
27
26
|
# @return [String] formatted instincts block, or empty string if none found
|
|
28
27
|
def call(db:, project_path:, context_tags: [], max_instincts: DEFAULT_MAX_INSTINCTS)
|
|
29
28
|
rows = fetch_instincts(db, project_path)
|
|
30
|
-
return
|
|
29
|
+
return '' if rows.empty?
|
|
31
30
|
|
|
32
31
|
instincts = rows.map { |row| row_to_instinct(row) }
|
|
33
32
|
|
|
@@ -46,7 +45,7 @@ module RubynCode
|
|
|
46
45
|
.sort_by { |inst| -inst.confidence }
|
|
47
46
|
.first(max_instincts)
|
|
48
47
|
|
|
49
|
-
return
|
|
48
|
+
return '' if instincts.empty?
|
|
50
49
|
|
|
51
50
|
format_instincts(instincts)
|
|
52
51
|
end
|
|
@@ -65,16 +64,16 @@ module RubynCode
|
|
|
65
64
|
|
|
66
65
|
def row_to_instinct(row)
|
|
67
66
|
Instinct.new(
|
|
68
|
-
id: row[
|
|
69
|
-
project_path: row[
|
|
70
|
-
pattern: row[
|
|
71
|
-
context_tags: parse_tags(row[
|
|
72
|
-
confidence: row[
|
|
73
|
-
decay_rate: row[
|
|
74
|
-
times_applied: row[
|
|
75
|
-
times_helpful: row[
|
|
76
|
-
created_at: parse_time(row[
|
|
77
|
-
updated_at: parse_time(row[
|
|
67
|
+
id: row['id'],
|
|
68
|
+
project_path: row['project_path'],
|
|
69
|
+
pattern: row['pattern'],
|
|
70
|
+
context_tags: parse_tags(row['context_tags']),
|
|
71
|
+
confidence: row['confidence'].to_f,
|
|
72
|
+
decay_rate: row['decay_rate'].to_f,
|
|
73
|
+
times_applied: row['times_applied'].to_i,
|
|
74
|
+
times_helpful: row['times_helpful'].to_i,
|
|
75
|
+
created_at: parse_time(row['created_at']),
|
|
76
|
+
updated_at: parse_time(row['updated_at'])
|
|
78
77
|
)
|
|
79
78
|
end
|
|
80
79
|
|
|
@@ -84,7 +83,7 @@ module RubynCode
|
|
|
84
83
|
begin
|
|
85
84
|
JSON.parse(tags)
|
|
86
85
|
rescue JSON::ParserError
|
|
87
|
-
tags.split(
|
|
86
|
+
tags.split(',').map(&:strip)
|
|
88
87
|
end
|
|
89
88
|
when Array
|
|
90
89
|
tags
|
|
@@ -111,7 +110,7 @@ module RubynCode
|
|
|
111
110
|
# @param tags [Array<String>] required context tags
|
|
112
111
|
# @return [Array<Instinct>] filtered instincts
|
|
113
112
|
def filter_by_tags(instincts, tags)
|
|
114
|
-
tag_set = tags.
|
|
113
|
+
tag_set = tags.to_set(&:downcase)
|
|
115
114
|
|
|
116
115
|
instincts.select do |inst|
|
|
117
116
|
inst_tags = inst.context_tags.map(&:downcase)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require
|
|
3
|
+
require 'time'
|
|
4
4
|
|
|
5
5
|
module RubynCode
|
|
6
6
|
module Learning
|
|
@@ -44,10 +44,10 @@ module RubynCode
|
|
|
44
44
|
|
|
45
45
|
# Confidence label thresholds, checked in descending order.
|
|
46
46
|
CONFIDENCE_LABELS = [
|
|
47
|
-
[0.9,
|
|
48
|
-
[0.7,
|
|
49
|
-
[0.5,
|
|
50
|
-
[0.3,
|
|
47
|
+
[0.9, 'near-certain'],
|
|
48
|
+
[0.7, 'confident'],
|
|
49
|
+
[0.5, 'moderate'],
|
|
50
|
+
[0.3, 'tentative']
|
|
51
51
|
].freeze
|
|
52
52
|
|
|
53
53
|
class << self
|
|
@@ -105,7 +105,7 @@ module RubynCode
|
|
|
105
105
|
return label if score >= threshold
|
|
106
106
|
end
|
|
107
107
|
|
|
108
|
-
|
|
108
|
+
'unreliable'
|
|
109
109
|
end
|
|
110
110
|
|
|
111
111
|
# Reinforces an instinct in the database by updating confidence
|
|
@@ -120,7 +120,7 @@ module RubynCode
|
|
|
120
120
|
|
|
121
121
|
if helpful
|
|
122
122
|
db.execute(
|
|
123
|
-
|
|
123
|
+
'UPDATE instincts SET confidence = MIN(1.0, confidence + 0.1 * (1.0 - confidence)), times_applied = times_applied + 1, times_helpful = times_helpful + 1, updated_at = ? WHERE id = ?',
|
|
124
124
|
[now, instinct_id]
|
|
125
125
|
)
|
|
126
126
|
else
|
|
@@ -141,25 +141,29 @@ module RubynCode
|
|
|
141
141
|
# @return [void]
|
|
142
142
|
def decay_all(db, project_path:)
|
|
143
143
|
rows = db.query(
|
|
144
|
-
|
|
144
|
+
'SELECT id, confidence, decay_rate, updated_at FROM instincts WHERE project_path = ?',
|
|
145
145
|
[project_path]
|
|
146
146
|
).to_a
|
|
147
147
|
|
|
148
148
|
now = Time.now
|
|
149
149
|
rows.each do |row|
|
|
150
|
-
updated_at =
|
|
150
|
+
updated_at = begin
|
|
151
|
+
Time.parse(row['updated_at'].to_s)
|
|
152
|
+
rescue StandardError
|
|
153
|
+
Time.now
|
|
154
|
+
end
|
|
151
155
|
elapsed_days = (now - updated_at).to_f / 86_400
|
|
152
156
|
next if elapsed_days <= 0
|
|
153
157
|
|
|
154
|
-
decay_factor = Math.exp(-row[
|
|
155
|
-
new_confidence = (row[
|
|
158
|
+
decay_factor = Math.exp(-row['decay_rate'].to_f * elapsed_days)
|
|
159
|
+
new_confidence = (row['confidence'].to_f * decay_factor).clamp(MIN_CONFIDENCE, 1.0)
|
|
156
160
|
|
|
157
161
|
if new_confidence <= MIN_CONFIDENCE
|
|
158
|
-
db.execute(
|
|
162
|
+
db.execute('DELETE FROM instincts WHERE id = ?', [row['id']])
|
|
159
163
|
else
|
|
160
164
|
db.execute(
|
|
161
|
-
|
|
162
|
-
[new_confidence, row[
|
|
165
|
+
'UPDATE instincts SET confidence = ? WHERE id = ?',
|
|
166
|
+
[new_confidence, row['id']]
|
|
163
167
|
)
|
|
164
168
|
end
|
|
165
169
|
end
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# LLM Layer
|
|
2
|
+
|
|
3
|
+
Faraday-based Claude API client with streaming support.
|
|
4
|
+
|
|
5
|
+
## Classes
|
|
6
|
+
|
|
7
|
+
- **`Client`** — Sends messages to the Claude API. Handles auth headers (OAuth bearer or API key),
|
|
8
|
+
model selection, system prompts, and tool definitions. Returns parsed response or streams.
|
|
9
|
+
|
|
10
|
+
- **`Streaming`** — SSE stream parser for Claude's streaming API. Buffers partial events,
|
|
11
|
+
emits content blocks and tool_use blocks as they arrive. Feeds into `CLI::StreamFormatter`.
|
|
12
|
+
|
|
13
|
+
- **`MessageBuilder`** — Constructs the messages array for the API. Handles system prompt
|
|
14
|
+
injection, tool result formatting, and context window limits. Knows about Claude's
|
|
15
|
+
message format (role/content pairs, tool_use/tool_result blocks).
|