Просмотр исходного кода

fix: exempt team members from compliance cleanup

Aiden Cline 2 месяцев назад
Родитель
Сommit
1268f8657e

+ 14 - 0
.github/workflows/compliance-close.yml

@@ -34,11 +34,25 @@ jobs:
 
             const now = Date.now();
             const twoHours = 2 * 60 * 60 * 1000;
+            const teamAssociations = ['OWNER', 'MEMBER', 'COLLABORATOR'];
 
             for (const item of items) {
               const isPR = !!item.pull_request;
               const kind = isPR ? 'PR' : 'issue';
 
+              if (teamAssociations.includes(item.author_association)) {
+                core.info(`Skipping ${kind} #${item.number}: author association is ${item.author_association}`);
+                try {
+                  await github.rest.issues.removeLabel({
+                    owner: context.repo.owner,
+                    repo: context.repo.repo,
+                    issue_number: item.number,
+                    name: 'needs:compliance',
+                  });
+                } catch (e) {}
+                continue;
+              }
+
               const { data: comments } = await github.rest.issues.listComments({
                 owner: context.repo.owner,
                 repo: context.repo.repo,

+ 2 - 2
.github/workflows/duplicate-issues.yml

@@ -6,7 +6,7 @@ on:
 
 jobs:
   check-duplicates:
-    if: github.event.action == 'opened'
+    if: github.event.action == 'opened' && !contains(fromJSON('["OWNER","MEMBER","COLLABORATOR"]'), github.event.issue.author_association)
     runs-on: blacksmith-4vcpu-ubuntu-2404
     permissions:
       contents: read
@@ -118,7 +118,7 @@ jobs:
           Remember: post at most ONE comment combining all findings. If everything is fine, post nothing."
 
   recheck-compliance:
-    if: github.event.action == 'edited' && contains(github.event.issue.labels.*.name, 'needs:compliance')
+    if: github.event.action == 'edited' && contains(github.event.issue.labels.*.name, 'needs:compliance') && !contains(fromJSON('["OWNER","MEMBER","COLLABORATOR"]'), github.event.issue.author_association)
     runs-on: blacksmith-4vcpu-ubuntu-2404
     permissions:
       contents: read

+ 9 - 6
.github/workflows/pr-management.yml

@@ -11,22 +11,25 @@ jobs:
       contents: read
       pull-requests: write
     steps:
-      - name: Checkout repository
-        uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
-        with:
-          fetch-depth: 1
-
       - name: Check team membership
         id: team-check
         run: |
           LOGIN="${{ github.event.pull_request.user.login }}"
-          if [ "$LOGIN" = "opencode-agent[bot]" ] || grep -qxF "$LOGIN" .github/TEAM_MEMBERS; then
+          ASSOCIATION="${{ github.event.pull_request.author_association }}"
+          if [ "$LOGIN" = "opencode-agent[bot]" ] || [ "$ASSOCIATION" = "OWNER" ] || [ "$ASSOCIATION" = "MEMBER" ] || [ "$ASSOCIATION" = "COLLABORATOR" ]; then
             echo "is_team=true" >> "$GITHUB_OUTPUT"
             echo "Skipping: $LOGIN is a team member or bot"
           else
             echo "is_team=false" >> "$GITHUB_OUTPUT"
           fi
 
+      - name: Checkout repository
+        if: steps.team-check.outputs.is_team != 'true'
+        uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
+        with:
+          fetch-depth: 1
+          ref: ${{ github.event.pull_request.base.sha }}
+
       - name: Setup Bun
         if: steps.team-check.outputs.is_team != 'true'
         uses: ./.github/actions/setup-bun

+ 6 - 18
.github/workflows/pr-standards.yml

@@ -28,15 +28,9 @@ jobs:
 
             // Check if author is a team member or bot
             if (login === 'opencode-agent[bot]') return;
-            const { data: file } = await github.rest.repos.getContent({
-              owner: context.repo.owner,
-              repo: context.repo.repo,
-              path: '.github/TEAM_MEMBERS',
-              ref: 'dev'
-            });
-            const members = Buffer.from(file.content, 'base64').toString().split('\n').map(l => l.trim()).filter(Boolean);
-            if (members.includes(login)) {
-              console.log(`Skipping: ${login} is a team member`);
+            const teamAssociations = ['OWNER', 'MEMBER', 'COLLABORATOR'];
+            if (teamAssociations.includes(pr.author_association)) {
+              console.log(`Skipping: ${login} has author association ${pr.author_association}`);
               return;
             }
 
@@ -175,15 +169,9 @@ jobs:
 
             // Check if author is a team member or bot
             if (login === 'opencode-agent[bot]') return;
-            const { data: file } = await github.rest.repos.getContent({
-              owner: context.repo.owner,
-              repo: context.repo.repo,
-              path: '.github/TEAM_MEMBERS',
-              ref: 'dev'
-            });
-            const members = Buffer.from(file.content, 'base64').toString().split('\n').map(l => l.trim()).filter(Boolean);
-            if (members.includes(login)) {
-              console.log(`Skipping: ${login} is a team member`);
+            const teamAssociations = ['OWNER', 'MEMBER', 'COLLABORATOR'];
+            if (teamAssociations.includes(pr.author_association)) {
+              console.log(`Skipping: ${login} has author association ${pr.author_association}`);
               return;
             }
 

+ 7 - 0
script/github/close-issues.ts

@@ -15,8 +15,11 @@ const cutoff = new Date(Date.now() - days * 24 * 60 * 60 * 1000)
 type Issue = {
   number: number
   updated_at: string
+  author_association: string
 }
 
+const teamAssociations = new Set(["OWNER", "MEMBER", "COLLABORATOR"])
+
 const headers = {
   Authorization: `Bearer ${token}`,
   "Content-Type": "application/json",
@@ -63,6 +66,10 @@ async function main() {
     for (const i of all) {
       const updated = new Date(i.updated_at)
       if (updated < cutoff) {
+        if (teamAssociations.has(i.author_association)) {
+          console.log(`Skipping #${i.number}: author association is ${i.author_association}`)
+          continue
+        }
         stale.push(i.number)
       } else {
         console.log(`\nFound fresh issue #${i.number}, stopping`)

+ 18 - 5
script/github/close-prs.ts

@@ -69,6 +69,7 @@ const maxClose =
 const sleepMs = requireNonNegativeInteger("sleep-ms", values["sleep-ms"])
 const printLimit = requireNonNegativeInteger("print-limit", values["print-limit"])
 const cutoff = subtractMonths(new Date(), ageMonths)
+const teamAssociations = new Set(["OWNER", "MEMBER", "COLLABORATOR"])
 
 const headers = {
   Authorization: `Bearer ${token}`,
@@ -82,6 +83,7 @@ type PullRequest = {
   title: string
   url: string
   createdAt: string
+  authorAssociation: string
   reactionGroups: Array<{
     content: string
     users: {
@@ -145,19 +147,25 @@ async function main() {
   console.log(`Threshold: fewer than ${threshold} positive reactions`)
 
   const prs = await fetchOpenPullRequests()
-  const recentCount = prs.filter((pr) => new Date(pr.createdAt) >= cutoff).length
-  const matching = prs
-    .map((pr) => ({ ...pr, positiveReactions: positiveReactionCount(pr) }))
-    .filter((pr) => new Date(pr.createdAt) < cutoff && pr.positiveReactions < threshold)
+  const scored = prs.map((pr) => ({ ...pr, positiveReactions: positiveReactionCount(pr) }))
+  const recentCount = scored.filter((pr) => new Date(pr.createdAt) >= cutoff).length
+  const teamCount = scored.filter((pr) => isTeamMember(pr)).length
+  const matching = scored.filter(
+    (pr) => !isTeamMember(pr) && new Date(pr.createdAt) < cutoff && pr.positiveReactions < threshold,
+  )
   const candidates = matching.filter((pr) => !hasPriorCleanup(pr))
   const selected = maxClose === undefined ? candidates : candidates.slice(0, maxClose)
 
   console.log(`Fetched ${prs.length} open PRs`)
   console.log(`Matching cleanup criteria: ${matching.length}`)
   console.log(`Skipped previously cleaned PRs: ${matching.length - candidates.length}`)
+  console.log(`Team member PRs untouched: ${teamCount}`)
   console.log(`Recent PRs untouched: ${recentCount}`)
   console.log(
-    `Older PRs with at least ${threshold} positive reactions untouched: ${prs.length - matching.length - recentCount}`,
+    `Older PRs with at least ${threshold} positive reactions untouched: ${
+      scored.filter((pr) => !isTeamMember(pr) && new Date(pr.createdAt) < cutoff && pr.positiveReactions >= threshold)
+        .length
+    }`,
   )
 
   if (selected.length === 0) return
@@ -205,6 +213,7 @@ async function fetchOpenPullRequests() {
               title
               url
               createdAt
+              authorAssociation
               reactionGroups {
                 content
                 users {
@@ -335,6 +344,10 @@ function hasPriorCleanup(pr: PullRequest) {
   return pr.labels.nodes.some((label) => label.name === cleanupLabel)
 }
 
+function isTeamMember(pr: PullRequest) {
+  return teamAssociations.has(pr.authorAssociation)
+}
+
 function requireRepo(value: string | undefined) {
   if (!value) throw new Error("repo is required")
   const [owner, name] = value.split("/")