Add JSON stats output
ober
5b2dd4dbf2adf89848d3b025f1a094ebb9f7a27a
--- a/README.md +++ b/README.md @@ -61,7 +61,8 @@ and `network_used:false`. `stats` reports commit count, distinct authors, recorded tools, recorded AI line counts, metadata/heuristic counts, score-band counts, and signal-category -coverage for the selected revision range. +coverage for the selected revision range. Use `stats --format json` for the +same summary as a stable machine-readable object. `explain` prints the selected commit's thresholds, raw signals, weighted signal contributions, corroboration bonus, counter-evidence from zero-score signals, --- a/main-binary.ss +++ b/main-binary.ss @@ -883,6 +883,38 @@ (def (finding-has-signal-category? f category) (for/or ([s (finding-signals f)]) (same-public-string? (signal-category s) category))) +(def (count-verdict findings verdict-name) + (count-where (lambda (f) (same-public-string? (finding-verdict f) verdict-name)) findings)) + +(def (score-band-json findings verdict-name) + (list (cons 'name verdict-name) + (cons 'count (count-verdict findings verdict-name)))) + +(def (signal-coverage-json findings category) + (list (cons 'category category) + (cons 'count (count-where (lambda (f) (finding-has-signal-category? f category)) findings)))) + +(def (stats-json repo findings) + (let ([tools (stats-tools findings)]) + (list (cons 'detector_version detector-version) + (cons 'config_hash (scan-config-hash current-config)) + (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 'author_count (length (unique (map finding-author-email findings)))) + (cons 'tools tools) + (cons 'recorded_ai_notes (count-where (lambda (f) (not (string-empty? (finding-note f)))) findings)) + (cons 'recorded_ai_lines (sum (map finding-recorded-line-count findings))) + (cons 'metadata_indicated_agents (count-where (lambda (f) (pair? (finding-metadata f))) findings)) + (cons 'likely_ai_assisted_by_heuristics (count-verdict findings "likely-ai-assisted")) + (cons 'score_bands (map (lambda (v) (score-band-json findings v)) stats-verdicts)) + (cons 'signal_coverage (map (lambda (category) (signal-coverage-json findings category)) stats-signal-categories))))) + +(def (display-stats-json repo findings) + (displayln (json-string (stats-json repo findings)))) (def (display-stats findings) (let ([tools (stats-tools findings)]) (displayln (str "commits: " (length findings))) @@ -1135,7 +1167,10 @@ [revs (commit-list root (options-count opts) commit (options-from opts) (options-to opts) (options-file opts))] [findings (scan-repo root revs (options-file opts) (options-min-lines opts) (options-metadata-only? opts) (options-heuristics-only? opts))]) - (cond [(string=? (options-command opts) "stats") (display-stats findings)] + (cond [(string=? (options-command opts) "stats") + (if (string=? (options-format opts) "json") + (display-stats-json root findings) + (display-stats findings))] [(string=? (options-command opts) "verify-authorship") (display-json root findings)] [(string=? (options-command opts) "explain") (if (pair? findings) --- a/tests/fixture-smoke.sh +++ b/tests/fixture-smoke.sh @@ -99,6 +99,14 @@ printf '%s\n' "$stats" | grep -q ' recorded-ai-authorship: 1' printf '%s\n' "$stats" | grep -q 'signal coverage:' printf '%s\n' "$stats" | grep -q ' code: 2' +stats_json=$("$root/bin/jerboa-aigit" stats "$fixture" --count 2 --format json) +printf '%s\n' "$stats_json" | grep -q '"count":2' +printf '%s\n' "$stats_json" | grep -q '"author_count":2' +printf '%s\n' "$stats_json" | grep -q '"tools":\["codex"\]' +printf '%s\n' "$stats_json" | grep -q '"recorded_ai_lines":360' +printf '%s\n' "$stats_json" | grep -q '"score_bands":\[{"name":"recorded-ai-authorship","count":1}' +printf '%s\n' "$stats_json" | grep -q '"signal_coverage":\[{"category":"text","count":2}' + jsonl=$("$root/bin/jerboa-aigit" scan "$fixture" --format jsonl --count 2) jsonl_lines=$(printf '%s\n' "$jsonl" | wc -l | tr -d ' ') test "$jsonl_lines" = 2