rubyn-code 0.5.0 → 0.7.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 +182 -11
- data/db/migrations/014_multi_agent_upgrade.rb +79 -0
- data/lib/rubyn_code/agent/conversation.rb +89 -3
- data/lib/rubyn_code/agent/llm_caller.rb +2 -2
- data/lib/rubyn_code/agent/loop.rb +49 -9
- data/lib/rubyn_code/agent/system_prompt_builder.rb +37 -2
- data/lib/rubyn_code/agent/tool_processor.rb +3 -1
- data/lib/rubyn_code/auth/oauth.rb +1 -1
- data/lib/rubyn_code/auth/token_store.rb +49 -4
- 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/app.rb +2 -2
- 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 +3 -1
- data/lib/rubyn_code/cli/commands/custom_command.rb +42 -0
- data/lib/rubyn_code/cli/commands/custom_loader.rb +69 -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 +18 -5
- data/lib/rubyn_code/cli/commands/megaplan.rb +50 -0
- data/lib/rubyn_code/cli/commands/registry.rb +14 -9
- data/lib/rubyn_code/cli/commands/rewind.rb +65 -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 +92 -0
- data/lib/rubyn_code/cli/renderer.rb +3 -2
- data/lib/rubyn_code/cli/repl.rb +37 -14
- data/lib/rubyn_code/cli/repl_commands.rb +77 -2
- data/lib/rubyn_code/cli/repl_setup.rb +9 -1
- data/lib/rubyn_code/cli/setup.rb +13 -0
- 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 +13 -1
- data/lib/rubyn_code/config/schema.json +4 -0
- data/lib/rubyn_code/config/settings.rb +17 -2
- data/lib/rubyn_code/context/manager.rb +29 -12
- 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 +65 -0
- data/lib/rubyn_code/ide/handlers/plan_interview_cancel_handler.rb +22 -0
- data/lib/rubyn_code/ide/handlers/plan_interview_start_handler.rb +53 -0
- data/lib/rubyn_code/ide/handlers/plan_propose_handler.rb +41 -0
- data/lib/rubyn_code/ide/handlers/prompt_handler.rb +9 -1
- data/lib/rubyn_code/ide/handlers/recover_ci_handler.rb +143 -0
- data/lib/rubyn_code/ide/handlers/session_resume_handler.rb +1 -1
- data/lib/rubyn_code/ide/handlers.rb +17 -2
- data/lib/rubyn_code/ide/protocol.rb +15 -0
- data/lib/rubyn_code/ide/server.rb +39 -1
- data/lib/rubyn_code/index/codebase_index.rb +39 -1
- data/lib/rubyn_code/learning/porter.rb +129 -0
- data/lib/rubyn_code/llm/adapters/anthropic.rb +65 -16
- data/lib/rubyn_code/llm/adapters/openai.rb +1 -1
- data/lib/rubyn_code/llm/adapters/prompt_caching.rb +5 -1
- data/lib/rubyn_code/llm/adapters/token_caching.rb +54 -0
- data/lib/rubyn_code/llm/model_router.rb +2 -2
- data/lib/rubyn_code/mcp/client.rb +59 -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 +104 -0
- data/lib/rubyn_code/megaplan/interview_session.rb +250 -0
- data/lib/rubyn_code/megaplan/plan_proposer.rb +153 -0
- 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 +3 -1
- data/lib/rubyn_code/output/diff_renderer.rb +62 -7
- 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/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 +5 -1
- data/lib/rubyn_code/tools/ask_user.rb +15 -1
- data/lib/rubyn_code/tools/executor.rb +5 -3
- 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/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 +53 -2
- data/skills/megaplan/megaplan.md +156 -0
- data/skills/rubyn_self_test.md +322 -14
- data/skills/self_test/chisel_smoke.rb +84 -0
- data/skills/self_test/fixtures/chisel_sample.rb +64 -0
- metadata +49 -4
|
@@ -9,8 +9,9 @@ module RubynCode
|
|
|
9
9
|
TOOL_NAME = 'spawn_agent'
|
|
10
10
|
DESCRIPTION = 'Spawn an isolated sub-agent to handle a task. The sub-agent gets its own ' \
|
|
11
11
|
"fresh context, works independently, and returns only a summary. Use 'explore' " \
|
|
12
|
-
"type for research/reading, 'worker' type for writing code/files
|
|
13
|
-
'
|
|
12
|
+
"type for research/reading, 'worker' type for writing code/files, or the name " \
|
|
13
|
+
'of any custom agent defined in .rubyn-code/agents/. The sub-agent shares the ' \
|
|
14
|
+
'filesystem but not your conversation.'
|
|
14
15
|
PARAMETERS = {
|
|
15
16
|
prompt: {
|
|
16
17
|
type: :string,
|
|
@@ -19,76 +20,73 @@ module RubynCode
|
|
|
19
20
|
},
|
|
20
21
|
agent_type: {
|
|
21
22
|
type: :string,
|
|
22
|
-
description: "
|
|
23
|
-
|
|
24
|
-
|
|
23
|
+
description: "Agent type: 'explore' (read-only), 'worker' (full write access), or a " \
|
|
24
|
+
'custom agent name from .rubyn-code/agents/. Default: explore',
|
|
25
|
+
required: false
|
|
25
26
|
}
|
|
26
27
|
}.freeze
|
|
27
28
|
RISK_LEVEL = :execute
|
|
28
29
|
|
|
30
|
+
READ_TOOLS = %w[read_file glob grep bash load_skill memory_search].freeze
|
|
31
|
+
BLOCKED_TOOLS = %w[spawn_agent send_message read_inbox compact memory_write].freeze
|
|
32
|
+
|
|
29
33
|
# These get injected by the executor or the REPL
|
|
30
34
|
attr_writer :llm_client, :on_status
|
|
31
35
|
|
|
32
36
|
def execute(prompt:, agent_type: 'explore')
|
|
33
|
-
|
|
37
|
+
agent = resolve_agent(agent_type)
|
|
34
38
|
callback = @on_status || method(:default_status)
|
|
35
39
|
@tool_count = 0
|
|
36
40
|
|
|
37
|
-
callback.call(:started, "Spawning #{
|
|
41
|
+
callback.call(:started, "Spawning #{agent.name} agent...")
|
|
38
42
|
|
|
39
|
-
tools =
|
|
40
|
-
result, hit_limit = run_sub_agent(
|
|
41
|
-
prompt: prompt, tools: tools, type: type, callback: callback
|
|
42
|
-
)
|
|
43
|
+
tools = tools_for(agent)
|
|
44
|
+
result, hit_limit = run_sub_agent(prompt: prompt, tools: tools, agent: agent, callback: callback)
|
|
43
45
|
|
|
44
46
|
callback.call(:done, "Agent finished (#{@tool_count} tool calls).")
|
|
45
47
|
|
|
46
48
|
summary = RubynCode::SubAgents::Summarizer.call(result, max_length: 3000)
|
|
47
|
-
format_agent_result(
|
|
49
|
+
format_agent_result(agent.name, summary, hit_limit)
|
|
48
50
|
end
|
|
49
51
|
|
|
50
52
|
private
|
|
51
53
|
|
|
52
|
-
|
|
54
|
+
# Resolve the requested type via the catalog, falling back to explore
|
|
55
|
+
# for an unknown name so a typo degrades gracefully instead of erroring.
|
|
56
|
+
def resolve_agent(agent_type)
|
|
57
|
+
catalog = RubynCode::SubAgents::Catalog.new(project_root: project_root)
|
|
58
|
+
catalog.get(agent_type) || catalog.get('explore')
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def format_agent_result(name, summary, hit_limit)
|
|
53
62
|
if hit_limit
|
|
54
|
-
"## Sub-Agent Result (#{
|
|
63
|
+
"## Sub-Agent Result (#{name}) — INCOMPLETE (reached #{@tool_count} tool calls)\n\n" \
|
|
55
64
|
'The sub-agent ran out of turns before finishing. Here is what it accomplished so far:' \
|
|
56
65
|
"\n\n#{summary}"
|
|
57
66
|
else
|
|
58
|
-
"## Sub-Agent Result (#{
|
|
59
|
-
end
|
|
60
|
-
end
|
|
61
|
-
|
|
62
|
-
def max_iterations_for(type)
|
|
63
|
-
if type == :explore
|
|
64
|
-
Config::Defaults::MAX_EXPLORE_AGENT_ITERATIONS
|
|
65
|
-
else
|
|
66
|
-
Config::Defaults::MAX_SUB_AGENT_ITERATIONS
|
|
67
|
+
"## Sub-Agent Result (#{name})\n\n#{summary}"
|
|
67
68
|
end
|
|
68
69
|
end
|
|
69
70
|
|
|
70
71
|
# Returns [result_text, hit_limit] tuple
|
|
71
|
-
def run_sub_agent(prompt:, tools:,
|
|
72
|
+
def run_sub_agent(prompt:, tools:, agent:, callback:)
|
|
72
73
|
conversation = RubynCode::Agent::Conversation.new
|
|
73
74
|
conversation.add_user_message(prompt)
|
|
74
75
|
|
|
75
|
-
max_iterations = max_iterations_for(type)
|
|
76
76
|
iteration = 0
|
|
77
77
|
last_text = nil
|
|
78
78
|
|
|
79
79
|
loop do
|
|
80
|
-
return finish_at_limit(conversation,
|
|
80
|
+
return finish_at_limit(conversation, agent, last_text) if iteration >= agent.max_iterations
|
|
81
81
|
|
|
82
|
-
last_text, done = process_iteration(
|
|
83
|
-
conversation, tools, type, callback, last_text
|
|
84
|
-
)
|
|
82
|
+
last_text, done = process_iteration(conversation, tools, agent, callback, last_text)
|
|
85
83
|
return [last_text || '', false] if done
|
|
86
84
|
|
|
87
85
|
iteration += 1
|
|
88
86
|
end
|
|
89
87
|
end
|
|
90
88
|
|
|
91
|
-
def finish_at_limit(conversation,
|
|
89
|
+
def finish_at_limit(conversation, agent, last_text)
|
|
92
90
|
conversation.add_user_message(
|
|
93
91
|
'You have reached your turn limit. Summarize everything you found or ' \
|
|
94
92
|
'accomplished so far. Be thorough — this is your last chance to report back.'
|
|
@@ -96,17 +94,17 @@ module RubynCode
|
|
|
96
94
|
response = @llm_client.chat(
|
|
97
95
|
messages: conversation.to_api_format,
|
|
98
96
|
tools: [],
|
|
99
|
-
system:
|
|
97
|
+
system: agent.system_prompt
|
|
100
98
|
)
|
|
101
99
|
summary = extract_text(response)
|
|
102
100
|
[summary.empty? ? (last_text || '') : summary, true]
|
|
103
101
|
end
|
|
104
102
|
|
|
105
|
-
def process_iteration(conversation, tools,
|
|
103
|
+
def process_iteration(conversation, tools, agent, callback, last_text)
|
|
106
104
|
response = @llm_client.chat(
|
|
107
105
|
messages: conversation.to_api_format,
|
|
108
106
|
tools: tools,
|
|
109
|
-
system:
|
|
107
|
+
system: agent.system_prompt
|
|
110
108
|
)
|
|
111
109
|
|
|
112
110
|
content = response_content(response)
|
|
@@ -117,22 +115,22 @@ module RubynCode
|
|
|
117
115
|
conversation.add_assistant_message(content)
|
|
118
116
|
return [last_text, true] if tool_calls.empty?
|
|
119
117
|
|
|
120
|
-
execute_sub_agent_tools(tool_calls, conversation,
|
|
118
|
+
execute_sub_agent_tools(tool_calls, conversation, agent, callback)
|
|
121
119
|
[last_text, false]
|
|
122
120
|
end
|
|
123
121
|
|
|
124
|
-
def execute_sub_agent_tools(tool_calls, conversation,
|
|
122
|
+
def execute_sub_agent_tools(tool_calls, conversation, agent, callback)
|
|
125
123
|
tool_calls.each do |tc|
|
|
126
124
|
name, input, id = extract_tool_call(tc)
|
|
127
125
|
@tool_count += 1
|
|
128
126
|
callback.call(:tool, name.to_s)
|
|
129
127
|
|
|
130
|
-
run_single_tool(name, input, id, conversation,
|
|
128
|
+
run_single_tool(name, input, id, conversation, agent)
|
|
131
129
|
end
|
|
132
130
|
end
|
|
133
131
|
|
|
134
|
-
def run_single_tool(name, input, id, conversation,
|
|
135
|
-
if
|
|
132
|
+
def run_single_tool(name, input, id, conversation, agent)
|
|
133
|
+
if name == 'spawn_agent'
|
|
136
134
|
conversation.add_tool_result(
|
|
137
135
|
id, name, 'Error: Sub-agents cannot spawn other agents.', is_error: true
|
|
138
136
|
)
|
|
@@ -140,9 +138,9 @@ module RubynCode
|
|
|
140
138
|
end
|
|
141
139
|
|
|
142
140
|
tool_class = RubynCode::Tools::Registry.get(name)
|
|
143
|
-
if
|
|
141
|
+
if agent.read_only? && tool_class.risk_level != :read
|
|
144
142
|
conversation.add_tool_result(
|
|
145
|
-
id, name,
|
|
143
|
+
id, name, "Error: #{agent.name} agents can only use read-only tools.", is_error: true
|
|
146
144
|
)
|
|
147
145
|
return
|
|
148
146
|
end
|
|
@@ -154,31 +152,18 @@ module RubynCode
|
|
|
154
152
|
conversation.add_tool_result(id, name, "Error: #{e.message}", is_error: true)
|
|
155
153
|
end
|
|
156
154
|
|
|
157
|
-
|
|
155
|
+
# The tool allowlist: explicit list from a custom agent, else the
|
|
156
|
+
# access-based default (read-only set or everything-minus-blocked).
|
|
157
|
+
def tools_for(agent)
|
|
158
158
|
all_tools = RubynCode::Tools::Registry.tool_definitions
|
|
159
|
-
blocked = %w[spawn_agent send_message read_inbox compact memory_write]
|
|
160
|
-
|
|
161
|
-
if type == :explore
|
|
162
|
-
read_tools = %w[read_file glob grep bash load_skill memory_search]
|
|
163
|
-
all_tools.select { |t| read_tools.include?(t[:name]) }
|
|
164
|
-
else
|
|
165
|
-
all_tools.reject { |t| blocked.include?(t[:name]) }
|
|
166
|
-
end
|
|
167
|
-
end
|
|
168
159
|
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
when :explore
|
|
175
|
-
"#{base}\nYou have read-only access. Search, read files, and analyze. " \
|
|
176
|
-
'Do NOT attempt to write or modify anything.'
|
|
177
|
-
when :worker
|
|
178
|
-
"#{base}\nYou have full read/write access. Make the changes needed, " \
|
|
179
|
-
'run tests if appropriate, and report what you did.'
|
|
160
|
+
if agent.tool_names && !agent.tool_names.empty?
|
|
161
|
+
allowed = agent.tool_names - BLOCKED_TOOLS
|
|
162
|
+
all_tools.select { |t| allowed.include?(t[:name]) }
|
|
163
|
+
elsif agent.read_only?
|
|
164
|
+
all_tools.select { |t| READ_TOOLS.include?(t[:name]) }
|
|
180
165
|
else
|
|
181
|
-
|
|
166
|
+
all_tools.reject { |t| BLOCKED_TOOLS.include?(t[:name]) }
|
|
182
167
|
end
|
|
183
168
|
end
|
|
184
169
|
|
|
@@ -25,13 +25,18 @@ module RubynCode
|
|
|
25
25
|
type: :string,
|
|
26
26
|
description: 'Initial task or instruction for the teammate',
|
|
27
27
|
required: true
|
|
28
|
+
},
|
|
29
|
+
parent_agent_id: {
|
|
30
|
+
type: :string,
|
|
31
|
+
description: 'ID of the parent agent spawning this teammate (for lineage tracking)',
|
|
32
|
+
required: false
|
|
28
33
|
}
|
|
29
34
|
}.freeze
|
|
30
35
|
RISK_LEVEL = :execute
|
|
31
36
|
|
|
32
37
|
attr_writer :llm_client, :on_status, :db
|
|
33
38
|
|
|
34
|
-
def execute(name:, role:, prompt:)
|
|
39
|
+
def execute(name:, role:, prompt:, parent_agent_id: nil)
|
|
35
40
|
callback = @on_status || method(:default_status)
|
|
36
41
|
|
|
37
42
|
raise Error, 'LLM client not available' unless @llm_client
|
|
@@ -40,7 +45,7 @@ module RubynCode
|
|
|
40
45
|
mailbox = Teams::Mailbox.new(@db)
|
|
41
46
|
manager = Teams::Manager.new(@db, mailbox: mailbox)
|
|
42
47
|
|
|
43
|
-
teammate = manager.spawn(name: name, role: role)
|
|
48
|
+
teammate = manager.spawn(name: name, role: role, parent_agent_id: parent_agent_id)
|
|
44
49
|
callback.call(:started, "Spawning teammate '#{name}' as #{role}...")
|
|
45
50
|
|
|
46
51
|
Thread.new do
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require 'faraday'
|
|
4
3
|
require_relative 'base'
|
|
5
4
|
require_relative 'registry'
|
|
6
5
|
|
|
@@ -76,6 +75,7 @@ module RubynCode
|
|
|
76
75
|
end
|
|
77
76
|
|
|
78
77
|
def build_connection
|
|
78
|
+
require 'faraday'
|
|
79
79
|
Faraday.new do |f|
|
|
80
80
|
f.options.timeout = 30
|
|
81
81
|
f.options.open_timeout = 10
|
|
@@ -3,7 +3,6 @@
|
|
|
3
3
|
require 'open3'
|
|
4
4
|
require 'cgi'
|
|
5
5
|
require 'json'
|
|
6
|
-
require 'faraday'
|
|
7
6
|
require_relative 'base'
|
|
8
7
|
require_relative 'registry'
|
|
9
8
|
|
|
@@ -112,6 +111,7 @@ module RubynCode
|
|
|
112
111
|
end
|
|
113
112
|
|
|
114
113
|
def brave_request(query, num_results)
|
|
114
|
+
require 'faraday'
|
|
115
115
|
Faraday.get('https://api.search.brave.com/res/v1/web/search') do |req|
|
|
116
116
|
req.params['q'] = query
|
|
117
117
|
req.params['count'] = num_results
|
|
@@ -135,6 +135,7 @@ module RubynCode
|
|
|
135
135
|
end
|
|
136
136
|
|
|
137
137
|
def tavily_request(query, num_results)
|
|
138
|
+
require 'faraday'
|
|
138
139
|
Faraday.post('https://api.tavily.com/search') do |req|
|
|
139
140
|
req.headers['Content-Type'] = 'application/json'
|
|
140
141
|
req.body = JSON.generate(
|
|
@@ -156,6 +157,7 @@ module RubynCode
|
|
|
156
157
|
end
|
|
157
158
|
|
|
158
159
|
def serpapi_request(query, num_results)
|
|
160
|
+
require 'faraday'
|
|
159
161
|
Faraday.get('https://serpapi.com/search.json') do |req|
|
|
160
162
|
req.params['q'] = query
|
|
161
163
|
req.params['num'] = num_results
|
|
@@ -175,6 +177,7 @@ module RubynCode
|
|
|
175
177
|
end
|
|
176
178
|
|
|
177
179
|
def google_request(query, num_results)
|
|
180
|
+
require 'faraday'
|
|
178
181
|
Faraday.get('https://www.googleapis.com/customsearch/v1') do |req|
|
|
179
182
|
req.params['q'] = query
|
|
180
183
|
req.params['num'] = [num_results, 10].min
|
data/lib/rubyn_code/version.rb
CHANGED
data/lib/rubyn_code.rb
CHANGED
|
@@ -16,11 +16,16 @@ module RubynCode
|
|
|
16
16
|
# rather than a successful tool call returning a string like "denied".
|
|
17
17
|
class UserDeniedError < Error; end
|
|
18
18
|
|
|
19
|
-
#
|
|
20
|
-
autoload :
|
|
19
|
+
# Chisel — opt-in "write the minimum that works" enforcement (off by default)
|
|
20
|
+
autoload :Chisel, 'rubyn_code/chisel'
|
|
21
21
|
|
|
22
|
+
# Infrastructure
|
|
22
23
|
module Config
|
|
24
|
+
autoload :Settings, 'rubyn_code/config/settings'
|
|
25
|
+
autoload :Defaults, 'rubyn_code/config/defaults'
|
|
26
|
+
autoload :ProjectConfig, 'rubyn_code/config/project_config'
|
|
23
27
|
autoload :ProjectProfile, 'rubyn_code/config/project_profile'
|
|
28
|
+
autoload :Validator, 'rubyn_code/config/validator'
|
|
24
29
|
end
|
|
25
30
|
|
|
26
31
|
# Database
|
|
@@ -49,6 +54,7 @@ module RubynCode
|
|
|
49
54
|
autoload :Base, 'rubyn_code/llm/adapters/base'
|
|
50
55
|
autoload :JsonParsing, 'rubyn_code/llm/adapters/json_parsing'
|
|
51
56
|
autoload :PromptCaching, 'rubyn_code/llm/adapters/prompt_caching'
|
|
57
|
+
autoload :TokenCaching, 'rubyn_code/llm/adapters/token_caching'
|
|
52
58
|
autoload :Anthropic, 'rubyn_code/llm/adapters/anthropic'
|
|
53
59
|
autoload :AnthropicCompatible, 'rubyn_code/llm/adapters/anthropic_compatible'
|
|
54
60
|
autoload :AnthropicStreaming, 'rubyn_code/llm/adapters/anthropic_streaming'
|
|
@@ -153,6 +159,14 @@ module RubynCode
|
|
|
153
159
|
module SubAgents
|
|
154
160
|
autoload :Runner, 'rubyn_code/sub_agents/runner'
|
|
155
161
|
autoload :Summarizer, 'rubyn_code/sub_agents/summarizer'
|
|
162
|
+
autoload :AgentType, 'rubyn_code/sub_agents/agent_type'
|
|
163
|
+
autoload :Catalog, 'rubyn_code/sub_agents/catalog'
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
# Checkpoints (/rewind)
|
|
167
|
+
module Checkpoint
|
|
168
|
+
autoload :Manager, 'rubyn_code/checkpoint/manager'
|
|
169
|
+
autoload :Hook, 'rubyn_code/checkpoint/hook'
|
|
156
170
|
end
|
|
157
171
|
|
|
158
172
|
# Layer 7: Tasks
|
|
@@ -162,6 +176,13 @@ module RubynCode
|
|
|
162
176
|
autoload :Models, 'rubyn_code/tasks/models'
|
|
163
177
|
end
|
|
164
178
|
|
|
179
|
+
# Layer 7b: Megaplan
|
|
180
|
+
module Megaplan
|
|
181
|
+
autoload :PlanProposer, 'rubyn_code/megaplan/plan_proposer'
|
|
182
|
+
autoload :InterviewSession, 'rubyn_code/megaplan/interview_session'
|
|
183
|
+
autoload :CiRecovery, 'rubyn_code/megaplan/ci_recovery'
|
|
184
|
+
end
|
|
185
|
+
|
|
165
186
|
# Layer 8: Background
|
|
166
187
|
module Background
|
|
167
188
|
autoload :Worker, 'rubyn_code/background/worker'
|
|
@@ -174,6 +195,7 @@ module RubynCode
|
|
|
174
195
|
autoload :Manager, 'rubyn_code/teams/manager'
|
|
175
196
|
autoload :Mailbox, 'rubyn_code/teams/mailbox'
|
|
176
197
|
autoload :Teammate, 'rubyn_code/teams/teammate'
|
|
198
|
+
autoload :AgentRegistry, 'rubyn_code/teams/agent_registry'
|
|
177
199
|
end
|
|
178
200
|
|
|
179
201
|
# Layer 10: Protocols
|
|
@@ -215,6 +237,17 @@ module RubynCode
|
|
|
215
237
|
autoload :Runner, 'rubyn_code/hooks/runner'
|
|
216
238
|
autoload :BuiltIn, 'rubyn_code/hooks/built_in'
|
|
217
239
|
autoload :UserHooks, 'rubyn_code/hooks/user_hooks'
|
|
240
|
+
autoload :GoalHook, 'rubyn_code/hooks/goal_hook'
|
|
241
|
+
autoload :EventMap, 'rubyn_code/hooks/event_map'
|
|
242
|
+
autoload :Response, 'rubyn_code/hooks/response'
|
|
243
|
+
autoload :SettingsJsonLoader, 'rubyn_code/hooks/settings_json_loader'
|
|
244
|
+
autoload :SubprocessExecutor, 'rubyn_code/hooks/subprocess_executor'
|
|
245
|
+
autoload :ExternalDispatcher, 'rubyn_code/hooks/external_dispatcher'
|
|
246
|
+
end
|
|
247
|
+
|
|
248
|
+
# Session goals (/goal)
|
|
249
|
+
module Goal
|
|
250
|
+
autoload :Evaluator, 'rubyn_code/goal/evaluator'
|
|
218
251
|
end
|
|
219
252
|
|
|
220
253
|
# Layer 15: MCP
|
|
@@ -223,6 +256,7 @@ module RubynCode
|
|
|
223
256
|
autoload :StdioTransport, 'rubyn_code/mcp/stdio_transport'
|
|
224
257
|
autoload :SSETransport, 'rubyn_code/mcp/sse_transport'
|
|
225
258
|
autoload :ToolBridge, 'rubyn_code/mcp/tool_bridge'
|
|
259
|
+
autoload :ServerExtrasBridge, 'rubyn_code/mcp/server_extras_bridge'
|
|
226
260
|
autoload :Config, 'rubyn_code/mcp/config'
|
|
227
261
|
end
|
|
228
262
|
|
|
@@ -233,6 +267,7 @@ module RubynCode
|
|
|
233
267
|
autoload :InstinctMethods, 'rubyn_code/learning/instinct'
|
|
234
268
|
autoload :Injector, 'rubyn_code/learning/injector'
|
|
235
269
|
autoload :Shortcut, 'rubyn_code/learning/shortcut'
|
|
270
|
+
autoload :Porter, 'rubyn_code/learning/porter'
|
|
236
271
|
end
|
|
237
272
|
|
|
238
273
|
# IDE (VS Code extension server)
|
|
@@ -264,6 +299,8 @@ module RubynCode
|
|
|
264
299
|
autoload :Setup, 'rubyn_code/cli/setup'
|
|
265
300
|
autoload :FirstRun, 'rubyn_code/cli/first_run'
|
|
266
301
|
autoload :DaemonRunner, 'rubyn_code/cli/daemon_runner'
|
|
302
|
+
autoload :LoopRunner, 'rubyn_code/cli/loop_runner'
|
|
303
|
+
autoload :MentionExpander, 'rubyn_code/cli/mention_expander'
|
|
267
304
|
autoload :VersionCheck, 'rubyn_code/cli/version_check'
|
|
268
305
|
|
|
269
306
|
# Slash Command System
|
|
@@ -296,6 +333,20 @@ module RubynCode
|
|
|
296
333
|
autoload :InstallSkills, 'rubyn_code/cli/commands/install_skills'
|
|
297
334
|
autoload :RemoveSkills, 'rubyn_code/cli/commands/remove_skills'
|
|
298
335
|
autoload :Skills, 'rubyn_code/cli/commands/skills'
|
|
336
|
+
autoload :Megaplan, 'rubyn_code/cli/commands/megaplan'
|
|
337
|
+
autoload :Goal, 'rubyn_code/cli/commands/goal'
|
|
338
|
+
autoload :Loop, 'rubyn_code/cli/commands/loop'
|
|
339
|
+
autoload :Agents, 'rubyn_code/cli/commands/agents'
|
|
340
|
+
autoload :CommandTemplate, 'rubyn_code/cli/commands/command_template'
|
|
341
|
+
autoload :CustomCommand, 'rubyn_code/cli/commands/custom_command'
|
|
342
|
+
autoload :CustomLoader, 'rubyn_code/cli/commands/custom_loader'
|
|
343
|
+
autoload :Learning, 'rubyn_code/cli/commands/learning'
|
|
344
|
+
autoload :Rewind, 'rubyn_code/cli/commands/rewind'
|
|
345
|
+
autoload :Chisel, 'rubyn_code/cli/commands/chisel'
|
|
346
|
+
autoload :ChiselReview, 'rubyn_code/cli/commands/chisel_review'
|
|
347
|
+
autoload :ChiselAudit, 'rubyn_code/cli/commands/chisel_audit'
|
|
348
|
+
autoload :ChiselDebt, 'rubyn_code/cli/commands/chisel_debt'
|
|
349
|
+
autoload :ChiselGain, 'rubyn_code/cli/commands/chisel_gain'
|
|
299
350
|
end
|
|
300
351
|
end
|
|
301
352
|
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: megaplan
|
|
3
|
+
description: Phased project planning. Interview the user one question at a time, then scaffold numbered phase folders (requirements/design/tasks). Trigger phrases include "megaplan", "mega plan", "plan phases", "phase this out", or any feature spanning 3+ PRs.
|
|
4
|
+
tags:
|
|
5
|
+
- planning
|
|
6
|
+
- process
|
|
7
|
+
- phases
|
|
8
|
+
- megaplan
|
|
9
|
+
triggers:
|
|
10
|
+
- megaplan
|
|
11
|
+
- mega plan
|
|
12
|
+
- plan phases
|
|
13
|
+
- phase this out
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
# Megaplan — Phased Project Planning
|
|
17
|
+
|
|
18
|
+
Ship in vertical slices. Each phase merges cleanly and leaves the trunk working.
|
|
19
|
+
|
|
20
|
+
## Don't use for
|
|
21
|
+
|
|
22
|
+
- Single-PR features — just do them
|
|
23
|
+
- Pure research / exploration — nothing shippable
|
|
24
|
+
- Work where the *shape* (not just the details) will change weekly — plan something smaller first
|
|
25
|
+
|
|
26
|
+
## Design principles to apply throughout
|
|
27
|
+
|
|
28
|
+
Hold the work to these when proposing phases and reviewing each `design.md`:
|
|
29
|
+
|
|
30
|
+
- **Vertical slices, not horizontal.** A phase touches every layer it needs to be end-to-end testable. The classic anti-pattern this skill exists to prevent: "Phase 1: all models. Phase 2: all controllers. Phase 3: all views." Trunk is unshippable until Phase 3 and Phase 2 has nothing to test. Instead: "Phase 1: one feature, full-stack, behind a flag."
|
|
31
|
+
- **SOLID, applied lightly.** Single Responsibility and Dependency Inversion are the two that actually bite at the phase-design level. The others usually take care of themselves if those two are clean.
|
|
32
|
+
- **KISS.** Skip the abstraction until you have two concrete callers. Three similar lines beat a premature framework. Inline before you extract.
|
|
33
|
+
- **Justify abstractions.** Every new module/service/class in `design.md` needs a one-sentence reason it exists separately. If you can't write it, inline the code.
|
|
34
|
+
|
|
35
|
+
## The workflow
|
|
36
|
+
|
|
37
|
+
### 1. Interview first
|
|
38
|
+
|
|
39
|
+
Don't propose phases until you understand the shape. Walk through the agenda below **one question per turn** — never dump the full list in a single message. The point is to let the operator steer at every step.
|
|
40
|
+
|
|
41
|
+
**How to ask:**
|
|
42
|
+
|
|
43
|
+
- **One topic per turn.** Pick the next unanswered item from the agenda. Ask only about that.
|
|
44
|
+
- **Number the options.** Whenever the question has 2+ plausible answers, present them as a numbered list so the operator can reply with just the number. Include a **recommended** pick (the one you'd default to given what you know so far) and say *why* it's the recommendation in one short line.
|
|
45
|
+
- **Restate locked-in answers at the top of each follow-up.** A running "Decisions so far:" block of one-line bullets, so the operator can spot drift and you can spot contradictions.
|
|
46
|
+
- **Open-ended questions are fine** when no obvious option set exists (e.g. "What's the end-state in user-facing terms?"). Still ask one at a time.
|
|
47
|
+
- **Stop when you're 95% sure of the shape.** Don't run the whole agenda for its own sake — skip topics that are already obvious from context. The agenda is a checklist *for you*, not a script to read aloud.
|
|
48
|
+
|
|
49
|
+
**Agenda (interviewer's checklist, not a dump):**
|
|
50
|
+
|
|
51
|
+
- **Goal** — end state in user-facing terms
|
|
52
|
+
- **Constraints** — deadlines, infra limits, things you can't break
|
|
53
|
+
- **Existing assets** — what's there to build on or rip out
|
|
54
|
+
- **Natural ordering** — dependency sequence (data → API → UI)?
|
|
55
|
+
- **External dependencies** — other teams, third-party APIs, infra access, design review. These reorder phases more than technical concerns do.
|
|
56
|
+
- **Destructive operations** — schema drops, data deletes, deprecations. These need their own phase with an explicit rollback note.
|
|
57
|
+
- **Test strategy** — what coverage is needed?
|
|
58
|
+
- **Done-per-phase** — minimum manual test that proves each phase shipped?
|
|
59
|
+
|
|
60
|
+
### 2. Propose phases, get agreement
|
|
61
|
+
|
|
62
|
+
A good phase:
|
|
63
|
+
- Is a vertical slice — testable end-to-end at merge time
|
|
64
|
+
- Ships independently — trunk works at every boundary
|
|
65
|
+
- Has a clear definition of done
|
|
66
|
+
- Is roughly 1–3 days of focused work
|
|
67
|
+
- Has a name that survives the PR title. If it adds *and* removes, capture both (e.g. "TX-Only Checkout + Geofencing Removal").
|
|
68
|
+
|
|
69
|
+
A good phase list:
|
|
70
|
+
- 3–8 phases for most projects
|
|
71
|
+
- Ordered by dependency, not priority
|
|
72
|
+
- Destructive operations isolated to their own phase
|
|
73
|
+
- Ends with a phase that visibly delivers the goal
|
|
74
|
+
|
|
75
|
+
Propose as a numbered outline. Let the user reorder, merge, or split before any files exist.
|
|
76
|
+
|
|
77
|
+
### 3. Scaffold the structure
|
|
78
|
+
|
|
79
|
+
```
|
|
80
|
+
docs/
|
|
81
|
+
README.md # roadmap tracker
|
|
82
|
+
NN-slug/
|
|
83
|
+
requirements.md # user stories + acceptance criteria
|
|
84
|
+
design.md # architecture + interfaces + test strategy
|
|
85
|
+
tasks.md # numbered checklist
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
Numbering: zero-padded (`01-`, `02-`). Slugs: kebab-case, ≤4 words.
|
|
89
|
+
|
|
90
|
+
Default: fully scaffold the *current* phase; future phases stay as one-liners in `README.md` until you start them. Later phases mutate based on what's learned early — don't pre-write what you'll have to rewrite.
|
|
91
|
+
|
|
92
|
+
### 4. Implement phase-by-phase
|
|
93
|
+
|
|
94
|
+
For each phase:
|
|
95
|
+
1. **Read the running architecture doc first** (`CLAUDE.md` or equivalent). That's how you don't repeat decisions or miss constraints from earlier phases.
|
|
96
|
+
2. Branch off main: `git checkout -b phase-NN-slug`
|
|
97
|
+
3. Work `tasks.md` top-to-bottom, checking subtasks as you go
|
|
98
|
+
4. Commit at section boundaries: `Phase N (M/X): description` — where `M` is the current section number and `X` is the total number of sections in `tasks.md`
|
|
99
|
+
5. Run full test suite + lint + format at each commit boundary
|
|
100
|
+
6. When `tasks.md` is fully checked, push and open a PR (see PR description shape below)
|
|
101
|
+
7. After merge: check the box in `docs/README.md`, update the running architecture doc if anything moved
|
|
102
|
+
|
|
103
|
+
## File templates
|
|
104
|
+
|
|
105
|
+
### requirements.md
|
|
106
|
+
|
|
107
|
+
Use RFC 2119 SHALL/SHOULD/MAY language for acceptance criteria. They're contracts — write them as something a QA tester could check.
|
|
108
|
+
|
|
109
|
+
Sections: Overview, Glossary, Requirements (per requirement: user story + numbered SHALL criteria), Out of scope.
|
|
110
|
+
|
|
111
|
+
### design.md
|
|
112
|
+
|
|
113
|
+
Sections: Overview, Architecture (each component gets Responsibility, Collaborators, "Why not inline?"), Data model changes, Test strategy, Migration / rollout, Future enhancements.
|
|
114
|
+
|
|
115
|
+
Every new abstraction needs a justification line. If you can't answer "why not inline this", inline it.
|
|
116
|
+
|
|
117
|
+
### tasks.md
|
|
118
|
+
|
|
119
|
+
Sections (`## [ ] N. <name>`) and tasks (`- [ ] N.M ...`) both get checkboxes. A section ticks only when every task under it ticks. Reference requirements by ID (`refs Req 1.1`) so coverage gaps are visible. Always end with a Validation section that includes the manual smoke flow.
|
|
120
|
+
|
|
121
|
+
## PR description shape
|
|
122
|
+
|
|
123
|
+
Three bullets. Don't pad them.
|
|
124
|
+
|
|
125
|
+
```
|
|
126
|
+
## What shipped
|
|
127
|
+
- <user-facing capability or removal>
|
|
128
|
+
|
|
129
|
+
## What proves it
|
|
130
|
+
- <new tests, smoke flow, manual check>
|
|
131
|
+
|
|
132
|
+
## What's deferred
|
|
133
|
+
- <link to later phase, or "none">
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
## Patterns and pitfalls
|
|
137
|
+
|
|
138
|
+
**Patterns to keep:**
|
|
139
|
+
- **Branch per phase:** `phase-NN-slug` — disposable, one PR per branch
|
|
140
|
+
- **Squash-merge:** one phase = one commit on main, full PR description preserved
|
|
141
|
+
- **Plan before code:** `requirements.md` is finalized and `design.md` is sketched before any task in `tasks.md` gets implemented.
|
|
142
|
+
- **Semantic test anchors** so later phases don't break earlier tests
|
|
143
|
+
- **One running architecture doc** kept current — read it before each phase, update it after
|
|
144
|
+
|
|
145
|
+
**When to break the rules:**
|
|
146
|
+
- **Phase grew mid-stream?** Split it. Add `NN-a-slug` / `NN-b-slug` or renumber.
|
|
147
|
+
- **Later phase invalidates an earlier requirement?** Update the earlier doc with a "Superseded by Phase N" note.
|
|
148
|
+
- **Phase ships nothing user-visible** (e.g. refactor prep)? Still its own PR — but say so in the description.
|
|
149
|
+
|
|
150
|
+
**Pitfalls to avoid:**
|
|
151
|
+
- **Horizontal-slice phases** (all models / all controllers / all views). Trunk is unshippable until the last phase merges.
|
|
152
|
+
- **Scaffolding all phases upfront in full detail** — later phases get invalidated by what you learn early.
|
|
153
|
+
- **Phases longer than ~3 days** — the phase should split. Long phases hide scope creep.
|
|
154
|
+
- **Requirements without acceptance criteria** — "Make X work" isn't a requirement; "When <condition>, the system SHALL <observable behavior>" is.
|
|
155
|
+
- **Tasks that don't reference requirements** — if you can't cite which requirement a task serves, the task probably isn't load-bearing.
|
|
156
|
+
- **Destructive operations mixed with feature work** — schema drops, deletes, deprecations belong in their own phase with a rollback note.
|