* Improve senior-fullstack skill description and workflow validation - Expand frontmatter description with concrete actions and trigger clauses - Add validation steps to scaffolding workflow (verify scaffold succeeded) - Add re-run verification step to audit workflow (confirm P0 fixes) * chore: sync codex skills symlinks [automated] * fix(skill): normalize senior-fullstack frontmatter to inline format Normalize YAML description from block scalar (>) to inline single-line format matching all other 50+ skills. Align frontmatter trigger phrases with the body's Trigger Phrases section to eliminate duplication. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix(ci): add GITHUB_TOKEN to checkout + restore corrupted skill descriptions - Add token: ${{ secrets.GITHUB_TOKEN }} to actions/checkout@v4 in sync-codex-skills.yml so git-auto-commit-action can push back to branch (fixes: fatal: could not read Username, exit 128) - Restore correct description for incident-commander (was: 'Skill from engineering-team') - Restore correct description for senior-fullstack (was: '>') * fix(ci): pass PROJECTS_TOKEN to fix automated commits + remove duplicate checkout Fixes PROJECTS_TOKEN passthrough for git-auto-commit-action and removes duplicate checkout step in pr-issue-auto-close workflow. * fix(ci): remove stray merge conflict marker in sync-codex-skills.yml (#221) Co-authored-by: Leo <leo@leo-agent-server> * fix(ci): fix workflow errors + add OpenClaw support (#222) --------- Co-authored-by: Baptiste Fernandez <fernandez.baptiste1@gmail.com> Co-authored-by: alirezarezvani <5697919+alirezarezvani@users.noreply.github.com> Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> Co-authored-by: Leo <leo@leo-agent-server>
201 lines
7.1 KiB
YAML
201 lines
7.1 KiB
YAML
---
|
|
name: Auto-Close Issues on PR Merge
|
|
|
|
'on':
|
|
pull_request:
|
|
types: [closed]
|
|
|
|
permissions:
|
|
issues: write
|
|
pull-requests: read
|
|
contents: read
|
|
|
|
jobs:
|
|
close-linked-issues:
|
|
name: Close Issues Linked in PR
|
|
if: github.event.pull_request.merged == true
|
|
runs-on: ubuntu-latest
|
|
|
|
steps:
|
|
- name: Checkout repository
|
|
uses: actions/checkout@v4
|
|
|
|
- name: Check Workflow Kill Switch
|
|
run: |
|
|
if [ -f ".github/WORKFLOW_KILLSWITCH" ]; then
|
|
STATUS=$(grep "STATUS:" .github/WORKFLOW_KILLSWITCH | awk '{print $2}')
|
|
if [ "$STATUS" = "DISABLED" ]; then
|
|
echo "🛑 Workflows disabled by kill switch"
|
|
exit 0
|
|
fi
|
|
fi
|
|
|
|
- name: Extract linked issues from PR body
|
|
id: extract_issues
|
|
uses: actions/github-script@v7
|
|
with:
|
|
github-token: ${{ secrets.GITHUB_TOKEN }}
|
|
script: |
|
|
const prBody = context.payload.pull_request.body || '';
|
|
const prNumber = context.payload.pull_request.number;
|
|
|
|
// Patterns to detect linked issues
|
|
// Supports: Fixes #123, Closes #456, Resolves #789, Related to #111, See #222
|
|
const patterns = [
|
|
/(?:fix|fixes|fixed|close|closes|closed|resolve|resolves|resolved)\s+#(\d+)/gi,
|
|
/(?:related\s+to|see|ref|references)\s+#(\d+)/gi
|
|
];
|
|
|
|
const issueNumbers = new Set();
|
|
|
|
// Extract issue numbers
|
|
patterns.forEach(pattern => {
|
|
let match;
|
|
while ((match = pattern.exec(prBody)) !== null) {
|
|
issueNumbers.add(match[1]);
|
|
}
|
|
});
|
|
|
|
// Also check PR title
|
|
const prTitle = context.payload.pull_request.title || '';
|
|
patterns.forEach(pattern => {
|
|
let match;
|
|
while ((match = pattern.exec(prTitle)) !== null) {
|
|
issueNumbers.add(match[1]);
|
|
}
|
|
});
|
|
|
|
// Also check commit messages in PR
|
|
const commits = await github.rest.pulls.listCommits({
|
|
owner: context.repo.owner,
|
|
repo: context.repo.repo,
|
|
pull_number: prNumber
|
|
});
|
|
|
|
commits.data.forEach(commit => {
|
|
const message = commit.commit.message;
|
|
patterns.forEach(pattern => {
|
|
let match;
|
|
while ((match = pattern.exec(message)) !== null) {
|
|
issueNumbers.add(match[1]);
|
|
}
|
|
});
|
|
});
|
|
|
|
const issues = Array.from(issueNumbers);
|
|
console.log(`Found linked issues: ${issues.join(', ')}`);
|
|
|
|
return issues;
|
|
|
|
- name: Close linked issues
|
|
if: steps.extract_issues.outputs.result != '[]'
|
|
uses: actions/github-script@v7
|
|
with:
|
|
github-token: ${{ secrets.GITHUB_TOKEN }}
|
|
script: |
|
|
const issueNumbers = ${{ steps.extract_issues.outputs.result }};
|
|
const prNumber = context.payload.pull_request.number;
|
|
const prTitle = context.payload.pull_request.title;
|
|
const prUrl = context.payload.pull_request.html_url;
|
|
const merger = context.payload.pull_request.merged_by.login;
|
|
|
|
console.log(`Processing ${issueNumbers.length} linked issue(s)`);
|
|
|
|
for (const issueNumber of issueNumbers) {
|
|
try {
|
|
// Get issue details first
|
|
const issue = await github.rest.issues.get({
|
|
owner: context.repo.owner,
|
|
repo: context.repo.repo,
|
|
issue_number: parseInt(issueNumber)
|
|
});
|
|
|
|
// Skip if already closed
|
|
if (issue.data.state === 'closed') {
|
|
console.log(`Issue #${issueNumber} already closed, skipping`);
|
|
continue;
|
|
}
|
|
|
|
// Add comment explaining closure
|
|
await github.rest.issues.createComment({
|
|
owner: context.repo.owner,
|
|
repo: context.repo.repo,
|
|
issue_number: parseInt(issueNumber),
|
|
body: `Completed via PR #${prNumber}\n\nPR: ${prTitle}\nURL: ${prUrl}\nMerged by: ${merger}\n\nThis issue has been resolved and the changes have been merged into main.\n\nAutomatically closed via PR merge automation`
|
|
});
|
|
|
|
// Close the issue
|
|
await github.rest.issues.update({
|
|
owner: context.repo.owner,
|
|
repo: context.repo.repo,
|
|
issue_number: parseInt(issueNumber),
|
|
state: 'closed',
|
|
state_reason: 'completed'
|
|
});
|
|
|
|
console.log(`✅ Closed issue #${issueNumber}`);
|
|
|
|
} catch (error) {
|
|
console.error(`❌ Failed to close issue #${issueNumber}: ${error.message}`);
|
|
}
|
|
}
|
|
|
|
- name: Update project board status
|
|
if: steps.extract_issues.outputs.result != '[]'
|
|
uses: actions/github-script@v7
|
|
with:
|
|
github-token: ${{ secrets.GITHUB_TOKEN }}
|
|
script: |
|
|
const issueNumbers = ${{ steps.extract_issues.outputs.result }};
|
|
|
|
for (const issueNumber of issueNumbers) {
|
|
try {
|
|
// Add status: done label
|
|
await github.rest.issues.addLabels({
|
|
owner: context.repo.owner,
|
|
repo: context.repo.repo,
|
|
issue_number: parseInt(issueNumber),
|
|
labels: ['status: done']
|
|
});
|
|
|
|
// Remove in-progress and in-review labels
|
|
const labelsToRemove = ['status: in-progress', 'status: in-review'];
|
|
for (const label of labelsToRemove) {
|
|
try {
|
|
await github.rest.issues.removeLabel({
|
|
owner: context.repo.owner,
|
|
repo: context.repo.repo,
|
|
issue_number: parseInt(issueNumber),
|
|
name: label
|
|
});
|
|
} catch (e) {
|
|
// Label might not exist, ignore error
|
|
}
|
|
}
|
|
|
|
console.log(`✅ Updated project status for issue #${issueNumber}`);
|
|
|
|
} catch (error) {
|
|
console.error(`❌ Failed to update status for issue #${issueNumber}: ${error.message}`);
|
|
}
|
|
}
|
|
|
|
- name: Summary
|
|
if: steps.extract_issues.outputs.result != '[]'
|
|
uses: actions/github-script@v7
|
|
with:
|
|
github-token: ${{ secrets.GITHUB_TOKEN }}
|
|
script: |
|
|
const issueNumbers = ${{ steps.extract_issues.outputs.result }};
|
|
const prNumber = context.payload.pull_request.number;
|
|
|
|
console.log(`
|
|
✅ PR #${prNumber} Merge Automation Complete
|
|
|
|
Closed issues: ${issueNumbers.join(', ')}
|
|
Updated project board: status → done
|
|
Comments added: Linked to PR #${prNumber}
|
|
|
|
All linked issues have been automatically closed and marked as done.
|
|
`);
|