ui_guardrails 1.7.0 → 1.7.1

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: bcb56bb52cb2fe3e2d0bd155eaf5b8563177588b53d34af57f4d125653cd6f47
4
- data.tar.gz: 5803d7480e71fe72cc6964bd424333fb888709d73104f215430a520d09cc4b49
3
+ metadata.gz: 32e83a5c20961feae0830c519f4bf0b3363bcd4774d4644bab49053ce02c8cd5
4
+ data.tar.gz: 8fbaaa46298c2ea2805b2b291354d36cf521fd6f11f948290d62e2796bbafb24
5
5
  SHA512:
6
- metadata.gz: 18bb8c26caf6971a74cc2a0d585acbcf57fbd27b1f703bc0fcedfa4d038724d0362623addcf06723e152f2f4cd4234829a88ba720dfcf0042a79c027067c27bb
7
- data.tar.gz: b559b93cc20faa99f6af5b7a806117ea5925d6dacee2df064cac8e2fe4499d7763277ce0c6a47944f4c77dd7373d091fd01bba65e21a09b3e1dcd56193628d71
6
+ metadata.gz: 88fe2925e7787c6b0a63861d142f9d53e5e832395c7b22e7515400954f9a16f57dab73c2fa318b3df516ee5fa12af3e104604bd63125a08eb403eb3ed93f083d
7
+ data.tar.gz: 9ed0ec91a1310da006bf525c05163cec82e730c17838d22ba6e5c2ed2ab7607241541769dda5c60f9a7e2681b587bc5747d2116ddbef156e952bd6f079d8cbae
data/README.md CHANGED
@@ -318,7 +318,7 @@ It reports only what the agent introduced. A template is compared with its versi
318
318
  | Cursor | `.cursor/hooks.json` | `stop` and `sessionStart` only: Cursor's after-edit output doesn't reliably reach the model, so the stop check covers every changed template. Cursor also runs `.claude/settings.json` hooks; the hook recognises Cursor's input and doesn't run twice. |
319
319
  | Gemini CLI | `.gemini/settings.json` | `AfterTool` (`write_file`, `replace`), `AfterAgent`, `SessionStart` |
320
320
 
321
- The hook never breaks the agent's turn: if anything goes wrong it prints `{}`, explains on stderr, and exits 0. Other gems add to the same events with `Guardrails::Hooks.register(name, contributor)`, shipped at `lib/guardrails/hook_contributors/<name>.rb` so the hook finds them without booting the app.
321
+ The hook never breaks the agent's turn: if anything goes wrong it prints `{}`, explains on stderr, and exits 0. The installed command ends in `|| echo '{}'`, so the same holds when the hook can't start at all. That happens on a branch whose Gemfile doesn't have the gem, since the agent's config usually isn't tracked by git and outlives the branch. Running the install again replaces an earlier install's hooks instead of adding new ones beside them. Other gems add to the same events with `Guardrails::Hooks.register(name, contributor)`, shipped at `lib/guardrails/hook_contributors/<name>.rb` so the hook finds them without booting the app.
322
322
 
323
323
  ---
324
324
 
@@ -18,7 +18,8 @@ module Guardrails
18
18
 
19
19
  # `entries` maps an event name to the list entries to add under
20
20
  # `hooks.<event>` (each a Hash in the agent's own shape). An entry
21
- # counts as present when one with the same command is there.
21
+ # counts as present when one with the same command is there; an
22
+ # older form of the same Guardrails hook is replaced.
22
23
  # `extra` merges top-level keys that must be set (Cursor's
23
24
  # `version`). Returns true when the file changed.
24
25
  def add(entries, extra: {})
@@ -32,7 +33,14 @@ module Guardrails
32
33
  existing = (hooks[event] ||= [])
33
34
  raise Error, "#{@path}: `hooks.#{event}` isn't a list" unless existing.is_a?(Array)
34
35
 
35
- list.each { |entry| existing << entry unless commands(existing).include?(command(entry)) }
36
+ list.each do |entry|
37
+ wanted = command(entry)
38
+ next if commands(existing).include?(wanted)
39
+
40
+ identity = wanted.to_s[OURS]
41
+ remove(existing, identity) if identity
42
+ existing << entry
43
+ end
36
44
  end
37
45
  return false if JSON.generate(config) == before
38
46
 
@@ -43,6 +51,24 @@ module Guardrails
43
51
 
44
52
  private
45
53
 
54
+ # An earlier install of the same hook, whatever its exact command:
55
+ # reinstalling replaces it rather than adding a second.
56
+ OURS = /guardrails-hook \w+ \w+/
57
+
58
+ # Takes out the hook with this identity and any group it leaves
59
+ # empty; the user's own hooks, in the same group or not, stay.
60
+ def remove(list, identity)
61
+ list.reject! do |entry|
62
+ next false unless entry.is_a?(Hash)
63
+ next true if entry["command"].to_s.include?(identity)
64
+ next false unless entry["hooks"].is_a?(Array)
65
+
66
+ before = entry["hooks"].size
67
+ entry["hooks"].reject! { |hook| hook.is_a?(Hash) && hook["command"].to_s.include?(identity) }
68
+ entry["hooks"].empty? && before.positive?
69
+ end
70
+ end
71
+
46
72
  def read
47
73
  return {} unless @path.file?
48
74
 
@@ -20,7 +20,10 @@ module Guardrails
20
20
  def call
21
21
  @adapters.map do |adapter|
22
22
  path = @root.join(adapter.config_path)
23
- command = ->(event) { "#{adapter.command_prefix}#{executable} #{adapter.name} #{event}" }
23
+ # Falls back to `{}`, the answer every agent reads as "nothing to
24
+ # say", when the hook can't start at all: a branch without the
25
+ # gem in its Gemfile, or a missing project directory.
26
+ command = ->(event) { "(#{adapter.command_prefix}#{executable} #{adapter.name} #{event}) || echo '{}'" }
24
27
  changed = ConfigFile.new(path).add(adapter.config_entries(command), extra: adapter.config_extra)
25
28
  Change.new(agent: adapter.name, path: path.relative_path_from(@root).to_s, changed: changed)
26
29
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Guardrails
4
- VERSION = "1.7.0"
4
+ VERSION = "1.7.1"
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ui_guardrails
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.7.0
4
+ version: 1.7.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - John Athayde