Skip to content

chore: regenerate agent-skills index when SKILL.md changes#9

Open
Bohdan-Itrinity wants to merge 1 commit into
mainfrom
feat/agent-skills-index-ci
Open

chore: regenerate agent-skills index when SKILL.md changes#9
Bohdan-Itrinity wants to merge 1 commit into
mainfrom
feat/agent-skills-index-ci

Conversation

@Bohdan-Itrinity

@Bohdan-Itrinity Bohdan-Itrinity commented Jun 22, 2026

Copy link
Copy Markdown

Add generator script and CI workflow to keep .well-known/agent-skills/index.json in sync with skills/*/SKILL.md descriptions and sha256 digests.

Summary by CodeRabbit

  • Chores
    • Added automated workflow to regenerate and maintain the agent skills index. Includes validation checks to keep the index current and automatically commits necessary updates on the main branch.

Add generator script and CI workflow to keep .well-known/agent-skills/index.json
in sync with skills/*/SKILL.md descriptions and sha256 digests.

Co-authored-by: Cursor <cursoragent@cursor.com>
@coderabbitai

coderabbitai Bot commented Jun 22, 2026

Copy link
Copy Markdown

Review Change Stack

Walkthrough

A new Node.js script (scripts/generate-agent-skills-index.mjs) reads YAML frontmatter from skills/*/SKILL.md files to build .well-known/agent-skills/index.json with per-skill name, description, raw GitHub URL, and SHA-256 digest. A new GitHub Actions workflow runs this script on relevant changes, failing PRs if the index is stale and auto-committing updates on pushes to main.

Changes

Agent Skills Index Generator & CI Workflow

Layer / File(s) Summary
Index generator script
scripts/generate-agent-skills-index.mjs
Adds the full generator: CLI arg parsing (--check, --branch=), YAML frontmatter description extraction with quote stripping, SHA-256 digest computation per skill, index object construction ($schema + skills array), JSON serialization/normalization utilities, --check mode that exits 1 on stale output, and write mode that creates the output directory and writes the JSON file.
CI freshness enforcement workflow
.github/workflows/agent-skills-index.yml
Introduces the GitHub Actions workflow triggered on pushes to main, pull requests, and manual dispatch when skills/*/SKILL.md or the generator script changes. On PRs it runs --check and fails if index.json would change; on main it commits and pushes the regenerated file using the GitHub Actions bot with contents: write permission.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

Possibly related PRs

  • uptimerobot/ai#7: Initially introduced .well-known/agent-skills/index.json, which this PR's generator script and workflow are designed to regenerate and keep current.
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'chore: regenerate agent-skills index when SKILL.md changes' clearly and concisely describes the main change: automation to regenerate the agent-skills index in response to SKILL.md modifications.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/agent-skills-index-ci
✨ Simplify code
  • Create PR with simplified code
  • Commit simplified code in branch feat/agent-skills-index-ci

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 6

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In @.github/workflows/agent-skills-index.yml:
- Around line 16-18: The global permissions block grants `contents: write` to
all jobs in the workflow, which violates the principle of least privilege.
Remove the workflow-level permissions block that contains `contents: write`, and
instead add job-specific permissions blocks for each job. For the pull request
validation job, set permissions to `contents: read` to restrict it to read-only
access. For the job that performs auto-commits on the main branch, set
permissions to `contents: write`. This ensures that only jobs that require write
access to the repository contents have that permission granted.
- Around line 25-26: The checkout action at the Checkout step is using the
default behavior which persists credentials in git config for subsequent steps.
To reduce the security risk from a write-capable token, add the
persist-credentials option set to false to the actions/checkout@v4 step. This
should be added in the with section of the checkout action to disable credential
persistence.
- Around line 3-14: The workflow lacks concurrency control which allows multiple
runs to overlap on the same ref, causing race conditions and potential push
failures. Add a concurrency section at the top level of the workflow (at the
same indentation level as the `on:` trigger section) with a group identifier
based on github.ref and set cancel-in-progress to true to automatically cancel
redundant in-flight runs when a new push occurs on the same branch or ref.
- Around line 26-30: The GitHub Actions in the workflow file are pinned to
version tags (`@v4`) instead of full commit SHAs, which leaves the workflow
vulnerable to upstream changes. Replace the version tag pinning in both the
actions/checkout and actions/setup-node steps with their respective full commit
SHAs to ensure immutability and verifiability of the workflow execution.

