wralph 0.1.2

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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: b57c8d8dca1cee5599eb0948a7eb9cbab5032bd33c9eb6aa879fbf7ec3b590b3
4
+ data.tar.gz: b04697444291bd7749c80a26a3f2cc29710f8fa9ef2d76a89c9334450ece3c52
5
+ SHA512:
6
+ metadata.gz: 727b377643d3159fa5832d0c67c08b207e11bd806648e652913f80b4b38412c929c6c028bfd92c55037ab59f4c56c919421ff7cced18733512ee29d568dab083
7
+ data.tar.gz: 6e12faa9ada5ac909b82e29f5850820cbd5c98407789632d6392a1fbe5eb1fd3cbafe5c6803fbd63f91851d0166af80a5b86f657b319df41679413a690551c4c
data/README.md ADDED
@@ -0,0 +1,275 @@
1
+ # WRALPH - Workflow Ralph
2
+
3
+ **WRALPH** (short for **Workflow Ralph**) is an implementation of the "Ralph Wiggum" technique of using AI agents to iteratively develop code until complete.
4
+
5
+ ## Overview
6
+
7
+ This toolset provides a streamlined workflow for fixing bugs, implementing features, or making code changes by combining AI-powered code generation with human oversight. The process flows from issue repository pull → plan generation → execution → CI iteration → human review.
8
+
9
+ ## What Makes WRALPH Distinct
10
+
11
+ What sets WRALPH apart from other autonomous development tools is its explicit use of **non-AI components** to facilitate the workflow and allow multiple instances running (even on the same repository) without interference:
12
+
13
+ - **Issue Repository**: Plans are generated from structured issue repositories (default: GitHub issues), providing clear requirements and context
14
+ - **Git Worktrees**: Each issue gets its own isolated worktree, ensuring clean separation and preventing conflicts
15
+ - **Remote CI Evaluation**: Results are evaluated using remote CI (default: CircleCI), which allows multiple instances of WRALPH to run in parallel without interfering with each other
16
+
17
+ The remote CI feature is particularly powerful—it enables you to run multiple instances of WRALPH simultaneously, each working on different issues, without the processes stepping on each other. This parallel execution capability makes WRALPH highly scalable for teams working on multiple issues concurrently.
18
+
19
+ ## Workflow
20
+
21
+ ```mermaid
22
+ flowchart TD
23
+ Start[Create GitHub Issue] --> Init[wralph init]
24
+ Init --> Plan[wralph plan ISSUE_NUMBER]
25
+ Plan --> PlanGen[AI Generates Plan]
26
+ PlanGen --> Review{Human Reviews Plan}
27
+ Review -->|Needs Changes| PlanGen
28
+ Review -->|Approved| Execute[Execute Plan]
29
+ Execute --> Code[AI Makes Code Changes]
30
+ Code --> PR[Create Pull Request]
31
+ PR --> CI[Monitor CI Build]
32
+ CI --> CheckCI{CI Passes?}
33
+ CheckCI -->|No| Fix[AI Analyzes Failures]
34
+ Fix --> Push[Push Fixes]
35
+ Push --> CI
36
+ CheckCI -->|Yes| HumanReview{Human Reviews PR}
37
+ HumanReview -->|Changes Requested| Feedback[wralph feedback ISSUE_NUMBER]
38
+ Feedback --> Changes[AI Makes Changes]
39
+ Changes --> CI
40
+ HumanReview -->|Approved| Cleanup[wralph remove ISSUE_NUMBER]
41
+ Cleanup --> Done[Complete]
42
+ ```
43
+
44
+ ## Prerequisites
45
+
46
+ Before using these scripts, ensure you have the following tools installed:
47
+
48
+ - **[Ruby](https://www.ruby-lang.org/)** `>= 3.0` - Ruby interpreter (use rbenv, rvm, or Homebrew to install)
49
+ - **[GitHub CLI](https://cli.github.com/)** (`gh`) - Must be authenticated (`gh auth login`)
50
+ - **[Claude Code CLI](https://code.claude.com/)** (`claude`) - For AI-powered code generation
51
+ - **[jq](https://stedolan.github.io/jq/)** - JSON processor (install via `brew install jq` on macOS)
52
+ - **[curl](https://curl.se/)** - HTTP client (usually pre-installed)
53
+ - **[worktrunk](https://github.com/max-sixty/worktrunk)** (`wt`) - Git worktree management tool
54
+
55
+ ## Configuration
56
+
57
+ After running `wralph init`, you'll have a `.wralph` directory with configuration files:
58
+
59
+ ### Secrets (`secrets.yaml`)
60
+
61
+ Add your CI API token to `.wralph/secrets.yaml`:
62
+
63
+ ```yaml
64
+ ci_api_token: your_token_here
65
+ ```
66
+
67
+ This file is automatically git-ignored for security.
68
+
69
+ ### Adapter Configuration (`config.yaml`)
70
+
71
+ The `.wralph/config.yaml` file allows you to configure which adapters to use:
72
+
73
+ ```yaml
74
+ # Objective repository adapter (where issues/objectives are stored)
75
+ objective_repository:
76
+ source: github_issues # or "custom" with class_name
77
+
78
+ # CI/CD adapter (for build monitoring)
79
+ ci:
80
+ source: circle_ci # or "custom" with class_name
81
+ ```
82
+
83
+ **Custom Adapters**: You can create custom adapters by:
84
+ 1. Setting `source: custom` in the config
85
+ 2. Specifying a `class_name` (e.g., `MyCustomAdapter`)
86
+ 3. Creating a file `.wralph/my_custom_adapter.rb` with your class implementation
87
+
88
+ See the config file comments for more details on creating custom adapters.
89
+
90
+ ## Installation
91
+
92
+ If you're installing WRALPH (once it's distributed via Homebrew):
93
+
94
+ ```bash
95
+ brew install wralph # When available
96
+ ```
97
+
98
+ For development:
99
+
100
+ 1. Ensure you have Ruby 3.0 or higher installed
101
+ 2. Clone this repository
102
+ 3. Install dependencies: `bundle install`
103
+
104
+ ## Usage
105
+
106
+ ### 1. Initialize WRALPH
107
+
108
+ ```bash
109
+ wralph init
110
+ ```
111
+
112
+ This creates the `.wralph` directory structure needed for plans and configuration.
113
+
114
+ ### 2. Create and Plan an Issue
115
+
116
+ ```bash
117
+ wralph plan 123
118
+ ```
119
+
120
+ This command:
121
+ - Fetches GitHub issue #123
122
+ - Uses Claude Code to generate a detailed plan saved to `.wralph/plans/plan_123.md`
123
+ - Prompts you to review and approve the plan
124
+ - Automatically proceeds to execution once approved
125
+
126
+ The plan includes:
127
+ - Analysis of the issue
128
+ - Approach to solving it
129
+ - Test cases to verify the solution
130
+ - Implementation steps
131
+ - Potential risks or considerations
132
+ - Questions for clarification (if needed)
133
+
134
+ After plan approval, `wralph plan` automatically:
135
+ - Creates a git worktree for branch `issue-123`
136
+ - Uses Claude Code to implement the plan
137
+ - Commits changes (including the plan file) with a descriptive message
138
+ - Pushes the branch and creates a pull request
139
+ - Monitors CI build status for the PR (default: CircleCI)
140
+ - If CI fails: extracts failure details, uses Claude Code to fix issues, pushes fixes, and iterates (up to 10 retries)
141
+ - If CI passes: exits successfully
142
+
143
+ **Note:** The AI is instructed not to run tests locally, but instead to push a PR and rely on CI.
144
+
145
+ Failure details are saved to `tmp/issue-{NUMBER}_failure_details_{ITERATION}.txt` for reference.
146
+
147
+ ### 3. Handle PR Review Feedback
148
+
149
+ When reviewers request changes on a PR:
150
+
151
+ ```bash
152
+ wralph feedback 123
153
+ ```
154
+
155
+ This command:
156
+ - Switches to the worktree for branch `issue-123`
157
+ - Prompts you to enter feedback (multi-line input, press Enter 3 times to submit)
158
+ - Uses Claude Code to analyze the feedback and make changes
159
+ - Pushes the changes and monitors CI again (with automatic retries if CI fails)
160
+
161
+ ### 4. Clean Up (Optional)
162
+
163
+ To delete a branch and its worktree after completion:
164
+
165
+ ```bash
166
+ wralph remove 123
167
+ ```
168
+
169
+ This removes:
170
+ - Local branch `issue-123`
171
+ - Remote branch `issue-123`
172
+ - Associated worktree
173
+
174
+ ## Commands Overview
175
+
176
+ | Command | Purpose |
177
+ |---------|---------|
178
+ | `wralph init` | Initialize WRALPH in the current repository |
179
+ | `wralph plan <issue_number>` | Generate plan and execute it (creates PR, monitors CI) |
180
+ | `wralph feedback <issue_number>` | Handle PR review feedback and iterate |
181
+ | `wralph remove <issue_number>` | Clean up branches and worktrees |
182
+
183
+ ## Directory Structure
184
+
185
+ The tool creates and manages the following:
186
+
187
+ ```
188
+ .
189
+ ├── .wralph/ # WRALPH configuration directory (created by `wralph init`)
190
+ │ ├── config.yaml # Adapter configuration (objective_repository, ci)
191
+ │ ├── secrets.yaml # API tokens and secrets (git-ignored)
192
+ │ └── plans/ # Generated plans
193
+ │ └── plan_123.md # Example plan file
194
+ ├── tmp/ # CI failure details (created automatically)
195
+ │ └── issue-123_failure_details_1_1.txt
196
+ └── [worktree directories] # Managed by worktrunk
197
+ ```
198
+
199
+ ## Key Features
200
+
201
+ - **Worktree Isolation**: Each issue gets its own git worktree via worktrunk, keeping your main working directory clean
202
+ - **Automatic CI Integration**: Monitors CI builds (configurable adapter), extracts failure details, and iteratively fixes issues
203
+ - **Configurable Adapters**: Support for custom objective repositories and CI adapters via configuration
204
+ - **Human Oversight**: Plans require approval before execution, and PRs can be reviewed before merging
205
+ - **Failure Recovery**: Automatically retries up to 10 times to fix CI failures
206
+ - **Branch Management**: Automatic branch creation, PR creation, and cleanup utilities
207
+ - **Plan Persistence**: All plans are saved for reference and context during iterations
208
+
209
+ ## Workflow Tips
210
+
211
+ 1. **Initialize First**: Always run `wralph init` before using other commands
212
+ 2. **Start Clean**: Ensure you don't have uncommitted changes before running `wralph plan` (or commit/stash them first)
213
+ 3. **Review Plans Carefully**: The plan review step is your opportunity to catch issues before code changes
214
+ 4. **Monitor Output**: Commands provide colored output (ℹ info, ✓ success, ⚠ warning, ✗ error) to track progress
215
+ 5. **PR Linking**: PRs automatically reference the GitHub issue (e.g., "Fixes #123") for proper linking
216
+ 6. **CI Timeout**: CI monitoring waits up to 1 hour for builds to complete
217
+ 7. **Max Retries**: If CI fails more than 10 times, the command exits and requires manual intervention
218
+
219
+ ## Example Session
220
+
221
+ ```bash
222
+ # 1. Initialize WRALPH in your repository
223
+ wralph init
224
+
225
+ # 2. Create a GitHub issue describing the bug/feature
226
+
227
+ # 3. Generate and execute plan:
228
+ wralph plan 456
229
+
230
+ # Review the generated plan at .wralph/plans/plan_456.md
231
+ # Answer any questions Claude asked, then approve
232
+
233
+ # 4. Command automatically:
234
+ # - Executes the plan
235
+ # - Creates PR #789
236
+ # - Monitors CI
237
+ # - Fixes failures if needed
238
+
239
+ # 5. Review the PR on GitHub
240
+
241
+ # 6. If changes are needed:
242
+ wralph feedback 456
243
+ # Enter your feedback, press Enter 3 times
244
+
245
+ # 7. After merging, clean up:
246
+ wralph remove 456
247
+ ```
248
+
249
+ ## Error Handling
250
+
251
+ The tool includes error handling for common scenarios:
252
+ - WRALPH not initialized (must run `wralph init` first)
253
+ - Missing GitHub authentication
254
+ - Uncommitted changes (with warning)
255
+ - Duplicate branches (must delete first)
256
+ - Missing plan files
257
+ - CI timeout or max retries exceeded
258
+ - Missing required tools
259
+
260
+ All errors provide clear messages about what went wrong and how to resolve the issue.
261
+
262
+ ## Development
263
+
264
+ ### Requirements
265
+
266
+ - Ruby 3.0 or higher (specified in `.ruby-version` and `Gemfile`)
267
+ - Development dependencies installed via `bundle install`
268
+
269
+ ### Running Tests
270
+
271
+ ```bash
272
+ bundle exec rspec
273
+ ```
274
+
275
+ Tests are located in the `spec/` directory and are excluded from distribution packages.
data/bin/wralph ADDED
@@ -0,0 +1,60 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ # Add lib directory to load path
5
+ lib_dir = File.expand_path(File.join(File.dirname(__FILE__), '..', 'lib'))
6
+ $LOAD_PATH.unshift(lib_dir) unless $LOAD_PATH.include?(lib_dir)
7
+
8
+ require 'wralph'
9
+
10
+ # Parse arguments
11
+ case ARGV[0]
12
+ when 'init'
13
+ Wralph::Run::Init.run
14
+ when 'plan'
15
+ if ARGV[1].nil?
16
+ puts "Usage: wralph plan <github_issue_number>"
17
+ exit 1
18
+ end
19
+ Wralph::Run::Plan.run(ARGV[1])
20
+ when 'execute'
21
+ if ARGV[1].nil?
22
+ puts "Usage: wralph execute <github_issue_number>"
23
+ exit 1
24
+ end
25
+ Wralph::Run::ExecutePlan.run(ARGV[1])
26
+ when 'feedback'
27
+ if ARGV[1].nil?
28
+ puts "Usage: wralph feedback <github_issue_number>"
29
+ exit 1
30
+ end
31
+ Wralph::Run::Feedback.run(ARGV[1])
32
+ when 'remove'
33
+ if ARGV[1].nil?
34
+ puts "Usage: wralph remove <github_issue_number>"
35
+ exit 1
36
+ end
37
+ Wralph::Run::Remove.run(ARGV[1])
38
+ when '--version', '-v'
39
+ puts "wralph #{Wralph::VERSION}"
40
+ else
41
+ if ARGV.empty? || ARGV[0] == '--help' || ARGV[0] == '-h'
42
+ puts "Usage: wralph <command> [options]"
43
+ puts ""
44
+ puts "Commands:"
45
+ puts " init Initialize WRALPH in the current repository"
46
+ puts " plan <issue_number> Generate and execute plan for GitHub issue"
47
+ puts " execute <issue_number> Execute an existing plan for GitHub issue"
48
+ puts " feedback <issue_number> Handle PR review feedback"
49
+ puts " remove <issue_number> Remove branch, worktree, and remote branch for issue"
50
+ puts ""
51
+ puts "Options:"
52
+ puts " --version, -v Show version"
53
+ puts " --help, -h Show this help message"
54
+ exit 0
55
+ else
56
+ puts "Unknown command: #{ARGV[0]}"
57
+ puts "Run 'wralph --help' for usage information"
58
+ exit 1
59
+ end
60
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'shellwords'
4
+ require_relative '../../interfaces/shell'
5
+
6
+ module Wralph
7
+ module Adapters
8
+ module Agents
9
+ module ClaudeCode
10
+ def self.run(instructions)
11
+ claude_output, = Interfaces::Shell.run_command(
12
+ "claude -p #{Shellwords.shellescape(instructions)} --dangerously-skip-permissions", raise_on_error: false
13
+ )
14
+ claude_output
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Wralph
4
+ module Adapters
5
+ module Agents
6
+ require_relative 'agents/claude_code'
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,199 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'json'
4
+ require 'net/http'
5
+ require 'uri'
6
+ require_relative '../../interfaces/shell'
7
+ require_relative '../../interfaces/print'
8
+
9
+ module Wralph
10
+ module Adapters
11
+ module Cis
12
+ module CircleCi
13
+ def self.http_get(url, api_token: nil)
14
+ uri = URI(url)
15
+ req = Net::HTTP::Get.new(uri)
16
+ req['Circle-Token'] = api_token if api_token
17
+ res = Net::HTTP.start(uri.hostname, uri.port, use_ssl: uri.scheme == 'https') { |http| http.request(req) }
18
+ [res.body, res.is_a?(Net::HTTPSuccess)]
19
+ end
20
+
21
+ # Get CircleCI build status for a PR
22
+ def self.build_status(pr_number, repo_owner, repo_name, api_token, verbose: true)
23
+ unless api_token
24
+ Interfaces::Print.error 'ci_api_token is not set in .wralph/secrets.yaml'
25
+ return nil
26
+ end
27
+
28
+ # Get the branch name from the PR
29
+ branch_name, = Interfaces::Shell.run_command("gh pr view #{pr_number} --json headRefName -q .headRefName")
30
+ branch_name = branch_name.strip
31
+ Interfaces::Print.info "Checking CircleCI build for branch: #{branch_name}" if verbose && $stderr.respond_to?(:puts)
32
+
33
+ # Get the pipeline for this branch
34
+ project_slug = "gh/#{repo_owner}/#{repo_name}"
35
+ pipeline_url = "https://circleci.com/api/v2/project/#{project_slug}/pipeline?branch=#{branch_name}"
36
+
37
+ stdout, success = http_get(pipeline_url, api_token: api_token)
38
+ unless success
39
+ Interfaces::Print.warning "No CircleCI pipeline found for branch #{branch_name}" if verbose
40
+ return 'not_found'
41
+ end
42
+
43
+ begin
44
+ pipeline_data = JSON.parse(stdout)
45
+ pipeline_item = pipeline_data['items']&.first
46
+ return 'not_found' unless pipeline_item
47
+
48
+ pipeline_id = pipeline_item['id']
49
+ pipeline_state = pipeline_item['state']
50
+
51
+ Interfaces::Print.info "Pipeline ID: #{pipeline_id}, State: #{pipeline_state}" if verbose
52
+
53
+ return 'running' if %w[running pending].include?(pipeline_state)
54
+
55
+ # Get workflow details to check if it succeeded
56
+ workflow_url = "https://circleci.com/api/v2/pipeline/#{pipeline_id}/workflow"
57
+ stdout, success = http_get(workflow_url, api_token: api_token)
58
+ unless success
59
+ Interfaces::Print.warning "No workflow found for pipeline #{pipeline_id}" if verbose
60
+ return 'unknown'
61
+ end
62
+
63
+ workflow_data = JSON.parse(stdout)
64
+ workflow_item = workflow_data['items']&.first
65
+ unless workflow_item
66
+ Interfaces::Print.warning "No workflow found for pipeline #{pipeline_id}" if verbose
67
+ return 'unknown'
68
+ end
69
+
70
+ workflow_status = workflow_item['status']&.strip
71
+ Interfaces::Print.info "Workflow status: #{workflow_status}" if verbose
72
+
73
+ workflow_status
74
+ rescue JSON::ParserError => e
75
+ Interfaces::Print.warning "Failed to parse JSON response: #{e.message}" if verbose
76
+ 'unknown'
77
+ end
78
+ end
79
+
80
+ # Wait for CircleCI build to complete
81
+ def self.wait_for_build(pr_number, repo_owner, repo_name, api_token)
82
+ max_wait_time = 3600 # 1 hour max wait
83
+ wait_interval = 30 # Check every 30 seconds
84
+ elapsed = 0
85
+
86
+ Interfaces::Print.info 'Waiting for CircleCI build to complete...'
87
+
88
+ last_status = nil
89
+ while elapsed < max_wait_time
90
+ # Get status quietly first to check if it changed
91
+ status = build_status(pr_number, repo_owner, repo_name, api_token, verbose: false)
92
+
93
+ # If status hasn't changed since last check, just print a dot
94
+ if last_status && last_status == status
95
+ print '.'
96
+ sleep wait_interval
97
+ elapsed += wait_interval
98
+ next
99
+ end
100
+
101
+ # Status changed (or first check) - get verbose output with pipeline/workflow details
102
+ status = build_status(pr_number, repo_owner, repo_name, api_token, verbose: true)
103
+
104
+ case status
105
+ when 'success'
106
+ Interfaces::Print.success 'CircleCI build passed!'
107
+ return true
108
+ when 'failed', 'error', 'canceled', 'unauthorized'
109
+ Interfaces::Print.warning "CircleCI build failed with status: #{status}"
110
+ return false
111
+ when 'running', 'on_hold'
112
+ Interfaces::Print.info "Build still running... (elapsed: #{elapsed}s)"
113
+ when 'not_found'
114
+ Interfaces::Print.warning 'Build not found yet, waiting...'
115
+ else
116
+ Interfaces::Print.warning "Unknown build status: #{status}, waiting..."
117
+ end
118
+ sleep wait_interval
119
+ elapsed += wait_interval
120
+ last_status = status
121
+ end
122
+
123
+ Interfaces::Print.error 'Timeout waiting for CircleCI build to complete'
124
+ exit 1
125
+ end
126
+
127
+ # Get CircleCI build failures
128
+ def self.build_failures(pr_number, repo_owner, repo_name, api_token)
129
+ return 'ci_api_token is not set in .wralph/secrets.yaml' unless api_token
130
+
131
+ # 1. Get branch name from PR
132
+ branch_name, = Interfaces::Shell.run_command("gh pr view #{pr_number} --json headRefName -q .headRefName")
133
+ branch_name = branch_name.strip
134
+
135
+ # 2. Get the latest pipeline for that branch
136
+ project_slug = "gh/#{repo_owner}/#{repo_name}"
137
+ pipeline_url = "https://circleci.com/api/v2/project/#{project_slug}/pipeline?branch=#{branch_name}"
138
+ stdout, success = http_get(pipeline_url, api_token: api_token)
139
+ return 'Could not fetch pipeline' unless success
140
+
141
+ pipeline_id = JSON.parse(stdout)['items']&.first&.[]('id')
142
+ return 'No pipeline found' unless pipeline_id
143
+
144
+ # 3. Get the workflow ID
145
+ workflow_url = "https://circleci.com/api/v2/pipeline/#{pipeline_id}/workflow"
146
+ stdout, = http_get(workflow_url, api_token: api_token)
147
+ workflow_id = JSON.parse(stdout)['items']&.first&.[]('id')
148
+ return 'No workflow found' unless workflow_id
149
+
150
+ # 4. Get jobs and filter for failures
151
+ jobs_url = "https://circleci.com/api/v2/workflow/#{workflow_id}/job"
152
+ stdout, = http_get(jobs_url, api_token: api_token)
153
+ jobs_data = JSON.parse(stdout)
154
+
155
+ failed_jobs = jobs_data['items']&.select { |j| %w[failed error].include?(j['status']) }
156
+ return "All jobs passed for branch #{branch_name}." if failed_jobs.nil? || failed_jobs.empty?
157
+
158
+ # 5. For each failed job, reach into v1.1 API to get the logs
159
+ failed_jobs.map do |job|
160
+ job_num = job['job_number']
161
+ job_name = job['name']
162
+
163
+ # We use v1.1 because v2 does not provide step-level output URLs
164
+ v1_api_url = "https://circleci.com/api/v1.1/project/github/#{repo_owner}/#{repo_name}/#{job_num}"
165
+ v1_stdout, v1_success = http_get(v1_api_url, api_token: api_token)
166
+
167
+ log_content = "Job: #{job_name} (##{job_num}) failed."
168
+
169
+ if v1_success
170
+ job_details = JSON.parse(v1_stdout)
171
+ # Find the step that actually failed
172
+ failed_step = job_details['steps']&.find { |s| s['actions'].to_a.any? { |a| a['failed'] } }
173
+
174
+ if failed_step
175
+ action = failed_step['actions'].find { |a| a['failed'] }
176
+ output_url = action&.[]('output_url')
177
+
178
+ if output_url
179
+ # The output_url provides a JSON array of log lines
180
+ raw_log_json, = http_get(output_url)
181
+ begin
182
+ logs = JSON.parse(raw_log_json)
183
+ # Join the last 30 lines of the log for context
184
+ tail_logs = logs.map { |l| l['message'] }.last(30).join("\n")
185
+ log_content += "\nFAILED STEP: #{failed_step['name']}\n\nLOG TAIL:\n#{tail_logs}"
186
+ rescue StandardError
187
+ log_content += "\n(Could not parse raw log output)"
188
+ end
189
+ end
190
+ end
191
+ end
192
+
193
+ log_content
194
+ end.join("\n\n#{'=' * 40}\n\n")
195
+ end
196
+ end
197
+ end
198
+ end
199
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Wralph
4
+ module Adapters
5
+ module Cis
6
+ require_relative 'cis/circle_ci'
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'fileutils'
4
+ require 'shellwords'
5
+ require_relative '../../interfaces/shell'
6
+ require_relative '../../interfaces/repo'
7
+
8
+ module Wralph
9
+ module Adapters
10
+ module ObjectiveRepositories
11
+ module GithubIssues
12
+ def self.download!(identifier)
13
+ # Ensure objectives directory exists
14
+ objectives_dir = File.join(Interfaces::Repo.wralph_dir, 'objectives')
15
+ FileUtils.mkdir_p(objectives_dir)
16
+
17
+ # Get the local file path
18
+ file_path = local_file_path(identifier)
19
+
20
+ # Fetch GitHub issue content
21
+ issue_content, stderr, success = Interfaces::Shell.run_command("gh issue view #{Shellwords.shellescape(identifier)}")
22
+ raise "Failed to download GitHub issue ##{identifier}: #{stderr}" unless success
23
+
24
+ # Write the issue content to the file (overwrites if exists)
25
+ File.write(file_path, issue_content)
26
+
27
+ file_path
28
+ end
29
+
30
+ def self.local_file_path(identifier)
31
+ objectives_dir = File.join(Interfaces::Repo.wralph_dir, 'objectives')
32
+ File.join(objectives_dir, "#{identifier}.md")
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Wralph
4
+ module Adapters
5
+ module ObjectiveRepositories
6
+ require_relative 'objective_repositories/github_issues'
7
+ end
8
+ end
9
+ end