gitbroker 0.3.2 → 0.4.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 59093b20e5a7ab790348ada3c1a75e9855f02dd5e45bf0fe41fc429508bdd446
4
- data.tar.gz: f32a209bf3262713ff41afe31745a184a4caa045ca8383ae99eb724f614b60eb
3
+ metadata.gz: 227a743489c659dad707f6e11180988105a2ec34d89601a6ec61d62f5c4a8f53
4
+ data.tar.gz: 2ab80c483b24299e9c2776cc44fcda117e7fc5274069f1107f48e26c044190d0
5
5
  SHA512:
6
- metadata.gz: 063ab1cd7edd780743c336726d09bfdd56214a7d151377355c4aa3ff62bca3c0d192d34b3d9d5694da73acad9f7995ba95f490d450cb6d66f01b1dd17c986be9
7
- data.tar.gz: 407c6e92912c69cc3915e953fa6e22c00819250250ff98f325e1ee9d07f6aacc81cec0a80bf2328fb783a6e6b47c72f5db90f4417a9a4c1a4a3f5e230cba4f3c
6
+ metadata.gz: ac95b2ec94aca69fcd5cc0f78b0cc1f5541098158722c13825d3f32746ec3ecd2c0c2c0f339f3a405ad12cca055d11894ebaabb96e462836bf69d2a9a71fcd5f
7
+ data.tar.gz: f0507d54eeb1efefebc493b2edbade05da29e6a589ae0260ab79b3c6d61090ffdeb6dacf7019a379c0237160ac5331029f2bdb20f65ca0fc072d03009681f0bf
data/lib/gitbroker/cli.rb CHANGED
@@ -16,6 +16,8 @@ module Gitbroker
16
16
  launch Start the companion in the background if it is not running
17
17
  stop Stop the companion (and its background service, so it stays stopped)
18
18
  restart Restart the companion, e.g. after `gem update gitbroker`
19
+ upgrade Install the newest gem, remove old versions, restart the companion
20
+ uninstall Remove the service, the start link, the Claude Code hooks and the gem (keeps your pairing)
19
21
  install-service Run `gitbroker start` in the background (launchd / systemd --user), and let
20
22
  the website's "Start companion" button wake it (gitbroker:// handler)
21
23
  install-skill Install the gitbroker skills into ~/.claude/skills
@@ -30,8 +32,10 @@ module Gitbroker
30
32
  Agent activity is visible only to you; only status is sent, never prompts, file contents or tool input.
31
33
  TEXT
32
34
 
33
- def initialize(argv, out: $stdout, err: $stderr, env: ENV, home: Dir.home, input: $stdin)
35
+ def initialize(argv, out: $stdout, err: $stderr, env: ENV, home: Dir.home, input: $stdin, maintenance: nil, lifecycle: nil)
34
36
  @argv = argv.dup
37
+ @maintenance = maintenance
38
+ @lifecycle = lifecycle
35
39
  @out = out
36
40
  @err = err
37
41
  @env = env
@@ -50,6 +54,8 @@ module Gitbroker
50
54
  when "launch" then launch
51
55
  when "stop" then stop
52
56
  when "restart" then restart
57
+ when "upgrade" then upgrade
58
+ when "uninstall" then uninstall
53
59
  when "install-service" then install_service
54
60
  when "install-skill" then install_skill
55
61
  when "status" then status
@@ -93,7 +99,12 @@ module Gitbroker
93
99
  raise Error, "This machine is not paired. Run `gitbroker login` first." unless config.paired?
94
100
 
95
101
  path = ServiceInstaller.new(home: @home, env: @env).install!
96
- @out.puts "Installed and started #{path}. Run this again after upgrading the gem."
102
+ @out.puts "Installed and started #{path}."
103
+ if ServiceInstaller.pinned?
104
+ @out.puts "No RubyGems `gitbroker` wrapper was found, so it runs this version-pinned gem executable: run " \
105
+ "`gitbroker install-service` again after upgrading the gem."
106
+ end
107
+ refresh_hooks
97
108
  handler = UrlHandlerInstaller.new(home: @home).install!
98
109
  @out.puts "Registered #{handler} so the website's \"Start companion\" button can wake it."
99
110
  end
@@ -120,6 +131,27 @@ module Gitbroker
120
131
  @out.puts "Restarting the companion."
