Class: Aidp::Execute::GuardPolicy

Inherits:
Object
  • Object
show all
Defined in:
lib/aidp/execute/guard_policy.rb

Overview

Enforces safety constraints during work loops Responsibilities:

  • Check file patterns (include/exclude globs)

  • Enforce max lines changed per commit

  • Track files requiring confirmation

  • Validate changes against policy before execution

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(project_dir, config) ⇒ GuardPolicy

Returns a new instance of GuardPolicy.



16
17
18
19
20
# File 'lib/aidp/execute/guard_policy.rb', line 16

def initialize(project_dir, config)
  @project_dir = project_dir
  @config = config
  @confirmed_files = Set.new
end

Instance Attribute Details

#configObject (readonly)

Returns the value of attribute config.



14
15
16
# File 'lib/aidp/execute/guard_policy.rb', line 14

def config
  @config
end

#project_dirObject (readonly)

Returns the value of attribute project_dir.



14
15
16
# File 'lib/aidp/execute/guard_policy.rb', line 14

def project_dir
  @project_dir
end

Instance Method Details

#bypass?Boolean

Bypass guards (for specific use cases like testing)

Returns:

  • (Boolean)


139
140
141
# File 'lib/aidp/execute/guard_policy.rb', line 139

def bypass?
  ENV["AIDP_BYPASS_GUARDS"] == "1" || config.dig(:bypass) == true
end

#can_modify_file?(file_path) ⇒ Boolean

Validate if a file can be modified Returns { allowed: true/false, reason: string }

Returns:

  • (Boolean)


29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
# File 'lib/aidp/execute/guard_policy.rb', line 29

def can_modify_file?(file_path)
  return {allowed: true} unless enabled?

  normalized_path = normalize_path(file_path)

  # Check exclude patterns first
  if excluded?(normalized_path)
    return {
      allowed: false,
      reason: "File matches exclude pattern in guards configuration"
    }
  end

  # Check include patterns (if specified, file must match at least one)
  if has_include_patterns? && !included?(normalized_path)
    return {
      allowed: false,
      reason: "File does not match any include pattern in guards configuration"
    }
  end

  # Check if file requires confirmation
  if requires_confirmation?(normalized_path) && !confirmed?(normalized_path)
    return {
      allowed: false,
      reason: "File requires one-time confirmation before modification",
      requires_confirmation: true,
      file_path: normalized_path
    }
  end

  {allowed: true}
end

#confirm_file(file_path) ⇒ Object

Confirm a file for modification (one-time confirmation)



64
65
66
67
# File 'lib/aidp/execute/guard_policy.rb', line 64

def confirm_file(file_path)
  normalized_path = normalize_path(file_path)
  @confirmed_files.add(normalized_path)
end

#confirmed?(file_path) ⇒ Boolean

Check if file has been confirmed

Returns:

  • (Boolean)


119
120
121
122
# File 'lib/aidp/execute/guard_policy.rb', line 119

def confirmed?(file_path)
  normalized_path = normalize_path(file_path)
  @confirmed_files.include?(normalized_path)
end

#disable!Object

Disable guards



149
150
151
# File 'lib/aidp/execute/guard_policy.rb', line 149

def disable!
  config[:enabled] = false
end

#enable!Object

Enable guards (override bypass)



144
145
146
# File 'lib/aidp/execute/guard_policy.rb', line 144

def enable!
  config[:enabled] = true
end

#enabled?Boolean

Check if guards are enabled

Returns:

  • (Boolean)


23
24
25
# File 'lib/aidp/execute/guard_policy.rb', line 23

def enabled?
  config.dig(:enabled) == true
end

#files_requiring_confirmationObject

Get list of files requiring confirmation



101
102
103
104
105
106
# File 'lib/aidp/execute/guard_policy.rb', line 101

def files_requiring_confirmation
  return [] unless enabled?

  patterns = config.dig(:confirm_files) || []
  patterns.map { |pattern| expand_glob_pattern(pattern) }.flatten.compact
end

#requires_confirmation?(file_path) ⇒ Boolean

Check if file requires confirmation

Returns:

  • (Boolean)


109
110
111
112
113
114
115
116
# File 'lib/aidp/execute/guard_policy.rb', line 109

def requires_confirmation?(file_path)
  return false unless enabled?

  patterns = config.dig(:confirm_files) || []
  normalized_path = normalize_path(file_path)

  patterns.any? { |pattern| matches_pattern?(normalized_path, pattern) }
end

#summaryObject

Get summary of guard policy configuration



125
126
127
128
129
130
131
132
133
134
135
136
# File 'lib/aidp/execute/guard_policy.rb', line 125

def summary
  return {enabled: false} unless enabled?

  {
    enabled: true,
    include_patterns: config.dig(:include_files) || [],
    exclude_patterns: config.dig(:exclude_files) || [],
    confirm_patterns: config.dig(:confirm_files) || [],
    max_lines_per_commit: config.dig(:max_lines_per_commit),
    confirmed_files: @confirmed_files.to_a
  }
end

#validate_changes(diff_stats) ⇒ Object

Check if total lines changed exceeds limit diff_stats: { file_path => { additions: n, deletions: n } }



71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
# File 'lib/aidp/execute/guard_policy.rb', line 71

def validate_changes(diff_stats)
  return {valid: true} unless enabled?

  errors = []

  # Check max lines per commit
  if (max_lines = config.dig(:max_lines_per_commit))
    total_changes = calculate_total_changes(diff_stats)

    if total_changes > max_lines
      errors << "Total lines changed (#{total_changes}) exceeds limit (#{max_lines})"
    end
  end

  # Check each file against policy
  diff_stats.each do |file_path, stats|
    result = can_modify_file?(file_path)
    unless result[:allowed]
      errors << "#{file_path}: #{result[:reason]}"
    end
  end

  if errors.any?
    {valid: false, errors: errors}
  else
    {valid: true}
  end
end