@intellectronica/ruler 0.3.40 → 0.3.42

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.
Files changed (95) hide show
  1. package/README.md +59 -46
  2. package/dist/agents/AbstractAgent.d.ts +53 -0
  3. package/dist/agents/AgentsMdAgent.d.ts +14 -0
  4. package/dist/agents/AiderAgent.d.ts +14 -0
  5. package/dist/agents/AiderAgent.js +3 -1
  6. package/dist/agents/AmazonQCliAgent.d.ts +13 -0
  7. package/dist/agents/AmpAgent.d.ts +6 -0
  8. package/dist/agents/AntigravityAgent.d.ts +10 -0
  9. package/dist/agents/AugmentCodeAgent.d.ts +13 -0
  10. package/dist/agents/ClaudeAgent.d.ts +13 -0
  11. package/dist/agents/ClineAgent.d.ts +9 -0
  12. package/dist/agents/CodexCliAgent.d.ts +31 -0
  13. package/dist/agents/CopilotAgent.d.ts +20 -0
  14. package/dist/agents/CrushAgent.d.ts +14 -0
  15. package/dist/agents/CrushAgent.js +5 -2
  16. package/dist/agents/CursorAgent.d.ts +17 -0
  17. package/dist/agents/FactoryDroidAgent.d.ts +13 -0
  18. package/dist/agents/FirebaseAgent.d.ts +11 -0
  19. package/dist/agents/FirebenderAgent.d.ts +36 -0
  20. package/dist/agents/GeminiCliAgent.d.ts +11 -0
  21. package/dist/agents/GeminiCliAgent.js +2 -2
  22. package/dist/agents/GooseAgent.d.ts +12 -0
  23. package/dist/agents/IAgent.d.ts +72 -0
  24. package/dist/agents/JetBrainsAiAssistantAgent.d.ts +10 -0
  25. package/dist/agents/JulesAgent.d.ts +5 -0
  26. package/dist/agents/JunieAgent.d.ts +12 -0
  27. package/dist/agents/KiloCodeAgent.d.ts +14 -0
  28. package/dist/agents/KiroAgent.d.ts +8 -0
  29. package/dist/agents/MistralVibeAgent.d.ts +31 -0
  30. package/dist/agents/OpenCodeAgent.d.ts +11 -0
  31. package/dist/agents/OpenCodeAgent.js +14 -9
  32. package/dist/agents/OpenHandsAgent.d.ts +8 -0
  33. package/dist/agents/PiAgent.d.ts +9 -0
  34. package/dist/agents/QwenCodeAgent.d.ts +10 -0
  35. package/dist/agents/QwenCodeAgent.js +2 -2
  36. package/dist/agents/RooCodeAgent.d.ts +16 -0
  37. package/dist/agents/TraeAgent.d.ts +10 -0
  38. package/dist/agents/WarpAgent.d.ts +12 -0
  39. package/dist/agents/WindsurfAgent.d.ts +13 -0
  40. package/dist/agents/ZedAgent.d.ts +21 -0
  41. package/dist/agents/ZedAgent.js +5 -2
  42. package/dist/agents/agent-utils.d.ts +5 -0
  43. package/dist/agents/agent-utils.js +8 -5
  44. package/dist/agents/index.d.ts +9 -0
  45. package/dist/cli/commands.d.ts +4 -0
  46. package/dist/cli/commands.js +2 -3
  47. package/dist/cli/handlers.d.ts +41 -0
  48. package/dist/cli/handlers.js +76 -60
  49. package/dist/cli/index.d.ts +2 -0
  50. package/dist/constants.d.ts +35 -0
  51. package/dist/core/ConfigLoader.d.ts +57 -0
  52. package/dist/core/ConfigLoader.js +123 -41
  53. package/dist/core/FileSystemUtils.d.ts +51 -0
  54. package/dist/core/FileSystemUtils.js +37 -17
  55. package/dist/core/GitignoreUtils.d.ts +15 -0
  56. package/dist/core/GitignoreUtils.js +32 -1
  57. package/dist/core/RuleProcessor.d.ts +8 -0
  58. package/dist/core/SkillsProcessor.d.ts +127 -0
  59. package/dist/core/SkillsProcessor.js +104 -218
  60. package/dist/core/SkillsUtils.d.ts +26 -0
  61. package/dist/core/SubagentsProcessor.d.ts +38 -0
  62. package/dist/core/SubagentsProcessor.js +68 -22
  63. package/dist/core/SubagentsUtils.d.ts +34 -0
  64. package/dist/core/UnifiedConfigLoader.d.ts +10 -0
  65. package/dist/core/UnifiedConfigLoader.js +61 -31
  66. package/dist/core/UnifiedConfigTypes.d.ts +95 -0
  67. package/dist/core/agent-selection.d.ts +12 -0
  68. package/dist/core/agent-selection.js +11 -3
  69. package/dist/core/apply-engine.d.ts +69 -0
  70. package/dist/core/apply-engine.js +57 -50
  71. package/dist/core/config-utils.d.ts +14 -0
  72. package/dist/core/config-utils.js +9 -3
  73. package/dist/core/hash.d.ts +2 -0
  74. package/dist/core/path-utils.d.ts +1 -0
  75. package/dist/core/path-utils.js +42 -0
  76. package/dist/core/revert-engine.d.ts +36 -0
  77. package/dist/core/revert-engine.js +70 -9
  78. package/dist/lib.d.ts +13 -0
  79. package/dist/lib.js +23 -5
  80. package/dist/mcp/capabilities.d.ts +20 -0
  81. package/dist/mcp/merge.d.ts +10 -0
  82. package/dist/mcp/merge.js +19 -1
  83. package/dist/mcp/propagateOpenCodeMcp.d.ts +2 -0
  84. package/dist/mcp/propagateOpenCodeMcp.js +21 -9
  85. package/dist/mcp/propagateOpenHandsMcp.d.ts +2 -0
  86. package/dist/mcp/propagateOpenHandsMcp.js +31 -15
  87. package/dist/mcp/validate.d.ts +7 -0
  88. package/dist/mcp/validate.js +6 -1
  89. package/dist/paths/mcp.d.ts +8 -0
  90. package/dist/paths/mcp.js +33 -4
  91. package/dist/revert.d.ts +6 -0
  92. package/dist/revert.js +39 -27
  93. package/dist/types.d.ts +87 -0
  94. package/dist/vscode/settings.d.ts +40 -0
  95. package/package.json +7 -4
