From bff43de5636793e50eacacba6e8df90947c01b24 Mon Sep 17 00:00:00 2001 From: Anvil Date: Thu, 30 Apr 2026 15:01:21 +0000 Subject: [PATCH 1/2] feat(mail): CI-gate for DONE mails (oys-9oye) --- scripts/mail-send-ci-gate.sh | 149 +++++++++++++++++++++++++++++++++++ scripts/test-ci-gate.sh | 44 +++++++++++ 2 files changed, 193 insertions(+) create mode 100755 scripts/mail-send-ci-gate.sh create mode 100755 scripts/test-ci-gate.sh diff --git a/scripts/mail-send-ci-gate.sh b/scripts/mail-send-ci-gate.sh new file mode 100755 index 0000000..a22c74f --- /dev/null +++ b/scripts/mail-send-ci-gate.sh @@ -0,0 +1,149 @@ +#!/bin/bash +# mail-send-ci-gate.sh - Gate for sending DONE mails based on CI status +# +# Usage: ./mail-send-ci-gate.sh "" +# +# The script checks if the mail body contains a DONE PR URL pattern. +# If it does, it verifies that all CI checks for that PR are successful. +# If not, it refuses to send the mail and exits with a non-zero status. +# +# Environment variables: +# TPS_AGENT_ID: The agent ID sending the mail (required for gh-as authentication). +# TPS_VAULT_KEY: The vault key for TPS (should be set in the environment). +# SKIP_CI_GATE: If set to "1", skip the CI check (for emergency override). +# +# The script sends the mail via the TPS CLI if the gate passes or if it's not a DONE mail. + +set -euo pipefail + +# Function to print error message and exit with error. +die() { + echo "ERROR: $*" >&2 + exit 1 +} + +# Check arguments. +if [ $# -lt 2 ]; then + die "Usage: $0 ''" +fi + +RECIPIENT="$1" +shift +BODY="$*" + +# Get the agent ID from the environment. +AGENT_ID="${TPS_AGENT_ID:-}" +if [ -z "$AGENT_ID" ]; then + die "Environment variable TPS_AGENT_ID is not set." +fi + +# Check if we should skip the CI gate. +if [ "${SKIP_CI_GATE:-}" = "1" ]; then + echo "Warning: SKIP_CI_GATE is set, bypassing CI check." >&2 + # We'll still send the mail below. + SKIP_CHECK=1 +else + SKIP_CHECK=0 +fi + +# Check if the body contains a DONE PR URL pattern. +# We look for the pattern: DONE +# We'll extract the URL using a simple regex: DONE (https?://[^ ]+) +# But note: the PR URL might be at the end of the line or have punctuation after. +# We'll use a more robust pattern: DONE where URL is a string that starts with http and contains a slash. +# We'll use grep to extract the first match. +if echo "$BODY" | grep -q -E 'DONE[[:space:]]+https?://[^[:space:]]+'; then + # Extract the first URL after DONE. + PR_URL=$(echo "$BODY" | grep -o -E 'DONE[[:space:]]+https?://[^[:space:]]+' | head -1 | sed -e 's/DONE[[:space:]]*//') + echo "Detected DONE PR URL: $PR_URL" >&2 + + if [ $SKIP_CHECK -eq 0 ]; then + # Check the CI status for this PR. + # We'll use gh-as to get the check runs. + # We'll use the command: gh-as pr checks --json state + # But note: the pr checks subcommand might not be available in all versions of gh. + # Alternatively, we can use: gh-as pr view --json statusCheckRollup + # We'll try the pr view method first. + + # We need to get the PR number from the URL to use with gh-as pr view. + # The URL is like: https://github.com/tpsdev-ai/flair/pull/310 + # We can extract the PR number from the URL. + PR_NUMBER=$(echo "$PR_URL" | grep -o -E '[0-9]+$') + if [ -z "$PR_NUMBER" ]; then + die "Could not extract PR number from URL: $PR_URL" + fi + + # We'll use the gh-as command to get the PR view with statusCheckRollup. + # We'll run: gh-as pr view --repo --json statusCheckRollup + # But we need to know the repo. We can extract it from the URL. + # The URL is: https://github.com///pull/ + # We'll extract the owner and repo. + REPO_PATH=$(echo "$PR_URL" | sed -e 's|https://github.com/||' -e 's|/pull/[0-9]*$||') + if [ -z "$REPO_PATH" ]; then + die "Could not extract repo path from URL: $PR_URL" + fi + + # Now run gh-as. + # We'll use the JSON output and check the statusCheckRollup. + # We'll look for any check that is not SUCCESS. + # We'll use jq to parse the JSON. + + # We'll run the command and capture the output. + # We'll set a timeout for the gh-as command. + CHECK_OUTPUT=$(gh-as "$AGENT_ID" pr view "$PR_NUMBER" --repo "$REPO_PATH" --json statusCheckRollup 2>/dev/null) || \ + die "Failed to fetch PR status for $PR_URL using gh-as" + + # Now parse the JSON to see if all checks are SUCCESS. + # We'll use jq to check the statusCheckRollup. We need to look at the checks array. + # The statusCheckRollup object has a state field that can be SUCCESS, PENDING, etc. + # But we also want to check individual checks? The statusCheckRollup gives an overall state. + # According to the GitHub API, the statusCheckRollup state is: + # - SUCCESS: All checks have completed successfully. + # - PENDING: Some checks are still pending. + # - FAILURE: At least one check has failed. + # - ERROR: There was an error running the check. + # - TIMEOUT: The check timed out. + # - CANCELLED: The check was cancelled. + # - NEUTRAL: The check is neither success nor failure. + # - SKIPPED: The check was skipped. + # - ACTION_REQUIRED: The check requires action. + # We'll consider the gate passed only if the state is SUCCESS. + # We'll also check if there are any pending checks? The spec says: if any check is FAILURE/PENDING, refuse. + # So we should also reject if the state is PENDING. + + # We'll extract the state from the statusCheckRollup. + STATE=$(echo "$CHECK_OUTPUT" | jq -r '.statusCheckRollup.state // empty') + if [ -z "$STATE" ]; then + die "Could not determine CI state for PR $PR_URL" + fi + + echo "CI state for PR $PR_URL: $STATE" >&2 + + if [ "$STATE" != "SUCCESS" ]; then + # Refuse to send. + echo "CI not green for PR $PR_URL (state: $STATE). Refusing to send DONE mail." >&2 + exit 1 + fi + + # If we get here, the CI is green. + echo "CI check passed for PR $PR_URL." >&2 + fi +else + # Not a DONE mail, we can send without CI check. + echo "No DONE PR URL found in mail body. Sending without CI check." >&2 +fi + +# If we reach here, we are allowed to send the mail. +# We'll send the mail using the TPS CLI. +# We'll use the TPS_VAULT_KEY and TPS_AGENT_ID from the environment. +# We'll run the command from the tps repo root. + +# We are already in the tps repo root. +# We'll use the bun command to run the TPS CLI. +# We'll send the mail. + +# We'll use the same environment variables that we have. +# We'll run: TPS_VAULT_KEY=$TPS_VAULT_KEY TPS_AGENT_ID=$TPS_AGENT_ID bun run packages/cli/dist/bin/tps.js mail send "$RECIPIENT" "$BODY" + +# We'll execute the command. +exec TPS_VAULT_KEY="$TPS_VAULT_KEY" TPS_AGENT_ID="$TPS_AGENT_ID" bun run packages/cli/dist/bin/tps.js mail send "$RECIPIENT" "$BODY" diff --git a/scripts/test-ci-gate.sh b/scripts/test-ci-gate.sh new file mode 100755 index 0000000..f3d131c --- /dev/null +++ b/scripts/test-ci-gate.sh @@ -0,0 +1,44 @@ +#!/usr/bin/env bash +# test-ci-gate.sh - Test script for the CG gate + +set -euo pipefail + +GATE_SCRIPT="scripts/mail-send-ci-gate.sh" + +echo "Testing CI gate..." + +# Test 1: Non-existent PR (should fail) +echo "Test 1: Non-existent PR (expecting failure)" +"$GATE_SCRIPT" test "DONE https://github.com/tpsdev-ai/cli/pull/999999" 2>&1 +result=$? +if [[ $result -ne 0 ]]; then + echo "Test 1 PASSED: gate correctly rejected non-existent PR (exit code $result)" +else + echo "Test 1 FAILED: gate should have rejected non-existent PR but didn't" + exit 1 +fi + +# Test 2: Known green PR (should pass) +echo "Test 2: Known green PR (expecting success)" +"$GATE_SCRIPT" test "DONE https://github.com/tpsdev-ai/cli/pull/271" 2>&1 +result=$? +if [[ $result -eq 0 ]]; then + echo "Test 2 PASSED: gate correctly allowed green PR (exit code $result)" +else + echo "Test 2 FAILED: gate should have allowed green PR but rejected it (exit code $result)" + exit 1 +fi + +# Test 3: Non-DONE message (should pass) +echo "Test 3: Non-DONE message (expecting success)" +"$GATE_SCRIPT" test "Just a regular message" 2>&1 +result=$? +if [[ $result -eq 0 ]]; then + echo "Test 3 PASSED: gate correctly allowed non-DONE message (exit code $result)" +else + echo "Test 3 FAILED: gate should have allowed non-DONE message but rejected it (exit code $result)" + exit 1 +fi + +echo "All tests passed!" +exit 0 From 2747301aba315bf6c4f2f0a11a82aabefa63993e Mon Sep 17 00:00:00 2001 From: Anvil Date: Thu, 30 Apr 2026 15:52:47 +0000 Subject: [PATCH 2/2] fix(test): capture gate exit without triggering set -e (Kern feedback on PR #272) Test 1 expects the gate to REJECT a non-existent PR with non-zero exit, but 'set -euo pipefail' was killing the test script on the gate's expected non-zero return before the result check could run. Replace inline rc=$? capture with the '|| result=$?' pattern so set -e treats the call as handled. Apply to all 3 tests for consistency. Also resolve TPS_AGENT_ID sourcing (default to 'anvil' if unset for test-harness use) and resolve GATE_SCRIPT path relative to the test script location instead of cwd-dependent. --- scripts/test-ci-gate.sh | 29 ++++++++++++++++------------- 1 file changed, 16 insertions(+), 13 deletions(-) diff --git a/scripts/test-ci-gate.sh b/scripts/test-ci-gate.sh index f3d131c..94172cb 100755 --- a/scripts/test-ci-gate.sh +++ b/scripts/test-ci-gate.sh @@ -1,27 +1,30 @@ #!/usr/bin/env bash -# test-ci-gate.sh - Test script for the CG gate +# test-ci-gate.sh - Test script for the CI gate -set -euo pipefail +set -uo pipefail -GATE_SCRIPT="scripts/mail-send-ci-gate.sh" +GATE_SCRIPT="$(dirname "$0")/mail-send-ci-gate.sh" + +# Gate uses TPS_AGENT_ID via gh-as; set a sensible default for the test harness. +export TPS_AGENT_ID="${TPS_AGENT_ID:-anvil}" echo "Testing CI gate..." -# Test 1: Non-existent PR (should fail) +# Test 1: Non-existent PR (gate should reject) echo "Test 1: Non-existent PR (expecting failure)" -"$GATE_SCRIPT" test "DONE https://github.com/tpsdev-ai/cli/pull/999999" 2>&1 -result=$? +result=0 +"$GATE_SCRIPT" test "DONE https://github.com/tpsdev-ai/cli/pull/999999" 2>&1 || result=$? if [[ $result -ne 0 ]]; then echo "Test 1 PASSED: gate correctly rejected non-existent PR (exit code $result)" else - echo "Test 1 FAILED: gate should have rejected non-existent PR but didn't" + echo "Test 1 FAILED: gate should have rejected non-existent PR but did not" exit 1 fi -# Test 2: Known green PR (should pass) +# Test 2: Known green PR (gate should allow) echo "Test 2: Known green PR (expecting success)" -"$GATE_SCRIPT" test "DONE https://github.com/tpsdev-ai/cli/pull/271" 2>&1 -result=$? +result=0 +"$GATE_SCRIPT" test "DONE https://github.com/tpsdev-ai/cli/pull/271" 2>&1 || result=$? if [[ $result -eq 0 ]]; then echo "Test 2 PASSED: gate correctly allowed green PR (exit code $result)" else @@ -29,10 +32,10 @@ else exit 1 fi -# Test 3: Non-DONE message (should pass) +# Test 3: Non-DONE message (gate should pass through) echo "Test 3: Non-DONE message (expecting success)" -"$GATE_SCRIPT" test "Just a regular message" 2>&1 -result=$? +result=0 +"$GATE_SCRIPT" test "Just a regular message" 2>&1 || result=$? if [[ $result -eq 0 ]]; then echo "Test 3 PASSED: gate correctly allowed non-DONE message (exit code $result)" else