feat: OpenDesign (nexu-io/open-design) compatibility — MCP today, native runtime adapter upstream #2230
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Issue triage | |
| on: | |
| issues: | |
| types: [opened, reopened] | |
| permissions: | |
| issues: write | |
| contents: read | |
| jobs: | |
| label: | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Auto-label by title and body | |
| uses: actions/github-script@v9 | |
| with: | |
| script: | | |
| const issue = context.payload.issue; | |
| const title = (issue.title || '').toLowerCase(); | |
| const body = (issue.body || '').toLowerCase(); | |
| const text = `${title}\n${body}`; | |
| const labels = new Set(); | |
| // Type — matched against the TITLE only. | |
| // | |
| // These ran against title+body and mislabelled long, well-specified | |
| // issues. Any body containing an acceptance criterion like "fails | |
| // when a pack drifts" matched the bug rule, and a "How to add a | |
| // locale" heading matched the question rule, so planned work landed | |
| // tagged `bug, question`. A title states the type; a body just | |
| // discusses it. `error`/`fail` are dropped entirely — they describe | |
| // what almost every issue talks about, not what any issue *is*. | |
| if (/\b(bug|crash|panic|broken|stack ?trace|regression)\b/.test(title)) labels.add('bug'); | |
| if (/\b(feat(?:ure)?|request|enhancement|wishlist|proposal|please add|would be nice|support for)\b/.test(title)) labels.add('enhancement'); | |
| if (/\b(docs?|readme|documentation|typo|grammar|wording|spelling)\b/.test(title)) labels.add('documentation'); | |
| if (/\b(question|how (?:do|to)|why does|what does|is it possible)\b/.test(title)) labels.add('question'); | |
| // Locale — title contains CJK (Chinese, Japanese, Korean) characters | |
| if (/[-ヿ㐀-鿿가-]/.test(issue.title || '')) labels.add('lang:zh'); | |
| // Areas (path-driven hints) | |
| if (/crates\/tui|\btui\b|ratatui|composer|sidebar/.test(text)) labels.add('area:tui'); | |
| if (/crates\/core|engine|turn ?loop|agent ?loop/.test(text)) labels.add('area:core'); | |
| if (/crates\/mcp|\bmcp\b/.test(text)) labels.add('area:mcp'); | |
| if (/crates\/state|sqlite|sessions?|persistence/.test(text)) labels.add('area:state'); | |
| if (/crates\/execpolicy|approval|sandbox|seatbelt|landlock/.test(text)) labels.add('area:execpolicy'); | |
| if (/crates\/tools|tool[ _]call|tool[ _]registry/.test(text)) labels.add('area:tools'); | |
| if (/install|cargo install|npm install|scoop|homebrew|prebuilt|binary/.test(text)) labels.add('area:install'); | |
| if (/windows/.test(text)) labels.add('os:windows'); | |
| if (/macos|darwin|apple silicon/.test(text)) labels.add('os:macos'); | |
| if (/\blinux\b|ubuntu|debian|fedora|arch ?linux/.test(text)) labels.add('os:linux'); | |
| if (labels.size === 0) return; | |
| // Only add labels that already exist on the repo to avoid creating noise. | |
| const existing = await github.paginate(github.rest.issues.listLabelsForRepo, { | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| per_page: 100, | |
| }); | |
| const existingNames = new Set(existing.map(l => l.name)); | |
| const toAdd = [...labels].filter(name => existingNames.has(name)); | |
| if (toAdd.length === 0) return; | |
| await github.rest.issues.addLabels({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| issue_number: issue.number, | |
| labels: toAdd, | |
| }); |