Accept explicit LLM options offline

ober

74d3f8f8fc79b9f0730d1cd6b127e172ac2f28bd

diff --git a/README.md b/README.md
index 7bc22b1..5dd57c1 100644
--- a/README.md
+++ b/README.md
@@ -35,6 +35,7 @@ Supported options are `--config FILE`, `--count N`, `--all`, `--from REV`,
 `--exclude PREFIX`, `--min-lines N`, `--max-files N`,
 `--max-added-lines N`, `--max-note-bytes N`,
 `--threshold HUMAN,AI`, `--threshold HUMAN,UNCERTAIN,AI`,
+`--no-llm`, `--llm`, `--provider NAME`,
 `--format table|json|jsonl|markdown`, `--metadata-only`, and
 `--heuristics-only`.
 
@@ -53,6 +54,11 @@ and scores at or above `AI` are `likely-ai-assisted`. The three-value form is
 accepted for compatibility with `human,uncertain,ai` wording; the middle value
 is retained as a label boundary only and does not create a separate verdict.
 
+`--no-llm` is the default. `--provider NAME` records the requested provider in
+JSON output. `--llm` is accepted as an explicit request, but this release has no
+active provider adapter; it emits a warning and still reports `llm_used:false`
+and `network_used:false`.
+
 By default, scans skip `vendor/`, `generated/`, `dist/`, `node_modules/`, and
 `.git/` paths. Use `--include PATH` or `--file PATH` to inspect one of those
 paths explicitly. `--include` and `--file` accept repository-relative pathspecs;
@@ -81,8 +87,8 @@ JSON output includes:
 
 - `detector_version`
 - `config_hash`
-- analysis provenance fields: `analysis_provider`, `llm_used`, and
-  `network_used`
+- analysis provenance fields: `analysis_provider`, `provider`, `llm_used`,
+  and `network_used`
 - repository path
 - commit and parent IDs
 - author/committer-facing metadata
diff --git a/main-binary.ss b/main-binary.ss
index b87fe2f..cec2864 100644
--- a/main-binary.ss
+++ b/main-binary.ss
@@ -23,7 +23,7 @@
         xs)))
 
 (def (usage)
-  (displayln "usage: jerboa main-binary.ss scan [PATH] [--config FILE] [--count N|--all] [--from REV] [--to REV] [--file PATH|--include PATH] [--exclude PREFIX] [--min-lines N] [--threshold HUMAN,AI] [--max-files N] [--max-added-lines N] [--max-note-bytes N] [--format table|json|jsonl|markdown]")
+  (displayln "usage: jerboa main-binary.ss scan [PATH] [--config FILE] [--count N|--all] [--from REV] [--to REV] [--file PATH|--include PATH] [--exclude PREFIX] [--min-lines N] [--threshold HUMAN,AI] [--no-llm|--llm] [--provider NAME] [--max-files N] [--max-added-lines N] [--max-note-bytes N] [--format table|json|jsonl|markdown]")
   (displayln "       jerboa main-binary.ss explain REV [PATH] [--format json|markdown|table]")
   (displayln "       jerboa main-binary.ss stats [PATH] [--count N]")
   (displayln "       jerboa main-binary.ss verify-authorship [PATH] [--count N]"))
@@ -467,6 +467,28 @@
                     (scan-config-max-note-bytes cfg)))
 
 (def current-config (finalize-config (default-scan-config)))
