From c316c546cb4d7cda2c2e5bc2836821d57f617675 Mon Sep 17 00:00:00 2001 From: Hong Minhee Date: Thu, 7 May 2026 13:36:44 +0900 Subject: [PATCH 1/9] Add Claude Code plugin for Fedify Adds a Claude Code plugin at claude-plugin/ that can be installed via the Fedify repository acting as its own marketplace: /plugin marketplace add fedify-dev/fedify /plugin install fedify@fedify The plugin provides six slash commands and two specialized agents: Slash commands: - /fedify:fedify General Fedify knowledge (auto-invoked by Claude) - /fedify:docs Fetch and explain fedify.dev documentation - /fedify:actor Guide through implementing an ActivityPub actor - /fedify:inbox Help set up inbox listeners - /fedify:migration Migrate code between Fedify versions - /fedify:fep Look up a Fediverse Enhancement Proposal Agents: - fedify-reviewer Best-practice and security review of Fedify code - fedify-debugger Federation troubleshooting specialist The fedify skill lives canonically in claude-plugin/skills/fedify/ and is referenced from packages/fedify/skills/fedify via a symlink. The npm prepack script (scripts/pack-skills.mjs) resolves the symlink to real files before packing so the published tarball is self-contained, then restores the symlink via postpack. Also updates docs/install.md to document the Claude Code plugin as the primary installation path for Deno users, adds .claude-plugin/ and claude-plugin/ to deno.json excludes, and registers "Claude Code" as a proper noun in .hongdown.toml. Closes https://github.com/fedify-dev/fedify/issues/489 Assisted-by: Claude Code:claude-sonnet-4-6 Assisted-by: Codex:gpt-5.5 --- .claude-plugin/marketplace.json | 18 ++++++ .hongdown.toml | 1 + CHANGES.md | 20 ++++++ claude-plugin/.claude-plugin/plugin.json | 12 ++++ claude-plugin/agents/fedify-debugger.md | 48 ++++++++++++++ claude-plugin/agents/fedify-reviewer.md | 64 +++++++++++++++++++ claude-plugin/skills/actor/SKILL.md | 25 ++++++++ claude-plugin/skills/docs/SKILL.md | 18 ++++++ .../skills/fedify/SKILL.md | 0 claude-plugin/skills/fep/SKILL.md | 34 ++++++++++ claude-plugin/skills/inbox/SKILL.md | 25 ++++++++ claude-plugin/skills/migration/SKILL.md | 30 +++++++++ deno.json | 2 + docs/install.md | 23 ++++--- packages/fedify/package.json | 3 +- packages/fedify/scripts/pack-skills.mjs | 45 +++++++++++++ packages/fedify/skills/fedify | 1 + 17 files changed, 359 insertions(+), 10 deletions(-) create mode 100644 .claude-plugin/marketplace.json create mode 100644 claude-plugin/.claude-plugin/plugin.json create mode 100644 claude-plugin/agents/fedify-debugger.md create mode 100644 claude-plugin/agents/fedify-reviewer.md create mode 100644 claude-plugin/skills/actor/SKILL.md create mode 100644 claude-plugin/skills/docs/SKILL.md rename {packages/fedify => claude-plugin}/skills/fedify/SKILL.md (100%) create mode 100644 claude-plugin/skills/fep/SKILL.md create mode 100644 claude-plugin/skills/inbox/SKILL.md create mode 100644 claude-plugin/skills/migration/SKILL.md create mode 100644 packages/fedify/scripts/pack-skills.mjs create mode 120000 packages/fedify/skills/fedify diff --git a/.claude-plugin/marketplace.json b/.claude-plugin/marketplace.json new file mode 100644 index 000000000..0fb010b25 --- /dev/null +++ b/.claude-plugin/marketplace.json @@ -0,0 +1,18 @@ +{ + "name": "fedify", + "owner": { + "name": "Hong Minhee", + "email": "hong@minhee.org" + }, + "description": "Official Fedify plugin for Claude Code: ActivityPub development tools", + "plugins": [ + { + "name": "fedify", + "source": "./claude-plugin", + "description": "Fedify ActivityPub development tools: skills, agents, and slash commands", + "homepage": "https://fedify.dev/", + "repository": "https://github.com/fedify-dev/fedify", + "license": "MIT" + } + ] +} diff --git a/.hongdown.toml b/.hongdown.toml index 7d378750a..978213c36 100644 --- a/.hongdown.toml +++ b/.hongdown.toml @@ -45,6 +45,7 @@ proper_nouns = [ "BotKit", "BrowserPub", "bun-types", + "Claude Code", "cloudflared", "Cloudflare Tunnel", "Cloudflare Workers", diff --git a/CHANGES.md b/CHANGES.md index c57a58599..85085a014 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -86,6 +86,26 @@ To be released. - Added `SqliteMessageQueue.getDepth()` for reporting queued, ready, and delayed message counts. [[#735], [#748]] +### Claude Code plugin + + - Added a Claude Code plugin at *claude-plugin/*, installable with: + + ~~~~ text + /plugin marketplace add fedify-dev/fedify + /plugin install fedify@fedify + ~~~~ + + The plugin provides six slash commands (`/fedify:fedify`, `/fedify:docs`, + `/fedify:actor`, `/fedify:inbox`, `/fedify:migration`, `/fedify:fep`) and + two specialized + agents (`fedify-reviewer` and `fedify-debugger`). The Agent Skills bundle + lives canonically in *claude-plugin/skills/fedify/* and is referenced from + *packages/fedify/skills/fedify/* via a symlink; the `prepack` script + resolves the symlink to real files before packing so the published npm + tarball is self-contained. [[#489]] + +[#489]: https://github.com/fedify-dev/fedify/issues/489 + Version 2.2.0 ------------- diff --git a/claude-plugin/.claude-plugin/plugin.json b/claude-plugin/.claude-plugin/plugin.json new file mode 100644 index 000000000..cb66da5fa --- /dev/null +++ b/claude-plugin/.claude-plugin/plugin.json @@ -0,0 +1,12 @@ +{ + "name": "fedify", + "description": "ActivityPub development tools powered by Fedify", + "version": "2.3.0", + "author": { + "name": "Hong Minhee", + "email": "hong@minhee.org" + }, + "homepage": "https://fedify.dev/", + "repository": "https://github.com/fedify-dev/fedify", + "license": "MIT" +} diff --git a/claude-plugin/agents/fedify-debugger.md b/claude-plugin/agents/fedify-debugger.md new file mode 100644 index 000000000..f233dfc36 --- /dev/null +++ b/claude-plugin/agents/fedify-debugger.md @@ -0,0 +1,48 @@ +--- +name: fedify-debugger +description: >- + Use when debugging Fedify issues: WebFinger resolution failures, HTTP + signature verification errors, activity delivery failures, inbox + processing problems, or interoperability issues with Mastodon, Misskey, + or other fediverse software. +tools: Read, Grep, Glob, Bash +model: sonnet +skills: + - fedify:fedify +--- + +You are a Fedify debugging specialist. + +When you are given a problem, follow this structured approach: + +1. *Gather symptoms:* collect error messages, log output, HTTP + response codes, and the Fedify version. + +2. *Identify the layer:* classify the problem into one of: + - WebFinger / actor discovery + - HTTP Signature verification (inbound or outbound) + - Object Integrity Proofs + - Activity delivery (outbound queue / fan-out) + - Inbox processing (signature check, deserialization, handler) + - NodeInfo or protocol negotiation + - Vocabulary / JSON-LD parsing + +3. *Root-cause analysis:* for each layer, check: + - *WebFinger:* Is the actor identifier correct? Does + `/.well-known/webfinger` resolve to the actor's canonical URL? + - *HTTP Signatures:* Is the `Date` header within clock skew? Is + the correct key ID returned from `setKeyPairsDispatcher`? Is the + host behind a proxy that rewrites `Host`? + - *Delivery:* Is `queue` configured? Are worker nodes load-balanced + (which breaks idempotency)? + - *Inbox:* Is an `.on()` handler registered for the activity type? + Does the activity pass signature verification? + - *Vocabulary:* Is the JSON-LD context resolvable? Are imports from + `@fedify/vocab`, not the deprecated shims? + +4. *Propose a fix:* show the minimal code change that resolves the + issue, with before/after snippets. + +5. *Suggest prevention:* mention LogTape categories to enable for + future visibility (`fedify.sig.http`, `fedify.federation.inbox`, + etc.) and link to the relevant docs page. diff --git a/claude-plugin/agents/fedify-reviewer.md b/claude-plugin/agents/fedify-reviewer.md new file mode 100644 index 000000000..ed423c6b0 --- /dev/null +++ b/claude-plugin/agents/fedify-reviewer.md @@ -0,0 +1,64 @@ +--- +name: fedify-reviewer +description: >- + Use proactively after changes to Fedify-based ActivityPub code to check + for best-practice violations, security issues, and interoperability + problems. Invoke when reviewing dispatcher implementations, inbox + listeners, key pair handling, vocabulary usage, or federation middleware + configuration. +tools: Read, Grep, Glob, Bash +model: sonnet +skills: + - fedify:fedify +--- + +You are a senior code reviewer specialising in Fedify and ActivityPub. + +When reviewing Fedify code, check each of the following in order: + +*Builder and federation setup* + + - Is `builder.build()` awaited when using `createFederationBuilder()`? + (`createFederation()` is synchronous and must not be awaited.) + - Is a real `KvStore` (not `MemoryKvStore`) used in production paths? + - Is a `queue` provided for production deployments? + - Is `allowPrivateAddress` only set in test code? + - Is `FederationOptions.origin` or `x-forwarded-fetch` configured when + running behind a reverse proxy or tunnel? + +*Actors and dispatchers* + + - Does the actor dispatcher return an `Actor` with `id`, `inbox`, and + at least one `publicKey`? + - Does `setKeyPairsDispatcher` return both an RSA and an Ed25519 key? + - Are URI template variables using `{+identifier}` when identifiers + can contain reserved URI characters? + - Does `mapActorAlias` validate the identifier before dispatching? + +*Inbox listeners* + + - Are all expected activity types registered with `.on()`? + - If unregistered types must be observed (rather than answered with HTTP 202), + is there a catch-all `.on(Activity, ...)` listener? + - Is `.onError()` used for handler-level error logging? + - Is idempotency handled to avoid duplicate processing? + +*Key and security hygiene* + + - Are private keys read from secret storage, not hardcoded or committed? + - Is `crossOrigin: "trust"` only used for genuinely trusted origins? + +*Vocabulary and imports* + + - Are types imported from `@fedify/vocab`, not from + `@fedify/fedify/vocab` (deprecated shim)? + - Are `fromJsonLd()` / `toJsonLd()` calls awaited? + +*Activity IDs* + + - Are outgoing activity IDs derived from fresh UUIDs/counters, not from + `(actor, object)` pairs? + +Report findings grouped by severity: *blocking*, *warning*, and +*suggestion*. For each finding, cite the file and line, explain the +risk, and provide a concrete fix. diff --git a/claude-plugin/skills/actor/SKILL.md b/claude-plugin/skills/actor/SKILL.md new file mode 100644 index 000000000..6b35cff5f --- /dev/null +++ b/claude-plugin/skills/actor/SKILL.md @@ -0,0 +1,25 @@ +--- +name: actor +description: >- + Guide the user through implementing an ActivityPub actor with Fedify. + Use when the user needs to add or configure an actor dispatcher, set up + key pairs, configure aliases, or handle actor-related requests. +--- + +Help the user implement an ActivityPub actor using Fedify. + +Walk through: + +1. Registering `setActorDispatcher(path, handler)` on the `Federation` + object or `FederationBuilder`, including the `{identifier}` path + parameter. +2. Returning a `Person`, `Service`, or other `Actor` vocabulary object + with the required fields (`id`, `inbox`, `publicKey`, etc.). +3. Chaining `.setKeyPairsDispatcher()` to supply RSA and Ed25519 keys. +4. Optionally configuring `mapActorAlias()` for handle-based or + fixed-path aliases. +5. Making the actor discoverable via WebFinger. Fedify handles + `/.well-known/webfinger` automatically once the actor dispatcher + is registered. + +Reference: diff --git a/claude-plugin/skills/docs/SKILL.md b/claude-plugin/skills/docs/SKILL.md new file mode 100644 index 000000000..3108d34de --- /dev/null +++ b/claude-plugin/skills/docs/SKILL.md @@ -0,0 +1,18 @@ +--- +name: docs +description: >- + Fetch and explain Fedify documentation on a specific topic. Use when the + user asks about Fedify API details, configuration options, or how a + specific feature works. Fetches up-to-date docs from fedify.dev. +argument-hint: +--- + +Fetch and explain Fedify documentation about “$ARGUMENTS”. + +1. Use WebFetch on the relevant fedify.dev page (append `.md` to get raw + Markdown, e.g. `https://fedify.dev/manual/federation.md`). + The documentation index is at . +2. Summarise the key points with runnable TypeScript examples. +3. Mention related pages the user might also want to read. + +Always strip the `.md` suffix when presenting links to the user. diff --git a/packages/fedify/skills/fedify/SKILL.md b/claude-plugin/skills/fedify/SKILL.md similarity index 100% rename from packages/fedify/skills/fedify/SKILL.md rename to claude-plugin/skills/fedify/SKILL.md diff --git a/claude-plugin/skills/fep/SKILL.md b/claude-plugin/skills/fep/SKILL.md new file mode 100644 index 000000000..1926f4a33 --- /dev/null +++ b/claude-plugin/skills/fep/SKILL.md @@ -0,0 +1,34 @@ +--- +name: fep +description: >- + Look up a Fediverse Enhancement Proposal (FEP) and explain how to + implement it with Fedify. Use when the user asks about a specific FEP + by ID (e.g., FEP-8fcf, FEP-1b12) or wants to implement a fediverse + standard in their Fedify application. +argument-hint: +--- + +Look up the Fediverse Enhancement Proposal identified by “$ARGUMENTS” and +explain how to implement it with Fedify. + +Normalise the identifier first: strip any leading `FEP-` or `fep-` prefix +and lowercase the result to get the bare four-character hex id (e.g. `8fcf`). +Call that `$ID` in the steps below. If the result does not match +`^[0-9a-f]{4}$`, stop and ask the user to provide a valid FEP identifier. + +1. If the `fep` MCP server is available, use `mcp__fep__get_fep` with + id `$ID` to retrieve the proposal. + Otherwise clone the proposals repo and read the file: + + ~~~~ bash + git clone https://codeberg.org/fediverse/fep.git /tmp/fep-repo 2>/dev/null \ + || git -C /tmp/fep-repo pull --ff-only + cat /tmp/fep-repo/fep/$ID/fep-$ID.md + ~~~~ + +2. Summarise: status, what problem it solves, and what extensions it + defines (new JSON-LD terms, HTTP endpoints, or activity shapes). + +3. Explain which Fedify APIs are relevant to the implementation: + vocabulary types in `@fedify/vocab`, custom context handling, + dispatcher or inbox listener patterns. diff --git a/claude-plugin/skills/inbox/SKILL.md b/claude-plugin/skills/inbox/SKILL.md new file mode 100644 index 000000000..c3c078b64 --- /dev/null +++ b/claude-plugin/skills/inbox/SKILL.md @@ -0,0 +1,25 @@ +--- +name: inbox +description: >- + Help the user set up Fedify inbox listeners for handling incoming + ActivityPub activities. Use when the user needs to handle Follow, Like, + Announce, Create, Undo, or other activity types delivered to their inbox. +--- + +Help the user configure Fedify inbox listeners. + +Walk through: + +1. Calling `setInboxListeners(inboxPath, sharedInboxPath?)` on the + `Federation` / `FederationBuilder`. +2. Chaining `.on(ActivityType, handler)` for each activity type to handle + (`Follow`, `Create`, `Undo`, `Like`, `Announce`, etc.). +3. Adding `.onError(handler)` for error logging. +4. Optionally adding `.onUnverifiedActivity(handler)` if the user needs + to inspect activities that failed signature verification. +5. Using `.withIdempotency(strategy)` to avoid duplicate processing. +6. Noting that activity types with no registered handler receive HTTP 202 + and are logged as unsupported: add a catch-all on the base `Activity` + class if needed. + +Reference: diff --git a/claude-plugin/skills/migration/SKILL.md b/claude-plugin/skills/migration/SKILL.md new file mode 100644 index 000000000..9a3d5c2f6 --- /dev/null +++ b/claude-plugin/skills/migration/SKILL.md @@ -0,0 +1,30 @@ +--- +name: migration +description: >- + Help the user migrate Fedify code between versions. Use when the user + needs to upgrade their Fedify version, fix breaking-change errors, or + update deprecated API usage. +argument-hint: [to-version] +--- + +Help the user migrate Fedify code from “$ARGUMENTS”. + +Steps: + +1. Fetch the CHANGES.md from the repo to identify breaking changes between + the versions in question: + `https://raw.githubusercontent.com/fedify-dev/fedify/refs/heads/main/CHANGES.md` +2. List every breaking change that affects the user's code range. +3. For each breaking change, show the old API, the new API, and a concrete + before/after code snippet. +4. Search the user's codebase for usages of deprecated symbols and suggest + the replacement. +5. Note any dependency changes (e.g., vocabulary moved to `@fedify/vocab`, + runtime to `@fedify/vocab-runtime`). + +Key migration hints: + + - `@fedify/fedify/vocab` → `@fedify/vocab` (dedicated package) + - `@fedify/fedify/runtime` → `@fedify/vocab-runtime` + - In-tree `src/webfinger` → `@fedify/webfinger` + - `src/x/` exports removed in 2.0.0 diff --git a/deno.json b/deno.json index 55f99c3a7..cc061b9fd 100644 --- a/deno.json +++ b/deno.json @@ -87,8 +87,10 @@ "exclude": [ "**/pnpm-lock.yaml", ".agents/skills/", + ".claude-plugin/", ".claude/skills/", ".github/", + "claude-plugin/", "docs/", "pnpm-lock.yaml", "pnpm-workspace.yaml" diff --git a/docs/install.md b/docs/install.md index 765abd4d5..e41382153 100644 --- a/docs/install.md +++ b/docs/install.md @@ -361,11 +361,22 @@ common pitfalls, and recommended patterns. The file lives inside the `@fedify/fedify` package itself, so the only remaining step is exposing it to your agent's skills directory. -[SKILL.md]: https://github.com/fedify-dev/fedify/blob/main/packages/fedify/skills/fedify/SKILL.md +[SKILL.md]: https://github.com/fedify-dev/fedify/blob/main/claude-plugin/skills/fedify/SKILL.md [Claude Code]: https://claude.com/product/claude-code [Codex]: https://developers.openai.com/codex [OpenCode]: https://opencode.ai/ +### Claude Code plugin + +Install the Fedify plugin from the Claude Code marketplace. The plugin works +with any runtime and adds six slash commands, two specialized agents, and the +full Fedify skill: + +~~~~ +/plugin marketplace add fedify-dev/fedify +/plugin install fedify@fedify +~~~~ + ### Node.js/Bun Use [`skills-npm`], a third-party tool by Anthony Fu (it is not a Fedify @@ -419,11 +430,7 @@ packages that adopt the convention. ### Deno -> [!NOTE] -> Automated installation for Deno is not available yet, so the skill must be -> installed by hand for the time being. Future automation through the -> Claude Code plugin marketplace is tracked in -> [issue #489]. +Install the skill by hand: 1. Pick your agent's skills directory. For Claude Code, this is *.claude/skills/fedify/*. @@ -433,10 +440,8 @@ packages that adopt the convention. ~~~~ sh mkdir -p .claude/skills/fedify curl -L -o .claude/skills/fedify/SKILL.md \ - https://raw.githubusercontent.com/fedify-dev/fedify/main/packages/fedify/skills/fedify/SKILL.md + https://raw.githubusercontent.com/fedify-dev/fedify/main/claude-plugin/skills/fedify/SKILL.md ~~~~ 3. Either commit the file or add it to *.gitignore*, depending on your team's preference. - -[issue #489]: https://github.com/fedify-dev/fedify/issues/489 diff --git a/packages/fedify/package.json b/packages/fedify/package.json index 4aab14153..4883c51e7 100644 --- a/packages/fedify/package.json +++ b/packages/fedify/package.json @@ -174,7 +174,8 @@ "scripts": { "build:self": "tsdown", "build": "pnpm --filter @fedify/fedify... run build:self", - "prepack": "pnpm build", + "prepack": "pnpm build && node scripts/pack-skills.mjs pre", + "postpack": "node scripts/pack-skills.mjs post", "prepublish": "pnpm build", "pretest": "pnpm build", "test": "cd dist/ && node --test", diff --git a/packages/fedify/scripts/pack-skills.mjs b/packages/fedify/scripts/pack-skills.mjs new file mode 100644 index 000000000..286bf8c7d --- /dev/null +++ b/packages/fedify/scripts/pack-skills.mjs @@ -0,0 +1,45 @@ +// Resolves the skills/fedify symlink to real files before npm pack, and +// restores the symlink afterwards. Only acts when the path is a symlink; +// a real directory is left untouched in both directions. +import { + cpSync, + existsSync, + lstatSync, + realpathSync, + rmSync, + symlinkSync, + unlinkSync, + writeFileSync, +} from "node:fs"; +import { dirname, join } from "node:path"; +import process from "node:process"; +import { fileURLToPath } from "node:url"; + +const root = dirname(fileURLToPath(import.meta.url)); +const skillsPath = join(root, "..", "skills", "fedify"); +const sentinel = join(root, "..", ".skills-fedify-symlink"); + +const [cmd] = process.argv.slice(2); + +if (cmd === "pre") { + const stat = lstatSync(skillsPath); + if (stat.isSymbolicLink()) { + const target = realpathSync(skillsPath); + rmSync(skillsPath, { recursive: true }); + cpSync(target, skillsPath, { recursive: true }); + writeFileSync(sentinel, ""); + } else if (!stat.isDirectory()) { + throw new Error( + `skills/fedify is neither a symlink nor a directory (got mode ${ + stat.mode.toString(8) + }). ` + + "On Windows, check core.symlinks in git config.", + ); + } +} else if (cmd === "post") { + if (existsSync(sentinel)) { + rmSync(skillsPath, { recursive: true }); + symlinkSync("../../../claude-plugin/skills/fedify", skillsPath); + unlinkSync(sentinel); + } +} diff --git a/packages/fedify/skills/fedify b/packages/fedify/skills/fedify new file mode 120000 index 000000000..55b1acb2a --- /dev/null +++ b/packages/fedify/skills/fedify @@ -0,0 +1 @@ +../../../claude-plugin/skills/fedify \ No newline at end of file From 3e5432661356f31fb9b9e19965ddfa2bf96f645c Mon Sep 17 00:00:00 2001 From: Hong Minhee Date: Thu, 7 May 2026 14:28:32 +0900 Subject: [PATCH 2/9] Wrap file paths in asterisks in migration skill CHANGES.md, src/webfinger, and src/x/ are file/directory paths and follow the repo convention of asterisk wrapping. Module specifiers such as @fedify/fedify/vocab and @fedify/fedify/runtime are code literals and stay in backticks. https://github.com/fedify-dev/fedify/pull/756#discussion_r3199039365 Assisted-by: Claude Code:claude-sonnet-4-6 --- claude-plugin/skills/migration/SKILL.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/claude-plugin/skills/migration/SKILL.md b/claude-plugin/skills/migration/SKILL.md index 9a3d5c2f6..2361fb6e2 100644 --- a/claude-plugin/skills/migration/SKILL.md +++ b/claude-plugin/skills/migration/SKILL.md @@ -11,7 +11,7 @@ Help the user migrate Fedify code from “$ARGUMENTS”. Steps: -1. Fetch the CHANGES.md from the repo to identify breaking changes between +1. Fetch *CHANGES.md* from the repo to identify breaking changes between the versions in question: `https://raw.githubusercontent.com/fedify-dev/fedify/refs/heads/main/CHANGES.md` 2. List every breaking change that affects the user's code range. @@ -26,5 +26,5 @@ Key migration hints: - `@fedify/fedify/vocab` → `@fedify/vocab` (dedicated package) - `@fedify/fedify/runtime` → `@fedify/vocab-runtime` - - In-tree `src/webfinger` → `@fedify/webfinger` - - `src/x/` exports removed in 2.0.0 + - In-tree *src/webfinger* → `@fedify/webfinger` + - *src/x/* exports removed in 2.0.0 From 9bd10af7f6d091bf32cc289b29389122a5b47872 Mon Sep 17 00:00:00 2001 From: Hong Minhee Date: Thu, 7 May 2026 15:39:24 +0900 Subject: [PATCH 3/9] Add curl fallback to docs skill WebFetch may be unavailable in some Claude Code configurations. The skill now falls back to a bash curl command when WebFetch cannot be used. https://github.com/fedify-dev/fedify/pull/756#discussion_r3199250426 Assisted-by: Claude Code:claude-sonnet-4-6 --- claude-plugin/skills/docs/SKILL.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/claude-plugin/skills/docs/SKILL.md b/claude-plugin/skills/docs/SKILL.md index 3108d34de..d7c9a9be1 100644 --- a/claude-plugin/skills/docs/SKILL.md +++ b/claude-plugin/skills/docs/SKILL.md @@ -11,8 +11,16 @@ Fetch and explain Fedify documentation about “$ARGUMENTS”. 1. Use WebFetch on the relevant fedify.dev page (append `.md` to get raw Markdown, e.g. `https://fedify.dev/manual/federation.md`). + If WebFetch is unavailable, fall back to Bash: + + ~~~~ bash + curl -sL https://fedify.dev/manual/federation.md + ~~~~ + The documentation index is at . + 2. Summarise the key points with runnable TypeScript examples. + 3. Mention related pages the user might also want to read. Always strip the `.md` suffix when presenting links to the user. From 3003b143cc5b17fa84292f93dcdd352bd295da60 Mon Sep 17 00:00:00 2001 From: Hong Minhee Date: Thu, 7 May 2026 15:39:52 +0900 Subject: [PATCH 4/9] Use TMPDIR for fep-repo clone path /tmp is not portable on Windows. Use ${TMPDIR:-${TEMP:-/tmp}} so the clone lands in the platform's designated temp directory. https://github.com/fedify-dev/fedify/pull/756#discussion_r3199250432 Assisted-by: Claude Code:claude-sonnet-4-6 --- claude-plugin/skills/fep/SKILL.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/claude-plugin/skills/fep/SKILL.md b/claude-plugin/skills/fep/SKILL.md index 1926f4a33..0c786829e 100644 --- a/claude-plugin/skills/fep/SKILL.md +++ b/claude-plugin/skills/fep/SKILL.md @@ -21,9 +21,10 @@ Call that `$ID` in the steps below. If the result does not match Otherwise clone the proposals repo and read the file: ~~~~ bash - git clone https://codeberg.org/fediverse/fep.git /tmp/fep-repo 2>/dev/null \ - || git -C /tmp/fep-repo pull --ff-only - cat /tmp/fep-repo/fep/$ID/fep-$ID.md + FEP_DIR="${TMPDIR:-${TEMP:-/tmp}}/fep-repo" + git clone https://codeberg.org/fediverse/fep.git "$FEP_DIR" 2>/dev/null \ + || git -C "$FEP_DIR" pull --ff-only + cat "$FEP_DIR/fep/$ID/fep-$ID.md" ~~~~ 2. Summarise: status, what problem it solves, and what extensions it From 94676cfa6c907d4495917887022c986357e383e9 Mon Sep 17 00:00:00 2001 From: Hong Minhee Date: Thu, 7 May 2026 15:40:08 +0900 Subject: [PATCH 5/9] Harden pack-skills.mjs for Windows and interrupted runs Add 'dir' type to symlinkSync so directory symlinks work on Windows (the type argument is ignored on non-Windows platforms). Add .skills-fedify-symlink to .gitignore so the sentinel file does not get committed if a pack run is interrupted before postpack can clean it up. https://github.com/fedify-dev/fedify/pull/756#discussion_r3199250436 https://github.com/fedify-dev/fedify/pull/756#discussion_r3199250442 Assisted-by: Claude Code:claude-sonnet-4-6 --- packages/fedify/.gitignore | 1 + packages/fedify/scripts/pack-skills.mjs | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/fedify/.gitignore b/packages/fedify/.gitignore index 3a9f96ef0..8f1c87241 100644 --- a/packages/fedify/.gitignore +++ b/packages/fedify/.gitignore @@ -1,5 +1,6 @@ .cov/ .cov.lcov +.skills-fedify-symlink .dnt-import-map.json .test-report.xml apidoc/ diff --git a/packages/fedify/scripts/pack-skills.mjs b/packages/fedify/scripts/pack-skills.mjs index 286bf8c7d..6f7638ea2 100644 --- a/packages/fedify/scripts/pack-skills.mjs +++ b/packages/fedify/scripts/pack-skills.mjs @@ -39,7 +39,7 @@ if (cmd === "pre") { } else if (cmd === "post") { if (existsSync(sentinel)) { rmSync(skillsPath, { recursive: true }); - symlinkSync("../../../claude-plugin/skills/fedify", skillsPath); + symlinkSync("../../../claude-plugin/skills/fedify", skillsPath, "dir"); unlinkSync(sentinel); } } From ab17fcb176a7cedb9b2490d7b95f3f5530b2edcd Mon Sep 17 00:00:00 2001 From: Hong Minhee Date: Thu, 7 May 2026 16:20:07 +0900 Subject: [PATCH 6/9] Tighten symlink removal in pack-skills.mjs In the pre branch, replace rmSync({ recursive: true }) with unlinkSync when removing a confirmed symlink. rmSync with recursive is the right call for directories but misleading for a symlink; unlinkSync is the correct and explicit API. In the post branch, add force: true to rmSync so that a missing skillsPath (e.g. after a manual recovery or half-interrupted prepack) does not throw ENOENT and leave the sentinel file stranded. https://github.com/fedify-dev/fedify/pull/756#discussion_r3199402749 https://github.com/fedify-dev/fedify/pull/756#discussion_r3199402767 Assisted-by: Claude Code:claude-sonnet-4-6 --- packages/fedify/scripts/pack-skills.mjs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/fedify/scripts/pack-skills.mjs b/packages/fedify/scripts/pack-skills.mjs index 6f7638ea2..e89fecba3 100644 --- a/packages/fedify/scripts/pack-skills.mjs +++ b/packages/fedify/scripts/pack-skills.mjs @@ -25,7 +25,7 @@ if (cmd === "pre") { const stat = lstatSync(skillsPath); if (stat.isSymbolicLink()) { const target = realpathSync(skillsPath); - rmSync(skillsPath, { recursive: true }); + unlinkSync(skillsPath); cpSync(target, skillsPath, { recursive: true }); writeFileSync(sentinel, ""); } else if (!stat.isDirectory()) { @@ -38,7 +38,7 @@ if (cmd === "pre") { } } else if (cmd === "post") { if (existsSync(sentinel)) { - rmSync(skillsPath, { recursive: true }); + rmSync(skillsPath, { recursive: true, force: true }); symlinkSync("../../../claude-plugin/skills/fedify", skillsPath, "dir"); unlinkSync(sentinel); } From cacb2e5f734a4c910e75bd8a19b601ef4acb7d52 Mon Sep 17 00:00:00 2001 From: Hong Minhee Date: Thu, 7 May 2026 16:49:55 +0900 Subject: [PATCH 7/9] Minor skill file cleanups Shorten the CHANGES.md URL in the migration skill from refs/heads/main to just main, consistent with other raw GitHub URLs in the repository. Rename the FEP clone directory from fep-repo to fedify-fep-repo to reduce the chance of name collisions on shared machines. https://github.com/fedify-dev/fedify/pull/756#discussion_r3199659286 https://github.com/fedify-dev/fedify/pull/756#discussion_r3199659304 Assisted-by: Claude Code:claude-sonnet-4-6 --- claude-plugin/skills/fep/SKILL.md | 2 +- claude-plugin/skills/migration/SKILL.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/claude-plugin/skills/fep/SKILL.md b/claude-plugin/skills/fep/SKILL.md index 0c786829e..4fff41be8 100644 --- a/claude-plugin/skills/fep/SKILL.md +++ b/claude-plugin/skills/fep/SKILL.md @@ -21,7 +21,7 @@ Call that `$ID` in the steps below. If the result does not match Otherwise clone the proposals repo and read the file: ~~~~ bash - FEP_DIR="${TMPDIR:-${TEMP:-/tmp}}/fep-repo" + FEP_DIR="${TMPDIR:-${TEMP:-/tmp}}/fedify-fep-repo" git clone https://codeberg.org/fediverse/fep.git "$FEP_DIR" 2>/dev/null \ || git -C "$FEP_DIR" pull --ff-only cat "$FEP_DIR/fep/$ID/fep-$ID.md" diff --git a/claude-plugin/skills/migration/SKILL.md b/claude-plugin/skills/migration/SKILL.md index 2361fb6e2..1e61c268a 100644 --- a/claude-plugin/skills/migration/SKILL.md +++ b/claude-plugin/skills/migration/SKILL.md @@ -13,7 +13,7 @@ Steps: 1. Fetch *CHANGES.md* from the repo to identify breaking changes between the versions in question: - `https://raw.githubusercontent.com/fedify-dev/fedify/refs/heads/main/CHANGES.md` + `https://raw.githubusercontent.com/fedify-dev/fedify/main/CHANGES.md` 2. List every breaking change that affects the user's code range. 3. For each breaking change, show the old API, the new API, and a concrete before/after code snippet. From dbd054df0c7f314d0fa3a86e8a743521b697de4c Mon Sep 17 00:00:00 2001 From: Hong Minhee Date: Thu, 7 May 2026 18:51:46 +0900 Subject: [PATCH 8/9] Use setext headings in migration skill Replace the plain-label sections "Steps:" and "Key migration hints:" with setext headings to match the repo's Markdown convention of using setext headings for document sections. https://github.com/fedify-dev/fedify/pull/756#discussion_r3199790616 Assisted-by: Claude Code:claude-sonnet-4-6 --- claude-plugin/skills/migration/SKILL.md | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/claude-plugin/skills/migration/SKILL.md b/claude-plugin/skills/migration/SKILL.md index 1e61c268a..09e1082ee 100644 --- a/claude-plugin/skills/migration/SKILL.md +++ b/claude-plugin/skills/migration/SKILL.md @@ -9,7 +9,9 @@ argument-hint: [to-version] Help the user migrate Fedify code from “$ARGUMENTS”. -Steps: + +Migration workflow +------------------ 1. Fetch *CHANGES.md* from the repo to identify breaking changes between the versions in question: @@ -22,7 +24,9 @@ Steps: 5. Note any dependency changes (e.g., vocabulary moved to `@fedify/vocab`, runtime to `@fedify/vocab-runtime`). -Key migration hints: + +Key migration hints +------------------- - `@fedify/fedify/vocab` → `@fedify/vocab` (dedicated package) - `@fedify/fedify/runtime` → `@fedify/vocab-runtime` From 9dbbe8511701d8b2832b094be1cd27c6cf689dc3 Mon Sep 17 00:00:00 2001 From: Hong Minhee Date: Thu, 7 May 2026 19:06:40 +0900 Subject: [PATCH 9/9] Add setext headings to fep skill Replace the flat numbered-step structure with setext sections: Normalization, Retrieval, Summary, and Fedify implementation. Matches the repo's Markdown convention of using setext headings for document sections. Assisted-by: Claude Code:claude-sonnet-4-6 --- claude-plugin/skills/fep/SKILL.md | 44 +++++++++++++++++++++---------- 1 file changed, 30 insertions(+), 14 deletions(-) diff --git a/claude-plugin/skills/fep/SKILL.md b/claude-plugin/skills/fep/SKILL.md index 4fff41be8..88fc4ef48 100644 --- a/claude-plugin/skills/fep/SKILL.md +++ b/claude-plugin/skills/fep/SKILL.md @@ -11,25 +11,41 @@ argument-hint: Look up the Fediverse Enhancement Proposal identified by “$ARGUMENTS” and explain how to implement it with Fedify. + +Normalization +------------- + Normalise the identifier first: strip any leading `FEP-` or `fep-` prefix and lowercase the result to get the bare four-character hex id (e.g. `8fcf`). Call that `$ID` in the steps below. If the result does not match `^[0-9a-f]{4}$`, stop and ask the user to provide a valid FEP identifier. -1. If the `fep` MCP server is available, use `mcp__fep__get_fep` with - id `$ID` to retrieve the proposal. - Otherwise clone the proposals repo and read the file: - ~~~~ bash - FEP_DIR="${TMPDIR:-${TEMP:-/tmp}}/fedify-fep-repo" - git clone https://codeberg.org/fediverse/fep.git "$FEP_DIR" 2>/dev/null \ - || git -C "$FEP_DIR" pull --ff-only - cat "$FEP_DIR/fep/$ID/fep-$ID.md" - ~~~~ +Retrieval +--------- + +If the `fep` MCP server is available, use `mcp__fep__get_fep` with id `$ID` +to retrieve the proposal. Otherwise clone the proposals repo and read the +file: + +~~~~ bash +FEP_DIR="${TMPDIR:-${TEMP:-/tmp}}/fedify-fep-repo" +git clone https://codeberg.org/fediverse/fep.git "$FEP_DIR" 2>/dev/null \ + || git -C "$FEP_DIR" pull --ff-only +cat "$FEP_DIR/fep/$ID/fep-$ID.md" +~~~~ + + +Summary +------- + +Summarise: status, what problem it solves, and what extensions it defines +(new JSON-LD terms, HTTP endpoints, or activity shapes). + -2. Summarise: status, what problem it solves, and what extensions it - defines (new JSON-LD terms, HTTP endpoints, or activity shapes). +Fedify implementation +--------------------- -3. Explain which Fedify APIs are relevant to the implementation: - vocabulary types in `@fedify/vocab`, custom context handling, - dispatcher or inbox listener patterns. +Explain which Fedify APIs are relevant to the implementation: vocabulary +types in `@fedify/vocab`, custom context handling, dispatcher or inbox +listener patterns.