skiller 0.4.3

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 (62) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +989 -0
  3. package/dist/agents/AbstractAgent.js +92 -0
  4. package/dist/agents/AgentsMdAgent.js +85 -0
  5. package/dist/agents/AiderAgent.js +108 -0
  6. package/dist/agents/AmazonQCliAgent.js +103 -0
  7. package/dist/agents/AmpAgent.js +13 -0
  8. package/dist/agents/AugmentCodeAgent.js +70 -0
  9. package/dist/agents/ClaudeAgent.js +95 -0
  10. package/dist/agents/ClineAgent.js +53 -0
  11. package/dist/agents/CodexCliAgent.js +143 -0
  12. package/dist/agents/CopilotAgent.js +43 -0
  13. package/dist/agents/CrushAgent.js +128 -0
  14. package/dist/agents/CursorAgent.js +93 -0
  15. package/dist/agents/FirebaseAgent.js +61 -0
  16. package/dist/agents/FirebenderAgent.js +205 -0
  17. package/dist/agents/GeminiCliAgent.js +99 -0
  18. package/dist/agents/GooseAgent.js +58 -0
  19. package/dist/agents/IAgent.js +2 -0
  20. package/dist/agents/JulesAgent.js +14 -0
  21. package/dist/agents/JunieAgent.js +53 -0
  22. package/dist/agents/KiloCodeAgent.js +63 -0
  23. package/dist/agents/KiroAgent.js +50 -0
  24. package/dist/agents/OpenCodeAgent.js +99 -0
  25. package/dist/agents/OpenHandsAgent.js +56 -0
  26. package/dist/agents/QwenCodeAgent.js +82 -0
  27. package/dist/agents/RooCodeAgent.js +139 -0
  28. package/dist/agents/TraeAgent.js +54 -0
  29. package/dist/agents/WarpAgent.js +61 -0
  30. package/dist/agents/WindsurfAgent.js +27 -0
  31. package/dist/agents/ZedAgent.js +132 -0
  32. package/dist/agents/agent-utils.js +37 -0
  33. package/dist/agents/index.js +77 -0
  34. package/dist/cli/commands.js +136 -0
  35. package/dist/cli/handlers.js +221 -0
  36. package/dist/cli/index.js +5 -0
  37. package/dist/constants.js +58 -0
  38. package/dist/core/ConfigLoader.js +274 -0
  39. package/dist/core/FileSystemUtils.js +421 -0
  40. package/dist/core/FrontmatterParser.js +142 -0
  41. package/dist/core/GitignoreUtils.js +171 -0
  42. package/dist/core/RuleProcessor.js +60 -0
  43. package/dist/core/SkillsProcessor.js +528 -0
  44. package/dist/core/SkillsUtils.js +230 -0
  45. package/dist/core/UnifiedConfigLoader.js +432 -0
  46. package/dist/core/UnifiedConfigTypes.js +2 -0
  47. package/dist/core/agent-selection.js +52 -0
  48. package/dist/core/apply-engine.js +668 -0
  49. package/dist/core/config-utils.js +30 -0
  50. package/dist/core/hash.js +24 -0
  51. package/dist/core/revert-engine.js +413 -0
  52. package/dist/lib.js +196 -0
  53. package/dist/mcp/capabilities.js +65 -0
  54. package/dist/mcp/merge.js +39 -0
  55. package/dist/mcp/propagateOpenCodeMcp.js +116 -0
  56. package/dist/mcp/propagateOpenHandsMcp.js +169 -0
  57. package/dist/mcp/validate.js +17 -0
  58. package/dist/paths/mcp.js +120 -0
  59. package/dist/revert.js +186 -0
  60. package/dist/types.js +2 -0
  61. package/dist/vscode/settings.js +117 -0
  62. package/package.json +77 -0
