rulesync 9.0.1 → 9.1.0
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 +8 -8
- package/dist/cli/index.cjs +8 -2
- package/dist/cli/index.js +8 -2
- package/dist/cli/index.js.map +1 -1
- package/dist/{import-BhqQwWah.cjs → import-CNtfs80W.cjs} +2697 -477
- package/dist/{import-zmpFGK87.js → import-ylOvQDpE.js} +2688 -468
- package/dist/import-ylOvQDpE.js.map +1 -0
- package/dist/index.cjs +1 -1
- package/dist/index.d.cts +5 -3
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.ts +5 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/package.json +1 -1
- package/dist/import-zmpFGK87.js.map +0 -1
|
@@ -198,6 +198,7 @@ const mcpProcessorToolTargetTuple = [
|
|
|
198
198
|
"reasonix",
|
|
199
199
|
"roo",
|
|
200
200
|
"rovodev",
|
|
201
|
+
"takt",
|
|
201
202
|
"vibe",
|
|
202
203
|
"warp",
|
|
203
204
|
"devin",
|
|
@@ -205,6 +206,7 @@ const mcpProcessorToolTargetTuple = [
|
|
|
205
206
|
];
|
|
206
207
|
const commandsProcessorToolTargetTuple = [
|
|
207
208
|
"agentsmd",
|
|
209
|
+
"antigravity-cli",
|
|
208
210
|
"antigravity-ide",
|
|
209
211
|
"augmentcode",
|
|
210
212
|
"claudecode",
|
|
@@ -224,7 +226,9 @@ const commandsProcessorToolTargetTuple = [
|
|
|
224
226
|
"opencode",
|
|
225
227
|
"pi",
|
|
226
228
|
"qwencode",
|
|
229
|
+
"reasonix",
|
|
227
230
|
"roo",
|
|
231
|
+
"rovodev",
|
|
228
232
|
"takt",
|
|
229
233
|
"devin"
|
|
230
234
|
];
|
|
@@ -309,11 +313,13 @@ const hooksProcessorToolTargetTuple = [
|
|
|
309
313
|
"deepagents",
|
|
310
314
|
"kiro",
|
|
311
315
|
"kiro-cli",
|
|
316
|
+
"kiro-ide",
|
|
312
317
|
"devin",
|
|
313
318
|
"augmentcode",
|
|
314
319
|
"junie",
|
|
315
320
|
"vibe",
|
|
316
|
-
"qwencode"
|
|
321
|
+
"qwencode",
|
|
322
|
+
"reasonix"
|
|
317
323
|
];
|
|
318
324
|
const permissionsProcessorToolTargetTuple = [
|
|
319
325
|
"amp",
|
|
@@ -324,16 +330,19 @@ const permissionsProcessorToolTargetTuple = [
|
|
|
324
330
|
"cline",
|
|
325
331
|
"codexcli",
|
|
326
332
|
"cursor",
|
|
333
|
+
"devin",
|
|
327
334
|
"factorydroid",
|
|
328
335
|
"goose",
|
|
329
336
|
"grokcli",
|
|
330
337
|
"hermesagent",
|
|
338
|
+
"junie",
|
|
331
339
|
"kilo",
|
|
332
340
|
"kiro",
|
|
333
341
|
"kiro-cli",
|
|
334
342
|
"kiro-ide",
|
|
335
343
|
"opencode",
|
|
336
344
|
"qwencode",
|
|
345
|
+
"reasonix",
|
|
337
346
|
"rovodev",
|
|
338
347
|
"takt",
|
|
339
348
|
"vibe",
|
|
@@ -1373,7 +1382,7 @@ function extractConfigFileTargets(targets) {
|
|
|
1373
1382
|
* Type guard to check if a value is a plain object (Record<string, unknown>).
|
|
1374
1383
|
* This excludes arrays and null values.
|
|
1375
1384
|
*/
|
|
1376
|
-
function isRecord
|
|
1385
|
+
function isRecord(value) {
|
|
1377
1386
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1378
1387
|
}
|
|
1379
1388
|
/**
|
|
@@ -1388,7 +1397,7 @@ function isRecord$1(value) {
|
|
|
1388
1397
|
* malicious accessor descriptors.
|
|
1389
1398
|
*/
|
|
1390
1399
|
function isPlainObject(value) {
|
|
1391
|
-
if (!isRecord
|
|
1400
|
+
if (!isRecord(value)) return false;
|
|
1392
1401
|
const proto = Object.getPrototypeOf(value);
|
|
1393
1402
|
return proto === null || proto === Object.prototype;
|
|
1394
1403
|
}
|
|
@@ -1739,6 +1748,17 @@ var ToolCommand = class extends AiFile {
|
|
|
1739
1748
|
throw new Error("Please implement this method in the subclass.");
|
|
1740
1749
|
}
|
|
1741
1750
|
/**
|
|
1751
|
+
* Optional hook for tools whose commands are not purely one-file-per-command
|
|
1752
|
+
* (e.g. Rovo Dev's `prompts.yml` manifest that indexes every saved-prompt
|
|
1753
|
+
* content file). Given the full set of ToolCommand instances just generated
|
|
1754
|
+
* for this target, returns any additional shared/aggregate files that must
|
|
1755
|
+
* be written alongside them. Most tools don't need this and inherit the
|
|
1756
|
+
* empty default (mirrors `ToolHooks.getAuxiliaryFiles`).
|
|
1757
|
+
*/
|
|
1758
|
+
static async getAuxiliaryFiles(_params) {
|
|
1759
|
+
return [];
|
|
1760
|
+
}
|
|
1761
|
+
/**
|
|
1742
1762
|
* Convert a RulesyncCommand to the tool-specific command format.
|
|
1743
1763
|
*
|
|
1744
1764
|
* This method should:
|
|
@@ -1897,16 +1917,25 @@ var AgentsmdCommand = class AgentsmdCommand extends SimulatedCommand {
|
|
|
1897
1917
|
}
|
|
1898
1918
|
};
|
|
1899
1919
|
//#endregion
|
|
1900
|
-
//#region src/constants/antigravity-
|
|
1901
|
-
const
|
|
1902
|
-
const
|
|
1903
|
-
const
|
|
1904
|
-
const
|
|
1905
|
-
const
|
|
1906
|
-
const
|
|
1907
|
-
const
|
|
1908
|
-
const
|
|
1909
|
-
const
|
|
1920
|
+
//#region src/constants/antigravity-paths.ts
|
|
1921
|
+
const ANTIGRAVITY_DIR = ".agents";
|
|
1922
|
+
const ANTIGRAVITY_SKILLS_DIR_PATH = join(ANTIGRAVITY_DIR, "skills");
|
|
1923
|
+
const ANTIGRAVITY_WORKFLOWS_DIR_PATH = join(ANTIGRAVITY_DIR, "workflows");
|
|
1924
|
+
const ANTIGRAVITY_MCP_FILE_NAME = "mcp_config.json";
|
|
1925
|
+
const ANTIGRAVITY_HOOKS_FILE_NAME = "hooks.json";
|
|
1926
|
+
const ANTIGRAVITY_IGNORE_FILE_NAME = ".geminiignore";
|
|
1927
|
+
const ANTIGRAVITY_GEMINI_DIR = ".gemini";
|
|
1928
|
+
const ANTIGRAVITY_CLI_PERMISSIONS_SUBDIR = "antigravity-cli";
|
|
1929
|
+
const ANTIGRAVITY_CLI_PERMISSIONS_DIR_PATH = join(ANTIGRAVITY_GEMINI_DIR, ANTIGRAVITY_CLI_PERMISSIONS_SUBDIR);
|
|
1930
|
+
const ANTIGRAVITY_CLI_PERMISSIONS_FILE_NAME = "settings.json";
|
|
1931
|
+
const ANTIGRAVITY_CLI_GLOBAL_WORKFLOWS_DIR_PATH = join(ANTIGRAVITY_GEMINI_DIR, ANTIGRAVITY_CLI_PERMISSIONS_SUBDIR, "global_workflows");
|
|
1932
|
+
const ANTIGRAVITY_GLOBAL_CONFIG_SUBDIR = "config";
|
|
1933
|
+
const ANTIGRAVITY_GLOBAL_CONFIG_DIR_PATH = join(ANTIGRAVITY_GEMINI_DIR, ANTIGRAVITY_GLOBAL_CONFIG_SUBDIR);
|
|
1934
|
+
//#endregion
|
|
1935
|
+
//#region src/constants/antigravity-cli-paths.ts
|
|
1936
|
+
const ANTIGRAVITY_AGENTS_DIR = ANTIGRAVITY_DIR;
|
|
1937
|
+
const ANTIGRAVITY_RULE_FILE_NAME = "AGENTS.md";
|
|
1938
|
+
const ANTIGRAVITY_GLOBAL_RULE_FILE_NAME = "GEMINI.md";
|
|
1910
1939
|
//#endregion
|
|
1911
1940
|
//#region src/features/commands/antigravity-command.ts
|
|
1912
1941
|
const AntigravityWorkflowFrontmatterSchema = z.looseObject({
|
|
@@ -2080,7 +2109,7 @@ var AntigravitySharedCommand = class extends ToolCommand {
|
|
|
2080
2109
|
}
|
|
2081
2110
|
static extractAntigravityConfig(rulesyncCommand) {
|
|
2082
2111
|
const antigravity = rulesyncCommand.getFrontmatter().antigravity;
|
|
2083
|
-
return isRecord
|
|
2112
|
+
return isRecord(antigravity) ? antigravity : void 0;
|
|
2084
2113
|
}
|
|
2085
2114
|
static resolveTrigger(rulesyncCommand, antigravityConfig) {
|
|
2086
2115
|
const rulesyncFrontmatter = rulesyncCommand.getFrontmatter();
|
|
@@ -2162,6 +2191,45 @@ var AntigravitySharedCommand = class extends ToolCommand {
|
|
|
2162
2191
|
}
|
|
2163
2192
|
};
|
|
2164
2193
|
//#endregion
|
|
2194
|
+
//#region src/features/commands/antigravity-cli-command.ts
|
|
2195
|
+
/**
|
|
2196
|
+
* Command (workflow) generator for the Google Antigravity CLI (`agy`, Antigravity 2.0).
|
|
2197
|
+
*
|
|
2198
|
+
* Shares all body and frontmatter handling with {@link AntigravitySharedCommand};
|
|
2199
|
+
* the CLI reads project workflows from the same `.agents/workflows/` directory as
|
|
2200
|
+
* the IDE (the shared Antigravity 2.0 harness), but keeps its own global
|
|
2201
|
+
* workflows tree at `~/.gemini/antigravity-cli/global_workflows/` (mirroring the
|
|
2202
|
+
* CLI's global skills tree). It answers to the `antigravity-cli` target.
|
|
2203
|
+
*/
|
|
2204
|
+
var AntigravityCliCommand = class extends AntigravitySharedCommand {
|
|
2205
|
+
static getProjectRelativeDirPath() {
|
|
2206
|
+
return ANTIGRAVITY_WORKFLOWS_DIR_PATH;
|
|
2207
|
+
}
|
|
2208
|
+
static getGlobalRelativeDirPath() {
|
|
2209
|
+
return ANTIGRAVITY_CLI_GLOBAL_WORKFLOWS_DIR_PATH;
|
|
2210
|
+
}
|
|
2211
|
+
getToolTargetName() {
|
|
2212
|
+
return "antigravity-cli";
|
|
2213
|
+
}
|
|
2214
|
+
static isTargetedByRulesyncCommand(rulesyncCommand) {
|
|
2215
|
+
return this.isTargetedByRulesyncCommandDefault({
|
|
2216
|
+
rulesyncCommand,
|
|
2217
|
+
toolTarget: "antigravity-cli"
|
|
2218
|
+
});
|
|
2219
|
+
}
|
|
2220
|
+
};
|
|
2221
|
+
//#endregion
|
|
2222
|
+
//#region src/constants/antigravity-ide-paths.ts
|
|
2223
|
+
const ANTIGRAVITY_IDE_AGENTS_DIR = ".agents";
|
|
2224
|
+
const ANTIGRAVITY_IDE_COMMANDS_DIR_PATH = join(ANTIGRAVITY_IDE_AGENTS_DIR, "workflows");
|
|
2225
|
+
const ANTIGRAVITY_IDE_RULE_FILE_NAME = "AGENTS.md";
|
|
2226
|
+
const ANTIGRAVITY_IDE_GEMINI_DIR = ".gemini";
|
|
2227
|
+
const ANTIGRAVITY_IDE_GLOBAL_RULE_FILE_NAME = "GEMINI.md";
|
|
2228
|
+
const ANTIGRAVITY_IDE_GLOBAL_CONFIG_SUBDIR = "config";
|
|
2229
|
+
const ANTIGRAVITY_IDE_GLOBAL_WORKFLOWS_DIR_PATH = join(ANTIGRAVITY_IDE_GEMINI_DIR, "antigravity", "global_workflows");
|
|
2230
|
+
const ANTIGRAVITY_IDE_PERMISSIONS_DIR = ".antigravity";
|
|
2231
|
+
const ANTIGRAVITY_IDE_PERMISSIONS_FILE_NAME = "settings.json";
|
|
2232
|
+
//#endregion
|
|
2165
2233
|
//#region src/features/commands/antigravity-ide-command.ts
|
|
2166
2234
|
/**
|
|
2167
2235
|
* Command (workflow) generator for the Google Antigravity IDE (Antigravity 2.0).
|
|
@@ -2546,6 +2614,16 @@ const CodexcliCommandFrontmatterSchema = z.looseObject({
|
|
|
2546
2614
|
description: z.optional(z.string()),
|
|
2547
2615
|
"argument-hint": z.optional(z.string())
|
|
2548
2616
|
});
|
|
2617
|
+
/**
|
|
2618
|
+
* Generates Codex CLI's global-only custom prompts under `~/.codex/prompts/*.md`.
|
|
2619
|
+
*
|
|
2620
|
+
* Note: upstream Codex docs now state "Custom prompts are deprecated. Use skills for
|
|
2621
|
+
* reusable instructions" (https://developers.openai.com/codex/custom-prompts). No removal
|
|
2622
|
+
* date has been announced and custom prompts remain functional, so this class's
|
|
2623
|
+
* generation behavior is unchanged. Prefer rulesync's `codexcli` skills support
|
|
2624
|
+
* (see `src/features/skills/codexcli-skill.ts`) for new reusable instructions going
|
|
2625
|
+
* forward; this class is kept for users who still rely on custom prompts.
|
|
2626
|
+
*/
|
|
2549
2627
|
var CodexcliCommand = class CodexcliCommand extends ToolCommand {
|
|
2550
2628
|
frontmatter;
|
|
2551
2629
|
body;
|
|
@@ -2669,7 +2747,7 @@ const COPILOTCLI_PROJECT_MCP_FILE_NAME = "mcp.json";
|
|
|
2669
2747
|
const COPILOTCLI_AGENTS_DIR_PATH = join(COPILOT_DIR, "agents");
|
|
2670
2748
|
const COPILOTCLI_HOOKS_DIR_PATH = join(COPILOT_DIR, "hooks");
|
|
2671
2749
|
const COPILOTCLI_HOOKS_FILE_NAME = "copilotcli-hooks.json";
|
|
2672
|
-
const
|
|
2750
|
+
const COPILOT_SKILLS_GLOBAL_DIR_PATH = join(COPILOT_DIR, "skills");
|
|
2673
2751
|
//#endregion
|
|
2674
2752
|
//#region src/features/commands/copilot-command.ts
|
|
2675
2753
|
const CopilotCommandFrontmatterSchema = z.looseObject({
|
|
@@ -2919,20 +2997,17 @@ var CursorCommand = class CursorCommand extends ToolCommand {
|
|
|
2919
2997
|
//#endregion
|
|
2920
2998
|
//#region src/constants/devin-paths.ts
|
|
2921
2999
|
const DEVIN_DIR = ".devin";
|
|
2922
|
-
const
|
|
2923
|
-
const CODEIUM_DIR = ".codeium";
|
|
2924
|
-
const WINDSURF_SUBDIR = "windsurf";
|
|
2925
|
-
const CODEIUM_WINDSURF_DIR = join(CODEIUM_DIR, WINDSURF_SUBDIR);
|
|
2926
|
-
const WINDSURF_MEMORIES_SUBDIR = join(WINDSURF_SUBDIR, "memories");
|
|
3000
|
+
const CODEIUM_WINDSURF_DIR = join(".codeium", "windsurf");
|
|
2927
3001
|
const DEVIN_WORKFLOWS_DIR_PATH = join(DEVIN_DIR, "workflows");
|
|
2928
3002
|
const DEVIN_SKILLS_DIR_PATH = join(DEVIN_DIR, "skills");
|
|
2929
3003
|
const DEVIN_AGENTS_DIR_PATH = join(DEVIN_DIR, "agents");
|
|
2930
|
-
const
|
|
3004
|
+
const DEVIN_GLOBAL_CONFIG_DIR_PATH = join(".config", "devin");
|
|
3005
|
+
const DEVIN_GLOBAL_AGENTS_DIR_PATH = join(DEVIN_GLOBAL_CONFIG_DIR_PATH, "agents");
|
|
2931
3006
|
const CODEIUM_WINDSURF_GLOBAL_WORKFLOWS_DIR_PATH = join(CODEIUM_WINDSURF_DIR, "global_workflows");
|
|
2932
3007
|
const CODEIUM_WINDSURF_SKILLS_DIR_PATH = join(CODEIUM_WINDSURF_DIR, "skills");
|
|
2933
|
-
const
|
|
2934
|
-
const
|
|
2935
|
-
const
|
|
3008
|
+
const DEVIN_CONFIG_FILE_NAME = "config.json";
|
|
3009
|
+
const DEVIN_HOOKS_V1_FILE_NAME = "hooks.v1.json";
|
|
3010
|
+
const DEVIN_GLOBAL_AGENTS_FILE_NAME = "AGENTS.md";
|
|
2936
3011
|
const DEVIN_IGNORE_FILE_NAME = ".devinignore";
|
|
2937
3012
|
const DEVIN_LEGACY_IGNORE_FILE_NAME = ".codeiumignore";
|
|
2938
3013
|
//#endregion
|
|
@@ -3181,6 +3256,8 @@ const GOOSE_MCP_FILE_NAME = "config.yaml";
|
|
|
3181
3256
|
const GOOSE_PERMISSIONS_FILE_NAME = "permission.yaml";
|
|
3182
3257
|
const GOOSE_HOOKS_DIR_PATH = join(".agents", "plugins", "rulesync", "hooks");
|
|
3183
3258
|
const GOOSE_HOOKS_FILE_NAME = "hooks.json";
|
|
3259
|
+
const GOOSE_PLUGIN_MCP_DIR_PATH = join(".agents", "plugins", "rulesync");
|
|
3260
|
+
const GOOSE_PLUGIN_MCP_FILE_NAME = ".mcp.json";
|
|
3184
3261
|
const GOOSE_SKILLS_DIR_PATH = join(GOOSE_DIR, "skills");
|
|
3185
3262
|
const GOOSE_RECIPES_DIR_PATH = join(GOOSE_DIR, "recipes");
|
|
3186
3263
|
const GOOSE_GLOBAL_RECIPES_DIR_PATH = join(GOOSE_GLOBAL_DIR, "recipes");
|
|
@@ -3438,6 +3515,7 @@ const JUNIE_ALT_AGENTS_DIR_PATH = ".agents";
|
|
|
3438
3515
|
const JUNIE_MCP_DIR_PATH = join(JUNIE_DIR, "mcp");
|
|
3439
3516
|
const JUNIE_MCP_FILE_NAME = "mcp.json";
|
|
3440
3517
|
const JUNIE_HOOKS_FILE_NAME = "config.json";
|
|
3518
|
+
const JUNIE_PERMISSIONS_FILE_NAME = "allowlist.json";
|
|
3441
3519
|
const JUNIE_IGNORE_FILE_NAME = ".aiignore";
|
|
3442
3520
|
const JUNIE_RULE_FILE_NAME = "AGENTS.md";
|
|
3443
3521
|
const JUNIE_LEGACY_RULE_FILE_NAME = "guidelines.md";
|
|
@@ -3690,6 +3768,15 @@ const KIRO_SKILLS_DIR_PATH = join(KIRO_DIR, "skills");
|
|
|
3690
3768
|
const KIRO_SETTINGS_DIR_PATH = join(KIRO_DIR, "settings");
|
|
3691
3769
|
const KIRO_AGENTS_DIR_PATH = join(KIRO_DIR, "agents");
|
|
3692
3770
|
const KIRO_HOOKS_FILE_NAME = "default.json";
|
|
3771
|
+
/**
|
|
3772
|
+
* Kiro IDE 1.0 stores hooks as structured JSON files in `.kiro/hooks/`
|
|
3773
|
+
* (workspace) and `~/.kiro/hooks/` (user). A single file may declare multiple
|
|
3774
|
+
* hooks in its `hooks` array, so rulesync emits all generated hooks into one
|
|
3775
|
+
* `rulesync.json` file per scope.
|
|
3776
|
+
* @see https://kiro.dev/docs/hooks/
|
|
3777
|
+
*/
|
|
3778
|
+
const KIRO_IDE_HOOKS_DIR_PATH = join(KIRO_DIR, "hooks");
|
|
3779
|
+
const KIRO_IDE_HOOKS_FILE_NAME = "rulesync.json";
|
|
3693
3780
|
const KIRO_MCP_FILE_NAME = "mcp.json";
|
|
3694
3781
|
const KIRO_IGNORE_FILE_NAME = ".kiroignore";
|
|
3695
3782
|
//#endregion
|
|
@@ -4269,6 +4356,135 @@ var QwencodeCommand = class QwencodeCommand extends ToolCommand {
|
|
|
4269
4356
|
}
|
|
4270
4357
|
};
|
|
4271
4358
|
//#endregion
|
|
4359
|
+
//#region src/constants/reasonix-paths.ts
|
|
4360
|
+
const REASONIX_PROJECT_MCP_FILE_NAME = "reasonix.toml";
|
|
4361
|
+
const REASONIX_GLOBAL_DIR = ".reasonix";
|
|
4362
|
+
const REASONIX_GLOBAL_MCP_FILE_NAME = "config.toml";
|
|
4363
|
+
const REASONIX_PROJECT_PERMISSIONS_FILE_NAME = REASONIX_PROJECT_MCP_FILE_NAME;
|
|
4364
|
+
const REASONIX_GLOBAL_PERMISSIONS_FILE_NAME = REASONIX_GLOBAL_MCP_FILE_NAME;
|
|
4365
|
+
const REASONIX_DIR = REASONIX_GLOBAL_DIR;
|
|
4366
|
+
const REASONIX_SETTINGS_FILE_NAME = "settings.json";
|
|
4367
|
+
const REASONIX_COMMANDS_DIR_PATH = join(REASONIX_DIR, "commands");
|
|
4368
|
+
//#endregion
|
|
4369
|
+
//#region src/features/commands/reasonix-command.ts
|
|
4370
|
+
/**
|
|
4371
|
+
* Reasonix custom slash commands are Markdown files under `.reasonix/commands/`
|
|
4372
|
+
* (project) / `~/.reasonix/commands/` (global) — directly analogous to Claude
|
|
4373
|
+
* Code's `.claude/commands/` (Reasonix explicitly copies Claude Code's
|
|
4374
|
+
* conventions). Frontmatter supports `description` and `argument-hint`; the
|
|
4375
|
+
* body uses the same `$ARGUMENTS` / `$1`…`$N` placeholder syntax rulesync's
|
|
4376
|
+
* universal command-body syntax already targets.
|
|
4377
|
+
* @see https://github.com/esengine/DeepSeek-Reasonix/blob/main-v2/docs/GUIDE.md
|
|
4378
|
+
*/
|
|
4379
|
+
const ReasonixCommandFrontmatterSchema = z.looseObject({
|
|
4380
|
+
description: z.optional(z.string()),
|
|
4381
|
+
"argument-hint": z.optional(z.string())
|
|
4382
|
+
});
|
|
4383
|
+
var ReasonixCommand = class ReasonixCommand extends ToolCommand {
|
|
4384
|
+
frontmatter;
|
|
4385
|
+
body;
|
|
4386
|
+
constructor({ frontmatter, body, ...rest }) {
|
|
4387
|
+
if (rest.validate) {
|
|
4388
|
+
const result = ReasonixCommandFrontmatterSchema.safeParse(frontmatter);
|
|
4389
|
+
if (!result.success) throw new Error(`Invalid frontmatter in ${join(rest.relativeDirPath, rest.relativeFilePath)}: ${formatError(result.error)}`);
|
|
4390
|
+
}
|
|
4391
|
+
super({
|
|
4392
|
+
...rest,
|
|
4393
|
+
fileContent: stringifyFrontmatter(body, frontmatter)
|
|
4394
|
+
});
|
|
4395
|
+
this.frontmatter = frontmatter;
|
|
4396
|
+
this.body = body;
|
|
4397
|
+
}
|
|
4398
|
+
static getSettablePaths(_options = {}) {
|
|
4399
|
+
return { relativeDirPath: REASONIX_COMMANDS_DIR_PATH };
|
|
4400
|
+
}
|
|
4401
|
+
getBody() {
|
|
4402
|
+
return this.body;
|
|
4403
|
+
}
|
|
4404
|
+
getFrontmatter() {
|
|
4405
|
+
return this.frontmatter;
|
|
4406
|
+
}
|
|
4407
|
+
toRulesyncCommand() {
|
|
4408
|
+
const { description, ...restFields } = this.frontmatter;
|
|
4409
|
+
const rulesyncFrontmatter = {
|
|
4410
|
+
targets: ["*"],
|
|
4411
|
+
description,
|
|
4412
|
+
...Object.keys(restFields).length > 0 && { reasonix: restFields }
|
|
4413
|
+
};
|
|
4414
|
+
const fileContent = stringifyFrontmatter(this.body, rulesyncFrontmatter);
|
|
4415
|
+
return new RulesyncCommand({
|
|
4416
|
+
outputRoot: ".",
|
|
4417
|
+
frontmatter: rulesyncFrontmatter,
|
|
4418
|
+
body: this.body,
|
|
4419
|
+
relativeDirPath: RulesyncCommand.getSettablePaths().relativeDirPath,
|
|
4420
|
+
relativeFilePath: this.relativeFilePath,
|
|
4421
|
+
fileContent,
|
|
4422
|
+
validate: true
|
|
4423
|
+
});
|
|
4424
|
+
}
|
|
4425
|
+
static fromRulesyncCommand({ outputRoot = process.cwd(), rulesyncCommand, validate = true, global = false }) {
|
|
4426
|
+
const rulesyncFrontmatter = rulesyncCommand.getFrontmatter();
|
|
4427
|
+
const reasonixFields = rulesyncFrontmatter.reasonix ?? {};
|
|
4428
|
+
return new ReasonixCommand({
|
|
4429
|
+
outputRoot,
|
|
4430
|
+
frontmatter: {
|
|
4431
|
+
description: rulesyncFrontmatter.description,
|
|
4432
|
+
...reasonixFields
|
|
4433
|
+
},
|
|
4434
|
+
body: rulesyncCommand.getBody(),
|
|
4435
|
+
relativeDirPath: this.getSettablePaths({ global }).relativeDirPath,
|
|
4436
|
+
relativeFilePath: rulesyncCommand.getRelativeFilePath(),
|
|
4437
|
+
validate
|
|
4438
|
+
});
|
|
4439
|
+
}
|
|
4440
|
+
validate() {
|
|
4441
|
+
if (!this.frontmatter) return {
|
|
4442
|
+
success: true,
|
|
4443
|
+
error: null
|
|
4444
|
+
};
|
|
4445
|
+
const result = ReasonixCommandFrontmatterSchema.safeParse(this.frontmatter);
|
|
4446
|
+
if (result.success) return {
|
|
4447
|
+
success: true,
|
|
4448
|
+
error: null
|
|
4449
|
+
};
|
|
4450
|
+
else return {
|
|
4451
|
+
success: false,
|
|
4452
|
+
error: /* @__PURE__ */ new Error(`Invalid frontmatter in ${join(this.relativeDirPath, this.relativeFilePath)}: ${formatError(result.error)}`)
|
|
4453
|
+
};
|
|
4454
|
+
}
|
|
4455
|
+
static isTargetedByRulesyncCommand(rulesyncCommand) {
|
|
4456
|
+
return this.isTargetedByRulesyncCommandDefault({
|
|
4457
|
+
rulesyncCommand,
|
|
4458
|
+
toolTarget: "reasonix"
|
|
4459
|
+
});
|
|
4460
|
+
}
|
|
4461
|
+
static async fromFile({ outputRoot = process.cwd(), relativeFilePath, validate = true, global = false }) {
|
|
4462
|
+
const paths = this.getSettablePaths({ global });
|
|
4463
|
+
const filePath = join(outputRoot, paths.relativeDirPath, relativeFilePath);
|
|
4464
|
+
const { frontmatter, body: content } = parseFrontmatter(await readFileContent(filePath), filePath);
|
|
4465
|
+
const result = ReasonixCommandFrontmatterSchema.safeParse(frontmatter);
|
|
4466
|
+
if (!result.success) throw new Error(`Invalid frontmatter in ${filePath}: ${formatError(result.error)}`);
|
|
4467
|
+
return new ReasonixCommand({
|
|
4468
|
+
outputRoot,
|
|
4469
|
+
relativeDirPath: paths.relativeDirPath,
|
|
4470
|
+
relativeFilePath,
|
|
4471
|
+
frontmatter: result.data,
|
|
4472
|
+
body: content.trim(),
|
|
4473
|
+
validate
|
|
4474
|
+
});
|
|
4475
|
+
}
|
|
4476
|
+
static forDeletion({ outputRoot = process.cwd(), relativeDirPath, relativeFilePath }) {
|
|
4477
|
+
return new ReasonixCommand({
|
|
4478
|
+
outputRoot,
|
|
4479
|
+
relativeDirPath,
|
|
4480
|
+
relativeFilePath,
|
|
4481
|
+
frontmatter: { description: "" },
|
|
4482
|
+
body: "",
|
|
4483
|
+
validate: false
|
|
4484
|
+
});
|
|
4485
|
+
}
|
|
4486
|
+
};
|
|
4487
|
+
//#endregion
|
|
4272
4488
|
//#region src/constants/roo-paths.ts
|
|
4273
4489
|
const ROO_DIR = ".roo";
|
|
4274
4490
|
const ROO_COMMANDS_DIR_PATH = join(ROO_DIR, "commands");
|
|
@@ -4400,6 +4616,217 @@ var RooCommand = class RooCommand extends ToolCommand {
|
|
|
4400
4616
|
}
|
|
4401
4617
|
};
|
|
4402
4618
|
//#endregion
|
|
4619
|
+
//#region src/constants/rovodev-paths.ts
|
|
4620
|
+
const ROVODEV_DIR = ".rovodev";
|
|
4621
|
+
const ROVODEV_SKILLS_DIR_PATH = join(ROVODEV_DIR, "skills");
|
|
4622
|
+
const ROVODEV_SUBAGENTS_DIR_PATH = join(ROVODEV_DIR, "subagents");
|
|
4623
|
+
const ROVODEV_MODULAR_RULES_DIR_PATH = join(ROVODEV_DIR, ".rulesync", "modular-rules");
|
|
4624
|
+
const ROVODEV_RULE_FILE_NAME = "AGENTS.md";
|
|
4625
|
+
const ROVODEV_LEGACY_RULE_FILE_NAME = "AGENTS.local.md";
|
|
4626
|
+
const ROVODEV_MCP_FILE_NAME = "mcp.json";
|
|
4627
|
+
const ROVODEV_CONFIG_FILE_NAME = "config.yml";
|
|
4628
|
+
const ROVODEV_AGENTS_SKILLS_DIR_PATH = join(".agents", "skills");
|
|
4629
|
+
const ROVODEV_PROMPTS_FILE_NAME = "prompts.yml";
|
|
4630
|
+
const ROVODEV_PROMPTS_DIR_PATH = join(ROVODEV_DIR, "prompts");
|
|
4631
|
+
//#endregion
|
|
4632
|
+
//#region src/types/tool-file.ts
|
|
4633
|
+
var ToolFile = class extends AiFile {};
|
|
4634
|
+
//#endregion
|
|
4635
|
+
//#region src/features/commands/rovodev-command.ts
|
|
4636
|
+
/**
|
|
4637
|
+
* Rovo Dev CLI "saved prompts": a file-based custom-command surface made of a
|
|
4638
|
+
* `prompts.yml` manifest (`{ name, description, content_file }` entries) plus
|
|
4639
|
+
* per-prompt Markdown content files, discovered in repo-root `.rovodev/`, cwd
|
|
4640
|
+
* `.rovodev/`, and global `~/.rovodev/`, and invoked via `/prompts [title] [extra]`.
|
|
4641
|
+
*
|
|
4642
|
+
* This class represents a single prompt's **content file** — pure Markdown,
|
|
4643
|
+
* no frontmatter — written to `.rovodev/prompts/<name>.md` (project) or
|
|
4644
|
+
* `~/.rovodev/prompts/<name>.md` (global). The `name`/`description` are kept
|
|
4645
|
+
* on the instance (not serialized into the content file itself) so that
|
|
4646
|
+
* {@link RovodevCommand.getAuxiliaryFiles} can build the sibling
|
|
4647
|
+
* `prompts.yml` manifest that indexes every generated prompt.
|
|
4648
|
+
*
|
|
4649
|
+
* @see https://support.atlassian.com/rovo/docs/save-and-reuse-a-prompt-in-rovo-dev-cli/
|
|
4650
|
+
* @see https://support.atlassian.com/rovo/docs/rovo-dev-cli-commands/
|
|
4651
|
+
*/
|
|
4652
|
+
var RovodevCommand = class RovodevCommand extends ToolCommand {
|
|
4653
|
+
name;
|
|
4654
|
+
description;
|
|
4655
|
+
body;
|
|
4656
|
+
constructor({ name, description, body, ...rest }) {
|
|
4657
|
+
super({
|
|
4658
|
+
...rest,
|
|
4659
|
+
fileContent: body
|
|
4660
|
+
});
|
|
4661
|
+
this.name = name;
|
|
4662
|
+
this.description = description;
|
|
4663
|
+
this.body = body;
|
|
4664
|
+
if (rest.validate) {
|
|
4665
|
+
const result = this.validate();
|
|
4666
|
+
if (!result.success) throw result.error;
|
|
4667
|
+
}
|
|
4668
|
+
}
|
|
4669
|
+
static getSettablePaths(_options = {}) {
|
|
4670
|
+
return { relativeDirPath: ROVODEV_PROMPTS_DIR_PATH };
|
|
4671
|
+
}
|
|
4672
|
+
getName() {
|
|
4673
|
+
return this.name;
|
|
4674
|
+
}
|
|
4675
|
+
getDescription() {
|
|
4676
|
+
return this.description;
|
|
4677
|
+
}
|
|
4678
|
+
getBody() {
|
|
4679
|
+
return this.body;
|
|
4680
|
+
}
|
|
4681
|
+
validate() {
|
|
4682
|
+
if (!this.name) return {
|
|
4683
|
+
success: false,
|
|
4684
|
+
error: /* @__PURE__ */ new Error(`${join(this.getRelativeDirPath(), this.getRelativeFilePath())}: Rovo Dev saved-prompt name must not be empty`)
|
|
4685
|
+
};
|
|
4686
|
+
return {
|
|
4687
|
+
success: true,
|
|
4688
|
+
error: null
|
|
4689
|
+
};
|
|
4690
|
+
}
|
|
4691
|
+
toRulesyncCommand() {
|
|
4692
|
+
const rulesyncFrontmatter = {
|
|
4693
|
+
targets: ["*"],
|
|
4694
|
+
description: this.description
|
|
4695
|
+
};
|
|
4696
|
+
const fileContent = stringifyFrontmatter(this.body, rulesyncFrontmatter);
|
|
4697
|
+
return new RulesyncCommand({
|
|
4698
|
+
outputRoot: ".",
|
|
4699
|
+
frontmatter: rulesyncFrontmatter,
|
|
4700
|
+
body: this.body,
|
|
4701
|
+
relativeDirPath: RulesyncCommand.getSettablePaths().relativeDirPath,
|
|
4702
|
+
relativeFilePath: this.getRelativeFilePath(),
|
|
4703
|
+
fileContent,
|
|
4704
|
+
validate: true
|
|
4705
|
+
});
|
|
4706
|
+
}
|
|
4707
|
+
static fromRulesyncCommand({ outputRoot = process.cwd(), rulesyncCommand, validate = true, global = false }) {
|
|
4708
|
+
const rulesyncFrontmatter = rulesyncCommand.getFrontmatter();
|
|
4709
|
+
const relativeFilePath = rulesyncCommand.getRelativeFilePath();
|
|
4710
|
+
const name = basename(relativeFilePath, ".md");
|
|
4711
|
+
const paths = this.getSettablePaths({ global });
|
|
4712
|
+
return new RovodevCommand({
|
|
4713
|
+
outputRoot,
|
|
4714
|
+
name,
|
|
4715
|
+
description: rulesyncFrontmatter.description ?? "",
|
|
4716
|
+
body: rulesyncCommand.getBody(),
|
|
4717
|
+
relativeDirPath: paths.relativeDirPath,
|
|
4718
|
+
relativeFilePath,
|
|
4719
|
+
validate,
|
|
4720
|
+
global
|
|
4721
|
+
});
|
|
4722
|
+
}
|
|
4723
|
+
static isTargetedByRulesyncCommand(rulesyncCommand) {
|
|
4724
|
+
return this.isTargetedByRulesyncCommandDefault({
|
|
4725
|
+
rulesyncCommand,
|
|
4726
|
+
toolTarget: "rovodev"
|
|
4727
|
+
});
|
|
4728
|
+
}
|
|
4729
|
+
static async fromFile({ outputRoot = process.cwd(), relativeFilePath, validate = true, global = false }) {
|
|
4730
|
+
const paths = this.getSettablePaths({ global });
|
|
4731
|
+
const body = (await readFileContent(join(outputRoot, paths.relativeDirPath, relativeFilePath))).trim();
|
|
4732
|
+
const name = basename(relativeFilePath, ".md");
|
|
4733
|
+
return new RovodevCommand({
|
|
4734
|
+
outputRoot,
|
|
4735
|
+
name,
|
|
4736
|
+
description: await lookupPromptDescription({
|
|
4737
|
+
outputRoot,
|
|
4738
|
+
relativeFilePath,
|
|
4739
|
+
name
|
|
4740
|
+
}),
|
|
4741
|
+
body,
|
|
4742
|
+
relativeDirPath: paths.relativeDirPath,
|
|
4743
|
+
relativeFilePath,
|
|
4744
|
+
validate,
|
|
4745
|
+
global
|
|
4746
|
+
});
|
|
4747
|
+
}
|
|
4748
|
+
static forDeletion({ outputRoot = process.cwd(), relativeDirPath, relativeFilePath, global = false }) {
|
|
4749
|
+
return new RovodevCommand({
|
|
4750
|
+
outputRoot,
|
|
4751
|
+
name: basename(relativeFilePath, ".md"),
|
|
4752
|
+
description: "",
|
|
4753
|
+
body: "",
|
|
4754
|
+
relativeDirPath,
|
|
4755
|
+
relativeFilePath,
|
|
4756
|
+
validate: false,
|
|
4757
|
+
global
|
|
4758
|
+
});
|
|
4759
|
+
}
|
|
4760
|
+
/**
|
|
4761
|
+
* Rebuilds `.rovodev/prompts.yml` (project) / `~/.rovodev/prompts.yml`
|
|
4762
|
+
* (global) from every `RovodevCommand` generated in this pass. The `prompts`
|
|
4763
|
+
* array is fully replaced with the current set of rulesync-managed prompts
|
|
4764
|
+
* (mirrors `RovodevMcp` fully replacing `mcpServers`); any other top-level
|
|
4765
|
+
* key in an existing manifest is preserved.
|
|
4766
|
+
*/
|
|
4767
|
+
static async getAuxiliaryFiles({ toolCommands, outputRoot = process.cwd(), global = false }) {
|
|
4768
|
+
const rovodevCommands = toolCommands.filter((command) => command instanceof RovodevCommand);
|
|
4769
|
+
if (rovodevCommands.length === 0) return [];
|
|
4770
|
+
const existingContent = await readFileContentOrNull(join(outputRoot, ROVODEV_DIR, ROVODEV_PROMPTS_FILE_NAME));
|
|
4771
|
+
let existing = {};
|
|
4772
|
+
if (existingContent) try {
|
|
4773
|
+
const parsed = load(existingContent);
|
|
4774
|
+
if (isPlainObject(parsed)) existing = parsed;
|
|
4775
|
+
} catch {}
|
|
4776
|
+
const prompts = rovodevCommands.map((command) => ({
|
|
4777
|
+
name: command.getName(),
|
|
4778
|
+
description: command.getDescription(),
|
|
4779
|
+
content_file: toPosixPath(join("prompts", command.getRelativeFilePath()))
|
|
4780
|
+
})).toSorted((a, b) => a.name.localeCompare(b.name));
|
|
4781
|
+
return [new RovodevPromptsManifest({
|
|
4782
|
+
outputRoot,
|
|
4783
|
+
relativeDirPath: ROVODEV_DIR,
|
|
4784
|
+
relativeFilePath: ROVODEV_PROMPTS_FILE_NAME,
|
|
4785
|
+
fileContent: dump({
|
|
4786
|
+
...existing,
|
|
4787
|
+
prompts
|
|
4788
|
+
}),
|
|
4789
|
+
global
|
|
4790
|
+
})];
|
|
4791
|
+
}
|
|
4792
|
+
};
|
|
4793
|
+
/**
|
|
4794
|
+
* The `prompts` entry shape read back from `content_file`, matched against
|
|
4795
|
+
* either the resolved path (relative to `prompts.yml`, i.e. the `.rovodev/`
|
|
4796
|
+
* directory) or the entry `name`, to recover the `description` that isn't
|
|
4797
|
+
* stored in the content file itself.
|
|
4798
|
+
*/
|
|
4799
|
+
async function lookupPromptDescription({ outputRoot, relativeFilePath, name }) {
|
|
4800
|
+
const manifestContent = await readFileContentOrNull(join(outputRoot, ROVODEV_DIR, ROVODEV_PROMPTS_FILE_NAME));
|
|
4801
|
+
if (!manifestContent) return "";
|
|
4802
|
+
let parsed;
|
|
4803
|
+
try {
|
|
4804
|
+
parsed = load(manifestContent);
|
|
4805
|
+
} catch {
|
|
4806
|
+
return "";
|
|
4807
|
+
}
|
|
4808
|
+
if (!isPlainObject(parsed) || !Array.isArray(parsed.prompts)) return "";
|
|
4809
|
+
const expectedContentFile = toPosixPath(join("prompts", relativeFilePath));
|
|
4810
|
+
const entry = parsed.prompts.find((candidate) => isRecord(candidate) && (candidate.content_file === expectedContentFile || candidate.name === name));
|
|
4811
|
+
return isRecord(entry) && typeof entry.description === "string" ? entry.description : "";
|
|
4812
|
+
}
|
|
4813
|
+
/**
|
|
4814
|
+
* The shared `.rovodev/prompts.yml` manifest that indexes every saved prompt.
|
|
4815
|
+
* Never deleted by orphan cleanup: it is regenerated (not individually
|
|
4816
|
+
* discovered) alongside the per-prompt content files it references.
|
|
4817
|
+
*/
|
|
4818
|
+
var RovodevPromptsManifest = class extends ToolFile {
|
|
4819
|
+
isDeletable() {
|
|
4820
|
+
return false;
|
|
4821
|
+
}
|
|
4822
|
+
validate() {
|
|
4823
|
+
return {
|
|
4824
|
+
success: true,
|
|
4825
|
+
error: null
|
|
4826
|
+
};
|
|
4827
|
+
}
|
|
4828
|
+
};
|
|
4829
|
+
//#endregion
|
|
4403
4830
|
//#region src/constants/takt-paths.ts
|
|
4404
4831
|
const TAKT_DIR = ".takt";
|
|
4405
4832
|
const TAKT_FACETS_SUBDIR = "facets";
|
|
@@ -4417,6 +4844,14 @@ const TAKT_RULE_OVERVIEW_FILE_NAME = "overview.md";
|
|
|
4417
4844
|
* @see https://github.com/nrslib/takt/blob/main/docs/configuration.md
|
|
4418
4845
|
*/
|
|
4419
4846
|
const TAKT_CONFIG_FILE_NAME = "config.yaml";
|
|
4847
|
+
/**
|
|
4848
|
+
* Top-level key in Takt's `config.yaml` holding the workflow MCP transport
|
|
4849
|
+
* allowlist (`stdio` / `sse` / `http` booleans). Takt is default-deny: a
|
|
4850
|
+
* transport must be explicitly enabled here before any workflow-defined MCP
|
|
4851
|
+
* server using it is permitted to run.
|
|
4852
|
+
* @see https://github.com/nrslib/takt/blob/main/docs/configuration.md
|
|
4853
|
+
*/
|
|
4854
|
+
const TAKT_WORKFLOW_MCP_SERVERS_KEY = "workflow_mcp_servers";
|
|
4420
4855
|
//#endregion
|
|
4421
4856
|
//#region src/features/takt-shared.ts
|
|
4422
4857
|
/**
|
|
@@ -4592,6 +5027,16 @@ const toolCommandFactories = /* @__PURE__ */ new Map([
|
|
|
4592
5027
|
supportsSubdirectory: false
|
|
4593
5028
|
}
|
|
4594
5029
|
}],
|
|
5030
|
+
["antigravity-cli", {
|
|
5031
|
+
class: AntigravityCliCommand,
|
|
5032
|
+
meta: {
|
|
5033
|
+
extension: "md",
|
|
5034
|
+
supportsProject: true,
|
|
5035
|
+
supportsGlobal: true,
|
|
5036
|
+
isSimulated: false,
|
|
5037
|
+
supportsSubdirectory: false
|
|
5038
|
+
}
|
|
5039
|
+
}],
|
|
4595
5040
|
["antigravity-ide", {
|
|
4596
5041
|
class: AntigravityIdeCommand,
|
|
4597
5042
|
meta: {
|
|
@@ -4737,7 +5182,7 @@ const toolCommandFactories = /* @__PURE__ */ new Map([
|
|
|
4737
5182
|
meta: {
|
|
4738
5183
|
extension: "md",
|
|
4739
5184
|
supportsProject: true,
|
|
4740
|
-
supportsGlobal:
|
|
5185
|
+
supportsGlobal: true,
|
|
4741
5186
|
isSimulated: false,
|
|
4742
5187
|
supportsSubdirectory: false
|
|
4743
5188
|
}
|
|
@@ -4782,6 +5227,16 @@ const toolCommandFactories = /* @__PURE__ */ new Map([
|
|
|
4782
5227
|
supportsSubdirectory: true
|
|
4783
5228
|
}
|
|
4784
5229
|
}],
|
|
5230
|
+
["reasonix", {
|
|
5231
|
+
class: ReasonixCommand,
|
|
5232
|
+
meta: {
|
|
5233
|
+
extension: "md",
|
|
5234
|
+
supportsProject: true,
|
|
5235
|
+
supportsGlobal: true,
|
|
5236
|
+
isSimulated: false,
|
|
5237
|
+
supportsSubdirectory: true
|
|
5238
|
+
}
|
|
5239
|
+
}],
|
|
4785
5240
|
["roo", {
|
|
4786
5241
|
class: RooCommand,
|
|
4787
5242
|
meta: {
|
|
@@ -4792,6 +5247,16 @@ const toolCommandFactories = /* @__PURE__ */ new Map([
|
|
|
4792
5247
|
supportsSubdirectory: true
|
|
4793
5248
|
}
|
|
4794
5249
|
}],
|
|
5250
|
+
["rovodev", {
|
|
5251
|
+
class: RovodevCommand,
|
|
5252
|
+
meta: {
|
|
5253
|
+
extension: "md",
|
|
5254
|
+
supportsProject: true,
|
|
5255
|
+
supportsGlobal: true,
|
|
5256
|
+
isSimulated: false,
|
|
5257
|
+
supportsSubdirectory: false
|
|
5258
|
+
}
|
|
5259
|
+
}],
|
|
4795
5260
|
["takt", {
|
|
4796
5261
|
class: TaktCommand,
|
|
4797
5262
|
meta: {
|
|
@@ -4849,7 +5314,7 @@ var CommandsProcessor = class extends FeatureProcessor {
|
|
|
4849
5314
|
const rulesyncCommands = rulesyncFiles.filter((file) => file instanceof RulesyncCommand);
|
|
4850
5315
|
const factory = this.getFactory(this.toolTarget);
|
|
4851
5316
|
const flattenedPathOrigins = /* @__PURE__ */ new Map();
|
|
4852
|
-
|
|
5317
|
+
const toolCommands = rulesyncCommands.map((rulesyncCommand) => {
|
|
4853
5318
|
if (!factory.class.isTargetedByRulesyncCommand(rulesyncCommand)) return null;
|
|
4854
5319
|
const originalRelativePath = rulesyncCommand.getRelativeFilePath();
|
|
4855
5320
|
const commandToConvert = factory.meta.supportsSubdirectory ? rulesyncCommand : this.flattenRelativeFilePath(rulesyncCommand);
|
|
@@ -4865,6 +5330,14 @@ var CommandsProcessor = class extends FeatureProcessor {
|
|
|
4865
5330
|
global: this.global
|
|
4866
5331
|
});
|
|
4867
5332
|
}).filter((command) => command !== null);
|
|
5333
|
+
const auxiliaryFiles = await factory.class.getAuxiliaryFiles?.({
|
|
5334
|
+
toolCommands,
|
|
5335
|
+
outputRoot: this.outputRoot,
|
|
5336
|
+
global: this.global
|
|
5337
|
+
});
|
|
5338
|
+
const result = [...toolCommands];
|
|
5339
|
+
if (auxiliaryFiles && auxiliaryFiles.length > 0) result.push(...auxiliaryFiles);
|
|
5340
|
+
return result;
|
|
4868
5341
|
}
|
|
4869
5342
|
async convertToolFilesToRulesyncFiles(toolFiles) {
|
|
4870
5343
|
return toolFiles.filter((file) => file instanceof ToolCommand).map((toolCommand) => {
|
|
@@ -5023,7 +5496,14 @@ const HookDefinitionSchema = z.looseObject({
|
|
|
5023
5496
|
name: z.optional(safeString),
|
|
5024
5497
|
description: z.optional(safeString),
|
|
5025
5498
|
failClosed: z.optional(z.boolean()),
|
|
5026
|
-
sequential: z.optional(z.boolean())
|
|
5499
|
+
sequential: z.optional(z.boolean()),
|
|
5500
|
+
async: z.optional(z.boolean()),
|
|
5501
|
+
env: z.optional(z.record(z.string(), safeString)),
|
|
5502
|
+
shell: z.optional(safeString),
|
|
5503
|
+
statusMessage: z.optional(safeString),
|
|
5504
|
+
headers: z.optional(z.record(z.string(), safeString)),
|
|
5505
|
+
allowedEnvVars: z.optional(z.array(z.string())),
|
|
5506
|
+
once: z.optional(z.boolean())
|
|
5027
5507
|
});
|
|
5028
5508
|
/** Hook events supported by Cursor. */
|
|
5029
5509
|
const CURSOR_HOOK_EVENTS = [
|
|
@@ -5087,6 +5567,30 @@ const CLAUDE_HOOK_EVENTS = [
|
|
|
5087
5567
|
"elicitation",
|
|
5088
5568
|
"elicitationResult"
|
|
5089
5569
|
];
|
|
5570
|
+
/**
|
|
5571
|
+
* Hook events supported by Devin Local (native `.devin/` hooks).
|
|
5572
|
+
*
|
|
5573
|
+
* Devin Local adopts a Claude-Code-style lifecycle hooks surface. It documents
|
|
5574
|
+
* seven events: `PreToolUse`, `PostToolUse`, `PermissionRequest`,
|
|
5575
|
+
* `UserPromptSubmit`, `Stop`, `SessionStart`, and `SessionEnd`. The
|
|
5576
|
+
* tool/permission events (`PreToolUse`/`PostToolUse`/`PermissionRequest`) carry
|
|
5577
|
+
* a `matcher` (regex against `tool_name`); the session/turn events do not.
|
|
5578
|
+
*
|
|
5579
|
+
* Hooks live in `.devin/hooks.v1.json` (project, standalone — the hooks object
|
|
5580
|
+
* is the entire file) or under the `"hooks"` key of `.devin/config.json` /
|
|
5581
|
+
* `~/.config/devin/config.json`.
|
|
5582
|
+
*
|
|
5583
|
+
* @see https://docs.devin.ai/cli/extensibility/hooks/overview
|
|
5584
|
+
*/
|
|
5585
|
+
const DEVIN_HOOK_EVENTS = [
|
|
5586
|
+
"sessionStart",
|
|
5587
|
+
"sessionEnd",
|
|
5588
|
+
"preToolUse",
|
|
5589
|
+
"postToolUse",
|
|
5590
|
+
"beforeSubmitPrompt",
|
|
5591
|
+
"stop",
|
|
5592
|
+
"permissionRequest"
|
|
5593
|
+
];
|
|
5090
5594
|
/** Hook events supported by OpenCode. */
|
|
5091
5595
|
const OPENCODE_HOOK_EVENTS = [
|
|
5092
5596
|
"sessionStart",
|
|
@@ -5129,7 +5633,11 @@ const COPILOT_HOOK_EVENTS = [
|
|
|
5129
5633
|
* `sessionStart`, `sessionEnd`, `userPromptSubmitted`, `preToolUse`,
|
|
5130
5634
|
* `postToolUse`, `postToolUseFailure`, `agentStop`, `subagentStart`,
|
|
5131
5635
|
* `subagentStop`, `errorOccurred`, `preCompact`, `permissionRequest`,
|
|
5132
|
-
* `notification`.
|
|
5636
|
+
* `notification`, `preMcpToolCall` ← `beforeMCPExecution`.
|
|
5637
|
+
*
|
|
5638
|
+
* `preMcpToolCall` (canonical `beforeMCPExecution`) was added in Copilot CLI
|
|
5639
|
+
* v1.0.51 (2026-05-20) for hook providers to control outgoing MCP request
|
|
5640
|
+
* metadata. https://github.com/github/copilot-cli/blob/main/changelog.md
|
|
5133
5641
|
*
|
|
5134
5642
|
* @see https://docs.github.com/en/copilot/reference/hooks-configuration
|
|
5135
5643
|
*/
|
|
@@ -5146,7 +5654,8 @@ const COPILOTCLI_HOOK_EVENTS = [
|
|
|
5146
5654
|
"afterError",
|
|
5147
5655
|
"preCompact",
|
|
5148
5656
|
"permissionRequest",
|
|
5149
|
-
"notification"
|
|
5657
|
+
"notification",
|
|
5658
|
+
"beforeMCPExecution"
|
|
5150
5659
|
];
|
|
5151
5660
|
/**
|
|
5152
5661
|
* Hook events supported by Factory Droid.
|
|
@@ -5231,6 +5740,25 @@ const KIRO_HOOK_EVENTS = [
|
|
|
5231
5740
|
"stop"
|
|
5232
5741
|
];
|
|
5233
5742
|
/**
|
|
5743
|
+
* Hook events supported by the Kiro IDE (`.kiro/hooks/*.json` v1).
|
|
5744
|
+
*
|
|
5745
|
+
* Kiro IDE 1.0 exposes PascalCase triggers. rulesync maps the canonical
|
|
5746
|
+
* lifecycle events that have a clean 1:1 IDE equivalent: `SessionStart`,
|
|
5747
|
+
* `Stop`, `UserPromptSubmit`, `PreToolUse`, and `PostToolUse`. The IDE also
|
|
5748
|
+
* documents file-event (`PostFileCreate`/`PostFileSave`/`PostFileDelete`) and
|
|
5749
|
+
* spec-task (`PreTaskExec`/`PostTaskExec`) triggers that have no canonical
|
|
5750
|
+
* equivalent; those can still be emitted verbatim via a `kiro-ide` override
|
|
5751
|
+
* block (unknown event keys pass through unchanged).
|
|
5752
|
+
* @see https://kiro.dev/docs/hooks/types/
|
|
5753
|
+
*/
|
|
5754
|
+
const KIRO_IDE_HOOK_EVENTS = [
|
|
5755
|
+
"sessionStart",
|
|
5756
|
+
"beforeSubmitPrompt",
|
|
5757
|
+
"preToolUse",
|
|
5758
|
+
"postToolUse",
|
|
5759
|
+
"stop"
|
|
5760
|
+
];
|
|
5761
|
+
/**
|
|
5234
5762
|
* Hook events supported by Google Antigravity (both the IDE and the CLI).
|
|
5235
5763
|
*
|
|
5236
5764
|
* Antigravity exposes a Claude-style hooks surface covering the five
|
|
@@ -5281,18 +5809,23 @@ const VIBE_HOOK_EVENTS = [
|
|
|
5281
5809
|
/**
|
|
5282
5810
|
* Hook events supported by JetBrains Junie CLI.
|
|
5283
5811
|
*
|
|
5284
|
-
* Junie CLI exposes
|
|
5285
|
-
* `~/.junie/config.json`: `SessionStart`, `UserPromptSubmit`, `
|
|
5286
|
-
* `
|
|
5287
|
-
*
|
|
5288
|
-
*
|
|
5289
|
-
*
|
|
5812
|
+
* Junie CLI exposes seven lifecycle events under the `"hooks"` key of
|
|
5813
|
+
* `~/.junie/config.json`: `SessionStart`, `UserPromptSubmit`, `PreToolUse`,
|
|
5814
|
+
* `Stop`, `StopFailure`, `PermissionRequest`, and `SessionEnd`. Matchers apply
|
|
5815
|
+
* to `SessionStart` (source), `PreToolUse` (tool name), `StopFailure` (error
|
|
5816
|
+
* type), `PermissionRequest` (tool name), and `SessionEnd` (reason);
|
|
5817
|
+
* `UserPromptSubmit` and `Stop` are matcher-less and always run. Only
|
|
5818
|
+
* `type: "command"` hooks are supported. Project-local hooks are ignored for
|
|
5819
|
+
* safety.
|
|
5290
5820
|
* @see https://junie.jetbrains.com/docs/junie-cli-hooks.html
|
|
5291
5821
|
*/
|
|
5292
5822
|
const JUNIE_HOOK_EVENTS = [
|
|
5293
5823
|
"sessionStart",
|
|
5294
5824
|
"beforeSubmitPrompt",
|
|
5825
|
+
"preToolUse",
|
|
5295
5826
|
"stop",
|
|
5827
|
+
"stopFailure",
|
|
5828
|
+
"permissionRequest",
|
|
5296
5829
|
"sessionEnd"
|
|
5297
5830
|
];
|
|
5298
5831
|
/**
|
|
@@ -5324,6 +5857,69 @@ const QWENCODE_HOOK_EVENTS = [
|
|
|
5324
5857
|
"todoCreated",
|
|
5325
5858
|
"todoCompleted"
|
|
5326
5859
|
];
|
|
5860
|
+
/**
|
|
5861
|
+
* Hook events supported by Reasonix.
|
|
5862
|
+
*
|
|
5863
|
+
* Reasonix's `.reasonix/settings.json` (project) / `~/.reasonix/settings.json`
|
|
5864
|
+
* (global) documents a ten-event surface (`PreToolUse`, `PostToolUse`,
|
|
5865
|
+
* `UserPromptSubmit`, `Stop`, `PostLLMCall`, `SessionStart`, `SessionEnd`,
|
|
5866
|
+
* `SubagentStop`, `Notification`, `PreCompact`), but only the four events the
|
|
5867
|
+
* upstream issue scoped in are mapped here: `PreToolUse`, `PostToolUse`,
|
|
5868
|
+
* `UserPromptSubmit` ← `beforeSubmitPrompt`, and `Stop`. `match` (Reasonix's
|
|
5869
|
+
* matcher field name) is honored only on `PreToolUse`/`PostToolUse`, matching
|
|
5870
|
+
* the canonical `matcher` field's tool-event scoping used by other adapters.
|
|
5871
|
+
* @see https://github.com/esengine/DeepSeek-Reasonix/blob/main-v2/docs/DESKTOP_HOOKS.zh-CN.md
|
|
5872
|
+
*/
|
|
5873
|
+
const REASONIX_HOOK_EVENTS = [
|
|
5874
|
+
"preToolUse",
|
|
5875
|
+
"postToolUse",
|
|
5876
|
+
"beforeSubmitPrompt",
|
|
5877
|
+
"stop"
|
|
5878
|
+
];
|
|
5879
|
+
/**
|
|
5880
|
+
* Hook events supported by Hermes Agent's native Shell Hooks system.
|
|
5881
|
+
*
|
|
5882
|
+
* Hermes validates hook events against a fixed `VALID_HOOKS` set:
|
|
5883
|
+
* `pre_tool_call`, `post_tool_call`, `pre_llm_call`, `post_llm_call`,
|
|
5884
|
+
* `pre_verify`, `on_session_start`, `on_session_end`, `on_session_finalize`,
|
|
5885
|
+
* `on_session_reset`, `subagent_start`, `subagent_stop`, `pre_gateway_dispatch`,
|
|
5886
|
+
* `pre_approval_request`, `post_approval_response`, `transform_tool_result`,
|
|
5887
|
+
* `transform_terminal_output`, `transform_llm_output`. Only the events with a
|
|
5888
|
+
* clean 1:1 canonical equivalent are mapped here; the remaining `VALID_HOOKS`
|
|
5889
|
+
* entries (`pre_verify`, `on_session_finalize`, `on_session_reset`,
|
|
5890
|
+
* `pre_gateway_dispatch`, `pre_approval_request`, `post_approval_response`, the
|
|
5891
|
+
* `transform_*` result-rewriting hooks) have no canonical rulesync equivalent,
|
|
5892
|
+
* so no canonical event maps to them.
|
|
5893
|
+
* @see https://github.com/NousResearch/hermes-agent/blob/main/website/docs/user-guide/features/hooks.md
|
|
5894
|
+
*/
|
|
5895
|
+
const HERMESAGENT_HOOK_EVENTS = [
|
|
5896
|
+
"sessionStart",
|
|
5897
|
+
"sessionEnd",
|
|
5898
|
+
"preToolUse",
|
|
5899
|
+
"postToolUse",
|
|
5900
|
+
"preModelInvocation",
|
|
5901
|
+
"postModelInvocation",
|
|
5902
|
+
"subagentStart",
|
|
5903
|
+
"subagentStop"
|
|
5904
|
+
];
|
|
5905
|
+
/**
|
|
5906
|
+
* Map canonical camelCase event names to Hermes Agent's native `VALID_HOOKS`
|
|
5907
|
+
* snake_case keys under the `hooks:` block of `~/.hermes/config.yaml`.
|
|
5908
|
+
*/
|
|
5909
|
+
const CANONICAL_TO_HERMESAGENT_EVENT_NAMES = {
|
|
5910
|
+
sessionStart: "on_session_start",
|
|
5911
|
+
sessionEnd: "on_session_end",
|
|
5912
|
+
preToolUse: "pre_tool_call",
|
|
5913
|
+
postToolUse: "post_tool_call",
|
|
5914
|
+
preModelInvocation: "pre_llm_call",
|
|
5915
|
+
postModelInvocation: "post_llm_call",
|
|
5916
|
+
subagentStart: "subagent_start",
|
|
5917
|
+
subagentStop: "subagent_stop"
|
|
5918
|
+
};
|
|
5919
|
+
/**
|
|
5920
|
+
* Map Hermes Agent's native `VALID_HOOKS` keys back to canonical camelCase.
|
|
5921
|
+
*/
|
|
5922
|
+
const HERMESAGENT_TO_CANONICAL_EVENT_NAMES = Object.fromEntries(Object.entries(CANONICAL_TO_HERMESAGENT_EVENT_NAMES).map(([k, v]) => [v, k]));
|
|
5327
5923
|
const hooksRecordSchema = z.record(z.string(), z.array(HookDefinitionSchema));
|
|
5328
5924
|
/**
|
|
5329
5925
|
* Canonical hooks config (canonical event names in camelCase).
|
|
@@ -5343,12 +5939,15 @@ const HooksConfigSchema = z.looseObject({
|
|
|
5343
5939
|
deepagents: z.optional(z.looseObject({ hooks: z.optional(hooksRecordSchema) })),
|
|
5344
5940
|
kiro: z.optional(z.looseObject({ hooks: z.optional(hooksRecordSchema) })),
|
|
5345
5941
|
"kiro-cli": z.optional(z.looseObject({ hooks: z.optional(hooksRecordSchema) })),
|
|
5942
|
+
"kiro-ide": z.optional(z.looseObject({ hooks: z.optional(hooksRecordSchema) })),
|
|
5346
5943
|
devin: z.optional(z.looseObject({ hooks: z.optional(hooksRecordSchema) })),
|
|
5347
5944
|
augmentcode: z.optional(z.looseObject({ hooks: z.optional(hooksRecordSchema) })),
|
|
5348
5945
|
"antigravity-ide": z.optional(z.looseObject({ hooks: z.optional(hooksRecordSchema) })),
|
|
5349
5946
|
"antigravity-cli": z.optional(z.looseObject({ hooks: z.optional(hooksRecordSchema) })),
|
|
5947
|
+
hermesagent: z.optional(z.looseObject({ hooks: z.optional(hooksRecordSchema) })),
|
|
5350
5948
|
junie: z.optional(z.looseObject({ hooks: z.optional(hooksRecordSchema) })),
|
|
5351
5949
|
vibe: z.optional(z.looseObject({ hooks: z.optional(hooksRecordSchema) })),
|
|
5950
|
+
reasonix: z.optional(z.looseObject({ hooks: z.optional(hooksRecordSchema) })),
|
|
5352
5951
|
qwencode: z.optional(z.looseObject({
|
|
5353
5952
|
hooks: z.optional(hooksRecordSchema),
|
|
5354
5953
|
disableAllHooks: z.optional(z.boolean())
|
|
@@ -5394,6 +5993,26 @@ const CANONICAL_TO_CLAUDE_EVENT_NAMES = {
|
|
|
5394
5993
|
*/
|
|
5395
5994
|
const CLAUDE_TO_CANONICAL_EVENT_NAMES = Object.fromEntries(Object.entries(CANONICAL_TO_CLAUDE_EVENT_NAMES).map(([k, v]) => [v, k]));
|
|
5396
5995
|
/**
|
|
5996
|
+
* Map canonical camelCase event names to Devin Local PascalCase.
|
|
5997
|
+
*
|
|
5998
|
+
* Devin Local reuses the same Claude-style PascalCase event names for the
|
|
5999
|
+
* subset of events it supports.
|
|
6000
|
+
* @see https://docs.devin.ai/cli/extensibility/hooks/overview
|
|
6001
|
+
*/
|
|
6002
|
+
const CANONICAL_TO_DEVIN_EVENT_NAMES = {
|
|
6003
|
+
sessionStart: "SessionStart",
|
|
6004
|
+
sessionEnd: "SessionEnd",
|
|
6005
|
+
preToolUse: "PreToolUse",
|
|
6006
|
+
postToolUse: "PostToolUse",
|
|
6007
|
+
beforeSubmitPrompt: "UserPromptSubmit",
|
|
6008
|
+
stop: "Stop",
|
|
6009
|
+
permissionRequest: "PermissionRequest"
|
|
6010
|
+
};
|
|
6011
|
+
/**
|
|
6012
|
+
* Map Devin Local PascalCase event names to canonical camelCase.
|
|
6013
|
+
*/
|
|
6014
|
+
const DEVIN_TO_CANONICAL_EVENT_NAMES = Object.fromEntries(Object.entries(CANONICAL_TO_DEVIN_EVENT_NAMES).map(([k, v]) => [v, k]));
|
|
6015
|
+
/**
|
|
5397
6016
|
* Map canonical camelCase event names to AugmentCode PascalCase.
|
|
5398
6017
|
* Auggie reuses the same PascalCase names as Claude for the events it supports.
|
|
5399
6018
|
*/
|
|
@@ -5524,7 +6143,8 @@ const CANONICAL_TO_COPILOTCLI_EVENT_NAMES = {
|
|
|
5524
6143
|
afterError: "errorOccurred",
|
|
5525
6144
|
preCompact: "preCompact",
|
|
5526
6145
|
permissionRequest: "permissionRequest",
|
|
5527
|
-
notification: "notification"
|
|
6146
|
+
notification: "notification",
|
|
6147
|
+
beforeMCPExecution: "preMcpToolCall"
|
|
5528
6148
|
};
|
|
5529
6149
|
/** Map GitHub Copilot CLI event names back to canonical camelCase. */
|
|
5530
6150
|
const COPILOTCLI_TO_CANONICAL_EVENT_NAMES = Object.fromEntries(Object.entries(CANONICAL_TO_COPILOTCLI_EVENT_NAMES).map(([k, v]) => [v, k]));
|
|
@@ -5606,13 +6226,35 @@ const CANONICAL_TO_KIRO_EVENT_NAMES = {
|
|
|
5606
6226
|
*/
|
|
5607
6227
|
const KIRO_TO_CANONICAL_EVENT_NAMES = Object.fromEntries(Object.entries(CANONICAL_TO_KIRO_EVENT_NAMES).map(([k, v]) => [v, k]));
|
|
5608
6228
|
/**
|
|
6229
|
+
* Map canonical camelCase event names to Kiro IDE PascalCase triggers.
|
|
6230
|
+
*
|
|
6231
|
+
* Only the canonical lifecycle events with a clean IDE equivalent are mapped.
|
|
6232
|
+
* Unknown keys (e.g. IDE-only `PostFileSave`/`PreTaskExec` set via a `kiro-ide`
|
|
6233
|
+
* override) pass through unchanged.
|
|
6234
|
+
* @see https://kiro.dev/docs/hooks/types/
|
|
6235
|
+
*/
|
|
6236
|
+
const CANONICAL_TO_KIRO_IDE_EVENT_NAMES = {
|
|
6237
|
+
sessionStart: "SessionStart",
|
|
6238
|
+
beforeSubmitPrompt: "UserPromptSubmit",
|
|
6239
|
+
preToolUse: "PreToolUse",
|
|
6240
|
+
postToolUse: "PostToolUse",
|
|
6241
|
+
stop: "Stop"
|
|
6242
|
+
};
|
|
6243
|
+
/**
|
|
6244
|
+
* Map Kiro IDE PascalCase trigger names to canonical camelCase.
|
|
6245
|
+
*/
|
|
6246
|
+
const KIRO_IDE_TO_CANONICAL_EVENT_NAMES = Object.fromEntries(Object.entries(CANONICAL_TO_KIRO_IDE_EVENT_NAMES).map(([k, v]) => [v, k]));
|
|
6247
|
+
/**
|
|
5609
6248
|
* Map canonical camelCase event names to Junie PascalCase.
|
|
5610
6249
|
* Junie reuses the same PascalCase names as Claude for the events it supports.
|
|
5611
6250
|
*/
|
|
5612
6251
|
const CANONICAL_TO_JUNIE_EVENT_NAMES = {
|
|
5613
6252
|
sessionStart: "SessionStart",
|
|
5614
6253
|
beforeSubmitPrompt: "UserPromptSubmit",
|
|
6254
|
+
preToolUse: "PreToolUse",
|
|
5615
6255
|
stop: "Stop",
|
|
6256
|
+
stopFailure: "StopFailure",
|
|
6257
|
+
permissionRequest: "PermissionRequest",
|
|
5616
6258
|
sessionEnd: "SessionEnd"
|
|
5617
6259
|
};
|
|
5618
6260
|
/**
|
|
@@ -5665,19 +6307,22 @@ const CANONICAL_TO_QWENCODE_EVENT_NAMES = {
|
|
|
5665
6307
|
* Map Qwen Code PascalCase event names to canonical camelCase.
|
|
5666
6308
|
*/
|
|
5667
6309
|
const QWENCODE_TO_CANONICAL_EVENT_NAMES = Object.fromEntries(Object.entries(CANONICAL_TO_QWENCODE_EVENT_NAMES).map(([k, v]) => [v, k]));
|
|
5668
|
-
|
|
5669
|
-
|
|
5670
|
-
|
|
5671
|
-
|
|
5672
|
-
|
|
5673
|
-
|
|
5674
|
-
const
|
|
5675
|
-
|
|
5676
|
-
|
|
5677
|
-
|
|
5678
|
-
|
|
5679
|
-
|
|
5680
|
-
|
|
6310
|
+
/**
|
|
6311
|
+
* Map canonical camelCase event names to Reasonix PascalCase.
|
|
6312
|
+
* Reasonix explicitly mirrors Claude Code's hooks model, so it reuses the same
|
|
6313
|
+
* PascalCase names for the four events rulesync maps.
|
|
6314
|
+
* @see https://github.com/esengine/DeepSeek-Reasonix/blob/main-v2/docs/DESKTOP_HOOKS.zh-CN.md
|
|
6315
|
+
*/
|
|
6316
|
+
const CANONICAL_TO_REASONIX_EVENT_NAMES = {
|
|
6317
|
+
preToolUse: "PreToolUse",
|
|
6318
|
+
postToolUse: "PostToolUse",
|
|
6319
|
+
beforeSubmitPrompt: "UserPromptSubmit",
|
|
6320
|
+
stop: "Stop"
|
|
6321
|
+
};
|
|
6322
|
+
/**
|
|
6323
|
+
* Map Reasonix PascalCase event names to canonical camelCase.
|
|
6324
|
+
*/
|
|
6325
|
+
const REASONIX_TO_CANONICAL_EVENT_NAMES = Object.fromEntries(Object.entries(CANONICAL_TO_REASONIX_EVENT_NAMES).map(([k, v]) => [v, k]));
|
|
5681
6326
|
//#endregion
|
|
5682
6327
|
//#region src/utils/prototype-pollution.ts
|
|
5683
6328
|
/**
|
|
@@ -5725,7 +6370,7 @@ function isToolMatcherEntry(x) {
|
|
|
5725
6370
|
/**
|
|
5726
6371
|
* Filter the shared canonical hooks to the supported events and merge tool overrides on top.
|
|
5727
6372
|
*/
|
|
5728
|
-
function buildEffectiveHooks({ config, toolOverrideHooks, supportedEvents }) {
|
|
6373
|
+
function buildEffectiveHooks$1({ config, toolOverrideHooks, supportedEvents }) {
|
|
5729
6374
|
const supported = new Set(supportedEvents);
|
|
5730
6375
|
const sharedHooks = {};
|
|
5731
6376
|
for (const [event, defs] of Object.entries(config.hooks)) if (supported.has(event)) sharedHooks[event] = defs;
|
|
@@ -5754,7 +6399,7 @@ function groupDefinitionsByMatcher(definitions) {
|
|
|
5754
6399
|
function applyCommandPrefix({ def, converterConfig }) {
|
|
5755
6400
|
const commandText = def.command;
|
|
5756
6401
|
const trimmedCommand = typeof commandText === "string" ? commandText.trimStart() : void 0;
|
|
5757
|
-
return converterConfig.projectDirVar !== "" && typeof trimmedCommand === "string" && !trimmedCommand.startsWith("$") && (!converterConfig.prefixDotRelativeCommandsOnly || trimmedCommand.startsWith(".")) && typeof trimmedCommand === "string" ?
|
|
6402
|
+
return converterConfig.projectDirVar !== "" && typeof trimmedCommand === "string" && !trimmedCommand.startsWith("$") && (!converterConfig.prefixDotRelativeCommandsOnly || trimmedCommand.startsWith(".")) && typeof trimmedCommand === "string" ? `"${converterConfig.projectDirVar}"/${trimmedCommand.replace(/^\.\//, "")}` : def.command;
|
|
5758
6403
|
}
|
|
5759
6404
|
/**
|
|
5760
6405
|
* Convert the definitions of a single matcher group into tool hook entries,
|
|
@@ -5787,7 +6432,7 @@ function buildToolHooks({ defs, converterConfig }) {
|
|
|
5787
6432
|
* (e.g. beforeSubmitPrompt → UserPromptSubmit).
|
|
5788
6433
|
*/
|
|
5789
6434
|
function canonicalToToolHooks({ config, toolOverrideHooks, converterConfig, logger }) {
|
|
5790
|
-
const effectiveHooks = buildEffectiveHooks({
|
|
6435
|
+
const effectiveHooks = buildEffectiveHooks$1({
|
|
5791
6436
|
config,
|
|
5792
6437
|
toolOverrideHooks,
|
|
5793
6438
|
supportedEvents: converterConfig.supportedEvents
|
|
@@ -5830,7 +6475,13 @@ function canonicalToToolHooks({ config, toolOverrideHooks, converterConfig, logg
|
|
|
5830
6475
|
*/
|
|
5831
6476
|
function stripCommandPrefix({ command, converterConfig }) {
|
|
5832
6477
|
const cmd = typeof command === "string" ? command : void 0;
|
|
5833
|
-
if (converterConfig.projectDirVar
|
|
6478
|
+
if (converterConfig.projectDirVar === "" || typeof cmd !== "string") return cmd;
|
|
6479
|
+
const quotedPrefix = `"${converterConfig.projectDirVar}"/`;
|
|
6480
|
+
if (cmd.startsWith(quotedPrefix)) return `./${cmd.slice(quotedPrefix.length)}`;
|
|
6481
|
+
if (cmd.includes(`${converterConfig.projectDirVar}/`)) {
|
|
6482
|
+
const escapedVar = converterConfig.projectDirVar.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
6483
|
+
return cmd.replace(new RegExp(`^${escapedVar}\\/?`), "./");
|
|
6484
|
+
}
|
|
5834
6485
|
return cmd;
|
|
5835
6486
|
}
|
|
5836
6487
|
/**
|
|
@@ -5883,9 +6534,6 @@ function toolHooksToCanonical({ hooks, converterConfig }) {
|
|
|
5883
6534
|
return canonical;
|
|
5884
6535
|
}
|
|
5885
6536
|
//#endregion
|
|
5886
|
-
//#region src/types/tool-file.ts
|
|
5887
|
-
var ToolFile = class extends AiFile {};
|
|
5888
|
-
//#endregion
|
|
5889
6537
|
//#region src/features/hooks/rulesync-hooks.ts
|
|
5890
6538
|
var RulesyncHooks = class RulesyncHooks extends RulesyncFile {
|
|
5891
6539
|
json;
|
|
@@ -6439,9 +7087,15 @@ const CODEXCLI_CONVERTER_CONFIG = {
|
|
|
6439
7087
|
passthroughFields: ["name", "description"]
|
|
6440
7088
|
};
|
|
6441
7089
|
/**
|
|
6442
|
-
* Build the content for `.codex/config.toml
|
|
6443
|
-
* Reads the existing file (if any), parses TOML,
|
|
6444
|
-
*
|
|
7090
|
+
* Build the content for `.codex/config.toml`, cleaning up the deprecated `codex_hooks` key.
|
|
7091
|
+
* Reads the existing file (if any), parses TOML, and returns the content without writing to
|
|
7092
|
+
* disk. The caller is responsible for writing via the normal write phase.
|
|
7093
|
+
*
|
|
7094
|
+
* Hooks are GA and enabled by default in Codex CLI, so `[features] hooks = true` is no longer
|
|
7095
|
+
* required and is intentionally NOT force-written here (doing so used to be harmless/idempotent,
|
|
7096
|
+
* but is now redundant and could mask a user's own `hooks = false` opt-out on a later edit).
|
|
7097
|
+
* See https://developers.openai.com/codex/hooks. Only the legacy `codex_hooks` alias — superseded
|
|
7098
|
+
* by `hooks` — is still cleaned up.
|
|
6445
7099
|
*/
|
|
6446
7100
|
async function buildCodexConfigTomlContent({ outputRoot }) {
|
|
6447
7101
|
const configPath = join(outputRoot, CODEXCLI_DIR, CODEXCLI_MCP_FILE_NAME);
|
|
@@ -6452,10 +7106,7 @@ async function buildCodexConfigTomlContent({ outputRoot }) {
|
|
|
6452
7106
|
} catch (error) {
|
|
6453
7107
|
throw new Error(`Failed to parse existing Codex CLI config at ${configPath}: ${formatError(error)}`, { cause: error });
|
|
6454
7108
|
}
|
|
6455
|
-
if (typeof configToml.features
|
|
6456
|
-
const features = configToml.features;
|
|
6457
|
-
delete features.codex_hooks;
|
|
6458
|
-
features.hooks = true;
|
|
7109
|
+
if (typeof configToml.features === "object" && configToml.features !== null) delete configToml.features.codex_hooks;
|
|
6459
7110
|
return smolToml.stringify(configToml);
|
|
6460
7111
|
}
|
|
6461
7112
|
/**
|
|
@@ -6723,6 +7374,20 @@ var CopilotHooks = class CopilotHooks extends ToolHooks {
|
|
|
6723
7374
|
}
|
|
6724
7375
|
};
|
|
6725
7376
|
//#endregion
|
|
7377
|
+
//#region src/utils/object.ts
|
|
7378
|
+
/**
|
|
7379
|
+
* Return a shallow copy of `obj` keeping only the entries whose value is
|
|
7380
|
+
* neither `undefined` nor `null`.
|
|
7381
|
+
*
|
|
7382
|
+
* Used to assemble generated config objects without one conditional spread per
|
|
7383
|
+
* optional field (which would otherwise exceed the lint complexity budget).
|
|
7384
|
+
*/
|
|
7385
|
+
function compact(obj) {
|
|
7386
|
+
const result = {};
|
|
7387
|
+
for (const [key, value] of Object.entries(obj)) if (value !== void 0 && value !== null) result[key] = value;
|
|
7388
|
+
return result;
|
|
7389
|
+
}
|
|
7390
|
+
//#endregion
|
|
6726
7391
|
//#region src/features/hooks/copilotcli-hooks.ts
|
|
6727
7392
|
/**
|
|
6728
7393
|
* GitHub Copilot CLI hooks.
|
|
@@ -6734,7 +7399,7 @@ var CopilotHooks = class CopilotHooks extends ToolHooks {
|
|
|
6734
7399
|
* (`sessionStart`, `sessionEnd`, `userPromptSubmitted`, `preToolUse`,
|
|
6735
7400
|
* `postToolUse`, `postToolUseFailure`, `agentStop`, `subagentStart`,
|
|
6736
7401
|
* `subagentStop`, `errorOccurred`, `preCompact`, `permissionRequest`,
|
|
6737
|
-
* `notification`). Each entry supports three hook types:
|
|
7402
|
+
* `notification`, `preMcpToolCall`). Each entry supports three hook types:
|
|
6738
7403
|
*
|
|
6739
7404
|
* - `command` — the `bash` / `powershell` command-field shape with optional
|
|
6740
7405
|
* `timeoutSec`, plus optional `cwd` / `env`.
|
|
@@ -6847,20 +7512,24 @@ function buildCopilotCliEntriesForEvent({ eventName, definitions, canonicalSchem
|
|
|
6847
7512
|
} else if (hookType === "http") entries.push({
|
|
6848
7513
|
type: "http",
|
|
6849
7514
|
...matcherPart,
|
|
6850
|
-
...
|
|
7515
|
+
...compact({
|
|
7516
|
+
url: def.url,
|
|
7517
|
+
headers: def.headers,
|
|
7518
|
+
allowedEnvVars: def.allowedEnvVars
|
|
7519
|
+
}),
|
|
7520
|
+
...timeoutPart,
|
|
7521
|
+
...rest
|
|
7522
|
+
});
|
|
7523
|
+
else entries.push({
|
|
7524
|
+
type: "command",
|
|
7525
|
+
...matcherPart,
|
|
7526
|
+
...compact({
|
|
7527
|
+
[commandField]: def.command,
|
|
7528
|
+
env: def.env
|
|
7529
|
+
}),
|
|
6851
7530
|
...timeoutPart,
|
|
6852
7531
|
...rest
|
|
6853
7532
|
});
|
|
6854
|
-
else {
|
|
6855
|
-
const command = def.command;
|
|
6856
|
-
entries.push({
|
|
6857
|
-
type: "command",
|
|
6858
|
-
...matcherPart,
|
|
6859
|
-
...command !== void 0 && command !== null && { [commandField]: command },
|
|
6860
|
-
...timeoutPart,
|
|
6861
|
-
...rest
|
|
6862
|
-
});
|
|
6863
|
-
}
|
|
6864
7533
|
}
|
|
6865
7534
|
return entries;
|
|
6866
7535
|
}
|
|
@@ -7269,148 +7938,63 @@ var DeepagentsHooks = class DeepagentsHooks extends ToolHooks {
|
|
|
7269
7938
|
};
|
|
7270
7939
|
//#endregion
|
|
7271
7940
|
//#region src/features/hooks/devin-hooks.ts
|
|
7941
|
+
const DEVIN_CONVERTER_CONFIG = {
|
|
7942
|
+
supportedEvents: DEVIN_HOOK_EVENTS,
|
|
7943
|
+
canonicalToToolEventNames: CANONICAL_TO_DEVIN_EVENT_NAMES,
|
|
7944
|
+
toolToCanonicalEventNames: DEVIN_TO_CANONICAL_EVENT_NAMES,
|
|
7945
|
+
projectDirVar: "",
|
|
7946
|
+
supportedHookTypes: /* @__PURE__ */ new Set(["command", "prompt"]),
|
|
7947
|
+
noMatcherEvents: /* @__PURE__ */ new Set([
|
|
7948
|
+
"sessionStart",
|
|
7949
|
+
"sessionEnd",
|
|
7950
|
+
"stop",
|
|
7951
|
+
"beforeSubmitPrompt"
|
|
7952
|
+
])
|
|
7953
|
+
};
|
|
7272
7954
|
/**
|
|
7273
|
-
*
|
|
7274
|
-
*
|
|
7275
|
-
* The mapping is bijective for every event with a Devin equivalent, so the
|
|
7276
|
-
* conversion round-trips cleanly. Devin splits the generic tool lifecycle
|
|
7277
|
-
* into file/command/MCP specific events, so we line them up with the closest
|
|
7278
|
-
* file/command/MCP specific canonical events rather than the generic
|
|
7279
|
-
* preToolUse/postToolUse pair:
|
|
7280
|
-
*
|
|
7281
|
-
* - beforeReadFile ⇄ pre_read_code
|
|
7282
|
-
* - beforeTabFileRead ⇄ post_read_code
|
|
7283
|
-
* - afterTabFileEdit ⇄ pre_write_code
|
|
7284
|
-
* - afterFileEdit ⇄ post_write_code
|
|
7285
|
-
* - beforeShellExecution⇄ pre_run_command
|
|
7286
|
-
* - afterShellExecution ⇄ post_run_command
|
|
7287
|
-
* - beforeMCPExecution ⇄ pre_mcp_tool_use
|
|
7288
|
-
* - afterMCPExecution ⇄ post_mcp_tool_use
|
|
7289
|
-
* - beforeSubmitPrompt ⇄ pre_user_prompt
|
|
7290
|
-
* - afterAgentResponse ⇄ post_cascade_response
|
|
7291
|
-
* - beforeAgentResponse ⇄ post_cascade_response_with_transcript
|
|
7292
|
-
* - worktreeCreate ⇄ post_setup_worktree
|
|
7293
|
-
*
|
|
7294
|
-
* NOTE on the before/after prefix mismatch: rulesync's canonical vocabulary has
|
|
7295
|
-
* no `afterReadFile` or `beforeWriteFile`/`beforeFileEdit` event — the only read
|
|
7296
|
-
* events are `beforeReadFile`/`beforeTabFileRead` (both read-side) and the only
|
|
7297
|
-
* edit events are `afterFileEdit`/`afterTabFileEdit` (both edit-side). To cover
|
|
7298
|
-
* all twelve Devin events bijectively we therefore pair the second read/write
|
|
7299
|
-
* event by DOMAIN (read↔read, write↔write) rather than by timing, which inverts
|
|
7300
|
-
* the prefix on `post_read_code` (⇄ beforeTabFileRead) and `pre_write_code`
|
|
7301
|
-
* (⇄ afterTabFileEdit). The alternative — pairing by timing — would leave two
|
|
7302
|
-
* Devin events unmapped and drop user hooks. The round-trip stays lossless;
|
|
7303
|
-
* only the human-readable prefix differs, so this is a deliberate trade-off.
|
|
7304
|
-
*
|
|
7305
|
-
* Canonical events that have no Devin equivalent (e.g. sessionStart, stop)
|
|
7306
|
-
* are dropped with a logged warning during export.
|
|
7307
|
-
*/
|
|
7308
|
-
const CANONICAL_TO_DEVIN_EVENT_NAMES = {
|
|
7309
|
-
beforeReadFile: "pre_read_code",
|
|
7310
|
-
beforeTabFileRead: "post_read_code",
|
|
7311
|
-
afterTabFileEdit: "pre_write_code",
|
|
7312
|
-
afterFileEdit: "post_write_code",
|
|
7313
|
-
beforeShellExecution: "pre_run_command",
|
|
7314
|
-
afterShellExecution: "post_run_command",
|
|
7315
|
-
beforeMCPExecution: "pre_mcp_tool_use",
|
|
7316
|
-
afterMCPExecution: "post_mcp_tool_use",
|
|
7317
|
-
beforeSubmitPrompt: "pre_user_prompt",
|
|
7318
|
-
afterAgentResponse: "post_cascade_response",
|
|
7319
|
-
beforeAgentResponse: "post_cascade_response_with_transcript",
|
|
7320
|
-
worktreeCreate: "post_setup_worktree"
|
|
7321
|
-
};
|
|
7322
|
-
/**
|
|
7323
|
-
* Map Devin event names back to canonical camelCase event names.
|
|
7324
|
-
*/
|
|
7325
|
-
const DEVIN_TO_CANONICAL_EVENT_NAMES = Object.fromEntries(Object.entries(CANONICAL_TO_DEVIN_EVENT_NAMES).map(([k, v]) => [v, k]));
|
|
7326
|
-
/**
|
|
7327
|
-
* Canonical hook events supported by Devin (the keys of the bijective map).
|
|
7955
|
+
* Hooks generator for Devin Local (native `.devin/` hooks).
|
|
7328
7956
|
*
|
|
7329
|
-
*
|
|
7330
|
-
*
|
|
7331
|
-
*
|
|
7332
|
-
*/
|
|
7333
|
-
const DEVIN_HOOK_EVENTS = [
|
|
7334
|
-
"beforeReadFile",
|
|
7335
|
-
"beforeTabFileRead",
|
|
7336
|
-
"afterTabFileEdit",
|
|
7337
|
-
"afterFileEdit",
|
|
7338
|
-
"beforeShellExecution",
|
|
7339
|
-
"afterShellExecution",
|
|
7340
|
-
"beforeMCPExecution",
|
|
7341
|
-
"afterMCPExecution",
|
|
7342
|
-
"beforeSubmitPrompt",
|
|
7343
|
-
"afterAgentResponse",
|
|
7344
|
-
"beforeAgentResponse",
|
|
7345
|
-
"worktreeCreate"
|
|
7346
|
-
];
|
|
7347
|
-
function isRecord(value) {
|
|
7348
|
-
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
7349
|
-
}
|
|
7350
|
-
/**
|
|
7351
|
-
* Read the per-tool `devin` override hooks from the rulesync hooks config.
|
|
7957
|
+
* Devin Local adopts a Claude-Code-style lifecycle hooks model: each event maps
|
|
7958
|
+
* to an array of `{ matcher?, hooks: [{ type, command|prompt, timeout? }] }`
|
|
7959
|
+
* matcher groups.
|
|
7352
7960
|
*
|
|
7353
|
-
*
|
|
7354
|
-
*
|
|
7355
|
-
* config
|
|
7356
|
-
|
|
7357
|
-
|
|
7358
|
-
|
|
7359
|
-
}
|
|
7360
|
-
/**
|
|
7361
|
-
* Convert a canonical hook definition into a Devin hook object, keeping only
|
|
7362
|
-
* the Devin-supported fields. Returns null when neither command nor
|
|
7363
|
-
* powershell is present (Devin requires at least one of them).
|
|
7364
|
-
*/
|
|
7365
|
-
function canonicalDefToDevinHook(def) {
|
|
7366
|
-
const hook = {};
|
|
7367
|
-
if (typeof def.command === "string") hook.command = def.command;
|
|
7368
|
-
if (typeof def.powershell === "string") hook.powershell = def.powershell;
|
|
7369
|
-
if (typeof def.show_output === "boolean") hook.show_output = def.show_output;
|
|
7370
|
-
if (typeof def.working_directory === "string") hook.working_directory = def.working_directory;
|
|
7371
|
-
if (hook.command === void 0 && hook.powershell === void 0) return null;
|
|
7372
|
-
return hook;
|
|
7373
|
-
}
|
|
7374
|
-
/**
|
|
7375
|
-
* Convert a Devin hook object into a canonical hook definition.
|
|
7376
|
-
*/
|
|
7377
|
-
function devinHookToCanonicalDef(hook) {
|
|
7378
|
-
const def = { type: "command" };
|
|
7379
|
-
if (typeof hook.command === "string") def.command = hook.command;
|
|
7380
|
-
if (typeof hook.powershell === "string") def.powershell = hook.powershell;
|
|
7381
|
-
if (typeof hook.show_output === "boolean") def.show_output = hook.show_output;
|
|
7382
|
-
if (typeof hook.working_directory === "string") def.working_directory = hook.working_directory;
|
|
7383
|
-
return def;
|
|
7384
|
-
}
|
|
7385
|
-
/**
|
|
7386
|
-
* Hooks generator for Devin Cascade (GA).
|
|
7961
|
+
* - Project scope: `.devin/hooks.v1.json`. This is a standalone file whose top
|
|
7962
|
+
* level IS the event map (no wrapper key).
|
|
7963
|
+
* - Global scope: `~/.config/devin/config.json` under the `"hooks"` key. This
|
|
7964
|
+
* file is shared with the MCP (`mcpServers`) and permissions (`permissions`)
|
|
7965
|
+
* features, so reads and writes merge into the existing JSON and the file is
|
|
7966
|
+
* never deleted in global mode.
|
|
7387
7967
|
*
|
|
7388
|
-
*
|
|
7389
|
-
* Devin event name to a flat array of hook objects. Project and global modes
|
|
7390
|
-
* share the same shape; only the location differs (`.windsurf/hooks.json` vs
|
|
7391
|
-
* `~/.codeium/windsurf/hooks.json`). The harness overrides `outputRoot` with the
|
|
7392
|
-
* home directory in global mode.
|
|
7968
|
+
* @see https://docs.devin.ai/cli/extensibility/hooks/overview
|
|
7393
7969
|
*/
|
|
7394
7970
|
var DevinHooks = class DevinHooks extends ToolHooks {
|
|
7395
7971
|
constructor(params) {
|
|
7396
7972
|
super({
|
|
7397
7973
|
...params,
|
|
7398
|
-
fileContent: params.fileContent ??
|
|
7974
|
+
fileContent: params.fileContent ?? "{}"
|
|
7399
7975
|
});
|
|
7400
7976
|
}
|
|
7977
|
+
/**
|
|
7978
|
+
* The project standalone `hooks.v1.json` is owned wholesale by rulesync and
|
|
7979
|
+
* may be deleted as an orphan. The global `config.json` is shared with the MCP
|
|
7980
|
+
* and permissions features, so it must never be deleted.
|
|
7981
|
+
*/
|
|
7982
|
+
isDeletable() {
|
|
7983
|
+
return this.getRelativeFilePath() !== DEVIN_CONFIG_FILE_NAME;
|
|
7984
|
+
}
|
|
7401
7985
|
static getSettablePaths({ global = false } = {}) {
|
|
7402
7986
|
if (global) return {
|
|
7403
|
-
relativeDirPath:
|
|
7404
|
-
relativeFilePath:
|
|
7987
|
+
relativeDirPath: DEVIN_GLOBAL_CONFIG_DIR_PATH,
|
|
7988
|
+
relativeFilePath: DEVIN_CONFIG_FILE_NAME
|
|
7405
7989
|
};
|
|
7406
7990
|
return {
|
|
7407
|
-
relativeDirPath:
|
|
7408
|
-
relativeFilePath:
|
|
7991
|
+
relativeDirPath: DEVIN_DIR,
|
|
7992
|
+
relativeFilePath: DEVIN_HOOKS_V1_FILE_NAME
|
|
7409
7993
|
};
|
|
7410
7994
|
}
|
|
7411
7995
|
static async fromFile({ outputRoot = process.cwd(), validate = true, global = false }) {
|
|
7412
7996
|
const paths = DevinHooks.getSettablePaths({ global });
|
|
7413
|
-
const fileContent = await readFileContentOrNull(join(outputRoot, paths.relativeDirPath, paths.relativeFilePath)) ??
|
|
7997
|
+
const fileContent = await readFileContentOrNull(join(outputRoot, paths.relativeDirPath, paths.relativeFilePath)) ?? "{}";
|
|
7414
7998
|
return new DevinHooks({
|
|
7415
7999
|
outputRoot,
|
|
7416
8000
|
relativeDirPath: paths.relativeDirPath,
|
|
@@ -7421,31 +8005,32 @@ var DevinHooks = class DevinHooks extends ToolHooks {
|
|
|
7421
8005
|
}
|
|
7422
8006
|
static async fromRulesyncHooks({ outputRoot = process.cwd(), rulesyncHooks, validate = true, global = false, logger }) {
|
|
7423
8007
|
const paths = DevinHooks.getSettablePaths({ global });
|
|
7424
|
-
|
|
8008
|
+
const filePath = join(outputRoot, paths.relativeDirPath, paths.relativeFilePath);
|
|
7425
8009
|
const config = rulesyncHooks.getJson();
|
|
7426
|
-
const
|
|
7427
|
-
|
|
7428
|
-
|
|
7429
|
-
|
|
7430
|
-
|
|
7431
|
-
|
|
7432
|
-
|
|
7433
|
-
|
|
7434
|
-
|
|
7435
|
-
|
|
7436
|
-
|
|
7437
|
-
|
|
7438
|
-
|
|
7439
|
-
|
|
7440
|
-
}
|
|
7441
|
-
const objects = [];
|
|
7442
|
-
for (const def of defs) {
|
|
7443
|
-
const hook = canonicalDefToDevinHook(def);
|
|
7444
|
-
if (hook !== null) objects.push(hook);
|
|
8010
|
+
const devinHooks = canonicalToToolHooks({
|
|
8011
|
+
config,
|
|
8012
|
+
toolOverrideHooks: config.devin?.hooks,
|
|
8013
|
+
converterConfig: DEVIN_CONVERTER_CONFIG,
|
|
8014
|
+
logger
|
|
8015
|
+
});
|
|
8016
|
+
let fileContent;
|
|
8017
|
+
if (global) {
|
|
8018
|
+
const existingContent = await readOrInitializeFileContent(filePath, JSON.stringify({}, null, 2));
|
|
8019
|
+
let parsedSettings;
|
|
8020
|
+
try {
|
|
8021
|
+
parsedSettings = JSON.parse(existingContent);
|
|
8022
|
+
} catch (error) {
|
|
8023
|
+
throw new Error(`Failed to parse existing Devin config at ${filePath}: ${formatError(error)}`, { cause: error });
|
|
7445
8024
|
}
|
|
7446
|
-
|
|
8025
|
+
const merged = {
|
|
8026
|
+
...isRecord(parsedSettings) ? parsedSettings : {},
|
|
8027
|
+
hooks: devinHooks
|
|
8028
|
+
};
|
|
8029
|
+
fileContent = JSON.stringify(merged, null, 2);
|
|
8030
|
+
} else {
|
|
8031
|
+
await readOrInitializeFileContent(filePath, JSON.stringify({}, null, 2));
|
|
8032
|
+
fileContent = JSON.stringify(devinHooks, null, 2);
|
|
7447
8033
|
}
|
|
7448
|
-
const fileContent = JSON.stringify({ hooks: devinHooks }, null, 2);
|
|
7449
8034
|
return new DevinHooks({
|
|
7450
8035
|
outputRoot,
|
|
7451
8036
|
relativeDirPath: paths.relativeDirPath,
|
|
@@ -7461,22 +8046,13 @@ var DevinHooks = class DevinHooks extends ToolHooks {
|
|
|
7461
8046
|
} catch (error) {
|
|
7462
8047
|
throw new Error(`Failed to parse Devin hooks content in ${join(this.getRelativeDirPath(), this.getRelativeFilePath())}: ${formatError(error)}`, { cause: error });
|
|
7463
8048
|
}
|
|
7464
|
-
const
|
|
7465
|
-
|
|
7466
|
-
|
|
7467
|
-
|
|
7468
|
-
const canonicalEvent = DEVIN_TO_CANONICAL_EVENT_NAMES[devinEvent];
|
|
7469
|
-
if (canonicalEvent === void 0) continue;
|
|
7470
|
-
const defs = [];
|
|
7471
|
-
for (const rawObject of rawObjects) {
|
|
7472
|
-
if (!isRecord(rawObject)) continue;
|
|
7473
|
-
defs.push(devinHookToCanonicalDef(rawObject));
|
|
7474
|
-
}
|
|
7475
|
-
if (defs.length > 0) canonicalHooks[canonicalEvent] = defs;
|
|
7476
|
-
}
|
|
8049
|
+
const hooks = toolHooksToCanonical({
|
|
8050
|
+
hooks: this.getRelativeFilePath() === "config.json" ? isRecord(parsed) && isRecord(parsed.hooks) ? parsed.hooks : {} : parsed,
|
|
8051
|
+
converterConfig: DEVIN_CONVERTER_CONFIG
|
|
8052
|
+
});
|
|
7477
8053
|
return this.toRulesyncHooksDefault({ fileContent: JSON.stringify({
|
|
7478
8054
|
version: 1,
|
|
7479
|
-
hooks
|
|
8055
|
+
hooks
|
|
7480
8056
|
}, null, 2) });
|
|
7481
8057
|
}
|
|
7482
8058
|
validate() {
|
|
@@ -7490,7 +8066,7 @@ var DevinHooks = class DevinHooks extends ToolHooks {
|
|
|
7490
8066
|
outputRoot,
|
|
7491
8067
|
relativeDirPath,
|
|
7492
8068
|
relativeFilePath,
|
|
7493
|
-
fileContent:
|
|
8069
|
+
fileContent: "{}",
|
|
7494
8070
|
validate: false
|
|
7495
8071
|
});
|
|
7496
8072
|
}
|
|
@@ -7717,6 +8293,106 @@ function mergeHermesConfig(fileContent, patch) {
|
|
|
7717
8293
|
}
|
|
7718
8294
|
//#endregion
|
|
7719
8295
|
//#region src/features/hooks/hermesagent-hooks.ts
|
|
8296
|
+
/**
|
|
8297
|
+
* Canonical events that map to a Hermes tool-call event (`pre_tool_call` /
|
|
8298
|
+
* `post_tool_call`) and therefore carry a `matcher`. Every other supported
|
|
8299
|
+
* canonical event maps to a Hermes lifecycle event, which never accepts a
|
|
8300
|
+
* `matcher`.
|
|
8301
|
+
* @see https://github.com/NousResearch/hermes-agent/blob/main/website/docs/user-guide/features/hooks.md
|
|
8302
|
+
*/
|
|
8303
|
+
const HERMESAGENT_MATCHER_EVENTS = /* @__PURE__ */ new Set(["preToolUse", "postToolUse"]);
|
|
8304
|
+
/**
|
|
8305
|
+
* Filter the shared canonical hooks to the events Hermes understands and merge
|
|
8306
|
+
* the `hermesagent`-specific override block on top.
|
|
8307
|
+
*/
|
|
8308
|
+
function buildEffectiveHooks(config, toolOverrideHooks) {
|
|
8309
|
+
const supported = new Set(HERMESAGENT_HOOK_EVENTS);
|
|
8310
|
+
const shared = {};
|
|
8311
|
+
for (const [event, defs] of Object.entries(config.hooks)) if (supported.has(event)) shared[event] = defs;
|
|
8312
|
+
return {
|
|
8313
|
+
...shared,
|
|
8314
|
+
...toolOverrideHooks
|
|
8315
|
+
};
|
|
8316
|
+
}
|
|
8317
|
+
/**
|
|
8318
|
+
* Convert the canonical hooks config into Hermes's native
|
|
8319
|
+
* `hooks: { <event>: [{ matcher?, command, timeout? }] }` shape.
|
|
8320
|
+
*
|
|
8321
|
+
* Only `type: "command"` canonical hooks are emitted — Hermes shell hooks run
|
|
8322
|
+
* via `shlex.split`/`shell=False`, so `prompt`/`http` hooks have no native
|
|
8323
|
+
* equivalent and are skipped (the shared `HooksProcessor` already warns about
|
|
8324
|
+
* unsupported hook types centrally). `matcher` is only carried through for
|
|
8325
|
+
* `pre_tool_call`/`post_tool_call`; on any other event it is dropped with a
|
|
8326
|
+
* warning, mirroring how other adapters (e.g. AugmentCode) handle
|
|
8327
|
+
* matcher-less lifecycle events.
|
|
8328
|
+
*/
|
|
8329
|
+
function canonicalToHermesHooks({ config, toolOverrideHooks, logger }) {
|
|
8330
|
+
const effectiveHooks = buildEffectiveHooks(config, toolOverrideHooks);
|
|
8331
|
+
const result = {};
|
|
8332
|
+
for (const [canonicalEvent, defs] of Object.entries(effectiveHooks)) {
|
|
8333
|
+
const nativeEvent = CANONICAL_TO_HERMESAGENT_EVENT_NAMES[canonicalEvent];
|
|
8334
|
+
if (!nativeEvent) continue;
|
|
8335
|
+
const supportsMatcher = HERMESAGENT_MATCHER_EVENTS.has(canonicalEvent);
|
|
8336
|
+
const entries = [];
|
|
8337
|
+
for (const def of defs) {
|
|
8338
|
+
if ((def.type ?? "command") !== "command") continue;
|
|
8339
|
+
if (typeof def.command !== "string" || def.command === "") continue;
|
|
8340
|
+
const entry = { command: def.command };
|
|
8341
|
+
if (typeof def.matcher === "string" && def.matcher !== "") if (supportsMatcher) entry.matcher = def.matcher;
|
|
8342
|
+
else logger?.warn(`matcher "${def.matcher}" on "${canonicalEvent}" hook will be ignored — Hermes Agent only supports matchers on pre_tool_call/post_tool_call`);
|
|
8343
|
+
if (typeof def.timeout === "number") entry.timeout = def.timeout;
|
|
8344
|
+
entries.push(entry);
|
|
8345
|
+
}
|
|
8346
|
+
if (entries.length > 0) result[nativeEvent] = entries;
|
|
8347
|
+
}
|
|
8348
|
+
return result;
|
|
8349
|
+
}
|
|
8350
|
+
/**
|
|
8351
|
+
* Reverse {@link canonicalToHermesHooks}: parse Hermes's native
|
|
8352
|
+
* `hooks: { <event>: [...] }` map back into a canonical event → definition[]
|
|
8353
|
+
* record. Native events with no canonical equivalent (`pre_verify`,
|
|
8354
|
+
* `transform_tool_result`, ...) are skipped since there is nothing to round
|
|
8355
|
+
* -trip them into.
|
|
8356
|
+
*/
|
|
8357
|
+
function hermesHooksToCanonical(hooks) {
|
|
8358
|
+
const canonical = {};
|
|
8359
|
+
if (hooks === null || typeof hooks !== "object" || Array.isArray(hooks)) return canonical;
|
|
8360
|
+
for (const [nativeEvent, entries] of Object.entries(hooks)) {
|
|
8361
|
+
if (PROTOTYPE_POLLUTION_KEYS.has(nativeEvent) || !Array.isArray(entries)) continue;
|
|
8362
|
+
const canonicalEvent = HERMESAGENT_TO_CANONICAL_EVENT_NAMES[nativeEvent];
|
|
8363
|
+
if (!canonicalEvent) continue;
|
|
8364
|
+
const defs = [];
|
|
8365
|
+
for (const raw of entries) {
|
|
8366
|
+
if (raw === null || typeof raw !== "object" || Array.isArray(raw)) continue;
|
|
8367
|
+
const entry = raw;
|
|
8368
|
+
if (typeof entry.command !== "string") continue;
|
|
8369
|
+
const def = {
|
|
8370
|
+
type: "command",
|
|
8371
|
+
command: entry.command
|
|
8372
|
+
};
|
|
8373
|
+
if (typeof entry.matcher === "string" && entry.matcher !== "") def.matcher = entry.matcher;
|
|
8374
|
+
if (typeof entry.timeout === "number") def.timeout = entry.timeout;
|
|
8375
|
+
defs.push(def);
|
|
8376
|
+
}
|
|
8377
|
+
if (defs.length > 0) canonical[canonicalEvent] = defs;
|
|
8378
|
+
}
|
|
8379
|
+
return canonical;
|
|
8380
|
+
}
|
|
8381
|
+
/**
|
|
8382
|
+
* Hermes Agent shell hooks.
|
|
8383
|
+
*
|
|
8384
|
+
* Hermes Agent registers shell-command hooks under the `hooks:` key of the
|
|
8385
|
+
* shared user config file `~/.hermes/config.yaml` (the HERMES_HOME directory;
|
|
8386
|
+
* global only — Hermes has no project-scoped hooks location). Hermes only runs
|
|
8387
|
+
* hooks declared under its fixed `VALID_HOOKS` event keys (`pre_tool_call`,
|
|
8388
|
+
* `post_tool_call`, `pre_llm_call`, `post_llm_call`, `on_session_start`,
|
|
8389
|
+
* `on_session_end`, `subagent_start`, `subagent_stop`, ...); any other key is
|
|
8390
|
+
* silently ignored. Generation therefore maps canonical events onto the real
|
|
8391
|
+
* `VALID_HOOKS` keys and merges the resulting `hooks:` block into the existing
|
|
8392
|
+
* config instead of overwriting it, since that file also holds other Hermes
|
|
8393
|
+
* settings (model, `mcp_servers`, `command_allowlist`, ...).
|
|
8394
|
+
* @see https://github.com/NousResearch/hermes-agent/blob/main/website/docs/user-guide/features/hooks.md
|
|
8395
|
+
*/
|
|
7720
8396
|
var HermesagentHooks = class HermesagentHooks extends ToolHooks {
|
|
7721
8397
|
static getSettablePaths() {
|
|
7722
8398
|
return {
|
|
@@ -7736,6 +8412,9 @@ var HermesagentHooks = class HermesagentHooks extends ToolHooks {
|
|
|
7736
8412
|
error: null
|
|
7737
8413
|
};
|
|
7738
8414
|
}
|
|
8415
|
+
isDeletable() {
|
|
8416
|
+
return false;
|
|
8417
|
+
}
|
|
7739
8418
|
shouldMergeExistingFileContent() {
|
|
7740
8419
|
return true;
|
|
7741
8420
|
}
|
|
@@ -7743,18 +8422,21 @@ var HermesagentHooks = class HermesagentHooks extends ToolHooks {
|
|
|
7743
8422
|
this.fileContent = mergeHermesConfig(fileContent, parseHermesConfig(this.fileContent));
|
|
7744
8423
|
}
|
|
7745
8424
|
toRulesyncHooks() {
|
|
7746
|
-
const
|
|
7747
|
-
|
|
7748
|
-
|
|
7749
|
-
|
|
7750
|
-
|
|
7751
|
-
fileContent: JSON.stringify(hooks ?? {}, null, 2)
|
|
7752
|
-
});
|
|
8425
|
+
const hooks = hermesHooksToCanonical(parseHermesConfig(this.getFileContent()).hooks);
|
|
8426
|
+
return this.toRulesyncHooksDefault({ fileContent: JSON.stringify({
|
|
8427
|
+
version: 1,
|
|
8428
|
+
hooks
|
|
8429
|
+
}, null, 2) });
|
|
7753
8430
|
}
|
|
7754
|
-
static fromRulesyncHooks({ outputRoot, rulesyncHooks }) {
|
|
8431
|
+
static fromRulesyncHooks({ outputRoot, rulesyncHooks, logger }) {
|
|
8432
|
+
const config = rulesyncHooks.getJson();
|
|
7755
8433
|
return new HermesagentHooks({
|
|
7756
8434
|
outputRoot,
|
|
7757
|
-
fileContent: stringifyHermesConfig({ hooks: {
|
|
8435
|
+
fileContent: stringifyHermesConfig({ hooks: canonicalToHermesHooks({
|
|
8436
|
+
config,
|
|
8437
|
+
toolOverrideHooks: config.hermesagent?.hooks,
|
|
8438
|
+
logger
|
|
8439
|
+
}) })
|
|
7758
8440
|
});
|
|
7759
8441
|
}
|
|
7760
8442
|
};
|
|
@@ -8224,9 +8906,8 @@ var KiroHooks = class KiroHooks extends ToolHooks {
|
|
|
8224
8906
|
* the tool-specific override key to `kiro-cli` (so `kiro-cli.hooks` overrides in
|
|
8225
8907
|
* the rulesync hooks config are honored, rather than the legacy `kiro.hooks`).
|
|
8226
8908
|
*
|
|
8227
|
-
* (The Kiro IDE uses
|
|
8228
|
-
*
|
|
8229
|
-
* adapter.)
|
|
8909
|
+
* (The Kiro IDE uses the structured `.kiro/hooks/*.json` v1 format instead; see
|
|
8910
|
+
* {@link import("./kiro-ide-hooks.js").KiroIdeHooks}.)
|
|
8230
8911
|
*/
|
|
8231
8912
|
var KiroCliHooks = class extends KiroHooks {
|
|
8232
8913
|
static getOverrideKey() {
|
|
@@ -8234,6 +8915,203 @@ var KiroCliHooks = class extends KiroHooks {
|
|
|
8234
8915
|
}
|
|
8235
8916
|
};
|
|
8236
8917
|
//#endregion
|
|
8918
|
+
//#region src/features/hooks/kiro-ide-hooks.ts
|
|
8919
|
+
/**
|
|
8920
|
+
* One hook entry inside the Kiro IDE v1 `hooks` array.
|
|
8921
|
+
*
|
|
8922
|
+
* `z.looseObject` keeps unknown fields added by future Kiro IDE versions, so
|
|
8923
|
+
* imports do not drop data they do not yet understand.
|
|
8924
|
+
* @see https://kiro.dev/docs/hooks/types/
|
|
8925
|
+
*/
|
|
8926
|
+
const KiroIdeHookActionSchema = z.union([z.looseObject({
|
|
8927
|
+
type: z.literal("command"),
|
|
8928
|
+
command: z.optional(safeString)
|
|
8929
|
+
}), z.looseObject({
|
|
8930
|
+
type: z.literal("agent"),
|
|
8931
|
+
prompt: z.optional(safeString)
|
|
8932
|
+
})]);
|
|
8933
|
+
const KiroIdeHookEntrySchema = z.looseObject({
|
|
8934
|
+
name: z.optional(z.string()),
|
|
8935
|
+
description: z.optional(z.string()),
|
|
8936
|
+
trigger: z.optional(z.string()),
|
|
8937
|
+
matcher: z.optional(z.string()),
|
|
8938
|
+
action: z.optional(KiroIdeHookActionSchema),
|
|
8939
|
+
timeout: z.optional(z.number()),
|
|
8940
|
+
enabled: z.optional(z.boolean())
|
|
8941
|
+
});
|
|
8942
|
+
const KiroIdeHooksFileSchema = z.looseObject({
|
|
8943
|
+
version: z.optional(z.string()),
|
|
8944
|
+
hooks: z.optional(z.array(KiroIdeHookEntrySchema))
|
|
8945
|
+
});
|
|
8946
|
+
/**
|
|
8947
|
+
* Build the Kiro IDE hook entries for a single canonical event's definitions.
|
|
8948
|
+
*
|
|
8949
|
+
* `command`-type definitions become `{ type: "command", command }` actions and
|
|
8950
|
+
* `prompt`-type definitions become `{ type: "agent", prompt }` actions. Other
|
|
8951
|
+
* types are skipped (the {@link import("./hooks-processor.js").HooksProcessor}
|
|
8952
|
+
* already warns about unsupported types).
|
|
8953
|
+
*/
|
|
8954
|
+
function buildKiroIdeEntriesForEvent(trigger, definitions) {
|
|
8955
|
+
const entries = [];
|
|
8956
|
+
for (const def of definitions) {
|
|
8957
|
+
const type = def.type ?? "command";
|
|
8958
|
+
let action;
|
|
8959
|
+
if (type === "command") {
|
|
8960
|
+
if (def.command === void 0) continue;
|
|
8961
|
+
action = {
|
|
8962
|
+
type: "command",
|
|
8963
|
+
command: def.command
|
|
8964
|
+
};
|
|
8965
|
+
} else if (type === "prompt") {
|
|
8966
|
+
if (def.prompt === void 0) continue;
|
|
8967
|
+
action = {
|
|
8968
|
+
type: "agent",
|
|
8969
|
+
prompt: def.prompt
|
|
8970
|
+
};
|
|
8971
|
+
} else continue;
|
|
8972
|
+
entries.push({
|
|
8973
|
+
name: def.name ?? trigger,
|
|
8974
|
+
...def.description !== void 0 && def.description !== null && { description: def.description },
|
|
8975
|
+
trigger,
|
|
8976
|
+
...def.matcher !== void 0 && def.matcher !== null && def.matcher !== "" && { matcher: def.matcher },
|
|
8977
|
+
action,
|
|
8978
|
+
...def.timeout !== void 0 && def.timeout !== null && def.timeout >= 0 && { timeout: def.timeout },
|
|
8979
|
+
enabled: true
|
|
8980
|
+
});
|
|
8981
|
+
}
|
|
8982
|
+
return entries;
|
|
8983
|
+
}
|
|
8984
|
+
function canonicalToKiroIdeHooks(config) {
|
|
8985
|
+
const kiroIdeSupported = new Set(KIRO_IDE_HOOK_EVENTS);
|
|
8986
|
+
const sharedHooks = {};
|
|
8987
|
+
for (const [event, defs] of Object.entries(config.hooks)) if (kiroIdeSupported.has(event)) sharedHooks[event] = defs;
|
|
8988
|
+
const effectiveHooks = {
|
|
8989
|
+
...sharedHooks,
|
|
8990
|
+
...config["kiro-ide"]?.hooks
|
|
8991
|
+
};
|
|
8992
|
+
const entries = [];
|
|
8993
|
+
for (const [eventName, definitions] of Object.entries(effectiveHooks)) {
|
|
8994
|
+
const trigger = CANONICAL_TO_KIRO_IDE_EVENT_NAMES[eventName] ?? eventName;
|
|
8995
|
+
entries.push(...buildKiroIdeEntriesForEvent(trigger, definitions));
|
|
8996
|
+
}
|
|
8997
|
+
return entries;
|
|
8998
|
+
}
|
|
8999
|
+
function kiroIdeHooksToCanonical(entries) {
|
|
9000
|
+
const canonical = {};
|
|
9001
|
+
for (const entry of entries) {
|
|
9002
|
+
if (entry.trigger === void 0 || entry.action === void 0) continue;
|
|
9003
|
+
const eventName = KIRO_IDE_TO_CANONICAL_EVENT_NAMES[entry.trigger] ?? entry.trigger;
|
|
9004
|
+
if (isPrototypePollutionKey(eventName)) continue;
|
|
9005
|
+
const def = {};
|
|
9006
|
+
if (entry.action.type === "command") {
|
|
9007
|
+
if (!entry.action.command) continue;
|
|
9008
|
+
def.type = "command";
|
|
9009
|
+
def.command = entry.action.command;
|
|
9010
|
+
} else {
|
|
9011
|
+
if (!entry.action.prompt) continue;
|
|
9012
|
+
def.type = "prompt";
|
|
9013
|
+
def.prompt = entry.action.prompt;
|
|
9014
|
+
}
|
|
9015
|
+
if (entry.name !== void 0 && entry.name !== null) def.name = entry.name;
|
|
9016
|
+
if (entry.description !== void 0 && entry.description !== null) def.description = entry.description;
|
|
9017
|
+
if (entry.matcher !== void 0 && entry.matcher !== null && entry.matcher !== "") def.matcher = entry.matcher;
|
|
9018
|
+
if (entry.timeout !== void 0 && entry.timeout !== null) def.timeout = entry.timeout;
|
|
9019
|
+
(canonical[eventName] ??= []).push(def);
|
|
9020
|
+
}
|
|
9021
|
+
return canonical;
|
|
9022
|
+
}
|
|
9023
|
+
/**
|
|
9024
|
+
* Hooks generator for the **Kiro IDE** (`.kiro/hooks/*.json` v1).
|
|
9025
|
+
*
|
|
9026
|
+
* Kiro IDE 1.0 reads structured JSON hooks from `.kiro/hooks/` (workspace) and
|
|
9027
|
+
* `~/.kiro/hooks/` (user). A single file may declare multiple hooks in its
|
|
9028
|
+
* `hooks` array, so rulesync emits every generated hook into one
|
|
9029
|
+
* `rulesync.json` file per scope (`{ "version": "v1", "hooks": [ ... ] }`),
|
|
9030
|
+
* which keeps it within the single-file hooks architecture.
|
|
9031
|
+
*
|
|
9032
|
+
* This is distinct from the Kiro CLI ({@link import("./kiro-cli-hooks.js").
|
|
9033
|
+
* KiroCliHooks}), which uses the `.kiro/agents/default.json` agent-config shape.
|
|
9034
|
+
*
|
|
9035
|
+
* @see https://kiro.dev/docs/hooks/
|
|
9036
|
+
*/
|
|
9037
|
+
var KiroIdeHooks = class KiroIdeHooks extends ToolHooks {
|
|
9038
|
+
constructor(params) {
|
|
9039
|
+
super({
|
|
9040
|
+
...params,
|
|
9041
|
+
fileContent: params.fileContent ?? JSON.stringify({
|
|
9042
|
+
version: "v1",
|
|
9043
|
+
hooks: []
|
|
9044
|
+
}, null, 2)
|
|
9045
|
+
});
|
|
9046
|
+
}
|
|
9047
|
+
static getSettablePaths(_options = {}) {
|
|
9048
|
+
return {
|
|
9049
|
+
relativeDirPath: KIRO_IDE_HOOKS_DIR_PATH,
|
|
9050
|
+
relativeFilePath: KIRO_IDE_HOOKS_FILE_NAME
|
|
9051
|
+
};
|
|
9052
|
+
}
|
|
9053
|
+
static async fromFile({ outputRoot = process.cwd(), validate = true, global = false }) {
|
|
9054
|
+
const paths = KiroIdeHooks.getSettablePaths({ global });
|
|
9055
|
+
const fileContent = await readFileContentOrNull(join(outputRoot, paths.relativeDirPath, paths.relativeFilePath)) ?? JSON.stringify({
|
|
9056
|
+
version: "v1",
|
|
9057
|
+
hooks: []
|
|
9058
|
+
}, null, 2);
|
|
9059
|
+
return new KiroIdeHooks({
|
|
9060
|
+
outputRoot,
|
|
9061
|
+
relativeDirPath: paths.relativeDirPath,
|
|
9062
|
+
relativeFilePath: paths.relativeFilePath,
|
|
9063
|
+
fileContent,
|
|
9064
|
+
validate
|
|
9065
|
+
});
|
|
9066
|
+
}
|
|
9067
|
+
static async fromRulesyncHooks({ outputRoot = process.cwd(), rulesyncHooks, validate = true, global = false }) {
|
|
9068
|
+
const paths = KiroIdeHooks.getSettablePaths({ global });
|
|
9069
|
+
const hooks = canonicalToKiroIdeHooks(rulesyncHooks.getJson());
|
|
9070
|
+
const fileContent = JSON.stringify({
|
|
9071
|
+
version: "v1",
|
|
9072
|
+
hooks
|
|
9073
|
+
}, null, 2);
|
|
9074
|
+
return new KiroIdeHooks({
|
|
9075
|
+
outputRoot,
|
|
9076
|
+
relativeDirPath: paths.relativeDirPath,
|
|
9077
|
+
relativeFilePath: paths.relativeFilePath,
|
|
9078
|
+
fileContent,
|
|
9079
|
+
validate
|
|
9080
|
+
});
|
|
9081
|
+
}
|
|
9082
|
+
toRulesyncHooks() {
|
|
9083
|
+
let parsed;
|
|
9084
|
+
try {
|
|
9085
|
+
parsed = KiroIdeHooksFileSchema.parse(JSON.parse(this.getFileContent()));
|
|
9086
|
+
} catch (error) {
|
|
9087
|
+
throw new Error(`Failed to parse Kiro IDE hooks content in ${join(this.getRelativeDirPath(), this.getRelativeFilePath())}: ${formatError(error)}`, { cause: error });
|
|
9088
|
+
}
|
|
9089
|
+
const hooks = kiroIdeHooksToCanonical(parsed.hooks ?? []);
|
|
9090
|
+
return this.toRulesyncHooksDefault({ fileContent: JSON.stringify({
|
|
9091
|
+
version: 1,
|
|
9092
|
+
hooks
|
|
9093
|
+
}, null, 2) });
|
|
9094
|
+
}
|
|
9095
|
+
validate() {
|
|
9096
|
+
return {
|
|
9097
|
+
success: true,
|
|
9098
|
+
error: null
|
|
9099
|
+
};
|
|
9100
|
+
}
|
|
9101
|
+
static forDeletion({ outputRoot = process.cwd(), relativeDirPath, relativeFilePath }) {
|
|
9102
|
+
return new KiroIdeHooks({
|
|
9103
|
+
outputRoot,
|
|
9104
|
+
relativeDirPath,
|
|
9105
|
+
relativeFilePath,
|
|
9106
|
+
fileContent: JSON.stringify({
|
|
9107
|
+
version: "v1",
|
|
9108
|
+
hooks: []
|
|
9109
|
+
}, null, 2),
|
|
9110
|
+
validate: false
|
|
9111
|
+
});
|
|
9112
|
+
}
|
|
9113
|
+
};
|
|
9114
|
+
//#endregion
|
|
8237
9115
|
//#region src/features/hooks/opencode-hooks.ts
|
|
8238
9116
|
var OpencodeHooks = class OpencodeHooks extends ToolHooks {
|
|
8239
9117
|
constructor(params) {
|
|
@@ -8292,6 +9170,35 @@ var OpencodeHooks = class OpencodeHooks extends ToolHooks {
|
|
|
8292
9170
|
//#endregion
|
|
8293
9171
|
//#region src/features/hooks/qwencode-hooks.ts
|
|
8294
9172
|
/**
|
|
9173
|
+
* Build a single Qwen Code hook object from a canonical hook definition.
|
|
9174
|
+
* Command-only fields (`async`/`env`/`shell`) are emitted only on command hooks
|
|
9175
|
+
* and http-only fields (`headers`/`allowedEnvVars`/`once`) only on http hooks,
|
|
9176
|
+
* matching upstream. `statusMessage` applies to both.
|
|
9177
|
+
* https://github.com/QwenLM/qwen-code/blob/main/docs/users/features/hooks.md
|
|
9178
|
+
*/
|
|
9179
|
+
function canonicalDefToQwencodeHook(def) {
|
|
9180
|
+
const type = def.type ?? "command";
|
|
9181
|
+
const isHttp = type === "http";
|
|
9182
|
+
const isCommand = type === "command";
|
|
9183
|
+
return {
|
|
9184
|
+
type,
|
|
9185
|
+
...compact({
|
|
9186
|
+
command: def.command,
|
|
9187
|
+
url: def.url,
|
|
9188
|
+
timeout: def.timeout,
|
|
9189
|
+
name: def.name,
|
|
9190
|
+
description: def.description,
|
|
9191
|
+
statusMessage: def.statusMessage,
|
|
9192
|
+
async: isCommand ? def.async : void 0,
|
|
9193
|
+
env: isCommand ? def.env : void 0,
|
|
9194
|
+
shell: isCommand ? def.shell : void 0,
|
|
9195
|
+
headers: isHttp ? def.headers : void 0,
|
|
9196
|
+
allowedEnvVars: isHttp ? def.allowedEnvVars : void 0,
|
|
9197
|
+
once: isHttp ? def.once : void 0
|
|
9198
|
+
})
|
|
9199
|
+
};
|
|
9200
|
+
}
|
|
9201
|
+
/**
|
|
8295
9202
|
* Convert canonical hooks config to Qwen Code format.
|
|
8296
9203
|
* Filters shared hooks to QWENCODE_HOOK_EVENTS, merges config.qwencode?.hooks,
|
|
8297
9204
|
* then converts to PascalCase and Qwen Code matcher/hooks structure.
|
|
@@ -8319,16 +9226,7 @@ function canonicalToQwencodeHooks(config) {
|
|
|
8319
9226
|
}
|
|
8320
9227
|
const entries = [];
|
|
8321
9228
|
for (const [matcherKey, defs] of byMatcher) {
|
|
8322
|
-
const hooks = defs.map(
|
|
8323
|
-
return {
|
|
8324
|
-
type: def.type ?? "command",
|
|
8325
|
-
...def.command !== void 0 && def.command !== null && { command: def.command },
|
|
8326
|
-
...def.url !== void 0 && def.url !== null && { url: def.url },
|
|
8327
|
-
...def.timeout !== void 0 && def.timeout !== null && { timeout: def.timeout },
|
|
8328
|
-
...def.name !== void 0 && def.name !== null && { name: def.name },
|
|
8329
|
-
...def.description !== void 0 && def.description !== null && { description: def.description }
|
|
8330
|
-
};
|
|
8331
|
-
});
|
|
9229
|
+
const hooks = defs.map(canonicalDefToQwencodeHook);
|
|
8332
9230
|
const sequential = defs.some((def) => def.sequential === true);
|
|
8333
9231
|
const group = matcherKey ? {
|
|
8334
9232
|
matcher: matcherKey,
|
|
@@ -8352,7 +9250,14 @@ const QwencodeHookEntrySchema = z.looseObject({
|
|
|
8352
9250
|
url: z.optional(z.string()),
|
|
8353
9251
|
timeout: z.optional(z.number()),
|
|
8354
9252
|
name: z.optional(z.string()),
|
|
8355
|
-
description: z.optional(z.string())
|
|
9253
|
+
description: z.optional(z.string()),
|
|
9254
|
+
statusMessage: z.optional(z.string()),
|
|
9255
|
+
async: z.optional(z.boolean()),
|
|
9256
|
+
env: z.optional(z.record(z.string(), z.string())),
|
|
9257
|
+
shell: z.optional(z.string()),
|
|
9258
|
+
headers: z.optional(z.record(z.string(), z.string())),
|
|
9259
|
+
allowedEnvVars: z.optional(z.array(z.string())),
|
|
9260
|
+
once: z.optional(z.boolean())
|
|
8356
9261
|
});
|
|
8357
9262
|
/**
|
|
8358
9263
|
* A matcher group entry in a Qwen Code event array.
|
|
@@ -8371,18 +9276,29 @@ function qwencodeMatcherEntryToCanonical(entry) {
|
|
|
8371
9276
|
const defs = [];
|
|
8372
9277
|
const hooks = entry.hooks ?? [];
|
|
8373
9278
|
const sequential = entry.sequential === true;
|
|
9279
|
+
const matcher = entry.matcher !== void 0 && entry.matcher !== null && entry.matcher !== "" ? entry.matcher : void 0;
|
|
8374
9280
|
for (const h of hooks) {
|
|
8375
|
-
const command = h.command;
|
|
8376
9281
|
const hookType = h.type === "command" || h.type === "prompt" || h.type === "http" ? h.type : "command";
|
|
9282
|
+
const isHttp = hookType === "http";
|
|
9283
|
+
const isCommand = hookType === "command";
|
|
8377
9284
|
defs.push({
|
|
8378
9285
|
type: hookType,
|
|
8379
|
-
...
|
|
8380
|
-
|
|
8381
|
-
|
|
8382
|
-
|
|
8383
|
-
|
|
8384
|
-
|
|
8385
|
-
|
|
9286
|
+
...compact({
|
|
9287
|
+
command: h.command,
|
|
9288
|
+
url: h.url,
|
|
9289
|
+
timeout: h.timeout,
|
|
9290
|
+
name: h.name,
|
|
9291
|
+
description: h.description,
|
|
9292
|
+
statusMessage: h.statusMessage,
|
|
9293
|
+
async: isCommand ? h.async : void 0,
|
|
9294
|
+
env: isCommand ? h.env : void 0,
|
|
9295
|
+
shell: isCommand ? h.shell : void 0,
|
|
9296
|
+
headers: isHttp ? h.headers : void 0,
|
|
9297
|
+
allowedEnvVars: isHttp ? h.allowedEnvVars : void 0,
|
|
9298
|
+
once: isHttp ? h.once : void 0,
|
|
9299
|
+
sequential: sequential ? true : void 0,
|
|
9300
|
+
matcher
|
|
9301
|
+
})
|
|
8386
9302
|
});
|
|
8387
9303
|
}
|
|
8388
9304
|
return defs;
|
|
@@ -8495,6 +9411,169 @@ var QwencodeHooks = class QwencodeHooks extends ToolHooks {
|
|
|
8495
9411
|
}
|
|
8496
9412
|
};
|
|
8497
9413
|
//#endregion
|
|
9414
|
+
//#region src/features/hooks/reasonix-hooks.ts
|
|
9415
|
+
/**
|
|
9416
|
+
* Only PreToolUse/PostToolUse honor the `match` field (an anchored regex
|
|
9417
|
+
* against the tool name); it is ignored on every other event.
|
|
9418
|
+
*/
|
|
9419
|
+
const REASONIX_MATCHER_EVENTS = /* @__PURE__ */ new Set(["PreToolUse", "PostToolUse"]);
|
|
9420
|
+
const SUPPORTED_REASONIX_EVENTS = new Set(REASONIX_HOOK_EVENTS);
|
|
9421
|
+
/**
|
|
9422
|
+
* Convert canonical hooks config to the Reasonix `hooks` object.
|
|
9423
|
+
* Filters shared hooks to REASONIX_HOOK_EVENTS, merges `config.reasonix?.hooks`,
|
|
9424
|
+
* then maps event names and emits flat per-event hook-entry arrays.
|
|
9425
|
+
*/
|
|
9426
|
+
function canonicalToReasonixHooks({ config, toolOverrideHooks, logger }) {
|
|
9427
|
+
const sharedHooks = {};
|
|
9428
|
+
for (const [event, defs] of Object.entries(config.hooks)) if (SUPPORTED_REASONIX_EVENTS.has(event)) sharedHooks[event] = defs;
|
|
9429
|
+
const effectiveHooks = {
|
|
9430
|
+
...sharedHooks,
|
|
9431
|
+
...toolOverrideHooks
|
|
9432
|
+
};
|
|
9433
|
+
const result = {};
|
|
9434
|
+
for (const [event, defs] of Object.entries(effectiveHooks)) {
|
|
9435
|
+
if (!SUPPORTED_REASONIX_EVENTS.has(event)) continue;
|
|
9436
|
+
const reasonixEvent = CANONICAL_TO_REASONIX_EVENT_NAMES[event] ?? event;
|
|
9437
|
+
const isMatcherEvent = REASONIX_MATCHER_EVENTS.has(reasonixEvent);
|
|
9438
|
+
const entries = [];
|
|
9439
|
+
for (const def of defs) {
|
|
9440
|
+
if ((def.type ?? "command") !== "command") continue;
|
|
9441
|
+
if (typeof def.command !== "string") continue;
|
|
9442
|
+
const entry = { command: def.command };
|
|
9443
|
+
if (typeof def.matcher === "string" && def.matcher !== "") if (isMatcherEvent) entry.match = def.matcher;
|
|
9444
|
+
else logger?.warn(`matcher "${def.matcher}" on "${event}" hook will be ignored — Reasonix's "${reasonixEvent}" event does not support matchers`);
|
|
9445
|
+
if (typeof def.description === "string" && def.description !== "") entry.description = def.description;
|
|
9446
|
+
if (typeof def.timeout === "number") entry.timeout = Math.round(def.timeout * 1e3);
|
|
9447
|
+
entries.push(entry);
|
|
9448
|
+
}
|
|
9449
|
+
if (entries.length > 0) result[reasonixEvent] = [...result[reasonixEvent] ?? [], ...entries];
|
|
9450
|
+
}
|
|
9451
|
+
return result;
|
|
9452
|
+
}
|
|
9453
|
+
/**
|
|
9454
|
+
* Reverse {@link canonicalToReasonixHooks}: parse the Reasonix `hooks` object
|
|
9455
|
+
* back into a canonical event -> definition[] record.
|
|
9456
|
+
*/
|
|
9457
|
+
function reasonixHooksToCanonical(hooks) {
|
|
9458
|
+
const canonical = {};
|
|
9459
|
+
if (hooks === null || hooks === void 0 || typeof hooks !== "object" || Array.isArray(hooks)) return canonical;
|
|
9460
|
+
for (const [reasonixEvent, rawEntries] of Object.entries(hooks)) {
|
|
9461
|
+
if (!Array.isArray(rawEntries)) continue;
|
|
9462
|
+
const canonicalEvent = REASONIX_TO_CANONICAL_EVENT_NAMES[reasonixEvent] ?? reasonixEvent;
|
|
9463
|
+
const defs = [];
|
|
9464
|
+
for (const rawEntry of rawEntries) {
|
|
9465
|
+
if (rawEntry === null || typeof rawEntry !== "object" || Array.isArray(rawEntry)) continue;
|
|
9466
|
+
const entry = rawEntry;
|
|
9467
|
+
if (typeof entry.command !== "string") continue;
|
|
9468
|
+
const def = {
|
|
9469
|
+
type: "command",
|
|
9470
|
+
command: entry.command
|
|
9471
|
+
};
|
|
9472
|
+
if (typeof entry.match === "string" && entry.match !== "") def.matcher = entry.match;
|
|
9473
|
+
if (typeof entry.description === "string" && entry.description !== "") def.description = entry.description;
|
|
9474
|
+
if (typeof entry.timeout === "number") def.timeout = entry.timeout / 1e3;
|
|
9475
|
+
defs.push(def);
|
|
9476
|
+
}
|
|
9477
|
+
if (defs.length > 0) canonical[canonicalEvent] = [...canonical[canonicalEvent] ?? [], ...defs];
|
|
9478
|
+
}
|
|
9479
|
+
return canonical;
|
|
9480
|
+
}
|
|
9481
|
+
/**
|
|
9482
|
+
* Reasonix hooks adapter.
|
|
9483
|
+
*
|
|
9484
|
+
* Reasonix hooks live in a Claude-Code-style but standalone JSON file —
|
|
9485
|
+
* `.reasonix/settings.json` (project) or `~/.reasonix/settings.json`
|
|
9486
|
+
* (global) — separate from the `[permissions]`/`[[plugins]]` TOML config.
|
|
9487
|
+
* Only the four events documented in the upstream issue are mapped:
|
|
9488
|
+
* PreToolUse/PostToolUse/UserPromptSubmit/Stop (see REASONIX_HOOK_EVENTS).
|
|
9489
|
+
* @see https://github.com/esengine/DeepSeek-Reasonix/blob/main-v2/docs/DESKTOP_HOOKS.zh-CN.md
|
|
9490
|
+
*/
|
|
9491
|
+
var ReasonixHooks = class ReasonixHooks extends ToolHooks {
|
|
9492
|
+
constructor(params) {
|
|
9493
|
+
super({
|
|
9494
|
+
...params,
|
|
9495
|
+
fileContent: params.fileContent ?? "{}"
|
|
9496
|
+
});
|
|
9497
|
+
}
|
|
9498
|
+
isDeletable() {
|
|
9499
|
+
return false;
|
|
9500
|
+
}
|
|
9501
|
+
static getSettablePaths(_options = {}) {
|
|
9502
|
+
return {
|
|
9503
|
+
relativeDirPath: REASONIX_DIR,
|
|
9504
|
+
relativeFilePath: REASONIX_SETTINGS_FILE_NAME
|
|
9505
|
+
};
|
|
9506
|
+
}
|
|
9507
|
+
static async fromFile({ outputRoot = process.cwd(), validate = true, global = false }) {
|
|
9508
|
+
const paths = ReasonixHooks.getSettablePaths({ global });
|
|
9509
|
+
const fileContent = await readFileContentOrNull(join(outputRoot, paths.relativeDirPath, paths.relativeFilePath)) ?? "{\"hooks\":{}}";
|
|
9510
|
+
return new ReasonixHooks({
|
|
9511
|
+
outputRoot,
|
|
9512
|
+
relativeDirPath: paths.relativeDirPath,
|
|
9513
|
+
relativeFilePath: paths.relativeFilePath,
|
|
9514
|
+
fileContent,
|
|
9515
|
+
validate
|
|
9516
|
+
});
|
|
9517
|
+
}
|
|
9518
|
+
static async fromRulesyncHooks({ outputRoot = process.cwd(), rulesyncHooks, validate = true, global = false, logger }) {
|
|
9519
|
+
const paths = ReasonixHooks.getSettablePaths({ global });
|
|
9520
|
+
const filePath = join(outputRoot, paths.relativeDirPath, paths.relativeFilePath);
|
|
9521
|
+
const existingContent = await readOrInitializeFileContent(filePath, JSON.stringify({}, null, 2));
|
|
9522
|
+
let settings;
|
|
9523
|
+
try {
|
|
9524
|
+
settings = JSON.parse(existingContent);
|
|
9525
|
+
} catch (error) {
|
|
9526
|
+
throw new Error(`Failed to parse existing Reasonix settings at ${filePath}: ${formatError(error)}`, { cause: error });
|
|
9527
|
+
}
|
|
9528
|
+
const config = rulesyncHooks.getJson();
|
|
9529
|
+
const reasonixHooks = canonicalToReasonixHooks({
|
|
9530
|
+
config,
|
|
9531
|
+
toolOverrideHooks: config.reasonix?.hooks,
|
|
9532
|
+
logger
|
|
9533
|
+
});
|
|
9534
|
+
const merged = {
|
|
9535
|
+
...settings,
|
|
9536
|
+
hooks: reasonixHooks
|
|
9537
|
+
};
|
|
9538
|
+
const fileContent = JSON.stringify(merged, null, 2);
|
|
9539
|
+
return new ReasonixHooks({
|
|
9540
|
+
outputRoot,
|
|
9541
|
+
relativeDirPath: paths.relativeDirPath,
|
|
9542
|
+
relativeFilePath: paths.relativeFilePath,
|
|
9543
|
+
fileContent,
|
|
9544
|
+
validate
|
|
9545
|
+
});
|
|
9546
|
+
}
|
|
9547
|
+
toRulesyncHooks() {
|
|
9548
|
+
let settings;
|
|
9549
|
+
try {
|
|
9550
|
+
settings = JSON.parse(this.getFileContent());
|
|
9551
|
+
} catch (error) {
|
|
9552
|
+
throw new Error(`Failed to parse Reasonix hooks content in ${join(this.getRelativeDirPath(), this.getRelativeFilePath())}: ${formatError(error)}`, { cause: error });
|
|
9553
|
+
}
|
|
9554
|
+
const hooks = reasonixHooksToCanonical(settings.hooks);
|
|
9555
|
+
return this.toRulesyncHooksDefault({ fileContent: JSON.stringify({
|
|
9556
|
+
version: 1,
|
|
9557
|
+
hooks
|
|
9558
|
+
}, null, 2) });
|
|
9559
|
+
}
|
|
9560
|
+
validate() {
|
|
9561
|
+
return {
|
|
9562
|
+
success: true,
|
|
9563
|
+
error: null
|
|
9564
|
+
};
|
|
9565
|
+
}
|
|
9566
|
+
static forDeletion({ outputRoot = process.cwd(), relativeDirPath, relativeFilePath }) {
|
|
9567
|
+
return new ReasonixHooks({
|
|
9568
|
+
outputRoot,
|
|
9569
|
+
relativeDirPath,
|
|
9570
|
+
relativeFilePath,
|
|
9571
|
+
fileContent: JSON.stringify({ hooks: {} }, null, 2),
|
|
9572
|
+
validate: false
|
|
9573
|
+
});
|
|
9574
|
+
}
|
|
9575
|
+
};
|
|
9576
|
+
//#endregion
|
|
8498
9577
|
//#region src/features/hooks/vibe-hooks.ts
|
|
8499
9578
|
const VIBE_DIR = ".vibe";
|
|
8500
9579
|
const VIBE_HOOKS_FILE_NAME = "hooks.toml";
|
|
@@ -8720,6 +9799,19 @@ var VibeHooks = class VibeHooks extends ToolHooks {
|
|
|
8720
9799
|
//#endregion
|
|
8721
9800
|
//#region src/features/hooks/hooks-processor.ts
|
|
8722
9801
|
const HooksProcessorToolTargetSchema = z.enum(hooksProcessorToolTargetTuple);
|
|
9802
|
+
/**
|
|
9803
|
+
* Event names present in the config that the target's adapter cannot emit.
|
|
9804
|
+
*
|
|
9805
|
+
* When the factory passes override-block keys through verbatim
|
|
9806
|
+
* (`passthroughOverrideEvents`), those keys are excluded from the check so
|
|
9807
|
+
* documented passthrough triggers aren't falsely reported as skipped.
|
|
9808
|
+
*/
|
|
9809
|
+
function unsupportedEventNames(params) {
|
|
9810
|
+
const { factory, sharedHooks, effectiveHooks } = params;
|
|
9811
|
+
const supportedEvents = new Set(factory.supportedEvents);
|
|
9812
|
+
const eventNames = factory.passthroughOverrideEvents ? Object.keys(sharedHooks) : Object.keys(effectiveHooks);
|
|
9813
|
+
return [...new Set(eventNames)].filter((e) => !supportedEvents.has(e));
|
|
9814
|
+
}
|
|
8723
9815
|
const toolHooksFactories = /* @__PURE__ */ new Map([
|
|
8724
9816
|
["antigravity-cli", {
|
|
8725
9817
|
class: AntigravityCliHooks,
|
|
@@ -8853,12 +9945,8 @@ const toolHooksFactories = /* @__PURE__ */ new Map([
|
|
|
8853
9945
|
supportsGlobal: true,
|
|
8854
9946
|
supportsImport: true
|
|
8855
9947
|
},
|
|
8856
|
-
supportedEvents:
|
|
8857
|
-
supportedHookTypes: [
|
|
8858
|
-
"command",
|
|
8859
|
-
"prompt",
|
|
8860
|
-
"http"
|
|
8861
|
-
],
|
|
9948
|
+
supportedEvents: HERMESAGENT_HOOK_EVENTS,
|
|
9949
|
+
supportedHookTypes: ["command"],
|
|
8862
9950
|
supportsMatcher: true
|
|
8863
9951
|
}],
|
|
8864
9952
|
["deepagents", {
|
|
@@ -8894,6 +9982,18 @@ const toolHooksFactories = /* @__PURE__ */ new Map([
|
|
|
8894
9982
|
supportedHookTypes: ["command"],
|
|
8895
9983
|
supportsMatcher: true
|
|
8896
9984
|
}],
|
|
9985
|
+
["kiro-ide", {
|
|
9986
|
+
class: KiroIdeHooks,
|
|
9987
|
+
meta: {
|
|
9988
|
+
supportsProject: true,
|
|
9989
|
+
supportsGlobal: true,
|
|
9990
|
+
supportsImport: true
|
|
9991
|
+
},
|
|
9992
|
+
supportedEvents: KIRO_IDE_HOOK_EVENTS,
|
|
9993
|
+
supportedHookTypes: ["command", "prompt"],
|
|
9994
|
+
supportsMatcher: true,
|
|
9995
|
+
passthroughOverrideEvents: true
|
|
9996
|
+
}],
|
|
8897
9997
|
["devin", {
|
|
8898
9998
|
class: DevinHooks,
|
|
8899
9999
|
meta: {
|
|
@@ -8902,8 +10002,8 @@ const toolHooksFactories = /* @__PURE__ */ new Map([
|
|
|
8902
10002
|
supportsImport: true
|
|
8903
10003
|
},
|
|
8904
10004
|
supportedEvents: DEVIN_HOOK_EVENTS,
|
|
8905
|
-
supportedHookTypes: ["command"],
|
|
8906
|
-
supportsMatcher:
|
|
10005
|
+
supportedHookTypes: ["command", "prompt"],
|
|
10006
|
+
supportsMatcher: true
|
|
8907
10007
|
}],
|
|
8908
10008
|
["augmentcode", {
|
|
8909
10009
|
class: AugmentcodeHooks,
|
|
@@ -8948,6 +10048,17 @@ const toolHooksFactories = /* @__PURE__ */ new Map([
|
|
|
8948
10048
|
supportedEvents: QWENCODE_HOOK_EVENTS,
|
|
8949
10049
|
supportedHookTypes: ["command"],
|
|
8950
10050
|
supportsMatcher: true
|
|
10051
|
+
}],
|
|
10052
|
+
["reasonix", {
|
|
10053
|
+
class: ReasonixHooks,
|
|
10054
|
+
meta: {
|
|
10055
|
+
supportsProject: true,
|
|
10056
|
+
supportsGlobal: true,
|
|
10057
|
+
supportsImport: true
|
|
10058
|
+
},
|
|
10059
|
+
supportedEvents: REASONIX_HOOK_EVENTS,
|
|
10060
|
+
supportedHookTypes: ["command"],
|
|
10061
|
+
supportsMatcher: true
|
|
8951
10062
|
}]
|
|
8952
10063
|
]);
|
|
8953
10064
|
const hooksProcessorToolTargets = [...toolHooksFactories.entries()].filter(([, f]) => f.meta.supportsProject).map(([t]) => t);
|
|
@@ -9023,8 +10134,11 @@ var HooksProcessor = class extends FeatureProcessor {
|
|
|
9023
10134
|
...overrideHooks
|
|
9024
10135
|
};
|
|
9025
10136
|
{
|
|
9026
|
-
const
|
|
9027
|
-
|
|
10137
|
+
const skipped = unsupportedEventNames({
|
|
10138
|
+
factory,
|
|
10139
|
+
sharedHooks,
|
|
10140
|
+
effectiveHooks
|
|
10141
|
+
});
|
|
9028
10142
|
if (skipped.length > 0) this.logger.warn(`Skipped hook event(s) for ${this.toolTarget} (not supported): ${skipped.join(", ")}`);
|
|
9029
10143
|
}
|
|
9030
10144
|
{
|
|
@@ -9220,11 +10334,6 @@ var AiassistantIgnore = class AiassistantIgnore extends ToolIgnore {
|
|
|
9220
10334
|
}
|
|
9221
10335
|
};
|
|
9222
10336
|
//#endregion
|
|
9223
|
-
//#region src/constants/antigravity-cli-paths.ts
|
|
9224
|
-
const ANTIGRAVITY_AGENTS_DIR = ANTIGRAVITY_DIR;
|
|
9225
|
-
const ANTIGRAVITY_RULE_FILE_NAME = "AGENTS.md";
|
|
9226
|
-
const ANTIGRAVITY_GLOBAL_RULE_FILE_NAME = "GEMINI.md";
|
|
9227
|
-
//#endregion
|
|
9228
10337
|
//#region src/features/ignore/antigravity-cli-ignore.ts
|
|
9229
10338
|
/**
|
|
9230
10339
|
* Antigravity CLI is the successor to Gemini CLI and is built on the same
|
|
@@ -10456,9 +11565,9 @@ function parseAmpSettingsJsonc(fileContent) {
|
|
|
10456
11565
|
}
|
|
10457
11566
|
function filterMcpServers(mcpServers) {
|
|
10458
11567
|
const filtered = {};
|
|
10459
|
-
if (!isRecord
|
|
11568
|
+
if (!isRecord(mcpServers)) return filtered;
|
|
10460
11569
|
for (const [name, config] of Object.entries(mcpServers)) {
|
|
10461
|
-
if (isPrototypePollutionKey(name) || !isRecord
|
|
11570
|
+
if (isPrototypePollutionKey(name) || !isRecord(config)) continue;
|
|
10462
11571
|
const filteredConfig = {};
|
|
10463
11572
|
for (const [key, value] of Object.entries(config)) {
|
|
10464
11573
|
if (isPrototypePollutionKey(key)) continue;
|
|
@@ -10573,7 +11682,7 @@ var AmpMcp = class AmpMcp extends ToolMcp {
|
|
|
10573
11682
|
success: true,
|
|
10574
11683
|
error: null
|
|
10575
11684
|
};
|
|
10576
|
-
if (!isRecord
|
|
11685
|
+
if (!isRecord(mcpServers)) return {
|
|
10577
11686
|
success: false,
|
|
10578
11687
|
error: /* @__PURE__ */ new Error(`${AMP_MCP_SERVERS_KEY} must be a JSON object`)
|
|
10579
11688
|
};
|
|
@@ -10582,7 +11691,7 @@ var AmpMcp = class AmpMcp extends ToolMcp {
|
|
|
10582
11691
|
success: false,
|
|
10583
11692
|
error: /* @__PURE__ */ new Error(`Server name "${serverName}" is a prototype pollution key and is not allowed`)
|
|
10584
11693
|
};
|
|
10585
|
-
if (!isRecord
|
|
11694
|
+
if (!isRecord(serverConfig)) return {
|
|
10586
11695
|
success: false,
|
|
10587
11696
|
error: /* @__PURE__ */ new Error(`MCP server "${serverName}" must be a JSON object`)
|
|
10588
11697
|
};
|
|
@@ -11103,7 +12212,7 @@ const MAX_REMOVE_EMPTY_ENTRIES_DEPTH$1 = 32;
|
|
|
11103
12212
|
function convertFromCodexFormat(codexMcp) {
|
|
11104
12213
|
const result = {};
|
|
11105
12214
|
for (const [name, config] of Object.entries(codexMcp)) {
|
|
11106
|
-
if (PROTOTYPE_POLLUTION_KEYS.has(name) || !isRecord
|
|
12215
|
+
if (PROTOTYPE_POLLUTION_KEYS.has(name) || !isRecord(config)) continue;
|
|
11107
12216
|
const converted = {};
|
|
11108
12217
|
for (const [key, value] of Object.entries(config)) {
|
|
11109
12218
|
if (PROTOTYPE_POLLUTION_KEYS.has(key)) continue;
|
|
@@ -11123,7 +12232,7 @@ function convertToCodexFormat(mcpServers) {
|
|
|
11123
12232
|
const result = {};
|
|
11124
12233
|
for (const [name, config] of Object.entries(mcpServers)) {
|
|
11125
12234
|
if (PROTOTYPE_POLLUTION_KEYS.has(name)) continue;
|
|
11126
|
-
if (!isRecord
|
|
12235
|
+
if (!isRecord(config)) continue;
|
|
11127
12236
|
const converted = {};
|
|
11128
12237
|
for (const [key, value] of Object.entries(config)) {
|
|
11129
12238
|
if (PROTOTYPE_POLLUTION_KEYS.has(key)) continue;
|
|
@@ -11185,19 +12294,19 @@ var CodexcliMcp = class CodexcliMcp extends ToolMcp {
|
|
|
11185
12294
|
const strippedMcpServers = rulesyncMcp.getMcpServers();
|
|
11186
12295
|
const rawMcpServers = rulesyncMcp.getJson().mcpServers;
|
|
11187
12296
|
const converted = convertToCodexFormat(Object.fromEntries(Object.entries(strippedMcpServers).map(([serverName, serverConfig]) => {
|
|
11188
|
-
const rawServer = isRecord
|
|
12297
|
+
const rawServer = isRecord(rawMcpServers) ? rawMcpServers[serverName] : void 0;
|
|
11189
12298
|
return [serverName, {
|
|
11190
12299
|
...serverConfig,
|
|
11191
|
-
...isRecord
|
|
12300
|
+
...isRecord(rawServer) && isStringArray(rawServer.envVars) ? { envVars: rawServer.envVars } : {}
|
|
11192
12301
|
}];
|
|
11193
12302
|
})));
|
|
11194
12303
|
const filteredMcpServers = this.removeEmptyEntries(converted);
|
|
11195
12304
|
for (const name of Object.keys(converted)) if (!Object.hasOwn(filteredMcpServers, name)) warnWithFallback(void 0, `MCP server "${name}" had no non-empty configuration and was dropped from the codex CLI config`);
|
|
11196
|
-
const existingMcpServers = isRecord
|
|
12305
|
+
const existingMcpServers = isRecord(configToml["mcp_servers"]) ? configToml["mcp_servers"] : {};
|
|
11197
12306
|
configToml["mcp_servers"] = Object.fromEntries(Object.entries(filteredMcpServers).map(([name, serverConfig]) => {
|
|
11198
|
-
const existingServer = isRecord
|
|
12307
|
+
const existingServer = isRecord(existingMcpServers[name]) ? existingMcpServers[name] : void 0;
|
|
11199
12308
|
const serverRecord = serverConfig;
|
|
11200
|
-
if (existingServer && isRecord
|
|
12309
|
+
if (existingServer && isRecord(existingServer["tools"]) && !("tools" in serverRecord)) return [name, {
|
|
11201
12310
|
...serverRecord,
|
|
11202
12311
|
tools: existingServer["tools"]
|
|
11203
12312
|
}];
|
|
@@ -11707,18 +12816,20 @@ var DeepagentsMcp = class DeepagentsMcp extends ToolMcp {
|
|
|
11707
12816
|
//#endregion
|
|
11708
12817
|
//#region src/features/mcp/devin-mcp.ts
|
|
11709
12818
|
/**
|
|
11710
|
-
* MCP generator for Devin (
|
|
12819
|
+
* MCP generator for Devin Local (native `.devin/` configuration).
|
|
11711
12820
|
*
|
|
11712
|
-
* Devin reads
|
|
11713
|
-
* - Project scope: `.
|
|
11714
|
-
* - Global scope: `~/.
|
|
12821
|
+
* Devin reads MCP servers from the `mcpServers` key of its native config file:
|
|
12822
|
+
* - Project scope: `.devin/config.json`
|
|
12823
|
+
* - Global scope: `~/.config/devin/config.json`
|
|
11715
12824
|
*
|
|
11716
|
-
* The
|
|
11717
|
-
* the
|
|
11718
|
-
*
|
|
11719
|
-
* stdio entry ({ command, args, env }) or a
|
|
11720
|
-
* ({ serverUrl | url, headers }), and may carry an optional
|
|
11721
|
-
* array.
|
|
12825
|
+
* The config file is shared with the permissions feature (`permissions` key)
|
|
12826
|
+
* and, in global mode, the hooks feature (`hooks` key), so reads and writes
|
|
12827
|
+
* merge into the existing JSON rather than overwriting it, and the file is
|
|
12828
|
+
* never deleted. Each server is a stdio entry ({ command, args, env }) or a
|
|
12829
|
+
* remote entry ({ serverUrl | url, headers }), and may carry an optional
|
|
12830
|
+
* `disabledTools` array.
|
|
12831
|
+
*
|
|
12832
|
+
* @see https://docs.devin.ai/cli/extensibility/configuration
|
|
11722
12833
|
*/
|
|
11723
12834
|
var DevinMcp = class DevinMcp extends ToolMcp {
|
|
11724
12835
|
json;
|
|
@@ -11736,14 +12847,21 @@ var DevinMcp = class DevinMcp extends ToolMcp {
|
|
|
11736
12847
|
throw new Error(`Failed to parse Devin MCP config at ${join(relativeDirPath, relativeFilePath)}: ${formatError(error)}`, { cause: error });
|
|
11737
12848
|
}
|
|
11738
12849
|
}
|
|
12850
|
+
/**
|
|
12851
|
+
* config.json may carry the permissions/hooks features' keys, so it is never
|
|
12852
|
+
* deleted; only the managed `mcpServers` key is rewritten.
|
|
12853
|
+
*/
|
|
12854
|
+
isDeletable() {
|
|
12855
|
+
return false;
|
|
12856
|
+
}
|
|
11739
12857
|
static getSettablePaths({ global = false } = {}) {
|
|
11740
12858
|
if (global) return {
|
|
11741
|
-
relativeDirPath:
|
|
11742
|
-
relativeFilePath:
|
|
12859
|
+
relativeDirPath: DEVIN_GLOBAL_CONFIG_DIR_PATH,
|
|
12860
|
+
relativeFilePath: DEVIN_CONFIG_FILE_NAME
|
|
11743
12861
|
};
|
|
11744
12862
|
return {
|
|
11745
|
-
relativeDirPath:
|
|
11746
|
-
relativeFilePath:
|
|
12863
|
+
relativeDirPath: DEVIN_DIR,
|
|
12864
|
+
relativeFilePath: DEVIN_CONFIG_FILE_NAME
|
|
11747
12865
|
};
|
|
11748
12866
|
}
|
|
11749
12867
|
static async fromFile({ outputRoot = process.cwd(), validate = true, global = false }) {
|
|
@@ -11858,7 +12976,7 @@ var FactorydroidMcp = class FactorydroidMcp extends ToolMcp {
|
|
|
11858
12976
|
};
|
|
11859
12977
|
//#endregion
|
|
11860
12978
|
//#region src/features/mcp/goose-mcp.ts
|
|
11861
|
-
const
|
|
12979
|
+
const GOOSE_PLUGIN_MCP_RELATIVE_PATH = join(GOOSE_PLUGIN_MCP_DIR_PATH, GOOSE_PLUGIN_MCP_FILE_NAME);
|
|
11862
12980
|
function parseGooseConfig(fileContent, relativeDirPath, relativeFilePath) {
|
|
11863
12981
|
const configPath = join(relativeDirPath, relativeFilePath);
|
|
11864
12982
|
let parsed;
|
|
@@ -11945,7 +13063,7 @@ function convertServerToGooseExtension(name, config) {
|
|
|
11945
13063
|
function convertToGooseFormat(mcpServers) {
|
|
11946
13064
|
const extensions = {};
|
|
11947
13065
|
for (const [name, config] of Object.entries(mcpServers)) {
|
|
11948
|
-
if (PROTOTYPE_POLLUTION_KEYS.has(name) || !isRecord
|
|
13066
|
+
if (PROTOTYPE_POLLUTION_KEYS.has(name) || !isRecord(config)) continue;
|
|
11949
13067
|
extensions[name] = convertServerToGooseExtension(name, config);
|
|
11950
13068
|
}
|
|
11951
13069
|
return extensions;
|
|
@@ -11962,7 +13080,7 @@ function convertToGooseFormat(mcpServers) {
|
|
|
11962
13080
|
function convertFromGooseFormat(extensions) {
|
|
11963
13081
|
const result = {};
|
|
11964
13082
|
for (const [name, ext] of Object.entries(extensions)) {
|
|
11965
|
-
if (PROTOTYPE_POLLUTION_KEYS.has(name) || !isRecord
|
|
13083
|
+
if (PROTOTYPE_POLLUTION_KEYS.has(name) || !isRecord(ext)) continue;
|
|
11966
13084
|
const server = {};
|
|
11967
13085
|
const type = typeof ext.type === "string" ? ext.type : void 0;
|
|
11968
13086
|
if (type === "sse") server.type = "sse";
|
|
@@ -11980,39 +13098,102 @@ function convertFromGooseFormat(extensions) {
|
|
|
11980
13098
|
return result;
|
|
11981
13099
|
}
|
|
11982
13100
|
/**
|
|
13101
|
+
* Builds a Claude-style stdio server entry (`command`/`args`/`env`/`cwd`) for the
|
|
13102
|
+
* open-plugins `.mcp.json` manifest.
|
|
13103
|
+
*/
|
|
13104
|
+
function buildGoosePluginStdioServer(config) {
|
|
13105
|
+
const server = {};
|
|
13106
|
+
const command = config.command;
|
|
13107
|
+
if (Array.isArray(command)) {
|
|
13108
|
+
if (typeof command[0] === "string") server.command = command[0];
|
|
13109
|
+
const rest = command.slice(1).filter((c) => typeof c === "string");
|
|
13110
|
+
const args = isStringArray(config.args) ? config.args : [];
|
|
13111
|
+
if (rest.length > 0 || args.length > 0) server.args = [...rest, ...args];
|
|
13112
|
+
} else if (typeof command === "string") {
|
|
13113
|
+
server.command = command;
|
|
13114
|
+
if (isStringArray(config.args)) server.args = config.args;
|
|
13115
|
+
}
|
|
13116
|
+
if (isPlainObject(config.env)) server.env = omitPrototypePollutionKeys(config.env);
|
|
13117
|
+
if (typeof config.cwd === "string") server.cwd = config.cwd;
|
|
13118
|
+
return server;
|
|
13119
|
+
}
|
|
13120
|
+
/**
|
|
13121
|
+
* Converts rulesync canonical MCP servers into the Claude-style `mcpServers` map
|
|
13122
|
+
* used by Goose open-plugin manifests (`.agents/plugins/rulesync/.mcp.json`).
|
|
13123
|
+
*
|
|
13124
|
+
* The open-plugins manifest is **stdio-only** (no `url`/`headers`), so remote
|
|
13125
|
+
* (http/sse/streamable_http) and `builtin` servers cannot be represented. They
|
|
13126
|
+
* are skipped with a warning rather than silently dropped — remote servers stay
|
|
13127
|
+
* global-config-only (sync them with `--global` to `~/.config/goose/config.yaml`).
|
|
13128
|
+
*/
|
|
13129
|
+
function convertToGoosePluginMcpServers(mcpServers, logger) {
|
|
13130
|
+
const result = {};
|
|
13131
|
+
for (const [name, config] of Object.entries(mcpServers)) {
|
|
13132
|
+
if (PROTOTYPE_POLLUTION_KEYS.has(name) || !isRecord(config)) continue;
|
|
13133
|
+
const gooseType = resolveGooseType(config, resolveGooseUrl(config));
|
|
13134
|
+
if (gooseType !== "stdio") {
|
|
13135
|
+
warnWithFallback(logger, `Goose open-plugin MCP manifest (${GOOSE_PLUGIN_MCP_RELATIVE_PATH}) is stdio-only; skipping "${name}" (${gooseType}). Sync it with --global to ~/.config/goose/config.yaml instead.`);
|
|
13136
|
+
continue;
|
|
13137
|
+
}
|
|
13138
|
+
result[name] = buildGoosePluginStdioServer(config);
|
|
13139
|
+
}
|
|
13140
|
+
return result;
|
|
13141
|
+
}
|
|
13142
|
+
/**
|
|
11983
13143
|
* Goose MCP servers.
|
|
11984
13144
|
*
|
|
11985
|
-
* Goose configures MCP servers
|
|
11986
|
-
*
|
|
11987
|
-
*
|
|
11988
|
-
*
|
|
11989
|
-
*
|
|
13145
|
+
* Goose configures MCP servers in two locations:
|
|
13146
|
+
*
|
|
13147
|
+
* - **Global** (`--global`): "extensions" in the shared user config
|
|
13148
|
+
* `~/.config/goose/config.yaml`. That file also holds other Goose settings
|
|
13149
|
+
* (model, provider, ...), so generation merges the `extensions:` block into the
|
|
13150
|
+
* existing config instead of overwriting it, and the file is never deleted.
|
|
13151
|
+
* This location supports both stdio and remote (http/sse) servers.
|
|
13152
|
+
* - **Project**: a stdio-only open-plugin manifest at
|
|
13153
|
+
* `.agents/plugins/rulesync/.mcp.json` (Goose v1.39.0+), reusing the same
|
|
13154
|
+
* `.agents/plugins/rulesync/` tree as Goose hooks. The manifest uses the
|
|
13155
|
+
* Claude-style `{ "mcpServers": { "<name>": { command, args, env, cwd } } }`
|
|
13156
|
+
* shape and cannot express `url`/`headers`, so remote servers are skipped with
|
|
13157
|
+
* a warning in project mode (use `--global` to sync them instead).
|
|
11990
13158
|
*
|
|
11991
13159
|
* @see https://block.github.io/goose/docs/getting-started/using-extensions/
|
|
13160
|
+
* @see https://github.com/block/goose/pull/9471
|
|
11992
13161
|
*/
|
|
11993
13162
|
var GooseMcp = class GooseMcp extends ToolMcp {
|
|
11994
13163
|
config;
|
|
11995
13164
|
constructor(params) {
|
|
11996
13165
|
super(params);
|
|
11997
|
-
if (this.fileContent
|
|
11998
|
-
else this.config =
|
|
13166
|
+
if (this.fileContent === void 0) this.config = {};
|
|
13167
|
+
else if (params.global) this.config = parseGooseConfig(this.fileContent, this.relativeDirPath, this.relativeFilePath);
|
|
13168
|
+
else this.config = this.fileContent ? this.parsePluginManifest(this.fileContent) : {};
|
|
13169
|
+
}
|
|
13170
|
+
parsePluginManifest(fileContent) {
|
|
13171
|
+
try {
|
|
13172
|
+
const parsed = JSON.parse(fileContent);
|
|
13173
|
+
return isRecord(parsed) ? parsed : {};
|
|
13174
|
+
} catch (error) {
|
|
13175
|
+
throw new Error(`Failed to parse Goose MCP manifest at ${join(this.relativeDirPath, this.relativeFilePath)}: ${formatError(error)}`, { cause: error });
|
|
13176
|
+
}
|
|
11999
13177
|
}
|
|
12000
13178
|
getConfig() {
|
|
12001
13179
|
return this.config;
|
|
12002
13180
|
}
|
|
12003
13181
|
isDeletable() {
|
|
12004
|
-
return
|
|
13182
|
+
return !this.global;
|
|
12005
13183
|
}
|
|
12006
|
-
static getSettablePaths(
|
|
12007
|
-
return {
|
|
13184
|
+
static getSettablePaths({ global = false } = {}) {
|
|
13185
|
+
if (global) return {
|
|
12008
13186
|
relativeDirPath: GOOSE_GLOBAL_DIR,
|
|
12009
13187
|
relativeFilePath: GOOSE_MCP_FILE_NAME
|
|
12010
13188
|
};
|
|
13189
|
+
return {
|
|
13190
|
+
relativeDirPath: GOOSE_PLUGIN_MCP_DIR_PATH,
|
|
13191
|
+
relativeFilePath: GOOSE_PLUGIN_MCP_FILE_NAME
|
|
13192
|
+
};
|
|
12011
13193
|
}
|
|
12012
13194
|
static async fromFile({ outputRoot = process.cwd(), validate = true, global = false }) {
|
|
12013
|
-
if (!global) throw new Error(GOOSE_GLOBAL_ONLY_MESSAGE$1);
|
|
12014
13195
|
const paths = this.getSettablePaths({ global });
|
|
12015
|
-
const fileContent = await readFileContentOrNull(join(outputRoot, paths.relativeDirPath, paths.relativeFilePath)) ?? "";
|
|
13196
|
+
const fileContent = await readFileContentOrNull(join(outputRoot, paths.relativeDirPath, paths.relativeFilePath)) ?? (global ? "" : JSON.stringify({ mcpServers: {} }, null, 2));
|
|
12016
13197
|
return new GooseMcp({
|
|
12017
13198
|
outputRoot,
|
|
12018
13199
|
relativeDirPath: paths.relativeDirPath,
|
|
@@ -12022,9 +13203,19 @@ var GooseMcp = class GooseMcp extends ToolMcp {
|
|
|
12022
13203
|
global
|
|
12023
13204
|
});
|
|
12024
13205
|
}
|
|
12025
|
-
static async fromRulesyncMcp({ outputRoot = process.cwd(), rulesyncMcp, validate = true, global = false }) {
|
|
12026
|
-
if (!global) throw new Error(GOOSE_GLOBAL_ONLY_MESSAGE$1);
|
|
13206
|
+
static async fromRulesyncMcp({ outputRoot = process.cwd(), rulesyncMcp, validate = true, global = false, logger }) {
|
|
12027
13207
|
const paths = this.getSettablePaths({ global });
|
|
13208
|
+
if (!global) {
|
|
13209
|
+
const mcpServers = convertToGoosePluginMcpServers(rulesyncMcp.getMcpServers(), logger);
|
|
13210
|
+
return new GooseMcp({
|
|
13211
|
+
outputRoot,
|
|
13212
|
+
relativeDirPath: paths.relativeDirPath,
|
|
13213
|
+
relativeFilePath: paths.relativeFilePath,
|
|
13214
|
+
fileContent: JSON.stringify({ mcpServers }, null, 2),
|
|
13215
|
+
validate,
|
|
13216
|
+
global
|
|
13217
|
+
});
|
|
13218
|
+
}
|
|
12028
13219
|
const merged = {
|
|
12029
13220
|
...parseGooseConfig(await readOrInitializeFileContent(join(outputRoot, paths.relativeDirPath, paths.relativeFilePath), ""), paths.relativeDirPath, paths.relativeFilePath),
|
|
12030
13221
|
extensions: convertToGooseFormat(rulesyncMcp.getMcpServers())
|
|
@@ -12039,7 +13230,11 @@ var GooseMcp = class GooseMcp extends ToolMcp {
|
|
|
12039
13230
|
});
|
|
12040
13231
|
}
|
|
12041
13232
|
toRulesyncMcp() {
|
|
12042
|
-
|
|
13233
|
+
if (!this.global) {
|
|
13234
|
+
const mcpServers = isRecord(this.config.mcpServers) ? this.config.mcpServers : {};
|
|
13235
|
+
return this.toRulesyncMcpDefault({ fileContent: JSON.stringify({ mcpServers }, null, 2) });
|
|
13236
|
+
}
|
|
13237
|
+
const mcpServers = convertFromGooseFormat(isRecord(this.config.extensions) ? this.config.extensions : {});
|
|
12043
13238
|
return this.toRulesyncMcpDefault({ fileContent: JSON.stringify({ mcpServers }, null, 2) });
|
|
12044
13239
|
}
|
|
12045
13240
|
validate() {
|
|
@@ -12116,7 +13311,7 @@ function convertToGrokFormat(mcpServers) {
|
|
|
12116
13311
|
const result = {};
|
|
12117
13312
|
for (const [name, config] of Object.entries(mcpServers)) {
|
|
12118
13313
|
if (PROTOTYPE_POLLUTION_KEYS.has(name)) continue;
|
|
12119
|
-
if (!isRecord
|
|
13314
|
+
if (!isRecord(config)) continue;
|
|
12120
13315
|
const converted = {};
|
|
12121
13316
|
for (const [key, value] of Object.entries(config)) {
|
|
12122
13317
|
if (PROTOTYPE_POLLUTION_KEYS.has(key)) continue;
|
|
@@ -12131,7 +13326,7 @@ function convertToGrokFormat(mcpServers) {
|
|
|
12131
13326
|
function convertFromGrokFormat(grokMcp) {
|
|
12132
13327
|
const result = {};
|
|
12133
13328
|
for (const [name, config] of Object.entries(grokMcp)) {
|
|
12134
|
-
if (PROTOTYPE_POLLUTION_KEYS.has(name) || !isRecord
|
|
13329
|
+
if (PROTOTYPE_POLLUTION_KEYS.has(name) || !isRecord(config)) continue;
|
|
12135
13330
|
const converted = {};
|
|
12136
13331
|
for (const [key, value] of Object.entries(config)) {
|
|
12137
13332
|
if (PROTOTYPE_POLLUTION_KEYS.has(key)) continue;
|
|
@@ -12294,13 +13489,13 @@ function convertServerToHermes(config) {
|
|
|
12294
13489
|
function convertToHermesFormat(mcpServers) {
|
|
12295
13490
|
const result = {};
|
|
12296
13491
|
for (const [name, config] of Object.entries(mcpServers)) {
|
|
12297
|
-
if (PROTOTYPE_POLLUTION_KEYS.has(name) || !isRecord
|
|
13492
|
+
if (PROTOTYPE_POLLUTION_KEYS.has(name) || !isRecord(config)) continue;
|
|
12298
13493
|
result[name] = convertServerToHermes(config);
|
|
12299
13494
|
}
|
|
12300
13495
|
return result;
|
|
12301
13496
|
}
|
|
12302
13497
|
function mergeHermesMcpServers(config, mcpServers) {
|
|
12303
|
-
const existingMcpServers = isRecord
|
|
13498
|
+
const existingMcpServers = isRecord(config.mcp_servers) ? config.mcp_servers : {};
|
|
12304
13499
|
return {
|
|
12305
13500
|
...config,
|
|
12306
13501
|
mcp_servers: {
|
|
@@ -12318,7 +13513,7 @@ function mergeHermesMcpServers(config, mcpServers) {
|
|
|
12318
13513
|
function convertFromHermesFormat(mcpServers) {
|
|
12319
13514
|
const result = {};
|
|
12320
13515
|
for (const [name, config] of Object.entries(mcpServers)) {
|
|
12321
|
-
if (PROTOTYPE_POLLUTION_KEYS.has(name) || !isRecord
|
|
13516
|
+
if (PROTOTYPE_POLLUTION_KEYS.has(name) || !isRecord(config)) continue;
|
|
12322
13517
|
const server = {};
|
|
12323
13518
|
if (typeof config.command === "string") server.command = config.command;
|
|
12324
13519
|
if (isStringArray(config.args)) server.args = config.args;
|
|
@@ -12354,7 +13549,7 @@ var HermesagentMcp = class HermesagentMcp extends ToolMcp {
|
|
|
12354
13549
|
return true;
|
|
12355
13550
|
}
|
|
12356
13551
|
setFileContent(fileContent) {
|
|
12357
|
-
const merged = mergeHermesMcpServers(parseHermesConfig(fileContent), isRecord
|
|
13552
|
+
const merged = mergeHermesMcpServers(parseHermesConfig(fileContent), isRecord(this.config.mcp_servers) ? this.config.mcp_servers : {});
|
|
12358
13553
|
this.config = merged;
|
|
12359
13554
|
super.setFileContent(stringifyHermesConfig(merged));
|
|
12360
13555
|
}
|
|
@@ -12394,7 +13589,7 @@ var HermesagentMcp = class HermesagentMcp extends ToolMcp {
|
|
|
12394
13589
|
});
|
|
12395
13590
|
}
|
|
12396
13591
|
toRulesyncMcp() {
|
|
12397
|
-
const servers = convertFromHermesFormat(isRecord
|
|
13592
|
+
const servers = convertFromHermesFormat(isRecord(this.config.mcp_servers) ? this.config.mcp_servers : {});
|
|
12398
13593
|
return this.toRulesyncMcpDefault({ fileContent: JSON.stringify({ mcpServers: servers }, null, 2) });
|
|
12399
13594
|
}
|
|
12400
13595
|
validate() {
|
|
@@ -13229,11 +14424,6 @@ var QwencodeMcp = class QwencodeMcp extends ToolMcp {
|
|
|
13229
14424
|
}
|
|
13230
14425
|
};
|
|
13231
14426
|
//#endregion
|
|
13232
|
-
//#region src/constants/reasonix-paths.ts
|
|
13233
|
-
const REASONIX_PROJECT_MCP_FILE_NAME = "reasonix.toml";
|
|
13234
|
-
const REASONIX_GLOBAL_DIR = ".reasonix";
|
|
13235
|
-
const REASONIX_GLOBAL_MCP_FILE_NAME = "config.toml";
|
|
13236
|
-
//#endregion
|
|
13237
14427
|
//#region src/features/mcp/reasonix-mcp.ts
|
|
13238
14428
|
const REASONIX_PLUGIN_FIELDS = [
|
|
13239
14429
|
"type",
|
|
@@ -13241,13 +14431,14 @@ const REASONIX_PLUGIN_FIELDS = [
|
|
|
13241
14431
|
"args",
|
|
13242
14432
|
"env",
|
|
13243
14433
|
"url",
|
|
13244
|
-
"headers"
|
|
14434
|
+
"headers",
|
|
14435
|
+
"trusted_read_only_tools"
|
|
13245
14436
|
];
|
|
13246
14437
|
var ReasonixMcp = class ReasonixMcp extends ToolMcp {
|
|
13247
14438
|
toml;
|
|
13248
14439
|
constructor(params) {
|
|
13249
14440
|
super(params);
|
|
13250
|
-
this.toml = parseReasonixConfig(this.fileContent);
|
|
14441
|
+
this.toml = parseReasonixConfig$1(this.fileContent);
|
|
13251
14442
|
}
|
|
13252
14443
|
getToml() {
|
|
13253
14444
|
return this.toml;
|
|
@@ -13271,7 +14462,7 @@ var ReasonixMcp = class ReasonixMcp extends ToolMcp {
|
|
|
13271
14462
|
}
|
|
13272
14463
|
static async fromFile({ outputRoot = process.cwd(), validate = true, global = false }) {
|
|
13273
14464
|
const paths = this.getSettablePaths({ global });
|
|
13274
|
-
const config = parseReasonixConfig(await readFileContentOrNull(join(outputRoot, paths.relativeDirPath, paths.relativeFilePath)) ?? smolToml.stringify({}));
|
|
14465
|
+
const config = parseReasonixConfig$1(await readFileContentOrNull(join(outputRoot, paths.relativeDirPath, paths.relativeFilePath)) ?? smolToml.stringify({}));
|
|
13275
14466
|
config.plugins = normalizePluginsArray(config.plugins);
|
|
13276
14467
|
return new ReasonixMcp({
|
|
13277
14468
|
outputRoot,
|
|
@@ -13284,7 +14475,7 @@ var ReasonixMcp = class ReasonixMcp extends ToolMcp {
|
|
|
13284
14475
|
}
|
|
13285
14476
|
static async fromRulesyncMcp({ outputRoot = process.cwd(), rulesyncMcp, validate = true, global = false }) {
|
|
13286
14477
|
const paths = this.getSettablePaths({ global });
|
|
13287
|
-
const config = parseReasonixConfig(await readFileContentOrNull(join(outputRoot, paths.relativeDirPath, paths.relativeFilePath)) ?? smolToml.stringify({}));
|
|
14478
|
+
const config = parseReasonixConfig$1(await readFileContentOrNull(join(outputRoot, paths.relativeDirPath, paths.relativeFilePath)) ?? smolToml.stringify({}));
|
|
13288
14479
|
config.plugins = Object.entries(rulesyncMcp.getMcpServers()).map(([name, server]) => rulesyncMcpServerToReasonix(name, server));
|
|
13289
14480
|
return new ReasonixMcp({
|
|
13290
14481
|
outputRoot,
|
|
@@ -13301,7 +14492,7 @@ var ReasonixMcp = class ReasonixMcp extends ToolMcp {
|
|
|
13301
14492
|
}
|
|
13302
14493
|
validate() {
|
|
13303
14494
|
try {
|
|
13304
|
-
parseReasonixConfig(this.fileContent);
|
|
14495
|
+
parseReasonixConfig$1(this.fileContent);
|
|
13305
14496
|
return {
|
|
13306
14497
|
success: true,
|
|
13307
14498
|
error: null
|
|
@@ -13324,7 +14515,7 @@ var ReasonixMcp = class ReasonixMcp extends ToolMcp {
|
|
|
13324
14515
|
});
|
|
13325
14516
|
}
|
|
13326
14517
|
};
|
|
13327
|
-
function parseReasonixConfig(fileContent) {
|
|
14518
|
+
function parseReasonixConfig$1(fileContent) {
|
|
13328
14519
|
const parsed = smolToml.parse(fileContent || smolToml.stringify({}));
|
|
13329
14520
|
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return {};
|
|
13330
14521
|
return { ...parsed };
|
|
@@ -13464,17 +14655,6 @@ var RooMcp = class RooMcp extends ToolMcp {
|
|
|
13464
14655
|
}
|
|
13465
14656
|
};
|
|
13466
14657
|
//#endregion
|
|
13467
|
-
//#region src/constants/rovodev-paths.ts
|
|
13468
|
-
const ROVODEV_DIR = ".rovodev";
|
|
13469
|
-
const ROVODEV_SKILLS_DIR_PATH = join(ROVODEV_DIR, "skills");
|
|
13470
|
-
const ROVODEV_SUBAGENTS_DIR_PATH = join(ROVODEV_DIR, "subagents");
|
|
13471
|
-
const ROVODEV_MODULAR_RULES_DIR_PATH = join(ROVODEV_DIR, ".rulesync", "modular-rules");
|
|
13472
|
-
const ROVODEV_RULE_FILE_NAME = "AGENTS.md";
|
|
13473
|
-
const ROVODEV_LEGACY_RULE_FILE_NAME = "AGENTS.local.md";
|
|
13474
|
-
const ROVODEV_MCP_FILE_NAME = "mcp.json";
|
|
13475
|
-
const ROVODEV_CONFIG_FILE_NAME = "config.yml";
|
|
13476
|
-
const ROVODEV_AGENTS_SKILLS_DIR_PATH = join(".agents", "skills");
|
|
13477
|
-
//#endregion
|
|
13478
14658
|
//#region src/features/mcp/rovodev-mcp.ts
|
|
13479
14659
|
function parseRovodevMcpJson(fileContent, relativeDirPath, relativeFilePath) {
|
|
13480
14660
|
const configPath = join(relativeDirPath, relativeFilePath);
|
|
@@ -13568,6 +14748,178 @@ var RovodevMcp = class RovodevMcp extends ToolMcp {
|
|
|
13568
14748
|
}
|
|
13569
14749
|
};
|
|
13570
14750
|
//#endregion
|
|
14751
|
+
//#region src/features/shared/takt-config.ts
|
|
14752
|
+
/**
|
|
14753
|
+
* Parse a Takt `config.yaml` into a plain object, treating an empty file as `{}`.
|
|
14754
|
+
*
|
|
14755
|
+
* Shared by the Takt adapters that read-modify-write the same `config.yaml`
|
|
14756
|
+
* (mcp, permissions, ...). Uses `isPlainObject` (not `isRecord`) so class
|
|
14757
|
+
* instances are rejected for prototype-pollution hardening; a YAML mapping
|
|
14758
|
+
* always parses to a plain object.
|
|
14759
|
+
*/
|
|
14760
|
+
function parseTaktConfig(fileContent, relativeDirPath, relativeFilePath) {
|
|
14761
|
+
const configPath = join(relativeDirPath, relativeFilePath);
|
|
14762
|
+
let parsed;
|
|
14763
|
+
try {
|
|
14764
|
+
parsed = fileContent.trim() === "" ? {} : load(fileContent);
|
|
14765
|
+
} catch (error) {
|
|
14766
|
+
throw new Error(`Failed to parse Takt config at ${configPath}: ${formatError(error)}`, { cause: error });
|
|
14767
|
+
}
|
|
14768
|
+
if (parsed === void 0 || parsed === null) return {};
|
|
14769
|
+
if (!isPlainObject(parsed)) throw new Error(`Failed to parse Takt config at ${configPath}: expected a YAML mapping`);
|
|
14770
|
+
return parsed;
|
|
14771
|
+
}
|
|
14772
|
+
//#endregion
|
|
14773
|
+
//#region src/features/mcp/takt-mcp.ts
|
|
14774
|
+
/**
|
|
14775
|
+
* MCP adapter for Takt (`.takt/config.yaml` project / `~/.takt/config.yaml`
|
|
14776
|
+
* global).
|
|
14777
|
+
*
|
|
14778
|
+
* IMPORTANT — what this adapter can and cannot represent.
|
|
14779
|
+
*
|
|
14780
|
+
* Takt does NOT have a project- or global-level registry of MCP *server
|
|
14781
|
+
* definitions*. The concrete `mcp_servers` map (a server's `command`/`args`/`env`
|
|
14782
|
+
* or `type`/`url`/`headers`) is declared per-step inside individual *workflow*
|
|
14783
|
+
* YAML files; there is no top-level `mcp_servers` key in `config.yaml`, and the
|
|
14784
|
+
* config loader hard-rejects unknown top-level keys
|
|
14785
|
+
* (`assertNoUnknownGlobalConfigKeys`). Writing a server map into `config.yaml`
|
|
14786
|
+
* would therefore both be ignored and break the user's config.
|
|
14787
|
+
*
|
|
14788
|
+
* What `config.yaml` *does* hold is the default-deny transport allowlist
|
|
14789
|
+
* `workflow_mcp_servers: { stdio, sse, http }`. Without it, workflow-defined MCP
|
|
14790
|
+
* servers are refused regardless of how they are declared. So this adapter emits
|
|
14791
|
+
* the transport allowlist derived from the transports present in
|
|
14792
|
+
* `.rulesync/mcp.json`, enabling exactly the transports the user's servers need.
|
|
14793
|
+
*
|
|
14794
|
+
* Lossiness (documented, intentional): the per-server names, commands, env, URLs
|
|
14795
|
+
* and headers are NOT representable in `config.yaml` and are intentionally not
|
|
14796
|
+
* written. Users still declare the concrete servers in their workflow YAML
|
|
14797
|
+
* steps; rulesync only opens the transport gate that permits them. As a
|
|
14798
|
+
* corollary, import (`toRulesyncMcp`) cannot reconstruct server definitions from
|
|
14799
|
+
* a transport allowlist and yields an empty `mcpServers` map.
|
|
14800
|
+
*
|
|
14801
|
+
* The shared `config.yaml` is merged in place: only the
|
|
14802
|
+
* `workflow_mcp_servers` key is set; every other top-level key (provider,
|
|
14803
|
+
* provider_profiles, etc.) is preserved. The file is never deleted.
|
|
14804
|
+
*
|
|
14805
|
+
* @see https://github.com/nrslib/takt/blob/main/docs/configuration.md
|
|
14806
|
+
* @see https://github.com/nrslib/takt/blob/main/src/core/models/mcp-schemas.ts
|
|
14807
|
+
*/
|
|
14808
|
+
var TaktMcp = class TaktMcp extends ToolMcp {
|
|
14809
|
+
constructor(params) {
|
|
14810
|
+
super({
|
|
14811
|
+
...params,
|
|
14812
|
+
fileContent: params.fileContent ?? ""
|
|
14813
|
+
});
|
|
14814
|
+
}
|
|
14815
|
+
isDeletable() {
|
|
14816
|
+
return false;
|
|
14817
|
+
}
|
|
14818
|
+
static getSettablePaths(_options) {
|
|
14819
|
+
return {
|
|
14820
|
+
relativeDirPath: TAKT_DIR,
|
|
14821
|
+
relativeFilePath: TAKT_CONFIG_FILE_NAME
|
|
14822
|
+
};
|
|
14823
|
+
}
|
|
14824
|
+
static async fromFile({ outputRoot = process.cwd(), validate = true, global = false }) {
|
|
14825
|
+
const paths = TaktMcp.getSettablePaths({ global });
|
|
14826
|
+
const fileContent = await readFileContentOrNull(join(outputRoot, paths.relativeDirPath, paths.relativeFilePath)) ?? "";
|
|
14827
|
+
return new TaktMcp({
|
|
14828
|
+
outputRoot,
|
|
14829
|
+
relativeDirPath: paths.relativeDirPath,
|
|
14830
|
+
relativeFilePath: paths.relativeFilePath,
|
|
14831
|
+
fileContent,
|
|
14832
|
+
validate,
|
|
14833
|
+
global
|
|
14834
|
+
});
|
|
14835
|
+
}
|
|
14836
|
+
static async fromRulesyncMcp({ outputRoot = process.cwd(), rulesyncMcp, validate = true, global = false }) {
|
|
14837
|
+
const paths = TaktMcp.getSettablePaths({ global });
|
|
14838
|
+
const config = parseTaktConfig(await readFileContentOrNull(join(outputRoot, paths.relativeDirPath, paths.relativeFilePath)) ?? "", paths.relativeDirPath, paths.relativeFilePath);
|
|
14839
|
+
const allowlist = deriveTransportAllowlist(rulesyncMcp.getMcpServers());
|
|
14840
|
+
const merged = {
|
|
14841
|
+
...config,
|
|
14842
|
+
[TAKT_WORKFLOW_MCP_SERVERS_KEY]: allowlist
|
|
14843
|
+
};
|
|
14844
|
+
return new TaktMcp({
|
|
14845
|
+
outputRoot,
|
|
14846
|
+
relativeDirPath: paths.relativeDirPath,
|
|
14847
|
+
relativeFilePath: paths.relativeFilePath,
|
|
14848
|
+
fileContent: dump(merged),
|
|
14849
|
+
validate,
|
|
14850
|
+
global
|
|
14851
|
+
});
|
|
14852
|
+
}
|
|
14853
|
+
/**
|
|
14854
|
+
* A transport allowlist cannot reconstruct the per-step server definitions,
|
|
14855
|
+
* so import yields an empty `mcpServers` map. This keeps the round-trip honest
|
|
14856
|
+
* rather than fabricating placeholder servers.
|
|
14857
|
+
*/
|
|
14858
|
+
toRulesyncMcp() {
|
|
14859
|
+
return this.toRulesyncMcpDefault({ fileContent: JSON.stringify({ mcpServers: {} }, null, 2) });
|
|
14860
|
+
}
|
|
14861
|
+
validate() {
|
|
14862
|
+
return {
|
|
14863
|
+
success: true,
|
|
14864
|
+
error: null
|
|
14865
|
+
};
|
|
14866
|
+
}
|
|
14867
|
+
static forDeletion({ outputRoot = process.cwd(), relativeDirPath, relativeFilePath, global = false }) {
|
|
14868
|
+
return new TaktMcp({
|
|
14869
|
+
outputRoot,
|
|
14870
|
+
relativeDirPath,
|
|
14871
|
+
relativeFilePath,
|
|
14872
|
+
fileContent: "",
|
|
14873
|
+
validate: false,
|
|
14874
|
+
global
|
|
14875
|
+
});
|
|
14876
|
+
}
|
|
14877
|
+
};
|
|
14878
|
+
/**
|
|
14879
|
+
* Map a rulesync MCP server onto the single Takt transport its allowlist gates.
|
|
14880
|
+
*
|
|
14881
|
+
* Takt allows only `stdio` / `sse` / `http`, so the broader rulesync alias set is
|
|
14882
|
+
* folded: `local` ⇒ stdio; `streamable-http` / `ws` ⇒ http. A server with no
|
|
14883
|
+
* explicit transport is treated as stdio when it carries a `command`, else as a
|
|
14884
|
+
* remote `http` server (it must have a `url`). Returns `undefined` only when the
|
|
14885
|
+
* shape is too ambiguous to classify.
|
|
14886
|
+
*/
|
|
14887
|
+
function transportOf(server) {
|
|
14888
|
+
switch (server.type ?? server.transport) {
|
|
14889
|
+
case "stdio":
|
|
14890
|
+
case "local": return "stdio";
|
|
14891
|
+
case "sse": return "sse";
|
|
14892
|
+
case "http":
|
|
14893
|
+
case "streamable-http":
|
|
14894
|
+
case "ws": return "http";
|
|
14895
|
+
default: break;
|
|
14896
|
+
}
|
|
14897
|
+
if (server.command !== void 0) return "stdio";
|
|
14898
|
+
if (server.url !== void 0 || server.httpUrl !== void 0) return "http";
|
|
14899
|
+
}
|
|
14900
|
+
/**
|
|
14901
|
+
* Derive Takt's `workflow_mcp_servers` allowlist from the transports present in
|
|
14902
|
+
* the rulesync servers. All three keys are emitted explicitly (default-deny made
|
|
14903
|
+
* visible): a transport is `true` only when at least one server uses it.
|
|
14904
|
+
*
|
|
14905
|
+
* Server entries are read defensively (record guard); prototype-pollution server
|
|
14906
|
+
* names are irrelevant here because no user-controlled key or value is written —
|
|
14907
|
+
* only the three fixed boolean keys are.
|
|
14908
|
+
*/
|
|
14909
|
+
function deriveTransportAllowlist(servers) {
|
|
14910
|
+
const allowlist = {
|
|
14911
|
+
stdio: false,
|
|
14912
|
+
sse: false,
|
|
14913
|
+
http: false
|
|
14914
|
+
};
|
|
14915
|
+
for (const server of Object.values(servers)) {
|
|
14916
|
+
if (!isRecord(server)) continue;
|
|
14917
|
+
const transport = transportOf(server);
|
|
14918
|
+
if (transport) allowlist[transport] = true;
|
|
14919
|
+
}
|
|
14920
|
+
return allowlist;
|
|
14921
|
+
}
|
|
14922
|
+
//#endregion
|
|
13571
14923
|
//#region src/features/mcp/vibe-mcp.ts
|
|
13572
14924
|
const VIBE_MCP_SERVER_FIELDS = [
|
|
13573
14925
|
"transport",
|
|
@@ -14028,7 +15380,7 @@ const toolMcpFactories = /* @__PURE__ */ new Map([
|
|
|
14028
15380
|
["goose", {
|
|
14029
15381
|
class: GooseMcp,
|
|
14030
15382
|
meta: {
|
|
14031
|
-
supportsProject:
|
|
15383
|
+
supportsProject: true,
|
|
14032
15384
|
supportsGlobal: true,
|
|
14033
15385
|
supportsEnabledTools: false,
|
|
14034
15386
|
supportsDisabledTools: false
|
|
@@ -14142,6 +15494,15 @@ const toolMcpFactories = /* @__PURE__ */ new Map([
|
|
|
14142
15494
|
supportsDisabledTools: false
|
|
14143
15495
|
}
|
|
14144
15496
|
}],
|
|
15497
|
+
["takt", {
|
|
15498
|
+
class: TaktMcp,
|
|
15499
|
+
meta: {
|
|
15500
|
+
supportsProject: true,
|
|
15501
|
+
supportsGlobal: true,
|
|
15502
|
+
supportsEnabledTools: false,
|
|
15503
|
+
supportsDisabledTools: false
|
|
15504
|
+
}
|
|
15505
|
+
}],
|
|
14145
15506
|
["vibe", {
|
|
14146
15507
|
class: VibeMcp,
|
|
14147
15508
|
meta: {
|
|
@@ -14270,7 +15631,8 @@ var McpProcessor = class extends FeatureProcessor {
|
|
|
14270
15631
|
return await factory.class.fromRulesyncMcp({
|
|
14271
15632
|
outputRoot: this.outputRoot,
|
|
14272
15633
|
rulesyncMcp: filteredRulesyncMcp,
|
|
14273
|
-
global: this.global
|
|
15634
|
+
global: this.global,
|
|
15635
|
+
logger: this.logger
|
|
14274
15636
|
});
|
|
14275
15637
|
}));
|
|
14276
15638
|
}
|
|
@@ -14784,7 +16146,7 @@ const ANTIGRAVITY_CLI_TO_CANONICAL_TOOL_NAMES = {
|
|
|
14784
16146
|
function toAntigravityCliToolName(canonical) {
|
|
14785
16147
|
return CANONICAL_TO_ANTIGRAVITY_CLI_TOOL_NAMES[canonical] ?? canonical;
|
|
14786
16148
|
}
|
|
14787
|
-
function toCanonicalToolName$
|
|
16149
|
+
function toCanonicalToolName$6(cliName) {
|
|
14788
16150
|
return ANTIGRAVITY_CLI_TO_CANONICAL_TOOL_NAMES[cliName] ?? cliName;
|
|
14789
16151
|
}
|
|
14790
16152
|
/**
|
|
@@ -14967,7 +16329,7 @@ function convertAntigravityCliToRulesyncPermissions(params) {
|
|
|
14967
16329
|
const processEntries = (entries, action) => {
|
|
14968
16330
|
for (const entry of entries) {
|
|
14969
16331
|
const { toolName, pattern } = parsePermissionEntry$1(entry);
|
|
14970
|
-
const canonical = toCanonicalToolName$
|
|
16332
|
+
const canonical = toCanonicalToolName$6(toolName);
|
|
14971
16333
|
if (!permission[canonical]) permission[canonical] = {};
|
|
14972
16334
|
permission[canonical][pattern] = action;
|
|
14973
16335
|
}
|
|
@@ -15006,7 +16368,7 @@ const IDE_ACTION_TO_CANONICAL = {
|
|
|
15006
16368
|
function toIdeAction(canonical) {
|
|
15007
16369
|
return CANONICAL_TO_IDE_ACTION[canonical] ?? canonical;
|
|
15008
16370
|
}
|
|
15009
|
-
function toCanonicalCategory$
|
|
16371
|
+
function toCanonicalCategory$2(ideAction) {
|
|
15010
16372
|
return IDE_ACTION_TO_CANONICAL[ideAction] ?? ideAction;
|
|
15011
16373
|
}
|
|
15012
16374
|
/**
|
|
@@ -15180,7 +16542,7 @@ function convertAntigravityIdeToRulesyncPermissions(params) {
|
|
|
15180
16542
|
const processEntries = (entries, action) => {
|
|
15181
16543
|
for (const entry of entries) {
|
|
15182
16544
|
const { action: ideAction, pattern } = parsePermissionEntry(entry);
|
|
15183
|
-
const canonical = toCanonicalCategory$
|
|
16545
|
+
const canonical = toCanonicalCategory$2(ideAction);
|
|
15184
16546
|
if (!permission[canonical]) permission[canonical] = {};
|
|
15185
16547
|
permission[canonical][pattern] = action;
|
|
15186
16548
|
}
|
|
@@ -15229,7 +16591,7 @@ const AUGMENT_TO_CANONICAL_TOOL_NAMES = Object.fromEntries(Object.entries(CANONI
|
|
|
15229
16591
|
function toAugmentToolName(canonical) {
|
|
15230
16592
|
return CANONICAL_TO_AUGMENT_TOOL_NAMES[canonical] ?? canonical;
|
|
15231
16593
|
}
|
|
15232
|
-
function toCanonicalToolName$
|
|
16594
|
+
function toCanonicalToolName$5(augmentName) {
|
|
15233
16595
|
return AUGMENT_TO_CANONICAL_TOOL_NAMES[augmentName] ?? augmentName;
|
|
15234
16596
|
}
|
|
15235
16597
|
function actionToAugmentType(action) {
|
|
@@ -15573,7 +16935,7 @@ function convertAugmentToRulesyncPermissions({ entries, logger }) {
|
|
|
15573
16935
|
}
|
|
15574
16936
|
const type = entry.permission.type;
|
|
15575
16937
|
if (!isBasicAugmentType(type)) continue;
|
|
15576
|
-
const canonical = toCanonicalToolName$
|
|
16938
|
+
const canonical = toCanonicalToolName$5(entry.toolName);
|
|
15577
16939
|
if (forbiddenMapKeys.has(canonical)) {
|
|
15578
16940
|
logger?.warn(`AugmentCode permissions: skipping entry for tool '${entry.toolName}' because it maps to the reserved object key '${canonical}', which cannot be used as a permission key.`);
|
|
15579
16941
|
continue;
|
|
@@ -15626,7 +16988,7 @@ const CLAUDE_TO_CANONICAL_TOOL_NAMES = Object.fromEntries(Object.entries(CANONIC
|
|
|
15626
16988
|
function toClaudeToolName(canonical) {
|
|
15627
16989
|
return CANONICAL_TO_CLAUDE_TOOL_NAMES[canonical] ?? canonical;
|
|
15628
16990
|
}
|
|
15629
|
-
function toCanonicalToolName$
|
|
16991
|
+
function toCanonicalToolName$4(claudeName) {
|
|
15630
16992
|
return CLAUDE_TO_CANONICAL_TOOL_NAMES[claudeName] ?? claudeName;
|
|
15631
16993
|
}
|
|
15632
16994
|
/**
|
|
@@ -15800,7 +17162,7 @@ function convertClaudeToRulesyncPermissions(params) {
|
|
|
15800
17162
|
const processEntries = (entries, action) => {
|
|
15801
17163
|
for (const entry of entries) {
|
|
15802
17164
|
const { toolName, pattern } = parseClaudePermissionEntry(entry);
|
|
15803
|
-
const canonical = toCanonicalToolName$
|
|
17165
|
+
const canonical = toCanonicalToolName$4(toolName);
|
|
15804
17166
|
if (!permission[canonical]) permission[canonical] = {};
|
|
15805
17167
|
permission[canonical][pattern] = action;
|
|
15806
17168
|
}
|
|
@@ -16427,7 +17789,7 @@ function toCursorPattern(canonical, pattern) {
|
|
|
16427
17789
|
}
|
|
16428
17790
|
return pattern;
|
|
16429
17791
|
}
|
|
16430
|
-
function toCanonicalCategory(cursorType, pattern) {
|
|
17792
|
+
function toCanonicalCategory$1(cursorType, pattern) {
|
|
16431
17793
|
if (cursorType === "Mcp") {
|
|
16432
17794
|
const match = pattern.match(/^([^:()]+):([^:()]+)$/);
|
|
16433
17795
|
if (match) return `${MCP_CANONICAL_PREFIX}${match[1] ?? "*"}__${match[2] ?? "*"}`;
|
|
@@ -16652,7 +18014,7 @@ function convertCursorToRulesyncPermissions(params) {
|
|
|
16652
18014
|
const processEntries = (entries, action) => {
|
|
16653
18015
|
for (const entry of entries) {
|
|
16654
18016
|
const { type, pattern } = parseCursorPermissionEntry(entry);
|
|
16655
|
-
const canonical = toCanonicalCategory(type, pattern);
|
|
18017
|
+
const canonical = toCanonicalCategory$1(type, pattern);
|
|
16656
18018
|
if (!permission[canonical]) permission[canonical] = {};
|
|
16657
18019
|
const canonicalPattern = type === "Mcp" && canonical.startsWith(MCP_CANONICAL_PREFIX) ? "*" : pattern;
|
|
16658
18020
|
permission[canonical][canonicalPattern] = action;
|
|
@@ -16663,6 +18025,245 @@ function convertCursorToRulesyncPermissions(params) {
|
|
|
16663
18025
|
return { permission };
|
|
16664
18026
|
}
|
|
16665
18027
|
//#endregion
|
|
18028
|
+
//#region src/features/permissions/devin-permissions.ts
|
|
18029
|
+
/**
|
|
18030
|
+
* Mapping from rulesync canonical tool category names to Devin Local permission
|
|
18031
|
+
* scope matchers.
|
|
18032
|
+
*
|
|
18033
|
+
* Devin expresses permissions with scope-based matchers — `Read(glob)`,
|
|
18034
|
+
* `Write(glob)`, `Exec(prefix)`, and `Fetch(pattern)` — plus MCP tool patterns
|
|
18035
|
+
* (`mcp__server__tool`). The canonical `edit` and `write` categories both map
|
|
18036
|
+
* onto Devin's single `Write` scope; on import `Write` maps back to `write`, so
|
|
18037
|
+
* `edit` rules round-trip as `write` (a lossy but documented collapse). Unknown
|
|
18038
|
+
* names (e.g. `mcp__github__list_issues`) pass through verbatim.
|
|
18039
|
+
*
|
|
18040
|
+
* @see https://docs.devin.ai/cli/reference/permissions
|
|
18041
|
+
*/
|
|
18042
|
+
const CANONICAL_TO_DEVIN_SCOPE = {
|
|
18043
|
+
read: "Read",
|
|
18044
|
+
write: "Write",
|
|
18045
|
+
edit: "Write",
|
|
18046
|
+
bash: "Exec",
|
|
18047
|
+
webfetch: "Fetch"
|
|
18048
|
+
};
|
|
18049
|
+
/**
|
|
18050
|
+
* Reverse mapping from Devin scope matchers to rulesync canonical names.
|
|
18051
|
+
*/
|
|
18052
|
+
const DEVIN_SCOPE_TO_CANONICAL = {
|
|
18053
|
+
Read: "read",
|
|
18054
|
+
Write: "write",
|
|
18055
|
+
Exec: "bash",
|
|
18056
|
+
Fetch: "webfetch"
|
|
18057
|
+
};
|
|
18058
|
+
function toDevinScope(canonical) {
|
|
18059
|
+
return CANONICAL_TO_DEVIN_SCOPE[canonical] ?? canonical;
|
|
18060
|
+
}
|
|
18061
|
+
function toCanonicalCategory(devinScope) {
|
|
18062
|
+
return DEVIN_SCOPE_TO_CANONICAL[devinScope] ?? devinScope;
|
|
18063
|
+
}
|
|
18064
|
+
/**
|
|
18065
|
+
* Parse a Devin permission entry like `Read(src/**)` into scope and pattern.
|
|
18066
|
+
* Bare entries (e.g. `Read`, or a whole-tool name like `exec`) yield `*`.
|
|
18067
|
+
*/
|
|
18068
|
+
function parseDevinPermissionEntry(entry) {
|
|
18069
|
+
const parenIndex = entry.indexOf("(");
|
|
18070
|
+
if (parenIndex === -1) return {
|
|
18071
|
+
scope: entry,
|
|
18072
|
+
pattern: "*"
|
|
18073
|
+
};
|
|
18074
|
+
const scope = entry.slice(0, parenIndex);
|
|
18075
|
+
if (!entry.endsWith(")")) return {
|
|
18076
|
+
scope,
|
|
18077
|
+
pattern: "*"
|
|
18078
|
+
};
|
|
18079
|
+
return {
|
|
18080
|
+
scope,
|
|
18081
|
+
pattern: entry.slice(parenIndex + 1, -1) || "*"
|
|
18082
|
+
};
|
|
18083
|
+
}
|
|
18084
|
+
/**
|
|
18085
|
+
* Build a Devin permission entry like `Read(src/**)`. A `*` pattern collapses to
|
|
18086
|
+
* the bare scope (`Read`), matching the whole scope.
|
|
18087
|
+
*/
|
|
18088
|
+
function buildDevinPermissionEntry(scope, pattern) {
|
|
18089
|
+
if (pattern === "*") return scope;
|
|
18090
|
+
return `${scope}(${pattern})`;
|
|
18091
|
+
}
|
|
18092
|
+
/**
|
|
18093
|
+
* Permissions generator for Devin Local (native `.devin/` configuration).
|
|
18094
|
+
*
|
|
18095
|
+
* Maps rulesync permission actions onto Devin's `permissions` block inside its
|
|
18096
|
+
* native config file — `allow` / `deny` / `ask` arrays of scope matchers
|
|
18097
|
+
* (`Read(glob)`, `Write(glob)`, `Exec(prefix)`, `Fetch(pattern)`, plus
|
|
18098
|
+
* `mcp__server__tool` patterns). Devin evaluates the arrays with strict
|
|
18099
|
+
* precedence: `deny` is checked before `ask`, which is checked before `allow`,
|
|
18100
|
+
* so a deny rule always wins.
|
|
18101
|
+
*
|
|
18102
|
+
* - Project scope: `.devin/config.json`
|
|
18103
|
+
* - Global scope: `~/.config/devin/config.json`
|
|
18104
|
+
*
|
|
18105
|
+
* The config file is shared with the MCP (`mcpServers`) and, in global mode, the
|
|
18106
|
+
* hooks (`hooks`) features, so reads and writes merge into the existing JSON and
|
|
18107
|
+
* the file is never deleted; only the managed `permissions` key is rewritten.
|
|
18108
|
+
*
|
|
18109
|
+
* @see https://docs.devin.ai/cli/reference/permissions
|
|
18110
|
+
*/
|
|
18111
|
+
var DevinPermissions = class DevinPermissions extends ToolPermissions {
|
|
18112
|
+
constructor(params) {
|
|
18113
|
+
super({
|
|
18114
|
+
...params,
|
|
18115
|
+
fileContent: params.fileContent ?? "{}"
|
|
18116
|
+
});
|
|
18117
|
+
}
|
|
18118
|
+
/**
|
|
18119
|
+
* config.json may carry the MCP/hooks features' keys, so it is never deleted;
|
|
18120
|
+
* only the managed `permissions` key is rewritten.
|
|
18121
|
+
*/
|
|
18122
|
+
isDeletable() {
|
|
18123
|
+
return false;
|
|
18124
|
+
}
|
|
18125
|
+
static getSettablePaths({ global = false } = {}) {
|
|
18126
|
+
if (global) return {
|
|
18127
|
+
relativeDirPath: DEVIN_GLOBAL_CONFIG_DIR_PATH,
|
|
18128
|
+
relativeFilePath: DEVIN_CONFIG_FILE_NAME
|
|
18129
|
+
};
|
|
18130
|
+
return {
|
|
18131
|
+
relativeDirPath: DEVIN_DIR,
|
|
18132
|
+
relativeFilePath: DEVIN_CONFIG_FILE_NAME
|
|
18133
|
+
};
|
|
18134
|
+
}
|
|
18135
|
+
static async fromFile({ outputRoot = process.cwd(), validate = true, global = false }) {
|
|
18136
|
+
const paths = DevinPermissions.getSettablePaths({ global });
|
|
18137
|
+
const fileContent = await readFileContentOrNull(join(outputRoot, paths.relativeDirPath, paths.relativeFilePath)) ?? "{\"permissions\":{}}";
|
|
18138
|
+
return new DevinPermissions({
|
|
18139
|
+
outputRoot,
|
|
18140
|
+
relativeDirPath: paths.relativeDirPath,
|
|
18141
|
+
relativeFilePath: paths.relativeFilePath,
|
|
18142
|
+
fileContent,
|
|
18143
|
+
validate
|
|
18144
|
+
});
|
|
18145
|
+
}
|
|
18146
|
+
static async fromRulesyncPermissions({ outputRoot = process.cwd(), rulesyncPermissions, global = false, validate = true }) {
|
|
18147
|
+
const paths = DevinPermissions.getSettablePaths({ global });
|
|
18148
|
+
const filePath = join(outputRoot, paths.relativeDirPath, paths.relativeFilePath);
|
|
18149
|
+
const existingContent = await readOrInitializeFileContent(filePath, JSON.stringify({}, null, 2));
|
|
18150
|
+
let settings;
|
|
18151
|
+
try {
|
|
18152
|
+
const parsed = JSON.parse(existingContent);
|
|
18153
|
+
settings = isRecord(parsed) ? parsed : {};
|
|
18154
|
+
} catch (error) {
|
|
18155
|
+
throw new Error(`Failed to parse existing Devin config at ${filePath}: ${formatError(error)}`, { cause: error });
|
|
18156
|
+
}
|
|
18157
|
+
const config = rulesyncPermissions.getJson();
|
|
18158
|
+
const { allow, ask, deny } = convertRulesyncToDevinPermissions(config);
|
|
18159
|
+
const managedScopes = new Set(Object.keys(config.permission).map((category) => toDevinScope(category)));
|
|
18160
|
+
const existingPermissions = isRecord(settings.permissions) ? settings.permissions : {};
|
|
18161
|
+
const preserve = (entries) => (entries ?? []).filter((entry) => !managedScopes.has(parseDevinPermissionEntry(entry).scope));
|
|
18162
|
+
const mergedAllow = uniq([...preserve(existingPermissions.allow), ...allow].toSorted());
|
|
18163
|
+
const mergedAsk = uniq([...preserve(existingPermissions.ask), ...ask].toSorted());
|
|
18164
|
+
const mergedDeny = uniq([...preserve(existingPermissions.deny), ...deny].toSorted());
|
|
18165
|
+
const mergedPermissions = { ...existingPermissions };
|
|
18166
|
+
if (mergedAllow.length > 0) mergedPermissions.allow = mergedAllow;
|
|
18167
|
+
else delete mergedPermissions.allow;
|
|
18168
|
+
if (mergedAsk.length > 0) mergedPermissions.ask = mergedAsk;
|
|
18169
|
+
else delete mergedPermissions.ask;
|
|
18170
|
+
if (mergedDeny.length > 0) mergedPermissions.deny = mergedDeny;
|
|
18171
|
+
else delete mergedPermissions.deny;
|
|
18172
|
+
const merged = {
|
|
18173
|
+
...settings,
|
|
18174
|
+
permissions: mergedPermissions
|
|
18175
|
+
};
|
|
18176
|
+
return new DevinPermissions({
|
|
18177
|
+
outputRoot,
|
|
18178
|
+
relativeDirPath: paths.relativeDirPath,
|
|
18179
|
+
relativeFilePath: paths.relativeFilePath,
|
|
18180
|
+
fileContent: JSON.stringify(merged, null, 2),
|
|
18181
|
+
validate
|
|
18182
|
+
});
|
|
18183
|
+
}
|
|
18184
|
+
toRulesyncPermissions() {
|
|
18185
|
+
let settings;
|
|
18186
|
+
try {
|
|
18187
|
+
const parsed = JSON.parse(this.getFileContent());
|
|
18188
|
+
settings = isRecord(parsed) ? parsed : {};
|
|
18189
|
+
} catch (error) {
|
|
18190
|
+
throw new Error(`Failed to parse Devin permissions content in ${join(this.getRelativeDirPath(), this.getRelativeFilePath())}: ${formatError(error)}`, { cause: error });
|
|
18191
|
+
}
|
|
18192
|
+
const permissions = isRecord(settings.permissions) ? settings.permissions : {};
|
|
18193
|
+
const config = convertDevinToRulesyncPermissions({
|
|
18194
|
+
allow: Array.isArray(permissions.allow) ? permissions.allow : [],
|
|
18195
|
+
ask: Array.isArray(permissions.ask) ? permissions.ask : [],
|
|
18196
|
+
deny: Array.isArray(permissions.deny) ? permissions.deny : []
|
|
18197
|
+
});
|
|
18198
|
+
return this.toRulesyncPermissionsDefault({ fileContent: JSON.stringify(config, null, 2) });
|
|
18199
|
+
}
|
|
18200
|
+
validate() {
|
|
18201
|
+
return {
|
|
18202
|
+
success: true,
|
|
18203
|
+
error: null
|
|
18204
|
+
};
|
|
18205
|
+
}
|
|
18206
|
+
static forDeletion({ outputRoot = process.cwd(), relativeDirPath, relativeFilePath }) {
|
|
18207
|
+
return new DevinPermissions({
|
|
18208
|
+
outputRoot,
|
|
18209
|
+
relativeDirPath,
|
|
18210
|
+
relativeFilePath,
|
|
18211
|
+
fileContent: JSON.stringify({ permissions: {} }, null, 2),
|
|
18212
|
+
validate: false
|
|
18213
|
+
});
|
|
18214
|
+
}
|
|
18215
|
+
};
|
|
18216
|
+
/**
|
|
18217
|
+
* Convert rulesync permissions config to Devin allow/ask/deny arrays.
|
|
18218
|
+
*/
|
|
18219
|
+
function convertRulesyncToDevinPermissions(config) {
|
|
18220
|
+
const allow = [];
|
|
18221
|
+
const ask = [];
|
|
18222
|
+
const deny = [];
|
|
18223
|
+
for (const [category, rules] of Object.entries(config.permission)) {
|
|
18224
|
+
const scope = toDevinScope(category);
|
|
18225
|
+
for (const [pattern, action] of Object.entries(rules)) {
|
|
18226
|
+
const entry = buildDevinPermissionEntry(scope, pattern);
|
|
18227
|
+
switch (action) {
|
|
18228
|
+
case "allow":
|
|
18229
|
+
allow.push(entry);
|
|
18230
|
+
break;
|
|
18231
|
+
case "ask":
|
|
18232
|
+
ask.push(entry);
|
|
18233
|
+
break;
|
|
18234
|
+
case "deny":
|
|
18235
|
+
deny.push(entry);
|
|
18236
|
+
break;
|
|
18237
|
+
}
|
|
18238
|
+
}
|
|
18239
|
+
}
|
|
18240
|
+
return {
|
|
18241
|
+
allow,
|
|
18242
|
+
ask,
|
|
18243
|
+
deny
|
|
18244
|
+
};
|
|
18245
|
+
}
|
|
18246
|
+
/**
|
|
18247
|
+
* Convert Devin allow/ask/deny arrays to rulesync permissions config. Entries
|
|
18248
|
+
* are applied allow → ask → deny so the most restrictive action wins for a
|
|
18249
|
+
* given (scope, pattern), mirroring Devin's deny > ask > allow precedence.
|
|
18250
|
+
*/
|
|
18251
|
+
function convertDevinToRulesyncPermissions(params) {
|
|
18252
|
+
const permission = {};
|
|
18253
|
+
const processEntries = (entries, action) => {
|
|
18254
|
+
for (const entry of entries) {
|
|
18255
|
+
const { scope, pattern } = parseDevinPermissionEntry(entry);
|
|
18256
|
+
if (isPrototypePollutionKey(scope) || isPrototypePollutionKey(pattern)) continue;
|
|
18257
|
+
const canonical = toCanonicalCategory(scope);
|
|
18258
|
+
(permission[canonical] ??= {})[pattern] = action;
|
|
18259
|
+
}
|
|
18260
|
+
};
|
|
18261
|
+
processEntries(params.allow, "allow");
|
|
18262
|
+
processEntries(params.ask, "ask");
|
|
18263
|
+
processEntries(params.deny, "deny");
|
|
18264
|
+
return { permission };
|
|
18265
|
+
}
|
|
18266
|
+
//#endregion
|
|
16666
18267
|
//#region src/features/permissions/factorydroid-permissions.ts
|
|
16667
18268
|
/**
|
|
16668
18269
|
* Permissions adapter for Factory Droid.
|
|
@@ -16922,7 +18523,7 @@ var GoosePermissions = class GoosePermissions extends ToolPermissions {
|
|
|
16922
18523
|
} catch (error) {
|
|
16923
18524
|
throw new Error(`Failed to parse existing Goose permission.yaml at ${filePath}: ${formatError(error)}`, { cause: error });
|
|
16924
18525
|
}
|
|
16925
|
-
const config = isRecord
|
|
18526
|
+
const config = isRecord(parsed) ? { ...parsed } : {};
|
|
16926
18527
|
config[GOOSE_USER_KEY] = convertRulesyncToGoosePermissionConfig({
|
|
16927
18528
|
config: rulesyncPermissions.getJson(),
|
|
16928
18529
|
logger
|
|
@@ -16944,8 +18545,8 @@ var GoosePermissions = class GoosePermissions extends ToolPermissions {
|
|
|
16944
18545
|
} catch (error) {
|
|
16945
18546
|
throw new Error(`Failed to parse Goose permissions content in ${join(this.getRelativeDirPath(), this.getRelativeFilePath())}: ${formatError(error)}`, { cause: error });
|
|
16946
18547
|
}
|
|
16947
|
-
const config = isRecord
|
|
16948
|
-
const rulesyncConfig = convertGoosePermissionConfigToRulesync(isRecord
|
|
18548
|
+
const config = isRecord(parsed) ? parsed : {};
|
|
18549
|
+
const rulesyncConfig = convertGoosePermissionConfigToRulesync(isRecord(config[GOOSE_USER_KEY]) ? config[GOOSE_USER_KEY] : {});
|
|
16949
18550
|
return this.toRulesyncPermissionsDefault({ fileContent: JSON.stringify(rulesyncConfig, null, 2) });
|
|
16950
18551
|
}
|
|
16951
18552
|
validate() {
|
|
@@ -17085,7 +18686,7 @@ var GrokcliPermissions = class GrokcliPermissions extends ToolPermissions {
|
|
|
17085
18686
|
throw new Error(`Failed to parse existing Grok config.toml at ${filePath}: ${formatError(error)}`, { cause: error });
|
|
17086
18687
|
}
|
|
17087
18688
|
const mode = deriveGrokPermissionMode(rulesyncPermissions.getJson());
|
|
17088
|
-
const existingUi = isRecord
|
|
18689
|
+
const existingUi = isRecord(parsed[GROKCLI_UI_KEY]) ? parsed[GROKCLI_UI_KEY] : {};
|
|
17089
18690
|
parsed[GROKCLI_UI_KEY] = {
|
|
17090
18691
|
...existingUi,
|
|
17091
18692
|
[GROKCLI_PERMISSION_MODE_KEY]: mode
|
|
@@ -17107,7 +18708,7 @@ var GrokcliPermissions = class GrokcliPermissions extends ToolPermissions {
|
|
|
17107
18708
|
} catch (error) {
|
|
17108
18709
|
throw new Error(`Failed to parse Grok config.toml content in ${join(this.getRelativeDirPath(), this.getRelativeFilePath())}: ${formatError(error)}`, { cause: error });
|
|
17109
18710
|
}
|
|
17110
|
-
const action = (isRecord
|
|
18711
|
+
const action = (isRecord(parsed[GROKCLI_UI_KEY]) ? parsed[GROKCLI_UI_KEY] : {})[GROKCLI_PERMISSION_MODE_KEY] === "always-approve" ? "allow" : "ask";
|
|
17111
18712
|
const rulesyncConfig = { permission: { bash: { [CATCH_ALL_PATTERN$2]: action } } };
|
|
17112
18713
|
return this.toRulesyncPermissionsDefault({ fileContent: JSON.stringify(rulesyncConfig, null, 2) });
|
|
17113
18714
|
}
|
|
@@ -17189,6 +18790,205 @@ var HermesagentPermissions = class HermesagentPermissions extends ToolPermission
|
|
|
17189
18790
|
}
|
|
17190
18791
|
};
|
|
17191
18792
|
//#endregion
|
|
18793
|
+
//#region src/features/permissions/junie-permissions.ts
|
|
18794
|
+
/**
|
|
18795
|
+
* JetBrains Junie CLI Action Allowlist (`allowlist.json`).
|
|
18796
|
+
*
|
|
18797
|
+
* Junie gates actions through an allowlist evaluated top-to-bottom (first match
|
|
18798
|
+
* wins). Project scope lives in `.junie/allowlist.json`; user scope lives in
|
|
18799
|
+
* `~/.junie/allowlist.json`.
|
|
18800
|
+
*
|
|
18801
|
+
* ```json
|
|
18802
|
+
* {
|
|
18803
|
+
* "defaultBehavior": "ask",
|
|
18804
|
+
* "allowReadonlyCommands": true,
|
|
18805
|
+
* "rules": {
|
|
18806
|
+
* "executables": [ { "prefix": "git ", "action": "allow" } ],
|
|
18807
|
+
* "fileEditing": [ { "pattern": "src/**", "action": "allow" } ],
|
|
18808
|
+
* "mcpTools": [ { "prefix": "search", "action": "allow" } ],
|
|
18809
|
+
* "readOutsideProject":[ { "pattern": "/etc/**", "action": "deny" } ]
|
|
18810
|
+
* }
|
|
18811
|
+
* }
|
|
18812
|
+
* ```
|
|
18813
|
+
*
|
|
18814
|
+
* Each rule carries a literal `prefix` (matches commands that start with it) or
|
|
18815
|
+
* a glob `pattern` (`*`, `**`, `?`, `[abc]`, `[!abc]`) plus an `action`
|
|
18816
|
+
* (`allow` | `ask` | `deny`). rulesync's canonical actions map 1:1 onto Junie's.
|
|
18817
|
+
*
|
|
18818
|
+
* Category mapping (rulesync canonical <-> Junie rule group):
|
|
18819
|
+
* - `bash` <-> `executables`
|
|
18820
|
+
* - `edit`/`write` -> `fileEditing` (imported back as `edit`)
|
|
18821
|
+
* - `read` <-> `readOutsideProject`
|
|
18822
|
+
* - `mcp` <-> `mcpTools`
|
|
18823
|
+
*
|
|
18824
|
+
* Categories Junie cannot represent (e.g. `webfetch`) are skipped on export
|
|
18825
|
+
* (with a warning when they carry rules). The top-level `defaultBehavior` and
|
|
18826
|
+
* `allowReadonlyCommands` settings have no canonical equivalent: they are
|
|
18827
|
+
* preserved verbatim on export but not imported into the rulesync model.
|
|
18828
|
+
*
|
|
18829
|
+
* @see https://junie.jetbrains.com/docs/action-allowlist-junie-cli.html
|
|
18830
|
+
*/
|
|
18831
|
+
const JUNIE_RULE_GROUPS = [
|
|
18832
|
+
"executables",
|
|
18833
|
+
"fileEditing",
|
|
18834
|
+
"mcpTools",
|
|
18835
|
+
"readOutsideProject"
|
|
18836
|
+
];
|
|
18837
|
+
const CANONICAL_TO_JUNIE_GROUP = {
|
|
18838
|
+
bash: "executables",
|
|
18839
|
+
edit: "fileEditing",
|
|
18840
|
+
write: "fileEditing",
|
|
18841
|
+
read: "readOutsideProject",
|
|
18842
|
+
mcp: "mcpTools"
|
|
18843
|
+
};
|
|
18844
|
+
const JUNIE_GROUP_TO_CANONICAL = {
|
|
18845
|
+
executables: "bash",
|
|
18846
|
+
fileEditing: "edit",
|
|
18847
|
+
mcpTools: "mcp",
|
|
18848
|
+
readOutsideProject: "read"
|
|
18849
|
+
};
|
|
18850
|
+
const JUNIE_DEFAULT_BEHAVIOR = "ask";
|
|
18851
|
+
function isPermissionAction$1(value) {
|
|
18852
|
+
return PermissionActionSchema.safeParse(value).success;
|
|
18853
|
+
}
|
|
18854
|
+
/**
|
|
18855
|
+
* Whether a rulesync pattern uses glob syntax. Junie expresses literal
|
|
18856
|
+
* "starts-with" matches as `prefix` and glob matches as `pattern`, so a pattern
|
|
18857
|
+
* containing any glob metacharacter (`*`, `?`, `[`) is emitted as `pattern`.
|
|
18858
|
+
*/
|
|
18859
|
+
function isGlobPattern(pattern) {
|
|
18860
|
+
return /[*?[]/.test(pattern);
|
|
18861
|
+
}
|
|
18862
|
+
var JuniePermissions = class JuniePermissions extends ToolPermissions {
|
|
18863
|
+
constructor(params) {
|
|
18864
|
+
super({
|
|
18865
|
+
...params,
|
|
18866
|
+
fileContent: params.fileContent ?? "{}"
|
|
18867
|
+
});
|
|
18868
|
+
}
|
|
18869
|
+
isDeletable() {
|
|
18870
|
+
return false;
|
|
18871
|
+
}
|
|
18872
|
+
static getSettablePaths(_options = {}) {
|
|
18873
|
+
return {
|
|
18874
|
+
relativeDirPath: JUNIE_DIR,
|
|
18875
|
+
relativeFilePath: JUNIE_PERMISSIONS_FILE_NAME
|
|
18876
|
+
};
|
|
18877
|
+
}
|
|
18878
|
+
static async fromFile({ outputRoot = process.cwd(), validate = true, global = false }) {
|
|
18879
|
+
const paths = JuniePermissions.getSettablePaths({ global });
|
|
18880
|
+
const fileContent = await readFileContentOrNull(join(outputRoot, paths.relativeDirPath, paths.relativeFilePath)) ?? "{}";
|
|
18881
|
+
return new JuniePermissions({
|
|
18882
|
+
outputRoot,
|
|
18883
|
+
relativeDirPath: paths.relativeDirPath,
|
|
18884
|
+
relativeFilePath: paths.relativeFilePath,
|
|
18885
|
+
fileContent,
|
|
18886
|
+
validate
|
|
18887
|
+
});
|
|
18888
|
+
}
|
|
18889
|
+
static async fromRulesyncPermissions({ outputRoot = process.cwd(), rulesyncPermissions, logger, global = false }) {
|
|
18890
|
+
const paths = JuniePermissions.getSettablePaths({ global });
|
|
18891
|
+
const filePath = join(outputRoot, paths.relativeDirPath, paths.relativeFilePath);
|
|
18892
|
+
const existingContent = await readOrInitializeFileContent(filePath, "{}");
|
|
18893
|
+
let existing;
|
|
18894
|
+
try {
|
|
18895
|
+
const parsed = JSON.parse(existingContent);
|
|
18896
|
+
existing = parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : {};
|
|
18897
|
+
} catch (error) {
|
|
18898
|
+
throw new Error(`Failed to parse existing Junie allowlist at ${filePath}: ${formatError(error)}`, { cause: error });
|
|
18899
|
+
}
|
|
18900
|
+
const rules = convertRulesyncToJunieRules({
|
|
18901
|
+
config: rulesyncPermissions.getJson(),
|
|
18902
|
+
logger
|
|
18903
|
+
});
|
|
18904
|
+
const merged = {
|
|
18905
|
+
...existing,
|
|
18906
|
+
defaultBehavior: existing.defaultBehavior ?? JUNIE_DEFAULT_BEHAVIOR,
|
|
18907
|
+
rules
|
|
18908
|
+
};
|
|
18909
|
+
return new JuniePermissions({
|
|
18910
|
+
outputRoot,
|
|
18911
|
+
relativeDirPath: paths.relativeDirPath,
|
|
18912
|
+
relativeFilePath: paths.relativeFilePath,
|
|
18913
|
+
fileContent: JSON.stringify(merged, null, 2),
|
|
18914
|
+
validate: true
|
|
18915
|
+
});
|
|
18916
|
+
}
|
|
18917
|
+
toRulesyncPermissions() {
|
|
18918
|
+
let allowlist;
|
|
18919
|
+
try {
|
|
18920
|
+
const parsed = JSON.parse(this.getFileContent());
|
|
18921
|
+
allowlist = parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : {};
|
|
18922
|
+
} catch (error) {
|
|
18923
|
+
throw new Error(`Failed to parse Junie permissions content in ${join(this.getRelativeDirPath(), this.getRelativeFilePath())}: ${formatError(error)}`, { cause: error });
|
|
18924
|
+
}
|
|
18925
|
+
const config = convertJunieToRulesyncPermissions({ allowlist });
|
|
18926
|
+
return this.toRulesyncPermissionsDefault({ fileContent: JSON.stringify(config, null, 2) });
|
|
18927
|
+
}
|
|
18928
|
+
validate() {
|
|
18929
|
+
return {
|
|
18930
|
+
success: true,
|
|
18931
|
+
error: null
|
|
18932
|
+
};
|
|
18933
|
+
}
|
|
18934
|
+
static forDeletion({ outputRoot = process.cwd(), relativeDirPath, relativeFilePath }) {
|
|
18935
|
+
return new JuniePermissions({
|
|
18936
|
+
outputRoot,
|
|
18937
|
+
relativeDirPath,
|
|
18938
|
+
relativeFilePath,
|
|
18939
|
+
fileContent: "{}",
|
|
18940
|
+
validate: false
|
|
18941
|
+
});
|
|
18942
|
+
}
|
|
18943
|
+
};
|
|
18944
|
+
/**
|
|
18945
|
+
* Convert rulesync permissions config into Junie's `rules` object. Categories
|
|
18946
|
+
* with no Junie rule group (e.g. `webfetch`) are skipped, with a warning when
|
|
18947
|
+
* they carry any rule so the gap is surfaced.
|
|
18948
|
+
*/
|
|
18949
|
+
function convertRulesyncToJunieRules({ config, logger }) {
|
|
18950
|
+
const rules = {};
|
|
18951
|
+
for (const [category, patterns] of Object.entries(config.permission)) {
|
|
18952
|
+
const group = CANONICAL_TO_JUNIE_GROUP[category];
|
|
18953
|
+
if (!group) {
|
|
18954
|
+
if (Object.keys(patterns).length > 0) logger?.warn(`Junie allowlist only models executables/fileEditing/mcpTools/readOutsideProject (canonical bash/edit/write/read/mcp); '${category}' rules cannot be represented and were skipped.`);
|
|
18955
|
+
continue;
|
|
18956
|
+
}
|
|
18957
|
+
for (const [pattern, action] of Object.entries(patterns)) {
|
|
18958
|
+
const rule = isGlobPattern(pattern) ? {
|
|
18959
|
+
pattern,
|
|
18960
|
+
action
|
|
18961
|
+
} : {
|
|
18962
|
+
prefix: pattern,
|
|
18963
|
+
action
|
|
18964
|
+
};
|
|
18965
|
+
(rules[group] ??= []).push(rule);
|
|
18966
|
+
}
|
|
18967
|
+
}
|
|
18968
|
+
return rules;
|
|
18969
|
+
}
|
|
18970
|
+
/**
|
|
18971
|
+
* Convert a Junie allowlist back into rulesync permissions config. The
|
|
18972
|
+
* top-level `defaultBehavior` / `allowReadonlyCommands` settings have no
|
|
18973
|
+
* canonical equivalent and are not imported.
|
|
18974
|
+
*/
|
|
18975
|
+
function convertJunieToRulesyncPermissions({ allowlist }) {
|
|
18976
|
+
const permission = {};
|
|
18977
|
+
const rules = allowlist.rules;
|
|
18978
|
+
if (rules && typeof rules === "object") for (const group of JUNIE_RULE_GROUPS) {
|
|
18979
|
+
const list = rules[group];
|
|
18980
|
+
if (!Array.isArray(list)) continue;
|
|
18981
|
+
const category = JUNIE_GROUP_TO_CANONICAL[group];
|
|
18982
|
+
for (const rule of list) {
|
|
18983
|
+
if (!rule || typeof rule !== "object") continue;
|
|
18984
|
+
const pattern = typeof rule.pattern === "string" ? rule.pattern : typeof rule.prefix === "string" ? rule.prefix : void 0;
|
|
18985
|
+
if (pattern === void 0 || !isPermissionAction$1(rule.action)) continue;
|
|
18986
|
+
(permission[category] ??= {})[pattern] = rule.action;
|
|
18987
|
+
}
|
|
18988
|
+
}
|
|
18989
|
+
return { permission };
|
|
18990
|
+
}
|
|
18991
|
+
//#endregion
|
|
17192
18992
|
//#region src/features/permissions/kilo-permissions.ts
|
|
17193
18993
|
const KiloPermissionSchema = z.union([z.enum([
|
|
17194
18994
|
"allow",
|
|
@@ -17537,7 +19337,11 @@ const OpencodePermissionSchema = z.union([z.enum([
|
|
|
17537
19337
|
"ask",
|
|
17538
19338
|
"deny"
|
|
17539
19339
|
]))]);
|
|
17540
|
-
const OpencodePermissionsConfigSchema = z.looseObject({ permission: z.optional(z.
|
|
19340
|
+
const OpencodePermissionsConfigSchema = z.looseObject({ permission: z.optional(z.union([z.enum([
|
|
19341
|
+
"allow",
|
|
19342
|
+
"ask",
|
|
19343
|
+
"deny"
|
|
19344
|
+
]), z.record(z.string(), OpencodePermissionSchema)])) });
|
|
17541
19345
|
var OpencodePermissions = class OpencodePermissions extends ToolPermissions {
|
|
17542
19346
|
json;
|
|
17543
19347
|
constructor(params) {
|
|
@@ -17640,6 +19444,7 @@ var OpencodePermissions = class OpencodePermissions extends ToolPermissions {
|
|
|
17640
19444
|
}
|
|
17641
19445
|
normalizePermission(permission) {
|
|
17642
19446
|
if (!permission) return {};
|
|
19447
|
+
if (typeof permission === "string") return { "*": { "*": permission } };
|
|
17643
19448
|
return Object.fromEntries(Object.entries(permission).map(([tool, value]) => [tool, typeof value === "string" ? { "*": value } : value]));
|
|
17644
19449
|
}
|
|
17645
19450
|
};
|
|
@@ -17676,7 +19481,7 @@ const QWEN_TO_CANONICAL_TOOL_NAMES = Object.fromEntries(Object.entries(CANONICAL
|
|
|
17676
19481
|
function toQwenToolName(canonical) {
|
|
17677
19482
|
return CANONICAL_TO_QWEN_TOOL_NAMES[canonical] ?? canonical;
|
|
17678
19483
|
}
|
|
17679
|
-
function toCanonicalToolName$
|
|
19484
|
+
function toCanonicalToolName$3(qwenName) {
|
|
17680
19485
|
return QWEN_TO_CANONICAL_TOOL_NAMES[qwenName] ?? qwenName;
|
|
17681
19486
|
}
|
|
17682
19487
|
function parseQwenPermissionEntry(entry, options = {}) {
|
|
@@ -17870,13 +19675,242 @@ function convertQwenToRulesyncPermissions(params) {
|
|
|
17870
19675
|
const parsed = parseQwenPermissionEntry(entry, { logger });
|
|
17871
19676
|
if (!parsed.ok) {
|
|
17872
19677
|
if (action === "deny") {
|
|
17873
|
-
const canonical = toCanonicalToolName$
|
|
19678
|
+
const canonical = toCanonicalToolName$3(parsed.toolName);
|
|
17874
19679
|
if (!permission[canonical]) permission[canonical] = {};
|
|
17875
19680
|
permission[canonical]["*"] = action;
|
|
17876
19681
|
}
|
|
17877
19682
|
continue;
|
|
17878
19683
|
}
|
|
17879
19684
|
const { toolName, pattern } = parsed;
|
|
19685
|
+
const canonical = toCanonicalToolName$3(toolName);
|
|
19686
|
+
if (!permission[canonical]) permission[canonical] = {};
|
|
19687
|
+
permission[canonical][pattern] = action;
|
|
19688
|
+
}
|
|
19689
|
+
};
|
|
19690
|
+
processEntries(params.allow, "allow");
|
|
19691
|
+
processEntries(params.ask, "ask");
|
|
19692
|
+
processEntries(params.deny, "deny");
|
|
19693
|
+
return { permission };
|
|
19694
|
+
}
|
|
19695
|
+
//#endregion
|
|
19696
|
+
//#region src/features/permissions/reasonix-permissions.ts
|
|
19697
|
+
/**
|
|
19698
|
+
* Mapping from rulesync canonical tool category names (lowercase) to Reasonix
|
|
19699
|
+
* permission-rule tool families (PascalCase).
|
|
19700
|
+
*
|
|
19701
|
+
* Reasonix's `[permissions]` rule syntax (SPEC.md §3.7) is explicitly
|
|
19702
|
+
* documented as "Claude Code-style": "Bash and file mutation approvals use
|
|
19703
|
+
* Claude Code-style families such as `Bash(npm run build)`, `Bash(npm run
|
|
19704
|
+
* test:*)`, and `Edit(docs/**)`." Reasonix also accepts legacy lowercase tool
|
|
19705
|
+
* IDs for compatibility, but new rules are saved using these PascalCase
|
|
19706
|
+
* families, so rulesync reuses the same mapping `claudecode-permissions.ts`
|
|
19707
|
+
* uses (the closest documented precedent for this syntax).
|
|
19708
|
+
* @see https://github.com/esengine/DeepSeek-Reasonix/blob/main-v2/docs/SPEC.md
|
|
19709
|
+
*/
|
|
19710
|
+
const CANONICAL_TO_REASONIX_TOOL_NAMES = {
|
|
19711
|
+
bash: "Bash",
|
|
19712
|
+
read: "Read",
|
|
19713
|
+
edit: "Edit",
|
|
19714
|
+
write: "Write",
|
|
19715
|
+
webfetch: "WebFetch",
|
|
19716
|
+
websearch: "WebSearch",
|
|
19717
|
+
grep: "Grep",
|
|
19718
|
+
glob: "Glob",
|
|
19719
|
+
notebookedit: "NotebookEdit",
|
|
19720
|
+
agent: "Agent"
|
|
19721
|
+
};
|
|
19722
|
+
/**
|
|
19723
|
+
* Reverse mapping from Reasonix tool names to rulesync canonical names.
|
|
19724
|
+
*/
|
|
19725
|
+
const REASONIX_TO_CANONICAL_TOOL_NAMES = Object.fromEntries(Object.entries(CANONICAL_TO_REASONIX_TOOL_NAMES).map(([k, v]) => [v, k]));
|
|
19726
|
+
function toReasonixToolName(canonical) {
|
|
19727
|
+
return CANONICAL_TO_REASONIX_TOOL_NAMES[canonical] ?? canonical;
|
|
19728
|
+
}
|
|
19729
|
+
function toCanonicalToolName$2(reasonixName) {
|
|
19730
|
+
return REASONIX_TO_CANONICAL_TOOL_NAMES[reasonixName] ?? reasonixName;
|
|
19731
|
+
}
|
|
19732
|
+
/**
|
|
19733
|
+
* Parse a Reasonix permission entry like "Bash(npm run *)" into tool name and pattern.
|
|
19734
|
+
* If no parentheses, returns the tool name with "*" as the pattern.
|
|
19735
|
+
*/
|
|
19736
|
+
function parseReasonixPermissionEntry(entry) {
|
|
19737
|
+
const parenIndex = entry.indexOf("(");
|
|
19738
|
+
if (parenIndex === -1) return {
|
|
19739
|
+
toolName: entry,
|
|
19740
|
+
pattern: "*"
|
|
19741
|
+
};
|
|
19742
|
+
const toolName = entry.slice(0, parenIndex);
|
|
19743
|
+
if (!entry.endsWith(")")) return {
|
|
19744
|
+
toolName,
|
|
19745
|
+
pattern: "*"
|
|
19746
|
+
};
|
|
19747
|
+
return {
|
|
19748
|
+
toolName,
|
|
19749
|
+
pattern: entry.slice(parenIndex + 1, -1) || "*"
|
|
19750
|
+
};
|
|
19751
|
+
}
|
|
19752
|
+
/**
|
|
19753
|
+
* Build a Reasonix permission entry like "Bash(npm run *)".
|
|
19754
|
+
* If the pattern is "*", returns just the tool name.
|
|
19755
|
+
*/
|
|
19756
|
+
function buildReasonixPermissionEntry(toolName, pattern) {
|
|
19757
|
+
if (pattern === "*") return toolName;
|
|
19758
|
+
return `${toolName}(${pattern})`;
|
|
19759
|
+
}
|
|
19760
|
+
function parseReasonixConfig(fileContent) {
|
|
19761
|
+
const parsed = smolToml.parse(fileContent || smolToml.stringify({}));
|
|
19762
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return {};
|
|
19763
|
+
return { ...parsed };
|
|
19764
|
+
}
|
|
19765
|
+
function toStringArray$1(value) {
|
|
19766
|
+
if (!Array.isArray(value)) return [];
|
|
19767
|
+
return value.filter((entry) => typeof entry === "string");
|
|
19768
|
+
}
|
|
19769
|
+
function toPermissionsTable(value) {
|
|
19770
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return {};
|
|
19771
|
+
return { ...value };
|
|
19772
|
+
}
|
|
19773
|
+
var ReasonixPermissions = class ReasonixPermissions extends ToolPermissions {
|
|
19774
|
+
toml;
|
|
19775
|
+
constructor(params) {
|
|
19776
|
+
super(params);
|
|
19777
|
+
this.toml = parseReasonixConfig(this.getFileContent());
|
|
19778
|
+
}
|
|
19779
|
+
isDeletable() {
|
|
19780
|
+
return false;
|
|
19781
|
+
}
|
|
19782
|
+
static getSettablePaths({ global } = {}) {
|
|
19783
|
+
if (global) return {
|
|
19784
|
+
relativeDirPath: REASONIX_GLOBAL_DIR,
|
|
19785
|
+
relativeFilePath: REASONIX_GLOBAL_PERMISSIONS_FILE_NAME
|
|
19786
|
+
};
|
|
19787
|
+
return {
|
|
19788
|
+
relativeDirPath: ".",
|
|
19789
|
+
relativeFilePath: REASONIX_PROJECT_PERMISSIONS_FILE_NAME
|
|
19790
|
+
};
|
|
19791
|
+
}
|
|
19792
|
+
static async fromFile({ outputRoot = process.cwd(), validate = true, global = false }) {
|
|
19793
|
+
const paths = this.getSettablePaths({ global });
|
|
19794
|
+
const fileContent = await readFileContentOrNull(join(outputRoot, paths.relativeDirPath, paths.relativeFilePath)) ?? smolToml.stringify({});
|
|
19795
|
+
return new ReasonixPermissions({
|
|
19796
|
+
outputRoot,
|
|
19797
|
+
relativeDirPath: paths.relativeDirPath,
|
|
19798
|
+
relativeFilePath: paths.relativeFilePath,
|
|
19799
|
+
fileContent,
|
|
19800
|
+
validate
|
|
19801
|
+
});
|
|
19802
|
+
}
|
|
19803
|
+
static async fromRulesyncPermissions({ outputRoot = process.cwd(), rulesyncPermissions, validate = true, logger, global = false }) {
|
|
19804
|
+
const paths = this.getSettablePaths({ global });
|
|
19805
|
+
const parsed = parseReasonixConfig(await readFileContentOrNull(join(outputRoot, paths.relativeDirPath, paths.relativeFilePath)) ?? smolToml.stringify({}));
|
|
19806
|
+
const config = rulesyncPermissions.getJson();
|
|
19807
|
+
const { allow, ask, deny } = convertRulesyncToReasonixPermissions(config);
|
|
19808
|
+
const managedToolNames = new Set(Object.keys(config.permission).map((category) => toReasonixToolName(category)));
|
|
19809
|
+
const existingPermissions = toPermissionsTable(parsed.permissions);
|
|
19810
|
+
const preservedAllow = toStringArray$1(existingPermissions.allow).filter((entry) => !managedToolNames.has(parseReasonixPermissionEntry(entry).toolName));
|
|
19811
|
+
const preservedAsk = toStringArray$1(existingPermissions.ask).filter((entry) => !managedToolNames.has(parseReasonixPermissionEntry(entry).toolName));
|
|
19812
|
+
const preservedDeny = toStringArray$1(existingPermissions.deny).filter((entry) => !managedToolNames.has(parseReasonixPermissionEntry(entry).toolName));
|
|
19813
|
+
if (logger && managedToolNames.has("Read")) {
|
|
19814
|
+
const droppedReadDenyEntries = toStringArray$1(existingPermissions.deny).filter((entry) => {
|
|
19815
|
+
const { toolName } = parseReasonixPermissionEntry(entry);
|
|
19816
|
+
return toolName === "Read";
|
|
19817
|
+
});
|
|
19818
|
+
if (droppedReadDenyEntries.length > 0) logger.warn(`Permissions feature manages 'Read' tool and will overwrite ${droppedReadDenyEntries.length} existing Read deny entries (possibly from ignore feature). Permissions take precedence.`);
|
|
19819
|
+
}
|
|
19820
|
+
const mergedPermissions = { ...existingPermissions };
|
|
19821
|
+
const mergedAllow = uniq([...preservedAllow, ...allow].toSorted());
|
|
19822
|
+
const mergedAsk = uniq([...preservedAsk, ...ask].toSorted());
|
|
19823
|
+
const mergedDeny = uniq([...preservedDeny, ...deny].toSorted());
|
|
19824
|
+
if (mergedAllow.length > 0) mergedPermissions.allow = mergedAllow;
|
|
19825
|
+
else delete mergedPermissions.allow;
|
|
19826
|
+
if (mergedAsk.length > 0) mergedPermissions.ask = mergedAsk;
|
|
19827
|
+
else delete mergedPermissions.ask;
|
|
19828
|
+
if (mergedDeny.length > 0) mergedPermissions.deny = mergedDeny;
|
|
19829
|
+
else delete mergedPermissions.deny;
|
|
19830
|
+
const merged = {
|
|
19831
|
+
...parsed,
|
|
19832
|
+
permissions: mergedPermissions
|
|
19833
|
+
};
|
|
19834
|
+
const fileContent = smolToml.stringify(merged);
|
|
19835
|
+
return new ReasonixPermissions({
|
|
19836
|
+
outputRoot,
|
|
19837
|
+
relativeDirPath: paths.relativeDirPath,
|
|
19838
|
+
relativeFilePath: paths.relativeFilePath,
|
|
19839
|
+
fileContent,
|
|
19840
|
+
validate
|
|
19841
|
+
});
|
|
19842
|
+
}
|
|
19843
|
+
toRulesyncPermissions() {
|
|
19844
|
+
const permissions = toPermissionsTable(this.toml.permissions);
|
|
19845
|
+
const config = convertReasonixToRulesyncPermissions({
|
|
19846
|
+
allow: toStringArray$1(permissions.allow),
|
|
19847
|
+
ask: toStringArray$1(permissions.ask),
|
|
19848
|
+
deny: toStringArray$1(permissions.deny)
|
|
19849
|
+
});
|
|
19850
|
+
return this.toRulesyncPermissionsDefault({ fileContent: JSON.stringify(config, null, 2) });
|
|
19851
|
+
}
|
|
19852
|
+
validate() {
|
|
19853
|
+
try {
|
|
19854
|
+
parseReasonixConfig(this.getFileContent());
|
|
19855
|
+
return {
|
|
19856
|
+
success: true,
|
|
19857
|
+
error: null
|
|
19858
|
+
};
|
|
19859
|
+
} catch (error) {
|
|
19860
|
+
return {
|
|
19861
|
+
success: false,
|
|
19862
|
+
error: /* @__PURE__ */ new Error(`Failed to parse Reasonix config TOML: ${formatError(error)}`)
|
|
19863
|
+
};
|
|
19864
|
+
}
|
|
19865
|
+
}
|
|
19866
|
+
static forDeletion({ outputRoot = process.cwd(), relativeDirPath, relativeFilePath }) {
|
|
19867
|
+
return new ReasonixPermissions({
|
|
19868
|
+
outputRoot,
|
|
19869
|
+
relativeDirPath,
|
|
19870
|
+
relativeFilePath,
|
|
19871
|
+
fileContent: smolToml.stringify({}),
|
|
19872
|
+
validate: false
|
|
19873
|
+
});
|
|
19874
|
+
}
|
|
19875
|
+
};
|
|
19876
|
+
/**
|
|
19877
|
+
* Convert rulesync permissions config to Reasonix allow/ask/deny arrays.
|
|
19878
|
+
*/
|
|
19879
|
+
function convertRulesyncToReasonixPermissions(config) {
|
|
19880
|
+
const allow = [];
|
|
19881
|
+
const ask = [];
|
|
19882
|
+
const deny = [];
|
|
19883
|
+
for (const [category, rules] of Object.entries(config.permission)) {
|
|
19884
|
+
const reasonixToolName = toReasonixToolName(category);
|
|
19885
|
+
for (const [pattern, action] of Object.entries(rules)) {
|
|
19886
|
+
const entry = buildReasonixPermissionEntry(reasonixToolName, pattern);
|
|
19887
|
+
switch (action) {
|
|
19888
|
+
case "allow":
|
|
19889
|
+
allow.push(entry);
|
|
19890
|
+
break;
|
|
19891
|
+
case "ask":
|
|
19892
|
+
ask.push(entry);
|
|
19893
|
+
break;
|
|
19894
|
+
case "deny":
|
|
19895
|
+
deny.push(entry);
|
|
19896
|
+
break;
|
|
19897
|
+
}
|
|
19898
|
+
}
|
|
19899
|
+
}
|
|
19900
|
+
return {
|
|
19901
|
+
allow,
|
|
19902
|
+
ask,
|
|
19903
|
+
deny
|
|
19904
|
+
};
|
|
19905
|
+
}
|
|
19906
|
+
/**
|
|
19907
|
+
* Convert Reasonix allow/ask/deny arrays to rulesync permissions config.
|
|
19908
|
+
*/
|
|
19909
|
+
function convertReasonixToRulesyncPermissions(params) {
|
|
19910
|
+
const permission = {};
|
|
19911
|
+
const processEntries = (entries, action) => {
|
|
19912
|
+
for (const entry of entries) {
|
|
19913
|
+
const { toolName, pattern } = parseReasonixPermissionEntry(entry);
|
|
17880
19914
|
const canonical = toCanonicalToolName$2(toolName);
|
|
17881
19915
|
if (!permission[canonical]) permission[canonical] = {};
|
|
17882
19916
|
permission[canonical][pattern] = action;
|
|
@@ -17989,13 +20023,13 @@ var RovodevPermissions = class RovodevPermissions extends ToolPermissions {
|
|
|
17989
20023
|
} catch (error) {
|
|
17990
20024
|
throw new Error(`Failed to parse existing Rovodev config at ${filePath}: ${formatError(error)}`, { cause: error });
|
|
17991
20025
|
}
|
|
17992
|
-
const config = isRecord
|
|
20026
|
+
const config = isRecord(parsed) ? { ...parsed } : {};
|
|
17993
20027
|
const toolPermissions = convertRulesyncToRovodevToolPermissions({
|
|
17994
20028
|
config: rulesyncPermissions.getJson(),
|
|
17995
20029
|
logger
|
|
17996
20030
|
});
|
|
17997
20031
|
config.toolPermissions = {
|
|
17998
|
-
...isRecord
|
|
20032
|
+
...isRecord(config.toolPermissions) ? { ...config.toolPermissions } : {},
|
|
17999
20033
|
...toolPermissions
|
|
18000
20034
|
};
|
|
18001
20035
|
return new RovodevPermissions({
|
|
@@ -18015,8 +20049,8 @@ var RovodevPermissions = class RovodevPermissions extends ToolPermissions {
|
|
|
18015
20049
|
} catch (error) {
|
|
18016
20050
|
throw new Error(`Failed to parse Rovodev permissions content in ${join(this.getRelativeDirPath(), this.getRelativeFilePath())}: ${formatError(error)}`, { cause: error });
|
|
18017
20051
|
}
|
|
18018
|
-
const config = isRecord
|
|
18019
|
-
const rulesyncConfig = convertRovodevToolPermissionsToRulesync(isRecord
|
|
20052
|
+
const config = isRecord(parsed) ? parsed : {};
|
|
20053
|
+
const rulesyncConfig = convertRovodevToolPermissionsToRulesync(isRecord(config.toolPermissions) ? config.toolPermissions : {});
|
|
18020
20054
|
return this.toRulesyncPermissionsDefault({ fileContent: JSON.stringify(rulesyncConfig, null, 2) });
|
|
18021
20055
|
}
|
|
18022
20056
|
validate() {
|
|
@@ -18094,11 +20128,11 @@ function convertBashRules(rules) {
|
|
|
18094
20128
|
function convertRovodevToolPermissionsToRulesync(toolPermissions) {
|
|
18095
20129
|
const permission = {};
|
|
18096
20130
|
const bash = toolPermissions.bash;
|
|
18097
|
-
if (isRecord
|
|
20131
|
+
if (isRecord(bash)) {
|
|
18098
20132
|
const bashRules = {};
|
|
18099
20133
|
if (isPermissionAction(bash.default)) bashRules[CATCH_ALL_PATTERN$1] = bash.default;
|
|
18100
20134
|
if (Array.isArray(bash.commands)) {
|
|
18101
|
-
for (const entry of bash.commands) if (isRecord
|
|
20135
|
+
for (const entry of bash.commands) if (isRecord(entry) && typeof entry.command === "string" && isPermissionAction(entry.permission)) bashRules[entry.command] = entry.permission;
|
|
18102
20136
|
}
|
|
18103
20137
|
if (Object.keys(bashRules).length > 0) permission.bash = bashRules;
|
|
18104
20138
|
}
|
|
@@ -18237,21 +20271,6 @@ var TaktPermissions = class TaktPermissions extends ToolPermissions {
|
|
|
18237
20271
|
}
|
|
18238
20272
|
};
|
|
18239
20273
|
/**
|
|
18240
|
-
* Parse a Takt `config.yaml` into a plain object, treating an empty file as `{}`.
|
|
18241
|
-
*/
|
|
18242
|
-
function parseTaktConfig(fileContent, relativeDirPath, relativeFilePath) {
|
|
18243
|
-
const configPath = join(relativeDirPath, relativeFilePath);
|
|
18244
|
-
let parsed;
|
|
18245
|
-
try {
|
|
18246
|
-
parsed = fileContent.trim() === "" ? {} : load(fileContent);
|
|
18247
|
-
} catch (error) {
|
|
18248
|
-
throw new Error(`Failed to parse Takt config at ${configPath}: ${formatError(error)}`, { cause: error });
|
|
18249
|
-
}
|
|
18250
|
-
if (parsed === void 0 || parsed === null) return {};
|
|
18251
|
-
if (!isPlainObject(parsed)) throw new Error(`Failed to parse Takt config at ${configPath}: expected a YAML mapping`);
|
|
18252
|
-
return parsed;
|
|
18253
|
-
}
|
|
18254
|
-
/**
|
|
18255
20274
|
* Resolve the active Takt provider: the top-level `provider:` value, else the
|
|
18256
20275
|
* sole key in `provider_profiles`, else the `claude` default.
|
|
18257
20276
|
*/
|
|
@@ -18573,8 +20592,8 @@ var WarpPermissions = class WarpPermissions extends ToolPermissions {
|
|
|
18573
20592
|
config: rulesyncPermissions.getJson(),
|
|
18574
20593
|
logger
|
|
18575
20594
|
});
|
|
18576
|
-
const agents = isRecord
|
|
18577
|
-
const profiles = isRecord
|
|
20595
|
+
const agents = isRecord(settings.agents) ? { ...settings.agents } : {};
|
|
20596
|
+
const profiles = isRecord(agents.profiles) ? { ...agents.profiles } : {};
|
|
18578
20597
|
const mergedAllow = uniq(allow.toSorted());
|
|
18579
20598
|
const mergedDeny = uniq(deny.toSorted());
|
|
18580
20599
|
if (mergedAllow.length > 0) profiles[ALLOWLIST_KEY] = mergedAllow;
|
|
@@ -18599,8 +20618,8 @@ var WarpPermissions = class WarpPermissions extends ToolPermissions {
|
|
|
18599
20618
|
} catch (error) {
|
|
18600
20619
|
throw new Error(`Failed to parse Warp permissions content in ${join(this.getRelativeDirPath(), this.getRelativeFilePath())}: ${formatError(error)}`, { cause: error });
|
|
18601
20620
|
}
|
|
18602
|
-
const agents = isRecord
|
|
18603
|
-
const profiles = isRecord
|
|
20621
|
+
const agents = isRecord(settings.agents) ? settings.agents : {};
|
|
20622
|
+
const profiles = isRecord(agents.profiles) ? agents.profiles : {};
|
|
18604
20623
|
const config = convertWarpToRulesyncPermissions({
|
|
18605
20624
|
allow: isStringArray(profiles[ALLOWLIST_KEY]) ? profiles[ALLOWLIST_KEY] : [],
|
|
18606
20625
|
deny: isStringArray(profiles[DENYLIST_KEY]) ? profiles[DENYLIST_KEY] : []
|
|
@@ -18951,6 +20970,14 @@ const toolPermissionsFactories = /* @__PURE__ */ new Map([
|
|
|
18951
20970
|
supportsImport: true
|
|
18952
20971
|
}
|
|
18953
20972
|
}],
|
|
20973
|
+
["devin", {
|
|
20974
|
+
class: DevinPermissions,
|
|
20975
|
+
meta: {
|
|
20976
|
+
supportsProject: true,
|
|
20977
|
+
supportsGlobal: true,
|
|
20978
|
+
supportsImport: true
|
|
20979
|
+
}
|
|
20980
|
+
}],
|
|
18954
20981
|
["factorydroid", {
|
|
18955
20982
|
class: FactorydroidPermissions,
|
|
18956
20983
|
meta: {
|
|
@@ -18983,6 +21010,14 @@ const toolPermissionsFactories = /* @__PURE__ */ new Map([
|
|
|
18983
21010
|
supportsImport: true
|
|
18984
21011
|
}
|
|
18985
21012
|
}],
|
|
21013
|
+
["junie", {
|
|
21014
|
+
class: JuniePermissions,
|
|
21015
|
+
meta: {
|
|
21016
|
+
supportsProject: true,
|
|
21017
|
+
supportsGlobal: true,
|
|
21018
|
+
supportsImport: true
|
|
21019
|
+
}
|
|
21020
|
+
}],
|
|
18986
21021
|
["kilo", {
|
|
18987
21022
|
class: KiloPermissions,
|
|
18988
21023
|
meta: {
|
|
@@ -19031,6 +21066,14 @@ const toolPermissionsFactories = /* @__PURE__ */ new Map([
|
|
|
19031
21066
|
supportsImport: true
|
|
19032
21067
|
}
|
|
19033
21068
|
}],
|
|
21069
|
+
["reasonix", {
|
|
21070
|
+
class: ReasonixPermissions,
|
|
21071
|
+
meta: {
|
|
21072
|
+
supportsProject: true,
|
|
21073
|
+
supportsGlobal: true,
|
|
21074
|
+
supportsImport: true
|
|
21075
|
+
}
|
|
21076
|
+
}],
|
|
19034
21077
|
["rovodev", {
|
|
19035
21078
|
class: RovodevPermissions,
|
|
19036
21079
|
meta: {
|
|
@@ -19583,7 +21626,7 @@ const RulesyncSkillFrontmatterSchema = z.looseObject({
|
|
|
19583
21626
|
opencode: z.optional(z.looseObject({
|
|
19584
21627
|
"allowed-tools": z.optional(z.array(z.string())),
|
|
19585
21628
|
license: z.optional(z.string()),
|
|
19586
|
-
compatibility: z.optional(z.looseObject({})),
|
|
21629
|
+
compatibility: z.optional(z.union([z.string(), z.looseObject({})])),
|
|
19587
21630
|
metadata: z.optional(z.looseObject({}))
|
|
19588
21631
|
})),
|
|
19589
21632
|
kilo: z.optional(z.looseObject({
|
|
@@ -19604,7 +21647,8 @@ const RulesyncSkillFrontmatterSchema = z.looseObject({
|
|
|
19604
21647
|
})),
|
|
19605
21648
|
copilotcli: z.optional(z.looseObject({
|
|
19606
21649
|
license: z.optional(z.string()),
|
|
19607
|
-
"allowed-tools": z.optional(z.union([z.string(), z.array(z.string())]))
|
|
21650
|
+
"allowed-tools": z.optional(z.union([z.string(), z.array(z.string())])),
|
|
21651
|
+
"argument-hint": z.optional(z.string())
|
|
19608
21652
|
})),
|
|
19609
21653
|
pi: z.optional(z.looseObject({
|
|
19610
21654
|
"allowed-tools": z.optional(z.array(z.string())),
|
|
@@ -21358,7 +23402,11 @@ const CopilotSkillFrontmatterSchema = z.looseObject({
|
|
|
21358
23402
|
});
|
|
21359
23403
|
/**
|
|
21360
23404
|
* Represents a GitHub Copilot skill directory.
|
|
21361
|
-
*
|
|
23405
|
+
*
|
|
23406
|
+
* Copilot discovers project skills from `.github/skills/` and personal/global
|
|
23407
|
+
* skills from `~/.copilot/skills/`. Each skill is a directory containing a
|
|
23408
|
+
* `SKILL.md` file with `name`/`description` frontmatter.
|
|
23409
|
+
* https://docs.github.com/en/copilot/concepts/agents/about-agent-skills
|
|
21362
23410
|
*/
|
|
21363
23411
|
var CopilotSkill = class CopilotSkill extends ToolSkill {
|
|
21364
23412
|
constructor({ outputRoot = process.cwd(), relativeDirPath = COPILOT_SKILLS_DIR_PATH, dirName, frontmatter, body, otherFiles = [], validate = true, global = false }) {
|
|
@@ -21380,7 +23428,7 @@ var CopilotSkill = class CopilotSkill extends ToolSkill {
|
|
|
21380
23428
|
}
|
|
21381
23429
|
}
|
|
21382
23430
|
static getSettablePaths(options) {
|
|
21383
|
-
if (options?.global)
|
|
23431
|
+
if (options?.global) return { relativeDirPath: COPILOT_SKILLS_GLOBAL_DIR_PATH };
|
|
21384
23432
|
return { relativeDirPath: COPILOT_SKILLS_DIR_PATH };
|
|
21385
23433
|
}
|
|
21386
23434
|
getFrontmatter() {
|
|
@@ -21495,7 +23543,8 @@ const CopilotcliSkillFrontmatterSchema = z.looseObject({
|
|
|
21495
23543
|
name: z.string(),
|
|
21496
23544
|
description: z.string(),
|
|
21497
23545
|
license: z.optional(z.string()),
|
|
21498
|
-
"allowed-tools": z.optional(z.union([z.string(), z.array(z.string())]))
|
|
23546
|
+
"allowed-tools": z.optional(z.union([z.string(), z.array(z.string())])),
|
|
23547
|
+
"argument-hint": z.optional(z.string())
|
|
21499
23548
|
});
|
|
21500
23549
|
/**
|
|
21501
23550
|
* Represents a GitHub Copilot CLI skill directory.
|
|
@@ -21525,7 +23574,7 @@ var CopilotcliSkill = class CopilotcliSkill extends ToolSkill {
|
|
|
21525
23574
|
}
|
|
21526
23575
|
}
|
|
21527
23576
|
static getSettablePaths(options) {
|
|
21528
|
-
if (options?.global) return { relativeDirPath:
|
|
23577
|
+
if (options?.global) return { relativeDirPath: COPILOT_SKILLS_GLOBAL_DIR_PATH };
|
|
21529
23578
|
return { relativeDirPath: COPILOT_SKILLS_DIR_PATH };
|
|
21530
23579
|
}
|
|
21531
23580
|
getFrontmatter() {
|
|
@@ -21553,7 +23602,8 @@ var CopilotcliSkill = class CopilotcliSkill extends ToolSkill {
|
|
|
21553
23602
|
const frontmatter = this.getFrontmatter();
|
|
21554
23603
|
const copilotcliSection = {
|
|
21555
23604
|
...frontmatter.license !== void 0 && { license: frontmatter.license },
|
|
21556
|
-
...frontmatter["allowed-tools"] !== void 0 && { "allowed-tools": frontmatter["allowed-tools"] }
|
|
23605
|
+
...frontmatter["allowed-tools"] !== void 0 && { "allowed-tools": frontmatter["allowed-tools"] },
|
|
23606
|
+
...frontmatter["argument-hint"] !== void 0 && { "argument-hint": frontmatter["argument-hint"] }
|
|
21557
23607
|
};
|
|
21558
23608
|
const rulesyncFrontmatter = {
|
|
21559
23609
|
name: frontmatter.name,
|
|
@@ -21579,7 +23629,8 @@ var CopilotcliSkill = class CopilotcliSkill extends ToolSkill {
|
|
|
21579
23629
|
name: rulesyncFrontmatter.name,
|
|
21580
23630
|
description: rulesyncFrontmatter.description,
|
|
21581
23631
|
...rulesyncFrontmatter.copilotcli?.license !== void 0 && { license: rulesyncFrontmatter.copilotcli.license },
|
|
21582
|
-
...rulesyncFrontmatter.copilotcli?.["allowed-tools"] !== void 0 && { "allowed-tools": rulesyncFrontmatter.copilotcli["allowed-tools"] }
|
|
23632
|
+
...rulesyncFrontmatter.copilotcli?.["allowed-tools"] !== void 0 && { "allowed-tools": rulesyncFrontmatter.copilotcli["allowed-tools"] },
|
|
23633
|
+
...rulesyncFrontmatter.copilotcli?.["argument-hint"] !== void 0 && { "argument-hint": rulesyncFrontmatter.copilotcli["argument-hint"] }
|
|
21583
23634
|
};
|
|
21584
23635
|
return new CopilotcliSkill({
|
|
21585
23636
|
outputRoot,
|
|
@@ -22805,8 +24856,7 @@ var KiroSkill = class KiroSkill extends ToolSkill {
|
|
|
22805
24856
|
if (!result.success) throw result.error;
|
|
22806
24857
|
}
|
|
22807
24858
|
}
|
|
22808
|
-
static getSettablePaths(
|
|
22809
|
-
if (options?.global) throw new Error("KiroSkill does not support global mode.");
|
|
24859
|
+
static getSettablePaths(_options) {
|
|
22810
24860
|
return { relativeDirPath: KIRO_SKILLS_DIR_PATH };
|
|
22811
24861
|
}
|
|
22812
24862
|
getFrontmatter() {
|
|
@@ -22948,10 +24998,19 @@ const OpenCodeSkillFrontmatterSchema = z.looseObject({
|
|
|
22948
24998
|
name: z.string(),
|
|
22949
24999
|
description: z.string(),
|
|
22950
25000
|
license: z.optional(z.string()),
|
|
22951
|
-
compatibility: z.optional(z.looseObject({})),
|
|
25001
|
+
compatibility: z.optional(z.union([z.string(), z.looseObject({})])),
|
|
22952
25002
|
metadata: z.optional(z.looseObject({})),
|
|
22953
25003
|
"allowed-tools": z.optional(z.array(z.string()))
|
|
22954
25004
|
});
|
|
25005
|
+
/**
|
|
25006
|
+
* Reads a top-level `compatibility` value from rulesync frontmatter, accepting
|
|
25007
|
+
* both the documented string form (e.g. `compatibility: opencode`) and the
|
|
25008
|
+
* legacy object form. Returns `undefined` for any other shape.
|
|
25009
|
+
*/
|
|
25010
|
+
function readTopLevelCompatibility(value) {
|
|
25011
|
+
if (typeof value === "string") return value;
|
|
25012
|
+
if (typeof value === "object" && value !== null) return value;
|
|
25013
|
+
}
|
|
22955
25014
|
var OpenCodeSkill = class OpenCodeSkill extends ToolSkill {
|
|
22956
25015
|
constructor({ outputRoot = process.cwd(), relativeDirPath = OPENCODE_SKILLS_DIR_PATH, dirName, frontmatter, body, otherFiles = [], validate = true, global = false }) {
|
|
22957
25016
|
super({
|
|
@@ -23028,7 +25087,7 @@ var OpenCodeSkill = class OpenCodeSkill extends ToolSkill {
|
|
|
23028
25087
|
const opencodeSection = rulesyncFrontmatter.opencode;
|
|
23029
25088
|
const looseTopLevel = rulesyncFrontmatter;
|
|
23030
25089
|
const topLevelLicense = typeof looseTopLevel.license === "string" ? looseTopLevel.license : void 0;
|
|
23031
|
-
const topLevelCompatibility =
|
|
25090
|
+
const topLevelCompatibility = readTopLevelCompatibility(looseTopLevel.compatibility);
|
|
23032
25091
|
const topLevelMetadata = typeof looseTopLevel.metadata === "object" && looseTopLevel.metadata !== null ? looseTopLevel.metadata : void 0;
|
|
23033
25092
|
const license = opencodeSection?.license ?? topLevelLicense;
|
|
23034
25093
|
const compatibility = opencodeSection?.compatibility ?? topLevelCompatibility;
|
|
@@ -24382,7 +26441,7 @@ const toolSkillFactories = /* @__PURE__ */ new Map([
|
|
|
24382
26441
|
meta: {
|
|
24383
26442
|
supportsProject: true,
|
|
24384
26443
|
supportsSimulated: false,
|
|
24385
|
-
supportsGlobal:
|
|
26444
|
+
supportsGlobal: true
|
|
24386
26445
|
}
|
|
24387
26446
|
}],
|
|
24388
26447
|
["copilotcli", {
|
|
@@ -24470,7 +26529,7 @@ const toolSkillFactories = /* @__PURE__ */ new Map([
|
|
|
24470
26529
|
meta: {
|
|
24471
26530
|
supportsProject: true,
|
|
24472
26531
|
supportsSimulated: false,
|
|
24473
|
-
supportsGlobal:
|
|
26532
|
+
supportsGlobal: true
|
|
24474
26533
|
}
|
|
24475
26534
|
}],
|
|
24476
26535
|
["kiro-ide", {
|
|
@@ -24478,7 +26537,7 @@ const toolSkillFactories = /* @__PURE__ */ new Map([
|
|
|
24478
26537
|
meta: {
|
|
24479
26538
|
supportsProject: true,
|
|
24480
26539
|
supportsSimulated: false,
|
|
24481
|
-
supportsGlobal:
|
|
26540
|
+
supportsGlobal: true
|
|
24482
26541
|
}
|
|
24483
26542
|
}],
|
|
24484
26543
|
["opencode", {
|
|
@@ -28566,7 +30625,7 @@ const toolSubagentFactories = /* @__PURE__ */ new Map([
|
|
|
28566
30625
|
class: KiroCliSubagent,
|
|
28567
30626
|
meta: {
|
|
28568
30627
|
supportsSimulated: false,
|
|
28569
|
-
supportsGlobal:
|
|
30628
|
+
supportsGlobal: true,
|
|
28570
30629
|
filePattern: "*.json"
|
|
28571
30630
|
}
|
|
28572
30631
|
}],
|
|
@@ -28574,7 +30633,7 @@ const toolSubagentFactories = /* @__PURE__ */ new Map([
|
|
|
28574
30633
|
class: KiroIdeSubagent,
|
|
28575
30634
|
meta: {
|
|
28576
30635
|
supportsSimulated: false,
|
|
28577
|
-
supportsGlobal:
|
|
30636
|
+
supportsGlobal: true,
|
|
28578
30637
|
filePattern: "*.md"
|
|
28579
30638
|
}
|
|
28580
30639
|
}],
|
|
@@ -31111,7 +33170,7 @@ const STRATEGIES = [
|
|
|
31111
33170
|
* plus companion `globs`/`description` fields. (`.devin/rules/` is the
|
|
31112
33171
|
* pre-rebrand legacy location the tool still reads.)
|
|
31113
33172
|
* - Global scope: a single plain-markdown, always-on file (no frontmatter) at
|
|
31114
|
-
* `~/.
|
|
33173
|
+
* `~/.config/devin/AGENTS.md` (Devin Local global always-on rules).
|
|
31115
33174
|
*
|
|
31116
33175
|
* Trigger inference (when no explicit devin trigger is persisted):
|
|
31117
33176
|
* - Specific globs (non wildcard) → glob
|
|
@@ -31138,8 +33197,8 @@ var DevinRule = class DevinRule extends ToolRule {
|
|
|
31138
33197
|
}
|
|
31139
33198
|
static getGlobalRootPath(excludeToolDir) {
|
|
31140
33199
|
return {
|
|
31141
|
-
relativeDirPath: buildToolPath(
|
|
31142
|
-
relativeFilePath:
|
|
33200
|
+
relativeDirPath: buildToolPath(DEVIN_GLOBAL_CONFIG_DIR_PATH, ".", excludeToolDir),
|
|
33201
|
+
relativeFilePath: DEVIN_GLOBAL_AGENTS_FILE_NAME
|
|
31143
33202
|
};
|
|
31144
33203
|
}
|
|
31145
33204
|
static getSettablePaths({ global = false, excludeToolDir } = {}) {
|
|
@@ -32029,7 +34088,7 @@ var OpenCodeRule = class OpenCodeRule extends ToolRule {
|
|
|
32029
34088
|
/**
|
|
32030
34089
|
* Rule generator for Pi Coding Agent.
|
|
32031
34090
|
*
|
|
32032
|
-
* Pi loads instruction context
|
|
34091
|
+
* Pi loads instruction context from the `AGENTS.md` / `CLAUDE.md` family —
|
|
32033
34092
|
* the global `~/.pi/agent/AGENTS.md` plus files discovered by walking up the
|
|
32034
34093
|
* directory tree from the current working directory. It does NOT resolve
|
|
32035
34094
|
* `@`-imports or a TOON file list, and has no `.agents/memories/` concept, so
|
|
@@ -32040,6 +34099,15 @@ var OpenCodeRule = class OpenCodeRule extends ToolRule {
|
|
|
32040
34099
|
* to map onto; their bodies are folded into the single root `AGENTS.md` by the
|
|
32041
34100
|
* RulesProcessor (there is no separate non-root output location — `nonRoot` is
|
|
32042
34101
|
* `undefined`). This mirrors the codexcli, grokcli, warp, and deepagents targets.
|
|
34102
|
+
*
|
|
34103
|
+
* Pi also loads two system-prompt instruction files that rulesync does NOT emit:
|
|
34104
|
+
* `.pi/SYSTEM.md` (global `~/.pi/agent/SYSTEM.md`) *replaces* the default system
|
|
34105
|
+
* prompt entirely, and `.pi/APPEND_SYSTEM.md` (global
|
|
34106
|
+
* `~/.pi/agent/APPEND_SYSTEM.md`) *appends* to it. rulesync's rules model only
|
|
34107
|
+
* routes a designated `root` rule to a single context file and has no convention
|
|
34108
|
+
* for marking a rule as "replace" vs "append" the system prompt, so these
|
|
34109
|
+
* surfaces are documented in docs/reference/file-formats.md and left to be
|
|
34110
|
+
* authored by hand rather than mapped to a speculative new frontmatter flag.
|
|
32043
34111
|
*/
|
|
32044
34112
|
var PiRule = class PiRule extends ToolRule {
|
|
32045
34113
|
constructor({ fileContent, root, ...rest }) {
|
|
@@ -34121,63 +36189,215 @@ function warnUnsupportedTargets(params) {
|
|
|
34121
36189
|
async function checkRulesyncDirExists(params) {
|
|
34122
36190
|
return fileExists(join(params.inputRoot, RULESYNC_RELATIVE_DIR_PATH));
|
|
34123
36191
|
}
|
|
36192
|
+
function dependsOnReachable(byId, from, target) {
|
|
36193
|
+
const seen = /* @__PURE__ */ new Set();
|
|
36194
|
+
const stack = [from];
|
|
36195
|
+
while (stack.length > 0) {
|
|
36196
|
+
const current = stack.pop();
|
|
36197
|
+
if (current === void 0 || seen.has(current)) continue;
|
|
36198
|
+
seen.add(current);
|
|
36199
|
+
if (current === target) return true;
|
|
36200
|
+
for (const dep of byId.get(current)?.dependsOn ?? []) stack.push(dep);
|
|
36201
|
+
}
|
|
36202
|
+
return false;
|
|
36203
|
+
}
|
|
36204
|
+
function assertSharedFilesOrdered(steps, byId) {
|
|
36205
|
+
const writersByFile = /* @__PURE__ */ new Map();
|
|
36206
|
+
for (const step of steps) for (const file of step.writesSharedFile ?? []) writersByFile.set(file, [...writersByFile.get(file) ?? [], step.id]);
|
|
36207
|
+
for (const [file, writers] of writersByFile) for (let i = 0; i < writers.length; i++) for (let j = i + 1; j < writers.length; j++) {
|
|
36208
|
+
const a = writers[i];
|
|
36209
|
+
const b = writers[j];
|
|
36210
|
+
if (!dependsOnReachable(byId, a, b) && !dependsOnReachable(byId, b, a)) throw new Error(`Generation steps '${a}' and '${b}' both write the shared file '${file}' but neither declares a 'dependsOn' the other. Add a 'dependsOn' so the read-modify-write order is fixed; otherwise one step silently drops the other's keys.`);
|
|
36211
|
+
}
|
|
36212
|
+
}
|
|
36213
|
+
/**
|
|
36214
|
+
* Topologically sort generation steps and reject ordering hazards: a shared file
|
|
36215
|
+
* with two writers not ordered by `dependsOn` (a silent data-loss trap), an
|
|
36216
|
+
* unknown dependency, or a cycle. Reordering `steps` stays safe as a result.
|
|
36217
|
+
*
|
|
36218
|
+
* @throws Error if a shared file has unordered writers, a dependency is unknown,
|
|
36219
|
+
* or the dependency graph contains a cycle.
|
|
36220
|
+
*/
|
|
36221
|
+
function resolveExecutionOrder(steps) {
|
|
36222
|
+
const byId = new Map(steps.map((step) => [step.id, step]));
|
|
36223
|
+
assertSharedFilesOrdered(steps, byId);
|
|
36224
|
+
const unresolvedDeps = new Map(steps.map((step) => [step.id, 0]));
|
|
36225
|
+
const dependents = /* @__PURE__ */ new Map();
|
|
36226
|
+
for (const step of steps) for (const dep of step.dependsOn ?? []) {
|
|
36227
|
+
if (!byId.has(dep)) throw new Error(`Generation step '${step.id}' depends on unknown step '${dep}'.`);
|
|
36228
|
+
unresolvedDeps.set(step.id, (unresolvedDeps.get(step.id) ?? 0) + 1);
|
|
36229
|
+
dependents.set(dep, [...dependents.get(dep) ?? [], step.id]);
|
|
36230
|
+
}
|
|
36231
|
+
const ready = steps.filter((step) => (unresolvedDeps.get(step.id) ?? 0) === 0).map((step) => step.id);
|
|
36232
|
+
const ordered = [];
|
|
36233
|
+
while (ready.length > 0) {
|
|
36234
|
+
const id = ready.shift();
|
|
36235
|
+
ordered.push(byId.get(id));
|
|
36236
|
+
for (const dependent of dependents.get(id) ?? []) {
|
|
36237
|
+
const next = (unresolvedDeps.get(dependent) ?? 0) - 1;
|
|
36238
|
+
unresolvedDeps.set(dependent, next);
|
|
36239
|
+
if (next === 0) ready.push(dependent);
|
|
36240
|
+
}
|
|
36241
|
+
}
|
|
36242
|
+
if (ordered.length !== steps.length) throw new Error("Generation steps contain a cyclic 'dependsOn' dependency.");
|
|
36243
|
+
return ordered;
|
|
36244
|
+
}
|
|
36245
|
+
/**
|
|
36246
|
+
* The static shape of the generation step graph: which steps write which shared
|
|
36247
|
+
* (read-modify-write) config files, and the `dependsOn` edges that fix a safe order
|
|
36248
|
+
* for those writers. Exported (separately from the `run` closures, which need a
|
|
36249
|
+
* live `config`/`logger`) so `resolveExecutionOrder`'s ordering guarantee can be
|
|
36250
|
+
* tested directly against the real graph rather than a hand-copied one. Readonly
|
|
36251
|
+
* so a consumer can't mutate this module-level singleton and affect every
|
|
36252
|
+
* subsequent `generate()` call in the process.
|
|
36253
|
+
*/
|
|
36254
|
+
const GENERATION_STEP_GRAPH = [
|
|
36255
|
+
{
|
|
36256
|
+
id: "ignore",
|
|
36257
|
+
writesSharedFile: ["claude-settings", "zed-settings"]
|
|
36258
|
+
},
|
|
36259
|
+
{
|
|
36260
|
+
id: "mcp",
|
|
36261
|
+
writesSharedFile: [
|
|
36262
|
+
"kilo-opencode-config",
|
|
36263
|
+
"zed-settings",
|
|
36264
|
+
"qwencode-settings",
|
|
36265
|
+
"augmentcode-settings",
|
|
36266
|
+
"hermesagent-config",
|
|
36267
|
+
"amp-settings",
|
|
36268
|
+
"codexcli-config",
|
|
36269
|
+
"grokcli-config",
|
|
36270
|
+
"vibe-config",
|
|
36271
|
+
"devin-config",
|
|
36272
|
+
"reasonix-config"
|
|
36273
|
+
],
|
|
36274
|
+
dependsOn: ["ignore"]
|
|
36275
|
+
},
|
|
36276
|
+
{ id: "commands" },
|
|
36277
|
+
{ id: "subagents" },
|
|
36278
|
+
{ id: "skills" },
|
|
36279
|
+
{
|
|
36280
|
+
id: "hooks",
|
|
36281
|
+
writesSharedFile: [
|
|
36282
|
+
"claude-settings",
|
|
36283
|
+
"qwencode-settings",
|
|
36284
|
+
"augmentcode-settings",
|
|
36285
|
+
"hermesagent-config",
|
|
36286
|
+
"kiro-agent-config",
|
|
36287
|
+
"codexcli-config",
|
|
36288
|
+
"vibe-config",
|
|
36289
|
+
"devin-config"
|
|
36290
|
+
],
|
|
36291
|
+
dependsOn: ["ignore", "mcp"]
|
|
36292
|
+
},
|
|
36293
|
+
{
|
|
36294
|
+
id: "permissions",
|
|
36295
|
+
writesSharedFile: [
|
|
36296
|
+
"claude-settings",
|
|
36297
|
+
"kilo-opencode-config",
|
|
36298
|
+
"zed-settings",
|
|
36299
|
+
"qwencode-settings",
|
|
36300
|
+
"augmentcode-settings",
|
|
36301
|
+
"hermesagent-config",
|
|
36302
|
+
"kiro-agent-config",
|
|
36303
|
+
"amp-settings",
|
|
36304
|
+
"codexcli-config",
|
|
36305
|
+
"grokcli-config",
|
|
36306
|
+
"vibe-config",
|
|
36307
|
+
"devin-config",
|
|
36308
|
+
"reasonix-config"
|
|
36309
|
+
],
|
|
36310
|
+
dependsOn: [
|
|
36311
|
+
"ignore",
|
|
36312
|
+
"hooks",
|
|
36313
|
+
"mcp"
|
|
36314
|
+
]
|
|
36315
|
+
},
|
|
36316
|
+
{
|
|
36317
|
+
id: "rules",
|
|
36318
|
+
writesSharedFile: ["kilo-opencode-config"],
|
|
36319
|
+
dependsOn: [
|
|
36320
|
+
"mcp",
|
|
36321
|
+
"skills",
|
|
36322
|
+
"permissions"
|
|
36323
|
+
]
|
|
36324
|
+
}
|
|
36325
|
+
];
|
|
34124
36326
|
/**
|
|
34125
36327
|
* Generate configuration files for AI tools.
|
|
34126
36328
|
* @throws Error if generation fails
|
|
34127
36329
|
*/
|
|
34128
36330
|
async function generate(params) {
|
|
34129
36331
|
const { config, logger } = params;
|
|
34130
|
-
|
|
34131
|
-
|
|
34132
|
-
|
|
34133
|
-
|
|
34134
|
-
|
|
34135
|
-
|
|
34136
|
-
|
|
34137
|
-
|
|
34138
|
-
|
|
34139
|
-
|
|
34140
|
-
|
|
34141
|
-
|
|
34142
|
-
|
|
34143
|
-
|
|
34144
|
-
|
|
34145
|
-
|
|
34146
|
-
|
|
34147
|
-
|
|
34148
|
-
|
|
34149
|
-
|
|
34150
|
-
|
|
34151
|
-
|
|
34152
|
-
|
|
34153
|
-
|
|
34154
|
-
|
|
34155
|
-
|
|
34156
|
-
|
|
34157
|
-
|
|
34158
|
-
|
|
34159
|
-
|
|
34160
|
-
|
|
34161
|
-
|
|
34162
|
-
|
|
34163
|
-
|
|
36332
|
+
let skillsResult;
|
|
36333
|
+
const runners = {
|
|
36334
|
+
ignore: () => generateIgnoreCore({
|
|
36335
|
+
config,
|
|
36336
|
+
logger
|
|
36337
|
+
}),
|
|
36338
|
+
mcp: () => generateMcpCore({
|
|
36339
|
+
config,
|
|
36340
|
+
logger
|
|
36341
|
+
}),
|
|
36342
|
+
commands: () => generateCommandsCore({
|
|
36343
|
+
config,
|
|
36344
|
+
logger
|
|
36345
|
+
}),
|
|
36346
|
+
subagents: () => generateSubagentsCore({
|
|
36347
|
+
config,
|
|
36348
|
+
logger
|
|
36349
|
+
}),
|
|
36350
|
+
skills: async () => {
|
|
36351
|
+
skillsResult = await generateSkillsCore({
|
|
36352
|
+
config,
|
|
36353
|
+
logger
|
|
36354
|
+
});
|
|
36355
|
+
return skillsResult;
|
|
36356
|
+
},
|
|
36357
|
+
hooks: () => generateHooksCore({
|
|
36358
|
+
config,
|
|
36359
|
+
logger
|
|
36360
|
+
}),
|
|
36361
|
+
permissions: () => generatePermissionsCore({
|
|
36362
|
+
config,
|
|
36363
|
+
logger
|
|
36364
|
+
}),
|
|
36365
|
+
rules: () => generateRulesCore({
|
|
36366
|
+
config,
|
|
36367
|
+
logger,
|
|
36368
|
+
skills: skillsResult?.skills
|
|
36369
|
+
})
|
|
36370
|
+
};
|
|
36371
|
+
const orderedSteps = resolveExecutionOrder(GENERATION_STEP_GRAPH.map((meta) => ({
|
|
36372
|
+
...meta,
|
|
36373
|
+
run: runners[meta.id]
|
|
36374
|
+
})));
|
|
36375
|
+
const resultsById = /* @__PURE__ */ new Map();
|
|
36376
|
+
for (const step of orderedSteps) resultsById.set(step.id, await step.run());
|
|
36377
|
+
if (!skillsResult) throw new Error("Skills generation step did not run.");
|
|
36378
|
+
const get = (id) => {
|
|
36379
|
+
const result = resultsById.get(id);
|
|
36380
|
+
if (!result) throw new Error(`Missing generation result for step '${id}'.`);
|
|
36381
|
+
return result;
|
|
36382
|
+
};
|
|
36383
|
+
const hasDiff = orderedSteps.some((step) => get(step.id).hasDiff);
|
|
34164
36384
|
return {
|
|
34165
|
-
rulesCount:
|
|
34166
|
-
rulesPaths:
|
|
34167
|
-
ignoreCount:
|
|
34168
|
-
ignorePaths:
|
|
34169
|
-
mcpCount:
|
|
34170
|
-
mcpPaths:
|
|
34171
|
-
commandsCount:
|
|
34172
|
-
commandsPaths:
|
|
34173
|
-
subagentsCount:
|
|
34174
|
-
subagentsPaths:
|
|
36385
|
+
rulesCount: get("rules").count,
|
|
36386
|
+
rulesPaths: get("rules").paths,
|
|
36387
|
+
ignoreCount: get("ignore").count,
|
|
36388
|
+
ignorePaths: get("ignore").paths,
|
|
36389
|
+
mcpCount: get("mcp").count,
|
|
36390
|
+
mcpPaths: get("mcp").paths,
|
|
36391
|
+
commandsCount: get("commands").count,
|
|
36392
|
+
commandsPaths: get("commands").paths,
|
|
36393
|
+
subagentsCount: get("subagents").count,
|
|
36394
|
+
subagentsPaths: get("subagents").paths,
|
|
34175
36395
|
skillsCount: skillsResult.count,
|
|
34176
36396
|
skillsPaths: skillsResult.paths,
|
|
34177
|
-
hooksCount:
|
|
34178
|
-
hooksPaths:
|
|
34179
|
-
permissionsCount:
|
|
34180
|
-
permissionsPaths:
|
|
36397
|
+
hooksCount: get("hooks").count,
|
|
36398
|
+
hooksPaths: get("hooks").paths,
|
|
36399
|
+
permissionsCount: get("permissions").count,
|
|
36400
|
+
permissionsPaths: get("permissions").paths,
|
|
34181
36401
|
skills: skillsResult.skills,
|
|
34182
36402
|
hasDiff
|
|
34183
36403
|
};
|
|
@@ -34794,4 +37014,4 @@ async function importPermissionsCore(params) {
|
|
|
34794
37014
|
//#endregion
|
|
34795
37015
|
export { CLIError as $, IgnoreProcessor as A, RULESYNC_LOCAL_CONFIG_RELATIVE_FILE_PATH as At, toolCommandFactories as B, RULESYNC_SOURCES_LOCK_RELATIVE_FILE_PATH as Bt, PermissionsProcessorToolTargetSchema as C, RULESYNC_COMMANDS_RELATIVE_DIR_PATH as Ct, McpProcessorToolTargetSchema as D, RULESYNC_HOOKS_FILE_NAME as Dt, McpProcessor as E, RULESYNC_CURATED_SKILLS_RELATIVE_DIR_PATH as Et, HooksProcessorToolTargetSchema as F, RULESYNC_PERMISSIONS_FILE_NAME as Ft, CLAUDECODE_SKILLS_DIR_PATH as G, CLAUDECODE_LOCAL_RULE_FILE_NAME as H, formatError as Ht, toolHooksFactories as I, RULESYNC_PERMISSIONS_RELATIVE_FILE_PATH as It, stringifyFrontmatter as J, RulesyncCommand as K, RulesyncHooks as L, RULESYNC_RELATIVE_DIR_PATH as Lt, toolIgnoreFactories as M, RULESYNC_MCP_RELATIVE_FILE_PATH as Mt, RulesyncIgnore as N, RULESYNC_MCP_SCHEMA_URL as Nt, toolMcpFactories as O, RULESYNC_HOOKS_RELATIVE_FILE_PATH as Ot, HooksProcessor as P, RULESYNC_OVERVIEW_FILE_NAME as Pt, JsonLogger as Q, CommandsProcessor as R, RULESYNC_RULES_RELATIVE_DIR_PATH as Rt, PermissionsProcessor as S, RULESYNC_AIIGNORE_RELATIVE_FILE_PATH as St, RulesyncPermissions as T, RULESYNC_CONFIG_SCHEMA_URL as Tt, CLAUDECODE_MEMORIES_DIR_NAME as U, ALL_FEATURES as Ut, CLAUDECODE_DIR as V, RULESYNC_SUBAGENTS_RELATIVE_DIR_PATH as Vt, CLAUDECODE_SETTINGS_LOCAL_FILE_NAME as W, ALL_FEATURES_WITH_WILDCARD as Wt, findControlCharacter as X, ConfigResolver as Y, ConsoleLogger as Z, toolSkillFactories as _, ALL_TOOL_TARGETS as _t, RulesProcessor as a, fileExists as at, RulesyncSkillFrontmatterSchema as b, MAX_FILE_SIZE as bt, RulesyncRule as c, getHomeDirectory as ct, SubagentsProcessorToolTargetSchema as d, readFileContent as dt, ErrorCodes as et, toolSubagentFactories as f, removeDirectory as ft, SkillsProcessorToolTargetSchema as g, writeFileContent as gt, SkillsProcessor as h, toPosixPath as ht, convertFromTool as i, ensureDir as it, IgnoreProcessorToolTargetSchema as j, RULESYNC_MCP_FILE_NAME as jt, RulesyncMcp as k, RULESYNC_IGNORE_RELATIVE_FILE_PATH as kt, RulesyncRuleFrontmatterSchema as l, isSymlink as lt, RulesyncSubagentFrontmatterSchema as m, removeTempDirectory as mt, checkRulesyncDirExists as n, createTempDirectory as nt, RulesProcessorToolTargetSchema as o, findFilesByGlobs as ot, RulesyncSubagent as p, removeFile as pt, RulesyncCommandFrontmatterSchema as q, generate as r, directoryExists as rt, toolRuleFactories as s, getFileSize as st, importFromTool as t, checkPathTraversal as tt, SubagentsProcessor as u, listDirectoryFiles as ut, getLocalSkillDirNames as v, ALL_TOOL_TARGETS_WITH_WILDCARD as vt, toolPermissionsFactories as w, RULESYNC_CONFIG_RELATIVE_FILE_PATH as wt, SKILL_FILE_NAME as x, RULESYNC_AIIGNORE_FILE_NAME as xt, RulesyncSkill as y, ToolTargetSchema as yt, CommandsProcessorToolTargetSchema as z, RULESYNC_SKILLS_RELATIVE_DIR_PATH as zt };
|
|
34796
37016
|
|
|
34797
|
-
//# sourceMappingURL=import-
|
|
37017
|
+
//# sourceMappingURL=import-ylOvQDpE.js.map
|