Add Cadence burst timing parity

ober

7614df9efde4da60ac8018f77dfc8645386c6ceb

diff --git a/GAPS.md b/GAPS.md
index 04f86f7..aaff9bf 100644
--- a/GAPS.md
+++ b/GAPS.md
@@ -210,6 +210,11 @@ Acceptance criteria:
 Current timing handles velocity and simple nearby commit count, but not full
 Cadence-style rapid-fire burst definitions.
 
+Status: implemented with explicit high-velocity evidence, burst windows that
+require at least five nearby commits and at least three substantial nonblank
+commits, zero/negative parent-time gap evidence for skewed or historical
+imports, and fixture coverage for burst, normal batch, and skewed-date cases.
+
 Acceptance criteria:
 
 - Implement burst windows requiring at least five commits in ten minutes and at
diff --git a/main-binary.ss b/main-binary.ss
index 805d17e..e6d5043 100644
--- a/main-binary.ss
+++ b/main-binary.ss
@@ -345,6 +345,9 @@
 
 (def (added-line-count-for-paths repo rev paths explicit-file)
   (length (added-lines-for-paths repo rev paths explicit-file)))
+(def (commit-nonblank-added-line-count repo rev file)
+  (count-where (lambda (line) (not (blank? line)))
+               (bounded-added-lines-for-paths repo rev (numstat-paths (changed-files repo rev file)) file)))
 
 (def known-agents
   '("codex" "copilot" "claude" "cursor" "openai" "anthropic" "aider" "windsurf" "cody" "tabnine" "ai-agent"))
@@ -736,15 +739,19 @@
                "similarity in one scan window cannot prove original authorship")
           hash)))
 
