rulesync 16.0.0 → 16.2.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 +7 -7
- package/dist/cli/index.cjs +298 -5
- package/dist/cli/index.js +299 -5
- package/dist/cli/index.js.map +1 -1
- package/dist/{import-BT5KR_K-.js → import-CArKOPG_.js} +2435 -504
- package/dist/import-CArKOPG_.js.map +1 -0
- package/dist/{import-Dyo6JaHg.cjs → import-DNfxS6QZ.cjs} +2448 -517
- package/dist/index.cjs +1 -1
- package/dist/index.d.cts +5 -1
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.ts +5 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/package.json +1 -1
- package/dist/import-BT5KR_K-.js.map +0 -1
|
@@ -127,27 +127,27 @@ const isFeatureValueEnabled = (value) => {
|
|
|
127
127
|
};
|
|
128
128
|
//#endregion
|
|
129
129
|
//#region src/constants/rulesync-paths.ts
|
|
130
|
-
const { join: join$
|
|
130
|
+
const { join: join$281 } = node_path.posix;
|
|
131
131
|
const RULESYNC_CONFIG_RELATIVE_FILE_PATH = "rulesync.jsonc";
|
|
132
132
|
const RULESYNC_LOCAL_CONFIG_RELATIVE_FILE_PATH = "rulesync.local.jsonc";
|
|
133
133
|
const RULESYNC_RELATIVE_DIR_PATH = ".rulesync";
|
|
134
|
-
const RULESYNC_RULES_RELATIVE_DIR_PATH = join$
|
|
135
|
-
const RULESYNC_CURATED_RULES_RELATIVE_DIR_PATH = join$
|
|
136
|
-
const RULESYNC_COMMANDS_RELATIVE_DIR_PATH = join$
|
|
137
|
-
const RULESYNC_SUBAGENTS_RELATIVE_DIR_PATH = join$
|
|
138
|
-
const RULESYNC_CHECKS_RELATIVE_DIR_PATH = join$
|
|
139
|
-
const RULESYNC_MCP_RELATIVE_FILE_PATH = join$
|
|
140
|
-
const RULESYNC_HOOKS_RELATIVE_FILE_PATH = join$
|
|
141
|
-
const RULESYNC_PERMISSIONS_RELATIVE_FILE_PATH = join$
|
|
142
|
-
join$
|
|
143
|
-
const RULESYNC_HOOKS_LEGACY_RELATIVE_FILE_PATH = join$
|
|
144
|
-
const RULESYNC_PERMISSIONS_LEGACY_RELATIVE_FILE_PATH = join$
|
|
134
|
+
const RULESYNC_RULES_RELATIVE_DIR_PATH = join$281(RULESYNC_RELATIVE_DIR_PATH, "rules");
|
|
135
|
+
const RULESYNC_CURATED_RULES_RELATIVE_DIR_PATH = join$281(RULESYNC_RULES_RELATIVE_DIR_PATH, ".curated");
|
|
136
|
+
const RULESYNC_COMMANDS_RELATIVE_DIR_PATH = join$281(RULESYNC_RELATIVE_DIR_PATH, "commands");
|
|
137
|
+
const RULESYNC_SUBAGENTS_RELATIVE_DIR_PATH = join$281(RULESYNC_RELATIVE_DIR_PATH, "subagents");
|
|
138
|
+
const RULESYNC_CHECKS_RELATIVE_DIR_PATH = join$281(RULESYNC_RELATIVE_DIR_PATH, "checks");
|
|
139
|
+
const RULESYNC_MCP_RELATIVE_FILE_PATH = join$281(RULESYNC_RELATIVE_DIR_PATH, "mcp.jsonc");
|
|
140
|
+
const RULESYNC_HOOKS_RELATIVE_FILE_PATH = join$281(RULESYNC_RELATIVE_DIR_PATH, "hooks.jsonc");
|
|
141
|
+
const RULESYNC_PERMISSIONS_RELATIVE_FILE_PATH = join$281(RULESYNC_RELATIVE_DIR_PATH, "permissions.jsonc");
|
|
142
|
+
join$281(RULESYNC_RELATIVE_DIR_PATH, "mcp.json");
|
|
143
|
+
const RULESYNC_HOOKS_LEGACY_RELATIVE_FILE_PATH = join$281(RULESYNC_RELATIVE_DIR_PATH, "hooks.json");
|
|
144
|
+
const RULESYNC_PERMISSIONS_LEGACY_RELATIVE_FILE_PATH = join$281(RULESYNC_RELATIVE_DIR_PATH, "permissions.json");
|
|
145
145
|
const RULESYNC_AIIGNORE_FILE_NAME = ".aiignore";
|
|
146
|
-
const RULESYNC_AIIGNORE_RELATIVE_FILE_PATH = join$
|
|
146
|
+
const RULESYNC_AIIGNORE_RELATIVE_FILE_PATH = join$281(RULESYNC_RELATIVE_DIR_PATH, ".aiignore");
|
|
147
147
|
const RULESYNC_IGNORE_RELATIVE_FILE_PATH = ".rulesyncignore";
|
|
148
148
|
const RULESYNC_OVERVIEW_FILE_NAME = "overview.md";
|
|
149
|
-
const RULESYNC_SKILLS_RELATIVE_DIR_PATH = join$
|
|
150
|
-
const RULESYNC_CURATED_SKILLS_RELATIVE_DIR_PATH = join$
|
|
149
|
+
const RULESYNC_SKILLS_RELATIVE_DIR_PATH = join$281(RULESYNC_RELATIVE_DIR_PATH, "skills");
|
|
150
|
+
const RULESYNC_CURATED_SKILLS_RELATIVE_DIR_PATH = join$281(RULESYNC_SKILLS_RELATIVE_DIR_PATH, ".curated");
|
|
151
151
|
const RULESYNC_SOURCES_LOCK_RELATIVE_FILE_PATH = "rulesync.lock";
|
|
152
152
|
const RULESYNC_NPM_SOURCES_LOCK_RELATIVE_FILE_PATH = "rulesync-npm.lock.json";
|
|
153
153
|
const RULESYNC_MCP_FILE_NAME = "mcp.jsonc";
|
|
@@ -217,6 +217,7 @@ const ignoreProcessorToolTargetTuple = [
|
|
|
217
217
|
"kiro-cli",
|
|
218
218
|
"kiro-ide",
|
|
219
219
|
"qwencode",
|
|
220
|
+
"reasonix",
|
|
220
221
|
"roo",
|
|
221
222
|
"devin",
|
|
222
223
|
"vibe",
|
|
@@ -274,6 +275,7 @@ const commandsProcessorToolTargetTuple = [
|
|
|
274
275
|
"cursor",
|
|
275
276
|
"factorydroid",
|
|
276
277
|
"goose",
|
|
278
|
+
"grokcli",
|
|
277
279
|
"hermesagent",
|
|
278
280
|
"junie",
|
|
279
281
|
"kilo",
|
|
@@ -293,6 +295,9 @@ const commandsProcessorToolTargetTuple = [
|
|
|
293
295
|
const subagentsProcessorToolTargetTuple = [
|
|
294
296
|
"kilo",
|
|
295
297
|
"agentsmd",
|
|
298
|
+
"antigravity-cli",
|
|
299
|
+
"antigravity-ide",
|
|
300
|
+
"antigravity-plugin",
|
|
296
301
|
"augmentcode",
|
|
297
302
|
"claudecode",
|
|
298
303
|
"claudecode-plugin",
|
|
@@ -424,7 +429,9 @@ const permissionsProcessorToolTargetTuple = [
|
|
|
424
429
|
];
|
|
425
430
|
const checksProcessorToolTargetTuple = [
|
|
426
431
|
"amp",
|
|
432
|
+
"cursor",
|
|
427
433
|
"hermesagent",
|
|
434
|
+
"rovodev",
|
|
428
435
|
"takt"
|
|
429
436
|
];
|
|
430
437
|
//#endregion
|
|
@@ -1078,6 +1085,17 @@ const ConfigFileSchema = zod_mini.z.object({
|
|
|
1078
1085
|
});
|
|
1079
1086
|
zod_mini.z.required(ConfigParamsSchema);
|
|
1080
1087
|
/**
|
|
1088
|
+
* Normalizes the configuration file location to an absolute path.
|
|
1089
|
+
*
|
|
1090
|
+
* `ConfigResolver` always supplies the path it actually loaded; the fallback
|
|
1091
|
+
* only covers direct programmatic construction, where the conventional
|
|
1092
|
+
* location next to the input root is the best guess.
|
|
1093
|
+
*/
|
|
1094
|
+
function normalizeConfigFilePath({ configFilePath, inputRoot }) {
|
|
1095
|
+
if (configFilePath === void 0) return (0, node_path.join)(inputRoot, RULESYNC_CONFIG_RELATIVE_FILE_PATH);
|
|
1096
|
+
return (0, node_path.isAbsolute)(configFilePath) ? configFilePath : (0, node_path.resolve)(configFilePath);
|
|
1097
|
+
}
|
|
1098
|
+
/**
|
|
1081
1099
|
* Conflicting target pairs that cannot be used together
|
|
1082
1100
|
*/
|
|
1083
1101
|
const CONFLICTING_TARGET_PAIRS = [["augmentcode", "augmentcode-legacy"], ["claudecode", "claudecode-legacy"]];
|
|
@@ -1148,8 +1166,9 @@ var Config = class Config {
|
|
|
1148
1166
|
dryRun;
|
|
1149
1167
|
check;
|
|
1150
1168
|
inputRoot;
|
|
1169
|
+
configFilePath;
|
|
1151
1170
|
sources;
|
|
1152
|
-
constructor({ outputRoots, targets, features, verbose, delete: isDelete, global, silent, simulateCommands, simulateSubagents, simulateSkills, flattenedCommandNaming, gitignoreTargetsOnly, gitignoreDestination, dryRun, check, inputRoot, sources, configFileTargets }) {
|
|
1171
|
+
constructor({ outputRoots, targets, features, verbose, delete: isDelete, global, silent, simulateCommands, simulateSubagents, simulateSkills, flattenedCommandNaming, gitignoreTargetsOnly, gitignoreDestination, dryRun, check, inputRoot, configFilePath, sources, configFileTargets }) {
|
|
1153
1172
|
assertTargetsFeaturesExclusive({
|
|
1154
1173
|
targets,
|
|
1155
1174
|
features
|
|
@@ -1182,6 +1201,10 @@ var Config = class Config {
|
|
|
1182
1201
|
this.dryRun = dryRun ?? false;
|
|
1183
1202
|
this.check = check ?? false;
|
|
1184
1203
|
this.inputRoot = inputRoot === void 0 ? process.cwd() : (0, node_path.isAbsolute)(inputRoot) ? inputRoot : (0, node_path.resolve)(inputRoot);
|
|
1204
|
+
this.configFilePath = normalizeConfigFilePath({
|
|
1205
|
+
configFilePath,
|
|
1206
|
+
inputRoot: this.inputRoot
|
|
1207
|
+
});
|
|
1185
1208
|
this.sources = sources ?? [];
|
|
1186
1209
|
}
|
|
1187
1210
|
/**
|
|
@@ -1375,6 +1398,14 @@ var Config = class Config {
|
|
|
1375
1398
|
getInputRoot() {
|
|
1376
1399
|
return this.inputRoot;
|
|
1377
1400
|
}
|
|
1401
|
+
/**
|
|
1402
|
+
* Returns the absolute path of the configuration file this config was
|
|
1403
|
+
* resolved from. The file itself may not exist — `rulesync` runs fine
|
|
1404
|
+
* without one — so callers must treat this as a location, not a guarantee.
|
|
1405
|
+
*/
|
|
1406
|
+
getConfigFilePath() {
|
|
1407
|
+
return this.configFilePath;
|
|
1408
|
+
}
|
|
1378
1409
|
getSources() {
|
|
1379
1410
|
return this.sources;
|
|
1380
1411
|
}
|
|
@@ -1597,6 +1628,7 @@ var ConfigResolver = class {
|
|
|
1597
1628
|
fallback: getDefaults().check
|
|
1598
1629
|
}),
|
|
1599
1630
|
inputRoot: resolvedInputRoot !== void 0 ? (0, node_path.resolve)(resolvedInputRoot) : cwd,
|
|
1631
|
+
configFilePath: validatedConfigPath,
|
|
1600
1632
|
sources: configByFile.sources ?? getDefaults().sources,
|
|
1601
1633
|
flattenedCommandNaming: configByFile.flattenedCommandNaming ?? getDefaults().flattenedCommandNaming,
|
|
1602
1634
|
configFileTargets: extractConfigFileTargets(configByFile.targets)
|
|
@@ -2134,7 +2166,10 @@ const HookDefinitionSchema = zod_mini.z.looseObject({
|
|
|
2134
2166
|
model: zod_mini.z.optional(safeString),
|
|
2135
2167
|
args: zod_mini.z.optional(zod_mini.z.array(safeString)),
|
|
2136
2168
|
metadata: zod_mini.z.optional(zod_mini.z.looseObject({})),
|
|
2137
|
-
if: zod_mini.z.optional(safeString)
|
|
2169
|
+
if: zod_mini.z.optional(safeString),
|
|
2170
|
+
commandWindows: zod_mini.z.optional(safeString),
|
|
2171
|
+
asyncRewake: zod_mini.z.optional(zod_mini.z.boolean()),
|
|
2172
|
+
continueOnBlock: zod_mini.z.optional(zod_mini.z.boolean())
|
|
2138
2173
|
});
|
|
2139
2174
|
/**
|
|
2140
2175
|
* All canonical hook event names.
|
|
@@ -2188,6 +2223,7 @@ const HOOK_EVENTS = [
|
|
|
2188
2223
|
"configChange",
|
|
2189
2224
|
"cwdChanged",
|
|
2190
2225
|
"fileChanged",
|
|
2226
|
+
"directoryAdded",
|
|
2191
2227
|
"elicitation",
|
|
2192
2228
|
"elicitationResult"
|
|
2193
2229
|
];
|
|
@@ -2249,6 +2285,7 @@ const CLAUDE_HOOK_EVENTS = [
|
|
|
2249
2285
|
"configChange",
|
|
2250
2286
|
"cwdChanged",
|
|
2251
2287
|
"fileChanged",
|
|
2288
|
+
"directoryAdded",
|
|
2252
2289
|
"postCompact",
|
|
2253
2290
|
"elicitation",
|
|
2254
2291
|
"elicitationResult"
|
|
@@ -2281,7 +2318,15 @@ const DEVIN_HOOK_EVENTS = [
|
|
|
2281
2318
|
"permissionRequest",
|
|
2282
2319
|
"postCompact"
|
|
2283
2320
|
];
|
|
2284
|
-
/**
|
|
2321
|
+
/**
|
|
2322
|
+
* Hook events supported by OpenCode.
|
|
2323
|
+
*
|
|
2324
|
+
* `preCompact` maps to `experimental.session.compacting`, which the plugin docs
|
|
2325
|
+
* document as a named `(input, output)` hook rather than an `event.type`
|
|
2326
|
+
* dispatch; the other entries are all generic events.
|
|
2327
|
+
*
|
|
2328
|
+
* @see https://opencode.ai/docs/plugins/
|
|
2329
|
+
*/
|
|
2285
2330
|
const OPENCODE_HOOK_EVENTS = [
|
|
2286
2331
|
"sessionStart",
|
|
2287
2332
|
"preToolUse",
|
|
@@ -2289,9 +2334,19 @@ const OPENCODE_HOOK_EVENTS = [
|
|
|
2289
2334
|
"stop",
|
|
2290
2335
|
"afterFileEdit",
|
|
2291
2336
|
"afterShellExecution",
|
|
2292
|
-
"permissionRequest"
|
|
2337
|
+
"permissionRequest",
|
|
2338
|
+
"preCompact",
|
|
2339
|
+
"postCompact",
|
|
2340
|
+
"afterError",
|
|
2341
|
+
"fileChanged"
|
|
2293
2342
|
];
|
|
2294
|
-
/**
|
|
2343
|
+
/**
|
|
2344
|
+
* Hook events supported by Kilo. Identical to OpenCode: Kilo's plugin docs list
|
|
2345
|
+
* the same event surface, including `session.compacted`, `session.error`,
|
|
2346
|
+
* `file.watcher.updated` and the experimental compaction hook.
|
|
2347
|
+
*
|
|
2348
|
+
* @see https://kilo.ai/docs/automate/extending/plugins
|
|
2349
|
+
*/
|
|
2295
2350
|
const KILO_HOOK_EVENTS = OPENCODE_HOOK_EVENTS;
|
|
2296
2351
|
/**
|
|
2297
2352
|
* Hook events supported by Pi Coding Agent, bridged through a generated
|
|
@@ -2358,7 +2413,8 @@ const COPILOT_HOOK_EVENTS = [
|
|
|
2358
2413
|
* `sessionStart`, `sessionEnd`, `userPromptSubmitted`, `preToolUse`,
|
|
2359
2414
|
* `postToolUse`, `postToolUseFailure`, `agentStop`, `subagentStart`,
|
|
2360
2415
|
* `subagentStop`, `errorOccurred`, `preCompact`, `permissionRequest`,
|
|
2361
|
-
* `notification`, `
|
|
2416
|
+
* `notification`, `userPromptTransformed` ← `userPromptExpansion`,
|
|
2417
|
+
* `preMcpToolCall` ← `beforeMCPExecution`.
|
|
2362
2418
|
*
|
|
2363
2419
|
* `preMcpToolCall` (canonical `beforeMCPExecution`) was added in Copilot CLI
|
|
2364
2420
|
* v1.0.51 (2026-05-20) for hook providers to control outgoing MCP request
|
|
@@ -2380,6 +2436,7 @@ const COPILOTCLI_HOOK_EVENTS = [
|
|
|
2380
2436
|
"preCompact",
|
|
2381
2437
|
"permissionRequest",
|
|
2382
2438
|
"notification",
|
|
2439
|
+
"userPromptExpansion",
|
|
2383
2440
|
"beforeMCPExecution"
|
|
2384
2441
|
];
|
|
2385
2442
|
/**
|
|
@@ -2424,6 +2481,7 @@ const DEEPAGENTS_HOOK_EVENTS = [
|
|
|
2424
2481
|
/** Hook events supported by Codex CLI. */
|
|
2425
2482
|
const CODEXCLI_HOOK_EVENTS = [
|
|
2426
2483
|
"sessionStart",
|
|
2484
|
+
"sessionEnd",
|
|
2427
2485
|
"preToolUse",
|
|
2428
2486
|
"postToolUse",
|
|
2429
2487
|
"beforeSubmitPrompt",
|
|
@@ -2858,6 +2916,7 @@ const CANONICAL_TO_CLAUDE_EVENT_NAMES = {
|
|
|
2858
2916
|
configChange: "ConfigChange",
|
|
2859
2917
|
cwdChanged: "CwdChanged",
|
|
2860
2918
|
fileChanged: "FileChanged",
|
|
2919
|
+
directoryAdded: "DirectoryAdded",
|
|
2861
2920
|
postCompact: "PostCompact",
|
|
2862
2921
|
elicitation: "Elicitation",
|
|
2863
2922
|
elicitationResult: "ElicitationResult"
|
|
@@ -2979,7 +3038,11 @@ const CANONICAL_TO_OPENCODE_EVENT_NAMES = {
|
|
|
2979
3038
|
stop: "session.idle",
|
|
2980
3039
|
afterFileEdit: "file.edited",
|
|
2981
3040
|
afterShellExecution: "command.executed",
|
|
2982
|
-
permissionRequest: "permission.asked"
|
|
3041
|
+
permissionRequest: "permission.asked",
|
|
3042
|
+
preCompact: "experimental.session.compacting",
|
|
3043
|
+
postCompact: "session.compacted",
|
|
3044
|
+
afterError: "session.error",
|
|
3045
|
+
fileChanged: "file.watcher.updated"
|
|
2983
3046
|
};
|
|
2984
3047
|
/**
|
|
2985
3048
|
* Map canonical camelCase event names to Kilo dot-notation.
|
|
@@ -3055,6 +3118,7 @@ const CANONICAL_TO_COPILOTCLI_EVENT_NAMES = {
|
|
|
3055
3118
|
preCompact: "preCompact",
|
|
3056
3119
|
permissionRequest: "permissionRequest",
|
|
3057
3120
|
notification: "notification",
|
|
3121
|
+
userPromptExpansion: "userPromptTransformed",
|
|
3058
3122
|
beforeMCPExecution: "preMcpToolCall"
|
|
3059
3123
|
};
|
|
3060
3124
|
/** Map GitHub Copilot CLI event names back to canonical camelCase. */
|
|
@@ -3064,6 +3128,7 @@ const COPILOTCLI_TO_CANONICAL_EVENT_NAMES = Object.fromEntries(Object.entries(CA
|
|
|
3064
3128
|
*/
|
|
3065
3129
|
const CANONICAL_TO_CODEXCLI_EVENT_NAMES = {
|
|
3066
3130
|
sessionStart: "SessionStart",
|
|
3131
|
+
sessionEnd: "SessionEnd",
|
|
3067
3132
|
preToolUse: "PreToolUse",
|
|
3068
3133
|
postToolUse: "PostToolUse",
|
|
3069
3134
|
beforeSubmitPrompt: "UserPromptSubmit",
|
|
@@ -3486,6 +3551,25 @@ var RulesyncIgnore = class RulesyncIgnore extends RulesyncFile {
|
|
|
3486
3551
|
//#endregion
|
|
3487
3552
|
//#region src/types/mcp.ts
|
|
3488
3553
|
const EnvVarNameSchema = zod_mini.z.string().check((0, zod_mini.refine)((value) => /^[A-Za-z_][A-Za-z0-9_]*$/.test(value), "envVars entries must be valid environment variable names"));
|
|
3554
|
+
/**
|
|
3555
|
+
* One `envVars` entry. A bare name reads the variable from Codex's own
|
|
3556
|
+
* environment; the object form names the environment to read it from, where
|
|
3557
|
+
* `source = "remote"` reads from the remote executor environment.
|
|
3558
|
+
* @see https://learn.chatgpt.com/docs/extend/mcp
|
|
3559
|
+
*/
|
|
3560
|
+
const EnvVarEntrySchema = zod_mini.z.union([EnvVarNameSchema, zod_mini.z.strictObject({
|
|
3561
|
+
name: EnvVarNameSchema,
|
|
3562
|
+
source: zod_mini.z.optional(zod_mini.z.enum(["local", "remote"]))
|
|
3563
|
+
})]);
|
|
3564
|
+
/**
|
|
3565
|
+
* Whether a value is usable as `envVars`. Applied in both directions by the
|
|
3566
|
+
* codex adapter, so an entry read out of somebody's `config.toml` can never be
|
|
3567
|
+
* imported into a `.rulesync/mcp.jsonc` that the next generate would refuse to
|
|
3568
|
+
* parse.
|
|
3569
|
+
*/
|
|
3570
|
+
function isEnvVarEntryArray(value) {
|
|
3571
|
+
return Array.isArray(value) && value.every((entry) => EnvVarEntrySchema.safeParse(entry).success);
|
|
3572
|
+
}
|
|
3489
3573
|
const McpServerSchema = zod_mini.z.looseObject({
|
|
3490
3574
|
type: zod_mini.z.optional(zod_mini.z.enum([
|
|
3491
3575
|
"local",
|
|
@@ -3500,7 +3584,8 @@ const McpServerSchema = zod_mini.z.looseObject({
|
|
|
3500
3584
|
url: zod_mini.z.optional(zod_mini.z.string()),
|
|
3501
3585
|
httpUrl: zod_mini.z.optional(zod_mini.z.string()),
|
|
3502
3586
|
env: zod_mini.z.optional(zod_mini.z.record(zod_mini.z.string(), zod_mini.z.string())),
|
|
3503
|
-
envVars: zod_mini.z.optional(zod_mini.z.array(
|
|
3587
|
+
envVars: zod_mini.z.optional(zod_mini.z.array(EnvVarEntrySchema)),
|
|
3588
|
+
experimentalEnvironment: zod_mini.z.optional(zod_mini.z.string()),
|
|
3504
3589
|
disabled: zod_mini.z.optional(zod_mini.z.boolean()),
|
|
3505
3590
|
networkTimeout: zod_mini.z.optional(zod_mini.z.number()),
|
|
3506
3591
|
timeout: zod_mini.z.optional(zod_mini.z.number()),
|
|
@@ -3666,6 +3751,8 @@ var RulesyncMcp = class RulesyncMcp extends RulesyncFile {
|
|
|
3666
3751
|
"description",
|
|
3667
3752
|
"exposed",
|
|
3668
3753
|
"envVars",
|
|
3754
|
+
"experimentalEnvironment",
|
|
3755
|
+
"experimental_environment",
|
|
3669
3756
|
"enabled"
|
|
3670
3757
|
])];
|
|
3671
3758
|
}));
|
|
@@ -3952,10 +4039,30 @@ const ClinePermissionsOverrideSchema = zod_mini.z.looseObject({
|
|
|
3952
4039
|
* portable and keeps them out of other tools' configs. Mirrors the OpenCode
|
|
3953
4040
|
* override; each value may be a bare action string or a pattern map.
|
|
3954
4041
|
*
|
|
4042
|
+
* `sandbox` is the sibling top-level block that governs the sandbox Kilo runs
|
|
4043
|
+
* commands in: `enabled` (boolean), `network` (`"deny"` and friends),
|
|
4044
|
+
* `allowed_hosts` (a list of `host` / `host:port` destination exceptions) and
|
|
4045
|
+
* `writable_paths`. It has no canonical permission category, so it is authored
|
|
4046
|
+
* here and emitted only for Kilo.
|
|
4047
|
+
*
|
|
4048
|
+
* Upstream restricts what a *project* config may say: `allowed_hosts` and
|
|
4049
|
+
* `writable_paths` are honored from the global config only, and a project
|
|
4050
|
+
* config may merely tighten (`enabled: true`, `network: "deny"`) — a
|
|
4051
|
+
* project-level network denial even clears the global destination exceptions.
|
|
4052
|
+
* rulesync mirrors that: at project scope only `enabled` and `network` are
|
|
4053
|
+
* written, and the rest are dropped with a warning rather than emitted into a
|
|
4054
|
+
* file Kilo would ignore.
|
|
4055
|
+
*
|
|
3955
4056
|
* @example
|
|
3956
4057
|
* { "permission": { "external_directory": "deny", "doom_loop": "ask" } }
|
|
4058
|
+
* @example
|
|
4059
|
+
* { "sandbox": { "enabled": true, "network": "deny" } }
|
|
4060
|
+
* @see https://kilo.ai/docs/getting-started/settings/sandboxing
|
|
3957
4061
|
*/
|
|
3958
|
-
const KiloPermissionsOverrideSchema = zod_mini.z.looseObject({
|
|
4062
|
+
const KiloPermissionsOverrideSchema = zod_mini.z.looseObject({
|
|
4063
|
+
permission: zod_mini.z.optional(zod_mini.z.record(zod_mini.z.string(), OpencodeOverridePermissionValueSchema)),
|
|
4064
|
+
sandbox: zod_mini.z.optional(zod_mini.z.looseObject({}))
|
|
4065
|
+
});
|
|
3959
4066
|
/**
|
|
3960
4067
|
* Tool-scoped override block for Claude Code. Claude Code's `permissions` object
|
|
3961
4068
|
* (in `.claude/settings.json`) carries non-list fields that have no canonical
|
|
@@ -3968,12 +4075,23 @@ const KiloPermissionsOverrideSchema = zod_mini.z.looseObject({ permission: zod_m
|
|
|
3968
4075
|
* authored without modeling each one; the managed `allow`/`ask`/`deny` arrays are
|
|
3969
4076
|
* ignored here (rulesync owns them).
|
|
3970
4077
|
*
|
|
4078
|
+
* `sandbox` is the sibling top-level settings subtree that governs the sandbox
|
|
4079
|
+
* Claude Code runs commands in (`sandbox.network.*`, `sandbox.filesystem.*`,
|
|
4080
|
+
* `sandbox.credentials`, `sandbox.allowAppleEvents`, ...). It has no canonical
|
|
4081
|
+
* permission category either — it constrains how a permitted command runs
|
|
4082
|
+
* rather than which commands are permitted — so it is a loose passthrough on
|
|
4083
|
+
* the same terms, merged into the top level of `.claude/settings.json`.
|
|
4084
|
+
*
|
|
3971
4085
|
* @example
|
|
3972
4086
|
* { "permissions": { "defaultMode": "acceptEdits", "additionalDirectories": ["../shared"] } }
|
|
4087
|
+
* @example
|
|
4088
|
+
* { "sandbox": { "network": { "allowedDomains": ["example.com"], "strictAllowlist": true } } }
|
|
4089
|
+
* @see https://code.claude.com/docs/en/sandboxing
|
|
3973
4090
|
*/
|
|
3974
4091
|
const ClaudecodePermissionsOverrideSchema = zod_mini.z.looseObject({
|
|
3975
4092
|
permission: zod_mini.z.optional(ToolScopedPermissionSchema),
|
|
3976
|
-
permissions: zod_mini.z.optional(zod_mini.z.looseObject({}))
|
|
4093
|
+
permissions: zod_mini.z.optional(zod_mini.z.looseObject({})),
|
|
4094
|
+
sandbox: zod_mini.z.optional(zod_mini.z.looseObject({}))
|
|
3977
4095
|
});
|
|
3978
4096
|
/**
|
|
3979
4097
|
* Tool-scoped override block for Mistral Vibe. Vibe's per-tool `BaseToolConfig`
|
|
@@ -4246,19 +4364,24 @@ const AmpPermissionsOverrideSchema = zod_mini.z.looseObject({
|
|
|
4246
4364
|
});
|
|
4247
4365
|
/**
|
|
4248
4366
|
* Tool-scoped override block for the Google Antigravity CLI. Antigravity's CLI
|
|
4249
|
-
* `settings.json` carries
|
|
4367
|
+
* `settings.json` carries four global autonomy/sandbox knobs outside the
|
|
4250
4368
|
* `permissions.allow/ask/deny` arrays rulesync manages: `toolPermission` (the
|
|
4251
4369
|
* global autonomy preset — `request-review` (default) / `proceed-in-sandbox` /
|
|
4252
|
-
* `always-proceed` / `strict`)
|
|
4253
|
-
* agent-run commands to OS containment)
|
|
4370
|
+
* `always-proceed` / `strict`), `enableTerminalSandbox` (a boolean confining
|
|
4371
|
+
* agent-run commands to OS containment), `artifactReviewPolicy` (whether the
|
|
4372
|
+
* agent's artifact changes are gated on a review prompt — `asks-for-review`
|
|
4373
|
+
* (default) / `agent-decides` / `always-proceed`) and `allowNonWorkspaceAccess`
|
|
4374
|
+
* (a boolean, off by default, letting the agent read or write files outside the
|
|
4375
|
+
* active workspace roots). Antigravity applies the allow/deny
|
|
4254
4376
|
* lists as per-rule exceptions to the preset at runtime, so rulesync only
|
|
4255
4377
|
* authors these keys verbatim — no precedence modeling is needed on our side.
|
|
4256
4378
|
* Fields placed here are merged onto the top level of
|
|
4257
4379
|
* `~/.gemini/antigravity-cli/settings.json` (global-only) and emitted only for
|
|
4258
4380
|
* the CLI. The Antigravity IDE exposes the same concepts through a GUI (no
|
|
4259
4381
|
* documented JSON schema), so this override does NOT apply to `antigravity-ide`.
|
|
4260
|
-
* Verified against https://antigravity.google/docs/cli/reference
|
|
4261
|
-
* https://antigravity.google/docs/cli/sandbox
|
|
4382
|
+
* Verified against https://antigravity.google/docs/cli/reference,
|
|
4383
|
+
* https://antigravity.google/docs/cli/sandbox and
|
|
4384
|
+
* https://antigravity.google/docs/cli/settings.
|
|
4262
4385
|
*
|
|
4263
4386
|
* @example
|
|
4264
4387
|
* { "toolPermission": "strict", "enableTerminalSandbox": true }
|
|
@@ -4271,7 +4394,13 @@ const AntigravityCliPermissionsOverrideSchema = zod_mini.z.looseObject({
|
|
|
4271
4394
|
"always-proceed",
|
|
4272
4395
|
"strict"
|
|
4273
4396
|
])),
|
|
4274
|
-
enableTerminalSandbox: zod_mini.z.optional(zod_mini.z.boolean())
|
|
4397
|
+
enableTerminalSandbox: zod_mini.z.optional(zod_mini.z.boolean()),
|
|
4398
|
+
artifactReviewPolicy: zod_mini.z.optional(zod_mini.z.enum([
|
|
4399
|
+
"asks-for-review",
|
|
4400
|
+
"agent-decides",
|
|
4401
|
+
"always-proceed"
|
|
4402
|
+
])),
|
|
4403
|
+
allowNonWorkspaceAccess: zod_mini.z.optional(zod_mini.z.boolean())
|
|
4275
4404
|
});
|
|
4276
4405
|
/**
|
|
4277
4406
|
* Tool-scoped override block for AugmentCode. AugmentCode's `toolPermissions[]`
|
|
@@ -4675,11 +4804,14 @@ const RulesyncRuleFrontmatterSchema = zod_mini.z.object({
|
|
|
4675
4804
|
description: zod_mini.z.optional(zod_mini.z.string()),
|
|
4676
4805
|
globs: zod_mini.z.optional(zod_mini.z.array(zod_mini.z.string()))
|
|
4677
4806
|
})),
|
|
4678
|
-
copilot: zod_mini.z.optional(zod_mini.z.looseObject({
|
|
4679
|
-
zod_mini.z.
|
|
4680
|
-
|
|
4681
|
-
|
|
4682
|
-
|
|
4807
|
+
copilot: zod_mini.z.optional(zod_mini.z.looseObject({
|
|
4808
|
+
excludeAgent: zod_mini.z.optional(zod_mini.z.union([
|
|
4809
|
+
zod_mini.z.literal("code-review"),
|
|
4810
|
+
zod_mini.z.literal("cloud-agent"),
|
|
4811
|
+
zod_mini.z.literal("coding-agent")
|
|
4812
|
+
])),
|
|
4813
|
+
name: zod_mini.z.optional(zod_mini.z.string())
|
|
4814
|
+
})),
|
|
4683
4815
|
antigravity: zod_mini.z.optional(zod_mini.z.looseObject({
|
|
4684
4816
|
trigger: zod_mini.z.optional(zod_mini.z.string()),
|
|
4685
4817
|
globs: zod_mini.z.optional(zod_mini.z.array(zod_mini.z.string()))
|
|
@@ -4887,6 +5019,7 @@ const RulesyncSkillFrontmatterSchema = zod_mini.z.looseObject({
|
|
|
4887
5019
|
arguments: zod_mini.z.optional(zod_mini.z.union([zod_mini.z.string(), zod_mini.z.array(zod_mini.z.string())])),
|
|
4888
5020
|
context: zod_mini.z.optional(zod_mini.z.string()),
|
|
4889
5021
|
agent: zod_mini.z.optional(zod_mini.z.string()),
|
|
5022
|
+
background: zod_mini.z.optional(zod_mini.z.boolean()),
|
|
4890
5023
|
hooks: zod_mini.z.optional(zod_mini.z.looseObject({})),
|
|
4891
5024
|
shell: zod_mini.z.optional(zod_mini.z.string()),
|
|
4892
5025
|
"disable-model-invocation": zod_mini.z.optional(zod_mini.z.boolean()),
|
|
@@ -4938,7 +5071,9 @@ const RulesyncSkillFrontmatterSchema = zod_mini.z.looseObject({
|
|
|
4938
5071
|
copilotcli: zod_mini.z.optional(zod_mini.z.looseObject({
|
|
4939
5072
|
license: zod_mini.z.optional(zod_mini.z.string()),
|
|
4940
5073
|
"allowed-tools": zod_mini.z.optional(zod_mini.z.union([zod_mini.z.string(), zod_mini.z.array(zod_mini.z.string())])),
|
|
4941
|
-
"argument-hint": zod_mini.z.optional(zod_mini.z.string())
|
|
5074
|
+
"argument-hint": zod_mini.z.optional(zod_mini.z.string()),
|
|
5075
|
+
"user-invocable": zod_mini.z.optional(zod_mini.z.boolean()),
|
|
5076
|
+
"disable-model-invocation": zod_mini.z.optional(zod_mini.z.boolean())
|
|
4942
5077
|
})),
|
|
4943
5078
|
pi: zod_mini.z.optional(zod_mini.z.looseObject({
|
|
4944
5079
|
"allowed-tools": zod_mini.z.optional(zod_mini.z.union([zod_mini.z.string(), zod_mini.z.array(zod_mini.z.string())])),
|
|
@@ -5224,6 +5359,38 @@ function resolveUserInvocable({ rootFrontmatter, section }) {
|
|
|
5224
5359
|
return section?.["user-invocable"] ?? rootFrontmatter["user-invocable"];
|
|
5225
5360
|
}
|
|
5226
5361
|
//#endregion
|
|
5362
|
+
//#region src/constants/cursor-paths.ts
|
|
5363
|
+
const CURSOR_DIR = ".cursor";
|
|
5364
|
+
const CURSOR_COMMANDS_DIR_PATH = (0, node_path.join)(CURSOR_DIR, "commands");
|
|
5365
|
+
const CURSOR_SKILLS_DIR_PATH = (0, node_path.join)(CURSOR_DIR, "skills");
|
|
5366
|
+
const CURSOR_AGENTS_DIR_PATH = (0, node_path.join)(CURSOR_DIR, "agents");
|
|
5367
|
+
const CURSOR_BUGBOT_FILE_NAME = "BUGBOT.md";
|
|
5368
|
+
const CURSOR_MCP_FILE_NAME = "mcp.json";
|
|
5369
|
+
const CURSOR_HOOKS_FILE_NAME = "hooks.json";
|
|
5370
|
+
const CURSOR_IGNORE_FILE_NAME = ".cursorignore";
|
|
5371
|
+
const CURSOR_PERMISSIONS_FILE_NAME = "cli.json";
|
|
5372
|
+
const CURSOR_PERMISSIONS_GLOBAL_FILE_NAME = "cli-config.json";
|
|
5373
|
+
//#endregion
|
|
5374
|
+
//#region src/constants/rovodev-paths.ts
|
|
5375
|
+
const ROVODEV_DIR = ".rovodev";
|
|
5376
|
+
const ROVODEV_SKILLS_DIR_PATH = (0, node_path.join)(ROVODEV_DIR, "skills");
|
|
5377
|
+
const ROVODEV_SUBAGENTS_DIR_PATH = (0, node_path.join)(ROVODEV_DIR, "subagents");
|
|
5378
|
+
const ROVODEV_MODULAR_RULES_DIR_PATH = (0, node_path.join)(ROVODEV_DIR, ".rulesync", "modular-rules");
|
|
5379
|
+
const ROVODEV_RULE_FILE_NAME = "AGENTS.md";
|
|
5380
|
+
const ROVODEV_LEGACY_RULE_FILE_NAME = "AGENTS.local.md";
|
|
5381
|
+
const ROVODEV_MCP_FILE_NAME = "mcp.json";
|
|
5382
|
+
const ROVODEV_CONFIG_FILE_NAME = "config.yml";
|
|
5383
|
+
const ROVODEV_AGENTS_SKILLS_DIR_PATH = (0, node_path.join)(".agents", "skills");
|
|
5384
|
+
const ROVODEV_PROMPTS_FILE_NAME = "prompts.yml";
|
|
5385
|
+
const ROVODEV_PROMPTS_DIR_PATH = (0, node_path.join)(ROVODEV_DIR, "prompts");
|
|
5386
|
+
/**
|
|
5387
|
+
* Custom instructions for Rovo Dev's code reviews: a plain-Markdown file (no
|
|
5388
|
+
* frontmatter) in the repository root's `.rovodev/` folder. Note the leading
|
|
5389
|
+
* dot in the file name.
|
|
5390
|
+
* @see https://support.atlassian.com/rovo/docs/set-custom-instructions-for-code-reviews/
|
|
5391
|
+
*/
|
|
5392
|
+
const ROVODEV_REVIEW_AGENT_FILE_NAME = ".review-agent.md";
|
|
5393
|
+
//#endregion
|
|
5227
5394
|
//#region src/constants/takt-paths.ts
|
|
5228
5395
|
const TAKT_DIR = ".takt";
|
|
5229
5396
|
const TAKT_FACETS_SUBDIR = "facets";
|
|
@@ -5674,6 +5841,276 @@ var AmpCheck = class AmpCheck extends ToolCheck {
|
|
|
5674
5841
|
}
|
|
5675
5842
|
};
|
|
5676
5843
|
//#endregion
|
|
5844
|
+
//#region src/features/checks/check-slug.ts
|
|
5845
|
+
/**
|
|
5846
|
+
* Turn a name that came out of a tool's own config file into one safe to use as
|
|
5847
|
+
* a `.rulesync/checks/<name>.md` file name. Shared by the adapters whose checks
|
|
5848
|
+
* collapse into a single file, since the name they read back is whatever the
|
|
5849
|
+
* user wrote there.
|
|
5850
|
+
*/
|
|
5851
|
+
function slugifyCheckName(value) {
|
|
5852
|
+
return value.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 48).replace(/-+$/, "");
|
|
5853
|
+
}
|
|
5854
|
+
//#endregion
|
|
5855
|
+
//#region src/features/checks/aggregated-check-file.ts
|
|
5856
|
+
/**
|
|
5857
|
+
* Shared machinery for the tools whose checks surface is **one aggregated
|
|
5858
|
+
* instruction file** rather than a file per check — Cursor Bugbot's
|
|
5859
|
+
* `.cursor/BUGBOT.md` and Rovo Dev's `.rovodev/.review-agent.md`.
|
|
5860
|
+
*
|
|
5861
|
+
* Both read the file as free prose, so the check identities have to be carried
|
|
5862
|
+
* in something invisible to the reader: an HTML-comment marker per section.
|
|
5863
|
+
* That marker convention, the escaping that keeps a check body from splitting
|
|
5864
|
+
* itself, and the import-side split are the same for both files, so they live
|
|
5865
|
+
* here once.
|
|
5866
|
+
*/
|
|
5867
|
+
/** Marks where one check starts inside the single instruction file. */
|
|
5868
|
+
const CHECK_MARKER_PATTERN = /^<!--\s*rulesync:check:(.+?)\s*-->[ \t]*$/gm;
|
|
5869
|
+
/**
|
|
5870
|
+
* A marker line a check body wrote itself — a rulesync doc fragment quoted in a
|
|
5871
|
+
* code block, say. Emitting it verbatim would split that check in two on the
|
|
5872
|
+
* next import, so `literal-` is inserted before `check:` on the way out and
|
|
5873
|
+
* taken off on the way back. `(?:literal-)*` makes it a ladder, so a body that
|
|
5874
|
+
* already contains an escaped marker survives the round trip too.
|
|
5875
|
+
*/
|
|
5876
|
+
const ESCAPABLE_MARKER_PATTERN = /^(<!--\s*rulesync:)((?:literal-)*check:.+?\s*-->[ \t]*)$/gm;
|
|
5877
|
+
const ESCAPED_MARKER_PATTERN = /^(<!--\s*rulesync:)literal-((?:literal-)*check:.+?\s*-->[ \t]*)$/gm;
|
|
5878
|
+
function renderCheckMarker(name) {
|
|
5879
|
+
return `<!-- rulesync:check:${name} -->`;
|
|
5880
|
+
}
|
|
5881
|
+
function escapeCheckMarkers(content) {
|
|
5882
|
+
return content.replace(ESCAPABLE_MARKER_PATTERN, "$1literal-$2");
|
|
5883
|
+
}
|
|
5884
|
+
function unescapeCheckMarkers(content) {
|
|
5885
|
+
return content.replace(ESCAPED_MARKER_PATTERN, "$1$2");
|
|
5886
|
+
}
|
|
5887
|
+
function findCheckMarkers(fileContent) {
|
|
5888
|
+
CHECK_MARKER_PATTERN.lastIndex = 0;
|
|
5889
|
+
const markers = [];
|
|
5890
|
+
let match = CHECK_MARKER_PATTERN.exec(fileContent);
|
|
5891
|
+
while (match !== null) {
|
|
5892
|
+
markers.push({
|
|
5893
|
+
name: match[1] ?? "",
|
|
5894
|
+
start: match.index,
|
|
5895
|
+
end: match.index + match[0].length
|
|
5896
|
+
});
|
|
5897
|
+
match = CHECK_MARKER_PATTERN.exec(fileContent);
|
|
5898
|
+
}
|
|
5899
|
+
return markers;
|
|
5900
|
+
}
|
|
5901
|
+
/**
|
|
5902
|
+
* Whether the file holds instruction text ahead of the first marker — the
|
|
5903
|
+
* question the "generating replaces this" warning asks. A file with no marker
|
|
5904
|
+
* at all is entirely hand-written, so it qualifies; an empty one does not,
|
|
5905
|
+
* since there is nothing to replace.
|
|
5906
|
+
*/
|
|
5907
|
+
function hasHandWrittenPreamble(fileContent) {
|
|
5908
|
+
const firstMarkerStart = findCheckMarkers(fileContent)[0]?.start ?? fileContent.length;
|
|
5909
|
+
return fileContent.slice(0, firstMarkerStart).trim().length > 0;
|
|
5910
|
+
}
|
|
5911
|
+
/**
|
|
5912
|
+
* Whether the file is nothing but sections rulesync generated — the question
|
|
5913
|
+
* the deletion guard asks, and a stricter one than
|
|
5914
|
+
* {@link hasHandWrittenPreamble}. A file carrying no marker at all is not
|
|
5915
|
+
* rulesync's to remove even when it is empty: rulesync never wrote it, so an
|
|
5916
|
+
* empty one is somebody's placeholder rather than our leftover.
|
|
5917
|
+
*/
|
|
5918
|
+
function isOnlyGeneratedSections(fileContent) {
|
|
5919
|
+
const firstMarkerStart = findCheckMarkers(fileContent)[0]?.start;
|
|
5920
|
+
if (firstMarkerStart === void 0) return false;
|
|
5921
|
+
return fileContent.slice(0, firstMarkerStart).trim().length === 0;
|
|
5922
|
+
}
|
|
5923
|
+
/**
|
|
5924
|
+
* The instruction text one check contributes. Neither file has a field to put a
|
|
5925
|
+
* summary in, so `description` is used only when there is no body.
|
|
5926
|
+
*/
|
|
5927
|
+
function toInstruction(rulesyncCheck) {
|
|
5928
|
+
const body = rulesyncCheck.getBody().trim();
|
|
5929
|
+
if (body.length > 0) return body;
|
|
5930
|
+
return rulesyncCheck.getFrontmatter().description?.trim() ?? "";
|
|
5931
|
+
}
|
|
5932
|
+
function renderCheckSection(rulesyncCheck) {
|
|
5933
|
+
const name = (0, node_path.basename)(rulesyncCheck.getRelativeFilePath(), ".md");
|
|
5934
|
+
const heading = `## ${name}`;
|
|
5935
|
+
const instruction = toInstruction(rulesyncCheck);
|
|
5936
|
+
const lines = [renderCheckMarker(name), heading];
|
|
5937
|
+
if (instruction.length > 0) lines.push("", escapeCheckMarkers(instruction));
|
|
5938
|
+
return lines.join("\n");
|
|
5939
|
+
}
|
|
5940
|
+
function renderCheckFile(rulesyncChecks) {
|
|
5941
|
+
return `${rulesyncChecks.map(renderCheckSection).join("\n\n")}\n`;
|
|
5942
|
+
}
|
|
5943
|
+
/** Drop the heading generate writes, so a round trip does not stack headings. */
|
|
5944
|
+
function stripGeneratedHeading(section, name) {
|
|
5945
|
+
const [firstLine, ...rest] = section.split("\n");
|
|
5946
|
+
if (firstLine?.trim() === `## ${name}`) return rest.join("\n").trim();
|
|
5947
|
+
return section.trim();
|
|
5948
|
+
}
|
|
5949
|
+
/**
|
|
5950
|
+
* Split an aggregated instruction file back into one check per section.
|
|
5951
|
+
*
|
|
5952
|
+
* Content ahead of the first marker — and a hand-written file with no markers
|
|
5953
|
+
* at all — becomes a single check named `fallbackName`, so nothing in the file
|
|
5954
|
+
* is dropped.
|
|
5955
|
+
*/
|
|
5956
|
+
function splitCheckFile({ fileContent, fallbackName }) {
|
|
5957
|
+
const sections = [];
|
|
5958
|
+
const markers = findCheckMarkers(fileContent);
|
|
5959
|
+
const preambleEnd = markers[0]?.start ?? fileContent.length;
|
|
5960
|
+
const preamble = fileContent.slice(0, preambleEnd).trim();
|
|
5961
|
+
if (preamble.length > 0) sections.push({
|
|
5962
|
+
name: fallbackName,
|
|
5963
|
+
content: unescapeCheckMarkers(preamble)
|
|
5964
|
+
});
|
|
5965
|
+
for (const [index, marker] of markers.entries()) {
|
|
5966
|
+
const sectionEnd = markers[index + 1]?.start ?? fileContent.length;
|
|
5967
|
+
const markerName = marker.name.trim();
|
|
5968
|
+
const name = slugifyCheckName(markerName) || fallbackName;
|
|
5969
|
+
const content = stripGeneratedHeading(fileContent.slice(marker.end, sectionEnd).trim(), markerName);
|
|
5970
|
+
sections.push({
|
|
5971
|
+
name,
|
|
5972
|
+
content: unescapeCheckMarkers(content)
|
|
5973
|
+
});
|
|
5974
|
+
}
|
|
5975
|
+
const used = /* @__PURE__ */ new Set();
|
|
5976
|
+
return sections.map(({ name, content }) => {
|
|
5977
|
+
let uniqueName = name;
|
|
5978
|
+
let suffix = 2;
|
|
5979
|
+
while (used.has(uniqueName)) {
|
|
5980
|
+
uniqueName = `${name}-${suffix}`;
|
|
5981
|
+
suffix += 1;
|
|
5982
|
+
}
|
|
5983
|
+
used.add(uniqueName);
|
|
5984
|
+
return new RulesyncCheck({
|
|
5985
|
+
outputRoot: ".",
|
|
5986
|
+
relativeDirPath: RULESYNC_CHECKS_RELATIVE_DIR_PATH,
|
|
5987
|
+
relativeFilePath: `${uniqueName}.md`,
|
|
5988
|
+
frontmatter: { targets: ["*"] },
|
|
5989
|
+
body: content
|
|
5990
|
+
});
|
|
5991
|
+
});
|
|
5992
|
+
}
|
|
5993
|
+
//#endregion
|
|
5994
|
+
//#region src/features/checks/cursor-check.ts
|
|
5995
|
+
const FALLBACK_CHECK_NAME$1 = "bugbot";
|
|
5996
|
+
/**
|
|
5997
|
+
* Checks adapter for Cursor Bugbot (`.cursor/BUGBOT.md`).
|
|
5998
|
+
*
|
|
5999
|
+
* Bugbot takes one aggregated instruction file per directory rather than a file
|
|
6000
|
+
* per check, so every `.rulesync/checks/*.md` targeting Cursor collapses into
|
|
6001
|
+
* the repository-root `.cursor/BUGBOT.md` — hence {@link fromRulesyncChecks}
|
|
6002
|
+
* rather than the usual per-check conversion. Each check becomes one section:
|
|
6003
|
+
* an HTML-comment marker carrying the check name, an `## <name>` heading, and
|
|
6004
|
+
* the check body as the instruction text (the `description` is used when the
|
|
6005
|
+
* body is empty).
|
|
6006
|
+
*
|
|
6007
|
+
* Bugbot reads the file as free prose, so a check's `severity` and `tools` have
|
|
6008
|
+
* no equivalent there: they are not written and do not come back on import. So
|
|
6009
|
+
* is `description` whenever the check also has a body.
|
|
6010
|
+
*
|
|
6011
|
+
* Project scope only — Bugbot reads repository files, and there is no
|
|
6012
|
+
* user-level instruction file. Bugbot also merges nested `<dir>/.cursor/BUGBOT.md`
|
|
6013
|
+
* files found while traversing upward from changed files, but rulesync check
|
|
6014
|
+
* sources carry no directory-placement semantics, so only the root file is
|
|
6015
|
+
* generated.
|
|
6016
|
+
*
|
|
6017
|
+
* On import the markers split the file back into one check per section; content
|
|
6018
|
+
* before the first marker — and a hand-written file with no markers at all —
|
|
6019
|
+
* becomes a single `bugbot` check, so nothing in the file is dropped. A file
|
|
6020
|
+
* holding anything rulesync did not write is never deleted either (see
|
|
6021
|
+
* {@link canDeleteAuxiliaryFiles}), though generating checks for Cursor does
|
|
6022
|
+
* replace it — import first to keep what is there, which is warned about.
|
|
6023
|
+
*
|
|
6024
|
+
* @see https://cursor.com/docs/bugbot
|
|
6025
|
+
*/
|
|
6026
|
+
var CursorCheck = class CursorCheck extends ToolCheck {
|
|
6027
|
+
static getSettablePaths(_options = {}) {
|
|
6028
|
+
return {
|
|
6029
|
+
relativeDirPath: CURSOR_DIR,
|
|
6030
|
+
relativeFilePath: CURSOR_BUGBOT_FILE_NAME
|
|
6031
|
+
};
|
|
6032
|
+
}
|
|
6033
|
+
static isTargetedByRulesyncCheck(rulesyncCheck) {
|
|
6034
|
+
return this.isTargetedByRulesyncCheckDefault({
|
|
6035
|
+
rulesyncCheck,
|
|
6036
|
+
toolTarget: "cursor"
|
|
6037
|
+
});
|
|
6038
|
+
}
|
|
6039
|
+
/**
|
|
6040
|
+
* Ownership guard the processor consults before it deletes anything for this
|
|
6041
|
+
* tool. `.cursor/BUGBOT.md` is a file Cursor's own documentation tells users
|
|
6042
|
+
* to hand-write, so anything in it that rulesync did not write is not
|
|
6043
|
+
* rulesync's to remove — dropping the last check targeting Cursor must not
|
|
6044
|
+
* take somebody's hand-written review instructions with it. Deletion is
|
|
6045
|
+
* therefore allowed only for a file that is nothing but generated sections:
|
|
6046
|
+
* one that carries no marker at all, or that carries hand-written text ahead
|
|
6047
|
+
* of the first marker, stays.
|
|
6048
|
+
*/
|
|
6049
|
+
static async canDeleteAuxiliaryFiles({ outputRoot }) {
|
|
6050
|
+
const paths = CursorCheck.getSettablePaths();
|
|
6051
|
+
const fileContent = await readFileContentOrNull((0, node_path.join)(outputRoot, paths.relativeDirPath, paths.relativeFilePath ?? "BUGBOT.md"));
|
|
6052
|
+
if (fileContent === null) return true;
|
|
6053
|
+
return isOnlyGeneratedSections(fileContent);
|
|
6054
|
+
}
|
|
6055
|
+
static fromRulesyncCheck(_params) {
|
|
6056
|
+
throw new Error("Cursor checks are built from all checks at once; use fromRulesyncChecks.");
|
|
6057
|
+
}
|
|
6058
|
+
static async fromRulesyncChecks({ outputRoot = process.cwd(), rulesyncChecks, global = false, logger }) {
|
|
6059
|
+
if (rulesyncChecks.length === 0) return [];
|
|
6060
|
+
const paths = CursorCheck.getSettablePaths({ global });
|
|
6061
|
+
const relativeFilePath = paths.relativeFilePath ?? "BUGBOT.md";
|
|
6062
|
+
const filePath = (0, node_path.join)(outputRoot, paths.relativeDirPath, relativeFilePath);
|
|
6063
|
+
if (hasHandWrittenPreamble(await readFileContentOrNull(filePath) ?? "")) logger?.warn(`Cursor checks: ${filePath} holds instructions rulesync did not write, and generating replaces the whole file. Run \`rulesync import --targets cursor --features checks\` first to keep them.`);
|
|
6064
|
+
const fileContent = renderCheckFile(rulesyncChecks);
|
|
6065
|
+
return [new CursorCheck({
|
|
6066
|
+
outputRoot,
|
|
6067
|
+
relativeDirPath: paths.relativeDirPath,
|
|
6068
|
+
relativeFilePath,
|
|
6069
|
+
fileContent,
|
|
6070
|
+
global
|
|
6071
|
+
})];
|
|
6072
|
+
}
|
|
6073
|
+
static async fromFile({ outputRoot = process.cwd(), global = false }) {
|
|
6074
|
+
const paths = CursorCheck.getSettablePaths({ global });
|
|
6075
|
+
const relativeFilePath = paths.relativeFilePath ?? "BUGBOT.md";
|
|
6076
|
+
const filePath = (0, node_path.join)(outputRoot, paths.relativeDirPath, relativeFilePath);
|
|
6077
|
+
return new CursorCheck({
|
|
6078
|
+
outputRoot,
|
|
6079
|
+
relativeDirPath: paths.relativeDirPath,
|
|
6080
|
+
relativeFilePath,
|
|
6081
|
+
fileContent: await readFileContentOrNull(filePath) ?? "",
|
|
6082
|
+
global
|
|
6083
|
+
});
|
|
6084
|
+
}
|
|
6085
|
+
static forDeletion({ outputRoot = process.cwd(), relativeDirPath, relativeFilePath, global = false }) {
|
|
6086
|
+
return new CursorCheck({
|
|
6087
|
+
outputRoot,
|
|
6088
|
+
relativeDirPath,
|
|
6089
|
+
relativeFilePath,
|
|
6090
|
+
fileContent: "",
|
|
6091
|
+
validate: false,
|
|
6092
|
+
global
|
|
6093
|
+
});
|
|
6094
|
+
}
|
|
6095
|
+
validate() {
|
|
6096
|
+
return {
|
|
6097
|
+
success: true,
|
|
6098
|
+
error: null
|
|
6099
|
+
};
|
|
6100
|
+
}
|
|
6101
|
+
toRulesyncCheck() {
|
|
6102
|
+
const first = this.toRulesyncChecks()[0];
|
|
6103
|
+
if (!first) throw new Error(`No check instructions found in ${(0, node_path.join)(this.getRelativeDirPath(), this.getRelativeFilePath())}.`);
|
|
6104
|
+
return first;
|
|
6105
|
+
}
|
|
6106
|
+
toRulesyncChecks() {
|
|
6107
|
+
return splitCheckFile({
|
|
6108
|
+
fileContent: this.getFileContent(),
|
|
6109
|
+
fallbackName: FALLBACK_CHECK_NAME$1
|
|
6110
|
+
});
|
|
6111
|
+
}
|
|
6112
|
+
};
|
|
6113
|
+
//#endregion
|
|
5677
6114
|
//#region src/constants/hermesagent-paths.ts
|
|
5678
6115
|
/**
|
|
5679
6116
|
* Hermes Agent configuration-layout conventions.
|
|
@@ -5691,8 +6128,22 @@ var AmpCheck = class AmpCheck extends ToolCheck {
|
|
|
5691
6128
|
*/
|
|
5692
6129
|
/** Project-root instruction file auto-injected by Hermes Agent. */
|
|
5693
6130
|
const HERMESAGENT_RULE_FILE_NAME = ".hermes.md";
|
|
5694
|
-
/**
|
|
6131
|
+
/**
|
|
6132
|
+
* Root directory for Hermes Agent global configuration (the HERMES_HOME dir).
|
|
6133
|
+
* Also the project-local plugin tree, which is `.hermes/` on every platform.
|
|
6134
|
+
*/
|
|
5695
6135
|
const HERMESAGENT_GLOBAL_DIR = ".hermes";
|
|
6136
|
+
/**
|
|
6137
|
+
* Home-relative global profile root on Windows: upstream defaults to
|
|
6138
|
+
* `%LOCALAPPDATA%\hermes` there, not `~/.hermes`.
|
|
6139
|
+
* Resolve it through `getHermesagentGlobalDir()` rather than reading it directly.
|
|
6140
|
+
*
|
|
6141
|
+
* Home-relative rather than read from `LOCALAPPDATA`, matching how every other
|
|
6142
|
+
* Windows global path in rulesync is spelled (`ZED_GLOBAL_WIN32_DIR`,
|
|
6143
|
+
* `WARP_WIN32_DIR`). A profile with `LOCALAPPDATA` redirected elsewhere is not
|
|
6144
|
+
* followed; those users should set `HERMES_HOME` explicitly.
|
|
6145
|
+
*/
|
|
6146
|
+
const HERMESAGENT_GLOBAL_WIN32_DIR = (0, node_path.join)("AppData", "Local", "hermes");
|
|
5696
6147
|
/** MCP servers and other settings live in `config.yaml` under `~/.hermes/`. */
|
|
5697
6148
|
const HERMESAGENT_CONFIG_FILE_NAME = "config.yaml";
|
|
5698
6149
|
const HERMESAGENT_CONFIG_FILE_PATH = (0, node_path.join)(HERMESAGENT_GLOBAL_DIR, HERMESAGENT_CONFIG_FILE_NAME);
|
|
@@ -5915,6 +6366,114 @@ var HermesagentCheck = class HermesagentCheck extends ToolCheck {
|
|
|
5915
6366
|
}
|
|
5916
6367
|
};
|
|
5917
6368
|
//#endregion
|
|
6369
|
+
//#region src/features/checks/rovodev-check.ts
|
|
6370
|
+
const FALLBACK_CHECK_NAME = "review-agent";
|
|
6371
|
+
/**
|
|
6372
|
+
* Checks adapter for Rovo Dev CLI's code-review custom instructions
|
|
6373
|
+
* (`.rovodev/.review-agent.md`).
|
|
6374
|
+
*
|
|
6375
|
+
* Rovo Dev takes one plain-Markdown instruction file at the repository root's
|
|
6376
|
+
* `.rovodev/` folder — no frontmatter, and note the leading dot in the file
|
|
6377
|
+
* name. Like Cursor Bugbot it is a single aggregated file rather than a file
|
|
6378
|
+
* per check, so every `.rulesync/checks/*.md` targeting Rovo Dev collapses into
|
|
6379
|
+
* it via {@link fromRulesyncChecks}, with each check written as a marked
|
|
6380
|
+
* section (see `aggregated-check-file.ts` for the marker convention the two
|
|
6381
|
+
* adapters share).
|
|
6382
|
+
*
|
|
6383
|
+
* Rovo Dev reads the file as free prose, so a check's `severity` and `tools`
|
|
6384
|
+
* have no equivalent there: they are not written and do not come back on
|
|
6385
|
+
* import. Neither does `description` whenever the check also has a body.
|
|
6386
|
+
*
|
|
6387
|
+
* Project scope only — these are per-repository review instructions, and Rovo
|
|
6388
|
+
* Dev documents no user-level equivalent. (The `permissions` adapter for the
|
|
6389
|
+
* same tool is the opposite: global only.)
|
|
6390
|
+
*
|
|
6391
|
+
* @see https://support.atlassian.com/rovo/docs/set-custom-instructions-for-code-reviews/
|
|
6392
|
+
*/
|
|
6393
|
+
var RovodevCheck = class RovodevCheck extends ToolCheck {
|
|
6394
|
+
static getSettablePaths(_options = {}) {
|
|
6395
|
+
return {
|
|
6396
|
+
relativeDirPath: ROVODEV_DIR,
|
|
6397
|
+
relativeFilePath: ROVODEV_REVIEW_AGENT_FILE_NAME
|
|
6398
|
+
};
|
|
6399
|
+
}
|
|
6400
|
+
static isTargetedByRulesyncCheck(rulesyncCheck) {
|
|
6401
|
+
return this.isTargetedByRulesyncCheckDefault({
|
|
6402
|
+
rulesyncCheck,
|
|
6403
|
+
toolTarget: "rovodev"
|
|
6404
|
+
});
|
|
6405
|
+
}
|
|
6406
|
+
/**
|
|
6407
|
+
* Ownership guard the processor consults before it deletes anything for this
|
|
6408
|
+
* tool. `.review-agent.md` is a file Rovo Dev's own documentation tells users
|
|
6409
|
+
* to hand-write, so anything in it that rulesync did not write is not
|
|
6410
|
+
* rulesync's to remove — dropping the last check targeting Rovo Dev must not
|
|
6411
|
+
* take somebody's hand-written review instructions with it.
|
|
6412
|
+
*/
|
|
6413
|
+
static async canDeleteAuxiliaryFiles({ outputRoot }) {
|
|
6414
|
+
const paths = RovodevCheck.getSettablePaths();
|
|
6415
|
+
const fileContent = await readFileContentOrNull((0, node_path.join)(outputRoot, paths.relativeDirPath, paths.relativeFilePath ?? ".review-agent.md"));
|
|
6416
|
+
if (fileContent === null) return true;
|
|
6417
|
+
return isOnlyGeneratedSections(fileContent);
|
|
6418
|
+
}
|
|
6419
|
+
static fromRulesyncCheck(_params) {
|
|
6420
|
+
throw new Error("Rovo Dev checks are built from all checks at once; use fromRulesyncChecks.");
|
|
6421
|
+
}
|
|
6422
|
+
static async fromRulesyncChecks({ outputRoot = process.cwd(), rulesyncChecks, global = false, logger }) {
|
|
6423
|
+
if (rulesyncChecks.length === 0) return [];
|
|
6424
|
+
const paths = RovodevCheck.getSettablePaths({ global });
|
|
6425
|
+
const relativeFilePath = paths.relativeFilePath ?? ".review-agent.md";
|
|
6426
|
+
const filePath = (0, node_path.join)(outputRoot, paths.relativeDirPath, relativeFilePath);
|
|
6427
|
+
if (hasHandWrittenPreamble(await readFileContentOrNull(filePath) ?? "")) logger?.warn(`Rovo Dev checks: ${filePath} holds instructions rulesync did not write, and generating replaces the whole file. Run \`rulesync import --targets rovodev --features checks\` first to keep them.`);
|
|
6428
|
+
return [new RovodevCheck({
|
|
6429
|
+
outputRoot,
|
|
6430
|
+
relativeDirPath: paths.relativeDirPath,
|
|
6431
|
+
relativeFilePath,
|
|
6432
|
+
fileContent: renderCheckFile(rulesyncChecks),
|
|
6433
|
+
global
|
|
6434
|
+
})];
|
|
6435
|
+
}
|
|
6436
|
+
static async fromFile({ outputRoot = process.cwd(), global = false }) {
|
|
6437
|
+
const paths = RovodevCheck.getSettablePaths({ global });
|
|
6438
|
+
const relativeFilePath = paths.relativeFilePath ?? ".review-agent.md";
|
|
6439
|
+
const filePath = (0, node_path.join)(outputRoot, paths.relativeDirPath, relativeFilePath);
|
|
6440
|
+
return new RovodevCheck({
|
|
6441
|
+
outputRoot,
|
|
6442
|
+
relativeDirPath: paths.relativeDirPath,
|
|
6443
|
+
relativeFilePath,
|
|
6444
|
+
fileContent: await readFileContentOrNull(filePath) ?? "",
|
|
6445
|
+
global
|
|
6446
|
+
});
|
|
6447
|
+
}
|
|
6448
|
+
static forDeletion({ outputRoot = process.cwd(), relativeDirPath, relativeFilePath, global = false }) {
|
|
6449
|
+
return new RovodevCheck({
|
|
6450
|
+
outputRoot,
|
|
6451
|
+
relativeDirPath,
|
|
6452
|
+
relativeFilePath,
|
|
6453
|
+
fileContent: "",
|
|
6454
|
+
validate: false,
|
|
6455
|
+
global
|
|
6456
|
+
});
|
|
6457
|
+
}
|
|
6458
|
+
validate() {
|
|
6459
|
+
return {
|
|
6460
|
+
success: true,
|
|
6461
|
+
error: null
|
|
6462
|
+
};
|
|
6463
|
+
}
|
|
6464
|
+
toRulesyncCheck() {
|
|
6465
|
+
const first = this.toRulesyncChecks()[0];
|
|
6466
|
+
if (!first) throw new Error(`No check instructions found in ${(0, node_path.join)(this.getRelativeDirPath(), this.getRelativeFilePath())}.`);
|
|
6467
|
+
return first;
|
|
6468
|
+
}
|
|
6469
|
+
toRulesyncChecks() {
|
|
6470
|
+
return splitCheckFile({
|
|
6471
|
+
fileContent: this.getFileContent(),
|
|
6472
|
+
fallbackName: FALLBACK_CHECK_NAME
|
|
6473
|
+
});
|
|
6474
|
+
}
|
|
6475
|
+
};
|
|
6476
|
+
//#endregion
|
|
5918
6477
|
//#region src/constants/codexcli-paths.ts
|
|
5919
6478
|
const CODEXCLI_DIR = ".codex";
|
|
5920
6479
|
const CODEXCLI_PROMPTS_DIR_PATH = (0, node_path.join)(CODEXCLI_DIR, "prompts");
|
|
@@ -6027,11 +6586,14 @@ function mergeSharedConfigDeep({ base, patch }) {
|
|
|
6027
6586
|
}
|
|
6028
6587
|
const CLAUDE_SETTINGS_SHARED_FILE_KEY = ".claude/settings.json";
|
|
6029
6588
|
const HERMES_CONFIG_SHARED_FILE_KEY = ".hermes/config.yaml";
|
|
6589
|
+
const HERMES_WIN32_CONFIG_SHARED_FILE_KEY = "AppData/Local/hermes/config.yaml";
|
|
6590
|
+
const HERMES_HOME_CONFIG_SHARED_FILE_KEY = "config.yaml";
|
|
6030
6591
|
const TAKT_CONFIG_SHARED_FILE_KEY = ".takt/config.yaml";
|
|
6031
6592
|
const CODEXCLI_CONFIG_SHARED_FILE_KEY = ".codex/config.toml";
|
|
6032
6593
|
const GROKCLI_CONFIG_SHARED_FILE_KEY = ".grok/config.toml";
|
|
6033
6594
|
const VIBE_CONFIG_SHARED_FILE_KEY = ".vibe/config.toml";
|
|
6034
6595
|
const KIMI_CODE_CONFIG_SHARED_FILE_KEY = ".kimi-code/config.toml";
|
|
6596
|
+
const KIMI_CODE_HOME_CONFIG_SHARED_FILE_KEY = "config.toml";
|
|
6035
6597
|
const REASONIX_PROJECT_CONFIG_SHARED_FILE_KEY = "reasonix.toml";
|
|
6036
6598
|
const REASONIX_GLOBAL_CONFIG_SHARED_FILE_KEY = ".reasonix/config.toml";
|
|
6037
6599
|
/**
|
|
@@ -6055,6 +6617,65 @@ const sharedConfigFileKey = ({ relativeDirPath, relativeFilePath }) => {
|
|
|
6055
6617
|
* lock-step with the writers derived from the processor registry, so an
|
|
6056
6618
|
* undeclared writer fails CI instead of merging by accident.
|
|
6057
6619
|
*/
|
|
6620
|
+
/**
|
|
6621
|
+
* Hermes writes one `config.yaml`, but its global profile root has three
|
|
6622
|
+
* spellings (`~/.hermes`, the win32 `%LOCALAPPDATA%\hermes`, and `HERMES_HOME`
|
|
6623
|
+
* itself). They are the same file with the same owners, so the declaration is
|
|
6624
|
+
* written once and shared — a policy edit cannot land on one spelling only.
|
|
6625
|
+
*/
|
|
6626
|
+
const HERMES_CONFIG_DECLARATION = {
|
|
6627
|
+
format: "yaml",
|
|
6628
|
+
features: {
|
|
6629
|
+
commands: {
|
|
6630
|
+
kind: "replace-owned-keys",
|
|
6631
|
+
ownedKeys: ["plugins"]
|
|
6632
|
+
},
|
|
6633
|
+
subagents: {
|
|
6634
|
+
kind: "replace-owned-keys",
|
|
6635
|
+
ownedKeys: ["plugins"]
|
|
6636
|
+
},
|
|
6637
|
+
mcp: {
|
|
6638
|
+
kind: "replace-owned-keys",
|
|
6639
|
+
ownedKeys: ["mcp_servers"]
|
|
6640
|
+
},
|
|
6641
|
+
hooks: {
|
|
6642
|
+
kind: "replace-owned-keys",
|
|
6643
|
+
ownedKeys: ["hooks"]
|
|
6644
|
+
},
|
|
6645
|
+
permissions: {
|
|
6646
|
+
kind: "deep-merge",
|
|
6647
|
+
replaceKeys: ["permissions"]
|
|
6648
|
+
}
|
|
6649
|
+
}
|
|
6650
|
+
};
|
|
6651
|
+
/**
|
|
6652
|
+
* Kimi Code's user config: hooks owns the flat `hooks` array; permissions owns
|
|
6653
|
+
* the ordered rule list and optional coarse default mode. `KIMI_CODE_HOME` can
|
|
6654
|
+
* name the profile directory itself, so the file has two spellings that share
|
|
6655
|
+
* one declaration — a policy edit cannot land on only one of them.
|
|
6656
|
+
*/
|
|
6657
|
+
const KIMI_CODE_CONFIG_DECLARATION = {
|
|
6658
|
+
format: "toml",
|
|
6659
|
+
invalidRootPolicy: "error",
|
|
6660
|
+
features: {
|
|
6661
|
+
hooks: {
|
|
6662
|
+
kind: "replace-owned-keys",
|
|
6663
|
+
ownedKeys: ["hooks"]
|
|
6664
|
+
},
|
|
6665
|
+
mcp: {
|
|
6666
|
+
kind: "replace-owned-keys",
|
|
6667
|
+
ownedKeys: ["mcp"]
|
|
6668
|
+
},
|
|
6669
|
+
permissions: {
|
|
6670
|
+
kind: "replace-owned-keys",
|
|
6671
|
+
ownedKeys: [
|
|
6672
|
+
"permission",
|
|
6673
|
+
"default_permission_mode",
|
|
6674
|
+
"tools"
|
|
6675
|
+
]
|
|
6676
|
+
}
|
|
6677
|
+
}
|
|
6678
|
+
};
|
|
6058
6679
|
const SHARED_CONFIG_OWNERSHIP = {
|
|
6059
6680
|
[CLAUDE_SETTINGS_SHARED_FILE_KEY]: {
|
|
6060
6681
|
format: "json",
|
|
@@ -6073,31 +6694,9 @@ const SHARED_CONFIG_OWNERSHIP = {
|
|
|
6073
6694
|
}
|
|
6074
6695
|
}
|
|
6075
6696
|
},
|
|
6076
|
-
[HERMES_CONFIG_SHARED_FILE_KEY]:
|
|
6077
|
-
|
|
6078
|
-
|
|
6079
|
-
commands: {
|
|
6080
|
-
kind: "replace-owned-keys",
|
|
6081
|
-
ownedKeys: ["plugins"]
|
|
6082
|
-
},
|
|
6083
|
-
subagents: {
|
|
6084
|
-
kind: "replace-owned-keys",
|
|
6085
|
-
ownedKeys: ["plugins"]
|
|
6086
|
-
},
|
|
6087
|
-
mcp: {
|
|
6088
|
-
kind: "replace-owned-keys",
|
|
6089
|
-
ownedKeys: ["mcp_servers"]
|
|
6090
|
-
},
|
|
6091
|
-
hooks: {
|
|
6092
|
-
kind: "replace-owned-keys",
|
|
6093
|
-
ownedKeys: ["hooks"]
|
|
6094
|
-
},
|
|
6095
|
-
permissions: {
|
|
6096
|
-
kind: "deep-merge",
|
|
6097
|
-
replaceKeys: ["permissions"]
|
|
6098
|
-
}
|
|
6099
|
-
}
|
|
6100
|
-
},
|
|
6697
|
+
[HERMES_CONFIG_SHARED_FILE_KEY]: HERMES_CONFIG_DECLARATION,
|
|
6698
|
+
[HERMES_WIN32_CONFIG_SHARED_FILE_KEY]: HERMES_CONFIG_DECLARATION,
|
|
6699
|
+
[HERMES_HOME_CONFIG_SHARED_FILE_KEY]: HERMES_CONFIG_DECLARATION,
|
|
6101
6700
|
[TAKT_CONFIG_SHARED_FILE_KEY]: {
|
|
6102
6701
|
format: "yaml",
|
|
6103
6702
|
invalidRootPolicy: "error",
|
|
@@ -6143,6 +6742,10 @@ const SHARED_CONFIG_OWNERSHIP = {
|
|
|
6143
6742
|
".config/zed/settings.json": {
|
|
6144
6743
|
format: "json",
|
|
6145
6744
|
features: {
|
|
6745
|
+
ignore: {
|
|
6746
|
+
kind: "replace-owned-keys",
|
|
6747
|
+
ownedKeys: ["private_files"]
|
|
6748
|
+
},
|
|
6146
6749
|
mcp: {
|
|
6147
6750
|
kind: "replace-owned-keys",
|
|
6148
6751
|
ownedKeys: ["context_servers"]
|
|
@@ -6156,6 +6759,10 @@ const SHARED_CONFIG_OWNERSHIP = {
|
|
|
6156
6759
|
"AppData/Roaming/Zed/settings.json": {
|
|
6157
6760
|
format: "json",
|
|
6158
6761
|
features: {
|
|
6762
|
+
ignore: {
|
|
6763
|
+
kind: "replace-owned-keys",
|
|
6764
|
+
ownedKeys: ["private_files"]
|
|
6765
|
+
},
|
|
6159
6766
|
mcp: {
|
|
6160
6767
|
kind: "replace-owned-keys",
|
|
6161
6768
|
ownedKeys: ["context_servers"]
|
|
@@ -6172,7 +6779,20 @@ const SHARED_CONFIG_OWNERSHIP = {
|
|
|
6172
6779
|
jsoncParseErrors: "error",
|
|
6173
6780
|
features: { permissions: {
|
|
6174
6781
|
kind: "replace-owned-keys",
|
|
6175
|
-
ownedKeys: [
|
|
6782
|
+
ownedKeys: [
|
|
6783
|
+
"chat.tools.terminal.autoApprove",
|
|
6784
|
+
"chat.tools.edits.autoApprove",
|
|
6785
|
+
"chat.tools.urls.autoApprove"
|
|
6786
|
+
]
|
|
6787
|
+
} }
|
|
6788
|
+
},
|
|
6789
|
+
".vscode/mcp.json": {
|
|
6790
|
+
format: "jsonc",
|
|
6791
|
+
invalidRootPolicy: "error",
|
|
6792
|
+
jsoncParseErrors: "error",
|
|
6793
|
+
features: { mcp: {
|
|
6794
|
+
kind: "replace-owned-keys",
|
|
6795
|
+
ownedKeys: ["servers"]
|
|
6176
6796
|
} }
|
|
6177
6797
|
},
|
|
6178
6798
|
".qwen/settings.json": {
|
|
@@ -6399,31 +7019,15 @@ const SHARED_CONFIG_OWNERSHIP = {
|
|
|
6399
7019
|
}
|
|
6400
7020
|
}
|
|
6401
7021
|
},
|
|
6402
|
-
[KIMI_CODE_CONFIG_SHARED_FILE_KEY]:
|
|
6403
|
-
|
|
6404
|
-
invalidRootPolicy: "error",
|
|
6405
|
-
features: {
|
|
6406
|
-
hooks: {
|
|
6407
|
-
kind: "replace-owned-keys",
|
|
6408
|
-
ownedKeys: ["hooks"]
|
|
6409
|
-
},
|
|
6410
|
-
mcp: {
|
|
6411
|
-
kind: "replace-owned-keys",
|
|
6412
|
-
ownedKeys: ["mcp"]
|
|
6413
|
-
},
|
|
6414
|
-
permissions: {
|
|
6415
|
-
kind: "replace-owned-keys",
|
|
6416
|
-
ownedKeys: [
|
|
6417
|
-
"permission",
|
|
6418
|
-
"default_permission_mode",
|
|
6419
|
-
"tools"
|
|
6420
|
-
]
|
|
6421
|
-
}
|
|
6422
|
-
}
|
|
6423
|
-
},
|
|
7022
|
+
[KIMI_CODE_CONFIG_SHARED_FILE_KEY]: KIMI_CODE_CONFIG_DECLARATION,
|
|
7023
|
+
[KIMI_CODE_HOME_CONFIG_SHARED_FILE_KEY]: KIMI_CODE_CONFIG_DECLARATION,
|
|
6424
7024
|
[REASONIX_PROJECT_CONFIG_SHARED_FILE_KEY]: {
|
|
6425
7025
|
format: "toml",
|
|
6426
7026
|
features: {
|
|
7027
|
+
ignore: {
|
|
7028
|
+
kind: "custom",
|
|
7029
|
+
policyFunction: "applyIgnoreReadDenies"
|
|
7030
|
+
},
|
|
6427
7031
|
mcp: {
|
|
6428
7032
|
kind: "replace-owned-keys",
|
|
6429
7033
|
ownedKeys: ["plugins"]
|
|
@@ -6441,6 +7045,10 @@ const SHARED_CONFIG_OWNERSHIP = {
|
|
|
6441
7045
|
[REASONIX_GLOBAL_CONFIG_SHARED_FILE_KEY]: {
|
|
6442
7046
|
format: "toml",
|
|
6443
7047
|
features: {
|
|
7048
|
+
ignore: {
|
|
7049
|
+
kind: "custom",
|
|
7050
|
+
policyFunction: "applyIgnoreReadDenies"
|
|
7051
|
+
},
|
|
6444
7052
|
mcp: {
|
|
6445
7053
|
kind: "replace-owned-keys",
|
|
6446
7054
|
ownedKeys: ["plugins"]
|
|
@@ -6538,7 +7146,12 @@ const applyIgnoreReadDenies = (params) => {
|
|
|
6538
7146
|
const applyPermissions = (params) => {
|
|
6539
7147
|
const { settings, managedToolNames, toolNameOf, allow, ask, deny, logger } = params;
|
|
6540
7148
|
const current = parsePermissionsBlock(settings);
|
|
6541
|
-
const
|
|
7149
|
+
const emitted = /* @__PURE__ */ new Set([
|
|
7150
|
+
...allow,
|
|
7151
|
+
...ask,
|
|
7152
|
+
...deny
|
|
7153
|
+
]);
|
|
7154
|
+
const keepUnmanaged = (entries) => entries.filter((entry) => !managedToolNames.has(toolNameOf(entry)) && !emitted.has(entry));
|
|
6542
7155
|
if (logger && managedToolNames.has(READ_TOOL_NAME)) {
|
|
6543
7156
|
const overwrittenReadDenies = current.deny.filter((entry) => toolNameOf(entry) === READ_TOOL_NAME);
|
|
6544
7157
|
if (overwrittenReadDenies.length > 0) logger.warn(`Permissions feature manages '${READ_TOOL_NAME}' tool and will overwrite ${overwrittenReadDenies.length} existing ${READ_TOOL_NAME} deny entries. Permissions take precedence.`);
|
|
@@ -6688,12 +7301,9 @@ function toRulesyncCheckFromGate({ gate, index, scope, editOnly }) {
|
|
|
6688
7301
|
body: ""
|
|
6689
7302
|
});
|
|
6690
7303
|
}
|
|
6691
|
-
function slugify(value) {
|
|
6692
|
-
return value.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 48).replace(/-+$/, "");
|
|
6693
|
-
}
|
|
6694
7304
|
function slugForGate({ gate, index, scope }) {
|
|
6695
|
-
const slug =
|
|
6696
|
-
return `${scope ? `${
|
|
7305
|
+
const slug = slugifyCheckName(typeof gate === "string" ? gate : typeof gate.name === "string" ? gate.name : typeof gate.command === "string" ? gate.command : "");
|
|
7306
|
+
return `${scope ? `${slugifyCheckName(scope.name)}-` : ""}${slug.length > 0 ? slug : "quality-gate"}-${index + 1}`;
|
|
6697
7307
|
}
|
|
6698
7308
|
/**
|
|
6699
7309
|
* Checks adapter for Takt (`.takt/config.yaml` project / `~/.takt/config.yaml`
|
|
@@ -6869,6 +7479,13 @@ const toolCheckFactories = /* @__PURE__ */ new Map([
|
|
|
6869
7479
|
filePattern: "*.md"
|
|
6870
7480
|
}
|
|
6871
7481
|
}],
|
|
7482
|
+
["cursor", {
|
|
7483
|
+
class: CursorCheck,
|
|
7484
|
+
meta: {
|
|
7485
|
+
supportsGlobal: false,
|
|
7486
|
+
filePattern: CURSOR_BUGBOT_FILE_NAME
|
|
7487
|
+
}
|
|
7488
|
+
}],
|
|
6872
7489
|
["hermesagent", {
|
|
6873
7490
|
class: HermesagentCheck,
|
|
6874
7491
|
meta: {
|
|
@@ -6876,6 +7493,13 @@ const toolCheckFactories = /* @__PURE__ */ new Map([
|
|
|
6876
7493
|
filePattern: "*.json"
|
|
6877
7494
|
}
|
|
6878
7495
|
}],
|
|
7496
|
+
["rovodev", {
|
|
7497
|
+
class: RovodevCheck,
|
|
7498
|
+
meta: {
|
|
7499
|
+
supportsGlobal: false,
|
|
7500
|
+
filePattern: ROVODEV_REVIEW_AGENT_FILE_NAME
|
|
7501
|
+
}
|
|
7502
|
+
}],
|
|
6879
7503
|
["takt", {
|
|
6880
7504
|
class: TaktCheck,
|
|
6881
7505
|
meta: {
|
|
@@ -7031,19 +7655,65 @@ var ChecksProcessor = class extends FeatureProcessor {
|
|
|
7031
7655
|
}
|
|
7032
7656
|
};
|
|
7033
7657
|
//#endregion
|
|
7658
|
+
//#region src/utils/tool-home.ts
|
|
7659
|
+
/**
|
|
7660
|
+
* Where the rulesync-side source files of a tool with a home override belong.
|
|
7661
|
+
*
|
|
7662
|
+
* A home override redirects the tool's OWN output, but the `.rulesync/` sources
|
|
7663
|
+
* imported back out of it are not part of the tool's profile — they stay under
|
|
7664
|
+
* the rulesync home. When no override is set, the native output root already is
|
|
7665
|
+
* that place.
|
|
7666
|
+
*/
|
|
7667
|
+
function getToolRulesyncOutputRoot({ nativeOutputRoot, global, toolHome }) {
|
|
7668
|
+
return global && toolHome() ? getHomeDirectory() : nativeOutputRoot;
|
|
7669
|
+
}
|
|
7670
|
+
//#endregion
|
|
7034
7671
|
//#region src/utils/hermesagent.ts
|
|
7035
7672
|
function getHermesagentHome() {
|
|
7036
7673
|
const configuredHome = process.env.HERMES_HOME?.trim();
|
|
7037
7674
|
return configuredHome ? (0, node_path.resolve)(configuredHome) : void 0;
|
|
7038
7675
|
}
|
|
7676
|
+
/**
|
|
7677
|
+
* The home-relative Hermes profile directory used when `HERMES_HOME` is unset.
|
|
7678
|
+
*
|
|
7679
|
+
* Upstream `_get_platform_default_hermes_home()` returns `%LOCALAPPDATA%\hermes`
|
|
7680
|
+
* on win32 and `~/.hermes` everywhere else, so the global output directory is
|
|
7681
|
+
* platform-dependent — a global generate on Windows that wrote `~/.hermes`
|
|
7682
|
+
* would land where Hermes never reads.
|
|
7683
|
+
*
|
|
7684
|
+
* @see https://github.com/NousResearch/hermes-agent `hermes_constants.py`
|
|
7685
|
+
*/
|
|
7686
|
+
function getHermesagentGlobalDir() {
|
|
7687
|
+
return process.platform === "win32" ? HERMESAGENT_GLOBAL_WIN32_DIR : HERMESAGENT_GLOBAL_DIR;
|
|
7688
|
+
}
|
|
7039
7689
|
function resolveHermesagentOutputRoot({ outputRoot, global }) {
|
|
7040
7690
|
return global ? getHermesagentHome() ?? outputRoot : outputRoot;
|
|
7041
7691
|
}
|
|
7692
|
+
/**
|
|
7693
|
+
* Map a canonical `.hermes/...` path constant onto the directory rulesync
|
|
7694
|
+
* actually writes in the requested scope.
|
|
7695
|
+
*
|
|
7696
|
+
* Project scope keeps the constant as-is (the project tree is `.hermes/`
|
|
7697
|
+
* everywhere). Global scope strips the `.hermes` prefix and re-anchors it:
|
|
7698
|
+
* `HERMES_HOME` *is* the profile root, so nothing is prepended; otherwise the
|
|
7699
|
+
* platform default directory takes its place.
|
|
7700
|
+
*/
|
|
7042
7701
|
function getHermesagentRelativeDirPath({ global, relativeDirPath }) {
|
|
7043
|
-
if (!global
|
|
7702
|
+
if (!global) return relativeDirPath;
|
|
7044
7703
|
const relativePath = (0, node_path.relative)(HERMESAGENT_GLOBAL_DIR, relativeDirPath);
|
|
7045
|
-
|
|
7046
|
-
|
|
7704
|
+
try {
|
|
7705
|
+
checkPathTraversal({
|
|
7706
|
+
relativePath: relativeDirPath,
|
|
7707
|
+
intendedRootDir: "."
|
|
7708
|
+
});
|
|
7709
|
+
checkPathTraversal({
|
|
7710
|
+
relativePath,
|
|
7711
|
+
intendedRootDir: HERMESAGENT_GLOBAL_DIR
|
|
7712
|
+
});
|
|
7713
|
+
} catch {
|
|
7714
|
+
throw new Error(`Hermes Agent global path must be within ${HERMESAGENT_GLOBAL_DIR}: ${relativeDirPath}`);
|
|
7715
|
+
}
|
|
7716
|
+
return getHermesagentHome() ? relativePath || "." : (0, node_path.join)(getHermesagentGlobalDir(), relativePath);
|
|
7047
7717
|
}
|
|
7048
7718
|
function getHermesagentRelativeFilePath({ global, relativeFilePath }) {
|
|
7049
7719
|
return (0, node_path.join)(getHermesagentRelativeDirPath({
|
|
@@ -7051,8 +7721,53 @@ function getHermesagentRelativeFilePath({ global, relativeFilePath }) {
|
|
|
7051
7721
|
relativeDirPath: (0, node_path.dirname)(relativeFilePath)
|
|
7052
7722
|
}), (0, node_path.basename)(relativeFilePath));
|
|
7053
7723
|
}
|
|
7724
|
+
/**
|
|
7725
|
+
* Every spelling `config.yaml` can take in global scope, so that the
|
|
7726
|
+
* shared-write derivation and the gateway ownership table it is checked against
|
|
7727
|
+
* see the same set of keys on every platform and with or without `HERMES_HOME`.
|
|
7728
|
+
*
|
|
7729
|
+
* `getHermesagentRelativeDirPath` resolves exactly one of these per process,
|
|
7730
|
+
* which would otherwise make the derived shared-file key depend on the ambient
|
|
7731
|
+
* environment — the drift guards would then go blind in precisely the
|
|
7732
|
+
* configuration this feature exists for.
|
|
7733
|
+
*/
|
|
7734
|
+
function getHermesagentSharedConfigWritePaths() {
|
|
7735
|
+
return [
|
|
7736
|
+
{
|
|
7737
|
+
relativeDirPath: HERMESAGENT_GLOBAL_DIR,
|
|
7738
|
+
relativeFilePath: HERMESAGENT_CONFIG_FILE_NAME
|
|
7739
|
+
},
|
|
7740
|
+
{
|
|
7741
|
+
relativeDirPath: HERMESAGENT_GLOBAL_WIN32_DIR,
|
|
7742
|
+
relativeFilePath: HERMESAGENT_CONFIG_FILE_NAME
|
|
7743
|
+
},
|
|
7744
|
+
{
|
|
7745
|
+
relativeDirPath: ".",
|
|
7746
|
+
relativeFilePath: HERMESAGENT_CONFIG_FILE_NAME
|
|
7747
|
+
}
|
|
7748
|
+
];
|
|
7749
|
+
}
|
|
7750
|
+
/**
|
|
7751
|
+
* The `SHARED_CONFIG_OWNERSHIP` key of the `config.yaml` this scope actually
|
|
7752
|
+
* writes. All three spellings carry the same declaration, but passing the key of
|
|
7753
|
+
* the file being written keeps the write path and the drift guards reading the
|
|
7754
|
+
* same entry.
|
|
7755
|
+
*/
|
|
7756
|
+
function getHermesagentConfigSharedFileKey({ global }) {
|
|
7757
|
+
return sharedConfigFileKey({
|
|
7758
|
+
relativeDirPath: getHermesagentRelativeDirPath({
|
|
7759
|
+
global,
|
|
7760
|
+
relativeDirPath: HERMESAGENT_GLOBAL_DIR
|
|
7761
|
+
}),
|
|
7762
|
+
relativeFilePath: HERMESAGENT_CONFIG_FILE_NAME
|
|
7763
|
+
});
|
|
7764
|
+
}
|
|
7054
7765
|
function getHermesagentRulesyncOutputRoot({ nativeOutputRoot, global }) {
|
|
7055
|
-
return
|
|
7766
|
+
return getToolRulesyncOutputRoot({
|
|
7767
|
+
nativeOutputRoot,
|
|
7768
|
+
global,
|
|
7769
|
+
toolHome: getHermesagentHome
|
|
7770
|
+
});
|
|
7056
7771
|
}
|
|
7057
7772
|
//#endregion
|
|
7058
7773
|
//#region src/constants/agentsmd-paths.ts
|
|
@@ -7284,6 +7999,7 @@ var AgentsmdCommand = class AgentsmdCommand extends SimulatedCommand {
|
|
|
7284
7999
|
const ANTIGRAVITY_DIR = ".agents";
|
|
7285
8000
|
const ANTIGRAVITY_SKILLS_DIR_PATH = (0, node_path.join)(ANTIGRAVITY_DIR, "skills");
|
|
7286
8001
|
const ANTIGRAVITY_WORKFLOWS_DIR_PATH = (0, node_path.join)(ANTIGRAVITY_DIR, "workflows");
|
|
8002
|
+
const ANTIGRAVITY_AGENTS_DIR_PATH = (0, node_path.join)(ANTIGRAVITY_DIR, "agents");
|
|
7287
8003
|
const ANTIGRAVITY_MCP_FILE_NAME = "mcp_config.json";
|
|
7288
8004
|
const ANTIGRAVITY_HOOKS_FILE_NAME = "hooks.json";
|
|
7289
8005
|
const ANTIGRAVITY_IGNORE_FILE_NAME = ".geminiignore";
|
|
@@ -7294,6 +8010,7 @@ const ANTIGRAVITY_CLI_PERMISSIONS_FILE_NAME = "settings.json";
|
|
|
7294
8010
|
const ANTIGRAVITY_CLI_GLOBAL_WORKFLOWS_DIR_PATH = (0, node_path.join)(ANTIGRAVITY_GEMINI_DIR, ANTIGRAVITY_CLI_PERMISSIONS_SUBDIR, "global_workflows");
|
|
7295
8011
|
const ANTIGRAVITY_GLOBAL_CONFIG_SUBDIR = "config";
|
|
7296
8012
|
const ANTIGRAVITY_GLOBAL_CONFIG_DIR_PATH = (0, node_path.join)(ANTIGRAVITY_GEMINI_DIR, ANTIGRAVITY_GLOBAL_CONFIG_SUBDIR);
|
|
8013
|
+
const ANTIGRAVITY_GLOBAL_AGENTS_DIR_PATH = (0, node_path.join)(ANTIGRAVITY_GLOBAL_CONFIG_DIR_PATH, "agents");
|
|
7297
8014
|
//#endregion
|
|
7298
8015
|
//#region src/constants/antigravity-cli-paths.ts
|
|
7299
8016
|
const ANTIGRAVITY_AGENTS_DIR = ANTIGRAVITY_DIR;
|
|
@@ -7852,6 +8569,7 @@ const CLAUDECODE_PLUGIN_HOOKS_DIR = "hooks";
|
|
|
7852
8569
|
const CLAUDECODE_PLUGIN_HOOKS_FILE_NAME = "hooks.json";
|
|
7853
8570
|
const ANTIGRAVITY_PLUGIN_RULES_DIR = "rules";
|
|
7854
8571
|
const ANTIGRAVITY_PLUGIN_SKILLS_DIR = "skills";
|
|
8572
|
+
const ANTIGRAVITY_PLUGIN_AGENTS_DIR = "agents";
|
|
7855
8573
|
const ANTIGRAVITY_PLUGIN_MCP_FILE_NAME = "mcp_config.json";
|
|
7856
8574
|
const ANTIGRAVITY_PLUGIN_HOOKS_FILE_NAME = "hooks.json";
|
|
7857
8575
|
//#endregion
|
|
@@ -8076,6 +8794,7 @@ const COPILOT_SKILLS_DIR_PATH = (0, node_path.join)(GITHUB_DIR, "skills");
|
|
|
8076
8794
|
const COPILOT_AGENTS_DIR_PATH = (0, node_path.join)(GITHUB_DIR, "agents");
|
|
8077
8795
|
const COPILOT_HOOKS_DIR_PATH = (0, node_path.join)(GITHUB_DIR, "hooks");
|
|
8078
8796
|
const COPILOT_HOOKS_FILE_NAME = "copilot-hooks.json";
|
|
8797
|
+
const COPILOT_GLOBAL_HOOKS_FILE_NAME = "copilot-ide-hooks.json";
|
|
8079
8798
|
const COPILOT_MCP_DIR = ".vscode";
|
|
8080
8799
|
const COPILOT_MCP_FILE_NAME = "mcp.json";
|
|
8081
8800
|
const COPILOT_VSCODE_SETTINGS_FILE_NAME = "settings.json";
|
|
@@ -8083,6 +8802,7 @@ const COPILOTCLI_MCP_FILE_NAME = "mcp-config.json";
|
|
|
8083
8802
|
const COPILOTCLI_PROJECT_MCP_FILE_NAME = "mcp.json";
|
|
8084
8803
|
const COPILOTCLI_AGENTS_DIR_PATH = (0, node_path.join)(COPILOT_DIR, "agents");
|
|
8085
8804
|
const COPILOTCLI_HOOKS_DIR_PATH = (0, node_path.join)(COPILOT_DIR, "hooks");
|
|
8805
|
+
const COPILOT_GLOBAL_HOOKS_DIR_PATH = COPILOTCLI_HOOKS_DIR_PATH;
|
|
8086
8806
|
const COPILOTCLI_HOOKS_FILE_NAME = "copilotcli-hooks.json";
|
|
8087
8807
|
const COPILOT_SKILLS_GLOBAL_DIR_PATH = (0, node_path.join)(COPILOT_DIR, "skills");
|
|
8088
8808
|
//#endregion
|
|
@@ -8206,17 +8926,6 @@ var CopilotCommand = class CopilotCommand extends ToolCommand {
|
|
|
8206
8926
|
}
|
|
8207
8927
|
};
|
|
8208
8928
|
//#endregion
|
|
8209
|
-
//#region src/constants/cursor-paths.ts
|
|
8210
|
-
const CURSOR_DIR = ".cursor";
|
|
8211
|
-
const CURSOR_COMMANDS_DIR_PATH = (0, node_path.join)(CURSOR_DIR, "commands");
|
|
8212
|
-
const CURSOR_SKILLS_DIR_PATH = (0, node_path.join)(CURSOR_DIR, "skills");
|
|
8213
|
-
const CURSOR_AGENTS_DIR_PATH = (0, node_path.join)(CURSOR_DIR, "agents");
|
|
8214
|
-
const CURSOR_MCP_FILE_NAME = "mcp.json";
|
|
8215
|
-
const CURSOR_HOOKS_FILE_NAME = "hooks.json";
|
|
8216
|
-
const CURSOR_IGNORE_FILE_NAME = ".cursorignore";
|
|
8217
|
-
const CURSOR_PERMISSIONS_FILE_NAME = "cli.json";
|
|
8218
|
-
const CURSOR_PERMISSIONS_GLOBAL_FILE_NAME = "cli-config.json";
|
|
8219
|
-
//#endregion
|
|
8220
8929
|
//#region src/features/commands/cursor-command.ts
|
|
8221
8930
|
const CursorCommandFrontmatterSchema = zod_mini.z.looseObject({
|
|
8222
8931
|
description: zod_mini.z.optional(zod_mini.z.string()),
|
|
@@ -8762,6 +9471,223 @@ var GooseCommand = class GooseCommand extends ToolCommand {
|
|
|
8762
9471
|
}
|
|
8763
9472
|
};
|
|
8764
9473
|
//#endregion
|
|
9474
|
+
//#region src/constants/grokcli-paths.ts
|
|
9475
|
+
/**
|
|
9476
|
+
* Grok Build CLI (xAI) configuration-layout conventions.
|
|
9477
|
+
*
|
|
9478
|
+
* Single source of truth for where Grok Build expects its files. Grok Build
|
|
9479
|
+
* stores MCP servers (and other settings) in a `config.toml` under `.grok/`,
|
|
9480
|
+
* with project/global scopes resolved by the directory the CLI runs in
|
|
9481
|
+
* (`./.grok/config.toml` vs `~/.grok/config.toml`).
|
|
9482
|
+
*
|
|
9483
|
+
* Verified against `grok` 0.2.54 (`grok mcp add --help`, `grok mcp add`):
|
|
9484
|
+
* `-s project` writes `./.grok/config.toml`, `-s user` writes
|
|
9485
|
+
* `~/.grok/config.toml`, both as a TOML `[mcp_servers.<name>]` table.
|
|
9486
|
+
* @see https://docs.x.ai/build/overview
|
|
9487
|
+
*/
|
|
9488
|
+
/** Root directory for Grok Build configuration, relative to the scope root. */
|
|
9489
|
+
const GROKCLI_DIR = ".grok";
|
|
9490
|
+
/** MCP servers and other settings live in `config.toml` under `.grok/`. */
|
|
9491
|
+
const GROKCLI_MCP_FILE_NAME = "config.toml";
|
|
9492
|
+
/**
|
|
9493
|
+
* Shared Grok CLI config file (`config.toml`). MCP servers, the `[ui]`
|
|
9494
|
+
* permission mode, and other settings all live here; permissions reuse the same
|
|
9495
|
+
* file name as MCP since Grok consolidates everything into one config.
|
|
9496
|
+
*/
|
|
9497
|
+
const GROKCLI_CONFIG_FILE_NAME = "config.toml";
|
|
9498
|
+
/** Skills directory under `.grok/` (project: `./.grok/skills`, global: `~/.grok/skills`). */
|
|
9499
|
+
const GROKCLI_SKILLS_DIR_PATH = (0, node_path.join)(GROKCLI_DIR, "skills");
|
|
9500
|
+
/**
|
|
9501
|
+
* Hooks directory under `.grok/`. Grok Build discovers hook config files from
|
|
9502
|
+
* `.grok/hooks/*.json` (project) and `~/.grok/hooks/*.json` (global), each a
|
|
9503
|
+
* standalone JSON file using the Claude-Code-compatible nested `{ hooks: { … } }`
|
|
9504
|
+
* shape. rulesync writes all its hooks into a single `rulesync.json`.
|
|
9505
|
+
* @see https://docs.x.ai/build/features/hooks
|
|
9506
|
+
*/
|
|
9507
|
+
const GROKCLI_HOOKS_DIR_PATH = (0, node_path.join)(GROKCLI_DIR, "hooks");
|
|
9508
|
+
/** rulesync-managed Grok hooks file under `.grok/hooks/`. */
|
|
9509
|
+
const GROKCLI_HOOKS_FILE_NAME = "rulesync.json";
|
|
9510
|
+
/**
|
|
9511
|
+
* Subagents (agent profiles) directory under `.grok/`. Grok Build discovers
|
|
9512
|
+
* agent definitions from `.grok/agents/*.md` (project) and `~/.grok/agents/*.md`
|
|
9513
|
+
* (global), each a Markdown file with YAML frontmatter (verified via
|
|
9514
|
+
* `grok inspect`; format matches the bundled `~/.grok/bundled/agents/*.md`).
|
|
9515
|
+
*/
|
|
9516
|
+
const GROKCLI_AGENTS_DIR_PATH = (0, node_path.join)(GROKCLI_DIR, "agents");
|
|
9517
|
+
/**
|
|
9518
|
+
* Instruction file. Grok reads the AGENTS.md instruction-file family natively,
|
|
9519
|
+
* including the user-level `~/.grok/AGENTS.md` for global rules (verified via
|
|
9520
|
+
* `grok inspect`, consistent with the `.grok/` global discovery used by the
|
|
9521
|
+
* MCP/skills/subagents adapters).
|
|
9522
|
+
*/
|
|
9523
|
+
const GROKCLI_RULE_FILE_NAME = "AGENTS.md";
|
|
9524
|
+
/**
|
|
9525
|
+
* Custom slash commands directory. Grok's `find_command_paths` scans
|
|
9526
|
+
* `commands/*.md` under every discovered config dir — `.grok/commands/`
|
|
9527
|
+
* (project, walked from cwd up to the git root) and `~/.grok/commands/`
|
|
9528
|
+
* (global). The scan is **flat and non-recursive**, so subdirectory
|
|
9529
|
+
* namespacing (`git/commit.md` → `/git:commit`) is not supported the way it is
|
|
9530
|
+
* for Claude Code.
|
|
9531
|
+
*
|
|
9532
|
+
* Skills are collected before commands and win name collisions, so a
|
|
9533
|
+
* `.grok/skills/<name>/` shadows `.grok/commands/<name>.md`.
|
|
9534
|
+
* @see https://docs.x.ai/build/features/skills-plugins-marketplaces
|
|
9535
|
+
*/
|
|
9536
|
+
const GROKCLI_COMMANDS_DIR_PATH = (0, node_path.join)(GROKCLI_DIR, "commands");
|
|
9537
|
+
/**
|
|
9538
|
+
* Non-root rules directory. Grok scans `*.md` here — flat, sorted by name —
|
|
9539
|
+
* alongside the AGENTS.md family: `.grok/rules/` in each project directory it
|
|
9540
|
+
* walks, and `~/.grok/rules/` in the home scope.
|
|
9541
|
+
* @see https://docs.x.ai/build/overview
|
|
9542
|
+
*/
|
|
9543
|
+
const GROKCLI_RULES_DIR_PATH = (0, node_path.join)(GROKCLI_DIR, "rules");
|
|
9544
|
+
//#endregion
|
|
9545
|
+
//#region src/features/commands/grokcli-command.ts
|
|
9546
|
+
/**
|
|
9547
|
+
* Grok CLI custom slash commands are Markdown files under `.grok/commands/`
|
|
9548
|
+
* (project) / `~/.grok/commands/` (global), discovered by the same
|
|
9549
|
+
* Claude-Code-compatible frontmatter parser Grok uses for skills.
|
|
9550
|
+
*
|
|
9551
|
+
* Two upstream constraints shape this adapter:
|
|
9552
|
+
*
|
|
9553
|
+
* - The scan is **flat and non-recursive**, so nested namespacing is not
|
|
9554
|
+
* modelled (`supportsSubdirectory: false` flattens nested rulesync commands
|
|
9555
|
+
* onto their basename).
|
|
9556
|
+
* - Skills are collected before commands and win name collisions, so a
|
|
9557
|
+
* `.grok/skills/<name>/` shadows `.grok/commands/<name>.md`.
|
|
9558
|
+
*
|
|
9559
|
+
* `description` and `argument-hint` describe the command, while
|
|
9560
|
+
* `user-invocable` (default true) and `disable-model-invocation` (default
|
|
9561
|
+
* false) control who may invoke it — the same pair `GrokcliSkill` emits.
|
|
9562
|
+
* @see https://docs.x.ai/build/features/skills-plugins-marketplaces
|
|
9563
|
+
*/
|
|
9564
|
+
const GrokcliCommandFrontmatterSchema = zod_mini.z.looseObject({
|
|
9565
|
+
description: zod_mini.z.optional(zod_mini.z.string()),
|
|
9566
|
+
"argument-hint": zod_mini.z.optional(zod_mini.z.string()),
|
|
9567
|
+
"user-invocable": zod_mini.z.optional(zod_mini.z.boolean()),
|
|
9568
|
+
"disable-model-invocation": zod_mini.z.optional(zod_mini.z.boolean())
|
|
9569
|
+
});
|
|
9570
|
+
var GrokcliCommand = class GrokcliCommand extends ToolCommand {
|
|
9571
|
+
frontmatter;
|
|
9572
|
+
body;
|
|
9573
|
+
constructor({ frontmatter, body, ...rest }) {
|
|
9574
|
+
if (rest.validate) {
|
|
9575
|
+
const result = GrokcliCommandFrontmatterSchema.safeParse(frontmatter);
|
|
9576
|
+
if (!result.success) throw new Error(`Invalid frontmatter in ${(0, node_path.join)(rest.relativeDirPath, rest.relativeFilePath)}: ${formatError(result.error)}`);
|
|
9577
|
+
}
|
|
9578
|
+
super({
|
|
9579
|
+
...rest,
|
|
9580
|
+
fileContent: stringifyFrontmatter(body, frontmatter)
|
|
9581
|
+
});
|
|
9582
|
+
this.frontmatter = frontmatter;
|
|
9583
|
+
this.body = body;
|
|
9584
|
+
}
|
|
9585
|
+
static getSettablePaths(_options = {}) {
|
|
9586
|
+
return { relativeDirPath: GROKCLI_COMMANDS_DIR_PATH };
|
|
9587
|
+
}
|
|
9588
|
+
getBody() {
|
|
9589
|
+
return this.body;
|
|
9590
|
+
}
|
|
9591
|
+
getFrontmatter() {
|
|
9592
|
+
return this.frontmatter;
|
|
9593
|
+
}
|
|
9594
|
+
toRulesyncCommand() {
|
|
9595
|
+
const { description, ...restFields } = this.frontmatter;
|
|
9596
|
+
const rulesyncFrontmatter = {
|
|
9597
|
+
targets: ["*"],
|
|
9598
|
+
description,
|
|
9599
|
+
...Object.keys(restFields).length > 0 && { grokcli: restFields }
|
|
9600
|
+
};
|
|
9601
|
+
return new RulesyncCommand({
|
|
9602
|
+
outputRoot: ".",
|
|
9603
|
+
frontmatter: rulesyncFrontmatter,
|
|
9604
|
+
body: this.body,
|
|
9605
|
+
relativeDirPath: RulesyncCommand.getSettablePaths().relativeDirPath,
|
|
9606
|
+
relativeFilePath: this.relativeFilePath,
|
|
9607
|
+
fileContent: stringifyFrontmatter(this.body, rulesyncFrontmatter),
|
|
9608
|
+
validate: true
|
|
9609
|
+
});
|
|
9610
|
+
}
|
|
9611
|
+
static fromRulesyncCommand({ outputRoot = process.cwd(), rulesyncCommand, validate = true, global = false }) {
|
|
9612
|
+
const rulesyncFrontmatter = rulesyncCommand.getFrontmatter();
|
|
9613
|
+
const grokcliFields = rulesyncFrontmatter.grokcli ?? {};
|
|
9614
|
+
const grokcliFrontmatter = {
|
|
9615
|
+
description: rulesyncFrontmatter.description,
|
|
9616
|
+
...grokcliFields
|
|
9617
|
+
};
|
|
9618
|
+
const paths = this.getSettablePaths({ global });
|
|
9619
|
+
return new GrokcliCommand({
|
|
9620
|
+
outputRoot,
|
|
9621
|
+
frontmatter: grokcliFrontmatter,
|
|
9622
|
+
body: rulesyncCommand.getBody(),
|
|
9623
|
+
relativeDirPath: paths.relativeDirPath,
|
|
9624
|
+
relativeFilePath: rulesyncCommand.getRelativeFilePath(),
|
|
9625
|
+
validate
|
|
9626
|
+
});
|
|
9627
|
+
}
|
|
9628
|
+
validate() {
|
|
9629
|
+
if (!this.frontmatter) return {
|
|
9630
|
+
success: true,
|
|
9631
|
+
error: null
|
|
9632
|
+
};
|
|
9633
|
+
const result = GrokcliCommandFrontmatterSchema.safeParse(this.frontmatter);
|
|
9634
|
+
if (result.success) return {
|
|
9635
|
+
success: true,
|
|
9636
|
+
error: null
|
|
9637
|
+
};
|
|
9638
|
+
return {
|
|
9639
|
+
success: false,
|
|
9640
|
+
error: /* @__PURE__ */ new Error(`Invalid frontmatter in ${(0, node_path.join)(this.relativeDirPath, this.relativeFilePath)}: ${formatError(result.error)}`)
|
|
9641
|
+
};
|
|
9642
|
+
}
|
|
9643
|
+
static isTargetedByRulesyncCommand(rulesyncCommand) {
|
|
9644
|
+
return this.isTargetedByRulesyncCommandDefault({
|
|
9645
|
+
rulesyncCommand,
|
|
9646
|
+
toolTarget: "grokcli"
|
|
9647
|
+
});
|
|
9648
|
+
}
|
|
9649
|
+
/**
|
|
9650
|
+
* Warn when a rulesync skill would shadow a rulesync command.
|
|
9651
|
+
*
|
|
9652
|
+
* Grok collects skills before commands and lets skills win name collisions,
|
|
9653
|
+
* so `.grok/skills/<name>/` makes `.grok/commands/<name>.md` unreachable.
|
|
9654
|
+
* Both files are still written correctly — nothing is overwritten and no
|
|
9655
|
+
* output is lost — so this warns rather than failing the run the way the
|
|
9656
|
+
* Hermes check does, where the two surfaces really do write the same path.
|
|
9657
|
+
*/
|
|
9658
|
+
static async validateRulesyncCommands({ inputRoot, rulesyncCommands, logger }) {
|
|
9659
|
+
const commandNames = new Set(rulesyncCommands.filter((command) => this.isTargetedByRulesyncCommand(command)).map((command) => (0, node_path.basename)(command.getRelativeFilePath(), ".md")));
|
|
9660
|
+
if (commandNames.size === 0) return;
|
|
9661
|
+
const shadowed = (await findFilesByGlobs((0, node_path.join)((0, node_path.join)(inputRoot, RULESYNC_SKILLS_RELATIVE_DIR_PATH), "**", "SKILL.md"))).map((filePath) => (0, node_path.basename)((0, node_path.dirname)(filePath))).filter((skillName) => commandNames.has(skillName));
|
|
9662
|
+
if (shadowed.length > 0) logger.warn(`Grok CLI resolves skills before commands, so these skills shadow the same-named commands, which will never be reachable: ${[...new Set(shadowed)].toSorted().join(", ")}. Rename either side to make both invocable.`);
|
|
9663
|
+
}
|
|
9664
|
+
static async fromFile({ outputRoot = process.cwd(), relativeFilePath, validate = true, global = false }) {
|
|
9665
|
+
const paths = this.getSettablePaths({ global });
|
|
9666
|
+
const filePath = (0, node_path.join)(outputRoot, paths.relativeDirPath, relativeFilePath);
|
|
9667
|
+
const { frontmatter, body: content } = parseFrontmatter(await readFileContent(filePath), filePath);
|
|
9668
|
+
const result = GrokcliCommandFrontmatterSchema.safeParse(frontmatter);
|
|
9669
|
+
if (!result.success) throw new Error(`Invalid frontmatter in ${filePath}: ${formatError(result.error)}`);
|
|
9670
|
+
return new GrokcliCommand({
|
|
9671
|
+
outputRoot,
|
|
9672
|
+
relativeDirPath: paths.relativeDirPath,
|
|
9673
|
+
relativeFilePath,
|
|
9674
|
+
frontmatter: result.data,
|
|
9675
|
+
body: content.trim(),
|
|
9676
|
+
validate
|
|
9677
|
+
});
|
|
9678
|
+
}
|
|
9679
|
+
static forDeletion({ outputRoot = process.cwd(), relativeDirPath, relativeFilePath }) {
|
|
9680
|
+
return new GrokcliCommand({
|
|
9681
|
+
outputRoot,
|
|
9682
|
+
relativeDirPath,
|
|
9683
|
+
relativeFilePath,
|
|
9684
|
+
frontmatter: { description: "" },
|
|
9685
|
+
body: "",
|
|
9686
|
+
validate: false
|
|
9687
|
+
});
|
|
9688
|
+
}
|
|
9689
|
+
};
|
|
9690
|
+
//#endregion
|
|
8765
9691
|
//#region src/features/skills/tool-skill.ts
|
|
8766
9692
|
/** Ordered skill directory roots: primary first. */
|
|
8767
9693
|
function toolSkillSearchRoots(paths) {
|
|
@@ -9396,7 +10322,7 @@ def register(ctx):
|
|
|
9396
10322
|
_register_command(ctx, command)
|
|
9397
10323
|
`;
|
|
9398
10324
|
}
|
|
9399
|
-
function getEnabledPluginConfigContent$1(currentContent) {
|
|
10325
|
+
function getEnabledPluginConfigContent$1({ currentContent, global }) {
|
|
9400
10326
|
const config = parseSharedConfig({
|
|
9401
10327
|
format: "yaml",
|
|
9402
10328
|
fileContent: currentContent
|
|
@@ -9404,7 +10330,7 @@ function getEnabledPluginConfigContent$1(currentContent) {
|
|
|
9404
10330
|
const plugins = config.plugins && typeof config.plugins === "object" ? config.plugins : {};
|
|
9405
10331
|
const enabled = Array.isArray(plugins.enabled) ? plugins.enabled : [];
|
|
9406
10332
|
return applySharedConfigPatch({
|
|
9407
|
-
fileKey:
|
|
10333
|
+
fileKey: getHermesagentConfigSharedFileKey({ global }),
|
|
9408
10334
|
feature: "commands",
|
|
9409
10335
|
existingContent: currentContent,
|
|
9410
10336
|
patch: { plugins: {
|
|
@@ -9413,7 +10339,7 @@ function getEnabledPluginConfigContent$1(currentContent) {
|
|
|
9413
10339
|
} }
|
|
9414
10340
|
});
|
|
9415
10341
|
}
|
|
9416
|
-
function getDisabledHermesCommandsPluginConfigContent(currentContent) {
|
|
10342
|
+
function getDisabledHermesCommandsPluginConfigContent({ currentContent, global }) {
|
|
9417
10343
|
const config = parseSharedConfig({
|
|
9418
10344
|
format: "yaml",
|
|
9419
10345
|
fileContent: currentContent
|
|
@@ -9421,7 +10347,7 @@ function getDisabledHermesCommandsPluginConfigContent(currentContent) {
|
|
|
9421
10347
|
const plugins = config.plugins && typeof config.plugins === "object" ? config.plugins : {};
|
|
9422
10348
|
const enabled = Array.isArray(plugins.enabled) ? plugins.enabled : [];
|
|
9423
10349
|
return applySharedConfigPatch({
|
|
9424
|
-
fileKey:
|
|
10350
|
+
fileKey: getHermesagentConfigSharedFileKey({ global }),
|
|
9425
10351
|
feature: "commands",
|
|
9426
10352
|
existingContent: currentContent,
|
|
9427
10353
|
patch: { plugins: {
|
|
@@ -9437,35 +10363,36 @@ var HermesagentCommandAuxiliaryFile = class extends ToolFile {
|
|
|
9437
10363
|
error: null
|
|
9438
10364
|
};
|
|
9439
10365
|
}
|
|
9440
|
-
|
|
10366
|
+
/**
|
|
10367
|
+
* Whether this auxiliary file is the one at `relativeFilePath`, comparing
|
|
10368
|
+
* against the scope-resolved location of that canonical `.hermes/...` path.
|
|
10369
|
+
*/
|
|
10370
|
+
matchesPath(relativeFilePath) {
|
|
9441
10371
|
return this.getRelativePathFromCwd() === toPosixPath(getHermesagentRelativeFilePath({
|
|
9442
10372
|
global: this.global,
|
|
9443
|
-
relativeFilePath
|
|
10373
|
+
relativeFilePath
|
|
9444
10374
|
}));
|
|
9445
10375
|
}
|
|
10376
|
+
shouldMergeExistingFileContent() {
|
|
10377
|
+
return this.matchesPath(HERMESAGENT_CONFIG_FILE_PATH);
|
|
10378
|
+
}
|
|
9446
10379
|
setFileContent(newFileContent) {
|
|
9447
|
-
if (this.
|
|
9448
|
-
|
|
9449
|
-
|
|
9450
|
-
|
|
9451
|
-
|
|
10380
|
+
if (this.matchesPath(HERMESAGENT_CONFIG_FILE_PATH)) {
|
|
10381
|
+
super.setFileContent(getEnabledPluginConfigContent$1({
|
|
10382
|
+
currentContent: newFileContent,
|
|
10383
|
+
global: this.global
|
|
10384
|
+
}));
|
|
9452
10385
|
return;
|
|
9453
10386
|
}
|
|
9454
10387
|
super.setFileContent(newFileContent);
|
|
9455
10388
|
}
|
|
9456
10389
|
getFileContent() {
|
|
9457
|
-
if (this.
|
|
9458
|
-
|
|
9459
|
-
|
|
9460
|
-
|
|
9461
|
-
|
|
9462
|
-
|
|
9463
|
-
relativeFilePath: HERMESAGENT_RULESYNC_COMMANDS_PLUGIN_INIT_PATH
|
|
9464
|
-
}))) return getPluginInitContent$2();
|
|
9465
|
-
if (this.getRelativePathFromCwd() === toPosixPath(getHermesagentRelativeFilePath({
|
|
9466
|
-
global: this.global,
|
|
9467
|
-
relativeFilePath: HERMESAGENT_CONFIG_FILE_PATH
|
|
9468
|
-
}))) return getEnabledPluginConfigContent$1(super.getFileContent());
|
|
10390
|
+
if (this.matchesPath(HERMESAGENT_RULESYNC_COMMANDS_PLUGIN_MANIFEST_PATH)) return getPluginManifestContent$2();
|
|
10391
|
+
if (this.matchesPath(HERMESAGENT_RULESYNC_COMMANDS_PLUGIN_INIT_PATH)) return getPluginInitContent$2();
|
|
10392
|
+
if (this.matchesPath(HERMESAGENT_CONFIG_FILE_PATH)) return getEnabledPluginConfigContent$1({
|
|
10393
|
+
currentContent: super.getFileContent(),
|
|
10394
|
+
global: this.global
|
|
10395
|
+
});
|
|
9469
10396
|
return super.getFileContent();
|
|
9470
10397
|
}
|
|
9471
10398
|
};
|
|
@@ -9482,14 +10409,12 @@ var HermesagentCommand = class HermesagentCommand extends ToolCommand {
|
|
|
9482
10409
|
relativeDirPath: HERMESAGENT_RULESYNC_COMMANDS_DIR_PATH
|
|
9483
10410
|
}) };
|
|
9484
10411
|
}
|
|
9485
|
-
|
|
9486
|
-
|
|
9487
|
-
|
|
9488
|
-
|
|
9489
|
-
|
|
9490
|
-
|
|
9491
|
-
relativeFilePath: (0, node_path.basename)(HERMESAGENT_CONFIG_FILE_PATH)
|
|
9492
|
-
}];
|
|
10412
|
+
/**
|
|
10413
|
+
* `config.yaml` under every spelling the global profile root can take.
|
|
10414
|
+
* @see getHermesagentSharedConfigWritePaths
|
|
10415
|
+
*/
|
|
10416
|
+
static getExtraSharedWritePaths() {
|
|
10417
|
+
return getHermesagentSharedConfigWritePaths();
|
|
9493
10418
|
}
|
|
9494
10419
|
static async validateRulesyncCommands({ inputRoot, rulesyncCommands }) {
|
|
9495
10420
|
const commandSlugs = /* @__PURE__ */ new Set();
|
|
@@ -9514,33 +10439,28 @@ var HermesagentCommand = class HermesagentCommand extends ToolCommand {
|
|
|
9514
10439
|
}
|
|
9515
10440
|
static async getAuxiliaryFiles({ toolCommands, outputRoot, global = false, forDeletion = false }) {
|
|
9516
10441
|
if (toolCommands.length === 0 && !forDeletion) return [];
|
|
10442
|
+
const pluginDirPath = getHermesagentRelativeDirPath({
|
|
10443
|
+
global,
|
|
10444
|
+
relativeDirPath: HERMESAGENT_RULESYNC_COMMANDS_PLUGIN_DIR_PATH
|
|
10445
|
+
});
|
|
9517
10446
|
const pluginFiles = [
|
|
9518
10447
|
new HermesagentCommandAuxiliaryFile({
|
|
9519
10448
|
outputRoot,
|
|
9520
|
-
relativeDirPath:
|
|
9521
|
-
global,
|
|
9522
|
-
relativeDirPath: HERMESAGENT_RULESYNC_COMMANDS_PLUGIN_DIR_PATH
|
|
9523
|
-
}),
|
|
10449
|
+
relativeDirPath: pluginDirPath,
|
|
9524
10450
|
relativeFilePath: (0, node_path.basename)(HERMESAGENT_RULESYNC_COMMANDS_PLUGIN_MANIFEST_PATH),
|
|
9525
10451
|
fileContent: "",
|
|
9526
10452
|
global
|
|
9527
10453
|
}),
|
|
9528
10454
|
new HermesagentCommandAuxiliaryFile({
|
|
9529
10455
|
outputRoot,
|
|
9530
|
-
relativeDirPath:
|
|
9531
|
-
global,
|
|
9532
|
-
relativeDirPath: HERMESAGENT_RULESYNC_COMMANDS_PLUGIN_DIR_PATH
|
|
9533
|
-
}),
|
|
10456
|
+
relativeDirPath: pluginDirPath,
|
|
9534
10457
|
relativeFilePath: (0, node_path.basename)(HERMESAGENT_RULESYNC_COMMANDS_PLUGIN_OWNERSHIP_PATH),
|
|
9535
10458
|
fileContent: "Generated and owned by RuleSync.\n",
|
|
9536
10459
|
global
|
|
9537
10460
|
}),
|
|
9538
10461
|
new HermesagentCommandAuxiliaryFile({
|
|
9539
10462
|
outputRoot,
|
|
9540
|
-
relativeDirPath:
|
|
9541
|
-
global,
|
|
9542
|
-
relativeDirPath: HERMESAGENT_RULESYNC_COMMANDS_PLUGIN_DIR_PATH
|
|
9543
|
-
}),
|
|
10463
|
+
relativeDirPath: pluginDirPath,
|
|
9544
10464
|
relativeFilePath: (0, node_path.basename)(HERMESAGENT_RULESYNC_COMMANDS_PLUGIN_INIT_PATH),
|
|
9545
10465
|
fileContent: "",
|
|
9546
10466
|
global
|
|
@@ -10775,19 +11695,6 @@ var RooCommand = class RooCommand extends ToolCommand {
|
|
|
10775
11695
|
}
|
|
10776
11696
|
};
|
|
10777
11697
|
//#endregion
|
|
10778
|
-
//#region src/constants/rovodev-paths.ts
|
|
10779
|
-
const ROVODEV_DIR = ".rovodev";
|
|
10780
|
-
const ROVODEV_SKILLS_DIR_PATH = (0, node_path.join)(ROVODEV_DIR, "skills");
|
|
10781
|
-
const ROVODEV_SUBAGENTS_DIR_PATH = (0, node_path.join)(ROVODEV_DIR, "subagents");
|
|
10782
|
-
const ROVODEV_MODULAR_RULES_DIR_PATH = (0, node_path.join)(ROVODEV_DIR, ".rulesync", "modular-rules");
|
|
10783
|
-
const ROVODEV_RULE_FILE_NAME = "AGENTS.md";
|
|
10784
|
-
const ROVODEV_LEGACY_RULE_FILE_NAME = "AGENTS.local.md";
|
|
10785
|
-
const ROVODEV_MCP_FILE_NAME = "mcp.json";
|
|
10786
|
-
const ROVODEV_CONFIG_FILE_NAME = "config.yml";
|
|
10787
|
-
const ROVODEV_AGENTS_SKILLS_DIR_PATH = (0, node_path.join)(".agents", "skills");
|
|
10788
|
-
const ROVODEV_PROMPTS_FILE_NAME = "prompts.yml";
|
|
10789
|
-
const ROVODEV_PROMPTS_DIR_PATH = (0, node_path.join)(ROVODEV_DIR, "prompts");
|
|
10790
|
-
//#endregion
|
|
10791
11698
|
//#region src/features/commands/rovodev-command.ts
|
|
10792
11699
|
/**
|
|
10793
11700
|
* Rovo Dev CLI "saved prompts": a file-based custom-command surface made of a
|
|
@@ -11371,6 +12278,16 @@ const toolCommandFactories = /* @__PURE__ */ new Map([
|
|
|
11371
12278
|
supportsSubdirectory: false
|
|
11372
12279
|
}
|
|
11373
12280
|
}],
|
|
12281
|
+
["grokcli", {
|
|
12282
|
+
class: GrokcliCommand,
|
|
12283
|
+
meta: {
|
|
12284
|
+
extension: "md",
|
|
12285
|
+
supportsProject: true,
|
|
12286
|
+
supportsGlobal: true,
|
|
12287
|
+
isSimulated: false,
|
|
12288
|
+
supportsSubdirectory: false
|
|
12289
|
+
}
|
|
12290
|
+
}],
|
|
11374
12291
|
["hermesagent", {
|
|
11375
12292
|
class: HermesagentCommand,
|
|
11376
12293
|
meta: {
|
|
@@ -11564,7 +12481,8 @@ var CommandsProcessor = class extends FeatureProcessor {
|
|
|
11564
12481
|
const factory = this.getFactory(this.toolTarget);
|
|
11565
12482
|
await factory.class.validateRulesyncCommands?.({
|
|
11566
12483
|
inputRoot: this.inputRoot,
|
|
11567
|
-
rulesyncCommands
|
|
12484
|
+
rulesyncCommands,
|
|
12485
|
+
logger: this.logger
|
|
11568
12486
|
});
|
|
11569
12487
|
const flattenedPathOrigins = /* @__PURE__ */ new Map();
|
|
11570
12488
|
const toolCommands = rulesyncCommands.map((rulesyncCommand) => {
|
|
@@ -11702,7 +12620,10 @@ var CommandsProcessor = class extends FeatureProcessor {
|
|
|
11702
12620
|
}));
|
|
11703
12621
|
const currentContent = await readFileContentOrNull(configPath);
|
|
11704
12622
|
if (currentContent === null) return changedCount;
|
|
11705
|
-
const nextContent = getDisabledHermesCommandsPluginConfigContent(
|
|
12623
|
+
const nextContent = getDisabledHermesCommandsPluginConfigContent({
|
|
12624
|
+
currentContent,
|
|
12625
|
+
global: this.global
|
|
12626
|
+
});
|
|
11706
12627
|
if (nextContent === currentContent) return changedCount;
|
|
11707
12628
|
if (this.dryRun) this.logger.info(`[DRY RUN] Would write: ${configPath}`);
|
|
11708
12629
|
else await writeFileContent(configPath, nextContent);
|
|
@@ -11985,6 +12906,13 @@ function groupDefinitionsByMatcher(definitions) {
|
|
|
11985
12906
|
}
|
|
11986
12907
|
return byMatcher;
|
|
11987
12908
|
}
|
|
12909
|
+
/** `$CLAUDE_PROJECT_DIR` -> `${CLAUDE_PROJECT_DIR}`, the form the tool substitutes. */
|
|
12910
|
+
function bracePlaceholder(projectDirVar) {
|
|
12911
|
+
return `\${${projectDirVar.replace(/^\$/, "")}}`;
|
|
12912
|
+
}
|
|
12913
|
+
function stripSurroundingQuotes(value) {
|
|
12914
|
+
return value.replace(/^(["'])(.*)\1$/, "$2").replace(/^["']/, "");
|
|
12915
|
+
}
|
|
11988
12916
|
/**
|
|
11989
12917
|
* Apply the optional project directory variable prefix to a command string.
|
|
11990
12918
|
*/
|
|
@@ -11994,8 +12922,10 @@ function applyCommandPrefix({ def, converterConfig }) {
|
|
|
11994
12922
|
const unquotedCommand = trimmedCommand?.replace(/^["']/, "");
|
|
11995
12923
|
const isDotRelativeCommand = unquotedCommand?.startsWith(".") ?? false;
|
|
11996
12924
|
const isAbsoluteCommand = typeof unquotedCommand === "string" && (node_path.posix.isAbsolute(unquotedCommand) || node_path.win32.isAbsolute(unquotedCommand) || unquotedCommand.startsWith("~/"));
|
|
12925
|
+
const isExecForm = (converterConfig.arrayPassthroughFields?.some(({ canonical }) => canonical === "args") ?? false) && Array.isArray(def.args);
|
|
11997
12926
|
if (!(converterConfig.projectDirVar !== "" && typeof trimmedCommand === "string" && !trimmedCommand.startsWith("$") && !isAbsoluteCommand && (!converterConfig.prefixDotRelativeCommandsOnly || isDotRelativeCommand)) || typeof trimmedCommand !== "string") return def.command;
|
|
11998
12927
|
const relativeCommand = trimmedCommand.replace(/^(["'])\.\//, "$1").replace(/^\.\//, "");
|
|
12928
|
+
if (isExecForm) return `${bracePlaceholder(converterConfig.projectDirVar)}/${stripSurroundingQuotes(relativeCommand)}`;
|
|
11999
12929
|
return `"${converterConfig.projectDirVar}"/${relativeCommand}`;
|
|
12000
12930
|
}
|
|
12001
12931
|
/**
|
|
@@ -12003,8 +12933,11 @@ function applyCommandPrefix({ def, converterConfig }) {
|
|
|
12003
12933
|
* canonical field name to its (possibly renamed) tool field name. Only boolean
|
|
12004
12934
|
* values are carried through.
|
|
12005
12935
|
*/
|
|
12006
|
-
function emitBooleanPassthroughFields({ def, converterConfig }) {
|
|
12007
|
-
return Object.fromEntries((converterConfig.booleanPassthroughFields ?? []).filter(({ canonical
|
|
12936
|
+
function emitBooleanPassthroughFields({ def, hookType, converterConfig }) {
|
|
12937
|
+
return Object.fromEntries((converterConfig.booleanPassthroughFields ?? []).filter(({ canonical, commandOnly }) => {
|
|
12938
|
+
if (commandOnly === true && hookType !== "command") return false;
|
|
12939
|
+
return typeof def[canonical] === "boolean";
|
|
12940
|
+
}).map(({ canonical, tool }) => [tool, def[canonical]]));
|
|
12008
12941
|
}
|
|
12009
12942
|
/**
|
|
12010
12943
|
* Import the configured boolean passthrough fields back into canonical fields,
|
|
@@ -12018,8 +12951,11 @@ function importBooleanPassthroughFields({ h, converterConfig }) {
|
|
|
12018
12951
|
* canonical field name to its (possibly renamed) tool field name. Only non-empty
|
|
12019
12952
|
* string values are carried through.
|
|
12020
12953
|
*/
|
|
12021
|
-
function emitStringPassthroughFields({ def, converterConfig }) {
|
|
12022
|
-
return Object.fromEntries((converterConfig.stringPassthroughFields ?? []).filter(({ canonical
|
|
12954
|
+
function emitStringPassthroughFields({ def, hookType, converterConfig }) {
|
|
12955
|
+
return Object.fromEntries((converterConfig.stringPassthroughFields ?? []).filter(({ canonical, commandOnly }) => {
|
|
12956
|
+
if (commandOnly === true && hookType !== "command") return false;
|
|
12957
|
+
return typeof def[canonical] === "string" && def[canonical] !== "";
|
|
12958
|
+
}).map(({ canonical, tool }) => [tool, def[canonical]]));
|
|
12023
12959
|
}
|
|
12024
12960
|
/**
|
|
12025
12961
|
* Import the configured string passthrough fields back into canonical fields,
|
|
@@ -12032,8 +12968,11 @@ function importStringPassthroughFields({ h, converterConfig }) {
|
|
|
12032
12968
|
/**
|
|
12033
12969
|
* Emit the configured string-array passthrough fields on the tool side.
|
|
12034
12970
|
*/
|
|
12035
|
-
function emitArrayPassthroughFields({ def, converterConfig }) {
|
|
12036
|
-
return Object.fromEntries((converterConfig.arrayPassthroughFields ?? []).filter(({ canonical
|
|
12971
|
+
function emitArrayPassthroughFields({ def, hookType, converterConfig }) {
|
|
12972
|
+
return Object.fromEntries((converterConfig.arrayPassthroughFields ?? []).filter(({ canonical, commandOnly }) => {
|
|
12973
|
+
if (commandOnly === true && hookType !== "command") return false;
|
|
12974
|
+
return isStringArray(def[canonical]);
|
|
12975
|
+
}).map(({ canonical, tool }) => [tool, def[canonical]]));
|
|
12037
12976
|
}
|
|
12038
12977
|
/**
|
|
12039
12978
|
* Import the configured string-array passthrough fields, reversing
|
|
@@ -12110,14 +13049,17 @@ function buildToolHooks({ defs, converterConfig }) {
|
|
|
12110
13049
|
hooks.push({
|
|
12111
13050
|
...emitBooleanPassthroughFields({
|
|
12112
13051
|
def,
|
|
13052
|
+
hookType,
|
|
12113
13053
|
converterConfig
|
|
12114
13054
|
}),
|
|
12115
13055
|
...emitStringPassthroughFields({
|
|
12116
13056
|
def,
|
|
13057
|
+
hookType,
|
|
12117
13058
|
converterConfig
|
|
12118
13059
|
}),
|
|
12119
13060
|
...emitArrayPassthroughFields({
|
|
12120
13061
|
def,
|
|
13062
|
+
hookType,
|
|
12121
13063
|
converterConfig
|
|
12122
13064
|
}),
|
|
12123
13065
|
type: hookType,
|
|
@@ -12201,6 +13143,8 @@ function stripCommandPrefix({ command, converterConfig }) {
|
|
|
12201
13143
|
if (converterConfig.projectDirVar === "" || typeof cmd !== "string") return cmd;
|
|
12202
13144
|
const quotedPrefix = `"${converterConfig.projectDirVar}"/`;
|
|
12203
13145
|
if (cmd.startsWith(quotedPrefix)) return `./${cmd.slice(quotedPrefix.length)}`;
|
|
13146
|
+
const bracedPrefix = `${bracePlaceholder(converterConfig.projectDirVar)}/`;
|
|
13147
|
+
if (cmd.startsWith(bracedPrefix)) return `./${cmd.slice(bracedPrefix.length)}`;
|
|
12204
13148
|
if (cmd.includes(`${converterConfig.projectDirVar}/`)) {
|
|
12205
13149
|
const escapedVar = converterConfig.projectDirVar.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
12206
13150
|
return cmd.replace(new RegExp(`^${escapedVar}\\/?`), "./");
|
|
@@ -12748,7 +13692,10 @@ const CLAUDE_CONVERTER_CONFIG = {
|
|
|
12748
13692
|
"taskCreated",
|
|
12749
13693
|
"taskCompleted",
|
|
12750
13694
|
"teammateIdle",
|
|
12751
|
-
"cwdChanged"
|
|
13695
|
+
"cwdChanged",
|
|
13696
|
+
"beforeSubmitPrompt",
|
|
13697
|
+
"stop",
|
|
13698
|
+
"directoryAdded"
|
|
12752
13699
|
]),
|
|
12753
13700
|
supportedHookTypes: /* @__PURE__ */ new Set([
|
|
12754
13701
|
"command",
|
|
@@ -12758,9 +13705,45 @@ const CLAUDE_CONVERTER_CONFIG = {
|
|
|
12758
13705
|
"agent"
|
|
12759
13706
|
]),
|
|
12760
13707
|
emitsPromptModel: true,
|
|
12761
|
-
stringPassthroughFields: [
|
|
12762
|
-
|
|
12763
|
-
|
|
13708
|
+
stringPassthroughFields: [
|
|
13709
|
+
{
|
|
13710
|
+
canonical: "if",
|
|
13711
|
+
tool: "if"
|
|
13712
|
+
},
|
|
13713
|
+
{
|
|
13714
|
+
canonical: "statusMessage",
|
|
13715
|
+
tool: "statusMessage"
|
|
13716
|
+
},
|
|
13717
|
+
{
|
|
13718
|
+
canonical: "shell",
|
|
13719
|
+
tool: "shell",
|
|
13720
|
+
commandOnly: true
|
|
13721
|
+
}
|
|
13722
|
+
],
|
|
13723
|
+
booleanPassthroughFields: [
|
|
13724
|
+
{
|
|
13725
|
+
canonical: "once",
|
|
13726
|
+
tool: "once"
|
|
13727
|
+
},
|
|
13728
|
+
{
|
|
13729
|
+
canonical: "async",
|
|
13730
|
+
tool: "async",
|
|
13731
|
+
commandOnly: true
|
|
13732
|
+
},
|
|
13733
|
+
{
|
|
13734
|
+
canonical: "asyncRewake",
|
|
13735
|
+
tool: "asyncRewake",
|
|
13736
|
+
commandOnly: true
|
|
13737
|
+
},
|
|
13738
|
+
{
|
|
13739
|
+
canonical: "continueOnBlock",
|
|
13740
|
+
tool: "continueOnBlock"
|
|
13741
|
+
}
|
|
13742
|
+
],
|
|
13743
|
+
arrayPassthroughFields: [{
|
|
13744
|
+
canonical: "args",
|
|
13745
|
+
tool: "args",
|
|
13746
|
+
commandOnly: true
|
|
12764
13747
|
}]
|
|
12765
13748
|
};
|
|
12766
13749
|
var ClaudecodeHooks = class extends ToolHooks {
|
|
@@ -12868,7 +13851,14 @@ const CODEXCLI_CONVERTER_CONFIG = {
|
|
|
12868
13851
|
toolToCanonicalEventNames: CODEXCLI_TO_CANONICAL_EVENT_NAMES,
|
|
12869
13852
|
projectDirVar: "",
|
|
12870
13853
|
supportedHookTypes: /* @__PURE__ */ new Set(["command"]),
|
|
12871
|
-
passthroughFields: ["name", "description"]
|
|
13854
|
+
passthroughFields: ["name", "description"],
|
|
13855
|
+
stringPassthroughFields: [{
|
|
13856
|
+
canonical: "commandWindows",
|
|
13857
|
+
tool: "commandWindows"
|
|
13858
|
+
}, {
|
|
13859
|
+
canonical: "statusMessage",
|
|
13860
|
+
tool: "statusMessage"
|
|
13861
|
+
}]
|
|
12872
13862
|
};
|
|
12873
13863
|
/**
|
|
12874
13864
|
* Build the content for `.codex/config.toml`, cleaning up the deprecated `codex_hooks` key.
|
|
@@ -13116,7 +14106,11 @@ var CopilotHooks = class CopilotHooks extends ToolHooks {
|
|
|
13116
14106
|
fileContent: params.fileContent ?? "{}"
|
|
13117
14107
|
});
|
|
13118
14108
|
}
|
|
13119
|
-
static getSettablePaths(
|
|
14109
|
+
static getSettablePaths({ global = false } = {}) {
|
|
14110
|
+
if (global) return {
|
|
14111
|
+
relativeDirPath: COPILOT_GLOBAL_HOOKS_DIR_PATH,
|
|
14112
|
+
relativeFilePath: COPILOT_GLOBAL_HOOKS_FILE_NAME
|
|
14113
|
+
};
|
|
13120
14114
|
return {
|
|
13121
14115
|
relativeDirPath: COPILOT_HOOKS_DIR_PATH,
|
|
13122
14116
|
relativeFilePath: COPILOT_HOOKS_FILE_NAME
|
|
@@ -13130,11 +14124,12 @@ var CopilotHooks = class CopilotHooks extends ToolHooks {
|
|
|
13130
14124
|
relativeDirPath: paths.relativeDirPath,
|
|
13131
14125
|
relativeFilePath: paths.relativeFilePath,
|
|
13132
14126
|
fileContent,
|
|
13133
|
-
validate
|
|
14127
|
+
validate,
|
|
14128
|
+
global
|
|
13134
14129
|
});
|
|
13135
14130
|
}
|
|
13136
|
-
static async fromRulesyncHooks({ outputRoot = process.cwd(), rulesyncHooks, validate = true }) {
|
|
13137
|
-
const paths = CopilotHooks.getSettablePaths();
|
|
14131
|
+
static async fromRulesyncHooks({ outputRoot = process.cwd(), rulesyncHooks, validate = true, global = false }) {
|
|
14132
|
+
const paths = CopilotHooks.getSettablePaths({ global });
|
|
13138
14133
|
const copilotHooks = canonicalToCopilotHooks(rulesyncHooks.getJson());
|
|
13139
14134
|
const fileContent = JSON.stringify({
|
|
13140
14135
|
version: 1,
|
|
@@ -13145,7 +14140,8 @@ var CopilotHooks = class CopilotHooks extends ToolHooks {
|
|
|
13145
14140
|
relativeDirPath: paths.relativeDirPath,
|
|
13146
14141
|
relativeFilePath: paths.relativeFilePath,
|
|
13147
14142
|
fileContent,
|
|
13148
|
-
validate
|
|
14143
|
+
validate,
|
|
14144
|
+
global
|
|
13149
14145
|
});
|
|
13150
14146
|
}
|
|
13151
14147
|
toRulesyncHooks(options) {
|
|
@@ -13189,10 +14185,16 @@ var CopilotHooks = class CopilotHooks extends ToolHooks {
|
|
|
13189
14185
|
* (`sessionStart`, `sessionEnd`, `userPromptSubmitted`, `preToolUse`,
|
|
13190
14186
|
* `postToolUse`, `postToolUseFailure`, `agentStop`, `subagentStart`,
|
|
13191
14187
|
* `subagentStop`, `errorOccurred`, `preCompact`, `permissionRequest`,
|
|
13192
|
-
* `notification`, `preMcpToolCall`). Each entry
|
|
14188
|
+
* `notification`, `userPromptTransformed`, `preMcpToolCall`). Each entry
|
|
14189
|
+
* supports three hook types:
|
|
13193
14190
|
*
|
|
13194
14191
|
* - `command` — the `bash` / `powershell` command-field shape with optional
|
|
13195
|
-
* `timeoutSec`, plus optional `cwd` / `env`.
|
|
14192
|
+
* `timeoutSec`, plus optional `cwd` / `env`. Upstream also accepts the
|
|
14193
|
+
* portable `command` field (copied to both shells when neither is present)
|
|
14194
|
+
* and `timeout` as an alias for `timeoutSec`; rulesync reads both on import.
|
|
14195
|
+
* On generate the canonical `shell` selector picks `bash` or `powershell`,
|
|
14196
|
+
* and without it the portable `command` field is written — so the generated
|
|
14197
|
+
* file does not depend on the machine rulesync ran on.
|
|
13196
14198
|
* - `prompt` — a `prompt` string (Copilot CLI only honors prompt hooks on
|
|
13197
14199
|
* `sessionStart`, so prompt hooks on other events are skipped).
|
|
13198
14200
|
* - `http` — `url` / `headers` / `allowedEnvVars` with optional `timeoutSec`.
|
|
@@ -13218,11 +14220,11 @@ var CopilotHooks = class CopilotHooks extends ToolHooks {
|
|
|
13218
14220
|
* all rulesync-managed Copilot CLI files under the single `~/.copilot/`
|
|
13219
14221
|
* root and will revisit if the spec later mandates an alternate layout.
|
|
13220
14222
|
*
|
|
13221
|
-
* Hook entries on
|
|
13222
|
-
*
|
|
13223
|
-
* emitted on those events and dropped (with a warning) on any other event,
|
|
13224
|
-
* which never honors matchers.
|
|
13225
|
-
*
|
|
14223
|
+
* Hook entries on the six matcher-aware events (see
|
|
14224
|
+
* {@link COPILOTCLI_MATCHER_EVENTS}) may carry an optional `matcher` regex; it
|
|
14225
|
+
* is emitted on those events and dropped (with a warning) on any other event,
|
|
14226
|
+
* which never honors matchers.
|
|
14227
|
+
* Reference: https://docs.github.com/en/copilot/reference/hooks-reference
|
|
13226
14228
|
*
|
|
13227
14229
|
* The output JSON schema and platform-specific `bash` / `powershell` command
|
|
13228
14230
|
* field selection match `copilot-hooks.ts`, but the event surface diverges (the
|
|
@@ -13237,19 +14239,30 @@ var CopilotHooks = class CopilotHooks extends ToolHooks {
|
|
|
13237
14239
|
* `Edit|Write`) are now honored instead of silently dropped").
|
|
13238
14240
|
* @see https://docs.github.com/en/copilot/reference/hooks-reference
|
|
13239
14241
|
*/
|
|
13240
|
-
const COPILOTCLI_MATCHER_EVENTS = /* @__PURE__ */ new Set([
|
|
14242
|
+
const COPILOTCLI_MATCHER_EVENTS = /* @__PURE__ */ new Set([
|
|
14243
|
+
"preToolUse",
|
|
14244
|
+
"postToolUse",
|
|
14245
|
+
"notification",
|
|
14246
|
+
"permissionRequest",
|
|
14247
|
+
"preCompact",
|
|
14248
|
+
"subagentStart"
|
|
14249
|
+
]);
|
|
14250
|
+
/** Human-readable list of the matcher-aware events, for the drop warning. */
|
|
14251
|
+
const COPILOTCLI_MATCHER_EVENTS_LABEL = [...COPILOTCLI_MATCHER_EVENTS].join("/");
|
|
13241
14252
|
const CopilotCliHookEntrySchema = zod_mini.z.looseObject({
|
|
13242
14253
|
type: zod_mini.z._default(zod_mini.z.string(), "command"),
|
|
13243
14254
|
matcher: zod_mini.z.optional(zod_mini.z.string()),
|
|
13244
14255
|
bash: zod_mini.z.optional(zod_mini.z.string()),
|
|
13245
14256
|
powershell: zod_mini.z.optional(zod_mini.z.string()),
|
|
14257
|
+
command: zod_mini.z.optional(zod_mini.z.string()),
|
|
13246
14258
|
prompt: zod_mini.z.optional(zod_mini.z.string()),
|
|
13247
14259
|
url: zod_mini.z.optional(zod_mini.z.string()),
|
|
13248
14260
|
headers: zod_mini.z.optional(zod_mini.z.record(zod_mini.z.string(), zod_mini.z.string())),
|
|
13249
14261
|
allowedEnvVars: zod_mini.z.optional(zod_mini.z.array(zod_mini.z.string())),
|
|
13250
14262
|
cwd: zod_mini.z.optional(zod_mini.z.string()),
|
|
13251
14263
|
env: zod_mini.z.optional(zod_mini.z.record(zod_mini.z.string(), zod_mini.z.string())),
|
|
13252
|
-
timeoutSec: zod_mini.z.optional(zod_mini.z.number())
|
|
14264
|
+
timeoutSec: zod_mini.z.optional(zod_mini.z.number()),
|
|
14265
|
+
timeout: zod_mini.z.optional(zod_mini.z.number())
|
|
13253
14266
|
});
|
|
13254
14267
|
/** Filter the shared config hooks down to events the Copilot CLI supports. */
|
|
13255
14268
|
function filterSupportedCopilotCliHooks(hooks) {
|
|
@@ -13260,21 +14273,21 @@ function filterSupportedCopilotCliHooks(hooks) {
|
|
|
13260
14273
|
}
|
|
13261
14274
|
/**
|
|
13262
14275
|
* Resolve the `matcher` part for an exported entry. Copilot CLI honors `matcher`
|
|
13263
|
-
* only on
|
|
13264
|
-
* silently dropped by the CLI, so we drop it here with
|
|
13265
|
-
* emitting a dead field.
|
|
14276
|
+
* only on the events listed in {@link COPILOTCLI_MATCHER_EVENTS}; on any other
|
|
14277
|
+
* event a matcher would be silently dropped by the CLI, so we drop it here with
|
|
14278
|
+
* a warning rather than emitting a dead field.
|
|
13266
14279
|
*/
|
|
13267
14280
|
function resolveExportMatcherPart({ matcher, matcherSupported, eventName, logger }) {
|
|
13268
14281
|
if (matcher === void 0 || matcher === null || matcher === "") return {};
|
|
13269
14282
|
if (matcherSupported) return { matcher };
|
|
13270
|
-
logger?.warn(`Copilot CLI hook matchers are only honored on
|
|
14283
|
+
logger?.warn(`Copilot CLI hook matchers are only honored on ${COPILOTCLI_MATCHER_EVENTS_LABEL}; dropping matcher "${matcher}" on '${eventName}'.`);
|
|
13271
14284
|
return {};
|
|
13272
14285
|
}
|
|
13273
14286
|
/**
|
|
13274
14287
|
* Build the exported entries for a single canonical event. Returns an empty
|
|
13275
14288
|
* array when no entries are emitted (e.g. all prompt hooks were skipped).
|
|
13276
14289
|
*/
|
|
13277
|
-
function buildCopilotCliEntriesForEvent({ eventName, definitions, canonicalSchemaKeys,
|
|
14290
|
+
function buildCopilotCliEntriesForEvent({ eventName, definitions, canonicalSchemaKeys, logger }) {
|
|
13278
14291
|
const matcherSupported = COPILOTCLI_MATCHER_EVENTS.has(eventName);
|
|
13279
14292
|
const entries = [];
|
|
13280
14293
|
for (const def of definitions) {
|
|
@@ -13310,22 +14323,24 @@ function buildCopilotCliEntriesForEvent({ eventName, definitions, canonicalSchem
|
|
|
13310
14323
|
...timeoutPart,
|
|
13311
14324
|
...rest
|
|
13312
14325
|
});
|
|
13313
|
-
else if (hookType === "command")
|
|
13314
|
-
|
|
13315
|
-
|
|
13316
|
-
|
|
13317
|
-
|
|
13318
|
-
|
|
13319
|
-
|
|
13320
|
-
|
|
13321
|
-
|
|
13322
|
-
|
|
14326
|
+
else if (hookType === "command") {
|
|
14327
|
+
const commandField = def.shell ?? "command";
|
|
14328
|
+
entries.push({
|
|
14329
|
+
type: "command",
|
|
14330
|
+
...matcherPart,
|
|
14331
|
+
...compact({
|
|
14332
|
+
[commandField]: def.command,
|
|
14333
|
+
env: def.env
|
|
14334
|
+
}),
|
|
14335
|
+
...timeoutPart,
|
|
14336
|
+
...rest
|
|
14337
|
+
});
|
|
14338
|
+
}
|
|
13323
14339
|
}
|
|
13324
14340
|
return entries;
|
|
13325
14341
|
}
|
|
13326
14342
|
function canonicalToCopilotCliHooks(config, logger) {
|
|
13327
14343
|
const canonicalSchemaKeys = Object.keys(HookDefinitionSchema.shape);
|
|
13328
|
-
const commandField = process.platform === "win32" ? "powershell" : "bash";
|
|
13329
14344
|
const effectiveHooks = {
|
|
13330
14345
|
...filterSupportedCopilotCliHooks(config.hooks),
|
|
13331
14346
|
...config.copilot?.hooks,
|
|
@@ -13338,7 +14353,6 @@ function canonicalToCopilotCliHooks(config, logger) {
|
|
|
13338
14353
|
eventName,
|
|
13339
14354
|
definitions,
|
|
13340
14355
|
canonicalSchemaKeys,
|
|
13341
|
-
commandField,
|
|
13342
14356
|
logger
|
|
13343
14357
|
});
|
|
13344
14358
|
if (entries.length > 0) out[copilotEventName] = entries;
|
|
@@ -13355,6 +14369,13 @@ function importPassthrough(entry) {
|
|
|
13355
14369
|
if (entry.env !== void 0) passthrough.env = entry.env;
|
|
13356
14370
|
return passthrough;
|
|
13357
14371
|
}
|
|
14372
|
+
/**
|
|
14373
|
+
* Resolve the canonical command and its `shell` selector from an imported entry.
|
|
14374
|
+
*
|
|
14375
|
+
* A shell-specific field carries its `shell` through so re-export writes the
|
|
14376
|
+
* same field back. An entry using only the portable `command` field leaves
|
|
14377
|
+
* `shell` unset, which re-export renders as the portable field again.
|
|
14378
|
+
*/
|
|
13358
14379
|
function resolveImportCommand(entry, logger) {
|
|
13359
14380
|
const hasBash = typeof entry.bash === "string";
|
|
13360
14381
|
const hasPowershell = typeof entry.powershell === "string";
|
|
@@ -13363,9 +14384,22 @@ function resolveImportCommand(entry, logger) {
|
|
|
13363
14384
|
const chosen = isWindows ? "powershell" : "bash";
|
|
13364
14385
|
const ignored = isWindows ? "bash" : "powershell";
|
|
13365
14386
|
logger?.warn(`Copilot CLI hook has both bash and powershell commands; using ${chosen} and ignoring ${ignored} on this platform.`);
|
|
13366
|
-
return isWindows ?
|
|
13367
|
-
|
|
13368
|
-
|
|
14387
|
+
return isWindows ? {
|
|
14388
|
+
command: entry.powershell,
|
|
14389
|
+
shell: "powershell"
|
|
14390
|
+
} : {
|
|
14391
|
+
command: entry.bash,
|
|
14392
|
+
shell: "bash"
|
|
14393
|
+
};
|
|
14394
|
+
} else if (hasBash) return {
|
|
14395
|
+
command: entry.bash,
|
|
14396
|
+
shell: "bash"
|
|
14397
|
+
};
|
|
14398
|
+
else if (hasPowershell) return {
|
|
14399
|
+
command: entry.powershell,
|
|
14400
|
+
shell: "powershell"
|
|
14401
|
+
};
|
|
14402
|
+
return typeof entry.command === "string" ? { command: entry.command } : {};
|
|
13369
14403
|
}
|
|
13370
14404
|
function copilotCliHooksToCanonical(rawHooks, logger) {
|
|
13371
14405
|
if (rawHooks === null || rawHooks === void 0 || typeof rawHooks !== "object") return {};
|
|
@@ -13378,7 +14412,7 @@ function copilotCliHooksToCanonical(rawHooks, logger) {
|
|
|
13378
14412
|
const parseResult = CopilotCliHookEntrySchema.safeParse(rawEntry);
|
|
13379
14413
|
if (!parseResult.success) continue;
|
|
13380
14414
|
const entry = parseResult.data;
|
|
13381
|
-
const timeout = entry.timeoutSec;
|
|
14415
|
+
const timeout = entry.timeoutSec ?? entry.timeout;
|
|
13382
14416
|
const timeoutPart = timeout !== void 0 ? { timeout } : {};
|
|
13383
14417
|
const matcherPart = entry.matcher !== void 0 && entry.matcher !== "" ? { matcher: entry.matcher } : {};
|
|
13384
14418
|
const passthrough = importPassthrough(entry);
|
|
@@ -13397,10 +14431,11 @@ function copilotCliHooksToCanonical(rawHooks, logger) {
|
|
|
13397
14431
|
...passthrough
|
|
13398
14432
|
});
|
|
13399
14433
|
else {
|
|
13400
|
-
const command = resolveImportCommand(entry, logger);
|
|
14434
|
+
const { command, shell } = resolveImportCommand(entry, logger);
|
|
13401
14435
|
defs.push({
|
|
13402
14436
|
type: "command",
|
|
13403
14437
|
...command !== void 0 && { command },
|
|
14438
|
+
...shell !== void 0 && { shell },
|
|
13404
14439
|
...matcherPart,
|
|
13405
14440
|
...timeoutPart,
|
|
13406
14441
|
...passthrough
|
|
@@ -14050,64 +15085,6 @@ var GooseHooks = class GooseHooks extends ToolHooks {
|
|
|
14050
15085
|
}
|
|
14051
15086
|
};
|
|
14052
15087
|
//#endregion
|
|
14053
|
-
//#region src/constants/grokcli-paths.ts
|
|
14054
|
-
/**
|
|
14055
|
-
* Grok Build CLI (xAI) configuration-layout conventions.
|
|
14056
|
-
*
|
|
14057
|
-
* Single source of truth for where Grok Build expects its files. Grok Build
|
|
14058
|
-
* stores MCP servers (and other settings) in a `config.toml` under `.grok/`,
|
|
14059
|
-
* with project/global scopes resolved by the directory the CLI runs in
|
|
14060
|
-
* (`./.grok/config.toml` vs `~/.grok/config.toml`).
|
|
14061
|
-
*
|
|
14062
|
-
* Verified against `grok` 0.2.54 (`grok mcp add --help`, `grok mcp add`):
|
|
14063
|
-
* `-s project` writes `./.grok/config.toml`, `-s user` writes
|
|
14064
|
-
* `~/.grok/config.toml`, both as a TOML `[mcp_servers.<name>]` table.
|
|
14065
|
-
* @see https://docs.x.ai/build/overview
|
|
14066
|
-
*/
|
|
14067
|
-
/** Root directory for Grok Build configuration, relative to the scope root. */
|
|
14068
|
-
const GROKCLI_DIR = ".grok";
|
|
14069
|
-
/** MCP servers and other settings live in `config.toml` under `.grok/`. */
|
|
14070
|
-
const GROKCLI_MCP_FILE_NAME = "config.toml";
|
|
14071
|
-
/**
|
|
14072
|
-
* Shared Grok CLI config file (`config.toml`). MCP servers, the `[ui]`
|
|
14073
|
-
* permission mode, and other settings all live here; permissions reuse the same
|
|
14074
|
-
* file name as MCP since Grok consolidates everything into one config.
|
|
14075
|
-
*/
|
|
14076
|
-
const GROKCLI_CONFIG_FILE_NAME = "config.toml";
|
|
14077
|
-
/** Skills directory under `.grok/` (project: `./.grok/skills`, global: `~/.grok/skills`). */
|
|
14078
|
-
const GROKCLI_SKILLS_DIR_PATH = (0, node_path.join)(GROKCLI_DIR, "skills");
|
|
14079
|
-
/**
|
|
14080
|
-
* Hooks directory under `.grok/`. Grok Build discovers hook config files from
|
|
14081
|
-
* `.grok/hooks/*.json` (project) and `~/.grok/hooks/*.json` (global), each a
|
|
14082
|
-
* standalone JSON file using the Claude-Code-compatible nested `{ hooks: { … } }`
|
|
14083
|
-
* shape. rulesync writes all its hooks into a single `rulesync.json`.
|
|
14084
|
-
* @see https://docs.x.ai/build/features/hooks
|
|
14085
|
-
*/
|
|
14086
|
-
const GROKCLI_HOOKS_DIR_PATH = (0, node_path.join)(GROKCLI_DIR, "hooks");
|
|
14087
|
-
/** rulesync-managed Grok hooks file under `.grok/hooks/`. */
|
|
14088
|
-
const GROKCLI_HOOKS_FILE_NAME = "rulesync.json";
|
|
14089
|
-
/**
|
|
14090
|
-
* Subagents (agent profiles) directory under `.grok/`. Grok Build discovers
|
|
14091
|
-
* agent definitions from `.grok/agents/*.md` (project) and `~/.grok/agents/*.md`
|
|
14092
|
-
* (global), each a Markdown file with YAML frontmatter (verified via
|
|
14093
|
-
* `grok inspect`; format matches the bundled `~/.grok/bundled/agents/*.md`).
|
|
14094
|
-
*/
|
|
14095
|
-
const GROKCLI_AGENTS_DIR_PATH = (0, node_path.join)(GROKCLI_DIR, "agents");
|
|
14096
|
-
/**
|
|
14097
|
-
* Instruction file. Grok reads the AGENTS.md instruction-file family natively,
|
|
14098
|
-
* including the user-level `~/.grok/AGENTS.md` for global rules (verified via
|
|
14099
|
-
* `grok inspect`, consistent with the `.grok/` global discovery used by the
|
|
14100
|
-
* MCP/skills/subagents adapters).
|
|
14101
|
-
*/
|
|
14102
|
-
const GROKCLI_RULE_FILE_NAME = "AGENTS.md";
|
|
14103
|
-
/**
|
|
14104
|
-
* Non-root rules directory. Grok scans `*.md` here — flat, sorted by name —
|
|
14105
|
-
* alongside the AGENTS.md family: `.grok/rules/` in each project directory it
|
|
14106
|
-
* walks, and `~/.grok/rules/` in the home scope.
|
|
14107
|
-
* @see https://docs.x.ai/build/overview
|
|
14108
|
-
*/
|
|
14109
|
-
const GROKCLI_RULES_DIR_PATH = (0, node_path.join)(GROKCLI_DIR, "rules");
|
|
14110
|
-
//#endregion
|
|
14111
15088
|
//#region src/features/hooks/grokcli-hooks.ts
|
|
14112
15089
|
const GROKCLI_CONVERTER_CONFIG = {
|
|
14113
15090
|
supportedEvents: GROKCLI_HOOK_EVENTS,
|
|
@@ -14353,6 +15330,13 @@ var HermesagentHooks = class HermesagentHooks extends ToolHooks {
|
|
|
14353
15330
|
relativeFilePath: HERMESAGENT_CONFIG_FILE_NAME
|
|
14354
15331
|
};
|
|
14355
15332
|
}
|
|
15333
|
+
/**
|
|
15334
|
+
* `config.yaml` under every spelling the global profile root can take.
|
|
15335
|
+
* @see getHermesagentSharedConfigWritePaths
|
|
15336
|
+
*/
|
|
15337
|
+
static getExtraSharedWritePaths() {
|
|
15338
|
+
return getHermesagentSharedConfigWritePaths();
|
|
15339
|
+
}
|
|
14356
15340
|
constructor(params) {
|
|
14357
15341
|
super({
|
|
14358
15342
|
...params,
|
|
@@ -14390,7 +15374,7 @@ var HermesagentHooks = class HermesagentHooks extends ToolHooks {
|
|
|
14390
15374
|
}
|
|
14391
15375
|
setFileContent(fileContent) {
|
|
14392
15376
|
this.fileContent = applySharedConfigPatch({
|
|
14393
|
-
fileKey:
|
|
15377
|
+
fileKey: getHermesagentConfigSharedFileKey({ global: this.global }),
|
|
14394
15378
|
feature: "hooks",
|
|
14395
15379
|
existingContent: fileContent,
|
|
14396
15380
|
patch: parseSharedConfig({
|
|
@@ -14540,7 +15524,23 @@ var JunieHooks = class JunieHooks extends ToolHooks {
|
|
|
14540
15524
|
};
|
|
14541
15525
|
//#endregion
|
|
14542
15526
|
//#region src/features/hooks/opencode-style-generator.ts
|
|
14543
|
-
|
|
15527
|
+
/**
|
|
15528
|
+
* Tool events emitted as named `(input, ...)` hooks rather than through the
|
|
15529
|
+
* generic `event.type` dispatch, mapped to the expression a hook's `matcher`
|
|
15530
|
+
* regex is tested against — or `null` when the hook has no matchable subject,
|
|
15531
|
+
* in which case a matcher is dropped rather than compiled against a field that
|
|
15532
|
+
* does not exist.
|
|
15533
|
+
*
|
|
15534
|
+
* `experimental.session.compacting` receives `(input, output)` and exposes no
|
|
15535
|
+
* per-invocation identifier worth matching on, so it takes `null`.
|
|
15536
|
+
*
|
|
15537
|
+
* @see https://opencode.ai/docs/plugins/
|
|
15538
|
+
*/
|
|
15539
|
+
const NAMED_HOOK_MATCHER_SUBJECTS = {
|
|
15540
|
+
"tool.execute.before": "input.tool",
|
|
15541
|
+
"tool.execute.after": "input.tool",
|
|
15542
|
+
"experimental.session.compacting": null
|
|
15543
|
+
};
|
|
14544
15544
|
function escapeForTemplateLiteral(command) {
|
|
14545
15545
|
return command.replace(/\\/g, "\\\\").replace(/`/g, "\\`").replace(/\$\{/g, "\\${");
|
|
14546
15546
|
}
|
|
@@ -14573,7 +15573,7 @@ function collectOpencodeStyleHandlers({ effectiveHooks, eventMap, namedEventHand
|
|
|
14573
15573
|
});
|
|
14574
15574
|
}
|
|
14575
15575
|
if (handlers.length > 0) {
|
|
14576
|
-
const grouped =
|
|
15576
|
+
const grouped = Object.hasOwn(NAMED_HOOK_MATCHER_SUBJECTS, toolEvent) ? namedEventHandlers : genericEventHandlers;
|
|
14577
15577
|
const existing = grouped[toolEvent];
|
|
14578
15578
|
if (existing) existing.push(...handlers);
|
|
14579
15579
|
else grouped[toolEvent] = handlers;
|
|
@@ -14602,14 +15602,15 @@ function buildGenericEventBodyLines(genericEventHandlers) {
|
|
|
14602
15602
|
function buildNamedEventBodyLines(namedEventHandlers) {
|
|
14603
15603
|
const bodyLines = [];
|
|
14604
15604
|
for (const [eventName, handlers] of Object.entries(namedEventHandlers)) {
|
|
15605
|
+
const matcherSubject = NAMED_HOOK_MATCHER_SUBJECTS[eventName] ?? null;
|
|
14605
15606
|
bodyLines.push(` "${eventName}": async (input) => {`);
|
|
14606
15607
|
for (const handler of handlers) {
|
|
14607
15608
|
const escapedCommand = escapeForTemplateLiteral(handler.command);
|
|
14608
|
-
if (handler.matcher) {
|
|
15609
|
+
if (handler.matcher && matcherSubject !== null) {
|
|
14609
15610
|
const safeMatcher = validateAndSanitizeMatcher(handler.matcher);
|
|
14610
15611
|
bodyLines.push(" {");
|
|
14611
15612
|
bodyLines.push(` const __re = new RegExp("${safeMatcher}");`);
|
|
14612
|
-
bodyLines.push(` if (__re.test(
|
|
15613
|
+
bodyLines.push(` if (__re.test(${matcherSubject})) {`);
|
|
14613
15614
|
bodyLines.push(` await $\`${escapedCommand}\`;`);
|
|
14614
15615
|
bodyLines.push(" }");
|
|
14615
15616
|
bodyLines.push(" }");
|
|
@@ -14749,8 +15750,38 @@ function getKimiCodeHome() {
|
|
|
14749
15750
|
function getKimiCodeRelativeDirPath({ global, relativeDirPath = "." }) {
|
|
14750
15751
|
return global && getKimiCodeHome() ? relativeDirPath : (0, node_path.join)(KIMI_CODE_DIR, relativeDirPath);
|
|
14751
15752
|
}
|
|
15753
|
+
/**
|
|
15754
|
+
* Both spellings the shared user `config.toml` can take: under `.kimi-code/`,
|
|
15755
|
+
* or at the root of `KIMI_CODE_HOME` when that override names the profile dir.
|
|
15756
|
+
* Declared unconditionally so the derived shared-file keys — and the drift
|
|
15757
|
+
* guards checked against them — do not depend on the ambient environment.
|
|
15758
|
+
*/
|
|
15759
|
+
function getKimiCodeSharedConfigWritePaths() {
|
|
15760
|
+
return [{
|
|
15761
|
+
relativeDirPath: KIMI_CODE_DIR,
|
|
15762
|
+
relativeFilePath: KIMI_CODE_CONFIG_FILE_NAME
|
|
15763
|
+
}, {
|
|
15764
|
+
relativeDirPath: ".",
|
|
15765
|
+
relativeFilePath: KIMI_CODE_CONFIG_FILE_NAME
|
|
15766
|
+
}];
|
|
15767
|
+
}
|
|
15768
|
+
/**
|
|
15769
|
+
* The `SHARED_CONFIG_OWNERSHIP` key of the `config.toml` actually being written.
|
|
15770
|
+
* Both spellings carry the same declaration, but passing the key of the file
|
|
15771
|
+
* being written keeps the write path and the drift guards on the same entry.
|
|
15772
|
+
*/
|
|
15773
|
+
function getKimiCodeConfigSharedFileKey({ global }) {
|
|
15774
|
+
return sharedConfigFileKey({
|
|
15775
|
+
relativeDirPath: getKimiCodeRelativeDirPath({ global }),
|
|
15776
|
+
relativeFilePath: KIMI_CODE_CONFIG_FILE_NAME
|
|
15777
|
+
});
|
|
15778
|
+
}
|
|
14752
15779
|
function getKimiCodeRulesyncOutputRoot({ nativeOutputRoot, global }) {
|
|
14753
|
-
return
|
|
15780
|
+
return getToolRulesyncOutputRoot({
|
|
15781
|
+
nativeOutputRoot,
|
|
15782
|
+
global,
|
|
15783
|
+
toolHome: getKimiCodeHome
|
|
15784
|
+
});
|
|
14754
15785
|
}
|
|
14755
15786
|
//#endregion
|
|
14756
15787
|
//#region src/features/hooks/kimi-code-hooks.ts
|
|
@@ -14848,13 +15879,20 @@ var KimiCodeHooks = class KimiCodeHooks extends ToolHooks {
|
|
|
14848
15879
|
isDeletable() {
|
|
14849
15880
|
return false;
|
|
14850
15881
|
}
|
|
15882
|
+
/**
|
|
15883
|
+
* `config.toml` under both spellings its directory can take.
|
|
15884
|
+
* @see getKimiCodeSharedConfigWritePaths
|
|
15885
|
+
*/
|
|
15886
|
+
static getExtraSharedWritePaths() {
|
|
15887
|
+
return getKimiCodeSharedConfigWritePaths();
|
|
15888
|
+
}
|
|
14851
15889
|
shouldMergeExistingFileContent() {
|
|
14852
15890
|
return true;
|
|
14853
15891
|
}
|
|
14854
15892
|
setFileContent(fileContent) {
|
|
14855
15893
|
const paths = KimiCodeHooks.getSettablePaths({ global: this.global });
|
|
14856
15894
|
this.fileContent = applySharedConfigPatch({
|
|
14857
|
-
fileKey:
|
|
15895
|
+
fileKey: getKimiCodeConfigSharedFileKey({ global: this.global }),
|
|
14858
15896
|
feature: "hooks",
|
|
14859
15897
|
existingContent: fileContent,
|
|
14860
15898
|
patch: parseSharedConfig({
|
|
@@ -16245,7 +17283,7 @@ const toolHooksFactories = /* @__PURE__ */ new Map([
|
|
|
16245
17283
|
class: CopilotHooks,
|
|
16246
17284
|
meta: {
|
|
16247
17285
|
supportsProject: true,
|
|
16248
|
-
supportsGlobal:
|
|
17286
|
+
supportsGlobal: true,
|
|
16249
17287
|
supportsImport: true
|
|
16250
17288
|
},
|
|
16251
17289
|
supportedEvents: COPILOT_HOOK_EVENTS,
|
|
@@ -17545,6 +18583,151 @@ var QwencodeIgnore = class QwencodeIgnore extends ToolIgnore {
|
|
|
17545
18583
|
}
|
|
17546
18584
|
};
|
|
17547
18585
|
//#endregion
|
|
18586
|
+
//#region src/features/shared/reasonix-config-table.ts
|
|
18587
|
+
/**
|
|
18588
|
+
* Shape-narrowing helpers for the Reasonix TOML config (`reasonix.toml` /
|
|
18589
|
+
* `~/.reasonix/config.toml`), shared by the features that read-modify-write it.
|
|
18590
|
+
*
|
|
18591
|
+
* TOML is only structurally validated on parse, so a hand-edited config can
|
|
18592
|
+
* hold any type under `permissions` or inside `allow`/`ask`/`deny`. Both the
|
|
18593
|
+
* `permissions` and `ignore` adapters have to narrow the same two shapes
|
|
18594
|
+
* before merging, so the narrowing lives here once rather than being re-spelled
|
|
18595
|
+
* (and re-diverging) per feature.
|
|
18596
|
+
*/
|
|
18597
|
+
/** Keep only the string entries of a TOML array; anything else becomes `[]`. */
|
|
18598
|
+
function toReasonixStringArray(value) {
|
|
18599
|
+
if (!Array.isArray(value)) return [];
|
|
18600
|
+
return value.filter((entry) => typeof entry === "string");
|
|
18601
|
+
}
|
|
18602
|
+
/** Copy a TOML table; a non-table (scalar, array, missing) becomes `{}`. */
|
|
18603
|
+
function toReasonixTable(value) {
|
|
18604
|
+
if (!isPlainObject$1(value)) return {};
|
|
18605
|
+
return { ...value };
|
|
18606
|
+
}
|
|
18607
|
+
//#endregion
|
|
18608
|
+
//#region src/features/ignore/reasonix-ignore.ts
|
|
18609
|
+
const permissionsTableOf = (document) => toReasonixTable(document.permissions);
|
|
18610
|
+
/**
|
|
18611
|
+
* Reshape the parsed TOML document into the `permissions.allow/ask/deny` shape
|
|
18612
|
+
* {@link applyIgnoreReadDenies} operates on. Reasonix's `[permissions]` table
|
|
18613
|
+
* is Claude-Code-shaped (SPEC.md §3.7), so the entry-level ownership rule the
|
|
18614
|
+
* gateway already implements applies verbatim; only the surrounding file
|
|
18615
|
+
* format differs. Sibling keys such as `mode` pass through untouched.
|
|
18616
|
+
*/
|
|
18617
|
+
const asClaudeStyleSettings = (document) => {
|
|
18618
|
+
const table = permissionsTableOf(document);
|
|
18619
|
+
return {
|
|
18620
|
+
...document,
|
|
18621
|
+
permissions: {
|
|
18622
|
+
...table,
|
|
18623
|
+
allow: toReasonixStringArray(table.allow),
|
|
18624
|
+
ask: toReasonixStringArray(table.ask),
|
|
18625
|
+
deny: toReasonixStringArray(table.deny)
|
|
18626
|
+
}
|
|
18627
|
+
};
|
|
18628
|
+
};
|
|
18629
|
+
/**
|
|
18630
|
+
* Drop a `[permissions]` table that ended up with nothing in it, so an empty
|
|
18631
|
+
* `.rulesyncignore` does not add a bare table header to a file that never had
|
|
18632
|
+
* one.
|
|
18633
|
+
*/
|
|
18634
|
+
const withoutEmptyPermissions = (settings) => {
|
|
18635
|
+
const document = { ...settings };
|
|
18636
|
+
const permissions = document.permissions;
|
|
18637
|
+
if (isPlainObject$1(permissions) && Object.keys(permissions).length === 0) delete document.permissions;
|
|
18638
|
+
return document;
|
|
18639
|
+
};
|
|
18640
|
+
/**
|
|
18641
|
+
* Writes `.rulesyncignore` patterns as `Read(<pattern>)` entries in the
|
|
18642
|
+
* `[permissions] deny` table of `reasonix.toml` (project) /
|
|
18643
|
+
* `~/.reasonix/config.toml` (global).
|
|
18644
|
+
*
|
|
18645
|
+
* `deny` is the right target rather than `[sandbox] forbid_read`: deny rules
|
|
18646
|
+
* take glob specifiers (`Edit(docs/**)`) and are "a hard block in every mode",
|
|
18647
|
+
* while `forbid_read` is documented as absolute paths with no glob support.
|
|
18648
|
+
* @see https://github.com/esengine/DeepSeek-Reasonix/blob/main-v2/docs/SPEC.md
|
|
18649
|
+
*/
|
|
18650
|
+
var ReasonixIgnore = class ReasonixIgnore extends ToolIgnore {
|
|
18651
|
+
constructor(params) {
|
|
18652
|
+
super(params);
|
|
18653
|
+
const document = parseSharedConfig({
|
|
18654
|
+
format: "toml",
|
|
18655
|
+
fileContent: this.fileContent
|
|
18656
|
+
});
|
|
18657
|
+
this.patterns = toReasonixStringArray(permissionsTableOf(document).deny);
|
|
18658
|
+
}
|
|
18659
|
+
static getSettablePaths({ global = false } = {}) {
|
|
18660
|
+
return {
|
|
18661
|
+
relativeDirPath: global ? REASONIX_GLOBAL_DIR : ".",
|
|
18662
|
+
relativeFilePath: global ? REASONIX_GLOBAL_PERMISSIONS_FILE_NAME : REASONIX_PROJECT_PERMISSIONS_FILE_NAME
|
|
18663
|
+
};
|
|
18664
|
+
}
|
|
18665
|
+
/**
|
|
18666
|
+
* The config file also carries `[[plugins]]`, `[permissions]` rules from the
|
|
18667
|
+
* permissions feature and user-authored tables, so rulesync must never
|
|
18668
|
+
* delete it.
|
|
18669
|
+
*/
|
|
18670
|
+
isDeletable() {
|
|
18671
|
+
return false;
|
|
18672
|
+
}
|
|
18673
|
+
toRulesyncIgnore() {
|
|
18674
|
+
const rulesyncPatterns = this.patterns.filter((pattern) => isReadDenyEntry(pattern)).map((pattern) => pattern.slice(5, -1)).filter((pattern) => pattern.length > 0);
|
|
18675
|
+
return new RulesyncIgnore({
|
|
18676
|
+
outputRoot: this.outputRoot,
|
|
18677
|
+
relativeDirPath: RulesyncIgnore.getSettablePaths().recommended.relativeDirPath,
|
|
18678
|
+
relativeFilePath: RulesyncIgnore.getSettablePaths().recommended.relativeFilePath,
|
|
18679
|
+
fileContent: rulesyncPatterns.join("\n")
|
|
18680
|
+
});
|
|
18681
|
+
}
|
|
18682
|
+
static async fromRulesyncIgnore({ outputRoot = process.cwd(), rulesyncIgnore, global = false }) {
|
|
18683
|
+
const readDenies = rulesyncIgnore.getFileContent().split(/\r?\n|\r/).map((line) => line.trim()).filter((line) => line.length > 0 && !line.startsWith("#")).map((pattern) => buildReadDenyEntry(pattern));
|
|
18684
|
+
const paths = this.getSettablePaths({ global });
|
|
18685
|
+
const filePath = (0, node_path.join)(outputRoot, paths.relativeDirPath, paths.relativeFilePath);
|
|
18686
|
+
const existingDocument = parseSharedConfig({
|
|
18687
|
+
format: "toml",
|
|
18688
|
+
fileContent: await readFileContentOrNull(filePath) ?? "",
|
|
18689
|
+
filePath
|
|
18690
|
+
});
|
|
18691
|
+
const document = withoutEmptyPermissions(applyIgnoreReadDenies({
|
|
18692
|
+
settings: asClaudeStyleSettings(existingDocument),
|
|
18693
|
+
readDenies
|
|
18694
|
+
}));
|
|
18695
|
+
return new ReasonixIgnore({
|
|
18696
|
+
outputRoot,
|
|
18697
|
+
relativeDirPath: paths.relativeDirPath,
|
|
18698
|
+
relativeFilePath: paths.relativeFilePath,
|
|
18699
|
+
fileContent: stringifySharedConfig({
|
|
18700
|
+
format: "toml",
|
|
18701
|
+
document
|
|
18702
|
+
}),
|
|
18703
|
+
validate: true,
|
|
18704
|
+
global
|
|
18705
|
+
});
|
|
18706
|
+
}
|
|
18707
|
+
static async fromFile({ outputRoot = process.cwd(), validate = true, global = false }) {
|
|
18708
|
+
const paths = this.getSettablePaths({ global });
|
|
18709
|
+
const fileContent = await readFileContentOrNull((0, node_path.join)(outputRoot, paths.relativeDirPath, paths.relativeFilePath)) ?? "";
|
|
18710
|
+
return new ReasonixIgnore({
|
|
18711
|
+
outputRoot,
|
|
18712
|
+
relativeDirPath: paths.relativeDirPath,
|
|
18713
|
+
relativeFilePath: paths.relativeFilePath,
|
|
18714
|
+
fileContent,
|
|
18715
|
+
validate,
|
|
18716
|
+
global
|
|
18717
|
+
});
|
|
18718
|
+
}
|
|
18719
|
+
static forDeletion({ outputRoot = process.cwd(), relativeDirPath, relativeFilePath, global = false }) {
|
|
18720
|
+
return new ReasonixIgnore({
|
|
18721
|
+
outputRoot,
|
|
18722
|
+
relativeDirPath,
|
|
18723
|
+
relativeFilePath,
|
|
18724
|
+
fileContent: "",
|
|
18725
|
+
validate: false,
|
|
18726
|
+
global
|
|
18727
|
+
});
|
|
18728
|
+
}
|
|
18729
|
+
};
|
|
18730
|
+
//#endregion
|
|
17548
18731
|
//#region src/features/ignore/roo-ignore.ts
|
|
17549
18732
|
/**
|
|
17550
18733
|
* RooIgnore represents ignore patterns for the Roo Code AI coding assistant.
|
|
@@ -17702,6 +18885,14 @@ const ZED_GLOBAL_WIN32_DIR = (0, node_path.join)("AppData", "Roaming", "Zed");
|
|
|
17702
18885
|
function getZedGlobalDir() {
|
|
17703
18886
|
return process.platform === "win32" ? ZED_GLOBAL_WIN32_DIR : ZED_GLOBAL_DIR;
|
|
17704
18887
|
}
|
|
18888
|
+
/**
|
|
18889
|
+
* The global config dir of the OTHER platform. `getZedGlobalDir()` resolves one
|
|
18890
|
+
* spelling per platform, but the shared-write derivation (and the gateway
|
|
18891
|
+
* ownership table it is checked against) must know both on every platform.
|
|
18892
|
+
*/
|
|
18893
|
+
function getZedOtherPlatformGlobalDir() {
|
|
18894
|
+
return process.platform === "win32" ? ZED_GLOBAL_DIR : ZED_GLOBAL_WIN32_DIR;
|
|
18895
|
+
}
|
|
17705
18896
|
const ZED_SETTINGS_FILE_NAME = "settings.json";
|
|
17706
18897
|
const ZED_RULE_FILE_NAME = ".rules";
|
|
17707
18898
|
const ZED_GLOBAL_RULE_FILE_NAME = "AGENTS.md";
|
|
@@ -17714,12 +18905,20 @@ var ZedIgnore = class ZedIgnore extends ToolIgnore {
|
|
|
17714
18905
|
const jsonValue = JSON.parse(this.fileContent);
|
|
17715
18906
|
this.patterns = jsonValue.private_files ?? [];
|
|
17716
18907
|
}
|
|
17717
|
-
static getSettablePaths() {
|
|
18908
|
+
static getSettablePaths({ global = false } = {}) {
|
|
17718
18909
|
return {
|
|
17719
|
-
relativeDirPath: ZED_DIR,
|
|
18910
|
+
relativeDirPath: global ? getZedGlobalDir() : ZED_DIR,
|
|
17720
18911
|
relativeFilePath: ZED_SETTINGS_FILE_NAME
|
|
17721
18912
|
};
|
|
17722
18913
|
}
|
|
18914
|
+
/** @see getZedOtherPlatformGlobalDir */
|
|
18915
|
+
static getExtraSharedWritePaths({ global = false } = {}) {
|
|
18916
|
+
if (!global) return [];
|
|
18917
|
+
return [{
|
|
18918
|
+
relativeDirPath: getZedOtherPlatformGlobalDir(),
|
|
18919
|
+
relativeFilePath: ZED_SETTINGS_FILE_NAME
|
|
18920
|
+
}];
|
|
18921
|
+
}
|
|
17723
18922
|
/**
|
|
17724
18923
|
* ZedIgnore uses settings.json which is a user-managed config file.
|
|
17725
18924
|
* It should not be deleted by rulesync.
|
|
@@ -17730,48 +18929,53 @@ var ZedIgnore = class ZedIgnore extends ToolIgnore {
|
|
|
17730
18929
|
toRulesyncIgnore() {
|
|
17731
18930
|
const fileContent = this.patterns.filter((pattern) => pattern.length > 0).join("\n");
|
|
17732
18931
|
return new RulesyncIgnore({
|
|
17733
|
-
outputRoot:
|
|
18932
|
+
outputRoot: ".",
|
|
17734
18933
|
relativeDirPath: RulesyncIgnore.getSettablePaths().recommended.relativeDirPath,
|
|
17735
18934
|
relativeFilePath: RulesyncIgnore.getSettablePaths().recommended.relativeFilePath,
|
|
17736
18935
|
fileContent
|
|
17737
18936
|
});
|
|
17738
18937
|
}
|
|
17739
|
-
static async fromRulesyncIgnore({ outputRoot = process.cwd(), rulesyncIgnore }) {
|
|
18938
|
+
static async fromRulesyncIgnore({ outputRoot = process.cwd(), rulesyncIgnore, global = false }) {
|
|
17740
18939
|
const patterns = rulesyncIgnore.getFileContent().split(/\r?\n|\r/).map((line) => line.trim()).filter((line) => line.length > 0 && !line.startsWith("#"));
|
|
17741
|
-
const
|
|
18940
|
+
const paths = this.getSettablePaths({ global });
|
|
18941
|
+
const filePath = (0, node_path.join)(outputRoot, paths.relativeDirPath, paths.relativeFilePath);
|
|
17742
18942
|
const existingFileContent = await fileExists(filePath) ? await readFileContent(filePath) : "{}";
|
|
17743
|
-
const
|
|
18943
|
+
const managedPatterns = patterns.length > 0 ? [...new Set(patterns)].toSorted() : void 0;
|
|
17744
18944
|
return new ZedIgnore({
|
|
17745
18945
|
outputRoot,
|
|
17746
|
-
relativeDirPath:
|
|
17747
|
-
relativeFilePath:
|
|
18946
|
+
relativeDirPath: paths.relativeDirPath,
|
|
18947
|
+
relativeFilePath: paths.relativeFilePath,
|
|
17748
18948
|
fileContent: applySharedConfigPatch({
|
|
17749
|
-
fileKey: sharedConfigFileKey(
|
|
18949
|
+
fileKey: sharedConfigFileKey(paths),
|
|
17750
18950
|
feature: "ignore",
|
|
17751
18951
|
existingContent: existingFileContent,
|
|
17752
|
-
patch: { private_files:
|
|
18952
|
+
patch: { private_files: managedPatterns },
|
|
17753
18953
|
filePath
|
|
17754
18954
|
}),
|
|
17755
|
-
validate: true
|
|
18955
|
+
validate: true,
|
|
18956
|
+
global
|
|
17756
18957
|
});
|
|
17757
18958
|
}
|
|
17758
|
-
static async fromFile({ outputRoot = process.cwd(), validate = true }) {
|
|
17759
|
-
const
|
|
18959
|
+
static async fromFile({ outputRoot = process.cwd(), validate = true, global = false }) {
|
|
18960
|
+
const paths = this.getSettablePaths({ global });
|
|
18961
|
+
const fileContent = await readFileContent((0, node_path.join)(outputRoot, paths.relativeDirPath, paths.relativeFilePath));
|
|
17760
18962
|
return new ZedIgnore({
|
|
17761
18963
|
outputRoot,
|
|
17762
|
-
relativeDirPath:
|
|
17763
|
-
relativeFilePath:
|
|
18964
|
+
relativeDirPath: paths.relativeDirPath,
|
|
18965
|
+
relativeFilePath: paths.relativeFilePath,
|
|
17764
18966
|
fileContent,
|
|
17765
|
-
validate
|
|
18967
|
+
validate,
|
|
18968
|
+
global
|
|
17766
18969
|
});
|
|
17767
18970
|
}
|
|
17768
|
-
static forDeletion({ outputRoot = process.cwd(), relativeDirPath, relativeFilePath }) {
|
|
18971
|
+
static forDeletion({ outputRoot = process.cwd(), relativeDirPath, relativeFilePath, global = false }) {
|
|
17769
18972
|
return new ZedIgnore({
|
|
17770
18973
|
outputRoot,
|
|
17771
18974
|
relativeDirPath,
|
|
17772
18975
|
relativeFilePath,
|
|
17773
18976
|
fileContent: "{}",
|
|
17774
|
-
validate: false
|
|
18977
|
+
validate: false,
|
|
18978
|
+
global
|
|
17775
18979
|
});
|
|
17776
18980
|
}
|
|
17777
18981
|
};
|
|
@@ -17793,6 +18997,7 @@ const toolIgnoreFactories = /* @__PURE__ */ new Map([
|
|
|
17793
18997
|
["kiro-cli", { class: KiroIgnore }],
|
|
17794
18998
|
["kiro-ide", { class: KiroIgnore }],
|
|
17795
18999
|
["qwencode", { class: QwencodeIgnore }],
|
|
19000
|
+
["reasonix", { class: ReasonixIgnore }],
|
|
17796
19001
|
["roo", { class: RooIgnore }],
|
|
17797
19002
|
["devin", { class: DevinIgnore }],
|
|
17798
19003
|
["vibe", { class: VibeIgnore }],
|
|
@@ -17803,7 +19008,9 @@ const ignoreProcessorToolTargets = [...toolIgnoreFactories.keys()];
|
|
|
17803
19008
|
const ignoreProcessorGlobalToolTargets = [
|
|
17804
19009
|
"kiro",
|
|
17805
19010
|
"kiro-cli",
|
|
17806
|
-
"kiro-ide"
|
|
19011
|
+
"kiro-ide",
|
|
19012
|
+
"reasonix",
|
|
19013
|
+
"zed"
|
|
17807
19014
|
];
|
|
17808
19015
|
const defaultGetFactory$4 = (target) => {
|
|
17809
19016
|
const factory = toolIgnoreFactories.get(target);
|
|
@@ -18722,8 +19929,20 @@ const RULESYNC_TO_CODEX_FIELD_MAP = {
|
|
|
18722
19929
|
disabledTools: "disabled_tools",
|
|
18723
19930
|
envVars: "env_vars"
|
|
18724
19931
|
};
|
|
19932
|
+
const RULESYNC_TO_CODEX_SCALAR_FIELD_MAP = { experimentalEnvironment: "experimental_environment" };
|
|
19933
|
+
const CODEX_TO_RULESYNC_SCALAR_FIELD_MAP = Object.fromEntries(Object.entries(RULESYNC_TO_CODEX_SCALAR_FIELD_MAP).map(([canonical, codex]) => [codex, canonical]));
|
|
18725
19934
|
const MAX_REMOVE_EMPTY_ENTRIES_DEPTH$1 = 32;
|
|
18726
19935
|
/**
|
|
19936
|
+
* `env_vars` entries are either a bare variable name or `{ name, source }`,
|
|
19937
|
+
* where `source = "remote"` reads the variable from the remote executor
|
|
19938
|
+
* environment. The other renamed keys (`enabled_tools`, `disabled_tools`) stay
|
|
19939
|
+
* plain string arrays, so the widened check applies to `env_vars` only.
|
|
19940
|
+
* @see https://learn.chatgpt.com/docs/extend/mcp
|
|
19941
|
+
*/
|
|
19942
|
+
function isValidRenamedArray(key, value) {
|
|
19943
|
+
return key === "env_vars" || key === "envVars" ? isEnvVarEntryArray(value) : isStringArray$1(value);
|
|
19944
|
+
}
|
|
19945
|
+
/**
|
|
18727
19946
|
* Translate a server's `oauth` table from the canonical rulesync shape (Claude
|
|
18728
19947
|
* Code style camelCase) into the shape Codex CLI understands. Codex expects the
|
|
18729
19948
|
* OAuth client id under snake_case `client_id`; without it `codex mcp login`
|
|
@@ -18783,8 +20002,12 @@ function convertFromCodexFormat(codexMcp) {
|
|
|
18783
20002
|
} else if (key === "oauth" && isRecord(value)) converted[key] = mapOauthFromCodex(value);
|
|
18784
20003
|
else if (Object.hasOwn(CODEX_TO_RULESYNC_FIELD_MAP, key)) {
|
|
18785
20004
|
const mappedKey = CODEX_TO_RULESYNC_FIELD_MAP[key];
|
|
18786
|
-
if (mappedKey) if (
|
|
20005
|
+
if (mappedKey) if (isValidRenamedArray(key, value)) converted[mappedKey] = value;
|
|
18787
20006
|
else warnWithFallback(void 0, `Ignored malformed array for ${key} in MCP server ${name}`);
|
|
20007
|
+
} else if (Object.hasOwn(CODEX_TO_RULESYNC_SCALAR_FIELD_MAP, key)) {
|
|
20008
|
+
const mappedKey = CODEX_TO_RULESYNC_SCALAR_FIELD_MAP[key];
|
|
20009
|
+
if (mappedKey) if (typeof value === "string") converted[mappedKey] = value;
|
|
20010
|
+
else warnWithFallback(void 0, `Ignored malformed value for ${key} in MCP server ${name}: expected a string`);
|
|
18788
20011
|
} else converted[key] = value;
|
|
18789
20012
|
}
|
|
18790
20013
|
result[name] = converted;
|
|
@@ -18806,8 +20029,12 @@ function convertToCodexFormat(mcpServers) {
|
|
|
18806
20029
|
} else if (key === "oauth" && isRecord(value)) converted[key] = mapOauthToCodex(value);
|
|
18807
20030
|
else if (Object.hasOwn(RULESYNC_TO_CODEX_FIELD_MAP, key)) {
|
|
18808
20031
|
const mappedKey = RULESYNC_TO_CODEX_FIELD_MAP[key];
|
|
18809
|
-
if (mappedKey) if (
|
|
20032
|
+
if (mappedKey) if (isValidRenamedArray(key, value)) converted[mappedKey] = value;
|
|
18810
20033
|
else warnWithFallback(void 0, `[CodexCliMcp] Skipping invalid value type for mapped key '${key}': expected string array, got ${typeof value}`);
|
|
20034
|
+
} else if (Object.hasOwn(RULESYNC_TO_CODEX_SCALAR_FIELD_MAP, key)) {
|
|
20035
|
+
const mappedKey = RULESYNC_TO_CODEX_SCALAR_FIELD_MAP[key];
|
|
20036
|
+
if (mappedKey) if (typeof value === "string") converted[mappedKey] = value;
|
|
20037
|
+
else warnWithFallback(void 0, `[CodexCliMcp] Skipping invalid value type for mapped key '${key}': expected string, got ${typeof value}`);
|
|
18811
20038
|
} else converted[key] = value;
|
|
18812
20039
|
}
|
|
18813
20040
|
const previousName = originalNames.get(codexName);
|
|
@@ -18878,7 +20105,9 @@ var CodexcliMcp = class CodexcliMcp extends ToolMcp {
|
|
|
18878
20105
|
const rawServer = isRecord(rawMcpServers) ? rawMcpServers[serverName] : void 0;
|
|
18879
20106
|
return [serverName, {
|
|
18880
20107
|
...serverConfig,
|
|
18881
|
-
...isRecord(rawServer) &&
|
|
20108
|
+
...isRecord(rawServer) && isEnvVarEntryArray(rawServer.envVars) ? { envVars: rawServer.envVars } : {},
|
|
20109
|
+
...isRecord(rawServer) && typeof rawServer.experimental_environment === "string" ? { experimentalEnvironment: rawServer.experimental_environment } : {},
|
|
20110
|
+
...isRecord(rawServer) && typeof rawServer.experimentalEnvironment === "string" ? { experimentalEnvironment: rawServer.experimentalEnvironment } : {}
|
|
18882
20111
|
}];
|
|
18883
20112
|
})));
|
|
18884
20113
|
const filteredMcpServers = this.removeEmptyEntries(converted);
|
|
@@ -18949,9 +20178,6 @@ var CodexcliMcp = class CodexcliMcp extends ToolMcp {
|
|
|
18949
20178
|
};
|
|
18950
20179
|
//#endregion
|
|
18951
20180
|
//#region src/features/mcp/copilot-mcp.ts
|
|
18952
|
-
function convertToCopilotFormat(mcpServers) {
|
|
18953
|
-
return { servers: mcpServers };
|
|
18954
|
-
}
|
|
18955
20181
|
function convertFromCopilotFormat(copilotConfig) {
|
|
18956
20182
|
return copilotConfig.servers ?? {};
|
|
18957
20183
|
}
|
|
@@ -18980,13 +20206,21 @@ var CopilotMcp = class CopilotMcp extends ToolMcp {
|
|
|
18980
20206
|
validate
|
|
18981
20207
|
});
|
|
18982
20208
|
}
|
|
18983
|
-
static fromRulesyncMcp({ outputRoot = process.cwd(), rulesyncMcp, validate = true }) {
|
|
18984
|
-
const
|
|
20209
|
+
static async fromRulesyncMcp({ outputRoot = process.cwd(), rulesyncMcp, validate = true }) {
|
|
20210
|
+
const paths = this.getSettablePaths();
|
|
20211
|
+
const filePath = (0, node_path.join)(outputRoot, paths.relativeDirPath, paths.relativeFilePath);
|
|
20212
|
+
const existingContent = await readFileContentOrNull(filePath) ?? "{}";
|
|
18985
20213
|
return new CopilotMcp({
|
|
18986
20214
|
outputRoot,
|
|
18987
|
-
relativeDirPath:
|
|
18988
|
-
relativeFilePath:
|
|
18989
|
-
fileContent:
|
|
20215
|
+
relativeDirPath: paths.relativeDirPath,
|
|
20216
|
+
relativeFilePath: paths.relativeFilePath,
|
|
20217
|
+
fileContent: applySharedConfigPatch({
|
|
20218
|
+
fileKey: sharedConfigFileKey(paths),
|
|
20219
|
+
feature: "mcp",
|
|
20220
|
+
existingContent,
|
|
20221
|
+
patch: { servers: rulesyncMcp.getMcpServers() },
|
|
20222
|
+
filePath
|
|
20223
|
+
}),
|
|
18990
20224
|
validate
|
|
18991
20225
|
});
|
|
18992
20226
|
}
|
|
@@ -20372,7 +21606,7 @@ var HermesagentMcp = class HermesagentMcp extends ToolMcp {
|
|
|
20372
21606
|
}), isRecord(this.config.mcp_servers) ? this.config.mcp_servers : {});
|
|
20373
21607
|
this.config = merged;
|
|
20374
21608
|
super.setFileContent(applySharedConfigPatch({
|
|
20375
|
-
fileKey:
|
|
21609
|
+
fileKey: getHermesagentConfigSharedFileKey({ global: this.global }),
|
|
20376
21610
|
feature: "mcp",
|
|
20377
21611
|
existingContent: fileContent,
|
|
20378
21612
|
patch: { mcp_servers: merged.mcp_servers }
|
|
@@ -20390,6 +21624,13 @@ var HermesagentMcp = class HermesagentMcp extends ToolMcp {
|
|
|
20390
21624
|
relativeFilePath: HERMESAGENT_CONFIG_FILE_NAME
|
|
20391
21625
|
};
|
|
20392
21626
|
}
|
|
21627
|
+
/**
|
|
21628
|
+
* `config.yaml` under every spelling the global profile root can take.
|
|
21629
|
+
* @see getHermesagentSharedConfigWritePaths
|
|
21630
|
+
*/
|
|
21631
|
+
static getExtraSharedWritePaths() {
|
|
21632
|
+
return getHermesagentSharedConfigWritePaths();
|
|
21633
|
+
}
|
|
20393
21634
|
static async fromFile({ outputRoot = process.cwd(), validate = true, global = false }) {
|
|
20394
21635
|
if (!global) throw new Error(HERMESAGENT_GLOBAL_ONLY_MESSAGE);
|
|
20395
21636
|
const paths = this.getSettablePaths({ global });
|
|
@@ -20416,7 +21657,7 @@ var HermesagentMcp = class HermesagentMcp extends ToolMcp {
|
|
|
20416
21657
|
relativeDirPath: paths.relativeDirPath,
|
|
20417
21658
|
relativeFilePath: paths.relativeFilePath,
|
|
20418
21659
|
fileContent: applySharedConfigPatch({
|
|
20419
|
-
fileKey:
|
|
21660
|
+
fileKey: getHermesagentConfigSharedFileKey({ global }),
|
|
20420
21661
|
feature: "mcp",
|
|
20421
21662
|
existingContent: fileContent,
|
|
20422
21663
|
patch: { mcp_servers: merged.mcp_servers }
|
|
@@ -20669,7 +21910,7 @@ const KILO_TOGGLE_KEPT_KEYS = /* @__PURE__ */ new Set([
|
|
|
20669
21910
|
* impossible to start — no command, no URL — so it is written as the toggle it
|
|
20670
21911
|
* resembles rather than dropped, but the loss is said out loud.
|
|
20671
21912
|
*/
|
|
20672
|
-
function warnAboutToggleDroppedKeys(serverName, serverConfig, logger) {
|
|
21913
|
+
function warnAboutToggleDroppedKeys$1(serverName, serverConfig, logger) {
|
|
20673
21914
|
const dropped = Object.keys(serverConfig).filter((key) => !KILO_TOGGLE_KEPT_KEYS.has(key));
|
|
20674
21915
|
if (dropped.length === 0) return;
|
|
20675
21916
|
logger?.warn(`Kilo MCP: "${serverName}" declares no transport, so it is written as a toggle entry and ${dropped.toSorted().join(", ")} ${dropped.length === 1 ? "is" : "are"} dropped.`);
|
|
@@ -20682,7 +21923,7 @@ function convertServerToKiloFormat(serverName, serverConfig, existingEntry, logg
|
|
|
20682
21923
|
if (declaresNoTransport(serverConfig)) {
|
|
20683
21924
|
if (serverConfig.disabled === void 0) {
|
|
20684
21925
|
if (existingEntry !== void 0 && !isKiloTransportServer(existingEntry)) {
|
|
20685
|
-
warnAboutToggleDroppedKeys(serverName, serverConfig, logger);
|
|
21926
|
+
warnAboutToggleDroppedKeys$1(serverName, serverConfig, logger);
|
|
20686
21927
|
return existingEntry;
|
|
20687
21928
|
}
|
|
20688
21929
|
return warnAndSkipMcpServer({
|
|
@@ -20692,7 +21933,7 @@ function convertServerToKiloFormat(serverName, serverConfig, existingEntry, logg
|
|
|
20692
21933
|
logger
|
|
20693
21934
|
});
|
|
20694
21935
|
}
|
|
20695
|
-
warnAboutToggleDroppedKeys(serverName, serverConfig, logger);
|
|
21936
|
+
warnAboutToggleDroppedKeys$1(serverName, serverConfig, logger);
|
|
20696
21937
|
return { enabled: !serverConfig.disabled };
|
|
20697
21938
|
}
|
|
20698
21939
|
if (isRemoteMcpServer(serverConfig)) {
|
|
@@ -20877,9 +22118,12 @@ var KiloMcp = class KiloMcp extends ToolMcp {
|
|
|
20877
22118
|
* Merge a list of project rule file globs into the `instructions` array of the
|
|
20878
22119
|
* shared `kilo.jsonc` (or `kilo.json`) config, preserving every existing key
|
|
20879
22120
|
* (notably `mcp`/`tools` written by the MCP feature). In Kilo v7, files under
|
|
20880
|
-
* `.kilo/rules/` are NOT auto-loaded; they are only picked up
|
|
20881
|
-
* the `instructions` key. The
|
|
20882
|
-
*
|
|
22121
|
+
* a *project* `.kilo/rules/` are NOT auto-loaded; they are only picked up
|
|
22122
|
+
* when listed in the `instructions` key. (The home-scope `~/.kilo/rules/` is
|
|
22123
|
+
* different — the rules migrator's `globalRulesDirs()` walks it on every
|
|
22124
|
+
* config load — which is why `KiloRule` registers instructions in project
|
|
22125
|
+
* scope only.) The resulting `instructions` list is deduped and sorted for a
|
|
22126
|
+
* stable output.
|
|
20883
22127
|
*
|
|
20884
22128
|
* @see https://kilo.ai/docs/automate/mcp/using-in-kilo-code
|
|
20885
22129
|
*/
|
|
@@ -21092,7 +22336,7 @@ var KimiCodeMcpConfigToml = class KimiCodeMcpConfigToml extends ToolFile {
|
|
|
21092
22336
|
const existingContent = existing.content;
|
|
21093
22337
|
const existingSection = existing.mcp;
|
|
21094
22338
|
const fileContent = applySharedConfigPatch({
|
|
21095
|
-
fileKey:
|
|
22339
|
+
fileKey: getKimiCodeConfigSharedFileKey({ global: true }),
|
|
21096
22340
|
feature: "mcp",
|
|
21097
22341
|
existingContent,
|
|
21098
22342
|
patch: { mcp: {
|
|
@@ -21179,11 +22423,8 @@ var KimiCodeMcp = class KimiCodeMcp extends ToolMcp {
|
|
|
21179
22423
|
* derivation sees this feature as one of that file's writers — it is not a
|
|
21180
22424
|
* settable path, since the servers themselves live in `mcp.json`.
|
|
21181
22425
|
*/
|
|
21182
|
-
static getExtraSharedWritePaths(
|
|
21183
|
-
return
|
|
21184
|
-
relativeDirPath: getKimiCodeRelativeDirPath({ global: true }),
|
|
21185
|
-
relativeFilePath: KIMI_CODE_CONFIG_FILE_NAME
|
|
21186
|
-
}] : [];
|
|
22426
|
+
static getExtraSharedWritePaths() {
|
|
22427
|
+
return getKimiCodeSharedConfigWritePaths();
|
|
21187
22428
|
}
|
|
21188
22429
|
/**
|
|
21189
22430
|
* The `[mcp]` defaults live in the shared user `config.toml`, not in
|
|
@@ -21318,13 +22559,49 @@ const OpencodeMcpRemoteServerSchema = zod_mini.z.looseObject({
|
|
|
21318
22559
|
headers: zod_mini.z.optional(zod_mini.z.record(zod_mini.z.string(), zod_mini.z.string())),
|
|
21319
22560
|
enabled: zod_mini.z._default(zod_mini.z.boolean(), true)
|
|
21320
22561
|
});
|
|
21321
|
-
const
|
|
22562
|
+
const OPENCODE_PASSTHROUGH_SERVER_FIELDS = ["timeout", "oauth"];
|
|
22563
|
+
const OPENCODE_MCP_TRANSPORT_KEYS = [
|
|
22564
|
+
...Object.keys(OpencodeMcpLocalServerSchema.def.shape),
|
|
22565
|
+
...Object.keys(OpencodeMcpRemoteServerSchema.def.shape),
|
|
22566
|
+
...OPENCODE_PASSTHROUGH_SERVER_FIELDS
|
|
22567
|
+
].filter((key) => key !== "enabled");
|
|
22568
|
+
/**
|
|
22569
|
+
* A bare toggle entry: `{"enabled": <bool>}` with no transport of its own,
|
|
22570
|
+
* disabling a server another config layer defines. It is the third member of
|
|
22571
|
+
* OpenCode's own `mcp` union in the published schema, described in-source as
|
|
22572
|
+
* "the legacy `{ enabled: false }` form used to disable a server". Without this
|
|
22573
|
+
* arm the union rejects the entry and the whole MCP import aborts — taking
|
|
22574
|
+
* every valid server in the same file down with it.
|
|
22575
|
+
*
|
|
22576
|
+
* Loose, unlike the two transport arms, so a key OpenCode adds to a toggle
|
|
22577
|
+
* later does not bring that abort back — but refined to reject anything
|
|
22578
|
+
* carrying a key only a transport entry has. A plain loose arm would sit under
|
|
22579
|
+
* a malformed `local` or `remote` entry that happens to carry `enabled` and
|
|
22580
|
+
* swallow it, dropping its command, URL, or headers without a word instead of
|
|
22581
|
+
* failing the way it does today. Mirrors {@link KiloMcpToggleSchema}.
|
|
22582
|
+
*
|
|
22583
|
+
* @see https://opencode.ai/config.json
|
|
22584
|
+
*/
|
|
22585
|
+
const OpencodeMcpToggleSchema = zod_mini.z.looseObject({ enabled: zod_mini.z.boolean() }).check((0, zod_mini.refine)((entry) => OPENCODE_MCP_TRANSPORT_KEYS.every((key) => !(key in entry)), "not a valid OpenCode MCP server: expected a local server ({type: \"local\", command: [...]}), a remote server ({type: \"remote\", url: \"...\"}), or a bare toggle ({enabled: <bool>}) carrying no field of either"));
|
|
22586
|
+
const OpencodeMcpServerSchema = zod_mini.z.union([
|
|
22587
|
+
OpencodeMcpLocalServerSchema,
|
|
22588
|
+
OpencodeMcpRemoteServerSchema,
|
|
22589
|
+
OpencodeMcpToggleSchema
|
|
22590
|
+
]);
|
|
21322
22591
|
const OpencodeConfigSchema = zod_mini.z.looseObject({
|
|
21323
22592
|
$schema: zod_mini.z.optional(zod_mini.z.string()),
|
|
21324
22593
|
mcp: zod_mini.z.optional(zod_mini.z.record(zod_mini.z.string(), OpencodeMcpServerSchema)),
|
|
21325
22594
|
tools: zod_mini.z.optional(zod_mini.z.record(zod_mini.z.string(), zod_mini.z.boolean()))
|
|
21326
22595
|
});
|
|
21327
22596
|
/**
|
|
22597
|
+
* Tell the two transport arms from a toggle entry. Both carry a `type` literal
|
|
22598
|
+
* and a toggle never does — the schema refuses one that tries — but the toggle
|
|
22599
|
+
* arm is loose, so its index signature hides that from `in` narrowing.
|
|
22600
|
+
*/
|
|
22601
|
+
function isOpencodeTransportServer(server) {
|
|
22602
|
+
return server.type === "local" || server.type === "remote";
|
|
22603
|
+
}
|
|
22604
|
+
/**
|
|
21328
22605
|
* Convert OpenCode native format back to standard MCP format
|
|
21329
22606
|
* - type: "local" -> "stdio", "remote" -> "sse"
|
|
21330
22607
|
* - command (array) -> command (first element) + args (rest)
|
|
@@ -21349,19 +22626,31 @@ function convertFromOpencodeFormat(opencodeMcp, tools) {
|
|
|
21349
22626
|
...convertOpencodeServers(opencodeMcp, tools)
|
|
21350
22627
|
};
|
|
21351
22628
|
}
|
|
22629
|
+
/** Split the shared top-level `tools` map into this server's own two lists. */
|
|
22630
|
+
function splitOpencodeServerTools(serverName, tools) {
|
|
22631
|
+
const enabledTools = [];
|
|
22632
|
+
const disabledTools = [];
|
|
22633
|
+
const prefix = `${serverName}_`;
|
|
22634
|
+
for (const [toolName, enabled] of Object.entries(tools ?? {})) {
|
|
22635
|
+
if (!toolName.startsWith(prefix)) continue;
|
|
22636
|
+
const toolSuffix = toolName.slice(prefix.length);
|
|
22637
|
+
(enabled ? enabledTools : disabledTools).push(toolSuffix);
|
|
22638
|
+
}
|
|
22639
|
+
return {
|
|
22640
|
+
enabledTools,
|
|
22641
|
+
disabledTools
|
|
22642
|
+
};
|
|
22643
|
+
}
|
|
21352
22644
|
function convertOpencodeServers(opencodeMcp, tools) {
|
|
21353
22645
|
return Object.fromEntries(Object.entries(opencodeMcp).map(([serverName, serverConfig]) => {
|
|
21354
22646
|
const extraFields = Object.fromEntries(Object.entries(serverConfig).filter(([key]) => !OPENCODE_KNOWN_SERVER_KEYS.has(key)));
|
|
21355
|
-
const enabledTools =
|
|
21356
|
-
|
|
21357
|
-
|
|
21358
|
-
|
|
21359
|
-
|
|
21360
|
-
|
|
21361
|
-
|
|
21362
|
-
else disabledTools.push(toolSuffix);
|
|
21363
|
-
}
|
|
21364
|
-
}
|
|
22647
|
+
const { enabledTools, disabledTools } = splitOpencodeServerTools(serverName, tools);
|
|
22648
|
+
if (!isOpencodeTransportServer(serverConfig)) return [serverName, {
|
|
22649
|
+
...extraFields,
|
|
22650
|
+
disabled: serverConfig.enabled === false,
|
|
22651
|
+
...enabledTools.length > 0 && { enabledTools },
|
|
22652
|
+
...disabledTools.length > 0 && { disabledTools }
|
|
22653
|
+
}];
|
|
21365
22654
|
if (serverConfig.type === "remote") return [serverName, {
|
|
21366
22655
|
...extraFields,
|
|
21367
22656
|
type: "sse",
|
|
@@ -21391,7 +22680,20 @@ function convertOpencodeServers(opencodeMcp, tools) {
|
|
|
21391
22680
|
}];
|
|
21392
22681
|
}));
|
|
21393
22682
|
}
|
|
21394
|
-
const
|
|
22683
|
+
const OPENCODE_TOGGLE_KEPT_KEYS = /* @__PURE__ */ new Set([
|
|
22684
|
+
"disabled",
|
|
22685
|
+
"enabledTools",
|
|
22686
|
+
"disabledTools"
|
|
22687
|
+
]);
|
|
22688
|
+
/**
|
|
22689
|
+
* Warn about the fields a transport-less server loses by being written as a
|
|
22690
|
+
* bare toggle, so the drop is never silent.
|
|
22691
|
+
*/
|
|
22692
|
+
function warnAboutToggleDroppedKeys(serverName, serverConfig, logger) {
|
|
22693
|
+
const dropped = Object.keys(serverConfig).filter((key) => !OPENCODE_TOGGLE_KEPT_KEYS.has(key));
|
|
22694
|
+
if (dropped.length === 0) return;
|
|
22695
|
+
logger?.warn(`OpenCode MCP: "${serverName}" declares no transport, so it is written as a toggle entry and ${dropped.toSorted().join(", ")} ${dropped.length === 1 ? "is" : "are"} dropped.`);
|
|
22696
|
+
}
|
|
21395
22697
|
/**
|
|
21396
22698
|
* Convert standard MCP format to OpenCode native format
|
|
21397
22699
|
* - type: "stdio" -> "local", "sse"/"http" -> "remote"
|
|
@@ -21401,11 +22703,27 @@ const OPENCODE_PASSTHROUGH_SERVER_FIELDS = ["timeout", "oauth"];
|
|
|
21401
22703
|
* - enabledTools/disabledTools -> top-level tools map (with server name prefix)
|
|
21402
22704
|
* - OpenCode-supported extras (timeout, oauth) -> passed through verbatim
|
|
21403
22705
|
*/
|
|
21404
|
-
function convertServerToOpencodeFormat(serverName, serverConfig, logger) {
|
|
22706
|
+
function convertServerToOpencodeFormat(serverName, serverConfig, existingEntry, logger) {
|
|
21405
22707
|
const serverRecord = serverConfig;
|
|
21406
22708
|
const passthrough = {};
|
|
21407
22709
|
for (const key of OPENCODE_PASSTHROUGH_SERVER_FIELDS) if (serverRecord[key] !== void 0) passthrough[key] = serverRecord[key];
|
|
21408
22710
|
const enabled = serverConfig.disabled !== void 0 ? !serverConfig.disabled : true;
|
|
22711
|
+
if (declaresNoTransport(serverConfig)) {
|
|
22712
|
+
if (serverConfig.disabled === void 0) {
|
|
22713
|
+
if (existingEntry !== void 0 && !isOpencodeTransportServer(existingEntry)) {
|
|
22714
|
+
warnAboutToggleDroppedKeys(serverName, serverConfig, logger);
|
|
22715
|
+
return existingEntry;
|
|
22716
|
+
}
|
|
22717
|
+
return warnAndSkipMcpServer({
|
|
22718
|
+
toolName: "OpenCode",
|
|
22719
|
+
serverName,
|
|
22720
|
+
reason: "no transport and no enabled state, so there is nothing to toggle",
|
|
22721
|
+
logger
|
|
22722
|
+
});
|
|
22723
|
+
}
|
|
22724
|
+
warnAboutToggleDroppedKeys(serverName, serverConfig, logger);
|
|
22725
|
+
return { enabled };
|
|
22726
|
+
}
|
|
21409
22727
|
if (isRemoteMcpServer(serverConfig)) {
|
|
21410
22728
|
const url = resolveRemoteMcpUrl(serverConfig);
|
|
21411
22729
|
if (url === void 0) return warnAndSkipMcpServer({
|
|
@@ -21426,7 +22744,7 @@ function convertServerToOpencodeFormat(serverName, serverConfig, logger) {
|
|
|
21426
22744
|
if (commandArray.length === 0) return warnAndSkipMcpServer({
|
|
21427
22745
|
toolName: "OpenCode",
|
|
21428
22746
|
serverName,
|
|
21429
|
-
reason:
|
|
22747
|
+
reason: "a local transport but no command",
|
|
21430
22748
|
logger
|
|
21431
22749
|
});
|
|
21432
22750
|
return {
|
|
@@ -21438,11 +22756,11 @@ function convertServerToOpencodeFormat(serverName, serverConfig, logger) {
|
|
|
21438
22756
|
...serverConfig.cwd && { cwd: serverConfig.cwd }
|
|
21439
22757
|
};
|
|
21440
22758
|
}
|
|
21441
|
-
function convertToOpencodeFormat(mcpServers, logger) {
|
|
22759
|
+
function convertToOpencodeFormat(mcpServers, existingMcp, logger) {
|
|
21442
22760
|
const tools = {};
|
|
21443
22761
|
return {
|
|
21444
22762
|
mcp: Object.fromEntries(Object.entries(mcpServers).map(([serverName, serverConfig]) => {
|
|
21445
|
-
const converted = convertServerToOpencodeFormat(serverName, serverConfig, logger);
|
|
22763
|
+
const converted = convertServerToOpencodeFormat(serverName, serverConfig, existingMcp[serverName], logger);
|
|
21446
22764
|
if (serverConfig.enabledTools) for (const tool of serverConfig.enabledTools) tools[`${serverName}_${tool}`] = true;
|
|
21447
22765
|
if (serverConfig.disabledTools) for (const tool of serverConfig.disabledTools) tools[`${serverName}_${tool}`] = false;
|
|
21448
22766
|
return converted === null ? null : [serverName, converted];
|
|
@@ -21512,10 +22830,12 @@ var OpencodeMcp = class OpencodeMcp extends ToolMcp {
|
|
|
21512
22830
|
fileContent = await readFileContentOrNull(jsonPath);
|
|
21513
22831
|
if (fileContent) relativeFilePath = OPENCODE_JSON_FILE_NAME;
|
|
21514
22832
|
}
|
|
21515
|
-
const
|
|
22833
|
+
const transformedServers = convertEnvVarRefsToToolFormat({
|
|
21516
22834
|
mcpServers: rulesyncMcp.getMcpServers(),
|
|
21517
22835
|
replacement: "{env:$1}"
|
|
21518
|
-
})
|
|
22836
|
+
});
|
|
22837
|
+
const existingMcp = OpencodeConfigSchema.safeParse((0, jsonc_parser.parse)(fileContent || "{}"));
|
|
22838
|
+
const { mcp: convertedMcp, tools: mcpTools } = convertToOpencodeFormat(transformedServers, (existingMcp.success ? existingMcp.data.mcp : void 0) ?? {}, logger);
|
|
21519
22839
|
return new OpencodeMcp({
|
|
21520
22840
|
outputRoot,
|
|
21521
22841
|
relativeDirPath: basePaths.relativeDirPath,
|
|
@@ -22740,16 +24060,11 @@ var ZedMcp = class ZedMcp extends ToolMcp {
|
|
|
22740
24060
|
relativeFilePath: ZED_SETTINGS_FILE_NAME
|
|
22741
24061
|
};
|
|
22742
24062
|
}
|
|
22743
|
-
/**
|
|
22744
|
-
* The global settings file of the OTHER platform: `getSettablePaths` resolves
|
|
22745
|
-
* `~/.config/zed` vs `%APPDATA%\Zed` per platform, but the shared-write
|
|
22746
|
-
* derivation (and the gateway ownership table it is checked against) must
|
|
22747
|
-
* know both spellings on every platform.
|
|
22748
|
-
*/
|
|
24063
|
+
/** @see getZedOtherPlatformGlobalDir */
|
|
22749
24064
|
static getExtraSharedWritePaths({ global = false } = {}) {
|
|
22750
24065
|
if (!global) return [];
|
|
22751
24066
|
return [{
|
|
22752
|
-
relativeDirPath:
|
|
24067
|
+
relativeDirPath: getZedOtherPlatformGlobalDir(),
|
|
22753
24068
|
relativeFilePath: ZED_SETTINGS_FILE_NAME
|
|
22754
24069
|
}];
|
|
22755
24070
|
}
|
|
@@ -23825,10 +25140,11 @@ function buildPermissionEntry$1(toolName, pattern) {
|
|
|
23825
25140
|
* file (global scope only). The file holds other CLI settings besides
|
|
23826
25141
|
* permissions, so it is never deleted.
|
|
23827
25142
|
*
|
|
23828
|
-
*
|
|
23829
|
-
* `toolPermission` (the global autonomy preset)
|
|
23830
|
-
*
|
|
23831
|
-
*
|
|
25143
|
+
* Four CLI-only autonomy/sandbox knobs outside the allow/ask/deny arrays —
|
|
25144
|
+
* `toolPermission` (the global autonomy preset), `enableTerminalSandbox`,
|
|
25145
|
+
* `artifactReviewPolicy` and `allowNonWorkspaceAccess` — are authored and
|
|
25146
|
+
* round-tripped through the `antigravity-cli` permissions override (see
|
|
25147
|
+
* `AntigravityCliPermissionsOverrideSchema`).
|
|
23832
25148
|
*/
|
|
23833
25149
|
var AntigravityCliPermissions = class AntigravityCliPermissions extends ToolPermissions {
|
|
23834
25150
|
constructor(params) {
|
|
@@ -23892,6 +25208,8 @@ var AntigravityCliPermissions = class AntigravityCliPermissions extends ToolPerm
|
|
|
23892
25208
|
const override = config["antigravity-cli"];
|
|
23893
25209
|
if (override?.toolPermission !== void 0) merged.toolPermission = override.toolPermission;
|
|
23894
25210
|
if (override?.enableTerminalSandbox !== void 0) merged.enableTerminalSandbox = override.enableTerminalSandbox;
|
|
25211
|
+
if (override?.artifactReviewPolicy !== void 0) merged.artifactReviewPolicy = override.artifactReviewPolicy;
|
|
25212
|
+
if (override?.allowNonWorkspaceAccess !== void 0) merged.allowNonWorkspaceAccess = override.allowNonWorkspaceAccess;
|
|
23895
25213
|
const fileContent = JSON.stringify(merged, null, 2);
|
|
23896
25214
|
return new AntigravityCliPermissions({
|
|
23897
25215
|
outputRoot,
|
|
@@ -23918,6 +25236,8 @@ var AntigravityCliPermissions = class AntigravityCliPermissions extends ToolPerm
|
|
|
23918
25236
|
const override = {};
|
|
23919
25237
|
if (typeof settings.toolPermission === "string") override.toolPermission = settings.toolPermission;
|
|
23920
25238
|
if (typeof settings.enableTerminalSandbox === "boolean") override.enableTerminalSandbox = settings.enableTerminalSandbox;
|
|
25239
|
+
if (typeof settings.artifactReviewPolicy === "string") override.artifactReviewPolicy = settings.artifactReviewPolicy;
|
|
25240
|
+
if (typeof settings.allowNonWorkspaceAccess === "boolean") override.allowNonWorkspaceAccess = settings.allowNonWorkspaceAccess;
|
|
23921
25241
|
const result = { ...config };
|
|
23922
25242
|
if (Object.keys(override).length > 0) result["antigravity-cli"] = override;
|
|
23923
25243
|
return this.toRulesyncPermissionsDefault({ fileContent: JSON.stringify(result, null, 2) });
|
|
@@ -24721,12 +26041,57 @@ function parseClaudePermissionEntry(entry) {
|
|
|
24721
26041
|
};
|
|
24722
26042
|
}
|
|
24723
26043
|
/**
|
|
26044
|
+
* Claude Code's file permission checks match only `Edit(path)` and `Read(path)`
|
|
26045
|
+
* rules. A `Write(path)`, `NotebookEdit(path)` or `Glob(path)` rule "is accepted
|
|
26046
|
+
* but never matched by those checks, so Claude Code warns at startup for each
|
|
26047
|
+
* allow, deny, or ask rule in one of these unmatched forms" — so a canonical
|
|
26048
|
+
* `write`/`notebookedit`/`glob` rule with a pattern is emitted in the form the
|
|
26049
|
+
* docs prescribe instead. A tool-name rule with no path is unaffected: it
|
|
26050
|
+
* matches the tool everywhere and produces no warning.
|
|
26051
|
+
* @see https://code.claude.com/docs/en/permissions
|
|
26052
|
+
*/
|
|
26053
|
+
function isPlainRecord(value) {
|
|
26054
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
26055
|
+
}
|
|
26056
|
+
/**
|
|
26057
|
+
* Merge `patch` into `base`, recursing into plain objects so a sibling key at
|
|
26058
|
+
* any depth survives. Arrays and scalars are replaced, since a list the author
|
|
26059
|
+
* states is the list they mean.
|
|
26060
|
+
*/
|
|
26061
|
+
function deepMergeRecords(base, patch) {
|
|
26062
|
+
const merged = { ...base };
|
|
26063
|
+
for (const [key, value] of Object.entries(patch)) {
|
|
26064
|
+
if (PROTOTYPE_POLLUTION_KEYS.has(key)) continue;
|
|
26065
|
+
const existing = merged[key];
|
|
26066
|
+
merged[key] = isPlainRecord(existing) && isPlainRecord(value) ? deepMergeRecords(existing, value) : value;
|
|
26067
|
+
}
|
|
26068
|
+
return merged;
|
|
26069
|
+
}
|
|
26070
|
+
const CLAUDE_PATH_RULE_ALIASES = {
|
|
26071
|
+
Write: "Edit",
|
|
26072
|
+
NotebookEdit: "Edit",
|
|
26073
|
+
Glob: "Read"
|
|
26074
|
+
};
|
|
26075
|
+
/**
|
|
24724
26076
|
* Build a Claude Code permission entry like "Bash(npm run *)".
|
|
24725
26077
|
* If the pattern is "*", returns just the tool name.
|
|
24726
26078
|
*/
|
|
24727
26079
|
function buildClaudePermissionEntry(toolName, pattern) {
|
|
24728
26080
|
if (pattern === "*") return toolName;
|
|
24729
|
-
return `${toolName}(${pattern})`;
|
|
26081
|
+
return `${CLAUDE_PATH_RULE_ALIASES[toolName] ?? toolName}(${pattern})`;
|
|
26082
|
+
}
|
|
26083
|
+
/**
|
|
26084
|
+
* The Claude tool names the canonical config manages. Deliberately the tool
|
|
26085
|
+
* names the categories map to and *not* the aliases a path rule is rewritten
|
|
26086
|
+
* to: claiming `Edit` because a `write` rule exists would sweep away the
|
|
26087
|
+
* `Read`/`Edit` entries the ignore feature and the user wrote in the same file.
|
|
26088
|
+
* The rewritten entries are still rulesync's to place — `applyPermissions`
|
|
26089
|
+
* replaces an entry this run emits wherever it currently sits — and the
|
|
26090
|
+
* original name stays claimed so an entry an older rulesync wrote in the warned
|
|
26091
|
+
* form is cleaned up on the next generate.
|
|
26092
|
+
*/
|
|
26093
|
+
function managedClaudeToolNames(config) {
|
|
26094
|
+
return new Set(Object.keys(config.permission).map((category) => toClaudeToolName(category)));
|
|
24730
26095
|
}
|
|
24731
26096
|
var ClaudecodePermissions = class ClaudecodePermissions extends ToolPermissions {
|
|
24732
26097
|
constructor(params) {
|
|
@@ -24766,7 +26131,10 @@ var ClaudecodePermissions = class ClaudecodePermissions extends ToolPermissions
|
|
|
24766
26131
|
throw new Error(`Failed to parse existing Claude settings at ${filePath}: ${formatError(error)}`, { cause: error });
|
|
24767
26132
|
}
|
|
24768
26133
|
const config = rulesyncPermissions.getJson();
|
|
24769
|
-
const { allow, ask, deny } = convertRulesyncToClaudePermissions(
|
|
26134
|
+
const { allow, ask, deny } = convertRulesyncToClaudePermissions({
|
|
26135
|
+
config,
|
|
26136
|
+
logger
|
|
26137
|
+
});
|
|
24770
26138
|
const overridePermissions = config.claudecode?.permissions;
|
|
24771
26139
|
if (overridePermissions && typeof overridePermissions === "object") {
|
|
24772
26140
|
const { allow: _a, ask: _k, deny: _d, ...nonListFields } = overridePermissions;
|
|
@@ -24775,7 +26143,9 @@ var ClaudecodePermissions = class ClaudecodePermissions extends ToolPermissions
|
|
|
24775
26143
|
...nonListFields
|
|
24776
26144
|
};
|
|
24777
26145
|
}
|
|
24778
|
-
const
|
|
26146
|
+
const overrideSandbox = config.claudecode?.sandbox;
|
|
26147
|
+
if (isPlainRecord(overrideSandbox)) settings.sandbox = deepMergeRecords(isPlainRecord(settings.sandbox) ? settings.sandbox : {}, overrideSandbox);
|
|
26148
|
+
const managedToolNames = managedClaudeToolNames(config);
|
|
24779
26149
|
const merged = applyPermissions({
|
|
24780
26150
|
settings,
|
|
24781
26151
|
managedToolNames,
|
|
@@ -24809,6 +26179,11 @@ var ClaudecodePermissions = class ClaudecodePermissions extends ToolPermissions
|
|
|
24809
26179
|
});
|
|
24810
26180
|
const { allow: _a, ask: _k, deny: _d, ...nonListFields } = permissions;
|
|
24811
26181
|
if (Object.keys(nonListFields).length > 0) config.claudecode = { permissions: nonListFields };
|
|
26182
|
+
const { sandbox } = settings;
|
|
26183
|
+
if (isPlainRecord(sandbox) && Object.keys(sandbox).length > 0) config.claudecode = {
|
|
26184
|
+
...config.claudecode,
|
|
26185
|
+
sandbox
|
|
26186
|
+
};
|
|
24812
26187
|
return this.toRulesyncPermissionsDefault({ fileContent: JSON.stringify(config, null, 2) });
|
|
24813
26188
|
}
|
|
24814
26189
|
validate() {
|
|
@@ -24830,14 +26205,18 @@ var ClaudecodePermissions = class ClaudecodePermissions extends ToolPermissions
|
|
|
24830
26205
|
/**
|
|
24831
26206
|
* Convert rulesync permissions config to Claude Code allow/ask/deny arrays.
|
|
24832
26207
|
*/
|
|
24833
|
-
function convertRulesyncToClaudePermissions(config) {
|
|
26208
|
+
function convertRulesyncToClaudePermissions({ config, logger }) {
|
|
24834
26209
|
const allow = [];
|
|
24835
26210
|
const ask = [];
|
|
24836
26211
|
const deny = [];
|
|
26212
|
+
const actionByEntry = /* @__PURE__ */ new Map();
|
|
24837
26213
|
for (const [category, rules] of Object.entries(config.permission)) {
|
|
24838
26214
|
const claudeToolName = toClaudeToolName(category);
|
|
24839
26215
|
for (const [pattern, action] of Object.entries(rules)) {
|
|
24840
26216
|
const entry = buildClaudePermissionEntry(claudeToolName, pattern);
|
|
26217
|
+
const previous = actionByEntry.get(entry);
|
|
26218
|
+
if (previous !== void 0 && previous !== action) logger?.warn(`Claude Code permissions: rules from different categories both resolve to "${entry}" with conflicting actions (${previous} and ${action}). Both are written; Claude Code applies deny first, then ask, then allow.`);
|
|
26219
|
+
actionByEntry.set(entry, action);
|
|
24841
26220
|
switch (action) {
|
|
24842
26221
|
case "allow":
|
|
24843
26222
|
allow.push(entry);
|
|
@@ -25650,19 +27029,24 @@ function mapBashActionToDecision(action) {
|
|
|
25650
27029
|
//#endregion
|
|
25651
27030
|
//#region src/features/permissions/copilot-permissions.ts
|
|
25652
27031
|
/**
|
|
25653
|
-
* The flat, dotted VS Code setting
|
|
25654
|
-
* settings with dotted keys flat at the
|
|
25655
|
-
* literal key — not a nested
|
|
27032
|
+
* The flat, dotted VS Code setting keys this adapter manages, one per canonical
|
|
27033
|
+
* permission category. VS Code stores settings with dotted keys flat at the
|
|
27034
|
+
* document top level, so each is a single literal key — not a nested
|
|
27035
|
+
* `chat.tools.terminal` path. All three share the same
|
|
27036
|
+
* pattern-to-boolean shape, so one conversion covers them.
|
|
27037
|
+
*
|
|
27038
|
+
* The canonical `read` and `write` categories stay unmapped: VS Code has no
|
|
27039
|
+
* read-approval surface, and folding `write` into the edits map alongside
|
|
27040
|
+
* `edit` would make the two indistinguishable on import.
|
|
27041
|
+
*
|
|
25656
27042
|
* @see https://code.visualstudio.com/docs/agents/approvals
|
|
27043
|
+
* @see https://code.visualstudio.com/docs/copilot/chat/review-code-edits
|
|
25657
27044
|
*/
|
|
25658
|
-
const
|
|
25659
|
-
|
|
25660
|
-
|
|
25661
|
-
|
|
25662
|
-
|
|
25663
|
-
* …) have no terminal-command equivalent and are intentionally not mapped.
|
|
25664
|
-
*/
|
|
25665
|
-
const TERMINAL_CATEGORY = "bash";
|
|
27045
|
+
const AUTO_APPROVE_KEYS = {
|
|
27046
|
+
bash: "chat.tools.terminal.autoApprove",
|
|
27047
|
+
edit: "chat.tools.edits.autoApprove",
|
|
27048
|
+
webfetch: "chat.tools.urls.autoApprove"
|
|
27049
|
+
};
|
|
25666
27050
|
function asAutoApproveMap(value) {
|
|
25667
27051
|
if (!isPlainObject$1(value)) return {};
|
|
25668
27052
|
const result = {};
|
|
@@ -25670,6 +27054,21 @@ function asAutoApproveMap(value) {
|
|
|
25670
27054
|
return result;
|
|
25671
27055
|
}
|
|
25672
27056
|
/**
|
|
27057
|
+
* Render one canonical category's rules as a VS Code auto-approve map. Returns
|
|
27058
|
+
* `undefined` when the category contributes nothing, so the key is retracted
|
|
27059
|
+
* rather than written as an empty object.
|
|
27060
|
+
*
|
|
27061
|
+
* The resulting map replaces the file's existing value wholesale — rulesync
|
|
27062
|
+
* owns these keys, so a rule dropped from the canonical config disappears from
|
|
27063
|
+
* the settings file too.
|
|
27064
|
+
*/
|
|
27065
|
+
function buildAutoApproveValue(rules) {
|
|
27066
|
+
const autoApprove = {};
|
|
27067
|
+
for (const [pattern, action] of Object.entries(rules)) if (action === "allow") autoApprove[pattern] = true;
|
|
27068
|
+
else if (action === "deny") autoApprove[pattern] = false;
|
|
27069
|
+
return Object.keys(autoApprove).length > 0 ? autoApprove : void 0;
|
|
27070
|
+
}
|
|
27071
|
+
/**
|
|
25673
27072
|
* Permissions generator for GitHub Copilot Chat in VS Code.
|
|
25674
27073
|
*
|
|
25675
27074
|
* VS Code has no standalone, environment-agnostic Copilot policy file (like
|
|
@@ -25679,13 +27078,16 @@ function asAutoApproveMap(value) {
|
|
|
25679
27078
|
* many unrelated keys, so reads and writes merge into the existing JSON
|
|
25680
27079
|
* (touching only the one managed key) and the file is never deleted.
|
|
25681
27080
|
*
|
|
25682
|
-
*
|
|
25683
|
-
*
|
|
25684
|
-
* map as: `allow` → `true` (auto-approve), `deny`
|
|
25685
|
-
*
|
|
25686
|
-
*
|
|
25687
|
-
*
|
|
25688
|
-
*
|
|
27081
|
+
* Three canonical categories have a clean, non-lossy representation and are
|
|
27082
|
+
* mapped (see {@link AUTO_APPROVE_KEYS}): `bash`, `edit` and `webfetch`. In
|
|
27083
|
+
* every one, per-pattern rules map as: `allow` → `true` (auto-approve), `deny`
|
|
27084
|
+
* → `false` (VS Code then always prompts — note this is "never auto-approve",
|
|
27085
|
+
* not a hard block), and `ask` → the entry is OMITTED (VS Code falls through to
|
|
27086
|
+
* the same default prompt). A key whose canonical category is absent entirely
|
|
27087
|
+
* is left untouched, so authoring only `bash` rules never disturbs a
|
|
27088
|
+
* hand-written edits or urls map.
|
|
27089
|
+
* Only project scope is modeled: VS Code's user-scope settings.json lives at a
|
|
27090
|
+
* platform-dependent path outside rulesync's home-relative global model.
|
|
25689
27091
|
*/
|
|
25690
27092
|
var CopilotPermissions = class CopilotPermissions extends ToolPermissions {
|
|
25691
27093
|
constructor(params) {
|
|
@@ -25722,11 +27124,13 @@ var CopilotPermissions = class CopilotPermissions extends ToolPermissions {
|
|
|
25722
27124
|
const paths = CopilotPermissions.getSettablePaths();
|
|
25723
27125
|
const filePath = (0, node_path.join)(outputRoot, paths.relativeDirPath, paths.relativeFilePath);
|
|
25724
27126
|
const existingContent = await readFileContentOrNull(filePath) ?? "{}";
|
|
25725
|
-
const
|
|
25726
|
-
const
|
|
25727
|
-
for (const [
|
|
25728
|
-
|
|
25729
|
-
|
|
27127
|
+
const config = rulesyncPermissions.getJson();
|
|
27128
|
+
const patch = {};
|
|
27129
|
+
for (const [category, settingKey] of Object.entries(AUTO_APPROVE_KEYS)) {
|
|
27130
|
+
const rules = config.permission[category];
|
|
27131
|
+
if (rules === void 0) continue;
|
|
27132
|
+
patch[settingKey] = buildAutoApproveValue(rules);
|
|
27133
|
+
}
|
|
25730
27134
|
return new CopilotPermissions({
|
|
25731
27135
|
outputRoot,
|
|
25732
27136
|
relativeDirPath: paths.relativeDirPath,
|
|
@@ -25735,7 +27139,7 @@ var CopilotPermissions = class CopilotPermissions extends ToolPermissions {
|
|
|
25735
27139
|
fileKey: sharedConfigFileKey(paths),
|
|
25736
27140
|
feature: "permissions",
|
|
25737
27141
|
existingContent,
|
|
25738
|
-
patch
|
|
27142
|
+
patch,
|
|
25739
27143
|
filePath
|
|
25740
27144
|
}),
|
|
25741
27145
|
validate: true
|
|
@@ -25754,10 +27158,13 @@ var CopilotPermissions = class CopilotPermissions extends ToolPermissions {
|
|
|
25754
27158
|
} catch (error) {
|
|
25755
27159
|
throw new Error(`Failed to parse Copilot VS Code settings in ${(0, node_path.join)(this.getRelativeDirPath(), this.getRelativeFilePath())}: ${formatError(error)}`, { cause: error });
|
|
25756
27160
|
}
|
|
25757
|
-
const
|
|
25758
|
-
const
|
|
25759
|
-
|
|
25760
|
-
|
|
27161
|
+
const permission = {};
|
|
27162
|
+
for (const [category, settingKey] of Object.entries(AUTO_APPROVE_KEYS)) {
|
|
27163
|
+
const autoApprove = asAutoApproveMap(settings[settingKey]);
|
|
27164
|
+
const rules = {};
|
|
27165
|
+
for (const [pattern, flag] of Object.entries(autoApprove)) rules[pattern] = flag ? "allow" : "deny";
|
|
27166
|
+
if (Object.keys(rules).length > 0) permission[category] = rules;
|
|
27167
|
+
}
|
|
25761
27168
|
return this.toRulesyncPermissionsDefault({ fileContent: JSON.stringify({ permission }, null, 2) });
|
|
25762
27169
|
}
|
|
25763
27170
|
validate() {
|
|
@@ -27087,6 +28494,13 @@ var HermesagentPermissions = class HermesagentPermissions extends ToolPermission
|
|
|
27087
28494
|
relativeFilePath: HERMESAGENT_CONFIG_FILE_NAME
|
|
27088
28495
|
};
|
|
27089
28496
|
}
|
|
28497
|
+
/**
|
|
28498
|
+
* `config.yaml` under every spelling the global profile root can take.
|
|
28499
|
+
* @see getHermesagentSharedConfigWritePaths
|
|
28500
|
+
*/
|
|
28501
|
+
static getExtraSharedWritePaths() {
|
|
28502
|
+
return getHermesagentSharedConfigWritePaths();
|
|
28503
|
+
}
|
|
27090
28504
|
constructor(params) {
|
|
27091
28505
|
super({
|
|
27092
28506
|
...params,
|
|
@@ -27124,7 +28538,7 @@ var HermesagentPermissions = class HermesagentPermissions extends ToolPermission
|
|
|
27124
28538
|
}
|
|
27125
28539
|
setFileContent(fileContent) {
|
|
27126
28540
|
this.fileContent = applySharedConfigPatch({
|
|
27127
|
-
fileKey:
|
|
28541
|
+
fileKey: getHermesagentConfigSharedFileKey({ global: this.global }),
|
|
27128
28542
|
feature: "permissions",
|
|
27129
28543
|
existingContent: fileContent,
|
|
27130
28544
|
patch: parseSharedConfig({
|
|
@@ -27537,7 +28951,10 @@ const KiloPermissionSchema = zod_mini.z.union([zod_mini.z.enum([
|
|
|
27537
28951
|
"ask",
|
|
27538
28952
|
"deny"
|
|
27539
28953
|
]))]);
|
|
27540
|
-
const KiloPermissionsConfigSchema = zod_mini.z.looseObject({
|
|
28954
|
+
const KiloPermissionsConfigSchema = zod_mini.z.looseObject({
|
|
28955
|
+
permission: zod_mini.z.optional(zod_mini.z.record(zod_mini.z.string(), KiloPermissionSchema)),
|
|
28956
|
+
sandbox: zod_mini.z.optional(zod_mini.z.unknown())
|
|
28957
|
+
});
|
|
27541
28958
|
/**
|
|
27542
28959
|
* Kilo permission keys that share a name with a canonical rulesync category and
|
|
27543
28960
|
* therefore stay in the shared `permission` block. Everything else (Kilo-only
|
|
@@ -27594,6 +29011,25 @@ function collectKiloDenyPatterns(value) {
|
|
|
27594
29011
|
}
|
|
27595
29012
|
return [];
|
|
27596
29013
|
}
|
|
29014
|
+
function asKiloRecord(value) {
|
|
29015
|
+
return isPlainObject$1(value) ? { ...value } : {};
|
|
29016
|
+
}
|
|
29017
|
+
/**
|
|
29018
|
+
* The `sandbox` keys a *project* `kilo.jsonc` may state. Kilo honors
|
|
29019
|
+
* `allowed_hosts` and `writable_paths` from the global config only, and lets a
|
|
29020
|
+
* project config merely tighten — so writing the wider keys into a project file
|
|
29021
|
+
* would produce config Kilo ignores.
|
|
29022
|
+
* @see https://kilo.ai/docs/getting-started/settings/sandboxing
|
|
29023
|
+
*/
|
|
29024
|
+
const KILO_PROJECT_SCOPE_SANDBOX_KEYS = /* @__PURE__ */ new Set(["enabled", "network"]);
|
|
29025
|
+
function narrowSandboxToProjectScope({ authored, logger }) {
|
|
29026
|
+
const emitted = {};
|
|
29027
|
+
const dropped = [];
|
|
29028
|
+
for (const [key, value] of Object.entries(authored)) if (KILO_PROJECT_SCOPE_SANDBOX_KEYS.has(key)) emitted[key] = value;
|
|
29029
|
+
else dropped.push(key);
|
|
29030
|
+
if (dropped.length > 0) logger?.warn(`Kilo honors these 'sandbox' keys from the global config only, so they were dropped from the project config: ${dropped.toSorted().join(", ")}. A project config may only tighten the sandbox ('enabled', 'network'); generate with --global to author the rest.`);
|
|
29031
|
+
return emitted;
|
|
29032
|
+
}
|
|
27597
29033
|
var KiloPermissions = class KiloPermissions extends ToolPermissions {
|
|
27598
29034
|
json;
|
|
27599
29035
|
constructor(params) {
|
|
@@ -27673,8 +29109,7 @@ var KiloPermissions = class KiloPermissions extends ToolPermissions {
|
|
|
27673
29109
|
const basePaths = KiloPermissions.getSettablePaths({ global });
|
|
27674
29110
|
const filePath = (0, node_path.join)(outputRoot, basePaths.relativeDirPath, basePaths.relativeFilePath);
|
|
27675
29111
|
const parsed = parseKiloJsoncStrict(await readFileContentOrNull(filePath) ?? "{}", filePath);
|
|
27676
|
-
const
|
|
27677
|
-
const existingPermission = parsedPermission && typeof parsedPermission === "object" && !Array.isArray(parsedPermission) ? { ...parsedPermission } : {};
|
|
29112
|
+
const existingPermission = asKiloRecord(parsed.permission);
|
|
27678
29113
|
const rulesyncJson = rulesyncPermissions.getJson();
|
|
27679
29114
|
const kiloOverride = rulesyncJson.kilo;
|
|
27680
29115
|
const incomingPermission = {
|
|
@@ -27700,6 +29135,18 @@ var KiloPermissions = class KiloPermissions extends ToolPermissions {
|
|
|
27700
29135
|
...parsed,
|
|
27701
29136
|
permission: mergedPermission
|
|
27702
29137
|
};
|
|
29138
|
+
if (kiloOverride?.sandbox !== void 0) {
|
|
29139
|
+
const authored = asKiloRecord(kiloOverride.sandbox);
|
|
29140
|
+
const emitted = global ? authored : narrowSandboxToProjectScope({
|
|
29141
|
+
authored,
|
|
29142
|
+
logger
|
|
29143
|
+
});
|
|
29144
|
+
const merged = {
|
|
29145
|
+
...asKiloRecord(parsed.sandbox),
|
|
29146
|
+
...emitted
|
|
29147
|
+
};
|
|
29148
|
+
if (Object.keys(merged).length > 0) nextJson.sandbox = merged;
|
|
29149
|
+
}
|
|
27703
29150
|
return new KiloPermissions({
|
|
27704
29151
|
outputRoot,
|
|
27705
29152
|
relativeDirPath: basePaths.relativeDirPath,
|
|
@@ -27714,9 +29161,14 @@ var KiloPermissions = class KiloPermissions extends ToolPermissions {
|
|
|
27714
29161
|
const overrideOnly = {};
|
|
27715
29162
|
for (const [key, value] of Object.entries(rawPermission)) if (isSharedKiloCategory(key)) shared[key] = typeof value === "string" ? { "*": value } : value;
|
|
27716
29163
|
else overrideOnly[key] = value;
|
|
27717
|
-
const
|
|
29164
|
+
const sandbox = this.json.sandbox;
|
|
29165
|
+
const override = {
|
|
29166
|
+
...Object.keys(overrideOnly).length > 0 && { permission: overrideOnly },
|
|
29167
|
+
...isPlainObject$1(sandbox) && { sandbox }
|
|
29168
|
+
};
|
|
29169
|
+
const json = Object.keys(override).length > 0 ? {
|
|
27718
29170
|
permission: shared,
|
|
27719
|
-
kilo:
|
|
29171
|
+
kilo: override
|
|
27720
29172
|
} : { permission: shared };
|
|
27721
29173
|
return this.toRulesyncPermissionsDefault({ fileContent: JSON.stringify(json, null, 2) });
|
|
27722
29174
|
}
|
|
@@ -27957,6 +29409,13 @@ var KimiCodePermissions = class KimiCodePermissions extends ToolPermissions {
|
|
|
27957
29409
|
isDeletable() {
|
|
27958
29410
|
return false;
|
|
27959
29411
|
}
|
|
29412
|
+
/**
|
|
29413
|
+
* `config.toml` under both spellings its directory can take.
|
|
29414
|
+
* @see getKimiCodeSharedConfigWritePaths
|
|
29415
|
+
*/
|
|
29416
|
+
static getExtraSharedWritePaths() {
|
|
29417
|
+
return getKimiCodeSharedConfigWritePaths();
|
|
29418
|
+
}
|
|
27960
29419
|
shouldMergeExistingFileContent() {
|
|
27961
29420
|
return true;
|
|
27962
29421
|
}
|
|
@@ -27971,7 +29430,7 @@ var KimiCodePermissions = class KimiCodePermissions extends ToolPermissions {
|
|
|
27971
29430
|
patch
|
|
27972
29431
|
});
|
|
27973
29432
|
this.fileContent = applySharedConfigPatch({
|
|
27974
|
-
fileKey:
|
|
29433
|
+
fileKey: getKimiCodeConfigSharedFileKey({ global: this.global }),
|
|
27975
29434
|
feature: "permissions",
|
|
27976
29435
|
existingContent: fileContent,
|
|
27977
29436
|
patch: {
|
|
@@ -28895,14 +30354,6 @@ function parseReasonixConfig(fileContent) {
|
|
|
28895
30354
|
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return {};
|
|
28896
30355
|
return { ...parsed };
|
|
28897
30356
|
}
|
|
28898
|
-
function toStringArray$1(value) {
|
|
28899
|
-
if (!Array.isArray(value)) return [];
|
|
28900
|
-
return value.filter((entry) => typeof entry === "string");
|
|
28901
|
-
}
|
|
28902
|
-
function toPermissionsTable(value) {
|
|
28903
|
-
if (!value || typeof value !== "object" || Array.isArray(value)) return {};
|
|
28904
|
-
return { ...value };
|
|
28905
|
-
}
|
|
28906
30357
|
const REASONIX_OVERRIDE_AGENT_KEYS = ["plan_mode_read_only_commands"];
|
|
28907
30358
|
/**
|
|
28908
30359
|
* `[agent]` keys an older `reasonix.toml` may carry that left the documented
|
|
@@ -28959,12 +30410,12 @@ var ReasonixPermissions = class ReasonixPermissions extends ToolPermissions {
|
|
|
28959
30410
|
const config = rulesyncPermissions.getJson();
|
|
28960
30411
|
const { allow, ask, deny } = convertRulesyncToReasonixPermissions(config);
|
|
28961
30412
|
const managedToolNames = new Set(Object.keys(config.permission).map((category) => toReasonixToolName(category)));
|
|
28962
|
-
const existingPermissions =
|
|
28963
|
-
const preservedAllow =
|
|
28964
|
-
const preservedAsk =
|
|
28965
|
-
const preservedDeny =
|
|
30413
|
+
const existingPermissions = toReasonixTable(parsed.permissions);
|
|
30414
|
+
const preservedAllow = toReasonixStringArray(existingPermissions.allow).filter((entry) => !managedToolNames.has(parseReasonixPermissionEntry(entry).toolName));
|
|
30415
|
+
const preservedAsk = toReasonixStringArray(existingPermissions.ask).filter((entry) => !managedToolNames.has(parseReasonixPermissionEntry(entry).toolName));
|
|
30416
|
+
const preservedDeny = toReasonixStringArray(existingPermissions.deny).filter((entry) => !managedToolNames.has(parseReasonixPermissionEntry(entry).toolName));
|
|
28966
30417
|
if (logger && managedToolNames.has("Read")) {
|
|
28967
|
-
const droppedReadDenyEntries =
|
|
30418
|
+
const droppedReadDenyEntries = toReasonixStringArray(existingPermissions.deny).filter((entry) => {
|
|
28968
30419
|
const { toolName } = parseReasonixPermissionEntry(entry);
|
|
28969
30420
|
return toolName === "Read";
|
|
28970
30421
|
});
|
|
@@ -29011,11 +30462,11 @@ var ReasonixPermissions = class ReasonixPermissions extends ToolPermissions {
|
|
|
29011
30462
|
});
|
|
29012
30463
|
}
|
|
29013
30464
|
toRulesyncPermissions() {
|
|
29014
|
-
const permissions =
|
|
30465
|
+
const permissions = toReasonixTable(this.toml.permissions);
|
|
29015
30466
|
const config = convertReasonixToRulesyncPermissions({
|
|
29016
|
-
allow:
|
|
29017
|
-
ask:
|
|
29018
|
-
deny:
|
|
30467
|
+
allow: toReasonixStringArray(permissions.allow),
|
|
30468
|
+
ask: toReasonixStringArray(permissions.ask),
|
|
30469
|
+
deny: toReasonixStringArray(permissions.deny)
|
|
29019
30470
|
});
|
|
29020
30471
|
const sandbox = asReasonixRecord(this.toml.sandbox);
|
|
29021
30472
|
const agentPlanMode = pickReasonixKeys(this.toml.agent, [...REASONIX_OVERRIDE_AGENT_KEYS, ...REASONIX_RETIRED_AGENT_KEYS]);
|
|
@@ -29107,19 +30558,31 @@ const CATEGORY_TO_TOOL_KEYS = {
|
|
|
29107
30558
|
"open_files",
|
|
29108
30559
|
"expand_code_chunks",
|
|
29109
30560
|
"expand_folder",
|
|
29110
|
-
"grep"
|
|
30561
|
+
"grep",
|
|
30562
|
+
"getJiraIssue",
|
|
30563
|
+
"getConfluencePage"
|
|
29111
30564
|
],
|
|
29112
30565
|
edit: [
|
|
29113
30566
|
"find_and_replace_code",
|
|
29114
30567
|
"create_file",
|
|
29115
30568
|
"delete_file",
|
|
29116
|
-
"move_file"
|
|
30569
|
+
"move_file",
|
|
30570
|
+
"createTechnicalPlan",
|
|
30571
|
+
"createJiraIssue",
|
|
30572
|
+
"updateJiraIssue",
|
|
30573
|
+
"createConfluencePage",
|
|
30574
|
+
"updateConfluencePage"
|
|
29117
30575
|
],
|
|
29118
30576
|
write: [
|
|
29119
30577
|
"create_file",
|
|
29120
30578
|
"delete_file",
|
|
29121
30579
|
"move_file",
|
|
29122
|
-
"find_and_replace_code"
|
|
30580
|
+
"find_and_replace_code",
|
|
30581
|
+
"createTechnicalPlan",
|
|
30582
|
+
"createJiraIssue",
|
|
30583
|
+
"updateJiraIssue",
|
|
30584
|
+
"createConfluencePage",
|
|
30585
|
+
"updateConfluencePage"
|
|
29123
30586
|
]
|
|
29124
30587
|
};
|
|
29125
30588
|
const TOOL_KEY_TO_CATEGORY = {
|
|
@@ -29127,13 +30590,24 @@ const TOOL_KEY_TO_CATEGORY = {
|
|
|
29127
30590
|
expand_code_chunks: "read",
|
|
29128
30591
|
expand_folder: "read",
|
|
29129
30592
|
grep: "read",
|
|
30593
|
+
getJiraIssue: "read",
|
|
30594
|
+
getConfluencePage: "read",
|
|
29130
30595
|
find_and_replace_code: "edit",
|
|
29131
30596
|
create_file: "edit",
|
|
29132
30597
|
delete_file: "edit",
|
|
29133
|
-
move_file: "edit"
|
|
30598
|
+
move_file: "edit",
|
|
30599
|
+
createTechnicalPlan: "edit",
|
|
30600
|
+
createJiraIssue: "edit",
|
|
30601
|
+
updateJiraIssue: "edit",
|
|
30602
|
+
createConfluencePage: "edit",
|
|
30603
|
+
updateConfluencePage: "edit"
|
|
29134
30604
|
};
|
|
29135
30605
|
const MANAGED_TOOL_KEYS = [.../* @__PURE__ */ new Set([...Object.values(CATEGORY_TO_TOOL_KEYS).flat(), ...Object.keys(TOOL_KEY_TO_CATEGORY)])];
|
|
29136
|
-
const OWNED_TOOL_PERMISSION_KEYS = [
|
|
30606
|
+
const OWNED_TOOL_PERMISSION_KEYS = [
|
|
30607
|
+
"bash",
|
|
30608
|
+
"allowedExternalPaths",
|
|
30609
|
+
"default"
|
|
30610
|
+
];
|
|
29137
30611
|
/**
|
|
29138
30612
|
* Permissions adapter for Rovo Dev CLI.
|
|
29139
30613
|
*
|
|
@@ -29147,9 +30621,16 @@ const OWNED_TOOL_PERMISSION_KEYS = ["bash", "allowedExternalPaths"];
|
|
|
29147
30621
|
* Mapping decisions (rulesync canonical -> Rovo Dev):
|
|
29148
30622
|
* - `bash`: the catch-all `*` pattern -> `bash.default`; every other pattern ->
|
|
29149
30623
|
* a `bash.commands[]` entry `{ command: <pattern as regex>, permission }`.
|
|
30624
|
+
* - the all-tools category `*`: its catch-all -> `toolPermissions.default`,
|
|
30625
|
+
* the level Rovo Dev falls back to for any tool with no more specific
|
|
30626
|
+
* setting (Rovo Dev's own default is `ask`).
|
|
29150
30627
|
* - `read` -> the inspection tools (`open_files`, `expand_code_chunks`,
|
|
29151
|
-
* `expand_folder`, `grep`
|
|
29152
|
-
*
|
|
30628
|
+
* `expand_folder`, `grep`, `getJiraIssue`, `getConfluencePage`);
|
|
30629
|
+
* `edit`/`write` -> the mutation tools (`find_and_replace_code`,
|
|
30630
|
+
* `create_file`, `delete_file`, `move_file`, `createTechnicalPlan`,
|
|
30631
|
+
* `createJiraIssue`, `updateJiraIssue`, `createConfluencePage`,
|
|
30632
|
+
* `updateConfluencePage`) — so these two categories reach Jira and
|
|
30633
|
+
* Confluence, not just the working tree.
|
|
29153
30634
|
* These Rovo Dev keys hold a single level (no per-pattern rules), so only the
|
|
29154
30635
|
* catch-all `*` of each category sets the level. Non-catch-all `allow` rules
|
|
29155
30636
|
* in those categories are surfaced as `allowedExternalPaths` so explicit path
|
|
@@ -29329,6 +30810,10 @@ function stripPermissiveOwnedValues(toolPermissions) {
|
|
|
29329
30810
|
delete toolPermissions.allowedExternalPaths;
|
|
29330
30811
|
strippedKeys.push("allowedExternalPaths");
|
|
29331
30812
|
}
|
|
30813
|
+
if (toolPermissions.default === "allow") {
|
|
30814
|
+
delete toolPermissions.default;
|
|
30815
|
+
strippedKeys.push("default");
|
|
30816
|
+
}
|
|
29332
30817
|
const bash = toolPermissions.bash;
|
|
29333
30818
|
if (isRecord(bash)) {
|
|
29334
30819
|
const stripped = { ...bash };
|
|
@@ -29353,10 +30838,19 @@ function stripPermissiveOwnedValues(toolPermissions) {
|
|
|
29353
30838
|
function convertRulesyncToRovodevToolPermissions({ config, logger }) {
|
|
29354
30839
|
const toolPermissions = {};
|
|
29355
30840
|
const allowedExternalPaths = [];
|
|
29356
|
-
|
|
29357
|
-
|
|
29358
|
-
|
|
30841
|
+
warnOnEditWriteConflict({
|
|
30842
|
+
config,
|
|
30843
|
+
logger
|
|
30844
|
+
});
|
|
29359
30845
|
for (const [category, rules] of Object.entries(config.permission)) {
|
|
30846
|
+
if (category === CATCH_ALL_PATTERN$1) {
|
|
30847
|
+
const toolWideDefault = convertAllToolsRules({
|
|
30848
|
+
rules,
|
|
30849
|
+
logger
|
|
30850
|
+
});
|
|
30851
|
+
if (toolWideDefault) toolPermissions.default = toolWideDefault;
|
|
30852
|
+
continue;
|
|
30853
|
+
}
|
|
29360
30854
|
if (category === "bash") {
|
|
29361
30855
|
const bash = convertBashRules(rules);
|
|
29362
30856
|
if (bash) toolPermissions.bash = bash;
|
|
@@ -29383,6 +30877,36 @@ function convertRulesyncToRovodevToolPermissions({ config, logger }) {
|
|
|
29383
30877
|
if (allowedExternalPaths.length > 0) toolPermissions.allowedExternalPaths = [...new Set(allowedExternalPaths)].toSorted();
|
|
29384
30878
|
return toolPermissions;
|
|
29385
30879
|
}
|
|
30880
|
+
/**
|
|
30881
|
+
* `edit` and `write` collapse onto the same Rovo Dev file-mutation tools, so a
|
|
30882
|
+
* conflicting catch-all between them cannot be represented. Warn that the loss
|
|
30883
|
+
* is happening; the conversion keeps the stricter of the two — the same
|
|
30884
|
+
* fail-closed rule the import direction uses when those tools disagree, so the
|
|
30885
|
+
* resolution never grants more than the author asked for.
|
|
30886
|
+
*/
|
|
30887
|
+
function warnOnEditWriteConflict({ config, logger }) {
|
|
30888
|
+
const editCatchAll = config.permission.edit?.[CATCH_ALL_PATTERN$1];
|
|
30889
|
+
const writeCatchAll = config.permission.write?.[CATCH_ALL_PATTERN$1];
|
|
30890
|
+
if (editCatchAll && writeCatchAll && editCatchAll !== writeCatchAll) logger?.warn(`Rovo Dev maps both "edit" and "write" onto the same file-mutation tools, but they have conflicting catch-all permissions ("edit": "${editCatchAll}", "write": "${writeCatchAll}"). The stricter of the two ("${strictestAction(editCatchAll, writeCatchAll)}") is used.`);
|
|
30891
|
+
}
|
|
30892
|
+
/**
|
|
30893
|
+
* The canonical all-tools category. Its catch-all sets the tool-wide
|
|
30894
|
+
* `toolPermissions.default`, the same way `bash`'s catch-all sets
|
|
30895
|
+
* `bash.default` — both are the level Rovo Dev falls back to. Pattern rules
|
|
30896
|
+
* under `*` have no counterpart (the default is a single level), so they are
|
|
30897
|
+
* reported and skipped like any other rule Rovo Dev cannot express.
|
|
30898
|
+
*/
|
|
30899
|
+
function convertAllToolsRules({ rules, logger }) {
|
|
30900
|
+
let toolWideDefault;
|
|
30901
|
+
for (const [pattern, action] of Object.entries(rules)) {
|
|
30902
|
+
if (pattern === CATCH_ALL_PATTERN$1) {
|
|
30903
|
+
toolWideDefault = action;
|
|
30904
|
+
continue;
|
|
30905
|
+
}
|
|
30906
|
+
logger?.warn(`Rovo Dev's tool-wide default is a single level, so it cannot express the pattern "${pattern}" in the "*" category. Skipping it.`);
|
|
30907
|
+
}
|
|
30908
|
+
return toolWideDefault;
|
|
30909
|
+
}
|
|
29386
30910
|
function convertBashRules(rules) {
|
|
29387
30911
|
const bash = {};
|
|
29388
30912
|
const commands = [];
|
|
@@ -29404,6 +30928,7 @@ function convertBashRules(rules) {
|
|
|
29404
30928
|
*/
|
|
29405
30929
|
function convertRovodevToolPermissionsToRulesync(toolPermissions) {
|
|
29406
30930
|
const permission = {};
|
|
30931
|
+
if (isPermissionAction(toolPermissions.default)) permission[CATCH_ALL_PATTERN$1] = { [CATCH_ALL_PATTERN$1]: toolPermissions.default };
|
|
29407
30932
|
const bash = toolPermissions.bash;
|
|
29408
30933
|
if (isRecord(bash)) {
|
|
29409
30934
|
const bashRules = {};
|
|
@@ -29414,12 +30939,15 @@ function convertRovodevToolPermissionsToRulesync(toolPermissions) {
|
|
|
29414
30939
|
if (Object.keys(bashRules).length > 0) permission.bash = bashRules;
|
|
29415
30940
|
}
|
|
29416
30941
|
const nestedTools = isRecord(toolPermissions.tools) ? toolPermissions.tools : {};
|
|
29417
|
-
|
|
29418
|
-
|
|
29419
|
-
|
|
30942
|
+
const implicitLevel = isPermissionAction(toolPermissions.default) ? toolPermissions.default : "ask";
|
|
30943
|
+
for (const category of new Set(Object.values(TOOL_KEY_TO_CATEGORY))) {
|
|
30944
|
+
const levels = Object.entries(TOOL_KEY_TO_CATEGORY).filter(([, mapped]) => mapped === category).map(([toolKey]) => {
|
|
30945
|
+
const value = Object.hasOwn(nestedTools, toolKey) ? nestedTools[toolKey] : toolPermissions[toolKey];
|
|
30946
|
+
return isPermissionAction(value) ? value : void 0;
|
|
30947
|
+
});
|
|
30948
|
+
if (levels.every((level) => level === void 0)) continue;
|
|
29420
30949
|
permission[category] ??= {};
|
|
29421
|
-
|
|
29422
|
-
permission[category][CATCH_ALL_PATTERN$1] = strictestAction(current, value);
|
|
30950
|
+
permission[category][CATCH_ALL_PATTERN$1] = levels.reduce((strictest, level) => strictestAction(strictest, level ?? implicitLevel), permission[category][CATCH_ALL_PATTERN$1]);
|
|
29423
30951
|
}
|
|
29424
30952
|
if (isStringArray$1(toolPermissions.allowedExternalPaths)) for (const path of toolPermissions.allowedExternalPaths) {
|
|
29425
30953
|
permission.read ??= {};
|
|
@@ -30251,16 +31779,11 @@ var ZedPermissions = class ZedPermissions extends ToolPermissions {
|
|
|
30251
31779
|
relativeFilePath: ZED_SETTINGS_FILE_NAME
|
|
30252
31780
|
};
|
|
30253
31781
|
}
|
|
30254
|
-
/**
|
|
30255
|
-
* The global settings file of the OTHER platform: `getSettablePaths` resolves
|
|
30256
|
-
* `~/.config/zed` vs `%APPDATA%\Zed` per platform, but the shared-write
|
|
30257
|
-
* derivation (and the gateway ownership table it is checked against) must
|
|
30258
|
-
* know both spellings on every platform.
|
|
30259
|
-
*/
|
|
31782
|
+
/** @see getZedOtherPlatformGlobalDir */
|
|
30260
31783
|
static getExtraSharedWritePaths({ global = false } = {}) {
|
|
30261
31784
|
if (!global) return [];
|
|
30262
31785
|
return [{
|
|
30263
|
-
relativeDirPath:
|
|
31786
|
+
relativeDirPath: getZedOtherPlatformGlobalDir(),
|
|
30264
31787
|
relativeFilePath: ZED_SETTINGS_FILE_NAME
|
|
30265
31788
|
}];
|
|
30266
31789
|
}
|
|
@@ -31777,6 +33300,7 @@ const ClaudecodeSkillFrontmatterSchema = zod_mini.z.looseObject({
|
|
|
31777
33300
|
arguments: zod_mini.z.optional(zod_mini.z.union([zod_mini.z.string(), zod_mini.z.array(zod_mini.z.string())])),
|
|
31778
33301
|
context: zod_mini.z.optional(zod_mini.z.string()),
|
|
31779
33302
|
agent: zod_mini.z.optional(zod_mini.z.string()),
|
|
33303
|
+
background: zod_mini.z.optional(zod_mini.z.boolean()),
|
|
31780
33304
|
hooks: zod_mini.z.optional(zod_mini.z.looseObject({})),
|
|
31781
33305
|
shell: zod_mini.z.optional(zod_mini.z.string()),
|
|
31782
33306
|
"disable-model-invocation": zod_mini.z.optional(zod_mini.z.boolean()),
|
|
@@ -31803,6 +33327,7 @@ function buildClaudecodeSkillFrontmatter({ rulesyncFrontmatter, resolvedDisableM
|
|
|
31803
33327
|
shell: section.shell
|
|
31804
33328
|
};
|
|
31805
33329
|
const definedFields = {
|
|
33330
|
+
background: section.background,
|
|
31806
33331
|
arguments: section.arguments,
|
|
31807
33332
|
hooks: section.hooks,
|
|
31808
33333
|
"disable-model-invocation": resolvedDisableModelInvocation,
|
|
@@ -31880,6 +33405,7 @@ var ClaudecodeSkill = class extends ToolSkill {
|
|
|
31880
33405
|
...frontmatter.arguments !== void 0 && { arguments: frontmatter.arguments },
|
|
31881
33406
|
...frontmatter.context && { context: frontmatter.context },
|
|
31882
33407
|
...frontmatter.agent && { agent: frontmatter.agent },
|
|
33408
|
+
...frontmatter.background !== void 0 && { background: frontmatter.background },
|
|
31883
33409
|
...frontmatter.hooks !== void 0 && { hooks: frontmatter.hooks },
|
|
31884
33410
|
...frontmatter.shell && { shell: frontmatter.shell },
|
|
31885
33411
|
...frontmatter["disable-model-invocation"] !== void 0 && { "disable-model-invocation": frontmatter["disable-model-invocation"] },
|
|
@@ -32489,7 +34015,9 @@ const CopilotcliSkillFrontmatterSchema = zod_mini.z.looseObject({
|
|
|
32489
34015
|
description: zod_mini.z.string(),
|
|
32490
34016
|
license: zod_mini.z.optional(zod_mini.z.string()),
|
|
32491
34017
|
"allowed-tools": zod_mini.z.optional(zod_mini.z.union([zod_mini.z.string(), zod_mini.z.array(zod_mini.z.string())])),
|
|
32492
|
-
"argument-hint": zod_mini.z.optional(zod_mini.z.string())
|
|
34018
|
+
"argument-hint": zod_mini.z.optional(zod_mini.z.string()),
|
|
34019
|
+
"user-invocable": zod_mini.z.optional(zod_mini.z.boolean()),
|
|
34020
|
+
"disable-model-invocation": zod_mini.z.optional(zod_mini.z.boolean())
|
|
32493
34021
|
});
|
|
32494
34022
|
/**
|
|
32495
34023
|
* Represents a GitHub Copilot CLI skill directory.
|
|
@@ -32548,7 +34076,9 @@ var CopilotcliSkill = class CopilotcliSkill extends ToolSkill {
|
|
|
32548
34076
|
const copilotcliSection = {
|
|
32549
34077
|
...frontmatter.license !== void 0 && { license: frontmatter.license },
|
|
32550
34078
|
...frontmatter["allowed-tools"] !== void 0 && { "allowed-tools": frontmatter["allowed-tools"] },
|
|
32551
|
-
...frontmatter["argument-hint"] !== void 0 && { "argument-hint": frontmatter["argument-hint"] }
|
|
34079
|
+
...frontmatter["argument-hint"] !== void 0 && { "argument-hint": frontmatter["argument-hint"] },
|
|
34080
|
+
...frontmatter["user-invocable"] !== void 0 && { "user-invocable": frontmatter["user-invocable"] },
|
|
34081
|
+
...frontmatter["disable-model-invocation"] !== void 0 && { "disable-model-invocation": frontmatter["disable-model-invocation"] }
|
|
32552
34082
|
};
|
|
32553
34083
|
const rulesyncFrontmatter = {
|
|
32554
34084
|
name: frontmatter.name,
|
|
@@ -32575,7 +34105,9 @@ var CopilotcliSkill = class CopilotcliSkill extends ToolSkill {
|
|
|
32575
34105
|
description: rulesyncFrontmatter.description,
|
|
32576
34106
|
...rulesyncFrontmatter.copilotcli?.license !== void 0 && { license: rulesyncFrontmatter.copilotcli.license },
|
|
32577
34107
|
...rulesyncFrontmatter.copilotcli?.["allowed-tools"] !== void 0 && { "allowed-tools": rulesyncFrontmatter.copilotcli["allowed-tools"] },
|
|
32578
|
-
...rulesyncFrontmatter.copilotcli?.["argument-hint"] !== void 0 && { "argument-hint": rulesyncFrontmatter.copilotcli["argument-hint"] }
|
|
34108
|
+
...rulesyncFrontmatter.copilotcli?.["argument-hint"] !== void 0 && { "argument-hint": rulesyncFrontmatter.copilotcli["argument-hint"] },
|
|
34109
|
+
...rulesyncFrontmatter.copilotcli?.["user-invocable"] !== void 0 && { "user-invocable": rulesyncFrontmatter.copilotcli["user-invocable"] },
|
|
34110
|
+
...rulesyncFrontmatter.copilotcli?.["disable-model-invocation"] !== void 0 && { "disable-model-invocation": rulesyncFrontmatter.copilotcli["disable-model-invocation"] }
|
|
32579
34111
|
};
|
|
32580
34112
|
return new CopilotcliSkill({
|
|
32581
34113
|
outputRoot,
|
|
@@ -34245,6 +35777,37 @@ var OpenCodeSkill = class OpenCodeSkill extends ToolSkill {
|
|
|
34245
35777
|
alternativeSkillRoots: [global ? OPENCODE_GLOBAL_SKILL_DIR_PATH : OPENCODE_SKILL_DIR_PATH]
|
|
34246
35778
|
};
|
|
34247
35779
|
}
|
|
35780
|
+
/**
|
|
35781
|
+
* Extra skill roots the project configured in `opencode.json` /
|
|
35782
|
+
* `opencode.jsonc` via `skills.paths` ("Additional paths to skill folders").
|
|
35783
|
+
* Without these, skills a project keeps outside `.opencode/skills/` are
|
|
35784
|
+
* invisible to `rulesync import` even though OpenCode loads them.
|
|
35785
|
+
*
|
|
35786
|
+
* Import-only: rulesync keeps writing to its own managed root, so a
|
|
35787
|
+
* configured path is read but never generated into. `skills.urls` is a
|
|
35788
|
+
* remote-fetch surface and is out of scope for a file-based generator.
|
|
35789
|
+
*
|
|
35790
|
+
* Absolute paths and paths escaping the output root are dropped — an import
|
|
35791
|
+
* root is joined onto `outputRoot`, and reaching outside it is not something
|
|
35792
|
+
* a project config should be able to ask for.
|
|
35793
|
+
*
|
|
35794
|
+
* @see https://opencode.ai/config.json
|
|
35795
|
+
*/
|
|
35796
|
+
static async getConfiguredImportRoots({ outputRoot, global = false }) {
|
|
35797
|
+
const skills = asOpencodeEntries((await readOpencodeConfig({
|
|
35798
|
+
outputRoot,
|
|
35799
|
+
global
|
|
35800
|
+
})).skills);
|
|
35801
|
+
if (skills === null || !Array.isArray(skills.paths)) return [];
|
|
35802
|
+
const configDir = getOpencodeConfigDir({
|
|
35803
|
+
outputRoot,
|
|
35804
|
+
global
|
|
35805
|
+
});
|
|
35806
|
+
return skills.paths.filter((candidate) => typeof candidate === "string" && candidate !== "" && !(0, node_path.isAbsolute)(candidate) && !(0, node_path.normalize)(candidate).startsWith("..")).map((relativeDirPath) => ({
|
|
35807
|
+
outputRoot: configDir,
|
|
35808
|
+
relativeDirPath
|
|
35809
|
+
}));
|
|
35810
|
+
}
|
|
34248
35811
|
getFrontmatter() {
|
|
34249
35812
|
return OpenCodeSkillFrontmatterSchema.parse(this.requireMainFileFrontmatter());
|
|
34250
35813
|
}
|
|
@@ -36178,12 +37741,19 @@ var SkillsProcessor = class extends DirFeatureProcessor {
|
|
|
36178
37741
|
*/
|
|
36179
37742
|
async loadToolDirs() {
|
|
36180
37743
|
const factory = this.getFactory(this.toolTarget);
|
|
36181
|
-
const
|
|
37744
|
+
const paths = factory.class.getSettablePaths({ global: this.global });
|
|
37745
|
+
const configuredRoots = factory.class.getConfiguredImportRoots ? await factory.class.getConfiguredImportRoots({
|
|
37746
|
+
outputRoot: this.outputRoot,
|
|
37747
|
+
global: this.global
|
|
37748
|
+
}) : [];
|
|
37749
|
+
const configuredRootPaths = new Set(configuredRoots.map((root) => root.relativeDirPath));
|
|
37750
|
+
const roots = [...toolSkillImportRoots(paths), ...configuredRoots];
|
|
36182
37751
|
const seenSkillNames = /* @__PURE__ */ new Set();
|
|
36183
37752
|
const toolSkills = [];
|
|
36184
37753
|
for (const root of roots) {
|
|
36185
37754
|
const rootOutputRoot = typeof root === "string" ? this.outputRoot : root.outputRoot;
|
|
36186
37755
|
const relativeDirPath = typeof root === "string" ? root : root.relativeDirPath;
|
|
37756
|
+
const isConfiguredRoot = configuredRootPaths.has(relativeDirPath);
|
|
36187
37757
|
const skillsDirPath = (0, node_path.join)(rootOutputRoot, relativeDirPath);
|
|
36188
37758
|
if (!await directoryExists(skillsDirPath)) continue;
|
|
36189
37759
|
const dirPaths = await findFilesByGlobs((0, node_path.join)(skillsDirPath, "*"), { type: "dir" });
|
|
@@ -36198,12 +37768,20 @@ var SkillsProcessor = class extends DirFeatureProcessor {
|
|
|
36198
37768
|
})) continue;
|
|
36199
37769
|
ownedDirNames.push(dirName);
|
|
36200
37770
|
}
|
|
36201
|
-
const directorySkills = await Promise.all(ownedDirNames.map((dirName) =>
|
|
36202
|
-
|
|
36203
|
-
|
|
36204
|
-
|
|
36205
|
-
|
|
36206
|
-
|
|
37771
|
+
const directorySkills = (await Promise.all(ownedDirNames.map(async (dirName) => {
|
|
37772
|
+
try {
|
|
37773
|
+
return await factory.class.fromDir({
|
|
37774
|
+
outputRoot: rootOutputRoot,
|
|
37775
|
+
relativeDirPath,
|
|
37776
|
+
dirName,
|
|
37777
|
+
global: this.global
|
|
37778
|
+
});
|
|
37779
|
+
} catch (error) {
|
|
37780
|
+
if (!isConfiguredRoot) throw error;
|
|
37781
|
+
this.logger.warn(`Skipping ${(0, node_path.join)(relativeDirPath, dirName)}: ${formatError(error)}`);
|
|
37782
|
+
return null;
|
|
37783
|
+
}
|
|
37784
|
+
}))).filter((skill) => skill !== null);
|
|
36207
37785
|
for (const skill of directorySkills) {
|
|
36208
37786
|
const skillName = skill.getImportIdentity();
|
|
36209
37787
|
if (seenSkillNames.has(skillName)) continue;
|
|
@@ -36728,6 +38306,242 @@ var RovodevSubagent = class RovodevSubagent extends ToolSubagent {
|
|
|
36728
38306
|
}
|
|
36729
38307
|
};
|
|
36730
38308
|
//#endregion
|
|
38309
|
+
//#region src/features/subagents/antigravity-shared-subagent.ts
|
|
38310
|
+
/**
|
|
38311
|
+
* Frontmatter of an Antigravity custom agent (Markdown format, CLI v1.1.6+).
|
|
38312
|
+
*
|
|
38313
|
+
* `name` and `description` are required upstream; the rest are optional and
|
|
38314
|
+
* documented with defaults (`tools: []`, `mainAgent: true`, `subagent: true`,
|
|
38315
|
+
* `model: inherit`, `commandExecutionPolicy: sandbox`, `mcpServers: []`,
|
|
38316
|
+
* `skills`/`plugins`: `[]`). `hidden` and `inheritMcp` appear in the v1.1.6
|
|
38317
|
+
* release notes but not in the documented frontmatter table, so they are
|
|
38318
|
+
* accepted as verbatim passthrough without any behavior modeled around them.
|
|
38319
|
+
* `looseObject` keeps unknown future fields round-tripping.
|
|
38320
|
+
*
|
|
38321
|
+
* @see https://antigravity.google/docs/subagents
|
|
38322
|
+
*/
|
|
38323
|
+
const AntigravitySubagentFrontmatterSchema = zod_mini.z.looseObject({
|
|
38324
|
+
name: zod_mini.z.string(),
|
|
38325
|
+
description: zod_mini.z.string().check(zod_mini.z.minLength(1)),
|
|
38326
|
+
tools: zod_mini.z.optional(zod_mini.z.array(zod_mini.z.string())),
|
|
38327
|
+
mainAgent: zod_mini.z.optional(zod_mini.z.boolean()),
|
|
38328
|
+
subagent: zod_mini.z.optional(zod_mini.z.boolean()),
|
|
38329
|
+
model: zod_mini.z.optional(zod_mini.z.string()),
|
|
38330
|
+
commandExecutionPolicy: zod_mini.z.optional(zod_mini.z.string()),
|
|
38331
|
+
mcpServers: zod_mini.z.optional(zod_mini.z.array(zod_mini.z.unknown())),
|
|
38332
|
+
skills: zod_mini.z.optional(zod_mini.z.array(zod_mini.z.string())),
|
|
38333
|
+
plugins: zod_mini.z.optional(zod_mini.z.array(zod_mini.z.string())),
|
|
38334
|
+
hidden: zod_mini.z.optional(zod_mini.z.boolean()),
|
|
38335
|
+
inheritMcp: zod_mini.z.optional(zod_mini.z.boolean())
|
|
38336
|
+
});
|
|
38337
|
+
/**
|
|
38338
|
+
* Shared custom-agent (subagent) implementation for Google Antigravity 2.0,
|
|
38339
|
+
* used by the IDE, the CLI and plugin bundles.
|
|
38340
|
+
*
|
|
38341
|
+
* Antigravity discovers agents at `.agents/agents/<name>.md` (project) and
|
|
38342
|
+
* `~/.gemini/config/agents/<name>.md` (global, shared by the IDE and the CLI
|
|
38343
|
+
* exactly like `~/.gemini/config/hooks.json`). The directory form
|
|
38344
|
+
* (`<name>/agent.md`) is an equivalent alternative upstream; rulesync emits and
|
|
38345
|
+
* imports the flat file form. The body after the frontmatter is the agent's
|
|
38346
|
+
* system prompt.
|
|
38347
|
+
*
|
|
38348
|
+
* Concrete subclasses only supply the rulesync target name they answer to via
|
|
38349
|
+
* {@link AntigravitySharedSubagent.getToolTarget} and, where the shared file is
|
|
38350
|
+
* not involved, the sections they read via
|
|
38351
|
+
* {@link AntigravitySharedSubagent.getReadSectionKeys}.
|
|
38352
|
+
*
|
|
38353
|
+
* @see https://antigravity.google/docs/subagents
|
|
38354
|
+
*/
|
|
38355
|
+
var AntigravitySharedSubagent = class extends ToolSubagent {
|
|
38356
|
+
frontmatter;
|
|
38357
|
+
body;
|
|
38358
|
+
constructor({ frontmatter, body, fileContent, ...rest }) {
|
|
38359
|
+
if (rest.validate !== false) {
|
|
38360
|
+
const result = AntigravitySubagentFrontmatterSchema.safeParse(frontmatter);
|
|
38361
|
+
if (!result.success) throw new Error(`Invalid frontmatter in ${(0, node_path.join)(rest.relativeDirPath, rest.relativeFilePath)}: ${formatError(result.error)}`);
|
|
38362
|
+
}
|
|
38363
|
+
super({
|
|
38364
|
+
...rest,
|
|
38365
|
+
fileContent: fileContent ?? stringifyFrontmatter(body, frontmatter, { avoidBlockScalars: true })
|
|
38366
|
+
});
|
|
38367
|
+
this.frontmatter = frontmatter;
|
|
38368
|
+
this.body = body;
|
|
38369
|
+
}
|
|
38370
|
+
/** The rulesync target name this subagent answers to. */
|
|
38371
|
+
static getToolTarget() {
|
|
38372
|
+
throw new Error("Please implement this method in the subclass.");
|
|
38373
|
+
}
|
|
38374
|
+
/**
|
|
38375
|
+
* Tool-specific sections this target reads, in increasing precedence order.
|
|
38376
|
+
*
|
|
38377
|
+
* `antigravity-ide` and `antigravity-cli` write the very same file, so a
|
|
38378
|
+
* target that read only its own section would silently drop the other's keys
|
|
38379
|
+
* — and which one survived would depend on `--targets` order. Every target
|
|
38380
|
+
* therefore merges the shared `antigravity-ide` → `antigravity-cli` sections
|
|
38381
|
+
* (the CLI block wins, matching the fixed order the MCP feature already uses
|
|
38382
|
+
* for the same shared-output reason), and the plugin target layers its own
|
|
38383
|
+
* section on top of that. Only `getToolTarget()` decides which section an
|
|
38384
|
+
* import writes back into.
|
|
38385
|
+
*/
|
|
38386
|
+
static getReadSectionKeys() {
|
|
38387
|
+
return ["antigravity-ide", "antigravity-cli"];
|
|
38388
|
+
}
|
|
38389
|
+
static getSettablePaths({ global = false } = {}) {
|
|
38390
|
+
return { relativeDirPath: global ? ANTIGRAVITY_GLOBAL_AGENTS_DIR_PATH : ANTIGRAVITY_AGENTS_DIR_PATH };
|
|
38391
|
+
}
|
|
38392
|
+
getFrontmatter() {
|
|
38393
|
+
return this.frontmatter;
|
|
38394
|
+
}
|
|
38395
|
+
getBody() {
|
|
38396
|
+
return this.body;
|
|
38397
|
+
}
|
|
38398
|
+
toRulesyncSubagent() {
|
|
38399
|
+
const { name, description, ...restFields } = this.frontmatter;
|
|
38400
|
+
return new RulesyncSubagent({
|
|
38401
|
+
outputRoot: ".",
|
|
38402
|
+
frontmatter: {
|
|
38403
|
+
targets: ["*"],
|
|
38404
|
+
name,
|
|
38405
|
+
description,
|
|
38406
|
+
...Object.keys(restFields).length > 0 && { [this.constructor.getToolTarget()]: restFields }
|
|
38407
|
+
},
|
|
38408
|
+
body: this.body,
|
|
38409
|
+
relativeDirPath: RULESYNC_SUBAGENTS_RELATIVE_DIR_PATH,
|
|
38410
|
+
relativeFilePath: this.getRelativeFilePath(),
|
|
38411
|
+
validate: true
|
|
38412
|
+
});
|
|
38413
|
+
}
|
|
38414
|
+
static fromRulesyncSubagent({ outputRoot = process.cwd(), rulesyncSubagent, validate = true, global = false }) {
|
|
38415
|
+
const rulesyncFrontmatter = rulesyncSubagent.getFrontmatter();
|
|
38416
|
+
const mergedSection = Object.assign({}, ...this.getReadSectionKeys().map((key) => rulesyncFrontmatter[key] ?? {}));
|
|
38417
|
+
const toolSection = this.filterToolSpecificSection(mergedSection, ["name", "description"]);
|
|
38418
|
+
const rawFrontmatter = {
|
|
38419
|
+
name: rulesyncFrontmatter.name,
|
|
38420
|
+
description: rulesyncFrontmatter.description || `${rulesyncFrontmatter.name} subagent`,
|
|
38421
|
+
...toolSection
|
|
38422
|
+
};
|
|
38423
|
+
const result = AntigravitySubagentFrontmatterSchema.safeParse(rawFrontmatter);
|
|
38424
|
+
if (!result.success) throw new Error(`Invalid ${this.getToolTarget()} subagent frontmatter in ${rulesyncSubagent.getRelativeFilePath()}: ${formatError(result.error)}`);
|
|
38425
|
+
const frontmatter = result.data;
|
|
38426
|
+
const body = rulesyncSubagent.getBody();
|
|
38427
|
+
const paths = this.getSettablePaths({ global });
|
|
38428
|
+
return new this({
|
|
38429
|
+
outputRoot,
|
|
38430
|
+
frontmatter,
|
|
38431
|
+
body,
|
|
38432
|
+
relativeDirPath: paths.relativeDirPath,
|
|
38433
|
+
relativeFilePath: rulesyncSubagent.getRelativeFilePath(),
|
|
38434
|
+
fileContent: stringifyFrontmatter(body, frontmatter, { avoidBlockScalars: true }),
|
|
38435
|
+
validate,
|
|
38436
|
+
global
|
|
38437
|
+
});
|
|
38438
|
+
}
|
|
38439
|
+
validate() {
|
|
38440
|
+
if (!this.frontmatter) return {
|
|
38441
|
+
success: true,
|
|
38442
|
+
error: null
|
|
38443
|
+
};
|
|
38444
|
+
const result = AntigravitySubagentFrontmatterSchema.safeParse(this.frontmatter);
|
|
38445
|
+
if (result.success) return {
|
|
38446
|
+
success: true,
|
|
38447
|
+
error: null
|
|
38448
|
+
};
|
|
38449
|
+
return {
|
|
38450
|
+
success: false,
|
|
38451
|
+
error: /* @__PURE__ */ new Error(`Invalid frontmatter in ${(0, node_path.join)(this.relativeDirPath, this.relativeFilePath)}: ${formatError(result.error)}`)
|
|
38452
|
+
};
|
|
38453
|
+
}
|
|
38454
|
+
static isTargetedByRulesyncSubagent(rulesyncSubagent) {
|
|
38455
|
+
return this.isTargetedByRulesyncSubagentDefault({
|
|
38456
|
+
rulesyncSubagent,
|
|
38457
|
+
toolTarget: this.getToolTarget()
|
|
38458
|
+
});
|
|
38459
|
+
}
|
|
38460
|
+
static async fromFile({ outputRoot = process.cwd(), relativeFilePath, validate = true, global = false }) {
|
|
38461
|
+
const paths = this.getSettablePaths({ global });
|
|
38462
|
+
const filePath = (0, node_path.join)(outputRoot, paths.relativeDirPath, relativeFilePath);
|
|
38463
|
+
const fileContent = await readFileContent(filePath);
|
|
38464
|
+
const { frontmatter, body: content } = parseFrontmatter(fileContent, filePath);
|
|
38465
|
+
const result = AntigravitySubagentFrontmatterSchema.safeParse(frontmatter);
|
|
38466
|
+
if (!result.success) throw new Error(`Invalid frontmatter in ${filePath}: ${formatError(result.error)}`);
|
|
38467
|
+
return new this({
|
|
38468
|
+
outputRoot,
|
|
38469
|
+
relativeDirPath: paths.relativeDirPath,
|
|
38470
|
+
relativeFilePath,
|
|
38471
|
+
frontmatter: result.data,
|
|
38472
|
+
body: content.trim(),
|
|
38473
|
+
fileContent,
|
|
38474
|
+
validate,
|
|
38475
|
+
global
|
|
38476
|
+
});
|
|
38477
|
+
}
|
|
38478
|
+
static forDeletion({ outputRoot = process.cwd(), relativeDirPath, relativeFilePath, global = false }) {
|
|
38479
|
+
return new this({
|
|
38480
|
+
outputRoot,
|
|
38481
|
+
relativeDirPath,
|
|
38482
|
+
relativeFilePath,
|
|
38483
|
+
frontmatter: {
|
|
38484
|
+
name: "",
|
|
38485
|
+
description: ""
|
|
38486
|
+
},
|
|
38487
|
+
body: "",
|
|
38488
|
+
fileContent: "",
|
|
38489
|
+
validate: false,
|
|
38490
|
+
global
|
|
38491
|
+
});
|
|
38492
|
+
}
|
|
38493
|
+
};
|
|
38494
|
+
//#endregion
|
|
38495
|
+
//#region src/features/subagents/antigravity-cli-subagent.ts
|
|
38496
|
+
/**
|
|
38497
|
+
* Google Antigravity CLI custom agent (subagent), shipped in CLI v1.1.6.
|
|
38498
|
+
*
|
|
38499
|
+
* Shares all behavior with {@link AntigravitySharedSubagent}; the CLI reads the
|
|
38500
|
+
* same `.agents/agents/` and `~/.gemini/config/agents/` roots as the IDE and
|
|
38501
|
+
* exposes them through the `agy agents` subcommand and the `--agent` flag. It
|
|
38502
|
+
* answers to the `antigravity-cli` target.
|
|
38503
|
+
*/
|
|
38504
|
+
var AntigravityCliSubagent = class extends AntigravitySharedSubagent {
|
|
38505
|
+
static getToolTarget() {
|
|
38506
|
+
return "antigravity-cli";
|
|
38507
|
+
}
|
|
38508
|
+
};
|
|
38509
|
+
//#endregion
|
|
38510
|
+
//#region src/features/subagents/antigravity-ide-subagent.ts
|
|
38511
|
+
/**
|
|
38512
|
+
* Google Antigravity IDE custom agent (subagent).
|
|
38513
|
+
*
|
|
38514
|
+
* Shares all behavior with {@link AntigravitySharedSubagent} — the subagents
|
|
38515
|
+
* documentation is product-wide and lists the same `.agents/agents/` and
|
|
38516
|
+
* `~/.gemini/config/agents/` roots for the IDE and the CLI. It answers to the
|
|
38517
|
+
* `antigravity-ide` target.
|
|
38518
|
+
*/
|
|
38519
|
+
var AntigravityIdeSubagent = class extends AntigravitySharedSubagent {
|
|
38520
|
+
static getToolTarget() {
|
|
38521
|
+
return "antigravity-ide";
|
|
38522
|
+
}
|
|
38523
|
+
};
|
|
38524
|
+
//#endregion
|
|
38525
|
+
//#region src/features/subagents/antigravity-plugin-subagent.ts
|
|
38526
|
+
/**
|
|
38527
|
+
* Custom agent inside an Antigravity plugin bundle
|
|
38528
|
+
* (`<plugin_name>/agents/<name>.md`). Plugin bundles are project-scope output
|
|
38529
|
+
* only; they are staged into `~/.gemini/antigravity-cli/plugins/` by the user.
|
|
38530
|
+
*
|
|
38531
|
+
* @see https://antigravity.google/docs/cli/plugins
|
|
38532
|
+
*/
|
|
38533
|
+
var AntigravityPluginSubagent = class extends AntigravitySharedSubagent {
|
|
38534
|
+
static getToolTarget() {
|
|
38535
|
+
return "antigravity-plugin";
|
|
38536
|
+
}
|
|
38537
|
+
static getReadSectionKeys() {
|
|
38538
|
+
return [...super.getReadSectionKeys(), "antigravity-plugin"];
|
|
38539
|
+
}
|
|
38540
|
+
static getSettablePaths() {
|
|
38541
|
+
return { relativeDirPath: ANTIGRAVITY_PLUGIN_AGENTS_DIR };
|
|
38542
|
+
}
|
|
38543
|
+
};
|
|
38544
|
+
//#endregion
|
|
36731
38545
|
//#region src/features/subagents/augmentcode-subagent.ts
|
|
36732
38546
|
const AugmentcodeSubagentFrontmatterSchema = zod_mini.z.looseObject({
|
|
36733
38547
|
name: zod_mini.z.string(),
|
|
@@ -38541,7 +40355,7 @@ def register(ctx):
|
|
|
38541
40355
|
_register_subagent(ctx, subagent)
|
|
38542
40356
|
`;
|
|
38543
40357
|
}
|
|
38544
|
-
function getEnabledPluginConfigContent(currentContent) {
|
|
40358
|
+
function getEnabledPluginConfigContent({ currentContent, global }) {
|
|
38545
40359
|
const config = parseSharedConfig({
|
|
38546
40360
|
format: "yaml",
|
|
38547
40361
|
fileContent: currentContent
|
|
@@ -38549,7 +40363,7 @@ function getEnabledPluginConfigContent(currentContent) {
|
|
|
38549
40363
|
const plugins = config.plugins && typeof config.plugins === "object" ? config.plugins : {};
|
|
38550
40364
|
const enabled = Array.isArray(plugins.enabled) ? plugins.enabled : [];
|
|
38551
40365
|
return applySharedConfigPatch({
|
|
38552
|
-
fileKey:
|
|
40366
|
+
fileKey: getHermesagentConfigSharedFileKey({ global }),
|
|
38553
40367
|
feature: "subagents",
|
|
38554
40368
|
existingContent: currentContent,
|
|
38555
40369
|
patch: { plugins: {
|
|
@@ -38600,6 +40414,10 @@ var HermesagentSubagent = class HermesagentSubagent extends ToolSubagent {
|
|
|
38600
40414
|
return !targets || targets.includes("*") || targets.includes("hermesagent");
|
|
38601
40415
|
}
|
|
38602
40416
|
static fromRulesyncSubagents({ rulesyncSubagents, outputRoot, global = false }) {
|
|
40417
|
+
const pluginDirPath = getHermesagentRelativeDirPath({
|
|
40418
|
+
global,
|
|
40419
|
+
relativeDirPath: HERMESAGENT_RULESYNC_SUBAGENTS_PLUGIN_DIR_PATH
|
|
40420
|
+
});
|
|
38603
40421
|
return [
|
|
38604
40422
|
...rulesyncSubagents.map((rulesyncSubagent) => HermesagentSubagent.fromRulesyncSubagent({
|
|
38605
40423
|
relativeDirPath: this.getSettablePaths({ global }).relativeDirPath,
|
|
@@ -38608,20 +40426,14 @@ var HermesagentSubagent = class HermesagentSubagent extends ToolSubagent {
|
|
|
38608
40426
|
global
|
|
38609
40427
|
})),
|
|
38610
40428
|
new HermesagentSubagent({
|
|
38611
|
-
relativeDirPath:
|
|
38612
|
-
global,
|
|
38613
|
-
relativeDirPath: HERMESAGENT_RULESYNC_SUBAGENTS_PLUGIN_DIR_PATH
|
|
38614
|
-
}),
|
|
40429
|
+
relativeDirPath: pluginDirPath,
|
|
38615
40430
|
relativeFilePath: (0, node_path.basename)(HERMESAGENT_RULESYNC_SUBAGENTS_PLUGIN_MANIFEST_PATH),
|
|
38616
40431
|
fileContent: "",
|
|
38617
40432
|
outputRoot,
|
|
38618
40433
|
global
|
|
38619
40434
|
}),
|
|
38620
40435
|
new HermesagentSubagent({
|
|
38621
|
-
relativeDirPath:
|
|
38622
|
-
global,
|
|
38623
|
-
relativeDirPath: HERMESAGENT_RULESYNC_SUBAGENTS_PLUGIN_DIR_PATH
|
|
38624
|
-
}),
|
|
40436
|
+
relativeDirPath: pluginDirPath,
|
|
38625
40437
|
relativeFilePath: (0, node_path.basename)(HERMESAGENT_RULESYNC_SUBAGENTS_PLUGIN_INIT_PATH),
|
|
38626
40438
|
fileContent: "",
|
|
38627
40439
|
outputRoot,
|
|
@@ -38661,14 +40473,12 @@ var HermesagentSubagent = class HermesagentSubagent extends ToolSubagent {
|
|
|
38661
40473
|
* shared `~/.hermes/config.yaml` (enabling the `rulesync-subagents` plugin),
|
|
38662
40474
|
* so the write must be declared for the shared-file order derivation.
|
|
38663
40475
|
*/
|
|
38664
|
-
|
|
38665
|
-
|
|
38666
|
-
|
|
38667
|
-
|
|
38668
|
-
|
|
38669
|
-
|
|
38670
|
-
relativeFilePath: (0, node_path.basename)(HERMESAGENT_CONFIG_FILE_PATH)
|
|
38671
|
-
}] : [];
|
|
40476
|
+
/**
|
|
40477
|
+
* `config.yaml` under every spelling the global profile root can take.
|
|
40478
|
+
* @see getHermesagentSharedConfigWritePaths
|
|
40479
|
+
*/
|
|
40480
|
+
static getExtraSharedWritePaths() {
|
|
40481
|
+
return getHermesagentSharedConfigWritePaths();
|
|
38672
40482
|
}
|
|
38673
40483
|
static getSettablePathsForRulesyncSubagent(rulesyncSubagent) {
|
|
38674
40484
|
return [(0, node_path.join)(HERMESAGENT_RULESYNC_SUBAGENTS_DIR_PATH, `${subagentSlug(rulesyncSubagent.getRelativePathFromCwd())}.json`)];
|
|
@@ -38701,7 +40511,10 @@ var HermesagentSubagent = class HermesagentSubagent extends ToolSubagent {
|
|
|
38701
40511
|
}
|
|
38702
40512
|
setFileContent(newFileContent) {
|
|
38703
40513
|
if (this.getRelativeFilePath() === (0, node_path.basename)(HERMESAGENT_CONFIG_FILE_PATH)) {
|
|
38704
|
-
super.setFileContent(getEnabledPluginConfigContent(
|
|
40514
|
+
super.setFileContent(getEnabledPluginConfigContent({
|
|
40515
|
+
currentContent: newFileContent,
|
|
40516
|
+
global: this.global
|
|
40517
|
+
}));
|
|
38705
40518
|
return;
|
|
38706
40519
|
}
|
|
38707
40520
|
super.setFileContent(newFileContent);
|
|
@@ -38709,7 +40522,10 @@ var HermesagentSubagent = class HermesagentSubagent extends ToolSubagent {
|
|
|
38709
40522
|
getFileContent() {
|
|
38710
40523
|
if (this.getRelativeFilePath() === (0, node_path.basename)(HERMESAGENT_RULESYNC_SUBAGENTS_PLUGIN_MANIFEST_PATH)) return getPluginManifestContent();
|
|
38711
40524
|
if (this.getRelativeFilePath() === (0, node_path.basename)(HERMESAGENT_RULESYNC_SUBAGENTS_PLUGIN_INIT_PATH)) return getPluginInitContent();
|
|
38712
|
-
if (this.getRelativeFilePath() === (0, node_path.basename)(HERMESAGENT_CONFIG_FILE_PATH)) return getEnabledPluginConfigContent(
|
|
40525
|
+
if (this.getRelativeFilePath() === (0, node_path.basename)(HERMESAGENT_CONFIG_FILE_PATH)) return getEnabledPluginConfigContent({
|
|
40526
|
+
currentContent: super.getFileContent(),
|
|
40527
|
+
global: this.global
|
|
40528
|
+
});
|
|
38713
40529
|
return super.getFileContent();
|
|
38714
40530
|
}
|
|
38715
40531
|
};
|
|
@@ -40305,6 +42121,30 @@ const toolSubagentFactories = /* @__PURE__ */ new Map([
|
|
|
40305
42121
|
filePattern: "*.md"
|
|
40306
42122
|
}
|
|
40307
42123
|
}],
|
|
42124
|
+
["antigravity-cli", {
|
|
42125
|
+
class: AntigravityCliSubagent,
|
|
42126
|
+
meta: {
|
|
42127
|
+
supportsSimulated: false,
|
|
42128
|
+
supportsGlobal: true,
|
|
42129
|
+
filePattern: "*.md"
|
|
42130
|
+
}
|
|
42131
|
+
}],
|
|
42132
|
+
["antigravity-ide", {
|
|
42133
|
+
class: AntigravityIdeSubagent,
|
|
42134
|
+
meta: {
|
|
42135
|
+
supportsSimulated: false,
|
|
42136
|
+
supportsGlobal: true,
|
|
42137
|
+
filePattern: "*.md"
|
|
42138
|
+
}
|
|
42139
|
+
}],
|
|
42140
|
+
["antigravity-plugin", {
|
|
42141
|
+
class: AntigravityPluginSubagent,
|
|
42142
|
+
meta: {
|
|
42143
|
+
supportsSimulated: false,
|
|
42144
|
+
supportsGlobal: false,
|
|
42145
|
+
filePattern: "*.md"
|
|
42146
|
+
}
|
|
42147
|
+
}],
|
|
40308
42148
|
["augmentcode", {
|
|
40309
42149
|
class: AugmentcodeSubagent,
|
|
40310
42150
|
meta: {
|
|
@@ -42518,6 +44358,7 @@ var CodexcliRule = class CodexcliRule extends ToolRule {
|
|
|
42518
44358
|
const CopilotRuleFrontmatterSchema = zod_mini.z.object({
|
|
42519
44359
|
description: zod_mini.z.optional(zod_mini.z.string()),
|
|
42520
44360
|
applyTo: zod_mini.z.optional(zod_mini.z.string()),
|
|
44361
|
+
name: zod_mini.z.optional(zod_mini.z.string()),
|
|
42521
44362
|
excludeAgent: zod_mini.z.optional(zod_mini.z.union([
|
|
42522
44363
|
zod_mini.z.literal("code-review"),
|
|
42523
44364
|
zod_mini.z.literal("cloud-agent"),
|
|
@@ -42575,7 +44416,10 @@ var CopilotRule = class CopilotRule extends ToolRule {
|
|
|
42575
44416
|
root: this.isRoot(),
|
|
42576
44417
|
description: this.frontmatter.description,
|
|
42577
44418
|
globs,
|
|
42578
|
-
...this.frontmatter.excludeAgent && { copilot: {
|
|
44419
|
+
...(this.frontmatter.excludeAgent || this.frontmatter.name) && { copilot: {
|
|
44420
|
+
...this.frontmatter.excludeAgent && { excludeAgent: this.frontmatter.excludeAgent },
|
|
44421
|
+
...this.frontmatter.name && { name: this.frontmatter.name }
|
|
44422
|
+
} }
|
|
42579
44423
|
};
|
|
42580
44424
|
const relativeFilePath = this.getRelativeFilePath().replace(/\.instructions\.md$/, ".md");
|
|
42581
44425
|
return new RulesyncRule({
|
|
@@ -42594,7 +44438,8 @@ var CopilotRule = class CopilotRule extends ToolRule {
|
|
|
42594
44438
|
const copilotFrontmatter = {
|
|
42595
44439
|
description: rulesyncFrontmatter.description,
|
|
42596
44440
|
applyTo: rulesyncFrontmatter.globs?.length ? rulesyncFrontmatter.globs.join(",") : void 0,
|
|
42597
|
-
excludeAgent: rulesyncFrontmatter.copilot?.excludeAgent
|
|
44441
|
+
excludeAgent: rulesyncFrontmatter.copilot?.excludeAgent,
|
|
44442
|
+
name: rulesyncFrontmatter.copilot?.name
|
|
42598
44443
|
};
|
|
42599
44444
|
const body = rulesyncRule.getBody();
|
|
42600
44445
|
if (root) return new CopilotRule({
|
|
@@ -43759,10 +45604,13 @@ var JunieRule = class JunieRule extends ToolRule {
|
|
|
43759
45604
|
//#region src/features/rules/kilo-rule.ts
|
|
43760
45605
|
var KiloRule = class KiloRule extends ToolRule {
|
|
43761
45606
|
static getSettablePaths({ global, excludeToolDir } = {}) {
|
|
43762
|
-
if (global) return {
|
|
43763
|
-
|
|
43764
|
-
|
|
43765
|
-
|
|
45607
|
+
if (global) return {
|
|
45608
|
+
root: {
|
|
45609
|
+
relativeDirPath: buildToolPath(KILO_GLOBAL_DIR, ".", excludeToolDir),
|
|
45610
|
+
relativeFilePath: KILO_RULE_FILE_NAME
|
|
45611
|
+
},
|
|
45612
|
+
nonRoot: { relativeDirPath: buildToolPath(KILO_DIR, KILO_RULES_DIR_NAME, excludeToolDir) }
|
|
45613
|
+
};
|
|
43766
45614
|
return {
|
|
43767
45615
|
root: {
|
|
43768
45616
|
relativeDirPath: ".",
|
|
@@ -45347,6 +47195,7 @@ const toolRuleFactories = /* @__PURE__ */ new Map([
|
|
|
45347
47195
|
extension: "md",
|
|
45348
47196
|
supportsGlobal: false,
|
|
45349
47197
|
ruleDiscoveryMode: "toon",
|
|
47198
|
+
collisionPolicy: "compose",
|
|
45350
47199
|
additionalConventions: {
|
|
45351
47200
|
commands: { commandClass: AgentsmdCommand },
|
|
45352
47201
|
subagents: { subagentClass: AgentsmdSubagent },
|
|
@@ -45367,7 +47216,8 @@ const toolRuleFactories = /* @__PURE__ */ new Map([
|
|
|
45367
47216
|
meta: {
|
|
45368
47217
|
extension: "md",
|
|
45369
47218
|
supportsGlobal: true,
|
|
45370
|
-
ruleDiscoveryMode: "toon"
|
|
47219
|
+
ruleDiscoveryMode: "toon",
|
|
47220
|
+
collisionPolicy: "compose"
|
|
45371
47221
|
}
|
|
45372
47222
|
}],
|
|
45373
47223
|
["antigravity-cli", {
|
|
@@ -45444,7 +47294,7 @@ const toolRuleFactories = /* @__PURE__ */ new Map([
|
|
|
45444
47294
|
extension: "md",
|
|
45445
47295
|
supportsGlobal: true,
|
|
45446
47296
|
ruleDiscoveryMode: "auto",
|
|
45447
|
-
|
|
47297
|
+
collisionPolicy: "fold"
|
|
45448
47298
|
}
|
|
45449
47299
|
}],
|
|
45450
47300
|
["copilot", {
|
|
@@ -45477,7 +47327,7 @@ const toolRuleFactories = /* @__PURE__ */ new Map([
|
|
|
45477
47327
|
extension: "md",
|
|
45478
47328
|
supportsGlobal: true,
|
|
45479
47329
|
ruleDiscoveryMode: "auto",
|
|
45480
|
-
|
|
47330
|
+
collisionPolicy: "fold"
|
|
45481
47331
|
}
|
|
45482
47332
|
}],
|
|
45483
47333
|
["factorydroid", {
|
|
@@ -45485,7 +47335,8 @@ const toolRuleFactories = /* @__PURE__ */ new Map([
|
|
|
45485
47335
|
meta: {
|
|
45486
47336
|
extension: "md",
|
|
45487
47337
|
supportsGlobal: true,
|
|
45488
|
-
ruleDiscoveryMode: "toon"
|
|
47338
|
+
ruleDiscoveryMode: "toon",
|
|
47339
|
+
collisionPolicy: "compose"
|
|
45489
47340
|
}
|
|
45490
47341
|
}],
|
|
45491
47342
|
["goose", {
|
|
@@ -45494,7 +47345,7 @@ const toolRuleFactories = /* @__PURE__ */ new Map([
|
|
|
45494
47345
|
extension: "md",
|
|
45495
47346
|
supportsGlobal: true,
|
|
45496
47347
|
ruleDiscoveryMode: "auto",
|
|
45497
|
-
|
|
47348
|
+
collisionPolicy: "fold"
|
|
45498
47349
|
}
|
|
45499
47350
|
}],
|
|
45500
47351
|
["hermesagent", {
|
|
@@ -45503,7 +47354,7 @@ const toolRuleFactories = /* @__PURE__ */ new Map([
|
|
|
45503
47354
|
extension: "md",
|
|
45504
47355
|
supportsGlobal: false,
|
|
45505
47356
|
ruleDiscoveryMode: "auto",
|
|
45506
|
-
|
|
47357
|
+
collisionPolicy: "fold"
|
|
45507
47358
|
}
|
|
45508
47359
|
}],
|
|
45509
47360
|
["grokcli", {
|
|
@@ -45520,7 +47371,7 @@ const toolRuleFactories = /* @__PURE__ */ new Map([
|
|
|
45520
47371
|
extension: "md",
|
|
45521
47372
|
supportsGlobal: true,
|
|
45522
47373
|
ruleDiscoveryMode: "auto",
|
|
45523
|
-
|
|
47374
|
+
collisionPolicy: "fold"
|
|
45524
47375
|
}
|
|
45525
47376
|
}],
|
|
45526
47377
|
["kilo", {
|
|
@@ -45529,7 +47380,8 @@ const toolRuleFactories = /* @__PURE__ */ new Map([
|
|
|
45529
47380
|
extension: "md",
|
|
45530
47381
|
supportsGlobal: true,
|
|
45531
47382
|
ruleDiscoveryMode: "auto",
|
|
45532
|
-
mcpInstructionsRegistrar: KiloMcp
|
|
47383
|
+
mcpInstructionsRegistrar: KiloMcp,
|
|
47384
|
+
collisionPolicy: "compose"
|
|
45533
47385
|
}
|
|
45534
47386
|
}],
|
|
45535
47387
|
["kimi-code", {
|
|
@@ -45538,7 +47390,7 @@ const toolRuleFactories = /* @__PURE__ */ new Map([
|
|
|
45538
47390
|
extension: "md",
|
|
45539
47391
|
supportsGlobal: true,
|
|
45540
47392
|
ruleDiscoveryMode: "auto",
|
|
45541
|
-
|
|
47393
|
+
collisionPolicy: "fold"
|
|
45542
47394
|
}
|
|
45543
47395
|
}],
|
|
45544
47396
|
["kiro", {
|
|
@@ -45571,7 +47423,8 @@ const toolRuleFactories = /* @__PURE__ */ new Map([
|
|
|
45571
47423
|
extension: "md",
|
|
45572
47424
|
supportsGlobal: true,
|
|
45573
47425
|
ruleDiscoveryMode: "toon",
|
|
45574
|
-
mcpInstructionsRegistrar: OpencodeMcp
|
|
47426
|
+
mcpInstructionsRegistrar: OpencodeMcp,
|
|
47427
|
+
collisionPolicy: "compose"
|
|
45575
47428
|
}
|
|
45576
47429
|
}],
|
|
45577
47430
|
["pi", {
|
|
@@ -45580,7 +47433,7 @@ const toolRuleFactories = /* @__PURE__ */ new Map([
|
|
|
45580
47433
|
extension: "md",
|
|
45581
47434
|
supportsGlobal: true,
|
|
45582
47435
|
ruleDiscoveryMode: "auto",
|
|
45583
|
-
|
|
47436
|
+
collisionPolicy: "fold"
|
|
45584
47437
|
}
|
|
45585
47438
|
}],
|
|
45586
47439
|
["qwencode", {
|
|
@@ -45598,7 +47451,7 @@ const toolRuleFactories = /* @__PURE__ */ new Map([
|
|
|
45598
47451
|
extension: "md",
|
|
45599
47452
|
supportsGlobal: true,
|
|
45600
47453
|
ruleDiscoveryMode: "auto",
|
|
45601
|
-
|
|
47454
|
+
collisionPolicy: "fold"
|
|
45602
47455
|
}
|
|
45603
47456
|
}],
|
|
45604
47457
|
["replit", {
|
|
@@ -45655,7 +47508,7 @@ const toolRuleFactories = /* @__PURE__ */ new Map([
|
|
|
45655
47508
|
extension: "md",
|
|
45656
47509
|
supportsGlobal: false,
|
|
45657
47510
|
ruleDiscoveryMode: "toon",
|
|
45658
|
-
|
|
47511
|
+
collisionPolicy: "fold"
|
|
45659
47512
|
}
|
|
45660
47513
|
}],
|
|
45661
47514
|
["devin", {
|
|
@@ -45724,16 +47577,23 @@ var RulesProcessor = class extends FeatureProcessor {
|
|
|
45724
47577
|
const nonLocalRootRules = rulesyncRules.filter((rule) => !rule.getFrontmatter().localRoot);
|
|
45725
47578
|
const factory = this.getFactory(this.toolTarget);
|
|
45726
47579
|
const { meta } = factory;
|
|
45727
|
-
const
|
|
47580
|
+
const convertedRules = nonLocalRootRules.map((rulesyncRule) => {
|
|
45728
47581
|
if (!factory.class.isTargetedByRulesyncRule(rulesyncRule)) return null;
|
|
45729
|
-
return
|
|
45730
|
-
|
|
45731
|
-
|
|
45732
|
-
|
|
45733
|
-
|
|
45734
|
-
|
|
47582
|
+
return {
|
|
47583
|
+
toolRule: factory.class.fromRulesyncRule({
|
|
47584
|
+
outputRoot: this.outputRoot,
|
|
47585
|
+
rulesyncRule,
|
|
47586
|
+
validate: true,
|
|
47587
|
+
global: this.global
|
|
47588
|
+
}),
|
|
47589
|
+
rulesyncRule
|
|
47590
|
+
};
|
|
45735
47591
|
}).filter((rule) => rule !== null);
|
|
45736
|
-
|
|
47592
|
+
this.mergeRulesByOutputPath({
|
|
47593
|
+
convertedRules,
|
|
47594
|
+
collisionPolicy: meta.collisionPolicy ?? "preserve"
|
|
47595
|
+
});
|
|
47596
|
+
const toolRules = convertedRules.map(({ toolRule }) => toolRule);
|
|
45737
47597
|
this.applyLocalRootRules({
|
|
45738
47598
|
toolRules,
|
|
45739
47599
|
localRootRules,
|
|
@@ -45751,7 +47611,12 @@ var RulesProcessor = class extends FeatureProcessor {
|
|
|
45751
47611
|
toolRules,
|
|
45752
47612
|
factory
|
|
45753
47613
|
});
|
|
45754
|
-
|
|
47614
|
+
const outputFiles = [...toolRules, ...extraFiles];
|
|
47615
|
+
this.warnForOutputPathCollisions({
|
|
47616
|
+
outputFiles,
|
|
47617
|
+
convertedRules
|
|
47618
|
+
});
|
|
47619
|
+
return outputFiles;
|
|
45755
47620
|
}
|
|
45756
47621
|
/**
|
|
45757
47622
|
* Handle localRoot rules (only in non-global mode and when enabled). Mutates
|
|
@@ -45838,39 +47703,80 @@ var RulesProcessor = class extends FeatureProcessor {
|
|
|
45838
47703
|
});
|
|
45839
47704
|
}
|
|
45840
47705
|
/**
|
|
45841
|
-
*
|
|
47706
|
+
* Reconcile rules that resolve to the same output path.
|
|
45842
47707
|
*
|
|
45843
|
-
*
|
|
45844
|
-
*
|
|
45845
|
-
*
|
|
45846
|
-
* `.
|
|
45847
|
-
*
|
|
45848
|
-
*
|
|
47708
|
+
* Multiple root fragments are composed for tools that emit a fixed root file.
|
|
47709
|
+
* The `fold` policy is for tools whose rules engine reads only one root file and
|
|
47710
|
+
* neither scans a modular rules directory nor follows references. For example,
|
|
47711
|
+
* dcode reads `.deepagents/AGENTS.md`, while Warp reads root or subdirectory
|
|
47712
|
+
* `AGENTS.md` files but never `.warp/memories/`. Those adapters must fold every
|
|
47713
|
+
* body into one instance because last-writer-wins would silently drop content.
|
|
47714
|
+
* Plain-Markdown adapters can opt into `compose` for colliding modular outputs.
|
|
45849
47715
|
*
|
|
45850
|
-
*
|
|
45851
|
-
*
|
|
45852
|
-
*
|
|
47716
|
+
* A generated root rule becomes the merge target when present. A `fold` group
|
|
47717
|
+
* without one uses its first rule. A group only composes when every rendered
|
|
47718
|
+
* fragment is plain Markdown — a fragment carrying its own frontmatter block
|
|
47719
|
+
* (e.g. Amp's `globs:` gate) would end up mid-body where the tool ignores it.
|
|
47720
|
+
* Root-involved collisions that cannot be composed safely fail; other
|
|
47721
|
+
* collisions remain separate and are reported by the final output-path check.
|
|
47722
|
+
* Mutates `convertedRules` in place.
|
|
45853
47723
|
*/
|
|
45854
|
-
|
|
45855
|
-
if (
|
|
47724
|
+
mergeRulesByOutputPath({ convertedRules, collisionPolicy }) {
|
|
47725
|
+
if (convertedRules.length <= 1) return;
|
|
45856
47726
|
const groups = /* @__PURE__ */ new Map();
|
|
45857
|
-
for (const
|
|
45858
|
-
const path = (0, node_path.join)(
|
|
47727
|
+
for (const conversion of convertedRules) {
|
|
47728
|
+
const path = (0, node_path.join)(conversion.toolRule.getRelativeDirPath(), conversion.toolRule.getRelativeFilePath());
|
|
45859
47729
|
const group = groups.get(path);
|
|
45860
|
-
if (group) group.push(
|
|
45861
|
-
else groups.set(path, [
|
|
47730
|
+
if (group) group.push(conversion);
|
|
47731
|
+
else groups.set(path, [conversion]);
|
|
45862
47732
|
}
|
|
45863
47733
|
const survivors = /* @__PURE__ */ new Set();
|
|
45864
|
-
for (const group of groups
|
|
45865
|
-
|
|
47734
|
+
for (const [path, group] of groups) {
|
|
47735
|
+
if (group.length === 1) {
|
|
47736
|
+
const conversion = group[0];
|
|
47737
|
+
if (conversion) {
|
|
47738
|
+
if (collisionPolicy === "fold") conversion.toolRule.setFileContent(conversion.toolRule.getFileContent().trim());
|
|
47739
|
+
survivors.add(conversion);
|
|
47740
|
+
}
|
|
47741
|
+
continue;
|
|
47742
|
+
}
|
|
47743
|
+
const rootConversion = group.find(({ toolRule }) => toolRule.isRoot());
|
|
47744
|
+
const allGeneratedRulesAreRoots = group.every(({ toolRule }) => toolRule.isRoot());
|
|
47745
|
+
const hasSourceRoot = group.some(({ rulesyncRule }) => rulesyncRule.getFrontmatter().root === true);
|
|
47746
|
+
const allFragmentsArePlain = group.every(({ toolRule }) => !/^---\r?\n/.test(toolRule.getFileContent()));
|
|
47747
|
+
const shouldCompose = (collisionPolicy === "fold" || collisionPolicy === "compose" || allGeneratedRulesAreRoots) && allFragmentsArePlain;
|
|
47748
|
+
if (!shouldCompose && hasSourceRoot) throw new Error(`Multiple generated rules resolve to output path '${path}' for target '${this.toolTarget}', but this target cannot safely compose a collision involving a root rule. Source rules: ${formatRulePaths(group.map(({ rulesyncRule }) => rulesyncRule))}`);
|
|
47749
|
+
if (!shouldCompose) {
|
|
47750
|
+
for (const conversion of group) survivors.add(conversion);
|
|
47751
|
+
continue;
|
|
47752
|
+
}
|
|
47753
|
+
const target = rootConversion ?? group[0];
|
|
45866
47754
|
if (!target) continue;
|
|
45867
|
-
const mergedContent = [target, ...group.filter((rule) => rule !== target)].map((
|
|
45868
|
-
target.setFileContent(mergedContent);
|
|
47755
|
+
const mergedContent = [target, ...group.filter((rule) => rule !== target)].map(({ toolRule }) => toolRule.getFileContent().trim()).filter((content) => content.length > 0).join("\n\n");
|
|
47756
|
+
target.toolRule.setFileContent(mergedContent);
|
|
45869
47757
|
survivors.add(target);
|
|
45870
47758
|
}
|
|
45871
|
-
for (let i =
|
|
45872
|
-
const
|
|
45873
|
-
if (
|
|
47759
|
+
for (let i = convertedRules.length - 1; i >= 0; i--) {
|
|
47760
|
+
const conversion = convertedRules[i];
|
|
47761
|
+
if (conversion && !survivors.has(conversion)) convertedRules.splice(i, 1);
|
|
47762
|
+
}
|
|
47763
|
+
}
|
|
47764
|
+
warnForOutputPathCollisions({ outputFiles, convertedRules }) {
|
|
47765
|
+
const seen = /* @__PURE__ */ new Map();
|
|
47766
|
+
const describeSource = (file) => {
|
|
47767
|
+
const source = convertedRules.find(({ toolRule }) => toolRule === file)?.rulesyncRule;
|
|
47768
|
+
return source ? formatRulePaths([source]) : (0, node_path.join)(file.getRelativeDirPath(), file.getRelativeFilePath());
|
|
47769
|
+
};
|
|
47770
|
+
for (const file of outputFiles) {
|
|
47771
|
+
const path = (0, node_path.join)(file.getRelativeDirPath(), file.getRelativeFilePath());
|
|
47772
|
+
const key = path.toLowerCase();
|
|
47773
|
+
const previous = seen.get(key);
|
|
47774
|
+
if (previous) {
|
|
47775
|
+
const previousPath = (0, node_path.join)(previous.getRelativeDirPath(), previous.getRelativeFilePath());
|
|
47776
|
+
const pathDescription = previousPath === path ? `'${path}'` : `'${previousPath}' and '${path}' (compared case-insensitively, as on macOS and Windows)`;
|
|
47777
|
+
this.logger.warn(`Both ${describeSource(previous)} and ${describeSource(file)} generate to ${pathDescription}; the last one wins wherever they collide.`);
|
|
47778
|
+
}
|
|
47779
|
+
seen.set(key, file);
|
|
45874
47780
|
}
|
|
45875
47781
|
}
|
|
45876
47782
|
/**
|
|
@@ -46030,14 +47936,13 @@ As this project's AI coding tool, you must follow the additional conventions bel
|
|
|
46030
47936
|
}));
|
|
46031
47937
|
const factory = this.getFactory(this.toolTarget);
|
|
46032
47938
|
const targetedRootRules = rulesyncRules.filter((rule) => rule.getFrontmatter().root).filter((rule) => factory.class.isTargetedByRulesyncRule(rule));
|
|
46033
|
-
if (targetedRootRules.length > 1) throw new Error(`Multiple root rulesync rules found for target '${this.toolTarget}': ${formatRulePaths(targetedRootRules)}`);
|
|
46034
47939
|
if (targetedRootRules.length === 0 && rulesyncRules.length > 0) this.logger.warn(`No root rulesync rule file found for target '${this.toolTarget}'. Consider adding 'root: true' to one of your rule files in ${RULESYNC_RULES_RELATIVE_DIR_PATH}.`);
|
|
46035
47940
|
const targetedLocalRootRules = rulesyncRules.filter((rule) => rule.getFrontmatter().localRoot).filter((rule) => factory.class.isTargetedByRulesyncRule(rule));
|
|
46036
47941
|
if (targetedLocalRootRules.length > 1) throw new Error(`Multiple localRoot rules found for target '${this.toolTarget}': ${formatRulePaths(targetedLocalRootRules)}. Only one rule can have localRoot: true`);
|
|
46037
47942
|
if (targetedLocalRootRules.length > 0 && targetedRootRules.length === 0) throw new Error(`localRoot: true requires a root: true rule to exist for target '${this.toolTarget}' (found in ${formatRulePaths(targetedLocalRootRules)})`);
|
|
46038
47943
|
if (this.global) {
|
|
46039
47944
|
const globalPaths = factory.class.getSettablePaths({ global: true });
|
|
46040
|
-
const supportsGlobalNonRoot = "nonRoot" in globalPaths && globalPaths.nonRoot !== null || factory.meta.supportsGlobal && factory.meta.
|
|
47945
|
+
const supportsGlobalNonRoot = "nonRoot" in globalPaths && globalPaths.nonRoot !== null || factory.meta.supportsGlobal && factory.meta.collisionPolicy === "fold";
|
|
46041
47946
|
const nonRootRules = rulesyncRules.filter((rule) => !rule.getFrontmatter().root && !rule.getFrontmatter().localRoot && factory.class.isTargetedByRulesyncRule(rule));
|
|
46042
47947
|
if (nonRootRules.length > 0 && !supportsGlobalNonRoot) this.logger.warn(`${nonRootRules.length} non-root rulesync rules found, but it's in global mode, so ignoring them: ${formatRulePaths(nonRootRules)}`);
|
|
46043
47948
|
if (targetedLocalRootRules.length > 0) this.logger.warn(`${targetedLocalRootRules.length} localRoot rules found, but localRoot is not supported in global mode, ignoring them: ${formatRulePaths(targetedLocalRootRules)}`);
|
|
@@ -46280,14 +48185,36 @@ async function assertPluginRootSafe(params) {
|
|
|
46280
48185
|
}
|
|
46281
48186
|
//#endregion
|
|
46282
48187
|
//#region src/utils/tool-output-root.ts
|
|
48188
|
+
/** The environment variable each tool reads for its profile root. */
|
|
48189
|
+
const TOOL_HOME_ENV_VARS = {
|
|
48190
|
+
hermesagent: "HERMES_HOME",
|
|
48191
|
+
"kimi-code": "KIMI_CODE_HOME"
|
|
48192
|
+
};
|
|
48193
|
+
/**
|
|
48194
|
+
* Substitute a tool's home override (`HERMES_HOME`, `KIMI_CODE_HOME`) for the
|
|
48195
|
+
* output root in global scope.
|
|
48196
|
+
*
|
|
48197
|
+
* The override wins over `--output-roots`: it names where the tool itself reads
|
|
48198
|
+
* its profile, so writing anywhere else would produce files the tool ignores.
|
|
48199
|
+
*
|
|
48200
|
+
* A substituted value goes through the same `validateOutputRoot` the CLI and
|
|
48201
|
+
* config paths use, so an override of `/` or an unnormalized path is rejected
|
|
48202
|
+
* instead of silently becoming the output root. The rejection is re-thrown
|
|
48203
|
+
* naming the variable, since the user never passed an `--output-roots` flag.
|
|
48204
|
+
*/
|
|
46283
48205
|
function resolveToolOutputRoot({ outputRoot, toolTarget, global }) {
|
|
46284
48206
|
if (!global) return outputRoot;
|
|
46285
|
-
|
|
48207
|
+
const resolved = toolTarget === "hermesagent" ? resolveHermesagentOutputRoot({
|
|
46286
48208
|
outputRoot,
|
|
46287
48209
|
global
|
|
46288
|
-
});
|
|
46289
|
-
if (
|
|
46290
|
-
|
|
48210
|
+
}) : toolTarget === "kimi-code" ? getKimiCodeHome() ?? outputRoot : outputRoot;
|
|
48211
|
+
if (resolved === outputRoot) return resolved;
|
|
48212
|
+
try {
|
|
48213
|
+
validateOutputRoot(resolved);
|
|
48214
|
+
} catch (error) {
|
|
48215
|
+
throw new Error(`${TOOL_HOME_ENV_VARS[toolTarget] ?? "The tool home override"} is not a usable output root: ${formatError(error)}`, { cause: error });
|
|
48216
|
+
}
|
|
48217
|
+
return resolved;
|
|
46291
48218
|
}
|
|
46292
48219
|
//#endregion
|
|
46293
48220
|
//#region src/lib/convert.ts
|
|
@@ -47688,7 +49615,11 @@ async function generateChecksCore(params) {
|
|
|
47688
49615
|
for (const toolTarget of toolTargets) for (const outputRoot of config.getOutputRoots(toolTarget)) {
|
|
47689
49616
|
if (!config.getFeatures(toolTarget).includes("checks")) continue;
|
|
47690
49617
|
const processor = new ChecksProcessor({
|
|
47691
|
-
outputRoot
|
|
49618
|
+
outputRoot: resolveToolOutputRoot({
|
|
49619
|
+
outputRoot,
|
|
49620
|
+
toolTarget,
|
|
49621
|
+
global: config.getGlobal()
|
|
49622
|
+
}),
|
|
47692
49623
|
inputRoot: config.getInputRoot(),
|
|
47693
49624
|
toolTarget,
|
|
47694
49625
|
global: config.getGlobal(),
|