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 +4 -4
- data/Gemfile.lock +2 -2
- data/bin/brainiac +59 -23
- data/lib/brainiac/handlers/shared/belt.rb +42 -0
- data/lib/brainiac/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 484a1d79de556a1d190c22cbafe8db117c24e54f7a051c2a3fb4884b9979f435
|
|
4
|
+
data.tar.gz: c170d7271b21aa627f2e591f261628c3abfe731de1cef5c65581e23f4118f46f
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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.
|
|
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.
|
|
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
|
-
|
|
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
|
|
data/lib/brainiac/version.rb
CHANGED