brainiac 0.0.30 → 0.0.32

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: 4d6606e86024e1763e4d3d7de19d4ef3acaa178114029a84ae6f4ee40ecca291
4
- data.tar.gz: b02c691f2076dbc31afb9a180d7fc0ef0de056bb298f7771902b74102e4cd212
3
+ metadata.gz: 484a1d79de556a1d190c22cbafe8db117c24e54f7a051c2a3fb4884b9979f435
4
+ data.tar.gz: c170d7271b21aa627f2e591f261628c3abfe731de1cef5c65581e23f4118f46f
5
5
  SHA512:
6
- metadata.gz: 16d1d6b39da2a6be050fcaa303628720e8dd909b73daf681cc3a52776c8d6bdd6ac54cced6fa18b3e92eda35f3e18eb4ecf9660b527614717c279ad94191ae3d
7
- data.tar.gz: d5118e7d1dc22b77b6da10ef95f2b9e0f821fd57ebd189726ac3d391a109eab6e804f86cfc0722f4e44f6baf5fadc6eb13e31f72944c9730d191166368b4eb8f
6
+ metadata.gz: 78f90db425eaa92c1b3f4718845ea7449a6e267f86bad4873147d01625bf3759180f8f121521ed0ab70fc9c72563e6c4ca8f367dcf6364e91774623588cff911
7
+ data.tar.gz: 9ef305db5eb93589935780ef5a1bcd4063e018d86522a825fb500d3ff0e023ca1e145154196a92c768c934d2dcf9c4a77c4ef67ce729d0e93a537e8c91bf1d1a
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- brainiac (0.0.30)
4
+ brainiac (0.0.32)
5
5
  puma (~> 7.2)
6
6
  rackup (~> 2.3)
7
7
  sinatra (~> 4.1)
@@ -84,7 +84,7 @@ DEPENDENCIES
84
84
  CHECKSUMS
85
85
  ast (2.4.3) sha256=954615157c1d6a382bc27d690d973195e79db7f55e9765ac7c481c60bdb4d383
86
86
  base64 (0.3.0) sha256=27337aeabad6ffae05c265c450490628ef3ebd4b67be58257393227588f5a97b
87
- brainiac (0.0.30)
87
+ brainiac (0.0.32)
88
88
  json (2.19.9) sha256=9b9025b7cdddafa38d316eca0b2358488e42d417045c1b90d216a9fefe46b79a
89
89
  language_server-protocol (3.17.0.5) sha256=fd1e39a51a28bf3eec959379985a72e296e9f9acfce46f6a79d31ca8760803cc
90
90
  lint_roller (1.1.0) sha256=2c0c845b632a7d172cb849cc90c1bce937a28c5c8ccccb50dfd46a485003cc87
data/bin/brainiac CHANGED
@@ -50,6 +50,64 @@ def save_config(config)
50
50
  File.write(CONFIG_FILE, JSON.pretty_generate(config))
51
51
  end
52
52
 
53
+ # Describe the git branch state of a local plugin checkout for the `plugins` list.
54
+ # Returns a short human-readable string like "main", "my-branch ↑2 ↓1 *"
55
+ # where ↑ = ahead of upstream, ↓ = behind upstream, * = uncommitted changes.
56
+ # Returns nil if the path isn't a git repo (so callers can skip the annotation).
57
+ def plugin_branch_status(path)
58
+ return nil unless Dir.exist?(path)
59
+
60
+ branch, _, br_status = Open3.capture3("git", "rev-parse", "--abbrev-ref", "HEAD", chdir: path)
61
+ return nil unless br_status.success?
62
+
63
+ branch = branch.strip
64
+ branch = "(detached)" if branch.empty? || branch == "HEAD"
65
+
66
+ parts = [branch]
67
+
68
+ # Ahead/behind counts vs the upstream tracking branch (if one is set).
69
+ counts, _, ab_status = Open3.capture3(
70
+ "git", "rev-list", "--left-right", "--count", "@{upstream}...HEAD", chdir: path
71
+ )
72
+ if ab_status.success?
73
+ behind, ahead = counts.strip.split(/\s+/).map(&:to_i)
74
+ parts << "↑#{ahead}" if ahead&.positive?
75
+ parts << "↓#{behind}" if behind&.positive?
76
+ end
77
+
78
+ # Uncommitted changes (staged, unstaged, or untracked).
79
+ dirty, _, st_status = Open3.capture3("git", "status", "--porcelain", chdir: path)
80
+ parts << "*" if st_status.success? && !dirty.strip.empty?
81
+
82
+ parts.join(" ")
83
+ end
84
+
85
+ # Format a single line for the `brainiac plugins` list. Handles both local
86
+ # (path-based) plugins and gem-based plugins, including branch/sync status
87
+ # for local checkouts.
88
+ def plugin_list_line(entry)
89
+ name = entry["name"]
90
+ gem_name = entry["gem"] || "brainiac-#{name}"
91
+ local_path = entry["path"]
92
+ installed_at = entry["installed_at"] ? " (#{entry["installed_at"][0..9]})" : ""
93
+
94
+ return " #{gem_plugin_status(name, gem_name)}#{installed_at}" unless local_path
95
+
96
+ return " ✗ #{name} → #{local_path} (path missing)#{installed_at}" unless Dir.exist?(local_path)
97
+
98
+ branch = plugin_branch_status(local_path)
99
+ branch_note = branch ? " [#{branch}]" : ""
100
+ " ✓ #{name} → #{local_path}#{branch_note}#{installed_at}"
101
+ end
102
+
103
+ # Check whether a gem-based plugin is installed/loadable and return a status string.
104
+ def gem_plugin_status(name, gem_name)
105
+ spec = Gem::Specification.find_by_name(gem_name)
106
+ "✓ #{name} (#{gem_name} #{spec.version})"
107
+ rescue Gem::MissingSpecError
108
+ "✗ #{name} (#{gem_name} — gem not found)"
109
+ end
110
+
53
111
  # Resolve the default agent name: env var → brainiac.json → nil.