In `@scripts/generate-agent-skills-index.mjs`:
- Around line 37-57: The parseDescription() function's regex pattern for
matching the description value only captures single-line descriptions and fails
to handle valid YAML multiline syntax like description: | or description: >.
Update the regex that creates descriptionMatch to handle both single-line and
multiline YAML description formats by allowing optional whitespace and
pipe/greater-than operators after the colon, and then implement logic to
properly extract and process the description content whether it's inline or
spans multiple lines in the YAML frontmatter.
- Around line 86-93: The digest is being computed from the decoded UTF-8 text
string rather than from the raw file bytes, which can cause digest mismatches
due to encoding variations. Modify the file reading logic to read the file as
raw bytes first (remove the "utf8" encoding parameter from fs.readFileSync),
compute the digest from those raw bytes using the digestFor function, and then
separately decode the bytes to UTF-8 text for use with the parseDescription
function. This ensures the digest value matches the actual file bytes rather
than a decoded text representation.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: a49bf05a-7c31-45ca-bf46-69a7386ae20b

📥 Commits

Reviewing files that changed from the base of the PR and between 86f3707 and 7ecc4cc.

📒 Files selected for processing (2)
  • .github/workflows/agent-skills-index.yml
  • scripts/generate-agent-skills-index.mjs

Comment on lines +3 to +14
on:
push:
branches: [main]
paths:
- 'skills/*/SKILL.md'
- 'scripts/generate-agent-skills-index.mjs'
pull_request:
paths:
- 'skills/*/SKILL.md'
- 'scripts/generate-agent-skills-index.mjs'
- '.well-known/agent-skills/index.json'
workflow_dispatch:

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Add concurrency control to avoid overlapping runs on the same ref.

Between Line 3 and Line 14, no concurrency group is defined. Multiple close pushes can race, causing redundant regenerate/commit attempts and occasional push failures.

Suggested fix
 on:
   push:
@@
   workflow_dispatch:
+
+concurrency:
+  group: agent-skills-index-${{ github.ref }}
+  cancel-in-progress: true
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
on:
push:
branches: [main]
paths:
- 'skills/*/SKILL.md'
- 'scripts/generate-agent-skills-index.mjs'
pull_request:
paths:
- 'skills/*/SKILL.md'
- 'scripts/generate-agent-skills-index.mjs'
- '.well-known/agent-skills/index.json'
workflow_dispatch:
on:
push:
branches: [main]
paths:
- 'skills/*/SKILL.md'
- 'scripts/generate-agent-skills-index.mjs'
pull_request:
paths:
- 'skills/*/SKILL.md'
- 'scripts/generate-agent-skills-index.mjs'
- '.well-known/agent-skills/index.json'
workflow_dispatch:
concurrency:
group: agent-skills-index-${{ github.ref }}
cancel-in-progress: true
🧰 Tools
🪛 zizmor (1.25.2)

[warning] 3-14: insufficient job-level concurrency limits (concurrency-limits): workflow is missing concurrency setting

(concurrency-limits)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/agent-skills-index.yml around lines 3 - 14, The workflow
lacks concurrency control which allows multiple runs to overlap on the same ref,
causing race conditions and potential push failures. Add a concurrency section
at the top level of the workflow (at the same indentation level as the `on:`
trigger section) with a group identifier based on github.ref and set
cancel-in-progress to true to automatically cancel redundant in-flight runs when
a new push occurs on the same branch or ref.

Source: Linters/SAST tools

Comment on lines +16 to +18
permissions:
contents: write

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Permissions are broader than required for PR validation.

At Line 17, contents: write is granted workflow-wide, including pull_request runs that only need read access. Split permissions by job (read for PR check, write only for main auto-commit) to enforce least privilege.

🧰 Tools
🪛 zizmor (1.25.2)

[error] 17-17: overly broad permissions (excessive-permissions): contents: write is overly broad at the workflow level

