Repository navigation
chore: sync workflow templates #128
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,265 @@ | ||||||||||||||||||||||||||||||||
| name: Auto-Label Issues | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| # Suggests or applies labels to new issues based on semantic matching | ||||||||||||||||||||||||||||||||
| # Uses label_matcher.py for embedding-based similarity | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| on: | ||||||||||||||||||||||||||||||||
| issues: | ||||||||||||||||||||||||||||||||
| types: [opened, edited] | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| permissions: | ||||||||||||||||||||||||||||||||
| contents: read | ||||||||||||||||||||||||||||||||
| issues: write | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| env: | ||||||||||||||||||||||||||||||||
| # Threshold for auto-applying labels (very high confidence) | ||||||||||||||||||||||||||||||||
| AUTO_APPLY_THRESHOLD: "0.90" | ||||||||||||||||||||||||||||||||
| # Threshold for suggesting labels (lower, for comments) | ||||||||||||||||||||||||||||||||
| SUGGEST_THRESHOLD: "0.75" | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| jobs: | ||||||||||||||||||||||||||||||||
| auto-label: | ||||||||||||||||||||||||||||||||
| runs-on: ubuntu-latest | ||||||||||||||||||||||||||||||||
| # Skip if issue already has agent-related labels | ||||||||||||||||||||||||||||||||
| if: | | ||||||||||||||||||||||||||||||||
| !contains(github.event.issue.labels.*.name, 'agents:formatted') && | ||||||||||||||||||||||||||||||||
| !contains(github.event.issue.labels.*.name, 'agent:codex') && | ||||||||||||||||||||||||||||||||
| !contains(github.event.issue.labels.*.name, 'automated') | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| steps: | ||||||||||||||||||||||||||||||||
| - name: Checkout Workflows repo | ||||||||||||||||||||||||||||||||
| uses: actions/checkout@v6 | ||||||||||||||||||||||||||||||||
| with: | ||||||||||||||||||||||||||||||||
| repository: stranske/Workflows | ||||||||||||||||||||||||||||||||
| path: workflows-repo | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| - name: Set up Python | ||||||||||||||||||||||||||||||||
| uses: actions/setup-python@v5 | ||||||||||||||||||||||||||||||||
| with: | ||||||||||||||||||||||||||||||||
| python-version: "3.12" | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| - name: Install dependencies | ||||||||||||||||||||||||||||||||
| run: | | ||||||||||||||||||||||||||||||||
| cd workflows-repo | ||||||||||||||||||||||||||||||||
| pip install -e ".[langchain]" --quiet | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| - name: Get repo labels | ||||||||||||||||||||||||||||||||
| id: get-labels | ||||||||||||||||||||||||||||||||
| uses: actions/github-script@v8 | ||||||||||||||||||||||||||||||||
| with: | ||||||||||||||||||||||||||||||||
| script: | | ||||||||||||||||||||||||||||||||
| // Paginate to get all labels (handles repos with >100 labels) | ||||||||||||||||||||||||||||||||
| const labels = await github.paginate( | ||||||||||||||||||||||||||||||||
| github.rest.issues.listLabelsForRepo, | ||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||
| owner: context.repo.owner, | ||||||||||||||||||||||||||||||||
| repo: context.repo.repo, | ||||||||||||||||||||||||||||||||
| per_page: 100 | ||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| const labelData = labels.map(l => ({ | ||||||||||||||||||||||||||||||||
| name: l.name, | ||||||||||||||||||||||||||||||||
| description: l.description || '' | ||||||||||||||||||||||||||||||||
| })); | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| core.setOutput('labels_json', JSON.stringify(labelData)); | ||||||||||||||||||||||||||||||||
| core.info(`Found ${labels.length} labels in repo`); | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| - name: Match labels | ||||||||||||||||||||||||||||||||
| id: match | ||||||||||||||||||||||||||||||||
| env: | ||||||||||||||||||||||||||||||||
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | ||||||||||||||||||||||||||||||||
| OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }} | ||||||||||||||||||||||||||||||||
| LABELS_JSON: ${{ steps.get-labels.outputs.labels_json }} | ||||||||||||||||||||||||||||||||
| ISSUE_TITLE: ${{ github.event.issue.title }} | ||||||||||||||||||||||||||||||||
| ISSUE_BODY: ${{ github.event.issue.body }} | ||||||||||||||||||||||||||||||||
| run: | | ||||||||||||||||||||||||||||||||
| cd workflows-repo | ||||||||||||||||||||||||||||||||
| python3 << 'PYTHON_SCRIPT' | ||||||||||||||||||||||||||||||||
| import json | ||||||||||||||||||||||||||||||||
| import os | ||||||||||||||||||||||||||||||||
| import sys | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| # Add scripts to path | ||||||||||||||||||||||||||||||||
| sys.path.insert(0, '.') | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| from scripts.langchain.label_matcher import ( | ||||||||||||||||||||||||||||||||
| build_label_vector_store, | ||||||||||||||||||||||||||||||||
| find_similar_labels, | ||||||||||||||||||||||||||||||||
| LabelRecord, | ||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| # Get issue content | ||||||||||||||||||||||||||||||||
| issue_title = os.environ.get('ISSUE_TITLE', '') | ||||||||||||||||||||||||||||||||
| issue_body = os.environ.get('ISSUE_BODY', '') | ||||||||||||||||||||||||||||||||
| query = f"{issue_title}\n\n{issue_body}" | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| # Get thresholds | ||||||||||||||||||||||||||||||||
| auto_threshold = float(os.environ.get('AUTO_APPLY_THRESHOLD', '0.90')) | ||||||||||||||||||||||||||||||||
| suggest_threshold = float(os.environ.get('SUGGEST_THRESHOLD', '0.75')) | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| # Parse labels | ||||||||||||||||||||||||||||||||
| labels_json = os.environ.get('LABELS_JSON', '[]') | ||||||||||||||||||||||||||||||||
| labels = json.loads(labels_json) | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| if not labels: | ||||||||||||||||||||||||||||||||
| print("No labels found in repo") | ||||||||||||||||||||||||||||||||
| with open(os.environ['GITHUB_OUTPUT'], 'a') as f: | ||||||||||||||||||||||||||||||||
| f.write('has_suggestions=false\n') | ||||||||||||||||||||||||||||||||
| sys.exit(0) | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| # Build vector store | ||||||||||||||||||||||||||||||||
| label_records = [ | ||||||||||||||||||||||||||||||||
| LabelRecord(name=l['name'], description=l['description']) | ||||||||||||||||||||||||||||||||
| for l in labels | ||||||||||||||||||||||||||||||||
| ] | ||||||||||||||||||||||||||||||||
| store = build_label_vector_store(label_records) | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| if store is None: | ||||||||||||||||||||||||||||||||
| print("Could not build label vector store (missing embeddings)") | ||||||||||||||||||||||||||||||||
| with open(os.environ['GITHUB_OUTPUT'], 'a') as f: | ||||||||||||||||||||||||||||||||
| f.write('has_suggestions=false\n') | ||||||||||||||||||||||||||||||||
| sys.exit(0) | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| # Find matches | ||||||||||||||||||||||||||||||||
| matches = find_similar_labels(store, query, threshold=suggest_threshold, k=5) | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| if not matches: | ||||||||||||||||||||||||||||||||
| print("No label matches found above threshold") | ||||||||||||||||||||||||||||||||
| with open(os.environ['GITHUB_OUTPUT'], 'a') as f: | ||||||||||||||||||||||||||||||||
| f.write('has_suggestions=false\n') | ||||||||||||||||||||||||||||||||
| sys.exit(0) | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| # Separate auto-apply from suggestions | ||||||||||||||||||||||||||||||||
| auto_apply = [m for m in matches if m.score >= auto_threshold] | ||||||||||||||||||||||||||||||||
| suggestions = [m for m in matches if suggest_threshold <= m.score < auto_threshold] | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| print(f"Auto-apply labels ({auto_threshold}+ confidence):") | ||||||||||||||||||||||||||||||||
| for m in auto_apply: | ||||||||||||||||||||||||||||||||
| print(f" - {m.label.name}: {m.score:.2%}") | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| print(f"Suggested labels ({suggest_threshold}-{auto_threshold} confidence):") | ||||||||||||||||||||||||||||||||
| for m in suggestions: | ||||||||||||||||||||||||||||||||
| print(f" - {m.label.name}: {m.score:.2%}") | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| # Output results | ||||||||||||||||||||||||||||||||
| auto_labels = json.dumps([m.label.name for m in auto_apply]) | ||||||||||||||||||||||||||||||||
| suggest_json = json.dumps([ | ||||||||||||||||||||||||||||||||
| {'name': m.label.name, 'score': f'{m.score:.0%}'} | ||||||||||||||||||||||||||||||||
| for m in suggestions | ||||||||||||||||||||||||||||||||
| ]) | ||||||||||||||||||||||||||||||||
| with open(os.environ['GITHUB_OUTPUT'], 'a') as f: | ||||||||||||||||||||||||||||||||
| f.write('has_suggestions=true\n') | ||||||||||||||||||||||||||||||||
| f.write(f'auto_apply_labels={auto_labels}\n') | ||||||||||||||||||||||||||||||||
| f.write(f'suggested_labels={suggest_json}\n') | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| PYTHON_SCRIPT | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| - name: Apply high-confidence labels | ||||||||||||||||||||||||||||||||
| if: | | ||||||||||||||||||||||||||||||||
| steps.match.outputs.has_suggestions == 'true' && | ||||||||||||||||||||||||||||||||
| steps.match.outputs.auto_apply_labels != '[]' | ||||||||||||||||||||||||||||||||
| uses: actions/github-script@v8 | ||||||||||||||||||||||||||||||||
| with: | ||||||||||||||||||||||||||||||||
| script: | | ||||||||||||||||||||||||||||||||
| const autoApplyLabels = JSON.parse('${{ steps.match.outputs.auto_apply_labels }}'); | ||||||||||||||||||||||||||||||||
|
Comment on lines
+164
to
+166
|
||||||||||||||||||||||||||||||||
| with: | |
| script: | | |
| const autoApplyLabels = JSON.parse('${{ steps.match.outputs.auto_apply_labels }}'); | |
| env: | |
| AUTO_APPLY_LABELS: ${{ steps.match.outputs.auto_apply_labels }} | |
| with: | |
| script: | | |
| let autoApplyLabelsRaw = process.env.AUTO_APPLY_LABELS; | |
| let autoApplyLabels; | |
| try { | |
| autoApplyLabels = JSON.parse(autoApplyLabelsRaw || '[]'); | |
| } catch (error) { | |
| core.setFailed(`Failed to parse AUTO_APPLY_LABELS: ${error}`); | |
| return; | |
| } |
Copilot
AI
Jan 8, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The JSON.parse call uses a template literal to directly parse step outputs. If the suggested_labels output contains special characters or is malformed, this will cause a runtime error. Consider using a safer parsing approach with try-catch error handling or accessing the value through process.env.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This workflow uses the label_matcher.py script which performs embedding-based similarity matching, but it's missing the 'models: read' permission that other similar workflows (agents-dedup.yml, agents-decompose.yml, agents-capability-check.yml) include. If the embedding provider requires this permission, the workflow may fail. Consider adding 'models: read' to the permissions block for consistency.