返回 DeepSeek-Reasonix
issue-auto-label.yml
根目录 / .github / workflows / issue-auto-label.yml
1 name: Auto-label issues
2
3 # Classify new issues into area / platform / severity labels using the DeepSeek
4 # API (the project's own model). The model is constrained to a fixed label set —
5 # anything it returns outside the set is dropped — so it can't invent labels.
6 # Issues it can't place into any area get `needs-triage` for a human to look at.
7 # Version (v1/v2) labels are handled separately by issue-version-label.yml.
8 on:
9 issues:
10 types: [opened, reopened]
11
12 permissions:
13 issues: write
14
15 concurrency:
16 group: issue-autolabel-${{ github.event.issue.number }}
17 cancel-in-progress: true
18
19 jobs:
20 classify:
21 runs-on: ubuntu-latest
22 steps:
23 - uses: actions/github-script@v9
24 env:
25 DEEPSEEK_API_KEY: ${{ secrets.DEEPSEEK_API_KEY }}
26 with:
27 script: |
28 const AREA = ['agent','mcp','config','updater','provider','desktop','tui','skills','rendering'];
29 const PLATFORM = ['windows','macos','linux'];
30 const SEVERITY = ['crash','data-loss','security'];
31 const ALLOWED = new Set([...AREA, ...PLATFORM, ...SEVERITY]);
32
33 const issue = context.payload.issue;
34 const title = issue.title || '';
35 const body = (issue.body || '').slice(0, 4000);
36
37 const system = [
38 'You categorize GitHub issues for Reasonix, a Go-based AI coding agent with a Wails desktop app and a terminal UI.',
39 'Pick labels ONLY from these fixed sets. Never invent labels.',
40 'area (0-2, the affected subsystem):',
41 ' agent: core agent loop / tool-calling / reasoning',
42 ' mcp: MCP servers, plugins, codegraph',
43 ' config: configuration, setup wizard, .toml/.env',
44 ' updater: auto-update, installer, release packaging',
45 ' provider: model providers, model selection/switching',
46 ' desktop: Wails desktop GUI',
47 ' tui: terminal UI / CLI',
48 ' skills: skills system',
49 ' rendering: terminal rendering / flicker / repaint',
50 'platform (only if clearly specific to one OS): windows, macos, linux',
51 'severity (only if clearly applicable):',
52 ' crash: app crashes, hangs, or freezes',
53 ' data-loss: loss of sessions, config, or history',
54 ' security: credential/secret exposure or a security flaw',
55 'Be conservative: omit a label when unsure. The issue may be in Chinese.',
56 'Reply with JSON only: {"area":[],"platform":[],"severity":[]}',
57 ].join('\n');
58
59 let labels = [];
60 try {
61 const res = await fetch('https://api.deepseek.com/chat/completions', {
62 method: 'POST',
63 headers: {
64 'Authorization': `Bearer ${process.env.DEEPSEEK_API_KEY}`,
65 'Content-Type': 'application/json',
66 },
67 body: JSON.stringify({
68 model: 'deepseek-chat',
69 temperature: 0,
70 response_format: { type: 'json_object' },
71 messages: [
72 { role: 'system', content: system },
73 { role: 'user', content: `Title: ${title}\n\nBody:\n${body}` },
74 ],
75 }),
76 });
77 if (!res.ok) {
78 core.warning(`DeepSeek API ${res.status}: ${await res.text()}`);
79 return;
80 }
81 const data = await res.json();
82 const parsed = JSON.parse(data.choices[0].message.content);
83 labels = [...(parsed.area || []), ...(parsed.platform || []), ...(parsed.severity || [])]
84 .filter(l => ALLOWED.has(l));
85 } catch (e) {
86 core.warning(`Classification failed: ${e.message}`);
87 return;
88 }
89
90 if (!labels.some(l => AREA.includes(l))) labels.push('needs-triage');
91
92 if (labels.length) {
93 await github.rest.issues.addLabels({
94 ...context.repo,
95 issue_number: issue.number,
96 labels,
97 });
98 core.info(`Applied: ${labels.join(', ')}`);
99 }
100
100 lines YAML