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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 5 additions & 5 deletions .github/CODEOWNERS
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# Auto-request reviews for skill changes
# Any PR that modifies skills will automatically request reviews from these users

/skills/ @stevekaliski-stripe @matv-stripe @sgr-stripe @jleong-stripe @tomchen-stripe
/providers/*/plugin/skills/ @stevekaliski-stripe @matv-stripe @sgr-stripe @jleong-stripe @tomchen-stripe
# Skills were previously reviewed here, but are now managed internally.
# No other directories were marked for CODEOWNER review, but if we do so
# in the future, the appropriate user list is:
#
# @anirudhgoyal-stripe @gusnguyen-stripe @jleong-stripe @johno-stripe @keshavc-stripe @markguan-stripe @matv-stripe @sgr-stripe @vncz-stripe
49 changes: 49 additions & 0 deletions .github/workflows/guard-skills.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
name: Guard synced skills

on:
pull_request:
paths:
- 'skills/**'
- '!skills/README.md'
- 'providers/claude/plugin/skills/**'
- 'providers/cursor/plugin/skills/**'

jobs:
block:
runs-on: ubuntu-latest
steps:
- name: Generate GitHub App token
id: app-token
uses: actions/create-github-app-token@v3
with:
client-id: ${{ secrets.GH_APP_STRIPE_AI_SYNC_CLIENT_ID }}
private-key: ${{ secrets.GH_APP_STRIPE_AI_SYNC_PEM }}
owner: ${{ github.repository_owner }}
repositories: ${{ github.event.repository.name }}
permission-pull-requests: write

- name: Build review body
id: message
uses: actions/github-script@v7
with:
script: |
const author = context.payload.pull_request.user.login;
const isInternal = author.endsWith('-stripe');

const action = isInternal
? 'To make lasting changes, apply them to the source. You can find instructions at [go/add-stripe-skill](http://go/add-stripe-skill).'
: 'These changes need to be applied internally. Tagging @stripe/developer-ai to take a look.';

const body = [
'This PR modifies files that are automatically synced from a centrally maintained copy at [docs.stripe.com/.well-known/skills](https://docs.stripe.com/.well-known/skills/index.json). Any changes made here will be overwritten by the next sync.',
'',
action,
].join('\n');

core.setOutput('body', body);

- name: Request changes on PR
env:
GH_TOKEN: ${{ steps.app-token.outputs.token }}
PR_URL: ${{ github.event.pull_request.html_url }}
run: gh pr review "$PR_URL" --request-changes --body "${{ steps.message.outputs.body }}"
Comment thread
johno-stripe marked this conversation as resolved.
Dismissed
32 changes: 0 additions & 32 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -92,38 +92,6 @@ jobs:
- name: Test
run: pnpm run test

skills-sync-check:
name: Check - Skills in sync
runs-on: ubuntu-latest

steps:
- name: Checkout
uses: actions/checkout@v5

- name: Verify provider skill directories match source
run: |
out_of_sync=false

for provider in claude cursor; do
target="providers/$provider/plugin/skills"

for skill_dir in skills/*/; do
skill_name="$(basename "$skill_dir")"
diff -r "skills/$skill_name" "$target/$skill_name" > /dev/null 2>&1 || {
echo "❌ $target/$skill_name is out of sync with skills/$skill_name"
diff -r "skills/$skill_name" "$target/$skill_name" || true
out_of_sync=true
}
done
done

if [ "$out_of_sync" = true ]; then
echo ""
echo "Fix: run ./scripts/sync-skills.sh and commit the result."
exit 1
fi

echo "✅ All provider skill directories are in sync."

python-build:
name: Build - Python
Expand Down
29 changes: 2 additions & 27 deletions .github/workflows/sync-skills.yml
Original file line number Diff line number Diff line change
Expand Up @@ -37,40 +37,15 @@ jobs:
node-version: '20'

- name: Run sync script
run: node skills/sync.js

- name: Verify provider skill directories match source
run: |
out_of_sync=false

for provider in claude cursor; do
target="providers/$provider/plugin/skills"

for skill_dir in skills/*/; do
skill_name="$(basename "$skill_dir")"
diff -r "skills/$skill_name" "$target/$skill_name" > /dev/null 2>&1 || {
echo "❌ $target/$skill_name is out of sync with skills/$skill_name"
Comment on lines -42 to -52
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

quick question - are we getting rid of this CI test?

Copy link
Copy Markdown
Contributor Author

@johno-stripe johno-stripe May 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah -- it seems like the test was there to pick up when a user changed something in /skills but forgot to sync it with the other provider files. Now that we've got checks in place to ensure that people never directly change files in that directory, and the same logic is being used to write the files into every one of the output directories, it seems unnecessary.

We could technically keep it, but it feels logically dead, so it felt simpler to clean out the last holdover from the previous approach! Happy to bring it back if you think otherwise, though 👍

diff -r "skills/$skill_name" "$target/$skill_name" || true
out_of_sync=true
}
done
done

if [ "$out_of_sync" = true ]; then
echo ""
echo "Fix: run ./scripts/sync-skills.sh and commit the result."
exit 1
fi

echo "✅ All provider skill directories are in sync."
run: node scripts/sync.js

- name: Push to Github
if: |
github.ref == 'refs/heads/main'&&
!github.event.repository.fork
shell: bash
run: |
git add -u
git add ':(glob)**/skills/'

