@intellectronica/ruler 0.3.33 → 0.3.35

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 CHANGED
@@ -76,7 +76,7 @@ Ruler solves this by providing a **single source of truth** for all your AI agen
76
76
  | Junie | `.junie/guidelines.md` | - | - |
77
77
  | AugmentCode | `.augment/rules/ruler_augment_instructions.md` | - | - |
78
78
  | Kilo Code | `AGENTS.md` | `.kilocode/mcp.json` | `.claude/skills/` |
79
- | OpenCode | `AGENTS.md` | `opencode.json` | `.opencode/skill/` |
79
+ | OpenCode | `AGENTS.md` | `opencode.json` | `.opencode/skills/` |
80
80
  | Goose | `.goosehints` | - | `.agents/skills/` |
81
81
  | Qwen Code | `AGENTS.md` | `.qwen/settings.json` | - |
82
82
  | RooCode | `AGENTS.md` | `.roo/mcp.json` | `.roo/skills/` |
@@ -238,6 +238,7 @@ The `apply` command looks for `.ruler/` in the current directory tree, reading t
238
238
  | `--mcp-overwrite` | Overwrite native MCP config instead of merging. |
239
239
  | `--gitignore` | Enable automatic .gitignore updates (default: true). |
240
240
  | `--no-gitignore` | Disable automatic .gitignore updates. |
241
+ | `--gitignore-local` | Write managed ignore entries to `.git/info/exclude` instead. |
241
242
  | `--nested` | Enable nested rule loading (default: inherit from config or disabled). |
242
243
  | `--no-nested` | Disable nested rule loading even if `nested = true` in config. |
243
244
  | `--backup` | Toggle creation of `.bak` backup files (default: enabled). |
@@ -405,6 +406,8 @@ Authorization = "Bearer your-token"
405
406
  [gitignore]
406
407
  # Enable/disable automatic .gitignore updates (default: true)
407
408
  enabled = true
409
+ # Write managed entries to .git/info/exclude instead of .gitignore (default: false)
410
+ local = false
408
411
 
409
412
  # --- Agent-Specific Configurations ---
410
413
  [agents.copilot]
@@ -578,7 +581,7 @@ Skills are specialized knowledge packages that extend AI agent capabilities with
578
581
  - **GitHub Copilot**: `.claude/skills/` (shared with Claude Code)
579
582
  - **Kilo Code**: `.claude/skills/` (shared with Claude Code)
580
583
  - **OpenAI Codex CLI**: `.codex/skills/`
581
- - **OpenCode**: `.opencode/skill/`
584
+ - **OpenCode**: `.opencode/skills/`
582
585
  - **Pi Coding Agent**: `.pi/skills/`
583
586
  - **Goose**: `.agents/skills/`
584
587
  - **Amp**: `.agents/skills/` (shared with Goose)
@@ -644,7 +647,7 @@ When skills support is enabled and gitignore integration is active, Ruler automa
644
647
 
645
648
  - `.claude/skills/` (for Claude Code, GitHub Copilot, and Kilo Code)
646
649
  - `.codex/skills/` (for OpenAI Codex CLI)
647
- - `.opencode/skill/` (for OpenCode)
650
+ - `.opencode/skills/` (for OpenCode)
648
651
  - `.pi/skills/` (for Pi Coding Agent)
649
652
  - `.agents/skills/` (for Goose and Amp)
650
653
  - `.agent/skills/` (for Antigravity)
@@ -703,7 +706,7 @@ ruler apply
703
706
  # 3. Skills are now available to compatible agents:
704
707
  # - Claude Code, GitHub Copilot & Kilo Code: .claude/skills/my-skill/
705
708
  # - OpenAI Codex CLI: .codex/skills/my-skill/
706
- # - OpenCode: .opencode/skill/my-skill/
709
+ # - OpenCode: .opencode/skills/my-skill/
707
710
  # - Pi Coding Agent: .pi/skills/my-skill/
708
711
  # - Goose & Amp: .agents/skills/my-skill/
709
712
  # - Antigravity: .agent/skills/my-skill/
@@ -744,8 +747,8 @@ dist/
744
747
 
