rulesync 16.0.0 → 16.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -4
- package/dist/cli/index.cjs +2 -2
- package/dist/cli/index.js +2 -2
- package/dist/cli/index.js.map +1 -1
- package/dist/{import-BT5KR_K-.js → import-BmXT7mRS.js} +1165 -172
- package/dist/import-BmXT7mRS.js.map +1 -0
- package/dist/{import-Dyo6JaHg.cjs → import-eG7fZPXI.cjs} +1178 -185
- 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$277 } = 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$277(RULESYNC_RELATIVE_DIR_PATH, "rules");
|
|
135
|
+
const RULESYNC_CURATED_RULES_RELATIVE_DIR_PATH = join$277(RULESYNC_RULES_RELATIVE_DIR_PATH, ".curated");
|
|
136
|
+
const RULESYNC_COMMANDS_RELATIVE_DIR_PATH = join$277(RULESYNC_RELATIVE_DIR_PATH, "commands");
|
|
137
|
+
const RULESYNC_SUBAGENTS_RELATIVE_DIR_PATH = join$277(RULESYNC_RELATIVE_DIR_PATH, "subagents");
|
|
138
|
+
const RULESYNC_CHECKS_RELATIVE_DIR_PATH = join$277(RULESYNC_RELATIVE_DIR_PATH, "checks");
|
|
139
|
+
const RULESYNC_MCP_RELATIVE_FILE_PATH = join$277(RULESYNC_RELATIVE_DIR_PATH, "mcp.jsonc");
|
|
140
|
+
const RULESYNC_HOOKS_RELATIVE_FILE_PATH = join$277(RULESYNC_RELATIVE_DIR_PATH, "hooks.jsonc");
|
|
141
|
+
const RULESYNC_PERMISSIONS_RELATIVE_FILE_PATH = join$277(RULESYNC_RELATIVE_DIR_PATH, "permissions.jsonc");
|
|
142
|
+
join$277(RULESYNC_RELATIVE_DIR_PATH, "mcp.json");
|
|
143
|
+
const RULESYNC_HOOKS_LEGACY_RELATIVE_FILE_PATH = join$277(RULESYNC_RELATIVE_DIR_PATH, "hooks.json");
|
|
144
|
+
const RULESYNC_PERMISSIONS_LEGACY_RELATIVE_FILE_PATH = join$277(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$277(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$277(RULESYNC_RELATIVE_DIR_PATH, "skills");
|
|
150
|
+
const RULESYNC_CURATED_SKILLS_RELATIVE_DIR_PATH = join$277(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";
|
|
@@ -293,6 +293,9 @@ const commandsProcessorToolTargetTuple = [
|
|
|
293
293
|
const subagentsProcessorToolTargetTuple = [
|
|
294
294
|
"kilo",
|
|
295
295
|
"agentsmd",
|
|
296
|
+
"antigravity-cli",
|
|
297
|
+
"antigravity-ide",
|
|
298
|
+
"antigravity-plugin",
|
|
296
299
|
"augmentcode",
|
|
297
300
|
"claudecode",
|
|
298
301
|
"claudecode-plugin",
|
|
@@ -424,6 +427,7 @@ const permissionsProcessorToolTargetTuple = [
|
|
|
424
427
|
];
|
|
425
428
|
const checksProcessorToolTargetTuple = [
|
|
426
429
|
"amp",
|
|
430
|
+
"cursor",
|
|
427
431
|
"hermesagent",
|
|
428
432
|
"takt"
|
|
429
433
|
];
|
|
@@ -2134,7 +2138,10 @@ const HookDefinitionSchema = zod_mini.z.looseObject({
|
|
|
2134
2138
|
model: zod_mini.z.optional(safeString),
|
|
2135
2139
|
args: zod_mini.z.optional(zod_mini.z.array(safeString)),
|
|
2136
2140
|
metadata: zod_mini.z.optional(zod_mini.z.looseObject({})),
|
|
2137
|
-
if: zod_mini.z.optional(safeString)
|
|
2141
|
+
if: zod_mini.z.optional(safeString),
|
|
2142
|
+
commandWindows: zod_mini.z.optional(safeString),
|
|
2143
|
+
asyncRewake: zod_mini.z.optional(zod_mini.z.boolean()),
|
|
2144
|
+
continueOnBlock: zod_mini.z.optional(zod_mini.z.boolean())
|
|
2138
2145
|
});
|
|
2139
2146
|
/**
|
|
2140
2147
|
* All canonical hook event names.
|
|
@@ -2188,6 +2195,7 @@ const HOOK_EVENTS = [
|
|
|
2188
2195
|
"configChange",
|
|
2189
2196
|
"cwdChanged",
|
|
2190
2197
|
"fileChanged",
|
|
2198
|
+
"directoryAdded",
|
|
2191
2199
|
"elicitation",
|
|
2192
2200
|
"elicitationResult"
|
|
2193
2201
|
];
|
|
@@ -2249,6 +2257,7 @@ const CLAUDE_HOOK_EVENTS = [
|
|
|
2249
2257
|
"configChange",
|
|
2250
2258
|
"cwdChanged",
|
|
2251
2259
|
"fileChanged",
|
|
2260
|
+
"directoryAdded",
|
|
2252
2261
|
"postCompact",
|
|
2253
2262
|
"elicitation",
|
|
2254
2263
|
"elicitationResult"
|
|
@@ -2281,7 +2290,15 @@ const DEVIN_HOOK_EVENTS = [
|
|
|
2281
2290
|
"permissionRequest",
|
|
2282
2291
|
"postCompact"
|
|
2283
2292
|
];
|
|
2284
|
-
/**
|
|
2293
|
+
/**
|
|
2294
|
+
* Hook events supported by OpenCode.
|
|
2295
|
+
*
|
|
2296
|
+
* `preCompact` maps to `experimental.session.compacting`, which the plugin docs
|
|
2297
|
+
* document as a named `(input, output)` hook rather than an `event.type`
|
|
2298
|
+
* dispatch; the other entries are all generic events.
|
|
2299
|
+
*
|
|
2300
|
+
* @see https://opencode.ai/docs/plugins/
|
|
2301
|
+
*/
|
|
2285
2302
|
const OPENCODE_HOOK_EVENTS = [
|
|
2286
2303
|
"sessionStart",
|
|
2287
2304
|
"preToolUse",
|
|
@@ -2289,9 +2306,19 @@ const OPENCODE_HOOK_EVENTS = [
|
|
|
2289
2306
|
"stop",
|
|
2290
2307
|
"afterFileEdit",
|
|
2291
2308
|
"afterShellExecution",
|
|
2292
|
-
"permissionRequest"
|
|
2309
|
+
"permissionRequest",
|
|
2310
|
+
"preCompact",
|
|
2311
|
+
"postCompact",
|
|
2312
|
+
"afterError",
|
|
2313
|
+
"fileChanged"
|
|
2293
2314
|
];
|
|
2294
|
-
/**
|
|
2315
|
+
/**
|
|
2316
|
+
* Hook events supported by Kilo. Identical to OpenCode: Kilo's plugin docs list
|
|
2317
|
+
* the same event surface, including `session.compacted`, `session.error`,
|
|
2318
|
+
* `file.watcher.updated` and the experimental compaction hook.
|
|
2319
|
+
*
|
|
2320
|
+
* @see https://kilo.ai/docs/automate/extending/plugins
|
|
2321
|
+
*/
|
|
2295
2322
|
const KILO_HOOK_EVENTS = OPENCODE_HOOK_EVENTS;
|
|
2296
2323
|
/**
|
|
2297
2324
|
* Hook events supported by Pi Coding Agent, bridged through a generated
|
|
@@ -2358,7 +2385,8 @@ const COPILOT_HOOK_EVENTS = [
|
|
|
2358
2385
|
* `sessionStart`, `sessionEnd`, `userPromptSubmitted`, `preToolUse`,
|
|
2359
2386
|
* `postToolUse`, `postToolUseFailure`, `agentStop`, `subagentStart`,
|
|
2360
2387
|
* `subagentStop`, `errorOccurred`, `preCompact`, `permissionRequest`,
|
|
2361
|
-
* `notification`, `
|
|
2388
|
+
* `notification`, `userPromptTransformed` ← `userPromptExpansion`,
|
|
2389
|
+
* `preMcpToolCall` ← `beforeMCPExecution`.
|
|
2362
2390
|
*
|
|
2363
2391
|
* `preMcpToolCall` (canonical `beforeMCPExecution`) was added in Copilot CLI
|
|
2364
2392
|
* v1.0.51 (2026-05-20) for hook providers to control outgoing MCP request
|
|
@@ -2380,6 +2408,7 @@ const COPILOTCLI_HOOK_EVENTS = [
|
|
|
2380
2408
|
"preCompact",
|
|
2381
2409
|
"permissionRequest",
|
|
2382
2410
|
"notification",
|
|
2411
|
+
"userPromptExpansion",
|
|
2383
2412
|
"beforeMCPExecution"
|
|
2384
2413
|
];
|
|
2385
2414
|
/**
|
|
@@ -2424,6 +2453,7 @@ const DEEPAGENTS_HOOK_EVENTS = [
|
|
|
2424
2453
|
/** Hook events supported by Codex CLI. */
|
|
2425
2454
|
const CODEXCLI_HOOK_EVENTS = [
|
|
2426
2455
|
"sessionStart",
|
|
2456
|
+
"sessionEnd",
|
|
2427
2457
|
"preToolUse",
|
|
2428
2458
|
"postToolUse",
|
|
2429
2459
|
"beforeSubmitPrompt",
|
|
@@ -2858,6 +2888,7 @@ const CANONICAL_TO_CLAUDE_EVENT_NAMES = {
|
|
|
2858
2888
|
configChange: "ConfigChange",
|
|
2859
2889
|
cwdChanged: "CwdChanged",
|
|
2860
2890
|
fileChanged: "FileChanged",
|
|
2891
|
+
directoryAdded: "DirectoryAdded",
|
|
2861
2892
|
postCompact: "PostCompact",
|
|
2862
2893
|
elicitation: "Elicitation",
|
|
2863
2894
|
elicitationResult: "ElicitationResult"
|
|
@@ -2979,7 +3010,11 @@ const CANONICAL_TO_OPENCODE_EVENT_NAMES = {
|
|
|
2979
3010
|
stop: "session.idle",
|
|
2980
3011
|
afterFileEdit: "file.edited",
|
|
2981
3012
|
afterShellExecution: "command.executed",
|
|
2982
|
-
permissionRequest: "permission.asked"
|
|
3013
|
+
permissionRequest: "permission.asked",
|
|
3014
|
+
preCompact: "experimental.session.compacting",
|
|
3015
|
+
postCompact: "session.compacted",
|
|
3016
|
+
afterError: "session.error",
|
|
3017
|
+
fileChanged: "file.watcher.updated"
|
|
2983
3018
|
};
|
|
2984
3019
|
/**
|
|
2985
3020
|
* Map canonical camelCase event names to Kilo dot-notation.
|
|
@@ -3055,6 +3090,7 @@ const CANONICAL_TO_COPILOTCLI_EVENT_NAMES = {
|
|
|
3055
3090
|
preCompact: "preCompact",
|
|
3056
3091
|
permissionRequest: "permissionRequest",
|
|
3057
3092
|
notification: "notification",
|
|
3093
|
+
userPromptExpansion: "userPromptTransformed",
|
|
3058
3094
|
beforeMCPExecution: "preMcpToolCall"
|
|
3059
3095
|
};
|
|
3060
3096
|
/** Map GitHub Copilot CLI event names back to canonical camelCase. */
|
|
@@ -3064,6 +3100,7 @@ const COPILOTCLI_TO_CANONICAL_EVENT_NAMES = Object.fromEntries(Object.entries(CA
|
|
|
3064
3100
|
*/
|
|
3065
3101
|
const CANONICAL_TO_CODEXCLI_EVENT_NAMES = {
|
|
3066
3102
|
sessionStart: "SessionStart",
|
|
3103
|
+
sessionEnd: "SessionEnd",
|
|
3067
3104
|
preToolUse: "PreToolUse",
|
|
3068
3105
|
postToolUse: "PostToolUse",
|
|
3069
3106
|
beforeSubmitPrompt: "UserPromptSubmit",
|
|
@@ -3486,6 +3523,25 @@ var RulesyncIgnore = class RulesyncIgnore extends RulesyncFile {
|
|
|
3486
3523
|
//#endregion
|
|
3487
3524
|
//#region src/types/mcp.ts
|
|
3488
3525
|
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"));
|
|
3526
|
+
/**
|
|
3527
|
+
* One `envVars` entry. A bare name reads the variable from Codex's own
|
|
3528
|
+
* environment; the object form names the environment to read it from, where
|
|
3529
|
+
* `source = "remote"` reads from the remote executor environment.
|
|
3530
|
+
* @see https://learn.chatgpt.com/docs/extend/mcp
|
|
3531
|
+
*/
|
|
3532
|
+
const EnvVarEntrySchema = zod_mini.z.union([EnvVarNameSchema, zod_mini.z.strictObject({
|
|
3533
|
+
name: EnvVarNameSchema,
|
|
3534
|
+
source: zod_mini.z.optional(zod_mini.z.enum(["local", "remote"]))
|
|
3535
|
+
})]);
|
|
3536
|
+
/**
|
|
3537
|
+
* Whether a value is usable as `envVars`. Applied in both directions by the
|
|
3538
|
+
* codex adapter, so an entry read out of somebody's `config.toml` can never be
|
|
3539
|
+
* imported into a `.rulesync/mcp.jsonc` that the next generate would refuse to
|
|
3540
|
+
* parse.
|
|
3541
|
+
*/
|
|
3542
|
+
function isEnvVarEntryArray(value) {
|
|
3543
|
+
return Array.isArray(value) && value.every((entry) => EnvVarEntrySchema.safeParse(entry).success);
|
|
3544
|
+
}
|
|
3489
3545
|
const McpServerSchema = zod_mini.z.looseObject({
|
|
3490
3546
|
type: zod_mini.z.optional(zod_mini.z.enum([
|
|
3491
3547
|
"local",
|
|
@@ -3500,7 +3556,8 @@ const McpServerSchema = zod_mini.z.looseObject({
|
|
|
3500
3556
|
url: zod_mini.z.optional(zod_mini.z.string()),
|
|
3501
3557
|
httpUrl: zod_mini.z.optional(zod_mini.z.string()),
|
|
3502
3558
|
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(
|
|
3559
|
+
envVars: zod_mini.z.optional(zod_mini.z.array(EnvVarEntrySchema)),
|
|
3560
|
+
experimentalEnvironment: zod_mini.z.optional(zod_mini.z.string()),
|
|
3504
3561
|
disabled: zod_mini.z.optional(zod_mini.z.boolean()),
|
|
3505
3562
|
networkTimeout: zod_mini.z.optional(zod_mini.z.number()),
|
|
3506
3563
|
timeout: zod_mini.z.optional(zod_mini.z.number()),
|
|
@@ -3666,6 +3723,8 @@ var RulesyncMcp = class RulesyncMcp extends RulesyncFile {
|
|
|
3666
3723
|
"description",
|
|
3667
3724
|
"exposed",
|
|
3668
3725
|
"envVars",
|
|
3726
|
+
"experimentalEnvironment",
|
|
3727
|
+
"experimental_environment",
|
|
3669
3728
|
"enabled"
|
|
3670
3729
|
])];
|
|
3671
3730
|
}));
|
|
@@ -3968,12 +4027,23 @@ const KiloPermissionsOverrideSchema = zod_mini.z.looseObject({ permission: zod_m
|
|
|
3968
4027
|
* authored without modeling each one; the managed `allow`/`ask`/`deny` arrays are
|
|
3969
4028
|
* ignored here (rulesync owns them).
|
|
3970
4029
|
*
|
|
4030
|
+
* `sandbox` is the sibling top-level settings subtree that governs the sandbox
|
|
4031
|
+
* Claude Code runs commands in (`sandbox.network.*`, `sandbox.filesystem.*`,
|
|
4032
|
+
* `sandbox.credentials`, `sandbox.allowAppleEvents`, ...). It has no canonical
|
|
4033
|
+
* permission category either — it constrains how a permitted command runs
|
|
4034
|
+
* rather than which commands are permitted — so it is a loose passthrough on
|
|
4035
|
+
* the same terms, merged into the top level of `.claude/settings.json`.
|
|
4036
|
+
*
|
|
3971
4037
|
* @example
|
|
3972
4038
|
* { "permissions": { "defaultMode": "acceptEdits", "additionalDirectories": ["../shared"] } }
|
|
4039
|
+
* @example
|
|
4040
|
+
* { "sandbox": { "network": { "allowedDomains": ["example.com"], "strictAllowlist": true } } }
|
|
4041
|
+
* @see https://code.claude.com/docs/en/sandboxing
|
|
3973
4042
|
*/
|
|
3974
4043
|
const ClaudecodePermissionsOverrideSchema = zod_mini.z.looseObject({
|
|
3975
4044
|
permission: zod_mini.z.optional(ToolScopedPermissionSchema),
|
|
3976
|
-
permissions: zod_mini.z.optional(zod_mini.z.looseObject({}))
|
|
4045
|
+
permissions: zod_mini.z.optional(zod_mini.z.looseObject({})),
|
|
4046
|
+
sandbox: zod_mini.z.optional(zod_mini.z.looseObject({}))
|
|
3977
4047
|
});
|
|
3978
4048
|
/**
|
|
3979
4049
|
* Tool-scoped override block for Mistral Vibe. Vibe's per-tool `BaseToolConfig`
|
|
@@ -4246,19 +4316,24 @@ const AmpPermissionsOverrideSchema = zod_mini.z.looseObject({
|
|
|
4246
4316
|
});
|
|
4247
4317
|
/**
|
|
4248
4318
|
* Tool-scoped override block for the Google Antigravity CLI. Antigravity's CLI
|
|
4249
|
-
* `settings.json` carries
|
|
4319
|
+
* `settings.json` carries four global autonomy/sandbox knobs outside the
|
|
4250
4320
|
* `permissions.allow/ask/deny` arrays rulesync manages: `toolPermission` (the
|
|
4251
4321
|
* global autonomy preset — `request-review` (default) / `proceed-in-sandbox` /
|
|
4252
|
-
* `always-proceed` / `strict`)
|
|
4253
|
-
* agent-run commands to OS containment)
|
|
4322
|
+
* `always-proceed` / `strict`), `enableTerminalSandbox` (a boolean confining
|
|
4323
|
+
* agent-run commands to OS containment), `artifactReviewPolicy` (whether the
|
|
4324
|
+
* agent's artifact changes are gated on a review prompt — `asks-for-review`
|
|
4325
|
+
* (default) / `agent-decides` / `always-proceed`) and `allowNonWorkspaceAccess`
|
|
4326
|
+
* (a boolean, off by default, letting the agent read or write files outside the
|
|
4327
|
+
* active workspace roots). Antigravity applies the allow/deny
|
|
4254
4328
|
* lists as per-rule exceptions to the preset at runtime, so rulesync only
|
|
4255
4329
|
* authors these keys verbatim — no precedence modeling is needed on our side.
|
|
4256
4330
|
* Fields placed here are merged onto the top level of
|
|
4257
4331
|
* `~/.gemini/antigravity-cli/settings.json` (global-only) and emitted only for
|
|
4258
4332
|
* the CLI. The Antigravity IDE exposes the same concepts through a GUI (no
|
|
4259
4333
|
* 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
|
|
4334
|
+
* Verified against https://antigravity.google/docs/cli/reference,
|
|
4335
|
+
* https://antigravity.google/docs/cli/sandbox and
|
|
4336
|
+
* https://antigravity.google/docs/cli/settings.
|
|
4262
4337
|
*
|
|
4263
4338
|
* @example
|
|
4264
4339
|
* { "toolPermission": "strict", "enableTerminalSandbox": true }
|
|
@@ -4271,7 +4346,13 @@ const AntigravityCliPermissionsOverrideSchema = zod_mini.z.looseObject({
|
|
|
4271
4346
|
"always-proceed",
|
|
4272
4347
|
"strict"
|
|
4273
4348
|
])),
|
|
4274
|
-
enableTerminalSandbox: zod_mini.z.optional(zod_mini.z.boolean())
|
|
4349
|
+
enableTerminalSandbox: zod_mini.z.optional(zod_mini.z.boolean()),
|
|
4350
|
+
artifactReviewPolicy: zod_mini.z.optional(zod_mini.z.enum([
|
|
4351
|
+
"asks-for-review",
|
|
4352
|
+
"agent-decides",
|
|
4353
|
+
"always-proceed"
|
|
4354
|
+
])),
|
|
4355
|
+
allowNonWorkspaceAccess: zod_mini.z.optional(zod_mini.z.boolean())
|
|
4275
4356
|
});
|
|
4276
4357
|
/**
|
|
4277
4358
|
* Tool-scoped override block for AugmentCode. AugmentCode's `toolPermissions[]`
|
|
@@ -4675,11 +4756,14 @@ const RulesyncRuleFrontmatterSchema = zod_mini.z.object({
|
|
|
4675
4756
|
description: zod_mini.z.optional(zod_mini.z.string()),
|
|
4676
4757
|
globs: zod_mini.z.optional(zod_mini.z.array(zod_mini.z.string()))
|
|
4677
4758
|
})),
|
|
4678
|
-
copilot: zod_mini.z.optional(zod_mini.z.looseObject({
|
|
4679
|
-
zod_mini.z.
|
|
4680
|
-
|
|
4681
|
-
|
|
4682
|
-
|
|
4759
|
+
copilot: zod_mini.z.optional(zod_mini.z.looseObject({
|
|
4760
|
+
excludeAgent: zod_mini.z.optional(zod_mini.z.union([
|
|
4761
|
+
zod_mini.z.literal("code-review"),
|
|
4762
|
+
zod_mini.z.literal("cloud-agent"),
|
|
4763
|
+
zod_mini.z.literal("coding-agent")
|
|
4764
|
+
])),
|
|
4765
|
+
name: zod_mini.z.optional(zod_mini.z.string())
|
|
4766
|
+
})),
|
|
4683
4767
|
antigravity: zod_mini.z.optional(zod_mini.z.looseObject({
|
|
4684
4768
|
trigger: zod_mini.z.optional(zod_mini.z.string()),
|
|
4685
4769
|
globs: zod_mini.z.optional(zod_mini.z.array(zod_mini.z.string()))
|
|
@@ -4887,6 +4971,7 @@ const RulesyncSkillFrontmatterSchema = zod_mini.z.looseObject({
|
|
|
4887
4971
|
arguments: zod_mini.z.optional(zod_mini.z.union([zod_mini.z.string(), zod_mini.z.array(zod_mini.z.string())])),
|
|
4888
4972
|
context: zod_mini.z.optional(zod_mini.z.string()),
|
|
4889
4973
|
agent: zod_mini.z.optional(zod_mini.z.string()),
|
|
4974
|
+
background: zod_mini.z.optional(zod_mini.z.boolean()),
|
|
4890
4975
|
hooks: zod_mini.z.optional(zod_mini.z.looseObject({})),
|
|
4891
4976
|
shell: zod_mini.z.optional(zod_mini.z.string()),
|
|
4892
4977
|
"disable-model-invocation": zod_mini.z.optional(zod_mini.z.boolean()),
|
|
@@ -4938,7 +5023,9 @@ const RulesyncSkillFrontmatterSchema = zod_mini.z.looseObject({
|
|
|
4938
5023
|
copilotcli: zod_mini.z.optional(zod_mini.z.looseObject({
|
|
4939
5024
|
license: zod_mini.z.optional(zod_mini.z.string()),
|
|
4940
5025
|
"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())
|
|
5026
|
+
"argument-hint": zod_mini.z.optional(zod_mini.z.string()),
|
|
5027
|
+
"user-invocable": zod_mini.z.optional(zod_mini.z.boolean()),
|
|
5028
|
+
"disable-model-invocation": zod_mini.z.optional(zod_mini.z.boolean())
|
|
4942
5029
|
})),
|
|
4943
5030
|
pi: zod_mini.z.optional(zod_mini.z.looseObject({
|
|
4944
5031
|
"allowed-tools": zod_mini.z.optional(zod_mini.z.union([zod_mini.z.string(), zod_mini.z.array(zod_mini.z.string())])),
|
|
@@ -5224,6 +5311,18 @@ function resolveUserInvocable({ rootFrontmatter, section }) {
|
|
|
5224
5311
|
return section?.["user-invocable"] ?? rootFrontmatter["user-invocable"];
|
|
5225
5312
|
}
|
|
5226
5313
|
//#endregion
|
|
5314
|
+
//#region src/constants/cursor-paths.ts
|
|
5315
|
+
const CURSOR_DIR = ".cursor";
|
|
5316
|
+
const CURSOR_COMMANDS_DIR_PATH = (0, node_path.join)(CURSOR_DIR, "commands");
|
|
5317
|
+
const CURSOR_SKILLS_DIR_PATH = (0, node_path.join)(CURSOR_DIR, "skills");
|
|
5318
|
+
const CURSOR_AGENTS_DIR_PATH = (0, node_path.join)(CURSOR_DIR, "agents");
|
|
5319
|
+
const CURSOR_BUGBOT_FILE_NAME = "BUGBOT.md";
|
|
5320
|
+
const CURSOR_MCP_FILE_NAME = "mcp.json";
|
|
5321
|
+
const CURSOR_HOOKS_FILE_NAME = "hooks.json";
|
|
5322
|
+
const CURSOR_IGNORE_FILE_NAME = ".cursorignore";
|
|
5323
|
+
const CURSOR_PERMISSIONS_FILE_NAME = "cli.json";
|
|
5324
|
+
const CURSOR_PERMISSIONS_GLOBAL_FILE_NAME = "cli-config.json";
|
|
5325
|
+
//#endregion
|
|
5227
5326
|
//#region src/constants/takt-paths.ts
|
|
5228
5327
|
const TAKT_DIR = ".takt";
|
|
5229
5328
|
const TAKT_FACETS_SUBDIR = "facets";
|
|
@@ -5674,6 +5773,235 @@ var AmpCheck = class AmpCheck extends ToolCheck {
|
|
|
5674
5773
|
}
|
|
5675
5774
|
};
|
|
5676
5775
|
//#endregion
|
|
5776
|
+
//#region src/features/checks/check-slug.ts
|
|
5777
|
+
/**
|
|
5778
|
+
* Turn a name that came out of a tool's own config file into one safe to use as
|
|
5779
|
+
* a `.rulesync/checks/<name>.md` file name. Shared by the adapters whose checks
|
|
5780
|
+
* collapse into a single file, since the name they read back is whatever the
|
|
5781
|
+
* user wrote there.
|
|
5782
|
+
*/
|
|
5783
|
+
function slugifyCheckName(value) {
|
|
5784
|
+
return value.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 48).replace(/-+$/, "");
|
|
5785
|
+
}
|
|
5786
|
+
//#endregion
|
|
5787
|
+
//#region src/features/checks/cursor-check.ts
|
|
5788
|
+
/**
|
|
5789
|
+
* Marks where one check starts inside the single instruction file. Bugbot reads
|
|
5790
|
+
* the file as prose, and an HTML comment is invisible in rendered Markdown, so
|
|
5791
|
+
* the marker carries the check identity without changing what Bugbot is told.
|
|
5792
|
+
*/
|
|
5793
|
+
const CHECK_MARKER_PATTERN = /^<!--\s*rulesync:check:(.+?)\s*-->[ \t]*$/gm;
|
|
5794
|
+
/**
|
|
5795
|
+
* A marker line a check body wrote itself — a rulesync doc fragment quoted in a
|
|
5796
|
+
* code block, say. Emitting it verbatim would split that check in two on the
|
|
5797
|
+
* next import, so `literal-` is inserted before `check:` on the way out and
|
|
5798
|
+
* taken off on the way back. `(?:literal-)*` makes it a ladder, so a body that
|
|
5799
|
+
* already contains an escaped marker survives the round trip too.
|
|
5800
|
+
*/
|
|
5801
|
+
const ESCAPABLE_MARKER_PATTERN = /^(<!--\s*rulesync:)((?:literal-)*check:.+?\s*-->[ \t]*)$/gm;
|
|
5802
|
+
const ESCAPED_MARKER_PATTERN = /^(<!--\s*rulesync:)literal-((?:literal-)*check:.+?\s*-->[ \t]*)$/gm;
|
|
5803
|
+
const FALLBACK_CHECK_NAME = "bugbot";
|
|
5804
|
+
function renderMarker(name) {
|
|
5805
|
+
return `<!-- rulesync:check:${name} -->`;
|
|
5806
|
+
}
|
|
5807
|
+
function escapeMarkers(content) {
|
|
5808
|
+
return content.replace(ESCAPABLE_MARKER_PATTERN, "$1literal-$2");
|
|
5809
|
+
}
|
|
5810
|
+
function unescapeMarkers(content) {
|
|
5811
|
+
return content.replace(ESCAPED_MARKER_PATTERN, "$1$2");
|
|
5812
|
+
}
|
|
5813
|
+
function findMarkers(fileContent) {
|
|
5814
|
+
CHECK_MARKER_PATTERN.lastIndex = 0;
|
|
5815
|
+
const markers = [];
|
|
5816
|
+
let match = CHECK_MARKER_PATTERN.exec(fileContent);
|
|
5817
|
+
while (match !== null) {
|
|
5818
|
+
markers.push({
|
|
5819
|
+
name: match[1] ?? "",
|
|
5820
|
+
start: match.index,
|
|
5821
|
+
end: match.index + match[0].length
|
|
5822
|
+
});
|
|
5823
|
+
match = CHECK_MARKER_PATTERN.exec(fileContent);
|
|
5824
|
+
}
|
|
5825
|
+
return markers;
|
|
5826
|
+
}
|
|
5827
|
+
/**
|
|
5828
|
+
* The instruction text one check contributes. Bugbot has no field to put a
|
|
5829
|
+
* summary in, so `description` is used only when there is no body — the same
|
|
5830
|
+
* fallback the file-stem heading above it gets.
|
|
5831
|
+
*/
|
|
5832
|
+
function toInstruction(rulesyncCheck) {
|
|
5833
|
+
const body = rulesyncCheck.getBody().trim();
|
|
5834
|
+
if (body.length > 0) return body;
|
|
5835
|
+
return rulesyncCheck.getFrontmatter().description?.trim() ?? "";
|
|
5836
|
+
}
|
|
5837
|
+
function renderSection(rulesyncCheck) {
|
|
5838
|
+
const name = (0, node_path.basename)(rulesyncCheck.getRelativeFilePath(), ".md");
|
|
5839
|
+
const heading = `## ${name}`;
|
|
5840
|
+
const instruction = toInstruction(rulesyncCheck);
|
|
5841
|
+
const lines = [renderMarker(name), heading];
|
|
5842
|
+
if (instruction.length > 0) lines.push("", escapeMarkers(instruction));
|
|
5843
|
+
return lines.join("\n");
|
|
5844
|
+
}
|
|
5845
|
+
/** Drop the heading generate writes, so a round trip does not stack headings. */
|
|
5846
|
+
function stripGeneratedHeading(section, name) {
|
|
5847
|
+
const [firstLine, ...rest] = section.split("\n");
|
|
5848
|
+
if (firstLine?.trim() === `## ${name}`) return rest.join("\n").trim();
|
|
5849
|
+
return section.trim();
|
|
5850
|
+
}
|
|
5851
|
+
/**
|
|
5852
|
+
* Checks adapter for Cursor Bugbot (`.cursor/BUGBOT.md`).
|
|
5853
|
+
*
|
|
5854
|
+
* Bugbot takes one aggregated instruction file per directory rather than a file
|
|
5855
|
+
* per check, so every `.rulesync/checks/*.md` targeting Cursor collapses into
|
|
5856
|
+
* the repository-root `.cursor/BUGBOT.md` — hence {@link fromRulesyncChecks}
|
|
5857
|
+
* rather than the usual per-check conversion. Each check becomes one section:
|
|
5858
|
+
* an HTML-comment marker carrying the check name, an `## <name>` heading, and
|
|
5859
|
+
* the check body as the instruction text (the `description` is used when the
|
|
5860
|
+
* body is empty).
|
|
5861
|
+
*
|
|
5862
|
+
* Bugbot reads the file as free prose, so a check's `severity` and `tools` have
|
|
5863
|
+
* no equivalent there: they are not written and do not come back on import. So
|
|
5864
|
+
* is `description` whenever the check also has a body.
|
|
5865
|
+
*
|
|
5866
|
+
* Project scope only — Bugbot reads repository files, and there is no
|
|
5867
|
+
* user-level instruction file. Bugbot also merges nested `<dir>/.cursor/BUGBOT.md`
|
|
5868
|
+
* files found while traversing upward from changed files, but rulesync check
|
|
5869
|
+
* sources carry no directory-placement semantics, so only the root file is
|
|
5870
|
+
* generated.
|
|
5871
|
+
*
|
|
5872
|
+
* On import the markers split the file back into one check per section; content
|
|
5873
|
+
* before the first marker — and a hand-written file with no markers at all —
|
|
5874
|
+
* becomes a single `bugbot` check, so nothing in the file is dropped. A file
|
|
5875
|
+
* holding anything rulesync did not write is never deleted either (see
|
|
5876
|
+
* {@link canDeleteAuxiliaryFiles}), though generating checks for Cursor does
|
|
5877
|
+
* replace it — import first to keep what is there, which is warned about.
|
|
5878
|
+
*
|
|
5879
|
+
* @see https://cursor.com/docs/bugbot
|
|
5880
|
+
*/
|
|
5881
|
+
var CursorCheck = class CursorCheck extends ToolCheck {
|
|
5882
|
+
static getSettablePaths(_options = {}) {
|
|
5883
|
+
return {
|
|
5884
|
+
relativeDirPath: CURSOR_DIR,
|
|
5885
|
+
relativeFilePath: CURSOR_BUGBOT_FILE_NAME
|
|
5886
|
+
};
|
|
5887
|
+
}
|
|
5888
|
+
static isTargetedByRulesyncCheck(rulesyncCheck) {
|
|
5889
|
+
return this.isTargetedByRulesyncCheckDefault({
|
|
5890
|
+
rulesyncCheck,
|
|
5891
|
+
toolTarget: "cursor"
|
|
5892
|
+
});
|
|
5893
|
+
}
|
|
5894
|
+
/**
|
|
5895
|
+
* Ownership guard the processor consults before it deletes anything for this
|
|
5896
|
+
* tool. `.cursor/BUGBOT.md` is a file Cursor's own documentation tells users
|
|
5897
|
+
* to hand-write, so anything in it that rulesync did not write is not
|
|
5898
|
+
* rulesync's to remove — dropping the last check targeting Cursor must not
|
|
5899
|
+
* take somebody's hand-written review instructions with it. Deletion is
|
|
5900
|
+
* therefore allowed only for a file that is nothing but generated sections:
|
|
5901
|
+
* one that carries no marker at all, or that carries hand-written text ahead
|
|
5902
|
+
* of the first marker, stays.
|
|
5903
|
+
*/
|
|
5904
|
+
static async canDeleteAuxiliaryFiles({ outputRoot }) {
|
|
5905
|
+
const paths = CursorCheck.getSettablePaths();
|
|
5906
|
+
const fileContent = await readFileContentOrNull((0, node_path.join)(outputRoot, paths.relativeDirPath, paths.relativeFilePath ?? "BUGBOT.md"));
|
|
5907
|
+
if (fileContent === null) return true;
|
|
5908
|
+
const firstMarkerStart = findMarkers(fileContent)[0]?.start;
|
|
5909
|
+
if (firstMarkerStart === void 0) return false;
|
|
5910
|
+
return fileContent.slice(0, firstMarkerStart).trim().length === 0;
|
|
5911
|
+
}
|
|
5912
|
+
static fromRulesyncCheck(_params) {
|
|
5913
|
+
throw new Error("Cursor checks are built from all checks at once; use fromRulesyncChecks.");
|
|
5914
|
+
}
|
|
5915
|
+
static async fromRulesyncChecks({ outputRoot = process.cwd(), rulesyncChecks, global = false, logger }) {
|
|
5916
|
+
if (rulesyncChecks.length === 0) return [];
|
|
5917
|
+
const paths = CursorCheck.getSettablePaths({ global });
|
|
5918
|
+
const relativeFilePath = paths.relativeFilePath ?? "BUGBOT.md";
|
|
5919
|
+
const filePath = (0, node_path.join)(outputRoot, paths.relativeDirPath, relativeFilePath);
|
|
5920
|
+
const existingContent = await readFileContentOrNull(filePath) ?? "";
|
|
5921
|
+
const firstMarkerStart = findMarkers(existingContent)[0]?.start ?? existingContent.length;
|
|
5922
|
+
if (existingContent.slice(0, firstMarkerStart).trim().length > 0) 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.`);
|
|
5923
|
+
const fileContent = `${rulesyncChecks.map(renderSection).join("\n\n")}\n`;
|
|
5924
|
+
return [new CursorCheck({
|
|
5925
|
+
outputRoot,
|
|
5926
|
+
relativeDirPath: paths.relativeDirPath,
|
|
5927
|
+
relativeFilePath,
|
|
5928
|
+
fileContent,
|
|
5929
|
+
global
|
|
5930
|
+
})];
|
|
5931
|
+
}
|
|
5932
|
+
static async fromFile({ outputRoot = process.cwd(), global = false }) {
|
|
5933
|
+
const paths = CursorCheck.getSettablePaths({ global });
|
|
5934
|
+
const relativeFilePath = paths.relativeFilePath ?? "BUGBOT.md";
|
|
5935
|
+
const filePath = (0, node_path.join)(outputRoot, paths.relativeDirPath, relativeFilePath);
|
|
5936
|
+
return new CursorCheck({
|
|
5937
|
+
outputRoot,
|
|
5938
|
+
relativeDirPath: paths.relativeDirPath,
|
|
5939
|
+
relativeFilePath,
|
|
5940
|
+
fileContent: await readFileContentOrNull(filePath) ?? "",
|
|
5941
|
+
global
|
|
5942
|
+
});
|
|
5943
|
+
}
|
|
5944
|
+
static forDeletion({ outputRoot = process.cwd(), relativeDirPath, relativeFilePath, global = false }) {
|
|
5945
|
+
return new CursorCheck({
|
|
5946
|
+
outputRoot,
|
|
5947
|
+
relativeDirPath,
|
|
5948
|
+
relativeFilePath,
|
|
5949
|
+
fileContent: "",
|
|
5950
|
+
validate: false,
|
|
5951
|
+
global
|
|
5952
|
+
});
|
|
5953
|
+
}
|
|
5954
|
+
validate() {
|
|
5955
|
+
return {
|
|
5956
|
+
success: true,
|
|
5957
|
+
error: null
|
|
5958
|
+
};
|
|
5959
|
+
}
|
|
5960
|
+
toRulesyncCheck() {
|
|
5961
|
+
const first = this.toRulesyncChecks()[0];
|
|
5962
|
+
if (!first) throw new Error(`No check instructions found in ${(0, node_path.join)(this.getRelativeDirPath(), this.getRelativeFilePath())}.`);
|
|
5963
|
+
return first;
|
|
5964
|
+
}
|
|
5965
|
+
toRulesyncChecks() {
|
|
5966
|
+
const fileContent = this.getFileContent();
|
|
5967
|
+
const sections = [];
|
|
5968
|
+
const markers = findMarkers(fileContent);
|
|
5969
|
+
const preambleEnd = markers[0]?.start ?? fileContent.length;
|
|
5970
|
+
const preamble = fileContent.slice(0, preambleEnd).trim();
|
|
5971
|
+
if (preamble.length > 0) sections.push({
|
|
5972
|
+
name: FALLBACK_CHECK_NAME,
|
|
5973
|
+
content: unescapeMarkers(preamble)
|
|
5974
|
+
});
|
|
5975
|
+
for (const [index, marker] of markers.entries()) {
|
|
5976
|
+
const sectionEnd = markers[index + 1]?.start ?? fileContent.length;
|
|
5977
|
+
const markerName = marker.name.trim();
|
|
5978
|
+
const name = slugifyCheckName(markerName) || FALLBACK_CHECK_NAME;
|
|
5979
|
+
const content = stripGeneratedHeading(fileContent.slice(marker.end, sectionEnd).trim(), markerName);
|
|
5980
|
+
sections.push({
|
|
5981
|
+
name,
|
|
5982
|
+
content: unescapeMarkers(content)
|
|
5983
|
+
});
|
|
5984
|
+
}
|
|
5985
|
+
const used = /* @__PURE__ */ new Set();
|
|
5986
|
+
return sections.map(({ name, content }) => {
|
|
5987
|
+
let uniqueName = name;
|
|
5988
|
+
let suffix = 2;
|
|
5989
|
+
while (used.has(uniqueName)) {
|
|
5990
|
+
uniqueName = `${name}-${suffix}`;
|
|
5991
|
+
suffix += 1;
|
|
5992
|
+
}
|
|
5993
|
+
used.add(uniqueName);
|
|
5994
|
+
return new RulesyncCheck({
|
|
5995
|
+
outputRoot: ".",
|
|
5996
|
+
relativeDirPath: RULESYNC_CHECKS_RELATIVE_DIR_PATH,
|
|
5997
|
+
relativeFilePath: `${uniqueName}.md`,
|
|
5998
|
+
frontmatter: { targets: ["*"] },
|
|
5999
|
+
body: content
|
|
6000
|
+
});
|
|
6001
|
+
});
|
|
6002
|
+
}
|
|
6003
|
+
};
|
|
6004
|
+
//#endregion
|
|
5677
6005
|
//#region src/constants/hermesagent-paths.ts
|
|
5678
6006
|
/**
|
|
5679
6007
|
* Hermes Agent configuration-layout conventions.
|
|
@@ -6172,7 +6500,20 @@ const SHARED_CONFIG_OWNERSHIP = {
|
|
|
6172
6500
|
jsoncParseErrors: "error",
|
|
6173
6501
|
features: { permissions: {
|
|
6174
6502
|
kind: "replace-owned-keys",
|
|
6175
|
-
ownedKeys: [
|
|
6503
|
+
ownedKeys: [
|
|
6504
|
+
"chat.tools.terminal.autoApprove",
|
|
6505
|
+
"chat.tools.edits.autoApprove",
|
|
6506
|
+
"chat.tools.urls.autoApprove"
|
|
6507
|
+
]
|
|
6508
|
+
} }
|
|
6509
|
+
},
|
|
6510
|
+
".vscode/mcp.json": {
|
|
6511
|
+
format: "jsonc",
|
|
6512
|
+
invalidRootPolicy: "error",
|
|
6513
|
+
jsoncParseErrors: "error",
|
|
6514
|
+
features: { mcp: {
|
|
6515
|
+
kind: "replace-owned-keys",
|
|
6516
|
+
ownedKeys: ["servers"]
|
|
6176
6517
|
} }
|
|
6177
6518
|
},
|
|
6178
6519
|
".qwen/settings.json": {
|
|
@@ -6538,7 +6879,12 @@ const applyIgnoreReadDenies = (params) => {
|
|
|
6538
6879
|
const applyPermissions = (params) => {
|
|
6539
6880
|
const { settings, managedToolNames, toolNameOf, allow, ask, deny, logger } = params;
|
|
6540
6881
|
const current = parsePermissionsBlock(settings);
|
|
6541
|
-
const
|
|
6882
|
+
const emitted = /* @__PURE__ */ new Set([
|
|
6883
|
+
...allow,
|
|
6884
|
+
...ask,
|
|
6885
|
+
...deny
|
|
6886
|
+
]);
|
|
6887
|
+
const keepUnmanaged = (entries) => entries.filter((entry) => !managedToolNames.has(toolNameOf(entry)) && !emitted.has(entry));
|
|
6542
6888
|
if (logger && managedToolNames.has(READ_TOOL_NAME)) {
|
|
6543
6889
|
const overwrittenReadDenies = current.deny.filter((entry) => toolNameOf(entry) === READ_TOOL_NAME);
|
|
6544
6890
|
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 +7034,9 @@ function toRulesyncCheckFromGate({ gate, index, scope, editOnly }) {
|
|
|
6688
7034
|
body: ""
|
|
6689
7035
|
});
|
|
6690
7036
|
}
|
|
6691
|
-
function slugify(value) {
|
|
6692
|
-
return value.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 48).replace(/-+$/, "");
|
|
6693
|
-
}
|
|
6694
7037
|
function slugForGate({ gate, index, scope }) {
|
|
6695
|
-
const slug =
|
|
6696
|
-
return `${scope ? `${
|
|
7038
|
+
const slug = slugifyCheckName(typeof gate === "string" ? gate : typeof gate.name === "string" ? gate.name : typeof gate.command === "string" ? gate.command : "");
|
|
7039
|
+
return `${scope ? `${slugifyCheckName(scope.name)}-` : ""}${slug.length > 0 ? slug : "quality-gate"}-${index + 1}`;
|
|
6697
7040
|
}
|
|
6698
7041
|
/**
|
|
6699
7042
|
* Checks adapter for Takt (`.takt/config.yaml` project / `~/.takt/config.yaml`
|
|
@@ -6869,6 +7212,13 @@ const toolCheckFactories = /* @__PURE__ */ new Map([
|
|
|
6869
7212
|
filePattern: "*.md"
|
|
6870
7213
|
}
|
|
6871
7214
|
}],
|
|
7215
|
+
["cursor", {
|
|
7216
|
+
class: CursorCheck,
|
|
7217
|
+
meta: {
|
|
7218
|
+
supportsGlobal: false,
|
|
7219
|
+
filePattern: CURSOR_BUGBOT_FILE_NAME
|
|
7220
|
+
}
|
|
7221
|
+
}],
|
|
6872
7222
|
["hermesagent", {
|
|
6873
7223
|
class: HermesagentCheck,
|
|
6874
7224
|
meta: {
|
|
@@ -7284,6 +7634,7 @@ var AgentsmdCommand = class AgentsmdCommand extends SimulatedCommand {
|
|
|
7284
7634
|
const ANTIGRAVITY_DIR = ".agents";
|
|
7285
7635
|
const ANTIGRAVITY_SKILLS_DIR_PATH = (0, node_path.join)(ANTIGRAVITY_DIR, "skills");
|
|
7286
7636
|
const ANTIGRAVITY_WORKFLOWS_DIR_PATH = (0, node_path.join)(ANTIGRAVITY_DIR, "workflows");
|
|
7637
|
+
const ANTIGRAVITY_AGENTS_DIR_PATH = (0, node_path.join)(ANTIGRAVITY_DIR, "agents");
|
|
7287
7638
|
const ANTIGRAVITY_MCP_FILE_NAME = "mcp_config.json";
|
|
7288
7639
|
const ANTIGRAVITY_HOOKS_FILE_NAME = "hooks.json";
|
|
7289
7640
|
const ANTIGRAVITY_IGNORE_FILE_NAME = ".geminiignore";
|
|
@@ -7294,6 +7645,7 @@ const ANTIGRAVITY_CLI_PERMISSIONS_FILE_NAME = "settings.json";
|
|
|
7294
7645
|
const ANTIGRAVITY_CLI_GLOBAL_WORKFLOWS_DIR_PATH = (0, node_path.join)(ANTIGRAVITY_GEMINI_DIR, ANTIGRAVITY_CLI_PERMISSIONS_SUBDIR, "global_workflows");
|
|
7295
7646
|
const ANTIGRAVITY_GLOBAL_CONFIG_SUBDIR = "config";
|
|
7296
7647
|
const ANTIGRAVITY_GLOBAL_CONFIG_DIR_PATH = (0, node_path.join)(ANTIGRAVITY_GEMINI_DIR, ANTIGRAVITY_GLOBAL_CONFIG_SUBDIR);
|
|
7648
|
+
const ANTIGRAVITY_GLOBAL_AGENTS_DIR_PATH = (0, node_path.join)(ANTIGRAVITY_GLOBAL_CONFIG_DIR_PATH, "agents");
|
|
7297
7649
|
//#endregion
|
|
7298
7650
|
//#region src/constants/antigravity-cli-paths.ts
|
|
7299
7651
|
const ANTIGRAVITY_AGENTS_DIR = ANTIGRAVITY_DIR;
|
|
@@ -7852,6 +8204,7 @@ const CLAUDECODE_PLUGIN_HOOKS_DIR = "hooks";
|
|
|
7852
8204
|
const CLAUDECODE_PLUGIN_HOOKS_FILE_NAME = "hooks.json";
|
|
7853
8205
|
const ANTIGRAVITY_PLUGIN_RULES_DIR = "rules";
|
|
7854
8206
|
const ANTIGRAVITY_PLUGIN_SKILLS_DIR = "skills";
|
|
8207
|
+
const ANTIGRAVITY_PLUGIN_AGENTS_DIR = "agents";
|
|
7855
8208
|
const ANTIGRAVITY_PLUGIN_MCP_FILE_NAME = "mcp_config.json";
|
|
7856
8209
|
const ANTIGRAVITY_PLUGIN_HOOKS_FILE_NAME = "hooks.json";
|
|
7857
8210
|
//#endregion
|
|
@@ -8076,6 +8429,7 @@ const COPILOT_SKILLS_DIR_PATH = (0, node_path.join)(GITHUB_DIR, "skills");
|
|
|
8076
8429
|
const COPILOT_AGENTS_DIR_PATH = (0, node_path.join)(GITHUB_DIR, "agents");
|
|
8077
8430
|
const COPILOT_HOOKS_DIR_PATH = (0, node_path.join)(GITHUB_DIR, "hooks");
|
|
8078
8431
|
const COPILOT_HOOKS_FILE_NAME = "copilot-hooks.json";
|
|
8432
|
+
const COPILOT_GLOBAL_HOOKS_FILE_NAME = "copilot-ide-hooks.json";
|
|
8079
8433
|
const COPILOT_MCP_DIR = ".vscode";
|
|
8080
8434
|
const COPILOT_MCP_FILE_NAME = "mcp.json";
|
|
8081
8435
|
const COPILOT_VSCODE_SETTINGS_FILE_NAME = "settings.json";
|
|
@@ -8083,6 +8437,7 @@ const COPILOTCLI_MCP_FILE_NAME = "mcp-config.json";
|
|
|
8083
8437
|
const COPILOTCLI_PROJECT_MCP_FILE_NAME = "mcp.json";
|
|
8084
8438
|
const COPILOTCLI_AGENTS_DIR_PATH = (0, node_path.join)(COPILOT_DIR, "agents");
|
|
8085
8439
|
const COPILOTCLI_HOOKS_DIR_PATH = (0, node_path.join)(COPILOT_DIR, "hooks");
|
|
8440
|
+
const COPILOT_GLOBAL_HOOKS_DIR_PATH = COPILOTCLI_HOOKS_DIR_PATH;
|
|
8086
8441
|
const COPILOTCLI_HOOKS_FILE_NAME = "copilotcli-hooks.json";
|
|
8087
8442
|
const COPILOT_SKILLS_GLOBAL_DIR_PATH = (0, node_path.join)(COPILOT_DIR, "skills");
|
|
8088
8443
|
//#endregion
|
|
@@ -8206,17 +8561,6 @@ var CopilotCommand = class CopilotCommand extends ToolCommand {
|
|
|
8206
8561
|
}
|
|
8207
8562
|
};
|
|
8208
8563
|
//#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
8564
|
//#region src/features/commands/cursor-command.ts
|
|
8221
8565
|
const CursorCommandFrontmatterSchema = zod_mini.z.looseObject({
|
|
8222
8566
|
description: zod_mini.z.optional(zod_mini.z.string()),
|
|
@@ -11985,6 +12329,13 @@ function groupDefinitionsByMatcher(definitions) {
|
|
|
11985
12329
|
}
|
|
11986
12330
|
return byMatcher;
|
|
11987
12331
|
}
|
|
12332
|
+
/** `$CLAUDE_PROJECT_DIR` -> `${CLAUDE_PROJECT_DIR}`, the form the tool substitutes. */
|
|
12333
|
+
function bracePlaceholder(projectDirVar) {
|
|
12334
|
+
return `\${${projectDirVar.replace(/^\$/, "")}}`;
|
|
12335
|
+
}
|
|
12336
|
+
function stripSurroundingQuotes(value) {
|
|
12337
|
+
return value.replace(/^(["'])(.*)\1$/, "$2").replace(/^["']/, "");
|
|
12338
|
+
}
|
|
11988
12339
|
/**
|
|
11989
12340
|
* Apply the optional project directory variable prefix to a command string.
|
|
11990
12341
|
*/
|
|
@@ -11994,8 +12345,10 @@ function applyCommandPrefix({ def, converterConfig }) {
|
|
|
11994
12345
|
const unquotedCommand = trimmedCommand?.replace(/^["']/, "");
|
|
11995
12346
|
const isDotRelativeCommand = unquotedCommand?.startsWith(".") ?? false;
|
|
11996
12347
|
const isAbsoluteCommand = typeof unquotedCommand === "string" && (node_path.posix.isAbsolute(unquotedCommand) || node_path.win32.isAbsolute(unquotedCommand) || unquotedCommand.startsWith("~/"));
|
|
12348
|
+
const isExecForm = (converterConfig.arrayPassthroughFields?.some(({ canonical }) => canonical === "args") ?? false) && Array.isArray(def.args);
|
|
11997
12349
|
if (!(converterConfig.projectDirVar !== "" && typeof trimmedCommand === "string" && !trimmedCommand.startsWith("$") && !isAbsoluteCommand && (!converterConfig.prefixDotRelativeCommandsOnly || isDotRelativeCommand)) || typeof trimmedCommand !== "string") return def.command;
|
|
11998
12350
|
const relativeCommand = trimmedCommand.replace(/^(["'])\.\//, "$1").replace(/^\.\//, "");
|
|
12351
|
+
if (isExecForm) return `${bracePlaceholder(converterConfig.projectDirVar)}/${stripSurroundingQuotes(relativeCommand)}`;
|
|
11999
12352
|
return `"${converterConfig.projectDirVar}"/${relativeCommand}`;
|
|
12000
12353
|
}
|
|
12001
12354
|
/**
|
|
@@ -12003,8 +12356,11 @@ function applyCommandPrefix({ def, converterConfig }) {
|
|
|
12003
12356
|
* canonical field name to its (possibly renamed) tool field name. Only boolean
|
|
12004
12357
|
* values are carried through.
|
|
12005
12358
|
*/
|
|
12006
|
-
function emitBooleanPassthroughFields({ def, converterConfig }) {
|
|
12007
|
-
return Object.fromEntries((converterConfig.booleanPassthroughFields ?? []).filter(({ canonical
|
|
12359
|
+
function emitBooleanPassthroughFields({ def, hookType, converterConfig }) {
|
|
12360
|
+
return Object.fromEntries((converterConfig.booleanPassthroughFields ?? []).filter(({ canonical, commandOnly }) => {
|
|
12361
|
+
if (commandOnly === true && hookType !== "command") return false;
|
|
12362
|
+
return typeof def[canonical] === "boolean";
|
|
12363
|
+
}).map(({ canonical, tool }) => [tool, def[canonical]]));
|
|
12008
12364
|
}
|
|
12009
12365
|
/**
|
|
12010
12366
|
* Import the configured boolean passthrough fields back into canonical fields,
|
|
@@ -12018,8 +12374,11 @@ function importBooleanPassthroughFields({ h, converterConfig }) {
|
|
|
12018
12374
|
* canonical field name to its (possibly renamed) tool field name. Only non-empty
|
|
12019
12375
|
* string values are carried through.
|
|
12020
12376
|
*/
|
|
12021
|
-
function emitStringPassthroughFields({ def, converterConfig }) {
|
|
12022
|
-
return Object.fromEntries((converterConfig.stringPassthroughFields ?? []).filter(({ canonical
|
|
12377
|
+
function emitStringPassthroughFields({ def, hookType, converterConfig }) {
|
|
12378
|
+
return Object.fromEntries((converterConfig.stringPassthroughFields ?? []).filter(({ canonical, commandOnly }) => {
|
|
12379
|
+
if (commandOnly === true && hookType !== "command") return false;
|
|
12380
|
+
return typeof def[canonical] === "string" && def[canonical] !== "";
|
|
12381
|
+
}).map(({ canonical, tool }) => [tool, def[canonical]]));
|
|
12023
12382
|
}
|
|
12024
12383
|
/**
|
|
12025
12384
|
* Import the configured string passthrough fields back into canonical fields,
|
|
@@ -12032,8 +12391,11 @@ function importStringPassthroughFields({ h, converterConfig }) {
|
|
|
12032
12391
|
/**
|
|
12033
12392
|
* Emit the configured string-array passthrough fields on the tool side.
|
|
12034
12393
|
*/
|
|
12035
|
-
function emitArrayPassthroughFields({ def, converterConfig }) {
|
|
12036
|
-
return Object.fromEntries((converterConfig.arrayPassthroughFields ?? []).filter(({ canonical
|
|
12394
|
+
function emitArrayPassthroughFields({ def, hookType, converterConfig }) {
|
|
12395
|
+
return Object.fromEntries((converterConfig.arrayPassthroughFields ?? []).filter(({ canonical, commandOnly }) => {
|
|
12396
|
+
if (commandOnly === true && hookType !== "command") return false;
|
|
12397
|
+
return isStringArray(def[canonical]);
|
|
12398
|
+
}).map(({ canonical, tool }) => [tool, def[canonical]]));
|
|
12037
12399
|
}
|
|
12038
12400
|
/**
|
|
12039
12401
|
* Import the configured string-array passthrough fields, reversing
|
|
@@ -12110,14 +12472,17 @@ function buildToolHooks({ defs, converterConfig }) {
|
|
|
12110
12472
|
hooks.push({
|
|
12111
12473
|
...emitBooleanPassthroughFields({
|
|
12112
12474
|
def,
|
|
12475
|
+
hookType,
|
|
12113
12476
|
converterConfig
|
|
12114
12477
|
}),
|
|
12115
12478
|
...emitStringPassthroughFields({
|
|
12116
12479
|
def,
|
|
12480
|
+
hookType,
|
|
12117
12481
|
converterConfig
|
|
12118
12482
|
}),
|
|
12119
12483
|
...emitArrayPassthroughFields({
|
|
12120
12484
|
def,
|
|
12485
|
+
hookType,
|
|
12121
12486
|
converterConfig
|
|
12122
12487
|
}),
|
|
12123
12488
|
type: hookType,
|
|
@@ -12201,6 +12566,8 @@ function stripCommandPrefix({ command, converterConfig }) {
|
|
|
12201
12566
|
if (converterConfig.projectDirVar === "" || typeof cmd !== "string") return cmd;
|
|
12202
12567
|
const quotedPrefix = `"${converterConfig.projectDirVar}"/`;
|
|
12203
12568
|
if (cmd.startsWith(quotedPrefix)) return `./${cmd.slice(quotedPrefix.length)}`;
|
|
12569
|
+
const bracedPrefix = `${bracePlaceholder(converterConfig.projectDirVar)}/`;
|
|
12570
|
+
if (cmd.startsWith(bracedPrefix)) return `./${cmd.slice(bracedPrefix.length)}`;
|
|
12204
12571
|
if (cmd.includes(`${converterConfig.projectDirVar}/`)) {
|
|
12205
12572
|
const escapedVar = converterConfig.projectDirVar.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
12206
12573
|
return cmd.replace(new RegExp(`^${escapedVar}\\/?`), "./");
|
|
@@ -12748,7 +13115,10 @@ const CLAUDE_CONVERTER_CONFIG = {
|
|
|
12748
13115
|
"taskCreated",
|
|
12749
13116
|
"taskCompleted",
|
|
12750
13117
|
"teammateIdle",
|
|
12751
|
-
"cwdChanged"
|
|
13118
|
+
"cwdChanged",
|
|
13119
|
+
"beforeSubmitPrompt",
|
|
13120
|
+
"stop",
|
|
13121
|
+
"directoryAdded"
|
|
12752
13122
|
]),
|
|
12753
13123
|
supportedHookTypes: /* @__PURE__ */ new Set([
|
|
12754
13124
|
"command",
|
|
@@ -12758,9 +13128,45 @@ const CLAUDE_CONVERTER_CONFIG = {
|
|
|
12758
13128
|
"agent"
|
|
12759
13129
|
]),
|
|
12760
13130
|
emitsPromptModel: true,
|
|
12761
|
-
stringPassthroughFields: [
|
|
12762
|
-
|
|
12763
|
-
|
|
13131
|
+
stringPassthroughFields: [
|
|
13132
|
+
{
|
|
13133
|
+
canonical: "if",
|
|
13134
|
+
tool: "if"
|
|
13135
|
+
},
|
|
13136
|
+
{
|
|
13137
|
+
canonical: "statusMessage",
|
|
13138
|
+
tool: "statusMessage"
|
|
13139
|
+
},
|
|
13140
|
+
{
|
|
13141
|
+
canonical: "shell",
|
|
13142
|
+
tool: "shell",
|
|
13143
|
+
commandOnly: true
|
|
13144
|
+
}
|
|
13145
|
+
],
|
|
13146
|
+
booleanPassthroughFields: [
|
|
13147
|
+
{
|
|
13148
|
+
canonical: "once",
|
|
13149
|
+
tool: "once"
|
|
13150
|
+
},
|
|
13151
|
+
{
|
|
13152
|
+
canonical: "async",
|
|
13153
|
+
tool: "async",
|
|
13154
|
+
commandOnly: true
|
|
13155
|
+
},
|
|
13156
|
+
{
|
|
13157
|
+
canonical: "asyncRewake",
|
|
13158
|
+
tool: "asyncRewake",
|
|
13159
|
+
commandOnly: true
|
|
13160
|
+
},
|
|
13161
|
+
{
|
|
13162
|
+
canonical: "continueOnBlock",
|
|
13163
|
+
tool: "continueOnBlock"
|
|
13164
|
+
}
|
|
13165
|
+
],
|
|
13166
|
+
arrayPassthroughFields: [{
|
|
13167
|
+
canonical: "args",
|
|
13168
|
+
tool: "args",
|
|
13169
|
+
commandOnly: true
|
|
12764
13170
|
}]
|
|
12765
13171
|
};
|
|
12766
13172
|
var ClaudecodeHooks = class extends ToolHooks {
|
|
@@ -12868,7 +13274,14 @@ const CODEXCLI_CONVERTER_CONFIG = {
|
|
|
12868
13274
|
toolToCanonicalEventNames: CODEXCLI_TO_CANONICAL_EVENT_NAMES,
|
|
12869
13275
|
projectDirVar: "",
|
|
12870
13276
|
supportedHookTypes: /* @__PURE__ */ new Set(["command"]),
|
|
12871
|
-
passthroughFields: ["name", "description"]
|
|
13277
|
+
passthroughFields: ["name", "description"],
|
|
13278
|
+
stringPassthroughFields: [{
|
|
13279
|
+
canonical: "commandWindows",
|
|
13280
|
+
tool: "commandWindows"
|
|
13281
|
+
}, {
|
|
13282
|
+
canonical: "statusMessage",
|
|
13283
|
+
tool: "statusMessage"
|
|
13284
|
+
}]
|
|
12872
13285
|
};
|
|
12873
13286
|
/**
|
|
12874
13287
|
* Build the content for `.codex/config.toml`, cleaning up the deprecated `codex_hooks` key.
|
|
@@ -13116,7 +13529,11 @@ var CopilotHooks = class CopilotHooks extends ToolHooks {
|
|
|
13116
13529
|
fileContent: params.fileContent ?? "{}"
|
|
13117
13530
|
});
|
|
13118
13531
|
}
|
|
13119
|
-
static getSettablePaths(
|
|
13532
|
+
static getSettablePaths({ global = false } = {}) {
|
|
13533
|
+
if (global) return {
|
|
13534
|
+
relativeDirPath: COPILOT_GLOBAL_HOOKS_DIR_PATH,
|
|
13535
|
+
relativeFilePath: COPILOT_GLOBAL_HOOKS_FILE_NAME
|
|
13536
|
+
};
|
|
13120
13537
|
return {
|
|
13121
13538
|
relativeDirPath: COPILOT_HOOKS_DIR_PATH,
|
|
13122
13539
|
relativeFilePath: COPILOT_HOOKS_FILE_NAME
|
|
@@ -13130,11 +13547,12 @@ var CopilotHooks = class CopilotHooks extends ToolHooks {
|
|
|
13130
13547
|
relativeDirPath: paths.relativeDirPath,
|
|
13131
13548
|
relativeFilePath: paths.relativeFilePath,
|
|
13132
13549
|
fileContent,
|
|
13133
|
-
validate
|
|
13550
|
+
validate,
|
|
13551
|
+
global
|
|
13134
13552
|
});
|
|
13135
13553
|
}
|
|
13136
|
-
static async fromRulesyncHooks({ outputRoot = process.cwd(), rulesyncHooks, validate = true }) {
|
|
13137
|
-
const paths = CopilotHooks.getSettablePaths();
|
|
13554
|
+
static async fromRulesyncHooks({ outputRoot = process.cwd(), rulesyncHooks, validate = true, global = false }) {
|
|
13555
|
+
const paths = CopilotHooks.getSettablePaths({ global });
|
|
13138
13556
|
const copilotHooks = canonicalToCopilotHooks(rulesyncHooks.getJson());
|
|
13139
13557
|
const fileContent = JSON.stringify({
|
|
13140
13558
|
version: 1,
|
|
@@ -13145,7 +13563,8 @@ var CopilotHooks = class CopilotHooks extends ToolHooks {
|
|
|
13145
13563
|
relativeDirPath: paths.relativeDirPath,
|
|
13146
13564
|
relativeFilePath: paths.relativeFilePath,
|
|
13147
13565
|
fileContent,
|
|
13148
|
-
validate
|
|
13566
|
+
validate,
|
|
13567
|
+
global
|
|
13149
13568
|
});
|
|
13150
13569
|
}
|
|
13151
13570
|
toRulesyncHooks(options) {
|
|
@@ -13189,10 +13608,16 @@ var CopilotHooks = class CopilotHooks extends ToolHooks {
|
|
|
13189
13608
|
* (`sessionStart`, `sessionEnd`, `userPromptSubmitted`, `preToolUse`,
|
|
13190
13609
|
* `postToolUse`, `postToolUseFailure`, `agentStop`, `subagentStart`,
|
|
13191
13610
|
* `subagentStop`, `errorOccurred`, `preCompact`, `permissionRequest`,
|
|
13192
|
-
* `notification`, `preMcpToolCall`). Each entry
|
|
13611
|
+
* `notification`, `userPromptTransformed`, `preMcpToolCall`). Each entry
|
|
13612
|
+
* supports three hook types:
|
|
13193
13613
|
*
|
|
13194
13614
|
* - `command` — the `bash` / `powershell` command-field shape with optional
|
|
13195
|
-
* `timeoutSec`, plus optional `cwd` / `env`.
|
|
13615
|
+
* `timeoutSec`, plus optional `cwd` / `env`. Upstream also accepts the
|
|
13616
|
+
* portable `command` field (copied to both shells when neither is present)
|
|
13617
|
+
* and `timeout` as an alias for `timeoutSec`; rulesync reads both on import.
|
|
13618
|
+
* On generate the canonical `shell` selector picks `bash` or `powershell`,
|
|
13619
|
+
* and without it the portable `command` field is written — so the generated
|
|
13620
|
+
* file does not depend on the machine rulesync ran on.
|
|
13196
13621
|
* - `prompt` — a `prompt` string (Copilot CLI only honors prompt hooks on
|
|
13197
13622
|
* `sessionStart`, so prompt hooks on other events are skipped).
|
|
13198
13623
|
* - `http` — `url` / `headers` / `allowedEnvVars` with optional `timeoutSec`.
|
|
@@ -13218,11 +13643,11 @@ var CopilotHooks = class CopilotHooks extends ToolHooks {
|
|
|
13218
13643
|
* all rulesync-managed Copilot CLI files under the single `~/.copilot/`
|
|
13219
13644
|
* root and will revisit if the spec later mandates an alternate layout.
|
|
13220
13645
|
*
|
|
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
|
-
*
|
|
13646
|
+
* Hook entries on the six matcher-aware events (see
|
|
13647
|
+
* {@link COPILOTCLI_MATCHER_EVENTS}) may carry an optional `matcher` regex; it
|
|
13648
|
+
* is emitted on those events and dropped (with a warning) on any other event,
|
|
13649
|
+
* which never honors matchers.
|
|
13650
|
+
* Reference: https://docs.github.com/en/copilot/reference/hooks-reference
|
|
13226
13651
|
*
|
|
13227
13652
|
* The output JSON schema and platform-specific `bash` / `powershell` command
|
|
13228
13653
|
* field selection match `copilot-hooks.ts`, but the event surface diverges (the
|
|
@@ -13237,19 +13662,30 @@ var CopilotHooks = class CopilotHooks extends ToolHooks {
|
|
|
13237
13662
|
* `Edit|Write`) are now honored instead of silently dropped").
|
|
13238
13663
|
* @see https://docs.github.com/en/copilot/reference/hooks-reference
|
|
13239
13664
|
*/
|
|
13240
|
-
const COPILOTCLI_MATCHER_EVENTS = /* @__PURE__ */ new Set([
|
|
13665
|
+
const COPILOTCLI_MATCHER_EVENTS = /* @__PURE__ */ new Set([
|
|
13666
|
+
"preToolUse",
|
|
13667
|
+
"postToolUse",
|
|
13668
|
+
"notification",
|
|
13669
|
+
"permissionRequest",
|
|
13670
|
+
"preCompact",
|
|
13671
|
+
"subagentStart"
|
|
13672
|
+
]);
|
|
13673
|
+
/** Human-readable list of the matcher-aware events, for the drop warning. */
|
|
13674
|
+
const COPILOTCLI_MATCHER_EVENTS_LABEL = [...COPILOTCLI_MATCHER_EVENTS].join("/");
|
|
13241
13675
|
const CopilotCliHookEntrySchema = zod_mini.z.looseObject({
|
|
13242
13676
|
type: zod_mini.z._default(zod_mini.z.string(), "command"),
|
|
13243
13677
|
matcher: zod_mini.z.optional(zod_mini.z.string()),
|
|
13244
13678
|
bash: zod_mini.z.optional(zod_mini.z.string()),
|
|
13245
13679
|
powershell: zod_mini.z.optional(zod_mini.z.string()),
|
|
13680
|
+
command: zod_mini.z.optional(zod_mini.z.string()),
|
|
13246
13681
|
prompt: zod_mini.z.optional(zod_mini.z.string()),
|
|
13247
13682
|
url: zod_mini.z.optional(zod_mini.z.string()),
|
|
13248
13683
|
headers: zod_mini.z.optional(zod_mini.z.record(zod_mini.z.string(), zod_mini.z.string())),
|
|
13249
13684
|
allowedEnvVars: zod_mini.z.optional(zod_mini.z.array(zod_mini.z.string())),
|
|
13250
13685
|
cwd: zod_mini.z.optional(zod_mini.z.string()),
|
|
13251
13686
|
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())
|
|
13687
|
+
timeoutSec: zod_mini.z.optional(zod_mini.z.number()),
|
|
13688
|
+
timeout: zod_mini.z.optional(zod_mini.z.number())
|
|
13253
13689
|
});
|
|
13254
13690
|
/** Filter the shared config hooks down to events the Copilot CLI supports. */
|
|
13255
13691
|
function filterSupportedCopilotCliHooks(hooks) {
|
|
@@ -13260,21 +13696,21 @@ function filterSupportedCopilotCliHooks(hooks) {
|
|
|
13260
13696
|
}
|
|
13261
13697
|
/**
|
|
13262
13698
|
* 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.
|
|
13699
|
+
* only on the events listed in {@link COPILOTCLI_MATCHER_EVENTS}; on any other
|
|
13700
|
+
* event a matcher would be silently dropped by the CLI, so we drop it here with
|
|
13701
|
+
* a warning rather than emitting a dead field.
|
|
13266
13702
|
*/
|
|
13267
13703
|
function resolveExportMatcherPart({ matcher, matcherSupported, eventName, logger }) {
|
|
13268
13704
|
if (matcher === void 0 || matcher === null || matcher === "") return {};
|
|
13269
13705
|
if (matcherSupported) return { matcher };
|
|
13270
|
-
logger?.warn(`Copilot CLI hook matchers are only honored on
|
|
13706
|
+
logger?.warn(`Copilot CLI hook matchers are only honored on ${COPILOTCLI_MATCHER_EVENTS_LABEL}; dropping matcher "${matcher}" on '${eventName}'.`);
|
|
13271
13707
|
return {};
|
|
13272
13708
|
}
|
|
13273
13709
|
/**
|
|
13274
13710
|
* Build the exported entries for a single canonical event. Returns an empty
|
|
13275
13711
|
* array when no entries are emitted (e.g. all prompt hooks were skipped).
|
|
13276
13712
|
*/
|
|
13277
|
-
function buildCopilotCliEntriesForEvent({ eventName, definitions, canonicalSchemaKeys,
|
|
13713
|
+
function buildCopilotCliEntriesForEvent({ eventName, definitions, canonicalSchemaKeys, logger }) {
|
|
13278
13714
|
const matcherSupported = COPILOTCLI_MATCHER_EVENTS.has(eventName);
|
|
13279
13715
|
const entries = [];
|
|
13280
13716
|
for (const def of definitions) {
|
|
@@ -13310,22 +13746,24 @@ function buildCopilotCliEntriesForEvent({ eventName, definitions, canonicalSchem
|
|
|
13310
13746
|
...timeoutPart,
|
|
13311
13747
|
...rest
|
|
13312
13748
|
});
|
|
13313
|
-
else if (hookType === "command")
|
|
13314
|
-
|
|
13315
|
-
|
|
13316
|
-
|
|
13317
|
-
|
|
13318
|
-
|
|
13319
|
-
|
|
13320
|
-
|
|
13321
|
-
|
|
13322
|
-
|
|
13749
|
+
else if (hookType === "command") {
|
|
13750
|
+
const commandField = def.shell ?? "command";
|
|
13751
|
+
entries.push({
|
|
13752
|
+
type: "command",
|
|
13753
|
+
...matcherPart,
|
|
13754
|
+
...compact({
|
|
13755
|
+
[commandField]: def.command,
|
|
13756
|
+
env: def.env
|
|
13757
|
+
}),
|
|
13758
|
+
...timeoutPart,
|
|
13759
|
+
...rest
|
|
13760
|
+
});
|
|
13761
|
+
}
|
|
13323
13762
|
}
|
|
13324
13763
|
return entries;
|
|
13325
13764
|
}
|
|
13326
13765
|
function canonicalToCopilotCliHooks(config, logger) {
|
|
13327
13766
|
const canonicalSchemaKeys = Object.keys(HookDefinitionSchema.shape);
|
|
13328
|
-
const commandField = process.platform === "win32" ? "powershell" : "bash";
|
|
13329
13767
|
const effectiveHooks = {
|
|
13330
13768
|
...filterSupportedCopilotCliHooks(config.hooks),
|
|
13331
13769
|
...config.copilot?.hooks,
|
|
@@ -13338,7 +13776,6 @@ function canonicalToCopilotCliHooks(config, logger) {
|
|
|
13338
13776
|
eventName,
|
|
13339
13777
|
definitions,
|
|
13340
13778
|
canonicalSchemaKeys,
|
|
13341
|
-
commandField,
|
|
13342
13779
|
logger
|
|
13343
13780
|
});
|
|
13344
13781
|
if (entries.length > 0) out[copilotEventName] = entries;
|
|
@@ -13355,6 +13792,13 @@ function importPassthrough(entry) {
|
|
|
13355
13792
|
if (entry.env !== void 0) passthrough.env = entry.env;
|
|
13356
13793
|
return passthrough;
|
|
13357
13794
|
}
|
|
13795
|
+
/**
|
|
13796
|
+
* Resolve the canonical command and its `shell` selector from an imported entry.
|
|
13797
|
+
*
|
|
13798
|
+
* A shell-specific field carries its `shell` through so re-export writes the
|
|
13799
|
+
* same field back. An entry using only the portable `command` field leaves
|
|
13800
|
+
* `shell` unset, which re-export renders as the portable field again.
|
|
13801
|
+
*/
|
|
13358
13802
|
function resolveImportCommand(entry, logger) {
|
|
13359
13803
|
const hasBash = typeof entry.bash === "string";
|
|
13360
13804
|
const hasPowershell = typeof entry.powershell === "string";
|
|
@@ -13363,9 +13807,22 @@ function resolveImportCommand(entry, logger) {
|
|
|
13363
13807
|
const chosen = isWindows ? "powershell" : "bash";
|
|
13364
13808
|
const ignored = isWindows ? "bash" : "powershell";
|
|
13365
13809
|
logger?.warn(`Copilot CLI hook has both bash and powershell commands; using ${chosen} and ignoring ${ignored} on this platform.`);
|
|
13366
|
-
return isWindows ?
|
|
13367
|
-
|
|
13368
|
-
|
|
13810
|
+
return isWindows ? {
|
|
13811
|
+
command: entry.powershell,
|
|
13812
|
+
shell: "powershell"
|
|
13813
|
+
} : {
|
|
13814
|
+
command: entry.bash,
|
|
13815
|
+
shell: "bash"
|
|
13816
|
+
};
|
|
13817
|
+
} else if (hasBash) return {
|
|
13818
|
+
command: entry.bash,
|
|
13819
|
+
shell: "bash"
|
|
13820
|
+
};
|
|
13821
|
+
else if (hasPowershell) return {
|
|
13822
|
+
command: entry.powershell,
|
|
13823
|
+
shell: "powershell"
|
|
13824
|
+
};
|
|
13825
|
+
return typeof entry.command === "string" ? { command: entry.command } : {};
|
|
13369
13826
|
}
|
|
13370
13827
|
function copilotCliHooksToCanonical(rawHooks, logger) {
|
|
13371
13828
|
if (rawHooks === null || rawHooks === void 0 || typeof rawHooks !== "object") return {};
|
|
@@ -13378,7 +13835,7 @@ function copilotCliHooksToCanonical(rawHooks, logger) {
|
|
|
13378
13835
|
const parseResult = CopilotCliHookEntrySchema.safeParse(rawEntry);
|
|
13379
13836
|
if (!parseResult.success) continue;
|
|
13380
13837
|
const entry = parseResult.data;
|
|
13381
|
-
const timeout = entry.timeoutSec;
|
|
13838
|
+
const timeout = entry.timeoutSec ?? entry.timeout;
|
|
13382
13839
|
const timeoutPart = timeout !== void 0 ? { timeout } : {};
|
|
13383
13840
|
const matcherPart = entry.matcher !== void 0 && entry.matcher !== "" ? { matcher: entry.matcher } : {};
|
|
13384
13841
|
const passthrough = importPassthrough(entry);
|
|
@@ -13397,10 +13854,11 @@ function copilotCliHooksToCanonical(rawHooks, logger) {
|
|
|
13397
13854
|
...passthrough
|
|
13398
13855
|
});
|
|
13399
13856
|
else {
|
|
13400
|
-
const command = resolveImportCommand(entry, logger);
|
|
13857
|
+
const { command, shell } = resolveImportCommand(entry, logger);
|
|
13401
13858
|
defs.push({
|
|
13402
13859
|
type: "command",
|
|
13403
13860
|
...command !== void 0 && { command },
|
|
13861
|
+
...shell !== void 0 && { shell },
|
|
13404
13862
|
...matcherPart,
|
|
13405
13863
|
...timeoutPart,
|
|
13406
13864
|
...passthrough
|
|
@@ -14540,7 +14998,23 @@ var JunieHooks = class JunieHooks extends ToolHooks {
|
|
|
14540
14998
|
};
|
|
14541
14999
|
//#endregion
|
|
14542
15000
|
//#region src/features/hooks/opencode-style-generator.ts
|
|
14543
|
-
|
|
15001
|
+
/**
|
|
15002
|
+
* Tool events emitted as named `(input, ...)` hooks rather than through the
|
|
15003
|
+
* generic `event.type` dispatch, mapped to the expression a hook's `matcher`
|
|
15004
|
+
* regex is tested against — or `null` when the hook has no matchable subject,
|
|
15005
|
+
* in which case a matcher is dropped rather than compiled against a field that
|
|
15006
|
+
* does not exist.
|
|
15007
|
+
*
|
|
15008
|
+
* `experimental.session.compacting` receives `(input, output)` and exposes no
|
|
15009
|
+
* per-invocation identifier worth matching on, so it takes `null`.
|
|
15010
|
+
*
|
|
15011
|
+
* @see https://opencode.ai/docs/plugins/
|
|
15012
|
+
*/
|
|
15013
|
+
const NAMED_HOOK_MATCHER_SUBJECTS = {
|
|
15014
|
+
"tool.execute.before": "input.tool",
|
|
15015
|
+
"tool.execute.after": "input.tool",
|
|
15016
|
+
"experimental.session.compacting": null
|
|
15017
|
+
};
|
|
14544
15018
|
function escapeForTemplateLiteral(command) {
|
|
14545
15019
|
return command.replace(/\\/g, "\\\\").replace(/`/g, "\\`").replace(/\$\{/g, "\\${");
|
|
14546
15020
|
}
|
|
@@ -14573,7 +15047,7 @@ function collectOpencodeStyleHandlers({ effectiveHooks, eventMap, namedEventHand
|
|
|
14573
15047
|
});
|
|
14574
15048
|
}
|
|
14575
15049
|
if (handlers.length > 0) {
|
|
14576
|
-
const grouped =
|
|
15050
|
+
const grouped = Object.hasOwn(NAMED_HOOK_MATCHER_SUBJECTS, toolEvent) ? namedEventHandlers : genericEventHandlers;
|
|
14577
15051
|
const existing = grouped[toolEvent];
|
|
14578
15052
|
if (existing) existing.push(...handlers);
|
|
14579
15053
|
else grouped[toolEvent] = handlers;
|
|
@@ -14602,14 +15076,15 @@ function buildGenericEventBodyLines(genericEventHandlers) {
|
|
|
14602
15076
|
function buildNamedEventBodyLines(namedEventHandlers) {
|
|
14603
15077
|
const bodyLines = [];
|
|
14604
15078
|
for (const [eventName, handlers] of Object.entries(namedEventHandlers)) {
|
|
15079
|
+
const matcherSubject = NAMED_HOOK_MATCHER_SUBJECTS[eventName] ?? null;
|
|
14605
15080
|
bodyLines.push(` "${eventName}": async (input) => {`);
|
|
14606
15081
|
for (const handler of handlers) {
|
|
14607
15082
|
const escapedCommand = escapeForTemplateLiteral(handler.command);
|
|
14608
|
-
if (handler.matcher) {
|
|
15083
|
+
if (handler.matcher && matcherSubject !== null) {
|
|
14609
15084
|
const safeMatcher = validateAndSanitizeMatcher(handler.matcher);
|
|
14610
15085
|
bodyLines.push(" {");
|
|
14611
15086
|
bodyLines.push(` const __re = new RegExp("${safeMatcher}");`);
|
|
14612
|
-
bodyLines.push(` if (__re.test(
|
|
15087
|
+
bodyLines.push(` if (__re.test(${matcherSubject})) {`);
|
|
14613
15088
|
bodyLines.push(` await $\`${escapedCommand}\`;`);
|
|
14614
15089
|
bodyLines.push(" }");
|
|
14615
15090
|
bodyLines.push(" }");
|
|
@@ -16245,7 +16720,7 @@ const toolHooksFactories = /* @__PURE__ */ new Map([
|
|
|
16245
16720
|
class: CopilotHooks,
|
|
16246
16721
|
meta: {
|
|
16247
16722
|
supportsProject: true,
|
|
16248
|
-
supportsGlobal:
|
|
16723
|
+
supportsGlobal: true,
|
|
16249
16724
|
supportsImport: true
|
|
16250
16725
|
},
|
|
16251
16726
|
supportedEvents: COPILOT_HOOK_EVENTS,
|
|
@@ -18722,8 +19197,20 @@ const RULESYNC_TO_CODEX_FIELD_MAP = {
|
|
|
18722
19197
|
disabledTools: "disabled_tools",
|
|
18723
19198
|
envVars: "env_vars"
|
|
18724
19199
|
};
|
|
19200
|
+
const RULESYNC_TO_CODEX_SCALAR_FIELD_MAP = { experimentalEnvironment: "experimental_environment" };
|
|
19201
|
+
const CODEX_TO_RULESYNC_SCALAR_FIELD_MAP = Object.fromEntries(Object.entries(RULESYNC_TO_CODEX_SCALAR_FIELD_MAP).map(([canonical, codex]) => [codex, canonical]));
|
|
18725
19202
|
const MAX_REMOVE_EMPTY_ENTRIES_DEPTH$1 = 32;
|
|
18726
19203
|
/**
|
|
19204
|
+
* `env_vars` entries are either a bare variable name or `{ name, source }`,
|
|
19205
|
+
* where `source = "remote"` reads the variable from the remote executor
|
|
19206
|
+
* environment. The other renamed keys (`enabled_tools`, `disabled_tools`) stay
|
|
19207
|
+
* plain string arrays, so the widened check applies to `env_vars` only.
|
|
19208
|
+
* @see https://learn.chatgpt.com/docs/extend/mcp
|
|
19209
|
+
*/
|
|
19210
|
+
function isValidRenamedArray(key, value) {
|
|
19211
|
+
return key === "env_vars" || key === "envVars" ? isEnvVarEntryArray(value) : isStringArray$1(value);
|
|
19212
|
+
}
|
|
19213
|
+
/**
|
|
18727
19214
|
* Translate a server's `oauth` table from the canonical rulesync shape (Claude
|
|
18728
19215
|
* Code style camelCase) into the shape Codex CLI understands. Codex expects the
|
|
18729
19216
|
* OAuth client id under snake_case `client_id`; without it `codex mcp login`
|
|
@@ -18783,8 +19270,12 @@ function convertFromCodexFormat(codexMcp) {
|
|
|
18783
19270
|
} else if (key === "oauth" && isRecord(value)) converted[key] = mapOauthFromCodex(value);
|
|
18784
19271
|
else if (Object.hasOwn(CODEX_TO_RULESYNC_FIELD_MAP, key)) {
|
|
18785
19272
|
const mappedKey = CODEX_TO_RULESYNC_FIELD_MAP[key];
|
|
18786
|
-
if (mappedKey) if (
|
|
19273
|
+
if (mappedKey) if (isValidRenamedArray(key, value)) converted[mappedKey] = value;
|
|
18787
19274
|
else warnWithFallback(void 0, `Ignored malformed array for ${key} in MCP server ${name}`);
|
|
19275
|
+
} else if (Object.hasOwn(CODEX_TO_RULESYNC_SCALAR_FIELD_MAP, key)) {
|
|
19276
|
+
const mappedKey = CODEX_TO_RULESYNC_SCALAR_FIELD_MAP[key];
|
|
19277
|
+
if (mappedKey) if (typeof value === "string") converted[mappedKey] = value;
|
|
19278
|
+
else warnWithFallback(void 0, `Ignored malformed value for ${key} in MCP server ${name}: expected a string`);
|
|
18788
19279
|
} else converted[key] = value;
|
|
18789
19280
|
}
|
|
18790
19281
|
result[name] = converted;
|
|
@@ -18806,8 +19297,12 @@ function convertToCodexFormat(mcpServers) {
|
|
|
18806
19297
|
} else if (key === "oauth" && isRecord(value)) converted[key] = mapOauthToCodex(value);
|
|
18807
19298
|
else if (Object.hasOwn(RULESYNC_TO_CODEX_FIELD_MAP, key)) {
|
|
18808
19299
|
const mappedKey = RULESYNC_TO_CODEX_FIELD_MAP[key];
|
|
18809
|
-
if (mappedKey) if (
|
|
19300
|
+
if (mappedKey) if (isValidRenamedArray(key, value)) converted[mappedKey] = value;
|
|
18810
19301
|
else warnWithFallback(void 0, `[CodexCliMcp] Skipping invalid value type for mapped key '${key}': expected string array, got ${typeof value}`);
|
|
19302
|
+
} else if (Object.hasOwn(RULESYNC_TO_CODEX_SCALAR_FIELD_MAP, key)) {
|
|
19303
|
+
const mappedKey = RULESYNC_TO_CODEX_SCALAR_FIELD_MAP[key];
|
|
19304
|
+
if (mappedKey) if (typeof value === "string") converted[mappedKey] = value;
|
|
19305
|
+
else warnWithFallback(void 0, `[CodexCliMcp] Skipping invalid value type for mapped key '${key}': expected string, got ${typeof value}`);
|
|
18811
19306
|
} else converted[key] = value;
|
|
18812
19307
|
}
|
|
18813
19308
|
const previousName = originalNames.get(codexName);
|
|
@@ -18878,7 +19373,9 @@ var CodexcliMcp = class CodexcliMcp extends ToolMcp {
|
|
|
18878
19373
|
const rawServer = isRecord(rawMcpServers) ? rawMcpServers[serverName] : void 0;
|
|
18879
19374
|
return [serverName, {
|
|
18880
19375
|
...serverConfig,
|
|
18881
|
-
...isRecord(rawServer) &&
|
|
19376
|
+
...isRecord(rawServer) && isEnvVarEntryArray(rawServer.envVars) ? { envVars: rawServer.envVars } : {},
|
|
19377
|
+
...isRecord(rawServer) && typeof rawServer.experimental_environment === "string" ? { experimentalEnvironment: rawServer.experimental_environment } : {},
|
|
19378
|
+
...isRecord(rawServer) && typeof rawServer.experimentalEnvironment === "string" ? { experimentalEnvironment: rawServer.experimentalEnvironment } : {}
|
|
18882
19379
|
}];
|
|
18883
19380
|
})));
|
|
18884
19381
|
const filteredMcpServers = this.removeEmptyEntries(converted);
|
|
@@ -18949,9 +19446,6 @@ var CodexcliMcp = class CodexcliMcp extends ToolMcp {
|
|
|
18949
19446
|
};
|
|
18950
19447
|
//#endregion
|
|
18951
19448
|
//#region src/features/mcp/copilot-mcp.ts
|
|
18952
|
-
function convertToCopilotFormat(mcpServers) {
|
|
18953
|
-
return { servers: mcpServers };
|
|
18954
|
-
}
|
|
18955
19449
|
function convertFromCopilotFormat(copilotConfig) {
|
|
18956
19450
|
return copilotConfig.servers ?? {};
|
|
18957
19451
|
}
|
|
@@ -18980,13 +19474,21 @@ var CopilotMcp = class CopilotMcp extends ToolMcp {
|
|
|
18980
19474
|
validate
|
|
18981
19475
|
});
|
|
18982
19476
|
}
|
|
18983
|
-
static fromRulesyncMcp({ outputRoot = process.cwd(), rulesyncMcp, validate = true }) {
|
|
18984
|
-
const
|
|
19477
|
+
static async fromRulesyncMcp({ outputRoot = process.cwd(), rulesyncMcp, validate = true }) {
|
|
19478
|
+
const paths = this.getSettablePaths();
|
|
19479
|
+
const filePath = (0, node_path.join)(outputRoot, paths.relativeDirPath, paths.relativeFilePath);
|
|
19480
|
+
const existingContent = await readFileContentOrNull(filePath) ?? "{}";
|
|
18985
19481
|
return new CopilotMcp({
|
|
18986
19482
|
outputRoot,
|
|
18987
|
-
relativeDirPath:
|
|
18988
|
-
relativeFilePath:
|
|
18989
|
-
fileContent:
|
|
19483
|
+
relativeDirPath: paths.relativeDirPath,
|
|
19484
|
+
relativeFilePath: paths.relativeFilePath,
|
|
19485
|
+
fileContent: applySharedConfigPatch({
|
|
19486
|
+
fileKey: sharedConfigFileKey(paths),
|
|
19487
|
+
feature: "mcp",
|
|
19488
|
+
existingContent,
|
|
19489
|
+
patch: { servers: rulesyncMcp.getMcpServers() },
|
|
19490
|
+
filePath
|
|
19491
|
+
}),
|
|
18990
19492
|
validate
|
|
18991
19493
|
});
|
|
18992
19494
|
}
|
|
@@ -20669,7 +21171,7 @@ const KILO_TOGGLE_KEPT_KEYS = /* @__PURE__ */ new Set([
|
|
|
20669
21171
|
* impossible to start — no command, no URL — so it is written as the toggle it
|
|
20670
21172
|
* resembles rather than dropped, but the loss is said out loud.
|
|
20671
21173
|
*/
|
|
20672
|
-
function warnAboutToggleDroppedKeys(serverName, serverConfig, logger) {
|
|
21174
|
+
function warnAboutToggleDroppedKeys$1(serverName, serverConfig, logger) {
|
|
20673
21175
|
const dropped = Object.keys(serverConfig).filter((key) => !KILO_TOGGLE_KEPT_KEYS.has(key));
|
|
20674
21176
|
if (dropped.length === 0) return;
|
|
20675
21177
|
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 +21184,7 @@ function convertServerToKiloFormat(serverName, serverConfig, existingEntry, logg
|
|
|
20682
21184
|
if (declaresNoTransport(serverConfig)) {
|
|
20683
21185
|
if (serverConfig.disabled === void 0) {
|
|
20684
21186
|
if (existingEntry !== void 0 && !isKiloTransportServer(existingEntry)) {
|
|
20685
|
-
warnAboutToggleDroppedKeys(serverName, serverConfig, logger);
|
|
21187
|
+
warnAboutToggleDroppedKeys$1(serverName, serverConfig, logger);
|
|
20686
21188
|
return existingEntry;
|
|
20687
21189
|
}
|
|
20688
21190
|
return warnAndSkipMcpServer({
|
|
@@ -20692,7 +21194,7 @@ function convertServerToKiloFormat(serverName, serverConfig, existingEntry, logg
|
|
|
20692
21194
|
logger
|
|
20693
21195
|
});
|
|
20694
21196
|
}
|
|
20695
|
-
warnAboutToggleDroppedKeys(serverName, serverConfig, logger);
|
|
21197
|
+
warnAboutToggleDroppedKeys$1(serverName, serverConfig, logger);
|
|
20696
21198
|
return { enabled: !serverConfig.disabled };
|
|
20697
21199
|
}
|
|
20698
21200
|
if (isRemoteMcpServer(serverConfig)) {
|
|
@@ -21318,13 +21820,49 @@ const OpencodeMcpRemoteServerSchema = zod_mini.z.looseObject({
|
|
|
21318
21820
|
headers: zod_mini.z.optional(zod_mini.z.record(zod_mini.z.string(), zod_mini.z.string())),
|
|
21319
21821
|
enabled: zod_mini.z._default(zod_mini.z.boolean(), true)
|
|
21320
21822
|
});
|
|
21321
|
-
const
|
|
21823
|
+
const OPENCODE_PASSTHROUGH_SERVER_FIELDS = ["timeout", "oauth"];
|
|
21824
|
+
const OPENCODE_MCP_TRANSPORT_KEYS = [
|
|
21825
|
+
...Object.keys(OpencodeMcpLocalServerSchema.def.shape),
|
|
21826
|
+
...Object.keys(OpencodeMcpRemoteServerSchema.def.shape),
|
|
21827
|
+
...OPENCODE_PASSTHROUGH_SERVER_FIELDS
|
|
21828
|
+
].filter((key) => key !== "enabled");
|
|
21829
|
+
/**
|
|
21830
|
+
* A bare toggle entry: `{"enabled": <bool>}` with no transport of its own,
|
|
21831
|
+
* disabling a server another config layer defines. It is the third member of
|
|
21832
|
+
* OpenCode's own `mcp` union in the published schema, described in-source as
|
|
21833
|
+
* "the legacy `{ enabled: false }` form used to disable a server". Without this
|
|
21834
|
+
* arm the union rejects the entry and the whole MCP import aborts — taking
|
|
21835
|
+
* every valid server in the same file down with it.
|
|
21836
|
+
*
|
|
21837
|
+
* Loose, unlike the two transport arms, so a key OpenCode adds to a toggle
|
|
21838
|
+
* later does not bring that abort back — but refined to reject anything
|
|
21839
|
+
* carrying a key only a transport entry has. A plain loose arm would sit under
|
|
21840
|
+
* a malformed `local` or `remote` entry that happens to carry `enabled` and
|
|
21841
|
+
* swallow it, dropping its command, URL, or headers without a word instead of
|
|
21842
|
+
* failing the way it does today. Mirrors {@link KiloMcpToggleSchema}.
|
|
21843
|
+
*
|
|
21844
|
+
* @see https://opencode.ai/config.json
|
|
21845
|
+
*/
|
|
21846
|
+
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"));
|
|
21847
|
+
const OpencodeMcpServerSchema = zod_mini.z.union([
|
|
21848
|
+
OpencodeMcpLocalServerSchema,
|
|
21849
|
+
OpencodeMcpRemoteServerSchema,
|
|
21850
|
+
OpencodeMcpToggleSchema
|
|
21851
|
+
]);
|
|
21322
21852
|
const OpencodeConfigSchema = zod_mini.z.looseObject({
|
|
21323
21853
|
$schema: zod_mini.z.optional(zod_mini.z.string()),
|
|
21324
21854
|
mcp: zod_mini.z.optional(zod_mini.z.record(zod_mini.z.string(), OpencodeMcpServerSchema)),
|
|
21325
21855
|
tools: zod_mini.z.optional(zod_mini.z.record(zod_mini.z.string(), zod_mini.z.boolean()))
|
|
21326
21856
|
});
|
|
21327
21857
|
/**
|
|
21858
|
+
* Tell the two transport arms from a toggle entry. Both carry a `type` literal
|
|
21859
|
+
* and a toggle never does — the schema refuses one that tries — but the toggle
|
|
21860
|
+
* arm is loose, so its index signature hides that from `in` narrowing.
|
|
21861
|
+
*/
|
|
21862
|
+
function isOpencodeTransportServer(server) {
|
|
21863
|
+
return server.type === "local" || server.type === "remote";
|
|
21864
|
+
}
|
|
21865
|
+
/**
|
|
21328
21866
|
* Convert OpenCode native format back to standard MCP format
|
|
21329
21867
|
* - type: "local" -> "stdio", "remote" -> "sse"
|
|
21330
21868
|
* - command (array) -> command (first element) + args (rest)
|
|
@@ -21349,19 +21887,31 @@ function convertFromOpencodeFormat(opencodeMcp, tools) {
|
|
|
21349
21887
|
...convertOpencodeServers(opencodeMcp, tools)
|
|
21350
21888
|
};
|
|
21351
21889
|
}
|
|
21890
|
+
/** Split the shared top-level `tools` map into this server's own two lists. */
|
|
21891
|
+
function splitOpencodeServerTools(serverName, tools) {
|
|
21892
|
+
const enabledTools = [];
|
|
21893
|
+
const disabledTools = [];
|
|
21894
|
+
const prefix = `${serverName}_`;
|
|
21895
|
+
for (const [toolName, enabled] of Object.entries(tools ?? {})) {
|
|
21896
|
+
if (!toolName.startsWith(prefix)) continue;
|
|
21897
|
+
const toolSuffix = toolName.slice(prefix.length);
|
|
21898
|
+
(enabled ? enabledTools : disabledTools).push(toolSuffix);
|
|
21899
|
+
}
|
|
21900
|
+
return {
|
|
21901
|
+
enabledTools,
|
|
21902
|
+
disabledTools
|
|
21903
|
+
};
|
|
21904
|
+
}
|
|
21352
21905
|
function convertOpencodeServers(opencodeMcp, tools) {
|
|
21353
21906
|
return Object.fromEntries(Object.entries(opencodeMcp).map(([serverName, serverConfig]) => {
|
|
21354
21907
|
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
|
-
}
|
|
21908
|
+
const { enabledTools, disabledTools } = splitOpencodeServerTools(serverName, tools);
|
|
21909
|
+
if (!isOpencodeTransportServer(serverConfig)) return [serverName, {
|
|
21910
|
+
...extraFields,
|
|
21911
|
+
disabled: serverConfig.enabled === false,
|
|
21912
|
+
...enabledTools.length > 0 && { enabledTools },
|
|
21913
|
+
...disabledTools.length > 0 && { disabledTools }
|
|
21914
|
+
}];
|
|
21365
21915
|
if (serverConfig.type === "remote") return [serverName, {
|
|
21366
21916
|
...extraFields,
|
|
21367
21917
|
type: "sse",
|
|
@@ -21391,7 +21941,20 @@ function convertOpencodeServers(opencodeMcp, tools) {
|
|
|
21391
21941
|
}];
|
|
21392
21942
|
}));
|
|
21393
21943
|
}
|
|
21394
|
-
const
|
|
21944
|
+
const OPENCODE_TOGGLE_KEPT_KEYS = /* @__PURE__ */ new Set([
|
|
21945
|
+
"disabled",
|
|
21946
|
+
"enabledTools",
|
|
21947
|
+
"disabledTools"
|
|
21948
|
+
]);
|
|
21949
|
+
/**
|
|
21950
|
+
* Warn about the fields a transport-less server loses by being written as a
|
|
21951
|
+
* bare toggle, so the drop is never silent.
|
|
21952
|
+
*/
|
|
21953
|
+
function warnAboutToggleDroppedKeys(serverName, serverConfig, logger) {
|
|
21954
|
+
const dropped = Object.keys(serverConfig).filter((key) => !OPENCODE_TOGGLE_KEPT_KEYS.has(key));
|
|
21955
|
+
if (dropped.length === 0) return;
|
|
21956
|
+
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.`);
|
|
21957
|
+
}
|
|
21395
21958
|
/**
|
|
21396
21959
|
* Convert standard MCP format to OpenCode native format
|
|
21397
21960
|
* - type: "stdio" -> "local", "sse"/"http" -> "remote"
|
|
@@ -21401,11 +21964,27 @@ const OPENCODE_PASSTHROUGH_SERVER_FIELDS = ["timeout", "oauth"];
|
|
|
21401
21964
|
* - enabledTools/disabledTools -> top-level tools map (with server name prefix)
|
|
21402
21965
|
* - OpenCode-supported extras (timeout, oauth) -> passed through verbatim
|
|
21403
21966
|
*/
|
|
21404
|
-
function convertServerToOpencodeFormat(serverName, serverConfig, logger) {
|
|
21967
|
+
function convertServerToOpencodeFormat(serverName, serverConfig, existingEntry, logger) {
|
|
21405
21968
|
const serverRecord = serverConfig;
|
|
21406
21969
|
const passthrough = {};
|
|
21407
21970
|
for (const key of OPENCODE_PASSTHROUGH_SERVER_FIELDS) if (serverRecord[key] !== void 0) passthrough[key] = serverRecord[key];
|
|
21408
21971
|
const enabled = serverConfig.disabled !== void 0 ? !serverConfig.disabled : true;
|
|
21972
|
+
if (declaresNoTransport(serverConfig)) {
|
|
21973
|
+
if (serverConfig.disabled === void 0) {
|
|
21974
|
+
if (existingEntry !== void 0 && !isOpencodeTransportServer(existingEntry)) {
|
|
21975
|
+
warnAboutToggleDroppedKeys(serverName, serverConfig, logger);
|
|
21976
|
+
return existingEntry;
|
|
21977
|
+
}
|
|
21978
|
+
return warnAndSkipMcpServer({
|
|
21979
|
+
toolName: "OpenCode",
|
|
21980
|
+
serverName,
|
|
21981
|
+
reason: "no transport and no enabled state, so there is nothing to toggle",
|
|
21982
|
+
logger
|
|
21983
|
+
});
|
|
21984
|
+
}
|
|
21985
|
+
warnAboutToggleDroppedKeys(serverName, serverConfig, logger);
|
|
21986
|
+
return { enabled };
|
|
21987
|
+
}
|
|
21409
21988
|
if (isRemoteMcpServer(serverConfig)) {
|
|
21410
21989
|
const url = resolveRemoteMcpUrl(serverConfig);
|
|
21411
21990
|
if (url === void 0) return warnAndSkipMcpServer({
|
|
@@ -21426,7 +22005,7 @@ function convertServerToOpencodeFormat(serverName, serverConfig, logger) {
|
|
|
21426
22005
|
if (commandArray.length === 0) return warnAndSkipMcpServer({
|
|
21427
22006
|
toolName: "OpenCode",
|
|
21428
22007
|
serverName,
|
|
21429
|
-
reason:
|
|
22008
|
+
reason: "a local transport but no command",
|
|
21430
22009
|
logger
|
|
21431
22010
|
});
|
|
21432
22011
|
return {
|
|
@@ -21438,11 +22017,11 @@ function convertServerToOpencodeFormat(serverName, serverConfig, logger) {
|
|
|
21438
22017
|
...serverConfig.cwd && { cwd: serverConfig.cwd }
|
|
21439
22018
|
};
|
|
21440
22019
|
}
|
|
21441
|
-
function convertToOpencodeFormat(mcpServers, logger) {
|
|
22020
|
+
function convertToOpencodeFormat(mcpServers, existingMcp, logger) {
|
|
21442
22021
|
const tools = {};
|
|
21443
22022
|
return {
|
|
21444
22023
|
mcp: Object.fromEntries(Object.entries(mcpServers).map(([serverName, serverConfig]) => {
|
|
21445
|
-
const converted = convertServerToOpencodeFormat(serverName, serverConfig, logger);
|
|
22024
|
+
const converted = convertServerToOpencodeFormat(serverName, serverConfig, existingMcp[serverName], logger);
|
|
21446
22025
|
if (serverConfig.enabledTools) for (const tool of serverConfig.enabledTools) tools[`${serverName}_${tool}`] = true;
|
|
21447
22026
|
if (serverConfig.disabledTools) for (const tool of serverConfig.disabledTools) tools[`${serverName}_${tool}`] = false;
|
|
21448
22027
|
return converted === null ? null : [serverName, converted];
|
|
@@ -21512,10 +22091,12 @@ var OpencodeMcp = class OpencodeMcp extends ToolMcp {
|
|
|
21512
22091
|
fileContent = await readFileContentOrNull(jsonPath);
|
|
21513
22092
|
if (fileContent) relativeFilePath = OPENCODE_JSON_FILE_NAME;
|
|
21514
22093
|
}
|
|
21515
|
-
const
|
|
22094
|
+
const transformedServers = convertEnvVarRefsToToolFormat({
|
|
21516
22095
|
mcpServers: rulesyncMcp.getMcpServers(),
|
|
21517
22096
|
replacement: "{env:$1}"
|
|
21518
|
-
})
|
|
22097
|
+
});
|
|
22098
|
+
const existingMcp = OpencodeConfigSchema.safeParse((0, jsonc_parser.parse)(fileContent || "{}"));
|
|
22099
|
+
const { mcp: convertedMcp, tools: mcpTools } = convertToOpencodeFormat(transformedServers, (existingMcp.success ? existingMcp.data.mcp : void 0) ?? {}, logger);
|
|
21519
22100
|
return new OpencodeMcp({
|
|
21520
22101
|
outputRoot,
|
|
21521
22102
|
relativeDirPath: basePaths.relativeDirPath,
|
|
@@ -23825,10 +24406,11 @@ function buildPermissionEntry$1(toolName, pattern) {
|
|
|
23825
24406
|
* file (global scope only). The file holds other CLI settings besides
|
|
23826
24407
|
* permissions, so it is never deleted.
|
|
23827
24408
|
*
|
|
23828
|
-
*
|
|
23829
|
-
* `toolPermission` (the global autonomy preset)
|
|
23830
|
-
*
|
|
23831
|
-
*
|
|
24409
|
+
* Four CLI-only autonomy/sandbox knobs outside the allow/ask/deny arrays —
|
|
24410
|
+
* `toolPermission` (the global autonomy preset), `enableTerminalSandbox`,
|
|
24411
|
+
* `artifactReviewPolicy` and `allowNonWorkspaceAccess` — are authored and
|
|
24412
|
+
* round-tripped through the `antigravity-cli` permissions override (see
|
|
24413
|
+
* `AntigravityCliPermissionsOverrideSchema`).
|
|
23832
24414
|
*/
|
|
23833
24415
|
var AntigravityCliPermissions = class AntigravityCliPermissions extends ToolPermissions {
|
|
23834
24416
|
constructor(params) {
|
|
@@ -23892,6 +24474,8 @@ var AntigravityCliPermissions = class AntigravityCliPermissions extends ToolPerm
|
|
|
23892
24474
|
const override = config["antigravity-cli"];
|
|
23893
24475
|
if (override?.toolPermission !== void 0) merged.toolPermission = override.toolPermission;
|
|
23894
24476
|
if (override?.enableTerminalSandbox !== void 0) merged.enableTerminalSandbox = override.enableTerminalSandbox;
|
|
24477
|
+
if (override?.artifactReviewPolicy !== void 0) merged.artifactReviewPolicy = override.artifactReviewPolicy;
|
|
24478
|
+
if (override?.allowNonWorkspaceAccess !== void 0) merged.allowNonWorkspaceAccess = override.allowNonWorkspaceAccess;
|
|
23895
24479
|
const fileContent = JSON.stringify(merged, null, 2);
|
|
23896
24480
|
return new AntigravityCliPermissions({
|
|
23897
24481
|
outputRoot,
|
|
@@ -23918,6 +24502,8 @@ var AntigravityCliPermissions = class AntigravityCliPermissions extends ToolPerm
|
|
|
23918
24502
|
const override = {};
|
|
23919
24503
|
if (typeof settings.toolPermission === "string") override.toolPermission = settings.toolPermission;
|
|
23920
24504
|
if (typeof settings.enableTerminalSandbox === "boolean") override.enableTerminalSandbox = settings.enableTerminalSandbox;
|
|
24505
|
+
if (typeof settings.artifactReviewPolicy === "string") override.artifactReviewPolicy = settings.artifactReviewPolicy;
|
|
24506
|
+
if (typeof settings.allowNonWorkspaceAccess === "boolean") override.allowNonWorkspaceAccess = settings.allowNonWorkspaceAccess;
|
|
23921
24507
|
const result = { ...config };
|
|
23922
24508
|
if (Object.keys(override).length > 0) result["antigravity-cli"] = override;
|
|
23923
24509
|
return this.toRulesyncPermissionsDefault({ fileContent: JSON.stringify(result, null, 2) });
|
|
@@ -24721,12 +25307,57 @@ function parseClaudePermissionEntry(entry) {
|
|
|
24721
25307
|
};
|
|
24722
25308
|
}
|
|
24723
25309
|
/**
|
|
25310
|
+
* Claude Code's file permission checks match only `Edit(path)` and `Read(path)`
|
|
25311
|
+
* rules. A `Write(path)`, `NotebookEdit(path)` or `Glob(path)` rule "is accepted
|
|
25312
|
+
* but never matched by those checks, so Claude Code warns at startup for each
|
|
25313
|
+
* allow, deny, or ask rule in one of these unmatched forms" — so a canonical
|
|
25314
|
+
* `write`/`notebookedit`/`glob` rule with a pattern is emitted in the form the
|
|
25315
|
+
* docs prescribe instead. A tool-name rule with no path is unaffected: it
|
|
25316
|
+
* matches the tool everywhere and produces no warning.
|
|
25317
|
+
* @see https://code.claude.com/docs/en/permissions
|
|
25318
|
+
*/
|
|
25319
|
+
function isPlainRecord(value) {
|
|
25320
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
25321
|
+
}
|
|
25322
|
+
/**
|
|
25323
|
+
* Merge `patch` into `base`, recursing into plain objects so a sibling key at
|
|
25324
|
+
* any depth survives. Arrays and scalars are replaced, since a list the author
|
|
25325
|
+
* states is the list they mean.
|
|
25326
|
+
*/
|
|
25327
|
+
function deepMergeRecords(base, patch) {
|
|
25328
|
+
const merged = { ...base };
|
|
25329
|
+
for (const [key, value] of Object.entries(patch)) {
|
|
25330
|
+
if (PROTOTYPE_POLLUTION_KEYS.has(key)) continue;
|
|
25331
|
+
const existing = merged[key];
|
|
25332
|
+
merged[key] = isPlainRecord(existing) && isPlainRecord(value) ? deepMergeRecords(existing, value) : value;
|
|
25333
|
+
}
|
|
25334
|
+
return merged;
|
|
25335
|
+
}
|
|
25336
|
+
const CLAUDE_PATH_RULE_ALIASES = {
|
|
25337
|
+
Write: "Edit",
|
|
25338
|
+
NotebookEdit: "Edit",
|
|
25339
|
+
Glob: "Read"
|
|
25340
|
+
};
|
|
25341
|
+
/**
|
|
24724
25342
|
* Build a Claude Code permission entry like "Bash(npm run *)".
|
|
24725
25343
|
* If the pattern is "*", returns just the tool name.
|
|
24726
25344
|
*/
|
|
24727
25345
|
function buildClaudePermissionEntry(toolName, pattern) {
|
|
24728
25346
|
if (pattern === "*") return toolName;
|
|
24729
|
-
return `${toolName}(${pattern})`;
|
|
25347
|
+
return `${CLAUDE_PATH_RULE_ALIASES[toolName] ?? toolName}(${pattern})`;
|
|
25348
|
+
}
|
|
25349
|
+
/**
|
|
25350
|
+
* The Claude tool names the canonical config manages. Deliberately the tool
|
|
25351
|
+
* names the categories map to and *not* the aliases a path rule is rewritten
|
|
25352
|
+
* to: claiming `Edit` because a `write` rule exists would sweep away the
|
|
25353
|
+
* `Read`/`Edit` entries the ignore feature and the user wrote in the same file.
|
|
25354
|
+
* The rewritten entries are still rulesync's to place — `applyPermissions`
|
|
25355
|
+
* replaces an entry this run emits wherever it currently sits — and the
|
|
25356
|
+
* original name stays claimed so an entry an older rulesync wrote in the warned
|
|
25357
|
+
* form is cleaned up on the next generate.
|
|
25358
|
+
*/
|
|
25359
|
+
function managedClaudeToolNames(config) {
|
|
25360
|
+
return new Set(Object.keys(config.permission).map((category) => toClaudeToolName(category)));
|
|
24730
25361
|
}
|
|
24731
25362
|
var ClaudecodePermissions = class ClaudecodePermissions extends ToolPermissions {
|
|
24732
25363
|
constructor(params) {
|
|
@@ -24766,7 +25397,10 @@ var ClaudecodePermissions = class ClaudecodePermissions extends ToolPermissions
|
|
|
24766
25397
|
throw new Error(`Failed to parse existing Claude settings at ${filePath}: ${formatError(error)}`, { cause: error });
|
|
24767
25398
|
}
|
|
24768
25399
|
const config = rulesyncPermissions.getJson();
|
|
24769
|
-
const { allow, ask, deny } = convertRulesyncToClaudePermissions(
|
|
25400
|
+
const { allow, ask, deny } = convertRulesyncToClaudePermissions({
|
|
25401
|
+
config,
|
|
25402
|
+
logger
|
|
25403
|
+
});
|
|
24770
25404
|
const overridePermissions = config.claudecode?.permissions;
|
|
24771
25405
|
if (overridePermissions && typeof overridePermissions === "object") {
|
|
24772
25406
|
const { allow: _a, ask: _k, deny: _d, ...nonListFields } = overridePermissions;
|
|
@@ -24775,7 +25409,9 @@ var ClaudecodePermissions = class ClaudecodePermissions extends ToolPermissions
|
|
|
24775
25409
|
...nonListFields
|
|
24776
25410
|
};
|
|
24777
25411
|
}
|
|
24778
|
-
const
|
|
25412
|
+
const overrideSandbox = config.claudecode?.sandbox;
|
|
25413
|
+
if (isPlainRecord(overrideSandbox)) settings.sandbox = deepMergeRecords(isPlainRecord(settings.sandbox) ? settings.sandbox : {}, overrideSandbox);
|
|
25414
|
+
const managedToolNames = managedClaudeToolNames(config);
|
|
24779
25415
|
const merged = applyPermissions({
|
|
24780
25416
|
settings,
|
|
24781
25417
|
managedToolNames,
|
|
@@ -24809,6 +25445,11 @@ var ClaudecodePermissions = class ClaudecodePermissions extends ToolPermissions
|
|
|
24809
25445
|
});
|
|
24810
25446
|
const { allow: _a, ask: _k, deny: _d, ...nonListFields } = permissions;
|
|
24811
25447
|
if (Object.keys(nonListFields).length > 0) config.claudecode = { permissions: nonListFields };
|
|
25448
|
+
const { sandbox } = settings;
|
|
25449
|
+
if (isPlainRecord(sandbox) && Object.keys(sandbox).length > 0) config.claudecode = {
|
|
25450
|
+
...config.claudecode,
|
|
25451
|
+
sandbox
|
|
25452
|
+
};
|
|
24812
25453
|
return this.toRulesyncPermissionsDefault({ fileContent: JSON.stringify(config, null, 2) });
|
|
24813
25454
|
}
|
|
24814
25455
|
validate() {
|
|
@@ -24830,14 +25471,18 @@ var ClaudecodePermissions = class ClaudecodePermissions extends ToolPermissions
|
|
|
24830
25471
|
/**
|
|
24831
25472
|
* Convert rulesync permissions config to Claude Code allow/ask/deny arrays.
|
|
24832
25473
|
*/
|
|
24833
|
-
function convertRulesyncToClaudePermissions(config) {
|
|
25474
|
+
function convertRulesyncToClaudePermissions({ config, logger }) {
|
|
24834
25475
|
const allow = [];
|
|
24835
25476
|
const ask = [];
|
|
24836
25477
|
const deny = [];
|
|
25478
|
+
const actionByEntry = /* @__PURE__ */ new Map();
|
|
24837
25479
|
for (const [category, rules] of Object.entries(config.permission)) {
|
|
24838
25480
|
const claudeToolName = toClaudeToolName(category);
|
|
24839
25481
|
for (const [pattern, action] of Object.entries(rules)) {
|
|
24840
25482
|
const entry = buildClaudePermissionEntry(claudeToolName, pattern);
|
|
25483
|
+
const previous = actionByEntry.get(entry);
|
|
25484
|
+
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.`);
|
|
25485
|
+
actionByEntry.set(entry, action);
|
|
24841
25486
|
switch (action) {
|
|
24842
25487
|
case "allow":
|
|
24843
25488
|
allow.push(entry);
|
|
@@ -25650,19 +26295,24 @@ function mapBashActionToDecision(action) {
|
|
|
25650
26295
|
//#endregion
|
|
25651
26296
|
//#region src/features/permissions/copilot-permissions.ts
|
|
25652
26297
|
/**
|
|
25653
|
-
* The flat, dotted VS Code setting
|
|
25654
|
-
* settings with dotted keys flat at the
|
|
25655
|
-
* literal key — not a nested
|
|
26298
|
+
* The flat, dotted VS Code setting keys this adapter manages, one per canonical
|
|
26299
|
+
* permission category. VS Code stores settings with dotted keys flat at the
|
|
26300
|
+
* document top level, so each is a single literal key — not a nested
|
|
26301
|
+
* `chat.tools.terminal` path. All three share the same
|
|
26302
|
+
* pattern-to-boolean shape, so one conversion covers them.
|
|
26303
|
+
*
|
|
26304
|
+
* The canonical `read` and `write` categories stay unmapped: VS Code has no
|
|
26305
|
+
* read-approval surface, and folding `write` into the edits map alongside
|
|
26306
|
+
* `edit` would make the two indistinguishable on import.
|
|
26307
|
+
*
|
|
25656
26308
|
* @see https://code.visualstudio.com/docs/agents/approvals
|
|
26309
|
+
* @see https://code.visualstudio.com/docs/copilot/chat/review-code-edits
|
|
25657
26310
|
*/
|
|
25658
|
-
const
|
|
25659
|
-
|
|
25660
|
-
|
|
25661
|
-
|
|
25662
|
-
|
|
25663
|
-
* …) have no terminal-command equivalent and are intentionally not mapped.
|
|
25664
|
-
*/
|
|
25665
|
-
const TERMINAL_CATEGORY = "bash";
|
|
26311
|
+
const AUTO_APPROVE_KEYS = {
|
|
26312
|
+
bash: "chat.tools.terminal.autoApprove",
|
|
26313
|
+
edit: "chat.tools.edits.autoApprove",
|
|
26314
|
+
webfetch: "chat.tools.urls.autoApprove"
|
|
26315
|
+
};
|
|
25666
26316
|
function asAutoApproveMap(value) {
|
|
25667
26317
|
if (!isPlainObject$1(value)) return {};
|
|
25668
26318
|
const result = {};
|
|
@@ -25670,6 +26320,21 @@ function asAutoApproveMap(value) {
|
|
|
25670
26320
|
return result;
|
|
25671
26321
|
}
|
|
25672
26322
|
/**
|
|
26323
|
+
* Render one canonical category's rules as a VS Code auto-approve map. Returns
|
|
26324
|
+
* `undefined` when the category contributes nothing, so the key is retracted
|
|
26325
|
+
* rather than written as an empty object.
|
|
26326
|
+
*
|
|
26327
|
+
* The resulting map replaces the file's existing value wholesale — rulesync
|
|
26328
|
+
* owns these keys, so a rule dropped from the canonical config disappears from
|
|
26329
|
+
* the settings file too.
|
|
26330
|
+
*/
|
|
26331
|
+
function buildAutoApproveValue(rules) {
|
|
26332
|
+
const autoApprove = {};
|
|
26333
|
+
for (const [pattern, action] of Object.entries(rules)) if (action === "allow") autoApprove[pattern] = true;
|
|
26334
|
+
else if (action === "deny") autoApprove[pattern] = false;
|
|
26335
|
+
return Object.keys(autoApprove).length > 0 ? autoApprove : void 0;
|
|
26336
|
+
}
|
|
26337
|
+
/**
|
|
25673
26338
|
* Permissions generator for GitHub Copilot Chat in VS Code.
|
|
25674
26339
|
*
|
|
25675
26340
|
* VS Code has no standalone, environment-agnostic Copilot policy file (like
|
|
@@ -25679,13 +26344,16 @@ function asAutoApproveMap(value) {
|
|
|
25679
26344
|
* many unrelated keys, so reads and writes merge into the existing JSON
|
|
25680
26345
|
* (touching only the one managed key) and the file is never deleted.
|
|
25681
26346
|
*
|
|
25682
|
-
*
|
|
25683
|
-
*
|
|
25684
|
-
* map as: `allow` → `true` (auto-approve), `deny`
|
|
25685
|
-
*
|
|
25686
|
-
*
|
|
25687
|
-
*
|
|
25688
|
-
*
|
|
26347
|
+
* Three canonical categories have a clean, non-lossy representation and are
|
|
26348
|
+
* mapped (see {@link AUTO_APPROVE_KEYS}): `bash`, `edit` and `webfetch`. In
|
|
26349
|
+
* every one, per-pattern rules map as: `allow` → `true` (auto-approve), `deny`
|
|
26350
|
+
* → `false` (VS Code then always prompts — note this is "never auto-approve",
|
|
26351
|
+
* not a hard block), and `ask` → the entry is OMITTED (VS Code falls through to
|
|
26352
|
+
* the same default prompt). A key whose canonical category is absent entirely
|
|
26353
|
+
* is left untouched, so authoring only `bash` rules never disturbs a
|
|
26354
|
+
* hand-written edits or urls map.
|
|
26355
|
+
* Only project scope is modeled: VS Code's user-scope settings.json lives at a
|
|
26356
|
+
* platform-dependent path outside rulesync's home-relative global model.
|
|
25689
26357
|
*/
|
|
25690
26358
|
var CopilotPermissions = class CopilotPermissions extends ToolPermissions {
|
|
25691
26359
|
constructor(params) {
|
|
@@ -25722,11 +26390,13 @@ var CopilotPermissions = class CopilotPermissions extends ToolPermissions {
|
|
|
25722
26390
|
const paths = CopilotPermissions.getSettablePaths();
|
|
25723
26391
|
const filePath = (0, node_path.join)(outputRoot, paths.relativeDirPath, paths.relativeFilePath);
|
|
25724
26392
|
const existingContent = await readFileContentOrNull(filePath) ?? "{}";
|
|
25725
|
-
const
|
|
25726
|
-
const
|
|
25727
|
-
for (const [
|
|
25728
|
-
|
|
25729
|
-
|
|
26393
|
+
const config = rulesyncPermissions.getJson();
|
|
26394
|
+
const patch = {};
|
|
26395
|
+
for (const [category, settingKey] of Object.entries(AUTO_APPROVE_KEYS)) {
|
|
26396
|
+
const rules = config.permission[category];
|
|
26397
|
+
if (rules === void 0) continue;
|
|
26398
|
+
patch[settingKey] = buildAutoApproveValue(rules);
|
|
26399
|
+
}
|
|
25730
26400
|
return new CopilotPermissions({
|
|
25731
26401
|
outputRoot,
|
|
25732
26402
|
relativeDirPath: paths.relativeDirPath,
|
|
@@ -25735,7 +26405,7 @@ var CopilotPermissions = class CopilotPermissions extends ToolPermissions {
|
|
|
25735
26405
|
fileKey: sharedConfigFileKey(paths),
|
|
25736
26406
|
feature: "permissions",
|
|
25737
26407
|
existingContent,
|
|
25738
|
-
patch
|
|
26408
|
+
patch,
|
|
25739
26409
|
filePath
|
|
25740
26410
|
}),
|
|
25741
26411
|
validate: true
|
|
@@ -25754,10 +26424,13 @@ var CopilotPermissions = class CopilotPermissions extends ToolPermissions {
|
|
|
25754
26424
|
} catch (error) {
|
|
25755
26425
|
throw new Error(`Failed to parse Copilot VS Code settings in ${(0, node_path.join)(this.getRelativeDirPath(), this.getRelativeFilePath())}: ${formatError(error)}`, { cause: error });
|
|
25756
26426
|
}
|
|
25757
|
-
const
|
|
25758
|
-
const
|
|
25759
|
-
|
|
25760
|
-
|
|
26427
|
+
const permission = {};
|
|
26428
|
+
for (const [category, settingKey] of Object.entries(AUTO_APPROVE_KEYS)) {
|
|
26429
|
+
const autoApprove = asAutoApproveMap(settings[settingKey]);
|
|
26430
|
+
const rules = {};
|
|
26431
|
+
for (const [pattern, flag] of Object.entries(autoApprove)) rules[pattern] = flag ? "allow" : "deny";
|
|
26432
|
+
if (Object.keys(rules).length > 0) permission[category] = rules;
|
|
26433
|
+
}
|
|
25761
26434
|
return this.toRulesyncPermissionsDefault({ fileContent: JSON.stringify({ permission }, null, 2) });
|
|
25762
26435
|
}
|
|
25763
26436
|
validate() {
|
|
@@ -31777,6 +32450,7 @@ const ClaudecodeSkillFrontmatterSchema = zod_mini.z.looseObject({
|
|
|
31777
32450
|
arguments: zod_mini.z.optional(zod_mini.z.union([zod_mini.z.string(), zod_mini.z.array(zod_mini.z.string())])),
|
|
31778
32451
|
context: zod_mini.z.optional(zod_mini.z.string()),
|
|
31779
32452
|
agent: zod_mini.z.optional(zod_mini.z.string()),
|
|
32453
|
+
background: zod_mini.z.optional(zod_mini.z.boolean()),
|
|
31780
32454
|
hooks: zod_mini.z.optional(zod_mini.z.looseObject({})),
|
|
31781
32455
|
shell: zod_mini.z.optional(zod_mini.z.string()),
|
|
31782
32456
|
"disable-model-invocation": zod_mini.z.optional(zod_mini.z.boolean()),
|
|
@@ -31803,6 +32477,7 @@ function buildClaudecodeSkillFrontmatter({ rulesyncFrontmatter, resolvedDisableM
|
|
|
31803
32477
|
shell: section.shell
|
|
31804
32478
|
};
|
|
31805
32479
|
const definedFields = {
|
|
32480
|
+
background: section.background,
|
|
31806
32481
|
arguments: section.arguments,
|
|
31807
32482
|
hooks: section.hooks,
|
|
31808
32483
|
"disable-model-invocation": resolvedDisableModelInvocation,
|
|
@@ -31880,6 +32555,7 @@ var ClaudecodeSkill = class extends ToolSkill {
|
|
|
31880
32555
|
...frontmatter.arguments !== void 0 && { arguments: frontmatter.arguments },
|
|
31881
32556
|
...frontmatter.context && { context: frontmatter.context },
|
|
31882
32557
|
...frontmatter.agent && { agent: frontmatter.agent },
|
|
32558
|
+
...frontmatter.background !== void 0 && { background: frontmatter.background },
|
|
31883
32559
|
...frontmatter.hooks !== void 0 && { hooks: frontmatter.hooks },
|
|
31884
32560
|
...frontmatter.shell && { shell: frontmatter.shell },
|
|
31885
32561
|
...frontmatter["disable-model-invocation"] !== void 0 && { "disable-model-invocation": frontmatter["disable-model-invocation"] },
|
|
@@ -32489,7 +33165,9 @@ const CopilotcliSkillFrontmatterSchema = zod_mini.z.looseObject({
|
|
|
32489
33165
|
description: zod_mini.z.string(),
|
|
32490
33166
|
license: zod_mini.z.optional(zod_mini.z.string()),
|
|
32491
33167
|
"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())
|
|
33168
|
+
"argument-hint": zod_mini.z.optional(zod_mini.z.string()),
|
|
33169
|
+
"user-invocable": zod_mini.z.optional(zod_mini.z.boolean()),
|
|
33170
|
+
"disable-model-invocation": zod_mini.z.optional(zod_mini.z.boolean())
|
|
32493
33171
|
});
|
|
32494
33172
|
/**
|
|
32495
33173
|
* Represents a GitHub Copilot CLI skill directory.
|
|
@@ -32548,7 +33226,9 @@ var CopilotcliSkill = class CopilotcliSkill extends ToolSkill {
|
|
|
32548
33226
|
const copilotcliSection = {
|
|
32549
33227
|
...frontmatter.license !== void 0 && { license: frontmatter.license },
|
|
32550
33228
|
...frontmatter["allowed-tools"] !== void 0 && { "allowed-tools": frontmatter["allowed-tools"] },
|
|
32551
|
-
...frontmatter["argument-hint"] !== void 0 && { "argument-hint": frontmatter["argument-hint"] }
|
|
33229
|
+
...frontmatter["argument-hint"] !== void 0 && { "argument-hint": frontmatter["argument-hint"] },
|
|
33230
|
+
...frontmatter["user-invocable"] !== void 0 && { "user-invocable": frontmatter["user-invocable"] },
|
|
33231
|
+
...frontmatter["disable-model-invocation"] !== void 0 && { "disable-model-invocation": frontmatter["disable-model-invocation"] }
|
|
32552
33232
|
};
|
|
32553
33233
|
const rulesyncFrontmatter = {
|
|
32554
33234
|
name: frontmatter.name,
|
|
@@ -32575,7 +33255,9 @@ var CopilotcliSkill = class CopilotcliSkill extends ToolSkill {
|
|
|
32575
33255
|
description: rulesyncFrontmatter.description,
|
|
32576
33256
|
...rulesyncFrontmatter.copilotcli?.license !== void 0 && { license: rulesyncFrontmatter.copilotcli.license },
|
|
32577
33257
|
...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"] }
|
|
33258
|
+
...rulesyncFrontmatter.copilotcli?.["argument-hint"] !== void 0 && { "argument-hint": rulesyncFrontmatter.copilotcli["argument-hint"] },
|
|
33259
|
+
...rulesyncFrontmatter.copilotcli?.["user-invocable"] !== void 0 && { "user-invocable": rulesyncFrontmatter.copilotcli["user-invocable"] },
|
|
33260
|
+
...rulesyncFrontmatter.copilotcli?.["disable-model-invocation"] !== void 0 && { "disable-model-invocation": rulesyncFrontmatter.copilotcli["disable-model-invocation"] }
|
|
32579
33261
|
};
|
|
32580
33262
|
return new CopilotcliSkill({
|
|
32581
33263
|
outputRoot,
|
|
@@ -34245,6 +34927,37 @@ var OpenCodeSkill = class OpenCodeSkill extends ToolSkill {
|
|
|
34245
34927
|
alternativeSkillRoots: [global ? OPENCODE_GLOBAL_SKILL_DIR_PATH : OPENCODE_SKILL_DIR_PATH]
|
|
34246
34928
|
};
|
|
34247
34929
|
}
|
|
34930
|
+
/**
|
|
34931
|
+
* Extra skill roots the project configured in `opencode.json` /
|
|
34932
|
+
* `opencode.jsonc` via `skills.paths` ("Additional paths to skill folders").
|
|
34933
|
+
* Without these, skills a project keeps outside `.opencode/skills/` are
|
|
34934
|
+
* invisible to `rulesync import` even though OpenCode loads them.
|
|
34935
|
+
*
|
|
34936
|
+
* Import-only: rulesync keeps writing to its own managed root, so a
|
|
34937
|
+
* configured path is read but never generated into. `skills.urls` is a
|
|
34938
|
+
* remote-fetch surface and is out of scope for a file-based generator.
|
|
34939
|
+
*
|
|
34940
|
+
* Absolute paths and paths escaping the output root are dropped — an import
|
|
34941
|
+
* root is joined onto `outputRoot`, and reaching outside it is not something
|
|
34942
|
+
* a project config should be able to ask for.
|
|
34943
|
+
*
|
|
34944
|
+
* @see https://opencode.ai/config.json
|
|
34945
|
+
*/
|
|
34946
|
+
static async getConfiguredImportRoots({ outputRoot, global = false }) {
|
|
34947
|
+
const skills = asOpencodeEntries((await readOpencodeConfig({
|
|
34948
|
+
outputRoot,
|
|
34949
|
+
global
|
|
34950
|
+
})).skills);
|
|
34951
|
+
if (skills === null || !Array.isArray(skills.paths)) return [];
|
|
34952
|
+
const configDir = getOpencodeConfigDir({
|
|
34953
|
+
outputRoot,
|
|
34954
|
+
global
|
|
34955
|
+
});
|
|
34956
|
+
return skills.paths.filter((candidate) => typeof candidate === "string" && candidate !== "" && !(0, node_path.isAbsolute)(candidate) && !(0, node_path.normalize)(candidate).startsWith("..")).map((relativeDirPath) => ({
|
|
34957
|
+
outputRoot: configDir,
|
|
34958
|
+
relativeDirPath
|
|
34959
|
+
}));
|
|
34960
|
+
}
|
|
34248
34961
|
getFrontmatter() {
|
|
34249
34962
|
return OpenCodeSkillFrontmatterSchema.parse(this.requireMainFileFrontmatter());
|
|
34250
34963
|
}
|
|
@@ -36178,12 +36891,19 @@ var SkillsProcessor = class extends DirFeatureProcessor {
|
|
|
36178
36891
|
*/
|
|
36179
36892
|
async loadToolDirs() {
|
|
36180
36893
|
const factory = this.getFactory(this.toolTarget);
|
|
36181
|
-
const
|
|
36894
|
+
const paths = factory.class.getSettablePaths({ global: this.global });
|
|
36895
|
+
const configuredRoots = factory.class.getConfiguredImportRoots ? await factory.class.getConfiguredImportRoots({
|
|
36896
|
+
outputRoot: this.outputRoot,
|
|
36897
|
+
global: this.global
|
|
36898
|
+
}) : [];
|
|
36899
|
+
const configuredRootPaths = new Set(configuredRoots.map((root) => root.relativeDirPath));
|
|
36900
|
+
const roots = [...toolSkillImportRoots(paths), ...configuredRoots];
|
|
36182
36901
|
const seenSkillNames = /* @__PURE__ */ new Set();
|
|
36183
36902
|
const toolSkills = [];
|
|
36184
36903
|
for (const root of roots) {
|
|
36185
36904
|
const rootOutputRoot = typeof root === "string" ? this.outputRoot : root.outputRoot;
|
|
36186
36905
|
const relativeDirPath = typeof root === "string" ? root : root.relativeDirPath;
|
|
36906
|
+
const isConfiguredRoot = configuredRootPaths.has(relativeDirPath);
|
|
36187
36907
|
const skillsDirPath = (0, node_path.join)(rootOutputRoot, relativeDirPath);
|
|
36188
36908
|
if (!await directoryExists(skillsDirPath)) continue;
|
|
36189
36909
|
const dirPaths = await findFilesByGlobs((0, node_path.join)(skillsDirPath, "*"), { type: "dir" });
|
|
@@ -36198,12 +36918,20 @@ var SkillsProcessor = class extends DirFeatureProcessor {
|
|
|
36198
36918
|
})) continue;
|
|
36199
36919
|
ownedDirNames.push(dirName);
|
|
36200
36920
|
}
|
|
36201
|
-
const directorySkills = await Promise.all(ownedDirNames.map((dirName) =>
|
|
36202
|
-
|
|
36203
|
-
|
|
36204
|
-
|
|
36205
|
-
|
|
36206
|
-
|
|
36921
|
+
const directorySkills = (await Promise.all(ownedDirNames.map(async (dirName) => {
|
|
36922
|
+
try {
|
|
36923
|
+
return await factory.class.fromDir({
|
|
36924
|
+
outputRoot: rootOutputRoot,
|
|
36925
|
+
relativeDirPath,
|
|
36926
|
+
dirName,
|
|
36927
|
+
global: this.global
|
|
36928
|
+
});
|
|
36929
|
+
} catch (error) {
|
|
36930
|
+
if (!isConfiguredRoot) throw error;
|
|
36931
|
+
this.logger.warn(`Skipping ${(0, node_path.join)(relativeDirPath, dirName)}: ${formatError(error)}`);
|
|
36932
|
+
return null;
|
|
36933
|
+
}
|
|
36934
|
+
}))).filter((skill) => skill !== null);
|
|
36207
36935
|
for (const skill of directorySkills) {
|
|
36208
36936
|
const skillName = skill.getImportIdentity();
|
|
36209
36937
|
if (seenSkillNames.has(skillName)) continue;
|
|
@@ -36728,6 +37456,242 @@ var RovodevSubagent = class RovodevSubagent extends ToolSubagent {
|
|
|
36728
37456
|
}
|
|
36729
37457
|
};
|
|
36730
37458
|
//#endregion
|
|
37459
|
+
//#region src/features/subagents/antigravity-shared-subagent.ts
|
|
37460
|
+
/**
|
|
37461
|
+
* Frontmatter of an Antigravity custom agent (Markdown format, CLI v1.1.6+).
|
|
37462
|
+
*
|
|
37463
|
+
* `name` and `description` are required upstream; the rest are optional and
|
|
37464
|
+
* documented with defaults (`tools: []`, `mainAgent: true`, `subagent: true`,
|
|
37465
|
+
* `model: inherit`, `commandExecutionPolicy: sandbox`, `mcpServers: []`,
|
|
37466
|
+
* `skills`/`plugins`: `[]`). `hidden` and `inheritMcp` appear in the v1.1.6
|
|
37467
|
+
* release notes but not in the documented frontmatter table, so they are
|
|
37468
|
+
* accepted as verbatim passthrough without any behavior modeled around them.
|
|
37469
|
+
* `looseObject` keeps unknown future fields round-tripping.
|
|
37470
|
+
*
|
|
37471
|
+
* @see https://antigravity.google/docs/subagents
|
|
37472
|
+
*/
|
|
37473
|
+
const AntigravitySubagentFrontmatterSchema = zod_mini.z.looseObject({
|
|
37474
|
+
name: zod_mini.z.string(),
|
|
37475
|
+
description: zod_mini.z.string().check(zod_mini.z.minLength(1)),
|
|
37476
|
+
tools: zod_mini.z.optional(zod_mini.z.array(zod_mini.z.string())),
|
|
37477
|
+
mainAgent: zod_mini.z.optional(zod_mini.z.boolean()),
|
|
37478
|
+
subagent: zod_mini.z.optional(zod_mini.z.boolean()),
|
|
37479
|
+
model: zod_mini.z.optional(zod_mini.z.string()),
|
|
37480
|
+
commandExecutionPolicy: zod_mini.z.optional(zod_mini.z.string()),
|
|
37481
|
+
mcpServers: zod_mini.z.optional(zod_mini.z.array(zod_mini.z.unknown())),
|
|
37482
|
+
skills: zod_mini.z.optional(zod_mini.z.array(zod_mini.z.string())),
|
|
37483
|
+
plugins: zod_mini.z.optional(zod_mini.z.array(zod_mini.z.string())),
|
|
37484
|
+
hidden: zod_mini.z.optional(zod_mini.z.boolean()),
|
|
37485
|
+
inheritMcp: zod_mini.z.optional(zod_mini.z.boolean())
|
|
37486
|
+
});
|
|
37487
|
+
/**
|
|
37488
|
+
* Shared custom-agent (subagent) implementation for Google Antigravity 2.0,
|
|
37489
|
+
* used by the IDE, the CLI and plugin bundles.
|
|
37490
|
+
*
|
|
37491
|
+
* Antigravity discovers agents at `.agents/agents/<name>.md` (project) and
|
|
37492
|
+
* `~/.gemini/config/agents/<name>.md` (global, shared by the IDE and the CLI
|
|
37493
|
+
* exactly like `~/.gemini/config/hooks.json`). The directory form
|
|
37494
|
+
* (`<name>/agent.md`) is an equivalent alternative upstream; rulesync emits and
|
|
37495
|
+
* imports the flat file form. The body after the frontmatter is the agent's
|
|
37496
|
+
* system prompt.
|
|
37497
|
+
*
|
|
37498
|
+
* Concrete subclasses only supply the rulesync target name they answer to via
|
|
37499
|
+
* {@link AntigravitySharedSubagent.getToolTarget} and, where the shared file is
|
|
37500
|
+
* not involved, the sections they read via
|
|
37501
|
+
* {@link AntigravitySharedSubagent.getReadSectionKeys}.
|
|
37502
|
+
*
|
|
37503
|
+
* @see https://antigravity.google/docs/subagents
|
|
37504
|
+
*/
|
|
37505
|
+
var AntigravitySharedSubagent = class extends ToolSubagent {
|
|
37506
|
+
frontmatter;
|
|
37507
|
+
body;
|
|
37508
|
+
constructor({ frontmatter, body, fileContent, ...rest }) {
|
|
37509
|
+
if (rest.validate !== false) {
|
|
37510
|
+
const result = AntigravitySubagentFrontmatterSchema.safeParse(frontmatter);
|
|
37511
|
+
if (!result.success) throw new Error(`Invalid frontmatter in ${(0, node_path.join)(rest.relativeDirPath, rest.relativeFilePath)}: ${formatError(result.error)}`);
|
|
37512
|
+
}
|
|
37513
|
+
super({
|
|
37514
|
+
...rest,
|
|
37515
|
+
fileContent: fileContent ?? stringifyFrontmatter(body, frontmatter, { avoidBlockScalars: true })
|
|
37516
|
+
});
|
|
37517
|
+
this.frontmatter = frontmatter;
|
|
37518
|
+
this.body = body;
|
|
37519
|
+
}
|
|
37520
|
+
/** The rulesync target name this subagent answers to. */
|
|
37521
|
+
static getToolTarget() {
|
|
37522
|
+
throw new Error("Please implement this method in the subclass.");
|
|
37523
|
+
}
|
|
37524
|
+
/**
|
|
37525
|
+
* Tool-specific sections this target reads, in increasing precedence order.
|
|
37526
|
+
*
|
|
37527
|
+
* `antigravity-ide` and `antigravity-cli` write the very same file, so a
|
|
37528
|
+
* target that read only its own section would silently drop the other's keys
|
|
37529
|
+
* — and which one survived would depend on `--targets` order. Every target
|
|
37530
|
+
* therefore merges the shared `antigravity-ide` → `antigravity-cli` sections
|
|
37531
|
+
* (the CLI block wins, matching the fixed order the MCP feature already uses
|
|
37532
|
+
* for the same shared-output reason), and the plugin target layers its own
|
|
37533
|
+
* section on top of that. Only `getToolTarget()` decides which section an
|
|
37534
|
+
* import writes back into.
|
|
37535
|
+
*/
|
|
37536
|
+
static getReadSectionKeys() {
|
|
37537
|
+
return ["antigravity-ide", "antigravity-cli"];
|
|
37538
|
+
}
|
|
37539
|
+
static getSettablePaths({ global = false } = {}) {
|
|
37540
|
+
return { relativeDirPath: global ? ANTIGRAVITY_GLOBAL_AGENTS_DIR_PATH : ANTIGRAVITY_AGENTS_DIR_PATH };
|
|
37541
|
+
}
|
|
37542
|
+
getFrontmatter() {
|
|
37543
|
+
return this.frontmatter;
|
|
37544
|
+
}
|
|
37545
|
+
getBody() {
|
|
37546
|
+
return this.body;
|
|
37547
|
+
}
|
|
37548
|
+
toRulesyncSubagent() {
|
|
37549
|
+
const { name, description, ...restFields } = this.frontmatter;
|
|
37550
|
+
return new RulesyncSubagent({
|
|
37551
|
+
outputRoot: ".",
|
|
37552
|
+
frontmatter: {
|
|
37553
|
+
targets: ["*"],
|
|
37554
|
+
name,
|
|
37555
|
+
description,
|
|
37556
|
+
...Object.keys(restFields).length > 0 && { [this.constructor.getToolTarget()]: restFields }
|
|
37557
|
+
},
|
|
37558
|
+
body: this.body,
|
|
37559
|
+
relativeDirPath: RULESYNC_SUBAGENTS_RELATIVE_DIR_PATH,
|
|
37560
|
+
relativeFilePath: this.getRelativeFilePath(),
|
|
37561
|
+
validate: true
|
|
37562
|
+
});
|
|
37563
|
+
}
|
|
37564
|
+
static fromRulesyncSubagent({ outputRoot = process.cwd(), rulesyncSubagent, validate = true, global = false }) {
|
|
37565
|
+
const rulesyncFrontmatter = rulesyncSubagent.getFrontmatter();
|
|
37566
|
+
const mergedSection = Object.assign({}, ...this.getReadSectionKeys().map((key) => rulesyncFrontmatter[key] ?? {}));
|
|
37567
|
+
const toolSection = this.filterToolSpecificSection(mergedSection, ["name", "description"]);
|
|
37568
|
+
const rawFrontmatter = {
|
|
37569
|
+
name: rulesyncFrontmatter.name,
|
|
37570
|
+
description: rulesyncFrontmatter.description || `${rulesyncFrontmatter.name} subagent`,
|
|
37571
|
+
...toolSection
|
|
37572
|
+
};
|
|
37573
|
+
const result = AntigravitySubagentFrontmatterSchema.safeParse(rawFrontmatter);
|
|
37574
|
+
if (!result.success) throw new Error(`Invalid ${this.getToolTarget()} subagent frontmatter in ${rulesyncSubagent.getRelativeFilePath()}: ${formatError(result.error)}`);
|
|
37575
|
+
const frontmatter = result.data;
|
|
37576
|
+
const body = rulesyncSubagent.getBody();
|
|
37577
|
+
const paths = this.getSettablePaths({ global });
|
|
37578
|
+
return new this({
|
|
37579
|
+
outputRoot,
|
|
37580
|
+
frontmatter,
|
|
37581
|
+
body,
|
|
37582
|
+
relativeDirPath: paths.relativeDirPath,
|
|
37583
|
+
relativeFilePath: rulesyncSubagent.getRelativeFilePath(),
|
|
37584
|
+
fileContent: stringifyFrontmatter(body, frontmatter, { avoidBlockScalars: true }),
|
|
37585
|
+
validate,
|
|
37586
|
+
global
|
|
37587
|
+
});
|
|
37588
|
+
}
|
|
37589
|
+
validate() {
|
|
37590
|
+
if (!this.frontmatter) return {
|
|
37591
|
+
success: true,
|
|
37592
|
+
error: null
|
|
37593
|
+
};
|
|
37594
|
+
const result = AntigravitySubagentFrontmatterSchema.safeParse(this.frontmatter);
|
|
37595
|
+
if (result.success) return {
|
|
37596
|
+
success: true,
|
|
37597
|
+
error: null
|
|
37598
|
+
};
|
|
37599
|
+
return {
|
|
37600
|
+
success: false,
|
|
37601
|
+
error: /* @__PURE__ */ new Error(`Invalid frontmatter in ${(0, node_path.join)(this.relativeDirPath, this.relativeFilePath)}: ${formatError(result.error)}`)
|
|
37602
|
+
};
|
|
37603
|
+
}
|
|
37604
|
+
static isTargetedByRulesyncSubagent(rulesyncSubagent) {
|
|
37605
|
+
return this.isTargetedByRulesyncSubagentDefault({
|
|
37606
|
+
rulesyncSubagent,
|
|
37607
|
+
toolTarget: this.getToolTarget()
|
|
37608
|
+
});
|
|
37609
|
+
}
|
|
37610
|
+
static async fromFile({ outputRoot = process.cwd(), relativeFilePath, validate = true, global = false }) {
|
|
37611
|
+
const paths = this.getSettablePaths({ global });
|
|
37612
|
+
const filePath = (0, node_path.join)(outputRoot, paths.relativeDirPath, relativeFilePath);
|
|
37613
|
+
const fileContent = await readFileContent(filePath);
|
|
37614
|
+
const { frontmatter, body: content } = parseFrontmatter(fileContent, filePath);
|
|
37615
|
+
const result = AntigravitySubagentFrontmatterSchema.safeParse(frontmatter);
|
|
37616
|
+
if (!result.success) throw new Error(`Invalid frontmatter in ${filePath}: ${formatError(result.error)}`);
|
|
37617
|
+
return new this({
|
|
37618
|
+
outputRoot,
|
|
37619
|
+
relativeDirPath: paths.relativeDirPath,
|
|
37620
|
+
relativeFilePath,
|
|
37621
|
+
frontmatter: result.data,
|
|
37622
|
+
body: content.trim(),
|
|
37623
|
+
fileContent,
|
|
37624
|
+
validate,
|
|
37625
|
+
global
|
|
37626
|
+
});
|
|
37627
|
+
}
|
|
37628
|
+
static forDeletion({ outputRoot = process.cwd(), relativeDirPath, relativeFilePath, global = false }) {
|
|
37629
|
+
return new this({
|
|
37630
|
+
outputRoot,
|
|
37631
|
+
relativeDirPath,
|
|
37632
|
+
relativeFilePath,
|
|
37633
|
+
frontmatter: {
|
|
37634
|
+
name: "",
|
|
37635
|
+
description: ""
|
|
37636
|
+
},
|
|
37637
|
+
body: "",
|
|
37638
|
+
fileContent: "",
|
|
37639
|
+
validate: false,
|
|
37640
|
+
global
|
|
37641
|
+
});
|
|
37642
|
+
}
|
|
37643
|
+
};
|
|
37644
|
+
//#endregion
|
|
37645
|
+
//#region src/features/subagents/antigravity-cli-subagent.ts
|
|
37646
|
+
/**
|
|
37647
|
+
* Google Antigravity CLI custom agent (subagent), shipped in CLI v1.1.6.
|
|
37648
|
+
*
|
|
37649
|
+
* Shares all behavior with {@link AntigravitySharedSubagent}; the CLI reads the
|
|
37650
|
+
* same `.agents/agents/` and `~/.gemini/config/agents/` roots as the IDE and
|
|
37651
|
+
* exposes them through the `agy agents` subcommand and the `--agent` flag. It
|
|
37652
|
+
* answers to the `antigravity-cli` target.
|
|
37653
|
+
*/
|
|
37654
|
+
var AntigravityCliSubagent = class extends AntigravitySharedSubagent {
|
|
37655
|
+
static getToolTarget() {
|
|
37656
|
+
return "antigravity-cli";
|
|
37657
|
+
}
|
|
37658
|
+
};
|
|
37659
|
+
//#endregion
|
|
37660
|
+
//#region src/features/subagents/antigravity-ide-subagent.ts
|
|
37661
|
+
/**
|
|
37662
|
+
* Google Antigravity IDE custom agent (subagent).
|
|
37663
|
+
*
|
|
37664
|
+
* Shares all behavior with {@link AntigravitySharedSubagent} — the subagents
|
|
37665
|
+
* documentation is product-wide and lists the same `.agents/agents/` and
|
|
37666
|
+
* `~/.gemini/config/agents/` roots for the IDE and the CLI. It answers to the
|
|
37667
|
+
* `antigravity-ide` target.
|
|
37668
|
+
*/
|
|
37669
|
+
var AntigravityIdeSubagent = class extends AntigravitySharedSubagent {
|
|
37670
|
+
static getToolTarget() {
|
|
37671
|
+
return "antigravity-ide";
|
|
37672
|
+
}
|
|
37673
|
+
};
|
|
37674
|
+
//#endregion
|
|
37675
|
+
//#region src/features/subagents/antigravity-plugin-subagent.ts
|
|
37676
|
+
/**
|
|
37677
|
+
* Custom agent inside an Antigravity plugin bundle
|
|
37678
|
+
* (`<plugin_name>/agents/<name>.md`). Plugin bundles are project-scope output
|
|
37679
|
+
* only; they are staged into `~/.gemini/antigravity-cli/plugins/` by the user.
|
|
37680
|
+
*
|
|
37681
|
+
* @see https://antigravity.google/docs/cli/plugins
|
|
37682
|
+
*/
|
|
37683
|
+
var AntigravityPluginSubagent = class extends AntigravitySharedSubagent {
|
|
37684
|
+
static getToolTarget() {
|
|
37685
|
+
return "antigravity-plugin";
|
|
37686
|
+
}
|
|
37687
|
+
static getReadSectionKeys() {
|
|
37688
|
+
return [...super.getReadSectionKeys(), "antigravity-plugin"];
|
|
37689
|
+
}
|
|
37690
|
+
static getSettablePaths() {
|
|
37691
|
+
return { relativeDirPath: ANTIGRAVITY_PLUGIN_AGENTS_DIR };
|
|
37692
|
+
}
|
|
37693
|
+
};
|
|
37694
|
+
//#endregion
|
|
36731
37695
|
//#region src/features/subagents/augmentcode-subagent.ts
|
|
36732
37696
|
const AugmentcodeSubagentFrontmatterSchema = zod_mini.z.looseObject({
|
|
36733
37697
|
name: zod_mini.z.string(),
|
|
@@ -40305,6 +41269,30 @@ const toolSubagentFactories = /* @__PURE__ */ new Map([
|
|
|
40305
41269
|
filePattern: "*.md"
|
|
40306
41270
|
}
|
|
40307
41271
|
}],
|
|
41272
|
+
["antigravity-cli", {
|
|
41273
|
+
class: AntigravityCliSubagent,
|
|
41274
|
+
meta: {
|
|
41275
|
+
supportsSimulated: false,
|
|
41276
|
+
supportsGlobal: true,
|
|
41277
|
+
filePattern: "*.md"
|
|
41278
|
+
}
|
|
41279
|
+
}],
|
|
41280
|
+
["antigravity-ide", {
|
|
41281
|
+
class: AntigravityIdeSubagent,
|
|
41282
|
+
meta: {
|
|
41283
|
+
supportsSimulated: false,
|
|
41284
|
+
supportsGlobal: true,
|
|
41285
|
+
filePattern: "*.md"
|
|
41286
|
+
}
|
|
41287
|
+
}],
|
|
41288
|
+
["antigravity-plugin", {
|
|
41289
|
+
class: AntigravityPluginSubagent,
|
|
41290
|
+
meta: {
|
|
41291
|
+
supportsSimulated: false,
|
|
41292
|
+
supportsGlobal: false,
|
|
41293
|
+
filePattern: "*.md"
|
|
41294
|
+
}
|
|
41295
|
+
}],
|
|
40308
41296
|
["augmentcode", {
|
|
40309
41297
|
class: AugmentcodeSubagent,
|
|
40310
41298
|
meta: {
|
|
@@ -42518,6 +43506,7 @@ var CodexcliRule = class CodexcliRule extends ToolRule {
|
|
|
42518
43506
|
const CopilotRuleFrontmatterSchema = zod_mini.z.object({
|
|
42519
43507
|
description: zod_mini.z.optional(zod_mini.z.string()),
|
|
42520
43508
|
applyTo: zod_mini.z.optional(zod_mini.z.string()),
|
|
43509
|
+
name: zod_mini.z.optional(zod_mini.z.string()),
|
|
42521
43510
|
excludeAgent: zod_mini.z.optional(zod_mini.z.union([
|
|
42522
43511
|
zod_mini.z.literal("code-review"),
|
|
42523
43512
|
zod_mini.z.literal("cloud-agent"),
|
|
@@ -42575,7 +43564,10 @@ var CopilotRule = class CopilotRule extends ToolRule {
|
|
|
42575
43564
|
root: this.isRoot(),
|
|
42576
43565
|
description: this.frontmatter.description,
|
|
42577
43566
|
globs,
|
|
42578
|
-
...this.frontmatter.excludeAgent && { copilot: {
|
|
43567
|
+
...(this.frontmatter.excludeAgent || this.frontmatter.name) && { copilot: {
|
|
43568
|
+
...this.frontmatter.excludeAgent && { excludeAgent: this.frontmatter.excludeAgent },
|
|
43569
|
+
...this.frontmatter.name && { name: this.frontmatter.name }
|
|
43570
|
+
} }
|
|
42579
43571
|
};
|
|
42580
43572
|
const relativeFilePath = this.getRelativeFilePath().replace(/\.instructions\.md$/, ".md");
|
|
42581
43573
|
return new RulesyncRule({
|
|
@@ -42594,7 +43586,8 @@ var CopilotRule = class CopilotRule extends ToolRule {
|
|
|
42594
43586
|
const copilotFrontmatter = {
|
|
42595
43587
|
description: rulesyncFrontmatter.description,
|
|
42596
43588
|
applyTo: rulesyncFrontmatter.globs?.length ? rulesyncFrontmatter.globs.join(",") : void 0,
|
|
42597
|
-
excludeAgent: rulesyncFrontmatter.copilot?.excludeAgent
|
|
43589
|
+
excludeAgent: rulesyncFrontmatter.copilot?.excludeAgent,
|
|
43590
|
+
name: rulesyncFrontmatter.copilot?.name
|
|
42598
43591
|
};
|
|
42599
43592
|
const body = rulesyncRule.getBody();
|
|
42600
43593
|
if (root) return new CopilotRule({
|