mirror of
https://github.com/Lissy93/awesome-privacy.git
synced 2026-03-11 08:55:33 +00:00
Updates contributing and automated checks to ensure project age
This commit is contained in:
parent
843a49181f
commit
ce3a59e139
7 changed files with 196 additions and 38 deletions
13
.github/CONTRIBUTING.md
vendored
13
.github/CONTRIBUTING.md
vendored
|
|
@ -62,6 +62,10 @@ For software to be included in this list, it must meet the following requirement
|
|||
- A stable (non-alpha/beta) release is required at a minimum
|
||||
- Must be accessible to the general public, and not just a select group of people
|
||||
- If technical knowledge is required to run it, the software must be well documented
|
||||
- **Mature**
|
||||
- Software needs to have a proven track record of commitment to maintenance
|
||||
- Repositories must not be newly created, and the first stable release older than 4 months
|
||||
- Projects primarily written with AI or vibe coded are not suitable for listing here
|
||||
|
||||
_There may be some exceptions, but these would need to be fully justified, reviewed
|
||||
by the community, and the drawbacks / anti-features must be clearly listed along-side the software.
|
||||
|
|
@ -79,19 +83,18 @@ Your pull request must follow these requirements. Failure to do so, might result
|
|||
- You must respond to any comments or requests for changes in a timely manner, 14 days maximum
|
||||
- Write short but descriptive git commit messages, under 50 characters. This must be in the format of `Adds [software-name] to [section-name]`. Your PR will be rejected if you name it `Updates README.md`
|
||||
- Only include a single addition / amendment / removal, per pull request
|
||||
- You must complete each of the sections in the pull request template. Do not delete it!
|
||||
- You must complete each of the sections in the [pull request template](https://github.com/Lissy93/awesome-privacy/blob/main/.github/PULL_REQUEST_TEMPLATE.md). Do not delete it!
|
||||
- Where applicable, include links to supporting material for your addition: git repo, docs, recent security audits, etc. This will make researching it much easier for reviewers
|
||||
- While adding new software to the list, don't make your entry read like an advert. Be objective, and include drawbacks as well as strengths
|
||||
- Your entry should be added at the bottom of the appropriate category, unless otherwise requested
|
||||
- If there are other pull requests open, please help review them before submitting yours
|
||||
- A pull request must receive multiple approval reviews before it can be merged
|
||||
- You must be transparent about your affiliation with a product or service that you are adding. It's totally okay to submit your own projects as additions (providing they meet the requirements), but if you don't declare your association with that project then there becomes a clear conflict of interest
|
||||
- You must adhere to the Contributor Covenant Code of Conduct
|
||||
- You must adhere to the [Contributor Covenant Code of Conduct](https://github.com/Lissy93/awesome-privacy?tab=coc-ov-file#contributor-covenant-code-of-conduct)
|
||||
- Don't open a Draft / WIP pull request while you work on the guidelines. A pull request should be 100% ready and should adhere to all the above guidelines when you open it
|
||||
- Your changes must be correctly spelled, and with good grammar
|
||||
- Your changes must be correctly formatted, in valid yaml and markdown
|
||||
- The addition title must be a link the project
|
||||
- The addition description must be no less than 50, and no more than 250 characters, keep it clear and to the point
|
||||
- If there are other pull requests open, please help review them before submitting yours
|
||||
- A pull request must receive multiple approval reviews before it can be merged
|
||||
|
||||
---
|
||||
|
||||
|
|
|
|||
10
.github/workflows/pr-check.yml
vendored
10
.github/workflows/pr-check.yml
vendored
|
|
@ -82,7 +82,9 @@ jobs:
|
|||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: pr-diff
|
||||
path: /tmp/pr-diff.json
|
||||
path: |
|
||||
/tmp/pr-diff.json
|
||||
/tmp/pr-diff-summary.md
|
||||
if-no-files-found: ignore
|
||||
- name: Upload findings
|
||||
if: always()
|
||||
|
|
@ -142,6 +144,12 @@ jobs:
|
|||
path: /tmp/artifacts
|
||||
merge-multiple: true
|
||||
continue-on-error: true
|
||||
- name: Download diff data
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: pr-diff
|
||||
path: /tmp/artifacts
|
||||
continue-on-error: true
|
||||
- name: Format comment
|
||||
env:
|
||||
PR_USER: ${{ github.event.pull_request.user.login }}
|
||||
|
|
|
|||
1
lib/.gitignore
vendored
Normal file
1
lib/.gitignore
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
__pycache__
|
||||
|
|
@ -131,7 +131,7 @@ def check_open_source(diff):
|
|||
"""Return a finding if an added service has openSource missing or not true."""
|
||||
for svc in diff.get("services", {}).get("added", []):
|
||||
fields = svc.get("fields", {})
|
||||
if fields.get("openSource") is not True:
|
||||
if fields.get("openSource") is not True and not fields.get("github"):
|
||||
return OPENSOURCE_MSG
|
||||
return None
|
||||
|
||||
|
|
@ -221,7 +221,7 @@ def check_description_length(diff):
|
|||
for svc in diff.get("services", {}).get("added", []):
|
||||
desc = svc.get("fields", {}).get("description", "")
|
||||
length = len(desc)
|
||||
if length < 50 or length > 250:
|
||||
if length < 50 or length > 280:
|
||||
return DESC_LENGTH_MSG.format(length=length)
|
||||
return None
|
||||
|
||||
|
|
|
|||
|
|
@ -17,6 +17,12 @@ TIMEOUT = 10
|
|||
USER_AGENT = "awesome-privacy-ci/1.0"
|
||||
MIN_STARS = 100
|
||||
INACTIVE_DAYS = 90
|
||||
MIN_AGE_DAYS = 120
|
||||
AI_COMMIT_THRESHOLD = 5
|
||||
AI_BOT_AUTHORS = [
|
||||
"noreply@anthropic.com",
|
||||
"devin-ai-integration[bot]",
|
||||
]
|
||||
|
||||
LINK_MSG = (
|
||||
"Our automated checks were unable to verify the link(s) you included"
|
||||
|
|
@ -35,6 +41,29 @@ ACTIVITY_MSG = (
|
|||
"Please confirm that the project you are adding is actively maintained,"
|
||||
" as it looks to not have had any recent updates in the past 3 months."
|
||||
)
|
||||
MATURITY_MSG = (
|
||||
"This project appears to be quite new (created less than 4 months ago)."
|
||||
" Repositories should have a proven track record before listing."
|
||||
)
|
||||
AI_CODE_MSG = (
|
||||
"This project appears to contain AI-generated code."
|
||||
" Additional care will be needed when reviewing the submission."
|
||||
)
|
||||
FORK_MSG = (
|
||||
"The GitHub link in this listing is a fork."
|
||||
" Please confirm it's the correct (and actively maintained) repository"
|
||||
)
|
||||
LICENSE_MSG = (
|
||||
"There doesn't appear to be a license included in the project's GitHub repo"
|
||||
)
|
||||
ARCHIVED_MSG = (
|
||||
"The GitHub project linked has been archived."
|
||||
" Additions must be actively maintained."
|
||||
)
|
||||
SECURITY_MSG = (
|
||||
"This project has open security vulnerabilities (critical or high severity)"
|
||||
" flagged by GitHub Dependabot. Please verify these have been addressed"
|
||||
)
|
||||
|
||||
|
||||
def load_diff(path):
|
||||
|
|
@ -154,6 +183,51 @@ def check_links(diff, head):
|
|||
return None
|
||||
|
||||
|
||||
def check_ai_commits(owner, repo, token):
|
||||
"""Return AI_CODE_MSG if recent commits contain significant AI bot activity."""
|
||||
try:
|
||||
headers = {"Accept": "application/vnd.github.v3+json", "User-Agent": USER_AGENT}
|
||||
if token:
|
||||
headers["Authorization"] = f"token {token}"
|
||||
resp = requests.get(
|
||||
f"https://api.github.com/repos/{owner}/{repo}/commits",
|
||||
headers=headers, timeout=TIMEOUT, params={"per_page": 100},
|
||||
)
|
||||
if resp.status_code != 200:
|
||||
return None
|
||||
bot_set = {a.lower() for a in AI_BOT_AUTHORS}
|
||||
count = 0
|
||||
for commit in resp.json():
|
||||
author = commit.get("commit", {}).get("author", {})
|
||||
email = (author.get("email") or "").lower()
|
||||
name = (author.get("name") or "").lower()
|
||||
if email in bot_set or name in bot_set:
|
||||
count += 1
|
||||
if count >= AI_COMMIT_THRESHOLD:
|
||||
return AI_CODE_MSG
|
||||
except Exception:
|
||||
pass
|
||||
return None
|
||||
|
||||
|
||||
def check_security_alerts(owner, repo, token):
|
||||
"""Return SECURITY_MSG if the repo has open critical/high Dependabot alerts."""
|
||||
try:
|
||||
headers = {"Accept": "application/vnd.github.v3+json", "User-Agent": USER_AGENT}
|
||||
if token:
|
||||
headers["Authorization"] = f"token {token}"
|
||||
resp = requests.get(
|
||||
f"https://api.github.com/repos/{owner}/{repo}/dependabot/alerts",
|
||||
headers=headers, timeout=TIMEOUT,
|
||||
params={"state": "open", "severity": "critical,high", "per_page": 1},
|
||||
)
|
||||
if resp.status_code == 200 and resp.json():
|
||||
return SECURITY_MSG
|
||||
except Exception:
|
||||
pass
|
||||
return None
|
||||
|
||||
|
||||
def check_repo_signals(diff, pr_user, token):
|
||||
"""Check GitHub repo author match, stars, and activity for added services."""
|
||||
findings = []
|
||||
|
|
@ -185,6 +259,15 @@ def check_repo_signals(diff, pr_user, token):
|
|||
if stars < MIN_STARS and STARS_MSG not in findings:
|
||||
findings.append(STARS_MSG)
|
||||
|
||||
if data.get("fork") and FORK_MSG not in findings:
|
||||
findings.append(FORK_MSG)
|
||||
|
||||
if not data.get("license") and LICENSE_MSG not in findings:
|
||||
findings.append(LICENSE_MSG)
|
||||
|
||||
if data.get("archived") and ARCHIVED_MSG not in findings:
|
||||
findings.append(ARCHIVED_MSG)
|
||||
|
||||
pushed = data.get("pushed_at")
|
||||
if pushed and ACTIVITY_MSG not in findings:
|
||||
try:
|
||||
|
|
@ -195,6 +278,26 @@ def check_repo_signals(diff, pr_user, token):
|
|||
except Exception:
|
||||
pass
|
||||
|
||||
created = data.get("created_at")
|
||||
if created and MATURITY_MSG not in findings:
|
||||
try:
|
||||
created_dt = datetime.fromisoformat(created.replace("Z", "+00:00"))
|
||||
now = datetime.now(timezone.utc)
|
||||
if (now - created_dt).days < MIN_AGE_DAYS:
|
||||
findings.append(MATURITY_MSG)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
if AI_CODE_MSG not in findings:
|
||||
finding = check_ai_commits(owner, repo, token)
|
||||
if finding:
|
||||
findings.append(finding)
|
||||
|
||||
if SECURITY_MSG not in findings:
|
||||
finding = check_security_alerts(owner, repo, token)
|
||||
if finding:
|
||||
findings.append(finding)
|
||||
|
||||
return findings
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ import yaml
|
|||
PROJECT_ROOT = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||
DATA_PATH = os.path.join(PROJECT_ROOT, "awesome-privacy.yml")
|
||||
DIFF_OUTPUT_PATH = "/tmp/pr-diff.json"
|
||||
SUMMARY_OUTPUT_PATH = "/tmp/pr-diff-summary.md"
|
||||
|
||||
EXIT_PASS = 0
|
||||
EXIT_RULE_VIOLATION = 1
|
||||
|
|
@ -112,13 +113,8 @@ def fmt_path(key):
|
|||
return " → ".join(key) if isinstance(key, tuple) else key
|
||||
|
||||
|
||||
def write_step_summary(diff_result):
|
||||
"""Write a bullet-point Markdown summary to $GITHUB_STEP_SUMMARY."""
|
||||
summary_file = os.environ.get("GITHUB_STEP_SUMMARY")
|
||||
if not summary_file:
|
||||
return
|
||||
|
||||
lines = ["## YAML Diff Analysis\n"]
|
||||
def format_diff_bullets(diff_result):
|
||||
"""Build bullet-point lines summarizing all changes. Returns list of strings or empty list."""
|
||||
bullets = []
|
||||
|
||||
for svc in diff_result["services"]["added"]:
|
||||
|
|
@ -149,6 +145,25 @@ def write_step_summary(diff_result):
|
|||
f"in {dup['category']} → {dup['section']}"
|
||||
)
|
||||
|
||||
return bullets
|
||||
|
||||
|
||||
def write_diff_summary(diff_result):
|
||||
"""Write the bullet-point summary to a file for downstream consumers."""
|
||||
bullets = format_diff_bullets(diff_result)
|
||||
if bullets:
|
||||
with open(SUMMARY_OUTPUT_PATH, "w") as f:
|
||||
f.write("\n".join(bullets) + "\n")
|
||||
|
||||
|
||||
def write_step_summary(diff_result):
|
||||
"""Write a bullet-point Markdown summary to $GITHUB_STEP_SUMMARY."""
|
||||
summary_file = os.environ.get("GITHUB_STEP_SUMMARY")
|
||||
if not summary_file:
|
||||
return
|
||||
|
||||
bullets = format_diff_bullets(diff_result)
|
||||
lines = ["## YAML Diff Analysis\n"]
|
||||
if bullets:
|
||||
lines.extend(bullets)
|
||||
else:
|
||||
|
|
@ -213,6 +228,7 @@ def main():
|
|||
|
||||
write_github_output("has_service_changes", str(bool(added or removed or modified)).lower())
|
||||
write_step_summary(diff_result)
|
||||
write_diff_summary(diff_result)
|
||||
|
||||
added_count = len(added)
|
||||
if added_count > 1:
|
||||
|
|
|
|||
|
|
@ -7,19 +7,9 @@ import sys
|
|||
ARTIFACTS_DIR = "/tmp/artifacts"
|
||||
OUTPUT_DIR = "/tmp/pr-meta"
|
||||
|
||||
CONTRIBUTING = "https://github.com/Lissy93/awesome-privacy/blob/main/.github/CONTRIBUTING.md"
|
||||
|
||||
COMMENT_TEMPLATE = """<!-- pr-check-bot -->
|
||||
Hello @{user}
|
||||
|
||||
Thank you for contributing to Awesome Privacy! We will review your PR shortly. In the meantime, please ensure that your submission is inline with our guidelines in our [Contributing Requirements]({contributing}).
|
||||
|
||||
Looks like there could be some issues in your PR. Please double check that:
|
||||
|
||||
{findings}
|
||||
|
||||
> [!NOTE]
|
||||
> I am a bot, and sometimes make mistakes in my suggestions. But a human will review your submission shortly!"""
|
||||
REPO_URL = "https://github.com/Lissy93/awesome-privacy"
|
||||
CONTRIBUTING = f"{REPO_URL}/blob/main/.github/CONTRIBUTING.md"
|
||||
DIFF_SUMMARY_PATH = os.path.join(ARTIFACTS_DIR, "pr-diff-summary.md")
|
||||
|
||||
|
||||
def load_findings(filename):
|
||||
|
|
@ -41,12 +31,49 @@ def collect_findings():
|
|||
return all_findings
|
||||
|
||||
|
||||
def format_comment(findings, user):
|
||||
"""Build the markdown comment from findings."""
|
||||
bullet_list = "\n".join(f"- {f}" for f in findings)
|
||||
return COMMENT_TEMPLATE.format(
|
||||
user=user, contributing=CONTRIBUTING, findings=bullet_list,
|
||||
)
|
||||
def load_diff_summary():
|
||||
"""Load the pre-formatted diff summary, or None if unavailable."""
|
||||
try:
|
||||
with open(DIFF_SUMMARY_PATH) as f:
|
||||
content = f.read().strip()
|
||||
return content if content else None
|
||||
except Exception:
|
||||
return None
|
||||
|
||||
|
||||
def format_comment(findings, user, changes_summary, run_id):
|
||||
"""Build the markdown comment."""
|
||||
parts = [
|
||||
f"<!-- pr-check-bot -->\nHello @{user}\n",
|
||||
f"Thank you for contributing to Awesome Privacy! We will review your "
|
||||
f"submission shortly. In the meantime, please ensure all changes are "
|
||||
f"correct and inline with our [Contributing Requirements]({CONTRIBUTING}).\n",
|
||||
]
|
||||
|
||||
if findings:
|
||||
bullet_list = "\n".join(f"- {f}" for f in findings)
|
||||
parts.append(
|
||||
f"Our automated checks detected some issues:\n\n{bullet_list}\n\n"
|
||||
f"> [!NOTE]\n"
|
||||
f"> I am a bot, and sometimes make mistakes in my suggestions. "
|
||||
f"But a human will review your submission shortly!"
|
||||
)
|
||||
else:
|
||||
parts.append("> ✅ All our automated checks have passed.")
|
||||
|
||||
if changes_summary:
|
||||
parts.append(
|
||||
f"<details><summary>Summary of Changes:</summary>\n\n"
|
||||
f"{changes_summary}\n</details>"
|
||||
)
|
||||
|
||||
if run_id:
|
||||
parts.append(
|
||||
f'<sup>For full details, please see workflow run '
|
||||
f'<a href="{REPO_URL}/actions/runs/{run_id}">{run_id}</a></sup>'
|
||||
)
|
||||
|
||||
return "\n\n".join(parts) + "\n"
|
||||
|
||||
|
||||
def write_step_summary(findings):
|
||||
|
|
@ -81,12 +108,12 @@ def main():
|
|||
f.write(run_id)
|
||||
|
||||
findings = collect_findings()
|
||||
changes_summary = load_diff_summary()
|
||||
write_step_summary(findings)
|
||||
|
||||
if findings:
|
||||
comment = format_comment(findings, user)
|
||||
with open(os.path.join(OUTPUT_DIR, "comment.md"), "w") as f:
|
||||
f.write(comment)
|
||||
comment = format_comment(findings, user, changes_summary, run_id)
|
||||
with open(os.path.join(OUTPUT_DIR, "comment.md"), "w") as f:
|
||||
f.write(comment)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue