@intellectronica/ruler 0.3.41 → 0.3.43

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 (105) hide show
  1. package/README.md +135 -36
  2. package/dist/agents/AbstractAgent.d.ts +53 -0
  3. package/dist/agents/AbstractAgent.js +3 -2
  4. package/dist/agents/AgentsMdAgent.d.ts +14 -0
  5. package/dist/agents/AgentsMdAgent.js +3 -2
  6. package/dist/agents/AiderAgent.d.ts +14 -0
  7. package/dist/agents/AiderAgent.js +7 -4
  8. package/dist/agents/AmazonQCliAgent.d.ts +13 -0
  9. package/dist/agents/AmazonQCliAgent.js +6 -4
  10. package/dist/agents/AmpAgent.d.ts +6 -0
  11. package/dist/agents/AntigravityAgent.d.ts +10 -0
  12. package/dist/agents/AugmentCodeAgent.d.ts +13 -0
  13. package/dist/agents/AugmentCodeAgent.js +3 -2
  14. package/dist/agents/ClaudeAgent.d.ts +13 -0
  15. package/dist/agents/ClineAgent.d.ts +9 -0
  16. package/dist/agents/CodexCliAgent.d.ts +31 -0
  17. package/dist/agents/CodexCliAgent.js +1 -1
  18. package/dist/agents/CopilotAgent.d.ts +20 -0
  19. package/dist/agents/CrushAgent.d.ts +14 -0
  20. package/dist/agents/CrushAgent.js +18 -6
  21. package/dist/agents/CursorAgent.d.ts +17 -0
  22. package/dist/agents/FactoryDroidAgent.d.ts +13 -0
  23. package/dist/agents/FirebaseAgent.d.ts +11 -0
  24. package/dist/agents/FirebenderAgent.d.ts +36 -0
  25. package/dist/agents/FirebenderAgent.js +5 -4
  26. package/dist/agents/GeminiCliAgent.d.ts +12 -0
  27. package/dist/agents/GeminiCliAgent.js +13 -7
  28. package/dist/agents/GooseAgent.d.ts +12 -0
  29. package/dist/agents/IAgent.d.ts +74 -0
  30. package/dist/agents/JetBrainsAiAssistantAgent.d.ts +10 -0
  31. package/dist/agents/JulesAgent.d.ts +5 -0
  32. package/dist/agents/JunieAgent.d.ts +12 -0
  33. package/dist/agents/KiloCodeAgent.d.ts +14 -0
  34. package/dist/agents/KiroAgent.d.ts +8 -0
  35. package/dist/agents/MistralVibeAgent.d.ts +31 -0
  36. package/dist/agents/MistralVibeAgent.js +14 -3
  37. package/dist/agents/OpenCodeAgent.d.ts +11 -0
  38. package/dist/agents/OpenCodeAgent.js +24 -12
  39. package/dist/agents/OpenHandsAgent.d.ts +8 -0
  40. package/dist/agents/PiAgent.d.ts +9 -0
  41. package/dist/agents/QwenCodeAgent.d.ts +11 -0
  42. package/dist/agents/QwenCodeAgent.js +11 -5
  43. package/dist/agents/RooCodeAgent.d.ts +16 -0
  44. package/dist/agents/RooCodeAgent.js +3 -2
  45. package/dist/agents/TraeAgent.d.ts +10 -0
  46. package/dist/agents/WarpAgent.d.ts +12 -0
  47. package/dist/agents/WindsurfAgent.d.ts +13 -0
  48. package/dist/agents/ZedAgent.d.ts +21 -0
  49. package/dist/agents/ZedAgent.js +8 -5
  50. package/dist/agents/agent-utils.d.ts +5 -0
  51. package/dist/agents/agent-utils.js +8 -5
  52. package/dist/agents/index.d.ts +9 -0
  53. package/dist/cli/commands.d.ts +4 -0
  54. package/dist/cli/commands.js +1 -2
  55. package/dist/cli/handlers.d.ts +41 -0
  56. package/dist/cli/handlers.js +75 -59
  57. package/dist/cli/index.d.ts +2 -0
  58. package/dist/constants.d.ts +35 -0
  59. package/dist/constants.js +1 -1
  60. package/dist/core/ConfigLoader.d.ts +59 -0
  61. package/dist/core/ConfigLoader.js +178 -44
  62. package/dist/core/FileSystemUtils.d.ts +53 -0
  63. package/dist/core/FileSystemUtils.js +157 -20
  64. package/dist/core/GitignoreUtils.d.ts +25 -0
  65. package/dist/core/GitignoreUtils.js +94 -32
  66. package/dist/core/RuleProcessor.d.ts +8 -0
  67. package/dist/core/SkillsProcessor.d.ts +127 -0
  68. package/dist/core/SkillsProcessor.js +118 -223
  69. package/dist/core/SkillsUtils.d.ts +26 -0
  70. package/dist/core/SubagentsProcessor.d.ts +38 -0
  71. package/dist/core/SubagentsProcessor.js +8 -5
  72. package/dist/core/SubagentsUtils.d.ts +34 -0
  73. package/dist/core/UnifiedConfigLoader.d.ts +10 -0
  74. package/dist/core/UnifiedConfigLoader.js +115 -33
  75. package/dist/core/UnifiedConfigTypes.d.ts +97 -0
  76. package/dist/core/agent-selection.d.ts +12 -0
  77. package/dist/core/agent-selection.js +17 -7
  78. package/dist/core/apply-engine.d.ts +70 -0
  79. package/dist/core/apply-engine.js +88 -58
  80. package/dist/core/config-utils.d.ts +14 -0
  81. package/dist/core/config-utils.js +9 -3
  82. package/dist/core/hash.d.ts +2 -0
  83. package/dist/core/path-utils.d.ts +1 -0
  84. package/dist/core/path-utils.js +42 -0
  85. package/dist/core/revert-engine.d.ts +37 -0
  86. package/dist/core/revert-engine.js +142 -34
  87. package/dist/lib.d.ts +13 -0
  88. package/dist/lib.js +24 -8
  89. package/dist/mcp/capabilities.d.ts +20 -0
  90. package/dist/mcp/merge.d.ts +10 -0
  91. package/dist/mcp/merge.js +36 -16
  92. package/dist/mcp/propagateOpenCodeMcp.d.ts +2 -0
  93. package/dist/mcp/propagateOpenCodeMcp.js +30 -11
  94. package/dist/mcp/propagateOpenHandsMcp.d.ts +2 -0
  95. package/dist/mcp/propagateOpenHandsMcp.js +48 -21
  96. package/dist/mcp/validate.d.ts +7 -0
  97. package/dist/mcp/validate.js +6 -1
  98. package/dist/paths/mcp.d.ts +8 -0
  99. package/dist/paths/mcp.js +44 -8
  100. package/dist/revert.d.ts +6 -0
  101. package/dist/revert.js +58 -46
  102. package/dist/types.d.ts +87 -0
  103. package/dist/vscode/settings.d.ts +40 -0
  104. package/dist/vscode/settings.js +3 -3
  105. package/package.json +8 -5
