x_aeon_agents 1.0.28

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (48) hide show
  1. checksums.yaml +7 -0
  2. data/AGENTS.md +136 -0
  3. data/CHANGELOG.md +398 -0
  4. data/README.md +579 -0
  5. data/TODO.md +48 -0
  6. data/bin/xaa +5 -0
  7. data/lib/x_aeon_agents/agent_defaults.rb +60 -0
  8. data/lib/x_aeon_agents/agent_options.rb +58 -0
  9. data/lib/x_aeon_agents/agents/coder_agent.rb +82 -0
  10. data/lib/x_aeon_agents/agents/committer_agent.rb +71 -0
  11. data/lib/x_aeon_agents/agents/developer_agent.rb +235 -0
  12. data/lib/x_aeon_agents/agents/diff_interpreter_agent.rb +80 -0
  13. data/lib/x_aeon_agents/agents/documenter_agent.rb +44 -0
  14. data/lib/x_aeon_agents/agents/executor_agent.rb +9 -0
  15. data/lib/x_aeon_agents/agents/feedback_analyst_agent.rb +62 -0
  16. data/lib/x_aeon_agents/agents/gh_comments.gql +64 -0
  17. data/lib/x_aeon_agents/agents/git_diff_interpreter_agent.rb +57 -0
  18. data/lib/x_aeon_agents/agents/issue_implementer_agent.rb +88 -0
  19. data/lib/x_aeon_agents/agents/one_line_code_diff_summarizer_agent.rb +57 -0
  20. data/lib/x_aeon_agents/agents/plan_generator_agent.rb +51 -0
  21. data/lib/x_aeon_agents/agents/planner_agent.rb +89 -0
  22. data/lib/x_aeon_agents/agents/pull_request_creator_agent.rb +144 -0
  23. data/lib/x_aeon_agents/agents/readme/about_analyzer_agent.rb +55 -0
  24. data/lib/x_aeon_agents/agents/readme/contributing_agent.rb +47 -0
  25. data/lib/x_aeon_agents/agents/readme/development_agent.rb +54 -0
  26. data/lib/x_aeon_agents/agents/readme/documentation_agent.rb +43 -0
  27. data/lib/x_aeon_agents/agents/readme/features_agent.rb +45 -0
  28. data/lib/x_aeon_agents/agents/readme/how_it_works_agent.rb +47 -0
  29. data/lib/x_aeon_agents/agents/readme/license_agent.rb +43 -0
  30. data/lib/x_aeon_agents/agents/readme/public_api_agent.rb +44 -0
  31. data/lib/x_aeon_agents/agents/readme/quick_start_agent.rb +50 -0
  32. data/lib/x_aeon_agents/agents/readme/requirements_agent.rb +43 -0
  33. data/lib/x_aeon_agents/agents/readme_generator_agent.rb +438 -0
  34. data/lib/x_aeon_agents/agents/review_resolver_agent.rb +236 -0
  35. data/lib/x_aeon_agents/agents/review_responder_agent.rb +64 -0
  36. data/lib/x_aeon_agents/agents/skill_generator_agent.rb +95 -0
  37. data/lib/x_aeon_agents/agents/skill_installer_agent.rb +103 -0
  38. data/lib/x_aeon_agents/agents/task_starter_agent.rb +66 -0
  39. data/lib/x_aeon_agents/agents/tester_agent.rb +44 -0
  40. data/lib/x_aeon_agents/cli.rb +384 -0
  41. data/lib/x_aeon_agents/config.rb +110 -0
  42. data/lib/x_aeon_agents/gen_helpers.rb +270 -0
  43. data/lib/x_aeon_agents/helpers.rb +211 -0
  44. data/lib/x_aeon_agents/logger.rb +21 -0
  45. data/lib/x_aeon_agents/providers/cline.rb +78 -0
  46. data/lib/x_aeon_agents/version.rb +6 -0
  47. data/lib/x_aeon_agents.rb +38 -0
  48. metadata +296 -0