121
132
  end
122
133
 
134
+ def maintenance = @maintenance ||= Maintenance.new(hooks: hooks_installer)
135
+
136
+ # The restart runs this (old) process's code, which only asks the service manager or spawns RubyGems' wrapper, so
137
+ # the companion comes back on the new version. An unpaired machine has no companion to restart.
138
+ def upgrade
139
+ maintenance.upgrade!
140
+ return @out.puts("Upgraded. Run `gitbroker login` to pair this machine.") unless config.paired?
141
+
142
+ lifecycle.restart
143
+ @out.puts "Upgraded. Restarting the companion."
144
+ end
145
+
146
+ def uninstall
147
+ lifecycle.stop # a companion started by hand too, not only the service
148
+ error = nil
149
+ removed = maintenance.uninstall!(service: ServiceInstaller.new(home: @home, env: @env), url_handler: UrlHandlerInstaller.new(home: @home)) { error = _1 }
150
+ raise Error, "Removed the service, the start link and the hooks, but `gem uninstall gitbroker` failed: #{error}" unless removed
151
+
152
+ @out.puts "Uninstalled the service, the start link, the Claude Code hooks and the gem. Your pairing in #{config.dir} is kept."
153
+ end
154
+
123
155
  def install_skill
124
156
  SkillInstaller.new(home: @home, env: @env).install!.each { @out.puts "Installed the #{File.basename(_1)} skill to #{_1}." }
125
157
  end
@@ -152,6 +184,14 @@ module Gitbroker
152
184
 
153
185
  def hooks_installer = HooksInstaller.default(env: @env, home: @home)
154
186
 
187
+ # Hooks written by an older version name its versioned exe; point them at the current launcher.
188
+ def refresh_hooks
189
+ installer = hooks_installer
190
+ installer.install! if installer.installed?
191
+ rescue Error
192
+ nil
193
+ end
194
+
155
195
  # Called by Claude Code on every hook event: silent, always exit 0.
156
196
  def hook
157
197
  Hook.new(event: @argv.shift, input: @input, socket_path: File.join(Config.default_dir(@env), "daemon.sock"), env: @env).run
@@ -38,7 +38,8 @@ module Gitbroker
38
38
  end
39
39
 
40
40
  def initialize(scanner:, client_factory:, task_runner_factory:, out: $stdout, sleeper: ->(seconds) { sleep(seconds) },
41
- clock: -> { Time.now }, spawner: ->(&block) { Thread.new(&block) }, relay: nil, process_scanner: nil, lifecycle: nil)
41
+ clock: -> { Time.now }, spawner: ->(&block) { Thread.new(&block) }, relay: nil, process_scanner: nil, lifecycle: nil,
42
+ maintenance: Maintenance.new)
42
43
  @scanner = scanner
43
44
  @client_factory = client_factory
44
45
  @task_runner = task_runner_factory.call(method(:report))
@@ -49,6 +50,7 @@ module Gitbroker
49
50
  @relay = relay
50
51
  @process_scanner = process_scanner
51
52
  @lifecycle = lifecycle
53
+ @maintenance = maintenance
52
54
  @repositories = []
53
55
  @running = Set.new
54
56
  @running_lock = Mutex.new
@@ -95,6 +97,8 @@ module Gitbroker
95
97
  client.on_subscribed do
96
98
  @backoff = 1
97
99
  rescan(client)
100
+ client.perform("hello", "version" => VERSION, "platform" => Gitbroker.platform,
101
+ "service" => @lifecycle ? @lifecycle.service_installed? : false)
98
102
  @out.puts "Connected. #{@repositories.size} repositories reported."
99
103
  end
100
104
  client.on_tick do
@@ -118,7 +122,8 @@ module Gitbroker
118
122
  clean.each_slice(BATCH) { client.perform("observe", "events" => _1) }
119
123
  end
120
124
 
121
- # The website's Restart / Stop buttons (Device#control!). Nothing else about the message is trusted or used.
125
+ # The website's Restart / Stop / Upgrade / Uninstall buttons (Device#control!). Only the action name is used, to pick
126
+ # one of these fixed behaviours; nothing from the message reaches a command.
122
127
  def control(action)
123
128
  return unless @lifecycle
124
129
 
@@ -134,9 +139,64 @@ module Gitbroker
134
139
 
