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: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -275,7 +275,7 @@ rulesync.local.jsonc
**/.factory/droids/
**/.factory/skills/
**/.factory/mcp.json
**/.factory/settings.json
**/.factory/hooks.json
**/.gemini/commands/
**/.gemini/agents/
**/.gemini/skills/
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 @@ -15,7 +15,7 @@ Rulesync supports both **generation** and **import** for All of the major AI cod
| Goose | goose | ✅ 🌏 | ✅ | | | | | ✅ 🌏 | |
| Cursor | cursor | ✅ | ✅ | ✅ 🌏 | ✅ 🌏 | ✅ 🌏 | ✅ 🌏 | ✅ 🌏 | ✅ 🌏 |
| deepagents-cli | deepagents | ✅ | | ✅ 🌏 | | ✅ | ✅ | 🌏 | |
| Factory Droid | factorydroid | ✅ 🌏 | | ✅ 🌏 | 🎮 | 🎮 | 🎮 | ✅ 🌏 | |
| Factory Droid | factorydroid | ✅ 🌏 | | ✅ 🌏 | ✅ 🌏 | ✅ 🌏 | ✅ 🌏 | ✅ 🌏 | |
| OpenCode | opencode | ✅ 🌏 | | ✅ 🌏 🔧 | ✅ 🌏 | ✅ 🌏 | ✅ 🌏 | ✅ 🌏 | ✅ 🌏 |
| Cline | cline | ✅ 🌏 | ✅ | ✅ | ✅ 🌏 | | ✅ 🌏 | | ✅ |
| Kilo Code | kilo | ✅ 🌏 | ✅ | ✅ 🌏 | ✅ 🌏 | ✅ 🌏 | ✅ 🌏 | ✅ 🌏 | ✅ 🌏 |
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 @@ -15,7 +15,7 @@ Rulesync supports both **generation** and **import** for All of the major AI cod
| Goose | goose | ✅ 🌏 | ✅ | | | | | ✅ 🌏 | |
| Cursor | cursor | ✅ | ✅ | ✅ 🌏 | ✅ 🌏 | ✅ 🌏 | ✅ 🌏 | ✅ 🌏 | ✅ 🌏 |
| deepagents-cli | deepagents | ✅ | | ✅ 🌏 | | ✅ | ✅ | 🌏 | |
| Factory Droid | factorydroid | ✅ 🌏 | | ✅ 🌏 | 🎮 | 🎮 | 🎮 | ✅ 🌏 | |
| Factory Droid | factorydroid | ✅ 🌏 | | ✅ 🌏 | ✅ 🌏 | ✅ 🌏 | ✅ 🌏 | ✅ 🌏 | |
| OpenCode | opencode | ✅ 🌏 | | ✅ 🌏 🔧 | ✅ 🌏 | ✅ 🌏 | ✅ 🌏 | ✅ 🌏 | ✅ 🌏 |
| Cline | cline | ✅ 🌏 | ✅ | ✅ | ✅ 🌏 | | ✅ 🌏 | | ✅ |
| Kilo Code | kilo | ✅ 🌏 | ✅ | ✅ 🌏 | ✅ 🌏 | ✅ 🌏 | ✅ 🌏 | ✅ 🌏 | ✅ 🌏 |
Expand Down
4 changes: 2 additions & 2 deletions src/cli/commands/gitignore-entries.ts
Original file line number Diff line number Diff line change
Expand Up @@ -163,8 +163,8 @@ export const GITIGNORE_ENTRY_REGISTRY: ReadonlyArray<GitignoreEntryTag> = [
{ target: "factorydroid", feature: "mcp", entry: "**/.factory/mcp.json" },
{
target: "factorydroid",
feature: "general",
entry: "**/.factory/settings.json",
feature: "hooks",
entry: "**/.factory/hooks.json",
},

// Gemini CLI
Expand Down
32 changes: 18 additions & 14 deletions src/e2e/e2e-commands.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ describe("E2E: commands", () => {
{ target: "takt", outputPath: join(".takt", "facets", "instructions", "review-pr.md") },
{ target: "pi", outputPath: join(".pi", "prompts", "review-pr.md") },
{ target: "devin", outputPath: join(".devin", "workflows", "review-pr.md") },
{ target: "factorydroid", outputPath: join(".factory", "commands", "review-pr.md") },
])("should generate $target commands", async ({ target, outputPath }) => {
const testDir = getTestDir();

Expand Down Expand Up @@ -59,28 +60,28 @@ Check the PR diff and provide feedback.
}
});