-(def (history-signal additions commit-time parent-time author-times)
+(def (history-signal additions commit-time parent-time author-events)
   (let* ([delta (- commit-time parent-time)]
+         [non-positive-gap? (and (> parent-time 0) (<= delta 0))]
          [minutes (if (> delta 0) (/ delta 60.0) 0.0)]
          [velocity (if (> minutes 0.0) (/ additions minutes) 0.0)]
-         [nearby (count-where (lambda (t) (<= (abs (- commit-time t)) 600)) author-times)]
-         [burst? (>= nearby 5)]
+         [nearby-events (filter (lambda (event) (<= (abs (- commit-time (car event))) 600)) author-events)]
+         [nearby (length nearby-events)]
+         [substantial (count-where (lambda (event) (>= (cdr event) 20)) nearby-events)]
+         [burst? (and (>= nearby 5) (>= substantial 3))]
          [score (+ (cond [(> velocity 50.0) 1.0] [(> velocity 20.0) 0.60] [else 0.0]) (if burst? 0.20 0.0))]
          [evidence (append (if (> velocity 20.0) (list (str "line velocity " velocity " additions/minute")) '())
-                           (if burst? (list (str nearby " commits by author inside ten minutes")) '()))])
+                           (if burst? (list (str nearby " commits by author inside ten minutes; " substantial " substantial")) '())
+                           (if non-positive-gap? (list (str "non-positive parent time gap " delta " seconds")) '()))])
     (sig "history-timing" "history" score 0.15 "low" "commit timestamps suggest high throughput or bursts" evidence
          "Git commit timestamps are not typing timestamps")))
 
@@ -816,14 +823,17 @@
               (loop (cdr xs) (cons (numstat-adds (changed-files repo rev file)) out))
               (loop (cdr xs) out))))))
 
-(def (author-times repo revs author-id)
+(def (author-events repo revs author-id file)
   (let loop ([xs revs] [out '()])
     (if (null? xs)
         out
-        (let* ([fields (commit-fields repo (car xs))]
+        (let* ([rev (car xs)]
+               [fields (commit-fields repo rev)]
                [e (safe-ref fields 3 "")]
                [t (parse-int (safe-ref fields 4 "0") 0)])
-          (if (same-public-string? e author-id) (loop (cdr xs) (cons t out)) (loop (cdr xs) out))))))
+          (if (same-public-string? e author-id)
+              (loop (cdr xs) (cons (cons t (commit-nonblank-added-line-count repo rev file)) out))
+              (loop (cdr xs) out))))))
 
 (def (commit-shape-warnings parents)
   (append (if (root-commit? parents) '("root commit; no parent diff baseline") '())
@@ -887,7 +897,7 @@
                                 (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))
+                                (car sim-pair) (history-signal adds time (parent-time repo parent) (author-events repo revs author-email file))
                                 (baseline-signal adds (prior-additions repo revs author-email rev file))
                                 (repository-baseline-signal adds (prior-repository-additions repo revs rev file)))
                           '())]
diff --git a/tests/fixture-smoke.sh b/tests/fixture-smoke.sh
index 83c742c..b5c7379 100755
--- a/tests/fixture-smoke.sh
+++ b/tests/fixture-smoke.sh
@@ -6,7 +6,9 @@ fixture=$(mktemp -d)
 shallow=$(mktemp -d)
 provider_tmp=$(mktemp -d)
 shape_fixture=$(mktemp -d)
-trap 'rm -rf "$fixture" "$shallow" "$provider_tmp" "$shape_fixture"' EXIT
+timing_fixture=$(mktemp -d)
+normal_timing_fixture=$(mktemp -d)
+trap 'rm -rf "$fixture" "$shallow" "$provider_tmp" "$shape_fixture" "$timing_fixture" "$normal_timing_fixture"' EXIT
 
 git -C "$fixture" init -q
 git -C "$fixture" config user.name "Human Dev"
@@ -100,6 +102,80 @@ 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'
 
+git -C "$timing_fixture" init -q
+git -C "$timing_fixture" config user.name "Burst Bot"
+git -C "$timing_fixture" config user.email "burst@example.test"
+printf 'base\n' > "$timing_fixture/base.txt"
+git -C "$timing_fixture" add base.txt
+GIT_AUTHOR_DATE='2026-07-29T07:00:00-06:00' \
+GIT_COMMITTER_DATE='2026-07-29T07:00:00-06:00' \
+  git -C "$timing_fixture" commit -q -m 'base timing fixture'
+i=1
+while [ "$i" -le 5 ]; do
+  file="$timing_fixture/burst-$i.py"
+  j=1
+  while [ "$j" -le 25 ]; do
+    printf 'def helper_%s_%s(value): return value + %s\n' "$i" "$j" "$j" >> "$file"
+    j=$((j + 1))
+  done
+  git -C "$timing_fixture" add "burst-$i.py"
+  minute=$(printf '%02d' "$i")
+  GIT_AUTHOR_DATE="2026-07-29T07:$minute:00-06:00" \
+  GIT_COMMITTER_DATE="2026-07-29T07:$minute:00-06:00" \
+    git -C "$timing_fixture" commit -q -m "burst commit $i"
+  i=$((i + 1))
+done
+burst_json=$("$root/bin/jerboa-aigit" scan "$timing_fixture" --format json --count 5)
+printf '%s\n' "$burst_json" | grep -q 'line velocity'
+printf '%s\n' "$burst_json" | grep -q 'commits by author inside ten minutes; 5 substantial'
+
+printf 'clock skew\n' >> "$timing_fixture/base.txt"
+git -C "$timing_fixture" add base.txt
+GIT_AUTHOR_DATE='2026-07-29T06:59:00-06:00' \
+GIT_COMMITTER_DATE='2026-07-29T06:59:00-06:00' \
+  git -C "$timing_fixture" commit -q -m 'clock skew commit'
+skew_json=$("$root/bin/jerboa-aigit" scan "$timing_fixture" --format json --count 1)
+printf '%s\n' "$skew_json" | grep -q 'non-positive parent time gap'
+
+git -C "$normal_timing_fixture" init -q
+git -C "$normal_timing_fixture" config user.name "Batch Bot"
+git -C "$normal_timing_fixture" config user.email "batch@example.test"
+printf 'base\n' > "$normal_timing_fixture/base.txt"
+git -C "$normal_timing_fixture" add base.txt
+GIT_AUTHOR_DATE='2026-07-29T10:00:00-06:00' \
+GIT_COMMITTER_DATE='2026-07-29T10:00:00-06:00' \
+  git -C "$normal_timing_fixture" commit -q -m 'base normal timing fixture'
+i=1
+while [ "$i" -le 5 ]; do
+  file="$normal_timing_fixture/batch-$i.py"
+  j=1
+  while [ "$j" -le 25 ]; do
+    printf 'def batch_%s_%s(value): return value + %s\n' "$i" "$j" "$j" >> "$file"
+    j=$((j + 1))
+  done
+  git -C "$normal_timing_fixture" add "batch-$i.py"
+  case "$i" in
+    1) stamp='2026-07-29T10:15:00-06:00' ;;
+    2) stamp='2026-07-29T10:30:00-06:00' ;;
+    3) stamp='2026-07-29T10:45:00-06:00' ;;
+    4) stamp='2026-07-29T11:00:00-06:00' ;;
+    *) stamp='2026-07-29T11:15:00-06:00' ;;
+  esac
+  GIT_AUTHOR_DATE="$stamp" \
+  GIT_COMMITTER_DATE="$stamp" \
+    git -C "$normal_timing_fixture" commit -q -m "normal batch commit $i"
+  i=$((i + 1))
+done
+normal_json=$("$root/bin/jerboa-aigit" scan "$normal_timing_fixture" --format json --count 5)
+if printf '%s\n' "$normal_json" | grep -q 'commits by author inside ten minutes'; then
+  echo "normal spaced batch should not trigger timing burst evidence" >&2
+  exit 1
+fi
+if printf '%s\n' "$normal_json" | grep -q 'line velocity'; then
+  echo "normal spaced batch should not trigger high-velocity evidence" >&2
+  exit 1
+fi
+
 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