Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 32 additions & 2 deletions .github/scripts/conflict_detector.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,34 @@ const IGNORED_CONFLICT_FILES = [
'residual-trend-history.ndjson',
];

// Comments from automation often mention "conflict" but should not block execution.
const IGNORED_COMMENT_AUTHORS = new Set([
'github-actions[bot]',
'github-merge-queue[bot]',
'dependabot[bot]',
'github',
]);

const IGNORED_COMMENT_MARKERS = [
'<!-- keepalive-state',
'keepalive-loop-summary',
'auto-status-summary',
];

function isIgnoredComment(comment) {
if (!comment) {
return false;
}

const author = comment.user?.login || '';
if (comment.user?.type === 'Bot' || IGNORED_COMMENT_AUTHORS.has(author)) {
return true;
}

const body = comment.body || '';
return IGNORED_COMMENT_MARKERS.some((marker) => body.includes(marker));
}

/**
* Check if a file should be excluded from conflict detection.
* @param {string} filename - File path to check
Expand Down Expand Up @@ -223,8 +251,10 @@ async function checkCommentsForConflicts(github, context, prNumber) {
per_page: 20,
});

// Check recent comments (last 10)
const recentComments = comments.slice(-10);
// Check recent comments (last 10) and ignore bot/system noise
const recentComments = comments
.filter((comment) => !isIgnoredComment(comment))
.slice(-10);

for (const comment of recentComments) {
for (const pattern of CONFLICT_PATTERNS) {
Expand Down
3 changes: 3 additions & 0 deletions .github/scripts/error_classifier.js
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,9 @@ function classifyByMessage(message) {

function classifyError(error) {
const message = normaliseMessage(error);
const preview = message ? message.slice(0, 50) : 'unknown';
// eslint-disable-next-line no-console
console.log(`[error_classifier] Classifying error: ${preview}`);
const status = getStatusCode(error);

const statusCategory = status ? classifyByStatus(status, message) : null;
Expand Down
5 changes: 5 additions & 0 deletions .github/scripts/parse_chatgpt_topics.py
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,11 @@ def _parse_sections(
"tasks": {"tasks"},
"acceptance_criteria": {"acceptance criteria", "acceptance criteria."},
"implementation_notes": {
"admin access",
"admin requirement",
"admin requirements",
"dependencies",
"dependency",
"implementation notes",
"implementation note",
"notes",
Expand Down
44 changes: 25 additions & 19 deletions .github/workflows/agents-auto-label.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,20 +27,16 @@ jobs:
!contains(github.event.issue.labels.*.name, 'automated')

steps:
- name: Checkout Workflows repo
uses: actions/checkout@v6
with:
repository: stranske/Workflows
path: workflows-repo
- name: Checkout repository
uses: actions/checkout@v4

- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: "3.12"
python-version: "3.11"

- name: Install dependencies
run: |
cd workflows-repo
pip install -e ".[langchain]" --quiet

- name: Get repo labels
Expand Down Expand Up @@ -74,8 +70,8 @@ jobs:
LABELS_JSON: ${{ steps.get-labels.outputs.labels_json }}
ISSUE_TITLE: ${{ github.event.issue.title }}
ISSUE_BODY: ${{ github.event.issue.body }}
PYTHONPATH: ${{ github.workspace }}
run: |
cd workflows-repo
python3 << 'PYTHON_SCRIPT'
import json
import os
Expand Down Expand Up @@ -135,6 +131,16 @@ jobs:
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]

# IMPORTANT: Only auto-apply the BEST matching label, not all above threshold
# This prevents over-labeling issues with multiple labels like bug+enhancement
if auto_apply:
best_match = auto_apply[0] # matches are already sorted by score descending
auto_apply = [best_match]
# Move other high-confidence matches to suggestions
for m in matches[1:]:
if m.score >= auto_threshold and m not in suggestions:
suggestions.insert(0, m)

print(f"Auto-apply labels ({auto_threshold}+ confidence):")
for m in auto_apply:
print(f" - {m.label.name}: {m.score:.2%}")
Expand All @@ -144,15 +150,15 @@ jobs:
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')
auto_json = json.dumps([m.label.name for m in auto_apply])
f.write(f'auto_apply_labels={auto_json}\n')
sugg_data = [
{"name": m.label.name, "score": f"{m.score:.0%}"}
for m in suggestions
]
f.write(f'suggested_labels={json.dumps(sugg_data)}\n')

PYTHON_SCRIPT

Expand Down Expand Up @@ -220,14 +226,14 @@ jobs:
body += `${suggestions}\n\n`;

if (autoApplied.length > 0) {
const appliedStr = autoApplied.map(l => `\`${l}\``).join(', ');
body += `**Auto-applied:** ${appliedStr}\n\n`;
const applied = autoApplied.map(l => `\`${l}\``).join(', ');
body += `**Auto-applied:** ${applied}\n\n`;
}

body += `<details>\n<summary>How to use these suggestions</summary>\n\n`;
body += `- Click the label name in the sidebar to add it\n`;
const ghCmd = `gh issue edit ${context.issue.number} --add-label "label-name"`;
body += `- Or use the GitHub CLI: \`${ghCmd}\`\n`;
const editCmd = `gh issue edit ${context.issue.number} --add-label "label-name"`;
body += `- Or use the GitHub CLI: \`${editCmd}\`\n`;
body += `</details>\n\n`;
body += `---\n*Auto-generated by label matcher*`;

Expand Down
Loading
Loading