From d99534dc354224cb4e3b5b79047222450488cc6f Mon Sep 17 00:00:00 2001 From: dtzp555-max Date: Tue, 5 May 2026 09:43:47 +1000 Subject: [PATCH] chore(repo): add .gitignore for runtime artifacts; commit scripts/heartbeat-field-check.sh (#55) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two related changes — both repo-hygiene, no behavior impact. ### `.gitignore` (new) Ignores runtime artifacts that should never be tracked: - `logs/` and `*.log` — proxy.log, last_send.log, heartbeat-field-check log - `node_modules/` — dependency cache - `.env`, `.env.*` — local secrets/config - `.DS_Store`, `*.swp`, `*~` — editor/OS scratch `logs/` was previously untracked-but-present in working trees; the new ignore makes that intent explicit and prevents accidental commits. ### `scripts/heartbeat-field-check.sh` (commit existing untracked file) This script was authored as a one-shot field-evidence gatherer for the v3.12.0 SSE heartbeat work (PR #49 / issue #47). It fired successfully on 2026-05-02 09:00 Australia/Brisbane via launchd (`~/Library/LaunchAgents/dev.ocp.heartbeat-check.plist`) and posted a summary comment to issue #47. Useful tooling pattern (one-shot field check + launchd schedule + dry-run flag), worth keeping under version control rather than letting it die in an untracked working tree. Verification: `git status` clean after both adds. Co-authored-by: dtzp555 --- .gitignore | 15 ++++ scripts/heartbeat-field-check.sh | 132 +++++++++++++++++++++++++++++++ 2 files changed, 147 insertions(+) create mode 100644 .gitignore create mode 100755 scripts/heartbeat-field-check.sh diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..0fccb61 --- /dev/null +++ b/.gitignore @@ -0,0 +1,15 @@ +# Runtime artifacts +logs/ +*.log + +# Dependencies +node_modules/ + +# Environment files +.env +.env.* + +# Editor / OS scratch +.DS_Store +*.swp +*~ diff --git a/scripts/heartbeat-field-check.sh b/scripts/heartbeat-field-check.sh new file mode 100755 index 0000000..26d7f2a --- /dev/null +++ b/scripts/heartbeat-field-check.sh @@ -0,0 +1,132 @@ +#!/bin/bash +# One-shot field-evidence gatherer for OCP v3.12.0 SSE heartbeat. +# Scheduled by ~/Library/LaunchAgents/dev.ocp.heartbeat-check.plist to fire +# once at 2026-05-02 09:00 Australia/Brisbane. Gathers evidence from local +# OCP logs + GitHub issue #47 + repo issue search, posts a summary comment +# on #47, and exits. Does NOT open PRs or change code — the maintainer +# decides after reading the summary. +# +# Dry-run: ./heartbeat-field-check.sh --dry-run (prints summary, skips post) + +set -euo pipefail + +REPO="dtzp555-max/ocp" +SHIP_DATE="2026-04-25" +# Baseline captured at script-install time so internal testing entries from +# Phase 3 verification (~5 entries from 2026-04-25T00:00–00:48Z) don't get +# counted as field evidence. Any heartbeat_active log entry with ts >= this +# timestamp is treated as a real opt-in. +BASELINE_TS="2026-04-25T01:00:00Z" +PROXY_LOG="$HOME/ocp/logs/proxy.log" +OUT_DIR="$HOME/ocp/logs" +SELF_LOG="$OUT_DIR/heartbeat-field-check-$(date +%Y-%m-%d).log" +DRY_RUN=0 +[ "${1:-}" = "--dry-run" ] && DRY_RUN=1 + +mkdir -p "$OUT_DIR" + +exec > >(tee -a "$SELF_LOG") 2>&1 + +echo "=== heartbeat field-evidence check: $(date -u +%Y-%m-%dT%H:%M:%SZ) (dry_run=$DRY_RUN) ===" + +# ── signal 1: local proxy log ───────────────────────────────────────────── +if [ -r "$PROXY_LOG" ]; then + # Only count entries with ts >= BASELINE_TS (string sort works on RFC3339) + HEARTBEAT_COUNT=$(grep '"event":"heartbeat_active"' "$PROXY_LOG" 2>/dev/null \ + | awk -v base="$BASELINE_TS" ' + match($0, /"ts":"[^"]+"/) { + ts = substr($0, RSTART+6, RLENGTH-7); + if (ts >= base) c++ + } + END { print c+0 }') +else + HEARTBEAT_COUNT=0 +fi +echo "signal 1 — heartbeat_active log entries since $BASELINE_TS: $HEARTBEAT_COUNT" + +# ── signal 2: comments on #47 since ship ────────────────────────────────── +NEW_47_JSON="/tmp/ocp-47-new-comments-$$.json" +gh issue view 47 --repo "$REPO" --json comments \ + --jq '[.comments[] | select(.createdAt >= "'"$SHIP_DATE"'T00:00:00Z")]' \ + > "$NEW_47_JSON" 2>/dev/null || echo "[]" > "$NEW_47_JSON" +NEW_COMMENTS=$(jq 'length' "$NEW_47_JSON") +echo "signal 2 — new comments on #47 since $SHIP_DATE: $NEW_COMMENTS" + +# Build a compact, human-readable excerpt for the summary body +NEW_47_EXCERPT="" +if [ "$NEW_COMMENTS" -gt 0 ]; then + NEW_47_EXCERPT=$(jq -r '.[] | "- **@\(.author.login)** (\(.createdAt)): " + (.body | gsub("\r"; "") | split("\n")[0])[:180]' "$NEW_47_JSON") +fi + +# ── signal 3: other heartbeat-related issues since ship ────────────────── +OTHER_ISSUES_JSON="/tmp/ocp-heartbeat-issues-$$.json" +gh search issues "repo:$REPO heartbeat" --json number,title,state,createdAt --limit 30 \ + --jq '[.[] | select(.createdAt >= "'"$SHIP_DATE"'T00:00:00Z" and .number != 47 and .number != 48)]' \ + > "$OTHER_ISSUES_JSON" 2>/dev/null || echo "[]" > "$OTHER_ISSUES_JSON" +OTHER_ISSUES=$(jq 'length' "$OTHER_ISSUES_JSON") +echo "signal 3 — other heartbeat-related issues since ship: $OTHER_ISSUES" + +OTHER_ISSUES_EXCERPT="" +if [ "$OTHER_ISSUES" -gt 0 ]; then + OTHER_ISSUES_EXCERPT=$(jq -r '.[] | "- #\(.number) [\(.state)] \(.title)"' "$OTHER_ISSUES_JSON") +fi + +# ── compose summary ────────────────────────────────────────────────────── +BODY_FILE="/tmp/ocp-47-summary-$$.md" +{ + echo "### Automated 7-day field-evidence check (v3.12.0)" + echo + echo "_Triggered by a local launchd scheduled task on the maintainer's rig at $(date -u +%Y-%m-%dT%H:%M:%SZ)._" + echo + echo "| Signal | Count |" + echo "|---|---|" + echo "| \`heartbeat_active\` log entries on prod rig (since baseline $BASELINE_TS) | $HEARTBEAT_COUNT |" + echo "| New comments on #47 since $SHIP_DATE | $NEW_COMMENTS |" + echo "| Other heartbeat-related issues filed since $SHIP_DATE | $OTHER_ISSUES |" + echo + if [ -n "$NEW_47_EXCERPT" ]; then + echo "**New #47 comments (first line each):**" + echo + echo "$NEW_47_EXCERPT" + echo + fi + if [ -n "$OTHER_ISSUES_EXCERPT" ]; then + echo "**Other heartbeat-related issues:**" + echo + echo "$OTHER_ISSUES_EXCERPT" + echo + fi + echo "**Decision guidance for maintainer (manual):**" + echo + echo "- If any of the above indicate a **crash report** on \`: keepalive\` comment frames → leave default at \`0\` and file a \`CLAUDE_HEARTBEAT_FORMAT=empty-delta\` follow-up issue (spec \`§D2\` fallback plan)." + echo "- If there is at least one **opt-in confirmation** (a user reports \`CLAUDE_HEARTBEAT_INTERVAL\` fixed their timeout issue) and no crash reports → consider opening a PR for v3.13.0 flipping the default to \`30000\`, following the same ALIGNMENT + independent-reviewer + release-kit discipline as PR #49." + echo "- If all three signals are zero → extend the soak window or close this follow-up as \"no field evidence.\"" + echo + echo "This bot does not open PRs or change code. The maintainer reviews and acts." +} > "$BODY_FILE" + +echo "--- summary preview ---" +cat "$BODY_FILE" +echo "--- end preview ---" + +# ── post (unless dry-run) ──────────────────────────────────────────────── +if [ "$DRY_RUN" -eq 1 ]; then + echo "DRY RUN — skipping gh issue comment" +else + gh issue comment 47 --repo "$REPO" --body-file "$BODY_FILE" && echo "comment posted on #47" +fi + +# ── cleanup + self-disable so the plist doesn't linger loaded forever ──── +rm -f "$NEW_47_JSON" "$OTHER_ISSUES_JSON" "$BODY_FILE" + +if [ "$DRY_RUN" -eq 0 ]; then + # Unload + remove the plist so this never fires again + PLIST="$HOME/Library/LaunchAgents/dev.ocp.heartbeat-check.plist" + if [ -f "$PLIST" ]; then + launchctl bootout "gui/$(id -u)" "$PLIST" 2>/dev/null || launchctl unload "$PLIST" 2>/dev/null || true + rm -f "$PLIST" + echo "self-disabled: removed $PLIST" + fi +fi + +echo "=== done ==="