mirror of
https://github.com/lobehub/lobe-chat.git
synced 2025-12-20 01:12:52 +08:00
🔨 chore: add claude code powered workflows (#9709)
* add claude dedupe issue workflow * add triage * add auto close duplicate * improve triage * improve
This commit is contained in:
38
.claude/commands/dedupe.md
Normal file
38
.claude/commands/dedupe.md
Normal file
@@ -0,0 +1,38 @@
|
||||
---
|
||||
allowed-tools: Bash(gh issue view:*), Bash(gh search:*), Bash(gh issue list:*), Bash(gh api:*), Bash(gh issue comment:*)
|
||||
description: Find duplicate GitHub issues
|
||||
---
|
||||
|
||||
Find up to 3 likely duplicate issues for a given GitHub issue.
|
||||
|
||||
To do this, follow these steps precisely:
|
||||
|
||||
1. Use an agent to check if the Github issue (a) is closed, (b) does not need to be deduped (eg. because it is broad product feedback without a specific solution, or positive feedback), or (c) already has a duplicates comment that you made earlier. If so, do not proceed.
|
||||
2. Use an agent to view a Github issue, and ask the agent to return a summary of the issue
|
||||
3. Then, launch 5 parallel agents to search Github for duplicates of this issue, using diverse keywords and search approaches, using the summary from #1
|
||||
4. Next, feed the results from #1 and #2 into another agent, so that it can filter out false positives, that are likely not actually duplicates of the original issue. If there are no duplicates remaining, do not proceed.
|
||||
5. Finally, comment back on the issue with a list of up to three duplicate issues (or zero, if there are no likely duplicates)
|
||||
|
||||
Notes (be sure to tell this to your agents, too):
|
||||
|
||||
- Use `gh` to interact with Github, rather than web fetch
|
||||
- Do not use other tools, beyond `gh` (eg. don't use other MCP servers, file edit, etc.)
|
||||
- Make a todo list first
|
||||
- For your comment, follow the following format precisely (assuming for this example that you found 3 suspected duplicates):
|
||||
|
||||
---
|
||||
|
||||
Found 3 possible duplicate issues:
|
||||
|
||||
1. <link to issue>
|
||||
2. <link to issue>
|
||||
3. <link to issue>
|
||||
|
||||
This issue will be automatically closed as a duplicate in 3 days.
|
||||
|
||||
- If your issue is a duplicate, please close it and 👍 the existing issue instead
|
||||
- To prevent auto-closure, add a comment or 👎 this comment
|
||||
|
||||
> 🤖 Generated with Claude Code
|
||||
|
||||
---
|
||||
135
.claude/prompts/team-assignment.md
Normal file
135
.claude/prompts/team-assignment.md
Normal file
@@ -0,0 +1,135 @@
|
||||
# Team Assignment Guide
|
||||
|
||||
## Quick Reference by Name
|
||||
|
||||
- **@arvinxx**: Last resort only, mention for priority:high issues, tool calling , mcp
|
||||
- **@canisminor1990**: Design, UI components, editor
|
||||
- **@tjx666**: Image/video generation, vision, cloud, documentation, TTS
|
||||
- **@ONLY-yours**: Performance, streaming, settings, general bugs, web platform, marketplace
|
||||
- **@RiverTwilight**: Knowledge base, files (KB-related), group chat
|
||||
- **@nekomeowww**: Memory, backend, deployment, DevOps
|
||||
- **@sudongyuer**: Mobile app (React Native)
|
||||
- **@sxjeru**: Model providers and configuration
|
||||
- **@cy948**: Auth Modules
|
||||
- **@rdmclin2**: Team workspace
|
||||
|
||||
Quick reference for assigning issues based on labels.
|
||||
|
||||
## Label to Team Member Mapping
|
||||
|
||||
### Provider Labels (provider:\*)
|
||||
|
||||
| Label | Owner | Notes |
|
||||
| ---------------- | ------- | -------------------------------------------- |
|
||||
| All `provider:*` | @sxjeru | Model configuration and provider integration |
|
||||
|
||||
### Platform Labels (platform:\*)
|
||||
|
||||
| Label | Owner | Notes |
|
||||
| ------------------ | ----------- | -------------------------------------- |
|
||||
| `platform:mobile` | @sudongyuer | React Native mobile app |
|
||||
| `platform:desktop` | @ONLY-yours | Electron desktop client (general) |
|
||||
| `platform:web` | @ONLY-yours | Web platform (unless specific feature) |
|
||||
|
||||
### Feature Labels (feature:\*)
|
||||
|
||||
| Label | Owner | Notes |
|
||||
| ------------------------ | --------------- | ----------------------------------------------------------------------- |
|
||||
| `feature:image` | @tjx666 | AI image generation |
|
||||
| `feature:dalle` | @tjx666 | DALL-E related |
|
||||
| `feature:vision` | @tjx666 | Vision/multimodal generation |
|
||||
| `feature:knowledge-base` | @RiverTwilight | Knowledge base and RAG |
|
||||
| `feature:files` | @RiverTwilight | File upload/management (when KB-related)<br>@ONLY-yours (general files) |
|
||||
| `feature:editor` | @canisminor1990 | Lobe Editor |
|
||||
| `feature:auth` | @cy948 | Authentication/authorization |
|
||||
| `feature:api` | @nekomeowww | Backend API |
|
||||
| `feature:streaming` | @arvinxx | Streaming response |
|
||||
| `feature:settings` | @ONLY-yours | Settings and configuration |
|
||||
| `feature:agent` | @ONLY-yours | Agent/Assistant |
|
||||
| `feature:topic` | @ONLY-yours | Topic/Conversation management |
|
||||
| `feature:thread` | @arvinxx | Thread/Subtopic |
|
||||
| `feature:marketplace` | @ONLY-yours | Agent marketplace |
|
||||
| `feature:tool` | @arvinxx | Tool calling |
|
||||
| `feature:mcp` | @arvinxx | MCP integration |
|
||||
| `feature:search` | @ONLY-yours | Search functionality |
|
||||
| `feature:tts` | @tjx666 | Text-to-speech |
|
||||
| `feature:export` | @ONLY-yours | Export functionality |
|
||||
| `feature:group-chat` | @RiverTwilight | Group chat functionality |
|
||||
| `feature:memory` | @nekomeowww | Memory feature |
|
||||
| `feature:team-workspace` | @rdmclin2 | Team workspace application |
|
||||
|
||||
### Deployment Labels (deployment:\*)
|
||||
|
||||
| Label | Owner | Notes |
|
||||
| ------------------ | ----------- | -------------------------- |
|
||||
| All `deployment:*` | @nekomeowww | Server/client/pglite modes |
|
||||
|
||||
### Hosting Labels (hosting:\*)
|
||||
|
||||
| Label | Owner | Notes |
|
||||
| ------------------- | ----------- | ---------------------- |
|
||||
| `hosting:cloud` | @tjx666 | Official LobeHub Cloud |
|
||||
| `hosting:self-host` | @nekomeowww | Self-hosting issues |
|
||||
| `hosting:vercel` | @nekomeowww | Vercel deployment |
|
||||
| `hosting:zeabur` | @nekomeowww | Zeabur deployment |
|
||||
| `hosting:railway` | @nekomeowww | Railway deployment |
|
||||
|
||||
### Issue Type Labels
|
||||
|
||||
| Label | Owner | Notes |
|
||||
| ------------------ | -------------------- | ---------------------------- |
|
||||
| 💄 Design | @canisminor1990 | Design and styling |
|
||||
| 📝 Documentation | @tjx666 | Documentation |
|
||||
| ⚡️ Performance | @ONLY-yours | Performance optimization |
|
||||
| 🐛 Bug | (depends on feature) | Assign based on other labels |
|
||||
| 🌠 Feature Request | (depends on feature) | Assign based on other labels |
|
||||
|
||||
## Assignment Rules
|
||||
|
||||
### Priority Order (apply in order)
|
||||
|
||||
1. **Specific feature owner** - e.g., `feature:knowledge-base` → @RiverTwilight
|
||||
2. **Platform owner** - e.g., `platform:mobile` → @sudongyuer
|
||||
3. **Provider owner** - e.g., `provider:*` → @sxjeru
|
||||
4. **Component owner** - e.g., 💄 Design → @canisminor1990
|
||||
5. **Infrastructure owner** - e.g., `deployment:*` → @nekomeowww
|
||||
6. **General maintainer** - @ONLY-yours for general bugs/issues
|
||||
7. **Last resort** - @arvinxx (only if no clear owner)
|
||||
|
||||
### Special Cases
|
||||
|
||||
**Multiple labels with different owners:**
|
||||
|
||||
- Mention the **most specific** feature owner first
|
||||
- Mention secondary owners if their input is valuable
|
||||
- Example: `feature:knowledge-base` + `deployment:server` → @RiverTwilight (primary), @nekomeowww (secondary)
|
||||
|
||||
**Priority:high issues:**
|
||||
|
||||
- Mention feature owner + @arvinxx
|
||||
- Example: `priority:high` + `feature:image` → @tjx666 @arvinxx
|
||||
|
||||
**No clear owner:**
|
||||
|
||||
- Assign to @ONLY-yours for general issues
|
||||
- Only mention @arvinxx if critical and truly unclear
|
||||
|
||||
## Comment Templates
|
||||
|
||||
**Single owner:**
|
||||
|
||||
```
|
||||
@username - This is a [feature/component] issue. Please take a look.
|
||||
```
|
||||
|
||||
**Multiple owners:**
|
||||
|
||||
```
|
||||
@primary @secondary - This involves [features]. Please coordinate.
|
||||
```
|
||||
|
||||
**High priority:**
|
||||
|
||||
```
|
||||
@owner @arvinxx - High priority [feature] issue.
|
||||
```
|
||||
260
.github/scripts/auto-close-duplicates.ts
vendored
Normal file
260
.github/scripts/auto-close-duplicates.ts
vendored
Normal file
@@ -0,0 +1,260 @@
|
||||
#!/usr/bin/env bun
|
||||
|
||||
declare global {
|
||||
// @ts-ignore
|
||||
// eslint-disable-next-line no-var
|
||||
var process: {
|
||||
env: Record<string, string | undefined>;
|
||||
};
|
||||
}
|
||||
|
||||
interface GitHubIssue {
|
||||
created_at: string;
|
||||
number: number;
|
||||
title: string;
|
||||
user: { id: number };
|
||||
}
|
||||
|
||||
interface GitHubComment {
|
||||
body: string;
|
||||
created_at: string;
|
||||
id: number;
|
||||
user: { id: number; type: string };
|
||||
}
|
||||
|
||||
interface GitHubReaction {
|
||||
content: string;
|
||||
user: { id: number };
|
||||
}
|
||||
|
||||
async function githubRequest<T>(
|
||||
endpoint: string,
|
||||
token: string,
|
||||
method: string = 'GET',
|
||||
body?: any,
|
||||
): Promise<T> {
|
||||
const response = await fetch(`https://api.github.com${endpoint}`, {
|
||||
headers: {
|
||||
'Accept': 'application/vnd.github.v3+json',
|
||||
'Authorization': `Bearer ${token}`,
|
||||
'User-Agent': 'auto-close-duplicates-script',
|
||||
...(body && { 'Content-Type': 'application/json' }),
|
||||
},
|
||||
method,
|
||||
...(body && { body: JSON.stringify(body) }),
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`GitHub API request failed: ${response.status} ${response.statusText}`);
|
||||
}
|
||||
|
||||
return response.json();
|
||||
}
|
||||
|
||||
function extractDuplicateIssueNumber(commentBody: string): number | null {
|
||||
// Try to match #123 format first
|
||||
let match = commentBody.match(/#(\d+)/);
|
||||
if (match) {
|
||||
return parseInt(match[1], 10);
|
||||
}
|
||||
|
||||
// Try to match GitHub issue URL format: https://github.com/owner/repo/issues/123
|
||||
match = commentBody.match(/github\.com\/[^/]+\/[^/]+\/issues\/(\d+)/);
|
||||
if (match) {
|
||||
return parseInt(match[1], 10);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
async function closeIssueAsDuplicate(
|
||||
owner: string,
|
||||
repo: string,
|
||||
issueNumber: number,
|
||||
duplicateOfNumber: number,
|
||||
token: string,
|
||||
): Promise<void> {
|
||||
await githubRequest(`/repos/${owner}/${repo}/issues/${issueNumber}`, token, 'PATCH', {
|
||||
labels: ['duplicate'],
|
||||
state: 'closed',
|
||||
state_reason: 'duplicate',
|
||||
});
|
||||
|
||||
await githubRequest(`/repos/${owner}/${repo}/issues/${issueNumber}/comments`, token, 'POST', {
|
||||
body: `This issue has been automatically closed as a duplicate of #${duplicateOfNumber}.
|
||||
|
||||
If this is incorrect, please re-open this issue or create a new one.
|
||||
|
||||
🤖 Generated with [Claude Code](https://claude.ai/code)`,
|
||||
});
|
||||
}
|
||||
|
||||
async function autoCloseDuplicates(): Promise<void> {
|
||||
console.log('[DEBUG] Starting auto-close duplicates script');
|
||||
|
||||
const token = process.env.GITHUB_TOKEN;
|
||||
if (!token) {
|
||||
throw new Error('GITHUB_TOKEN environment variable is required');
|
||||
}
|
||||
console.log('[DEBUG] GitHub token found');
|
||||
|
||||
const owner = process.env.GITHUB_REPOSITORY_OWNER || 'lobehub';
|
||||
const repo = process.env.GITHUB_REPOSITORY_NAME || 'lobe-chat';
|
||||
console.log(`[DEBUG] Repository: ${owner}/${repo}`);
|
||||
|
||||
const threeDaysAgo = new Date();
|
||||
threeDaysAgo.setDate(threeDaysAgo.getDate() - 3);
|
||||
console.log(`[DEBUG] Checking for duplicate comments older than: ${threeDaysAgo.toISOString()}`);
|
||||
|
||||
console.log('[DEBUG] Fetching open issues created more than 3 days ago...');
|
||||
const allIssues: GitHubIssue[] = [];
|
||||
let page = 1;
|
||||
const perPage = 100;
|
||||
|
||||
// eslint-disable-next-line no-constant-condition
|
||||
while (true) {
|
||||
const pageIssues: GitHubIssue[] = await githubRequest(
|
||||
`/repos/${owner}/${repo}/issues?state=open&per_page=${perPage}&page=${page}`,
|
||||
token,
|
||||
);
|
||||
|
||||
if (pageIssues.length === 0) break;
|
||||
|
||||
// Filter for issues created more than 3 days ago
|
||||
const oldEnoughIssues = pageIssues.filter(
|
||||
(issue) => new Date(issue.created_at) <= threeDaysAgo,
|
||||
);
|
||||
|
||||
allIssues.push(...oldEnoughIssues);
|
||||
page++;
|
||||
|
||||
// Safety limit to avoid infinite loops
|
||||
if (page > 20) break;
|
||||
}
|
||||
|
||||
const issues = allIssues;
|
||||
console.log(`[DEBUG] Found ${issues.length} open issues`);
|
||||
|
||||
let processedCount = 0;
|
||||
let candidateCount = 0;
|
||||
|
||||
for (const issue of issues) {
|
||||
processedCount++;
|
||||
console.log(
|
||||
`[DEBUG] Processing issue #${issue.number} (${processedCount}/${issues.length}): ${issue.title}`,
|
||||
);
|
||||
|
||||
console.log(`[DEBUG] Fetching comments for issue #${issue.number}...`);
|
||||
const comments: GitHubComment[] = await githubRequest(
|
||||
`/repos/${owner}/${repo}/issues/${issue.number}/comments`,
|
||||
token,
|
||||
);
|
||||
console.log(`[DEBUG] Issue #${issue.number} has ${comments.length} comments`);
|
||||
|
||||
const dupeComments = comments.filter(
|
||||
(comment) =>
|
||||
comment.body.includes('Found') &&
|
||||
comment.body.includes('possible duplicate') &&
|
||||
comment.user.type === 'Bot',
|
||||
);
|
||||
console.log(
|
||||
`[DEBUG] Issue #${issue.number} has ${dupeComments.length} duplicate detection comments`,
|
||||
);
|
||||
|
||||
if (dupeComments.length === 0) {
|
||||
console.log(`[DEBUG] Issue #${issue.number} - no duplicate comments found, skipping`);
|
||||
continue;
|
||||
}
|
||||
|
||||
const lastDupeComment = dupeComments.at(-1);
|
||||
// @ts-ignore
|
||||
const dupeCommentDate = new Date(lastDupeComment.created_at);
|
||||
console.log(
|
||||
`[DEBUG] Issue #${
|
||||
issue.number
|
||||
} - most recent duplicate comment from: ${dupeCommentDate.toISOString()}`,
|
||||
);
|
||||
|
||||
if (dupeCommentDate > threeDaysAgo) {
|
||||
console.log(`[DEBUG] Issue #${issue.number} - duplicate comment is too recent, skipping`);
|
||||
continue;
|
||||
}
|
||||
console.log(
|
||||
`[DEBUG] Issue #${issue.number} - duplicate comment is old enough (${Math.floor(
|
||||
(Date.now() - dupeCommentDate.getTime()) / (1000 * 60 * 60 * 24),
|
||||
)} days)`,
|
||||
);
|
||||
|
||||
const commentsAfterDupe = comments.filter(
|
||||
(comment) => new Date(comment.created_at) > dupeCommentDate,
|
||||
);
|
||||
console.log(
|
||||
`[DEBUG] Issue #${issue.number} - ${commentsAfterDupe.length} comments after duplicate detection`,
|
||||
);
|
||||
|
||||
if (commentsAfterDupe.length > 0) {
|
||||
console.log(
|
||||
`[DEBUG] Issue #${issue.number} - has activity after duplicate comment, skipping`,
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
console.log(`[DEBUG] Issue #${issue.number} - checking reactions on duplicate comment...`);
|
||||
const reactions: GitHubReaction[] = await githubRequest(
|
||||
// @ts-ignore
|
||||
`/repos/${owner}/${repo}/issues/comments/${lastDupeComment.id}/reactions`,
|
||||
token,
|
||||
);
|
||||
console.log(
|
||||
`[DEBUG] Issue #${issue.number} - duplicate comment has ${reactions.length} reactions`,
|
||||
);
|
||||
|
||||
const authorThumbsDown = reactions.some(
|
||||
(reaction) => reaction.user.id === issue.user.id && reaction.content === '-1',
|
||||
);
|
||||
console.log(
|
||||
`[DEBUG] Issue #${issue.number} - author thumbs down reaction: ${authorThumbsDown}`,
|
||||
);
|
||||
|
||||
if (authorThumbsDown) {
|
||||
console.log(
|
||||
`[DEBUG] Issue #${issue.number} - author disagreed with duplicate detection, skipping`,
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
// @ts-ignore
|
||||
const duplicateIssueNumber = extractDuplicateIssueNumber(lastDupeComment.body);
|
||||
if (!duplicateIssueNumber) {
|
||||
console.log(
|
||||
`[DEBUG] Issue #${issue.number} - could not extract duplicate issue number from comment, skipping`,
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
candidateCount++;
|
||||
const issueUrl = `https://github.com/${owner}/${repo}/issues/${issue.number}`;
|
||||
|
||||
try {
|
||||
console.log(
|
||||
`[INFO] Auto-closing issue #${issue.number} as duplicate of #${duplicateIssueNumber}: ${issueUrl}`,
|
||||
);
|
||||
await closeIssueAsDuplicate(owner, repo, issue.number, duplicateIssueNumber, token);
|
||||
console.log(
|
||||
`[SUCCESS] Successfully closed issue #${issue.number} as duplicate of #${duplicateIssueNumber}`,
|
||||
);
|
||||
} catch (error) {
|
||||
console.error(`[ERROR] Failed to close issue #${issue.number} as duplicate: ${error}`);
|
||||
}
|
||||
}
|
||||
|
||||
console.log(
|
||||
`[DEBUG] Script completed. Processed ${processedCount} issues, found ${candidateCount} candidates for auto-close`,
|
||||
);
|
||||
}
|
||||
|
||||
// eslint-disable-next-line unicorn/prefer-top-level-await
|
||||
autoCloseDuplicates().catch(console.error);
|
||||
|
||||
// Make it a module
|
||||
export {};
|
||||
33
.github/workflows/claude-dedupe-issues.yml
vendored
Normal file
33
.github/workflows/claude-dedupe-issues.yml
vendored
Normal file
@@ -0,0 +1,33 @@
|
||||
name: Claude Issue Dedupe
|
||||
description: Automatically dedupe GitHub issues using Claude Code
|
||||
on:
|
||||
issues:
|
||||
types: [opened]
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
issue_number:
|
||||
description: 'Issue number to process for duplicate detection'
|
||||
required: true
|
||||
type: string
|
||||
|
||||
jobs:
|
||||
claude-dedupe-issues:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 10
|
||||
permissions:
|
||||
contents: read
|
||||
issues: write
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v5
|
||||
with:
|
||||
fetch-depth: 1
|
||||
|
||||
- name: Run Claude Code slash command
|
||||
uses: anthropics/claude-code-base-action@beta
|
||||
with:
|
||||
prompt: '/dedupe ${{ github.repository }}/issues/${{ github.event.issue.number || inputs.issue_number }}'
|
||||
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
|
||||
claude_env: |
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
199
.github/workflows/claude-issue-triage.yml
vendored
Normal file
199
.github/workflows/claude-issue-triage.yml
vendored
Normal file
@@ -0,0 +1,199 @@
|
||||
name: Claude Issue Triage
|
||||
description: Automatically triage GitHub issues using Claude Code
|
||||
on:
|
||||
issues:
|
||||
types: [opened, labeled]
|
||||
|
||||
jobs:
|
||||
triage-issue:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 10
|
||||
# Only run on issue opened, or when "trigger:triage" label is added
|
||||
if: github.event.action == 'opened' || (github.event.action == 'labeled' && github.event.label.name == 'trigger:triage')
|
||||
permissions:
|
||||
contents: read
|
||||
issues: write
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v5
|
||||
|
||||
- name: Copy team assignment guide
|
||||
run: |
|
||||
mkdir -p /tmp/claude-prompts
|
||||
cp .claude/prompts/team-assignment.md /tmp/claude-prompts/
|
||||
|
||||
- name: Create triage prompt
|
||||
run: |
|
||||
mkdir -p /tmp/claude-prompts
|
||||
cat > /tmp/claude-prompts/triage-prompt.md << 'EOF'
|
||||
You're an issue triage assistant for GitHub issues. Your task is to analyze issues, apply appropriate labels, and mention the responsible team member.
|
||||
|
||||
REPOSITORY: ${{ github.repository }}
|
||||
ISSUE_NUMBER: ${{ github.event.issue.number }}
|
||||
|
||||
## WORKFLOW
|
||||
|
||||
### Step 1: Get Available Labels
|
||||
```bash
|
||||
gh label list --json name,description --limit 300
|
||||
```
|
||||
|
||||
### Step 2: Get Issue Details
|
||||
```bash
|
||||
gh issue view ${{ github.event.issue.number }} --json number,title,body,labels,comments
|
||||
```
|
||||
|
||||
### Step 3: Read Team Assignment Guide
|
||||
```bash
|
||||
cat /tmp/claude-prompts/team-assignment.md
|
||||
```
|
||||
|
||||
### Step 4: Analyze and Select Labels
|
||||
|
||||
Extract information from the issue template:
|
||||
- 📦 Platform field → platform:web/desktop/mobile
|
||||
- 💻 Operating System → os:windows/macos/linux/ios
|
||||
- 🌐 Browser → device:pc/mobile
|
||||
- 📦 Deployment mode → deployment:server/client/pglite
|
||||
- Platform (hosting) → hosting:cloud/self-host/vercel/zeabur/railway
|
||||
|
||||
**LABEL CATEGORIES:**
|
||||
|
||||
**a) Issue Type (REQUIRED - select ONE):**
|
||||
- 🐛 Bug, 🌠 Feature Request, 💄 Design, 📝 Documentation, ⚡️ Performance
|
||||
|
||||
**b) Priority (select ONE if applicable):**
|
||||
- priority:high - Critical, maintainer mentions "urgent"/"serious"/"critical"
|
||||
- priority:medium - Important, affects multiple users
|
||||
- priority:low - Nice to have, minor issues
|
||||
|
||||
**c) Platform (select ALL applicable):**
|
||||
- platform:web, platform:desktop, platform:mobile
|
||||
|
||||
**d) Device (for platform:web, select ONE):**
|
||||
- device:pc, device:mobile
|
||||
|
||||
**e) Operating System (select ALL applicable):**
|
||||
- os:windows, os:macos, os:linux, os:ios
|
||||
|
||||
**f) Hosting Platform (select ONE):**
|
||||
- hosting:cloud, hosting:self-host, hosting:vercel, hosting:zeabur, hosting:railway
|
||||
|
||||
**g) Deployment Mode (select ONE if mentioned):**
|
||||
- deployment:server, deployment:client, deployment:pglite
|
||||
|
||||
**h) Model Provider (select ALL applicable):**
|
||||
- provider:openai, provider:gemini, provider:claude, provider:deepseek
|
||||
- provider:google, provider:ollama, provider:azure, provider:bedrock, provider:vertex
|
||||
|
||||
**i) Feature/Component (select ALL applicable):**
|
||||
- feature:settings, feature:agent, feature:topic, feature:marketplace
|
||||
- feature:streaming, feature:tool, feature:sync, feature:export
|
||||
- feature:search, feature:auth, feature:files, feature:knowledge-base
|
||||
- feature:tts, feature:vision, feature:mcp, feature:editor, feature:thread
|
||||
- feature:image, feature:api, feature:dalle, feature:plugin, feature:markdown
|
||||
- feature:group-chat, feature:memory, feature:team-workspace
|
||||
|
||||
**j) Workflow/Status:**
|
||||
- Duplicate - Only if duplicate of OPEN issue
|
||||
- needs-reproduction, good-first-issue, 🤔 Need Reproduce
|
||||
|
||||
**IMPORTANT RULES:**
|
||||
- Read issue template fields carefully
|
||||
- Check maintainer comments for priority/status
|
||||
- Use ALL applicable labels from different categories
|
||||
- Always use prefixes (feature:, provider:, os:, platform:, etc.)
|
||||
|
||||
### Step 5: Apply Labels
|
||||
|
||||
Add labels:
|
||||
```bash
|
||||
gh issue edit ${{ github.event.issue.number }} --add-label "label1,label2,label3"
|
||||
```
|
||||
|
||||
Remove "unconfirm" if adding other labels:
|
||||
```bash
|
||||
gh issue edit ${{ github.event.issue.number }} --remove-label "unconfirm"
|
||||
```
|
||||
|
||||
**Execute the commands now.**
|
||||
|
||||
### Step 6: Determine Team Member
|
||||
|
||||
Based on the team assignment guide and applied labels, determine who to mention.
|
||||
|
||||
**Priority Order:**
|
||||
1. Specific feature owner (feature:knowledge-base → @RiverTwilight)
|
||||
2. Platform owner (platform:mobile → @sudongyuer)
|
||||
3. Provider owner (provider:* → @sxjeru)
|
||||
4. Component owner (💄 Design → @canisminor1990)
|
||||
5. Infrastructure owner (deployment:* → @nekomeowww)
|
||||
6. General maintainer (@ONLY-yours)
|
||||
7. Last resort (@arvinxx)
|
||||
|
||||
**Special Cases:**
|
||||
- Multiple owners: Mention primary + secondary
|
||||
- priority:high: Mention owner + @arvinxx
|
||||
|
||||
### Step 7: Post Comment
|
||||
|
||||
Format the comment (1-2 sentences):
|
||||
|
||||
**Single owner:**
|
||||
```
|
||||
@username - This is a [feature/component] issue. Please take a look.
|
||||
```
|
||||
|
||||
**Multiple owners:**
|
||||
```
|
||||
@user1 @user2 - This involves [features]. Please coordinate.
|
||||
```
|
||||
|
||||
**High priority:**
|
||||
```
|
||||
@owner @arvinxx - High priority [feature] issue.
|
||||
```
|
||||
|
||||
Post the comment:
|
||||
```bash
|
||||
gh issue comment ${{ github.event.issue.number }} --body "@username - [message]"
|
||||
```
|
||||
|
||||
**Execute the command now.**
|
||||
|
||||
### Step 8: Output Summary
|
||||
|
||||
Log your reasoning (2-4 sentences):
|
||||
- Labels applied and why
|
||||
- Team member(s) mentioned and reason
|
||||
- Key factors from issue template/comments
|
||||
|
||||
## GUIDELINES
|
||||
|
||||
- Be thorough in analysis
|
||||
- Use only labels from the provided list
|
||||
- Extract info from issue template fields
|
||||
- ALWAYS post a mention comment (unless no clear owner)
|
||||
- Keep comments professional and brief
|
||||
- Output reasoning to logs
|
||||
|
||||
**Start the triage process now.**
|
||||
EOF
|
||||
|
||||
- name: Run Claude Code for Issue Triage
|
||||
uses: anthropics/claude-code-base-action@beta
|
||||
with:
|
||||
prompt_file: /tmp/claude-prompts/triage-prompt.md
|
||||
allowed_tools: "Bash(gh *),Read"
|
||||
timeout_minutes: "5"
|
||||
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
|
||||
claude_env: |
|
||||
GH_TOKEN: ${{ secrets.GH_TOKEN }}
|
||||
|
||||
- name: Remove trigger label
|
||||
if: github.event.action == 'labeled' && github.event.label.name == 'trigger:triage'
|
||||
run: |
|
||||
gh issue edit ${{ github.event.issue.number }} --remove-label "trigger:triage"
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GH_TOKEN }}
|
||||
6
.github/workflows/desktop-pr-build.yml
vendored
6
.github/workflows/desktop-pr-build.yml
vendored
@@ -18,8 +18,8 @@ env:
|
||||
jobs:
|
||||
test:
|
||||
name: Code quality check
|
||||
# 添加 PR label 触发条件,只有添加了 Build Desktop 标签的 PR 才会触发构建
|
||||
if: contains(github.event.pull_request.labels.*.name, 'Build Desktop')
|
||||
# 添加 PR label 触发条件,只有添加了 trigger:build-desktop 标签的 PR 才会触发构建
|
||||
if: contains(github.event.pull_request.labels.*.name, 'trigger:build-desktop')
|
||||
runs-on: ubuntu-latest # 只在 ubuntu 上运行一次检查
|
||||
steps:
|
||||
- name: Checkout base
|
||||
@@ -51,7 +51,7 @@ jobs:
|
||||
version:
|
||||
name: Determine version
|
||||
# 与 test job 相同的触发条件
|
||||
if: contains(github.event.pull_request.labels.*.name, 'Build Desktop')
|
||||
if: contains(github.event.pull_request.labels.*.name, 'trigger:build-desktop')
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
# 输出版本信息,供后续 job 使用
|
||||
|
||||
2
.github/workflows/docker-database.yml
vendored
2
.github/workflows/docker-database.yml
vendored
@@ -22,7 +22,7 @@ jobs:
|
||||
# 添加 PR label 触发条件
|
||||
if: |
|
||||
(github.event_name == 'pull_request' &&
|
||||
contains(github.event.pull_request.labels.*.name, 'Build Docker')) ||
|
||||
contains(github.event.pull_request.labels.*.name, 'trigger:build-docker')) ||
|
||||
github.event_name != 'pull_request'
|
||||
|
||||
strategy:
|
||||
|
||||
2
.github/workflows/docker-pglite.yml
vendored
2
.github/workflows/docker-pglite.yml
vendored
@@ -22,7 +22,7 @@ jobs:
|
||||
# 添加 PR label 触发条件
|
||||
if: |
|
||||
(github.event_name == 'pull_request' &&
|
||||
contains(github.event.pull_request.labels.*.name, 'Build Docker')) ||
|
||||
contains(github.event.pull_request.labels.*.name, 'trigger:build-docker')) ||
|
||||
github.event_name != 'pull_request'
|
||||
|
||||
strategy:
|
||||
|
||||
2
.github/workflows/docker.yml
vendored
2
.github/workflows/docker.yml
vendored
@@ -22,7 +22,7 @@ jobs:
|
||||
# 添加 PR label 触发条件
|
||||
if: |
|
||||
(github.event_name == 'pull_request' &&
|
||||
contains(github.event.pull_request.labels.*.name, 'Build Docker')) ||
|
||||
contains(github.event.pull_request.labels.*.name, 'trigger:build-docker')) ||
|
||||
github.event_name != 'pull_request'
|
||||
|
||||
strategy:
|
||||
|
||||
30
.github/workflows/issue-auto-close-duplicates.yml
vendored
Normal file
30
.github/workflows/issue-auto-close-duplicates.yml
vendored
Normal file
@@ -0,0 +1,30 @@
|
||||
name: Auto-close duplicate issues
|
||||
description: Auto-closes issues that are duplicates of existing issues
|
||||
on:
|
||||
schedule:
|
||||
- cron: "0 2 * * *"
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
auto-close-duplicates:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 10
|
||||
permissions:
|
||||
contents: read
|
||||
issues: write
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v5
|
||||
|
||||
- name: Setup Bun
|
||||
uses: oven-sh/setup-bun@v2
|
||||
with:
|
||||
bun-version: latest
|
||||
|
||||
- name: Auto-close duplicate issues
|
||||
run: bun run .github/scripts/auto-close-duplicates.ts
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GH_TOKEN }}
|
||||
GITHUB_REPOSITORY_OWNER: ${{ github.repository_owner }}
|
||||
GITHUB_REPOSITORY_NAME: ${{ github.event.repository.name }}
|
||||
2
.github/workflows/release-desktop-beta.yml
vendored
2
.github/workflows/release-desktop-beta.yml
vendored
@@ -15,7 +15,7 @@ permissions: read-all
|
||||
jobs:
|
||||
test:
|
||||
name: Code quality check
|
||||
# 添加 PR label 触发条件,只有添加了 Build Desktop 标签的 PR 才会触发构建
|
||||
# 添加 PR label 触发条件,只有添加了 trigger:build-desktop 标签的 PR 才会触发构建
|
||||
runs-on: ubuntu-latest # 只在 ubuntu 上运行一次检查
|
||||
steps:
|
||||
- name: Checkout base
|
||||
|
||||
Reference in New Issue
Block a user