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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion docs/reference/file-formats.md
Original file line number Diff line number Diff line change
Expand Up @@ -324,6 +324,8 @@ The command body itself uses a Claude Code-compatible **universal syntax** (e.g.

> **Devin note:** Devin's extensibility docs no longer document a standalone workflows/commands component — reusable prompts invoked as slash commands are [Skills](https://docs.devin.ai/cli/extensibility/skills/overview) (`/name`). Rulesync therefore emits each command onto the native skills surface as `.devin/skills/<name>/SKILL.md` (project) / `~/.config/devin/skills/<name>/SKILL.md` (global, via `--global`), with `name`/`description` frontmatter derived from the command file. The legacy Windsurf/Cascade-era `.devin/workflows/` and `~/.codeium/windsurf/global_workflows/` locations are no longer emitted (stale outputs there stay gitignored but are not cleaned up automatically). Commands import and `--delete` are no-ops for `devin` because the skills feature owns the `.devin/skills/` tree (importing it as commands would double-import every skill). Note that a command and a skill sharing the same name write the same `SKILL.md` path, so keep command and skill names distinct for this target.

> **Agent Skills standard note (`agentsskills`):** Rulesync accepts the legacy rulesync spellings on input but always **emits** the shapes the [specification](https://agentskills.io/specification) requires: `allowed-tools` becomes a space-separated scalar (a YAML list is joined), `compatibility` becomes a string (an object is flattened to `key: value` pairs), and `metadata` values are stringified so the block stays a string→string map. Generation also checks the normative constraints and warns — without failing the run — when `name` is empty, longer than 64 characters, contains anything but lowercase letters, digits and single hyphens, or does not match its parent directory name; when `description` is empty or longer than 1024 characters; when `compatibility` exceeds 500 characters; or when an `allowed-tools` list entry contains whitespace, which the space-separated form cannot represent. These are warnings rather than errors because a conformant client only _skips_ such a skill, and because import stays lenient as the [client-implementation guide](https://agentskills.io/client-implementation/adding-skills-support) advises. A value that normalizes to the empty string (`compatibility: {}`, `allowed-tools: []`) is dropped rather than written, since the spec requires `compatibility` to be 1–500 characters when present. On **import**, `allowed-tools` is normalized back to the canonical rulesync list, so a generate → import round trip leaves `.rulesync/skills/**` in the shape it started in (the `compatibility` and `metadata` coercions are one-way, because the legacy object/number forms have no conformant equivalent). `hermesagent` reads the same `agentsskills` block and applies the same normalization in both directions, so one rulesync source never produces two different on-disk spellings — except for `metadata`, which stays structured there because Hermes reads `metadata.hermes.*` as YAML. A `hermesagent:` override still wins over the shared block (as for every tool-specific section), so a list or mapping written there is emitted as-is and reported as a spec violation rather than rewritten. Validate the result with the spec's own [`skills-ref validate`](https://github.com/agentskills/agentskills/tree/main/skills-ref).

> **Hermes Agent note:** Commands are global-only and remain distinct from skills. Rulesync writes JSON command specs to `~/.hermes/rulesync/commands/<name>.json`, installs the `rulesync-commands` plugin under `~/.hermes/plugins/`, and enables it in `~/.hermes/config.yaml`. The plugin registers each spec with Hermes's [`ctx.register_command()` plugin API](https://hermes-agent.nousresearch.com/docs/user-guide/features/plugins/) and dispatches the prompt through `delegate_task`; invocation arguments are appended to the prompt. `.rulesync/skills/<name>/SKILL.md` still generates a full [Hermes Agent Skill](https://hermes-agent.nousresearch.com/docs/user-guide/features/skills/) under `~/.hermes/skills/<name>/SKILL.md`, which Hermes also exposes as a dynamic slash command. Rulesync rejects command/skill names that would collide in Hermes's slash-command namespace, and rejects nested command paths that flatten to the same name. Generate commands with `rulesync generate --targets hermesagent --features commands --global`.
>
> Releases before this native plugin transport emitted Hermes commands as `~/.hermes/skills/<name>/SKILL.md`. Rulesync cannot distinguish those files from real user-authored skills safely, so remove an obsolete legacy file manually after confirming that `.rulesync/skills/<name>/SKILL.md` does not own it.
Expand Down Expand Up @@ -678,7 +680,7 @@ When `claudecode.scheduled-task: true` is set, that skill is emitted only as a C

> **Reasonix note:** Reasonix discovers Anthropic-style directory-layout skills (`<name>/SKILL.md`) under `.reasonix/skills/` (project) / `~/.reasonix/skills/` (global, via `--global`). Rulesync emits the portable `name`/`description` frontmatter (Reasonix supports additional optional keys, but only that pair is modeled); the schema is loose, so any extra keys on an imported `SKILL.md` survive the round-trip. See the [Reasonix GUIDE](https://github.com/esengine/DeepSeek-Reasonix/blob/main-v2/docs/GUIDE.md).

> **Hermes Agent note:** Hermes skills are global-only under `~/.hermes/skills/<name>/SKILL.md`. Standard Agent Skills fields (`license`, `compatibility`, and `allowed-tools`) round-trip through `agentsskills`; Hermes-native fields such as `version`, `author`, `platforms`, `environments`, `required_environment_variables`, `required_credential_files`, and `metadata.hermes` round-trip through `hermesagent`. Canonical `name` and `description` always own those two frontmatter keys.
> **Hermes Agent note:** Hermes skills are global-only under `~/.hermes/skills/<name>/SKILL.md`. Standard Agent Skills fields (`license`, `compatibility`, and `allowed-tools`) round-trip through `agentsskills` and are normalized to the Agent Skills spec shapes described above (so `allowed-tools` is written and imported the same way as for `agentsskills`); Hermes-native fields such as `version`, `author`, `platforms`, `environments`, `required_environment_variables`, `required_credential_files`, and `metadata.hermes` round-trip through `hermesagent`. Canonical `name` and `description` always own those two frontmatter keys.

> **Kimi Code note:** Kimi Code discovers skills under `.kimi-code/skills/` (project) and `~/.kimi-code/skills/` (global), plus the shared `.agents/skills/` root at either scope. Rulesync generates the recommended directory layout (`<name>/SKILL.md`) and imports both that layout and flat `<name>.md` skills; for flat files, a missing `name` comes from the filename and a missing `description` falls back to the first non-empty body line (up to 240 characters), matching Kimi. Imported skills are written to `.rulesync/skills/<logical-name>/SKILL.md`, using the normalized logical frontmatter name rather than the source directory or filename. Duplicate precedence follows Kimi's case-insensitive logical frontmatter `name`: the Kimi-specific root takes precedence over `.agents/skills/`, and a directory skill takes precedence over a same-named flat file within one root. Shared roots are import-only and are never removed by Kimi-target orphan deletion. Besides `name`/`description`, Rulesync maps Kimi's `type`, `whenToUse`, `disableModelInvocation`, and `arguments` frontmatter through the `kimi-code:` block and preserves supporting files beside directory-layout `SKILL.md`. The shared top-level `disable-model-invocation` value supplies the Kimi flag unless the tool-specific block overrides it. See the [Kimi Code Agent Skills docs](https://moonshotai.github.io/kimi-code/en/customization/skills.html).

Expand Down
4 changes: 3 additions & 1 deletion skills/rulesync/file-formats.md
Original file line number Diff line number Diff line change
Expand Up @@ -324,6 +324,8 @@ The command body itself uses a Claude Code-compatible **universal syntax** (e.g.

> **Devin note:** Devin's extensibility docs no longer document a standalone workflows/commands component — reusable prompts invoked as slash commands are [Skills](https://docs.devin.ai/cli/extensibility/skills/overview) (`/name`). Rulesync therefore emits each command onto the native skills surface as `.devin/skills/<name>/SKILL.md` (project) / `~/.config/devin/skills/<name>/SKILL.md` (global, via `--global`), with `name`/`description` frontmatter derived from the command file. The legacy Windsurf/Cascade-era `.devin/workflows/` and `~/.codeium/windsurf/global_workflows/` locations are no longer emitted (stale outputs there stay gitignored but are not cleaned up automatically). Commands import and `--delete` are no-ops for `devin` because the skills feature owns the `.devin/skills/` tree (importing it as commands would double-import every skill). Note that a command and a skill sharing the same name write the same `SKILL.md` path, so keep command and skill names distinct for this target.

> **Agent Skills standard note (`agentsskills`):** Rulesync accepts the legacy rulesync spellings on input but always **emits** the shapes the [specification](https://agentskills.io/specification) requires: `allowed-tools` becomes a space-separated scalar (a YAML list is joined), `compatibility` becomes a string (an object is flattened to `key: value` pairs), and `metadata` values are stringified so the block stays a string→string map. Generation also checks the normative constraints and warns — without failing the run — when `name` is empty, longer than 64 characters, contains anything but lowercase letters, digits and single hyphens, or does not match its parent directory name; when `description` is empty or longer than 1024 characters; when `compatibility` exceeds 500 characters; or when an `allowed-tools` list entry contains whitespace, which the space-separated form cannot represent. These are warnings rather than errors because a conformant client only _skips_ such a skill, and because import stays lenient as the [client-implementation guide](https://agentskills.io/client-implementation/adding-skills-support) advises. A value that normalizes to the empty string (`compatibility: {}`, `allowed-tools: []`) is dropped rather than written, since the spec requires `compatibility` to be 1–500 characters when present. On **import**, `allowed-tools` is normalized back to the canonical rulesync list, so a generate → import round trip leaves `.rulesync/skills/**` in the shape it started in (the `compatibility` and `metadata` coercions are one-way, because the legacy object/number forms have no conformant equivalent). `hermesagent` reads the same `agentsskills` block and applies the same normalization in both directions, so one rulesync source never produces two different on-disk spellings — except for `metadata`, which stays structured there because Hermes reads `metadata.hermes.*` as YAML. A `hermesagent:` override still wins over the shared block (as for every tool-specific section), so a list or mapping written there is emitted as-is and reported as a spec violation rather than rewritten. Validate the result with the spec's own [`skills-ref validate`](https://github.com/agentskills/agentskills/tree/main/skills-ref).

> **Hermes Agent note:** Commands are global-only and remain distinct from skills. Rulesync writes JSON command specs to `~/.hermes/rulesync/commands/<name>.json`, installs the `rulesync-commands` plugin under `~/.hermes/plugins/`, and enables it in `~/.hermes/config.yaml`. The plugin registers each spec with Hermes's [`ctx.register_command()` plugin API](https://hermes-agent.nousresearch.com/docs/user-guide/features/plugins/) and dispatches the prompt through `delegate_task`; invocation arguments are appended to the prompt. `.rulesync/skills/<name>/SKILL.md` still generates a full [Hermes Agent Skill](https://hermes-agent.nousresearch.com/docs/user-guide/features/skills/) under `~/.hermes/skills/<name>/SKILL.md`, which Hermes also exposes as a dynamic slash command. Rulesync rejects command/skill names that would collide in Hermes's slash-command namespace, and rejects nested command paths that flatten to the same name. Generate commands with `rulesync generate --targets hermesagent --features commands --global`.
>
> Releases before this native plugin transport emitted Hermes commands as `~/.hermes/skills/<name>/SKILL.md`. Rulesync cannot distinguish those files from real user-authored skills safely, so remove an obsolete legacy file manually after confirming that `.rulesync/skills/<name>/SKILL.md` does not own it.
Expand Down Expand Up @@ -678,7 +680,7 @@ When `claudecode.scheduled-task: true` is set, that skill is emitted only as a C

> **Reasonix note:** Reasonix discovers Anthropic-style directory-layout skills (`<name>/SKILL.md`) under `.reasonix/skills/` (project) / `~/.reasonix/skills/` (global, via `--global`). Rulesync emits the portable `name`/`description` frontmatter (Reasonix supports additional optional keys, but only that pair is modeled); the schema is loose, so any extra keys on an imported `SKILL.md` survive the round-trip. See the [Reasonix GUIDE](https://github.com/esengine/DeepSeek-Reasonix/blob/main-v2/docs/GUIDE.md).

> **Hermes Agent note:** Hermes skills are global-only under `~/.hermes/skills/<name>/SKILL.md`. Standard Agent Skills fields (`license`, `compatibility`, and `allowed-tools`) round-trip through `agentsskills`; Hermes-native fields such as `version`, `author`, `platforms`, `environments`, `required_environment_variables`, `required_credential_files`, and `metadata.hermes` round-trip through `hermesagent`. Canonical `name` and `description` always own those two frontmatter keys.
> **Hermes Agent note:** Hermes skills are global-only under `~/.hermes/skills/<name>/SKILL.md`. Standard Agent Skills fields (`license`, `compatibility`, and `allowed-tools`) round-trip through `agentsskills` and are normalized to the Agent Skills spec shapes described above (so `allowed-tools` is written and imported the same way as for `agentsskills`); Hermes-native fields such as `version`, `author`, `platforms`, `environments`, `required_environment_variables`, `required_credential_files`, and `metadata.hermes` round-trip through `hermesagent`. Canonical `name` and `description` always own those two frontmatter keys.

> **Kimi Code note:** Kimi Code discovers skills under `.kimi-code/skills/` (project) and `~/.kimi-code/skills/` (global), plus the shared `.agents/skills/` root at either scope. Rulesync generates the recommended directory layout (`<name>/SKILL.md`) and imports both that layout and flat `<name>.md` skills; for flat files, a missing `name` comes from the filename and a missing `description` falls back to the first non-empty body line (up to 240 characters), matching Kimi. Imported skills are written to `.rulesync/skills/<logical-name>/SKILL.md`, using the normalized logical frontmatter name rather than the source directory or filename. Duplicate precedence follows Kimi's case-insensitive logical frontmatter `name`: the Kimi-specific root takes precedence over `.agents/skills/`, and a directory skill takes precedence over a same-named flat file within one root. Shared roots are import-only and are never removed by Kimi-target orphan deletion. Besides `name`/`description`, Rulesync maps Kimi's `type`, `whenToUse`, `disableModelInvocation`, and `arguments` frontmatter through the `kimi-code:` block and preserves supporting files beside directory-layout `SKILL.md`. The shared top-level `disable-model-invocation` value supplies the Kimi flag unless the tool-specific block overrides it. See the [Kimi Code Agent Skills docs](https://moonshotai.github.io/kimi-code/en/customization/skills.html).

Expand Down
35 changes: 35 additions & 0 deletions src/e2e/e2e-skills.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,41 @@ This is the test skill body content.
},
);

// The Agent Skills spec types `allowed-tools` as a space-separated string,
// `compatibility` as a string and `metadata` as a string→string map, so the
// legacy rulesync list/object/number spellings must not reach the file.
// https://agentskills.io/specification
it("should write spec-conformant scalar frontmatter for agentsskills", async () => {
const testDir = getTestDir();

await writeFileContent(
join(testDir, RULESYNC_SKILLS_RELATIVE_DIR_PATH, "test-skill", "SKILL.md"),
`---
name: test-skill
description: "A test skill for E2E testing"
targets: ["*"]
agentsskills:
allowed-tools: ["Read", "Bash(git:*)"]
compatibility:
runtime: node
metadata:
version: 1
---
This is the test skill body content.
`,
);

await runGenerate({ target: "agentsskills", features: "skills" });

const generatedContent = await readFileContent(
join(testDir, ".agents", "skills", "test-skill", "SKILL.md"),
);
expect(generatedContent).toContain("allowed-tools: Read Bash(git:*)");
expect(generatedContent).toContain("compatibility: 'runtime: node'");
expect(generatedContent).toContain("version: '1'");
expect(generatedContent).not.toContain("- Read");
});

it.each([
{
target: "agentsmd",
Expand Down
Loading
Loading