package/dist/mcp/merge.js CHANGED
@@ -1,6 +1,13 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.mergeMcp = mergeMcp;
4
+ const MCP_SERVER_KEYS = [
5
+ 'mcp',
6
+ 'mcpServers',
7
+ 'servers',
8
+ 'mcp_servers',
9
+ 'context_servers',
10
+ ];
4
11
  /**
5
12
  * Merge native and incoming MCP server configurations according to strategy.
6
13
  * @param base Existing native MCP config object.
@@ -17,7 +24,14 @@ function mergeMcp(base, incoming, strategy, serverKey) {
17
24
  incoming.mcpServers ||
18
25
  incoming.mcp ||
19
26
  {};
27
+ const preservedBase = { ...base };
28
+ for (const key of MCP_SERVER_KEYS) {
29
+ if (key !== serverKey) {
30
+ delete preservedBase[key];
31
+ }
32
+ }
20
33
  return {
34
+ ...preservedBase,
21
35
  [serverKey]: incomingServers,
22
36
  };
23
37
  }
@@ -31,7 +45,11 @@ function mergeMcp(base, incoming, strategy, serverKey) {
31
45
  {};
32
46
  const mergedServers = { ...baseServers, ...incomingServers };
33
47
  const newBase = { ...base };
34
- delete newBase.mcpServers; // Remove old key if present
48
+ for (const key of MCP_SERVER_KEYS) {
49
+ if (key !== serverKey) {
50
+ delete newBase[key];
51
+ }
52
+ }
35
53
  return {
36
54
  ...newBase,
37
55
  [serverKey]: mergedServers,
@@ -0,0 +1,2 @@
1
+ import { McpStrategy } from '../types';
2
+ export declare function propagateMcpToOpenCode(rulerMcpData: Record<string, unknown> | null, openCodeConfigPath: string, backup?: boolean, strategy?: McpStrategy): Promise<void>;
@@ -91,16 +91,26 @@ function transformToOpenCodeFormat(rulerMcp) {
91
91
  mcp: openCodeServers,
92
92
  };
93
93
  }
94
- async function propagateMcpToOpenCode(rulerMcpData, openCodeConfigPath, backup = true) {
94
+ async function propagateMcpToOpenCode(rulerMcpData, openCodeConfigPath, backup = true, strategy = 'merge') {
95
95
  const rulerMcp = rulerMcpData || {};
96
96
  // Read existing OpenCode config if it exists
97
97
  let existingConfig = {};
98
+ let existingContent;
98
99
  try {
99
- const existingContent = await fs.readFile(openCodeConfigPath, 'utf8');
100
- existingConfig = JSON.parse(existingContent);
100
+ existingContent = await fs.readFile(openCodeConfigPath, 'utf8');
101
101
  }
102
- catch {
103
- // File doesn't exist, we'll create it
102
+ catch (error) {
103
+ if (error.code !== 'ENOENT') {
104
+ throw new Error(`Could not read OpenCode config at ${openCodeConfigPath}: ${error instanceof Error ? error.message : String(error)}`);
105
+ }
106
+ }
107
+ if (existingContent !== undefined) {
108
+ try {
109
+ existingConfig = JSON.parse(existingContent);
110
+ }
111
+ catch (error) {
112
+ throw new Error(`Invalid OpenCode config at ${openCodeConfigPath}: ${error instanceof Error ? error.message : String(error)}`);
113
+ }
104
114
  }
105
115
  // Transform ruler MCP to OpenCode format
106
116
  const transformedConfig = transformToOpenCodeFormat(rulerMcp);
@@ -108,10 +118,12 @@ async function propagateMcpToOpenCode(rulerMcpData, openCodeConfigPath, backup =
108
118
  const finalConfig = {
109
119
  ...existingConfig,
110
120
  $schema: transformedConfig.$schema,
111
- mcp: {
112
- ...existingConfig.mcp,
113
- ...transformedConfig.mcp,
114
- },
121
+ mcp: strategy === 'overwrite'
122
+ ? transformedConfig.mcp
123
+ : {
124
+ ...existingConfig.mcp,
125
+ ...transformedConfig.mcp,
126
+ },
115
127
  };
116
128
  await (0, FileSystemUtils_1.ensureDirExists)(path.dirname(openCodeConfigPath));
117
129
  if (backup) {
@@ -0,0 +1,2 @@
1
+ import { McpStrategy } from '../types';
2
+ export declare function propagateMcpToOpenHands(rulerMcpData: Record<string, unknown> | null, openHandsConfigPath: string, backup?: boolean, strategy?: McpStrategy): Promise<void>;
@@ -89,7 +89,7 @@ function normalizeRemoteServerArray(entries) {
89
89
  // All entries are strings, keep as is
90
90
  return entries;
91
91
  }
92
- async function propagateMcpToOpenHands(rulerMcpData, openHandsConfigPath, backup = true) {
92
+ async function propagateMcpToOpenHands(rulerMcpData, openHandsConfigPath, backup = true, strategy = 'merge') {
93
93
  const rulerMcp = rulerMcpData || {};
94
94
  // Always use the legacy Ruler MCP config format as input (top-level "mcpServers" key)
95
95
  const rulerServers = rulerMcp.mcpServers || {};
@@ -100,12 +100,22 @@ async function propagateMcpToOpenHands(rulerMcpData, openHandsConfigPath, backup
100
100
  return;
101
101
  }
102
102
  let config = {};
103
+ let tomlContent;
103
104
  try {
104
- const tomlContent = await fs.readFile(openHandsConfigPath, 'utf8');
105
- config = (0, toml_1.parse)(tomlContent);
105
+ tomlContent = await fs.readFile(openHandsConfigPath, 'utf8');
106
106
  }
107
- catch {
108
- // File doesn't exist, we'll create it.
107
+ catch (error) {
108
+ if (error.code !== 'ENOENT') {
109
+ throw new Error(`Could not read OpenHands config at ${openHandsConfigPath}: ${error instanceof Error ? error.message : String(error)}`);
110
+ }
111
+ }
112
+ if (tomlContent !== undefined) {
113
+ try {
114
+ config = (0, toml_1.parse)(tomlContent);
115
+ }
116
+ catch (error) {
117
+ throw new Error(`Invalid OpenHands config at ${openHandsConfigPath}: ${error instanceof Error ? error.message : String(error)}`);
118
+ }
109
119
  }
110
120
  if (!config.mcp) {
111
121
  config.mcp = {};
@@ -119,18 +129,24 @@ async function propagateMcpToOpenHands(rulerMcpData, openHandsConfigPath, backup
119
129
  if (!config.mcp.shttp_servers) {
120
130
  config.mcp.shttp_servers = [];
121
131
  }
122
- // Build maps for merging existing servers
123
- const existingStdioServers = new Map(config.mcp.stdio_servers.map((s) => [s.name, s]));
132
+ // Build maps for merging existing servers, or start fresh when overwriting.
133
+ const existingStdioServers = new Map(strategy === 'overwrite'
134
+ ? []
135
+ : config.mcp.stdio_servers.map((s) => [s.name, s]));
124
136
  const existingSseServers = new Map();
125
- config.mcp.sse_servers.forEach((entry) => {
126
- const url = typeof entry === 'string' ? entry : entry.url;
127
- existingSseServers.set(url, entry);
128
- });
137
+ if (strategy !== 'overwrite') {
138
+ config.mcp.sse_servers.forEach((entry) => {
139
+ const url = typeof entry === 'string' ? entry : entry.url;
140
+ existingSseServers.set(url, entry);
141
+ });
142
+ }
129
143
  const existingShttpServers = new Map();
130
- config.mcp.shttp_servers.forEach((entry) => {
131
- const url = typeof entry === 'string' ? entry : entry.url;
132
- existingShttpServers.set(url, entry);
133
- });
144
+ if (strategy !== 'overwrite') {
145
+ config.mcp.shttp_servers.forEach((entry) => {
146
+ const url = typeof entry === 'string' ? entry : entry.url;
147
+ existingShttpServers.set(url, entry);
148
+ });
149
+ }
134
150
  for (const [name, serverDef] of Object.entries(rulerServers)) {
135
151
  if (isRulerMcpServer(serverDef)) {
136
152
  if (serverDef.command) {
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Validate the structure of the Ruler MCP JSON config.
3
+ * Minimal validation: ensure 'mcpServers' property exists and is an object.
4
+ * @param data Parsed JSON object from .ruler/mcp.json.
5
+ * @throws Error if validation fails.
6
+ */
7
+ export declare function validateMcp(data: unknown): void;
@@ -8,10 +8,15 @@ exports.validateMcp = validateMcp;
8
8
  * @throws Error if validation fails.
