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,64 @@
1
+ module XAeonAgents
2
+ module Agents
3
+ # Agent responsible for generating a reply to a review comment
4
+ class ReviewResponderAgent < ComposableAgents::Cline::Agent
5
+ prepend AgentDefaults
6
+
7
+ # Define input artifacts contracts
8
+ #
9
+ # @return [Hash{Symbol => Object}] Set of input artifacts description, per artifact name
10
+ def input_artifacts_contracts
11
+ super.merge(
12
+ {
13
+ conversations: {
14
+ description: 'All PR conversations and comments to be considered (context)',
15
+ type: :json
16
+ },
17
+ open_comment_for_reply: {
18
+ description: 'The exact comment to be replied to',
19
+ type: :json
20
+ },
21
+ requirements: 'The requirements that have been implemented (or "No requirements")',
22
+ plan: 'The implementation plan that was used to implement those requirements (or "No implementation plan")',
23
+ files_diffs: 'The code changes from implement_requirements workflow (or "No changes")'
24
+ }
25
+ )
26
+ end
27
+
28
+ # Define output artifacts contracts
29
+ #
30
+ # @return [Hash{Symbol => Object}] Set of output artifacts description, per artifact name
31
+ def output_artifacts_contracts
32
+ super.merge(
33
+ reply: {
34
+ description: 'The exact reply text to post',
35
+ type: :text
36
+ }
37
+ )
38
+ end
39
+
40
+ # Constructor
41
+ #
42
+ # @param agent_params [Hash{Symbol => Object}] Extra agent parameters
43
+ def initialize(**agent_params)
44
+ super(
45
+ name: 'ReviewResponder',
46
+ role: 'You are a review responder agent. Your role is to reply to feedback, ' \
47
+ 'taking into account the feedback itself, and the work that has been done because of this feedback.',
48
+ objective: 'Generate a reply to a review comment',
49
+ **agent_params
50
+ )
51
+ self.constraints = <<~EO_CONSTRAINTS
52
+ - You are in read-only mode.
53
+ - Do NOT modify or write any file.
54
+ - The implementation work is already complete (captured in the artifacts).
55
+ - ONLY focus on addressing the specific comment of the `#{artifact_ref(:open_comment_for_reply)}` artifact appropriately.
56
+ - Do NOT answer or reply to any other comment.
57
+ - You already have ALL the information required.
58
+ - You MUST NOT ask follow-up questions.
59
+ - You MUST NOT ask for user confirmation.
60
+ EO_CONSTRAINTS
61
+ end
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,95 @@
1
+ require 'fileutils'
2
+ require 'pathname'
3
+
4
+ module XAeonAgents
5
+ module Agents
6
+ # Agent responsible for generating skill files from ERB templates.
7
+ class SkillGeneratorAgent < ComposableAgents::Agent
8
+ prepend AgentDefaults
9
+
10
+ # Define input artifacts contracts
11
+ #
12
+ # @return [Hash{Symbol => Object}] Set of input artifacts description, per artifact name
13
+ def input_artifacts_contracts
14
+ {
15
+ output_dir: 'Output directory for generated skills',
16
+ skill_names: {
17
+ description: 'Optional list of skill names to generate. If nil or empty, all skills are generated.',
18
+ optional: true
19
+ }
20
+ }
21
+ end
22
+
23
+ # Define output artifacts contracts
24
+ #
25
+ # @return [Hash{Symbol => Object}] Set of output artifacts description, per artifact name
26
+ def output_artifacts_contracts
27
+ { success: 'Whether the skill generation was successful' }
28
+ end
29
+
30
+ # Execute the agent to generate skill files from ERB templates.
31
+ #
32
+ # @param output_dir [String] Output directory for generated skills
33
+ # @param skill_names [Array<String>, nil] Optional list of skill names to generate.
34
+ # Supports comma-separated values within each element. If nil or empty, all skills are generated.
35
+ # @return [Hash{Symbol => Object}] Output artifacts content
36
+ def run(output_dir: 'skills', skill_names: nil)
37
+ transformations = {
38
+ '.erb' => proc { |src_file| GenHelpers::ErbEvaluator.new(src_file).result }
39
+ }.freeze
40
+
41
+ src_dir = File.expand_path('skills.src')
42
+ src_pathname = Pathname.new(src_dir)
43
+ dest_dir = File.expand_path(output_dir)
44
+
45
+ FileUtils.mkdir_p(dest_dir)
46
+
47
+ # Normalize skill_names: flatten, split by comma, strip, compact, uniq
48
+ normalized_skill_names = (skill_names || [])
49
+ .flat_map { |name| name.split(',') }
50
+ .map(&:strip)
51
+ .reject(&:empty?)
52
+ .uniq
53
+
54
+ failed = false
55
+ Dir.glob(File.join(src_dir, '**', '*'), File::FNM_DOTMATCH)
56
+ .select { |f| File.file?(f) && File.basename(f) != '.skill_config.yml' }
57
+ .each do |src_file|
58
+ relative_path = Pathname.new(src_file).relative_path_from(src_pathname).to_s
59
+ # Determine the top-level skill directory for this file.
60
+ # Skip files whose top-level skill directory is not in the requested list
61
+ next if !normalized_skill_names.empty? && !normalized_skill_names.include?(relative_path.split('/').first)
62
+
63
+ file_ext = File.extname(relative_path)
64
+ dst_file = File.join(
65
+ dest_dir,
66
+ transformations.key?(file_ext) ? relative_path.sub(/#{Regexp.escape(file_ext)}$/, '') : relative_path
67
+ )
68
+ puts "Processing: #{relative_path}"
69
+ puts " Output: #{dst_file}"
70
+ begin
71
+ FileUtils.mkdir_p(File.dirname(dst_file))
72
+ if transformations.key?(file_ext)
73
+ File.write(dst_file, transformations[file_ext].call(src_file))
74
+ else
75
+ FileUtils.cp(src_file, dst_file)
76
+ end
77
+ puts ' Status: ✓ Processed successfully'
78
+ rescue StandardError => e
79
+ puts " Status: ✗ Error - #{e.message}\n #{e.backtrace.first}"
80
+ failed = true
81
+ end
82
+ puts
83
+ end
84
+
85
+ puts
86
+ if failed
87
+ puts 'Skills generated with some errors (see above).'
88
+ else
89
+ puts 'Skills generated successfully.'
90
+ end
91
+ { success: !failed }
92
+ end
93
+ end
94
+ end
95
+ end
@@ -0,0 +1,103 @@
1
+ require 'front_matter_parser'
2
+
3
+ module XAeonAgents
4
+ module Agents
5
+ # Agent responsible for installing skills from the .skills manifest.
6
+ class SkillInstallerAgent < ComposableAgents::Agent
7
+ prepend AgentDefaults
8
+
9
+ # Define input artifacts contracts
10
+ #
11
+ # @return [Hash{Symbol => Object}] Set of input artifacts description, per artifact name
12
+ def input_artifacts_contracts
13
+ { agent: 'Agent name to be used to install skills' }
14
+ end
15
+
16
+ # Execute the agent to install skills from the .skills manifest.
17
+ #
18
+ # @param agent [String] Agent name to be used to install skills
19
+ # @return [Hash{Symbol => Object}] Output artifacts content
20
+ def run(agent: 'cline')
21
+ agent_name = agent.to_sym
22
+ original_no_color = ENV.fetch('NO_COLOR', nil)
23
+ ENV['NO_COLOR'] = '1'
24
+ begin
25
+ list_lines = `skillkit manifest`.split("\n")
26
+ ensure
27
+ ENV['NO_COLOR'] = original_no_color
28
+ end
29
+ list_lines = list_lines[(list_lines.index('Skills:') + 1)..]
30
+ list_lines[0..(list_lines.index('') - 1)].each_slice(2) do |repo_desc, skills_desc|
31
+ install_skills_recursive(
32
+ repo_desc[4..].strip,
33
+ skills_desc[12..].split(',').map(&:strip),
34
+ agent_name
35
+ )
36
+ end
37
+
38
+ puts
39
+ puts 'Skills identified in the skillkit manifest and their dependencies have been installed successfully'
40
+ { installed: true }
41
+ end
42
+
43
+ private
44
+
45
+ # Install skills using skillkit, including dependencies recursively.
46
+ #
47
+ # @param repo [String] Repository
48
+ # @param skills [Array<String>] Skills to install
49
+ # @param agent [Symbol] Agent to use
50
+ def install_skills_recursive(repo, skills, agent)
51
+ return if skills.empty?
52
+
53
+ agents_config = {
54
+ cline: { skills_dir: '.cline/skills' }
55
+ }
56
+ skills_dir = agents_config[agent][:skills_dir]
57
+
58
+ puts "Install skills #{repo} / #{skills.join(',')}..."
59
+ system "skillkit install #{repo} --yes --skills=#{skills.join(',')} --agent=#{agent}", exception: true
60
+ fix_skills_metadata(skills, agent)
61
+
62
+ # Resolve dependencies
63
+ deps_per_repo = {}
64
+ skills.each do |skill|
65
+ deps = FrontMatterParser::Parser.parse_file("#{skills_dir}/#{skill}/SKILL.md").front_matter.dig('metadata', 'dependencies')
66
+ next if deps.nil?
67
+
68
+ deps.each do |skill_dep|
69
+ skill_dep = "#{repo}:#{skill_dep}" unless skill_dep.include?(':')
70
+ skill_dep_repo, skill_dep_name = skill_dep.split(':')
71
+ unless File.exist?("#{skills_dir}/#{skill_dep_name}/SKILL.md")
72
+ deps_per_repo[skill_dep_repo] ||= []
73
+ deps_per_repo[skill_dep_repo] << skill_dep_name unless deps_per_repo[skill_dep_repo].include?(skill_dep_name)
74
+ end
75
+ end
76
+ end
77
+
78
+ deps_per_repo.each { |dep_repo, dep_skills| install_skills_recursive(dep_repo, dep_skills, agent) }
79
+ end
80
+
81
+ # Fix the .skillkit.json subpath property after skillkit install.
82
+ #
83
+ # @param skills [Array<String>] Installed skills
84
+ # @param agent [Symbol] Agent used
85
+ def fix_skills_metadata(skills, agent)
86
+ require 'json'
87
+
88
+ agents_config = { cline: { skills_dir: '.cline/skills' } }
89
+ skills_dir = agents_config[agent][:skills_dir]
90
+
91
+ skills.each do |skill_name|
92
+ json_file = "#{skills_dir}/#{skill_name}/.skillkit.json"
93
+ json = JSON.parse(File.read(json_file))
94
+ next if json['subpath'].start_with?('skills/')
95
+
96
+ json['subpath'] = "skills/#{json['subpath']}"
97
+ puts "Fix subpath of #{json_file}"
98
+ File.write(json_file, JSON.pretty_generate(json))
99
+ end
100
+ end
101
+ end
102
+ end
103
+ end
@@ -0,0 +1,66 @@
1
+ module XAeonAgents
2
+ module Agents
3
+ # Agent responsible for opening a new git worktree for a task.
4
+ class TaskStarterAgent < ComposableAgents::Agent
5
+ # Exception raised when the target worktree directory is invalid
6
+ # (already exists but is not a git worktree, or is a worktree on a
7
+ # different branch than the requested one).
8
+ class TaskStarterError < StandardError
9
+ end
10
+
11
+ prepend AgentDefaults
12
+
13
+ # Define input artifacts contracts
14
+ #
15
+ # @return [Hash{Symbol => Object}] Set of input artifacts description, per artifact name
16
+ def input_artifacts_contracts
17
+ { branch_name: 'The name of the git branch to create worktree for' }
18
+ end
19
+
20
+ # Define output artifacts contracts
21
+ #
22
+ # @return [Hash{Symbol => Object}] Set of output artifacts description, per artifact name
23
+ def output_artifacts_contracts
24
+ { worktree_dir: 'The directory where the worktree was created' }
25
+ end
26
+
27
+ # Execute the agent to open a new git worktree for a feature branch.
28
+ #
29
+ # @param branch_name [String] Name of the git branch to create worktree for
30
+ # @return [Hash{Symbol => Object}] Output artifacts content
31
+ def run(branch_name:)
32
+ dir = ".worktrees/#{branch_name.tr('/', '_')}"
33
+ puts "Setting worktree #{dir} to work on branch #{branch_name}..."
34
+ # Create the branch if it does not exist (without checking it out)
35
+ Helpers.git.branch(branch_name).create unless Helpers.git.branches.any? { |branch| branch.name == branch_name }
36
+ # Create the git worktree only if it does not exist yet (idempotent)
37
+ if File.directory?(dir)
38
+ # The directory already exists: it must be a git worktree for the requested branch.
39
+ # A git worktree has a `.git` file (a gitdir pointer), not a `.git` directory.
40
+ unless File.file?(File.join(dir, '.git'))
41
+ raise TaskStarterError, <<~EO_MSG.strip
42
+ Directory '#{dir}' already exists but is not a git worktree (no '.git' pointer file found).
43
+ Please remove it or choose a different branch name.
44
+ EO_MSG
45
+ end
46
+
47
+ # The directory is a worktree: ensure it tracks the requested branch.
48
+ worktree_branch = Git.open(dir).current_branch
49
+ if worktree_branch != branch_name
50
+ raise TaskStarterError, <<~EO_MSG.strip
51
+ Directory '#{dir}' is already a git worktree on branch '#{worktree_branch}', which differs from the requested branch '#{branch_name}'.
52
+ Please choose a different branch name or remove the existing worktree.
53
+ EO_MSG
54
+ end
55
+ else
56
+ # Call git worktree add on existing branches only
57
+ Helpers.git.lib.worktree_add(dir, branch_name)
58
+ end
59
+ # Push to remote if branch doesn't exist there yet
60
+ Helpers.git.push(Helpers.github_remote, branch_name, set_upstream: true)
61
+ Helpers.run_cmd("VSCodium.exe \"#{dir}\"")
62
+ { worktree_dir: dir }
63
+ end
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,44 @@
1
+ module XAeonAgents
2
+ module Agents
3
+ # Agent responsible for fixing regressions induced by new features or fixes, while keeping initial requirements and implementation plan in mind.
4
+ # If decisions in the implementation plan prevent fixing regressions, modify the implementation plan and report those modifications.
5
+ class TesterAgent < ComposableAgents::Cline::Agent
6
+ prepend AgentDefaults
7
+
8
+ # Define input artifacts contracts
9
+ #
10
+ # @return [Hash{Symbol => Object}] Set of input artifacts description, per artifact name
11
+ def input_artifacts_contracts
12
+ super.merge(
13
+ requirements: 'The initial requirements',
14
+ plan: 'The implementation plan devised from the requirements',
15
+ files_diffs: 'The full list of files changes and differences that have been done to implement the initial ' \
16
+ 'requirements following the implementation plan',
17
+ tests_output: 'The output of running the whole tests suite',
18
+ tests_cmd: 'The command line to be used to run the whole tests suite'
19
+ )
20
+ end
21
+
22
+ # Define output artifacts contracts
23
+ #
24
+ # @return [Hash{Symbol => Object}] Set of output artifacts description, per artifact name
25
+ def output_artifacts_contracts
26
+ super.merge(plan_modifications: 'The modification or divergence you considered from the implementation plan')
27
+ end
28
+
29
+ # Constructor
30
+ #
31
+ # @param agent_params [Hash{Symbol => Object}] Extra agent parameters
32
+ def initialize(**agent_params)
33
+ super(
34
+ name: 'Tester',
35
+ objective: <<~EO_OBJECTIVE,
36
+ Fix any regression that has been induced by new features or fixes, while keeping the initial requirements and implementation plan in mind.
37
+ If the decisions taken in the implementation plan prevent you from fixing regressions, modify the implementation plan and report those modifications to the user.
38
+ EO_OBJECTIVE
39
+ **agent_params
40
+ )
41
+ end
42
+ end
43
+ end
44
+ end