54
112
  def default_agent_name
55
113
  return ENV["AI_AGENT_NAME"] if ENV["AI_AGENT_NAME"] && !ENV["AI_AGENT_NAME"].empty?
@@ -3035,29 +3093,7 @@ when "plugins"
3035
3093
  puts "Installed plugins:"
3036
3094
  plugins.each do |p|
3037
3095
  entry = p.is_a?(Hash) ? p : { "name" => p.to_s }
3038
- name = entry["name"]
3039
- gem_name = entry["gem"] || "brainiac-#{name}"
3040
- local_path = entry["path"]
3041
- installed_at = entry["installed_at"] ? " (#{entry["installed_at"][0..9]})" : ""
3042
-
3043
- if local_path
3044
- # Local plugin — check path exists
3045
- if Dir.exist?(local_path)
3046
- puts " ✓ #{name} → #{local_path}#{installed_at}"
3047
- else
3048
- puts " ✗ #{name} → #{local_path} (path missing)#{installed_at}"
3049
- end
3050
- else
3051
- # Gem plugin — check gem is loadable
3052
- version = entry["version"] || "latest"
3053
- loadable = begin
3054
- spec = Gem::Specification.find_by_name(gem_name)
3055
- "✓ #{name} (#{gem_name} #{spec.version})"
3056
- rescue Gem::MissingSpecError
3057
- "✗ #{name} (#{gem_name} — gem not found)"
3058
- end
3059
- puts " #{loadable}#{installed_at}"
3060
- end
3096
+ puts plugin_list_line(entry)
3061
3097
  end
3062
3098
  end
3063
3099
  else
@@ -177,6 +177,48 @@ module BeltConfig
177
177
  def ephemeral_env_for_epic(epic_number)
178
178
  "epic-#{epic_number}"
179
179
  end
180
+
181
+ # Find an active epic ephemeral env by its tracked epic branch.
182
+ #
183
+ # Epic envs are keyed by name (e.g. "epic-fp-ux") rather than a card number,
184
+ # and carry `epic_branch` / `epic_pr` fields. This scans the tracking file
185
+ # for an active entry whose `epic_branch` matches.
186
+ #
187
+ # @param branch [String] Head branch name (e.g. "epic/feature-parity-...")
188
+ # @return [Array(String, Hash), nil] [env_name, entry] or nil
189
+ def epic_env_for_branch(branch)
190
+ return nil if branch.nil? || branch.empty?
191
+
192
+ find_active_epic_env { |entry| entry["epic_branch"] == branch }
193
+ end
194
+
195
+ # Find an active epic ephemeral env by its tracked epic PR URL.
196
+ #
197
+ # @param pr_url [String] Pull request html_url
198
+ # @return [Array(String, Hash), nil] [env_name, entry] or nil
199
+ def epic_env_for_pr(pr_url)
200
+ return nil if pr_url.nil? || pr_url.empty?
201
+
202
+ find_active_epic_env { |entry| entry["epic_pr"] == pr_url }
203
+ end
204
+
205
+ private
206
+
207
+ # Scan ephemeral_envs.json for the first active epic entry matching the block.
208
+ # @yield [entry] each active entry hash
209
+ # @return [Array(String, Hash), nil] [env_name, entry] or nil
210
+ def find_active_epic_env
211
+ state_file = File.join(BRAINIAC_DIR, "ephemeral_envs.json")
212
+ return nil unless File.exist?(state_file)
213
+
214
+ state = JSON.parse(File.read(state_file))
215
+ # Returns [name, entry] for the first match, or nil.
216
+ state.find do |_name, entry|
217
+ entry.is_a?(Hash) && entry["status"] == "active" && entry["epic_branch"] && yield(entry)
218
+ end
219
+ rescue StandardError
220
+ nil
221
+ end
180
222
  end
181
223
  end
182
224
 
@@ -2,5 +2,5 @@
2
2
 
3
3
  module Brainiac
4
4
  # @return [String] the current gem version
5
- VERSION = "0.0.30"
5
+ VERSION = "0.0.32"
6
6
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: brainiac
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.30
4
+ version: 0.0.32
5
5
  platform: ruby
6
6
  authors:
7
7
  - Andy Davis