@@ -0,0 +1,384 @@
1
+ require 'thor'
2
+
3
+ module XAeonAgents
4
+ # Main X-Aeon Agents CLI
5
+ class Cli < Thor
6
+ # Global options
7
+ class_option :session_id, type: :string, desc: 'Session ID for persistence'
8
+ class_option :debug, type: :boolean, default: false, desc: 'Enable debug mode'
9
+
10
+ # --------------------------------------------------------------------------- #
11
+ # review-comments: Address Pull Request review comments
12
+ # --------------------------------------------------------------------------- #
13
+
14
+ desc 'review-comments [PULL_REQUEST_NUMBER]', 'Address review comments on a GitHub Pull Request'
15
+ long_desc <<~LONGDESC
16
+ Reads Pull Request comments addressed to the agent, improves or fixes
17
+ the code based on those comments, and replies to each one. The Pull
18
+ Request is identified by its number in the current GitHub repository,
19
+ or auto-detected from the current git branch when no number is given.
20
+
21
+ Examples:
22
+
23
+ $ xaa review-comments 42
24
+
25
+ $ xaa review-comments
26
+
27
+ $ xaa review-comments 42 --session-id my-session
28
+ LONGDESC
29
+ # Addresses review comments on a GitHub Pull Request.
30
+ #
31
+ # @param pull_request_number [Integer, nil] The GitHub Pull Request number to process, or nil to auto-detect
32
+ def review_comments(pull_request_number = nil)
33
+ Agents::ReviewResolverAgent.new(session_id: options[:session_id]).run(
34
+ pull_request_number: pull_request_number ? Integer(pull_request_number) : nil
35
+ )
36
+ end
37
+
38
+ # --------------------------------------------------------------------------- #
39
+ # commit: Commit staged changes with an AI-generated message
40
+ # --------------------------------------------------------------------------- #
41
+
42
+ desc 'commit', 'Commit staged changes with an AI-generated commit message'
43
+ long_desc <<~LONGDESC
44
+ Analyzes the currently staged changes and generates a meaningful commit
45
+ message before committing.
46
+
47
+ The --stage option controls how files are staged before committing:
48
+
49
+ --stage all Always stage all changes (tracked and untracked)
50
+ --stage if_empty Stage all changes only if the staging area is empty (default)
51
+ --stage none Do not stage anything; only commit already staged changes
52
+
53
+ Examples:
54
+
55
+ $ xaa commit
56
+
57
+ $ xaa commit --stage all
58
+
59
+ $ xaa commit --stage none
60
+ LONGDESC
61
+ option(
62
+ :stage,
63
+ type: :string,
64
+ default: 'if_empty',
65
+ desc: 'Staging strategy: all, if_empty, or none'
66
+ )
67
+ # Commits staged changes with an AI-generated commit message.
68
+ def commit
69
+ Agents::CommitterAgent.new(
70
+ session_id: options[:session_id],
71
+ stage: options[:stage].to_sym
72
+ ).run
73
+ end
74
+
75
+ # --------------------------------------------------------------------------- #
76
+ # create-pr: Push on Github and create a Pull Request if not already existing
77
+ # --------------------------------------------------------------------------- #
78
+
79
+ desc 'create-pr', 'Push on Github and create a Pull Request if not already existing'
80
+ long_desc <<~LONGDESC
81
+ Analyzes the current changes between the existing branch and its base,
82
+ pushes the branch on Github and generates a meaningful Pull Request on it.
83
+
84
+ The --base option gives the base ref of the Pull Request (defaults to main).
85
+
86
+ The --requirements option gives an optional description of requirements behind this Pull Request.
87
+
88
+ Examples:
89
+
90
+ $ xaa create-pr
91
+
92
+ $ xaa create-pr --base master
93
+
94
+ $ xaa create-pr --requirements "Add a new Home button on main screen"
95
+ LONGDESC
96
+ option :base, type: :string, default: 'main', desc: 'Base git ref to compare the existing base with for the Pull Request'
97
+ option :requirements, type: :string, default: nil, desc: 'Optional requirements that describe this Pull Request purpose'
98
+ # Push changes on Github and create a Pull Request.
99
+ def create_pr
100
+ agent_kwargs = {
101
+ base_sha: options[:base]
102
+ }
103
+ agent_kwargs[:requirements] = options[:requirements] if options[:requirements]
104
+ Agents::PullRequestCreatorAgent.new(session_id: options[:session_id]).run(**agent_kwargs)
105
+ end
106
+
107
+ # --------------------------------------------------------------------------- #
108
+ # generate-readme: Generate or update the project README
109
+ # --------------------------------------------------------------------------- #
110
+
111
+ desc 'generate-readme', 'Generate a comprehensive README from the codebase'
112
+ long_desc <<~LONGDESC
113
+ Uses AI to analyze the project and generate a README file with the
114
+ following sections (all enabled by default):
115
+
116
+ --about
117
+ --quick-start
118
+ --requirements
119
+ --features
120
+ --public-api
121
+ --documentation
122
+ --how-it-works
123
+ --development
124
+ --contributing
125
+ --license
126
+
127
+ Disable individual sections with --no-<section>.
128
+
129
+ Examples:
130
+
131
+ $ xaa generate-readme
132
+
133
+ $ xaa generate-readme --no-features --no-license --session-id my-session
134
+
135
+ $ xaa generate-readme --readme-file-path /path/to/custom/README.md
136
+ LONGDESC
137
+ option :about, type: :boolean, default: true, desc: 'Generate the About section'
138
+ option :quick_start, type: :boolean, default: true, desc: 'Generate the Quick Start section'
139
+ option :requirements, type: :boolean, default: true, desc: 'Generate the Requirements section'
140
+ option :features, type: :boolean, default: true, desc: 'Generate the Features section'
141
+ option :public_api, type: :boolean, default: true, desc: 'Generate the Public API section'
142
+ option :documentation, type: :boolean, default: true, desc: 'Generate the Documentation section'
143
+ option :how_it_works, type: :boolean, default: true, desc: 'Generate the How It Works section'
144
+ option :development, type: :boolean, default: true, desc: 'Generate the Development section'
145
+ option :contributing, type: :boolean, default: true, desc: 'Generate the Contributing section'
146
+ option :license, type: :boolean, default: true, desc: 'Generate the License section'
147
+ option :readme_file_path, type: :string, default: nil, desc: 'Path to the README file to generate or update'
148
+ # Generates or updates the project README file.
149
+ def generate_readme
150
+ agent_kwargs = {
151
+ gen_about: options[:about],
152
+ gen_quick_start: options[:quick_start],
153
+ gen_requirements: options[:requirements],
154
+ gen_features: options[:features],
155
+ gen_public_api: options[:public_api],
156
+ gen_documentation: options[:documentation],
157
+ gen_how_it_works: options[:how_it_works],
158
+ gen_development: options[:development],
159
+ gen_contributing: options[:contributing],
160
+ gen_license: options[:license]
161
+ }
162
+ agent_kwargs[:readme_file_path] = options[:readme_file_path] if options[:readme_file_path]
163
+ Agents::ReadmeGeneratorAgent.new(session_id: options[:session_id]).run(**agent_kwargs)
164
+ end
165
+
166
+ # --------------------------------------------------------------------------- #
167
+ # generate-skills: Generate skill files from ERB templates
168
+ # --------------------------------------------------------------------------- #
169
+
170
+ desc 'generate-skills', 'Generate skill files from ERB templates in skills.src/'
171
+ long_desc <<~LONGDESC
172
+ Processes the skills.src/ directory and writes generated skill files to
173
+ the output directory (default: skills/). ERB templates (.erb) are
174
+ evaluated; all other files are copied as-is.
175
+
176
+ Examples:
177
+
178
+ $ xaa generate-skills
179
+
180
+ $ xaa generate-skills --output-dir custom_skills
181
+ LONGDESC
182
+ option :output_dir, type: :string, default: 'skills', desc: 'Output directory for generated skills'
183
+ option(
184
+ :skill,
185
+ type: :string,
186
+ repeatable: true,
187
+ desc: 'Skill name(s) to generate. Can be repeated: --skill name1 --skill name2 or comma-separated: --skill name1,name2. Omit to generate all skills.'
188
+ )
189
+ # Generates skill files from ERB templates in skills.src/.
190
+ #
191
+ # @note Exits with status 1 if skill generation fails
192
+ def generate_skills
193
+ result = Agents::SkillGeneratorAgent.new(session_id: options[:session_id]).run(
194
+ output_dir: options[:output_dir],
195
+ skill_names: options[:skill]
196
+ )
197
+ exit 1 unless result[:success]
198
+ end
199
+
200
+ # --------------------------------------------------------------------------- #
201
+ # implement-issue: Implement a GitHub issue
202
+ # --------------------------------------------------------------------------- #
203
+
204
+ desc 'implement-issue ISSUE_NUMBER', 'Implement a GitHub issue using AI'
205
+ long_desc <<~LONGDESC
206
+ Reads the content (and comments) of a GitHub issue and delegates the
207
+ implementation to the Developer agent. The agent commits changes and
208
+ opens a Pull Request automatically.
209
+
210
+ Examples:
211
+
212
+ $ xaa implement-issue 15
213
+
214
+ $ xaa implement-issue 15 --session-id my-session
215
+ LONGDESC
216
+ # Implements a GitHub issue using AI.
217
+ #
218
+ # @param github_issue_number [Integer] The GitHub issue number to implement
219
+ def implement_issue(github_issue_number)
220
+ Agents::IssueImplementerAgent.new(
221
+ commit: true,
222
+ pull_request: true,
223
+ session_id: options[:session_id]
224
+ ).run(github_issue_number: Integer(github_issue_number))
225
+ end
226
+
227
+ # --------------------------------------------------------------------------- #
228
+ # implement: Implement arbitrary requirements
229
+ # --------------------------------------------------------------------------- #
230
+
231
+ desc 'implement REQUIREMENTS', 'Implement given requirements with AI'
232
+ long_desc <<~LONGDESC
233
+ Pass free-form requirements to the Developer agent, which will analyze
234
+ the codebase and produce the requested changes.
235
+
236
+ Examples:
237
+
238
+ $ xaa implement "Add authentication middleware"
239
+
240
+ $ xaa implement "Refactor the database layer" --commit --pr --session-id my-session
241
+ LONGDESC
242
+ option :commit, type: :boolean, default: false, desc: 'Commit files at every step'
243
+ option :pr, type: :boolean, default: false, desc: 'Create a GitHub Pull Request for the changes'
244
+ # Implements arbitrary requirements using AI.
245
+ #
246
+ # @param requirements [String] Free-form requirements describing the desired changes
247
+ def implement(requirements)
248
+ Agents::DeveloperAgent.new(
249
+ commit: options[:commit],
250
+ pull_request: options[:pr],
251
+ session_id: options[:session_id]
252
+ ).run(requirements:)
253
+ end
254
+
255
+ # --------------------------------------------------------------------------- #
256
+ # interpret-diffs: Summarize current git diffs
257
+ # --------------------------------------------------------------------------- #
258
+
259
+ desc 'interpret-diffs [BASE]', 'Summarize git diffs relative to a base ref'
260
+ long_desc <<~LONGDESC
261
+ Produces a one-line summary and a description of the intent behind the
262
+ changes in the working tree relative to BASE (default: HEAD).
263
+
264
+ Examples:
265
+
266
+ $ xaa interpret-diffs
267
+
268
+ $ xaa interpret-diffs main
269
+
270
+ $ xaa interpret-diffs HEAD~3
271
+ LONGDESC
272
+ # Summarizes current git diffs relative to a base ref.
273
+ #
274
+ # @param base [String] Git reference to diff against
275
+ def interpret_diffs(base = 'HEAD')
276
+ output = Agents::GitDiffInterpreterAgent.new(session_id: options[:session_id]).run(git_ref_base: base)
277
+ puts <<~EO_OUTPUT
278
+ ===== Code diffs interpretation:
279
+
280
+ #{output[:one_line_summary].strip}
281
+
282
+ #{output[:change_intent].strip}
283
+ EO_OUTPUT
284
+ end
285
+
286
+ # --------------------------------------------------------------------------- #
287
+ # prompt: Send a one-shot prompt to the AI agent
288
+ # --------------------------------------------------------------------------- #
289
+
290
+ desc 'prompt PROMPT', 'Send a one-shot prompt to the AI agent and print the response'
291
+ long_desc <<~LONGDESC
292
+ Executes a single prompt against the free simple AI model and prints
293
+ the last message of the conversation.
294
+
295
+ Examples:
296
+
297
+ $ xaa prompt "What is the capital of France?"
298
+
299
+ $ xaa prompt "Explain this code: puts 'hello'"
300
+ LONGDESC
301
+ # Sends a one-shot prompt to the AI agent and prints the response.
302
+ #
303
+ # @param user_prompt [String] The prompt text to send to the AI agent
304
+ def prompt(user_prompt)
305
+ agent = Agents::ExecutorAgent.new(session_id: options[:session_id], **Config.agent_options['free_simple'])
306
+ agent.run(user_instructions: user_prompt)
307
+ puts agent.conversation.last[:message]
308
+ end
309
+
310
+ # --------------------------------------------------------------------------- #
311
+ # install-skills: Install skills from the .skills manifest
312
+ # --------------------------------------------------------------------------- #
313
+
314
+ desc 'install-skills', 'Install skills and their dependencies from the .skills manifest'
315
+ long_desc <<~LONGDESC
316
+ Reads the .skills manifest file and installs every listed skill,
317
+ together with their recursively-resolved dependencies.
318
+
319
+ Example:
320
+
321
+ $ xaa install-skills
322
+
323
+ $ xaa install-skills --agent cline
324
+ LONGDESC
325
+ option :agent, type: :string, default: 'cline', desc: 'Agent name to be used to install skills'
326
+ # Installs skills and their dependencies from the .skills manifest.
327
+ def install_skills
328
+ Agents::SkillInstallerAgent.new(session_id: options[:session_id]).run(
329
+ agent: options[:agent].to_sym
330
+ )
331
+ end
332
+
333
+ # --------------------------------------------------------------------------- #
334
+ # start-task: Open a new git worktree for a task
335
+ # --------------------------------------------------------------------------- #
336
+
337
+ desc 'start-task', 'Open a new git worktree for a feature branch'
338
+ long_desc <<~LONGDESC
339
+ Interactive command that prompts for a branch name, creates the branch
340
+ (if it does not exist), sets up a git worktree in .worktrees/, pushes
341
+ the branch upstream, and opens it in VSCodium.
342
+
343
+ The --branch option gives the branch name directly, so the command is
344
+ not interactive anymore (no prompt to STDIN).
345
+
346
+ Example:
347
+
348
+ $ xaa start-task
349
+
350
+ $ xaa start-task --branch feature/my-task
351
+ LONGDESC
352
+ option :branch, type: :string, default: nil, desc: 'Branch name to create a worktree for. If omitted, the branch name is read interactively from STDIN.'
353
+ # Opens a new git worktree for a feature branch.
354
+ def start_task
355
+ branch = options[:branch] || begin
356
+ puts 'Branch name:'
357
+ $stdin.gets.strip
358
+ end
359
+ Agents::TaskStarterAgent.new(session_id: options[:session_id]).run(branch_name: branch)
360
+ end
361
+
362
+ # --------------------------------------------------------------------------- #
363
+ # Exit on failure for consistent scripting
364
+ # --------------------------------------------------------------------------- #
365
+
366
+ # Returns whether to exit on command failure.
367
+ #
368
+ # @return [Boolean] Always returns true so Thor exits with a non-zero status on command failure
369
+ def self.exit_on_failure?
370
+ true
371
+ end
372
+
373
+ no_commands do
374
+ # Initializes the CLI and applies global configuration from options.
375
+ #
376
+ # @param args [Array<Object>] Arguments forwarded to Thor's constructor
377
+ def initialize(*)
378
+ super
379
+ # Handle all the global setup from options
380
+ Config.debug = options[:debug]
381
+ end
382
+ end
383
+ end
384
+ end
@@ -0,0 +1,110 @@
1
+ require 'agents'
2
+ require 'ruby_llm'
3
+ require 'secret_string'
4
+
5
+ module XAeonAgents
6
+ # Singleton module to get all configuration of X-Aeon Agents
7
+ module Config
8
+ # TODO: Add test cases for this module
9
+ class << self
10
+ include Logger
11
+
12
+ # @!group Public API
13
+
14
+ # Automatically generate accessors for secrets taken from the ENV or the Launcher
15
+ %i[
16
+ cline_api_key
17
+ openrouter_api_key
18
+ github_token
19
+ ].each do |secret_name|
20
+ # Set the secret from a string
21
+ #
22
+ # @param secret [String] The secret value
23
+ define_method(:"#{secret_name}=") do |secret|
24
+ @secrets ||= {}
25
+ @secrets[secret_name] = SecretString.new(secret)
26
+ end
27
+
28
+ # Get the unprotected secret as a string.
29
+ # Use defaults if the secret was never set.
30
+ #
31
+ # @return [String, nil] The secret value, or nil if none
32
+ define_method(secret_name) do
33
+ @secrets ||= {}
34
+ @secrets[secret_name] ||= begin
35
+ env_secret = ENV.fetch(secret_name.to_s.upcase, nil)
36
+ env_secret ? SecretString.new(env_secret.dup) : Helpers.keys_from_launcher[secret_name]
37
+ end
38
+ @secrets[secret_name]&.to_unprotected
39
+ end
40
+ end
41
+
42
+ # @return [String] X-Aeon Agents data directory
43
+ attr_writer :data_dir
44
+
45
+ # @return [String] X-Aeon Agents data directory
46
+ def data_dir
47
+ @data_dir ||= '.x_aeon_agents'
48
+ end
49
+
50
+ # @return [Hash{Symbol => Object}] Default Cline CLI arguments
51
+ attr_writer :default_cline_cli_args
52
+
53
+ # @return [Hash{Symbol => Object}] Default Cline CLI arguments
54
+ def default_cline_cli_args
55
+ @default_cline_cli_args ||= { thinking: 'xhigh' }
56
+ end
57
+
58
+ # @return [Boolean] The debug mode
59
+ def debug=(value)
60
+ @debug = value
61
+ Logger.debug = debug
62
+ end
63
+
64
+ # @return [Boolean] The debug mode
65
+ def debug
66
+ @debug ||= ENV['X_AEON_AGENTS_DEBUG'] == '1'
67
+ end
68
+
69
+ # @return [AgentOptions] The available agent options.
70
+ def agent_options
71
+ @agent_options ||= AgentOptions.new
72
+ end
73
+
74
+ # Configure X-Aeon Agents
75
+ #
76
+ # @param kwargs [Hash] Any configuration property that can be set
77
+ def configure(**kwargs)
78
+ kwargs.each do |property, value|
79
+ send(:"#{property}=", value)
80
+ end
81
+ Logger.debug = debug
82
+ end
83
+
84
+ # @!group Internal
85
+
86
+ # Setup composable_agents in a lazy and memoized way
87
+ def setup_composable_agents
88
+ ENV['COMPOSABLE_AGENTS_DEBUG'] = '1' if debug
89
+ end
90
+
91
+ # Setup ai-agents in a lazy and memoized way
92
+ def setup_ai_agents
93
+ ENV['RUBYLLM_DEBUG'] = '1' if debug
94
+ ::Agents.configure do |ai_agents_config|
95
+ ai_agents_config.debug = debug
96
+ end
97
+ RubyLLM.configure do |ruby_llm_config|
98
+ ruby_llm_config.openrouter_api_key = openrouter_api_key
99
+ end
100
+ # Discover all the models
101
+ RubyLLM::Models.refresh!
102
+ end
103
+
104
+ # Setup Cline in a lazy and memoized way
105
+ def setup_cline
106
+ # Nothing to do
107
+ end
108
+ end
109
+ end
110
+ end