it.each([
{ target: "agentsmd", outputPath: join(".agents", "commands", "review-pr.md") },
{ target: "factorydroid", outputPath: join(".factory", "commands", "review-pr.md") },
])("should generate $target simulated commands", async ({ target, outputPath }) => {
const testDir = getTestDir();
it.each([{ target: "agentsmd", outputPath: join(".agents", "commands", "review-pr.md") }])(
"should generate $target simulated commands",
async ({ target, outputPath }) => {
const testDir = getTestDir();

const commandContent = `---
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 writeFileContent(
join(testDir, RULESYNC_COMMANDS_RELATIVE_DIR_PATH, "review-pr.md"),
commandContent,
);

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

const generatedContent = await readFileContent(join(testDir, outputPath));
expect(generatedContent).toContain("Check the PR diff and provide feedback.");
});
const generatedContent = await readFileContent(join(testDir, outputPath));
expect(generatedContent).toContain("Check the PR diff and provide feedback.");
},
);

it.each([
{ target: "claudecode", orphanPath: join(".claude", "commands", "orphan.md") },
Expand All @@ -98,6 +99,7 @@ Check the PR diff and provide feedback.
{ target: "junie", orphanPath: join(".junie", "commands", "orphan.md") },
{ target: "pi", orphanPath: join(".pi", "prompts", "orphan.md") },
{ target: "devin", orphanPath: join(".devin", "workflows", "orphan.md") },
{ target: "factorydroid", orphanPath: join(".factory", "commands", "orphan.md") },
])(
"should fail in check mode when delete would remove an orphan $target command file",
async ({ target, orphanPath }) => {
Expand Down Expand Up @@ -144,6 +146,7 @@ describe("E2E: commands (import)", () => {
{ target: "junie", sourcePath: join(".junie", "commands", "review-pr.md") },
{ target: "pi", sourcePath: join(".pi", "prompts", "review-pr.md") },
{ target: "devin", sourcePath: join(".devin", "workflows", "review-pr.md") },
{ target: "factorydroid", sourcePath: join(".factory", "commands", "review-pr.md") },
])("should import $target commands", async ({ target, sourcePath }) => {
const testDir = getTestDir();

Expand Down Expand Up @@ -185,6 +188,7 @@ describe("E2E: commands (global mode)", () => {
target: "devin",
outputPath: join(".codeium", "windsurf", "global_workflows", "review-pr.md"),
},
{ target: "factorydroid", outputPath: join(".factory", "commands", "review-pr.md") },
])("should generate $target commands in home directory", async ({ target, outputPath }) => {
const projectDir = getProjectDir();
const homeDir = getHomeDir();
Expand Down
10 changes: 6 additions & 4 deletions src/e2e/e2e-hooks.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ describe("E2E: hooks", () => {
},
{ target: "copilot", outputPath: join(".github", "hooks", "copilot-hooks.json") },
{ target: "copilotcli", outputPath: join(".github", "hooks", "copilotcli-hooks.json") },
{ target: "factorydroid", outputPath: join(".factory", "settings.json") },
{ target: "factorydroid", outputPath: join(".factory", "hooks.json") },
{ target: "kiro", outputPath: join(".kiro", "agents", "default.json") },
{ target: "antigravity-ide", outputPath: join(".agents", "hooks.json") },
{ target: "antigravity-cli", outputPath: join(".agents", "hooks.json") },
Expand Down Expand Up @@ -158,11 +158,13 @@ describe("E2E: hooks", () => {
});

it.each([
// claudecode, geminicli, factorydroid, kiro use shared config files (isDeletable=false) — excluded
// claudecode, geminicli, kiro use shared config files (isDeletable=false) — excluded.
// factorydroid now writes a dedicated .factory/hooks.json (isDeletable=true).
{ target: "cursor", orphanPath: join(".cursor", "hooks.json") },
{ target: "opencode", orphanPath: join(".opencode", "plugins", "rulesync-hooks.js") },
{ target: "codexcli", orphanPath: join(".codex", "hooks.json") },
{ target: "copilot", orphanPath: join(".github", "hooks", "copilot-hooks.json") },
{ target: "factorydroid", orphanPath: join(".factory", "hooks.json") },
])(
"should fail in check mode when delete would remove an orphan $target hooks file",
async ({ target, orphanPath }) => {
Expand Down Expand Up @@ -282,7 +284,7 @@ describe("E2E: hooks (import)", () => {
},
{
target: "factorydroid",
sourcePath: join(".factory", "settings.json"),
sourcePath: join(".factory", "hooks.json"),
sourceContent: {
hooks: {
sessionStart: [
Expand Down Expand Up @@ -389,7 +391,7 @@ describe("E2E: hooks (global mode)", () => {
outputPath: join(".agents", "plugins", "rulesync", "hooks", "hooks.json"),
},
{ target: "opencode", outputPath: join(".config", "opencode", "plugins", "rulesync-hooks.js") },
{ target: "factorydroid", outputPath: join(".factory", "settings.json") },
{ target: "factorydroid", outputPath: join(".factory", "hooks.json") },
{ target: "deepagents", outputPath: join(".deepagents", "hooks.json") },
{ target: "junie", outputPath: join(".junie", "config.json") },
{ target: "cursor", outputPath: join(".cursor", "hooks.json") },
Expand Down
14 changes: 10 additions & 4 deletions src/e2e/e2e-skills.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,10 @@ describe("E2E: skills", () => {
target: "zed",
outputPath: join(".agents", "skills", "test-skill", "SKILL.md"),
},
{
target: "factorydroid",
outputPath: join(".factory", "skills", "test-skill", "SKILL.md"),
},
])("should generate $target skills", async ({ target, outputPath }) => {
const testDir = getTestDir();

Expand Down Expand Up @@ -136,10 +140,6 @@ This is the test skill body content.
target: "agentsmd",
outputPath: join(".agents", "skills", "test-skill", "SKILL.md"),
},
{
target: "factorydroid",
outputPath: join(".factory", "skills", "test-skill", "SKILL.md"),
},
])("should generate $target simulated skills", async ({ target, outputPath }) => {
const testDir = getTestDir();

Expand Down Expand Up @@ -189,6 +189,7 @@ This is the test skill body content.
{ target: "agentsskills", orphanPath: join(".agents", "skills", "orphan-skill", "SKILL.md") },
{ target: "pi", orphanPath: join(".pi", "skills", "orphan-skill", "SKILL.md") },
{ target: "zed", orphanPath: join(".agents", "skills", "orphan-skill", "SKILL.md") },
{ target: "factorydroid", orphanPath: join(".factory", "skills", "orphan-skill", "SKILL.md") },
])(
"should fail in check mode when delete would remove an orphan $target skill file",
async ({ target, orphanPath }) => {
Expand Down Expand Up @@ -242,6 +243,7 @@ describe("E2E: skills (import)", () => {
{ target: "replit", sourcePath: join(".agents", "skills", "test-skill", "SKILL.md") },
{ target: "pi", sourcePath: join(".pi", "skills", "test-skill", "SKILL.md") },
{ target: "zed", sourcePath: join(".agents", "skills", "test-skill", "SKILL.md") },
{ target: "factorydroid", sourcePath: join(".factory", "skills", "test-skill", "SKILL.md") },
])("should import $target skills", async ({ target, sourcePath }) => {
const testDir = getTestDir();

Expand Down Expand Up @@ -345,6 +347,10 @@ describe("E2E: skills (global mode)", () => {
target: "zed",
outputPath: join(".agents", "skills", "test-skill", "SKILL.md"),
},
{
target: "factorydroid",
outputPath: join(".factory", "skills", "test-skill", "SKILL.md"),
},
])("should generate $target skills in home directory", async ({ target, outputPath }) => {
const projectDir = getProjectDir();
const homeDir = getHomeDir();
Expand Down
8 changes: 7 additions & 1 deletion src/e2e/e2e-subagents.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,10 @@ describe("E2E: subagents", () => {
target: "takt",
outputPath: join(".takt", "facets", "personas", "planner.md"),
},
{
target: "factorydroid",
outputPath: join(".factory", "droids", "planner.md"),
},
])("should generate $target subagents", async ({ target, outputPath }) => {
const testDir = getTestDir();

Expand Down Expand Up @@ -82,7 +86,6 @@ You are the planner. Analyze files and create a plan.

it.each([
{ target: "agentsmd", outputPath: join(".agents", "subagents", "planner.md") },
{ target: "factorydroid", outputPath: join(".factory", "droids", "planner.md") },
{ target: "roo", outputPath: join(".roo", "subagents", "planner.md") },
])("should generate $target simulated subagents", async ({ target, outputPath }) => {
const testDir = getTestDir();
Expand Down Expand Up @@ -204,6 +207,7 @@ You are a subagent-only helper.
{ target: "deepagents", orphanPath: join(".deepagents", "agents", "orphan.md") },
{ target: "kiro", orphanPath: join(".kiro", "agents", "orphan.json") },
{ target: "junie", orphanPath: join(".junie", "agents", "orphan.md") },
{ target: "factorydroid", orphanPath: join(".factory", "droids", "orphan.md") },
])(
"should fail in check mode when delete would remove an orphan $target subagent file",
async ({ target, orphanPath }) => {
Expand Down Expand Up @@ -243,6 +247,7 @@ describe("E2E: subagents (import)", () => {
{ target: "opencode", sourcePath: join(".opencode", "agents", "planner.md") },
{ target: "deepagents", sourcePath: join(".deepagents", "agents", "planner.md") },
{ target: "junie", sourcePath: join(".junie", "agents", "planner.md") },
{ target: "factorydroid", sourcePath: join(".factory", "droids", "planner.md") },
])("should import $target subagents", async ({ target, sourcePath }) => {
const testDir = getTestDir();

Expand Down Expand Up @@ -296,6 +301,7 @@ describe("E2E: subagents (global mode)", () => {
{ target: "opencode", outputPath: join(".config", "opencode", "agents", "planner.md") },
{ target: "rovodev", outputPath: join(".rovodev", "subagents", "planner.md") },
{ target: "takt", outputPath: join(".takt", "facets", "personas", "planner.md") },
{ target: "factorydroid", outputPath: join(".factory", "droids", "planner.md") },
])("should generate $target subagents in home directory", async ({ target, outputPath }) => {
const projectDir = getProjectDir();
const homeDir = getHomeDir();
Expand Down
2 changes: 2 additions & 0 deletions src/features/commands/commands-processor.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1142,6 +1142,7 @@ describe("CommandsProcessor", () => {
"cline",
"copilot",
"cursor",
"factorydroid",
"geminicli",
"junie",
"kilo",
Expand Down Expand Up @@ -1194,6 +1195,7 @@ describe("CommandsProcessor", () => {
"claudecode-legacy",
"cline",
"cursor",
"factorydroid",
"geminicli",
"junie",
"codexcli",
Expand Down
7 changes: 5 additions & 2 deletions src/features/commands/commands-processor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -235,10 +235,13 @@ const toolCommandFactories = new Map<CommandsProcessorToolTarget, ToolCommandFac
{
class: FactorydroidCommand,
meta: {
// Factory Droid custom slash commands are native Markdown files under
// .factory/commands/ (project) and ~/.factory/commands/ (personal/global).
// https://docs.factory.ai/cli/configuration/custom-slash-commands
extension: "md",
supportsProject: true,
supportsGlobal: false,
isSimulated: true,
supportsGlobal: true,
isSimulated: false,
supportsSubdirectory: false,
},
},
Expand Down
Loading
Loading