Add Cadence diff-shape parity signal
ober
ee7d1c1683e6cd097a633f19fb0f1810d3d470d6
--- a/GAPS.md +++ b/GAPS.md @@ -186,6 +186,11 @@ Cadence includes too many files, skewed addition/deletion ratio, suspiciously balanced large changes, consistent per-file sizes, template/file-extension patterns, and merge filtering. This project covers some but not all. +Status: implemented for file dispersion, skewed ratios, balanced large changes, +consistent per-file additions, and template/generated paths via the +`cadence-diff-shape` signal. Merge-mode handling was already implemented +separately through first-parent/all-parent options and merge warnings. + Acceptance criteria: - Add explicit signals for: --- a/main-binary.ss +++ b/main-binary.ss @@ -482,6 +482,40 @@ (if balanced? '("balanced large addition/deletion ratio") '()))]) (sig "diff-structure" "structure" score 0.15 "medium" "diff shape is unusually regular or broad" evidence "formatters, generated files, and refactors can look similar"))) +(def template-path-fragments '("generated" "template" "scaffold" "fixture" "snapshot")) +(def template-path-suffixes '(".lock" ".snap" ".snapshot" ".generated")) +(def (suffix-any? text suffixes) + (for/or ([suffix suffixes]) (string-suffix? suffix text))) +(def (template-like-path? path) + (let ([p (down path)]) + (or (any-contains? p template-path-fragments) + (suffix-any? p template-path-suffixes)))) +(def (cadence-diff-shape-signal files paths additions deletions) + (let* ([per-file-adds (map cadr files)] + [avg-adds (mean per-file-adds)] + [size-cv (if (= avg-adds 0.0) 0.0 (/ (stddev per-file-adds) avg-adds))] + [many-files? (> (length paths) 12)] + [skewed? (or (and (= deletions 0) (> additions 80)) + (and (> deletions 0) (> (/ (exact->inexact additions) deletions) 8.0)))] + [balanced? (and (> additions 80) (> deletions 80) + (< (abs (- (/ (exact->inexact additions) deletions) 1.0)) 0.15))] + [consistent? (and (>= (length per-file-adds) 5) (> avg-adds 0.0) (< size-cv 0.20))] + [template-count (count-where template-like-path? paths)] + [template? (> template-count 0)] + [score (+ (if many-files? 0.20 0.0) + (if skewed? 0.18 0.0) + (if balanced? 0.18 0.0) + (if consistent? 0.18 0.0) + (if template? 0.14 0.0))] + [evidence (append (if many-files? (list (str "many files changed: " (length paths))) '()) + (if skewed? (list (str "skewed addition/deletion ratio " additions "/" deletions)) '()) + (if balanced? '("balanced large addition/deletion ratio") '()) + (if consistent? (list (str "consistent per-file additions cv " size-cv)) '()) + (if template? (list (str "template/generated path count " template-count)) '()))]) + (sig "cadence-diff-shape" "structure" score 0.15 "medium" + "Cadence-style diff shape strategies matched broad or regular changes" + evidence + "large generated files, vendored updates, formatters, and mechanical refactors can match these patterns"))) (def (fnv-step h ch) (modulo (* (bitwise-xor h (char->integer ch)) 16777619) 4294967296)) (def (stable-hash32 s) (for/fold ([h 2166136261]) ([ch (in-string s)]) (fnv-step h ch))) @@ -820,6 +854,7 @@ [sim-pair (similarity-signal lines hashes)] [raw-signals (if eligible? (list (message-signal subject body adds) (code-signal lines) (structure-signal paths adds dels lines) + (cadence-diff-shape-signal files paths adds dels) (semantic-alignment-signal subject body lines) (car sim-pair) (history-signal adds time (parent-time repo parent) (author-times repo revs author-email)) (baseline-signal adds (prior-additions repo revs author-email rev file)) --- a/tests/fixture-smoke.sh +++ b/tests/fixture-smoke.sh @@ -5,7 +5,8 @@ root=$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd) fixture=$(mktemp -d) shallow=$(mktemp -d) provider_tmp=$(mktemp -d) -trap 'rm -rf "$fixture" "$shallow" "$provider_tmp"' EXIT +shape_fixture=$(mktemp -d) +trap 'rm -rf "$fixture" "$shallow" "$provider_tmp" "$shape_fixture"' EXIT git -C "$fixture" init -q git -C "$fixture" config user.name "Human Dev" @@ -55,6 +56,7 @@ printf '%s\n' "$json" | grep -q '"unmatched_recorded_attribution":\[\]' printf '%s\n' "$json" | grep -q '"metadata_hits":\["codex","openai"\]' printf '%s\n' "$json" | grep -q '"category":"code"' printf '%s\n' "$json" | grep -q '"category":"structure"' +printf '%s\n' "$json" | grep -q '"name":"cadence-diff-shape"' printf '%s\n' "$json" | grep -q '"name":"semantic-alignment"' printf '%s\n' "$json" | grep -q '"category":"semantic"' printf '%s\n' "$json" | grep -q '"category":"similarity"' @@ -68,6 +70,34 @@ if printf '%s\n' "$json" | grep -q 'vendor/library.py'; then exit 1 fi +git -C "$shape_fixture" init -q +git -C "$shape_fixture" config user.name "Human Dev" +git -C "$shape_fixture" config user.email "human@example.test" +printf 'base\n' > "$shape_fixture/README.md" +git -C "$shape_fixture" add README.md +GIT_AUTHOR_DATE='2026-07-29T08:00:00-06:00' \ +GIT_COMMITTER_DATE='2026-07-29T08:00:00-06:00' \ + git -C "$shape_fixture" commit -q -m 'initial shape fixture' +mkdir -p "$shape_fixture/generated" +i=1 +while [ "$i" -le 6 ]; do + file="$shape_fixture/generated/template-$i.snap" + j=1 + while [ "$j" -le 20 ]; do + printf 'snapshot line %s\n' "$j" >> "$file" + j=$((j + 1)) + done + git -C "$shape_fixture" add "generated/template-$i.snap" + i=$((i + 1)) +done +GIT_AUTHOR_DATE='2026-07-29T08:01:00-06:00' \ +GIT_COMMITTER_DATE='2026-07-29T08:01:00-06:00' \ + git -C "$shape_fixture" commit -q -m 'add generated snapshots' +shape_json=$("$root/bin/jerboa-aigit" scan "$shape_fixture" --format json --count 1 --include generated) +printf '%s\n' "$shape_json" | grep -q '"name":"cadence-diff-shape"' +printf '%s\n' "$shape_json" | grep -q 'skewed addition/deletion ratio' +printf '%s\n' "$shape_json" | grep -q 'template/generated path count' + heuristics=$("$root/bin/jerboa-aigit" scan "$fixture" --format json --count 1 --heuristics-only) printf '%s\n' "$heuristics" | grep -q '"recorded_ai_note_present":true' if printf '%s\n' "$heuristics" | grep -q '"verdict":"recorded-ai-authorship"'; then