@intellectronica/ruler 0.3.26 → 0.3.28
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -2
- package/dist/agents/FactoryDroidAgent.js +29 -0
- package/dist/agents/KiloCodeAgent.js +4 -4
- package/dist/agents/index.js +2 -0
- package/dist/constants.js +2 -1
- package/dist/core/SkillsProcessor.js +70 -1
- package/dist/core/apply-engine.js +31 -0
- package/dist/paths/mcp.js +3 -0
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -75,7 +75,7 @@ Ruler solves this by providing a **single source of truth** for all your AI agen
|
|
|
75
75
|
| Gemini CLI | `AGENTS.md` | `.gemini/settings.json` | `.gemini/skills/` |
|
|
76
76
|
| Junie | `.junie/guidelines.md` | - | - |
|
|
77
77
|
| AugmentCode | `.augment/rules/ruler_augment_instructions.md` | - | - |
|
|
78
|
-
| Kilo Code |
|
|
78
|
+
| Kilo Code | `AGENTS.md` | `.kilocode/mcp.json` | `.claude/skills/` |
|
|
79
79
|
| OpenCode | `AGENTS.md` | `opencode.json` | `.opencode/skill/` |
|
|
80
80
|
| Goose | `.goosehints` | - | `.agents/skills/` |
|
|
81
81
|
| Qwen Code | `AGENTS.md` | `.qwen/settings.json` | - |
|
|
@@ -91,6 +91,8 @@ Ruler solves this by providing a **single source of truth** for all your AI agen
|
|
|
91
91
|
|
|
92
92
|
### Installation
|
|
93
93
|
|
|
94
|
+
Requires Node.js `^20.19.0 || ^22.12.0 || >=23`.
|
|
95
|
+
|
|
94
96
|
**Global Installation (Recommended for CLI use):**
|
|
95
97
|
|
|
96
98
|
```bash
|
|
@@ -449,7 +451,7 @@ enabled = false
|
|
|
449
451
|
|
|
450
452
|
[agents.kilocode]
|
|
451
453
|
enabled = true
|
|
452
|
-
output_path = ".
|
|
454
|
+
output_path = "AGENTS.md"
|
|
453
455
|
|
|
454
456
|
[agents.warp]
|
|
455
457
|
enabled = true
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.FactoryDroidAgent = void 0;
|
|
4
|
+
const AgentsMdAgent_1 = require("./AgentsMdAgent");
|
|
5
|
+
/**
|
|
6
|
+
* Factory Droid agent adapter.
|
|
7
|
+
* Uses the root-level AGENTS.md for instructions.
|
|
8
|
+
*/
|
|
9
|
+
class FactoryDroidAgent extends AgentsMdAgent_1.AgentsMdAgent {
|
|
10
|
+
getIdentifier() {
|
|
11
|
+
return 'factory';
|
|
12
|
+
}
|
|
13
|
+
getName() {
|
|
14
|
+
return 'Factory Droid';
|
|
15
|
+
}
|
|
16
|
+
getMcpServerKey() {
|
|
17
|
+
return 'mcpServers';
|
|
18
|
+
}
|
|
19
|
+
supportsMcpStdio() {
|
|
20
|
+
return true;
|
|
21
|
+
}
|
|
22
|
+
supportsMcpRemote() {
|
|
23
|
+
return true;
|
|
24
|
+
}
|
|
25
|
+
supportsNativeSkills() {
|
|
26
|
+
return true;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
exports.FactoryDroidAgent = FactoryDroidAgent;
|
|
@@ -35,12 +35,12 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
35
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
36
|
exports.KiloCodeAgent = void 0;
|
|
37
37
|
const path = __importStar(require("path"));
|
|
38
|
-
const
|
|
38
|
+
const AgentsMdAgent_1 = require("./AgentsMdAgent");
|
|
39
39
|
/**
|
|
40
40
|
* Kilo Code agent adapter.
|
|
41
|
-
*
|
|
41
|
+
* Uses AGENTS.md for instructions and .kilocode/mcp.json for MCP configuration.
|
|
42
42
|
*/
|
|
43
|
-
class KiloCodeAgent extends
|
|
43
|
+
class KiloCodeAgent extends AgentsMdAgent_1.AgentsMdAgent {
|
|
44
44
|
getIdentifier() {
|
|
45
45
|
return 'kilocode';
|
|
46
46
|
}
|
|
@@ -48,7 +48,7 @@ class KiloCodeAgent extends AbstractAgent_1.AbstractAgent {
|
|
|
48
48
|
return 'Kilo Code';
|
|
49
49
|
}
|
|
50
50
|
getDefaultOutputPath(projectRoot) {
|
|
51
|
-
return path.join(projectRoot, '.
|
|
51
|
+
return path.join(projectRoot, 'AGENTS.md');
|
|
52
52
|
}
|
|
53
53
|
getMcpServerKey() {
|
|
54
54
|
return 'mcpServers';
|
package/dist/agents/index.js
CHANGED
|
@@ -31,6 +31,7 @@ const RooCodeAgent_1 = require("./RooCodeAgent");
|
|
|
31
31
|
const TraeAgent_1 = require("./TraeAgent");
|
|
32
32
|
const AmazonQCliAgent_1 = require("./AmazonQCliAgent");
|
|
33
33
|
const FirebenderAgent_1 = require("./FirebenderAgent");
|
|
34
|
+
const FactoryDroidAgent_1 = require("./FactoryDroidAgent");
|
|
34
35
|
const AntigravityAgent_1 = require("./AntigravityAgent");
|
|
35
36
|
const MistralVibeAgent_1 = require("./MistralVibeAgent");
|
|
36
37
|
const PiAgent_1 = require("./PiAgent");
|
|
@@ -62,6 +63,7 @@ exports.allAgents = [
|
|
|
62
63
|
new TraeAgent_1.TraeAgent(),
|
|
63
64
|
new AmazonQCliAgent_1.AmazonQCliAgent(),
|
|
64
65
|
new FirebenderAgent_1.FirebenderAgent(),
|
|
66
|
+
new FactoryDroidAgent_1.FactoryDroidAgent(),
|
|
65
67
|
new AntigravityAgent_1.AntigravityAgent(),
|
|
66
68
|
new MistralVibeAgent_1.MistralVibeAgent(),
|
|
67
69
|
new PiAgent_1.PiAgent(),
|
package/dist/constants.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.SKILL_MD_FILENAME = exports.ANTIGRAVITY_SKILLS_PATH = exports.CURSOR_SKILLS_PATH = exports.GEMINI_SKILLS_PATH = exports.ROO_SKILLS_PATH = exports.VIBE_SKILLS_PATH = exports.GOOSE_SKILLS_PATH = exports.PI_SKILLS_PATH = exports.OPENCODE_SKILLS_PATH = exports.CODEX_SKILLS_PATH = exports.CLAUDE_SKILLS_PATH = exports.RULER_SKILLS_PATH = exports.SKILLS_DIR = exports.DEFAULT_RULES_FILENAME = exports.ERROR_PREFIX = void 0;
|
|
3
|
+
exports.SKILL_MD_FILENAME = exports.ANTIGRAVITY_SKILLS_PATH = exports.FACTORY_SKILLS_PATH = exports.CURSOR_SKILLS_PATH = exports.GEMINI_SKILLS_PATH = exports.ROO_SKILLS_PATH = exports.VIBE_SKILLS_PATH = exports.GOOSE_SKILLS_PATH = exports.PI_SKILLS_PATH = exports.OPENCODE_SKILLS_PATH = exports.CODEX_SKILLS_PATH = exports.CLAUDE_SKILLS_PATH = exports.RULER_SKILLS_PATH = exports.SKILLS_DIR = exports.DEFAULT_RULES_FILENAME = exports.ERROR_PREFIX = void 0;
|
|
4
4
|
exports.actionPrefix = actionPrefix;
|
|
5
5
|
exports.createRulerError = createRulerError;
|
|
6
6
|
exports.logVerbose = logVerbose;
|
|
@@ -61,5 +61,6 @@ exports.VIBE_SKILLS_PATH = '.vibe/skills';
|
|
|
61
61
|
exports.ROO_SKILLS_PATH = '.roo/skills';
|
|
62
62
|
exports.GEMINI_SKILLS_PATH = '.gemini/skills';
|
|
63
63
|
exports.CURSOR_SKILLS_PATH = '.cursor/skills';
|
|
64
|
+
exports.FACTORY_SKILLS_PATH = '.factory/skills';
|
|
64
65
|
exports.ANTIGRAVITY_SKILLS_PATH = '.agent/skills';
|
|
65
66
|
exports.SKILL_MD_FILENAME = 'SKILL.md';
|
|
@@ -45,6 +45,7 @@ exports.propagateSkillsForVibe = propagateSkillsForVibe;
|
|
|
45
45
|
exports.propagateSkillsForRoo = propagateSkillsForRoo;
|
|
46
46
|
exports.propagateSkillsForGemini = propagateSkillsForGemini;
|
|
47
47
|
exports.propagateSkillsForCursor = propagateSkillsForCursor;
|
|
48
|
+
exports.propagateSkillsForFactory = propagateSkillsForFactory;
|
|
48
49
|
exports.propagateSkillsForAntigravity = propagateSkillsForAntigravity;
|
|
49
50
|
const path = __importStar(require("path"));
|
|
50
51
|
const fs = __importStar(require("fs/promises"));
|
|
@@ -81,7 +82,7 @@ async function getSkillsGitignorePaths(projectRoot) {
|
|
|
81
82
|
return [];
|
|
82
83
|
}
|
|
83
84
|
// Import here to avoid circular dependency
|
|
84
|
-
const { CLAUDE_SKILLS_PATH, CODEX_SKILLS_PATH, OPENCODE_SKILLS_PATH, PI_SKILLS_PATH, GOOSE_SKILLS_PATH, VIBE_SKILLS_PATH, ROO_SKILLS_PATH, GEMINI_SKILLS_PATH, CURSOR_SKILLS_PATH, ANTIGRAVITY_SKILLS_PATH, } = await Promise.resolve().then(() => __importStar(require('../constants')));
|
|
85
|
+
const { CLAUDE_SKILLS_PATH, CODEX_SKILLS_PATH, OPENCODE_SKILLS_PATH, PI_SKILLS_PATH, GOOSE_SKILLS_PATH, VIBE_SKILLS_PATH, ROO_SKILLS_PATH, GEMINI_SKILLS_PATH, CURSOR_SKILLS_PATH, FACTORY_SKILLS_PATH, ANTIGRAVITY_SKILLS_PATH, } = await Promise.resolve().then(() => __importStar(require('../constants')));
|
|
85
86
|
return [
|
|
86
87
|
path.join(projectRoot, CLAUDE_SKILLS_PATH),
|
|
87
88
|
path.join(projectRoot, CODEX_SKILLS_PATH),
|
|
@@ -92,6 +93,7 @@ async function getSkillsGitignorePaths(projectRoot) {
|
|
|
92
93
|
path.join(projectRoot, ROO_SKILLS_PATH),
|
|
93
94
|
path.join(projectRoot, GEMINI_SKILLS_PATH),
|
|
94
95
|
path.join(projectRoot, CURSOR_SKILLS_PATH),
|
|
96
|
+
path.join(projectRoot, FACTORY_SKILLS_PATH),
|
|
95
97
|
path.join(projectRoot, ANTIGRAVITY_SKILLS_PATH),
|
|
96
98
|
];
|
|
97
99
|
}
|
|
@@ -127,6 +129,7 @@ async function cleanupSkillsDirectories(projectRoot, dryRun, verbose) {
|
|
|
127
129
|
const rooSkillsPath = path.join(projectRoot, constants_1.ROO_SKILLS_PATH);
|
|
128
130
|
const geminiSkillsPath = path.join(projectRoot, constants_1.GEMINI_SKILLS_PATH);
|
|
129
131
|
const cursorSkillsPath = path.join(projectRoot, constants_1.CURSOR_SKILLS_PATH);
|
|
132
|
+
const factorySkillsPath = path.join(projectRoot, constants_1.FACTORY_SKILLS_PATH);
|
|
130
133
|
const antigravitySkillsPath = path.join(projectRoot, constants_1.ANTIGRAVITY_SKILLS_PATH);
|
|
131
134
|
// Clean up .claude/skills
|
|
132
135
|
try {
|
|
@@ -254,6 +257,20 @@ async function cleanupSkillsDirectories(projectRoot, dryRun, verbose) {
|
|
|
254
257
|
catch {
|
|
255
258
|
// Directory doesn't exist, nothing to clean
|
|
256
259
|
}
|
|
260
|
+
// Clean up .factory/skills
|
|
261
|
+
try {
|
|
262
|
+
await fs.access(factorySkillsPath);
|
|
263
|
+
if (dryRun) {
|
|
264
|
+
(0, constants_1.logVerboseInfo)(`DRY RUN: Would remove ${constants_1.FACTORY_SKILLS_PATH}`, verbose, dryRun);
|
|
265
|
+
}
|
|
266
|
+
else {
|
|
267
|
+
await fs.rm(factorySkillsPath, { recursive: true, force: true });
|
|
268
|
+
(0, constants_1.logVerboseInfo)(`Removed ${constants_1.FACTORY_SKILLS_PATH} (skills disabled)`, verbose, dryRun);
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
catch {
|
|
272
|
+
// Directory doesn't exist, nothing to clean
|
|
273
|
+
}
|
|
257
274
|
// Clean up .agent/skills
|
|
258
275
|
try {
|
|
259
276
|
await fs.access(antigravitySkillsPath);
|
|
@@ -333,6 +350,8 @@ async function propagateSkills(projectRoot, agents, skillsEnabled, verbose, dryR
|
|
|
333
350
|
await propagateSkillsForGemini(projectRoot, { dryRun });
|
|
334
351
|
(0, constants_1.logVerboseInfo)(`Copying skills to ${constants_1.CURSOR_SKILLS_PATH} for Cursor`, verbose, dryRun);
|
|
335
352
|
await propagateSkillsForCursor(projectRoot, { dryRun });
|
|
353
|
+
(0, constants_1.logVerboseInfo)(`Copying skills to ${constants_1.FACTORY_SKILLS_PATH} for Factory Droid`, verbose, dryRun);
|
|
354
|
+
await propagateSkillsForFactory(projectRoot, { dryRun });
|
|
336
355
|
(0, constants_1.logVerboseInfo)(`Copying skills to ${constants_1.ANTIGRAVITY_SKILLS_PATH} for Antigravity`, verbose, dryRun);
|
|
337
356
|
await propagateSkillsForAntigravity(projectRoot, { dryRun });
|
|
338
357
|
}
|
|
@@ -788,6 +807,56 @@ async function propagateSkillsForCursor(projectRoot, options) {
|
|
|
788
807
|
}
|
|
789
808
|
return [];
|
|
790
809
|
}
|
|
810
|
+
/**
|
|
811
|
+
* Propagates skills for Factory Droid by copying .ruler/skills to .factory/skills.
|
|
812
|
+
* Uses atomic replace to ensure safe overwriting of existing skills.
|
|
813
|
+
* Returns dry-run steps if dryRun is true, otherwise returns empty array.
|
|
814
|
+
*/
|
|
815
|
+
async function propagateSkillsForFactory(projectRoot, options) {
|
|
816
|
+
const skillsDir = path.join(projectRoot, constants_1.RULER_SKILLS_PATH);
|
|
817
|
+
const factorySkillsPath = path.join(projectRoot, constants_1.FACTORY_SKILLS_PATH);
|
|
818
|
+
const factoryDir = path.dirname(factorySkillsPath);
|
|
819
|
+
// Check if source skills directory exists
|
|
820
|
+
try {
|
|
821
|
+
await fs.access(skillsDir);
|
|
822
|
+
}
|
|
823
|
+
catch {
|
|
824
|
+
// No skills directory - return empty
|
|
825
|
+
return [];
|
|
826
|
+
}
|
|
827
|
+
if (options.dryRun) {
|
|
828
|
+
return [`Copy skills from ${constants_1.RULER_SKILLS_PATH} to ${constants_1.FACTORY_SKILLS_PATH}`];
|
|
829
|
+
}
|
|
830
|
+
// Ensure .factory directory exists
|
|
831
|
+
await fs.mkdir(factoryDir, { recursive: true });
|
|
832
|
+
// Use atomic replace: copy to temp, then rename
|
|
833
|
+
const tempDir = path.join(factoryDir, `skills.tmp-${Date.now()}`);
|
|
834
|
+
try {
|
|
835
|
+
// Copy to temp directory
|
|
836
|
+
await (0, SkillsUtils_1.copySkillsDirectory)(skillsDir, tempDir);
|
|
837
|
+
// Atomically replace the target
|
|
838
|
+
// First, remove existing target if it exists
|
|
839
|
+
try {
|
|
840
|
+
await fs.rm(factorySkillsPath, { recursive: true, force: true });
|
|
841
|
+
}
|
|
842
|
+
catch {
|
|
843
|
+
// Target didn't exist, that's fine
|
|
844
|
+
}
|
|
845
|
+
// Rename temp to target
|
|
846
|
+
await fs.rename(tempDir, factorySkillsPath);
|
|
847
|
+
}
|
|
848
|
+
catch (error) {
|
|
849
|
+
// Clean up temp directory on error
|
|
850
|
+
try {
|
|
851
|
+
await fs.rm(tempDir, { recursive: true, force: true });
|
|
852
|
+
}
|
|
853
|
+
catch {
|
|
854
|
+
// Ignore cleanup errors
|
|
855
|
+
}
|
|
856
|
+
throw error;
|
|
857
|
+
}
|
|
858
|
+
return [];
|
|
859
|
+
}
|
|
791
860
|
/**
|
|
792
861
|
* Propagates skills for Antigravity by copying .ruler/skills to .agent/skills.
|
|
793
862
|
* Uses atomic replace to ensure safe overwriting of existing skills.
|
|
@@ -489,6 +489,34 @@ function transformMcpForKiloCode(mcpJson) {
|
|
|
489
489
|
transformedMcp.mcpServers = transformedServers;
|
|
490
490
|
return transformedMcp;
|
|
491
491
|
}
|
|
492
|
+
/**
|
|
493
|
+
* Transform MCP server types for Factory Droid compatibility.
|
|
494
|
+
* Factory Droid expects "http" for remote HTTP servers, not "remote".
|
|
495
|
+
*/
|
|
496
|
+
function transformMcpForFactoryDroid(mcpJson) {
|
|
497
|
+
if (!mcpJson.mcpServers || typeof mcpJson.mcpServers !== 'object') {
|
|
498
|
+
return mcpJson;
|
|
499
|
+
}
|
|
500
|
+
const transformedMcp = { ...mcpJson };
|
|
501
|
+
const transformedServers = {};
|
|
502
|
+
for (const [name, serverDef] of Object.entries(mcpJson.mcpServers)) {
|
|
503
|
+
if (serverDef && typeof serverDef === 'object') {
|
|
504
|
+
const server = serverDef;
|
|
505
|
+
const transformedServer = { ...server };
|
|
506
|
+
if (server.type === 'remote' &&
|
|
507
|
+
server.url &&
|
|
508
|
+
typeof server.url === 'string') {
|
|
509
|
+
transformedServer.type = 'http';
|
|
510
|
+
}
|
|
511
|
+
transformedServers[name] = transformedServer;
|
|
512
|
+
}
|
|
513
|
+
else {
|
|
514
|
+
transformedServers[name] = serverDef;
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
transformedMcp.mcpServers = transformedServers;
|
|
518
|
+
return transformedMcp;
|
|
519
|
+
}
|
|
492
520
|
async function applyStandardMcpConfiguration(agent, filteredMcpJson, dest, agentConfig, config, cliMcpStrategy, dryRun, verbose, backup = true) {
|
|
493
521
|
const strategy = cliMcpStrategy ??
|
|
494
522
|
agentConfig?.mcp?.strategy ??
|
|
@@ -513,6 +541,9 @@ async function applyStandardMcpConfiguration(agent, filteredMcpJson, dest, agent
|
|
|
513
541
|
else if (agent.getIdentifier() === 'kilocode') {
|
|
514
542
|
mcpToMerge = transformMcpForKiloCode(filteredMcpJson);
|
|
515
543
|
}
|
|
544
|
+
else if (agent.getIdentifier() === 'factory') {
|
|
545
|
+
mcpToMerge = transformMcpForFactoryDroid(filteredMcpJson);
|
|
546
|
+
}
|
|
516
547
|
const existing = await (0, mcp_1.readNativeMcp)(dest);
|
|
517
548
|
const merged = (0, merge_1.mergeMcp)(existing, mcpToMerge, strategy, serverKey);
|
|
518
549
|
// Firebase Studio (IDX) expects no "type" fields in .idx/mcp.json server entries.
|
package/dist/paths/mcp.js
CHANGED
|
@@ -86,6 +86,9 @@ async function getNativeMcpPath(adapterName, projectRoot) {
|
|
|
86
86
|
case 'Firebase Studio':
|
|
87
87
|
candidates.push(path.join(projectRoot, '.idx', 'mcp.json'));
|
|
88
88
|
break;
|
|
89
|
+
case 'Factory Droid':
|
|
90
|
+
candidates.push(path.join(projectRoot, '.factory', 'mcp.json'));
|
|
91
|
+
break;
|
|
89
92
|
case 'Zed':
|
|
90
93
|
// Only consider project-local Zed settings (avoid writing to user home directory)
|
|
91
94
|
candidates.push(path.join(projectRoot, '.zed', 'settings.json'));
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@intellectronica/ruler",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.28",
|
|
4
4
|
"description": "Ruler — apply the same rules to all coding agents",
|
|
5
5
|
"main": "dist/lib.js",
|
|
6
6
|
"scripts": {
|
|
@@ -36,7 +36,7 @@
|
|
|
36
36
|
},
|
|
37
37
|
"homepage": "https://ai.intellectronica.net/ruler",
|
|
38
38
|
"engines": {
|
|
39
|
-
"node": ">=
|
|
39
|
+
"node": "^20.19.0 || ^22.12.0 || >=23"
|
|
40
40
|
},
|
|
41
41
|
"files": [
|
|
42
42
|
"dist",
|