Skip to content

Commit 3118cab

Browse files
authored
feat: integrate vouch & stricter issue trust management system (anomalyco#12640)
1 parent 31f893f commit 3118cab

File tree

8 files changed

+434
-15
lines changed

8 files changed

+434
-15
lines changed

.github/ISSUE_TEMPLATE/config.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
blank_issues_enabled: true
1+
blank_issues_enabled: false
22
contact_links:
33
- name: 💬 Discord Community
44
url: https://discord.gg/opencode

.github/VOUCHED.td

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
# Vouched contributors for this project.
2+
#
3+
# See https://github.com/mitchellh/vouch for details.
4+
#
5+
# Syntax:
6+
# - One handle per line (without @), sorted alphabetically.
7+
# - Optional platform prefix: platform:username (e.g., github:user).
8+
# - Denounce with minus prefix: -username or -platform:username.
9+
# - Optional details after a space following the handle.
10+
adamdotdevin
11+
fwang
12+
iamdavidhill
13+
jayair
14+
kommander
15+
r44vc0rp
16+
rekram1-node
17+
thdxr
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
name: compliance-close
2+
3+
on:
4+
schedule:
5+
# Run every 30 minutes to check for expired compliance windows
6+
- cron: "*/30 * * * *"
7+
workflow_dispatch:
8+
9+
permissions:
10+
contents: read
11+
issues: write
12+
pull-requests: write
13+
14+
jobs:
15+
close-non-compliant:
16+
runs-on: ubuntu-latest
17+
steps:
18+
- name: Close non-compliant issues and PRs after 2 hours
19+
uses: actions/github-script@v7
20+
with:
21+
script: |
22+
const { data: items } = await github.rest.issues.listForRepo({
23+
owner: context.repo.owner,
24+
repo: context.repo.repo,
25+
labels: 'needs:compliance',
26+
state: 'open',
27+
per_page: 100,
28+
});
29+
30+
if (items.length === 0) {
31+
core.info('No open issues/PRs with needs:compliance label');
32+
return;
33+
}
34+
35+
const now = Date.now();
36+
const twoHours = 2 * 60 * 60 * 1000;
37+
38+
for (const item of items) {
39+
const isPR = !!item.pull_request;
40+
const kind = isPR ? 'PR' : 'issue';
41+
42+
const { data: comments } = await github.rest.issues.listComments({
43+
owner: context.repo.owner,
44+
repo: context.repo.repo,
45+
issue_number: item.number,
46+
});
47+
48+
const complianceComment = comments.find(c => c.body.includes('<!-- issue-compliance -->'));
49+
if (!complianceComment) continue;
50+
51+
const commentAge = now - new Date(complianceComment.created_at).getTime();
52+
if (commentAge < twoHours) {
53+
core.info(`${kind} #${item.number} still within 2-hour window (${Math.round(commentAge / 60000)}m elapsed)`);
54+
continue;
55+
}
56+
57+
const closeMessage = isPR
58+
? 'This pull request has been automatically closed because it was not updated to meet our [contributing guidelines](../blob/dev/CONTRIBUTING.md) within the 2-hour window.\n\nFeel free to open a new pull request that follows our guidelines.'
59+
: 'This issue has been automatically closed because it was not updated to meet our [contributing guidelines](../blob/dev/CONTRIBUTING.md) within the 2-hour window.\n\nFeel free to open a new issue that follows our issue templates.';
60+
61+
await github.rest.issues.createComment({
62+
owner: context.repo.owner,
63+
repo: context.repo.repo,
64+
issue_number: item.number,
65+
body: closeMessage,
66+
});
67+
68+
if (isPR) {
69+
await github.rest.pulls.update({
70+
owner: context.repo.owner,
71+
repo: context.repo.repo,
72+
pull_number: item.number,
73+
state: 'closed',
74+
});
75+
} else {
76+
await github.rest.issues.update({
77+
owner: context.repo.owner,
78+
repo: context.repo.repo,
79+
issue_number: item.number,
80+
state: 'closed',
81+
state_reason: 'not_planned',
82+
});
83+
}
84+
85+
core.info(`Closed non-compliant ${kind} #${item.number} after 2-hour window`);
86+
}

.github/workflows/duplicate-issues.yml

Lines changed: 68 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ jobs:
2121
- name: Install opencode
2222
run: curl -fsSL https://opencode.ai/install | bash
2323

24-
- name: Check for duplicate issues
24+
- name: Check duplicates and compliance
2525
env:
2626
OPENCODE_API_KEY: ${{ secrets.OPENCODE_API_KEY }}
2727
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
@@ -34,30 +34,84 @@ jobs:
3434
"webfetch": "deny"
3535
}
3636
run: |
37-
opencode run -m opencode/claude-haiku-4-5 "A new issue has been created:'
37+
opencode run -m opencode/claude-haiku-4-5 "A new issue has been created:
3838
39-
Issue number:
40-
${{ github.event.issue.number }}
39+
Issue number: ${{ github.event.issue.number }}
4140
42-
Lookup this issue and search through existing issues (excluding #${{ github.event.issue.number }}) in this repository to find any potential duplicates of this new issue.
41+
Lookup this issue with gh issue view ${{ github.event.issue.number }}.
42+
43+
You have TWO tasks. Perform both, then post a SINGLE comment (if needed).
44+
45+
---
46+
47+
TASK 1: CONTRIBUTING GUIDELINES COMPLIANCE CHECK
48+
49+
Check whether the issue follows our contributing guidelines and issue templates.
50+
51+
This project has three issue templates that every issue MUST use one of:
52+
53+
1. Bug Report - requires a Description field with real content
54+
2. Feature Request - requires a verification checkbox and description, title should start with [FEATURE]:
55+
3. Question - requires the Question field with real content
56+
57+
Additionally check:
58+
- No AI-generated walls of text (long, AI-generated descriptions are not acceptable)
59+
- The issue has real content, not just template placeholder text left unchanged
60+
- Bug reports should include some context about how to reproduce
61+
- Feature requests should explain the problem or need
62+
- We want to push for having the user provide system description & information
63+
64+
Do NOT be nitpicky about optional fields. Only flag real problems like: no template used, required fields empty or placeholder text only, obviously AI-generated walls of text, or completely empty/nonsensical content.
65+
66+
---
67+
68+
TASK 2: DUPLICATE CHECK
69+
70+
Search through existing issues (excluding #${{ github.event.issue.number }}) to find potential duplicates.
4371
Consider:
4472
1. Similar titles or descriptions
4573
2. Same error messages or symptoms
4674
3. Related functionality or components
4775
4. Similar feature requests
4876
49-
If you find any potential duplicates, please comment on the new issue with:
50-
- A brief explanation of why it might be a duplicate
51-
- Links to the potentially duplicate issues
52-
- A suggestion to check those issues first
77+
Additionally, if the issue mentions keybinds, keyboard shortcuts, or key bindings, note the pinned keybinds issue #4997.
78+
79+
---
80+
81+
POSTING YOUR COMMENT:
82+
83+
Based on your findings, post a SINGLE comment on issue #${{ github.event.issue.number }}. Build the comment as follows:
84+
85+
If the issue is NOT compliant, start the comment with:
86+
<!-- issue-compliance -->
87+
Then explain what needs to be fixed and that they have 2 hours to edit the issue before it is automatically closed. Also add the label needs:compliance to the issue using: gh issue edit ${{ github.event.issue.number }} --add-label needs:compliance
88+
89+
If duplicates were found, include a section about potential duplicates with links.
90+
91+
If the issue mentions keybinds/keyboard shortcuts, include a note about #4997.
92+
93+
If the issue IS compliant AND no duplicates were found AND no keybind reference, do NOT comment at all.
5394
5495
Use this format for the comment:
55-
'This issue might be a duplicate of existing issues. Please check:
96+
97+
[If not compliant:]
98+
<!-- issue-compliance -->
99+
This issue doesn't fully meet our [contributing guidelines](../blob/dev/CONTRIBUTING.md).
100+
101+
**What needs to be fixed:**
102+
- [specific reasons]
103+
104+
Please edit this issue to address the above within **2 hours**, or it will be automatically closed.
105+
106+
[If duplicates found, add:]
107+
---
108+
This issue might be a duplicate of existing issues. Please check:
56109
- #[issue_number]: [brief description of similarity]
57110
58-
Feel free to ignore if none of these address your specific case.'
111+
[If keybind-related, add:]
112+
For keybind-related issues, please also check our pinned keybinds documentation: #4997
59113
60-
Additionally, if the issue mentions keybinds, keyboard shortcuts, or key bindings, please add a comment mentioning the pinned keybinds issue #4997:
61-
'For keybind-related issues, please also check our pinned keybinds documentation: #4997'
114+
[End with if not compliant:]
115+
If you believe this was flagged incorrectly, please let a maintainer know.
62116
63-
If no clear duplicates are found, do not comment."
117+
Remember: post at most ONE comment combining all findings. If everything is fine, post nothing."
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
name: vouch-check-issue
2+
3+
on:
4+
issues:
5+
types: [opened]
6+
7+
permissions:
8+
contents: read
9+
issues: write
10+
11+
jobs:
12+
check:
13+
runs-on: ubuntu-latest
14+
steps:
15+
- name: Check if issue author is denounced
16+
uses: actions/github-script@v7
17+
with:
18+
script: |
19+
const author = context.payload.issue.user.login;
20+
const issueNumber = context.payload.issue.number;
21+
22+
// Skip bots
23+
if (author.endsWith('[bot]')) {
24+
core.info(`Skipping bot: ${author}`);
25+
return;
26+
}
27+
28+
// Read the VOUCHED.td file via API (no checkout needed)
29+
let content;
30+
try {
31+
const response = await github.rest.repos.getContent({
32+
owner: context.repo.owner,
33+
repo: context.repo.repo,
34+
path: '.github/VOUCHED.td',
35+
});
36+
content = Buffer.from(response.data.content, 'base64').toString('utf-8');
37+
} catch (error) {
38+
if (error.status === 404) {
39+
core.info('No .github/VOUCHED.td file found, skipping check.');
40+
return;
41+
}
42+
throw error;
43+
}
44+
45+
// Parse the .td file for denounced users
46+
const denounced = new Map();
47+
for (const line of content.split('\n')) {
48+
const trimmed = line.trim();
49+
if (!trimmed || trimmed.startsWith('#')) continue;
50+
if (!trimmed.startsWith('-')) continue;
51+
52+
const rest = trimmed.slice(1).trim();
53+
if (!rest) continue;
54+
const spaceIdx = rest.indexOf(' ');
55+
const handle = spaceIdx === -1 ? rest : rest.slice(0, spaceIdx);
56+
const reason = spaceIdx === -1 ? null : rest.slice(spaceIdx + 1).trim();
57+
58+
// Handle platform:username or bare username
59+
// Only match bare usernames or github: prefix (skip other platforms)
60+
const colonIdx = handle.indexOf(':');
61+
if (colonIdx !== -1) {
62+
const platform = handle.slice(0, colonIdx).toLowerCase();
63+
if (platform !== 'github') continue;
64+
}
65+
const username = colonIdx === -1 ? handle : handle.slice(colonIdx + 1);
66+
if (!username) continue;
67+
68+
denounced.set(username.toLowerCase(), reason);
69+
}
70+
71+
// Check if the author is denounced
72+
const reason = denounced.get(author.toLowerCase());
73+
if (reason === undefined) {
74+
core.info(`User ${author} is not denounced. Allowing issue.`);
75+
return;
76+
}
77+
78+
// Author is denounced — close the issue
79+
const body = 'This issue has been automatically closed.';
80+
81+
await github.rest.issues.createComment({
82+
owner: context.repo.owner,
83+
repo: context.repo.repo,
84+
issue_number: issueNumber,
85+
body,
86+
});
87+
88+
await github.rest.issues.update({
89+
owner: context.repo.owner,
90+
repo: context.repo.repo,
91+
issue_number: issueNumber,
92+
state: 'closed',
93+
state_reason: 'not_planned',
94+
});
95+
96+
core.info(`Closed issue #${issueNumber} from denounced user ${author}`);

0 commit comments

Comments
 (0)