@@ -34,8 +34,12 @@ var __importStar = (this && this.__importStar) || (function () {
34
34
  })();
35
35
  Object.defineProperty(exports, "__esModule", { value: true });
36
36
  exports.updateGitignore = updateGitignore;
37
+ exports.resolveIgnoreFilePath = resolveIgnoreFilePath;
38
+ exports.findCompleteRulerBlocks = findCompleteRulerBlocks;
39
+ exports.removeCompleteRulerBlocks = removeCompleteRulerBlocks;
37
40
  const fs_1 = require("fs");
38
41
  const path = __importStar(require("path"));
42
+ const FileSystemUtils_1 = require("./FileSystemUtils");
39
43
  const RULER_START_MARKER = '# START Ruler Generated Files';
40
44
  const RULER_END_MARKER = '# END Ruler Generated Files';
41
45
  /**
@@ -47,7 +51,7 @@ const RULER_END_MARKER = '# END Ruler Generated Files';
47
51
  * @param ignoreFile Relative path to the ignore file from project root (defaults to .gitignore)
48
52
  */
49
53
  async function updateGitignore(projectRoot, paths, ignoreFile = '.gitignore') {
50
- const gitignorePath = path.join(projectRoot, ignoreFile);
54
+ const gitignorePath = await resolveIgnoreFilePath(projectRoot, ignoreFile);
51
55
  // Read existing .gitignore or start with empty content
52
56
  let existingContent = '';
53
57
  try {
@@ -98,64 +102,122 @@ async function updateGitignore(projectRoot, paths, ignoreFile = '.gitignore') {
98
102
  // Create new content
99
103
  const newContent = updateGitignoreContent(existingContent, allRulerPaths);
100
104
  // Write the updated content
101
- await fs_1.promises.mkdir(path.dirname(gitignorePath), { recursive: true });
102
- await fs_1.promises.writeFile(gitignorePath, newContent);
105
+ await (0, FileSystemUtils_1.writeGeneratedFile)(gitignorePath, newContent);
106
+ }
107
+ /**
108
+ * Resolves ignore files Ruler manages. Linked worktrees store `.git` as a
109
+ * file containing a `gitdir:` pointer, so `.git/info/exclude` must be resolved
110
+ * through that pointer.
111
+ */
112
+ async function resolveIgnoreFilePath(projectRoot, ignoreFile) {
113
+ if (ignoreFile !== '.git/info/exclude') {
114
+ return path.join(projectRoot, ignoreFile);
115
+ }
116
+ const dotGitPath = path.join(projectRoot, '.git');
117
+ try {
118
+ const dotGitStat = await fs_1.promises.lstat(dotGitPath);
119
+ if (dotGitStat.isFile()) {
120
+ const dotGitContent = await fs_1.promises.readFile(dotGitPath, 'utf8');
121
+ const gitDirMatch = dotGitContent.match(/^gitdir:\s*(.+)\s*$/m);
122
+ if (gitDirMatch) {
123
+ const gitDir = gitDirMatch[1];
124
+ const resolvedGitDir = path.isAbsolute(gitDir)
125
+ ? gitDir
126
+ : path.resolve(projectRoot, gitDir);
127
+ return path.join(resolvedGitDir, 'info', 'exclude');
128
+ }
129
+ }
130
+ }
131
+ catch {
132
+ // Fall back to the historical project-root path for non-git test fixtures
133
+ // and unusual repositories where `.git` cannot be inspected.
134
+ }
135
+ return path.join(projectRoot, ignoreFile);
103
136
  }
104
137
  /**
105
138
  * Gets all paths from .gitignore content excluding those in the Ruler block.
106
139
  */
107
140
  function getExistingPathsExcludingRulerBlock(content) {
108
141
  const lines = content.split('\n');
142
+ const rulerBlocks = findCompleteRulerBlocks(lines);
109
143
  const paths = [];
110
- let inRulerBlock = false;
111
- for (const line of lines) {
112
- const trimmed = line.trim();
113
- if (trimmed === RULER_START_MARKER) {
114
- inRulerBlock = true;
115
- continue;
116
- }
117
- if (trimmed === RULER_END_MARKER) {
118
- inRulerBlock = false;
144
+ for (const [index, line] of lines.entries()) {
145
+ if (rulerBlocks.some((block) => index >= block.start && index <= block.end)) {
119
146
  continue;
120
147
  }
121
- if (!inRulerBlock && trimmed && !trimmed.startsWith('#')) {
148
+ const trimmed = line.trim();
149
+ if (trimmed && !trimmed.startsWith('#')) {
122
150
  paths.push(trimmed);
123
151
  }
124
152
  }
125
153
  return paths;
126
154
  }
155
+ function findCompleteRulerBlocks(lines) {
156
+ const ranges = [];
157
+ for (let index = 0; index < lines.length; index++) {
158
+ if (lines[index].trim() !== RULER_START_MARKER) {
159
+ continue;
160
+ }
161
+ for (let endIndex = index + 1; endIndex < lines.length; endIndex++) {
162
+ const trimmed = lines[endIndex].trim();
163
+ if (trimmed === RULER_START_MARKER) {
164
+ break;
165
+ }
166
+ if (trimmed === RULER_END_MARKER) {
167
+ ranges.push({ start: index, end: endIndex });
168
+ index = endIndex;
169
+ break;
170
+ }
171
+ }
172
+ }
173
+ return ranges;
174
+ }
175
+ function removeCompleteRulerBlocks(content) {
176
+ const lines = content.split('\n');
177
+ const rulerBlocks = findCompleteRulerBlocks(lines);
178
+ if (rulerBlocks.length === 0) {
179
+ return { content, removed: false };
180
+ }
181
+ const retainedLines = [];
182
+ for (let index = 0; index < lines.length; index++) {
183
+ const block = rulerBlocks.find((range) => range.start === index);
184
+ if (block) {
185
+ index = block.end;
186
+ continue;
187
+ }
188
+ retainedLines.push(lines[index]);
189
+ }
190
+ return {
191
+ content: retainedLines.join('\n').replace(/\n{3,}/g, '\n\n'),
192
+ removed: true,
193
+ };
194
+ }
127
195
  /**
128
196
  * Updates the .gitignore content by replacing or adding the Ruler block.
129
197
  */
130
198
  function updateGitignoreContent(existingContent, rulerPaths) {
131
199
  const lines = existingContent.split('\n');
200
+ const rulerBlocks = findCompleteRulerBlocks(lines);
132
201
  const newLines = [];
133
- let inFirstRulerBlock = false;
134
- let hasRulerBlock = false;
135
- let processedFirstBlock = false;
136
- for (const line of lines) {
137
- const trimmed = line.trim();
138
- if (trimmed === RULER_START_MARKER && !processedFirstBlock) {
139
- inFirstRulerBlock = true;
140
- hasRulerBlock = true;
141
- newLines.push(line);
142
- // Add the new Ruler paths
202
+ let replacedFirstBlock = false;
203
+ for (let index = 0; index < lines.length; index++) {
204
+ const block = rulerBlocks.find((range) => range.start === index);
205
+ if (block && !replacedFirstBlock) {
206
+ newLines.push(lines[block.start]);
143
207
  rulerPaths.forEach((p) => newLines.push(p));
208
+ newLines.push(lines[block.end]);
209
+ replacedFirstBlock = true;
210
+ index = block.end;
144
211
  continue;
145
212
  }
146
- if (trimmed === RULER_END_MARKER && inFirstRulerBlock) {
147
- inFirstRulerBlock = false;
148
- processedFirstBlock = true;
149
- newLines.push(line);
213
+ if (block) {
214
+ index = block.end;
150
215
  continue;
151
216
  }
152
- if (!inFirstRulerBlock) {
153
- newLines.push(line);
154
- }
155
- // Skip lines that are in the first Ruler block (they get replaced)
217
+ newLines.push(lines[index]);
156
218
  }
157
219
  // If no Ruler block exists, add one at the end
158
- if (!hasRulerBlock) {
220
+ if (rulerBlocks.length === 0) {
159
221
  // Add blank line if content exists and doesn't end with blank line
160
222
  if (existingContent.trim() && !existingContent.endsWith('\n\n')) {
161
223
  newLines.push('');
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Concatenates markdown rule files into a single string,
3
+ * marking each section with its source filename.
4
+ */
5
+ export declare function concatenateRules(files: {
6
+ path: string;
7
+ content: string;
8
+ }[], baseDir?: string): string;
@@ -0,0 +1,127 @@
1
+ import * as fs from 'fs/promises';
2
+ import { SkillInfo } from '../types';
3
+ import type { IAgent } from '../agents/IAgent';
4
+ /**
5
+ * Discovers skills in the project's .ruler/skills directory.
6
+ * Returns discovered skills and any validation warnings.
7
+ */
8
+ export declare function discoverSkills(projectRoot: string): Promise<{
9
+ skills: SkillInfo[];
10
+ warnings: string[];
11
+ }>;
12
+ /**
13
+ * Gets the paths that skills will generate, for gitignore purposes.
14
+ * Returns empty array if skills directory doesn't exist.
15
+ */
16
+ export declare function getSkillsGitignorePaths(projectRoot: string, agents: IAgent[]): Promise<string[]>;
17
+ type ReplaceSkillsFsOps = Pick<typeof fs, 'rename' | 'cp' | 'rm'>;
18
+ export declare function replaceSkillsDirectory(tempDir: string, targetDir: string, fsOps?: ReplaceSkillsFsOps, containmentRoot?: string): Promise<void>;
19
+ /**
20
+ * Propagates skills for agents that need them.
21
+ */
22
+ export declare function propagateSkills(projectRoot: string, agents: IAgent[], skillsEnabled: boolean, verbose: boolean, dryRun: boolean): Promise<void>;
23
+ /**
24
+ * Propagates skills for Claude Code by copying .ruler/skills to .claude/skills.
25
+ * Uses atomic replace to ensure safe overwriting of existing skills.
26
+ * Returns dry-run steps if dryRun is true, otherwise returns empty array.
27
+ */
28
+ export declare function propagateSkillsForClaude(projectRoot: string, options: {
29
+ dryRun: boolean;
30
+ }): Promise<string[]>;
31
+ /**
32
+ * Propagates skills for OpenAI Codex CLI by copying .ruler/skills to .agents/skills.
33
+ * Uses atomic replace to ensure safe overwriting of existing skills.
34
+ * Returns dry-run steps if dryRun is true, otherwise returns empty array.
35
+ */
36
+ export declare function propagateSkillsForCodex(projectRoot: string, options: {
37
+ dryRun: boolean;
38
+ }): Promise<string[]>;
39
+ /**
40
+ * Propagates skills for OpenCode by copying .ruler/skills to .opencode/skills.
41
+ * Uses atomic replace to ensure safe overwriting of existing skills.
42
+ * Returns dry-run steps if dryRun is true, otherwise returns empty array.
43
+ */
44
+ export declare function propagateSkillsForOpenCode(projectRoot: string, options: {
45
+ dryRun: boolean;
46
+ }): Promise<string[]>;
47
+ /**
48
+ * Propagates skills for Pi Coding Agent by copying .ruler/skills to .pi/skills.
49
+ * Uses atomic replace to ensure safe overwriting of existing skills.
50
+ * Returns dry-run steps if dryRun is true, otherwise returns empty array.
51
+ */
52
+ export declare function propagateSkillsForPi(projectRoot: string, options: {
53
+ dryRun: boolean;
54
+ }): Promise<string[]>;
55
+ /**
56
+ * Propagates skills for Goose by copying .ruler/skills to .agents/skills.
57
+ * Uses atomic replace to ensure safe overwriting of existing skills.
58
+ * Returns dry-run steps if dryRun is true, otherwise returns empty array.
59
+ */
60
+ export declare function propagateSkillsForGoose(projectRoot: string, options: {
61
+ dryRun: boolean;
62
+ }): Promise<string[]>;
63
+ /**
64
+ * Propagates skills for Mistral Vibe by copying .ruler/skills to .vibe/skills.
65
+ * Uses atomic replace to ensure safe overwriting of existing skills.
66
+ * Returns dry-run steps if dryRun is true, otherwise returns empty array.
67
+ */
68
+ export declare function propagateSkillsForVibe(projectRoot: string, options: {
69
+ dryRun: boolean;
70
+ }): Promise<string[]>;
71
+ /**
72
+ * Propagates skills for Roo Code by copying .ruler/skills to .roo/skills.
73
+ * Uses atomic replace to ensure safe overwriting of existing skills.
74
+ * Returns dry-run steps if dryRun is true, otherwise returns empty array.
75
+ */
76
+ export declare function propagateSkillsForRoo(projectRoot: string, options: {
77
+ dryRun: boolean;
78
+ }): Promise<string[]>;
79
+ /**
80
+ * Propagates skills for Gemini CLI by copying .ruler/skills to .gemini/skills.
81
+ * Uses atomic replace to ensure safe overwriting of existing skills.
82
+ * Returns dry-run steps if dryRun is true, otherwise returns empty array.
83
+ */
84
+ export declare function propagateSkillsForGemini(projectRoot: string, options: {
85
+ dryRun: boolean;
86
+ }): Promise<string[]>;
87
+ /**
88
+ * Propagates skills for Junie by copying .ruler/skills to .junie/skills.
89
+ * Uses atomic replace to ensure safe overwriting of existing skills.
90
+ * Returns dry-run steps if dryRun is true, otherwise returns empty array.
91
+ */
92
+ export declare function propagateSkillsForJunie(projectRoot: string, options: {
93
+ dryRun: boolean;
94
+ }): Promise<string[]>;
95
+ /**
96
+ * Propagates skills for Cursor by copying .ruler/skills to .cursor/skills.
97
+ * Uses atomic replace to ensure safe overwriting of existing skills.
98
+ * Returns dry-run steps if dryRun is true, otherwise returns empty array.
99
+ */
100
+ export declare function propagateSkillsForCursor(projectRoot: string, options: {
101
+ dryRun: boolean;
102
+ }): Promise<string[]>;
103
+ /**
104
+ * Propagates skills for Windsurf by copying .ruler/skills to .windsurf/skills.
105
+ * Uses atomic replace to ensure safe overwriting of existing skills.
106
+ * Returns dry-run steps if dryRun is true, otherwise returns empty array.
107
+ */
108
+ export declare function propagateSkillsForWindsurf(projectRoot: string, options: {
109
+ dryRun: boolean;
110
+ }): Promise<string[]>;
111
+ /**
112
+ * Propagates skills for Factory Droid by copying .ruler/skills to .factory/skills.
113
+ * Uses atomic replace to ensure safe overwriting of existing skills.
114
+ * Returns dry-run steps if dryRun is true, otherwise returns empty array.
115
+ */
116
+ export declare function propagateSkillsForFactory(projectRoot: string, options: {
117
+ dryRun: boolean;
118
+ }): Promise<string[]>;
119
+ /**
120
+ * Propagates skills for Antigravity by copying .ruler/skills to .agent/skills.
121
+ * Uses atomic replace to ensure safe overwriting of existing skills.
122
+ * Returns dry-run steps if dryRun is true, otherwise returns empty array.
123
+ */
124
+ export declare function propagateSkillsForAntigravity(projectRoot: string, options: {
125
+ dryRun: boolean;
126
+ }): Promise<string[]>;
127
+ export {};