Class: Scryer::AiFixSuggester

Inherits:
Object
  • Object
show all
Defined in:
lib/scryer/ai_fix_suggester.rb

Overview

Optional, opt-in enrichment of a finding's suggested_fix using an external LLM. Every rule already ships a generic, human-reviewable suggested fix (see Rule#finding) — this replaces that generic text with one written against the finding's actual code snippet, when the host app has configured an LLM client.

Scryer stays entirely provider-agnostic here: client is any object (or bare Proc/lambda) responding to #call(prompt) — or #complete(prompt) — that returns the model's reply as a String. Claude, OpenAI, a local Ollama server, a Bedrock/Vertex-backed client, a fake in a test: all of them work identically, and nothing in this class knows or cares which one is in use. Scryer::AiClient (ai_client.rb) is a small ready-made adapter for wiring up any JSON/HTTP chat endpoint; using it is optional.

Off by default: with no client configured (the default), .enhance!/ .enhance_result! are no-ops, and nothing here ever runs — this feature makes no network calls unless Scryer.configuration.ai_client is set. Nothing is ever auto-applied to source files; this only changes the text of a finding's suggested_fix, same as every other rule's fix text — still just something for a human to read and act on.

Class Method Summary collapse

Class Method Details

.enhance!(finding, client: Scryer.configuration.ai_client) ⇒ Object

Enhances a single Finding in place and returns it. Any failure (client raises, times out, returns nothing usable) is swallowed and the finding's original suggested_fix is left as-is — an LLM call failing should never break a scan.



28
29
30
31
32
33
34
35
36
# File 'lib/scryer/ai_fix_suggester.rb', line 28

def enhance!(finding, client: Scryer.configuration.ai_client)
  return finding unless client

  reply = call_client(client, prompt_for(finding))
  finding.suggested_fix = reply.strip unless blank?(reply)
  finding
rescue StandardError
  finding
end

.enhance_many!(findings, client: Scryer.configuration.ai_client, concurrency: 4) ⇒ Object

Same as enhance_result! but for a plain array of findings — used for Scryer::DependencyAudit::Finding objects, which aren't attached to a Scanner::Result. Works on any mix of Finding/DependencyAudit::Finding (prompt_for below dispatches on which one it got). No-op if no client is configured.



55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
# File 'lib/scryer/ai_fix_suggester.rb', line 55

def enhance_many!(findings, client: Scryer.configuration.ai_client, concurrency: 4)
  return findings unless client

  queue = Queue.new
  findings.each { |f| queue << f }

  workers = Array.new([concurrency, findings.size].min) do
    Thread.new do
      loop do
        finding = begin
          queue.pop(true)
        rescue ThreadError
          nil
        end
        break unless finding

        enhance!(finding, client: client)
      end
    end
  end
  workers.each(&:join)

  findings
end

.enhance_result!(result, client: Scryer.configuration.ai_client, concurrency: 4) ⇒ Object

Enhances every security/performance/style finding on a Scanner::Result in place. Runs across a small thread pool (network-bound work, same pattern as DependencyAudit.vulnerable_gems) so a large finding count doesn't mean one-request-at-a-time. No-op if no client is configured — callers don't need to check first.



44
45
46
47
48
# File 'lib/scryer/ai_fix_suggester.rb', line 44

def enhance_result!(result, client: Scryer.configuration.ai_client, concurrency: 4)
  enhance_many!(result.security_findings + result.performance_findings + result.style_findings,
                client: client, concurrency: concurrency)
  result
end