@@ -0,0 +1,143 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.CodexCliAgent = void 0;
37
+ const path = __importStar(require("path"));
38
+ const fs_1 = require("fs");
39
+ const toml_1 = require("@iarna/toml");
40
+ const AgentsMdAgent_1 = require("./AgentsMdAgent");
41
+ const FileSystemUtils_1 = require("../core/FileSystemUtils");
42
+ const constants_1 = require("../constants");
43
+ /**
44
+ * OpenAI Codex CLI agent adapter.
45
+ */
46
+ class CodexCliAgent {
47
+ constructor() {
48
+ this.agentsMdAgent = new AgentsMdAgent_1.AgentsMdAgent();
49
+ }
50
+ getIdentifier() {
51
+ return 'codex';
52
+ }
53
+ getName() {
54
+ return 'OpenAI Codex CLI';
55
+ }
56
+ async applyRulerConfig(concatenatedRules, projectRoot, rulerMcpJson, agentConfig, backup = true) {
57
+ // First perform idempotent AGENTS.md write via composed AgentsMdAgent
58
+ await this.agentsMdAgent.applyRulerConfig(concatenatedRules, projectRoot, null, {
59
+ // Preserve explicit outputPath precedence semantics if provided.
60
+ outputPath: agentConfig?.outputPath ||
61
+ agentConfig?.outputPathInstructions ||
62
+ undefined,
63
+ }, backup);
64
+ // Use proper path resolution from getDefaultOutputPath and agentConfig
65
+ const defaults = this.getDefaultOutputPath(projectRoot);
66
+ const mcpEnabled = agentConfig?.mcp?.enabled ?? true;
67
+ if (mcpEnabled && rulerMcpJson) {
68
+ // Apply MCP server filtering and transformation
69
+ const { filterMcpConfigForAgent } = await Promise.resolve().then(() => __importStar(require('../mcp/capabilities')));
70
+ const filteredMcpConfig = filterMcpConfigForAgent(rulerMcpJson, this);
71
+ if (!filteredMcpConfig) {
72
+ return; // No compatible servers found
73
+ }
74
+ const filteredRulerMcpJson = filteredMcpConfig;
75
+ // Determine the config file path using proper precedence
76
+ const configPath = agentConfig?.outputPathConfig ?? defaults.config;
77
+ // Ensure the parent directory exists
78
+ await fs_1.promises.mkdir(path.dirname(configPath), { recursive: true });
79
+ // Get the merge strategy
80
+ const strategy = agentConfig?.mcp?.strategy ?? 'merge';
81
+ // Extract MCP servers from filtered ruler config
82
+ const rulerServers = filteredRulerMcpJson.mcpServers || {};
83
+ // Read existing TOML config if it exists
84
+ let existingConfig = {};
85
+ try {
86
+ const existingContent = await fs_1.promises.readFile(configPath, 'utf8');
87
+ existingConfig = (0, toml_1.parse)(existingContent);
88
+ }
89
+ catch {
90
+ // File doesn't exist or can't be parsed, use empty config
91
+ }
92
+ // Create the updated config
93
+ const updatedConfig = { ...existingConfig };
94
+ // Initialize mcp_servers if it doesn't exist
95
+ if (!updatedConfig.mcp_servers) {
96
+ updatedConfig.mcp_servers = {};
97
+ }
98
+ if (strategy === 'overwrite') {
99
+ // For overwrite strategy, replace the entire mcp_servers section
100
+ updatedConfig.mcp_servers = {};
101
+ }
102
+ // Add the ruler servers
103
+ for (const [serverName, serverConfig] of Object.entries(rulerServers)) {
104
+ // Create a properly formatted MCP server entry
105
+ const mcpServer = {
106
+ command: serverConfig.command,
107
+ };
108
+ if (serverConfig.args) {
109
+ mcpServer.args = serverConfig.args;
110
+ }
111
+ // Format env as an inline table
112
+ if (serverConfig.env) {
113
+ mcpServer.env = serverConfig.env;
114
+ }
115
+ // Handle additional properties from remote server transformation
116
+ if (serverConfig.headers) {
117
+ mcpServer.headers = serverConfig.headers;
118
+ }
119
+ if (updatedConfig.mcp_servers) {
120
+ updatedConfig.mcp_servers[serverName] = mcpServer;
121
+ }
122
+ }
123
+ // Convert to TOML using structured objects
124
+ const finalConfig = { ...updatedConfig };
125
+ // @iarna/toml should handle the formatting properly
126
+ const tomlContent = (0, toml_1.stringify)(finalConfig);
127
+ await (0, FileSystemUtils_1.writeGeneratedFile)(configPath, tomlContent);
128
+ }
129
+ }
130
+ getDefaultOutputPath(projectRoot) {
131
+ return {
132
+ instructions: path.join(projectRoot, constants_1.DEFAULT_RULES_FILENAME),
133
+ config: path.join(projectRoot, '.codex', 'config.toml'),
134
+ };
135
+ }
136
+ supportsMcpStdio() {
137
+ return true;
138
+ }
139
+ supportsMcpRemote() {
140
+ return false; // Codex CLI only supports STDIO based on PR description
141
+ }
142
+ }
143
+ exports.CodexCliAgent = CodexCliAgent;
@@ -0,0 +1,43 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.CopilotAgent = void 0;
4
+ const AgentsMdAgent_1 = require("./AgentsMdAgent");
5
+ /**
6
+ * GitHub Copilot agent adapter.
7
+ * Writes to AGENTS.md for both web-based GitHub Copilot and VS Code extension.
8
+ */
9
+ class CopilotAgent {
10
+ constructor() {
11
+ this.agentsMdAgent = new AgentsMdAgent_1.AgentsMdAgent();
12
+ }
13
+ getIdentifier() {
14
+ return 'copilot';
15
+ }
16
+ getName() {
17
+ return 'GitHub Copilot';
18
+ }
19
+ /**
20
+ * Returns the default output path for AGENTS.md.
21
+ */
22
+ getDefaultOutputPath(projectRoot) {
23
+ return this.agentsMdAgent.getDefaultOutputPath(projectRoot);
24
+ }
25
+ async applyRulerConfig(concatenatedRules, projectRoot, rulerMcpJson, agentConfig, backup = true) {
26
+ // Write to AGENTS.md using the existing AgentsMdAgent infrastructure
27
+ await this.agentsMdAgent.applyRulerConfig(concatenatedRules, projectRoot, null, // No MCP config needed for the instructions file
28
+ {
29
+ // Preserve explicit outputPath precedence semantics if provided
30
+ outputPath: agentConfig?.outputPath || agentConfig?.outputPathInstructions,
31
+ }, backup);
32
+ }
33
+ getMcpServerKey() {
34
+ return 'servers';
35
+ }
36
+ supportsMcpStdio() {
37
+ return true;
38
+ }
39
+ supportsMcpRemote() {
40
+ return true;
41
+ }
42
+ }
43
+ exports.CopilotAgent = CopilotAgent;
@@ -0,0 +1,128 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.CrushAgent = void 0;
37
+ const fs = __importStar(require("fs/promises"));
38
+ const path = __importStar(require("path"));
39
+ class CrushAgent {
40
+ getIdentifier() {
41
+ return 'crush';
42
+ }
43
+ getName() {
44
+ return 'Crush';
45
+ }
46
+ getDefaultOutputPath(projectRoot) {
47
+ return {
48
+ instructions: path.join(projectRoot, 'CRUSH.md'),
49
+ mcp: path.join(projectRoot, '.crush.json'),
50
+ };
51
+ }
52
+ /**
53
+ * Transform MCP server types for Crush compatibility.
54
+ * Crush expects "http" for HTTP servers and "sse" for SSE servers, not "remote".
55
+ */
56
+ transformMcpServersForCrush(mcpServers) {
57
+ const transformedServers = {};
58
+ for (const [name, serverDef] of Object.entries(mcpServers)) {
59
+ if (serverDef && typeof serverDef === 'object') {
60
+ const server = serverDef;
61
+ const transformedServer = { ...server };
62
+ // Transform type: "remote" to appropriate Crush types
63
+ if (server.type === 'remote' &&
64
+ server.url &&
65
+ typeof server.url === 'string') {
66
+ const url = server.url;
67
+ // Check if URL suggests SSE (contains /sse path segment)
68
+ if (/\/sse(\/|$)/i.test(url)) {
69
+ transformedServer.type = 'sse';
70
+ }
71
+ else {
72
+ transformedServer.type = 'http';
73
+ }
74
+ }
75
+ transformedServers[name] = transformedServer;
76
+ }
77
+ else {
78
+ transformedServers[name] = serverDef;
79
+ }
80
+ }
81
+ return transformedServers;
82
+ }
83
+ async applyRulerConfig(concatenatedRules, projectRoot, rulerMcpJson, agentConfig) {
84
+ const outputPaths = this.getDefaultOutputPath(projectRoot);
85
+ const instructionsPath = agentConfig?.outputPathInstructions ?? outputPaths['instructions'];
86
+ const mcpPath = agentConfig?.outputPathConfig ?? outputPaths['mcp'];
87
+ await fs.writeFile(instructionsPath, concatenatedRules);
88
+ // Always transform from mcpServers ({ mcpServers: ... }) to { mcp: ... } for Crush
89
+ let finalMcpConfig = { mcp: {} };
90
+ try {
91
+ const existingMcpConfig = JSON.parse(await fs.readFile(mcpPath, 'utf-8'));
92
+ if (existingMcpConfig && typeof existingMcpConfig === 'object') {
93
+ const transformedServers = this.transformMcpServersForCrush((rulerMcpJson?.mcpServers ?? {}));
94
+ finalMcpConfig = {
95
+ ...existingMcpConfig,
96
+ mcp: {
97
+ ...(existingMcpConfig.mcp || {}),
98
+ ...transformedServers,
99
+ },
100
+ };
101
+ }
102
+ else if (rulerMcpJson) {
103
+ const transformedServers = this.transformMcpServersForCrush((rulerMcpJson?.mcpServers ?? {}));
104
+ finalMcpConfig = {
105
+ mcp: transformedServers,
106
+ };
107
+ }
108
+ }
109
+ catch {
110
+ if (rulerMcpJson) {
111
+ const transformedServers = this.transformMcpServersForCrush((rulerMcpJson?.mcpServers ?? {}));
112
+ finalMcpConfig = {
113
+ mcp: transformedServers,
114
+ };
115
+ }
116
+ }
117
+ if (Object.keys(finalMcpConfig.mcp).length > 0) {
118
+ await fs.writeFile(mcpPath, JSON.stringify(finalMcpConfig, null, 2));
119
+ }
120
+ }
121
+ supportsMcpStdio() {
122
+ return true;
123
+ }
124
+ supportsMcpRemote() {
125
+ return true;
126
+ }
127
+ }
128
+ exports.CrushAgent = CrushAgent;
@@ -0,0 +1,93 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.CursorAgent = void 0;
37
+ const path = __importStar(require("path"));
38
+ const fs = __importStar(require("fs/promises"));
39
+ const AgentsMdAgent_1 = require("./AgentsMdAgent");
40
+ const SkillsUtils_1 = require("../core/SkillsUtils");
41
+ /**
42
+ * Cursor agent adapter.
43
+ * Leverages the standardized AGENTS.md approach supported natively by Cursor.
44
+ * See: https://docs.cursor.com/en/cli/using
45
+ */
46
+ class CursorAgent extends AgentsMdAgent_1.AgentsMdAgent {
47
+ getIdentifier() {
48
+ return 'cursor';
49
+ }
50
+ getName() {
51
+ return 'Cursor';
52
+ }
53
+ async applyRulerConfig(concatenatedRules, projectRoot, _rulerMcpJson, agentConfig, backup = true, _ruleFiles, rulerDir, mergeStrategy) {
54
+ // Write AGENTS.md via base class
55
+ // Cursor natively reads AGENTS.md from the project root
56
+ await super.applyRulerConfig(concatenatedRules, projectRoot, null, {
57
+ outputPath: agentConfig?.outputPath,
58
+ }, backup);
59
+ // Copy .claude/rules to .cursor/rules when using cursor merge strategy
60
+ if (mergeStrategy === 'cursor' && rulerDir) {
61
+ const rulerDirName = path.basename(rulerDir);
62
+ if (rulerDirName === '.claude') {
63
+ const sourceRulesDir = path.join(rulerDir, 'rules');
64
+ const targetRulesDir = path.join(projectRoot, '.cursor', 'rules');
65
+ // Check if source rules directory exists
66
+ try {
67
+ await fs.access(sourceRulesDir);
68
+ // Copy the entire rules directory
69
+ await fs.mkdir(path.dirname(targetRulesDir), { recursive: true });
70
+ await fs.rm(targetRulesDir, { recursive: true, force: true });
71
+ await (0, SkillsUtils_1.copySkillsDirectory)(sourceRulesDir, targetRulesDir);
72
+ }
73
+ catch {
74
+ // Source rules directory doesn't exist, skip copying
75
+ }
76
+ }
77
+ }
78
+ }
79
+ getMcpServerKey() {
80
+ return 'mcpServers';
81
+ }
82
+ supportsMcpStdio() {
83
+ return true;
84
+ }
85
+ supportsMcpRemote() {
86
+ return true;
87
+ }
88
+ supportsNativeSkills() {
89
+ // Cursor has native support for rules via .cursor/rules/
90
+ return true;
91
+ }
92
+ }
93
+ exports.CursorAgent = CursorAgent;
@@ -0,0 +1,61 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.FirebaseAgent = void 0;
37
+ const path = __importStar(require("path"));
38
+ const AbstractAgent_1 = require("./AbstractAgent");
39
+ /**
40
+ * Firebase Studio agent adapter.
41
+ */
42
+ class FirebaseAgent extends AbstractAgent_1.AbstractAgent {
43
+ getIdentifier() {
44
+ return 'firebase';
45
+ }
46
+ getName() {
47
+ return 'Firebase Studio';
48
+ }
49
+ getDefaultOutputPath(projectRoot) {
50
+ return path.join(projectRoot, '.idx', 'airules.md');
51
+ }
52
+ // Firebase Studio (IDX) supports stdio MCP servers via .idx/mcp.json
53
+ supportsMcpStdio() {
54
+ return true;
55
+ }
56
+ // Remote MCP over HTTP/SSE is not documented for Firebase Studio yet
57
+ supportsMcpRemote() {
58
+ return false;
59
+ }
60
+ }
61
+ exports.FirebaseAgent = FirebaseAgent;
@@ -0,0 +1,205 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.FirebenderAgent = void 0;
37
+ const path = __importStar(require("path"));
38
+ const fs = __importStar(require("fs"));
39
+ const FileSystemUtils_1 = require("../core/FileSystemUtils");
40
+ /**
41
+ * Firebender agent adapter.
42
+ */
43
+ class FirebenderAgent {
44
+ /**
45
+ * Type guard function to safely check if an object is a FirebenderRule.
46
+ */
47
+ isFirebenderRule(rule) {
48
+ return (typeof rule === 'object' &&
49
+ rule !== null &&
50
+ 'filePathMatches' in rule &&
51
+ 'rulesPaths' in rule &&
52
+ typeof rule.filePathMatches === 'string' &&
53
+ typeof rule.rulesPaths === 'string');
54
+ }
55
+ getIdentifier() {
56
+ return 'firebender';
57
+ }
58
+ getName() {
59
+ return 'Firebender';
60
+ }
61
+ async applyRulerConfig(concatenatedRules, projectRoot, rulerMcpJson, agentConfig, backup = true) {
62
+ const rulesPath = this.resolveOutputPath(projectRoot, agentConfig);
63
+ await (0, FileSystemUtils_1.ensureDirExists)(path.dirname(rulesPath));
64
+ const firebenderConfig = await this.loadExistingConfig(rulesPath);
65
+ const newRules = this.createRulesFromConcatenatedRules(concatenatedRules, projectRoot);
66
+ firebenderConfig.rules.push(...newRules);
67
+ this.removeDuplicateRules(firebenderConfig);
68
+ const mcpEnabled = agentConfig?.mcp?.enabled ?? true;
69
+ if (mcpEnabled && rulerMcpJson) {
70
+ await this.handleMcpConfiguration(firebenderConfig, rulerMcpJson, agentConfig);
71
+ }
72
+ await this.saveConfig(rulesPath, firebenderConfig, backup);
73
+ }
74
+ resolveOutputPath(projectRoot, agentConfig) {
75
+ const outputPaths = this.getDefaultOutputPath(projectRoot);
76
+ const output = agentConfig?.outputPath ??
77
+ agentConfig?.outputPathInstructions ??
78
+ outputPaths['instructions'];
79
+ return path.resolve(projectRoot, output);
80
+ }
81
+ async loadExistingConfig(rulesPath) {
82
+ try {
83
+ const existingContent = await fs.promises.readFile(rulesPath, 'utf8');
84
+ const config = JSON.parse(existingContent);
85
+ if (!config.rules) {
86
+ config.rules = [];
87
+ }
88
+ return config;
89
+ }
90
+ catch (error) {
91
+ if (error &&
92
+ typeof error === 'object' &&
93
+ 'code' in error &&
94
+ error.code === 'ENOENT') {
95
+ return { rules: [] };
96
+ }
97
+ console.warn(`Failed to read/parse existing firebender.json: ${error}`);
98
+ return { rules: [] };
99
+ }
100
+ }
101
+ createRulesFromConcatenatedRules(concatenatedRules, projectRoot) {
102
+ const filePaths = this.extractFilePathsFromRules(concatenatedRules, projectRoot);
103
+ if (filePaths.length > 0) {
104
+ return this.createRuleObjectsFromFilePaths(filePaths);
105
+ }
106
+ else {
107
+ return this.createRulesFromPlainText(concatenatedRules);
108
+ }
109
+ }
110
+ createRuleObjectsFromFilePaths(filePaths) {
111
+ return filePaths.map((filePath) => ({
112
+ filePathMatches: '**/*',
113
+ rulesPaths: filePath,
114
+ }));
115
+ }
116
+ createRulesFromPlainText(concatenatedRules) {
117
+ return concatenatedRules.split('\n').filter((rule) => rule.trim());
118
+ }
119
+ removeDuplicateRules(firebenderConfig) {
120
+ const seen = new Set();
121
+ firebenderConfig.rules = firebenderConfig.rules.filter((rule) => {
122
+ let key;
123
+ if (this.isFirebenderRule(rule)) {
124
+ const filePathMatchesPart = rule.filePathMatches;
125
+ const rulesPathsPart = rule.rulesPaths;
126
+ key = `${filePathMatchesPart}::${rulesPathsPart}`;
127
+ }
128
+ else {
129
+ key = String(rule);
130
+ }
131
+ if (seen.has(key)) {
132
+ return false;
133
+ }
134
+ seen.add(key);
135
+ return true;
136
+ });
137
+ }
138
+ async saveConfig(rulesPath, config, backup) {
139
+ const updatedContent = JSON.stringify(config, null, 2);
140
+ if (backup) {
141
+ await (0, FileSystemUtils_1.backupFile)(rulesPath);
142
+ }
143
+ await (0, FileSystemUtils_1.writeGeneratedFile)(rulesPath, updatedContent);
144
+ }
145
+ /**
146
+ * Handle MCP server configuration for Firebender.
147
+ * Merges or overwrites MCP servers in the firebender.json configuration based on strategy.
148
+ */
149
+ async handleMcpConfiguration(firebenderConfig, rulerMcpJson, agentConfig) {
150
+ const strategy = agentConfig?.mcp?.strategy ?? 'merge';
151
+ const incomingServers = rulerMcpJson.mcpServers || {};
152
+ if (!firebenderConfig.mcpServers) {
153
+ firebenderConfig.mcpServers = {};
154
+ }
155
+ if (strategy === 'overwrite') {
156
+ firebenderConfig.mcpServers = { ...incomingServers };
157
+ }
158
+ else if (strategy === 'merge') {
159
+ const existingServers = firebenderConfig.mcpServers || {};
160
+ firebenderConfig.mcpServers = { ...existingServers, ...incomingServers };
161
+ }
162
+ }
163
+ getDefaultOutputPath(projectRoot) {
164
+ return {
165
+ instructions: path.join(projectRoot, 'firebender.json'),
166
+ mcp: path.join(projectRoot, 'firebender.json'),
167
+ };
168
+ }
169
+ getMcpServerKey() {
170
+ return 'mcpServers';
171
+ }
172
+ supportsMcpStdio() {
173
+ return true;
174
+ }
175
+ supportsMcpRemote() {
176
+ return true;
177
+ }
178
+ /**
179
+ * Extracts file paths from concatenated rules by parsing HTML source comments.
180
+ * @param concatenatedRules The concatenated rules string with HTML comments
181
+ * @param projectRoot The project root directory
182
+ * @returns Array of file paths relative to project root
183
+ */
184
+ extractFilePathsFromRules(concatenatedRules, projectRoot) {
185
+ const sourceCommentRegex = /<!-- Source: (.+?) -->/g;
186
+ const filePaths = [];
187
+ let match;
188
+ while ((match = sourceCommentRegex.exec(concatenatedRules)) !== null) {
189
+ const relativePath = match[1];
190
+ const absolutePath = path.resolve(projectRoot, relativePath);
191
+ const normalizedProjectRoot = path.resolve(projectRoot);
192
+ // Ensure the absolutePath is within the project root (cross-platform compatible)
193
+ // This prevents path traversal attacks while handling Windows/Unix path differences
194
+ const isWithinProject = absolutePath.startsWith(normalizedProjectRoot) &&
195
+ (absolutePath.length === normalizedProjectRoot.length ||
196
+ absolutePath[normalizedProjectRoot.length] === path.sep);
197
+ if (isWithinProject) {
198
+ const projectRelativePath = path.relative(projectRoot, absolutePath);
199
+ filePaths.push(projectRelativePath);
200
+ }
201
+ }
202
+ return filePaths;
203
+ }
204
+ }
205
+ exports.FirebenderAgent = FirebenderAgent;