internator 0.1.5 → 0.1.7

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 1af75a509f24238b06b9081ec89eda9dcf3981ec697fc1dd0e1b6287ee86afff
4
- data.tar.gz: 1e105ebea5a252205ef570a996ede9759c072d0f4ae58555813ec291458d6b66
3
+ metadata.gz: 9f6853d7e223f60aa6d4ea55bde5b3c5f3ba13b7f349b2f3029efd25c97b4ba6
4
+ data.tar.gz: 442f68165403712347f11e182aa5c460762568d3f50d7efbb347acf4e783c04c
5
5
  SHA512:
6
- metadata.gz: 69d1281c009c468e94937e19a45fc68238b3bdb0bdb8f35b6e401882f8b9552c5dc992ff65b30f1ffac80511edd9aedccc6375b403dcb781c6279bd610811bad
7
- data.tar.gz: 17d16892a135a1ae0d92be8f674329cdafe50f978726387347e73e86e7e6b6f4f3c1cc69e3e164733a8d981ae7fb1191035ec91ae50c42486896793843ae27de
6
+ metadata.gz: 909efaf0ae3e29c473356f4a07e815a615488c32b138d3c06e502f82bfd748726a24f9c7151452825153305aeab739819429967612fddfd477e78701f713e0d7
7
+ data.tar.gz: bcc2c29ef1b5c197d45219c2ed4bea00beb520d40fe60c3f8b1b82a22be1ded73b937719620ad1fb45086874451767c395d843bd4ad43f45a34864b38e3c0f18
data/README.md CHANGED
@@ -7,7 +7,7 @@ Internator is a Ruby-based CLI tool that automates iterative pull request improv
7
7
  ## Requirements
8
8
 
9
9
  - Ruby (>= 2.5).
