Compare commits
1 Commits
step35/595
...
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": {
|
"perplexity": {
|
||||||
"lane": "research triage, integration evaluation, architecture memos, and open-source scouting",
|
"lane": "research triage, integration evaluation, architecture memos, and open-source scouting",
|
||||||
"skills_to_practice": [
|
"skills_to_practice": [
|
||||||
"compressing research into decisions",
|
|
||||||
"comparing build-vs-borrow options",
|
"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": [
|
"missing_skills": [
|
||||||
"avoid generating duplicate backlog without a collapse pass"
|
"avoid generating duplicate backlog without a collapse pass"
|
||||||
],
|
],
|
||||||
"anti_lane": [
|
"anti_lane": [
|
||||||
"shipping broad implementation without a bounded owner",
|
"authoring code without explicit lane ownership",
|
||||||
"opening speculative issue trees without consolidation"
|
"opening speculative issue trees without consolidation",
|
||||||
|
"shipping broad implementation without a bounded owner"
|
||||||
],
|
],
|
||||||
"review_checklist": [
|
"review_checklist": [
|
||||||
"Did I reduce uncertainty enough for a builder to act?",
|
"Did I reduce uncertainty enough for a builder to act?",
|
||||||
|
|||||||
Reference in New Issue
Block a user