135
140
  @respawn = true
136
141
  stop
142
+ when "upgrade" then upgrade
143
+ when "uninstall" then uninstall
137
144
  end
138
145
  end
139
146
 
147
+ # Never while a task runs: the restart would kill it. A deferred upgrade runs once, when the last task ends.
148
+ # Results name the control under "control", not "action": Action Cable's perform overwrites a data key "action" with
149
+ # the method name ("control_result").
150
+ def upgrade
151
+ return unless claim_upgrade
152
+
153
+ @out.puts "Upgrading (asked from GitBroker)."
154
+ @maintenance.upgrade!
155
+ report("control_result", "control" => "upgrade", "ok" => true, "error" => nil)
156
+ restart_into_new_version
157
+ rescue StandardError => e
158
+ @running_lock.synchronize { @upgrading = false } # the old companion keeps running; another request may retry
159
+ @out.puts "Upgrade failed: #{e.message}"
160
+ report("control_result", "control" => "upgrade", "ok" => false, "error" => e.message[0, 200])
161
+ end
162
+
163
+ # True when the upgrade should run now. With tasks running it is remembered instead; one already under way wins.
164
+ def claim_upgrade
165
+ @running_lock.synchronize do
166
+ if @upgrading
167
+ false
168
+ elsif @running.any?
169
+ @pending_upgrade = true
170
+ false
171
+ else
172
+ @pending_upgrade = false
173
+ @upgrading = true
174
+ end
175
+ end
176
+ end
177
+
178
+ # With a service the manager restarts us into the new gem (it may kill us mid-call); otherwise respawn after the loop.
179
+ def restart_into_new_version
180
+ @out.puts "Upgraded; restarting."
181
+ return if @lifecycle.restart_service
182
+
183
+ @respawn = true
184
+ stop
185
+ end
186
+
187
+ # Reports once the gem is gone (or failed to go), before removing the service: that step may end this process.
188
+ # Never respawns.
189
+ def uninstall
190
+ @running_lock.synchronize { @upgrading = true } # no task is claimed or started from here on
191
+ @out.puts "Uninstalling (asked from GitBroker)."
192
+ @maintenance.uninstall!(service: @lifecycle.service, url_handler: UrlHandlerInstaller.new) do |error|
193
+ report("control_result", "control" => "uninstall", "ok" => error.nil?, "error" => error && error[0, 200])
194
+ end
195
+ stop
196
+ end
197
+
198
+ def upgrading? = @running_lock.synchronize { @upgrading }
199
+
140
200
  def handle(client, message)
141
201
  return control(message["action"]) if message["type"] == "control"
142
202
 
@@ -144,14 +204,14 @@ module Gitbroker
144
204
  return unless id.is_a?(String)
145
205
 
146
206
  case message["type"]
147
- when "task_available" then client.perform("claim", "task_id" => id)
207
+ when "task_available" then client.perform("claim", "task_id" => id) unless upgrading? # left to another device or the sweep
148
208
  when "cancel" then @task_runner.cancel(id)
149
209
  when "task" then start_task(id, message)
150
210
  end
151
211
  end
152
212
 
153
213
  def start_task(id, message)
154
- return unless @running_lock.synchronize { @running.add?(id) }
214
+ return unless @running_lock.synchronize { !@upgrading && @running.add?(id) } # the upgrade's restart would kill it
155
215
 
156
216
  repositories = @repositories
157
217
  @spawner.call do
@@ -159,7 +219,8 @@ module Gitbroker
159
219
  rescue StandardError => e
160
220
  @out.puts "Task #{id} crashed: #{e.class}: #{e.message}"
161
221
  ensure
162
- @running_lock.synchronize { @running.delete(id) }
222
+ upgrade_now = @running_lock.synchronize { @running.delete(id) && @pending_upgrade && @running.empty? }
223
+ upgrade if upgrade_now
163
224
  end
164
225
  end
165
226
  end
@@ -16,9 +16,10 @@ module Gitbroker
16
16
 
17
17
  attr_reader :settings_path
18
18
 
