From dada04bd8cc470caeba9bf117501858f2ee8c3e7 Mon Sep 17 00:00:00 2001 From: Ash Date: Fri, 10 Jul 2026 15:39:07 +0900 Subject: [PATCH] Publish Markdown agent maintenance skill --- .gitignore | 1 + README.md | 2 ++ package.json | 3 +- .../agent-skills/markdown-for-agents/SKILL.md | 31 ++++++++++++++++++ scripts/generate-agent-skills-index.mjs | 32 +++++++++++++++++++ test/agent-skills-index.test.mjs | 20 ++++++++++++ 6 files changed, 88 insertions(+), 1 deletion(-) create mode 100644 public/.well-known/agent-skills/markdown-for-agents/SKILL.md create mode 100644 scripts/generate-agent-skills-index.mjs create mode 100644 test/agent-skills-index.test.mjs diff --git a/.gitignore b/.gitignore index 97e2304..15b5f8c 100644 --- a/.gitignore +++ b/.gitignore @@ -7,6 +7,7 @@ dist/ # build-generated agent Markdown public/agent-home.en.md public/agent-home.ja.md +public/.well-known/agent-skills/index.json # dependencies node_modules/ diff --git a/README.md b/README.md index d2f98ee..166e2f6 100644 --- a/README.md +++ b/README.md @@ -43,6 +43,7 @@ Everything here is maintained by community members; contributions that make the | `npm run events:pull` | Refreshes the committed Meetup event cache and fails on errors. | | `npm run events:pull:stale-ok` | Refreshes Meetup events or preserves the last valid cache. | | `npm run agent:markdown` | Generates the English and Japanese Markdown responses used for agent content negotiation. | +| `npm run agent:skills` | Generates the agent-skills discovery index and its SHA-256 digests. | | `npm run feeds:notify` | Polls approved member feeds and posts unseen items to configured channels. | | `npm run feeds:notify:dry-run` | Shows what the notifier would send without posting or updating state. | | `npm run preview` | Serves the production build locally. | @@ -106,6 +107,7 @@ public/ # Files served as-is (favicon, images) - Production builds run this generator after the stale-safe event/feed refreshes. - Cloudflare Pages Functions at `/` and `/ja/` return those files only when the request explicitly accepts `text/markdown`; normal browser requests continue to receive the Astro HTML pages. - Verify locally or against a preview with `curl -H 'Accept: text/markdown' -D - https://preview-url/`. +- The Markdown maintenance skill is published at `/.well-known/agent-skills/markdown-for-agents/SKILL.md`; `npm run agent:skills` regenerates its discovery index digest during builds. ## Community Feed Notifier diff --git a/package.json b/package.json index cf42dd6..758b16e 100644 --- a/package.json +++ b/package.json @@ -9,7 +9,8 @@ "astro": "astro", "astrocheck": "astro check", "agent:markdown": "node scripts/agent-markdown.mjs", - "build": "npm run feeds:pull:stale-ok && npm run events:pull:stale-ok && npm run agent:markdown && astro build", + "agent:skills": "node scripts/generate-agent-skills-index.mjs", + "build": "npm run feeds:pull:stale-ok && npm run events:pull:stale-ok && npm run agent:markdown && npm run agent:skills && astro build", "check": "npm run lint; npm run tsc; npm run knip; npm run test; npm run astrocheck; npm run images:check", "dev": "astro dev", "feeds:notify": "node scripts/community-feed-notifier.mjs", diff --git a/public/.well-known/agent-skills/markdown-for-agents/SKILL.md b/public/.well-known/agent-skills/markdown-for-agents/SKILL.md new file mode 100644 index 0000000..eb5870b --- /dev/null +++ b/public/.well-known/agent-skills/markdown-for-agents/SKILL.md @@ -0,0 +1,31 @@ +--- +name: markdown-for-agents +description: Maintain Kyoto Tech Meetup's localized Markdown responses for AI agents. +--- + +# Maintain Markdown for Agents + +Keep the agent-facing Markdown contract aligned with the normal Astro homepage. + +## Source of truth + +- `scripts/agent-markdown.mjs` generates the English and Japanese Markdown documents. +- `src/data/meetup-events.json` supplies the event snapshot. +- `src/data/composite-feed.json` supplies the “What members are publishing” items. +- `functions/index.js` and `functions/ja/index.js` negotiate `Accept: text/markdown`. +- `public/_routes.json` keeps the Pages Functions scope limited to `/` and `/ja/`. + +## When changing the contract + +1. Update both English and Japanese output in `scripts/agent-markdown.mjs`. +2. Keep event, feed, RSVP, map, and community links validated as HTTP(S) URLs. +3. Keep the Markdown concise and factual; do not add API, OAuth, MCP, publishing, or account capabilities that the site does not provide. +4. Update `test/agent-markdown.test.mjs` for content, locale, or negotiation changes. +5. Run `npm run check` and `npm run build`. +6. Verify `/` and `/ja/` with `Accept: text/markdown`, and verify a request without that header still returns HTML. + +## Response contract + +Markdown responses must return HTTP 200 with `Content-Type: text/markdown; charset=utf-8` and `Vary: Accept`. Normal browser requests must continue to receive the Astro HTML page and the existing security headers. + +The generated discovery index contains a digest for this file. Run the build after editing it so the index is regenerated. diff --git a/scripts/generate-agent-skills-index.mjs b/scripts/generate-agent-skills-index.mjs new file mode 100644 index 0000000..64062d7 --- /dev/null +++ b/scripts/generate-agent-skills-index.mjs @@ -0,0 +1,32 @@ +import crypto from "node:crypto"; +import fs from "node:fs/promises"; +import path from "node:path"; +import { fileURLToPath } from "node:url"; + +const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), ".."); +const SKILL_PATH = path.join(ROOT, "public/.well-known/agent-skills/markdown-for-agents/SKILL.md"); +const DEFAULT_OUTPUT_PATH = path.join(ROOT, "public/.well-known/agent-skills/index.json"); +const DEFAULT_SKILL_URL = "https://kyototechmeetup.com/.well-known/agent-skills/markdown-for-agents/SKILL.md"; + +export function buildAgentSkillsIndex(skill, skillUrl = DEFAULT_SKILL_URL) { + const digest = crypto.createHash("sha256").update(skill).digest("hex"); + return { + $schema: "https://schemas.agentskills.io/discovery/0.2.0/schema.json", + skills: [{ + name: "markdown-for-agents", + type: "skill-md", + description: "Maintain Kyoto Tech Meetup's localized Markdown responses for AI agents.", + url: skillUrl, + digest: `sha256:${digest}`, + }], + }; +} + +export async function writeAgentSkillsIndex({ skillPath = SKILL_PATH, outputPath = DEFAULT_OUTPUT_PATH, skillUrl = DEFAULT_SKILL_URL } = {}) { + const skill = await fs.readFile(skillPath, "utf8"); + const index = buildAgentSkillsIndex(skill, skillUrl); + await fs.mkdir(path.dirname(outputPath), { recursive: true }); + await fs.writeFile(outputPath, `${JSON.stringify(index, null, 2)}\n`); +} + +if (import.meta.url === `file://${process.argv[1]}`) await writeAgentSkillsIndex(); diff --git a/test/agent-skills-index.test.mjs b/test/agent-skills-index.test.mjs new file mode 100644 index 0000000..1e8a162 --- /dev/null +++ b/test/agent-skills-index.test.mjs @@ -0,0 +1,20 @@ +import crypto from "node:crypto"; +import { describe, expect, test } from "vitest"; +import { buildAgentSkillsIndex } from "../scripts/generate-agent-skills-index.mjs"; + +describe("agent skills discovery index", () => { + test("publishes a digest for the Markdown maintenance skill", () => { + const skill = "# Markdown skill\n"; + const index = buildAgentSkillsIndex(skill, "https://example.com/SKILL.md"); + const expectedDigest = crypto.createHash("sha256").update(skill).digest("hex"); + + expect(index.$schema).toBe("https://schemas.agentskills.io/discovery/0.2.0/schema.json"); + expect(index.skills).toEqual([{ + name: "markdown-for-agents", + type: "skill-md", + description: "Maintain Kyoto Tech Meetup's localized Markdown responses for AI agents.", + url: "https://example.com/SKILL.md", + digest: `sha256:${expectedDigest}`, + }]); + }); +});