10
- - [Codex CLI](https://github.com/openai/codex) installed.
10
+ - [Codex CLI](https://github.com/openai/codex) installed (>= 0.3.0).
11
11
  - Environment variable `OPENAI_API_KEY` set to your OpenAI API key.
12
12
 
13
13
  ## Installation
@@ -17,15 +17,15 @@ gem install internator
17
17
  ```
18
18
 
19
19
  ## Usage
20
-
21
- Push to Github your new empty branch and run the `internator` command:
20
+ Create your new empty branch and run the `internator` command:
22
21
 
23
22
  ```bash
24
- internator "<PR Objectives>" [delay_mins]
23
+ internator "<PR Objectives>" [delay_mins] [parent_branch]
25
24
  ```
26
25
 
27
- - `<PR Objectives>`: Description of what the pull request should achieve.
28
- - `[delay_mins]`: (Optional) Minutes to wait between iterations (default: 0).
26
+ - `<PR Objectives>`: Description of what the pull request should achieve.
27
+ - `[delay_mins]`: (Optional) Minutes to wait between commits (default: 0).
28
+ - `[parent_branch]`: (Optional) Branch name to diff against (default: detected repository default branch).
29
29
 
30
30
  Example:
31
31
  ```bash
@@ -33,9 +33,22 @@ internator "Refactor authentication flow and add tests" 10
33
33
  ```
34
34
  For more detailed usage tips, see the [Usage Tips wiki page](https://github.com/AlexLarra/internator/wiki/Usage-tips).
35
35
 
36
- ## Contributing
36
+ ## Configuration
37
+
38
+ Internator reads custom instructions from a YAML file at `~/.internator_config.yml`. The file must define an `instructions` key whose value is the instruction text. For example:
39
+
40
+ ```yaml
41
+ # ~/.internator_config.yml
42
+ instructions: |
43
+ 1. Do not overuse code comments; if the method name says it all, comments are not necessary.
44
+ 2. Please treat files as if Vim were saving them with `set binary` and `set noeol`, i.e. do not add a final newline at the end of the file.
45
+ ```
46
+
47
+ When present, Internator will use these instructions instead of the built-in defaults.
48
+
49
+ ## Contributing
37
50
 
38
- Feel free to open issues or submit pull requests.
51
+ Feel free to open issues or submit pull requests.
39
52
 
40
53
  ## License
41
54
 
@@ -2,10 +2,54 @@ require "net/http"
2
2
  require "uri"
3
3
  require "json"
4
4
  require "tempfile"
5
+ require "yaml"
5
6
 
6
7
  module Internator
7
8
  # Command-line interface for the Internator gem
8
9
  class CLI
10
+ # Configuration file for custom options (YAML format).
11
+ CONFIG_FILE = File.expand_path('~/.internator_config.yml')
12
+
13
+ def self.config
14
+ @config ||= begin
15
+ if File.exist?(CONFIG_FILE)
16
+ YAML.load_file(CONFIG_FILE)
17
+ else
18
+ {}
19
+ end
20
+ rescue => e
21
+ warn "⚠️ Could not parse config file #{CONFIG_FILE}: #{e.message}"
22
+ {}
23
+ end
24
+ end
25
+
26
+ # Load custom instructions from config or fall back to built-in defaults
27
+ def self.instructions
28
+ main = <<~MAIN_INSTRUCTIONS
29
+ 1. If there are changes in the PR, first check if it has already been completed; if so, do nothing.
30
+ 2. Make ONLY one incremental change.
31
+ 3. Prioritize completing main objectives.
32
+ MAIN_INSTRUCTIONS
33
+
34
+ # Load custom instructions from ~/.internator_config.yml or fall back to built-in defaults
35
+ secondary =
36
+ if config.is_a?(Hash) && config['instructions'].is_a?(String)
37
+ config['instructions'].strip
38
+ else
39
+ <<~SECONDARY_INSTRUCTIONS
40
+ 1. Do not overuse code comments; if the method name says it all, comments are not necessary.
41
+ 2. Please treat files as if Vim were saving them with `set binary` and `set noeol`, i.e. do not add a final newline at the end of the file.
42
+ SECONDARY_INSTRUCTIONS
43
+ end
44
+
45
+ <<~INSTRUCTIONS.chomp
46
+ Main instructions:
47
+ #{main}
48
+ Secondary instructions:
49
+ #{secondary}
50
+ INSTRUCTIONS
51
+ end
52
+
9
53
  def self.run(args = ARGV)
10
54
  unless system("which codex > /dev/null 2>&1")
11
55
  abort "❌ 'codex' CLI is not installed or not in PATH. Please install it from https://github.com/openai/codex"
@@ -15,16 +59,42 @@ module Internator
15
59
  abort "❌ OPENAI_API_KEY not set. Please set the environment variable."
16
60
  end
17
61
 
18
- if args.empty? || args.size > 2
19
- abort "❌ Usage: internator \"<PR Objectives>\" [delay_mins]"
62
+ # Parse arguments: objectives, optional delay (minutes), optional parent_branch
63
+ if args.empty? || args.size > 3
64
+ abort "❌ Usage: internator \"<PR Objectives>\" [delay_mins] [parent_branch]"
20
65
  end
21
66
 
22
67
  objectives = args[0]
23
- delay_mins = if args[1]
24
- Integer(args[1]) rescue abort("❌ Invalid delay_mins: must be an integer")
25
- else
26
- 0
27
- end
68
+ delay_mins = 0
69
+ parent_branch = nil
70
+ case args.size
71
+ when 2
72
+ # single extra arg: integer delay or parent branch
73
+ begin
74
+ delay_mins = Integer(args[1])
75
+ rescue ArgumentError
76
+ parent_branch = args[1]
77
+ end
78
+ when 3
79
+ delay_mins = Integer(args[1]) rescue abort("❌ Invalid delay_mins: must be an integer")
80
+ parent_branch = args[2]
81
+ end
82
+
83
+ remote, default_base = git_detect_default_base&.split("/", 2)
84
+ branch = git_current_branch
85
+
86
+ abort "❌ Git remote is not detected." unless remote
87
+ abort "❌ Git default branch is not detected." unless default_base
88
+
89
+ if branch == default_base
90
+ abort "❌ You are on the default branch '#{default_base}'. Please create a new branch before running Internator."
91
+ end
92
+
93
+ if parent_branch && !system("git rev-parse --verify --quiet #{parent_branch} > /dev/null 2>&1")
94
+ abort "❌ Specified parent branch '#{parent_branch}' does not exist."
95
+ end
96
+
97
+ git_upstream(remote, branch)
28
98
 
29
99
  iteration = 1
30
100
  Signal.trap("INT") do
@@ -35,15 +105,14 @@ module Internator
35
105
  begin
36
106
  loop do
37
107
  puts "\n🌀 Iteration ##{iteration} - #{Time.now.strftime("%Y-%m-%d %H:%M:%S")}"
38
- exit_code = codex_cycle(objectives, iteration)
108
+
109
+ exit_code = codex_cycle(objectives, iteration, remote, default_base, branch, parent_branch)
39
110
  if exit_code != 0
40
- puts "🚨 Codex process exited with code #{exit_code}. Stopping."
41
- break
111
+ abort "🚨 Codex process exited with code #{exit_code}. Stopping."
42
112
  end
43
113
 
44
114
  if `git status --porcelain`.strip.empty?
45
- puts "🎉 Objectives completed; no new changes. Exiting loop..."
46
- break
115
+ abort "🎉 Objectives completed; no new changes. Exiting loop..."
47
116
  end
48
117
 
49
118
  auto_commit
@@ -59,7 +128,7 @@ module Internator
59
128
  end
60
129
 
61
130
  # Detect the repository's default branch across remotes (e.g., main, master, develop)
62
- def self.detect_default_base
131
+ def self.git_detect_default_base
63
132
  remotes = `git remote`.split("\n").reject(&:empty?)
64
133
  remotes.unshift('origin') unless remotes.include?('origin')
65
134
  remotes.each do |remote|
@@ -75,23 +144,27 @@ module Internator
75
144
  nil
76
145
  end
77
146
 
78
- # Executes one Codex iteration by diffing against the appropriate base branch
79
- def self.codex_cycle(objectives, iteration)
80
- # Determine configured upstream and current branch name
147
+ def self.git_current_branch
148
+ `git rev-parse --abbrev-ref HEAD`.strip
149
+ end
150
+
151
+ def self.git_upstream(remote, branch)
81
152
  upstream = `git rev-parse --abbrev-ref --symbolic-full-name @{u} 2>/dev/null`.strip
82
- current_branch = `git rev-parse --abbrev-ref HEAD`.strip
83
- # Choose diff base:
84
- # 1) If upstream is tracking current branch, use remote default branch
85
- # 2) Else if upstream exists, diff against that
86
- # 3) Otherwise fallback to remote default branch or master
87
- if !upstream.empty? && upstream.split('/').last == current_branch
88
- base = detect_default_base || upstream
89
- elsif !upstream.empty?
90
- base = upstream
91
- else
92
- base = detect_default_base || 'master'
153
+
154
+ if upstream.empty?
155
+ # As upstream is not configured, push the current branch and set upstream to remote
156
+ puts "🔄 No upstream configured for branch '#{branch}'. Sending to #{remote}..."
157
+ system("git push -u #{remote} #{branch}")
158
+ upstream = `git rev-parse --abbrev-ref --symbolic-full-name @{u} 2>/dev/null`.strip
93
159
  end
94
- # Get the diff against the chosen base
160
+
161
+ upstream
162
+ end
163
+
164
+ # Executes one Codex iteration by diffing against the parent or default branch
165
+ def self.codex_cycle(objectives, iteration, remote, default_base, branch, parent_branch = nil)
166
+ # Determine base branch: user-specified parent or detected default
167
+ base = parent_branch || default_base
95
168
  current_diff = `git diff #{base} 2>/dev/null`
96
169
  current_diff = "No initial changes" if current_diff.strip.empty?
97
170
  prompt = <<~PROMPT
@@ -99,12 +172,7 @@ module Internator
99
172
  Iteration: #{iteration}
100
173
  Current Pull Request: #{current_diff}
101
174
 
102
- Instructions:
103
- 1. If there are changes in the PR, first check if it has already been completed; if so, do nothing.
104
- 2. Make ONLY one incremental change.
105
- 3. Prioritize completing main objectives.
106
- 4. Do not overuse code comments; if the method name says it all, comments are not necessary.
107
- 5. Please treat files as if Vim were saving them with `set binary` and `set noeol`, i.e. do not add a final newline at the end of the file.
175
+ #{instructions}
108
176
  PROMPT
109
177
 
110
178
  CodexService.new(prompt).call
@@ -1,3 +1,3 @@
1
1
  module Internator
2
- VERSION = "0.1.5"
2
+ VERSION = "0.1.7"
3
3
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: internator
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.5
4
+ version: 0.1.7
5
5
  platform: ruby
6
6
  authors:
7
7
  - AlexLarra