9
9
  */
10
10
  function validateMcp(data) {
11
+ const mcpServers = data && typeof data === 'object'
12
+ ? data.mcpServers
13
+ : undefined;
11
14
  if (!data ||
12
15
  typeof data !== 'object' ||
13
16
  !('mcpServers' in data) ||
14
- typeof data.mcpServers !== 'object') {
17
+ !mcpServers ||
18
+ typeof mcpServers !== 'object' ||
19
+ Array.isArray(mcpServers)) {
15
20
  throw new Error('[ruler] Invalid MCP config: must contain an object property "mcpServers" (Ruler style)');
16
21
  }
17
22
  }
@@ -0,0 +1,8 @@
1
+ /** Determine the native MCP config path for a given agent. */
2
+ export declare function getNativeMcpPath(adapterName: string, projectRoot: string): Promise<string | null>;
3
+ /** Read native MCP config from disk, or return empty object if missing. */
4
+ export declare function readNativeMcp(filePath: string): Promise<Record<string, unknown>>;
5
+ /** Read native Codex TOML MCP config from disk, or return empty object if missing. */
6
+ export declare function readNativeMcpToml(filePath: string, parseToml: (text: string) => Record<string, unknown>): Promise<Record<string, unknown>>;
7
+ /** Write native MCP config to disk, creating parent directories as needed. */
8
+ export declare function writeNativeMcp(filePath: string, data: unknown): Promise<void>;
package/dist/paths/mcp.js CHANGED
@@ -35,6 +35,7 @@ var __importStar = (this && this.__importStar) || (function () {
35
35
  Object.defineProperty(exports, "__esModule", { value: true });
36
36
  exports.getNativeMcpPath = getNativeMcpPath;
37
37
  exports.readNativeMcp = readNativeMcp;
38
+ exports.readNativeMcpToml = readNativeMcpToml;
38
39
  exports.writeNativeMcp = writeNativeMcp;
39
40
  const path = __importStar(require("path"));
40
41
  const fs_1 = require("fs");
@@ -111,14 +112,42 @@ async function getNativeMcpPath(adapterName, projectRoot) {
111
112
  // default to first candidate if none exist
112
113
  return candidates.length > 0 ? candidates[0] : null;
113
114
  }
114
- /** Read native MCP config from disk, or return empty object if missing/invalid. */
115
+ /** Read native MCP config from disk, or return empty object if missing. */
115
116
  async function readNativeMcp(filePath) {
117
+ let text;
118
+ try {
119
+ text = await fs_1.promises.readFile(filePath, 'utf8');
120
+ }
121
+ catch (error) {
122
+ if (error.code === 'ENOENT') {
123
+ return {};
124
+ }
125
+ throw new Error(`Could not read MCP config at ${filePath}: ${error instanceof Error ? error.message : String(error)}`);
126
+ }
116
127
  try {
117
- const text = await fs_1.promises.readFile(filePath, 'utf8');
118
128
  return JSON.parse(text);
119
129
  }
120
- catch {
121
- return {};
130
+ catch (error) {
131
+ throw new Error(`Invalid MCP config at ${filePath}: ${error instanceof Error ? error.message : String(error)}`);
132
+ }
133
+ }
134
+ /** Read native Codex TOML MCP config from disk, or return empty object if missing. */
135
+ async function readNativeMcpToml(filePath, parseToml) {
136
+ let text;
137
+ try {
138
+ text = await fs_1.promises.readFile(filePath, 'utf8');
139
+ }
140
+ catch (error) {
141
+ if (error.code === 'ENOENT') {
142
+ return {};
143
+ }
144
+ throw new Error(`Could not read MCP config at ${filePath}: ${error instanceof Error ? error.message : String(error)}`);
145
+ }
146
+ try {
147
+ return parseToml(text);
148
+ }
149
+ catch (error) {
150
+ throw new Error(`Invalid MCP config at ${filePath}: ${error instanceof Error ? error.message : String(error)}`);
122
151
  }
123
152
  }
124
153
  /** Write native MCP config to disk, creating parent directories as needed. */
@@ -0,0 +1,6 @@
1
+ import { allAgents } from './agents';
2
+ export { allAgents };
3
+ /**
4
+ * Reverts ruler configurations for selected AI agents.
5
+ */
6
+ export declare function revertAllAgentConfigs(projectRoot: string, includedAgents?: string[], configPath?: string, keepBackups?: boolean, verbose?: boolean, dryRun?: boolean, localOnly?: boolean): Promise<void>;
package/dist/revert.js CHANGED
@@ -35,7 +35,6 @@ var __importStar = (this && this.__importStar) || (function () {
35
35
  Object.defineProperty(exports, "__esModule", { value: true });
36
36
  exports.allAgents = void 0;
37
37
  exports.revertAllAgentConfigs = revertAllAgentConfigs;
38
- const path = __importStar(require("path"));
39
38
  const fs_1 = require("fs");
40
39
  const FileSystemUtils = __importStar(require("./core/FileSystemUtils"));
41
40
  const ConfigLoader_1 = require("./core/ConfigLoader");
@@ -45,7 +44,11 @@ const constants_1 = require("./constants");
45
44
  const revert_engine_1 = require("./core/revert-engine");
46
45
  const agent_selection_1 = require("./core/agent-selection");
47
46
  const config_utils_1 = require("./core/config-utils");
47
+ const GitignoreUtils_1 = require("./core/GitignoreUtils");
48
48
  const agents = agents_1.allAgents;
49
+ const RULER_IGNORE_START_MARKER = '# START Ruler Generated Files';
50
+ const RULER_IGNORE_END_MARKER = '# END Ruler Generated Files';
51
+ const MANAGED_IGNORE_FILES = ['.gitignore', '.git/info/exclude'];
49
52
  /**
50
53
  * Reverts ruler configurations for selected AI agents.
51
54
  */
@@ -55,6 +58,7 @@ async function revertAllAgentConfigs(projectRoot, includedAgents, configPath, ke
55
58
  projectRoot,
56
59
  cliAgents: includedAgents,
57
60
  configPath,
61
+ checkGlobal: !localOnly,
58
62
  });
59
63
  const rulerDir = await FileSystemUtils.findRulerDir(projectRoot, !localOnly);
60
64
  if (!rulerDir) {
@@ -77,18 +81,19 @@ async function revertAllAgentConfigs(projectRoot, includedAgents, configPath, ke
77
81
  // Fall back to the old logic without validation
78
82
  if (config.cliAgents && config.cliAgents.length > 0) {
79
83
  const filters = config.cliAgents.map((n) => n.toLowerCase());
80
- selected = agents.filter((agent) => filters.some((f) => agent.getIdentifier() === f ||
81
- agent.getName().toLowerCase().includes(f)));
84
+ const validAgentIdentifiers = new Set(agents.map((agent) => agent.getIdentifier()));
85
+ selected = agents.filter((agent) => filters.some((f) => (0, agent_selection_1.agentMatchesFilter)(agent, f, validAgentIdentifiers)));
82
86
  }
83
87
  else if (config.defaultAgents && config.defaultAgents.length > 0) {
84
88
  const defaults = config.defaultAgents.map((n) => n.toLowerCase());
89
+ const validAgentIdentifiers = new Set(agents.map((agent) => agent.getIdentifier()));
85
90
  selected = agents.filter((agent) => {
86
91
  const identifier = agent.getIdentifier();
87
92
  const override = config.agentConfigs[identifier]?.enabled;
88
93
  if (override !== undefined) {
89
94
  return override;
90
95
  }
91
- return defaults.some((d) => identifier === d || agent.getName().toLowerCase().includes(d));
96
+ return defaults.some((d) => (0, agent_selection_1.agentMatchesFilter)(agent, d, validAgentIdentifiers));
92
97
  });
93
98
  }
94
99
  else {
@@ -118,10 +123,10 @@ async function revertAllAgentConfigs(projectRoot, includedAgents, configPath, ke
118
123
  // Clean up auxiliary files and directories
119
124
  const cleanupResult = await (0, revert_engine_1.cleanUpAuxiliaryFiles)(projectRoot, verbose, dryRun);
120
125
  totalFilesRemoved += cleanupResult.additionalFilesRemoved;
121
- // Clean .gitignore if reverting all agents
122
- const gitignoreCleaned = !config.cliAgents || config.cliAgents.length === 0
123
- ? await cleanGitignore(projectRoot, verbose, dryRun)
124
- : false;
126
+ // Clean managed ignore blocks if reverting all agents.
127
+ const cleanedIgnoreFiles = !config.cliAgents || config.cliAgents.length === 0
128
+ ? await cleanManagedIgnoreFiles(projectRoot, verbose, dryRun)
129
+ : [];
125
130
  // Display summary
126
131
  const prefix = (0, constants_1.actionPrefix)(dryRun);
127
132
  if (dryRun) {
@@ -139,47 +144,54 @@ async function revertAllAgentConfigs(projectRoot, includedAgents, configPath, ke
139
144
  if (cleanupResult.directoriesRemoved > 0) {
140
145
  console.log(` Empty directories removed: ${cleanupResult.directoriesRemoved}`);
141
146
  }
142
- if (gitignoreCleaned) {
143
- console.log(` .gitignore cleaned: yes`);
147
+ for (const ignoreFile of cleanedIgnoreFiles) {
148
+ console.log(` ${ignoreFile} cleaned: yes`);
144
149
  }
145
150
  }
146
151
  /**
147
- * Removes the ruler-managed block from .gitignore file.
152
+ * Removes the ruler-managed block from ignore files Ruler can update.
148
153
  */
149
- async function cleanGitignore(projectRoot, verbose, dryRun) {
150
- const gitignorePath = path.join(projectRoot, '.gitignore');
154
+ async function cleanManagedIgnoreFiles(projectRoot, verbose, dryRun) {
155
+ const cleanedFiles = [];
156
+ for (const ignoreFile of MANAGED_IGNORE_FILES) {
157
+ if (await cleanIgnoreFile(projectRoot, ignoreFile, verbose, dryRun)) {
158
+ cleanedFiles.push(ignoreFile);
159
+ }
160
+ }
161
+ return cleanedFiles;
162
+ }
163
+ async function cleanIgnoreFile(projectRoot, ignoreFile, verbose, dryRun) {
164
+ const ignorePath = await (0, GitignoreUtils_1.resolveIgnoreFilePath)(projectRoot, ignoreFile);
151
165
  try {
152
- await fs_1.promises.access(gitignorePath);
166
+ await fs_1.promises.access(ignorePath);
153
167
  }
154
168
  catch {
155
- (0, constants_1.logVerbose)('No .gitignore file found', verbose);
169
+ (0, constants_1.logVerbose)(`No ${ignoreFile} file found`, verbose);
156
170
  return false;
157
171
  }
158
- const content = await fs_1.promises.readFile(gitignorePath, 'utf8');
159
- const startMarker = '# START Ruler Generated Files';
160
- const endMarker = '# END Ruler Generated Files';
161
- const startIndex = content.indexOf(startMarker);
162
- const endIndex = content.indexOf(endMarker);
172
+ const content = await fs_1.promises.readFile(ignorePath, 'utf8');
173
+ const startIndex = content.indexOf(RULER_IGNORE_START_MARKER);
174
+ const endIndex = content.indexOf(RULER_IGNORE_END_MARKER);
163
175
  if (startIndex === -1 || endIndex === -1) {
164
- (0, constants_1.logVerbose)('No ruler-managed block found in .gitignore', verbose);
176
+ (0, constants_1.logVerbose)(`No ruler-managed block found in ${ignoreFile}`, verbose);
165
177
  return false;
166
178
  }
167
179
  const prefix = (0, constants_1.actionPrefix)(dryRun);
168
180
  if (dryRun) {
169
- (0, constants_1.logVerbose)(`${prefix} Would remove ruler block from .gitignore`, verbose);
181
+ (0, constants_1.logVerbose)(`${prefix} Would remove ruler block from ${ignoreFile}`, verbose);
170
182
  }
171
183
  else {
172
184
  const beforeBlock = content.substring(0, startIndex);
173
- const afterBlock = content.substring(endIndex + endMarker.length);
185
+ const afterBlock = content.substring(endIndex + RULER_IGNORE_END_MARKER.length);
174
186
  let newContent = beforeBlock + afterBlock;
175
187
  newContent = newContent.replace(/\n{3,}/g, '\n\n'); // Replace 3+ newlines with 2
176
188
  if (newContent.trim() === '') {
177
- await fs_1.promises.unlink(gitignorePath);
178
- (0, constants_1.logVerbose)(`${prefix} Removed empty .gitignore file`, verbose);
189
+ await fs_1.promises.unlink(ignorePath);
190
+ (0, constants_1.logVerbose)(`${prefix} Removed empty ${ignoreFile} file`, verbose);
179
191
  }
180
192
  else {
181
- await fs_1.promises.writeFile(gitignorePath, newContent);
182
- (0, constants_1.logVerbose)(`${prefix} Removed ruler block from .gitignore`, verbose);
193
+ await fs_1.promises.writeFile(ignorePath, newContent);
194
+ (0, constants_1.logVerbose)(`${prefix} Removed ruler block from ${ignoreFile}`, verbose);
183
195
  }
184
196
  }
185
197
  return true;
@@ -0,0 +1,87 @@
1
+ /**
2
+ * Types for Model Context Protocol (MCP) server configuration.
3
+ */
4
+ export type McpStrategy = 'merge' | 'overwrite';
5
+ /** MCP configuration for an agent or global. */
6
+ export interface McpConfig {
7
+ /** Enable or disable MCP propagation (merge or overwrite). */
8
+ enabled?: boolean;
9
+ /** Merge strategy: 'merge' to merge servers, 'overwrite' to replace config. */
10
+ strategy?: McpStrategy;
11
+ }
12
+ /** Global MCP configuration section (same as agent-specific config). */
13
+ export type GlobalMcpConfig = McpConfig;
14
+ /** Gitignore configuration for automatic .gitignore file updates. */
15
+ export interface GitignoreConfig {
16
+ /** Enable or disable automatic .gitignore updates. */
17
+ enabled?: boolean;
18
+ /** Write managed ignore entries to .git/info/exclude instead of .gitignore. */
19
+ local?: boolean;
20
+ }
21
+ /** Backup configuration for .bak file generation. */
22
+ export interface BackupConfig {
23
+ /** Enable or disable creation of .bak backup files. */
24
+ enabled?: boolean;
25
+ }
26
+ /** Skills configuration for automatic skills distribution. */
27
+ export interface SkillsConfig {
28
+ /** Enable or disable skills support. */
29
+ enabled?: boolean;
30
+ }
31
+ /** Information about a discovered skill. */
32
+ export interface SkillInfo {
33
+ /** Name of the skill (directory name). */
34
+ name: string;
35
+ /** Absolute path to the skill directory. */
36
+ path: string;
37
+ /** Whether the directory contains a SKILL.md file. */
38
+ hasSkillMd: boolean;
39
+ /** Whether this is a valid skill. */
40
+ valid: boolean;
41
+ /** Error message if invalid. */
42
+ error?: string;
43
+ }
44
+ /** Subagents configuration for automatic subagent distribution. */
45
+ export interface SubagentsConfig {
46
+ /** Enable or disable subagents support. */
47
+ enabled?: boolean;
48
+ /**
49
+ * When true, Ruler may delete previously generated native subagent
50
+ * directories that are stale (disabled, no source definitions, or
51
+ * deselected targets). Defaults to false (non-destructive).
52
+ */
53
+ cleanup_orphaned?: boolean;
54
+ /**
55
+ * When true, `.ruler/agents/*.md` are also concatenated into the
56
+ * generated top-level rule files (CLAUDE.md, AGENTS.md, Copilot
57
+ * instructions, etc.). When false (default), `.ruler/agents/` is
58
+ * skipped during rule concatenation, mirroring `.ruler/skills/`.
59
+ */
60
+ include_in_rules?: boolean;
61
+ }
62
+ /** Frontmatter fields recognised on a source subagent definition. */
63
+ export interface SubagentFrontmatter {
64
+ name: string;
65
+ description: string;
66
+ tools?: string[];
67
+ model?: string;
68
+ readonly?: boolean;
69
+ is_background?: boolean;
70
+ }
71
+ /** Information about a discovered subagent. */
72
+ export interface SubagentInfo {
73
+ /** Name of the subagent (filename stem and frontmatter `name`). */
74
+ name: string;
75
+ /** Absolute path to the source `.md` file. */
76
+ path: string;
77
+ /** Relative `.md` path under `.ruler/agents/` (preserves nested layout). */
78
+ sourceRelativePath?: string;
79
+ /** Parsed frontmatter (only present when valid). */
80
+ frontmatter?: SubagentFrontmatter;
81
+ /** Body content after the frontmatter delimiter. */
82
+ body?: string;
83
+ /** Whether this subagent passed validation. */
84
+ valid: boolean;
85
+ /** Error message if invalid. */
86
+ error?: string;
87
+ }
@@ -0,0 +1,40 @@
1
+ import { McpStrategy } from '../types';
2
+ /**
3
+ * VSCode settings.json structure for Augment MCP configuration
4
+ */
5
+ export interface VSCodeSettings {
6
+ 'augment.advanced'?: {
7
+ mcpServers?: AugmentMcpServer[];
8
+ [key: string]: unknown;
9
+ };
10
+ [key: string]: unknown;
11
+ }
12
+ /**
13
+ * Augment MCP server configuration format
14
+ */
15
+ export interface AugmentMcpServer {
16
+ name: string;
17
+ command: string;
18
+ args?: string[];
19
+ env?: Record<string, string>;
20
+ }
21
+ /**
22
+ * Read VSCode settings.json file
23
+ */
24
+ export declare function readVSCodeSettings(settingsPath: string): Promise<VSCodeSettings>;
25
+ /**
26
+ * Write VSCode settings.json file
27
+ */
28
+ export declare function writeVSCodeSettings(settingsPath: string, settings: VSCodeSettings): Promise<void>;
29
+ /**
30
+ * Transform ruler MCP config to Augment MCP server array format
31
+ */
32
+ export declare function transformRulerToAugmentMcp(rulerMcpJson: Record<string, unknown>): AugmentMcpServer[];
33
+ /**
34
+ * Merge MCP servers into VSCode settings using the specified strategy
35
+ */
36
+ export declare function mergeAugmentMcpServers(existingSettings: VSCodeSettings, newServers: AugmentMcpServer[], strategy: McpStrategy): VSCodeSettings;
37
+ /**
38
+ * Get the VSCode settings.json path for a project (local)
39
+ */
40
+ export declare function getVSCodeSettingsPath(projectRoot: string): string;
package/package.json CHANGED
@@ -1,12 +1,14 @@
1
1
  {
2
2
  "name": "@intellectronica/ruler",
3
- "version": "0.3.40",
3
+ "version": "0.3.42",
4
4
  "description": "Ruler — apply the same rules to all coding agents",
5
5
  "main": "dist/lib.js",
6
+ "types": "dist/lib.d.ts",
6
7
  "scripts": {
7
8
  "lint": "eslint \"src/**/*.{ts,tsx}\"",
8
- "format": "prettier --write \"src/**/*.{ts,tsx,json,md}\"",
9
- "test": "jest",
9
+ "format": "prettier --write package.json package-lock.json tsconfig.json README.md \".github/**/*.yml\" \"src/**/*.{ts,tsx,json,md}\" \"tests/**/*.{ts,tsx,json,md}\"",
10
+ "format:check": "prettier --check package.json package-lock.json tsconfig.json README.md \".github/**/*.yml\" \"src/**/*.{ts,tsx,json,md}\" \"tests/**/*.{ts,tsx,json,md}\"",
11
+ "test": "jest --coverage",
10
12
  "test:watch": "jest --watch",
11
13
  "test:coverage": "jest --coverage",
12
14
  "test:integration": "jest tests/e2e/ruler.integration.test.ts --verbose",
@@ -58,6 +60,7 @@
58
60
  "eslint-config-prettier": "^10.1.8",
59
61
  "eslint-plugin-prettier": "^5.5.4",
60
62
  "jest": "^29.7.0",
63
+ "jest-util": "^29.7.0",
61
64
  "prettier": "^3.6.2",
62
65
  "ts-jest": "^29.4.5",
63
66
  "typescript": "^5.9.3",
@@ -65,7 +68,7 @@
65
68
  },
66
69
  "dependencies": {
67
70
  "@iarna/toml": "^2.2.5",
68
- "js-yaml": "^4.1.0",
71
+ "js-yaml": "^4.1.1",
69
72
  "yargs": "^18.0.0",
70
73
  "zod": "^4.1.12"
71
74
  }