# Skip if nothing changed
if git diff --staged --quiet; then
Expand Down
10 changes: 3 additions & 7 deletions providers/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,8 @@ This directory contains plugins for different AI code editors.

## Skills

**Do not edit skill files in provider directories manually**
**Do not edit skill files in provider directories manually.**

Skills in `providers/*/plugin/skills/` are automatically synced from mcp.stripe.com via [this GitHub Action](https://github.com/stripe/agent-toolkit/blob/main/.github/workflows/sync-skills.yml).
Skills in `providers/*/plugin/skills/` are automatically synced from [docs.stripe.com/.well-known/skills](https://docs.stripe.com/.well-known/skills) via the [sync-skills workflow](/.github/workflows/sync-skills.yml). Any manual changes will be overwritten.

To manually trigger a sync:

1. Go to https://github.com/stripe/agent-toolkit/actions/workflows/sync-skills.yml
2. Click "Run workflow"
3. Click the green "Run workflow" button
To manually trigger a sync, go to the [workflow page](https://github.com/stripe/agent-toolkit/actions/workflows/sync-skills.yml) and click "Run workflow".
25 changes: 0 additions & 25 deletions scripts/sync-skills.sh

This file was deleted.

32 changes: 23 additions & 9 deletions skills/sync.js → scripts/sync.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ const fetchText = (url) => {
try {
return execSync(
`curl -sf --user-agent "github.com/stripe/ai/skills" "${url}"`,
{ encoding: "utf8" }
{ encoding: "utf8" },
);
} catch (err) {
throw new Error(`Failed to fetch ${url}: ${err.message}`);
Expand All @@ -24,17 +24,29 @@ const fetchManifest = () => {
}
};

const cleanDirectory = async (dir) => {
const entries = await fs.readdir(dir, { withFileTypes: true });
for (const entry of entries) {
if (entry.name === "README.md") continue;
await fs.rm(path.join(dir, entry.name), { recursive: true, force: true });
}
};

const OUTPUT_LOCATIONS = [
path.join(__dirname, "../skills"),
path.join(__dirname, "../providers/claude/plugin/skills"),
path.join(__dirname, "../providers/cursor/plugin/skills"),
];

const run = async () => {
const manifest = fetchManifest();
const skills = manifest.skills;
console.log(`Found ${skills.length} skills`);

// Define all locations where skills should be written
const outputLocations = [
__dirname, // skills/ (source of truth)
path.join(__dirname, "../providers/claude/plugin/skills"),
path.join(__dirname, "../providers/cursor/plugin/skills"),
];
for (const location of OUTPUT_LOCATIONS) {
await fs.mkdir(location, { recursive: true });
await cleanDirectory(location);
}

let errors = 0;
for (const skill of skills) {
Expand All @@ -51,7 +63,7 @@ const run = async () => {
continue;
}

for (const location of outputLocations) {
for (const location of OUTPUT_LOCATIONS) {
const outputPath = path.join(location, skill.name, file);
await fs.mkdir(path.dirname(outputPath), { recursive: true });
await fs.writeFile(outputPath, content, "utf8");
Expand All @@ -67,6 +79,8 @@ const run = async () => {

run().catch((err) => {
console.error(err.message);
console.error("Encountered an error while fetching skills, skills will not be updated. Try triggering the workflow manually.");
console.error(
"Encountered an error while fetching skills, skills will not be updated. Try triggering the workflow manually.",
);
process.exit(1);
});
2 changes: 1 addition & 1 deletion skills/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,4 @@ Stripe has:
- MCP Prompts
- Agent skills

This folder is a collection of [agent skills](https://agentskills.io) to steer your agents to build optimal Stripe integrations. These are synced from Stripe servers through [this GitHub Action](https://github.com/stripe/agent-toolkit/blob/main/.github/workflows/sync-skills.yml).
This folder is a collection of [agent skills](https://agentskills.io) to steer your agents to build optimal Stripe integrations. These are synced automatically from [docs.stripe.com/.well-known/skills](https://docs.stripe.com/.well-known/skills) via the [sync-skills workflow](/.github/workflows/sync-skills.yml).
Loading