Analyzer::AI::Unified
Inherits Analyzer < FileHelper < Reference < Object
Unified AI analyzer that uses a provider-agnostic LLM adapter. Supports both OpenAI-compatible APIs and Ollama.
Constants
Expanded code paths of the static endpoints, set by
analysis_endpoints under --ai-scope unmatched.
Every adapter maps a call it could not complete — an HTTP error the retries did not clear, a provider error body, a refused or empty reply, a dead ACP agent, a request skipped after repeated failures — to an empty string, usually after a WARNING naming the cause. The empty string is what reaches here, so this is the reason the analyzer can attach to the lost coverage; it must stay true when no warning was printed.
Provider aliases whose default URL is on this host (see LLM::General).
Ceiling on simultaneous bundle requests, independent of a high
--concurrency: past a handful of in-flight calls a metered provider
answers with 429s rather than faster.
A bundle the provider says is over the model's window is re-split and retried instead of lost, so a model the token table doesn't know (or knows wrong) costs a rejected request, not the bundle's endpoints. Providers reject an oversized prompt before inference, so each bundle finding out for itself costs latency, not tokens.
:id, {id}, <int:id>, [id], *: not literal text to look for.
Bare URL tokens the LLM emits when it has nothing real to report
(schema echoes, "no endpoint" stand-ins). Compared case-folded
against the whole path with a single leading slash stripped, so a
legitimate nested route like /example/users is never rejected —
only a URL that IS one of these placeholders is dropped.
Files that exist to hold credentials. Matched on the basename and
withheld from every request — path listing, bundles, per-file prompts
and agent tools — unless --ai-include-sensitive is given. Source-code
extensions are deliberately absent from secrets.*: secrets.go is as
likely a route file as a vault.
Constructors
Class methods
A well-formed URL the code never mentions is a hallucination or
injected output. The last literal segment must occur in the source,
compared case-folded without -/_ and without a .ext suffix
(UsersController grounds /users, getUserProfile grounds
/user-profile, admin.php grounds /admin.php). Prefix-composed
routes still pass on their own last segment; / and all-placeholder
paths have nothing to check and pass. haystack must be downcased
with -/_ removed.
ponytail: convention-generated segments (Rails resources -> /new,
/edit) are dropped when the static analyzer did not report them.
Instance methods
Containment gate for every agent file tool. The agent picks these paths from LLM output, and the LLM is steered by the source tree being scanned, so "stay inside the scan base" is a boundary an untrusted repo gets to push on.
File.expand_path is purely lexical — it folds .. and makes the path
absolute but never follows a link. A checked-out repo containing
notes -> /home/user/.ssh/id_rsa therefore passed as an in-base path,
and read_file shipped the target's contents to the LLM provider.
(The tree walk, the grep and the bundling path all skip symlinks
already; these two entry points were the gap.)
So resolve both sides and require containment of the real path. A
base that is itself reached through a link (/tmp -> /private/tmp on
macOS) still matches, because @real_base_paths went through the same
resolution. The lexical check stays as the first gate: it rejects the
plain ../../etc/passwd case without touching the filesystem.
Public rather than private so the rule can be asserted directly: reaching it through a live LLM round trip is not a test.
The one gate on credentials files leaving the machine; see SENSITIVE_FILE.
Instance-side view of the same declaration. The per-file rescues live on
this base class, which has no way to name the analyzer that is running
inside them, so a skipped file could not be attributed to a tech.
Deriving it from analyzer_for keeps the name written exactly once.