19
- def self.default(env: ENV, home: Dir.home)
19
+ # RubyGems' stable wrapper (ServiceInstaller.launcher): the hooks keep working after an upgrade's `gem cleanup`.
20
+ def self.default(env: ENV, home: Dir.home, exe: ServiceInstaller.launcher)
20
21
  new(settings_path: File.join(env["CLAUDE_CONFIG_DIR"] || File.join(home, ".claude"), "settings.json"),
21
- command_prefix: "#{Shellwords.escape(RbConfig.ruby)} #{Shellwords.escape(ServiceInstaller::EXE)}")
22
+ command_prefix: "#{Shellwords.escape(RbConfig.ruby)} #{Shellwords.escape(exe)}")
22
23
  end
23
24
 
24
25
  def initialize(settings_path:, command_prefix:, clock: -> { Time.now })
@@ -11,7 +11,7 @@ module Gitbroker
11
11
  PID_FILE = "daemon.pid"
12
12
  WAIT = 10
13
13
 
14
- def initialize(dir:, home: Dir.home, service: ServiceInstaller.new(home:), runner: Runner.new, exe: ServiceInstaller::EXE,
14
+ def initialize(dir:, home: Dir.home, service: ServiceInstaller.new(home:), runner: Runner.new, exe: ServiceInstaller.launcher,
15
15
  ruby: RbConfig.ruby, spawner: nil, sleeper: ->(seconds) { sleep(seconds) }, pid: Process.pid)
16
16
  @dir = dir
17
17
  @home = home
@@ -24,6 +24,8 @@ module Gitbroker
24
24
  @pid = pid
25
25
  end
26
26
 
27
+ attr_reader :service
28
+
27
29
  def pid_path = File.join(@dir, PID_FILE)
28
30
  def service_installed? = @service.installed?
29
31
  def log_path = File.join(@home, ".config", "gitbroker", "companion.log")
@@ -0,0 +1,65 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rbconfig"
4
+
5
+ module Gitbroker
6
+ # Upgrading and uninstalling the companion itself: fixed command sequences, nothing taken from the website's message.
7
+ class Maintenance
8
+ GEM_NAME = "gitbroker"
9
+
10
+ # `hooks`: the user's Claude Code hooks (HooksInstaller). `launcher`: what the service and the hooks run.
11
+ def initialize(runner: Runner.new, bindir: RbConfig::CONFIG["bindir"], hooks: HooksInstaller.default,
12
+ launcher: ServiceInstaller.launcher)
13
+ @runner = runner
14
+ @gem = File.join(bindir, "gem")
15
+ @hooks = hooks
16
+ @launcher = launcher
17
+ end
18
+
19
+ # Installs the newest release, then removes the old versions. The running process loads all its code first: cleanup
20
+ # deletes the files of the version it runs. Hooks written by an older version name its versioned exe, so they are
21
+ # rewritten to the launcher first. With no RubyGems wrapper the service runs this version's own exe, so nothing is
22
+ # cleaned up. Raises Error (with gem's output) when the install fails.
23
+ def upgrade!
24
+ @runner.run!(@gem, "install", GEM_NAME, "--no-document")
25
+ Gitbroker.eager_load!
26
+ refresh_hooks
27
+ @runner.success?(@gem, "cleanup", GEM_NAME) unless ServiceInstaller.pinned?(@launcher) # a failure is harmless
28
+ true
29
+ end
30
+
31
+ # True when the gem was uninstalled. The service goes last: stopping it ends the daemon when the daemon is the one
32
+ # uninstalling, so the gem's failure text (nil on success) is yielded before that step, for a report that gets out.
33
+ def uninstall!(service:, url_handler:)
34
+ Gitbroker.eager_load!
35
+ remove_hooks # they would run a missing file on every Claude Code event
36
+ error = uninstall_gem
37
+ url_handler.uninstall!
38
+ yield error if block_given?
39
+ service.uninstall!
40
+ error.nil?
41
+ end
42
+
43
+ private
44
+
45
+ # A broken settings file never blocks an upgrade or an uninstall.
46
+ def refresh_hooks
47
+ @hooks.install! if @hooks.installed?
48
+ rescue Error
49
+ nil
50
+ end
51
+
52
+ def remove_hooks
53
+ @hooks.uninstall!
54
+ rescue Error
55
+ nil
56
+ end
57
+
58
+ def uninstall_gem
59
+ @runner.run!(@gem, "uninstall", GEM_NAME, "--all", "--executables", "--ignore-dependencies")
60
+ nil
61
+ rescue Error => e
62
+ e.message
63
+ end
64
+ end
65
+ end
@@ -10,9 +10,9 @@ module Gitbroker
10
10
  # quoting never matters. When the agent exits, `gitbroker task-ended` tells GitBroker.
11
11
  class RunScript
12
12
  TASK_ID = /\A[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\z/
13
- EXE = File.join(Gitbroker::ROOT, "exe", "gitbroker")
14
13
 
15
- def initialize(state_dir:, ruby: RbConfig.ruby, exe: EXE)
14
+ # RubyGems' stable wrapper (ServiceInstaller.launcher): a terminal task may outlive an upgrade's `gem cleanup`.
15
+ def initialize(state_dir:, ruby: RbConfig.ruby, exe: ServiceInstaller.launcher)
16
16
  @state_dir = state_dir
17
17
  @ruby = ruby
18
18
  @exe = exe
@@ -9,7 +9,24 @@ module Gitbroker
9
9
  LABEL = "broker.git.companion"
10
10
  EXE = File.join(Gitbroker::ROOT, "exe", "gitbroker")
11
11
 
12
- def initialize(platform: Gitbroker.platform, home: Dir.home, ruby: RbConfig.ruby, exe: EXE, env: ENV, runner: Runner.new,
12
+ # What the service and the "Start companion" app run. RubyGems' own wrapper activates the newest installed gitbroker
13
+ # on every start, so a restart loads an upgraded gem and old versions can be removed. The gem's own exe is the
14
+ # fallback (and what a test passes), pinned to the version that ran `install-service`.
15
+ def self.launcher(bindir: launcher_bindirs)
16
+ Array(bindir).map { File.join(_1, "gitbroker") }.find { File.executable?(_1) } || EXE
17
+ end
18
+
19
+ # Where RubyGems puts the wrapper: the default bindir, the bindir of the install location this gem came from, and
20
+ # the per-user one (`gem install --user-install`).
21
+ def self.launcher_bindirs
22
+ spec = Gem.loaded_specs["gitbroker"]
23
+ [ Gem.bindir, (Gem.bindir(spec.base_dir) if spec), File.join(Gem.user_dir, "bin") ].compact.uniq
24
+ end
25
+
26
+ # True when the launcher is this version's own exe, which `gem cleanup` deletes once a newer version is installed.
27
+ def self.pinned?(launcher = self.launcher) = launcher == EXE
28
+
29
+ def initialize(platform: Gitbroker.platform, home: Dir.home, ruby: RbConfig.ruby, exe: ServiceInstaller.launcher, env: ENV, runner: Runner.new,
13
30
  uid: Process.uid)
14
31
  @platform = platform
15
32
  @home = home
@@ -60,6 +77,23 @@ module Gitbroker
60
77
  end
61
78
  end
62
79
 
80
+ # Stops and removes the background service. False when none was installed. The file goes first on macOS: booting
81
+ # out the job ends the daemon when it is the one uninstalling. systemd needs the unit file to disable it, so a
82
+ # daemon uninstalling itself may leave a disabled, stopped unit behind.
83
+ def uninstall!
84
+ return false unless installed?
85
+
86
+ if @platform == "macos"
87
+ File.delete(launchd_path)
88
+ @runner.success?("launchctl", "bootout", target)
89
+ else
90
+ @runner.success?("systemctl", "--user", "disable", "--now", "gitbroker.service")
91
+ File.delete(systemd_path)
92
+ @runner.success?("systemctl", "--user", "daemon-reload")
93
+ end
94
+ true
95
+ end
96
+
63
97
  def plist
64
98
  arguments = [ @ruby, @exe, "start" ].map { " <string>#{xml(_1)}</string>" }.join("\n")
65
99
  environment = { "PATH" => @env["PATH"].to_s, "SHELL" => @env["SHELL"] || "/bin/zsh", "LANG" => @env["LANG"] || "en_US.UTF-8" }
@@ -13,7 +13,7 @@ module Gitbroker
13
13
  APP_NAME = "GitBroker Companion"
14
14
  LSREGISTER = "/System/Library/Frameworks/CoreServices.framework/Frameworks/LaunchServices.framework/Support/lsregister"
15
15
 
16
- def initialize(platform: Gitbroker.platform, home: Dir.home, ruby: RbConfig.ruby, exe: ServiceInstaller::EXE, runner: Runner.new)
16
+ def initialize(platform: Gitbroker.platform, home: Dir.home, ruby: RbConfig.ruby, exe: ServiceInstaller.launcher, runner: Runner.new)
17
17
  @platform = platform
18
18
  @home = home
19
19
  @ruby = ruby
@@ -23,10 +23,23 @@ module Gitbroker
23
23
 
24
24
  def install! = @platform == "macos" ? install_app! : install_desktop_entry!
25
25
 
26
+ # Removes the app bundle (unregistered from LaunchServices first) or the .desktop entry. False when absent.
27
+ def uninstall!
28
+ path = @platform == "macos" ? app_path : desktop_entry_path
29
+ return false unless File.exist?(path)
30
+
31
+ @runner.success?(LSREGISTER, "-u", path) if @platform == "macos"
32
+ FileUtils.rm_rf(path)
33
+ true
34
+ end
35
+
26
36
  private
27
37
 
38
+ def app_path = File.join(@home, "Applications", "#{APP_NAME}.app")
39
+ def desktop_entry_path = File.join(@home, ".local", "share", "applications", "gitbroker.desktop")
40
+
28
41
  def install_app!
29
- app = File.join(@home, "Applications", "#{APP_NAME}.app")
42
+ app = app_path
30
43
  script = File.join(app, "Contents", "MacOS", "gitbroker-launch")
31
44
  FileUtils.mkdir_p(File.dirname(script))
32
45
  File.write(File.join(app, "Contents", "Info.plist"), info_plist)
@@ -69,7 +82,7 @@ module Gitbroker
69
82
  end
70
83
 
71
84
  def install_desktop_entry!
72
- path = File.join(@home, ".local", "share", "applications", "gitbroker.desktop")
85
+ path = desktop_entry_path
73
86
  FileUtils.mkdir_p(File.dirname(path))
74
87
  File.write(path, <<~ENTRY)
75
88
  [Desktop Entry]
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Gitbroker
4
- VERSION = "0.3.2"
4
+ VERSION = "0.4.0"
5
5
  end
data/lib/gitbroker.rb CHANGED
@@ -10,6 +10,9 @@ module Gitbroker
10
10
 
11
11
  def self.platform = RUBY_PLATFORM.include?("darwin") ? "macos" : "linux"
12
12
 
13
+ # Loads every autoloaded constant. Called before `gem cleanup` removes the files of the running version.
14
+ def self.eager_load! = constants.each { const_get(_1) }
15
+
13
16
  autoload :Agents, File.expand_path("gitbroker/agents", __dir__)
14
17
  autoload :Api, File.expand_path("gitbroker/api", __dir__)
15
18
  autoload :CableClient, File.expand_path("gitbroker/cable_client", __dir__)
@@ -23,6 +26,7 @@ module Gitbroker
23
26
  autoload :HooksInstaller, File.expand_path("gitbroker/hooks_installer", __dir__)
24
27
  autoload :Lifecycle, File.expand_path("gitbroker/lifecycle", __dir__)
25
28
  autoload :Launcher, File.expand_path("gitbroker/launcher", __dir__)
29
+ autoload :Maintenance, File.expand_path("gitbroker/maintenance", __dir__)
26
30
  autoload :LocalRelay, File.expand_path("gitbroker/local_relay", __dir__)
27
31
  autoload :Login, File.expand_path("gitbroker/login", __dir__)
28
32
  autoload :Neutralizer, File.expand_path("gitbroker/neutralizer", __dir__)
@@ -1,5 +1,5 @@
1
1
  {
2
2
  "name": "gitbroker",
3
- "version": "0.3.2",
3
+ "version": "0.4.0",
4
4
  "description": "Skills for working on GitBroker agent tasks: the gitbroker MCP tools, write rules and report_task."
5
5
  }
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: gitbroker
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.2
4
+ version: 0.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Yuri Sidorov (@newstler)
@@ -52,6 +52,7 @@ files:
52
52
  - lib/gitbroker/lifecycle.rb
53
53
  - lib/gitbroker/local_relay.rb
54
54
  - lib/gitbroker/login.rb
55
+ - lib/gitbroker/maintenance.rb
55
56
  - lib/gitbroker/neutralizer.rb
56
57
  - lib/gitbroker/process_scanner.rb
57
58
  - lib/gitbroker/repo_scanner.rb