Class: OKF::Pro::Event

Inherits:
Object
  • Object
show all
Defined in:
lib/okf/pro/event.rb

Overview

The hook event: the only place this checker parses input it did not write.

An event it cannot read is enforcement that did not run, so the failure is recorded rather than swallowed. The first cut rescued the parse error into an empty hash, every guard read an empty path, and the check passed — the same shape as the jq hole this checker exists to close, rebuilt in Ruby. Whoever calls it decides what to do with the failure; nobody gets to not know about it.

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(raw) ⇒ Event

Returns a new instance of Event.



20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
# File 'lib/okf/pro/event.rb', line 20

def initialize(raw)
  @data = {}
  @parse_error = nil

  # The same defense Pro.read_text gives files, applied to the one
  # input that arrives as a stream: forced to UTF-8 and scrubbed. Stdin
  # under a bare locale (common in CI and git hooks) arrives tagged
  # US-ASCII, and a clean em dash in an edit's new_string then raised
  # out of the very first string operation — above the dispatch rescue,
  # so ruby exited 1, which the hook protocol reads as NON-BLOCKING.
  # The guard's own input was the bypass.
  text = raw.to_s.dup.force_encoding(Encoding::UTF_8).scrub
  if text.strip.empty?
    @parse_error = "the hook received no event on stdin"
    return
  end

  begin
    parsed = JSON.parse(text)
    if parsed.is_a?(Hash)
      @data = parsed
    else
      @parse_error = "the event was #{parsed.class}, not a JSON object"
    end
  rescue JSON::ParserError => e
    @parse_error = "the event was not parseable JSON (#{e.message.split("\n").first})"
  end
end

Instance Attribute Details

#parse_errorObject (readonly)

Returns the value of attribute parse_error.



14
15
16
# File 'lib/okf/pro/event.rb', line 14

def parse_error
  @parse_error
end

Class Method Details

.from_stdin(io = $stdin) ⇒ Object



16
17
18
# File 'lib/okf/pro/event.rb', line 16

def self.from_stdin(io = $stdin)
  new(io.read)
end

Instance Method Details

#added_textObject

Both spellings are read on purpose. Claude Code's Edit/MultiEdit send new_string; the shell shims this replaces only ever looked for new_str, so the verified: guard passed every real edit it was built to stop and only ever refused the synthetic JSON in its own drill. A gate tested exclusively by the shape it was written against is not tested.



85
86
87
88
89
90
91
92
# File 'lib/okf/pro/event.rb', line 85

def added_text
  parts = [ tool_input["new_string"], tool_input["new_str"], tool_input["content"] ]
  edits = tool_input["edits"]
  if edits.is_a?(Array)
    parts.concat(edits.map { |e| e.is_a?(Hash) ? [ e["new_string"], e["new_str"] ] : nil })
  end
  parts.flatten.compact.join("\n")
end

#commandObject

Bash's payload. Named for what it is rather than reached for through tool_input at three call sites.



71
72
73
# File 'lib/okf/pro/event.rb', line 71

def command
  tool_input["command"].to_s
end

#cwdObject



75
76
77
78
# File 'lib/okf/pro/event.rb', line 75

def cwd
  raw = @data["cwd"].to_s
  raw.empty? ? Dir.pwd : raw
end

#file_pathObject



65
66
67
# File 'lib/okf/pro/event.rb', line 65

def file_path
  tool_input["file_path"].to_s
end

#parse_error?Boolean

Returns:

  • (Boolean)


49
50
51
# File 'lib/okf/pro/event.rb', line 49

def parse_error?
  !@parse_error.nil?
end

#stop_hook_active?Boolean

Returns:

  • (Boolean)


57
58
59
# File 'lib/okf/pro/event.rb', line 57

def stop_hook_active?
  @data["stop_hook_active"] == true
end

#tool_inputObject



61
62
63
# File 'lib/okf/pro/event.rb', line 61

def tool_input
  @data["tool_input"].is_a?(Hash) ? @data["tool_input"] : {}
end

#tool_nameObject



53
54
55
# File 'lib/okf/pro/event.rb', line 53

def tool_name
  @data["tool_name"].to_s
end