#!/usr/bin/env bash # # Hardened LEARNINGS update verification helper. # # Previous verification gates used grep-only checks and could pass by matching # an old file title (e.g. "Batch 6" appearing in the existing title). This helper # snapshots the file mtime before a batch and checks it after, then verifies a # fresh "Batch N" header appears in the first 5 lines. # # Usage: # scripts/verify-learnings-update.sh snapshot # scripts/verify-learnings-update.sh check # # Conventions: # LEARNINGS path: .learnings//LEARNINGS.md # Baseline store: .learnings//.mtime-baseline # # Exit codes: # 0 OK (file modified, batch header found) # 1 FAIL (mtime unchanged or batch header missing) # 2 WARNING / no baseline snapshot (needs manual review) # set -euo pipefail SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" PROJECT_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" usage() { cat < $0 check snapshot Capture the current mtime of .learnings//LEARNINGS.md and write it to .learnings//.mtime-baseline. check Compare current mtime against the stored baseline and verify that a "Batch N" header appears in the first 5 lines. EOF } log() { echo "[verify-learnings] $1" } main() { if [[ $# -lt 2 ]]; then usage exit 2 fi local mode="$1" local agent="$2" local learnings="$PROJECT_ROOT/.learnings/$agent/LEARNINGS.md" local baseline="$PROJECT_ROOT/.learnings/$agent/.mtime-baseline" if [[ ! -f "$learnings" ]]; then log "ERROR: $learnings not found" exit 2 fi case "$mode" in snapshot) if [[ $# -lt 3 ]]; then usage exit 2 fi local epoch="$3" mkdir -p "$(dirname "$baseline")" echo "$epoch" > "$baseline" log "Snapshot for $agent: baseline epoch = $epoch" exit 0 ;; check) if [[ $# -lt 3 ]]; then usage exit 2 fi local batch="$3" if [[ ! -f "$baseline" ]]; then log "ERROR: no baseline snapshot for $agent — run snapshot mode first." exit 2 fi local base_epoch base_epoch="$(cat "$baseline")" local cur_epoch cur_epoch="$(stat -c '%Y' "$learnings")" if [[ "$cur_epoch" -lt "$base_epoch" ]]; then log "WARNING: mtime regressed for $agent (baseline $base_epoch -> current $cur_epoch); manual review needed." exit 2 fi if [[ "$cur_epoch" -eq "$base_epoch" ]]; then log "FAIL: $agent LEARNINGS untouched (epoch unchanged at $cur_epoch)" exit 1 fi log "OK: $agent LEARNINGS updated (epoch: $base_epoch -> $cur_epoch)" if ! head -n 5 "$learnings" | grep -qE "^## Batch $batch|^### Batch $batch"; then log "FAIL: mtime updated but no Batch $batch header found in first 5 lines of $learnings" exit 1 fi log "OK: Batch $batch header present in first 5 lines" exit 0 ;; *) usage exit 2 ;; esac } main "$@"