rulesync 37.0.0 → 37.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/dist/cli/index.cjs +3 -3
- package/dist/cli/index.js +3 -3
- package/dist/cli/index.js.map +1 -1
- package/dist/{import-BPSwSahy.js → import-BsEFYJsg.js} +461 -32
- package/dist/{import-BPSwSahy.js.map → import-BsEFYJsg.js.map} +1 -1
- package/dist/{import-S70XttcD.cjs → import-DYYG88Rs.cjs} +460 -31
- package/dist/index.cjs +1 -1
- package/dist/index.js +1 -1
- package/package.json +1 -1
|
@@ -7917,14 +7917,53 @@ const COPILOT_DEFAULT_PERMISSION_MODES = [
|
|
|
7917
7917
|
"autoApprove",
|
|
7918
7918
|
"autopilot"
|
|
7919
7919
|
];
|
|
7920
|
+
/** `chat.agent.sandbox.enabled`: `"off"` or `"on"`. */
|
|
7921
|
+
const CopilotSandboxEnabledSchema = zod_mini.z.enum(["off", "on"]);
|
|
7922
|
+
/** `chat.agent.sandbox.fileSystem.userConfiguredPaths`. */
|
|
7923
|
+
const CopilotSandboxUserConfiguredPathsSchema = zod_mini.z.strictObject({
|
|
7924
|
+
readwritePaths: zod_mini.z.optional(zod_mini.z.array(zod_mini.z.string())),
|
|
7925
|
+
readonlyPaths: zod_mini.z.optional(zod_mini.z.array(zod_mini.z.string())),
|
|
7926
|
+
deniedPaths: zod_mini.z.optional(zod_mini.z.array(zod_mini.z.string()))
|
|
7927
|
+
});
|
|
7928
|
+
/**
|
|
7929
|
+
* The Agent Host sandbox settings VS Code 1.141 settled on, as a nested object
|
|
7930
|
+
* whose path mirrors the dotted setting key under `chat.agent.sandbox.` — e.g.
|
|
7931
|
+
* `network.allowNetwork` writes `chat.agent.sandbox.network.allowNetwork`.
|
|
7932
|
+
* Only the documented, registered keys are accepted; the deprecated 1.138
|
|
7933
|
+
* shapes (`chat.agent.sandbox.allowNetwork`, `.fileSystem.linux|mac|windows`)
|
|
7934
|
+
* are not modeled, since the Agent Host ignores them.
|
|
7935
|
+
*
|
|
7936
|
+
* @see https://code.visualstudio.com/docs/agents/run/agent-sandboxing
|
|
7937
|
+
* @see https://code.visualstudio.com/docs/agents/reference/ai-settings
|
|
7938
|
+
*/
|
|
7939
|
+
const CopilotSandboxSchema = zod_mini.z.strictObject({
|
|
7940
|
+
enabled: zod_mini.z.optional(CopilotSandboxEnabledSchema),
|
|
7941
|
+
network: zod_mini.z.optional(zod_mini.z.strictObject({
|
|
7942
|
+
allowNetwork: zod_mini.z.optional(zod_mini.z.boolean()),
|
|
7943
|
+
allowLocalNetwork: zod_mini.z.optional(zod_mini.z.boolean())
|
|
7944
|
+
})),
|
|
7945
|
+
allowUnsandboxedCommands: zod_mini.z.optional(zod_mini.z.boolean()),
|
|
7946
|
+
mcpServers: zod_mini.z.optional(zod_mini.z.boolean()),
|
|
7947
|
+
lspServers: zod_mini.z.optional(zod_mini.z.boolean()),
|
|
7948
|
+
credentials: zod_mini.z.optional(zod_mini.z.strictObject({
|
|
7949
|
+
authenticategit: zod_mini.z.optional(zod_mini.z.boolean()),
|
|
7950
|
+
authenticategh: zod_mini.z.optional(zod_mini.z.boolean())
|
|
7951
|
+
})),
|
|
7952
|
+
fileSystem: zod_mini.z.optional(zod_mini.z.strictObject({
|
|
7953
|
+
allowDevToolAccess: zod_mini.z.optional(zod_mini.z.boolean()),
|
|
7954
|
+
userConfiguredPaths: zod_mini.z.optional(CopilotSandboxUserConfiguredPathsSchema)
|
|
7955
|
+
}))
|
|
7956
|
+
});
|
|
7920
7957
|
/**
|
|
7921
7958
|
* Tool-scoped override block for GitHub Copilot in VS Code.
|
|
7922
7959
|
* `defaultPermissionMode` writes the workspace `chat.permissions.default`
|
|
7923
|
-
* setting,
|
|
7960
|
+
* setting, and `sandbox` writes the `chat.agent.sandbox.*` settings; neither
|
|
7961
|
+
* has a canonical category.
|
|
7924
7962
|
*/
|
|
7925
7963
|
const CopilotPermissionsOverrideSchema = zod_mini.z.looseObject({
|
|
7926
7964
|
permission: zod_mini.z.optional(ToolScopedPermissionSchema),
|
|
7927
|
-
defaultPermissionMode: zod_mini.z.optional(zod_mini.z.enum(COPILOT_DEFAULT_PERMISSION_MODES))
|
|
7965
|
+
defaultPermissionMode: zod_mini.z.optional(zod_mini.z.enum(COPILOT_DEFAULT_PERMISSION_MODES)),
|
|
7966
|
+
sandbox: zod_mini.z.optional(CopilotSandboxSchema)
|
|
7928
7967
|
});
|
|
7929
7968
|
/**
|
|
7930
7969
|
* oh-my-pi's `tools.approvalMode` values: the tool tiers it auto-approves
|
|
@@ -12896,6 +12935,16 @@ const SHARED_CONFIG_OWNERSHIP = {
|
|
|
12896
12935
|
"chat.tools.edits.autoApprove",
|
|
12897
12936
|
"chat.tools.urls.autoApprove",
|
|
12898
12937
|
"chat.permissions.default",
|
|
12938
|
+
"chat.agent.sandbox.enabled",
|
|
12939
|
+
"chat.agent.sandbox.network.allowNetwork",
|
|
12940
|
+
"chat.agent.sandbox.network.allowLocalNetwork",
|
|
12941
|
+
"chat.agent.sandbox.allowUnsandboxedCommands",
|
|
12942
|
+
"chat.agent.sandbox.mcpServers",
|
|
12943
|
+
"chat.agent.sandbox.lspServers",
|
|
12944
|
+
"chat.agent.sandbox.credentials.authenticategit",
|
|
12945
|
+
"chat.agent.sandbox.credentials.authenticategh",
|
|
12946
|
+
"chat.agent.sandbox.fileSystem.allowDevToolAccess",
|
|
12947
|
+
"chat.agent.sandbox.fileSystem.userConfiguredPaths",
|
|
12899
12948
|
"zoo-code.allowedCommands",
|
|
12900
12949
|
"zoo-code.deniedCommands",
|
|
12901
12950
|
"roo-cline.allowedCommands",
|
|
@@ -28058,8 +28107,34 @@ function collectOwnedRefs({ generatedHooks, shape }) {
|
|
|
28058
28107
|
}
|
|
28059
28108
|
return owned;
|
|
28060
28109
|
}
|
|
28110
|
+
/**
|
|
28111
|
+
* The merge keeps every unowned existing handler where it is. Some tools key
|
|
28112
|
+
* state by position — Codex CLI persists a hook's trust review under its event,
|
|
28113
|
+
* matcher group index and handler index — so moving a handler the user already
|
|
28114
|
+
* reviewed would silently change which hook that review belongs to. Hence:
|
|
28115
|
+
* - an unowned existing handler stays at its index;
|
|
28116
|
+
* - a newly generated handler is appended to the first existing group with its
|
|
28117
|
+
* matcher and group-level keys, or in a new group after the existing ones;
|
|
28118
|
+
* - within a group, the slots rulesync's own handlers occupy (including the
|
|
28119
|
+
* appended ones) are filled in generated order, so reordering the sources
|
|
28120
|
+
* still reorders the generated handlers, which matters wherever handlers
|
|
28121
|
+
* run in order (a Qwen Code `sequential` group, a Cursor list).
|
|
28122
|
+
*
|
|
28123
|
+
* A retracted handler, or a group left empty by retractions, is still removed,
|
|
28124
|
+
* which shifts whatever follows it in the same list; that is unavoidable
|
|
28125
|
+
* without leaving a placeholder behind.
|
|
28126
|
+
*/
|
|
28061
28127
|
function mergeMatcherGroups({ existing, generated, event, seen, previouslyOwned, logger }) {
|
|
28062
|
-
const
|
|
28128
|
+
const pending = createPendingGenerated();
|
|
28129
|
+
for (const group of generated) if (isMatcherGroup(group)) addPending({
|
|
28130
|
+
pending,
|
|
28131
|
+
handlers: group.hooks,
|
|
28132
|
+
event,
|
|
28133
|
+
matcher: matcherKey(group.matcher),
|
|
28134
|
+
attributes: groupAttributesKey(group)
|
|
28135
|
+
});
|
|
28136
|
+
const merged = [];
|
|
28137
|
+
const homes = /* @__PURE__ */ new Map();
|
|
28063
28138
|
for (const group of existing) {
|
|
28064
28139
|
if (!isMatcherGroup(group)) {
|
|
28065
28140
|
warnSkip({
|
|
@@ -28070,27 +28145,57 @@ function mergeMatcherGroups({ existing, generated, event, seen, previouslyOwned,
|
|
|
28070
28145
|
continue;
|
|
28071
28146
|
}
|
|
28072
28147
|
const matcher = matcherKey(group.matcher);
|
|
28073
|
-
const
|
|
28074
|
-
|
|
28148
|
+
const hooks = keepExistingHandlers({
|
|
28149
|
+
handlers: group.hooks,
|
|
28075
28150
|
event,
|
|
28076
28151
|
matcher,
|
|
28152
|
+
attributes: groupAttributesKey(group),
|
|
28153
|
+
pending,
|
|
28077
28154
|
seen,
|
|
28078
28155
|
previouslyOwned,
|
|
28079
28156
|
logger
|
|
28080
|
-
})
|
|
28081
|
-
if (
|
|
28082
|
-
const
|
|
28083
|
-
const target = merged.find((candidate) => isMatcherGroup(candidate) && matcherKey(candidate.matcher) === matcher && groupAttributesKey(candidate) === attributes);
|
|
28084
|
-
if (target !== void 0 && isMatcherGroup(target)) target.hooks.push(...preserved);
|
|
28085
|
-
else merged.push({
|
|
28157
|
+
});
|
|
28158
|
+
if (hooks.length === 0) continue;
|
|
28159
|
+
const kept = {
|
|
28086
28160
|
...omitPrototypePollutionKeys(group),
|
|
28087
|
-
hooks
|
|
28161
|
+
hooks
|
|
28162
|
+
};
|
|
28163
|
+
merged.push(kept);
|
|
28164
|
+
const home = groupSlotKey(group);
|
|
28165
|
+
if (!homes.has(home)) homes.set(home, kept);
|
|
28166
|
+
}
|
|
28167
|
+
for (const group of generated) {
|
|
28168
|
+
if (!isMatcherGroup(group)) {
|
|
28169
|
+
merged.push(group);
|
|
28170
|
+
continue;
|
|
28171
|
+
}
|
|
28172
|
+
const remaining = group.hooks.filter((handler) => !pending.placed.has(handler));
|
|
28173
|
+
if (remaining.length === 0) continue;
|
|
28174
|
+
const home = homes.get(groupSlotKey(group));
|
|
28175
|
+
if (home === void 0) merged.push({
|
|
28176
|
+
...group,
|
|
28177
|
+
hooks: remaining
|
|
28088
28178
|
});
|
|
28179
|
+
else home.hooks.push(...remaining);
|
|
28089
28180
|
}
|
|
28181
|
+
const order = generatedOrder(generated.flatMap((group) => isMatcherGroup(group) ? group.hooks : []));
|
|
28182
|
+
for (const group of merged) if (isMatcherGroup(group)) orderGeneratedSlots({
|
|
28183
|
+
handlers: group.hooks,
|
|
28184
|
+
order
|
|
28185
|
+
});
|
|
28090
28186
|
return merged;
|
|
28091
28187
|
}
|
|
28188
|
+
/** The flat-list counterpart of {@link mergeMatcherGroups}, with the same positional rules. */
|
|
28092
28189
|
function mergeFlatHandlers({ existing, generated, event, seen, previouslyOwned, logger }) {
|
|
28093
|
-
const
|
|
28190
|
+
const pending = createPendingGenerated();
|
|
28191
|
+
addPending({
|
|
28192
|
+
pending,
|
|
28193
|
+
handlers: generated,
|
|
28194
|
+
event,
|
|
28195
|
+
matcher: void 0,
|
|
28196
|
+
attributes: ""
|
|
28197
|
+
});
|
|
28198
|
+
const flat = [];
|
|
28094
28199
|
for (const handler of existing) {
|
|
28095
28200
|
if (isMatcherGroup(handler)) {
|
|
28096
28201
|
warnSkip({
|
|
@@ -28100,17 +28205,93 @@ function mergeFlatHandlers({ existing, generated, event, seen, previouslyOwned,
|
|
|
28100
28205
|
});
|
|
28101
28206
|
continue;
|
|
28102
28207
|
}
|
|
28103
|
-
|
|
28208
|
+
flat.push(handler);
|
|
28209
|
+
}
|
|
28210
|
+
const merged = keepExistingHandlers({
|
|
28211
|
+
handlers: flat,
|
|
28212
|
+
event,
|
|
28213
|
+
matcher: void 0,
|
|
28214
|
+
attributes: "",
|
|
28215
|
+
pending,
|
|
28216
|
+
seen,
|
|
28217
|
+
previouslyOwned,
|
|
28218
|
+
logger
|
|
28219
|
+
});
|
|
28220
|
+
merged.push(...generated.filter((handler) => !pending.placed.has(handler)));
|
|
28221
|
+
orderGeneratedSlots({
|
|
28222
|
+
handlers: merged,
|
|
28223
|
+
order: generatedOrder(generated)
|
|
28224
|
+
});
|
|
28225
|
+
return merged;
|
|
28226
|
+
}
|
|
28227
|
+
/** Each generated handler's position in the generated list, by object identity. */
|
|
28228
|
+
function generatedOrder(handlers) {
|
|
28229
|
+
const order = /* @__PURE__ */ new Map();
|
|
28230
|
+
for (const [index, handler] of handlers.entries()) if (isPlainObject$1(handler)) order.set(handler, index);
|
|
28231
|
+
return order;
|
|
28232
|
+
}
|
|
28233
|
+
/**
|
|
28234
|
+
* Rewrite, in place, the slots of `handlers` that hold generated handlers so
|
|
28235
|
+
* they follow the generated order. Every other slot — an unowned handler — is
|
|
28236
|
+
* left at its index.
|
|
28237
|
+
*/
|
|
28238
|
+
function orderGeneratedSlots({ handlers, order }) {
|
|
28239
|
+
const slots = [];
|
|
28240
|
+
for (const [index, handler] of handlers.entries()) if (order.has(handler)) slots.push(index);
|
|
28241
|
+
const sorted = slots.map((index) => handlers[index]).toSorted((left, right) => (order.get(left) ?? 0) - (order.get(right) ?? 0));
|
|
28242
|
+
for (const [position, index] of slots.entries()) handlers[index] = sorted[position];
|
|
28243
|
+
}
|
|
28244
|
+
function createPendingGenerated() {
|
|
28245
|
+
return {
|
|
28246
|
+
byKey: /* @__PURE__ */ new Map(),
|
|
28247
|
+
placed: /* @__PURE__ */ new Set()
|
|
28248
|
+
};
|
|
28249
|
+
}
|
|
28250
|
+
function addPending({ pending, handlers, event, matcher, attributes }) {
|
|
28251
|
+
for (const handler of handlers) {
|
|
28252
|
+
if (!isPlainObject$1(handler)) continue;
|
|
28253
|
+
const key = pendingKey({
|
|
28254
|
+
event,
|
|
28255
|
+
matcher,
|
|
28256
|
+
attributes,
|
|
28257
|
+
handler
|
|
28258
|
+
});
|
|
28259
|
+
const queue = pending.byKey.get(key);
|
|
28260
|
+
if (queue === void 0) pending.byKey.set(key, [handler]);
|
|
28261
|
+
else queue.push(handler);
|
|
28262
|
+
}
|
|
28263
|
+
}
|
|
28264
|
+
/**
|
|
28265
|
+
* Walk one existing list in order and decide each handler: replaced in place
|
|
28266
|
+
* by its generated copy, kept as unowned, or dropped (retracted or duplicate).
|
|
28267
|
+
*/
|
|
28268
|
+
function keepExistingHandlers({ handlers, event, matcher, attributes, pending, seen, previouslyOwned, logger }) {
|
|
28269
|
+
const kept = [];
|
|
28270
|
+
for (const handler of handlers) {
|
|
28271
|
+
if (isPlainObject$1(handler)) {
|
|
28272
|
+
const key = pendingKey({
|
|
28273
|
+
event,
|
|
28274
|
+
matcher,
|
|
28275
|
+
attributes,
|
|
28276
|
+
handler
|
|
28277
|
+
});
|
|
28278
|
+
const generated = pending.byKey.get(key)?.shift();
|
|
28279
|
+
if (generated !== void 0) {
|
|
28280
|
+
pending.placed.add(generated);
|
|
28281
|
+
kept.push(generated);
|
|
28282
|
+
continue;
|
|
28283
|
+
}
|
|
28284
|
+
}
|
|
28285
|
+
if (shouldPreserve({
|
|
28104
28286
|
handler,
|
|
28105
28287
|
event,
|
|
28106
|
-
matcher
|
|
28288
|
+
matcher,
|
|
28107
28289
|
seen,
|
|
28108
28290
|
previouslyOwned,
|
|
28109
28291
|
logger
|
|
28110
|
-
}))
|
|
28111
|
-
preserved.push(cloneHandler(handler));
|
|
28292
|
+
})) kept.push(cloneHandler(handler));
|
|
28112
28293
|
}
|
|
28113
|
-
return
|
|
28294
|
+
return kept;
|
|
28114
28295
|
}
|
|
28115
28296
|
function isMatcherGroup(value) {
|
|
28116
28297
|
return isPlainObject$1(value) && Array.isArray(value.hooks);
|
|
@@ -28201,10 +28382,35 @@ function stableStringify$1(value) {
|
|
|
28201
28382
|
function matcherKey(matcher) {
|
|
28202
28383
|
return matcher === void 0 ? "" : stableStringify$1(matcher);
|
|
28203
28384
|
}
|
|
28204
|
-
|
|
28385
|
+
function pendingKey({ event, matcher, attributes, handler }) {
|
|
28386
|
+
const key = ownedHookKey({
|
|
28387
|
+
event,
|
|
28388
|
+
matcher,
|
|
28389
|
+
identity: handlerIdentity(handler)
|
|
28390
|
+
});
|
|
28391
|
+
return JSON.stringify([key, attributes]);
|
|
28392
|
+
}
|
|
28393
|
+
/** Groups that share a matcher and attributes are interchangeable homes for a handler. */
|
|
28394
|
+
function groupSlotKey(group) {
|
|
28395
|
+
return JSON.stringify([matcherKey(group.matcher), groupAttributesKey(group)]);
|
|
28396
|
+
}
|
|
28397
|
+
/** Group-level keys whose default value is known, so an explicit default can be ignored. */
|
|
28398
|
+
const GROUP_ATTRIBUTE_DEFAULTS = Object.freeze({
|
|
28399
|
+
__proto__: null,
|
|
28400
|
+
sequential: false
|
|
28401
|
+
});
|
|
28402
|
+
/**
|
|
28403
|
+
* The stable form of a matcher group's keys other than `matcher` and `hooks`.
|
|
28404
|
+
*
|
|
28405
|
+
* A key set to its known default is the same group as one that omits it:
|
|
28406
|
+
* Qwen Code's `sequential` defaults to `false` and rulesync emits it only when
|
|
28407
|
+
* `true`, so an explicit `sequential: false` (or any `undefined` key) is
|
|
28408
|
+
* dropped before comparing. Other `false` values are kept, since their default
|
|
28409
|
+
* is not known here.
|
|
28410
|
+
*/
|
|
28205
28411
|
function groupAttributesKey(group) {
|
|
28206
28412
|
const { matcher: _matcher, hooks: _hooks, ...attributes } = group;
|
|
28207
|
-
return stableStringify$1(attributes);
|
|
28413
|
+
return stableStringify$1(Object.fromEntries(Object.entries(attributes).filter(([key, value]) => value !== void 0 && GROUP_ATTRIBUTE_DEFAULTS[key] !== value)));
|
|
28208
28414
|
}
|
|
28209
28415
|
function warnSkip({ logger, event, expected }) {
|
|
28210
28416
|
logger?.warn(`Skipping existing hook entry on ${event}: expected ${expected}`);
|
|
@@ -42673,9 +42879,11 @@ var CopilotcliMcp = class CopilotcliMcp extends ToolMcp {
|
|
|
42673
42879
|
outputRoot,
|
|
42674
42880
|
global
|
|
42675
42881
|
});
|
|
42882
|
+
const existingDisabled = settings.document?.disabledMcpServers;
|
|
42883
|
+
const offSwitchBlocker = settings.document === null ? "cannot be parsed" : existingDisabled !== void 0 && !isStringArray$1(existingDisabled) ? "has a disabledMcpServers value that is not an array of server names" : void 0;
|
|
42676
42884
|
const copilotCliMcpServers = addTypeField(Object.fromEntries(Object.entries(rulesyncMcp.getMcpServers()).filter(([name, server]) => {
|
|
42677
|
-
if (server.disabled !== true ||
|
|
42678
|
-
logger?.warn(`[CopilotcliMcp] Skipping disabled MCP server "${name}": ${(0, node_path.join)(settings.paths.relativeDirPath, settings.paths.relativeFilePath)}
|
|
42885
|
+
if (server.disabled !== true || offSwitchBlocker === void 0) return true;
|
|
42886
|
+
logger?.warn(`[CopilotcliMcp] Skipping disabled MCP server "${name}": ${(0, node_path.join)(settings.paths.relativeDirPath, settings.paths.relativeFilePath)} ${offSwitchBlocker}, so disabledMcpServers cannot be written to switch it off.`);
|
|
42679
42887
|
return false;
|
|
42680
42888
|
})), logger);
|
|
42681
42889
|
const mcpJson = {
|
|
@@ -42705,7 +42913,8 @@ var CopilotcliMcp = class CopilotcliMcp extends ToolMcp {
|
|
|
42705
42913
|
* Copilot CLI actually consults: an MCP file entry has no `disabled` field.
|
|
42706
42914
|
* rulesync owns the toggle for the servers it writes; names it does not
|
|
42707
42915
|
* manage keep their existing state. The file is not created or touched when
|
|
42708
|
-
* there is nothing to disable and nothing to retract
|
|
42916
|
+
* there is nothing to disable and nothing to retract, nor when the existing
|
|
42917
|
+
* `disabledMcpServers` is not a string array (left as is, with a warning).
|
|
42709
42918
|
*/
|
|
42710
42919
|
static async getAuxiliaryFiles({ outputRoot = process.cwd(), global = false, rulesyncMcp, logger }) {
|
|
42711
42920
|
const servers = rulesyncMcp.forTarget({
|
|
@@ -42723,7 +42932,11 @@ var CopilotcliMcp = class CopilotcliMcp extends ToolMcp {
|
|
|
42723
42932
|
return [];
|
|
42724
42933
|
}
|
|
42725
42934
|
const existing = settings.document.disabledMcpServers;
|
|
42726
|
-
|
|
42935
|
+
if (existing !== void 0 && !isStringArray$1(existing)) {
|
|
42936
|
+
logger?.warn(`Skipping the Copilot CLI disabledMcpServers update: the existing value is not an array of server names. Fix it in ${(0, node_path.join)(settings.paths.relativeDirPath, settings.paths.relativeFilePath)} so rulesync can manage the toggle.`);
|
|
42937
|
+
return [];
|
|
42938
|
+
}
|
|
42939
|
+
const existingDisabled = existing ?? [];
|
|
42727
42940
|
const managedNameSet = new Set(managedNames);
|
|
42728
42941
|
const reEnabled = existingDisabled.filter((name) => managedNameSet.has(name) && !disabledNames.includes(name));
|
|
42729
42942
|
if (reEnabled.length > 0) logger?.warn(`Copilot CLI MCP: re-enabling ${reEnabled.join(", ")} — the canonical config does not mark ${reEnabled.length === 1 ? "it" : "them"} disabled. Set "disabled": true in .rulesync/mcp.jsonc to keep a managed server off.`);
|
|
@@ -56729,7 +56942,7 @@ function convertClaudeToRulesyncPermissions(params) {
|
|
|
56729
56942
|
* (https://docs.cline.bot/cli/cli-reference). The output is still generated for
|
|
56730
56943
|
* compatibility, with a deprecation warning on every generation.
|
|
56731
56944
|
*/
|
|
56732
|
-
const CLINE_PERMISSIONS_DEPRECATION_WARNING = "
|
|
56945
|
+
const CLINE_PERMISSIONS_DEPRECATION_WARNING = "The Cline permissions output (.cline/command-permissions.json) is deprecated. No Cline CLI 3 release reads CLINE_COMMAND_PERMISSIONS, so the generated allow/deny lists are not enforced by current Cline releases. To block shell commands in Cline, use a preToolUse hook (rulesync hooks feature, cline target) that cancels the run_commands calls you do not allow. Note that a hook returning `cancel: true` stops the whole Cline task, not just that one tool call (https://github.com/cline/cline/issues/14697). See https://docs.cline.bot/cli/cli-reference.";
|
|
56733
56946
|
const ClineCommandPermissionsSchema = zod_mini.z.looseObject({
|
|
56734
56947
|
allow: zod_mini.z.optional(zod_mini.z.array(zod_mini.z.string())),
|
|
56735
56948
|
deny: zod_mini.z.optional(zod_mini.z.array(zod_mini.z.string())),
|
|
@@ -60245,6 +60458,193 @@ const AUTO_APPROVE_KEYS = {
|
|
|
60245
60458
|
* @see https://code.visualstudio.com/docs/agents/reference/ai-settings
|
|
60246
60459
|
*/
|
|
60247
60460
|
const DEFAULT_PERMISSION_MODE_KEY = "chat.permissions.default";
|
|
60461
|
+
/**
|
|
60462
|
+
* The prefix every Agent Host sandbox setting shares. A `copilot.sandbox` leaf
|
|
60463
|
+
* at path `a.b` is written to `chat.agent.sandbox.a.b`.
|
|
60464
|
+
*/
|
|
60465
|
+
const SANDBOX_KEY_PREFIX = "chat.agent.sandbox.";
|
|
60466
|
+
/**
|
|
60467
|
+
* The `copilot.sandbox` leaves rulesync manages, each a flat VS Code setting
|
|
60468
|
+
* (`SANDBOX_KEY_PREFIX` + the dotted path) with the value shape VS Code
|
|
60469
|
+
* registers for it. The shape is re-checked on import, so a hand-written value
|
|
60470
|
+
* VS Code would not accept is skipped instead of failing the whole import.
|
|
60471
|
+
*
|
|
60472
|
+
* @see https://code.visualstudio.com/docs/agents/run/agent-sandboxing
|
|
60473
|
+
* @see https://code.visualstudio.com/docs/agents/reference/ai-settings
|
|
60474
|
+
*/
|
|
60475
|
+
const SANDBOX_LEAVES = [
|
|
60476
|
+
{
|
|
60477
|
+
path: ["enabled"],
|
|
60478
|
+
schema: CopilotSandboxEnabledSchema,
|
|
60479
|
+
normalize: (value) => value === true ? "on" : value === false ? "off" : value
|
|
60480
|
+
},
|
|
60481
|
+
{
|
|
60482
|
+
path: ["network", "allowNetwork"],
|
|
60483
|
+
schema: zod_mini.z.boolean()
|
|
60484
|
+
},
|
|
60485
|
+
{
|
|
60486
|
+
path: ["network", "allowLocalNetwork"],
|
|
60487
|
+
schema: zod_mini.z.boolean()
|
|
60488
|
+
},
|
|
60489
|
+
{
|
|
60490
|
+
path: ["allowUnsandboxedCommands"],
|
|
60491
|
+
schema: zod_mini.z.boolean()
|
|
60492
|
+
},
|
|
60493
|
+
{
|
|
60494
|
+
path: ["mcpServers"],
|
|
60495
|
+
schema: zod_mini.z.boolean()
|
|
60496
|
+
},
|
|
60497
|
+
{
|
|
60498
|
+
path: ["lspServers"],
|
|
60499
|
+
schema: zod_mini.z.boolean()
|
|
60500
|
+
},
|
|
60501
|
+
{
|
|
60502
|
+
path: ["credentials", "authenticategit"],
|
|
60503
|
+
schema: zod_mini.z.boolean()
|
|
60504
|
+
},
|
|
60505
|
+
{
|
|
60506
|
+
path: ["credentials", "authenticategh"],
|
|
60507
|
+
schema: zod_mini.z.boolean()
|
|
60508
|
+
},
|
|
60509
|
+
{
|
|
60510
|
+
path: ["fileSystem", "allowDevToolAccess"],
|
|
60511
|
+
schema: zod_mini.z.boolean()
|
|
60512
|
+
},
|
|
60513
|
+
{
|
|
60514
|
+
path: ["fileSystem", "userConfiguredPaths"],
|
|
60515
|
+
schema: CopilotSandboxUserConfiguredPathsSchema
|
|
60516
|
+
}
|
|
60517
|
+
];
|
|
60518
|
+
const USER_CONFIGURED_PATHS_KEY = `${SANDBOX_KEY_PREFIX}fileSystem.userConfiguredPaths`;
|
|
60519
|
+
/**
|
|
60520
|
+
* `copilot.sandbox` values that loosen the Agent Host sandbox. They are still
|
|
60521
|
+
* written — a sandbox policy is the user's call — but never silently. Each
|
|
60522
|
+
* `widens` names the value that does *not* loosen anything and reports the
|
|
60523
|
+
* rest, so an unexpected value is reported rather than ignored.
|
|
60524
|
+
*
|
|
60525
|
+
* @see https://code.visualstudio.com/docs/agents/run/agent-sandboxing
|
|
60526
|
+
*/
|
|
60527
|
+
const COPILOT_TRUST_AFFECTING_SANDBOX_PATHS = [
|
|
60528
|
+
{
|
|
60529
|
+
path: ["enabled"],
|
|
60530
|
+
reason: "turns the sandbox off, so agent terminal commands run unconfined",
|
|
60531
|
+
widens: (value) => value !== "on"
|
|
60532
|
+
},
|
|
60533
|
+
{
|
|
60534
|
+
path: ["network", "allowNetwork"],
|
|
60535
|
+
reason: "lets sandboxed commands reach the external network",
|
|
60536
|
+
widens: isNotFalse
|
|
60537
|
+
},
|
|
60538
|
+
{
|
|
60539
|
+
path: ["network", "allowLocalNetwork"],
|
|
60540
|
+
reason: "lets sandboxed commands reach local network resources",
|
|
60541
|
+
widens: isNotFalse
|
|
60542
|
+
},
|
|
60543
|
+
{
|
|
60544
|
+
path: ["allowUnsandboxedCommands"],
|
|
60545
|
+
reason: "lets the agent ask to run a blocked command outside the sandbox",
|
|
60546
|
+
widens: isNotFalse
|
|
60547
|
+
},
|
|
60548
|
+
{
|
|
60549
|
+
path: ["mcpServers"],
|
|
60550
|
+
reason: "runs locally launched MCP servers outside the sandbox",
|
|
60551
|
+
widens: isNotTrue
|
|
60552
|
+
},
|
|
60553
|
+
{
|
|
60554
|
+
path: ["lspServers"],
|
|
60555
|
+
reason: "runs language servers outside the sandbox",
|
|
60556
|
+
widens: isNotTrue
|
|
60557
|
+
},
|
|
60558
|
+
{
|
|
60559
|
+
path: ["credentials", "authenticategit"],
|
|
60560
|
+
reason: "hands your Git credentials to sandboxed commands",
|
|
60561
|
+
widens: isNotFalse
|
|
60562
|
+
},
|
|
60563
|
+
{
|
|
60564
|
+
path: ["credentials", "authenticategh"],
|
|
60565
|
+
reason: "hands your GitHub CLI authentication to sandboxed commands",
|
|
60566
|
+
widens: isNotFalse
|
|
60567
|
+
},
|
|
60568
|
+
{
|
|
60569
|
+
path: ["fileSystem", "allowDevToolAccess"],
|
|
60570
|
+
reason: "grants sandboxed commands developer-tool directories, which can hold credentials such as registry tokens",
|
|
60571
|
+
widens: isNotFalse
|
|
60572
|
+
},
|
|
60573
|
+
{
|
|
60574
|
+
path: [
|
|
60575
|
+
"fileSystem",
|
|
60576
|
+
"userConfiguredPaths",
|
|
60577
|
+
"readwritePaths"
|
|
60578
|
+
],
|
|
60579
|
+
reason: "adds paths sandboxed commands may write to, outside the working directory",
|
|
60580
|
+
widens: isNonEmptyList
|
|
60581
|
+
},
|
|
60582
|
+
{
|
|
60583
|
+
path: [
|
|
60584
|
+
"fileSystem",
|
|
60585
|
+
"userConfiguredPaths",
|
|
60586
|
+
"readonlyPaths"
|
|
60587
|
+
],
|
|
60588
|
+
reason: "adds paths sandboxed commands may read, outside the working directory",
|
|
60589
|
+
widens: isNonEmptyList
|
|
60590
|
+
}
|
|
60591
|
+
];
|
|
60592
|
+
const COPILOT_TOOL_LABEL = "GitHub Copilot";
|
|
60593
|
+
function sandboxKey(path) {
|
|
60594
|
+
return `${SANDBOX_KEY_PREFIX}${path.join(".")}`;
|
|
60595
|
+
}
|
|
60596
|
+
/**
|
|
60597
|
+
* Patch entries for every `copilot.sandbox` leaf the canonical config states.
|
|
60598
|
+
* Leaves it does not state are left as the file has them, so authoring only
|
|
60599
|
+
* `network.allowNetwork` never disturbs a hand-written `enabled`. The
|
|
60600
|
+
* `userConfiguredPaths` object merges per list for the same reason: authoring
|
|
60601
|
+
* `readwritePaths` keeps a hand-written `deniedPaths`, which would otherwise be
|
|
60602
|
+
* a restriction lost without a word.
|
|
60603
|
+
*/
|
|
60604
|
+
function buildSandboxPatch({ sandbox, existingSettings }) {
|
|
60605
|
+
const patch = {};
|
|
60606
|
+
for (const { path } of SANDBOX_LEAVES) {
|
|
60607
|
+
const value = readSandboxPath({
|
|
60608
|
+
sandbox,
|
|
60609
|
+
path
|
|
60610
|
+
});
|
|
60611
|
+
if (value === void 0 || value === UNREADABLE_SANDBOX_PATH) continue;
|
|
60612
|
+
const key = sandboxKey(path);
|
|
60613
|
+
if (key === USER_CONFIGURED_PATHS_KEY && isPlainObject$1(value)) {
|
|
60614
|
+
const existing = existingSettings[key];
|
|
60615
|
+
patch[key] = {
|
|
60616
|
+
...isPlainObject$1(existing) ? existing : {},
|
|
60617
|
+
...value
|
|
60618
|
+
};
|
|
60619
|
+
continue;
|
|
60620
|
+
}
|
|
60621
|
+
patch[key] = value;
|
|
60622
|
+
}
|
|
60623
|
+
return patch;
|
|
60624
|
+
}
|
|
60625
|
+
/**
|
|
60626
|
+
* Lift every managed `chat.agent.sandbox.*` key the file holds back into a
|
|
60627
|
+
* nested `copilot.sandbox` object. A value whose shape VS Code would reject is
|
|
60628
|
+
* skipped; the write path leaves it in place.
|
|
60629
|
+
*/
|
|
60630
|
+
function readSandboxFromSettings(settings) {
|
|
60631
|
+
const sandbox = {};
|
|
60632
|
+
for (const { path, schema, normalize } of SANDBOX_LEAVES) {
|
|
60633
|
+
const value = settings[sandboxKey(path)];
|
|
60634
|
+
const parsed = schema.safeParse(normalize === void 0 ? value : normalize(value));
|
|
60635
|
+
if (!parsed.success) continue;
|
|
60636
|
+
let cursor = sandbox;
|
|
60637
|
+
for (const segment of path.slice(0, -1)) {
|
|
60638
|
+
const next = cursor[segment];
|
|
60639
|
+
const child = isPlainObject$1(next) ? next : {};
|
|
60640
|
+
cursor[segment] = child;
|
|
60641
|
+
cursor = child;
|
|
60642
|
+
}
|
|
60643
|
+
const leaf = path[path.length - 1];
|
|
60644
|
+
if (leaf !== void 0) cursor[leaf] = parsed.data;
|
|
60645
|
+
}
|
|
60646
|
+
return Object.keys(sandbox).length > 0 ? sandbox : void 0;
|
|
60647
|
+
}
|
|
60248
60648
|
function isCopilotDefaultPermissionMode(value) {
|
|
60249
60649
|
return COPILOT_DEFAULT_PERMISSION_MODES.some((mode) => mode === value);
|
|
60250
60650
|
}
|
|
@@ -60289,7 +60689,9 @@ function buildAutoApproveValue(rules) {
|
|
|
60289
60689
|
* hand-written edits or urls map.
|
|
60290
60690
|
* The `copilot.defaultPermissionMode` override writes `chat.permissions.default`
|
|
60291
60691
|
* (the starting permission level of new chat sessions); without it the key is
|
|
60292
|
-
* left untouched.
|
|
60692
|
+
* left untouched. The `copilot.sandbox` override writes the Agent Host sandbox
|
|
60693
|
+
* settings (`chat.agent.sandbox.*`, see {@link SANDBOX_LEAVES}) per key, and
|
|
60694
|
+
* warns about every value it writes that loosens the sandbox.
|
|
60293
60695
|
* Only project scope is modeled: VS Code's user-scope settings.json lives at a
|
|
60294
60696
|
* platform-dependent path outside rulesync's home-relative global model.
|
|
60295
60697
|
*/
|
|
@@ -60337,6 +60739,26 @@ var CopilotPermissions = class CopilotPermissions extends ToolPermissions {
|
|
|
60337
60739
|
}
|
|
60338
60740
|
const defaultPermissionMode = config.copilot?.defaultPermissionMode;
|
|
60339
60741
|
if (defaultPermissionMode !== void 0) patch[DEFAULT_PERMISSION_MODE_KEY] = defaultPermissionMode;
|
|
60742
|
+
const sandbox = config.copilot?.sandbox;
|
|
60743
|
+
if (sandbox !== void 0) {
|
|
60744
|
+
Object.assign(patch, buildSandboxPatch({
|
|
60745
|
+
sandbox,
|
|
60746
|
+
existingSettings: parseSharedConfig({
|
|
60747
|
+
format: "jsonc",
|
|
60748
|
+
fileContent: existingContent,
|
|
60749
|
+
filePath
|
|
60750
|
+
})
|
|
60751
|
+
}));
|
|
60752
|
+
warnOnTrustAffectingEntries({
|
|
60753
|
+
toolLabel: COPILOT_TOOL_LABEL,
|
|
60754
|
+
entries: collectTrustAffectingSandboxPaths({
|
|
60755
|
+
sandbox,
|
|
60756
|
+
paths: COPILOT_TRUST_AFFECTING_SANDBOX_PATHS
|
|
60757
|
+
}),
|
|
60758
|
+
relativeFilePath: (0, node_path.join)(paths.relativeDirPath, paths.relativeFilePath),
|
|
60759
|
+
logger
|
|
60760
|
+
});
|
|
60761
|
+
}
|
|
60340
60762
|
return new CopilotPermissions({
|
|
60341
60763
|
outputRoot,
|
|
60342
60764
|
relativeDirPath: paths.relativeDirPath,
|
|
@@ -60373,9 +60795,14 @@ var CopilotPermissions = class CopilotPermissions extends ToolPermissions {
|
|
|
60373
60795
|
if (Object.keys(rules).length > 0) permission[category] = rules;
|
|
60374
60796
|
}
|
|
60375
60797
|
const defaultPermissionMode = settings[DEFAULT_PERMISSION_MODE_KEY];
|
|
60798
|
+
const sandbox = readSandboxFromSettings(settings);
|
|
60799
|
+
const copilot = {
|
|
60800
|
+
...isCopilotDefaultPermissionMode(defaultPermissionMode) && { defaultPermissionMode },
|
|
60801
|
+
...sandbox !== void 0 && { sandbox }
|
|
60802
|
+
};
|
|
60376
60803
|
return this.toRulesyncPermissionsDefault({ fileContent: JSON.stringify({
|
|
60377
60804
|
permission,
|
|
60378
|
-
...
|
|
60805
|
+
...Object.keys(copilot).length > 0 && { copilot }
|
|
60379
60806
|
}, null, 2) });
|
|
60380
60807
|
}
|
|
60381
60808
|
validate() {
|
|
@@ -99651,9 +100078,10 @@ const toolRuleFactories = /* @__PURE__ */ new Map([
|
|
|
99651
100078
|
collisionPolicy: "fold",
|
|
99652
100079
|
projectInstructionBudget: {
|
|
99653
100080
|
bytes: CODEXCLI_PROJECT_DOC_MAX_BYTES,
|
|
99654
|
-
remedy: "Codex CLI stops reading project `AGENTS.md` files once their combined size on the root-to-working-directory chain reaches `project_doc_max_bytes` (32 KiB by default) and silently drops the rest. Move directory-scoped rules into nested `AGENTS.md` files with `agentsmd.subprojectPath` (or `deriveSubprojectPathFromGlobs: true` in rulesync.jsonc), trim the rules, or raise `project_doc_max_bytes` in `.codex/config.toml
|
|
100081
|
+
remedy: "Codex CLI stops reading project `AGENTS.md` files once their combined size on the root-to-working-directory chain reaches `project_doc_max_bytes` (32 KiB by default) and silently drops the rest. Move directory-scoped rules into nested `AGENTS.md` files with `agentsmd.subprojectPath` (or `deriveSubprojectPathFromGlobs: true` in rulesync.jsonc), trim the rules, or raise `project_doc_max_bytes` in `.codex/config.toml` (Codex reads that file only if the project is trusted).",
|
|
99655
100082
|
readConfiguredBytes: readCodexcliProjectDocMaxBytes,
|
|
99656
|
-
configuredSource: "`project_doc_max_bytes` in `.codex/config.toml`"
|
|
100083
|
+
configuredSource: "`project_doc_max_bytes` in `.codex/config.toml` (honored only if the project is trusted)",
|
|
100084
|
+
defaultLimitHint: "Rulesync does not read `~/.codex/config.toml`; ignore this warning if you already raised `project_doc_max_bytes` there."
|
|
99657
100085
|
}
|
|
99658
100086
|
}
|
|
99659
100087
|
}],
|
|
@@ -100232,6 +100660,7 @@ var RulesProcessor = class extends FeatureProcessor {
|
|
|
100232
100660
|
const configuredBytes = await budget.readConfiguredBytes?.(this.outputRoot);
|
|
100233
100661
|
const limitBytes = configuredBytes ?? budget.bytes;
|
|
100234
100662
|
const limitLabel = budget.readConfiguredBytes === void 0 ? `${limitBytes}-byte limit` : configuredBytes === void 0 ? `${limitBytes}-byte default limit` : `${limitBytes}-byte limit set by ${budget.configuredSource ?? "the tool config"}`;
|
|
100663
|
+
const remedy = configuredBytes === void 0 && budget.defaultLimitHint !== void 0 ? `${budget.remedy} ${budget.defaultLimitHint}` : budget.remedy;
|
|
100235
100664
|
const files = toolRules.map((rule) => ({
|
|
100236
100665
|
dir: normalizeRelativeDir(rule.getRelativeDirPath()),
|
|
100237
100666
|
path: toPosixPath((0, node_path.join)(rule.getRelativeDirPath(), rule.getRelativeFilePath())),
|
|
@@ -100242,7 +100671,7 @@ var RulesProcessor = class extends FeatureProcessor {
|
|
|
100242
100671
|
const rootFiles = files.filter(({ dir }) => dir === ".");
|
|
100243
100672
|
const rootBytes = rootFiles.reduce((sum, { bytes }) => sum + bytes, 0);
|
|
100244
100673
|
if (rootBytes > limitBytes) {
|
|
100245
|
-
this.logger.warn(`The generated ${this.toolTarget} root instruction file${location} (${rootFiles.map(({ path }) => path).join(", ")}) is ${rootBytes} bytes, over the ${limitLabel}. ${
|
|
100674
|
+
this.logger.warn(`The generated ${this.toolTarget} root instruction file${location} (${rootFiles.map(({ path }) => path).join(", ")}) is ${rootBytes} bytes, over the ${limitLabel}. ${remedy}`);
|
|
100246
100675
|
return;
|
|
100247
100676
|
}
|
|
100248
100677
|
const reportedDirs = [];
|
|
@@ -100253,7 +100682,7 @@ var RulesProcessor = class extends FeatureProcessor {
|
|
|
100253
100682
|
const chainBytes = chain.reduce((sum, { bytes }) => sum + bytes, 0);
|
|
100254
100683
|
if (chainBytes > limitBytes) {
|
|
100255
100684
|
reportedDirs.push(file.dir);
|
|
100256
|
-
this.logger.warn(`The generated ${this.toolTarget} instruction files${location} loaded while working in '${file.dir}' (${chain.map(({ path }) => path).join(", ")}) total ${chainBytes} bytes, over the ${limitLabel} (as is every directory below it). ${
|
|
100685
|
+
this.logger.warn(`The generated ${this.toolTarget} instruction files${location} loaded while working in '${file.dir}' (${chain.map(({ path }) => path).join(", ")}) total ${chainBytes} bytes, over the ${limitLabel} (as is every directory below it). ${remedy}`);
|
|
100257
100686
|
}
|
|
100258
100687
|
}
|
|
100259
100688
|
}
|
package/dist/index.cjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
-
const require_import = require("./import-
|
|
2
|
+
const require_import = require("./import-DYYG88Rs.cjs");
|
|
3
3
|
//#region src/index.ts
|
|
4
4
|
async function generate(options = {}) {
|
|
5
5
|
const { silent = true, verbose = false, ...rest } = options;
|
package/dist/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Vn as ALL_FEATURES, bt as ConsoleLogger, dn as ALL_TOOL_TARGETS, dt as ConfigResolver, i as inspectInputRoots, o as convertFromTool$1, r as generate$1, t as importFromTool$1 } from "./import-
|
|
1
|
+
import { Vn as ALL_FEATURES, bt as ConsoleLogger, dn as ALL_TOOL_TARGETS, dt as ConfigResolver, i as inspectInputRoots, o as convertFromTool$1, r as generate$1, t as importFromTool$1 } from "./import-BsEFYJsg.js";
|
|
2
2
|
//#region src/index.ts
|
|
3
3
|
async function generate(options = {}) {
|
|
4
4
|
const { silent = true, verbose = false, ...rest } = options;
|