Class: Scryer::AiFixSuggester
- Inherits:
-
Object
- Object
- Scryer::AiFixSuggester
- 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
-
.enhance!(finding, client: Scryer.configuration.ai_client, root: nil) ⇒ Object
Enhances a single Finding in place and returns it.
-
.enhance_many!(findings, client: Scryer.configuration.ai_client, concurrency: 4, root: nil) ⇒ 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.
-
.enhance_result!(result, client: Scryer.configuration.ai_client, concurrency: 4, root: nil) ⇒ Object
Enhances every security/performance/style finding on a Scanner::Result in place.
Class Method Details
.enhance!(finding, client: Scryer.configuration.ai_client, root: nil) ⇒ 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.
root, when given (and only for a Scryer::Finding — see
FixVerifier), triggers a follow-up verification pass: re-read the
actual file from disk, substitute the AI's suggested replacement for
the one offending line, and re-run just this finding's own rule
against the result. Sets finding.fix_verified to true/false/nil (see
Finding#fix_verified) — never raises, same failure-swallowing
philosophy as the AI call itself.
36 37 38 39 40 41 42 43 44 45 |
# File 'lib/scryer/ai_fix_suggester.rb', line 36 def enhance!(finding, client: Scryer.configuration.ai_client, root: nil) return finding unless client reply = call_client(client, prompt_for(finding)) finding.suggested_fix = reply.strip unless blank?(reply) finding.fix_verified = FixVerifier.verify(finding: finding, root: root) if root finding rescue StandardError finding end |
.enhance_many!(findings, client: Scryer.configuration.ai_client, concurrency: 4, root: nil) ⇒ 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. root is ignored for DependencyAudit::Finding objects
(FixVerifier only handles rule-backed Finding — see its guard clause).
69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 |
# File 'lib/scryer/ai_fix_suggester.rb', line 69 def enhance_many!(findings, client: Scryer.configuration.ai_client, concurrency: 4, root: nil) 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, root: root) end end end workers.each(&:join) findings end |
.enhance_result!(result, client: Scryer.configuration.ai_client, concurrency: 4, root: nil) ⇒ 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. Pass root (the project root
finding.file is relative to) to also run fix verification — see
enhance!; omit it to skip verification entirely (e.g. when the
caller has no meaningful root, or doesn't want the extra re-parse
work).
57 58 59 60 61 |
# File 'lib/scryer/ai_fix_suggester.rb', line 57 def enhance_result!(result, client: Scryer.configuration.ai_client, concurrency: 4, root: nil) enhance_many!(result.security_findings + result.performance_findings + result.style_findings, client: client, concurrency: concurrency, root: root) result end |