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
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { ZodError } from "zod";
|
|
2
2
|
import { meta, minLength, nonnegative, optional, refine, z } from "zod/mini";
|
|
3
3
|
import { cp, lstat, mkdir, mkdtemp, readFile, readdir, realpath, rm, stat, writeFile } from "node:fs/promises";
|
|
4
|
-
import path, { basename, dirname, extname, isAbsolute, join, posix, relative, resolve, sep, win32 } from "node:path";
|
|
4
|
+
import path, { basename, dirname, extname, isAbsolute, join, normalize, posix, relative, resolve, sep, win32 } from "node:path";
|
|
5
5
|
import { parse, printParseErrorCode } from "jsonc-parser";
|
|
6
6
|
import os from "node:os";
|
|
7
7
|
import { intersection, kebabCase, uniq } from "es-toolkit";
|
|
@@ -268,6 +268,9 @@ const commandsProcessorToolTargetTuple = [
|
|
|
268
268
|
const subagentsProcessorToolTargetTuple = [
|
|
269
269
|
"kilo",
|
|
270
270
|
"agentsmd",
|
|
271
|
+
"antigravity-cli",
|
|
272
|
+
"antigravity-ide",
|
|
273
|
+
"antigravity-plugin",
|
|
271
274
|
"augmentcode",
|
|
272
275
|
"claudecode",
|
|
273
276
|
"claudecode-plugin",
|
|
@@ -399,6 +402,7 @@ const permissionsProcessorToolTargetTuple = [
|
|
|
399
402
|
];
|
|
400
403
|
const checksProcessorToolTargetTuple = [
|
|
401
404
|
"amp",
|
|
405
|
+
"cursor",
|
|
402
406
|
"hermesagent",
|
|
403
407
|
"takt"
|
|
404
408
|
];
|
|
@@ -2109,7 +2113,10 @@ const HookDefinitionSchema = z.looseObject({
|
|
|
2109
2113
|
model: z.optional(safeString),
|
|
2110
2114
|
args: z.optional(z.array(safeString)),
|
|
2111
2115
|
metadata: z.optional(z.looseObject({})),
|
|
2112
|
-
if: z.optional(safeString)
|
|
2116
|
+
if: z.optional(safeString),
|
|
2117
|
+
commandWindows: z.optional(safeString),
|
|
2118
|
+
asyncRewake: z.optional(z.boolean()),
|
|
2119
|
+
continueOnBlock: z.optional(z.boolean())
|
|
2113
2120
|
});
|
|
2114
2121
|
/**
|
|
2115
2122
|
* All canonical hook event names.
|
|
@@ -2163,6 +2170,7 @@ const HOOK_EVENTS = [
|
|
|
2163
2170
|
"configChange",
|
|
2164
2171
|
"cwdChanged",
|
|
2165
2172
|
"fileChanged",
|
|
2173
|
+
"directoryAdded",
|
|
2166
2174
|
"elicitation",
|
|
2167
2175
|
"elicitationResult"
|
|
2168
2176
|
];
|
|
@@ -2224,6 +2232,7 @@ const CLAUDE_HOOK_EVENTS = [
|
|
|
2224
2232
|
"configChange",
|
|
2225
2233
|
"cwdChanged",
|
|
2226
2234
|
"fileChanged",
|
|
2235
|
+
"directoryAdded",
|
|
2227
2236
|
"postCompact",
|
|
2228
2237
|
"elicitation",
|
|
2229
2238
|
"elicitationResult"
|
|
@@ -2256,7 +2265,15 @@ const DEVIN_HOOK_EVENTS = [
|
|
|
2256
2265
|
"permissionRequest",
|
|
2257
2266
|
"postCompact"
|
|
2258
2267
|
];
|
|
2259
|
-
/**
|
|
2268
|
+
/**
|
|
2269
|
+
* Hook events supported by OpenCode.
|
|
2270
|
+
*
|
|
2271
|
+
* `preCompact` maps to `experimental.session.compacting`, which the plugin docs
|
|
2272
|
+
* document as a named `(input, output)` hook rather than an `event.type`
|
|
2273
|
+
* dispatch; the other entries are all generic events.
|
|
2274
|
+
*
|
|
2275
|
+
* @see https://opencode.ai/docs/plugins/
|
|
2276
|
+
*/
|
|
2260
2277
|
const OPENCODE_HOOK_EVENTS = [
|
|
2261
2278
|
"sessionStart",
|
|
2262
2279
|
"preToolUse",
|
|
@@ -2264,9 +2281,19 @@ const OPENCODE_HOOK_EVENTS = [
|
|
|
2264
2281
|
"stop",
|
|
2265
2282
|
"afterFileEdit",
|
|
2266
2283
|
"afterShellExecution",
|
|
2267
|
-
"permissionRequest"
|
|
2284
|
+
"permissionRequest",
|
|
2285
|
+
"preCompact",
|
|
2286
|
+
"postCompact",
|
|
2287
|
+
"afterError",
|
|
2288
|
+
"fileChanged"
|
|
2268
2289
|
];
|
|
2269
|
-
/**
|
|
2290
|
+
/**
|
|
2291
|
+
* Hook events supported by Kilo. Identical to OpenCode: Kilo's plugin docs list
|
|
2292
|
+
* the same event surface, including `session.compacted`, `session.error`,
|
|
2293
|
+
* `file.watcher.updated` and the experimental compaction hook.
|
|
2294
|
+
*
|
|
2295
|
+
* @see https://kilo.ai/docs/automate/extending/plugins
|
|
2296
|
+
*/
|
|
2270
2297
|
const KILO_HOOK_EVENTS = OPENCODE_HOOK_EVENTS;
|
|
2271
2298
|
/**
|
|
2272
2299
|
* Hook events supported by Pi Coding Agent, bridged through a generated
|
|
@@ -2333,7 +2360,8 @@ const COPILOT_HOOK_EVENTS = [
|
|
|
2333
2360
|
* `sessionStart`, `sessionEnd`, `userPromptSubmitted`, `preToolUse`,
|
|
2334
2361
|
* `postToolUse`, `postToolUseFailure`, `agentStop`, `subagentStart`,
|
|
2335
2362
|
* `subagentStop`, `errorOccurred`, `preCompact`, `permissionRequest`,
|
|
2336
|
-
* `notification`, `
|
|
2363
|
+
* `notification`, `userPromptTransformed` ← `userPromptExpansion`,
|
|
2364
|
+
* `preMcpToolCall` ← `beforeMCPExecution`.
|
|
2337
2365
|
*
|
|
2338
2366
|
* `preMcpToolCall` (canonical `beforeMCPExecution`) was added in Copilot CLI
|
|
2339
2367
|
* v1.0.51 (2026-05-20) for hook providers to control outgoing MCP request
|
|
@@ -2355,6 +2383,7 @@ const COPILOTCLI_HOOK_EVENTS = [
|
|
|
2355
2383
|
"preCompact",
|
|
2356
2384
|
"permissionRequest",
|
|
2357
2385
|
"notification",
|
|
2386
|
+
"userPromptExpansion",
|
|
2358
2387
|
"beforeMCPExecution"
|
|
2359
2388
|
];
|
|
2360
2389
|
/**
|
|
@@ -2399,6 +2428,7 @@ const DEEPAGENTS_HOOK_EVENTS = [
|
|
|
2399
2428
|
/** Hook events supported by Codex CLI. */
|
|
2400
2429
|
const CODEXCLI_HOOK_EVENTS = [
|
|
2401
2430
|
"sessionStart",
|
|
2431
|
+
"sessionEnd",
|
|
2402
2432
|
"preToolUse",
|
|
2403
2433
|
"postToolUse",
|
|
2404
2434
|
"beforeSubmitPrompt",
|
|
@@ -2833,6 +2863,7 @@ const CANONICAL_TO_CLAUDE_EVENT_NAMES = {
|
|
|
2833
2863
|
configChange: "ConfigChange",
|
|
2834
2864
|
cwdChanged: "CwdChanged",
|
|
2835
2865
|
fileChanged: "FileChanged",
|
|
2866
|
+
directoryAdded: "DirectoryAdded",
|
|
2836
2867
|
postCompact: "PostCompact",
|
|
2837
2868
|
elicitation: "Elicitation",
|
|
2838
2869
|
elicitationResult: "ElicitationResult"
|
|
@@ -2954,7 +2985,11 @@ const CANONICAL_TO_OPENCODE_EVENT_NAMES = {
|
|
|
2954
2985
|
stop: "session.idle",
|
|
2955
2986
|
afterFileEdit: "file.edited",
|
|
2956
2987
|
afterShellExecution: "command.executed",
|
|
2957
|
-
permissionRequest: "permission.asked"
|
|
2988
|
+
permissionRequest: "permission.asked",
|
|
2989
|
+
preCompact: "experimental.session.compacting",
|
|
2990
|
+
postCompact: "session.compacted",
|
|
2991
|
+
afterError: "session.error",
|
|
2992
|
+
fileChanged: "file.watcher.updated"
|
|
2958
2993
|
};
|
|
2959
2994
|
/**
|
|
2960
2995
|
* Map canonical camelCase event names to Kilo dot-notation.
|
|
@@ -3030,6 +3065,7 @@ const CANONICAL_TO_COPILOTCLI_EVENT_NAMES = {
|
|
|
3030
3065
|
preCompact: "preCompact",
|
|
3031
3066
|
permissionRequest: "permissionRequest",
|
|
3032
3067
|
notification: "notification",
|
|
3068
|
+
userPromptExpansion: "userPromptTransformed",
|
|
3033
3069
|
beforeMCPExecution: "preMcpToolCall"
|
|
3034
3070
|
};
|
|
3035
3071
|
/** Map GitHub Copilot CLI event names back to canonical camelCase. */
|
|
@@ -3039,6 +3075,7 @@ const COPILOTCLI_TO_CANONICAL_EVENT_NAMES = Object.fromEntries(Object.entries(CA
|
|
|
3039
3075
|
*/
|
|
3040
3076
|
const CANONICAL_TO_CODEXCLI_EVENT_NAMES = {
|
|
3041
3077
|
sessionStart: "SessionStart",
|
|
3078
|
+
sessionEnd: "SessionEnd",
|
|
3042
3079
|
preToolUse: "PreToolUse",
|
|
3043
3080
|
postToolUse: "PostToolUse",
|
|
3044
3081
|
beforeSubmitPrompt: "UserPromptSubmit",
|
|
@@ -3461,6 +3498,25 @@ var RulesyncIgnore = class RulesyncIgnore extends RulesyncFile {
|
|
|
3461
3498
|
//#endregion
|
|
3462
3499
|
//#region src/types/mcp.ts
|
|
3463
3500
|
const EnvVarNameSchema = z.string().check(refine((value) => /^[A-Za-z_][A-Za-z0-9_]*$/.test(value), "envVars entries must be valid environment variable names"));
|
|
3501
|
+
/**
|
|
3502
|
+
* One `envVars` entry. A bare name reads the variable from Codex's own
|
|
3503
|
+
* environment; the object form names the environment to read it from, where
|
|
3504
|
+
* `source = "remote"` reads from the remote executor environment.
|
|
3505
|
+
* @see https://learn.chatgpt.com/docs/extend/mcp
|
|
3506
|
+
*/
|
|
3507
|
+
const EnvVarEntrySchema = z.union([EnvVarNameSchema, z.strictObject({
|
|
3508
|
+
name: EnvVarNameSchema,
|
|
3509
|
+
source: z.optional(z.enum(["local", "remote"]))
|
|
3510
|
+
})]);
|
|
3511
|
+
/**
|
|
3512
|
+
* Whether a value is usable as `envVars`. Applied in both directions by the
|
|
3513
|
+
* codex adapter, so an entry read out of somebody's `config.toml` can never be
|
|
3514
|
+
* imported into a `.rulesync/mcp.jsonc` that the next generate would refuse to
|
|
3515
|
+
* parse.
|
|
3516
|
+
*/
|
|
3517
|
+
function isEnvVarEntryArray(value) {
|
|
3518
|
+
return Array.isArray(value) && value.every((entry) => EnvVarEntrySchema.safeParse(entry).success);
|
|
3519
|
+
}
|
|
3464
3520
|
const McpServerSchema = z.looseObject({
|
|
3465
3521
|
type: z.optional(z.enum([
|
|
3466
3522
|
"local",
|
|
@@ -3475,7 +3531,8 @@ const McpServerSchema = z.looseObject({
|
|
|
3475
3531
|
url: z.optional(z.string()),
|
|
3476
3532
|
httpUrl: z.optional(z.string()),
|
|
3477
3533
|
env: z.optional(z.record(z.string(), z.string())),
|
|
3478
|
-
envVars: z.optional(z.array(
|
|
3534
|
+
envVars: z.optional(z.array(EnvVarEntrySchema)),
|
|
3535
|
+
experimentalEnvironment: z.optional(z.string()),
|
|
3479
3536
|
disabled: z.optional(z.boolean()),
|
|
3480
3537
|
networkTimeout: z.optional(z.number()),
|
|
3481
3538
|
timeout: z.optional(z.number()),
|
|
@@ -3641,6 +3698,8 @@ var RulesyncMcp = class RulesyncMcp extends RulesyncFile {
|
|
|
3641
3698
|
"description",
|
|
3642
3699
|
"exposed",
|
|
3643
3700
|
"envVars",
|
|
3701
|
+
"experimentalEnvironment",
|
|
3702
|
+
"experimental_environment",
|
|
3644
3703
|
"enabled"
|
|
3645
3704
|
])];
|
|
3646
3705
|
}));
|
|
@@ -3943,12 +4002,23 @@ const KiloPermissionsOverrideSchema = z.looseObject({ permission: z.optional(z.r
|
|
|
3943
4002
|
* authored without modeling each one; the managed `allow`/`ask`/`deny` arrays are
|
|
3944
4003
|
* ignored here (rulesync owns them).
|
|
3945
4004
|
*
|
|
4005
|
+
* `sandbox` is the sibling top-level settings subtree that governs the sandbox
|
|
4006
|
+
* Claude Code runs commands in (`sandbox.network.*`, `sandbox.filesystem.*`,
|
|
4007
|
+
* `sandbox.credentials`, `sandbox.allowAppleEvents`, ...). It has no canonical
|
|
4008
|
+
* permission category either — it constrains how a permitted command runs
|
|
4009
|
+
* rather than which commands are permitted — so it is a loose passthrough on
|
|
4010
|
+
* the same terms, merged into the top level of `.claude/settings.json`.
|
|
4011
|
+
*
|
|
3946
4012
|
* @example
|
|
3947
4013
|
* { "permissions": { "defaultMode": "acceptEdits", "additionalDirectories": ["../shared"] } }
|
|
4014
|
+
* @example
|
|
4015
|
+
* { "sandbox": { "network": { "allowedDomains": ["example.com"], "strictAllowlist": true } } }
|
|
4016
|
+
* @see https://code.claude.com/docs/en/sandboxing
|
|
3948
4017
|
*/
|
|
3949
4018
|
const ClaudecodePermissionsOverrideSchema = z.looseObject({
|
|
3950
4019
|
permission: z.optional(ToolScopedPermissionSchema),
|
|
3951
|
-
permissions: z.optional(z.looseObject({}))
|
|
4020
|
+
permissions: z.optional(z.looseObject({})),
|
|
4021
|
+
sandbox: z.optional(z.looseObject({}))
|
|
3952
4022
|
});
|
|
3953
4023
|
/**
|
|
3954
4024
|
* Tool-scoped override block for Mistral Vibe. Vibe's per-tool `BaseToolConfig`
|
|
@@ -4221,19 +4291,24 @@ const AmpPermissionsOverrideSchema = z.looseObject({
|
|
|
4221
4291
|
});
|
|
4222
4292
|
/**
|
|
4223
4293
|
* Tool-scoped override block for the Google Antigravity CLI. Antigravity's CLI
|
|
4224
|
-
* `settings.json` carries
|
|
4294
|
+
* `settings.json` carries four global autonomy/sandbox knobs outside the
|
|
4225
4295
|
* `permissions.allow/ask/deny` arrays rulesync manages: `toolPermission` (the
|
|
4226
4296
|
* global autonomy preset — `request-review` (default) / `proceed-in-sandbox` /
|
|
4227
|
-
* `always-proceed` / `strict`)
|
|
4228
|
-
* agent-run commands to OS containment)
|
|
4297
|
+
* `always-proceed` / `strict`), `enableTerminalSandbox` (a boolean confining
|
|
4298
|
+
* agent-run commands to OS containment), `artifactReviewPolicy` (whether the
|
|
4299
|
+
* agent's artifact changes are gated on a review prompt — `asks-for-review`
|
|
4300
|
+
* (default) / `agent-decides` / `always-proceed`) and `allowNonWorkspaceAccess`
|
|
4301
|
+
* (a boolean, off by default, letting the agent read or write files outside the
|
|
4302
|
+
* active workspace roots). Antigravity applies the allow/deny
|
|
4229
4303
|
* lists as per-rule exceptions to the preset at runtime, so rulesync only
|
|
4230
4304
|
* authors these keys verbatim — no precedence modeling is needed on our side.
|
|
4231
4305
|
* Fields placed here are merged onto the top level of
|
|
4232
4306
|
* `~/.gemini/antigravity-cli/settings.json` (global-only) and emitted only for
|
|
4233
4307
|
* the CLI. The Antigravity IDE exposes the same concepts through a GUI (no
|
|
4234
4308
|
* documented JSON schema), so this override does NOT apply to `antigravity-ide`.
|
|
4235
|
-
* Verified against https://antigravity.google/docs/cli/reference
|
|
4236
|
-
* https://antigravity.google/docs/cli/sandbox
|
|
4309
|
+
* Verified against https://antigravity.google/docs/cli/reference,
|
|
4310
|
+
* https://antigravity.google/docs/cli/sandbox and
|
|
4311
|
+
* https://antigravity.google/docs/cli/settings.
|
|
4237
4312
|
*
|
|
4238
4313
|
* @example
|
|
4239
4314
|
* { "toolPermission": "strict", "enableTerminalSandbox": true }
|
|
@@ -4246,7 +4321,13 @@ const AntigravityCliPermissionsOverrideSchema = z.looseObject({
|
|
|
4246
4321
|
"always-proceed",
|
|
4247
4322
|
"strict"
|
|
4248
4323
|
])),
|
|
4249
|
-
enableTerminalSandbox: z.optional(z.boolean())
|
|
4324
|
+
enableTerminalSandbox: z.optional(z.boolean()),
|
|
4325
|
+
artifactReviewPolicy: z.optional(z.enum([
|
|
4326
|
+
"asks-for-review",
|
|
4327
|
+
"agent-decides",
|
|
4328
|
+
"always-proceed"
|
|
4329
|
+
])),
|
|
4330
|
+
allowNonWorkspaceAccess: z.optional(z.boolean())
|
|
4250
4331
|
});
|
|
4251
4332
|
/**
|
|
4252
4333
|
* Tool-scoped override block for AugmentCode. AugmentCode's `toolPermissions[]`
|
|
@@ -4650,11 +4731,14 @@ const RulesyncRuleFrontmatterSchema = z.object({
|
|
|
4650
4731
|
description: z.optional(z.string()),
|
|
4651
4732
|
globs: z.optional(z.array(z.string()))
|
|
4652
4733
|
})),
|
|
4653
|
-
copilot: z.optional(z.looseObject({
|
|
4654
|
-
z.
|
|
4655
|
-
|
|
4656
|
-
|
|
4657
|
-
|
|
4734
|
+
copilot: z.optional(z.looseObject({
|
|
4735
|
+
excludeAgent: z.optional(z.union([
|
|
4736
|
+
z.literal("code-review"),
|
|
4737
|
+
z.literal("cloud-agent"),
|
|
4738
|
+
z.literal("coding-agent")
|
|
4739
|
+
])),
|
|
4740
|
+
name: z.optional(z.string())
|
|
4741
|
+
})),
|
|
4658
4742
|
antigravity: z.optional(z.looseObject({
|
|
4659
4743
|
trigger: z.optional(z.string()),
|
|
4660
4744
|
globs: z.optional(z.array(z.string()))
|
|
@@ -4862,6 +4946,7 @@ const RulesyncSkillFrontmatterSchema = z.looseObject({
|
|
|
4862
4946
|
arguments: z.optional(z.union([z.string(), z.array(z.string())])),
|
|
4863
4947
|
context: z.optional(z.string()),
|
|
4864
4948
|
agent: z.optional(z.string()),
|
|
4949
|
+
background: z.optional(z.boolean()),
|
|
4865
4950
|
hooks: z.optional(z.looseObject({})),
|
|
4866
4951
|
shell: z.optional(z.string()),
|
|
4867
4952
|
"disable-model-invocation": z.optional(z.boolean()),
|
|
@@ -4913,7 +4998,9 @@ const RulesyncSkillFrontmatterSchema = z.looseObject({
|
|
|
4913
4998
|
copilotcli: z.optional(z.looseObject({
|
|
4914
4999
|
license: z.optional(z.string()),
|
|
4915
5000
|
"allowed-tools": z.optional(z.union([z.string(), z.array(z.string())])),
|
|
4916
|
-
"argument-hint": z.optional(z.string())
|
|
5001
|
+
"argument-hint": z.optional(z.string()),
|
|
5002
|
+
"user-invocable": z.optional(z.boolean()),
|
|
5003
|
+
"disable-model-invocation": z.optional(z.boolean())
|
|
4917
5004
|
})),
|
|
4918
5005
|
pi: z.optional(z.looseObject({
|
|
4919
5006
|
"allowed-tools": z.optional(z.union([z.string(), z.array(z.string())])),
|
|
@@ -5199,6 +5286,18 @@ function resolveUserInvocable({ rootFrontmatter, section }) {
|
|
|
5199
5286
|
return section?.["user-invocable"] ?? rootFrontmatter["user-invocable"];
|
|
5200
5287
|
}
|
|
5201
5288
|
//#endregion
|
|
5289
|
+
//#region src/constants/cursor-paths.ts
|
|
5290
|
+
const CURSOR_DIR = ".cursor";
|
|
5291
|
+
const CURSOR_COMMANDS_DIR_PATH = join(CURSOR_DIR, "commands");
|
|
5292
|
+
const CURSOR_SKILLS_DIR_PATH = join(CURSOR_DIR, "skills");
|
|
5293
|
+
const CURSOR_AGENTS_DIR_PATH = join(CURSOR_DIR, "agents");
|
|
5294
|
+
const CURSOR_BUGBOT_FILE_NAME = "BUGBOT.md";
|
|
5295
|
+
const CURSOR_MCP_FILE_NAME = "mcp.json";
|
|
5296
|
+
const CURSOR_HOOKS_FILE_NAME = "hooks.json";
|
|
5297
|
+
const CURSOR_IGNORE_FILE_NAME = ".cursorignore";
|
|
5298
|
+
const CURSOR_PERMISSIONS_FILE_NAME = "cli.json";
|
|
5299
|
+
const CURSOR_PERMISSIONS_GLOBAL_FILE_NAME = "cli-config.json";
|
|
5300
|
+
//#endregion
|
|
5202
5301
|
//#region src/constants/takt-paths.ts
|
|
5203
5302
|
const TAKT_DIR = ".takt";
|
|
5204
5303
|
const TAKT_FACETS_SUBDIR = "facets";
|
|
@@ -5649,6 +5748,235 @@ var AmpCheck = class AmpCheck extends ToolCheck {
|
|
|
5649
5748
|
}
|
|
5650
5749
|
};
|
|
5651
5750
|
//#endregion
|
|
5751
|
+
//#region src/features/checks/check-slug.ts
|
|
5752
|
+
/**
|
|
5753
|
+
* Turn a name that came out of a tool's own config file into one safe to use as
|
|
5754
|
+
* a `.rulesync/checks/<name>.md` file name. Shared by the adapters whose checks
|
|
5755
|
+
* collapse into a single file, since the name they read back is whatever the
|
|
5756
|
+
* user wrote there.
|
|
5757
|
+
*/
|
|
5758
|
+
function slugifyCheckName(value) {
|
|
5759
|
+
return value.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 48).replace(/-+$/, "");
|
|
5760
|
+
}
|
|
5761
|
+
//#endregion
|
|
5762
|
+
//#region src/features/checks/cursor-check.ts
|
|
5763
|
+
/**
|
|
5764
|
+
* Marks where one check starts inside the single instruction file. Bugbot reads
|
|
5765
|
+
* the file as prose, and an HTML comment is invisible in rendered Markdown, so
|
|
5766
|
+
* the marker carries the check identity without changing what Bugbot is told.
|
|
5767
|
+
*/
|
|
5768
|
+
const CHECK_MARKER_PATTERN = /^<!--\s*rulesync:check:(.+?)\s*-->[ \t]*$/gm;
|
|
5769
|
+
/**
|
|
5770
|
+
* A marker line a check body wrote itself — a rulesync doc fragment quoted in a
|
|
5771
|
+
* code block, say. Emitting it verbatim would split that check in two on the
|
|
5772
|
+
* next import, so `literal-` is inserted before `check:` on the way out and
|
|
5773
|
+
* taken off on the way back. `(?:literal-)*` makes it a ladder, so a body that
|
|
5774
|
+
* already contains an escaped marker survives the round trip too.
|
|
5775
|
+
*/
|
|
5776
|
+
const ESCAPABLE_MARKER_PATTERN = /^(<!--\s*rulesync:)((?:literal-)*check:.+?\s*-->[ \t]*)$/gm;
|
|
5777
|
+
const ESCAPED_MARKER_PATTERN = /^(<!--\s*rulesync:)literal-((?:literal-)*check:.+?\s*-->[ \t]*)$/gm;
|
|
5778
|
+
const FALLBACK_CHECK_NAME = "bugbot";
|
|
5779
|
+
function renderMarker(name) {
|
|
5780
|
+
return `<!-- rulesync:check:${name} -->`;
|
|
5781
|
+
}
|
|
5782
|
+
function escapeMarkers(content) {
|
|
5783
|
+
return content.replace(ESCAPABLE_MARKER_PATTERN, "$1literal-$2");
|
|
5784
|
+
}
|
|
5785
|
+
function unescapeMarkers(content) {
|
|
5786
|
+
return content.replace(ESCAPED_MARKER_PATTERN, "$1$2");
|
|
5787
|
+
}
|
|
5788
|
+
function findMarkers(fileContent) {
|
|
5789
|
+
CHECK_MARKER_PATTERN.lastIndex = 0;
|
|
5790
|
+
const markers = [];
|
|
5791
|
+
let match = CHECK_MARKER_PATTERN.exec(fileContent);
|
|
5792
|
+
while (match !== null) {
|
|
5793
|
+
markers.push({
|
|
5794
|
+
name: match[1] ?? "",
|
|
5795
|
+
start: match.index,
|
|
5796
|
+
end: match.index + match[0].length
|
|
5797
|
+
});
|
|
5798
|
+
match = CHECK_MARKER_PATTERN.exec(fileContent);
|
|
5799
|
+
}
|
|
5800
|
+
return markers;
|
|
5801
|
+
}
|
|
5802
|
+
/**
|
|
5803
|
+
* The instruction text one check contributes. Bugbot has no field to put a
|
|
5804
|
+
* summary in, so `description` is used only when there is no body — the same
|
|
5805
|
+
* fallback the file-stem heading above it gets.
|
|
5806
|
+
*/
|
|
5807
|
+
function toInstruction(rulesyncCheck) {
|
|
5808
|
+
const body = rulesyncCheck.getBody().trim();
|
|
5809
|
+
if (body.length > 0) return body;
|
|
5810
|
+
return rulesyncCheck.getFrontmatter().description?.trim() ?? "";
|
|
5811
|
+
}
|
|
5812
|
+
function renderSection(rulesyncCheck) {
|
|
5813
|
+
const name = basename(rulesyncCheck.getRelativeFilePath(), ".md");
|
|
5814
|
+
const heading = `## ${name}`;
|
|
5815
|
+
const instruction = toInstruction(rulesyncCheck);
|
|
5816
|
+
const lines = [renderMarker(name), heading];
|
|
5817
|
+
if (instruction.length > 0) lines.push("", escapeMarkers(instruction));
|
|
5818
|
+
return lines.join("\n");
|
|
5819
|
+
}
|
|
5820
|
+
/** Drop the heading generate writes, so a round trip does not stack headings. */
|
|
5821
|
+
function stripGeneratedHeading(section, name) {
|
|
5822
|
+
const [firstLine, ...rest] = section.split("\n");
|
|
5823
|
+
if (firstLine?.trim() === `## ${name}`) return rest.join("\n").trim();
|
|
5824
|
+
return section.trim();
|
|
5825
|
+
}
|
|
5826
|
+
/**
|
|
5827
|
+
* Checks adapter for Cursor Bugbot (`.cursor/BUGBOT.md`).
|
|
5828
|
+
*
|
|
5829
|
+
* Bugbot takes one aggregated instruction file per directory rather than a file
|
|
5830
|
+
* per check, so every `.rulesync/checks/*.md` targeting Cursor collapses into
|
|
5831
|
+
* the repository-root `.cursor/BUGBOT.md` — hence {@link fromRulesyncChecks}
|
|
5832
|
+
* rather than the usual per-check conversion. Each check becomes one section:
|
|
5833
|
+
* an HTML-comment marker carrying the check name, an `## <name>` heading, and
|
|
5834
|
+
* the check body as the instruction text (the `description` is used when the
|
|
5835
|
+
* body is empty).
|
|
5836
|
+
*
|
|
5837
|
+
* Bugbot reads the file as free prose, so a check's `severity` and `tools` have
|
|
5838
|
+
* no equivalent there: they are not written and do not come back on import. So
|
|
5839
|
+
* is `description` whenever the check also has a body.
|
|
5840
|
+
*
|
|
5841
|
+
* Project scope only — Bugbot reads repository files, and there is no
|
|
5842
|
+
* user-level instruction file. Bugbot also merges nested `<dir>/.cursor/BUGBOT.md`
|
|
5843
|
+
* files found while traversing upward from changed files, but rulesync check
|
|
5844
|
+
* sources carry no directory-placement semantics, so only the root file is
|
|
5845
|
+
* generated.
|
|
5846
|
+
*
|
|
5847
|
+
* On import the markers split the file back into one check per section; content
|
|
5848
|
+
* before the first marker — and a hand-written file with no markers at all —
|
|
5849
|
+
* becomes a single `bugbot` check, so nothing in the file is dropped. A file
|
|
5850
|
+
* holding anything rulesync did not write is never deleted either (see
|
|
5851
|
+
* {@link canDeleteAuxiliaryFiles}), though generating checks for Cursor does
|
|
5852
|
+
* replace it — import first to keep what is there, which is warned about.
|
|
5853
|
+
*
|
|
5854
|
+
* @see https://cursor.com/docs/bugbot
|
|
5855
|
+
*/
|
|
5856
|
+
var CursorCheck = class CursorCheck extends ToolCheck {
|
|
5857
|
+
static getSettablePaths(_options = {}) {
|
|
5858
|
+
return {
|
|
5859
|
+
relativeDirPath: CURSOR_DIR,
|
|
5860
|
+
relativeFilePath: CURSOR_BUGBOT_FILE_NAME
|
|
5861
|
+
};
|
|
5862
|
+
}
|
|
5863
|
+
static isTargetedByRulesyncCheck(rulesyncCheck) {
|
|
5864
|
+
return this.isTargetedByRulesyncCheckDefault({
|
|
5865
|
+
rulesyncCheck,
|
|
5866
|
+
toolTarget: "cursor"
|
|
5867
|
+
});
|
|
5868
|
+
}
|
|
5869
|
+
/**
|
|
5870
|
+
* Ownership guard the processor consults before it deletes anything for this
|
|
5871
|
+
* tool. `.cursor/BUGBOT.md` is a file Cursor's own documentation tells users
|
|
5872
|
+
* to hand-write, so anything in it that rulesync did not write is not
|
|
5873
|
+
* rulesync's to remove — dropping the last check targeting Cursor must not
|
|
5874
|
+
* take somebody's hand-written review instructions with it. Deletion is
|
|
5875
|
+
* therefore allowed only for a file that is nothing but generated sections:
|
|
5876
|
+
* one that carries no marker at all, or that carries hand-written text ahead
|
|
5877
|
+
* of the first marker, stays.
|
|
5878
|
+
*/
|
|
5879
|
+
static async canDeleteAuxiliaryFiles({ outputRoot }) {
|
|
5880
|
+
const paths = CursorCheck.getSettablePaths();
|
|
5881
|
+
const fileContent = await readFileContentOrNull(join(outputRoot, paths.relativeDirPath, paths.relativeFilePath ?? "BUGBOT.md"));
|
|
5882
|
+
if (fileContent === null) return true;
|
|
5883
|
+
const firstMarkerStart = findMarkers(fileContent)[0]?.start;
|
|
5884
|
+
if (firstMarkerStart === void 0) return false;
|
|
5885
|
+
return fileContent.slice(0, firstMarkerStart).trim().length === 0;
|
|
5886
|
+
}
|
|
5887
|
+
static fromRulesyncCheck(_params) {
|
|
5888
|
+
throw new Error("Cursor checks are built from all checks at once; use fromRulesyncChecks.");
|
|
5889
|
+
}
|
|
5890
|
+
static async fromRulesyncChecks({ outputRoot = process.cwd(), rulesyncChecks, global = false, logger }) {
|
|
5891
|
+
if (rulesyncChecks.length === 0) return [];
|
|
5892
|
+
const paths = CursorCheck.getSettablePaths({ global });
|
|
5893
|
+
const relativeFilePath = paths.relativeFilePath ?? "BUGBOT.md";
|
|
5894
|
+
const filePath = join(outputRoot, paths.relativeDirPath, relativeFilePath);
|
|
5895
|
+
const existingContent = await readFileContentOrNull(filePath) ?? "";
|
|
5896
|
+
const firstMarkerStart = findMarkers(existingContent)[0]?.start ?? existingContent.length;
|
|
5897
|
+
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.`);
|
|
5898
|
+
const fileContent = `${rulesyncChecks.map(renderSection).join("\n\n")}\n`;
|
|
5899
|
+
return [new CursorCheck({
|
|
5900
|
+
outputRoot,
|
|
5901
|
+
relativeDirPath: paths.relativeDirPath,
|
|
5902
|
+
relativeFilePath,
|
|
5903
|
+
fileContent,
|
|
5904
|
+
global
|
|
5905
|
+
})];
|
|
5906
|
+
}
|
|
5907
|
+
static async fromFile({ outputRoot = process.cwd(), global = false }) {
|
|
5908
|
+
const paths = CursorCheck.getSettablePaths({ global });
|
|
5909
|
+
const relativeFilePath = paths.relativeFilePath ?? "BUGBOT.md";
|
|
5910
|
+
const filePath = join(outputRoot, paths.relativeDirPath, relativeFilePath);
|
|
5911
|
+
return new CursorCheck({
|
|
5912
|
+
outputRoot,
|
|
5913
|
+
relativeDirPath: paths.relativeDirPath,
|
|
5914
|
+
relativeFilePath,
|
|
5915
|
+
fileContent: await readFileContentOrNull(filePath) ?? "",
|
|
5916
|
+
global
|
|
5917
|
+
});
|
|
5918
|
+
}
|
|
5919
|
+
static forDeletion({ outputRoot = process.cwd(), relativeDirPath, relativeFilePath, global = false }) {
|
|
5920
|
+
return new CursorCheck({
|
|
5921
|
+
outputRoot,
|
|
5922
|
+
relativeDirPath,
|
|
5923
|
+
relativeFilePath,
|
|
5924
|
+
fileContent: "",
|
|
5925
|
+
validate: false,
|
|
5926
|
+
global
|
|
5927
|
+
});
|
|
5928
|
+
}
|
|
5929
|
+
validate() {
|
|
5930
|
+
return {
|
|
5931
|
+
success: true,
|
|
5932
|
+
error: null
|
|
5933
|
+
};
|
|
5934
|
+
}
|
|
5935
|
+
toRulesyncCheck() {
|
|
5936
|
+
const first = this.toRulesyncChecks()[0];
|
|
5937
|
+
if (!first) throw new Error(`No check instructions found in ${join(this.getRelativeDirPath(), this.getRelativeFilePath())}.`);
|
|
5938
|
+
return first;
|
|
5939
|
+
}
|
|
5940
|
+
toRulesyncChecks() {
|
|
5941
|
+
const fileContent = this.getFileContent();
|
|
5942
|
+
const sections = [];
|
|
5943
|
+
const markers = findMarkers(fileContent);
|
|
5944
|
+
const preambleEnd = markers[0]?.start ?? fileContent.length;
|
|
5945
|
+
const preamble = fileContent.slice(0, preambleEnd).trim();
|
|
5946
|
+
if (preamble.length > 0) sections.push({
|
|
5947
|
+
name: FALLBACK_CHECK_NAME,
|
|
5948
|
+
content: unescapeMarkers(preamble)
|
|
5949
|
+
});
|
|
5950
|
+
for (const [index, marker] of markers.entries()) {
|
|
5951
|
+
const sectionEnd = markers[index + 1]?.start ?? fileContent.length;
|
|
5952
|
+
const markerName = marker.name.trim();
|
|
5953
|
+
const name = slugifyCheckName(markerName) || FALLBACK_CHECK_NAME;
|
|
5954
|
+
const content = stripGeneratedHeading(fileContent.slice(marker.end, sectionEnd).trim(), markerName);
|
|
5955
|
+
sections.push({
|
|
5956
|
+
name,
|
|
5957
|
+
content: unescapeMarkers(content)
|
|
5958
|
+
});
|
|
5959
|
+
}
|
|
5960
|
+
const used = /* @__PURE__ */ new Set();
|
|
5961
|
+
return sections.map(({ name, content }) => {
|
|
5962
|
+
let uniqueName = name;
|
|
5963
|
+
let suffix = 2;
|
|
5964
|
+
while (used.has(uniqueName)) {
|
|
5965
|
+
uniqueName = `${name}-${suffix}`;
|
|
5966
|
+
suffix += 1;
|
|
5967
|
+
}
|
|
5968
|
+
used.add(uniqueName);
|
|
5969
|
+
return new RulesyncCheck({
|
|
5970
|
+
outputRoot: ".",
|
|
5971
|
+
relativeDirPath: RULESYNC_CHECKS_RELATIVE_DIR_PATH,
|
|
5972
|
+
relativeFilePath: `${uniqueName}.md`,
|
|
5973
|
+
frontmatter: { targets: ["*"] },
|
|
5974
|
+
body: content
|
|
5975
|
+
});
|
|
5976
|
+
});
|
|
5977
|
+
}
|
|
5978
|
+
};
|
|
5979
|
+
//#endregion
|
|
5652
5980
|
//#region src/constants/hermesagent-paths.ts
|
|
5653
5981
|
/**
|
|
5654
5982
|
* Hermes Agent configuration-layout conventions.
|
|
@@ -6147,7 +6475,20 @@ const SHARED_CONFIG_OWNERSHIP = {
|
|
|
6147
6475
|
jsoncParseErrors: "error",
|
|
6148
6476
|
features: { permissions: {
|
|
6149
6477
|
kind: "replace-owned-keys",
|
|
6150
|
-
ownedKeys: [
|
|
6478
|
+
ownedKeys: [
|
|
6479
|
+
"chat.tools.terminal.autoApprove",
|
|
6480
|
+
"chat.tools.edits.autoApprove",
|
|
6481
|
+
"chat.tools.urls.autoApprove"
|
|
6482
|
+
]
|
|
6483
|
+
} }
|
|
6484
|
+
},
|
|
6485
|
+
".vscode/mcp.json": {
|
|
6486
|
+
format: "jsonc",
|
|
6487
|
+
invalidRootPolicy: "error",
|
|
6488
|
+
jsoncParseErrors: "error",
|
|
6489
|
+
features: { mcp: {
|
|
6490
|
+
kind: "replace-owned-keys",
|
|
6491
|
+
ownedKeys: ["servers"]
|
|
6151
6492
|
} }
|
|
6152
6493
|
},
|
|
6153
6494
|
".qwen/settings.json": {
|
|
@@ -6513,7 +6854,12 @@ const applyIgnoreReadDenies = (params) => {
|
|
|
6513
6854
|
const applyPermissions = (params) => {
|
|
6514
6855
|
const { settings, managedToolNames, toolNameOf, allow, ask, deny, logger } = params;
|
|
6515
6856
|
const current = parsePermissionsBlock(settings);
|
|
6516
|
-
const
|
|
6857
|
+
const emitted = /* @__PURE__ */ new Set([
|
|
6858
|
+
...allow,
|
|
6859
|
+
...ask,
|
|
6860
|
+
...deny
|
|
6861
|
+
]);
|
|
6862
|
+
const keepUnmanaged = (entries) => entries.filter((entry) => !managedToolNames.has(toolNameOf(entry)) && !emitted.has(entry));
|
|
6517
6863
|
if (logger && managedToolNames.has(READ_TOOL_NAME)) {
|
|
6518
6864
|
const overwrittenReadDenies = current.deny.filter((entry) => toolNameOf(entry) === READ_TOOL_NAME);
|
|
6519
6865
|
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.`);
|
|
@@ -6663,12 +7009,9 @@ function toRulesyncCheckFromGate({ gate, index, scope, editOnly }) {
|
|
|
6663
7009
|
body: ""
|
|
6664
7010
|
});
|
|
6665
7011
|
}
|
|
6666
|
-
function slugify(value) {
|
|
6667
|
-
return value.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 48).replace(/-+$/, "");
|
|
6668
|
-
}
|
|
6669
7012
|
function slugForGate({ gate, index, scope }) {
|
|
6670
|
-
const slug =
|
|
6671
|
-
return `${scope ? `${
|
|
7013
|
+
const slug = slugifyCheckName(typeof gate === "string" ? gate : typeof gate.name === "string" ? gate.name : typeof gate.command === "string" ? gate.command : "");
|
|
7014
|
+
return `${scope ? `${slugifyCheckName(scope.name)}-` : ""}${slug.length > 0 ? slug : "quality-gate"}-${index + 1}`;
|
|
6672
7015
|
}
|
|
6673
7016
|
/**
|
|
6674
7017
|
* Checks adapter for Takt (`.takt/config.yaml` project / `~/.takt/config.yaml`
|
|
@@ -6844,6 +7187,13 @@ const toolCheckFactories = /* @__PURE__ */ new Map([
|
|
|
6844
7187
|
filePattern: "*.md"
|
|
6845
7188
|
}
|
|
6846
7189
|
}],
|
|
7190
|
+
["cursor", {
|
|
7191
|
+
class: CursorCheck,
|
|
7192
|
+
meta: {
|
|
7193
|
+
supportsGlobal: false,
|
|
7194
|
+
filePattern: CURSOR_BUGBOT_FILE_NAME
|
|
7195
|
+
}
|
|
7196
|
+
}],
|
|
6847
7197
|
["hermesagent", {
|
|
6848
7198
|
class: HermesagentCheck,
|
|
6849
7199
|
meta: {
|
|
@@ -7259,6 +7609,7 @@ var AgentsmdCommand = class AgentsmdCommand extends SimulatedCommand {
|
|
|
7259
7609
|
const ANTIGRAVITY_DIR = ".agents";
|
|
7260
7610
|
const ANTIGRAVITY_SKILLS_DIR_PATH = join(ANTIGRAVITY_DIR, "skills");
|
|
7261
7611
|
const ANTIGRAVITY_WORKFLOWS_DIR_PATH = join(ANTIGRAVITY_DIR, "workflows");
|
|
7612
|
+
const ANTIGRAVITY_AGENTS_DIR_PATH = join(ANTIGRAVITY_DIR, "agents");
|
|
7262
7613
|
const ANTIGRAVITY_MCP_FILE_NAME = "mcp_config.json";
|
|
7263
7614
|
const ANTIGRAVITY_HOOKS_FILE_NAME = "hooks.json";
|
|
7264
7615
|
const ANTIGRAVITY_IGNORE_FILE_NAME = ".geminiignore";
|
|
@@ -7269,6 +7620,7 @@ const ANTIGRAVITY_CLI_PERMISSIONS_FILE_NAME = "settings.json";
|
|
|
7269
7620
|
const ANTIGRAVITY_CLI_GLOBAL_WORKFLOWS_DIR_PATH = join(ANTIGRAVITY_GEMINI_DIR, ANTIGRAVITY_CLI_PERMISSIONS_SUBDIR, "global_workflows");
|
|
7270
7621
|
const ANTIGRAVITY_GLOBAL_CONFIG_SUBDIR = "config";
|
|
7271
7622
|
const ANTIGRAVITY_GLOBAL_CONFIG_DIR_PATH = join(ANTIGRAVITY_GEMINI_DIR, ANTIGRAVITY_GLOBAL_CONFIG_SUBDIR);
|
|
7623
|
+
const ANTIGRAVITY_GLOBAL_AGENTS_DIR_PATH = join(ANTIGRAVITY_GLOBAL_CONFIG_DIR_PATH, "agents");
|
|
7272
7624
|
//#endregion
|
|
7273
7625
|
//#region src/constants/antigravity-cli-paths.ts
|
|
7274
7626
|
const ANTIGRAVITY_AGENTS_DIR = ANTIGRAVITY_DIR;
|
|
@@ -7827,6 +8179,7 @@ const CLAUDECODE_PLUGIN_HOOKS_DIR = "hooks";
|
|
|
7827
8179
|
const CLAUDECODE_PLUGIN_HOOKS_FILE_NAME = "hooks.json";
|
|
7828
8180
|
const ANTIGRAVITY_PLUGIN_RULES_DIR = "rules";
|
|
7829
8181
|
const ANTIGRAVITY_PLUGIN_SKILLS_DIR = "skills";
|
|
8182
|
+
const ANTIGRAVITY_PLUGIN_AGENTS_DIR = "agents";
|
|
7830
8183
|
const ANTIGRAVITY_PLUGIN_MCP_FILE_NAME = "mcp_config.json";
|
|
7831
8184
|
const ANTIGRAVITY_PLUGIN_HOOKS_FILE_NAME = "hooks.json";
|
|
7832
8185
|
//#endregion
|
|
@@ -8051,6 +8404,7 @@ const COPILOT_SKILLS_DIR_PATH = join(GITHUB_DIR, "skills");
|
|
|
8051
8404
|
const COPILOT_AGENTS_DIR_PATH = join(GITHUB_DIR, "agents");
|
|
8052
8405
|
const COPILOT_HOOKS_DIR_PATH = join(GITHUB_DIR, "hooks");
|
|
8053
8406
|
const COPILOT_HOOKS_FILE_NAME = "copilot-hooks.json";
|
|
8407
|
+
const COPILOT_GLOBAL_HOOKS_FILE_NAME = "copilot-ide-hooks.json";
|
|
8054
8408
|
const COPILOT_MCP_DIR = ".vscode";
|
|
8055
8409
|
const COPILOT_MCP_FILE_NAME = "mcp.json";
|
|
8056
8410
|
const COPILOT_VSCODE_SETTINGS_FILE_NAME = "settings.json";
|
|
@@ -8058,6 +8412,7 @@ const COPILOTCLI_MCP_FILE_NAME = "mcp-config.json";
|
|
|
8058
8412
|
const COPILOTCLI_PROJECT_MCP_FILE_NAME = "mcp.json";
|
|
8059
8413
|
const COPILOTCLI_AGENTS_DIR_PATH = join(COPILOT_DIR, "agents");
|
|
8060
8414
|
const COPILOTCLI_HOOKS_DIR_PATH = join(COPILOT_DIR, "hooks");
|
|
8415
|
+
const COPILOT_GLOBAL_HOOKS_DIR_PATH = COPILOTCLI_HOOKS_DIR_PATH;
|
|
8061
8416
|
const COPILOTCLI_HOOKS_FILE_NAME = "copilotcli-hooks.json";
|
|
8062
8417
|
const COPILOT_SKILLS_GLOBAL_DIR_PATH = join(COPILOT_DIR, "skills");
|
|
8063
8418
|
//#endregion
|
|
@@ -8181,17 +8536,6 @@ var CopilotCommand = class CopilotCommand extends ToolCommand {
|
|
|
8181
8536
|
}
|
|
8182
8537
|
};
|
|
8183
8538
|
//#endregion
|
|
8184
|
-
//#region src/constants/cursor-paths.ts
|
|
8185
|
-
const CURSOR_DIR = ".cursor";
|
|
8186
|
-
const CURSOR_COMMANDS_DIR_PATH = join(CURSOR_DIR, "commands");
|
|
8187
|
-
const CURSOR_SKILLS_DIR_PATH = join(CURSOR_DIR, "skills");
|
|
8188
|
-
const CURSOR_AGENTS_DIR_PATH = join(CURSOR_DIR, "agents");
|
|
8189
|
-
const CURSOR_MCP_FILE_NAME = "mcp.json";
|
|
8190
|
-
const CURSOR_HOOKS_FILE_NAME = "hooks.json";
|
|
8191
|
-
const CURSOR_IGNORE_FILE_NAME = ".cursorignore";
|
|
8192
|
-
const CURSOR_PERMISSIONS_FILE_NAME = "cli.json";
|
|
8193
|
-
const CURSOR_PERMISSIONS_GLOBAL_FILE_NAME = "cli-config.json";
|
|
8194
|
-
//#endregion
|
|
8195
8539
|
//#region src/features/commands/cursor-command.ts
|
|
8196
8540
|
const CursorCommandFrontmatterSchema = z.looseObject({
|
|
8197
8541
|
description: z.optional(z.string()),
|
|
@@ -11960,6 +12304,13 @@ function groupDefinitionsByMatcher(definitions) {
|
|
|
11960
12304
|
}
|
|
11961
12305
|
return byMatcher;
|
|
11962
12306
|
}
|
|
12307
|
+
/** `$CLAUDE_PROJECT_DIR` -> `${CLAUDE_PROJECT_DIR}`, the form the tool substitutes. */
|
|
12308
|
+
function bracePlaceholder(projectDirVar) {
|
|
12309
|
+
return `\${${projectDirVar.replace(/^\$/, "")}}`;
|
|
12310
|
+
}
|
|
12311
|
+
function stripSurroundingQuotes(value) {
|
|
12312
|
+
return value.replace(/^(["'])(.*)\1$/, "$2").replace(/^["']/, "");
|
|
12313
|
+
}
|
|
11963
12314
|
/**
|
|
11964
12315
|
* Apply the optional project directory variable prefix to a command string.
|
|
11965
12316
|
*/
|
|
@@ -11969,8 +12320,10 @@ function applyCommandPrefix({ def, converterConfig }) {
|
|
|
11969
12320
|
const unquotedCommand = trimmedCommand?.replace(/^["']/, "");
|
|
11970
12321
|
const isDotRelativeCommand = unquotedCommand?.startsWith(".") ?? false;
|
|
11971
12322
|
const isAbsoluteCommand = typeof unquotedCommand === "string" && (posix.isAbsolute(unquotedCommand) || win32.isAbsolute(unquotedCommand) || unquotedCommand.startsWith("~/"));
|
|
12323
|
+
const isExecForm = (converterConfig.arrayPassthroughFields?.some(({ canonical }) => canonical === "args") ?? false) && Array.isArray(def.args);
|
|
11972
12324
|
if (!(converterConfig.projectDirVar !== "" && typeof trimmedCommand === "string" && !trimmedCommand.startsWith("$") && !isAbsoluteCommand && (!converterConfig.prefixDotRelativeCommandsOnly || isDotRelativeCommand)) || typeof trimmedCommand !== "string") return def.command;
|
|
11973
12325
|
const relativeCommand = trimmedCommand.replace(/^(["'])\.\//, "$1").replace(/^\.\//, "");
|
|
12326
|
+
if (isExecForm) return `${bracePlaceholder(converterConfig.projectDirVar)}/${stripSurroundingQuotes(relativeCommand)}`;
|
|
11974
12327
|
return `"${converterConfig.projectDirVar}"/${relativeCommand}`;
|
|
11975
12328
|
}
|
|
11976
12329
|
/**
|
|
@@ -11978,8 +12331,11 @@ function applyCommandPrefix({ def, converterConfig }) {
|
|
|
11978
12331
|
* canonical field name to its (possibly renamed) tool field name. Only boolean
|
|
11979
12332
|
* values are carried through.
|
|
11980
12333
|
*/
|
|
11981
|
-
function emitBooleanPassthroughFields({ def, converterConfig }) {
|
|
11982
|
-
return Object.fromEntries((converterConfig.booleanPassthroughFields ?? []).filter(({ canonical
|
|
12334
|
+
function emitBooleanPassthroughFields({ def, hookType, converterConfig }) {
|
|
12335
|
+
return Object.fromEntries((converterConfig.booleanPassthroughFields ?? []).filter(({ canonical, commandOnly }) => {
|
|
12336
|
+
if (commandOnly === true && hookType !== "command") return false;
|
|
12337
|
+
return typeof def[canonical] === "boolean";
|
|
12338
|
+
}).map(({ canonical, tool }) => [tool, def[canonical]]));
|
|
11983
12339
|
}
|
|
11984
12340
|
/**
|
|
11985
12341
|
* Import the configured boolean passthrough fields back into canonical fields,
|
|
@@ -11993,8 +12349,11 @@ function importBooleanPassthroughFields({ h, converterConfig }) {
|
|
|
11993
12349
|
* canonical field name to its (possibly renamed) tool field name. Only non-empty
|
|
11994
12350
|
* string values are carried through.
|
|
11995
12351
|
*/
|
|
11996
|
-
function emitStringPassthroughFields({ def, converterConfig }) {
|
|
11997
|
-
return Object.fromEntries((converterConfig.stringPassthroughFields ?? []).filter(({ canonical
|
|
12352
|
+
function emitStringPassthroughFields({ def, hookType, converterConfig }) {
|
|
12353
|
+
return Object.fromEntries((converterConfig.stringPassthroughFields ?? []).filter(({ canonical, commandOnly }) => {
|
|
12354
|
+
if (commandOnly === true && hookType !== "command") return false;
|
|
12355
|
+
return typeof def[canonical] === "string" && def[canonical] !== "";
|
|
12356
|
+
}).map(({ canonical, tool }) => [tool, def[canonical]]));
|
|
11998
12357
|
}
|
|
11999
12358
|
/**
|
|
12000
12359
|
* Import the configured string passthrough fields back into canonical fields,
|
|
@@ -12007,8 +12366,11 @@ function importStringPassthroughFields({ h, converterConfig }) {
|
|
|
12007
12366
|
/**
|
|
12008
12367
|
* Emit the configured string-array passthrough fields on the tool side.
|
|
12009
12368
|
*/
|
|
12010
|
-
function emitArrayPassthroughFields({ def, converterConfig }) {
|
|
12011
|
-
return Object.fromEntries((converterConfig.arrayPassthroughFields ?? []).filter(({ canonical
|
|
12369
|
+
function emitArrayPassthroughFields({ def, hookType, converterConfig }) {
|
|
12370
|
+
return Object.fromEntries((converterConfig.arrayPassthroughFields ?? []).filter(({ canonical, commandOnly }) => {
|
|
12371
|
+
if (commandOnly === true && hookType !== "command") return false;
|
|
12372
|
+
return isStringArray(def[canonical]);
|
|
12373
|
+
}).map(({ canonical, tool }) => [tool, def[canonical]]));
|
|
12012
12374
|
}
|
|
12013
12375
|
/**
|
|
12014
12376
|
* Import the configured string-array passthrough fields, reversing
|
|
@@ -12085,14 +12447,17 @@ function buildToolHooks({ defs, converterConfig }) {
|
|
|
12085
12447
|
hooks.push({
|
|
12086
12448
|
...emitBooleanPassthroughFields({
|
|
12087
12449
|
def,
|
|
12450
|
+
hookType,
|
|
12088
12451
|
converterConfig
|
|
12089
12452
|
}),
|
|
12090
12453
|
...emitStringPassthroughFields({
|
|
12091
12454
|
def,
|
|
12455
|
+
hookType,
|
|
12092
12456
|
converterConfig
|
|
12093
12457
|
}),
|
|
12094
12458
|
...emitArrayPassthroughFields({
|
|
12095
12459
|
def,
|
|
12460
|
+
hookType,
|
|
12096
12461
|
converterConfig
|
|
12097
12462
|
}),
|
|
12098
12463
|
type: hookType,
|
|
@@ -12176,6 +12541,8 @@ function stripCommandPrefix({ command, converterConfig }) {
|
|
|
12176
12541
|
if (converterConfig.projectDirVar === "" || typeof cmd !== "string") return cmd;
|
|
12177
12542
|
const quotedPrefix = `"${converterConfig.projectDirVar}"/`;
|
|
12178
12543
|
if (cmd.startsWith(quotedPrefix)) return `./${cmd.slice(quotedPrefix.length)}`;
|
|
12544
|
+
const bracedPrefix = `${bracePlaceholder(converterConfig.projectDirVar)}/`;
|
|
12545
|
+
if (cmd.startsWith(bracedPrefix)) return `./${cmd.slice(bracedPrefix.length)}`;
|
|
12179
12546
|
if (cmd.includes(`${converterConfig.projectDirVar}/`)) {
|
|
12180
12547
|
const escapedVar = converterConfig.projectDirVar.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
12181
12548
|
return cmd.replace(new RegExp(`^${escapedVar}\\/?`), "./");
|
|
@@ -12723,7 +13090,10 @@ const CLAUDE_CONVERTER_CONFIG = {
|
|
|
12723
13090
|
"taskCreated",
|
|
12724
13091
|
"taskCompleted",
|
|
12725
13092
|
"teammateIdle",
|
|
12726
|
-
"cwdChanged"
|
|
13093
|
+
"cwdChanged",
|
|
13094
|
+
"beforeSubmitPrompt",
|
|
13095
|
+
"stop",
|
|
13096
|
+
"directoryAdded"
|
|
12727
13097
|
]),
|
|
12728
13098
|
supportedHookTypes: /* @__PURE__ */ new Set([
|
|
12729
13099
|
"command",
|
|
@@ -12733,9 +13103,45 @@ const CLAUDE_CONVERTER_CONFIG = {
|
|
|
12733
13103
|
"agent"
|
|
12734
13104
|
]),
|
|
12735
13105
|
emitsPromptModel: true,
|
|
12736
|
-
stringPassthroughFields: [
|
|
12737
|
-
|
|
12738
|
-
|
|
13106
|
+
stringPassthroughFields: [
|
|
13107
|
+
{
|
|
13108
|
+
canonical: "if",
|
|
13109
|
+
tool: "if"
|
|
13110
|
+
},
|
|
13111
|
+
{
|
|
13112
|
+
canonical: "statusMessage",
|
|
13113
|
+
tool: "statusMessage"
|
|
13114
|
+
},
|
|
13115
|
+
{
|
|
13116
|
+
canonical: "shell",
|
|
13117
|
+
tool: "shell",
|
|
13118
|
+
commandOnly: true
|
|
13119
|
+
}
|
|
13120
|
+
],
|
|
13121
|
+
booleanPassthroughFields: [
|
|
13122
|
+
{
|
|
13123
|
+
canonical: "once",
|
|
13124
|
+
tool: "once"
|
|
13125
|
+
},
|
|
13126
|
+
{
|
|
13127
|
+
canonical: "async",
|
|
13128
|
+
tool: "async",
|
|
13129
|
+
commandOnly: true
|
|
13130
|
+
},
|
|
13131
|
+
{
|
|
13132
|
+
canonical: "asyncRewake",
|
|
13133
|
+
tool: "asyncRewake",
|
|
13134
|
+
commandOnly: true
|
|
13135
|
+
},
|
|
13136
|
+
{
|
|
13137
|
+
canonical: "continueOnBlock",
|
|
13138
|
+
tool: "continueOnBlock"
|
|
13139
|
+
}
|
|
13140
|
+
],
|
|
13141
|
+
arrayPassthroughFields: [{
|
|
13142
|
+
canonical: "args",
|
|
13143
|
+
tool: "args",
|
|
13144
|
+
commandOnly: true
|
|
12739
13145
|
}]
|
|
12740
13146
|
};
|
|
12741
13147
|
var ClaudecodeHooks = class extends ToolHooks {
|
|
@@ -12843,7 +13249,14 @@ const CODEXCLI_CONVERTER_CONFIG = {
|
|
|
12843
13249
|
toolToCanonicalEventNames: CODEXCLI_TO_CANONICAL_EVENT_NAMES,
|
|
12844
13250
|
projectDirVar: "",
|
|
12845
13251
|
supportedHookTypes: /* @__PURE__ */ new Set(["command"]),
|
|
12846
|
-
passthroughFields: ["name", "description"]
|
|
13252
|
+
passthroughFields: ["name", "description"],
|
|
13253
|
+
stringPassthroughFields: [{
|
|
13254
|
+
canonical: "commandWindows",
|
|
13255
|
+
tool: "commandWindows"
|
|
13256
|
+
}, {
|
|
13257
|
+
canonical: "statusMessage",
|
|
13258
|
+
tool: "statusMessage"
|
|
13259
|
+
}]
|
|
12847
13260
|
};
|
|
12848
13261
|
/**
|
|
12849
13262
|
* Build the content for `.codex/config.toml`, cleaning up the deprecated `codex_hooks` key.
|
|
@@ -13091,7 +13504,11 @@ var CopilotHooks = class CopilotHooks extends ToolHooks {
|
|
|
13091
13504
|
fileContent: params.fileContent ?? "{}"
|
|
13092
13505
|
});
|
|
13093
13506
|
}
|
|
13094
|
-
static getSettablePaths(
|
|
13507
|
+
static getSettablePaths({ global = false } = {}) {
|
|
13508
|
+
if (global) return {
|
|
13509
|
+
relativeDirPath: COPILOT_GLOBAL_HOOKS_DIR_PATH,
|
|
13510
|
+
relativeFilePath: COPILOT_GLOBAL_HOOKS_FILE_NAME
|
|
13511
|
+
};
|
|
13095
13512
|
return {
|
|
13096
13513
|
relativeDirPath: COPILOT_HOOKS_DIR_PATH,
|
|
13097
13514
|
relativeFilePath: COPILOT_HOOKS_FILE_NAME
|
|
@@ -13105,11 +13522,12 @@ var CopilotHooks = class CopilotHooks extends ToolHooks {
|
|
|
13105
13522
|
relativeDirPath: paths.relativeDirPath,
|
|
13106
13523
|
relativeFilePath: paths.relativeFilePath,
|
|
13107
13524
|
fileContent,
|
|
13108
|
-
validate
|
|
13525
|
+
validate,
|
|
13526
|
+
global
|
|
13109
13527
|
});
|
|
13110
13528
|
}
|
|
13111
|
-
static async fromRulesyncHooks({ outputRoot = process.cwd(), rulesyncHooks, validate = true }) {
|
|
13112
|
-
const paths = CopilotHooks.getSettablePaths();
|
|
13529
|
+
static async fromRulesyncHooks({ outputRoot = process.cwd(), rulesyncHooks, validate = true, global = false }) {
|
|
13530
|
+
const paths = CopilotHooks.getSettablePaths({ global });
|
|
13113
13531
|
const copilotHooks = canonicalToCopilotHooks(rulesyncHooks.getJson());
|
|
13114
13532
|
const fileContent = JSON.stringify({
|
|
13115
13533
|
version: 1,
|
|
@@ -13120,7 +13538,8 @@ var CopilotHooks = class CopilotHooks extends ToolHooks {
|
|
|
13120
13538
|
relativeDirPath: paths.relativeDirPath,
|
|
13121
13539
|
relativeFilePath: paths.relativeFilePath,
|
|
13122
13540
|
fileContent,
|
|
13123
|
-
validate
|
|
13541
|
+
validate,
|
|
13542
|
+
global
|
|
13124
13543
|
});
|
|
13125
13544
|
}
|
|
13126
13545
|
toRulesyncHooks(options) {
|
|
@@ -13164,10 +13583,16 @@ var CopilotHooks = class CopilotHooks extends ToolHooks {
|
|
|
13164
13583
|
* (`sessionStart`, `sessionEnd`, `userPromptSubmitted`, `preToolUse`,
|
|
13165
13584
|
* `postToolUse`, `postToolUseFailure`, `agentStop`, `subagentStart`,
|
|
13166
13585
|
* `subagentStop`, `errorOccurred`, `preCompact`, `permissionRequest`,
|
|
13167
|
-
* `notification`, `preMcpToolCall`). Each entry
|
|
13586
|
+
* `notification`, `userPromptTransformed`, `preMcpToolCall`). Each entry
|
|
13587
|
+
* supports three hook types:
|
|
13168
13588
|
*
|
|
13169
13589
|
* - `command` — the `bash` / `powershell` command-field shape with optional
|
|
13170
|
-
* `timeoutSec`, plus optional `cwd` / `env`.
|
|
13590
|
+
* `timeoutSec`, plus optional `cwd` / `env`. Upstream also accepts the
|
|
13591
|
+
* portable `command` field (copied to both shells when neither is present)
|
|
13592
|
+
* and `timeout` as an alias for `timeoutSec`; rulesync reads both on import.
|
|
13593
|
+
* On generate the canonical `shell` selector picks `bash` or `powershell`,
|
|
13594
|
+
* and without it the portable `command` field is written — so the generated
|
|
13595
|
+
* file does not depend on the machine rulesync ran on.
|
|
13171
13596
|
* - `prompt` — a `prompt` string (Copilot CLI only honors prompt hooks on
|
|
13172
13597
|
* `sessionStart`, so prompt hooks on other events are skipped).
|
|
13173
13598
|
* - `http` — `url` / `headers` / `allowedEnvVars` with optional `timeoutSec`.
|
|
@@ -13193,11 +13618,11 @@ var CopilotHooks = class CopilotHooks extends ToolHooks {
|
|
|
13193
13618
|
* all rulesync-managed Copilot CLI files under the single `~/.copilot/`
|
|
13194
13619
|
* root and will revisit if the spec later mandates an alternate layout.
|
|
13195
13620
|
*
|
|
13196
|
-
* Hook entries on
|
|
13197
|
-
*
|
|
13198
|
-
* emitted on those events and dropped (with a warning) on any other event,
|
|
13199
|
-
* which never honors matchers.
|
|
13200
|
-
*
|
|
13621
|
+
* Hook entries on the six matcher-aware events (see
|
|
13622
|
+
* {@link COPILOTCLI_MATCHER_EVENTS}) may carry an optional `matcher` regex; it
|
|
13623
|
+
* is emitted on those events and dropped (with a warning) on any other event,
|
|
13624
|
+
* which never honors matchers.
|
|
13625
|
+
* Reference: https://docs.github.com/en/copilot/reference/hooks-reference
|
|
13201
13626
|
*
|
|
13202
13627
|
* The output JSON schema and platform-specific `bash` / `powershell` command
|
|
13203
13628
|
* field selection match `copilot-hooks.ts`, but the event surface diverges (the
|
|
@@ -13212,19 +13637,30 @@ var CopilotHooks = class CopilotHooks extends ToolHooks {
|
|
|
13212
13637
|
* `Edit|Write`) are now honored instead of silently dropped").
|
|
13213
13638
|
* @see https://docs.github.com/en/copilot/reference/hooks-reference
|
|
13214
13639
|
*/
|
|
13215
|
-
const COPILOTCLI_MATCHER_EVENTS = /* @__PURE__ */ new Set([
|
|
13640
|
+
const COPILOTCLI_MATCHER_EVENTS = /* @__PURE__ */ new Set([
|
|
13641
|
+
"preToolUse",
|
|
13642
|
+
"postToolUse",
|
|
13643
|
+
"notification",
|
|
13644
|
+
"permissionRequest",
|
|
13645
|
+
"preCompact",
|
|
13646
|
+
"subagentStart"
|
|
13647
|
+
]);
|
|
13648
|
+
/** Human-readable list of the matcher-aware events, for the drop warning. */
|
|
13649
|
+
const COPILOTCLI_MATCHER_EVENTS_LABEL = [...COPILOTCLI_MATCHER_EVENTS].join("/");
|
|
13216
13650
|
const CopilotCliHookEntrySchema = z.looseObject({
|
|
13217
13651
|
type: z._default(z.string(), "command"),
|
|
13218
13652
|
matcher: z.optional(z.string()),
|
|
13219
13653
|
bash: z.optional(z.string()),
|
|
13220
13654
|
powershell: z.optional(z.string()),
|
|
13655
|
+
command: z.optional(z.string()),
|
|
13221
13656
|
prompt: z.optional(z.string()),
|
|
13222
13657
|
url: z.optional(z.string()),
|
|
13223
13658
|
headers: z.optional(z.record(z.string(), z.string())),
|
|
13224
13659
|
allowedEnvVars: z.optional(z.array(z.string())),
|
|
13225
13660
|
cwd: z.optional(z.string()),
|
|
13226
13661
|
env: z.optional(z.record(z.string(), z.string())),
|
|
13227
|
-
timeoutSec: z.optional(z.number())
|
|
13662
|
+
timeoutSec: z.optional(z.number()),
|
|
13663
|
+
timeout: z.optional(z.number())
|
|
13228
13664
|
});
|
|
13229
13665
|
/** Filter the shared config hooks down to events the Copilot CLI supports. */
|
|
13230
13666
|
function filterSupportedCopilotCliHooks(hooks) {
|
|
@@ -13235,21 +13671,21 @@ function filterSupportedCopilotCliHooks(hooks) {
|
|
|
13235
13671
|
}
|
|
13236
13672
|
/**
|
|
13237
13673
|
* Resolve the `matcher` part for an exported entry. Copilot CLI honors `matcher`
|
|
13238
|
-
* only on
|
|
13239
|
-
* silently dropped by the CLI, so we drop it here with
|
|
13240
|
-
* emitting a dead field.
|
|
13674
|
+
* only on the events listed in {@link COPILOTCLI_MATCHER_EVENTS}; on any other
|
|
13675
|
+
* event a matcher would be silently dropped by the CLI, so we drop it here with
|
|
13676
|
+
* a warning rather than emitting a dead field.
|
|
13241
13677
|
*/
|
|
13242
13678
|
function resolveExportMatcherPart({ matcher, matcherSupported, eventName, logger }) {
|
|
13243
13679
|
if (matcher === void 0 || matcher === null || matcher === "") return {};
|
|
13244
13680
|
if (matcherSupported) return { matcher };
|
|
13245
|
-
logger?.warn(`Copilot CLI hook matchers are only honored on
|
|
13681
|
+
logger?.warn(`Copilot CLI hook matchers are only honored on ${COPILOTCLI_MATCHER_EVENTS_LABEL}; dropping matcher "${matcher}" on '${eventName}'.`);
|
|
13246
13682
|
return {};
|
|
13247
13683
|
}
|
|
13248
13684
|
/**
|
|
13249
13685
|
* Build the exported entries for a single canonical event. Returns an empty
|
|
13250
13686
|
* array when no entries are emitted (e.g. all prompt hooks were skipped).
|
|
13251
13687
|
*/
|
|
13252
|
-
function buildCopilotCliEntriesForEvent({ eventName, definitions, canonicalSchemaKeys,
|
|
13688
|
+
function buildCopilotCliEntriesForEvent({ eventName, definitions, canonicalSchemaKeys, logger }) {
|
|
13253
13689
|
const matcherSupported = COPILOTCLI_MATCHER_EVENTS.has(eventName);
|
|
13254
13690
|
const entries = [];
|
|
13255
13691
|
for (const def of definitions) {
|
|
@@ -13285,22 +13721,24 @@ function buildCopilotCliEntriesForEvent({ eventName, definitions, canonicalSchem
|
|
|
13285
13721
|
...timeoutPart,
|
|
13286
13722
|
...rest
|
|
13287
13723
|
});
|
|
13288
|
-
else if (hookType === "command")
|
|
13289
|
-
|
|
13290
|
-
|
|
13291
|
-
|
|
13292
|
-
|
|
13293
|
-
|
|
13294
|
-
|
|
13295
|
-
|
|
13296
|
-
|
|
13297
|
-
|
|
13724
|
+
else if (hookType === "command") {
|
|
13725
|
+
const commandField = def.shell ?? "command";
|
|
13726
|
+
entries.push({
|
|
13727
|
+
type: "command",
|
|
13728
|
+
...matcherPart,
|
|
13729
|
+
...compact({
|
|
13730
|
+
[commandField]: def.command,
|
|
13731
|
+
env: def.env
|
|
13732
|
+
}),
|
|
13733
|
+
...timeoutPart,
|
|
13734
|
+
...rest
|
|
13735
|
+
});
|
|
13736
|
+
}
|
|
13298
13737
|
}
|
|
13299
13738
|
return entries;
|
|
13300
13739
|
}
|
|
13301
13740
|
function canonicalToCopilotCliHooks(config, logger) {
|
|
13302
13741
|
const canonicalSchemaKeys = Object.keys(HookDefinitionSchema.shape);
|
|
13303
|
-
const commandField = process.platform === "win32" ? "powershell" : "bash";
|
|
13304
13742
|
const effectiveHooks = {
|
|
13305
13743
|
...filterSupportedCopilotCliHooks(config.hooks),
|
|
13306
13744
|
...config.copilot?.hooks,
|
|
@@ -13313,7 +13751,6 @@ function canonicalToCopilotCliHooks(config, logger) {
|
|
|
13313
13751
|
eventName,
|
|
13314
13752
|
definitions,
|
|
13315
13753
|
canonicalSchemaKeys,
|
|
13316
|
-
commandField,
|
|
13317
13754
|
logger
|
|
13318
13755
|
});
|
|
13319
13756
|
if (entries.length > 0) out[copilotEventName] = entries;
|
|
@@ -13330,6 +13767,13 @@ function importPassthrough(entry) {
|
|
|
13330
13767
|
if (entry.env !== void 0) passthrough.env = entry.env;
|
|
13331
13768
|
return passthrough;
|
|
13332
13769
|
}
|
|
13770
|
+
/**
|
|
13771
|
+
* Resolve the canonical command and its `shell` selector from an imported entry.
|
|
13772
|
+
*
|
|
13773
|
+
* A shell-specific field carries its `shell` through so re-export writes the
|
|
13774
|
+
* same field back. An entry using only the portable `command` field leaves
|
|
13775
|
+
* `shell` unset, which re-export renders as the portable field again.
|
|
13776
|
+
*/
|
|
13333
13777
|
function resolveImportCommand(entry, logger) {
|
|
13334
13778
|
const hasBash = typeof entry.bash === "string";
|
|
13335
13779
|
const hasPowershell = typeof entry.powershell === "string";
|
|
@@ -13338,9 +13782,22 @@ function resolveImportCommand(entry, logger) {
|
|
|
13338
13782
|
const chosen = isWindows ? "powershell" : "bash";
|
|
13339
13783
|
const ignored = isWindows ? "bash" : "powershell";
|
|
13340
13784
|
logger?.warn(`Copilot CLI hook has both bash and powershell commands; using ${chosen} and ignoring ${ignored} on this platform.`);
|
|
13341
|
-
return isWindows ?
|
|
13342
|
-
|
|
13343
|
-
|
|
13785
|
+
return isWindows ? {
|
|
13786
|
+
command: entry.powershell,
|
|
13787
|
+
shell: "powershell"
|
|
13788
|
+
} : {
|
|
13789
|
+
command: entry.bash,
|
|
13790
|
+
shell: "bash"
|
|
13791
|
+
};
|
|
13792
|
+
} else if (hasBash) return {
|
|
13793
|
+
command: entry.bash,
|
|
13794
|
+
shell: "bash"
|
|
13795
|
+
};
|
|
13796
|
+
else if (hasPowershell) return {
|
|
13797
|
+
command: entry.powershell,
|
|
13798
|
+
shell: "powershell"
|
|
13799
|
+
};
|
|
13800
|
+
return typeof entry.command === "string" ? { command: entry.command } : {};
|
|
13344
13801
|
}
|
|
13345
13802
|
function copilotCliHooksToCanonical(rawHooks, logger) {
|
|
13346
13803
|
if (rawHooks === null || rawHooks === void 0 || typeof rawHooks !== "object") return {};
|
|
@@ -13353,7 +13810,7 @@ function copilotCliHooksToCanonical(rawHooks, logger) {
|
|
|
13353
13810
|
const parseResult = CopilotCliHookEntrySchema.safeParse(rawEntry);
|
|
13354
13811
|
if (!parseResult.success) continue;
|
|
13355
13812
|
const entry = parseResult.data;
|
|
13356
|
-
const timeout = entry.timeoutSec;
|
|
13813
|
+
const timeout = entry.timeoutSec ?? entry.timeout;
|
|
13357
13814
|
const timeoutPart = timeout !== void 0 ? { timeout } : {};
|
|
13358
13815
|
const matcherPart = entry.matcher !== void 0 && entry.matcher !== "" ? { matcher: entry.matcher } : {};
|
|
13359
13816
|
const passthrough = importPassthrough(entry);
|
|
@@ -13372,10 +13829,11 @@ function copilotCliHooksToCanonical(rawHooks, logger) {
|
|
|
13372
13829
|
...passthrough
|
|
13373
13830
|
});
|
|
13374
13831
|
else {
|
|
13375
|
-
const command = resolveImportCommand(entry, logger);
|
|
13832
|
+
const { command, shell } = resolveImportCommand(entry, logger);
|
|
13376
13833
|
defs.push({
|
|
13377
13834
|
type: "command",
|
|
13378
13835
|
...command !== void 0 && { command },
|
|
13836
|
+
...shell !== void 0 && { shell },
|
|
13379
13837
|
...matcherPart,
|
|
13380
13838
|
...timeoutPart,
|
|
13381
13839
|
...passthrough
|
|
@@ -14515,7 +14973,23 @@ var JunieHooks = class JunieHooks extends ToolHooks {
|
|
|
14515
14973
|
};
|
|
14516
14974
|
//#endregion
|
|
14517
14975
|
//#region src/features/hooks/opencode-style-generator.ts
|
|
14518
|
-
|
|
14976
|
+
/**
|
|
14977
|
+
* Tool events emitted as named `(input, ...)` hooks rather than through the
|
|
14978
|
+
* generic `event.type` dispatch, mapped to the expression a hook's `matcher`
|
|
14979
|
+
* regex is tested against — or `null` when the hook has no matchable subject,
|
|
14980
|
+
* in which case a matcher is dropped rather than compiled against a field that
|
|
14981
|
+
* does not exist.
|
|
14982
|
+
*
|
|
14983
|
+
* `experimental.session.compacting` receives `(input, output)` and exposes no
|
|
14984
|
+
* per-invocation identifier worth matching on, so it takes `null`.
|
|
14985
|
+
*
|
|
14986
|
+
* @see https://opencode.ai/docs/plugins/
|
|
14987
|
+
*/
|
|
14988
|
+
const NAMED_HOOK_MATCHER_SUBJECTS = {
|
|
14989
|
+
"tool.execute.before": "input.tool",
|
|
14990
|
+
"tool.execute.after": "input.tool",
|
|
14991
|
+
"experimental.session.compacting": null
|
|
14992
|
+
};
|
|
14519
14993
|
function escapeForTemplateLiteral(command) {
|
|
14520
14994
|
return command.replace(/\\/g, "\\\\").replace(/`/g, "\\`").replace(/\$\{/g, "\\${");
|
|
14521
14995
|
}
|
|
@@ -14548,7 +15022,7 @@ function collectOpencodeStyleHandlers({ effectiveHooks, eventMap, namedEventHand
|
|
|
14548
15022
|
});
|
|
14549
15023
|
}
|
|
14550
15024
|
if (handlers.length > 0) {
|
|
14551
|
-
const grouped =
|
|
15025
|
+
const grouped = Object.hasOwn(NAMED_HOOK_MATCHER_SUBJECTS, toolEvent) ? namedEventHandlers : genericEventHandlers;
|
|
14552
15026
|
const existing = grouped[toolEvent];
|
|
14553
15027
|
if (existing) existing.push(...handlers);
|
|
14554
15028
|
else grouped[toolEvent] = handlers;
|
|
@@ -14577,14 +15051,15 @@ function buildGenericEventBodyLines(genericEventHandlers) {
|
|
|
14577
15051
|
function buildNamedEventBodyLines(namedEventHandlers) {
|
|
14578
15052
|
const bodyLines = [];
|
|
14579
15053
|
for (const [eventName, handlers] of Object.entries(namedEventHandlers)) {
|
|
15054
|
+
const matcherSubject = NAMED_HOOK_MATCHER_SUBJECTS[eventName] ?? null;
|
|
14580
15055
|
bodyLines.push(` "${eventName}": async (input) => {`);
|
|
14581
15056
|
for (const handler of handlers) {
|
|
14582
15057
|
const escapedCommand = escapeForTemplateLiteral(handler.command);
|
|
14583
|
-
if (handler.matcher) {
|
|
15058
|
+
if (handler.matcher && matcherSubject !== null) {
|
|
14584
15059
|
const safeMatcher = validateAndSanitizeMatcher(handler.matcher);
|
|
14585
15060
|
bodyLines.push(" {");
|
|
14586
15061
|
bodyLines.push(` const __re = new RegExp("${safeMatcher}");`);
|
|
14587
|
-
bodyLines.push(` if (__re.test(
|
|
15062
|
+
bodyLines.push(` if (__re.test(${matcherSubject})) {`);
|
|
14588
15063
|
bodyLines.push(` await $\`${escapedCommand}\`;`);
|
|
14589
15064
|
bodyLines.push(" }");
|
|
14590
15065
|
bodyLines.push(" }");
|
|
@@ -16220,7 +16695,7 @@ const toolHooksFactories = /* @__PURE__ */ new Map([
|
|
|
16220
16695
|
class: CopilotHooks,
|
|
16221
16696
|
meta: {
|
|
16222
16697
|
supportsProject: true,
|
|
16223
|
-
supportsGlobal:
|
|
16698
|
+
supportsGlobal: true,
|
|
16224
16699
|
supportsImport: true
|
|
16225
16700
|
},
|
|
16226
16701
|
supportedEvents: COPILOT_HOOK_EVENTS,
|
|
@@ -18697,8 +19172,20 @@ const RULESYNC_TO_CODEX_FIELD_MAP = {
|
|
|
18697
19172
|
disabledTools: "disabled_tools",
|
|
18698
19173
|
envVars: "env_vars"
|
|
18699
19174
|
};
|
|
19175
|
+
const RULESYNC_TO_CODEX_SCALAR_FIELD_MAP = { experimentalEnvironment: "experimental_environment" };
|
|
19176
|
+
const CODEX_TO_RULESYNC_SCALAR_FIELD_MAP = Object.fromEntries(Object.entries(RULESYNC_TO_CODEX_SCALAR_FIELD_MAP).map(([canonical, codex]) => [codex, canonical]));
|
|
18700
19177
|
const MAX_REMOVE_EMPTY_ENTRIES_DEPTH$1 = 32;
|
|
18701
19178
|
/**
|
|
19179
|
+
* `env_vars` entries are either a bare variable name or `{ name, source }`,
|
|
19180
|
+
* where `source = "remote"` reads the variable from the remote executor
|
|
19181
|
+
* environment. The other renamed keys (`enabled_tools`, `disabled_tools`) stay
|
|
19182
|
+
* plain string arrays, so the widened check applies to `env_vars` only.
|
|
19183
|
+
* @see https://learn.chatgpt.com/docs/extend/mcp
|
|
19184
|
+
*/
|
|
19185
|
+
function isValidRenamedArray(key, value) {
|
|
19186
|
+
return key === "env_vars" || key === "envVars" ? isEnvVarEntryArray(value) : isStringArray$1(value);
|
|
19187
|
+
}
|
|
19188
|
+
/**
|
|
18702
19189
|
* Translate a server's `oauth` table from the canonical rulesync shape (Claude
|
|
18703
19190
|
* Code style camelCase) into the shape Codex CLI understands. Codex expects the
|
|
18704
19191
|
* OAuth client id under snake_case `client_id`; without it `codex mcp login`
|
|
@@ -18758,8 +19245,12 @@ function convertFromCodexFormat(codexMcp) {
|
|
|
18758
19245
|
} else if (key === "oauth" && isRecord(value)) converted[key] = mapOauthFromCodex(value);
|
|
18759
19246
|
else if (Object.hasOwn(CODEX_TO_RULESYNC_FIELD_MAP, key)) {
|
|
18760
19247
|
const mappedKey = CODEX_TO_RULESYNC_FIELD_MAP[key];
|
|
18761
|
-
if (mappedKey) if (
|
|
19248
|
+
if (mappedKey) if (isValidRenamedArray(key, value)) converted[mappedKey] = value;
|
|
18762
19249
|
else warnWithFallback(void 0, `Ignored malformed array for ${key} in MCP server ${name}`);
|
|
19250
|
+
} else if (Object.hasOwn(CODEX_TO_RULESYNC_SCALAR_FIELD_MAP, key)) {
|
|
19251
|
+
const mappedKey = CODEX_TO_RULESYNC_SCALAR_FIELD_MAP[key];
|
|
19252
|
+
if (mappedKey) if (typeof value === "string") converted[mappedKey] = value;
|
|
19253
|
+
else warnWithFallback(void 0, `Ignored malformed value for ${key} in MCP server ${name}: expected a string`);
|
|
18763
19254
|
} else converted[key] = value;
|
|
18764
19255
|
}
|
|
18765
19256
|
result[name] = converted;
|
|
@@ -18781,8 +19272,12 @@ function convertToCodexFormat(mcpServers) {
|
|
|
18781
19272
|
} else if (key === "oauth" && isRecord(value)) converted[key] = mapOauthToCodex(value);
|
|
18782
19273
|
else if (Object.hasOwn(RULESYNC_TO_CODEX_FIELD_MAP, key)) {
|
|
18783
19274
|
const mappedKey = RULESYNC_TO_CODEX_FIELD_MAP[key];
|
|
18784
|
-
if (mappedKey) if (
|
|
19275
|
+
if (mappedKey) if (isValidRenamedArray(key, value)) converted[mappedKey] = value;
|
|
18785
19276
|
else warnWithFallback(void 0, `[CodexCliMcp] Skipping invalid value type for mapped key '${key}': expected string array, got ${typeof value}`);
|
|
19277
|
+
} else if (Object.hasOwn(RULESYNC_TO_CODEX_SCALAR_FIELD_MAP, key)) {
|
|
19278
|
+
const mappedKey = RULESYNC_TO_CODEX_SCALAR_FIELD_MAP[key];
|
|
19279
|
+
if (mappedKey) if (typeof value === "string") converted[mappedKey] = value;
|
|
19280
|
+
else warnWithFallback(void 0, `[CodexCliMcp] Skipping invalid value type for mapped key '${key}': expected string, got ${typeof value}`);
|
|
18786
19281
|
} else converted[key] = value;
|
|
18787
19282
|
}
|
|
18788
19283
|
const previousName = originalNames.get(codexName);
|
|
@@ -18853,7 +19348,9 @@ var CodexcliMcp = class CodexcliMcp extends ToolMcp {
|
|
|
18853
19348
|
const rawServer = isRecord(rawMcpServers) ? rawMcpServers[serverName] : void 0;
|
|
18854
19349
|
return [serverName, {
|
|
18855
19350
|
...serverConfig,
|
|
18856
|
-
...isRecord(rawServer) &&
|
|
19351
|
+
...isRecord(rawServer) && isEnvVarEntryArray(rawServer.envVars) ? { envVars: rawServer.envVars } : {},
|
|
19352
|
+
...isRecord(rawServer) && typeof rawServer.experimental_environment === "string" ? { experimentalEnvironment: rawServer.experimental_environment } : {},
|
|
19353
|
+
...isRecord(rawServer) && typeof rawServer.experimentalEnvironment === "string" ? { experimentalEnvironment: rawServer.experimentalEnvironment } : {}
|
|
18857
19354
|
}];
|
|
18858
19355
|
})));
|
|
18859
19356
|
const filteredMcpServers = this.removeEmptyEntries(converted);
|
|
@@ -18924,9 +19421,6 @@ var CodexcliMcp = class CodexcliMcp extends ToolMcp {
|
|
|
18924
19421
|
};
|
|
18925
19422
|
//#endregion
|
|
18926
19423
|
//#region src/features/mcp/copilot-mcp.ts
|
|
18927
|
-
function convertToCopilotFormat(mcpServers) {
|
|
18928
|
-
return { servers: mcpServers };
|
|
18929
|
-
}
|
|
18930
19424
|
function convertFromCopilotFormat(copilotConfig) {
|
|
18931
19425
|
return copilotConfig.servers ?? {};
|
|
18932
19426
|
}
|
|
@@ -18955,13 +19449,21 @@ var CopilotMcp = class CopilotMcp extends ToolMcp {
|
|
|
18955
19449
|
validate
|
|
18956
19450
|
});
|
|
18957
19451
|
}
|
|
18958
|
-
static fromRulesyncMcp({ outputRoot = process.cwd(), rulesyncMcp, validate = true }) {
|
|
18959
|
-
const
|
|
19452
|
+
static async fromRulesyncMcp({ outputRoot = process.cwd(), rulesyncMcp, validate = true }) {
|
|
19453
|
+
const paths = this.getSettablePaths();
|
|
19454
|
+
const filePath = join(outputRoot, paths.relativeDirPath, paths.relativeFilePath);
|
|
19455
|
+
const existingContent = await readFileContentOrNull(filePath) ?? "{}";
|
|
18960
19456
|
return new CopilotMcp({
|
|
18961
19457
|
outputRoot,
|
|
18962
|
-
relativeDirPath:
|
|
18963
|
-
relativeFilePath:
|
|
18964
|
-
fileContent:
|
|
19458
|
+
relativeDirPath: paths.relativeDirPath,
|
|
19459
|
+
relativeFilePath: paths.relativeFilePath,
|
|
19460
|
+
fileContent: applySharedConfigPatch({
|
|
19461
|
+
fileKey: sharedConfigFileKey(paths),
|
|
19462
|
+
feature: "mcp",
|
|
19463
|
+
existingContent,
|
|
19464
|
+
patch: { servers: rulesyncMcp.getMcpServers() },
|
|
19465
|
+
filePath
|
|
19466
|
+
}),
|
|
18965
19467
|
validate
|
|
18966
19468
|
});
|
|
18967
19469
|
}
|
|
@@ -20644,7 +21146,7 @@ const KILO_TOGGLE_KEPT_KEYS = /* @__PURE__ */ new Set([
|
|
|
20644
21146
|
* impossible to start — no command, no URL — so it is written as the toggle it
|
|
20645
21147
|
* resembles rather than dropped, but the loss is said out loud.
|
|
20646
21148
|
*/
|
|
20647
|
-
function warnAboutToggleDroppedKeys(serverName, serverConfig, logger) {
|
|
21149
|
+
function warnAboutToggleDroppedKeys$1(serverName, serverConfig, logger) {
|
|
20648
21150
|
const dropped = Object.keys(serverConfig).filter((key) => !KILO_TOGGLE_KEPT_KEYS.has(key));
|
|
20649
21151
|
if (dropped.length === 0) return;
|
|
20650
21152
|
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.`);
|
|
@@ -20657,7 +21159,7 @@ function convertServerToKiloFormat(serverName, serverConfig, existingEntry, logg
|
|
|
20657
21159
|
if (declaresNoTransport(serverConfig)) {
|
|
20658
21160
|
if (serverConfig.disabled === void 0) {
|
|
20659
21161
|
if (existingEntry !== void 0 && !isKiloTransportServer(existingEntry)) {
|
|
20660
|
-
warnAboutToggleDroppedKeys(serverName, serverConfig, logger);
|
|
21162
|
+
warnAboutToggleDroppedKeys$1(serverName, serverConfig, logger);
|
|
20661
21163
|
return existingEntry;
|
|
20662
21164
|
}
|
|
20663
21165
|
return warnAndSkipMcpServer({
|
|
@@ -20667,7 +21169,7 @@ function convertServerToKiloFormat(serverName, serverConfig, existingEntry, logg
|
|
|
20667
21169
|
logger
|
|
20668
21170
|
});
|
|
20669
21171
|
}
|
|
20670
|
-
warnAboutToggleDroppedKeys(serverName, serverConfig, logger);
|
|
21172
|
+
warnAboutToggleDroppedKeys$1(serverName, serverConfig, logger);
|
|
20671
21173
|
return { enabled: !serverConfig.disabled };
|
|
20672
21174
|
}
|
|
20673
21175
|
if (isRemoteMcpServer(serverConfig)) {
|
|
@@ -21293,13 +21795,49 @@ const OpencodeMcpRemoteServerSchema = z.looseObject({
|
|
|
21293
21795
|
headers: z.optional(z.record(z.string(), z.string())),
|
|
21294
21796
|
enabled: z._default(z.boolean(), true)
|
|
21295
21797
|
});
|
|
21296
|
-
const
|
|
21798
|
+
const OPENCODE_PASSTHROUGH_SERVER_FIELDS = ["timeout", "oauth"];
|
|
21799
|
+
const OPENCODE_MCP_TRANSPORT_KEYS = [
|
|
21800
|
+
...Object.keys(OpencodeMcpLocalServerSchema.def.shape),
|
|
21801
|
+
...Object.keys(OpencodeMcpRemoteServerSchema.def.shape),
|
|
21802
|
+
...OPENCODE_PASSTHROUGH_SERVER_FIELDS
|
|
21803
|
+
].filter((key) => key !== "enabled");
|
|
21804
|
+
/**
|
|
21805
|
+
* A bare toggle entry: `{"enabled": <bool>}` with no transport of its own,
|
|
21806
|
+
* disabling a server another config layer defines. It is the third member of
|
|
21807
|
+
* OpenCode's own `mcp` union in the published schema, described in-source as
|
|
21808
|
+
* "the legacy `{ enabled: false }` form used to disable a server". Without this
|
|
21809
|
+
* arm the union rejects the entry and the whole MCP import aborts — taking
|
|
21810
|
+
* every valid server in the same file down with it.
|
|
21811
|
+
*
|
|
21812
|
+
* Loose, unlike the two transport arms, so a key OpenCode adds to a toggle
|
|
21813
|
+
* later does not bring that abort back — but refined to reject anything
|
|
21814
|
+
* carrying a key only a transport entry has. A plain loose arm would sit under
|
|
21815
|
+
* a malformed `local` or `remote` entry that happens to carry `enabled` and
|
|
21816
|
+
* swallow it, dropping its command, URL, or headers without a word instead of
|
|
21817
|
+
* failing the way it does today. Mirrors {@link KiloMcpToggleSchema}.
|
|
21818
|
+
*
|
|
21819
|
+
* @see https://opencode.ai/config.json
|
|
21820
|
+
*/
|
|
21821
|
+
const OpencodeMcpToggleSchema = z.looseObject({ enabled: z.boolean() }).check(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"));
|
|
21822
|
+
const OpencodeMcpServerSchema = z.union([
|
|
21823
|
+
OpencodeMcpLocalServerSchema,
|
|
21824
|
+
OpencodeMcpRemoteServerSchema,
|
|
21825
|
+
OpencodeMcpToggleSchema
|
|
21826
|
+
]);
|
|
21297
21827
|
const OpencodeConfigSchema = z.looseObject({
|
|
21298
21828
|
$schema: z.optional(z.string()),
|
|
21299
21829
|
mcp: z.optional(z.record(z.string(), OpencodeMcpServerSchema)),
|
|
21300
21830
|
tools: z.optional(z.record(z.string(), z.boolean()))
|
|
21301
21831
|
});
|
|
21302
21832
|
/**
|
|
21833
|
+
* Tell the two transport arms from a toggle entry. Both carry a `type` literal
|
|
21834
|
+
* and a toggle never does — the schema refuses one that tries — but the toggle
|
|
21835
|
+
* arm is loose, so its index signature hides that from `in` narrowing.
|
|
21836
|
+
*/
|
|
21837
|
+
function isOpencodeTransportServer(server) {
|
|
21838
|
+
return server.type === "local" || server.type === "remote";
|
|
21839
|
+
}
|
|
21840
|
+
/**
|
|
21303
21841
|
* Convert OpenCode native format back to standard MCP format
|
|
21304
21842
|
* - type: "local" -> "stdio", "remote" -> "sse"
|
|
21305
21843
|
* - command (array) -> command (first element) + args (rest)
|
|
@@ -21324,19 +21862,31 @@ function convertFromOpencodeFormat(opencodeMcp, tools) {
|
|
|
21324
21862
|
...convertOpencodeServers(opencodeMcp, tools)
|
|
21325
21863
|
};
|
|
21326
21864
|
}
|
|
21865
|
+
/** Split the shared top-level `tools` map into this server's own two lists. */
|
|
21866
|
+
function splitOpencodeServerTools(serverName, tools) {
|
|
21867
|
+
const enabledTools = [];
|
|
21868
|
+
const disabledTools = [];
|
|
21869
|
+
const prefix = `${serverName}_`;
|
|
21870
|
+
for (const [toolName, enabled] of Object.entries(tools ?? {})) {
|
|
21871
|
+
if (!toolName.startsWith(prefix)) continue;
|
|
21872
|
+
const toolSuffix = toolName.slice(prefix.length);
|
|
21873
|
+
(enabled ? enabledTools : disabledTools).push(toolSuffix);
|
|
21874
|
+
}
|
|
21875
|
+
return {
|
|
21876
|
+
enabledTools,
|
|
21877
|
+
disabledTools
|
|
21878
|
+
};
|
|
21879
|
+
}
|
|
21327
21880
|
function convertOpencodeServers(opencodeMcp, tools) {
|
|
21328
21881
|
return Object.fromEntries(Object.entries(opencodeMcp).map(([serverName, serverConfig]) => {
|
|
21329
21882
|
const extraFields = Object.fromEntries(Object.entries(serverConfig).filter(([key]) => !OPENCODE_KNOWN_SERVER_KEYS.has(key)));
|
|
21330
|
-
const enabledTools =
|
|
21331
|
-
|
|
21332
|
-
|
|
21333
|
-
|
|
21334
|
-
|
|
21335
|
-
|
|
21336
|
-
|
|
21337
|
-
else disabledTools.push(toolSuffix);
|
|
21338
|
-
}
|
|
21339
|
-
}
|
|
21883
|
+
const { enabledTools, disabledTools } = splitOpencodeServerTools(serverName, tools);
|
|
21884
|
+
if (!isOpencodeTransportServer(serverConfig)) return [serverName, {
|
|
21885
|
+
...extraFields,
|
|
21886
|
+
disabled: serverConfig.enabled === false,
|
|
21887
|
+
...enabledTools.length > 0 && { enabledTools },
|
|
21888
|
+
...disabledTools.length > 0 && { disabledTools }
|
|
21889
|
+
}];
|
|
21340
21890
|
if (serverConfig.type === "remote") return [serverName, {
|
|
21341
21891
|
...extraFields,
|
|
21342
21892
|
type: "sse",
|
|
@@ -21366,7 +21916,20 @@ function convertOpencodeServers(opencodeMcp, tools) {
|
|
|
21366
21916
|
}];
|
|
21367
21917
|
}));
|
|
21368
21918
|
}
|
|
21369
|
-
const
|
|
21919
|
+
const OPENCODE_TOGGLE_KEPT_KEYS = /* @__PURE__ */ new Set([
|
|
21920
|
+
"disabled",
|
|
21921
|
+
"enabledTools",
|
|
21922
|
+
"disabledTools"
|
|
21923
|
+
]);
|
|
21924
|
+
/**
|
|
21925
|
+
* Warn about the fields a transport-less server loses by being written as a
|
|
21926
|
+
* bare toggle, so the drop is never silent.
|
|
21927
|
+
*/
|
|
21928
|
+
function warnAboutToggleDroppedKeys(serverName, serverConfig, logger) {
|
|
21929
|
+
const dropped = Object.keys(serverConfig).filter((key) => !OPENCODE_TOGGLE_KEPT_KEYS.has(key));
|
|
21930
|
+
if (dropped.length === 0) return;
|
|
21931
|
+
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.`);
|
|
21932
|
+
}
|
|
21370
21933
|
/**
|
|
21371
21934
|
* Convert standard MCP format to OpenCode native format
|
|
21372
21935
|
* - type: "stdio" -> "local", "sse"/"http" -> "remote"
|
|
@@ -21376,11 +21939,27 @@ const OPENCODE_PASSTHROUGH_SERVER_FIELDS = ["timeout", "oauth"];
|
|
|
21376
21939
|
* - enabledTools/disabledTools -> top-level tools map (with server name prefix)
|
|
21377
21940
|
* - OpenCode-supported extras (timeout, oauth) -> passed through verbatim
|
|
21378
21941
|
*/
|
|
21379
|
-
function convertServerToOpencodeFormat(serverName, serverConfig, logger) {
|
|
21942
|
+
function convertServerToOpencodeFormat(serverName, serverConfig, existingEntry, logger) {
|
|
21380
21943
|
const serverRecord = serverConfig;
|
|
21381
21944
|
const passthrough = {};
|
|
21382
21945
|
for (const key of OPENCODE_PASSTHROUGH_SERVER_FIELDS) if (serverRecord[key] !== void 0) passthrough[key] = serverRecord[key];
|
|
21383
21946
|
const enabled = serverConfig.disabled !== void 0 ? !serverConfig.disabled : true;
|
|
21947
|
+
if (declaresNoTransport(serverConfig)) {
|
|
21948
|
+
if (serverConfig.disabled === void 0) {
|
|
21949
|
+
if (existingEntry !== void 0 && !isOpencodeTransportServer(existingEntry)) {
|
|
21950
|
+
warnAboutToggleDroppedKeys(serverName, serverConfig, logger);
|
|
21951
|
+
return existingEntry;
|
|
21952
|
+
}
|
|
21953
|
+
return warnAndSkipMcpServer({
|
|
21954
|
+
toolName: "OpenCode",
|
|
21955
|
+
serverName,
|
|
21956
|
+
reason: "no transport and no enabled state, so there is nothing to toggle",
|
|
21957
|
+
logger
|
|
21958
|
+
});
|
|
21959
|
+
}
|
|
21960
|
+
warnAboutToggleDroppedKeys(serverName, serverConfig, logger);
|
|
21961
|
+
return { enabled };
|
|
21962
|
+
}
|
|
21384
21963
|
if (isRemoteMcpServer(serverConfig)) {
|
|
21385
21964
|
const url = resolveRemoteMcpUrl(serverConfig);
|
|
21386
21965
|
if (url === void 0) return warnAndSkipMcpServer({
|
|
@@ -21401,7 +21980,7 @@ function convertServerToOpencodeFormat(serverName, serverConfig, logger) {
|
|
|
21401
21980
|
if (commandArray.length === 0) return warnAndSkipMcpServer({
|
|
21402
21981
|
toolName: "OpenCode",
|
|
21403
21982
|
serverName,
|
|
21404
|
-
reason:
|
|
21983
|
+
reason: "a local transport but no command",
|
|
21405
21984
|
logger
|
|
21406
21985
|
});
|
|
21407
21986
|
return {
|
|
@@ -21413,11 +21992,11 @@ function convertServerToOpencodeFormat(serverName, serverConfig, logger) {
|
|
|
21413
21992
|
...serverConfig.cwd && { cwd: serverConfig.cwd }
|
|
21414
21993
|
};
|
|
21415
21994
|
}
|
|
21416
|
-
function convertToOpencodeFormat(mcpServers, logger) {
|
|
21995
|
+
function convertToOpencodeFormat(mcpServers, existingMcp, logger) {
|
|
21417
21996
|
const tools = {};
|
|
21418
21997
|
return {
|
|
21419
21998
|
mcp: Object.fromEntries(Object.entries(mcpServers).map(([serverName, serverConfig]) => {
|
|
21420
|
-
const converted = convertServerToOpencodeFormat(serverName, serverConfig, logger);
|
|
21999
|
+
const converted = convertServerToOpencodeFormat(serverName, serverConfig, existingMcp[serverName], logger);
|
|
21421
22000
|
if (serverConfig.enabledTools) for (const tool of serverConfig.enabledTools) tools[`${serverName}_${tool}`] = true;
|
|
21422
22001
|
if (serverConfig.disabledTools) for (const tool of serverConfig.disabledTools) tools[`${serverName}_${tool}`] = false;
|
|
21423
22002
|
return converted === null ? null : [serverName, converted];
|
|
@@ -21487,10 +22066,12 @@ var OpencodeMcp = class OpencodeMcp extends ToolMcp {
|
|
|
21487
22066
|
fileContent = await readFileContentOrNull(jsonPath);
|
|
21488
22067
|
if (fileContent) relativeFilePath = OPENCODE_JSON_FILE_NAME;
|
|
21489
22068
|
}
|
|
21490
|
-
const
|
|
22069
|
+
const transformedServers = convertEnvVarRefsToToolFormat({
|
|
21491
22070
|
mcpServers: rulesyncMcp.getMcpServers(),
|
|
21492
22071
|
replacement: "{env:$1}"
|
|
21493
|
-
})
|
|
22072
|
+
});
|
|
22073
|
+
const existingMcp = OpencodeConfigSchema.safeParse(parse(fileContent || "{}"));
|
|
22074
|
+
const { mcp: convertedMcp, tools: mcpTools } = convertToOpencodeFormat(transformedServers, (existingMcp.success ? existingMcp.data.mcp : void 0) ?? {}, logger);
|
|
21494
22075
|
return new OpencodeMcp({
|
|
21495
22076
|
outputRoot,
|
|
21496
22077
|
relativeDirPath: basePaths.relativeDirPath,
|
|
@@ -23800,10 +24381,11 @@ function buildPermissionEntry$1(toolName, pattern) {
|
|
|
23800
24381
|
* file (global scope only). The file holds other CLI settings besides
|
|
23801
24382
|
* permissions, so it is never deleted.
|
|
23802
24383
|
*
|
|
23803
|
-
*
|
|
23804
|
-
* `toolPermission` (the global autonomy preset)
|
|
23805
|
-
*
|
|
23806
|
-
*
|
|
24384
|
+
* Four CLI-only autonomy/sandbox knobs outside the allow/ask/deny arrays —
|
|
24385
|
+
* `toolPermission` (the global autonomy preset), `enableTerminalSandbox`,
|
|
24386
|
+
* `artifactReviewPolicy` and `allowNonWorkspaceAccess` — are authored and
|
|
24387
|
+
* round-tripped through the `antigravity-cli` permissions override (see
|
|
24388
|
+
* `AntigravityCliPermissionsOverrideSchema`).
|
|
23807
24389
|
*/
|
|
23808
24390
|
var AntigravityCliPermissions = class AntigravityCliPermissions extends ToolPermissions {
|
|
23809
24391
|
constructor(params) {
|
|
@@ -23867,6 +24449,8 @@ var AntigravityCliPermissions = class AntigravityCliPermissions extends ToolPerm
|
|
|
23867
24449
|
const override = config["antigravity-cli"];
|
|
23868
24450
|
if (override?.toolPermission !== void 0) merged.toolPermission = override.toolPermission;
|
|
23869
24451
|
if (override?.enableTerminalSandbox !== void 0) merged.enableTerminalSandbox = override.enableTerminalSandbox;
|
|
24452
|
+
if (override?.artifactReviewPolicy !== void 0) merged.artifactReviewPolicy = override.artifactReviewPolicy;
|
|
24453
|
+
if (override?.allowNonWorkspaceAccess !== void 0) merged.allowNonWorkspaceAccess = override.allowNonWorkspaceAccess;
|
|
23870
24454
|
const fileContent = JSON.stringify(merged, null, 2);
|
|
23871
24455
|
return new AntigravityCliPermissions({
|
|
23872
24456
|
outputRoot,
|
|
@@ -23893,6 +24477,8 @@ var AntigravityCliPermissions = class AntigravityCliPermissions extends ToolPerm
|
|
|
23893
24477
|
const override = {};
|
|
23894
24478
|
if (typeof settings.toolPermission === "string") override.toolPermission = settings.toolPermission;
|
|
23895
24479
|
if (typeof settings.enableTerminalSandbox === "boolean") override.enableTerminalSandbox = settings.enableTerminalSandbox;
|
|
24480
|
+
if (typeof settings.artifactReviewPolicy === "string") override.artifactReviewPolicy = settings.artifactReviewPolicy;
|
|
24481
|
+
if (typeof settings.allowNonWorkspaceAccess === "boolean") override.allowNonWorkspaceAccess = settings.allowNonWorkspaceAccess;
|
|
23896
24482
|
const result = { ...config };
|
|
23897
24483
|
if (Object.keys(override).length > 0) result["antigravity-cli"] = override;
|
|
23898
24484
|
return this.toRulesyncPermissionsDefault({ fileContent: JSON.stringify(result, null, 2) });
|
|
@@ -24696,12 +25282,57 @@ function parseClaudePermissionEntry(entry) {
|
|
|
24696
25282
|
};
|
|
24697
25283
|
}
|
|
24698
25284
|
/**
|
|
25285
|
+
* Claude Code's file permission checks match only `Edit(path)` and `Read(path)`
|
|
25286
|
+
* rules. A `Write(path)`, `NotebookEdit(path)` or `Glob(path)` rule "is accepted
|
|
25287
|
+
* but never matched by those checks, so Claude Code warns at startup for each
|
|
25288
|
+
* allow, deny, or ask rule in one of these unmatched forms" — so a canonical
|
|
25289
|
+
* `write`/`notebookedit`/`glob` rule with a pattern is emitted in the form the
|
|
25290
|
+
* docs prescribe instead. A tool-name rule with no path is unaffected: it
|
|
25291
|
+
* matches the tool everywhere and produces no warning.
|
|
25292
|
+
* @see https://code.claude.com/docs/en/permissions
|
|
25293
|
+
*/
|
|
25294
|
+
function isPlainRecord(value) {
|
|
25295
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
25296
|
+
}
|
|
25297
|
+
/**
|
|
25298
|
+
* Merge `patch` into `base`, recursing into plain objects so a sibling key at
|
|
25299
|
+
* any depth survives. Arrays and scalars are replaced, since a list the author
|
|
25300
|
+
* states is the list they mean.
|
|
25301
|
+
*/
|
|
25302
|
+
function deepMergeRecords(base, patch) {
|
|
25303
|
+
const merged = { ...base };
|
|
25304
|
+
for (const [key, value] of Object.entries(patch)) {
|
|
25305
|
+
if (PROTOTYPE_POLLUTION_KEYS.has(key)) continue;
|
|
25306
|
+
const existing = merged[key];
|
|
25307
|
+
merged[key] = isPlainRecord(existing) && isPlainRecord(value) ? deepMergeRecords(existing, value) : value;
|
|
25308
|
+
}
|
|
25309
|
+
return merged;
|
|
25310
|
+
}
|
|
25311
|
+
const CLAUDE_PATH_RULE_ALIASES = {
|
|
25312
|
+
Write: "Edit",
|
|
25313
|
+
NotebookEdit: "Edit",
|
|
25314
|
+
Glob: "Read"
|
|
25315
|
+
};
|
|
25316
|
+
/**
|
|
24699
25317
|
* Build a Claude Code permission entry like "Bash(npm run *)".
|
|
24700
25318
|
* If the pattern is "*", returns just the tool name.
|
|
24701
25319
|
*/
|
|
24702
25320
|
function buildClaudePermissionEntry(toolName, pattern) {
|
|
24703
25321
|
if (pattern === "*") return toolName;
|
|
24704
|
-
return `${toolName}(${pattern})`;
|
|
25322
|
+
return `${CLAUDE_PATH_RULE_ALIASES[toolName] ?? toolName}(${pattern})`;
|
|
25323
|
+
}
|
|
25324
|
+
/**
|
|
25325
|
+
* The Claude tool names the canonical config manages. Deliberately the tool
|
|
25326
|
+
* names the categories map to and *not* the aliases a path rule is rewritten
|
|
25327
|
+
* to: claiming `Edit` because a `write` rule exists would sweep away the
|
|
25328
|
+
* `Read`/`Edit` entries the ignore feature and the user wrote in the same file.
|
|
25329
|
+
* The rewritten entries are still rulesync's to place — `applyPermissions`
|
|
25330
|
+
* replaces an entry this run emits wherever it currently sits — and the
|
|
25331
|
+
* original name stays claimed so an entry an older rulesync wrote in the warned
|
|
25332
|
+
* form is cleaned up on the next generate.
|
|
25333
|
+
*/
|
|
25334
|
+
function managedClaudeToolNames(config) {
|
|
25335
|
+
return new Set(Object.keys(config.permission).map((category) => toClaudeToolName(category)));
|
|
24705
25336
|
}
|
|
24706
25337
|
var ClaudecodePermissions = class ClaudecodePermissions extends ToolPermissions {
|
|
24707
25338
|
constructor(params) {
|
|
@@ -24741,7 +25372,10 @@ var ClaudecodePermissions = class ClaudecodePermissions extends ToolPermissions
|
|
|
24741
25372
|
throw new Error(`Failed to parse existing Claude settings at ${filePath}: ${formatError(error)}`, { cause: error });
|
|
24742
25373
|
}
|
|
24743
25374
|
const config = rulesyncPermissions.getJson();
|
|
24744
|
-
const { allow, ask, deny } = convertRulesyncToClaudePermissions(
|
|
25375
|
+
const { allow, ask, deny } = convertRulesyncToClaudePermissions({
|
|
25376
|
+
config,
|
|
25377
|
+
logger
|
|
25378
|
+
});
|
|
24745
25379
|
const overridePermissions = config.claudecode?.permissions;
|
|
24746
25380
|
if (overridePermissions && typeof overridePermissions === "object") {
|
|
24747
25381
|
const { allow: _a, ask: _k, deny: _d, ...nonListFields } = overridePermissions;
|
|
@@ -24750,7 +25384,9 @@ var ClaudecodePermissions = class ClaudecodePermissions extends ToolPermissions
|
|
|
24750
25384
|
...nonListFields
|
|
24751
25385
|
};
|
|
24752
25386
|
}
|
|
24753
|
-
const
|
|
25387
|
+
const overrideSandbox = config.claudecode?.sandbox;
|
|
25388
|
+
if (isPlainRecord(overrideSandbox)) settings.sandbox = deepMergeRecords(isPlainRecord(settings.sandbox) ? settings.sandbox : {}, overrideSandbox);
|
|
25389
|
+
const managedToolNames = managedClaudeToolNames(config);
|
|
24754
25390
|
const merged = applyPermissions({
|
|
24755
25391
|
settings,
|
|
24756
25392
|
managedToolNames,
|
|
@@ -24784,6 +25420,11 @@ var ClaudecodePermissions = class ClaudecodePermissions extends ToolPermissions
|
|
|
24784
25420
|
});
|
|
24785
25421
|
const { allow: _a, ask: _k, deny: _d, ...nonListFields } = permissions;
|
|
24786
25422
|
if (Object.keys(nonListFields).length > 0) config.claudecode = { permissions: nonListFields };
|
|
25423
|
+
const { sandbox } = settings;
|
|
25424
|
+
if (isPlainRecord(sandbox) && Object.keys(sandbox).length > 0) config.claudecode = {
|
|
25425
|
+
...config.claudecode,
|
|
25426
|
+
sandbox
|
|
25427
|
+
};
|
|
24787
25428
|
return this.toRulesyncPermissionsDefault({ fileContent: JSON.stringify(config, null, 2) });
|
|
24788
25429
|
}
|
|
24789
25430
|
validate() {
|
|
@@ -24805,14 +25446,18 @@ var ClaudecodePermissions = class ClaudecodePermissions extends ToolPermissions
|
|
|
24805
25446
|
/**
|
|
24806
25447
|
* Convert rulesync permissions config to Claude Code allow/ask/deny arrays.
|
|
24807
25448
|
*/
|
|
24808
|
-
function convertRulesyncToClaudePermissions(config) {
|
|
25449
|
+
function convertRulesyncToClaudePermissions({ config, logger }) {
|
|
24809
25450
|
const allow = [];
|
|
24810
25451
|
const ask = [];
|
|
24811
25452
|
const deny = [];
|
|
25453
|
+
const actionByEntry = /* @__PURE__ */ new Map();
|
|
24812
25454
|
for (const [category, rules] of Object.entries(config.permission)) {
|
|
24813
25455
|
const claudeToolName = toClaudeToolName(category);
|
|
24814
25456
|
for (const [pattern, action] of Object.entries(rules)) {
|
|
24815
25457
|
const entry = buildClaudePermissionEntry(claudeToolName, pattern);
|
|
25458
|
+
const previous = actionByEntry.get(entry);
|
|
25459
|
+
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.`);
|
|
25460
|
+
actionByEntry.set(entry, action);
|
|
24816
25461
|
switch (action) {
|
|
24817
25462
|
case "allow":
|
|
24818
25463
|
allow.push(entry);
|
|
@@ -25625,19 +26270,24 @@ function mapBashActionToDecision(action) {
|
|
|
25625
26270
|
//#endregion
|
|
25626
26271
|
//#region src/features/permissions/copilot-permissions.ts
|
|
25627
26272
|
/**
|
|
25628
|
-
* The flat, dotted VS Code setting
|
|
25629
|
-
* settings with dotted keys flat at the
|
|
25630
|
-
* literal key — not a nested
|
|
26273
|
+
* The flat, dotted VS Code setting keys this adapter manages, one per canonical
|
|
26274
|
+
* permission category. VS Code stores settings with dotted keys flat at the
|
|
26275
|
+
* document top level, so each is a single literal key — not a nested
|
|
26276
|
+
* `chat.tools.terminal` path. All three share the same
|
|
26277
|
+
* pattern-to-boolean shape, so one conversion covers them.
|
|
26278
|
+
*
|
|
26279
|
+
* The canonical `read` and `write` categories stay unmapped: VS Code has no
|
|
26280
|
+
* read-approval surface, and folding `write` into the edits map alongside
|
|
26281
|
+
* `edit` would make the two indistinguishable on import.
|
|
26282
|
+
*
|
|
25631
26283
|
* @see https://code.visualstudio.com/docs/agents/approvals
|
|
26284
|
+
* @see https://code.visualstudio.com/docs/copilot/chat/review-code-edits
|
|
25632
26285
|
*/
|
|
25633
|
-
const
|
|
25634
|
-
|
|
25635
|
-
|
|
25636
|
-
|
|
25637
|
-
|
|
25638
|
-
* …) have no terminal-command equivalent and are intentionally not mapped.
|
|
25639
|
-
*/
|
|
25640
|
-
const TERMINAL_CATEGORY = "bash";
|
|
26286
|
+
const AUTO_APPROVE_KEYS = {
|
|
26287
|
+
bash: "chat.tools.terminal.autoApprove",
|
|
26288
|
+
edit: "chat.tools.edits.autoApprove",
|
|
26289
|
+
webfetch: "chat.tools.urls.autoApprove"
|
|
26290
|
+
};
|
|
25641
26291
|
function asAutoApproveMap(value) {
|
|
25642
26292
|
if (!isPlainObject$1(value)) return {};
|
|
25643
26293
|
const result = {};
|
|
@@ -25645,6 +26295,21 @@ function asAutoApproveMap(value) {
|
|
|
25645
26295
|
return result;
|
|
25646
26296
|
}
|
|
25647
26297
|
/**
|
|
26298
|
+
* Render one canonical category's rules as a VS Code auto-approve map. Returns
|
|
26299
|
+
* `undefined` when the category contributes nothing, so the key is retracted
|
|
26300
|
+
* rather than written as an empty object.
|
|
26301
|
+
*
|
|
26302
|
+
* The resulting map replaces the file's existing value wholesale — rulesync
|
|
26303
|
+
* owns these keys, so a rule dropped from the canonical config disappears from
|
|
26304
|
+
* the settings file too.
|
|
26305
|
+
*/
|
|
26306
|
+
function buildAutoApproveValue(rules) {
|
|
26307
|
+
const autoApprove = {};
|
|
26308
|
+
for (const [pattern, action] of Object.entries(rules)) if (action === "allow") autoApprove[pattern] = true;
|
|
26309
|
+
else if (action === "deny") autoApprove[pattern] = false;
|
|
26310
|
+
return Object.keys(autoApprove).length > 0 ? autoApprove : void 0;
|
|
26311
|
+
}
|
|
26312
|
+
/**
|
|
25648
26313
|
* Permissions generator for GitHub Copilot Chat in VS Code.
|
|
25649
26314
|
*
|
|
25650
26315
|
* VS Code has no standalone, environment-agnostic Copilot policy file (like
|
|
@@ -25654,13 +26319,16 @@ function asAutoApproveMap(value) {
|
|
|
25654
26319
|
* many unrelated keys, so reads and writes merge into the existing JSON
|
|
25655
26320
|
* (touching only the one managed key) and the file is never deleted.
|
|
25656
26321
|
*
|
|
25657
|
-
*
|
|
25658
|
-
*
|
|
25659
|
-
* map as: `allow` → `true` (auto-approve), `deny`
|
|
25660
|
-
*
|
|
25661
|
-
*
|
|
25662
|
-
*
|
|
25663
|
-
*
|
|
26322
|
+
* Three canonical categories have a clean, non-lossy representation and are
|
|
26323
|
+
* mapped (see {@link AUTO_APPROVE_KEYS}): `bash`, `edit` and `webfetch`. In
|
|
26324
|
+
* every one, per-pattern rules map as: `allow` → `true` (auto-approve), `deny`
|
|
26325
|
+
* → `false` (VS Code then always prompts — note this is "never auto-approve",
|
|
26326
|
+
* not a hard block), and `ask` → the entry is OMITTED (VS Code falls through to
|
|
26327
|
+
* the same default prompt). A key whose canonical category is absent entirely
|
|
26328
|
+
* is left untouched, so authoring only `bash` rules never disturbs a
|
|
26329
|
+
* hand-written edits or urls map.
|
|
26330
|
+
* Only project scope is modeled: VS Code's user-scope settings.json lives at a
|
|
26331
|
+
* platform-dependent path outside rulesync's home-relative global model.
|
|
25664
26332
|
*/
|
|
25665
26333
|
var CopilotPermissions = class CopilotPermissions extends ToolPermissions {
|
|
25666
26334
|
constructor(params) {
|
|
@@ -25697,11 +26365,13 @@ var CopilotPermissions = class CopilotPermissions extends ToolPermissions {
|
|
|
25697
26365
|
const paths = CopilotPermissions.getSettablePaths();
|
|
25698
26366
|
const filePath = join(outputRoot, paths.relativeDirPath, paths.relativeFilePath);
|
|
25699
26367
|
const existingContent = await readFileContentOrNull(filePath) ?? "{}";
|
|
25700
|
-
const
|
|
25701
|
-
const
|
|
25702
|
-
for (const [
|
|
25703
|
-
|
|
25704
|
-
|
|
26368
|
+
const config = rulesyncPermissions.getJson();
|
|
26369
|
+
const patch = {};
|
|
26370
|
+
for (const [category, settingKey] of Object.entries(AUTO_APPROVE_KEYS)) {
|
|
26371
|
+
const rules = config.permission[category];
|
|
26372
|
+
if (rules === void 0) continue;
|
|
26373
|
+
patch[settingKey] = buildAutoApproveValue(rules);
|
|
26374
|
+
}
|
|
25705
26375
|
return new CopilotPermissions({
|
|
25706
26376
|
outputRoot,
|
|
25707
26377
|
relativeDirPath: paths.relativeDirPath,
|
|
@@ -25710,7 +26380,7 @@ var CopilotPermissions = class CopilotPermissions extends ToolPermissions {
|
|
|
25710
26380
|
fileKey: sharedConfigFileKey(paths),
|
|
25711
26381
|
feature: "permissions",
|
|
25712
26382
|
existingContent,
|
|
25713
|
-
patch
|
|
26383
|
+
patch,
|
|
25714
26384
|
filePath
|
|
25715
26385
|
}),
|
|
25716
26386
|
validate: true
|
|
@@ -25729,10 +26399,13 @@ var CopilotPermissions = class CopilotPermissions extends ToolPermissions {
|
|
|
25729
26399
|
} catch (error) {
|
|
25730
26400
|
throw new Error(`Failed to parse Copilot VS Code settings in ${join(this.getRelativeDirPath(), this.getRelativeFilePath())}: ${formatError(error)}`, { cause: error });
|
|
25731
26401
|
}
|
|
25732
|
-
const
|
|
25733
|
-
const
|
|
25734
|
-
|
|
25735
|
-
|
|
26402
|
+
const permission = {};
|
|
26403
|
+
for (const [category, settingKey] of Object.entries(AUTO_APPROVE_KEYS)) {
|
|
26404
|
+
const autoApprove = asAutoApproveMap(settings[settingKey]);
|
|
26405
|
+
const rules = {};
|
|
26406
|
+
for (const [pattern, flag] of Object.entries(autoApprove)) rules[pattern] = flag ? "allow" : "deny";
|
|
26407
|
+
if (Object.keys(rules).length > 0) permission[category] = rules;
|
|
26408
|
+
}
|
|
25736
26409
|
return this.toRulesyncPermissionsDefault({ fileContent: JSON.stringify({ permission }, null, 2) });
|
|
25737
26410
|
}
|
|
25738
26411
|
validate() {
|
|
@@ -31752,6 +32425,7 @@ const ClaudecodeSkillFrontmatterSchema = z.looseObject({
|
|
|
31752
32425
|
arguments: z.optional(z.union([z.string(), z.array(z.string())])),
|
|
31753
32426
|
context: z.optional(z.string()),
|
|
31754
32427
|
agent: z.optional(z.string()),
|
|
32428
|
+
background: z.optional(z.boolean()),
|
|
31755
32429
|
hooks: z.optional(z.looseObject({})),
|
|
31756
32430
|
shell: z.optional(z.string()),
|
|
31757
32431
|
"disable-model-invocation": z.optional(z.boolean()),
|
|
@@ -31778,6 +32452,7 @@ function buildClaudecodeSkillFrontmatter({ rulesyncFrontmatter, resolvedDisableM
|
|
|
31778
32452
|
shell: section.shell
|
|
31779
32453
|
};
|
|
31780
32454
|
const definedFields = {
|
|
32455
|
+
background: section.background,
|
|
31781
32456
|
arguments: section.arguments,
|
|
31782
32457
|
hooks: section.hooks,
|
|
31783
32458
|
"disable-model-invocation": resolvedDisableModelInvocation,
|
|
@@ -31855,6 +32530,7 @@ var ClaudecodeSkill = class extends ToolSkill {
|
|
|
31855
32530
|
...frontmatter.arguments !== void 0 && { arguments: frontmatter.arguments },
|
|
31856
32531
|
...frontmatter.context && { context: frontmatter.context },
|
|
31857
32532
|
...frontmatter.agent && { agent: frontmatter.agent },
|
|
32533
|
+
...frontmatter.background !== void 0 && { background: frontmatter.background },
|
|
31858
32534
|
...frontmatter.hooks !== void 0 && { hooks: frontmatter.hooks },
|
|
31859
32535
|
...frontmatter.shell && { shell: frontmatter.shell },
|
|
31860
32536
|
...frontmatter["disable-model-invocation"] !== void 0 && { "disable-model-invocation": frontmatter["disable-model-invocation"] },
|
|
@@ -32464,7 +33140,9 @@ const CopilotcliSkillFrontmatterSchema = z.looseObject({
|
|
|
32464
33140
|
description: z.string(),
|
|
32465
33141
|
license: z.optional(z.string()),
|
|
32466
33142
|
"allowed-tools": z.optional(z.union([z.string(), z.array(z.string())])),
|
|
32467
|
-
"argument-hint": z.optional(z.string())
|
|
33143
|
+
"argument-hint": z.optional(z.string()),
|
|
33144
|
+
"user-invocable": z.optional(z.boolean()),
|
|
33145
|
+
"disable-model-invocation": z.optional(z.boolean())
|
|
32468
33146
|
});
|
|
32469
33147
|
/**
|
|
32470
33148
|
* Represents a GitHub Copilot CLI skill directory.
|
|
@@ -32523,7 +33201,9 @@ var CopilotcliSkill = class CopilotcliSkill extends ToolSkill {
|
|
|
32523
33201
|
const copilotcliSection = {
|
|
32524
33202
|
...frontmatter.license !== void 0 && { license: frontmatter.license },
|
|
32525
33203
|
...frontmatter["allowed-tools"] !== void 0 && { "allowed-tools": frontmatter["allowed-tools"] },
|
|
32526
|
-
...frontmatter["argument-hint"] !== void 0 && { "argument-hint": frontmatter["argument-hint"] }
|
|
33204
|
+
...frontmatter["argument-hint"] !== void 0 && { "argument-hint": frontmatter["argument-hint"] },
|
|
33205
|
+
...frontmatter["user-invocable"] !== void 0 && { "user-invocable": frontmatter["user-invocable"] },
|
|
33206
|
+
...frontmatter["disable-model-invocation"] !== void 0 && { "disable-model-invocation": frontmatter["disable-model-invocation"] }
|
|
32527
33207
|
};
|
|
32528
33208
|
const rulesyncFrontmatter = {
|
|
32529
33209
|
name: frontmatter.name,
|
|
@@ -32550,7 +33230,9 @@ var CopilotcliSkill = class CopilotcliSkill extends ToolSkill {
|
|
|
32550
33230
|
description: rulesyncFrontmatter.description,
|
|
32551
33231
|
...rulesyncFrontmatter.copilotcli?.license !== void 0 && { license: rulesyncFrontmatter.copilotcli.license },
|
|
32552
33232
|
...rulesyncFrontmatter.copilotcli?.["allowed-tools"] !== void 0 && { "allowed-tools": rulesyncFrontmatter.copilotcli["allowed-tools"] },
|
|
32553
|
-
...rulesyncFrontmatter.copilotcli?.["argument-hint"] !== void 0 && { "argument-hint": rulesyncFrontmatter.copilotcli["argument-hint"] }
|
|
33233
|
+
...rulesyncFrontmatter.copilotcli?.["argument-hint"] !== void 0 && { "argument-hint": rulesyncFrontmatter.copilotcli["argument-hint"] },
|
|
33234
|
+
...rulesyncFrontmatter.copilotcli?.["user-invocable"] !== void 0 && { "user-invocable": rulesyncFrontmatter.copilotcli["user-invocable"] },
|
|
33235
|
+
...rulesyncFrontmatter.copilotcli?.["disable-model-invocation"] !== void 0 && { "disable-model-invocation": rulesyncFrontmatter.copilotcli["disable-model-invocation"] }
|
|
32554
33236
|
};
|
|
32555
33237
|
return new CopilotcliSkill({
|
|
32556
33238
|
outputRoot,
|
|
@@ -34220,6 +34902,37 @@ var OpenCodeSkill = class OpenCodeSkill extends ToolSkill {
|
|
|
34220
34902
|
alternativeSkillRoots: [global ? OPENCODE_GLOBAL_SKILL_DIR_PATH : OPENCODE_SKILL_DIR_PATH]
|
|
34221
34903
|
};
|
|
34222
34904
|
}
|
|
34905
|
+
/**
|
|
34906
|
+
* Extra skill roots the project configured in `opencode.json` /
|
|
34907
|
+
* `opencode.jsonc` via `skills.paths` ("Additional paths to skill folders").
|
|
34908
|
+
* Without these, skills a project keeps outside `.opencode/skills/` are
|
|
34909
|
+
* invisible to `rulesync import` even though OpenCode loads them.
|
|
34910
|
+
*
|
|
34911
|
+
* Import-only: rulesync keeps writing to its own managed root, so a
|
|
34912
|
+
* configured path is read but never generated into. `skills.urls` is a
|
|
34913
|
+
* remote-fetch surface and is out of scope for a file-based generator.
|
|
34914
|
+
*
|
|
34915
|
+
* Absolute paths and paths escaping the output root are dropped — an import
|
|
34916
|
+
* root is joined onto `outputRoot`, and reaching outside it is not something
|
|
34917
|
+
* a project config should be able to ask for.
|
|
34918
|
+
*
|
|
34919
|
+
* @see https://opencode.ai/config.json
|
|
34920
|
+
*/
|
|
34921
|
+
static async getConfiguredImportRoots({ outputRoot, global = false }) {
|
|
34922
|
+
const skills = asOpencodeEntries((await readOpencodeConfig({
|
|
34923
|
+
outputRoot,
|
|
34924
|
+
global
|
|
34925
|
+
})).skills);
|
|
34926
|
+
if (skills === null || !Array.isArray(skills.paths)) return [];
|
|
34927
|
+
const configDir = getOpencodeConfigDir({
|
|
34928
|
+
outputRoot,
|
|
34929
|
+
global
|
|
34930
|
+
});
|
|
34931
|
+
return skills.paths.filter((candidate) => typeof candidate === "string" && candidate !== "" && !isAbsolute(candidate) && !normalize(candidate).startsWith("..")).map((relativeDirPath) => ({
|
|
34932
|
+
outputRoot: configDir,
|
|
34933
|
+
relativeDirPath
|
|
34934
|
+
}));
|
|
34935
|
+
}
|
|
34223
34936
|
getFrontmatter() {
|
|
34224
34937
|
return OpenCodeSkillFrontmatterSchema.parse(this.requireMainFileFrontmatter());
|
|
34225
34938
|
}
|
|
@@ -36153,12 +36866,19 @@ var SkillsProcessor = class extends DirFeatureProcessor {
|
|
|
36153
36866
|
*/
|
|
36154
36867
|
async loadToolDirs() {
|
|
36155
36868
|
const factory = this.getFactory(this.toolTarget);
|
|
36156
|
-
const
|
|
36869
|
+
const paths = factory.class.getSettablePaths({ global: this.global });
|
|
36870
|
+
const configuredRoots = factory.class.getConfiguredImportRoots ? await factory.class.getConfiguredImportRoots({
|
|
36871
|
+
outputRoot: this.outputRoot,
|
|
36872
|
+
global: this.global
|
|
36873
|
+
}) : [];
|
|
36874
|
+
const configuredRootPaths = new Set(configuredRoots.map((root) => root.relativeDirPath));
|
|
36875
|
+
const roots = [...toolSkillImportRoots(paths), ...configuredRoots];
|
|
36157
36876
|
const seenSkillNames = /* @__PURE__ */ new Set();
|
|
36158
36877
|
const toolSkills = [];
|
|
36159
36878
|
for (const root of roots) {
|
|
36160
36879
|
const rootOutputRoot = typeof root === "string" ? this.outputRoot : root.outputRoot;
|
|
36161
36880
|
const relativeDirPath = typeof root === "string" ? root : root.relativeDirPath;
|
|
36881
|
+
const isConfiguredRoot = configuredRootPaths.has(relativeDirPath);
|
|
36162
36882
|
const skillsDirPath = join(rootOutputRoot, relativeDirPath);
|
|
36163
36883
|
if (!await directoryExists(skillsDirPath)) continue;
|
|
36164
36884
|
const dirPaths = await findFilesByGlobs(join(skillsDirPath, "*"), { type: "dir" });
|
|
@@ -36173,12 +36893,20 @@ var SkillsProcessor = class extends DirFeatureProcessor {
|
|
|
36173
36893
|
})) continue;
|
|
36174
36894
|
ownedDirNames.push(dirName);
|
|
36175
36895
|
}
|
|
36176
|
-
const directorySkills = await Promise.all(ownedDirNames.map((dirName) =>
|
|
36177
|
-
|
|
36178
|
-
|
|
36179
|
-
|
|
36180
|
-
|
|
36181
|
-
|
|
36896
|
+
const directorySkills = (await Promise.all(ownedDirNames.map(async (dirName) => {
|
|
36897
|
+
try {
|
|
36898
|
+
return await factory.class.fromDir({
|
|
36899
|
+
outputRoot: rootOutputRoot,
|
|
36900
|
+
relativeDirPath,
|
|
36901
|
+
dirName,
|
|
36902
|
+
global: this.global
|
|
36903
|
+
});
|
|
36904
|
+
} catch (error) {
|
|
36905
|
+
if (!isConfiguredRoot) throw error;
|
|
36906
|
+
this.logger.warn(`Skipping ${join(relativeDirPath, dirName)}: ${formatError(error)}`);
|
|
36907
|
+
return null;
|
|
36908
|
+
}
|
|
36909
|
+
}))).filter((skill) => skill !== null);
|
|
36182
36910
|
for (const skill of directorySkills) {
|
|
36183
36911
|
const skillName = skill.getImportIdentity();
|
|
36184
36912
|
if (seenSkillNames.has(skillName)) continue;
|
|
@@ -36703,6 +37431,242 @@ var RovodevSubagent = class RovodevSubagent extends ToolSubagent {
|
|
|
36703
37431
|
}
|
|
36704
37432
|
};
|
|
36705
37433
|
//#endregion
|
|
37434
|
+
//#region src/features/subagents/antigravity-shared-subagent.ts
|
|
37435
|
+
/**
|
|
37436
|
+
* Frontmatter of an Antigravity custom agent (Markdown format, CLI v1.1.6+).
|
|
37437
|
+
*
|
|
37438
|
+
* `name` and `description` are required upstream; the rest are optional and
|
|
37439
|
+
* documented with defaults (`tools: []`, `mainAgent: true`, `subagent: true`,
|
|
37440
|
+
* `model: inherit`, `commandExecutionPolicy: sandbox`, `mcpServers: []`,
|
|
37441
|
+
* `skills`/`plugins`: `[]`). `hidden` and `inheritMcp` appear in the v1.1.6
|
|
37442
|
+
* release notes but not in the documented frontmatter table, so they are
|
|
37443
|
+
* accepted as verbatim passthrough without any behavior modeled around them.
|
|
37444
|
+
* `looseObject` keeps unknown future fields round-tripping.
|
|
37445
|
+
*
|
|
37446
|
+
* @see https://antigravity.google/docs/subagents
|
|
37447
|
+
*/
|
|
37448
|
+
const AntigravitySubagentFrontmatterSchema = z.looseObject({
|
|
37449
|
+
name: z.string(),
|
|
37450
|
+
description: z.string().check(z.minLength(1)),
|
|
37451
|
+
tools: z.optional(z.array(z.string())),
|
|
37452
|
+
mainAgent: z.optional(z.boolean()),
|
|
37453
|
+
subagent: z.optional(z.boolean()),
|
|
37454
|
+
model: z.optional(z.string()),
|
|
37455
|
+
commandExecutionPolicy: z.optional(z.string()),
|
|
37456
|
+
mcpServers: z.optional(z.array(z.unknown())),
|
|
37457
|
+
skills: z.optional(z.array(z.string())),
|
|
37458
|
+
plugins: z.optional(z.array(z.string())),
|
|
37459
|
+
hidden: z.optional(z.boolean()),
|
|
37460
|
+
inheritMcp: z.optional(z.boolean())
|
|
37461
|
+
});
|
|
37462
|
+
/**
|
|
37463
|
+
* Shared custom-agent (subagent) implementation for Google Antigravity 2.0,
|
|
37464
|
+
* used by the IDE, the CLI and plugin bundles.
|
|
37465
|
+
*
|
|
37466
|
+
* Antigravity discovers agents at `.agents/agents/<name>.md` (project) and
|
|
37467
|
+
* `~/.gemini/config/agents/<name>.md` (global, shared by the IDE and the CLI
|
|
37468
|
+
* exactly like `~/.gemini/config/hooks.json`). The directory form
|
|
37469
|
+
* (`<name>/agent.md`) is an equivalent alternative upstream; rulesync emits and
|
|
37470
|
+
* imports the flat file form. The body after the frontmatter is the agent's
|
|
37471
|
+
* system prompt.
|
|
37472
|
+
*
|
|
37473
|
+
* Concrete subclasses only supply the rulesync target name they answer to via
|
|
37474
|
+
* {@link AntigravitySharedSubagent.getToolTarget} and, where the shared file is
|
|
37475
|
+
* not involved, the sections they read via
|
|
37476
|
+
* {@link AntigravitySharedSubagent.getReadSectionKeys}.
|
|
37477
|
+
*
|
|
37478
|
+
* @see https://antigravity.google/docs/subagents
|
|
37479
|
+
*/
|
|
37480
|
+
var AntigravitySharedSubagent = class extends ToolSubagent {
|
|
37481
|
+
frontmatter;
|
|
37482
|
+
body;
|
|
37483
|
+
constructor({ frontmatter, body, fileContent, ...rest }) {
|
|
37484
|
+
if (rest.validate !== false) {
|
|
37485
|
+
const result = AntigravitySubagentFrontmatterSchema.safeParse(frontmatter);
|
|
37486
|
+
if (!result.success) throw new Error(`Invalid frontmatter in ${join(rest.relativeDirPath, rest.relativeFilePath)}: ${formatError(result.error)}`);
|
|
37487
|
+
}
|
|
37488
|
+
super({
|
|
37489
|
+
...rest,
|
|
37490
|
+
fileContent: fileContent ?? stringifyFrontmatter(body, frontmatter, { avoidBlockScalars: true })
|
|
37491
|
+
});
|
|
37492
|
+
this.frontmatter = frontmatter;
|
|
37493
|
+
this.body = body;
|
|
37494
|
+
}
|
|
37495
|
+
/** The rulesync target name this subagent answers to. */
|
|
37496
|
+
static getToolTarget() {
|
|
37497
|
+
throw new Error("Please implement this method in the subclass.");
|
|
37498
|
+
}
|
|
37499
|
+
/**
|
|
37500
|
+
* Tool-specific sections this target reads, in increasing precedence order.
|
|
37501
|
+
*
|
|
37502
|
+
* `antigravity-ide` and `antigravity-cli` write the very same file, so a
|
|
37503
|
+
* target that read only its own section would silently drop the other's keys
|
|
37504
|
+
* — and which one survived would depend on `--targets` order. Every target
|
|
37505
|
+
* therefore merges the shared `antigravity-ide` → `antigravity-cli` sections
|
|
37506
|
+
* (the CLI block wins, matching the fixed order the MCP feature already uses
|
|
37507
|
+
* for the same shared-output reason), and the plugin target layers its own
|
|
37508
|
+
* section on top of that. Only `getToolTarget()` decides which section an
|
|
37509
|
+
* import writes back into.
|
|
37510
|
+
*/
|
|
37511
|
+
static getReadSectionKeys() {
|
|
37512
|
+
return ["antigravity-ide", "antigravity-cli"];
|
|
37513
|
+
}
|
|
37514
|
+
static getSettablePaths({ global = false } = {}) {
|
|
37515
|
+
return { relativeDirPath: global ? ANTIGRAVITY_GLOBAL_AGENTS_DIR_PATH : ANTIGRAVITY_AGENTS_DIR_PATH };
|
|
37516
|
+
}
|
|
37517
|
+
getFrontmatter() {
|
|
37518
|
+
return this.frontmatter;
|
|
37519
|
+
}
|
|
37520
|
+
getBody() {
|
|
37521
|
+
return this.body;
|
|
37522
|
+
}
|
|
37523
|
+
toRulesyncSubagent() {
|
|
37524
|
+
const { name, description, ...restFields } = this.frontmatter;
|
|
37525
|
+
return new RulesyncSubagent({
|
|
37526
|
+
outputRoot: ".",
|
|
37527
|
+
frontmatter: {
|
|
37528
|
+
targets: ["*"],
|
|
37529
|
+
name,
|
|
37530
|
+
description,
|
|
37531
|
+
...Object.keys(restFields).length > 0 && { [this.constructor.getToolTarget()]: restFields }
|
|
37532
|
+
},
|
|
37533
|
+
body: this.body,
|
|
37534
|
+
relativeDirPath: RULESYNC_SUBAGENTS_RELATIVE_DIR_PATH,
|
|
37535
|
+
relativeFilePath: this.getRelativeFilePath(),
|
|
37536
|
+
validate: true
|
|
37537
|
+
});
|
|
37538
|
+
}
|
|
37539
|
+
static fromRulesyncSubagent({ outputRoot = process.cwd(), rulesyncSubagent, validate = true, global = false }) {
|
|
37540
|
+
const rulesyncFrontmatter = rulesyncSubagent.getFrontmatter();
|
|
37541
|
+
const mergedSection = Object.assign({}, ...this.getReadSectionKeys().map((key) => rulesyncFrontmatter[key] ?? {}));
|
|
37542
|
+
const toolSection = this.filterToolSpecificSection(mergedSection, ["name", "description"]);
|
|
37543
|
+
const rawFrontmatter = {
|
|
37544
|
+
name: rulesyncFrontmatter.name,
|
|
37545
|
+
description: rulesyncFrontmatter.description || `${rulesyncFrontmatter.name} subagent`,
|
|
37546
|
+
...toolSection
|
|
37547
|
+
};
|
|
37548
|
+
const result = AntigravitySubagentFrontmatterSchema.safeParse(rawFrontmatter);
|
|
37549
|
+
if (!result.success) throw new Error(`Invalid ${this.getToolTarget()} subagent frontmatter in ${rulesyncSubagent.getRelativeFilePath()}: ${formatError(result.error)}`);
|
|
37550
|
+
const frontmatter = result.data;
|
|
37551
|
+
const body = rulesyncSubagent.getBody();
|
|
37552
|
+
const paths = this.getSettablePaths({ global });
|
|
37553
|
+
return new this({
|
|
37554
|
+
outputRoot,
|
|
37555
|
+
frontmatter,
|
|
37556
|
+
body,
|
|
37557
|
+
relativeDirPath: paths.relativeDirPath,
|
|
37558
|
+
relativeFilePath: rulesyncSubagent.getRelativeFilePath(),
|
|
37559
|
+
fileContent: stringifyFrontmatter(body, frontmatter, { avoidBlockScalars: true }),
|
|
37560
|
+
validate,
|
|
37561
|
+
global
|
|
37562
|
+
});
|
|
37563
|
+
}
|
|
37564
|
+
validate() {
|
|
37565
|
+
if (!this.frontmatter) return {
|
|
37566
|
+
success: true,
|
|
37567
|
+
error: null
|
|
37568
|
+
};
|
|
37569
|
+
const result = AntigravitySubagentFrontmatterSchema.safeParse(this.frontmatter);
|
|
37570
|
+
if (result.success) return {
|
|
37571
|
+
success: true,
|
|
37572
|
+
error: null
|
|
37573
|
+
};
|
|
37574
|
+
return {
|
|
37575
|
+
success: false,
|
|
37576
|
+
error: /* @__PURE__ */ new Error(`Invalid frontmatter in ${join(this.relativeDirPath, this.relativeFilePath)}: ${formatError(result.error)}`)
|
|
37577
|
+
};
|
|
37578
|
+
}
|
|
37579
|
+
static isTargetedByRulesyncSubagent(rulesyncSubagent) {
|
|
37580
|
+
return this.isTargetedByRulesyncSubagentDefault({
|
|
37581
|
+
rulesyncSubagent,
|
|
37582
|
+
toolTarget: this.getToolTarget()
|
|
37583
|
+
});
|
|
37584
|
+
}
|
|
37585
|
+
static async fromFile({ outputRoot = process.cwd(), relativeFilePath, validate = true, global = false }) {
|
|
37586
|
+
const paths = this.getSettablePaths({ global });
|
|
37587
|
+
const filePath = join(outputRoot, paths.relativeDirPath, relativeFilePath);
|
|
37588
|
+
const fileContent = await readFileContent(filePath);
|
|
37589
|
+
const { frontmatter, body: content } = parseFrontmatter(fileContent, filePath);
|
|
37590
|
+
const result = AntigravitySubagentFrontmatterSchema.safeParse(frontmatter);
|
|
37591
|
+
if (!result.success) throw new Error(`Invalid frontmatter in ${filePath}: ${formatError(result.error)}`);
|
|
37592
|
+
return new this({
|
|
37593
|
+
outputRoot,
|
|
37594
|
+
relativeDirPath: paths.relativeDirPath,
|
|
37595
|
+
relativeFilePath,
|
|
37596
|
+
frontmatter: result.data,
|
|
37597
|
+
body: content.trim(),
|
|
37598
|
+
fileContent,
|
|
37599
|
+
validate,
|
|
37600
|
+
global
|
|
37601
|
+
});
|
|
37602
|
+
}
|
|
37603
|
+
static forDeletion({ outputRoot = process.cwd(), relativeDirPath, relativeFilePath, global = false }) {
|
|
37604
|
+
return new this({
|
|
37605
|
+
outputRoot,
|
|
37606
|
+
relativeDirPath,
|
|
37607
|
+
relativeFilePath,
|
|
37608
|
+
frontmatter: {
|
|
37609
|
+
name: "",
|
|
37610
|
+
description: ""
|
|
37611
|
+
},
|
|
37612
|
+
body: "",
|
|
37613
|
+
fileContent: "",
|
|
37614
|
+
validate: false,
|
|
37615
|
+
global
|
|
37616
|
+
});
|
|
37617
|
+
}
|
|
37618
|
+
};
|
|
37619
|
+
//#endregion
|
|
37620
|
+
//#region src/features/subagents/antigravity-cli-subagent.ts
|
|
37621
|
+
/**
|
|
37622
|
+
* Google Antigravity CLI custom agent (subagent), shipped in CLI v1.1.6.
|
|
37623
|
+
*
|
|
37624
|
+
* Shares all behavior with {@link AntigravitySharedSubagent}; the CLI reads the
|
|
37625
|
+
* same `.agents/agents/` and `~/.gemini/config/agents/` roots as the IDE and
|
|
37626
|
+
* exposes them through the `agy agents` subcommand and the `--agent` flag. It
|
|
37627
|
+
* answers to the `antigravity-cli` target.
|
|
37628
|
+
*/
|
|
37629
|
+
var AntigravityCliSubagent = class extends AntigravitySharedSubagent {
|
|
37630
|
+
static getToolTarget() {
|
|
37631
|
+
return "antigravity-cli";
|
|
37632
|
+
}
|
|
37633
|
+
};
|
|
37634
|
+
//#endregion
|
|
37635
|
+
//#region src/features/subagents/antigravity-ide-subagent.ts
|
|
37636
|
+
/**
|
|
37637
|
+
* Google Antigravity IDE custom agent (subagent).
|
|
37638
|
+
*
|
|
37639
|
+
* Shares all behavior with {@link AntigravitySharedSubagent} — the subagents
|
|
37640
|
+
* documentation is product-wide and lists the same `.agents/agents/` and
|
|
37641
|
+
* `~/.gemini/config/agents/` roots for the IDE and the CLI. It answers to the
|
|
37642
|
+
* `antigravity-ide` target.
|
|
37643
|
+
*/
|
|
37644
|
+
var AntigravityIdeSubagent = class extends AntigravitySharedSubagent {
|
|
37645
|
+
static getToolTarget() {
|
|
37646
|
+
return "antigravity-ide";
|
|
37647
|
+
}
|
|
37648
|
+
};
|
|
37649
|
+
//#endregion
|
|
37650
|
+
//#region src/features/subagents/antigravity-plugin-subagent.ts
|
|
37651
|
+
/**
|
|
37652
|
+
* Custom agent inside an Antigravity plugin bundle
|
|
37653
|
+
* (`<plugin_name>/agents/<name>.md`). Plugin bundles are project-scope output
|
|
37654
|
+
* only; they are staged into `~/.gemini/antigravity-cli/plugins/` by the user.
|
|
37655
|
+
*
|
|
37656
|
+
* @see https://antigravity.google/docs/cli/plugins
|
|
37657
|
+
*/
|
|
37658
|
+
var AntigravityPluginSubagent = class extends AntigravitySharedSubagent {
|
|
37659
|
+
static getToolTarget() {
|
|
37660
|
+
return "antigravity-plugin";
|
|
37661
|
+
}
|
|
37662
|
+
static getReadSectionKeys() {
|
|
37663
|
+
return [...super.getReadSectionKeys(), "antigravity-plugin"];
|
|
37664
|
+
}
|
|
37665
|
+
static getSettablePaths() {
|
|
37666
|
+
return { relativeDirPath: ANTIGRAVITY_PLUGIN_AGENTS_DIR };
|
|
37667
|
+
}
|
|
37668
|
+
};
|
|
37669
|
+
//#endregion
|
|
36706
37670
|
//#region src/features/subagents/augmentcode-subagent.ts
|
|
36707
37671
|
const AugmentcodeSubagentFrontmatterSchema = z.looseObject({
|
|
36708
37672
|
name: z.string(),
|
|
@@ -40280,6 +41244,30 @@ const toolSubagentFactories = /* @__PURE__ */ new Map([
|
|
|
40280
41244
|
filePattern: "*.md"
|
|
40281
41245
|
}
|
|
40282
41246
|
}],
|
|
41247
|
+
["antigravity-cli", {
|
|
41248
|
+
class: AntigravityCliSubagent,
|
|
41249
|
+
meta: {
|
|
41250
|
+
supportsSimulated: false,
|
|
41251
|
+
supportsGlobal: true,
|
|
41252
|
+
filePattern: "*.md"
|
|
41253
|
+
}
|
|
41254
|
+
}],
|
|
41255
|
+
["antigravity-ide", {
|
|
41256
|
+
class: AntigravityIdeSubagent,
|
|
41257
|
+
meta: {
|
|
41258
|
+
supportsSimulated: false,
|
|
41259
|
+
supportsGlobal: true,
|
|
41260
|
+
filePattern: "*.md"
|
|
41261
|
+
}
|
|
41262
|
+
}],
|
|
41263
|
+
["antigravity-plugin", {
|
|
41264
|
+
class: AntigravityPluginSubagent,
|
|
41265
|
+
meta: {
|
|
41266
|
+
supportsSimulated: false,
|
|
41267
|
+
supportsGlobal: false,
|
|
41268
|
+
filePattern: "*.md"
|
|
41269
|
+
}
|
|
41270
|
+
}],
|
|
40283
41271
|
["augmentcode", {
|
|
40284
41272
|
class: AugmentcodeSubagent,
|
|
40285
41273
|
meta: {
|
|
@@ -42493,6 +43481,7 @@ var CodexcliRule = class CodexcliRule extends ToolRule {
|
|
|
42493
43481
|
const CopilotRuleFrontmatterSchema = z.object({
|
|
42494
43482
|
description: z.optional(z.string()),
|
|
42495
43483
|
applyTo: z.optional(z.string()),
|
|
43484
|
+
name: z.optional(z.string()),
|
|
42496
43485
|
excludeAgent: z.optional(z.union([
|
|
42497
43486
|
z.literal("code-review"),
|
|
42498
43487
|
z.literal("cloud-agent"),
|
|
@@ -42550,7 +43539,10 @@ var CopilotRule = class CopilotRule extends ToolRule {
|
|
|
42550
43539
|
root: this.isRoot(),
|
|
42551
43540
|
description: this.frontmatter.description,
|
|
42552
43541
|
globs,
|
|
42553
|
-
...this.frontmatter.excludeAgent && { copilot: {
|
|
43542
|
+
...(this.frontmatter.excludeAgent || this.frontmatter.name) && { copilot: {
|
|
43543
|
+
...this.frontmatter.excludeAgent && { excludeAgent: this.frontmatter.excludeAgent },
|
|
43544
|
+
...this.frontmatter.name && { name: this.frontmatter.name }
|
|
43545
|
+
} }
|
|
42554
43546
|
};
|
|
42555
43547
|
const relativeFilePath = this.getRelativeFilePath().replace(/\.instructions\.md$/, ".md");
|
|
42556
43548
|
return new RulesyncRule({
|
|
@@ -42569,7 +43561,8 @@ var CopilotRule = class CopilotRule extends ToolRule {
|
|
|
42569
43561
|
const copilotFrontmatter = {
|
|
42570
43562
|
description: rulesyncFrontmatter.description,
|
|
42571
43563
|
applyTo: rulesyncFrontmatter.globs?.length ? rulesyncFrontmatter.globs.join(",") : void 0,
|
|
42572
|
-
excludeAgent: rulesyncFrontmatter.copilot?.excludeAgent
|
|
43564
|
+
excludeAgent: rulesyncFrontmatter.copilot?.excludeAgent,
|
|
43565
|
+
name: rulesyncFrontmatter.copilot?.name
|
|
42573
43566
|
};
|
|
42574
43567
|
const body = rulesyncRule.getBody();
|
|
42575
43568
|
if (root) return new CopilotRule({
|
|
@@ -48038,4 +49031,4 @@ async function importChecksCore(params) {
|
|
|
48038
49031
|
//#endregion
|
|
48039
49032
|
export { ErrorCodes as $, RULESYNC_SKILLS_RELATIVE_DIR_PATH as $t, RulesyncMcp as A, RULESYNC_AIIGNORE_RELATIVE_FILE_PATH as At, stringifyFrontmatter as B, RULESYNC_IGNORE_RELATIVE_FILE_PATH as Bt, RulesyncSubagent as C, writeFileContent as Ct, RulesyncRule as D, ToolTargetSchema as Dt, RulesyncSkillFrontmatterSchema as E, PACKAGING_TOOL_TARGETS as Et, parseJsonc as F, RULESYNC_CURATED_RULES_RELATIVE_DIR_PATH as Ft, ConfigFileSchema as G, RULESYNC_MCP_SCHEMA_URL as Gt, SHARED_USER_MANAGED_CONFIG_PATHS as H, RULESYNC_MCP_FILE_NAME as Ht, RulesyncCommand as I, RULESYNC_CURATED_SKILLS_RELATIVE_DIR_PATH as It, ConsoleLogger as J, RULESYNC_PERMISSIONS_LEGACY_FILE_NAME as Jt, SourceEntrySchema as K, RULESYNC_NPM_SOURCES_LOCK_RELATIVE_FILE_PATH as Kt, RulesyncCommandFrontmatterSchema as L, RULESYNC_HOOKS_FILE_NAME as Lt, RulesyncHooks as M, RULESYNC_COMMANDS_RELATIVE_DIR_PATH as Mt, getRulesyncSourceCandidates as N, RULESYNC_CONFIG_RELATIVE_FILE_PATH as Nt, RulesyncRuleFrontmatterSchema as O, MAX_FILE_SIZE as Ot, resolveRulesyncSourceWritePath as P, RULESYNC_CONFIG_SCHEMA_URL as Pt, CLIError as Q, RULESYNC_RULES_RELATIVE_DIR_PATH as Qt, RulesyncCheck as R, RULESYNC_HOOKS_LEGACY_FILE_NAME as Rt, getLocalSkillDirNames as S, toPosixPath as St, RulesyncSkill as T, ALL_TOOL_TARGETS_WITH_WILDCARD as Tt, SKILL_FILE_NAME as U, RULESYNC_MCP_LEGACY_FILE_NAME as Ut, loadYaml as V, RULESYNC_LOCAL_CONFIG_RELATIVE_FILE_PATH as Vt, ConfigResolver as W, RULESYNC_MCP_RELATIVE_FILE_PATH as Wt, fallbackLogger as X, RULESYNC_PERMISSIONS_SCHEMA_URL as Xt, JsonLogger as Y, RULESYNC_PERMISSIONS_RELATIVE_FILE_PATH as Yt, warnOnConflictingFlags as Z, RULESYNC_RELATIVE_DIR_PATH as Zt, CLAUDECODE_SETTINGS_LOCAL_FILE_NAME as _, removeFile as _t, convertFromTool as a, directoryExists as at, CODEXCLI_BASH_RULES_FILE_NAME as b, resolvePath as bt, SubagentsProcessor as c, findFilesByGlobs as ct, IgnoreProcessor as d, isSymlink as dt, RULESYNC_SOURCES_LOCK_RELATIVE_FILE_PATH as en, assertDirectoryIfExists as et, HooksProcessor as f, listDirectoryFiles as ft, CLAUDECODE_MEMORIES_DIR_NAME as g, removeDirectoryStrict as gt, CLAUDECODE_LOCAL_RULE_FILE_NAME as h, removeDirectory as ht, getProcessorRegistryEntry as i, formatError as in, createTempDirectory as it, RulesyncIgnore as j, RULESYNC_CHECKS_RELATIVE_DIR_PATH as jt, RulesyncPermissions as k, RULESYNC_AIIGNORE_FILE_NAME as kt, SkillsProcessor as l, getFileSize as lt, CLAUDECODE_DIR as m, readFileContentOrNull as mt, checkRulesyncDirExists as n, ALL_FEATURES as nn, assertWritablePathInsideRoot as nt, isPackagingToolTarget as o, ensureDir as ot, CommandsProcessor as p, readFileContent as pt, findControlCharacter as q, RULESYNC_PERMISSIONS_FILE_NAME as qt, generate as r, ALL_FEATURES_WITH_WILDCARD as rn, checkPathTraversal as rt, RulesProcessor as s, fileExists as st, importFromTool as t, RULESYNC_SUBAGENTS_RELATIVE_DIR_PATH as tn, assertTreeContainsNoSymlinks as tt, McpProcessor as u, getHomeDirectory as ut, CLAUDECODE_SKILLS_DIR_PATH as v, removeFileStrict as vt, RulesyncSubagentFrontmatterSchema as w, ALL_TOOL_TARGETS as wt, CODEXCLI_DIR as x, runWithDirectoryRollback as xt, ChecksProcessor as y, removeTempDirectory as yt, RulesyncCheckFrontmatterSchema as z, RULESYNC_HOOKS_RELATIVE_FILE_PATH as zt };
|
|
48040
49033
|
|
|
48041
|
-
//# sourceMappingURL=import-
|
|
49034
|
+
//# sourceMappingURL=import-BmXT7mRS.js.map
|