Module: Deja::RequirementsCache

Defined in:
lib/deja/requirements_cache.rb

Overview

Cache layer behind the ‘meet_requirements` matcher (defined in deja/rspec.rb). Stores confirmed requirement/value pairs keyed by a hash of the requirements text. One file per test: `<cache_root>/meets_requirements/<suite>/<id>.yaml`.

test_suite: <derived from spec file path>
test_name:  <full RSpec description>
summary:    <human-readable counts: assertions / total confirmed values>
assertions:
  - hash:         <12-char fingerprint of the requirements text — used for lookup>
    requirements: <the requirements text — auditable from the file alone>
    confirmed_values:
      - <values previously approved by the LLM judge>

Pruning mirrors Deja::Cache: at the end of a passing example (when ALLOW_LLM_CALL=1), assertions whose hash wasn’t touched are dropped — so changing the requirements text blows away the now-stale confirmed values.

Class Method Summary collapse

Class Method Details

.append!(requirements, value) ⇒ Object



35
36
37
38
39
40
41
# File 'lib/deja/requirements_cache.rb', line 35

def append!(requirements, value)
  record_touched(requirements)
  data = load_or_init
  upsert_assertion(data, requirements, value)
  data["summary"] = build_summary(data["assertions"])
  cache_file.write(YAML.dump(Deja::Cache.stringify(data)))
end

.build_summary(assertions) ⇒ Object



103
104
105
106
107
# File 'lib/deja/requirements_cache.rb', line 103

def build_summary(assertions)
  total_values = assertions.sum {|a| a["confirmed_values"].size }
  "#{assertions.size} #{assertions.size == 1 ? 'assertion' : 'assertions'}, " \
    "#{total_values} confirmed #{total_values == 1 ? 'value' : 'values'} total."
end

.cache_dirObject



25
26
27
# File 'lib/deja/requirements_cache.rb', line 25

def cache_dir
  Deja.configuration.cache_root.join("meets_requirements")
end

.cache_fileObject



60
61
62
# File 'lib/deja/requirements_cache.rb', line 60

def cache_file
  cache_dir.join(Deja::Cache.test_suite, "#{Deja::Cache.current_id!}.yaml")
end

.load_assertion(requirements) ⇒ Object



68
69
70
71
72
73
# File 'lib/deja/requirements_cache.rb', line 68

def load_assertion(requirements)
  return nil unless cache_file.exist?

  hash = requirements_hash(requirements)
  YAML.safe_load(cache_file.read).fetch("assertions").find {|a| a["hash"] == hash }
end

.load_or_initObject



75
76
77
78
79
80
81
82
83
84
85
86
87
# File 'lib/deja/requirements_cache.rb', line 75

def load_or_init
  if cache_file.exist?
    YAML.safe_load(cache_file.read)
  else
    FileUtils.mkdir_p(cache_file.dirname)
    {
      "test_suite" => Deja::Cache.test_suite,
      "test_name" => Deja::Cache.current_test_name,
      "summary" => "",
      "assertions" => [],
    }
  end
end

.prune_untouched_in_current_example!Object



43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
# File 'lib/deja/requirements_cache.rb', line 43

def prune_untouched_in_current_example!
  return unless cache_file.exist?

  data = YAML.safe_load(cache_file.read)
  touched = touched_hashes
  fresh_assertions = data["assertions"].select {|a| touched.include?(a["hash"]) }
  return if fresh_assertions.size == data["assertions"].size

  if fresh_assertions.empty?
    cache_file.delete
  else
    data["assertions"] = fresh_assertions
    data["summary"] = build_summary(fresh_assertions)
    cache_file.write(YAML.dump(Deja::Cache.stringify(data)))
  end
end

.record_touched(requirements) ⇒ Object



109
110
111
# File 'lib/deja/requirements_cache.rb', line 109

def record_touched(requirements)
  touched_hashes << requirements_hash(requirements)
end

.requirements_hash(requirements) ⇒ Object



64
65
66
# File 'lib/deja/requirements_cache.rb', line 64

def requirements_hash(requirements)
  Digest::SHA256.hexdigest(requirements.strip)[0, 12]
end

.touched_hashesObject



113
114
115
# File 'lib/deja/requirements_cache.rb', line 113

def touched_hashes
  Deja::Cache.current_example!.[:touched_meet_requirements_hashes] ||= Set.new
end

.upsert_assertion(data, requirements, value) ⇒ Object



89
90
91
92
93
94
95
96
97
98
99
100
101
# File 'lib/deja/requirements_cache.rb', line 89

def upsert_assertion(data, requirements, value)
  hash = requirements_hash(requirements)
  existing = data["assertions"].find {|a| a["hash"] == hash }
  if existing
    existing["confirmed_values"] = existing.fetch("confirmed_values") + [ value ]
  else
    data["assertions"] << {
      "hash" => hash,
      "requirements" => requirements.strip,
      "confirmed_values" => [ value ],
    }
  end
end

.values_for(requirements) ⇒ Object



29
30
31
32
33
# File 'lib/deja/requirements_cache.rb', line 29

def values_for(requirements)
  record_touched(requirements)
  assertion = load_assertion(requirements)
  assertion ? assertion.fetch("confirmed_values") : []
end