-
-
Notifications
You must be signed in to change notification settings - Fork 0
ci(rhodibot): switch to the report-only canary (standards#759) #107
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -1,236 +1,94 @@ | ||||||||||||||
| # SPDX-License-Identifier: MPL-2.0 | ||||||||||||||
| # This workflow is managed by gh actions-lock. | ||||||||||||||
| # rhodibot.yml — Automated RSR compliance enforcement | ||||||||||||||
| # SPDX-License-Identifier: MPL-2.0 | ||||||||||||||
| # rhodibot.yml — RSR compliance CANARY (report-only) | ||||||||||||||
| # | ||||||||||||||
| # Reads root-hygiene rules and auto-fixes what it can: | ||||||||||||||
| # - Delete banned files (AI.djot, duplicate CONTRIBUTING.adoc, stale snapshots) | ||||||||||||||
| # - Rename misnamed files (AI.a2ml → 0-AI-MANIFEST.a2ml) | ||||||||||||||
| # - Fix SPDX headers (AGPL → PMPL in dotfiles) | ||||||||||||||
| # - Create missing required files (SECURITY.md, CONTRIBUTING.md) | ||||||||||||||
| # - Report unfixable issues as PR comments | ||||||||||||||
| # Rhodibot does NOT mutate this repository. It never deletes, renames, | ||||||||||||||
| # rewrites SPDX headers, creates files, or opens PRs. Instead it DETECTS | ||||||||||||||
| # what an auto-fixer would have changed and reports it. | ||||||||||||||
| # | ||||||||||||||
| # Runs weekly and on Hypatia scan completion. | ||||||||||||||
|
|
||||||||||||||
| name: "🤖 Rhodibot — RSR Auto-Fix" | ||||||||||||||
| # Design intent (owner): if rhodibot "feels the desire to edit" — i.e. it | ||||||||||||||
| # detects something it considers non-compliant — that is itself a MAJOR | ||||||||||||||
| # WARNING. Either the repo has drifted, OR rhodibot's own rules have | ||||||||||||||
| # diverged from the normative style it is meant to enforce. Both warrant | ||||||||||||||
| # a human look, so the canary FAILS the run when it finds would-mutate | ||||||||||||||
| # drift. Dangerous-pattern hits are advisory warnings only. | ||||||||||||||
| # | ||||||||||||||
| # Licence note: SPDX/licence drift is reported for MANUAL, owner-only | ||||||||||||||
| # correction. Rhodibot must never edit a licence header (estate directive). | ||||||||||||||
|
|
||||||||||||||
| name: "\U0001F916 Rhodibot — RSR Compliance Canary" | ||||||||||||||
| on: | ||||||||||||||
| schedule: | ||||||||||||||
| - cron: '0 6 * * 1' # Every Monday at 06:00 UTC | ||||||||||||||
| workflow_dispatch: # Manual trigger | ||||||||||||||
| workflow_run: | ||||||||||||||
| workflows: ["Hypatia Neurosymbolic Analysis"] | ||||||||||||||
| types: [completed] | ||||||||||||||
| - cron: '0 6 * * 1' # Every Monday at 06:00 UTC | ||||||||||||||
| workflow_dispatch: # Manual trigger | ||||||||||||||
|
|
||||||||||||||
| permissions: | ||||||||||||||
| contents: write | ||||||||||||||
| pull-requests: write | ||||||||||||||
| concurrency: | ||||||||||||||
| group: ${{ github.workflow }}-${{ github.ref }} | ||||||||||||||
| cancel-in-progress: true | ||||||||||||||
|
|
||||||||||||||
| permissions: | ||||||||||||||
| contents: read | ||||||||||||||
| jobs: | ||||||||||||||
| rhodibot: | ||||||||||||||
| canary: | ||||||||||||||
| runs-on: ubuntu-latest | ||||||||||||||
| timeout-minutes: 15 | ||||||||||||||
| steps: | ||||||||||||||
| - name: Checkout | ||||||||||||||
| uses: actions/checkout@v4.3.1 | ||||||||||||||
| uses: actions/checkout@v7.0.1 | ||||||||||||||
| with: | ||||||||||||||
| fetch-depth: 1 | ||||||||||||||
|
|
||||||||||||||
| - name: Rhodibot — Scan and Fix | ||||||||||||||
| id: fix | ||||||||||||||
| - name: Rhodibot — detect drift (no mutations) | ||||||||||||||
| run: | | ||||||||||||||
| set -euo pipefail | ||||||||||||||
| FIXES="" | ||||||||||||||
| ISSUES="" | ||||||||||||||
| CHANGED=false | ||||||||||||||
| set -uo pipefail | ||||||||||||||
| DRIFT=0 | ||||||||||||||
| warn() { echo "::warning title=Rhodibot canary::$*"; DRIFT=$((DRIFT+1)); } | ||||||||||||||
| note() { echo "::warning title=Rhodibot advisory::$*"; } | ||||||||||||||
|
|
||||||||||||||
| # --- 1. Delete banned files --- | ||||||||||||||
| for pattern in "AI.djot" "NEXT_STEPS.md" "TODO.md" "NOTES.md" "TASKS.md"; do | ||||||||||||||
| if [ -f "$pattern" ]; then | ||||||||||||||
| rm "$pattern" | ||||||||||||||
| FIXES="$FIXES\n- Deleted \`$pattern\` (superseded)" | ||||||||||||||
| CHANGED=true | ||||||||||||||
| fi | ||||||||||||||
| done | ||||||||||||||
| echo "## 🤖 Rhodibot canary — report only (no edits made)" >> "$GITHUB_STEP_SUMMARY" | ||||||||||||||
|
|
||||||||||||||
| # Delete stale snapshot files | ||||||||||||||
| # --- would-DELETE: banned files --- | ||||||||||||||
| for f in AI.djot NEXT_STEPS.md TODO.md NOTES.md TASKS.md; do | ||||||||||||||
| [ -f "$f" ] && warn "banned file present: $f (an auto-fixer would delete it)" | ||||||||||||||
| done | ||||||||||||||
| # would-DELETE: stale snapshots | ||||||||||||||
| for f in *-STATUS-*.md *-COMPLETION-*.md *-COMPLETE.md *-VERIFIED-*.md; do | ||||||||||||||
| if [ -f "$f" ]; then | ||||||||||||||
| rm "$f" | ||||||||||||||
| FIXES="$FIXES\n- Deleted stale snapshot \`$f\`" | ||||||||||||||
| CHANGED=true | ||||||||||||||
| fi | ||||||||||||||
| [ -f "$f" ] && warn "stale snapshot present: $f (would be deleted)" | ||||||||||||||
| done | ||||||||||||||
|
|
||||||||||||||
| # --- 2. Rename misnamed files --- | ||||||||||||||
| # would-RENAME: legacy manifest name | ||||||||||||||
| if [ -f "AI.a2ml" ] && [ ! -f "0-AI-MANIFEST.a2ml" ]; then | ||||||||||||||
| mv AI.a2ml 0-AI-MANIFEST.a2ml | ||||||||||||||
| FIXES="$FIXES\n- Renamed \`AI.a2ml\` → \`0-AI-MANIFEST.a2ml\`" | ||||||||||||||
| CHANGED=true | ||||||||||||||
| warn "AI.a2ml present without 0-AI-MANIFEST.a2ml (would be renamed)" | ||||||||||||||
| fi | ||||||||||||||
|
|
||||||||||||||
| # --- 3. Delete duplicate format files --- | ||||||||||||||
| if [ -f "CONTRIBUTING.md" ] && [ -f "CONTRIBUTING.adoc" ]; then | ||||||||||||||
| rm CONTRIBUTING.adoc | ||||||||||||||
| FIXES="$FIXES\n- Deleted duplicate \`CONTRIBUTING.adoc\` (keeping .md for GitHub)" | ||||||||||||||
| CHANGED=true | ||||||||||||||
| # would-DELETE: duplicate community files | ||||||||||||||
| [ -f "CONTRIBUTING.md" ] && [ -f "CONTRIBUTING.adoc" ] && warn "duplicate CONTRIBUTING.md + CONTRIBUTING.adoc (one would be removed)" | ||||||||||||||
| if [ -f "README.md" ] && [ -f "README.adoc" ] && [ "$(wc -l < README.md)" -lt 5 ]; then | ||||||||||||||
| warn "stub README.md alongside README.adoc (would be removed)" | ||||||||||||||
| fi | ||||||||||||||
|
|
||||||||||||||
| if [ -f "README.md" ] && [ -f "README.adoc" ]; then | ||||||||||||||
| # Only delete README.md if it's a stub (<5 lines) | ||||||||||||||
| lines=$(wc -l < README.md) | ||||||||||||||
| if [ "$lines" -lt 5 ]; then | ||||||||||||||
| rm README.md | ||||||||||||||
| FIXES="$FIXES\n- Deleted stub \`README.md\` (keeping .adoc)" | ||||||||||||||
| CHANGED=true | ||||||||||||||
| fi | ||||||||||||||
| fi | ||||||||||||||
|
|
||||||||||||||
| # --- 4. Fix SPDX headers in dotfiles --- | ||||||||||||||
| # SPDX drift — MANUAL owner-only fix, never auto-edited | ||||||||||||||
| for dotfile in .gitignore .gitattributes .editorconfig; do | ||||||||||||||
| if [ -f "$dotfile" ] && grep -q "AGPL-3.0" "$dotfile" 2>/dev/null; then | ||||||||||||||
| sed -i 's/AGPL-3.0-or-later/MPL-2.0/g; s/AGPL-3.0/MPL-2.0/g' "$dotfile" | ||||||||||||||
| FIXES="$FIXES\n- Fixed SPDX header in \`$dotfile\` (AGPL → PMPL)" | ||||||||||||||
| CHANGED=true | ||||||||||||||
| if [ -f "$dotfile" ] && grep "AGPL-3.0" "$dotfile" 2>/dev/null | grep -v "AGPL-3.0-or-later" | grep -q .; then | ||||||||||||||
| warn "$dotfile carries an AGPL-3.0 SPDX header; estate policy is MPL-2.0 — fix MANUALLY (owner-only, never auto-edited)" | ||||||||||||||
| fi | ||||||||||||||
| done | ||||||||||||||
|
|
||||||||||||||
| # --- 5. Create missing required files --- | ||||||||||||||
| if [ ! -f "SECURITY.md" ]; then | ||||||||||||||
| cat > SECURITY.md << 'SECEOF' | ||||||||||||||
| <!-- SPDX-License-Identifier: MPL-2.0 --> | ||||||||||||||
| # Security Policy | ||||||||||||||
|
|
||||||||||||||
| ## Reporting a Vulnerability | ||||||||||||||
|
|
||||||||||||||
| **Email:** j.d.a.jewell@open.ac.uk | ||||||||||||||
|
|
||||||||||||||
| **Response timeline:** | ||||||||||||||
| - Acknowledgement within 48 hours | ||||||||||||||
| - Initial assessment within 7 days | ||||||||||||||
| - Fix or mitigation within 90 days | ||||||||||||||
|
|
||||||||||||||
| **Safe harbour:** We will not pursue legal action against security researchers who follow responsible disclosure. | ||||||||||||||
| SECEOF | ||||||||||||||
| FIXES="$FIXES\n- Created missing \`SECURITY.md\`" | ||||||||||||||
| CHANGED=true | ||||||||||||||
| fi | ||||||||||||||
|
|
||||||||||||||
| if [ ! -f "CONTRIBUTING.md" ]; then | ||||||||||||||
| cat > CONTRIBUTING.md << 'CONTEOF' | ||||||||||||||
| <!-- SPDX-License-Identifier: MPL-2.0 --> | ||||||||||||||
| # Contributing | ||||||||||||||
|
|
||||||||||||||
| 1. Fork the repository | ||||||||||||||
| 2. Create a feature branch | ||||||||||||||
| 3. Ensure SPDX headers on all files | ||||||||||||||
| 4. Submit a pull request | ||||||||||||||
|
|
||||||||||||||
| **Author:** Jonathan D.A. Jewell <j.d.a.jewell@open.ac.uk> | ||||||||||||||
| CONTEOF | ||||||||||||||
| FIXES="$FIXES\n- Created missing \`CONTRIBUTING.md\`" | ||||||||||||||
| CHANGED=true | ||||||||||||||
| fi | ||||||||||||||
|
|
||||||||||||||
| # --- 6. Check for issues we can't auto-fix --- | ||||||||||||||
| if [ ! -f "0-AI-MANIFEST.a2ml" ] && [ ! -f "AI.a2ml" ]; then | ||||||||||||||
| ISSUES="$ISSUES\n- Missing AI manifest (0-AI-MANIFEST.a2ml)" | ||||||||||||||
| fi | ||||||||||||||
|
|
||||||||||||||
| if [ ! -f "LICENSE" ] && [ ! -f "LICENSE.md" ] && [ ! -f "LICENSE.txt" ]; then | ||||||||||||||
| ISSUES="$ISSUES\n- Missing LICENSE file" | ||||||||||||||
| fi | ||||||||||||||
|
|
||||||||||||||
| if [ ! -f "README.adoc" ] && [ ! -f "README.md" ]; then | ||||||||||||||
| ISSUES="$ISSUES\n- Missing README" | ||||||||||||||
| fi | ||||||||||||||
|
|
||||||||||||||
| # Check for third-party fork (skip SPDX enforcement) | ||||||||||||||
| if [ -f "LICENSE" ] && grep -q "multiple licenses\|LGPL\|Apache" LICENSE 2>/dev/null; then | ||||||||||||||
| echo "FORK=true" >> $GITHUB_OUTPUT | ||||||||||||||
| fi | ||||||||||||||
|
|
||||||||||||||
| # --- 7. Check dangerous patterns --- | ||||||||||||||
| DANGEROUS="" | ||||||||||||||
| for pattern in "believe_me" "assert_total" "Admitted" "sorry" "unsafeCoerce" "Obj.magic"; do | ||||||||||||||
| count=$(grep -r "$pattern" --include='*.idr' --include='*.v' --include='*.lean' --include='*.hs' --include='*.ml' --include='*.res' . 2>/dev/null | grep -v node_modules | wc -l || echo 0) | ||||||||||||||
| if [ "$count" -gt 0 ]; then | ||||||||||||||
| DANGEROUS="$DANGEROUS\n- \`$pattern\`: $count occurrences" | ||||||||||||||
| fi | ||||||||||||||
| # would-CREATE: missing required files | ||||||||||||||
| [ -f "SECURITY.md" ] || [ -f ".github/SECURITY.md" ] || warn "no SECURITY.md (would be created)" | ||||||||||||||
| [ -f "CONTRIBUTING.md" ] || [ -f ".github/CONTRIBUTING.md" ] || warn "no CONTRIBUTING.md (would be created)" | ||||||||||||||
|
Comment on lines
+73
to
+74
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win 🔎 Supported by static analysis🏁 Script executed: #!/bin/bash
# Description: Locate community health files in both Markdown and AsciiDoc form.
fd -i -H -t f '^(SECURITY|CONTRIBUTING|README)\.(md|adoc)$' . --max-depth 3Repository: hyperpolymath/nextgen-typing Length of output: 1541 🏁 Script executed: #!/bin/bash
set -u
printf '%s\n' '--- workflow ---'
cat -n .github/workflows/rhodibot.yml | sed -n '55,98p'
printf '%s\n' '--- tracked community files ---'
git ls-files -- \
'SECURITY.md' '.github/SECURITY.md' 'SECURITY.adoc' '.github/SECURITY.adoc' \
'CONTRIBUTING.md' '.github/CONTRIBUTING.md' 'CONTRIBUTING.adoc' '.github/CONTRIBUTING.adoc' \
'README.md' '.github/README.md' 'README.adoc' '.github/README.adoc'
printf '%s\n' '--- relevant policy references ---'
rg -n -i --glob '*.md' --glob '*.adoc' --glob '*.yml' --glob '*.yaml' \
'SECURITY|CONTRIBUTING|AsciiDoc|Asciidoc|\.adoc' .github README.adoc CONTRIBUTING.adoc SECURITY.adoc 2>/dev/null | head -120Repository: hyperpolymath/nextgen-typing Length of output: 15727 🏁 Script executed: #!/bin/bash
set -u
cat -n .github/workflows/rhodibot.yml | sed -n '35,54p'Repository: hyperpolymath/nextgen-typing Length of output: 1129 Accept root AsciiDoc community files in the required-file checks. If a repository contains only 🐛 Proposed fix for the required-file checks- [ -f "SECURITY.md" ] || [ -f ".github/SECURITY.md" ] || warn "no SECURITY.md (would be created)"
- [ -f "CONTRIBUTING.md" ] || [ -f ".github/CONTRIBUTING.md" ] || warn "no CONTRIBUTING.md (would be created)"
+ [ -f "SECURITY.md" ] || [ -f ".github/SECURITY.md" ] || [ -f "SECURITY.adoc" ] \
+ || warn "no SECURITY policy file (would be created)"
+ [ -f "CONTRIBUTING.md" ] || [ -f ".github/CONTRIBUTING.md" ] || [ -f "CONTRIBUTING.adoc" ] \
+ || warn "no CONTRIBUTING file (would be created)"📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||
|
|
||||||||||||||
| # --- unfixable compliance gaps (also drift) --- | ||||||||||||||
| [ -f "0-AI-MANIFEST.a2ml" ] || [ -f "AI.a2ml" ] || warn "missing AI manifest (0-AI-MANIFEST.a2ml)" | ||||||||||||||
| [ -f "LICENSE" ] || [ -f "LICENSE.md" ] || [ -f "LICENSE.txt" ] || warn "missing LICENSE file" | ||||||||||||||
| [ -f "README.adoc" ] || [ -f "README.md" ] || warn "missing README" | ||||||||||||||
|
|
||||||||||||||
| # --- advisory only: dangerous verification-bypass patterns --- | ||||||||||||||
| for pattern in believe_me assert_total Admitted sorry unsafeCoerce Obj.magic; do | ||||||||||||||
| count=$(grep -rl "$pattern" --include='*.idr' --include='*.v' --include='*.lean' --include='*.hs' --include='*.ml' --include='*.res' . 2>/dev/null | grep -v node_modules | wc -l || true) | ||||||||||||||
| [ "$count" -gt 0 ] && note "verification-bypass pattern '$pattern' in $count file(s) (advisory)" | ||||||||||||||
| done | ||||||||||||||
|
|
||||||||||||||
| # Output results | ||||||||||||||
| echo "CHANGED=$CHANGED" >> $GITHUB_OUTPUT | ||||||||||||||
| { | ||||||||||||||
| echo "FIXES<<EOF" | ||||||||||||||
| echo -e "$FIXES" | ||||||||||||||
| echo "EOF" | ||||||||||||||
| } >> $GITHUB_OUTPUT | ||||||||||||||
| { | ||||||||||||||
| echo "ISSUES<<EOF" | ||||||||||||||
| echo -e "$ISSUES" | ||||||||||||||
| echo "EOF" | ||||||||||||||
| } >> $GITHUB_OUTPUT | ||||||||||||||
| { | ||||||||||||||
| echo "DANGEROUS<<EOF" | ||||||||||||||
| echo -e "$DANGEROUS" | ||||||||||||||
| echo "EOF" | ||||||||||||||
| } >> $GITHUB_OUTPUT | ||||||||||||||
|
|
||||||||||||||
| - name: Create PR with fixes | ||||||||||||||
| if: steps.fix.outputs.CHANGED == 'true' | ||||||||||||||
| run: | | ||||||||||||||
| git config user.name "rhodibot" | ||||||||||||||
| git config user.email "rhodibot@hyperpolymath.dev" | ||||||||||||||
| BRANCH="rhodibot/rsr-compliance-$(date +%Y%m%d)" | ||||||||||||||
| git checkout -b "$BRANCH" | ||||||||||||||
| git add -A | ||||||||||||||
| git commit -m "fix(rhodibot): automated RSR compliance fixes | ||||||||||||||
|
|
||||||||||||||
| ${{ steps.fix.outputs.FIXES }} | ||||||||||||||
|
|
||||||||||||||
| Co-Authored-By: rhodibot <rhodibot@hyperpolymath.dev>" | ||||||||||||||
|
|
||||||||||||||
| git push origin "$BRANCH" | ||||||||||||||
|
|
||||||||||||||
| BODY="## 🤖 Rhodibot — RSR Compliance Fixes | ||||||||||||||
|
|
||||||||||||||
| ### Changes Made | ||||||||||||||
| ${{ steps.fix.outputs.FIXES }} | ||||||||||||||
| " | ||||||||||||||
|
|
||||||||||||||
| if [ -n "${{ steps.fix.outputs.ISSUES }}" ]; then | ||||||||||||||
| BODY="$BODY | ||||||||||||||
| ### Issues Found (manual fix needed) | ||||||||||||||
| ${{ steps.fix.outputs.ISSUES }} | ||||||||||||||
| " | ||||||||||||||
| fi | ||||||||||||||
|
|
||||||||||||||
| if [ -n "${{ steps.fix.outputs.DANGEROUS }}" ]; then | ||||||||||||||
| BODY="$BODY | ||||||||||||||
| ### ⚠️ Dangerous Patterns Detected | ||||||||||||||
| ${{ steps.fix.outputs.DANGEROUS }} | ||||||||||||||
|
|
||||||||||||||
| _These bypass formal verification. See \`proven\` repo for alternatives._ | ||||||||||||||
| " | ||||||||||||||
| fi | ||||||||||||||
|
|
||||||||||||||
| gh pr create \ | ||||||||||||||
| --title "🤖 Rhodibot: RSR compliance fixes" \ | ||||||||||||||
| --body "$BODY" \ | ||||||||||||||
| --base main \ | ||||||||||||||
| --head "$BRANCH" | ||||||||||||||
| env: | ||||||||||||||
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | ||||||||||||||
|
|
||||||||||||||
| - name: Report (no changes needed) | ||||||||||||||
| if: steps.fix.outputs.CHANGED != 'true' | ||||||||||||||
| run: | | ||||||||||||||
| echo "✅ Repository is RSR-compliant. No fixes needed." | ||||||||||||||
| if [ -n "${{ steps.fix.outputs.ISSUES }}" ]; then | ||||||||||||||
| echo "⚠️ Issues found (manual fix needed):" | ||||||||||||||
| echo -e "${{ steps.fix.outputs.ISSUES }}" | ||||||||||||||
| fi | ||||||||||||||
| if [ -n "${{ steps.fix.outputs.DANGEROUS }}" ]; then | ||||||||||||||
| echo "⚠️ Dangerous patterns:" | ||||||||||||||
| echo -e "${{ steps.fix.outputs.DANGEROUS }}" | ||||||||||||||
| echo "" >> "$GITHUB_STEP_SUMMARY" | ||||||||||||||
| if [ "$DRIFT" -gt 0 ]; then | ||||||||||||||
| echo "🔴 **Canary tripped: $DRIFT would-mutate finding(s).** Either the repo drifted or rhodibot's rules diverged from the norm — investigate (no edits were made)." >> "$GITHUB_STEP_SUMMARY" | ||||||||||||||
| echo "::error title=Rhodibot canary::$DRIFT would-mutate finding(s) detected — rhodibot wants to edit. Investigate; nothing was changed." | ||||||||||||||
| exit 1 | ||||||||||||||
| fi | ||||||||||||||
| echo "✅ Canary clean — rhodibot has no desire to edit. Repository matches the norm." >> "$GITHUB_STEP_SUMMARY" | ||||||||||||||
| echo "✅ Rhodibot canary clean — no drift, no mutations." | ||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
🔎 Supported by static analysis
🏁 Script executed:
Repository: hyperpolymath/nextgen-typing
Length of output: 5706
Align the AGPL detection with the MPL-2.0 policy. The condition excludes
AGPL-3.0-or-later, so that identifier produces no warning. This conflicts with the warning text and the repository convention requiringMPL-2.0. IncludeAGPL-3.0-or-laterin the detection, or change the warning to describe the narrower rule.🤖 Prompt for AI Agents