awesome-persona-distill-skills Documentation

Curated list of Agent Skills centered on people, relationships, commemorative scenes, and methodological perspectives

4,706 stars JavaScript JavaScript 1 file · ~13,267 tokens #agent-skills#awesome#awesome-list#persona-distill
RAW Doc

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

  • 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:

md
- [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

  1. 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.
  2. After review, a maintainer may add the approved label to the issue, and the workflow will generate a pull request automatically.
  3. 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.
  4. For direct PR changes, update the affected content in both README.md and README.en.md. If your change affects inclusion standards or collaboration rules, update the relevant contribution guide as well.
  5. Run formatting locally, then check that entry style, links, and wording remain consistent with the current content.

Local Check

Before submitting, run:

bash
bun install
bun run format

Then 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 the approved label 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},
});

text
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;

text
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

text
- 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: 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

text
- 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:links

File: .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);

text
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

text
- 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:consistency

File: .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 }}

text
- 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,
          },
        });