745
748
  ### Control Options
746
749
 
747
- - **CLI flags**: `--gitignore` or `--no-gitignore`
748
- - **Configuration**: `[gitignore].enabled` in `ruler.toml`
750
+ - **CLI flags**: `--gitignore`, `--no-gitignore`, `--gitignore-local`, `--no-gitignore-local`
751
+ - **Configuration**: `[gitignore].enabled` and `[gitignore].local` in `ruler.toml`
749
752
  - **Default**: enabled
750
753
 
751
754
  ## Practical Usage Scenarios
@@ -44,6 +44,10 @@ function run() {
44
44
  .option('gitignore', {
45
45
  type: 'boolean',
46
46
  description: 'Enable/disable automatic .gitignore updates (default: enabled)',
47
+ })
48
+ .option('gitignore-local', {
49
+ type: 'boolean',
50
+ description: 'Write generated ignore entries to .git/info/exclude instead of .gitignore',
47
51
  })
48
52
  .option('verbose', {
49
53
  type: 'boolean',
@@ -78,6 +78,13 @@ async function applyHandler(argv) {
78
78
  else {
79
79
  gitignorePreference = undefined; // Let TOML/default decide
80
80
  }
81
+ let gitignoreLocalPreference;
82
+ if (argv['gitignore-local'] !== undefined) {
83
+ gitignoreLocalPreference = argv['gitignore-local'];
84
+ }
85
+ else {
86
+ gitignoreLocalPreference = undefined; // Let TOML/default decide
87
+ }
81
88
  // Determine nested preference: CLI > TOML > Default (false)
82
89
  let nested;
83
90
  if (argv.nested !== undefined) {
@@ -108,7 +115,7 @@ async function applyHandler(argv) {
108
115
  skillsEnabled = undefined; // Let config/default decide
109
116
  }
110
117
  try {
111
- await (0, lib_1.applyAllAgentConfigs)(projectRoot, agents, configPath, mcpEnabled, mcpStrategy, gitignorePreference, verbose, dryRun, localOnly, nested, backup, skillsEnabled);
118
+ await (0, lib_1.applyAllAgentConfigs)(projectRoot, agents, configPath, mcpEnabled, mcpStrategy, gitignorePreference, verbose, dryRun, localOnly, nested, backup, skillsEnabled, gitignoreLocalPreference);
112
119
  console.log('Ruler apply completed successfully.');
113
120
  }
114
121
  catch (err) {
@@ -150,6 +157,10 @@ async function initHandler(argv) {
150
157
  # When enabled, ruler will search for and process .ruler directories throughout the project hierarchy
151
158
  # nested = false
152
159
 
160
+ # [gitignore]
161
+ # enabled = true
162
+ # local = false # set true to write generated ignores to .git/info/exclude instead
163
+
153
164
  # --- Agent Specific Configurations ---
154
165
  # You can enable/disable agents and override their default output paths here.
155
166
  # Use lowercase agent identifiers: aider, amp, claude, cline, codex, copilot, cursor, jetbrains-ai, kilocode, pi, windsurf
package/dist/constants.js CHANGED
@@ -54,7 +54,7 @@ exports.SKILLS_DIR = 'skills';
54
54
  exports.RULER_SKILLS_PATH = '.ruler/skills';
55
55
  exports.CLAUDE_SKILLS_PATH = '.claude/skills';
56
56
  exports.CODEX_SKILLS_PATH = '.codex/skills';
57
- exports.OPENCODE_SKILLS_PATH = '.opencode/skill';
57
+ exports.OPENCODE_SKILLS_PATH = '.opencode/skills';
58
58
  exports.PI_SKILLS_PATH = '.pi/skills';
59
59
  exports.GOOSE_SKILLS_PATH = '.agents/skills';
60
60
  exports.VIBE_SKILLS_PATH = '.vibe/skills';
@@ -67,6 +67,7 @@ const rulerConfigSchema = zod_1.z.object({
67
67
  gitignore: zod_1.z
68
68
  .object({
69
69
  enabled: zod_1.z.boolean().optional(),
70
+ local: zod_1.z.boolean().optional(),
70
71
  })
71
72
  .optional(),
72
73
  skills: zod_1.z
@@ -203,6 +204,9 @@ async function loadConfig(options) {
203
204
  if (typeof rawGitignoreSection.enabled === 'boolean') {
204
205
  gitignoreConfig.enabled = rawGitignoreSection.enabled;
205
206
  }
207
+ if (typeof rawGitignoreSection.local === 'boolean') {
208
+ gitignoreConfig.local = rawGitignoreSection.local;
209
+ }
206
210
  const rawSkillsSection = raw.skills && typeof raw.skills === 'object' && !Array.isArray(raw.skills)
207
211
  ? raw.skills
208
212
  : {};
@@ -39,14 +39,15 @@ const path = __importStar(require("path"));
39
39
  const RULER_START_MARKER = '# START Ruler Generated Files';
40
40
  const RULER_END_MARKER = '# END Ruler Generated Files';
41
41
  /**
42
- * Updates the .gitignore file in the project root with paths in a managed Ruler block.
42
+ * Updates an ignore file in the project root with paths in a managed Ruler block.
43
43
  * Creates the file if it doesn't exist, and creates or updates the Ruler-managed block.
44
44
  *
45
- * @param projectRoot The project root directory (where .gitignore should be located)
46
- * @param paths Array of file paths to add to .gitignore (can be absolute or relative)
45
+ * @param projectRoot The project root directory
46
+ * @param paths Array of file paths to add to the ignore file (can be absolute or relative)
47
+ * @param ignoreFile Relative path to the ignore file from project root (defaults to .gitignore)
47
48
  */
48
- async function updateGitignore(projectRoot, paths) {
49
- const gitignorePath = path.join(projectRoot, '.gitignore');
49
+ async function updateGitignore(projectRoot, paths, ignoreFile = '.gitignore') {
50
+ const gitignorePath = path.join(projectRoot, ignoreFile);
50
51
  // Read existing .gitignore or start with empty content
51
52
  let existingContent = '';
52
53
  try {
@@ -97,6 +98,7 @@ async function updateGitignore(projectRoot, paths) {
97
98
  // Create new content
98
99
  const newContent = updateGitignoreContent(existingContent, allRulerPaths);
99
100
  // Write the updated content
101
+ await fs_1.promises.mkdir(path.dirname(gitignorePath), { recursive: true });
100
102
  await fs_1.promises.writeFile(gitignorePath, newContent);
101
103
  }
102
104
  /**
@@ -186,7 +186,7 @@ async function cleanupSkillsDirectories(projectRoot, dryRun, verbose) {
186
186
  catch {
187
187
  // Directory doesn't exist, nothing to clean
188
188
  }
189
- // Clean up .opencode/skill
189
+ // Clean up .opencode/skills
190
190
  try {
191
191
  await fs.access(opencodeSkillsPath);
192
192
  if (dryRun) {
@@ -510,7 +510,7 @@ async function propagateSkillsForCodex(projectRoot, options) {
510
510
  return [];
511
511
  }
512
512
  /**
513
- * Propagates skills for OpenCode by copying .ruler/skills to .opencode/skill.
513
+ * Propagates skills for OpenCode by copying .ruler/skills to .opencode/skills.
514
514
  * Uses atomic replace to ensure safe overwriting of existing skills.
515
515
  * Returns dry-run steps if dryRun is true, otherwise returns empty array.
516
516
  */
@@ -644,8 +644,9 @@ async function applyStandardMcpConfiguration(agent, filteredMcpJson, dest, agent
644
644
  * @param config Loaded configuration
645
645
  * @param cliGitignoreEnabled CLI gitignore setting
646
646
  * @param dryRun Whether to perform a dry run
647
+ * @param cliGitignoreLocal CLI toggle for .git/info/exclude usage
647
648
  */
648
- async function updateGitignore(projectRoot, generatedPaths, config, cliGitignoreEnabled, dryRun) {
649
+ async function updateGitignore(projectRoot, generatedPaths, config, cliGitignoreEnabled, dryRun, cliGitignoreLocal) {
649
650
  // Configuration precedence: CLI > TOML > Default (enabled)
650
651
  let gitignoreEnabled;
651
652
  if (cliGitignoreEnabled !== undefined) {
@@ -657,17 +658,24 @@ async function updateGitignore(projectRoot, generatedPaths, config, cliGitignore
657
658
  else {
658
659
  gitignoreEnabled = true; // Default enabled
659
660
  }
661
+ const gitignoreTarget = cliGitignoreLocal !== undefined
662
+ ? cliGitignoreLocal
663
+ ? '.git/info/exclude'
664
+ : '.gitignore'
665
+ : config.gitignore?.local
666
+ ? '.git/info/exclude'
667
+ : '.gitignore';
660
668
  if (gitignoreEnabled && generatedPaths.length > 0) {
661
669
  const uniquePaths = [...new Set(generatedPaths)];
662
670
  // Note: Individual backup patterns are added per-file in the collection phase
663
671
  // No need to add a broad *.bak pattern here
664
672
  if (uniquePaths.length > 0) {
665
673
  if (dryRun) {
666
- (0, constants_1.logInfo)(`Would update .gitignore with ${uniquePaths.length} unique path(s): ${uniquePaths.join(', ')}`, dryRun);
674
+ (0, constants_1.logInfo)(`Would update ${gitignoreTarget} with ${uniquePaths.length} unique path(s): ${uniquePaths.join(', ')}`, dryRun);
667
675
  }
668
676
  else {
669
- await (0, GitignoreUtils_1.updateGitignore)(projectRoot, uniquePaths);
670
- (0, constants_1.logInfo)(`Updated .gitignore with ${uniquePaths.length} unique path(s) in the Ruler block.`, dryRun);
677
+ await (0, GitignoreUtils_1.updateGitignore)(projectRoot, uniquePaths, gitignoreTarget);
678
+ (0, constants_1.logInfo)(`Updated ${gitignoreTarget} with ${uniquePaths.length} unique path(s) in the Ruler block.`, dryRun);
671
679
  }
672
680
  }
673
681
  }
package/dist/lib.js CHANGED
@@ -62,7 +62,7 @@ function resolveSkillsEnabled(cliFlag, configSetting) {
62
62
  * @param projectRoot Root directory of the project
63
63
  * @param includedAgents Optional list of agent name filters (case-insensitive substrings)
64
64
  */
65
- async function applyAllAgentConfigs(projectRoot, includedAgents, configPath, cliMcpEnabled = true, cliMcpStrategy, cliGitignoreEnabled, verbose = false, dryRun = false, localOnly = false, nested = false, backup = true, skillsEnabled) {
65
+ async function applyAllAgentConfigs(projectRoot, includedAgents, configPath, cliMcpEnabled = true, cliMcpStrategy, cliGitignoreEnabled, verbose = false, dryRun = false, localOnly = false, nested = false, backup = true, skillsEnabled, cliGitignoreLocal) {
66
66
  // Load configuration and rules
67
67
  (0, constants_1.logVerbose)(`Loading configuration from project root: ${projectRoot}`, verbose);
68
68
  if (configPath) {
@@ -128,7 +128,7 @@ async function applyAllAgentConfigs(projectRoot, includedAgents, configPath, cli
128
128
  const skillsPaths = await getSkillsGitignorePaths(projectRoot, selectedAgents);
129
129
  allGeneratedPaths = [...generatedPaths, ...skillsPaths];
130
130
  }
131
- await (0, apply_engine_1.updateGitignore)(projectRoot, allGeneratedPaths, loadedConfig, cliGitignoreEnabled, dryRun);
131
+ await (0, apply_engine_1.updateGitignore)(projectRoot, allGeneratedPaths, loadedConfig, cliGitignoreEnabled, dryRun, cliGitignoreLocal);
132
132
  }
133
133
  /**
134
134
  * Normalizes per-agent config keys to agent identifiers for consistent lookup.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@intellectronica/ruler",
3
- "version": "0.3.33",
3
+ "version": "0.3.35",
4
4
  "description": "Ruler — apply the same rules to all coding agents",
5
5
  "main": "dist/lib.js",
6
6
  "scripts": {