+(def current-analysis-provider "offline-heuristics")
+(def current-requested-provider "none")
+(def current-llm-used? #f)
+(def current-network-used? #f)
+(def current-llm-requested? #f)
+
+(def (set-provider! name)
+  (set! current-requested-provider (if (string-empty? name) "none" name)))
+
+(def (request-llm!)
+  (set! current-llm-requested? #t))
+
+(def (disable-llm!)
+  (set! current-llm-requested? #f)
+  (set! current-llm-used? #f)
+  (set! current-network-used? #f)
+  (set! current-analysis-provider "offline-heuristics"))
+
+(def (llm-warnings)
+  (if current-llm-requested?
+      (list (str "LLM analysis requested for provider " current-requested-provider "; no provider adapter is active, using offline heuristics"))
+      '()))
 
 (def (weight-for-category cfg category fallback)
   (cond [(same-public-string? category "text") (scan-config-text-weight cfg)]
@@ -565,6 +587,7 @@
   (append (commit-shape-warnings parents)
           (parent-history-warnings shallow? missing-parent?)
           (binary-change-warnings binary-count)
+          (llm-warnings)
           (if (null? files) '("no changed text files found or commit is unavailable") '())
           (if (null? lines) '("no added UTF-8 patch lines available") '())
           (limit-warning "files" file-count (scan-config-max-files current-config))
@@ -653,7 +676,8 @@
         (cons 'evidence (signal-evidence s)) (cons 'limitations (signal-limitations s))))
 (def (finding-json repo f)
   (list (cons 'detector_version detector-version) (cons 'config_hash (scan-config-hash current-config))
-        (cons 'analysis_provider "offline-heuristics") (cons 'llm_used #f) (cons 'network_used #f)
+        (cons 'analysis_provider current-analysis-provider) (cons 'provider current-requested-provider)
+        (cons 'llm_used current-llm-used?) (cons 'network_used current-network-used?)
         (cons 'repository repo) (cons 'commit (finding-commit f))
         (cons 'parent (finding-parent f)) (cons 'author_name (finding-author-name f)) (cons 'author_email (finding-author-email f))
         (cons 'time (finding-time f)) (cons 'subject (finding-subject f)) (cons 'files (finding-files f))
@@ -665,7 +689,8 @@
         (cons 'score (finding-score f)) (cons 'verdict (finding-verdict f)) (cons 'warnings (finding-warnings f))))
 (def (report-json repo findings)
   (list (cons 'detector_version detector-version) (cons 'config_hash (scan-config-hash current-config))
-        (cons 'analysis_provider "offline-heuristics") (cons 'llm_used #f) (cons 'network_used #f)
+        (cons 'analysis_provider current-analysis-provider) (cons 'provider current-requested-provider)
+        (cons 'llm_used current-llm-used?) (cons 'network_used current-network-used?)
         (cons 'repository repo) (cons 'count (length findings))
         (cons 'findings (map (lambda (f) (finding-json repo f)) findings))))
 
@@ -892,6 +917,18 @@
                                (options-commit opts) (options-from opts) (options-to opts) (options-file opts)
                                (options-min-lines opts) (options-metadata-only? opts) #t)
                  path-set?)]
+          [(string=? (car xs) "--no-llm")
+           (begin
+             (disable-llm!)
+             (loop (cdr xs) opts path-set?))]
+          [(string=? (car xs) "--llm")
+           (begin
+             (request-llm!)
+             (loop (cdr xs) opts path-set?))]
+          [(and (string=? (car xs) "--provider") (pair? (cdr xs)))
+           (begin
+             (set-provider! (cadr xs))
+             (loop (cddr xs) opts path-set?))]
           [(option-flag? (car xs)) (loop (cdr xs) opts path-set?)]
           [(and (string=? (options-command opts) "explain") (not (options-commit opts)))
            (loop (cdr xs)
diff --git a/tests/fixture-smoke.sh b/tests/fixture-smoke.sh
index 572172e..8804b55 100755
--- a/tests/fixture-smoke.sh
+++ b/tests/fixture-smoke.sh
@@ -42,6 +42,7 @@ json=$("$root/bin/jerboa-aigit" scan "$fixture" --format json --count 2)
 printf '%s\n' "$json" | grep -q '"count":2'
 printf '%s\n' "$json" | grep -q '"config_hash":"[0-9]'
 printf '%s\n' "$json" | grep -q '"analysis_provider":"offline-heuristics"'
+printf '%s\n' "$json" | grep -q '"provider":"none"'
 printf '%s\n' "$json" | grep -q '"llm_used":false'
 printf '%s\n' "$json" | grep -q '"network_used":false'
 printf '%s\n' "$json" | grep -q '"verdict":"recorded-ai-authorship"'
@@ -67,6 +68,18 @@ if printf '%s\n' "$heuristics" | grep -q '"verdict":"recorded-ai-authorship"'; t
   exit 1
 fi
 
+llm_json=$("$root/bin/jerboa-aigit" scan "$fixture" --format json --count 1 --llm --provider local)
+printf '%s\n' "$llm_json" | grep -q '"provider":"local"'
+printf '%s\n' "$llm_json" | grep -q '"llm_used":false'
+printf '%s\n' "$llm_json" | grep -q '"network_used":false'
+printf '%s\n' "$llm_json" | grep -q 'LLM analysis requested for provider local; no provider adapter is active, using offline heuristics'
+
+no_llm_json=$("$root/bin/jerboa-aigit" scan "$fixture" --format json --count 1 --llm --provider local --no-llm)
+if printf '%s\n' "$no_llm_json" | grep -q 'LLM analysis requested'; then
+  echo "no-llm should disable requested LLM warning" >&2
+  exit 1
+fi
+
 metadata=$("$root/bin/jerboa-aigit" scan "$fixture" --format json --count 1 --metadata-only)
 printf '%s\n' "$metadata" | grep -q '"signals":\[\]'
 printf '%s\n' "$metadata" | grep -q '"score":0.0'