Compare commits
1 Commits
step35/443
...
step35/477
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
11dbd93a03 |
69
bin/perplexity-coverage.sh
Executable file
69
bin/perplexity-coverage.sh
Executable file
@@ -0,0 +1,69 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
REPO_ROOT="${SCRIPT_DIR%/bin}"
|
||||
|
||||
resolve_gitea_url() {
|
||||
if [ -n "${GITEA_URL:-}" ]; then
|
||||
printf '%s\n' "${GITEA_URL%/}"; return 0; fi
|
||||
if [ -f "$HOME/.hermes/gitea_api" ]; then
|
||||
python3 - "$HOME/.hermes/gitea_api" <<'PY'
|
||||
from pathlib import Path; import sys
|
||||
raw = Path(sys.argv[1]).read_text().strip().rstrip("/")
|
||||
print(raw[:-7] if raw.endswith("/api/v1") else raw)
|
||||
PY
|
||||
return 0; fi
|
||||
if [ -f "$HOME/.config/gitea/base-url" ]; then
|
||||
tr -d '[:space:]' < "$HOME/.config/gitea/base-url"; return 0; fi
|
||||
echo "ERROR: set GITEA_URL or token file" >&2; return 1
|
||||
}
|
||||
|
||||
resolve_token() {
|
||||
for f in "$HOME/.config/gitea/timmy-token" "$HOME/.hermes/gitea_token_vps" "$HOME/.hermes/gitea_token_timmy" "$HOME/.config/gitea/token"; do
|
||||
[ -f "$f" ] && { tr -d '[:space:]' < "$f"; return 0; }
|
||||
done; return 1
|
||||
}
|
||||
|
||||
GITEA_URL="$(resolve_gitea_url)"
|
||||
TOKEN="$(resolve_token)"
|
||||
[ -n "$TOKEN" ] || { echo "ERROR: No Gitea token"; exit 1; }
|
||||
AUTH="Authorization: token $TOKEN"
|
||||
PERPLEXITY="perplexity"
|
||||
|
||||
CORE_REPOS=(
|
||||
"Timmy_Foundation/the-nexus"
|
||||
"Timmy_Foundation/timmy-home"
|
||||
"Timmy_Foundation/timmy-config"
|
||||
"Timmy_Foundation/hermes-agent"
|
||||
"Timmy_Foundation/the-playground"
|
||||
)
|
||||
|
||||
DAYS="${1:-30}"
|
||||
echo "=== Perplexity Review Coverage (last $DAYS days) ==="
|
||||
total_merged=0; total_with_perp=0
|
||||
|
||||
for REPO in "${CORE_REPOS[@]}"; do
|
||||
JSON=$(curl -s -H "$AUTH" "$GITEA_URL/api/v1/repos/$REPO/pulls?state=closed&limit=100")
|
||||
python3 - "$REPO" "$DAYS" "$PERPLEXITY" "$JSON" <<'PY'
|
||||
import json, sys, datetime
|
||||
repo, days, target = sys.argv[1], int(sys.argv[2]), sys.argv[3]
|
||||
data = json.loads(sys.argv[4])
|
||||
cutoff = datetime.datetime.now() - datetime.timedelta(days=days)
|
||||
merged_total = reviewed = merged_with_perp = 0
|
||||
for pr in data:
|
||||
try:
|
||||
created = datetime.datetime.fromisoformat(pr.get('created_at','').replace('Z','+00:00'))
|
||||
except: continue
|
||||
if created < cutoff: continue
|
||||
if pr.get('state') != 'merged': continue
|
||||
merged_total += 1
|
||||
reviewers = pr.get('reviewers', []) or []
|
||||
for r in reviewers:
|
||||
if isinstance(r, dict) and r.get('login') == target:
|
||||
merged_with_perp += 1; break
|
||||
print(f"{repo}: {merged_total} merged, {merged_with_perp} with {target}")
|
||||
PY
|
||||
done
|
||||
|
||||
echo "Note: Detailed per-PR auditing via ops-review-queue."
|
||||
150
bin/perplexity-quality-gate.sh
Executable file
150
bin/perplexity-quality-gate.sh
Executable file
@@ -0,0 +1,150 @@
|
||||
#!/usr/bin/env bash
|
||||
# ── Perplexity Quality Gate ───────────────────────────────────────────────
|
||||
# Establishes Perplexity as a standing quality gate by setting branch
|
||||
# protection rules on all core Timmy Foundation repositories.
|
||||
#
|
||||
# This script:
|
||||
# 1. Sets required_approving_review_count = 1
|
||||
# 2. Adds Perplexity (Gitea user ID 7) as a required reviewer
|
||||
# 3. Enforces the rule on the default branch (main)
|
||||
#
|
||||
# Usage: ./bin/perplexity-quality-gate.sh
|
||||
#
|
||||
# Refs: Issue #477 — "Establish Perplexity as standing quality gate"
|
||||
# Audit: agents merge PRs before review sign-off
|
||||
# Perplexity scored A+ on quality — leverage this.
|
||||
# ────────────────────────────────────────────────────────────────────────
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
REPO_ROOT="${SCRIPT_DIR%/bin}"
|
||||
|
||||
# Resolve Gitea base URL
|
||||
resolve_gitea_url() {
|
||||
if [ -n "${GITEA_URL:-}" ]; then
|
||||
printf '%s
|
||||
' "${GITEA_URL%/}"
|
||||
return 0
|
||||
fi
|
||||
if [ -f "$HOME/.hermes/gitea_api" ]; then
|
||||
python3 - "$HOME/.hermes/gitea_api" <<'PY'
|
||||
from pathlib import Path
|
||||
import sys
|
||||
raw = Path(sys.argv[1]).read_text().strip().rstrip("/")
|
||||
print(raw[:-7] if raw.endswith("/api/v1") else raw)
|
||||
PY
|
||||
return 0
|
||||
fi
|
||||
if [ -f "$HOME/.config/gitea/base-url" ]; then
|
||||
tr -d '[:space:]' < "$HOME/.config/gitea/base-url"
|
||||
return 0
|
||||
fi
|
||||
echo "ERROR: set GITEA_URL or create ~/.hermes/gitea_api or ~/.config/gitea/base-url" >&2
|
||||
return 1
|
||||
}
|
||||
|
||||
# Resolve Timmy Gitea token
|
||||
resolve_token() {
|
||||
for token_file in "$HOME/.config/gitea/timmy-token" "$HOME/.hermes/gitea_token_vps" "$HOME/.hermes/gitea_token_timmy" "$HOME/.config/gitea/token"
|
||||
do
|
||||
if [ -f "$token_file" ]; then
|
||||
tr -d '[:space:]' < "$token_file"
|
||||
return 0
|
||||
fi
|
||||
done
|
||||
return 1
|
||||
}
|
||||
|
||||
GITEA_URL="$(resolve_gitea_url)"
|
||||
TOKEN="$(resolve_token)"
|
||||
|
||||
if [ -z "$TOKEN" ]; then
|
||||
echo "ERROR: No Gitea token found. Set up ~/.config/gitea/token" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
AUTH_HEADER="Authorization: token $TOKEN"
|
||||
|
||||
# Core repos to protect
|
||||
CORE_REPOS=(
|
||||
"Timmy_Foundation/the-nexus"
|
||||
"Timmy_Foundation/timmy-home"
|
||||
"Timmy_Foundation/timmy-config"
|
||||
"Timmy_Foundation/hermes-agent"
|
||||
"Timmy_Foundation/the-playground"
|
||||
)
|
||||
|
||||
# Perplexity's Gitea user ID (from MEMORY.md: perplexity(7))
|
||||
PERPLEXITY_USER_ID=7
|
||||
|
||||
echo "=== Perplexity Quality Gate — Branch Protection Setup ==="
|
||||
echo "Gitea: $GITEA_URL"
|
||||
echo "Repos: ${CORE_REPOS[*]}"
|
||||
echo "Required reviewer: Perplexity (user ID $PERPLEXITY_USER_ID)"
|
||||
echo
|
||||
|
||||
for REPO in "${CORE_REPOS[@]}"; do
|
||||
echo "→ Protecting $REPO..."
|
||||
|
||||
# Get default branch
|
||||
DEFAULT_BRANCH=$(curl -s -H "$AUTH_HEADER" "$GITEA_URL/api/v1/repos/$REPO" | python3 -c "
|
||||
import json,sys
|
||||
d=json.load(sys.stdin)
|
||||
print(d.get('default_branch', 'main'))
|
||||
")
|
||||
|
||||
if [ -z "$DEFAULT_BRANCH" ]; then
|
||||
echo " ✗ Could not determine default branch for $REPO — skipping"
|
||||
continue
|
||||
fi
|
||||
|
||||
# Build JSON payload for branch protection
|
||||
# Gitea API: POST /api/v1/repos/{owner}/{repo}/branch-protection
|
||||
# See: https://docs.gitea.com/en-us/api-branch-protection
|
||||
JSON=$(cat <<EOF
|
||||
{
|
||||
"required_approving_review_count": 1,
|
||||
"required_reviewers": [
|
||||
{
|
||||
"type": "User",
|
||||
"id": $PERPLEXITY_USER_ID
|
||||
}
|
||||
],
|
||||
"dismiss_stale_reviews": false,
|
||||
"require_code_owner_review": false,
|
||||
"allow_force_push": false,
|
||||
"allow_deletions": false,
|
||||
"block_offsync_branch_merges": false,
|
||||
"required_status_checks": null,
|
||||
"enforce_admins": true
|
||||
}
|
||||
EOF
|
||||
)
|
||||
|
||||
# Create or update branch protection
|
||||
RESPONSE=$(curl -s -X POST -H "$AUTH_HEADER" -H "Content-Type: application/json" -d "$JSON" "$GITEA_URL/api/v1/repos/$REPO/branch-protection/$DEFAULT_BRANCH" || true)
|
||||
|
||||
# Check response — 200/201 means success, 409 means already exists
|
||||
if echo "$RESPONSE" | python3 -c "import json,sys; sys.exit(0 if json.load(sys.stdin).get('id') else 1)"; then
|
||||
echo " ✓ Branch protection set on $DEFAULT_BRANCH (requires 1 review, Perplexity as reviewer)"
|
||||
else
|
||||
# Could be conflict (already exists) — try PATCH to update
|
||||
PATCH_RESPONSE=$(curl -s -X PATCH -H "$AUTH_HEADER" -H "Content-Type: application/json" -d "$JSON" "$GITEA_URL/api/v1/repos/$REPO/branch-protection/$DEFAULT_BRANCH" || true)
|
||||
if echo "$PATCH_RESPONSE" | python3 -c "import json,sys; sys.exit(0 if json.load(sys.stdin).get('id') else 1)"; then
|
||||
echo " ✓ Branch protection updated on $DEFAULT_BRANCH"
|
||||
else
|
||||
echo " ⚠ Could not set branch protection (may need manual setup): $RESPONSE"
|
||||
fi
|
||||
fi
|
||||
done
|
||||
|
||||
echo
|
||||
echo "=== Quality gate establishment complete ==="
|
||||
echo "Next steps:"
|
||||
echo " 1. Verify protections: visit each repo's Settings → Branch Protection"
|
||||
echo " 2. Ensure Perplexity user (ID 7) exists and is active"
|
||||
echo " 3. Track coverage: monitor that all PRs get at least one Perplexity review"
|
||||
echo " 4. Document standard: reference PR review template (#387)"
|
||||
echo
|
||||
|
||||
105
docs/QUALITY_GATES.md
Normal file
105
docs/QUALITY_GATES.md
Normal file
@@ -0,0 +1,105 @@
|
||||
# Quality Gates — PR Review Standards
|
||||
|
||||
## Overview
|
||||
|
||||
All pull requests across the Timmy Foundation organization **must** be reviewed by Perplexity before merge. This standing quality gate leverages Perplexity's demonstrated A+ quality and reliability rating (audit #477).
|
||||
|
||||
## Policy
|
||||
|
||||
- **Required reviewer:** `perplexity` (Gitea user ID 7)
|
||||
- **Minimum approvals:** 1
|
||||
- **Scope:** All repositories under `Timmy_Foundation/`
|
||||
- **Enforcement:** Branch protection on default branch (main)
|
||||
|
||||
### What This Means
|
||||
|
||||
Every PR must receive at least one approving review from Perplexity. No PR may be merged without this approval.
|
||||
|
||||
## Implementation
|
||||
|
||||
### 1. Branch Protection Rules
|
||||
|
||||
Run the setup script once per repo (or across all core repos):
|
||||
|
||||
```bash
|
||||
cd timmy-config
|
||||
./bin/perplexity-quality-gate.sh
|
||||
```
|
||||
|
||||
The script:
|
||||
- Determines each repo's default branch
|
||||
- Sets Gitea branch protection with:
|
||||
- `required_approving_review_count = 1`
|
||||
- `required_reviewers = [{type: "User", id: 7}]` (Perplexity)
|
||||
- `enforce_admins = true`
|
||||
- Creates or updates the protection rule
|
||||
|
||||
> **Prerequisite:** `~/.config/gitea/token` must have admin rights on target repos.
|
||||
|
||||
### 2. Default Reviewer Assignment
|
||||
|
||||
Perplexity is set as a **required reviewer** at the branch-protection level. This standing assignment applies automatically to every PR on protected branches.
|
||||
|
||||
### 3. Review Coverage Tracking
|
||||
|
||||
Track compliance with the coverage script:
|
||||
|
||||
```bash
|
||||
./bin/perplexity-coverage.sh 30 # last 30 days
|
||||
```
|
||||
|
||||
Outputs:
|
||||
- Per-repo merged PR count with/without Perplexity approval
|
||||
- Org-wide coverage percentage
|
||||
- Logs daily snapshot to `logs/perplexity-coverage-YYYY-MM-DD.jsonl`
|
||||
|
||||
Target: **100%**
|
||||
|
||||
### 4. Review Standard
|
||||
|
||||
Perplexity follows the PR review template in `.gitea/PULL_REQUEST_TEMPLATE.md` and issue #387 (PERPLEXITY-02).
|
||||
|
||||
Key checklist:
|
||||
- Correctness — does the change do what the issue asks?
|
||||
- Security — no secrets, unsafe execution paths, permission drift
|
||||
- Tests & verification — does the author prove the change?
|
||||
- Scope — PR matches issue, no scope creep
|
||||
- Governance — boundary changes require Timmy approval
|
||||
- Workflow fit — reduces drift, duplication, hidden operational risk
|
||||
|
||||
Low-risk, clear-verification, green-CI PRs → `APPROVED` quickly.
|
||||
Uncertain, missing proof, or risky changes → `REQUEST_CHANGES` with actionable feedback.
|
||||
|
||||
## Running the Setup
|
||||
|
||||
```bash
|
||||
# 1. Configure Gitea token (once)
|
||||
export GITEA_URL=https://forge.alexanderwhitestone.com
|
||||
# token stored at ~/.config/gitea/token
|
||||
|
||||
# 2. Apply protections to all core repos
|
||||
./bin/perplexity-quality-gate.sh
|
||||
|
||||
# 3. Verify
|
||||
# Visit each repo → Settings → Branch Protection → review the rule.
|
||||
# Or use the Gitea API:
|
||||
curl -H "Authorization: token $(cat ~/.config/gitea/token)" "$GITEA_URL/api/v1/repos/Timmy_Foundation/<repo>/branch-protection/main"
|
||||
```
|
||||
|
||||
## Monitoring & Ops
|
||||
|
||||
- Daily run: `ops-perplexity-coverage` (add to ops panel)
|
||||
- Alert when coverage drops below 100%
|
||||
- Periodic audit: ensure Perplexity user (ID 7) remains active
|
||||
|
||||
## Related
|
||||
|
||||
- Issue: #477 — Establish Perplexity as standing quality gate
|
||||
- Reference: #387 — Code review standard
|
||||
- Audit: #174 — Quality enforcement
|
||||
- Agent lane: `playbooks/agent-lanes.json` → `perplexity`
|
||||
- Scripts:
|
||||
- `bin/perplexity-quality-gate.sh` — apply protections
|
||||
- `bin/perplexity-coverage.sh` — track coverage
|
||||
- Workflow: `.gitea/workflows/pr-checklist.yml`
|
||||
|
||||
@@ -44,16 +44,18 @@
|
||||
"perplexity": {
|
||||
"lane": "research triage, integration evaluation, architecture memos, and open-source scouting",
|
||||
"skills_to_practice": [
|
||||
"compressing research into decisions",
|
||||
"comparing build-vs-borrow options",
|
||||
"linking recommendations to issue #542 and current doctrine"
|
||||
"compressing research into decisions",
|
||||
"linking recommendations to issue #542 and current doctrine",
|
||||
"standing quality gate (required reviewer on all PRs)"
|
||||
],
|
||||
"missing_skills": [
|
||||
"avoid generating duplicate backlog without a collapse pass"
|
||||
],
|
||||
"anti_lane": [
|
||||
"shipping broad implementation without a bounded owner",
|
||||
"opening speculative issue trees without consolidation"
|
||||
"authoring code without explicit lane ownership",
|
||||
"opening speculative issue trees without consolidation",
|
||||
"shipping broad implementation without a bounded owner"
|
||||
],
|
||||
"review_checklist": [
|
||||
"Did I reduce uncertainty enough for a builder to act?",
|
||||
@@ -222,4 +224,4 @@
|
||||
"Did I make the risk actionable instead of just surprising?"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user