@intellectronica/ruler 0.3.31 → 0.3.33

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.
@@ -101,7 +101,20 @@ async function readMarkdownFiles(rulerDir) {
101
101
  const entries = await fs_1.promises.readdir(dir, { withFileTypes: true });
102
102
  for (const entry of entries) {
103
103
  const fullPath = path.join(dir, entry.name);
104
- if (entry.isDirectory()) {
104
+ // Resolve symlinks to determine actual type
105
+ let isDir = entry.isDirectory();
106
+ let isFile = entry.isFile();
107
+ if (entry.isSymbolicLink()) {
108
+ try {
109
+ const stat = await fs_1.promises.stat(fullPath);
110
+ isDir = stat.isDirectory();
111
+ isFile = stat.isFile();
112
+ }
113
+ catch {
114
+ continue; // skip broken symlinks
115
+ }
116
+ }
117
+ if (isDir) {
105
118
  // Skip .ruler/skills; skills are propagated separately and should not be concatenated
106
119
  const relativeFromRoot = path.relative(rulerDir, fullPath);
107
120
  const isSkillsDir = relativeFromRoot === constants_1.SKILLS_DIR ||
@@ -111,7 +124,7 @@ async function readMarkdownFiles(rulerDir) {
111
124
  }
112
125
  await walk(fullPath);
113
126
  }
114
- else if (entry.isFile() && entry.name.endsWith('.md')) {
127
+ else if (isFile && entry.name.endsWith('.md')) {
115
128
  const content = await fs_1.promises.readFile(fullPath, 'utf8');
116
129
  mdFiles.push({ path: fullPath, content });
117
130
  }
@@ -72,7 +72,7 @@ async function discoverSkills(projectRoot) {
72
72
  * Gets the paths that skills will generate, for gitignore purposes.
73
73
  * Returns empty array if skills directory doesn't exist.
74
74
  */
75
- async function getSkillsGitignorePaths(projectRoot) {
75
+ async function getSkillsGitignorePaths(projectRoot, agents) {
76
76
  const skillsDir = path.join(projectRoot, constants_1.RULER_SKILLS_PATH);
77
77
  // Check if skills directory exists
78
78
  try {
@@ -83,19 +83,21 @@ async function getSkillsGitignorePaths(projectRoot) {
83
83
  }
84
84
  // Import here to avoid circular dependency
85
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')));
86
- return [
87
- path.join(projectRoot, CLAUDE_SKILLS_PATH),
88
- path.join(projectRoot, CODEX_SKILLS_PATH),
89
- path.join(projectRoot, OPENCODE_SKILLS_PATH),
90
- path.join(projectRoot, PI_SKILLS_PATH),
91
- path.join(projectRoot, GOOSE_SKILLS_PATH),
92
- path.join(projectRoot, VIBE_SKILLS_PATH),
93
- path.join(projectRoot, ROO_SKILLS_PATH),
94
- path.join(projectRoot, GEMINI_SKILLS_PATH),
95
- path.join(projectRoot, CURSOR_SKILLS_PATH),
96
- path.join(projectRoot, FACTORY_SKILLS_PATH),
97
- path.join(projectRoot, ANTIGRAVITY_SKILLS_PATH),
98
- ];
86
+ const selectedTargets = getSelectedSkillTargets(agents);
87
+ const targetPaths = {
88
+ claude: CLAUDE_SKILLS_PATH,
89
+ codex: CODEX_SKILLS_PATH,
90
+ opencode: OPENCODE_SKILLS_PATH,
91
+ pi: PI_SKILLS_PATH,
92
+ goose: GOOSE_SKILLS_PATH,
93
+ vibe: VIBE_SKILLS_PATH,
94
+ roo: ROO_SKILLS_PATH,
95
+ gemini: GEMINI_SKILLS_PATH,
96
+ cursor: CURSOR_SKILLS_PATH,
97
+ factory: FACTORY_SKILLS_PATH,
98
+ antigravity: ANTIGRAVITY_SKILLS_PATH,
99
+ };
100
+ return Array.from(selectedTargets).map((target) => path.join(projectRoot, targetPaths[target]));
99
101
  }
100
102
  /**
101
103
  * Module-level state to track if experimental warning has been shown.
@@ -115,6 +117,31 @@ function warnOnceExperimental(verbose, dryRun) {
115
117
  hasWarnedExperimental = true;
116
118
  (0, constants_1.logWarn)('Skills support is experimental and behavior may change in future releases.', dryRun);
117
119
  }
120
+ const SKILL_TARGET_TO_IDENTIFIERS = new Map([
121
+ ['claude', ['claude', 'copilot', 'kilocode']],
122
+ ['codex', ['codex']],
123
+ ['opencode', ['opencode']],
124
+ ['pi', ['pi']],
125
+ ['goose', ['goose', 'amp']],
126
+ ['vibe', ['mistral']],
127
+ ['roo', ['roo']],
128
+ ['gemini', ['gemini-cli']],
129
+ ['cursor', ['cursor']],
130
+ ['factory', ['factory']],
131
+ ['antigravity', ['antigravity']],
132
+ ]);
133
+ function getSelectedSkillTargets(agents) {
134
+ const selectedIdentifiers = new Set(agents
135
+ .filter((agent) => agent.supportsNativeSkills?.())
136
+ .map((agent) => agent.getIdentifier()));
137
+ const targets = new Set();
138
+ for (const [target, identifiers] of SKILL_TARGET_TO_IDENTIFIERS) {
139
+ if (identifiers.some((id) => selectedIdentifiers.has(id))) {
140
+ targets.add(target);
141
+ }
142
+ }
143
+ return targets;
144
+ }
118
145
  /**
119
146
  * Cleans up skills directories when skills are disabled.
120
147
  * This ensures that stale skills from previous runs don't persist when skills are turned off.
@@ -330,28 +357,53 @@ async function propagateSkills(projectRoot, agents, skillsEnabled, verbose, dryR
330
357
  }
331
358
  // Warn about experimental features
332
359
  warnOnceExperimental(verbose, dryRun);
360
+ const selectedTargets = getSelectedSkillTargets(agents);
361
+ if (selectedTargets.size === 0) {
362
+ (0, constants_1.logVerboseInfo)('No selected agents require skills propagation, skipping skills propagation', verbose, dryRun);
363
+ return;
364
+ }
333
365
  // Copy to Claude skills directory if needed
334
- if (hasNativeSkillsAgent) {
335
- (0, constants_1.logVerboseInfo)(`Copying skills to ${constants_1.CLAUDE_SKILLS_PATH} for Claude Code, GitHub Copilot, and Kilo Code`, verbose, dryRun);
366
+ if (selectedTargets.has('claude')) {
367
+ (0, constants_1.logVerboseInfo)(`Copying skills to ${constants_1.CLAUDE_SKILLS_PATH} for Claude Code, GitHub Copilot, and KiloCode`, verbose, dryRun);
336
368
  await propagateSkillsForClaude(projectRoot, { dryRun });
369
+ }
370
+ if (selectedTargets.has('codex')) {
337
371
  (0, constants_1.logVerboseInfo)(`Copying skills to ${constants_1.CODEX_SKILLS_PATH} for OpenAI Codex CLI`, verbose, dryRun);
338
372
  await propagateSkillsForCodex(projectRoot, { dryRun });
373
+ }
374
+ if (selectedTargets.has('opencode')) {
339
375
  (0, constants_1.logVerboseInfo)(`Copying skills to ${constants_1.OPENCODE_SKILLS_PATH} for OpenCode`, verbose, dryRun);
340
376
  await propagateSkillsForOpenCode(projectRoot, { dryRun });
377
+ }
378
+ if (selectedTargets.has('pi')) {
341
379
  (0, constants_1.logVerboseInfo)(`Copying skills to ${constants_1.PI_SKILLS_PATH} for Pi Coding Agent`, verbose, dryRun);
342
380
  await propagateSkillsForPi(projectRoot, { dryRun });
381
+ }
382
+ if (selectedTargets.has('goose')) {
343
383
  (0, constants_1.logVerboseInfo)(`Copying skills to ${constants_1.GOOSE_SKILLS_PATH} for Goose and Amp`, verbose, dryRun);
344
384
  await propagateSkillsForGoose(projectRoot, { dryRun });
385
+ }
386
+ if (selectedTargets.has('vibe')) {
345
387
  (0, constants_1.logVerboseInfo)(`Copying skills to ${constants_1.VIBE_SKILLS_PATH} for Mistral Vibe`, verbose, dryRun);
346
388
  await propagateSkillsForVibe(projectRoot, { dryRun });
389
+ }
390
+ if (selectedTargets.has('roo')) {
347
391
  (0, constants_1.logVerboseInfo)(`Copying skills to ${constants_1.ROO_SKILLS_PATH} for Roo Code`, verbose, dryRun);
348
392
  await propagateSkillsForRoo(projectRoot, { dryRun });
393
+ }
394
+ if (selectedTargets.has('gemini')) {
349
395
  (0, constants_1.logVerboseInfo)(`Copying skills to ${constants_1.GEMINI_SKILLS_PATH} for Gemini CLI`, verbose, dryRun);
350
396
  await propagateSkillsForGemini(projectRoot, { dryRun });
397
+ }
398
+ if (selectedTargets.has('cursor')) {
351
399
  (0, constants_1.logVerboseInfo)(`Copying skills to ${constants_1.CURSOR_SKILLS_PATH} for Cursor`, verbose, dryRun);
352
400
  await propagateSkillsForCursor(projectRoot, { dryRun });
401
+ }
402
+ if (selectedTargets.has('factory')) {
353
403
  (0, constants_1.logVerboseInfo)(`Copying skills to ${constants_1.FACTORY_SKILLS_PATH} for Factory Droid`, verbose, dryRun);
354
404
  await propagateSkillsForFactory(projectRoot, { dryRun });
405
+ }
406
+ if (selectedTargets.has('antigravity')) {
355
407
  (0, constants_1.logVerboseInfo)(`Copying skills to ${constants_1.ANTIGRAVITY_SKILLS_PATH} for Antigravity`, verbose, dryRun);
356
408
  await propagateSkillsForAntigravity(projectRoot, { dryRun });
357
409
  }
package/dist/lib.js CHANGED
@@ -125,7 +125,7 @@ async function applyAllAgentConfigs(projectRoot, includedAgents, configPath, cli
125
125
  if (skillsEnabledForGitignore) {
126
126
  // Skills enabled by default or explicitly
127
127
  const { getSkillsGitignorePaths } = await Promise.resolve().then(() => __importStar(require('./core/SkillsProcessor')));
128
- const skillsPaths = await getSkillsGitignorePaths(projectRoot);
128
+ const skillsPaths = await getSkillsGitignorePaths(projectRoot, selectedAgents);
129
129
  allGeneratedPaths = [...generatedPaths, ...skillsPaths];
130
130
  }
131
131
  await (0, apply_engine_1.updateGitignore)(projectRoot, allGeneratedPaths, loadedConfig, cliGitignoreEnabled, dryRun);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@intellectronica/ruler",
3
- "version": "0.3.31",
3
+ "version": "0.3.33",
4
4
  "description": "Ruler — apply the same rules to all coding agents",
5
5
  "main": "dist/lib.js",
6
6
  "scripts": {