(excessive-permissions)


[warning] 17-17: permissions without explanatory comments (undocumented-permissions): needs an explanatory comment

(undocumented-permissions)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/agent-skills-index.yml around lines 16 - 18, The global
permissions block grants `contents: write` to all jobs in the workflow, which
violates the principle of least privilege. Remove the workflow-level permissions
block that contains `contents: write`, and instead add job-specific permissions
blocks for each job. For the pull request validation job, set permissions to
`contents: read` to restrict it to read-only access. For the job that performs
auto-commits on the main branch, set permissions to `contents: write`. This
ensures that only jobs that require write access to the repository contents have
that permission granted.

Source: Linters/SAST tools

Comment on lines +25 to +26
- name: Checkout
uses: actions/checkout@v4

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Disable credential persistence on checkout for safer token handling.

At Line 26, checkout defaults to persisting credentials in git config for later steps. With a write-capable token in this job, reducing credential lifetime lowers blast radius if later steps are compromised.

Suggested fix
       - name: Checkout
         uses: actions/checkout@v4
+        with:
+          persist-credentials: false
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
- name: Checkout
uses: actions/checkout@v4
- name: Checkout
uses: actions/checkout@v4
with:
persist-credentials: false
🧰 Tools
🪛 zizmor (1.25.2)

[warning] 25-26: credential persistence through GitHub Actions artifacts (artipacked): does not set persist-credentials: false

(artipacked)


[error] 26-26: unpinned action reference (unpinned-uses): action is not pinned to a hash (required by blanket policy)

(unpinned-uses)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/agent-skills-index.yml around lines 25 - 26, The checkout
action at the Checkout step is using the default behavior which persists
credentials in git config for subsequent steps. To reduce the security risk from
a write-capable token, add the persist-credentials option set to false to the
actions/checkout@v4 step. This should be added in the with section of the
checkout action to disable credential persistence.

Source: Linters/SAST tools

Comment on lines +26 to +30
uses: actions/checkout@v4

- name: Setup Node.js
uses: actions/setup-node@v4
with:

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify all workflow actions are SHA-pinned.
# Expected: each `uses:` should include a full commit SHA (40 hex chars).
rg -nP --type=yaml '^\s*uses:\s*[^@]+@(?!(?:[a-f0-9]{40})\b).+' .github/workflows

Repository: uptimerobot/ai

Length of output: 216


🏁 Script executed:

cat -n .github/workflows/agent-skills-index.yml | head -35

Repository: uptimerobot/ai

Length of output: 1014


🏁 Script executed:

rg -nP --type=yaml '^\s*uses:\s*[^@]+@(?!(?:[a-f0-9]{40})\b).+' .github/workflows

Repository: uptimerobot/ai

Length of output: 216


Pin GitHub Actions to commit SHAs instead of version tags.

Lines 26 and 29 use tag-pinning (@v4) rather than commit SHA pinning, which leaves the workflow exposed to upstream changes. Replace with full commit SHAs for immutability and verifiability:

Required changes
      - name: Checkout
        uses: actions/checkout@<full-commit-sha>

      - name: Setup Node.js
        uses: actions/setup-node@<full-commit-sha>
🧰 Tools
🪛 zizmor (1.25.2)

[error] 26-26: unpinned action reference (unpinned-uses): action is not pinned to a hash (required by blanket policy)

(unpinned-uses)


[error] 29-29: unpinned action reference (unpinned-uses): action is not pinned to a hash (required by blanket policy)

(unpinned-uses)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/agent-skills-index.yml around lines 26 - 30, The GitHub
Actions in the workflow file are pinned to version tags (`@v4`) instead of full
commit SHAs, which leaves the workflow vulnerable to upstream changes. Replace
the version tag pinning in both the actions/checkout and actions/setup-node
steps with their respective full commit SHAs to ensure immutability and
verifiability of the workflow execution.

Source: Linters/SAST tools

