File: README.en.md
A curated list of Agent Skills centered on people, relationships, commemorative scenes, and methodological perspectives.
Here, "persona distillation" mainly refers to extracting expressive style, decision frameworks, and interaction patterns from conversations, works, materials, or digital traces. It is not assumed to be equivalent to a complete reconstruction of a real individual.
[!NOTE]
Entries are sorted by repository name for easier maintenance and lookup. The order does not imply recommendation priority, quality, or importance.
Want to try skills without setup? Try Bloome
Bloome lets you skip local installation and configuration: add any skill from this list as an agent to a chat with one click, then talk to it directly. If you like it, share it with others or invite it into a group, with zero setup. If you want to try the skills in this list one by one, Bloome is the fastest way to do it.
Table of Contents
- Self Distillation and Meta Tools
- Workplace and Academic Relationships
- Intimate Relationships and Family Memories
- Public Figures and Methodological Perspectives
- Spiritual and Specialized Topics
- Contributing
Self Distillation and Meta Tools
- Bazi Person Skill - Generate an AI persona from Bazi (Chinese astrology) that talks, feels, and evolves over time.
- curator.skill - Cross-author persona skill router — matches user intent to the best-fit persona from 30 skills.
- 数字人生.skills - Distills a structured self-portrait from the digital traces a person leaves across everyday tools.
- Forge Skill - Separates self-distillation and other-persona distillation into distinct workflows for reflection, memory preservation, and persona-driven dialogue.
- 永生.skill - Distills a multidimensional digital persona profile from chat logs and related materials.
- master-skill - Distills how practitioners in a given industry judge, decide, and use tools into a reusable methodological skill.
- midas.skill - Extracts a 6-dimension wealth operating system from any public figure (money engine, deal architecture, investment thesis, risk profile, leverage model, exit pattern), and mines personal daily data for wealth signals.
- anti-distill - Separates publicly distributable skill content from private experience backups for skill-delivery workflows.
- 数字生命开源计划 - Provides a framework for turning a personal knowledge base into a digital avatar.
- Nuwa - Distills reusable skills from an individual's mental models, decision heuristics, and expression patterns.
- Persona Skill - Unified multilingual persona distillation, synthesis, role-playing, and management.
- Skill Fidelity Bench - Benchmarks whether a modified or poisoned persona-distill skill preserves the clean skill's capabilities, reasoning, boundaries, and distinctiveness.
- VibePortrait - Distills a developer portrait, development skills, and preferences from vibe-coding conversations.
- Yourself.skill - Organizes personal conversations and records into a self-distillation assistant.
- zhuzi-skill - Orchestrates structured debates among existing persona skills around the same question, with automatic persona selection, round-based exchanges, and neutral synthesis.
Workplace and Academic Relationships
- boss.skill - Distills a manager's judgment criteria, review style, and communication expectations from work materials.
- colleague.skill - Organizes a former colleague's work context, habits, and communication style from team materials.
- HR.skill - Organizes HR communication logic and decision patterns from rejection letters and hiring processes for reverse-engineering screening criteria and reframing a job-seeking narrative.
- 大学老师.skill - Organizes review priorities, exam patterns, and grading cues from course materials and a professor's style.
- roast-cold-email-skill - Organizes a critical job-outreach style from public company information and hiring materials for evidence-based cold emails.
- Senpai.skill - Distills how senior lab members mentor others and handle emergencies from research group materials.
- Mentor.skill - Organizes a supervisor's advising style into a mentor assistant for students and educators.
Intimate Relationships and Family Memories
- brother.skill - Distill your bros (working for real brothers, group chat friends, and internet personalities) from YouTube, TikTok, Discord, WeChat and more — their voice, catchphrases, humor style, and chaos energy.
- Crush.skill - Distills conversational style from chats, photos, and social traces for reflection and emotional recollection.
- ex.skill - Organizes speaking style and shared memories from private records for remembrance and reflection.
- MamaSkill - Organizes chat logs, letters, and voice notes from a loved one into a commemorative family companion.
- 父母.skill - Distills parents' tone, habits, and family memories from personal materials.
- 恋爱训练营.skill - Simulates a crush's communication style from chat history so users can practice expression, invitations, and repair conversations in a safe sandbox.
- Reunion Skill - Builds a locally runnable commemorative companion assistant from the digital relics of deceased relatives or friends.
Public Figures and Methodological Perspectives
- AI 维舟.skill - Distill Wei Zhou's mindset and thought framework on social issues, and develop a reusable methodological perspective for analyzing hot topics or evaluating specific viewpoints.
- Buffett Perspective - Claude Code Skill - Distills Warren Buffett's investment and decision-making frameworks into a reusable methodological perspective.
- 常熟阿诺(加州盛亦陶) Skill - Distills Changshu Anuo's "left-brain versus right-brain" analytical perspective into a reusable methodological framework.
- 陈泽.skill - Anchor host Chen Ze’s thinking operating system, analyzing your problems using Chen Ze’s cognitive framework.
- 马斯克.skill - Distills Elon Musk's first-principles thinking and product mindset into a reusable decision framework.
- 峰哥亡命天涯 Skill - Distills the "Fengge Wangming Tianya" style into a reality-first, stop-loss-oriented, darkly humorous methodological perspective.
- 费曼.skill - Distills Richard Feynman's explanatory style and truth-seeking heuristics into a reusable methodological framework.
- 郭德纲.skill - Distills Guo Degang's expressive structure, practical judgment, and career methods into a reusable methodological perspective.
- 户晨风.skill - Distills Hu Chenfeng's consumer-realist perspective for analyzing spending, cities, and career choices.
- Ilya.skill - Distills Ilya Sutskever's judgment on scaling, research breakthroughs, and superintelligence into a reusable methodological perspective.
- Justin Bieber ·Skill - Justin Bieber's thinking operating system — engaging in conversation with you through his experiences and creative work.
- Karl Marx Skill - Distilled from 17 of Marx's original texts, surfacing his critical framework and dialectical method, with dual output modes for casual dialogue and scholarly analysis.
- KarlMarx Skill - Distills structural analysis, contradiction analysis, and practice-based verification methods from Marxist methodology into a framework for deep problem analysis.
- Karpathy.skill - Distills Andrej Karpathy's thinking on AI engineering, education, and research into a reusable methodological perspective.
- liangxi-skills - Distills Liangxi's structural trade reading, risk habits, and Chinese trading voice from publicly indexed X posts and trade replays into a reusable methodological perspective.
- luxun skill - Distilled from Lu Xun's 305 collected works and Xu Shoushang's chronological biography, surfacing his ironic prose, "deceit-and-self-deceit" critique and detached observational frame.
- MaoZedong.skill - Distills Mao Zedong's thought framework and methodological perspective from publicly available writings.
- 毛选.skill - Distills contradiction analysis, base-building thinking, and strategic judgment frameworks from Mao's selected works into a reusable methodological perspective.
- Mises Method Skill - Distills Ludwig von Mises's praxeology, economic calculation, and critique of interventionism into a reusable methodological lens for political economy and institutional analysis.
- MrBeast.skill - Distills MrBeast's methods for content concepts, packaging, and audience retention into a reusable creator playbook.
- 芒格.skill - Distills Charlie Munger's interdisciplinary mental models and decision heuristics into a reusable methodological framework.
- 纳瓦尔.skill - Distills Naval's frameworks on wealth, leverage, and judgment into a reusable methodological perspective.
- PG.skill - Distills Paul Graham's frameworks on startups, writing, and independent thinking into a reusable methodological perspective.
- 求是 Skill - Organizes fact-seeking, investigation, and strategic-judgment tools from related methodology into a reusable analytical framework.
- RobPike.skill - Distills Rob Pike's technical judgment and expressive style on Unix, Go, and engineering design into a reusable methodological perspective.
- 内娱.skill - Organizes a singer's or idol's expressive style and fan-facing interaction patterns from lyrics, videos, posts, and comments into a conversational digital persona assistant.
- 乔布斯.skill - Distills Steve Jobs's product judgment, narrative style, and decision heuristics into a reusable methodological framework.
- 塔勒布.skill - Distills Nassim Taleb's heuristics on antifragility and risk into a reusable methodological framework.
- 童锦程.skill - Distills Tong Jincheng's blunt heuristics for reading relationships and interpersonal dynamics.
- 特朗普.skill - Distills Donald Trump's frameworks for negotiation, anchoring, and power analysis into a reusable methodological perspective.
- 王二.skill - Refine the spirit and way of thinking of the character 'Wang Er' in Wang Xiaobo's works to form a reusable methodological framework.
- X 导师.skill - Integrates the writing and growth playbooks of multiple social-platform creators into a unified mentor-style methodological skill.
- 新青年.Skill - Invite the "most capable brain in New China" to serve as a "temporary advisor" for a period of time. Condense 157 articles from "Selected Works of Mao" and transform its methodology into an executable practical analysis skill.
- 张一鸣.skill - Distills Zhang Yiming's product, organizational, and strategic judgment frameworks into a reusable methodological perspective.
- 张雪峰.skill - Distills Zhang Xuefeng's practical frameworks for education planning, exams, and career planning into a reusable methodological perspective.
- zizek-skill - Organizes a Zizekian mode of analysis around interrogating presuppositions, tracking desire, and exposing contradictions into a reusable analytical tool.
Spiritual and Specialized Topics
- 赛博算命 Skill - Organizes Four Pillars charting and analysis from birth information and traditional destiny texts.
- DiamondSutra.skill - Organizes teaching frameworks for the Diamond Sutra from the text and related interpretations into a conversational specialized skill.
- 堪舆子 - Distills traditional feng shui methods — flying stars, eight mansions, and date selection — from classical texts into a skill with the persona of a seventh-generation Jiangnan geomancy master.
- Master-skill - Organizes Han Chinese Buddhist teaching styles and explanatory perspectives based on Buddhist canonical literature.
- Numerologist Skills (AI 术数工程化) - Uses structured references and scripted constraints to organize Qimen Dunjia, Ziwei Doushu, and related metaphysics practices.
- 月老·姻缘测算 Skills - Organizes relationship divination into a multi-mode traditional metaphysics skill for matching, fortune sticks, and romance-luck readings.
Contributing
For new inclusion requests, please start with the issue form. After a maintainer reviews the request and adds the approved label, the workflow will generate a pull request automatically.
If you are fixing existing entries, documentation, or broken links, you can still open a pull request directly. See CONTRIBUTING.en.md for the full process.
File: README.md
[Binary file]
File: CONTRIBUTING.en.md
Contributing Guide
Thank you for contributing to this repository.
This repository collects Agent Skills centered on "persona distillation", with emphasis on:
- Distilling expressive style, judgment patterns, or interaction habits from people, relationships, conversations, works, or digital traces.
- Turning a personal perspective, relationship experience, or methodology into a reusable skill.
- Keeping a clear thematic boundary so each entry fits an awesome-style list instead of a generic AI tools directory.
Welcome Contributions
- Add links to skill repositories that fit the theme.
- Fix dead links, repository names, descriptions, or category placement.
- Improve wording in the READMEs so categories and descriptions are more accurate.
For new inclusion requests, the issue form is the only public intake path. After a maintainer reviews the request and adds the approved label, the workflow will generate a pull request automatically.
Contributions We Discourage
- Generic AI tools, prompt collections, or resource directories that are only loosely related to persona distillation.
- Projects without a public repository, with inaccessible content, or with too little public material to evaluate.
- Obvious duplicates, thin repackaging, or entries that heavily overlap with existing items without adding value.
- Projects primarily intended for privacy invasion, harassment, impersonation of real people, or consent avoidance.
- Pure advertising copy or submissions that require readers to click through before they can understand the basics.
Before You Submit
Please try to make sure each entry meets these standards:
- The repository is publicly accessible.
- The project is relevant to this list and fits the chosen category.
- The Chinese and English names each use the project's commonly used name in that language.
- The description stays within one sentence and explains what is being distilled, from what source, or for what purpose, without promotional wording.
- If the project fits a different category better, submit it directly in the more appropriate section.
Entry Writing Reference
If you are requesting a new inclusion, use the format and tone below when filling in the bilingual names and descriptions in the issue form. After a maintainer adds the approved label, the workflow will use that content to generate the README entry automatically.
If you are directly revising an existing entry, keep using the current README list format:
- [Project Name](https://github.com/owner/repo) - One sentence describing what the project distills, where it comes from, or what it is for.Suggested writing style:
- Say what it is before saying what it does.
- Be specific instead of using vague praise like "high quality", "powerful", or "useful".
- Match the tone and level of detail of nearby entries.
Submission Process
- If you want to request a new inclusion, start with the issue form and fill in the repository link, category, bilingual names, bilingual descriptions, and rationale.
- After review, a maintainer may add the
approvedlabel to the issue, and the workflow will generate a pull request automatically. - If you are fixing existing entries, documentation, or broken links, you can still fork the repository, create a branch, and open a pull request directly.
- For direct PR changes, update the affected content in both
README.mdandREADME.en.md. If your change affects inclusion standards or collaboration rules, update the relevant contribution guide as well. - Run formatting locally, then check that entry style, links, and wording remain consistent with the current content.
Local Check
Before submitting, run:
bun install
bun run formatThen double-check:
- Markdown list formatting matches the existing entries.
- Links are publicly accessible.
- Names and descriptions are accurate and non-promotional.
PR Notes
To help review move faster, please explain:
- What you added or changed.
- Which category it belongs to and why.
- If it is a new project, how it relates to the persona distillation theme.
- If there are similar entries already listed, how this one differs from them.
Communication
- New inclusion requests should go through the issue form instead of a direct PR.
- Small fixes, updates to existing entries, and broken-link fixes can go straight to a PR.
- If you plan to restructure categories, rewrite many descriptions, or noticeably expand the inclusion scope, it is better to open an issue first to explain the idea.
- Keep discussion focused on thematic boundaries, readability, and reader value.
File: CONTRIBUTING.md
[Binary file]
File: package.json
{
"name": "awesome-persona-distill-skills",
"version": "0.1.0",
"private": true,
"description": "Persona distillation skills for turning people, relationships, and personal context into reusable AI assistants.",
"license": "CC0-1.0",
"packageManager": "[email protected]",
"engines": {
"bun": ">=1.2.0"
},
"scripts": {
"format": "prettier --write .",
"format:check": "prettier --check .",
"test": "bun test",
"check:consistency": "bun scripts/check-repository-consistency.mjs",
"check:links": "bun scripts/check-links.mjs"
},
"devDependencies": {
"prettier": "^3.9.10"
}
}
File: .github/dependabot.yml
version: 2
updates:
- package-ecosystem: "bun"
directory: "/"
schedule:
interval: "weekly"
day: "monday"
time: "03:00"
timezone: "Asia/Shanghai" - package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "weekly"
day: "monday"
time: "03:00"
timezone: "Asia/Shanghai"
File: .github/ISSUE_TEMPLATE/config.yml
blank_issues_enabled: false
contact_links:
- name: 联系方式 / Contact
url: https://xi-xu.me/#contact
about: 如有一般问题或不属于收录申请的想法,请使用此联系页面。 / Use this contact page for general questions or ideas that are not inclusion requests.
File: .github/ISSUE_TEMPLATE/submission.yml
name: 申请收录 / Submission Request
title: "[Submission]: "
labels:
- submission
body: - type: markdown
attributes:
value: |
请先阅读 CONTRIBUTING.md。 / Please read CONTRIBUTING.en.md first.
请完整填写下列表单。维护者审核通过后,会通过approved标签自动生成 PR。
Complete this form in full. Once a maintainer approves it, adding theapprovedlabel will generate a pull request automatically. - type: input
id: repository
attributes:
label: 存储库链接 / Repository URL
placeholder: https://github.com/owner/repo
validations:
required: true - type: input
id: project_name_zh
attributes:
label: 汉语名称 / Chinese Name
validations:
required: true - type: input
id: project_name_en
attributes:
label: 英语名称 / English Name
validations:
required: true - type: textarea
id: description_zh
attributes:
label: 汉语描述 / Chinese Description
placeholder: 一句话,不要使用宣传性措辞。 / Keep it factual and non-promotional.
validations:
required: true - type: textarea
id: description_en
attributes:
label: 英语描述 / English Description
placeholder: 一句话,不要使用宣传性措辞。 / Keep it factual and non-promotional.
validations:
required: true - type: dropdown
id: category
attributes:
label: 分类 / Category
options:
- 自我蒸馏与元工具 / Self Distillation and Meta Tools
- 职场与学术关系 / Workplace and Academic Relationships
- 亲密关系与家庭记忆 / Intimate Relationships and Family Memories
- 公众人物与方法论视角 / Public Figures and Methodological Perspectives
- 精神性与专门化主题 / Spiritual and Specialized Topics
validations:
required: true - type: textarea
id: rationale
attributes:
label: 收录理由 / Why It Belongs Here
validations:
required: true - type: textarea
id: differentiation
attributes:
label: 与现有条目的区别 / Differentiation
validations:
required: true - type: checkboxes
id: checks
attributes:
label: 确认事项 / Confirmations
options:
- label: 我已阅读并遵循贡献指南 / I have read and follow the contribution guide
required: true
File: .github/workflows/create-approved-submission-pr.yml
name: Create Approved Submission PR
on:
issues:
types:
- labeled
permissions:
actions: write
contents: write
issues: write
pull-requests: write
concurrency:
group: submission-issue-${{ github.event.issue.number }}
cancel-in-progress: false
jobs:
create-pr:
if: github.event.label.name == 'approved' && github.event.issue.pull_request == null
runs-on: ubuntu-latest
steps:
- name: Check for existing automation PR
id: existing-pr
uses: actions/github-script@v9
with:
script: |
const branch = auto/submission-issue-${context.payload.issue.number};
const pulls = await github.paginate(github.rest.pulls.list, {
owner: context.repo.owner,
repo: context.repo.repo,
state: "open",
head: ${context.repo.owner}:${branch},
});
core.setOutput("exists", pulls.length > 0 ? "true" : "false");
core.setOutput("url", pulls[0]?.html_url ?? "");
- name: Comment when a PR already exists
if: steps.existing-pr.outputs.exists == 'true'
uses: actions/github-script@v9
with:
script: |
const body = `An automated pull request already exists for this submission: ${process.env.PR_URL}`;
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.payload.issue.number,
body,
});
env:
PR_URL: ${{ steps.existing-pr.outputs.url }}
- name: Check out repository
if: steps.existing-pr.outputs.exists != 'true'
uses: actions/checkout@v7
with:
token: ${{ secrets.GITHUB_TOKEN }}
- name: Set up Bun
if: steps.existing-pr.outputs.exists != 'true'
uses: oven-sh/setup-bun@v2
with:
bun-version: 1.3.11
- name: Install dependencies
if: steps.existing-pr.outputs.exists != 'true'
run: bun ci
- name: Check for existing remote branch
if: steps.existing-pr.outputs.exists != 'true'
id: existing-branch
env:
ISSUE_NUMBER: ${{ github.event.issue.number }}
run: |
branch="auto/submission-issue-${ISSUE_NUMBER}"
if git ls-remote --exit-code --heads origin "${branch}" >/dev/null 2>&1; then
echo "exists=true" >> "$GITHUB_OUTPUT"
else
echo "exists=false" >> "$GITHUB_OUTPUT"
fi
- name: Comment when a remote branch already exists
if: steps.existing-pr.outputs.exists != 'true' && steps.existing-branch.outputs.exists == 'true'
uses: actions/github-script@v9
with:
script: |
const branch = `auto/submission-issue-${context.payload.issue.number}`;
const body = `The automation branch \`${branch}\` already exists. Please inspect it before relabeling this issue.`;
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.payload.issue.number,
body,
});
- name: Apply submission to READMEs
if: steps.existing-pr.outputs.exists != 'true' && steps.existing-branch.outputs.exists != 'true'
id: apply
continue-on-error: true
env:
ISSUE_BODY: ${{ github.event.issue.body }}
run: |
bun scripts/submission-automation.mjs > submission-result.json 2> submission-error.log
- name: Comment on validation failure
if: steps.apply.outcome == 'failure'
uses: actions/github-script@v9
with:
script: |
const fs = require("node:fs");
const errorBody = fs.existsSync("submission-error.log")
? fs.readFileSync("submission-error.log", "utf8").trim()
: "The submission automation failed without an explicit error message.";
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.payload.issue.number,
body: `The automated PR could not be created for this issue.\n\n${errorBody}`,
});
- name: Stop after validation failure
if: steps.apply.outcome == 'failure'
run: exit 1
- name: Detect no-op changes
if: steps.apply.outcome == 'success'
id: diff
run: |
if git diff --quiet; then
echo "has_changes=false" >> "$GITHUB_OUTPUT"
else
echo "has_changes=true" >> "$GITHUB_OUTPUT"
fi
- name: Comment when no changes were produced
if: steps.apply.outcome == 'success' && steps.diff.outputs.has_changes != 'true'
uses: actions/github-script@v9
with:
script: |
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.payload.issue.number,
body: "The automation did not produce README changes. This usually means the repository is already listed or the issue body needs review.",
});
- name: Stop when no changes were produced
if: steps.apply.outcome == 'success' && steps.diff.outputs.has_changes != 'true'
run: exit 1
- name: Configure git author
if: steps.diff.outputs.has_changes == 'true'
run: |
git config user.name "github-actions[bot]"
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
- name: Create remote branch reference
if: steps.diff.outputs.has_changes == 'true'
uses: actions/github-script@v9
env:
ISSUE_NUMBER: ${{ github.event.issue.number }}
with:
script: |
const branch = `auto/submission-issue-${process.env.ISSUE_NUMBER}`;
const defaultBranch = context.payload.repository.default_branch;
const { data: branchInfo } = await github.rest.repos.getBranch({
owner: context.repo.owner,
repo: context.repo.repo,
branch: defaultBranch,
});
await github.rest.git.createRef({
owner: context.repo.owner,
repo: context.repo.repo,
ref: `refs/heads/${branch}`,
sha: branchInfo.commit.sha,
});
- name: Create branch, commit, and push
if: steps.diff.outputs.has_changes == 'true'
env:
ISSUE_NUMBER: ${{ github.event.issue.number }}
run: |
branch="auto/submission-issue-${ISSUE_NUMBER}"
git checkout -b "${branch}"
git add README.md README.en.md
git commit -m "docs: add submission from issue #${ISSUE_NUMBER}"
git push origin HEAD:"${branch}"
- name: Create pull request
if: steps.diff.outputs.has_changes == 'true'
id: create-pr
uses: actions/github-script@v9
with:
script: |
const branch = `auto/submission-issue-${context.payload.issue.number}`;
const response = await github.rest.pulls.create({
owner: context.repo.owner,
repo: context.repo.repo,
title: `docs: add submission from issue #${context.payload.issue.number}`,
head: branch,
base: "main",
body: [
`Automated submission PR for #${context.payload.issue.number}.`,
"",
`Source issue: #${context.payload.issue.number}`,
"",
"This PR was generated after a maintainer added the `approved` label.",
].join("\n"),
});
core.setOutput("number", String(response.data.number));
core.setOutput("url", response.data.html_url);
- name: Trigger submission PR validation
if: steps.create-pr.outputs.number != ''
uses: actions/github-script@v9
env:
DEFAULT_BRANCH: ${{ github.event.repository.default_branch }}
ISSUE_NUMBER: ${{ github.event.issue.number }}
PR_NUMBER: ${{ steps.create-pr.outputs.number }}
with:
script: |
const branch = `auto/submission-issue-${process.env.ISSUE_NUMBER}`;
await github.rest.actions.createWorkflowDispatch({
owner: context.repo.owner,
repo: context.repo.repo,
workflow_id: "submission-pr-validation.yml",
ref: process.env.DEFAULT_BRANCH,
inputs: {
pr_number: process.env.PR_NUMBER,
branch,
issue_number: process.env.ISSUE_NUMBER,
},
});
- name: Comment with created PR link
if: steps.create-pr.outputs.url != ''
uses: actions/github-script@v9
with:
script: |
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.payload.issue.number,
body: `An automated pull request has been created for this approved submission: ${process.env.PR_URL}`,
});
env:
PR_URL: ${{ steps.create-pr.outputs.url }}File: .github/workflows/dependabot-auto-merge.yml
name: Dependabot Auto Merge
on:
pull_request_target:
types: [opened, synchronize, reopened, ready_for_review]
check_suite:
types: [completed]
workflow_run:
workflows:
- "CodeQL"
- "Copilot"
- "Create Approved Submission PR"
- "Format"
- "Link Check"
- "Merge Approved Submission PR"
- "Repository Consistency"
- "Submission PR Validation"
types:
- completed
workflow_dispatch:
concurrency:
group: ${{ github.workflow }}-${{ github.repository }}
cancel-in-progress: false
permissions:
contents: write
pull-requests: write
checks: read
statuses: read
jobs:
merge:
name: Auto-merge Dependabot PRs
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- name: Merge Dependabot PRs when checks pass
uses: actions/github-script@v9
with:
github-token: ${{ github.token }}
script: |
const owner = context.repo.owner;
const repo = context.repo.repo;
const requiredCheckNames = ["prettier", "link-check", "consistency"];
const successfulCheckConclusions = new Set(["success", "skipped", "neutral"]);
const successfulStatusStates = new Set(["success"]);
const wait = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
const latestBy = (items, keyOf, timeOf) => {
const latest = new Map();
for (const item of items) {
const key = keyOf(item);
const itemTime = new Date(timeOf(item) || 0).getTime();
const existing = latest.get(key);
const existingTime = existing ? new Date(timeOf(existing) || 0).getTime() : -1;
if (!existing || itemTime >= existingTime) {
latest.set(key, item);
}
}
return [...latest.values()];
};
const findCandidatePulls = async () => {
const pulls = await github.paginate(github.rest.pulls.list, {
owner,
repo,
state: "open",
per_page: 100,
});
return pulls.filter((pr) => pr.user?.login === "dependabot[bot]");
};
const getMergeablePullRequest = async (pull_number) => {
for (let attempt = 1; attempt <= 6; attempt += 1) {
const { data: pr } = await github.rest.pulls.get({ owner, repo, pull_number });
if (pr.mergeable !== null) {
return pr;
}
core.info("PR #" + pull_number + " mergeability is still being computed; retry " + attempt + "/6.");
await wait(5000);
}
const { data: pr } = await github.rest.pulls.get({ owner, repo, pull_number });
return pr;
};
const getSignalState = async (sha) => {
const checkRuns = await github.paginate(github.rest.checks.listForRef, {
owner,
repo,
ref: sha,
per_page: 100,
});
const statuses = await github.paginate(github.rest.repos.listCommitStatusesForRef, {
owner,
repo,
ref: sha,
per_page: 100,
});
const latestChecks = latestBy(
checkRuns.filter((run) => !["Dependabot Auto Merge", "Auto-merge Dependabot PRs", "Enable auto-merge for dependency PRs"].includes(run.name)),
(run) => (run.app?.slug || "unknown") + ":" + run.name,
(run) => run.started_at || run.created_at || run.completed_at,
);
const latestStatuses = latestBy(statuses, (status) => status.context, (status) => status.updated_at || status.created_at);
const pendingChecks = latestChecks.filter((run) => run.status !== "completed");
const failedChecks = latestChecks.filter(
(run) => run.status === "completed" && !successfulCheckConclusions.has(String(run.conclusion || "").toLowerCase()),
);
const failedStatuses = latestStatuses.filter((status) => !successfulStatusStates.has(String(status.state || "").toLowerCase()));
return {
totalSignals: latestChecks.length + latestStatuses.length,
missingChecks: requiredCheckNames.filter((name) => !latestChecks.some((run) => run.name === name)),
pendingChecks,
failedChecks,
failedStatuses,
};
};
const { data: repository } = await github.rest.repos.get({ owner, repo });
const mergeMethods = [];
if (repository.allow_merge_commit) mergeMethods.push("merge");
if (repository.allow_squash_merge) mergeMethods.push("squash");
if (repository.allow_rebase_merge) mergeMethods.push("rebase");
if (mergeMethods.length === 0) {
core.info("This repository has no enabled pull request merge methods.");
return;
}
const mergeErrors = [];
const pulls = await findCandidatePulls();
if (pulls.length === 0) {
core.info("No open Dependabot PRs to evaluate.");
return;
}
for (const candidate of pulls) {
const pull_number = candidate.number;
const pr = await getMergeablePullRequest(pull_number);
if (pr.user?.login !== "dependabot[bot]") {
core.info("PR #" + pull_number + " is no longer a Dependabot PR.");
continue;
}
if (pr.state !== "open" || pr.draft) {
core.info("PR #" + pull_number + " is not an open, ready PR.");
continue;
}
if (pr.mergeable !== true) {
core.info("PR #" + pull_number + " is not currently mergeable.");
continue;
}
const files = await github.paginate(github.rest.pulls.listFiles, {
owner, repo, pull_number, per_page: 100,
});
if (files.some((file) => [file.filename, file.previous_filename].some((name) => name?.startsWith(".github/workflows/")))) {
const message = "PR #" + pull_number + " changes workflow files and requires manual merging.";
core.info(message);
core.summary.addRaw(message + "\n");
await core.summary.write();
continue;
}
const signalState = await getSignalState(pr.head.sha);
if (signalState.missingChecks.length > 0) {
core.info("PR #" + pull_number + " is waiting for validation checks: " + signalState.missingChecks.join(", "));
continue;
}
if (signalState.totalSignals === 0) {
core.info("PR #" + pull_number + " has no checks or statuses yet; skipping.");
continue;
}
if (signalState.pendingChecks.length > 0) {
core.info("PR #" + pull_number + " still has pending checks: " + signalState.pendingChecks.map((run) => run.name).join(", ") + ".");
continue;
}
if (signalState.failedChecks.length > 0 || signalState.failedStatuses.length > 0) {
const failedChecks = signalState.failedChecks.map((run) => run.name + "=" + run.conclusion).join(", ");
const failedStatuses = signalState.failedStatuses.map((status) => status.context + "=" + status.state).join(", ");
core.info("PR #" + pull_number + " is not green. Checks: " + (failedChecks || "none") + ". Statuses: " + (failedStatuses || "none") + ".");
continue;
}
let merged = false;
let lastError = null;
for (const merge_method of mergeMethods) {
try {
await github.rest.pulls.merge({ owner, repo, pull_number, merge_method, sha: pr.head.sha });
core.info("Merged Dependabot PR #" + pull_number + " with " + merge_method + ".");
merged = true;
break;
} catch (error) {
lastError = error;
if (error.status === 403 || error.status === 405 || error.status === 409) {
core.info("Cannot merge PR #" + pull_number + " with " + merge_method + ": " + error.message);
continue;
}
throw error;
}
}
if (!merged) {
const message = "PR #" + pull_number + " could not be merged: " + (lastError?.message || "unknown error");
core.error(message);
mergeErrors.push(message);
continue;
}
if (pr.head.repo?.full_name === owner + "/" + repo) {
try {
await github.rest.git.deleteRef({ owner, repo, ref: "heads/" + pr.head.ref });
core.info("Deleted branch " + pr.head.ref + ".");
} catch (error) {
core.info("Could not delete branch " + pr.head.ref + ": " + error.message);
}
}
}
if (mergeErrors.length > 0) {
core.setFailed(mergeErrors.join("\n"));
}File: .github/workflows/format.yml
name: Format
on:
push:
branches:
- main
pull_request:
permissions:
contents: read
jobs:
prettier:
runs-on: ubuntu-latest
steps:
- name: Check out repository
uses: actions/checkout@v7
- name: Set up Bun
uses: oven-sh/setup-bun@v2
with:
bun-version: 1.3.11
- name: Install dependencies
run: bun ci
- name: Check formatting
run: bun run format:checkFile: .github/workflows/link-check.yml
name: Link Check
on:
pull_request:
push:
branches:
- main
schedule:
- cron: "1 3 * * 1"
permissions:
contents: read
jobs:
link-check:
runs-on: ubuntu-latest
steps:
- name: Check out repository
uses: actions/checkout@v7
- name: Set up Bun
uses: oven-sh/setup-bun@v2
with:
bun-version: 1.3.11
- name: Install dependencies
run: bun ci
- name: Check repository links
run: bun run check:linksFile: .github/workflows/merge-approved-submission-pr.yml
name: Merge Approved Submission PR
on:
workflow_dispatch:
inputs:
pr_number:
required: true
type: string
branch:
required: true
type: string
issue_number:
required: true
type: string
permissions:
contents: write
issues: write
pull-requests: write
concurrency:
group: submission-pr-${{ inputs.pr_number }}
cancel-in-progress: false
jobs:
merge-pr:
if: startsWith(inputs.branch, 'auto/submission-issue-')
runs-on: ubuntu-latest
steps:
- name: Merge automated submission PR when checks are green
uses: actions/github-script@v9
env:
PR_NUMBER: ${{ inputs.pr_number }}
BRANCH: ${{ inputs.branch }}
ISSUE_NUMBER: ${{ inputs.issue_number }}
with:
script: |
const owner = context.repo.owner;
const repo = context.repo.repo;
const pullNumber = Number(process.env.PR_NUMBER);
const branch = process.env.BRANCH;
const issueNumber = Number(process.env.ISSUE_NUMBER);
for (let attempt = 1; attempt <= 24; attempt += 1) {
const pulls = await github.paginate(github.rest.pulls.list, {
owner,
repo,
state: "open",
head: `${owner}:${branch}`,
});
if (pulls.length === 0) {
core.info(`No open PR found for automation branch ${branch}.`);
return;
}
const pullRequest = pulls[0];
const { data: checkRuns } = await github.rest.checks.listForRef({
owner,
repo,
ref: pullRequest.head.sha,
per_page: 100,
});
const hasCodeQL = checkRuns.check_runs.some(
(checkRun) =>
checkRun.name === "CodeQL" &&
checkRun.status === "completed" &&
checkRun.conclusion === "success",
);
const allChecksCompleted = checkRuns.check_runs.every(
(checkRun) =>
checkRun.status === "completed" &&
["success", "neutral", "skipped"].includes(checkRun.conclusion),
);
if (!hasCodeQL || !allChecksCompleted) {
core.info(`Checks for PR #${pullNumber} are not all green yet (attempt ${attempt}/24).`);
await new Promise((resolve) => setTimeout(resolve, 5000));
continue;
}
const { data: pr } = await github.rest.pulls.get({
owner,
repo,
pull_number: pullNumber,
});
if (pr.merged || pr.state === "closed") {
core.info(`Pull request #${pullNumber} is already closed.`);
return;
}
if (pr.mergeable === false && pr.mergeable_state === "behind") {
core.info(
`Pull request #${pullNumber} is behind the base branch. Updating branch before merge.`,
);
await github.rest.pulls.updateBranch({
owner,
repo,
pull_number: pullNumber,
});
await new Promise((resolve) => setTimeout(resolve, 5000));
continue;
}
if (
pr.mergeable === null ||
pr.mergeable_state === "unknown" ||
pr.mergeable_state === "unstable"
) {
core.info(
`Mergeability for PR #${pullNumber} is still being computed (attempt ${attempt}/24).`,
);
await new Promise((resolve) => setTimeout(resolve, 5000));
continue;
}
if (pr.mergeable !== true) {
core.setFailed(
`Pull request #${pullNumber} is not mergeable (state: ${pr.mergeable_state}).`,
);
return;
}
await github.rest.pulls.merge({
owner,
repo,
pull_number: pullNumber,
merge_method: "squash",
sha: pr.head.sha,
});
core.info(`Merged pull request #${pullNumber}.`);
await github.rest.issues.createComment({
owner,
repo,
issue_number: issueNumber,
body: `An automated pull request has been merged for this approved submission: ${pr.html_url}`,
});
return;
}
core.setFailed(`Checks for PR #${pullNumber} did not turn green before timeout.`);File: .github/workflows/repository-consistency.yml
name: Repository Consistency
on:
pull_request:
push:
branches:
- main
permissions:
contents: read
jobs:
consistency:
runs-on: ubuntu-latest
steps:
- name: Check out repository
uses: actions/checkout@v7
- name: Set up Bun
uses: oven-sh/setup-bun@v2
with:
bun-version: 1.3.11
- name: Test Dependabot merge policy
run: node --test .github/scripts/check-dependabot-policy.cjs
- name: Install dependencies
run: bun ci
- name: Check repository consistency
run: bun run check:consistencyFile: .github/workflows/submission-pr-validation.yml
name: Submission PR Validation
on:
pull_request:
types:
- opened
- synchronize
- reopened
- ready_for_review
workflow_dispatch:
inputs:
pr_number:
required: true
type: string
branch:
required: true
type: string
issue_number:
required: true
type: string
permissions:
actions: write
contents: read
concurrency:
group: submission-validation-${{ github.event.pull_request.number || inputs.pr_number }}
cancel-in-progress: true
jobs:
validate:
name: Submission PR Validation
if: >-
(github.event_name == 'pull_request' &&
startsWith(github.event.pull_request.head.ref, 'auto/submission-issue-')) ||
(github.event_name == 'workflow_dispatch' &&
startsWith(inputs.branch, 'auto/submission-issue-'))
runs-on: ubuntu-latest
steps:
- name: Check out pull request head
if: github.event_name == 'pull_request'
uses: actions/checkout@v7
with:
ref: ${{ github.event.pull_request.head.sha }}
- name: Check out submission branch
if: github.event_name == 'workflow_dispatch'
uses: actions/checkout@v7
with:
ref: ${{ inputs.branch }}
- name: Set up Bun
uses: oven-sh/setup-bun@v2
with:
bun-version: 1.3.11
- name: Install dependencies
run: bun ci
- name: Check formatting
run: bun run format:check
- name: Check repository links
run: bun run check:links
- name: Check repository consistency
run: bun run check:consistency
- name: Trigger merge workflow
if: github.event_name == 'workflow_dispatch'
uses: actions/github-script@v9
env:
DEFAULT_BRANCH: ${{ github.event.repository.default_branch }}
PR_NUMBER: ${{ inputs.pr_number }}
BRANCH: ${{ inputs.branch }}
ISSUE_NUMBER: ${{ inputs.issue_number }}
with:
script: |
await github.rest.actions.createWorkflowDispatch({
owner: context.repo.owner,
repo: context.repo.repo,
workflow_id: "merge-approved-submission-pr.yml",
ref: process.env.DEFAULT_BRANCH,
inputs: {
pr_number: process.env.PR_NUMBER,
branch: process.env.BRANCH,
issue_number: process.env.ISSUE_NUMBER,
},
});