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
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -235,6 +235,7 @@ rulesync.local.jsonc
**/.takt/config.yaml
**/.augment-guidelines
**/.agents/skills/
**/.rovodev/prompts.yml
**/.codeium/windsurf/skills/
**/.copilot/agents/
**/.copilot/mcp-config.json
Expand Down Expand Up @@ -290,6 +291,7 @@ rulesync.local.jsonc
**/.qwen/commands/
**/.reasonix/commands/
**/.roo/commands/
**/.rovodev/prompts/
**/.takt/facets/instructions/
**/.devin/workflows/
**/.augment/skills/
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ The tables below show whether each tool supports a given feature (✅ = supporte
| Cline | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | | ✅ |
| Kilo Code | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
| Roo Code | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | | |
| Rovodev (Atlassian) | ✅ | | ✅ | | ✅ | ✅ | | ✅ |
| Rovodev (Atlassian) | ✅ | | ✅ | | ✅ | ✅ | | ✅ |
| Takt | ✅ | | ✅ | ✅ | ✅ | ✅ | | ✅ |
| Vibe Code | ✅ | ✅ | ✅ | | ✅ | ✅ | ✅ | ✅ |
| Qwen Code | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
Expand Down
2 changes: 2 additions & 0 deletions docs/reference/file-formats.md
Original file line number Diff line number Diff line change
Expand Up @@ -287,6 +287,8 @@ The command body itself uses a Claude Code-compatible **universal syntax** (e.g.

> **Reasonix note:** Custom slash commands are Markdown files under `.reasonix/commands/` (project) / `~/.reasonix/commands/` (global, via `--global`) — directly analogous to Claude Code's `.claude/commands/`, since Reasonix explicitly mirrors Claude Code's conventions. Frontmatter supports `description` and `argument-hint`, and the body uses the same `$ARGUMENTS` / `$1`…`$N` placeholder syntax. Subdirectory namespacing is supported (`git/commit.md` → `/git:commit`). Any extra fields are preserved on round-trip under the `reasonix:` block. See the [Reasonix GUIDE](https://github.com/esengine/DeepSeek-Reasonix/blob/main-v2/docs/GUIDE.md#slash-commands).

> **Rovo Dev CLI note:** Rovo Dev's "saved prompts" are a file-based custom-command surface made of a `prompts.yml` manifest plus per-prompt Markdown content files, invoked via `/prompts [title] [extra]`. Rulesync writes the content (no frontmatter) to `.rovodev/prompts/<name>.md` (project) / `~/.rovodev/prompts/<name>.md` (global, via `--global`), and rebuilds the sibling `.rovodev/prompts.yml` / `~/.rovodev/prompts.yml` manifest with one `{ name, description, content_file }` entry per prompt, `content_file` pointing at `prompts/<name>.md` (resolved relative to `prompts.yml`, matching Rovo Dev's own resolution order). The `prompts` array is fully replaced from the current rulesync commands on each generate (mirrors the Rovodev MCP adapter fully replacing `mcpServers`); any other top-level key in an existing manifest is preserved, and the manifest is never deleted. See the [saved prompts](https://support.atlassian.com/rovo/docs/save-and-reuse-a-prompt-in-rovo-dev-cli/) and [CLI commands](https://support.atlassian.com/rovo/docs/rovo-dev-cli-commands/) docs.

## `rulesync/subagents/*.md`

Example:
Expand Down
2 changes: 1 addition & 1 deletion docs/reference/supported-tools.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ Rulesync supports both **generation** and **import** for All of the major AI cod
| Cline | cline | ✅ 🌏 | ✅ | 🌏 | ✅ 🌏 | ✅ 🌏 | ✅ 🌏 | | ✅ |
| Kilo Code | kilo | ✅ 🌏 | ✅ | ✅ 🌏 | ✅ 🌏 | ✅ 🌏 | ✅ 🌏 | ✅ 🌏 | ✅ 🌏 |
| Roo Code | roo | ✅ 🌏 | ✅ | ✅ | ✅ | ✅ | ✅ 🌏 | | |
| Rovodev (Atlassian) | rovodev | ✅ 🌏 | | 🌏 | | ✅ 🌏 | ✅ 🌏 | | 🌏 |
| Rovodev (Atlassian) | rovodev | ✅ 🌏 | | 🌏 | ✅ 🌏 | ✅ 🌏 | ✅ 🌏 | | 🌏 |
| Takt | takt | ✅ 🌏 | | ✅ 🌏 | ✅ 🌏 | ✅ 🌏 | ✅ 🌏 | | ✅ 🌏 |
| Vibe Code | vibe | ✅ 🌏 | ✅ | ✅ 🌏 | | ✅ 🌏 | ✅ 🌏 | ✅ 🌏 | ✅ 🌏 |
| Qwen Code | qwencode | ✅ 🌏 | ✅ | ✅ 🌏 🔧 | ✅ 🌏 | ✅ 🌏 | ✅ 🌏 | ✅ 🌏 | ✅ 🌏 |
Expand Down
2 changes: 2 additions & 0 deletions skills/rulesync/file-formats.md
Original file line number Diff line number Diff line change
Expand Up @@ -287,6 +287,8 @@ The command body itself uses a Claude Code-compatible **universal syntax** (e.g.

> **Reasonix note:** Custom slash commands are Markdown files under `.reasonix/commands/` (project) / `~/.reasonix/commands/` (global, via `--global`) — directly analogous to Claude Code's `.claude/commands/`, since Reasonix explicitly mirrors Claude Code's conventions. Frontmatter supports `description` and `argument-hint`, and the body uses the same `$ARGUMENTS` / `$1`…`$N` placeholder syntax. Subdirectory namespacing is supported (`git/commit.md` → `/git:commit`). Any extra fields are preserved on round-trip under the `reasonix:` block. See the [Reasonix GUIDE](https://github.com/esengine/DeepSeek-Reasonix/blob/main-v2/docs/GUIDE.md#slash-commands).

> **Rovo Dev CLI note:** Rovo Dev's "saved prompts" are a file-based custom-command surface made of a `prompts.yml` manifest plus per-prompt Markdown content files, invoked via `/prompts [title] [extra]`. Rulesync writes the content (no frontmatter) to `.rovodev/prompts/<name>.md` (project) / `~/.rovodev/prompts/<name>.md` (global, via `--global`), and rebuilds the sibling `.rovodev/prompts.yml` / `~/.rovodev/prompts.yml` manifest with one `{ name, description, content_file }` entry per prompt, `content_file` pointing at `prompts/<name>.md` (resolved relative to `prompts.yml`, matching Rovo Dev's own resolution order). The `prompts` array is fully replaced from the current rulesync commands on each generate (mirrors the Rovodev MCP adapter fully replacing `mcpServers`); any other top-level key in an existing manifest is preserved, and the manifest is never deleted. See the [saved prompts](https://support.atlassian.com/rovo/docs/save-and-reuse-a-prompt-in-rovo-dev-cli/) and [CLI commands](https://support.atlassian.com/rovo/docs/rovo-dev-cli-commands/) docs.

## `rulesync/subagents/*.md`

Example:
Expand Down
2 changes: 1 addition & 1 deletion skills/rulesync/supported-tools.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ Rulesync supports both **generation** and **import** for All of the major AI cod
| Cline | cline | ✅ 🌏 | ✅ | 🌏 | ✅ 🌏 | ✅ 🌏 | ✅ 🌏 | | ✅ |
| Kilo Code | kilo | ✅ 🌏 | ✅ | ✅ 🌏 | ✅ 🌏 | ✅ 🌏 | ✅ 🌏 | ✅ 🌏 | ✅ 🌏 |
| Roo Code | roo | ✅ 🌏 | ✅ | ✅ | ✅ | ✅ | ✅ 🌏 | | |
| Rovodev (Atlassian) | rovodev | ✅ 🌏 | | 🌏 | | ✅ 🌏 | ✅ 🌏 | | 🌏 |
| Rovodev (Atlassian) | rovodev | ✅ 🌏 | | 🌏 | ✅ 🌏 | ✅ 🌏 | ✅ 🌏 | | 🌏 |
| Takt | takt | ✅ 🌏 | | ✅ 🌏 | ✅ 🌏 | ✅ 🌏 | ✅ 🌏 | | ✅ 🌏 |
| Vibe Code | vibe | ✅ 🌏 | ✅ | ✅ 🌏 | | ✅ 🌏 | ✅ 🌏 | ✅ 🌏 | ✅ 🌏 |
| Qwen Code | qwencode | ✅ 🌏 | ✅ | ✅ 🌏 🔧 | ✅ 🌏 | ✅ 🌏 | ✅ 🌏 | ✅ 🌏 | ✅ 🌏 |
Expand Down
1 change: 1 addition & 0 deletions src/cli/commands/gitignore-entries.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,7 @@ describe("registry derivation", () => {
"codexcli::ignore::**/.codexignore",
// Shared trees and global-scope outputs (emitted under the home dir).
"rovodev::skills::**/.agents/skills/",
"rovodev::commands::**/.rovodev/prompts.yml",
"devin::skills::**/.codeium/windsurf/skills/",
"copilotcli::subagents::**/.copilot/agents/",
"copilotcli::mcp::**/.copilot/mcp-config.json",
Expand Down
4 changes: 4 additions & 0 deletions src/cli/commands/gitignore-entries.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,10 @@ export const HAND_MAINTAINED_GITIGNORE_ENTRIES: ReadonlyArray<GitignoreEntryTag>

// Shared trees and global-scope outputs not produced via project getSettablePaths.
{ target: "rovodev", feature: "skills", entry: "**/.agents/skills/" },
// The `prompts.yml` manifest is produced via `RovodevCommand.getAuxiliaryFiles`,
// not `getSettablePaths` (only the sibling `.rovodev/prompts/` content-file
// directory is derived automatically), so it needs a hand-maintained entry.
{ target: "rovodev", feature: "commands", entry: "**/.rovodev/prompts.yml" },
{ target: "devin", feature: "skills", entry: "**/.codeium/windsurf/skills/" },
{ target: "copilotcli", feature: "subagents", entry: "**/.copilot/agents/" },
{ target: "copilotcli", feature: "mcp", entry: "**/.copilot/mcp-config.json" },
Expand Down
2 changes: 2 additions & 0 deletions src/constants/rovodev-paths.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,5 @@ export const ROVODEV_LEGACY_RULE_FILE_NAME = "AGENTS.local.md";
export const ROVODEV_MCP_FILE_NAME = "mcp.json";
export const ROVODEV_CONFIG_FILE_NAME = "config.yml";
export const ROVODEV_AGENTS_SKILLS_DIR_PATH = join(".agents", "skills");
export const ROVODEV_PROMPTS_FILE_NAME = "prompts.yml";
export const ROVODEV_PROMPTS_DIR_PATH = join(ROVODEV_DIR, "prompts");
31 changes: 31 additions & 0 deletions src/e2e/e2e-commands.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ describe("E2E: commands", () => {
{ target: "goose", outputPath: join(".goose", "recipes", "review-pr.yaml") },
{ target: "qwencode", outputPath: join(".qwen", "commands", "review-pr.md") },
{ target: "reasonix", outputPath: join(".reasonix", "commands", "review-pr.md") },
{ target: "rovodev", outputPath: join(".rovodev", "prompts", "review-pr.md") },
])("should generate $target commands", async ({ target, outputPath }) => {
const testDir = getTestDir();

Expand All @@ -57,6 +58,33 @@ Check the PR diff and provide feedback.
expect(generatedContent).toContain("Check the PR diff and provide feedback.");
});

it("should generate a rovodev prompts.yml manifest alongside the content file", async () => {
const testDir = getTestDir();

const commandContent = `---
description: "Review a pull request"
targets: ["*"]
---
Check the PR diff and provide feedback.
`;
await writeFileContent(
join(testDir, RULESYNC_COMMANDS_RELATIVE_DIR_PATH, "review-pr.md"),
commandContent,
);

await runGenerate({ target: "rovodev", features: "commands" });

// The content file holds the raw prompt body (no frontmatter).
const contentFile = await readFileContent(join(testDir, ".rovodev", "prompts", "review-pr.md"));
expect(contentFile.trim()).toBe("Check the PR diff and provide feedback.");

// The manifest indexes the prompt by name/description/content_file.
const manifest = await readFileContent(join(testDir, ".rovodev", "prompts.yml"));
expect(manifest).toContain("name: review-pr");
expect(manifest).toContain("description: Review a pull request");
expect(manifest).toContain("content_file: prompts/review-pr.md");
});

it.each([{ target: "agentsmd", outputPath: join(".agents", "commands", "review-pr.md") }])(
"should generate $target simulated commands",
async ({ target, outputPath }) => {
Expand Down Expand Up @@ -97,6 +125,7 @@ Check the PR diff and provide feedback.
{ target: "devin", orphanPath: join(".devin", "workflows", "orphan.md") },
{ target: "factorydroid", orphanPath: join(".factory", "commands", "orphan.md") },
{ target: "goose", orphanPath: join(".goose", "recipes", "orphan.yaml") },
{ target: "rovodev", orphanPath: join(".rovodev", "prompts", "orphan.md") },
])(
"should fail in check mode when delete would remove an orphan $target command file",
async ({ target, orphanPath }) => {
Expand Down Expand Up @@ -145,6 +174,7 @@ describe("E2E: commands (import)", () => {
{ target: "devin", sourcePath: join(".devin", "workflows", "review-pr.md") },
{ target: "factorydroid", sourcePath: join(".factory", "commands", "review-pr.md") },
{ target: "reasonix", sourcePath: join(".reasonix", "commands", "review-pr.md") },
{ target: "rovodev", sourcePath: join(".rovodev", "prompts", "review-pr.md") },
])("should import $target commands", async ({ target, sourcePath }) => {
const testDir = getTestDir();

Expand Down Expand Up @@ -216,6 +246,7 @@ describe("E2E: commands (global mode)", () => {
// as Hermes skills under ~/.hermes/skills/<slug>/SKILL.md (global only).
{ target: "hermesagent", outputPath: join(".hermes", "skills", "review-pr", "SKILL.md") },
{ target: "reasonix", outputPath: join(".reasonix", "commands", "review-pr.md") },
{ target: "rovodev", outputPath: join(".rovodev", "prompts", "review-pr.md") },
])("should generate $target commands in home directory", async ({ target, outputPath }) => {
const projectDir = getProjectDir();
const homeDir = getHomeDir();
Expand Down
3 changes: 3 additions & 0 deletions src/features/commands/commands-processor.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1082,6 +1082,7 @@ describe("CommandsProcessor", () => {
"qwencode",
"reasonix",
"roo",
"rovodev",
"takt",
"devin",
]),
Expand Down Expand Up @@ -1113,6 +1114,7 @@ describe("CommandsProcessor", () => {
"qwencode",
"reasonix",
"roo",
"rovodev",
"takt",
"devin",
]),
Expand Down Expand Up @@ -1143,6 +1145,7 @@ describe("CommandsProcessor", () => {
"pi",
"qwencode",
"reasonix",
"rovodev",
"takt",
"devin",
]),
Expand Down
44 changes: 43 additions & 1 deletion src/features/commands/commands-processor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ import { PiCommand } from "./pi-command.js";
import { QwencodeCommand } from "./qwencode-command.js";
import { ReasonixCommand } from "./reasonix-command.js";
import { RooCommand } from "./roo-command.js";
import { RovodevCommand } from "./rovodev-command.js";
import { RulesyncCommand } from "./rulesync-command.js";
import { TaktCommand } from "./takt-command.js";
import {
Expand Down Expand Up @@ -65,6 +66,16 @@ type ToolCommandFactory = {
outputRoot: string;
global: boolean;
}): Promise<ToolCommand[]>;
/**
* Optional hook for tools that need a shared/aggregate file alongside the
* per-command files (e.g. Rovo Dev's `prompts.yml` manifest). See
* {@link ToolCommand.getAuxiliaryFiles}.
*/
getAuxiliaryFiles?(params: {
toolCommands: ToolCommand[];
outputRoot?: string;
global?: boolean;
}): Promise<ToolFile[]> | ToolFile[];
};
meta: {
/** File extension for the command file */
Expand Down Expand Up @@ -413,6 +424,26 @@ export const toolCommandFactories = new Map<CommandsProcessorToolTarget, ToolCom
},
},
],
[
// Rovo Dev CLI "saved prompts": a `prompts.yml` manifest (one entry per
// prompt: `{ name, description, content_file }`) plus per-prompt Markdown
// content files. Discovered in repo-root `.rovodev/`, cwd `.rovodev/`, and
// global `~/.rovodev/`. Content files live under `.rovodev/prompts/`
// (project) / `~/.rovodev/prompts/` (global); the manifest is regenerated
// via `RovodevCommand.getAuxiliaryFiles`.
// https://support.atlassian.com/rovo/docs/save-and-reuse-a-prompt-in-rovo-dev-cli/
"rovodev",
{
class: RovodevCommand,
meta: {
extension: "md",
supportsProject: true,
supportsGlobal: true,
isSimulated: false,
supportsSubdirectory: false,
},
},
],
[
"takt",
{
Expand Down Expand Up @@ -546,7 +577,18 @@ export class CommandsProcessor extends FeatureProcessor {
})
.filter((command): command is ToolCommand => command !== null);

return toolCommands;
const auxiliaryFiles = await factory.class.getAuxiliaryFiles?.({
toolCommands,
outputRoot: this.outputRoot,
global: this.global,
});

const result: ToolFile[] = [...toolCommands];
if (auxiliaryFiles && auxiliaryFiles.length > 0) {
result.push(...auxiliaryFiles);
}

return result;
}

async convertToolFilesToRulesyncFiles(toolFiles: ToolFile[]): Promise<RulesyncFile[]> {
Expand Down
Loading
Loading