Add Cadence diff-shape parity signal

ober

ee7d1c1683e6cd097a633f19fb0f1810d3d470d6

diff --git a/GAPS.md b/GAPS.md
index 9a135ce..57cc3aa 100644
--- 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:
diff --git a/main-binary.ss b/main-binary.ss
index 99f5d27..a0c7773 100644
--- 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))
diff --git a/tests/fixture-smoke.sh b/tests/fixture-smoke.sh
index 8e1fe5d..3f7e8c7 100755
--- 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