#!/usr/bin/env ruby
# encoding: UTF-8
# frozen_string_literal: true

# PreToolUse gate for Bash (intent 27a): close the Bash-edit bypass. When auto
# mode is armed and the active intent has not reached How (plan.md + checklist.md),
# block Bash commands that WRITE project code outside the store.
#
# Reads the Claude Code PreToolUse payload as JSON on STDIN:
#   { "tool_input": { "command": "..." }, "cwd": "..." }
# Empty / unparseable / no command => exit 0 (allow). Conservatism is the prime
# directive: when a command's write intent is ambiguous, ALLOW.
#
# Exit 0 = allow. Exit 2 = block (reason on stderr, shown to the agent).

require "json"
require_relative "lib/bridge"

raw = $stdin.read rescue nil
exit 0 if raw.nil? || raw.strip.empty?

payload = JSON.parse(raw) rescue nil
exit 0 unless payload.is_a?(Hash)

command = payload.dig("tool_input", "command") || payload.dig("tool_params", "command")
exit 0 if command.nil? || command.to_s.strip.empty?

cwd = payload["cwd"]
cwd = Dir.pwd if cwd.nil? || cwd.to_s.empty?

session = payload["session_id"]
session = ENV["CLAUDE_CODE_SESSION_ID"] if session.nil? || session.to_s.empty?

# Auditable escape (intent 108, D7): a trailing `# plastic-ok` allows the
# command and logs it, so sanctioned writes are visible, not silent.
if Bridge.bash_escape?(command)
  begin
    require "fileutils"
    log = File.join(Dir.home, ".plastic", ".cache", "gate-escapes.log")
    FileUtils.mkdir_p(File.dirname(log))
    File.open(log, "a") do |io|
      io.puts("#{Time.now.utc.iso8601}\t#{session}\t#{command.gsub(/\s+/, ' ').strip}")
    end
  rescue StandardError
    # the escape still applies; logging is best-effort
  end
  exit 0
end

# --- Load bridge (shared resolution; stdin session_id -> CLAUDE_CODE_SESSION_ID -> /tmp scan) ---
# A nil bridge does NOT short-circuit (intent 108): the lock gate decides from
# the durable delivery.lock file, so it must run even without a bridge cache.
bridge_data = Bridge.discover_bridge(session: session, cwd: cwd)

reason = Bridge.bash_gate_decision(bridge_data, command, cwd: cwd, session: session)
exit 0 unless reason

# Block log (intent 229): one six-field TSV line, hand-rolled here exactly as
# the escape write above is, so this script keeps requiring only lib/bridge and
# pays no extra load on every Bash tool call. Best-effort: the block applies
# regardless.
begin
  require "fileutils"
  require "time"
  blocklog = File.join(Dir.home, ".plastic", ".cache", "gate-blocks.log")
  FileUtils.mkdir_p(File.dirname(blocklog))
  intent_id = bridge_data.is_a?(Hash) ? bridge_data.dig("intent", "id").to_s : ""
  File.open(blocklog, "a") do |io|
    io.puts([Time.now.utc.iso8601, "bash-gate", session.to_s, intent_id,
             command.to_s.gsub(/\s+/, " ").strip,
             reason.to_s.gsub(/\s+/, " ").strip].join("\t"))
  end
rescue StandardError
  # the block still applies; logging is best-effort
end

$stderr.puts "PLASTIC GATE — #{reason}"
exit 2