Comment on lines +37 to +57
function parseDescription(skillMd) {
const match = skillMd.match(/^---\r?\n([\s\S]*?)\r?\n---/);
if (!match) {
fail("SKILL.md is missing YAML frontmatter");
}

const frontmatter = match[1];
const descriptionMatch = frontmatter.match(/^description:\s*(.+)$/m);
if (!descriptionMatch) {
fail("SKILL.md frontmatter is missing description");
}

let description = descriptionMatch[1].trim();
if (
(description.startsWith('"') && description.endsWith('"')) ||
(description.startsWith("'") && description.endsWith("'"))
) {
description = description.slice(1, -1);
}

return description;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

parseDescription() rejects valid YAML descriptions.

At Line 44, the regex only accepts single-line description: values. Valid frontmatter like description: | / > (multiline YAML) will fail or produce incorrect data, making index generation brittle against legitimate SKILL.md metadata.

Suggested fix
 function parseDescription(skillMd) {
   const match = skillMd.match(/^---\r?\n([\s\S]*?)\r?\n---/);
   if (!match) {
     fail("SKILL.md is missing YAML frontmatter");
   }

   const frontmatter = match[1];
-  const descriptionMatch = frontmatter.match(/^description:\s*(.+)$/m);
+  const descriptionMatch = frontmatter.match(
+    /^description:\s*(.+)$|^description:\s*[>|]\s*\n([\s\S]*?)(?=^\w[\w-]*:\s*|\s*$)/m,
+  );
   if (!descriptionMatch) {
     fail("SKILL.md frontmatter is missing description");
   }

-  let description = descriptionMatch[1].trim();
+  let description = (descriptionMatch[1] ?? descriptionMatch[2] ?? "").trim();
   if (
     (description.startsWith('"') && description.endsWith('"')) ||
     (description.startsWith("'") && description.endsWith("'"))
   ) {
     description = description.slice(1, -1);
   }

   return description;
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@scripts/generate-agent-skills-index.mjs` around lines 37 - 57, The
parseDescription() function's regex pattern for matching the description value
only captures single-line descriptions and fails to handle valid YAML multiline
syntax like description: | or description: >. Update the regex that creates
descriptionMatch to handle both single-line and multiline YAML description
formats by allowing optional whitespace and pipe/greater-than operators after
the colon, and then implement logic to properly extract and process the
description content whether it's inline or spans multiple lines in the YAML
frontmatter.

Comment on lines +86 to +93
const content = fs.readFileSync(skillPath, "utf8");
return {
name,
type: "skill-md",
description: parseDescription(content),
url: `https://raw.githubusercontent.com/${GITHUB_ORG_REPO}/${branch}/skills/${name}/SKILL.md`,
digest: digestFor(content),
};

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Digest should be computed from file bytes, not decoded text.

At Line 86 and Line 92, the file is read as UTF-8 text and then hashed. The stated contract is hash of SKILL.md bytes; hashing decoded text can diverge for non-canonical encodings/BOM edge cases and break cross-system digest verification.

Suggested fix
-function digestFor(content) {
-  const hash = crypto.createHash("sha256").update(content, "utf8").digest("hex");
+function digestFor(contentBytes) {
+  const hash = crypto.createHash("sha256").update(contentBytes).digest("hex");
   return `sha256:${hash}`;
 }
@@
-    const content = fs.readFileSync(skillPath, "utf8");
+    const contentBytes = fs.readFileSync(skillPath);
+    const content = contentBytes.toString("utf8");
     return {
       name,
       type: "skill-md",
       description: parseDescription(content),
       url: `https://raw.githubusercontent.com/${GITHUB_ORG_REPO}/${branch}/skills/${name}/SKILL.md`,
-      digest: digestFor(content),
+      digest: digestFor(contentBytes),
     };
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@scripts/generate-agent-skills-index.mjs` around lines 86 - 93, The digest is
being computed from the decoded UTF-8 text string rather than from the raw file
bytes, which can cause digest mismatches due to encoding variations. Modify the
file reading logic to read the file as raw bytes first (remove the "utf8"
encoding parameter from fs.readFileSync), compute the digest from those raw
bytes using the digestFor function, and then separately decode the bytes to
UTF-8 text for use with the parseDescription function. This ensures the digest
value matches the actual file bytes rather than a decoded text representation.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant