rulesync 36.0.2 → 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 +83 -26
- package/dist/cli/index.js +83 -26
- package/dist/cli/index.js.map +1 -1
- package/dist/{import-Tya9qZRK.js → import-BsEFYJsg.js} +776 -129
- package/dist/{import-Tya9qZRK.js.map → import-BsEFYJsg.js.map} +1 -1
- package/dist/{import-DVcemXlR.cjs → import-DYYG88Rs.cjs} +792 -145
- package/dist/index.cjs +1 -1
- package/dist/index.js +1 -1
- package/package.json +17 -20
|
@@ -457,36 +457,36 @@ const isFeatureValueEnabled = (value) => {
|
|
|
457
457
|
const parseCommaSeparatedList = (value) => value.split(",").map((s) => s.trim()).filter(Boolean);
|
|
458
458
|
//#endregion
|
|
459
459
|
//#region src/constants/rulesync-paths.ts
|
|
460
|
-
const { join: join$
|
|
460
|
+
const { join: join$423 } = node_path.posix;
|
|
461
461
|
const RULESYNC_CONFIG_RELATIVE_FILE_PATH = "rulesync.jsonc";
|
|
462
462
|
const RULESYNC_LOCAL_CONFIG_RELATIVE_FILE_PATH = "rulesync.local.jsonc";
|
|
463
463
|
const RULESYNC_USER_CONFIG_DIR_NAME = "rulesync";
|
|
464
464
|
const RULESYNC_XDG_CONFIG_HOME_DEFAULT_DIR_NAME = ".config";
|
|
465
465
|
const RULESYNC_RELATIVE_DIR_PATH = ".rulesync";
|
|
466
466
|
const RULES_FEATURE_SUBDIR = "rules";
|
|
467
|
-
const CURATED_RULES_FEATURE_SUBDIR = join$
|
|
467
|
+
const CURATED_RULES_FEATURE_SUBDIR = join$423(RULES_FEATURE_SUBDIR, ".curated");
|
|
468
468
|
const COMMANDS_FEATURE_SUBDIR = "commands";
|
|
469
469
|
const SUBAGENTS_FEATURE_SUBDIR = "subagents";
|
|
470
470
|
const CHECKS_FEATURE_SUBDIR = "checks";
|
|
471
471
|
const SKILLS_FEATURE_SUBDIR = "skills";
|
|
472
|
-
const CURATED_SKILLS_FEATURE_SUBDIR = join$
|
|
473
|
-
const RULESYNC_RULES_RELATIVE_DIR_PATH = join$
|
|
474
|
-
const RULESYNC_CURATED_RULES_RELATIVE_DIR_PATH = join$
|
|
475
|
-
const RULESYNC_COMMANDS_RELATIVE_DIR_PATH = join$
|
|
476
|
-
const RULESYNC_SUBAGENTS_RELATIVE_DIR_PATH = join$
|
|
477
|
-
const RULESYNC_CHECKS_RELATIVE_DIR_PATH = join$
|
|
478
|
-
const RULESYNC_MCP_RELATIVE_FILE_PATH = join$
|
|
479
|
-
const RULESYNC_HOOKS_RELATIVE_FILE_PATH = join$
|
|
480
|
-
const RULESYNC_PERMISSIONS_RELATIVE_FILE_PATH = join$
|
|
481
|
-
join$
|
|
482
|
-
const RULESYNC_HOOKS_LEGACY_RELATIVE_FILE_PATH = join$
|
|
483
|
-
const RULESYNC_PERMISSIONS_LEGACY_RELATIVE_FILE_PATH = join$
|
|
472
|
+
const CURATED_SKILLS_FEATURE_SUBDIR = join$423(SKILLS_FEATURE_SUBDIR, ".curated");
|
|
473
|
+
const RULESYNC_RULES_RELATIVE_DIR_PATH = join$423(RULESYNC_RELATIVE_DIR_PATH, RULES_FEATURE_SUBDIR);
|
|
474
|
+
const RULESYNC_CURATED_RULES_RELATIVE_DIR_PATH = join$423(RULESYNC_RELATIVE_DIR_PATH, CURATED_RULES_FEATURE_SUBDIR);
|
|
475
|
+
const RULESYNC_COMMANDS_RELATIVE_DIR_PATH = join$423(RULESYNC_RELATIVE_DIR_PATH, COMMANDS_FEATURE_SUBDIR);
|
|
476
|
+
const RULESYNC_SUBAGENTS_RELATIVE_DIR_PATH = join$423(RULESYNC_RELATIVE_DIR_PATH, SUBAGENTS_FEATURE_SUBDIR);
|
|
477
|
+
const RULESYNC_CHECKS_RELATIVE_DIR_PATH = join$423(RULESYNC_RELATIVE_DIR_PATH, CHECKS_FEATURE_SUBDIR);
|
|
478
|
+
const RULESYNC_MCP_RELATIVE_FILE_PATH = join$423(RULESYNC_RELATIVE_DIR_PATH, "mcp.jsonc");
|
|
479
|
+
const RULESYNC_HOOKS_RELATIVE_FILE_PATH = join$423(RULESYNC_RELATIVE_DIR_PATH, "hooks.jsonc");
|
|
480
|
+
const RULESYNC_PERMISSIONS_RELATIVE_FILE_PATH = join$423(RULESYNC_RELATIVE_DIR_PATH, "permissions.jsonc");
|
|
481
|
+
join$423(RULESYNC_RELATIVE_DIR_PATH, "mcp.json");
|
|
482
|
+
const RULESYNC_HOOKS_LEGACY_RELATIVE_FILE_PATH = join$423(RULESYNC_RELATIVE_DIR_PATH, "hooks.json");
|
|
483
|
+
const RULESYNC_PERMISSIONS_LEGACY_RELATIVE_FILE_PATH = join$423(RULESYNC_RELATIVE_DIR_PATH, "permissions.json");
|
|
484
484
|
const RULESYNC_AIIGNORE_FILE_NAME = ".aiignore";
|
|
485
|
-
const RULESYNC_AIIGNORE_RELATIVE_FILE_PATH = join$
|
|
485
|
+
const RULESYNC_AIIGNORE_RELATIVE_FILE_PATH = join$423(RULESYNC_RELATIVE_DIR_PATH, ".aiignore");
|
|
486
486
|
const RULESYNC_IGNORE_RELATIVE_FILE_PATH = ".rulesyncignore";
|
|
487
487
|
const RULESYNC_OVERVIEW_FILE_NAME = "overview.md";
|
|
488
|
-
const RULESYNC_SKILLS_RELATIVE_DIR_PATH = join$
|
|
489
|
-
const RULESYNC_CURATED_SKILLS_RELATIVE_DIR_PATH = join$
|
|
488
|
+
const RULESYNC_SKILLS_RELATIVE_DIR_PATH = join$423(RULESYNC_RELATIVE_DIR_PATH, SKILLS_FEATURE_SUBDIR);
|
|
489
|
+
const RULESYNC_CURATED_SKILLS_RELATIVE_DIR_PATH = join$423(RULESYNC_RELATIVE_DIR_PATH, CURATED_SKILLS_FEATURE_SUBDIR);
|
|
490
490
|
const RULESYNC_SOURCES_LOCK_RELATIVE_FILE_PATH = "rulesync.lock";
|
|
491
491
|
const RULESYNC_NPM_SOURCES_LOCK_RELATIVE_FILE_PATH = "rulesync-npm.lock.json";
|
|
492
492
|
const RULESYNC_MCP_FILE_NAME = "mcp.jsonc";
|
|
@@ -4062,7 +4062,7 @@ function isPlainObject$1(value) {
|
|
|
4062
4062
|
/**
|
|
4063
4063
|
* Type guard to check if a value is an array of strings.
|
|
4064
4064
|
*/
|
|
4065
|
-
function isStringArray$
|
|
4065
|
+
function isStringArray$1(value) {
|
|
4066
4066
|
return Array.isArray(value) && value.every((item) => typeof item === "string");
|
|
4067
4067
|
}
|
|
4068
4068
|
//#endregion
|
|
@@ -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
|
|
@@ -12857,6 +12896,10 @@ const SHARED_CONFIG_OWNERSHIP = {
|
|
|
12857
12896
|
kind: "replace-owned-keys",
|
|
12858
12897
|
ownedKeys: ["disableAllHooks"]
|
|
12859
12898
|
},
|
|
12899
|
+
mcp: {
|
|
12900
|
+
kind: "replace-owned-keys",
|
|
12901
|
+
ownedKeys: ["disabledMcpServers"]
|
|
12902
|
+
},
|
|
12860
12903
|
permissions: {
|
|
12861
12904
|
kind: "replace-owned-keys",
|
|
12862
12905
|
ownedKeys: ["deniedUrls"]
|
|
@@ -12871,6 +12914,10 @@ const SHARED_CONFIG_OWNERSHIP = {
|
|
|
12871
12914
|
kind: "replace-owned-keys",
|
|
12872
12915
|
ownedKeys: ["disableAllHooks"]
|
|
12873
12916
|
},
|
|
12917
|
+
mcp: {
|
|
12918
|
+
kind: "replace-owned-keys",
|
|
12919
|
+
ownedKeys: ["disabledMcpServers"]
|
|
12920
|
+
},
|
|
12874
12921
|
permissions: {
|
|
12875
12922
|
kind: "replace-owned-keys",
|
|
12876
12923
|
ownedKeys: ["allowedUrls", "deniedUrls"]
|
|
@@ -12888,6 +12935,16 @@ const SHARED_CONFIG_OWNERSHIP = {
|
|
|
12888
12935
|
"chat.tools.edits.autoApprove",
|
|
12889
12936
|
"chat.tools.urls.autoApprove",
|
|
12890
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",
|
|
12891
12948
|
"zoo-code.allowedCommands",
|
|
12892
12949
|
"zoo-code.deniedCommands",
|
|
12893
12950
|
"roo-cline.allowedCommands",
|
|
@@ -22135,7 +22192,7 @@ function getOpencodeCommandEntries({ config, layout = OPENCODE_LAYOUT }) {
|
|
|
22135
22192
|
*/
|
|
22136
22193
|
function getOpencodeSkillPaths({ config, layout = OPENCODE_LAYOUT }) {
|
|
22137
22194
|
if (Array.isArray(config.skills)) {
|
|
22138
|
-
if (!layout.readsV2ConfigSpellings || !isStringArray$
|
|
22195
|
+
if (!layout.readsV2ConfigSpellings || !isStringArray$1(config.skills)) return [];
|
|
22139
22196
|
return config.skills.filter((value) => !/^https?:\/\//i.test(value));
|
|
22140
22197
|
}
|
|
22141
22198
|
const skills = asOpencodeEntries(config.skills);
|
|
@@ -26381,7 +26438,7 @@ const isBooleanValue = ({ value }) => typeof value === "boolean";
|
|
|
26381
26438
|
const isFiniteNumber = ({ value }) => Number.isFinite(value);
|
|
26382
26439
|
const isNonEmptyString = (value) => typeof value === "string" && value !== "";
|
|
26383
26440
|
const isEmittableString = ({ value }) => isNonEmptyString(value);
|
|
26384
|
-
const isEmittableArray = ({ value }) => isStringArray
|
|
26441
|
+
const isEmittableArray = ({ value }) => isStringArray(value);
|
|
26385
26442
|
const isEmittableRecord = ({ value }) => isSafeStringRecord(value);
|
|
26386
26443
|
const isImportableArray = ({ value }) => isSafeStringArray(value);
|
|
26387
26444
|
const isEmittableObject = ({ value }) => isPlainObject$1(value);
|
|
@@ -26823,7 +26880,7 @@ function isStringRecord(value) {
|
|
|
26823
26880
|
if (value === null || typeof value !== "object" || Array.isArray(value)) return false;
|
|
26824
26881
|
return Object.values(value).every((v) => typeof v === "string");
|
|
26825
26882
|
}
|
|
26826
|
-
function isStringArray
|
|
26883
|
+
function isStringArray(value) {
|
|
26827
26884
|
return Array.isArray(value) && value.every((v) => typeof v === "string");
|
|
26828
26885
|
}
|
|
26829
26886
|
/** Compare object values without letting key order decide the answer. */
|
|
@@ -26868,7 +26925,7 @@ function isSafeEnvEntry(entry) {
|
|
|
26868
26925
|
* skipped wholesale when that read fails.
|
|
26869
26926
|
*/
|
|
26870
26927
|
function isSafeStringArray(value) {
|
|
26871
|
-
return isStringArray
|
|
26928
|
+
return isStringArray(value) && value.every((entry) => !CONTROL_CHARS.some((char) => entry.includes(char)));
|
|
26872
26929
|
}
|
|
26873
26930
|
/**
|
|
26874
26931
|
* A raw string kept only if the canonical field it would land in accepts it,
|
|
@@ -26910,7 +26967,7 @@ function importTypePayloadFields({ h, hookType, warn }) {
|
|
|
26910
26967
|
return {
|
|
26911
26968
|
...url !== void 0 && { url },
|
|
26912
26969
|
...isStringRecord(headers) && { headers },
|
|
26913
|
-
...isStringArray
|
|
26970
|
+
...isStringArray(h.allowedEnvVars) && { allowedEnvVars: h.allowedEnvVars }
|
|
26914
26971
|
};
|
|
26915
26972
|
}
|
|
26916
26973
|
if (hookType === "mcp_tool") {
|
|
@@ -28050,8 +28107,34 @@ function collectOwnedRefs({ generatedHooks, shape }) {
|
|
|
28050
28107
|
}
|
|
28051
28108
|
return owned;
|
|
28052
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
|
+
*/
|
|
28053
28127
|
function mergeMatcherGroups({ existing, generated, event, seen, previouslyOwned, logger }) {
|
|
28054
|
-
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();
|
|
28055
28138
|
for (const group of existing) {
|
|
28056
28139
|
if (!isMatcherGroup(group)) {
|
|
28057
28140
|
warnSkip({
|
|
@@ -28062,26 +28145,57 @@ function mergeMatcherGroups({ existing, generated, event, seen, previouslyOwned,
|
|
|
28062
28145
|
continue;
|
|
28063
28146
|
}
|
|
28064
28147
|
const matcher = matcherKey(group.matcher);
|
|
28065
|
-
const
|
|
28066
|
-
|
|
28148
|
+
const hooks = keepExistingHandlers({
|
|
28149
|
+
handlers: group.hooks,
|
|
28067
28150
|
event,
|
|
28068
28151
|
matcher,
|
|
28152
|
+
attributes: groupAttributesKey(group),
|
|
28153
|
+
pending,
|
|
28069
28154
|
seen,
|
|
28070
28155
|
previouslyOwned,
|
|
28071
28156
|
logger
|
|
28072
|
-
})
|
|
28073
|
-
if (
|
|
28074
|
-
const
|
|
28075
|
-
if (target !== void 0 && isMatcherGroup(target)) target.hooks.push(...preserved);
|
|
28076
|
-
else merged.push({
|
|
28157
|
+
});
|
|
28158
|
+
if (hooks.length === 0) continue;
|
|
28159
|
+
const kept = {
|
|
28077
28160
|
...omitPrototypePollutionKeys(group),
|
|
28078
|
-
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
|
|
28079
28178
|
});
|
|
28179
|
+
else home.hooks.push(...remaining);
|
|
28080
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
|
+
});
|
|
28081
28186
|
return merged;
|
|
28082
28187
|
}
|
|
28188
|
+
/** The flat-list counterpart of {@link mergeMatcherGroups}, with the same positional rules. */
|
|
28083
28189
|
function mergeFlatHandlers({ existing, generated, event, seen, previouslyOwned, logger }) {
|
|
28084
|
-
const
|
|
28190
|
+
const pending = createPendingGenerated();
|
|
28191
|
+
addPending({
|
|
28192
|
+
pending,
|
|
28193
|
+
handlers: generated,
|
|
28194
|
+
event,
|
|
28195
|
+
matcher: void 0,
|
|
28196
|
+
attributes: ""
|
|
28197
|
+
});
|
|
28198
|
+
const flat = [];
|
|
28085
28199
|
for (const handler of existing) {
|
|
28086
28200
|
if (isMatcherGroup(handler)) {
|
|
28087
28201
|
warnSkip({
|
|
@@ -28091,17 +28205,93 @@ function mergeFlatHandlers({ existing, generated, event, seen, previouslyOwned,
|
|
|
28091
28205
|
});
|
|
28092
28206
|
continue;
|
|
28093
28207
|
}
|
|
28094
|
-
|
|
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({
|
|
28095
28286
|
handler,
|
|
28096
28287
|
event,
|
|
28097
|
-
matcher
|
|
28288
|
+
matcher,
|
|
28098
28289
|
seen,
|
|
28099
28290
|
previouslyOwned,
|
|
28100
28291
|
logger
|
|
28101
|
-
}))
|
|
28102
|
-
preserved.push(cloneHandler(handler));
|
|
28292
|
+
})) kept.push(cloneHandler(handler));
|
|
28103
28293
|
}
|
|
28104
|
-
return
|
|
28294
|
+
return kept;
|
|
28105
28295
|
}
|
|
28106
28296
|
function isMatcherGroup(value) {
|
|
28107
28297
|
return isPlainObject$1(value) && Array.isArray(value.hooks);
|
|
@@ -28192,6 +28382,36 @@ function stableStringify$1(value) {
|
|
|
28192
28382
|
function matcherKey(matcher) {
|
|
28193
28383
|
return matcher === void 0 ? "" : stableStringify$1(matcher);
|
|
28194
28384
|
}
|
|
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
|
+
*/
|
|
28411
|
+
function groupAttributesKey(group) {
|
|
28412
|
+
const { matcher: _matcher, hooks: _hooks, ...attributes } = group;
|
|
28413
|
+
return stableStringify$1(Object.fromEntries(Object.entries(attributes).filter(([key, value]) => value !== void 0 && GROUP_ATTRIBUTE_DEFAULTS[key] !== value)));
|
|
28414
|
+
}
|
|
28195
28415
|
function warnSkip({ logger, event, expected }) {
|
|
28196
28416
|
logger?.warn(`Skipping existing hook entry on ${event}: expected ${expected}`);
|
|
28197
28417
|
}
|
|
@@ -28687,7 +28907,7 @@ const MANAGED_EVENT_NAMES = new Set(Object.values(CANONICAL_TO_CLINE_EVENT_NAMES
|
|
|
28687
28907
|
function parseManifest(fileContent) {
|
|
28688
28908
|
try {
|
|
28689
28909
|
const parsed = JSON.parse(fileContent);
|
|
28690
|
-
if (!isRecord$1(parsed) || !isStringArray$
|
|
28910
|
+
if (!isRecord$1(parsed) || !isStringArray$1(parsed.events)) return null;
|
|
28691
28911
|
return {
|
|
28692
28912
|
generatedBy: "rulesync",
|
|
28693
28913
|
events: parsed.events.filter((event) => MANAGED_EVENT_NAMES.has(event))
|
|
@@ -30225,7 +30445,7 @@ function copilotCliHooksToCanonical(rawHooks, logger) {
|
|
|
30225
30445
|
* see the CLI config dir reference:
|
|
30226
30446
|
* https://docs.github.com/en/copilot/reference/copilot-cli-reference/cli-config-dir-reference
|
|
30227
30447
|
*/
|
|
30228
|
-
function copilotCliSettingsPaths({ global }) {
|
|
30448
|
+
function copilotCliSettingsPaths$1({ global }) {
|
|
30229
30449
|
return global ? {
|
|
30230
30450
|
relativeDirPath: COPILOT_DIR,
|
|
30231
30451
|
relativeFilePath: COPILOTCLI_SETTINGS_FILE_NAME
|
|
@@ -30240,7 +30460,7 @@ function copilotCliSettingsPaths({ global }) {
|
|
|
30240
30460
|
* subject, and the permissions feature reports a broken settings file itself.
|
|
30241
30461
|
*/
|
|
30242
30462
|
async function readDisableAllHooks({ outputRoot, global }) {
|
|
30243
|
-
const paths = copilotCliSettingsPaths({ global });
|
|
30463
|
+
const paths = copilotCliSettingsPaths$1({ global });
|
|
30244
30464
|
const fileContent = await readFileContentOrNull((0, node_path.join)(outputRoot, paths.relativeDirPath, paths.relativeFilePath));
|
|
30245
30465
|
if (fileContent === null) return;
|
|
30246
30466
|
try {
|
|
@@ -30270,7 +30490,7 @@ var CopilotcliHooksSettings = class CopilotcliHooksSettings extends ToolFile {
|
|
|
30270
30490
|
return false;
|
|
30271
30491
|
}
|
|
30272
30492
|
static async fromDisableAllHooks({ outputRoot = process.cwd(), global = false, disableAllHooks }) {
|
|
30273
|
-
const paths = copilotCliSettingsPaths({ global });
|
|
30493
|
+
const paths = copilotCliSettingsPaths$1({ global });
|
|
30274
30494
|
const filePath = (0, node_path.join)(outputRoot, paths.relativeDirPath, paths.relativeFilePath);
|
|
30275
30495
|
const existingContent = await readFileContentOrNull(filePath) ?? "{}";
|
|
30276
30496
|
return new CopilotcliHooksSettings({
|
|
@@ -30300,7 +30520,7 @@ var CopilotcliHooks = class CopilotcliHooks extends ToolHooks {
|
|
|
30300
30520
|
this.disableAllHooks = disableAllHooks;
|
|
30301
30521
|
}
|
|
30302
30522
|
static getExtraSharedWritePaths({ global = false } = {}) {
|
|
30303
|
-
return [copilotCliSettingsPaths({ global })];
|
|
30523
|
+
return [copilotCliSettingsPaths$1({ global })];
|
|
30304
30524
|
}
|
|
30305
30525
|
static async getAuxiliaryFiles({ toolHooks } = {}) {
|
|
30306
30526
|
return toolHooks instanceof CopilotcliHooks && toolHooks.settingsFile ? [toolHooks.settingsFile] : [];
|
|
@@ -34744,12 +34964,21 @@ var QwencodeHooks = class QwencodeHooks extends ToolHooks {
|
|
|
34744
34964
|
validate
|
|
34745
34965
|
});
|
|
34746
34966
|
}
|
|
34747
|
-
static async fromRulesyncHooks({ outputRoot = process.cwd(), rulesyncHooks, validate = true, global = false, logger }) {
|
|
34967
|
+
static async fromRulesyncHooks({ outputRoot = process.cwd(), rulesyncHooks, validate = true, global = false, preserveUnowned = false, logger }) {
|
|
34748
34968
|
const paths = QwencodeHooks.getSettablePaths({ global });
|
|
34749
34969
|
const filePath = (0, node_path.join)(outputRoot, paths.relativeDirPath, paths.relativeFilePath);
|
|
34750
34970
|
const existingContent = await readFileContentOrNull(filePath) ?? JSON.stringify({}, null, 2);
|
|
34751
34971
|
const config = rulesyncHooks.getJson();
|
|
34752
|
-
const
|
|
34972
|
+
const previouslyOwned = preserveUnowned ? parseHooksOwnershipLock(await readFileContentOrNull((0, node_path.join)(outputRoot, paths.relativeDirPath, HOOKS_OWNERSHIP_LOCK_FILE_NAME))) : void 0;
|
|
34973
|
+
const merged = mergeGeneratedHookLists({
|
|
34974
|
+
existingContent,
|
|
34975
|
+
generatedHooks: canonicalToQwencodeHooks(config, logger),
|
|
34976
|
+
shape: "matcher-groups",
|
|
34977
|
+
preserveUnowned,
|
|
34978
|
+
previouslyOwned,
|
|
34979
|
+
logger
|
|
34980
|
+
});
|
|
34981
|
+
const patch = { hooks: merged.hooks };
|
|
34753
34982
|
const disableAllHooks = config.qwencode?.disableAllHooks;
|
|
34754
34983
|
if (typeof disableAllHooks === "boolean") patch.disableAllHooks = disableAllHooks;
|
|
34755
34984
|
const fileContent = applySharedConfigPatch({
|
|
@@ -34764,9 +34993,16 @@ var QwencodeHooks = class QwencodeHooks extends ToolHooks {
|
|
|
34764
34993
|
relativeDirPath: paths.relativeDirPath,
|
|
34765
34994
|
relativeFilePath: paths.relativeFilePath,
|
|
34766
34995
|
fileContent,
|
|
34996
|
+
ownedHookRefs: preserveUnowned ? merged.owned : void 0,
|
|
34767
34997
|
validate
|
|
34768
34998
|
});
|
|
34769
34999
|
}
|
|
35000
|
+
static supportsPreserveUnowned() {
|
|
35001
|
+
return true;
|
|
35002
|
+
}
|
|
35003
|
+
static async getAuxiliaryFiles({ toolHooks } = {}) {
|
|
35004
|
+
return toolHooks instanceof QwencodeHooks ? toolHooks.getOwnershipLockFiles() : [];
|
|
35005
|
+
}
|
|
34770
35006
|
toRulesyncHooks() {
|
|
34771
35007
|
let settings;
|
|
34772
35008
|
try {
|
|
@@ -41166,7 +41402,7 @@ function isCodexStdioServer(config) {
|
|
|
41166
41402
|
* @see https://learn.chatgpt.com/docs/extend/mcp
|
|
41167
41403
|
*/
|
|
41168
41404
|
function isValidRenamedArray(key, value) {
|
|
41169
|
-
return key === "env_vars" || key === "envVars" ? isEnvVarEntryArray(value) : isStringArray$
|
|
41405
|
+
return key === "env_vars" || key === "envVars" ? isEnvVarEntryArray(value) : isStringArray$1(value);
|
|
41170
41406
|
}
|
|
41171
41407
|
/**
|
|
41172
41408
|
* The keys of Codex's `McpServerOAuthConfig`. Anything else in a server's
|
|
@@ -42385,18 +42621,6 @@ function renameEnabledToolsToTools({ server, serverName, logger }) {
|
|
|
42385
42621
|
};
|
|
42386
42622
|
}
|
|
42387
42623
|
/**
|
|
42388
|
-
* Copilot CLI's `tools` accepts more than the canonical `enabledTools` shape:
|
|
42389
|
-
* `--tools` documents `*`, a comma-separated list, or `""`, so a hand-written or
|
|
42390
|
-
* CLI-written entry can be a bare string. Canonical `enabledTools` is
|
|
42391
|
-
* `string[]`, and `RulesyncMcp` throws on a schema failure — carrying a string
|
|
42392
|
-
* through would reject the WHOLE imported MCP file rather than one key. Anything
|
|
42393
|
-
* that is not a string array is therefore left under `tools` untouched, matching
|
|
42394
|
-
* `codexcli-mcp.ts`'s `isValidRenamedArray` guard on the same class of rename.
|
|
42395
|
-
*/
|
|
42396
|
-
function isStringArray(value) {
|
|
42397
|
-
return Array.isArray(value) && value.every((entry) => typeof entry === "string");
|
|
42398
|
-
}
|
|
42399
|
-
/**
|
|
42400
42624
|
* Resolves and sets the transport type for each MCP server config.
|
|
42401
42625
|
* GitHub Copilot CLI requires the "type" field for each server. A server it
|
|
42402
42626
|
* cannot express — one that names no transport, or names one it carries no way
|
|
@@ -42438,7 +42662,7 @@ function addTypeField(mcpServers, logger) {
|
|
|
42438
42662
|
});
|
|
42439
42663
|
continue;
|
|
42440
42664
|
}
|
|
42441
|
-
const { httpUrl: _httpUrl, ...rest } = parsed;
|
|
42665
|
+
const { httpUrl: _httpUrl, disabled: _disabled, ...rest } = parsed;
|
|
42442
42666
|
result[name] = {
|
|
42443
42667
|
...renameEnabledToolsToTools({
|
|
42444
42668
|
server: rest,
|
|
@@ -42450,7 +42674,7 @@ function addTypeField(mcpServers, logger) {
|
|
|
42450
42674
|
};
|
|
42451
42675
|
continue;
|
|
42452
42676
|
}
|
|
42453
|
-
const { httpUrl: _localHttpUrl, ...local } = parsed;
|
|
42677
|
+
const { httpUrl: _localHttpUrl, disabled: _localDisabled, ...local } = parsed;
|
|
42454
42678
|
const [head, ...tail] = resolveLocalMcpCommand(parsed);
|
|
42455
42679
|
if (head === void 0) {
|
|
42456
42680
|
warnAndSkipMcpServer({
|
|
@@ -42481,7 +42705,7 @@ function removeTypeField(config) {
|
|
|
42481
42705
|
const result = {};
|
|
42482
42706
|
for (const [name, server] of Object.entries(config.mcpServers ?? {})) {
|
|
42483
42707
|
const { tools, ...withoutTools } = server;
|
|
42484
|
-
const restored = isStringArray(tools) ? {
|
|
42708
|
+
const restored = isStringArray$1(tools) ? {
|
|
42485
42709
|
...withoutTools,
|
|
42486
42710
|
enabledTools: tools
|
|
42487
42711
|
} : {
|
|
@@ -42497,6 +42721,99 @@ function removeTypeField(config) {
|
|
|
42497
42721
|
}
|
|
42498
42722
|
return result;
|
|
42499
42723
|
}
|
|
42724
|
+
/**
|
|
42725
|
+
* The Copilot CLI settings file carrying `disabledMcpServers`:
|
|
42726
|
+
* `.github/copilot/settings.json` (project) / `~/.copilot/settings.json`
|
|
42727
|
+
* (global). Both scopes document the key ("MCP server names to disable. Listed
|
|
42728
|
+
* servers are configured but not started"); at repository scope it is
|
|
42729
|
+
* union-merged with the user value, so a repository can add names but never
|
|
42730
|
+
* remove them.
|
|
42731
|
+
*
|
|
42732
|
+
* @see https://docs.github.com/en/copilot/reference/copilot-cli-reference/cli-config-dir-reference
|
|
42733
|
+
*/
|
|
42734
|
+
function copilotCliSettingsPaths({ global }) {
|
|
42735
|
+
return global ? {
|
|
42736
|
+
relativeDirPath: COPILOT_DIR,
|
|
42737
|
+
relativeFilePath: COPILOTCLI_SETTINGS_FILE_NAME
|
|
42738
|
+
} : {
|
|
42739
|
+
relativeDirPath: COPILOTCLI_PROJECT_SETTINGS_DIR_PATH,
|
|
42740
|
+
relativeFilePath: COPILOTCLI_SETTINGS_FILE_NAME
|
|
42741
|
+
};
|
|
42742
|
+
}
|
|
42743
|
+
async function readCopilotCliSettings({ outputRoot, global }) {
|
|
42744
|
+
const paths = copilotCliSettingsPaths({ global });
|
|
42745
|
+
const existingContent = await readFileContentOrNull((0, node_path.join)(outputRoot, paths.relativeDirPath, paths.relativeFilePath)) ?? "";
|
|
42746
|
+
try {
|
|
42747
|
+
return {
|
|
42748
|
+
paths,
|
|
42749
|
+
existingContent,
|
|
42750
|
+
document: parseSharedConfig({
|
|
42751
|
+
format: "json",
|
|
42752
|
+
fileContent: existingContent,
|
|
42753
|
+
filePath: (0, node_path.join)(paths.relativeDirPath, paths.relativeFilePath),
|
|
42754
|
+
invalidRootPolicy: "error"
|
|
42755
|
+
})
|
|
42756
|
+
};
|
|
42757
|
+
} catch (error) {
|
|
42758
|
+
return {
|
|
42759
|
+
paths,
|
|
42760
|
+
existingContent,
|
|
42761
|
+
document: null,
|
|
42762
|
+
error
|
|
42763
|
+
};
|
|
42764
|
+
}
|
|
42765
|
+
}
|
|
42766
|
+
/**
|
|
42767
|
+
* The names of the servers rulesync actually writes for this target: one
|
|
42768
|
+
* `addTypeField` skips (no transport, WebSocket, missing url or command) is
|
|
42769
|
+
* not managed, so its name neither gains a toggle nor strips a same-named
|
|
42770
|
+
* user-authored entry from the settings file.
|
|
42771
|
+
*/
|
|
42772
|
+
function managedServerNames(mcpServers) {
|
|
42773
|
+
return Object.keys(addTypeField(mcpServers) ?? {});
|
|
42774
|
+
}
|
|
42775
|
+
/**
|
|
42776
|
+
* Overlays `disabled: true` on the servers the scope's settings file lists in
|
|
42777
|
+
* `disabledMcpServers`, so import round-trips the toggle into the canonical
|
|
42778
|
+
* config. A settings file that exists but cannot be parsed throws
|
|
42779
|
+
* (fail-closed): importing past it would re-enable every disabled server.
|
|
42780
|
+
*/
|
|
42781
|
+
async function overlayDisabledServers({ mcpServers, outputRoot, global }) {
|
|
42782
|
+
const settings = await readCopilotCliSettings({
|
|
42783
|
+
outputRoot,
|
|
42784
|
+
global
|
|
42785
|
+
});
|
|
42786
|
+
if (settings.document === null) throw new Error(`Cannot import Copilot CLI MCP servers: ${formatError(settings.error)}. Fix the file so disabledMcpServers can be read.`, { cause: settings.error });
|
|
42787
|
+
const disabled = settings.document.disabledMcpServers;
|
|
42788
|
+
if (!isStringArray$1(disabled) || disabled.length === 0) return mcpServers;
|
|
42789
|
+
const servers = { ...mcpServers };
|
|
42790
|
+
for (const name of disabled) {
|
|
42791
|
+
if (!Object.hasOwn(servers, name)) continue;
|
|
42792
|
+
const server = servers[name];
|
|
42793
|
+
if (isPlainObject$1(server)) servers[name] = {
|
|
42794
|
+
...server,
|
|
42795
|
+
disabled: true
|
|
42796
|
+
};
|
|
42797
|
+
}
|
|
42798
|
+
return servers;
|
|
42799
|
+
}
|
|
42800
|
+
/**
|
|
42801
|
+
* Auxiliary writer for `disabledMcpServers` in the Copilot CLI settings file.
|
|
42802
|
+
* Only that key is owned (through the shared-config gateway), so `deniedUrls`,
|
|
42803
|
+
* `disableAllHooks`, `model` and every other setting are preserved. It is a
|
|
42804
|
+
* shared, user-owned file and is never deleted.
|
|
42805
|
+
*/
|
|
42806
|
+
var CopilotcliMcpSettings = class extends ToolFile {
|
|
42807
|
+
validate() {
|
|
42808
|
+
return {
|
|
42809
|
+
success: true,
|
|
42810
|
+
error: null
|
|
42811
|
+
};
|
|
42812
|
+
}
|
|
42813
|
+
isDeletable() {
|
|
42814
|
+
return false;
|
|
42815
|
+
}
|
|
42816
|
+
};
|
|
42500
42817
|
var CopilotcliMcp = class CopilotcliMcp extends ToolMcp {
|
|
42501
42818
|
json;
|
|
42502
42819
|
constructor(params) {
|
|
@@ -42539,7 +42856,11 @@ var CopilotcliMcp = class CopilotcliMcp extends ToolMcp {
|
|
|
42539
42856
|
const json = JSON.parse(fileContent);
|
|
42540
42857
|
const newJson = {
|
|
42541
42858
|
...json,
|
|
42542
|
-
mcpServers:
|
|
42859
|
+
mcpServers: await overlayDisabledServers({
|
|
42860
|
+
mcpServers: json.mcpServers ?? {},
|
|
42861
|
+
outputRoot,
|
|
42862
|
+
global
|
|
42863
|
+
})
|
|
42543
42864
|
};
|
|
42544
42865
|
return new CopilotcliMcp({
|
|
42545
42866
|
outputRoot,
|
|
@@ -42554,7 +42875,17 @@ var CopilotcliMcp = class CopilotcliMcp extends ToolMcp {
|
|
|
42554
42875
|
const paths = this.getSettablePaths({ global });
|
|
42555
42876
|
const fileContent = await readFileContentOrNull((0, node_path.join)(outputRoot, paths.relativeDirPath, paths.relativeFilePath)) ?? JSON.stringify({ mcpServers: {} }, null, 2);
|
|
42556
42877
|
const json = JSON.parse(fileContent);
|
|
42557
|
-
const
|
|
42878
|
+
const settings = await readCopilotCliSettings({
|
|
42879
|
+
outputRoot,
|
|
42880
|
+
global
|
|
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;
|
|
42884
|
+
const copilotCliMcpServers = addTypeField(Object.fromEntries(Object.entries(rulesyncMcp.getMcpServers()).filter(([name, server]) => {
|
|
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.`);
|
|
42887
|
+
return false;
|
|
42888
|
+
})), logger);
|
|
42558
42889
|
const mcpJson = {
|
|
42559
42890
|
...json,
|
|
42560
42891
|
mcpServers: copilotCliMcpServers
|
|
@@ -42568,6 +42899,66 @@ var CopilotcliMcp = class CopilotcliMcp extends ToolMcp {
|
|
|
42568
42899
|
global
|
|
42569
42900
|
});
|
|
42570
42901
|
}
|
|
42902
|
+
/**
|
|
42903
|
+
* `disabledMcpServers` lives in the Copilot CLI settings file the hooks and
|
|
42904
|
+
* permissions features also write. Declared here so the write-order
|
|
42905
|
+
* derivation sees this feature as one of that file's writers — it is not a
|
|
42906
|
+
* settable path, since the servers themselves live in the MCP file.
|
|
42907
|
+
*/
|
|
42908
|
+
static getExtraSharedWritePaths({ global = false } = {}) {
|
|
42909
|
+
return [copilotCliSettingsPaths({ global })];
|
|
42910
|
+
}
|
|
42911
|
+
/**
|
|
42912
|
+
* Routes the canonical `disabled: true` to `disabledMcpServers`, the key
|
|
42913
|
+
* Copilot CLI actually consults: an MCP file entry has no `disabled` field.
|
|
42914
|
+
* rulesync owns the toggle for the servers it writes; names it does not
|
|
42915
|
+
* manage keep their existing state. The file is not created or touched when
|
|
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).
|
|
42918
|
+
*/
|
|
42919
|
+
static async getAuxiliaryFiles({ outputRoot = process.cwd(), global = false, rulesyncMcp, logger }) {
|
|
42920
|
+
const servers = rulesyncMcp.forTarget({
|
|
42921
|
+
toolTarget: "copilotcli",
|
|
42922
|
+
global
|
|
42923
|
+
}).getMcpServers();
|
|
42924
|
+
const managedNames = managedServerNames(servers);
|
|
42925
|
+
const disabledNames = managedNames.filter((name) => servers[name]?.disabled === true);
|
|
42926
|
+
const settings = await readCopilotCliSettings({
|
|
42927
|
+
outputRoot,
|
|
42928
|
+
global
|
|
42929
|
+
});
|
|
42930
|
+
if (settings.document === null) {
|
|
42931
|
+
logger?.warn(`Skipping the Copilot CLI disabledMcpServers update: ${formatError(settings.error)}`);
|
|
42932
|
+
return [];
|
|
42933
|
+
}
|
|
42934
|
+
const existing = settings.document.disabledMcpServers;
|
|
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 ?? [];
|
|
42940
|
+
const managedNameSet = new Set(managedNames);
|
|
42941
|
+
const reEnabled = existingDisabled.filter((name) => managedNameSet.has(name) && !disabledNames.includes(name));
|
|
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.`);
|
|
42943
|
+
const mergedDisabled = [...existingDisabled.filter((name) => !managedNameSet.has(name)), ...disabledNames].toSorted();
|
|
42944
|
+
if (mergedDisabled.length === 0 && existing === void 0) return [];
|
|
42945
|
+
if (isStringArray$1(existing) && existing.length === mergedDisabled.length && existing.toSorted().every((name, index) => name === mergedDisabled[index])) return [];
|
|
42946
|
+
const { paths } = settings;
|
|
42947
|
+
const filePath = (0, node_path.join)(outputRoot, paths.relativeDirPath, paths.relativeFilePath);
|
|
42948
|
+
return [new CopilotcliMcpSettings({
|
|
42949
|
+
outputRoot,
|
|
42950
|
+
relativeDirPath: paths.relativeDirPath,
|
|
42951
|
+
relativeFilePath: paths.relativeFilePath,
|
|
42952
|
+
fileContent: applySharedConfigPatch({
|
|
42953
|
+
fileKey: sharedConfigFileKey(paths),
|
|
42954
|
+
feature: "mcp",
|
|
42955
|
+
existingContent: settings.existingContent,
|
|
42956
|
+
patch: { disabledMcpServers: mergedDisabled.length > 0 ? mergedDisabled : void 0 },
|
|
42957
|
+
filePath
|
|
42958
|
+
}),
|
|
42959
|
+
global
|
|
42960
|
+
})];
|
|
42961
|
+
}
|
|
42571
42962
|
toRulesyncMcp() {
|
|
42572
42963
|
const mcpServers = removeTypeField(this.json);
|
|
42573
42964
|
return this.toRulesyncMcpDefault({ fileContent: JSON.stringify({ mcpServers }, null, 2) });
|
|
@@ -42999,10 +43390,10 @@ function convertFromCrushFormat(crushServers) {
|
|
|
42999
43390
|
if (value === "http" || value === "sse") converted.type = value;
|
|
43000
43391
|
break;
|
|
43001
43392
|
case "disabled_tools":
|
|
43002
|
-
if (isStringArray$
|
|
43393
|
+
if (isStringArray$1(value)) converted.disabledTools = value;
|
|
43003
43394
|
break;
|
|
43004
43395
|
case "enabled_tools":
|
|
43005
|
-
if (isStringArray$
|
|
43396
|
+
if (isStringArray$1(value)) converted.enabledTools = value;
|
|
43006
43397
|
break;
|
|
43007
43398
|
case "oauth_token": break;
|
|
43008
43399
|
default: {
|
|
@@ -43989,7 +44380,7 @@ function convertRowsFromDsh(rows, logger) {
|
|
|
43989
44380
|
} else {
|
|
43990
44381
|
server.type = "stdio";
|
|
43991
44382
|
if (typeof config.command === "string") server.command = config.command;
|
|
43992
|
-
if (isStringArray$
|
|
44383
|
+
if (isStringArray$1(config.args)) server.args = config.args;
|
|
43993
44384
|
const env = copyStringRecord(config.env);
|
|
43994
44385
|
if (env !== void 0) server.env = env;
|
|
43995
44386
|
if (typeof config.cwd === "string") server.cwd = config.cwd;
|
|
@@ -44420,11 +44811,11 @@ function applyGooseStdioFields(ext, config) {
|
|
|
44420
44811
|
if (Array.isArray(command)) {
|
|
44421
44812
|
if (typeof command[0] === "string") ext.cmd = command[0];
|
|
44422
44813
|
const rest = command.slice(1).filter((c) => typeof c === "string");
|
|
44423
|
-
const args = isStringArray$
|
|
44814
|
+
const args = isStringArray$1(config.args) ? config.args : [];
|
|
44424
44815
|
if (rest.length > 0 || args.length > 0) ext.args = [...rest, ...args];
|
|
44425
44816
|
} else if (typeof command === "string") {
|
|
44426
44817
|
ext.cmd = command;
|
|
44427
|
-
if (isStringArray$
|
|
44818
|
+
if (isStringArray$1(config.args)) ext.args = config.args;
|
|
44428
44819
|
}
|
|
44429
44820
|
if (isPlainObject$1(config.env)) ext.envs = omitPrototypePollutionKeys(config.env);
|
|
44430
44821
|
if (typeof config.cwd === "string") ext.cwd = config.cwd;
|
|
@@ -44527,7 +44918,7 @@ function convertFromGooseFormat(extensions) {
|
|
|
44527
44918
|
else if (type === "streamable_http") server.type = "http";
|
|
44528
44919
|
else server.type = "stdio";
|
|
44529
44920
|
if (typeof ext.cmd === "string") server.command = ext.cmd;
|
|
44530
|
-
if (isStringArray$
|
|
44921
|
+
if (isStringArray$1(ext.args)) server.args = ext.args;
|
|
44531
44922
|
if (isPlainObject$1(ext.envs)) server.env = omitPrototypePollutionKeys(ext.envs);
|
|
44532
44923
|
if (typeof ext.cwd === "string") server.cwd = ext.cwd;
|
|
44533
44924
|
if (typeof ext.uri === "string") server.url = ext.uri;
|
|
@@ -44549,11 +44940,11 @@ function buildGoosePluginStdioServer(config) {
|
|
|
44549
44940
|
if (Array.isArray(command)) {
|
|
44550
44941
|
if (typeof command[0] === "string") server.command = command[0];
|
|
44551
44942
|
const rest = command.slice(1).filter((c) => typeof c === "string");
|
|
44552
|
-
const args = isStringArray$
|
|
44943
|
+
const args = isStringArray$1(config.args) ? config.args : [];
|
|
44553
44944
|
if (rest.length > 0 || args.length > 0) server.args = [...rest, ...args];
|
|
44554
44945
|
} else if (typeof command === "string") {
|
|
44555
44946
|
server.command = command;
|
|
44556
|
-
if (isStringArray$
|
|
44947
|
+
if (isStringArray$1(config.args)) server.args = config.args;
|
|
44557
44948
|
}
|
|
44558
44949
|
if (isPlainObject$1(config.env)) server.env = omitPrototypePollutionKeys(config.env);
|
|
44559
44950
|
if (typeof config.cwd === "string") server.cwd = config.cwd;
|
|
@@ -44961,7 +45352,7 @@ function copyHermesOauth(source) {
|
|
|
44961
45352
|
if (!isRecord$1(source)) return;
|
|
44962
45353
|
const oauth = {};
|
|
44963
45354
|
for (const key of HERMES_OAUTH_STRING_KEYS) if (typeof source[key] === "string") oauth[key] = source[key];
|
|
44964
|
-
if (typeof oauth.scope !== "string" && isStringArray$
|
|
45355
|
+
if (typeof oauth.scope !== "string" && isStringArray$1(source.scopes)) oauth.scope = source.scopes.join(" ");
|
|
44965
45356
|
for (const key of ["redirect_port", "timeout"]) if (typeof source[key] === "number") oauth[key] = source[key];
|
|
44966
45357
|
if (typeof source.cimd === "boolean") oauth.cimd = source.cimd;
|
|
44967
45358
|
return Object.keys(oauth).length > 0 ? oauth : void 0;
|
|
@@ -44988,7 +45379,7 @@ function copyHermesAdvancedFields(source, target) {
|
|
|
44988
45379
|
target.auth = source.auth;
|
|
44989
45380
|
copied = true;
|
|
44990
45381
|
}
|
|
44991
|
-
if (typeof source.client_cert === "string" || isStringArray$
|
|
45382
|
+
if (typeof source.client_cert === "string" || isStringArray$1(source.client_cert)) {
|
|
44992
45383
|
target.client_cert = source.client_cert;
|
|
44993
45384
|
copied = true;
|
|
44994
45385
|
}
|
|
@@ -45060,8 +45451,8 @@ function copyHermesAdvancedFields(source, target) {
|
|
|
45060
45451
|
*/
|
|
45061
45452
|
function buildHermesToolsBlock(config) {
|
|
45062
45453
|
const tools = {};
|
|
45063
|
-
if (isStringArray$
|
|
45064
|
-
if (isStringArray$
|
|
45454
|
+
if (isStringArray$1(config.enabledTools)) tools.include = config.enabledTools;
|
|
45455
|
+
if (isStringArray$1(config.disabledTools)) tools.exclude = config.disabledTools;
|
|
45065
45456
|
if (typeof config.promptsEnabled === "boolean") tools.prompts = config.promptsEnabled;
|
|
45066
45457
|
if (typeof config.resourcesEnabled === "boolean") tools.resources = config.resourcesEnabled;
|
|
45067
45458
|
return tools;
|
|
@@ -45073,8 +45464,8 @@ function buildHermesToolsBlock(config) {
|
|
|
45073
45464
|
* `promptsEnabled`/`resourcesEnabled` top-level toggles.
|
|
45074
45465
|
*/
|
|
45075
45466
|
function applyHermesToolsBlock(hermesTools, server) {
|
|
45076
|
-
if (isStringArray$
|
|
45077
|
-
if (isStringArray$
|
|
45467
|
+
if (isStringArray$1(hermesTools.include)) server.enabledTools = hermesTools.include;
|
|
45468
|
+
if (isStringArray$1(hermesTools.exclude)) server.disabledTools = hermesTools.exclude;
|
|
45078
45469
|
if (typeof hermesTools.prompts === "boolean") server.promptsEnabled = hermesTools.prompts;
|
|
45079
45470
|
if (typeof hermesTools.resources === "boolean") server.resourcesEnabled = hermesTools.resources;
|
|
45080
45471
|
}
|
|
@@ -45100,11 +45491,11 @@ function convertServerToHermes(config) {
|
|
|
45100
45491
|
if (Array.isArray(command)) {
|
|
45101
45492
|
if (typeof command[0] === "string") out.command = command[0];
|
|
45102
45493
|
const rest = command.slice(1).filter((c) => typeof c === "string");
|
|
45103
|
-
const args = isStringArray$
|
|
45494
|
+
const args = isStringArray$1(config.args) ? config.args : [];
|
|
45104
45495
|
if (rest.length > 0 || args.length > 0) out.args = [...rest, ...args];
|
|
45105
45496
|
} else if (typeof command === "string") {
|
|
45106
45497
|
out.command = command;
|
|
45107
|
-
if (isStringArray$
|
|
45498
|
+
if (isStringArray$1(config.args)) out.args = config.args;
|
|
45108
45499
|
}
|
|
45109
45500
|
if (isPlainObject$1(config.env)) out.env = omitPrototypePollutionKeys(config.env);
|
|
45110
45501
|
} else if (url !== void 0) {
|
|
@@ -45154,7 +45545,7 @@ function convertFromHermesFormat(mcpServers) {
|
|
|
45154
45545
|
if (PROTOTYPE_POLLUTION_KEYS.has(name) || !isRecord$1(config)) continue;
|
|
45155
45546
|
const server = {};
|
|
45156
45547
|
if (typeof config.command === "string") server.command = config.command;
|
|
45157
|
-
if (isStringArray$
|
|
45548
|
+
if (isStringArray$1(config.args)) server.args = config.args;
|
|
45158
45549
|
if (isPlainObject$1(config.env)) server.env = omitPrototypePollutionKeys(config.env);
|
|
45159
45550
|
if (typeof config.url === "string") server.url = config.url;
|
|
45160
45551
|
if (isPlainObject$1(config.headers)) server.headers = omitPrototypePollutionKeys(config.headers);
|
|
@@ -46346,7 +46737,7 @@ function mergeToolLists(...lists) {
|
|
|
46346
46737
|
function toKiroMcpServers(servers) {
|
|
46347
46738
|
return Object.fromEntries(Object.entries(servers).map(([name, server]) => {
|
|
46348
46739
|
const { kiroAutoApprove, kiroAutoBlock, disabledTools, ...rest } = server;
|
|
46349
|
-
const autoApprove = mergeToolLists(isStringArray$
|
|
46740
|
+
const autoApprove = mergeToolLists(isStringArray$1(rest.autoApprove) ? rest.autoApprove : void 0, kiroAutoApprove);
|
|
46350
46741
|
const disabled = mergeToolLists(disabledTools, kiroAutoBlock);
|
|
46351
46742
|
return [name, {
|
|
46352
46743
|
...rest,
|
|
@@ -46370,7 +46761,7 @@ function fromKiroMcpServers(servers) {
|
|
|
46370
46761
|
return Object.fromEntries(Object.entries(servers).map(([name, server]) => {
|
|
46371
46762
|
if (server === null || typeof server !== "object" || Array.isArray(server)) return [name, server];
|
|
46372
46763
|
const { autoApprove, ...rest } = server;
|
|
46373
|
-
if (!isStringArray$
|
|
46764
|
+
if (!isStringArray$1(autoApprove)) return [name, server];
|
|
46374
46765
|
return [name, {
|
|
46375
46766
|
...rest,
|
|
46376
46767
|
kiroAutoApprove: autoApprove
|
|
@@ -47799,7 +48190,7 @@ function headersRecordToPoolList(headers) {
|
|
|
47799
48190
|
return Object.entries(headers).map(([name, value]) => `${name}: ${value}`);
|
|
47800
48191
|
}
|
|
47801
48192
|
function poolHeadersListToRecord(serverName, headers) {
|
|
47802
|
-
if (!isStringArray$
|
|
48193
|
+
if (!isStringArray$1(headers)) return void 0;
|
|
47803
48194
|
const result = {};
|
|
47804
48195
|
for (const entry of headers) {
|
|
47805
48196
|
const separator = entry.indexOf(":");
|
|
@@ -47825,7 +48216,7 @@ const GLOB_METACHARACTERS = /[*?[]/;
|
|
|
47825
48216
|
/** Patterns named in the glob warning before the rest collapse to `(+N more)`. */
|
|
47826
48217
|
const MAX_LISTED_GLOB_PATTERNS = 10;
|
|
47827
48218
|
function importPoolToolList({ serverName, poolKey, value, warnOnGlobs }) {
|
|
47828
|
-
if (!isStringArray$
|
|
48219
|
+
if (!isStringArray$1(value)) {
|
|
47829
48220
|
warnWithFallback(void 0, `Ignored malformed value for ${poolKey} in Pool MCP server ${quoteValueForWarning(serverName)}: expected a list of strings`);
|
|
47830
48221
|
return;
|
|
47831
48222
|
}
|
|
@@ -48035,7 +48426,7 @@ var PoolMcp = class PoolMcp extends ToolMcp {
|
|
|
48035
48426
|
const poolAllow = isRecord$1(rawServer) ? rawServer.poolAllow : void 0;
|
|
48036
48427
|
return [serverName, {
|
|
48037
48428
|
...serverConfig,
|
|
48038
|
-
...isStringArray$
|
|
48429
|
+
...isStringArray$1(poolAllow) && { poolAllow }
|
|
48039
48430
|
}];
|
|
48040
48431
|
})), logger);
|
|
48041
48432
|
return new PoolMcp({
|
|
@@ -48266,7 +48657,7 @@ function pickServerLists(source) {
|
|
|
48266
48657
|
if (!isPlainObject$1(source)) return {};
|
|
48267
48658
|
return Object.fromEntries(QWENCODE_MCP_SERVER_LIST_KEYS.flatMap((key) => {
|
|
48268
48659
|
const value = source[key];
|
|
48269
|
-
return isStringArray$
|
|
48660
|
+
return isStringArray$1(value) ? [[key, value]] : [];
|
|
48270
48661
|
}));
|
|
48271
48662
|
}
|
|
48272
48663
|
/**
|
|
@@ -48959,7 +49350,7 @@ async function resolveRovodevMcpImportPath({ outputRoot, global, config, logger
|
|
|
48959
49350
|
}
|
|
48960
49351
|
function disabledNamesOf(config) {
|
|
48961
49352
|
const mcpBlock = config && isRecord$1(config.mcp) ? config.mcp : {};
|
|
48962
|
-
return isStringArray$
|
|
49353
|
+
return isStringArray$1(mcpBlock.disabledMcpServers) ? mcpBlock.disabledMcpServers : [];
|
|
48963
49354
|
}
|
|
48964
49355
|
/**
|
|
48965
49356
|
* The value `mcp.mcpConfigPath` needs so Rovo Dev reads the `mcp.json` rulesync
|
|
@@ -49375,7 +49766,7 @@ var RovodevMcp = class RovodevMcp extends ToolMcp {
|
|
|
49375
49766
|
return [];
|
|
49376
49767
|
}
|
|
49377
49768
|
const existingMcp = isRecord$1(existingParsed.mcp) ? { ...existingParsed.mcp } : {};
|
|
49378
|
-
const existingDisabled = isStringArray$
|
|
49769
|
+
const existingDisabled = isStringArray$1(existingMcp.disabledMcpServers) ? existingMcp.disabledMcpServers : [];
|
|
49379
49770
|
const managedNameSet = new Set(managedNames);
|
|
49380
49771
|
const reEnabled = existingDisabled.filter((name) => managedNameSet.has(name) && !disabledNames.includes(name));
|
|
49381
49772
|
if (reEnabled.length > 0) logger?.warn(`Rovo Dev 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.`);
|
|
@@ -56534,8 +56925,9 @@ function convertClaudeToRulesyncPermissions(params) {
|
|
|
56534
56925
|
//#endregion
|
|
56535
56926
|
//#region src/features/permissions/cline-permissions.ts
|
|
56536
56927
|
/**
|
|
56537
|
-
* Cline CLI
|
|
56538
|
-
*
|
|
56928
|
+
* The legacy Cline CLI loaded command permissions from the `CLINE_COMMAND_PERMISSIONS`
|
|
56929
|
+
* environment variable. It supported only `allow` and `deny` (no `ask`), and only for
|
|
56930
|
+
* shell commands.
|
|
56539
56931
|
* Schema:
|
|
56540
56932
|
* ```json
|
|
56541
56933
|
* { "allow": ["pattern1", ...], "deny": ["pattern2", ...], "allowRedirects": false }
|
|
@@ -56543,7 +56935,14 @@ function convertClaudeToRulesyncPermissions(params) {
|
|
|
56543
56935
|
*
|
|
56544
56936
|
* Rulesync writes the JSON to `.cline/command-permissions.json` so users can do:
|
|
56545
56937
|
* `export CLINE_COMMAND_PERMISSIONS=$(cat .cline/command-permissions.json)`
|
|
56938
|
+
*
|
|
56939
|
+
* Deprecated: no Cline CLI 3 release reads `CLINE_COMMAND_PERMISSIONS`. Upstream removed
|
|
56940
|
+
* it from the docs in cline/cline@6354d209, and the CLI reference now says the CLI has no
|
|
56941
|
+
* allow or deny list for shell commands and recommends a `PreToolUse` hook instead
|
|
56942
|
+
* (https://docs.cline.bot/cli/cli-reference). The output is still generated for
|
|
56943
|
+
* compatibility, with a deprecation warning on every generation.
|
|
56546
56944
|
*/
|
|
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.";
|
|
56547
56946
|
const ClineCommandPermissionsSchema = zod_mini.z.looseObject({
|
|
56548
56947
|
allow: zod_mini.z.optional(zod_mini.z.array(zod_mini.z.string())),
|
|
56549
56948
|
deny: zod_mini.z.optional(zod_mini.z.array(zod_mini.z.string())),
|
|
@@ -56661,6 +57060,7 @@ var ClinePermissions = class ClinePermissions extends ToolPermissions {
|
|
|
56661
57060
|
});
|
|
56662
57061
|
}
|
|
56663
57062
|
static async fromRulesyncPermissions({ outputRoot = process.cwd(), rulesyncPermissions, global = false, logger }) {
|
|
57063
|
+
logger?.warn(CLINE_PERMISSIONS_DEPRECATION_WARNING);
|
|
56664
57064
|
const paths = ClinePermissions.getSettablePaths({ global });
|
|
56665
57065
|
const filePath = (0, node_path.join)(outputRoot, paths.relativeDirPath, paths.relativeFilePath);
|
|
56666
57066
|
const existingContent = await readFileContentOrNull(filePath) ?? "{}";
|
|
@@ -56690,7 +57090,7 @@ var ClinePermissions = class ClinePermissions extends ToolPermissions {
|
|
|
56690
57090
|
const mergedDeny = (0, es_toolkit.uniq)([...existing.deny ?? [], ...dedupedDeny]).toSorted();
|
|
56691
57091
|
const denySet = new Set(mergedDeny);
|
|
56692
57092
|
const collisions = dedupedAllow.filter((p) => denySet.has(p));
|
|
56693
|
-
if (collisions.length > 0) logger?.warn(`Cline command permissions: pattern(s) ${collisions.map((p) => `'${p}'`).join(", ")} appear in both 'allow' and 'deny'. Cline
|
|
57093
|
+
if (collisions.length > 0) logger?.warn(`Cline command permissions: pattern(s) ${collisions.map((p) => `'${p}'`).join(", ")} appear in both 'allow' and 'deny'. The legacy Cline CLI gave deny rules precedence, so the 'allow' entry has no effect. Consider removing the duplicate rule.`);
|
|
56694
57094
|
const next = {
|
|
56695
57095
|
...existing,
|
|
56696
57096
|
allow: dedupedAllow,
|
|
@@ -59973,9 +60373,9 @@ function managedContinueToolNames(config) {
|
|
|
59973
60373
|
*/
|
|
59974
60374
|
function parseContinuePermissionLists(parsed) {
|
|
59975
60375
|
return {
|
|
59976
|
-
exclude: isStringArray$
|
|
59977
|
-
ask: isStringArray$
|
|
59978
|
-
allow: isStringArray$
|
|
60376
|
+
exclude: isStringArray$1(parsed.exclude) ? parsed.exclude : [],
|
|
60377
|
+
ask: isStringArray$1(parsed.ask) ? parsed.ask : [],
|
|
60378
|
+
allow: isStringArray$1(parsed.allow) ? parsed.allow : []
|
|
59979
60379
|
};
|
|
59980
60380
|
}
|
|
59981
60381
|
/**
|
|
@@ -60058,6 +60458,193 @@ const AUTO_APPROVE_KEYS = {
|
|
|
60058
60458
|
* @see https://code.visualstudio.com/docs/agents/reference/ai-settings
|
|
60059
60459
|
*/
|
|
60060
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
|
+
}
|
|
60061
60648
|
function isCopilotDefaultPermissionMode(value) {
|
|
60062
60649
|
return COPILOT_DEFAULT_PERMISSION_MODES.some((mode) => mode === value);
|
|
60063
60650
|
}
|
|
@@ -60102,7 +60689,9 @@ function buildAutoApproveValue(rules) {
|
|
|
60102
60689
|
* hand-written edits or urls map.
|
|
60103
60690
|
* The `copilot.defaultPermissionMode` override writes `chat.permissions.default`
|
|
60104
60691
|
* (the starting permission level of new chat sessions); without it the key is
|
|
60105
|
-
* 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.
|
|
60106
60695
|
* Only project scope is modeled: VS Code's user-scope settings.json lives at a
|
|
60107
60696
|
* platform-dependent path outside rulesync's home-relative global model.
|
|
60108
60697
|
*/
|
|
@@ -60150,6 +60739,26 @@ var CopilotPermissions = class CopilotPermissions extends ToolPermissions {
|
|
|
60150
60739
|
}
|
|
60151
60740
|
const defaultPermissionMode = config.copilot?.defaultPermissionMode;
|
|
60152
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
|
+
}
|
|
60153
60762
|
return new CopilotPermissions({
|
|
60154
60763
|
outputRoot,
|
|
60155
60764
|
relativeDirPath: paths.relativeDirPath,
|
|
@@ -60186,9 +60795,14 @@ var CopilotPermissions = class CopilotPermissions extends ToolPermissions {
|
|
|
60186
60795
|
if (Object.keys(rules).length > 0) permission[category] = rules;
|
|
60187
60796
|
}
|
|
60188
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
|
+
};
|
|
60189
60803
|
return this.toRulesyncPermissionsDefault({ fileContent: JSON.stringify({
|
|
60190
60804
|
permission,
|
|
60191
|
-
...
|
|
60805
|
+
...Object.keys(copilot).length > 0 && { copilot }
|
|
60192
60806
|
}, null, 2) });
|
|
60193
60807
|
}
|
|
60194
60808
|
validate() {
|
|
@@ -60502,8 +61116,8 @@ var CrushPermissions = class CrushPermissions extends ToolPermissions {
|
|
|
60502
61116
|
logger
|
|
60503
61117
|
});
|
|
60504
61118
|
const managedToolNames = managedCrushToolNames(config);
|
|
60505
|
-
const preservedAllowed = (isStringArray$
|
|
60506
|
-
const preservedDisabled = (isStringArray$
|
|
61119
|
+
const preservedAllowed = (isStringArray$1(existingPermissions?.["allowed_tools"]) ? existingPermissions[CRUSH_ALLOWED_TOOLS_KEY] : []).filter((entry) => entry.includes(":") || !managedToolNames.has(entry));
|
|
61120
|
+
const preservedDisabled = (isStringArray$1(existingOptions?.["disabled_tools"]) ? existingOptions[CRUSH_DISABLED_TOOLS_KEY] : []).filter((entry) => !managedToolNames.has(entry));
|
|
60507
61121
|
const allowedList = uniq$11([...preservedAllowed, ...generated.allowed]);
|
|
60508
61122
|
const disabledList = uniq$11([...preservedDisabled, ...generated.disabled]);
|
|
60509
61123
|
const patch = {};
|
|
@@ -60529,8 +61143,8 @@ var CrushPermissions = class CrushPermissions extends ToolPermissions {
|
|
|
60529
61143
|
const permissions = isRecord$1(this.json["permissions"]) ? this.json[CRUSH_PERMISSIONS_KEY] : {};
|
|
60530
61144
|
const options = isRecord$1(this.json["options"]) ? this.json[CRUSH_OPTIONS_KEY] : {};
|
|
60531
61145
|
const rulesyncConfig = convertCrushListsToRulesync({
|
|
60532
|
-
allowed: isStringArray$
|
|
60533
|
-
disabled: isStringArray$
|
|
61146
|
+
allowed: isStringArray$1(permissions["allowed_tools"]) ? permissions[CRUSH_ALLOWED_TOOLS_KEY] : [],
|
|
61147
|
+
disabled: isStringArray$1(options["disabled_tools"]) ? options[CRUSH_DISABLED_TOOLS_KEY] : []
|
|
60534
61148
|
});
|
|
60535
61149
|
return this.toRulesyncPermissionsDefault({ fileContent: JSON.stringify(rulesyncConfig, null, 2) });
|
|
60536
61150
|
}
|
|
@@ -62705,7 +63319,7 @@ function convertRulesyncToGoosePermissionConfig({ config, logger }) {
|
|
|
62705
63319
|
function convertGoosePermissionConfigToRulesync(userPermission) {
|
|
62706
63320
|
const permission = {};
|
|
62707
63321
|
for (const key of GOOSE_PERMISSION_LIST_KEYS) {
|
|
62708
|
-
const toolNames = isStringArray$
|
|
63322
|
+
const toolNames = isStringArray$1(userPermission[key]) ? userPermission[key] : [];
|
|
62709
63323
|
const action = GOOSE_LIST_TO_ACTION[key];
|
|
62710
63324
|
for (const toolName of toolNames) for (const category of GOOSE_TO_RULESYNC_TOOL_NAMES[toolName] ?? [toolName]) {
|
|
62711
63325
|
permission[category] ??= {};
|
|
@@ -63053,7 +63667,7 @@ const ACTION_RANK$3 = {
|
|
|
63053
63667
|
* (mirrors the Cursor adapter's preservation of unmanaged types).
|
|
63054
63668
|
*/
|
|
63055
63669
|
function unmanagedEntries(existingPermission, key) {
|
|
63056
|
-
return (isStringArray$
|
|
63670
|
+
return (isStringArray$1(existingPermission[key]) ? existingPermission[key] : []).filter((entry) => parseGrokEntry(entry) === null);
|
|
63057
63671
|
}
|
|
63058
63672
|
/**
|
|
63059
63673
|
* Bucket canonical rules into Grok's `[permission]` allow/deny/ask arrays of
|
|
@@ -63110,7 +63724,7 @@ function warnRemovedAllToolsRestrictions({ existingPermission, ranked, logger })
|
|
|
63110
63724
|
const generated = ranked.get(GROK_ALL_TOOLS_ENTRY);
|
|
63111
63725
|
for (const action of ["deny", "ask"]) {
|
|
63112
63726
|
const entries = existingPermission[action];
|
|
63113
|
-
if (!isStringArray$
|
|
63727
|
+
if (!isStringArray$1(entries)) continue;
|
|
63114
63728
|
if (!entries.some((entry) => parseGrokEntry(entry)?.category === "*")) continue;
|
|
63115
63729
|
if (generated !== void 0 && ACTION_RANK$3[generated] >= ACTION_RANK$3[action]) continue;
|
|
63116
63730
|
logger.warn(`Grok CLI permissions: removing the existing bare '*' entry from the '${action}' array — Rulesync now manages it through the all-tools '*' category, and the canonical config has no '*' '${action}' rule. Add "*": { "*": "${action}" } to keep restricting every tool.`);
|
|
@@ -63141,9 +63755,9 @@ function warnEntriesShadowedByAllTools({ ranked, logger }) {
|
|
|
63141
63755
|
* described more than once resolves to the strictest action.
|
|
63142
63756
|
*/
|
|
63143
63757
|
function parseGrokPermissionArrays(permission) {
|
|
63144
|
-
const allow = isStringArray$
|
|
63145
|
-
const deny = isStringArray$
|
|
63146
|
-
const ask = isStringArray$
|
|
63758
|
+
const allow = isStringArray$1(permission.allow) ? permission.allow : void 0;
|
|
63759
|
+
const deny = isStringArray$1(permission.deny) ? permission.deny : void 0;
|
|
63760
|
+
const ask = isStringArray$1(permission.ask) ? permission.ask : void 0;
|
|
63147
63761
|
const rules = Array.isArray(permission[GROKCLI_PERMISSION_RULES_KEY]) ? permission[GROKCLI_PERMISSION_RULES_KEY] : void 0;
|
|
63148
63762
|
if ((allow?.length ?? 0) + (deny?.length ?? 0) + (ask?.length ?? 0) + (rules?.length ?? 0) === 0) return null;
|
|
63149
63763
|
const result = {};
|
|
@@ -63351,20 +63965,20 @@ var HermesagentPermissions = class HermesagentPermissions extends ToolPermission
|
|
|
63351
63965
|
const permission = clonePermissionBlock(provenance.permission);
|
|
63352
63966
|
reconcileCommandAllowlist({
|
|
63353
63967
|
permission,
|
|
63354
|
-
commandAllowlist: isStringArray$
|
|
63968
|
+
commandAllowlist: isStringArray$1(config.command_allowlist) ? config.command_allowlist : []
|
|
63355
63969
|
});
|
|
63356
63970
|
const approvals = isRecord$1(config.approvals) ? config.approvals : {};
|
|
63357
63971
|
reconcileNativeDenies({
|
|
63358
63972
|
permission,
|
|
63359
63973
|
category: SHELL_PERMISSION_CATEGORY,
|
|
63360
|
-
patterns: isStringArray$
|
|
63974
|
+
patterns: isStringArray$1(approvals.deny) ? approvals.deny : []
|
|
63361
63975
|
});
|
|
63362
63976
|
const security = isRecord$1(config.security) ? config.security : {};
|
|
63363
63977
|
const websiteBlocklist = isRecord$1(security.website_blocklist) ? security.website_blocklist : {};
|
|
63364
63978
|
reconcileNativeDenies({
|
|
63365
63979
|
permission,
|
|
63366
63980
|
category: WEBFETCH_PERMISSION_CATEGORY,
|
|
63367
|
-
patterns: websiteBlocklist.enabled === true && isStringArray$
|
|
63981
|
+
patterns: websiteBlocklist.enabled === true && isStringArray$1(websiteBlocklist.domains) ? websiteBlocklist.domains : []
|
|
63368
63982
|
});
|
|
63369
63983
|
removeEmptyCategories(permission);
|
|
63370
63984
|
const { permission: _permission, hermes: _hermes, ...otherProvenance } = provenance;
|
|
@@ -64758,13 +65372,13 @@ function classifyExistingEntry({ emitted, capability, pattern, effect }) {
|
|
|
64758
65372
|
* how many entries rulesync does not generate.
|
|
64759
65373
|
*/
|
|
64760
65374
|
function mergeExistingRule({ rule, emitted, logger }) {
|
|
64761
|
-
if (!isRecord$1(rule) || typeof rule.capability !== "string" || rule.exclude !== void 0 || !MANAGED_CAPABILITIES.has(rule.capability) || rule.match !== void 0 && !isStringArray$
|
|
65375
|
+
if (!isRecord$1(rule) || typeof rule.capability !== "string" || rule.exclude !== void 0 || !MANAGED_CAPABILITIES.has(rule.capability) || rule.match !== void 0 && !isStringArray$1(rule.match)) return {
|
|
64762
65376
|
rule,
|
|
64763
65377
|
unrelatedEntries: 0
|
|
64764
65378
|
};
|
|
64765
65379
|
const capability = rule.capability;
|
|
64766
65380
|
const isCatchAll = rule.match === void 0;
|
|
64767
|
-
const patterns = isStringArray$
|
|
65381
|
+
const patterns = isStringArray$1(rule.match) ? rule.match : [CATCH_ALL_PATTERN$6];
|
|
64768
65382
|
if (patterns.length === 0) return {
|
|
64769
65383
|
rule,
|
|
64770
65384
|
unrelatedEntries: 0
|
|
@@ -64948,7 +65562,7 @@ var KiroCapabilityPermissions = class KiroCapabilityPermissions extends ToolPerm
|
|
|
64948
65562
|
for (const rule of rules) {
|
|
64949
65563
|
if (!isImportableRule(rule) || !isRecord$1(rule) || !isPermissionAction$1(rule.effect)) continue;
|
|
64950
65564
|
const category = KIRO_CAPABILITY_TO_CANONICAL[rule.capability];
|
|
64951
|
-
const patterns = rule.match === void 0 ? [CATCH_ALL_PATTERN$6] : isStringArray$
|
|
65565
|
+
const patterns = rule.match === void 0 ? [CATCH_ALL_PATTERN$6] : isStringArray$1(rule.match) ? rule.match : [];
|
|
64952
65566
|
const categoryRules = permission[category] ??= {};
|
|
64953
65567
|
for (const pattern of patterns) {
|
|
64954
65568
|
const previous = categoryRules[pattern];
|
|
@@ -66666,8 +67280,8 @@ function importPoolTool({ toolConfig, bucket }) {
|
|
|
66666
67280
|
bucket[CATCH_ALL_PATTERN$3] = "deny";
|
|
66667
67281
|
return;
|
|
66668
67282
|
}
|
|
66669
|
-
const denied = isStringArray$
|
|
66670
|
-
const allowed = isStringArray$
|
|
67283
|
+
const denied = isStringArray$1(toolConfig[POOL_DENY_KEY]) ? toolConfig[POOL_DENY_KEY] : [];
|
|
67284
|
+
const allowed = isStringArray$1(toolConfig[POOL_ALLOW_KEY]) ? toolConfig[POOL_ALLOW_KEY] : [];
|
|
66671
67285
|
for (const pattern of denied.filter(isUsablePattern)) bucket[pattern] = "deny";
|
|
66672
67286
|
for (const pattern of allowed.filter(isUsablePattern)) bucket[pattern] ??= "allow";
|
|
66673
67287
|
}
|
|
@@ -68872,7 +69486,7 @@ function convertRovodevToolPermissionsToRulesync(toolPermissions) {
|
|
|
68872
69486
|
permission[category] ??= {};
|
|
68873
69487
|
permission[category][CATCH_ALL_PATTERN$1] = levels.reduce((strictest, level) => strictestAction(strictest, level ?? implicitLevel), permission[category][CATCH_ALL_PATTERN$1]);
|
|
68874
69488
|
}
|
|
68875
|
-
if (isStringArray$
|
|
69489
|
+
if (isStringArray$1(toolPermissions.allowedExternalPaths)) for (const path of toolPermissions.allowedExternalPaths) {
|
|
68876
69490
|
permission.read ??= {};
|
|
68877
69491
|
permission.read[path] ??= "allow";
|
|
68878
69492
|
}
|
|
@@ -69128,7 +69742,7 @@ function stripRefusedOverridePaths(override) {
|
|
|
69128
69742
|
*/
|
|
69129
69743
|
function collectTrustAffectingOverrideEntries({ tools, general }) {
|
|
69130
69744
|
const entries = [];
|
|
69131
|
-
const allowed = isStringArray$
|
|
69745
|
+
const allowed = isStringArray$1(tools[ALLOWED_KEY]) ? tools[ALLOWED_KEY] : [];
|
|
69132
69746
|
if (allowed.length > 0) entries.push({
|
|
69133
69747
|
label: `tools.allowed (${allowed.map(quoteValueForWarning).join(", ")})`,
|
|
69134
69748
|
reason: "auto-approves what it names as the override spells it; a non-shell entry is only checked against a whole-tool deny of its tool"
|
|
@@ -69279,7 +69893,7 @@ function withholdDeniedOverrideAllowed({ overrideVerbatimAllowed, exclude, logge
|
|
|
69279
69893
|
* hand-written entry went.
|
|
69280
69894
|
*/
|
|
69281
69895
|
function reportDroppedManagedEntries({ existingTools, allowedList, excludeList, logger }) {
|
|
69282
|
-
const dropped = (key, list) => (0, es_toolkit.uniq)(isStringArray$
|
|
69896
|
+
const dropped = (key, list) => (0, es_toolkit.uniq)(isStringArray$1(existingTools?.[key]) ? existingTools[key] : []).filter((entry) => {
|
|
69283
69897
|
if (list.includes(entry)) return false;
|
|
69284
69898
|
const toolName = parseTabnineEntry(entry)?.toolName;
|
|
69285
69899
|
return toolName === void 0 || !list.includes(toolName);
|
|
@@ -69361,7 +69975,7 @@ var TabninePermissions = class TabninePermissions extends ToolPermissions {
|
|
|
69361
69975
|
}
|
|
69362
69976
|
const overrideList = (key) => {
|
|
69363
69977
|
const value = overrideTools[key];
|
|
69364
|
-
if (value === void 0 || isStringArray$
|
|
69978
|
+
if (value === void 0 || isStringArray$1(value)) return value ?? [];
|
|
69365
69979
|
warnWithFallback(logger, `Tabnine CLI permissions: ignored tools.${key} of the tabnine override (${quoteValueForWarning(value)}); it must be a list of tool names.`);
|
|
69366
69980
|
return [];
|
|
69367
69981
|
};
|
|
@@ -69399,7 +70013,7 @@ var TabninePermissions = class TabninePermissions extends ToolPermissions {
|
|
|
69399
70013
|
if (ignoredOverrideKeys.length > 0) warnWithFallback(logger, `Tabnine CLI permissions: ignored ${ignoredOverrideKeys.length} key(s) of the tabnine override (${ignoredOverrideKeys.map(quoteValueForWarning).join(", ")}); only 'tools' and 'general' are written through the permissions feature.`);
|
|
69400
70014
|
const existingTools = this.existingToolsGroup(existingContent);
|
|
69401
70015
|
const managedToolNames = new Set(Object.keys(config.permission).map((category) => category === "bash" || category === "*" ? SHELL_TOOL_NAME : toTabnineToolName(category)));
|
|
69402
|
-
const preservedEntries = (key) => (isStringArray$
|
|
70016
|
+
const preservedEntries = (key) => (isStringArray$1(existingTools?.[key]) ? existingTools[key] : []).filter((entry) => {
|
|
69403
70017
|
const toolName = parseTabnineEntry(entry)?.toolName;
|
|
69404
70018
|
return toolName === void 0 || !managedToolNames.has(toolName);
|
|
69405
70019
|
});
|
|
@@ -69477,7 +70091,7 @@ var TabninePermissions = class TabninePermissions extends ToolPermissions {
|
|
|
69477
70091
|
if (rules !== void 0 && !Object.hasOwn(rules, pattern)) rules[pattern] = action;
|
|
69478
70092
|
};
|
|
69479
70093
|
for (const [key, action] of [[EXCLUDE_KEY, "deny"], [ALLOWED_KEY, "allow"]]) {
|
|
69480
|
-
const entries = isStringArray$
|
|
70094
|
+
const entries = isStringArray$1(tools[key]) ? tools[key] : [];
|
|
69481
70095
|
for (const entry of entries) {
|
|
69482
70096
|
const parsed = parseTabnineEntry(entry);
|
|
69483
70097
|
if (parsed === void 0) {
|
|
@@ -70345,12 +70959,12 @@ const WIDE_CHARACTERS_PATTERN = /\p{Emoji_Presentation}|\p{Emoji_Modifier_Base}|
|
|
|
70345
70959
|
* The ranges are the `East_Asian_Width=A` property of Unicode 17.0, taken from
|
|
70346
70960
|
* the table `get-east-asian-width` 1.6.0 carries and merged where two are
|
|
70347
70961
|
* adjacent. The table is written out here rather than read from the package so
|
|
70348
|
-
* that the one lookup adds no dependency of its own, and the drift test
|
|
70349
|
-
* `display-width.test.ts` is what keeps it honest: it loads the package
|
|
70350
|
-
*
|
|
70351
|
-
*
|
|
70352
|
-
*
|
|
70353
|
-
* the
|
|
70962
|
+
* that the one lookup adds no runtime dependency of its own, and the drift test
|
|
70963
|
+
* in `display-width.test.ts` is what keeps it honest: it loads the package,
|
|
70964
|
+
* pinned as a dev dependency, walks every code point, and fails when this
|
|
70965
|
+
* table and the property disagree in either direction. When that pin moves to
|
|
70966
|
+
* a newer Unicode, that test is what fails, and the table is regenerated from
|
|
70967
|
+
* the new property — its ranges, marks aside,
|
|
70354
70968
|
* merged where adjacent and written as escapes — with the version named here.
|
|
70355
70969
|
*
|
|
70356
70970
|
* Three stretches of the property are left out because they never reach this
|
|
@@ -72099,8 +72713,8 @@ var WarpPermissions = class WarpPermissions extends ToolPermissions {
|
|
|
72099
72713
|
const executionProfiles = isRecord$1(agents["execution_profiles"]) ? agents[EXECUTION_PROFILES_KEY] : void 0;
|
|
72100
72714
|
const defaultProfile = executionProfiles && isRecord$1(executionProfiles["default"]) ? executionProfiles[DEFAULT_PROFILE_KEY] : void 0;
|
|
72101
72715
|
const config = convertWarpToRulesyncPermissions({
|
|
72102
|
-
allow: defaultProfile ? isStringArray$
|
|
72103
|
-
deny: defaultProfile ? isStringArray$
|
|
72716
|
+
allow: defaultProfile ? isStringArray$1(defaultProfile["command_allowlist"]) ? defaultProfile[PROFILE_ALLOWLIST_KEY] : [] : isStringArray$1(profiles[ALLOWLIST_KEY]) ? profiles[ALLOWLIST_KEY] : [],
|
|
72717
|
+
deny: defaultProfile ? isStringArray$1(defaultProfile["command_denylist"]) ? defaultProfile[PROFILE_DENYLIST_KEY] : [] : isStringArray$1(profiles[DENYLIST_KEY]) ? profiles[DENYLIST_KEY] : []
|
|
72104
72718
|
});
|
|
72105
72719
|
const warpOverride = {};
|
|
72106
72720
|
for (const key of WARP_OVERRIDE_KEYS) if (profiles[key] !== void 0) warpOverride[key] = profiles[key];
|
|
@@ -72630,8 +73244,8 @@ var WarpcliPermissions = class WarpcliPermissions extends ToolPermissions {
|
|
|
72630
73244
|
const allowlist = defaultProfile[PROFILE_ALLOWLIST_KEY];
|
|
72631
73245
|
const denylist = defaultProfile[PROFILE_DENYLIST_KEY];
|
|
72632
73246
|
const config = convertWarpToRulesyncPermissions({
|
|
72633
|
-
allow: isStringArray$
|
|
72634
|
-
deny: isStringArray$
|
|
73247
|
+
allow: isStringArray$1(allowlist) ? allowlist : [],
|
|
73248
|
+
deny: isStringArray$1(denylist) ? denylist : []
|
|
72635
73249
|
});
|
|
72636
73250
|
const executionProfileOverride = liftExecutionProfileOverride(defaultProfile);
|
|
72637
73251
|
const warpcliOverride = {};
|
|
@@ -84646,7 +85260,7 @@ var BobSubagent = class extends RooSubagent {
|
|
|
84646
85260
|
const mode = super.toRooMode(rulesyncSubagent);
|
|
84647
85261
|
const frontmatter = rulesyncSubagent.getFrontmatter();
|
|
84648
85262
|
const bobSection = isRecord$1(frontmatter.bob) ? frontmatter.bob : {};
|
|
84649
|
-
if (isStringArray$
|
|
85263
|
+
if (isStringArray$1(bobSection.allowedSubagents)) mode.allowedSubagents = bobSection.allowedSubagents;
|
|
84650
85264
|
return mode;
|
|
84651
85265
|
}
|
|
84652
85266
|
};
|
|
@@ -90101,7 +90715,7 @@ var ZoocodeSubagent = class extends RooSubagent {
|
|
|
90101
90715
|
const mode = super.toRooMode(rulesyncSubagent);
|
|
90102
90716
|
const frontmatter = rulesyncSubagent.getFrontmatter();
|
|
90103
90717
|
const zoocodeSection = isRecord$1(frontmatter.zoocode) ? frontmatter.zoocode : {};
|
|
90104
|
-
if (isStringArray$
|
|
90718
|
+
if (isStringArray$1(zoocodeSection.allowedMcpServers)) mode.allowedMcpServers = zoocodeSection.allowedMcpServers;
|
|
90105
90719
|
return mode;
|
|
90106
90720
|
}
|
|
90107
90721
|
toRulesyncSubagents() {
|
|
@@ -90113,7 +90727,7 @@ var ZoocodeSubagent = class extends RooSubagent {
|
|
|
90113
90727
|
targets: ["zoocode"],
|
|
90114
90728
|
roo: restRooSection
|
|
90115
90729
|
};
|
|
90116
|
-
if (isStringArray$
|
|
90730
|
+
if (isStringArray$1(allowedMcpServers)) rebuilt.zoocode = { allowedMcpServers };
|
|
90117
90731
|
return new RulesyncSubagent({
|
|
90118
90732
|
outputRoot: ".",
|
|
90119
90733
|
frontmatter: rebuilt,
|
|
@@ -94054,7 +94668,8 @@ var NestedAgentsmdRule = class extends ToolRule {
|
|
|
94054
94668
|
* `<subprojectPath>/AGENTS.md` (project scope only); every other non-root rule
|
|
94055
94669
|
* folds into the root `AGENTS.md`, since Codex has no modular rules directory.
|
|
94056
94670
|
* The project files on the root-to-cwd chain share one `project_doc_max_bytes`
|
|
94057
|
-
* budget (32 KiB by default
|
|
94671
|
+
* budget (32 KiB by default, or the value set in the project's
|
|
94672
|
+
* `.codex/config.toml`), which the RulesProcessor warns about.
|
|
94058
94673
|
*
|
|
94059
94674
|
* @see https://learn.chatgpt.com/docs/agent-configuration/agents-md
|
|
94060
94675
|
*/
|
|
@@ -94066,6 +94681,29 @@ var CodexcliRule = class extends NestedAgentsmdRule {
|
|
|
94066
94681
|
};
|
|
94067
94682
|
}
|
|
94068
94683
|
};
|
|
94684
|
+
/**
|
|
94685
|
+
* Read the `project_doc_max_bytes` value set in the project's
|
|
94686
|
+
* `.codex/config.toml`, which Codex loads for trusted projects and which
|
|
94687
|
+
* overrides the 32 KiB default budget for project `AGENTS.md` files.
|
|
94688
|
+
*
|
|
94689
|
+
* Answers `undefined` when the file is missing, is not valid TOML, or does not
|
|
94690
|
+
* set the key to a positive integer, so the caller falls back to the default.
|
|
94691
|
+
*
|
|
94692
|
+
* @see https://learn.chatgpt.com/docs/config-file/config-reference
|
|
94693
|
+
*/
|
|
94694
|
+
async function readCodexcliProjectDocMaxBytes(outputRoot) {
|
|
94695
|
+
const content = await readFileContentOrNull((0, node_path.join)(outputRoot, CODEXCLI_DIR, CODEXCLI_MCP_FILE_NAME));
|
|
94696
|
+
if (content === null) return;
|
|
94697
|
+
let parsed;
|
|
94698
|
+
try {
|
|
94699
|
+
parsed = smol_toml.parse(content);
|
|
94700
|
+
} catch {
|
|
94701
|
+
return;
|
|
94702
|
+
}
|
|
94703
|
+
if (!isPlainObject$1(parsed)) return;
|
|
94704
|
+
const value = parsed.project_doc_max_bytes;
|
|
94705
|
+
return typeof value === "number" && Number.isSafeInteger(value) && value > 0 ? value : void 0;
|
|
94706
|
+
}
|
|
94069
94707
|
//#endregion
|
|
94070
94708
|
//#region src/features/rules/commandcode-rule.ts
|
|
94071
94709
|
var CommandcodeRule = class CommandcodeRule extends ToolRule {
|
|
@@ -99440,7 +100078,10 @@ const toolRuleFactories = /* @__PURE__ */ new Map([
|
|
|
99440
100078
|
collisionPolicy: "fold",
|
|
99441
100079
|
projectInstructionBudget: {
|
|
99442
100080
|
bytes: CODEXCLI_PROJECT_DOC_MAX_BYTES,
|
|
99443
|
-
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).",
|
|
100082
|
+
readConfiguredBytes: readCodexcliProjectDocMaxBytes,
|
|
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."
|
|
99444
100085
|
}
|
|
99445
100086
|
}
|
|
99446
100087
|
}],
|
|
@@ -99985,7 +100626,7 @@ var RulesProcessor = class extends FeatureProcessor {
|
|
|
99985
100626
|
factory
|
|
99986
100627
|
});
|
|
99987
100628
|
await this.warnForWarpLegacyRootFile(toolRules);
|
|
99988
|
-
this.warnForInstructionBudget({
|
|
100629
|
+
await this.warnForInstructionBudget({
|
|
99989
100630
|
toolRules,
|
|
99990
100631
|
meta
|
|
99991
100632
|
});
|
|
@@ -100006,14 +100647,20 @@ var RulesProcessor = class extends FeatureProcessor {
|
|
|
100006
100647
|
* once, at its shallowest directory. With several output roots the warning
|
|
100007
100648
|
* names the root it refers to. An output root below the git root is measured
|
|
100008
100649
|
* without the parent directories' files Codex would also load, so the
|
|
100009
|
-
* estimate can come out low there. The budget checked is the
|
|
100010
|
-
*
|
|
100650
|
+
* estimate can come out low there. The budget checked is the value set in the
|
|
100651
|
+
* output root's tool config when the tool supports one (Codex's
|
|
100652
|
+
* `project_doc_max_bytes` in `.codex/config.toml`), else the tool's default;
|
|
100653
|
+
* the warning names which one it used. Global scope is not
|
|
100011
100654
|
* checked because Codex loads the personal `~/.codex/AGENTS.md` outside the
|
|
100012
100655
|
* project budget.
|
|
100013
100656
|
*/
|
|
100014
|
-
warnForInstructionBudget({ toolRules, meta }) {
|
|
100657
|
+
async warnForInstructionBudget({ toolRules, meta }) {
|
|
100015
100658
|
const budget = meta.projectInstructionBudget;
|
|
100016
100659
|
if (budget === void 0 || this.global) return;
|
|
100660
|
+
const configuredBytes = await budget.readConfiguredBytes?.(this.outputRoot);
|
|
100661
|
+
const limitBytes = configuredBytes ?? budget.bytes;
|
|
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;
|
|
100017
100664
|
const files = toolRules.map((rule) => ({
|
|
100018
100665
|
dir: normalizeRelativeDir(rule.getRelativeDirPath()),
|
|
100019
100666
|
path: toPosixPath((0, node_path.join)(rule.getRelativeDirPath(), rule.getRelativeFilePath())),
|
|
@@ -100023,8 +100670,8 @@ var RulesProcessor = class extends FeatureProcessor {
|
|
|
100023
100670
|
const location = this.outputRoot === process.cwd() ? "" : ` under '${this.outputRoot}'`;
|
|
100024
100671
|
const rootFiles = files.filter(({ dir }) => dir === ".");
|
|
100025
100672
|
const rootBytes = rootFiles.reduce((sum, { bytes }) => sum + bytes, 0);
|
|
100026
|
-
if (rootBytes >
|
|
100027
|
-
this.logger.warn(`The generated ${this.toolTarget} root instruction file${location} (${rootFiles.map(({ path }) => path).join(", ")}) is ${rootBytes} bytes, over the ${
|
|
100673
|
+
if (rootBytes > limitBytes) {
|
|
100674
|
+
this.logger.warn(`The generated ${this.toolTarget} root instruction file${location} (${rootFiles.map(({ path }) => path).join(", ")}) is ${rootBytes} bytes, over the ${limitLabel}. ${remedy}`);
|
|
100028
100675
|
return;
|
|
100029
100676
|
}
|
|
100030
100677
|
const reportedDirs = [];
|
|
@@ -100033,9 +100680,9 @@ var RulesProcessor = class extends FeatureProcessor {
|
|
|
100033
100680
|
if (reportedDirs.some((dir) => isAncestorOrSelf(dir, file.dir))) continue;
|
|
100034
100681
|
const chain = files.filter(({ dir }) => isAncestorOrSelf(dir, file.dir));
|
|
100035
100682
|
const chainBytes = chain.reduce((sum, { bytes }) => sum + bytes, 0);
|
|
100036
|
-
if (chainBytes >
|
|
100683
|
+
if (chainBytes > limitBytes) {
|
|
100037
100684
|
reportedDirs.push(file.dir);
|
|
100038
|
-
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 ${
|
|
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}`);
|
|
100039
100686
|
}
|
|
100040
100687
|
}
|
|
100041
100688
|
}
|