From c316c546cb4d7cda2c2e5bc2836821d57f617675 Mon Sep 17 00:00:00 2001 From: Hong Minhee Date: Thu, 7 May 2026 13:36:44 +0900 Subject: [PATCH 01/12] 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 02/12] 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 03/12] 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 04/12] 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 05/12] 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 06/12] 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 07/12] 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 08/12] 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 09/12] 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. From 506d3afada18ac85a7f8f3876a442984654ba3e7 Mon Sep 17 00:00:00 2001 From: Hong Minhee Date: Fri, 8 May 2026 20:36:10 +0900 Subject: [PATCH 10/12] Clarify reviewer agent inbox and import checks Inbox listeners: reword "all expected activity types" to make clear this means only the types the application is designed to handle, not every ActivityPub type. Vocabulary and imports: replace the single-shim check with a broader prompt to scan for all deprecated import paths and refer to the /fedify:migration skill for the full list and remediation steps. https://github.com/fedify-dev/fedify/pull/756#discussion_r3208175982 https://github.com/fedify-dev/fedify/pull/756#discussion_r3208187188 Assisted-by: Claude Code:claude-sonnet-4-6 --- claude-plugin/agents/fedify-reviewer.md | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/claude-plugin/agents/fedify-reviewer.md b/claude-plugin/agents/fedify-reviewer.md index ed423c6b0..b37e0c48f 100644 --- a/claude-plugin/agents/fedify-reviewer.md +++ b/claude-plugin/agents/fedify-reviewer.md @@ -37,7 +37,9 @@ When reviewing Fedify code, check each of the following in order: *Inbox listeners* - - Are all expected activity types registered with `.on()`? + - Are the activity types this application is designed to handle + registered with `.on()`? (Only types the app actually needs — not + necessarily every ActivityPub activity type.) - 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? @@ -50,8 +52,9 @@ When reviewing Fedify code, check each of the following in order: *Vocabulary and imports* - - Are types imported from `@fedify/vocab`, not from - `@fedify/fedify/vocab` (deprecated shim)? + - Are there any deprecated import paths in use (`@fedify/fedify/vocab`, + `@fedify/fedify/runtime`, `src/webfinger`, `src/x/`, etc.)? If so, + invoke the `/fedify:migration` skill for the full list of replacements. - Are `fromJsonLd()` / `toJsonLd()` calls awaited? *Activity IDs* From 60573a0ec494282e7c15550134e5d8d3737b3712 Mon Sep 17 00:00:00 2001 From: Hong Minhee Date: Fri, 8 May 2026 22:13:59 +0900 Subject: [PATCH 11/12] Recover from interrupted pack in pack-skills.mjs pre step If a previous npm pack run was interrupted after prepack but before postpack, skills/fedify remains a real directory and the sentinel file still exists. On the next pre run the script previously skipped conversion entirely, which could leave stale files in the tarball if the canonical source in claude-plugin had changed. Fix: at the start of pre, check for the sentinel and restore the symlink if it is present, then proceed with the normal conversion so the copy is always fresh. https://github.com/fedify-dev/fedify/pull/756#discussion_r3208401496 Assisted-by: Claude Code:claude-sonnet-4-6 --- packages/fedify/scripts/pack-skills.mjs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/packages/fedify/scripts/pack-skills.mjs b/packages/fedify/scripts/pack-skills.mjs index e89fecba3..9e1ab4991 100644 --- a/packages/fedify/scripts/pack-skills.mjs +++ b/packages/fedify/scripts/pack-skills.mjs @@ -22,6 +22,16 @@ const sentinel = join(root, "..", ".skills-fedify-symlink"); const [cmd] = process.argv.slice(2); if (cmd === "pre") { + // If a previous pack run was interrupted after prepack but before postpack, + // skillsPath is a real directory and the sentinel still exists. Restore + // the symlink first so the conversion below can run cleanly and pick up + // any source changes that happened in the interim. + if (existsSync(sentinel)) { + rmSync(skillsPath, { recursive: true, force: true }); + symlinkSync("../../../claude-plugin/skills/fedify", skillsPath, "dir"); + unlinkSync(sentinel); + } + const stat = lstatSync(skillsPath); if (stat.isSymbolicLink()) { const target = realpathSync(skillsPath); From 11d9dad323b8c223ba2c20795d5477c93a73e5a2 Mon Sep 17 00:00:00 2001 From: Hong Minhee Date: Fri, 8 May 2026 22:49:04 +0900 Subject: [PATCH 12/12] Reduce window for broken workspace in pack-skills.mjs If the script is interrupted between unlinkSync and cpSync, the skills/fedify path is missing until manual intervention. Fix by copying to a .tmp sibling first, then removing the symlink, then renaming the temp directory into place. This reduces the broken window to the instant of renameSync, which is close to atomic on the same filesystem. Also cleans up any stale .tmp directory left by a previous interrupted run before starting a fresh copy. https://github.com/fedify-dev/fedify/pull/756#discussion_r3208902906 Assisted-by: Claude Code:claude-sonnet-4-6 --- packages/fedify/scripts/pack-skills.mjs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/fedify/scripts/pack-skills.mjs b/packages/fedify/scripts/pack-skills.mjs index 9e1ab4991..a18264554 100644 --- a/packages/fedify/scripts/pack-skills.mjs +++ b/packages/fedify/scripts/pack-skills.mjs @@ -6,6 +6,7 @@ import { existsSync, lstatSync, realpathSync, + renameSync, rmSync, symlinkSync, unlinkSync, @@ -35,8 +36,11 @@ if (cmd === "pre") { const stat = lstatSync(skillsPath); if (stat.isSymbolicLink()) { const target = realpathSync(skillsPath); + const tempPath = `${skillsPath}.tmp`; + rmSync(tempPath, { recursive: true, force: true }); + cpSync(target, tempPath, { recursive: true }); unlinkSync(skillsPath); - cpSync(target, skillsPath, { recursive: true }); + renameSync(tempPath, skillsPath); writeFileSync(sentinel, ""); } else if (!stat.isDirectory()) { throw new Error(