Files
boshu2__agentops/scripts/evolve-capture-daily-learning.sh
T
Bo 10db732740 fix(evolve): guard jq|wc pipefail so a malformed line cannot abort the script (ag-1j1 #jq-wc-guards) (#584)
## What

Under `set -euo pipefail`, a `jq ... 2>/dev/null | wc -l` substitution
aborts the entire script when `jq` hits a malformed JSON line:
`2>/dev/null` suppresses jq's stderr but not its non-zero exit code,
`pipefail` propagates that through the pipeline, and `set -e` then kills
the script mid-run (leaving partial state).

## Fix

Append `|| echo 0` to the unguarded substitutions:

- **`scripts/bootstrap-maturity.sh` line 65** — reads the raw `.jsonl`
file directly, so a malformed line genuinely aborts the migration before
the summary prints. Verified empirically and caught by the regression
test (test fails against the unguarded copy with `status != 0`).
- **`scripts/evolve-capture-daily-learning.sh` lines 59-62 (4x)** —
matches the already-guarded `grep -c` on line 58. Verification note: in
normal operation `TODAY_CYCLES` is pre-sanitized by the line-55 `jq ...
|| true` filter, so these lines don't see malformed input in practice.
The guard is defense-in-depth that pins abort-proof behavior if
malformed content ever reaches them (e.g., a future refactor drops the
line-55 sanitization).

`halt-check.sh` is intentionally out of scope (`set -uo`, no `-e` —
degrades, doesn't abort).

## Test

`tests/scripts/jq-wc-pipefail-guards.bats` (5 tests):
- Malformed-input fixtures for both scripts prove neither aborts and the
consolidated artifacts still get written.
- A guarded/unguarded expression pair (positive + negative control) pins
the `|| echo 0` mechanism so a future edit can't silently drop it.

## Local validation

- `bats tests/scripts/jq-wc-pipefail-guards.bats` — 5/5 green
- `shellcheck scripts/evolve-capture-daily-learning.sh
scripts/bootstrap-maturity.sh` — clean
- Existing `tests/scripts/bootstrap-maturity.bats` — still 4/4 green

Closes-scenario: ag-1j1#jq-wc-guards
Bounded-context: BC5-Runtime
Evidence: bats tests/scripts/jq-wc-pipefail-guards.bats
2026-05-28 17:19:15 -04:00

211 lines
7.1 KiB
Bash
Executable File

#!/usr/bin/env bash
# evolve-capture-daily-learning.sh
# End-of-day consolidator for the /evolve all-day loop's self-reflection.
#
# Reads:
# .agents/evolve/daily-learning-log-YYYY-MM-DD.md (per-cycle micro-capture, append-only)
# .agents/evolve/cycle-history.jsonl (all cycles, filtered to today)
# .agents/learnings/YYYY-MM-DD-evolve-loop-learnings.md (prior days, for pattern detection)
#
# Writes:
# .agents/learnings/YYYY-MM-DD-evolve-loop-learnings.md (today's consolidated reflection)
#
# Side effects:
# - If a friction pattern is detected in 2+ daily learning files, files a bd issue under
# the evolution-roadmap label with the evolve-improvement label.
#
# Idempotent: re-running overwrites today's consolidated file but leaves the daily log alone.
#
# Usage:
# bash scripts/evolve-capture-daily-learning.sh # consolidate today
# bash scripts/evolve-capture-daily-learning.sh --dry-run # preview, no writes
# bash scripts/evolve-capture-daily-learning.sh DATE # consolidate explicit YYYY-MM-DD
set -euo pipefail
REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
cd "$REPO_ROOT"
DRY_RUN=false
DATE=""
for arg in "$@"; do
case "$arg" in
--dry-run) DRY_RUN=true ;;
[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9]) DATE="$arg" ;;
-h|--help)
command sed -n '2,/^set -euo/p' "$0" | command head -25
exit 0
;;
esac
done
[[ -z "$DATE" ]] && DATE="$(date +%Y-%m-%d)"
LOG=".agents/evolve/daily-learning-log-${DATE}.md"
HISTORY=".agents/evolve/cycle-history.jsonl"
OUT=".agents/learnings/${DATE}-evolve-loop-learnings.md"
if [[ ! -f "$LOG" && ! -f "$HISTORY" ]]; then
echo "no daily log or cycle history; nothing to consolidate for $DATE" >&2
exit 0
fi
# --- Gather: today's cycles from history ---
TODAY_CYCLES=""
if [[ -f "$HISTORY" ]]; then
TODAY_CYCLES="$(jq -c --arg d "$DATE" 'select(.started_at|startswith($d))' "$HISTORY" 2>/dev/null || true)"
fi
TOTAL=$(printf '%s\n' "$TODAY_CYCLES" | grep -c . 2>/dev/null || echo 0)
PRODUCTIVE=$(printf '%s\n' "$TODAY_CYCLES" | jq -r 'select(.result=="improved") | .cycle' 2>/dev/null | wc -l | tr -d ' ' || echo 0)
SCOUTS=$(printf '%s\n' "$TODAY_CYCLES" | jq -r 'select(.result=="harvested") | .cycle' 2>/dev/null | wc -l | tr -d ' ' || echo 0)
IDLE=$(printf '%s\n' "$TODAY_CYCLES" | jq -r 'select(.result=="idle" or .result=="unchanged") | .cycle' 2>/dev/null | wc -l | tr -d ' ' || echo 0)
REGRESSED=$(printf '%s\n' "$TODAY_CYCLES" | jq -r 'select(.result=="regressed") | .cycle' 2>/dev/null | wc -l | tr -d ' ' || echo 0)
# --- Gather: micro-capture lines from today's daily log ---
MICRO_LINES=""
if [[ -f "$LOG" ]]; then
MICRO_LINES="$(command grep -E '^- ' "$LOG" 2>/dev/null || true)"
fi
MICRO_COUNT=$(printf '%s\n' "$MICRO_LINES" | grep -c . 2>/dev/null || echo 0)
# --- Extract friction tags (lines beginning with "- FRICTION:" in daily log) ---
FRICTIONS=""
if [[ -n "$MICRO_LINES" ]]; then
FRICTIONS="$(printf '%s\n' "$MICRO_LINES" | command grep -iE 'FRICTION:' 2>/dev/null || true)"
fi
FRICTION_COUNT=$(printf '%s\n' "$FRICTIONS" | grep -c . 2>/dev/null || echo 0)
# --- Cross-day pattern detection: scan prior evolve-loop-learnings files for matching friction tags ---
PRIOR_DAYS="$(command find .agents/learnings -maxdepth 1 -name '*-evolve-loop-learnings.md' -not -name "${DATE}*" 2>/dev/null | sort -r | head -7)"
RECURRING=""
if [[ -n "$FRICTIONS" && -n "$PRIOR_DAYS" ]]; then
while IFS= read -r line; do
[[ -z "$line" ]] && continue
tag="$(echo "$line" | command sed -nE 's/.*FRICTION:[[:space:]]*([A-Za-z0-9-]+).*/\1/p')"
[[ -z "$tag" ]] && continue
if echo "$PRIOR_DAYS" | xargs -I {} command grep -l "FRICTION: ${tag}" {} 2>/dev/null | head -1 | grep -q .; then
RECURRING="${RECURRING}- ${tag} (also seen in prior days)"$'\n'
fi
done <<< "$FRICTIONS"
fi
# --- Build the consolidated learning markdown ---
TMP="$(mktemp)"
{
cat <<EOF
---
id: learning-${DATE}-evolve-loop
type: learning
date: ${DATE}
category: process
confidence: high
maturity: provisional
utility: 0.7
source: evolve-loop-self-reflection
---
# Learning: /evolve loop self-reflection — ${DATE}
## Counts
| Metric | Count |
|--------|-------|
| Total cycles | ${TOTAL} |
| Productive (commit landed) | ${PRODUCTIVE} |
| Scout (queue annotated, no commit) | ${SCOUTS} |
| Idle | ${IDLE} |
| Regressed | ${REGRESSED} |
| Micro-captures in daily log | ${MICRO_COUNT} |
| Friction tags this session | ${FRICTION_COUNT} |
## Cycle ledger (today)
EOF
if [[ -n "$TODAY_CYCLES" ]]; then
printf '%s\n' "$TODAY_CYCLES" | jq -r '"- cycle " + (.cycle|tostring) + " [" + (.result // "?") + "] " + (.work_ref // "") + " — " + (.title // "")[0:90]' 2>/dev/null
else
echo "_(no cycles found for ${DATE})_"
fi
cat <<EOF
## Per-cycle micro-captures
EOF
if [[ -n "$MICRO_LINES" ]]; then
printf '%s\n' "$MICRO_LINES"
else
echo "_(no micro-captures appended to daily log)_"
fi
cat <<EOF
## Frictions observed
EOF
if [[ "$FRICTION_COUNT" -gt 0 ]]; then
printf '%s\n' "$FRICTIONS"
else
echo "_(no friction tags this session)_"
fi
cat <<EOF
## Recurring (seen on 2+ days)
EOF
if [[ -n "$RECURRING" ]]; then
printf '%s' "$RECURRING"
else
echo "_(no recurring patterns vs prior daily learnings)_"
fi
cat <<EOF
## Promotion candidates
If any line in "Recurring" matches a friction that has appeared on 2+ days, a bead under the \`evolution-roadmap\` label with \`evolve-improvement\` should be filed (or an existing one updated) to fold it into the skill improvement queue. See \`docs/plans/2026-05-11-evolution-roadmap.md\` section LC for the protocol.
## Source
Auto-generated by \`scripts/evolve-capture-daily-learning.sh\` from \`${LOG}\` + \`${HISTORY}\`.
EOF
} > "$TMP"
if [[ "$DRY_RUN" == "true" ]]; then
echo "=== would write $OUT ==="
command cat "$TMP"
rm -f "$TMP"
exit 0
fi
command mkdir -p "$(dirname "$OUT")"
command mv "$TMP" "$OUT"
echo "wrote: $OUT"
# --- Auto-file evolve-improvement bead for recurring frictions ---
if [[ -n "$RECURRING" ]]; then
while IFS= read -r line; do
[[ -z "$line" ]] && continue
tag="$(echo "$line" | command sed -nE 's/.*- ([A-Za-z0-9-]+).*/\1/p')"
[[ -z "$tag" ]] && continue
title="LC-followup: Address recurring /evolve friction \"${tag}\""
# Idempotency: skip if a bead with this title already exists open
if bd list --status=open --json 2>/dev/null | jq -e --arg t "$title" '.[] | select(.title == $t)' >/dev/null 2>&1; then
echo "skip (open bead exists): $title"
continue
fi
bd create \
--title="$title" \
--description="Friction \"${tag}\" appeared in /evolve loop self-reflection on ${DATE} and at least one prior day. Auto-filed by scripts/evolve-capture-daily-learning.sh. Review .agents/learnings/${DATE}-evolve-loop-learnings.md and the matching prior-day file for context. Acceptance: patch skills/evolve/SKILL.md or related script to address the friction; close this bead when the next /evolve session does not surface the same tag." \
--type=task \
--priority=2 \
--labels="evolution-roadmap,evolve-improvement,auto-filed" \
>/dev/null 2>&1 && echo "filed bead: $title" || echo "FAILED to file: $title"
done <<< "$RECURRING"
fi